diff --git a/.github/workflows/create_release_on_tag.yml b/.github/workflows/create_release_on_tag.yml new file mode 100644 index 000000000..a27f90fa8 --- /dev/null +++ b/.github/workflows/create_release_on_tag.yml @@ -0,0 +1,50 @@ +# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Create Release + +on: + push: + tags: + - 'v*' + branches: ["main"] + +jobs: + create-release: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout GCM + uses: actions/checkout@v3 + - name: Checkout Util + uses: actions/checkout@v3 + with: + repository: HHS/ASPR-ms-util + path: util + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + cache: maven + - name: Build Util + run: mvn clean install -DskipTests --file util/pom.xml + - name: Build GCM + run: mvn clean install -Prelease --file gcm/pom.xml + - name: Get Version + run: | + echo "version=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout --file gcm/pom.xml)" >> "$GITHUB_ENV" + - name: Make Release + uses: ncipollo/release-action@v1 + with: + artifacts: "gcm/target/gcm-${{ env.version }}.jar,gcm/target/gcm-${{ env.version }}-sources.jar,gcm/target/gcm-${{ env.version }}-javadoc.jar" + prerelease: ${{ endsWith(env.version, 'SNAPSHOT') || contains(env.version, '-RC') }} + name: "v${{ env.version }}" + tag: "v${{ env.version }}" + generateReleaseNotes: true diff --git a/.github/workflows/dev-pre-mavencentral.yml b/.github/workflows/dev-pre-mavencentral.yml new file mode 100644 index 000000000..4befca617 --- /dev/null +++ b/.github/workflows/dev-pre-mavencentral.yml @@ -0,0 +1,44 @@ +# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Development Build Pre Maven Central + +on: + push: + branches: [ "dev" ] + paths: + - '**.xml' + - '**.java' + pull_request: + branches: [ "dev", "main" ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout GCM + uses: actions/checkout@v3 + - name: Checkout Util + uses: actions/checkout@v3 + with: + repository: HHS/ASPR-ms-util + path: util + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + cache: maven + - name: Build Util + run: mvn clean install -DskipTests --file util/pom.xml + - name: Build GCM + run: mvn clean package --file gcm/pom.xml + + # Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive + # - name: Update dependency graph + # uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6 diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..8524ee842 --- /dev/null +++ b/.gitignore @@ -0,0 +1,329 @@ +################################### +# START Custom +################################### + +.classpath +.project +.idea + +# Specifically for intelliJ. Duplicative with part of the intelliJ section, but not a full replacement +.idea/ +*.iml + +################################### +# END Custom +################################### + +################################### +# START Java +# https://github.com/github/gitignore/blob/master/Java.gitignore +################################### + +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +################################### +# END Java +################################### + +################################### +# START R +# https://github.com/github/gitignore/blob/master/R.gitignore +################################### + +# History files +.Rhistory +.Rapp.history + +# Session Data files +.RData + +# Example code in package build process +*-Ex.R + +# Output files from R CMD build +/*.tar.gz + +# Output files from R CMD check +/*.Rcheck/ + +# RStudio files +.Rproj.user/ + +# produced vignettes +vignettes/*.html +vignettes/*.pdf + +# OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3 +.httr-oauth + +# knitr and R markdown default cache directories +/*_cache/ +/cache/ + +# Temporary files created by R markdown +*.utf8.md +*.knit.md + +################################### +# END R +################################### + +################################### +# START Eclipse +# https://github.com/github/gitignore/blob/master/Global/Eclipse.gitignore +################################### + +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# PyDev specific (Python IDE for Eclipse) +*.pydevproject + +# CDT-specific (C/C++ Development Tooling) +.cproject + +# Java annotation processor (APT) +.factorypath + +# PDT-specific (PHP Development Tools) +.buildpath + +# sbteclipse plugin +.target + +# Tern plugin +.tern-project + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + +# Code Recommenders +.recommenders/ + +# Scala IDE specific (Scala & Java development for Eclipse) +.cache-main +.scala_dependencies +.worksheet + +################################### +# END Eclipse +################################### + +################################### +# START JetBrains +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 +# https://github.com/github/gitignore/blob/master/Global/JetBrains.gitignore +################################### +#Ignore all .idea folder content +.idea/ +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# CMake +cmake-build-debug/ + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +################################### +# END JetBrains +################################### + +################################### +# START Maven +# https://github.com/github/gitignore/blob/master/Maven.gitignore +################################### + +target/ +.flattened-pom.xml +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties + +# Avoid ignoring Maven wrapper jar file (.jar files are usually ignored) +!/.mvn/wrapper/maven-wrapper.jar + +################################### +# END Maven +################################### + +################################### +# START Windows +# https://github.com/github/gitignore/blob/master/Global/Windows.gitignore +################################### + +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +################################### +# END Windows +################################### + +################################### +# START MS Office +# https://github.com/github/gitignore/blob/master/Global/MicrosoftOffice.gitignore +################################### + +*.tmp + +# Word temporary +~$*.doc* + +# Excel temporary +~$*.xls* + +# Excel Backup File +*.xlk + +# PowerPoint temporary +~$*.ppt* + +# Visio autosave temporary files +*.~vsd* + +################################### +# END MS Office +################################### + +################################### +# START MacOS +# https://github.com/github/gitignore/blob/master/Global/macOS.gitignore +################################### + +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +################################### +# END MacOS +################################### + +# VSCode +.vscode \ No newline at end of file diff --git a/README.md b/README.md index 3e1ec2b4c..d8d442783 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,61 @@ -ASPR-8 -gcm3 repo +[![GPL LICENSE][license-shield]][license-url] +[![GitHub tag (with filter)][tag-shield]][tag-url] +[![GitHub contributors][contributors-shield]][contributors-url] +[![GitHub Workflow Status (with event)][dev-build-shield]][dev-build-url] + + + +# General Computational Model +This repository contains the source code for the General Computational Model herein reffered to as GCM, along with a set of tutorials that have been created to aide new users with using this model. + +## What is GCM? +GCM is a Java based simulation framework for building disease progression models. +Users of GCM should have a general familiarity with Java and object oriented programming and would benefit from some exposure to event based modeling. + +## Overview +THere are 3 core tenants to GCM. + +### Simulation +GCM is an event based simulation composed of data managers, actors and an event engine. +The data managers contain the state of the simulation and generate events when that state changes. +The actors contain the business logic of your model and act on the data managers. +The engine transports events generated by the data managers to any data managers and actors that subscribe to those events. + +### Plugins +Data managers and actors are organized into plugins. A GCM model is thus composed of the core simulation and a suite of plugins. +The plugin architecture provides for the scalable reuse of concepts and capabilities between models. +GCM is provided with a set of existing plugins that define many of the concepts useful to a broad range of models such as the management of people, their properties, social group structures and the like. + +The modeler is free to compose a model from their choice of plugins. + +### Experiment +GCM also provides an experiment management system. +Each plugin contains zero to many data objects that define the initial state of its actors and data managers. Each such data object may be altered freely. +The complete set of all combinations (scenarios) of the variant plugin data objects form an experiment and a separate simulation instance is executed for each combination. + +## Requirements +- Maven 3.8.x +- Java 17 +- Your favroite IDE for developing Java projects +- Modeling Utilities located [here](https://github.com/HHS/ASPR-ms-util) + +## Building +Once you have cloned the repo and imported it into your favorite IDE, navigate into the gcm directory on the command line. +Once there, run the following command: ```mvn clean install``` +That's all there is to building the project. +After running the above command, the next place you should start looking is at the Modeling Guide located in [doc](doc) and following the lessons. + + +## License +Distributed under the GPLv3 License. See [LICENSE](LICENSE) for more information. + + + +[contributors-shield]: https://img.shields.io/github/contributors/HHS/ASPR-8 +[contributors-url]: https://github.com/HHS/ASPR-8/graphs/contributors +[tag-shield]: https://img.shields.io/github/v/tag/HHS/ASPR-8 +[tag-url]: https://github.com/HHS/ASPR-8/releases/tag/v4.0.0-RC1 +[license-shield]: https://img.shields.io/github/license/HHS/ASPR-8 +[license-url]: LICENSE +[dev-build-shield]: https://img.shields.io/github/actions/workflow/status/HHS/ASPR-8/dev-pre-mavencentral.yml?label=dev-build +[dev-build-url]: https://github.com/HHS/ASPR-8/actions/workflows/dev-pre-mavencentral.yml diff --git a/demos/gcm-taskit/.gitignore b/demos/gcm-taskit/.gitignore new file mode 100644 index 000000000..b554593a2 --- /dev/null +++ b/demos/gcm-taskit/.gitignore @@ -0,0 +1,327 @@ +################################### +# START Custom +################################### + +.classpath +.project +.idea + +# Specifically for intelliJ. Duplicative with part of the intelliJ section, but not a full replacement +.idea/ +*.iml + +################################### +# END Custom +################################### + +################################### +# START Java +# https://github.com/github/gitignore/blob/master/Java.gitignore +################################### + +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +################################### +# END Java +################################### + +################################### +# START R +# https://github.com/github/gitignore/blob/master/R.gitignore +################################### + +# History files +.Rhistory +.Rapp.history + +# Session Data files +.RData + +# Example code in package build process +*-Ex.R + +# Output files from R CMD build +/*.tar.gz + +# Output files from R CMD check +/*.Rcheck/ + +# RStudio files +.Rproj.user/ + +# produced vignettes +vignettes/*.html +vignettes/*.pdf + +# OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3 +.httr-oauth + +# knitr and R markdown default cache directories +/*_cache/ +/cache/ + +# Temporary files created by R markdown +*.utf8.md +*.knit.md + +################################### +# END R +################################### + +################################### +# START Eclipse +# https://github.com/github/gitignore/blob/master/Global/Eclipse.gitignore +################################### + +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# PyDev specific (Python IDE for Eclipse) +*.pydevproject + +# CDT-specific (C/C++ Development Tooling) +.cproject + +# Java annotation processor (APT) +.factorypath + +# PDT-specific (PHP Development Tools) +.buildpath + +# sbteclipse plugin +.target + +# Tern plugin +.tern-project + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + +# Code Recommenders +.recommenders/ + +# Scala IDE specific (Scala & Java development for Eclipse) +.cache-main +.scala_dependencies +.worksheet + +################################### +# END Eclipse +################################### + +################################### +# START JetBrains +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 +# https://github.com/github/gitignore/blob/master/Global/JetBrains.gitignore +################################### + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# CMake +cmake-build-debug/ + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +################################### +# END JetBrains +################################### + +################################### +# START Maven +# https://github.com/github/gitignore/blob/master/Maven.gitignore +################################### + +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties + +# Avoid ignoring Maven wrapper jar file (.jar files are usually ignored) +!/.mvn/wrapper/maven-wrapper.jar + +################################### +# END Maven +################################### + +################################### +# START Windows +# https://github.com/github/gitignore/blob/master/Global/Windows.gitignore +################################### + +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +################################### +# END Windows +################################### + +################################### +# START MS Office +# https://github.com/github/gitignore/blob/master/Global/MicrosoftOffice.gitignore +################################### + +*.tmp + +# Word temporary +~$*.doc* + +# Excel temporary +~$*.xls* + +# Excel Backup File +*.xlk + +# PowerPoint temporary +~$*.ppt* + +# Visio autosave temporary files +*.~vsd* + +################################### +# END MS Office +################################### + +################################### +# START MacOS +# https://github.com/github/gitignore/blob/master/Global/macOS.gitignore +################################### + +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +################################### +# END MacOS +################################### + + +.vscode \ No newline at end of file diff --git a/demos/gcm-taskit/pom.xml b/demos/gcm-taskit/pom.xml new file mode 100644 index 000000000..cd84bbdf4 --- /dev/null +++ b/demos/gcm-taskit/pom.xml @@ -0,0 +1,193 @@ + + + 4.0.0 + + + + ASPR + https://www.phe.gov + + + + gov.hhs.aspr.ms.gcm.demos + gcm-taskit + ${revision} + jar + serialization demo + Demonstrate serialization integration with simulation + + + + UTF-8 + 17 + 17 + ${gcm.version} + + + 1.7.0 + 3.3.0 + 1.3.0 + 0.6.1 + 1.3.2 + 3.2.1 + + + 4.0.0-SNAPSHOT + 2.4.0-SNAPSHOT + 3.21.12 + + + + + + gov.hhs.aspr.ms + gcm + ${gcm.version} + + + gov.hhs.aspr.ms.gcm.taskit.protobuf + gcm-taskit-protobuf + ${gcm-taskit-protobuf.version} + + + + + + + Shawn Hatch + Leidos + https://www.leidos.com + + + + + + + + kr.motd.maven + os-maven-plugin + ${os-maven-plugin.version} + + + initialize + + detect + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + ${build-helper-maven-plugin.version} + + + generate-sources + + add-source + + + + ${project.build.directory}/generated-sources/protobuf/java + + + + + + + + + org.codehaus.mojo + flatten-maven-plugin + ${flatten-maven-plugin.version} + + + + flatten + process-resources + + flatten + + + true + + + + + flatten.clean + clean + + clean + + + + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + ${protobuf-maven-plugin.version} + + + + compile + test-compile + + + + + com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier} + ${project.basedir}/src/main/proto + + + + + com.google.code.maven-replacer-plugin + maven-replacer-plugin + ${maven-replacer-plugin.version} + + + prepare-package + + replace + + + + + + target/generated-sources/**/*.java + + true + + MULTILINE + + + + ^(@SuppressWarnings\(.*?\)\s+)?public final class + @SuppressWarnings("all") public final class + + + + + + + org.apache.maven.plugins + maven-source-plugin + ${maven-source-plugin.version} + + + attach-sources + verify + + jar-no-fork + + + + + + + + \ No newline at end of file diff --git a/demos/gcm-taskit/src/main/java/lesson/SerializationDemonstration.java b/demos/gcm-taskit/src/main/java/lesson/SerializationDemonstration.java new file mode 100644 index 000000000..8fd007f02 --- /dev/null +++ b/demos/gcm-taskit/src/main/java/lesson/SerializationDemonstration.java @@ -0,0 +1,455 @@ +package lesson; + +import java.io.File; +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.nucleus.Dimension; +import gov.hhs.aspr.ms.gcm.nucleus.Experiment; +import gov.hhs.aspr.ms.gcm.nucleus.ExperimentContext; +import gov.hhs.aspr.ms.gcm.nucleus.ExperimentParameterData; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.SimulationState; +import gov.hhs.aspr.ms.gcm.nucleus.SimulationStateCollector; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.GlobalPropertiesPlugin; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertyDimension; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePlugin; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeoplePluginData; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.PersonPropertiesPlugin; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.regions.RegionsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.reports.ReportsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.NIOReportItemHandler; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.taskit.protobuf.nucleus.NucleusTranslator; +import gov.hhs.aspr.ms.gcm.taskit.protobuf.plugins.globalproperties.GlobalPropertiesTranslator; +import gov.hhs.aspr.ms.gcm.taskit.protobuf.plugins.globalproperties.data.input.GlobalPropertiesPluginDataInput; +import gov.hhs.aspr.ms.gcm.taskit.protobuf.plugins.people.PeopleTranslator; +import gov.hhs.aspr.ms.gcm.taskit.protobuf.plugins.people.data.input.PeoplePluginDataInput; +import gov.hhs.aspr.ms.gcm.taskit.protobuf.plugins.personproperties.PersonPropertiesTranslator; +import gov.hhs.aspr.ms.gcm.taskit.protobuf.plugins.properties.PropertiesTranslator; +import gov.hhs.aspr.ms.gcm.taskit.protobuf.plugins.regions.RegionsTranslator; +import gov.hhs.aspr.ms.gcm.taskit.protobuf.plugins.regions.data.input.RegionsPluginDataInput; +import gov.hhs.aspr.ms.gcm.taskit.protobuf.plugins.reports.ReportsTranslator; +import gov.hhs.aspr.ms.gcm.taskit.protobuf.plugins.stochastics.StochasticsTranslator; +import gov.hhs.aspr.ms.taskit.core.TranslationController; +import gov.hhs.aspr.ms.taskit.core.Translator; +import gov.hhs.aspr.ms.taskit.protobuf.ProtobufTranslationEngine; +import lesson.plugins.model.GlobalProperty; +import lesson.plugins.model.ModelPlugin; +import lesson.plugins.model.ModelReportLabel; +import lesson.plugins.model.PersonProperty; +import lesson.plugins.model.Region; +import lesson.translatorSpecs.GlobalPropertyTranslatorSpec; +import lesson.translatorSpecs.PersonPropertyTranslatorSpec; +import lesson.translatorSpecs.RegionTranslatorSpec; +import util.random.RandomGeneratorProvider; +import util.time.Stopwatch; + +public final class SerializationDemonstration { + + private final String personPropertiesOutputFileName = "personPropertiesOutput.json"; + private final String globalPropertiesOutputFileName = "globalPropertiesOutput.json"; + private final String regionsOutputFileName = "regionsOutput.json"; + private final String peopleOutputFileName = "peopleOutput.json"; + private final String stochasticsOutputFileName = "stochasticsOutput.json"; + private final String simStateOutputFileName = "simStateOutput.json"; + private final String peopleInputFileName = "/peopleInput.json"; + private final String regionsInputFileName = "/regionsInput.json"; + private final String globalPropertiesInputFileName = "/globalPropertiesInput.json"; + + private final Path outputDirectory; + private TranslationController writingTranslationController; + private TranslationController readingTranslationController; + + // test + private SerializationDemonstration(Path outputDirectory) { + this.outputDirectory = outputDirectory; + + Path peopleInputPath; + Path regionsInputPath; + Path globalPropsInputPath; + + try { + peopleInputPath = Paths.get(this.getClass().getResource(peopleInputFileName).toURI()); + regionsInputPath = Paths.get(this.getClass().getResource(regionsInputFileName).toURI()); + globalPropsInputPath = Paths.get(this.getClass().getResource(globalPropertiesInputFileName).toURI()); + + this.readingTranslationController = TranslationController + .builder() + .addTranslator( + PersonPropertiesTranslator.getTranslator()) + .addTranslator(PropertiesTranslator.getTranslator()) + .addTranslator(PeopleTranslator.getTranslator()) + .addTranslator(RegionsTranslator.getTranslator()) + .addTranslator( + GlobalPropertiesTranslator.getTranslator()) + .addTranslator(ReportsTranslator.getTranslator()) + .setTranslationEngineBuilder(ProtobufTranslationEngine.builder() + .addTranslationSpec(new PersonPropertyTranslatorSpec()) + .addTranslationSpec(new GlobalPropertyTranslatorSpec()) + .addTranslationSpec(new RegionTranslatorSpec())) + .addInputFilePath(peopleInputPath, + PeoplePluginDataInput.class) + .addInputFilePath(regionsInputPath, + RegionsPluginDataInput.class) + .addInputFilePath(globalPropsInputPath, + GlobalPropertiesPluginDataInput.class) + .build(); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + + } + + private RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(524055747550937602L); + + private NIOReportItemHandler getNIOReportItemHandler() { + return NIOReportItemHandler.builder()// + .addReport(ModelReportLabel.PERSON_PROPERTY_REPORT, // + outputDirectory.resolve("person_property_report.xls"))// + .addReport(ModelReportLabel.VACCINATION, // + outputDirectory.resolve("vaccination_report.xls"))// + .build(); + } + + private Plugin getPeoplePlugin() { + PeoplePluginData peoplePluginData = PeoplePluginData.builder().build(); + return PeoplePlugin.getPeoplePlugin(peoplePluginData); + } + + private Plugin getRegionsPlugin() { + RegionsPluginData.Builder regionsPluginDataBuilder = RegionsPluginData.builder(); + + for (int i = 0; i < 5; i++) { + regionsPluginDataBuilder.addRegion(new Region(i)); + } + RegionsPluginData regionsPluginData = regionsPluginDataBuilder.build(); + return RegionsPlugin.builder().setRegionsPluginData(regionsPluginData).getRegionsPlugin(); + } + + private Plugin getPersonPropertiesPlugin() { + PersonPropertiesPluginData.Builder builder = PersonPropertiesPluginData.builder(); + PropertyDefinition propertyDefinition = PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(0)// + .build(); + builder.definePersonProperty(PersonProperty.EDUCATION_ATTEMPTS, + propertyDefinition, 0.0, false); + builder.definePersonProperty(PersonProperty.VACCINE_ATTEMPTS, + propertyDefinition, 0.0, false); + + propertyDefinition = PropertyDefinition.builder()// + .setType(Boolean.class)// + .build(); + builder.definePersonProperty(PersonProperty.REFUSES_VACCINE, + propertyDefinition, 0.0, false); + + propertyDefinition = PropertyDefinition.builder()// + .setType(Boolean.class)// + .setDefaultValue(false)// + .build(); + builder.definePersonProperty(PersonProperty.VACCINATED, propertyDefinition, 0.0, false); + + PersonPropertiesPluginData personPropertiesPluginData = builder.build(); + return PersonPropertiesPlugin.builder().setPersonPropertiesPluginData(personPropertiesPluginData) + .getPersonPropertyPlugin(); + } + + private Plugin getStochasticsPlugin() { + StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder()// + + .setMainRNGState(WellState.builder().setSeed(randomGenerator.nextLong()).build())// + .build(); + + return StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData); + } + + private Dimension getGlobalPropertyDimension(GlobalPropertyId globalPropertyId, String header, double[] values) { + GlobalPropertyDimension.Builder dimensionBuilder = GlobalPropertyDimension.builder();// + + dimensionBuilder.setGlobalPropertyId(globalPropertyId) + .setAssignmentTime(0.0); + + for (Double val : values) { + dimensionBuilder.addValue(val); + } + return dimensionBuilder.build(); + } + + private Dimension getVaccineRefusalProbabilityDimension() { + double[] values = new double[] { 0.0, 0.25, 0.5, 0.75, 1.0 }; + return getGlobalPropertyDimension(GlobalProperty.VACCINE_REFUSAL_PROBABILITY, + "intial_refusal_probability", + values); + } + + private Dimension getImmunityStartTimeDimension() { + double[] values = new double[] { 120.0, 180.0 }; + return getGlobalPropertyDimension(GlobalProperty.IMMUNITY_START_TIME, + "immunity_start_time", values); + } + + private Dimension getImmunityProbabilityDimension() { + double[] values = new double[] { 0.0, 0.1, 0.2, 0.5 }; + return getGlobalPropertyDimension(GlobalProperty.IMMUNITY_PROBABILITY, + "immunity_probabilty", values); + } + + private Dimension getVaccineAttemptIntervalDimension() { + double[] values = new double[] { 30.0, 45.0, 60.0 }; + + return getGlobalPropertyDimension(GlobalProperty.VACCINE_ATTEMPT_INTERVAL, + "vaccine_atttempt_interval", values); + } + + private Dimension getEducationAttemptIntervalDimension() { + double[] values = new double[] { 30.0, 60.0, 180.0 }; + return getGlobalPropertyDimension(GlobalProperty.EDUCATION_ATTEMPT_INTERVAL, + "education_attempt_interval", + values); + } + + private Dimension getEducationSuccessRatedimension() { + double[] values = new double[] { 0.0, 0.1, 0.2 }; + return getGlobalPropertyDimension(GlobalProperty.EDUCATION_SUCCESS_RATE, + "education_success_rate", values); + } + + private Plugin getGlobalPropertiesPlugin() { + GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder();// + + PropertyDefinition propertyDefinition = PropertyDefinition.builder()// + .setType(Double.class)// + .setDefaultValue(0.0)// + .setPropertyValueMutability(false)// + .build(); + + builder.defineGlobalProperty(GlobalProperty.IMMUNITY_START_TIME, + propertyDefinition, 0.0); + builder.defineGlobalProperty(GlobalProperty.VACCINE_ATTEMPT_INTERVAL, + propertyDefinition, 0.0); + builder.defineGlobalProperty(GlobalProperty.EDUCATION_ATTEMPT_INTERVAL, + propertyDefinition, 0.0); + builder.defineGlobalProperty(GlobalProperty.EDUCATION_SUCCESS_RATE, + propertyDefinition, 0.0); + builder.defineGlobalProperty(GlobalProperty.VACCINE_REFUSAL_PROBABILITY, + propertyDefinition, 0.0); + builder.defineGlobalProperty(GlobalProperty.IMMUNITY_PROBABILITY, + propertyDefinition, 0.0); + + propertyDefinition = PropertyDefinition.builder()// + .setType(Double.class)// + .setDefaultValue(365.0)// + .setPropertyValueMutability(false)// + .build(); + builder.defineGlobalProperty(GlobalProperty.SIMULATION_DURATION, + propertyDefinition, 0.0); + + propertyDefinition = PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(1000)// + .setPropertyValueMutability(false)// + .build(); + + builder.defineGlobalProperty(GlobalProperty.POPULATION_SIZE, + propertyDefinition, 0.0); + + GlobalPropertiesPluginData globalPropertiesPluginData = builder.build(); + + return GlobalPropertiesPlugin.builder().setGlobalPropertiesPluginData(globalPropertiesPluginData) + .getGlobalPropertiesPlugin(); + } + + private void execute() { + /* + * Create the people plugin filled with 1000 people + */ + Plugin peoplePlugin = getPeoplePlugin(); + + // Create the person properties plugin + Plugin personPropertiesPlugin = getPersonPropertiesPlugin(); + + /* + * Create the region plugin 5 regions, each having a lat and lon and + * assign the people to random regions. + * + */ + Plugin regionsPlugin = getRegionsPlugin(); + + /* + * Create the global properties plugin + */ + Plugin globalPropertiesPlugin = getGlobalPropertiesPlugin(); + + /* + * create the stochastics plugin + */ + Plugin stochasticsPlugin = getStochasticsPlugin(); + + Stopwatch stopwatch = new Stopwatch(); + + stopwatch.start(); + + this.readingTranslationController.readInput(); + + stopwatch.stop(); + System.out.println("Read input took: " + stopwatch.getElapsedMilliSeconds() + "ms"); + + List pluginDatas = this.readingTranslationController.getObjects(PluginData.class); + + for (PluginData pluginData : pluginDatas) { + if (pluginData instanceof PeoplePluginData) { + peoplePlugin = PeoplePlugin.getPeoplePlugin((PeoplePluginData) pluginData); + continue; + } + if (pluginData instanceof PersonPropertiesPluginData) { + personPropertiesPlugin = PersonPropertiesPlugin.builder() + .setPersonPropertiesPluginData((PersonPropertiesPluginData) pluginData) + .getPersonPropertyPlugin(); + continue; + } + + if (pluginData instanceof RegionsPluginData) { + regionsPlugin = RegionsPlugin.builder().setRegionsPluginData((RegionsPluginData) pluginData) + .getRegionsPlugin(); + continue; + } + + if (pluginData instanceof GlobalPropertiesPluginData) { + globalPropertiesPlugin = GlobalPropertiesPlugin.builder() + .setGlobalPropertiesPluginData((GlobalPropertiesPluginData) pluginData) + .getGlobalPropertiesPlugin(); + continue; + } + + if (pluginData instanceof StochasticsPluginData) { + stochasticsPlugin = StochasticsPlugin.getStochasticsPlugin((StochasticsPluginData) pluginData); + continue; + } + + } + + /* + * Create the reports + */ + NIOReportItemHandler nioReportItemHandler = getNIOReportItemHandler(); + + Plugin modelPlugin = ModelPlugin.getModelPlugin(); + + /* + * Assemble and execute the experiment + */ + + Experiment.builder()// + + .addPlugin(personPropertiesPlugin)// + .addPlugin(globalPropertiesPlugin)// + .addPlugin(modelPlugin)// + .addPlugin(regionsPlugin)// + .addPlugin(peoplePlugin)// + .addPlugin(stochasticsPlugin)// + .addPlugin(ReportsPlugin.getReportsPlugin()) + + .addDimension(getImmunityStartTimeDimension())// + .addDimension(getImmunityProbabilityDimension())// + .addDimension(getVaccineAttemptIntervalDimension())// + .addDimension(getEducationAttemptIntervalDimension())// + .addDimension(getEducationSuccessRatedimension())// + .addDimension(getVaccineRefusalProbabilityDimension())// + .addExperimentContextConsumer(nioReportItemHandler)// + .setExperimentParameterData(ExperimentParameterData.builder() + .setRecordState(true) + .setSimulationHaltTime(10.0) + .setThreadCount(8) + .build()) + .addExperimentContextConsumer(new SimulationStateCollector(this::handleSimulationStateCollection, + this::handleExperiementOpen)) + .build()// + .execute();// + + } + + private void handleExperiementOpen(ExperimentContext experimentContext) { + + TranslationController.Builder translationControllerBuilder = TranslationController.builder(); + + Translator personPropertiesTranslator = PersonPropertiesTranslator.getTranslator(); + Translator globalPropertiesTranslator = GlobalPropertiesTranslator.getTranslator(); + Translator regionsTranslator = RegionsTranslator.getTranslator(); + Translator peopleTranslator = PeopleTranslator.getTranslator(); + Translator stochasticsTranslator = StochasticsTranslator.getTranslator(); + Translator nucleusTranslator = NucleusTranslator.getTranslator(); + + for (int i = 0; i < experimentContext.getScenarioCount(); i++) { + File outputDir = this.outputDirectory.resolve("scenario" + i).toFile(); + outputDir.mkdir(); + + Path personPropertiesPath = Paths.get(outputDir.getAbsolutePath()).resolve(personPropertiesOutputFileName); + Path globalPropertiesPath = Paths.get(outputDir.getAbsolutePath()).resolve(globalPropertiesOutputFileName); + Path regionsPath = Paths.get(outputDir.getAbsolutePath()).resolve(regionsOutputFileName); + Path peoplePath = Paths.get(outputDir.getAbsolutePath()).resolve(peopleOutputFileName); + Path stochasticsPath = Paths.get(outputDir.getAbsolutePath()).resolve(stochasticsOutputFileName); + Path simStatepath = Paths.get(outputDir.getAbsolutePath()).resolve(simStateOutputFileName); + + translationControllerBuilder + .addOutputFilePath(personPropertiesPath, PersonPropertiesPluginData.class, i) + .addOutputFilePath(globalPropertiesPath, GlobalPropertiesPluginData.class, i) + .addOutputFilePath(regionsPath, RegionsPluginData.class, i) + .addOutputFilePath(peoplePath, PeoplePluginData.class, i) + .addOutputFilePath(stochasticsPath, StochasticsPluginData.class, i) + .addOutputFilePath(simStatepath, SimulationState.class, + i); + + } + + translationControllerBuilder + .addTranslator(personPropertiesTranslator) + .addTranslator(globalPropertiesTranslator) + .addTranslator(regionsTranslator) + .addTranslator(peopleTranslator) + .addTranslator(stochasticsTranslator) + .addTranslator(nucleusTranslator) + .addTranslator(PropertiesTranslator.getTranslator()) + .addTranslator(ReportsTranslator.getTranslator()) + .setTranslationEngineBuilder(ProtobufTranslationEngine.builder() + .addTranslationSpec(new PersonPropertyTranslatorSpec()) + .addTranslationSpec(new GlobalPropertyTranslatorSpec()) + .addTranslationSpec(new RegionTranslatorSpec())); + + this.writingTranslationController = translationControllerBuilder.build(); + } + + private void handleSimulationStateCollection(Integer scenarioId, List output) { + + for (Object object : output) { + if (object instanceof PluginData) { + writingTranslationController.writeOutput((PluginData) object, scenarioId); + } + + if (object instanceof SimulationState) { + writingTranslationController.writeOutput(object, scenarioId); + } + + } + + } + + public static void main(String[] args) { + Path outputDirectory = Paths.get(args[0]); + + new SerializationDemonstration(outputDirectory).execute(); + } + +} diff --git a/demos/gcm-taskit/src/main/java/lesson/plugins/model/GlobalProperty.java b/demos/gcm-taskit/src/main/java/lesson/plugins/model/GlobalProperty.java new file mode 100644 index 000000000..aebea72d7 --- /dev/null +++ b/demos/gcm-taskit/src/main/java/lesson/plugins/model/GlobalProperty.java @@ -0,0 +1,18 @@ +package lesson.plugins.model; + +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertyId; + +public enum GlobalProperty implements GlobalPropertyId { + IMMUNITY_START_TIME, // the time in days until immunity person property is added + IMMUNITY_PROBABILITY, // the probability that person will be immune when the immunity property is + // added + VACCINE_ATTEMPT_INTERVAL, // the maximum time between vaccine attempts + EDUCATION_ATTEMPT_INTERVAL, // the maximum time between vaccine education attempts + EDUCATION_SUCCESS_RATE, // the probability of changing the refusal person property per attempt + POPULATION_SIZE, // the initial size of the population + VACCINE_REFUSAL_PROBABILITY, // the probability that a person will refuse vaccination at the start of the + // simulation + SIMULATION_DURATION,// the total time the simulation will run + + ; +} diff --git a/demos/gcm-taskit/src/main/java/lesson/plugins/model/ModelError.java b/demos/gcm-taskit/src/main/java/lesson/plugins/model/ModelError.java new file mode 100644 index 000000000..6d9076778 --- /dev/null +++ b/demos/gcm-taskit/src/main/java/lesson/plugins/model/ModelError.java @@ -0,0 +1,26 @@ +package lesson.plugins.model; + +import util.errors.ContractError; +import util.errors.ContractException; + +/** + * An enumeration supporting {@link ContractException} that acts as a general + * description of the exception. + * + * + */ +public enum ModelError implements ContractError { + + NEGATIVE_REGION_ID("Negative region id"),; + + private final String description; + + private ModelError(final String description) { + this.description = description; + } + + @Override + public String getDescription() { + return description; + } +} diff --git a/demos/gcm-taskit/src/main/java/lesson/plugins/model/ModelPlugin.java b/demos/gcm-taskit/src/main/java/lesson/plugins/model/ModelPlugin.java new file mode 100644 index 000000000..933ccd998 --- /dev/null +++ b/demos/gcm-taskit/src/main/java/lesson/plugins/model/ModelPlugin.java @@ -0,0 +1,23 @@ +package lesson.plugins.model; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.plugins.reports.ReportsPluginId; +import lesson.plugins.model.actors.PopulationLoader; +import lesson.plugins.model.actors.Vaccinator; +import lesson.plugins.model.actors.VaccineEducator; + +public final class ModelPlugin { + private ModelPlugin() { + + } + + public static Plugin getModelPlugin() { + return Plugin.builder()// + .addPluginDependency(ReportsPluginId.PLUGIN_ID)// + .setPluginId(ModelPluginId.PLUGIN_ID).setInitializer((c) -> { + c.addActor(new VaccineEducator()::init); + c.addActor(new Vaccinator()::init); + c.addActor(new PopulationLoader()::init); + }).build(); + } +} diff --git a/demos/gcm-taskit/src/main/java/lesson/plugins/model/ModelPluginId.java b/demos/gcm-taskit/src/main/java/lesson/plugins/model/ModelPluginId.java new file mode 100644 index 000000000..ec3bae928 --- /dev/null +++ b/demos/gcm-taskit/src/main/java/lesson/plugins/model/ModelPluginId.java @@ -0,0 +1,11 @@ +package lesson.plugins.model; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.nucleus.SimplePluginId; + +public final class ModelPluginId { + private ModelPluginId() { + } + + public final static PluginId PLUGIN_ID = new SimplePluginId("model plugin"); +} diff --git a/demos/gcm-taskit/src/main/java/lesson/plugins/model/ModelReportLabel.java b/demos/gcm-taskit/src/main/java/lesson/plugins/model/ModelReportLabel.java new file mode 100644 index 000000000..fcc8c3d7d --- /dev/null +++ b/demos/gcm-taskit/src/main/java/lesson/plugins/model/ModelReportLabel.java @@ -0,0 +1,8 @@ +package lesson.plugins.model; + +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; + +public enum ModelReportLabel implements ReportLabel { + PERSON_PROPERTY_REPORT, + VACCINATION +} diff --git a/demos/gcm-taskit/src/main/java/lesson/plugins/model/PersonProperty.java b/demos/gcm-taskit/src/main/java/lesson/plugins/model/PersonProperty.java new file mode 100644 index 000000000..82c88b785 --- /dev/null +++ b/demos/gcm-taskit/src/main/java/lesson/plugins/model/PersonProperty.java @@ -0,0 +1,12 @@ +package lesson.plugins.model; + +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyId; + +public enum PersonProperty implements PersonPropertyId { + REFUSES_VACCINE, // + EDUCATION_ATTEMPTS, // + VACCINE_ATTEMPTS, // + IS_IMMUNE, // + VACCINATED,// boolean state of being vaccinated + ; +} diff --git a/demos/gcm-taskit/src/main/java/lesson/plugins/model/Region.java b/demos/gcm-taskit/src/main/java/lesson/plugins/model/Region.java new file mode 100644 index 000000000..bdcd75324 --- /dev/null +++ b/demos/gcm-taskit/src/main/java/lesson/plugins/model/Region.java @@ -0,0 +1,59 @@ +package lesson.plugins.model; + +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * Identifier for all regions + * + * + */ + +@Immutable +public final class Region implements RegionId { + + private final int id; + + /** + * Constructs the region + * + * @throws ContractException + *
  • {@linkplain ModelError#NEGATIVE_REGION_ID}
  • + */ + public Region(int id) { + if (id < 0) { + throw new ContractException(ModelError.NEGATIVE_REGION_ID); + } + this.id = id; + } + + public int getValue() { + return id; + } + + @Override + public int hashCode() { + return id; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Region)) { + return false; + } + Region other = (Region) obj; + if (id != other.id) { + return false; + } + return true; + } + + @Override + public String toString() { + return "Region_" + id; + } +} diff --git a/demos/gcm-taskit/src/main/java/lesson/plugins/model/actors/PopulationLoader.java b/demos/gcm-taskit/src/main/java/lesson/plugins/model/actors/PopulationLoader.java new file mode 100644 index 000000000..80522e4df --- /dev/null +++ b/demos/gcm-taskit/src/main/java/lesson/plugins/model/actors/PopulationLoader.java @@ -0,0 +1,98 @@ +package lesson.plugins.model.actors; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonConstructionData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonConstructionData.Builder; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyDefinitionInitialization; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyValueInitialization; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import lesson.plugins.model.GlobalProperty; +import lesson.plugins.model.PersonProperty; + +public class PopulationLoader { + private RandomGenerator randomGenerator; + private PeopleDataManager peopleDataManager; + private PersonPropertiesDataManager personPropertiesDataManager; + private GlobalPropertiesDataManager globalPropertiesDataManager; + + private void addImmunityProperty() { + PersonPropertyDefinitionInitialization.Builder builder = PersonPropertyDefinitionInitialization.builder(); + builder.setPersonPropertyId(PersonProperty.IS_IMMUNE); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Boolean.class).build(); + builder.setPropertyDefinition(propertyDefinition); + double immunityProbability = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.IMMUNITY_PROBABILITY); + + for (PersonId personId : peopleDataManager.getPeople()) { + boolean isImmune = randomGenerator.nextDouble() < immunityProbability; + builder.addPropertyValue(personId, isImmune); + } + PersonPropertyDefinitionInitialization personPropertyDefinitionInitialization = builder.build(); + personPropertiesDataManager.definePersonProperty(personPropertyDefinitionInitialization); + } + + public void init(ActorContext actorContext) { + peopleDataManager = actorContext.getDataManager(PeopleDataManager.class); + personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class); + globalPropertiesDataManager = actorContext.getDataManager(GlobalPropertiesDataManager.class); + RegionsDataManager regionsDataManager = actorContext.getDataManager(RegionsDataManager.class); + StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class); + randomGenerator = stochasticsDataManager.getRandomGenerator(); + List regionIds = new ArrayList<>(regionsDataManager.getRegionIds()); + + int populationSize = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.POPULATION_SIZE); + + populationSize -= peopleDataManager.getPopulationCount(); + + populationSize += 10; + + double refusalProbability = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.VACCINE_REFUSAL_PROBABILITY); + + boolean isImmuneIsValidProp = personPropertiesDataManager.personPropertyIdExists(PersonProperty.IS_IMMUNE); + + Builder personConstructionDataBuilder = PersonConstructionData.builder(); + for (int i = 0; i < populationSize; i++) { + RegionId regionId = regionIds.get(randomGenerator.nextInt(regionIds.size())); + personConstructionDataBuilder.add(regionId); + + boolean refusesVaccine = randomGenerator.nextDouble() < refusalProbability; + PersonPropertyValueInitialization personPropertyInitialization = new PersonPropertyValueInitialization( + PersonProperty.REFUSES_VACCINE, refusesVaccine); + personConstructionDataBuilder.add(personPropertyInitialization); + + if (isImmuneIsValidProp) { + double immunity_probabilty = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.IMMUNITY_PROBABILITY); + boolean isImmune = randomGenerator.nextDouble() < immunity_probabilty; + personConstructionDataBuilder + .add(new PersonPropertyValueInitialization(PersonProperty.IS_IMMUNE, isImmune)); + } + PersonConstructionData personConstructionData = personConstructionDataBuilder.build(); + peopleDataManager.addPerson(personConstructionData); + } + + double simulationDuration = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.SIMULATION_DURATION); + actorContext.addPlan((c) -> c.halt(), simulationDuration); + + if (!isImmuneIsValidProp) { + double immunityStartTime = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.IMMUNITY_START_TIME); + actorContext.addPlan((c) -> addImmunityProperty(), immunityStartTime); + } + } + +} diff --git a/demos/gcm-taskit/src/main/java/lesson/plugins/model/actors/Vaccinator.java b/demos/gcm-taskit/src/main/java/lesson/plugins/model/actors/Vaccinator.java new file mode 100644 index 000000000..108f42483 --- /dev/null +++ b/demos/gcm-taskit/src/main/java/lesson/plugins/model/actors/Vaccinator.java @@ -0,0 +1,133 @@ +package lesson.plugins.model.actors; + +import java.util.List; +import java.util.function.Consumer; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.EventFilter; +import gov.hhs.aspr.ms.gcm.nucleus.Plan; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.events.PersonPropertyUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import lesson.plugins.model.GlobalProperty; +import lesson.plugins.model.PersonProperty; + +public final class Vaccinator { + + private PeopleDataManager peopleDataManager; + private RandomGenerator randomGenerator; + private PersonPropertiesDataManager personPropertiesDataManager; + private GlobalPropertiesDataManager globalPropertiesDataManager; + private double vaccineAttemptInterval; + private ActorContext actorContext; + + private void vaccinatePerson(PersonId personId) { + int vaccineAttempts = personPropertiesDataManager + .getPersonPropertyValue(personId, PersonProperty.VACCINE_ATTEMPTS); + personPropertiesDataManager + .setPersonPropertyValue(personId, PersonProperty.VACCINE_ATTEMPTS, vaccineAttempts + 1); + + boolean isImmune = false; + if (personPropertiesDataManager.personPropertyIdExists(PersonProperty.IS_IMMUNE)) { + isImmune = personPropertiesDataManager + .getPersonPropertyValue(personId, PersonProperty.IS_IMMUNE); + } + + Boolean refusesVaccine = personPropertiesDataManager + .getPersonPropertyValue(personId, PersonProperty.REFUSES_VACCINE); + if (!isImmune) { + if (refusesVaccine) { + double planTime = actorContext.getTime() + + randomGenerator.nextDouble() * vaccineAttemptInterval; + Object planKey = personId; + Consumer consumer = (c) -> vaccinatePerson(personId); + + Plan plan = Plan.builder(ActorContext.class)// + .setActive(true)// + .setCallbackConsumer(consumer)// + .setKey(planKey)// + .setPlanData(null) + .setTime(planTime)// + .build();// + actorContext.addPlan(plan); + } else { + personPropertiesDataManager + .setPersonPropertyValue(personId, PersonProperty.VACCINATED, true); + } + } + } + + private void handleVaccineAcceptance(ActorContext actorContext, + PersonPropertyUpdateEvent personPropertyUpdateEvent) { + /* + * We know that the person property is PersonProperty.REFUSES_VACCINE + * since we used an event filter when subscribing + */ + Boolean refusesVaccine = personPropertyUpdateEvent.getCurrentPropertyValue(); + if (!refusesVaccine) { + PersonId personId = personPropertyUpdateEvent.personId(); + // drop the current plan + actorContext.removePlan(personId); + vaccinatePerson(personId); + } + } + + private void planVaccination(PersonId personId) { + double planTime = actorContext.getTime() + + randomGenerator.nextDouble() * vaccineAttemptInterval; + Object planKey = personId; + Consumer consumer = (c) -> vaccinatePerson(personId); + + Plan plan = Plan.builder(ActorContext.class)// + .setActive(true)// + .setCallbackConsumer(consumer)// + .setKey(planKey)// + .setTime(planTime)// + .setPlanData(null) + .build();// + actorContext.addPlan(plan); + } + + private void handleNewPerson(PersonId personId) { + boolean vaccinated = personPropertiesDataManager.getPersonPropertyValue(personId, PersonProperty.VACCINATED); + if (!vaccinated) { + planVaccination(personId); + } + } + + public void init(ActorContext actorContext) { + this.actorContext = actorContext; + StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class); + randomGenerator = stochasticsDataManager.getRandomGenerator(); + peopleDataManager = actorContext.getDataManager(PeopleDataManager.class); + personPropertiesDataManager = actorContext + .getDataManager(PersonPropertiesDataManager.class); + globalPropertiesDataManager = actorContext + .getDataManager(GlobalPropertiesDataManager.class); + + List unvaccinatedPeople = personPropertiesDataManager + .getPeopleWithPropertyValue(PersonProperty.VACCINATED, false); + vaccineAttemptInterval = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.VACCINE_ATTEMPT_INTERVAL); + for (PersonId personId : unvaccinatedPeople) { + planVaccination(personId); + } + + EventFilter eventFilter = personPropertiesDataManager// + .getEventFilterForPersonPropertyUpdateEvent(PersonProperty.REFUSES_VACCINE); + + actorContext.subscribe(eventFilter, this::handleVaccineAcceptance); + + actorContext.subscribe( + peopleDataManager.getEventFilterForPersonAdditionEvent(), (c, e) -> { + handleNewPerson(e.personId()); + }); + + } + +} diff --git a/demos/gcm-taskit/src/main/java/lesson/plugins/model/actors/VaccineEducator.java b/demos/gcm-taskit/src/main/java/lesson/plugins/model/actors/VaccineEducator.java new file mode 100644 index 000000000..c745977ec --- /dev/null +++ b/demos/gcm-taskit/src/main/java/lesson/plugins/model/actors/VaccineEducator.java @@ -0,0 +1,87 @@ +package lesson.plugins.model.actors; + +import java.util.List; +import java.util.function.Consumer; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import lesson.plugins.model.GlobalProperty; +import lesson.plugins.model.PersonProperty; + +public class VaccineEducator { + + private PersonPropertiesDataManager personPropertiesDataManager; + private GlobalPropertiesDataManager globalPropertiesDataManager; + private double educationAttemptInterval; + private double educationSuccessRate; + private RandomGenerator randomGenerator; + private ActorContext actorContext; + + private void educatePerson(PersonId personId) { + int educationAttempts = personPropertiesDataManager + .getPersonPropertyValue(personId, PersonProperty.EDUCATION_ATTEMPTS); + personPropertiesDataManager + .setPersonPropertyValue(personId, PersonProperty.EDUCATION_ATTEMPTS, educationAttempts + 1); + + if (randomGenerator.nextDouble() < educationSuccessRate) { + personPropertiesDataManager + .setPersonPropertyValue(personId, PersonProperty.REFUSES_VACCINE, false); + } else { + planEducation(personId); + } + } + + private void planEducation(PersonId personId) { + double planTime = actorContext.getTime() + randomGenerator.nextDouble() * educationAttemptInterval; + Consumer plan = (c) -> educatePerson(personId); + actorContext.addPlan(plan, planTime); + } + + private void handleNewPerson(PersonId personId) { + boolean vaccinated = personPropertiesDataManager + .getPersonPropertyValue(personId, PersonProperty.VACCINATED); + if (!vaccinated) { + Boolean refusesVaccine = personPropertiesDataManager + .getPersonPropertyValue(personId, PersonProperty.REFUSES_VACCINE); + if (refusesVaccine) { + planEducation(personId); + } + } + } + + public void init(ActorContext actorContext) { + this.actorContext = actorContext; + + StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class); + randomGenerator = stochasticsDataManager.getRandomGenerator(); + PeopleDataManager peopleDataManager = actorContext.getDataManager(PeopleDataManager.class); + personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class); + globalPropertiesDataManager = actorContext.getDataManager(GlobalPropertiesDataManager.class); + + educationAttemptInterval = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.EDUCATION_ATTEMPT_INTERVAL); + educationSuccessRate = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.EDUCATION_SUCCESS_RATE); + + List unvaccinatedPeople = personPropertiesDataManager + .getPeopleWithPropertyValue(PersonProperty.VACCINATED, false); + for (PersonId personId : unvaccinatedPeople) { + Boolean refusesVaccine = personPropertiesDataManager + .getPersonPropertyValue(personId, PersonProperty.REFUSES_VACCINE); + if (refusesVaccine) { + planEducation(personId); + } + } + + actorContext.subscribe(peopleDataManager.getEventFilterForPersonAdditionEvent(), (c, e) -> { + handleNewPerson(e.personId()); + }); + } + +} diff --git a/demos/gcm-taskit/src/main/java/lesson/plugins/model/reports/VaccineReport.java b/demos/gcm-taskit/src/main/java/lesson/plugins/model/reports/VaccineReport.java new file mode 100644 index 000000000..ba4ac4d80 --- /dev/null +++ b/demos/gcm-taskit/src/main/java/lesson/plugins/model/reports/VaccineReport.java @@ -0,0 +1,76 @@ +package lesson.plugins.model.reports; + +import java.util.List; + +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import lesson.plugins.model.PersonProperty; + +public final class VaccineReport { + + private final ReportLabel reportLabel; + + public VaccineReport(ReportLabel reportLabel) { + this.reportLabel = reportLabel; + } + + public void init(ReportContext reportContext) { + reportContext.subscribeToSimulationClose(this::report); + } + + private ReportHeader reportHeader = ReportHeader.builder()// + .add("vaccinated_immune")// + .add("vaccinated_susceptible")// + .add("unvaccinated_immune")// + .add("unvaccinated_susceptible")// + .build(); + + private void report(ReportContext reportContext) { + PeopleDataManager peopleDataManager = reportContext.getDataManager(PeopleDataManager.class); + PersonPropertiesDataManager personPropertiesDataManager = reportContext + .getDataManager(PersonPropertiesDataManager.class); + + int vaccinated_immune = 0; + int vaccinated_susceptible = 0; + int unvaccinated_immune = 0; + int unvaccinated_susceptible = 0; + + List people = peopleDataManager.getPeople(); + for (PersonId personId : people) { + boolean vaccinated = personPropertiesDataManager.getPersonPropertyValue(personId, + PersonProperty.VACCINATED); + boolean immune = personPropertiesDataManager.getPersonPropertyValue(personId, PersonProperty.IS_IMMUNE); + if (vaccinated) { + if (immune) { + vaccinated_immune++; + } else { + vaccinated_susceptible++; + } + } else { + if (immune) { + unvaccinated_immune++; + } else { + unvaccinated_susceptible++; + } + } + } + + ReportItem.Builder builder = ReportItem.builder()// + .setReportLabel(reportLabel)// + .setReportHeader(reportHeader); + + builder.addValue(vaccinated_immune); + builder.addValue(vaccinated_susceptible); + builder.addValue(unvaccinated_immune); + builder.addValue(unvaccinated_susceptible); + + ReportItem reportItem = builder.build(); + reportContext.releaseOutput(reportItem); + } + +} diff --git a/demos/gcm-taskit/src/main/java/lesson/translatorSpecs/GlobalPropertyTranslatorSpec.java b/demos/gcm-taskit/src/main/java/lesson/translatorSpecs/GlobalPropertyTranslatorSpec.java new file mode 100644 index 000000000..55f4ac376 --- /dev/null +++ b/demos/gcm-taskit/src/main/java/lesson/translatorSpecs/GlobalPropertyTranslatorSpec.java @@ -0,0 +1,29 @@ +package lesson.translatorSpecs; + +import gov.hhs.aspr.ms.taskit.protobuf.ProtobufTranslationSpec; +import lesson.input.GlobalPropertyInput; +import lesson.plugins.model.GlobalProperty; + +public class GlobalPropertyTranslatorSpec extends ProtobufTranslationSpec { + + @Override + protected GlobalProperty convertInputObject(GlobalPropertyInput inputObject) { + return GlobalProperty.valueOf(inputObject.name()); + } + + @Override + protected GlobalPropertyInput convertAppObject(GlobalProperty simObject) { + return GlobalPropertyInput.valueOf(simObject.name()); + } + + @Override + public Class getInputObjectClass() { + return GlobalPropertyInput.class; + } + + @Override + public Class getAppObjectClass() { + return GlobalProperty.class; + } + +} diff --git a/demos/gcm-taskit/src/main/java/lesson/translatorSpecs/PersonPropertyTranslatorSpec.java b/demos/gcm-taskit/src/main/java/lesson/translatorSpecs/PersonPropertyTranslatorSpec.java new file mode 100644 index 000000000..0435d9381 --- /dev/null +++ b/demos/gcm-taskit/src/main/java/lesson/translatorSpecs/PersonPropertyTranslatorSpec.java @@ -0,0 +1,29 @@ +package lesson.translatorSpecs; + +import gov.hhs.aspr.ms.taskit.protobuf.ProtobufTranslationSpec; +import lesson.input.PersonPropertyInput; +import lesson.plugins.model.PersonProperty; + +public class PersonPropertyTranslatorSpec extends ProtobufTranslationSpec { + + @Override + protected PersonProperty convertInputObject(PersonPropertyInput inputObject) { + return PersonProperty.valueOf(inputObject.name()); + } + + @Override + protected PersonPropertyInput convertAppObject(PersonProperty simObject) { + return PersonPropertyInput.valueOf(simObject.name()); + } + + @Override + public Class getInputObjectClass() { + return PersonPropertyInput.class; + } + + @Override + public Class getAppObjectClass() { + return PersonProperty.class; + } + +} diff --git a/demos/gcm-taskit/src/main/java/lesson/translatorSpecs/RegionTranslatorSpec.java b/demos/gcm-taskit/src/main/java/lesson/translatorSpecs/RegionTranslatorSpec.java new file mode 100644 index 000000000..c41bbcab2 --- /dev/null +++ b/demos/gcm-taskit/src/main/java/lesson/translatorSpecs/RegionTranslatorSpec.java @@ -0,0 +1,29 @@ +package lesson.translatorSpecs; + +import gov.hhs.aspr.ms.taskit.protobuf.ProtobufTranslationSpec; +import lesson.input.RegionInput; +import lesson.plugins.model.Region; + +public class RegionTranslatorSpec extends ProtobufTranslationSpec { + + @Override + protected Region convertInputObject(RegionInput inputObject) { + return new Region(inputObject.getId()); + } + + @Override + protected RegionInput convertAppObject(Region simObject) { + return RegionInput.newBuilder().setId(simObject.getValue()).build(); + } + + @Override + public Class getInputObjectClass() { + return RegionInput.class; + } + + @Override + public Class getAppObjectClass() { + return Region.class; + } + +} diff --git a/demos/gcm-taskit/src/main/proto/lesson.proto b/demos/gcm-taskit/src/main/proto/lesson.proto new file mode 100644 index 000000000..f73aefd8c --- /dev/null +++ b/demos/gcm-taskit/src/main/proto/lesson.proto @@ -0,0 +1,29 @@ +syntax = "proto3"; +package gov.hhs.aspr.ms.gcm.taskit.protobuf.demo; + +option java_multiple_files = true; +option java_package = "lesson.input"; + + +enum PersonPropertyInput { + REFUSES_VACCINE = 0; + EDUCATION_ATTEMPTS = 1; + VACCINE_ATTEMPTS = 2; + IS_IMMUNE = 3; + VACCINATED = 4; +} + +enum GlobalPropertyInput { + IMMUNITY_START_TIME = 0; + IMMUNITY_PROBABILITY = 1; + VACCINE_ATTEMPT_INTERVAL = 2; + EDUCATION_ATTEMPT_INTERVAL = 3; + EDUCATION_SUCCESS_RATE = 4; + POPULATION_SIZE = 5; + VACCINE_REFUSAL_PROBABILITY = 6; + SIMULATION_DURATION = 7; +} + +message RegionInput { + int32 id = 1; +} \ No newline at end of file diff --git a/demos/gcm-taskit/src/main/resources/globalPropertiesInput.json b/demos/gcm-taskit/src/main/resources/globalPropertiesInput.json new file mode 100644 index 000000000..0e2b7c276 --- /dev/null +++ b/demos/gcm-taskit/src/main/resources/globalPropertiesInput.json @@ -0,0 +1,174 @@ +{ + "globalPropertyDefinitinions": [{ + "propertyId": { + "@type": "type.googleapis.com/gov.hhs.aspr.ms.taskit.protobuf.WrapperEnumValue", + "enumTypeUrl": "gov.hhs.aspr.ms.gcm.taskit.protobuf.demo.GlobalPropertyInput", + "value": "IMMUNITY_START_TIME" + }, + "propertyDefinition": { + "defaultValue": { + "@type": "type.googleapis.com/google.protobuf.DoubleValue", + "value": 0.0 + } + }, + "propertyTrackingPolicy": true + }, { + "propertyId": { + "@type": "type.googleapis.com/gov.hhs.aspr.ms.taskit.protobuf.WrapperEnumValue", + "enumTypeUrl": "gov.hhs.aspr.ms.gcm.taskit.protobuf.demo.GlobalPropertyInput", + "value": "VACCINE_ATTEMPT_INTERVAL" + }, + "propertyDefinition": { + "defaultValue": { + "@type": "type.googleapis.com/google.protobuf.DoubleValue", + "value": 0.0 + } + }, + "propertyTrackingPolicy": true + }, { + "propertyId": { + "@type": "type.googleapis.com/gov.hhs.aspr.ms.taskit.protobuf.WrapperEnumValue", + "enumTypeUrl": "gov.hhs.aspr.ms.gcm.taskit.protobuf.demo.GlobalPropertyInput", + "value": "EDUCATION_ATTEMPT_INTERVAL" + }, + "propertyDefinition": { + "defaultValue": { + "@type": "type.googleapis.com/google.protobuf.DoubleValue", + "value": 0.0 + } + }, + "propertyTrackingPolicy": true + }, { + "propertyId": { + "@type": "type.googleapis.com/gov.hhs.aspr.ms.taskit.protobuf.WrapperEnumValue", + "enumTypeUrl": "gov.hhs.aspr.ms.gcm.taskit.protobuf.demo.GlobalPropertyInput", + "value": "EDUCATION_SUCCESS_RATE" + }, + "propertyDefinition": { + "defaultValue": { + "@type": "type.googleapis.com/google.protobuf.DoubleValue", + "value": 0.0 + } + }, + "propertyTrackingPolicy": true + }, { + "propertyId": { + "@type": "type.googleapis.com/gov.hhs.aspr.ms.taskit.protobuf.WrapperEnumValue", + "enumTypeUrl": "gov.hhs.aspr.ms.gcm.taskit.protobuf.demo.GlobalPropertyInput", + "value": "VACCINE_REFUSAL_PROBABILITY" + }, + "propertyDefinition": { + "defaultValue": { + "@type": "type.googleapis.com/google.protobuf.DoubleValue", + "value": 0.0 + } + }, + "propertyTrackingPolicy": true + }, { + "propertyId": { + "@type": "type.googleapis.com/gov.hhs.aspr.ms.taskit.protobuf.WrapperEnumValue", + "enumTypeUrl": "gov.hhs.aspr.ms.gcm.taskit.protobuf.demo.GlobalPropertyInput", + "value": "IMMUNITY_PROBABILITY" + }, + "propertyDefinition": { + "defaultValue": { + "@type": "type.googleapis.com/google.protobuf.DoubleValue", + "value": 0.0 + } + }, + "propertyTrackingPolicy": true + }, { + "propertyId": { + "@type": "type.googleapis.com/gov.hhs.aspr.ms.taskit.protobuf.WrapperEnumValue", + "enumTypeUrl": "gov.hhs.aspr.ms.gcm.taskit.protobuf.demo.GlobalPropertyInput", + "value": "SIMULATION_DURATION" + }, + "propertyDefinition": { + "defaultValue": { + "@type": "type.googleapis.com/google.protobuf.DoubleValue", + "value": 365.0 + } + }, + "propertyTrackingPolicy": true + }, { + "propertyId": { + "@type": "type.googleapis.com/gov.hhs.aspr.ms.taskit.protobuf.WrapperEnumValue", + "enumTypeUrl": "gov.hhs.aspr.ms.gcm.taskit.protobuf.demo.GlobalPropertyInput", + "value": "POPULATION_SIZE" + }, + "propertyDefinition": { + "defaultValue": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1000 + } + }, + "propertyTrackingPolicy": true + }], + "globalPropertyValues": [{ + "propertyId": { + "@type": "type.googleapis.com/gov.hhs.aspr.ms.taskit.protobuf.WrapperEnumValue", + "enumTypeUrl": "gov.hhs.aspr.ms.gcm.taskit.protobuf.demo.GlobalPropertyInput", + "value": "IMMUNITY_START_TIME" + }, + "propertyValue": { + "@type": "type.googleapis.com/google.protobuf.DoubleValue", + "value": 120.0 + }, + "propertyValueTime": 0.0 + }, { + "propertyId": { + "@type": "type.googleapis.com/gov.hhs.aspr.ms.taskit.protobuf.WrapperEnumValue", + "enumTypeUrl": "gov.hhs.aspr.ms.gcm.taskit.protobuf.demo.GlobalPropertyInput", + "value": "IMMUNITY_PROBABILITY" + }, + "propertyValue": { + "@type": "type.googleapis.com/google.protobuf.DoubleValue", + "value": 0.0 + }, + "propertyValueTime": 0.0 + }, { + "propertyId": { + "@type": "type.googleapis.com/gov.hhs.aspr.ms.taskit.protobuf.WrapperEnumValue", + "enumTypeUrl": "gov.hhs.aspr.ms.gcm.taskit.protobuf.demo.GlobalPropertyInput", + "value": "VACCINE_ATTEMPT_INTERVAL" + }, + "propertyValue": { + "@type": "type.googleapis.com/google.protobuf.DoubleValue", + "value": 30.0 + }, + "propertyValueTime": 0.0 + }, { + "propertyId": { + "@type": "type.googleapis.com/gov.hhs.aspr.ms.taskit.protobuf.WrapperEnumValue", + "enumTypeUrl": "gov.hhs.aspr.ms.gcm.taskit.protobuf.demo.GlobalPropertyInput", + "value": "EDUCATION_ATTEMPT_INTERVAL" + }, + "propertyValue": { + "@type": "type.googleapis.com/google.protobuf.DoubleValue", + "value": 30.0 + }, + "propertyValueTime": 0.0 + }, { + "propertyId": { + "@type": "type.googleapis.com/gov.hhs.aspr.ms.taskit.protobuf.WrapperEnumValue", + "enumTypeUrl": "gov.hhs.aspr.ms.gcm.taskit.protobuf.demo.GlobalPropertyInput", + "value": "EDUCATION_SUCCESS_RATE" + }, + "propertyValue": { + "@type": "type.googleapis.com/google.protobuf.DoubleValue", + "value": 0.0 + }, + "propertyValueTime": 0.0 + }, { + "propertyId": { + "@type": "type.googleapis.com/gov.hhs.aspr.ms.taskit.protobuf.WrapperEnumValue", + "enumTypeUrl": "gov.hhs.aspr.ms.gcm.taskit.protobuf.demo.GlobalPropertyInput", + "value": "VACCINE_REFUSAL_PROBABILITY" + }, + "propertyValue": { + "@type": "type.googleapis.com/google.protobuf.DoubleValue", + "value": 0.0 + }, + "propertyValueTime": 0.0 + }] +} \ No newline at end of file diff --git a/demos/gcm-taskit/src/main/resources/peopleInput.json b/demos/gcm-taskit/src/main/resources/peopleInput.json new file mode 100644 index 000000000..ec5ab8694 --- /dev/null +++ b/demos/gcm-taskit/src/main/resources/peopleInput.json @@ -0,0 +1,6 @@ +{ + "personRanges": [{ + "highPersonId": 999 + }], + "personCount": 1000 +} \ No newline at end of file diff --git a/demos/gcm-taskit/src/main/resources/personPropertiesInput.json b/demos/gcm-taskit/src/main/resources/personPropertiesInput.json new file mode 100644 index 000000000..0a4421e9c --- /dev/null +++ b/demos/gcm-taskit/src/main/resources/personPropertiesInput.json @@ -0,0 +1,18078 @@ +{ + "personPropertyDefinitions": [{ + "propertyId": { + "@type": "type.googleapis.com/gov.hhs.aspr.ms.taskit.protobuf.WrapperEnumValue", + "enumTypeUrl": "gov.hhs.aspr.ms.gcm.taskit.protobuf.demo.PersonPropertyInput", + "value": "EDUCATION_ATTEMPTS" + }, + "propertyDefinition": { + "propertyValuesAreMutable": true, + "defaultValue": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + } + }, { + "propertyId": { + "@type": "type.googleapis.com/gov.hhs.aspr.ms.taskit.protobuf.WrapperEnumValue", + "enumTypeUrl": "gov.hhs.aspr.ms.gcm.taskit.protobuf.demo.PersonPropertyInput", + "value": "VACCINE_ATTEMPTS" + }, + "propertyDefinition": { + "propertyValuesAreMutable": true, + "defaultValue": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + } + }, { + "propertyId": { + "@type": "type.googleapis.com/gov.hhs.aspr.ms.taskit.protobuf.WrapperEnumValue", + "enumTypeUrl": "gov.hhs.aspr.ms.gcm.taskit.protobuf.demo.PersonPropertyInput", + "value": "REFUSES_VACCINE" + }, + "propertyDefinition": { + "type": "java.lang.Boolean", + "propertyValuesAreMutable": true + } + }, { + "propertyId": { + "@type": "type.googleapis.com/gov.hhs.aspr.ms.taskit.protobuf.WrapperEnumValue", + "enumTypeUrl": "gov.hhs.aspr.ms.gcm.taskit.protobuf.demo.PersonPropertyInput", + "value": "VACCINATED" + }, + "propertyDefinition": { + "propertyValuesAreMutable": true, + "defaultValue": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + } + }], + "personPropertyValues": [{ + "personPropertyId": { + "id": { + "@type": "type.googleapis.com/gov.hhs.aspr.ms.taskit.protobuf.WrapperEnumValue", + "enumTypeUrl": "gov.hhs.aspr.ms.gcm.taskit.protobuf.demo.PersonPropertyInput", + "value": "REFUSES_VACCINE" + } + }, + "propertyValues": [{ + "pId": 0, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 1, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 2, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 3, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 4, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 5, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 6, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 7, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 8, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 9, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 10, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 11, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 12, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 13, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 14, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 15, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 16, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 17, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 18, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 19, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 20, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 21, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 22, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 23, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 24, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 25, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 26, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 27, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 28, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 29, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 30, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 31, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 32, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 33, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 34, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 35, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 36, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 37, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 38, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 39, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 40, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 41, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 42, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 43, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 44, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 45, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 46, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 47, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 48, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 49, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 50, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 51, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 52, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 53, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 54, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 55, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 56, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 57, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 58, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 59, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 60, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 61, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 62, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 63, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 64, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 65, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 66, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 67, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 68, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 69, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 70, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 71, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 72, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 73, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 74, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 75, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 76, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 77, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 78, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 79, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 80, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 81, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 82, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 83, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 84, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 85, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 86, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 87, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 88, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 89, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 90, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 91, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 92, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 93, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 94, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 95, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 96, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 97, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 98, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 99, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 100, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 101, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 102, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 103, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 104, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 105, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 106, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 107, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 108, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 109, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 110, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 111, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 112, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 113, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 114, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 115, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 116, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 117, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 118, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 119, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 120, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 121, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 122, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 123, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 124, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 125, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 126, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 127, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 128, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 129, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 130, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 131, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 132, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 133, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 134, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 135, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 136, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 137, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 138, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 139, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 140, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 141, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 142, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 143, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 144, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 145, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 146, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 147, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 148, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 149, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 150, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 151, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 152, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 153, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 154, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 155, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 156, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 157, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 158, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 159, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 160, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 161, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 162, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 163, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 164, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 165, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 166, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 167, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 168, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 169, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 170, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 171, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 172, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 173, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 174, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 175, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 176, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 177, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 178, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 179, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 180, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 181, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 182, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 183, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 184, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 185, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 186, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 187, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 188, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 189, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 190, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 191, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 192, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 193, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 194, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 195, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 196, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 197, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 198, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 199, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 200, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 201, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 202, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 203, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 204, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 205, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 206, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 207, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 208, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 209, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 210, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 211, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 212, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 213, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 214, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 215, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 216, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 217, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 218, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 219, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 220, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 221, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 222, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 223, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 224, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 225, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 226, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 227, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 228, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 229, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 230, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 231, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 232, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 233, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 234, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 235, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 236, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 237, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 238, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 239, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 240, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 241, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 242, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 243, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 244, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 245, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 246, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 247, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 248, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 249, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 250, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 251, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 252, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 253, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 254, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 255, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 256, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 257, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 258, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 259, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 260, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 261, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 262, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 263, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 264, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 265, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 266, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 267, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 268, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 269, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 270, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 271, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 272, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 273, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 274, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 275, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 276, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 277, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 278, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 279, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 280, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 281, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 282, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 283, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 284, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 285, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 286, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 287, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 288, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 289, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 290, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 291, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 292, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 293, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 294, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 295, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 296, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 297, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 298, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 299, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 300, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 301, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 302, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 303, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 304, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 305, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 306, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 307, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 308, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 309, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 310, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 311, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 312, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 313, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 314, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 315, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 316, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 317, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 318, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 319, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 320, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 321, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 322, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 323, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 324, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 325, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 326, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 327, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 328, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 329, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 330, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 331, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 332, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 333, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 334, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 335, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 336, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 337, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 338, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 339, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 340, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 341, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 342, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 343, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 344, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 345, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 346, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 347, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 348, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 349, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 350, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 351, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 352, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 353, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 354, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 355, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 356, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 357, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 358, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 359, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 360, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 361, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 362, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 363, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 364, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 365, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 366, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 367, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 368, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 369, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 370, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 371, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 372, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 373, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 374, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 375, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 376, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 377, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 378, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 379, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 380, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 381, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 382, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 383, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 384, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 385, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 386, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 387, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 388, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 389, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 390, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 391, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 392, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 393, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 394, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 395, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 396, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 397, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 398, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 399, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 400, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 401, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 402, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 403, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 404, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 405, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 406, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 407, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 408, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 409, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 410, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 411, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 412, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 413, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 414, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 415, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 416, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 417, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 418, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 419, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 420, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 421, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 422, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 423, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 424, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 425, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 426, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 427, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 428, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 429, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 430, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 431, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 432, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 433, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 434, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 435, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 436, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 437, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 438, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 439, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 440, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 441, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 442, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 443, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 444, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 445, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 446, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 447, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 448, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 449, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 450, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 451, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 452, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 453, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 454, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 455, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 456, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 457, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 458, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 459, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 460, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 461, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 462, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 463, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 464, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 465, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 466, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 467, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 468, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 469, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 470, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 471, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 472, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 473, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 474, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 475, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 476, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 477, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 478, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 479, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 480, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 481, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 482, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 483, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 484, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 485, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 486, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 487, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 488, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 489, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 490, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 491, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 492, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 493, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 494, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 495, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 496, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 497, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 498, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 499, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 500, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 501, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 502, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 503, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 504, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 505, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 506, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 507, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 508, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 509, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 510, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 511, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 512, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 513, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 514, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 515, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 516, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 517, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 518, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 519, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 520, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 521, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 522, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 523, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 524, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 525, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 526, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 527, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 528, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 529, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 530, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 531, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 532, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 533, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 534, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 535, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 536, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 537, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 538, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 539, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 540, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 541, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 542, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 543, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 544, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 545, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 546, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 547, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 548, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 549, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 550, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 551, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 552, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 553, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 554, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 555, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 556, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 557, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 558, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 559, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 560, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 561, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 562, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 563, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 564, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 565, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 566, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 567, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 568, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 569, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 570, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 571, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 572, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 573, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 574, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 575, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 576, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 577, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 578, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 579, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 580, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 581, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 582, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 583, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 584, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 585, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 586, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 587, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 588, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 589, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 590, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 591, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 592, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 593, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 594, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 595, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 596, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 597, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 598, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 599, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 600, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 601, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 602, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 603, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 604, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 605, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 606, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 607, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 608, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 609, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 610, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 611, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 612, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 613, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 614, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 615, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 616, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 617, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 618, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 619, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 620, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 621, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 622, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 623, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 624, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 625, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 626, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 627, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 628, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 629, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 630, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 631, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 632, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 633, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 634, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 635, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 636, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 637, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 638, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 639, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 640, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 641, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 642, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 643, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 644, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 645, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 646, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 647, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 648, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 649, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 650, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 651, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 652, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 653, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 654, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 655, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 656, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 657, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 658, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 659, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 660, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 661, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 662, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 663, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 664, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 665, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 666, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 667, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 668, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 669, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 670, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 671, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 672, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 673, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 674, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 675, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 676, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 677, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 678, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 679, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 680, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 681, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 682, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 683, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 684, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 685, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 686, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 687, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 688, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 689, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 690, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 691, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 692, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 693, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 694, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 695, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 696, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 697, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 698, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 699, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 700, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 701, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 702, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 703, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 704, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 705, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 706, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 707, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 708, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 709, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 710, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 711, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 712, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 713, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 714, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 715, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 716, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 717, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 718, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 719, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 720, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 721, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 722, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 723, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 724, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 725, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 726, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 727, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 728, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 729, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 730, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 731, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 732, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 733, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 734, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 735, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 736, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 737, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 738, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 739, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 740, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 741, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 742, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 743, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 744, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 745, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 746, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 747, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 748, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 749, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 750, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 751, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 752, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 753, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 754, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 755, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 756, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 757, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 758, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 759, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 760, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 761, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 762, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 763, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 764, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 765, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 766, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 767, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 768, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 769, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 770, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 771, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 772, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 773, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 774, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 775, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 776, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 777, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 778, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 779, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 780, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 781, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 782, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 783, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 784, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 785, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 786, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 787, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 788, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 789, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 790, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 791, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 792, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 793, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 794, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 795, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 796, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 797, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 798, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 799, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 800, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 801, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 802, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 803, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 804, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 805, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 806, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 807, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 808, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 809, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 810, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 811, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 812, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 813, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 814, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 815, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 816, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 817, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 818, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 819, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 820, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 821, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 822, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 823, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 824, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 825, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 826, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 827, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 828, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 829, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 830, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 831, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 832, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 833, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 834, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 835, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 836, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 837, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 838, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 839, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 840, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 841, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 842, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 843, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 844, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 845, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 846, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 847, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 848, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 849, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 850, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 851, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 852, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 853, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 854, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 855, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 856, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 857, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 858, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 859, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 860, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 861, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 862, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 863, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 864, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 865, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 866, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 867, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 868, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 869, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 870, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 871, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 872, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 873, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 874, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 875, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 876, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 877, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 878, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 879, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 880, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 881, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 882, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 883, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 884, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 885, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 886, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 887, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 888, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 889, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 890, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 891, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 892, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 893, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 894, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 895, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 896, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 897, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 898, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 899, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 900, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 901, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 902, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 903, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 904, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 905, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 906, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 907, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 908, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 909, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 910, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 911, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 912, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 913, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 914, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 915, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 916, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 917, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 918, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 919, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 920, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 921, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 922, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 923, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 924, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 925, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 926, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 927, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 928, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 929, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 930, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 931, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 932, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 933, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 934, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 935, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 936, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 937, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 938, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 939, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 940, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 941, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 942, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 943, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 944, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 945, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 946, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 947, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 948, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 949, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 950, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 951, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 952, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 953, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 954, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 955, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 956, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 957, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 958, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 959, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 960, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 961, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 962, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 963, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 964, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 965, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 966, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 967, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 968, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 969, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 970, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 971, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 972, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 973, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 974, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 975, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 976, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 977, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 978, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 979, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 980, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 981, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 982, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 983, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 984, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 985, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 986, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 987, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 988, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 989, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 990, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 991, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 992, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 993, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 994, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 995, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 996, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 997, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 998, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 999, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }] + }, { + "personPropertyId": { + "id": { + "@type": "type.googleapis.com/gov.hhs.aspr.ms.taskit.protobuf.WrapperEnumValue", + "enumTypeUrl": "gov.hhs.aspr.ms.gcm.taskit.protobuf.demo.PersonPropertyInput", + "value": "VACCINE_ATTEMPTS" + } + }, + "propertyValues": [{ + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 1, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 2, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 3, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 4, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 5, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 6, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 7, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 8, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 9, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 10, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 11, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 12, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 13, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 14, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 15, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 16, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 17, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 18, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 19, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 20, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 21, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 22, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 23, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 24, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 25, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 26, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 27, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 28, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 29, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 30, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 31, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 32, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 33, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 34, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 35, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 36, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 37, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 38, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 39, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 40, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 41, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 42, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 43, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 44, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 45, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 46, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 47, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 48, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 49, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 50, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 51, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 52, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 53, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 54, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 55, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 56, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 57, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 58, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 59, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 60, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 61, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 62, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 63, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 64, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 65, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 66, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 67, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 68, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 69, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 70, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 71, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 72, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 73, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 74, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 75, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 76, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 77, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 78, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 79, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 80, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 81, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 82, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 83, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 84, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 85, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 86, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 87, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 88, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 89, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 90, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 91, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 92, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 93, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 94, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 95, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 96, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 97, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 98, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 99, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 100, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 101, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 102, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 103, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 104, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 105, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 106, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 107, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 108, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 109, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 110, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 111, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 112, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 113, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 114, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 115, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 116, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 117, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 118, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 119, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 120, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 121, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 122, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 123, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 124, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 125, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 126, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 127, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 128, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 129, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 130, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 131, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 132, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 133, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 134, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 135, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 136, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 137, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 138, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 139, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 140, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 141, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 142, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 143, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 144, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 145, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 146, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 147, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 148, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 149, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 150, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 151, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 152, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 153, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 154, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 155, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 156, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 157, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 158, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 159, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 160, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 161, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 162, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 163, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 164, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 165, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 166, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 167, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 168, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 169, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 170, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 171, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 172, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 173, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 174, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 175, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 176, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 177, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 178, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 179, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 180, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 181, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 182, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 183, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 184, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 185, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 186, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 187, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 188, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 189, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 190, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 191, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 192, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 193, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 194, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 195, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 196, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 197, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 198, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 199, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 200, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 201, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 202, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 203, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 204, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 205, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 206, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 207, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 208, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 209, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 210, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 211, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 212, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 213, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 214, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 215, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 216, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 217, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 218, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 219, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 220, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 221, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 222, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 223, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 224, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 225, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 226, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 227, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 228, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 229, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 230, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 231, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 232, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 233, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 234, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 235, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 236, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 237, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 238, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 239, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 240, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 241, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 242, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 243, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 244, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 245, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 246, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 247, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 248, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 249, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 250, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 251, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 252, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 253, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 254, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 255, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 256, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 257, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 258, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 259, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 260, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 261, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 262, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 263, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 264, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 265, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 266, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 267, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 268, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 269, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 270, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 271, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 272, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 273, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 274, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 275, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 276, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 277, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 278, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 279, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 280, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 281, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 282, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 283, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 284, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 285, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 286, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 287, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 288, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 289, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 290, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 291, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 292, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 293, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 294, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 295, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 296, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 297, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 298, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 299, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 300, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 301, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 302, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 303, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 304, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 305, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 306, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 307, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 308, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 309, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 310, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 311, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 312, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 313, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 314, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 315, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 316, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 317, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 318, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 319, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 320, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 321, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 322, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 323, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 324, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 325, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 326, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 327, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 328, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 329, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 330, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 331, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 332, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 333, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 334, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 335, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 336, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 337, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 338, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 339, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 340, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 341, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 342, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 343, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 344, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 345, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 346, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 347, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 348, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 349, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 350, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 351, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 352, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 353, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 354, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 355, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 356, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 357, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 358, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 359, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 360, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 361, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 362, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 363, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 364, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 365, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 366, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 367, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 368, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 369, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 370, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 371, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 372, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 373, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 374, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 375, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 376, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 377, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 378, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 379, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 380, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 381, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 382, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 383, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 384, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 385, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 386, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 387, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 388, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 389, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 390, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 391, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 392, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 393, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 394, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 395, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 396, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 397, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 398, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 399, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 400, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 401, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 402, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 403, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 404, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 405, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 406, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 407, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 408, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 409, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 410, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 411, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 412, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 413, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 414, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 415, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 416, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 417, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 418, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 419, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 420, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 421, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 422, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 423, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 424, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 425, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 426, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 427, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 428, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 429, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 430, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 431, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 432, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 433, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 434, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 435, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 436, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 437, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 438, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 439, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 440, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 441, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 442, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 443, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 444, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 445, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 446, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 447, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 448, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 449, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 450, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 451, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 452, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 453, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 454, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 455, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 456, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 457, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 458, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 459, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 460, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 461, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 462, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 463, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 464, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 465, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 466, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 467, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 468, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 469, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 470, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 471, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 472, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 473, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 474, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 475, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 476, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 477, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 478, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 479, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 480, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 481, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 482, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 483, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 484, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 485, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 486, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 487, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 488, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 489, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 490, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 491, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 492, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 493, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 494, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 495, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 496, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 497, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 498, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 499, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 500, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 501, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 502, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 503, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 504, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 505, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 506, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 507, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 508, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 509, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 510, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 511, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 512, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 513, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 514, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 515, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 516, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 517, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 518, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 519, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 520, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 521, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 522, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 523, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 524, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 525, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 526, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 527, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 528, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 529, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 530, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 531, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 532, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 533, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 534, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 535, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 536, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 537, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 538, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 539, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 540, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 541, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 542, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 543, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 544, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 545, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 546, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 547, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 548, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 549, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 550, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 551, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 552, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 553, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 554, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 555, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 556, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 557, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 558, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 559, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 560, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 561, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 562, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 563, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 564, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 565, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 566, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 567, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 568, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 569, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 570, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 571, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 572, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 573, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 574, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 575, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 576, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 577, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 578, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 579, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 580, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 581, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 582, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 583, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 584, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 585, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 586, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 587, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 588, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 589, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 590, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 591, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 592, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 593, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 594, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 595, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 596, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 597, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 598, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 599, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 600, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 601, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 602, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 603, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 604, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 605, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 606, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 607, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 608, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 609, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 610, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 611, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 612, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 613, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 614, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 615, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 616, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 617, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 618, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 619, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 620, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 621, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 622, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 623, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 624, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 625, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 626, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 627, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 628, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 629, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 630, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 631, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 632, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 633, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 634, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 635, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 636, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 637, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 638, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 639, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 640, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 641, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 642, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 643, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 644, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 645, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 646, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 647, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 648, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 649, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 650, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 651, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 652, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 653, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 654, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 655, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 656, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 657, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 658, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 659, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 660, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 661, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 662, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 663, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 664, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 665, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 666, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 667, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 668, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 669, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 670, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 671, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 672, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 673, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 674, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 675, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 676, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 677, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 678, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 679, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 680, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 681, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 682, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 683, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 684, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 685, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 686, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 687, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 688, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 689, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 690, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 691, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 692, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 693, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 694, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 695, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 696, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 697, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 698, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 699, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 700, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 701, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 702, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 703, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 704, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 705, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 706, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 707, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 708, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 709, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 710, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 711, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 712, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 713, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 714, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 715, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 716, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 717, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 718, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 719, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 720, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 721, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 722, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 723, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 724, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 725, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 726, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 727, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 728, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 729, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 730, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 731, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 732, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 733, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 734, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 735, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 736, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 737, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 738, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 739, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 740, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 741, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 742, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 743, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 744, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 745, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 746, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 747, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 748, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 749, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 750, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 751, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 752, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 753, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 754, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 755, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 756, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 757, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 758, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 759, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 760, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 761, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 762, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 763, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 764, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 765, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 766, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 767, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 768, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 769, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 770, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 771, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 772, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 773, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 774, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 775, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 776, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 777, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 778, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 779, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 780, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 781, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 782, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 783, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 784, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 785, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 786, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 787, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 788, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 789, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 790, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 791, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 792, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 793, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 794, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 795, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 796, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 797, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 798, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 799, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 800, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 801, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 802, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 803, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 804, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 805, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 806, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 807, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 808, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 809, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 810, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 811, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 812, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 813, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 814, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 815, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 816, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 817, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 818, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 819, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 820, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 821, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 822, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 823, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 824, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 825, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 826, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 827, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 828, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 829, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 830, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 831, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 832, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 833, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 834, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 835, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 836, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 837, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 838, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 839, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 840, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 841, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 842, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 843, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 844, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 845, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 846, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 847, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 848, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 849, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 850, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 851, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 852, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 853, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 854, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 855, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 856, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 857, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 858, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 859, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 860, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 861, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 862, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 863, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 864, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 865, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 866, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 867, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 868, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 869, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 870, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 871, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 872, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 873, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 874, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 875, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 876, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 877, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 878, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 879, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 880, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 881, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 882, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 883, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 884, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 885, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 886, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 887, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 888, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 889, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 890, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 891, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 892, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 893, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 894, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 895, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 896, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 897, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 898, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 899, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 900, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 901, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 902, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 903, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 904, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 905, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 906, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 907, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 908, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 909, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 910, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 911, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 912, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 913, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 914, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 915, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 916, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 917, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 918, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 919, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 920, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 921, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 922, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 923, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 924, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 925, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 926, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 927, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 928, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 929, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 930, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 931, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 932, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 933, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 934, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 935, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 936, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 937, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 938, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 939, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 940, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 941, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 942, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 943, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 944, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 945, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 946, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 947, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 948, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 949, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 950, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 951, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 952, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 953, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 954, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 955, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 956, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 957, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 958, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 959, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 960, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 961, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 962, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 963, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 964, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 965, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 966, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 967, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 968, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 969, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 970, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 971, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 972, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 973, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 974, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 975, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 976, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 977, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 978, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 979, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 980, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 981, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 982, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 983, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 984, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 985, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 986, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 987, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 988, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 989, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 990, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 991, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 992, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 993, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 994, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 995, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 996, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 1 + } + }, { + "pId": 997, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 998, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }, { + "pId": 999, + "value": { + "@type": "type.googleapis.com/google.protobuf.Int32Value", + "value": 0 + } + }] + }, { + "personPropertyId": { + "id": { + "@type": "type.googleapis.com/gov.hhs.aspr.ms.taskit.protobuf.WrapperEnumValue", + "enumTypeUrl": "gov.hhs.aspr.ms.gcm.taskit.protobuf.demo.PersonPropertyInput", + "value": "VACCINATED" + } + }, + "propertyValues": [{ + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 1, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 2, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 3, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 4, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 5, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 6, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 7, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 8, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 9, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 10, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 11, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 12, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 13, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 14, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 15, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 16, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 17, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 18, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 19, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 20, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 21, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 22, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 23, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 24, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 25, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 26, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 27, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 28, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 29, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 30, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 31, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 32, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 33, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 34, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 35, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 36, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 37, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 38, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 39, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 40, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 41, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 42, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 43, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 44, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 45, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 46, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 47, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 48, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 49, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 50, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 51, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 52, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 53, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 54, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 55, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 56, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 57, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 58, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 59, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 60, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 61, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 62, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 63, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 64, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 65, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 66, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 67, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 68, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 69, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 70, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 71, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 72, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 73, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 74, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 75, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 76, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 77, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 78, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 79, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 80, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 81, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 82, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 83, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 84, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 85, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 86, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 87, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 88, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 89, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 90, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 91, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 92, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 93, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 94, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 95, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 96, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 97, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 98, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 99, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 100, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 101, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 102, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 103, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 104, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 105, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 106, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 107, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 108, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 109, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 110, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 111, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 112, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 113, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 114, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 115, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 116, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 117, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 118, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 119, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 120, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 121, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 122, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 123, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 124, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 125, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 126, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 127, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 128, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 129, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 130, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 131, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 132, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 133, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 134, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 135, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 136, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 137, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 138, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 139, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 140, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 141, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 142, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 143, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 144, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 145, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 146, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 147, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 148, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 149, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 150, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 151, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 152, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 153, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 154, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 155, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 156, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 157, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 158, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 159, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 160, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 161, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 162, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 163, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 164, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 165, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 166, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 167, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 168, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 169, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 170, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 171, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 172, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 173, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 174, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 175, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 176, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 177, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 178, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 179, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 180, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 181, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 182, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 183, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 184, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 185, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 186, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 187, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 188, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 189, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 190, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 191, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 192, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 193, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 194, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 195, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 196, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 197, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 198, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 199, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 200, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 201, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 202, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 203, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 204, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 205, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 206, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 207, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 208, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 209, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 210, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 211, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 212, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 213, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 214, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 215, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 216, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 217, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 218, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 219, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 220, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 221, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 222, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 223, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 224, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 225, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 226, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 227, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 228, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 229, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 230, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 231, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 232, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 233, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 234, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 235, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 236, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 237, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 238, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 239, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 240, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 241, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 242, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 243, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 244, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 245, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 246, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 247, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 248, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 249, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 250, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 251, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 252, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 253, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 254, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 255, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 256, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 257, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 258, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 259, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 260, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 261, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 262, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 263, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 264, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 265, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 266, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 267, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 268, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 269, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 270, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 271, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 272, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 273, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 274, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 275, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 276, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 277, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 278, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 279, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 280, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 281, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 282, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 283, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 284, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 285, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 286, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 287, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 288, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 289, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 290, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 291, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 292, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 293, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 294, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 295, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 296, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 297, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 298, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 299, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 300, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 301, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 302, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 303, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 304, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 305, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 306, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 307, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 308, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 309, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 310, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 311, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 312, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 313, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 314, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 315, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 316, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 317, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 318, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 319, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 320, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 321, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 322, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 323, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 324, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 325, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 326, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 327, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 328, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 329, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 330, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 331, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 332, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 333, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 334, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 335, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 336, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 337, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 338, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 339, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 340, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 341, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 342, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 343, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 344, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 345, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 346, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 347, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 348, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 349, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 350, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 351, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 352, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 353, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 354, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 355, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 356, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 357, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 358, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 359, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 360, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 361, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 362, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 363, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 364, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 365, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 366, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 367, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 368, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 369, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 370, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 371, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 372, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 373, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 374, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 375, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 376, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 377, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 378, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 379, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 380, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 381, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 382, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 383, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 384, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 385, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 386, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 387, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 388, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 389, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 390, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 391, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 392, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 393, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 394, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 395, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 396, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 397, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 398, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 399, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 400, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 401, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 402, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 403, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 404, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 405, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 406, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 407, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 408, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 409, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 410, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 411, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 412, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 413, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 414, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 415, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 416, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 417, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 418, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 419, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 420, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 421, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 422, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 423, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 424, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 425, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 426, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 427, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 428, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 429, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 430, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 431, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 432, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 433, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 434, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 435, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 436, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 437, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 438, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 439, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 440, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 441, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 442, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 443, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 444, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 445, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 446, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 447, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 448, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 449, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 450, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 451, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 452, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 453, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 454, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 455, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 456, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 457, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 458, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 459, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 460, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 461, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 462, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 463, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 464, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 465, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 466, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 467, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 468, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 469, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 470, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 471, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 472, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 473, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 474, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 475, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 476, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 477, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 478, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 479, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 480, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 481, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 482, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 483, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 484, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 485, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 486, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 487, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 488, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 489, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 490, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 491, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 492, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 493, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 494, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 495, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 496, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 497, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 498, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 499, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 500, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 501, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 502, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 503, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 504, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 505, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 506, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 507, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 508, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 509, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 510, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 511, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 512, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 513, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 514, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 515, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 516, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 517, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 518, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 519, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 520, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 521, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 522, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 523, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 524, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 525, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 526, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 527, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 528, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 529, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 530, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 531, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 532, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 533, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 534, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 535, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 536, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 537, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 538, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 539, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 540, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 541, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 542, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 543, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 544, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 545, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 546, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 547, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 548, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 549, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 550, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 551, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 552, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 553, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 554, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 555, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 556, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 557, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 558, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 559, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 560, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 561, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 562, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 563, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 564, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 565, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 566, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 567, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 568, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 569, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 570, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 571, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 572, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 573, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 574, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 575, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 576, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 577, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 578, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 579, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 580, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 581, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 582, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 583, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 584, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 585, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 586, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 587, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 588, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 589, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 590, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 591, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 592, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 593, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 594, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 595, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 596, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 597, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 598, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 599, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 600, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 601, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 602, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 603, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 604, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 605, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 606, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 607, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 608, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 609, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 610, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 611, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 612, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 613, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 614, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 615, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 616, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 617, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 618, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 619, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 620, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 621, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 622, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 623, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 624, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 625, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 626, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 627, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 628, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 629, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 630, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 631, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 632, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 633, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 634, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 635, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 636, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 637, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 638, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 639, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 640, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 641, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 642, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 643, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 644, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 645, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 646, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 647, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 648, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 649, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 650, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 651, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 652, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 653, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 654, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 655, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 656, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 657, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 658, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 659, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 660, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 661, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 662, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 663, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 664, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 665, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 666, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 667, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 668, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 669, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 670, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 671, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 672, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 673, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 674, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 675, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 676, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 677, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 678, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 679, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 680, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 681, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 682, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 683, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 684, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 685, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 686, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 687, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 688, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 689, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 690, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 691, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 692, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 693, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 694, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 695, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 696, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 697, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 698, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 699, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 700, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 701, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 702, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 703, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 704, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 705, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 706, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 707, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 708, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 709, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 710, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 711, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 712, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 713, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 714, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 715, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 716, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 717, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 718, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 719, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 720, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 721, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 722, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 723, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 724, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 725, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 726, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 727, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 728, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 729, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 730, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 731, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 732, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 733, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 734, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 735, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 736, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 737, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 738, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 739, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 740, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 741, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 742, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 743, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 744, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 745, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 746, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 747, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 748, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 749, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 750, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 751, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 752, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 753, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 754, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 755, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 756, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 757, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 758, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 759, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 760, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 761, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 762, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 763, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 764, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 765, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 766, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 767, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 768, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 769, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 770, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 771, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 772, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 773, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 774, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 775, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 776, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 777, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 778, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 779, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 780, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 781, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 782, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 783, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 784, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 785, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 786, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 787, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 788, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 789, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 790, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 791, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 792, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 793, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 794, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 795, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 796, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 797, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 798, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 799, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 800, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 801, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 802, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 803, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 804, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 805, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 806, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 807, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 808, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 809, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 810, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 811, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 812, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 813, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 814, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 815, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 816, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 817, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 818, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 819, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 820, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 821, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 822, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 823, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 824, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 825, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 826, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 827, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 828, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 829, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 830, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 831, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 832, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 833, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 834, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 835, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 836, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 837, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 838, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 839, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 840, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 841, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 842, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 843, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 844, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 845, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 846, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 847, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 848, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 849, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 850, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 851, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 852, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 853, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 854, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 855, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 856, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 857, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 858, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 859, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 860, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 861, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 862, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 863, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 864, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 865, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 866, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 867, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 868, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 869, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 870, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 871, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 872, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 873, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 874, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 875, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 876, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 877, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 878, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 879, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 880, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 881, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 882, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 883, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 884, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 885, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 886, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 887, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 888, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 889, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 890, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 891, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 892, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 893, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 894, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 895, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 896, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 897, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 898, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 899, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 900, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 901, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 902, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 903, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 904, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 905, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 906, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 907, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 908, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 909, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 910, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 911, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 912, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 913, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 914, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 915, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 916, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 917, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 918, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 919, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 920, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 921, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 922, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 923, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 924, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 925, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 926, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 927, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 928, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 929, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 930, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 931, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 932, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 933, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 934, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 935, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 936, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 937, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 938, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 939, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 940, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 941, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 942, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 943, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 944, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 945, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 946, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 947, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 948, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 949, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 950, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 951, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 952, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 953, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 954, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 955, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 956, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 957, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 958, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 959, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 960, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 961, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 962, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 963, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 964, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 965, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 966, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 967, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 968, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 969, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 970, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 971, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 972, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 973, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 974, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 975, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 976, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 977, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 978, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 979, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 980, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 981, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 982, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 983, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 984, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 985, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 986, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 987, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 988, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 989, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 990, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 991, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 992, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 993, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 994, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 995, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 996, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": true + } + }, { + "pId": 997, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 998, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }, { + "pId": 999, + "value": { + "@type": "type.googleapis.com/google.protobuf.BoolValue", + "value": false + } + }] + }] +} \ No newline at end of file diff --git a/demos/gcm-taskit/src/main/resources/regionsInput.json b/demos/gcm-taskit/src/main/resources/regionsInput.json new file mode 100644 index 000000000..d33725d51 --- /dev/null +++ b/demos/gcm-taskit/src/main/resources/regionsInput.json @@ -0,0 +1,2035 @@ +{ + "regionIds": [{ + "id": { + "@type": "type.googleapis.com/gov.hhs.aspr.ms.gcm.taskit.protobuf.demo.RegionInput" + } + }, { + "id": { + "@type": "type.googleapis.com/gov.hhs.aspr.ms.gcm.taskit.protobuf.demo.RegionInput", + "id": 1 + } + }, { + "id": { + "@type": "type.googleapis.com/gov.hhs.aspr.ms.gcm.taskit.protobuf.demo.RegionInput", + "id": 2 + } + }, { + "id": { + "@type": "type.googleapis.com/gov.hhs.aspr.ms.gcm.taskit.protobuf.demo.RegionInput", + "id": 3 + } + }, { + "id": { + "@type": "type.googleapis.com/gov.hhs.aspr.ms.gcm.taskit.protobuf.demo.RegionInput", + "id": 4 + } + }], + "personRegions": [{ + "regionId": { + "id": { + "@type": "type.googleapis.com/gov.hhs.aspr.ms.gcm.taskit.protobuf.demo.RegionInput" + } + }, + "people": [{ + "personId": 0 + }, { + "personId": 1 + }, { + "personId": 2 + }, { + "personId": 3 + }, { + "personId": 4 + }, { + "personId": 5 + }, { + "personId": 6 + }, { + "personId": 7 + }, { + "personId": 8 + }, { + "personId": 9 + }, { + "personId": 10 + }, { + "personId": 11 + }, { + "personId": 12 + }, { + "personId": 13 + }, { + "personId": 14 + }, { + "personId": 15 + }, { + "personId": 16 + }, { + "personId": 17 + }, { + "personId": 18 + }, { + "personId": 19 + }, { + "personId": 20 + }, { + "personId": 21 + }, { + "personId": 22 + }, { + "personId": 23 + }, { + "personId": 24 + }, { + "personId": 25 + }, { + "personId": 26 + }, { + "personId": 27 + }, { + "personId": 28 + }, { + "personId": 29 + }, { + "personId": 30 + }, { + "personId": 31 + }, { + "personId": 32 + }, { + "personId": 33 + }, { + "personId": 34 + }, { + "personId": 35 + }, { + "personId": 36 + }, { + "personId": 37 + }, { + "personId": 38 + }, { + "personId": 39 + }, { + "personId": 40 + }, { + "personId": 41 + }, { + "personId": 42 + }, { + "personId": 43 + }, { + "personId": 44 + }, { + "personId": 45 + }, { + "personId": 46 + }, { + "personId": 47 + }, { + "personId": 48 + }, { + "personId": 49 + }, { + "personId": 50 + }, { + "personId": 51 + }, { + "personId": 52 + }, { + "personId": 53 + }, { + "personId": 54 + }, { + "personId": 55 + }, { + "personId": 56 + }, { + "personId": 57 + }, { + "personId": 58 + }, { + "personId": 59 + }, { + "personId": 60 + }, { + "personId": 61 + }, { + "personId": 62 + }, { + "personId": 63 + }, { + "personId": 64 + }, { + "personId": 65 + }, { + "personId": 66 + }, { + "personId": 67 + }, { + "personId": 68 + }, { + "personId": 69 + }, { + "personId": 70 + }, { + "personId": 71 + }, { + "personId": 72 + }, { + "personId": 73 + }, { + "personId": 74 + }, { + "personId": 75 + }, { + "personId": 76 + }, { + "personId": 77 + }, { + "personId": 78 + }, { + "personId": 79 + }, { + "personId": 80 + }, { + "personId": 81 + }, { + "personId": 82 + }, { + "personId": 83 + }, { + "personId": 84 + }, { + "personId": 85 + }, { + "personId": 86 + }, { + "personId": 87 + }, { + "personId": 88 + }, { + "personId": 89 + }, { + "personId": 90 + }, { + "personId": 91 + }, { + "personId": 92 + }, { + "personId": 93 + }, { + "personId": 94 + }, { + "personId": 95 + }, { + "personId": 96 + }, { + "personId": 97 + }, { + "personId": 98 + }, { + "personId": 99 + }, { + "personId": 100 + }, { + "personId": 101 + }, { + "personId": 102 + }, { + "personId": 103 + }, { + "personId": 104 + }, { + "personId": 105 + }, { + "personId": 106 + }, { + "personId": 107 + }, { + "personId": 108 + }, { + "personId": 109 + }, { + "personId": 110 + }, { + "personId": 111 + }, { + "personId": 112 + }, { + "personId": 113 + }, { + "personId": 114 + }, { + "personId": 115 + }, { + "personId": 116 + }, { + "personId": 117 + }, { + "personId": 118 + }, { + "personId": 119 + }, { + "personId": 120 + }, { + "personId": 121 + }, { + "personId": 122 + }, { + "personId": 123 + }, { + "personId": 124 + }, { + "personId": 125 + }, { + "personId": 126 + }, { + "personId": 127 + }, { + "personId": 128 + }, { + "personId": 129 + }, { + "personId": 130 + }, { + "personId": 131 + }, { + "personId": 132 + }, { + "personId": 133 + }, { + "personId": 134 + }, { + "personId": 135 + }, { + "personId": 136 + }, { + "personId": 137 + }, { + "personId": 138 + }, { + "personId": 139 + }, { + "personId": 140 + }, { + "personId": 141 + }, { + "personId": 142 + }, { + "personId": 143 + }, { + "personId": 144 + }, { + "personId": 145 + }, { + "personId": 146 + }, { + "personId": 147 + }, { + "personId": 148 + }, { + "personId": 149 + }, { + "personId": 150 + }, { + "personId": 151 + }, { + "personId": 152 + }, { + "personId": 153 + }, { + "personId": 154 + }, { + "personId": 155 + }, { + "personId": 156 + }, { + "personId": 157 + }, { + "personId": 158 + }, { + "personId": 159 + }, { + "personId": 160 + }, { + "personId": 161 + }, { + "personId": 162 + }, { + "personId": 163 + }, { + "personId": 164 + }, { + "personId": 165 + }, { + "personId": 166 + }, { + "personId": 167 + }, { + "personId": 168 + }, { + "personId": 169 + }, { + "personId": 170 + }, { + "personId": 171 + }, { + "personId": 172 + }, { + "personId": 173 + }, { + "personId": 174 + }, { + "personId": 175 + }, { + "personId": 176 + }, { + "personId": 177 + }, { + "personId": 178 + }, { + "personId": 179 + }, { + "personId": 180 + }, { + "personId": 181 + }, { + "personId": 182 + }, { + "personId": 183 + }, { + "personId": 184 + }, { + "personId": 185 + }, { + "personId": 186 + }, { + "personId": 187 + }, { + "personId": 188 + }, { + "personId": 189 + }, { + "personId": 190 + }, { + "personId": 191 + }, { + "personId": 192 + }, { + "personId": 193 + }, { + "personId": 194 + }, { + "personId": 195 + }, { + "personId": 196 + }, { + "personId": 197 + }, { + "personId": 198 + }, { + "personId": 199 + }, { + "personId": 200 + }, { + "personId": 201 + }, { + "personId": 202 + }, { + "personId": 203 + }, { + "personId": 204 + }, { + "personId": 205 + }, { + "personId": 206 + }, { + "personId": 207 + }, { + "personId": 208 + }, { + "personId": 209 + }, { + "personId": 210 + }, { + "personId": 211 + }, { + "personId": 212 + }, { + "personId": 213 + }, { + "personId": 214 + }, { + "personId": 215 + }, { + "personId": 216 + }, { + "personId": 217 + }, { + "personId": 218 + }, { + "personId": 219 + }, { + "personId": 220 + }, { + "personId": 221 + }, { + "personId": 222 + }, { + "personId": 223 + }, { + "personId": 224 + }, { + "personId": 225 + }, { + "personId": 226 + }, { + "personId": 227 + }, { + "personId": 228 + }, { + "personId": 229 + }, { + "personId": 230 + }, { + "personId": 231 + }, { + "personId": 232 + }, { + "personId": 233 + }, { + "personId": 234 + }, { + "personId": 235 + }, { + "personId": 236 + }, { + "personId": 237 + }, { + "personId": 238 + }, { + "personId": 239 + }, { + "personId": 240 + }, { + "personId": 241 + }, { + "personId": 242 + }, { + "personId": 243 + }, { + "personId": 244 + }, { + "personId": 245 + }, { + "personId": 246 + }, { + "personId": 247 + }, { + "personId": 248 + }, { + "personId": 249 + }, { + "personId": 250 + }, { + "personId": 251 + }, { + "personId": 252 + }, { + "personId": 253 + }, { + "personId": 254 + }, { + "personId": 255 + }, { + "personId": 256 + }, { + "personId": 257 + }, { + "personId": 258 + }, { + "personId": 259 + }, { + "personId": 260 + }, { + "personId": 261 + }, { + "personId": 262 + }, { + "personId": 263 + }, { + "personId": 264 + }, { + "personId": 265 + }, { + "personId": 266 + }, { + "personId": 267 + }, { + "personId": 268 + }, { + "personId": 269 + }, { + "personId": 270 + }, { + "personId": 271 + }, { + "personId": 272 + }, { + "personId": 273 + }, { + "personId": 274 + }, { + "personId": 275 + }, { + "personId": 276 + }, { + "personId": 277 + }, { + "personId": 278 + }, { + "personId": 279 + }, { + "personId": 280 + }, { + "personId": 281 + }, { + "personId": 282 + }, { + "personId": 283 + }, { + "personId": 284 + }, { + "personId": 285 + }, { + "personId": 286 + }, { + "personId": 287 + }, { + "personId": 288 + }, { + "personId": 289 + }, { + "personId": 290 + }, { + "personId": 291 + }, { + "personId": 292 + }, { + "personId": 293 + }, { + "personId": 294 + }, { + "personId": 295 + }, { + "personId": 296 + }, { + "personId": 297 + }, { + "personId": 298 + }, { + "personId": 299 + }, { + "personId": 300 + }, { + "personId": 301 + }, { + "personId": 302 + }, { + "personId": 303 + }, { + "personId": 304 + }, { + "personId": 305 + }, { + "personId": 306 + }, { + "personId": 307 + }, { + "personId": 308 + }, { + "personId": 309 + }, { + "personId": 310 + }, { + "personId": 311 + }, { + "personId": 312 + }, { + "personId": 313 + }, { + "personId": 314 + }, { + "personId": 315 + }, { + "personId": 316 + }, { + "personId": 317 + }, { + "personId": 318 + }, { + "personId": 319 + }, { + "personId": 320 + }, { + "personId": 321 + }, { + "personId": 322 + }, { + "personId": 323 + }, { + "personId": 324 + }, { + "personId": 325 + }, { + "personId": 326 + }, { + "personId": 327 + }, { + "personId": 328 + }, { + "personId": 329 + }, { + "personId": 330 + }, { + "personId": 331 + }, { + "personId": 332 + }, { + "personId": 333 + }, { + "personId": 334 + }, { + "personId": 335 + }, { + "personId": 336 + }, { + "personId": 337 + }, { + "personId": 338 + }, { + "personId": 339 + }, { + "personId": 340 + }, { + "personId": 341 + }, { + "personId": 342 + }, { + "personId": 343 + }, { + "personId": 344 + }, { + "personId": 345 + }, { + "personId": 346 + }, { + "personId": 347 + }, { + "personId": 348 + }, { + "personId": 349 + }, { + "personId": 350 + }, { + "personId": 351 + }, { + "personId": 352 + }, { + "personId": 353 + }, { + "personId": 354 + }, { + "personId": 355 + }, { + "personId": 356 + }, { + "personId": 357 + }, { + "personId": 358 + }, { + "personId": 359 + }, { + "personId": 360 + }, { + "personId": 361 + }, { + "personId": 362 + }, { + "personId": 363 + }, { + "personId": 364 + }, { + "personId": 365 + }, { + "personId": 366 + }, { + "personId": 367 + }, { + "personId": 368 + }, { + "personId": 369 + }, { + "personId": 370 + }, { + "personId": 371 + }, { + "personId": 372 + }, { + "personId": 373 + }, { + "personId": 374 + }, { + "personId": 375 + }, { + "personId": 376 + }, { + "personId": 377 + }, { + "personId": 378 + }, { + "personId": 379 + }, { + "personId": 380 + }, { + "personId": 381 + }, { + "personId": 382 + }, { + "personId": 383 + }, { + "personId": 384 + }, { + "personId": 385 + }, { + "personId": 386 + }, { + "personId": 387 + }, { + "personId": 388 + }, { + "personId": 389 + }, { + "personId": 390 + }, { + "personId": 391 + }, { + "personId": 392 + }, { + "personId": 393 + }, { + "personId": 394 + }, { + "personId": 395 + }, { + "personId": 396 + }, { + "personId": 397 + }, { + "personId": 398 + }, { + "personId": 399 + }, { + "personId": 400 + }, { + "personId": 401 + }, { + "personId": 402 + }, { + "personId": 403 + }, { + "personId": 404 + }, { + "personId": 405 + }, { + "personId": 406 + }, { + "personId": 407 + }, { + "personId": 408 + }, { + "personId": 409 + }, { + "personId": 410 + }, { + "personId": 411 + }, { + "personId": 412 + }, { + "personId": 413 + }, { + "personId": 414 + }, { + "personId": 415 + }, { + "personId": 416 + }, { + "personId": 417 + }, { + "personId": 418 + }, { + "personId": 419 + }, { + "personId": 420 + }, { + "personId": 421 + }, { + "personId": 422 + }, { + "personId": 423 + }, { + "personId": 424 + }, { + "personId": 425 + }, { + "personId": 426 + }, { + "personId": 427 + }, { + "personId": 428 + }, { + "personId": 429 + }, { + "personId": 430 + }, { + "personId": 431 + }, { + "personId": 432 + }, { + "personId": 433 + }, { + "personId": 434 + }, { + "personId": 435 + }, { + "personId": 436 + }, { + "personId": 437 + }, { + "personId": 438 + }, { + "personId": 439 + }, { + "personId": 440 + }, { + "personId": 441 + }, { + "personId": 442 + }, { + "personId": 443 + }, { + "personId": 444 + }, { + "personId": 445 + }, { + "personId": 446 + }, { + "personId": 447 + }, { + "personId": 448 + }, { + "personId": 449 + }, { + "personId": 450 + }, { + "personId": 451 + }, { + "personId": 452 + }, { + "personId": 453 + }, { + "personId": 454 + }, { + "personId": 455 + }, { + "personId": 456 + }, { + "personId": 457 + }, { + "personId": 458 + }, { + "personId": 459 + }, { + "personId": 460 + }, { + "personId": 461 + }, { + "personId": 462 + }, { + "personId": 463 + }, { + "personId": 464 + }, { + "personId": 465 + }, { + "personId": 466 + }, { + "personId": 467 + }, { + "personId": 468 + }, { + "personId": 469 + }, { + "personId": 470 + }, { + "personId": 471 + }, { + "personId": 472 + }, { + "personId": 473 + }, { + "personId": 474 + }, { + "personId": 475 + }, { + "personId": 476 + }, { + "personId": 477 + }, { + "personId": 478 + }, { + "personId": 479 + }, { + "personId": 480 + }, { + "personId": 481 + }, { + "personId": 482 + }, { + "personId": 483 + }, { + "personId": 484 + }, { + "personId": 485 + }, { + "personId": 486 + }, { + "personId": 487 + }, { + "personId": 488 + }, { + "personId": 489 + }, { + "personId": 490 + }, { + "personId": 491 + }, { + "personId": 492 + }, { + "personId": 493 + }, { + "personId": 494 + }, { + "personId": 495 + }, { + "personId": 496 + }, { + "personId": 497 + }, { + "personId": 498 + }, { + "personId": 499 + }, { + "personId": 500 + }, { + "personId": 501 + }, { + "personId": 502 + }, { + "personId": 503 + }, { + "personId": 504 + }, { + "personId": 505 + }, { + "personId": 506 + }, { + "personId": 507 + }, { + "personId": 508 + }, { + "personId": 509 + }, { + "personId": 510 + }, { + "personId": 511 + }, { + "personId": 512 + }, { + "personId": 513 + }, { + "personId": 514 + }, { + "personId": 515 + }, { + "personId": 516 + }, { + "personId": 517 + }, { + "personId": 518 + }, { + "personId": 519 + }, { + "personId": 520 + }, { + "personId": 521 + }, { + "personId": 522 + }, { + "personId": 523 + }, { + "personId": 524 + }, { + "personId": 525 + }, { + "personId": 526 + }, { + "personId": 527 + }, { + "personId": 528 + }, { + "personId": 529 + }, { + "personId": 530 + }, { + "personId": 531 + }, { + "personId": 532 + }, { + "personId": 533 + }, { + "personId": 534 + }, { + "personId": 535 + }, { + "personId": 536 + }, { + "personId": 537 + }, { + "personId": 538 + }, { + "personId": 539 + }, { + "personId": 540 + }, { + "personId": 541 + }, { + "personId": 542 + }, { + "personId": 543 + }, { + "personId": 544 + }, { + "personId": 545 + }, { + "personId": 546 + }, { + "personId": 547 + }, { + "personId": 548 + }, { + "personId": 549 + }, { + "personId": 550 + }, { + "personId": 551 + }, { + "personId": 552 + }, { + "personId": 553 + }, { + "personId": 554 + }, { + "personId": 555 + }, { + "personId": 556 + }, { + "personId": 557 + }, { + "personId": 558 + }, { + "personId": 559 + }, { + "personId": 560 + }, { + "personId": 561 + }, { + "personId": 562 + }, { + "personId": 563 + }, { + "personId": 564 + }, { + "personId": 565 + }, { + "personId": 566 + }, { + "personId": 567 + }, { + "personId": 568 + }, { + "personId": 569 + }, { + "personId": 570 + }, { + "personId": 571 + }, { + "personId": 572 + }, { + "personId": 573 + }, { + "personId": 574 + }, { + "personId": 575 + }, { + "personId": 576 + }, { + "personId": 577 + }, { + "personId": 578 + }, { + "personId": 579 + }, { + "personId": 580 + }, { + "personId": 581 + }, { + "personId": 582 + }, { + "personId": 583 + }, { + "personId": 584 + }, { + "personId": 585 + }, { + "personId": 586 + }, { + "personId": 587 + }, { + "personId": 588 + }, { + "personId": 589 + }, { + "personId": 590 + }, { + "personId": 591 + }, { + "personId": 592 + }, { + "personId": 593 + }, { + "personId": 594 + }, { + "personId": 595 + }, { + "personId": 596 + }, { + "personId": 597 + }, { + "personId": 598 + }, { + "personId": 599 + }, { + "personId": 600 + }, { + "personId": 601 + }, { + "personId": 602 + }, { + "personId": 603 + }, { + "personId": 604 + }, { + "personId": 605 + }, { + "personId": 606 + }, { + "personId": 607 + }, { + "personId": 608 + }, { + "personId": 609 + }, { + "personId": 610 + }, { + "personId": 611 + }, { + "personId": 612 + }, { + "personId": 613 + }, { + "personId": 614 + }, { + "personId": 615 + }, { + "personId": 616 + }, { + "personId": 617 + }, { + "personId": 618 + }, { + "personId": 619 + }, { + "personId": 620 + }, { + "personId": 621 + }, { + "personId": 622 + }, { + "personId": 623 + }, { + "personId": 624 + }, { + "personId": 625 + }, { + "personId": 626 + }, { + "personId": 627 + }, { + "personId": 628 + }, { + "personId": 629 + }, { + "personId": 630 + }, { + "personId": 631 + }, { + "personId": 632 + }, { + "personId": 633 + }, { + "personId": 634 + }, { + "personId": 635 + }, { + "personId": 636 + }, { + "personId": 637 + }, { + "personId": 638 + }, { + "personId": 639 + }, { + "personId": 640 + }, { + "personId": 641 + }, { + "personId": 642 + }, { + "personId": 643 + }, { + "personId": 644 + }, { + "personId": 645 + }, { + "personId": 646 + }, { + "personId": 647 + }, { + "personId": 648 + }, { + "personId": 649 + }, { + "personId": 650 + }, { + "personId": 651 + }, { + "personId": 652 + }, { + "personId": 653 + }, { + "personId": 654 + }, { + "personId": 655 + }, { + "personId": 656 + }, { + "personId": 657 + }, { + "personId": 658 + }, { + "personId": 659 + }, { + "personId": 660 + }, { + "personId": 661 + }, { + "personId": 662 + }, { + "personId": 663 + }, { + "personId": 664 + }, { + "personId": 665 + }, { + "personId": 666 + }, { + "personId": 667 + }, { + "personId": 668 + }, { + "personId": 669 + }, { + "personId": 670 + }, { + "personId": 671 + }, { + "personId": 672 + }, { + "personId": 673 + }, { + "personId": 674 + }, { + "personId": 675 + }, { + "personId": 676 + }, { + "personId": 677 + }, { + "personId": 678 + }, { + "personId": 679 + }, { + "personId": 680 + }, { + "personId": 681 + }, { + "personId": 682 + }, { + "personId": 683 + }, { + "personId": 684 + }, { + "personId": 685 + }, { + "personId": 686 + }, { + "personId": 687 + }, { + "personId": 688 + }, { + "personId": 689 + }, { + "personId": 690 + }, { + "personId": 691 + }, { + "personId": 692 + }, { + "personId": 693 + }, { + "personId": 694 + }, { + "personId": 695 + }, { + "personId": 696 + }, { + "personId": 697 + }, { + "personId": 698 + }, { + "personId": 699 + }, { + "personId": 700 + }, { + "personId": 701 + }, { + "personId": 702 + }, { + "personId": 703 + }, { + "personId": 704 + }, { + "personId": 705 + }, { + "personId": 706 + }, { + "personId": 707 + }, { + "personId": 708 + }, { + "personId": 709 + }, { + "personId": 710 + }, { + "personId": 711 + }, { + "personId": 712 + }, { + "personId": 713 + }, { + "personId": 714 + }, { + "personId": 715 + }, { + "personId": 716 + }, { + "personId": 717 + }, { + "personId": 718 + }, { + "personId": 719 + }, { + "personId": 720 + }, { + "personId": 721 + }, { + "personId": 722 + }, { + "personId": 723 + }, { + "personId": 724 + }, { + "personId": 725 + }, { + "personId": 726 + }, { + "personId": 727 + }, { + "personId": 728 + }, { + "personId": 729 + }, { + "personId": 730 + }, { + "personId": 731 + }, { + "personId": 732 + }, { + "personId": 733 + }, { + "personId": 734 + }, { + "personId": 735 + }, { + "personId": 736 + }, { + "personId": 737 + }, { + "personId": 738 + }, { + "personId": 739 + }, { + "personId": 740 + }, { + "personId": 741 + }, { + "personId": 742 + }, { + "personId": 743 + }, { + "personId": 744 + }, { + "personId": 745 + }, { + "personId": 746 + }, { + "personId": 747 + }, { + "personId": 748 + }, { + "personId": 749 + }, { + "personId": 750 + }, { + "personId": 751 + }, { + "personId": 752 + }, { + "personId": 753 + }, { + "personId": 754 + }, { + "personId": 755 + }, { + "personId": 756 + }, { + "personId": 757 + }, { + "personId": 758 + }, { + "personId": 759 + }, { + "personId": 760 + }, { + "personId": 761 + }, { + "personId": 762 + }, { + "personId": 763 + }, { + "personId": 764 + }, { + "personId": 765 + }, { + "personId": 766 + }, { + "personId": 767 + }, { + "personId": 768 + }, { + "personId": 769 + }, { + "personId": 770 + }, { + "personId": 771 + }, { + "personId": 772 + }, { + "personId": 773 + }, { + "personId": 774 + }, { + "personId": 775 + }, { + "personId": 776 + }, { + "personId": 777 + }, { + "personId": 778 + }, { + "personId": 779 + }, { + "personId": 780 + }, { + "personId": 781 + }, { + "personId": 782 + }, { + "personId": 783 + }, { + "personId": 784 + }, { + "personId": 785 + }, { + "personId": 786 + }, { + "personId": 787 + }, { + "personId": 788 + }, { + "personId": 789 + }, { + "personId": 790 + }, { + "personId": 791 + }, { + "personId": 792 + }, { + "personId": 793 + }, { + "personId": 794 + }, { + "personId": 795 + }, { + "personId": 796 + }, { + "personId": 797 + }, { + "personId": 798 + }, { + "personId": 799 + }, { + "personId": 800 + }, { + "personId": 801 + }, { + "personId": 802 + }, { + "personId": 803 + }, { + "personId": 804 + }, { + "personId": 805 + }, { + "personId": 806 + }, { + "personId": 807 + }, { + "personId": 808 + }, { + "personId": 809 + }, { + "personId": 810 + }, { + "personId": 811 + }, { + "personId": 812 + }, { + "personId": 813 + }, { + "personId": 814 + }, { + "personId": 815 + }, { + "personId": 816 + }, { + "personId": 817 + }, { + "personId": 818 + }, { + "personId": 819 + }, { + "personId": 820 + }, { + "personId": 821 + }, { + "personId": 822 + }, { + "personId": 823 + }, { + "personId": 824 + }, { + "personId": 825 + }, { + "personId": 826 + }, { + "personId": 827 + }, { + "personId": 828 + }, { + "personId": 829 + }, { + "personId": 830 + }, { + "personId": 831 + }, { + "personId": 832 + }, { + "personId": 833 + }, { + "personId": 834 + }, { + "personId": 835 + }, { + "personId": 836 + }, { + "personId": 837 + }, { + "personId": 838 + }, { + "personId": 839 + }, { + "personId": 840 + }, { + "personId": 841 + }, { + "personId": 842 + }, { + "personId": 843 + }, { + "personId": 844 + }, { + "personId": 845 + }, { + "personId": 846 + }, { + "personId": 847 + }, { + "personId": 848 + }, { + "personId": 849 + }, { + "personId": 850 + }, { + "personId": 851 + }, { + "personId": 852 + }, { + "personId": 853 + }, { + "personId": 854 + }, { + "personId": 855 + }, { + "personId": 856 + }, { + "personId": 857 + }, { + "personId": 858 + }, { + "personId": 859 + }, { + "personId": 860 + }, { + "personId": 861 + }, { + "personId": 862 + }, { + "personId": 863 + }, { + "personId": 864 + }, { + "personId": 865 + }, { + "personId": 866 + }, { + "personId": 867 + }, { + "personId": 868 + }, { + "personId": 869 + }, { + "personId": 870 + }, { + "personId": 871 + }, { + "personId": 872 + }, { + "personId": 873 + }, { + "personId": 874 + }, { + "personId": 875 + }, { + "personId": 876 + }, { + "personId": 877 + }, { + "personId": 878 + }, { + "personId": 879 + }, { + "personId": 880 + }, { + "personId": 881 + }, { + "personId": 882 + }, { + "personId": 883 + }, { + "personId": 884 + }, { + "personId": 885 + }, { + "personId": 886 + }, { + "personId": 887 + }, { + "personId": 888 + }, { + "personId": 889 + }, { + "personId": 890 + }, { + "personId": 891 + }, { + "personId": 892 + }, { + "personId": 893 + }, { + "personId": 894 + }, { + "personId": 895 + }, { + "personId": 896 + }, { + "personId": 897 + }, { + "personId": 898 + }, { + "personId": 899 + }, { + "personId": 900 + }, { + "personId": 901 + }, { + "personId": 902 + }, { + "personId": 903 + }, { + "personId": 904 + }, { + "personId": 905 + }, { + "personId": 906 + }, { + "personId": 907 + }, { + "personId": 908 + }, { + "personId": 909 + }, { + "personId": 910 + }, { + "personId": 911 + }, { + "personId": 912 + }, { + "personId": 913 + }, { + "personId": 914 + }, { + "personId": 915 + }, { + "personId": 916 + }, { + "personId": 917 + }, { + "personId": 918 + }, { + "personId": 919 + }, { + "personId": 920 + }, { + "personId": 921 + }, { + "personId": 922 + }, { + "personId": 923 + }, { + "personId": 924 + }, { + "personId": 925 + }, { + "personId": 926 + }, { + "personId": 927 + }, { + "personId": 928 + }, { + "personId": 929 + }, { + "personId": 930 + }, { + "personId": 931 + }, { + "personId": 932 + }, { + "personId": 933 + }, { + "personId": 934 + }, { + "personId": 935 + }, { + "personId": 936 + }, { + "personId": 937 + }, { + "personId": 938 + }, { + "personId": 939 + }, { + "personId": 940 + }, { + "personId": 941 + }, { + "personId": 942 + }, { + "personId": 943 + }, { + "personId": 944 + }, { + "personId": 945 + }, { + "personId": 946 + }, { + "personId": 947 + }, { + "personId": 948 + }, { + "personId": 949 + }, { + "personId": 950 + }, { + "personId": 951 + }, { + "personId": 952 + }, { + "personId": 953 + }, { + "personId": 954 + }, { + "personId": 955 + }, { + "personId": 956 + }, { + "personId": 957 + }, { + "personId": 958 + }, { + "personId": 959 + }, { + "personId": 960 + }, { + "personId": 961 + }, { + "personId": 962 + }, { + "personId": 963 + }, { + "personId": 964 + }, { + "personId": 965 + }, { + "personId": 966 + }, { + "personId": 967 + }, { + "personId": 968 + }, { + "personId": 969 + }, { + "personId": 970 + }, { + "personId": 971 + }, { + "personId": 972 + }, { + "personId": 973 + }, { + "personId": 974 + }, { + "personId": 975 + }, { + "personId": 976 + }, { + "personId": 977 + }, { + "personId": 978 + }, { + "personId": 979 + }, { + "personId": 980 + }, { + "personId": 981 + }, { + "personId": 982 + }, { + "personId": 983 + }, { + "personId": 984 + }, { + "personId": 985 + }, { + "personId": 986 + }, { + "personId": 987 + }, { + "personId": 988 + }, { + "personId": 989 + }, { + "personId": 990 + }, { + "personId": 991 + }, { + "personId": 992 + }, { + "personId": 993 + }, { + "personId": 994 + }, { + "personId": 995 + }, { + "personId": 996 + }, { + "personId": 997 + }, { + "personId": 998 + }, { + "personId": 999 + }] + }] +} \ No newline at end of file diff --git a/demos/gcm-taskit/src/main/resources/stochasticsInput.json b/demos/gcm-taskit/src/main/resources/stochasticsInput.json new file mode 100644 index 000000000..f6a34826e --- /dev/null +++ b/demos/gcm-taskit/src/main/resources/stochasticsInput.json @@ -0,0 +1,7 @@ +{ + "wellState": { + "seed": "4164926956531376674", + "index": 514, + "vArray": "WzExMDg4MzI3MDUsOTIxMTA0NjkzLC0xNjk1NTAxODk0LDI2NjMxNjY3MiwtMTAxMDA1MDQwNCwxMDgzNTc4OSwtMTA1ODE2MjU4MCwtMTc4Mjc0NzIyLC05OTY1Nzc5NjgsLTE1ODc0OTE3ODYsMTI4ODMwMDA1LC0xNDc4Nzg5NTU2LC0xOTgzMjA4NzEyLC04MjYzMDI3ODMsMTUyODc4MjM1MywtMTYzMDE0OTMzNCwtMTk3NTA1MTE5MSwxNzI3OTM2Mzg0LC00NDk2MDQzNjMsMjAxMTA0Nzk2MiwxNjY4ODk4OTIwLC0xODU4MzI2ODY3LDgwNDk3MjkyNCw2MDI4ODg1NjgsLTE5ODM0MzAzODMsLTUyNTcyNTUxOCwtODU1OTUzNDQ2LC0xODA3OTQ3MDM0LC0yODYxNjE1MzgsLTc0ODEwOTY5MiwyNDA2MjkxMzcsLTE2Mzg0NjQwNDAsLTMwOTE4Mjc5NywtMTA2NzUyMDM2MywtMTk5NDkxOTk4MSwtMTQ3MzAxNzcxMiwtMjA3NTI3MDA1MCw0ODI4MjM3ODksMTIzMjA0OTQ0NiwxMTA5MjQ0MzY2LDYyNTg4NzQ3NCw3NTE2NTQwMTUsLTE4ODMxODU1NTQsOTY1MzE3ODAyLC02NzE0NDgwNyw2NzkxNjc2OTksLTc3MzE5MjE0NSwtMjEzMzY1MTE0NCwxODM5NTI1NTAxLC0xODMyMjkzODkyLC0xMzkxOTM4MjcwLC0xNDUxMTgwMDE5LDk5MjE2NzA1NCwtOTY2ODc1MDEzLC0xMDQ3MjE0NjAxLDE4MzIwMzQxMDcsOTE2MjIyOTgzLDE1MDk5MDAxMDcsMTIzNjA1MDkwLDE5NTY5NTc4MzEsLTE0NTMyNDI4ODksLTExMzE5Njc1NTQsMzUzNjY5ODE1LC0yODk1OTMwMDgsMjA0MzcyMDM0MCwtMTg4MjQyNDc1NSwtNDg2ODkyMTIxLDE5NTgzNjkyMzcsMTE4MjkzNTc5MywtMTgzMzkzNzk1OSwtMTIzODE1NTExMiwtNzg1MDUzNTQ5LC01ODA0MTM2MDcsLTIwMjkyNDExODksMjkyNjMyNTM4LC0xMzg3NDc5ODc4LC00MjY3Njc2MzQsMTE3ODAzNTY4MCwyMDUxOTgyMTcsMTk4NzA3MjQxLDQ4NDE0NTQ2NywtMTI3ODk4OTg1NCwtMjY5NTQ4ODU2LC00Mjk1NjU5MSwyMDc4MjI2ODI1LDE2NDkxMDc2NTksLTExNzgwNDczNjUsMjU5MjI1MDI2LDExNjA2MjMzNTksMTU3MjcyMzMyNCwtMTY1NzY5NTE3Nyw2OTU5MDQyMDQsLTMxMjcxMDAzNSwtMTI0OTQwOTc5LDU1NTcyNTU3Miw3MjMxMTQ1NDMsLTE2MjMzNzU5NjEsMTY2MTg0ODkyMywxOTIzMTE3NDc3LC04OTg5NjkyODYsMTkwNjE4ODkzLDM5MDUyNjU3MSwtMTYyNDU4NTg1OSwyMDQ2OTU2MzgzLC02OTUyNjU5MiwyMDU0NDcyNzEwLC0zMTkxNTQyNSwxODY0MjU4MTMxLDIwODcwNzA3MTUsLTEzMjMxODAxNiwzMDczNTAxMDIsLTEwMjA3NjEwNTksLTIwNjQzMzc0NywxMzIyOTk1Mjc1LDE1MzY5NjcyNjksMjEzNTQ1MTA5OCwtMTE1NzM4OTY4LDEyMjY1MDU0ODIsLTY0MjQ5MDIyOSwtMTEzODMwOTYxNCwtMTc4MDAxOTIyMywtNzQ2Mzg4MjI3LDExNzgwMzA4MjgsMTI2Mzc0OTg5MiwxMDkzMzIyMDI0LC0xNDYzOTkxMDAzLDE1MjM4NjAyNSwtODMwMDUxMDA1LC0xMDQwNDQ0NzA2LC0xOTk2Mzk5NjA1LDMwMzA4MzU0NCwyODI5NTk4NDAsLTEyOTA5MzE2OTksLTgzMDc5Nzc1NywtNjUwMTM4OTU3LC0xMTM2NTcwMDA1LDQ5MzE2MDA5LDg3OTMyNzQ3Myw2MTU4NjIzNjEsLTQxNDQyMjc5MywtMTY4NTkwOTc2OSwxOTQ0MjQwODk3LC0xMjYxNDI3MjIyLC0xNjQyNTgzNDc4LDYxODMwMjk0NywxNjQzMTE5ODUsNTUwOTYyMTQ1LC03OTQzNjkwMDQsNjk0ODA5MzQzLDExMDY1NTU4MTgsLTE3MDUzMTYzMjksMTc4OTk0MDA0MCwtNTIxNjI2MzMzLDE3MjY2OTI5NTksLTEwMzcyNzE2NTUsLTg1ODMyMzIxNSwtODk3MjA0NzQ5LC0xNjUzMDA0NTcsLTE1Mzc0Mjk4MzEsLTEzNTI3NDY5MTcsMjExNzE4NTQxLC00ODI0NTUwNzMsLTIwNDg2Mzg2MjQsLTM1Mzk5ODAsLTE4Mzg1MDMzMDksLTU3MzIwNjk0MSwzMDA0ODQyOTYsMTIzNTk3OTg3Niw3NzM2MDM1NTgsLTE5NzYxNDE1MTcsLTE5NjM2MjU1MDAsLTE5NDI0ODk4NjEsMTg0MzIwNTkxMSwtMTg3Njk4OTg3LC0xMTk1NTM2NDc0LC0xODE4NDM4NDk3LDIxMTA4MDk1MzcsMjA0OTg1ODMzOSwyMDI0MDM2NjUyLDEzNzgxMjkxMTEsNjMxNjQ3MTg2LC0xNjI1MjIwODAyLC03NTM2NzgwMjksOTUzMzg4NTcyLDMxNzQ2NzI1MywtNDE5NzU4MzI1LDg5NDExNjkxMCwtOTI3OTIyNzI0LDEyNDM5MjY2MjMsMTI0NTgzMzUxNSwzMzU2NjAxNjcsMTcxNjE2NTY1NiwtNzc5NDYyOTA0LC05MzQyMzY5NDksMTU5OTkyMjUzOSwxMjg5NzE2MTYyLC0yNzkzMDYzNDUsLTQyODM1NjI3NywyMzQ1NDI5MzIsLTE3MDU1MzY2MSwxMjAzNTI3NTM4LC04MTg2MjU3LC02OTcyMzkyNTQsODIyMjQ0MjAwLDEyMTIwNDI0NzksMTYzMTUyNzY4MSwxMDE0NjUzMzA3LC04NDgwOTY2NzgsMjY5NTI5MjQyLDE3NDIyOTQ3MDgsLTEzMjQxMDQwNzMsMzQ1MzQ2NTE1LC0yMDcwNDYzNTEwLDEwNTk3MDA2MzUsNDkxNDc1NjIwLDE3MzY4NjI4NDUsLTE3NDg5MDAzMywtMjA3NDc2ODczNywtMTg4Njc5ODM0LC0xODM1NTgwMDQ3LC0xNTg2NDE1NjkwLC01MjEwOTAwMTcsLTIwMjA5MjI3NywyODk3NDI1NDAsMTExNzkyNTk1Nyw0Nzg4NzM2NDcsLTE4MjE2MDI1MjYsLTQ2ODQzMDQ5NywxNjIxOTY1MzcwLC0yMDc2MzczNDk3LC0xNDQxNDQ3MDUsMjA3NjI0Njc0NywxMzIwOTE1OTY4LDExMTc3NjU0MzMsMTY4NzM0NjkzNiwxMDA2ODg5NDEsMTQ0MDk1MDk0MCwtMTQ2OTA1OTE0MywtOTAyODg5MzEsLTE3NDk5MDQyMjcsNzY4NDQ0NzM1LDE3OTg5ODY0Nyw1Nzk2NTUwMTksMTc3NzM3OTU5OCwyMTI4NjY0MTQxLC0zMDAwNTc0NzAsLTM1MDkyODc4OCw0ODk5NTgxNzYsMjg1MTI1NDk5LC0yMTQyNDA4NzIwLC01NzIzMzI3OTAsLTE2OTYwNTg4NywtNzAwNjYwODM0LDI3Njc5NDA3MiwtMTA3NDUxOTg2NSw3Mzk0MzU1OTIsMjAzNDA4Mzg4MiwtNDYyNjI4NTQsMTkyNzQzNzcwMSwyMDI4MzcwODMxLDg4NDMzODM4NCwtMTM3NzE5MzY5Niw1OTkxNDAwODAsLTIxMjQxMjY2MTQsMTY1MTI4ODQ2NSwtMTM2NjE5MzIyNSwtOTQ1NDI4NjI1LC0yMDc3MTYxMywxOTUzMTU1OTU0LC05NDI0NDE4NDUsLTY5OTg5MTMxNSwzMDg1MDQ5NSwxNzAxNjg4Mzg1LDEzOTkxODk5NTMsLTkyNTU0MDQ2NSwxNDc0OTA1NDM2LC0xNTE2NTEyODYsLTE4MDk4MzU5NCwtNjE0MTE5MjgsLTE3NDAzMTEyMzYsLTE3ODc2ODk2NDcsLTg1OTEwMzUzOSwyMDEwNzYwNTkwLC02OTMxOTAwNjQsLTM0NzE3MTc4NCwyMTk5ODkwMDAsLTIxMTcwNTE1MzgsMTA4NjIzNDMxNSwtMzY4NDY4MTI4LDEwOTgyOTUyMDcsLTE5ODgwOTY0MDcsMTI5MTI3MTI5MCwtNzkyNTU5NDA0LDEwOTEzMTIxMTcsMTI2MjI1NDU2LDQ2MzE3NTk0Myw2ODM3MzI5NTIsMTE1OTQ4NjYxNiw2NDQ0MDQzMywyMDA1Mjg5ODkwLDEzOTQ5Nzg0MTEsMjEzODQxMTgwOCwxMjcxNjUzMTQ2LC0xNTQxODE4NjU2LC0xNDc1Njc5MDcxLC0xMjU2Mjg3OTQ3LDEyNzQxNTkyNzksNjk2MzA3Njc2LC0xNTkwNjE5OTE4LC0zMDYwNjczNDAsLTE4MjE3MzExOTUsLTEwMDk3NjAzMDIsNDQ2NjY1MzgyLC0xOTQ1NTI3MDY0LDk2MTM1ODI1NSwtNjU2NjQ4OTE0LDExMzUwODEzMDcsMTY2MDI2MzU3NiwtMTk4MzY5MzEzNiwtNjQ2NjY4MDM2LDc1MTM3NDc3NiwtOTczMDY1MDIwLC0xMzUyNjM2NzgxLC04MzkwMTkwODMsLTEyMTQwNzQ2NTMsLTE1MzAwMjI0MTgsLTM1NzIxNDYyLDExNDM4NzQ3NDMsMTAwNDY1NDUxNiwxMjA2MjQ5ODkzLC0xNDA1NTk5Njk4LDE5MjI2OTYzOTksMTg3ODg3NDgzNCwtMTc4MTY2NjgzMCwxNDY1MTA4OTQzLDE3NTg2NTU2NDMsMTcwMTM4MDI1NywtNjQ4OTU1OTc0LDE0MDAwMzg5NzYsMTU2MTc1ODYsNDgwOTA1MjYwLC0yMTEzODA3NzMzLC0xMTcwNzE3OTU3LDE3OTM4ODQ1NjMsLTkzMjE2MTQwMywtNDQ4NDg3MjIyLDI3NzIzMTMzNCwtMTM2MjQ2MzE3MSwtMTczNTI0Nzk4MCwtMTYzNDcxOTIsLTI2NzYzNDIxMCwtMTg0MDM0NTc5OCw3NTI5NTM2MDUsLTIwMTI4MzAzMzcsMTgwMDgzNzQ3NywxMzgwOTMyMzQ3LC0xODE2ODgwMDYwLC0xODQyMTIyMDUyLDE4Mjg1MDM0MzksOTAwNDI3NjU1LC05ODg4MTYwNDAsLTM4NjM0MTM1NywxOTM4MzE3MDc4LC0xMDE5MjMzMDg1LDIxMDU3MzM1OCwtMTk5MzA3NzU1OSwtMTM4NDcyMjA5MywtMzYzNTQyMDQ2LC0xMTk0NzgzMDU0LC0yMTA3NjMxMDEsMTQ4MTM1MzMzMiwxOTEyNzIzNTU0LDE0NDQ3MjI0OTksLTE4NDc1OTQ3MTMsLTgyODk0MjA4LC0xNDY5OTQxNTc3LC01OTE3NzAzOSwtMTI3MDQ5OTIwNywxMzczNjA3OTAwLC0xNjYwNDA5OTc3LC00OTkwNjkyOSw5NDQwNTcwOCwxNDgwMTM0NDY4LDExNDY5MTUzNTEsMTA4OTIzNDc1MiwtMTQ1MjY1MjIwNCwtNzc4NTU5MDM4LC0xMDIwNDgzODkyLDIwOTg1ODY2NjcsLTY5Njc2OTYzMiwyMjkwODA4NDQsLTEyNzMzODU2MTYsMjEyNTAzODAyMCwxNDgyODk0MDU0LDcyMDA4MTQ2MCwtNTg0NDU5NjI4LDU5MTg0OTIsMTg0MTE0ODAzMywxODkyOTI3NzMsNTA5OTk2ODIxLC0xMzM3MzA1OTg2LDg1MDA0NDc4OSwyMTEzNzc3MTkyLC0yMTg5NDc2MTAsMzgyMzUzMjQ5LDUyNTYyNTE0OCw2MDg4MTQyMzAsLTg1NTcxODM0OSwtMTA4MjA3MDIwLC02MjUxMzQ0OTksMTAwOTAzOTM0NCwtMzQ5ODYyNDM5LDExNzYyMzUzMCwxMjQyNzA3NTIzLC03OTUyMzYwOTMsMTcxNzYxMTQ2NCwxMDc2NDA4MzM2LC02NDY3NzE2MTcsLTIwNzYzMTI3NSwtMTgzOTc0MTk1OCwtMTE2MjgyNjkzNywtOTY1ODAyMTUyLC0xOTI5OTQ1MjE3LDE4ODY2ODEwMjIsLTE3NDU0MTUzNTEsLTEyMzA2Mjc1NzMsLTE4MzUxNzI1MjAsLTQyMzk5Mjc1MiwxMzUxMzU2NjAxLC00OTk3NjI2MiwtMTE2MTEwODE2MCwtMTE4MTA1MzIxNCwtMjEzNTQ5MzQ4NywxNTA1NjM4MDMxLDY2MDAyNjc1MiwtMTA3NTE2MjQ2MywzNzQ1Nzk2NzcsLTE3ODgwMjQ2MDQsLTY3NzgzNjA0OSw1NTgxNDIzNDksMTEyMTkxODM1MSwtMTQzMDI0MjQzNSwxNTE4NTg3MzgwLDE0MDg0NDczNjksLTE2OTY4NTY3MTgsMjU3Mjg2OTg5LC0xODQxNjE2MTgxLDE4MDAyNDE3NzksLTE5ODAwNTU5MTUsNjM5ODIwOTE2LDIwMzkwOTkwNCwxNTUwMzIzOTYwLDE3NzMyMDY5ODksMTc1NjQ2OTQwLDg2Njk2OTMxMSwtMTYzMTgxNTQxOCwtMTcxNjU1NzQxMCwtMTEwNDM1MDgwMSwtMjQ0MTg5OTEsLTE2MjM5NzE4ODUsMTIzOTQ3ODI3Nyw2NTk0NzY4MzIsLTE0NzY4NTE2MjcsMTc2MjkxNzU2NSwtMTcxMzY4Njg3NywxMjA5OTU5NzE4LC0xNzkwMDE2ODc3LC02ODU5NzIzNTMsLTExNzE2MjExMzQsLTEzMzIzMTY3NDUsLTExOTA0OTM3NjYsLTE4OTE4MzA0MTMsLTk5MjI3MjgzOSwxMTQyNTMxNDQwLC0xNDgyOTEyMzE2LDE2ODQwODU2NDIsNDMxNzEzMTIxLC0xNDkxMjA3OTAxLC04MDg2MzgwMDEsMTQ1NjIzMjQ4OCwyMTEwOTU3MDM0LDEzNTg1NzY5NzQsMzE3NDYwNjQ3LC04MTc1OTE0MzksLTEyMTY0OTQ5MDIsLTIwMzg2OTczMjYsLTIxMzc1Njg2MywyMDYxMjQ4NjEsLTE5NDAyMjI0ODAsLTM3NDM1Mzk1MywtMjUyMjEyNzU4LDEwNTYyNTc3MjQsLTUxNDA3MTE0NSwtNjU0NDQyOTM1LDY3NjAyNTMwMywtMTA4MTY0ODM5Nyw5MTM4OTE0MzUsLTkwNDI4MDk2MiwtMTg3MDcwNTYwNiwxOTk0MzAwMzU4LC0xMTU1OTU1NTg2LC0xNTM3NTIzMjg2LC0xMzAwMzIxMzYzLC02NDk1NTkxMDksLTc2NTc2NzM1NSwxNTE2OTU4NjM5LDEzMjQwOTYzMDEsLTc5NDk3MzM2NSwxMDU1MDk4MjEzLC04NTI3NjkyOTEsLTEzMzgyOTA1NjUsLTE4Mjg2OTcwOTEsLTY3NzEzODYwMiwtOTEzMDQ3NTUyLDI0ODc4OTQ0NiwxNjY3NTc5MjYyLDExMzYzNjUxNjMsNTE5MDY3NzA1LDE5Nzc4NjE1MzIsLTE5MTAyODQ4MDAsMTk3NDc1MzEyNywtMTQ4NTkyOTc1NCwxOTU1NjM0OTEzLC02Nzk1OTY4NDMsMjMxNjg0Nzc4LC0xODM0MTk5MDgxLC0yMDAxNzM2NTM4LDE2NjE3MzM5NjUsMTAzMTIxMjY3Miw0OTI0MTMxMzksLTEzNjIxNzk4NTYsMTcxMTY5MzU1Nyw2NTk2MTUwMjksLTY2MjUzODQ0LDE2MzQwMDQ2MDcsLTM3NDQ5NzQ2NywxMzMyNTc3MjA1LDE2OTY3NjkzNDAsLTE4Njc1NzI1NDYsMTYyOTc4MDYwLDIxMDE3MDQ2NjUsLTk5ODIxOTUwMiwtNTAwNTM1Njc4LC05NzQ5MzQ2NDAsLTEzNDc1ODczNjEsNzc2NDg4NDgzLC0xODEzNTY2MzkyLDEyMTYxMTIwNjUsLTEwNzI5NzgzLDE0ODE1NTEwMTgsMTkwNzkxMzk1MSwtMTMxNDYxNjI4MSwxNDU5ODMwNjgzLC00MjU0NDk2NjIsNzA2MjA3NTM1LC03MjY2MTEyNzksMjQwMjk3MzUyLC0zNjQ3MjEzMiwtMzU3NDA3MjQ5LC0yNjQ0ODgyNTksLTg5OTcxNjIyNCwxMzkwMzY3MTYwLC0xNzI0NDg0NzcxLC0xMjE3MTE0NjkxLDg5NjI5NzY5MywtMTk5MTY4MDE5OSwtMTk4Njg2OTAxMiw4MDI1NjY4NzUsNTAyNjAxMzk4LDMzODk1MzY0MCwxOTcyNDM4OTAxLDE5MDY0MjUxNzEsLTI5MjQxNDE5MCw4NTU4NDYyMTcsLTI2OTg5ODkwLDE0NDUwMjY4MTAsLTQyMjY4OTY0OCw5MjgyODg5MDAsLTIxNDQ4MDM5MDcsLTcyMjU0MTM3MywtNzIyOTEwNzksLTE5NTcyNzc4NjUsLTEyOTczMjUxMzMsMTcyODc1ODQ4LC0xNTUxMzA3NzM3LC00NDA1NzU2NDcsLTMyNzAzOTU5OCwtMTI1MTU5Mzk0NiwtMzA1NTcwNzUwLC0xNzI0MzE5NzgzLDc1MTI3MDQ3NiwyMDA1ODAxODY2LDE5MTA2ODA5NDQsMTE5NjQzNTg1Nyw0NzU3NDQ1OCwtMjYzMDY3NTE3LDEzMzU1Mzk5MDAsODMwNzE2MzU4LDE1MTYxNzI2MjIsLTE2NzEzMTY0MjMsLTE5MDAyMDkzMDEsMjE4ODU4OTgxLC0xMTgwMTUwMjg3LC05MDg0Njc1NDcsNDY5Njk2NTM1LC0xNzU1NDU2MjI5LDEwMTk5ODAxNzgsLTExMjAzMjE4NTcsLTIxMzg0NjQ1NTcsLTE1OTg3NzE1MzQsMTgyOTc1NjIsLTIwODI1MjgwMTksLTE4NTk2OTIwMjEsMTEzNDAxMDU2Nyw5MDg1MDc5NjEsLTQ2ODI4ODAxOCwtNjg3OTc5ODk3LC0xNjk3MzA1NDQyLC0xMjg0ODMzNSwyMDk0MjQ2OTgxLC0xNTUwMDA5NDU2LC0xMTM3NjE1NjcyLDIyNjQwMDE3NSwtNzY5MTI3MDcwLDg4MjkyNDg0Miw3NTYwMjAyMiwxMjE3MTk3NDY0LC0xMjk5Nzc1Njk4LC0xNjY5NTEzNzk4LDIxMTgyOTAzMjEsLTEwMzk2NTM3NjEsLTE2OTczNTQwNTksLTI4MDQxOTgyNCwyMTExMzUyMTI5LC0xNDQzODE3MjksMzI4NTcxNjI2LDE0OTM4OTk1MDksLTE3MDY4MDczNjgsLTE4OTEzOTkxNzEsMTk3NTA3MjA5NCwtNTQxNzExNzE0LDU4MzQ5Mjc2Nyw5MjUxODIxNCwtMTI1ODk2MDEzNyw1NDk4MjczNjYsLTY5MDI1ODA2NywxOTgzMTIyMTkxLC0yMDcxNDU0MTk5LDY4Nzg4ODM0MSwxNzA3NDI3NTgxLC05MzU5Mjg5ODIsMTg1NjQ4MDA5Myw1NjU1Njg4NzEsMjAxMDQ5Mzk4MCwyMzkyMTIxMzcsLTIwNDg5MjA3NTUsMTk5NDMxMjIxLDYzMjIwMzc1OCwtMzM1NDQ4OTEzLC0xMzY3OTI3OTY4LC0xMDU4MTAzMzAxLC05OTQ5OTg5NiwyNDUxNDYyMywtMTY1ODI0MTI1NCwyMDY2NzIxNDg1LDE1NzMzMjczNzQsLTYzNzUwOTcxOSwtMjAzMzU5NTEyNiwtMjEzMjMzNjQ0Miw2OTEzMTMwNjgsLTE2MTA5MTYwNzksLTIxMzEyMjM4ODIsLTIxMjQ3MzUwMzMsLTI4NzA5NDU2LDEwMjMzNjk1ODQsMTk2NTgyOTQzOSwtNDQ1Mjc0MTUyLDIwODQ3OTcwNjcsMzE0ODI5NTMxLC0zMTE2MzQ2NzIsLTE1MjM4NTYwMjksMzk1MTkyMTQ4LC0yMDg0ODI3MTIsLTE0MDQwMTI0NjksMTc0MjU4NDkzOCwxNDYwNDg0NzUsLTE0NzEwMTM2ODcsMTkyNDA5MjUwOCwxNDc2MTYyOTQ5LDEwMjk0MjI2NywtMzg5OTk0MzAxLC02ODc4MDM0MjMsLTIyMzQ0OTk0LDExNjY5NTI5MjUsNjI0MzA0OTE0LDM0NDA5NzM3NSwtNzI5MDg1MDc4LC03NjA1MDc1ODQsNTc1NTgwNTcyLDg0NjAyMjY5NCw3Njg2Mzg4OSwxMzEzNDY4NTM2LC05MzA3MTYzNzAsLTcyOTUwMDkzOSw1MTk1MTUzOTYsNDUxNzMxMzE5LDkxMjc4OTcxMCw2OTY0MjAxNzUsMTMwOTYzMzIxNiw5NjMzMTYzNTEsLTIzMTU5NDIyNSwtMTA3MTI5MzQ5MiwxNTU0NTk5Nzk3LDEzODExMTg0MDIsLTkzNTY4MjM2MCwyOTc1MTIwMTMsLTgxOTM4NzI2NywtMTAwMjYzNTg5NCwzNDU1NzUwMjIsLTEyMjIyNjc1NjIsMTUyNjIxNzk1NSwtMTU2OTYzOTAzNCwyMDQyMzI5OTcwLC0xNjk1MjI3MzYwLDEyNzE4ODYyNiwxNDE2MTgxNzQsMTAxMzQ2MDM5NCw1NzUyMzA4OTQsLTIyODYwOTQ1MywtMjAwNTAzMDI1NiwtMjM5MzAwNDY5LC0xMzcxNjAxMTk4LDE2ODAxNjY5NzcsMzgwMDg0MzcwLDE4MDQ3NTgxOTUsLTE5MzA3Nzk2NTIsNDk1Njg4ODc4LC0yMDk2NTk2ODYsMTgzMDIyMDcxNywtMTU0Mjg0NzY5OCwxODAwNTY2ODU5LC03Mjg5MjQ4ODksLTIwMTU4MTU1NDEsMTA4NDczOTU3LC0xNTY4OTM0Nzg0LDE1NDAwNzE5OTcsLTE0OTg1MTU5MiwtMjM0MDc4NzgwLDIxNjI2MTA5NCwxODU0ODU2NjYzLDYzODIwMTk0LC0xNDM5NTkxNjg3LC02MjY2OTc4MTEsMTcyNzUzMjgxMiwtMjEyOTg4OTE4OSwtMTAzMTgyNTYzMiwtMTI2OTM0NDk5MSwtMTM3NjU1MDAxMiwtMjExNzM0NTU5NCwxMjU2NTI3MTg4LDg3NTE1MjI4NSwxMDIxMjE5MzgzLDEwOTE2NjY5MTAsLTE0ODI3MTkyOTksLTE0MzM0MTU0MTEsLTk2NjM4ODY2MCw3ODI0NzY2MjcsLTE2ODM2Nzk4MjUsLTE5OTk1MjI4OTAsLTUxODE1MzEwLDE1MjQ2NzM4MSwyMDI2MDI4OTU1LDE4MDEwOTYyODAsLTE2NzIyNTgzNzAsMjk4MTk0NjI1LDE5Mjk4NjE5NTEsMjE1NjcxNzIzLDQ5MTUzMDI4LC01OTU4Nzk4MDAsMTc2NzM5NTE4NCwtMjAzNTg1OTI4NCwtNDE4MDAxODU2LC0xNzQ3ODA5MzEsNzI4NTc4NzUyLC0yMDY5Njk1NTgwLDQyNzYxODkyNywyMDEyMTg2NjU2LC05MDA4NTAwNTIsLTc4ODM4MDk0NiwtMTMzNzgwODI2NCwxMTEzODU4MzUxLC0xMzUwMTI4MDU3LDQyNzcxMDA1NSwtNTQ4NDcxOTEsLTIwOTgyNTkyMTgsLTYxODYxNzkwLDI0Njc3NzM5MiwtMTA0OTkzNDgzMiwtMTIwODg4MjkzNSwxMDIwMDk0OTM0LC0yMzc3MTE5MDcsMjE0MTU5NDc2NywxMjE0MDAyMTQ2LC0yNjcxNzQ1NCwtMTUxNDg4OTc0OSwtMTM1NTMyNDA3OSwxMzg0MTk4NTg0LC00OTk0MTM3NzcsMzI0ODc0MTQ1LC0yMDcxMjU5MjM0LC0xODY0MTg1MzQ4LDgxNTc5MjQ5MywtMzk4NTAxNzcxLDMxNDE0NzA5MywtMTcyOTQwNjYwMiwtNDEzODU4MjcwLDkzMTAzMjMzLDE5ODAwOTQ3OTksLTk2MDIyNjc3MiwxMzU0NjkzODcsNjA2MzkwODQsMTQ1MjI4MDY5NywyNzYwMzE4MywxMDA2MjYwODE2LC04NzU3ODczNzcsLTE3MDA5ODQ2NDgsMTY0MzcxODc5OSwxOTM3OTE3NTg0LC0yMDE3NjA4MjE1LDE2NjU3ODc3MjIsMjkyNzU3MDY5LDgyNzMwNjI0MSwtMTEyNTM5Nzc0MSwtMzc3MjYxNDA0LC0xNzkyMDI1NDEsMTU5NzY3MDM2Myw5MTY0MTI5MSwyMDEzNTM3ODI1LC03NzIxMjkzNTQsMzIxMjAyNzcxLDE0NTI2ODU4ODMsLTYyNTI1MjQxNyw2NjQzMTIxOTIsLTExNzk3NDgzMTUsNjUyMTEyNDg1LDEyMDE3MDUxMzAsMTM4OTU5NTY3NSwxNjU3ODE3MDU1LC05ODU4MjM3MzIsMTIwOTc2OTc0OCwxNDE3MTUyMTM0LC0yMDczODc0NjM3LC00OTM1NzM5NjcsMTQ2MDIzODcyNiwtMTkzODA0NDg2NywtMTg5OTMwNzgzOSwtMTMxNjkxMjAzLDQ2MDA4Mzg2MiwtNTAwMjkyOTkxLDEzMjM0NDc1NzUsLTY1NTYwMTkwNSwyMDU0NDIxNTYxLDE4NzQ2NzY4MjMsLTExODEwNTA2MjgsLTU3NDY1MDExMywtNTU2MjEyNTI3LC00OTMyMzQxNjIsLTE5NDM0NTU5NDksNTI2MDUyNjYzLC0xNTAxMjg2MDA0LDEwMTUyNzk2ODcsNDE3ODI5NDI3LDE0ODcxMjQ2MTQsLTQxMzI5OTk2LC0xMTYxMjA5NjA1LDIwOTkxMTMxMTcsLTQ3ODE3NTY1MiwtNTg5NzMwNTkwLC0xODcxNzE3NzE1LC0xNDUzMTE5NjAyLDE5NDAwOTgzOTgsMTQ0OTIzNzk5LDI2OTU1MjYzOCw4NzM0MTUyNzgsMTIwODU0NjE2OSwtMTY0ODczMTY0OCwtMTI3ODc0MzEzOCwtMzYyMjMyNTM1LDg5NTQ1NzE2NCwtNjUxNDI4MzA5LDE0Mjc1MjI4MDgsLTk5OTEzODEzOCwtMTQxODIzODExNiwtMTk3ODcyMjU4OCw5ODA4ODA2NTMsLTgwMDI3ODI2Niw2Mzg0MTU0NjksMTc2NTc3NjI1MCwtMTM0NDA4MjgyNiwxMzM4NzUxNjQ3LC00NTI2OTU5MzIsMTM4Mjg2NzM4NCwtMTg2MDgxMDIzMCwxMzQwMTIzNTU0LC0xMjg0OTg5MzAxLC0xMDE5MTc0NDEsMTgzMDIwNDg5NSwtMTU5Mjc4OTk0NywxODM0NjYxNzkzLC01OTI1NDUxMTIsMzQ2NjE2MTcyLC0yMTM4MDM1NTcyLDIwODQ5Nzk3OTgsMjIyMDYxMzgxLDk0MDU4MDIxOSwtNDk1MDQxOTUxLDkzMzA4NDc5NywxOTQ3NTAwNzcxLDkxMjAzMDA5MSwtMTU1MjgxMzM2MiwtNzE1NzM1OTE3LC0xNDE2MzYzMzA1LDE0MDQ0MjgxMjUsMTg3NjcxNDUyMiwtNDE4MDY3OTE1LC0yMDExNDQ4OTkxLC0xMDcyNTA2MTk2LDE3Mjg1Mjg5MzIsMTM5ODU2NDU1OSwtNDI0NzQ1Nzc5LDEyMDEyNDA0ODYsLTYxODk2OTI4MSwtNTE0NjgxMDg0LDI3MTUxODQ5NCwtOTUzMjc0MDksLTE5MDc1NjA1NjEsMTI1ODczMzcxMiwtMTI1NzE0NDE0LC0xNjkyMzc4MTI3LDE4MTQwODI3MTEsLTg4NDQyNTkwNywtMjExNjQzNDAxMSwtMTg4NTQ0NzEyNSwtMjAxOTk3NDA0MCwxMDg4MTYyNTE1LDg4NjgwNzMzOSwyNTg4OTE3MzQsLTc5NDYwMTE3NCwxNzk2MzY2NTAyLC0xMzMxNzI1MjI0LDEwMTEwMzQwNCwxOTI0MTk5MjIzLDEzMzU5MTM2Miw3NDUyMjk0OTcsMzcwODk4MTIwLC0xNTkxMTg2NzIwLC0xMzE4OTYzMTM1LDE4MTg3MDUwNTgsMTUwNzE2MzU2MSwxNzQ1MTM4Njc5LC0xOTA3MTUwNTA2LC0yMTQ0Mzc4ODE4LC0xODQ2MzI0MDkzLC0xMDUyMDg2NTkxLDE4ODAwMjYzODQsLTU5MDE0MDI4LC0yODU3NTA0ODMsMTYxNDY5MTEzOCwtNjczOTUwNDA1LC0xODc1MzQ5OTk3LDY5MTQzNTk2MiwxMTcxNzk4OTE0LC0yMDgxNjYzMjAxLC0xMTM3NTM0NzA2LC0xMTQ4ODczOTc4LDEzMTkwMzI4MDYsMTk2NjU1MjcyNywtMTQ5MDE5MzU0MiwtMjcwNTE1NDgwLDEyMjk1ODE1OCwtMjEyNTczMzAzLDE2NTA0NTU5OTYsMTA3MDI4Mzc0MSwxNzk4ODQxMzk2LC02OTc3MzEwNjEsODg2NTI1OTI3LC00NTQwNzIwNDYsLTg4MDk3NjU5Myw0NTcxNTk1MSwyMzU4NDMyNDksNzI4OTUxOTc0LC0xMjM3NjM5NDQ5LDEwNDg5NjM3MzgsLTIwNjE5NzgzMjYsLTQxNTc2OTcyNywtNzc3NDE0ODY3LC0xMzUyNzkxODgsNDI1ODk0MDIwLC0yMDIyMjU5NTg2LDEyMjAwNjc5OTEsLTE2MDgyNTQ3MTYsMTc3OTM3ODQ1LC01MzMyODY5NjAsMTAzMDcyOTI4Myw4MDQxNDc4NDgsNTgxMzQwOTEzLC0xNjAxODIzNDE3LDM2NDU3ODg5OCw3Njg4MTg4MiwxOTA1MjYxNjg5LDExMzQ1NTU1MDIsMTUwMzk4OTU2NiwyNzY4NjE3MDcsNjY2MjA4NzA2LC0xMDU1ODY5MDgxLC0yMDEzNDUzODkzLDUwNjk3NDE1LDE0NjQ1NTU3MTYsMTYyOTI1NDY2LC0xMzk0NzczNjk5LC05Njg2NTE2OTQsMjE0MDM1MTk2MCwxNzQ0MzM5ODU2LDIyNTE4MDkwMCwtNTQ5NzU3NjIyLC0yMTE1MjUzOTUyLC01NDM5MzI3MjEsLTIxNTgzMTA5NSwyNDEzNjMzOTMsMTQ5MjIxMTA4MiwtODQ1MjEwOTQ0LC05NDcyODU5NiwxNjU2NTQxOTcsLTE1MTI2Mzg5MzQsLTcyNjA4MTQ3NiwzNTQwMjg2MDgsLTk2MzI4MTkzMywxMDYyNjI2MzUwLC0xMjUzNTcyNjQ3LDIxMDQ0MjMyMDQsLTc1MTYyMDE4OCwxNjY1NjgxOTM2LDEzMjE2OTAzOCwxODc0Mzc4NjUxLC0xNzIyNTc0NjM5LDIxMjY3MTk5ODIsLTEzNDc0MzY4MzEsMTg3MTMzMDYzLDE3MTczMTA1NDksLTE5NDUzODQwMTYsNzU2MzQ5NjcyLC0xMDA1NjI0MzgzLDE1ODQ0Mzg2NjMsMjEwMTAzNjc5NiwtOTI3OTcxMDA1LDU5Nzk2MTgwMSwxODQ3NDg2MzAxLDcxNjUxODc4MiwxMDcxNjgxMDg3LC02MDgyNzg3OTEsLTE3Nzg2MzcyNCwxMTMxMjM3MTUyLC00ODg3NzE3ODUsLTc0MDQ5ODY0MiwtNDc2MjA5NDMsMTgyNDQ0ODI0NCwtOTM0Mzg4MzA4LDIxNDM2NjI0ODEsLTExNTY0NDU0NzcsLTE2MDAyNzg2NzIsLTI5MzI2MjU5NSw2NzUyMzA0NTEsLTQxNjEzMjQyOCw2NzY1MTA4MjEsLTI4Mzc4MTk4NCwxMDAzNTY2ODE2LC0yMTEyNzA1ODA1LC04NDg3OTQwNTksLTE4NDU3MDA4OTEsMTM4MTE5ODk4NiwtMTUxMTI3NTI0OSwxMDE1Njg4MjQ3LDE3NTMzMjcxMzgsLTE0NTY5MjI3ODMsLTEzOTE4MTcwODksMTE4OTU3MjYzNywxMDY4Mzc0MTE2LDI2NDU5NzE4NywtMTMyOTU2ODU5MywtMTc0ODc4NzU4NSwtMTU0MDgzOTk4NiwtNzc0NDA0MDcwLC0xMDM5ODE5OTY1LDEzNDkzNzM2NTIsLTc3NTQ0MzQ3NSwtMzMxMzY1MzQ1LC0zMjYzNDU0NDcsLTE3NDQ2NDE1NSwtMTE4NzQ0NTEzNiwxMTQyMjQ1MTkxLDMwMjg0MzQyOSwxMjc4MTkyNDcsMTM5OTk4MzcxNiwtMTEzNDkwODAzMSwtMTk1MjUxNjM4NSw5NDM5MjgxNzYsNDg5MDYzNjMsMTYxMzQwMDYwMyw2ODMwNzYyMTgsLTI3OTg0MzE2OCw1NDM2Njc0NDcsMTIwMDI3ODQ2NSwtOTc1NjAzMDcyLC04MzQ5MjY0OTcsOTc1MjI2ODMxLDEyODczNzM3NTEsLTE3NzkzMDM1MzIsLTE0MTA4ODc4MjIsMTg1NDg3MjU0NywyMDQxNzAxMTIxLDE3MjMyMTUxNjYsLTk3MzgxMjExMCwtMTg3Mzc5MTg1OCwxMDcyNzQyMjEwLDIxMjMzMDE3OTAsMTQwODkwOTYzLDE1NTE4NjU0MjEsLTE3MjM2MjE5MCwxMDQ4NTQwODEzLC0yMDkyMjQ5NjgzLDE5OTUxODM1NTYsLTQ3NDM5OTUyMSwxNDU4NDUwODAwLDE5MjU1NDUxNTcsMjEyNzAyNTA5MCwtMTU3MTc0MzMzOCwtNDExMzkwMDU4LDE5NjQwODU4NTAsLTE3MTM1NDg5NDQsMTc1MTM0MDE5Niw5ODQxMzA3NDMsMjI3NzcwNjEyLDE2NjcwODAyNDcsLTE1NDc3MTMyMzMsNDg4NDk1NjU5LDEyOTAzNjYzMzUsNzQ3MDMwOTIzLDE2NjkxOTE2NzQsMjA2MTc4MTE3NiwtMjQ5MDYxMzcyLDU2NTE0Mzk5MCwtNTg3ODgzOTEzLDEwOTM2MTM5NzAsLTE5MDYxMjAzMTMsLTM0MjgxNjUyOSwtMTA4MDQ3MjY2MSwtMjEzMjkyODgwNywtNDkyMjIyMTMwLC05MTQ2MTEzNCwxMjk1MTM5MjEwLDE3ODYwOTA5ODIsMTE4MDg5OTI4Niw1ODQ4OTM3NjIsLTExNjMyOTM4MCw1OTI1OTE4NjQsLTE3MjY5NDE5NzgsODIxNTU0NjUzLC0yMTIwNDA0Njc4LDgyNTE2MjgzOCwxMjgwNTAwMTU5LC0yMzE0NDQ5MTAsLTExNzU2NTExOTUsLTEwNTcwMTcxMDgsNTEzOTM0NjQwLC0zMDgyNzQxMzUsLTIwNzMwMDYxNywtMTU2ODc2MzQ1MCw5MDU5NDA2MywxMDExOTI1NTI2LC0xMjQwMzk3NDk4LC01OTI5ODY0MDIsMTcwMTQzNjU4OSwxMDgwMDUwNDAxLC0xMzI1MTM4MzI2LDE4ODk2Mjk2MDgsMTU5MzIwNTk5MCwyMTMwODc2OTIyLDE1NTY2NTAyMywzNTk5NzgwNDgsLTE0MjU1MTEyODksLTIxMDI4Mzc5MjYsMTQ4NTQ4MTM1NywtNzg0OTMyODg1LC0xMzE2OTM3NDY2LC0xODI4NjMyODM4LC0yMDQ2OTI3Mzg4LDIwNTU5MDU1MTEsLTEzNDY1NzA0NjMsNDUxNjc2ODM4LDE3ODk5MDMwMDksLTEyNTEzNTk3ODQsLTEyODkzNzQ5OTgsLTM4MDM1ODUxNiw1NDUwMjY1NjYsLTE5MzY3MTcyOSwtMTMzMzc5MzI1NiwzNjQyMTYxOTAsLTQ1MTc0MzgwNSwxNTQxNDkwMzEzLDkzNDg1NTM5MywtNjI1NzM5NTc2LC0yMDczOTcxOTIwLDE1Mzg5OTc2NTIsNDE1NTQ2MDE0LC04MTgyMDQwNjYsLTExMDEwMzYxLC01MjQ5NDYyODAsNDkyMTE1MzM2LDEzNDQyNDEyMDUsLTY2MTExMTA5Niw1MjU5Nzk4NTgsLTM2OTc0MzYyNSwtMjAzMDczMzYwMiwxNzcxMzQ4MzAsLTMyNzA3ODgzOCw2NDA3MjQwNzksMTE5OTQ2Nzg1NSwxNzU5NjA4ODM1LDE5NTg3MDY2MDksLTE5Nzc4MTU4MTUsLTExNDM2MTAzNjEsNzcyNTk3MTk3LC05MzE2NjU5MDgsMTYxMDI5Mzg2OSwxNzg3NTg5MjI0LC0xNzkxMTEzNTg2LDMzMTExMjA3OCw3NTA4NzY2NDksOTU1NjY5NzE4LDE0MzkwOTE4NjAsLTE0NTU5NjI1MjcsLTE2MzI3Mjg0OTEsLTI0NTQ5ODczNiwtMjIxNTkwOTc2LC0xMzkwNjMwODM1LC0yMTU1MDc5OTYsMTIzMDY1NzQ5NywxMDIwNDI2MjIyLC0xMzc1NDA0ODg0LC0xOTkyMzIwOTY2LC03NTI1NDAzNDcsLTE4MjAwNjM4MTYsODQxMDEwMDEyLC03NzIyNTM1NjcsMjA3NTQ5ODA1MCwtMTIwNzk0NzAwNSwtMjc4OTU2NjUxLDIxMDAxNDk1NjIsLTcwNTY2ODA4NSwzNjA3NzQ0NzksLTY1Njc5NzUxNSwxODE4MTQ0NjMwLDEzMzcyMzIzMzAsLTExMjE0NzczNzAsLTE1MzMzOTUxMDEsMTE4NTY2NTMwNSwxMjQyMjc0ODQ0LDIwNTg3MjgwMDIsNzU4Nzk2MDEyLDE2NTAwODM4ODEsLTMyMTI5MzkzNywtNzUxMjMwMDEwLDExMzE0OTU4NTUsLTE4MjM3MzE5ODQsLTc3Nzg1NTkzMSw1NTY5ODgyNzksLTE5ODczMjk3ODksNTI2NDYxNDA3LC04OTUxMDM5NzMsLTI5MDU3MDgsLTEyNTc1MDcwNTQsLTI5NjEwMjMzNSwtOTE5MTA2MTkzLDU3MjU3MTIyNywxNDYyNTQ1NDA4LDIxMzEzMjEwOTksMTUwMjcwOTczNCw3OTM3NTc4NDEsLTU4NzE5MTQ1Miw5OTMzNDM2OTcsMTMxMDQ1OTYzMCwtMTE2ODk2NTM5OSwxMTI4MDA4NjI5LDExOTIzMzM2MjMsLTMwMTA0Mjc1OSwtNDgxNTk1Mzk0LC0yNDM0MTcyNzMsLTk3NDcyNjQ1NCwyMDMzMzg4NDIxLDIwNTM1NDQzNTgsMTc4MjIyODMwNyw4MDYzNTY5OTYsNjMzNDgxMjA4LDE2NjkwMjI4MjgsMTc1NDUwMTE2Niw0MTQ0MTI3MzEsMTc5NjQ4MjgyNiwtODYzMzg5MTg5LC0xNjU1NDE2OTQ1LDEyNDIyNTAzMTIsLTk1Mjk0NzI2NCwtMTkzNDM3MTM0MCwtNjcwNDQzNjA1LDEzNTMyMzE1MDUsLTEwODA4NTE1ODcsLTQwMDQ2NzU4MCwtODUwNjc1OTg4LC0yMTM5OTk1NjIzLC01MTEzNzIwMDAsNzEwMTc4OTIyLDEzMzIwMzI4MTMsLTUzMjIxNDY0LC02Mjg3MDc0NjUsLTM4MzcxMjIwNywtMTYyMzE2NjE1MywtMTA0OTIwODY5MCwtNTY1Mzc2NzI4LC0xODQ1NzQzMzMyLC0xMjc5Nzc0MDE1LC02ODUyMDQwMDIsLTE0MzcwNzYzNTIsODA1MjY3MjIzLDE0OTgyNzAyOTEsLTIzMzE0OTM0NCwxMjY0OTUwNzcyLDE1MjIyOTkyMjgsLTg0NTQ0MzQxNCw3NTI5MzU3MSwxNDMzNTMwMjI4LC0xMzUyOTgyMTkyLC0xNjI1MzMyMzE4LC0xODE0NTE4NDc3LC02NDg2NDYzNjksMTM3NjI0MjYyOSwxNjUxNjExOTQ4LC0xNTU4OTMxODMzLDE3NzE3ODM0MjIsMTEyNzUzMzY0MSw5Njk4MTM3NzIsOTA3NTA2MDg2LC0yMTQzMDg1NjE2LDU4Mzg1Mjg5OCwtODE4NDIxMzI4LDE2ODAyNDIzMzAsMjYxOTY3MzAxLDc3NjgwMjY3LDMzNTYyMzAzMSwxMjU2MzkxOTUwLDk0Mjg3NDk2NiwtMjEyNTUwODMxMSwxOTM1OTM2ODQ3LDE3NTMxMTAwODIsLTU1MjIyNjMwMywxOTkxNDcyMDMzLDE1NzM2Mjg4OTYsMTY0ODQ4NjY3MywxMDc0ODk2ODA1LDE5Njc4MDE5MDIsLTMyNjkwNjczNCwxOTUwMDIyNzY2LDU4MDE0NTA1NywtMTg0NjEyODI3OSwxMjY3MjQzMDIzLDE3NjE2MTA3MTIsNjkxODQ2MzI1LDE1ODQ0MTMxNTUsMTk2MDQ0MzMyMCwyOTk1OTEyOCw4OTM3NDQ5MzAsLTExNTQ0NTc2NTYsLTIxMzIxMTUyOSw5Mjg3NDA3MTAsMTU2Nzg2MzMyMSwtNzYxMDUyOTkyLC04Mzk2NTczNCwtNjA2MzQ3ODAsLTEzMDE2MTEwMzEsMTcxMjAyMTM3NSwtMjkxNzgyNDYwLC0xOTM0NjIwMDYwLDIwMDMxMjIxNTdd" + } + } \ No newline at end of file diff --git a/doc/.nojekyll b/doc/.nojekyll new file mode 100644 index 000000000..e69de29bb diff --git a/doc/ch01-Introduction.html b/doc/ch01-Introduction.html new file mode 100644 index 000000000..07eefb7fb --- /dev/null +++ b/doc/ch01-Introduction.html @@ -0,0 +1,378 @@ + + + + + + + + + +GCM-Docs - 1  Introduction + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    + +
    + + + + + +
    + +
    +
    +

    1  Introduction

    +
    + + + +
    + + + + +
    + + +
    + +
    +

    1.1 Who is this for?

    +

    The General Computation Model (GCM) is a Java based simulation framework for building disease progression models. Users of GCM should have a general familiarity with Java and object oriented programming and would benefit from some exposure to event based modeling.

    +
    +
    +

    1.2 High level overview

    +
    +

    1.2.1 Simulation

    +

    GCM is an event based simulation composed of data managers, actors, reports and an event engine. The data managers contain the state of the simulation and generate events when that state changes. The actors contain the business logic of your model and act on the data managers. The engine transports events generated by the data managers to any data managers and actors that subscribe to those events and manages the flow of time.

    +
    +
    +

    1.2.2 Plugins

    +

    Data managers, actors and reports are organized into plugins. A GCM model is thus composed of the core simulation and a suite of plugins. The plugin architecture provides for the scalable reuse of concepts and capabilities between models. GCM contains a set of existing plugins that define many concepts useful to a broad range of models such as the management of people, their properties, social group structures and the like. The modeler is free to compose a model from their choice of plugins.

    +
    +
    +

    1.2.3 Experiment

    +

    GCM also provides a multi-threaded experiment management system. Each plugin contains zero to many data objects that define the initial state of its actors and data managers. Each such data object may be altered freely. The complete set of all combinations (scenarios) of the variant plugin data objects form an experiment and a separate simulation instance is executed for each combination.

    + + +
    +
    + +
    + + +
    + + + + \ No newline at end of file diff --git a/doc/ch02-GettingStarted.html b/doc/ch02-GettingStarted.html new file mode 100644 index 000000000..b55382452 --- /dev/null +++ b/doc/ch02-GettingStarted.html @@ -0,0 +1,2627 @@ + + + + + + + + + +GCM-Docs - 2  Getting Started + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    + +
    + + + + + +
    + +
    +
    +

    2  Getting Started

    +
    + + + +
    + + + + +
    + + +
    + +
    + +
    +

    We start with a set of practical lessons that will help clarify the core concepts of GCM. The lessons generally build on one another and should be taken in order. You are encouraged to code along with the lessons. The full set of lessons are included in the main code repository as maven-based, standalone Java projects.

    +
    +

    2.1 Hello World Lesson (Lesson 1)

    +

    Our first lesson is a very reduced “Hello World” example where we will execute the simulation with one line of code.

    +
    +
    +

    Code Block 2.1: Building and executing an empty simulation.

    +
    package gov.hhs.aspr.ms.gcm.lessons;
    +
    +import gov.hhs.aspr.ms.gcm.nucleus.Simulation;
    +
    +public final class Example_1 {
    +
    +    public static void main(String[] args) {
    +        Simulation.builder().build().execute();
    +    }
    +
    +
    +

    With this one line we have created and executed a simulation.  Since the simulation had no actors or data managers there was nothing to do and so it terminated immediately. Let’s analyze the line in a more drawn out form:

    +
    +
    +

    Code Block 2.2: Building and executing an empty simulation broken out into discrete commands.

    +
    Simulation.Builder builder = Simulation.builder();
    +Simulation simulation = builder.build();
    +simulation.execute();
    +
    +
    +

    The simulation does not have a constructor.  Instead it uses a static builder class that creates the simulation from various arguments.  The builder is immediately capable of building a simulation instance so we will skip giving it any more information.  The simulation is only capable of executing, so we execute it.

    +
    +
    +

    2.2 Plugins Lesson (Lesson 2)

    +

    Models are made of plugins. In this lesson(Example2) we will add a single plugin to the simulation and execute it.

    +
    +
    +

    Code Block 2.3: A simple plugin added to the simulation. Plugins act as modules for all components contributed to the simulation.

    +
    public final class Example_2 {
    +
    +    private Example_2() {
    +    }
    +
    +    public static void main(String[] args) {
    +
    +        PluginId pluginId = new SimplePluginId("example plugin");
    +
    +        Plugin plugin = Plugin.builder()//
    +                .setPluginId(pluginId)//
    +                .build();
    +
    +        Simulation.builder()//
    +                .addPlugin(plugin)//
    +                .build()//
    +                .execute();
    +    }
    +
    +}
    +
    +
    +

    The first thing we will need to do to build a plugin is to identify it. The PluginId is a marker interface – it has no methods and serves to help differentiate between plugin id values and other identifiers. The SimplePluginId is a convenience implementor of PluginId and will wrap any object as an identifier. In this case we use the string “example plugin”, but you are free to implement them however best fits your needs.

    +

    Next we build the plugin. The Plugin class implements all plugins and you can provide several arguments to its builder to specify the contents and behavior of your plugin. A plugin is composed of four items:

    +
      +
    1. An id
    2. +
    3. Dependencies on other plugins
    4. +
    5. Data objects used to initialize data managers, actors and reports
    6. +
    7. An initializer to load the data into the simulation
    8. +
    +

    For now, we will only need to add the plugin id and build the plugin.

    +

    Finally, we build the simulation by adding the plugin and then executing as usual. The result is the same as the previous lesson: nothing happens. However, internally, the simulation did add the plugin and found it had no information other than its id.

    +
    +
    +

    2.3 Actors Lesson (Lesson 3)

    +
    +

    Contexts

    +

    In all that follows(Example3), we will encounter various context objects. Contexts are interfaces into the simulation that are tailored to the thing using the context. For example, an ActorContext provides everything that an actor will need to interact with the simulation. Similarly, a DataManager context provides the capabilities needed by data managers.

    +

    The first context we encounter is the PluginContext. It provides the plugin with the following abilities:

    +
      +
    1. Add an actor to the simulation
    2. +
    3. Add a data manager to the simulation
    4. +
    5. Add a report to the simulation
    6. +
    7. Get plugin data
    8. +
    +

    The PluginContext is passed to the plugin’s initializer and is used to add all data managers, all initial data and any actors or reports that need to exist at the beginning of the simulation run.

    +

    The next context will be the ActorContext. It provides actors with a wide array of capabilities that we demonstrate later. For now, the important takeaway is that being granted a context implicitly identifies the recipient as having a particular role in the simulation.

    +
    +
    +

    Code Block 2.4: An initializer uses a plugin context to execute the initialization logic at the beginning of each simulation.

    +
    PluginId pluginId = new SimplePluginId("example plugin");
    +
    +Plugin plugin = Plugin.builder()//
    +        .setPluginId(pluginId)//
    +        .setInitializer(Example_3::pluginInit)//
    +        .build();
    +
    +Simulation.builder()//
    +        .addPlugin(plugin)//
    +        .build()//
    +        .execute();
    +
    +
    +

    We are setting the plugin’s initializer. The initializer is a method that consumes a PluginContext and returns void. For this example, we use a static local method for our initializer:

    +
    +
    +

    Code Block 2.5: A single actor is being added to the simulation at initialization.

    +
    public static void pluginInit(PluginContext pluginContext) {
    +    System.out.println("plugin being initialized -- we will add one actor");
    +    pluginContext.addActor(Example_3::actorInit);
    +}
    +
    +
    +

    When the simulation starts up its execution, one of the first things it will do is to execute each plugin’s initializer to give the plugin an opportunity to add actors, reports and data managers to the simulation before time and events begin to flow. Adding an actor is done with another consumer, but this time it is a consumer of ActorContext.

    +
    +
    +

    Code Block 2.6: The actor prints out some identifying information when it initializes.

    +
    public static void actorInit(ActorContext actorContext) {
    +    System.out.println("actor being initialized");
    +    System.out.println("my id = " + actorContext.getActorId());
    +    System.out.println("time = " + actorContext.getTime());
    +}
    +
    +
    +

    After the plugins are initialized, the actors and data managers are next. For this example, the actor is initialized and it prints a few statements and ceases activity. Here is the resulting console output:

    +
    +
    +
    +

    Figure 2.1: Output from the single actor as it initializes.

    + + + + + + + + +
    +plugin being initialized – we will add one actor
    actor being initialized
    my id = ActorId [id=0]
    time = 0.0 +
    +
    +
    +
    +

    We can replace the local method references above with lamdas to be more succinct.

    +
    +
    +

    Code Block 2.7: A single actor writes output to the console during its initialization.

    +
    PluginId pluginId = new SimplePluginId("example plugin");
    +
    +Plugin plugin = Plugin.builder()//
    +        .setPluginId(pluginId)//
    +        .setInitializer(pluginContext -> {
    +            System.out.println("plugin being initialized -- we will add one actor");
    +            pluginContext.addActor(actorContext -> {
    +                System.out.println("actor being initialized");
    +                System.out.println("my id = " + actorContext.getActorId());
    +                System.out.println("time = " + actorContext.getTime());
    +            });
    +        })//
    +        .build();
    +
    +Simulation.builder()//
    +        .addPlugin(plugin)//
    +        .build()//
    +        .execute();
    +
    +
    +
    +
    +
    +

    2.4 Data Managers Lesson (Lesson 4)

    +

    We extend the previous lesson by slightly altering the actor and adding a data manager. But first let’s list some of the attributes of data managers, actors and reports to better understand the roles they play in the simulation. Reports are presented in detail in a later chapter.

    +
      +
    • Data Managers +
        +
      • Exist for the full duration of the simulation
      • +
      • Contain and maintain the entire state of the world.
      • +
      • Are highly stateful
      • +
      • Produce events in reaction to state changes
      • +
      • Interact with other data managers via events
      • +
      • Do not have a set of objectives. They are not trying to achieve some particular state of the world
      • +
      • Are narrowly focused on some particular aspect of the world, but are concerned with all instances of that aspect
      • +
      • Are added as instances and are limited to a single instance per class type
      • +
    • +
    • Actors +
        +
      • May be added and removed over time
      • +
      • Are not considered to be part of the world
      • +
      • Are generally stateless
      • +
      • React to but do not produce events
      • +
      • May access any data manager
      • +
      • Have objectives. They contain the business logic of the model and are trying to achieve some particular state of the world
      • +
      • Are concerned with many aspects of the world, but often focused on a particular subset of world
      • +
      • Are added as consumers of ActorContext and may be composed of any such consumers
      • +
    • +
    + +
      +
    • Reports

      +
        +
      • Exist for the full duration of the simulation
      • +
      • Are not considered to be part of the world
      • +
      • React to but do not produce events
      • +
      • May access any data manager
      • +
      • Do not have a set of objectives
      • +
      • Cannot mutate data and have no effect on the outcome of the simulation
      • +
    • +
    +
    +
    +

    Code Block 2.8: A data manager is added to the simulation.

    +
    public static void main(String[] args) {
    +
    +    PluginId pluginId = new SimplePluginId("example plugin");
    +
    +    Plugin plugin = Plugin.builder()//
    +            .setPluginId(pluginId)//
    +            .setInitializer(pluginContext -> {
    +                pluginContext.addActor(new ExampleActor()::init);
    +                pluginContext.addDataManager(new ExampleDataManager());
    +            })//
    +            .build();
    +
    +    Simulation.builder()//
    +            .addPlugin(plugin)//
    +            .build()//
    +            .execute();
    +}
    +
    +
    +

    We add an instance of ExampleDataManager to the simulation. Unlike the actor, where we pass a consumer of context, we need to provide an actual instance of a data manager. Note that the ExampleDataManager extends the base class DataManager. The base class provides only the init() method to override and you must include the super.init(dataManagerContext) call as its first line. This is done to ensure that each data manager is initialized exactly once by the simulation.

    +

    The ExampleDataManager has two (completely arbitrary) data fields alpha and beta and provides both getters and setters for each.

    +
    +
    +

    Code Block 2.9: The example data manager manages the state of two properties and prints to the console when changes are made.

    +
    public final class ExampleDataManager extends DataManager {
    +
    +    private int alpha = 7;
    +
    +    private double beta = 1.2345;
    +
    +    private DataManagerContext dataManagerContext;
    +
    +    @Override
    +    public void init(DataManagerContext dataManagerContext) {
    +        super.init(dataManagerContext);
    +        this.dataManagerContext = dataManagerContext;
    +        System.out.println("ExampleDataManager is initialized");
    +    }
    +
    +    public int getAlpha() {
    +        return alpha;
    +    }
    +
    +    public void setAlpha(int alpha) {
    +        this.alpha = alpha;
    +        System.out.println("ExampleDataManager sets alpha = " + alpha + " at time = " + dataManagerContext.getTime());
    +    }
    +
    +    public double getBeta() {
    +        return beta;
    +    }
    +
    +    public void setBeta(double beta) {
    +        this.beta = beta;
    +        System.out.println("ExampleDataManager sets beta = " + beta + " at time = " + dataManagerContext.getTime());
    +    }
    +
    +}
    +
    +
    +

    The actor is now specified via the ExampleActor class, Code Block 2.10. Most actors contain enough code that we usually put that code into a separate class rather than a lambda statement as we did in the previous lesson. Note that the init() method has the correct method signature of being a consumer of ActorContext.

    +
    +

    Plans

    +

    In GCM, an actor can do three things:

    +
      +
    1. Observe: Observation can be done directly by gaining access to a data manager and then getting a value from that data manager. Observation can be done indirectly by subscribing to events. We will cover that option later.
    2. +
    3. Act: A mutation to some data manager’s managed data.
    4. +
    5. Plan: At some time in the future, the actor will take some particular action
    6. +
    +

    Actions in GCM are always executed in the current moment in the simulation. Unlike many future event simulations where events are queued for future execution, GCM allows an actor to plan for an action or observation in the future. The plan is a consumer of ActorContext and can be a static method, member method or a lambda. The plan is registered with the simulation and is executed only when time has moved forward to the plan’s scheduled time. There is no requirement that the plan do anything at all. This allows the flexibility to re-evaluate the circumstances of the planned action and choose to take appropriate action at that time. Plans are queued in GCM by their associated planning times and it is this queue that dictates the flow of time. For example, suppose the simulation finds the first plan is scheduled for time= 2.4 days. The current time = 0 days and the simulation progresses time to 2.4 days and then invokes the plan. Plans are always privately managed by the actor that owns the plan and no other actor or data manager has any insight into those plans.

    +

    In this example, the actor is initialized at time= 0 and generates 10 plans to increment the value of the alpha in the ExampleManager. Each time the ExampleManager changes the value of alpha, it outputs to the console a description of the change.

    +
    +
    +

    Code Block 2.10: The example actor initializes by making plans to update the alpha property on a daily basis.

    +
    public final class ExampleActor {
    +
    +    public void init(ActorContext actorContext) {
    +        System.out.println("Example Actor is initialized and will plan to set Alpha");
    +
    +        ExampleDataManager exampleDataManager = actorContext.getDataManager(ExampleDataManager.class);
    +
    +        for (double planTime = 0; planTime < 10; planTime++) {
    +            actorContext.addPlan((context) -> {
    +                int alpha = exampleDataManager.getAlpha();
    +                alpha++;
    +                exampleDataManager.setAlpha(alpha);
    +            }, planTime);
    +        }
    +    }
    +}
    +
    +
    +

    The output from the simulation is:

    +
    +
    +
    +

    Figure 2.2: Output from the example actor and example data manager showing the alpha property initialization and subsequent evolution.

    + + + + + + + + +
    +ExampleDataManager is initialized
    Example Actor is initialized and will plan to set Alpha
    ExampleDataManager sets alpha = 8 at time = 0.0
    ExampleDataManager sets alpha = 9 at time = 1.0
    ExampleDataManager sets alpha = 10 at time = 2.0
    ExampleDataManager sets alpha = 11 at time = 3.0
    ExampleDataManager sets alpha = 12 at time = 4.0
    ExampleDataManager sets alpha = 13 at time = 5.0
    ExampleDataManager sets alpha = 14 at time = 6.0
    ExampleDataManager sets alpha = 15 at time = 7.0
    ExampleDataManager sets alpha = 16 at time = 8.0
    ExampleDataManager sets alpha = 17 at time = 9.0 +
    +
    +
    +
    +
    +
    +
    +

    2.5 Events Lesson (Lesson 5)

    +

    An event in GCM is a notification of a data change to the state of a data manager. In this example we will introduce two events corresponding to the two changes to the ExampleDataManager. Both events document the previous value and current value (at the time when the event was generated) and are immutable data classes.

    +
    +
    +

    Code Block 2.11: An event to notify that the alpha property has been updated.

    +
    public final class AlphaChangeEvent implements Event {
    +
    +    private final int previousAlpha;
    +
    +    private final int currentAlpha;
    +
    +    public AlphaChangeEvent(int previousAlpha, int currentAlpha) {
    +        super();
    +        this.previousAlpha = previousAlpha;
    +        this.currentAlpha = currentAlpha;
    +    }
    +
    +    public int getPreviousAlpha() {
    +        return previousAlpha;
    +    }
    +
    +    public int getCurrentAlpha() {
    +        return currentAlpha;
    +    }
    +
    +    @Override
    +    public String toString() {
    +        StringBuilder builder = new StringBuilder();
    +        builder.append("AlphaChangeEvent [previousAlpha=");
    +        builder.append(previousAlpha);
    +        builder.append(", currentAlpha=");
    +        builder.append(currentAlpha);
    +        builder.append("]");
    +        return builder.toString();
    +    }
    +
    +}
    +
    +
    +
    +
    +

    Code Block 2.12: An event to notify that the beta property has been updated.

    +
    public final class BetaChangeEvent implements Event {
    +
    +    private final double previousBeta;
    +
    +    private final double currentBeta;
    +
    +    public BetaChangeEvent(double previousBeta, double currentBeta) {
    +        super();
    +        this.previousBeta = previousBeta;
    +        this.currentBeta = currentBeta;
    +    }
    +
    +    public double getPreviousBeta() {
    +        return previousBeta;
    +    }
    +
    +    public double getCurrentBeta() {
    +        return currentBeta;
    +    }
    +
    +    @Override
    +    public String toString() {
    +        StringBuilder builder = new StringBuilder();
    +        builder.append("BetaChangeEvent [previousBeta=");
    +        builder.append(previousBeta);
    +        builder.append(", currentBeta=");
    +        builder.append(currentBeta);
    +        builder.append("]");
    +        return builder.toString();
    +    }
    +
    +}
    +
    +
    +

    Each is generated by the ExampleDataManager, Code Block 2.13, when the alpha or beta values are mutated by releasing the events through the DataManagerContext to the simulation:

    +
    +
    +

    Code Block 2.13: The alpha and beta updates are managed via private mutation events.

    +
    private static record AlphaChangeMutationEvent(int alpha) implements Event {
    +}
    +
    +public void setAlpha(int alpha) {
    +    dataManagerContext.releaseMutationEvent(new AlphaChangeMutationEvent(alpha));
    +}
    +
    +private void handleAlphaChangeMutationEvent(DataManagerContext dataManagerContext,
    +        AlphaChangeMutationEvent alphaChangeMutationEvent) {
    +    int alpha = alphaChangeMutationEvent.alpha();
    +    int previousValue = this.alpha;
    +    this.alpha = alpha;
    +    dataManagerContext.releaseObservationEvent(new AlphaChangeEvent(previousValue, this.alpha));
    +}
    +
    +private static record BetaChangeMutationEvent(double beta) implements Event {
    +}
    +
    +public void setBeta(double beta) {
    +    dataManagerContext.releaseMutationEvent(new BetaChangeMutationEvent(beta));
    +}
    +
    +private void handleBetaChangeMutationEvent(DataManagerContext dataManagerContext,
    +        BetaChangeMutationEvent betaChangeMutationEvent) {
    +    double beta = betaChangeMutationEvent.beta();
    +    double previousValue = this.beta;
    +    this.beta = beta;
    +    dataManagerContext.releaseObservationEvent(new BetaChangeEvent(previousValue, this.beta));
    +}
    +
    +
    +

    There are three actors in this example:

    +
      +
    1. Actor1 makes changes to both the alpha and beta values at 1 and 3.5 day intervals respectively.

    2. +
    3. Actor2 subscribes to AlphaChangeEvent events and reports to console what it receives.

    4. +
    5. Actor3 does the same for BetaChangeEvent events

    6. +
    +
    +
    +

    Code Block 2.14: Actor 1 schedules updates to both the alpha and beta properties.

    +
    public final class Actor1 {
    +
    +    public void init(ActorContext actorContext) {
    +        ExampleDataManager exampleDataManager = actorContext.getDataManager(ExampleDataManager.class);
    +
    +        for (double planTime = 1; planTime <= 10; planTime++) {
    +            actorContext.addPlan((context) -> {
    +                int alpha = exampleDataManager.getAlpha();
    +                alpha++;
    +                exampleDataManager.setAlpha(alpha);
    +            }, planTime);
    +        }
    +
    +        for (int i = 1; i <= 5; i++) {
    +            double planTime = i * 3.5;
    +            actorContext.addPlan((context) -> {
    +                double beta = exampleDataManager.getBeta();
    +                beta *= 2;
    +                exampleDataManager.setBeta(beta);
    +            }, planTime);
    +        }
    +
    +    }
    +}
    +
    +
    +
    +
    +

    Code Block 2.15: Actor 2 reacts to changes in the alpha property.

    +
    public final class Actor2 {
    +    public void init(ActorContext actorContext) {
    +
    +        EventFilter<AlphaChangeEvent> eventFilter = EventFilter.builder(AlphaChangeEvent.class).build();
    +
    +        actorContext.subscribe(eventFilter, (context, event) -> {
    +            System.out.println("Actor2 observes event " + event + " at time = " + context.getTime());
    +        });
    +    }
    +}
    +
    +
    +
    +
    +

    Code Block 2.16: Actor 3 reacts to changes in the beta property.

    +
    public final class Actor3 {
    +    public void init(ActorContext actorContext) {
    +        EventFilter<BetaChangeEvent> eventFilter = EventFilter.builder(BetaChangeEvent.class).build();
    +        actorContext.subscribe(eventFilter, (context, event) -> {
    +            System.out.println("Actor3 observes event " + event + " at time = " + context.getTime());
    +        });
    +    }
    +}
    +
    +
    +

    The resulting console output shows Actor2 and Actor3 observing the expected events at the expected times:

    +
    +
    +
    +

    Figure 2.3: Output from Actors 2 and 3 as they observe changes to the alpha and beta properties.

    + + + + + + + + +
    +Actor2 observes event AlphaChangeEvent [previousAlpha=7, currentAlpha=8] at time = 1.0
    Actor2 observes event AlphaChangeEvent [previousAlpha=8, currentAlpha=9] at time = 2.0
    Actor2 observes event AlphaChangeEvent [previousAlpha=9, currentAlpha=10] at time = 3.0
    Actor3 observes event BetaChangeEvent [previousBeta=1.2345, currentBeta=2.469] at time = 3.5
    Actor2 observes event AlphaChangeEvent [previousAlpha=10, currentAlpha=11] at time = 4.0
    Actor2 observes event AlphaChangeEvent [previousAlpha=11, currentAlpha=12] at time = 5.0
    Actor2 observes event AlphaChangeEvent [previousAlpha=12, currentAlpha=13] at time = 6.0
    Actor2 observes event AlphaChangeEvent [previousAlpha=13, currentAlpha=14] at time = 7.0
    Actor3 observes event BetaChangeEvent [previousBeta=2.469, currentBeta=4.938] at time = 7.0
    Actor2 observes event AlphaChangeEvent [previousAlpha=14, currentAlpha=15] at time = 8.0
    Actor2 observes event AlphaChangeEvent [previousAlpha=15, currentAlpha=16] at time = 9.0
    Actor2 observes event AlphaChangeEvent [previousAlpha=16, currentAlpha=17] at time = 10.0
    Actor3 observes event BetaChangeEvent [previousBeta=4.938, currentBeta=9.876] at time = 10.5
    Actor3 observes event BetaChangeEvent [previousBeta=9.876, currentBeta=19.752] at time = 14.0
    Actor3 observes event BetaChangeEvent [previousBeta=19.752, currentBeta=39.504] at time = 17.5 +
    +
    +
    +
    +
    +

    Event Filters

    +

    Subscription to events for data managers and actors differ a bit. Data managers subscribe directly to the event type since they are generally interested in all events of some given type. Actors are often more selective and would like a predicate (in Java, the predicate is a function that returns a Boolean) to return true before they handle an event. For example, an actor wants to subscribe for person property change events, but is only interested in those events indicate a change to a particular person property. Since there will likely be dozens of person properties, the actor would get stimulated many times over, only to ignore the event most of the time. Unfortunately, a simple predicate added during the subscription process will not suffice since that predicate would have to be executed for each event and we will have gained little efficiency. Instead, GCM uses the EventFilter class that is essentially a predicate grouping mechanism that allows the subscription engine to group subscribers into a tree structure so that a single predicate execution might suffice to allow an event to be passed to multiple subscribers.

    +

    The event filter is logically composed of functions and target values as pairs. Each function takes in an event and releases a value. If that value is equal to the target value, then the event passes that function. An event passes the event filter if it passes all the functions that compose the filter. The construction of the builder for event filters requires the event class reference. The addition of function-value pairs requires that the functions take in only events of the given class reference, but may return any non-null object value. The simple examples given so far have only specified the event class and thus every event of that type will pass the event filter.

    +

    The functions that compose the event filter are often non-meaningfully comparable. For example, two functions that return the same values for every event may be separate instances of lambda code that are logically equal, but are not equal from the point of view of Java. To get around this, each function is associated with an id value and the id and function pair are called an IdentifiableFunction. Two such functions will be equal if and only if their ids are equal without regard to what their functions actually do. Thus is it very important that the mapping of id to actual logical function be stable and the usual best practice is to manage that mapping in a curated manner via the data manager that is associated with the plugin that defines the event. As we examine plugins that define events, we will encounter event filters that are managed by data managers and we will generally not generate event filters directly in the actor code.

    +
    +
    +
    +

    2.6 Plugin Dependencies Lesson (Lesson 6)

    +

    So far we have covered what actors and data managers do and that they are introduced into the simulation via plugins. Over the next lessons we take a closer look at the plugins. This lesson starts with creating a more realistic set of plugins arranged into separate java packages.

    +
      +
    • People plugin +
        +
      • Defines a person id
      • +
      • Adds the PersonDataManager for tracking people
      • +
      • Adds events for the the addition and removal of people
      • +
    • +
    • Family Plugin +
        +
      • Defines a family id
      • +
      • Adds the FamilyDataManager for grouping people into families
      • +
    • +
    • Vaccine Plugin +
        +
      • Adds the VaccineDataManager for tracking which people have been vaccinated
      • +
    • +
    • Model Plugin +
        +
      • Contains the ModelActor class to add people organized into family structures and vaccinate some of those people
      • +
    • +
    +

    Here are the classes that implement this example:

    +
    +

    People Plugin:

    +

    The people plugin defines a PersonId as a simple, immutable wrapper to an int value. The PersonDataManager tracks people via PersonId values and allows for the addition and removal of people. PersonId values are generated in order and never reused. Events are generated when people are added or removed.

    +
    +
    +

    Code Block 2.17: The PersonId class defines people and wraps an int value.

    +
    public final class PersonId implements Comparable<PersonId> {
    +
    +    private final int id;
    +
    +    public PersonId(int id) {
    +        this.id = id;
    +    }
    +
    +    public int getValue() {
    +        return id;
    +    }
    +
    +    @Override
    +    public int compareTo(PersonId personId) {
    +        return Integer.compare(id, personId.id);
    +    }
    +
    +    @Override
    +    public int hashCode() {
    +        return id;
    +    }
    +
    +    @Override
    +    public boolean equals(Object obj) {
    +        if (this == obj) {
    +            return true;
    +        }
    +        if (!(obj instanceof PersonId)) {
    +            return false;
    +        }
    +        PersonId other = (PersonId) obj;
    +        if (id != other.id) {
    +            return false;
    +        }
    +        return true;
    +    }
    +
    +    @Override
    +    public String toString() {
    +        return Integer.toString(id);
    +    }
    +}
    +
    +
    +
    +
    +

    Code Block 2.18: The PersonDataManager manages people by adding people, removing people and releasing person removal events.

    +
    public final class PersonDataManager extends DataManager {
    +
    +    private int masterPersonId;
    +
    +    private Set<PersonId> people = new LinkedHashSet<>();
    +
    +    private DataManagerContext dataManagerContext;
    +
    +    @Override
    +    public void init(DataManagerContext dataManagerContext) {
    +        super.init(dataManagerContext);
    +        this.dataManagerContext = dataManagerContext;
    +        dataManagerContext.subscribe(PersonRemovalMutationEvent.class, this::handlePersonRemovalMutationEvent);
    +    }
    +
    +    public PersonId addPerson() {
    +        PersonId personId = new PersonId(masterPersonId++);
    +        people.add(personId);
    +        return personId;
    +    }
    +
    +    public boolean personExists(PersonId personId) {
    +        return people.contains(personId);
    +    }
    +
    +    public Set<PersonId> getPeople() {
    +        return new LinkedHashSet<>(people);
    +    }
    +
    +    private static record PersonRemovalMutationEvent(PersonId personId) implements Event {
    +    }
    +
    +    public void removePerson(PersonId personId) {
    +        dataManagerContext.releaseMutationEvent(new PersonRemovalMutationEvent(personId));
    +    }
    +
    +    private void handlePersonRemovalMutationEvent(DataManagerContext dataManagerContext,
    +            PersonRemovalMutationEvent personRemovalMutationEvent) {
    +        PersonId personId = personRemovalMutationEvent.personId();
    +        if (!personExists(personId)) {
    +            throw new RuntimeException("person " + personId + " does not exist");
    +        }
    +        people.remove(personId);
    +        dataManagerContext.releaseObservationEvent(new PersonRemovalEvent(personId));
    +    }
    +}
    +
    +
    +
    +
    +

    Code Block 2.19: An event signifying that a person has been removed from the simulation.

    +
    public final class PersonRemovalEvent implements Event {
    +
    +    private final PersonId personId;
    +
    +    public PersonRemovalEvent(PersonId personId) {
    +        this.personId = personId;
    +    }
    +
    +    public PersonId getPersonId() {
    +        return personId;
    +    }
    +
    +}
    +
    +
    +
    +
    +

    Family Plugin

    +

    The family plugin defines a FamilyId as a simple, immutable wrapper to an int value. The FamilyDataManager tracks family membership via two-way mappings of PersonId to FamilyId. In this example, families can only be added and people can only be added to families. However, people can be removed via the PeoplePlugin so the FamilyDataManager subscribes to PersonRemovalEvent(s) and thus removes the people from families.

    +
    +
    +

    Code Block 2.20: The family id, like the person id, simply wraps an int.

    +
    public final class FamilyId implements Comparable<FamilyId> {
    +
    +    private final int id;
    +
    +    public FamilyId(int id) {
    +        this.id = id;
    +    }
    +
    +    public int getValue() {
    +        return id;
    +    }
    +
    +    @Override
    +    public int compareTo(FamilyId familyId) {
    +        return Integer.compare(id, familyId.id);
    +    }
    +
    +    @Override
    +    public int hashCode() {
    +        return id;
    +    }
    +
    +    @Override
    +    public boolean equals(Object obj) {
    +        if (this == obj) {
    +            return true;
    +        }
    +        if (!(obj instanceof FamilyId)) {
    +            return false;
    +        }
    +        FamilyId other = (FamilyId) obj;
    +        if (id != other.id) {
    +            return false;
    +        }
    +        return true;
    +    }
    +
    +    @Override
    +    public String toString() {
    +        return Integer.toString(id);
    +    }
    +}
    +
    +
    +
    +
    +

    Code Block 2.21: The family data manager manages families, their person members and various information methods.

    +
    public final class FamilyDataManager extends DataManager {
    +
    +    private int masterFamilyId;
    +    private Map<FamilyId, Set<PersonId>> familyMap = new LinkedHashMap<>();
    +    private Map<PersonId, FamilyId> personMap = new LinkedHashMap<>();
    +    private PersonDataManager personDataManager;
    +
    +    @Override
    +    public void init(DataManagerContext dataManagerContext) {
    +        super.init(dataManagerContext);
    +        personDataManager = dataManagerContext.getDataManager(PersonDataManager.class);
    +        dataManagerContext.subscribe(PersonRemovalEvent.class, this::handlePersonRemovalEvent);
    +    }
    +
    +    private void handlePersonRemovalEvent(DataManagerContext dataManagerContext,
    +            PersonRemovalEvent personRemovalEvent) {
    +        PersonId personId = personRemovalEvent.getPersonId();
    +        FamilyId familyId = personMap.remove(personId);
    +        if (familyId != null) {
    +            familyMap.get(familyId).remove(personId);
    +        }
    +        System.out.println(
    +                "Family Data Manager is removing person " + personId + " at time = " + dataManagerContext.getTime());
    +    }
    +
    +    public FamilyId addFamily() {
    +        FamilyId familyId = new FamilyId(masterFamilyId++);
    +        familyMap.put(familyId, new LinkedHashSet<>());
    +        return familyId;
    +    }
    +
    +    public boolean familyExists(FamilyId familyId) {
    +        return familyMap.keySet().contains(familyId);
    +    }
    +
    +    public List<PersonId> getFamilyMembers(FamilyId familyId) {
    +        if (!familyExists(familyId)) {
    +            throw new RuntimeException("unknown family " + familyId);
    +        }
    +        return new ArrayList<>(familyMap.get(familyId));
    +    }
    +
    +    public Optional<FamilyId> getFamilyId(PersonId personId) {
    +        if (!personDataManager.personExists(personId)) {
    +            throw new RuntimeException("unknown person " + personId);
    +        }
    +        FamilyId familyId = personMap.get(personId);
    +        return Optional.ofNullable(familyId);
    +    }
    +
    +    public void addFamilyMember(PersonId personId, FamilyId familyId) {
    +        if (!personDataManager.personExists(personId)) {
    +            throw new RuntimeException("unknown person " + personId);
    +        }
    +        if (!familyExists(familyId)) {
    +            throw new RuntimeException("unknown family " + familyId);
    +        }
    +        FamilyId currentFamilyId = personMap.get(personId);
    +        if (currentFamilyId != null) {
    +            throw new RuntimeException("person " + personId + " is already assigned to family " + currentFamilyId);
    +        }
    +        familyMap.get(familyId).add(personId);
    +        personMap.put(personId, familyId);
    +    }
    +
    +}
    +
    +
    +
    +
    +

    Vaccine Plugin

    +

    The vaccine plugin contains only the VaccineDataManager which tracks by PersonId which people have been vaccinated. Like the FamilyDataManager, it too subscribes to PersonRemovalEvent(s) and adjusts its data accordingly.

    +
    +
    +

    Code Block 2.22: The vaccination manager tracks the vaccination status of each person, reacting to the person removal as needed.

    +
    public final class VaccinationDataManager extends DataManager {
    +
    +    private Set<PersonId> vaccinatedPeople = new LinkedHashSet<>();
    +
    +    private PersonDataManager personDataManager;
    +
    +    @Override
    +    public void init(DataManagerContext dataManagerContext) {
    +        super.init(dataManagerContext);
    +        dataManagerContext.subscribe(PersonRemovalEvent.class, this::handlePersonRemovalEvent);
    +        personDataManager = dataManagerContext.getDataManager(PersonDataManager.class);
    +    }
    +
    +    private void handlePersonRemovalEvent(DataManagerContext dataManagerContext,
    +            PersonRemovalEvent personRemovalEvent) {
    +        PersonId personId = personRemovalEvent.getPersonId();
    +        vaccinatedPeople.remove(personId);
    +        System.out.println("Vaccination Data Manager is removing person " + personId + " at time = "
    +                + dataManagerContext.getTime());
    +    }
    +
    +    public Set<PersonId> getVaccinatedPeople() {
    +        return new LinkedHashSet<>(vaccinatedPeople);
    +    }
    +
    +    public Set<PersonId> getUnvaccinatedPeople() {
    +        Set<PersonId> people = personDataManager.getPeople();
    +        people.removeAll(vaccinatedPeople);
    +        return people;
    +    }
    +
    +    public boolean isPersonVaccinated(PersonId personId) {
    +        if (!personDataManager.personExists(personId)) {
    +            throw new RuntimeException("unknown person " + personId);
    +        }
    +        return vaccinatedPeople.contains(personId);
    +    }
    +
    +    public void vaccinatePerson(PersonId personId) {
    +        if (!personDataManager.personExists(personId)) {
    +            throw new RuntimeException("unknown person " + personId);
    +        }
    +        vaccinatedPeople.add(personId);
    +    }
    +
    +}
    +
    +
    +
    +
    +

    Model Plugin

    +

    The model plugin contains a single actor, the ModelActor, that serves to:

    +
      +
    • Add people to the simulation
    • +
    • Group them into families
    • +
    • Vaccinate some people
    • +
    • Demonstrate that events cascade
    • +
    +
    +
    +

    Connecting the Plugins

    +

    Both the family and vaccine plugins depend on the concept of a person as implemented by the PersonId class. They also need to respond when a person is removed from the simulation and do so by handling the corresponding PersonRemovalEvent generated by the person plugin. We build these dependencies via the Plugin.Builder class in the example code below.

    +
    +
    +

    Code Block 2.23: The people, vaccine, family and model plugins are contributed to the simulation. On execution, the model plugin’s single actor schedules the vaccination of each person as well as a few random removals of people from the simulation.

    +
    public static void main(String[] args) {
    +
    +    PluginId peoplePluginId = new SimplePluginId("people plugin");
    +    Plugin peoplePlugin = Plugin.builder()//
    +            .setPluginId(peoplePluginId)//
    +            .setInitializer(pluginContext -> {
    +                pluginContext.addDataManager(new PersonDataManager());
    +            })//
    +            .build();
    +
    +    PluginId vaccinePluginId = new SimplePluginId("vaccine plugin");
    +    Plugin vaccinePlugin = Plugin.builder()//
    +            .setPluginId(vaccinePluginId)//
    +            .addPluginDependency(peoplePluginId)//
    +            .setInitializer(pluginContext -> {
    +                pluginContext.addDataManager(new VaccinationDataManager());
    +            })//
    +            .build();
    +
    +    PluginId familyPluginId = new SimplePluginId("family plugin");
    +    Plugin familyPlugin = Plugin.builder()//
    +            .setPluginId(familyPluginId)//
    +            .addPluginDependency(peoplePluginId)//
    +            .setInitializer(pluginContext -> {
    +                pluginContext.addDataManager(new FamilyDataManager());
    +            })//
    +            .build();
    +
    +    PluginId modelPluginId = new SimplePluginId("model plugin");
    +    Plugin modelPlugin = Plugin.builder()//
    +            .setPluginId(modelPluginId)//
    +            .setInitializer(pluginContext -> {
    +                pluginContext.addActor(new ModelActor()::init);
    +
    +            })//
    +            .build();
    +
    +    Simulation.builder()//
    +            .addPlugin(vaccinePlugin)//
    +            .addPlugin(familyPlugin)//
    +            .addPlugin(peoplePlugin)//
    +            .addPlugin(modelPlugin)//
    +            .build()//
    +            .execute();
    +}
    +
    +
    +

    Note the addition of the dependency on the people plugin via its id when adding both the vaccine and family plugins. The order of addition of the plugins to the simulation is relatively unimportant as is ordering in general in any of the builder patterns used in GCM.

    +

    The resulting output:

    +
    +
    +
    +

    Figure 2.4: The output shows the combined reporting from actors and data managers as they report vaccination progress.

    + + + + + + + + +
    +Person 7 was vaccinated at time = 1.0
    Person 2 was vaccinated at time = 2.0
    Person 5 was vaccinated at time = 3.0
    Vaccination Data Manager is removing person 2 at time = 3.0
    Family Data Manager is removing person 2 at time = 3.0
    Person 6 was vaccinated at time = 4.0
    Vaccination Data Manager is removing person 1 at time = 4.0
    Family Data Manager is removing person 1 at time = 4.0
    Person 3 was vaccinated at time = 5.0
    Vaccination Data Manager is removing person 8 at time = 5.0
    Family Data Manager is removing person 8 at time = 5.0
    Failed to vaccinate Person 1 at time = 6.0
    Vaccination Data Manager is removing person 6 at time = 6.0
    Family Data Manager is removing person 6 at time = 6.0
    Person 0 was vaccinated at time = 7.0
    Vaccination Data Manager is removing person 7 at time = 7.0
    Family Data Manager is removing person 7 at time = 7.0
    Person 9 was vaccinated at time = 8.0
    Vaccination Data Manager is removing person 5 at time = 8.0
    Family Data Manager is removing person 5 at time = 8.0
    Failed to vaccinate Person 8 at time = 9.0
    Vaccination Data Manager is removing person 3 at time = 9.0
    Family Data Manager is removing person 3 at time = 9.0
    Person 4 was vaccinated at time = 10.0
    Vaccination Data Manager is removing person 4 at time = 10.0
    Family Data Manager is removing person 4 at time = 10.0
    Vaccination Data Manager is removing person 0 at time = 11.0
    Family Data Manager is removing person 0 at time = 11.0
    Vaccination Data Manager is removing person 9 at time = 12.0
    Family Data Manager is removing person 9 at time = 12.0 +
    +
    +
    +
    +
    +
    +
    +

    2.7 Plugin Dependency Graph Lesson (Lesson 7)

    +

    We extend the previous lesson by adding an additional dependency of the vaccine plugin on the family plugin. This will allow the VaccineDataManager to answer queries about which members of a family have yet to be vaccinated.

    +

    From the VaccineDataManager:

    +
    +
    +

    Code Block 2.24: By adding a plugin dependencies on the people and family plugins, the vaccine data manager can now answer questions about the vaccine status of family members

    +
    public List<PersonId> getUnvaccinatedFamilyMembers(PersonId personId) {
    +    if (!personDataManager.personExists(personId)) {
    +        throw new RuntimeException("unknown person " + personId);
    +    }
    +    List<PersonId> result = new ArrayList<>();
    +    Optional<FamilyId> optional = familyDataManager.getFamilyId(personId);
    +    if (optional.isPresent()) {
    +        FamilyId familyId = optional.get();
    +        List<PersonId> familyMembers = familyDataManager.getFamilyMembers(familyId);
    +        for (PersonId familyMemeberId : familyMembers) {
    +            if (!isPersonVaccinated(familyMemeberId)) {
    +                result.add(personId);
    +            }
    +        }
    +    }
    +    return result;
    +}
    +
    +
    +

    The plugins in this example form a dependency pattern:

    +
    +
    +
    +
    +

    Figure 2.5: The three plugins form a simple, directed acyclic graph (DAG). GCM uses this DAG to ensure that all information provided by any data manager is fully updated whenever an event is being processed by a dependent of the data manager.

    +

    +
    +
    +
    +
    +

    All plugin dependencies in GCM form similar directed, acyclic graphs (DAGs). There can be no loops in the dependency graph, but the graph does not have to be fully connected. The dependencies reflect the requirements of the data managers within a plugin to access data managers in other plugins. This pattern drives the order in which events are presented to data managers. This way, a data manager is guaranteed that any event that it is processing has already been fully processed by all the data managers it depends on.

    +

    In this lesson, the VaccineDataManager and the FamilyDataManager have both subscribed to the PersonRemovalEvent generated by the PersonDataManager. Since the VaccineDataManager also has a dependency on the FamilyDataManager, the VaccineDataManager should receive the event after the FamilyDataManager. Events cascade through the subscribed data managers in an order that is consistent with the plugin dependency DAG.

    +
    +
    +

    2.8 Plugin Data Lesson (Lesson 8)

    +

    The Example code in the last lesson was a bit verbose and can be improved. Identifying and generating the plugins can be included in the plugin packages by introducing classes for each id and classes for each plugin’s contents. In the disease package we add a unique plugin identifier with a final static id field:

    +
    +
    +

    Code Block 2.25: The plugin id for the disease plugin is implemented as a static constant.

    +
    public final class DiseasePluginId implements PluginId {
    +    private DiseasePluginId() {
    +    }
    +
    +    public final static PluginId PLUGIN_ID = new SimplePluginId("disease plugin id");
    +}
    +
    +
    +

    We also add a static class (DiseasePlugin) that implements the construction of the plugin from the required plugin data.

    +
    +
    +

    Code Block 2.26: The DiseasePlugin class is a static class for creating the disease plugin.

    +
    public final class DiseasePlugin {
    +
    +    private DiseasePlugin() {
    +
    +    }
    +
    +    public static Plugin getDiseasePlugin(DiseasePluginData diseasePluginData) {
    +
    +        return Plugin.builder()//
    +                .addPluginData(diseasePluginData)//
    +                .setPluginId(DiseasePluginId.PLUGIN_ID)//
    +                .setInitializer((pluginContext) -> {
    +                    DiseasePluginData pluginData = pluginContext.getPluginData(DiseasePluginData.class).get();
    +                    pluginContext.addDataManager(new DiseaseDataManager(pluginData));
    +                })//
    +                .build();
    +    }
    +
    +}
    +
    +
    +

    The plugin is initialized with a DiseasePluginData object that contains the initial values for r0, asymptomatic days and symptomatic days. Most plugins will have a single plugin data object, but some may not need any and some may be designed with multiple such classes. All such classes must implement the PluginData interface:

    +
    +
    +

    Code Block 2.27: The PluginData interface indicates that its implementors are immutable. Plugin data objects are shared between all simulation instances and thus must be thread safe. It introduces a single method used to copy plugin datas during the experiment process.

    +
    @ThreadSafe
    +public interface PluginData {
    +    /**
    +     * Returns a PluginDataBuilder that can build the plugin data. The returned
    +     * builder should be initialized with this plugin data object's internal state
    +     * such that invocation of pluginData.getCloneBuilder().build() will generate a
    +     * copy of the current plugin.
    +     */
    +    public PluginDataBuilder getCloneBuilder();
    +
    +    @Override
    +    public int hashCode();
    +
    +    /**
    +     * Plugin datas are equal if they are implicitly equal. They contain the same
    +     * implicit information without regard to order.
    +     */
    +    @Override
    +    public boolean equals(Object obj);
    +
    +    /**
    +     * A string representation of the plugin data implicit data and reflects the
    +     * order of addition of the data. Equal plugin datas have equal strings in terms
    +     * of content, but not necessarily order.
    +     */
    +    @Override
    +    public String toString();
    +}
    +
    +
    +

    Plugin data classes must be threadsafe since they will be shared between multiple simulations running on separate threads. This stands in contrast to the actors and data managers which are created and managed in the thread of a single simulation. The best practice is to make plugin data classes immutable since immutable classes in Java are guaranteed to be threadsafe. For a class to be immutable in Java it must meet three conditions:

    +
      +
    1. It cannot be mutated, i.e. it has no setters and no public fields.
    2. +
    3. All its fields are marked final.
    4. +
    5. Its constructor(s) do not pass reference to self. No reference to the newly created object leaks out before construction is complete.
    6. +
    +

    Besides carrying whatever data is needed by the plugin, the PluginData implementor must provide a PluginDataBuilder:

    +
    +
    +

    Code Block 2.28: Every plugin data class has a corresponding builder class to aid in the experiment’s generation of alternate scenarios.

    +
    public interface PluginDataBuilder {
    +    /**
    +     * Returns a plugin data
    +     */
    +    public PluginData build();
    +}
    +
    +
    +

    The role of the plugin data builder will be explored in the next lesson where it will be used to make alterable copies of plugin data to drive the experiment. For now, let’s examine the DiseasePluginData class. It is composed several sections:

    +
      +
    • A data class
    • +
    • A static builder class
    • +
    • A single data field and private constructor
    • +
    • Getter methods for the data
    • +
    • A clone builder method
    • +
    +
    +
    +

    Code Block 2.29: The disease plugin data collects the various general disease properties used to initialize the disease data manager.

    +
    private static class Data {
    +
    +    private double r0;
    +
    +    private double asymptomaticDays;
    +
    +    private double symptomaticDays;
    +
    +    private Data() {
    +    }
    +
    +    private Data(final Data data) {
    +        r0 = data.r0;
    +        asymptomaticDays = data.asymptomaticDays;
    +        symptomaticDays = data.symptomaticDays;
    +    }
    +
    +
    +

    The Data class is private and just contains the fields needed by the plugin. Note that it is a mutable class and that its fields are not final. It will be used by the builder class later to store values. Its constructors are private and allow one Data object to be copied from another.

    +
    +
    +

    Code Block 2.30: The builder class for the immutable disease plugin data class.

    +
    public static class Builder implements PluginDataBuilder {
    +    private Data data;
    +
    +    private Builder(final Data data) {
    +        this.data = data;
    +    }
    +
    +    @Override
    +    public DiseasePluginData build() {
    +
    +        return new DiseasePluginData(new Data(data));
    +
    +    }
    +
    +    public Builder setAsymptomaticDays(final double asymptomaticDays) {
    +        data.asymptomaticDays = asymptomaticDays;
    +        return this;
    +    }
    +
    +    public Builder setR0(final double r0) {
    +        data.r0 = r0;
    +        return this;
    +    }
    +
    +    public Builder setSymptomaticDays(final double symptomaticDays) {
    +        data.symptomaticDays = symptomaticDays;
    +        return this;
    +    }
    +}
    +
    +public static Builder builder() {
    +    return new Builder(new Data());
    +}
    +
    +
    +

    The static builder class is used instead of a constructor. The use of builder classes for plugin data objects is key to the creation of experiments covered in the next lesson. For now, let’s concentrate on what the builder does. First, it has setter methods for each of the data fields and each such method returns the builder instance to support method chaining. Next, the build() method returns the DiseasePluginData. Finally, the builder’s own constructor is private and is accessed via a static method. This is done to grant a syntax that is more compatible with the method chaining.

    +
    +
    +

    Code Block 2.31: The disease plugin data is constructed from the collected data in a private constructor.

    +
    private final Data data;
    +
    +private DiseasePluginData(final Data data) {
    +    this.data = data;
    +}
    +
    +
    +

    After the builder collects the data, it passes that data to the instance of the DiseasePluginData which is stored as a final field. Recall that the field must be final in an immutable class.

    +
    +
    +

    Code Block 2.32: The disease plugin data grants access to its immutable field values.

    +
    public double getAsymptomaticDays() {
    +    return data.asymptomaticDays;
    +}
    +
    +public double getR0() {
    +    return data.r0;
    +}
    +
    +public double getSymptomaticDays() {
    +    return data.symptomaticDays;
    +}
    +
    +
    +

    The getter methods for each field value in the data are added. There are no corresponding setter methods.

    +
    +
    +

    Code Block 2.33: The disease plugin data creates a copy of its data and places it in the returned plugin data builder.

    +
    @Override
    +public PluginDataBuilder getCloneBuilder() {
    +    return new Builder(new Data(data));
    +}
    +
    +
    +

    We end the class with the getCloneBuilder method.

    +
    +
    +
    + +
    +
    +Terminology Note +
    +
    +
    +

    Our use of the term clone is intuitive but may cause some confusion. What we are doing is copying the data in the DiseasePluginData and placing into a builder so that it can be further mutated later in the experiment. Java formally defines the term clone as a part of the Object class definition and implements it with a protected method clone(). Use of the Object.clone() method has generally fallen out of favor in Java but still has some proponents/use cases.

    +
    +
    +

    The method returns a new Builder that has reference to the current data object. The resulting example class is easier to read and more succinct:

    +
    +
    +

    Code Block 2.34: Example 8 executes more succinctly by use of static plugin classes.

    +
    public final class Example_8 {
    +
    +    private Example_8() {
    +    }
    +
    +    private static DiseasePluginData getDiseasePluginData() {
    +        return DiseasePluginData.builder()//
    +                .setR0(1.5)//
    +                .setAsymptomaticDays(4.0)//
    +                .setSymptomaticDays(12.0)//
    +                .build();
    +    }
    +
    +    private static PolicyPluginData getPolicyPluginData() {
    +        return PolicyPluginData.builder()//
    +                .setDistributeVaccineLocally(true)//
    +                .setSchoolClosingInfectionRate(0.05)//
    +                .build();
    +    }
    +
    +    public static void main(String[] args) {
    +
    +        DiseasePluginData diseasePluginData = getDiseasePluginData();
    +        Plugin diseasePlugin = DiseasePlugin.getDiseasePlugin(diseasePluginData);
    +
    +        PolicyPluginData policyPluginData = getPolicyPluginData();
    +        Plugin policyPlugin = PolicyPlugin.getPolicyPlugin(policyPluginData);
    +
    +        Plugin modelPlugin = ModelPlugin.getModelPlugin();
    +
    +        Simulation.builder()//
    +                .addPlugin(diseasePlugin)//
    +                .addPlugin(modelPlugin)//
    +                .addPlugin(policyPlugin)//
    +                .build()//
    +                .execute();
    +    }
    +}
    +
    +
    +
    +
    +

    2.9 Experiments Lesson (Lesson 9)

    +

    So far we have mentioned that the plugin data classes play a role in executing an experiment via the getCloneBuilder method. Let’s start with the simple experiment. We will update the last example class by replacing the Simulation execution with an Experiment execution:

    +
    +
    +

    Code Block 2.35: Example 9 replaces Example 8’s use of the simulation with an experiment.

    +
    public final class Example_9_A {
    +
    +    private Example_9_A() {
    +    }
    +
    +    private static DiseasePluginData getDiseasePluginData() {
    +        return DiseasePluginData.builder()//
    +                .setR0(1.5)//
    +                .setAsymptomaticDays(4.0)//
    +                .setSymptomaticDays(12.0)//
    +                .build();
    +    }
    +
    +    private static PolicyPluginData getPolicyPluginData() {
    +        return PolicyPluginData.builder()//
    +                .setDistributeVaccineLocally(true)//
    +                .setSchoolClosingInfectionRate(0.05)//
    +                .build();
    +    }
    +
    +    public static void main(String[] args) {
    +
    +        DiseasePluginData diseasePluginData = getDiseasePluginData();
    +        Plugin diseasePlugin = DiseasePlugin.getDiseasePlugin(diseasePluginData);
    +
    +        PolicyPluginData policyPluginData = getPolicyPluginData();
    +        Plugin policyPlugin = PolicyPlugin.getPolicyPlugin(policyPluginData);
    +
    +        Plugin modelPlugin = ModelPlugin.getModelPlugin();
    +
    +        Experiment.builder()//
    +                .addPlugin(diseasePlugin)//
    +                .addPlugin(modelPlugin)//
    +                .addPlugin(policyPlugin)//
    +                .addExperimentContextConsumer(ExperimentStatusConsole.builder().build())//
    +                .build()//              
    +                .execute();
    +    }
    +}
    +
    +
    +

    The experiment class has a very similar builder to the Simulation class so we only have to swap out the Simulation reference for an Experiment reference. The resulting execution created an experiment containing exactly one simulation that runs in the main thread. However, the output contains information about the status of the experiment.

    +
    +
    +
    +

    Figure 2.6: The output of the experiment version is the same as the simulation since the experiment contains exactly one scenario.

    + + + + + + + + +
    +Model Actor initializing
    r0 = 1.5
    asymptomatic days = 4.0
    symptomatic days = 12.0
    school closing infection rate = 0.05
    distribute vaccine locally = true

    1 of 1 scenario, 100% complete. Expected experiment completion in 0:00:00
    Experiment completion of 1 scenario in 0:00:00:
    SUCCEDED : 1
    end of experiment status console +
    +
    +
    +
    +

    What happens when the experiment executes?

    +

    You have contributed several plugins to the experiment and on execution the experiment generates multiple simulation runs on multiple threads. Let’s examine how this is accomplished as a way to motivate this lesson’s code examples.

    +

    The experiment is composed of several plugins, each with zero to many plugin data objects. For purposes of the diagrams we will assume that each plugin has a single plugin data object.

    +
    +
    +
    +
    +

    Figure 2.7: Each plugin contains zero to many plugin data objects. For simplicity, we show one plugin data object per plugin.

    +

    +
    +
    +
    +
    +

    The experiment gathers the plugin data objects and gets the plugin data builder for each. These plugin data builders will come pre-filled with the data from the original data objects.

    +
    +
    +
    +
    +

    Figure 2.8: Using the clone builder mechanism, each plugin data generates a new plugin data builder that is prefilled with the plugin data’s information.

    +

    +
    +
    +
    +
    +

    By altering the data in these builders, we generate new scenarios for the simulations to execute. GCM manages the instructions to alter the plugin data via Dimensions. Each dimension contains one to many levels.

    +
    +
    +
    +
    +

    Figure 2.9: Dimensions are contributed to the experiment. Each dimension has a fixed number of levels and each such level alters the plugin data builders in a consistent way.

    +

    +
    +
    +
    +
    +

    For example, we may have a dimension that alters the value of alpha from plugin data A and the value of beta from plugin data B. Each level in the dimension will set specific values for alpha and beta via the builders.

    +

    ::: {#fig-experiments_diagram_dimension_levels_example2 .figure fig-cap=“Example levels in a dimension”}

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    levelalphabeta
    02.3FALSE
    13.6TRUE
    24.8FALSE
    +

    Each level in a dimension is actually a function that takes in the builders and manipulates the content of each plugin as needed.

    +
    +
    +
    +
    +

    Figure 2.10: Dimension levels act as a function to change plugin data builders.

    +

    +
    +
    +
    +
    +

    Consider an experiment with two dimensions having 3 and 5 levels respectively. The number of level permutations is 3x5 = 15. Each such permutation is referred to as a scenario and the scenarios are numbered from 0 to 14. As the experiment executes, it works with each scenario id and determines for that id which levels are active for each dimension.

    +
    +
    +
    +
    +

    Figure 2.11: The scenario id is a numberical composite of the level ids in each of the dimensions.

    +

    +
    +
    +
    +
    +

    Each level (via its function) alters the contents of the builders in turn, resulting in a unique set of content for that scenario.

    +
    +
    +
    +
    +

    Figure 2.12: Once all of the dimensions have added content to the plugin data builders, the experiment builds the resulting plugin datas to be used in the next scenario.

    +

    +
    +
    +
    +
    +

    The builders are then instructed by the experiment to build the plugin data objects. The resulting data objects are inserted into copies of the original plugins to produce a unique set of altered plugins that are specific to the scenario id and executed via a single simulation instance.

    +
    +
    +
    +
    +

    Figure 2.13: For each original, experiment level plugin, a copy of the plugin is made with the various plugin datas replaced with those specific to the scenario. These altered plugins will be contributed to the simulation instance for the scenario.

    +

    +
    +
    +
    +
    +

    You may have noticed that the initializer code above acquires the DiseasePluginData via the context rather than the instance passed to the getDiseasePlugin() method. This is a necessity due to experiment design and will be covered in the lessons that follow. In general, the initializer code should always retrieve plugin data from the plugin context.

    +

    We expand the example by adding a single dimension that sets r0 to two values, generating two simulations.

    +
    +
    +

    Code Block 2.36: Example 9 B introduces a single dimension that sets the R0 value of the disease plugin data to two values.

    +
    public static void main(String[] args) {
    +
    +    DiseasePluginData diseasePluginData = getDiseasePluginData();
    +    Plugin diseasePlugin = DiseasePlugin.getDiseasePlugin(diseasePluginData);
    +
    +    PolicyPluginData policyPluginData = getPolicyPluginData();
    +    Plugin policyPlugin = PolicyPlugin.getPolicyPlugin(policyPluginData);
    +
    +    Plugin modelPlugin = ModelPlugin.getModelPlugin();
    +
    +    Dimension dimension = FunctionalDimension.builder()//
    +            .addLevel((context) -> {
    +                DiseasePluginData.Builder builder = context.getPluginDataBuilder(DiseasePluginData.Builder.class);
    +                double r0 = 2.5;
    +                builder.setR0(r0);
    +                ArrayList<String> result = new ArrayList<>();
    +                result.add(Double.toString(r0));
    +                return result;
    +            })//
    +
    +            .addLevel((context) -> {
    +                DiseasePluginData.Builder builder = context.getPluginDataBuilder(DiseasePluginData.Builder.class);
    +                double r0 = 2.0;
    +                builder.setR0(r0);
    +                ArrayList<String> result = new ArrayList<>();
    +                result.add(Double.toString(r0));
    +                return result;
    +            })//
    +
    +            .addMetaDatum("r0")//
    +
    +            .build();
    +
    +    Experiment.builder()//
    +            .addPlugin(diseasePlugin)//
    +            .addPlugin(modelPlugin)//
    +            .addPlugin(policyPlugin)//
    +            .addDimension(dimension)//
    +            .build()//
    +            .execute();
    +}
    +
    +
    +

    In the dimension we see that there are two levels and the addition of some meta data in the addMetaDatum(“r0”) invocation. The meta data here represents the information that each level is altering in the experiment. The main purpose of each level is to alter the state of a builder(s) but must also return meta data values to match the meta data for the dimension. The meta data of the dimension acts as a header to a table while the meta data for each level are the values in that table.

    +

    The building of the dimension can be streamlined without typing out each level:

    +
    +
    +

    Code Block 2.37: Example 9 C improves on the creation of the R0 dimension.

    +
    public final class Example_9_C {
    +
    +    private Example_9_C() {
    +    }
    +
    +    private static DiseasePluginData getDiseasePluginData() {
    +        return DiseasePluginData.builder()//
    +                .setR0(1.5)//
    +                .setAsymptomaticDays(4.0)//
    +                .setSymptomaticDays(12.0)//
    +                .build();
    +    }
    +
    +    private static PolicyPluginData getPolicyPluginData() {
    +        return PolicyPluginData.builder()//
    +                .setDistributeVaccineLocally(true)//
    +                .setSchoolClosingInfectionRate(0.05)//
    +                .build();
    +    }
    +
    +    private static Dimension getDimension() {
    +        FunctionalDimension.Builder builder = FunctionalDimension.builder();//
    +
    +        List<Double> r0Values = new ArrayList<>();
    +        r0Values.add(0.5);
    +        r0Values.add(0.75);
    +        r0Values.add(1.0);
    +        r0Values.add(1.5);
    +        r0Values.add(2.0);
    +        r0Values.add(2.5);
    +
    +        for (Double r0 : r0Values) {
    +            builder.addLevel((context) -> {
    +                DiseasePluginData.Builder pluginDataBuilder = context
    +                        .getPluginDataBuilder(DiseasePluginData.Builder.class);
    +                pluginDataBuilder.setR0(r0);
    +                ArrayList<String> result = new ArrayList<>();
    +                result.add(Double.toString(r0));
    +                return result;
    +            });//
    +        }
    +        builder.addMetaDatum("r0");//
    +
    +        return builder.build();
    +    }
    +
    +
    +

    The resulting experiment execution is more streamlined:

    +
    +
    +

    Code Block 2.38: Execution of the experiment is cleaner.

    +
    public static void main(String[] args) {
    +
    +    DiseasePluginData diseasePluginData = getDiseasePluginData();
    +    Plugin diseasePlugin = DiseasePlugin.getDiseasePlugin(diseasePluginData);
    +
    +    PolicyPluginData policyPluginData = getPolicyPluginData();
    +    Plugin policyPlugin = PolicyPlugin.getPolicyPlugin(policyPluginData);
    +
    +    Plugin modelPlugin = ModelPlugin.getModelPlugin();
    +
    +    Dimension dimension = getDimension();
    +
    +    Experiment.builder()//
    +            .addPlugin(diseasePlugin)//
    +            .addPlugin(modelPlugin)//
    +            .addPlugin(policyPlugin)//
    +            .addDimension(dimension)//
    +            .build()//
    +            .execute();
    +}
    +
    +
    +

    We have turned off the experiment report progress to console in the code above. We have chosen six values for r0 in our dimension and thus we have 6 simulation executions, each having the model actor print out the contents of the DiseaseDataManager:

    +
    +
    +
    +

    Figure 2.14: The model actor writes output for each of the five scenarios corresponding to the five values of the R0 dimension.

    + + + + + + + + +
    +Model Actor initializing
    r0 = 0.5
    asymptomatic days = 4.0
    symptomatic days = 12.0
    school closing infection rate = 0.05
    distribute vaccine locally = true

    Model Actor initializing
    r0 = 0.75
    asymptomatic days = 4.0
    symptomatic days = 12.0
    school closing infection rate = 0.05
    distribute vaccine locally = true

    Model Actor initializing
    r0 = 1.0
    asymptomatic days = 4.0
    symptomatic days = 12.0
    school closing infection rate = 0.05
    distribute vaccine locally = true

    Model Actor initializing
    r0 = 1.5
    asymptomatic days = 4.0
    symptomatic days = 12.0
    school closing infection rate = 0.05
    distribute vaccine locally = true

    Model Actor initializing
    r0 = 2.0
    asymptomatic days = 4.0
    symptomatic days = 12.0
    school closing infection rate = 0.05
    distribute vaccine locally = true

    Model Actor initializing
    r0 = 2.5
    asymptomatic days = 4.0
    symptomatic days = 12.0
    school closing infection rate = 0.05
    distribute vaccine locally = true
    +
    +
    +
    +
    +

    We are extending the example again, reducing the r0 dimension to just three levels and introducing a dimension over the policy data. This new dimension has four levels controlling local vaccine distribution and school closing infection rates:

    +
    +
    +

    Code Block 2.39: A dimension representing school related policies is added. Note that this dimension has four levels and covers two policies.

    +
    private static Dimension getPolicyDimension() {
    +    FunctionalDimension.Builder builder = FunctionalDimension.builder();//
    +
    +    List<Double> schoolClosingInfectionRates = new ArrayList<>();
    +    schoolClosingInfectionRates.add(0.05);
    +    schoolClosingInfectionRates.add(0.10);
    +
    +    List<Boolean> localVaccineDistributionValues = new ArrayList<>();
    +    localVaccineDistributionValues.add(false);
    +    localVaccineDistributionValues.add(true);
    +
    +    for (Boolean localVaccineDistribution : localVaccineDistributionValues) {
    +        for (Double schoolClosingInfectionRate : schoolClosingInfectionRates) {
    +            builder.addLevel((context) -> {
    +                PolicyPluginData.Builder pluginDataBuilder = context
    +                        .getPluginDataBuilder(PolicyPluginData.Builder.class);
    +                pluginDataBuilder.setSchoolClosingInfectionRate(schoolClosingInfectionRate);
    +                pluginDataBuilder.setDistributeVaccineLocally(localVaccineDistribution);
    +
    +                ArrayList<String> result = new ArrayList<>();
    +                result.add(Double.toString(schoolClosingInfectionRate));
    +                result.add(Boolean.toString(localVaccineDistribution));
    +                return result;
    +            });//
    +        }
    +    }
    +    builder.addMetaDatum("school_closing_infection_rate");//
    +    builder.addMetaDatum("distribute_vaccine_locally");//
    +
    +    return builder.build();
    +}
    +
    +
    +

    We add the new dimension to the experiment:

    +
    +
    +

    Code Block 2.40: The new policy dimension is added to the experiment with four levels. The R0 dimension was reduced to three levels. Thus the experiment will run twelve scenarios.

    +
    public static void main(String[] args) {
    +
    +    DiseasePluginData diseasePluginData = getDiseasePluginData();
    +    Plugin diseasePlugin = DiseasePlugin.getDiseasePlugin(diseasePluginData);
    +
    +    PolicyPluginData policyPluginData = getPolicyPluginData();
    +    Plugin policyPlugin = PolicyPlugin.getPolicyPlugin(policyPluginData);
    +
    +    Plugin modelPlugin = ModelPlugin.getModelPlugin();
    +
    +    Dimension r0Dimension = getR0Dimension();
    +
    +    Dimension policyDimension = getPolicyDimension();
    +
    +    Experiment.builder()//
    +            .addPlugin(diseasePlugin)//
    +            .addPlugin(modelPlugin)//
    +            .addPlugin(policyPlugin)//
    +            .addDimension(r0Dimension)//
    +            .addDimension(policyDimension)//
    +            .build()//
    +            .execute();
    +}
    +
    +
    +

    The result is now 12 executed scenarios:

    +
    +
    +
    +

    Figure 2.15: The first two scenarios.

    + + + + + + + + +
    +Model Actor initializing
    r0 = 1.5
    asymptomatic days = 4.0
    symptomatic days = 12.0
    school closing infection rate = 0.05
    distribute vaccine locally = false

    Model Actor initializing
    r0 = 2.0
    asymptomatic days = 4.0
    symptomatic days = 12.0
    school closing infection rate = 0.05
    distribute vaccine locally = false
    +
    +
    +
    +
    +

    +
    +
    +
    +

    Figure 2.16: The last two scenarios.

    + + + + + + + + +
    +Model Actor initializing
    r0 = 2.0
    asymptomatic days = 4.0
    symptomatic days = 12.0
    school closing infection rate = 0.1
    distribute vaccine locally = true

    Model Actor initializing
    r0 = 2.5
    asymptomatic days = 4.0
    symptomatic days = 12.0
    school closing infection rate = 0.1
    distribute vaccine locally = true
    +
    +
    +
    +
    +

    So far, the experiment has run in a single thread. We now run it in four threads by adding an ExperimentParameterData.

    +
    +
    +

    Code Block 2.41: Executing the 12 scenarios of the previous experiment with four threads.

    +
    public static void main(String[] args) {
    +
    +    DiseasePluginData diseasePluginData = getDiseasePluginData();
    +    Plugin diseasePlugin = DiseasePlugin.getDiseasePlugin(diseasePluginData);
    +
    +    PolicyPluginData policyPluginData = getPolicyPluginData();
    +    Plugin policyPlugin = PolicyPlugin.getPolicyPlugin(policyPluginData);
    +
    +    Plugin modelPlugin = ModelPlugin.getModelPlugin();
    +
    +    Dimension r0Dimension = getR0Dimension();
    +
    +    Dimension policyDimension = getPolicyDimension();
    +
    +    ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()//
    +            .setThreadCount(4)//
    +            .build();
    +
    +    /*
    +         * Adding threads. Scrambled output
    +         */
    +    Experiment.builder()//
    +            .addPlugin(diseasePlugin)//
    +            .addPlugin(modelPlugin)//
    +            .addPlugin(policyPlugin)//
    +            .addDimension(r0Dimension)//
    +            .addDimension(policyDimension)//
    +            .setExperimentParameterData(experimentParameterData)//
    +            .build()//
    +            .execute();
    +}
    +
    +
    +

    The experiment runs in the main thread and the scenarios now run the four additional threads. The resulting console output a bit jumbled since the writes to the console are now coming from four simultaneous simulation runs:

    +
    +
    +
    +

    Figure 2.17: The output from the four threads running the twelve scenarios is a bit jumbled and hard to follow. This will get resolved later with improved output handling.

    + + + + + + + + +
    +Model Actor initializing
    Model Actor initializing
    r0 = 1.5
    Model Actor initializing
    Model Actor initializing
    r0 = 1.5
    asymptomatic days = 4.0
    symptomatic days = 12.0
    r0 = 2.5
    school closing infection rate = 0.05
    asymptomatic days = 4.0
    asymptomatic days = 4.0
    symptomatic days = 12.0
    r0 = 2.0
    school closing infection rate = 0.1
    distribute vaccine locally = false


    +
    +
    +
    +
    +
    +

    We will alleviate this problem as we explore how the simulation and experiment manage output.

    +
    +
    +

    2.10 Output Lesson (Lesson 10)

    +

    So far we have only produced output by writing directly to the console in the various actors and data managers. The simulation contexts (ActorContext / ReportContext / DataManagerContext) provide for the release of output objects to an external handler (outside the simulation). In this lesson, the ModelActor class has been altered to use this mechanism:

    +
    +
    +

    Code Block 2.42: The model actor now reports output via the release output method provided by its context.

    +
    public void init(ActorContext actorContext) {
    +    DiseaseDataManager diseaseDataManager = actorContext.getDataManager(DiseaseDataManager.class);
    +    actorContext.releaseOutput("Model Actor initializing");
    +    String tab = "\t";
    +    actorContext.releaseOutput(tab + "r0 = " + diseaseDataManager.getR0());
    +    actorContext.releaseOutput(tab + "asymptomatic days = " + diseaseDataManager.getAsymptomaticDays());
    +    actorContext.releaseOutput(tab + "symptomatic days = " + diseaseDataManager.getSymptomaticDays());
    +    PolicyDataManager policyDataManager = actorContext.getDataManager(PolicyDataManager.class);
    +    actorContext.releaseOutput(
    +            tab + "school closing infection rate = " + policyDataManager.getSchoolClosingInfectionRate());
    +    actorContext
    +            .releaseOutput(tab + "distribute vaccine locally = " + policyDataManager.distributeVaccineLocally());
    +}
    +
    +
    +

    Data managers can release output in a completely similar way. The output objects are handled by an external handler presented during the build of the simulation:

    +
    +
    +

    Code Block 2.43: The simulation sends the released output from the contexts to an output consumer.

    +
    public final class Example_10_A {
    +
    +    private Example_10_A() {
    +    }
    +
    +    private static DiseasePluginData getDiseasePluginData() {
    +        return DiseasePluginData.builder()//
    +                .setR0(1.5)//
    +                .setAsymptomaticDays(4.0)//
    +                .setSymptomaticDays(12.0)//
    +                .build();
    +    }
    +
    +    private static PolicyPluginData getPolicyPluginData() {
    +        return PolicyPluginData.builder()//
    +                .setDistributeVaccineLocally(true)//
    +                .setSchoolClosingInfectionRate(0.05)//
    +                .build();
    +    }
    +
    +    public static void main(String[] args) {
    +
    +        DiseasePluginData diseasePluginData = getDiseasePluginData();
    +        Plugin diseasePlugin = DiseasePlugin.getDiseasePlugin(diseasePluginData);
    +
    +        PolicyPluginData policyPluginData = getPolicyPluginData();
    +        Plugin policyPlugin = PolicyPlugin.getPolicyPlugin(policyPluginData);
    +
    +        Plugin modelPlugin = ModelPlugin.getModelPlugin();
    +
    +        Simulation.builder()//
    +                .addPlugin(diseasePlugin)//
    +                .addPlugin(modelPlugin)//
    +                .addPlugin(policyPlugin)//
    +                .setOutputConsumer(new OutputConsumer_A()).build()//
    +                .execute();
    +    }
    +}
    +
    +
    +

    Released output objects are sent to the output consumer. In the current example, that consumer is an instance of the class OutputConsumer_A and it simply prints the object to the console:

    +
    +
    +

    Code Block 2.44: Output consumer A simply prints output to the console.

    +
    public class OutputConsumer_A implements Consumer<Object> {
    +
    +    @Override
    +    public void accept(Object t) {
    +        System.out.println(t);
    +    }
    +
    +}
    +
    +
    +

    At first glance this mechanism seems simple and not particularly useful. In practice, one rarely uses the simulation directly and instead favors the experiment which has a somewhat more sophisticated handling of output. With experiments, GCM is potentially using multiple threads to execute each simulation, so output handling must be threadsafe.

    +
    +

    2.10.1 Experiment Context

    +

    Just as the simulation supplies contexts, the experiment uses the ExperimentContext to give output consumers a view into the ongoing experiment. It gives each output consumer several capabilities:

    +
      +
    • Subscription to output by output class type
    • +
    • Subscription to the opening and closing of the experiment
    • +
    • Subscription to the opening and closing of each simulation
    • +
    • Scenario status information
    • +
    • Experiment and Scenario meta data
    • +
    +

    In Example_10_B, we bring back the dimensions from previous lessons and will excerpt just the main method:

    +
    +
    +

    Code Block 2.45: The experiment is now involved in the output process. A new output consumer is used that has access to scenario level information.

    +
    public static void main(String[] args) {
    +
    +    DiseasePluginData diseasePluginData = getDiseasePluginData();
    +    Plugin diseasePlugin = DiseasePlugin.getDiseasePlugin(diseasePluginData);
    +
    +    PolicyPluginData policyPluginData = getPolicyPluginData();
    +    Plugin policyPlugin = PolicyPlugin.getPolicyPlugin(policyPluginData);
    +
    +    Plugin modelPlugin = ModelPlugin.getModelPlugin();
    +
    +    Dimension r0Dimension = getR0Dimension();
    +
    +    Dimension policyDimension = getPolicyDimension();
    +
    +    ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()//
    +            .setThreadCount(4)//
    +            .build();
    +
    +    Experiment.builder()//
    +            .addPlugin(diseasePlugin)//
    +            .addPlugin(modelPlugin)//
    +            .addPlugin(policyPlugin)//
    +            .addDimension(r0Dimension)//
    +            .addDimension(policyDimension)//
    +            .addExperimentContextConsumer(new OutputConsumer_B())//
    +            .setExperimentParameterData(experimentParameterData)//
    +            .build()//
    +            .execute();
    +}
    +
    +
    +

    Like the simulation, the experiment is adding a consumer for output, but this time that consumer is “consuming” an experiment context. Once the consumer receives that context, it will use it to further subscribe to output and various experiment level events.

    +
    +
    +

    Code Block 2.46: Output consumer B has access to the experiment level data, so it prints the output to the console as before, but also adds the relevant scenario id.

    +
    public class OutputConsumer_B implements Consumer<ExperimentContext> {
    +
    +    @Override
    +    public void accept(ExperimentContext experimentContext) {
    +        experimentContext.subscribeToOutput(Object.class, this::handleOutput);
    +    }
    +
    +    private void handleOutput(ExperimentContext experimentContext, Integer scenarioId, Object output) {
    +        System.out.println("scenario " + scenarioId + ": " + output);
    +    }
    +}
    +
    +
    +

    The experiment can have any number of ExperimentContext consumers and initializes each at the beginning of its execution via the accept() method. In OuputConsumer_B, the only action the consumer takes is to subscribe to all output and have that output handled by the handleOutput() method. The resulting output shows the scenario id for each line:

    +
    +
    +
    +

    Figure 2.18: The output is still a bit scrambled, but each row now has the relevant scenario id.

    + + + + + + + + +
    +scenario 1: Model Actor initializing
    scenario 2: Model Actor initializing
    scenario 3: Model Actor initializing
    scenario 0: Model Actor initializing
    scenario 3: r0 = 1.5
    scenario 0: r0 = 1.5
    scenario 2: r0 = 2.5
    scenario 2: asymptomatic days = 4.0
    scenario 1: r0 = 2.0
    scenario 2: symptomatic days = 12.0
    scenario 1: asymptomatic days = 4.0
    scenario 0: asymptomatic days = 4.0
    scenario 2: school closing infection rate = 0.05
    scenario 3: asymptomatic days = 4.0
    scenario 2: distribute vaccine locally = false
    scenario 0: symptomatic days = 12.0
    scenario 1: symptomatic days = 12.0
    scenario 0: school closing infection rate = 0.05
    scenario 1: school closing infection rate = 0.05
    scenario 3: symptomatic days = 12.0
    scenario 3: school closing infection rate = 0.1
    scenario 1: distribute vaccine locally = false
    scenario 0: distribute vaccine locally = false
    scenario 3: distribute vaccine locally = false
    scenario 5: Model Actor initializing
    scenario 4: Model Actor initializing
    scenario 5: r0 = 2.5
    scenario 4: r0 = 2.0


    +
    +
    +
    +
    +

    Example_10_C switches the experiment context consumer to an instance of OuputConsumer_C which subscribes to all output types as well as the opening and closing of the experiment and all simulations (scenarios):

    +
    +
    +

    Code Block 2.47: The output consumer C demonstrates the broader life cycle of the experiment context by printing out experiment and scenario status while still printing output to the console.

    +
    public class OutputConsumer_C implements Consumer<ExperimentContext> {
    +
    +    @Override
    +    public void accept(ExperimentContext experimentContext) {
    +        experimentContext.subscribeToOutput(Object.class, this::handleOutput);
    +
    +        experimentContext.subscribeToExperimentOpen(this::handleExperimentOpen);
    +        experimentContext.subscribeToExperimentClose(this::handleExperimentClose);
    +
    +        experimentContext.subscribeToSimulationOpen(this::handleSimulationOpen);
    +        experimentContext.subscribeToSimulationClose(this::handleSimulationClose);
    +    }
    +
    +    private void handleOutput(ExperimentContext experimentContext, Integer scenarioId, Object output) {
    +        System.out.println("scenario " + scenarioId + ": " + output);
    +    }
    +
    +    private void handleExperimentOpen(ExperimentContext experimentContext) {
    +        System.out.println("the experiment is open");
    +    }
    +
    +    private void handleExperimentClose(ExperimentContext experimentContext) {
    +        System.out.println("the experiment is closed");
    +    }
    +
    +    private void handleSimulationOpen(ExperimentContext experimentContext, Integer scenarioId) {
    +        System.out.println("scenario " + scenarioId + " is open");
    +    }
    +
    +    private void handleSimulationClose(ExperimentContext experimentContext, Integer scenarioId) {
    +        System.out.println("scenario " + scenarioId + " is closed");
    +    }
    +}
    +
    +
    +

    The resulting output shows the usual released output along with the opening and closing of each simulation:

    +
    +
    +
    +

    Figure 2.19: The first output lines for OutputConsumer_C.

    + + + + + + + + +
    +
    the experiment is open
    scenario 0 is open
    scenario 1 is open
    scenario 2 is open
    scenario 3 is open
    scenario 0: Model Actor initializing
    scenario 2: Model Actor initializing
    scenario 1: Model Actor initializing
    scenario 2: r0 = 2.5
    scenario 3: Model Actor initializing
    scenario 2: asymptomatic days = 4.0
    scenario 1: r0 = 2.0
    scenario 0: r0 = 1.5
    scenario 2: symptomatic days = 12.0


    +
    +
    +
    +
    +
    +
    +
    +

    Figure 2.20: The last output lines for OutputConsumer_C.

    + + + + + + + + +
    +


    scenario 11: Model Actor initializing
    scenario 10: distribute vaccine locally = true
    scenario 11: r0 = 2.5
    scenario 11: asymptomatic days = 4.0
    scenario 10 is closed
    scenario 11: symptomatic days = 12.0
    scenario 11: school closing infection rate = 0.1
    scenario 11: distribute vaccine locally = true
    scenario 11 is closed
    the experiment is closed
    +
    +
    +
    +
    +

    In the final example, OuputConsumer_D, we drop the output handling and demonstrate that the meta data used to build the dimensions of the experiment can be retrieved from the experiment context and used for reporting:

    +
    +
    +

    Code Block 2.48: OutputConsumer_D demonstrates that the meta data collected from the dimensions is available from the experiment context. Thus output can be associated with the scenario’s meta data.

    +
    public class OutputConsumer_D implements Consumer<ExperimentContext> {
    +
    +    @Override
    +    public void accept(ExperimentContext experimentContext) {
    +        experimentContext.subscribeToExperimentOpen(this::handleExperimentOpen);
    +        experimentContext.subscribeToSimulationOpen(this::handleSimulationOpen);
    +    }
    +
    +    private void handleExperimentOpen(ExperimentContext experimentContext) {
    +
    +        StringJoiner joiner = new StringJoiner("\t", "", "");
    +        joiner.add("scenario");
    +        experimentContext.getExperimentMetaData().forEach(joiner::add);
    +
    +        System.out.println(joiner);
    +    }
    +
    +    private void handleSimulationOpen(ExperimentContext experimentContext, Integer scenarioId) {
    +
    +        StringJoiner joiner = new StringJoiner("\t", "", "");
    +        joiner.add(scenarioId.toString());
    +        experimentContext.getScenarioMetaData(scenarioId).forEach(joiner::add);
    +
    +        System.out.println(joiner);
    +    }
    +
    +}
    +
    +
    +

    The resulting output shows for each scenario the meta-data that defines that scenario:

    +
    +
    +
    +

    Figure 2.21: The output of consumer D showing the scenario meta data for the twelve scenarios.

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +scenario + +r0 + +school_closing_infection_rate + +distribute_vaccine_locally +
    +0 + +1.5 + +0.05 + +FALSE +
    +1 + +2.0 + +0.05 + +FALSE +
    +2 + +2.5 + +0.05 + +FALSE +
    +3 + +1.5 + +0.10 + +FALSE +
    +4 + +2.0 + +0.10 + +FALSE +
    +5 + +2.5 + +0.10 + +FALSE +
    +6 + +1.5 + +0.05 + +TRUE +
    +7 + +2.0 + +0.05 + +TRUE +
    +8 + +2.5 + +0.05 + +TRUE +
    +9 + +1.5 + +0.10 + +TRUE +
    +10 + +2.0 + +0.10 + +TRUE +
    +11 + +2.5 + +0.10 + +TRUE +
    +
    +
    +
    +
    +

    Recall that as the experiment executes, it utilizes multiple threads to execute the individual scenarios. Thus every experiment context consumer must be threadsafe. We have accomplished this by making each such consumer stateless. In practice, it is often necessary for experiment context consumers to be stateful and this can involve careful consideration of the use of synchronization and other concurrency issues. Fortunately, GCM provides a reporting plugin that deals with these issues and provides a general method for producing tabular reports.

    + + +
    +
    + +
    + + +
    + + + + \ No newline at end of file diff --git a/doc/ch02-GettingStarted_files/figure-html/fig-testthis2-1.png b/doc/ch02-GettingStarted_files/figure-html/fig-testthis2-1.png new file mode 100644 index 000000000..ec14ebc8f Binary files /dev/null and b/doc/ch02-GettingStarted_files/figure-html/fig-testthis2-1.png differ diff --git a/doc/ch02-GettingStarted_files/figure-html/plotcars-1.png b/doc/ch02-GettingStarted_files/figure-html/plotcars-1.png new file mode 100644 index 000000000..ec14ebc8f Binary files /dev/null and b/doc/ch02-GettingStarted_files/figure-html/plotcars-1.png differ diff --git a/doc/ch02-GettingStarted_files/libs/bootstrap/bootstrap-icons.css b/doc/ch02-GettingStarted_files/libs/bootstrap/bootstrap-icons.css new file mode 100644 index 000000000..f51d04bc9 --- /dev/null +++ b/doc/ch02-GettingStarted_files/libs/bootstrap/bootstrap-icons.css @@ -0,0 +1,1704 @@ +@font-face { + font-family: "bootstrap-icons"; + src: +url("./bootstrap-icons.woff?524846017b983fc8ded9325d94ed40f3") format("woff"); +} + +.bi::before, +[class^="bi-"]::before, +[class*=" bi-"]::before { + display: inline-block; + font-family: bootstrap-icons !important; + font-style: normal; + font-weight: normal !important; + font-variant: normal; + text-transform: none; + line-height: 1; + vertical-align: -.125em; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.bi-123::before { content: "\f67f"; } +.bi-alarm-fill::before { content: "\f101"; } +.bi-alarm::before { content: "\f102"; } +.bi-align-bottom::before { content: "\f103"; } +.bi-align-center::before { content: "\f104"; } +.bi-align-end::before { content: "\f105"; } +.bi-align-middle::before { content: "\f106"; } +.bi-align-start::before { content: "\f107"; } +.bi-align-top::before { content: "\f108"; } +.bi-alt::before { content: "\f109"; } +.bi-app-indicator::before { content: "\f10a"; } +.bi-app::before { content: "\f10b"; } +.bi-archive-fill::before { content: "\f10c"; } +.bi-archive::before { content: "\f10d"; } +.bi-arrow-90deg-down::before { content: "\f10e"; } +.bi-arrow-90deg-left::before { content: "\f10f"; } +.bi-arrow-90deg-right::before { content: "\f110"; } +.bi-arrow-90deg-up::before { content: "\f111"; } +.bi-arrow-bar-down::before { content: "\f112"; } +.bi-arrow-bar-left::before { content: "\f113"; } +.bi-arrow-bar-right::before { content: "\f114"; } +.bi-arrow-bar-up::before { content: "\f115"; } +.bi-arrow-clockwise::before { content: "\f116"; } +.bi-arrow-counterclockwise::before { content: "\f117"; } +.bi-arrow-down-circle-fill::before { content: "\f118"; } +.bi-arrow-down-circle::before { content: "\f119"; } +.bi-arrow-down-left-circle-fill::before { content: "\f11a"; } +.bi-arrow-down-left-circle::before { content: "\f11b"; } +.bi-arrow-down-left-square-fill::before { content: "\f11c"; } +.bi-arrow-down-left-square::before { content: "\f11d"; } +.bi-arrow-down-left::before { content: "\f11e"; } +.bi-arrow-down-right-circle-fill::before { content: "\f11f"; } +.bi-arrow-down-right-circle::before { content: "\f120"; } +.bi-arrow-down-right-square-fill::before { content: "\f121"; } +.bi-arrow-down-right-square::before { content: "\f122"; } +.bi-arrow-down-right::before { content: "\f123"; } +.bi-arrow-down-short::before { content: "\f124"; } +.bi-arrow-down-square-fill::before { content: "\f125"; } +.bi-arrow-down-square::before { content: "\f126"; } +.bi-arrow-down-up::before { content: "\f127"; } +.bi-arrow-down::before { content: "\f128"; } +.bi-arrow-left-circle-fill::before { content: "\f129"; } +.bi-arrow-left-circle::before { content: "\f12a"; } +.bi-arrow-left-right::before { content: "\f12b"; } +.bi-arrow-left-short::before { content: "\f12c"; } +.bi-arrow-left-square-fill::before { content: "\f12d"; } +.bi-arrow-left-square::before { content: "\f12e"; } +.bi-arrow-left::before { content: "\f12f"; } +.bi-arrow-repeat::before { content: "\f130"; } +.bi-arrow-return-left::before { content: "\f131"; } +.bi-arrow-return-right::before { content: "\f132"; } +.bi-arrow-right-circle-fill::before { content: "\f133"; } +.bi-arrow-right-circle::before { content: "\f134"; } +.bi-arrow-right-short::before { content: "\f135"; } +.bi-arrow-right-square-fill::before { content: "\f136"; } +.bi-arrow-right-square::before { content: "\f137"; } +.bi-arrow-right::before { content: "\f138"; } +.bi-arrow-up-circle-fill::before { content: "\f139"; } +.bi-arrow-up-circle::before { content: "\f13a"; } +.bi-arrow-up-left-circle-fill::before { content: "\f13b"; } +.bi-arrow-up-left-circle::before { content: "\f13c"; } +.bi-arrow-up-left-square-fill::before { content: "\f13d"; } +.bi-arrow-up-left-square::before { content: "\f13e"; } +.bi-arrow-up-left::before { content: "\f13f"; } +.bi-arrow-up-right-circle-fill::before { content: "\f140"; } +.bi-arrow-up-right-circle::before { content: "\f141"; } +.bi-arrow-up-right-square-fill::before { content: "\f142"; } +.bi-arrow-up-right-square::before { content: "\f143"; } +.bi-arrow-up-right::before { content: "\f144"; } +.bi-arrow-up-short::before { content: "\f145"; } +.bi-arrow-up-square-fill::before { content: "\f146"; } +.bi-arrow-up-square::before { content: "\f147"; } +.bi-arrow-up::before { content: "\f148"; } +.bi-arrows-angle-contract::before { content: "\f149"; } +.bi-arrows-angle-expand::before { content: "\f14a"; } +.bi-arrows-collapse::before { content: "\f14b"; } +.bi-arrows-expand::before { content: "\f14c"; } +.bi-arrows-fullscreen::before { content: "\f14d"; } +.bi-arrows-move::before { content: "\f14e"; } +.bi-aspect-ratio-fill::before { content: "\f14f"; } +.bi-aspect-ratio::before { content: "\f150"; } +.bi-asterisk::before { content: "\f151"; } +.bi-at::before { content: "\f152"; } +.bi-award-fill::before { content: "\f153"; } +.bi-award::before { content: "\f154"; } +.bi-back::before { content: "\f155"; } +.bi-backspace-fill::before { content: "\f156"; } +.bi-backspace-reverse-fill::before { content: "\f157"; } +.bi-backspace-reverse::before { content: "\f158"; } +.bi-backspace::before { content: "\f159"; } +.bi-badge-3d-fill::before { content: "\f15a"; } +.bi-badge-3d::before { content: "\f15b"; } +.bi-badge-4k-fill::before { content: "\f15c"; } +.bi-badge-4k::before { content: "\f15d"; } +.bi-badge-8k-fill::before { content: "\f15e"; } +.bi-badge-8k::before { content: "\f15f"; } +.bi-badge-ad-fill::before { content: "\f160"; } +.bi-badge-ad::before { content: "\f161"; } +.bi-badge-ar-fill::before { content: "\f162"; } +.bi-badge-ar::before { content: "\f163"; } +.bi-badge-cc-fill::before { content: "\f164"; } +.bi-badge-cc::before { content: "\f165"; } +.bi-badge-hd-fill::before { content: "\f166"; } +.bi-badge-hd::before { content: "\f167"; } +.bi-badge-tm-fill::before { content: "\f168"; } +.bi-badge-tm::before { content: "\f169"; } +.bi-badge-vo-fill::before { content: "\f16a"; } +.bi-badge-vo::before { content: "\f16b"; } +.bi-badge-vr-fill::before { content: "\f16c"; } +.bi-badge-vr::before { content: "\f16d"; } +.bi-badge-wc-fill::before { content: "\f16e"; } +.bi-badge-wc::before { content: "\f16f"; } +.bi-bag-check-fill::before { content: "\f170"; } +.bi-bag-check::before { content: "\f171"; } +.bi-bag-dash-fill::before { content: "\f172"; } +.bi-bag-dash::before { content: "\f173"; } +.bi-bag-fill::before { content: "\f174"; } +.bi-bag-plus-fill::before { content: "\f175"; } +.bi-bag-plus::before { content: "\f176"; } +.bi-bag-x-fill::before { content: "\f177"; } +.bi-bag-x::before { content: "\f178"; } +.bi-bag::before { content: "\f179"; } +.bi-bar-chart-fill::before { content: "\f17a"; } +.bi-bar-chart-line-fill::before { content: "\f17b"; } +.bi-bar-chart-line::before { content: "\f17c"; } +.bi-bar-chart-steps::before { content: "\f17d"; } +.bi-bar-chart::before { content: "\f17e"; } +.bi-basket-fill::before { content: "\f17f"; } +.bi-basket::before { content: "\f180"; } +.bi-basket2-fill::before { content: "\f181"; } +.bi-basket2::before { content: "\f182"; } +.bi-basket3-fill::before { content: "\f183"; } +.bi-basket3::before { content: "\f184"; } +.bi-battery-charging::before { content: "\f185"; } +.bi-battery-full::before { content: "\f186"; } +.bi-battery-half::before { content: "\f187"; } +.bi-battery::before { content: "\f188"; } +.bi-bell-fill::before { content: "\f189"; } +.bi-bell::before { content: "\f18a"; } +.bi-bezier::before { content: "\f18b"; } +.bi-bezier2::before { content: "\f18c"; } +.bi-bicycle::before { content: "\f18d"; } +.bi-binoculars-fill::before { content: "\f18e"; } +.bi-binoculars::before { content: "\f18f"; } +.bi-blockquote-left::before { content: "\f190"; } +.bi-blockquote-right::before { content: "\f191"; } +.bi-book-fill::before { content: "\f192"; } +.bi-book-half::before { content: "\f193"; } +.bi-book::before { content: "\f194"; } +.bi-bookmark-check-fill::before { content: "\f195"; } +.bi-bookmark-check::before { content: "\f196"; } +.bi-bookmark-dash-fill::before { content: "\f197"; } +.bi-bookmark-dash::before { content: "\f198"; } +.bi-bookmark-fill::before { content: "\f199"; } +.bi-bookmark-heart-fill::before { content: "\f19a"; } +.bi-bookmark-heart::before { content: "\f19b"; } +.bi-bookmark-plus-fill::before { content: "\f19c"; } +.bi-bookmark-plus::before { content: "\f19d"; } +.bi-bookmark-star-fill::before { content: "\f19e"; } +.bi-bookmark-star::before { content: "\f19f"; } +.bi-bookmark-x-fill::before { content: "\f1a0"; } +.bi-bookmark-x::before { content: "\f1a1"; } +.bi-bookmark::before { content: "\f1a2"; } +.bi-bookmarks-fill::before { content: "\f1a3"; } +.bi-bookmarks::before { content: "\f1a4"; } +.bi-bookshelf::before { content: "\f1a5"; } +.bi-bootstrap-fill::before { content: "\f1a6"; } +.bi-bootstrap-reboot::before { content: "\f1a7"; } +.bi-bootstrap::before { content: "\f1a8"; } +.bi-border-all::before { content: "\f1a9"; } +.bi-border-bottom::before { content: "\f1aa"; } +.bi-border-center::before { content: "\f1ab"; } +.bi-border-inner::before { content: "\f1ac"; } +.bi-border-left::before { content: "\f1ad"; } +.bi-border-middle::before { content: "\f1ae"; } +.bi-border-outer::before { content: "\f1af"; } +.bi-border-right::before { content: "\f1b0"; } +.bi-border-style::before { content: "\f1b1"; } +.bi-border-top::before { content: "\f1b2"; } +.bi-border-width::before { content: "\f1b3"; } +.bi-border::before { content: "\f1b4"; } +.bi-bounding-box-circles::before { content: "\f1b5"; } +.bi-bounding-box::before { content: "\f1b6"; } +.bi-box-arrow-down-left::before { content: "\f1b7"; } +.bi-box-arrow-down-right::before { content: "\f1b8"; } +.bi-box-arrow-down::before { content: "\f1b9"; } +.bi-box-arrow-in-down-left::before { content: "\f1ba"; } +.bi-box-arrow-in-down-right::before { content: "\f1bb"; } +.bi-box-arrow-in-down::before { content: "\f1bc"; } +.bi-box-arrow-in-left::before { content: "\f1bd"; } +.bi-box-arrow-in-right::before { content: "\f1be"; } +.bi-box-arrow-in-up-left::before { content: "\f1bf"; } +.bi-box-arrow-in-up-right::before { content: "\f1c0"; } +.bi-box-arrow-in-up::before { content: "\f1c1"; } +.bi-box-arrow-left::before { content: "\f1c2"; } +.bi-box-arrow-right::before { content: "\f1c3"; } +.bi-box-arrow-up-left::before { content: "\f1c4"; } +.bi-box-arrow-up-right::before { content: "\f1c5"; } +.bi-box-arrow-up::before { content: "\f1c6"; } +.bi-box-seam::before { content: "\f1c7"; } +.bi-box::before { content: "\f1c8"; } +.bi-braces::before { content: "\f1c9"; } +.bi-bricks::before { content: "\f1ca"; } +.bi-briefcase-fill::before { content: "\f1cb"; } +.bi-briefcase::before { content: "\f1cc"; } +.bi-brightness-alt-high-fill::before { content: "\f1cd"; } +.bi-brightness-alt-high::before { content: "\f1ce"; } +.bi-brightness-alt-low-fill::before { content: "\f1cf"; } +.bi-brightness-alt-low::before { content: "\f1d0"; } +.bi-brightness-high-fill::before { content: "\f1d1"; } +.bi-brightness-high::before { content: "\f1d2"; } +.bi-brightness-low-fill::before { content: "\f1d3"; } +.bi-brightness-low::before { content: "\f1d4"; } +.bi-broadcast-pin::before { content: "\f1d5"; } +.bi-broadcast::before { content: "\f1d6"; } +.bi-brush-fill::before { content: "\f1d7"; } +.bi-brush::before { content: "\f1d8"; } +.bi-bucket-fill::before { content: "\f1d9"; } +.bi-bucket::before { content: "\f1da"; } +.bi-bug-fill::before { content: "\f1db"; } +.bi-bug::before { content: "\f1dc"; } +.bi-building::before { content: "\f1dd"; } +.bi-bullseye::before { content: "\f1de"; } +.bi-calculator-fill::before { content: "\f1df"; } +.bi-calculator::before { content: "\f1e0"; } +.bi-calendar-check-fill::before { content: "\f1e1"; } +.bi-calendar-check::before { content: "\f1e2"; } +.bi-calendar-date-fill::before { content: "\f1e3"; } +.bi-calendar-date::before { content: "\f1e4"; } +.bi-calendar-day-fill::before { content: "\f1e5"; } +.bi-calendar-day::before { content: "\f1e6"; } +.bi-calendar-event-fill::before { content: "\f1e7"; } +.bi-calendar-event::before { content: "\f1e8"; } +.bi-calendar-fill::before { content: "\f1e9"; } +.bi-calendar-minus-fill::before { content: "\f1ea"; } +.bi-calendar-minus::before { content: "\f1eb"; } +.bi-calendar-month-fill::before { content: "\f1ec"; } +.bi-calendar-month::before { content: "\f1ed"; } +.bi-calendar-plus-fill::before { content: "\f1ee"; } +.bi-calendar-plus::before { content: "\f1ef"; } +.bi-calendar-range-fill::before { content: "\f1f0"; } +.bi-calendar-range::before { content: "\f1f1"; } +.bi-calendar-week-fill::before { content: "\f1f2"; } +.bi-calendar-week::before { content: "\f1f3"; } +.bi-calendar-x-fill::before { content: "\f1f4"; } +.bi-calendar-x::before { content: "\f1f5"; } +.bi-calendar::before { content: "\f1f6"; } +.bi-calendar2-check-fill::before { content: "\f1f7"; } +.bi-calendar2-check::before { content: "\f1f8"; } +.bi-calendar2-date-fill::before { content: "\f1f9"; } +.bi-calendar2-date::before { content: "\f1fa"; } +.bi-calendar2-day-fill::before { content: "\f1fb"; } +.bi-calendar2-day::before { content: "\f1fc"; } +.bi-calendar2-event-fill::before { content: "\f1fd"; } +.bi-calendar2-event::before { content: "\f1fe"; } +.bi-calendar2-fill::before { content: "\f1ff"; } +.bi-calendar2-minus-fill::before { content: "\f200"; } +.bi-calendar2-minus::before { content: "\f201"; } +.bi-calendar2-month-fill::before { content: "\f202"; } +.bi-calendar2-month::before { content: "\f203"; } +.bi-calendar2-plus-fill::before { content: "\f204"; } +.bi-calendar2-plus::before { content: "\f205"; } +.bi-calendar2-range-fill::before { content: "\f206"; } +.bi-calendar2-range::before { content: "\f207"; } +.bi-calendar2-week-fill::before { content: "\f208"; } +.bi-calendar2-week::before { content: "\f209"; } +.bi-calendar2-x-fill::before { content: "\f20a"; } +.bi-calendar2-x::before { content: "\f20b"; } +.bi-calendar2::before { content: "\f20c"; } +.bi-calendar3-event-fill::before { content: "\f20d"; } +.bi-calendar3-event::before { content: "\f20e"; } +.bi-calendar3-fill::before { content: "\f20f"; } +.bi-calendar3-range-fill::before { content: "\f210"; } +.bi-calendar3-range::before { content: "\f211"; } +.bi-calendar3-week-fill::before { content: "\f212"; } +.bi-calendar3-week::before { content: "\f213"; } +.bi-calendar3::before { content: "\f214"; } +.bi-calendar4-event::before { content: "\f215"; } +.bi-calendar4-range::before { content: "\f216"; } +.bi-calendar4-week::before { content: "\f217"; } +.bi-calendar4::before { content: "\f218"; } +.bi-camera-fill::before { content: "\f219"; } +.bi-camera-reels-fill::before { content: "\f21a"; } +.bi-camera-reels::before { content: "\f21b"; } +.bi-camera-video-fill::before { content: "\f21c"; } +.bi-camera-video-off-fill::before { content: "\f21d"; } +.bi-camera-video-off::before { content: "\f21e"; } +.bi-camera-video::before { content: "\f21f"; } +.bi-camera::before { content: "\f220"; } +.bi-camera2::before { content: "\f221"; } +.bi-capslock-fill::before { content: "\f222"; } +.bi-capslock::before { content: "\f223"; } +.bi-card-checklist::before { content: "\f224"; } +.bi-card-heading::before { content: "\f225"; } +.bi-card-image::before { content: "\f226"; } +.bi-card-list::before { content: "\f227"; } +.bi-card-text::before { content: "\f228"; } +.bi-caret-down-fill::before { content: "\f229"; } +.bi-caret-down-square-fill::before { content: "\f22a"; } +.bi-caret-down-square::before { content: "\f22b"; } +.bi-caret-down::before { content: "\f22c"; } +.bi-caret-left-fill::before { content: "\f22d"; } +.bi-caret-left-square-fill::before { content: "\f22e"; } +.bi-caret-left-square::before { content: "\f22f"; } +.bi-caret-left::before { content: "\f230"; } +.bi-caret-right-fill::before { content: "\f231"; } +.bi-caret-right-square-fill::before { content: "\f232"; } +.bi-caret-right-square::before { content: "\f233"; } +.bi-caret-right::before { content: "\f234"; } +.bi-caret-up-fill::before { content: "\f235"; } +.bi-caret-up-square-fill::before { content: "\f236"; } +.bi-caret-up-square::before { content: "\f237"; } +.bi-caret-up::before { content: "\f238"; } +.bi-cart-check-fill::before { content: "\f239"; } +.bi-cart-check::before { content: "\f23a"; } +.bi-cart-dash-fill::before { content: "\f23b"; } +.bi-cart-dash::before { content: "\f23c"; } +.bi-cart-fill::before { content: "\f23d"; } +.bi-cart-plus-fill::before { content: "\f23e"; } +.bi-cart-plus::before { content: "\f23f"; } +.bi-cart-x-fill::before { content: "\f240"; } +.bi-cart-x::before { content: "\f241"; } +.bi-cart::before { content: "\f242"; } +.bi-cart2::before { content: "\f243"; } +.bi-cart3::before { content: "\f244"; } +.bi-cart4::before { content: "\f245"; } +.bi-cash-stack::before { content: "\f246"; } +.bi-cash::before { content: "\f247"; } +.bi-cast::before { content: "\f248"; } +.bi-chat-dots-fill::before { content: "\f249"; } +.bi-chat-dots::before { content: "\f24a"; } +.bi-chat-fill::before { content: "\f24b"; } +.bi-chat-left-dots-fill::before { content: "\f24c"; } +.bi-chat-left-dots::before { content: "\f24d"; } +.bi-chat-left-fill::before { content: "\f24e"; } +.bi-chat-left-quote-fill::before { content: "\f24f"; } +.bi-chat-left-quote::before { content: "\f250"; } +.bi-chat-left-text-fill::before { content: "\f251"; } +.bi-chat-left-text::before { content: "\f252"; } +.bi-chat-left::before { content: "\f253"; } +.bi-chat-quote-fill::before { content: "\f254"; } +.bi-chat-quote::before { content: "\f255"; } +.bi-chat-right-dots-fill::before { content: "\f256"; } +.bi-chat-right-dots::before { content: "\f257"; } +.bi-chat-right-fill::before { content: "\f258"; } +.bi-chat-right-quote-fill::before { content: "\f259"; } +.bi-chat-right-quote::before { content: "\f25a"; } +.bi-chat-right-text-fill::before { content: "\f25b"; } +.bi-chat-right-text::before { content: "\f25c"; } +.bi-chat-right::before { content: "\f25d"; } +.bi-chat-square-dots-fill::before { content: "\f25e"; } +.bi-chat-square-dots::before { content: "\f25f"; } +.bi-chat-square-fill::before { content: "\f260"; } +.bi-chat-square-quote-fill::before { content: "\f261"; } +.bi-chat-square-quote::before { content: "\f262"; } +.bi-chat-square-text-fill::before { content: "\f263"; } +.bi-chat-square-text::before { content: "\f264"; } +.bi-chat-square::before { content: "\f265"; } +.bi-chat-text-fill::before { content: "\f266"; } +.bi-chat-text::before { content: "\f267"; } +.bi-chat::before { content: "\f268"; } +.bi-check-all::before { content: "\f269"; } +.bi-check-circle-fill::before { content: "\f26a"; } +.bi-check-circle::before { content: "\f26b"; } +.bi-check-square-fill::before { content: "\f26c"; } +.bi-check-square::before { content: "\f26d"; } +.bi-check::before { content: "\f26e"; } +.bi-check2-all::before { content: "\f26f"; } +.bi-check2-circle::before { content: "\f270"; } +.bi-check2-square::before { content: "\f271"; } +.bi-check2::before { content: "\f272"; } +.bi-chevron-bar-contract::before { content: "\f273"; } +.bi-chevron-bar-down::before { content: "\f274"; } +.bi-chevron-bar-expand::before { content: "\f275"; } +.bi-chevron-bar-left::before { content: "\f276"; } +.bi-chevron-bar-right::before { content: "\f277"; } +.bi-chevron-bar-up::before { content: "\f278"; } +.bi-chevron-compact-down::before { content: "\f279"; } +.bi-chevron-compact-left::before { content: "\f27a"; } +.bi-chevron-compact-right::before { content: "\f27b"; } +.bi-chevron-compact-up::before { content: "\f27c"; } +.bi-chevron-contract::before { content: "\f27d"; } +.bi-chevron-double-down::before { content: "\f27e"; } +.bi-chevron-double-left::before { content: "\f27f"; } +.bi-chevron-double-right::before { content: "\f280"; } +.bi-chevron-double-up::before { content: "\f281"; } +.bi-chevron-down::before { content: "\f282"; } +.bi-chevron-expand::before { content: "\f283"; } +.bi-chevron-left::before { content: "\f284"; } +.bi-chevron-right::before { content: "\f285"; } +.bi-chevron-up::before { content: "\f286"; } +.bi-circle-fill::before { content: "\f287"; } +.bi-circle-half::before { content: "\f288"; } +.bi-circle-square::before { content: "\f289"; } +.bi-circle::before { content: "\f28a"; } +.bi-clipboard-check::before { content: "\f28b"; } +.bi-clipboard-data::before { content: "\f28c"; } +.bi-clipboard-minus::before { content: "\f28d"; } +.bi-clipboard-plus::before { content: "\f28e"; } +.bi-clipboard-x::before { content: "\f28f"; } +.bi-clipboard::before { content: "\f290"; } +.bi-clock-fill::before { content: "\f291"; } +.bi-clock-history::before { content: "\f292"; } +.bi-clock::before { content: "\f293"; } +.bi-cloud-arrow-down-fill::before { content: "\f294"; } +.bi-cloud-arrow-down::before { content: "\f295"; } +.bi-cloud-arrow-up-fill::before { content: "\f296"; } +.bi-cloud-arrow-up::before { content: "\f297"; } +.bi-cloud-check-fill::before { content: "\f298"; } +.bi-cloud-check::before { content: "\f299"; } +.bi-cloud-download-fill::before { content: "\f29a"; } +.bi-cloud-download::before { content: "\f29b"; } +.bi-cloud-drizzle-fill::before { content: "\f29c"; } +.bi-cloud-drizzle::before { content: "\f29d"; } +.bi-cloud-fill::before { content: "\f29e"; } +.bi-cloud-fog-fill::before { content: "\f29f"; } +.bi-cloud-fog::before { content: "\f2a0"; } +.bi-cloud-fog2-fill::before { content: "\f2a1"; } +.bi-cloud-fog2::before { content: "\f2a2"; } +.bi-cloud-hail-fill::before { content: "\f2a3"; } +.bi-cloud-hail::before { content: "\f2a4"; } +.bi-cloud-haze-1::before { content: "\f2a5"; } +.bi-cloud-haze-fill::before { content: "\f2a6"; } +.bi-cloud-haze::before { content: "\f2a7"; } +.bi-cloud-haze2-fill::before { content: "\f2a8"; } +.bi-cloud-lightning-fill::before { content: "\f2a9"; } +.bi-cloud-lightning-rain-fill::before { content: "\f2aa"; } +.bi-cloud-lightning-rain::before { content: "\f2ab"; } +.bi-cloud-lightning::before { content: "\f2ac"; } +.bi-cloud-minus-fill::before { content: "\f2ad"; } +.bi-cloud-minus::before { content: "\f2ae"; } +.bi-cloud-moon-fill::before { content: "\f2af"; } +.bi-cloud-moon::before { content: "\f2b0"; } +.bi-cloud-plus-fill::before { content: "\f2b1"; } +.bi-cloud-plus::before { content: "\f2b2"; } +.bi-cloud-rain-fill::before { content: "\f2b3"; } +.bi-cloud-rain-heavy-fill::before { content: "\f2b4"; } +.bi-cloud-rain-heavy::before { content: "\f2b5"; } +.bi-cloud-rain::before { content: "\f2b6"; } +.bi-cloud-slash-fill::before { content: "\f2b7"; } +.bi-cloud-slash::before { content: "\f2b8"; } +.bi-cloud-sleet-fill::before { content: "\f2b9"; } +.bi-cloud-sleet::before { content: "\f2ba"; } +.bi-cloud-snow-fill::before { content: "\f2bb"; } +.bi-cloud-snow::before { content: "\f2bc"; } +.bi-cloud-sun-fill::before { content: "\f2bd"; } +.bi-cloud-sun::before { content: "\f2be"; } +.bi-cloud-upload-fill::before { content: "\f2bf"; } +.bi-cloud-upload::before { content: "\f2c0"; } +.bi-cloud::before { content: "\f2c1"; } +.bi-clouds-fill::before { content: "\f2c2"; } +.bi-clouds::before { content: "\f2c3"; } +.bi-cloudy-fill::before { content: "\f2c4"; } +.bi-cloudy::before { content: "\f2c5"; } +.bi-code-slash::before { content: "\f2c6"; } +.bi-code-square::before { content: "\f2c7"; } +.bi-code::before { content: "\f2c8"; } +.bi-collection-fill::before { content: "\f2c9"; } +.bi-collection-play-fill::before { content: "\f2ca"; } +.bi-collection-play::before { content: "\f2cb"; } +.bi-collection::before { content: "\f2cc"; } +.bi-columns-gap::before { content: "\f2cd"; } +.bi-columns::before { content: "\f2ce"; } +.bi-command::before { content: "\f2cf"; } +.bi-compass-fill::before { content: "\f2d0"; } +.bi-compass::before { content: "\f2d1"; } +.bi-cone-striped::before { content: "\f2d2"; } +.bi-cone::before { content: "\f2d3"; } +.bi-controller::before { content: "\f2d4"; } +.bi-cpu-fill::before { content: "\f2d5"; } +.bi-cpu::before { content: "\f2d6"; } +.bi-credit-card-2-back-fill::before { content: "\f2d7"; } +.bi-credit-card-2-back::before { content: "\f2d8"; } +.bi-credit-card-2-front-fill::before { content: "\f2d9"; } +.bi-credit-card-2-front::before { content: "\f2da"; } +.bi-credit-card-fill::before { content: "\f2db"; } +.bi-credit-card::before { content: "\f2dc"; } +.bi-crop::before { content: "\f2dd"; } +.bi-cup-fill::before { content: "\f2de"; } +.bi-cup-straw::before { content: "\f2df"; } +.bi-cup::before { content: "\f2e0"; } +.bi-cursor-fill::before { content: "\f2e1"; } +.bi-cursor-text::before { content: "\f2e2"; } +.bi-cursor::before { content: "\f2e3"; } +.bi-dash-circle-dotted::before { content: "\f2e4"; } +.bi-dash-circle-fill::before { content: "\f2e5"; } +.bi-dash-circle::before { content: "\f2e6"; } +.bi-dash-square-dotted::before { content: "\f2e7"; } +.bi-dash-square-fill::before { content: "\f2e8"; } +.bi-dash-square::before { content: "\f2e9"; } +.bi-dash::before { content: "\f2ea"; } +.bi-diagram-2-fill::before { content: "\f2eb"; } +.bi-diagram-2::before { content: "\f2ec"; } +.bi-diagram-3-fill::before { content: "\f2ed"; } +.bi-diagram-3::before { content: "\f2ee"; } +.bi-diamond-fill::before { content: "\f2ef"; } +.bi-diamond-half::before { content: "\f2f0"; } +.bi-diamond::before { content: "\f2f1"; } +.bi-dice-1-fill::before { content: "\f2f2"; } +.bi-dice-1::before { content: "\f2f3"; } +.bi-dice-2-fill::before { content: "\f2f4"; } +.bi-dice-2::before { content: "\f2f5"; } +.bi-dice-3-fill::before { content: "\f2f6"; } +.bi-dice-3::before { content: "\f2f7"; } +.bi-dice-4-fill::before { content: "\f2f8"; } +.bi-dice-4::before { content: "\f2f9"; } +.bi-dice-5-fill::before { content: "\f2fa"; } +.bi-dice-5::before { content: "\f2fb"; } +.bi-dice-6-fill::before { content: "\f2fc"; } +.bi-dice-6::before { content: "\f2fd"; } +.bi-disc-fill::before { content: "\f2fe"; } +.bi-disc::before { content: "\f2ff"; } +.bi-discord::before { content: "\f300"; } +.bi-display-fill::before { content: "\f301"; } +.bi-display::before { content: "\f302"; } +.bi-distribute-horizontal::before { content: "\f303"; } +.bi-distribute-vertical::before { content: "\f304"; } +.bi-door-closed-fill::before { content: "\f305"; } +.bi-door-closed::before { content: "\f306"; } +.bi-door-open-fill::before { content: "\f307"; } +.bi-door-open::before { content: "\f308"; } +.bi-dot::before { content: "\f309"; } +.bi-download::before { content: "\f30a"; } +.bi-droplet-fill::before { content: "\f30b"; } +.bi-droplet-half::before { content: "\f30c"; } +.bi-droplet::before { content: "\f30d"; } +.bi-earbuds::before { content: "\f30e"; } +.bi-easel-fill::before { content: "\f30f"; } +.bi-easel::before { content: "\f310"; } +.bi-egg-fill::before { content: "\f311"; } +.bi-egg-fried::before { content: "\f312"; } +.bi-egg::before { content: "\f313"; } +.bi-eject-fill::before { content: "\f314"; } +.bi-eject::before { content: "\f315"; } +.bi-emoji-angry-fill::before { content: "\f316"; } +.bi-emoji-angry::before { content: "\f317"; } +.bi-emoji-dizzy-fill::before { content: "\f318"; } +.bi-emoji-dizzy::before { content: "\f319"; } +.bi-emoji-expressionless-fill::before { content: "\f31a"; } +.bi-emoji-expressionless::before { content: "\f31b"; } +.bi-emoji-frown-fill::before { content: "\f31c"; } +.bi-emoji-frown::before { content: "\f31d"; } +.bi-emoji-heart-eyes-fill::before { content: "\f31e"; } +.bi-emoji-heart-eyes::before { content: "\f31f"; } +.bi-emoji-laughing-fill::before { content: "\f320"; } +.bi-emoji-laughing::before { content: "\f321"; } +.bi-emoji-neutral-fill::before { content: "\f322"; } +.bi-emoji-neutral::before { content: "\f323"; } +.bi-emoji-smile-fill::before { content: "\f324"; } +.bi-emoji-smile-upside-down-fill::before { content: "\f325"; } +.bi-emoji-smile-upside-down::before { content: "\f326"; } +.bi-emoji-smile::before { content: "\f327"; } +.bi-emoji-sunglasses-fill::before { content: "\f328"; } +.bi-emoji-sunglasses::before { content: "\f329"; } +.bi-emoji-wink-fill::before { content: "\f32a"; } +.bi-emoji-wink::before { content: "\f32b"; } +.bi-envelope-fill::before { content: "\f32c"; } +.bi-envelope-open-fill::before { content: "\f32d"; } +.bi-envelope-open::before { content: "\f32e"; } +.bi-envelope::before { content: "\f32f"; } +.bi-eraser-fill::before { content: "\f330"; } +.bi-eraser::before { content: "\f331"; } +.bi-exclamation-circle-fill::before { content: "\f332"; } +.bi-exclamation-circle::before { content: "\f333"; } +.bi-exclamation-diamond-fill::before { content: "\f334"; } +.bi-exclamation-diamond::before { content: "\f335"; } +.bi-exclamation-octagon-fill::before { content: "\f336"; } +.bi-exclamation-octagon::before { content: "\f337"; } +.bi-exclamation-square-fill::before { content: "\f338"; } +.bi-exclamation-square::before { content: "\f339"; } +.bi-exclamation-triangle-fill::before { content: "\f33a"; } +.bi-exclamation-triangle::before { content: "\f33b"; } +.bi-exclamation::before { content: "\f33c"; } +.bi-exclude::before { content: "\f33d"; } +.bi-eye-fill::before { content: "\f33e"; } +.bi-eye-slash-fill::before { content: "\f33f"; } +.bi-eye-slash::before { content: "\f340"; } +.bi-eye::before { content: "\f341"; } +.bi-eyedropper::before { content: "\f342"; } +.bi-eyeglasses::before { content: "\f343"; } +.bi-facebook::before { content: "\f344"; } +.bi-file-arrow-down-fill::before { content: "\f345"; } +.bi-file-arrow-down::before { content: "\f346"; } +.bi-file-arrow-up-fill::before { content: "\f347"; } +.bi-file-arrow-up::before { content: "\f348"; } +.bi-file-bar-graph-fill::before { content: "\f349"; } +.bi-file-bar-graph::before { content: "\f34a"; } +.bi-file-binary-fill::before { content: "\f34b"; } +.bi-file-binary::before { content: "\f34c"; } +.bi-file-break-fill::before { content: "\f34d"; } +.bi-file-break::before { content: "\f34e"; } +.bi-file-check-fill::before { content: "\f34f"; } +.bi-file-check::before { content: "\f350"; } +.bi-file-code-fill::before { content: "\f351"; } +.bi-file-code::before { content: "\f352"; } +.bi-file-diff-fill::before { content: "\f353"; } +.bi-file-diff::before { content: "\f354"; } +.bi-file-earmark-arrow-down-fill::before { content: "\f355"; } +.bi-file-earmark-arrow-down::before { content: "\f356"; } +.bi-file-earmark-arrow-up-fill::before { content: "\f357"; } +.bi-file-earmark-arrow-up::before { content: "\f358"; } +.bi-file-earmark-bar-graph-fill::before { content: "\f359"; } +.bi-file-earmark-bar-graph::before { content: "\f35a"; } +.bi-file-earmark-binary-fill::before { content: "\f35b"; } +.bi-file-earmark-binary::before { content: "\f35c"; } +.bi-file-earmark-break-fill::before { content: "\f35d"; } +.bi-file-earmark-break::before { content: "\f35e"; } +.bi-file-earmark-check-fill::before { content: "\f35f"; } +.bi-file-earmark-check::before { content: "\f360"; } +.bi-file-earmark-code-fill::before { content: "\f361"; } +.bi-file-earmark-code::before { content: "\f362"; } +.bi-file-earmark-diff-fill::before { content: "\f363"; } +.bi-file-earmark-diff::before { content: "\f364"; } +.bi-file-earmark-easel-fill::before { content: "\f365"; } +.bi-file-earmark-easel::before { content: "\f366"; } +.bi-file-earmark-excel-fill::before { content: "\f367"; } +.bi-file-earmark-excel::before { content: "\f368"; } +.bi-file-earmark-fill::before { content: "\f369"; } +.bi-file-earmark-font-fill::before { content: "\f36a"; } +.bi-file-earmark-font::before { content: "\f36b"; } +.bi-file-earmark-image-fill::before { content: "\f36c"; } +.bi-file-earmark-image::before { content: "\f36d"; } +.bi-file-earmark-lock-fill::before { content: "\f36e"; } +.bi-file-earmark-lock::before { content: "\f36f"; } +.bi-file-earmark-lock2-fill::before { content: "\f370"; } +.bi-file-earmark-lock2::before { content: "\f371"; } +.bi-file-earmark-medical-fill::before { content: "\f372"; } +.bi-file-earmark-medical::before { content: "\f373"; } +.bi-file-earmark-minus-fill::before { content: "\f374"; } +.bi-file-earmark-minus::before { content: "\f375"; } +.bi-file-earmark-music-fill::before { content: "\f376"; } +.bi-file-earmark-music::before { content: "\f377"; } +.bi-file-earmark-person-fill::before { content: "\f378"; } +.bi-file-earmark-person::before { content: "\f379"; } +.bi-file-earmark-play-fill::before { content: "\f37a"; } +.bi-file-earmark-play::before { content: "\f37b"; } +.bi-file-earmark-plus-fill::before { content: "\f37c"; } +.bi-file-earmark-plus::before { content: "\f37d"; } +.bi-file-earmark-post-fill::before { content: "\f37e"; } +.bi-file-earmark-post::before { content: "\f37f"; } +.bi-file-earmark-ppt-fill::before { content: "\f380"; } +.bi-file-earmark-ppt::before { content: "\f381"; } +.bi-file-earmark-richtext-fill::before { content: "\f382"; } +.bi-file-earmark-richtext::before { content: "\f383"; } +.bi-file-earmark-ruled-fill::before { content: "\f384"; } +.bi-file-earmark-ruled::before { content: "\f385"; } +.bi-file-earmark-slides-fill::before { content: "\f386"; } +.bi-file-earmark-slides::before { content: "\f387"; } +.bi-file-earmark-spreadsheet-fill::before { content: "\f388"; } +.bi-file-earmark-spreadsheet::before { content: "\f389"; } +.bi-file-earmark-text-fill::before { content: "\f38a"; } +.bi-file-earmark-text::before { content: "\f38b"; } +.bi-file-earmark-word-fill::before { content: "\f38c"; } +.bi-file-earmark-word::before { content: "\f38d"; } +.bi-file-earmark-x-fill::before { content: "\f38e"; } +.bi-file-earmark-x::before { content: "\f38f"; } +.bi-file-earmark-zip-fill::before { content: "\f390"; } +.bi-file-earmark-zip::before { content: "\f391"; } +.bi-file-earmark::before { content: "\f392"; } +.bi-file-easel-fill::before { content: "\f393"; } +.bi-file-easel::before { content: "\f394"; } +.bi-file-excel-fill::before { content: "\f395"; } +.bi-file-excel::before { content: "\f396"; } +.bi-file-fill::before { content: "\f397"; } +.bi-file-font-fill::before { content: "\f398"; } +.bi-file-font::before { content: "\f399"; } +.bi-file-image-fill::before { content: "\f39a"; } +.bi-file-image::before { content: "\f39b"; } +.bi-file-lock-fill::before { content: "\f39c"; } +.bi-file-lock::before { content: "\f39d"; } +.bi-file-lock2-fill::before { content: "\f39e"; } +.bi-file-lock2::before { content: "\f39f"; } +.bi-file-medical-fill::before { content: "\f3a0"; } +.bi-file-medical::before { content: "\f3a1"; } +.bi-file-minus-fill::before { content: "\f3a2"; } +.bi-file-minus::before { content: "\f3a3"; } +.bi-file-music-fill::before { content: "\f3a4"; } +.bi-file-music::before { content: "\f3a5"; } +.bi-file-person-fill::before { content: "\f3a6"; } +.bi-file-person::before { content: "\f3a7"; } +.bi-file-play-fill::before { content: "\f3a8"; } +.bi-file-play::before { content: "\f3a9"; } +.bi-file-plus-fill::before { content: "\f3aa"; } +.bi-file-plus::before { content: "\f3ab"; } +.bi-file-post-fill::before { content: "\f3ac"; } +.bi-file-post::before { content: "\f3ad"; } +.bi-file-ppt-fill::before { content: "\f3ae"; } +.bi-file-ppt::before { content: "\f3af"; } +.bi-file-richtext-fill::before { content: "\f3b0"; } +.bi-file-richtext::before { content: "\f3b1"; } +.bi-file-ruled-fill::before { content: "\f3b2"; } +.bi-file-ruled::before { content: "\f3b3"; } +.bi-file-slides-fill::before { content: "\f3b4"; } +.bi-file-slides::before { content: "\f3b5"; } +.bi-file-spreadsheet-fill::before { content: "\f3b6"; } +.bi-file-spreadsheet::before { content: "\f3b7"; } +.bi-file-text-fill::before { content: "\f3b8"; } +.bi-file-text::before { content: "\f3b9"; } +.bi-file-word-fill::before { content: "\f3ba"; } +.bi-file-word::before { content: "\f3bb"; } +.bi-file-x-fill::before { content: "\f3bc"; } +.bi-file-x::before { content: "\f3bd"; } +.bi-file-zip-fill::before { content: "\f3be"; } +.bi-file-zip::before { content: "\f3bf"; } +.bi-file::before { content: "\f3c0"; } +.bi-files-alt::before { content: "\f3c1"; } +.bi-files::before { content: "\f3c2"; } +.bi-film::before { content: "\f3c3"; } +.bi-filter-circle-fill::before { content: "\f3c4"; } +.bi-filter-circle::before { content: "\f3c5"; } +.bi-filter-left::before { content: "\f3c6"; } +.bi-filter-right::before { content: "\f3c7"; } +.bi-filter-square-fill::before { content: "\f3c8"; } +.bi-filter-square::before { content: "\f3c9"; } +.bi-filter::before { content: "\f3ca"; } +.bi-flag-fill::before { content: "\f3cb"; } +.bi-flag::before { content: "\f3cc"; } +.bi-flower1::before { content: "\f3cd"; } +.bi-flower2::before { content: "\f3ce"; } +.bi-flower3::before { content: "\f3cf"; } +.bi-folder-check::before { content: "\f3d0"; } +.bi-folder-fill::before { content: "\f3d1"; } +.bi-folder-minus::before { content: "\f3d2"; } +.bi-folder-plus::before { content: "\f3d3"; } +.bi-folder-symlink-fill::before { content: "\f3d4"; } +.bi-folder-symlink::before { content: "\f3d5"; } +.bi-folder-x::before { content: "\f3d6"; } +.bi-folder::before { content: "\f3d7"; } +.bi-folder2-open::before { content: "\f3d8"; } +.bi-folder2::before { content: "\f3d9"; } +.bi-fonts::before { content: "\f3da"; } +.bi-forward-fill::before { content: "\f3db"; } +.bi-forward::before { content: "\f3dc"; } +.bi-front::before { content: "\f3dd"; } +.bi-fullscreen-exit::before { content: "\f3de"; } +.bi-fullscreen::before { content: "\f3df"; } +.bi-funnel-fill::before { content: "\f3e0"; } +.bi-funnel::before { content: "\f3e1"; } +.bi-gear-fill::before { content: "\f3e2"; } +.bi-gear-wide-connected::before { content: "\f3e3"; } +.bi-gear-wide::before { content: "\f3e4"; } +.bi-gear::before { content: "\f3e5"; } +.bi-gem::before { content: "\f3e6"; } +.bi-geo-alt-fill::before { content: "\f3e7"; } +.bi-geo-alt::before { content: "\f3e8"; } +.bi-geo-fill::before { content: "\f3e9"; } +.bi-geo::before { content: "\f3ea"; } +.bi-gift-fill::before { content: "\f3eb"; } +.bi-gift::before { content: "\f3ec"; } +.bi-github::before { content: "\f3ed"; } +.bi-globe::before { content: "\f3ee"; } +.bi-globe2::before { content: "\f3ef"; } +.bi-google::before { content: "\f3f0"; } +.bi-graph-down::before { content: "\f3f1"; } +.bi-graph-up::before { content: "\f3f2"; } +.bi-grid-1x2-fill::before { content: "\f3f3"; } +.bi-grid-1x2::before { content: "\f3f4"; } +.bi-grid-3x2-gap-fill::before { content: "\f3f5"; } +.bi-grid-3x2-gap::before { content: "\f3f6"; } +.bi-grid-3x2::before { content: "\f3f7"; } +.bi-grid-3x3-gap-fill::before { content: "\f3f8"; } +.bi-grid-3x3-gap::before { content: "\f3f9"; } +.bi-grid-3x3::before { content: "\f3fa"; } +.bi-grid-fill::before { content: "\f3fb"; } +.bi-grid::before { content: "\f3fc"; } +.bi-grip-horizontal::before { content: "\f3fd"; } +.bi-grip-vertical::before { content: "\f3fe"; } +.bi-hammer::before { content: "\f3ff"; } +.bi-hand-index-fill::before { content: "\f400"; } +.bi-hand-index-thumb-fill::before { content: "\f401"; } +.bi-hand-index-thumb::before { content: "\f402"; } +.bi-hand-index::before { content: "\f403"; } +.bi-hand-thumbs-down-fill::before { content: "\f404"; } +.bi-hand-thumbs-down::before { content: "\f405"; } +.bi-hand-thumbs-up-fill::before { content: "\f406"; } +.bi-hand-thumbs-up::before { content: "\f407"; } +.bi-handbag-fill::before { content: "\f408"; } +.bi-handbag::before { content: "\f409"; } +.bi-hash::before { content: "\f40a"; } +.bi-hdd-fill::before { content: "\f40b"; } +.bi-hdd-network-fill::before { content: "\f40c"; } +.bi-hdd-network::before { content: "\f40d"; } +.bi-hdd-rack-fill::before { content: "\f40e"; } +.bi-hdd-rack::before { content: "\f40f"; } +.bi-hdd-stack-fill::before { content: "\f410"; } +.bi-hdd-stack::before { content: "\f411"; } +.bi-hdd::before { content: "\f412"; } +.bi-headphones::before { content: "\f413"; } +.bi-headset::before { content: "\f414"; } +.bi-heart-fill::before { content: "\f415"; } +.bi-heart-half::before { content: "\f416"; } +.bi-heart::before { content: "\f417"; } +.bi-heptagon-fill::before { content: "\f418"; } +.bi-heptagon-half::before { content: "\f419"; } +.bi-heptagon::before { content: "\f41a"; } +.bi-hexagon-fill::before { content: "\f41b"; } +.bi-hexagon-half::before { content: "\f41c"; } +.bi-hexagon::before { content: "\f41d"; } +.bi-hourglass-bottom::before { content: "\f41e"; } +.bi-hourglass-split::before { content: "\f41f"; } +.bi-hourglass-top::before { content: "\f420"; } +.bi-hourglass::before { content: "\f421"; } +.bi-house-door-fill::before { content: "\f422"; } +.bi-house-door::before { content: "\f423"; } +.bi-house-fill::before { content: "\f424"; } +.bi-house::before { content: "\f425"; } +.bi-hr::before { content: "\f426"; } +.bi-hurricane::before { content: "\f427"; } +.bi-image-alt::before { content: "\f428"; } +.bi-image-fill::before { content: "\f429"; } +.bi-image::before { content: "\f42a"; } +.bi-images::before { content: "\f42b"; } +.bi-inbox-fill::before { content: "\f42c"; } +.bi-inbox::before { content: "\f42d"; } +.bi-inboxes-fill::before { content: "\f42e"; } +.bi-inboxes::before { content: "\f42f"; } +.bi-info-circle-fill::before { content: "\f430"; } +.bi-info-circle::before { content: "\f431"; } +.bi-info-square-fill::before { content: "\f432"; } +.bi-info-square::before { content: "\f433"; } +.bi-info::before { content: "\f434"; } +.bi-input-cursor-text::before { content: "\f435"; } +.bi-input-cursor::before { content: "\f436"; } +.bi-instagram::before { content: "\f437"; } +.bi-intersect::before { content: "\f438"; } +.bi-journal-album::before { content: "\f439"; } +.bi-journal-arrow-down::before { content: "\f43a"; } +.bi-journal-arrow-up::before { content: "\f43b"; } +.bi-journal-bookmark-fill::before { content: "\f43c"; } +.bi-journal-bookmark::before { content: "\f43d"; } +.bi-journal-check::before { content: "\f43e"; } +.bi-journal-code::before { content: "\f43f"; } +.bi-journal-medical::before { content: "\f440"; } +.bi-journal-minus::before { content: "\f441"; } +.bi-journal-plus::before { content: "\f442"; } +.bi-journal-richtext::before { content: "\f443"; } +.bi-journal-text::before { content: "\f444"; } +.bi-journal-x::before { content: "\f445"; } +.bi-journal::before { content: "\f446"; } +.bi-journals::before { content: "\f447"; } +.bi-joystick::before { content: "\f448"; } +.bi-justify-left::before { content: "\f449"; } +.bi-justify-right::before { content: "\f44a"; } +.bi-justify::before { content: "\f44b"; } +.bi-kanban-fill::before { content: "\f44c"; } +.bi-kanban::before { content: "\f44d"; } +.bi-key-fill::before { content: "\f44e"; } +.bi-key::before { content: "\f44f"; } +.bi-keyboard-fill::before { content: "\f450"; } +.bi-keyboard::before { content: "\f451"; } +.bi-ladder::before { content: "\f452"; } +.bi-lamp-fill::before { content: "\f453"; } +.bi-lamp::before { content: "\f454"; } +.bi-laptop-fill::before { content: "\f455"; } +.bi-laptop::before { content: "\f456"; } +.bi-layer-backward::before { content: "\f457"; } +.bi-layer-forward::before { content: "\f458"; } +.bi-layers-fill::before { content: "\f459"; } +.bi-layers-half::before { content: "\f45a"; } +.bi-layers::before { content: "\f45b"; } +.bi-layout-sidebar-inset-reverse::before { content: "\f45c"; } +.bi-layout-sidebar-inset::before { content: "\f45d"; } +.bi-layout-sidebar-reverse::before { content: "\f45e"; } +.bi-layout-sidebar::before { content: "\f45f"; } +.bi-layout-split::before { content: "\f460"; } +.bi-layout-text-sidebar-reverse::before { content: "\f461"; } +.bi-layout-text-sidebar::before { content: "\f462"; } +.bi-layout-text-window-reverse::before { content: "\f463"; } +.bi-layout-text-window::before { content: "\f464"; } +.bi-layout-three-columns::before { content: "\f465"; } +.bi-layout-wtf::before { content: "\f466"; } +.bi-life-preserver::before { content: "\f467"; } +.bi-lightbulb-fill::before { content: "\f468"; } +.bi-lightbulb-off-fill::before { content: "\f469"; } +.bi-lightbulb-off::before { content: "\f46a"; } +.bi-lightbulb::before { content: "\f46b"; } +.bi-lightning-charge-fill::before { content: "\f46c"; } +.bi-lightning-charge::before { content: "\f46d"; } +.bi-lightning-fill::before { content: "\f46e"; } +.bi-lightning::before { content: "\f46f"; } +.bi-link-45deg::before { content: "\f470"; } +.bi-link::before { content: "\f471"; } +.bi-linkedin::before { content: "\f472"; } +.bi-list-check::before { content: "\f473"; } +.bi-list-nested::before { content: "\f474"; } +.bi-list-ol::before { content: "\f475"; } +.bi-list-stars::before { content: "\f476"; } +.bi-list-task::before { content: "\f477"; } +.bi-list-ul::before { content: "\f478"; } +.bi-list::before { content: "\f479"; } +.bi-lock-fill::before { content: "\f47a"; } +.bi-lock::before { content: "\f47b"; } +.bi-mailbox::before { content: "\f47c"; } +.bi-mailbox2::before { content: "\f47d"; } +.bi-map-fill::before { content: "\f47e"; } +.bi-map::before { content: "\f47f"; } +.bi-markdown-fill::before { content: "\f480"; } +.bi-markdown::before { content: "\f481"; } +.bi-mask::before { content: "\f482"; } +.bi-megaphone-fill::before { content: "\f483"; } +.bi-megaphone::before { content: "\f484"; } +.bi-menu-app-fill::before { content: "\f485"; } +.bi-menu-app::before { content: "\f486"; } +.bi-menu-button-fill::before { content: "\f487"; } +.bi-menu-button-wide-fill::before { content: "\f488"; } +.bi-menu-button-wide::before { content: "\f489"; } +.bi-menu-button::before { content: "\f48a"; } +.bi-menu-down::before { content: "\f48b"; } +.bi-menu-up::before { content: "\f48c"; } +.bi-mic-fill::before { content: "\f48d"; } +.bi-mic-mute-fill::before { content: "\f48e"; } +.bi-mic-mute::before { content: "\f48f"; } +.bi-mic::before { content: "\f490"; } +.bi-minecart-loaded::before { content: "\f491"; } +.bi-minecart::before { content: "\f492"; } +.bi-moisture::before { content: "\f493"; } +.bi-moon-fill::before { content: "\f494"; } +.bi-moon-stars-fill::before { content: "\f495"; } +.bi-moon-stars::before { content: "\f496"; } +.bi-moon::before { content: "\f497"; } +.bi-mouse-fill::before { content: "\f498"; } +.bi-mouse::before { content: "\f499"; } +.bi-mouse2-fill::before { content: "\f49a"; } +.bi-mouse2::before { content: "\f49b"; } +.bi-mouse3-fill::before { content: "\f49c"; } +.bi-mouse3::before { content: "\f49d"; } +.bi-music-note-beamed::before { content: "\f49e"; } +.bi-music-note-list::before { content: "\f49f"; } +.bi-music-note::before { content: "\f4a0"; } +.bi-music-player-fill::before { content: "\f4a1"; } +.bi-music-player::before { content: "\f4a2"; } +.bi-newspaper::before { content: "\f4a3"; } +.bi-node-minus-fill::before { content: "\f4a4"; } +.bi-node-minus::before { content: "\f4a5"; } +.bi-node-plus-fill::before { content: "\f4a6"; } +.bi-node-plus::before { content: "\f4a7"; } +.bi-nut-fill::before { content: "\f4a8"; } +.bi-nut::before { content: "\f4a9"; } +.bi-octagon-fill::before { content: "\f4aa"; } +.bi-octagon-half::before { content: "\f4ab"; } +.bi-octagon::before { content: "\f4ac"; } +.bi-option::before { content: "\f4ad"; } +.bi-outlet::before { content: "\f4ae"; } +.bi-paint-bucket::before { content: "\f4af"; } +.bi-palette-fill::before { content: "\f4b0"; } +.bi-palette::before { content: "\f4b1"; } +.bi-palette2::before { content: "\f4b2"; } +.bi-paperclip::before { content: "\f4b3"; } +.bi-paragraph::before { content: "\f4b4"; } +.bi-patch-check-fill::before { content: "\f4b5"; } +.bi-patch-check::before { content: "\f4b6"; } +.bi-patch-exclamation-fill::before { content: "\f4b7"; } +.bi-patch-exclamation::before { content: "\f4b8"; } +.bi-patch-minus-fill::before { content: "\f4b9"; } +.bi-patch-minus::before { content: "\f4ba"; } +.bi-patch-plus-fill::before { content: "\f4bb"; } +.bi-patch-plus::before { content: "\f4bc"; } +.bi-patch-question-fill::before { content: "\f4bd"; } +.bi-patch-question::before { content: "\f4be"; } +.bi-pause-btn-fill::before { content: "\f4bf"; } +.bi-pause-btn::before { content: "\f4c0"; } +.bi-pause-circle-fill::before { content: "\f4c1"; } +.bi-pause-circle::before { content: "\f4c2"; } +.bi-pause-fill::before { content: "\f4c3"; } +.bi-pause::before { content: "\f4c4"; } +.bi-peace-fill::before { content: "\f4c5"; } +.bi-peace::before { content: "\f4c6"; } +.bi-pen-fill::before { content: "\f4c7"; } +.bi-pen::before { content: "\f4c8"; } +.bi-pencil-fill::before { content: "\f4c9"; } +.bi-pencil-square::before { content: "\f4ca"; } +.bi-pencil::before { content: "\f4cb"; } +.bi-pentagon-fill::before { content: "\f4cc"; } +.bi-pentagon-half::before { content: "\f4cd"; } +.bi-pentagon::before { content: "\f4ce"; } +.bi-people-fill::before { content: "\f4cf"; } +.bi-people::before { content: "\f4d0"; } +.bi-percent::before { content: "\f4d1"; } +.bi-person-badge-fill::before { content: "\f4d2"; } +.bi-person-badge::before { content: "\f4d3"; } +.bi-person-bounding-box::before { content: "\f4d4"; } +.bi-person-check-fill::before { content: "\f4d5"; } +.bi-person-check::before { content: "\f4d6"; } +.bi-person-circle::before { content: "\f4d7"; } +.bi-person-dash-fill::before { content: "\f4d8"; } +.bi-person-dash::before { content: "\f4d9"; } +.bi-person-fill::before { content: "\f4da"; } +.bi-person-lines-fill::before { content: "\f4db"; } +.bi-person-plus-fill::before { content: "\f4dc"; } +.bi-person-plus::before { content: "\f4dd"; } +.bi-person-square::before { content: "\f4de"; } +.bi-person-x-fill::before { content: "\f4df"; } +.bi-person-x::before { content: "\f4e0"; } +.bi-person::before { content: "\f4e1"; } +.bi-phone-fill::before { content: "\f4e2"; } +.bi-phone-landscape-fill::before { content: "\f4e3"; } +.bi-phone-landscape::before { content: "\f4e4"; } +.bi-phone-vibrate-fill::before { content: "\f4e5"; } +.bi-phone-vibrate::before { content: "\f4e6"; } +.bi-phone::before { content: "\f4e7"; } +.bi-pie-chart-fill::before { content: "\f4e8"; } +.bi-pie-chart::before { content: "\f4e9"; } +.bi-pin-angle-fill::before { content: "\f4ea"; } +.bi-pin-angle::before { content: "\f4eb"; } +.bi-pin-fill::before { content: "\f4ec"; } +.bi-pin::before { content: "\f4ed"; } +.bi-pip-fill::before { content: "\f4ee"; } +.bi-pip::before { content: "\f4ef"; } +.bi-play-btn-fill::before { content: "\f4f0"; } +.bi-play-btn::before { content: "\f4f1"; } +.bi-play-circle-fill::before { content: "\f4f2"; } +.bi-play-circle::before { content: "\f4f3"; } +.bi-play-fill::before { content: "\f4f4"; } +.bi-play::before { content: "\f4f5"; } +.bi-plug-fill::before { content: "\f4f6"; } +.bi-plug::before { content: "\f4f7"; } +.bi-plus-circle-dotted::before { content: "\f4f8"; } +.bi-plus-circle-fill::before { content: "\f4f9"; } +.bi-plus-circle::before { content: "\f4fa"; } +.bi-plus-square-dotted::before { content: "\f4fb"; } +.bi-plus-square-fill::before { content: "\f4fc"; } +.bi-plus-square::before { content: "\f4fd"; } +.bi-plus::before { content: "\f4fe"; } +.bi-power::before { content: "\f4ff"; } +.bi-printer-fill::before { content: "\f500"; } +.bi-printer::before { content: "\f501"; } +.bi-puzzle-fill::before { content: "\f502"; } +.bi-puzzle::before { content: "\f503"; } +.bi-question-circle-fill::before { content: "\f504"; } +.bi-question-circle::before { content: "\f505"; } +.bi-question-diamond-fill::before { content: "\f506"; } +.bi-question-diamond::before { content: "\f507"; } +.bi-question-octagon-fill::before { content: "\f508"; } +.bi-question-octagon::before { content: "\f509"; } +.bi-question-square-fill::before { content: "\f50a"; } +.bi-question-square::before { content: "\f50b"; } +.bi-question::before { content: "\f50c"; } +.bi-rainbow::before { content: "\f50d"; } +.bi-receipt-cutoff::before { content: "\f50e"; } +.bi-receipt::before { content: "\f50f"; } +.bi-reception-0::before { content: "\f510"; } +.bi-reception-1::before { content: "\f511"; } +.bi-reception-2::before { content: "\f512"; } +.bi-reception-3::before { content: "\f513"; } +.bi-reception-4::before { content: "\f514"; } +.bi-record-btn-fill::before { content: "\f515"; } +.bi-record-btn::before { content: "\f516"; } +.bi-record-circle-fill::before { content: "\f517"; } +.bi-record-circle::before { content: "\f518"; } +.bi-record-fill::before { content: "\f519"; } +.bi-record::before { content: "\f51a"; } +.bi-record2-fill::before { content: "\f51b"; } +.bi-record2::before { content: "\f51c"; } +.bi-reply-all-fill::before { content: "\f51d"; } +.bi-reply-all::before { content: "\f51e"; } +.bi-reply-fill::before { content: "\f51f"; } +.bi-reply::before { content: "\f520"; } +.bi-rss-fill::before { content: "\f521"; } +.bi-rss::before { content: "\f522"; } +.bi-rulers::before { content: "\f523"; } +.bi-save-fill::before { content: "\f524"; } +.bi-save::before { content: "\f525"; } +.bi-save2-fill::before { content: "\f526"; } +.bi-save2::before { content: "\f527"; } +.bi-scissors::before { content: "\f528"; } +.bi-screwdriver::before { content: "\f529"; } +.bi-search::before { content: "\f52a"; } +.bi-segmented-nav::before { content: "\f52b"; } +.bi-server::before { content: "\f52c"; } +.bi-share-fill::before { content: "\f52d"; } +.bi-share::before { content: "\f52e"; } +.bi-shield-check::before { content: "\f52f"; } +.bi-shield-exclamation::before { content: "\f530"; } +.bi-shield-fill-check::before { content: "\f531"; } +.bi-shield-fill-exclamation::before { content: "\f532"; } +.bi-shield-fill-minus::before { content: "\f533"; } +.bi-shield-fill-plus::before { content: "\f534"; } +.bi-shield-fill-x::before { content: "\f535"; } +.bi-shield-fill::before { content: "\f536"; } +.bi-shield-lock-fill::before { content: "\f537"; } +.bi-shield-lock::before { content: "\f538"; } +.bi-shield-minus::before { content: "\f539"; } +.bi-shield-plus::before { content: "\f53a"; } +.bi-shield-shaded::before { content: "\f53b"; } +.bi-shield-slash-fill::before { content: "\f53c"; } +.bi-shield-slash::before { content: "\f53d"; } +.bi-shield-x::before { content: "\f53e"; } +.bi-shield::before { content: "\f53f"; } +.bi-shift-fill::before { content: "\f540"; } +.bi-shift::before { content: "\f541"; } +.bi-shop-window::before { content: "\f542"; } +.bi-shop::before { content: "\f543"; } +.bi-shuffle::before { content: "\f544"; } +.bi-signpost-2-fill::before { content: "\f545"; } +.bi-signpost-2::before { content: "\f546"; } +.bi-signpost-fill::before { content: "\f547"; } +.bi-signpost-split-fill::before { content: "\f548"; } +.bi-signpost-split::before { content: "\f549"; } +.bi-signpost::before { content: "\f54a"; } +.bi-sim-fill::before { content: "\f54b"; } +.bi-sim::before { content: "\f54c"; } +.bi-skip-backward-btn-fill::before { content: "\f54d"; } +.bi-skip-backward-btn::before { content: "\f54e"; } +.bi-skip-backward-circle-fill::before { content: "\f54f"; } +.bi-skip-backward-circle::before { content: "\f550"; } +.bi-skip-backward-fill::before { content: "\f551"; } +.bi-skip-backward::before { content: "\f552"; } +.bi-skip-end-btn-fill::before { content: "\f553"; } +.bi-skip-end-btn::before { content: "\f554"; } +.bi-skip-end-circle-fill::before { content: "\f555"; } +.bi-skip-end-circle::before { content: "\f556"; } +.bi-skip-end-fill::before { content: "\f557"; } +.bi-skip-end::before { content: "\f558"; } +.bi-skip-forward-btn-fill::before { content: "\f559"; } +.bi-skip-forward-btn::before { content: "\f55a"; } +.bi-skip-forward-circle-fill::before { content: "\f55b"; } +.bi-skip-forward-circle::before { content: "\f55c"; } +.bi-skip-forward-fill::before { content: "\f55d"; } +.bi-skip-forward::before { content: "\f55e"; } +.bi-skip-start-btn-fill::before { content: "\f55f"; } +.bi-skip-start-btn::before { content: "\f560"; } +.bi-skip-start-circle-fill::before { content: "\f561"; } +.bi-skip-start-circle::before { content: "\f562"; } +.bi-skip-start-fill::before { content: "\f563"; } +.bi-skip-start::before { content: "\f564"; } +.bi-slack::before { content: "\f565"; } +.bi-slash-circle-fill::before { content: "\f566"; } +.bi-slash-circle::before { content: "\f567"; } +.bi-slash-square-fill::before { content: "\f568"; } +.bi-slash-square::before { content: "\f569"; } +.bi-slash::before { content: "\f56a"; } +.bi-sliders::before { content: "\f56b"; } +.bi-smartwatch::before { content: "\f56c"; } +.bi-snow::before { content: "\f56d"; } +.bi-snow2::before { content: "\f56e"; } +.bi-snow3::before { content: "\f56f"; } +.bi-sort-alpha-down-alt::before { content: "\f570"; } +.bi-sort-alpha-down::before { content: "\f571"; } +.bi-sort-alpha-up-alt::before { content: "\f572"; } +.bi-sort-alpha-up::before { content: "\f573"; } +.bi-sort-down-alt::before { content: "\f574"; } +.bi-sort-down::before { content: "\f575"; } +.bi-sort-numeric-down-alt::before { content: "\f576"; } +.bi-sort-numeric-down::before { content: "\f577"; } +.bi-sort-numeric-up-alt::before { content: "\f578"; } +.bi-sort-numeric-up::before { content: "\f579"; } +.bi-sort-up-alt::before { content: "\f57a"; } +.bi-sort-up::before { content: "\f57b"; } +.bi-soundwave::before { content: "\f57c"; } +.bi-speaker-fill::before { content: "\f57d"; } +.bi-speaker::before { content: "\f57e"; } +.bi-speedometer::before { content: "\f57f"; } +.bi-speedometer2::before { content: "\f580"; } +.bi-spellcheck::before { content: "\f581"; } +.bi-square-fill::before { content: "\f582"; } +.bi-square-half::before { content: "\f583"; } +.bi-square::before { content: "\f584"; } +.bi-stack::before { content: "\f585"; } +.bi-star-fill::before { content: "\f586"; } +.bi-star-half::before { content: "\f587"; } +.bi-star::before { content: "\f588"; } +.bi-stars::before { content: "\f589"; } +.bi-stickies-fill::before { content: "\f58a"; } +.bi-stickies::before { content: "\f58b"; } +.bi-sticky-fill::before { content: "\f58c"; } +.bi-sticky::before { content: "\f58d"; } +.bi-stop-btn-fill::before { content: "\f58e"; } +.bi-stop-btn::before { content: "\f58f"; } +.bi-stop-circle-fill::before { content: "\f590"; } +.bi-stop-circle::before { content: "\f591"; } +.bi-stop-fill::before { content: "\f592"; } +.bi-stop::before { content: "\f593"; } +.bi-stoplights-fill::before { content: "\f594"; } +.bi-stoplights::before { content: "\f595"; } +.bi-stopwatch-fill::before { content: "\f596"; } +.bi-stopwatch::before { content: "\f597"; } +.bi-subtract::before { content: "\f598"; } +.bi-suit-club-fill::before { content: "\f599"; } +.bi-suit-club::before { content: "\f59a"; } +.bi-suit-diamond-fill::before { content: "\f59b"; } +.bi-suit-diamond::before { content: "\f59c"; } +.bi-suit-heart-fill::before { content: "\f59d"; } +.bi-suit-heart::before { content: "\f59e"; } +.bi-suit-spade-fill::before { content: "\f59f"; } +.bi-suit-spade::before { content: "\f5a0"; } +.bi-sun-fill::before { content: "\f5a1"; } +.bi-sun::before { content: "\f5a2"; } +.bi-sunglasses::before { content: "\f5a3"; } +.bi-sunrise-fill::before { content: "\f5a4"; } +.bi-sunrise::before { content: "\f5a5"; } +.bi-sunset-fill::before { content: "\f5a6"; } +.bi-sunset::before { content: "\f5a7"; } +.bi-symmetry-horizontal::before { content: "\f5a8"; } +.bi-symmetry-vertical::before { content: "\f5a9"; } +.bi-table::before { content: "\f5aa"; } +.bi-tablet-fill::before { content: "\f5ab"; } +.bi-tablet-landscape-fill::before { content: "\f5ac"; } +.bi-tablet-landscape::before { content: "\f5ad"; } +.bi-tablet::before { content: "\f5ae"; } +.bi-tag-fill::before { content: "\f5af"; } +.bi-tag::before { content: "\f5b0"; } +.bi-tags-fill::before { content: "\f5b1"; } +.bi-tags::before { content: "\f5b2"; } +.bi-telegram::before { content: "\f5b3"; } +.bi-telephone-fill::before { content: "\f5b4"; } +.bi-telephone-forward-fill::before { content: "\f5b5"; } +.bi-telephone-forward::before { content: "\f5b6"; } +.bi-telephone-inbound-fill::before { content: "\f5b7"; } +.bi-telephone-inbound::before { content: "\f5b8"; } +.bi-telephone-minus-fill::before { content: "\f5b9"; } +.bi-telephone-minus::before { content: "\f5ba"; } +.bi-telephone-outbound-fill::before { content: "\f5bb"; } +.bi-telephone-outbound::before { content: "\f5bc"; } +.bi-telephone-plus-fill::before { content: "\f5bd"; } +.bi-telephone-plus::before { content: "\f5be"; } +.bi-telephone-x-fill::before { content: "\f5bf"; } +.bi-telephone-x::before { content: "\f5c0"; } +.bi-telephone::before { content: "\f5c1"; } +.bi-terminal-fill::before { content: "\f5c2"; } +.bi-terminal::before { content: "\f5c3"; } +.bi-text-center::before { content: "\f5c4"; } +.bi-text-indent-left::before { content: "\f5c5"; } +.bi-text-indent-right::before { content: "\f5c6"; } +.bi-text-left::before { content: "\f5c7"; } +.bi-text-paragraph::before { content: "\f5c8"; } +.bi-text-right::before { content: "\f5c9"; } +.bi-textarea-resize::before { content: "\f5ca"; } +.bi-textarea-t::before { content: "\f5cb"; } +.bi-textarea::before { content: "\f5cc"; } +.bi-thermometer-half::before { content: "\f5cd"; } +.bi-thermometer-high::before { content: "\f5ce"; } +.bi-thermometer-low::before { content: "\f5cf"; } +.bi-thermometer-snow::before { content: "\f5d0"; } +.bi-thermometer-sun::before { content: "\f5d1"; } +.bi-thermometer::before { content: "\f5d2"; } +.bi-three-dots-vertical::before { content: "\f5d3"; } +.bi-three-dots::before { content: "\f5d4"; } +.bi-toggle-off::before { content: "\f5d5"; } +.bi-toggle-on::before { content: "\f5d6"; } +.bi-toggle2-off::before { content: "\f5d7"; } +.bi-toggle2-on::before { content: "\f5d8"; } +.bi-toggles::before { content: "\f5d9"; } +.bi-toggles2::before { content: "\f5da"; } +.bi-tools::before { content: "\f5db"; } +.bi-tornado::before { content: "\f5dc"; } +.bi-trash-fill::before { content: "\f5dd"; } +.bi-trash::before { content: "\f5de"; } +.bi-trash2-fill::before { content: "\f5df"; } +.bi-trash2::before { content: "\f5e0"; } +.bi-tree-fill::before { content: "\f5e1"; } +.bi-tree::before { content: "\f5e2"; } +.bi-triangle-fill::before { content: "\f5e3"; } +.bi-triangle-half::before { content: "\f5e4"; } +.bi-triangle::before { content: "\f5e5"; } +.bi-trophy-fill::before { content: "\f5e6"; } +.bi-trophy::before { content: "\f5e7"; } +.bi-tropical-storm::before { content: "\f5e8"; } +.bi-truck-flatbed::before { content: "\f5e9"; } +.bi-truck::before { content: "\f5ea"; } +.bi-tsunami::before { content: "\f5eb"; } +.bi-tv-fill::before { content: "\f5ec"; } +.bi-tv::before { content: "\f5ed"; } +.bi-twitch::before { content: "\f5ee"; } +.bi-twitter::before { content: "\f5ef"; } +.bi-type-bold::before { content: "\f5f0"; } +.bi-type-h1::before { content: "\f5f1"; } +.bi-type-h2::before { content: "\f5f2"; } +.bi-type-h3::before { content: "\f5f3"; } +.bi-type-italic::before { content: "\f5f4"; } +.bi-type-strikethrough::before { content: "\f5f5"; } +.bi-type-underline::before { content: "\f5f6"; } +.bi-type::before { content: "\f5f7"; } +.bi-ui-checks-grid::before { content: "\f5f8"; } +.bi-ui-checks::before { content: "\f5f9"; } +.bi-ui-radios-grid::before { content: "\f5fa"; } +.bi-ui-radios::before { content: "\f5fb"; } +.bi-umbrella-fill::before { content: "\f5fc"; } +.bi-umbrella::before { content: "\f5fd"; } +.bi-union::before { content: "\f5fe"; } +.bi-unlock-fill::before { content: "\f5ff"; } +.bi-unlock::before { content: "\f600"; } +.bi-upc-scan::before { content: "\f601"; } +.bi-upc::before { content: "\f602"; } +.bi-upload::before { content: "\f603"; } +.bi-vector-pen::before { content: "\f604"; } +.bi-view-list::before { content: "\f605"; } +.bi-view-stacked::before { content: "\f606"; } +.bi-vinyl-fill::before { content: "\f607"; } +.bi-vinyl::before { content: "\f608"; } +.bi-voicemail::before { content: "\f609"; } +.bi-volume-down-fill::before { content: "\f60a"; } +.bi-volume-down::before { content: "\f60b"; } +.bi-volume-mute-fill::before { content: "\f60c"; } +.bi-volume-mute::before { content: "\f60d"; } +.bi-volume-off-fill::before { content: "\f60e"; } +.bi-volume-off::before { content: "\f60f"; } +.bi-volume-up-fill::before { content: "\f610"; } +.bi-volume-up::before { content: "\f611"; } +.bi-vr::before { content: "\f612"; } +.bi-wallet-fill::before { content: "\f613"; } +.bi-wallet::before { content: "\f614"; } +.bi-wallet2::before { content: "\f615"; } +.bi-watch::before { content: "\f616"; } +.bi-water::before { content: "\f617"; } +.bi-whatsapp::before { content: "\f618"; } +.bi-wifi-1::before { content: "\f619"; } +.bi-wifi-2::before { content: "\f61a"; } +.bi-wifi-off::before { content: "\f61b"; } +.bi-wifi::before { content: "\f61c"; } +.bi-wind::before { content: "\f61d"; } +.bi-window-dock::before { content: "\f61e"; } +.bi-window-sidebar::before { content: "\f61f"; } +.bi-window::before { content: "\f620"; } +.bi-wrench::before { content: "\f621"; } +.bi-x-circle-fill::before { content: "\f622"; } +.bi-x-circle::before { content: "\f623"; } +.bi-x-diamond-fill::before { content: "\f624"; } +.bi-x-diamond::before { content: "\f625"; } +.bi-x-octagon-fill::before { content: "\f626"; } +.bi-x-octagon::before { content: "\f627"; } +.bi-x-square-fill::before { content: "\f628"; } +.bi-x-square::before { content: "\f629"; } +.bi-x::before { content: "\f62a"; } +.bi-youtube::before { content: "\f62b"; } +.bi-zoom-in::before { content: "\f62c"; } +.bi-zoom-out::before { content: "\f62d"; } +.bi-bank::before { content: "\f62e"; } +.bi-bank2::before { content: "\f62f"; } +.bi-bell-slash-fill::before { content: "\f630"; } +.bi-bell-slash::before { content: "\f631"; } +.bi-cash-coin::before { content: "\f632"; } +.bi-check-lg::before { content: "\f633"; } +.bi-coin::before { content: "\f634"; } +.bi-currency-bitcoin::before { content: "\f635"; } +.bi-currency-dollar::before { content: "\f636"; } +.bi-currency-euro::before { content: "\f637"; } +.bi-currency-exchange::before { content: "\f638"; } +.bi-currency-pound::before { content: "\f639"; } +.bi-currency-yen::before { content: "\f63a"; } +.bi-dash-lg::before { content: "\f63b"; } +.bi-exclamation-lg::before { content: "\f63c"; } +.bi-file-earmark-pdf-fill::before { content: "\f63d"; } +.bi-file-earmark-pdf::before { content: "\f63e"; } +.bi-file-pdf-fill::before { content: "\f63f"; } +.bi-file-pdf::before { content: "\f640"; } +.bi-gender-ambiguous::before { content: "\f641"; } +.bi-gender-female::before { content: "\f642"; } +.bi-gender-male::before { content: "\f643"; } +.bi-gender-trans::before { content: "\f644"; } +.bi-headset-vr::before { content: "\f645"; } +.bi-info-lg::before { content: "\f646"; } +.bi-mastodon::before { content: "\f647"; } +.bi-messenger::before { content: "\f648"; } +.bi-piggy-bank-fill::before { content: "\f649"; } +.bi-piggy-bank::before { content: "\f64a"; } +.bi-pin-map-fill::before { content: "\f64b"; } +.bi-pin-map::before { content: "\f64c"; } +.bi-plus-lg::before { content: "\f64d"; } +.bi-question-lg::before { content: "\f64e"; } +.bi-recycle::before { content: "\f64f"; } +.bi-reddit::before { content: "\f650"; } +.bi-safe-fill::before { content: "\f651"; } +.bi-safe2-fill::before { content: "\f652"; } +.bi-safe2::before { content: "\f653"; } +.bi-sd-card-fill::before { content: "\f654"; } +.bi-sd-card::before { content: "\f655"; } +.bi-skype::before { content: "\f656"; } +.bi-slash-lg::before { content: "\f657"; } +.bi-translate::before { content: "\f658"; } +.bi-x-lg::before { content: "\f659"; } +.bi-safe::before { content: "\f65a"; } +.bi-apple::before { content: "\f65b"; } +.bi-microsoft::before { content: "\f65d"; } +.bi-windows::before { content: "\f65e"; } +.bi-behance::before { content: "\f65c"; } +.bi-dribbble::before { content: "\f65f"; } +.bi-line::before { content: "\f660"; } +.bi-medium::before { content: "\f661"; } +.bi-paypal::before { content: "\f662"; } +.bi-pinterest::before { content: "\f663"; } +.bi-signal::before { content: "\f664"; } +.bi-snapchat::before { content: "\f665"; } +.bi-spotify::before { content: "\f666"; } +.bi-stack-overflow::before { content: "\f667"; } +.bi-strava::before { content: "\f668"; } +.bi-wordpress::before { content: "\f669"; } +.bi-vimeo::before { content: "\f66a"; } +.bi-activity::before { content: "\f66b"; } +.bi-easel2-fill::before { content: "\f66c"; } +.bi-easel2::before { content: "\f66d"; } +.bi-easel3-fill::before { content: "\f66e"; } +.bi-easel3::before { content: "\f66f"; } +.bi-fan::before { content: "\f670"; } +.bi-fingerprint::before { content: "\f671"; } +.bi-graph-down-arrow::before { content: "\f672"; } +.bi-graph-up-arrow::before { content: "\f673"; } +.bi-hypnotize::before { content: "\f674"; } +.bi-magic::before { content: "\f675"; } +.bi-person-rolodex::before { content: "\f676"; } +.bi-person-video::before { content: "\f677"; } +.bi-person-video2::before { content: "\f678"; } +.bi-person-video3::before { content: "\f679"; } +.bi-person-workspace::before { content: "\f67a"; } +.bi-radioactive::before { content: "\f67b"; } +.bi-webcam-fill::before { content: "\f67c"; } +.bi-webcam::before { content: "\f67d"; } +.bi-yin-yang::before { content: "\f67e"; } +.bi-bandaid-fill::before { content: "\f680"; } +.bi-bandaid::before { content: "\f681"; } +.bi-bluetooth::before { content: "\f682"; } +.bi-body-text::before { content: "\f683"; } +.bi-boombox::before { content: "\f684"; } +.bi-boxes::before { content: "\f685"; } +.bi-dpad-fill::before { content: "\f686"; } +.bi-dpad::before { content: "\f687"; } +.bi-ear-fill::before { content: "\f688"; } +.bi-ear::before { content: "\f689"; } +.bi-envelope-check-1::before { content: "\f68a"; } +.bi-envelope-check-fill::before { content: "\f68b"; } +.bi-envelope-check::before { content: "\f68c"; } +.bi-envelope-dash-1::before { content: "\f68d"; } +.bi-envelope-dash-fill::before { content: "\f68e"; } +.bi-envelope-dash::before { content: "\f68f"; } +.bi-envelope-exclamation-1::before { content: "\f690"; } +.bi-envelope-exclamation-fill::before { content: "\f691"; } +.bi-envelope-exclamation::before { content: "\f692"; } +.bi-envelope-plus-fill::before { content: "\f693"; } +.bi-envelope-plus::before { content: "\f694"; } +.bi-envelope-slash-1::before { content: "\f695"; } +.bi-envelope-slash-fill::before { content: "\f696"; } +.bi-envelope-slash::before { content: "\f697"; } +.bi-envelope-x-1::before { content: "\f698"; } +.bi-envelope-x-fill::before { content: "\f699"; } +.bi-envelope-x::before { content: "\f69a"; } +.bi-explicit-fill::before { content: "\f69b"; } +.bi-explicit::before { content: "\f69c"; } +.bi-git::before { content: "\f69d"; } +.bi-infinity::before { content: "\f69e"; } +.bi-list-columns-reverse::before { content: "\f69f"; } +.bi-list-columns::before { content: "\f6a0"; } +.bi-meta::before { content: "\f6a1"; } +.bi-mortorboard-fill::before { content: "\f6a2"; } +.bi-mortorboard::before { content: "\f6a3"; } +.bi-nintendo-switch::before { content: "\f6a4"; } +.bi-pc-display-horizontal::before { content: "\f6a5"; } +.bi-pc-display::before { content: "\f6a6"; } +.bi-pc-horizontal::before { content: "\f6a7"; } +.bi-pc::before { content: "\f6a8"; } +.bi-playstation::before { content: "\f6a9"; } +.bi-plus-slash-minus::before { content: "\f6aa"; } +.bi-projector-fill::before { content: "\f6ab"; } +.bi-projector::before { content: "\f6ac"; } +.bi-qr-code-scan::before { content: "\f6ad"; } +.bi-qr-code::before { content: "\f6ae"; } +.bi-quora::before { content: "\f6af"; } +.bi-quote::before { content: "\f6b0"; } +.bi-robot::before { content: "\f6b1"; } +.bi-send-check-fill::before { content: "\f6b2"; } +.bi-send-check::before { content: "\f6b3"; } +.bi-send-dash-fill::before { content: "\f6b4"; } +.bi-send-dash::before { content: "\f6b5"; } +.bi-send-exclamation-1::before { content: "\f6b6"; } +.bi-send-exclamation-fill::before { content: "\f6b7"; } +.bi-send-exclamation::before { content: "\f6b8"; } +.bi-send-fill::before { content: "\f6b9"; } +.bi-send-plus-fill::before { content: "\f6ba"; } +.bi-send-plus::before { content: "\f6bb"; } +.bi-send-slash-fill::before { content: "\f6bc"; } +.bi-send-slash::before { content: "\f6bd"; } +.bi-send-x-fill::before { content: "\f6be"; } +.bi-send-x::before { content: "\f6bf"; } +.bi-send::before { content: "\f6c0"; } +.bi-steam::before { content: "\f6c1"; } +.bi-terminal-dash-1::before { content: "\f6c2"; } +.bi-terminal-dash::before { content: "\f6c3"; } +.bi-terminal-plus::before { content: "\f6c4"; } +.bi-terminal-split::before { content: "\f6c5"; } +.bi-ticket-detailed-fill::before { content: "\f6c6"; } +.bi-ticket-detailed::before { content: "\f6c7"; } +.bi-ticket-fill::before { content: "\f6c8"; } +.bi-ticket-perforated-fill::before { content: "\f6c9"; } +.bi-ticket-perforated::before { content: "\f6ca"; } +.bi-ticket::before { content: "\f6cb"; } +.bi-tiktok::before { content: "\f6cc"; } +.bi-window-dash::before { content: "\f6cd"; } +.bi-window-desktop::before { content: "\f6ce"; } +.bi-window-fullscreen::before { content: "\f6cf"; } +.bi-window-plus::before { content: "\f6d0"; } +.bi-window-split::before { content: "\f6d1"; } +.bi-window-stack::before { content: "\f6d2"; } +.bi-window-x::before { content: "\f6d3"; } +.bi-xbox::before { content: "\f6d4"; } +.bi-ethernet::before { content: "\f6d5"; } +.bi-hdmi-fill::before { content: "\f6d6"; } +.bi-hdmi::before { content: "\f6d7"; } +.bi-usb-c-fill::before { content: "\f6d8"; } +.bi-usb-c::before { content: "\f6d9"; } +.bi-usb-fill::before { content: "\f6da"; } +.bi-usb-plug-fill::before { content: "\f6db"; } +.bi-usb-plug::before { content: "\f6dc"; } +.bi-usb-symbol::before { content: "\f6dd"; } +.bi-usb::before { content: "\f6de"; } +.bi-boombox-fill::before { content: "\f6df"; } +.bi-displayport-1::before { content: "\f6e0"; } +.bi-displayport::before { content: "\f6e1"; } +.bi-gpu-card::before { content: "\f6e2"; } +.bi-memory::before { content: "\f6e3"; } +.bi-modem-fill::before { content: "\f6e4"; } +.bi-modem::before { content: "\f6e5"; } +.bi-motherboard-fill::before { content: "\f6e6"; } +.bi-motherboard::before { content: "\f6e7"; } +.bi-optical-audio-fill::before { content: "\f6e8"; } +.bi-optical-audio::before { content: "\f6e9"; } +.bi-pci-card::before { content: "\f6ea"; } +.bi-router-fill::before { content: "\f6eb"; } +.bi-router::before { content: "\f6ec"; } +.bi-ssd-fill::before { content: "\f6ed"; } +.bi-ssd::before { content: "\f6ee"; } +.bi-thunderbolt-fill::before { content: "\f6ef"; } +.bi-thunderbolt::before { content: "\f6f0"; } +.bi-usb-drive-fill::before { content: "\f6f1"; } +.bi-usb-drive::before { content: "\f6f2"; } +.bi-usb-micro-fill::before { content: "\f6f3"; } +.bi-usb-micro::before { content: "\f6f4"; } +.bi-usb-mini-fill::before { content: "\f6f5"; } +.bi-usb-mini::before { content: "\f6f6"; } +.bi-cloud-haze2::before { content: "\f6f7"; } +.bi-device-hdd-fill::before { content: "\f6f8"; } +.bi-device-hdd::before { content: "\f6f9"; } +.bi-device-ssd-fill::before { content: "\f6fa"; } +.bi-device-ssd::before { content: "\f6fb"; } +.bi-displayport-fill::before { content: "\f6fc"; } +.bi-mortarboard-fill::before { content: "\f6fd"; } +.bi-mortarboard::before { content: "\f6fe"; } +.bi-terminal-x::before { content: "\f6ff"; } +.bi-arrow-through-heart-fill::before { content: "\f700"; } +.bi-arrow-through-heart::before { content: "\f701"; } +.bi-badge-sd-fill::before { content: "\f702"; } +.bi-badge-sd::before { content: "\f703"; } +.bi-bag-heart-fill::before { content: "\f704"; } +.bi-bag-heart::before { content: "\f705"; } +.bi-balloon-fill::before { content: "\f706"; } +.bi-balloon-heart-fill::before { content: "\f707"; } +.bi-balloon-heart::before { content: "\f708"; } +.bi-balloon::before { content: "\f709"; } +.bi-box2-fill::before { content: "\f70a"; } +.bi-box2-heart-fill::before { content: "\f70b"; } +.bi-box2-heart::before { content: "\f70c"; } +.bi-box2::before { content: "\f70d"; } +.bi-braces-asterisk::before { content: "\f70e"; } +.bi-calendar-heart-fill::before { content: "\f70f"; } +.bi-calendar-heart::before { content: "\f710"; } +.bi-calendar2-heart-fill::before { content: "\f711"; } +.bi-calendar2-heart::before { content: "\f712"; } +.bi-chat-heart-fill::before { content: "\f713"; } +.bi-chat-heart::before { content: "\f714"; } +.bi-chat-left-heart-fill::before { content: "\f715"; } +.bi-chat-left-heart::before { content: "\f716"; } +.bi-chat-right-heart-fill::before { content: "\f717"; } +.bi-chat-right-heart::before { content: "\f718"; } +.bi-chat-square-heart-fill::before { content: "\f719"; } +.bi-chat-square-heart::before { content: "\f71a"; } +.bi-clipboard-check-fill::before { content: "\f71b"; } +.bi-clipboard-data-fill::before { content: "\f71c"; } +.bi-clipboard-fill::before { content: "\f71d"; } +.bi-clipboard-heart-fill::before { content: "\f71e"; } +.bi-clipboard-heart::before { content: "\f71f"; } +.bi-clipboard-minus-fill::before { content: "\f720"; } +.bi-clipboard-plus-fill::before { content: "\f721"; } +.bi-clipboard-pulse::before { content: "\f722"; } +.bi-clipboard-x-fill::before { content: "\f723"; } +.bi-clipboard2-check-fill::before { content: "\f724"; } +.bi-clipboard2-check::before { content: "\f725"; } +.bi-clipboard2-data-fill::before { content: "\f726"; } +.bi-clipboard2-data::before { content: "\f727"; } +.bi-clipboard2-fill::before { content: "\f728"; } +.bi-clipboard2-heart-fill::before { content: "\f729"; } +.bi-clipboard2-heart::before { content: "\f72a"; } +.bi-clipboard2-minus-fill::before { content: "\f72b"; } +.bi-clipboard2-minus::before { content: "\f72c"; } +.bi-clipboard2-plus-fill::before { content: "\f72d"; } +.bi-clipboard2-plus::before { content: "\f72e"; } +.bi-clipboard2-pulse-fill::before { content: "\f72f"; } +.bi-clipboard2-pulse::before { content: "\f730"; } +.bi-clipboard2-x-fill::before { content: "\f731"; } +.bi-clipboard2-x::before { content: "\f732"; } +.bi-clipboard2::before { content: "\f733"; } +.bi-emoji-kiss-fill::before { content: "\f734"; } +.bi-emoji-kiss::before { content: "\f735"; } +.bi-envelope-heart-fill::before { content: "\f736"; } +.bi-envelope-heart::before { content: "\f737"; } +.bi-envelope-open-heart-fill::before { content: "\f738"; } +.bi-envelope-open-heart::before { content: "\f739"; } +.bi-envelope-paper-fill::before { content: "\f73a"; } +.bi-envelope-paper-heart-fill::before { content: "\f73b"; } +.bi-envelope-paper-heart::before { content: "\f73c"; } +.bi-envelope-paper::before { content: "\f73d"; } +.bi-filetype-aac::before { content: "\f73e"; } +.bi-filetype-ai::before { content: "\f73f"; } +.bi-filetype-bmp::before { content: "\f740"; } +.bi-filetype-cs::before { content: "\f741"; } +.bi-filetype-css::before { content: "\f742"; } +.bi-filetype-csv::before { content: "\f743"; } +.bi-filetype-doc::before { content: "\f744"; } +.bi-filetype-docx::before { content: "\f745"; } +.bi-filetype-exe::before { content: "\f746"; } +.bi-filetype-gif::before { content: "\f747"; } +.bi-filetype-heic::before { content: "\f748"; } +.bi-filetype-html::before { content: "\f749"; } +.bi-filetype-java::before { content: "\f74a"; } +.bi-filetype-jpg::before { content: "\f74b"; } +.bi-filetype-js::before { content: "\f74c"; } +.bi-filetype-jsx::before { content: "\f74d"; } +.bi-filetype-key::before { content: "\f74e"; } +.bi-filetype-m4p::before { content: "\f74f"; } +.bi-filetype-md::before { content: "\f750"; } +.bi-filetype-mdx::before { content: "\f751"; } +.bi-filetype-mov::before { content: "\f752"; } +.bi-filetype-mp3::before { content: "\f753"; } +.bi-filetype-mp4::before { content: "\f754"; } +.bi-filetype-otf::before { content: "\f755"; } +.bi-filetype-pdf::before { content: "\f756"; } +.bi-filetype-php::before { content: "\f757"; } +.bi-filetype-png::before { content: "\f758"; } +.bi-filetype-ppt-1::before { content: "\f759"; } +.bi-filetype-ppt::before { content: "\f75a"; } +.bi-filetype-psd::before { content: "\f75b"; } +.bi-filetype-py::before { content: "\f75c"; } +.bi-filetype-raw::before { content: "\f75d"; } +.bi-filetype-rb::before { content: "\f75e"; } +.bi-filetype-sass::before { content: "\f75f"; } +.bi-filetype-scss::before { content: "\f760"; } +.bi-filetype-sh::before { content: "\f761"; } +.bi-filetype-svg::before { content: "\f762"; } +.bi-filetype-tiff::before { content: "\f763"; } +.bi-filetype-tsx::before { content: "\f764"; } +.bi-filetype-ttf::before { content: "\f765"; } +.bi-filetype-txt::before { content: "\f766"; } +.bi-filetype-wav::before { content: "\f767"; } +.bi-filetype-woff::before { content: "\f768"; } +.bi-filetype-xls-1::before { content: "\f769"; } +.bi-filetype-xls::before { content: "\f76a"; } +.bi-filetype-xml::before { content: "\f76b"; } +.bi-filetype-yml::before { content: "\f76c"; } +.bi-heart-arrow::before { content: "\f76d"; } +.bi-heart-pulse-fill::before { content: "\f76e"; } +.bi-heart-pulse::before { content: "\f76f"; } +.bi-heartbreak-fill::before { content: "\f770"; } +.bi-heartbreak::before { content: "\f771"; } +.bi-hearts::before { content: "\f772"; } +.bi-hospital-fill::before { content: "\f773"; } +.bi-hospital::before { content: "\f774"; } +.bi-house-heart-fill::before { content: "\f775"; } +.bi-house-heart::before { content: "\f776"; } +.bi-incognito::before { content: "\f777"; } +.bi-magnet-fill::before { content: "\f778"; } +.bi-magnet::before { content: "\f779"; } +.bi-person-heart::before { content: "\f77a"; } +.bi-person-hearts::before { content: "\f77b"; } +.bi-phone-flip::before { content: "\f77c"; } +.bi-plugin::before { content: "\f77d"; } +.bi-postage-fill::before { content: "\f77e"; } +.bi-postage-heart-fill::before { content: "\f77f"; } +.bi-postage-heart::before { content: "\f780"; } +.bi-postage::before { content: "\f781"; } +.bi-postcard-fill::before { content: "\f782"; } +.bi-postcard-heart-fill::before { content: "\f783"; } +.bi-postcard-heart::before { content: "\f784"; } +.bi-postcard::before { content: "\f785"; } +.bi-search-heart-fill::before { content: "\f786"; } +.bi-search-heart::before { content: "\f787"; } +.bi-sliders2-vertical::before { content: "\f788"; } +.bi-sliders2::before { content: "\f789"; } +.bi-trash3-fill::before { content: "\f78a"; } +.bi-trash3::before { content: "\f78b"; } +.bi-valentine::before { content: "\f78c"; } +.bi-valentine2::before { content: "\f78d"; } +.bi-wrench-adjustable-circle-fill::before { content: "\f78e"; } +.bi-wrench-adjustable-circle::before { content: "\f78f"; } +.bi-wrench-adjustable::before { content: "\f790"; } +.bi-filetype-json::before { content: "\f791"; } +.bi-filetype-pptx::before { content: "\f792"; } +.bi-filetype-xlsx::before { content: "\f793"; } diff --git a/doc/ch02-GettingStarted_files/libs/bootstrap/bootstrap-icons.woff b/doc/ch02-GettingStarted_files/libs/bootstrap/bootstrap-icons.woff new file mode 100644 index 000000000..b26ccd1ac Binary files /dev/null and b/doc/ch02-GettingStarted_files/libs/bootstrap/bootstrap-icons.woff differ diff --git a/doc/ch02-GettingStarted_files/libs/bootstrap/bootstrap.min.css b/doc/ch02-GettingStarted_files/libs/bootstrap/bootstrap.min.css new file mode 100644 index 000000000..62b4dbc31 --- /dev/null +++ b/doc/ch02-GettingStarted_files/libs/bootstrap/bootstrap.min.css @@ -0,0 +1,10 @@ +/*! + * Bootstrap v5.1.3 (https://getbootstrap.com/) + * Copyright 2011-2021 The Bootstrap Authors + * Copyright 2011-2021 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */:root{--bs-blue: #0d6efd;--bs-indigo: #6610f2;--bs-purple: #6f42c1;--bs-pink: #d63384;--bs-red: #dc3545;--bs-orange: #fd7e14;--bs-yellow: #ffc107;--bs-green: #198754;--bs-teal: #20c997;--bs-cyan: #0dcaf0;--bs-white: #ffffff;--bs-gray: #6c757d;--bs-gray-dark: #343a40;--bs-gray-100: #f8f9fa;--bs-gray-200: #e9ecef;--bs-gray-300: #dee2e6;--bs-gray-400: #ced4da;--bs-gray-500: #adb5bd;--bs-gray-600: #6c757d;--bs-gray-700: #495057;--bs-gray-800: #343a40;--bs-gray-900: #212529;--bs-default: #dee2e6;--bs-primary: #0d6efd;--bs-secondary: #6c757d;--bs-success: #198754;--bs-info: #0dcaf0;--bs-warning: #ffc107;--bs-danger: #dc3545;--bs-light: #f8f9fa;--bs-dark: #212529;--bs-default-rgb: 222, 226, 230;--bs-primary-rgb: 13, 110, 253;--bs-secondary-rgb: 108, 117, 125;--bs-success-rgb: 25, 135, 84;--bs-info-rgb: 13, 202, 240;--bs-warning-rgb: 255, 193, 7;--bs-danger-rgb: 220, 53, 69;--bs-light-rgb: 248, 249, 250;--bs-dark-rgb: 33, 37, 41;--bs-white-rgb: 255, 255, 255;--bs-black-rgb: 0, 0, 0;--bs-body-color-rgb: 33, 37, 41;--bs-body-bg-rgb: 255, 255, 255;--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-root-font-size: 18px;--bs-body-font-family: var(--bs-font-sans-serif);--bs-body-font-size: 1rem;--bs-body-font-weight: 400;--bs-body-line-height: 1.5;--bs-body-color: #212529;--bs-body-bg: #ffffff}*,*::before,*::after{box-sizing:border-box}:root{font-size:var(--bs-root-font-size)}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}h6,.h6,h5,.h5,h4,.h4,h3,.h3,h2,.h2,h1,.h1{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}h1,.h1{font-size:calc(1.345rem + 1.14vw)}@media(min-width: 1200px){h1,.h1{font-size:2.2rem}}h2,.h2{font-size:calc(1.3rem + 0.6vw)}@media(min-width: 1200px){h2,.h2{font-size:1.75rem}}h3,.h3{font-size:calc(1.275rem + 0.3vw)}@media(min-width: 1200px){h3,.h3{font-size:1.5rem}}h4,.h4{font-size:1.25rem}h5,.h5{font-size:1.1rem}h6,.h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[title],abbr[data-bs-original-title]{text-decoration:underline dotted;-webkit-text-decoration:underline dotted;-moz-text-decoration:underline dotted;-ms-text-decoration:underline dotted;-o-text-decoration:underline dotted;cursor:help;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}ol,ul,dl{margin-top:0;margin-bottom:1rem}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem;padding:.625rem 1.25rem;border-left:.25rem solid #e9ecef}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}b,strong{font-weight:bolder}small,.small{font-size:0.875em}mark,.mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:0.75em;line-height:0;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}a{color:#0d6efd;text-decoration:underline;-webkit-text-decoration:underline;-moz-text-decoration:underline;-ms-text-decoration:underline;-o-text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}pre,code,kbd,samp{font-family:var(--bs-font-monospace);font-size:1em;direction:ltr /* rtl:ignore */;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:0.875em;color:#000;background-color:#f6f6f6;padding:.5rem;border:1px solid #dee2e6;border-radius:.25rem}pre code{background-color:transparent;font-size:inherit;color:inherit;word-break:normal}code{font-size:0.875em;color:#9753b8;background-color:#f6f6f6;border-radius:.25rem;padding:.125rem .25rem;word-wrap:break-word}a>code{color:inherit}kbd{padding:.4rem .4rem;font-size:0.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}thead,tbody,tfoot,tr,td,th{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}input,button,select,optgroup,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button:not(:disabled),[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + 0.3vw);line-height:inherit}@media(min-width: 1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-text,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none !important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:0.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:0.875em;color:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:0.875em;color:#6c757d}.grid{display:grid;grid-template-rows:repeat(var(--bs-rows, 1), 1fr);grid-template-columns:repeat(var(--bs-columns, 12), 1fr);gap:var(--bs-gap, 1.5rem)}.grid .g-col-1{grid-column:auto/span 1}.grid .g-col-2{grid-column:auto/span 2}.grid .g-col-3{grid-column:auto/span 3}.grid .g-col-4{grid-column:auto/span 4}.grid .g-col-5{grid-column:auto/span 5}.grid .g-col-6{grid-column:auto/span 6}.grid .g-col-7{grid-column:auto/span 7}.grid .g-col-8{grid-column:auto/span 8}.grid .g-col-9{grid-column:auto/span 9}.grid .g-col-10{grid-column:auto/span 10}.grid .g-col-11{grid-column:auto/span 11}.grid .g-col-12{grid-column:auto/span 12}.grid .g-start-1{grid-column-start:1}.grid .g-start-2{grid-column-start:2}.grid .g-start-3{grid-column-start:3}.grid .g-start-4{grid-column-start:4}.grid .g-start-5{grid-column-start:5}.grid .g-start-6{grid-column-start:6}.grid .g-start-7{grid-column-start:7}.grid .g-start-8{grid-column-start:8}.grid .g-start-9{grid-column-start:9}.grid .g-start-10{grid-column-start:10}.grid .g-start-11{grid-column-start:11}@media(min-width: 576px){.grid .g-col-sm-1{grid-column:auto/span 1}.grid .g-col-sm-2{grid-column:auto/span 2}.grid .g-col-sm-3{grid-column:auto/span 3}.grid .g-col-sm-4{grid-column:auto/span 4}.grid .g-col-sm-5{grid-column:auto/span 5}.grid .g-col-sm-6{grid-column:auto/span 6}.grid .g-col-sm-7{grid-column:auto/span 7}.grid .g-col-sm-8{grid-column:auto/span 8}.grid .g-col-sm-9{grid-column:auto/span 9}.grid .g-col-sm-10{grid-column:auto/span 10}.grid .g-col-sm-11{grid-column:auto/span 11}.grid .g-col-sm-12{grid-column:auto/span 12}.grid .g-start-sm-1{grid-column-start:1}.grid .g-start-sm-2{grid-column-start:2}.grid .g-start-sm-3{grid-column-start:3}.grid .g-start-sm-4{grid-column-start:4}.grid .g-start-sm-5{grid-column-start:5}.grid .g-start-sm-6{grid-column-start:6}.grid .g-start-sm-7{grid-column-start:7}.grid .g-start-sm-8{grid-column-start:8}.grid .g-start-sm-9{grid-column-start:9}.grid .g-start-sm-10{grid-column-start:10}.grid .g-start-sm-11{grid-column-start:11}}@media(min-width: 768px){.grid .g-col-md-1{grid-column:auto/span 1}.grid .g-col-md-2{grid-column:auto/span 2}.grid .g-col-md-3{grid-column:auto/span 3}.grid .g-col-md-4{grid-column:auto/span 4}.grid .g-col-md-5{grid-column:auto/span 5}.grid .g-col-md-6{grid-column:auto/span 6}.grid .g-col-md-7{grid-column:auto/span 7}.grid .g-col-md-8{grid-column:auto/span 8}.grid .g-col-md-9{grid-column:auto/span 9}.grid .g-col-md-10{grid-column:auto/span 10}.grid .g-col-md-11{grid-column:auto/span 11}.grid .g-col-md-12{grid-column:auto/span 12}.grid .g-start-md-1{grid-column-start:1}.grid .g-start-md-2{grid-column-start:2}.grid .g-start-md-3{grid-column-start:3}.grid .g-start-md-4{grid-column-start:4}.grid .g-start-md-5{grid-column-start:5}.grid .g-start-md-6{grid-column-start:6}.grid .g-start-md-7{grid-column-start:7}.grid .g-start-md-8{grid-column-start:8}.grid .g-start-md-9{grid-column-start:9}.grid .g-start-md-10{grid-column-start:10}.grid .g-start-md-11{grid-column-start:11}}@media(min-width: 992px){.grid .g-col-lg-1{grid-column:auto/span 1}.grid .g-col-lg-2{grid-column:auto/span 2}.grid .g-col-lg-3{grid-column:auto/span 3}.grid .g-col-lg-4{grid-column:auto/span 4}.grid .g-col-lg-5{grid-column:auto/span 5}.grid .g-col-lg-6{grid-column:auto/span 6}.grid .g-col-lg-7{grid-column:auto/span 7}.grid .g-col-lg-8{grid-column:auto/span 8}.grid .g-col-lg-9{grid-column:auto/span 9}.grid .g-col-lg-10{grid-column:auto/span 10}.grid .g-col-lg-11{grid-column:auto/span 11}.grid .g-col-lg-12{grid-column:auto/span 12}.grid .g-start-lg-1{grid-column-start:1}.grid .g-start-lg-2{grid-column-start:2}.grid .g-start-lg-3{grid-column-start:3}.grid .g-start-lg-4{grid-column-start:4}.grid .g-start-lg-5{grid-column-start:5}.grid .g-start-lg-6{grid-column-start:6}.grid .g-start-lg-7{grid-column-start:7}.grid .g-start-lg-8{grid-column-start:8}.grid .g-start-lg-9{grid-column-start:9}.grid .g-start-lg-10{grid-column-start:10}.grid .g-start-lg-11{grid-column-start:11}}@media(min-width: 1200px){.grid .g-col-xl-1{grid-column:auto/span 1}.grid .g-col-xl-2{grid-column:auto/span 2}.grid .g-col-xl-3{grid-column:auto/span 3}.grid .g-col-xl-4{grid-column:auto/span 4}.grid .g-col-xl-5{grid-column:auto/span 5}.grid .g-col-xl-6{grid-column:auto/span 6}.grid .g-col-xl-7{grid-column:auto/span 7}.grid .g-col-xl-8{grid-column:auto/span 8}.grid .g-col-xl-9{grid-column:auto/span 9}.grid .g-col-xl-10{grid-column:auto/span 10}.grid .g-col-xl-11{grid-column:auto/span 11}.grid .g-col-xl-12{grid-column:auto/span 12}.grid .g-start-xl-1{grid-column-start:1}.grid .g-start-xl-2{grid-column-start:2}.grid .g-start-xl-3{grid-column-start:3}.grid .g-start-xl-4{grid-column-start:4}.grid .g-start-xl-5{grid-column-start:5}.grid .g-start-xl-6{grid-column-start:6}.grid .g-start-xl-7{grid-column-start:7}.grid .g-start-xl-8{grid-column-start:8}.grid .g-start-xl-9{grid-column-start:9}.grid .g-start-xl-10{grid-column-start:10}.grid .g-start-xl-11{grid-column-start:11}}@media(min-width: 1400px){.grid .g-col-xxl-1{grid-column:auto/span 1}.grid .g-col-xxl-2{grid-column:auto/span 2}.grid .g-col-xxl-3{grid-column:auto/span 3}.grid .g-col-xxl-4{grid-column:auto/span 4}.grid .g-col-xxl-5{grid-column:auto/span 5}.grid .g-col-xxl-6{grid-column:auto/span 6}.grid .g-col-xxl-7{grid-column:auto/span 7}.grid .g-col-xxl-8{grid-column:auto/span 8}.grid .g-col-xxl-9{grid-column:auto/span 9}.grid .g-col-xxl-10{grid-column:auto/span 10}.grid .g-col-xxl-11{grid-column:auto/span 11}.grid .g-col-xxl-12{grid-column:auto/span 12}.grid .g-start-xxl-1{grid-column-start:1}.grid .g-start-xxl-2{grid-column-start:2}.grid .g-start-xxl-3{grid-column-start:3}.grid .g-start-xxl-4{grid-column-start:4}.grid .g-start-xxl-5{grid-column-start:5}.grid .g-start-xxl-6{grid-column-start:6}.grid .g-start-xxl-7{grid-column-start:7}.grid .g-start-xxl-8{grid-column-start:8}.grid .g-start-xxl-9{grid-column-start:9}.grid .g-start-xxl-10{grid-column-start:10}.grid .g-start-xxl-11{grid-column-start:11}}.table{--bs-table-bg: transparent;--bs-table-accent-bg: transparent;--bs-table-striped-color: #212529;--bs-table-striped-bg: rgba(0, 0, 0, 0.05);--bs-table-active-color: #212529;--bs-table-active-bg: rgba(0, 0, 0, 0.1);--bs-table-hover-color: #212529;--bs-table-hover-bg: rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;color:#212529;vertical-align:top;border-color:#dee2e6}.table>:not(caption)>*>*{padding:.5rem .5rem;background-color:var(--bs-table-bg);border-bottom-width:1px;box-shadow:inset 0 0 0 9999px var(--bs-table-accent-bg)}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table>:not(:first-child){border-top:2px solid currentColor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:1px 0}.table-bordered>:not(caption)>*>*{border-width:0 1px}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-borderless>:not(:first-child){border-top-width:0}.table-striped>tbody>tr:nth-of-type(odd)>*{--bs-table-accent-bg: var(--bs-table-striped-bg);color:var(--bs-table-striped-color)}.table-active{--bs-table-accent-bg: var(--bs-table-active-bg);color:var(--bs-table-active-color)}.table-hover>tbody>tr:hover>*{--bs-table-accent-bg: var(--bs-table-hover-bg);color:var(--bs-table-hover-color)}.table-primary{--bs-table-bg: #cfe2ff;--bs-table-striped-bg: #c5d7f2;--bs-table-striped-color: #000;--bs-table-active-bg: #bacbe6;--bs-table-active-color: #000;--bs-table-hover-bg: #bfd1ec;--bs-table-hover-color: #000;color:#000;border-color:#bacbe6}.table-secondary{--bs-table-bg: #e2e3e5;--bs-table-striped-bg: #d7d8da;--bs-table-striped-color: #000;--bs-table-active-bg: #cbccce;--bs-table-active-color: #000;--bs-table-hover-bg: #d1d2d4;--bs-table-hover-color: #000;color:#000;border-color:#cbccce}.table-success{--bs-table-bg: #d1e7dd;--bs-table-striped-bg: #c7dbd2;--bs-table-striped-color: #000;--bs-table-active-bg: #bcd0c7;--bs-table-active-color: #000;--bs-table-hover-bg: #c1d6cc;--bs-table-hover-color: #000;color:#000;border-color:#bcd0c7}.table-info{--bs-table-bg: #cff4fc;--bs-table-striped-bg: #c5e8ef;--bs-table-striped-color: #000;--bs-table-active-bg: #badce3;--bs-table-active-color: #000;--bs-table-hover-bg: #bfe2e9;--bs-table-hover-color: #000;color:#000;border-color:#badce3}.table-warning{--bs-table-bg: #fff3cd;--bs-table-striped-bg: #f2e7c3;--bs-table-striped-color: #000;--bs-table-active-bg: #e6dbb9;--bs-table-active-color: #000;--bs-table-hover-bg: #ece1be;--bs-table-hover-color: #000;color:#000;border-color:#e6dbb9}.table-danger{--bs-table-bg: #f8d7da;--bs-table-striped-bg: #eccccf;--bs-table-striped-color: #000;--bs-table-active-bg: #dfc2c4;--bs-table-active-color: #000;--bs-table-hover-bg: #e5c7ca;--bs-table-hover-color: #000;color:#000;border-color:#dfc2c4}.table-light{--bs-table-bg: #f8f9fa;--bs-table-striped-bg: #ecedee;--bs-table-striped-color: #000;--bs-table-active-bg: #dfe0e1;--bs-table-active-color: #000;--bs-table-hover-bg: #e5e6e7;--bs-table-hover-color: #000;color:#000;border-color:#dfe0e1}.table-dark{--bs-table-bg: #212529;--bs-table-striped-bg: #2c3034;--bs-table-striped-color: #ffffff;--bs-table-active-bg: #373b3e;--bs-table-active-color: #ffffff;--bs-table-hover-bg: #323539;--bs-table-hover-color: #ffffff;color:#fff;border-color:#373b3e}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media(max-width: 575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label,.shiny-input-container .control-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(0.375rem + 1px);padding-bottom:calc(0.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(0.5rem + 1px);padding-bottom:calc(0.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(0.25rem + 1px);padding-bottom:calc(0.25rem + 1px);font-size:0.875rem}.form-text{margin-top:.25rem;font-size:0.875em;color:#6c757d}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:#212529;background-color:#fff;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-control::-webkit-date-and-time-value{height:1.5em}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}.form-control::file-selector-button{padding:.375rem .75rem;margin:-0.375rem -0.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:#dde0e3}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-0.375rem -0.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-control::-webkit-file-upload-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#dde0e3}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-sm,.form-control-plaintext.form-control-lg{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + 0.5rem + 2px);padding:.25rem .5rem;font-size:0.875rem;border-radius:.2rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-0.25rem -0.5rem;margin-inline-end:.5rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-0.25rem -0.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-0.5rem -1rem;margin-inline-end:1rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-0.5rem -1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + 0.75rem + 2px)}textarea.form-control-sm{min-height:calc(1.5em + 0.5rem + 2px)}textarea.form-control-lg{min-height:calc(1.5em + 1rem + 2px)}.form-control-color{width:3rem;height:auto;padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{height:1.5em;border-radius:.25rem}.form-control-color::-webkit-color-swatch{height:1.5em;border-radius:.25rem}.form-select{display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;-moz-padding-start:calc(0.75rem - 3px);font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none}@media(prefers-reduced-motion: reduce){.form-select{transition:none}}.form-select:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:#e9ecef}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #212529}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:0.875rem;border-radius:.2rem}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem;border-radius:.3rem}.form-check,.shiny-input-container .checkbox,.shiny-input-container .radio{display:block;min-height:1.5rem;padding-left:0;margin-bottom:.125rem}.form-check .form-check-input,.form-check .shiny-input-container .checkbox input,.form-check .shiny-input-container .radio input,.shiny-input-container .checkbox .form-check-input,.shiny-input-container .checkbox .shiny-input-container .checkbox input,.shiny-input-container .checkbox .shiny-input-container .radio input,.shiny-input-container .radio .form-check-input,.shiny-input-container .radio .shiny-input-container .checkbox input,.shiny-input-container .radio .shiny-input-container .radio input{float:left;margin-left:0}.form-check-input,.shiny-input-container .checkbox input,.shiny-input-container .checkbox-inline input,.shiny-input-container .radio input,.shiny-input-container .radio-inline input{width:1em;height:1em;margin-top:.25em;vertical-align:top;background-color:#fff;background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid rgba(0,0,0,.25);appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;color-adjust:exact;-webkit-print-color-adjust:exact}.form-check-input[type=checkbox],.shiny-input-container .checkbox input[type=checkbox],.shiny-input-container .checkbox-inline input[type=checkbox],.shiny-input-container .radio input[type=checkbox],.shiny-input-container .radio-inline input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio],.shiny-input-container .checkbox input[type=radio],.shiny-input-container .checkbox-inline input[type=radio],.shiny-input-container .radio input[type=radio],.shiny-input-container .radio-inline input[type=radio]{border-radius:50%}.form-check-input:active,.shiny-input-container .checkbox input:active,.shiny-input-container .checkbox-inline input:active,.shiny-input-container .radio input:active,.shiny-input-container .radio-inline input:active{filter:brightness(90%)}.form-check-input:focus,.shiny-input-container .checkbox input:focus,.shiny-input-container .checkbox-inline input:focus,.shiny-input-container .radio input:focus,.shiny-input-container .radio-inline input:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-check-input:checked,.shiny-input-container .checkbox input:checked,.shiny-input-container .checkbox-inline input:checked,.shiny-input-container .radio input:checked,.shiny-input-container .radio-inline input:checked{background-color:#0d6efd;border-color:#0d6efd}.form-check-input:checked[type=checkbox],.shiny-input-container .checkbox input:checked[type=checkbox],.shiny-input-container .checkbox-inline input:checked[type=checkbox],.shiny-input-container .radio input:checked[type=checkbox],.shiny-input-container .radio-inline input:checked[type=checkbox]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23ffffff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio],.shiny-input-container .checkbox input:checked[type=radio],.shiny-input-container .checkbox-inline input:checked[type=radio],.shiny-input-container .radio input:checked[type=radio],.shiny-input-container .radio-inline input:checked[type=radio]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23ffffff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate,.shiny-input-container .checkbox input[type=checkbox]:indeterminate,.shiny-input-container .checkbox-inline input[type=checkbox]:indeterminate,.shiny-input-container .radio input[type=checkbox]:indeterminate,.shiny-input-container .radio-inline input[type=checkbox]:indeterminate{background-color:#0d6efd;border-color:#0d6efd;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23ffffff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled,.shiny-input-container .checkbox input:disabled,.shiny-input-container .checkbox-inline input:disabled,.shiny-input-container .radio input:disabled,.shiny-input-container .radio-inline input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input[disabled]~.form-check-label,.form-check-input[disabled]~span,.form-check-input:disabled~.form-check-label,.form-check-input:disabled~span,.shiny-input-container .checkbox input[disabled]~.form-check-label,.shiny-input-container .checkbox input[disabled]~span,.shiny-input-container .checkbox input:disabled~.form-check-label,.shiny-input-container .checkbox input:disabled~span,.shiny-input-container .checkbox-inline input[disabled]~.form-check-label,.shiny-input-container .checkbox-inline input[disabled]~span,.shiny-input-container .checkbox-inline input:disabled~.form-check-label,.shiny-input-container .checkbox-inline input:disabled~span,.shiny-input-container .radio input[disabled]~.form-check-label,.shiny-input-container .radio input[disabled]~span,.shiny-input-container .radio input:disabled~.form-check-label,.shiny-input-container .radio input:disabled~span,.shiny-input-container .radio-inline input[disabled]~.form-check-label,.shiny-input-container .radio-inline input[disabled]~span,.shiny-input-container .radio-inline input:disabled~.form-check-label,.shiny-input-container .radio-inline input:disabled~span{opacity:.5}.form-check-label,.shiny-input-container .checkbox label,.shiny-input-container .checkbox-inline label,.shiny-input-container .radio label,.shiny-input-container .radio-inline label{cursor:pointer}.form-switch{padding-left:2.5em}.form-switch .form-check-input{width:2em;margin-left:-2.5em;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23ffffff'/%3e%3c/svg%3e")}.form-check-inline,.shiny-input-container .checkbox-inline,.shiny-input-container .radio-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.btn-check[disabled]+.btn,.btn-check:disabled+.btn{pointer-events:none;filter:none;opacity:.65}.form-range{width:100%;height:1.5rem;padding:0;background-color:transparent;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-0.25rem;background-color:#0d6efd;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none}@media(prefers-reduced-motion: reduce){.form-range::-webkit-slider-thumb{transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b6d4fe}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#0d6efd;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none}@media(prefers-reduced-motion: reduce){.form-range::-moz-range-thumb{transition:none}}.form-range::-moz-range-thumb:active{background-color:#b6d4fe}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.form-range:disabled::-moz-range-thumb{background-color:#adb5bd}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-select{height:calc(3.5rem + 2px);line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;pointer-events:none;border:1px solid transparent;transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media(prefers-reduced-motion: reduce){.form-floating>label{transition:none}}.form-floating>.form-control{padding:1rem .75rem}.form-floating>.form-control::placeholder{color:transparent}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{opacity:.65;transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.form-floating>.form-control:-webkit-autofill~label{opacity:.65;transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.input-group{position:relative;display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;align-items:stretch;-webkit-align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select{position:relative;flex:1 1 auto;-webkit-flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus{z-index:3}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:3}.input-group-text{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text,.input-group-lg>.btn{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text,.input-group-sm>.btn{padding:.25rem .5rem;font-size:0.875rem;border-radius:.2rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu),.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu),.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:0.875em;color:#198754}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:0.875rem;color:#fff;background-color:rgba(25,135,84,.9);border-radius:.25rem}.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip,.is-valid~.valid-feedback,.is-valid~.valid-tooltip{display:block}.was-validated .form-control:valid,.form-control.is-valid{border-color:#198754;padding-right:calc(1.5em + 0.75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(0.375em + 0.1875rem) center;background-size:calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-control:valid:focus,.form-control.is-valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + 0.75rem);background-position:top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem)}.was-validated .form-select:valid,.form-select.is-valid{border-color:#198754}.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"],.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-select:valid:focus,.form-select.is-valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated .form-check-input:valid,.form-check-input.is-valid{border-color:#198754}.was-validated .form-check-input:valid:checked,.form-check-input.is-valid:checked{background-color:#198754}.was-validated .form-check-input:valid:focus,.form-check-input.is-valid:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated .form-check-input:valid~.form-check-label,.form-check-input.is-valid~.form-check-label{color:#198754}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.was-validated .input-group .form-control:valid,.input-group .form-control.is-valid,.was-validated .input-group .form-select:valid,.input-group .form-select.is-valid{z-index:1}.was-validated .input-group .form-control:valid:focus,.input-group .form-control.is-valid:focus,.was-validated .input-group .form-select:valid:focus,.input-group .form-select.is-valid:focus{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:0.875em;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:0.875rem;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip,.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip{display:block}.was-validated .form-control:invalid,.form-control.is-invalid{border-color:#dc3545;padding-right:calc(1.5em + 0.75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(0.375em + 0.1875rem) center;background-size:calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-control:invalid:focus,.form-control.is-invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + 0.75rem);background-position:top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem)}.was-validated .form-select:invalid,.form-select.is-invalid{border-color:#dc3545}.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"],.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-select:invalid:focus,.form-select.is-invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated .form-check-input:invalid,.form-check-input.is-invalid{border-color:#dc3545}.was-validated .form-check-input:invalid:checked,.form-check-input.is-invalid:checked{background-color:#dc3545}.was-validated .form-check-input:invalid:focus,.form-check-input.is-invalid:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated .form-check-input:invalid~.form-check-label,.form-check-input.is-invalid~.form-check-label{color:#dc3545}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.was-validated .input-group .form-control:invalid,.input-group .form-control.is-invalid,.was-validated .input-group .form-select:invalid,.input-group .form-select.is-invalid{z-index:2}.was-validated .input-group .form-control:invalid:focus,.input-group .form-control.is-invalid:focus,.was-validated .input-group .form-select:invalid:focus,.input-group .form-select.is-invalid:focus{z-index:3}.btn{display:inline-block;font-weight:400;line-height:1.5;color:#212529;text-align:center;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;vertical-align:middle;cursor:pointer;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.btn{transition:none}}.btn:hover{color:#212529}.btn-check:focus+.btn,.btn:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.btn:disabled,.btn.disabled,fieldset:disabled .btn{pointer-events:none;opacity:.65}.btn-default{color:#000;background-color:#dee2e6;border-color:#dee2e6}.btn-default:hover{color:#000;background-color:#e3e6ea;border-color:#e1e5e9}.btn-check:focus+.btn-default,.btn-default:focus{color:#000;background-color:#e3e6ea;border-color:#e1e5e9;box-shadow:0 0 0 .25rem rgba(189,192,196,.5)}.btn-check:checked+.btn-default,.btn-check:active+.btn-default,.btn-default:active,.btn-default.active,.show>.btn-default.dropdown-toggle{color:#000;background-color:#e5e8eb;border-color:#e1e5e9}.btn-check:checked+.btn-default:focus,.btn-check:active+.btn-default:focus,.btn-default:active:focus,.btn-default.active:focus,.show>.btn-default.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(189,192,196,.5)}.btn-default:disabled,.btn-default.disabled{color:#000;background-color:#dee2e6;border-color:#dee2e6}.btn-primary{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-primary:hover{color:#fff;background-color:#0b5ed7;border-color:#0a58ca}.btn-check:focus+.btn-primary,.btn-primary:focus{color:#fff;background-color:#0b5ed7;border-color:#0a58ca;box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-check:checked+.btn-primary,.btn-check:active+.btn-primary,.btn-primary:active,.btn-primary.active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0a58ca;border-color:#0a53be}.btn-check:checked+.btn-primary:focus,.btn-check:active+.btn-primary:focus,.btn-primary:active:focus,.btn-primary.active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-primary:disabled,.btn-primary.disabled{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5c636a;border-color:#565e64}.btn-check:focus+.btn-secondary,.btn-secondary:focus{color:#fff;background-color:#5c636a;border-color:#565e64;box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-check:checked+.btn-secondary,.btn-check:active+.btn-secondary,.btn-secondary:active,.btn-secondary.active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#565e64;border-color:#51585e}.btn-check:checked+.btn-secondary:focus,.btn-check:active+.btn-secondary:focus,.btn-secondary:active:focus,.btn-secondary.active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-secondary:disabled,.btn-secondary.disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-success{color:#fff;background-color:#198754;border-color:#198754}.btn-success:hover{color:#fff;background-color:#157347;border-color:#146c43}.btn-check:focus+.btn-success,.btn-success:focus{color:#fff;background-color:#157347;border-color:#146c43;box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-check:checked+.btn-success,.btn-check:active+.btn-success,.btn-success:active,.btn-success.active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#146c43;border-color:#13653f}.btn-check:checked+.btn-success:focus,.btn-check:active+.btn-success:focus,.btn-success:active:focus,.btn-success.active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-success:disabled,.btn-success.disabled{color:#fff;background-color:#198754;border-color:#198754}.btn-info{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-info:hover{color:#000;background-color:#31d2f2;border-color:#25cff2}.btn-check:focus+.btn-info,.btn-info:focus{color:#000;background-color:#31d2f2;border-color:#25cff2;box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-check:checked+.btn-info,.btn-check:active+.btn-info,.btn-info:active,.btn-info.active,.show>.btn-info.dropdown-toggle{color:#000;background-color:#3dd5f3;border-color:#25cff2}.btn-check:checked+.btn-info:focus,.btn-check:active+.btn-info:focus,.btn-info:active:focus,.btn-info.active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-info:disabled,.btn-info.disabled{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-warning{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#000;background-color:#ffca2c;border-color:#ffc720}.btn-check:focus+.btn-warning,.btn-warning:focus{color:#000;background-color:#ffca2c;border-color:#ffc720;box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-check:checked+.btn-warning,.btn-check:active+.btn-warning,.btn-warning:active,.btn-warning.active,.show>.btn-warning.dropdown-toggle{color:#000;background-color:#ffcd39;border-color:#ffc720}.btn-check:checked+.btn-warning:focus,.btn-check:active+.btn-warning:focus,.btn-warning:active:focus,.btn-warning.active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-warning:disabled,.btn-warning.disabled{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#bb2d3b;border-color:#b02a37}.btn-check:focus+.btn-danger,.btn-danger:focus{color:#fff;background-color:#bb2d3b;border-color:#b02a37;box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-check:checked+.btn-danger,.btn-check:active+.btn-danger,.btn-danger:active,.btn-danger.active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#b02a37;border-color:#a52834}.btn-check:checked+.btn-danger:focus,.btn-check:active+.btn-danger:focus,.btn-danger:active:focus,.btn-danger.active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-danger:disabled,.btn-danger.disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-light{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:focus+.btn-light,.btn-light:focus{color:#000;background-color:#f9fafb;border-color:#f9fafb;box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-check:checked+.btn-light,.btn-check:active+.btn-light,.btn-light:active,.btn-light.active,.show>.btn-light.dropdown-toggle{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:checked+.btn-light:focus,.btn-check:active+.btn-light:focus,.btn-light:active:focus,.btn-light.active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-light:disabled,.btn-light.disabled{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-dark{color:#fff;background-color:#212529;border-color:#212529}.btn-dark:hover{color:#fff;background-color:#1c1f23;border-color:#1a1e21}.btn-check:focus+.btn-dark,.btn-dark:focus{color:#fff;background-color:#1c1f23;border-color:#1a1e21;box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-check:checked+.btn-dark,.btn-check:active+.btn-dark,.btn-dark:active,.btn-dark.active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1a1e21;border-color:#191c1f}.btn-check:checked+.btn-dark:focus,.btn-check:active+.btn-dark:focus,.btn-dark:active:focus,.btn-dark.active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-dark:disabled,.btn-dark.disabled{color:#fff;background-color:#212529;border-color:#212529}.btn-outline-default{color:#dee2e6;border-color:#dee2e6;background-color:transparent}.btn-outline-default:hover{color:#000;background-color:#dee2e6;border-color:#dee2e6}.btn-check:focus+.btn-outline-default,.btn-outline-default:focus{box-shadow:0 0 0 .25rem rgba(222,226,230,.5)}.btn-check:checked+.btn-outline-default,.btn-check:active+.btn-outline-default,.btn-outline-default:active,.btn-outline-default.active,.btn-outline-default.dropdown-toggle.show{color:#000;background-color:#dee2e6;border-color:#dee2e6}.btn-check:checked+.btn-outline-default:focus,.btn-check:active+.btn-outline-default:focus,.btn-outline-default:active:focus,.btn-outline-default.active:focus,.btn-outline-default.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(222,226,230,.5)}.btn-outline-default:disabled,.btn-outline-default.disabled{color:#dee2e6;background-color:transparent}.btn-outline-primary{color:#0d6efd;border-color:#0d6efd;background-color:transparent}.btn-outline-primary:hover{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:focus+.btn-outline-primary,.btn-outline-primary:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-check:checked+.btn-outline-primary,.btn-check:active+.btn-outline-primary,.btn-outline-primary:active,.btn-outline-primary.active,.btn-outline-primary.dropdown-toggle.show{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:checked+.btn-outline-primary:focus,.btn-check:active+.btn-outline-primary:focus,.btn-outline-primary:active:focus,.btn-outline-primary.active:focus,.btn-outline-primary.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-outline-primary:disabled,.btn-outline-primary.disabled{color:#0d6efd;background-color:transparent}.btn-outline-secondary{color:#6c757d;border-color:#6c757d;background-color:transparent}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:focus+.btn-outline-secondary,.btn-outline-secondary:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-check:checked+.btn-outline-secondary,.btn-check:active+.btn-outline-secondary,.btn-outline-secondary:active,.btn-outline-secondary.active,.btn-outline-secondary.dropdown-toggle.show{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:checked+.btn-outline-secondary:focus,.btn-check:active+.btn-outline-secondary:focus,.btn-outline-secondary:active:focus,.btn-outline-secondary.active:focus,.btn-outline-secondary.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-outline-secondary:disabled,.btn-outline-secondary.disabled{color:#6c757d;background-color:transparent}.btn-outline-success{color:#198754;border-color:#198754;background-color:transparent}.btn-outline-success:hover{color:#fff;background-color:#198754;border-color:#198754}.btn-check:focus+.btn-outline-success,.btn-outline-success:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-check:checked+.btn-outline-success,.btn-check:active+.btn-outline-success,.btn-outline-success:active,.btn-outline-success.active,.btn-outline-success.dropdown-toggle.show{color:#fff;background-color:#198754;border-color:#198754}.btn-check:checked+.btn-outline-success:focus,.btn-check:active+.btn-outline-success:focus,.btn-outline-success:active:focus,.btn-outline-success.active:focus,.btn-outline-success.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-outline-success:disabled,.btn-outline-success.disabled{color:#198754;background-color:transparent}.btn-outline-info{color:#0dcaf0;border-color:#0dcaf0;background-color:transparent}.btn-outline-info:hover{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:focus+.btn-outline-info,.btn-outline-info:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-check:checked+.btn-outline-info,.btn-check:active+.btn-outline-info,.btn-outline-info:active,.btn-outline-info.active,.btn-outline-info.dropdown-toggle.show{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:checked+.btn-outline-info:focus,.btn-check:active+.btn-outline-info:focus,.btn-outline-info:active:focus,.btn-outline-info.active:focus,.btn-outline-info.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-outline-info:disabled,.btn-outline-info.disabled{color:#0dcaf0;background-color:transparent}.btn-outline-warning{color:#ffc107;border-color:#ffc107;background-color:transparent}.btn-outline-warning:hover{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:focus+.btn-outline-warning,.btn-outline-warning:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-check:checked+.btn-outline-warning,.btn-check:active+.btn-outline-warning,.btn-outline-warning:active,.btn-outline-warning.active,.btn-outline-warning.dropdown-toggle.show{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:checked+.btn-outline-warning:focus,.btn-check:active+.btn-outline-warning:focus,.btn-outline-warning:active:focus,.btn-outline-warning.active:focus,.btn-outline-warning.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-outline-warning:disabled,.btn-outline-warning.disabled{color:#ffc107;background-color:transparent}.btn-outline-danger{color:#dc3545;border-color:#dc3545;background-color:transparent}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:focus+.btn-outline-danger,.btn-outline-danger:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-check:checked+.btn-outline-danger,.btn-check:active+.btn-outline-danger,.btn-outline-danger:active,.btn-outline-danger.active,.btn-outline-danger.dropdown-toggle.show{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:checked+.btn-outline-danger:focus,.btn-check:active+.btn-outline-danger:focus,.btn-outline-danger:active:focus,.btn-outline-danger.active:focus,.btn-outline-danger.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-outline-danger:disabled,.btn-outline-danger.disabled{color:#dc3545;background-color:transparent}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa;background-color:transparent}.btn-outline-light:hover{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:focus+.btn-outline-light,.btn-outline-light:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-check:checked+.btn-outline-light,.btn-check:active+.btn-outline-light,.btn-outline-light:active,.btn-outline-light.active,.btn-outline-light.dropdown-toggle.show{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:checked+.btn-outline-light:focus,.btn-check:active+.btn-outline-light:focus,.btn-outline-light:active:focus,.btn-outline-light.active:focus,.btn-outline-light.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-outline-light:disabled,.btn-outline-light.disabled{color:#f8f9fa;background-color:transparent}.btn-outline-dark{color:#212529;border-color:#212529;background-color:transparent}.btn-outline-dark:hover{color:#fff;background-color:#212529;border-color:#212529}.btn-check:focus+.btn-outline-dark,.btn-outline-dark:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-check:checked+.btn-outline-dark,.btn-check:active+.btn-outline-dark,.btn-outline-dark:active,.btn-outline-dark.active,.btn-outline-dark.dropdown-toggle.show{color:#fff;background-color:#212529;border-color:#212529}.btn-check:checked+.btn-outline-dark:focus,.btn-check:active+.btn-outline-dark:focus,.btn-outline-dark:active:focus,.btn-outline-dark.active:focus,.btn-outline-dark.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-outline-dark:disabled,.btn-outline-dark.disabled{color:#212529;background-color:transparent}.btn-link{font-weight:400;color:#0d6efd;text-decoration:underline;-webkit-text-decoration:underline;-moz-text-decoration:underline;-ms-text-decoration:underline;-o-text-decoration:underline}.btn-link:hover{color:#0a58ca}.btn-link:disabled,.btn-link.disabled{color:#6c757d}.btn-lg,.btn-group-lg>.btn{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.btn-sm,.btn-group-sm>.btn{padding:.25rem .5rem;font-size:0.875rem;border-radius:.2rem}.fade{transition:opacity .15s linear}@media(prefers-reduced-motion: reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .2s ease}@media(prefers-reduced-motion: reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s ease}@media(prefers-reduced-motion: reduce){.collapsing.collapse-horizontal{transition:none}}.dropup,.dropend,.dropdown,.dropstart{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;z-index:1000;display:none;min-width:10rem;padding:.5rem 0;margin:0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:.125rem}.dropdown-menu-start{--bs-position: start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position: end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media(min-width: 576px){.dropdown-menu-sm-start{--bs-position: start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position: end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 768px){.dropdown-menu-md-start{--bs-position: start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position: end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 992px){.dropdown-menu-lg-start{--bs-position: start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position: end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 1200px){.dropdown-menu-xl-start{--bs-position: start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position: end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 1400px){.dropdown-menu-xxl-start{--bs-position: start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position: end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid rgba(0,0,0,.15)}.dropdown-item{display:block;width:100%;padding:.25rem 1rem;clear:both;font-weight:400;color:#212529;text-align:inherit;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:hover,.dropdown-item:focus{color:#1e2125;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#0d6efd}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1rem;margin-bottom:0;font-size:0.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1rem;color:#212529}.dropdown-menu-dark{color:#dee2e6;background-color:#343a40;border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item{color:#dee2e6}.dropdown-menu-dark .dropdown-item:hover,.dropdown-menu-dark .dropdown-item:focus{color:#fff;background-color:rgba(255,255,255,.15)}.dropdown-menu-dark .dropdown-item.active,.dropdown-menu-dark .dropdown-item:active{color:#fff;background-color:#0d6efd}.dropdown-menu-dark .dropdown-item.disabled,.dropdown-menu-dark .dropdown-item:disabled{color:#adb5bd}.dropdown-menu-dark .dropdown-divider{border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item-text{color:#dee2e6}.dropdown-menu-dark .dropdown-header{color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;flex:1 1 auto;-webkit-flex:1 1 auto}.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn:hover,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn.active{z-index:1}.btn-toolbar{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;justify-content:flex-start;-webkit-justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn:not(:first-child),.btn-group>.btn-group:not(:first-child){margin-left:-1px}.btn-group>.btn:not(:last-child):not(.dropdown-toggle),.btn-group>.btn-group:not(:last-child)>.btn{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn,.btn-group>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-sm+.dropdown-toggle-split,.btn-group-sm>.btn+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-lg+.dropdown-toggle-split,.btn-group-lg>.btn+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;-webkit-flex-direction:column;align-items:flex-start;-webkit-align-items:flex-start;justify-content:center;-webkit-justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn:not(:first-child),.btn-group-vertical>.btn-group:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle),.btn-group-vertical>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn~.btn,.btn-group-vertical>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem;color:#0d6efd;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media(prefers-reduced-motion: reduce){.nav-link{transition:none}}.nav-link:hover,.nav-link:focus{color:#0a58ca}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;background:none;border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:hover,.nav-tabs .nav-link:focus{border-color:#e9ecef #e9ecef #dee2e6;isolation:isolate}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-link.active,.nav-tabs .nav-item.show .nav-link{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{background:none;border:0;border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#0d6efd}.nav-fill>.nav-link,.nav-fill .nav-item{flex:1 1 auto;-webkit-flex:1 1 auto;text-align:center}.nav-justified>.nav-link,.nav-justified .nav-item{flex-basis:0;-webkit-flex-basis:0;flex-grow:1;-webkit-flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between;padding-top:.5rem;padding-bottom:.5rem}.navbar>.container-xxl,.navbar>.container-xl,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container,.navbar>.container-fluid{display:flex;display:-webkit-flex;flex-wrap:inherit;-webkit-flex-wrap:inherit;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between}.navbar-brand{padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;white-space:nowrap}.navbar-nav{display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;-webkit-flex-basis:100%;flex-grow:1;-webkit-flex-grow:1;align-items:center;-webkit-align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem;transition:box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 .25rem}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height, 75vh);overflow-y:auto}@media(min-width: 576px){.navbar-expand-sm{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas-header{display:none}.navbar-expand-sm .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-sm .offcanvas-top,.navbar-expand-sm .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-sm .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 768px){.navbar-expand-md{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas-header{display:none}.navbar-expand-md .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-md .offcanvas-top,.navbar-expand-md .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-md .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 992px){.navbar-expand-lg{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas-header{display:none}.navbar-expand-lg .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-lg .offcanvas-top,.navbar-expand-lg .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-lg .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 1200px){.navbar-expand-xl{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas-header{display:none}.navbar-expand-xl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xl .offcanvas-top,.navbar-expand-xl .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-xl .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 1400px){.navbar-expand-xxl{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xxl .offcanvas-top,.navbar-expand-xxl .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-xxl .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas-header{display:none}.navbar-expand .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand .offcanvas-top,.navbar-expand .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}.navbar-light{background-color:#0d6efd}.navbar-light .navbar-brand{color:#fdfeff}.navbar-light .navbar-brand:hover,.navbar-light .navbar-brand:focus{color:#fdfeff}.navbar-light .navbar-nav .nav-link{color:#fdfeff}.navbar-light .navbar-nav .nav-link:hover,.navbar-light .navbar-nav .nav-link:focus{color:rgba(253,254,255,.8)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(253,254,255,.75)}.navbar-light .navbar-nav .show>.nav-link,.navbar-light .navbar-nav .nav-link.active{color:#fdfeff}.navbar-light .navbar-toggler{color:#fdfeff;border-color:rgba(253,254,255,.4)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='%23fdfeff' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:#fdfeff}.navbar-light .navbar-text a,.navbar-light .navbar-text a:hover,.navbar-light .navbar-text a:focus{color:#fdfeff}.navbar-dark{background-color:#0d6efd}.navbar-dark .navbar-brand{color:#fdfeff}.navbar-dark .navbar-brand:hover,.navbar-dark .navbar-brand:focus{color:#fdfeff}.navbar-dark .navbar-nav .nav-link{color:#fdfeff}.navbar-dark .navbar-nav .nav-link:hover,.navbar-dark .navbar-nav .nav-link:focus{color:rgba(253,254,255,.8)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(253,254,255,.75)}.navbar-dark .navbar-nav .show>.nav-link,.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active{color:#fdfeff}.navbar-dark .navbar-toggler{color:#fdfeff;border-color:rgba(253,254,255,.4)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='%23fdfeff' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:#fdfeff}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:hover,.navbar-dark .navbar-text a:focus{color:#fdfeff}.card{position:relative;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(0.25rem - 1px);border-bottom-left-radius:calc(0.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;-webkit-flex:1 1 auto;padding:1rem 1rem}.card-title{margin-bottom:.5rem}.card-subtitle{margin-top:-0.25rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:1rem}.card-header{padding:.5rem 1rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0}.card-footer{padding:.5rem 1rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(0.25rem - 1px) calc(0.25rem - 1px)}.card-header-tabs{margin-right:-0.5rem;margin-bottom:-0.5rem;margin-left:-0.5rem;border-bottom:0}.card-header-pills{margin-right:-0.5rem;margin-left:-0.5rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(0.25rem - 1px)}.card-img,.card-img-top,.card-img-bottom{width:100%}.card-img,.card-img-top{border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(0.25rem - 1px);border-bottom-left-radius:calc(0.25rem - 1px)}.card-group>.card{margin-bottom:.75rem}@media(min-width: 576px){.card-group{display:flex;display:-webkit-flex;flex-flow:row wrap;-webkit-flex-flow:row wrap}.card-group>.card{flex:1 0 0%;-webkit-flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-img-top,.card-group>.card:not(:last-child) .card-header{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-img-bottom,.card-group>.card:not(:last-child) .card-footer{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-img-top,.card-group>.card:not(:first-child) .card-header{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-img-bottom,.card-group>.card:not(:first-child) .card-footer{border-bottom-left-radius:0}}.accordion-button{position:relative;display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;width:100%;padding:1rem 1.25rem;font-size:1rem;color:#212529;text-align:left;background-color:#fff;border:0;border-radius:0;overflow-anchor:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,border-radius .15s ease}@media(prefers-reduced-motion: reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:#0c63e4;background-color:#e7f1ff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.125)}.accordion-button:not(.collapsed)::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%230c63e4'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");transform:rotate(-180deg)}.accordion-button::after{flex-shrink:0;-webkit-flex-shrink:0;width:1.25rem;height:1.25rem;margin-left:auto;content:"";background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-size:1.25rem;transition:transform .2s ease-in-out}@media(prefers-reduced-motion: reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.accordion-header{margin-bottom:0}.accordion-item{background-color:#fff;border:1px solid rgba(0,0,0,.125)}.accordion-item:first-of-type{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.accordion-item:first-of-type .accordion-button{border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-item:last-of-type .accordion-button.collapsed{border-bottom-right-radius:calc(0.25rem - 1px);border-bottom-left-radius:calc(0.25rem - 1px)}.accordion-item:last-of-type .accordion-collapse{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-body{padding:1rem 1.25rem}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.accordion-flush .accordion-item .accordion-button{border-radius:0}.breadcrumb{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;padding:0 0;margin-bottom:1rem;list-style:none}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:var(--bs-breadcrumb-divider, "/") /* rtl: var(--bs-breadcrumb-divider, "/") */}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;display:-webkit-flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;color:#0d6efd;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;background-color:#fff;border:1px solid #dee2e6;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:#0a58ca;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;color:#0a58ca;background-color:#e9ecef;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.page-item:not(:first-child) .page-link{margin-left:-1px}.page-item.active .page-link{z-index:3;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;background-color:#fff;border-color:#dee2e6}.page-link{padding:.375rem .75rem}.page-item:first-child .page-link{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:0.875rem}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.35em .65em;font-size:0.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{position:relative;padding:1rem 1rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-default{color:#595a5c;background-color:#f8f9fa;border-color:#f5f6f8}.alert-default .alert-link{color:#47484a}.alert-primary{color:#084298;background-color:#cfe2ff;border-color:#b6d4fe}.alert-primary .alert-link{color:#06357a}.alert-secondary{color:#41464b;background-color:#e2e3e5;border-color:#d3d6d8}.alert-secondary .alert-link{color:#34383c}.alert-success{color:#0f5132;background-color:#d1e7dd;border-color:#badbcc}.alert-success .alert-link{color:#0c4128}.alert-info{color:#055160;background-color:#cff4fc;border-color:#b6effb}.alert-info .alert-link{color:#04414d}.alert-warning{color:#664d03;background-color:#fff3cd;border-color:#ffecb5}.alert-warning .alert-link{color:#523e02}.alert-danger{color:#842029;background-color:#f8d7da;border-color:#f5c2c7}.alert-danger .alert-link{color:#6a1a21}.alert-light{color:#636464;background-color:#fefefe;border-color:#fdfdfe}.alert-light .alert-link{color:#4f5050}.alert-dark{color:#141619;background-color:#d3d3d4;border-color:#bcbebf}.alert-dark .alert-link{color:#101214}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress{display:flex;display:-webkit-flex;height:1rem;overflow:hidden;font-size:0.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;justify-content:center;-webkit-justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#0d6efd;transition:width .6s ease}@media(prefers-reduced-motion: reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-size:1rem 1rem}.progress-bar-animated{animation:1s linear infinite progress-bar-stripes}@media(prefers-reduced-motion: reduce){.progress-bar-animated{animation:none}}.list-group{display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>li::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:hover,.list-group-item-action:focus{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.5rem 1rem;color:#212529;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media(min-width: 576px){.list-group-horizontal-sm{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width: 768px){.list-group-horizontal-md{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width: 992px){.list-group-horizontal-lg{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width: 1200px){.list-group-horizontal-xl{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width: 1400px){.list-group-horizontal-xxl{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-default{color:#595a5c;background-color:#f8f9fa}.list-group-item-default.list-group-item-action:hover,.list-group-item-default.list-group-item-action:focus{color:#595a5c;background-color:#dfe0e1}.list-group-item-default.list-group-item-action.active{color:#fff;background-color:#595a5c;border-color:#595a5c}.list-group-item-primary{color:#084298;background-color:#cfe2ff}.list-group-item-primary.list-group-item-action:hover,.list-group-item-primary.list-group-item-action:focus{color:#084298;background-color:#bacbe6}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#084298;border-color:#084298}.list-group-item-secondary{color:#41464b;background-color:#e2e3e5}.list-group-item-secondary.list-group-item-action:hover,.list-group-item-secondary.list-group-item-action:focus{color:#41464b;background-color:#cbccce}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#41464b;border-color:#41464b}.list-group-item-success{color:#0f5132;background-color:#d1e7dd}.list-group-item-success.list-group-item-action:hover,.list-group-item-success.list-group-item-action:focus{color:#0f5132;background-color:#bcd0c7}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#0f5132;border-color:#0f5132}.list-group-item-info{color:#055160;background-color:#cff4fc}.list-group-item-info.list-group-item-action:hover,.list-group-item-info.list-group-item-action:focus{color:#055160;background-color:#badce3}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#055160;border-color:#055160}.list-group-item-warning{color:#664d03;background-color:#fff3cd}.list-group-item-warning.list-group-item-action:hover,.list-group-item-warning.list-group-item-action:focus{color:#664d03;background-color:#e6dbb9}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#664d03;border-color:#664d03}.list-group-item-danger{color:#842029;background-color:#f8d7da}.list-group-item-danger.list-group-item-action:hover,.list-group-item-danger.list-group-item-action:focus{color:#842029;background-color:#dfc2c4}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#842029;border-color:#842029}.list-group-item-light{color:#636464;background-color:#fefefe}.list-group-item-light.list-group-item-action:hover,.list-group-item-light.list-group-item-action:focus{color:#636464;background-color:#e5e5e5}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#636464;border-color:#636464}.list-group-item-dark{color:#141619;background-color:#d3d3d4}.list-group-item-dark.list-group-item-action:hover,.list-group-item-dark.list-group-item-action:focus{color:#141619;background-color:#bebebf}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#141619;border-color:#141619}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:#000;background:transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;border:0;border-radius:.25rem;opacity:.5}.btn-close:hover{color:#000;text-decoration:none;opacity:.75}.btn-close:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25);opacity:1}.btn-close:disabled,.btn-close.disabled{pointer-events:none;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;opacity:.25}.btn-close-white{filter:invert(1) grayscale(100%) brightness(200%)}.toast{width:350px;max-width:100%;font-size:0.875rem;pointer-events:auto;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .5rem 1rem rgba(0,0,0,.15);border-radius:.25rem}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{width:max-content;width:-webkit-max-content;width:-moz-max-content;width:-ms-max-content;width:-o-max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:.75rem}.toast-header{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;padding:.5rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.toast-header .btn-close{margin-right:-0.375rem;margin-left:.75rem}.toast-body{padding:.75rem;word-wrap:break-word}.modal{position:fixed;top:0;left:0;z-index:1055;display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0, -50px)}@media(prefers-reduced-motion: reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;min-height:calc(100% - 1rem)}.modal-content{position:relative;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1050;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;display:-webkit-flex;flex-shrink:0;-webkit-flex-shrink:0;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(0.3rem - 1px);border-top-right-radius:calc(0.3rem - 1px)}.modal-header .btn-close{padding:.5rem .5rem;margin:-0.5rem -0.5rem -0.5rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;-webkit-flex:1 1 auto;padding:1rem}.modal-footer{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;flex-shrink:0;-webkit-flex-shrink:0;align-items:center;-webkit-align-items:center;justify-content:flex-end;-webkit-justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(0.3rem - 1px);border-bottom-left-radius:calc(0.3rem - 1px)}.modal-footer>*{margin:.25rem}@media(min-width: 576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{height:calc(100% - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-sm{max-width:300px}}@media(min-width: 992px){.modal-lg,.modal-xl{max-width:800px}}@media(min-width: 1200px){.modal-xl{max-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}.modal-fullscreen .modal-footer{border-radius:0}@media(max-width: 575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}.modal-fullscreen-sm-down .modal-footer{border-radius:0}}@media(max-width: 767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}.modal-fullscreen-md-down .modal-footer{border-radius:0}}@media(max-width: 991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}.modal-fullscreen-lg-down .modal-footer{border-radius:0}}@media(max-width: 1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}.modal-fullscreen-xl-down .modal-footer{border-radius:0}}@media(max-width: 1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}.modal-fullscreen-xxl-down .modal-footer{border-radius:0}}.tooltip{position:absolute;z-index:1080;display:block;margin:0;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:0.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .tooltip-arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-top,.bs-tooltip-auto[data-popper-placement^=top]{padding:.4rem 0}.bs-tooltip-top .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow{bottom:0}.bs-tooltip-top .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before{top:-1px;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-end,.bs-tooltip-auto[data-popper-placement^=right]{padding:0 .4rem}.bs-tooltip-end .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-end .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before{right:-1px;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-bottom,.bs-tooltip-auto[data-popper-placement^=bottom]{padding:.4rem 0}.bs-tooltip-bottom .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow{top:0}.bs-tooltip-bottom .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before{bottom:-1px;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-start,.bs-tooltip-auto[data-popper-placement^=left]{padding:0 .4rem}.bs-tooltip-start .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-start .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before{left:-1px;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0 /* rtl:ignore */;z-index:1070;display:block;max-width:276px;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:0.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .popover-arrow{position:absolute;display:block;width:1rem;height:.5rem}.popover .popover-arrow::before,.popover .popover-arrow::after{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-top>.popover-arrow,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow{bottom:calc(-0.5rem - 1px)}.bs-popover-top>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-top>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-end>.popover-arrow,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow{left:calc(-0.5rem - 1px);width:.5rem;height:1rem}.bs-popover-end>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-end>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-bottom>.popover-arrow,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow{top:calc(-0.5rem - 1px)}.bs-popover-bottom>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-bottom>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-bottom .popover-header::before,.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-0.5rem;content:"";border-bottom:1px solid #f0f0f0}.bs-popover-start>.popover-arrow,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow{right:calc(-0.5rem - 1px);width:.5rem;height:1rem}.bs-popover-start>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-start>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem 1rem;margin-bottom:0;font-size:1rem;background-color:#f0f0f0;border-bottom:1px solid rgba(0,0,0,.2);border-top-left-radius:calc(0.3rem - 1px);border-top-right-radius:calc(0.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:1rem 1rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y;-webkit-touch-action:pan-y;-moz-touch-action:pan-y;-ms-touch-action:pan-y;-o-touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;backface-visibility:hidden;-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;-ms-backface-visibility:hidden;-o-backface-visibility:hidden;transition:transform .6s ease-in-out}@media(prefers-reduced-motion: reduce){.carousel-item{transition:none}}.carousel-item.active,.carousel-item-next,.carousel-item-prev{display:block}.carousel-item-next:not(.carousel-item-start),.active.carousel-item-end{transform:translateX(100%)}.carousel-item-prev:not(.carousel-item-end),.active.carousel-item-start{transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item.active,.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end{z-index:1;opacity:1}.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{z-index:0;opacity:0;transition:opacity 0s .6s}@media(prefers-reduced-motion: reduce){.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{transition:none}}.carousel-control-prev,.carousel-control-next{position:absolute;top:0;bottom:0;z-index:1;display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;justify-content:center;-webkit-justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:none;border:0;opacity:.5;transition:opacity .15s ease}@media(prefers-reduced-motion: reduce){.carousel-control-prev,.carousel-control-next{transition:none}}.carousel-control-prev:hover,.carousel-control-prev:focus,.carousel-control-next:hover,.carousel-control-next:focus{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-prev-icon,.carousel-control-next-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23ffffff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23ffffff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;display:-webkit-flex;justify-content:center;-webkit-justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%;list-style:none}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;-webkit-flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media(prefers-reduced-motion: reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-prev-icon,.carousel-dark .carousel-control-next-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}@keyframes spinner-border{to{transform:rotate(360deg) /* rtl:ignore */}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:-0.125em;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-0.125em;background-color:currentColor;border-radius:50%;opacity:0;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media(prefers-reduced-motion: reduce){.spinner-border,.spinner-grow{animation-duration:1.5s;-webkit-animation-duration:1.5s;-moz-animation-duration:1.5s;-ms-animation-duration:1.5s;-o-animation-duration:1.5s}}.offcanvas{position:fixed;bottom:0;z-index:1045;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;max-width:100%;visibility:hidden;background-color:#fff;background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}@media(prefers-reduced-motion: reduce){.offcanvas{transition:none}}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between;padding:1rem 1rem}.offcanvas-header .btn-close{padding:.5rem .5rem;margin-top:-0.5rem;margin-right:-0.5rem;margin-bottom:-0.5rem}.offcanvas-title{margin-bottom:0;line-height:1.5}.offcanvas-body{flex-grow:1;-webkit-flex-grow:1;padding:1rem 1rem;overflow-y:auto}.offcanvas-start{top:0;left:0;width:400px;border-right:1px solid rgba(0,0,0,.2);transform:translateX(-100%)}.offcanvas-end{top:0;right:0;width:400px;border-left:1px solid rgba(0,0,0,.2);transform:translateX(100%)}.offcanvas-top{top:0;right:0;left:0;height:30vh;max-height:100%;border-bottom:1px solid rgba(0,0,0,.2);transform:translateY(-100%)}.offcanvas-bottom{right:0;left:0;height:30vh;max-height:100%;border-top:1px solid rgba(0,0,0,.2);transform:translateY(100%)}.offcanvas.show{transform:none}.placeholder{display:inline-block;min-height:1em;vertical-align:middle;cursor:wait;background-color:currentColor;opacity:.5}.placeholder.btn::before{display:inline-block;content:""}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{animation:placeholder-glow 2s ease-in-out infinite}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{mask-image:linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%);-webkit-mask-image:linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%);mask-size:200% 100%;-webkit-mask-size:200% 100%;animation:placeholder-wave 2s linear infinite}@keyframes placeholder-wave{100%{mask-position:-200% 0%;-webkit-mask-position:-200% 0%}}.clearfix::after{display:block;clear:both;content:""}.link-default{color:#dee2e6}.link-default:hover,.link-default:focus{color:#e5e8eb}.link-primary{color:#0d6efd}.link-primary:hover,.link-primary:focus{color:#0a58ca}.link-secondary{color:#6c757d}.link-secondary:hover,.link-secondary:focus{color:#565e64}.link-success{color:#198754}.link-success:hover,.link-success:focus{color:#146c43}.link-info{color:#0dcaf0}.link-info:hover,.link-info:focus{color:#3dd5f3}.link-warning{color:#ffc107}.link-warning:hover,.link-warning:focus{color:#ffcd39}.link-danger{color:#dc3545}.link-danger:hover,.link-danger:focus{color:#b02a37}.link-light{color:#f8f9fa}.link-light:hover,.link-light:focus{color:#f9fafb}.link-dark{color:#212529}.link-dark:hover,.link-dark:focus{color:#1a1e21}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio: 100%}.ratio-4x3{--bs-aspect-ratio: calc(3 / 4 * 100%)}.ratio-16x9{--bs-aspect-ratio: calc(9 / 16 * 100%)}.ratio-21x9{--bs-aspect-ratio: calc(9 / 21 * 100%)}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:sticky;top:0;z-index:1020}@media(min-width: 576px){.sticky-sm-top{position:sticky;top:0;z-index:1020}}@media(min-width: 768px){.sticky-md-top{position:sticky;top:0;z-index:1020}}@media(min-width: 992px){.sticky-lg-top{position:sticky;top:0;z-index:1020}}@media(min-width: 1200px){.sticky-xl-top{position:sticky;top:0;z-index:1020}}@media(min-width: 1400px){.sticky-xxl-top{position:sticky;top:0;z-index:1020}}.hstack{display:flex;display:-webkit-flex;flex-direction:row;-webkit-flex-direction:row;align-items:center;-webkit-align-items:center;align-self:stretch;-webkit-align-self:stretch}.vstack{display:flex;display:-webkit-flex;flex:1 1 auto;-webkit-flex:1 1 auto;flex-direction:column;-webkit-flex-direction:column;align-self:stretch;-webkit-align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute !important;width:1px !important;height:1px !important;padding:0 !important;margin:-1px !important;overflow:hidden !important;clip:rect(0, 0, 0, 0) !important;white-space:nowrap !important;border:0 !important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;-webkit-align-self:stretch;width:1px;min-height:1em;background-color:currentColor;opacity:.25}.align-baseline{vertical-align:baseline !important}.align-top{vertical-align:top !important}.align-middle{vertical-align:middle !important}.align-bottom{vertical-align:bottom !important}.align-text-bottom{vertical-align:text-bottom !important}.align-text-top{vertical-align:text-top !important}.float-start{float:left !important}.float-end{float:right !important}.float-none{float:none !important}.opacity-0{opacity:0 !important}.opacity-25{opacity:.25 !important}.opacity-50{opacity:.5 !important}.opacity-75{opacity:.75 !important}.opacity-100{opacity:1 !important}.overflow-auto{overflow:auto !important}.overflow-hidden{overflow:hidden !important}.overflow-visible{overflow:visible !important}.overflow-scroll{overflow:scroll !important}.d-inline{display:inline !important}.d-inline-block{display:inline-block !important}.d-block{display:block !important}.d-grid{display:grid !important}.d-table{display:table !important}.d-table-row{display:table-row !important}.d-table-cell{display:table-cell !important}.d-flex{display:flex !important}.d-inline-flex{display:inline-flex !important}.d-none{display:none !important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15) !important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075) !important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175) !important}.shadow-none{box-shadow:none !important}.position-static{position:static !important}.position-relative{position:relative !important}.position-absolute{position:absolute !important}.position-fixed{position:fixed !important}.position-sticky{position:sticky !important}.top-0{top:0 !important}.top-50{top:50% !important}.top-100{top:100% !important}.bottom-0{bottom:0 !important}.bottom-50{bottom:50% !important}.bottom-100{bottom:100% !important}.start-0{left:0 !important}.start-50{left:50% !important}.start-100{left:100% !important}.end-0{right:0 !important}.end-50{right:50% !important}.end-100{right:100% !important}.translate-middle{transform:translate(-50%, -50%) !important}.translate-middle-x{transform:translateX(-50%) !important}.translate-middle-y{transform:translateY(-50%) !important}.border{border:1px solid #dee2e6 !important}.border-0{border:0 !important}.border-top{border-top:1px solid #dee2e6 !important}.border-top-0{border-top:0 !important}.border-end{border-right:1px solid #dee2e6 !important}.border-end-0{border-right:0 !important}.border-bottom{border-bottom:1px solid #dee2e6 !important}.border-bottom-0{border-bottom:0 !important}.border-start{border-left:1px solid #dee2e6 !important}.border-start-0{border-left:0 !important}.border-default{border-color:#dee2e6 !important}.border-primary{border-color:#0d6efd !important}.border-secondary{border-color:#6c757d !important}.border-success{border-color:#198754 !important}.border-info{border-color:#0dcaf0 !important}.border-warning{border-color:#ffc107 !important}.border-danger{border-color:#dc3545 !important}.border-light{border-color:#f8f9fa !important}.border-dark{border-color:#212529 !important}.border-white{border-color:#fff !important}.border-1{border-width:1px !important}.border-2{border-width:2px !important}.border-3{border-width:3px !important}.border-4{border-width:4px !important}.border-5{border-width:5px !important}.w-25{width:25% !important}.w-50{width:50% !important}.w-75{width:75% !important}.w-100{width:100% !important}.w-auto{width:auto !important}.mw-100{max-width:100% !important}.vw-100{width:100vw !important}.min-vw-100{min-width:100vw !important}.h-25{height:25% !important}.h-50{height:50% !important}.h-75{height:75% !important}.h-100{height:100% !important}.h-auto{height:auto !important}.mh-100{max-height:100% !important}.vh-100{height:100vh !important}.min-vh-100{min-height:100vh !important}.flex-fill{flex:1 1 auto !important}.flex-row{flex-direction:row !important}.flex-column{flex-direction:column !important}.flex-row-reverse{flex-direction:row-reverse !important}.flex-column-reverse{flex-direction:column-reverse !important}.flex-grow-0{flex-grow:0 !important}.flex-grow-1{flex-grow:1 !important}.flex-shrink-0{flex-shrink:0 !important}.flex-shrink-1{flex-shrink:1 !important}.flex-wrap{flex-wrap:wrap !important}.flex-nowrap{flex-wrap:nowrap !important}.flex-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-0{gap:0 !important}.gap-1{gap:.25rem !important}.gap-2{gap:.5rem !important}.gap-3{gap:1rem !important}.gap-4{gap:1.5rem !important}.gap-5{gap:3rem !important}.justify-content-start{justify-content:flex-start !important}.justify-content-end{justify-content:flex-end !important}.justify-content-center{justify-content:center !important}.justify-content-between{justify-content:space-between !important}.justify-content-around{justify-content:space-around !important}.justify-content-evenly{justify-content:space-evenly !important}.align-items-start{align-items:flex-start !important}.align-items-end{align-items:flex-end !important}.align-items-center{align-items:center !important}.align-items-baseline{align-items:baseline !important}.align-items-stretch{align-items:stretch !important}.align-content-start{align-content:flex-start !important}.align-content-end{align-content:flex-end !important}.align-content-center{align-content:center !important}.align-content-between{align-content:space-between !important}.align-content-around{align-content:space-around !important}.align-content-stretch{align-content:stretch !important}.align-self-auto{align-self:auto !important}.align-self-start{align-self:flex-start !important}.align-self-end{align-self:flex-end !important}.align-self-center{align-self:center !important}.align-self-baseline{align-self:baseline !important}.align-self-stretch{align-self:stretch !important}.order-first{order:-1 !important}.order-0{order:0 !important}.order-1{order:1 !important}.order-2{order:2 !important}.order-3{order:3 !important}.order-4{order:4 !important}.order-5{order:5 !important}.order-last{order:6 !important}.m-0{margin:0 !important}.m-1{margin:.25rem !important}.m-2{margin:.5rem !important}.m-3{margin:1rem !important}.m-4{margin:1.5rem !important}.m-5{margin:3rem !important}.m-auto{margin:auto !important}.mx-0{margin-right:0 !important;margin-left:0 !important}.mx-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-3{margin-right:1rem !important;margin-left:1rem !important}.mx-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-5{margin-right:3rem !important;margin-left:3rem !important}.mx-auto{margin-right:auto !important;margin-left:auto !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-0{margin-top:0 !important}.mt-1{margin-top:.25rem !important}.mt-2{margin-top:.5rem !important}.mt-3{margin-top:1rem !important}.mt-4{margin-top:1.5rem !important}.mt-5{margin-top:3rem !important}.mt-auto{margin-top:auto !important}.me-0{margin-right:0 !important}.me-1{margin-right:.25rem !important}.me-2{margin-right:.5rem !important}.me-3{margin-right:1rem !important}.me-4{margin-right:1.5rem !important}.me-5{margin-right:3rem !important}.me-auto{margin-right:auto !important}.mb-0{margin-bottom:0 !important}.mb-1{margin-bottom:.25rem !important}.mb-2{margin-bottom:.5rem !important}.mb-3{margin-bottom:1rem !important}.mb-4{margin-bottom:1.5rem !important}.mb-5{margin-bottom:3rem !important}.mb-auto{margin-bottom:auto !important}.ms-0{margin-left:0 !important}.ms-1{margin-left:.25rem !important}.ms-2{margin-left:.5rem !important}.ms-3{margin-left:1rem !important}.ms-4{margin-left:1.5rem !important}.ms-5{margin-left:3rem !important}.ms-auto{margin-left:auto !important}.p-0{padding:0 !important}.p-1{padding:.25rem !important}.p-2{padding:.5rem !important}.p-3{padding:1rem !important}.p-4{padding:1.5rem !important}.p-5{padding:3rem !important}.px-0{padding-right:0 !important;padding-left:0 !important}.px-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-3{padding-right:1rem !important;padding-left:1rem !important}.px-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-5{padding-right:3rem !important;padding-left:3rem !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-0{padding-top:0 !important}.pt-1{padding-top:.25rem !important}.pt-2{padding-top:.5rem !important}.pt-3{padding-top:1rem !important}.pt-4{padding-top:1.5rem !important}.pt-5{padding-top:3rem !important}.pe-0{padding-right:0 !important}.pe-1{padding-right:.25rem !important}.pe-2{padding-right:.5rem !important}.pe-3{padding-right:1rem !important}.pe-4{padding-right:1.5rem !important}.pe-5{padding-right:3rem !important}.pb-0{padding-bottom:0 !important}.pb-1{padding-bottom:.25rem !important}.pb-2{padding-bottom:.5rem !important}.pb-3{padding-bottom:1rem !important}.pb-4{padding-bottom:1.5rem !important}.pb-5{padding-bottom:3rem !important}.ps-0{padding-left:0 !important}.ps-1{padding-left:.25rem !important}.ps-2{padding-left:.5rem !important}.ps-3{padding-left:1rem !important}.ps-4{padding-left:1.5rem !important}.ps-5{padding-left:3rem !important}.font-monospace{font-family:var(--bs-font-monospace) !important}.fs-1{font-size:calc(1.345rem + 1.14vw) !important}.fs-2{font-size:calc(1.3rem + 0.6vw) !important}.fs-3{font-size:calc(1.275rem + 0.3vw) !important}.fs-4{font-size:1.25rem !important}.fs-5{font-size:1.1rem !important}.fs-6{font-size:1rem !important}.fst-italic{font-style:italic !important}.fst-normal{font-style:normal !important}.fw-light{font-weight:300 !important}.fw-lighter{font-weight:lighter !important}.fw-normal{font-weight:400 !important}.fw-bold{font-weight:700 !important}.fw-bolder{font-weight:bolder !important}.lh-1{line-height:1 !important}.lh-sm{line-height:1.25 !important}.lh-base{line-height:1.5 !important}.lh-lg{line-height:2 !important}.text-start{text-align:left !important}.text-end{text-align:right !important}.text-center{text-align:center !important}.text-decoration-none{text-decoration:none !important}.text-decoration-underline{text-decoration:underline !important}.text-decoration-line-through{text-decoration:line-through !important}.text-lowercase{text-transform:lowercase !important}.text-uppercase{text-transform:uppercase !important}.text-capitalize{text-transform:capitalize !important}.text-wrap{white-space:normal !important}.text-nowrap{white-space:nowrap !important}.text-break{word-wrap:break-word !important;word-break:break-word !important}.text-default{--bs-text-opacity: 1;color:rgba(var(--bs-default-rgb), var(--bs-text-opacity)) !important}.text-primary{--bs-text-opacity: 1;color:rgba(var(--bs-primary-rgb), var(--bs-text-opacity)) !important}.text-secondary{--bs-text-opacity: 1;color:rgba(var(--bs-secondary-rgb), var(--bs-text-opacity)) !important}.text-success{--bs-text-opacity: 1;color:rgba(var(--bs-success-rgb), var(--bs-text-opacity)) !important}.text-info{--bs-text-opacity: 1;color:rgba(var(--bs-info-rgb), var(--bs-text-opacity)) !important}.text-warning{--bs-text-opacity: 1;color:rgba(var(--bs-warning-rgb), var(--bs-text-opacity)) !important}.text-danger{--bs-text-opacity: 1;color:rgba(var(--bs-danger-rgb), var(--bs-text-opacity)) !important}.text-light{--bs-text-opacity: 1;color:rgba(var(--bs-light-rgb), var(--bs-text-opacity)) !important}.text-dark{--bs-text-opacity: 1;color:rgba(var(--bs-dark-rgb), var(--bs-text-opacity)) !important}.text-black{--bs-text-opacity: 1;color:rgba(var(--bs-black-rgb), var(--bs-text-opacity)) !important}.text-white{--bs-text-opacity: 1;color:rgba(var(--bs-white-rgb), var(--bs-text-opacity)) !important}.text-body{--bs-text-opacity: 1;color:rgba(var(--bs-body-color-rgb), var(--bs-text-opacity)) !important}.text-muted{--bs-text-opacity: 1;color:#6c757d !important}.text-black-50{--bs-text-opacity: 1;color:rgba(0,0,0,.5) !important}.text-white-50{--bs-text-opacity: 1;color:rgba(255,255,255,.5) !important}.text-reset{--bs-text-opacity: 1;color:inherit !important}.text-opacity-25{--bs-text-opacity: 0.25}.text-opacity-50{--bs-text-opacity: 0.5}.text-opacity-75{--bs-text-opacity: 0.75}.text-opacity-100{--bs-text-opacity: 1}.bg-default{--bs-bg-opacity: 1;background-color:rgba(var(--bs-default-rgb), var(--bs-bg-opacity)) !important}.bg-primary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-primary-rgb), var(--bs-bg-opacity)) !important}.bg-secondary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-secondary-rgb), var(--bs-bg-opacity)) !important}.bg-success{--bs-bg-opacity: 1;background-color:rgba(var(--bs-success-rgb), var(--bs-bg-opacity)) !important}.bg-info{--bs-bg-opacity: 1;background-color:rgba(var(--bs-info-rgb), var(--bs-bg-opacity)) !important}.bg-warning{--bs-bg-opacity: 1;background-color:rgba(var(--bs-warning-rgb), var(--bs-bg-opacity)) !important}.bg-danger{--bs-bg-opacity: 1;background-color:rgba(var(--bs-danger-rgb), var(--bs-bg-opacity)) !important}.bg-light{--bs-bg-opacity: 1;background-color:rgba(var(--bs-light-rgb), var(--bs-bg-opacity)) !important}.bg-dark{--bs-bg-opacity: 1;background-color:rgba(var(--bs-dark-rgb), var(--bs-bg-opacity)) !important}.bg-black{--bs-bg-opacity: 1;background-color:rgba(var(--bs-black-rgb), var(--bs-bg-opacity)) !important}.bg-white{--bs-bg-opacity: 1;background-color:rgba(var(--bs-white-rgb), var(--bs-bg-opacity)) !important}.bg-body{--bs-bg-opacity: 1;background-color:rgba(var(--bs-body-bg-rgb), var(--bs-bg-opacity)) !important}.bg-transparent{--bs-bg-opacity: 1;background-color:transparent !important}.bg-opacity-10{--bs-bg-opacity: 0.1}.bg-opacity-25{--bs-bg-opacity: 0.25}.bg-opacity-50{--bs-bg-opacity: 0.5}.bg-opacity-75{--bs-bg-opacity: 0.75}.bg-opacity-100{--bs-bg-opacity: 1}.bg-gradient{background-image:var(--bs-gradient) !important}.user-select-all{user-select:all !important}.user-select-auto{user-select:auto !important}.user-select-none{user-select:none !important}.pe-none{pointer-events:none !important}.pe-auto{pointer-events:auto !important}.rounded{border-radius:.25rem !important}.rounded-0{border-radius:0 !important}.rounded-1{border-radius:.2rem !important}.rounded-2{border-radius:.25rem !important}.rounded-3{border-radius:.3rem !important}.rounded-circle{border-radius:50% !important}.rounded-pill{border-radius:50rem !important}.rounded-top{border-top-left-radius:.25rem !important;border-top-right-radius:.25rem !important}.rounded-end{border-top-right-radius:.25rem !important;border-bottom-right-radius:.25rem !important}.rounded-bottom{border-bottom-right-radius:.25rem !important;border-bottom-left-radius:.25rem !important}.rounded-start{border-bottom-left-radius:.25rem !important;border-top-left-radius:.25rem !important}.visible{visibility:visible !important}.invisible{visibility:hidden !important}@media(min-width: 576px){.float-sm-start{float:left !important}.float-sm-end{float:right !important}.float-sm-none{float:none !important}.d-sm-inline{display:inline !important}.d-sm-inline-block{display:inline-block !important}.d-sm-block{display:block !important}.d-sm-grid{display:grid !important}.d-sm-table{display:table !important}.d-sm-table-row{display:table-row !important}.d-sm-table-cell{display:table-cell !important}.d-sm-flex{display:flex !important}.d-sm-inline-flex{display:inline-flex !important}.d-sm-none{display:none !important}.flex-sm-fill{flex:1 1 auto !important}.flex-sm-row{flex-direction:row !important}.flex-sm-column{flex-direction:column !important}.flex-sm-row-reverse{flex-direction:row-reverse !important}.flex-sm-column-reverse{flex-direction:column-reverse !important}.flex-sm-grow-0{flex-grow:0 !important}.flex-sm-grow-1{flex-grow:1 !important}.flex-sm-shrink-0{flex-shrink:0 !important}.flex-sm-shrink-1{flex-shrink:1 !important}.flex-sm-wrap{flex-wrap:wrap !important}.flex-sm-nowrap{flex-wrap:nowrap !important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-sm-0{gap:0 !important}.gap-sm-1{gap:.25rem !important}.gap-sm-2{gap:.5rem !important}.gap-sm-3{gap:1rem !important}.gap-sm-4{gap:1.5rem !important}.gap-sm-5{gap:3rem !important}.justify-content-sm-start{justify-content:flex-start !important}.justify-content-sm-end{justify-content:flex-end !important}.justify-content-sm-center{justify-content:center !important}.justify-content-sm-between{justify-content:space-between !important}.justify-content-sm-around{justify-content:space-around !important}.justify-content-sm-evenly{justify-content:space-evenly !important}.align-items-sm-start{align-items:flex-start !important}.align-items-sm-end{align-items:flex-end !important}.align-items-sm-center{align-items:center !important}.align-items-sm-baseline{align-items:baseline !important}.align-items-sm-stretch{align-items:stretch !important}.align-content-sm-start{align-content:flex-start !important}.align-content-sm-end{align-content:flex-end !important}.align-content-sm-center{align-content:center !important}.align-content-sm-between{align-content:space-between !important}.align-content-sm-around{align-content:space-around !important}.align-content-sm-stretch{align-content:stretch !important}.align-self-sm-auto{align-self:auto !important}.align-self-sm-start{align-self:flex-start !important}.align-self-sm-end{align-self:flex-end !important}.align-self-sm-center{align-self:center !important}.align-self-sm-baseline{align-self:baseline !important}.align-self-sm-stretch{align-self:stretch !important}.order-sm-first{order:-1 !important}.order-sm-0{order:0 !important}.order-sm-1{order:1 !important}.order-sm-2{order:2 !important}.order-sm-3{order:3 !important}.order-sm-4{order:4 !important}.order-sm-5{order:5 !important}.order-sm-last{order:6 !important}.m-sm-0{margin:0 !important}.m-sm-1{margin:.25rem !important}.m-sm-2{margin:.5rem !important}.m-sm-3{margin:1rem !important}.m-sm-4{margin:1.5rem !important}.m-sm-5{margin:3rem !important}.m-sm-auto{margin:auto !important}.mx-sm-0{margin-right:0 !important;margin-left:0 !important}.mx-sm-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-sm-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-sm-3{margin-right:1rem !important;margin-left:1rem !important}.mx-sm-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-sm-5{margin-right:3rem !important;margin-left:3rem !important}.mx-sm-auto{margin-right:auto !important;margin-left:auto !important}.my-sm-0{margin-top:0 !important;margin-bottom:0 !important}.my-sm-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-sm-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-sm-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-sm-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-sm-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-sm-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-sm-0{margin-top:0 !important}.mt-sm-1{margin-top:.25rem !important}.mt-sm-2{margin-top:.5rem !important}.mt-sm-3{margin-top:1rem !important}.mt-sm-4{margin-top:1.5rem !important}.mt-sm-5{margin-top:3rem !important}.mt-sm-auto{margin-top:auto !important}.me-sm-0{margin-right:0 !important}.me-sm-1{margin-right:.25rem !important}.me-sm-2{margin-right:.5rem !important}.me-sm-3{margin-right:1rem !important}.me-sm-4{margin-right:1.5rem !important}.me-sm-5{margin-right:3rem !important}.me-sm-auto{margin-right:auto !important}.mb-sm-0{margin-bottom:0 !important}.mb-sm-1{margin-bottom:.25rem !important}.mb-sm-2{margin-bottom:.5rem !important}.mb-sm-3{margin-bottom:1rem !important}.mb-sm-4{margin-bottom:1.5rem !important}.mb-sm-5{margin-bottom:3rem !important}.mb-sm-auto{margin-bottom:auto !important}.ms-sm-0{margin-left:0 !important}.ms-sm-1{margin-left:.25rem !important}.ms-sm-2{margin-left:.5rem !important}.ms-sm-3{margin-left:1rem !important}.ms-sm-4{margin-left:1.5rem !important}.ms-sm-5{margin-left:3rem !important}.ms-sm-auto{margin-left:auto !important}.p-sm-0{padding:0 !important}.p-sm-1{padding:.25rem !important}.p-sm-2{padding:.5rem !important}.p-sm-3{padding:1rem !important}.p-sm-4{padding:1.5rem !important}.p-sm-5{padding:3rem !important}.px-sm-0{padding-right:0 !important;padding-left:0 !important}.px-sm-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-sm-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-sm-3{padding-right:1rem !important;padding-left:1rem !important}.px-sm-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-sm-5{padding-right:3rem !important;padding-left:3rem !important}.py-sm-0{padding-top:0 !important;padding-bottom:0 !important}.py-sm-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-sm-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-sm-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-sm-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-sm-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-sm-0{padding-top:0 !important}.pt-sm-1{padding-top:.25rem !important}.pt-sm-2{padding-top:.5rem !important}.pt-sm-3{padding-top:1rem !important}.pt-sm-4{padding-top:1.5rem !important}.pt-sm-5{padding-top:3rem !important}.pe-sm-0{padding-right:0 !important}.pe-sm-1{padding-right:.25rem !important}.pe-sm-2{padding-right:.5rem !important}.pe-sm-3{padding-right:1rem !important}.pe-sm-4{padding-right:1.5rem !important}.pe-sm-5{padding-right:3rem !important}.pb-sm-0{padding-bottom:0 !important}.pb-sm-1{padding-bottom:.25rem !important}.pb-sm-2{padding-bottom:.5rem !important}.pb-sm-3{padding-bottom:1rem !important}.pb-sm-4{padding-bottom:1.5rem !important}.pb-sm-5{padding-bottom:3rem !important}.ps-sm-0{padding-left:0 !important}.ps-sm-1{padding-left:.25rem !important}.ps-sm-2{padding-left:.5rem !important}.ps-sm-3{padding-left:1rem !important}.ps-sm-4{padding-left:1.5rem !important}.ps-sm-5{padding-left:3rem !important}.text-sm-start{text-align:left !important}.text-sm-end{text-align:right !important}.text-sm-center{text-align:center !important}}@media(min-width: 768px){.float-md-start{float:left !important}.float-md-end{float:right !important}.float-md-none{float:none !important}.d-md-inline{display:inline !important}.d-md-inline-block{display:inline-block !important}.d-md-block{display:block !important}.d-md-grid{display:grid !important}.d-md-table{display:table !important}.d-md-table-row{display:table-row !important}.d-md-table-cell{display:table-cell !important}.d-md-flex{display:flex !important}.d-md-inline-flex{display:inline-flex !important}.d-md-none{display:none !important}.flex-md-fill{flex:1 1 auto !important}.flex-md-row{flex-direction:row !important}.flex-md-column{flex-direction:column !important}.flex-md-row-reverse{flex-direction:row-reverse !important}.flex-md-column-reverse{flex-direction:column-reverse !important}.flex-md-grow-0{flex-grow:0 !important}.flex-md-grow-1{flex-grow:1 !important}.flex-md-shrink-0{flex-shrink:0 !important}.flex-md-shrink-1{flex-shrink:1 !important}.flex-md-wrap{flex-wrap:wrap !important}.flex-md-nowrap{flex-wrap:nowrap !important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-md-0{gap:0 !important}.gap-md-1{gap:.25rem !important}.gap-md-2{gap:.5rem !important}.gap-md-3{gap:1rem !important}.gap-md-4{gap:1.5rem !important}.gap-md-5{gap:3rem !important}.justify-content-md-start{justify-content:flex-start !important}.justify-content-md-end{justify-content:flex-end !important}.justify-content-md-center{justify-content:center !important}.justify-content-md-between{justify-content:space-between !important}.justify-content-md-around{justify-content:space-around !important}.justify-content-md-evenly{justify-content:space-evenly !important}.align-items-md-start{align-items:flex-start !important}.align-items-md-end{align-items:flex-end !important}.align-items-md-center{align-items:center !important}.align-items-md-baseline{align-items:baseline !important}.align-items-md-stretch{align-items:stretch !important}.align-content-md-start{align-content:flex-start !important}.align-content-md-end{align-content:flex-end !important}.align-content-md-center{align-content:center !important}.align-content-md-between{align-content:space-between !important}.align-content-md-around{align-content:space-around !important}.align-content-md-stretch{align-content:stretch !important}.align-self-md-auto{align-self:auto !important}.align-self-md-start{align-self:flex-start !important}.align-self-md-end{align-self:flex-end !important}.align-self-md-center{align-self:center !important}.align-self-md-baseline{align-self:baseline !important}.align-self-md-stretch{align-self:stretch !important}.order-md-first{order:-1 !important}.order-md-0{order:0 !important}.order-md-1{order:1 !important}.order-md-2{order:2 !important}.order-md-3{order:3 !important}.order-md-4{order:4 !important}.order-md-5{order:5 !important}.order-md-last{order:6 !important}.m-md-0{margin:0 !important}.m-md-1{margin:.25rem !important}.m-md-2{margin:.5rem !important}.m-md-3{margin:1rem !important}.m-md-4{margin:1.5rem !important}.m-md-5{margin:3rem !important}.m-md-auto{margin:auto !important}.mx-md-0{margin-right:0 !important;margin-left:0 !important}.mx-md-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-md-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-md-3{margin-right:1rem !important;margin-left:1rem !important}.mx-md-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-md-5{margin-right:3rem !important;margin-left:3rem !important}.mx-md-auto{margin-right:auto !important;margin-left:auto !important}.my-md-0{margin-top:0 !important;margin-bottom:0 !important}.my-md-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-md-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-md-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-md-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-md-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-md-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-md-0{margin-top:0 !important}.mt-md-1{margin-top:.25rem !important}.mt-md-2{margin-top:.5rem !important}.mt-md-3{margin-top:1rem !important}.mt-md-4{margin-top:1.5rem !important}.mt-md-5{margin-top:3rem !important}.mt-md-auto{margin-top:auto !important}.me-md-0{margin-right:0 !important}.me-md-1{margin-right:.25rem !important}.me-md-2{margin-right:.5rem !important}.me-md-3{margin-right:1rem !important}.me-md-4{margin-right:1.5rem !important}.me-md-5{margin-right:3rem !important}.me-md-auto{margin-right:auto !important}.mb-md-0{margin-bottom:0 !important}.mb-md-1{margin-bottom:.25rem !important}.mb-md-2{margin-bottom:.5rem !important}.mb-md-3{margin-bottom:1rem !important}.mb-md-4{margin-bottom:1.5rem !important}.mb-md-5{margin-bottom:3rem !important}.mb-md-auto{margin-bottom:auto !important}.ms-md-0{margin-left:0 !important}.ms-md-1{margin-left:.25rem !important}.ms-md-2{margin-left:.5rem !important}.ms-md-3{margin-left:1rem !important}.ms-md-4{margin-left:1.5rem !important}.ms-md-5{margin-left:3rem !important}.ms-md-auto{margin-left:auto !important}.p-md-0{padding:0 !important}.p-md-1{padding:.25rem !important}.p-md-2{padding:.5rem !important}.p-md-3{padding:1rem !important}.p-md-4{padding:1.5rem !important}.p-md-5{padding:3rem !important}.px-md-0{padding-right:0 !important;padding-left:0 !important}.px-md-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-md-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-md-3{padding-right:1rem !important;padding-left:1rem !important}.px-md-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-md-5{padding-right:3rem !important;padding-left:3rem !important}.py-md-0{padding-top:0 !important;padding-bottom:0 !important}.py-md-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-md-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-md-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-md-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-md-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-md-0{padding-top:0 !important}.pt-md-1{padding-top:.25rem !important}.pt-md-2{padding-top:.5rem !important}.pt-md-3{padding-top:1rem !important}.pt-md-4{padding-top:1.5rem !important}.pt-md-5{padding-top:3rem !important}.pe-md-0{padding-right:0 !important}.pe-md-1{padding-right:.25rem !important}.pe-md-2{padding-right:.5rem !important}.pe-md-3{padding-right:1rem !important}.pe-md-4{padding-right:1.5rem !important}.pe-md-5{padding-right:3rem !important}.pb-md-0{padding-bottom:0 !important}.pb-md-1{padding-bottom:.25rem !important}.pb-md-2{padding-bottom:.5rem !important}.pb-md-3{padding-bottom:1rem !important}.pb-md-4{padding-bottom:1.5rem !important}.pb-md-5{padding-bottom:3rem !important}.ps-md-0{padding-left:0 !important}.ps-md-1{padding-left:.25rem !important}.ps-md-2{padding-left:.5rem !important}.ps-md-3{padding-left:1rem !important}.ps-md-4{padding-left:1.5rem !important}.ps-md-5{padding-left:3rem !important}.text-md-start{text-align:left !important}.text-md-end{text-align:right !important}.text-md-center{text-align:center !important}}@media(min-width: 992px){.float-lg-start{float:left !important}.float-lg-end{float:right !important}.float-lg-none{float:none !important}.d-lg-inline{display:inline !important}.d-lg-inline-block{display:inline-block !important}.d-lg-block{display:block !important}.d-lg-grid{display:grid !important}.d-lg-table{display:table !important}.d-lg-table-row{display:table-row !important}.d-lg-table-cell{display:table-cell !important}.d-lg-flex{display:flex !important}.d-lg-inline-flex{display:inline-flex !important}.d-lg-none{display:none !important}.flex-lg-fill{flex:1 1 auto !important}.flex-lg-row{flex-direction:row !important}.flex-lg-column{flex-direction:column !important}.flex-lg-row-reverse{flex-direction:row-reverse !important}.flex-lg-column-reverse{flex-direction:column-reverse !important}.flex-lg-grow-0{flex-grow:0 !important}.flex-lg-grow-1{flex-grow:1 !important}.flex-lg-shrink-0{flex-shrink:0 !important}.flex-lg-shrink-1{flex-shrink:1 !important}.flex-lg-wrap{flex-wrap:wrap !important}.flex-lg-nowrap{flex-wrap:nowrap !important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-lg-0{gap:0 !important}.gap-lg-1{gap:.25rem !important}.gap-lg-2{gap:.5rem !important}.gap-lg-3{gap:1rem !important}.gap-lg-4{gap:1.5rem !important}.gap-lg-5{gap:3rem !important}.justify-content-lg-start{justify-content:flex-start !important}.justify-content-lg-end{justify-content:flex-end !important}.justify-content-lg-center{justify-content:center !important}.justify-content-lg-between{justify-content:space-between !important}.justify-content-lg-around{justify-content:space-around !important}.justify-content-lg-evenly{justify-content:space-evenly !important}.align-items-lg-start{align-items:flex-start !important}.align-items-lg-end{align-items:flex-end !important}.align-items-lg-center{align-items:center !important}.align-items-lg-baseline{align-items:baseline !important}.align-items-lg-stretch{align-items:stretch !important}.align-content-lg-start{align-content:flex-start !important}.align-content-lg-end{align-content:flex-end !important}.align-content-lg-center{align-content:center !important}.align-content-lg-between{align-content:space-between !important}.align-content-lg-around{align-content:space-around !important}.align-content-lg-stretch{align-content:stretch !important}.align-self-lg-auto{align-self:auto !important}.align-self-lg-start{align-self:flex-start !important}.align-self-lg-end{align-self:flex-end !important}.align-self-lg-center{align-self:center !important}.align-self-lg-baseline{align-self:baseline !important}.align-self-lg-stretch{align-self:stretch !important}.order-lg-first{order:-1 !important}.order-lg-0{order:0 !important}.order-lg-1{order:1 !important}.order-lg-2{order:2 !important}.order-lg-3{order:3 !important}.order-lg-4{order:4 !important}.order-lg-5{order:5 !important}.order-lg-last{order:6 !important}.m-lg-0{margin:0 !important}.m-lg-1{margin:.25rem !important}.m-lg-2{margin:.5rem !important}.m-lg-3{margin:1rem !important}.m-lg-4{margin:1.5rem !important}.m-lg-5{margin:3rem !important}.m-lg-auto{margin:auto !important}.mx-lg-0{margin-right:0 !important;margin-left:0 !important}.mx-lg-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-lg-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-lg-3{margin-right:1rem !important;margin-left:1rem !important}.mx-lg-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-lg-5{margin-right:3rem !important;margin-left:3rem !important}.mx-lg-auto{margin-right:auto !important;margin-left:auto !important}.my-lg-0{margin-top:0 !important;margin-bottom:0 !important}.my-lg-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-lg-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-lg-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-lg-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-lg-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-lg-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-lg-0{margin-top:0 !important}.mt-lg-1{margin-top:.25rem !important}.mt-lg-2{margin-top:.5rem !important}.mt-lg-3{margin-top:1rem !important}.mt-lg-4{margin-top:1.5rem !important}.mt-lg-5{margin-top:3rem !important}.mt-lg-auto{margin-top:auto !important}.me-lg-0{margin-right:0 !important}.me-lg-1{margin-right:.25rem !important}.me-lg-2{margin-right:.5rem !important}.me-lg-3{margin-right:1rem !important}.me-lg-4{margin-right:1.5rem !important}.me-lg-5{margin-right:3rem !important}.me-lg-auto{margin-right:auto !important}.mb-lg-0{margin-bottom:0 !important}.mb-lg-1{margin-bottom:.25rem !important}.mb-lg-2{margin-bottom:.5rem !important}.mb-lg-3{margin-bottom:1rem !important}.mb-lg-4{margin-bottom:1.5rem !important}.mb-lg-5{margin-bottom:3rem !important}.mb-lg-auto{margin-bottom:auto !important}.ms-lg-0{margin-left:0 !important}.ms-lg-1{margin-left:.25rem !important}.ms-lg-2{margin-left:.5rem !important}.ms-lg-3{margin-left:1rem !important}.ms-lg-4{margin-left:1.5rem !important}.ms-lg-5{margin-left:3rem !important}.ms-lg-auto{margin-left:auto !important}.p-lg-0{padding:0 !important}.p-lg-1{padding:.25rem !important}.p-lg-2{padding:.5rem !important}.p-lg-3{padding:1rem !important}.p-lg-4{padding:1.5rem !important}.p-lg-5{padding:3rem !important}.px-lg-0{padding-right:0 !important;padding-left:0 !important}.px-lg-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-lg-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-lg-3{padding-right:1rem !important;padding-left:1rem !important}.px-lg-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-lg-5{padding-right:3rem !important;padding-left:3rem !important}.py-lg-0{padding-top:0 !important;padding-bottom:0 !important}.py-lg-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-lg-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-lg-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-lg-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-lg-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-lg-0{padding-top:0 !important}.pt-lg-1{padding-top:.25rem !important}.pt-lg-2{padding-top:.5rem !important}.pt-lg-3{padding-top:1rem !important}.pt-lg-4{padding-top:1.5rem !important}.pt-lg-5{padding-top:3rem !important}.pe-lg-0{padding-right:0 !important}.pe-lg-1{padding-right:.25rem !important}.pe-lg-2{padding-right:.5rem !important}.pe-lg-3{padding-right:1rem !important}.pe-lg-4{padding-right:1.5rem !important}.pe-lg-5{padding-right:3rem !important}.pb-lg-0{padding-bottom:0 !important}.pb-lg-1{padding-bottom:.25rem !important}.pb-lg-2{padding-bottom:.5rem !important}.pb-lg-3{padding-bottom:1rem !important}.pb-lg-4{padding-bottom:1.5rem !important}.pb-lg-5{padding-bottom:3rem !important}.ps-lg-0{padding-left:0 !important}.ps-lg-1{padding-left:.25rem !important}.ps-lg-2{padding-left:.5rem !important}.ps-lg-3{padding-left:1rem !important}.ps-lg-4{padding-left:1.5rem !important}.ps-lg-5{padding-left:3rem !important}.text-lg-start{text-align:left !important}.text-lg-end{text-align:right !important}.text-lg-center{text-align:center !important}}@media(min-width: 1200px){.float-xl-start{float:left !important}.float-xl-end{float:right !important}.float-xl-none{float:none !important}.d-xl-inline{display:inline !important}.d-xl-inline-block{display:inline-block !important}.d-xl-block{display:block !important}.d-xl-grid{display:grid !important}.d-xl-table{display:table !important}.d-xl-table-row{display:table-row !important}.d-xl-table-cell{display:table-cell !important}.d-xl-flex{display:flex !important}.d-xl-inline-flex{display:inline-flex !important}.d-xl-none{display:none !important}.flex-xl-fill{flex:1 1 auto !important}.flex-xl-row{flex-direction:row !important}.flex-xl-column{flex-direction:column !important}.flex-xl-row-reverse{flex-direction:row-reverse !important}.flex-xl-column-reverse{flex-direction:column-reverse !important}.flex-xl-grow-0{flex-grow:0 !important}.flex-xl-grow-1{flex-grow:1 !important}.flex-xl-shrink-0{flex-shrink:0 !important}.flex-xl-shrink-1{flex-shrink:1 !important}.flex-xl-wrap{flex-wrap:wrap !important}.flex-xl-nowrap{flex-wrap:nowrap !important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-xl-0{gap:0 !important}.gap-xl-1{gap:.25rem !important}.gap-xl-2{gap:.5rem !important}.gap-xl-3{gap:1rem !important}.gap-xl-4{gap:1.5rem !important}.gap-xl-5{gap:3rem !important}.justify-content-xl-start{justify-content:flex-start !important}.justify-content-xl-end{justify-content:flex-end !important}.justify-content-xl-center{justify-content:center !important}.justify-content-xl-between{justify-content:space-between !important}.justify-content-xl-around{justify-content:space-around !important}.justify-content-xl-evenly{justify-content:space-evenly !important}.align-items-xl-start{align-items:flex-start !important}.align-items-xl-end{align-items:flex-end !important}.align-items-xl-center{align-items:center !important}.align-items-xl-baseline{align-items:baseline !important}.align-items-xl-stretch{align-items:stretch !important}.align-content-xl-start{align-content:flex-start !important}.align-content-xl-end{align-content:flex-end !important}.align-content-xl-center{align-content:center !important}.align-content-xl-between{align-content:space-between !important}.align-content-xl-around{align-content:space-around !important}.align-content-xl-stretch{align-content:stretch !important}.align-self-xl-auto{align-self:auto !important}.align-self-xl-start{align-self:flex-start !important}.align-self-xl-end{align-self:flex-end !important}.align-self-xl-center{align-self:center !important}.align-self-xl-baseline{align-self:baseline !important}.align-self-xl-stretch{align-self:stretch !important}.order-xl-first{order:-1 !important}.order-xl-0{order:0 !important}.order-xl-1{order:1 !important}.order-xl-2{order:2 !important}.order-xl-3{order:3 !important}.order-xl-4{order:4 !important}.order-xl-5{order:5 !important}.order-xl-last{order:6 !important}.m-xl-0{margin:0 !important}.m-xl-1{margin:.25rem !important}.m-xl-2{margin:.5rem !important}.m-xl-3{margin:1rem !important}.m-xl-4{margin:1.5rem !important}.m-xl-5{margin:3rem !important}.m-xl-auto{margin:auto !important}.mx-xl-0{margin-right:0 !important;margin-left:0 !important}.mx-xl-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-xl-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-xl-3{margin-right:1rem !important;margin-left:1rem !important}.mx-xl-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-xl-5{margin-right:3rem !important;margin-left:3rem !important}.mx-xl-auto{margin-right:auto !important;margin-left:auto !important}.my-xl-0{margin-top:0 !important;margin-bottom:0 !important}.my-xl-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-xl-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-xl-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-xl-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-xl-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-xl-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-xl-0{margin-top:0 !important}.mt-xl-1{margin-top:.25rem !important}.mt-xl-2{margin-top:.5rem !important}.mt-xl-3{margin-top:1rem !important}.mt-xl-4{margin-top:1.5rem !important}.mt-xl-5{margin-top:3rem !important}.mt-xl-auto{margin-top:auto !important}.me-xl-0{margin-right:0 !important}.me-xl-1{margin-right:.25rem !important}.me-xl-2{margin-right:.5rem !important}.me-xl-3{margin-right:1rem !important}.me-xl-4{margin-right:1.5rem !important}.me-xl-5{margin-right:3rem !important}.me-xl-auto{margin-right:auto !important}.mb-xl-0{margin-bottom:0 !important}.mb-xl-1{margin-bottom:.25rem !important}.mb-xl-2{margin-bottom:.5rem !important}.mb-xl-3{margin-bottom:1rem !important}.mb-xl-4{margin-bottom:1.5rem !important}.mb-xl-5{margin-bottom:3rem !important}.mb-xl-auto{margin-bottom:auto !important}.ms-xl-0{margin-left:0 !important}.ms-xl-1{margin-left:.25rem !important}.ms-xl-2{margin-left:.5rem !important}.ms-xl-3{margin-left:1rem !important}.ms-xl-4{margin-left:1.5rem !important}.ms-xl-5{margin-left:3rem !important}.ms-xl-auto{margin-left:auto !important}.p-xl-0{padding:0 !important}.p-xl-1{padding:.25rem !important}.p-xl-2{padding:.5rem !important}.p-xl-3{padding:1rem !important}.p-xl-4{padding:1.5rem !important}.p-xl-5{padding:3rem !important}.px-xl-0{padding-right:0 !important;padding-left:0 !important}.px-xl-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-xl-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-xl-3{padding-right:1rem !important;padding-left:1rem !important}.px-xl-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-xl-5{padding-right:3rem !important;padding-left:3rem !important}.py-xl-0{padding-top:0 !important;padding-bottom:0 !important}.py-xl-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-xl-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-xl-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-xl-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-xl-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-xl-0{padding-top:0 !important}.pt-xl-1{padding-top:.25rem !important}.pt-xl-2{padding-top:.5rem !important}.pt-xl-3{padding-top:1rem !important}.pt-xl-4{padding-top:1.5rem !important}.pt-xl-5{padding-top:3rem !important}.pe-xl-0{padding-right:0 !important}.pe-xl-1{padding-right:.25rem !important}.pe-xl-2{padding-right:.5rem !important}.pe-xl-3{padding-right:1rem !important}.pe-xl-4{padding-right:1.5rem !important}.pe-xl-5{padding-right:3rem !important}.pb-xl-0{padding-bottom:0 !important}.pb-xl-1{padding-bottom:.25rem !important}.pb-xl-2{padding-bottom:.5rem !important}.pb-xl-3{padding-bottom:1rem !important}.pb-xl-4{padding-bottom:1.5rem !important}.pb-xl-5{padding-bottom:3rem !important}.ps-xl-0{padding-left:0 !important}.ps-xl-1{padding-left:.25rem !important}.ps-xl-2{padding-left:.5rem !important}.ps-xl-3{padding-left:1rem !important}.ps-xl-4{padding-left:1.5rem !important}.ps-xl-5{padding-left:3rem !important}.text-xl-start{text-align:left !important}.text-xl-end{text-align:right !important}.text-xl-center{text-align:center !important}}@media(min-width: 1400px){.float-xxl-start{float:left !important}.float-xxl-end{float:right !important}.float-xxl-none{float:none !important}.d-xxl-inline{display:inline !important}.d-xxl-inline-block{display:inline-block !important}.d-xxl-block{display:block !important}.d-xxl-grid{display:grid !important}.d-xxl-table{display:table !important}.d-xxl-table-row{display:table-row !important}.d-xxl-table-cell{display:table-cell !important}.d-xxl-flex{display:flex !important}.d-xxl-inline-flex{display:inline-flex !important}.d-xxl-none{display:none !important}.flex-xxl-fill{flex:1 1 auto !important}.flex-xxl-row{flex-direction:row !important}.flex-xxl-column{flex-direction:column !important}.flex-xxl-row-reverse{flex-direction:row-reverse !important}.flex-xxl-column-reverse{flex-direction:column-reverse !important}.flex-xxl-grow-0{flex-grow:0 !important}.flex-xxl-grow-1{flex-grow:1 !important}.flex-xxl-shrink-0{flex-shrink:0 !important}.flex-xxl-shrink-1{flex-shrink:1 !important}.flex-xxl-wrap{flex-wrap:wrap !important}.flex-xxl-nowrap{flex-wrap:nowrap !important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-xxl-0{gap:0 !important}.gap-xxl-1{gap:.25rem !important}.gap-xxl-2{gap:.5rem !important}.gap-xxl-3{gap:1rem !important}.gap-xxl-4{gap:1.5rem !important}.gap-xxl-5{gap:3rem !important}.justify-content-xxl-start{justify-content:flex-start !important}.justify-content-xxl-end{justify-content:flex-end !important}.justify-content-xxl-center{justify-content:center !important}.justify-content-xxl-between{justify-content:space-between !important}.justify-content-xxl-around{justify-content:space-around !important}.justify-content-xxl-evenly{justify-content:space-evenly !important}.align-items-xxl-start{align-items:flex-start !important}.align-items-xxl-end{align-items:flex-end !important}.align-items-xxl-center{align-items:center !important}.align-items-xxl-baseline{align-items:baseline !important}.align-items-xxl-stretch{align-items:stretch !important}.align-content-xxl-start{align-content:flex-start !important}.align-content-xxl-end{align-content:flex-end !important}.align-content-xxl-center{align-content:center !important}.align-content-xxl-between{align-content:space-between !important}.align-content-xxl-around{align-content:space-around !important}.align-content-xxl-stretch{align-content:stretch !important}.align-self-xxl-auto{align-self:auto !important}.align-self-xxl-start{align-self:flex-start !important}.align-self-xxl-end{align-self:flex-end !important}.align-self-xxl-center{align-self:center !important}.align-self-xxl-baseline{align-self:baseline !important}.align-self-xxl-stretch{align-self:stretch !important}.order-xxl-first{order:-1 !important}.order-xxl-0{order:0 !important}.order-xxl-1{order:1 !important}.order-xxl-2{order:2 !important}.order-xxl-3{order:3 !important}.order-xxl-4{order:4 !important}.order-xxl-5{order:5 !important}.order-xxl-last{order:6 !important}.m-xxl-0{margin:0 !important}.m-xxl-1{margin:.25rem !important}.m-xxl-2{margin:.5rem !important}.m-xxl-3{margin:1rem !important}.m-xxl-4{margin:1.5rem !important}.m-xxl-5{margin:3rem !important}.m-xxl-auto{margin:auto !important}.mx-xxl-0{margin-right:0 !important;margin-left:0 !important}.mx-xxl-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-xxl-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-xxl-3{margin-right:1rem !important;margin-left:1rem !important}.mx-xxl-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-xxl-5{margin-right:3rem !important;margin-left:3rem !important}.mx-xxl-auto{margin-right:auto !important;margin-left:auto !important}.my-xxl-0{margin-top:0 !important;margin-bottom:0 !important}.my-xxl-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-xxl-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-xxl-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-xxl-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-xxl-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-xxl-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-xxl-0{margin-top:0 !important}.mt-xxl-1{margin-top:.25rem !important}.mt-xxl-2{margin-top:.5rem !important}.mt-xxl-3{margin-top:1rem !important}.mt-xxl-4{margin-top:1.5rem !important}.mt-xxl-5{margin-top:3rem !important}.mt-xxl-auto{margin-top:auto !important}.me-xxl-0{margin-right:0 !important}.me-xxl-1{margin-right:.25rem !important}.me-xxl-2{margin-right:.5rem !important}.me-xxl-3{margin-right:1rem !important}.me-xxl-4{margin-right:1.5rem !important}.me-xxl-5{margin-right:3rem !important}.me-xxl-auto{margin-right:auto !important}.mb-xxl-0{margin-bottom:0 !important}.mb-xxl-1{margin-bottom:.25rem !important}.mb-xxl-2{margin-bottom:.5rem !important}.mb-xxl-3{margin-bottom:1rem !important}.mb-xxl-4{margin-bottom:1.5rem !important}.mb-xxl-5{margin-bottom:3rem !important}.mb-xxl-auto{margin-bottom:auto !important}.ms-xxl-0{margin-left:0 !important}.ms-xxl-1{margin-left:.25rem !important}.ms-xxl-2{margin-left:.5rem !important}.ms-xxl-3{margin-left:1rem !important}.ms-xxl-4{margin-left:1.5rem !important}.ms-xxl-5{margin-left:3rem !important}.ms-xxl-auto{margin-left:auto !important}.p-xxl-0{padding:0 !important}.p-xxl-1{padding:.25rem !important}.p-xxl-2{padding:.5rem !important}.p-xxl-3{padding:1rem !important}.p-xxl-4{padding:1.5rem !important}.p-xxl-5{padding:3rem !important}.px-xxl-0{padding-right:0 !important;padding-left:0 !important}.px-xxl-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-xxl-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-xxl-3{padding-right:1rem !important;padding-left:1rem !important}.px-xxl-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-xxl-5{padding-right:3rem !important;padding-left:3rem !important}.py-xxl-0{padding-top:0 !important;padding-bottom:0 !important}.py-xxl-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-xxl-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-xxl-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-xxl-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-xxl-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-xxl-0{padding-top:0 !important}.pt-xxl-1{padding-top:.25rem !important}.pt-xxl-2{padding-top:.5rem !important}.pt-xxl-3{padding-top:1rem !important}.pt-xxl-4{padding-top:1.5rem !important}.pt-xxl-5{padding-top:3rem !important}.pe-xxl-0{padding-right:0 !important}.pe-xxl-1{padding-right:.25rem !important}.pe-xxl-2{padding-right:.5rem !important}.pe-xxl-3{padding-right:1rem !important}.pe-xxl-4{padding-right:1.5rem !important}.pe-xxl-5{padding-right:3rem !important}.pb-xxl-0{padding-bottom:0 !important}.pb-xxl-1{padding-bottom:.25rem !important}.pb-xxl-2{padding-bottom:.5rem !important}.pb-xxl-3{padding-bottom:1rem !important}.pb-xxl-4{padding-bottom:1.5rem !important}.pb-xxl-5{padding-bottom:3rem !important}.ps-xxl-0{padding-left:0 !important}.ps-xxl-1{padding-left:.25rem !important}.ps-xxl-2{padding-left:.5rem !important}.ps-xxl-3{padding-left:1rem !important}.ps-xxl-4{padding-left:1.5rem !important}.ps-xxl-5{padding-left:3rem !important}.text-xxl-start{text-align:left !important}.text-xxl-end{text-align:right !important}.text-xxl-center{text-align:center !important}}.bg-default{color:#000}.bg-primary{color:#fff}.bg-secondary{color:#fff}.bg-success{color:#fff}.bg-info{color:#000}.bg-warning{color:#000}.bg-danger{color:#fff}.bg-light{color:#000}.bg-dark{color:#fff}@media(min-width: 1200px){.fs-1{font-size:2.2rem !important}.fs-2{font-size:1.75rem !important}.fs-3{font-size:1.5rem !important}}@media print{.d-print-inline{display:inline !important}.d-print-inline-block{display:inline-block !important}.d-print-block{display:block !important}.d-print-grid{display:grid !important}.d-print-table{display:table !important}.d-print-table-row{display:table-row !important}.d-print-table-cell{display:table-cell !important}.d-print-flex{display:flex !important}.d-print-inline-flex{display:inline-flex !important}.d-print-none{display:none !important}}.tippy-box[data-theme~=quarto]{background-color:#fff;color:#212529;border-radius:.25rem;border:solid 1px #dee2e6;font-size:.875rem}.tippy-box[data-theme~=quarto] .tippy-arrow{color:#dee2e6}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:-1px}.tippy-box[data-placement^=bottom]>.tippy-content{padding:.75em 1em;z-index:1}.top-right{position:absolute;top:1em;right:1em}.hidden{display:none !important}.zindex-bottom{z-index:-1 !important}.quarto-layout-panel{margin-bottom:1em}.quarto-layout-panel>figure{width:100%}.quarto-layout-panel>figure>figcaption,.quarto-layout-panel>.panel-caption{margin-top:10pt}.quarto-layout-panel>.table-caption{margin-top:0px}.table-caption p{margin-bottom:.5em}.quarto-layout-row{display:flex;flex-direction:row;align-items:flex-start}.quarto-layout-valign-top{align-items:flex-start}.quarto-layout-valign-bottom{align-items:flex-end}.quarto-layout-valign-center{align-items:center}.quarto-layout-cell{position:relative;margin-right:20px}.quarto-layout-cell:last-child{margin-right:0}.quarto-layout-cell figure,.quarto-layout-cell>p{margin:.2em}.quarto-layout-cell img{max-width:100%}.quarto-layout-cell .html-widget{width:100% !important}.quarto-layout-cell div figure p{margin:0}.quarto-layout-cell figure{display:inline-block;margin-inline-start:0;margin-inline-end:0}.quarto-layout-cell table{display:inline-table}.quarto-layout-cell-subref figcaption,figure .quarto-layout-row figure figcaption{text-align:center;font-style:italic}.quarto-figure{position:relative;margin-bottom:1em}.quarto-figure>figure{width:100%;margin-bottom:0}.quarto-figure-left>figure>p{text-align:left}.quarto-figure-center>figure>p{text-align:center}.quarto-figure-right>figure>p{text-align:right}figure>p:empty{display:none}figure>p:first-child{margin-top:0;margin-bottom:0}figure>figcaption{margin-top:.5em}div[id^=tbl-]{position:relative}.quarto-figure>.anchorjs-link,div[id^=tbl-]>.anchorjs-link{position:absolute;top:0;right:0}.quarto-figure:hover>.anchorjs-link,div[id^=tbl-]:hover>.anchorjs-link,h2:hover>.anchorjs-link,.h2:hover>.anchorjs-link,h3:hover>.anchorjs-link,.h3:hover>.anchorjs-link,h4:hover>.anchorjs-link,.h4:hover>.anchorjs-link,h5:hover>.anchorjs-link,.h5:hover>.anchorjs-link,h6:hover>.anchorjs-link,.h6:hover>.anchorjs-link,.reveal-anchorjs-link>.anchorjs-link{opacity:1}#title-block-header{margin-block-end:1rem;position:relative;margin-top:-1px}#title-block-header .abstract{margin-block-start:1rem}#title-block-header .abstract .abstract-title{font-weight:600}#title-block-header a{text-decoration:none}#title-block-header .author,#title-block-header .date,#title-block-header .doi{margin-block-end:.2rem}#title-block-header .quarto-title-block>div{display:flex}#title-block-header .quarto-title-block>div>h1,#title-block-header .quarto-title-block>div>.h1{flex-grow:1}#title-block-header .quarto-title-block>div>button{flex-shrink:0;height:2.25rem;margin-top:0}@media(min-width: 992px){#title-block-header .quarto-title-block>div>button{margin-top:5px}}tr.header>th>p:last-of-type{margin-bottom:0px}table,.table{caption-side:top;margin-bottom:1.5rem}caption,.table-caption{padding-top:.5rem;padding-bottom:.5rem;text-align:center}.utterances{max-width:none;margin-left:-8px}iframe{margin-bottom:1em}details{margin-bottom:1em}details[show]{margin-bottom:0}details>summary{color:#6c757d}details>summary>p:only-child{display:inline}pre.sourceCode,code.sourceCode{position:relative}p code:not(.sourceCode){white-space:pre-wrap}code{white-space:pre}@media print{code{white-space:pre-wrap}}pre>code{display:block}pre>code.sourceCode{white-space:pre}pre>code.sourceCode>span>a:first-child::before{text-decoration:none}pre.code-overflow-wrap>code.sourceCode{white-space:pre-wrap}pre.code-overflow-scroll>code.sourceCode{white-space:pre}code a:any-link{color:inherit;text-decoration:none}code a:hover{color:inherit;text-decoration:underline}ul.task-list{padding-left:1em}[data-tippy-root]{display:inline-block}.tippy-content .footnote-back{display:none}.quarto-embedded-source-code{display:none}.quarto-unresolved-ref{font-weight:600}.quarto-cover-image{max-width:35%;float:right;margin-left:30px}.cell-output-display .widget-subarea{margin-bottom:1em}.cell-output-display:not(.no-overflow-x){overflow-x:auto}.panel-input{margin-bottom:1em}.panel-input>div,.panel-input>div>div{display:inline-block;vertical-align:top;padding-right:12px}.panel-input>p:last-child{margin-bottom:0}.layout-sidebar{margin-bottom:1em}.layout-sidebar .tab-content{border:none}.tab-content>.page-columns.active{display:grid}div.sourceCode>iframe{width:100%;height:300px;margin-bottom:-0.5em}div.ansi-escaped-output{font-family:monospace;display:block}/*! +* +* ansi colors from IPython notebook's +* +*/.ansi-black-fg{color:#3e424d}.ansi-black-bg{background-color:#3e424d}.ansi-black-intense-fg{color:#282c36}.ansi-black-intense-bg{background-color:#282c36}.ansi-red-fg{color:#e75c58}.ansi-red-bg{background-color:#e75c58}.ansi-red-intense-fg{color:#b22b31}.ansi-red-intense-bg{background-color:#b22b31}.ansi-green-fg{color:#00a250}.ansi-green-bg{background-color:#00a250}.ansi-green-intense-fg{color:#007427}.ansi-green-intense-bg{background-color:#007427}.ansi-yellow-fg{color:#ddb62b}.ansi-yellow-bg{background-color:#ddb62b}.ansi-yellow-intense-fg{color:#b27d12}.ansi-yellow-intense-bg{background-color:#b27d12}.ansi-blue-fg{color:#208ffb}.ansi-blue-bg{background-color:#208ffb}.ansi-blue-intense-fg{color:#0065ca}.ansi-blue-intense-bg{background-color:#0065ca}.ansi-magenta-fg{color:#d160c4}.ansi-magenta-bg{background-color:#d160c4}.ansi-magenta-intense-fg{color:#a03196}.ansi-magenta-intense-bg{background-color:#a03196}.ansi-cyan-fg{color:#60c6c8}.ansi-cyan-bg{background-color:#60c6c8}.ansi-cyan-intense-fg{color:#258f8f}.ansi-cyan-intense-bg{background-color:#258f8f}.ansi-white-fg{color:#c5c1b4}.ansi-white-bg{background-color:#c5c1b4}.ansi-white-intense-fg{color:#a1a6b2}.ansi-white-intense-bg{background-color:#a1a6b2}.ansi-default-inverse-fg{color:#fff}.ansi-default-inverse-bg{background-color:#000}.ansi-bold{font-weight:bold}.ansi-underline{text-decoration:underline}:root{--quarto-body-bg: #ffffff;--quarto-body-color: #212529;--quarto-text-muted: #6c757d;--quarto-border-color: #dee2e6;--quarto-border-width: 1px;--quarto-border-radius: 0.25rem}table.gt_table{color:var(--quarto-body-color);font-size:1em;width:100%;background-color:transparent;border-top-width:inherit;border-bottom-width:inherit;border-color:var(--quarto-border-color)}table.gt_table th.gt_column_spanner_outer{color:var(--quarto-body-color);background-color:transparent;border-top-width:inherit;border-bottom-width:inherit;border-color:var(--quarto-border-color)}table.gt_table th.gt_col_heading{color:var(--quarto-body-color);font-weight:bold;background-color:transparent}table.gt_table thead.gt_col_headings{border-bottom:1px solid currentColor;border-top-width:inherit;border-top-color:var(--quarto-border-color)}table.gt_table thead.gt_col_headings:not(:first-child){border-top-width:1px;border-top-color:var(--quarto-border-color)}table.gt_table td.gt_row{border-bottom-width:1px;border-bottom-color:var(--quarto-border-color);border-top-width:0px}table.gt_table tbody.gt_table_body{border-top-width:1px;border-bottom-width:1px;border-bottom-color:var(--quarto-border-color);border-top-color:currentColor}div.columns{display:initial;gap:initial}div.column{display:inline-block;overflow-x:initial;vertical-align:top;width:50%}@media print{:root{font-size:11pt}#quarto-sidebar,#TOC,.nav-page{display:none}.page-columns .content{grid-column-start:page-start}.fixed-top{position:relative}.panel-caption,.figure-caption,figcaption{color:#666}}.code-copy-button{position:absolute;top:0;right:0;border:0;margin-top:5px;margin-right:5px;background-color:transparent}.code-copy-button:focus{outline:none}.code-copy-button-tooltip{font-size:.75em}pre.sourceCode:hover>.code-copy-button>.bi::before{display:inline-block;height:1rem;width:1rem;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:1rem 1rem}pre.sourceCode:hover>.code-copy-button-checked>.bi::before{background-image:url('data:image/svg+xml,')}pre.sourceCode:hover>.code-copy-button:hover>.bi::before{background-image:url('data:image/svg+xml,')}pre.sourceCode:hover>.code-copy-button-checked:hover>.bi::before{background-image:url('data:image/svg+xml,')}main ol ol,main ul ul,main ol ul,main ul ol{margin-bottom:1em}body{margin:0}main.page-columns>header>h1.title,main.page-columns>header>.title.h1{margin-bottom:0}@media(min-width: 992px){body .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset] 35px [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(500px, calc(850px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.fullcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset] 35px [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(500px, calc(850px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] 35px [page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.slimcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset] 35px [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.listing:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc(1200px - 3em)) [body-content-end] 3em [body-end] 50px [body-end-outset] minmax(0px, 250px) [page-end-inset] 50px [page-end] 1fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 175px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 175px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] minmax(25px, 50px) [page-start-inset] minmax(50px, 150px) [body-start-outset] minmax(25px, 50px) [body-start] 1.5em [body-content-start] minmax(500px, calc(800px - 3em)) [body-content-end] 1.5em [body-end] minmax(25px, 50px) [body-end-outset] minmax(50px, 150px) [page-end-inset] minmax(25px, 50px) [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc( 1000px - 3em )) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 100px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc( 1000px - 3em )) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 50px [page-start-inset] minmax(50px, 150px) [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc(800px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(450px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc( 1000px - 3em )) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 100px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 50px [page-start-inset] minmax(50px, 150px) [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(450px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(50px, 150px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] minmax(25px, 50px) [page-start-inset] minmax(50px, 150px) [body-start-outset] minmax(25px, 50px) [body-start] 1.5em [body-content-start] minmax(500px, calc(800px - 3em)) [body-content-end] 1.5em [body-end] minmax(25px, 50px) [body-end-outset] minmax(50px, 150px) [page-end-inset] minmax(25px, 50px) [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}}@media(max-width: 991.98px){body .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.fullcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.slimcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.listing:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc(1200px - 3em)) [body-content-end body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 145px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc(750px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 145px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc(750px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(75px, 150px) [page-end-inset] 25px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(25px, 50px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc( 1000px - 3em )) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc(800px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(25px, 50px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 4fr [screen-end-inset] 1.5em [screen-end]}body.floating.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(75px, 150px) [page-end-inset] 25px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}}@media(max-width: 767.98px){body .page-columns,body.fullcontent:not(.floating):not(.docked) .page-columns,body.slimcontent:not(.floating):not(.docked) .page-columns,body.docked .page-columns,body.docked.slimcontent .page-columns,body.docked.fullcontent .page-columns,body.floating .page-columns,body.floating.slimcontent .page-columns,body.floating.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(0px, 1fr) [body-content-end body-end body-end-outset page-end-inset page-end screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(0px, 1fr) [body-content-end body-end body-end-outset page-end-inset page-end screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(0px, 1fr) [body-content-end body-end body-end-outset page-end-inset page-end screen-end-inset] 1.5em [screen-end]}nav[role=doc-toc]{display:none}}body,.page-row-navigation{grid-template-rows:[page-top] max-content [contents-top] max-content [contents-bottom] max-content [page-bottom]}.page-rows-contents{grid-template-rows:[content-top] minmax(max-content, 1fr) [content-bottom] minmax(60px, max-content) [page-bottom]}.page-full{grid-column:screen-start/screen-end !important}.page-columns>*{grid-column:body-content-start/body-content-end}.page-columns.column-page>*{grid-column:page-start/page-end}.page-columns.column-page-left>*{grid-column:page-start/body-content-end}.page-columns.column-page-right>*{grid-column:body-content-start/page-end}.page-rows{grid-auto-rows:auto}.header{grid-column:screen-start/screen-end;grid-row:page-top/contents-top}#quarto-content{padding:0;grid-column:screen-start/screen-end;grid-row:contents-top/contents-bottom}body.floating .sidebar.sidebar-navigation{grid-column:page-start/body-start;grid-row:content-top/page-bottom}body.docked .sidebar.sidebar-navigation{grid-column:screen-start/body-start;grid-row:content-top/page-bottom}.sidebar.toc-left{grid-column:page-start/body-start;grid-row:content-top/page-bottom}.sidebar.margin-sidebar{grid-column:body-end/page-end;grid-row:content-top/page-bottom}.page-columns .content{grid-column:body-content-start/body-content-end;grid-row:content-top/content-bottom;align-content:flex-start}.page-columns .page-navigation{grid-column:body-content-start/body-content-end;grid-row:content-bottom/page-bottom}.page-columns .footer{grid-column:screen-start/screen-end;grid-row:contents-bottom/page-bottom}.page-columns .column-body{grid-column:body-content-start/body-content-end}.page-columns .column-body-fullbleed{grid-column:body-start/body-end}.page-columns .column-body-outset{grid-column:body-start-outset/body-end-outset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-body-outset table{background:#fff}.page-columns .column-body-outset-left{grid-column:body-start-outset/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-body-outset-left table{background:#fff}.page-columns .column-body-outset-right{grid-column:body-content-start/body-end-outset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-body-outset-right table{background:#fff}.page-columns .column-page{grid-column:page-start/page-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page table{background:#fff}.page-columns .column-page-inset{grid-column:page-start-inset/page-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-inset table{background:#fff}.page-columns .column-page-inset-left{grid-column:page-start-inset/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-inset-left table{background:#fff}.page-columns .column-page-inset-right{grid-column:body-content-start/page-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-inset-right figcaption table{background:#fff}.page-columns .column-page-left{grid-column:page-start/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-left table{background:#fff}.page-columns .column-page-right{grid-column:body-content-start/page-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-right figcaption table{background:#fff}#quarto-content.page-columns #quarto-margin-sidebar,#quarto-content.page-columns #quarto-sidebar{z-index:1}@media(max-width: 991.98px){#quarto-content.page-columns #quarto-margin-sidebar.collapse,#quarto-content.page-columns #quarto-sidebar.collapse{z-index:1055}}#quarto-content.page-columns main.column-page,#quarto-content.page-columns main.column-page-right,#quarto-content.page-columns main.column-page-left{z-index:0}.page-columns .column-screen-inset{grid-column:screen-start-inset/screen-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset table{background:#fff}.page-columns .column-screen-inset-left{grid-column:screen-start-inset/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset-left table{background:#fff}.page-columns .column-screen-inset-right{grid-column:body-content-start/screen-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset-right table{background:#fff}.page-columns .column-screen{grid-column:screen-start/screen-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen table{background:#fff}.page-columns .column-screen-left{grid-column:screen-start/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-left table{background:#fff}.page-columns .column-screen-right{grid-column:body-content-start/screen-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-right table{background:#fff}.page-columns .column-screen-inset-shaded{grid-column:screen-start/screen-end;padding:1em;background:#f8f9fa;z-index:998;transform:translate3d(0, 0, 0);margin-bottom:1em}.zindex-content{z-index:998;transform:translate3d(0, 0, 0)}.zindex-modal{z-index:1055;transform:translate3d(0, 0, 0)}.zindex-over-content{z-index:999;transform:translate3d(0, 0, 0)}img.img-fluid.column-screen,img.img-fluid.column-screen-inset-shaded,img.img-fluid.column-screen-inset,img.img-fluid.column-screen-inset-left,img.img-fluid.column-screen-inset-right,img.img-fluid.column-screen-left,img.img-fluid.column-screen-right{width:100%}@media(min-width: 992px){.margin-caption,div.aside,aside,.column-margin{grid-column:body-end/page-end !important;z-index:998}.column-sidebar{grid-column:page-start/body-start !important;z-index:998}.column-leftmargin{grid-column:screen-start-inset/body-start !important;z-index:998}.no-row-height{height:1em;overflow:visible}}@media(max-width: 991.98px){.margin-caption,div.aside,aside,.column-margin{grid-column:body-end/page-end !important;z-index:998}.no-row-height{height:1em;overflow:visible}.page-columns.page-full{overflow:visible}.page-columns.toc-left .margin-caption,.page-columns.toc-left div.aside,.page-columns.toc-left aside,.page-columns.toc-left .column-margin{grid-column:body-content-start/body-content-end !important;z-index:998;transform:translate3d(0, 0, 0)}.page-columns.toc-left .no-row-height{height:initial;overflow:initial}}@media(max-width: 767.98px){.margin-caption,div.aside,aside,.column-margin{grid-column:body-content-start/body-content-end !important;z-index:998;transform:translate3d(0, 0, 0)}.no-row-height{height:initial;overflow:initial}#quarto-margin-sidebar{display:none}.hidden-sm{display:none}}.panel-grid{display:grid;grid-template-rows:repeat(1, 1fr);grid-template-columns:repeat(24, 1fr);gap:1em}.panel-grid .g-col-1{grid-column:auto/span 1}.panel-grid .g-col-2{grid-column:auto/span 2}.panel-grid .g-col-3{grid-column:auto/span 3}.panel-grid .g-col-4{grid-column:auto/span 4}.panel-grid .g-col-5{grid-column:auto/span 5}.panel-grid .g-col-6{grid-column:auto/span 6}.panel-grid .g-col-7{grid-column:auto/span 7}.panel-grid .g-col-8{grid-column:auto/span 8}.panel-grid .g-col-9{grid-column:auto/span 9}.panel-grid .g-col-10{grid-column:auto/span 10}.panel-grid .g-col-11{grid-column:auto/span 11}.panel-grid .g-col-12{grid-column:auto/span 12}.panel-grid .g-col-13{grid-column:auto/span 13}.panel-grid .g-col-14{grid-column:auto/span 14}.panel-grid .g-col-15{grid-column:auto/span 15}.panel-grid .g-col-16{grid-column:auto/span 16}.panel-grid .g-col-17{grid-column:auto/span 17}.panel-grid .g-col-18{grid-column:auto/span 18}.panel-grid .g-col-19{grid-column:auto/span 19}.panel-grid .g-col-20{grid-column:auto/span 20}.panel-grid .g-col-21{grid-column:auto/span 21}.panel-grid .g-col-22{grid-column:auto/span 22}.panel-grid .g-col-23{grid-column:auto/span 23}.panel-grid .g-col-24{grid-column:auto/span 24}.panel-grid .g-start-1{grid-column-start:1}.panel-grid .g-start-2{grid-column-start:2}.panel-grid .g-start-3{grid-column-start:3}.panel-grid .g-start-4{grid-column-start:4}.panel-grid .g-start-5{grid-column-start:5}.panel-grid .g-start-6{grid-column-start:6}.panel-grid .g-start-7{grid-column-start:7}.panel-grid .g-start-8{grid-column-start:8}.panel-grid .g-start-9{grid-column-start:9}.panel-grid .g-start-10{grid-column-start:10}.panel-grid .g-start-11{grid-column-start:11}.panel-grid .g-start-12{grid-column-start:12}.panel-grid .g-start-13{grid-column-start:13}.panel-grid .g-start-14{grid-column-start:14}.panel-grid .g-start-15{grid-column-start:15}.panel-grid .g-start-16{grid-column-start:16}.panel-grid .g-start-17{grid-column-start:17}.panel-grid .g-start-18{grid-column-start:18}.panel-grid .g-start-19{grid-column-start:19}.panel-grid .g-start-20{grid-column-start:20}.panel-grid .g-start-21{grid-column-start:21}.panel-grid .g-start-22{grid-column-start:22}.panel-grid .g-start-23{grid-column-start:23}@media(min-width: 576px){.panel-grid .g-col-sm-1{grid-column:auto/span 1}.panel-grid .g-col-sm-2{grid-column:auto/span 2}.panel-grid .g-col-sm-3{grid-column:auto/span 3}.panel-grid .g-col-sm-4{grid-column:auto/span 4}.panel-grid .g-col-sm-5{grid-column:auto/span 5}.panel-grid .g-col-sm-6{grid-column:auto/span 6}.panel-grid .g-col-sm-7{grid-column:auto/span 7}.panel-grid .g-col-sm-8{grid-column:auto/span 8}.panel-grid .g-col-sm-9{grid-column:auto/span 9}.panel-grid .g-col-sm-10{grid-column:auto/span 10}.panel-grid .g-col-sm-11{grid-column:auto/span 11}.panel-grid .g-col-sm-12{grid-column:auto/span 12}.panel-grid .g-col-sm-13{grid-column:auto/span 13}.panel-grid .g-col-sm-14{grid-column:auto/span 14}.panel-grid .g-col-sm-15{grid-column:auto/span 15}.panel-grid .g-col-sm-16{grid-column:auto/span 16}.panel-grid .g-col-sm-17{grid-column:auto/span 17}.panel-grid .g-col-sm-18{grid-column:auto/span 18}.panel-grid .g-col-sm-19{grid-column:auto/span 19}.panel-grid .g-col-sm-20{grid-column:auto/span 20}.panel-grid .g-col-sm-21{grid-column:auto/span 21}.panel-grid .g-col-sm-22{grid-column:auto/span 22}.panel-grid .g-col-sm-23{grid-column:auto/span 23}.panel-grid .g-col-sm-24{grid-column:auto/span 24}.panel-grid .g-start-sm-1{grid-column-start:1}.panel-grid .g-start-sm-2{grid-column-start:2}.panel-grid .g-start-sm-3{grid-column-start:3}.panel-grid .g-start-sm-4{grid-column-start:4}.panel-grid .g-start-sm-5{grid-column-start:5}.panel-grid .g-start-sm-6{grid-column-start:6}.panel-grid .g-start-sm-7{grid-column-start:7}.panel-grid .g-start-sm-8{grid-column-start:8}.panel-grid .g-start-sm-9{grid-column-start:9}.panel-grid .g-start-sm-10{grid-column-start:10}.panel-grid .g-start-sm-11{grid-column-start:11}.panel-grid .g-start-sm-12{grid-column-start:12}.panel-grid .g-start-sm-13{grid-column-start:13}.panel-grid .g-start-sm-14{grid-column-start:14}.panel-grid .g-start-sm-15{grid-column-start:15}.panel-grid .g-start-sm-16{grid-column-start:16}.panel-grid .g-start-sm-17{grid-column-start:17}.panel-grid .g-start-sm-18{grid-column-start:18}.panel-grid .g-start-sm-19{grid-column-start:19}.panel-grid .g-start-sm-20{grid-column-start:20}.panel-grid .g-start-sm-21{grid-column-start:21}.panel-grid .g-start-sm-22{grid-column-start:22}.panel-grid .g-start-sm-23{grid-column-start:23}}@media(min-width: 768px){.panel-grid .g-col-md-1{grid-column:auto/span 1}.panel-grid .g-col-md-2{grid-column:auto/span 2}.panel-grid .g-col-md-3{grid-column:auto/span 3}.panel-grid .g-col-md-4{grid-column:auto/span 4}.panel-grid .g-col-md-5{grid-column:auto/span 5}.panel-grid .g-col-md-6{grid-column:auto/span 6}.panel-grid .g-col-md-7{grid-column:auto/span 7}.panel-grid .g-col-md-8{grid-column:auto/span 8}.panel-grid .g-col-md-9{grid-column:auto/span 9}.panel-grid .g-col-md-10{grid-column:auto/span 10}.panel-grid .g-col-md-11{grid-column:auto/span 11}.panel-grid .g-col-md-12{grid-column:auto/span 12}.panel-grid .g-col-md-13{grid-column:auto/span 13}.panel-grid .g-col-md-14{grid-column:auto/span 14}.panel-grid .g-col-md-15{grid-column:auto/span 15}.panel-grid .g-col-md-16{grid-column:auto/span 16}.panel-grid .g-col-md-17{grid-column:auto/span 17}.panel-grid .g-col-md-18{grid-column:auto/span 18}.panel-grid .g-col-md-19{grid-column:auto/span 19}.panel-grid .g-col-md-20{grid-column:auto/span 20}.panel-grid .g-col-md-21{grid-column:auto/span 21}.panel-grid .g-col-md-22{grid-column:auto/span 22}.panel-grid .g-col-md-23{grid-column:auto/span 23}.panel-grid .g-col-md-24{grid-column:auto/span 24}.panel-grid .g-start-md-1{grid-column-start:1}.panel-grid .g-start-md-2{grid-column-start:2}.panel-grid .g-start-md-3{grid-column-start:3}.panel-grid .g-start-md-4{grid-column-start:4}.panel-grid .g-start-md-5{grid-column-start:5}.panel-grid .g-start-md-6{grid-column-start:6}.panel-grid .g-start-md-7{grid-column-start:7}.panel-grid .g-start-md-8{grid-column-start:8}.panel-grid .g-start-md-9{grid-column-start:9}.panel-grid .g-start-md-10{grid-column-start:10}.panel-grid .g-start-md-11{grid-column-start:11}.panel-grid .g-start-md-12{grid-column-start:12}.panel-grid .g-start-md-13{grid-column-start:13}.panel-grid .g-start-md-14{grid-column-start:14}.panel-grid .g-start-md-15{grid-column-start:15}.panel-grid .g-start-md-16{grid-column-start:16}.panel-grid .g-start-md-17{grid-column-start:17}.panel-grid .g-start-md-18{grid-column-start:18}.panel-grid .g-start-md-19{grid-column-start:19}.panel-grid .g-start-md-20{grid-column-start:20}.panel-grid .g-start-md-21{grid-column-start:21}.panel-grid .g-start-md-22{grid-column-start:22}.panel-grid .g-start-md-23{grid-column-start:23}}@media(min-width: 992px){.panel-grid .g-col-lg-1{grid-column:auto/span 1}.panel-grid .g-col-lg-2{grid-column:auto/span 2}.panel-grid .g-col-lg-3{grid-column:auto/span 3}.panel-grid .g-col-lg-4{grid-column:auto/span 4}.panel-grid .g-col-lg-5{grid-column:auto/span 5}.panel-grid .g-col-lg-6{grid-column:auto/span 6}.panel-grid .g-col-lg-7{grid-column:auto/span 7}.panel-grid .g-col-lg-8{grid-column:auto/span 8}.panel-grid .g-col-lg-9{grid-column:auto/span 9}.panel-grid .g-col-lg-10{grid-column:auto/span 10}.panel-grid .g-col-lg-11{grid-column:auto/span 11}.panel-grid .g-col-lg-12{grid-column:auto/span 12}.panel-grid .g-col-lg-13{grid-column:auto/span 13}.panel-grid .g-col-lg-14{grid-column:auto/span 14}.panel-grid .g-col-lg-15{grid-column:auto/span 15}.panel-grid .g-col-lg-16{grid-column:auto/span 16}.panel-grid .g-col-lg-17{grid-column:auto/span 17}.panel-grid .g-col-lg-18{grid-column:auto/span 18}.panel-grid .g-col-lg-19{grid-column:auto/span 19}.panel-grid .g-col-lg-20{grid-column:auto/span 20}.panel-grid .g-col-lg-21{grid-column:auto/span 21}.panel-grid .g-col-lg-22{grid-column:auto/span 22}.panel-grid .g-col-lg-23{grid-column:auto/span 23}.panel-grid .g-col-lg-24{grid-column:auto/span 24}.panel-grid .g-start-lg-1{grid-column-start:1}.panel-grid .g-start-lg-2{grid-column-start:2}.panel-grid .g-start-lg-3{grid-column-start:3}.panel-grid .g-start-lg-4{grid-column-start:4}.panel-grid .g-start-lg-5{grid-column-start:5}.panel-grid .g-start-lg-6{grid-column-start:6}.panel-grid .g-start-lg-7{grid-column-start:7}.panel-grid .g-start-lg-8{grid-column-start:8}.panel-grid .g-start-lg-9{grid-column-start:9}.panel-grid .g-start-lg-10{grid-column-start:10}.panel-grid .g-start-lg-11{grid-column-start:11}.panel-grid .g-start-lg-12{grid-column-start:12}.panel-grid .g-start-lg-13{grid-column-start:13}.panel-grid .g-start-lg-14{grid-column-start:14}.panel-grid .g-start-lg-15{grid-column-start:15}.panel-grid .g-start-lg-16{grid-column-start:16}.panel-grid .g-start-lg-17{grid-column-start:17}.panel-grid .g-start-lg-18{grid-column-start:18}.panel-grid .g-start-lg-19{grid-column-start:19}.panel-grid .g-start-lg-20{grid-column-start:20}.panel-grid .g-start-lg-21{grid-column-start:21}.panel-grid .g-start-lg-22{grid-column-start:22}.panel-grid .g-start-lg-23{grid-column-start:23}}@media(min-width: 1200px){.panel-grid .g-col-xl-1{grid-column:auto/span 1}.panel-grid .g-col-xl-2{grid-column:auto/span 2}.panel-grid .g-col-xl-3{grid-column:auto/span 3}.panel-grid .g-col-xl-4{grid-column:auto/span 4}.panel-grid .g-col-xl-5{grid-column:auto/span 5}.panel-grid .g-col-xl-6{grid-column:auto/span 6}.panel-grid .g-col-xl-7{grid-column:auto/span 7}.panel-grid .g-col-xl-8{grid-column:auto/span 8}.panel-grid .g-col-xl-9{grid-column:auto/span 9}.panel-grid .g-col-xl-10{grid-column:auto/span 10}.panel-grid .g-col-xl-11{grid-column:auto/span 11}.panel-grid .g-col-xl-12{grid-column:auto/span 12}.panel-grid .g-col-xl-13{grid-column:auto/span 13}.panel-grid .g-col-xl-14{grid-column:auto/span 14}.panel-grid .g-col-xl-15{grid-column:auto/span 15}.panel-grid .g-col-xl-16{grid-column:auto/span 16}.panel-grid .g-col-xl-17{grid-column:auto/span 17}.panel-grid .g-col-xl-18{grid-column:auto/span 18}.panel-grid .g-col-xl-19{grid-column:auto/span 19}.panel-grid .g-col-xl-20{grid-column:auto/span 20}.panel-grid .g-col-xl-21{grid-column:auto/span 21}.panel-grid .g-col-xl-22{grid-column:auto/span 22}.panel-grid .g-col-xl-23{grid-column:auto/span 23}.panel-grid .g-col-xl-24{grid-column:auto/span 24}.panel-grid .g-start-xl-1{grid-column-start:1}.panel-grid .g-start-xl-2{grid-column-start:2}.panel-grid .g-start-xl-3{grid-column-start:3}.panel-grid .g-start-xl-4{grid-column-start:4}.panel-grid .g-start-xl-5{grid-column-start:5}.panel-grid .g-start-xl-6{grid-column-start:6}.panel-grid .g-start-xl-7{grid-column-start:7}.panel-grid .g-start-xl-8{grid-column-start:8}.panel-grid .g-start-xl-9{grid-column-start:9}.panel-grid .g-start-xl-10{grid-column-start:10}.panel-grid .g-start-xl-11{grid-column-start:11}.panel-grid .g-start-xl-12{grid-column-start:12}.panel-grid .g-start-xl-13{grid-column-start:13}.panel-grid .g-start-xl-14{grid-column-start:14}.panel-grid .g-start-xl-15{grid-column-start:15}.panel-grid .g-start-xl-16{grid-column-start:16}.panel-grid .g-start-xl-17{grid-column-start:17}.panel-grid .g-start-xl-18{grid-column-start:18}.panel-grid .g-start-xl-19{grid-column-start:19}.panel-grid .g-start-xl-20{grid-column-start:20}.panel-grid .g-start-xl-21{grid-column-start:21}.panel-grid .g-start-xl-22{grid-column-start:22}.panel-grid .g-start-xl-23{grid-column-start:23}}@media(min-width: 1400px){.panel-grid .g-col-xxl-1{grid-column:auto/span 1}.panel-grid .g-col-xxl-2{grid-column:auto/span 2}.panel-grid .g-col-xxl-3{grid-column:auto/span 3}.panel-grid .g-col-xxl-4{grid-column:auto/span 4}.panel-grid .g-col-xxl-5{grid-column:auto/span 5}.panel-grid .g-col-xxl-6{grid-column:auto/span 6}.panel-grid .g-col-xxl-7{grid-column:auto/span 7}.panel-grid .g-col-xxl-8{grid-column:auto/span 8}.panel-grid .g-col-xxl-9{grid-column:auto/span 9}.panel-grid .g-col-xxl-10{grid-column:auto/span 10}.panel-grid .g-col-xxl-11{grid-column:auto/span 11}.panel-grid .g-col-xxl-12{grid-column:auto/span 12}.panel-grid .g-col-xxl-13{grid-column:auto/span 13}.panel-grid .g-col-xxl-14{grid-column:auto/span 14}.panel-grid .g-col-xxl-15{grid-column:auto/span 15}.panel-grid .g-col-xxl-16{grid-column:auto/span 16}.panel-grid .g-col-xxl-17{grid-column:auto/span 17}.panel-grid .g-col-xxl-18{grid-column:auto/span 18}.panel-grid .g-col-xxl-19{grid-column:auto/span 19}.panel-grid .g-col-xxl-20{grid-column:auto/span 20}.panel-grid .g-col-xxl-21{grid-column:auto/span 21}.panel-grid .g-col-xxl-22{grid-column:auto/span 22}.panel-grid .g-col-xxl-23{grid-column:auto/span 23}.panel-grid .g-col-xxl-24{grid-column:auto/span 24}.panel-grid .g-start-xxl-1{grid-column-start:1}.panel-grid .g-start-xxl-2{grid-column-start:2}.panel-grid .g-start-xxl-3{grid-column-start:3}.panel-grid .g-start-xxl-4{grid-column-start:4}.panel-grid .g-start-xxl-5{grid-column-start:5}.panel-grid .g-start-xxl-6{grid-column-start:6}.panel-grid .g-start-xxl-7{grid-column-start:7}.panel-grid .g-start-xxl-8{grid-column-start:8}.panel-grid .g-start-xxl-9{grid-column-start:9}.panel-grid .g-start-xxl-10{grid-column-start:10}.panel-grid .g-start-xxl-11{grid-column-start:11}.panel-grid .g-start-xxl-12{grid-column-start:12}.panel-grid .g-start-xxl-13{grid-column-start:13}.panel-grid .g-start-xxl-14{grid-column-start:14}.panel-grid .g-start-xxl-15{grid-column-start:15}.panel-grid .g-start-xxl-16{grid-column-start:16}.panel-grid .g-start-xxl-17{grid-column-start:17}.panel-grid .g-start-xxl-18{grid-column-start:18}.panel-grid .g-start-xxl-19{grid-column-start:19}.panel-grid .g-start-xxl-20{grid-column-start:20}.panel-grid .g-start-xxl-21{grid-column-start:21}.panel-grid .g-start-xxl-22{grid-column-start:22}.panel-grid .g-start-xxl-23{grid-column-start:23}}main{margin-top:1em;margin-bottom:1em}h1,.h1,h2,.h2{margin-top:2rem;margin-bottom:1rem}h1.title,.title.h1{margin-top:0}h2,.h2{border-bottom:1px solid #dee2e6;padding-bottom:.5rem}h3,.h3,h4,.h4{margin-top:1.5rem}.header-section-number{color:#5a6570}.nav-link.active .header-section-number{color:inherit}mark,.mark{padding:0em}.panel-caption,caption,.figure-caption{font-size:1rem}.panel-caption,.figure-caption,figcaption{color:#5a6570}.table-caption,caption{color:#212529}.quarto-layout-cell[data-ref-parent] caption{color:#5a6570}.column-margin figcaption,.margin-caption,div.aside,aside,.column-margin{color:#5a6570;font-size:.825rem}.panel-caption.margin-caption{text-align:inherit}.column-margin.column-container p{margin-bottom:0}.column-margin.column-container>*:not(.collapse){padding-top:.5em;padding-bottom:.5em;display:block}.column-margin.column-container>*.collapse:not(.show){display:none}@media(min-width: 768px){.column-margin.column-container .callout-margin-content:first-child{margin-top:4.5em}.column-margin.column-container .callout-margin-content-simple:first-child{margin-top:3.5em}}.margin-caption>*{padding-top:.5em;padding-bottom:.5em}@media(max-width: 767.98px){.quarto-layout-row{flex-direction:column}}.tab-content{margin-top:0px;border-left:#dee2e6 1px solid;border-right:#dee2e6 1px solid;border-bottom:#dee2e6 1px solid;margin-left:0;padding:1em;margin-bottom:1em}@media(max-width: 767.98px){.layout-sidebar{margin-left:0;margin-right:0}}.panel-sidebar,.panel-sidebar .form-control,.panel-input,.panel-input .form-control,.selectize-dropdown{font-size:.9rem}.panel-sidebar .form-control,.panel-input .form-control{padding-top:.1rem}.tab-pane div.sourceCode{margin-top:0px}.tab-pane>p{padding-top:1em}.tab-content>.tab-pane:not(.active){display:none !important}div.sourceCode{background-color:rgba(233,236,239,.65);border:1px solid rgba(233,236,239,.65);border-radius:.25rem}pre.sourceCode{background-color:transparent}pre.sourceCode{border:none;font-size:.875em;overflow:visible !important;padding:.4em}.callout pre.sourceCode{padding-left:0}div.sourceCode{overflow-y:hidden}.callout div.sourceCode{margin-left:initial}.blockquote{font-size:inherit;padding-left:1rem;padding-right:1.5rem;color:#5a6570}.blockquote h1:first-child,.blockquote .h1:first-child,.blockquote h2:first-child,.blockquote .h2:first-child,.blockquote h3:first-child,.blockquote .h3:first-child,.blockquote h4:first-child,.blockquote .h4:first-child,.blockquote h5:first-child,.blockquote .h5:first-child{margin-top:0}pre{background-color:initial;padding:initial;border:initial}p code:not(.sourceCode),li code:not(.sourceCode){background-color:#f6f6f6;padding:.2em}nav p code:not(.sourceCode),nav li code:not(.sourceCode){background-color:transparent;padding:0}#quarto-embedded-source-code-modal>.modal-dialog{max-width:1000px;padding-left:1.75rem;padding-right:1.75rem}#quarto-embedded-source-code-modal>.modal-dialog>.modal-content>.modal-body{padding:0}#quarto-embedded-source-code-modal>.modal-dialog>.modal-content>.modal-body div.sourceCode{margin:0;padding:.2rem .2rem;border-radius:0px;border:none}#quarto-embedded-source-code-modal>.modal-dialog>.modal-content>.modal-header{padding:.7rem}.code-tools-button{font-size:1rem;padding:.15rem .15rem;margin-left:5px;color:#6c757d;background-color:transparent;transition:initial;cursor:pointer}.code-tools-button>.bi::before{display:inline-block;height:1rem;width:1rem;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:1rem 1rem}.code-tools-button:hover>.bi::before{background-image:url('data:image/svg+xml,')}#quarto-embedded-source-code-modal .code-copy-button>.bi::before{background-image:url('data:image/svg+xml,')}#quarto-embedded-source-code-modal .code-copy-button-checked>.bi::before{background-image:url('data:image/svg+xml,')}.sidebar{will-change:top;transition:top 200ms linear;position:sticky;overflow-y:auto;padding-top:1.2em;max-height:100vh}.sidebar.toc-left,.sidebar.margin-sidebar{top:0px;padding-top:1em}.sidebar.toc-left>*,.sidebar.margin-sidebar>*{padding-top:.5em}.sidebar.quarto-banner-title-block-sidebar>*{padding-top:1.65em}.sidebar nav[role=doc-toc]>h2,.sidebar nav[role=doc-toc]>.h2{font-size:.875rem;font-weight:400;margin-bottom:.5rem;margin-top:.3rem;font-family:inherit;border-bottom:0;padding-bottom:0;padding-top:0px}.sidebar nav[role=doc-toc]>ul a{border-left:1px solid #e9ecef;padding-left:.6rem}.sidebar nav[role=doc-toc]>ul a:empty{display:none}.sidebar nav[role=doc-toc] ul{padding-left:0;list-style:none;font-size:.875rem;font-weight:300}.sidebar nav[role=doc-toc]>ul li a{line-height:1.1rem;padding-bottom:.2rem;padding-top:.2rem;color:inherit}.sidebar nav[role=doc-toc] ul>li>ul>li>a{padding-left:1.2em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>a{padding-left:2.4em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>ul>li>a{padding-left:3.6em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>ul>li>ul>li>a{padding-left:4.8em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>ul>li>ul>li>ul>li>a{padding-left:6em}.sidebar nav[role=doc-toc] ul>li>ul>li>a.active{border-left:1px solid #0d6efd;color:#0d6efd !important}.sidebar nav[role=doc-toc] ul>li>a.active{border-left:1px solid #0d6efd;color:#0d6efd !important}kbd,.kbd{color:#212529;background-color:#f8f9fa;border:1px solid;border-radius:5px;border-color:#dee2e6}div.hanging-indent{margin-left:1em;text-indent:-1em}.citation a,.footnote-ref{text-decoration:none}.footnotes ol{padding-left:1em}.tippy-content>*{margin-bottom:.7em}.tippy-content>*:last-child{margin-bottom:0}.table a{word-break:break-word}.table>:not(:first-child){border-top-width:1px;border-top-color:#dee2e6}.table>thead{border-bottom:1px solid currentColor}.table>tbody{border-top:1px solid #dee2e6}.callout{margin-top:1.25rem;margin-bottom:1.25rem;border-radius:.25rem}.callout.callout-style-simple{padding:.4em .7em;border-left:5px solid;border-right:1px solid #dee2e6;border-top:1px solid #dee2e6;border-bottom:1px solid #dee2e6}.callout.callout-style-default{border-left:5px solid;border-right:1px solid #dee2e6;border-top:1px solid #dee2e6;border-bottom:1px solid #dee2e6}.callout .callout-body-container{flex-grow:1}.callout.callout-style-simple .callout-body{font-size:.9rem;font-weight:400}.callout.callout-style-default .callout-body{font-size:.9rem;font-weight:400}.callout.callout-captioned .callout-body{margin-top:.2em}.callout:not(.no-icon).callout-captioned.callout-style-simple .callout-body{padding-left:1.6em}.callout.callout-captioned>.callout-header{padding-top:.2em;margin-bottom:-0.2em}.callout.callout-style-simple>div.callout-header{border-bottom:none;font-size:.9rem;font-weight:600;opacity:75%}.callout.callout-style-default>div.callout-header{border-bottom:none;font-weight:600;opacity:85%;font-size:.9rem;padding-left:.5em;padding-right:.5em}.callout.callout-style-default div.callout-body{padding-left:.5em;padding-right:.5em}.callout.callout-style-default div.callout-body>:first-child{margin-top:.5em}.callout>div.callout-header[data-bs-toggle=collapse]{cursor:pointer}.callout.callout-style-default .callout-header[aria-expanded=false],.callout.callout-style-default .callout-header[aria-expanded=true]{padding-top:0px;margin-bottom:0px;align-items:center}.callout.callout-captioned .callout-body>:last-child:not(.sourceCode),.callout.callout-captioned .callout-body>div>:last-child:not(.sourceCode){margin-bottom:.5rem}.callout:not(.callout-captioned) .callout-body>:first-child,.callout:not(.callout-captioned) .callout-body>div>:first-child{margin-top:.25rem}.callout:not(.callout-captioned) .callout-body>:last-child,.callout:not(.callout-captioned) .callout-body>div>:last-child{margin-bottom:.2rem}.callout.callout-style-simple .callout-icon::before,.callout.callout-style-simple .callout-toggle::before{height:1rem;width:1rem;display:inline-block;content:"";background-repeat:no-repeat;background-size:1rem 1rem}.callout.callout-style-default .callout-icon::before,.callout.callout-style-default .callout-toggle::before{height:.9rem;width:.9rem;display:inline-block;content:"";background-repeat:no-repeat;background-size:.9rem .9rem}.callout.callout-style-default .callout-toggle::before{margin-top:5px}.callout .callout-btn-toggle .callout-toggle::before{transition:transform .2s linear}.callout .callout-header[aria-expanded=false] .callout-toggle::before{transform:rotate(-90deg)}.callout .callout-header[aria-expanded=true] .callout-toggle::before{transform:none}.callout.callout-style-simple:not(.no-icon) div.callout-icon-container{padding-top:.2em;padding-right:.55em}.callout.callout-style-default:not(.no-icon) div.callout-icon-container{padding-top:.1em;padding-right:.35em}.callout.callout-style-default:not(.no-icon) div.callout-caption-container{margin-top:-1px}.callout.callout-style-default.callout-caution:not(.no-icon) div.callout-icon-container{padding-top:.3em;padding-right:.35em}.callout>.callout-body>.callout-icon-container>.no-icon,.callout>.callout-header>.callout-icon-container>.no-icon{display:none}div.callout.callout{border-left-color:#6c757d}div.callout.callout-style-default>.callout-header{background-color:#6c757d}div.callout-note.callout{border-left-color:#0d6efd}div.callout-note.callout-style-default>.callout-header{background-color:#e7f1ff}div.callout-note:not(.callout-captioned) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-note.callout-captioned .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-note .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-tip.callout{border-left-color:#198754}div.callout-tip.callout-style-default>.callout-header{background-color:#e8f3ee}div.callout-tip:not(.callout-captioned) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-tip.callout-captioned .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-tip .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-warning.callout{border-left-color:#ffc107}div.callout-warning.callout-style-default>.callout-header{background-color:#fff9e6}div.callout-warning:not(.callout-captioned) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-warning.callout-captioned .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-warning .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-caution.callout{border-left-color:#fd7e14}div.callout-caution.callout-style-default>.callout-header{background-color:#fff2e8}div.callout-caution:not(.callout-captioned) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-caution.callout-captioned .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-caution .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-important.callout{border-left-color:#dc3545}div.callout-important.callout-style-default>.callout-header{background-color:#fcebec}div.callout-important:not(.callout-captioned) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-important.callout-captioned .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-important .callout-toggle::before{background-image:url('data:image/svg+xml,')}.quarto-toggle-container{display:flex;align-items:center}@media(min-width: 992px){.navbar .quarto-color-scheme-toggle{padding-left:.5rem;padding-right:.5rem}}@media(max-width: 767.98px){.navbar .quarto-color-scheme-toggle{padding-left:0;padding-right:0;padding-bottom:.5em}}.quarto-reader-toggle .bi::before,.quarto-color-scheme-toggle .bi::before{display:inline-block;height:1rem;width:1rem;content:"";background-repeat:no-repeat;background-size:1rem 1rem}.navbar-collapse .quarto-color-scheme-toggle{padding-left:.6rem;padding-right:0;margin-top:-12px}.sidebar-navigation{padding-left:20px}.sidebar-navigation .quarto-color-scheme-toggle .bi::before{padding-top:.2rem;margin-bottom:-0.2rem}.sidebar-tools-main .quarto-color-scheme-toggle .bi::before{padding-top:.2rem;margin-bottom:-0.2rem}.navbar .quarto-color-scheme-toggle .bi::before{padding-top:7px;margin-bottom:-7px;padding-left:2px;margin-right:2px}.navbar .quarto-color-scheme-toggle:not(.alternate) .bi::before{background-image:url('data:image/svg+xml,')}.navbar .quarto-color-scheme-toggle.alternate .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-color-scheme-toggle:not(.alternate) .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-color-scheme-toggle.alternate .bi::before{background-image:url('data:image/svg+xml,')}.quarto-sidebar-toggle{border-color:#dee2e6;border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem;border-style:solid;border-width:1px;overflow:hidden;border-top-width:0px;padding-top:0px !important}.quarto-sidebar-toggle-title{cursor:pointer;padding-bottom:2px;margin-left:.25em;text-align:center;font-weight:400;font-size:.775em}#quarto-content .quarto-sidebar-toggle{background:#fafafa}#quarto-content .quarto-sidebar-toggle-title{color:#212529}.quarto-sidebar-toggle-icon{color:#dee2e6;margin-right:.5em;float:right;transition:transform .2s ease}.quarto-sidebar-toggle-icon::before{padding-top:5px}.quarto-sidebar-toggle.expanded .quarto-sidebar-toggle-icon{transform:rotate(-180deg)}.quarto-sidebar-toggle.expanded .quarto-sidebar-toggle-title{border-bottom:solid #dee2e6 1px}.quarto-sidebar-toggle-contents{background-color:#fff;padding-right:10px;padding-left:10px;margin-top:0px !important;transition:max-height .5s ease}.quarto-sidebar-toggle.expanded .quarto-sidebar-toggle-contents{padding-top:1em;padding-bottom:10px}.quarto-sidebar-toggle:not(.expanded) .quarto-sidebar-toggle-contents{padding-top:0px !important;padding-bottom:0px}nav[role=doc-toc]{z-index:1020}#quarto-sidebar>*,nav[role=doc-toc]>*{transition:opacity .1s ease,border .1s ease}#quarto-sidebar.slow>*,nav[role=doc-toc].slow>*{transition:opacity .4s ease,border .4s ease}.quarto-color-scheme-toggle:not(.alternate).top-right .bi::before{background-image:url('data:image/svg+xml,')}.quarto-color-scheme-toggle.alternate.top-right .bi::before{background-image:url('data:image/svg+xml,')}#quarto-appendix.default{border-top:1px solid #dee2e6}#quarto-appendix.default{background-color:#fff;padding-top:1.5em;margin-top:2em;z-index:998}#quarto-appendix.default .quarto-appendix-heading{margin-top:0;line-height:1.4em;font-weight:600;opacity:.9;border-bottom:none;margin-bottom:0}#quarto-appendix.default .footnotes ol,#quarto-appendix.default .footnotes ol li>p:last-of-type,#quarto-appendix.default .quarto-appendix-contents>p:last-of-type{margin-bottom:0}#quarto-appendix.default .quarto-appendix-secondary-label{margin-bottom:.4em}#quarto-appendix.default .quarto-appendix-bibtex{font-size:.7em;padding:1em;border:solid 1px #dee2e6;margin-bottom:1em}#quarto-appendix.default .quarto-appendix-bibtex code.sourceCode{white-space:pre-wrap}#quarto-appendix.default .quarto-appendix-citeas{font-size:.9em;padding:1em;border:solid 1px #dee2e6;margin-bottom:1em}#quarto-appendix.default .quarto-appendix-heading{font-size:1em !important}#quarto-appendix.default *[role=doc-endnotes]>ol,#quarto-appendix.default .quarto-appendix-contents>*:not(h2):not(.h2){font-size:.9em}#quarto-appendix.default section{padding-bottom:1.5em}#quarto-appendix.default section *[role=doc-endnotes],#quarto-appendix.default section>*:not(a){opacity:.9;word-wrap:break-word}.btn.btn-quarto,div.cell-output-display .btn-quarto{color:#fefefe;background-color:#6c757d;border-color:#6c757d}.btn.btn-quarto:hover,div.cell-output-display .btn-quarto:hover{color:#fefefe;background-color:#828a91;border-color:#7b838a}.btn-check:focus+.btn.btn-quarto,.btn.btn-quarto:focus,.btn-check:focus+div.cell-output-display .btn-quarto,div.cell-output-display .btn-quarto:focus{color:#fefefe;background-color:#828a91;border-color:#7b838a;box-shadow:0 0 0 .25rem rgba(130,138,144,.5)}.btn-check:checked+.btn.btn-quarto,.btn-check:active+.btn.btn-quarto,.btn.btn-quarto:active,.btn.btn-quarto.active,.show>.btn.btn-quarto.dropdown-toggle,.btn-check:checked+div.cell-output-display .btn-quarto,.btn-check:active+div.cell-output-display .btn-quarto,div.cell-output-display .btn-quarto:active,div.cell-output-display .btn-quarto.active,.show>div.cell-output-display .btn-quarto.dropdown-toggle{color:#000;background-color:#899197;border-color:#7b838a}.btn-check:checked+.btn.btn-quarto:focus,.btn-check:active+.btn.btn-quarto:focus,.btn.btn-quarto:active:focus,.btn.btn-quarto.active:focus,.show>.btn.btn-quarto.dropdown-toggle:focus,.btn-check:checked+div.cell-output-display .btn-quarto:focus,.btn-check:active+div.cell-output-display .btn-quarto:focus,div.cell-output-display .btn-quarto:active:focus,div.cell-output-display .btn-quarto.active:focus,.show>div.cell-output-display .btn-quarto.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(130,138,144,.5)}.btn.btn-quarto:disabled,.btn.btn-quarto.disabled,div.cell-output-display .btn-quarto:disabled,div.cell-output-display .btn-quarto.disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}nav.quarto-secondary-nav.color-navbar{background-color:#0d6efd;color:#fdfeff}nav.quarto-secondary-nav.color-navbar h1,nav.quarto-secondary-nav.color-navbar .h1,nav.quarto-secondary-nav.color-navbar .quarto-btn-toggle{color:#fdfeff}@media(max-width: 991.98px){body.nav-sidebar .quarto-title-banner,body.nav-sidebar .quarto-title-banner{display:none}}p.subtitle{margin-top:.25em;margin-bottom:.5em}code a:any-link{color:inherit;text-decoration-color:#6c757d}/*! light */div.observablehq table thead tr th{background-color:var(--bs-body-bg)}input,button,select,optgroup,textarea{background-color:var(--bs-body-bg)}@media print{.page-columns .column-screen-inset{grid-column:page-start-inset/page-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset table{background:#fff}.page-columns .column-screen-inset-left{grid-column:page-start-inset/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset-left table{background:#fff}.page-columns .column-screen-inset-right{grid-column:body-content-start/page-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset-right table{background:#fff}.page-columns .column-screen{grid-column:page-start/page-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen table{background:#fff}.page-columns .column-screen-left{grid-column:page-start/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-left table{background:#fff}.page-columns .column-screen-right{grid-column:body-content-start/page-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-right table{background:#fff}.page-columns .column-screen-inset-shaded{grid-column:page-start-inset/page-end-inset;padding:1em;background:#f8f9fa;z-index:998;transform:translate3d(0, 0, 0);margin-bottom:1em}}.quarto-video{margin-bottom:1em}a.external:after{display:inline-block;height:.75rem;width:.75rem;margin-bottom:.15em;margin-left:.25em;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:.75rem .75rem}a.external:after:hover{cursor:pointer}.quarto-ext-icon{display:inline-block;font-size:.75em;padding-left:.3em}.code-with-filename .code-with-filename-file{margin-bottom:0;padding-bottom:2px;padding-top:2px;padding-left:.7em;border:var(--quarto-border-width) solid var(--quarto-border-color);border-radius:var(--quarto-border-radius);border-bottom:0;border-bottom-left-radius:0%;border-bottom-right-radius:0%}.code-with-filename div.sourceCode,.reveal .code-with-filename div.sourceCode{margin-top:0;border-top-left-radius:0%;border-top-right-radius:0%}.code-with-filename .code-with-filename-file pre{margin-bottom:0}.code-with-filename .code-with-filename-file,.code-with-filename .code-with-filename-file pre{background-color:rgba(219,219,219,.8)}.quarto-dark .code-with-filename .code-with-filename-file,.quarto-dark .code-with-filename .code-with-filename-file pre{background-color:#555}.code-with-filename .code-with-filename-file strong{font-weight:400}.quarto-title-banner{margin-bottom:1em;color:#fdfeff;background:#0d6efd}.quarto-title-banner .code-tools-button{color:#97cbff}.quarto-title-banner .code-tools-button:hover{color:#fdfeff}.quarto-title-banner .code-tools-button>.bi::before{background-image:url('data:image/svg+xml,')}.quarto-title-banner .code-tools-button:hover>.bi::before{background-image:url('data:image/svg+xml,')}.quarto-title-banner .quarto-title .title{font-weight:600}.quarto-title-banner .quarto-categories{margin-top:.75em}@media(min-width: 992px){.quarto-title-banner{padding-top:2.5em;padding-bottom:2.5em}}@media(max-width: 991.98px){.quarto-title-banner{padding-top:1em;padding-bottom:1em}}main.quarto-banner-title-block section:first-of-type h2:first-of-type,main.quarto-banner-title-block section:first-of-type .h2:first-of-type,main.quarto-banner-title-block section:first-of-type h3:first-of-type,main.quarto-banner-title-block section:first-of-type .h3:first-of-type,main.quarto-banner-title-block section:first-of-type h4:first-of-type,main.quarto-banner-title-block section:first-of-type .h4:first-of-type{margin-top:0}.quarto-title .quarto-categories{display:flex;column-gap:.4em;padding-bottom:.5em;margin-top:.75em}.quarto-title .quarto-categories .quarto-category{padding:.25em .75em;font-size:.65em;text-transform:uppercase;border:solid 1px;border-radius:.25rem;opacity:.6}.quarto-title .quarto-categories .quarto-category a{color:inherit}#title-block-header.quarto-title-block.default .quarto-title-meta{display:grid;grid-template-columns:repeat(2, 1fr)}#title-block-header.quarto-title-block.default .quarto-title .title{margin-bottom:0}#title-block-header.quarto-title-block.default .quarto-title-author-orcid img{margin-top:-5px}#title-block-header.quarto-title-block.default .quarto-description p:last-of-type{margin-bottom:0}#title-block-header.quarto-title-block.default .quarto-title-meta-contents p,#title-block-header.quarto-title-block.default .quarto-title-authors p,#title-block-header.quarto-title-block.default .quarto-title-affiliations p{margin-bottom:.1em}#title-block-header.quarto-title-block.default .quarto-title-meta-heading{text-transform:uppercase;margin-top:1em;font-size:.8em;opacity:.8;font-weight:400}#title-block-header.quarto-title-block.default .quarto-title-meta-contents{font-size:.9em}#title-block-header.quarto-title-block.default .quarto-title-meta-contents a{color:#212529}#title-block-header.quarto-title-block.default .quarto-title-meta-contents p.affiliation:last-of-type{margin-bottom:.7em}#title-block-header.quarto-title-block.default p.affiliation{margin-bottom:.1em}#title-block-header.quarto-title-block.default .description,#title-block-header.quarto-title-block.default .abstract{margin-top:0}#title-block-header.quarto-title-block.default .description>p,#title-block-header.quarto-title-block.default .abstract>p{font-size:.9em}#title-block-header.quarto-title-block.default .description>p:last-of-type,#title-block-header.quarto-title-block.default .abstract>p:last-of-type{margin-bottom:0}#title-block-header.quarto-title-block.default .description .abstract-title,#title-block-header.quarto-title-block.default .abstract .abstract-title{margin-top:1em;text-transform:uppercase;font-size:.8em;opacity:.8;font-weight:400}#title-block-header.quarto-title-block.default .quarto-title-meta-author{display:grid;grid-template-columns:1fr 1fr}/*# sourceMappingURL=397ef2e52d54cf686e4908b90039e9db.css.map */ diff --git a/doc/ch02-GettingStarted_files/libs/bootstrap/bootstrap.min.js b/doc/ch02-GettingStarted_files/libs/bootstrap/bootstrap.min.js new file mode 100644 index 000000000..cc0a25561 --- /dev/null +++ b/doc/ch02-GettingStarted_files/libs/bootstrap/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v5.1.3 (https://getbootstrap.com/) + * Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,(function(){"use strict";const t="transitionend",e=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return e},i=t=>{const i=e(t);return i&&document.querySelector(i)?i:null},n=t=>{const i=e(t);return i?document.querySelector(i):null},s=e=>{e.dispatchEvent(new Event(t))},o=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),r=t=>o(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(t):null,a=(t,e,i)=>{Object.keys(i).forEach((n=>{const s=i[n],r=e[n],a=r&&o(r)?"element":null==(l=r)?`${l}`:{}.toString.call(l).match(/\s([a-z]+)/i)[1].toLowerCase();var l;if(!new RegExp(s).test(a))throw new TypeError(`${t.toUpperCase()}: Option "${n}" provided type "${a}" but expected type "${s}".`)}))},l=t=>!(!o(t)||0===t.getClientRects().length)&&"visible"===getComputedStyle(t).getPropertyValue("visibility"),c=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),h=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?h(t.parentNode):null},d=()=>{},u=t=>{t.offsetHeight},f=()=>{const{jQuery:t}=window;return t&&!document.body.hasAttribute("data-bs-no-jquery")?t:null},p=[],m=()=>"rtl"===document.documentElement.dir,g=t=>{var e;e=()=>{const e=f();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(p.length||document.addEventListener("DOMContentLoaded",(()=>{p.forEach((t=>t()))})),p.push(e)):e()},_=t=>{"function"==typeof t&&t()},b=(e,i,n=!0)=>{if(!n)return void _(e);const o=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(i)+5;let r=!1;const a=({target:n})=>{n===i&&(r=!0,i.removeEventListener(t,a),_(e))};i.addEventListener(t,a),setTimeout((()=>{r||s(i)}),o)},v=(t,e,i,n)=>{let s=t.indexOf(e);if(-1===s)return t[!i&&n?t.length-1:0];const o=t.length;return s+=i?1:-1,n&&(s=(s+o)%o),t[Math.max(0,Math.min(s,o-1))]},y=/[^.]*(?=\..*)\.|.*/,w=/\..*/,E=/::\d+$/,A={};let T=1;const O={mouseenter:"mouseover",mouseleave:"mouseout"},C=/^(mouseenter|mouseleave)/i,k=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function L(t,e){return e&&`${e}::${T++}`||t.uidEvent||T++}function x(t){const e=L(t);return t.uidEvent=e,A[e]=A[e]||{},A[e]}function D(t,e,i=null){const n=Object.keys(t);for(let s=0,o=n.length;sfunction(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};n?n=t(n):i=t(i)}const[o,r,a]=S(e,i,n),l=x(t),c=l[a]||(l[a]={}),h=D(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=L(r,e.replace(y,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(let a=o.length;a--;)if(o[a]===r)return s.delegateTarget=r,n.oneOff&&j.off(t,s.type,e,i),i.apply(r,[s]);return null}}(t,i,n):function(t,e){return function i(n){return n.delegateTarget=t,i.oneOff&&j.off(t,n.type,e),e.apply(t,[n])}}(t,i);u.delegationSelector=o?i:null,u.originalHandler=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function I(t,e,i,n,s){const o=D(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function P(t){return t=t.replace(w,""),O[t]||t}const j={on(t,e,i,n){N(t,e,i,n,!1)},one(t,e,i,n){N(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=S(e,i,n),a=r!==e,l=x(t),c=e.startsWith(".");if(void 0!==o){if(!l||!l[r])return;return void I(t,l,r,o,s?i:null)}c&&Object.keys(l).forEach((i=>{!function(t,e,i,n){const s=e[i]||{};Object.keys(s).forEach((o=>{if(o.includes(n)){const n=s[o];I(t,e,i,n.originalHandler,n.delegationSelector)}}))}(t,l,i,e.slice(1))}));const h=l[r]||{};Object.keys(h).forEach((i=>{const n=i.replace(E,"");if(!a||e.includes(n)){const e=h[i];I(t,l,r,e.originalHandler,e.delegationSelector)}}))},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=f(),s=P(e),o=e!==s,r=k.has(s);let a,l=!0,c=!0,h=!1,d=null;return o&&n&&(a=n.Event(e,i),n(t).trigger(a),l=!a.isPropagationStopped(),c=!a.isImmediatePropagationStopped(),h=a.isDefaultPrevented()),r?(d=document.createEvent("HTMLEvents"),d.initEvent(s,l,!0)):d=new CustomEvent(e,{bubbles:l,cancelable:!0}),void 0!==i&&Object.keys(i).forEach((t=>{Object.defineProperty(d,t,{get:()=>i[t]})})),h&&d.preventDefault(),c&&t.dispatchEvent(d),d.defaultPrevented&&void 0!==a&&a.preventDefault(),d}},M=new Map,H={set(t,e,i){M.has(t)||M.set(t,new Map);const n=M.get(t);n.has(e)||0===n.size?n.set(e,i):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(n.keys())[0]}.`)},get:(t,e)=>M.has(t)&&M.get(t).get(e)||null,remove(t,e){if(!M.has(t))return;const i=M.get(t);i.delete(e),0===i.size&&M.delete(t)}};class B{constructor(t){(t=r(t))&&(this._element=t,H.set(this._element,this.constructor.DATA_KEY,this))}dispose(){H.remove(this._element,this.constructor.DATA_KEY),j.off(this._element,this.constructor.EVENT_KEY),Object.getOwnPropertyNames(this).forEach((t=>{this[t]=null}))}_queueCallback(t,e,i=!0){b(t,e,i)}static getInstance(t){return H.get(r(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.1.3"}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}}const R=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,s=t.NAME;j.on(document,i,`[data-bs-dismiss="${s}"]`,(function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),c(this))return;const o=n(this)||this.closest(`.${s}`);t.getOrCreateInstance(o)[e]()}))};class W extends B{static get NAME(){return"alert"}close(){if(j.trigger(this._element,"close.bs.alert").defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback((()=>this._destroyElement()),this._element,t)}_destroyElement(){this._element.remove(),j.trigger(this._element,"closed.bs.alert"),this.dispose()}static jQueryInterface(t){return this.each((function(){const e=W.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}R(W,"close"),g(W);const $='[data-bs-toggle="button"]';class z extends B{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=z.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}function q(t){return"true"===t||"false"!==t&&(t===Number(t).toString()?Number(t):""===t||"null"===t?null:t)}function F(t){return t.replace(/[A-Z]/g,(t=>`-${t.toLowerCase()}`))}j.on(document,"click.bs.button.data-api",$,(t=>{t.preventDefault();const e=t.target.closest($);z.getOrCreateInstance(e).toggle()})),g(z);const U={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${F(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${F(e)}`)},getDataAttributes(t){if(!t)return{};const e={};return Object.keys(t.dataset).filter((t=>t.startsWith("bs"))).forEach((i=>{let n=i.replace(/^bs/,"");n=n.charAt(0).toLowerCase()+n.slice(1,n.length),e[n]=q(t.dataset[i])})),e},getDataAttribute:(t,e)=>q(t.getAttribute(`data-bs-${F(e)}`)),offset(t){const e=t.getBoundingClientRect();return{top:e.top+window.pageYOffset,left:e.left+window.pageXOffset}},position:t=>({top:t.offsetTop,left:t.offsetLeft})},V={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter((t=>t.matches(e))),parents(t,e){const i=[];let n=t.parentNode;for(;n&&n.nodeType===Node.ELEMENT_NODE&&3!==n.nodeType;)n.matches(e)&&i.push(n),n=n.parentNode;return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map((t=>`${t}:not([tabindex^="-"])`)).join(", ");return this.find(e,t).filter((t=>!c(t)&&l(t)))}},K="carousel",X={interval:5e3,keyboard:!0,slide:!1,pause:"hover",wrap:!0,touch:!0},Y={interval:"(number|boolean)",keyboard:"boolean",slide:"(boolean|string)",pause:"(string|boolean)",wrap:"boolean",touch:"boolean"},Q="next",G="prev",Z="left",J="right",tt={ArrowLeft:J,ArrowRight:Z},et="slid.bs.carousel",it="active",nt=".active.carousel-item";class st extends B{constructor(t,e){super(t),this._items=null,this._interval=null,this._activeElement=null,this._isPaused=!1,this._isSliding=!1,this.touchTimeout=null,this.touchStartX=0,this.touchDeltaX=0,this._config=this._getConfig(e),this._indicatorsElement=V.findOne(".carousel-indicators",this._element),this._touchSupported="ontouchstart"in document.documentElement||navigator.maxTouchPoints>0,this._pointerEvent=Boolean(window.PointerEvent),this._addEventListeners()}static get Default(){return X}static get NAME(){return K}next(){this._slide(Q)}nextWhenVisible(){!document.hidden&&l(this._element)&&this.next()}prev(){this._slide(G)}pause(t){t||(this._isPaused=!0),V.findOne(".carousel-item-next, .carousel-item-prev",this._element)&&(s(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null}cycle(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config&&this._config.interval&&!this._isPaused&&(this._updateInterval(),this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))}to(t){this._activeElement=V.findOne(nt,this._element);const e=this._getItemIndex(this._activeElement);if(t>this._items.length-1||t<0)return;if(this._isSliding)return void j.one(this._element,et,(()=>this.to(t)));if(e===t)return this.pause(),void this.cycle();const i=t>e?Q:G;this._slide(i,this._items[t])}_getConfig(t){return t={...X,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(K,t,Y),t}_handleSwipe(){const t=Math.abs(this.touchDeltaX);if(t<=40)return;const e=t/this.touchDeltaX;this.touchDeltaX=0,e&&this._slide(e>0?J:Z)}_addEventListeners(){this._config.keyboard&&j.on(this._element,"keydown.bs.carousel",(t=>this._keydown(t))),"hover"===this._config.pause&&(j.on(this._element,"mouseenter.bs.carousel",(t=>this.pause(t))),j.on(this._element,"mouseleave.bs.carousel",(t=>this.cycle(t)))),this._config.touch&&this._touchSupported&&this._addTouchEventListeners()}_addTouchEventListeners(){const t=t=>this._pointerEvent&&("pen"===t.pointerType||"touch"===t.pointerType),e=e=>{t(e)?this.touchStartX=e.clientX:this._pointerEvent||(this.touchStartX=e.touches[0].clientX)},i=t=>{this.touchDeltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this.touchStartX},n=e=>{t(e)&&(this.touchDeltaX=e.clientX-this.touchStartX),this._handleSwipe(),"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((t=>this.cycle(t)),500+this._config.interval))};V.find(".carousel-item img",this._element).forEach((t=>{j.on(t,"dragstart.bs.carousel",(t=>t.preventDefault()))})),this._pointerEvent?(j.on(this._element,"pointerdown.bs.carousel",(t=>e(t))),j.on(this._element,"pointerup.bs.carousel",(t=>n(t))),this._element.classList.add("pointer-event")):(j.on(this._element,"touchstart.bs.carousel",(t=>e(t))),j.on(this._element,"touchmove.bs.carousel",(t=>i(t))),j.on(this._element,"touchend.bs.carousel",(t=>n(t))))}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=tt[t.key];e&&(t.preventDefault(),this._slide(e))}_getItemIndex(t){return this._items=t&&t.parentNode?V.find(".carousel-item",t.parentNode):[],this._items.indexOf(t)}_getItemByOrder(t,e){const i=t===Q;return v(this._items,e,i,this._config.wrap)}_triggerSlideEvent(t,e){const i=this._getItemIndex(t),n=this._getItemIndex(V.findOne(nt,this._element));return j.trigger(this._element,"slide.bs.carousel",{relatedTarget:t,direction:e,from:n,to:i})}_setActiveIndicatorElement(t){if(this._indicatorsElement){const e=V.findOne(".active",this._indicatorsElement);e.classList.remove(it),e.removeAttribute("aria-current");const i=V.find("[data-bs-target]",this._indicatorsElement);for(let e=0;e{j.trigger(this._element,et,{relatedTarget:o,direction:d,from:s,to:r})};if(this._element.classList.contains("slide")){o.classList.add(h),u(o),n.classList.add(c),o.classList.add(c);const t=()=>{o.classList.remove(c,h),o.classList.add(it),n.classList.remove(it,h,c),this._isSliding=!1,setTimeout(f,0)};this._queueCallback(t,n,!0)}else n.classList.remove(it),o.classList.add(it),this._isSliding=!1,f();a&&this.cycle()}_directionToOrder(t){return[J,Z].includes(t)?m()?t===Z?G:Q:t===Z?Q:G:t}_orderToDirection(t){return[Q,G].includes(t)?m()?t===G?Z:J:t===G?J:Z:t}static carouselInterface(t,e){const i=st.getOrCreateInstance(t,e);let{_config:n}=i;"object"==typeof e&&(n={...n,...e});const s="string"==typeof e?e:n.slide;if("number"==typeof e)i.to(e);else if("string"==typeof s){if(void 0===i[s])throw new TypeError(`No method named "${s}"`);i[s]()}else n.interval&&n.ride&&(i.pause(),i.cycle())}static jQueryInterface(t){return this.each((function(){st.carouselInterface(this,t)}))}static dataApiClickHandler(t){const e=n(this);if(!e||!e.classList.contains("carousel"))return;const i={...U.getDataAttributes(e),...U.getDataAttributes(this)},s=this.getAttribute("data-bs-slide-to");s&&(i.interval=!1),st.carouselInterface(e,i),s&&st.getInstance(e).to(s),t.preventDefault()}}j.on(document,"click.bs.carousel.data-api","[data-bs-slide], [data-bs-slide-to]",st.dataApiClickHandler),j.on(window,"load.bs.carousel.data-api",(()=>{const t=V.find('[data-bs-ride="carousel"]');for(let e=0,i=t.length;et===this._element));null!==s&&o.length&&(this._selector=s,this._triggerArray.push(e))}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return rt}static get NAME(){return ot}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t,e=[];if(this._config.parent){const t=V.find(ut,this._config.parent);e=V.find(".collapse.show, .collapse.collapsing",this._config.parent).filter((e=>!t.includes(e)))}const i=V.findOne(this._selector);if(e.length){const n=e.find((t=>i!==t));if(t=n?pt.getInstance(n):null,t&&t._isTransitioning)return}if(j.trigger(this._element,"show.bs.collapse").defaultPrevented)return;e.forEach((e=>{i!==e&&pt.getOrCreateInstance(e,{toggle:!1}).hide(),t||H.set(e,"bs.collapse",null)}));const n=this._getDimension();this._element.classList.remove(ct),this._element.classList.add(ht),this._element.style[n]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const s=`scroll${n[0].toUpperCase()+n.slice(1)}`;this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(ht),this._element.classList.add(ct,lt),this._element.style[n]="",j.trigger(this._element,"shown.bs.collapse")}),this._element,!0),this._element.style[n]=`${this._element[s]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(j.trigger(this._element,"hide.bs.collapse").defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,u(this._element),this._element.classList.add(ht),this._element.classList.remove(ct,lt);const e=this._triggerArray.length;for(let t=0;t{this._isTransitioning=!1,this._element.classList.remove(ht),this._element.classList.add(ct),j.trigger(this._element,"hidden.bs.collapse")}),this._element,!0)}_isShown(t=this._element){return t.classList.contains(lt)}_getConfig(t){return(t={...rt,...U.getDataAttributes(this._element),...t}).toggle=Boolean(t.toggle),t.parent=r(t.parent),a(ot,t,at),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=V.find(ut,this._config.parent);V.find(ft,this._config.parent).filter((e=>!t.includes(e))).forEach((t=>{const e=n(t);e&&this._addAriaAndCollapsedClass([t],this._isShown(e))}))}_addAriaAndCollapsedClass(t,e){t.length&&t.forEach((t=>{e?t.classList.remove(dt):t.classList.add(dt),t.setAttribute("aria-expanded",e)}))}static jQueryInterface(t){return this.each((function(){const e={};"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1);const i=pt.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}}))}}j.on(document,"click.bs.collapse.data-api",ft,(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();const e=i(this);V.find(e).forEach((t=>{pt.getOrCreateInstance(t,{toggle:!1}).toggle()}))})),g(pt);var mt="top",gt="bottom",_t="right",bt="left",vt="auto",yt=[mt,gt,_t,bt],wt="start",Et="end",At="clippingParents",Tt="viewport",Ot="popper",Ct="reference",kt=yt.reduce((function(t,e){return t.concat([e+"-"+wt,e+"-"+Et])}),[]),Lt=[].concat(yt,[vt]).reduce((function(t,e){return t.concat([e,e+"-"+wt,e+"-"+Et])}),[]),xt="beforeRead",Dt="read",St="afterRead",Nt="beforeMain",It="main",Pt="afterMain",jt="beforeWrite",Mt="write",Ht="afterWrite",Bt=[xt,Dt,St,Nt,It,Pt,jt,Mt,Ht];function Rt(t){return t?(t.nodeName||"").toLowerCase():null}function Wt(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function $t(t){return t instanceof Wt(t).Element||t instanceof Element}function zt(t){return t instanceof Wt(t).HTMLElement||t instanceof HTMLElement}function qt(t){return"undefined"!=typeof ShadowRoot&&(t instanceof Wt(t).ShadowRoot||t instanceof ShadowRoot)}const Ft={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];zt(s)&&Rt(s)&&(Object.assign(s.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});zt(n)&&Rt(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function Ut(t){return t.split("-")[0]}function Vt(t,e){var i=t.getBoundingClientRect();return{width:i.width/1,height:i.height/1,top:i.top/1,right:i.right/1,bottom:i.bottom/1,left:i.left/1,x:i.left/1,y:i.top/1}}function Kt(t){var e=Vt(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function Xt(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&qt(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function Yt(t){return Wt(t).getComputedStyle(t)}function Qt(t){return["table","td","th"].indexOf(Rt(t))>=0}function Gt(t){return(($t(t)?t.ownerDocument:t.document)||window.document).documentElement}function Zt(t){return"html"===Rt(t)?t:t.assignedSlot||t.parentNode||(qt(t)?t.host:null)||Gt(t)}function Jt(t){return zt(t)&&"fixed"!==Yt(t).position?t.offsetParent:null}function te(t){for(var e=Wt(t),i=Jt(t);i&&Qt(i)&&"static"===Yt(i).position;)i=Jt(i);return i&&("html"===Rt(i)||"body"===Rt(i)&&"static"===Yt(i).position)?e:i||function(t){var e=-1!==navigator.userAgent.toLowerCase().indexOf("firefox");if(-1!==navigator.userAgent.indexOf("Trident")&&zt(t)&&"fixed"===Yt(t).position)return null;for(var i=Zt(t);zt(i)&&["html","body"].indexOf(Rt(i))<0;){var n=Yt(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function ee(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}var ie=Math.max,ne=Math.min,se=Math.round;function oe(t,e,i){return ie(t,ne(e,i))}function re(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function ae(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}const le={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,n=t.name,s=t.options,o=i.elements.arrow,r=i.modifiersData.popperOffsets,a=Ut(i.placement),l=ee(a),c=[bt,_t].indexOf(a)>=0?"height":"width";if(o&&r){var h=function(t,e){return re("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:ae(t,yt))}(s.padding,i),d=Kt(o),u="y"===l?mt:bt,f="y"===l?gt:_t,p=i.rects.reference[c]+i.rects.reference[l]-r[l]-i.rects.popper[c],m=r[l]-i.rects.reference[l],g=te(o),_=g?"y"===l?g.clientHeight||0:g.clientWidth||0:0,b=p/2-m/2,v=h[u],y=_-d[c]-h[f],w=_/2-d[c]/2+b,E=oe(v,w,y),A=l;i.modifiersData[n]=((e={})[A]=E,e.centerOffset=E-w,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&Xt(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function ce(t){return t.split("-")[1]}var he={top:"auto",right:"auto",bottom:"auto",left:"auto"};function de(t){var e,i=t.popper,n=t.popperRect,s=t.placement,o=t.variation,r=t.offsets,a=t.position,l=t.gpuAcceleration,c=t.adaptive,h=t.roundOffsets,d=!0===h?function(t){var e=t.x,i=t.y,n=window.devicePixelRatio||1;return{x:se(se(e*n)/n)||0,y:se(se(i*n)/n)||0}}(r):"function"==typeof h?h(r):r,u=d.x,f=void 0===u?0:u,p=d.y,m=void 0===p?0:p,g=r.hasOwnProperty("x"),_=r.hasOwnProperty("y"),b=bt,v=mt,y=window;if(c){var w=te(i),E="clientHeight",A="clientWidth";w===Wt(i)&&"static"!==Yt(w=Gt(i)).position&&"absolute"===a&&(E="scrollHeight",A="scrollWidth"),w=w,s!==mt&&(s!==bt&&s!==_t||o!==Et)||(v=gt,m-=w[E]-n.height,m*=l?1:-1),s!==bt&&(s!==mt&&s!==gt||o!==Et)||(b=_t,f-=w[A]-n.width,f*=l?1:-1)}var T,O=Object.assign({position:a},c&&he);return l?Object.assign({},O,((T={})[v]=_?"0":"",T[b]=g?"0":"",T.transform=(y.devicePixelRatio||1)<=1?"translate("+f+"px, "+m+"px)":"translate3d("+f+"px, "+m+"px, 0)",T)):Object.assign({},O,((e={})[v]=_?m+"px":"",e[b]=g?f+"px":"",e.transform="",e))}const ue={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:Ut(e.placement),variation:ce(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,de(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,de(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}};var fe={passive:!0};const pe={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=Wt(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,fe)})),a&&l.addEventListener("resize",i.update,fe),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,fe)})),a&&l.removeEventListener("resize",i.update,fe)}},data:{}};var me={left:"right",right:"left",bottom:"top",top:"bottom"};function ge(t){return t.replace(/left|right|bottom|top/g,(function(t){return me[t]}))}var _e={start:"end",end:"start"};function be(t){return t.replace(/start|end/g,(function(t){return _e[t]}))}function ve(t){var e=Wt(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function ye(t){return Vt(Gt(t)).left+ve(t).scrollLeft}function we(t){var e=Yt(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function Ee(t){return["html","body","#document"].indexOf(Rt(t))>=0?t.ownerDocument.body:zt(t)&&we(t)?t:Ee(Zt(t))}function Ae(t,e){var i;void 0===e&&(e=[]);var n=Ee(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=Wt(n),r=s?[o].concat(o.visualViewport||[],we(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(Ae(Zt(r)))}function Te(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function Oe(t,e){return e===Tt?Te(function(t){var e=Wt(t),i=Gt(t),n=e.visualViewport,s=i.clientWidth,o=i.clientHeight,r=0,a=0;return n&&(s=n.width,o=n.height,/^((?!chrome|android).)*safari/i.test(navigator.userAgent)||(r=n.offsetLeft,a=n.offsetTop)),{width:s,height:o,x:r+ye(t),y:a}}(t)):zt(e)?function(t){var e=Vt(t);return e.top=e.top+t.clientTop,e.left=e.left+t.clientLeft,e.bottom=e.top+t.clientHeight,e.right=e.left+t.clientWidth,e.width=t.clientWidth,e.height=t.clientHeight,e.x=e.left,e.y=e.top,e}(e):Te(function(t){var e,i=Gt(t),n=ve(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=ie(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=ie(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+ye(t),l=-n.scrollTop;return"rtl"===Yt(s||i).direction&&(a+=ie(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(Gt(t)))}function Ce(t){var e,i=t.reference,n=t.element,s=t.placement,o=s?Ut(s):null,r=s?ce(s):null,a=i.x+i.width/2-n.width/2,l=i.y+i.height/2-n.height/2;switch(o){case mt:e={x:a,y:i.y-n.height};break;case gt:e={x:a,y:i.y+i.height};break;case _t:e={x:i.x+i.width,y:l};break;case bt:e={x:i.x-n.width,y:l};break;default:e={x:i.x,y:i.y}}var c=o?ee(o):null;if(null!=c){var h="y"===c?"height":"width";switch(r){case wt:e[c]=e[c]-(i[h]/2-n[h]/2);break;case Et:e[c]=e[c]+(i[h]/2-n[h]/2)}}return e}function ke(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=void 0===n?t.placement:n,o=i.boundary,r=void 0===o?At:o,a=i.rootBoundary,l=void 0===a?Tt:a,c=i.elementContext,h=void 0===c?Ot:c,d=i.altBoundary,u=void 0!==d&&d,f=i.padding,p=void 0===f?0:f,m=re("number"!=typeof p?p:ae(p,yt)),g=h===Ot?Ct:Ot,_=t.rects.popper,b=t.elements[u?g:h],v=function(t,e,i){var n="clippingParents"===e?function(t){var e=Ae(Zt(t)),i=["absolute","fixed"].indexOf(Yt(t).position)>=0&&zt(t)?te(t):t;return $t(i)?e.filter((function(t){return $t(t)&&Xt(t,i)&&"body"!==Rt(t)})):[]}(t):[].concat(e),s=[].concat(n,[i]),o=s[0],r=s.reduce((function(e,i){var n=Oe(t,i);return e.top=ie(n.top,e.top),e.right=ne(n.right,e.right),e.bottom=ne(n.bottom,e.bottom),e.left=ie(n.left,e.left),e}),Oe(t,o));return r.width=r.right-r.left,r.height=r.bottom-r.top,r.x=r.left,r.y=r.top,r}($t(b)?b:b.contextElement||Gt(t.elements.popper),r,l),y=Vt(t.elements.reference),w=Ce({reference:y,element:_,strategy:"absolute",placement:s}),E=Te(Object.assign({},_,w)),A=h===Ot?E:y,T={top:v.top-A.top+m.top,bottom:A.bottom-v.bottom+m.bottom,left:v.left-A.left+m.left,right:A.right-v.right+m.right},O=t.modifiersData.offset;if(h===Ot&&O){var C=O[s];Object.keys(T).forEach((function(t){var e=[_t,gt].indexOf(t)>=0?1:-1,i=[mt,gt].indexOf(t)>=0?"y":"x";T[t]+=C[i]*e}))}return T}function Le(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,a=i.flipVariations,l=i.allowedAutoPlacements,c=void 0===l?Lt:l,h=ce(n),d=h?a?kt:kt.filter((function(t){return ce(t)===h})):yt,u=d.filter((function(t){return c.indexOf(t)>=0}));0===u.length&&(u=d);var f=u.reduce((function(e,i){return e[i]=ke(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[Ut(i)],e}),{});return Object.keys(f).sort((function(t,e){return f[t]-f[e]}))}const xe={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name;if(!e.modifiersData[n]._skip){for(var s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0===r||r,l=i.fallbackPlacements,c=i.padding,h=i.boundary,d=i.rootBoundary,u=i.altBoundary,f=i.flipVariations,p=void 0===f||f,m=i.allowedAutoPlacements,g=e.options.placement,_=Ut(g),b=l||(_!==g&&p?function(t){if(Ut(t)===vt)return[];var e=ge(t);return[be(t),e,be(e)]}(g):[ge(g)]),v=[g].concat(b).reduce((function(t,i){return t.concat(Ut(i)===vt?Le(e,{placement:i,boundary:h,rootBoundary:d,padding:c,flipVariations:p,allowedAutoPlacements:m}):i)}),[]),y=e.rects.reference,w=e.rects.popper,E=new Map,A=!0,T=v[0],O=0;O=0,D=x?"width":"height",S=ke(e,{placement:C,boundary:h,rootBoundary:d,altBoundary:u,padding:c}),N=x?L?_t:bt:L?gt:mt;y[D]>w[D]&&(N=ge(N));var I=ge(N),P=[];if(o&&P.push(S[k]<=0),a&&P.push(S[N]<=0,S[I]<=0),P.every((function(t){return t}))){T=C,A=!1;break}E.set(C,P)}if(A)for(var j=function(t){var e=v.find((function(e){var i=E.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return T=e,"break"},M=p?3:1;M>0&&"break"!==j(M);M--);e.placement!==T&&(e.modifiersData[n]._skip=!0,e.placement=T,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function De(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function Se(t){return[mt,_t,gt,bt].some((function(e){return t[e]>=0}))}const Ne={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,s=e.rects.popper,o=e.modifiersData.preventOverflow,r=ke(e,{elementContext:"reference"}),a=ke(e,{altBoundary:!0}),l=De(r,n),c=De(a,s,o),h=Se(l),d=Se(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:h,hasPopperEscaped:d},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":h,"data-popper-escaped":d})}},Ie={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.offset,o=void 0===s?[0,0]:s,r=Lt.reduce((function(t,i){return t[i]=function(t,e,i){var n=Ut(t),s=[bt,mt].indexOf(n)>=0?-1:1,o="function"==typeof i?i(Object.assign({},e,{placement:t})):i,r=o[0],a=o[1];return r=r||0,a=(a||0)*s,[bt,_t].indexOf(n)>=0?{x:a,y:r}:{x:r,y:a}}(i,e.rects,o),t}),{}),a=r[e.placement],l=a.x,c=a.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=l,e.modifiersData.popperOffsets.y+=c),e.modifiersData[n]=r}},Pe={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=Ce({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}},je={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0!==r&&r,l=i.boundary,c=i.rootBoundary,h=i.altBoundary,d=i.padding,u=i.tether,f=void 0===u||u,p=i.tetherOffset,m=void 0===p?0:p,g=ke(e,{boundary:l,rootBoundary:c,padding:d,altBoundary:h}),_=Ut(e.placement),b=ce(e.placement),v=!b,y=ee(_),w="x"===y?"y":"x",E=e.modifiersData.popperOffsets,A=e.rects.reference,T=e.rects.popper,O="function"==typeof m?m(Object.assign({},e.rects,{placement:e.placement})):m,C={x:0,y:0};if(E){if(o||a){var k="y"===y?mt:bt,L="y"===y?gt:_t,x="y"===y?"height":"width",D=E[y],S=E[y]+g[k],N=E[y]-g[L],I=f?-T[x]/2:0,P=b===wt?A[x]:T[x],j=b===wt?-T[x]:-A[x],M=e.elements.arrow,H=f&&M?Kt(M):{width:0,height:0},B=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},R=B[k],W=B[L],$=oe(0,A[x],H[x]),z=v?A[x]/2-I-$-R-O:P-$-R-O,q=v?-A[x]/2+I+$+W+O:j+$+W+O,F=e.elements.arrow&&te(e.elements.arrow),U=F?"y"===y?F.clientTop||0:F.clientLeft||0:0,V=e.modifiersData.offset?e.modifiersData.offset[e.placement][y]:0,K=E[y]+z-V-U,X=E[y]+q-V;if(o){var Y=oe(f?ne(S,K):S,D,f?ie(N,X):N);E[y]=Y,C[y]=Y-D}if(a){var Q="x"===y?mt:bt,G="x"===y?gt:_t,Z=E[w],J=Z+g[Q],tt=Z-g[G],et=oe(f?ne(J,K):J,Z,f?ie(tt,X):tt);E[w]=et,C[w]=et-Z}}e.modifiersData[n]=C}},requiresIfExists:["offset"]};function Me(t,e,i){void 0===i&&(i=!1);var n=zt(e);zt(e)&&function(t){var e=t.getBoundingClientRect();e.width,t.offsetWidth,e.height,t.offsetHeight}(e);var s,o,r=Gt(e),a=Vt(t),l={scrollLeft:0,scrollTop:0},c={x:0,y:0};return(n||!n&&!i)&&(("body"!==Rt(e)||we(r))&&(l=(s=e)!==Wt(s)&&zt(s)?{scrollLeft:(o=s).scrollLeft,scrollTop:o.scrollTop}:ve(s)),zt(e)?((c=Vt(e)).x+=e.clientLeft,c.y+=e.clientTop):r&&(c.x=ye(r))),{x:a.left+l.scrollLeft-c.x,y:a.top+l.scrollTop-c.y,width:a.width,height:a.height}}function He(t){var e=new Map,i=new Set,n=[];function s(t){i.add(t.name),[].concat(t.requires||[],t.requiresIfExists||[]).forEach((function(t){if(!i.has(t)){var n=e.get(t);n&&s(n)}})),n.push(t)}return t.forEach((function(t){e.set(t.name,t)})),t.forEach((function(t){i.has(t.name)||s(t)})),n}var Be={placement:"bottom",modifiers:[],strategy:"absolute"};function Re(){for(var t=arguments.length,e=new Array(t),i=0;ij.on(t,"mouseover",d))),this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.add(Je),this._element.classList.add(Je),j.trigger(this._element,"shown.bs.dropdown",t)}hide(){if(c(this._element)||!this._isShown(this._menu))return;const t={relatedTarget:this._element};this._completeHide(t)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_completeHide(t){j.trigger(this._element,"hide.bs.dropdown",t).defaultPrevented||("ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>j.off(t,"mouseover",d))),this._popper&&this._popper.destroy(),this._menu.classList.remove(Je),this._element.classList.remove(Je),this._element.setAttribute("aria-expanded","false"),U.removeDataAttribute(this._menu,"popper"),j.trigger(this._element,"hidden.bs.dropdown",t))}_getConfig(t){if(t={...this.constructor.Default,...U.getDataAttributes(this._element),...t},a(Ue,t,this.constructor.DefaultType),"object"==typeof t.reference&&!o(t.reference)&&"function"!=typeof t.reference.getBoundingClientRect)throw new TypeError(`${Ue.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);return t}_createPopper(t){if(void 0===Fe)throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org)");let e=this._element;"parent"===this._config.reference?e=t:o(this._config.reference)?e=r(this._config.reference):"object"==typeof this._config.reference&&(e=this._config.reference);const i=this._getPopperConfig(),n=i.modifiers.find((t=>"applyStyles"===t.name&&!1===t.enabled));this._popper=qe(e,this._menu,i),n&&U.setDataAttribute(this._menu,"popper","static")}_isShown(t=this._element){return t.classList.contains(Je)}_getMenuElement(){return V.next(this._element,ei)[0]}_getPlacement(){const t=this._element.parentNode;if(t.classList.contains("dropend"))return ri;if(t.classList.contains("dropstart"))return ai;const e="end"===getComputedStyle(this._menu).getPropertyValue("--bs-position").trim();return t.classList.contains("dropup")?e?ni:ii:e?oi:si}_detectNavbar(){return null!==this._element.closest(".navbar")}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return"static"===this._config.display&&(t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,..."function"==typeof this._config.popperConfig?this._config.popperConfig(t):this._config.popperConfig}}_selectMenuItem({key:t,target:e}){const i=V.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter(l);i.length&&v(i,e,t===Ye,!i.includes(e)).focus()}static jQueryInterface(t){return this.each((function(){const e=hi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}static clearMenus(t){if(t&&(2===t.button||"keyup"===t.type&&"Tab"!==t.key))return;const e=V.find(ti);for(let i=0,n=e.length;ie+t)),this._setElementAttributes(di,"paddingRight",(e=>e+t)),this._setElementAttributes(ui,"marginRight",(e=>e-t))}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const n=this.getWidth();this._applyManipulationCallback(t,(t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+n)return;this._saveInitialAttribute(t,e);const s=window.getComputedStyle(t)[e];t.style[e]=`${i(Number.parseFloat(s))}px`}))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,"paddingRight"),this._resetElementAttributes(di,"paddingRight"),this._resetElementAttributes(ui,"marginRight")}_saveInitialAttribute(t,e){const i=t.style[e];i&&U.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,(t=>{const i=U.getDataAttribute(t,e);void 0===i?t.style.removeProperty(e):(U.removeDataAttribute(t,e),t.style[e]=i)}))}_applyManipulationCallback(t,e){o(t)?e(t):V.find(t,this._element).forEach(e)}isOverflowing(){return this.getWidth()>0}}const pi={className:"modal-backdrop",isVisible:!0,isAnimated:!1,rootElement:"body",clickCallback:null},mi={className:"string",isVisible:"boolean",isAnimated:"boolean",rootElement:"(element|string)",clickCallback:"(function|null)"},gi="show",_i="mousedown.bs.backdrop";class bi{constructor(t){this._config=this._getConfig(t),this._isAppended=!1,this._element=null}show(t){this._config.isVisible?(this._append(),this._config.isAnimated&&u(this._getElement()),this._getElement().classList.add(gi),this._emulateAnimation((()=>{_(t)}))):_(t)}hide(t){this._config.isVisible?(this._getElement().classList.remove(gi),this._emulateAnimation((()=>{this.dispose(),_(t)}))):_(t)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_getConfig(t){return(t={...pi,..."object"==typeof t?t:{}}).rootElement=r(t.rootElement),a("backdrop",t,mi),t}_append(){this._isAppended||(this._config.rootElement.append(this._getElement()),j.on(this._getElement(),_i,(()=>{_(this._config.clickCallback)})),this._isAppended=!0)}dispose(){this._isAppended&&(j.off(this._element,_i),this._element.remove(),this._isAppended=!1)}_emulateAnimation(t){b(t,this._getElement(),this._config.isAnimated)}}const vi={trapElement:null,autofocus:!0},yi={trapElement:"element",autofocus:"boolean"},wi=".bs.focustrap",Ei="backward";class Ai{constructor(t){this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}activate(){const{trapElement:t,autofocus:e}=this._config;this._isActive||(e&&t.focus(),j.off(document,wi),j.on(document,"focusin.bs.focustrap",(t=>this._handleFocusin(t))),j.on(document,"keydown.tab.bs.focustrap",(t=>this._handleKeydown(t))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,j.off(document,wi))}_handleFocusin(t){const{target:e}=t,{trapElement:i}=this._config;if(e===document||e===i||i.contains(e))return;const n=V.focusableChildren(i);0===n.length?i.focus():this._lastTabNavDirection===Ei?n[n.length-1].focus():n[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?Ei:"forward")}_getConfig(t){return t={...vi,..."object"==typeof t?t:{}},a("focustrap",t,yi),t}}const Ti="modal",Oi="Escape",Ci={backdrop:!0,keyboard:!0,focus:!0},ki={backdrop:"(boolean|string)",keyboard:"boolean",focus:"boolean"},Li="hidden.bs.modal",xi="show.bs.modal",Di="resize.bs.modal",Si="click.dismiss.bs.modal",Ni="keydown.dismiss.bs.modal",Ii="mousedown.dismiss.bs.modal",Pi="modal-open",ji="show",Mi="modal-static";class Hi extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._dialog=V.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._ignoreBackdropClick=!1,this._isTransitioning=!1,this._scrollBar=new fi}static get Default(){return Ci}static get NAME(){return Ti}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||j.trigger(this._element,xi,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isAnimated()&&(this._isTransitioning=!0),this._scrollBar.hide(),document.body.classList.add(Pi),this._adjustDialog(),this._setEscapeEvent(),this._setResizeEvent(),j.on(this._dialog,Ii,(()=>{j.one(this._element,"mouseup.dismiss.bs.modal",(t=>{t.target===this._element&&(this._ignoreBackdropClick=!0)}))})),this._showBackdrop((()=>this._showElement(t))))}hide(){if(!this._isShown||this._isTransitioning)return;if(j.trigger(this._element,"hide.bs.modal").defaultPrevented)return;this._isShown=!1;const t=this._isAnimated();t&&(this._isTransitioning=!0),this._setEscapeEvent(),this._setResizeEvent(),this._focustrap.deactivate(),this._element.classList.remove(ji),j.off(this._element,Si),j.off(this._dialog,Ii),this._queueCallback((()=>this._hideModal()),this._element,t)}dispose(){[window,this._dialog].forEach((t=>j.off(t,".bs.modal"))),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new bi({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new Ai({trapElement:this._element})}_getConfig(t){return t={...Ci,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(Ti,t,ki),t}_showElement(t){const e=this._isAnimated(),i=V.findOne(".modal-body",this._dialog);this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0,i&&(i.scrollTop=0),e&&u(this._element),this._element.classList.add(ji),this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,j.trigger(this._element,"shown.bs.modal",{relatedTarget:t})}),this._dialog,e)}_setEscapeEvent(){this._isShown?j.on(this._element,Ni,(t=>{this._config.keyboard&&t.key===Oi?(t.preventDefault(),this.hide()):this._config.keyboard||t.key!==Oi||this._triggerBackdropTransition()})):j.off(this._element,Ni)}_setResizeEvent(){this._isShown?j.on(window,Di,(()=>this._adjustDialog())):j.off(window,Di)}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide((()=>{document.body.classList.remove(Pi),this._resetAdjustments(),this._scrollBar.reset(),j.trigger(this._element,Li)}))}_showBackdrop(t){j.on(this._element,Si,(t=>{this._ignoreBackdropClick?this._ignoreBackdropClick=!1:t.target===t.currentTarget&&(!0===this._config.backdrop?this.hide():"static"===this._config.backdrop&&this._triggerBackdropTransition())})),this._backdrop.show(t)}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(j.trigger(this._element,"hidePrevented.bs.modal").defaultPrevented)return;const{classList:t,scrollHeight:e,style:i}=this._element,n=e>document.documentElement.clientHeight;!n&&"hidden"===i.overflowY||t.contains(Mi)||(n||(i.overflowY="hidden"),t.add(Mi),this._queueCallback((()=>{t.remove(Mi),n||this._queueCallback((()=>{i.overflowY=""}),this._dialog)}),this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;(!i&&t&&!m()||i&&!t&&m())&&(this._element.style.paddingLeft=`${e}px`),(i&&!t&&!m()||!i&&t&&m())&&(this._element.style.paddingRight=`${e}px`)}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=Hi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}j.on(document,"click.bs.modal.data-api",'[data-bs-toggle="modal"]',(function(t){const e=n(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),j.one(e,xi,(t=>{t.defaultPrevented||j.one(e,Li,(()=>{l(this)&&this.focus()}))}));const i=V.findOne(".modal.show");i&&Hi.getInstance(i).hide(),Hi.getOrCreateInstance(e).toggle(this)})),R(Hi),g(Hi);const Bi="offcanvas",Ri={backdrop:!0,keyboard:!0,scroll:!1},Wi={backdrop:"boolean",keyboard:"boolean",scroll:"boolean"},$i="show",zi=".offcanvas.show",qi="hidden.bs.offcanvas";class Fi extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get NAME(){return Bi}static get Default(){return Ri}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||j.trigger(this._element,"show.bs.offcanvas",{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._element.style.visibility="visible",this._backdrop.show(),this._config.scroll||(new fi).hide(),this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add($i),this._queueCallback((()=>{this._config.scroll||this._focustrap.activate(),j.trigger(this._element,"shown.bs.offcanvas",{relatedTarget:t})}),this._element,!0))}hide(){this._isShown&&(j.trigger(this._element,"hide.bs.offcanvas").defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.remove($i),this._backdrop.hide(),this._queueCallback((()=>{this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._element.style.visibility="hidden",this._config.scroll||(new fi).reset(),j.trigger(this._element,qi)}),this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_getConfig(t){return t={...Ri,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(Bi,t,Wi),t}_initializeBackDrop(){return new bi({className:"offcanvas-backdrop",isVisible:this._config.backdrop,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:()=>this.hide()})}_initializeFocusTrap(){return new Ai({trapElement:this._element})}_addEventListeners(){j.on(this._element,"keydown.dismiss.bs.offcanvas",(t=>{this._config.keyboard&&"Escape"===t.key&&this.hide()}))}static jQueryInterface(t){return this.each((function(){const e=Fi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}j.on(document,"click.bs.offcanvas.data-api",'[data-bs-toggle="offcanvas"]',(function(t){const e=n(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),c(this))return;j.one(e,qi,(()=>{l(this)&&this.focus()}));const i=V.findOne(zi);i&&i!==e&&Fi.getInstance(i).hide(),Fi.getOrCreateInstance(e).toggle(this)})),j.on(window,"load.bs.offcanvas.data-api",(()=>V.find(zi).forEach((t=>Fi.getOrCreateInstance(t).show())))),R(Fi),g(Fi);const Ui=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Vi=/^(?:(?:https?|mailto|ftp|tel|file|sms):|[^#&/:?]*(?:[#/?]|$))/i,Ki=/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z]+=*$/i,Xi=(t,e)=>{const i=t.nodeName.toLowerCase();if(e.includes(i))return!Ui.has(i)||Boolean(Vi.test(t.nodeValue)||Ki.test(t.nodeValue));const n=e.filter((t=>t instanceof RegExp));for(let t=0,e=n.length;t{Xi(t,r)||i.removeAttribute(t.nodeName)}))}return n.body.innerHTML}const Qi="tooltip",Gi=new Set(["sanitize","allowList","sanitizeFn"]),Zi={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(array|string|function)",container:"(string|element|boolean)",fallbackPlacements:"array",boundary:"(string|element)",customClass:"(string|function)",sanitize:"boolean",sanitizeFn:"(null|function)",allowList:"object",popperConfig:"(null|object|function)"},Ji={AUTO:"auto",TOP:"top",RIGHT:m()?"left":"right",BOTTOM:"bottom",LEFT:m()?"right":"left"},tn={animation:!0,template:'',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:[0,0],container:!1,fallbackPlacements:["top","right","bottom","left"],boundary:"clippingParents",customClass:"",sanitize:!0,sanitizeFn:null,allowList:{"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},popperConfig:null},en={HIDE:"hide.bs.tooltip",HIDDEN:"hidden.bs.tooltip",SHOW:"show.bs.tooltip",SHOWN:"shown.bs.tooltip",INSERTED:"inserted.bs.tooltip",CLICK:"click.bs.tooltip",FOCUSIN:"focusin.bs.tooltip",FOCUSOUT:"focusout.bs.tooltip",MOUSEENTER:"mouseenter.bs.tooltip",MOUSELEAVE:"mouseleave.bs.tooltip"},nn="fade",sn="show",on="show",rn="out",an=".tooltip-inner",ln=".modal",cn="hide.bs.modal",hn="hover",dn="focus";class un extends B{constructor(t,e){if(void 0===Fe)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t),this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this._config=this._getConfig(e),this.tip=null,this._setListeners()}static get Default(){return tn}static get NAME(){return Qi}static get Event(){return en}static get DefaultType(){return Zi}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(t){if(this._isEnabled)if(t){const e=this._initializeOnDelegatedTarget(t);e._activeTrigger.click=!e._activeTrigger.click,e._isWithActiveTrigger()?e._enter(null,e):e._leave(null,e)}else{if(this.getTipElement().classList.contains(sn))return void this._leave(null,this);this._enter(null,this)}}dispose(){clearTimeout(this._timeout),j.off(this._element.closest(ln),cn,this._hideModalHandler),this.tip&&this.tip.remove(),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this.isWithContent()||!this._isEnabled)return;const t=j.trigger(this._element,this.constructor.Event.SHOW),e=h(this._element),i=null===e?this._element.ownerDocument.documentElement.contains(this._element):e.contains(this._element);if(t.defaultPrevented||!i)return;"tooltip"===this.constructor.NAME&&this.tip&&this.getTitle()!==this.tip.querySelector(an).innerHTML&&(this._disposePopper(),this.tip.remove(),this.tip=null);const n=this.getTipElement(),s=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME);n.setAttribute("id",s),this._element.setAttribute("aria-describedby",s),this._config.animation&&n.classList.add(nn);const o="function"==typeof this._config.placement?this._config.placement.call(this,n,this._element):this._config.placement,r=this._getAttachment(o);this._addAttachmentClass(r);const{container:a}=this._config;H.set(n,this.constructor.DATA_KEY,this),this._element.ownerDocument.documentElement.contains(this.tip)||(a.append(n),j.trigger(this._element,this.constructor.Event.INSERTED)),this._popper?this._popper.update():this._popper=qe(this._element,n,this._getPopperConfig(r)),n.classList.add(sn);const l=this._resolvePossibleFunction(this._config.customClass);l&&n.classList.add(...l.split(" ")),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>{j.on(t,"mouseover",d)}));const c=this.tip.classList.contains(nn);this._queueCallback((()=>{const t=this._hoverState;this._hoverState=null,j.trigger(this._element,this.constructor.Event.SHOWN),t===rn&&this._leave(null,this)}),this.tip,c)}hide(){if(!this._popper)return;const t=this.getTipElement();if(j.trigger(this._element,this.constructor.Event.HIDE).defaultPrevented)return;t.classList.remove(sn),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>j.off(t,"mouseover",d))),this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1;const e=this.tip.classList.contains(nn);this._queueCallback((()=>{this._isWithActiveTrigger()||(this._hoverState!==on&&t.remove(),this._cleanTipClass(),this._element.removeAttribute("aria-describedby"),j.trigger(this._element,this.constructor.Event.HIDDEN),this._disposePopper())}),this.tip,e),this._hoverState=""}update(){null!==this._popper&&this._popper.update()}isWithContent(){return Boolean(this.getTitle())}getTipElement(){if(this.tip)return this.tip;const t=document.createElement("div");t.innerHTML=this._config.template;const e=t.children[0];return this.setContent(e),e.classList.remove(nn,sn),this.tip=e,this.tip}setContent(t){this._sanitizeAndSetContent(t,this.getTitle(),an)}_sanitizeAndSetContent(t,e,i){const n=V.findOne(i,t);e||!n?this.setElementContent(n,e):n.remove()}setElementContent(t,e){if(null!==t)return o(e)?(e=r(e),void(this._config.html?e.parentNode!==t&&(t.innerHTML="",t.append(e)):t.textContent=e.textContent)):void(this._config.html?(this._config.sanitize&&(e=Yi(e,this._config.allowList,this._config.sanitizeFn)),t.innerHTML=e):t.textContent=e)}getTitle(){const t=this._element.getAttribute("data-bs-original-title")||this._config.title;return this._resolvePossibleFunction(t)}updateAttachment(t){return"right"===t?"end":"left"===t?"start":t}_initializeOnDelegatedTarget(t,e){return e||this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return"function"==typeof t?t.call(this._element):t}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"onChange",enabled:!0,phase:"afterWrite",fn:t=>this._handlePopperPlacementChange(t)}],onFirstUpdate:t=>{t.options.placement!==t.placement&&this._handlePopperPlacementChange(t)}};return{...e,..."function"==typeof this._config.popperConfig?this._config.popperConfig(e):this._config.popperConfig}}_addAttachmentClass(t){this.getTipElement().classList.add(`${this._getBasicClassPrefix()}-${this.updateAttachment(t)}`)}_getAttachment(t){return Ji[t.toUpperCase()]}_setListeners(){this._config.trigger.split(" ").forEach((t=>{if("click"===t)j.on(this._element,this.constructor.Event.CLICK,this._config.selector,(t=>this.toggle(t)));else if("manual"!==t){const e=t===hn?this.constructor.Event.MOUSEENTER:this.constructor.Event.FOCUSIN,i=t===hn?this.constructor.Event.MOUSELEAVE:this.constructor.Event.FOCUSOUT;j.on(this._element,e,this._config.selector,(t=>this._enter(t))),j.on(this._element,i,this._config.selector,(t=>this._leave(t)))}})),this._hideModalHandler=()=>{this._element&&this.hide()},j.on(this._element.closest(ln),cn,this._hideModalHandler),this._config.selector?this._config={...this._config,trigger:"manual",selector:""}:this._fixTitle()}_fixTitle(){const t=this._element.getAttribute("title"),e=typeof this._element.getAttribute("data-bs-original-title");(t||"string"!==e)&&(this._element.setAttribute("data-bs-original-title",t||""),!t||this._element.getAttribute("aria-label")||this._element.textContent||this._element.setAttribute("aria-label",t),this._element.setAttribute("title",""))}_enter(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusin"===t.type?dn:hn]=!0),e.getTipElement().classList.contains(sn)||e._hoverState===on?e._hoverState=on:(clearTimeout(e._timeout),e._hoverState=on,e._config.delay&&e._config.delay.show?e._timeout=setTimeout((()=>{e._hoverState===on&&e.show()}),e._config.delay.show):e.show())}_leave(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusout"===t.type?dn:hn]=e._element.contains(t.relatedTarget)),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=rn,e._config.delay&&e._config.delay.hide?e._timeout=setTimeout((()=>{e._hoverState===rn&&e.hide()}),e._config.delay.hide):e.hide())}_isWithActiveTrigger(){for(const t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1}_getConfig(t){const e=U.getDataAttributes(this._element);return Object.keys(e).forEach((t=>{Gi.has(t)&&delete e[t]})),(t={...this.constructor.Default,...e,..."object"==typeof t&&t?t:{}}).container=!1===t.container?document.body:r(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),a(Qi,t,this.constructor.DefaultType),t.sanitize&&(t.template=Yi(t.template,t.allowList,t.sanitizeFn)),t}_getDelegateConfig(){const t={};for(const e in this._config)this.constructor.Default[e]!==this._config[e]&&(t[e]=this._config[e]);return t}_cleanTipClass(){const t=this.getTipElement(),e=new RegExp(`(^|\\s)${this._getBasicClassPrefix()}\\S+`,"g"),i=t.getAttribute("class").match(e);null!==i&&i.length>0&&i.map((t=>t.trim())).forEach((e=>t.classList.remove(e)))}_getBasicClassPrefix(){return"bs-tooltip"}_handlePopperPlacementChange(t){const{state:e}=t;e&&(this.tip=e.elements.popper,this._cleanTipClass(),this._addAttachmentClass(this._getAttachment(e.placement)))}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null)}static jQueryInterface(t){return this.each((function(){const e=un.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}g(un);const fn={...un.Default,placement:"right",offset:[0,8],trigger:"click",content:"",template:''},pn={...un.DefaultType,content:"(string|element|function)"},mn={HIDE:"hide.bs.popover",HIDDEN:"hidden.bs.popover",SHOW:"show.bs.popover",SHOWN:"shown.bs.popover",INSERTED:"inserted.bs.popover",CLICK:"click.bs.popover",FOCUSIN:"focusin.bs.popover",FOCUSOUT:"focusout.bs.popover",MOUSEENTER:"mouseenter.bs.popover",MOUSELEAVE:"mouseleave.bs.popover"};class gn extends un{static get Default(){return fn}static get NAME(){return"popover"}static get Event(){return mn}static get DefaultType(){return pn}isWithContent(){return this.getTitle()||this._getContent()}setContent(t){this._sanitizeAndSetContent(t,this.getTitle(),".popover-header"),this._sanitizeAndSetContent(t,this._getContent(),".popover-body")}_getContent(){return this._resolvePossibleFunction(this._config.content)}_getBasicClassPrefix(){return"bs-popover"}static jQueryInterface(t){return this.each((function(){const e=gn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}g(gn);const _n="scrollspy",bn={offset:10,method:"auto",target:""},vn={offset:"number",method:"string",target:"(string|element)"},yn="active",wn=".nav-link, .list-group-item, .dropdown-item",En="position";class An extends B{constructor(t,e){super(t),this._scrollElement="BODY"===this._element.tagName?window:this._element,this._config=this._getConfig(e),this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,j.on(this._scrollElement,"scroll.bs.scrollspy",(()=>this._process())),this.refresh(),this._process()}static get Default(){return bn}static get NAME(){return _n}refresh(){const t=this._scrollElement===this._scrollElement.window?"offset":En,e="auto"===this._config.method?t:this._config.method,n=e===En?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),V.find(wn,this._config.target).map((t=>{const s=i(t),o=s?V.findOne(s):null;if(o){const t=o.getBoundingClientRect();if(t.width||t.height)return[U[e](o).top+n,s]}return null})).filter((t=>t)).sort(((t,e)=>t[0]-e[0])).forEach((t=>{this._offsets.push(t[0]),this._targets.push(t[1])}))}dispose(){j.off(this._scrollElement,".bs.scrollspy"),super.dispose()}_getConfig(t){return(t={...bn,...U.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}}).target=r(t.target)||document.documentElement,a(_n,t,vn),t}_getScrollTop(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop}_getScrollHeight(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)}_getOffsetHeight(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height}_process(){const t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),i=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=i){const t=this._targets[this._targets.length-1];this._activeTarget!==t&&this._activate(t)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(let e=this._offsets.length;e--;)this._activeTarget!==this._targets[e]&&t>=this._offsets[e]&&(void 0===this._offsets[e+1]||t`${e}[data-bs-target="${t}"],${e}[href="${t}"]`)),i=V.findOne(e.join(","),this._config.target);i.classList.add(yn),i.classList.contains("dropdown-item")?V.findOne(".dropdown-toggle",i.closest(".dropdown")).classList.add(yn):V.parents(i,".nav, .list-group").forEach((t=>{V.prev(t,".nav-link, .list-group-item").forEach((t=>t.classList.add(yn))),V.prev(t,".nav-item").forEach((t=>{V.children(t,".nav-link").forEach((t=>t.classList.add(yn)))}))})),j.trigger(this._scrollElement,"activate.bs.scrollspy",{relatedTarget:t})}_clear(){V.find(wn,this._config.target).filter((t=>t.classList.contains(yn))).forEach((t=>t.classList.remove(yn)))}static jQueryInterface(t){return this.each((function(){const e=An.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}j.on(window,"load.bs.scrollspy.data-api",(()=>{V.find('[data-bs-spy="scroll"]').forEach((t=>new An(t)))})),g(An);const Tn="active",On="fade",Cn="show",kn=".active",Ln=":scope > li > .active";class xn extends B{static get NAME(){return"tab"}show(){if(this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE&&this._element.classList.contains(Tn))return;let t;const e=n(this._element),i=this._element.closest(".nav, .list-group");if(i){const e="UL"===i.nodeName||"OL"===i.nodeName?Ln:kn;t=V.find(e,i),t=t[t.length-1]}const s=t?j.trigger(t,"hide.bs.tab",{relatedTarget:this._element}):null;if(j.trigger(this._element,"show.bs.tab",{relatedTarget:t}).defaultPrevented||null!==s&&s.defaultPrevented)return;this._activate(this._element,i);const o=()=>{j.trigger(t,"hidden.bs.tab",{relatedTarget:this._element}),j.trigger(this._element,"shown.bs.tab",{relatedTarget:t})};e?this._activate(e,e.parentNode,o):o()}_activate(t,e,i){const n=(!e||"UL"!==e.nodeName&&"OL"!==e.nodeName?V.children(e,kn):V.find(Ln,e))[0],s=i&&n&&n.classList.contains(On),o=()=>this._transitionComplete(t,n,i);n&&s?(n.classList.remove(Cn),this._queueCallback(o,t,!0)):o()}_transitionComplete(t,e,i){if(e){e.classList.remove(Tn);const t=V.findOne(":scope > .dropdown-menu .active",e.parentNode);t&&t.classList.remove(Tn),"tab"===e.getAttribute("role")&&e.setAttribute("aria-selected",!1)}t.classList.add(Tn),"tab"===t.getAttribute("role")&&t.setAttribute("aria-selected",!0),u(t),t.classList.contains(On)&&t.classList.add(Cn);let n=t.parentNode;if(n&&"LI"===n.nodeName&&(n=n.parentNode),n&&n.classList.contains("dropdown-menu")){const e=t.closest(".dropdown");e&&V.find(".dropdown-toggle",e).forEach((t=>t.classList.add(Tn))),t.setAttribute("aria-expanded",!0)}i&&i()}static jQueryInterface(t){return this.each((function(){const e=xn.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}j.on(document,"click.bs.tab.data-api",'[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),c(this)||xn.getOrCreateInstance(this).show()})),g(xn);const Dn="toast",Sn="hide",Nn="show",In="showing",Pn={animation:"boolean",autohide:"boolean",delay:"number"},jn={animation:!0,autohide:!0,delay:5e3};class Mn extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get DefaultType(){return Pn}static get Default(){return jn}static get NAME(){return Dn}show(){j.trigger(this._element,"show.bs.toast").defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(Sn),u(this._element),this._element.classList.add(Nn),this._element.classList.add(In),this._queueCallback((()=>{this._element.classList.remove(In),j.trigger(this._element,"shown.bs.toast"),this._maybeScheduleHide()}),this._element,this._config.animation))}hide(){this._element.classList.contains(Nn)&&(j.trigger(this._element,"hide.bs.toast").defaultPrevented||(this._element.classList.add(In),this._queueCallback((()=>{this._element.classList.add(Sn),this._element.classList.remove(In),this._element.classList.remove(Nn),j.trigger(this._element,"hidden.bs.toast")}),this._element,this._config.animation)))}dispose(){this._clearTimeout(),this._element.classList.contains(Nn)&&this._element.classList.remove(Nn),super.dispose()}_getConfig(t){return t={...jn,...U.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}},a(Dn,t,this.constructor.DefaultType),t}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout((()=>{this.hide()}),this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){j.on(this._element,"mouseover.bs.toast",(t=>this._onInteraction(t,!0))),j.on(this._element,"mouseout.bs.toast",(t=>this._onInteraction(t,!1))),j.on(this._element,"focusin.bs.toast",(t=>this._onInteraction(t,!0))),j.on(this._element,"focusout.bs.toast",(t=>this._onInteraction(t,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=Mn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}return R(Mn),g(Mn),{Alert:W,Button:z,Carousel:st,Collapse:pt,Dropdown:hi,Modal:Hi,Offcanvas:Fi,Popover:gn,ScrollSpy:An,Tab:xn,Toast:Mn,Tooltip:un}})); +//# sourceMappingURL=bootstrap.bundle.min.js.map \ No newline at end of file diff --git a/doc/ch02-GettingStarted_files/libs/clipboard/clipboard.min.js b/doc/ch02-GettingStarted_files/libs/clipboard/clipboard.min.js new file mode 100644 index 000000000..41c6a0f7b --- /dev/null +++ b/doc/ch02-GettingStarted_files/libs/clipboard/clipboard.min.js @@ -0,0 +1,7 @@ +/*! + * clipboard.js v2.0.10 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return o}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),c=n.n(e);function a(t){try{return document.execCommand(t)}catch(t){return}}var f=function(t){t=c()(t);return a("cut"),t};var l=function(t){var e,n,o,r=1.anchorjs-link,.anchorjs-link:focus{opacity:1}",u.sheet.cssRules.length),u.sheet.insertRule("[data-anchorjs-icon]::after{content:attr(data-anchorjs-icon)}",u.sheet.cssRules.length),u.sheet.insertRule('@font-face{font-family:anchorjs-icons;src:url(data:n/a;base64,AAEAAAALAIAAAwAwT1MvMg8yG2cAAAE4AAAAYGNtYXDp3gC3AAABpAAAAExnYXNwAAAAEAAAA9wAAAAIZ2x5ZlQCcfwAAAH4AAABCGhlYWQHFvHyAAAAvAAAADZoaGVhBnACFwAAAPQAAAAkaG10eASAADEAAAGYAAAADGxvY2EACACEAAAB8AAAAAhtYXhwAAYAVwAAARgAAAAgbmFtZQGOH9cAAAMAAAAAunBvc3QAAwAAAAADvAAAACAAAQAAAAEAAHzE2p9fDzz1AAkEAAAAAADRecUWAAAAANQA6R8AAAAAAoACwAAAAAgAAgAAAAAAAAABAAADwP/AAAACgAAA/9MCrQABAAAAAAAAAAAAAAAAAAAAAwABAAAAAwBVAAIAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAMCQAGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAg//0DwP/AAEADwABAAAAAAQAAAAAAAAAAAAAAIAAAAAAAAAIAAAACgAAxAAAAAwAAAAMAAAAcAAEAAwAAABwAAwABAAAAHAAEADAAAAAIAAgAAgAAACDpy//9//8AAAAg6cv//f///+EWNwADAAEAAAAAAAAAAAAAAAAACACEAAEAAAAAAAAAAAAAAAAxAAACAAQARAKAAsAAKwBUAAABIiYnJjQ3NzY2MzIWFxYUBwcGIicmNDc3NjQnJiYjIgYHBwYUFxYUBwYGIwciJicmNDc3NjIXFhQHBwYUFxYWMzI2Nzc2NCcmNDc2MhcWFAcHBgYjARQGDAUtLXoWOR8fORYtLTgKGwoKCjgaGg0gEhIgDXoaGgkJBQwHdR85Fi0tOAobCgoKOBoaDSASEiANehoaCQkKGwotLXoWOR8BMwUFLYEuehYXFxYugC44CQkKGwo4GkoaDQ0NDXoaShoKGwoFBe8XFi6ALjgJCQobCjgaShoNDQ0NehpKGgobCgoKLYEuehYXAAAADACWAAEAAAAAAAEACAAAAAEAAAAAAAIAAwAIAAEAAAAAAAMACAAAAAEAAAAAAAQACAAAAAEAAAAAAAUAAQALAAEAAAAAAAYACAAAAAMAAQQJAAEAEAAMAAMAAQQJAAIABgAcAAMAAQQJAAMAEAAMAAMAAQQJAAQAEAAMAAMAAQQJAAUAAgAiAAMAAQQJAAYAEAAMYW5jaG9yanM0MDBAAGEAbgBjAGgAbwByAGoAcwA0ADAAMABAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAH//wAP) format("truetype")}',u.sheet.cssRules.length)),u=document.querySelectorAll("[id]"),t=[].map.call(u,function(A){return A.id}),i=0;i\]./()*\\\n\t\b\v\u00A0]/g,"-").replace(/-{2,}/g,"-").substring(0,this.options.truncate).replace(/^-+|-+$/gm,"").toLowerCase()},this.hasAnchorJSLink=function(A){var e=A.firstChild&&-1<(" "+A.firstChild.className+" ").indexOf(" anchorjs-link "),A=A.lastChild&&-1<(" "+A.lastChild.className+" ").indexOf(" anchorjs-link ");return e||A||!1}}}); +// @license-end \ No newline at end of file diff --git a/doc/ch02-GettingStarted_files/libs/quarto-html/popper.min.js b/doc/ch02-GettingStarted_files/libs/quarto-html/popper.min.js new file mode 100644 index 000000000..2269d6696 --- /dev/null +++ b/doc/ch02-GettingStarted_files/libs/quarto-html/popper.min.js @@ -0,0 +1,6 @@ +/** + * @popperjs/core v2.11.4 - MIT License + */ + +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Popper={})}(this,(function(e){"use strict";function t(e){if(null==e)return window;if("[object Window]"!==e.toString()){var t=e.ownerDocument;return t&&t.defaultView||window}return e}function n(e){return e instanceof t(e).Element||e instanceof Element}function r(e){return e instanceof t(e).HTMLElement||e instanceof HTMLElement}function o(e){return"undefined"!=typeof ShadowRoot&&(e instanceof t(e).ShadowRoot||e instanceof ShadowRoot)}var i=Math.max,a=Math.min,s=Math.round;function f(e,t){void 0===t&&(t=!1);var n=e.getBoundingClientRect(),o=1,i=1;if(r(e)&&t){var a=e.offsetHeight,f=e.offsetWidth;f>0&&(o=s(n.width)/f||1),a>0&&(i=s(n.height)/a||1)}return{width:n.width/o,height:n.height/i,top:n.top/i,right:n.right/o,bottom:n.bottom/i,left:n.left/o,x:n.left/o,y:n.top/i}}function c(e){var n=t(e);return{scrollLeft:n.pageXOffset,scrollTop:n.pageYOffset}}function p(e){return e?(e.nodeName||"").toLowerCase():null}function u(e){return((n(e)?e.ownerDocument:e.document)||window.document).documentElement}function l(e){return f(u(e)).left+c(e).scrollLeft}function d(e){return t(e).getComputedStyle(e)}function h(e){var t=d(e),n=t.overflow,r=t.overflowX,o=t.overflowY;return/auto|scroll|overlay|hidden/.test(n+o+r)}function m(e,n,o){void 0===o&&(o=!1);var i,a,d=r(n),m=r(n)&&function(e){var t=e.getBoundingClientRect(),n=s(t.width)/e.offsetWidth||1,r=s(t.height)/e.offsetHeight||1;return 1!==n||1!==r}(n),v=u(n),g=f(e,m),y={scrollLeft:0,scrollTop:0},b={x:0,y:0};return(d||!d&&!o)&&(("body"!==p(n)||h(v))&&(y=(i=n)!==t(i)&&r(i)?{scrollLeft:(a=i).scrollLeft,scrollTop:a.scrollTop}:c(i)),r(n)?((b=f(n,!0)).x+=n.clientLeft,b.y+=n.clientTop):v&&(b.x=l(v))),{x:g.left+y.scrollLeft-b.x,y:g.top+y.scrollTop-b.y,width:g.width,height:g.height}}function v(e){var t=f(e),n=e.offsetWidth,r=e.offsetHeight;return Math.abs(t.width-n)<=1&&(n=t.width),Math.abs(t.height-r)<=1&&(r=t.height),{x:e.offsetLeft,y:e.offsetTop,width:n,height:r}}function g(e){return"html"===p(e)?e:e.assignedSlot||e.parentNode||(o(e)?e.host:null)||u(e)}function y(e){return["html","body","#document"].indexOf(p(e))>=0?e.ownerDocument.body:r(e)&&h(e)?e:y(g(e))}function b(e,n){var r;void 0===n&&(n=[]);var o=y(e),i=o===(null==(r=e.ownerDocument)?void 0:r.body),a=t(o),s=i?[a].concat(a.visualViewport||[],h(o)?o:[]):o,f=n.concat(s);return i?f:f.concat(b(g(s)))}function x(e){return["table","td","th"].indexOf(p(e))>=0}function w(e){return r(e)&&"fixed"!==d(e).position?e.offsetParent:null}function O(e){for(var n=t(e),i=w(e);i&&x(i)&&"static"===d(i).position;)i=w(i);return i&&("html"===p(i)||"body"===p(i)&&"static"===d(i).position)?n:i||function(e){var t=-1!==navigator.userAgent.toLowerCase().indexOf("firefox");if(-1!==navigator.userAgent.indexOf("Trident")&&r(e)&&"fixed"===d(e).position)return null;var n=g(e);for(o(n)&&(n=n.host);r(n)&&["html","body"].indexOf(p(n))<0;){var i=d(n);if("none"!==i.transform||"none"!==i.perspective||"paint"===i.contain||-1!==["transform","perspective"].indexOf(i.willChange)||t&&"filter"===i.willChange||t&&i.filter&&"none"!==i.filter)return n;n=n.parentNode}return null}(e)||n}var j="top",E="bottom",D="right",A="left",L="auto",P=[j,E,D,A],M="start",k="end",W="viewport",B="popper",H=P.reduce((function(e,t){return e.concat([t+"-"+M,t+"-"+k])}),[]),T=[].concat(P,[L]).reduce((function(e,t){return e.concat([t,t+"-"+M,t+"-"+k])}),[]),R=["beforeRead","read","afterRead","beforeMain","main","afterMain","beforeWrite","write","afterWrite"];function S(e){var t=new Map,n=new Set,r=[];function o(e){n.add(e.name),[].concat(e.requires||[],e.requiresIfExists||[]).forEach((function(e){if(!n.has(e)){var r=t.get(e);r&&o(r)}})),r.push(e)}return e.forEach((function(e){t.set(e.name,e)})),e.forEach((function(e){n.has(e.name)||o(e)})),r}function C(e){return e.split("-")[0]}function q(e,t){var n=t.getRootNode&&t.getRootNode();if(e.contains(t))return!0;if(n&&o(n)){var r=t;do{if(r&&e.isSameNode(r))return!0;r=r.parentNode||r.host}while(r)}return!1}function V(e){return Object.assign({},e,{left:e.x,top:e.y,right:e.x+e.width,bottom:e.y+e.height})}function N(e,r){return r===W?V(function(e){var n=t(e),r=u(e),o=n.visualViewport,i=r.clientWidth,a=r.clientHeight,s=0,f=0;return o&&(i=o.width,a=o.height,/^((?!chrome|android).)*safari/i.test(navigator.userAgent)||(s=o.offsetLeft,f=o.offsetTop)),{width:i,height:a,x:s+l(e),y:f}}(e)):n(r)?function(e){var t=f(e);return t.top=t.top+e.clientTop,t.left=t.left+e.clientLeft,t.bottom=t.top+e.clientHeight,t.right=t.left+e.clientWidth,t.width=e.clientWidth,t.height=e.clientHeight,t.x=t.left,t.y=t.top,t}(r):V(function(e){var t,n=u(e),r=c(e),o=null==(t=e.ownerDocument)?void 0:t.body,a=i(n.scrollWidth,n.clientWidth,o?o.scrollWidth:0,o?o.clientWidth:0),s=i(n.scrollHeight,n.clientHeight,o?o.scrollHeight:0,o?o.clientHeight:0),f=-r.scrollLeft+l(e),p=-r.scrollTop;return"rtl"===d(o||n).direction&&(f+=i(n.clientWidth,o?o.clientWidth:0)-a),{width:a,height:s,x:f,y:p}}(u(e)))}function I(e,t,o){var s="clippingParents"===t?function(e){var t=b(g(e)),o=["absolute","fixed"].indexOf(d(e).position)>=0&&r(e)?O(e):e;return n(o)?t.filter((function(e){return n(e)&&q(e,o)&&"body"!==p(e)})):[]}(e):[].concat(t),f=[].concat(s,[o]),c=f[0],u=f.reduce((function(t,n){var r=N(e,n);return t.top=i(r.top,t.top),t.right=a(r.right,t.right),t.bottom=a(r.bottom,t.bottom),t.left=i(r.left,t.left),t}),N(e,c));return u.width=u.right-u.left,u.height=u.bottom-u.top,u.x=u.left,u.y=u.top,u}function _(e){return e.split("-")[1]}function F(e){return["top","bottom"].indexOf(e)>=0?"x":"y"}function U(e){var t,n=e.reference,r=e.element,o=e.placement,i=o?C(o):null,a=o?_(o):null,s=n.x+n.width/2-r.width/2,f=n.y+n.height/2-r.height/2;switch(i){case j:t={x:s,y:n.y-r.height};break;case E:t={x:s,y:n.y+n.height};break;case D:t={x:n.x+n.width,y:f};break;case A:t={x:n.x-r.width,y:f};break;default:t={x:n.x,y:n.y}}var c=i?F(i):null;if(null!=c){var p="y"===c?"height":"width";switch(a){case M:t[c]=t[c]-(n[p]/2-r[p]/2);break;case k:t[c]=t[c]+(n[p]/2-r[p]/2)}}return t}function z(e){return Object.assign({},{top:0,right:0,bottom:0,left:0},e)}function X(e,t){return t.reduce((function(t,n){return t[n]=e,t}),{})}function Y(e,t){void 0===t&&(t={});var r=t,o=r.placement,i=void 0===o?e.placement:o,a=r.boundary,s=void 0===a?"clippingParents":a,c=r.rootBoundary,p=void 0===c?W:c,l=r.elementContext,d=void 0===l?B:l,h=r.altBoundary,m=void 0!==h&&h,v=r.padding,g=void 0===v?0:v,y=z("number"!=typeof g?g:X(g,P)),b=d===B?"reference":B,x=e.rects.popper,w=e.elements[m?b:d],O=I(n(w)?w:w.contextElement||u(e.elements.popper),s,p),A=f(e.elements.reference),L=U({reference:A,element:x,strategy:"absolute",placement:i}),M=V(Object.assign({},x,L)),k=d===B?M:A,H={top:O.top-k.top+y.top,bottom:k.bottom-O.bottom+y.bottom,left:O.left-k.left+y.left,right:k.right-O.right+y.right},T=e.modifiersData.offset;if(d===B&&T){var R=T[i];Object.keys(H).forEach((function(e){var t=[D,E].indexOf(e)>=0?1:-1,n=[j,E].indexOf(e)>=0?"y":"x";H[e]+=R[n]*t}))}return H}var G={placement:"bottom",modifiers:[],strategy:"absolute"};function J(){for(var e=arguments.length,t=new Array(e),n=0;n=0?-1:1,i="function"==typeof n?n(Object.assign({},t,{placement:e})):n,a=i[0],s=i[1];return a=a||0,s=(s||0)*o,[A,D].indexOf(r)>=0?{x:s,y:a}:{x:a,y:s}}(n,t.rects,i),e}),{}),s=a[t.placement],f=s.x,c=s.y;null!=t.modifiersData.popperOffsets&&(t.modifiersData.popperOffsets.x+=f,t.modifiersData.popperOffsets.y+=c),t.modifiersData[r]=a}},ie={left:"right",right:"left",bottom:"top",top:"bottom"};function ae(e){return e.replace(/left|right|bottom|top/g,(function(e){return ie[e]}))}var se={start:"end",end:"start"};function fe(e){return e.replace(/start|end/g,(function(e){return se[e]}))}function ce(e,t){void 0===t&&(t={});var n=t,r=n.placement,o=n.boundary,i=n.rootBoundary,a=n.padding,s=n.flipVariations,f=n.allowedAutoPlacements,c=void 0===f?T:f,p=_(r),u=p?s?H:H.filter((function(e){return _(e)===p})):P,l=u.filter((function(e){return c.indexOf(e)>=0}));0===l.length&&(l=u);var d=l.reduce((function(t,n){return t[n]=Y(e,{placement:n,boundary:o,rootBoundary:i,padding:a})[C(n)],t}),{});return Object.keys(d).sort((function(e,t){return d[e]-d[t]}))}var pe={name:"flip",enabled:!0,phase:"main",fn:function(e){var t=e.state,n=e.options,r=e.name;if(!t.modifiersData[r]._skip){for(var o=n.mainAxis,i=void 0===o||o,a=n.altAxis,s=void 0===a||a,f=n.fallbackPlacements,c=n.padding,p=n.boundary,u=n.rootBoundary,l=n.altBoundary,d=n.flipVariations,h=void 0===d||d,m=n.allowedAutoPlacements,v=t.options.placement,g=C(v),y=f||(g===v||!h?[ae(v)]:function(e){if(C(e)===L)return[];var t=ae(e);return[fe(e),t,fe(t)]}(v)),b=[v].concat(y).reduce((function(e,n){return e.concat(C(n)===L?ce(t,{placement:n,boundary:p,rootBoundary:u,padding:c,flipVariations:h,allowedAutoPlacements:m}):n)}),[]),x=t.rects.reference,w=t.rects.popper,O=new Map,P=!0,k=b[0],W=0;W=0,S=R?"width":"height",q=Y(t,{placement:B,boundary:p,rootBoundary:u,altBoundary:l,padding:c}),V=R?T?D:A:T?E:j;x[S]>w[S]&&(V=ae(V));var N=ae(V),I=[];if(i&&I.push(q[H]<=0),s&&I.push(q[V]<=0,q[N]<=0),I.every((function(e){return e}))){k=B,P=!1;break}O.set(B,I)}if(P)for(var F=function(e){var t=b.find((function(t){var n=O.get(t);if(n)return n.slice(0,e).every((function(e){return e}))}));if(t)return k=t,"break"},U=h?3:1;U>0;U--){if("break"===F(U))break}t.placement!==k&&(t.modifiersData[r]._skip=!0,t.placement=k,t.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function ue(e,t,n){return i(e,a(t,n))}var le={name:"preventOverflow",enabled:!0,phase:"main",fn:function(e){var t=e.state,n=e.options,r=e.name,o=n.mainAxis,s=void 0===o||o,f=n.altAxis,c=void 0!==f&&f,p=n.boundary,u=n.rootBoundary,l=n.altBoundary,d=n.padding,h=n.tether,m=void 0===h||h,g=n.tetherOffset,y=void 0===g?0:g,b=Y(t,{boundary:p,rootBoundary:u,padding:d,altBoundary:l}),x=C(t.placement),w=_(t.placement),L=!w,P=F(x),k="x"===P?"y":"x",W=t.modifiersData.popperOffsets,B=t.rects.reference,H=t.rects.popper,T="function"==typeof y?y(Object.assign({},t.rects,{placement:t.placement})):y,R="number"==typeof T?{mainAxis:T,altAxis:T}:Object.assign({mainAxis:0,altAxis:0},T),S=t.modifiersData.offset?t.modifiersData.offset[t.placement]:null,q={x:0,y:0};if(W){if(s){var V,N="y"===P?j:A,I="y"===P?E:D,U="y"===P?"height":"width",z=W[P],X=z+b[N],G=z-b[I],J=m?-H[U]/2:0,K=w===M?B[U]:H[U],Q=w===M?-H[U]:-B[U],Z=t.elements.arrow,$=m&&Z?v(Z):{width:0,height:0},ee=t.modifiersData["arrow#persistent"]?t.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},te=ee[N],ne=ee[I],re=ue(0,B[U],$[U]),oe=L?B[U]/2-J-re-te-R.mainAxis:K-re-te-R.mainAxis,ie=L?-B[U]/2+J+re+ne+R.mainAxis:Q+re+ne+R.mainAxis,ae=t.elements.arrow&&O(t.elements.arrow),se=ae?"y"===P?ae.clientTop||0:ae.clientLeft||0:0,fe=null!=(V=null==S?void 0:S[P])?V:0,ce=z+ie-fe,pe=ue(m?a(X,z+oe-fe-se):X,z,m?i(G,ce):G);W[P]=pe,q[P]=pe-z}if(c){var le,de="x"===P?j:A,he="x"===P?E:D,me=W[k],ve="y"===k?"height":"width",ge=me+b[de],ye=me-b[he],be=-1!==[j,A].indexOf(x),xe=null!=(le=null==S?void 0:S[k])?le:0,we=be?ge:me-B[ve]-H[ve]-xe+R.altAxis,Oe=be?me+B[ve]+H[ve]-xe-R.altAxis:ye,je=m&&be?function(e,t,n){var r=ue(e,t,n);return r>n?n:r}(we,me,Oe):ue(m?we:ge,me,m?Oe:ye);W[k]=je,q[k]=je-me}t.modifiersData[r]=q}},requiresIfExists:["offset"]};var de={name:"arrow",enabled:!0,phase:"main",fn:function(e){var t,n=e.state,r=e.name,o=e.options,i=n.elements.arrow,a=n.modifiersData.popperOffsets,s=C(n.placement),f=F(s),c=[A,D].indexOf(s)>=0?"height":"width";if(i&&a){var p=function(e,t){return z("number"!=typeof(e="function"==typeof e?e(Object.assign({},t.rects,{placement:t.placement})):e)?e:X(e,P))}(o.padding,n),u=v(i),l="y"===f?j:A,d="y"===f?E:D,h=n.rects.reference[c]+n.rects.reference[f]-a[f]-n.rects.popper[c],m=a[f]-n.rects.reference[f],g=O(i),y=g?"y"===f?g.clientHeight||0:g.clientWidth||0:0,b=h/2-m/2,x=p[l],w=y-u[c]-p[d],L=y/2-u[c]/2+b,M=ue(x,L,w),k=f;n.modifiersData[r]=((t={})[k]=M,t.centerOffset=M-L,t)}},effect:function(e){var t=e.state,n=e.options.element,r=void 0===n?"[data-popper-arrow]":n;null!=r&&("string"!=typeof r||(r=t.elements.popper.querySelector(r)))&&q(t.elements.popper,r)&&(t.elements.arrow=r)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function he(e,t,n){return void 0===n&&(n={x:0,y:0}),{top:e.top-t.height-n.y,right:e.right-t.width+n.x,bottom:e.bottom-t.height+n.y,left:e.left-t.width-n.x}}function me(e){return[j,D,E,A].some((function(t){return e[t]>=0}))}var ve={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(e){var t=e.state,n=e.name,r=t.rects.reference,o=t.rects.popper,i=t.modifiersData.preventOverflow,a=Y(t,{elementContext:"reference"}),s=Y(t,{altBoundary:!0}),f=he(a,r),c=he(s,o,i),p=me(f),u=me(c);t.modifiersData[n]={referenceClippingOffsets:f,popperEscapeOffsets:c,isReferenceHidden:p,hasPopperEscaped:u},t.attributes.popper=Object.assign({},t.attributes.popper,{"data-popper-reference-hidden":p,"data-popper-escaped":u})}},ge=K({defaultModifiers:[Z,$,ne,re]}),ye=[Z,$,ne,re,oe,pe,le,de,ve],be=K({defaultModifiers:ye});e.applyStyles=re,e.arrow=de,e.computeStyles=ne,e.createPopper=be,e.createPopperLite=ge,e.defaultModifiers=ye,e.detectOverflow=Y,e.eventListeners=Z,e.flip=pe,e.hide=ve,e.offset=oe,e.popperGenerator=K,e.popperOffsets=$,e.preventOverflow=le,Object.defineProperty(e,"__esModule",{value:!0})})); + diff --git a/doc/ch02-GettingStarted_files/libs/quarto-html/quarto-syntax-highlighting.css b/doc/ch02-GettingStarted_files/libs/quarto-html/quarto-syntax-highlighting.css new file mode 100644 index 000000000..36cb3287b --- /dev/null +++ b/doc/ch02-GettingStarted_files/libs/quarto-html/quarto-syntax-highlighting.css @@ -0,0 +1,171 @@ +/* quarto syntax highlight colors */ +:root { + --quarto-hl-ot-color: #003B4F; + --quarto-hl-at-color: #657422; + --quarto-hl-ss-color: #20794D; + --quarto-hl-an-color: #5E5E5E; + --quarto-hl-fu-color: #4758AB; + --quarto-hl-st-color: #20794D; + --quarto-hl-cf-color: #003B4F; + --quarto-hl-op-color: #5E5E5E; + --quarto-hl-er-color: #AD0000; + --quarto-hl-bn-color: #AD0000; + --quarto-hl-al-color: #AD0000; + --quarto-hl-va-color: #111111; + --quarto-hl-bu-color: inherit; + --quarto-hl-ex-color: inherit; + --quarto-hl-pp-color: #AD0000; + --quarto-hl-in-color: #5E5E5E; + --quarto-hl-vs-color: #20794D; + --quarto-hl-wa-color: #5E5E5E; + --quarto-hl-do-color: #5E5E5E; + --quarto-hl-im-color: #00769E; + --quarto-hl-ch-color: #20794D; + --quarto-hl-dt-color: #AD0000; + --quarto-hl-fl-color: #AD0000; + --quarto-hl-co-color: #5E5E5E; + --quarto-hl-cv-color: #5E5E5E; + --quarto-hl-cn-color: #8f5902; + --quarto-hl-sc-color: #5E5E5E; + --quarto-hl-dv-color: #AD0000; + --quarto-hl-kw-color: #003B4F; +} + +/* other quarto variables */ +:root { + --quarto-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; +} + +pre > code.sourceCode > span { + color: #003B4F; +} + +code span { + color: #003B4F; +} + +code.sourceCode > span { + color: #003B4F; +} + +div.sourceCode, +div.sourceCode pre.sourceCode { + color: #003B4F; +} + +code span.ot { + color: #003B4F; +} + +code span.at { + color: #657422; +} + +code span.ss { + color: #20794D; +} + +code span.an { + color: #5E5E5E; +} + +code span.fu { + color: #4758AB; +} + +code span.st { + color: #20794D; +} + +code span.cf { + color: #003B4F; +} + +code span.op { + color: #5E5E5E; +} + +code span.er { + color: #AD0000; +} + +code span.bn { + color: #AD0000; +} + +code span.al { + color: #AD0000; +} + +code span.va { + color: #111111; +} + +code span.pp { + color: #AD0000; +} + +code span.in { + color: #5E5E5E; +} + +code span.vs { + color: #20794D; +} + +code span.wa { + color: #5E5E5E; + font-style: italic; +} + +code span.do { + color: #5E5E5E; + font-style: italic; +} + +code span.im { + color: #00769E; +} + +code span.ch { + color: #20794D; +} + +code span.dt { + color: #AD0000; +} + +code span.fl { + color: #AD0000; +} + +code span.co { + color: #5E5E5E; +} + +code span.cv { + color: #5E5E5E; + font-style: italic; +} + +code span.cn { + color: #8f5902; +} + +code span.sc { + color: #5E5E5E; +} + +code span.dv { + color: #AD0000; +} + +code span.kw { + color: #003B4F; +} + +.prevent-inlining { + content: " { + const sibling = el.previousElementSibling; + if (sibling && sibling.tagName === "A") { + return sibling.classList.contains("active"); + } else { + return false; + } + }; + + // fire slideEnter for bootstrap tab activations (for htmlwidget resize behavior) + function fireSlideEnter(e) { + const event = window.document.createEvent("Event"); + event.initEvent("slideenter", true, true); + window.document.dispatchEvent(event); + } + const tabs = window.document.querySelectorAll('a[data-bs-toggle="tab"]'); + tabs.forEach((tab) => { + tab.addEventListener("shown.bs.tab", fireSlideEnter); + }); + + // fire slideEnter for tabby tab activations (for htmlwidget resize behavior) + document.addEventListener("tabby", fireSlideEnter, false); + + // Track scrolling and mark TOC links as active + // get table of contents and sidebar (bail if we don't have at least one) + const tocLinks = tocEl + ? [...tocEl.querySelectorAll("a[data-scroll-target]")] + : []; + const makeActive = (link) => tocLinks[link].classList.add("active"); + const removeActive = (link) => tocLinks[link].classList.remove("active"); + const removeAllActive = () => + [...Array(tocLinks.length).keys()].forEach((link) => removeActive(link)); + + // activate the anchor for a section associated with this TOC entry + tocLinks.forEach((link) => { + link.addEventListener("click", () => { + if (link.href.indexOf("#") !== -1) { + const anchor = link.href.split("#")[1]; + const heading = window.document.querySelector( + `[data-anchor-id=${anchor}]` + ); + if (heading) { + // Add the class + heading.classList.add("reveal-anchorjs-link"); + + // function to show the anchor + const handleMouseout = () => { + heading.classList.remove("reveal-anchorjs-link"); + heading.removeEventListener("mouseout", handleMouseout); + }; + + // add a function to clear the anchor when the user mouses out of it + heading.addEventListener("mouseout", handleMouseout); + } + } + }); + }); + + const sections = tocLinks.map((link) => { + const target = link.getAttribute("data-scroll-target"); + if (target.startsWith("#")) { + return window.document.getElementById(decodeURI(`${target.slice(1)}`)); + } else { + return window.document.querySelector(decodeURI(`${target}`)); + } + }); + + const sectionMargin = 200; + let currentActive = 0; + // track whether we've initialized state the first time + let init = false; + + const updateActiveLink = () => { + // The index from bottom to top (e.g. reversed list) + let sectionIndex = -1; + if ( + window.innerHeight + window.pageYOffset >= + window.document.body.offsetHeight + ) { + sectionIndex = 0; + } else { + sectionIndex = [...sections].reverse().findIndex((section) => { + if (section) { + return window.pageYOffset >= section.offsetTop - sectionMargin; + } else { + return false; + } + }); + } + if (sectionIndex > -1) { + const current = sections.length - sectionIndex - 1; + if (current !== currentActive) { + removeAllActive(); + currentActive = current; + makeActive(current); + if (init) { + window.dispatchEvent(sectionChanged); + } + init = true; + } + } + }; + + const inHiddenRegion = (top, bottom, hiddenRegions) => { + for (const region of hiddenRegions) { + if (top <= region.bottom && bottom >= region.top) { + return true; + } + } + return false; + }; + + const categorySelector = "header.quarto-title-block .quarto-category"; + const activateCategories = (href) => { + // Find any categories + // Surround them with a link pointing back to: + // #category=Authoring + try { + const categoryEls = window.document.querySelectorAll(categorySelector); + for (const categoryEl of categoryEls) { + const categoryText = categoryEl.textContent; + if (categoryText) { + const link = `${href}#category=${encodeURIComponent(categoryText)}`; + const linkEl = window.document.createElement("a"); + linkEl.setAttribute("href", link); + for (const child of categoryEl.childNodes) { + linkEl.append(child); + } + categoryEl.appendChild(linkEl); + } + } + } catch { + // Ignore errors + } + }; + function hasTitleCategories() { + return window.document.querySelector(categorySelector) !== null; + } + + function offsetRelativeUrl(url) { + const offset = getMeta("quarto:offset"); + return offset ? offset + url : url; + } + + function offsetAbsoluteUrl(url) { + const offset = getMeta("quarto:offset"); + const baseUrl = new URL(offset, window.location); + + const projRelativeUrl = url.replace(baseUrl, ""); + if (projRelativeUrl.startsWith("/")) { + return projRelativeUrl; + } else { + return "/" + projRelativeUrl; + } + } + + // read a meta tag value + function getMeta(metaName) { + const metas = window.document.getElementsByTagName("meta"); + for (let i = 0; i < metas.length; i++) { + if (metas[i].getAttribute("name") === metaName) { + return metas[i].getAttribute("content"); + } + } + return ""; + } + + async function findAndActivateCategories() { + const currentPagePath = offsetAbsoluteUrl(window.location.href); + const response = await fetch(offsetRelativeUrl("listings.json")); + if (response.status == 200) { + return response.json().then(function (listingPaths) { + const listingHrefs = []; + for (const listingPath of listingPaths) { + const pathWithoutLeadingSlash = listingPath.listing.substring(1); + for (const item of listingPath.items) { + if ( + item === currentPagePath || + item === currentPagePath + "index.html" + ) { + // Resolve this path against the offset to be sure + // we already are using the correct path to the listing + // (this adjusts the listing urls to be rooted against + // whatever root the page is actually running against) + const relative = offsetRelativeUrl(pathWithoutLeadingSlash); + const baseUrl = window.location; + const resolvedPath = new URL(relative, baseUrl); + listingHrefs.push(resolvedPath.pathname); + break; + } + } + } + + // Look up the tree for a nearby linting and use that if we find one + const nearestListing = findNearestParentListing( + offsetAbsoluteUrl(window.location.pathname), + listingHrefs + ); + if (nearestListing) { + activateCategories(nearestListing); + } else { + // See if the referrer is a listing page for this item + const referredRelativePath = offsetAbsoluteUrl(document.referrer); + const referrerListing = listingHrefs.find((listingHref) => { + const isListingReferrer = + listingHref === referredRelativePath || + listingHref === referredRelativePath + "index.html"; + return isListingReferrer; + }); + + if (referrerListing) { + // Try to use the referrer if possible + activateCategories(referrerListing); + } else if (listingHrefs.length > 0) { + // Otherwise, just fall back to the first listing + activateCategories(listingHrefs[0]); + } + } + }); + } + } + if (hasTitleCategories()) { + findAndActivateCategories(); + } + + const findNearestParentListing = (href, listingHrefs) => { + if (!href || !listingHrefs) { + return undefined; + } + // Look up the tree for a nearby linting and use that if we find one + const relativeParts = href.substring(1).split("/"); + while (relativeParts.length > 0) { + const path = relativeParts.join("/"); + for (const listingHref of listingHrefs) { + if (listingHref.startsWith(path)) { + return listingHref; + } + } + relativeParts.pop(); + } + + return undefined; + }; + + const manageSidebarVisiblity = (el, placeholderDescriptor) => { + let isVisible = true; + + return (hiddenRegions) => { + if (el === null) { + return; + } + + // Find the last element of the TOC + const lastChildEl = el.lastElementChild; + + if (lastChildEl) { + // Find the top and bottom o the element that is being managed + const elTop = el.offsetTop; + const elBottom = + elTop + lastChildEl.offsetTop + lastChildEl.offsetHeight; + + // Converts the sidebar to a menu + const convertToMenu = () => { + for (const child of el.children) { + child.style.opacity = 0; + child.style.overflow = "hidden"; + } + + const toggleContainer = window.document.createElement("div"); + toggleContainer.style.width = "100%"; + toggleContainer.classList.add("zindex-over-content"); + toggleContainer.classList.add("quarto-sidebar-toggle"); + toggleContainer.classList.add("headroom-target"); // Marks this to be managed by headeroom + toggleContainer.id = placeholderDescriptor.id; + toggleContainer.style.position = "fixed"; + + const toggleIcon = window.document.createElement("i"); + toggleIcon.classList.add("quarto-sidebar-toggle-icon"); + toggleIcon.classList.add("bi"); + toggleIcon.classList.add("bi-caret-down-fill"); + + const toggleTitle = window.document.createElement("div"); + const titleEl = window.document.body.querySelector( + placeholderDescriptor.titleSelector + ); + if (titleEl) { + toggleTitle.append(titleEl.innerText, toggleIcon); + } + toggleTitle.classList.add("zindex-over-content"); + toggleTitle.classList.add("quarto-sidebar-toggle-title"); + toggleContainer.append(toggleTitle); + + const toggleContents = window.document.createElement("div"); + toggleContents.classList = el.classList; + toggleContents.classList.add("zindex-over-content"); + toggleContents.classList.add("quarto-sidebar-toggle-contents"); + for (const child of el.children) { + if (child.id === "toc-title") { + continue; + } + + const clone = child.cloneNode(true); + clone.style.opacity = 1; + clone.style.display = null; + toggleContents.append(clone); + } + toggleContents.style.height = "0px"; + toggleContainer.append(toggleContents); + el.parentElement.prepend(toggleContainer); + + // Process clicks + let tocShowing = false; + // Allow the caller to control whether this is dismissed + // when it is clicked (e.g. sidebar navigation supports + // opening and closing the nav tree, so don't dismiss on click) + const clickEl = placeholderDescriptor.dismissOnClick + ? toggleContainer + : toggleTitle; + + const closeToggle = () => { + if (tocShowing) { + toggleContainer.classList.remove("expanded"); + toggleContents.style.height = "0px"; + tocShowing = false; + } + }; + + const positionToggle = () => { + // position the element (top left of parent, same width as parent) + const elRect = el.getBoundingClientRect(); + toggleContainer.style.left = `${elRect.left}px`; + toggleContainer.style.top = `${elRect.top}px`; + toggleContainer.style.width = `${elRect.width}px`; + }; + + // Get rid of any expanded toggle if the user scrolls + window.document.addEventListener( + "scroll", + throttle(() => { + closeToggle(); + }, 50) + ); + + // Handle positioning of the toggle + window.addEventListener( + "resize", + throttle(() => { + positionToggle(); + }, 50) + ); + positionToggle(); + + // Process the click + clickEl.onclick = () => { + if (!tocShowing) { + toggleContainer.classList.add("expanded"); + toggleContents.style.height = null; + tocShowing = true; + } else { + closeToggle(); + } + }; + }; + + // Converts a sidebar from a menu back to a sidebar + const convertToSidebar = () => { + for (const child of el.children) { + child.style.opacity = 1; + child.style.overflow = null; + } + + const placeholderEl = window.document.getElementById( + placeholderDescriptor.id + ); + if (placeholderEl) { + placeholderEl.remove(); + } + + el.classList.remove("rollup"); + }; + + if (isReaderMode()) { + convertToMenu(); + isVisible = false; + } else { + if (!isVisible) { + // If the element is current not visible reveal if there are + // no conflicts with overlay regions + if (!inHiddenRegion(elTop, elBottom, hiddenRegions)) { + convertToSidebar(); + isVisible = true; + } + } else { + // If the element is visible, hide it if it conflicts with overlay regions + // and insert a placeholder toggle (or if we're in reader mode) + if (inHiddenRegion(elTop, elBottom, hiddenRegions)) { + convertToMenu(); + isVisible = false; + } + } + } + } + }; + }; + + // Find any conflicting margin elements and add margins to the + // top to prevent overlap + const marginChildren = window.document.querySelectorAll( + ".column-margin.column-container > * " + ); + + nexttick(() => { + let lastBottom = 0; + for (const marginChild of marginChildren) { + const top = marginChild.getBoundingClientRect().top + window.scrollY; + if (top < lastBottom) { + const margin = lastBottom - top; + marginChild.style.marginTop = `${margin}px`; + } + const styles = window.getComputedStyle(marginChild); + const marginTop = parseFloat(styles["marginTop"]); + + lastBottom = top + marginChild.getBoundingClientRect().height + marginTop; + } + }); + + // Manage the visibility of the toc and the sidebar + const marginScrollVisibility = manageSidebarVisiblity(marginSidebarEl, { + id: "quarto-toc-toggle", + titleSelector: "#toc-title", + dismissOnClick: true, + }); + const sidebarScrollVisiblity = manageSidebarVisiblity(sidebarEl, { + id: "quarto-sidebarnav-toggle", + titleSelector: ".title", + dismissOnClick: false, + }); + let tocLeftScrollVisibility; + if (leftTocEl) { + tocLeftScrollVisibility = manageSidebarVisiblity(leftTocEl, { + id: "quarto-lefttoc-toggle", + titleSelector: "#toc-title", + dismissOnClick: true, + }); + } + + // Find the first element that uses formatting in special columns + const conflictingEls = window.document.body.querySelectorAll( + '[class^="column-"], [class*=" column-"], aside, [class*="margin-caption"], [class*=" margin-caption"], [class*="margin-ref"], [class*=" margin-ref"]' + ); + + // Filter all the possibly conflicting elements into ones + // the do conflict on the left or ride side + const arrConflictingEls = Array.from(conflictingEls); + const leftSideConflictEls = arrConflictingEls.filter((el) => { + if (el.tagName === "ASIDE") { + return false; + } + return Array.from(el.classList).find((className) => { + return ( + className !== "column-body" && + className.startsWith("column-") && + !className.endsWith("right") && + !className.endsWith("container") && + className !== "column-margin" + ); + }); + }); + const rightSideConflictEls = arrConflictingEls.filter((el) => { + if (el.tagName === "ASIDE") { + return true; + } + + const hasMarginCaption = Array.from(el.classList).find((className) => { + return className == "margin-caption"; + }); + if (hasMarginCaption) { + return true; + } + + return Array.from(el.classList).find((className) => { + return ( + className !== "column-body" && + !className.endsWith("container") && + className.startsWith("column-") && + !className.endsWith("left") + ); + }); + }); + + const kOverlapPaddingSize = 10; + function toRegions(els) { + return els.map((el) => { + const top = + el.getBoundingClientRect().top + + document.documentElement.scrollTop - + kOverlapPaddingSize; + return { + top, + bottom: top + el.scrollHeight + 2 * kOverlapPaddingSize, + }; + }); + } + + const hideOverlappedSidebars = () => { + marginScrollVisibility(toRegions(rightSideConflictEls)); + sidebarScrollVisiblity(toRegions(leftSideConflictEls)); + if (tocLeftScrollVisibility) { + tocLeftScrollVisibility(toRegions(leftSideConflictEls)); + } + }; + + window.quartoToggleReader = () => { + // Applies a slow class (or removes it) + // to update the transition speed + const slowTransition = (slow) => { + const manageTransition = (id, slow) => { + const el = document.getElementById(id); + if (el) { + if (slow) { + el.classList.add("slow"); + } else { + el.classList.remove("slow"); + } + } + }; + + manageTransition("TOC", slow); + manageTransition("quarto-sidebar", slow); + }; + + const readerMode = !isReaderMode(); + setReaderModeValue(readerMode); + + // If we're entering reader mode, slow the transition + if (readerMode) { + slowTransition(readerMode); + } + highlightReaderToggle(readerMode); + hideOverlappedSidebars(); + + // If we're exiting reader mode, restore the non-slow transition + if (!readerMode) { + slowTransition(!readerMode); + } + }; + + const highlightReaderToggle = (readerMode) => { + const els = document.querySelectorAll(".quarto-reader-toggle"); + if (els) { + els.forEach((el) => { + if (readerMode) { + el.classList.add("reader"); + } else { + el.classList.remove("reader"); + } + }); + } + }; + + const setReaderModeValue = (val) => { + if (window.location.protocol !== "file:") { + window.localStorage.setItem("quarto-reader-mode", val); + } else { + localReaderMode = val; + } + }; + + const isReaderMode = () => { + if (window.location.protocol !== "file:") { + return window.localStorage.getItem("quarto-reader-mode") === "true"; + } else { + return localReaderMode; + } + }; + let localReaderMode = null; + + // Walk the TOC and collapse/expand nodes + // Nodes are expanded if: + // - they are top level + // - they have children that are 'active' links + // - they are directly below an link that is 'active' + const walk = (el, depth) => { + // Tick depth when we enter a UL + if (el.tagName === "UL") { + depth = depth + 1; + } + + // It this is active link + let isActiveNode = false; + if (el.tagName === "A" && el.classList.contains("active")) { + isActiveNode = true; + } + + // See if there is an active child to this element + let hasActiveChild = false; + for (child of el.children) { + hasActiveChild = walk(child, depth) || hasActiveChild; + } + + // Process the collapse state if this is an UL + if (el.tagName === "UL") { + if (depth === 1 || hasActiveChild || prevSiblingIsActiveLink(el)) { + el.classList.remove("collapse"); + } else { + el.classList.add("collapse"); + } + + // untick depth when we leave a UL + depth = depth - 1; + } + return hasActiveChild || isActiveNode; + }; + + // walk the TOC and expand / collapse any items that should be shown + + if (tocEl) { + walk(tocEl, 0); + updateActiveLink(); + } + + // Throttle the scroll event and walk peridiocally + window.document.addEventListener( + "scroll", + throttle(() => { + if (tocEl) { + updateActiveLink(); + walk(tocEl, 0); + } + if (!isReaderMode()) { + hideOverlappedSidebars(); + } + }, 5) + ); + window.addEventListener( + "resize", + throttle(() => { + if (!isReaderMode()) { + hideOverlappedSidebars(); + } + }, 10) + ); + hideOverlappedSidebars(); + highlightReaderToggle(isReaderMode()); +}); + +// grouped tabsets +window.addEventListener("pageshow", (_event) => { + function getTabSettings() { + const data = localStorage.getItem("quarto-persistent-tabsets-data"); + if (!data) { + localStorage.setItem("quarto-persistent-tabsets-data", "{}"); + return {}; + } + if (data) { + return JSON.parse(data); + } + } + + function setTabSettings(data) { + localStorage.setItem( + "quarto-persistent-tabsets-data", + JSON.stringify(data) + ); + } + + function setTabState(groupName, groupValue) { + const data = getTabSettings(); + data[groupName] = groupValue; + setTabSettings(data); + } + + function toggleTab(tab, active) { + const tabPanelId = tab.getAttribute("aria-controls"); + const tabPanel = document.getElementById(tabPanelId); + if (active) { + tab.classList.add("active"); + tabPanel.classList.add("active"); + } else { + tab.classList.remove("active"); + tabPanel.classList.remove("active"); + } + } + + function toggleAll(selectedGroup, selectorsToSync) { + for (const [thisGroup, tabs] of Object.entries(selectorsToSync)) { + const active = selectedGroup === thisGroup; + for (const tab of tabs) { + toggleTab(tab, active); + } + } + } + + function findSelectorsToSyncByLanguage() { + const result = {}; + const tabs = Array.from( + document.querySelectorAll(`div[data-group] a[id^='tabset-']`) + ); + for (const item of tabs) { + const div = item.parentElement.parentElement.parentElement; + const group = div.getAttribute("data-group"); + if (!result[group]) { + result[group] = {}; + } + const selectorsToSync = result[group]; + const value = item.innerHTML; + if (!selectorsToSync[value]) { + selectorsToSync[value] = []; + } + selectorsToSync[value].push(item); + } + return result; + } + + function setupSelectorSync() { + const selectorsToSync = findSelectorsToSyncByLanguage(); + Object.entries(selectorsToSync).forEach(([group, tabSetsByValue]) => { + Object.entries(tabSetsByValue).forEach(([value, items]) => { + items.forEach((item) => { + item.addEventListener("click", (_event) => { + setTabState(group, value); + toggleAll(value, selectorsToSync[group]); + }); + }); + }); + }); + return selectorsToSync; + } + + const selectorsToSync = setupSelectorSync(); + for (const [group, selectedName] of Object.entries(getTabSettings())) { + const selectors = selectorsToSync[group]; + // it's possible that stale state gives us empty selections, so we explicitly check here. + if (selectors) { + toggleAll(selectedName, selectors); + } + } +}); + +function throttle(func, wait) { + let waiting = false; + return function () { + if (!waiting) { + func.apply(this, arguments); + waiting = true; + setTimeout(function () { + waiting = false; + }, wait); + } + }; +} + +function nexttick(func) { + return setTimeout(func, 0); +} diff --git a/doc/ch02-GettingStarted_files/libs/quarto-html/tippy.css b/doc/ch02-GettingStarted_files/libs/quarto-html/tippy.css new file mode 100644 index 000000000..e6ae635cb --- /dev/null +++ b/doc/ch02-GettingStarted_files/libs/quarto-html/tippy.css @@ -0,0 +1 @@ +.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{position:relative;background-color:#333;color:#fff;border-radius:4px;font-size:14px;line-height:1.4;white-space:normal;outline:0;transition-property:transform,visibility,opacity}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-7px;left:0;border-width:8px 8px 0;border-top-color:initial;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-7px;left:0;border-width:0 8px 8px;border-bottom-color:initial;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-width:8px 0 8px 8px;border-left-color:initial;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-7px;border-width:8px 8px 8px 0;border-right-color:initial;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{width:16px;height:16px;color:#333}.tippy-arrow:before{content:"";position:absolute;border-color:transparent;border-style:solid}.tippy-content{position:relative;padding:5px 9px;z-index:1} \ No newline at end of file diff --git a/doc/ch02-GettingStarted_files/libs/quarto-html/tippy.umd.min.js b/doc/ch02-GettingStarted_files/libs/quarto-html/tippy.umd.min.js new file mode 100644 index 000000000..ca292be32 --- /dev/null +++ b/doc/ch02-GettingStarted_files/libs/quarto-html/tippy.umd.min.js @@ -0,0 +1,2 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("@popperjs/core")):"function"==typeof define&&define.amd?define(["@popperjs/core"],t):(e=e||self).tippy=t(e.Popper)}(this,(function(e){"use strict";var t={passive:!0,capture:!0},n=function(){return document.body};function r(e,t,n){if(Array.isArray(e)){var r=e[t];return null==r?Array.isArray(n)?n[t]:n:r}return e}function o(e,t){var n={}.toString.call(e);return 0===n.indexOf("[object")&&n.indexOf(t+"]")>-1}function i(e,t){return"function"==typeof e?e.apply(void 0,t):e}function a(e,t){return 0===t?e:function(r){clearTimeout(n),n=setTimeout((function(){e(r)}),t)};var n}function s(e,t){var n=Object.assign({},e);return t.forEach((function(e){delete n[e]})),n}function u(e){return[].concat(e)}function c(e,t){-1===e.indexOf(t)&&e.push(t)}function p(e){return e.split("-")[0]}function f(e){return[].slice.call(e)}function l(e){return Object.keys(e).reduce((function(t,n){return void 0!==e[n]&&(t[n]=e[n]),t}),{})}function d(){return document.createElement("div")}function v(e){return["Element","Fragment"].some((function(t){return o(e,t)}))}function m(e){return o(e,"MouseEvent")}function g(e){return!(!e||!e._tippy||e._tippy.reference!==e)}function h(e){return v(e)?[e]:function(e){return o(e,"NodeList")}(e)?f(e):Array.isArray(e)?e:f(document.querySelectorAll(e))}function b(e,t){e.forEach((function(e){e&&(e.style.transitionDuration=t+"ms")}))}function y(e,t){e.forEach((function(e){e&&e.setAttribute("data-state",t)}))}function w(e){var t,n=u(e)[0];return null!=n&&null!=(t=n.ownerDocument)&&t.body?n.ownerDocument:document}function E(e,t,n){var r=t+"EventListener";["transitionend","webkitTransitionEnd"].forEach((function(t){e[r](t,n)}))}function O(e,t){for(var n=t;n;){var r;if(e.contains(n))return!0;n=null==n.getRootNode||null==(r=n.getRootNode())?void 0:r.host}return!1}var x={isTouch:!1},C=0;function T(){x.isTouch||(x.isTouch=!0,window.performance&&document.addEventListener("mousemove",A))}function A(){var e=performance.now();e-C<20&&(x.isTouch=!1,document.removeEventListener("mousemove",A)),C=e}function L(){var e=document.activeElement;if(g(e)){var t=e._tippy;e.blur&&!t.state.isVisible&&e.blur()}}var D=!!("undefined"!=typeof window&&"undefined"!=typeof document)&&!!window.msCrypto,R=Object.assign({appendTo:n,aria:{content:"auto",expanded:"auto"},delay:0,duration:[300,250],getReferenceClientRect:null,hideOnClick:!0,ignoreAttributes:!1,interactive:!1,interactiveBorder:2,interactiveDebounce:0,moveTransition:"",offset:[0,10],onAfterUpdate:function(){},onBeforeUpdate:function(){},onCreate:function(){},onDestroy:function(){},onHidden:function(){},onHide:function(){},onMount:function(){},onShow:function(){},onShown:function(){},onTrigger:function(){},onUntrigger:function(){},onClickOutside:function(){},placement:"top",plugins:[],popperOptions:{},render:null,showOnCreate:!1,touch:!0,trigger:"mouseenter focus",triggerTarget:null},{animateFill:!1,followCursor:!1,inlinePositioning:!1,sticky:!1},{allowHTML:!1,animation:"fade",arrow:!0,content:"",inertia:!1,maxWidth:350,role:"tooltip",theme:"",zIndex:9999}),k=Object.keys(R);function P(e){var t=(e.plugins||[]).reduce((function(t,n){var r,o=n.name,i=n.defaultValue;o&&(t[o]=void 0!==e[o]?e[o]:null!=(r=R[o])?r:i);return t}),{});return Object.assign({},e,t)}function j(e,t){var n=Object.assign({},t,{content:i(t.content,[e])},t.ignoreAttributes?{}:function(e,t){return(t?Object.keys(P(Object.assign({},R,{plugins:t}))):k).reduce((function(t,n){var r=(e.getAttribute("data-tippy-"+n)||"").trim();if(!r)return t;if("content"===n)t[n]=r;else try{t[n]=JSON.parse(r)}catch(e){t[n]=r}return t}),{})}(e,t.plugins));return n.aria=Object.assign({},R.aria,n.aria),n.aria={expanded:"auto"===n.aria.expanded?t.interactive:n.aria.expanded,content:"auto"===n.aria.content?t.interactive?null:"describedby":n.aria.content},n}function M(e,t){e.innerHTML=t}function V(e){var t=d();return!0===e?t.className="tippy-arrow":(t.className="tippy-svg-arrow",v(e)?t.appendChild(e):M(t,e)),t}function I(e,t){v(t.content)?(M(e,""),e.appendChild(t.content)):"function"!=typeof t.content&&(t.allowHTML?M(e,t.content):e.textContent=t.content)}function S(e){var t=e.firstElementChild,n=f(t.children);return{box:t,content:n.find((function(e){return e.classList.contains("tippy-content")})),arrow:n.find((function(e){return e.classList.contains("tippy-arrow")||e.classList.contains("tippy-svg-arrow")})),backdrop:n.find((function(e){return e.classList.contains("tippy-backdrop")}))}}function N(e){var t=d(),n=d();n.className="tippy-box",n.setAttribute("data-state","hidden"),n.setAttribute("tabindex","-1");var r=d();function o(n,r){var o=S(t),i=o.box,a=o.content,s=o.arrow;r.theme?i.setAttribute("data-theme",r.theme):i.removeAttribute("data-theme"),"string"==typeof r.animation?i.setAttribute("data-animation",r.animation):i.removeAttribute("data-animation"),r.inertia?i.setAttribute("data-inertia",""):i.removeAttribute("data-inertia"),i.style.maxWidth="number"==typeof r.maxWidth?r.maxWidth+"px":r.maxWidth,r.role?i.setAttribute("role",r.role):i.removeAttribute("role"),n.content===r.content&&n.allowHTML===r.allowHTML||I(a,e.props),r.arrow?s?n.arrow!==r.arrow&&(i.removeChild(s),i.appendChild(V(r.arrow))):i.appendChild(V(r.arrow)):s&&i.removeChild(s)}return r.className="tippy-content",r.setAttribute("data-state","hidden"),I(r,e.props),t.appendChild(n),n.appendChild(r),o(e.props,e.props),{popper:t,onUpdate:o}}N.$$tippy=!0;var B=1,H=[],U=[];function _(o,s){var v,g,h,C,T,A,L,k,M=j(o,Object.assign({},R,P(l(s)))),V=!1,I=!1,N=!1,_=!1,F=[],W=a(we,M.interactiveDebounce),X=B++,Y=(k=M.plugins).filter((function(e,t){return k.indexOf(e)===t})),$={id:X,reference:o,popper:d(),popperInstance:null,props:M,state:{isEnabled:!0,isVisible:!1,isDestroyed:!1,isMounted:!1,isShown:!1},plugins:Y,clearDelayTimeouts:function(){clearTimeout(v),clearTimeout(g),cancelAnimationFrame(h)},setProps:function(e){if($.state.isDestroyed)return;ae("onBeforeUpdate",[$,e]),be();var t=$.props,n=j(o,Object.assign({},t,l(e),{ignoreAttributes:!0}));$.props=n,he(),t.interactiveDebounce!==n.interactiveDebounce&&(ce(),W=a(we,n.interactiveDebounce));t.triggerTarget&&!n.triggerTarget?u(t.triggerTarget).forEach((function(e){e.removeAttribute("aria-expanded")})):n.triggerTarget&&o.removeAttribute("aria-expanded");ue(),ie(),J&&J(t,n);$.popperInstance&&(Ce(),Ae().forEach((function(e){requestAnimationFrame(e._tippy.popperInstance.forceUpdate)})));ae("onAfterUpdate",[$,e])},setContent:function(e){$.setProps({content:e})},show:function(){var e=$.state.isVisible,t=$.state.isDestroyed,o=!$.state.isEnabled,a=x.isTouch&&!$.props.touch,s=r($.props.duration,0,R.duration);if(e||t||o||a)return;if(te().hasAttribute("disabled"))return;if(ae("onShow",[$],!1),!1===$.props.onShow($))return;$.state.isVisible=!0,ee()&&(z.style.visibility="visible");ie(),de(),$.state.isMounted||(z.style.transition="none");if(ee()){var u=re(),p=u.box,f=u.content;b([p,f],0)}A=function(){var e;if($.state.isVisible&&!_){if(_=!0,z.offsetHeight,z.style.transition=$.props.moveTransition,ee()&&$.props.animation){var t=re(),n=t.box,r=t.content;b([n,r],s),y([n,r],"visible")}se(),ue(),c(U,$),null==(e=$.popperInstance)||e.forceUpdate(),ae("onMount",[$]),$.props.animation&&ee()&&function(e,t){me(e,t)}(s,(function(){$.state.isShown=!0,ae("onShown",[$])}))}},function(){var e,t=$.props.appendTo,r=te();e=$.props.interactive&&t===n||"parent"===t?r.parentNode:i(t,[r]);e.contains(z)||e.appendChild(z);$.state.isMounted=!0,Ce()}()},hide:function(){var e=!$.state.isVisible,t=$.state.isDestroyed,n=!$.state.isEnabled,o=r($.props.duration,1,R.duration);if(e||t||n)return;if(ae("onHide",[$],!1),!1===$.props.onHide($))return;$.state.isVisible=!1,$.state.isShown=!1,_=!1,V=!1,ee()&&(z.style.visibility="hidden");if(ce(),ve(),ie(!0),ee()){var i=re(),a=i.box,s=i.content;$.props.animation&&(b([a,s],o),y([a,s],"hidden"))}se(),ue(),$.props.animation?ee()&&function(e,t){me(e,(function(){!$.state.isVisible&&z.parentNode&&z.parentNode.contains(z)&&t()}))}(o,$.unmount):$.unmount()},hideWithInteractivity:function(e){ne().addEventListener("mousemove",W),c(H,W),W(e)},enable:function(){$.state.isEnabled=!0},disable:function(){$.hide(),$.state.isEnabled=!1},unmount:function(){$.state.isVisible&&$.hide();if(!$.state.isMounted)return;Te(),Ae().forEach((function(e){e._tippy.unmount()})),z.parentNode&&z.parentNode.removeChild(z);U=U.filter((function(e){return e!==$})),$.state.isMounted=!1,ae("onHidden",[$])},destroy:function(){if($.state.isDestroyed)return;$.clearDelayTimeouts(),$.unmount(),be(),delete o._tippy,$.state.isDestroyed=!0,ae("onDestroy",[$])}};if(!M.render)return $;var q=M.render($),z=q.popper,J=q.onUpdate;z.setAttribute("data-tippy-root",""),z.id="tippy-"+$.id,$.popper=z,o._tippy=$,z._tippy=$;var G=Y.map((function(e){return e.fn($)})),K=o.hasAttribute("aria-expanded");return he(),ue(),ie(),ae("onCreate",[$]),M.showOnCreate&&Le(),z.addEventListener("mouseenter",(function(){$.props.interactive&&$.state.isVisible&&$.clearDelayTimeouts()})),z.addEventListener("mouseleave",(function(){$.props.interactive&&$.props.trigger.indexOf("mouseenter")>=0&&ne().addEventListener("mousemove",W)})),$;function Q(){var e=$.props.touch;return Array.isArray(e)?e:[e,0]}function Z(){return"hold"===Q()[0]}function ee(){var e;return!(null==(e=$.props.render)||!e.$$tippy)}function te(){return L||o}function ne(){var e=te().parentNode;return e?w(e):document}function re(){return S(z)}function oe(e){return $.state.isMounted&&!$.state.isVisible||x.isTouch||C&&"focus"===C.type?0:r($.props.delay,e?0:1,R.delay)}function ie(e){void 0===e&&(e=!1),z.style.pointerEvents=$.props.interactive&&!e?"":"none",z.style.zIndex=""+$.props.zIndex}function ae(e,t,n){var r;(void 0===n&&(n=!0),G.forEach((function(n){n[e]&&n[e].apply(n,t)})),n)&&(r=$.props)[e].apply(r,t)}function se(){var e=$.props.aria;if(e.content){var t="aria-"+e.content,n=z.id;u($.props.triggerTarget||o).forEach((function(e){var r=e.getAttribute(t);if($.state.isVisible)e.setAttribute(t,r?r+" "+n:n);else{var o=r&&r.replace(n,"").trim();o?e.setAttribute(t,o):e.removeAttribute(t)}}))}}function ue(){!K&&$.props.aria.expanded&&u($.props.triggerTarget||o).forEach((function(e){$.props.interactive?e.setAttribute("aria-expanded",$.state.isVisible&&e===te()?"true":"false"):e.removeAttribute("aria-expanded")}))}function ce(){ne().removeEventListener("mousemove",W),H=H.filter((function(e){return e!==W}))}function pe(e){if(!x.isTouch||!N&&"mousedown"!==e.type){var t=e.composedPath&&e.composedPath()[0]||e.target;if(!$.props.interactive||!O(z,t)){if(u($.props.triggerTarget||o).some((function(e){return O(e,t)}))){if(x.isTouch)return;if($.state.isVisible&&$.props.trigger.indexOf("click")>=0)return}else ae("onClickOutside",[$,e]);!0===$.props.hideOnClick&&($.clearDelayTimeouts(),$.hide(),I=!0,setTimeout((function(){I=!1})),$.state.isMounted||ve())}}}function fe(){N=!0}function le(){N=!1}function de(){var e=ne();e.addEventListener("mousedown",pe,!0),e.addEventListener("touchend",pe,t),e.addEventListener("touchstart",le,t),e.addEventListener("touchmove",fe,t)}function ve(){var e=ne();e.removeEventListener("mousedown",pe,!0),e.removeEventListener("touchend",pe,t),e.removeEventListener("touchstart",le,t),e.removeEventListener("touchmove",fe,t)}function me(e,t){var n=re().box;function r(e){e.target===n&&(E(n,"remove",r),t())}if(0===e)return t();E(n,"remove",T),E(n,"add",r),T=r}function ge(e,t,n){void 0===n&&(n=!1),u($.props.triggerTarget||o).forEach((function(r){r.addEventListener(e,t,n),F.push({node:r,eventType:e,handler:t,options:n})}))}function he(){var e;Z()&&(ge("touchstart",ye,{passive:!0}),ge("touchend",Ee,{passive:!0})),(e=$.props.trigger,e.split(/\s+/).filter(Boolean)).forEach((function(e){if("manual"!==e)switch(ge(e,ye),e){case"mouseenter":ge("mouseleave",Ee);break;case"focus":ge(D?"focusout":"blur",Oe);break;case"focusin":ge("focusout",Oe)}}))}function be(){F.forEach((function(e){var t=e.node,n=e.eventType,r=e.handler,o=e.options;t.removeEventListener(n,r,o)})),F=[]}function ye(e){var t,n=!1;if($.state.isEnabled&&!xe(e)&&!I){var r="focus"===(null==(t=C)?void 0:t.type);C=e,L=e.currentTarget,ue(),!$.state.isVisible&&m(e)&&H.forEach((function(t){return t(e)})),"click"===e.type&&($.props.trigger.indexOf("mouseenter")<0||V)&&!1!==$.props.hideOnClick&&$.state.isVisible?n=!0:Le(e),"click"===e.type&&(V=!n),n&&!r&&De(e)}}function we(e){var t=e.target,n=te().contains(t)||z.contains(t);"mousemove"===e.type&&n||function(e,t){var n=t.clientX,r=t.clientY;return e.every((function(e){var t=e.popperRect,o=e.popperState,i=e.props.interactiveBorder,a=p(o.placement),s=o.modifiersData.offset;if(!s)return!0;var u="bottom"===a?s.top.y:0,c="top"===a?s.bottom.y:0,f="right"===a?s.left.x:0,l="left"===a?s.right.x:0,d=t.top-r+u>i,v=r-t.bottom-c>i,m=t.left-n+f>i,g=n-t.right-l>i;return d||v||m||g}))}(Ae().concat(z).map((function(e){var t,n=null==(t=e._tippy.popperInstance)?void 0:t.state;return n?{popperRect:e.getBoundingClientRect(),popperState:n,props:M}:null})).filter(Boolean),e)&&(ce(),De(e))}function Ee(e){xe(e)||$.props.trigger.indexOf("click")>=0&&V||($.props.interactive?$.hideWithInteractivity(e):De(e))}function Oe(e){$.props.trigger.indexOf("focusin")<0&&e.target!==te()||$.props.interactive&&e.relatedTarget&&z.contains(e.relatedTarget)||De(e)}function xe(e){return!!x.isTouch&&Z()!==e.type.indexOf("touch")>=0}function Ce(){Te();var t=$.props,n=t.popperOptions,r=t.placement,i=t.offset,a=t.getReferenceClientRect,s=t.moveTransition,u=ee()?S(z).arrow:null,c=a?{getBoundingClientRect:a,contextElement:a.contextElement||te()}:o,p=[{name:"offset",options:{offset:i}},{name:"preventOverflow",options:{padding:{top:2,bottom:2,left:5,right:5}}},{name:"flip",options:{padding:5}},{name:"computeStyles",options:{adaptive:!s}},{name:"$$tippy",enabled:!0,phase:"beforeWrite",requires:["computeStyles"],fn:function(e){var t=e.state;if(ee()){var n=re().box;["placement","reference-hidden","escaped"].forEach((function(e){"placement"===e?n.setAttribute("data-placement",t.placement):t.attributes.popper["data-popper-"+e]?n.setAttribute("data-"+e,""):n.removeAttribute("data-"+e)})),t.attributes.popper={}}}}];ee()&&u&&p.push({name:"arrow",options:{element:u,padding:3}}),p.push.apply(p,(null==n?void 0:n.modifiers)||[]),$.popperInstance=e.createPopper(c,z,Object.assign({},n,{placement:r,onFirstUpdate:A,modifiers:p}))}function Te(){$.popperInstance&&($.popperInstance.destroy(),$.popperInstance=null)}function Ae(){return f(z.querySelectorAll("[data-tippy-root]"))}function Le(e){$.clearDelayTimeouts(),e&&ae("onTrigger",[$,e]),de();var t=oe(!0),n=Q(),r=n[0],o=n[1];x.isTouch&&"hold"===r&&o&&(t=o),t?v=setTimeout((function(){$.show()}),t):$.show()}function De(e){if($.clearDelayTimeouts(),ae("onUntrigger",[$,e]),$.state.isVisible){if(!($.props.trigger.indexOf("mouseenter")>=0&&$.props.trigger.indexOf("click")>=0&&["mouseleave","mousemove"].indexOf(e.type)>=0&&V)){var t=oe(!1);t?g=setTimeout((function(){$.state.isVisible&&$.hide()}),t):h=requestAnimationFrame((function(){$.hide()}))}}else ve()}}function F(e,n){void 0===n&&(n={});var r=R.plugins.concat(n.plugins||[]);document.addEventListener("touchstart",T,t),window.addEventListener("blur",L);var o=Object.assign({},n,{plugins:r}),i=h(e).reduce((function(e,t){var n=t&&_(t,o);return n&&e.push(n),e}),[]);return v(e)?i[0]:i}F.defaultProps=R,F.setDefaultProps=function(e){Object.keys(e).forEach((function(t){R[t]=e[t]}))},F.currentInput=x;var W=Object.assign({},e.applyStyles,{effect:function(e){var t=e.state,n={popper:{position:t.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};Object.assign(t.elements.popper.style,n.popper),t.styles=n,t.elements.arrow&&Object.assign(t.elements.arrow.style,n.arrow)}}),X={mouseover:"mouseenter",focusin:"focus",click:"click"};var Y={name:"animateFill",defaultValue:!1,fn:function(e){var t;if(null==(t=e.props.render)||!t.$$tippy)return{};var n=S(e.popper),r=n.box,o=n.content,i=e.props.animateFill?function(){var e=d();return e.className="tippy-backdrop",y([e],"hidden"),e}():null;return{onCreate:function(){i&&(r.insertBefore(i,r.firstElementChild),r.setAttribute("data-animatefill",""),r.style.overflow="hidden",e.setProps({arrow:!1,animation:"shift-away"}))},onMount:function(){if(i){var e=r.style.transitionDuration,t=Number(e.replace("ms",""));o.style.transitionDelay=Math.round(t/10)+"ms",i.style.transitionDuration=e,y([i],"visible")}},onShow:function(){i&&(i.style.transitionDuration="0ms")},onHide:function(){i&&y([i],"hidden")}}}};var $={clientX:0,clientY:0},q=[];function z(e){var t=e.clientX,n=e.clientY;$={clientX:t,clientY:n}}var J={name:"followCursor",defaultValue:!1,fn:function(e){var t=e.reference,n=w(e.props.triggerTarget||t),r=!1,o=!1,i=!0,a=e.props;function s(){return"initial"===e.props.followCursor&&e.state.isVisible}function u(){n.addEventListener("mousemove",f)}function c(){n.removeEventListener("mousemove",f)}function p(){r=!0,e.setProps({getReferenceClientRect:null}),r=!1}function f(n){var r=!n.target||t.contains(n.target),o=e.props.followCursor,i=n.clientX,a=n.clientY,s=t.getBoundingClientRect(),u=i-s.left,c=a-s.top;!r&&e.props.interactive||e.setProps({getReferenceClientRect:function(){var e=t.getBoundingClientRect(),n=i,r=a;"initial"===o&&(n=e.left+u,r=e.top+c);var s="horizontal"===o?e.top:r,p="vertical"===o?e.right:n,f="horizontal"===o?e.bottom:r,l="vertical"===o?e.left:n;return{width:p-l,height:f-s,top:s,right:p,bottom:f,left:l}}})}function l(){e.props.followCursor&&(q.push({instance:e,doc:n}),function(e){e.addEventListener("mousemove",z)}(n))}function d(){0===(q=q.filter((function(t){return t.instance!==e}))).filter((function(e){return e.doc===n})).length&&function(e){e.removeEventListener("mousemove",z)}(n)}return{onCreate:l,onDestroy:d,onBeforeUpdate:function(){a=e.props},onAfterUpdate:function(t,n){var i=n.followCursor;r||void 0!==i&&a.followCursor!==i&&(d(),i?(l(),!e.state.isMounted||o||s()||u()):(c(),p()))},onMount:function(){e.props.followCursor&&!o&&(i&&(f($),i=!1),s()||u())},onTrigger:function(e,t){m(t)&&($={clientX:t.clientX,clientY:t.clientY}),o="focus"===t.type},onHidden:function(){e.props.followCursor&&(p(),c(),i=!0)}}}};var G={name:"inlinePositioning",defaultValue:!1,fn:function(e){var t,n=e.reference;var r=-1,o=!1,i=[],a={name:"tippyInlinePositioning",enabled:!0,phase:"afterWrite",fn:function(o){var a=o.state;e.props.inlinePositioning&&(-1!==i.indexOf(a.placement)&&(i=[]),t!==a.placement&&-1===i.indexOf(a.placement)&&(i.push(a.placement),e.setProps({getReferenceClientRect:function(){return function(e){return function(e,t,n,r){if(n.length<2||null===e)return t;if(2===n.length&&r>=0&&n[0].left>n[1].right)return n[r]||t;switch(e){case"top":case"bottom":var o=n[0],i=n[n.length-1],a="top"===e,s=o.top,u=i.bottom,c=a?o.left:i.left,p=a?o.right:i.right;return{top:s,bottom:u,left:c,right:p,width:p-c,height:u-s};case"left":case"right":var f=Math.min.apply(Math,n.map((function(e){return e.left}))),l=Math.max.apply(Math,n.map((function(e){return e.right}))),d=n.filter((function(t){return"left"===e?t.left===f:t.right===l})),v=d[0].top,m=d[d.length-1].bottom;return{top:v,bottom:m,left:f,right:l,width:l-f,height:m-v};default:return t}}(p(e),n.getBoundingClientRect(),f(n.getClientRects()),r)}(a.placement)}})),t=a.placement)}};function s(){var t;o||(t=function(e,t){var n;return{popperOptions:Object.assign({},e.popperOptions,{modifiers:[].concat(((null==(n=e.popperOptions)?void 0:n.modifiers)||[]).filter((function(e){return e.name!==t.name})),[t])})}}(e.props,a),o=!0,e.setProps(t),o=!1)}return{onCreate:s,onAfterUpdate:s,onTrigger:function(t,n){if(m(n)){var o=f(e.reference.getClientRects()),i=o.find((function(e){return e.left-2<=n.clientX&&e.right+2>=n.clientX&&e.top-2<=n.clientY&&e.bottom+2>=n.clientY})),a=o.indexOf(i);r=a>-1?a:r}},onHidden:function(){r=-1}}}};var K={name:"sticky",defaultValue:!1,fn:function(e){var t=e.reference,n=e.popper;function r(t){return!0===e.props.sticky||e.props.sticky===t}var o=null,i=null;function a(){var s=r("reference")?(e.popperInstance?e.popperInstance.state.elements.reference:t).getBoundingClientRect():null,u=r("popper")?n.getBoundingClientRect():null;(s&&Q(o,s)||u&&Q(i,u))&&e.popperInstance&&e.popperInstance.update(),o=s,i=u,e.state.isMounted&&requestAnimationFrame(a)}return{onMount:function(){e.props.sticky&&a()}}}};function Q(e,t){return!e||!t||(e.top!==t.top||e.right!==t.right||e.bottom!==t.bottom||e.left!==t.left)}return F.setDefaultProps({plugins:[Y,J,G,K],render:N}),F.createSingleton=function(e,t){var n;void 0===t&&(t={});var r,o=e,i=[],a=[],c=t.overrides,p=[],f=!1;function l(){a=o.map((function(e){return u(e.props.triggerTarget||e.reference)})).reduce((function(e,t){return e.concat(t)}),[])}function v(){i=o.map((function(e){return e.reference}))}function m(e){o.forEach((function(t){e?t.enable():t.disable()}))}function g(e){return o.map((function(t){var n=t.setProps;return t.setProps=function(o){n(o),t.reference===r&&e.setProps(o)},function(){t.setProps=n}}))}function h(e,t){var n=a.indexOf(t);if(t!==r){r=t;var s=(c||[]).concat("content").reduce((function(e,t){return e[t]=o[n].props[t],e}),{});e.setProps(Object.assign({},s,{getReferenceClientRect:"function"==typeof s.getReferenceClientRect?s.getReferenceClientRect:function(){var e;return null==(e=i[n])?void 0:e.getBoundingClientRect()}}))}}m(!1),v(),l();var b={fn:function(){return{onDestroy:function(){m(!0)},onHidden:function(){r=null},onClickOutside:function(e){e.props.showOnCreate&&!f&&(f=!0,r=null)},onShow:function(e){e.props.showOnCreate&&!f&&(f=!0,h(e,i[0]))},onTrigger:function(e,t){h(e,t.currentTarget)}}}},y=F(d(),Object.assign({},s(t,["overrides"]),{plugins:[b].concat(t.plugins||[]),triggerTarget:a,popperOptions:Object.assign({},t.popperOptions,{modifiers:[].concat((null==(n=t.popperOptions)?void 0:n.modifiers)||[],[W])})})),w=y.show;y.show=function(e){if(w(),!r&&null==e)return h(y,i[0]);if(!r||null!=e){if("number"==typeof e)return i[e]&&h(y,i[e]);if(o.indexOf(e)>=0){var t=e.reference;return h(y,t)}return i.indexOf(e)>=0?h(y,e):void 0}},y.showNext=function(){var e=i[0];if(!r)return y.show(0);var t=i.indexOf(r);y.show(i[t+1]||e)},y.showPrevious=function(){var e=i[i.length-1];if(!r)return y.show(e);var t=i.indexOf(r),n=i[t-1]||e;y.show(n)};var E=y.setProps;return y.setProps=function(e){c=e.overrides||c,E(e)},y.setInstances=function(e){m(!0),p.forEach((function(e){return e()})),o=e,m(!1),v(),l(),p=g(y),y.setProps({triggerTarget:a})},p=g(y),y},F.delegate=function(e,n){var r=[],o=[],i=!1,a=n.target,c=s(n,["target"]),p=Object.assign({},c,{trigger:"manual",touch:!1}),f=Object.assign({touch:R.touch},c,{showOnCreate:!0}),l=F(e,p);function d(e){if(e.target&&!i){var t=e.target.closest(a);if(t){var r=t.getAttribute("data-tippy-trigger")||n.trigger||R.trigger;if(!t._tippy&&!("touchstart"===e.type&&"boolean"==typeof f.touch||"touchstart"!==e.type&&r.indexOf(X[e.type])<0)){var s=F(t,f);s&&(o=o.concat(s))}}}}function v(e,t,n,o){void 0===o&&(o=!1),e.addEventListener(t,n,o),r.push({node:e,eventType:t,handler:n,options:o})}return u(l).forEach((function(e){var n=e.destroy,a=e.enable,s=e.disable;e.destroy=function(e){void 0===e&&(e=!0),e&&o.forEach((function(e){e.destroy()})),o=[],r.forEach((function(e){var t=e.node,n=e.eventType,r=e.handler,o=e.options;t.removeEventListener(n,r,o)})),r=[],n()},e.enable=function(){a(),o.forEach((function(e){return e.enable()})),i=!1},e.disable=function(){s(),o.forEach((function(e){return e.disable()})),i=!0},function(e){var n=e.reference;v(n,"touchstart",d,t),v(n,"mouseover",d),v(n,"focusin",d),v(n,"click",d)}(e)})),l},F.hideAll=function(e){var t=void 0===e?{}:e,n=t.exclude,r=t.duration;U.forEach((function(e){var t=!1;if(n&&(t=g(n)?e.reference===n:e.popper===n.popper),!t){var o=e.props.duration;e.setProps({duration:r}),e.hide(),e.state.isDestroyed||e.setProps({duration:o})}}))},F.roundArrow='',F})); + diff --git a/doc/ch03-StochasticsPlugin.html b/doc/ch03-StochasticsPlugin.html new file mode 100644 index 000000000..2e32ad5b1 --- /dev/null +++ b/doc/ch03-StochasticsPlugin.html @@ -0,0 +1,1400 @@ + + + + + + + + + +GCM-Docs - 3  Stochastics Plugin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    + +
    + + + + + +
    + +
    +
    +

    3  Stochastics Plugin

    +
    + + + +
    + + + + +
    + + +
    + +
    + +
    +

    The stochastics plugin provides for the management of random number generators. It contains a default random number generator (RNG) as well as any number of RNGs associated with identifiers.

    +
    +

    3.1 Plugin Data Initialization

    +

    The plugin is initialized using a StochasticsPluginData object that collects starting seed values for the default RNG as well as any number of RNG identifiers. These identifiers are implemented via the RandomGeneratorId interface which only specifies that such an identifier have a non-null, non-empty and stable implementation of the Object.toString() method.

    +

    All RNGs in GCM are implemented using the org.apache.commons.math3.random.Well44497b random number generator. GCM introduces the class Well.java that extends the Well44497b to allow for the serialization of its internal state. The data that supports this serialization is contained in the WellState.java class that uses a standard builder pattern. Serialization concerns are beyond the scope of this chapter, so all WellState objects will be seeded with long values only.

    +
    +
    +

    3.2 Plugin Behavior

    +

    The plugin adds a single data manager to the simulation as an instance of the StochasticsDataManager that is initialized with the StochasticsPluginData.

    +
    +
    +

    3.3 Data Manager

    +

    The data manager provides access to its RNGs via various getter methods.

    +
    +
    +

    3.4 (Lesson 11) Example 11A

    +

    Our first example lesson uses the disease, model and policy plugins again. This time we will have the single ModelActor schedule three random times to set the R0 value to a random number between 1 and 2. Four scenarios will result from a policy based dimension that alters the school closing infection rates, which will not influence the ModelActor.

    +
    +
    +

    Code Block 3.1: The policy dimension has four levels for the infection rates that trigger school closure.

    +
    private static Dimension getPolicyDimension() {
    +    FunctionalDimension.Builder builder = FunctionalDimension.builder();//
    +
    +    List<Double> schoolClosingInfectionRates = new ArrayList<>();
    +    schoolClosingInfectionRates.add(0.05);
    +    schoolClosingInfectionRates.add(0.10);
    +    schoolClosingInfectionRates.add(0.15);
    +    schoolClosingInfectionRates.add(0.20);
    +
    +    for (Double schoolClosingInfectionRate : schoolClosingInfectionRates) {
    +        builder.addLevel((context) -> {
    +            PolicyPluginData.Builder pluginDataBuilder = context
    +                    .getPluginDataBuilder(PolicyPluginData.Builder.class);
    +            pluginDataBuilder.setSchoolClosingInfectionRate(schoolClosingInfectionRate);
    +
    +            ArrayList<String> result = new ArrayList<>();
    +            result.add(Double.toString(schoolClosingInfectionRate));
    +
    +            return result;
    +        });//
    +    }
    +
    +    builder.addMetaDatum("school_closing_infection_rate");//
    +
    +    return builder.build();
    +
    +}
    +
    +
    +
    +
    +

    Code Block 3.2: Example 11 introduces the stochastics plugin and executes four scenarios. The random seed for each scenario will be identical.

    +
    public static void main(String[] args) {
    +
    +    DiseasePluginData diseasePluginData = getDiseasePluginData();
    +    Plugin diseasePlugin = DiseasePlugin.getDiseasePlugin(diseasePluginData);
    +
    +    PolicyPluginData policyPluginData = getPolicyPluginData();
    +    Plugin policyPlugin = PolicyPlugin.getPolicyPlugin(policyPluginData);
    +
    +    Plugin modelPlugin = ModelPlugin.getModelPlugin();
    +
    +    WellState wellState = WellState.builder().setSeed(0).build();
    +    StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder().setMainRNGState(wellState)
    +            .build();
    +    Plugin stochasticsPlugin = StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData);
    +
    +    Dimension policyDimension = getPolicyDimension();
    +
    +    ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()//
    +            .setThreadCount(4)//
    +            .setHaltOnException(true)//
    +            .build();
    +
    +    Experiment.builder()//
    +            .addPlugin(stochasticsPlugin)//
    +            .addPlugin(diseasePlugin)//
    +            .addPlugin(modelPlugin)//
    +            .addPlugin(policyPlugin)//
    +            .addDimension(policyDimension)//
    +            .addExperimentContextConsumer(new SimpleOutputConsumer())//
    +            .setExperimentParameterData(experimentParameterData)//
    +            .build()//
    +            .execute();
    +}
    +
    +
    +

    The stochastics plugin is initialized with a seed value of zero and that seed will be used in each scenario as the initial seeding for the default random generator. Thus we expect that each scenario will have identical output.

    +
    +
    +
    +

    Figure 3.1: The output for example 11 shows the four scenarios each reporting three changes to R0 by the model actor.

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +scenario + +school_closing_infection_rate + +output +
    +1 + +0.10 + +setting R0 to 1.432233562051883 at time = 3.252869296309885 +
    +1 + +0.10 + +setting R0 to 1.1828080336720215 at time = 4.115633147309184 +
    +0 + +0.05 + +setting R0 to 1.432233562051883 at time = 3.252869296309885 +
    +3 + +0.20 + +setting R0 to 1.432233562051883 at time = 3.252869296309885 +
    +1 + +0.10 + +setting R0 to 1.895526664357549 at time = 8.614888683848772 +
    +0 + +0.05 + +setting R0 to 1.1828080336720215 at time = 4.115633147309184 +
    +2 + +0.15 + +setting R0 to 1.432233562051883 at time = 3.252869296309885 +
    +3 + +0.20 + +setting R0 to 1.1828080336720215 at time = 4.115633147309184 +
    +0 + +0.05 + +setting R0 to 1.895526664357549 at time = 8.614888683848772 +
    +2 + +0.15 + +setting R0 to 1.1828080336720215 at time = 4.115633147309184 +
    +3 + +0.20 + +setting R0 to 1.895526664357549 at time = 8.614888683848772 +
    +2 + +0.15 + +setting R0 to 1.895526664357549 at time = 8.614888683848772 +
    +
    +
    +
    +
    +
    +
    +

    3.5 Example 11B

    +

    Our next example lesson adds a dimension used to alter the initial seed value of the stochastics plugin data to one of three values. Combined with the policy dimension, this will result in 12 scenarios.

    +
    +
    +

    Code Block 3.3: The stochastics dimension introduces three random seeds that will be used in creating the scenarios. Note that seeds are generated outside of the levels within the dimension.

    +
    private static Dimension getStochasticsDimension(long seed) {
    +    FunctionalDimension.Builder builder = FunctionalDimension.builder();//
    +
    +    Random random = new Random(seed);
    +
    +    List<Long> seedValues = new ArrayList<>();
    +    for (int i = 0; i < 3; i++) {
    +        seedValues.add(random.nextLong());
    +    }
    +
    +    IntStream.range(0, seedValues.size()).forEach((i) -> {
    +        builder.addLevel((context) -> {
    +            StochasticsPluginData.Builder stochasticsPluginDataBuilder = context
    +                    .getPluginDataBuilder(StochasticsPluginData.Builder.class);
    +            long seedValue = seedValues.get(i);
    +            WellState wellState = WellState.builder().setSeed(seedValue).build();
    +            stochasticsPluginDataBuilder.setMainRNGState(wellState);
    +
    +            ArrayList<String> result = new ArrayList<>();
    +            result.add(Integer.toString(i));
    +            result.add(Long.toString(seedValue) + "L");
    +
    +            return result;
    +        });
    +    });
    +
    +    builder.addMetaDatum("seed index");//
    +    builder.addMetaDatum("seed value");//
    +
    +    return builder.build();
    +}
    +
    +
    +
    +
    +

    Code Block 3.4: The experiment uses the stochastics dimension, resulting in twelve scenarios.

    +
    public static void main(String[] args) {
    +
    +    DiseasePluginData diseasePluginData = getDiseasePluginData();
    +    Plugin diseasePlugin = DiseasePlugin.getDiseasePlugin(diseasePluginData);
    +
    +    PolicyPluginData policyPluginData = getPolicyPluginData();
    +    Plugin policyPlugin = PolicyPlugin.getPolicyPlugin(policyPluginData);
    +
    +    Plugin modelPlugin = ModelPlugin.getModelPlugin();
    +    WellState wellState = WellState.builder().setSeed(0).build();
    +    StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder().setMainRNGState(wellState)
    +            .build();
    +    Plugin stochasticsPlugin = StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData);
    +
    +    Dimension policyDimension = getPolicyDimension();
    +    Dimension stochasticsDimension = getStochasticsDimension(539847398756272L);
    +
    +    ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()//
    +            .setThreadCount(4)//
    +            .build();
    +
    +    Experiment.builder()//
    +            .addPlugin(stochasticsPlugin)//
    +            .addPlugin(diseasePlugin)//
    +            .addPlugin(modelPlugin)//
    +            .addPlugin(policyPlugin)//
    +            .addDimension(policyDimension)//
    +            .addDimension(stochasticsDimension)//
    +            .addExperimentContextConsumer(new SimpleOutputConsumer())//
    +            .setExperimentParameterData(experimentParameterData)//
    +            .build()//
    +            .execute();
    +}
    +
    +
    +

    The resulting output shows the varying random number generation:

    +
    +
    +
    +

    Figure 3.2: The output show that twelve scearnios result in 36 output lines since the model actor update R0 three times per scenario.

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +scenario + +school_closing_infection_rate + +seed_index + +seed_value + +output +
    +3 + +0.2 + +0 + +1768604912325913878L + +setting R0 to 1.4595120508977955 at time = 5.672294645832067 +
    +2 + +0.15 + +0 + +1768604912325913878L + +setting R0 to 1.4595120508977955 at time = 5.672294645832067 +
    +3 + +0.2 + +0 + +1768604912325913878L + +setting R0 to 1.672857576347001 at time = 9.396161237311702 +
    +1 + +0.1 + +0 + +1768604912325913878L + +setting R0 to 1.4595120508977955 at time = 5.672294645832067 +
    +0 + +0.05 + +0 + +1768604912325913878L + +setting R0 to 1.4595120508977955 at time = 5.672294645832067 +
    +3 + +0.2 + +0 + +1768604912325913878L + +setting R0 to 1.4552243930124602 at time = 9.545450999459224 +
    +1 + +0.1 + +0 + +1768604912325913878L + +setting R0 to 1.672857576347001 at time = 9.396161237311702 +
    +2 + +0.15 + +0 + +1768604912325913878L + +setting R0 to 1.672857576347001 at time = 9.396161237311702 +
    +0 + +0.05 + +0 + +1768604912325913878L + +setting R0 to 1.672857576347001 at time = 9.396161237311702 +
    +1 + +0.1 + +0 + +1768604912325913878L + +setting R0 to 1.4552243930124602 at time = 9.545450999459224 +
    +2 + +0.15 + +0 + +1768604912325913878L + +setting R0 to 1.4552243930124602 at time = 9.545450999459224 +
    +0 + +0.05 + +0 + +1768604912325913878L + +setting R0 to 1.4552243930124602 at time = 9.545450999459224 +
    +4 + +0.05 + +1 + +2407662077113051075L + +setting R0 to 1.7124977513361193 at time = 1.878219018807409 +
    +4 + +0.05 + +1 + +2407662077113051075L + +setting R0 to 1.4297609713254456 at time = 2.4215934269433106 +
    +4 + +0.05 + +1 + +2407662077113051075L + +setting R0 to 1.5167619787625548 at time = 5.992476899450338 +
    +5 + +0.1 + +1 + +2407662077113051075L + +setting R0 to 1.7124977513361193 at time = 1.878219018807409 +
    +5 + +0.1 + +1 + +2407662077113051075L + +setting R0 to 1.4297609713254456 at time = 2.4215934269433106 +
    +5 + +0.1 + +1 + +2407662077113051075L + +setting R0 to 1.5167619787625548 at time = 5.992476899450338 +
    +6 + +0.15 + +1 + +2407662077113051075L + +setting R0 to 1.7124977513361193 at time = 1.878219018807409 +
    +6 + +0.15 + +1 + +2407662077113051075L + +setting R0 to 1.4297609713254456 at time = 2.4215934269433106 +
    +6 + +0.15 + +1 + +2407662077113051075L + +setting R0 to 1.5167619787625548 at time = 5.992476899450338 +
    +7 + +0.2 + +1 + +2407662077113051075L + +setting R0 to 1.7124977513361193 at time = 1.878219018807409 +
    +7 + +0.2 + +1 + +2407662077113051075L + +setting R0 to 1.4297609713254456 at time = 2.4215934269433106 +
    +7 + +0.2 + +1 + +2407662077113051075L + +setting R0 to 1.5167619787625548 at time = 5.992476899450338 +
    +9 + +0.1 + +2 + +-2698580492431892402L + +setting R0 to 1.9665140789775497 at time = 4.990978097055602 +
    +9 + +0.1 + +2 + +-2698580492431892402L + +setting R0 to 1.9525439902956225 at time = 6.227836574620975 +
    +9 + +0.1 + +2 + +-2698580492431892402L + +setting R0 to 1.8972135699711736 at time = 7.764180353240558 +
    +8 + +0.05 + +2 + +-2698580492431892402L + +setting R0 to 1.9665140789775497 at time = 4.990978097055602 +
    +8 + +0.05 + +2 + +-2698580492431892402L + +setting R0 to 1.9525439902956225 at time = 6.227836574620975 +
    +8 + +0.05 + +2 + +-2698580492431892402L + +setting R0 to 1.8972135699711736 at time = 7.764180353240558 +
    +10 + +0.15 + +2 + +-2698580492431892402L + +setting R0 to 1.9665140789775497 at time = 4.990978097055602 +
    +10 + +0.15 + +2 + +-2698580492431892402L + +setting R0 to 1.9525439902956225 at time = 6.227836574620975 +
    +10 + +0.15 + +2 + +-2698580492431892402L + +setting R0 to 1.8972135699711736 at time = 7.764180353240558 +
    +11 + +0.2 + +2 + +-2698580492431892402L + +setting R0 to 1.9665140789775497 at time = 4.990978097055602 +
    +11 + +0.2 + +2 + +-2698580492431892402L + +setting R0 to 1.9525439902956225 at time = 6.227836574620975 +
    +11 + +0.2 + +2 + +-2698580492431892402L + +setting R0 to 1.8972135699711736 at time = 7.764180353240558 +
    +
    +
    +
    +
    + + +
    + +
    + + +
    + + + + \ No newline at end of file diff --git a/doc/ch04-ReportsPlugin.html b/doc/ch04-ReportsPlugin.html new file mode 100644 index 000000000..16a193f0f --- /dev/null +++ b/doc/ch04-ReportsPlugin.html @@ -0,0 +1,2318 @@ + + + + + + + + + +GCM-Docs - 4  Reports Plugin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    + +
    + + + + + +
    + +
    +
    +

    4  Reports Plugin

    +
    + + + +
    + + + + +
    + + +
    + +
    + +
    +

    The reports plugin implements an experiment context consumer that records output into tab-delimited (or other delimiter) text files via the java.nio library using blocking file writes. Three new concepts form the core of the reports:

    +
      +
    1. Report Label – a unique identifier for each report
    2. +
    3. Report Header – the header content for the report
    4. +
    5. Report Item – the data content(strings) for each line in the report
    6. +
    +

    The report label is a unique identifier used to mark every report item that the plugin processes and helps associate those items to the specific files where they will be recorded. Report items are a flexible list of values that have an associated report label as well as a report header used to build the header of the file. A report file is built from the report items that are associated with a specific report label in the order received. The first report item is used to build the header of the report file. All other lines of the file are an ordered, delimited listing of the string values contained in each report item. No attempt is made to ensure that the header matches the report lines or that all report lines have equal field lengths.

    +

    While any data manager or actor can release report items, in practice most reports are managed solely by special purpose, passive reports that simply observe events and do not act on any data manager. These reports often subscribe to multiple event types and aggregate several events into a single report item. This reduces the amount of information recorded in output files and thus output files are often more complex than simple event trace files.

    +

    Reports enjoy most of the privileges of actors and use a ReportContext to work with the simulation. They can get information directly from data managers, subscribe to events and plan. However, they cannot mutate data and thus their presence should have little to no impact on the state of the simulation. Like actors, reports are confined to simulation instances and there will often be multiple report instances contributing to the same output file from simultaneously running scenarios.

    +
    +

    4.1 Plugin Data Initialization

    +

    There is no plugin initialization data class. Reports are contributed by other plugins via their initializers.

    +
    +
    +

    4.2 Plugin Behavior

    +

    The Plugin contains no data managers, actors or reports.

    +
    +
    +

    4.3 Experiment Context Consumer

    +

    So far we have seen that reports tend to be produced by specialized classes and that those classes can be added to the simulation via the plugin initialization data. This covers the production and release of the report items from each simulation instance but not what happens to the report items afterward. The output files that receive the report items must work with multiple threads. We manage this with a threadsafe experiment context consumer, the NIOReportItemHandler, that is added to the experiment. The NIOReportItemHandler is created via a builder pattern that allows the modeler to associate report ids to file paths.

    +
    +
    +

    4.4 Example Reports (Lesson 12)

    +

    We reach back to the previous lessons where we introduced plugins for people, families and vaccines for a demonstration of reports. The reports will center on the vaccination of families in various forms and are implemented by three dedicated report classes in the vaccine plugin:

    +
      +
    • FamilyVaccineReport – Immediate reporting based on observed events
    • +
    • HourlyVaccineReport – Hourly reporting based on observed events
    • +
    • StatelessVaccineReport – Hourly reports based on inspection of current state
    • +
    +
    +
    +

    4.5 General Setup

    +

    This example uses the following plugins:

    +
      +
    • Person Plugin – provides containment for person identifiers
    • +
    • Stochastics Plugin – (GCM plugin) provides random number generation
    • +
    • Reports Plugin – (GCM plugin) provides reporting mechanisms
    • +
    • Family Plugin – defines families and associates people with families
    • +
    • Vaccine Plugin – maintains vaccine assignments with people and families and defines the three reports
    • +
    • Model Plugin – provides an actor for loading the initial population and an actor for scheduling vaccinations
    • +
    +

    The general flow of action in the simulation is that the PopulationLoader actor will add people and families to the simulation based on the initial plugin data provided in the family plugin.  The VaccineScheduler actor will then schedule people at random times to be vaccinated. As people and families are created, people join families and people are vaccinated, the various data mangers will generate the relevant events for observation by the three reports.  The reports will observe these events and correspondingly generate report items that will flow out of the simulation into the experiment level report mechanisms that will result in report files being written.

    +

    Let’s examine Example_12. In Code Block 4.1 we see that the plugins are generated with the person, vaccine and model plugins requiring no input data. The stochastics plugin is generated with a fixed seed value.  Next, the family plugin is created with initial data specifying that 30 families will be created and that each family will have a random number of members up to 5 people.

    +
    +
    +

    Code Block 4.1: Initialization of the various plugins.

    +
    public static void main(String[] args) throws IOException {
    +    if (args.length == 0) {
    +        throw new RuntimeException("One output directory argument is required");
    +    }
    +    Path outputDirectory = Paths.get(args[0]);
    +    if (!Files.exists(outputDirectory)) {
    +        Files.createDirectory(outputDirectory);
    +    } else {
    +        if (!Files.isDirectory(outputDirectory)) {
    +            throw new IOException("Provided path is not a directory");
    +        }
    +    }
    +
    +    Plugin personPlugin = PersonPlugin.getPersonPlugin();
    +
    +    Plugin vaccinePlugin = VaccinePlugin.getVaccinePlugin();
    +
    +    Plugin modelPlugin = ModelPlugin.getModelPlugin();
    +
    +    WellState wellState = WellState.builder().setSeed(452363456L).build();
    +    StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder().setMainRNGState(wellState)
    +            .build();
    +    Plugin stochasticsPlugin = StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData);
    +
    +    FamilyPluginData familyPluginData = FamilyPluginData.builder()//
    +            .setFamilyCount(30)//
    +            .setMaxFamilySize(5)//
    +            .build();
    +    Plugin familyPlugin = FamilyPlugin.getFamilyPlugin(familyPluginData);
    +
    +
    +

    Code Block 4.2 continues with the association of report labels with specific report files. Recall that reports generally use a unique report label and mark each report item with that label. The three reports are added to the simulation by the vaccine plugin.

    +
    +
    +

    Code Block 4.2: The three reports in this experiment each produce report items and release them as output. The NIOReportItemHandler is initialized here by indicating the file associated with each report.

    +
    NIOReportItemHandler nioReportItemHandler = NIOReportItemHandler.builder()//
    +        .addReport(ModelLabel.FAMILY_VACCINE_REPORT, outputDirectory.resolve("family_vaccine_report.xls"))//
    +        .addReport(ModelLabel.HOURLY_VACCINE_REPORT, outputDirectory.resolve("hourly_vaccine_report.xls"))//
    +        .addReport(ModelLabel.STATELESS_VACCINE_REPORT, outputDirectory.resolve("stateless_vaccine_report.xls"))//
    +        .build();
    +
    +
    +

    Each report label is now associated with a particular file path. Although each file is a tab-delimited text file, we use the .xls file extension so that they can be automatically opened as a spreadsheet. Had we skipped adding these last specifications, the report items would flow out of the simulation and into the experiment but would not find an associated file and thus be ignored.

    +

    Finally, in Code Block 4.3 and Code Block 4.4, we create a single experiment dimension that will override the maximum family size with four values and thus create four scenarios for the experiment.

    +
    +
    +

    Code Block 4.3: The experiment is executed using the NIOReportItemHandler as an experiment output consumer.

    +
    Dimension familySizeDimension = getFamilySizeDimension();
    +
    +Experiment.builder()//
    +        .addPlugin(vaccinePlugin)//
    +        .addPlugin(familyPlugin)//
    +        .addPlugin(personPlugin)//
    +        .addPlugin(modelPlugin)//
    +        .addPlugin(stochasticsPlugin)//
    +        .addDimension(familySizeDimension)//
    +        .addExperimentContextConsumer(nioReportItemHandler)//
    +        .build()//
    +        .execute();
    +
    +
    +
    +
    +

    Code Block 4.4: The family dimension set the maximum family size to four values.

    +
    private static Dimension getFamilySizeDimension() {
    +    FunctionalDimension.Builder builder = FunctionalDimension.builder();//
    +
    +    List<Integer> maxFamilySizes = new ArrayList<>();
    +
    +    maxFamilySizes.add(3);
    +    maxFamilySizes.add(5);
    +    maxFamilySizes.add(7);
    +    maxFamilySizes.add(10);
    +
    +    for (Integer maxFamilySize : maxFamilySizes) {
    +        builder.addLevel((context) -> {
    +            FamilyPluginData.Builder pluginDataBuilder = context
    +                    .getPluginDataBuilder(FamilyPluginData.Builder.class);
    +            pluginDataBuilder.setMaxFamilySize(maxFamilySize);
    +
    +            ArrayList<String> result = new ArrayList<>();
    +            result.add(Double.toString(maxFamilySize));
    +
    +            return result;
    +        });//
    +    }
    +
    +    builder.addMetaDatum("max_family_size");//
    +
    +    return builder.build();
    +
    +}
    +
    +
    +
    +
    +

    4.6 The Family Vaccine Report

    +

    The first report documents the changes in the number of families that are vaccinated over time as individual people receive the vaccine. The field headers for the report are:

    +
      +
    • scenario – the id of the scenario
    • +
    • max_family_size – the maximum family size dictated by the scenario
    • +
    • time – the time in days for each item in the report
    • +
    • unvacinated_families – the number of families that have no members vaccinated
    • +
    • partially_vaccinated_families – the number of families that have at least one, but not all members vaccinated
    • +
    • fully_vaccinated_families – the number of families that have all members vaccinated
    • +
    • unvaccinated_individuals – the number of people who are unvaccinated and have no family assignment
    • +
    • vaccinated_individuals – the number of people who are vaccinated and have no family assignment
    • +
    +

    The experiment report mechanisms are responsible for reporting the scenario and the max_family_size fields since they are part of the experiment design. The remaining fields are contributed by the report. Note that family membership is not guaranteed and that some people may not be associated with any family id. The report accounts for these people in the last two fields.

    +

    There are four events that drive the report:

    +
      +
    1. the addition of a person to the simulation
    2. +
    3. the addition of a family to the simulation
    4. +
    5. the assignment of a person to a family
    6. +
    7. the vaccination of a person
    8. +
    +

    Note that the model logic does not allow for the removal of a person from the simulation, the removal of person from a family or loss of vaccination coverage for a person. In a more nuanced model, there would likely be more events that would influence the report.

    +

    The FamilyVaccineReport has several private fields and classes for maintaining the five counts of the reports. In Code Block 4.5 we have two convenience enumerations for families and individuals that help with the creation of the report header and with maintaining counts.

    +
    +
    +

    Code Block 4.5: The family vaccine report defines two enums for the vaccination status of families and individuals.

    +
    private static enum FamilyVaccineStatus {
    +    NONE("unvacinated_families"), //
    +    PARTIAL("partially_vaccinated_families"), //
    +    FULL("fully_vaccinated_families");//
    +
    +    private final String description;
    +
    +    private FamilyVaccineStatus(final String description) {
    +        this.description = description;
    +    }
    +}
    +
    +private static enum IndividualVaccineStatus {
    +    NONE("unvaccinated_individuals"), //
    +    FULL("vaccinated_individuals");//
    +
    +    private final String description;
    +
    +    private IndividualVaccineStatus(final String description) {
    +        this.description = description;
    +    }
    +}
    +
    +
    +

    Code Block 4.6 shows the remaining private fields.

    +
      +
    • report id – remains fixed from construction and is used to mark every report item
    • +
    • reportHeader – is constructed once and used in the construction of every report item
    • +
    • actorContext – a convenience reference kept by the actor to retrieve the simulation time
    • +
    • vaccinationDataManager – a convenience reference to retrieve the vaccination status of each person
    • +
    • familyDataManager – a convenience reference to retrieve the family members associated with a given person who has just been vaccinated
    • +
    • statusToFamiliesMap – a map from family vaccine status to a mutable counter
    • +
    • familyToStatusMap – a map for recording the current family vaccine status for each family
    • +
    • statusToIndividualMap – a map from individual vaccine status to a mutable counter
    • +
    • individualToStatusMap – a map for recording the current individual vaccine status for each person not assigned to a family
    • +
    +
    +
    +

    Code Block 4.6: The family vaccine report collects summary data as events unfold and requires a few private data structures to record these events.

    +
    private final ReportLabel reportLabel;
    +
    +private ReportHeader reportHeader;
    +
    +private ReportContext reportContext;
    +
    +private VaccinationDataManager vaccinationDataManager;
    +
    +private FamilyDataManager familyDataManager;
    +
    +private final Map<FamilyVaccineStatus, MutableInteger> statusToFamiliesMap = new LinkedHashMap<>();
    +
    +private final Map<FamilyId, FamilyVaccineStatus> familyToStatusMap = new LinkedHashMap<>();
    +
    +private final Map<IndividualVaccineStatus, MutableInteger> statusToIndividualsMap = new LinkedHashMap<>();
    +
    +private final Map<PersonId, IndividualVaccineStatus> individualToStatusMap = new LinkedHashMap<>();
    +
    +
    +

    The report’s methods start with its constructor in Code Block 4.7. The report label is recorded and the report header field is built from the support enumerations.

    +
    +
    +

    Code Block 4.7: The report initializes its data structures.

    +
    public FamilyVaccineReport(final ReportLabel reportLabel) {
    +    this.reportLabel = reportLabel;
    +
    +    final ReportHeader.Builder builder = ReportHeader.builder();
    +    builder.add("time");
    +    for (final FamilyVaccineStatus familyVaccineStatus : FamilyVaccineStatus.values()) {
    +        builder.add(familyVaccineStatus.description);
    +    }
    +    for (final IndividualVaccineStatus individualVaccineStatus : IndividualVaccineStatus.values()) {
    +        builder.add(individualVaccineStatus.description);
    +    }
    +    reportHeader = builder.build();
    +}
    +
    +
    +

    Next is the initialization method that was passed to the simulation. This is invoked by the simulation just once at the begining of time flow and gives the report a chance to register for events and to initialize the private fields from Code Block 4.6. The report records the actor context and subscribes to the four events of interest in Code Block 4.8. These subscriptions reference local private methods that will be discussed later.

    +
    +
    +

    Code Block 4.8: The report must subscribe to the events that are pertinent to reporting individual and family vaccination status.

    +
    public void init(final ReportContext reportContext) {
    +    this.reportContext = reportContext;
    +    /*
    +         * Subscribe to all the relevant events
    +         */
    +    reportContext.subscribe(VaccinationEvent.class, this::handleVaccinationEvent);
    +    reportContext.subscribe(FamilyAdditionEvent.class, this::handleFamilyAdditionEvent);
    +    reportContext.subscribe(FamilyMemberShipAdditionEvent.class, this::handleFamilyMemberShipAdditionEvent);
    +    reportContext.subscribe(PersonAdditionEvent.class, this::handlePersonAdditionEvent);
    +
    +
    +

    In Code Block 4.9 we continue with the retrieval of the person, family and vaccination data managers. The maps containing the counts are initialized to zero.

    +
    +
    +

    Code Block 4.9: The local data structures are initialized from the current vaccine states.

    +
    familyDataManager = reportContext.getDataManager(FamilyDataManager.class);
    +vaccinationDataManager = reportContext.getDataManager(VaccinationDataManager.class);
    +PersonDataManager personDataManager = reportContext.getDataManager(PersonDataManager.class);
    +
    +for (final FamilyVaccineStatus familyVaccineStatus : FamilyVaccineStatus.values()) {
    +    statusToFamiliesMap.put(familyVaccineStatus, new MutableInteger());
    +}
    +
    +for (final IndividualVaccineStatus individualVaccineStatus : IndividualVaccineStatus.values()) {
    +    statusToIndividualsMap.put(individualVaccineStatus, new MutableInteger());
    +}
    +
    +
    +

    Code Block 4.10 and Code Block 4.11 use the data managers to fill the count structures with the current state of the population.

    +
    +
    +

    Code Block 4.10: Determining vaccine status for each family.

    +
    for (final FamilyId familyId : familyDataManager.getFamilyIds()) {
    +
    +    final int familySize = familyDataManager.getFamilySize(familyId);
    +    final List<PersonId> familyMembers = familyDataManager.getFamilyMembers(familyId);
    +    int vaccinatedCount = 0;
    +    for (final PersonId personId : familyMembers) {
    +        if (vaccinationDataManager.isPersonVaccinated(personId)) {
    +            vaccinatedCount++;
    +        }
    +    }
    +    FamilyVaccineStatus status;
    +
    +    if (vaccinatedCount == 0) {
    +        status = FamilyVaccineStatus.NONE;
    +    } else if (vaccinatedCount == familySize) {
    +        status = FamilyVaccineStatus.FULL;
    +    } else {
    +        status = FamilyVaccineStatus.PARTIAL;
    +    }
    +
    +    statusToFamiliesMap.get(status).increment();
    +    familyToStatusMap.put(familyId, status);
    +
    +}
    +
    +
    +
    +
    +

    Code Block 4.11: Capturing individuals who have no family association.

    +
    for (final PersonId personId : personDataManager.getPeople()) {
    +    if (familyDataManager.getFamilyId(personId).isEmpty()) {
    +
    +        IndividualVaccineStatus status;
    +        if (vaccinationDataManager.isPersonVaccinated(personId)) {
    +            status = IndividualVaccineStatus.FULL;
    +        } else {
    +            status = IndividualVaccineStatus.NONE;
    +        }
    +        statusToIndividualsMap.get(status).increment();
    +        individualToStatusMap.put(personId, status);
    +    }
    +}
    +
    +
    +

    Initialization finishes with the release of a single report item that summarizes the state of family vaccination at time zero.

    +
    +
    +

    Code Block 4.12: The initial state of the report is released as a single report item.

    +
    releaseReportItem();
    +
    +
    +

    The methods for handling each event are shown in Code Block 4.13. All four methods select some relevant family id or person id and process changes to the counting data structures using the refreshFamilyStatus() and refreshInidividual() methods. The accounting for reports that are synthesizing multiple events can be somewhat tricky. No assumptions are made as to how people are created, vaccinated and added to families so that changes to those processes in future versions of the model do not cause errors in the report.

    +
    +
    +

    Code Block 4.13: The report will need to have handlers for each of the subscribed events.

    +
    private void handleFamilyAdditionEvent(final ReportContext reportContext,
    +        final FamilyAdditionEvent familyAdditionEvent) {
    +    refreshFamilyStatus(familyAdditionEvent.getFamilyId());
    +}
    +
    +private void handleFamilyMemberShipAdditionEvent(final ReportContext reportContext,
    +        final FamilyMemberShipAdditionEvent familyMemberShipAdditionEvent) {
    +    individualToStatusMap.remove(familyMemberShipAdditionEvent.getPersonId());
    +    refreshFamilyStatus(familyMemberShipAdditionEvent.getFamilyId());
    +}
    +
    +private void handlePersonAdditionEvent(final ReportContext reportContext,
    +        final PersonAdditionEvent personAdditionEvent) {
    +    final PersonId personId = personAdditionEvent.getPersonId();
    +    final Optional<FamilyId> optional = familyDataManager.getFamilyId(personId);
    +    if (optional.isEmpty()) {
    +        refreshIndividualStatus(personId);
    +    } else {
    +        final FamilyId familyId = optional.get();
    +        refreshFamilyStatus(familyId);
    +    }
    +}
    +
    +private void handleVaccinationEvent(final ReportContext reportContext, final VaccinationEvent vaccinationEvent) {
    +    final PersonId personId = vaccinationEvent.getPersonId();
    +
    +    final Optional<FamilyId> optional = familyDataManager.getFamilyId(personId);
    +
    +    if (optional.isEmpty()) {
    +        refreshIndividualStatus(personId);
    +    } else {
    +        final FamilyId familyId = optional.get();
    +        refreshFamilyStatus(familyId);
    +    }
    +}
    +
    +
    +

    The refresh methods in Code Block 4.14 and Code Block 4.15 compare the current vaccination state of the families and individuals against the corresponding states tracked in the counting maps. If a change in the counts has occurred the counts are corrected and a new report item is released.

    +
    +
    +

    Code Block 4.14: Events that effect the status of a family are processed centrally.

    +
    private void refreshFamilyStatus(final FamilyId familyId) {
    +
    +    final int familySize = familyDataManager.getFamilySize(familyId);
    +    final List<PersonId> familyMembers = familyDataManager.getFamilyMembers(familyId);
    +    int vaccinatedCount = 0;
    +    for (final PersonId personId : familyMembers) {
    +        if (vaccinationDataManager.isPersonVaccinated(personId)) {
    +            vaccinatedCount++;
    +        }
    +    }
    +    FamilyVaccineStatus newStatus;
    +
    +    if (vaccinatedCount == 0) {
    +        newStatus = FamilyVaccineStatus.NONE;
    +    } else if (vaccinatedCount == familySize) {
    +        newStatus = FamilyVaccineStatus.FULL;
    +    } else {
    +        newStatus = FamilyVaccineStatus.PARTIAL;
    +    }
    +
    +    final FamilyVaccineStatus currentStatus = familyToStatusMap.get(familyId);
    +    if (currentStatus == newStatus) {
    +        return;
    +    }
    +    if (currentStatus != null) {
    +        statusToFamiliesMap.get(currentStatus).decrement();
    +    }
    +    statusToFamiliesMap.get(newStatus).increment();
    +    familyToStatusMap.put(familyId, newStatus);
    +    releaseReportItem();
    +}
    +
    +
    +
    +
    +

    Code Block 4.15: Events that effect the status of an individual are processed centrally.

    +
    private void refreshIndividualStatus(final PersonId personId) {
    +    IndividualVaccineStatus newStatus;
    +    if (vaccinationDataManager.isPersonVaccinated(personId)) {
    +        newStatus = IndividualVaccineStatus.FULL;
    +    } else {
    +        newStatus = IndividualVaccineStatus.NONE;
    +    }
    +
    +    final IndividualVaccineStatus currentStatus = individualToStatusMap.get(personId);
    +
    +    if (currentStatus == newStatus) {
    +        return;
    +    }
    +
    +    if (currentStatus != null) {
    +        statusToIndividualsMap.get(currentStatus).decrement();
    +    }
    +    statusToIndividualsMap.get(newStatus).increment();
    +    individualToStatusMap.put(personId, newStatus);
    +    releaseReportItem();
    +}
    +
    +
    +

    Releasing the report items that summarizes the family vaccination counts requires building a new report item with the fixed report label and report header values determined in the constructor. We then go on to add the time and count values in the order dictated by the helper enumerations so that they follow the header values established in the report header. Once the report item is complete it is released as output via the report context. The simulation will in turn release the report item to the experiment where it will be distributed to the NIOReportItemHandler and then on the specific file manager(s) that record the items.

    +
    +
    +

    Code Block 4.16: Each time a family or individual have a relevant change a report item is released.

    +
    private void releaseReportItem() {
    +    final ReportItem.Builder builder = ReportItem.builder().setReportLabel(reportLabel)
    +            .setReportHeader(reportHeader);
    +    builder.addValue(reportContext.getTime());
    +    for (final FamilyVaccineStatus familyVaccineStatus : statusToFamiliesMap.keySet()) {
    +        MutableInteger mutableInteger = statusToFamiliesMap.get(familyVaccineStatus);
    +        builder.addValue(mutableInteger.getValue());
    +    }
    +    for (final IndividualVaccineStatus individualVaccineStatus : statusToIndividualsMap.keySet()) {
    +        MutableInteger mutableInteger = statusToIndividualsMap.get(individualVaccineStatus);
    +        builder.addValue(mutableInteger.getValue());
    +    }
    +    final ReportItem reportItem = builder.build();
    +    reportContext.releaseOutput(reportItem);
    +}
    +
    +
    +

    The resulting output in Figure 4.1 contains the four scenarios showing the buildup of the population with all families and individuals being unvaccinated. Over time the number of vaccinated families increase and each simulation ends when all people have been vaccinated. The increase of max family size over the experiment causes there to be more people and thus the number of days to reach full vaccination also increases as expected.

    +
    +
    +
    +

    Figure 4.1: Excerpt of the family vaccine report.

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +scenario + +max_family_size + +time + +unvacinated_families + +partially_vaccinated_families + +fully_vaccinated_families + +unvaccinated_individuals + +vaccinated_individuals +
    +0 + +3.0 + +0.0 + +0 + +0 + +0 + +0 + +0 +
    +0 + +3.0 + +0.0 + +1 + +0 + +0 + +0 + +0 +
    +0 + +3.0 + +0.0 + +1 + +0 + +0 + +1 + +0 +
    +0 + +3.0 + +0.0 + +1 + +0 + +0 + +2 + +0 +
    +0 + +3.0 + +0.0 + +2 + +0 + +0 + +2 + +0 +
    +… + + + + + + + +
    +0 + +3.0 + +0.0 + +15 + +0 + +0 + +30 + +0 +
    +0 + +3.0 + +0.0 + +16 + +0 + +0 + +30 + +0 +
    +0 + +3.0 + +0.0 + +16 + +0 + +0 + +31 + +0 +
    +0 + +3.0 + +0.0 + +16 + +0 + +0 + +32 + +0 +
    +0 + +3.0 + +0.0 + +17 + +0 + +0 + +32 + +0 +
    +… + + + + + + + +
    +0 + +3.0 + +1.0603090039611633 + +28 + +2 + +0 + +63 + +0 +
    +0 + +3.0 + +1.114413651330966 + +27 + +3 + +0 + +63 + +0 +
    +0 + +3.0 + +1.1516487861564502 + +26 + +4 + +0 + +63 + +0 +
    +0 + +3.0 + +1.1871612468129367 + +25 + +5 + +0 + +63 + +0 +
    +0 + +3.0 + +1.2426374003057261 + +25 + +4 + +1 + +63 + +0 +
    +… + + + + + + + +
    +3 + +10.0 + +8.981789652547315 + +0 + +4 + +26 + +163 + +3 +
    +3 + +10.0 + +9.047774924066472 + +0 + +3 + +27 + +163 + +3 +
    +3 + +10.0 + +9.101648563188967 + +0 + +2 + +28 + +163 + +3 +
    +3 + +10.0 + +9.18260688675053 + +0 + +1 + +29 + +163 + +3 +
    +3 + +10.0 + +9.210064962863902 + +0 + +0 + +30 + +163 + +3 +
    +
    +
    +
    +
    +
    +
    +

    4.7 Periodic Reports

    +

    Producing a new report item each time a relevant event changes the internal tracking variable of a report actor will often produce too much output. An alternative is to periodically release one or more report items, usually on an hourly or daily basis. The reports plugin defines an abstract report class, the PeriodicReport, that manages the periodic flushing of the state of the report. This allows descendant report classes to concentrate on responding to events while leaving the periodic production of report items to the base class.

    +

    The PeriodicReport defines a constructor that requires both a report label and a reporting period. If the constructor is overridden, the super() constructor must be invoked. The init() method is declared final in the PeriodicReport class and the descendant report class should implement the prepare() method to conduct initialization. Several protected methods are introduced:

    +
      +
    • prepare is called by the init() method of the periodic report, it provides the descendant report class with an opportunity to initialize
    • +
    • getReportLabel and getReportPeriod retrieve the label and report period passed in construction
    • +
    • addTimeFieldHeaders is used to help create the report header
    • +
    • fillTimeFields is used to help create report items
    • +
    • flush is an abstract method for flushing the content of the report actor that must be implemented by the descendant report class
    • +
    +

    Our next example report is the HourlyVaccineReport that descends from the PeriodicReport. It produces the same output as the FamilyVaccineReport, but does so on an hourly basis. This outputs a report item every hour whether or not there were stimulating events. The implementation of this report is nearly identical to the previous report and we will concentrate on highlighting the differences between the two approaches.

    +

    In Code Block 4.17 we see that the constructor invokes the super constructor. The construction of the report header is aided by the protected method addTimeFieldHeaders() which should be invoked as the first inputs to the report header builder. Note as well that we do not store the report label locally.

    +
    +
    +

    Code Block 4.17: The hourly vaccine report covers the same content as the family vaccine report. Rather than report events as they happen, it instead periodically summarizes these events.

    +
    public HourlyVaccineReport(ReportLabel reportLabel, ReportPeriod reportPeriod) {
    +    super(reportLabel, reportPeriod);
    +
    +    ReportHeader.Builder builder = ReportHeader.builder();
    +    addTimeFieldHeaders(builder);
    +    for (FamilyVaccineStatus familyVaccineStatus : FamilyVaccineStatus.values()) {
    +        builder.add(familyVaccineStatus.description);
    +    }
    +    for (IndividualVaccineStatus individualVaccineStatus : IndividualVaccineStatus.values()) {
    +        builder.add(individualVaccineStatus.description);
    +    }
    +    reportHeader = builder.build();
    +}
    +
    +
    +

    The prepare() method is nearly identical to the previous report’s init() method.

    +
    +
    +

    Code Block 4.18: The same subscriptions are created as before.

    +
    protected void prepare(ReportContext reportContext) {
    +
    +    /*
    +         * Subscribe to all the relevant events
    +         */     
    +
    +    reportContext.subscribe(VaccinationEvent.class, this::handleVaccinationEvent);
    +    reportContext.subscribe(FamilyAdditionEvent.class, this::handleFamilyAdditionEvent);
    +    reportContext.subscribe(FamilyMemberShipAdditionEvent.class, this::handleFamilyMemberShipAdditionEvent);
    +    reportContext.subscribe(PersonAdditionEvent.class, this::handlePersonAdditionEvent);
    +
    +
    +

    The releaseReportItem() method of the previous report is now replaced by the flush() method override in Code Block 4.19.

    +
    +
    +

    Code Block 4.19: Once an hour the report releases a report item that summarizes the family and individual vaccine status.

    +
    protected void flush(ReportContext reportContext) {
    +    ReportItem.Builder builder = ReportItem.builder()//
    +            .setReportLabel(getReportLabel())//
    +            .setReportHeader(reportHeader);
    +    fillTimeFields(builder);
    +    for (FamilyVaccineStatus familyVaccineStatus : statusToFamiliesMap.keySet()) {
    +        MutableInteger mutableInteger = statusToFamiliesMap.get(familyVaccineStatus);
    +        builder.addValue(mutableInteger.getValue());
    +    }
    +    for (IndividualVaccineStatus individualVaccineStatus : statusToIndividualsMap.keySet()) {
    +        MutableInteger mutableInteger = statusToIndividualsMap.get(individualVaccineStatus);
    +        builder.addValue(mutableInteger.getValue());
    +    }
    +    ReportItem reportItem = builder.build();
    +    reportContext.releaseOutput(reportItem);
    +}
    +
    +
    +

    The corresponding invocations of the releaseReportItem() that would have generated a new report item each time an event changed the internal counting variables are dropped. The flush() method will be invoked each time the parent report class determines that the planned next period has occurred. Note also that the time fields of the report item are filled by invoking the fillTimeFields() method which will add the correct time value for the period being reported rather than the current time. Otherwise, the implementations are identical.

    +

    The resulting output in Figure 4.2 contains the four scenarios showing the buildup of the population with all families and individuals being unvaccinated. It shows the same overall pattern as the previous report, but treats the reporting of time in integer days and hours. Note that some of the output values repeat over the days and hours since there were no vaccinations during those periods.

    +
    +
    +
    +

    Figure 4.2: Excerpt from the hourly vaccine report.

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +scenario + +max_family_size + +day + +hour + +unvacinated_families + +partially_vaccinated_families + +fully_vaccinated_families + +unvaccinated_individuals + +vaccinated_individuals +
    +0 + +3.0 + +0 + +0 + +0 + +0 + +0 + +0 + +0 +
    +0 + +3.0 + +0 + +1 + +30 + +0 + +0 + +63 + +0 +
    +0 + +3.0 + +0 + +2 + +30 + +0 + +0 + +63 + +0 +
    +0 + +3.0 + +0 + +3 + +30 + +0 + +0 + +63 + +0 +
    +0 + +3.0 + +0 + +4 + +30 + +0 + +0 + +63 + +0 +
    +… + + + + + + + + +
    +0 + +3.0 + +2 + +11 + +9 + +17 + +4 + +62 + +1 +
    +0 + +3.0 + +2 + +12 + +9 + +17 + +4 + +62 + +1 +
    +0 + +3.0 + +2 + +13 + +8 + +17 + +5 + +62 + +1 +
    +0 + +3.0 + +2 + +14 + +8 + +17 + +5 + +62 + +1 +
    +0 + +3.0 + +2 + +15 + +8 + +17 + +5 + +62 + +1 +
    +… + + + + + + + + +
    +2 + +7.0 + +3 + +3 + +8 + +21 + +1 + +128 + +1 +
    +2 + +7.0 + +3 + +4 + +8 + +21 + +1 + +128 + +1 +
    +2 + +7.0 + +3 + +5 + +8 + +21 + +1 + +128 + +1 +
    +2 + +7.0 + +3 + +6 + +8 + +21 + +1 + +128 + +1 +
    +2 + +7.0 + +3 + +7 + +8 + +21 + +1 + +128 + +1 +
    +… + + + + + + + + +
    +3 + +10.0 + +9 + +2 + +0 + +3 + +27 + +163 + +3 +
    +3 + +10.0 + +9 + +3 + +0 + +2 + +28 + +163 + +3 +
    +3 + +10.0 + +9 + +4 + +0 + +2 + +28 + +163 + +3 +
    +3 + +10.0 + +9 + +5 + +0 + +1 + +29 + +163 + +3 +
    +3 + +10.0 + +9 + +6 + +0 + +0 + +30 + +163 + +3 +
    +
    +
    +
    +
    +

    Our final example, the StatelessVaccineReport , Code Block 4.20, continues from the HourlyVaccineReport but eschews the stateful counting mechanisms. Like the previous report, it is a periodic report actor but it does not store any state and does not subscribe to any events. Instead, it simply derives the report item on each flush() invocation.

    +
    +
    +

    Code Block 4.20: The stateless vaccine report does not process any events. Instead, it periodically derives the report item by polling the relevant data managers.

    +
    protected void flush(ReportContext reportContext) {
    +
    +    FamilyDataManager familyDataManager = reportContext.getDataManager(FamilyDataManager.class);
    +    VaccinationDataManager vaccinationDataManager = reportContext.getDataManager(VaccinationDataManager.class);
    +    PersonDataManager personDataManager = reportContext.getDataManager(PersonDataManager.class);
    +
    +    Map<VaccineStatus, MutableInteger> statusMap = new LinkedHashMap<>();
    +    for (VaccineStatus vaccineStatus : VaccineStatus.values()) {
    +        statusMap.put(vaccineStatus, new MutableInteger());
    +    }
    +
    +    // determine the family vaccine status for every family
    +    for (FamilyId familyId : familyDataManager.getFamilyIds()) {
    +        VaccineStatus vaccineStatus = getFamilyStatus(familyId, vaccinationDataManager, familyDataManager);
    +        statusMap.get(vaccineStatus).increment();
    +    }
    +
    +    // ensure that any person not assigned to a family is still counted
    +    for (PersonId personId : personDataManager.getPeople()) {
    +        if (familyDataManager.getFamilyId(personId).isEmpty()) {
    +            VaccineStatus vaccineStatus = getIndividualStatus(personId, vaccinationDataManager);
    +            statusMap.get(vaccineStatus).increment();
    +        }
    +    }
    +    ReportHeader.Builder headerBuilder = ReportHeader.builder();
    +    addTimeFieldHeaders(headerBuilder);
    +    for (VaccineStatus vaccineStatus : VaccineStatus.values()) {
    +        headerBuilder.add(vaccineStatus.description);
    +    }
    +    ReportHeader reportHeader = headerBuilder.build();
    +
    +    ReportItem.Builder builder = ReportItem.builder()//
    +            .setReportLabel(getReportLabel())//
    +            .setReportHeader(reportHeader);
    +    fillTimeFields(builder);
    +    for (VaccineStatus vaccineStatus : VaccineStatus.values()) {
    +        int value = statusMap.get(vaccineStatus).getValue();
    +        builder.addValue(value);
    +    }
    +
    +    ReportItem reportItem = builder.build();
    +    reportContext.releaseOutput(reportItem);
    +
    +}
    +
    +
    +

    This approach may seem wasteful since there is the potential for a great deal of recalculation, but since this is done on a daily basis, it may be well worth the reduction in memory if the report was actively tracking millions of families.

    + + +
    + +
    + + +
    + + + + \ No newline at end of file diff --git a/doc/ch05-Properties.html b/doc/ch05-Properties.html new file mode 100644 index 000000000..f1f11fd03 --- /dev/null +++ b/doc/ch05-Properties.html @@ -0,0 +1,404 @@ + + + + + + + + + +GCM-Docs - 5  Properties + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    + +
    + + + + + +
    + +
    +
    +

    5  Properties

    +
    + + + +
    + + + + +
    + + +
    + +
    + +
    +

    Modelers often need to associate properties with concepts found in plugins. For example, you may want to associate an integer number of times a person has been given a vaccine booster or have a double valued disease detection threshold that can be defined regionally. Most plugins will have a flexible, modeler-defined, set of properties that can be associated with people, groups or any other concept defined by the plugin. The core plugins included with GCM use a common property utility that introduces property Ids, property definitions and property values. It also provides several property value container classes to aid in efficient storage and retrieval.

    +
    +

    5.1 Property Identifiers

    +

    Property Ids are generally marker interfaces used to force unambiguous types in method signatures dealing with property related concepts. Each plugin that uses properties will introduce its own marker interface(s) and instances of the identifier are left to client (other plugins) to implement. This is often accomplished with enumerations.

    +
    +
    +

    5.2 Property Definitions

    +

    Property definitions supply each plugin with:

    +
      +
    • A class reference that defines the type of the property values
    • +
    • A Boolean value indicating if property values are mutable
    • +
    • An optional default property value
    • +
    +

    The mutability indicator controls whether property values can be set after the initial value is established. For example, consider the integer property “age” that is defined for people. Each person has a distinct integer age upon initial value assignment. If the property definition asserts that the property is not mutable, then the age value cannot be changed during the simulation’s execution. This is often used to fix global property values so that there is no chance that they can be reset by mistake.

    +

    It is often useful to know when a property was last assigned. Some plugins will introduce tracking of property value assignment times based on a policy per property id to avoid recording such time values where there would be tens of millions of entries and no use of these values by the modeler.

    +

    Default property values are used to spare the modeler from having to set property values when introducing new items to the simulation. For example, when adding a person to the simulation it might be useful to have a default of false for the property of “vaccinated”. However, for some properties there may be no meaningful default value. For example, consider the “age” property for a person. What would constitute a good default value? For this reason, supplying a default value as part of the property definition is optional. Property values in GCM are never null. If a property definition does not supply a default value, then all assoicated property values must be explicitly set to non-null values.

    +
    +
    +

    5.3 Concurrency Requirements

    +

    Property ids, property definitions and property values must be thread safe since they are shared across multiple scenarios (different simulation instances). It is usually best practice if they are implemented as immutable classes.

    +
      +
    • Property ids are usually marker interfaces and are often implemented by static enumerations and are thus generally threadsafe
    • +
    • The PropertyDefinition class is provided by the utility and is threadsafe subject to the thread safety of its default value
    • +
    • Property values are often boxed primitives and are generally threadsafe. In general, mutation of a property value in GCM does not mean that the property value is mutated. Rather, it usually means that a new immutable value is now associated with the property id.
    • +
    +
    +
    +

    5.4 Immutability

    +

    For a class to be immutable in Java it must meet three requirements:

    +
      +
    1. Its internal fields must not be mutated. There can be no setter methods or any other mechanism that changes an assignment post construction
    2. +
    3. All fields are declared final
    4. +
    5. No reference to the immutable object may be passed during its construction
    6. +
    +
    +
    +

    5.5 Expected Behaviors of Plugins using properties

    +

    All implementations of property mechanisms in GCM are expected to meet the following requirements:

    +
      +
    • Property values are never null
    • +
    • Property definitions that do not supply a default value must be supported by other mechanisms that ensure that property values are never null
    • +
    • Property instance values must always be assignment compatible to their corresponding property definition’s property type reference
    • +
    + + +
    + +
    + + +
    + + + + \ No newline at end of file diff --git a/doc/ch06-GlobalPropertiesPlugin.html b/doc/ch06-GlobalPropertiesPlugin.html new file mode 100644 index 000000000..d359dbf8a --- /dev/null +++ b/doc/ch06-GlobalPropertiesPlugin.html @@ -0,0 +1,1075 @@ + + + + + + + + + +GCM-Docs - 6  Global Properties Plugin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    + +
    + + + + + +
    + +
    +
    +

    6  Global Properties Plugin

    +
    + + + +
    + + + + +
    + + +
    + +
    + +
    +

    The global property plugin implements a flexible property system for properties that have global scope. Specifically, global properties have no association with a specific person, place or other instance-based concept.

    +
    +

    6.1 Plugin Data Initialization

    +

    The plugin is initialized using a GlobalPropertiesPluginData object that collects global property definitions and global property values. Even though the property definitions can contain default property values, the ability to set property values is included to add some flexibility to the collection process since the client model may separate definitions from values in its input files.

    +
    +
    +

    6.2 Plugin Behavior

    +

    The plugin adds a single data manager to the simulation as an instance of the GlobalPropertiesDataManager that is initialized with the GlobalPropertiesPluginData.

    +
    +
    +

    6.3 Data Manager

    +

    The data manager provides access to the global properties and provides the ability to:

    +
      +
    • Define new global properties (not contained in the initial data)
    • +
    • Retrieve global property definitions
    • +
    • Retrieve global property ids
    • +
    • Retrieve global property values and the times when they were set
    • +
    • Set global property values
    • +
    +

    The data manager also produces observable events when a new global property is defined or when a global property value is assigned. The plugin provides the GlobalPropertyReport that subscribes to these events and produces a trace report of property value assignments.

    +
    +
    +

    6.4 Example Code (Lesson 13)

    +

    Example_13.java shows a simple usage of the global properties plugin. In it we will add three double valued properties: ALPHA, BETA, and GAMMA. ALPHA and BETA will be used to vary the scenarios in the experiment and GAMMA will be set to a simple function of ALPHA and BETA that will change over time in the simulation. This will culminate in a report that shows each time the global variables are defined or their values are set.

    +

    The example includes two plugins:

    +
      +
    • Global properties plugin – (GCM core plugin) used to manage the properties
    • +
    • Model plugin – (local plugin) used to introduce a single actor that will alter the value of GAMMA over time
    • +
    +

    The example’s main method in Code Block 6.1 adds the two plugins:

    +
      +
    • Global properties plugin +
        +
      • initialized with the three global properties
      • +
      • adds the GlobalPropertyReport
      • +
    • +
    • Model plugin +
        +
      • Uses no inputs, but will add a single instance of the GammaActor class
      • +
    • +
    +

    The main method continues by associating the report to a file via the NIOReportItemHandler. It then forms a dimension for the experiment from variant values of ALPHA and BETA. Finally, it executes the experiment.

    +
    +
    +

    Code Block 6.1: Using the global properties plugin to add three global properties.

    +
    public static void main(String[] args) throws IOException {
    +    if (args.length == 0) {
    +        throw new RuntimeException("One output directory argument is required");
    +    }
    +    Path outputDirectory = Paths.get(args[0]);
    +    if (!Files.exists(outputDirectory)) {
    +        Files.createDirectory(outputDirectory);
    +    } else {
    +        if (!Files.isDirectory(outputDirectory)) {
    +            throw new IOException("Provided path is not a directory");
    +        }
    +    }
    +
    +    GlobalPropertiesPluginData globalPropertiesPluginData = getGlobalPropertiesPluginData();
    +
    +    GlobalPropertyReportPluginData globalPropertyReportPluginData = GlobalPropertyReportPluginData.builder()//
    +            .setReportLabel(ModelReportLabel.GLOBAL_PROPERTY_REPORT)//
    +            .setDefaultInclusion(true)//
    +            .build();
    +
    +    Plugin globalPropertiesPlugin = GlobalPropertiesPlugin.builder()
    +            .setGlobalPropertiesPluginData(globalPropertiesPluginData)
    +            .setGlobalPropertyReportPluginData(globalPropertyReportPluginData)//
    +            .getGlobalPropertiesPlugin();
    +
    +    Plugin modelPlugin = ModelPlugin.getModelPlugin();
    +
    +    NIOReportItemHandler nioReportItemHandler = //
    +            NIOReportItemHandler.builder()//
    +                    .addReport(ModelReportLabel.GLOBAL_PROPERTY_REPORT, //
    +                            outputDirectory.resolve("global property report.xls"))//
    +                    .build();
    +
    +    Dimension alphaBetaDimension = getAlphaBetaDimension();
    +
    +    Experiment.builder()//
    +            .addPlugin(globalPropertiesPlugin)//
    +            .addPlugin(modelPlugin)//
    +            .addExperimentContextConsumer(nioReportItemHandler)//
    +            .addDimension(alphaBetaDimension)//
    +            .build()//
    +            .execute();//
    +
    +}
    +
    +
    +

    Initialization of the global properties is shown in Code Block 6.2.

    +
    +
    +

    Code Block 6.2: Initializing the global properties plugin data with three property definitions.

    +
    private static GlobalPropertiesPluginData getGlobalPropertiesPluginData() {
    +    GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder();//
    +
    +    PropertyDefinition propertyDefinition = PropertyDefinition.builder()//
    +            .setType(Double.class)//
    +            .setDefaultValue(2.0)//
    +            .setPropertyValueMutability(false)//
    +            .build();
    +    builder.defineGlobalProperty(GlobalProperty.ALPHA, propertyDefinition, 0);
    +
    +    propertyDefinition = PropertyDefinition.builder()//
    +            .setType(Double.class)//
    +            .setDefaultValue(5.0)//
    +            .setPropertyValueMutability(false)//
    +            .build();
    +
    +    builder.defineGlobalProperty(GlobalProperty.BETA, propertyDefinition, 0);
    +
    +    propertyDefinition = PropertyDefinition.builder()//
    +            .setType(Double.class)//
    +            .setDefaultValue(1.0)//
    +            .setPropertyValueMutability(true)//
    +            .build();
    +
    +    builder.defineGlobalProperty(GlobalProperty.GAMMA, propertyDefinition, 0);
    +
    +    return builder.build();
    +}
    +
    +
    +

    Code Block 6.3 shows the construction of the experiment’s single dimension.

    +
    +
    +

    Code Block 6.3: A dimension is created that adds five pairs of values over the ALPHA and BETA global properties.

    +
    private static Dimension getAlphaBetaDimension() {
    +    List<Pair<Double, Double>> alphaBetaPairs = new ArrayList<>();
    +    alphaBetaPairs.add(new Pair<>(3.0, 10.0));
    +    alphaBetaPairs.add(new Pair<>(12.0, 25.0));
    +    alphaBetaPairs.add(new Pair<>(30.0, 40.0));
    +    alphaBetaPairs.add(new Pair<>(45.0, 70.0));
    +    alphaBetaPairs.add(new Pair<>(80.0, 100.0));
    +
    +    FunctionalDimension.Builder dimensionBuilder = FunctionalDimension.builder();
    +
    +    for (Pair<Double, Double> pair : alphaBetaPairs) {
    +        dimensionBuilder.addLevel((c) -> {
    +            List<String> result = new ArrayList<>();
    +            GlobalPropertiesPluginData.Builder builder = c
    +                    .getPluginDataBuilder(GlobalPropertiesPluginData.Builder.class);
    +            builder.setGlobalPropertyValue(GlobalProperty.ALPHA, pair.getFirst(), 0);
    +            builder.setGlobalPropertyValue(GlobalProperty.BETA, pair.getSecond(), 0);
    +            result.add(pair.getFirst().toString());
    +            result.add(pair.getSecond().toString());
    +            return result;
    +        });
    +    }
    +
    +    dimensionBuilder.addMetaDatum(GlobalProperty.ALPHA.toString());
    +    dimensionBuilder.addMetaDatum(GlobalProperty.BETA.toString());
    +
    +    return dimensionBuilder.build();
    +}
    +
    +
    +

    The GammaActor class in Code Block 6.4 schedules 10 plans, set one day apart, to change the GAMMA value as a successive interpolation between the ALPHA and BETA values that are in turn controlled by the experiment.

    +
    +
    +

    Code Block 6.4: The gamma actor sets the value of the GAMMA property over time as a function of the ALPHA and BETA properties.

    +
    public final class GammaActor {
    +
    +    public void init(ActorContext actorContext) {
    +        int count = 10;
    +        IntStream.range(0, count).forEach((i) -> {
    +            actorContext.addPlan((c) -> {
    +                GlobalPropertiesDataManager globalPropertiesDataManager = c
    +                        .getDataManager(GlobalPropertiesDataManager.class);
    +                Double alpha = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.ALPHA);
    +                Double beta = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.BETA);
    +                double gamma = (beta - alpha) * i / count + alpha;
    +                globalPropertiesDataManager.setGlobalPropertyValue(GlobalProperty.GAMMA, gamma);
    +            }, i + 1);
    +        });
    +    }
    +}
    +
    +
    +

    The resultant global properties report shows the correct interpolated values for the five scenarios in Figure 6.1.

    +
    +
    +
    +

    Figure 6.1: An excerpt of the global property report showing the three global property values over time in the five scenarios.

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +scenario + +ALPHA + +BETA + +time + +property + +value +
    +0 + +3.0 + +10.0 + +0.0 + +ALPHA + +3.0 +
    +0 + +3.0 + +10.0 + +0.0 + +BETA + +10.0 +
    +0 + +3.0 + +10.0 + +0.0 + +GAMMA + +1.0 +
    +0 + +3.0 + +10.0 + +1.0 + +GAMMA + +3.0 +
    +0 + +3.0 + +10.0 + +2.0 + +GAMMA + +3.7 +
    +0 + +3.0 + +10.0 + +3.0 + +GAMMA + +4.4 +
    +0 + +3.0 + +10.0 + +4.0 + +GAMMA + +5.1 +
    +0 + +3.0 + +10.0 + +5.0 + +GAMMA + +5.8 +
    +0 + +3.0 + +10.0 + +6.0 + +GAMMA + +6.5 +
    +0 + +3.0 + +10.0 + +7.0 + +GAMMA + +7.2 +
    +… + + + + + +
    +1 + +12.0 + +25.0 + +9.0 + +GAMMA + +22.4 +
    +1 + +12.0 + +25.0 + +10.0 + +GAMMA + +23.7 +
    +2 + +30.0 + +40.0 + +0.0 + +ALPHA + +30.0 +
    +2 + +30.0 + +40.0 + +0.0 + +BETA + +40.0 +
    +2 + +30.0 + +40.0 + +0.0 + +GAMMA + +1.0 +
    +2 + +30.0 + +40.0 + +1.0 + +GAMMA + +30.0 +
    +… + + + + + +
    +4 + +80.0 + +100.0 + +7.0 + +GAMMA + +92.0 +
    +4 + +80.0 + +100.0 + +8.0 + +GAMMA + +94.0 +
    +4 + +80.0 + +100.0 + +9.0 + +GAMMA + +96.0 +
    +4 + +80.0 + +100.0 + +10.0 + +GAMMA + +98.0 +
    +
    +
    +
    +
    + + +
    + +
    + + +
    + + + + \ No newline at end of file diff --git a/doc/ch07-PeoplePlugin.html b/doc/ch07-PeoplePlugin.html new file mode 100644 index 000000000..6d8392c82 --- /dev/null +++ b/doc/ch07-PeoplePlugin.html @@ -0,0 +1,3032 @@ + + + + + + + + + +GCM-Docs - 7  People Plugin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    + +
    + + + + + +
    + +
    +
    +

    7  People Plugin

    +
    + + + +
    + + + + +
    + + +
    + +
    + +
    +

    The people plugin implements the dynamic management of person identity. Each person is identified with an immutable PersonId object that wraps an int value. People are numbered with non-negative values starting with zero and filling a contiguous range, but may contain any number of gaps in that range.

    +
    +

    7.1 Plugin Data Initialization

    +

    The plugin is initialized using a PeoplePluginData object that collects person id values in contiguous ranges. Note that there is no auxiliary data about people and only their existence as a person at the start of the simulation is captured. Other plugins that deal with the various characteristics of people will separately handle adding that data via their own plugin data structures.

    +
    +
    +

    7.2 Plugin Behavior

    +

    The plugin adds a single data manager to the simulation as an instance of the PeopleDataManager that is initialized with the PeoplePluginData.

    +
    +
    +

    7.3 Data Manager

    +

    The data manager provides access to people and provides the ability to:

    +
      +
    • Add or remove a person
    • +
    • Answer questions about person existence +
        +
      • Get the current set of PersonId values
      • +
    • +
    • Get the total number of people
    • +
    • Transform PersonId objects to and from int values +
        +
      • Answer questions about int value ranges used in managing internal data structures in various data managers
      • +
    • +
    +

    The data manager also produces observable events when people are added or removed from the simulation:

    +
      +
    • PersonImminentAdditionEvent – notifies that a person is about to be removed
    • +
    • PersonAdditionEvent – notifies that a person is removed
    • +
    • PersonImminentRemovalEvent – notifies that a person is being added
    • +
    • PersonRemovalEvent – notifies that a person is fully added
    • +
    +
    +
    +

    7.4 Add/Remove event patterns

    +

    A common pattern used throughout many plugins for events signifying the addition or removal of an item from the simulation is to represent each of these with two events. The first event is to notify all concerned actors and data managers that an item is about to be removed, but the removal has not yet occurred so any reference to the item will still be available and any finalization or bookkeeping can be performed. The second event will act as an instruction to remove the item and it is expected that the item will not be available for further inspection.

    +

    As an example, let’s consider the removal of a person by an actor. The person to remove is PersonId[47] and the actor requests the person be removed by the PeopleDataManager. The data manager first plans to release the PersonRemovalEvent as soon as possible. This will schedule the release of the event onto the planning queue and time will not move forward before the execution of this event. However, this is a plan and it will only take place after the all current activities are complete. The data manager next releases the PersonImminentRemovalEvent. This event will propagate immediately to the other data managers and to any actors or reports that are subscribed to person removals. Since the data managers generally do not act on the imminent removal, the actors are able to retrieve any information about the person they need to take final actions or produce reports. Once everyone has had a chance to see that the person will be removed, the planned PersonRemovalEvent will be released and the data managers will finally remove any information related to the person from their data structures. This two-phase removal pattern is useful and practical but does present one problem: Consider the original actor that was deleting person 47. On the very next line of their code after they request the removal of the person, the person still exists. The removal is not immediate, but is slightly delayed in that it will occur only after flow of control has returned to the simulation. This delay will not correspond to any time flow, so the removal of the person will occur at the same time as the request for the removal.

    +

    The addition of a person follows a similar pattern. To understand this, we first need to look at the PersonConstructionData used to add a person. The PersonConstructionData is a container for zero to many objects that carry information about the new person to be used by the various data managers who will need to integrate corresponding data about the person. For example, if the Regions plugin is being used, it requires that every person has a region assignment and thus a RegionId will need to be included in the PersonConstructionData. The people data manager does not understand this auxiliary data but simply repackages it into the PersonImminentAdditionEvent. The event is released and all the relevant data managers take what they need from the data stored in the event to fully initialize the state of the person. Once all data managers have initialized the person, the people data manager releases the PersonAdditionEvent and actors/reports, will now see that the new person has been added to the simulation and will have access to the person’s full initialized complement of data.

    +

    In summary, the general convention is:

    +
      +
    • imminent addition event +
        +
      • used by data managers to piecemeal add an item’s details
      • +
      • ignored by actors and reports
      • +
    • +
    • addition event +
        +
      • ignored by data managers
      • +
      • used by actors and reports to integrate the addition now that all the details are in place
      • +
    • +
    • imminent removal event +
        +
      • ignored by data managers
      • +
      • used by actors to have a last chance to reference details on the item
      • +
    • +
    • removal event +
        +
      • used by data managers to fully remove all stored data on the item
      • +
      • ignored by the actors since the item will be fully removed
      • +
    • +
    +
    +
    +

    7.5 Example Code (Lesson 14)

    +

    Example_14.java shows the use of the people plugin. The example includes four plugins:

    +
      +
    • People plugin – (GCM core plugin) used to manage people
    • +
    • Stochastics Plugin – (GCM plugin) provides random number generation
    • +
    • Model plugin – (local plugin) used to introduce two actors that will +
        +
      • add/remove people
      • +
      • vaccinate people
      • +
    • +
    • Vaccine plugin – (local plugin) used to track vaccinations for each person
    • +
    +

    The example’s main method starts in Code Block 7.1 by establishing two reports:

    +
      +
    • The population trace report simply lists the additions and deletions of people by time. The report is managed by the PopulationTraceReport and is added to the simulation by the model plugin.
    • +
    • The vaccination report shows a daily accounting of the number of people having 0, 1…6+ vaccinations. The report is managed by the VaccineReport class added by the vaccine plugin.
    • +
    +
    +
    +

    Code Block 7.1: The population trace and vaccination reports are associated with corresponding file names via the NIO report item handler.

    +
    public static void main(String[] args) throws IOException {
    +    if (args.length == 0) {
    +        throw new RuntimeException("One output directory argument is required");
    +    }
    +    Path outputDirectory = Paths.get(args[0]);
    +    if (!Files.exists(outputDirectory)) {
    +        Files.createDirectory(outputDirectory);
    +    } else {
    +        if (!Files.isDirectory(outputDirectory)) {
    +            throw new IOException("Provided path is not a directory");
    +        }
    +    }
    +
    +    // reports
    +    NIOReportItemHandler nioReportItemHandler = //
    +            NIOReportItemHandler.builder()//
    +                    .addReport(ModelReportLabel.POPULATION_TRACE, //
    +                            outputDirectory.resolve("population_trace_report.xls"))//
    +                    .addReport(ModelReportLabel.VACCINATION, //
    +                            outputDirectory.resolve("vaccination_report.xls"))//
    +                    .build();
    +
    +
    +

    The main method continues by creating the people plugin and initializing it with 10 people. Note that the people will have id values of 1, 3, 5, … ,19 showing that any set of non-negative values are acceptable. The stochastics plugin is next and is initialized with a seed value. We will be controlling the random seed values via a dimension as presented in Code Block 7.3. As a result, the experiment will have 5 scenarios, with each scenario differing in only the random seed value that starts the simulation.

    +
    +
    +

    Code Block 7.2: The various plugins are initialized with data and added to the experiment.

    +
            // create the people plugin with an initial population of ten people,
    +        // numbered 1, 3, 5,...,19
    +        PeoplePluginData.Builder peoplePluginDataBuilder = PeoplePluginData.builder();
    +        for (int i = 0; i < 10; i++) {
    +            PersonId personId = new PersonId(i * 2 + 1);
    +            peoplePluginDataBuilder.addPersonRange(new PersonRange(personId.getValue(), personId.getValue()));
    +        }
    +        PeoplePluginData peoplePluginData = peoplePluginDataBuilder.build();
    +        Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(peoplePluginData);
    +
    +        // create the stochastics plugin and build a dimension with 5 seed
    +        // values
    +        WellState wellState = WellState.builder().setSeed(463390897335624435L).build();
    +        StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder().setMainRNGState(wellState)
    +                .build();
    +        Plugin stochasticsPlugin = StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData);
    +
    +        Dimension stochasticsDimension = getStochasticsDimension(5, 8265427588292179209L);
    +
    +        // create the vaccine and model plugins
    +        Plugin vaccinePlugin = VaccinePlugin.getVaccinePlugin();
    +        Plugin modelPlugin = ModelPlugin.getModelPlugin();
    +
    +        Experiment.builder()//
    +                .addPlugin(modelPlugin)//
    +                .addPlugin(peoplePlugin)//
    +                .addPlugin(stochasticsPlugin)//
    +                .addPlugin(vaccinePlugin)//
    +                .addExperimentContextConsumer(nioReportItemHandler)//
    +                .addDimension(stochasticsDimension)//
    +                .build()//
    +                .execute();//
    +    }
    +
    +
    +
    +
    +

    Code Block 7.3: The stochastics dimension contains levels for each replication value. Note that the generation of the random seed values occurs outside of the lambda code.

    +
    private static Dimension getStochasticsDimension(int replicationCount, long seed) {
    +    FunctionalDimension.Builder builder = FunctionalDimension.builder();//
    +
    +    RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed);
    +
    +    List<Long> seedValues = new ArrayList<>();
    +    for (int i = 0; i < replicationCount; i++) {
    +        seedValues.add(randomGenerator.nextLong());
    +    }
    +
    +    IntStream.range(0, seedValues.size()).forEach((i) -> {
    +        builder.addLevel((context) -> {
    +            StochasticsPluginData.Builder stochasticsPluginDataBuilder = context
    +                    .getPluginDataBuilder(StochasticsPluginData.Builder.class);
    +            long seedValue = seedValues.get(i);
    +            WellState wellState = WellState.builder().setSeed(seedValue).build();
    +            stochasticsPluginDataBuilder.setMainRNGState(wellState);
    +
    +            ArrayList<String> result = new ArrayList<>();
    +            result.add(Integer.toString(i));
    +            result.add(Long.toString(seedValue) + "L");
    +
    +            return result;
    +        });//
    +    });
    +
    +    builder.addMetaDatum("seed_index");//
    +    builder.addMetaDatum("seed_value");//
    +
    +    return builder.build();
    +}
    +
    +
    +

    There are two actors provided by the model plugin. The first is the PopulationManager (Code Block 7.4) that upon its initialization plans 100 future actions to randomly remove (10% chance) or add (90% chance) people to the simulation. For people who are added, an initial vaccination count is included in the request to add the person so that the vaccine data manager can set the proper count.

    +
    +
    +

    Code Block 7.4: The population manager schedules 100 randomized actions to either add or remove people.

    +
    public void init(ActorContext actorContext) {
    +    StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);
    +    RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator();
    +    double planTime = randomGenerator.nextDouble();
    +    for (int i = 0; i < 100; i++) {
    +        actorContext.addPlan((c) -> {
    +            PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class);
    +            if (randomGenerator.nextDouble() < 0.1) {
    +                List<PersonId> people = peopleDataManager.getPeople();
    +                if (!people.isEmpty()) {
    +                    PersonId personId = people.get(randomGenerator.nextInt(people.size()));
    +                    peopleDataManager.removePerson(personId);
    +                }
    +            } else {
    +                int intialVaccineCount = randomGenerator.nextInt(3);
    +                VaccineInitialization vaccineInitialization = new VaccineInitialization(intialVaccineCount);
    +                PersonConstructionData personConstructionData = PersonConstructionData.builder()//
    +                        .add(vaccineInitialization)//
    +                        .build();
    +                peopleDataManager.addPerson(personConstructionData);
    +            }
    +        }, planTime);
    +        planTime += randomGenerator.nextDouble();
    +    }
    +}
    +
    +
    +

    Code Block 7.5 shows the second actor, the Vaccinator. It plans 300 vaccination actions over a period of approximately 100 days, selecting a random person to vaccinate each time. There is no limit to the number of vaccinations a person can have and we would expect that some people will have a relatively high number of vaccinations in the vaccine report.

    +
    +
    +

    Code Block 7.5: The vaccinator administers 300 vaccine doses over 100 days.

    +
    public void init(ActorContext actorContext) {
    +    StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);
    +    RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator();
    +    double planTime = randomGenerator.nextDouble();
    +    for (int i = 0; i < 300; i++) {
    +        actorContext.addPlan((c) -> {
    +            PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class);
    +            VaccinationDataManager vaccinationDataManager = c.getDataManager(VaccinationDataManager.class);
    +            List<PersonId> people = peopleDataManager.getPeople();
    +            if (!people.isEmpty()) {
    +                PersonId personId = people.get(randomGenerator.nextInt(people.size()));
    +                vaccinationDataManager.vaccinatePerson(personId);
    +            }
    +        }, planTime);
    +        planTime += randomGenerator.nextDouble() / 3;
    +    }
    +}
    +
    +
    +
    +
    +

    7.6 Interacting with the addition and removal events

    +

    The remaining code blocks will focus on the handling of the four person addition and removal events in the vaccine data manager and the population trace report. The vaccine report is periodic and does not subscribe to any events and is left for the reader to examine.

    +

    Following the general conventions above, the vaccine data manager subscribes to the PersonRemovalEvent and the PersonImminentAdditionEvent during its initialization in Code Block 7.6.

    +
    +
    +

    Code Block 7.6: The vaccination data manager initializes by recording initial vaccine counts for each person and subscribing to person addition, person removal and person vaccination events.

    +
    public void init(DataManagerContext dataManagerContext) {
    +    super.init(dataManagerContext);
    +    dataManagerContext.subscribe(PersonRemovalEvent.class, this::handlePersonRemovalEvent);
    +    dataManagerContext.subscribe(PersonImminentAdditionEvent.class, this::handlePersonImminentAdditionEvent);
    +    personDataManager = dataManagerContext.getDataManager(PeopleDataManager.class);
    +    this.dataManagerContext = dataManagerContext;
    +    for (PersonId personId : personDataManager.getPeople()) {
    +        vaccinationCounts.put(personId, new MutableInteger());
    +    }
    +    dataManagerContext.subscribe(VaccinationMutationEvent.class, this::handleVaccinationMutationEvent);
    +}
    +
    +
    +

    The vaccine data manager uses a simple map from person id to a counter to track the number of vaccinations for each person:

    +
    +
    +

    Code Block 7.7: The vaccination data manager uses a simple map from person id to a counter to track the number of vaccinations for each person.

    +
    private Map<PersonId, MutableInteger> vaccinationCounts = new LinkedHashMap<>();
    +
    +
    +

    The subscriptions above refer to the local methods of the vaccine data manager in Code Block 7.8. Handling the removal of a person is simple; the person id dropped from the map. Handling the addition requires that the manager try to locate a VaccinationInitialization object (which is just a wrapper around and integer count) contained in the construction. If the VaccinationInitialization is present, then the manager further validates the count is not negative.

    +
    +
    +

    Code Block 7.8: The vaccination manager removes people from its count tracking as needed. Newly added people may enter into the simulation with some vaccinations.

    +
        private void handlePersonRemovalEvent(DataManagerContext dataManagerContext,
    +            PersonRemovalEvent personRemovalEvent) {
    +        PersonId personId = personRemovalEvent.personId();
    +        vaccinationCounts.remove(personId);
    +    }
    +
    +    private void handlePersonImminentAdditionEvent(DataManagerContext dataManagerContext,
    +            PersonImminentAdditionEvent personImminentAdditionEvent) {
    +        PersonId personId = personImminentAdditionEvent.personId();
    +        validateNewPersonId(personId);
    +        MutableInteger mutableInteger = new MutableInteger();
    +        vaccinationCounts.put(personId, mutableInteger);
    +        Optional<VaccineInitialization> optional = personImminentAdditionEvent//
    +                .personConstructionData()//
    +                .getValue(VaccineInitialization.class);
    +        if (optional.isPresent()) {
    +            VaccineInitialization vaccineInitialization = optional.get();
    +            int vaccineCount = vaccineInitialization.getVaccineCount();
    +            validateInitialVaccineCount(vaccineCount);
    +            mutableInteger.setValue(vaccineCount);
    +        }
    +    }
    +
    +
    +
    +
    +

    7.7 Inspecting the output

    +

    Figure 7.1 shows the population trace report spanning the five scenarios and 500 additions and removals of people. In Figure 7.2 we have the vaccination report showing the number of people having from 0 to 6+ vaccinations over each day of the simulation across the five scenarios. As expected, the number of people having six or more vaccinations starts out at zero and monotonically increases as the days progress.

    +
    +
    +
    +

    Figure 7.1: An excerpt of the population trace report.

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +scenario + +seed_index + +seed_value + +time + +personId + +action +
    +0 + +0 + +1126862960420803077L + +0.0000000 + +1 + +ADDITION +
    +0 + +0 + +1126862960420803077L + +0.0000000 + +3 + +ADDITION +
    +0 + +0 + +1126862960420803077L + +0.0000000 + +5 + +ADDITION +
    +0 + +0 + +1126862960420803077L + +0.0000000 + +7 + +ADDITION +
    +0 + +0 + +1126862960420803077L + +0.0000000 + +9 + +ADDITION +
    +0 + +0 + +1126862960420803077L + +0.0000000 + +11 + +ADDITION +
    +0 + +0 + +1126862960420803077L + +0.0000000 + +13 + +ADDITION +
    +0 + +0 + +1126862960420803077L + +0.0000000 + +15 + +ADDITION +
    +0 + +0 + +1126862960420803077L + +0.0000000 + +17 + +ADDITION +
    +0 + +0 + +1126862960420803077L + +0.0000000 + +19 + +ADDITION +
    +0 + +0 + +1126862960420803077L + +0.8335755 + +19 + +REMOVAL +
    +0 + +0 + +1126862960420803077L + +1.2826070 + +20 + +ADDITION +
    +0 + +0 + +1126862960420803077L + +1.6263299 + +21 + +ADDITION +
    +… + + + + + +
    +1 + +1 + +-4486033808643580070L + +0.0000000 + +17 + +ADDITION +
    +1 + +1 + +-4486033808643580070L + +0.0000000 + +19 + +ADDITION +
    +1 + +1 + +-4486033808643580070L + +0.0667491 + +20 + +ADDITION +
    +1 + +1 + +-4486033808643580070L + +0.9322293 + +21 + +ADDITION +
    +1 + +1 + +-4486033808643580070L + +1.6514183 + +22 + +ADDITION +
    +1 + +1 + +-4486033808643580070L + +2.1177839 + +23 + +ADDITION +
    +1 + +1 + +-4486033808643580070L + +2.2623884 + +24 + +ADDITION +
    +1 + +1 + +-4486033808643580070L + +2.4082708 + +25 + +ADDITION +
    +1 + +1 + +-4486033808643580070L + +2.8132398 + +26 + +ADDITION +
    +1 + +1 + +-4486033808643580070L + +2.9103862 + +27 + +ADDITION +
    +1 + +1 + +-4486033808643580070L + +3.1314042 + +28 + +ADDITION +
    +1 + +1 + +-4486033808643580070L + +3.9782907 + +29 + +ADDITION +
    +1 + +1 + +-4486033808643580070L + +4.8481078 + +30 + +ADDITION +
    +1 + +1 + +-4486033808643580070L + +5.7753568 + +31 + +ADDITION +
    +1 + +1 + +-4486033808643580070L + +6.0714214 + +32 + +ADDITION +
    +1 + +1 + +-4486033808643580070L + +6.4493810 + +33 + +ADDITION +
    +… + + + + + +
    +4 + +4 + +2435395143614485495L + +44.8710482 + +100 + +ADDITION +
    +4 + +4 + +2435395143614485495L + +45.0533288 + +101 + +ADDITION +
    +4 + +4 + +2435395143614485495L + +45.1289192 + +102 + +ADDITION +
    +4 + +4 + +2435395143614485495L + +45.5197380 + +103 + +ADDITION +
    +4 + +4 + +2435395143614485495L + +45.8369307 + +104 + +ADDITION +
    +4 + +4 + +2435395143614485495L + +46.2957569 + +105 + +ADDITION +
    +4 + +4 + +2435395143614485495L + +46.8744186 + +106 + +ADDITION +
    +4 + +4 + +2435395143614485495L + +47.3473702 + +107 + +ADDITION +
    +4 + +4 + +2435395143614485495L + +48.3166466 + +108 + +ADDITION +
    +4 + +4 + +2435395143614485495L + +49.2053410 + +109 + +ADDITION +
    +4 + +4 + +2435395143614485495L + +49.2524537 + +110 + +ADDITION +
    +4 + +4 + +2435395143614485495L + +49.5378229 + +111 + +ADDITION +
    +
    +
    +
    +
    +
    +
    +
    +

    Figure 7.2: An excerpt of the vaccination report.

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +scenario + +seed_index + +seed_value + +day + +count_0 + +count_1 + +count_2 + +count_3 + +count_4 + +count_5 + +count_6+ +
    +0 + +0 + +1126862960420803077L + +0 + +10 + +0 + +0 + +0 + +0 + +0 + +0 +
    +0 + +0 + +1126862960420803077L + +1 + +7 + +2 + +0 + +0 + +0 + +0 + +0 +
    +0 + +0 + +1126862960420803077L + +2 + +4 + +5 + +2 + +0 + +0 + +0 + +0 +
    +0 + +0 + +1126862960420803077L + +3 + +2 + +3 + +8 + +0 + +0 + +0 + +0 +
    +0 + +0 + +1126862960420803077L + +4 + +1 + +2 + +10 + +0 + +1 + +0 + +0 +
    +0 + +0 + +1126862960420803077L + +5 + +2 + +3 + +6 + +3 + +3 + +0 + +0 +
    +0 + +0 + +1126862960420803077L + +6 + +1 + +5 + +5 + +3 + +2 + +2 + +0 +
    +0 + +0 + +1126862960420803077L + +7 + +0 + +7 + +4 + +4 + +2 + +3 + +0 +
    +0 + +0 + +1126862960420803077L + +8 + +0 + +7 + +6 + +5 + +2 + +2 + +1 +
    +… + + + + + + + + + + +
    +0 + +0 + +1126862960420803077L + +52 + +6 + +13 + +13 + +7 + +10 + +9 + +22 +
    +0 + +0 + +1126862960420803077L + +53 + +7 + +13 + +15 + +7 + +10 + +9 + +22 +
    +0 + +0 + +1126862960420803077L + +54 + +7 + +13 + +16 + +7 + +10 + +9 + +22 +
    +1 + +1 + +-4486033808643580070L + +0 + +10 + +0 + +0 + +0 + +0 + +0 + +0 +
    +1 + +1 + +-4486033808643580070L + +1 + +9 + +3 + +0 + +0 + +0 + +0 + +0 +
    +1 + +1 + +-4486033808643580070L + +2 + +5 + +6 + +2 + +0 + +0 + +0 + +0 +
    +1 + +1 + +-4486033808643580070L + +3 + +5 + +8 + +4 + +1 + +0 + +0 + +0 +
    +1 + +1 + +-4486033808643580070L + +4 + +5 + +6 + +5 + +3 + +1 + +0 + +0 +
    +1 + +1 + +-4486033808643580070L + +5 + +4 + +6 + +6 + +2 + +3 + +0 + +0 +
    +1 + +1 + +-4486033808643580070L + +6 + +2 + +6 + +7 + +3 + +4 + +0 + +0 +
    +1 + +1 + +-4486033808643580070L + +7 + +2 + +4 + +11 + +4 + +4 + +0 + +0 +
    +… + + + + + + + + + + +
    +3 + +3 + +-821383327301461075L + +42 + +5 + +13 + +15 + +9 + +8 + +10 + +15 +
    +3 + +3 + +-821383327301461075L + +43 + +5 + +13 + +15 + +9 + +8 + +11 + +15 +
    +3 + +3 + +-821383327301461075L + +44 + +6 + +12 + +15 + +10 + +8 + +9 + +17 +
    +3 + +3 + +-821383327301461075L + +45 + +6 + +13 + +15 + +9 + +8 + +7 + +19 +
    +3 + +3 + +-821383327301461075L + +46 + +6 + +13 + +15 + +8 + +8 + +7 + +20 +
    +3 + +3 + +-821383327301461075L + +47 + +6 + +14 + +17 + +5 + +11 + +6 + +21 +
    +3 + +3 + +-821383327301461075L + +48 + +5 + +14 + +17 + +6 + +10 + +5 + +22 +
    +3 + +3 + +-821383327301461075L + +49 + +5 + +13 + +18 + +7 + +9 + +6 + +22 +
    +… + + + + + + + + + + +
    +4 + +4 + +2435395143614485495L + +42 + +11 + +12 + +11 + +10 + +12 + +7 + +19 +
    +4 + +4 + +2435395143614485495L + +43 + +9 + +13 + +12 + +9 + +11 + +8 + +20 +
    +4 + +4 + +2435395143614485495L + +44 + +7 + +14 + +11 + +10 + +10 + +8 + +21 +
    +4 + +4 + +2435395143614485495L + +45 + +7 + +16 + +10 + +11 + +9 + +7 + +23 +
    +4 + +4 + +2435395143614485495L + +46 + +7 + +16 + +13 + +10 + +11 + +6 + +24 +
    +4 + +4 + +2435395143614485495L + +47 + +8 + +14 + +15 + +10 + +11 + +7 + +24 +
    +4 + +4 + +2435395143614485495L + +48 + +9 + +13 + +15 + +11 + +11 + +7 + +24 +
    +4 + +4 + +2435395143614485495L + +49 + +9 + +13 + +15 + +12 + +11 + +7 + +24 +
    +4 + +4 + +2435395143614485495L + +50 + +12 + +13 + +15 + +12 + +11 + +7 + +24 +
    +
    +
    +
    +
    + + +
    + +
    + + +
    + + + + \ No newline at end of file diff --git a/doc/ch08-RegionsPlugin.html b/doc/ch08-RegionsPlugin.html new file mode 100644 index 000000000..33684103a --- /dev/null +++ b/doc/ch08-RegionsPlugin.html @@ -0,0 +1,2350 @@ + + + + + + + + + +GCM-Docs - 8  Regions Plugin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    + +
    + + + + + +
    + +
    +
    +

    8  Regions Plugin

    +
    + + + +
    + + + + +
    + + +
    + +
    + +
    +

    The regions plugin manages the assignment of people to regions. As such, it is dependent on the people plugin. A region does not have an associated geo-location and does not specifically designate a county, state or any other regional concept. The interpretation of what defines a region is left to the modeler, although it will usually represent some sort of contiguous land area. When the regions plugin is being used, each person has a region association at all times and regions can be associated with zero to many people. Regions may also have a set of associated property values that can be dynamically defined. Regions are identified via the RegionId marker interface that does not define any methods. It is left to the modeler to implement a RegionId data type. When the number of regions is fixed and relatively small this can be accomplished via an enumeration. For larger or dynamic sets of region id values it is typical to implement a simple immutable class that wraps an integer.

    +
    +

    8.1 Plugin Data Initialization

    +

    The plugin is initialized using a RegionsPluginData object that collects person to region assignments and region property values.

    +
    +
    +

    8.2 Plugin Behavior

    +

    The plugin adds a single data manager to the simulation as an instance of the RegionsDataManager that is initialized with the RegionsPluginData.

    +
    +
    +

    8.3 Data Manager

    +

    The data manager controls regions, their properties and the assignment of people to those regions. The data manager provides public methods that:

    +
      +
    • Add a region
    • +
    • Define a region property
    • +
    • Set a region property value
    • +
    • Move a person from one region to another
    • +
    • Answer various questions about: +
        +
      • Person membership in regions
      • +
      • Region property values
      • +
    • +
    +

    The data manager also produces observable events:

    +
      +
    • PersonRegionUpdateEvent – when a person is moved from one region to another
    • +
    • RegionAdditionEvent – when a region is added to the simulation
    • +
    • RegionPropertyDefintionEvent – when a new region property is defined
    • +
    • RegionPropertyUpdateEvent – when a region property value is assigned
    • +
    +
    +
    +

    8.4 Example Code (Lesson 15)

    +

    Example_15.java shows the use of the regions plugin. In it we will examine

    +
      +
    • The initialization of the regions plugin
    • +
    • The movement of people between regions
    • +
    • The dynamic addition of regions
    • +
    • The dynamic addition of region properties
    • +
    • The update of region property values
    • +
    +

    The example includes five plugins:

    +
      +
    • Regions Plugin– (GCM core plugin) used to manage regions, their properties and person membership in regions
    • +
    • People plugin – (GCM core plugin) used to manage people
    • +
    • Stochastics plugin – (GCM core plugin) used to generate random numbers used in various decisions
    • +
    • Model plugin – (local plugin) used to introduce three actors that will: +
        +
      • Move people between regions
      • +
      • Create new regions
      • +
      • Vaccinate people, reacting to changes in region properties
      • +
    • +
    • Vaccine plugin – (local plugin) used to track vaccinations for each person
    • +
    +

    The example’s main method starts in Code Block 8.1 by creating an instance of the example class rather than building the experiment directly since this example is somewhat more complex than previous examples.

    +
    +
    +

    Code Block 8.1: Executing example 15 with an output directory.

    +
    public static void main(String[] args) throws IOException {
    +    if (args.length == 0) {
    +        throw new RuntimeException("One output directory argument is required");
    +    }
    +    Path outputDirectory = Paths.get(args[0]);
    +    if (!Files.exists(outputDirectory)) {
    +        Files.createDirectory(outputDirectory);
    +    } else {
    +        if (!Files.isDirectory(outputDirectory)) {
    +            throw new IOException("Provided path is not a directory");
    +        }
    +    }
    +
    +    new Example_15(outputDirectory).execute();
    +}
    +
    +
    +

    The execution method first gathers together the five plugins in Code Block 8.2:

    +
    +
    +

    Code Block 8.2: The various plugins are gathered from their initial data.

    +
    private void execute() {
    +    /*
    +         * Create person ids and region ids that are shared across the plugins
    +         */
    +    initializePeopleAndRegions();
    +
    +    /*
    +         * Create the reports
    +         */
    +
    +    NIOReportItemHandler nioReportItemHandler = getNIOReportItemHandler();
    +
    +    /*
    +         * Create the people plugin filled with 1000 people
    +         */
    +    Plugin peoplePlugin = getPeoplePlugin();
    +
    +    /*
    +         * Create the region plugin 5 regions, each having a lat and lon and assign the
    +         * people to random regions.
    +         * 
    +         */
    +    Plugin regionsPlugin = getRegionsPlugin();
    +
    +    /*
    +         * create the stochastics plugin and build a dimension with 5 seed values
    +         */
    +    Plugin stochasticsPlugin = getStochasticsPlugin();
    +    Dimension stochasticsDimension = getStochasticsDimension(5, randomGenerator.nextLong());
    +
    +    /*
    +         * Create the vaccine and model plugins
    +         */
    +    Plugin vaccinePlugin = VaccinePlugin.getVaccinePlugin();
    +
    +    Plugin modelPlugin = ModelPlugin.getModelPlugin();
    +
    +
    +

    The first action is to create 1000 people and 5 regions that will be used in the creation of both the people plugin and the regions plugin.

    +
    +
    +

    Code Block 8.3: Lists of initial people and regions are created and will be used to initialize the various plugins.

    +
    private void initializePeopleAndRegions() {
    +    for (int i = 0; i < 1000; i++) {
    +        initialPeople.add(new PersonId(i));
    +    }
    +    for (int i = 0; i < 5; i++) {
    +        initialRegions.add(new Region(i));
    +    }
    +}
    +
    +
    +

    The regions plugin defines a region id with a marker interface. Marker interfaces are used to differentiate arguments and reduce variable type ambiguities while not imposing any particular implementation on the modeler. Region ids might reasonably be implemented as integer based identifiers or as strings that represent place names. In this example we will implement the region ids with an integer based class, the Region (Code Block 8.4), which is a boiler-plate wrapper around an int id value.

    +
    +
    +

    Code Block 8.4: The region id is implemented as a wrapper class of int.

    +
    public final class Region implements RegionId {
    +
    +    private final int id;
    +
    +    /**
    +     * Constructs the region
    +     * 
    +     * @throws ContractException
    +     *                           <li>{@linkplain ModelError#NEGATIVE_REGION_ID}</li>
    +     */
    +    public Region(int id) {
    +        if (id < 0) {
    +            throw new ContractException(ModelError.NEGATIVE_REGION_ID);
    +        }
    +        this.id = id;
    +    }
    +
    +    public int getValue() {
    +        return id;
    +    }
    +
    +    @Override
    +    public int hashCode() {
    +        return id;
    +    }
    +
    +    @Override
    +    public boolean equals(Object obj) {
    +        if (this == obj) {
    +            return true;
    +        }
    +        if (!(obj instanceof Region)) {
    +            return false;
    +        }
    +        Region other = (Region) obj;
    +        if (id != other.id) {
    +            return false;
    +        }
    +        return true;
    +    }
    +
    +    @Override
    +    public String toString() {
    +        return "Region_" + id;
    +    }
    +}
    +
    +
    +

    The example contains three reports in Code Block 8.5:

    +
      +
    • RegionPropertyReport - shows changes to region property values
    • +
    • RegionTransferReport - shows movements of people between regions
    • +
    • VaccinationReport - shows vaccinations of people
    • +
    +
    +
    +

    Code Block 8.5: The region property, region transfer and vaccination reports are mapped to distinct file names.

    +
    private NIOReportItemHandler getNIOReportItemHandler() {
    +    return NIOReportItemHandler.builder()//
    +            .addReport(ModelReportLabel.REGION_PROPERTY_REPORT, //
    +                    outputDirectory.resolve("region_property_report.xls"))//
    +            .addReport(ModelReportLabel.REGION_TRANSFER_REPORT, //
    +                    outputDirectory.resolve("region_transfer_report.xls"))//
    +            .addReport(ModelReportLabel.VACCINATION, //
    +                    outputDirectory.resolve("vaccine_report.xls"))//
    +            .build();
    +}
    +
    +
    +

    The people plugin, Code Block 8.6, is built from the 1000 people created earlier.

    +
    +
    +

    Code Block 8.6: The people plugin is initialized with the starting populaiton.

    +
    private Plugin getPeoplePlugin() {
    +    PeoplePluginData.Builder peoplePluginDataBuilder = PeoplePluginData.builder();
    +    for (PersonId personId : initialPeople) {
    +        peoplePluginDataBuilder.addPersonRange(new PersonRange(personId.getValue(), personId.getValue()));
    +    }
    +    PeoplePluginData peoplePluginData = peoplePluginDataBuilder.build();
    +    return PeoplePlugin.getPeoplePlugin(peoplePluginData);
    +}
    +
    +
    +

    Creating the regions plugin Code Block 8.7 is a bit more involved. First, the five regions created before are added to the plugin. Since the plugin requires that every person always have a region assignment, we assign a randomly selected region to each person. We define the LAT and LON properties to give the regions a geographic location. Notice that the definitions do not have default values since it does not make sense to say a region has a default position. This then will require that we assign specific latitude and longitude values for each region. Later on we will examine adding a new region property definition dynamically as the simulation is running.

    +
    +
    +

    Code Block 8.7: The regions plugin is initialized with the starting regions and people, with each person assigned to a randomly selected region. The two region-based reports are also initialized and added to the region plugin’s data.

    +
    private Plugin getRegionsPlugin() {
    +    // create the region plugin with an initial five regions, each region
    +    // having 200 people
    +    RegionsPluginData.Builder regionsPluginDataBuilder = RegionsPluginData.builder();
    +    for (Region region : initialRegions) {
    +        regionsPluginDataBuilder.addRegion(region);
    +    }
    +
    +    for (PersonId personId : initialPeople) {
    +        Region region = initialRegions.get(randomGenerator.nextInt(initialRegions.size()));
    +        regionsPluginDataBuilder.addPerson(personId, region);
    +    }
    +
    +    PropertyDefinition propertyDefinition = PropertyDefinition.builder()//
    +            .setType(Double.class)//
    +            .setPropertyValueMutability(false)//
    +            .build();
    +    regionsPluginDataBuilder.defineRegionProperty(RegionProperty.LAT, propertyDefinition);
    +    regionsPluginDataBuilder.defineRegionProperty(RegionProperty.LON, propertyDefinition);
    +
    +    for (Region region : initialRegions) {
    +        regionsPluginDataBuilder.setRegionPropertyValue(region, RegionProperty.LAT,
    +                randomGenerator.nextDouble() + 45.0);
    +        regionsPluginDataBuilder.setRegionPropertyValue(region, RegionProperty.LON,
    +                randomGenerator.nextDouble() + 128.0);
    +    }
    +
    +    RegionsPluginData regionsPluginData = regionsPluginDataBuilder.build();
    +
    +    RegionPropertyReportPluginData regionPropertyReportPluginData = //
    +            RegionPropertyReportPluginData.builder()//
    +                    .setReportLabel(ModelReportLabel.REGION_PROPERTY_REPORT)//
    +                    .build();
    +
    +    RegionTransferReportPluginData regionTransferReportPluginData = RegionTransferReportPluginData.builder()//
    +            .setReportLabel(ModelReportLabel.REGION_TRANSFER_REPORT)//
    +            .setReportPeriod(ReportPeriod.END_OF_SIMULATION)//
    +            .build();//
    +
    +    return RegionsPlugin.builder()//
    +            .setRegionsPluginData(regionsPluginData)//
    +            .setRegionPropertyReportPluginData(regionPropertyReportPluginData)//
    +            .setRegionTransferReportPluginData(regionTransferReportPluginData)//
    +            .getRegionsPlugin();
    +}
    +
    +
    +

    Adding the stochastics plugin with a corresponding dimension that will create five scenarios proceeds in the usual way in Code Block 8.8:

    +
    +
    +

    Code Block 8.8: The stochastics plugin is initialized with a random seed value. A dimension is added to add new seeds to the resulting scenarios.

    +
    private Plugin getStochasticsPlugin() {
    +
    +    WellState wellState = WellState.builder().setSeed(randomGenerator.nextLong()).build();
    +    StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder()//
    +            .setMainRNGState(wellState).build();
    +    return StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData);
    +}
    +
    +private Dimension getStochasticsDimension(int replicationCount, long seed) {
    +    FunctionalDimension.Builder builder = FunctionalDimension.builder();//
    +
    +    RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed);
    +
    +    List<Long> seedValues = new ArrayList<>();
    +    for (int i = 0; i < replicationCount; i++) {
    +        seedValues.add(randomGenerator.nextLong());
    +    }
    +
    +    IntStream.range(0, seedValues.size()).forEach((i) -> {
    +        builder.addLevel((context) -> {
    +            StochasticsPluginData.Builder stochasticsPluginDataBuilder = context
    +                    .getPluginDataBuilder(StochasticsPluginData.Builder.class);
    +            long seedValue = seedValues.get(i);
    +            WellState wellState = WellState.builder().setSeed(seedValue).build();
    +            stochasticsPluginDataBuilder.setMainRNGState(wellState);
    +
    +            ArrayList<String> result = new ArrayList<>();
    +            result.add(Integer.toString(i));
    +            result.add(Long.toString(seedValue) + "L");
    +
    +            return result;
    +        });//
    +    });
    +
    +    builder.addMetaDatum("seed index");//
    +    builder.addMetaDatum("seed value");//
    +
    +    return builder.build();
    +}
    +
    +
    +

    Finally, we add the vaccine and model plugins. This will add the vaccine data manager as well as three previously mentioned actors that will be used to demonstrate the various capabilities of the regions plugin.

    +
      +
    • PersonMover – used to move people between regions
    • +
    • RegionCreator – used to create new regions during the simulation run
    • +
    • Vaccinator – used to vaccinate people, reacting to changes in region properties
    • +
    +

    The execute method finishes (Code Block 8.9) by constructing and executing the experiment:

    +
    +
    +

    Code Block 8.9: The experiment is run with five scenarios, each using distinct random seed values.

    +
    Experiment.builder()//
    +        .addPlugin(modelPlugin)//
    +        .addPlugin(regionsPlugin)//
    +        .addPlugin(peoplePlugin)//
    +        .addPlugin(stochasticsPlugin)//
    +        .addPlugin(vaccinePlugin)//
    +        .addExperimentContextConsumer(nioReportItemHandler)//
    +        .addDimension(stochasticsDimension)//
    +        .build()//
    +        .execute();//
    +
    +
    +
    +
    +

    8.5 The actors

    +

    We will finish this chapter by reviewing the three actors of the model plugin and then examine the three reports.

    +

    The PersonMover actor, in Code Block 8.10 and Code Block 8.11, schedules 1000 random moves of a person from one region to another over the course of 100 days.

    +
    +
    +

    Code Block 8.10: The person mover actor plans for 1000 movements of people over time.

    +
    public void init(ActorContext actorContext) {
    +    for (int i = 0; i < 1000; i++) {
    +        double planTime = ((double) i) * 0.1;
    +        actorContext.addPlan(this::moveRandomPerson, planTime);
    +    }
    +}
    +
    +
    +

    Moving the person requires that we use the stochastics plugin and the people plugin to select a random person. We next use the regions plugin to first select a random new region for the person and then move the person to that region.

    +
    +
    +

    Code Block 8.11: The person mover actor attempts to move a randomly selected person from their current region to a new region.

    +
    private void moveRandomPerson(ActorContext actorContext) {
    +    StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);
    +    RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator();
    +    PeopleDataManager peopleDataManager = actorContext.getDataManager(PeopleDataManager.class);
    +    RegionsDataManager regionsDataManager = actorContext.getDataManager(RegionsDataManager.class);
    +
    +    // pick a random person
    +    List<PersonId> people = peopleDataManager.getPeople();
    +    if (people.isEmpty()) {
    +        return;
    +    }
    +    PersonId personId = people.get(randomGenerator.nextInt(people.size()));
    +
    +    // pick a new random new region for that person
    +    List<RegionId> regionIds = new ArrayList<>(regionsDataManager.getRegionIds());
    +    RegionId personRegion = regionsDataManager.getPersonRegion(personId);
    +    regionIds.remove(personRegion);
    +    if (regionIds.isEmpty()) {
    +        return;
    +    }
    +    RegionId newPersonRegion = regionIds.get(randomGenerator.nextInt(regionIds.size()));
    +
    +    // assign the region to the person
    +    regionsDataManager.setPersonRegion(personId, newPersonRegion);
    +}
    +
    +
    +

    The RegionCreator actor, in Code Block 8.12 and Code Block 8.13, follows a similar pattern, scheduling the creation of five new regions over 101 days.

    +
    +
    +

    Code Block 8.12: The region creator actor plans the addition of five new regions.

    +
    public void init(ActorContext actorContext) {
    +    for (int i = 0; i < 5; i++) {
    +        double planTime = 20 * i + 1;
    +        actorContext.addPlan(this::addRegion, planTime);
    +    }
    +}
    +
    +
    +

    When adding a region, we have to be aware that the region will have LAT and LON properties and that these properties were not defined with default values. Thus we must supply values for the region’s latitude and longitude as part of the RegionConstructionData object that is passed to the regions data manager. We will similarly assign a new random Boolean value for the VACCINE_PRIORITY property. The VACCINE_PRIORITY is a dynamically added property that is introduced later. Note that we first check for the existence of the property and only then set a value since setting such a value before the property is defined will result in a runtime exception.

    +
    +
    +
    + +
    +
    +Note +
    +
    +
    +

    Such considerations are unusual since properties are usually defined in the plugin initialization data or added very early in the simulation before any actors have initialized. We do so here for the purposes of demonstrating dynamic property definitions.

    +
    +
    +
    +
    +

    Code Block 8.13: When the region creator actor adds a new region, it assigns a random lat-lon corrdinate and possibly assigns a vaccine priority status to the region.

    +
    private void addRegion(ActorContext actorContext) {
    +    RegionsDataManager regionsDataManager = actorContext.getDataManager(RegionsDataManager.class);
    +    StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);
    +    RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator();
    +
    +    Set<Region> regions = regionsDataManager.getRegionIds();
    +    int maxRegionValue = -1;
    +    for (Region region : regions) {
    +        int value = region.getValue();
    +        maxRegionValue = FastMath.max(value, maxRegionValue);
    +    }
    +    Region newRegion = new Region(maxRegionValue + 1);
    +    Builder regionBuilder = RegionConstructionData.builder().setRegionId(newRegion);
    +    regionBuilder.setRegionPropertyValue(RegionProperty.LAT, 35 + randomGenerator.nextDouble());
    +    regionBuilder.setRegionPropertyValue(RegionProperty.LON, 128 + randomGenerator.nextDouble());
    +
    +    if (regionsDataManager.regionPropertyIdExists(RegionProperty.VACCINE_PRIORITY)) {
    +        regionBuilder.setRegionPropertyValue(RegionProperty.VACCINE_PRIORITY, randomGenerator.nextBoolean());
    +    }
    +    RegionConstructionData regionConstructionData = regionBuilder.build();
    +    regionsDataManager.addRegion(regionConstructionData);
    +}
    +
    +
    +

    The Vaccinator actor is somewhat more complicated than the other actors. It initializes (Code Block 8.14) by storing references to various data managers for convenience and then plans 5000 vaccinations spread over 100 days. It also plans to add the VACCINE_PRIORITY property on day 50.

    +
    +
    +

    Code Block 8.14: The vaccinator initializes by planning the vaccination of 5000 people carried out over approximately 50 days.

    +
    public void init(ActorContext actorContext) {
    +    StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);
    +    randomGenerator = stochasticsDataManager.getRandomGenerator();
    +    regionsDataManager = actorContext.getDataManager(RegionsDataManager.class);
    +    vaccinationDataManager = actorContext.getDataManager(VaccinationDataManager.class);
    +
    +    double planTime = randomGenerator.nextDouble();
    +    for (int i = 0; i < 5000; i++) {
    +        actorContext.addPlan(this::vaccinateRandomPerson, planTime);
    +        planTime += randomGenerator.nextDouble() * 0.02;
    +    }
    +
    +    actorContext.addPlan(this::addVaccinePriorityPropertyToRegions, 50);
    +}
    +
    +
    +

    Let’s first look at the addition of the new region property on day 50 in Code Block 8.15. The new property is a Boolean value defaulted to false and indicates whether people should be chosen from regions randomly or by preferring people with the fewest vaccinations. Since the property has a default value, we do not have to set values for each region in the RegionPropertyDefinitionInitialization object that is passed to the regions data manager when creating the region. We do so anyway to demonstrate such value assignments. Once the new property is in place, the Vaccinator schedules the switching of the value for random regions once per day for the next 50 days.

    +
    +
    +

    Code Block 8.15: On day 50, the vaccinator defines the Boolean VACCINE PRIORITY property and assigns randomized values to the existing regions. It then plans for updates to 50 regional vaccine priority property values over 50 days.

    +
    private void addVaccinePriorityPropertyToRegions(ActorContext actorContext) {
    +
    +    PropertyDefinition propertyDefinition = //
    +            PropertyDefinition.builder()//
    +                    .setType(Boolean.class)//
    +                    .setDefaultValue(false)//
    +                    .build();
    +
    +    RegionPropertyDefinitionInitialization.Builder defBuilder = RegionPropertyDefinitionInitialization.builder()//
    +            .setPropertyDefinition(propertyDefinition)//
    +            .setRegionPropertyId(RegionProperty.VACCINE_PRIORITY);
    +
    +    for (RegionId regionId : regionsDataManager.getRegionIds()) {
    +        defBuilder.addPropertyValue(regionId, randomGenerator.nextBoolean());
    +    }
    +
    +    RegionPropertyDefinitionInitialization regionPropertyDefinitionInitialization = defBuilder.build();
    +    regionsDataManager.defineRegionProperty(regionPropertyDefinitionInitialization);
    +
    +    for (int i = 0; i < 50; i++) {
    +        double planTime = actorContext.getTime() + i;
    +        actorContext.addPlan(this::alterVaccinePriorityPropertyOnRandomRegion, planTime);
    +    }
    +}
    +
    +
    +

    In Code Block 8.16 the Vaccinator performs this value switching:

    +
    +
    +

    Code Block 8.16: Toggling the vaccine priority for a randomly selected region.

    +
    private void alterVaccinePriorityPropertyOnRandomRegion(ActorContext actorContext) {
    +    List<RegionId> regionids = new ArrayList<>(regionsDataManager.getRegionIds());
    +    if (regionids.isEmpty()) {
    +        return;
    +    }
    +    RegionId regionId = regionids.get(randomGenerator.nextInt(regionids.size()));
    +    Boolean vaccinePriority = regionsDataManager.getRegionPropertyValue(regionId, RegionProperty.VACCINE_PRIORITY);
    +    regionsDataManager.setRegionPropertyValue(regionId, RegionProperty.VACCINE_PRIORITY, !vaccinePriority);
    +}
    +
    +
    +

    The Vaccinator vaccinates people at random (Code Block 8.17) by first selecting a random region and then selecting a random person in that region. The selection of the person is subject to the presence of the VACCINE_PRIORITY property and whether the value of the property is true for the selected region. If the priority selection is being used, then a first pass through the people in the region establishes the lowest number of vaccines received by any person. A second pass through the same people now selects only those having this number of vaccinations. Finally, a person is selected at random from the eligible people.

    +
    +
    +

    Code Block 8.17: The vaccinator selects a person at random from the population to vaccinate. If the region is using the VACCINE_PRIORITY policy, then those with the least number of vaccinations have preference.

    +
    private void vaccinateRandomPerson(ActorContext actorContext) {
    +
    +    List<RegionId> regionIds = new ArrayList<>(regionsDataManager.getRegionIds());
    +    if (regionIds.isEmpty()) {
    +        return;
    +    }
    +    RegionId regionId = regionIds.get(randomGenerator.nextInt(regionIds.size()));
    +    List<PersonId> peopleInRegion = regionsDataManager.getPeopleInRegion(regionId);
    +
    +    Boolean prioritizePeople = false;
    +    boolean vaccinePriorityPropertyExists = regionsDataManager
    +            .regionPropertyIdExists(RegionProperty.VACCINE_PRIORITY);
    +    if (vaccinePriorityPropertyExists) {
    +        prioritizePeople = regionsDataManager.getRegionPropertyValue(regionId, RegionProperty.VACCINE_PRIORITY);
    +    }
    +
    +    PersonId selectedPersonId = null;
    +    if (prioritizePeople) {
    +        int minVaccinationCount = Integer.MAX_VALUE;
    +        for (PersonId personId : peopleInRegion) {
    +            int personVaccinationCount = vaccinationDataManager.getPersonVaccinationCount(personId);
    +            if (personVaccinationCount < minVaccinationCount) {
    +                minVaccinationCount = personVaccinationCount;
    +            }
    +        }
    +        List<PersonId> eligiblePeople = new ArrayList<>();
    +        for (PersonId personId : peopleInRegion) {
    +            int personVaccinationCount = vaccinationDataManager.getPersonVaccinationCount(personId);
    +            if (personVaccinationCount == minVaccinationCount) {
    +                eligiblePeople.add(personId);
    +            }
    +        }
    +        if (!eligiblePeople.isEmpty()) {
    +            selectedPersonId = eligiblePeople.get(randomGenerator.nextInt(eligiblePeople.size()));
    +        }
    +    } else {
    +        if (!peopleInRegion.isEmpty()) {
    +            selectedPersonId = peopleInRegion.get(randomGenerator.nextInt(peopleInRegion.size()));
    +        }
    +    }
    +
    +    if (selectedPersonId != null) {
    +        vaccinationDataManager.vaccinatePerson(selectedPersonId);
    +    }
    +}
    +
    +
    +
    +
    +

    8.6 Inspecting the output

    +

    The region transfer report shows the number of transfers of a person from one region to another across all days in the simulation. The rows where the source and destination regions are the same represent the addition of people at the start of the simulation and, as expected, the sum of such transfers equals to 1000. We also expect to see regions that were added beyond the original five regions and that transfers in and out of those regions should be reduced compared to the original regions since they start out with no people and come into the simulation only after day 50.

    +

    no vaccinations during those periods.

    +
    +
    +
    +

    Figure 8.1: Excerpts from the region transfer report.

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +scenario + +seed_index + +seed_value + +source_region + +destination_region + +transfers +
    +0 + +0 + +-7834265884293137617L + +Region_2 + +Region_2 + +215 +
    +0 + +0 + +-7834265884293137617L + +Region_0 + +Region_0 + +188 +
    +0 + +0 + +-7834265884293137617L + +Region_3 + +Region_3 + +208 +
    +0 + +0 + +-7834265884293137617L + +Region_4 + +Region_4 + +215 +
    +0 + +0 + +-7834265884293137617L + +Region_1 + +Region_1 + +174 +
    +0 + +0 + +-7834265884293137617L + +Region_4 + +Region_3 + +26 +
    +0 + +0 + +-7834265884293137617L + +Region_4 + +Region_0 + +36 +
    +0 + +0 + +-7834265884293137617L + +Region_3 + +Region_4 + +24 +
    +… + + + + + +
    +1 + +1 + +-7320358285742045393L + +Region_2 + +Region_2 + +215 +
    +1 + +1 + +-7320358285742045393L + +Region_0 + +Region_0 + +188 +
    +1 + +1 + +-7320358285742045393L + +Region_3 + +Region_3 + +208 +
    +1 + +1 + +-7320358285742045393L + +Region_4 + +Region_4 + +215 +
    +1 + +1 + +-7320358285742045393L + +Region_1 + +Region_1 + +174 +
    +… + + + + + +
    +2 + +2 + +-4619948863677044400L + +Region_3 + +Region_0 + +26 +
    +2 + +2 + +-4619948863677044400L + +Region_3 + +Region_1 + +33 +
    +2 + +2 + +-4619948863677044400L + +Region_0 + +Region_4 + +22 +
    +2 + +2 + +-4619948863677044400L + +Region_1 + +Region_4 + +29 +
    +2 + +2 + +-4619948863677044400L + +Region_4 + +Region_0 + +32 +
    +… + + + + + +
    +4 + +4 + +-7584580254621783722L + +Region_8 + +Region_4 + +1 +
    +4 + +4 + +-7584580254621783722L + +Region_9 + +Region_1 + +1 +
    +4 + +4 + +-7584580254621783722L + +Region_9 + +Region_0 + +2 +
    +4 + +4 + +-7584580254621783722L + +Region_9 + +Region_6 + +2 +
    +
    +
    +
    +
    +

    In the region property report (Figure 8.2) we see that the LAT and LON properties were set for the first five regions at the start of the simulation. Starting on day 1, new regions were added and each has an assigned LAT and LON value at that time. Beginning on day 50, all regions are assigned a VACCINE_PRIOITY value and assignments to that property continue daily for random regions.

    +
    +
    +
    +

    Figure 8.2: Excerpts from the region property report.

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +scenario + +seed_index + +seed_value + +time + +region + +property + +value +
    +0 + +0 + +-7834265884293137617L + +0.0 + +Region_0 + +LAT + +45.08307901948476 +
    +0 + +0 + +-7834265884293137617L + +0.0 + +Region_0 + +LON + +128.5497267736204 +
    +0 + +0 + +-7834265884293137617L + +0.0 + +Region_1 + +LAT + +45.73317078392115 +
    +0 + +0 + +-7834265884293137617L + +0.0 + +Region_1 + +LON + +128.98292164396958 +
    +0 + +0 + +-7834265884293137617L + +0.0 + +Region_2 + +LAT + +45.74702447122078 +
    +0 + +0 + +-7834265884293137617L + +0.0 + +Region_2 + +LON + +128.5118606592755 +
    +0 + +0 + +-7834265884293137617L + +0.0 + +Region_3 + +LAT + +45.8303102139607 +
    +0 + +0 + +-7834265884293137617L + +0.0 + +Region_3 + +LON + +128.55192626408567 +
    +0 + +0 + +-7834265884293137617L + +0.0 + +Region_4 + +LAT + +45.59334365958997 +
    +0 + +0 + +-7834265884293137617L + +0.0 + +Region_4 + +LON + +128.7915941303198 +
    +0 + +0 + +-7834265884293137617L + +1.0 + +Region_5 + +LAT + +35.99754757587744 +
    +0 + +0 + +-7834265884293137617L + +1.0 + +Region_5 + +LON + +128.2594279657217 +
    +0 + +0 + +-7834265884293137617L + +21.0 + +Region_6 + +LAT + +35.84682720256188 +
    +0 + +0 + +-7834265884293137617L + +21.0 + +Region_6 + +LON + +128.2211833000355 +
    +0 + +0 + +-7834265884293137617L + +41.0 + +Region_7 + +LAT + +35.07267582475035 +
    +0 + +0 + +-7834265884293137617L + +41.0 + +Region_7 + +LON + +128.37457313813837 +
    +0 + +0 + +-7834265884293137617L + +50.0 + +Region_0 + +VACCINE_PRIORITY + +false +
    +0 + +0 + +-7834265884293137617L + +50.0 + +Region_1 + +VACCINE_PRIORITY + +false +
    +… + + + + + + +
    +4 + +4 + +-7584580254621783722L + +93.0 + +Region_2 + +VACCINE_PRIORITY + +false +
    +4 + +4 + +-7584580254621783722L + +94.0 + +Region_1 + +VACCINE_PRIORITY + +true +
    +4 + +4 + +-7584580254621783722L + +95.0 + +Region_2 + +VACCINE_PRIORITY + +true +
    +4 + +4 + +-7584580254621783722L + +96.0 + +Region_7 + +VACCINE_PRIORITY + +true +
    +4 + +4 + +-7584580254621783722L + +97.0 + +Region_0 + +VACCINE_PRIORITY + +false +
    +4 + +4 + +-7584580254621783722L + +98.0 + +Region_1 + +VACCINE_PRIORITY + +false +
    +4 + +4 + +-7584580254621783722L + +99.0 + +Region_0 + +VACCINE_PRIORITY + +true +
    +
    +
    +
    +
    +

    Finally, the vaccine report shows the number of people having various vaccine counts at the end of each simulation. Although the priority policy was being used, most vaccinations were for randomly selected people so we expect a fairly wide distribution in those values.

    +
    +
    +
    +

    Figure 8.3: The vaccine report shows vaccine counts at the end of each simulation.

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +scenario + +seed_index + +seed_value + +count_0 + +count_1 + +count_2 + +count_3 + +count_4 + +count_5 + +count_6+ +
    +0 + +0 + +-7834265884293137617L + +13 + +67 + +145 + +168 + +170 + +148 + +289 +
    +1 + +1 + +-7320358285742045393L + +13 + +63 + +141 + +181 + +187 + +133 + +282 +
    +2 + +2 + +-4619948863677044400L + +17 + +53 + +157 + +196 + +176 + +140 + +261 +
    +3 + +3 + +3282202756261196294L + +10 + +72 + +159 + +166 + +163 + +146 + +284 +
    +4 + +4 + +-7584580254621783722L + +4 + +66 + +153 + +198 + +163 + +136 + +280 +
    +
    +
    +
    +
    + + +
    + +
    + + +
    + + + + \ No newline at end of file diff --git a/doc/ch09-PersonPropertiesPlugin.html b/doc/ch09-PersonPropertiesPlugin.html new file mode 100644 index 000000000..5cfb98965 --- /dev/null +++ b/doc/ch09-PersonPropertiesPlugin.html @@ -0,0 +1,954 @@ + + + + + + + + + +GCM-Docs - 9  Person Properties Plugin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    + +
    + + + + + +
    + +
    +
    +

    9  Person Properties Plugin

    +
    + + + +
    + + + + +
    + + +
    + +
    + +
    +

    The person properties plugin manages the assignment of properties to individual people. As such, it is dependent on the people plugin. It also depends on the regions plugin for reports. Property definitions apply to every person in the simulation and are generally added to the person properties plugin data. However, person property definitions can be added dynamically and thus other plugins can contribute person property definitions directly rather than as inputs to the person properties plugin data.

    +
    +

    9.1 Plugin Data Initialization

    +

    The plugin is initialized using a PersonPropertiesPluginData object that collects person property definitions and person property value assignments for the initial population.

    +
    +
    +

    9.2 Plugin Behavior

    +

    The plugin adds a single data manager to the simulation as an instance of the PersonPropertiesDataManager that is initialized with the PersonPropertiesPluginData.

    +
    +
    +

    9.3 Data Manager

    +

    The data manager manages person properties and stores their property values in a memory dense fashion that is transparent to the modeler. The data manager provides public methods that:

    +
      +
    • Define person properties
    • +
    • Set person property values
    • +
    • Answer various questions about: +
        +
      • The value of a person property for particular people
      • +
      • The people associated with a particular property value
      • +
      • The existence and value of property definitions
      • +
    • +
    +

    The data manager also produces observable events:

    +
      +
    • PersonPropertyUpdateEvent – when a person is assigned a person property value
    • +
    • PersonPropertyDefintionEvent – when a new person property is defined
    • +
    +
    +
    +

    9.4 Example Code (Lesson 16)

    +

    Example_16.java shows the use of the person properties plugin. In it we will examine

    +
      +
    • The initialization of the person properties plugin
    • +
    • The assignment of values to individuals
    • +
    • The dynamic definition of person properties
    • +
    +

    The example includes six plugins:

    +
      +
    • People plugin – (GCM core plugin) used to manage people
    • +
    • Person properties plugin– (GCM core plugin) used to decorate properties onto people
    • +
    • Global properties plugin– (GCM core plugin) used to store policies and initial conditions affecting vaccination
    • +
    • Stochastics plugin – (GCM core plugin) used to generate random numbers used in various decisions
    • +
    • Regions Plugin – (GCM core plugin) used to represent regions
    • +
    • Model plugin – (local plugin) used to introduce three actors that will: +
        +
      • Load the population
      • +
      • Vaccinate people
      • +
      • Educate people on the vaccine
      • +
    • +
    +

    The example’s main method starts in Code Block 9.1 by creating an instance of the example class rather than building the experiment directly since this example is somewhat more complex than previous examples. The population starts out being unvaccinated and some proportion of people initially refuse the vaccine. Attempts to both vaccinate and educate people are ongoing until a person is vaccinated. This will demonstrate the planning capability as well. If education is successful, the person is immediately vaccinated, demonstrating the cancellation of planning. At some point in the timeline, immunity will become discoverable during vaccine attempts and immune people will no longer pursue vaccination. The simulation is terminated at one year and output reports are then generated.

    +
    +
    +

    Code Block 9.1: Executing example 16 with an output directory.

    +
    public static void main(String[] args) throws IOException {
    +    if (args.length == 0) {
    +        throw new RuntimeException("One output directory argument is required");
    +    }
    +    Path outputPath = Paths.get(args[0]);
    +    if (!Files.exists(outputPath)) {
    +        Files.createDirectory(outputPath);
    +    } else {
    +        if (!Files.isDirectory(outputPath)) {
    +            throw new IOException("Provided path is not a directory");
    +        }
    +    }
    +    new Example_16(outputPath).execute();
    +}
    +
    +
    +

    The execution method first gathers together the six plugins in Code Block 9.2:

    +
    +
    +

    Code Block 9.2: The various plugins are gathered from their initial data.

    +
    private void execute() {
    +
    +    /*
    +         * Create the global properties plugin
    +         */
    +    Plugin globalPropertiesPlugin = getGlobalPropertiesPlugin();
    +
    +    /*
    +         * Create the reports
    +         */
    +
    +    NIOReportItemHandler nioReportItemHandler = getNIOReportItemHandler();
    +
    +    /*
    +         * Create the people plugin filled with 1000 people
    +         */
    +    Plugin peoplePlugin = getPeoplePlugin();
    +
    +    /*
    +         * Create the region plugin 5 regions, each having a lat and lon and assign the
    +         * people to random regions.
    +         * 
    +         */
    +    Plugin regionsPlugin = getRegionsPlugin();
    +
    +    // Create the person properties plugin
    +    Plugin personPropertiesPlugin = getPersonPropertiesPlugin();
    +
    +    /*
    +         * create the stochastics plugin
    +         */
    +    Plugin stochasticsPlugin = getStochasticsPlugin();
    +
    +    Plugin modelPlugin = ModelPlugin.getModelPlugin();
    +
    +
    +

    The first action is to generate the global properties plugin, (Code Block 9.3). All of the global properties are marked as immutable since they will not change over the course of the simulation:

    +
      +
    • VACCINE_ATTEMPT_INTERVAL – The maximum time between attempts to vaccinate an unvaccinated person. Specific intervals are chosen using a uniform random time between zero and the maximum.
    • +
    • EDUCATION_ATTEMPT_INTERVAL – The maximum time between attempts to educate an unvaccinated person who is currently refusing vaccination. Specific intervals are chosen using a uniform random time between zero and the maximum.
    • +
    • VACCINE_REFUSAL_PROPBABILTY – The initial probability that a person will refuse vaccination. Used to initialize the person property REFUSES_VACCINE.
    • +
    • EDUCATION_SUCCESS_RATE – The probability that an attempt to educate a person to accept vaccination will succeed.
    • +
    • IMMUNITY_START_TIME – The time when immunity is detectable in people. Used to halt attempts at vaccination and to demonstrate the dynamic addition of the person property IS_IMMUNE.
    • +
    • IMMUNITY_PROBABILITY – The probability that a person will be immune when the IS_IMMUNE person property is added.
    • +
    • POPULATION_SIZE – The number of people in the simulation.
    • +
    • SIMULATION_DURATION – The maximum time (in days) that the simulation will execute.
    • +
    +
    +
    +

    Code Block 9.3: The global properties plugin is initialized with several properties.

    +
    private Plugin getGlobalPropertiesPlugin() {
    +    GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder();//
    +
    +    PropertyDefinition propertyDefinition = PropertyDefinition.builder()//
    +            .setType(Double.class)//
    +            .setDefaultValue(0.0)//
    +            .setPropertyValueMutability(false)//
    +            .build();
    +
    +    builder.defineGlobalProperty(GlobalProperty.IMMUNITY_START_TIME, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.VACCINE_ATTEMPT_INTERVAL, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.EDUCATION_ATTEMPT_INTERVAL, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.EDUCATION_SUCCESS_RATE, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.VACCINE_REFUSAL_PROBABILITY, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.IMMUNITY_PROBABILITY, propertyDefinition, 0);
    +
    +    propertyDefinition = PropertyDefinition.builder()//
    +            .setType(Double.class)//
    +            .setDefaultValue(365.0)//
    +            .setPropertyValueMutability(false)//
    +            .build();
    +    builder.defineGlobalProperty(GlobalProperty.SIMULATION_DURATION, propertyDefinition, 0);
    +
    +    propertyDefinition = PropertyDefinition.builder()//
    +            .setType(Integer.class)//
    +            .setDefaultValue(1000)//
    +            .setPropertyValueMutability(false)//
    +            .build();
    +
    +    builder.defineGlobalProperty(GlobalProperty.POPULATION_SIZE, propertyDefinition, 0);
    +
    +    GlobalPropertiesPluginData globalPropertiesPluginData = builder.build();
    +
    +    return GlobalPropertiesPlugin.builder().setGlobalPropertiesPluginData(globalPropertiesPluginData)
    +            .getGlobalPropertiesPlugin();
    +
    +}
    +
    +
    +

    The execution method then loads reports (Code Block 9.4). The person property report will be quite large and is set to only show the state of each person at the end of the simulation for brevity. The vaccine report will show the state of vaccination and immunity at the end of the simulation to allow for analysis of the experiment.

    +

    The people plugin is created without any initial people since that will be handled by one of the model plugin’s actors. The regions plugin is initialized with five regions and only plays a role in the person property report.

    +

    The person properties plugin is generated in Code Block 9.4. All of the person properties are mutable since they will change over the course of the simulation:

    +
      +
    • EDUCATION_ATTEMPTS – The number of attempts to change a person’s vaccine refusal.
    • +
    • VACCINE_ATTEMPTS – The number of attempts to vaccinate a person.
    • +
    • REFUSES_VACCINE – Boolean indicating whether the person will refuse vaccination attempts. Note that there is no default value and that new people must have this property set as part of the addition of the person to the simulation.
    • +
    • VACCINATED – Boolean indicating that a person has been vaccinated. People all start out with no vaccination and receive at most one vaccination.
    • +
    +

    Note that the final person property, IS_IMMUNE, is not added at the beginning of the simulation as a demonstration of the dynamic addition of person properties.

    +
    +
    +

    Code Block 9.4: The person properties plugin is built with the four person properties needed to model each person. The person property report is set to report only at the end of the simulation.

    +
    private Plugin getPersonPropertiesPlugin() {
    +    PersonPropertiesPluginData.Builder builder = PersonPropertiesPluginData.builder();
    +    PropertyDefinition propertyDefinition = PropertyDefinition.builder()//
    +            .setType(Integer.class)//
    +            .setDefaultValue(0)//
    +            .build();
    +    builder.definePersonProperty(PersonProperty.EDUCATION_ATTEMPTS, propertyDefinition, 0, false);
    +    builder.definePersonProperty(PersonProperty.VACCINE_ATTEMPTS, propertyDefinition, 0, false);
    +
    +    propertyDefinition = PropertyDefinition.builder()//
    +            .setType(Boolean.class)//
    +            .build();
    +    builder.definePersonProperty(PersonProperty.REFUSES_VACCINE, propertyDefinition, 0, false);
    +
    +    propertyDefinition = PropertyDefinition.builder()//
    +            .setType(Boolean.class)//
    +            .setDefaultValue(false)//
    +            .build();
    +    builder.definePersonProperty(PersonProperty.VACCINATED, propertyDefinition, 0, false);
    +
    +    PersonPropertiesPluginData personPropertiesPluginData = builder.build();
    +
    +    PersonPropertyReportPluginData personPropertyReportPluginData = PersonPropertyReportPluginData.builder()//
    +            .setReportLabel(ModelReportLabel.PERSON_PROPERTY_REPORT)//
    +            .setReportPeriod(ReportPeriod.END_OF_SIMULATION)//
    +            .setDefaultInclusion(true)//
    +            .build();//
    +
    +    return PersonPropertiesPlugin.builder()//
    +            .setPersonPropertiesPluginData(personPropertiesPluginData)//
    +            .setPersonPropertyReportPluginData(personPropertyReportPluginData)//
    +            .getPersonPropertyPlugin();
    +}
    +
    +
    +

    Adding the stochastics plugin involves setting only the seed that will be used in every simulation instance. It will not play a role in defining the experiment space since that will be quite large already with various global property variants. Finally, the execution method generates the model plugin which in turn adds three actors:

    +
      +
    • Vaccinator – vaccinates people at random times
    • +
    • Vaccine Educator – seeks to get people to accept vaccination
    • +
    • Population Loader – initializes the population
    • +
    +

    The execute method finishes (Code Block 9.5) by constructing and executing the experiment.

    +
    +
    +

    Code Block 9.5: The experiment executes 810 scenarios on 8 threads.

    +
            ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()//
    +                .setThreadCount(8)//
    +                .build();
    +
    +        Experiment.builder()//
    +
    +                .addPlugin(personPropertiesPlugin)//
    +                .addPlugin(globalPropertiesPlugin)//
    +                .addPlugin(modelPlugin)//
    +                .addPlugin(regionsPlugin)//
    +                .addPlugin(peoplePlugin)//
    +                .addPlugin(stochasticsPlugin)//
    +
    +                .addDimension(getImmunityStartTimeDimension())//
    +                .addDimension(getImmunityProbabilityDimension())//
    +                .addDimension(getVaccineAttemptIntervalDimension())//
    +                .addDimension(getEducationAttemptIntervalDimension())//
    +                .addDimension(getEducationSuccessRatedimension())//
    +                .addDimension(getVaccineRefusalProbabilityDimension())//
    +                .addExperimentContextConsumer(nioReportItemHandler)//
    +                .setExperimentParameterData(experimentParameterData)//
    +                .build()//
    +                .execute();//
    +
    +
    +

    Five dimensions are added to the experiment that define alternate values for five of the global properties resulting in 810 scenarios. These values are:

    +
      +
    • Immunity start time – 120 and 180 days
    • +
    • Immunity probability – 0, 10 and 20 percent
    • +
    • Vaccine attempt interval – 30, 45 and 60 days
    • +
    • Education attempt interval – 30, 60 and 180 days
    • +
    • Education success rate – 0, 10 and 20 percent
    • +
    • Initial vaccine refusal – 0, 25, 50, 75 and 100 percent
    • +
    +
    +
    +

    9.5 The actors

    +

    We will finish this chapter by reviewing the three actors of the model plugin and then examining the vaccine report.

    +

    The PopulationLoader actor, in Code Block 9.6, adds people to the simulation based on the number in the POPULATION_SIZE global property. Each person is assigned a random region and the person property, REFUSES_VACCINE, is randomly assigned based on the global property VACCINE_REFUSAL_PROBABILITY.

    +
    +
    +

    Code Block 9.6: The population loader initializes by creating people dictated by the POPULATION_SIZE global property. Each person is assigned a region and random value for the person property, REFUSES_VACCINE, based on the global property, VACCINE_REFUSAL_PROBABILITY.

    +
    public void init(ActorContext actorContext) {
    +    peopleDataManager = actorContext.getDataManager(PeopleDataManager.class);
    +    personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class);
    +    globalPropertiesDataManager = actorContext.getDataManager(GlobalPropertiesDataManager.class);
    +    RegionsDataManager regionsDataManager = actorContext.getDataManager(RegionsDataManager.class);
    +    StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);
    +    randomGenerator = stochasticsDataManager.getRandomGenerator();
    +    List<RegionId> regionIds = new ArrayList<>(regionsDataManager.getRegionIds());
    +
    +    int populationSize = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.POPULATION_SIZE);
    +    double refusalProbability = globalPropertiesDataManager
    +            .getGlobalPropertyValue(GlobalProperty.VACCINE_REFUSAL_PROBABILITY);
    +
    +    Builder personConstructionDataBuilder = PersonConstructionData.builder();
    +    for (int i = 0; i < populationSize; i++) {
    +        RegionId regionId = regionIds.get(randomGenerator.nextInt(regionIds.size()));
    +        personConstructionDataBuilder.add(regionId);
    +
    +        boolean refusesVaccine = randomGenerator.nextDouble() < refusalProbability;
    +        PersonPropertyValueInitialization personPropertyInitialization = new PersonPropertyValueInitialization(
    +                PersonProperty.REFUSES_VACCINE, refusesVaccine);
    +        personConstructionDataBuilder.add(personPropertyInitialization);
    +        PersonConstructionData personConstructionData = personConstructionDataBuilder.build();
    +        peopleDataManager.addPerson(personConstructionData);
    +    }
    +
    +    double simulationDuration = globalPropertiesDataManager
    +            .getGlobalPropertyValue(GlobalProperty.SIMULATION_DURATION);
    +    actorContext.addPlan((c) -> c.halt(), simulationDuration);
    +
    +    double immunityStartTime = globalPropertiesDataManager
    +            .getGlobalPropertyValue(GlobalProperty.IMMUNITY_START_TIME);
    +    actorContext.addPlan((c) -> addImmunityProperty(), immunityStartTime);
    +}
    +
    +
    +

    The actor finishes its initialization by scheduling a time to halt the simulation based on the global property, SIMULATION_DURATION. It also schedules the addition of the person property, IS_IMMUNE, based on the global property, IMMUNITY_START_TIME. Code Block 9.7 shows the details of this dynamic definition.

    +
    +
    +

    Code Block 9.7: At the time set via the global property, IMMUNITY_START_TIME, the population loader defines the person property, IS_IMMUNE, and sets the property value for each person.

    +
    private void addImmunityProperty() {
    +    PersonPropertyDefinitionInitialization.Builder builder = PersonPropertyDefinitionInitialization.builder();
    +    builder.setPersonPropertyId(PersonProperty.IS_IMMUNE);
    +    PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Boolean.class).build();
    +    builder.setPropertyDefinition(propertyDefinition);
    +    double immunityProbability = globalPropertiesDataManager
    +            .getGlobalPropertyValue(GlobalProperty.IMMUNITY_PROBABILITY);
    +
    +    for (PersonId personId : peopleDataManager.getPeople()) {
    +        boolean isImmune = randomGenerator.nextDouble() < immunityProbability;
    +        builder.addPropertyValue(personId, isImmune);
    +    }
    +    PersonPropertyDefinitionInitialization personPropertyDefinitionInitialization = builder.build();
    +    personPropertiesDataManager.definePersonProperty(personPropertyDefinitionInitialization);
    +}
    +
    +
    +

    The vaccine educator (Code Block 9.8) attempts to change unvaccinated people who refuse vaccination to vaccine acceptance. It initializes by planning an educational attempt for each person in the existing population who has not been vaccinated and who will refuse vaccination. It also subscribes to the addition of people so that it might plan education for newly added people.

    +
    +
    +

    Code Block 9.8: The vaccine educator initializes by planning the education for each person who refuses vaccination.

    +
    public void init(ActorContext actorContext) {
    +    this.actorContext = actorContext;
    +
    +    StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);
    +    randomGenerator = stochasticsDataManager.getRandomGenerator();
    +    PeopleDataManager peopleDataManager = actorContext.getDataManager(PeopleDataManager.class);
    +    personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class);
    +    globalPropertiesDataManager = actorContext.getDataManager(GlobalPropertiesDataManager.class);
    +
    +    educationAttemptInterval = globalPropertiesDataManager
    +            .getGlobalPropertyValue(GlobalProperty.EDUCATION_ATTEMPT_INTERVAL);
    +    educationSuccessRate = globalPropertiesDataManager
    +            .getGlobalPropertyValue(GlobalProperty.EDUCATION_SUCCESS_RATE);
    +
    +    List<PersonId> unvaccinatedPeople = personPropertiesDataManager
    +            .getPeopleWithPropertyValue(PersonProperty.VACCINATED, false);
    +    for (PersonId personId : unvaccinatedPeople) {
    +        Boolean refusesVaccine = personPropertiesDataManager.getPersonPropertyValue(personId,
    +                PersonProperty.REFUSES_VACCINE);
    +        if (refusesVaccine) {
    +            planEducation(personId);
    +        }
    +    }
    +
    +    actorContext.subscribe(peopleDataManager.getEventFilterForPersonAdditionEvent(), (c, e) -> {
    +        handleNewPerson(e.personId());
    +    });
    +}
    +
    +
    +

    The education of a person (Code Block 9.9) is accomplished with planning that schedules the education at a random time between the current time and a globally defined attempt interval.

    +
    +
    +

    Code Block 9.9: Attempts to educate a person on vaccination are scheduled at random times in the future based on the global property, EDUCATION_ATTEMPT_INTERVAL.

    +
    private void planEducation(PersonId personId) {
    +    double planTime = actorContext.getTime() + randomGenerator.nextDouble() * educationAttemptInterval;
    +    Consumer<ActorContext> plan = (c) -> educatePerson(personId);
    +    actorContext.addPlan(plan, planTime);
    +}
    +
    +private void handleNewPerson(PersonId personId) {
    +    boolean vaccinated = personPropertiesDataManager.getPersonPropertyValue(personId, PersonProperty.VACCINATED);
    +    if (!vaccinated) {
    +        Boolean refusesVaccine = personPropertiesDataManager.getPersonPropertyValue(personId,
    +                PersonProperty.REFUSES_VACCINE);
    +        if (refusesVaccine) {
    +            planEducation(personId);
    +        }
    +    }
    +}
    +
    +
    +

    The education attempt sets the vaccine refusal to false on a random draw based on the EDUCATION_SUCCESS_RATE global variable in Code Block 9.10.

    +
    +
    +

    Code Block 9.10: After updating the number of educational attempts for a person, the vaccine educator succeeds in educating the person to stop refusing vaccination based on the global property, EDUCATION_SUCCESS_RATE.

    +
    private void educatePerson(PersonId personId) {
    +    int educationAttempts = personPropertiesDataManager.getPersonPropertyValue(personId,
    +            PersonProperty.EDUCATION_ATTEMPTS);
    +    personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.EDUCATION_ATTEMPTS,
    +            educationAttempts + 1);
    +
    +    if (randomGenerator.nextDouble() < educationSuccessRate) {
    +        personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.REFUSES_VACCINE, false);
    +    } else {
    +        planEducation(personId);
    +    }
    +}
    +
    +
    +

    The Vaccinator (Code Block 9.11) tries to vaccinate the population. It initializes by planning a vaccination attempt for each person in the existing population who has not yet been vaccinated. It subscribes to the addition of people so that it might plan vaccination for newly added people. It also subscribes to changes to the VACCINE_REFUSAL person property so that it can immediately attempt vaccination.

    +
    +
    +

    Code Block 9.11: The vaccinator initializes by planning vaccination attempts for each person who is unvaccinated. It also subscribes to changes in the vaccine refusal property for all people so that when a person stops refusing vaccine, they can be vaccinated immediately.

    +
    public void init(ActorContext actorContext) {
    +    this.actorContext = actorContext;
    +    StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);
    +    randomGenerator = stochasticsDataManager.getRandomGenerator();
    +    peopleDataManager = actorContext.getDataManager(PeopleDataManager.class);
    +    personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class);
    +    globalPropertiesDataManager = actorContext.getDataManager(GlobalPropertiesDataManager.class);
    +
    +    List<PersonId> unvaccinatedPeople = personPropertiesDataManager
    +            .getPeopleWithPropertyValue(PersonProperty.VACCINATED, false);
    +    vaccineAttemptInterval = globalPropertiesDataManager
    +            .getGlobalPropertyValue(GlobalProperty.VACCINE_ATTEMPT_INTERVAL);
    +    for (PersonId personId : unvaccinatedPeople) {
    +        planVaccination(personId);
    +    }
    +
    +    EventFilter<PersonPropertyUpdateEvent> eventFilter = personPropertiesDataManager//
    +            .getEventFilterForPersonPropertyUpdateEvent(PersonProperty.REFUSES_VACCINE);
    +
    +    actorContext.subscribe(eventFilter, this::handleVaccineAcceptance);
    +
    +    actorContext.subscribe(peopleDataManager.getEventFilterForPersonAdditionEvent(), (c, e) -> {
    +        handleNewPerson(e.personId());
    +    });
    +
    +}
    +
    +
    +

    Vaccination of a person (Code Block 9.12) is accomplished with planning that schedules the vaccination at a random time between the current time and a globally defined attempt interval.

    +
    +
    +

    Code Block 9.12: Each unvaccinated person has a planned vaccination based on the VACCINE_ATTEMPT_INTERVAL global property.

    +
    private void planVaccination(PersonId personId) {
    +    double planTime = actorContext.getTime() + randomGenerator.nextDouble() * vaccineAttemptInterval;
    +    Object planKey = personId;
    +    Plan<ActorContext> plan = Plan.builder(ActorContext.class)//
    +            .setCallbackConsumer((c) -> vaccinatePerson(personId))//
    +            .setKey(planKey)//
    +            .setTime(planTime)//
    +            .build();//
    +    actorContext.addPlan(plan);
    +}
    +
    +private void handleNewPerson(PersonId personId) {
    +    boolean vaccinated = personPropertiesDataManager.getPersonPropertyValue(personId, PersonProperty.VACCINATED);
    +    if (!vaccinated) {
    +        planVaccination(personId);
    +    }
    +}
    +
    +
    +

    Note that the plan uses a key value set to the person id. This is used when reacting to a person changing from refusal of the vaccine to acceptance. Instead of waiting for the next vaccine attempt (Code Block 9.13), the current plan to vaccinate is removed and the person is immediately vaccinated.

    +
    +
    +

    Code Block 9.13: When a person stops refusing vaccination, the vaccinator immediately attempts the vaccination of that person.

    +
    private void handleVaccineAcceptance(ActorContext actorContext,
    +        PersonPropertyUpdateEvent personPropertyUpdateEvent) {
    +    /*
    +         * We know that the person property is PersonProperty.REFUSES_VACCINE since we
    +         * used an event filter when subscribing
    +         */
    +    Boolean refusesVaccine = personPropertyUpdateEvent.getCurrentPropertyValue();
    +    if (!refusesVaccine) {
    +        PersonId personId = personPropertyUpdateEvent.personId();
    +        // drop the current plan
    +        actorContext.removePlan(personId);
    +        vaccinatePerson(personId);
    +    }
    +}
    +
    +
    +

    The vaccination attempt (Code Block 9.14) first considers whether the IS_IMMUNE property has been added. If it has then immunity for the person is determined. Immune people do not receive the vaccine and no more attempts to vaccinate the person will be scheduled. If the person is still refusing the vaccine, then a new attempt to vaccinate the person is scheduled. Otherwise the person is vaccinated and no further attempts are scheduled.

    +
    +
    +

    Code Block 9.14: With each vaccination attempt, the vaccinator updates the VACCINE_ATTEMPTS person property for the person. People who refuse vaccination are scheduled for another vaccination attempt.

    +
    private void vaccinatePerson(PersonId personId) {
    +    int vaccineAttempts = personPropertiesDataManager.getPersonPropertyValue(personId,
    +            PersonProperty.VACCINE_ATTEMPTS);
    +    personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.VACCINE_ATTEMPTS,
    +            vaccineAttempts + 1);
    +
    +    boolean isImmune = false;
    +    if (personPropertiesDataManager.personPropertyIdExists(PersonProperty.IS_IMMUNE)) {
    +        isImmune = personPropertiesDataManager.getPersonPropertyValue(personId, PersonProperty.IS_IMMUNE);
    +    }
    +
    +    Boolean refusesVaccine = personPropertiesDataManager.getPersonPropertyValue(personId,
    +            PersonProperty.REFUSES_VACCINE);
    +    if (!isImmune) {
    +        if (refusesVaccine) {
    +            double planTime = actorContext.getTime() + randomGenerator.nextDouble() * vaccineAttemptInterval;
    +            Object planKey = personId;
    +
    +            Plan<ActorContext> plan = Plan.builder(ActorContext.class)//
    +                    .setCallbackConsumer((c) -> vaccinatePerson(personId))//
    +                    .setKey(planKey)//
    +                    .setTime(planTime)//
    +                    .build();//
    +
    +            actorContext.addPlan(plan);
    +        } else {
    +            personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.VACCINATED, true);
    +        }
    +    }
    +}
    +
    +
    +
    +
    +

    9.6 Inspecting the output

    +

    The 810 scenarios result in a large amount of output in the person properties report with over 125,000 entries. The vaccine report is a bit too large to fully present here. Its fields are:

    +
      +
    • Scenario – 0 to 809
    • +
    • Experiment fields that show what differentiates each scenario +
        +
      • immunity start time
      • +
      • immunity probabilty
      • +
      • vaccine attempt interval
      • +
      • education attempt interval
      • +
      • education success rate
      • +
      • intial refusal probability
      • +
    • +
    • The metric fields produced as a result of the experiment choices +
        +
      • vaccinated immune
      • +
      • vaccinated susceptible
      • +
      • unvaccinated immune
      • +
      • unvaccinated susceptible
      • +
    • +
    +

    Analyzing the output yields no surprises. Higher education attempt rates and greater probabilities of education success yield more people getting vaccinated. Similarly, early and high levels of immunity have a slight dampening effect on vaccinations.

    + + +
    + +
    + + +
    + + + + \ No newline at end of file diff --git a/doc/ch10-GroupsPlugins.html b/doc/ch10-GroupsPlugins.html new file mode 100644 index 000000000..8e31c7176 --- /dev/null +++ b/doc/ch10-GroupsPlugins.html @@ -0,0 +1,1263 @@ + + + + + + + + + +GCM-Docs - 10  Groups Plugin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    + +
    + + + + + +
    + +
    +
    +

    10  Groups Plugin

    +
    + + + +
    + + + + +
    + + +
    + +
    + +
    +

    The groups plugin manages the assignment people to various groups such as homes, schools, work places and other social organizations. As such, it is dependent on the people plugin. Like people, groups can have property definitions. Unlike people, groups have a modeler-defined group type that dictates which group properties apply.

    +
    +

    10.1 Plugin Data Dependency

    +

    The groups plugin depends on the people plugin and the stochastics plugin. The stochastics plugin is used for random sampling of people from groups.

    +
    +
    +

    10.2 Plugin Data initialization

    +

    The plugin is initialized using a GroupsPluginData object that:

    +
      +
    • adds group types
    • +
    • defines group properties for each group type
    • +
    • sets group property value per group
    • +
    • adds groups
    • +
    • initializes the person membership in groups
    • +
    +
    +
    +

    10.3 Plugin Behavior

    +

    The plugin adds a single data manager to the simulation as an instance of the GroupsDataManager that is initialized with the GroupsPluginData.

    +
    +
    +

    10.4 Data Manager

    +

    The data manager manages group memberships and group property values. The data manager provides public methods that:

    +
      +
    • Add/Remove a group
    • +
    • Add a group type
    • +
    • Add/Remove a person to/from a group
    • +
    • Define a group property
    • +
    • Set a group property value
    • +
    • Sample a random person from a group
    • +
    • Provide many queries about the state of groups
    • +
    +

    The data manager also produces observable events:

    +
      +
    • GroupAdditionEvent – when a group is added
    • +
    • GroupImminentRemovalEvent – when a group is about to be removed
    • +
    • GroupMembershipAdditionEvent – when a person is added to a group
    • +
    • GroupMembershipRemovalEvent – when a person is removed from a group
    • +
    • GroupPropertyDefinitionEvent – when a new group property is defined
    • +
    • GroupPropertyUpdateEvent – when a group property value is updated
    • +
    • GroupTypeAdditionEvent – when a new group type is added
    • +
    +
    +
    +

    10.5 Example Code (Lesson 17)

    +

    Example_17.java shows the use of the groups plugin. In it we will examine:

    +
      +
    • The initialization of the groups plugin
    • +
    • The movement of people in and out of groups
    • +
    +

    The example includes seven plugins:

    +
      +
    • Groups plugin – (GCM core plugin) used to manage groups
    • +
    • People plugin – (GCM core plugin) used to manage people
    • +
    • Person properties plugin – (GCM core plugin) used to decorate properties onto people
    • +
    • Global properties plugin – (GCM core plugin) used to store policies and initial conditions affecting groups and disease transmission
    • +
    • Stochastics plugin – (GCM core plugin) used to generate random numbers used in various decisions
    • +
    • Regions Plugin – (GCM core plugin) used for various reports
    • +
    • Model plugin – (local plugin) used to introduce four actors that will: +
        +
      • Load the population
      • +
      • Manage the spread of disease through transmission in groups
      • +
      • Manage group-based mitigation strategies in schools
      • +
      • Manage group-based mitigation strategies in work places
      • +
    • +
    +
    +
    +

    10.6 Model

    +

    The example’s model represents a disease that is not treatable but can be mitigated through social distancing, school closures and the use of telework arrangements. People at the start of the simulation are either immune or susceptible. Infection is spread through personal contact in groups and is subject to public policies triggered by the number of infectious people present in the total population or in specific groups. The population is set to 10,000 people and the groups are synthetically derived using fixed proportions (global properties) for school aged children (0-18 yrs), working adults (19 to 64 yrs) and non-working seniors (65+yrs). All people are assigned a home group. School aged children are assigned to a single school group and working age adults are assigned to a single work group. The number of each group type is determined by fixed expected groups sizes (global properties).

    +

    At the beginning of the model, a small number of adults are infected at random. Each infected person is immediately infectious and will infect one person per day over a random period from 3 to 12 days inclusive. Without mitigations, each transmission is successful. Public policies are reviewed on a weekly basis. As the number of total public infections increase, some work groups may elect to move to a telework status, reducing transmissions by 50% in those groups. Similarly, when a school’s level of infection increases it will move to a split cohort mode, reducing transmission success to 50%. If infection within a cohort continues to rise, the cohort is closed and the children are no longer assigned to a school group. For simplicity of modeling, telework arrangements, cohort schools and school closures do not revert as infection levels subside.

    +
    +
    +

    10.7 Model Execution

    +

    The example’s execution is shown in Code Block 10.1 and Code Block 10.2.

    +
    +
    +

    Code Block 10.1: Executing example 17 with an output directory.

    +
    public static void main(String[] args) throws IOException {
    +    if (args.length == 0) {
    +        throw new RuntimeException("One output directory argument is required");
    +    }
    +    Path outputDirectory = Paths.get(args[0]);
    +    if (!Files.exists(outputDirectory)) {
    +        Files.createDirectory(outputDirectory);
    +    } else {
    +        if (!Files.isDirectory(outputDirectory)) {
    +            throw new IOException("Provided path is not a directory");
    +        }
    +    }
    +
    +    new Example_17(outputDirectory).execute();
    +}
    +
    +
    +
    +
    +

    Code Block 10.2: The various plugins are gathered from their initial data, dimensions are added and the experiment is executed over 36 scenarios using 8 threads.

    +
    private void execute() {
    +
    +    ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()//
    +            .setThreadCount(8)//
    +            .build();
    +
    +    Experiment.builder()
    +
    +            .addPlugin(getGlobalPropertiesPlugin())//
    +            .addPlugin(getPersonPropertiesPlugin())//
    +            .addPlugin(getRegionsPlugin())//
    +            .addPlugin(getPeoplePlugin())//
    +            .addPlugin(getGroupsPlugin())//
    +            .addPlugin(getStochasticsPlugin())//
    +            .addPlugin(ModelPlugin.getModelPlugin())//
    +
    +            .addDimension(getTeleworkProbabilityDimension())//
    +            .addDimension(getTeleworkInfectionThresholdDimension())//
    +            .addDimension(getSchoolDimension())//
    +
    +            .addExperimentContextConsumer(getNIOReportItemHandler())//
    +            .setExperimentParameterData(experimentParameterData)//              
    +            .build()//
    +            .execute();//
    +}
    +
    +
    +

    The first action is to load the global properties plugin (Code Block 10.3). The fifteen global properties are marked as immutable since they will not change over the course of the simulation. Further, eleven of the properties are fixed and their values are set in the plugin data. The four remaining properties participate in the dimensions of the experiment and are not directly set in the global plugin.

    +
      +
    • SUSCEPTIBLE_POPULATION_PROPORTION – The fraction of the population that is susceptible
    • +
    • INITIAL_INFECTIONS – The number of adults initially infected
    • +
    • MIN_INFECTIOUS_PERIOD – The minimum number of days a person is infectious
    • +
    • MAX_INFECTIOUS_PERIOD – The maximum number of days a person is infectious
    • +
    • POPULATION_SIZE – The initial size of the population
    • +
    • CHILD_POPULATION_PROPORTION – The fraction of the population between the ages of 0 and 18, inclusive
    • +
    • SENIOR_POPULATION_PROPORTION – The fraction of the population 65 and older
    • +
    • R0 – The expected number of people a single person will infect if all contacts are susceptible and transmission success is 100% likely
    • +
    • AVERAGE_HOME_SIZE – The average number of people per household
    • +
    • AVERAGE_SCHOOL_SIZE – The average number of students in a school
    • +
    • AVERAGE_WORK_SIZE – The average number of people per work place
    • +
    • TELEWORK_INFECTION_THRESHOLD – The total infection density that triggers some work places to institute telework mode
    • +
    • TELEWORK_PROBABILTY – The probability that a work place will convert to telework mode once telework is allowed
    • +
    • SCHOOL_COHORT_INFECTION_THRESHOLD – The infection density within a school that triggers the school moving to a split cohort
    • +
    • SCHOOL_CLOSURE_INFECTION_THRESHOLD – The infection density within a school cohort that triggers the closure of that cohort.
    • +
    +
    +
    +

    Code Block 10.3: The global properties plugin is initialized with several properties

    +
    private Plugin getGlobalPropertiesPlugin() {
    +    GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder();//
    +
    +    PropertyDefinition propertyDefinition = PropertyDefinition.builder()//
    +            .setType(Double.class)//
    +            .setPropertyValueMutability(false)//
    +            .setDefaultValue(0.0).build();
    +
    +    builder.defineGlobalProperty(GlobalProperty.SUSCEPTIBLE_POPULATION_PROPORTION, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.AVERAGE_HOME_SIZE, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.AVERAGE_SCHOOL_SIZE, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.AVERAGE_WORK_SIZE, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.CHILD_POPULATION_PROPORTION, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.SENIOR_POPULATION_PROPORTION, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.R0, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.TELEWORK_INFECTION_THRESHOLD, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.TELEWORK_PROBABILTY, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.SCHOOL_COHORT_INFECTION_THRESHOLD, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.SCHOOL_CLOSURE_INFECTION_THRESHOLD, propertyDefinition, 0);
    +
    +    propertyDefinition = PropertyDefinition.builder()//
    +            .setType(Integer.class)//
    +            .setPropertyValueMutability(false)//
    +            .build();
    +    builder.defineGlobalProperty(GlobalProperty.INITIAL_INFECTIONS, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.MIN_INFECTIOUS_PERIOD, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.MAX_INFECTIOUS_PERIOD, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.POPULATION_SIZE, propertyDefinition, 0);
    +
    +    builder.setGlobalPropertyValue(GlobalProperty.POPULATION_SIZE, 10_000, 0);
    +    builder.setGlobalPropertyValue(GlobalProperty.SUSCEPTIBLE_POPULATION_PROPORTION, 1.0, 0);
    +    builder.setGlobalPropertyValue(GlobalProperty.INITIAL_INFECTIONS, 10, 0);
    +    builder.setGlobalPropertyValue(GlobalProperty.MIN_INFECTIOUS_PERIOD, 3, 0);
    +    builder.setGlobalPropertyValue(GlobalProperty.MAX_INFECTIOUS_PERIOD, 12, 0);
    +    builder.setGlobalPropertyValue(GlobalProperty.R0, 2.0, 0);
    +    builder.setGlobalPropertyValue(GlobalProperty.CHILD_POPULATION_PROPORTION, 0.235, 0);
    +    builder.setGlobalPropertyValue(GlobalProperty.SENIOR_POPULATION_PROPORTION, 0.169, 0);
    +    builder.setGlobalPropertyValue(GlobalProperty.AVERAGE_HOME_SIZE, 2.5, 0);
    +    builder.setGlobalPropertyValue(GlobalProperty.AVERAGE_SCHOOL_SIZE, 250.0, 0);
    +    builder.setGlobalPropertyValue(GlobalProperty.AVERAGE_WORK_SIZE, 30.0, 0);
    +
    +    GlobalPropertiesPluginData globalPropertiesPluginData = builder.build();
    +
    +    return GlobalPropertiesPlugin.builder().setGlobalPropertiesPluginData(globalPropertiesPluginData)
    +            .getGlobalPropertiesPlugin();
    +}
    +
    +
    +

    The person properties plugin is loaded in (Code Block 10.4). The properties are:

    +
      +
    • AGE – An integer value for the age of a person: child (0-18), adult (19-64), senior (65+)
    • +
    • DISEASE_STATE – The state for a person: IMMUNE, SUSCEPTIBLE, INFECTIOUS, RECOVERED
    • +
    • INFECTED_COUNT – The number of people infected by each particular person
    • +
    +
    +
    +

    Code Block 10.4: The person properties plugin is initialized with several properties.

    +
    private Plugin getPersonPropertiesPlugin() {
    +
    +    PersonPropertiesPluginData.Builder builder = PersonPropertiesPluginData.builder();
    +
    +    PropertyDefinition propertyDefinition = PropertyDefinition.builder()//
    +            .setType(Integer.class)//
    +            .build();
    +
    +    builder.definePersonProperty(PersonProperty.AGE, propertyDefinition, 0, false);//
    +
    +    propertyDefinition = PropertyDefinition.builder()//
    +            .setType(DiseaseState.class)//
    +            .setDefaultValue(DiseaseState.SUSCEPTIBLE).build();
    +
    +    builder.definePersonProperty(PersonProperty.DISEASE_STATE, propertyDefinition, 0, false);//
    +
    +    propertyDefinition = PropertyDefinition.builder()//
    +            .setType(Integer.class)//
    +            .setDefaultValue(0).build();
    +
    +    builder.definePersonProperty(PersonProperty.INFECTED_COUNT, propertyDefinition, 0, false);//
    +
    +    PersonPropertiesPluginData personPropertiesPluginData = builder.build();
    +
    +    PersonPropertyReportPluginData personPropertyReportPluginData = PersonPropertyReportPluginData.builder()//
    +            .setReportLabel(ModelReportLabel.PERSON_PROPERTY)//
    +            .setReportPeriod(ReportPeriod.DAILY)//
    +            .includePersonProperty(PersonProperty.DISEASE_STATE)//
    +            .build();
    +
    +    return PersonPropertiesPlugin.builder()//
    +            .setPersonPropertiesPluginData(personPropertiesPluginData)//
    +            .setPersonPropertyReportPluginData(personPropertyReportPluginData)//
    +            .getPersonPropertyPlugin();
    +}
    +
    +
    +

    The groups plugin (Code Block 10.5) loads the group types and their corresponding group properties. The creation of groups is left to the Population Loader.

    +
      +
    • HOME
    • +
    • SCHOOL +
        +
      • SCHOOL_STATUS : OPEN, COHORT (50% transmission reduction), CLOSED (100% transmission reduction)
      • +
    • +
    • WORK +
        +
      • TELEWORK – Boolean designating work place as telework, reducing transmission by 50%
      • +
    • +
    +
    +
    +

    Code Block 10.5: The groups plugin includes a tele-work property for work places and open status properties for schools.

    +
    private Plugin getGroupsPlugin() {
    +    GroupsPluginData.Builder builder = GroupsPluginData.builder();
    +    for (GroupType groupType : GroupType.values()) {
    +        builder.addGroupTypeId(groupType);
    +    }
    +    PropertyDefinition propertyDefinition = PropertyDefinition.builder()//
    +            .setType(Boolean.class)//
    +            .setDefaultValue(false)//
    +            .build();
    +
    +    builder.defineGroupProperty(GroupType.WORK, GroupProperty.TELEWORK, propertyDefinition);
    +
    +    propertyDefinition = PropertyDefinition.builder()//
    +            .setType(SchoolStatus.class)//
    +            .setDefaultValue(SchoolStatus.OPEN)//
    +            .build();
    +
    +    builder.defineGroupProperty(GroupType.SCHOOL, GroupProperty.SCHOOL_STATUS, propertyDefinition);
    +
    +    GroupsPluginData groupsPluginData = builder.build();
    +
    +    GroupPopulationReportPluginData groupPopulationReportPluginData = //
    +            GroupPopulationReportPluginData.builder()//
    +                    .setReportLabel(ModelReportLabel.GROUP_POPULATON)//
    +                    .setReportPeriod(ReportPeriod.END_OF_SIMULATION)//
    +                    .build();//
    +    return GroupsPlugin.builder()//
    +            .setGroupsPluginData(groupsPluginData)//
    +            .setGroupPopulationReportPluginData(groupPopulationReportPluginData)//
    +            .getGroupsPlugin();
    +}
    +
    +
    +

    The stochastics plugin is initialized with a random seed and all simulations will start in the same stochastic state. The regions plugin has five regions and the people plugin is loaded in an empty state. People are added by an actor in the model plugin.

    +

    The model plugin adds four actors

    +
      +
    • PopulationLoader – Adds people and groups to the simulation and initializes immunity.
    • +
    • InfectionManager– Infects a small number of randomly chosen adults. Manages the progress of infections and the spread of infection from one per to another via their shared groups.
    • +
    • TeleworkManager – Reviews the status of infections within the greater population every week and triggers randomly selected work places to move to telework mode.
    • +
    • SchoolManager – Reviews the status of infections per school every week and splits school groups into cohorts. If infections increase it can close individual schools.
    • +
    +

    The reports in this model are:

    +
      +
    • GroupPopulationReport – Shows the distribution of group sizes for each group type at the end of the simulation.
    • +
    • PersonPropertyReport – Shows the distribution of disease state values over each day by regions
    • +
    • DiseaseStateReport – Shows the distribution of disease state at the end of the simulation with a single line per scenario
    • +
    • ContagionReport – Shows the distribution of the number of infections spread per person
    • +
    +
    +
    +

    10.8 Experiment dimensions

    +

    Three dimensions are added to the experiment that define alternate values for telework infection thresholds, telework adoption probability, school cohort infection thresholds and school closure infection thresholds, yielding 36 scenarios. The values are:

    +
      +
    • Telework infection threshold – 0.1, 1, and 10 percent
    • +
    • Telework adoption probability – 10, 30, 50 and 80 percent
    • +
    • School cohort and closure infection thresholds +
        +
      • 0.1 and 1 percent
      • +
      • 1 and 2 percent
      • +
      • 10 and 20 percent
      • +
    • +
    +
    +
    +

    10.9 The actors

    +

    We will finish this chapter by reviewing the four actors of the model plugin and then examine the output.

    +
    +
    +

    10.10 Population Loader

    +

    The PopulationLoader actor, in Code Block 10.6, adds people to the simulation based on the number in the POPULATION_SIZE global property. The population is split evenly between the regions. For each region, the manager determines the number of homes needed to house the people based on the AVERAGE_HOME_SIZE. The people are grouped into children, working adults and seniors based on related global properties. Similar calculations determine the number of schools and workplaces. The people are randomly distributed based on their ages into homes, work places and schools. Some care is given to ensure that every household has at least one adult.

    +
    +
    +

    Code Block 10.6: The population loader initializes by establishing various constants from the global properties and establishing the population of each region.

    +
    public void init(ActorContext actorContext) {
    +    personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class);
    +    groupsDataManager = actorContext.getDataManager(GroupsDataManager.class);
    +    StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);
    +    randomGenerator = stochasticsDataManager.getRandomGenerator();
    +    peopleDataManager = actorContext.getDataManager(PeopleDataManager.class);
    +    GlobalPropertiesDataManager globalPropertiesDataManager = actorContext
    +            .getDataManager(GlobalPropertiesDataManager.class);
    +    regionsDataManager = actorContext.getDataManager(RegionsDataManager.class);
    +
    +    int populationSize = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.POPULATION_SIZE);
    +    susceptibleProbability = globalPropertiesDataManager
    +            .getGlobalPropertyValue(GlobalProperty.SUSCEPTIBLE_POPULATION_PROPORTION);
    +    childPopulationProportion = globalPropertiesDataManager
    +            .getGlobalPropertyValue(GlobalProperty.CHILD_POPULATION_PROPORTION);
    +    seniorPopulationProportion = globalPropertiesDataManager
    +            .getGlobalPropertyValue(GlobalProperty.SENIOR_POPULATION_PROPORTION);
    +    averageHomeSize = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.AVERAGE_HOME_SIZE);
    +    averageSchoolSize = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.AVERAGE_SCHOOL_SIZE);
    +    averageWorkSize = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.AVERAGE_WORK_SIZE);
    +
    +    Set<RegionId> regionIds = regionsDataManager.getRegionIds();
    +    int regionSize = populationSize / regionIds.size();
    +    int leftoverPeople = populationSize % regionIds.size();
    +
    +    for (RegionId regionId : regionIds) {
    +        int regionPopulation = regionSize;
    +        if (leftoverPeople > 0) {
    +            leftoverPeople--;
    +            regionPopulation++;
    +        }
    +        initializeRegionPopulation(regionId, regionPopulation);
    +    }
    +}
    +
    +
    +

    Initializing the region population, Code Block 10.7, first establishes the number of home groups, school groups and work groups by determining the number of children, working adults and seniors that are present based on various fixed global property values. The people are added with randomly determined ages and disease immunity.

    +
    +
    +

    Code Block 10.7: The population for a region is initialized with each person being assigned an age, an immunity status and a region.

    +
    private void initializeRegionPopulation(RegionId regionId, int populationSize) {
    +
    +    double n = populationSize;
    +    int homeCount = (int) (n / averageHomeSize) + 1;
    +    int childCount = (int) (n * childPopulationProportion);
    +    int adultCount = populationSize - childCount;
    +    homeCount = FastMath.min(homeCount, adultCount);
    +    int seniorCount = (int) (n * seniorPopulationProportion);
    +    seniorCount = FastMath.min(seniorCount, adultCount);
    +    int workingAdultCount = adultCount - seniorCount;
    +    int workCount = (int) ((double) workingAdultCount / averageWorkSize) + 1;
    +    int schoolCount = (int) ((double) childCount / averageSchoolSize) + 1;
    +
    +    // create the population
    +    for (int i = 0; i < populationSize; i++) {
    +        int age;
    +        if (i < seniorCount) {
    +            age = randomGenerator.nextInt(25) + 65;
    +        } else if (i < adultCount) {
    +            age = randomGenerator.nextInt(18) + (65 - 18);
    +        } else {
    +            age = randomGenerator.nextInt(18);
    +        }
    +        PersonPropertyValueInitialization ageInitialization = new PersonPropertyValueInitialization(
    +                PersonProperty.AGE, age);
    +
    +        DiseaseState diseaseState = DiseaseState.IMMUNE;
    +        if (randomGenerator.nextDouble() < susceptibleProbability) {
    +            diseaseState = DiseaseState.SUSCEPTIBLE;
    +        }
    +
    +        PersonPropertyValueInitialization diseaseInitialization = new PersonPropertyValueInitialization(
    +                PersonProperty.DISEASE_STATE, diseaseState);
    +        PersonConstructionData personConstructionData = PersonConstructionData.builder()//
    +                .add(ageInitialization)//
    +                .add(diseaseInitialization)//
    +                .add(regionId)//
    +                .build();
    +        peopleDataManager.addPerson(personConstructionData);
    +    }
    +
    +
    +

    In Code Block 10.8, the manager continues by creating the work, home and school groups. It then organizes the people by age group in Code Block 10.9. Finally, it places the people into the appropriate groups in Code Block 10.10.

    +
    +
    +

    Code Block 10.8: The home, work and school groups are added to the groups data manager.

    +
    // create the home groups
    +List<GroupId> homeGroupIds = new ArrayList<>();
    +for (int i = 0; i < homeCount; i++) {
    +    GroupConstructionInfo groupConstructionInfo = GroupConstructionInfo.builder().setGroupTypeId(GroupType.HOME)
    +            .build();
    +    GroupId groupId = groupsDataManager.addGroup(groupConstructionInfo);
    +    homeGroupIds.add(groupId);
    +}
    +
    +// create the work groups
    +List<GroupId> workGroupIds = new ArrayList<>();
    +for (int i = 0; i < workCount; i++) {
    +    GroupConstructionInfo groupConstructionInfo = GroupConstructionInfo.builder().setGroupTypeId(GroupType.WORK)
    +            .build();
    +    GroupId groupId = groupsDataManager.addGroup(groupConstructionInfo);
    +    workGroupIds.add(groupId);
    +}
    +
    +// create the school groups
    +List<GroupId> schoolGroupIds = new ArrayList<>();
    +for (int i = 0; i < schoolCount; i++) {
    +    GroupConstructionInfo groupConstructionInfo = GroupConstructionInfo.builder()
    +            .setGroupTypeId(GroupType.SCHOOL).build();
    +    GroupId groupId = groupsDataManager.addGroup(groupConstructionInfo);
    +    schoolGroupIds.add(groupId);
    +}
    +
    +
    +
    +
    +

    Code Block 10.9: The people are separated into age related lists.

    +
    List<PersonId> peopleInRegion = regionsDataManager.getPeopleInRegion(regionId);
    +List<PersonId> adults = new ArrayList<>();
    +List<PersonId> children = new ArrayList<>();
    +List<PersonId> workingAdults = new ArrayList<>();
    +for (PersonId personId : peopleInRegion) {
    +    int age = personPropertiesDataManager.getPersonPropertyValue(personId, PersonProperty.AGE);
    +    if (age < 18) {
    +        children.add(personId);
    +    } else {
    +        adults.add(personId);
    +        if (age < 65) {
    +            workingAdults.add(personId);
    +        }
    +    }
    +}
    +
    +
    +
    +
    +

    Code Block 10.10: People are assigned to homes, work places and schools.

    +
    Random random = new Random(randomGenerator.nextLong());
    +/*
    +         * Randomize the adults and assign them to the home groups such that there is at
    +         * least one adult in each home
    +         */
    +Collections.shuffle(adults, random);
    +// put one adult in each home
    +for (int i = 0; i < homeGroupIds.size(); i++) {
    +    PersonId personId = adults.get(i);
    +    GroupId groupId = homeGroupIds.get(i);
    +    groupsDataManager.addPersonToGroup(personId, groupId);
    +}
    +
    +// assign the remaining adults at random to homes
    +for (int i = homeGroupIds.size(); i < adults.size(); i++) {
    +    PersonId personId = adults.get(i);
    +    GroupId groupId = homeGroupIds.get(randomGenerator.nextInt(homeGroupIds.size()));
    +    groupsDataManager.addPersonToGroup(personId, groupId);
    +}
    +
    +// assign working age adults to work groups
    +for (int i = 0; i < workingAdults.size(); i++) {
    +    PersonId personId = workingAdults.get(i);
    +    GroupId groupId = workGroupIds.get(randomGenerator.nextInt(workGroupIds.size()));
    +    groupsDataManager.addPersonToGroup(personId, groupId);
    +}
    +
    +// assign children to school groups
    +for (int i = 0; i < children.size(); i++) {
    +    PersonId personId = children.get(i);
    +    GroupId groupId = schoolGroupIds.get(randomGenerator.nextInt(schoolGroupIds.size()));
    +    groupsDataManager.addPersonToGroup(personId, groupId);
    +}
    +
    +// assign children to home groups
    +for (int i = 0; i < children.size(); i++) {
    +    PersonId personId = children.get(i);
    +    GroupId groupId = homeGroupIds.get(randomGenerator.nextInt(homeGroupIds.size()));
    +    groupsDataManager.addPersonToGroup(personId, groupId);
    +}
    +    }
    +
    +
    +
    +

    10.10.1 Telework manager

    +

    The TeleworkManager actor, in Code Block 10.11, schedules a weekly review of school disease mitigation strategies. Note that the scheduling is through a passive plan, which indicates to the simulation that this plan should only be executed if there are active plans still remaining. Passive plans are generally used when there is a continuing task that is not driven directly by events. When active plans cease and only passive plans remain, the simulation halts since the passive plans are not reason enough to continue time flow.

    +
    +
    +

    Code Block 10.11: The telework manager initializes by scheduling a telework status review for seven days from the start of the simulation.

    +
    public void init(ActorContext actorContext) {
    +    this.actorContext = actorContext;
    +    scheduleNextReview();
    +}
    +
    +private void scheduleNextReview() {
    +    double planTime = actorContext.getTime() + reviewInterval;
    +    Plan<ActorContext> plan = Plan.builder(ActorContext.class)//
    +            .setCallbackConsumer(this::reviewTeleworkStatus)//
    +            .setActive(false)//
    +            .setTime(planTime)//
    +            .build();
    +
    +    actorContext.addPlan(plan);
    +}
    +
    +
    +

    The review process (Code Block 10.12) continues until a disease threshold has been reached. At that point, the telework manager randomly determines which work groups will be in telework mode. Note that there is no returning from this mode and there is no further evolution of the telework mode even after the disease wanes.

    +
    +
    +

    Code Block 10.12: The telework manager schedules a review every seven days until a threshold of infections is reached. Once the threshold is achieved, work places are randomly selected to use telework until the end of the simulation.

    +
    private void reviewTeleworkStatus(ActorContext actorContext) {
    +    StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);
    +    RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator();
    +    PeopleDataManager peopleDataManager = actorContext.getDataManager(PeopleDataManager.class);
    +    PersonPropertiesDataManager personPropertiesDataManager = actorContext
    +            .getDataManager(PersonPropertiesDataManager.class);
    +    GroupsDataManager groupsDataManager = actorContext.getDataManager(GroupsDataManager.class);
    +    GlobalPropertiesDataManager globalPropertiesDataManager = actorContext
    +            .getDataManager(GlobalPropertiesDataManager.class);
    +    double threshold = globalPropertiesDataManager
    +            .getGlobalPropertyValue(GlobalProperty.TELEWORK_INFECTION_THRESHOLD);
    +    double teleworkProbability = globalPropertiesDataManager
    +            .getGlobalPropertyValue(GlobalProperty.TELEWORK_PROBABILTY);
    +
    +    int infectiousCount = personPropertiesDataManager.getPersonCountForPropertyValue(PersonProperty.DISEASE_STATE,
    +            DiseaseState.INFECTIOUS);
    +    int populationCount = peopleDataManager.getPopulationCount();
    +
    +    double infectiousFraction = infectiousCount;
    +    infectiousFraction /= populationCount;
    +
    +    if (infectiousFraction >= threshold) {
    +        List<GroupId> workGroupIds = groupsDataManager.getGroupsForGroupType(GroupType.WORK);
    +        for (GroupId groupId : workGroupIds) {
    +            if (randomGenerator.nextDouble() < teleworkProbability) {
    +                groupsDataManager.setGroupPropertyValue(groupId, GroupProperty.TELEWORK, true);
    +            }
    +        }
    +    } else {
    +        scheduleNextReview();
    +    }
    +}
    +
    +
    +
    +
    +

    10.10.2 School manager

    +

    The SchoolManager actor (Code Block 10.13) initializes by establishing various values derived from fixed global properties and then scheduling a weekly review of all schools.

    +
    +
    +

    Code Block 10.13: The school manager initializes by establishing some property constants and planning school status review for seven days after the simulation starts.

    +
    public void init(ActorContext actorContext) {
    +    this.actorContext = actorContext;
    +    personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class);
    +    groupsDataManager = actorContext.getDataManager(GroupsDataManager.class);
    +    GlobalPropertiesDataManager globalPropertiesDataManager = actorContext
    +            .getDataManager(GlobalPropertiesDataManager.class);
    +    cohortThreshold = globalPropertiesDataManager
    +            .getGlobalPropertyValue(GlobalProperty.SCHOOL_COHORT_INFECTION_THRESHOLD);
    +    closureThreshold = globalPropertiesDataManager
    +            .getGlobalPropertyValue(GlobalProperty.SCHOOL_CLOSURE_INFECTION_THRESHOLD);
    +    planNextReview();
    +}
    +
    +private void planNextReview() {
    +    double planTime = actorContext.getTime() + reviewInterval;
    +    Plan<ActorContext> plan = Plan.builder(ActorContext.class)//
    +            .setCallbackConsumer(this::reviewSchools)//
    +            .setActive(false)//
    +            .setTime(planTime)//
    +            .build();
    +    actorContext.addPlan(plan);
    +}
    +
    +private void reviewSchools(ActorContext actorContext) {
    +    List<GroupId> schoolGroupIds = groupsDataManager.getGroupsForGroupType(GroupType.SCHOOL);
    +    for (GroupId groupId : schoolGroupIds) {
    +        reviewSchool(groupId);
    +    }
    +    planNextReview();
    +}
    +
    +
    +

    The review of each school group is shown in Code Block 10.14. The fraction of the school’s students who are infectious is calculated. Depending on the current state of the group (OPEN, COHORT, CLOSED) the group may move to the next state. If the group moves from OPEN to COHORT (Code Block 10.15), a new group is formed and half of the students are moved into the new group. Both groups are then marked as COHORT groups. If the group moves from COHORT to CLOSED (Code Block 10.16), all students are removed from the group.

    +
    +
    +

    Code Block 10.14: Each school is reviewed on a weekly basis. As the fraction of students who are infected increases, the school transitions from OPEN to COHORT to CLOSED.

    +
    private void reviewSchool(GroupId groupId) {
    +
    +    int infectiousCount = 0;
    +    List<PersonId> peopleForGroup = groupsDataManager.getPeopleForGroup(groupId);
    +    for (PersonId personId : peopleForGroup) {
    +        DiseaseState diseaseState = personPropertiesDataManager.getPersonPropertyValue(personId,
    +                PersonProperty.DISEASE_STATE);
    +        if (diseaseState == DiseaseState.INFECTIOUS) {
    +            infectiousCount++;
    +        }
    +    }
    +
    +    double infectiousFraction = infectiousCount;
    +    if (!peopleForGroup.isEmpty()) {
    +        infectiousFraction /= peopleForGroup.size();
    +    }
    +
    +    SchoolStatus schoolStatus = groupsDataManager.getGroupPropertyValue(groupId, GroupProperty.SCHOOL_STATUS);
    +
    +    switch (schoolStatus) {
    +    case OPEN:
    +        if (infectiousFraction >= cohortThreshold) {
    +            splitSchoolIntoCohorts(groupId);
    +        }
    +        break;
    +    case COHORT:
    +        if (infectiousFraction >= closureThreshold) {
    +            closeSchool(groupId);
    +        }
    +        break;
    +    case CLOSED:
    +        // do nothing
    +        break;
    +    default:
    +        throw new RuntimeException("unhandled case " + schoolStatus);
    +    }
    +}
    +
    +
    +
    +
    +

    Code Block 10.15: When a school moves to COHORT status, a new group is added to the simulation and half of the students move to this new group.

    +
    private void splitSchoolIntoCohorts(GroupId groupId) {
    +    GroupConstructionInfo groupConstructionInfo = GroupConstructionInfo.builder().setGroupTypeId(GroupType.SCHOOL)
    +            .build();
    +    GroupId newGroupId = groupsDataManager.addGroup(groupConstructionInfo);
    +
    +    List<PersonId> peopleForGroup = groupsDataManager.getPeopleForGroup(groupId);
    +    for (int i = 0; i < peopleForGroup.size(); i++) {
    +        if (i % 2 == 0) {
    +            PersonId personId = peopleForGroup.get(i);
    +            groupsDataManager.removePersonFromGroup(personId, groupId);
    +            groupsDataManager.addPersonToGroup(personId, newGroupId);
    +        }
    +    }
    +
    +    groupsDataManager.setGroupPropertyValue(newGroupId, GroupProperty.SCHOOL_STATUS, SchoolStatus.COHORT);
    +    groupsDataManager.setGroupPropertyValue(groupId, GroupProperty.SCHOOL_STATUS, SchoolStatus.COHORT);
    +
    +}
    +
    +
    +
    +
    +

    Code Block 10.16: When a school is closed, all the students are removed from the school group so that infection can no longer spread via school-based contact.

    +
    private void closeSchool(GroupId groupId) {
    +    groupsDataManager.setGroupPropertyValue(groupId, GroupProperty.SCHOOL_STATUS, SchoolStatus.CLOSED);
    +    List<PersonId> people = groupsDataManager.getPeopleForGroup(groupId);
    +    for (PersonId personId : people) {
    +        groupsDataManager.removePersonFromGroup(personId, groupId);
    +    }
    +}
    +
    +
    +
    +
    +

    10.10.3 Infection Manager

    +

    The InfectionManager actor, in Code Block 10.17, first determines various values from the fixed global properties that will be used to manage infections. The most important calculation is to determine the length of time between infectious contacts as a function of the global R0 value and the expected number of days a person will be infectious. It then selects a small set of adults to infect during first day of the simulation. Infecting a person (Code Block 10.18) first requires the manager to determine the number of days of the infection for the particular person and then schedule infectious contact times for that person. At the end of the infectious contacts the manager schedules the transition of the person from infectious to recovered.

    +
    +
    +

    Code Block 10.17: The infection manager initializes by infecting the initially infected people in the first day.

    +
    public void init(ActorContext actorContext) {
    +    this.actorContext = actorContext;
    +
    +    StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);
    +    randomGenerator = stochasticsDataManager.getRandomGenerator();
    +    Random random = new Random(randomGenerator.nextLong());
    +
    +    groupsDataManager = actorContext.getDataManager(GroupsDataManager.class);
    +    GlobalPropertiesDataManager globalPropertiesDataManager = actorContext
    +            .getDataManager(GlobalPropertiesDataManager.class);
    +    personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class);
    +    List<PersonId> susceptiblePeople = personPropertiesDataManager
    +            .getPeopleWithPropertyValue(PersonProperty.DISEASE_STATE, DiseaseState.SUSCEPTIBLE);
    +    List<PersonId> susceptibleAdults = new ArrayList<>();
    +    for (PersonId personId : susceptiblePeople) {
    +        int age = personPropertiesDataManager.getPersonPropertyValue(personId, PersonProperty.AGE);
    +        if (age > 18) {
    +            susceptibleAdults.add(personId);
    +        }
    +    }
    +
    +    Collections.shuffle(susceptibleAdults, random);
    +
    +    minInfectiousPeriod = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.MIN_INFECTIOUS_PERIOD);
    +    maxInfectiousPeriod = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.MAX_INFECTIOUS_PERIOD);
    +    double r0 = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.R0);
    +    infectionInterval = (double) (minInfectiousPeriod + maxInfectiousPeriod) / (2 * r0);
    +
    +    int initialInfections = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.INITIAL_INFECTIONS);
    +    initialInfections = FastMath.min(initialInfections, susceptibleAdults.size());
    +
    +    for (int i = 0; i < initialInfections; i++) {
    +        PersonId personId = susceptibleAdults.get(i);
    +        double planTime = randomGenerator.nextDouble() * 0.5 + 0.25;
    +        actorContext.addPlan((c) -> infectPerson(personId), planTime);
    +    }
    +}
    +
    +
    +
    +
    +

    Code Block 10.18: When a person is infected, the number of possible infectious contacts is determined and planned. After the last infectious contact, the person is scheduled to become recovered.

    +
    private void infectPerson(PersonId personId) {
    +    personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.DISEASE_STATE,
    +            DiseaseState.INFECTIOUS);
    +    int infectiousDays = randomGenerator.nextInt(maxInfectiousPeriod - minInfectiousPeriod) + minInfectiousPeriod;
    +    int infectionCount = (int) FastMath.round(((double) infectiousDays / infectionInterval));
    +    double planTime = actorContext.getTime();
    +    for (int j = 0; j < infectionCount; j++) {
    +        planTime += infectionInterval;
    +        actorContext.addPlan((c) -> infectContact(personId), planTime);
    +    }
    +    actorContext.addPlan((c) -> endInfectiousness(personId), planTime);
    +}
    +
    +
    +

    An infectious contact , Code Block 10.19, first selects a group at random from the person’s groups. A person to infect is selected from the group, excluding the person who is infecting. If such a person can be found and that person is susceptible then, barring mitigation, the susceptible person becomes infected and is immediately infectious. Cohort schools are mitigated by 50% and closed schools are 100% mitigated. Telework groups are mitigated by 50%.

    +
    +
    +

    Code Block 10.19: The infection manager attempts to infect a susceptible person found in a randomly selected group associated with the currently infected person.

    +
    private void infectContact(PersonId personId) {
    +    List<GroupId> groupsForPerson = groupsDataManager.getGroupsForPerson(personId);
    +    GroupId groupId = groupsForPerson.get(randomGenerator.nextInt(groupsForPerson.size()));
    +
    +    // work groups doing telework have a 50% contact mitigation
    +    GroupTypeId groupTypeId = groupsDataManager.getGroupType(groupId);
    +    if (groupTypeId.equals(GroupType.WORK)) {
    +        boolean teleworkGroup = groupsDataManager.getGroupPropertyValue(groupId, GroupProperty.TELEWORK);
    +        if (teleworkGroup) {
    +            if (randomGenerator.nextBoolean()) {
    +                return;
    +            }
    +        }
    +    }
    +
    +    // school groups in COHORT mode have a 50% contact mitigation
    +    // school groups in CLOSED mode have a 100% contact mitigation
    +    if (groupTypeId.equals(GroupType.SCHOOL)) {
    +        SchoolStatus schoolStatus = groupsDataManager.getGroupPropertyValue(groupId, GroupProperty.SCHOOL_STATUS);
    +        switch (schoolStatus) {
    +        case COHORT:
    +            if (randomGenerator.nextBoolean()) {
    +                return;
    +            }
    +            break;
    +        case CLOSED:
    +            return;
    +        default:
    +            // no mitigation
    +            break;
    +        }
    +    }
    +
    +    GroupSampler groupSampler = GroupSampler.builder().setExcludedPersonId(personId).build();
    +    Optional<PersonId> optional = groupsDataManager.sampleGroup(groupId, groupSampler);
    +    if (optional.isPresent()) {
    +        PersonId contactedPerson = optional.get();
    +        DiseaseState diseaseState = personPropertiesDataManager.getPersonPropertyValue(contactedPerson,
    +                PersonProperty.DISEASE_STATE);
    +        if (diseaseState == DiseaseState.SUSCEPTIBLE) {
    +            int infectedCount = personPropertiesDataManager.getPersonPropertyValue(personId,
    +                    PersonProperty.INFECTED_COUNT);
    +            infectedCount++;
    +            personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.INFECTED_COUNT,
    +                    infectedCount);
    +            infectPerson(contactedPerson);
    +        }
    +    }
    +}
    +
    +
    +
    +
    +

    10.10.4 Inspecting the output

    +
    +
    +

    10.10.5 Person Property Report

    +

    The 36 scenarios result in a large amount of output in the person property report. It is effectively a trace log of the initial state and changes to each person.

    +
    +
    +

    10.10.6 Group Population Report

    +

    The group population report shows the distribution of people at the end of the simulation. Since there is no stochastics dimension, each scenario has an identical distribution of people into homes. The report shows all the expected patterns as a response to initial policies. For example, higher school closure infection thresholds drive the ending school populations to less than 1/3 their initial values.

    +
    +
    +

    10.10.7 Disease State Report

    +

    The disease state report shows the ending counts for each disease state. By analyzing the number of people recovered over the 36 scenarios, we observe that the disease threshold for triggering work places into a telework status is significant. If that threshold is set to 10%, the working population does not telework and the number of recovered is significantly higher that at the lower levels. School cohort and closure policies exhibit a similar pattern.

    +
    +
    +

    10.10.8 Contagion Report

    +

    The contagion report shows then number of people infected by each infectious person as counts. The report shows that early decisions to close schools and institute telework policies can significantly reduce the spread of the disease.

    + + +
    +
    + +
    + + +
    + + + + \ No newline at end of file diff --git a/doc/ch11-ResourcesPlugin.html b/doc/ch11-ResourcesPlugin.html new file mode 100644 index 000000000..d62851978 --- /dev/null +++ b/doc/ch11-ResourcesPlugin.html @@ -0,0 +1,1055 @@ + + + + + + + + + +GCM-Docs - 11  Resources Plugin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    + +
    + + + + + +
    + +
    +
    +

    11  Resources Plugin

    +
    + + + +
    + + + + +
    + + +
    + +
    + +
    +

    The resources plugin manages the assignment of resources between people and regions. It is dependent on the people and region plugins. A resource can represent expendable goods such as vaccines and medications as well as durable goods such as hospital beds. Resource instances do not have id values. For example, while a person or a group of people has an integer-based id, a single syringe, dose of vaccine or hospital bed has no unique id. Instead, they are measured as countable quantities that are associated with people or regions.

    +

    The flow of resources is diagrammed in Figure 11.1. Resources may:

    +
      +
    1. Be added to a region +
        +
      • An amount of resource is created and added to a region
      • +
    2. +
    3. Be removed from a region +
        +
      • The resource is expended and removed from the simulation. For example, an expired vaccine lot.
      • +
    4. +
    5. Transferred between regions
    6. +
    7. Transferred from regions to people +
        +
      • Resources can be transferred from a region to the people in that region
      • +
    8. +
    9. Transferred from people to regions +
        +
      • Resources can be returned to the person’s region. For example, the return of a hospital ICU bed after a person’s treatment.
      • +
    10. +
    11. Be removed from a person +
        +
      • The resource is expended and removed from the simulation. For example, a dose of medication
      • +
    12. +
    +
    +
    +
    +
    +

    Figure 11.1: Resource plugin flow diagram.

    +

    +
    +
    +
    +
    +

    Resource flow to and from people is restricted to the region that is associated with the person.

    +
    +

    11.1 Plugin Data Initialization

    +

    The plugin is initialized using a ResourcesPluginData object that:

    +
      +
    • Defines resource ids
    • +
    • Defines resource properties
    • +
    • Collects resource property values
    • +
    • Sets initial resource amounts for regions
    • +
    • Sets initial resource amounts for people
    • +
    +
    +
    +

    11.2 Plugin Behavior

    +

    The plugin adds a single data manager to the simulation as an instance of the ResourcesDataManager that is initialized with the ResourcesPluginData. 

    +
    +
    +

    11.3 Data Manager

    +

    The data manager manages person and region inventories of resources as well as any properties associated with resources.  The data manager provides public methods that:

    +
      +
    • Add a new resource id, thus defining a new resource type
    • +
    • Define a resource property
    • +
    • Add resource amounts to regions
    • +
    • Remove resource amounts from regions
    • +
    • Transfer resource amounts between regions
    • +
    • Transfer resource amounts between regions and people
    • +
    • Remove resource amounts from people
    • +
    • Answer various questions about: +
        +
      • People who have or do not have a particular resource
      • +
      • Resource levels for people and regions
      • +
      • Property values, definitions, resource id values, etc.
      • +
    • +
    +

    The data manager also produces observable events:

    +
      +
    • PersonResourceUpdateEvent – when a person has a change in a resource level
    • +
    • RegionResourceUpdateEvent – when a region has a change in a resource level
    • +
    • ResourceIdAdditionEvent – when a new resource id is added
    • +
    • ResourcePropertyDefinitionEvent – when a new resource property definition is added
    • +
    • ResourcePropertyUpdateEvent – when a resource property value is assigned
    • +
    +
    +
    +

    11.4 Example Code (Lesson 18)

    +

    Example_18.java shows the use of the resources plugin. In it we will examine:

    +
      +
    • The initialization of the resource properties plugin
    • +
    • The flow of resources between regions and people
    • +
    • The observation of resource events
    • +
    +

    The example includes seven plugins:

    +
      +
    • Resources plugin – (GCM core plugin) used to manage resources
    • +
    • People plugin – (GCM core plugin) used to manage people
    • +
    • Person properties plugin– (GCM core plugin) used to decorate properties onto people
    • +
    • Global properties plugin– (GCM core plugin) used to store policies and initial conditions affecting resource use
    • +
    • Stochastics plugin – (GCM core plugin) used to generate random numbers used in various decisions
    • +
    • Regions Plugin– (GCM core plugin) used for resource flow
    • +
    • Model plugin – (local plugin) used to introduce three actors that will: +
        +
      • Load the population
      • +
      • Distribute resources to regions
      • +
      • Manage the treatment of disease utilizing resources
      • +
    • +
    +
    +
    +

    11.5 Model

    +

    The example’s model represents a disease that is treatable through antiviral medication and hospitalization. People at the start of the simulation are either immune or susceptible and all susceptible people are exposed and infected at random times over a given period. There is no transmission modeling. Initial treatment is with a single dose of an antiviral drug, if it is available. If the antiviral drug is not available or the drug is not effective, the person is hospitalized. If there are no hospital beds are available, the person dies. After a course of treatment in the hospital, the person is either immune or dead. Previous treatment with the antiviral alters the outcome probabilities of the hospital stay. Upon the person’s release from the hospital, the hospital bed is returned to the person’s region. Questionnaires are sent to people who have been successfully treated.

    +
    +
    +

    11.6 Model Execution

    +

    The example’s execution is shown in Code Block 11.1 and Code Block 11.2.

    +
    +
    +

    Code Block 11.1: Executing example 18 with an output directory.

    +
    public static void main(String[] args) throws IOException {
    +    if (args.length == 0) {
    +        throw new RuntimeException("One output directory argument is required");
    +    }
    +    Path outputDirectory = Paths.get(args[0]);
    +    if (!Files.exists(outputDirectory)) {
    +        Files.createDirectory(outputDirectory);
    +    } else {
    +        if (!Files.isDirectory(outputDirectory)) {
    +            throw new IOException("Provided path is not a directory");
    +        }
    +    }
    +
    +    new Example_18(outputDirectory).execute();
    +}
    +
    +
    +
    +
    +

    Code Block 11.2: The various plugins are gathered from their initial data, dimensions are added and the experiment is executed over 864 scenarios using 8 threads.

    +
    private void execute() {
    +
    +    ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()//
    +            .setThreadCount(8)//
    +            .build();
    +
    +    Experiment.builder()
    +
    +            .addPlugin(getResourcesPlugin())//
    +            .addPlugin(getGlobalPropertiesPlugin())//
    +            .addPlugin(getPersonPropertiesPlugin())//
    +            .addPlugin(getRegionsPlugin())//
    +            .addPlugin(getPeoplePlugin())//
    +            .addPlugin(getStochasticsPlugin())//
    +            .addPlugin(ModelPlugin.getModelPlugin())//
    +
    +            .addDimension(getMaximumSymptomOnsetTimeDimension())//
    +            .addDimension(getSusceptiblePopulationProportionDimension())//
    +            .addDimension(getAntiviralCoverageTimeDimension())//
    +            .addDimension(getAntiviralSuccessRateDimension())//
    +            .addDimension(getHospitalSuccessDimension())//
    +            .addDimension(getHospitalBedsPerPersonDimension())//
    +            .addDimension(getAntiviralDosesPerPersonDimension())//
    +            .addDimension(getHospitalStayDurationDimension())//
    +
    +            .addExperimentContextConsumer(getNIOReportItemHandler())//
    +            .setExperimentParameterData(experimentParameterData)//
    +            .build()//
    +            .execute();//
    +}
    +
    +
    +

    The first action is to add the resources plugin (Code Block 11.3, Code Block 11.4). Only the resource id values contained in the Resource enumeration are added to the plugin’s data. Region and person initial resource levels can also be added, but we will instead initialize them via an actor in the model plugin.

    +
    +
    +

    Code Block 11.3: Resource ids are implemented as an enumeration.

    +
    public enum Resource implements ResourceId {
    +    ANTI_VIRAL_MED, HOSPITAL_BED;
    +}
    +
    +
    +
    +
    +

    Code Block 11.4: The resource plugin is initialized with defining the two resource ids at time zero with time tracking turned on. The person resource report is set to report at the end of the simulation.

    +
    private Plugin getResourcesPlugin() {
    +    ResourcesPluginData.Builder builder = ResourcesPluginData.builder();
    +    for (ResourceId resourceId : Resource.values()) {
    +        builder.addResource(resourceId, 0.0, true);
    +    }
    +    ResourcesPluginData resourcesPluginData = builder.build();
    +
    +    PersonResourceReportPluginData personResourceReportPluginData = PersonResourceReportPluginData//
    +            .builder()//
    +            .setReportLabel(ModelReportLabel.PERSON_RESOURCE_REPORT)//
    +            .setReportPeriod(ReportPeriod.END_OF_SIMULATION)//
    +            .build();
    +
    +    return ResourcesPlugin.builder()//
    +            .setResourcesPluginData(resourcesPluginData)//
    +            .setPersonResourceReportPluginData(personResourceReportPluginData)//
    +            .getResourcesPlugin();//
    +}
    +
    +
    +

    The next action is to load the global properties plugin (Code Block 11.5).  All of the global properties are marked as immutable since they will not change over the course of the simulation.  Further, most of the properties will participate in the dimensions of the experiment, so we can set the default values to zero.

    +
      +
    • SUSCEPTIBLE_POPULATION_PROPORTION – The fraction of the population that is susceptible
    • +
    • MAXIMUM_SYMPTOM_ONSET_TIME – The last time where any person will have onset of symptoms
    • +
    • ANTIVIRAL_COVERAGE_TIME – The amount of time for the antiviral to be effective
    • +
    • ANTIVIRAL_SUCCESS_RATE – The probability that the antiviral will be effective
    • +
    • HOSPITAL_SUCCESS_WITH_ANTIVIRAL – The probability that hospital treatment will be effective for people who previously had antiviral treatment
    • +
    • HOSPITAL_SUCCESS_WITHOUT_ANTIVIRAL – The probability that hospital treatment will be effective for people who previously had no antiviral treatment
    • +
    • HOSPITAL_STAY_DURATION_MIN –  The minimum duration of a hospital stay
    • +
    • HOSPITAL_STAY_DURATION_MAX – The maximum duration of a hospital stay
    • +
    • POPULATION_SIZE – The number of people across all regions.  Regions will not be uniformly populated.
    • +
    • HOSPITAL_BEDS_PER_PERSON – The number of hospital beds per person on average stored in the regions.
    • +
    • ANTIVIRAL_DOSES_PER_PERSON – The number of antiviral doses per person on average stored in the regions.
    • +
    +
    +
    +

    Code Block 11.5: The global properties plugin is initialized with several properties.

    +
    private Plugin getGlobalPropertiesPlugin() {
    +    GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder();//
    +
    +    PropertyDefinition propertyDefinition = PropertyDefinition.builder()//
    +            .setType(Double.class)//
    +            .setDefaultValue(0.0)//
    +            .setPropertyValueMutability(false)//
    +            .build();
    +
    +    builder.defineGlobalProperty(GlobalProperty.SUSCEPTIBLE_POPULATION_PROPORTION, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.MAXIMUM_SYMPTOM_ONSET_TIME, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.ANTIVIRAL_COVERAGE_TIME, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.ANTIVIRAL_SUCCESS_RATE, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.HOSPITAL_SUCCESS_WITH_ANTIVIRAL, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.HOSPITAL_SUCCESS_WITHOUT_ANTIVIRAL, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.HOSPITAL_BEDS_PER_PERSON, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.ANTIVIRAL_DOSES_PER_PERSON, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.HOSPITAL_STAY_DURATION_MIN, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.HOSPITAL_STAY_DURATION_MAX, propertyDefinition, 0);
    +
    +    propertyDefinition = PropertyDefinition.builder()//
    +            .setType(Integer.class)//
    +            .setDefaultValue(10000)//
    +            .setPropertyValueMutability(false)//
    +            .build();
    +
    +    builder.defineGlobalProperty(GlobalProperty.POPULATION_SIZE, propertyDefinition, 0);
    +
    +    GlobalPropertiesPluginData globalPropertiesPluginData = builder.build();
    +
    +    return GlobalPropertiesPlugin.builder().setGlobalPropertiesPluginData(globalPropertiesPluginData)
    +            .getGlobalPropertiesPlugin();
    +}
    +
    +
    +

    The person properties plugin is loaded (Code Block 11.6).  All person properties are Boolean values and defaulted to false. 

    +
      +
    • IMMUNE – The person is immune
    • +
    • INFECTED – The person is infected
    • +
    • TREATED_WITH_ANTIVIRAL – The person received antiviral treatment
    • +
    • HOSPITALIZED – The person received hospital treatment
    • +
    • DEAD_IN_HOSPITAL – The person dies in the hospital
    • +
    • DEAD_IN_HOME – The person dies in the home
    • +
    • RECEIVED_QUESTIONNAIRE – An infected person who successfully completes treatment receives a questionnaire
    • +
    +
    +
    +

    Code Block 11.6: The person properties plugin is initialized with several properties.

    +
    private Plugin getPersonPropertiesPlugin() {
    +
    +    PersonPropertiesPluginData.Builder builder = PersonPropertiesPluginData.builder();
    +
    +    PropertyDefinition propertyDefinition = PropertyDefinition.builder()//
    +            .setType(Boolean.class)//
    +            .setDefaultValue(false)//
    +            .build();
    +
    +    builder.definePersonProperty(PersonProperty.IMMUNE, propertyDefinition, 0, false);//
    +    builder.definePersonProperty(PersonProperty.INFECTED, propertyDefinition, 0, false);//
    +    builder.definePersonProperty(PersonProperty.HOSPITALIZED, propertyDefinition, 0, false);//
    +    builder.definePersonProperty(PersonProperty.TREATED_WITH_ANTIVIRAL, propertyDefinition, 0, false);//
    +    builder.definePersonProperty(PersonProperty.DEAD_IN_HOME, propertyDefinition, 0, false);//
    +    builder.definePersonProperty(PersonProperty.DEAD_IN_HOSPITAL, propertyDefinition, 0, false);//
    +
    +    propertyDefinition = PropertyDefinition.builder()//
    +            .setType(Boolean.class)//
    +            .setDefaultValue(false)//
    +            .build();
    +    builder.definePersonProperty(PersonProperty.RECEIVED_QUESTIONNAIRE, propertyDefinition, 0, true);//
    +
    +    PersonPropertiesPluginData personPropertiesPluginData = builder.build();
    +
    +    return PersonPropertiesPlugin.builder().setPersonPropertiesPluginData(personPropertiesPluginData)
    +            .getPersonPropertyPlugin();
    +}
    +
    +
    +

    The are four reports:

    +
      +
    • PersonResourceReport – Shows resource allocations by day and region.  Resources are possessed by a person during the course of treatment.
    • +
    • TreatmentReport – Shows a summary of all person property value combinations at the end of the simulation
    • +
    • DeathReport – Shows absolute and per capita death rate per region
    • +
    • QuestionnaireReport – Shows statistics on the distribution of questionnaires
    • +
    +

    The stochastic plugin is initialized with a random seed and all simulations will start in the same stochastic state.  The Regions plugin has five regions and the people plugin is loaded in an empty state.  People are added by an actor in the model plugin.

    +

    The model plugin adds three actors

    +
      +
    • PopulationLoader – Adds people to the simulation and initializes immunity
    • +
    • ResourceLoader – Initializes resources for regions. People have no allocated resources.
    • +
    • TreatmentManager – Moves people through treatment states
    • +
    +
    +
    +

    11.7 Experiment dimensions

    +

    Eight dimensions are added to the experiment that define alternate values for ten of the eleven global properties resulting in 864 scenarios. Only population size is fixed and is set to 10,000.  The values are:

    +
      +
    • Max Symptom onset – 60 and 120 days
    • +
    • Susceptibility – 25, 50 and 75 percent
    • +
    • Antiviral treatment duration – 10 and 15 days
    • +
    • Antiviral treatment success – 50 and 80 percent
    • +
    • Hospital success rate with and without antivirals +
        +
      • 75 and 50 percent
      • +
      • 50 and 30 percent
      • +
    • +
    • Hospital beds per capita – 1, 3 and 5 per thousand
    • +
    • Antiviral doses per capita – 0.1, 0.2 and 0.5
    • +
    • Hospital stay +
        +
      • 2 to 5 days
      • +
      • 5 to 10 days
      • +
    • +
    +
    +
    +

    11.8 The actors

    +

    We will finish this chapter by reviewing the three actors of the model plugin and then examine the output.

    +
    +

    11.8.1 Population Loader

    +

    The PopulationLoader actor, in Code Block 11.7, adds people to the simulation based on the number in the POPULATION_SIZE global property.  Each person is assigned a random region and the person property, IMMUNE, is randomly assigned based on the SUSCEPTIBLE_POPULATION_PROPORTION global property. The selection of regions, although random, is designed to ensure that regions have different population sizes so that each region may have unique output statistics.

    +
    +
    +

    Code Block 11.7: The population loader initializes the population by assigning to each person a randomly selected region id and immunity status.

    +
    public void init(ActorContext actorContext) {
    +
    +    StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);
    +    randomGenerator = stochasticsDataManager.getRandomGenerator();
    +    PeopleDataManager peopleDataManager = actorContext.getDataManager(PeopleDataManager.class);
    +    GlobalPropertiesDataManager globalPropertiesDataManager = actorContext
    +            .getDataManager(GlobalPropertiesDataManager.class);
    +    regionsDataManager = actorContext.getDataManager(RegionsDataManager.class);
    +
    +    int populationSize = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.POPULATION_SIZE);
    +    double susceptibleProbability = globalPropertiesDataManager
    +            .getGlobalPropertyValue(GlobalProperty.SUSCEPTIBLE_POPULATION_PROPORTION);
    +    double immuneProbabilty = 1 - susceptibleProbability;
    +
    +    /*
    +         * Derive mapping from region to probability that a person will be assigned to
    +         * that region that will likely not put the same number of people in each
    +         * region.
    +         */
    +    buildUnbalancedRegions();
    +
    +    /*
    +         * Add each person to the simulation. Determine their region id and the immune
    +         * state. The other person properties will have default values.
    +         */
    +    for (int i = 0; i < populationSize; i++) {
    +        RegionId regionId = getRandomRegionId();
    +        boolean immune = randomGenerator.nextDouble() < immuneProbabilty;
    +        PersonPropertyValueInitialization personPropertyInitialization = new PersonPropertyValueInitialization(
    +                PersonProperty.IMMUNE, immune);
    +        PersonConstructionData personConstructionData = PersonConstructionData.builder()//
    +                .add(personPropertyInitialization)//
    +                .add(regionId)//
    +                .build();
    +        peopleDataManager.addPerson(personConstructionData);
    +    }
    +}
    +
    +
    +
    +
    +

    11.8.2 Resource Loader

    +

    The ResourceLoader actor, in Code Block 11.8, determines the number of antiviral doses and hospital beds for each region.  Note that each region receives the same amounts, but that the regions will have very different population sizes.

    +
    +
    +

    Code Block 11.8: The resource loader initializes the anti-viral medication doses and hospital beds for each region.

    +
    public void init(ActorContext actorContext) {
    +
    +    RegionsDataManager regionsDataManager = actorContext.getDataManager(RegionsDataManager.class);
    +    ResourcesDataManager resourcesDataManager = actorContext.getDataManager(ResourcesDataManager.class);
    +    GlobalPropertiesDataManager globalPropertiesDataManager = actorContext
    +            .getDataManager(GlobalPropertiesDataManager.class);
    +    int populationSize = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.POPULATION_SIZE);
    +    Set<RegionId> regionIds = regionsDataManager.getRegionIds();
    +
    +    double dosesPerPerson = globalPropertiesDataManager
    +            .getGlobalPropertyValue(GlobalProperty.ANTIVIRAL_DOSES_PER_PERSON);
    +
    +    double totalDoses = dosesPerPerson * populationSize;
    +    int doseCount = (int) totalDoses;
    +    int doseCountPerRegion = doseCount / regionIds.size();
    +
    +    double bedsPerPerson = globalPropertiesDataManager
    +            .getGlobalPropertyValue(GlobalProperty.HOSPITAL_BEDS_PER_PERSON);
    +
    +    double totalBeds = bedsPerPerson * populationSize;
    +    int bedCount = (int) totalBeds;
    +    int bedCountPerRegion = bedCount / regionIds.size();
    +
    +    for (RegionId regionId : regionIds) {
    +        resourcesDataManager.addResourceToRegion(Resource.ANTI_VIRAL_MED, regionId, doseCountPerRegion);
    +        resourcesDataManager.addResourceToRegion(Resource.HOSPITAL_BED, regionId, bedCountPerRegion);
    +    }
    +}
    +
    +
    +
    +
    +

    11.8.3 Treatment Manager

    +

    The TreatmentManager actor, in Code Block 11.9, initializes by first deriving several values from the global properties plugin that will be useful as it manages people. It then gets from the person properties manager a list of all people who are not currently immune and marks them as infected.  It further determines for each person when they will become symptomatic and schedules the allocation of the antiviral resource.  

    +
    +
    +

    Code Block 11.9: The treatment manager establishes various constants and infects all non-immune people, scheduling them for anti-viral treatment.

    +
    public void init(ActorContext actorContext) {
    +    this.actorContext = actorContext;
    +    regionsDataManager = actorContext.getDataManager(RegionsDataManager.class);
    +    resourcesDataManager = actorContext.getDataManager(ResourcesDataManager.class);
    +    StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);
    +    randomGenerator = stochasticsDataManager.getRandomGenerator();
    +    personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class);
    +    GlobalPropertiesDataManager globalPropertiesDataManager = actorContext
    +            .getDataManager(GlobalPropertiesDataManager.class);
    +
    +    double maximumSymptomOnsetTime = globalPropertiesDataManager
    +            .getGlobalPropertyValue(GlobalProperty.MAXIMUM_SYMPTOM_ONSET_TIME);
    +    antiviralCoverageTime = globalPropertiesDataManager
    +            .getGlobalPropertyValue(GlobalProperty.ANTIVIRAL_COVERAGE_TIME);
    +    antiviralSuccessRate = globalPropertiesDataManager
    +            .getGlobalPropertyValue(GlobalProperty.ANTIVIRAL_SUCCESS_RATE);
    +    hospitalSuccessWithAntiviral = globalPropertiesDataManager
    +            .getGlobalPropertyValue(GlobalProperty.HOSPITAL_SUCCESS_WITH_ANTIVIRAL);
    +    hospitalSuccessWithoutAntiviral = globalPropertiesDataManager
    +            .getGlobalPropertyValue(GlobalProperty.HOSPITAL_SUCCESS_WITHOUT_ANTIVIRAL);
    +    hospitalStayDurationMin = globalPropertiesDataManager
    +            .getGlobalPropertyValue(GlobalProperty.HOSPITAL_STAY_DURATION_MIN);
    +    hospitalStayDurationMax = globalPropertiesDataManager
    +            .getGlobalPropertyValue(GlobalProperty.HOSPITAL_STAY_DURATION_MAX);
    +
    +    List<PersonId> susceptiblePeople = personPropertiesDataManager.getPeopleWithPropertyValue(PersonProperty.IMMUNE,
    +            false);
    +    for (PersonId personId : susceptiblePeople) {
    +        double symptomOnsetTime = randomGenerator.nextDouble() * maximumSymptomOnsetTime;
    +        personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.INFECTED, true);
    +
    +        actorContext.addPlan((c) -> treatWithAntiviral(personId), symptomOnsetTime);
    +    }
    +}
    +
    +
    +

    When a person becomes symptomatic (Code Block 11.10), the treatment manager attempts to find a single dose of the antiviral medication for them.   The dose must come from the region associated with the person.  If the dose is available, the person is treated and a review of the effectiveness of the treatment is scheduled.  If no dose is available, the person is immediately sent to hospital treatment. 

    +
    +
    +

    Code Block 11.10: The treatment manager attempts to treat an infected person with anti-viral medication, if it is available. If no dose is available, the person is immediately hospitalized.

    +
    private void treatWithAntiviral(PersonId personId) {
    +
    +    RegionId regionId = regionsDataManager.getPersonRegion(personId);
    +
    +    long regionResourceLevel = resourcesDataManager.getRegionResourceLevel(regionId, Resource.ANTI_VIRAL_MED);
    +
    +    if (regionResourceLevel > 0) {
    +        resourcesDataManager.transferResourceToPersonFromRegion(Resource.ANTI_VIRAL_MED, personId, 1L);
    +
    +        personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.TREATED_WITH_ANTIVIRAL, true);
    +
    +        actorContext.addPlan((c) -> assessAntiviralTreatment(personId),
    +                actorContext.getTime() + antiviralCoverageTime);
    +    } else {
    +        hospitalizePerson(personId);
    +    }
    +}
    +
    +
    +

    To assess the effectiveness of the antiviral treatment (Code Block 11.11), the treatment manager randomly makes a determination based on the global property settings.  If the treatment is successful, then the person is marked as immune. Otherwise, the person is immediately sent to hospital treatment.

    +
    +
    +

    Code Block 11.11: The treatment manager assesses the success of the anti-viral treatment of a person and expends the medication. If the treatment was unsuccessful the person is immediately hospitalized.

    +
    private void assessAntiviralTreatment(PersonId personId) {
    +
    +    resourcesDataManager.removeResourceFromPerson(Resource.ANTI_VIRAL_MED, personId, 1L);
    +    if (randomGenerator.nextDouble() < antiviralSuccessRate) {
    +        personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.IMMUNE, true);
    +    } else {
    +        hospitalizePerson(personId);
    +    }
    +}
    +
    +
    +

    Hospitalization starts (Code Block 11.12) by finding an available hospital bed for the person from the person’s current region.  If one is available, it is assigned to the person and a review of the effectiveness of the hospital treatment is scheduled for a random time based on the min and max hospital treatment durations.  If no hospital bed is available, then the person dies.

    +
    +
    +

    Code Block 11.12: The treatment manager attempts to find a hospital bed for a person who has not been treated or for whom treatment failed. If no hospital bed is available, the person is set to die in their home.

    +
    private void hospitalizePerson(PersonId personId) {
    +    RegionId regionId = regionsDataManager.getPersonRegion(personId);
    +
    +    long availableHospitalBeds = resourcesDataManager.getRegionResourceLevel(regionId, Resource.HOSPITAL_BED);
    +
    +    if (availableHospitalBeds > 0) {
    +        resourcesDataManager.transferResourceToPersonFromRegion(Resource.HOSPITAL_BED, personId, 1L);
    +
    +        personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.HOSPITALIZED, true);
    +
    +        double hospitalizationDuration = (hospitalStayDurationMax - hospitalStayDurationMin)
    +                * randomGenerator.nextDouble() + hospitalStayDurationMin;
    +
    +        actorContext.addPlan((c) -> assessHospitalization(personId),
    +                actorContext.getTime() + hospitalizationDuration);
    +    } else {
    +        personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.DEAD_IN_HOME, true);
    +    }
    +}
    +
    +
    +

    Reviewing the effectiveness of the hospital treatment is shown in Code Block 11.13.  The outcome depends the previous treatment of the person with the antiviral medication.  If the treatment is successful, then the person is marked immune.  Otherwise, the person dies. The hospital bed resource is returned to the person’s region either way.

    +
    +
    +

    Code Block 11.13: The treatment manager determines whether the hospitalization was a success taking into account whether the person received an antiviral treatment before entering the hospital. If the treatment was successful then the person is marked as immune. Otherwise, the person is marked as a hospital death.

    +
    private void assessHospitalization(PersonId personId) {
    +
    +    boolean treatedWithAntiViral = personPropertiesDataManager.getPersonPropertyValue(personId,
    +            PersonProperty.TREATED_WITH_ANTIVIRAL);
    +    double probabilityOfSuccess;
    +    if (treatedWithAntiViral) {
    +        probabilityOfSuccess = hospitalSuccessWithAntiviral;
    +    } else {
    +        probabilityOfSuccess = hospitalSuccessWithoutAntiviral;
    +    }
    +    if (randomGenerator.nextDouble() < probabilityOfSuccess) {
    +        personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.IMMUNE, true);
    +    } else {
    +        personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.DEAD_IN_HOSPITAL, true);
    +    }
    +    resourcesDataManager.transferResourceFromPersonToRegion(Resource.HOSPITAL_BED, personId, 1L);
    +}
    +
    +
    +
    +
    +

    11.8.4 Questionnaire Manager

    +

    The QuestionnaireManager actor, in Code Block 11.14, initializes by subscribing to changes to the number of antivirals or hospital beds possessed by people.

    +
    +
    +

    Code Block 11.14: The questionnaire distributor initializes by subscribing to anti-viral and hospital bed person resource updates.

    +
    public void init(ActorContext actorContext) {
    +    ResourcesDataManager resourcesDataManager = actorContext.getDataManager(ResourcesDataManager.class);
    +    personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class);
    +
    +    EventFilter<PersonResourceUpdateEvent> eventFilter = resourcesDataManager
    +            .getEventFilterForPersonResourceUpdateEvent(Resource.ANTI_VIRAL_MED);
    +    actorContext.subscribe(eventFilter, this::handleAntiViralDistribution);
    +
    +    eventFilter = resourcesDataManager.getEventFilterForPersonResourceUpdateEvent(Resource.HOSPITAL_BED);
    +    actorContext.subscribe(eventFilter, this::handleHospitalBedDistribution);
    +
    +}
    +
    +
    +

    Upon observing a change in the number of antivirals assigned to a person (Code Block 11.15), the questionnaire manager distributes the questionnaire to the person if that person has just ended antiviral treatment (their level is now zero) but has not been hospitalized.

    +
    +
    +

    Code Block 11.15: The questionnaire distributor distributes a questionnaire to each person who ends their anti-viral treatment and is not also hospitalized.

    +
    private void handleAntiViralDistribution(ActorContext actorContext,
    +        PersonResourceUpdateEvent personResourceUpdateEvent) {
    +    PersonId personId = personResourceUpdateEvent.personId();
    +    boolean hasAntiviral = personResourceUpdateEvent.currentResourceLevel() > 0;
    +    if (!hasAntiviral) {
    +        boolean hospitalized = personPropertiesDataManager.getPersonPropertyValue(personId,
    +                PersonProperty.HOSPITALIZED);
    +        if (!hospitalized) {
    +            distributeQuestionaire(personId);
    +        }
    +    }
    +}
    +
    +
    +

    For those who receive treatment at a hospital (Code Block 11.16), questionnaire manager distributes the questionnaire to the them when their hospital bed is returned and they have not died.

    +
    +
    +

    Code Block 11.16: The questionnaire distributor distributes a questionnaire to each person that leaves the hospital.

    +
    private void handleHospitalBedDistribution(ActorContext actorContext,
    +        PersonResourceUpdateEvent personResourceUpdateEvent) {
    +    PersonId personId = personResourceUpdateEvent.personId();
    +    boolean hasBed = personResourceUpdateEvent.currentResourceLevel() > 0;
    +    boolean dead = personIsDead(personId);
    +    if (!hasBed && !dead) {
    +        distributeQuestionaire(personId);
    +    }
    +}
    +
    +
    +
    +
    +
    +

    11.9 Inspecting the output

    +
    +

    11.9.1 person resource report

    +

    The 864 scenarios result in a large amount of output in the person resource report.  The example code has this report set to only record the end state of the simulation, where all resources have either been expended or returned back to the regions.  Running the report in daily mode will produce over 900,000 data rows, but will show a more useful result. 

    +
    +
    +

    11.9.2 treatment report

    +

    The treatment report shows for each scenario the number or people matching any particular set of person property values at the end of the simulation.  Since there are six person properties and they are all Boolean valued, there are potentially 64 possible tuples.  However, some tuples are impossible since a person cannot be both immune and dead, etc.  The result is 6,240 rows of data for the 864 scenarios.

    +
    +
    +

    11.9.3 death report

    +

    The death report summarizes the treatment report and calculates per capita death rates for each region.  Due to the uneven distribution of resources between the regions based on their populations, the per capita deaths vary between 0.25 and 75 percent.  The trends in the data match expectations with deaths being driven by earlier onset of symptoms, longer hospital stays, and lower effectiveness of both treatment capabilities.

    +
    +
    +

    11.9.4 questionnaire report

    +

    The questionnaire report shows the proportion of infected people that received the questionnaire. It also shows the mean and standard deviation of the delivery times of those questionnaires.

    + + +
    +
    + +
    + + +
    + + + + \ No newline at end of file diff --git a/doc/ch12-MaterialsPlugin.html b/doc/ch12-MaterialsPlugin.html new file mode 100644 index 000000000..35c80755f --- /dev/null +++ b/doc/ch12-MaterialsPlugin.html @@ -0,0 +1,1348 @@ + + + + + + + + + +GCM-Docs - 12  Materials Plugin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    + +
    + + + + + +
    + +
    +
    +

    12  Materials Plugin

    +
    + + + +
    + + + + +
    + + +
    + +
    + +
    +

    The materials plugin manages the transformation of materials into resources. It is dependent on the resource and regions plugins. A material is a real-valued, non-negative quantity of a tangible substance such as eggs, blood products, purified water, etc. These materials are used in the manufacture of resources such as vaccines, antivirals and other medicines. The plugin defines several key concepts:

    +
      +
    • Material Id – The identifier for a material substance used in the manufacture of resources
    • +
    • Batches – A real valued, non-negative, amount of a material
    • +
    • Materials Producers – A physical site of manufacture where materials may be manipulated into resources
    • +
    • Stages – A grouping of batches used to organize the manufacturing process
    • +
    +

    The plugin represents these material transformations in a low fidelity manner providing only the basic structures and ensuring numerical consistency. Material amounts are guaranteed to be non-negative, finite values. Direct mixing of quantities of different materials is not allowed and all material operations are conducted locally to a materials producer. Expiration of materials, the representation of specific chemical or mechanical processes and other higher fidelity considerations are left to the modeler using the plugin.

    +

    The batch is the basic unit of material (Figure 12.1). Each batch is a non-negative, finite amount of a single material and has property values associated with the material. Material properties are modeler defined and specific to each material type.

    +
    +
    +
    +
    +

    Figure 12.1: Materials are collected in non-negative amounts called batches. Each batch has properties defined by the material type.

    +

    +
    +
    +
    +
    +

    The flow and transformation of materials is diagramed in Figure 12.2.

    +
    +
    +
    +
    +

    Figure 12.2: The materials life cycle has materials gathered in the inventories of materials producers that are then staged, traded or converted into new materials or resources.

    +

    +
    +
    +
    +
    +
      +
    1. Material are created in batches and placed into the inventory of a Materials Producer.  Material amounts may be exchanged between batches and batches may have individualized property value assignments.

    2. +
    3. Batches may be grouped onto stages. A stage represents an ongoing process that is possibly transforming the materials.  Batches may be removed from staging and placed back into inventory.

    4. +
    5. Stages may be offered to other materials producers. Once offered, the stage and its batches cannot be altered until the offer is revoked or the stage is transferred. 

    6. +
    7. An offered stage can be transferred to any materials producer where it will be placed with the non-offered stages of that producer. This ensures that materials cannot interact across multiple locations until a physical transfer has occurred.

    8. +
    9. A non-offered stage can be transformed into a discrete amount of a single resource.  The stage and its associated batches are destroyed in this process.  Alternatively, the stage can be transformed into a single batch of some material that is then placed into the inventory of the materials producer.

    10. +
    11. Resources produced by a materials producer are stored on the materials producer and can be distributed to any region(s) in a piecemeal fashion. Resource flow to and from people is restricted to the region that is associated with the person.

    12. +
    +
    +

    12.1 Plugin Data Initialization

    +

    The plugin is initialized using a MaterialsPluginData object that:

    +
      +
    • Contains material producer ids
    • +
    • Defines materials producer properties
    • +
    • Sets materials producer property values
    • +
    • Sets materials producer initial resource levels
    • +
    • Initializes stage and batches in initial inventory
    • +
    • Defines batch property properties
    • +
    • Sets initial batch property values
    • +
    +
    +
    +

    12.2 Plugin Behavior

    +

    The plugin adds a single data manager to the simulation as an instance of the MaterialsDataManager that is initialized with the MaterialsPluginData.

    +
    +
    +

    12.3 Data Manager

    +

    The data manager manages the batch and stage inventories of the materials producers as well as managing the relevant property values and material transformations to resources. The data manager provides public methods that:

    +
      +
    • Defined a new material type
    • +
    • Add and remove batches
    • +
    • Add and remove stages
    • +
    • Move batches to and from stages
    • +
    • Exchange materials between batches
    • +
    • Offer stages for distribution to other materials producers
    • +
    • Move stages between producers
    • +
    • Add materials producers
    • +
    • Define various properties for producers and batches
    • +
    • Set various property values
    • +
    • Transform stages into new batches
    • +
    • Transform stages into resources
    • +
    • Distribute resources to regions
    • +
    • Provide a wide range of materials related queries +
        +
      • Details per batch and stage
      • +
      • Inventories by producer/material type
      • +
      • Offered stages that can be transferred
      • +
    • +
    +

    The data manager also produces observable events:

    +
      +
    • BatchAdditionEvent – when a batch is created
    • +
    • BatchAmountUpdateEvent – when material is exchanged between batches
    • +
    • BatchImminentRemovalEvent – when a batch is about to be removed from the simulation
    • +
    • BatchPropertyDefinitionEvent – when a new material property is defined
    • +
    • BatchPropertyUpdateEvent – when a batch property value is updated
    • +
    • MaterialIdAdditionEvent. – when a new material type is added
    • +
    • MaterialsProducerAdditionEvent – when a new materials producer is added
    • +
    • MaterialsProducerPropertyDefinitionEvent – when a new materials producer property is defined
    • +
    • MaterialsProducerPropertyUpdateEvent – when a material producer property value is updated
    • +
    • MaterialsProducerResourceUpdateEvent – when a resource amount is added to a materials producer
    • +
    • StageAdditionEvent – when a stage is created
    • +
    • StageImminentRemovalEvent – when a stage is about to be removed from the simulation
    • +
    • StageMaterialsProducerUpdateEvent – when a stage is exchanged between materials producers
    • +
    • StageMembershipAdditionEvent – when a batch is added to a stage
    • +
    • StageMembershipRemovalEvent – when a batch is removed from a stage
    • +
    • StageOfferUpdateEvent – when a stage’s offer state is updated
    • +
    +
    +
    +

    12.4 Example Code (Lesson 19)

    +

    Example_19.java shows the use of the materials plugin. In it we will examine:

    +
      +
    • The initialization of the materials plugin
    • +
    • The flow of materials and resources between regions and materials producers
    • +
    • The observation of materials events
    • +
    +

    The example includes nine plugins:

    +
      +
    • Materials plugin – (GCM core plugin) used to manage materials
    • +
    • Resources plugin – (GCM core plugin) used to manage resources
    • +
    • People plugin – (GCM core plugin) used to manage people
    • +
    • Groups plugin – (GCM core plugin) used for spreading infections to people via their homes, schools and work places
    • +
    • Person properties plugin– (GCM core plugin) used to decorate properties onto people
    • +
    • Global properties plugin– (GCM core plugin) used to store policies and initial conditions
    • +
    • Stochastics plugin – (GCM core plugin) used to generate random numbers used in various decisions
    • +
    • Regions Plugin– (GCM core plugin) used for resource flow
    • +
    • Model plugin – (local plugin) used to introduce five actors that will: +
        +
      • Load the population
      • +
      • Manage infectious contacts
      • +
      • Manage vaccinations
      • +
      • Produce antigen used in vaccine production
      • +
      • Produce vaccines
      • +
    • +
    +
    +
    +

    12.5 Model

    +

    The example’s model represents a disease that is preventable through vaccination. People at the start of the simulation are either immune or susceptible and some of the susceptible adults are infected. The disease is transmitted via home, work, and school environments. Vaccination is the only disease mitigation strategy and vaccinated people do not infect others nor can be infected. Vaccine production is started once a threshold of infections is reached and continues until adequate vaccine has been produced to cover the entire population. Vaccines are modeled as a resource and are produced using several materials by two actors. The first of these actors produces antigen materials that are used in the production of the vaccine by the second actor.

    +
    +
    +

    12.6 Model Execution

    +

    The example’s execution is shown in Code Block 12.1

    +
    +
    +

    Code Block 12.1: The various plugins are gathered from their initial data, dimensions are added and the experiment is executed over 81 scenarios using 8 threads.

    +
        private void execute() {
    +
    +        ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()//
    +                .setThreadCount(8)//
    +                .build();
    +
    +        Experiment.builder()//
    +                .addPlugin(getMaterialsPlugin())//
    +                .addPlugin(getResourcesPlugin())//
    +                .addPlugin(getGlobalPropertiesPlugin())//
    +                .addPlugin(getPersonPropertiesPlugin())//
    +                .addPlugin(getStochasticsPlugin())//
    +                .addPlugin(getRegionsPlugin())//
    +                .addPlugin(getPeoplePlugin())//
    +                .addPlugin(getGroupsPlugin())//
    +                .addPlugin(ModelPlugin.getModelPlugin())//
    +
    +                .addDimension(getInfectionThresholdDimension())//
    +                .addDimension(getCommunityContactRateDimension())//
    +                .addDimension(getIntialInfectionsDimension())//
    +                .addDimension(getR0Dimension())//
    +
    +                .addExperimentContextConsumer(getNIOReportItemHandler())//
    +                .setExperimentParameterData(experimentParameterData).build()//
    +                .execute();//
    +
    +    }
    +
    +
    +

    The first action is to add the materials plugin (Code Block 12.2, Code Block 12.3, Code Block 12.4). Only the material producer id values contained in the MaterialsProducer enumeration and the material ids contained in the Material enumeration are added to the plugin’s data.

    +
    +
    +

    Code Block 12.2: The materials plugin establishes the materials producer ids and the material types.

    +
    private Plugin getMaterialsPlugin() {
    +    final MaterialsPluginData.Builder builder = MaterialsPluginData.builder();
    +    for (final MaterialsProducer materialsProducer : MaterialsProducer.values()) {
    +        builder.addMaterialsProducerId(materialsProducer);
    +    }
    +    for (final Material material : Material.values()) {
    +        builder.addMaterial(material);
    +    }
    +    final MaterialsPluginData materialsPluginData = builder.build();
    +    return MaterialsPlugin.builder().setMaterialsPluginData(materialsPluginData).getMaterialsPlugin();
    +}
    +
    +
    +
    +
    +

    Code Block 12.3: The ids of the materials producers are implemented via an enumeration.

    +
    public enum MaterialsProducer implements MaterialsProducerId {
    +
    +    VACCINE_PRODUCER, //
    +    ANTIGEN_PRODUCER;
    +
    +}
    +
    +
    +
    +
    +

    Code Block 12.4: The material ids are implemented via an enumeration.

    +
    public enum Material implements MaterialId {
    +
    +    VIRUS, //
    +    GROWTH_MEDIUM, //
    +    ANTIGEN, //
    +    ADJUVANT, //
    +    PRESERVATIVE, //
    +    STABILIZER;//
    +}
    +
    +
    +

    The next action is to add the resources plugin (Code Block 12.5, Code Block 12.6). Only the resource id values contained in the Resource enumeration are added to the plugin’s data. Region and person resource levels are initialized to zero.

    +
    +
    +

    Code Block 12.5: The resources plugin is created with the single VACCINE resource id.

    +
    private Plugin getResourcesPlugin() {
    +    final ResourcesPluginData.Builder builder = ResourcesPluginData.builder();
    +    for (final ResourceId resourcId : Resource.values()) {
    +        builder.addResource(resourcId, 0.0, true);
    +    }
    +    final ResourcesPluginData resourcesPluginData = builder.build();
    +    return ResourcesPlugin.builder().setResourcesPluginData(resourcesPluginData).getResourcesPlugin();
    +}
    +
    +
    +
    +
    +

    Code Block 12.6: The resource ids are implemented via an enumeration.

    +
    public enum Resource implements ResourceId {
    +    VACCINE;
    +}
    +
    +
    +

    The next action is to load the global properties plugin (Code Block 12.7). All of the global properties are marked as immutable since they will not change over the course of the simulation.

    +
      +
    • SUSCEPTIBLE_POPULATION_PROPORTION – The fraction of the population that is susceptible
    • +
    • INITIAL_INFECTIONS – The number of adults initially infected
    • +
    • MIN_INFECTIOUS_PERIOD – The minimum number of days a person is infectious
    • +
    • MAX_INFECTIOUS_PERIOD – The maximum number of days a person is infectious
    • +
    • POPULATION_SIZE – The initial size of the population
    • +
    • CHILD_POPULATION_PROPORTION – The fraction of the population between the ages of 0 and 18, inclusive
    • +
    • SENIOR_POPULATION_PROPORTION – The fraction of the population 65 and older
    • +
    • R0 – The expected number of people a single person will infect if all contacts are susceptible and transmission success is 100%
    • +
    • AVERAGE_HOME_SIZE – The average number of people per household
    • +
    • AVERAGE_SCHOOL_SIZE – The average number of students in a school
    • +
    • AVERAGE_WORK_SIZE – The average number of people per work place
    • +
    • COMMUNITY_CONTACT_RATE – The proportion of contacts that will be randomly chosen from the entire population
    • +
    • MANUFACTURE_VACCINE – Boolean that triggers vaccine manufacture
    • +
    • INFECTION_THRESHOLD – The fraction of the population that are infected in order to start vaccine manufacture
    • +
    +
    +
    +

    Code Block 12.7: The global properties plugin is initialized with several properties.

    +
    private Plugin getGlobalPropertiesPlugin() {
    +    final GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder();//
    +
    +    PropertyDefinition propertyDefinition = PropertyDefinition.builder()//
    +            .setType(Double.class)//
    +            .setPropertyValueMutability(false)//
    +            .setDefaultValue(0.0)//
    +            .build();
    +
    +    builder.defineGlobalProperty(GlobalProperty.SUSCEPTIBLE_POPULATION_PROPORTION, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.AVERAGE_HOME_SIZE, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.AVERAGE_SCHOOL_SIZE, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.AVERAGE_WORK_SIZE, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.CHILD_POPULATION_PROPORTION, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.SENIOR_POPULATION_PROPORTION, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.R0, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.COMMUNITY_CONTACT_RATE, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.INFECTION_THRESHOLD, propertyDefinition, 0);
    +
    +    propertyDefinition = PropertyDefinition.builder()//
    +            .setType(Integer.class)//
    +            .setPropertyValueMutability(false)//
    +            .build();
    +    builder.defineGlobalProperty(GlobalProperty.INITIAL_INFECTIONS, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.MIN_INFECTIOUS_PERIOD, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.MAX_INFECTIOUS_PERIOD, propertyDefinition, 0);
    +    builder.defineGlobalProperty(GlobalProperty.POPULATION_SIZE, propertyDefinition, 0);
    +
    +    propertyDefinition = PropertyDefinition.builder()//
    +            .setType(Boolean.class)//
    +            .setDefaultValue(false)//
    +            .setPropertyValueMutability(true)//
    +            .build();
    +    builder.defineGlobalProperty(GlobalProperty.MANUFACTURE_VACCINE, propertyDefinition, 0);
    +
    +    builder.setGlobalPropertyValue(GlobalProperty.POPULATION_SIZE, 10_000, 0);
    +    builder.setGlobalPropertyValue(GlobalProperty.SUSCEPTIBLE_POPULATION_PROPORTION, 1.0, 0);
    +    builder.setGlobalPropertyValue(GlobalProperty.INITIAL_INFECTIONS, 1, 0);
    +    builder.setGlobalPropertyValue(GlobalProperty.MIN_INFECTIOUS_PERIOD, 7, 0);
    +    builder.setGlobalPropertyValue(GlobalProperty.MAX_INFECTIOUS_PERIOD, 14, 0);
    +    builder.setGlobalPropertyValue(GlobalProperty.R0, 2.0, 0);
    +    builder.setGlobalPropertyValue(GlobalProperty.CHILD_POPULATION_PROPORTION, 0.235, 0);
    +    builder.setGlobalPropertyValue(GlobalProperty.SENIOR_POPULATION_PROPORTION, 0.169, 0);
    +    builder.setGlobalPropertyValue(GlobalProperty.AVERAGE_HOME_SIZE, 2.5, 0);
    +    builder.setGlobalPropertyValue(GlobalProperty.AVERAGE_SCHOOL_SIZE, 250.0, 0);
    +    builder.setGlobalPropertyValue(GlobalProperty.AVERAGE_WORK_SIZE, 30.0, 0);
    +    builder.setGlobalPropertyValue(GlobalProperty.INFECTION_THRESHOLD, 0.0, 0);
    +    builder.setGlobalPropertyValue(GlobalProperty.COMMUNITY_CONTACT_RATE, 0.0, 0);
    +
    +    final GlobalPropertiesPluginData globalPropertiesPluginData = builder.build();
    +
    +    return GlobalPropertiesPlugin.builder().setGlobalPropertiesPluginData(globalPropertiesPluginData)
    +            .getGlobalPropertiesPlugin();
    +
    +}
    +
    +
    +

    The person properties plugin is loaded (Code Block 12.8).

    +
      +
    • AGE – The integer age of a person
    • +
    • VACCINATED – Boolean vaccination status
    • +
    • VACCINE_SCHEDULED – Boolean indicating whether a vaccination is scheduled for a person
    • +
    • DISEASE_STATE – IMMUNE, SUSCEPTIBLE, INFECTIOUS or RECOVERED
    • +
    +
    +
    +

    Code Block 12.8: The person properties plugin includes person property definitions and the data for the person property report.

    +
    private Plugin getPersonPropertiesPlugin() {
    +
    +    final PersonPropertiesPluginData.Builder builder = PersonPropertiesPluginData.builder();
    +
    +    PropertyDefinition propertyDefinition = PropertyDefinition.builder()//
    +            .setType(Boolean.class)//
    +            .setDefaultValue(false)//
    +            .build();
    +
    +    builder.definePersonProperty(PersonProperty.VACCINATED, propertyDefinition, 0, false);//
    +    builder.definePersonProperty(PersonProperty.VACCINE_SCHEDULED, propertyDefinition, 0, false);//
    +
    +    propertyDefinition = PropertyDefinition.builder()//
    +            .setType(Integer.class)//
    +            .build();//
    +    builder.definePersonProperty(PersonProperty.AGE, propertyDefinition, 0, false);//
    +
    +    propertyDefinition = PropertyDefinition.builder()//
    +            .setType(DiseaseState.class)//
    +            .setDefaultValue(DiseaseState.SUSCEPTIBLE)//
    +            .build();
    +
    +    builder.definePersonProperty(PersonProperty.DISEASE_STATE, propertyDefinition, 0, false);//
    +
    +    final PersonPropertiesPluginData personPropertiesPluginData = builder.build();
    +
    +    PersonPropertyReportPluginData personPropertyReportPluginData = //
    +            PersonPropertyReportPluginData.builder()//
    +                    .setReportLabel(ModelReportLabel.PERSON_PROPERTY_REPORT)//
    +                    .setReportPeriod(ReportPeriod.DAILY)//
    +                    .includePersonProperty(PersonProperty.VACCINATED)//
    +                    .includePersonProperty(PersonProperty.VACCINE_SCHEDULED)//
    +                    .build();
    +
    +    return PersonPropertiesPlugin.builder()//
    +            .setPersonPropertiesPluginData(personPropertiesPluginData)//
    +            .setPersonPropertyReportPluginData(personPropertyReportPluginData)//
    +            .getPersonPropertyPlugin();
    +
    +}
    +
    +
    +

    There are four reports:

    +
      +
    • DiseaseStateReport – Shows the number of people in each of the disease states at the end of each simulation
    • +
    • PersonPropertyReport – Shows an hourly summary the number of people having various person property values
    • +
    • VaccineReport – Shows a daily summary of the number of people who have been scheduled for or have received vaccination
    • +
    • VaccineProductionReport – Shows a daily summary of the internal inventory of the antigen and vaccine producers
    • +
    +

    The stochastic plugin is initialized with a random seed and all simulations will start in the same stochastic state. The Regions plugin has one region and the people plugin is loaded in an empty state. People are added by an actor in the model plugin. The groups plugin is initialized with the three group types: HOME, SCHOOL, and WORK.

    +

    The model plugin adds three actors:

    +
      +
    • PopulationLoader – Adds people to the simulation and initializes immunity
    • +
    • ContactManager – Manages person to person transmission of the disease
    • +
    • Vaccinator – Distributes vaccine to people
    • +
    +
    +
    +

    12.7 Experiment dimensions

    +

    Four dimensions are added to the experiment that define alternate values for some of the global properties resulting in 81 scenarios. The dimension values are:

    +
      +
    • INFECTION_THRESHOLD – 0.01, 0.02, 0.05
    • +
    • COMMUNITY_CONTACT_RATE – 0.0, 0.01, 0.05
    • +
    • INITIAL_INFECTIONS – 1, 10, 100
    • +
    • R0 – 2.0, 2.5, 3.0
    • +
    +

    The fixed values are:

    +
      +
    • POPULATION_SIZE – 10,000
    • +
    • SUSCEPTIBLE_POPULATION_PROPORTION - 100%
    • +
    • MIN_INFECTIOUS_PERIOD – 7 days
    • +
    • MAX_INFECTIOUS_PERIOD – 14 days
    • +
    • CHILD_POPULATION_PROPORTION – 0.235
    • +
    • SENIOR_POPULATION_PROPORTION – 0.169
    • +
    • AVERAGE_HOME_SIZE – 2.5
    • +
    • AVERAGE_SCHOOL_SIZE – 250.0
    • +
    • AVERAGE_WORK_SIZE – 30.0
    • +
    • MANUFACTURE_VACCINE – false
    • +
    +
    +
    +

    12.8 The actors

    +

    We will finish this chapter by reviewing the actors and then examine the output.

    +
    +

    12.8.1 Population Loader

    +

    The PopulationLoader actor adds people to the simulation based on the number in the POPULATION_SIZE global property. The people are evenly distributed among the regions and each region creates children, working age adults and seniors based on the relevant global variables. The people are assigned homes, schools and work places accordingly, with each home having at least one adult. All people start out as either IMMUNE or SUSCEPTIBLE to the disease based on the SUSCEPTIBLE_POPULATION_PROPORTION global property.

    +
    +
    +

    12.8.2 Contact Manager

    +

    The ContactManager actor, Code Block 12.9, schedules infectious contacts between infected people and the susceptible population. On its initialization, it establishes various parameters from the global variables and schedules the initial infections over the first day of the simulation.

    +
    +
    +

    Code Block 12.9: The contact manager initializes by infecting the initially infected people in the first day.

    +
    public void init(final ActorContext actorContext) {
    +    this.actorContext = actorContext;
    +
    +    final StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);
    +    randomGenerator = stochasticsDataManager.getRandomGenerator();
    +    final Random random = new Random(randomGenerator.nextLong());
    +
    +    peopleDataManager = actorContext.getDataManager(PeopleDataManager.class);
    +
    +    groupsDataManager = actorContext.getDataManager(GroupsDataManager.class);
    +    final GlobalPropertiesDataManager globalPropertiesDataManager = actorContext
    +            .getDataManager(GlobalPropertiesDataManager.class);
    +    communityContactRate = globalPropertiesDataManager
    +            .getGlobalPropertyValue(GlobalProperty.COMMUNITY_CONTACT_RATE);
    +
    +    personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class);
    +    final List<PersonId> susceptiblePeople = personPropertiesDataManager
    +            .getPeopleWithPropertyValue(PersonProperty.DISEASE_STATE, DiseaseState.SUSCEPTIBLE);
    +    final List<PersonId> susceptibleAdults = new ArrayList<>();
    +    for (final PersonId personId : susceptiblePeople) {
    +        final int age = personPropertiesDataManager.getPersonPropertyValue(personId, PersonProperty.AGE);
    +        if (age > 18) {
    +            susceptibleAdults.add(personId);
    +        }
    +    }
    +
    +    Collections.shuffle(susceptibleAdults, random);
    +
    +    minInfectiousPeriod = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.MIN_INFECTIOUS_PERIOD);
    +    maxInfectiousPeriod = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.MAX_INFECTIOUS_PERIOD);
    +    final double r0 = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.R0);
    +    infectionInterval = (minInfectiousPeriod + maxInfectiousPeriod) / (2 * r0);
    +
    +    int initialInfections = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.INITIAL_INFECTIONS);
    +    initialInfections = FastMath.min(initialInfections, susceptibleAdults.size());
    +
    +    for (int i = 0; i < initialInfections; i++) {
    +        final PersonId personId = susceptibleAdults.get(i);
    +        final double planTime = (randomGenerator.nextDouble() * 0.5) + 0.25;
    +        actorContext.addPlan((c) -> infectPerson(personId), planTime);
    +    }
    +}
    +
    +
    +

    When a person is infected (Code Block 12.10), the contact manager determines the number of days that the person will be infectious and the number of potential infections that person will cause over those days. For each potential infectious contact, the manager schedules that contact at the relevant time. Finally, it schedules the transition of the person from infectious to recovered.

    +
    +
    +

    Code Block 12.10: When a person is infected, the number of possible infectious contacts is determined and planned. After the last infectious contact, the person is scheduled to become recovered.

    +
    private void infectPerson(final PersonId personId) {
    +    personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.DISEASE_STATE,
    +            DiseaseState.INFECTIOUS);
    +    final int infectiousDays = randomGenerator.nextInt(maxInfectiousPeriod - minInfectiousPeriod)
    +            + minInfectiousPeriod;
    +    final int infectionCount = (int) FastMath.round((infectiousDays / infectionInterval));
    +
    +    double planTime = actorContext.getTime();
    +
    +    for (int j = 0; j < infectionCount; j++) {
    +        planTime += infectionInterval;
    +        actorContext.addPlan((c) -> infectContact(personId), planTime);
    +    }
    +    actorContext.addPlan((c) -> endInfectiousness(personId), planTime);
    +}
    +
    +
    +

    For each potential infectious contact, the manager (Code Block 12.11) first determines if the contact is in one of the person’s groups or is in the general community. If it is a community contact, a person is selected from the general population. If it is a group contact, one of the person’s groups is chosen at random and a person is selected from that group. The selected person must be susceptible and unvaccinated for the contact to transfer the infection. Thus, as more people are either infected or vaccinated, the number of people each infectious person infects decreases.

    +
    +
    +

    Code Block 12.11: The contact manager attempts to make an infectious contact from an infected person to a randomly selected susceptible person who is not vaccinated from either the general public or from one of the infected person’s groups.

    +
    private void infectContact(final PersonId personId) {
    +
    +    if (randomGenerator.nextDouble() < communityContactRate) {
    +        final List<PersonId> people = peopleDataManager.getPeople();
    +        people.remove(personId);
    +        if (people.size() > 0) {
    +            final PersonId contactedPerson = people.get(randomGenerator.nextInt(people.size()));
    +            final DiseaseState diseaseState = personPropertiesDataManager.getPersonPropertyValue(contactedPerson,
    +                    PersonProperty.DISEASE_STATE);
    +            final boolean vaccinated = personPropertiesDataManager.getPersonPropertyValue(contactedPerson,
    +                    PersonProperty.VACCINATED);
    +            if ((diseaseState == DiseaseState.SUSCEPTIBLE) && !vaccinated) {
    +                infectPerson(contactedPerson);
    +            }
    +        }
    +    } else {
    +        final List<GroupId> groupsForPerson = groupsDataManager.getGroupsForPerson(personId);
    +        final GroupId groupId = groupsForPerson.get(randomGenerator.nextInt(groupsForPerson.size()));
    +        final GroupSampler groupSampler = GroupSampler.builder().setExcludedPersonId(personId).build();
    +        final Optional<PersonId> optional = groupsDataManager.sampleGroup(groupId, groupSampler);
    +        if (optional.isPresent()) {
    +            final PersonId contactedPerson = optional.get();
    +            final DiseaseState diseaseState = personPropertiesDataManager.getPersonPropertyValue(contactedPerson,
    +                    PersonProperty.DISEASE_STATE);
    +            final boolean vaccinated = personPropertiesDataManager.getPersonPropertyValue(contactedPerson,
    +                    PersonProperty.VACCINATED);
    +            if ((diseaseState == DiseaseState.SUSCEPTIBLE) && !vaccinated) {
    +                infectPerson(contactedPerson);
    +            }
    +        }
    +    }
    +}
    +
    +
    +
    +
    +

    12.8.3 Vaccinator

    +

    The Vaccinator actor, in Code Block 12.12, creates the VaccineProducer and AntigenProducer actors and initializes various parameters and data structures that will help it distribute vaccines. It subscribes to changes in the disease state of people so that it can determine how many vaccines are needed. It subscribes to changes in the resource states of materials producers so that it can distribute vaccines from the VaccineProduer to the regions and then on to the people in those regions. The two producers will not start manufacturing materials until the Vaccinator determines that enough people have been infected. When the number of infected exceeds the required threshold the Vaccinator will set the MANUFACTURE_VACCINE global variable to true and the producers will begin material and resource production.

    +
    +
    +

    Code Block 12.12: The vaccinator initializes by subscribing to changes in materials producer resource levels so that it can distribute vaccines to regions. It also subscribes to changes in person disease state to select people for vaccination.

    +
    public void init(final ActorContext actorContext) {
    +    this.actorContext = actorContext;
    +    actorContext.addActor(new VaccineProducer(MaterialsProducer.VACCINE_PRODUCER)::init);
    +    actorContext.addActor(new AntigenProducer(MaterialsProducer.ANTIGEN_PRODUCER)::init);
    +
    +    globalPropertiesDataManager = actorContext.getDataManager(GlobalPropertiesDataManager.class);
    +    peopleDataManager = actorContext.getDataManager(PeopleDataManager.class);
    +    personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class);
    +    resourcesDataManager = actorContext.getDataManager(ResourcesDataManager.class);
    +    regionsDataManager = actorContext.getDataManager(RegionsDataManager.class);
    +    materialsDataManager = actorContext.getDataManager(MaterialsDataManager.class);
    +    actorContext.subscribe(materialsDataManager.getEventFilterForMaterialsProducerResourceUpdateEvent(),
    +            this::handleMaterialsProducerResourceUpdateEvent);
    +
    +    for (final RegionId regionId : regionsDataManager.getRegionIds()) {
    +        vaccinationSchedules.put(regionId, new MutableDouble());
    +        availableVaccines.put(regionId, new MutableLong());
    +    }
    +
    +    actorContext.subscribe(
    +            personPropertiesDataManager.getEventFilterForPersonPropertyUpdateEvent(PersonProperty.DISEASE_STATE),
    +            this::handlePersonPropertyUpdateEvent);
    +
    +    final double infectionThreshold = globalPropertiesDataManager
    +            .getGlobalPropertyValue(GlobalProperty.INFECTION_THRESHOLD);
    +    infectedPersonCount = personPropertiesDataManager
    +            .getPeopleWithPropertyValue(PersonProperty.DISEASE_STATE, DiseaseState.INFECTIOUS).size();
    +    infectionPersonCountThreshold = (int) (peopleDataManager.getPopulationCount() * infectionThreshold);
    +    determineVaccineManufacutureStart();
    +    scheduleVaccinations();
    +}
    +
    +
    +

    As people become infected (Code Block 12.13, Code Block 12.14), the Vaccinator will determine if the vaccine manufacture needs to be started. Once the decision is made to start manufacture, the Vaccinator no longer needs to observe people becoming infectious, so it unsubscribes to changes in the disease states of people.

    +
    +
    +

    Code Block 12.13: If a person become infectious, the vaccinator reviews whether to start vaccine manufacture.

    +
    private void handlePersonPropertyUpdateEvent(final ActorContext actorContext,
    +        final PersonPropertyUpdateEvent personPropertyUpdateEvent) {
    +
    +    final DiseaseState diseaseState = (DiseaseState) personPropertyUpdateEvent.getCurrentPropertyValue();
    +    if (diseaseState == DiseaseState.INFECTIOUS) {
    +        infectedPersonCount++;
    +        determineVaccineManufacutureStart();
    +    }
    +}
    +
    +
    +
    +
    +

    Code Block 12.14: If vaccine manufacture has not yet started and the number of infected people exceeds a threshold, then the vaccinator set the MANUFACTURE_VACCINE global property to true, signaling to the vaccine related materials producers to start.

    +
    private void determineVaccineManufacutureStart() {
    +    if (!manufactureStarted) {
    +        if (infectedPersonCount >= infectionPersonCountThreshold) {
    +            manufactureStarted = true;
    +            globalPropertiesDataManager.setGlobalPropertyValue(GlobalProperty.MANUFACTURE_VACCINE, true);
    +            actorContext.unsubscribe(personPropertiesDataManager
    +                    .getEventFilterForPersonPropertyUpdateEvent(PersonProperty.DISEASE_STATE));
    +        }
    +    }
    +}
    +
    +
    +

    Whenever resources (vaccines in this case) are accumulated on a materials producer (Code Block 12.15), the Vaccinator must determine (Code Block 12.16) if the resource is a vaccine, whether the change to the resource level represents an addition of that resource to the inventory of the materials producer and whether any vaccine is needed. If the vaccine is available and needed, then the Vaccinator transfers the vaccines evenly amongst the regions and tries to schedule new vaccinations.

    +
    +
    +

    Code Block 12.15: When a resource change occurs on a materials producer, the vaccinator determines if the change represents doses of vaccine and whether there is any remaining demand.

    +
    private void handleMaterialsProducerResourceUpdateEvent(final ActorContext actorContext,
    +        final MaterialsProducerResourceUpdateEvent materialsProducerResourceUpdateEvent) {
    +    if (isCapturableResource(materialsProducerResourceUpdateEvent)) {
    +
    +        final MaterialsProducerId materialsProducerId = materialsProducerResourceUpdateEvent.materialsProducerId();
    +
    +        final long resourceLevel = materialsDataManager.getMaterialsProducerResourceLevel(materialsProducerId,
    +                Resource.VACCINE);
    +        final List<RegionId> regionIds = new ArrayList<>(regionsDataManager.getRegionIds());
    +
    +        final long resourceToTransfer = resourceLevel / regionIds.size();
    +        long remainderResource = resourceLevel % regionIds.size();
    +
    +        for (final RegionId regionId : regionIds) {
    +            final MutableLong availableVaccine = availableVaccines.get(regionId);
    +            if (remainderResource > 0) {
    +                materialsDataManager.transferResourceToRegion(materialsProducerId, Resource.VACCINE, regionId,
    +                        resourceToTransfer + 1);
    +                remainderResource--;
    +                availableVaccine.increment(resourceToTransfer + 1);
    +            } else {
    +                materialsDataManager.transferResourceToRegion(materialsProducerId, Resource.VACCINE, regionId,
    +                        resourceToTransfer);
    +                availableVaccine.increment(resourceToTransfer);
    +            }
    +        }
    +        scheduleVaccinations();
    +    }
    +}
    +
    +
    +
    +
    +

    Code Block 12.16: When a materials producer updates its resource level, the vaccinator confirms that the resource is vaccine doses that have been added to the producer’s inventory and that there is current demand for the vaccine.

    +
    private boolean isCapturableResource(
    +        final MaterialsProducerResourceUpdateEvent materialsProducerResourceUpdateEvent) {
    +    if (!materialsProducerResourceUpdateEvent.resourceId().equals(Resource.VACCINE)) {
    +        return false;
    +    }
    +    final boolean isResourceAdditionToProducer = materialsProducerResourceUpdateEvent
    +            .currentResourceLevel() > materialsProducerResourceUpdateEvent.previousResourceLevel();
    +    if (!isResourceAdditionToProducer) {
    +        return false;
    +    }
    +
    +    long distributedVaccineCount = personPropertiesDataManager
    +            .getPersonCountForPropertyValue(PersonProperty.VACCINATED, true);
    +
    +    for (final RegionId regionId : regionsDataManager.getRegionIds()) {
    +        distributedVaccineCount += resourcesDataManager.getRegionResourceLevel(regionId, Resource.VACCINE);
    +    }
    +    if (distributedVaccineCount >= peopleDataManager.getPopulationCount()) {
    +        return false;
    +    }
    +    return true;
    +}
    +
    +
    +

    Once vaccine doses are available, the Vaccinator schedules vaccinations (Code Block 12.17) at a fixed rate of 100 per day. The vaccines doses are distributed evenly amongst the regions and people are scheduled for vaccination throughout the day(s) following the receipt of vaccines from the VaccineProducer. Finally, the Vaccinator determines if enough vaccine has been received and to vaccinate the entire population and thus the manufacture of resource and materials can be halted.

    +
    +
    +

    Code Block 12.17: The vaccinator schedules vaccinations at initialization and whenever a materials producer produces resources. The vaccinator tries to distribute the available doses of vaccine with a standard delay time between scheduled vaccinations.

    +
    private void scheduleVaccinations() {
    +    final double delayTime = 1 / (double) vaccinationsPerRegionPerDay;
    +
    +    for (final RegionId regionId : vaccinationSchedules.keySet()) {
    +        final MutableLong availableVaccine = availableVaccines.get(regionId);
    +        final MutableDouble vaccineTime = vaccinationSchedules.get(regionId);
    +        vaccineTime.increment(delayTime);
    +        if (vaccineTime.getValue() < actorContext.getTime()) {
    +            vaccineTime.setValue(actorContext.getTime());
    +        }
    +        final List<PersonId> peopleInRegion = regionsDataManager.getPeopleInRegion(regionId);
    +        for (final PersonId personId : peopleInRegion) {
    +            final boolean vaccine_scheduled = personPropertiesDataManager.getPersonPropertyValue(personId,
    +                    PersonProperty.VACCINE_SCHEDULED);
    +            if (availableVaccine.getValue() <= 0) {
    +                break;
    +            }
    +            if (!vaccine_scheduled) {
    +                availableVaccine.decrement();
    +                personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.VACCINE_SCHEDULED,
    +                        true);
    +                actorContext.addPlan((c) -> vaccinatePerson(personId), vaccineTime.getValue());
    +                vaccineTime.increment(delayTime);
    +            }
    +        }
    +    }
    +    final int populationSize = peopleDataManager.getPopulationCount();
    +    final int scheduledVaccinationCount = personPropertiesDataManager
    +            .getPersonCountForPropertyValue(PersonProperty.VACCINE_SCHEDULED, true);
    +    if (scheduledVaccinationCount >= populationSize) {
    +        globalPropertiesDataManager.setGlobalPropertyValue(GlobalProperty.MANUFACTURE_VACCINE, false);
    +    }
    +}
    +
    +
    +

    The vaccination for each person (Code Block 12.18) sets the person’s VACCINATED property to true and transfers a single unit (dose) of vaccine from the person’s region to the person.

    +
    +
    +

    Code Block 12.18: The vaccinator sets the person’s VACCINATED property to true and moves one unit of vaccine from the person’s region to the person.

    +
    private void vaccinatePerson(final PersonId personId) {
    +    personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.VACCINATED, true);
    +    resourcesDataManager.transferResourceToPersonFromRegion(Resource.VACCINE, personId, 1L);
    +}
    +
    +
    +
    +
    +

    12.8.4 Antigen Producer

    +

    The AntigenProducer creates the ANTIGEN material from the GROWTH_MEDIUM and VIRUS materials. The generated ANTIGEN material is placed on stages in batches of 25 units of antigen and the stages are offered up to other materials producers. The VaccineProducer will transfer these stages to itself and use the ANTIGEN material in its own production of vaccine. Various factors limit the production of antigen. The GROWTH_MEDIUM and VIRUS materials initialize to a zero inventory and are scheduled to be received at regular intervals depending on ongoing demands and ordering limits. The AntigenProducer has a maximum stage storage capacity of 60 stages and production must halt once that limit is reached. The VaccineProducer must remove the stages for manufacture to continue. Each batch requires 6 hours to create and 15 days to ferment. The AntigenProducer can only start one batch of fermentation at a time.

    +

    The AntigenProducer initializes (Code Block 12.19) by adding specifications for the two materials used in the production of antigen. GROWTH_MEDIUM is ordered in allotments of 35 units and there is a 7 day delay from the time of order to receipt. VIRUS is ordered in allotments of 100 units with a 21 day delay. Both resources are utilized at one unit per unit of ANTIGEN produced. The AntigenProducer subscribes to the transfer of stages away from itself (capture by the VaccineProduer) so that it can possibly resume manufacture. It also subscribes to changes in the MANUFACTURE_VACCINE global property which must be true for any manufacturing of antigen to continue.

    +
    +
    +

    Code Block 12.19: The antigen producer initializes by subscribing to stage transfers from itself as well as changes to the manufacturing policy.

    +
    public void init(final ActorContext actorContext) {
    +    this.actorContext = actorContext;
    +    materialsDataManager = actorContext.getDataManager(MaterialsDataManager.class);
    +    globalPropertiesDataManager = actorContext.getDataManager(GlobalPropertiesDataManager.class);
    +    addMaterialRec(Material.GROWTH_MEDIUM, MaterialManufactureSpecification.builder()//
    +            .setDeliveryAmount(35.0)//
    +            .setDeliveryDelay(7.0)//
    +            .setStageAmount(1.0));//
    +
    +    addMaterialRec(Material.VIRUS, MaterialManufactureSpecification.builder()//
    +            .setDeliveryAmount(100.0)//
    +            .setDeliveryDelay(21.0)//
    +            .setStageAmount(1.0));//
    +
    +    // each time a stage is transferred
    +    actorContext.subscribe(
    +            materialsDataManager.getEventFilterForStageMaterialsProducerUpdateEvent_BySource(materialsProducerId),
    +            (c, e) -> planFermentation());
    +
    +    // each time the manufacture policy is changed
    +    actorContext.subscribe(globalPropertiesDataManager.getEventFilterForGlobalPropertyUpdateEvent(
    +            GlobalProperty.MANUFACTURE_VACCINE), (c, e) -> planFermentation());
    +
    +    planFermentation();
    +}
    +
    +
    +

    The planning of antigen batch creation and fermentation is triggered by several events:

    +
      +
    • Initialization of the AntigenProducer
    • +
    • Transfer of an offered stage to the VaccineProducer
    • +
    • Change of the MANUFACTURE_VACCINE global property by the Vaccinator
    • +
    • Receipt of ordered GROWTH_MEDIUM or VIRUS
    • +
    • The fermentation of a batch of ANTIGEN
    • +
    +

    The planning process (Code Block 12.20) starts by determining if manufacture has been halted by the Vaccinator. If manufacturing is allowed, then each of the required materials are analyzed and ordered as needed to build a full complement of offered stages. The process continues by constructing stages from the batches of GROWTH_MEDIUM and VIRUS that are held in inventory until there are either insufficient materials or no more capacity to store stages. The start of the fermentation process is determined and the end of each fermentation is scheduled.

    +
    +
    +

    Code Block 12.20: Responding to events that may allow for additional manufacture of antigen stages, the antigen producer attempts to continue manufacturing.

    +
    private void planFermentation() {
    +
    +    final Boolean continueManufature = globalPropertiesDataManager
    +            .getGlobalPropertyValue(GlobalProperty.MANUFACTURE_VACCINE);
    +    if (!continueManufature) {
    +        return;
    +    }
    +    orderMaterials();
    +
    +    while (!stagesAtCapacity() && hasSufficientMaterialsForNewStage()) {
    +        final StageId stageId = materialsDataManager.addStage(materialsProducerId);
    +        for (final MaterialId materialId : materialRecs.keySet()) {
    +            final MaterialManufactureSpecification materialManufactureSpecification = materialRecs.get(materialId);
    +            final BatchId newBatchId = materialsDataManager.addBatch(BatchConstructionInfo.builder()
    +                    .setMaterialsProducerId(materialsProducerId).setMaterialId(materialId).build());
    +            materialsDataManager.transferMaterialBetweenBatches(materialManufactureSpecification.getBatchId(),
    +                    newBatchId, materialManufactureSpecification.getStageAmount());
    +            materialsDataManager.moveBatchToStage(newBatchId, stageId);
    +        }
    +        final double batchAssemblyStartTime = FastMath.max(actorContext.getTime(), lastBatchAssemblyEndTime);
    +        final double fermentationStartTime = batchAssemblyStartTime + batchAssemblyDuration;
    +        lastBatchAssemblyEndTime = fermentationStartTime;
    +        final double planTime = fermentationStartTime + fermentationTime;
    +        actorContext.addPlan((c) -> endFermentationStage(stageId), planTime);
    +    }
    +}
    +
    +
    +

    Once fermentation is complete (Code Block 12.21) for a stage containing the GROWTH_MEDIUM and VIRUS, the stage is converted into a batch of 25 ANTIGEN units and staged. The new stage is offered for transfer to the VaccineProducer.

    +
    +
    +

    Code Block 12.21: When an antigen containing stage is ready for release, the antigen producer converts the stage into a batch of antigen and re-stages this batch on an offered stage.

    +
    private void endFermentationStage(final StageId stageId) {
    +    final BatchId batch = materialsDataManager.convertStageToBatch(//
    +            StageConversionInfo.builder()//
    +                    .setAmount(antigenUnits)//
    +                    .setMaterialId(Material.ANTIGEN)//
    +                    .setStageId(stageId)//
    +                    .build());//
    +
    +    final StageId antigenStage = materialsDataManager.addStage(materialsProducerId);
    +    materialsDataManager.moveBatchToStage(batch, antigenStage);
    +    materialsDataManager.setStageOfferState(antigenStage, true);
    +    planFermentation();
    +}
    +
    +
    +
    +
    +

    12.8.5 Vaccine Producer

    +

    The VaccineProducer creates 50 VACCINE resource units from staged ANTIGEN, ADJUVANT, PRESERVATIVE and STABILIZER materials and stores those vaccines. The Vaccinator gathers the stored vaccines and distributes them to the regions and people. The ANTIGEN material is gathered from the offered stages of the AntigenProducer and the other materials are ordered and received in a similar manner to the AntigenProducer. Vaccine production is limited by various factors. The non-ANTIGEN materials initialize to a zero inventory and are scheduled to be received at regular intervals depending on ongoing demands and ordering limits. The ANTIGEN material must be received from the AntigenProducer. There is a maximum stage storage capacity of 15 stages and production must halt once that limit is reached. Staged batches require 2.4 hours to assemble and 2 days to produce vaccine doses.

    +

    The VaccineProducer initializes (Code Block 12.22) by constructing an empty batch of ANTIGEN that will act as the inventory for all ANTIGEN used in stage creation. It then initializes the ordering constraints for the remaining materials. It subscribes to changes in stage offer states that will be used to detect when the AntigenProducer is offering stages containing ANTIGEN. Finally, it subscribes to changes in the MANUFACTURE_VACCINE global property which must be true for any manufacturing of vaccine to continue.

    +

    The planning of vaccine preparation and creation is triggered by several events:

    +
      +
    • Initialization of the VaccineProducer
    • +
    • The offering of a stage containing ANTIGEN from the AntigenProducer
    • +
    • Change of the MANUFACTURE_VACCINE global property by the Vaccinator
    • +
    • Receipt of ordered ADJUVANT, PRESERVATIVE or STABILIZER
    • +
    • The completion of stage conversion into vaccine doses
    • +
    +

    The planning process (Code Block 12.23) starts by determining if manufacture has been halted by the Vaccinator. If manufacturing is allowed, then each of the required materials are analyzed and ordered as needed to build a full complement of offered stages. The process continues by constructing stages from the batches of ANTIGEN, ADJUVANT, PRESERVATIVE and STABILIZER that are held in inventory until there are either insufficient materials or no more capacity to store stages. The start of the vaccine preparation process is determined and the end of each preparation is scheduled.

    +
    +
    +

    Code Block 12.22: The vaccine producer initializes by subscribing to offered stages(from the antigen producer) and subscribing to the start of vaccine manufacture.

    +
    public void init(final ActorContext actorContext) {
    +    this.actorContext = actorContext;
    +    materialsDataManager = actorContext.getDataManager(MaterialsDataManager.class);
    +    globalPropertiesDataManager = actorContext.getDataManager(GlobalPropertiesDataManager.class);
    +
    +    final BatchConstructionInfo batchConstructionInfo = //
    +            BatchConstructionInfo.builder()//
    +                    .setMaterialId(Material.ANTIGEN)//
    +                    .setMaterialsProducerId(materialsProducerId)//
    +                    .build();//
    +    antigenBatchId = materialsDataManager.addBatch(batchConstructionInfo);
    +
    +    addMaterialRec(Material.ADJUVANT, MaterialManufactureSpecification.builder()//
    +            .setDeliveryAmount(150.0)//
    +            .setDeliveryDelay(3.0)//
    +            .setStageAmount(2.7));//
    +
    +    addMaterialRec(Material.PRESERVATIVE, MaterialManufactureSpecification.builder()//
    +            .setDeliveryAmount(1000.0)//
    +            .setDeliveryDelay(14.0)//
    +            .setStageAmount(3.0));//
    +
    +    addMaterialRec(Material.STABILIZER, MaterialManufactureSpecification.builder()//
    +            .setDeliveryAmount(100.0)//
    +            .setDeliveryDelay(14.0)//
    +            .setStageAmount(1.0));//
    +
    +    actorContext.subscribe(materialsDataManager.getEventFilterForStageOfferUpdateEvent(),
    +            this::handleStageOfferUpdateEvent);
    +    actorContext.subscribe(globalPropertiesDataManager.getEventFilterForGlobalPropertyUpdateEvent(
    +            GlobalProperty.MANUFACTURE_VACCINE), (c, e) -> planVaccinePrepartion());
    +
    +    planVaccinePrepartion();
    +}
    +
    +
    +
    +
    +

    Code Block 12.23: Responding to events that may allow for additional manufacture of vaccine doses, the vaccine producer attempts to continue manufacturing.

    +
    private void planVaccinePrepartion() {
    +    final Boolean continueManufature = globalPropertiesDataManager
    +            .getGlobalPropertyValue(GlobalProperty.MANUFACTURE_VACCINE);
    +    if (!continueManufature) {
    +        return;
    +    }
    +    orderMaterials();
    +
    +    while (!stagesAtCapacity() && hasSufficientMaterialsForNewStage() && vaccineLevelBelowCapacity()) {
    +        final StageId stageId = materialsDataManager.addStage(materialsProducerId);
    +        for (final MaterialId materialId : materialRecs.keySet()) {
    +            final MaterialManufactureSpecification materialManufactureSpecification = materialRecs.get(materialId);
    +            final BatchId newBatchId = materialsDataManager.addBatch(BatchConstructionInfo.builder()
    +                    .setMaterialsProducerId(materialsProducerId).setMaterialId(materialId).build());
    +            materialsDataManager.transferMaterialBetweenBatches(materialManufactureSpecification.getBatchId(),
    +                    newBatchId, materialManufactureSpecification.getStageAmount());
    +            materialsDataManager.moveBatchToStage(newBatchId, stageId);
    +        }
    +
    +        BatchId newBatchId = materialsDataManager.addBatch(BatchConstructionInfo.builder()
    +                .setMaterialsProducerId(materialsProducerId).setMaterialId(Material.ANTIGEN).build());
    +        materialsDataManager.transferMaterialBetweenBatches(antigenBatchId, newBatchId, antigenAmountPerBatch);
    +        materialsDataManager.moveBatchToStage(newBatchId, stageId);
    +
    +        final double batchAssemblyStartTime = FastMath.max(actorContext.getTime(), lastBatchAssemblyEndTime);
    +        final double fermentationStartTime = batchAssemblyStartTime + batchAssemblyDuration;
    +        lastBatchAssemblyEndTime = fermentationStartTime;
    +        final double planTime = fermentationStartTime + vaccinePreparationTime;
    +        actorContext.addPlan((c) -> endVaccinePreparation(stageId), planTime);
    +    }
    +}
    +
    +
    +

    Once vaccine preparation is complete (Code Block 12.24) for a stage containing the ANTIGEN, ADJUVANT, PRESERVATIVE and STABILIZER materials, the stage is converted into 25 VACCINE units and stored on the producer. The Vaccinator will observe this change in inventory and may transfer the vaccine units to the regions and people as needed.

    +
    +
    +

    Code Block 12.24: When a vaccine production stage is ready for release, the vaccine producer converts the stage doses of vaccine and places them in its resource inventory.

    +
    private void endVaccinePreparation(final StageId stageId) {
    +    materialsDataManager.convertStageToResource(stageId, Resource.VACCINE, vaccineUnits);
    +    planVaccinePrepartion();
    +}
    +
    +
    +
    +
    +
    +

    12.9 Inspecting the output

    +
    +

    12.9.1 Person Property Report

    +

    The 81 scenarios result in a large amount of output (>1,000,000 rows) in the person property report. It shows the aggregate counts for people across all the person properties.

    +
    +
    +

    12.9.2 Disease State Report

    +

    Analysis of the number of recovered people at the end of each simulation shows that R0 and infection threshold needed to start vaccine manufacture play a significant role in the outcome. The number of initial infections and the community contact rate also show the expected positive correlation to the number of recovered, but to a lesser extent.

    +
    +
    +

    12.9.3 Vaccine Report

    +

    The vaccine report shows that lower infection thresholds drive an earlier start of vaccine manufacture. However, due to the relatively low production of the vaccine, it still takes 20-23 weeks to vaccinate the entire population.

    +
    +
    +

    12.9.4 Vaccine Production Report

    +

    The vaccine production report records the slow build up of ordered materials and conversion of those into vaccines. From the start of the materials collection to the first doses of vaccine being administered is approximately 32 days.

    + + +
    +
    + +
    + + +
    + + + + \ No newline at end of file diff --git a/doc/ch13-PartitionsPlugin.html b/doc/ch13-PartitionsPlugin.html new file mode 100644 index 000000000..b8d3303f5 --- /dev/null +++ b/doc/ch13-PartitionsPlugin.html @@ -0,0 +1,1558 @@ + + + + + + + + + +GCM-Docs - 13  Partitions Plugin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    + +
    + + + + + +
    + +
    +
    +

    13  Partitions Plugin

    +
    + + + +
    + + + + +
    + + +
    + +
    + +
    +

    A common problem that arises during modeling is the need to select (sample) people based on complex criteria while the people are evolving relative to those criteria. For example, a model may need to select people to vaccinate while the selection policies evolve, people are changing their status relative to vaccination and the avalibilty of vaccine is waxing and waning. Another common example is in infectious contact management, where contact rules, tranmission probabilities and mitigation strategies are all changing over time. The resultant code in the relevant actors and data managers can be complex and have unexpected run time and memory allocation consequences.

    +

    The partitions plugin provides a robust general solution to these requirements by introducing the partition. A partition represents a subset of the population that meets various filtering criteria and is further subdivided into a cellular structure of sub-populations within that partition. Partitions remain current as people change their characteristics.

    +
    +

    13.0.0.1 An example:

    +

    A partition could represent all people who are have been vaccinated in the last 20 years and who have not yet received a booster. The partition could be further subdivided by regional associations, work status or any other relevant person-related characteristic. An actor using the partition could then select people from the partition who live in a certain region and are employed. Person membership in the partition and the person’s location in the cells of the partition are maintained by the plugin and the client actor needs to simply specify the partition’s parameters once.

    +
    +
    +

    13.1 Plugin Data Initialization

    +

    Partitions are built dynamically by actors and data managers. While serialization of simulation state exceeds the scope of this chapter, the PartitionsPluginData class contains a single argument to support run-continuity between related experiment executions. The default policy is to not support run continuity and should only be set to true when the need for run continuity exists.

    +
    +
    +

    13.2 Plugin Behavior

    +

    The plugin adds a single data manager to the simulation as an instance of the PartitionsDataManager that is initialized with the PartitionsPluginData. The plugin has fixed dependencies on the People plugin and the Stochastic plugin. Unlike other plugins, its dependencies are dynamic and provided by the modeler. This topic will be covered later in the example code.

    +
    +
    +

    13.3 Data Manager

    +

    The PartitionsDataManager provides various operations with partitions. The data manager provides public methods that:

    +
      +
    • Add partitions
    • +
    • Remove partitions
    • +
    • Provide various queries about the state of partitions
    • +
    • Allow random sampling of people from partitions
    • +
    +

    Partitions are referenced by a client-assigned id and are maintained by the data manager. Each partition is composed of a multi-layered filter and zero to many labelers. A labeler is a mechanism for labeling a person based on that person’s characteristics. The labels form the dimensions of the cellular structure of the partition. For example, a labeler could label a person based on their integer age, but convert that age into labels such as “CHILD”, “ADULT” AND “SENIOR”. Labelers can span multiple personal charactertics such as combining regions with ages. For example, the labels might be “EASTERN_SHOOL_AGE_CHILDREN” or “WESTERN_SENIORS”, etc.

    +
    +
    +

    13.4 Example Code (Lesson 20)

    +

    Example_20.java shows the use of the partitions plugin. In it we will examine

    +
      +
    • The formation of partitions
    • +
    • The sampling of partitions
    • +
    +

    The example includes seven plugins:

    +
      +
    • People plugin – (GCM core plugin) used to manage people
    • +
    • Person properties plugin– (GCM core plugin) used to decorate properties onto people
    • +
    • Global properties plugin– (GCM core plugin) used to store policies and initial conditions
    • +
    • Stochastics plugin – (GCM core plugin) used to generate random numbers used in various decisions
    • +
    • Regions Plugin– (GCM core plugin) supports the person properties plugin
    • +
    • Partitions Plugin - (GCM core plugin) used for managing partitions
    • +
    • Model plugin – (local plugin) used to introduce three actors that will: +
        +
      • Load the population
      • +
      • Manage infectious contacts
      • +
      • Manage vaccinations
      • +
    • +
    +
    +
    +

    13.5 Model

    +

    The example’s model represents a disease that is preventable through vaccination. A small number of people at the start of the simulation are infected and the rest are susceptible. Vaccination is limited to uninfected adults and three vaccinations are needed to confer full immunity. There is 30 day delay between vaccine doses. The disease is transmitted at random from the infected to the susceptible, there is no incubation period and infectiousness lasts from 3 to 12 days. Vaccine supply is unlimited, but the vaccination rate is fixed with vaccine being assigned using lottery assignment: Older adults and those with the least vaccine protection are given a statistical preference.

    +
    +
    +

    13.6 Model Execution

    +

    The example’s execution is shown in Code Block 13.1

    +
    +
    +

    Code Block 13.1: The various plugins are gathered from their initial data and the experiment is executed.

    +
    private void execute() {
    +    Experiment.builder()//
    +            .addPlugin(getGlobalPropertiesPlugin())//
    +            .addPlugin(getPersonPropertiesPlugin())//
    +            .addPlugin(getRegionsPlugin())//
    +            .addPlugin(getStochasticPlugin())//
    +            .addPlugin(getPeoplePlugin())//
    +            .addPlugin(getPartitionsPlugin())//
    +            .addPlugin(ModelPlugin.getModelPlugin())//
    +            .addExperimentContextConsumer(getNIOReportItemHandler())//
    +            .build()//
    +            .execute();
    +}
    +
    +
    +

    There are several fixed global properties, Code Block 13.2 :

    +
      +
    • VACCINATOR_TYPE – the type of vaccinator used by the model
    • +
    • VACCINATIONS_PER_DAY – the number of vaccinations per day allowed
    • +
    • TRANSMISSION_PROBABILTY – the base probabilty of disease transmission per infectious contact
    • +
    • INFECTIOUS_CONTACT_RATE – the number of potentially infectious contacts per day per person
    • +
    • MINIMUM_INFECTIOUS_PERIOD – then minimum number of days that a person is infectious
    • +
    • MAXIMUM_INFECTIOUS_PERIOD – the maximum number of days that a person is infectious
    • +
    • INITIAL_INFECTION_COUNT – the number of people who are infectious at the begining of the simulation
    • +
    • INTER_VACCINATION_DELAY_TIME – the number of days required between a peson’s vaccinations
    • +
    • POPULATION_SIZE – the initial size of the population
    • +
    +
    +
    +

    Code Block 13.2: The global properties are all fixed values.

    +
    private Plugin getGlobalPropertiesPlugin() {
    +    GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder();
    +    PropertyDefinition propertyDefinition = PropertyDefinition.builder().setPropertyValueMutability(false)
    +            .setDefaultValue(10_000).setType(Integer.class).build();
    +    builder.defineGlobalProperty(GlobalProperty.POPULATION_SIZE, propertyDefinition, 0);
    +
    +    propertyDefinition = PropertyDefinition.builder().setPropertyValueMutability(false).setDefaultValue(10)
    +            .setType(Integer.class).build();
    +    builder.defineGlobalProperty(GlobalProperty.INITIAL_INFECTION_COUNT, propertyDefinition, 0);
    +
    +    propertyDefinition = PropertyDefinition.builder().setPropertyValueMutability(false)
    +            .setDefaultValue(VaccinatorType.PARTITION).setType(VaccinatorType.class).build();
    +    builder.defineGlobalProperty(GlobalProperty.VACCINATOR_TYPE, propertyDefinition, 0);
    +
    +    propertyDefinition = PropertyDefinition.builder().setPropertyValueMutability(false).setDefaultValue(3.0)
    +            .setType(Double.class).build();
    +    builder.defineGlobalProperty(GlobalProperty.MINIMUM_INFECTIOUS_PERIOD, propertyDefinition, 0);
    +
    +    propertyDefinition = PropertyDefinition.builder().setPropertyValueMutability(false).setDefaultValue(12.0)
    +            .setType(Double.class).build();
    +    builder.defineGlobalProperty(GlobalProperty.MAXIMUM_INFECTIOUS_PERIOD, propertyDefinition, 0);
    +
    +    propertyDefinition = PropertyDefinition.builder().setPropertyValueMutability(false).setDefaultValue(2.0)
    +            .setType(Double.class).build();
    +    builder.defineGlobalProperty(GlobalProperty.INFECTIOUS_CONTACT_RATE, propertyDefinition, 0);
    +
    +    propertyDefinition = PropertyDefinition.builder().setPropertyValueMutability(false).setDefaultValue(0.15)
    +            .setType(Double.class).build();
    +    builder.defineGlobalProperty(GlobalProperty.TRANSMISSION_PROBABILTY, propertyDefinition, 0);
    +
    +    propertyDefinition = PropertyDefinition.builder().setPropertyValueMutability(false).setDefaultValue(100)
    +            .setType(Integer.class).build();
    +    builder.defineGlobalProperty(GlobalProperty.VACCINATIONS_PER_DAY, propertyDefinition, 0);
    +
    +    propertyDefinition = PropertyDefinition.builder().setPropertyValueMutability(false).setDefaultValue(30.0)
    +            .setType(Double.class).build();
    +    builder.defineGlobalProperty(GlobalProperty.INTER_VACCINATION_DELAY_TIME, propertyDefinition, 0);
    +
    +    GlobalPropertiesPluginData globalPropertiesPluginData = builder.build();
    +    return GlobalPropertiesPlugin.builder().setGlobalPropertiesPluginData(globalPropertiesPluginData)
    +            .getGlobalPropertiesPlugin();
    +}
    +
    +
    +

    The person properties plugin contains four properties, Code Block 13.3:

    +
      +
    • AGE – the integer age of a person
    • +
    • WAITING_FOR_NEXT_DOSE – Boolean indicating that it is too soon to administer another dose of the vaccine
    • +
    • VACCINATION_COUNT – the number or vaccinations received
    • +
    • DISEASE_STATE – the state of the disease: SUSCEPTIBLE, INFECTIOUS or RECOVERED
    • +
    +
    +
    +

    Code Block 13.3: The four person properties are defined.

    +
    private Plugin getPersonPropertiesPlugin() {
    +    Builder builder = PersonPropertiesPluginData.builder();
    +
    +    PropertyDefinition propertyDefinition = PropertyDefinition.builder()//
    +            .setPropertyValueMutability(false).setType(Integer.class).build();
    +    builder.definePersonProperty(PersonProperty.AGE, propertyDefinition, 0.0, false);
    +
    +    propertyDefinition = PropertyDefinition.builder()//
    +            .setPropertyValueMutability(true).setType(Boolean.class)//
    +            .setDefaultValue(false).build();
    +    builder.definePersonProperty(PersonProperty.WAITING_FOR_NEXT_DOSE, propertyDefinition, 0.0, false);
    +
    +    propertyDefinition = PropertyDefinition.builder()//
    +            .setPropertyValueMutability(true)//
    +            .setType(DiseaseState.class).setDefaultValue(DiseaseState.SUSCEPTIBLE).build();
    +    builder.definePersonProperty(PersonProperty.DISEASE_STATE, propertyDefinition, 0.0, false);
    +
    +    propertyDefinition = PropertyDefinition.builder()//
    +            .setPropertyValueMutability(true)//
    +            .setType(Integer.class)//
    +            .setDefaultValue(0)//
    +            .build();
    +    builder.definePersonProperty(PersonProperty.VACCINATION_COUNT, propertyDefinition, 0.0, false);
    +
    +    PersonPropertiesPluginData personPropertiesPluginData = builder.build();
    +    return PersonPropertiesPlugin.builder()//
    +            .setPersonPropertiesPluginData(personPropertiesPluginData)//
    +            .getPersonPropertyPlugin();
    +}
    +
    +
    +

    The regions plugin contains three regions that will be randomly assigned to people. It exists to fulfill a dependency of the person properties plugin. The stochastics and people plugins are similarly minimal.

    +

    The partitions plugin is initialized in Code Block 13.4. It requires dependencies on those plugins that will be used to calculate partition filters and labelers. This runs counter to intuition since the model plugin (via its actors) is using the partitions plugin. However, the partitions data manager needs to keep the partitions current with the state of people. In the example model, all partitions use person properties, so the person properties plugin must process events before the partitions plugin.

    +
    +
    +

    Code Block 13.4: The partitions plugin is simple, but requires that it has dependencies on those plugins that will be used to calculate partition filters and labelers.

    +
    private Plugin getPartitionsPlugin() {
    +    PartitionsPluginData partitionsPluginData = PartitionsPluginData.builder()//
    +            .setRunContinuitySupport(false).build();
    +
    +    return PartitionsPlugin.builder()//
    +            .setPartitionsPluginData(partitionsPluginData)//
    +            .addPluginDependency(PersonPropertiesPluginId.PLUGIN_ID)//
    +            .getPartitionsPlugin();
    +}
    +
    +
    +
    +
    +

    13.7 Experiment dimensions

    +

    There are no experiment dimensions in this example with a single scenario being executed.

    +
    +
    +

    13.8 The Actors

    +
    +

    13.8.1 Population Loader

    +

    The population loader adds 10,000 people to the simulation in Code Block 13.5. Each person is assigned a randomly chosen region. All person properties are left at default value except for AGE which is generated randomly.

    +
    +
    +

    Code Block 13.5: People are added to the simulation with region and age assignments.

    +
    public class PopulationLoader {
    +    public void init(ActorContext actorContext) {
    +        StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);
    +        Well randomGenerator = stochasticsDataManager.getRandomGenerator();
    +        PeopleDataManager peopleDataManager = actorContext.getDataManager(PeopleDataManager.class);
    +        GlobalPropertiesDataManager globalPropertiesDataManager = actorContext
    +                .getDataManager(GlobalPropertiesDataManager.class);
    +        Integer populationSize = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.POPULATION_SIZE);
    +        for (int i = 0; i < populationSize; i++) {
    +            Region region = Region.getRandomRegion(randomGenerator);
    +            int age = AgeGroup.getRandomAge(randomGenerator);
    +            PersonPropertyValueInitialization personPropertyValueInitialization = new PersonPropertyValueInitialization(PersonProperty.AGE,age);
    +            PersonConstructionData personConstructionData = PersonConstructionData.builder()//
    +                    .add(region)//
    +                    .add(personPropertyValueInitialization)
    +                    .build();//
    +            peopleDataManager.addPerson(personConstructionData);
    +        }
    +    }
    +}
    +
    +
    +
    +
    +

    13.8.2 Contact Manager

    +

    The ContactManager actor, Code Block 13.6, schedules infectious contacts between infected people and the susceptible population. On its initialization, it establishes various parameters from the global variables and schedules the initial infections. It uses a simple partition configured in its default state. It has no filter and no labelers, so it will include all people in the simulation. It is used to select random people for potential infectious contacts and is somewhat more efficient than storing a list of all people locally.

    +
    +
    +

    Code Block 13.6: The Contact Manager uses a simple partition that contains the entire population and uses it to randomly select infectious contacts.

    +
    public void init(ActorContext actorContext) {
    +    this.actorContext = actorContext;
    +    partitionsDataManager = actorContext.getDataManager(PartitionsDataManager.class);
    +    personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class);
    +    peopleDataManager = actorContext.getDataManager(PeopleDataManager.class);
    +    globalPropertiesDataManager = actorContext.getDataManager(GlobalPropertiesDataManager.class);
    +    StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);
    +    randomGenerator = stochasticsDataManager.getRandomGenerator();
    +
    +    loadGlobalProperties();
    +    establishPopulationPartition();
    +    initializeInfections();
    +}
    +
    +private void establishPopulationPartition() {
    +    partitionsDataManager.addPartition(Partition.builder().build(), partitionKey);
    +}
    +
    +
    +

    Infection of a person, Code Block 13.7, results in the immediate scheduling of infectious contacts. The person is infectious for 3 to 12 days and will have 2 contacts per day. Each contact has a base 15% probability of infecting the contacted person, Code Block 13.8. Susceptible people who have been previously vaccinated have a reduced chance of contracting the disease. Note that the partition is used to select the contacted person and that the infectious person is excluded from contact since a person cannot contact themselves.

    +
    +
    +

    Code Block 13.7: Each time a person is infected, the contact manager schedules several follow-on infectious contacts.

    +
    private void infectPerson(PersonId personId) {
    +    personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.DISEASE_STATE,
    +            DiseaseState.INFECTIOUS);
    +
    +    double infectiousPeriod = randomGenerator.nextDouble() * (maximumInfectiousPeriod - minimumInfectiousPeriod)
    +            + minimumInfectiousPeriod;
    +
    +    double lastContactTime = actorContext.getTime() + infectiousPeriod;
    +
    +    double infectionTime = actorContext.getTime();
    +    while (true) {
    +        double contactDelay = (1.0 / infectiousContactRate);
    +        contactDelay *= (1 + randomGenerator.nextDouble() / 5 - 0.1);
    +        infectionTime += contactDelay;
    +
    +        if (infectionTime < lastContactTime) {
    +            actorContext.addPlan((c2) -> {
    +                processInfectiousContact(personId);
    +            }, infectionTime);
    +        } else {
    +            break;
    +        }
    +    }
    +    actorContext.addPlan((c2) -> {
    +        endInfectiousness(personId);
    +    }, lastContactTime);
    +
    +}
    +
    +
    +
    +
    +

    Code Block 13.8: Infectious contacts are subject to mitigation. To be infected, the contacted person must be susceptible and may having varying degrees of protection from previous vaccinations.

    +
    private void processInfectiousContact(PersonId personId) {
    +    
    +    PartitionSampler partitionSampler = PartitionSampler.builder().setExcludedPerson(personId).build();
    +    Optional<PersonId> optionalPersonId = partitionsDataManager.samplePartition(partitionKey, partitionSampler);
    +    if (optionalPersonId.isPresent()) {
    +
    +        PersonId contactedPersonId = optionalPersonId.get();
    +        
    +
    +        DiseaseState diseaseState = personPropertiesDataManager.getPersonPropertyValue(contactedPersonId,
    +                PersonProperty.DISEASE_STATE);
    +        if (diseaseState == DiseaseState.SUSCEPTIBLE) {
    +
    +            int vaccinationCount = personPropertiesDataManager.getPersonPropertyValue(contactedPersonId,
    +                    PersonProperty.VACCINATION_COUNT);
    +            double mitigatedTransmissionProbability;
    +
    +            switch (vaccinationCount) {
    +            case 0:
    +                mitigatedTransmissionProbability = 1;
    +                break;
    +            case 1:
    +                mitigatedTransmissionProbability = 0.5;
    +                break;
    +            case 2:
    +                mitigatedTransmissionProbability = 0.2;
    +                break;
    +            default:
    +                mitigatedTransmissionProbability = 0;
    +                break;
    +            }
    +
    +            mitigatedTransmissionProbability *= transmissionProbabilty;
    +
    +            if (randomGenerator.nextDouble() < mitigatedTransmissionProbability) {
    +                infectPerson(contactedPersonId);
    +            }
    +        }
    +    }
    +
    +}
    +
    +
    +
    +
    +

    13.8.3 Vaccinator Manager

    +

    This example contains three versions of the vaccinator actor that demonstrate an evolving solution to person selection. The vaccine manager, Code Block 13.9, uses the global property,VACCINATOR_TYPE, to create an instance of the vaccinator actor. The global property is set to the partition vaccinator and the remaining two possibilities are left for reader inspection.

    +
    +
    +

    Code Block 13.9: The vaccine manager creates one of three vaccinator actors based on the global property,VACCINATOR_TYPE.

    +
    public class VaccinatorManager {
    +    public void init(ActorContext actorContext) {
    +        GlobalPropertiesDataManager globalPropertiesDataManager = actorContext.getDataManager(GlobalPropertiesDataManager.class);
    +        VaccinatorType vaccinatorType =
    +        globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.VACCINATOR_TYPE);
    +        switch (vaccinatorType) {
    +        case PARTITION:
    +            actorContext.addActor(new PartitionVaccinator()::init);
    +            break;
    +        case EVENT:
    +            actorContext.addActor(new EventVaccinator()::init);
    +            break;
    +        case INSPECTION:
    +            actorContext.addActor(new InspectionVaccinator()::init);
    +            break;
    +        default:
    +            throw new RuntimeException("unhandled case "+vaccinatorType);                       
    +        }
    +    }
    +}
    +
    +
    +
    +
    +

    13.8.4 Inspection Vaccinator

    +

    The inspection vaccinator represents the most obvious approach to finding the next person to vaccinate. It initializes, Code Block 13.10, and immediately starts vaccinating the population.

    +
    +
    +

    Code Block 13.10: The inspection-based vaccinator establishes its working variables and begins planning the next vaccination.

    +
    public void init(ActorContext actorContext) {
    +    this.actorContext = actorContext;
    +
    +    StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);
    +    randomGenerator = stochasticsDataManager.getRandomGenerator();
    +    personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class);
    +    peopleDataManager = actorContext.getDataManager(PeopleDataManager.class);
    +    globalPropertiesDataManager = actorContext.getDataManager(GlobalPropertiesDataManager.class);
    +
    +    establishWorkingVaribles();
    +    planNextVaccination();
    +}
    +
    +
    +

    Each vaccine attempt repeats the following steps, Code Block 13.11:

    +
      +
    • Gather the list of all people

    • +
    • Build data structures to hold those people in separate groups aligned to age and vaccination status priorities

    • +
    • Loop through the population, selecting people who:

      +
        +
      • Are adults
      • +
      • Are not infectious or recovered
      • +
      • Have fewer that 3 vaccinations
      • +
      • Are not the in post-vaccination 30 day waiting period
      • +
    • +
    • Assign each selected person to an age/vaccination count category

    • +
    • Note if there are people who will need vaccination in the future after the waiting period is over

    • +
    • Select a category based on the weight, Code Block 13.12, of the category and the number of people who are associated with the category

    • +
    • Select a person at random from the selected category

    • +
    • If a person was selected or a future vaccination will be needed, choose to continue the vaccination process

    • +
    +
    +
    +

    Code Block 13.11: The inspection-based vaccinator vaccinates 100 people per day. Each vaccination attempt considers every person in the simulation.

    +
    private void planNextVaccination() {
    +    actorContext.addPlan(this::vaccinatePerson, interVaccinationTime + actorContext.getTime());
    +}
    +
    +
    +private void vaccinatePerson(ActorContext actorContext) {
    +    List<PersonId> people = peopleDataManager.getPeople();
    +    Map<MultiKey, List<PersonId>> candidates = new LinkedHashMap<>();
    +    Map<MultiKey, Double> weights = new LinkedHashMap<>();
    +
    +    List<AgeGroup> eligibleAgeGroups = new ArrayList<>();
    +    eligibleAgeGroups.add(AgeGroup.ADULT_18_44);
    +    eligibleAgeGroups.add(AgeGroup.ADULT_45_64);
    +    eligibleAgeGroups.add(AgeGroup.SENIOR);
    +
    +    List<Integer> eligibleVaccineCounts = new ArrayList<>();
    +    eligibleVaccineCounts.add(0);
    +    eligibleVaccineCounts.add(1);
    +    eligibleVaccineCounts.add(2);
    +
    +    for (AgeGroup ageGroup : eligibleAgeGroups) {
    +        for (Integer vaccineCount : eligibleVaccineCounts) {
    +            MultiKey multiKey = new MultiKey(ageGroup, vaccineCount);
    +            double weight = getWeight(ageGroup, vaccineCount);
    +            weights.put(multiKey, weight);
    +            candidates.put(multiKey, new ArrayList<>());
    +        }
    +    }
    +
    +    potentialEligiblePeopleExist = false;
    +
    +    for (PersonId personId : people) {
    +        int age = personPropertiesDataManager.getPersonPropertyValue(personId, PersonProperty.AGE);
    +        if (age < 18) {
    +            continue;
    +        }
    +        DiseaseState diseaseState = personPropertiesDataManager.getPersonPropertyValue(personId,
    +                PersonProperty.DISEASE_STATE);
    +        if (diseaseState != DiseaseState.SUSCEPTIBLE) {
    +            continue;
    +        }
    +        int vaccinationCount = personPropertiesDataManager.getPersonPropertyValue(personId,
    +                PersonProperty.VACCINATION_COUNT);
    +        if (vaccinationCount > 2) {
    +            continue;
    +        }
    +
    +        boolean waitingFromPreviousVaccination = personPropertiesDataManager.getPersonPropertyValue(personId,
    +                PersonProperty.WAITING_FOR_NEXT_DOSE);
    +
    +        if (waitingFromPreviousVaccination) {
    +            potentialEligiblePeopleExist = true;
    +            continue;
    +        }
    +
    +        AgeGroup ageGroup = AgeGroup.getAgeGroup(age);
    +        MultiKey multiKey = new MultiKey(ageGroup, vaccinationCount);
    +        candidates.get(multiKey).add(personId);
    +    }
    +
    +    Map<MultiKey, Double> extendedWeights = new LinkedHashMap<>();
    +
    +    double sumOfExtendedWeights = 0;
    +    for (MultiKey multiKey : weights.keySet()) {
    +        Double weight = weights.get(multiKey);
    +        int candidateCount = candidates.get(multiKey).size();
    +        Double extenedWeight = weight * candidateCount;
    +        extendedWeights.put(multiKey, extenedWeight);
    +        sumOfExtendedWeights += extenedWeight;
    +    }
    +
    +    PersonId selectedCandidate = null;
    +
    +    double selectedWeight = sumOfExtendedWeights * randomGenerator.nextDouble();
    +    for (MultiKey multiKey : extendedWeights.keySet()) {
    +        Double extendedWeight = extendedWeights.get(multiKey);
    +        selectedWeight -= extendedWeight;
    +        if (selectedWeight <= 0) {
    +            List<PersonId> seletedCandidates = candidates.get(multiKey);
    +            if (!seletedCandidates.isEmpty()) {
    +                int index = randomGenerator.nextInt(seletedCandidates.size());
    +                selectedCandidate = seletedCandidates.get(index);
    +            }
    +            break;
    +        }
    +    }
    +
    +    if (selectedCandidate != null) {
    +
    +        int vaccinationCount = personPropertiesDataManager.getPersonPropertyValue(selectedCandidate,
    +                PersonProperty.VACCINATION_COUNT);
    +        vaccinationCount++;
    +        personPropertiesDataManager.setPersonPropertyValue(selectedCandidate, PersonProperty.VACCINATION_COUNT,
    +                vaccinationCount);
    +        if (vaccinationCount < 3) {
    +            personPropertiesDataManager.setPersonPropertyValue(selectedCandidate,
    +                    PersonProperty.WAITING_FOR_NEXT_DOSE, true);
    +            planWaitTermination(selectedCandidate);
    +        }
    +        planNextVaccination();
    +    } else {
    +        if (potentialEligiblePeopleExist) {
    +            planNextVaccination();
    +        }
    +    }
    +}
    +
    +
    +
    +
    +

    Code Block 13.12: The inspection-based vaccinator assigns a probability weight to each person based on their age and number of vaccine doses administered.

    +
    private double getWeight(AgeGroup ageGroup, int vaccineCount) {
    +
    +    double result = 1;
    +
    +    switch (ageGroup) {
    +    case ADULT_18_44:
    +        result += 0;
    +        break;
    +    case ADULT_45_64:
    +        result += 3;
    +        break;
    +    case CHILD:
    +        result += 0;
    +        break;
    +    case SENIOR:
    +        result += 10;
    +        break;
    +    default:
    +        break;
    +    }
    +
    +    switch (vaccineCount) {
    +    case 0:
    +        result += 4;
    +        break;
    +    case 1:
    +        result += 3;
    +        break;
    +    case 2:
    +        result += 2;
    +        break;
    +    default:
    +        result += 0;
    +        break;
    +    }
    +
    +    return result;
    +}
    +
    +
    +

    While this process is fairly straight forward, it does involve fairly complex and tricky calculations. Worse yet, it is extremely repetitive and does not scale well to realistic population sizes. It is orders of magnitude slower than the next two approaches.

    +
    +
    +

    13.8.5 Event Vaccinator

    +

    The event vaccinator transforms the inspection vaccinator’s approach by retaining the category organizational structures and using the event system to maintain the lists of eligible people. It initializes, Code Block 13.13, by building the sub-populations of eligible people and subscribing to all events that may alter those populations.

    +
    +
    +

    Code Block 13.13: The event-based vaccinator improves on the inspection-based vaccinator by maintaining the eligible sub-populations.

    +
    public void init(ActorContext actorContext) {
    +    this.actorContext = actorContext;
    +
    +    StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);
    +    randomGenerator = stochasticsDataManager.getRandomGenerator();
    +    personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class);
    +    peopleDataManager = actorContext.getDataManager(PeopleDataManager.class);
    +    globalPropertiesDataManager = actorContext.getDataManager(GlobalPropertiesDataManager.class);
    +
    +    establishWorkingVaribles();
    +    subscribeToPersonPropertyUpdateEvents();
    +    intializeCandidatesAndWeights();
    +    planNextVaccination();
    +}
    +
    +
    +

    The vaccination process is very similar to the inspection-based method, but there is no recalculation of the sub-populations, Code Block 13.14. Instead, the event-based approach subscribes to all person property updates, Code Block 13.15 , and processes each update by:

    +
      +
    • Removing the relevant person from the sub-populations
    • +
    • Filtering out people who : +
        +
      • Are not susceptible
      • +
      • Are not adults
      • +
      • Are waiting from the last dose
      • +
      • Already have 3 vaccinations
      • +
    • +
    • Adding the person back into the sub-populations. Note the person may have moved from one sub-population to another.
    • +
    +
    +
    +

    Code Block 13.14: The event-based vaccinator selects from maintained sub-populations.

    +
    private void planNextVaccination() {
    +    Plan<ActorContext> plan = Plan.builder(ActorContext.class)//
    +            .setTime(interVaccinationTime + actorContext.getTime())//
    +            .setKey(planId).setCallbackConsumer(this::vaccinatePerson).build();
    +
    +    actorContext.addPlan(plan);
    +}
    +
    +private void vaccinatePerson(ActorContext actorContext) {
    +
    +    Map<MultiKey, Double> extendedWeights = new LinkedHashMap<>();
    +
    +    double sumOfExtendedWeights = 0;
    +    for (MultiKey multiKey : weights.keySet()) {
    +        Double weight = weights.get(multiKey);
    +        int candidateCount = candidates.get(multiKey).size();
    +        Double extenedWeight = weight * candidateCount;
    +        extendedWeights.put(multiKey, extenedWeight);
    +        sumOfExtendedWeights += extenedWeight;
    +    }
    +
    +    PersonId selectedCandidate = null;
    +
    +    double selectedWeight = sumOfExtendedWeights * randomGenerator.nextDouble();
    +    for (MultiKey multiKey : extendedWeights.keySet()) {
    +        Double extendedWeight = extendedWeights.get(multiKey);
    +        selectedWeight -= extendedWeight;
    +        if (selectedWeight <= 0) {
    +            List<PersonId> seletedCandidates = candidates.get(multiKey);
    +            if (!seletedCandidates.isEmpty()) {
    +                int index = randomGenerator.nextInt(seletedCandidates.size());
    +                selectedCandidate = seletedCandidates.get(index);
    +            }
    +            break;
    +        }
    +    }
    +
    +    if (selectedCandidate != null) {
    +
    +        int vaccinationCount = personPropertiesDataManager.getPersonPropertyValue(selectedCandidate,
    +                PersonProperty.VACCINATION_COUNT);
    +        vaccinationCount++;
    +        personPropertiesDataManager.setPersonPropertyValue(selectedCandidate, PersonProperty.VACCINATION_COUNT,
    +                vaccinationCount);
    +        if (vaccinationCount < 3) {
    +            personPropertiesDataManager.setPersonPropertyValue(selectedCandidate,
    +                    PersonProperty.WAITING_FOR_NEXT_DOSE, true);
    +            planWaitTermination(selectedCandidate);
    +        }
    +        planNextVaccination();
    +    }
    +
    +}
    +
    +
    +
    +
    +

    Code Block 13.15: The event-based vaccinator processes each person property update event by first removing the person from the sub-populations and then adding them back in if required.

    +
    private void handlePersonPropertyChange(ActorContext actorContext,
    +        PersonPropertyUpdateEvent personPropertyUpdateEvent) {
    +
    +    PersonId personId = personPropertyUpdateEvent.personId();
    +
    +    // remove the person if they are being tracked
    +    MultiKey multiKey = groupMap.remove(personId);
    +    List<PersonId> list = candidates.get(multiKey);
    +    if (list != null) {
    +        list.remove(personId);
    +    }
    +
    +    DiseaseState diseaseState = personPropertiesDataManager.getPersonPropertyValue(personId,
    +            PersonProperty.DISEASE_STATE);
    +
    +    // the person must be susceptible
    +    if (diseaseState != DiseaseState.SUSCEPTIBLE) {
    +        return;
    +    }
    +
    +    Integer vaccinationCount = personPropertiesDataManager.getPersonPropertyValue(personId,
    +            PersonProperty.VACCINATION_COUNT);
    +
    +    if (vaccinationCount > 2) {
    +        return;
    +    }
    +
    +    int age = personPropertiesDataManager.getPersonPropertyValue(personId, PersonProperty.AGE);
    +    AgeGroup ageGroup = AgeGroup.getAgeGroup(age);
    +    if (ageGroup == AgeGroup.CHILD) {
    +        return;
    +    }
    +    boolean waitingForNextDose = personPropertiesDataManager.getPersonPropertyValue(personId,
    +            PersonProperty.WAITING_FOR_NEXT_DOSE);
    +    if (waitingForNextDose) {
    +        return;
    +    }
    +
    +    multiKey = new MultiKey(ageGroup, vaccinationCount);
    +
    +    list = candidates.get(multiKey);
    +    if (list != null) {
    +        list.add(personId);
    +        groupMap.put(personId, multiKey);
    +    }
    +
    +}
    +
    +
    +

    Some care must be given to properly terminating the vaccination process. When a vaccination attempt is made and there is no eligible candidate, no new attempt is scheduled. Instead, the vaccinator relies on knowing that a new candidate will appear only when a person has ended their post-vaccination waiting period. To accomplish this, Code Block 13.16, the vaccinator uses a plan id for vaccination plans. When it processes the end of the waiting period for a person it looks to the simulation and determines whether there is a future plan to vaccinate. If no such plan exists, it immediately vaccinates the person and re-starts the vaccination process.

    +
    +
    +

    Code Block 13.16: The event-vaccinator can restart the vaccination process when a person becomes eligible after the post-vacination waiting period is over.

    +
    private void endWaitTime(PersonId personId) {
    +    personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.WAITING_FOR_NEXT_DOSE, false);
    +    if (actorContext.getPlan(planId).isEmpty()) {
    +        vaccinatePerson(actorContext);
    +    }
    +}
    +
    +
    +

    The performance of the code is multiple orders of magnitude better than the inspection approach. However, there are a few drawbacks:

    +
      +
    • The code is more complex and is more difficult to get right. Edge cases abound.
    • +
    • It still relies on list/map/set based data structures which can be slow and use too much memory.
    • +
    • If the selection criteria were to become more complex or if the actor needs to select people from subsets of the eligible people for special purposes as occurs in more realistic use cases, a great deal of effort would have to expended to ensure correctly functioning code.
    • +
    +
    +
    +

    13.8.6 Partition Vaccinator

    +

    The partition-vaccinator improves on the event-vaccinator by using partitions. Internally, the partitions are performing similar event-triggered updates of the sub-populations. However, their approach is more sophisticated:

    +
      +
    • Supports complex filtering
    • +
    • Allows for categorization of data via labels
    • +
    • Supports multi-dimensional labeling of the filtered population
    • +
    • Supports nuanced sampling of the population that can be aligned to label based subsets
    • +
    +

    The vaccinator initializes, Code Block 13.17, by creating partitions that will manage the data structures and subscribe to the relevant events. It then proceeds with vaccination planning as in the previous versions.

    +
    +
    +

    Code Block 13.17: The partition-vaccinator manages the eligible population via partitions.

    +
    public void init(ActorContext actorContext) {
    +    this.actorContext = actorContext;
    +    personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class);
    +    partitionsDataManager = actorContext.getDataManager(PartitionsDataManager.class);
    +    globalPropertiesDataManager = actorContext.getDataManager(GlobalPropertiesDataManager.class);
    +    establishWorkingVaribles();
    +    createPartitions();
    +    planNextVaccination();
    +}
    +
    +
    +

    The vaccinator uses two partitions to manage vaccination, Code Block 13.18. The first partition is used to select currently eligible people and the second covers people who may become eligible in the future and is used to determine if vaccination of the population is complete.

    +
    +
    +

    Code Block 13.18: The partition-vaccinator creates two partitions to help with person selection and termination of vaccinations.

    +
    private void createPartitions() {
    +
    +    PersonPropertyFilter ageFilter = new PersonPropertyFilter(PersonProperty.AGE, Equality.GREATER_THAN_EQUAL, 18);
    +
    +    PersonPropertyFilter diseaseFilter = new PersonPropertyFilter(PersonProperty.DISEASE_STATE, Equality.EQUAL,
    +            DiseaseState.SUSCEPTIBLE);
    +
    +    PersonPropertyFilter vaccineFilter = new PersonPropertyFilter(PersonProperty.VACCINATION_COUNT,
    +            Equality.LESS_THAN, 3);
    +
    +    PersonPropertyFilter waitFilter = new PersonPropertyFilter(PersonProperty.WAITING_FOR_NEXT_DOSE, Equality.EQUAL,
    +            false);
    +
    +    Filter filter = ageFilter.and(diseaseFilter).and(vaccineFilter).and(waitFilter);
    +
    +    Labeler ageLabeler = new FunctionalPersonPropertyLabeler(PersonProperty.AGE,
    +            (value) -> AgeGroup.getAgeGroup((Integer) value));
    +
    +    Labeler vaccineCountLabeler = new FunctionalPersonPropertyLabeler(PersonProperty.VACCINATION_COUNT,
    +            (value) -> value);
    +
    +    Partition partition = Partition.builder()//
    +            .setFilter(filter)//
    +            .addLabeler(ageLabeler)//
    +            .addLabeler(vaccineCountLabeler)//
    +            .build();
    +
    +    partitionsDataManager.addPartition(partition, currentlyEligibleKey);
    +
    +    filter = ageFilter.and(diseaseFilter).and(vaccineFilter);
    +    partition = Partition.builder()//
    +            .setFilter(filter)//
    +            .build();
    +
    +    partitionsDataManager.addPartition(partition, potentiallyEligibleKey);
    +}
    +
    +
    +

    The first step in this process is to create the filter that will pass only eligible people. Filters are provided as extensions of the Filter.java class in the partitions plugin. The plugin contains base filter implementations for the logical operators of AND, OR, NOT, TRUE, and FALSE. Nearly all plugins provide more refined filters via the plugin’s support package. In the example code, we create four filters that select:

    +
      +
    • Adults
    • +
    • People who are susceptible
    • +
    • People who have received fewer that 3 vaccinations
    • +
    • People who have not been recently vaccinated
    • +
    +

    All these filters are based on person properties, so we use the PersonPropertyFilter class provided by the person properties plugin. We combine the filters using the AND operator native to all filters.

    +

    We need to select people not only on their membership in the partition, but also on their personal properties, preferring older people and those who have had fewer vaccinations. Thus we will be using weighted selection and will use labelers assign weight to the cells in the partition. The nine cells are formed from the combinations of :

    +
      +
    • ADULT_18_44, ADULT_45_64, SENIOR

    • +
    • VACCINATION_COUNT = 0, 1, 2

    • +
    +

    The two labelers will use the FunctionalPersonPropertyLabeler class to specify the transformation of values into labels. Note that the age labeler is converting an integer age value into an AgeGroup while the vaccination count labeler is simply returning the vaccination count. The resulting partition is formed from the filter and the two labelers and is added to the partitions data manager under the ‘currentlyEligibleKey’ key value. This partition will be used to select a new person to vaccinate each time a vaccination comes due.

    +

    The next partition uses a reduced filter that allows for people who have been recently vaccinated but are not fully vaccinated. It does not require any labelers since we will use this partition only to determine if vaccinations should continue to be scheduled.

    +

    The vaccination process, Code Block 13.19, greatly simplifies the previous designs. The decision to continue vaccination is based on the potentially eligible population containing at least one person. Selection of a person from the currently eligible population uses a PartitionSampler, which specifies how the partition is to perform the sample. In this case we are only providing the weighting function that assigns a weighting value to each of the nine categories. Other capabilities of the PartitionSampler include:

    +
      +
    • Excluding a particular person – useful for contact management
    • +
    • Limiting sampling to a constrained portion of the cells that compose the partition
    • +
    • Selecting a specific random number generator for the sampling process
    • +
    +

    The resulting code is easier to refactor, less error prone and executes much faster with far less memory than the previous implementations. Cells in the partition are dynamically allocated to multiple implementations that can approach 1.3 bits per person in the population while maintaining O(ln(n) ) performance for sampling.

    +
    +
    +

    Code Block 13.19: The partition-vaccinator schedules and executes vaccinations using partitions.

    +
    private void planNextVaccination() {
    +    if (partitionsDataManager.getPersonCount(potentiallyEligibleKey) == 0) {
    +        return;
    +    }
    +    actorContext.addPlan(this::vaccinatePerson, vaccinatorDelay + actorContext.getTime());
    +}
    +
    +private void vaccinatePerson(ActorContext actorContext) {
    +
    +    PartitionSampler partitionSampler = PartitionSampler.builder()//
    +            .setLabelSetWeightingFunction(this::getWeight)//
    +            .build();
    +
    +    Optional<PersonId> optionalPersonId = partitionsDataManager.samplePartition(currentlyEligibleKey,
    +            partitionSampler);
    +    if (optionalPersonId.isPresent()) {
    +        PersonId personId = optionalPersonId.get();
    +        int vaccinationCount = personPropertiesDataManager.getPersonPropertyValue(personId,
    +                PersonProperty.VACCINATION_COUNT);
    +        vaccinationCount++;
    +        personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.VACCINATION_COUNT,
    +                vaccinationCount);
    +        if (vaccinationCount < 3) {
    +            personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.WAITING_FOR_NEXT_DOSE,
    +                    true);
    +            planWaitTermination(personId);
    +        }
    +
    +    }
    +    planNextVaccination();
    +}
    +
    +
    +
    +
    +
    +

    13.9 Inspecting the output

    +
    +

    13.9.1 Disease State Report

    +

    The disease state report, Figure 13.1, records the number of people having various vaccine counts, disease state and age grouping at the end of the simulation. The results show that the vaccination rate is not sufficient to prevent the majority of infections, but does reflect vaccination eligibility and prioritization rules. The results here are for the partition vaccinator, but the results for the other vaccinators are similar. Since each implementation has subtle ordering differences when choosing people to vaccinate, the results vary by amounts that would correspond to changes in the stochastic seed value.

    +
    +
    +
    +

    Figure 13.1: The experiment's single scenario showing expected vaccinations for different age groups.

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +scenario + +age_group + +disease_state + +vaccinations + +people +
    +0 + +CHILD + +RECOVERED + +0 + +1599 +
    +0 + +SENIOR + +RECOVERED + +0 + +377 +
    +0 + +ADULT_18_44 + +RECOVERED + +0 + +1745 +
    +0 + +ADULT_45_64 + +RECOVERED + +0 + +1018 +
    +0 + +ADULT_18_44 + +RECOVERED + +1 + +591 +
    +0 + +ADULT_18_44 + +SUSCEPTIBLE + +3 + +1253 +
    +0 + +SENIOR + +SUSCEPTIBLE + +3 + +742 +
    +0 + +ADULT_45_64 + +SUSCEPTIBLE + +3 + +1026 +
    +0 + +CHILD + +SUSCEPTIBLE + +0 + +553 +
    +0 + +SENIOR + +RECOVERED + +1 + +484 +
    +0 + +ADULT_45_64 + +RECOVERED + +1 + +568 +
    +0 + +ADULT_45_64 + +RECOVERED + +2 + +19 +
    +0 + +ADULT_18_44 + +RECOVERED + +2 + +10 +
    +0 + +SENIOR + +RECOVERED + +2 + +15 +
    +
    +
    +
    +
    + + +
    +
    + +
    + + +
    + + + + \ No newline at end of file diff --git a/doc/index.html b/doc/index.html new file mode 100644 index 000000000..192ee8cf9 --- /dev/null +++ b/doc/index.html @@ -0,0 +1,395 @@ + + + + + + + + + + + +GCM-Docs + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    + +
    + + + + + +
    + +
    +
    +

    GCM-Docs

    +
    + + + +
    + +
    +
    Author
    +
    +

    HHS ASPR Modeling Group

    +
    +
    + +
    +
    Published
    +
    +

    September 8, 2023

    +
    +
    + + +
    + + +
    + +
    +

    The General Computation Model (GCM)

    +

    The General Computation Model (GCM) is a Java based simulation framework for building disease progression models. Users of GCM should have a general familiarity with Java and object oriented programming and would benefit from some exposure to event based modeling.

    +

    Changes to the documentation will be maintained in the table below:

    +
    +

    Change History

    + +++++ + + + + + + + + + + + + + + +
    IDDateDescription
    12023-09-08Initial GCM Documentation made available in Quarto
    + + +
    +
    + +
    + + +
    + + + + \ No newline at end of file diff --git a/doc/inputimages/dag_simple_diagram.svg b/doc/inputimages/dag_simple_diagram.svg new file mode 100644 index 000000000..6110e831f --- /dev/null +++ b/doc/inputimages/dag_simple_diagram.svg @@ -0,0 +1 @@ +familyvaccinepeople \ No newline at end of file diff --git a/doc/inputimages/experiments_diagram_dimension_levels.svg b/doc/inputimages/experiments_diagram_dimension_levels.svg new file mode 100644 index 000000000..cf169bf4b --- /dev/null +++ b/doc/inputimages/experiments_diagram_dimension_levels.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/doc/inputimages/experiments_diagram_gathering_builders.svg b/doc/inputimages/experiments_diagram_gathering_builders.svg new file mode 100644 index 000000000..c1a3cec90 --- /dev/null +++ b/doc/inputimages/experiments_diagram_gathering_builders.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/doc/inputimages/experiments_diagram_level_functions.svg b/doc/inputimages/experiments_diagram_level_functions.svg new file mode 100644 index 000000000..7428bb8cc --- /dev/null +++ b/doc/inputimages/experiments_diagram_level_functions.svg @@ -0,0 +1 @@ +)F( \ No newline at end of file diff --git a/doc/inputimages/experiments_diagram_plugins_and_data.svg b/doc/inputimages/experiments_diagram_plugins_and_data.svg new file mode 100644 index 000000000..51874d3fe --- /dev/null +++ b/doc/inputimages/experiments_diagram_plugins_and_data.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/doc/inputimages/experiments_diagram_prefilled_data.svg b/doc/inputimages/experiments_diagram_prefilled_data.svg new file mode 100644 index 000000000..54918d7fa --- /dev/null +++ b/doc/inputimages/experiments_diagram_prefilled_data.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/doc/inputimages/experiments_diagram_scenario_plugins.svg b/doc/inputimages/experiments_diagram_scenario_plugins.svg new file mode 100644 index 000000000..5bb5b398e --- /dev/null +++ b/doc/inputimages/experiments_diagram_scenario_plugins.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/doc/inputimages/experiments_diagram_scenarios_and_levels.svg b/doc/inputimages/experiments_diagram_scenarios_and_levels.svg new file mode 100644 index 000000000..2942787c8 --- /dev/null +++ b/doc/inputimages/experiments_diagram_scenarios_and_levels.svg @@ -0,0 +1 @@ +)F()F()F( \ No newline at end of file diff --git a/doc/inputimages/materials_plugin_batches_and_properties.svg b/doc/inputimages/materials_plugin_batches_and_properties.svg new file mode 100644 index 000000000..f2c9e5a9e --- /dev/null +++ b/doc/inputimages/materials_plugin_batches_and_properties.svg @@ -0,0 +1 @@ +PROPABPROPXYZPROPABPROPXYZ \ No newline at end of file diff --git a/doc/inputimages/materials_plugin_materials_producer.svg b/doc/inputimages/materials_plugin_materials_producer.svg new file mode 100644 index 000000000..3777de65e --- /dev/null +++ b/doc/inputimages/materials_plugin_materials_producer.svg @@ -0,0 +1 @@ +MATERIALS PRODUCERINVENTORYSTAGESOFFERED STAGESRESOURCES123564REGIONSPRODUCERS \ No newline at end of file diff --git a/doc/inputimages/resource_plugins_flow_diagram.svg b/doc/inputimages/resource_plugins_flow_diagram.svg new file mode 100644 index 000000000..1e84363a9 --- /dev/null +++ b/doc/inputimages/resource_plugins_flow_diagram.svg @@ -0,0 +1 @@ +RegionsPeople154362 \ No newline at end of file diff --git a/doc/search.json b/doc/search.json new file mode 100644 index 000000000..8fee4710f --- /dev/null +++ b/doc/search.json @@ -0,0 +1,632 @@ +[ + { + "objectID": "index.html", + "href": "index.html", + "title": "GCM-Docs", + "section": "", + "text": "The General Computation Model (GCM)\nThe General Computation Model (GCM) is a Java based simulation framework for building disease progression models. Users of GCM should have a general familiarity with Java and object oriented programming and would benefit from some exposure to event based modeling.\nChanges to the documentation will be maintained in the table below:\n\nChange History\n\n\n\n\n\n\n\n\nID\nDate\nDescription\n\n\n\n\n1\n2023-09-08\nInitial GCM Documentation made available in Quarto" + }, + { + "objectID": "ch01-Introduction.html#who-is-this-for", + "href": "ch01-Introduction.html#who-is-this-for", + "title": "1  Introduction", + "section": "1.1 Who is this for?", + "text": "1.1 Who is this for?\nThe General Computation Model (GCM) is a Java based simulation framework for building disease progression models. Users of GCM should have a general familiarity with Java and object oriented programming and would benefit from some exposure to event based modeling." + }, + { + "objectID": "ch01-Introduction.html#high-level-overview", + "href": "ch01-Introduction.html#high-level-overview", + "title": "1  Introduction", + "section": "1.2 High level overview", + "text": "1.2 High level overview\n\n1.2.1 Simulation\nGCM is an event based simulation composed of data managers, actors, reports and an event engine. The data managers contain the state of the simulation and generate events when that state changes. The actors contain the business logic of your model and act on the data managers. The engine transports events generated by the data managers to any data managers and actors that subscribe to those events and manages the flow of time.\n\n\n1.2.2 Plugins\nData managers, actors and reports are organized into plugins. A GCM model is thus composed of the core simulation and a suite of plugins. The plugin architecture provides for the scalable reuse of concepts and capabilities between models. GCM contains a set of existing plugins that define many concepts useful to a broad range of models such as the management of people, their properties, social group structures and the like. The modeler is free to compose a model from their choice of plugins.\n\n\n1.2.3 Experiment\nGCM also provides a multi-threaded experiment management system. Each plugin contains zero to many data objects that define the initial state of its actors and data managers. Each such data object may be altered freely. The complete set of all combinations (scenarios) of the variant plugin data objects form an experiment and a separate simulation instance is executed for each combination." + }, + { + "objectID": "ch02-GettingStarted.html#hello-world-lesson-lesson-1", + "href": "ch02-GettingStarted.html#hello-world-lesson-lesson-1", + "title": "2  Getting Started", + "section": "2.1 Hello World Lesson (Lesson 1)", + "text": "2.1 Hello World Lesson (Lesson 1)\nOur first lesson is a very reduced “Hello World” example where we will execute the simulation with one line of code.\n\n\nCode Block 2.1: Building and executing an empty simulation.\npackage gov.hhs.aspr.ms.gcm.lessons;\n\nimport gov.hhs.aspr.ms.gcm.nucleus.Simulation;\n\npublic final class Example_1 {\n\n public static void main(String[] args) {\n Simulation.builder().build().execute();\n }\n\n\nWith this one line we have created and executed a simulation.  Since the simulation had no actors or data managers there was nothing to do and so it terminated immediately. Let’s analyze the line in a more drawn out form:\n\n\nCode Block 2.2: Building and executing an empty simulation broken out into discrete commands.\nSimulation.Builder builder = Simulation.builder();\nSimulation simulation = builder.build();\nsimulation.execute();\n\n\nThe simulation does not have a constructor.  Instead it uses a static builder class that creates the simulation from various arguments.  The builder is immediately capable of building a simulation instance so we will skip giving it any more information.  The simulation is only capable of executing, so we execute it." + }, + { + "objectID": "ch02-GettingStarted.html#plugins-lesson-lesson-2", + "href": "ch02-GettingStarted.html#plugins-lesson-lesson-2", + "title": "2  Getting Started", + "section": "2.2 Plugins Lesson (Lesson 2)", + "text": "2.2 Plugins Lesson (Lesson 2)\nModels are made of plugins. In this lesson(Example2) we will add a single plugin to the simulation and execute it.\n\n\nCode Block 2.3: A simple plugin added to the simulation. Plugins act as modules for all components contributed to the simulation.\npublic final class Example_2 {\n\n private Example_2() {\n }\n\n public static void main(String[] args) {\n\n PluginId pluginId = new SimplePluginId(\"example plugin\");\n\n Plugin plugin = Plugin.builder()//\n .setPluginId(pluginId)//\n .build();\n\n Simulation.builder()//\n .addPlugin(plugin)//\n .build()//\n .execute();\n }\n\n}\n\n\nThe first thing we will need to do to build a plugin is to identify it. The PluginId is a marker interface – it has no methods and serves to help differentiate between plugin id values and other identifiers. The SimplePluginId is a convenience implementor of PluginId and will wrap any object as an identifier. In this case we use the string “example plugin”, but you are free to implement them however best fits your needs.\nNext we build the plugin. The Plugin class implements all plugins and you can provide several arguments to its builder to specify the contents and behavior of your plugin. A plugin is composed of four items:\n\nAn id\nDependencies on other plugins\nData objects used to initialize data managers, actors and reports\nAn initializer to load the data into the simulation\n\nFor now, we will only need to add the plugin id and build the plugin.\nFinally, we build the simulation by adding the plugin and then executing as usual. The result is the same as the previous lesson: nothing happens. However, internally, the simulation did add the plugin and found it had no information other than its id." + }, + { + "objectID": "ch02-GettingStarted.html#actors-lesson-lesson-3", + "href": "ch02-GettingStarted.html#actors-lesson-lesson-3", + "title": "2  Getting Started", + "section": "2.3 Actors Lesson (Lesson 3)", + "text": "2.3 Actors Lesson (Lesson 3)\n\nContexts\nIn all that follows(Example3), we will encounter various context objects. Contexts are interfaces into the simulation that are tailored to the thing using the context. For example, an ActorContext provides everything that an actor will need to interact with the simulation. Similarly, a DataManager context provides the capabilities needed by data managers.\nThe first context we encounter is the PluginContext. It provides the plugin with the following abilities:\n\nAdd an actor to the simulation\nAdd a data manager to the simulation\nAdd a report to the simulation\nGet plugin data\n\nThe PluginContext is passed to the plugin’s initializer and is used to add all data managers, all initial data and any actors or reports that need to exist at the beginning of the simulation run.\nThe next context will be the ActorContext. It provides actors with a wide array of capabilities that we demonstrate later. For now, the important takeaway is that being granted a context implicitly identifies the recipient as having a particular role in the simulation.\n\n\nCode Block 2.4: An initializer uses a plugin context to execute the initialization logic at the beginning of each simulation.\nPluginId pluginId = new SimplePluginId(\"example plugin\");\n\nPlugin plugin = Plugin.builder()//\n .setPluginId(pluginId)//\n .setInitializer(Example_3::pluginInit)//\n .build();\n\nSimulation.builder()//\n .addPlugin(plugin)//\n .build()//\n .execute();\n\n\nWe are setting the plugin’s initializer. The initializer is a method that consumes a PluginContext and returns void. For this example, we use a static local method for our initializer:\n\n\nCode Block 2.5: A single actor is being added to the simulation at initialization.\npublic static void pluginInit(PluginContext pluginContext) {\n System.out.println(\"plugin being initialized -- we will add one actor\");\n pluginContext.addActor(Example_3::actorInit);\n}\n\n\nWhen the simulation starts up its execution, one of the first things it will do is to execute each plugin’s initializer to give the plugin an opportunity to add actors, reports and data managers to the simulation before time and events begin to flow. Adding an actor is done with another consumer, but this time it is a consumer of ActorContext.\n\n\nCode Block 2.6: The actor prints out some identifying information when it initializes.\npublic static void actorInit(ActorContext actorContext) {\n System.out.println(\"actor being initialized\");\n System.out.println(\"my id = \" + actorContext.getActorId());\n System.out.println(\"time = \" + actorContext.getTime());\n}\n\n\nAfter the plugins are initialized, the actors and data managers are next. For this example, the actor is initialized and it prints a few statements and ceases activity. Here is the resulting console output:\n\n\n\nFigure 2.1: Output from the single actor as it initializes.\n\n\n\n\n\nplugin being initialized – we will add one actor actor being initialized my id = ActorId [id=0] time = 0.0\n\n\n\n\n\n\n\n\nWe can replace the local method references above with lamdas to be more succinct.\n\n\nCode Block 2.7: A single actor writes output to the console during its initialization.\nPluginId pluginId = new SimplePluginId(\"example plugin\");\n\nPlugin plugin = Plugin.builder()//\n .setPluginId(pluginId)//\n .setInitializer(pluginContext -> {\n System.out.println(\"plugin being initialized -- we will add one actor\");\n pluginContext.addActor(actorContext -> {\n System.out.println(\"actor being initialized\");\n System.out.println(\"my id = \" + actorContext.getActorId());\n System.out.println(\"time = \" + actorContext.getTime());\n });\n })//\n .build();\n\nSimulation.builder()//\n .addPlugin(plugin)//\n .build()//\n .execute();" + }, + { + "objectID": "ch02-GettingStarted.html#data-managers-lesson-lesson-4", + "href": "ch02-GettingStarted.html#data-managers-lesson-lesson-4", + "title": "2  Getting Started", + "section": "2.4 Data Managers Lesson (Lesson 4)", + "text": "2.4 Data Managers Lesson (Lesson 4)\nWe extend the previous lesson by slightly altering the actor and adding a data manager. But first let’s list some of the attributes of data managers, actors and reports to better understand the roles they play in the simulation. Reports are presented in detail in a later chapter.\n\nData Managers\n\nExist for the full duration of the simulation\nContain and maintain the entire state of the world.\nAre highly stateful\nProduce events in reaction to state changes\nInteract with other data managers via events\nDo not have a set of objectives. They are not trying to achieve some particular state of the world\nAre narrowly focused on some particular aspect of the world, but are concerned with all instances of that aspect\nAre added as instances and are limited to a single instance per class type\n\nActors\n\nMay be added and removed over time\nAre not considered to be part of the world\nAre generally stateless\nReact to but do not produce events\nMay access any data manager\nHave objectives. They contain the business logic of the model and are trying to achieve some particular state of the world\nAre concerned with many aspects of the world, but often focused on a particular subset of world\nAre added as consumers of ActorContext and may be composed of any such consumers\n\n\n\n\nReports\n\nExist for the full duration of the simulation\nAre not considered to be part of the world\nReact to but do not produce events\nMay access any data manager\nDo not have a set of objectives\nCannot mutate data and have no effect on the outcome of the simulation\n\n\n\n\nCode Block 2.8: A data manager is added to the simulation.\npublic static void main(String[] args) {\n\n PluginId pluginId = new SimplePluginId(\"example plugin\");\n\n Plugin plugin = Plugin.builder()//\n .setPluginId(pluginId)//\n .setInitializer(pluginContext -> {\n pluginContext.addActor(new ExampleActor()::init);\n pluginContext.addDataManager(new ExampleDataManager());\n })//\n .build();\n\n Simulation.builder()//\n .addPlugin(plugin)//\n .build()//\n .execute();\n}\n\n\nWe add an instance of ExampleDataManager to the simulation. Unlike the actor, where we pass a consumer of context, we need to provide an actual instance of a data manager. Note that the ExampleDataManager extends the base class DataManager. The base class provides only the init() method to override and you must include the super.init(dataManagerContext) call as its first line. This is done to ensure that each data manager is initialized exactly once by the simulation.\nThe ExampleDataManager has two (completely arbitrary) data fields alpha and beta and provides both getters and setters for each.\n\n\nCode Block 2.9: The example data manager manages the state of two properties and prints to the console when changes are made.\npublic final class ExampleDataManager extends DataManager {\n\n private int alpha = 7;\n\n private double beta = 1.2345;\n\n private DataManagerContext dataManagerContext;\n\n @Override\n public void init(DataManagerContext dataManagerContext) {\n super.init(dataManagerContext);\n this.dataManagerContext = dataManagerContext;\n System.out.println(\"ExampleDataManager is initialized\");\n }\n\n public int getAlpha() {\n return alpha;\n }\n\n public void setAlpha(int alpha) {\n this.alpha = alpha;\n System.out.println(\"ExampleDataManager sets alpha = \" + alpha + \" at time = \" + dataManagerContext.getTime());\n }\n\n public double getBeta() {\n return beta;\n }\n\n public void setBeta(double beta) {\n this.beta = beta;\n System.out.println(\"ExampleDataManager sets beta = \" + beta + \" at time = \" + dataManagerContext.getTime());\n }\n\n}\n\n\nThe actor is now specified via the ExampleActor class, Code Block 2.10. Most actors contain enough code that we usually put that code into a separate class rather than a lambda statement as we did in the previous lesson. Note that the init() method has the correct method signature of being a consumer of ActorContext.\n\nPlans\nIn GCM, an actor can do three things:\n\nObserve: Observation can be done directly by gaining access to a data manager and then getting a value from that data manager. Observation can be done indirectly by subscribing to events. We will cover that option later.\nAct: A mutation to some data manager’s managed data.\nPlan: At some time in the future, the actor will take some particular action\n\nActions in GCM are always executed in the current moment in the simulation. Unlike many future event simulations where events are queued for future execution, GCM allows an actor to plan for an action or observation in the future. The plan is a consumer of ActorContext and can be a static method, member method or a lambda. The plan is registered with the simulation and is executed only when time has moved forward to the plan’s scheduled time. There is no requirement that the plan do anything at all. This allows the flexibility to re-evaluate the circumstances of the planned action and choose to take appropriate action at that time. Plans are queued in GCM by their associated planning times and it is this queue that dictates the flow of time. For example, suppose the simulation finds the first plan is scheduled for time= 2.4 days. The current time = 0 days and the simulation progresses time to 2.4 days and then invokes the plan. Plans are always privately managed by the actor that owns the plan and no other actor or data manager has any insight into those plans.\nIn this example, the actor is initialized at time= 0 and generates 10 plans to increment the value of the alpha in the ExampleManager. Each time the ExampleManager changes the value of alpha, it outputs to the console a description of the change.\n\n\nCode Block 2.10: The example actor initializes by making plans to update the alpha property on a daily basis.\npublic final class ExampleActor {\n\n public void init(ActorContext actorContext) {\n System.out.println(\"Example Actor is initialized and will plan to set Alpha\");\n\n ExampleDataManager exampleDataManager = actorContext.getDataManager(ExampleDataManager.class);\n\n for (double planTime = 0; planTime < 10; planTime++) {\n actorContext.addPlan((context) -> {\n int alpha = exampleDataManager.getAlpha();\n alpha++;\n exampleDataManager.setAlpha(alpha);\n }, planTime);\n }\n }\n}\n\n\nThe output from the simulation is:\n\n\n\nFigure 2.2: Output from the example actor and example data manager showing the alpha property initialization and subsequent evolution.\n\n\n\n\n\nExampleDataManager is initialized Example Actor is initialized and will plan to set Alpha ExampleDataManager sets alpha = 8 at time = 0.0 ExampleDataManager sets alpha = 9 at time = 1.0 ExampleDataManager sets alpha = 10 at time = 2.0 ExampleDataManager sets alpha = 11 at time = 3.0 ExampleDataManager sets alpha = 12 at time = 4.0 ExampleDataManager sets alpha = 13 at time = 5.0 ExampleDataManager sets alpha = 14 at time = 6.0 ExampleDataManager sets alpha = 15 at time = 7.0 ExampleDataManager sets alpha = 16 at time = 8.0 ExampleDataManager sets alpha = 17 at time = 9.0" + }, + { + "objectID": "ch02-GettingStarted.html#events-lesson-lesson-5", + "href": "ch02-GettingStarted.html#events-lesson-lesson-5", + "title": "2  Getting Started", + "section": "2.5 Events Lesson (Lesson 5)", + "text": "2.5 Events Lesson (Lesson 5)\nAn event in GCM is a notification of a data change to the state of a data manager. In this example we will introduce two events corresponding to the two changes to the ExampleDataManager. Both events document the previous value and current value (at the time when the event was generated) and are immutable data classes.\n\n\nCode Block 2.11: An event to notify that the alpha property has been updated.\npublic final class AlphaChangeEvent implements Event {\n\n private final int previousAlpha;\n\n private final int currentAlpha;\n\n public AlphaChangeEvent(int previousAlpha, int currentAlpha) {\n super();\n this.previousAlpha = previousAlpha;\n this.currentAlpha = currentAlpha;\n }\n\n public int getPreviousAlpha() {\n return previousAlpha;\n }\n\n public int getCurrentAlpha() {\n return currentAlpha;\n }\n\n @Override\n public String toString() {\n StringBuilder builder = new StringBuilder();\n builder.append(\"AlphaChangeEvent [previousAlpha=\");\n builder.append(previousAlpha);\n builder.append(\", currentAlpha=\");\n builder.append(currentAlpha);\n builder.append(\"]\");\n return builder.toString();\n }\n\n}\n\n\n\n\nCode Block 2.12: An event to notify that the beta property has been updated.\npublic final class BetaChangeEvent implements Event {\n\n private final double previousBeta;\n\n private final double currentBeta;\n\n public BetaChangeEvent(double previousBeta, double currentBeta) {\n super();\n this.previousBeta = previousBeta;\n this.currentBeta = currentBeta;\n }\n\n public double getPreviousBeta() {\n return previousBeta;\n }\n\n public double getCurrentBeta() {\n return currentBeta;\n }\n\n @Override\n public String toString() {\n StringBuilder builder = new StringBuilder();\n builder.append(\"BetaChangeEvent [previousBeta=\");\n builder.append(previousBeta);\n builder.append(\", currentBeta=\");\n builder.append(currentBeta);\n builder.append(\"]\");\n return builder.toString();\n }\n\n}\n\n\nEach is generated by the ExampleDataManager, Code Block 2.13, when the alpha or beta values are mutated by releasing the events through the DataManagerContext to the simulation:\n\n\nCode Block 2.13: The alpha and beta updates are managed via private mutation events.\nprivate static record AlphaChangeMutationEvent(int alpha) implements Event {\n}\n\npublic void setAlpha(int alpha) {\n dataManagerContext.releaseMutationEvent(new AlphaChangeMutationEvent(alpha));\n}\n\nprivate void handleAlphaChangeMutationEvent(DataManagerContext dataManagerContext,\n AlphaChangeMutationEvent alphaChangeMutationEvent) {\n int alpha = alphaChangeMutationEvent.alpha();\n int previousValue = this.alpha;\n this.alpha = alpha;\n dataManagerContext.releaseObservationEvent(new AlphaChangeEvent(previousValue, this.alpha));\n}\n\nprivate static record BetaChangeMutationEvent(double beta) implements Event {\n}\n\npublic void setBeta(double beta) {\n dataManagerContext.releaseMutationEvent(new BetaChangeMutationEvent(beta));\n}\n\nprivate void handleBetaChangeMutationEvent(DataManagerContext dataManagerContext,\n BetaChangeMutationEvent betaChangeMutationEvent) {\n double beta = betaChangeMutationEvent.beta();\n double previousValue = this.beta;\n this.beta = beta;\n dataManagerContext.releaseObservationEvent(new BetaChangeEvent(previousValue, this.beta));\n}\n\n\nThere are three actors in this example:\n\nActor1 makes changes to both the alpha and beta values at 1 and 3.5 day intervals respectively.\nActor2 subscribes to AlphaChangeEvent events and reports to console what it receives.\nActor3 does the same for BetaChangeEvent events\n\n\n\nCode Block 2.14: Actor 1 schedules updates to both the alpha and beta properties.\npublic final class Actor1 {\n\n public void init(ActorContext actorContext) {\n ExampleDataManager exampleDataManager = actorContext.getDataManager(ExampleDataManager.class);\n\n for (double planTime = 1; planTime <= 10; planTime++) {\n actorContext.addPlan((context) -> {\n int alpha = exampleDataManager.getAlpha();\n alpha++;\n exampleDataManager.setAlpha(alpha);\n }, planTime);\n }\n\n for (int i = 1; i <= 5; i++) {\n double planTime = i * 3.5;\n actorContext.addPlan((context) -> {\n double beta = exampleDataManager.getBeta();\n beta *= 2;\n exampleDataManager.setBeta(beta);\n }, planTime);\n }\n\n }\n}\n\n\n\n\nCode Block 2.15: Actor 2 reacts to changes in the alpha property.\npublic final class Actor2 {\n public void init(ActorContext actorContext) {\n\n EventFilter eventFilter = EventFilter.builder(AlphaChangeEvent.class).build();\n\n actorContext.subscribe(eventFilter, (context, event) -> {\n System.out.println(\"Actor2 observes event \" + event + \" at time = \" + context.getTime());\n });\n }\n}\n\n\n\n\nCode Block 2.16: Actor 3 reacts to changes in the beta property.\npublic final class Actor3 {\n public void init(ActorContext actorContext) {\n EventFilter eventFilter = EventFilter.builder(BetaChangeEvent.class).build();\n actorContext.subscribe(eventFilter, (context, event) -> {\n System.out.println(\"Actor3 observes event \" + event + \" at time = \" + context.getTime());\n });\n }\n}\n\n\nThe resulting console output shows Actor2 and Actor3 observing the expected events at the expected times:\n\n\n\nFigure 2.3: Output from Actors 2 and 3 as they observe changes to the alpha and beta properties.\n\n\n\n\n\nActor2 observes event AlphaChangeEvent [previousAlpha=7, currentAlpha=8] at time = 1.0 Actor2 observes event AlphaChangeEvent [previousAlpha=8, currentAlpha=9] at time = 2.0 Actor2 observes event AlphaChangeEvent [previousAlpha=9, currentAlpha=10] at time = 3.0 Actor3 observes event BetaChangeEvent [previousBeta=1.2345, currentBeta=2.469] at time = 3.5 Actor2 observes event AlphaChangeEvent [previousAlpha=10, currentAlpha=11] at time = 4.0 Actor2 observes event AlphaChangeEvent [previousAlpha=11, currentAlpha=12] at time = 5.0 Actor2 observes event AlphaChangeEvent [previousAlpha=12, currentAlpha=13] at time = 6.0 Actor2 observes event AlphaChangeEvent [previousAlpha=13, currentAlpha=14] at time = 7.0 Actor3 observes event BetaChangeEvent [previousBeta=2.469, currentBeta=4.938] at time = 7.0 Actor2 observes event AlphaChangeEvent [previousAlpha=14, currentAlpha=15] at time = 8.0 Actor2 observes event AlphaChangeEvent [previousAlpha=15, currentAlpha=16] at time = 9.0 Actor2 observes event AlphaChangeEvent [previousAlpha=16, currentAlpha=17] at time = 10.0 Actor3 observes event BetaChangeEvent [previousBeta=4.938, currentBeta=9.876] at time = 10.5 Actor3 observes event BetaChangeEvent [previousBeta=9.876, currentBeta=19.752] at time = 14.0 Actor3 observes event BetaChangeEvent [previousBeta=19.752, currentBeta=39.504] at time = 17.5\n\n\n\n\n\n\n\n\n\nEvent Filters\nSubscription to events for data managers and actors differ a bit. Data managers subscribe directly to the event type since they are generally interested in all events of some given type. Actors are often more selective and would like a predicate (in Java, the predicate is a function that returns a Boolean) to return true before they handle an event. For example, an actor wants to subscribe for person property change events, but is only interested in those events indicate a change to a particular person property. Since there will likely be dozens of person properties, the actor would get stimulated many times over, only to ignore the event most of the time. Unfortunately, a simple predicate added during the subscription process will not suffice since that predicate would have to be executed for each event and we will have gained little efficiency. Instead, GCM uses the EventFilter class that is essentially a predicate grouping mechanism that allows the subscription engine to group subscribers into a tree structure so that a single predicate execution might suffice to allow an event to be passed to multiple subscribers.\nThe event filter is logically composed of functions and target values as pairs. Each function takes in an event and releases a value. If that value is equal to the target value, then the event passes that function. An event passes the event filter if it passes all the functions that compose the filter. The construction of the builder for event filters requires the event class reference. The addition of function-value pairs requires that the functions take in only events of the given class reference, but may return any non-null object value. The simple examples given so far have only specified the event class and thus every event of that type will pass the event filter.\nThe functions that compose the event filter are often non-meaningfully comparable. For example, two functions that return the same values for every event may be separate instances of lambda code that are logically equal, but are not equal from the point of view of Java. To get around this, each function is associated with an id value and the id and function pair are called an IdentifiableFunction. Two such functions will be equal if and only if their ids are equal without regard to what their functions actually do. Thus is it very important that the mapping of id to actual logical function be stable and the usual best practice is to manage that mapping in a curated manner via the data manager that is associated with the plugin that defines the event. As we examine plugins that define events, we will encounter event filters that are managed by data managers and we will generally not generate event filters directly in the actor code." + }, + { + "objectID": "ch02-GettingStarted.html#plugin-dependencies-lesson-lesson-6", + "href": "ch02-GettingStarted.html#plugin-dependencies-lesson-lesson-6", + "title": "2  Getting Started", + "section": "2.6 Plugin Dependencies Lesson (Lesson 6)", + "text": "2.6 Plugin Dependencies Lesson (Lesson 6)\nSo far we have covered what actors and data managers do and that they are introduced into the simulation via plugins. Over the next lessons we take a closer look at the plugins. This lesson starts with creating a more realistic set of plugins arranged into separate java packages.\n\nPeople plugin\n\nDefines a person id\nAdds the PersonDataManager for tracking people\nAdds events for the the addition and removal of people\n\nFamily Plugin\n\nDefines a family id\nAdds the FamilyDataManager for grouping people into families\n\nVaccine Plugin\n\nAdds the VaccineDataManager for tracking which people have been vaccinated\n\nModel Plugin\n\nContains the ModelActor class to add people organized into family structures and vaccinate some of those people\n\n\nHere are the classes that implement this example:\n\nPeople Plugin:\nThe people plugin defines a PersonId as a simple, immutable wrapper to an int value. The PersonDataManager tracks people via PersonId values and allows for the addition and removal of people. PersonId values are generated in order and never reused. Events are generated when people are added or removed.\n\n\nCode Block 2.17: The PersonId class defines people and wraps an int value.\npublic final class PersonId implements Comparable {\n\n private final int id;\n\n public PersonId(int id) {\n this.id = id;\n }\n\n public int getValue() {\n return id;\n }\n\n @Override\n public int compareTo(PersonId personId) {\n return Integer.compare(id, personId.id);\n }\n\n @Override\n public int hashCode() {\n return id;\n }\n\n @Override\n public boolean equals(Object obj) {\n if (this == obj) {\n return true;\n }\n if (!(obj instanceof PersonId)) {\n return false;\n }\n PersonId other = (PersonId) obj;\n if (id != other.id) {\n return false;\n }\n return true;\n }\n\n @Override\n public String toString() {\n return Integer.toString(id);\n }\n}\n\n\n\n\nCode Block 2.18: The PersonDataManager manages people by adding people, removing people and releasing person removal events.\npublic final class PersonDataManager extends DataManager {\n\n private int masterPersonId;\n\n private Set people = new LinkedHashSet<>();\n\n private DataManagerContext dataManagerContext;\n\n @Override\n public void init(DataManagerContext dataManagerContext) {\n super.init(dataManagerContext);\n this.dataManagerContext = dataManagerContext;\n dataManagerContext.subscribe(PersonRemovalMutationEvent.class, this::handlePersonRemovalMutationEvent);\n }\n\n public PersonId addPerson() {\n PersonId personId = new PersonId(masterPersonId++);\n people.add(personId);\n return personId;\n }\n\n public boolean personExists(PersonId personId) {\n return people.contains(personId);\n }\n\n public Set getPeople() {\n return new LinkedHashSet<>(people);\n }\n\n private static record PersonRemovalMutationEvent(PersonId personId) implements Event {\n }\n\n public void removePerson(PersonId personId) {\n dataManagerContext.releaseMutationEvent(new PersonRemovalMutationEvent(personId));\n }\n\n private void handlePersonRemovalMutationEvent(DataManagerContext dataManagerContext,\n PersonRemovalMutationEvent personRemovalMutationEvent) {\n PersonId personId = personRemovalMutationEvent.personId();\n if (!personExists(personId)) {\n throw new RuntimeException(\"person \" + personId + \" does not exist\");\n }\n people.remove(personId);\n dataManagerContext.releaseObservationEvent(new PersonRemovalEvent(personId));\n }\n}\n\n\n\n\nCode Block 2.19: An event signifying that a person has been removed from the simulation.\npublic final class PersonRemovalEvent implements Event {\n\n private final PersonId personId;\n\n public PersonRemovalEvent(PersonId personId) {\n this.personId = personId;\n }\n\n public PersonId getPersonId() {\n return personId;\n }\n\n}\n\n\n\n\nFamily Plugin\nThe family plugin defines a FamilyId as a simple, immutable wrapper to an int value. The FamilyDataManager tracks family membership via two-way mappings of PersonId to FamilyId. In this example, families can only be added and people can only be added to families. However, people can be removed via the PeoplePlugin so the FamilyDataManager subscribes to PersonRemovalEvent(s) and thus removes the people from families.\n\n\nCode Block 2.20: The family id, like the person id, simply wraps an int.\npublic final class FamilyId implements Comparable {\n\n private final int id;\n\n public FamilyId(int id) {\n this.id = id;\n }\n\n public int getValue() {\n return id;\n }\n\n @Override\n public int compareTo(FamilyId familyId) {\n return Integer.compare(id, familyId.id);\n }\n\n @Override\n public int hashCode() {\n return id;\n }\n\n @Override\n public boolean equals(Object obj) {\n if (this == obj) {\n return true;\n }\n if (!(obj instanceof FamilyId)) {\n return false;\n }\n FamilyId other = (FamilyId) obj;\n if (id != other.id) {\n return false;\n }\n return true;\n }\n\n @Override\n public String toString() {\n return Integer.toString(id);\n }\n}\n\n\n\n\nCode Block 2.21: The family data manager manages families, their person members and various information methods.\npublic final class FamilyDataManager extends DataManager {\n\n private int masterFamilyId;\n private Map> familyMap = new LinkedHashMap<>();\n private Map personMap = new LinkedHashMap<>();\n private PersonDataManager personDataManager;\n\n @Override\n public void init(DataManagerContext dataManagerContext) {\n super.init(dataManagerContext);\n personDataManager = dataManagerContext.getDataManager(PersonDataManager.class);\n dataManagerContext.subscribe(PersonRemovalEvent.class, this::handlePersonRemovalEvent);\n }\n\n private void handlePersonRemovalEvent(DataManagerContext dataManagerContext,\n PersonRemovalEvent personRemovalEvent) {\n PersonId personId = personRemovalEvent.getPersonId();\n FamilyId familyId = personMap.remove(personId);\n if (familyId != null) {\n familyMap.get(familyId).remove(personId);\n }\n System.out.println(\n \"Family Data Manager is removing person \" + personId + \" at time = \" + dataManagerContext.getTime());\n }\n\n public FamilyId addFamily() {\n FamilyId familyId = new FamilyId(masterFamilyId++);\n familyMap.put(familyId, new LinkedHashSet<>());\n return familyId;\n }\n\n public boolean familyExists(FamilyId familyId) {\n return familyMap.keySet().contains(familyId);\n }\n\n public List getFamilyMembers(FamilyId familyId) {\n if (!familyExists(familyId)) {\n throw new RuntimeException(\"unknown family \" + familyId);\n }\n return new ArrayList<>(familyMap.get(familyId));\n }\n\n public Optional getFamilyId(PersonId personId) {\n if (!personDataManager.personExists(personId)) {\n throw new RuntimeException(\"unknown person \" + personId);\n }\n FamilyId familyId = personMap.get(personId);\n return Optional.ofNullable(familyId);\n }\n\n public void addFamilyMember(PersonId personId, FamilyId familyId) {\n if (!personDataManager.personExists(personId)) {\n throw new RuntimeException(\"unknown person \" + personId);\n }\n if (!familyExists(familyId)) {\n throw new RuntimeException(\"unknown family \" + familyId);\n }\n FamilyId currentFamilyId = personMap.get(personId);\n if (currentFamilyId != null) {\n throw new RuntimeException(\"person \" + personId + \" is already assigned to family \" + currentFamilyId);\n }\n familyMap.get(familyId).add(personId);\n personMap.put(personId, familyId);\n }\n\n}\n\n\n\n\nVaccine Plugin\nThe vaccine plugin contains only the VaccineDataManager which tracks by PersonId which people have been vaccinated. Like the FamilyDataManager, it too subscribes to PersonRemovalEvent(s) and adjusts its data accordingly.\n\n\nCode Block 2.22: The vaccination manager tracks the vaccination status of each person, reacting to the person removal as needed.\npublic final class VaccinationDataManager extends DataManager {\n\n private Set vaccinatedPeople = new LinkedHashSet<>();\n\n private PersonDataManager personDataManager;\n\n @Override\n public void init(DataManagerContext dataManagerContext) {\n super.init(dataManagerContext);\n dataManagerContext.subscribe(PersonRemovalEvent.class, this::handlePersonRemovalEvent);\n personDataManager = dataManagerContext.getDataManager(PersonDataManager.class);\n }\n\n private void handlePersonRemovalEvent(DataManagerContext dataManagerContext,\n PersonRemovalEvent personRemovalEvent) {\n PersonId personId = personRemovalEvent.getPersonId();\n vaccinatedPeople.remove(personId);\n System.out.println(\"Vaccination Data Manager is removing person \" + personId + \" at time = \"\n + dataManagerContext.getTime());\n }\n\n public Set getVaccinatedPeople() {\n return new LinkedHashSet<>(vaccinatedPeople);\n }\n\n public Set getUnvaccinatedPeople() {\n Set people = personDataManager.getPeople();\n people.removeAll(vaccinatedPeople);\n return people;\n }\n\n public boolean isPersonVaccinated(PersonId personId) {\n if (!personDataManager.personExists(personId)) {\n throw new RuntimeException(\"unknown person \" + personId);\n }\n return vaccinatedPeople.contains(personId);\n }\n\n public void vaccinatePerson(PersonId personId) {\n if (!personDataManager.personExists(personId)) {\n throw new RuntimeException(\"unknown person \" + personId);\n }\n vaccinatedPeople.add(personId);\n }\n\n}\n\n\n\n\nModel Plugin\nThe model plugin contains a single actor, the ModelActor, that serves to:\n\nAdd people to the simulation\nGroup them into families\nVaccinate some people\nDemonstrate that events cascade\n\n\n\nConnecting the Plugins\nBoth the family and vaccine plugins depend on the concept of a person as implemented by the PersonId class. They also need to respond when a person is removed from the simulation and do so by handling the corresponding PersonRemovalEvent generated by the person plugin. We build these dependencies via the Plugin.Builder class in the example code below.\n\n\nCode Block 2.23: The people, vaccine, family and model plugins are contributed to the simulation. On execution, the model plugin’s single actor schedules the vaccination of each person as well as a few random removals of people from the simulation.\npublic static void main(String[] args) {\n\n PluginId peoplePluginId = new SimplePluginId(\"people plugin\");\n Plugin peoplePlugin = Plugin.builder()//\n .setPluginId(peoplePluginId)//\n .setInitializer(pluginContext -> {\n pluginContext.addDataManager(new PersonDataManager());\n })//\n .build();\n\n PluginId vaccinePluginId = new SimplePluginId(\"vaccine plugin\");\n Plugin vaccinePlugin = Plugin.builder()//\n .setPluginId(vaccinePluginId)//\n .addPluginDependency(peoplePluginId)//\n .setInitializer(pluginContext -> {\n pluginContext.addDataManager(new VaccinationDataManager());\n })//\n .build();\n\n PluginId familyPluginId = new SimplePluginId(\"family plugin\");\n Plugin familyPlugin = Plugin.builder()//\n .setPluginId(familyPluginId)//\n .addPluginDependency(peoplePluginId)//\n .setInitializer(pluginContext -> {\n pluginContext.addDataManager(new FamilyDataManager());\n })//\n .build();\n\n PluginId modelPluginId = new SimplePluginId(\"model plugin\");\n Plugin modelPlugin = Plugin.builder()//\n .setPluginId(modelPluginId)//\n .setInitializer(pluginContext -> {\n pluginContext.addActor(new ModelActor()::init);\n\n })//\n .build();\n\n Simulation.builder()//\n .addPlugin(vaccinePlugin)//\n .addPlugin(familyPlugin)//\n .addPlugin(peoplePlugin)//\n .addPlugin(modelPlugin)//\n .build()//\n .execute();\n}\n\n\nNote the addition of the dependency on the people plugin via its id when adding both the vaccine and family plugins. The order of addition of the plugins to the simulation is relatively unimportant as is ordering in general in any of the builder patterns used in GCM.\nThe resulting output:\n\n\n\nFigure 2.4: The output shows the combined reporting from actors and data managers as they report vaccination progress.\n\n\n\n\n\nPerson 7 was vaccinated at time = 1.0 Person 2 was vaccinated at time = 2.0 Person 5 was vaccinated at time = 3.0 Vaccination Data Manager is removing person 2 at time = 3.0 Family Data Manager is removing person 2 at time = 3.0 Person 6 was vaccinated at time = 4.0 Vaccination Data Manager is removing person 1 at time = 4.0 Family Data Manager is removing person 1 at time = 4.0 Person 3 was vaccinated at time = 5.0 Vaccination Data Manager is removing person 8 at time = 5.0 Family Data Manager is removing person 8 at time = 5.0 Failed to vaccinate Person 1 at time = 6.0 Vaccination Data Manager is removing person 6 at time = 6.0 Family Data Manager is removing person 6 at time = 6.0 Person 0 was vaccinated at time = 7.0 Vaccination Data Manager is removing person 7 at time = 7.0 Family Data Manager is removing person 7 at time = 7.0 Person 9 was vaccinated at time = 8.0 Vaccination Data Manager is removing person 5 at time = 8.0 Family Data Manager is removing person 5 at time = 8.0 Failed to vaccinate Person 8 at time = 9.0 Vaccination Data Manager is removing person 3 at time = 9.0 Family Data Manager is removing person 3 at time = 9.0 Person 4 was vaccinated at time = 10.0 Vaccination Data Manager is removing person 4 at time = 10.0 Family Data Manager is removing person 4 at time = 10.0 Vaccination Data Manager is removing person 0 at time = 11.0 Family Data Manager is removing person 0 at time = 11.0 Vaccination Data Manager is removing person 9 at time = 12.0 Family Data Manager is removing person 9 at time = 12.0" + }, + { + "objectID": "ch02-GettingStarted.html#plugin-dependency-graph-lesson-lesson-7", + "href": "ch02-GettingStarted.html#plugin-dependency-graph-lesson-lesson-7", + "title": "2  Getting Started", + "section": "2.7 Plugin Dependency Graph Lesson (Lesson 7)", + "text": "2.7 Plugin Dependency Graph Lesson (Lesson 7)\nWe extend the previous lesson by adding an additional dependency of the vaccine plugin on the family plugin. This will allow the VaccineDataManager to answer queries about which members of a family have yet to be vaccinated.\nFrom the VaccineDataManager:\n\n\nCode Block 2.24: By adding a plugin dependencies on the people and family plugins, the vaccine data manager can now answer questions about the vaccine status of family members\npublic List getUnvaccinatedFamilyMembers(PersonId personId) {\n if (!personDataManager.personExists(personId)) {\n throw new RuntimeException(\"unknown person \" + personId);\n }\n List result = new ArrayList<>();\n Optional optional = familyDataManager.getFamilyId(personId);\n if (optional.isPresent()) {\n FamilyId familyId = optional.get();\n List familyMembers = familyDataManager.getFamilyMembers(familyId);\n for (PersonId familyMemeberId : familyMembers) {\n if (!isPersonVaccinated(familyMemeberId)) {\n result.add(personId);\n }\n }\n }\n return result;\n}\n\n\nThe plugins in this example form a dependency pattern:\n\n\n\n\nFigure 2.5: The three plugins form a simple, directed acyclic graph (DAG). GCM uses this DAG to ensure that all information provided by any data manager is fully updated whenever an event is being processed by a dependent of the data manager.\n\n\n\n\n\nAll plugin dependencies in GCM form similar directed, acyclic graphs (DAGs). There can be no loops in the dependency graph, but the graph does not have to be fully connected. The dependencies reflect the requirements of the data managers within a plugin to access data managers in other plugins. This pattern drives the order in which events are presented to data managers. This way, a data manager is guaranteed that any event that it is processing has already been fully processed by all the data managers it depends on.\nIn this lesson, the VaccineDataManager and the FamilyDataManager have both subscribed to the PersonRemovalEvent generated by the PersonDataManager. Since the VaccineDataManager also has a dependency on the FamilyDataManager, the VaccineDataManager should receive the event after the FamilyDataManager. Events cascade through the subscribed data managers in an order that is consistent with the plugin dependency DAG." + }, + { + "objectID": "ch02-GettingStarted.html#plugin-data-lesson-lesson-8", + "href": "ch02-GettingStarted.html#plugin-data-lesson-lesson-8", + "title": "2  Getting Started", + "section": "2.8 Plugin Data Lesson (Lesson 8)", + "text": "2.8 Plugin Data Lesson (Lesson 8)\nThe Example code in the last lesson was a bit verbose and can be improved. Identifying and generating the plugins can be included in the plugin packages by introducing classes for each id and classes for each plugin’s contents. In the disease package we add a unique plugin identifier with a final static id field:\n\n\nCode Block 2.25: The plugin id for the disease plugin is implemented as a static constant.\npublic final class DiseasePluginId implements PluginId {\n private DiseasePluginId() {\n }\n\n public final static PluginId PLUGIN_ID = new SimplePluginId(\"disease plugin id\");\n}\n\n\nWe also add a static class (DiseasePlugin) that implements the construction of the plugin from the required plugin data.\n\n\nCode Block 2.26: The DiseasePlugin class is a static class for creating the disease plugin.\npublic final class DiseasePlugin {\n\n private DiseasePlugin() {\n\n }\n\n public static Plugin getDiseasePlugin(DiseasePluginData diseasePluginData) {\n\n return Plugin.builder()//\n .addPluginData(diseasePluginData)//\n .setPluginId(DiseasePluginId.PLUGIN_ID)//\n .setInitializer((pluginContext) -> {\n DiseasePluginData pluginData = pluginContext.getPluginData(DiseasePluginData.class).get();\n pluginContext.addDataManager(new DiseaseDataManager(pluginData));\n })//\n .build();\n }\n\n}\n\n\nThe plugin is initialized with a DiseasePluginData object that contains the initial values for r0, asymptomatic days and symptomatic days. Most plugins will have a single plugin data object, but some may not need any and some may be designed with multiple such classes. All such classes must implement the PluginData interface:\n\n\nCode Block 2.27: The PluginData interface indicates that its implementors are immutable. Plugin data objects are shared between all simulation instances and thus must be thread safe. It introduces a single method used to copy plugin datas during the experiment process.\n@ThreadSafe\npublic interface PluginData {\n /**\n * Returns a PluginDataBuilder that can build the plugin data. The returned\n * builder should be initialized with this plugin data object's internal state\n * such that invocation of pluginData.getCloneBuilder().build() will generate a\n * copy of the current plugin.\n */\n public PluginDataBuilder getCloneBuilder();\n\n @Override\n public int hashCode();\n\n /**\n * Plugin datas are equal if they are implicitly equal. They contain the same\n * implicit information without regard to order.\n */\n @Override\n public boolean equals(Object obj);\n\n /**\n * A string representation of the plugin data implicit data and reflects the\n * order of addition of the data. Equal plugin datas have equal strings in terms\n * of content, but not necessarily order.\n */\n @Override\n public String toString();\n}\n\n\nPlugin data classes must be threadsafe since they will be shared between multiple simulations running on separate threads. This stands in contrast to the actors and data managers which are created and managed in the thread of a single simulation. The best practice is to make plugin data classes immutable since immutable classes in Java are guaranteed to be threadsafe. For a class to be immutable in Java it must meet three conditions:\n\nIt cannot be mutated, i.e. it has no setters and no public fields.\nAll its fields are marked final.\nIts constructor(s) do not pass reference to self. No reference to the newly created object leaks out before construction is complete.\n\nBesides carrying whatever data is needed by the plugin, the PluginData implementor must provide a PluginDataBuilder:\n\n\nCode Block 2.28: Every plugin data class has a corresponding builder class to aid in the experiment’s generation of alternate scenarios.\npublic interface PluginDataBuilder {\n /**\n * Returns a plugin data\n */\n public PluginData build();\n}\n\n\nThe role of the plugin data builder will be explored in the next lesson where it will be used to make alterable copies of plugin data to drive the experiment. For now, let’s examine the DiseasePluginData class. It is composed several sections:\n\nA data class\nA static builder class\nA single data field and private constructor\nGetter methods for the data\nA clone builder method\n\n\n\nCode Block 2.29: The disease plugin data collects the various general disease properties used to initialize the disease data manager.\nprivate static class Data {\n\n private double r0;\n\n private double asymptomaticDays;\n\n private double symptomaticDays;\n\n private Data() {\n }\n\n private Data(final Data data) {\n r0 = data.r0;\n asymptomaticDays = data.asymptomaticDays;\n symptomaticDays = data.symptomaticDays;\n }\n\n\nThe Data class is private and just contains the fields needed by the plugin. Note that it is a mutable class and that its fields are not final. It will be used by the builder class later to store values. Its constructors are private and allow one Data object to be copied from another.\n\n\nCode Block 2.30: The builder class for the immutable disease plugin data class.\npublic static class Builder implements PluginDataBuilder {\n private Data data;\n\n private Builder(final Data data) {\n this.data = data;\n }\n\n @Override\n public DiseasePluginData build() {\n\n return new DiseasePluginData(new Data(data));\n\n }\n\n public Builder setAsymptomaticDays(final double asymptomaticDays) {\n data.asymptomaticDays = asymptomaticDays;\n return this;\n }\n\n public Builder setR0(final double r0) {\n data.r0 = r0;\n return this;\n }\n\n public Builder setSymptomaticDays(final double symptomaticDays) {\n data.symptomaticDays = symptomaticDays;\n return this;\n }\n}\n\npublic static Builder builder() {\n return new Builder(new Data());\n}\n\n\nThe static builder class is used instead of a constructor. The use of builder classes for plugin data objects is key to the creation of experiments covered in the next lesson. For now, let’s concentrate on what the builder does. First, it has setter methods for each of the data fields and each such method returns the builder instance to support method chaining. Next, the build() method returns the DiseasePluginData. Finally, the builder’s own constructor is private and is accessed via a static method. This is done to grant a syntax that is more compatible with the method chaining.\n\n\nCode Block 2.31: The disease plugin data is constructed from the collected data in a private constructor.\nprivate final Data data;\n\nprivate DiseasePluginData(final Data data) {\n this.data = data;\n}\n\n\nAfter the builder collects the data, it passes that data to the instance of the DiseasePluginData which is stored as a final field. Recall that the field must be final in an immutable class.\n\n\nCode Block 2.32: The disease plugin data grants access to its immutable field values.\npublic double getAsymptomaticDays() {\n return data.asymptomaticDays;\n}\n\npublic double getR0() {\n return data.r0;\n}\n\npublic double getSymptomaticDays() {\n return data.symptomaticDays;\n}\n\n\nThe getter methods for each field value in the data are added. There are no corresponding setter methods.\n\n\nCode Block 2.33: The disease plugin data creates a copy of its data and places it in the returned plugin data builder.\n@Override\npublic PluginDataBuilder getCloneBuilder() {\n return new Builder(new Data(data));\n}\n\n\nWe end the class with the getCloneBuilder method.\n\n\n\n\n\n\nTerminology Note\n\n\n\nOur use of the term clone is intuitive but may cause some confusion. What we are doing is copying the data in the DiseasePluginData and placing into a builder so that it can be further mutated later in the experiment. Java formally defines the term clone as a part of the Object class definition and implements it with a protected method clone(). Use of the Object.clone() method has generally fallen out of favor in Java but still has some proponents/use cases.\n\n\nThe method returns a new Builder that has reference to the current data object. The resulting example class is easier to read and more succinct:\n\n\nCode Block 2.34: Example 8 executes more succinctly by use of static plugin classes.\npublic final class Example_8 {\n\n private Example_8() {\n }\n\n private static DiseasePluginData getDiseasePluginData() {\n return DiseasePluginData.builder()//\n .setR0(1.5)//\n .setAsymptomaticDays(4.0)//\n .setSymptomaticDays(12.0)//\n .build();\n }\n\n private static PolicyPluginData getPolicyPluginData() {\n return PolicyPluginData.builder()//\n .setDistributeVaccineLocally(true)//\n .setSchoolClosingInfectionRate(0.05)//\n .build();\n }\n\n public static void main(String[] args) {\n\n DiseasePluginData diseasePluginData = getDiseasePluginData();\n Plugin diseasePlugin = DiseasePlugin.getDiseasePlugin(diseasePluginData);\n\n PolicyPluginData policyPluginData = getPolicyPluginData();\n Plugin policyPlugin = PolicyPlugin.getPolicyPlugin(policyPluginData);\n\n Plugin modelPlugin = ModelPlugin.getModelPlugin();\n\n Simulation.builder()//\n .addPlugin(diseasePlugin)//\n .addPlugin(modelPlugin)//\n .addPlugin(policyPlugin)//\n .build()//\n .execute();\n }\n}" + }, + { + "objectID": "ch02-GettingStarted.html#experiments-lesson-lesson-9", + "href": "ch02-GettingStarted.html#experiments-lesson-lesson-9", + "title": "2  Getting Started", + "section": "2.9 Experiments Lesson (Lesson 9)", + "text": "2.9 Experiments Lesson (Lesson 9)\nSo far we have mentioned that the plugin data classes play a role in executing an experiment via the getCloneBuilder method. Let’s start with the simple experiment. We will update the last example class by replacing the Simulation execution with an Experiment execution:\n\n\nCode Block 2.35: Example 9 replaces Example 8’s use of the simulation with an experiment.\npublic final class Example_9_A {\n\n private Example_9_A() {\n }\n\n private static DiseasePluginData getDiseasePluginData() {\n return DiseasePluginData.builder()//\n .setR0(1.5)//\n .setAsymptomaticDays(4.0)//\n .setSymptomaticDays(12.0)//\n .build();\n }\n\n private static PolicyPluginData getPolicyPluginData() {\n return PolicyPluginData.builder()//\n .setDistributeVaccineLocally(true)//\n .setSchoolClosingInfectionRate(0.05)//\n .build();\n }\n\n public static void main(String[] args) {\n\n DiseasePluginData diseasePluginData = getDiseasePluginData();\n Plugin diseasePlugin = DiseasePlugin.getDiseasePlugin(diseasePluginData);\n\n PolicyPluginData policyPluginData = getPolicyPluginData();\n Plugin policyPlugin = PolicyPlugin.getPolicyPlugin(policyPluginData);\n\n Plugin modelPlugin = ModelPlugin.getModelPlugin();\n\n Experiment.builder()//\n .addPlugin(diseasePlugin)//\n .addPlugin(modelPlugin)//\n .addPlugin(policyPlugin)//\n .addExperimentContextConsumer(ExperimentStatusConsole.builder().build())//\n .build()// \n .execute();\n }\n}\n\n\nThe experiment class has a very similar builder to the Simulation class so we only have to swap out the Simulation reference for an Experiment reference. The resulting execution created an experiment containing exactly one simulation that runs in the main thread. However, the output contains information about the status of the experiment.\n\n\n\nFigure 2.6: The output of the experiment version is the same as the simulation since the experiment contains exactly one scenario.\n\n\n\n\n\nModel Actor initializing r0 = 1.5 asymptomatic days = 4.0 symptomatic days = 12.0 school closing infection rate = 0.05 distribute vaccine locally = true 1 of 1 scenario, 100% complete. Expected experiment completion in 0:00:00 Experiment completion of 1 scenario in 0:00:00: SUCCEDED : 1 end of experiment status console\n\n\n\n\n\n\n\n\nWhat happens when the experiment executes?\nYou have contributed several plugins to the experiment and on execution the experiment generates multiple simulation runs on multiple threads. Let’s examine how this is accomplished as a way to motivate this lesson’s code examples.\nThe experiment is composed of several plugins, each with zero to many plugin data objects. For purposes of the diagrams we will assume that each plugin has a single plugin data object.\n\n\n\n\nFigure 2.7: Each plugin contains zero to many plugin data objects. For simplicity, we show one plugin data object per plugin.\n\n\n\n\n\nThe experiment gathers the plugin data objects and gets the plugin data builder for each. These plugin data builders will come pre-filled with the data from the original data objects.\n\n\n\n\nFigure 2.8: Using the clone builder mechanism, each plugin data generates a new plugin data builder that is prefilled with the plugin data’s information.\n\n\n\n\n\nBy altering the data in these builders, we generate new scenarios for the simulations to execute. GCM manages the instructions to alter the plugin data via Dimensions. Each dimension contains one to many levels.\n\n\n\n\nFigure 2.9: Dimensions are contributed to the experiment. Each dimension has a fixed number of levels and each such level alters the plugin data builders in a consistent way.\n\n\n\n\n\nFor example, we may have a dimension that alters the value of alpha from plugin data A and the value of beta from plugin data B. Each level in the dimension will set specific values for alpha and beta via the builders.\n::: {#fig-experiments_diagram_dimension_levels_example2 .figure fig-cap=“Example levels in a dimension”}\n\n\n\nlevel\nalpha\nbeta\n\n\n\n\n0\n2.3\nFALSE\n\n\n1\n3.6\nTRUE\n\n\n2\n4.8\nFALSE\n\n\n\nEach level in a dimension is actually a function that takes in the builders and manipulates the content of each plugin as needed.\n\n\n\n\nFigure 2.10: Dimension levels act as a function to change plugin data builders.\n\n\n\n\n\nConsider an experiment with two dimensions having 3 and 5 levels respectively. The number of level permutations is 3x5 = 15. Each such permutation is referred to as a scenario and the scenarios are numbered from 0 to 14. As the experiment executes, it works with each scenario id and determines for that id which levels are active for each dimension.\n\n\n\n\nFigure 2.11: The scenario id is a numberical composite of the level ids in each of the dimensions.\n\n\n\n\n\nEach level (via its function) alters the contents of the builders in turn, resulting in a unique set of content for that scenario.\n\n\n\n\nFigure 2.12: Once all of the dimensions have added content to the plugin data builders, the experiment builds the resulting plugin datas to be used in the next scenario.\n\n\n\n\n\nThe builders are then instructed by the experiment to build the plugin data objects. The resulting data objects are inserted into copies of the original plugins to produce a unique set of altered plugins that are specific to the scenario id and executed via a single simulation instance.\n\n\n\n\nFigure 2.13: For each original, experiment level plugin, a copy of the plugin is made with the various plugin datas replaced with those specific to the scenario. These altered plugins will be contributed to the simulation instance for the scenario.\n\n\n\n\n\nYou may have noticed that the initializer code above acquires the DiseasePluginData via the context rather than the instance passed to the getDiseasePlugin() method. This is a necessity due to experiment design and will be covered in the lessons that follow. In general, the initializer code should always retrieve plugin data from the plugin context.\nWe expand the example by adding a single dimension that sets r0 to two values, generating two simulations.\n\n\nCode Block 2.36: Example 9 B introduces a single dimension that sets the R0 value of the disease plugin data to two values.\npublic static void main(String[] args) {\n\n DiseasePluginData diseasePluginData = getDiseasePluginData();\n Plugin diseasePlugin = DiseasePlugin.getDiseasePlugin(diseasePluginData);\n\n PolicyPluginData policyPluginData = getPolicyPluginData();\n Plugin policyPlugin = PolicyPlugin.getPolicyPlugin(policyPluginData);\n\n Plugin modelPlugin = ModelPlugin.getModelPlugin();\n\n Dimension dimension = FunctionalDimension.builder()//\n .addLevel((context) -> {\n DiseasePluginData.Builder builder = context.getPluginDataBuilder(DiseasePluginData.Builder.class);\n double r0 = 2.5;\n builder.setR0(r0);\n ArrayList result = new ArrayList<>();\n result.add(Double.toString(r0));\n return result;\n })//\n\n .addLevel((context) -> {\n DiseasePluginData.Builder builder = context.getPluginDataBuilder(DiseasePluginData.Builder.class);\n double r0 = 2.0;\n builder.setR0(r0);\n ArrayList result = new ArrayList<>();\n result.add(Double.toString(r0));\n return result;\n })//\n\n .addMetaDatum(\"r0\")//\n\n .build();\n\n Experiment.builder()//\n .addPlugin(diseasePlugin)//\n .addPlugin(modelPlugin)//\n .addPlugin(policyPlugin)//\n .addDimension(dimension)//\n .build()//\n .execute();\n}\n\n\nIn the dimension we see that there are two levels and the addition of some meta data in the addMetaDatum(“r0”) invocation. The meta data here represents the information that each level is altering in the experiment. The main purpose of each level is to alter the state of a builder(s) but must also return meta data values to match the meta data for the dimension. The meta data of the dimension acts as a header to a table while the meta data for each level are the values in that table.\nThe building of the dimension can be streamlined without typing out each level:\n\n\nCode Block 2.37: Example 9 C improves on the creation of the R0 dimension.\npublic final class Example_9_C {\n\n private Example_9_C() {\n }\n\n private static DiseasePluginData getDiseasePluginData() {\n return DiseasePluginData.builder()//\n .setR0(1.5)//\n .setAsymptomaticDays(4.0)//\n .setSymptomaticDays(12.0)//\n .build();\n }\n\n private static PolicyPluginData getPolicyPluginData() {\n return PolicyPluginData.builder()//\n .setDistributeVaccineLocally(true)//\n .setSchoolClosingInfectionRate(0.05)//\n .build();\n }\n\n private static Dimension getDimension() {\n FunctionalDimension.Builder builder = FunctionalDimension.builder();//\n\n List r0Values = new ArrayList<>();\n r0Values.add(0.5);\n r0Values.add(0.75);\n r0Values.add(1.0);\n r0Values.add(1.5);\n r0Values.add(2.0);\n r0Values.add(2.5);\n\n for (Double r0 : r0Values) {\n builder.addLevel((context) -> {\n DiseasePluginData.Builder pluginDataBuilder = context\n .getPluginDataBuilder(DiseasePluginData.Builder.class);\n pluginDataBuilder.setR0(r0);\n ArrayList result = new ArrayList<>();\n result.add(Double.toString(r0));\n return result;\n });//\n }\n builder.addMetaDatum(\"r0\");//\n\n return builder.build();\n }\n\n\nThe resulting experiment execution is more streamlined:\n\n\nCode Block 2.38: Execution of the experiment is cleaner.\npublic static void main(String[] args) {\n\n DiseasePluginData diseasePluginData = getDiseasePluginData();\n Plugin diseasePlugin = DiseasePlugin.getDiseasePlugin(diseasePluginData);\n\n PolicyPluginData policyPluginData = getPolicyPluginData();\n Plugin policyPlugin = PolicyPlugin.getPolicyPlugin(policyPluginData);\n\n Plugin modelPlugin = ModelPlugin.getModelPlugin();\n\n Dimension dimension = getDimension();\n\n Experiment.builder()//\n .addPlugin(diseasePlugin)//\n .addPlugin(modelPlugin)//\n .addPlugin(policyPlugin)//\n .addDimension(dimension)//\n .build()//\n .execute();\n}\n\n\nWe have turned off the experiment report progress to console in the code above. We have chosen six values for r0 in our dimension and thus we have 6 simulation executions, each having the model actor print out the contents of the DiseaseDataManager:\n\n\n\nFigure 2.14: The model actor writes output for each of the five scenarios corresponding to the five values of the R0 dimension.\n\n\n\n\n\nModel Actor initializing r0 = 0.5 asymptomatic days = 4.0 symptomatic days = 12.0 school closing infection rate = 0.05 distribute vaccine locally = true Model Actor initializing r0 = 0.75 asymptomatic days = 4.0 symptomatic days = 12.0 school closing infection rate = 0.05 distribute vaccine locally = true Model Actor initializing r0 = 1.0 asymptomatic days = 4.0 symptomatic days = 12.0 school closing infection rate = 0.05 distribute vaccine locally = true Model Actor initializing r0 = 1.5 asymptomatic days = 4.0 symptomatic days = 12.0 school closing infection rate = 0.05 distribute vaccine locally = true Model Actor initializing r0 = 2.0 asymptomatic days = 4.0 symptomatic days = 12.0 school closing infection rate = 0.05 distribute vaccine locally = true Model Actor initializing r0 = 2.5 asymptomatic days = 4.0 symptomatic days = 12.0 school closing infection rate = 0.05 distribute vaccine locally = true \n\n\n\n\n\n\n\n\nWe are extending the example again, reducing the r0 dimension to just three levels and introducing a dimension over the policy data. This new dimension has four levels controlling local vaccine distribution and school closing infection rates:\n\n\nCode Block 2.39: A dimension representing school related policies is added. Note that this dimension has four levels and covers two policies.\nprivate static Dimension getPolicyDimension() {\n FunctionalDimension.Builder builder = FunctionalDimension.builder();//\n\n List schoolClosingInfectionRates = new ArrayList<>();\n schoolClosingInfectionRates.add(0.05);\n schoolClosingInfectionRates.add(0.10);\n\n List localVaccineDistributionValues = new ArrayList<>();\n localVaccineDistributionValues.add(false);\n localVaccineDistributionValues.add(true);\n\n for (Boolean localVaccineDistribution : localVaccineDistributionValues) {\n for (Double schoolClosingInfectionRate : schoolClosingInfectionRates) {\n builder.addLevel((context) -> {\n PolicyPluginData.Builder pluginDataBuilder = context\n .getPluginDataBuilder(PolicyPluginData.Builder.class);\n pluginDataBuilder.setSchoolClosingInfectionRate(schoolClosingInfectionRate);\n pluginDataBuilder.setDistributeVaccineLocally(localVaccineDistribution);\n\n ArrayList result = new ArrayList<>();\n result.add(Double.toString(schoolClosingInfectionRate));\n result.add(Boolean.toString(localVaccineDistribution));\n return result;\n });//\n }\n }\n builder.addMetaDatum(\"school_closing_infection_rate\");//\n builder.addMetaDatum(\"distribute_vaccine_locally\");//\n\n return builder.build();\n}\n\n\nWe add the new dimension to the experiment:\n\n\nCode Block 2.40: The new policy dimension is added to the experiment with four levels. The R0 dimension was reduced to three levels. Thus the experiment will run twelve scenarios.\npublic static void main(String[] args) {\n\n DiseasePluginData diseasePluginData = getDiseasePluginData();\n Plugin diseasePlugin = DiseasePlugin.getDiseasePlugin(diseasePluginData);\n\n PolicyPluginData policyPluginData = getPolicyPluginData();\n Plugin policyPlugin = PolicyPlugin.getPolicyPlugin(policyPluginData);\n\n Plugin modelPlugin = ModelPlugin.getModelPlugin();\n\n Dimension r0Dimension = getR0Dimension();\n\n Dimension policyDimension = getPolicyDimension();\n\n Experiment.builder()//\n .addPlugin(diseasePlugin)//\n .addPlugin(modelPlugin)//\n .addPlugin(policyPlugin)//\n .addDimension(r0Dimension)//\n .addDimension(policyDimension)//\n .build()//\n .execute();\n}\n\n\nThe result is now 12 executed scenarios:\n\n\n\nFigure 2.15: The first two scenarios.\n\n\n\n\n\nModel Actor initializing r0 = 1.5 asymptomatic days = 4.0 symptomatic days = 12.0 school closing infection rate = 0.05 distribute vaccine locally = false Model Actor initializing r0 = 2.0 asymptomatic days = 4.0 symptomatic days = 12.0 school closing infection rate = 0.05 distribute vaccine locally = false \n\n\n\n\n\n\n\n\n…\n\n\n\nFigure 2.16: The last two scenarios.\n\n\n\n\n\nModel Actor initializing r0 = 2.0 asymptomatic days = 4.0 symptomatic days = 12.0 school closing infection rate = 0.1 distribute vaccine locally = true Model Actor initializing r0 = 2.5 asymptomatic days = 4.0 symptomatic days = 12.0 school closing infection rate = 0.1 distribute vaccine locally = true \n\n\n\n\n\n\n\n\nSo far, the experiment has run in a single thread. We now run it in four threads by adding an ExperimentParameterData.\n\n\nCode Block 2.41: Executing the 12 scenarios of the previous experiment with four threads.\npublic static void main(String[] args) {\n\n DiseasePluginData diseasePluginData = getDiseasePluginData();\n Plugin diseasePlugin = DiseasePlugin.getDiseasePlugin(diseasePluginData);\n\n PolicyPluginData policyPluginData = getPolicyPluginData();\n Plugin policyPlugin = PolicyPlugin.getPolicyPlugin(policyPluginData);\n\n Plugin modelPlugin = ModelPlugin.getModelPlugin();\n\n Dimension r0Dimension = getR0Dimension();\n\n Dimension policyDimension = getPolicyDimension();\n\n ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()//\n .setThreadCount(4)//\n .build();\n\n /*\n * Adding threads. Scrambled output\n */\n Experiment.builder()//\n .addPlugin(diseasePlugin)//\n .addPlugin(modelPlugin)//\n .addPlugin(policyPlugin)//\n .addDimension(r0Dimension)//\n .addDimension(policyDimension)//\n .setExperimentParameterData(experimentParameterData)//\n .build()//\n .execute();\n}\n\n\nThe experiment runs in the main thread and the scenarios now run the four additional threads. The resulting console output a bit jumbled since the writes to the console are now coming from four simultaneous simulation runs:\n\n\n\nFigure 2.17: The output from the four threads running the twelve scenarios is a bit jumbled and hard to follow. This will get resolved later with improved output handling.\n\n\n\n\n\nModel Actor initializing Model Actor initializing r0 = 1.5 Model Actor initializing Model Actor initializing r0 = 1.5 asymptomatic days = 4.0 symptomatic days = 12.0 r0 = 2.5 school closing infection rate = 0.05 asymptomatic days = 4.0 asymptomatic days = 4.0 symptomatic days = 12.0 r0 = 2.0 school closing infection rate = 0.1 distribute vaccine locally = false …\n\n\n\n\n\n\n\n\n\nWe will alleviate this problem as we explore how the simulation and experiment manage output." + }, + { + "objectID": "ch02-GettingStarted.html#output-lesson-lesson-10", + "href": "ch02-GettingStarted.html#output-lesson-lesson-10", + "title": "2  Getting Started", + "section": "2.10 Output Lesson (Lesson 10)", + "text": "2.10 Output Lesson (Lesson 10)\nSo far we have only produced output by writing directly to the console in the various actors and data managers. The simulation contexts (ActorContext / ReportContext / DataManagerContext) provide for the release of output objects to an external handler (outside the simulation). In this lesson, the ModelActor class has been altered to use this mechanism:\n\n\nCode Block 2.42: The model actor now reports output via the release output method provided by its context.\npublic void init(ActorContext actorContext) {\n DiseaseDataManager diseaseDataManager = actorContext.getDataManager(DiseaseDataManager.class);\n actorContext.releaseOutput(\"Model Actor initializing\");\n String tab = \"\\t\";\n actorContext.releaseOutput(tab + \"r0 = \" + diseaseDataManager.getR0());\n actorContext.releaseOutput(tab + \"asymptomatic days = \" + diseaseDataManager.getAsymptomaticDays());\n actorContext.releaseOutput(tab + \"symptomatic days = \" + diseaseDataManager.getSymptomaticDays());\n PolicyDataManager policyDataManager = actorContext.getDataManager(PolicyDataManager.class);\n actorContext.releaseOutput(\n tab + \"school closing infection rate = \" + policyDataManager.getSchoolClosingInfectionRate());\n actorContext\n .releaseOutput(tab + \"distribute vaccine locally = \" + policyDataManager.distributeVaccineLocally());\n}\n\n\nData managers can release output in a completely similar way. The output objects are handled by an external handler presented during the build of the simulation:\n\n\nCode Block 2.43: The simulation sends the released output from the contexts to an output consumer.\npublic final class Example_10_A {\n\n private Example_10_A() {\n }\n\n private static DiseasePluginData getDiseasePluginData() {\n return DiseasePluginData.builder()//\n .setR0(1.5)//\n .setAsymptomaticDays(4.0)//\n .setSymptomaticDays(12.0)//\n .build();\n }\n\n private static PolicyPluginData getPolicyPluginData() {\n return PolicyPluginData.builder()//\n .setDistributeVaccineLocally(true)//\n .setSchoolClosingInfectionRate(0.05)//\n .build();\n }\n\n public static void main(String[] args) {\n\n DiseasePluginData diseasePluginData = getDiseasePluginData();\n Plugin diseasePlugin = DiseasePlugin.getDiseasePlugin(diseasePluginData);\n\n PolicyPluginData policyPluginData = getPolicyPluginData();\n Plugin policyPlugin = PolicyPlugin.getPolicyPlugin(policyPluginData);\n\n Plugin modelPlugin = ModelPlugin.getModelPlugin();\n\n Simulation.builder()//\n .addPlugin(diseasePlugin)//\n .addPlugin(modelPlugin)//\n .addPlugin(policyPlugin)//\n .setOutputConsumer(new OutputConsumer_A()).build()//\n .execute();\n }\n}\n\n\nReleased output objects are sent to the output consumer. In the current example, that consumer is an instance of the class OutputConsumer_A and it simply prints the object to the console:\n\n\nCode Block 2.44: Output consumer A simply prints output to the console.\npublic class OutputConsumer_A implements Consumer {\n\n @Override\n public void accept(Object t) {\n System.out.println(t);\n }\n\n}\n\n\nAt first glance this mechanism seems simple and not particularly useful. In practice, one rarely uses the simulation directly and instead favors the experiment which has a somewhat more sophisticated handling of output. With experiments, GCM is potentially using multiple threads to execute each simulation, so output handling must be threadsafe.\n\n2.10.1 Experiment Context\nJust as the simulation supplies contexts, the experiment uses the ExperimentContext to give output consumers a view into the ongoing experiment. It gives each output consumer several capabilities:\n\nSubscription to output by output class type\nSubscription to the opening and closing of the experiment\nSubscription to the opening and closing of each simulation\nScenario status information\nExperiment and Scenario meta data\n\nIn Example_10_B, we bring back the dimensions from previous lessons and will excerpt just the main method:\n\n\nCode Block 2.45: The experiment is now involved in the output process. A new output consumer is used that has access to scenario level information.\npublic static void main(String[] args) {\n\n DiseasePluginData diseasePluginData = getDiseasePluginData();\n Plugin diseasePlugin = DiseasePlugin.getDiseasePlugin(diseasePluginData);\n\n PolicyPluginData policyPluginData = getPolicyPluginData();\n Plugin policyPlugin = PolicyPlugin.getPolicyPlugin(policyPluginData);\n\n Plugin modelPlugin = ModelPlugin.getModelPlugin();\n\n Dimension r0Dimension = getR0Dimension();\n\n Dimension policyDimension = getPolicyDimension();\n\n ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()//\n .setThreadCount(4)//\n .build();\n\n Experiment.builder()//\n .addPlugin(diseasePlugin)//\n .addPlugin(modelPlugin)//\n .addPlugin(policyPlugin)//\n .addDimension(r0Dimension)//\n .addDimension(policyDimension)//\n .addExperimentContextConsumer(new OutputConsumer_B())//\n .setExperimentParameterData(experimentParameterData)//\n .build()//\n .execute();\n}\n\n\nLike the simulation, the experiment is adding a consumer for output, but this time that consumer is “consuming” an experiment context. Once the consumer receives that context, it will use it to further subscribe to output and various experiment level events.\n\n\nCode Block 2.46: Output consumer B has access to the experiment level data, so it prints the output to the console as before, but also adds the relevant scenario id.\npublic class OutputConsumer_B implements Consumer {\n\n @Override\n public void accept(ExperimentContext experimentContext) {\n experimentContext.subscribeToOutput(Object.class, this::handleOutput);\n }\n\n private void handleOutput(ExperimentContext experimentContext, Integer scenarioId, Object output) {\n System.out.println(\"scenario \" + scenarioId + \": \" + output);\n }\n}\n\n\nThe experiment can have any number of ExperimentContext consumers and initializes each at the beginning of its execution via the accept() method. In OuputConsumer_B, the only action the consumer takes is to subscribe to all output and have that output handled by the handleOutput() method. The resulting output shows the scenario id for each line:\n\n\n\nFigure 2.18: The output is still a bit scrambled, but each row now has the relevant scenario id.\n\n\n\n\n\nscenario 1: Model Actor initializing scenario 2: Model Actor initializing scenario 3: Model Actor initializing scenario 0: Model Actor initializing scenario 3: r0 = 1.5 scenario 0: r0 = 1.5 scenario 2: r0 = 2.5 scenario 2: asymptomatic days = 4.0 scenario 1: r0 = 2.0 scenario 2: symptomatic days = 12.0 scenario 1: asymptomatic days = 4.0 scenario 0: asymptomatic days = 4.0 scenario 2: school closing infection rate = 0.05 scenario 3: asymptomatic days = 4.0 scenario 2: distribute vaccine locally = false scenario 0: symptomatic days = 12.0 scenario 1: symptomatic days = 12.0 scenario 0: school closing infection rate = 0.05 scenario 1: school closing infection rate = 0.05 scenario 3: symptomatic days = 12.0 scenario 3: school closing infection rate = 0.1 scenario 1: distribute vaccine locally = false scenario 0: distribute vaccine locally = false scenario 3: distribute vaccine locally = false scenario 5: Model Actor initializing scenario 4: Model Actor initializing scenario 5: r0 = 2.5 scenario 4: r0 = 2.0 … \n\n\n\n\n\n\n\n\nExample_10_C switches the experiment context consumer to an instance of OuputConsumer_C which subscribes to all output types as well as the opening and closing of the experiment and all simulations (scenarios):\n\n\nCode Block 2.47: The output consumer C demonstrates the broader life cycle of the experiment context by printing out experiment and scenario status while still printing output to the console.\npublic class OutputConsumer_C implements Consumer {\n\n @Override\n public void accept(ExperimentContext experimentContext) {\n experimentContext.subscribeToOutput(Object.class, this::handleOutput);\n\n experimentContext.subscribeToExperimentOpen(this::handleExperimentOpen);\n experimentContext.subscribeToExperimentClose(this::handleExperimentClose);\n\n experimentContext.subscribeToSimulationOpen(this::handleSimulationOpen);\n experimentContext.subscribeToSimulationClose(this::handleSimulationClose);\n }\n\n private void handleOutput(ExperimentContext experimentContext, Integer scenarioId, Object output) {\n System.out.println(\"scenario \" + scenarioId + \": \" + output);\n }\n\n private void handleExperimentOpen(ExperimentContext experimentContext) {\n System.out.println(\"the experiment is open\");\n }\n\n private void handleExperimentClose(ExperimentContext experimentContext) {\n System.out.println(\"the experiment is closed\");\n }\n\n private void handleSimulationOpen(ExperimentContext experimentContext, Integer scenarioId) {\n System.out.println(\"scenario \" + scenarioId + \" is open\");\n }\n\n private void handleSimulationClose(ExperimentContext experimentContext, Integer scenarioId) {\n System.out.println(\"scenario \" + scenarioId + \" is closed\");\n }\n}\n\n\nThe resulting output shows the usual released output along with the opening and closing of each simulation:\n\n\n\nFigure 2.19: The first output lines for OutputConsumer_C.\n\n\n\n\n\nthe experiment is open scenario 0 is open scenario 1 is open scenario 2 is open scenario 3 is open scenario 0: Model Actor initializing scenario 2: Model Actor initializing scenario 1: Model Actor initializing scenario 2: r0 = 2.5 scenario 3: Model Actor initializing scenario 2: asymptomatic days = 4.0 scenario 1: r0 = 2.0 scenario 0: r0 = 1.5 scenario 2: symptomatic days = 12.0 … \n\n\n\n\n\n\n\n\n\n\n\nFigure 2.20: The last output lines for OutputConsumer_C.\n\n\n\n\n\n… scenario 11: Model Actor initializing scenario 10: distribute vaccine locally = true scenario 11: r0 = 2.5 scenario 11: asymptomatic days = 4.0 scenario 10 is closed scenario 11: symptomatic days = 12.0 scenario 11: school closing infection rate = 0.1 scenario 11: distribute vaccine locally = true scenario 11 is closed the experiment is closed \n\n\n\n\n\n\n\n\nIn the final example, OuputConsumer_D, we drop the output handling and demonstrate that the meta data used to build the dimensions of the experiment can be retrieved from the experiment context and used for reporting:\n\n\nCode Block 2.48: OutputConsumer_D demonstrates that the meta data collected from the dimensions is available from the experiment context. Thus output can be associated with the scenario’s meta data.\npublic class OutputConsumer_D implements Consumer {\n\n @Override\n public void accept(ExperimentContext experimentContext) {\n experimentContext.subscribeToExperimentOpen(this::handleExperimentOpen);\n experimentContext.subscribeToSimulationOpen(this::handleSimulationOpen);\n }\n\n private void handleExperimentOpen(ExperimentContext experimentContext) {\n\n StringJoiner joiner = new StringJoiner(\"\\t\", \"\", \"\");\n joiner.add(\"scenario\");\n experimentContext.getExperimentMetaData().forEach(joiner::add);\n\n System.out.println(joiner);\n }\n\n private void handleSimulationOpen(ExperimentContext experimentContext, Integer scenarioId) {\n\n StringJoiner joiner = new StringJoiner(\"\\t\", \"\", \"\");\n joiner.add(scenarioId.toString());\n experimentContext.getScenarioMetaData(scenarioId).forEach(joiner::add);\n\n System.out.println(joiner);\n }\n\n}\n\n\nThe resulting output shows for each scenario the meta-data that defines that scenario:\n\n\n\nFigure 2.21: The output of consumer D showing the scenario meta data for the twelve scenarios.\n\n\n\n\n\n\nscenario\n\n\nr0\n\n\nschool_closing_infection_rate\n\n\ndistribute_vaccine_locally\n\n\n\n\n\n\n0\n\n\n1.5\n\n\n0.05\n\n\nFALSE\n\n\n\n\n1\n\n\n2.0\n\n\n0.05\n\n\nFALSE\n\n\n\n\n2\n\n\n2.5\n\n\n0.05\n\n\nFALSE\n\n\n\n\n3\n\n\n1.5\n\n\n0.10\n\n\nFALSE\n\n\n\n\n4\n\n\n2.0\n\n\n0.10\n\n\nFALSE\n\n\n\n\n5\n\n\n2.5\n\n\n0.10\n\n\nFALSE\n\n\n\n\n6\n\n\n1.5\n\n\n0.05\n\n\nTRUE\n\n\n\n\n7\n\n\n2.0\n\n\n0.05\n\n\nTRUE\n\n\n\n\n8\n\n\n2.5\n\n\n0.05\n\n\nTRUE\n\n\n\n\n9\n\n\n1.5\n\n\n0.10\n\n\nTRUE\n\n\n\n\n10\n\n\n2.0\n\n\n0.10\n\n\nTRUE\n\n\n\n\n11\n\n\n2.5\n\n\n0.10\n\n\nTRUE\n\n\n\n\n\n\n\n\n\nRecall that as the experiment executes, it utilizes multiple threads to execute the individual scenarios. Thus every experiment context consumer must be threadsafe. We have accomplished this by making each such consumer stateless. In practice, it is often necessary for experiment context consumers to be stateful and this can involve careful consideration of the use of synchronization and other concurrency issues. Fortunately, GCM provides a reporting plugin that deals with these issues and provides a general method for producing tabular reports." + }, + { + "objectID": "ch03-StochasticsPlugin.html#plugin-data-initialization", + "href": "ch03-StochasticsPlugin.html#plugin-data-initialization", + "title": "3  Stochastics Plugin", + "section": "3.1 Plugin Data Initialization", + "text": "3.1 Plugin Data Initialization\nThe plugin is initialized using a StochasticsPluginData object that collects starting seed values for the default RNG as well as any number of RNG identifiers. These identifiers are implemented via the RandomGeneratorId interface which only specifies that such an identifier have a non-null, non-empty and stable implementation of the Object.toString() method.\nAll RNGs in GCM are implemented using the org.apache.commons.math3.random.Well44497b random number generator. GCM introduces the class Well.java that extends the Well44497b to allow for the serialization of its internal state. The data that supports this serialization is contained in the WellState.java class that uses a standard builder pattern. Serialization concerns are beyond the scope of this chapter, so all WellState objects will be seeded with long values only." + }, + { + "objectID": "ch03-StochasticsPlugin.html#plugin-behavior", + "href": "ch03-StochasticsPlugin.html#plugin-behavior", + "title": "3  Stochastics Plugin", + "section": "3.2 Plugin Behavior", + "text": "3.2 Plugin Behavior\nThe plugin adds a single data manager to the simulation as an instance of the StochasticsDataManager that is initialized with the StochasticsPluginData." + }, + { + "objectID": "ch03-StochasticsPlugin.html#data-manager", + "href": "ch03-StochasticsPlugin.html#data-manager", + "title": "3  Stochastics Plugin", + "section": "3.3 Data Manager", + "text": "3.3 Data Manager\nThe data manager provides access to its RNGs via various getter methods." + }, + { + "objectID": "ch03-StochasticsPlugin.html#lesson-11-example-11a", + "href": "ch03-StochasticsPlugin.html#lesson-11-example-11a", + "title": "3  Stochastics Plugin", + "section": "3.4 (Lesson 11) Example 11A", + "text": "3.4 (Lesson 11) Example 11A\nOur first example lesson uses the disease, model and policy plugins again. This time we will have the single ModelActor schedule three random times to set the R0 value to a random number between 1 and 2. Four scenarios will result from a policy based dimension that alters the school closing infection rates, which will not influence the ModelActor.\n\n\nCode Block 3.1: The policy dimension has four levels for the infection rates that trigger school closure.\nprivate static Dimension getPolicyDimension() {\n FunctionalDimension.Builder builder = FunctionalDimension.builder();//\n\n List schoolClosingInfectionRates = new ArrayList<>();\n schoolClosingInfectionRates.add(0.05);\n schoolClosingInfectionRates.add(0.10);\n schoolClosingInfectionRates.add(0.15);\n schoolClosingInfectionRates.add(0.20);\n\n for (Double schoolClosingInfectionRate : schoolClosingInfectionRates) {\n builder.addLevel((context) -> {\n PolicyPluginData.Builder pluginDataBuilder = context\n .getPluginDataBuilder(PolicyPluginData.Builder.class);\n pluginDataBuilder.setSchoolClosingInfectionRate(schoolClosingInfectionRate);\n\n ArrayList result = new ArrayList<>();\n result.add(Double.toString(schoolClosingInfectionRate));\n\n return result;\n });//\n }\n\n builder.addMetaDatum(\"school_closing_infection_rate\");//\n\n return builder.build();\n\n}\n\n\n\n\nCode Block 3.2: Example 11 introduces the stochastics plugin and executes four scenarios. The random seed for each scenario will be identical.\npublic static void main(String[] args) {\n\n DiseasePluginData diseasePluginData = getDiseasePluginData();\n Plugin diseasePlugin = DiseasePlugin.getDiseasePlugin(diseasePluginData);\n\n PolicyPluginData policyPluginData = getPolicyPluginData();\n Plugin policyPlugin = PolicyPlugin.getPolicyPlugin(policyPluginData);\n\n Plugin modelPlugin = ModelPlugin.getModelPlugin();\n\n WellState wellState = WellState.builder().setSeed(0).build();\n StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder().setMainRNGState(wellState)\n .build();\n Plugin stochasticsPlugin = StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData);\n\n Dimension policyDimension = getPolicyDimension();\n\n ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()//\n .setThreadCount(4)//\n .setHaltOnException(true)//\n .build();\n\n Experiment.builder()//\n .addPlugin(stochasticsPlugin)//\n .addPlugin(diseasePlugin)//\n .addPlugin(modelPlugin)//\n .addPlugin(policyPlugin)//\n .addDimension(policyDimension)//\n .addExperimentContextConsumer(new SimpleOutputConsumer())//\n .setExperimentParameterData(experimentParameterData)//\n .build()//\n .execute();\n}\n\n\nThe stochastics plugin is initialized with a seed value of zero and that seed will be used in each scenario as the initial seeding for the default random generator. Thus we expect that each scenario will have identical output.\n\n\n\nFigure 3.1: The output for example 11 shows the four scenarios each reporting three changes to R0 by the model actor.\n\n\n\n\n\n\nscenario\n\n\nschool_closing_infection_rate\n\n\noutput\n\n\n\n\n\n\n1\n\n\n0.10\n\n\nsetting R0 to 1.432233562051883 at time = 3.252869296309885\n\n\n\n\n1\n\n\n0.10\n\n\nsetting R0 to 1.1828080336720215 at time = 4.115633147309184\n\n\n\n\n0\n\n\n0.05\n\n\nsetting R0 to 1.432233562051883 at time = 3.252869296309885\n\n\n\n\n3\n\n\n0.20\n\n\nsetting R0 to 1.432233562051883 at time = 3.252869296309885\n\n\n\n\n1\n\n\n0.10\n\n\nsetting R0 to 1.895526664357549 at time = 8.614888683848772\n\n\n\n\n0\n\n\n0.05\n\n\nsetting R0 to 1.1828080336720215 at time = 4.115633147309184\n\n\n\n\n2\n\n\n0.15\n\n\nsetting R0 to 1.432233562051883 at time = 3.252869296309885\n\n\n\n\n3\n\n\n0.20\n\n\nsetting R0 to 1.1828080336720215 at time = 4.115633147309184\n\n\n\n\n0\n\n\n0.05\n\n\nsetting R0 to 1.895526664357549 at time = 8.614888683848772\n\n\n\n\n2\n\n\n0.15\n\n\nsetting R0 to 1.1828080336720215 at time = 4.115633147309184\n\n\n\n\n3\n\n\n0.20\n\n\nsetting R0 to 1.895526664357549 at time = 8.614888683848772\n\n\n\n\n2\n\n\n0.15\n\n\nsetting R0 to 1.895526664357549 at time = 8.614888683848772" + }, + { + "objectID": "ch03-StochasticsPlugin.html#example-11b", + "href": "ch03-StochasticsPlugin.html#example-11b", + "title": "3  Stochastics Plugin", + "section": "3.5 Example 11B", + "text": "3.5 Example 11B\nOur next example lesson adds a dimension used to alter the initial seed value of the stochastics plugin data to one of three values. Combined with the policy dimension, this will result in 12 scenarios.\n\n\nCode Block 3.3: The stochastics dimension introduces three random seeds that will be used in creating the scenarios. Note that seeds are generated outside of the levels within the dimension.\nprivate static Dimension getStochasticsDimension(long seed) {\n FunctionalDimension.Builder builder = FunctionalDimension.builder();//\n\n Random random = new Random(seed);\n\n List seedValues = new ArrayList<>();\n for (int i = 0; i < 3; i++) {\n seedValues.add(random.nextLong());\n }\n\n IntStream.range(0, seedValues.size()).forEach((i) -> {\n builder.addLevel((context) -> {\n StochasticsPluginData.Builder stochasticsPluginDataBuilder = context\n .getPluginDataBuilder(StochasticsPluginData.Builder.class);\n long seedValue = seedValues.get(i);\n WellState wellState = WellState.builder().setSeed(seedValue).build();\n stochasticsPluginDataBuilder.setMainRNGState(wellState);\n\n ArrayList result = new ArrayList<>();\n result.add(Integer.toString(i));\n result.add(Long.toString(seedValue) + \"L\");\n\n return result;\n });\n });\n\n builder.addMetaDatum(\"seed index\");//\n builder.addMetaDatum(\"seed value\");//\n\n return builder.build();\n}\n\n\n\n\nCode Block 3.4: The experiment uses the stochastics dimension, resulting in twelve scenarios.\npublic static void main(String[] args) {\n\n DiseasePluginData diseasePluginData = getDiseasePluginData();\n Plugin diseasePlugin = DiseasePlugin.getDiseasePlugin(diseasePluginData);\n\n PolicyPluginData policyPluginData = getPolicyPluginData();\n Plugin policyPlugin = PolicyPlugin.getPolicyPlugin(policyPluginData);\n\n Plugin modelPlugin = ModelPlugin.getModelPlugin();\n WellState wellState = WellState.builder().setSeed(0).build();\n StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder().setMainRNGState(wellState)\n .build();\n Plugin stochasticsPlugin = StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData);\n\n Dimension policyDimension = getPolicyDimension();\n Dimension stochasticsDimension = getStochasticsDimension(539847398756272L);\n\n ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()//\n .setThreadCount(4)//\n .build();\n\n Experiment.builder()//\n .addPlugin(stochasticsPlugin)//\n .addPlugin(diseasePlugin)//\n .addPlugin(modelPlugin)//\n .addPlugin(policyPlugin)//\n .addDimension(policyDimension)//\n .addDimension(stochasticsDimension)//\n .addExperimentContextConsumer(new SimpleOutputConsumer())//\n .setExperimentParameterData(experimentParameterData)//\n .build()//\n .execute();\n}\n\n\nThe resulting output shows the varying random number generation:\n\n\n\nFigure 3.2: The output show that twelve scearnios result in 36 output lines since the model actor update R0 three times per scenario.\n\n\n\n\n\n\nscenario\n\n\nschool_closing_infection_rate\n\n\nseed_index\n\n\nseed_value\n\n\noutput\n\n\n\n\n\n\n3\n\n\n0.2\n\n\n0\n\n\n1768604912325913878L\n\n\nsetting R0 to 1.4595120508977955 at time = 5.672294645832067\n\n\n\n\n2\n\n\n0.15\n\n\n0\n\n\n1768604912325913878L\n\n\nsetting R0 to 1.4595120508977955 at time = 5.672294645832067\n\n\n\n\n3\n\n\n0.2\n\n\n0\n\n\n1768604912325913878L\n\n\nsetting R0 to 1.672857576347001 at time = 9.396161237311702\n\n\n\n\n1\n\n\n0.1\n\n\n0\n\n\n1768604912325913878L\n\n\nsetting R0 to 1.4595120508977955 at time = 5.672294645832067\n\n\n\n\n0\n\n\n0.05\n\n\n0\n\n\n1768604912325913878L\n\n\nsetting R0 to 1.4595120508977955 at time = 5.672294645832067\n\n\n\n\n3\n\n\n0.2\n\n\n0\n\n\n1768604912325913878L\n\n\nsetting R0 to 1.4552243930124602 at time = 9.545450999459224\n\n\n\n\n1\n\n\n0.1\n\n\n0\n\n\n1768604912325913878L\n\n\nsetting R0 to 1.672857576347001 at time = 9.396161237311702\n\n\n\n\n2\n\n\n0.15\n\n\n0\n\n\n1768604912325913878L\n\n\nsetting R0 to 1.672857576347001 at time = 9.396161237311702\n\n\n\n\n0\n\n\n0.05\n\n\n0\n\n\n1768604912325913878L\n\n\nsetting R0 to 1.672857576347001 at time = 9.396161237311702\n\n\n\n\n1\n\n\n0.1\n\n\n0\n\n\n1768604912325913878L\n\n\nsetting R0 to 1.4552243930124602 at time = 9.545450999459224\n\n\n\n\n2\n\n\n0.15\n\n\n0\n\n\n1768604912325913878L\n\n\nsetting R0 to 1.4552243930124602 at time = 9.545450999459224\n\n\n\n\n0\n\n\n0.05\n\n\n0\n\n\n1768604912325913878L\n\n\nsetting R0 to 1.4552243930124602 at time = 9.545450999459224\n\n\n\n\n4\n\n\n0.05\n\n\n1\n\n\n2407662077113051075L\n\n\nsetting R0 to 1.7124977513361193 at time = 1.878219018807409\n\n\n\n\n4\n\n\n0.05\n\n\n1\n\n\n2407662077113051075L\n\n\nsetting R0 to 1.4297609713254456 at time = 2.4215934269433106\n\n\n\n\n4\n\n\n0.05\n\n\n1\n\n\n2407662077113051075L\n\n\nsetting R0 to 1.5167619787625548 at time = 5.992476899450338\n\n\n\n\n5\n\n\n0.1\n\n\n1\n\n\n2407662077113051075L\n\n\nsetting R0 to 1.7124977513361193 at time = 1.878219018807409\n\n\n\n\n5\n\n\n0.1\n\n\n1\n\n\n2407662077113051075L\n\n\nsetting R0 to 1.4297609713254456 at time = 2.4215934269433106\n\n\n\n\n5\n\n\n0.1\n\n\n1\n\n\n2407662077113051075L\n\n\nsetting R0 to 1.5167619787625548 at time = 5.992476899450338\n\n\n\n\n6\n\n\n0.15\n\n\n1\n\n\n2407662077113051075L\n\n\nsetting R0 to 1.7124977513361193 at time = 1.878219018807409\n\n\n\n\n6\n\n\n0.15\n\n\n1\n\n\n2407662077113051075L\n\n\nsetting R0 to 1.4297609713254456 at time = 2.4215934269433106\n\n\n\n\n6\n\n\n0.15\n\n\n1\n\n\n2407662077113051075L\n\n\nsetting R0 to 1.5167619787625548 at time = 5.992476899450338\n\n\n\n\n7\n\n\n0.2\n\n\n1\n\n\n2407662077113051075L\n\n\nsetting R0 to 1.7124977513361193 at time = 1.878219018807409\n\n\n\n\n7\n\n\n0.2\n\n\n1\n\n\n2407662077113051075L\n\n\nsetting R0 to 1.4297609713254456 at time = 2.4215934269433106\n\n\n\n\n7\n\n\n0.2\n\n\n1\n\n\n2407662077113051075L\n\n\nsetting R0 to 1.5167619787625548 at time = 5.992476899450338\n\n\n\n\n9\n\n\n0.1\n\n\n2\n\n\n-2698580492431892402L\n\n\nsetting R0 to 1.9665140789775497 at time = 4.990978097055602\n\n\n\n\n9\n\n\n0.1\n\n\n2\n\n\n-2698580492431892402L\n\n\nsetting R0 to 1.9525439902956225 at time = 6.227836574620975\n\n\n\n\n9\n\n\n0.1\n\n\n2\n\n\n-2698580492431892402L\n\n\nsetting R0 to 1.8972135699711736 at time = 7.764180353240558\n\n\n\n\n8\n\n\n0.05\n\n\n2\n\n\n-2698580492431892402L\n\n\nsetting R0 to 1.9665140789775497 at time = 4.990978097055602\n\n\n\n\n8\n\n\n0.05\n\n\n2\n\n\n-2698580492431892402L\n\n\nsetting R0 to 1.9525439902956225 at time = 6.227836574620975\n\n\n\n\n8\n\n\n0.05\n\n\n2\n\n\n-2698580492431892402L\n\n\nsetting R0 to 1.8972135699711736 at time = 7.764180353240558\n\n\n\n\n10\n\n\n0.15\n\n\n2\n\n\n-2698580492431892402L\n\n\nsetting R0 to 1.9665140789775497 at time = 4.990978097055602\n\n\n\n\n10\n\n\n0.15\n\n\n2\n\n\n-2698580492431892402L\n\n\nsetting R0 to 1.9525439902956225 at time = 6.227836574620975\n\n\n\n\n10\n\n\n0.15\n\n\n2\n\n\n-2698580492431892402L\n\n\nsetting R0 to 1.8972135699711736 at time = 7.764180353240558\n\n\n\n\n11\n\n\n0.2\n\n\n2\n\n\n-2698580492431892402L\n\n\nsetting R0 to 1.9665140789775497 at time = 4.990978097055602\n\n\n\n\n11\n\n\n0.2\n\n\n2\n\n\n-2698580492431892402L\n\n\nsetting R0 to 1.9525439902956225 at time = 6.227836574620975\n\n\n\n\n11\n\n\n0.2\n\n\n2\n\n\n-2698580492431892402L\n\n\nsetting R0 to 1.8972135699711736 at time = 7.764180353240558" + }, + { + "objectID": "ch04-ReportsPlugin.html#plugin-data-initialization", + "href": "ch04-ReportsPlugin.html#plugin-data-initialization", + "title": "4  Reports Plugin", + "section": "4.1 Plugin Data Initialization", + "text": "4.1 Plugin Data Initialization\nThere is no plugin initialization data class. Reports are contributed by other plugins via their initializers." + }, + { + "objectID": "ch04-ReportsPlugin.html#plugin-behavior", + "href": "ch04-ReportsPlugin.html#plugin-behavior", + "title": "4  Reports Plugin", + "section": "4.2 Plugin Behavior", + "text": "4.2 Plugin Behavior\nThe Plugin contains no data managers, actors or reports." + }, + { + "objectID": "ch04-ReportsPlugin.html#experiment-context-consumer", + "href": "ch04-ReportsPlugin.html#experiment-context-consumer", + "title": "4  Reports Plugin", + "section": "4.3 Experiment Context Consumer", + "text": "4.3 Experiment Context Consumer\nSo far we have seen that reports tend to be produced by specialized classes and that those classes can be added to the simulation via the plugin initialization data. This covers the production and release of the report items from each simulation instance but not what happens to the report items afterward. The output files that receive the report items must work with multiple threads. We manage this with a threadsafe experiment context consumer, the NIOReportItemHandler, that is added to the experiment. The NIOReportItemHandler is created via a builder pattern that allows the modeler to associate report ids to file paths." + }, + { + "objectID": "ch04-ReportsPlugin.html#example-reports-lesson-12", + "href": "ch04-ReportsPlugin.html#example-reports-lesson-12", + "title": "4  Reports Plugin", + "section": "4.4 Example Reports (Lesson 12)", + "text": "4.4 Example Reports (Lesson 12)\nWe reach back to the previous lessons where we introduced plugins for people, families and vaccines for a demonstration of reports. The reports will center on the vaccination of families in various forms and are implemented by three dedicated report classes in the vaccine plugin:\n\nFamilyVaccineReport – Immediate reporting based on observed events\nHourlyVaccineReport – Hourly reporting based on observed events\nStatelessVaccineReport – Hourly reports based on inspection of current state" + }, + { + "objectID": "ch04-ReportsPlugin.html#general-setup", + "href": "ch04-ReportsPlugin.html#general-setup", + "title": "4  Reports Plugin", + "section": "4.5 General Setup", + "text": "4.5 General Setup\nThis example uses the following plugins:\n\nPerson Plugin – provides containment for person identifiers\nStochastics Plugin – (GCM plugin) provides random number generation\nReports Plugin – (GCM plugin) provides reporting mechanisms\nFamily Plugin – defines families and associates people with families\nVaccine Plugin – maintains vaccine assignments with people and families and defines the three reports\nModel Plugin – provides an actor for loading the initial population and an actor for scheduling vaccinations\n\nThe general flow of action in the simulation is that the PopulationLoader actor will add people and families to the simulation based on the initial plugin data provided in the family plugin.  The VaccineScheduler actor will then schedule people at random times to be vaccinated. As people and families are created, people join families and people are vaccinated, the various data mangers will generate the relevant events for observation by the three reports.  The reports will observe these events and correspondingly generate report items that will flow out of the simulation into the experiment level report mechanisms that will result in report files being written.\nLet’s examine Example_12. In Code Block 4.1 we see that the plugins are generated with the person, vaccine and model plugins requiring no input data. The stochastics plugin is generated with a fixed seed value.  Next, the family plugin is created with initial data specifying that 30 families will be created and that each family will have a random number of members up to 5 people.\n\n\nCode Block 4.1: Initialization of the various plugins.\npublic static void main(String[] args) throws IOException {\n if (args.length == 0) {\n throw new RuntimeException(\"One output directory argument is required\");\n }\n Path outputDirectory = Paths.get(args[0]);\n if (!Files.exists(outputDirectory)) {\n Files.createDirectory(outputDirectory);\n } else {\n if (!Files.isDirectory(outputDirectory)) {\n throw new IOException(\"Provided path is not a directory\");\n }\n }\n\n Plugin personPlugin = PersonPlugin.getPersonPlugin();\n\n Plugin vaccinePlugin = VaccinePlugin.getVaccinePlugin();\n\n Plugin modelPlugin = ModelPlugin.getModelPlugin();\n\n WellState wellState = WellState.builder().setSeed(452363456L).build();\n StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder().setMainRNGState(wellState)\n .build();\n Plugin stochasticsPlugin = StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData);\n\n FamilyPluginData familyPluginData = FamilyPluginData.builder()//\n .setFamilyCount(30)//\n .setMaxFamilySize(5)//\n .build();\n Plugin familyPlugin = FamilyPlugin.getFamilyPlugin(familyPluginData);\n\n\nCode Block 4.2 continues with the association of report labels with specific report files. Recall that reports generally use a unique report label and mark each report item with that label. The three reports are added to the simulation by the vaccine plugin.\n\n\nCode Block 4.2: The three reports in this experiment each produce report items and release them as output. The NIOReportItemHandler is initialized here by indicating the file associated with each report.\nNIOReportItemHandler nioReportItemHandler = NIOReportItemHandler.builder()//\n .addReport(ModelLabel.FAMILY_VACCINE_REPORT, outputDirectory.resolve(\"family_vaccine_report.xls\"))//\n .addReport(ModelLabel.HOURLY_VACCINE_REPORT, outputDirectory.resolve(\"hourly_vaccine_report.xls\"))//\n .addReport(ModelLabel.STATELESS_VACCINE_REPORT, outputDirectory.resolve(\"stateless_vaccine_report.xls\"))//\n .build();\n\n\nEach report label is now associated with a particular file path. Although each file is a tab-delimited text file, we use the .xls file extension so that they can be automatically opened as a spreadsheet. Had we skipped adding these last specifications, the report items would flow out of the simulation and into the experiment but would not find an associated file and thus be ignored.\nFinally, in Code Block 4.3 and Code Block 4.4, we create a single experiment dimension that will override the maximum family size with four values and thus create four scenarios for the experiment.\n\n\nCode Block 4.3: The experiment is executed using the NIOReportItemHandler as an experiment output consumer.\nDimension familySizeDimension = getFamilySizeDimension();\n\nExperiment.builder()//\n .addPlugin(vaccinePlugin)//\n .addPlugin(familyPlugin)//\n .addPlugin(personPlugin)//\n .addPlugin(modelPlugin)//\n .addPlugin(stochasticsPlugin)//\n .addDimension(familySizeDimension)//\n .addExperimentContextConsumer(nioReportItemHandler)//\n .build()//\n .execute();\n\n\n\n\nCode Block 4.4: The family dimension set the maximum family size to four values.\nprivate static Dimension getFamilySizeDimension() {\n FunctionalDimension.Builder builder = FunctionalDimension.builder();//\n\n List maxFamilySizes = new ArrayList<>();\n\n maxFamilySizes.add(3);\n maxFamilySizes.add(5);\n maxFamilySizes.add(7);\n maxFamilySizes.add(10);\n\n for (Integer maxFamilySize : maxFamilySizes) {\n builder.addLevel((context) -> {\n FamilyPluginData.Builder pluginDataBuilder = context\n .getPluginDataBuilder(FamilyPluginData.Builder.class);\n pluginDataBuilder.setMaxFamilySize(maxFamilySize);\n\n ArrayList result = new ArrayList<>();\n result.add(Double.toString(maxFamilySize));\n\n return result;\n });//\n }\n\n builder.addMetaDatum(\"max_family_size\");//\n\n return builder.build();\n\n}" + }, + { + "objectID": "ch04-ReportsPlugin.html#the-family-vaccine-report", + "href": "ch04-ReportsPlugin.html#the-family-vaccine-report", + "title": "4  Reports Plugin", + "section": "4.6 The Family Vaccine Report", + "text": "4.6 The Family Vaccine Report\nThe first report documents the changes in the number of families that are vaccinated over time as individual people receive the vaccine. The field headers for the report are:\n\nscenario – the id of the scenario\nmax_family_size – the maximum family size dictated by the scenario\ntime – the time in days for each item in the report\nunvacinated_families – the number of families that have no members vaccinated\npartially_vaccinated_families – the number of families that have at least one, but not all members vaccinated\nfully_vaccinated_families – the number of families that have all members vaccinated\nunvaccinated_individuals – the number of people who are unvaccinated and have no family assignment\nvaccinated_individuals – the number of people who are vaccinated and have no family assignment\n\nThe experiment report mechanisms are responsible for reporting the scenario and the max_family_size fields since they are part of the experiment design. The remaining fields are contributed by the report. Note that family membership is not guaranteed and that some people may not be associated with any family id. The report accounts for these people in the last two fields.\nThere are four events that drive the report:\n\nthe addition of a person to the simulation\nthe addition of a family to the simulation\nthe assignment of a person to a family\nthe vaccination of a person\n\nNote that the model logic does not allow for the removal of a person from the simulation, the removal of person from a family or loss of vaccination coverage for a person. In a more nuanced model, there would likely be more events that would influence the report.\nThe FamilyVaccineReport has several private fields and classes for maintaining the five counts of the reports. In Code Block 4.5 we have two convenience enumerations for families and individuals that help with the creation of the report header and with maintaining counts.\n\n\nCode Block 4.5: The family vaccine report defines two enums for the vaccination status of families and individuals.\nprivate static enum FamilyVaccineStatus {\n NONE(\"unvacinated_families\"), //\n PARTIAL(\"partially_vaccinated_families\"), //\n FULL(\"fully_vaccinated_families\");//\n\n private final String description;\n\n private FamilyVaccineStatus(final String description) {\n this.description = description;\n }\n}\n\nprivate static enum IndividualVaccineStatus {\n NONE(\"unvaccinated_individuals\"), //\n FULL(\"vaccinated_individuals\");//\n\n private final String description;\n\n private IndividualVaccineStatus(final String description) {\n this.description = description;\n }\n}\n\n\nCode Block 4.6 shows the remaining private fields.\n\nreport id – remains fixed from construction and is used to mark every report item\nreportHeader – is constructed once and used in the construction of every report item\nactorContext – a convenience reference kept by the actor to retrieve the simulation time\nvaccinationDataManager – a convenience reference to retrieve the vaccination status of each person\nfamilyDataManager – a convenience reference to retrieve the family members associated with a given person who has just been vaccinated\nstatusToFamiliesMap – a map from family vaccine status to a mutable counter\nfamilyToStatusMap – a map for recording the current family vaccine status for each family\nstatusToIndividualMap – a map from individual vaccine status to a mutable counter\nindividualToStatusMap – a map for recording the current individual vaccine status for each person not assigned to a family\n\n\n\nCode Block 4.6: The family vaccine report collects summary data as events unfold and requires a few private data structures to record these events.\nprivate final ReportLabel reportLabel;\n\nprivate ReportHeader reportHeader;\n\nprivate ReportContext reportContext;\n\nprivate VaccinationDataManager vaccinationDataManager;\n\nprivate FamilyDataManager familyDataManager;\n\nprivate final Map statusToFamiliesMap = new LinkedHashMap<>();\n\nprivate final Map familyToStatusMap = new LinkedHashMap<>();\n\nprivate final Map statusToIndividualsMap = new LinkedHashMap<>();\n\nprivate final Map individualToStatusMap = new LinkedHashMap<>();\n\n\nThe report’s methods start with its constructor in Code Block 4.7. The report label is recorded and the report header field is built from the support enumerations.\n\n\nCode Block 4.7: The report initializes its data structures.\npublic FamilyVaccineReport(final ReportLabel reportLabel) {\n this.reportLabel = reportLabel;\n\n final ReportHeader.Builder builder = ReportHeader.builder();\n builder.add(\"time\");\n for (final FamilyVaccineStatus familyVaccineStatus : FamilyVaccineStatus.values()) {\n builder.add(familyVaccineStatus.description);\n }\n for (final IndividualVaccineStatus individualVaccineStatus : IndividualVaccineStatus.values()) {\n builder.add(individualVaccineStatus.description);\n }\n reportHeader = builder.build();\n}\n\n\nNext is the initialization method that was passed to the simulation. This is invoked by the simulation just once at the begining of time flow and gives the report a chance to register for events and to initialize the private fields from Code Block 4.6. The report records the actor context and subscribes to the four events of interest in Code Block 4.8. These subscriptions reference local private methods that will be discussed later.\n\n\nCode Block 4.8: The report must subscribe to the events that are pertinent to reporting individual and family vaccination status.\npublic void init(final ReportContext reportContext) {\n this.reportContext = reportContext;\n /*\n * Subscribe to all the relevant events\n */\n reportContext.subscribe(VaccinationEvent.class, this::handleVaccinationEvent);\n reportContext.subscribe(FamilyAdditionEvent.class, this::handleFamilyAdditionEvent);\n reportContext.subscribe(FamilyMemberShipAdditionEvent.class, this::handleFamilyMemberShipAdditionEvent);\n reportContext.subscribe(PersonAdditionEvent.class, this::handlePersonAdditionEvent);\n\n\nIn Code Block 4.9 we continue with the retrieval of the person, family and vaccination data managers. The maps containing the counts are initialized to zero.\n\n\nCode Block 4.9: The local data structures are initialized from the current vaccine states.\nfamilyDataManager = reportContext.getDataManager(FamilyDataManager.class);\nvaccinationDataManager = reportContext.getDataManager(VaccinationDataManager.class);\nPersonDataManager personDataManager = reportContext.getDataManager(PersonDataManager.class);\n\nfor (final FamilyVaccineStatus familyVaccineStatus : FamilyVaccineStatus.values()) {\n statusToFamiliesMap.put(familyVaccineStatus, new MutableInteger());\n}\n\nfor (final IndividualVaccineStatus individualVaccineStatus : IndividualVaccineStatus.values()) {\n statusToIndividualsMap.put(individualVaccineStatus, new MutableInteger());\n}\n\n\nCode Block 4.10 and Code Block 4.11 use the data managers to fill the count structures with the current state of the population.\n\n\nCode Block 4.10: Determining vaccine status for each family.\nfor (final FamilyId familyId : familyDataManager.getFamilyIds()) {\n\n final int familySize = familyDataManager.getFamilySize(familyId);\n final List familyMembers = familyDataManager.getFamilyMembers(familyId);\n int vaccinatedCount = 0;\n for (final PersonId personId : familyMembers) {\n if (vaccinationDataManager.isPersonVaccinated(personId)) {\n vaccinatedCount++;\n }\n }\n FamilyVaccineStatus status;\n\n if (vaccinatedCount == 0) {\n status = FamilyVaccineStatus.NONE;\n } else if (vaccinatedCount == familySize) {\n status = FamilyVaccineStatus.FULL;\n } else {\n status = FamilyVaccineStatus.PARTIAL;\n }\n\n statusToFamiliesMap.get(status).increment();\n familyToStatusMap.put(familyId, status);\n\n}\n\n\n\n\nCode Block 4.11: Capturing individuals who have no family association.\nfor (final PersonId personId : personDataManager.getPeople()) {\n if (familyDataManager.getFamilyId(personId).isEmpty()) {\n\n IndividualVaccineStatus status;\n if (vaccinationDataManager.isPersonVaccinated(personId)) {\n status = IndividualVaccineStatus.FULL;\n } else {\n status = IndividualVaccineStatus.NONE;\n }\n statusToIndividualsMap.get(status).increment();\n individualToStatusMap.put(personId, status);\n }\n}\n\n\nInitialization finishes with the release of a single report item that summarizes the state of family vaccination at time zero.\n\n\nCode Block 4.12: The initial state of the report is released as a single report item.\nreleaseReportItem();\n\n\nThe methods for handling each event are shown in Code Block 4.13. All four methods select some relevant family id or person id and process changes to the counting data structures using the refreshFamilyStatus() and refreshInidividual() methods. The accounting for reports that are synthesizing multiple events can be somewhat tricky. No assumptions are made as to how people are created, vaccinated and added to families so that changes to those processes in future versions of the model do not cause errors in the report.\n\n\nCode Block 4.13: The report will need to have handlers for each of the subscribed events.\nprivate void handleFamilyAdditionEvent(final ReportContext reportContext,\n final FamilyAdditionEvent familyAdditionEvent) {\n refreshFamilyStatus(familyAdditionEvent.getFamilyId());\n}\n\nprivate void handleFamilyMemberShipAdditionEvent(final ReportContext reportContext,\n final FamilyMemberShipAdditionEvent familyMemberShipAdditionEvent) {\n individualToStatusMap.remove(familyMemberShipAdditionEvent.getPersonId());\n refreshFamilyStatus(familyMemberShipAdditionEvent.getFamilyId());\n}\n\nprivate void handlePersonAdditionEvent(final ReportContext reportContext,\n final PersonAdditionEvent personAdditionEvent) {\n final PersonId personId = personAdditionEvent.getPersonId();\n final Optional optional = familyDataManager.getFamilyId(personId);\n if (optional.isEmpty()) {\n refreshIndividualStatus(personId);\n } else {\n final FamilyId familyId = optional.get();\n refreshFamilyStatus(familyId);\n }\n}\n\nprivate void handleVaccinationEvent(final ReportContext reportContext, final VaccinationEvent vaccinationEvent) {\n final PersonId personId = vaccinationEvent.getPersonId();\n\n final Optional optional = familyDataManager.getFamilyId(personId);\n\n if (optional.isEmpty()) {\n refreshIndividualStatus(personId);\n } else {\n final FamilyId familyId = optional.get();\n refreshFamilyStatus(familyId);\n }\n}\n\n\nThe refresh methods in Code Block 4.14 and Code Block 4.15 compare the current vaccination state of the families and individuals against the corresponding states tracked in the counting maps. If a change in the counts has occurred the counts are corrected and a new report item is released.\n\n\nCode Block 4.14: Events that effect the status of a family are processed centrally.\nprivate void refreshFamilyStatus(final FamilyId familyId) {\n\n final int familySize = familyDataManager.getFamilySize(familyId);\n final List familyMembers = familyDataManager.getFamilyMembers(familyId);\n int vaccinatedCount = 0;\n for (final PersonId personId : familyMembers) {\n if (vaccinationDataManager.isPersonVaccinated(personId)) {\n vaccinatedCount++;\n }\n }\n FamilyVaccineStatus newStatus;\n\n if (vaccinatedCount == 0) {\n newStatus = FamilyVaccineStatus.NONE;\n } else if (vaccinatedCount == familySize) {\n newStatus = FamilyVaccineStatus.FULL;\n } else {\n newStatus = FamilyVaccineStatus.PARTIAL;\n }\n\n final FamilyVaccineStatus currentStatus = familyToStatusMap.get(familyId);\n if (currentStatus == newStatus) {\n return;\n }\n if (currentStatus != null) {\n statusToFamiliesMap.get(currentStatus).decrement();\n }\n statusToFamiliesMap.get(newStatus).increment();\n familyToStatusMap.put(familyId, newStatus);\n releaseReportItem();\n}\n\n\n\n\nCode Block 4.15: Events that effect the status of an individual are processed centrally.\nprivate void refreshIndividualStatus(final PersonId personId) {\n IndividualVaccineStatus newStatus;\n if (vaccinationDataManager.isPersonVaccinated(personId)) {\n newStatus = IndividualVaccineStatus.FULL;\n } else {\n newStatus = IndividualVaccineStatus.NONE;\n }\n\n final IndividualVaccineStatus currentStatus = individualToStatusMap.get(personId);\n\n if (currentStatus == newStatus) {\n return;\n }\n\n if (currentStatus != null) {\n statusToIndividualsMap.get(currentStatus).decrement();\n }\n statusToIndividualsMap.get(newStatus).increment();\n individualToStatusMap.put(personId, newStatus);\n releaseReportItem();\n}\n\n\nReleasing the report items that summarizes the family vaccination counts requires building a new report item with the fixed report label and report header values determined in the constructor. We then go on to add the time and count values in the order dictated by the helper enumerations so that they follow the header values established in the report header. Once the report item is complete it is released as output via the report context. The simulation will in turn release the report item to the experiment where it will be distributed to the NIOReportItemHandler and then on the specific file manager(s) that record the items.\n\n\nCode Block 4.16: Each time a family or individual have a relevant change a report item is released.\nprivate void releaseReportItem() {\n final ReportItem.Builder builder = ReportItem.builder().setReportLabel(reportLabel)\n .setReportHeader(reportHeader);\n builder.addValue(reportContext.getTime());\n for (final FamilyVaccineStatus familyVaccineStatus : statusToFamiliesMap.keySet()) {\n MutableInteger mutableInteger = statusToFamiliesMap.get(familyVaccineStatus);\n builder.addValue(mutableInteger.getValue());\n }\n for (final IndividualVaccineStatus individualVaccineStatus : statusToIndividualsMap.keySet()) {\n MutableInteger mutableInteger = statusToIndividualsMap.get(individualVaccineStatus);\n builder.addValue(mutableInteger.getValue());\n }\n final ReportItem reportItem = builder.build();\n reportContext.releaseOutput(reportItem);\n}\n\n\nThe resulting output in Figure 4.1 contains the four scenarios showing the buildup of the population with all families and individuals being unvaccinated. Over time the number of vaccinated families increase and each simulation ends when all people have been vaccinated. The increase of max family size over the experiment causes there to be more people and thus the number of days to reach full vaccination also increases as expected.\n\n\n\nFigure 4.1: Excerpt of the family vaccine report.\n\n\n\n\n\n\nscenario\n\n\nmax_family_size\n\n\ntime\n\n\nunvacinated_families\n\n\npartially_vaccinated_families\n\n\nfully_vaccinated_families\n\n\nunvaccinated_individuals\n\n\nvaccinated_individuals\n\n\n\n\n\n\n0\n\n\n3.0\n\n\n0.0\n\n\n0\n\n\n0\n\n\n0\n\n\n0\n\n\n0\n\n\n\n\n0\n\n\n3.0\n\n\n0.0\n\n\n1\n\n\n0\n\n\n0\n\n\n0\n\n\n0\n\n\n\n\n0\n\n\n3.0\n\n\n0.0\n\n\n1\n\n\n0\n\n\n0\n\n\n1\n\n\n0\n\n\n\n\n0\n\n\n3.0\n\n\n0.0\n\n\n1\n\n\n0\n\n\n0\n\n\n2\n\n\n0\n\n\n\n\n0\n\n\n3.0\n\n\n0.0\n\n\n2\n\n\n0\n\n\n0\n\n\n2\n\n\n0\n\n\n\n\n…\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n0\n\n\n3.0\n\n\n0.0\n\n\n15\n\n\n0\n\n\n0\n\n\n30\n\n\n0\n\n\n\n\n0\n\n\n3.0\n\n\n0.0\n\n\n16\n\n\n0\n\n\n0\n\n\n30\n\n\n0\n\n\n\n\n0\n\n\n3.0\n\n\n0.0\n\n\n16\n\n\n0\n\n\n0\n\n\n31\n\n\n0\n\n\n\n\n0\n\n\n3.0\n\n\n0.0\n\n\n16\n\n\n0\n\n\n0\n\n\n32\n\n\n0\n\n\n\n\n0\n\n\n3.0\n\n\n0.0\n\n\n17\n\n\n0\n\n\n0\n\n\n32\n\n\n0\n\n\n\n\n…\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n0\n\n\n3.0\n\n\n1.0603090039611633\n\n\n28\n\n\n2\n\n\n0\n\n\n63\n\n\n0\n\n\n\n\n0\n\n\n3.0\n\n\n1.114413651330966\n\n\n27\n\n\n3\n\n\n0\n\n\n63\n\n\n0\n\n\n\n\n0\n\n\n3.0\n\n\n1.1516487861564502\n\n\n26\n\n\n4\n\n\n0\n\n\n63\n\n\n0\n\n\n\n\n0\n\n\n3.0\n\n\n1.1871612468129367\n\n\n25\n\n\n5\n\n\n0\n\n\n63\n\n\n0\n\n\n\n\n0\n\n\n3.0\n\n\n1.2426374003057261\n\n\n25\n\n\n4\n\n\n1\n\n\n63\n\n\n0\n\n\n\n\n…\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n3\n\n\n10.0\n\n\n8.981789652547315\n\n\n0\n\n\n4\n\n\n26\n\n\n163\n\n\n3\n\n\n\n\n3\n\n\n10.0\n\n\n9.047774924066472\n\n\n0\n\n\n3\n\n\n27\n\n\n163\n\n\n3\n\n\n\n\n3\n\n\n10.0\n\n\n9.101648563188967\n\n\n0\n\n\n2\n\n\n28\n\n\n163\n\n\n3\n\n\n\n\n3\n\n\n10.0\n\n\n9.18260688675053\n\n\n0\n\n\n1\n\n\n29\n\n\n163\n\n\n3\n\n\n\n\n3\n\n\n10.0\n\n\n9.210064962863902\n\n\n0\n\n\n0\n\n\n30\n\n\n163\n\n\n3" + }, + { + "objectID": "ch04-ReportsPlugin.html#periodic-reports", + "href": "ch04-ReportsPlugin.html#periodic-reports", + "title": "4  Reports Plugin", + "section": "4.7 Periodic Reports", + "text": "4.7 Periodic Reports\nProducing a new report item each time a relevant event changes the internal tracking variable of a report actor will often produce too much output. An alternative is to periodically release one or more report items, usually on an hourly or daily basis. The reports plugin defines an abstract report class, the PeriodicReport, that manages the periodic flushing of the state of the report. This allows descendant report classes to concentrate on responding to events while leaving the periodic production of report items to the base class.\nThe PeriodicReport defines a constructor that requires both a report label and a reporting period. If the constructor is overridden, the super() constructor must be invoked. The init() method is declared final in the PeriodicReport class and the descendant report class should implement the prepare() method to conduct initialization. Several protected methods are introduced:\n\nprepare is called by the init() method of the periodic report, it provides the descendant report class with an opportunity to initialize\ngetReportLabel and getReportPeriod retrieve the label and report period passed in construction\naddTimeFieldHeaders is used to help create the report header\nfillTimeFields is used to help create report items\nflush is an abstract method for flushing the content of the report actor that must be implemented by the descendant report class\n\nOur next example report is the HourlyVaccineReport that descends from the PeriodicReport. It produces the same output as the FamilyVaccineReport, but does so on an hourly basis. This outputs a report item every hour whether or not there were stimulating events. The implementation of this report is nearly identical to the previous report and we will concentrate on highlighting the differences between the two approaches.\nIn Code Block 4.17 we see that the constructor invokes the super constructor. The construction of the report header is aided by the protected method addTimeFieldHeaders() which should be invoked as the first inputs to the report header builder. Note as well that we do not store the report label locally.\n\n\nCode Block 4.17: The hourly vaccine report covers the same content as the family vaccine report. Rather than report events as they happen, it instead periodically summarizes these events.\npublic HourlyVaccineReport(ReportLabel reportLabel, ReportPeriod reportPeriod) {\n super(reportLabel, reportPeriod);\n\n ReportHeader.Builder builder = ReportHeader.builder();\n addTimeFieldHeaders(builder);\n for (FamilyVaccineStatus familyVaccineStatus : FamilyVaccineStatus.values()) {\n builder.add(familyVaccineStatus.description);\n }\n for (IndividualVaccineStatus individualVaccineStatus : IndividualVaccineStatus.values()) {\n builder.add(individualVaccineStatus.description);\n }\n reportHeader = builder.build();\n}\n\n\nThe prepare() method is nearly identical to the previous report’s init() method.\n\n\nCode Block 4.18: The same subscriptions are created as before.\nprotected void prepare(ReportContext reportContext) {\n\n /*\n * Subscribe to all the relevant events\n */ \n\n reportContext.subscribe(VaccinationEvent.class, this::handleVaccinationEvent);\n reportContext.subscribe(FamilyAdditionEvent.class, this::handleFamilyAdditionEvent);\n reportContext.subscribe(FamilyMemberShipAdditionEvent.class, this::handleFamilyMemberShipAdditionEvent);\n reportContext.subscribe(PersonAdditionEvent.class, this::handlePersonAdditionEvent);\n\n\nThe releaseReportItem() method of the previous report is now replaced by the flush() method override in Code Block 4.19.\n\n\nCode Block 4.19: Once an hour the report releases a report item that summarizes the family and individual vaccine status.\nprotected void flush(ReportContext reportContext) {\n ReportItem.Builder builder = ReportItem.builder()//\n .setReportLabel(getReportLabel())//\n .setReportHeader(reportHeader);\n fillTimeFields(builder);\n for (FamilyVaccineStatus familyVaccineStatus : statusToFamiliesMap.keySet()) {\n MutableInteger mutableInteger = statusToFamiliesMap.get(familyVaccineStatus);\n builder.addValue(mutableInteger.getValue());\n }\n for (IndividualVaccineStatus individualVaccineStatus : statusToIndividualsMap.keySet()) {\n MutableInteger mutableInteger = statusToIndividualsMap.get(individualVaccineStatus);\n builder.addValue(mutableInteger.getValue());\n }\n ReportItem reportItem = builder.build();\n reportContext.releaseOutput(reportItem);\n}\n\n\nThe corresponding invocations of the releaseReportItem() that would have generated a new report item each time an event changed the internal counting variables are dropped. The flush() method will be invoked each time the parent report class determines that the planned next period has occurred. Note also that the time fields of the report item are filled by invoking the fillTimeFields() method which will add the correct time value for the period being reported rather than the current time. Otherwise, the implementations are identical.\nThe resulting output in Figure 4.2 contains the four scenarios showing the buildup of the population with all families and individuals being unvaccinated. It shows the same overall pattern as the previous report, but treats the reporting of time in integer days and hours. Note that some of the output values repeat over the days and hours since there were no vaccinations during those periods.\n\n\n\nFigure 4.2: Excerpt from the hourly vaccine report.\n\n\n\n\n\n\nscenario\n\n\nmax_family_size\n\n\nday\n\n\nhour\n\n\nunvacinated_families\n\n\npartially_vaccinated_families\n\n\nfully_vaccinated_families\n\n\nunvaccinated_individuals\n\n\nvaccinated_individuals\n\n\n\n\n\n\n0\n\n\n3.0\n\n\n0\n\n\n0\n\n\n0\n\n\n0\n\n\n0\n\n\n0\n\n\n0\n\n\n\n\n0\n\n\n3.0\n\n\n0\n\n\n1\n\n\n30\n\n\n0\n\n\n0\n\n\n63\n\n\n0\n\n\n\n\n0\n\n\n3.0\n\n\n0\n\n\n2\n\n\n30\n\n\n0\n\n\n0\n\n\n63\n\n\n0\n\n\n\n\n0\n\n\n3.0\n\n\n0\n\n\n3\n\n\n30\n\n\n0\n\n\n0\n\n\n63\n\n\n0\n\n\n\n\n0\n\n\n3.0\n\n\n0\n\n\n4\n\n\n30\n\n\n0\n\n\n0\n\n\n63\n\n\n0\n\n\n\n\n…\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n0\n\n\n3.0\n\n\n2\n\n\n11\n\n\n9\n\n\n17\n\n\n4\n\n\n62\n\n\n1\n\n\n\n\n0\n\n\n3.0\n\n\n2\n\n\n12\n\n\n9\n\n\n17\n\n\n4\n\n\n62\n\n\n1\n\n\n\n\n0\n\n\n3.0\n\n\n2\n\n\n13\n\n\n8\n\n\n17\n\n\n5\n\n\n62\n\n\n1\n\n\n\n\n0\n\n\n3.0\n\n\n2\n\n\n14\n\n\n8\n\n\n17\n\n\n5\n\n\n62\n\n\n1\n\n\n\n\n0\n\n\n3.0\n\n\n2\n\n\n15\n\n\n8\n\n\n17\n\n\n5\n\n\n62\n\n\n1\n\n\n\n\n…\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n2\n\n\n7.0\n\n\n3\n\n\n3\n\n\n8\n\n\n21\n\n\n1\n\n\n128\n\n\n1\n\n\n\n\n2\n\n\n7.0\n\n\n3\n\n\n4\n\n\n8\n\n\n21\n\n\n1\n\n\n128\n\n\n1\n\n\n\n\n2\n\n\n7.0\n\n\n3\n\n\n5\n\n\n8\n\n\n21\n\n\n1\n\n\n128\n\n\n1\n\n\n\n\n2\n\n\n7.0\n\n\n3\n\n\n6\n\n\n8\n\n\n21\n\n\n1\n\n\n128\n\n\n1\n\n\n\n\n2\n\n\n7.0\n\n\n3\n\n\n7\n\n\n8\n\n\n21\n\n\n1\n\n\n128\n\n\n1\n\n\n\n\n…\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n3\n\n\n10.0\n\n\n9\n\n\n2\n\n\n0\n\n\n3\n\n\n27\n\n\n163\n\n\n3\n\n\n\n\n3\n\n\n10.0\n\n\n9\n\n\n3\n\n\n0\n\n\n2\n\n\n28\n\n\n163\n\n\n3\n\n\n\n\n3\n\n\n10.0\n\n\n9\n\n\n4\n\n\n0\n\n\n2\n\n\n28\n\n\n163\n\n\n3\n\n\n\n\n3\n\n\n10.0\n\n\n9\n\n\n5\n\n\n0\n\n\n1\n\n\n29\n\n\n163\n\n\n3\n\n\n\n\n3\n\n\n10.0\n\n\n9\n\n\n6\n\n\n0\n\n\n0\n\n\n30\n\n\n163\n\n\n3\n\n\n\n\n\n\n\n\n\nOur final example, the StatelessVaccineReport , Code Block 4.20, continues from the HourlyVaccineReport but eschews the stateful counting mechanisms. Like the previous report, it is a periodic report actor but it does not store any state and does not subscribe to any events. Instead, it simply derives the report item on each flush() invocation.\n\n\nCode Block 4.20: The stateless vaccine report does not process any events. Instead, it periodically derives the report item by polling the relevant data managers.\nprotected void flush(ReportContext reportContext) {\n\n FamilyDataManager familyDataManager = reportContext.getDataManager(FamilyDataManager.class);\n VaccinationDataManager vaccinationDataManager = reportContext.getDataManager(VaccinationDataManager.class);\n PersonDataManager personDataManager = reportContext.getDataManager(PersonDataManager.class);\n\n Map statusMap = new LinkedHashMap<>();\n for (VaccineStatus vaccineStatus : VaccineStatus.values()) {\n statusMap.put(vaccineStatus, new MutableInteger());\n }\n\n // determine the family vaccine status for every family\n for (FamilyId familyId : familyDataManager.getFamilyIds()) {\n VaccineStatus vaccineStatus = getFamilyStatus(familyId, vaccinationDataManager, familyDataManager);\n statusMap.get(vaccineStatus).increment();\n }\n\n // ensure that any person not assigned to a family is still counted\n for (PersonId personId : personDataManager.getPeople()) {\n if (familyDataManager.getFamilyId(personId).isEmpty()) {\n VaccineStatus vaccineStatus = getIndividualStatus(personId, vaccinationDataManager);\n statusMap.get(vaccineStatus).increment();\n }\n }\n ReportHeader.Builder headerBuilder = ReportHeader.builder();\n addTimeFieldHeaders(headerBuilder);\n for (VaccineStatus vaccineStatus : VaccineStatus.values()) {\n headerBuilder.add(vaccineStatus.description);\n }\n ReportHeader reportHeader = headerBuilder.build();\n\n ReportItem.Builder builder = ReportItem.builder()//\n .setReportLabel(getReportLabel())//\n .setReportHeader(reportHeader);\n fillTimeFields(builder);\n for (VaccineStatus vaccineStatus : VaccineStatus.values()) {\n int value = statusMap.get(vaccineStatus).getValue();\n builder.addValue(value);\n }\n\n ReportItem reportItem = builder.build();\n reportContext.releaseOutput(reportItem);\n\n}\n\n\nThis approach may seem wasteful since there is the potential for a great deal of recalculation, but since this is done on a daily basis, it may be well worth the reduction in memory if the report was actively tracking millions of families." + }, + { + "objectID": "ch05-Properties.html#property-identifiers", + "href": "ch05-Properties.html#property-identifiers", + "title": "5  Properties", + "section": "5.1 Property Identifiers", + "text": "5.1 Property Identifiers\nProperty Ids are generally marker interfaces used to force unambiguous types in method signatures dealing with property related concepts. Each plugin that uses properties will introduce its own marker interface(s) and instances of the identifier are left to client (other plugins) to implement. This is often accomplished with enumerations." + }, + { + "objectID": "ch05-Properties.html#property-definitions", + "href": "ch05-Properties.html#property-definitions", + "title": "5  Properties", + "section": "5.2 Property Definitions", + "text": "5.2 Property Definitions\nProperty definitions supply each plugin with:\n\nA class reference that defines the type of the property values\nA Boolean value indicating if property values are mutable\nAn optional default property value\n\nThe mutability indicator controls whether property values can be set after the initial value is established. For example, consider the integer property “age” that is defined for people. Each person has a distinct integer age upon initial value assignment. If the property definition asserts that the property is not mutable, then the age value cannot be changed during the simulation’s execution. This is often used to fix global property values so that there is no chance that they can be reset by mistake.\nIt is often useful to know when a property was last assigned. Some plugins will introduce tracking of property value assignment times based on a policy per property id to avoid recording such time values where there would be tens of millions of entries and no use of these values by the modeler.\nDefault property values are used to spare the modeler from having to set property values when introducing new items to the simulation. For example, when adding a person to the simulation it might be useful to have a default of false for the property of “vaccinated”. However, for some properties there may be no meaningful default value. For example, consider the “age” property for a person. What would constitute a good default value? For this reason, supplying a default value as part of the property definition is optional. Property values in GCM are never null. If a property definition does not supply a default value, then all assoicated property values must be explicitly set to non-null values." + }, + { + "objectID": "ch05-Properties.html#concurrency-requirements", + "href": "ch05-Properties.html#concurrency-requirements", + "title": "5  Properties", + "section": "5.3 Concurrency Requirements", + "text": "5.3 Concurrency Requirements\nProperty ids, property definitions and property values must be thread safe since they are shared across multiple scenarios (different simulation instances). It is usually best practice if they are implemented as immutable classes.\n\nProperty ids are usually marker interfaces and are often implemented by static enumerations and are thus generally threadsafe\nThe PropertyDefinition class is provided by the utility and is threadsafe subject to the thread safety of its default value\nProperty values are often boxed primitives and are generally threadsafe. In general, mutation of a property value in GCM does not mean that the property value is mutated. Rather, it usually means that a new immutable value is now associated with the property id." + }, + { + "objectID": "ch05-Properties.html#immutability", + "href": "ch05-Properties.html#immutability", + "title": "5  Properties", + "section": "5.4 Immutability", + "text": "5.4 Immutability\nFor a class to be immutable in Java it must meet three requirements:\n\nIts internal fields must not be mutated. There can be no setter methods or any other mechanism that changes an assignment post construction\nAll fields are declared final\nNo reference to the immutable object may be passed during its construction" + }, + { + "objectID": "ch05-Properties.html#expected-behaviors-of-plugins-using-properties", + "href": "ch05-Properties.html#expected-behaviors-of-plugins-using-properties", + "title": "5  Properties", + "section": "5.5 Expected Behaviors of Plugins using properties", + "text": "5.5 Expected Behaviors of Plugins using properties\nAll implementations of property mechanisms in GCM are expected to meet the following requirements:\n\nProperty values are never null\nProperty definitions that do not supply a default value must be supported by other mechanisms that ensure that property values are never null\nProperty instance values must always be assignment compatible to their corresponding property definition’s property type reference" + }, + { + "objectID": "ch06-GlobalPropertiesPlugin.html#plugin-data-initialization", + "href": "ch06-GlobalPropertiesPlugin.html#plugin-data-initialization", + "title": "6  Global Properties Plugin", + "section": "6.1 Plugin Data Initialization", + "text": "6.1 Plugin Data Initialization\nThe plugin is initialized using a GlobalPropertiesPluginData object that collects global property definitions and global property values. Even though the property definitions can contain default property values, the ability to set property values is included to add some flexibility to the collection process since the client model may separate definitions from values in its input files." + }, + { + "objectID": "ch06-GlobalPropertiesPlugin.html#plugin-behavior", + "href": "ch06-GlobalPropertiesPlugin.html#plugin-behavior", + "title": "6  Global Properties Plugin", + "section": "6.2 Plugin Behavior", + "text": "6.2 Plugin Behavior\nThe plugin adds a single data manager to the simulation as an instance of the GlobalPropertiesDataManager that is initialized with the GlobalPropertiesPluginData." + }, + { + "objectID": "ch06-GlobalPropertiesPlugin.html#data-manager", + "href": "ch06-GlobalPropertiesPlugin.html#data-manager", + "title": "6  Global Properties Plugin", + "section": "6.3 Data Manager", + "text": "6.3 Data Manager\nThe data manager provides access to the global properties and provides the ability to:\n\nDefine new global properties (not contained in the initial data)\nRetrieve global property definitions\nRetrieve global property ids\nRetrieve global property values and the times when they were set\nSet global property values\n\nThe data manager also produces observable events when a new global property is defined or when a global property value is assigned. The plugin provides the GlobalPropertyReport that subscribes to these events and produces a trace report of property value assignments." + }, + { + "objectID": "ch06-GlobalPropertiesPlugin.html#example-code-lesson-13", + "href": "ch06-GlobalPropertiesPlugin.html#example-code-lesson-13", + "title": "6  Global Properties Plugin", + "section": "6.4 Example Code (Lesson 13)", + "text": "6.4 Example Code (Lesson 13)\nExample_13.java shows a simple usage of the global properties plugin. In it we will add three double valued properties: ALPHA, BETA, and GAMMA. ALPHA and BETA will be used to vary the scenarios in the experiment and GAMMA will be set to a simple function of ALPHA and BETA that will change over time in the simulation. This will culminate in a report that shows each time the global variables are defined or their values are set.\nThe example includes two plugins:\n\nGlobal properties plugin – (GCM core plugin) used to manage the properties\nModel plugin – (local plugin) used to introduce a single actor that will alter the value of GAMMA over time\n\nThe example’s main method in Code Block 6.1 adds the two plugins:\n\nGlobal properties plugin\n\ninitialized with the three global properties\nadds the GlobalPropertyReport\n\nModel plugin\n\nUses no inputs, but will add a single instance of the GammaActor class\n\n\nThe main method continues by associating the report to a file via the NIOReportItemHandler. It then forms a dimension for the experiment from variant values of ALPHA and BETA. Finally, it executes the experiment.\n\n\nCode Block 6.1: Using the global properties plugin to add three global properties.\npublic static void main(String[] args) throws IOException {\n if (args.length == 0) {\n throw new RuntimeException(\"One output directory argument is required\");\n }\n Path outputDirectory = Paths.get(args[0]);\n if (!Files.exists(outputDirectory)) {\n Files.createDirectory(outputDirectory);\n } else {\n if (!Files.isDirectory(outputDirectory)) {\n throw new IOException(\"Provided path is not a directory\");\n }\n }\n\n GlobalPropertiesPluginData globalPropertiesPluginData = getGlobalPropertiesPluginData();\n\n GlobalPropertyReportPluginData globalPropertyReportPluginData = GlobalPropertyReportPluginData.builder()//\n .setReportLabel(ModelReportLabel.GLOBAL_PROPERTY_REPORT)//\n .setDefaultInclusion(true)//\n .build();\n\n Plugin globalPropertiesPlugin = GlobalPropertiesPlugin.builder()\n .setGlobalPropertiesPluginData(globalPropertiesPluginData)\n .setGlobalPropertyReportPluginData(globalPropertyReportPluginData)//\n .getGlobalPropertiesPlugin();\n\n Plugin modelPlugin = ModelPlugin.getModelPlugin();\n\n NIOReportItemHandler nioReportItemHandler = //\n NIOReportItemHandler.builder()//\n .addReport(ModelReportLabel.GLOBAL_PROPERTY_REPORT, //\n outputDirectory.resolve(\"global property report.xls\"))//\n .build();\n\n Dimension alphaBetaDimension = getAlphaBetaDimension();\n\n Experiment.builder()//\n .addPlugin(globalPropertiesPlugin)//\n .addPlugin(modelPlugin)//\n .addExperimentContextConsumer(nioReportItemHandler)//\n .addDimension(alphaBetaDimension)//\n .build()//\n .execute();//\n\n}\n\n\nInitialization of the global properties is shown in Code Block 6.2.\n\n\nCode Block 6.2: Initializing the global properties plugin data with three property definitions.\nprivate static GlobalPropertiesPluginData getGlobalPropertiesPluginData() {\n GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder();//\n\n PropertyDefinition propertyDefinition = PropertyDefinition.builder()//\n .setType(Double.class)//\n .setDefaultValue(2.0)//\n .setPropertyValueMutability(false)//\n .build();\n builder.defineGlobalProperty(GlobalProperty.ALPHA, propertyDefinition, 0);\n\n propertyDefinition = PropertyDefinition.builder()//\n .setType(Double.class)//\n .setDefaultValue(5.0)//\n .setPropertyValueMutability(false)//\n .build();\n\n builder.defineGlobalProperty(GlobalProperty.BETA, propertyDefinition, 0);\n\n propertyDefinition = PropertyDefinition.builder()//\n .setType(Double.class)//\n .setDefaultValue(1.0)//\n .setPropertyValueMutability(true)//\n .build();\n\n builder.defineGlobalProperty(GlobalProperty.GAMMA, propertyDefinition, 0);\n\n return builder.build();\n}\n\n\nCode Block 6.3 shows the construction of the experiment’s single dimension.\n\n\nCode Block 6.3: A dimension is created that adds five pairs of values over the ALPHA and BETA global properties.\nprivate static Dimension getAlphaBetaDimension() {\n List> alphaBetaPairs = new ArrayList<>();\n alphaBetaPairs.add(new Pair<>(3.0, 10.0));\n alphaBetaPairs.add(new Pair<>(12.0, 25.0));\n alphaBetaPairs.add(new Pair<>(30.0, 40.0));\n alphaBetaPairs.add(new Pair<>(45.0, 70.0));\n alphaBetaPairs.add(new Pair<>(80.0, 100.0));\n\n FunctionalDimension.Builder dimensionBuilder = FunctionalDimension.builder();\n\n for (Pair pair : alphaBetaPairs) {\n dimensionBuilder.addLevel((c) -> {\n List result = new ArrayList<>();\n GlobalPropertiesPluginData.Builder builder = c\n .getPluginDataBuilder(GlobalPropertiesPluginData.Builder.class);\n builder.setGlobalPropertyValue(GlobalProperty.ALPHA, pair.getFirst(), 0);\n builder.setGlobalPropertyValue(GlobalProperty.BETA, pair.getSecond(), 0);\n result.add(pair.getFirst().toString());\n result.add(pair.getSecond().toString());\n return result;\n });\n }\n\n dimensionBuilder.addMetaDatum(GlobalProperty.ALPHA.toString());\n dimensionBuilder.addMetaDatum(GlobalProperty.BETA.toString());\n\n return dimensionBuilder.build();\n}\n\n\nThe GammaActor class in Code Block 6.4 schedules 10 plans, set one day apart, to change the GAMMA value as a successive interpolation between the ALPHA and BETA values that are in turn controlled by the experiment.\n\n\nCode Block 6.4: The gamma actor sets the value of the GAMMA property over time as a function of the ALPHA and BETA properties.\npublic final class GammaActor {\n\n public void init(ActorContext actorContext) {\n int count = 10;\n IntStream.range(0, count).forEach((i) -> {\n actorContext.addPlan((c) -> {\n GlobalPropertiesDataManager globalPropertiesDataManager = c\n .getDataManager(GlobalPropertiesDataManager.class);\n Double alpha = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.ALPHA);\n Double beta = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.BETA);\n double gamma = (beta - alpha) * i / count + alpha;\n globalPropertiesDataManager.setGlobalPropertyValue(GlobalProperty.GAMMA, gamma);\n }, i + 1);\n });\n }\n}\n\n\nThe resultant global properties report shows the correct interpolated values for the five scenarios in Figure 6.1.\n\n\n\nFigure 6.1: An excerpt of the global property report showing the three global property values over time in the five scenarios.\n\n\n\n\n\n\nscenario\n\n\nALPHA\n\n\nBETA\n\n\ntime\n\n\nproperty\n\n\nvalue\n\n\n\n\n\n\n0\n\n\n3.0\n\n\n10.0\n\n\n0.0\n\n\nALPHA\n\n\n3.0\n\n\n\n\n0\n\n\n3.0\n\n\n10.0\n\n\n0.0\n\n\nBETA\n\n\n10.0\n\n\n\n\n0\n\n\n3.0\n\n\n10.0\n\n\n0.0\n\n\nGAMMA\n\n\n1.0\n\n\n\n\n0\n\n\n3.0\n\n\n10.0\n\n\n1.0\n\n\nGAMMA\n\n\n3.0\n\n\n\n\n0\n\n\n3.0\n\n\n10.0\n\n\n2.0\n\n\nGAMMA\n\n\n3.7\n\n\n\n\n0\n\n\n3.0\n\n\n10.0\n\n\n3.0\n\n\nGAMMA\n\n\n4.4\n\n\n\n\n0\n\n\n3.0\n\n\n10.0\n\n\n4.0\n\n\nGAMMA\n\n\n5.1\n\n\n\n\n0\n\n\n3.0\n\n\n10.0\n\n\n5.0\n\n\nGAMMA\n\n\n5.8\n\n\n\n\n0\n\n\n3.0\n\n\n10.0\n\n\n6.0\n\n\nGAMMA\n\n\n6.5\n\n\n\n\n0\n\n\n3.0\n\n\n10.0\n\n\n7.0\n\n\nGAMMA\n\n\n7.2\n\n\n\n\n…\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n1\n\n\n12.0\n\n\n25.0\n\n\n9.0\n\n\nGAMMA\n\n\n22.4\n\n\n\n\n1\n\n\n12.0\n\n\n25.0\n\n\n10.0\n\n\nGAMMA\n\n\n23.7\n\n\n\n\n2\n\n\n30.0\n\n\n40.0\n\n\n0.0\n\n\nALPHA\n\n\n30.0\n\n\n\n\n2\n\n\n30.0\n\n\n40.0\n\n\n0.0\n\n\nBETA\n\n\n40.0\n\n\n\n\n2\n\n\n30.0\n\n\n40.0\n\n\n0.0\n\n\nGAMMA\n\n\n1.0\n\n\n\n\n2\n\n\n30.0\n\n\n40.0\n\n\n1.0\n\n\nGAMMA\n\n\n30.0\n\n\n\n\n…\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n4\n\n\n80.0\n\n\n100.0\n\n\n7.0\n\n\nGAMMA\n\n\n92.0\n\n\n\n\n4\n\n\n80.0\n\n\n100.0\n\n\n8.0\n\n\nGAMMA\n\n\n94.0\n\n\n\n\n4\n\n\n80.0\n\n\n100.0\n\n\n9.0\n\n\nGAMMA\n\n\n96.0\n\n\n\n\n4\n\n\n80.0\n\n\n100.0\n\n\n10.0\n\n\nGAMMA\n\n\n98.0" + }, + { + "objectID": "ch07-PeoplePlugin.html#plugin-data-initialization", + "href": "ch07-PeoplePlugin.html#plugin-data-initialization", + "title": "7  People Plugin", + "section": "7.1 Plugin Data Initialization", + "text": "7.1 Plugin Data Initialization\nThe plugin is initialized using a PeoplePluginData object that collects person id values in contiguous ranges. Note that there is no auxiliary data about people and only their existence as a person at the start of the simulation is captured. Other plugins that deal with the various characteristics of people will separately handle adding that data via their own plugin data structures." + }, + { + "objectID": "ch07-PeoplePlugin.html#plugin-behavior", + "href": "ch07-PeoplePlugin.html#plugin-behavior", + "title": "7  People Plugin", + "section": "7.2 Plugin Behavior", + "text": "7.2 Plugin Behavior\nThe plugin adds a single data manager to the simulation as an instance of the PeopleDataManager that is initialized with the PeoplePluginData." + }, + { + "objectID": "ch07-PeoplePlugin.html#data-manager", + "href": "ch07-PeoplePlugin.html#data-manager", + "title": "7  People Plugin", + "section": "7.3 Data Manager", + "text": "7.3 Data Manager\nThe data manager provides access to people and provides the ability to:\n\nAdd or remove a person\nAnswer questions about person existence\n\nGet the current set of PersonId values\n\nGet the total number of people\nTransform PersonId objects to and from int values\n\nAnswer questions about int value ranges used in managing internal data structures in various data managers\n\n\nThe data manager also produces observable events when people are added or removed from the simulation:\n\nPersonImminentAdditionEvent – notifies that a person is about to be removed\nPersonAdditionEvent – notifies that a person is removed\nPersonImminentRemovalEvent – notifies that a person is being added\nPersonRemovalEvent – notifies that a person is fully added" + }, + { + "objectID": "ch07-PeoplePlugin.html#addremove-event-patterns", + "href": "ch07-PeoplePlugin.html#addremove-event-patterns", + "title": "7  People Plugin", + "section": "7.4 Add/Remove event patterns", + "text": "7.4 Add/Remove event patterns\nA common pattern used throughout many plugins for events signifying the addition or removal of an item from the simulation is to represent each of these with two events. The first event is to notify all concerned actors and data managers that an item is about to be removed, but the removal has not yet occurred so any reference to the item will still be available and any finalization or bookkeeping can be performed. The second event will act as an instruction to remove the item and it is expected that the item will not be available for further inspection.\nAs an example, let’s consider the removal of a person by an actor. The person to remove is PersonId[47] and the actor requests the person be removed by the PeopleDataManager. The data manager first plans to release the PersonRemovalEvent as soon as possible. This will schedule the release of the event onto the planning queue and time will not move forward before the execution of this event. However, this is a plan and it will only take place after the all current activities are complete. The data manager next releases the PersonImminentRemovalEvent. This event will propagate immediately to the other data managers and to any actors or reports that are subscribed to person removals. Since the data managers generally do not act on the imminent removal, the actors are able to retrieve any information about the person they need to take final actions or produce reports. Once everyone has had a chance to see that the person will be removed, the planned PersonRemovalEvent will be released and the data managers will finally remove any information related to the person from their data structures. This two-phase removal pattern is useful and practical but does present one problem: Consider the original actor that was deleting person 47. On the very next line of their code after they request the removal of the person, the person still exists. The removal is not immediate, but is slightly delayed in that it will occur only after flow of control has returned to the simulation. This delay will not correspond to any time flow, so the removal of the person will occur at the same time as the request for the removal.\nThe addition of a person follows a similar pattern. To understand this, we first need to look at the PersonConstructionData used to add a person. The PersonConstructionData is a container for zero to many objects that carry information about the new person to be used by the various data managers who will need to integrate corresponding data about the person. For example, if the Regions plugin is being used, it requires that every person has a region assignment and thus a RegionId will need to be included in the PersonConstructionData. The people data manager does not understand this auxiliary data but simply repackages it into the PersonImminentAdditionEvent. The event is released and all the relevant data managers take what they need from the data stored in the event to fully initialize the state of the person. Once all data managers have initialized the person, the people data manager releases the PersonAdditionEvent and actors/reports, will now see that the new person has been added to the simulation and will have access to the person’s full initialized complement of data.\nIn summary, the general convention is:\n\nimminent addition event\n\nused by data managers to piecemeal add an item’s details\nignored by actors and reports\n\naddition event\n\nignored by data managers\nused by actors and reports to integrate the addition now that all the details are in place\n\nimminent removal event\n\nignored by data managers\nused by actors to have a last chance to reference details on the item\n\nremoval event\n\nused by data managers to fully remove all stored data on the item\nignored by the actors since the item will be fully removed" + }, + { + "objectID": "ch07-PeoplePlugin.html#example-code-lesson-14", + "href": "ch07-PeoplePlugin.html#example-code-lesson-14", + "title": "7  People Plugin", + "section": "7.5 Example Code (Lesson 14)", + "text": "7.5 Example Code (Lesson 14)\nExample_14.java shows the use of the people plugin. The example includes four plugins:\n\nPeople plugin – (GCM core plugin) used to manage people\nStochastics Plugin – (GCM plugin) provides random number generation\nModel plugin – (local plugin) used to introduce two actors that will\n\nadd/remove people\nvaccinate people\n\nVaccine plugin – (local plugin) used to track vaccinations for each person\n\nThe example’s main method starts in Code Block 7.1 by establishing two reports:\n\nThe population trace report simply lists the additions and deletions of people by time. The report is managed by the PopulationTraceReport and is added to the simulation by the model plugin.\nThe vaccination report shows a daily accounting of the number of people having 0, 1…6+ vaccinations. The report is managed by the VaccineReport class added by the vaccine plugin.\n\n\n\nCode Block 7.1: The population trace and vaccination reports are associated with corresponding file names via the NIO report item handler.\npublic static void main(String[] args) throws IOException {\n if (args.length == 0) {\n throw new RuntimeException(\"One output directory argument is required\");\n }\n Path outputDirectory = Paths.get(args[0]);\n if (!Files.exists(outputDirectory)) {\n Files.createDirectory(outputDirectory);\n } else {\n if (!Files.isDirectory(outputDirectory)) {\n throw new IOException(\"Provided path is not a directory\");\n }\n }\n\n // reports\n NIOReportItemHandler nioReportItemHandler = //\n NIOReportItemHandler.builder()//\n .addReport(ModelReportLabel.POPULATION_TRACE, //\n outputDirectory.resolve(\"population_trace_report.xls\"))//\n .addReport(ModelReportLabel.VACCINATION, //\n outputDirectory.resolve(\"vaccination_report.xls\"))//\n .build();\n\n\nThe main method continues by creating the people plugin and initializing it with 10 people. Note that the people will have id values of 1, 3, 5, … ,19 showing that any set of non-negative values are acceptable. The stochastics plugin is next and is initialized with a seed value. We will be controlling the random seed values via a dimension as presented in Code Block 7.3. As a result, the experiment will have 5 scenarios, with each scenario differing in only the random seed value that starts the simulation.\n\n\nCode Block 7.2: The various plugins are initialized with data and added to the experiment.\n // create the people plugin with an initial population of ten people,\n // numbered 1, 3, 5,...,19\n PeoplePluginData.Builder peoplePluginDataBuilder = PeoplePluginData.builder();\n for (int i = 0; i < 10; i++) {\n PersonId personId = new PersonId(i * 2 + 1);\n peoplePluginDataBuilder.addPersonRange(new PersonRange(personId.getValue(), personId.getValue()));\n }\n PeoplePluginData peoplePluginData = peoplePluginDataBuilder.build();\n Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(peoplePluginData);\n\n // create the stochastics plugin and build a dimension with 5 seed\n // values\n WellState wellState = WellState.builder().setSeed(463390897335624435L).build();\n StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder().setMainRNGState(wellState)\n .build();\n Plugin stochasticsPlugin = StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData);\n\n Dimension stochasticsDimension = getStochasticsDimension(5, 8265427588292179209L);\n\n // create the vaccine and model plugins\n Plugin vaccinePlugin = VaccinePlugin.getVaccinePlugin();\n Plugin modelPlugin = ModelPlugin.getModelPlugin();\n\n Experiment.builder()//\n .addPlugin(modelPlugin)//\n .addPlugin(peoplePlugin)//\n .addPlugin(stochasticsPlugin)//\n .addPlugin(vaccinePlugin)//\n .addExperimentContextConsumer(nioReportItemHandler)//\n .addDimension(stochasticsDimension)//\n .build()//\n .execute();//\n }\n\n\n\n\nCode Block 7.3: The stochastics dimension contains levels for each replication value. Note that the generation of the random seed values occurs outside of the lambda code.\nprivate static Dimension getStochasticsDimension(int replicationCount, long seed) {\n FunctionalDimension.Builder builder = FunctionalDimension.builder();//\n\n RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed);\n\n List seedValues = new ArrayList<>();\n for (int i = 0; i < replicationCount; i++) {\n seedValues.add(randomGenerator.nextLong());\n }\n\n IntStream.range(0, seedValues.size()).forEach((i) -> {\n builder.addLevel((context) -> {\n StochasticsPluginData.Builder stochasticsPluginDataBuilder = context\n .getPluginDataBuilder(StochasticsPluginData.Builder.class);\n long seedValue = seedValues.get(i);\n WellState wellState = WellState.builder().setSeed(seedValue).build();\n stochasticsPluginDataBuilder.setMainRNGState(wellState);\n\n ArrayList result = new ArrayList<>();\n result.add(Integer.toString(i));\n result.add(Long.toString(seedValue) + \"L\");\n\n return result;\n });//\n });\n\n builder.addMetaDatum(\"seed_index\");//\n builder.addMetaDatum(\"seed_value\");//\n\n return builder.build();\n}\n\n\nThere are two actors provided by the model plugin. The first is the PopulationManager (Code Block 7.4) that upon its initialization plans 100 future actions to randomly remove (10% chance) or add (90% chance) people to the simulation. For people who are added, an initial vaccination count is included in the request to add the person so that the vaccine data manager can set the proper count.\n\n\nCode Block 7.4: The population manager schedules 100 randomized actions to either add or remove people.\npublic void init(ActorContext actorContext) {\n StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);\n RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator();\n double planTime = randomGenerator.nextDouble();\n for (int i = 0; i < 100; i++) {\n actorContext.addPlan((c) -> {\n PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class);\n if (randomGenerator.nextDouble() < 0.1) {\n List people = peopleDataManager.getPeople();\n if (!people.isEmpty()) {\n PersonId personId = people.get(randomGenerator.nextInt(people.size()));\n peopleDataManager.removePerson(personId);\n }\n } else {\n int intialVaccineCount = randomGenerator.nextInt(3);\n VaccineInitialization vaccineInitialization = new VaccineInitialization(intialVaccineCount);\n PersonConstructionData personConstructionData = PersonConstructionData.builder()//\n .add(vaccineInitialization)//\n .build();\n peopleDataManager.addPerson(personConstructionData);\n }\n }, planTime);\n planTime += randomGenerator.nextDouble();\n }\n}\n\n\nCode Block 7.5 shows the second actor, the Vaccinator. It plans 300 vaccination actions over a period of approximately 100 days, selecting a random person to vaccinate each time. There is no limit to the number of vaccinations a person can have and we would expect that some people will have a relatively high number of vaccinations in the vaccine report.\n\n\nCode Block 7.5: The vaccinator administers 300 vaccine doses over 100 days.\npublic void init(ActorContext actorContext) {\n StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);\n RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator();\n double planTime = randomGenerator.nextDouble();\n for (int i = 0; i < 300; i++) {\n actorContext.addPlan((c) -> {\n PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class);\n VaccinationDataManager vaccinationDataManager = c.getDataManager(VaccinationDataManager.class);\n List people = peopleDataManager.getPeople();\n if (!people.isEmpty()) {\n PersonId personId = people.get(randomGenerator.nextInt(people.size()));\n vaccinationDataManager.vaccinatePerson(personId);\n }\n }, planTime);\n planTime += randomGenerator.nextDouble() / 3;\n }\n}" + }, + { + "objectID": "ch07-PeoplePlugin.html#interacting-with-the-addition-and-removal-events", + "href": "ch07-PeoplePlugin.html#interacting-with-the-addition-and-removal-events", + "title": "7  People Plugin", + "section": "7.6 Interacting with the addition and removal events", + "text": "7.6 Interacting with the addition and removal events\nThe remaining code blocks will focus on the handling of the four person addition and removal events in the vaccine data manager and the population trace report. The vaccine report is periodic and does not subscribe to any events and is left for the reader to examine.\nFollowing the general conventions above, the vaccine data manager subscribes to the PersonRemovalEvent and the PersonImminentAdditionEvent during its initialization in Code Block 7.6.\n\n\nCode Block 7.6: The vaccination data manager initializes by recording initial vaccine counts for each person and subscribing to person addition, person removal and person vaccination events.\npublic void init(DataManagerContext dataManagerContext) {\n super.init(dataManagerContext);\n dataManagerContext.subscribe(PersonRemovalEvent.class, this::handlePersonRemovalEvent);\n dataManagerContext.subscribe(PersonImminentAdditionEvent.class, this::handlePersonImminentAdditionEvent);\n personDataManager = dataManagerContext.getDataManager(PeopleDataManager.class);\n this.dataManagerContext = dataManagerContext;\n for (PersonId personId : personDataManager.getPeople()) {\n vaccinationCounts.put(personId, new MutableInteger());\n }\n dataManagerContext.subscribe(VaccinationMutationEvent.class, this::handleVaccinationMutationEvent);\n}\n\n\nThe vaccine data manager uses a simple map from person id to a counter to track the number of vaccinations for each person:\n\n\nCode Block 7.7: The vaccination data manager uses a simple map from person id to a counter to track the number of vaccinations for each person.\nprivate Map vaccinationCounts = new LinkedHashMap<>();\n\n\nThe subscriptions above refer to the local methods of the vaccine data manager in Code Block 7.8. Handling the removal of a person is simple; the person id dropped from the map. Handling the addition requires that the manager try to locate a VaccinationInitialization object (which is just a wrapper around and integer count) contained in the construction. If the VaccinationInitialization is present, then the manager further validates the count is not negative.\n\n\nCode Block 7.8: The vaccination manager removes people from its count tracking as needed. Newly added people may enter into the simulation with some vaccinations.\n private void handlePersonRemovalEvent(DataManagerContext dataManagerContext,\n PersonRemovalEvent personRemovalEvent) {\n PersonId personId = personRemovalEvent.personId();\n vaccinationCounts.remove(personId);\n }\n\n private void handlePersonImminentAdditionEvent(DataManagerContext dataManagerContext,\n PersonImminentAdditionEvent personImminentAdditionEvent) {\n PersonId personId = personImminentAdditionEvent.personId();\n validateNewPersonId(personId);\n MutableInteger mutableInteger = new MutableInteger();\n vaccinationCounts.put(personId, mutableInteger);\n Optional optional = personImminentAdditionEvent//\n .personConstructionData()//\n .getValue(VaccineInitialization.class);\n if (optional.isPresent()) {\n VaccineInitialization vaccineInitialization = optional.get();\n int vaccineCount = vaccineInitialization.getVaccineCount();\n validateInitialVaccineCount(vaccineCount);\n mutableInteger.setValue(vaccineCount);\n }\n }" + }, + { + "objectID": "ch07-PeoplePlugin.html#inspecting-the-output", + "href": "ch07-PeoplePlugin.html#inspecting-the-output", + "title": "7  People Plugin", + "section": "7.7 Inspecting the output", + "text": "7.7 Inspecting the output\nFigure 7.1 shows the population trace report spanning the five scenarios and 500 additions and removals of people. In Figure 7.2 we have the vaccination report showing the number of people having from 0 to 6+ vaccinations over each day of the simulation across the five scenarios. As expected, the number of people having six or more vaccinations starts out at zero and monotonically increases as the days progress.\n\n\n\nFigure 7.1: An excerpt of the population trace report.\n\n\n\n\n\n\nscenario\n\n\nseed_index\n\n\nseed_value\n\n\ntime\n\n\npersonId\n\n\naction\n\n\n\n\n\n\n0\n\n\n0\n\n\n1126862960420803077L\n\n\n0.0000000\n\n\n1\n\n\nADDITION\n\n\n\n\n0\n\n\n0\n\n\n1126862960420803077L\n\n\n0.0000000\n\n\n3\n\n\nADDITION\n\n\n\n\n0\n\n\n0\n\n\n1126862960420803077L\n\n\n0.0000000\n\n\n5\n\n\nADDITION\n\n\n\n\n0\n\n\n0\n\n\n1126862960420803077L\n\n\n0.0000000\n\n\n7\n\n\nADDITION\n\n\n\n\n0\n\n\n0\n\n\n1126862960420803077L\n\n\n0.0000000\n\n\n9\n\n\nADDITION\n\n\n\n\n0\n\n\n0\n\n\n1126862960420803077L\n\n\n0.0000000\n\n\n11\n\n\nADDITION\n\n\n\n\n0\n\n\n0\n\n\n1126862960420803077L\n\n\n0.0000000\n\n\n13\n\n\nADDITION\n\n\n\n\n0\n\n\n0\n\n\n1126862960420803077L\n\n\n0.0000000\n\n\n15\n\n\nADDITION\n\n\n\n\n0\n\n\n0\n\n\n1126862960420803077L\n\n\n0.0000000\n\n\n17\n\n\nADDITION\n\n\n\n\n0\n\n\n0\n\n\n1126862960420803077L\n\n\n0.0000000\n\n\n19\n\n\nADDITION\n\n\n\n\n0\n\n\n0\n\n\n1126862960420803077L\n\n\n0.8335755\n\n\n19\n\n\nREMOVAL\n\n\n\n\n0\n\n\n0\n\n\n1126862960420803077L\n\n\n1.2826070\n\n\n20\n\n\nADDITION\n\n\n\n\n0\n\n\n0\n\n\n1126862960420803077L\n\n\n1.6263299\n\n\n21\n\n\nADDITION\n\n\n\n\n…\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n1\n\n\n1\n\n\n-4486033808643580070L\n\n\n0.0000000\n\n\n17\n\n\nADDITION\n\n\n\n\n1\n\n\n1\n\n\n-4486033808643580070L\n\n\n0.0000000\n\n\n19\n\n\nADDITION\n\n\n\n\n1\n\n\n1\n\n\n-4486033808643580070L\n\n\n0.0667491\n\n\n20\n\n\nADDITION\n\n\n\n\n1\n\n\n1\n\n\n-4486033808643580070L\n\n\n0.9322293\n\n\n21\n\n\nADDITION\n\n\n\n\n1\n\n\n1\n\n\n-4486033808643580070L\n\n\n1.6514183\n\n\n22\n\n\nADDITION\n\n\n\n\n1\n\n\n1\n\n\n-4486033808643580070L\n\n\n2.1177839\n\n\n23\n\n\nADDITION\n\n\n\n\n1\n\n\n1\n\n\n-4486033808643580070L\n\n\n2.2623884\n\n\n24\n\n\nADDITION\n\n\n\n\n1\n\n\n1\n\n\n-4486033808643580070L\n\n\n2.4082708\n\n\n25\n\n\nADDITION\n\n\n\n\n1\n\n\n1\n\n\n-4486033808643580070L\n\n\n2.8132398\n\n\n26\n\n\nADDITION\n\n\n\n\n1\n\n\n1\n\n\n-4486033808643580070L\n\n\n2.9103862\n\n\n27\n\n\nADDITION\n\n\n\n\n1\n\n\n1\n\n\n-4486033808643580070L\n\n\n3.1314042\n\n\n28\n\n\nADDITION\n\n\n\n\n1\n\n\n1\n\n\n-4486033808643580070L\n\n\n3.9782907\n\n\n29\n\n\nADDITION\n\n\n\n\n1\n\n\n1\n\n\n-4486033808643580070L\n\n\n4.8481078\n\n\n30\n\n\nADDITION\n\n\n\n\n1\n\n\n1\n\n\n-4486033808643580070L\n\n\n5.7753568\n\n\n31\n\n\nADDITION\n\n\n\n\n1\n\n\n1\n\n\n-4486033808643580070L\n\n\n6.0714214\n\n\n32\n\n\nADDITION\n\n\n\n\n1\n\n\n1\n\n\n-4486033808643580070L\n\n\n6.4493810\n\n\n33\n\n\nADDITION\n\n\n\n\n…\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n4\n\n\n4\n\n\n2435395143614485495L\n\n\n44.8710482\n\n\n100\n\n\nADDITION\n\n\n\n\n4\n\n\n4\n\n\n2435395143614485495L\n\n\n45.0533288\n\n\n101\n\n\nADDITION\n\n\n\n\n4\n\n\n4\n\n\n2435395143614485495L\n\n\n45.1289192\n\n\n102\n\n\nADDITION\n\n\n\n\n4\n\n\n4\n\n\n2435395143614485495L\n\n\n45.5197380\n\n\n103\n\n\nADDITION\n\n\n\n\n4\n\n\n4\n\n\n2435395143614485495L\n\n\n45.8369307\n\n\n104\n\n\nADDITION\n\n\n\n\n4\n\n\n4\n\n\n2435395143614485495L\n\n\n46.2957569\n\n\n105\n\n\nADDITION\n\n\n\n\n4\n\n\n4\n\n\n2435395143614485495L\n\n\n46.8744186\n\n\n106\n\n\nADDITION\n\n\n\n\n4\n\n\n4\n\n\n2435395143614485495L\n\n\n47.3473702\n\n\n107\n\n\nADDITION\n\n\n\n\n4\n\n\n4\n\n\n2435395143614485495L\n\n\n48.3166466\n\n\n108\n\n\nADDITION\n\n\n\n\n4\n\n\n4\n\n\n2435395143614485495L\n\n\n49.2053410\n\n\n109\n\n\nADDITION\n\n\n\n\n4\n\n\n4\n\n\n2435395143614485495L\n\n\n49.2524537\n\n\n110\n\n\nADDITION\n\n\n\n\n4\n\n\n4\n\n\n2435395143614485495L\n\n\n49.5378229\n\n\n111\n\n\nADDITION\n\n\n\n\n\n\n\n\n\n\n\n\nFigure 7.2: An excerpt of the vaccination report.\n\n\n\n\n\n\nscenario\n\n\nseed_index\n\n\nseed_value\n\n\nday\n\n\ncount_0\n\n\ncount_1\n\n\ncount_2\n\n\ncount_3\n\n\ncount_4\n\n\ncount_5\n\n\ncount_6+\n\n\n\n\n\n\n0\n\n\n0\n\n\n1126862960420803077L\n\n\n0\n\n\n10\n\n\n0\n\n\n0\n\n\n0\n\n\n0\n\n\n0\n\n\n0\n\n\n\n\n0\n\n\n0\n\n\n1126862960420803077L\n\n\n1\n\n\n7\n\n\n2\n\n\n0\n\n\n0\n\n\n0\n\n\n0\n\n\n0\n\n\n\n\n0\n\n\n0\n\n\n1126862960420803077L\n\n\n2\n\n\n4\n\n\n5\n\n\n2\n\n\n0\n\n\n0\n\n\n0\n\n\n0\n\n\n\n\n0\n\n\n0\n\n\n1126862960420803077L\n\n\n3\n\n\n2\n\n\n3\n\n\n8\n\n\n0\n\n\n0\n\n\n0\n\n\n0\n\n\n\n\n0\n\n\n0\n\n\n1126862960420803077L\n\n\n4\n\n\n1\n\n\n2\n\n\n10\n\n\n0\n\n\n1\n\n\n0\n\n\n0\n\n\n\n\n0\n\n\n0\n\n\n1126862960420803077L\n\n\n5\n\n\n2\n\n\n3\n\n\n6\n\n\n3\n\n\n3\n\n\n0\n\n\n0\n\n\n\n\n0\n\n\n0\n\n\n1126862960420803077L\n\n\n6\n\n\n1\n\n\n5\n\n\n5\n\n\n3\n\n\n2\n\n\n2\n\n\n0\n\n\n\n\n0\n\n\n0\n\n\n1126862960420803077L\n\n\n7\n\n\n0\n\n\n7\n\n\n4\n\n\n4\n\n\n2\n\n\n3\n\n\n0\n\n\n\n\n0\n\n\n0\n\n\n1126862960420803077L\n\n\n8\n\n\n0\n\n\n7\n\n\n6\n\n\n5\n\n\n2\n\n\n2\n\n\n1\n\n\n\n\n…\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n0\n\n\n0\n\n\n1126862960420803077L\n\n\n52\n\n\n6\n\n\n13\n\n\n13\n\n\n7\n\n\n10\n\n\n9\n\n\n22\n\n\n\n\n0\n\n\n0\n\n\n1126862960420803077L\n\n\n53\n\n\n7\n\n\n13\n\n\n15\n\n\n7\n\n\n10\n\n\n9\n\n\n22\n\n\n\n\n0\n\n\n0\n\n\n1126862960420803077L\n\n\n54\n\n\n7\n\n\n13\n\n\n16\n\n\n7\n\n\n10\n\n\n9\n\n\n22\n\n\n\n\n1\n\n\n1\n\n\n-4486033808643580070L\n\n\n0\n\n\n10\n\n\n0\n\n\n0\n\n\n0\n\n\n0\n\n\n0\n\n\n0\n\n\n\n\n1\n\n\n1\n\n\n-4486033808643580070L\n\n\n1\n\n\n9\n\n\n3\n\n\n0\n\n\n0\n\n\n0\n\n\n0\n\n\n0\n\n\n\n\n1\n\n\n1\n\n\n-4486033808643580070L\n\n\n2\n\n\n5\n\n\n6\n\n\n2\n\n\n0\n\n\n0\n\n\n0\n\n\n0\n\n\n\n\n1\n\n\n1\n\n\n-4486033808643580070L\n\n\n3\n\n\n5\n\n\n8\n\n\n4\n\n\n1\n\n\n0\n\n\n0\n\n\n0\n\n\n\n\n1\n\n\n1\n\n\n-4486033808643580070L\n\n\n4\n\n\n5\n\n\n6\n\n\n5\n\n\n3\n\n\n1\n\n\n0\n\n\n0\n\n\n\n\n1\n\n\n1\n\n\n-4486033808643580070L\n\n\n5\n\n\n4\n\n\n6\n\n\n6\n\n\n2\n\n\n3\n\n\n0\n\n\n0\n\n\n\n\n1\n\n\n1\n\n\n-4486033808643580070L\n\n\n6\n\n\n2\n\n\n6\n\n\n7\n\n\n3\n\n\n4\n\n\n0\n\n\n0\n\n\n\n\n1\n\n\n1\n\n\n-4486033808643580070L\n\n\n7\n\n\n2\n\n\n4\n\n\n11\n\n\n4\n\n\n4\n\n\n0\n\n\n0\n\n\n\n\n…\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n3\n\n\n3\n\n\n-821383327301461075L\n\n\n42\n\n\n5\n\n\n13\n\n\n15\n\n\n9\n\n\n8\n\n\n10\n\n\n15\n\n\n\n\n3\n\n\n3\n\n\n-821383327301461075L\n\n\n43\n\n\n5\n\n\n13\n\n\n15\n\n\n9\n\n\n8\n\n\n11\n\n\n15\n\n\n\n\n3\n\n\n3\n\n\n-821383327301461075L\n\n\n44\n\n\n6\n\n\n12\n\n\n15\n\n\n10\n\n\n8\n\n\n9\n\n\n17\n\n\n\n\n3\n\n\n3\n\n\n-821383327301461075L\n\n\n45\n\n\n6\n\n\n13\n\n\n15\n\n\n9\n\n\n8\n\n\n7\n\n\n19\n\n\n\n\n3\n\n\n3\n\n\n-821383327301461075L\n\n\n46\n\n\n6\n\n\n13\n\n\n15\n\n\n8\n\n\n8\n\n\n7\n\n\n20\n\n\n\n\n3\n\n\n3\n\n\n-821383327301461075L\n\n\n47\n\n\n6\n\n\n14\n\n\n17\n\n\n5\n\n\n11\n\n\n6\n\n\n21\n\n\n\n\n3\n\n\n3\n\n\n-821383327301461075L\n\n\n48\n\n\n5\n\n\n14\n\n\n17\n\n\n6\n\n\n10\n\n\n5\n\n\n22\n\n\n\n\n3\n\n\n3\n\n\n-821383327301461075L\n\n\n49\n\n\n5\n\n\n13\n\n\n18\n\n\n7\n\n\n9\n\n\n6\n\n\n22\n\n\n\n\n…\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n4\n\n\n4\n\n\n2435395143614485495L\n\n\n42\n\n\n11\n\n\n12\n\n\n11\n\n\n10\n\n\n12\n\n\n7\n\n\n19\n\n\n\n\n4\n\n\n4\n\n\n2435395143614485495L\n\n\n43\n\n\n9\n\n\n13\n\n\n12\n\n\n9\n\n\n11\n\n\n8\n\n\n20\n\n\n\n\n4\n\n\n4\n\n\n2435395143614485495L\n\n\n44\n\n\n7\n\n\n14\n\n\n11\n\n\n10\n\n\n10\n\n\n8\n\n\n21\n\n\n\n\n4\n\n\n4\n\n\n2435395143614485495L\n\n\n45\n\n\n7\n\n\n16\n\n\n10\n\n\n11\n\n\n9\n\n\n7\n\n\n23\n\n\n\n\n4\n\n\n4\n\n\n2435395143614485495L\n\n\n46\n\n\n7\n\n\n16\n\n\n13\n\n\n10\n\n\n11\n\n\n6\n\n\n24\n\n\n\n\n4\n\n\n4\n\n\n2435395143614485495L\n\n\n47\n\n\n8\n\n\n14\n\n\n15\n\n\n10\n\n\n11\n\n\n7\n\n\n24\n\n\n\n\n4\n\n\n4\n\n\n2435395143614485495L\n\n\n48\n\n\n9\n\n\n13\n\n\n15\n\n\n11\n\n\n11\n\n\n7\n\n\n24\n\n\n\n\n4\n\n\n4\n\n\n2435395143614485495L\n\n\n49\n\n\n9\n\n\n13\n\n\n15\n\n\n12\n\n\n11\n\n\n7\n\n\n24\n\n\n\n\n4\n\n\n4\n\n\n2435395143614485495L\n\n\n50\n\n\n12\n\n\n13\n\n\n15\n\n\n12\n\n\n11\n\n\n7\n\n\n24" + }, + { + "objectID": "ch08-RegionsPlugin.html#plugin-data-initialization", + "href": "ch08-RegionsPlugin.html#plugin-data-initialization", + "title": "8  Regions Plugin", + "section": "8.1 Plugin Data Initialization", + "text": "8.1 Plugin Data Initialization\nThe plugin is initialized using a RegionsPluginData object that collects person to region assignments and region property values." + }, + { + "objectID": "ch08-RegionsPlugin.html#plugin-behavior", + "href": "ch08-RegionsPlugin.html#plugin-behavior", + "title": "8  Regions Plugin", + "section": "8.2 Plugin Behavior", + "text": "8.2 Plugin Behavior\nThe plugin adds a single data manager to the simulation as an instance of the RegionsDataManager that is initialized with the RegionsPluginData." + }, + { + "objectID": "ch08-RegionsPlugin.html#data-manager", + "href": "ch08-RegionsPlugin.html#data-manager", + "title": "8  Regions Plugin", + "section": "8.3 Data Manager", + "text": "8.3 Data Manager\nThe data manager controls regions, their properties and the assignment of people to those regions. The data manager provides public methods that:\n\nAdd a region\nDefine a region property\nSet a region property value\nMove a person from one region to another\nAnswer various questions about:\n\nPerson membership in regions\nRegion property values\n\n\nThe data manager also produces observable events:\n\nPersonRegionUpdateEvent – when a person is moved from one region to another\nRegionAdditionEvent – when a region is added to the simulation\nRegionPropertyDefintionEvent – when a new region property is defined\nRegionPropertyUpdateEvent – when a region property value is assigned" + }, + { + "objectID": "ch08-RegionsPlugin.html#example-code-lesson-15", + "href": "ch08-RegionsPlugin.html#example-code-lesson-15", + "title": "8  Regions Plugin", + "section": "8.4 Example Code (Lesson 15)", + "text": "8.4 Example Code (Lesson 15)\nExample_15.java shows the use of the regions plugin. In it we will examine\n\nThe initialization of the regions plugin\nThe movement of people between regions\nThe dynamic addition of regions\nThe dynamic addition of region properties\nThe update of region property values\n\nThe example includes five plugins:\n\nRegions Plugin– (GCM core plugin) used to manage regions, their properties and person membership in regions\nPeople plugin – (GCM core plugin) used to manage people\nStochastics plugin – (GCM core plugin) used to generate random numbers used in various decisions\nModel plugin – (local plugin) used to introduce three actors that will:\n\nMove people between regions\nCreate new regions\nVaccinate people, reacting to changes in region properties\n\nVaccine plugin – (local plugin) used to track vaccinations for each person\n\nThe example’s main method starts in Code Block 8.1 by creating an instance of the example class rather than building the experiment directly since this example is somewhat more complex than previous examples.\n\n\nCode Block 8.1: Executing example 15 with an output directory.\npublic static void main(String[] args) throws IOException {\n if (args.length == 0) {\n throw new RuntimeException(\"One output directory argument is required\");\n }\n Path outputDirectory = Paths.get(args[0]);\n if (!Files.exists(outputDirectory)) {\n Files.createDirectory(outputDirectory);\n } else {\n if (!Files.isDirectory(outputDirectory)) {\n throw new IOException(\"Provided path is not a directory\");\n }\n }\n\n new Example_15(outputDirectory).execute();\n}\n\n\nThe execution method first gathers together the five plugins in Code Block 8.2:\n\n\nCode Block 8.2: The various plugins are gathered from their initial data.\nprivate void execute() {\n /*\n * Create person ids and region ids that are shared across the plugins\n */\n initializePeopleAndRegions();\n\n /*\n * Create the reports\n */\n\n NIOReportItemHandler nioReportItemHandler = getNIOReportItemHandler();\n\n /*\n * Create the people plugin filled with 1000 people\n */\n Plugin peoplePlugin = getPeoplePlugin();\n\n /*\n * Create the region plugin 5 regions, each having a lat and lon and assign the\n * people to random regions.\n * \n */\n Plugin regionsPlugin = getRegionsPlugin();\n\n /*\n * create the stochastics plugin and build a dimension with 5 seed values\n */\n Plugin stochasticsPlugin = getStochasticsPlugin();\n Dimension stochasticsDimension = getStochasticsDimension(5, randomGenerator.nextLong());\n\n /*\n * Create the vaccine and model plugins\n */\n Plugin vaccinePlugin = VaccinePlugin.getVaccinePlugin();\n\n Plugin modelPlugin = ModelPlugin.getModelPlugin();\n\n\nThe first action is to create 1000 people and 5 regions that will be used in the creation of both the people plugin and the regions plugin.\n\n\nCode Block 8.3: Lists of initial people and regions are created and will be used to initialize the various plugins.\nprivate void initializePeopleAndRegions() {\n for (int i = 0; i < 1000; i++) {\n initialPeople.add(new PersonId(i));\n }\n for (int i = 0; i < 5; i++) {\n initialRegions.add(new Region(i));\n }\n}\n\n\nThe regions plugin defines a region id with a marker interface. Marker interfaces are used to differentiate arguments and reduce variable type ambiguities while not imposing any particular implementation on the modeler. Region ids might reasonably be implemented as integer based identifiers or as strings that represent place names. In this example we will implement the region ids with an integer based class, the Region (Code Block 8.4), which is a boiler-plate wrapper around an int id value.\n\n\nCode Block 8.4: The region id is implemented as a wrapper class of int.\npublic final class Region implements RegionId {\n\n private final int id;\n\n /**\n * Constructs the region\n * \n * @throws ContractException\n *
  • {@linkplain ModelError#NEGATIVE_REGION_ID}
  • \n */\n public Region(int id) {\n if (id < 0) {\n throw new ContractException(ModelError.NEGATIVE_REGION_ID);\n }\n this.id = id;\n }\n\n public int getValue() {\n return id;\n }\n\n @Override\n public int hashCode() {\n return id;\n }\n\n @Override\n public boolean equals(Object obj) {\n if (this == obj) {\n return true;\n }\n if (!(obj instanceof Region)) {\n return false;\n }\n Region other = (Region) obj;\n if (id != other.id) {\n return false;\n }\n return true;\n }\n\n @Override\n public String toString() {\n return \"Region_\" + id;\n }\n}\n\n\nThe example contains three reports in Code Block 8.5:\n\nRegionPropertyReport - shows changes to region property values\nRegionTransferReport - shows movements of people between regions\nVaccinationReport - shows vaccinations of people\n\n\n\nCode Block 8.5: The region property, region transfer and vaccination reports are mapped to distinct file names.\nprivate NIOReportItemHandler getNIOReportItemHandler() {\n return NIOReportItemHandler.builder()//\n .addReport(ModelReportLabel.REGION_PROPERTY_REPORT, //\n outputDirectory.resolve(\"region_property_report.xls\"))//\n .addReport(ModelReportLabel.REGION_TRANSFER_REPORT, //\n outputDirectory.resolve(\"region_transfer_report.xls\"))//\n .addReport(ModelReportLabel.VACCINATION, //\n outputDirectory.resolve(\"vaccine_report.xls\"))//\n .build();\n}\n\n\nThe people plugin, Code Block 8.6, is built from the 1000 people created earlier.\n\n\nCode Block 8.6: The people plugin is initialized with the starting populaiton.\nprivate Plugin getPeoplePlugin() {\n PeoplePluginData.Builder peoplePluginDataBuilder = PeoplePluginData.builder();\n for (PersonId personId : initialPeople) {\n peoplePluginDataBuilder.addPersonRange(new PersonRange(personId.getValue(), personId.getValue()));\n }\n PeoplePluginData peoplePluginData = peoplePluginDataBuilder.build();\n return PeoplePlugin.getPeoplePlugin(peoplePluginData);\n}\n\n\nCreating the regions plugin Code Block 8.7 is a bit more involved. First, the five regions created before are added to the plugin. Since the plugin requires that every person always have a region assignment, we assign a randomly selected region to each person. We define the LAT and LON properties to give the regions a geographic location. Notice that the definitions do not have default values since it does not make sense to say a region has a default position. This then will require that we assign specific latitude and longitude values for each region. Later on we will examine adding a new region property definition dynamically as the simulation is running.\n\n\nCode Block 8.7: The regions plugin is initialized with the starting regions and people, with each person assigned to a randomly selected region. The two region-based reports are also initialized and added to the region plugin’s data.\nprivate Plugin getRegionsPlugin() {\n // create the region plugin with an initial five regions, each region\n // having 200 people\n RegionsPluginData.Builder regionsPluginDataBuilder = RegionsPluginData.builder();\n for (Region region : initialRegions) {\n regionsPluginDataBuilder.addRegion(region);\n }\n\n for (PersonId personId : initialPeople) {\n Region region = initialRegions.get(randomGenerator.nextInt(initialRegions.size()));\n regionsPluginDataBuilder.addPerson(personId, region);\n }\n\n PropertyDefinition propertyDefinition = PropertyDefinition.builder()//\n .setType(Double.class)//\n .setPropertyValueMutability(false)//\n .build();\n regionsPluginDataBuilder.defineRegionProperty(RegionProperty.LAT, propertyDefinition);\n regionsPluginDataBuilder.defineRegionProperty(RegionProperty.LON, propertyDefinition);\n\n for (Region region : initialRegions) {\n regionsPluginDataBuilder.setRegionPropertyValue(region, RegionProperty.LAT,\n randomGenerator.nextDouble() + 45.0);\n regionsPluginDataBuilder.setRegionPropertyValue(region, RegionProperty.LON,\n randomGenerator.nextDouble() + 128.0);\n }\n\n RegionsPluginData regionsPluginData = regionsPluginDataBuilder.build();\n\n RegionPropertyReportPluginData regionPropertyReportPluginData = //\n RegionPropertyReportPluginData.builder()//\n .setReportLabel(ModelReportLabel.REGION_PROPERTY_REPORT)//\n .build();\n\n RegionTransferReportPluginData regionTransferReportPluginData = RegionTransferReportPluginData.builder()//\n .setReportLabel(ModelReportLabel.REGION_TRANSFER_REPORT)//\n .setReportPeriod(ReportPeriod.END_OF_SIMULATION)//\n .build();//\n\n return RegionsPlugin.builder()//\n .setRegionsPluginData(regionsPluginData)//\n .setRegionPropertyReportPluginData(regionPropertyReportPluginData)//\n .setRegionTransferReportPluginData(regionTransferReportPluginData)//\n .getRegionsPlugin();\n}\n\n\nAdding the stochastics plugin with a corresponding dimension that will create five scenarios proceeds in the usual way in Code Block 8.8:\n\n\nCode Block 8.8: The stochastics plugin is initialized with a random seed value. A dimension is added to add new seeds to the resulting scenarios.\nprivate Plugin getStochasticsPlugin() {\n\n WellState wellState = WellState.builder().setSeed(randomGenerator.nextLong()).build();\n StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder()//\n .setMainRNGState(wellState).build();\n return StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData);\n}\n\nprivate Dimension getStochasticsDimension(int replicationCount, long seed) {\n FunctionalDimension.Builder builder = FunctionalDimension.builder();//\n\n RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed);\n\n List seedValues = new ArrayList<>();\n for (int i = 0; i < replicationCount; i++) {\n seedValues.add(randomGenerator.nextLong());\n }\n\n IntStream.range(0, seedValues.size()).forEach((i) -> {\n builder.addLevel((context) -> {\n StochasticsPluginData.Builder stochasticsPluginDataBuilder = context\n .getPluginDataBuilder(StochasticsPluginData.Builder.class);\n long seedValue = seedValues.get(i);\n WellState wellState = WellState.builder().setSeed(seedValue).build();\n stochasticsPluginDataBuilder.setMainRNGState(wellState);\n\n ArrayList result = new ArrayList<>();\n result.add(Integer.toString(i));\n result.add(Long.toString(seedValue) + \"L\");\n\n return result;\n });//\n });\n\n builder.addMetaDatum(\"seed index\");//\n builder.addMetaDatum(\"seed value\");//\n\n return builder.build();\n}\n\n\nFinally, we add the vaccine and model plugins. This will add the vaccine data manager as well as three previously mentioned actors that will be used to demonstrate the various capabilities of the regions plugin.\n\nPersonMover – used to move people between regions\nRegionCreator – used to create new regions during the simulation run\nVaccinator – used to vaccinate people, reacting to changes in region properties\n\nThe execute method finishes (Code Block 8.9) by constructing and executing the experiment:\n\n\nCode Block 8.9: The experiment is run with five scenarios, each using distinct random seed values.\nExperiment.builder()//\n .addPlugin(modelPlugin)//\n .addPlugin(regionsPlugin)//\n .addPlugin(peoplePlugin)//\n .addPlugin(stochasticsPlugin)//\n .addPlugin(vaccinePlugin)//\n .addExperimentContextConsumer(nioReportItemHandler)//\n .addDimension(stochasticsDimension)//\n .build()//\n .execute();//" + }, + { + "objectID": "ch08-RegionsPlugin.html#the-actors", + "href": "ch08-RegionsPlugin.html#the-actors", + "title": "8  Regions Plugin", + "section": "8.5 The actors", + "text": "8.5 The actors\nWe will finish this chapter by reviewing the three actors of the model plugin and then examine the three reports.\nThe PersonMover actor, in Code Block 8.10 and Code Block 8.11, schedules 1000 random moves of a person from one region to another over the course of 100 days.\n\n\nCode Block 8.10: The person mover actor plans for 1000 movements of people over time.\npublic void init(ActorContext actorContext) {\n for (int i = 0; i < 1000; i++) {\n double planTime = ((double) i) * 0.1;\n actorContext.addPlan(this::moveRandomPerson, planTime);\n }\n}\n\n\nMoving the person requires that we use the stochastics plugin and the people plugin to select a random person. We next use the regions plugin to first select a random new region for the person and then move the person to that region.\n\n\nCode Block 8.11: The person mover actor attempts to move a randomly selected person from their current region to a new region.\nprivate void moveRandomPerson(ActorContext actorContext) {\n StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);\n RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator();\n PeopleDataManager peopleDataManager = actorContext.getDataManager(PeopleDataManager.class);\n RegionsDataManager regionsDataManager = actorContext.getDataManager(RegionsDataManager.class);\n\n // pick a random person\n List people = peopleDataManager.getPeople();\n if (people.isEmpty()) {\n return;\n }\n PersonId personId = people.get(randomGenerator.nextInt(people.size()));\n\n // pick a new random new region for that person\n List regionIds = new ArrayList<>(regionsDataManager.getRegionIds());\n RegionId personRegion = regionsDataManager.getPersonRegion(personId);\n regionIds.remove(personRegion);\n if (regionIds.isEmpty()) {\n return;\n }\n RegionId newPersonRegion = regionIds.get(randomGenerator.nextInt(regionIds.size()));\n\n // assign the region to the person\n regionsDataManager.setPersonRegion(personId, newPersonRegion);\n}\n\n\nThe RegionCreator actor, in Code Block 8.12 and Code Block 8.13, follows a similar pattern, scheduling the creation of five new regions over 101 days.\n\n\nCode Block 8.12: The region creator actor plans the addition of five new regions.\npublic void init(ActorContext actorContext) {\n for (int i = 0; i < 5; i++) {\n double planTime = 20 * i + 1;\n actorContext.addPlan(this::addRegion, planTime);\n }\n}\n\n\nWhen adding a region, we have to be aware that the region will have LAT and LON properties and that these properties were not defined with default values. Thus we must supply values for the region’s latitude and longitude as part of the RegionConstructionData object that is passed to the regions data manager. We will similarly assign a new random Boolean value for the VACCINE_PRIORITY property. The VACCINE_PRIORITY is a dynamically added property that is introduced later. Note that we first check for the existence of the property and only then set a value since setting such a value before the property is defined will result in a runtime exception.\n\n\n\n\n\n\nNote\n\n\n\nSuch considerations are unusual since properties are usually defined in the plugin initialization data or added very early in the simulation before any actors have initialized. We do so here for the purposes of demonstrating dynamic property definitions.\n\n\n\n\nCode Block 8.13: When the region creator actor adds a new region, it assigns a random lat-lon corrdinate and possibly assigns a vaccine priority status to the region.\nprivate void addRegion(ActorContext actorContext) {\n RegionsDataManager regionsDataManager = actorContext.getDataManager(RegionsDataManager.class);\n StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);\n RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator();\n\n Set regions = regionsDataManager.getRegionIds();\n int maxRegionValue = -1;\n for (Region region : regions) {\n int value = region.getValue();\n maxRegionValue = FastMath.max(value, maxRegionValue);\n }\n Region newRegion = new Region(maxRegionValue + 1);\n Builder regionBuilder = RegionConstructionData.builder().setRegionId(newRegion);\n regionBuilder.setRegionPropertyValue(RegionProperty.LAT, 35 + randomGenerator.nextDouble());\n regionBuilder.setRegionPropertyValue(RegionProperty.LON, 128 + randomGenerator.nextDouble());\n\n if (regionsDataManager.regionPropertyIdExists(RegionProperty.VACCINE_PRIORITY)) {\n regionBuilder.setRegionPropertyValue(RegionProperty.VACCINE_PRIORITY, randomGenerator.nextBoolean());\n }\n RegionConstructionData regionConstructionData = regionBuilder.build();\n regionsDataManager.addRegion(regionConstructionData);\n}\n\n\nThe Vaccinator actor is somewhat more complicated than the other actors. It initializes (Code Block 8.14) by storing references to various data managers for convenience and then plans 5000 vaccinations spread over 100 days. It also plans to add the VACCINE_PRIORITY property on day 50.\n\n\nCode Block 8.14: The vaccinator initializes by planning the vaccination of 5000 people carried out over approximately 50 days.\npublic void init(ActorContext actorContext) {\n StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);\n randomGenerator = stochasticsDataManager.getRandomGenerator();\n regionsDataManager = actorContext.getDataManager(RegionsDataManager.class);\n vaccinationDataManager = actorContext.getDataManager(VaccinationDataManager.class);\n\n double planTime = randomGenerator.nextDouble();\n for (int i = 0; i < 5000; i++) {\n actorContext.addPlan(this::vaccinateRandomPerson, planTime);\n planTime += randomGenerator.nextDouble() * 0.02;\n }\n\n actorContext.addPlan(this::addVaccinePriorityPropertyToRegions, 50);\n}\n\n\nLet’s first look at the addition of the new region property on day 50 in Code Block 8.15. The new property is a Boolean value defaulted to false and indicates whether people should be chosen from regions randomly or by preferring people with the fewest vaccinations. Since the property has a default value, we do not have to set values for each region in the RegionPropertyDefinitionInitialization object that is passed to the regions data manager when creating the region. We do so anyway to demonstrate such value assignments. Once the new property is in place, the Vaccinator schedules the switching of the value for random regions once per day for the next 50 days.\n\n\nCode Block 8.15: On day 50, the vaccinator defines the Boolean VACCINE PRIORITY property and assigns randomized values to the existing regions. It then plans for updates to 50 regional vaccine priority property values over 50 days.\nprivate void addVaccinePriorityPropertyToRegions(ActorContext actorContext) {\n\n PropertyDefinition propertyDefinition = //\n PropertyDefinition.builder()//\n .setType(Boolean.class)//\n .setDefaultValue(false)//\n .build();\n\n RegionPropertyDefinitionInitialization.Builder defBuilder = RegionPropertyDefinitionInitialization.builder()//\n .setPropertyDefinition(propertyDefinition)//\n .setRegionPropertyId(RegionProperty.VACCINE_PRIORITY);\n\n for (RegionId regionId : regionsDataManager.getRegionIds()) {\n defBuilder.addPropertyValue(regionId, randomGenerator.nextBoolean());\n }\n\n RegionPropertyDefinitionInitialization regionPropertyDefinitionInitialization = defBuilder.build();\n regionsDataManager.defineRegionProperty(regionPropertyDefinitionInitialization);\n\n for (int i = 0; i < 50; i++) {\n double planTime = actorContext.getTime() + i;\n actorContext.addPlan(this::alterVaccinePriorityPropertyOnRandomRegion, planTime);\n }\n}\n\n\nIn Code Block 8.16 the Vaccinator performs this value switching:\n\n\nCode Block 8.16: Toggling the vaccine priority for a randomly selected region.\nprivate void alterVaccinePriorityPropertyOnRandomRegion(ActorContext actorContext) {\n List regionids = new ArrayList<>(regionsDataManager.getRegionIds());\n if (regionids.isEmpty()) {\n return;\n }\n RegionId regionId = regionids.get(randomGenerator.nextInt(regionids.size()));\n Boolean vaccinePriority = regionsDataManager.getRegionPropertyValue(regionId, RegionProperty.VACCINE_PRIORITY);\n regionsDataManager.setRegionPropertyValue(regionId, RegionProperty.VACCINE_PRIORITY, !vaccinePriority);\n}\n\n\nThe Vaccinator vaccinates people at random (Code Block 8.17) by first selecting a random region and then selecting a random person in that region. The selection of the person is subject to the presence of the VACCINE_PRIORITY property and whether the value of the property is true for the selected region. If the priority selection is being used, then a first pass through the people in the region establishes the lowest number of vaccines received by any person. A second pass through the same people now selects only those having this number of vaccinations. Finally, a person is selected at random from the eligible people.\n\n\nCode Block 8.17: The vaccinator selects a person at random from the population to vaccinate. If the region is using the VACCINE_PRIORITY policy, then those with the least number of vaccinations have preference.\nprivate void vaccinateRandomPerson(ActorContext actorContext) {\n\n List regionIds = new ArrayList<>(regionsDataManager.getRegionIds());\n if (regionIds.isEmpty()) {\n return;\n }\n RegionId regionId = regionIds.get(randomGenerator.nextInt(regionIds.size()));\n List peopleInRegion = regionsDataManager.getPeopleInRegion(regionId);\n\n Boolean prioritizePeople = false;\n boolean vaccinePriorityPropertyExists = regionsDataManager\n .regionPropertyIdExists(RegionProperty.VACCINE_PRIORITY);\n if (vaccinePriorityPropertyExists) {\n prioritizePeople = regionsDataManager.getRegionPropertyValue(regionId, RegionProperty.VACCINE_PRIORITY);\n }\n\n PersonId selectedPersonId = null;\n if (prioritizePeople) {\n int minVaccinationCount = Integer.MAX_VALUE;\n for (PersonId personId : peopleInRegion) {\n int personVaccinationCount = vaccinationDataManager.getPersonVaccinationCount(personId);\n if (personVaccinationCount < minVaccinationCount) {\n minVaccinationCount = personVaccinationCount;\n }\n }\n List eligiblePeople = new ArrayList<>();\n for (PersonId personId : peopleInRegion) {\n int personVaccinationCount = vaccinationDataManager.getPersonVaccinationCount(personId);\n if (personVaccinationCount == minVaccinationCount) {\n eligiblePeople.add(personId);\n }\n }\n if (!eligiblePeople.isEmpty()) {\n selectedPersonId = eligiblePeople.get(randomGenerator.nextInt(eligiblePeople.size()));\n }\n } else {\n if (!peopleInRegion.isEmpty()) {\n selectedPersonId = peopleInRegion.get(randomGenerator.nextInt(peopleInRegion.size()));\n }\n }\n\n if (selectedPersonId != null) {\n vaccinationDataManager.vaccinatePerson(selectedPersonId);\n }\n}" + }, + { + "objectID": "ch08-RegionsPlugin.html#inspecting-the-output", + "href": "ch08-RegionsPlugin.html#inspecting-the-output", + "title": "8  Regions Plugin", + "section": "8.6 Inspecting the output", + "text": "8.6 Inspecting the output\nThe region transfer report shows the number of transfers of a person from one region to another across all days in the simulation. The rows where the source and destination regions are the same represent the addition of people at the start of the simulation and, as expected, the sum of such transfers equals to 1000. We also expect to see regions that were added beyond the original five regions and that transfers in and out of those regions should be reduced compared to the original regions since they start out with no people and come into the simulation only after day 50.\nno vaccinations during those periods.\n\n\n\nFigure 8.1: Excerpts from the region transfer report.\n\n\n\n\n\n\nscenario\n\n\nseed_index\n\n\nseed_value\n\n\nsource_region\n\n\ndestination_region\n\n\ntransfers\n\n\n\n\n\n\n0\n\n\n0\n\n\n-7834265884293137617L\n\n\nRegion_2\n\n\nRegion_2\n\n\n215\n\n\n\n\n0\n\n\n0\n\n\n-7834265884293137617L\n\n\nRegion_0\n\n\nRegion_0\n\n\n188\n\n\n\n\n0\n\n\n0\n\n\n-7834265884293137617L\n\n\nRegion_3\n\n\nRegion_3\n\n\n208\n\n\n\n\n0\n\n\n0\n\n\n-7834265884293137617L\n\n\nRegion_4\n\n\nRegion_4\n\n\n215\n\n\n\n\n0\n\n\n0\n\n\n-7834265884293137617L\n\n\nRegion_1\n\n\nRegion_1\n\n\n174\n\n\n\n\n0\n\n\n0\n\n\n-7834265884293137617L\n\n\nRegion_4\n\n\nRegion_3\n\n\n26\n\n\n\n\n0\n\n\n0\n\n\n-7834265884293137617L\n\n\nRegion_4\n\n\nRegion_0\n\n\n36\n\n\n\n\n0\n\n\n0\n\n\n-7834265884293137617L\n\n\nRegion_3\n\n\nRegion_4\n\n\n24\n\n\n\n\n…\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n1\n\n\n1\n\n\n-7320358285742045393L\n\n\nRegion_2\n\n\nRegion_2\n\n\n215\n\n\n\n\n1\n\n\n1\n\n\n-7320358285742045393L\n\n\nRegion_0\n\n\nRegion_0\n\n\n188\n\n\n\n\n1\n\n\n1\n\n\n-7320358285742045393L\n\n\nRegion_3\n\n\nRegion_3\n\n\n208\n\n\n\n\n1\n\n\n1\n\n\n-7320358285742045393L\n\n\nRegion_4\n\n\nRegion_4\n\n\n215\n\n\n\n\n1\n\n\n1\n\n\n-7320358285742045393L\n\n\nRegion_1\n\n\nRegion_1\n\n\n174\n\n\n\n\n…\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n2\n\n\n2\n\n\n-4619948863677044400L\n\n\nRegion_3\n\n\nRegion_0\n\n\n26\n\n\n\n\n2\n\n\n2\n\n\n-4619948863677044400L\n\n\nRegion_3\n\n\nRegion_1\n\n\n33\n\n\n\n\n2\n\n\n2\n\n\n-4619948863677044400L\n\n\nRegion_0\n\n\nRegion_4\n\n\n22\n\n\n\n\n2\n\n\n2\n\n\n-4619948863677044400L\n\n\nRegion_1\n\n\nRegion_4\n\n\n29\n\n\n\n\n2\n\n\n2\n\n\n-4619948863677044400L\n\n\nRegion_4\n\n\nRegion_0\n\n\n32\n\n\n\n\n…\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n4\n\n\n4\n\n\n-7584580254621783722L\n\n\nRegion_8\n\n\nRegion_4\n\n\n1\n\n\n\n\n4\n\n\n4\n\n\n-7584580254621783722L\n\n\nRegion_9\n\n\nRegion_1\n\n\n1\n\n\n\n\n4\n\n\n4\n\n\n-7584580254621783722L\n\n\nRegion_9\n\n\nRegion_0\n\n\n2\n\n\n\n\n4\n\n\n4\n\n\n-7584580254621783722L\n\n\nRegion_9\n\n\nRegion_6\n\n\n2\n\n\n\n\n\n\n\n\n\nIn the region property report (Figure 8.2) we see that the LAT and LON properties were set for the first five regions at the start of the simulation. Starting on day 1, new regions were added and each has an assigned LAT and LON value at that time. Beginning on day 50, all regions are assigned a VACCINE_PRIOITY value and assignments to that property continue daily for random regions.\n\n\n\nFigure 8.2: Excerpts from the region property report.\n\n\n\n\n\n\nscenario\n\n\nseed_index\n\n\nseed_value\n\n\ntime\n\n\nregion\n\n\nproperty\n\n\nvalue\n\n\n\n\n\n\n0\n\n\n0\n\n\n-7834265884293137617L\n\n\n0.0\n\n\nRegion_0\n\n\nLAT\n\n\n45.08307901948476\n\n\n\n\n0\n\n\n0\n\n\n-7834265884293137617L\n\n\n0.0\n\n\nRegion_0\n\n\nLON\n\n\n128.5497267736204\n\n\n\n\n0\n\n\n0\n\n\n-7834265884293137617L\n\n\n0.0\n\n\nRegion_1\n\n\nLAT\n\n\n45.73317078392115\n\n\n\n\n0\n\n\n0\n\n\n-7834265884293137617L\n\n\n0.0\n\n\nRegion_1\n\n\nLON\n\n\n128.98292164396958\n\n\n\n\n0\n\n\n0\n\n\n-7834265884293137617L\n\n\n0.0\n\n\nRegion_2\n\n\nLAT\n\n\n45.74702447122078\n\n\n\n\n0\n\n\n0\n\n\n-7834265884293137617L\n\n\n0.0\n\n\nRegion_2\n\n\nLON\n\n\n128.5118606592755\n\n\n\n\n0\n\n\n0\n\n\n-7834265884293137617L\n\n\n0.0\n\n\nRegion_3\n\n\nLAT\n\n\n45.8303102139607\n\n\n\n\n0\n\n\n0\n\n\n-7834265884293137617L\n\n\n0.0\n\n\nRegion_3\n\n\nLON\n\n\n128.55192626408567\n\n\n\n\n0\n\n\n0\n\n\n-7834265884293137617L\n\n\n0.0\n\n\nRegion_4\n\n\nLAT\n\n\n45.59334365958997\n\n\n\n\n0\n\n\n0\n\n\n-7834265884293137617L\n\n\n0.0\n\n\nRegion_4\n\n\nLON\n\n\n128.7915941303198\n\n\n\n\n0\n\n\n0\n\n\n-7834265884293137617L\n\n\n1.0\n\n\nRegion_5\n\n\nLAT\n\n\n35.99754757587744\n\n\n\n\n0\n\n\n0\n\n\n-7834265884293137617L\n\n\n1.0\n\n\nRegion_5\n\n\nLON\n\n\n128.2594279657217\n\n\n\n\n0\n\n\n0\n\n\n-7834265884293137617L\n\n\n21.0\n\n\nRegion_6\n\n\nLAT\n\n\n35.84682720256188\n\n\n\n\n0\n\n\n0\n\n\n-7834265884293137617L\n\n\n21.0\n\n\nRegion_6\n\n\nLON\n\n\n128.2211833000355\n\n\n\n\n0\n\n\n0\n\n\n-7834265884293137617L\n\n\n41.0\n\n\nRegion_7\n\n\nLAT\n\n\n35.07267582475035\n\n\n\n\n0\n\n\n0\n\n\n-7834265884293137617L\n\n\n41.0\n\n\nRegion_7\n\n\nLON\n\n\n128.37457313813837\n\n\n\n\n0\n\n\n0\n\n\n-7834265884293137617L\n\n\n50.0\n\n\nRegion_0\n\n\nVACCINE_PRIORITY\n\n\nfalse\n\n\n\n\n0\n\n\n0\n\n\n-7834265884293137617L\n\n\n50.0\n\n\nRegion_1\n\n\nVACCINE_PRIORITY\n\n\nfalse\n\n\n\n\n…\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n4\n\n\n4\n\n\n-7584580254621783722L\n\n\n93.0\n\n\nRegion_2\n\n\nVACCINE_PRIORITY\n\n\nfalse\n\n\n\n\n4\n\n\n4\n\n\n-7584580254621783722L\n\n\n94.0\n\n\nRegion_1\n\n\nVACCINE_PRIORITY\n\n\ntrue\n\n\n\n\n4\n\n\n4\n\n\n-7584580254621783722L\n\n\n95.0\n\n\nRegion_2\n\n\nVACCINE_PRIORITY\n\n\ntrue\n\n\n\n\n4\n\n\n4\n\n\n-7584580254621783722L\n\n\n96.0\n\n\nRegion_7\n\n\nVACCINE_PRIORITY\n\n\ntrue\n\n\n\n\n4\n\n\n4\n\n\n-7584580254621783722L\n\n\n97.0\n\n\nRegion_0\n\n\nVACCINE_PRIORITY\n\n\nfalse\n\n\n\n\n4\n\n\n4\n\n\n-7584580254621783722L\n\n\n98.0\n\n\nRegion_1\n\n\nVACCINE_PRIORITY\n\n\nfalse\n\n\n\n\n4\n\n\n4\n\n\n-7584580254621783722L\n\n\n99.0\n\n\nRegion_0\n\n\nVACCINE_PRIORITY\n\n\ntrue\n\n\n\n\n\n\n\n\n\nFinally, the vaccine report shows the number of people having various vaccine counts at the end of each simulation. Although the priority policy was being used, most vaccinations were for randomly selected people so we expect a fairly wide distribution in those values.\n\n\n\nFigure 8.3: The vaccine report shows vaccine counts at the end of each simulation.\n\n\n\n\n\n\nscenario\n\n\nseed_index\n\n\nseed_value\n\n\ncount_0\n\n\ncount_1\n\n\ncount_2\n\n\ncount_3\n\n\ncount_4\n\n\ncount_5\n\n\ncount_6+\n\n\n\n\n\n\n0\n\n\n0\n\n\n-7834265884293137617L\n\n\n13\n\n\n67\n\n\n145\n\n\n168\n\n\n170\n\n\n148\n\n\n289\n\n\n\n\n1\n\n\n1\n\n\n-7320358285742045393L\n\n\n13\n\n\n63\n\n\n141\n\n\n181\n\n\n187\n\n\n133\n\n\n282\n\n\n\n\n2\n\n\n2\n\n\n-4619948863677044400L\n\n\n17\n\n\n53\n\n\n157\n\n\n196\n\n\n176\n\n\n140\n\n\n261\n\n\n\n\n3\n\n\n3\n\n\n3282202756261196294L\n\n\n10\n\n\n72\n\n\n159\n\n\n166\n\n\n163\n\n\n146\n\n\n284\n\n\n\n\n4\n\n\n4\n\n\n-7584580254621783722L\n\n\n4\n\n\n66\n\n\n153\n\n\n198\n\n\n163\n\n\n136\n\n\n280" + }, + { + "objectID": "ch09-PersonPropertiesPlugin.html#plugin-data-initialization", + "href": "ch09-PersonPropertiesPlugin.html#plugin-data-initialization", + "title": "9  Person Properties Plugin", + "section": "9.1 Plugin Data Initialization", + "text": "9.1 Plugin Data Initialization\nThe plugin is initialized using a PersonPropertiesPluginData object that collects person property definitions and person property value assignments for the initial population." + }, + { + "objectID": "ch09-PersonPropertiesPlugin.html#plugin-behavior", + "href": "ch09-PersonPropertiesPlugin.html#plugin-behavior", + "title": "9  Person Properties Plugin", + "section": "9.2 Plugin Behavior", + "text": "9.2 Plugin Behavior\nThe plugin adds a single data manager to the simulation as an instance of the PersonPropertiesDataManager that is initialized with the PersonPropertiesPluginData." + }, + { + "objectID": "ch09-PersonPropertiesPlugin.html#data-manager", + "href": "ch09-PersonPropertiesPlugin.html#data-manager", + "title": "9  Person Properties Plugin", + "section": "9.3 Data Manager", + "text": "9.3 Data Manager\nThe data manager manages person properties and stores their property values in a memory dense fashion that is transparent to the modeler. The data manager provides public methods that:\n\nDefine person properties\nSet person property values\nAnswer various questions about:\n\nThe value of a person property for particular people\nThe people associated with a particular property value\nThe existence and value of property definitions\n\n\nThe data manager also produces observable events:\n\nPersonPropertyUpdateEvent – when a person is assigned a person property value\nPersonPropertyDefintionEvent – when a new person property is defined" + }, + { + "objectID": "ch09-PersonPropertiesPlugin.html#example-code-lesson-16", + "href": "ch09-PersonPropertiesPlugin.html#example-code-lesson-16", + "title": "9  Person Properties Plugin", + "section": "9.4 Example Code (Lesson 16)", + "text": "9.4 Example Code (Lesson 16)\nExample_16.java shows the use of the person properties plugin. In it we will examine\n\nThe initialization of the person properties plugin\nThe assignment of values to individuals\nThe dynamic definition of person properties\n\nThe example includes six plugins:\n\nPeople plugin – (GCM core plugin) used to manage people\nPerson properties plugin– (GCM core plugin) used to decorate properties onto people\nGlobal properties plugin– (GCM core plugin) used to store policies and initial conditions affecting vaccination\nStochastics plugin – (GCM core plugin) used to generate random numbers used in various decisions\nRegions Plugin – (GCM core plugin) used to represent regions\nModel plugin – (local plugin) used to introduce three actors that will:\n\nLoad the population\nVaccinate people\nEducate people on the vaccine\n\n\nThe example’s main method starts in Code Block 9.1 by creating an instance of the example class rather than building the experiment directly since this example is somewhat more complex than previous examples. The population starts out being unvaccinated and some proportion of people initially refuse the vaccine. Attempts to both vaccinate and educate people are ongoing until a person is vaccinated. This will demonstrate the planning capability as well. If education is successful, the person is immediately vaccinated, demonstrating the cancellation of planning. At some point in the timeline, immunity will become discoverable during vaccine attempts and immune people will no longer pursue vaccination. The simulation is terminated at one year and output reports are then generated.\n\n\nCode Block 9.1: Executing example 16 with an output directory.\npublic static void main(String[] args) throws IOException {\n if (args.length == 0) {\n throw new RuntimeException(\"One output directory argument is required\");\n }\n Path outputPath = Paths.get(args[0]);\n if (!Files.exists(outputPath)) {\n Files.createDirectory(outputPath);\n } else {\n if (!Files.isDirectory(outputPath)) {\n throw new IOException(\"Provided path is not a directory\");\n }\n }\n new Example_16(outputPath).execute();\n}\n\n\nThe execution method first gathers together the six plugins in Code Block 9.2:\n\n\nCode Block 9.2: The various plugins are gathered from their initial data.\nprivate void execute() {\n\n /*\n * Create the global properties plugin\n */\n Plugin globalPropertiesPlugin = getGlobalPropertiesPlugin();\n\n /*\n * Create the reports\n */\n\n NIOReportItemHandler nioReportItemHandler = getNIOReportItemHandler();\n\n /*\n * Create the people plugin filled with 1000 people\n */\n Plugin peoplePlugin = getPeoplePlugin();\n\n /*\n * Create the region plugin 5 regions, each having a lat and lon and assign the\n * people to random regions.\n * \n */\n Plugin regionsPlugin = getRegionsPlugin();\n\n // Create the person properties plugin\n Plugin personPropertiesPlugin = getPersonPropertiesPlugin();\n\n /*\n * create the stochastics plugin\n */\n Plugin stochasticsPlugin = getStochasticsPlugin();\n\n Plugin modelPlugin = ModelPlugin.getModelPlugin();\n\n\nThe first action is to generate the global properties plugin, (Code Block 9.3). All of the global properties are marked as immutable since they will not change over the course of the simulation:\n\nVACCINE_ATTEMPT_INTERVAL – The maximum time between attempts to vaccinate an unvaccinated person. Specific intervals are chosen using a uniform random time between zero and the maximum.\nEDUCATION_ATTEMPT_INTERVAL – The maximum time between attempts to educate an unvaccinated person who is currently refusing vaccination. Specific intervals are chosen using a uniform random time between zero and the maximum.\nVACCINE_REFUSAL_PROPBABILTY – The initial probability that a person will refuse vaccination. Used to initialize the person property REFUSES_VACCINE.\nEDUCATION_SUCCESS_RATE – The probability that an attempt to educate a person to accept vaccination will succeed.\nIMMUNITY_START_TIME – The time when immunity is detectable in people. Used to halt attempts at vaccination and to demonstrate the dynamic addition of the person property IS_IMMUNE.\nIMMUNITY_PROBABILITY – The probability that a person will be immune when the IS_IMMUNE person property is added.\nPOPULATION_SIZE – The number of people in the simulation.\nSIMULATION_DURATION – The maximum time (in days) that the simulation will execute.\n\n\n\nCode Block 9.3: The global properties plugin is initialized with several properties.\nprivate Plugin getGlobalPropertiesPlugin() {\n GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder();//\n\n PropertyDefinition propertyDefinition = PropertyDefinition.builder()//\n .setType(Double.class)//\n .setDefaultValue(0.0)//\n .setPropertyValueMutability(false)//\n .build();\n\n builder.defineGlobalProperty(GlobalProperty.IMMUNITY_START_TIME, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.VACCINE_ATTEMPT_INTERVAL, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.EDUCATION_ATTEMPT_INTERVAL, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.EDUCATION_SUCCESS_RATE, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.VACCINE_REFUSAL_PROBABILITY, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.IMMUNITY_PROBABILITY, propertyDefinition, 0);\n\n propertyDefinition = PropertyDefinition.builder()//\n .setType(Double.class)//\n .setDefaultValue(365.0)//\n .setPropertyValueMutability(false)//\n .build();\n builder.defineGlobalProperty(GlobalProperty.SIMULATION_DURATION, propertyDefinition, 0);\n\n propertyDefinition = PropertyDefinition.builder()//\n .setType(Integer.class)//\n .setDefaultValue(1000)//\n .setPropertyValueMutability(false)//\n .build();\n\n builder.defineGlobalProperty(GlobalProperty.POPULATION_SIZE, propertyDefinition, 0);\n\n GlobalPropertiesPluginData globalPropertiesPluginData = builder.build();\n\n return GlobalPropertiesPlugin.builder().setGlobalPropertiesPluginData(globalPropertiesPluginData)\n .getGlobalPropertiesPlugin();\n\n}\n\n\nThe execution method then loads reports (Code Block 9.4). The person property report will be quite large and is set to only show the state of each person at the end of the simulation for brevity. The vaccine report will show the state of vaccination and immunity at the end of the simulation to allow for analysis of the experiment.\nThe people plugin is created without any initial people since that will be handled by one of the model plugin’s actors. The regions plugin is initialized with five regions and only plays a role in the person property report.\nThe person properties plugin is generated in Code Block 9.4. All of the person properties are mutable since they will change over the course of the simulation:\n\nEDUCATION_ATTEMPTS – The number of attempts to change a person’s vaccine refusal.\nVACCINE_ATTEMPTS – The number of attempts to vaccinate a person.\nREFUSES_VACCINE – Boolean indicating whether the person will refuse vaccination attempts. Note that there is no default value and that new people must have this property set as part of the addition of the person to the simulation.\nVACCINATED – Boolean indicating that a person has been vaccinated. People all start out with no vaccination and receive at most one vaccination.\n\nNote that the final person property, IS_IMMUNE, is not added at the beginning of the simulation as a demonstration of the dynamic addition of person properties.\n\n\nCode Block 9.4: The person properties plugin is built with the four person properties needed to model each person. The person property report is set to report only at the end of the simulation.\nprivate Plugin getPersonPropertiesPlugin() {\n PersonPropertiesPluginData.Builder builder = PersonPropertiesPluginData.builder();\n PropertyDefinition propertyDefinition = PropertyDefinition.builder()//\n .setType(Integer.class)//\n .setDefaultValue(0)//\n .build();\n builder.definePersonProperty(PersonProperty.EDUCATION_ATTEMPTS, propertyDefinition, 0, false);\n builder.definePersonProperty(PersonProperty.VACCINE_ATTEMPTS, propertyDefinition, 0, false);\n\n propertyDefinition = PropertyDefinition.builder()//\n .setType(Boolean.class)//\n .build();\n builder.definePersonProperty(PersonProperty.REFUSES_VACCINE, propertyDefinition, 0, false);\n\n propertyDefinition = PropertyDefinition.builder()//\n .setType(Boolean.class)//\n .setDefaultValue(false)//\n .build();\n builder.definePersonProperty(PersonProperty.VACCINATED, propertyDefinition, 0, false);\n\n PersonPropertiesPluginData personPropertiesPluginData = builder.build();\n\n PersonPropertyReportPluginData personPropertyReportPluginData = PersonPropertyReportPluginData.builder()//\n .setReportLabel(ModelReportLabel.PERSON_PROPERTY_REPORT)//\n .setReportPeriod(ReportPeriod.END_OF_SIMULATION)//\n .setDefaultInclusion(true)//\n .build();//\n\n return PersonPropertiesPlugin.builder()//\n .setPersonPropertiesPluginData(personPropertiesPluginData)//\n .setPersonPropertyReportPluginData(personPropertyReportPluginData)//\n .getPersonPropertyPlugin();\n}\n\n\nAdding the stochastics plugin involves setting only the seed that will be used in every simulation instance. It will not play a role in defining the experiment space since that will be quite large already with various global property variants. Finally, the execution method generates the model plugin which in turn adds three actors:\n\nVaccinator – vaccinates people at random times\nVaccine Educator – seeks to get people to accept vaccination\nPopulation Loader – initializes the population\n\nThe execute method finishes (Code Block 9.5) by constructing and executing the experiment.\n\n\nCode Block 9.5: The experiment executes 810 scenarios on 8 threads.\n ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()//\n .setThreadCount(8)//\n .build();\n\n Experiment.builder()//\n\n .addPlugin(personPropertiesPlugin)//\n .addPlugin(globalPropertiesPlugin)//\n .addPlugin(modelPlugin)//\n .addPlugin(regionsPlugin)//\n .addPlugin(peoplePlugin)//\n .addPlugin(stochasticsPlugin)//\n\n .addDimension(getImmunityStartTimeDimension())//\n .addDimension(getImmunityProbabilityDimension())//\n .addDimension(getVaccineAttemptIntervalDimension())//\n .addDimension(getEducationAttemptIntervalDimension())//\n .addDimension(getEducationSuccessRatedimension())//\n .addDimension(getVaccineRefusalProbabilityDimension())//\n .addExperimentContextConsumer(nioReportItemHandler)//\n .setExperimentParameterData(experimentParameterData)//\n .build()//\n .execute();//\n\n\nFive dimensions are added to the experiment that define alternate values for five of the global properties resulting in 810 scenarios. These values are:\n\nImmunity start time – 120 and 180 days\nImmunity probability – 0, 10 and 20 percent\nVaccine attempt interval – 30, 45 and 60 days\nEducation attempt interval – 30, 60 and 180 days\nEducation success rate – 0, 10 and 20 percent\nInitial vaccine refusal – 0, 25, 50, 75 and 100 percent" + }, + { + "objectID": "ch09-PersonPropertiesPlugin.html#the-actors", + "href": "ch09-PersonPropertiesPlugin.html#the-actors", + "title": "9  Person Properties Plugin", + "section": "9.5 The actors", + "text": "9.5 The actors\nWe will finish this chapter by reviewing the three actors of the model plugin and then examining the vaccine report.\nThe PopulationLoader actor, in Code Block 9.6, adds people to the simulation based on the number in the POPULATION_SIZE global property. Each person is assigned a random region and the person property, REFUSES_VACCINE, is randomly assigned based on the global property VACCINE_REFUSAL_PROBABILITY.\n\n\nCode Block 9.6: The population loader initializes by creating people dictated by the POPULATION_SIZE global property. Each person is assigned a region and random value for the person property, REFUSES_VACCINE, based on the global property, VACCINE_REFUSAL_PROBABILITY.\npublic void init(ActorContext actorContext) {\n peopleDataManager = actorContext.getDataManager(PeopleDataManager.class);\n personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class);\n globalPropertiesDataManager = actorContext.getDataManager(GlobalPropertiesDataManager.class);\n RegionsDataManager regionsDataManager = actorContext.getDataManager(RegionsDataManager.class);\n StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);\n randomGenerator = stochasticsDataManager.getRandomGenerator();\n List regionIds = new ArrayList<>(regionsDataManager.getRegionIds());\n\n int populationSize = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.POPULATION_SIZE);\n double refusalProbability = globalPropertiesDataManager\n .getGlobalPropertyValue(GlobalProperty.VACCINE_REFUSAL_PROBABILITY);\n\n Builder personConstructionDataBuilder = PersonConstructionData.builder();\n for (int i = 0; i < populationSize; i++) {\n RegionId regionId = regionIds.get(randomGenerator.nextInt(regionIds.size()));\n personConstructionDataBuilder.add(regionId);\n\n boolean refusesVaccine = randomGenerator.nextDouble() < refusalProbability;\n PersonPropertyValueInitialization personPropertyInitialization = new PersonPropertyValueInitialization(\n PersonProperty.REFUSES_VACCINE, refusesVaccine);\n personConstructionDataBuilder.add(personPropertyInitialization);\n PersonConstructionData personConstructionData = personConstructionDataBuilder.build();\n peopleDataManager.addPerson(personConstructionData);\n }\n\n double simulationDuration = globalPropertiesDataManager\n .getGlobalPropertyValue(GlobalProperty.SIMULATION_DURATION);\n actorContext.addPlan((c) -> c.halt(), simulationDuration);\n\n double immunityStartTime = globalPropertiesDataManager\n .getGlobalPropertyValue(GlobalProperty.IMMUNITY_START_TIME);\n actorContext.addPlan((c) -> addImmunityProperty(), immunityStartTime);\n}\n\n\nThe actor finishes its initialization by scheduling a time to halt the simulation based on the global property, SIMULATION_DURATION. It also schedules the addition of the person property, IS_IMMUNE, based on the global property, IMMUNITY_START_TIME. Code Block 9.7 shows the details of this dynamic definition.\n\n\nCode Block 9.7: At the time set via the global property, IMMUNITY_START_TIME, the population loader defines the person property, IS_IMMUNE, and sets the property value for each person.\nprivate void addImmunityProperty() {\n PersonPropertyDefinitionInitialization.Builder builder = PersonPropertyDefinitionInitialization.builder();\n builder.setPersonPropertyId(PersonProperty.IS_IMMUNE);\n PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Boolean.class).build();\n builder.setPropertyDefinition(propertyDefinition);\n double immunityProbability = globalPropertiesDataManager\n .getGlobalPropertyValue(GlobalProperty.IMMUNITY_PROBABILITY);\n\n for (PersonId personId : peopleDataManager.getPeople()) {\n boolean isImmune = randomGenerator.nextDouble() < immunityProbability;\n builder.addPropertyValue(personId, isImmune);\n }\n PersonPropertyDefinitionInitialization personPropertyDefinitionInitialization = builder.build();\n personPropertiesDataManager.definePersonProperty(personPropertyDefinitionInitialization);\n}\n\n\nThe vaccine educator (Code Block 9.8) attempts to change unvaccinated people who refuse vaccination to vaccine acceptance. It initializes by planning an educational attempt for each person in the existing population who has not been vaccinated and who will refuse vaccination. It also subscribes to the addition of people so that it might plan education for newly added people.\n\n\nCode Block 9.8: The vaccine educator initializes by planning the education for each person who refuses vaccination.\npublic void init(ActorContext actorContext) {\n this.actorContext = actorContext;\n\n StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);\n randomGenerator = stochasticsDataManager.getRandomGenerator();\n PeopleDataManager peopleDataManager = actorContext.getDataManager(PeopleDataManager.class);\n personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class);\n globalPropertiesDataManager = actorContext.getDataManager(GlobalPropertiesDataManager.class);\n\n educationAttemptInterval = globalPropertiesDataManager\n .getGlobalPropertyValue(GlobalProperty.EDUCATION_ATTEMPT_INTERVAL);\n educationSuccessRate = globalPropertiesDataManager\n .getGlobalPropertyValue(GlobalProperty.EDUCATION_SUCCESS_RATE);\n\n List unvaccinatedPeople = personPropertiesDataManager\n .getPeopleWithPropertyValue(PersonProperty.VACCINATED, false);\n for (PersonId personId : unvaccinatedPeople) {\n Boolean refusesVaccine = personPropertiesDataManager.getPersonPropertyValue(personId,\n PersonProperty.REFUSES_VACCINE);\n if (refusesVaccine) {\n planEducation(personId);\n }\n }\n\n actorContext.subscribe(peopleDataManager.getEventFilterForPersonAdditionEvent(), (c, e) -> {\n handleNewPerson(e.personId());\n });\n}\n\n\nThe education of a person (Code Block 9.9) is accomplished with planning that schedules the education at a random time between the current time and a globally defined attempt interval.\n\n\nCode Block 9.9: Attempts to educate a person on vaccination are scheduled at random times in the future based on the global property, EDUCATION_ATTEMPT_INTERVAL.\nprivate void planEducation(PersonId personId) {\n double planTime = actorContext.getTime() + randomGenerator.nextDouble() * educationAttemptInterval;\n Consumer plan = (c) -> educatePerson(personId);\n actorContext.addPlan(plan, planTime);\n}\n\nprivate void handleNewPerson(PersonId personId) {\n boolean vaccinated = personPropertiesDataManager.getPersonPropertyValue(personId, PersonProperty.VACCINATED);\n if (!vaccinated) {\n Boolean refusesVaccine = personPropertiesDataManager.getPersonPropertyValue(personId,\n PersonProperty.REFUSES_VACCINE);\n if (refusesVaccine) {\n planEducation(personId);\n }\n }\n}\n\n\nThe education attempt sets the vaccine refusal to false on a random draw based on the EDUCATION_SUCCESS_RATE global variable in Code Block 9.10.\n\n\nCode Block 9.10: After updating the number of educational attempts for a person, the vaccine educator succeeds in educating the person to stop refusing vaccination based on the global property, EDUCATION_SUCCESS_RATE.\nprivate void educatePerson(PersonId personId) {\n int educationAttempts = personPropertiesDataManager.getPersonPropertyValue(personId,\n PersonProperty.EDUCATION_ATTEMPTS);\n personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.EDUCATION_ATTEMPTS,\n educationAttempts + 1);\n\n if (randomGenerator.nextDouble() < educationSuccessRate) {\n personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.REFUSES_VACCINE, false);\n } else {\n planEducation(personId);\n }\n}\n\n\nThe Vaccinator (Code Block 9.11) tries to vaccinate the population. It initializes by planning a vaccination attempt for each person in the existing population who has not yet been vaccinated. It subscribes to the addition of people so that it might plan vaccination for newly added people. It also subscribes to changes to the VACCINE_REFUSAL person property so that it can immediately attempt vaccination.\n\n\nCode Block 9.11: The vaccinator initializes by planning vaccination attempts for each person who is unvaccinated. It also subscribes to changes in the vaccine refusal property for all people so that when a person stops refusing vaccine, they can be vaccinated immediately.\npublic void init(ActorContext actorContext) {\n this.actorContext = actorContext;\n StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);\n randomGenerator = stochasticsDataManager.getRandomGenerator();\n peopleDataManager = actorContext.getDataManager(PeopleDataManager.class);\n personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class);\n globalPropertiesDataManager = actorContext.getDataManager(GlobalPropertiesDataManager.class);\n\n List unvaccinatedPeople = personPropertiesDataManager\n .getPeopleWithPropertyValue(PersonProperty.VACCINATED, false);\n vaccineAttemptInterval = globalPropertiesDataManager\n .getGlobalPropertyValue(GlobalProperty.VACCINE_ATTEMPT_INTERVAL);\n for (PersonId personId : unvaccinatedPeople) {\n planVaccination(personId);\n }\n\n EventFilter eventFilter = personPropertiesDataManager//\n .getEventFilterForPersonPropertyUpdateEvent(PersonProperty.REFUSES_VACCINE);\n\n actorContext.subscribe(eventFilter, this::handleVaccineAcceptance);\n\n actorContext.subscribe(peopleDataManager.getEventFilterForPersonAdditionEvent(), (c, e) -> {\n handleNewPerson(e.personId());\n });\n\n}\n\n\nVaccination of a person (Code Block 9.12) is accomplished with planning that schedules the vaccination at a random time between the current time and a globally defined attempt interval.\n\n\nCode Block 9.12: Each unvaccinated person has a planned vaccination based on the VACCINE_ATTEMPT_INTERVAL global property.\nprivate void planVaccination(PersonId personId) {\n double planTime = actorContext.getTime() + randomGenerator.nextDouble() * vaccineAttemptInterval;\n Object planKey = personId;\n Plan plan = Plan.builder(ActorContext.class)//\n .setCallbackConsumer((c) -> vaccinatePerson(personId))//\n .setKey(planKey)//\n .setTime(planTime)//\n .build();//\n actorContext.addPlan(plan);\n}\n\nprivate void handleNewPerson(PersonId personId) {\n boolean vaccinated = personPropertiesDataManager.getPersonPropertyValue(personId, PersonProperty.VACCINATED);\n if (!vaccinated) {\n planVaccination(personId);\n }\n}\n\n\nNote that the plan uses a key value set to the person id. This is used when reacting to a person changing from refusal of the vaccine to acceptance. Instead of waiting for the next vaccine attempt (Code Block 9.13), the current plan to vaccinate is removed and the person is immediately vaccinated.\n\n\nCode Block 9.13: When a person stops refusing vaccination, the vaccinator immediately attempts the vaccination of that person.\nprivate void handleVaccineAcceptance(ActorContext actorContext,\n PersonPropertyUpdateEvent personPropertyUpdateEvent) {\n /*\n * We know that the person property is PersonProperty.REFUSES_VACCINE since we\n * used an event filter when subscribing\n */\n Boolean refusesVaccine = personPropertyUpdateEvent.getCurrentPropertyValue();\n if (!refusesVaccine) {\n PersonId personId = personPropertyUpdateEvent.personId();\n // drop the current plan\n actorContext.removePlan(personId);\n vaccinatePerson(personId);\n }\n}\n\n\nThe vaccination attempt (Code Block 9.14) first considers whether the IS_IMMUNE property has been added. If it has then immunity for the person is determined. Immune people do not receive the vaccine and no more attempts to vaccinate the person will be scheduled. If the person is still refusing the vaccine, then a new attempt to vaccinate the person is scheduled. Otherwise the person is vaccinated and no further attempts are scheduled.\n\n\nCode Block 9.14: With each vaccination attempt, the vaccinator updates the VACCINE_ATTEMPTS person property for the person. People who refuse vaccination are scheduled for another vaccination attempt.\nprivate void vaccinatePerson(PersonId personId) {\n int vaccineAttempts = personPropertiesDataManager.getPersonPropertyValue(personId,\n PersonProperty.VACCINE_ATTEMPTS);\n personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.VACCINE_ATTEMPTS,\n vaccineAttempts + 1);\n\n boolean isImmune = false;\n if (personPropertiesDataManager.personPropertyIdExists(PersonProperty.IS_IMMUNE)) {\n isImmune = personPropertiesDataManager.getPersonPropertyValue(personId, PersonProperty.IS_IMMUNE);\n }\n\n Boolean refusesVaccine = personPropertiesDataManager.getPersonPropertyValue(personId,\n PersonProperty.REFUSES_VACCINE);\n if (!isImmune) {\n if (refusesVaccine) {\n double planTime = actorContext.getTime() + randomGenerator.nextDouble() * vaccineAttemptInterval;\n Object planKey = personId;\n\n Plan plan = Plan.builder(ActorContext.class)//\n .setCallbackConsumer((c) -> vaccinatePerson(personId))//\n .setKey(planKey)//\n .setTime(planTime)//\n .build();//\n\n actorContext.addPlan(plan);\n } else {\n personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.VACCINATED, true);\n }\n }\n}" + }, + { + "objectID": "ch09-PersonPropertiesPlugin.html#inspecting-the-output", + "href": "ch09-PersonPropertiesPlugin.html#inspecting-the-output", + "title": "9  Person Properties Plugin", + "section": "9.6 Inspecting the output", + "text": "9.6 Inspecting the output\nThe 810 scenarios result in a large amount of output in the person properties report with over 125,000 entries. The vaccine report is a bit too large to fully present here. Its fields are:\n\nScenario – 0 to 809\nExperiment fields that show what differentiates each scenario\n\nimmunity start time\nimmunity probabilty\nvaccine attempt interval\neducation attempt interval\neducation success rate\nintial refusal probability\n\nThe metric fields produced as a result of the experiment choices\n\nvaccinated immune\nvaccinated susceptible\nunvaccinated immune\nunvaccinated susceptible\n\n\nAnalyzing the output yields no surprises. Higher education attempt rates and greater probabilities of education success yield more people getting vaccinated. Similarly, early and high levels of immunity have a slight dampening effect on vaccinations." + }, + { + "objectID": "ch10-GroupsPlugins.html#plugin-data-dependency", + "href": "ch10-GroupsPlugins.html#plugin-data-dependency", + "title": "10  Groups Plugin", + "section": "10.1 Plugin Data Dependency", + "text": "10.1 Plugin Data Dependency\nThe groups plugin depends on the people plugin and the stochastics plugin. The stochastics plugin is used for random sampling of people from groups." + }, + { + "objectID": "ch10-GroupsPlugins.html#plugin-data-initialization", + "href": "ch10-GroupsPlugins.html#plugin-data-initialization", + "title": "10  Groups Plugin", + "section": "10.2 Plugin Data initialization", + "text": "10.2 Plugin Data initialization\nThe plugin is initialized using a GroupsPluginData object that:\n\nadds group types\ndefines group properties for each group type\nsets group property value per group\nadds groups\ninitializes the person membership in groups" + }, + { + "objectID": "ch10-GroupsPlugins.html#plugin-behavior", + "href": "ch10-GroupsPlugins.html#plugin-behavior", + "title": "10  Groups Plugin", + "section": "10.3 Plugin Behavior", + "text": "10.3 Plugin Behavior\nThe plugin adds a single data manager to the simulation as an instance of the GroupsDataManager that is initialized with the GroupsPluginData." + }, + { + "objectID": "ch10-GroupsPlugins.html#data-manager", + "href": "ch10-GroupsPlugins.html#data-manager", + "title": "10  Groups Plugin", + "section": "10.4 Data Manager", + "text": "10.4 Data Manager\nThe data manager manages group memberships and group property values. The data manager provides public methods that:\n\nAdd/Remove a group\nAdd a group type\nAdd/Remove a person to/from a group\nDefine a group property\nSet a group property value\nSample a random person from a group\nProvide many queries about the state of groups\n\nThe data manager also produces observable events:\n\nGroupAdditionEvent – when a group is added\nGroupImminentRemovalEvent – when a group is about to be removed\nGroupMembershipAdditionEvent – when a person is added to a group\nGroupMembershipRemovalEvent – when a person is removed from a group\nGroupPropertyDefinitionEvent – when a new group property is defined\nGroupPropertyUpdateEvent – when a group property value is updated\nGroupTypeAdditionEvent – when a new group type is added" + }, + { + "objectID": "ch10-GroupsPlugins.html#example-code-lesson-17", + "href": "ch10-GroupsPlugins.html#example-code-lesson-17", + "title": "10  Groups Plugin", + "section": "10.5 Example Code (Lesson 17)", + "text": "10.5 Example Code (Lesson 17)\nExample_17.java shows the use of the groups plugin. In it we will examine:\n\nThe initialization of the groups plugin\nThe movement of people in and out of groups\n\nThe example includes seven plugins:\n\nGroups plugin – (GCM core plugin) used to manage groups\nPeople plugin – (GCM core plugin) used to manage people\nPerson properties plugin – (GCM core plugin) used to decorate properties onto people\nGlobal properties plugin – (GCM core plugin) used to store policies and initial conditions affecting groups and disease transmission\nStochastics plugin – (GCM core plugin) used to generate random numbers used in various decisions\nRegions Plugin – (GCM core plugin) used for various reports\nModel plugin – (local plugin) used to introduce four actors that will:\n\nLoad the population\nManage the spread of disease through transmission in groups\nManage group-based mitigation strategies in schools\nManage group-based mitigation strategies in work places" + }, + { + "objectID": "ch10-GroupsPlugins.html#model", + "href": "ch10-GroupsPlugins.html#model", + "title": "10  Groups Plugin", + "section": "10.6 Model", + "text": "10.6 Model\nThe example’s model represents a disease that is not treatable but can be mitigated through social distancing, school closures and the use of telework arrangements. People at the start of the simulation are either immune or susceptible. Infection is spread through personal contact in groups and is subject to public policies triggered by the number of infectious people present in the total population or in specific groups. The population is set to 10,000 people and the groups are synthetically derived using fixed proportions (global properties) for school aged children (0-18 yrs), working adults (19 to 64 yrs) and non-working seniors (65+yrs). All people are assigned a home group. School aged children are assigned to a single school group and working age adults are assigned to a single work group. The number of each group type is determined by fixed expected groups sizes (global properties).\nAt the beginning of the model, a small number of adults are infected at random. Each infected person is immediately infectious and will infect one person per day over a random period from 3 to 12 days inclusive. Without mitigations, each transmission is successful. Public policies are reviewed on a weekly basis. As the number of total public infections increase, some work groups may elect to move to a telework status, reducing transmissions by 50% in those groups. Similarly, when a school’s level of infection increases it will move to a split cohort mode, reducing transmission success to 50%. If infection within a cohort continues to rise, the cohort is closed and the children are no longer assigned to a school group. For simplicity of modeling, telework arrangements, cohort schools and school closures do not revert as infection levels subside." + }, + { + "objectID": "ch10-GroupsPlugins.html#model-execution", + "href": "ch10-GroupsPlugins.html#model-execution", + "title": "10  Groups Plugin", + "section": "10.7 Model Execution", + "text": "10.7 Model Execution\nThe example’s execution is shown in Code Block 10.1 and Code Block 10.2.\n\n\nCode Block 10.1: Executing example 17 with an output directory.\npublic static void main(String[] args) throws IOException {\n if (args.length == 0) {\n throw new RuntimeException(\"One output directory argument is required\");\n }\n Path outputDirectory = Paths.get(args[0]);\n if (!Files.exists(outputDirectory)) {\n Files.createDirectory(outputDirectory);\n } else {\n if (!Files.isDirectory(outputDirectory)) {\n throw new IOException(\"Provided path is not a directory\");\n }\n }\n\n new Example_17(outputDirectory).execute();\n}\n\n\n\n\nCode Block 10.2: The various plugins are gathered from their initial data, dimensions are added and the experiment is executed over 36 scenarios using 8 threads.\nprivate void execute() {\n\n ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()//\n .setThreadCount(8)//\n .build();\n\n Experiment.builder()\n\n .addPlugin(getGlobalPropertiesPlugin())//\n .addPlugin(getPersonPropertiesPlugin())//\n .addPlugin(getRegionsPlugin())//\n .addPlugin(getPeoplePlugin())//\n .addPlugin(getGroupsPlugin())//\n .addPlugin(getStochasticsPlugin())//\n .addPlugin(ModelPlugin.getModelPlugin())//\n\n .addDimension(getTeleworkProbabilityDimension())//\n .addDimension(getTeleworkInfectionThresholdDimension())//\n .addDimension(getSchoolDimension())//\n\n .addExperimentContextConsumer(getNIOReportItemHandler())//\n .setExperimentParameterData(experimentParameterData)// \n .build()//\n .execute();//\n}\n\n\nThe first action is to load the global properties plugin (Code Block 10.3). The fifteen global properties are marked as immutable since they will not change over the course of the simulation. Further, eleven of the properties are fixed and their values are set in the plugin data. The four remaining properties participate in the dimensions of the experiment and are not directly set in the global plugin.\n\nSUSCEPTIBLE_POPULATION_PROPORTION – The fraction of the population that is susceptible\nINITIAL_INFECTIONS – The number of adults initially infected\nMIN_INFECTIOUS_PERIOD – The minimum number of days a person is infectious\nMAX_INFECTIOUS_PERIOD – The maximum number of days a person is infectious\nPOPULATION_SIZE – The initial size of the population\nCHILD_POPULATION_PROPORTION – The fraction of the population between the ages of 0 and 18, inclusive\nSENIOR_POPULATION_PROPORTION – The fraction of the population 65 and older\nR0 – The expected number of people a single person will infect if all contacts are susceptible and transmission success is 100% likely\nAVERAGE_HOME_SIZE – The average number of people per household\nAVERAGE_SCHOOL_SIZE – The average number of students in a school\nAVERAGE_WORK_SIZE – The average number of people per work place\nTELEWORK_INFECTION_THRESHOLD – The total infection density that triggers some work places to institute telework mode\nTELEWORK_PROBABILTY – The probability that a work place will convert to telework mode once telework is allowed\nSCHOOL_COHORT_INFECTION_THRESHOLD – The infection density within a school that triggers the school moving to a split cohort\nSCHOOL_CLOSURE_INFECTION_THRESHOLD – The infection density within a school cohort that triggers the closure of that cohort.\n\n\n\nCode Block 10.3: The global properties plugin is initialized with several properties\nprivate Plugin getGlobalPropertiesPlugin() {\n GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder();//\n\n PropertyDefinition propertyDefinition = PropertyDefinition.builder()//\n .setType(Double.class)//\n .setPropertyValueMutability(false)//\n .setDefaultValue(0.0).build();\n\n builder.defineGlobalProperty(GlobalProperty.SUSCEPTIBLE_POPULATION_PROPORTION, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.AVERAGE_HOME_SIZE, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.AVERAGE_SCHOOL_SIZE, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.AVERAGE_WORK_SIZE, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.CHILD_POPULATION_PROPORTION, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.SENIOR_POPULATION_PROPORTION, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.R0, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.TELEWORK_INFECTION_THRESHOLD, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.TELEWORK_PROBABILTY, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.SCHOOL_COHORT_INFECTION_THRESHOLD, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.SCHOOL_CLOSURE_INFECTION_THRESHOLD, propertyDefinition, 0);\n\n propertyDefinition = PropertyDefinition.builder()//\n .setType(Integer.class)//\n .setPropertyValueMutability(false)//\n .build();\n builder.defineGlobalProperty(GlobalProperty.INITIAL_INFECTIONS, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.MIN_INFECTIOUS_PERIOD, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.MAX_INFECTIOUS_PERIOD, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.POPULATION_SIZE, propertyDefinition, 0);\n\n builder.setGlobalPropertyValue(GlobalProperty.POPULATION_SIZE, 10_000, 0);\n builder.setGlobalPropertyValue(GlobalProperty.SUSCEPTIBLE_POPULATION_PROPORTION, 1.0, 0);\n builder.setGlobalPropertyValue(GlobalProperty.INITIAL_INFECTIONS, 10, 0);\n builder.setGlobalPropertyValue(GlobalProperty.MIN_INFECTIOUS_PERIOD, 3, 0);\n builder.setGlobalPropertyValue(GlobalProperty.MAX_INFECTIOUS_PERIOD, 12, 0);\n builder.setGlobalPropertyValue(GlobalProperty.R0, 2.0, 0);\n builder.setGlobalPropertyValue(GlobalProperty.CHILD_POPULATION_PROPORTION, 0.235, 0);\n builder.setGlobalPropertyValue(GlobalProperty.SENIOR_POPULATION_PROPORTION, 0.169, 0);\n builder.setGlobalPropertyValue(GlobalProperty.AVERAGE_HOME_SIZE, 2.5, 0);\n builder.setGlobalPropertyValue(GlobalProperty.AVERAGE_SCHOOL_SIZE, 250.0, 0);\n builder.setGlobalPropertyValue(GlobalProperty.AVERAGE_WORK_SIZE, 30.0, 0);\n\n GlobalPropertiesPluginData globalPropertiesPluginData = builder.build();\n\n return GlobalPropertiesPlugin.builder().setGlobalPropertiesPluginData(globalPropertiesPluginData)\n .getGlobalPropertiesPlugin();\n}\n\n\nThe person properties plugin is loaded in (Code Block 10.4). The properties are:\n\nAGE – An integer value for the age of a person: child (0-18), adult (19-64), senior (65+)\nDISEASE_STATE – The state for a person: IMMUNE, SUSCEPTIBLE, INFECTIOUS, RECOVERED\nINFECTED_COUNT – The number of people infected by each particular person\n\n\n\nCode Block 10.4: The person properties plugin is initialized with several properties.\nprivate Plugin getPersonPropertiesPlugin() {\n\n PersonPropertiesPluginData.Builder builder = PersonPropertiesPluginData.builder();\n\n PropertyDefinition propertyDefinition = PropertyDefinition.builder()//\n .setType(Integer.class)//\n .build();\n\n builder.definePersonProperty(PersonProperty.AGE, propertyDefinition, 0, false);//\n\n propertyDefinition = PropertyDefinition.builder()//\n .setType(DiseaseState.class)//\n .setDefaultValue(DiseaseState.SUSCEPTIBLE).build();\n\n builder.definePersonProperty(PersonProperty.DISEASE_STATE, propertyDefinition, 0, false);//\n\n propertyDefinition = PropertyDefinition.builder()//\n .setType(Integer.class)//\n .setDefaultValue(0).build();\n\n builder.definePersonProperty(PersonProperty.INFECTED_COUNT, propertyDefinition, 0, false);//\n\n PersonPropertiesPluginData personPropertiesPluginData = builder.build();\n\n PersonPropertyReportPluginData personPropertyReportPluginData = PersonPropertyReportPluginData.builder()//\n .setReportLabel(ModelReportLabel.PERSON_PROPERTY)//\n .setReportPeriod(ReportPeriod.DAILY)//\n .includePersonProperty(PersonProperty.DISEASE_STATE)//\n .build();\n\n return PersonPropertiesPlugin.builder()//\n .setPersonPropertiesPluginData(personPropertiesPluginData)//\n .setPersonPropertyReportPluginData(personPropertyReportPluginData)//\n .getPersonPropertyPlugin();\n}\n\n\nThe groups plugin (Code Block 10.5) loads the group types and their corresponding group properties. The creation of groups is left to the Population Loader.\n\nHOME\nSCHOOL\n\nSCHOOL_STATUS : OPEN, COHORT (50% transmission reduction), CLOSED (100% transmission reduction)\n\nWORK\n\nTELEWORK – Boolean designating work place as telework, reducing transmission by 50%\n\n\n\n\nCode Block 10.5: The groups plugin includes a tele-work property for work places and open status properties for schools.\nprivate Plugin getGroupsPlugin() {\n GroupsPluginData.Builder builder = GroupsPluginData.builder();\n for (GroupType groupType : GroupType.values()) {\n builder.addGroupTypeId(groupType);\n }\n PropertyDefinition propertyDefinition = PropertyDefinition.builder()//\n .setType(Boolean.class)//\n .setDefaultValue(false)//\n .build();\n\n builder.defineGroupProperty(GroupType.WORK, GroupProperty.TELEWORK, propertyDefinition);\n\n propertyDefinition = PropertyDefinition.builder()//\n .setType(SchoolStatus.class)//\n .setDefaultValue(SchoolStatus.OPEN)//\n .build();\n\n builder.defineGroupProperty(GroupType.SCHOOL, GroupProperty.SCHOOL_STATUS, propertyDefinition);\n\n GroupsPluginData groupsPluginData = builder.build();\n\n GroupPopulationReportPluginData groupPopulationReportPluginData = //\n GroupPopulationReportPluginData.builder()//\n .setReportLabel(ModelReportLabel.GROUP_POPULATON)//\n .setReportPeriod(ReportPeriod.END_OF_SIMULATION)//\n .build();//\n return GroupsPlugin.builder()//\n .setGroupsPluginData(groupsPluginData)//\n .setGroupPopulationReportPluginData(groupPopulationReportPluginData)//\n .getGroupsPlugin();\n}\n\n\nThe stochastics plugin is initialized with a random seed and all simulations will start in the same stochastic state. The regions plugin has five regions and the people plugin is loaded in an empty state. People are added by an actor in the model plugin.\nThe model plugin adds four actors\n\nPopulationLoader – Adds people and groups to the simulation and initializes immunity.\nInfectionManager– Infects a small number of randomly chosen adults. Manages the progress of infections and the spread of infection from one per to another via their shared groups.\nTeleworkManager – Reviews the status of infections within the greater population every week and triggers randomly selected work places to move to telework mode.\nSchoolManager – Reviews the status of infections per school every week and splits school groups into cohorts. If infections increase it can close individual schools.\n\nThe reports in this model are:\n\nGroupPopulationReport – Shows the distribution of group sizes for each group type at the end of the simulation.\nPersonPropertyReport – Shows the distribution of disease state values over each day by regions\nDiseaseStateReport – Shows the distribution of disease state at the end of the simulation with a single line per scenario\nContagionReport – Shows the distribution of the number of infections spread per person" + }, + { + "objectID": "ch10-GroupsPlugins.html#experiment-dimensions", + "href": "ch10-GroupsPlugins.html#experiment-dimensions", + "title": "10  Groups Plugin", + "section": "10.8 Experiment dimensions", + "text": "10.8 Experiment dimensions\nThree dimensions are added to the experiment that define alternate values for telework infection thresholds, telework adoption probability, school cohort infection thresholds and school closure infection thresholds, yielding 36 scenarios. The values are:\n\nTelework infection threshold – 0.1, 1, and 10 percent\nTelework adoption probability – 10, 30, 50 and 80 percent\nSchool cohort and closure infection thresholds\n\n0.1 and 1 percent\n1 and 2 percent\n10 and 20 percent" + }, + { + "objectID": "ch10-GroupsPlugins.html#the-actors", + "href": "ch10-GroupsPlugins.html#the-actors", + "title": "10  Groups Plugin", + "section": "10.9 The actors", + "text": "10.9 The actors\nWe will finish this chapter by reviewing the four actors of the model plugin and then examine the output." + }, + { + "objectID": "ch10-GroupsPlugins.html#population-loader", + "href": "ch10-GroupsPlugins.html#population-loader", + "title": "10  Groups Plugin", + "section": "10.10 Population Loader", + "text": "10.10 Population Loader\nThe PopulationLoader actor, in Code Block 10.6, adds people to the simulation based on the number in the POPULATION_SIZE global property. The population is split evenly between the regions. For each region, the manager determines the number of homes needed to house the people based on the AVERAGE_HOME_SIZE. The people are grouped into children, working adults and seniors based on related global properties. Similar calculations determine the number of schools and workplaces. The people are randomly distributed based on their ages into homes, work places and schools. Some care is given to ensure that every household has at least one adult.\n\n\nCode Block 10.6: The population loader initializes by establishing various constants from the global properties and establishing the population of each region.\npublic void init(ActorContext actorContext) {\n personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class);\n groupsDataManager = actorContext.getDataManager(GroupsDataManager.class);\n StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);\n randomGenerator = stochasticsDataManager.getRandomGenerator();\n peopleDataManager = actorContext.getDataManager(PeopleDataManager.class);\n GlobalPropertiesDataManager globalPropertiesDataManager = actorContext\n .getDataManager(GlobalPropertiesDataManager.class);\n regionsDataManager = actorContext.getDataManager(RegionsDataManager.class);\n\n int populationSize = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.POPULATION_SIZE);\n susceptibleProbability = globalPropertiesDataManager\n .getGlobalPropertyValue(GlobalProperty.SUSCEPTIBLE_POPULATION_PROPORTION);\n childPopulationProportion = globalPropertiesDataManager\n .getGlobalPropertyValue(GlobalProperty.CHILD_POPULATION_PROPORTION);\n seniorPopulationProportion = globalPropertiesDataManager\n .getGlobalPropertyValue(GlobalProperty.SENIOR_POPULATION_PROPORTION);\n averageHomeSize = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.AVERAGE_HOME_SIZE);\n averageSchoolSize = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.AVERAGE_SCHOOL_SIZE);\n averageWorkSize = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.AVERAGE_WORK_SIZE);\n\n Set regionIds = regionsDataManager.getRegionIds();\n int regionSize = populationSize / regionIds.size();\n int leftoverPeople = populationSize % regionIds.size();\n\n for (RegionId regionId : regionIds) {\n int regionPopulation = regionSize;\n if (leftoverPeople > 0) {\n leftoverPeople--;\n regionPopulation++;\n }\n initializeRegionPopulation(regionId, regionPopulation);\n }\n}\n\n\nInitializing the region population, Code Block 10.7, first establishes the number of home groups, school groups and work groups by determining the number of children, working adults and seniors that are present based on various fixed global property values. The people are added with randomly determined ages and disease immunity.\n\n\nCode Block 10.7: The population for a region is initialized with each person being assigned an age, an immunity status and a region.\nprivate void initializeRegionPopulation(RegionId regionId, int populationSize) {\n\n double n = populationSize;\n int homeCount = (int) (n / averageHomeSize) + 1;\n int childCount = (int) (n * childPopulationProportion);\n int adultCount = populationSize - childCount;\n homeCount = FastMath.min(homeCount, adultCount);\n int seniorCount = (int) (n * seniorPopulationProportion);\n seniorCount = FastMath.min(seniorCount, adultCount);\n int workingAdultCount = adultCount - seniorCount;\n int workCount = (int) ((double) workingAdultCount / averageWorkSize) + 1;\n int schoolCount = (int) ((double) childCount / averageSchoolSize) + 1;\n\n // create the population\n for (int i = 0; i < populationSize; i++) {\n int age;\n if (i < seniorCount) {\n age = randomGenerator.nextInt(25) + 65;\n } else if (i < adultCount) {\n age = randomGenerator.nextInt(18) + (65 - 18);\n } else {\n age = randomGenerator.nextInt(18);\n }\n PersonPropertyValueInitialization ageInitialization = new PersonPropertyValueInitialization(\n PersonProperty.AGE, age);\n\n DiseaseState diseaseState = DiseaseState.IMMUNE;\n if (randomGenerator.nextDouble() < susceptibleProbability) {\n diseaseState = DiseaseState.SUSCEPTIBLE;\n }\n\n PersonPropertyValueInitialization diseaseInitialization = new PersonPropertyValueInitialization(\n PersonProperty.DISEASE_STATE, diseaseState);\n PersonConstructionData personConstructionData = PersonConstructionData.builder()//\n .add(ageInitialization)//\n .add(diseaseInitialization)//\n .add(regionId)//\n .build();\n peopleDataManager.addPerson(personConstructionData);\n }\n\n\nIn Code Block 10.8, the manager continues by creating the work, home and school groups. It then organizes the people by age group in Code Block 10.9. Finally, it places the people into the appropriate groups in Code Block 10.10.\n\n\nCode Block 10.8: The home, work and school groups are added to the groups data manager.\n// create the home groups\nList homeGroupIds = new ArrayList<>();\nfor (int i = 0; i < homeCount; i++) {\n GroupConstructionInfo groupConstructionInfo = GroupConstructionInfo.builder().setGroupTypeId(GroupType.HOME)\n .build();\n GroupId groupId = groupsDataManager.addGroup(groupConstructionInfo);\n homeGroupIds.add(groupId);\n}\n\n// create the work groups\nList workGroupIds = new ArrayList<>();\nfor (int i = 0; i < workCount; i++) {\n GroupConstructionInfo groupConstructionInfo = GroupConstructionInfo.builder().setGroupTypeId(GroupType.WORK)\n .build();\n GroupId groupId = groupsDataManager.addGroup(groupConstructionInfo);\n workGroupIds.add(groupId);\n}\n\n// create the school groups\nList schoolGroupIds = new ArrayList<>();\nfor (int i = 0; i < schoolCount; i++) {\n GroupConstructionInfo groupConstructionInfo = GroupConstructionInfo.builder()\n .setGroupTypeId(GroupType.SCHOOL).build();\n GroupId groupId = groupsDataManager.addGroup(groupConstructionInfo);\n schoolGroupIds.add(groupId);\n}\n\n\n\n\nCode Block 10.9: The people are separated into age related lists.\nList peopleInRegion = regionsDataManager.getPeopleInRegion(regionId);\nList adults = new ArrayList<>();\nList children = new ArrayList<>();\nList workingAdults = new ArrayList<>();\nfor (PersonId personId : peopleInRegion) {\n int age = personPropertiesDataManager.getPersonPropertyValue(personId, PersonProperty.AGE);\n if (age < 18) {\n children.add(personId);\n } else {\n adults.add(personId);\n if (age < 65) {\n workingAdults.add(personId);\n }\n }\n}\n\n\n\n\nCode Block 10.10: People are assigned to homes, work places and schools.\nRandom random = new Random(randomGenerator.nextLong());\n/*\n * Randomize the adults and assign them to the home groups such that there is at\n * least one adult in each home\n */\nCollections.shuffle(adults, random);\n// put one adult in each home\nfor (int i = 0; i < homeGroupIds.size(); i++) {\n PersonId personId = adults.get(i);\n GroupId groupId = homeGroupIds.get(i);\n groupsDataManager.addPersonToGroup(personId, groupId);\n}\n\n// assign the remaining adults at random to homes\nfor (int i = homeGroupIds.size(); i < adults.size(); i++) {\n PersonId personId = adults.get(i);\n GroupId groupId = homeGroupIds.get(randomGenerator.nextInt(homeGroupIds.size()));\n groupsDataManager.addPersonToGroup(personId, groupId);\n}\n\n// assign working age adults to work groups\nfor (int i = 0; i < workingAdults.size(); i++) {\n PersonId personId = workingAdults.get(i);\n GroupId groupId = workGroupIds.get(randomGenerator.nextInt(workGroupIds.size()));\n groupsDataManager.addPersonToGroup(personId, groupId);\n}\n\n// assign children to school groups\nfor (int i = 0; i < children.size(); i++) {\n PersonId personId = children.get(i);\n GroupId groupId = schoolGroupIds.get(randomGenerator.nextInt(schoolGroupIds.size()));\n groupsDataManager.addPersonToGroup(personId, groupId);\n}\n\n// assign children to home groups\nfor (int i = 0; i < children.size(); i++) {\n PersonId personId = children.get(i);\n GroupId groupId = homeGroupIds.get(randomGenerator.nextInt(homeGroupIds.size()));\n groupsDataManager.addPersonToGroup(personId, groupId);\n}\n }\n\n\n\n10.10.1 Telework manager\nThe TeleworkManager actor, in Code Block 10.11, schedules a weekly review of school disease mitigation strategies. Note that the scheduling is through a passive plan, which indicates to the simulation that this plan should only be executed if there are active plans still remaining. Passive plans are generally used when there is a continuing task that is not driven directly by events. When active plans cease and only passive plans remain, the simulation halts since the passive plans are not reason enough to continue time flow.\n\n\nCode Block 10.11: The telework manager initializes by scheduling a telework status review for seven days from the start of the simulation.\npublic void init(ActorContext actorContext) {\n this.actorContext = actorContext;\n scheduleNextReview();\n}\n\nprivate void scheduleNextReview() {\n double planTime = actorContext.getTime() + reviewInterval;\n Plan plan = Plan.builder(ActorContext.class)//\n .setCallbackConsumer(this::reviewTeleworkStatus)//\n .setActive(false)//\n .setTime(planTime)//\n .build();\n\n actorContext.addPlan(plan);\n}\n\n\nThe review process (Code Block 10.12) continues until a disease threshold has been reached. At that point, the telework manager randomly determines which work groups will be in telework mode. Note that there is no returning from this mode and there is no further evolution of the telework mode even after the disease wanes.\n\n\nCode Block 10.12: The telework manager schedules a review every seven days until a threshold of infections is reached. Once the threshold is achieved, work places are randomly selected to use telework until the end of the simulation.\nprivate void reviewTeleworkStatus(ActorContext actorContext) {\n StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);\n RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator();\n PeopleDataManager peopleDataManager = actorContext.getDataManager(PeopleDataManager.class);\n PersonPropertiesDataManager personPropertiesDataManager = actorContext\n .getDataManager(PersonPropertiesDataManager.class);\n GroupsDataManager groupsDataManager = actorContext.getDataManager(GroupsDataManager.class);\n GlobalPropertiesDataManager globalPropertiesDataManager = actorContext\n .getDataManager(GlobalPropertiesDataManager.class);\n double threshold = globalPropertiesDataManager\n .getGlobalPropertyValue(GlobalProperty.TELEWORK_INFECTION_THRESHOLD);\n double teleworkProbability = globalPropertiesDataManager\n .getGlobalPropertyValue(GlobalProperty.TELEWORK_PROBABILTY);\n\n int infectiousCount = personPropertiesDataManager.getPersonCountForPropertyValue(PersonProperty.DISEASE_STATE,\n DiseaseState.INFECTIOUS);\n int populationCount = peopleDataManager.getPopulationCount();\n\n double infectiousFraction = infectiousCount;\n infectiousFraction /= populationCount;\n\n if (infectiousFraction >= threshold) {\n List workGroupIds = groupsDataManager.getGroupsForGroupType(GroupType.WORK);\n for (GroupId groupId : workGroupIds) {\n if (randomGenerator.nextDouble() < teleworkProbability) {\n groupsDataManager.setGroupPropertyValue(groupId, GroupProperty.TELEWORK, true);\n }\n }\n } else {\n scheduleNextReview();\n }\n}\n\n\n\n\n10.10.2 School manager\nThe SchoolManager actor (Code Block 10.13) initializes by establishing various values derived from fixed global properties and then scheduling a weekly review of all schools.\n\n\nCode Block 10.13: The school manager initializes by establishing some property constants and planning school status review for seven days after the simulation starts.\npublic void init(ActorContext actorContext) {\n this.actorContext = actorContext;\n personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class);\n groupsDataManager = actorContext.getDataManager(GroupsDataManager.class);\n GlobalPropertiesDataManager globalPropertiesDataManager = actorContext\n .getDataManager(GlobalPropertiesDataManager.class);\n cohortThreshold = globalPropertiesDataManager\n .getGlobalPropertyValue(GlobalProperty.SCHOOL_COHORT_INFECTION_THRESHOLD);\n closureThreshold = globalPropertiesDataManager\n .getGlobalPropertyValue(GlobalProperty.SCHOOL_CLOSURE_INFECTION_THRESHOLD);\n planNextReview();\n}\n\nprivate void planNextReview() {\n double planTime = actorContext.getTime() + reviewInterval;\n Plan plan = Plan.builder(ActorContext.class)//\n .setCallbackConsumer(this::reviewSchools)//\n .setActive(false)//\n .setTime(planTime)//\n .build();\n actorContext.addPlan(plan);\n}\n\nprivate void reviewSchools(ActorContext actorContext) {\n List schoolGroupIds = groupsDataManager.getGroupsForGroupType(GroupType.SCHOOL);\n for (GroupId groupId : schoolGroupIds) {\n reviewSchool(groupId);\n }\n planNextReview();\n}\n\n\nThe review of each school group is shown in Code Block 10.14. The fraction of the school’s students who are infectious is calculated. Depending on the current state of the group (OPEN, COHORT, CLOSED) the group may move to the next state. If the group moves from OPEN to COHORT (Code Block 10.15), a new group is formed and half of the students are moved into the new group. Both groups are then marked as COHORT groups. If the group moves from COHORT to CLOSED (Code Block 10.16), all students are removed from the group.\n\n\nCode Block 10.14: Each school is reviewed on a weekly basis. As the fraction of students who are infected increases, the school transitions from OPEN to COHORT to CLOSED.\nprivate void reviewSchool(GroupId groupId) {\n\n int infectiousCount = 0;\n List peopleForGroup = groupsDataManager.getPeopleForGroup(groupId);\n for (PersonId personId : peopleForGroup) {\n DiseaseState diseaseState = personPropertiesDataManager.getPersonPropertyValue(personId,\n PersonProperty.DISEASE_STATE);\n if (diseaseState == DiseaseState.INFECTIOUS) {\n infectiousCount++;\n }\n }\n\n double infectiousFraction = infectiousCount;\n if (!peopleForGroup.isEmpty()) {\n infectiousFraction /= peopleForGroup.size();\n }\n\n SchoolStatus schoolStatus = groupsDataManager.getGroupPropertyValue(groupId, GroupProperty.SCHOOL_STATUS);\n\n switch (schoolStatus) {\n case OPEN:\n if (infectiousFraction >= cohortThreshold) {\n splitSchoolIntoCohorts(groupId);\n }\n break;\n case COHORT:\n if (infectiousFraction >= closureThreshold) {\n closeSchool(groupId);\n }\n break;\n case CLOSED:\n // do nothing\n break;\n default:\n throw new RuntimeException(\"unhandled case \" + schoolStatus);\n }\n}\n\n\n\n\nCode Block 10.15: When a school moves to COHORT status, a new group is added to the simulation and half of the students move to this new group.\nprivate void splitSchoolIntoCohorts(GroupId groupId) {\n GroupConstructionInfo groupConstructionInfo = GroupConstructionInfo.builder().setGroupTypeId(GroupType.SCHOOL)\n .build();\n GroupId newGroupId = groupsDataManager.addGroup(groupConstructionInfo);\n\n List peopleForGroup = groupsDataManager.getPeopleForGroup(groupId);\n for (int i = 0; i < peopleForGroup.size(); i++) {\n if (i % 2 == 0) {\n PersonId personId = peopleForGroup.get(i);\n groupsDataManager.removePersonFromGroup(personId, groupId);\n groupsDataManager.addPersonToGroup(personId, newGroupId);\n }\n }\n\n groupsDataManager.setGroupPropertyValue(newGroupId, GroupProperty.SCHOOL_STATUS, SchoolStatus.COHORT);\n groupsDataManager.setGroupPropertyValue(groupId, GroupProperty.SCHOOL_STATUS, SchoolStatus.COHORT);\n\n}\n\n\n\n\nCode Block 10.16: When a school is closed, all the students are removed from the school group so that infection can no longer spread via school-based contact.\nprivate void closeSchool(GroupId groupId) {\n groupsDataManager.setGroupPropertyValue(groupId, GroupProperty.SCHOOL_STATUS, SchoolStatus.CLOSED);\n List people = groupsDataManager.getPeopleForGroup(groupId);\n for (PersonId personId : people) {\n groupsDataManager.removePersonFromGroup(personId, groupId);\n }\n}\n\n\n\n\n10.10.3 Infection Manager\nThe InfectionManager actor, in Code Block 10.17, first determines various values from the fixed global properties that will be used to manage infections. The most important calculation is to determine the length of time between infectious contacts as a function of the global R0 value and the expected number of days a person will be infectious. It then selects a small set of adults to infect during first day of the simulation. Infecting a person (Code Block 10.18) first requires the manager to determine the number of days of the infection for the particular person and then schedule infectious contact times for that person. At the end of the infectious contacts the manager schedules the transition of the person from infectious to recovered.\n\n\nCode Block 10.17: The infection manager initializes by infecting the initially infected people in the first day.\npublic void init(ActorContext actorContext) {\n this.actorContext = actorContext;\n\n StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);\n randomGenerator = stochasticsDataManager.getRandomGenerator();\n Random random = new Random(randomGenerator.nextLong());\n\n groupsDataManager = actorContext.getDataManager(GroupsDataManager.class);\n GlobalPropertiesDataManager globalPropertiesDataManager = actorContext\n .getDataManager(GlobalPropertiesDataManager.class);\n personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class);\n List susceptiblePeople = personPropertiesDataManager\n .getPeopleWithPropertyValue(PersonProperty.DISEASE_STATE, DiseaseState.SUSCEPTIBLE);\n List susceptibleAdults = new ArrayList<>();\n for (PersonId personId : susceptiblePeople) {\n int age = personPropertiesDataManager.getPersonPropertyValue(personId, PersonProperty.AGE);\n if (age > 18) {\n susceptibleAdults.add(personId);\n }\n }\n\n Collections.shuffle(susceptibleAdults, random);\n\n minInfectiousPeriod = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.MIN_INFECTIOUS_PERIOD);\n maxInfectiousPeriod = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.MAX_INFECTIOUS_PERIOD);\n double r0 = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.R0);\n infectionInterval = (double) (minInfectiousPeriod + maxInfectiousPeriod) / (2 * r0);\n\n int initialInfections = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.INITIAL_INFECTIONS);\n initialInfections = FastMath.min(initialInfections, susceptibleAdults.size());\n\n for (int i = 0; i < initialInfections; i++) {\n PersonId personId = susceptibleAdults.get(i);\n double planTime = randomGenerator.nextDouble() * 0.5 + 0.25;\n actorContext.addPlan((c) -> infectPerson(personId), planTime);\n }\n}\n\n\n\n\nCode Block 10.18: When a person is infected, the number of possible infectious contacts is determined and planned. After the last infectious contact, the person is scheduled to become recovered.\nprivate void infectPerson(PersonId personId) {\n personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.DISEASE_STATE,\n DiseaseState.INFECTIOUS);\n int infectiousDays = randomGenerator.nextInt(maxInfectiousPeriod - minInfectiousPeriod) + minInfectiousPeriod;\n int infectionCount = (int) FastMath.round(((double) infectiousDays / infectionInterval));\n double planTime = actorContext.getTime();\n for (int j = 0; j < infectionCount; j++) {\n planTime += infectionInterval;\n actorContext.addPlan((c) -> infectContact(personId), planTime);\n }\n actorContext.addPlan((c) -> endInfectiousness(personId), planTime);\n}\n\n\nAn infectious contact , Code Block 10.19, first selects a group at random from the person’s groups. A person to infect is selected from the group, excluding the person who is infecting. If such a person can be found and that person is susceptible then, barring mitigation, the susceptible person becomes infected and is immediately infectious. Cohort schools are mitigated by 50% and closed schools are 100% mitigated. Telework groups are mitigated by 50%.\n\n\nCode Block 10.19: The infection manager attempts to infect a susceptible person found in a randomly selected group associated with the currently infected person.\nprivate void infectContact(PersonId personId) {\n List groupsForPerson = groupsDataManager.getGroupsForPerson(personId);\n GroupId groupId = groupsForPerson.get(randomGenerator.nextInt(groupsForPerson.size()));\n\n // work groups doing telework have a 50% contact mitigation\n GroupTypeId groupTypeId = groupsDataManager.getGroupType(groupId);\n if (groupTypeId.equals(GroupType.WORK)) {\n boolean teleworkGroup = groupsDataManager.getGroupPropertyValue(groupId, GroupProperty.TELEWORK);\n if (teleworkGroup) {\n if (randomGenerator.nextBoolean()) {\n return;\n }\n }\n }\n\n // school groups in COHORT mode have a 50% contact mitigation\n // school groups in CLOSED mode have a 100% contact mitigation\n if (groupTypeId.equals(GroupType.SCHOOL)) {\n SchoolStatus schoolStatus = groupsDataManager.getGroupPropertyValue(groupId, GroupProperty.SCHOOL_STATUS);\n switch (schoolStatus) {\n case COHORT:\n if (randomGenerator.nextBoolean()) {\n return;\n }\n break;\n case CLOSED:\n return;\n default:\n // no mitigation\n break;\n }\n }\n\n GroupSampler groupSampler = GroupSampler.builder().setExcludedPersonId(personId).build();\n Optional optional = groupsDataManager.sampleGroup(groupId, groupSampler);\n if (optional.isPresent()) {\n PersonId contactedPerson = optional.get();\n DiseaseState diseaseState = personPropertiesDataManager.getPersonPropertyValue(contactedPerson,\n PersonProperty.DISEASE_STATE);\n if (diseaseState == DiseaseState.SUSCEPTIBLE) {\n int infectedCount = personPropertiesDataManager.getPersonPropertyValue(personId,\n PersonProperty.INFECTED_COUNT);\n infectedCount++;\n personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.INFECTED_COUNT,\n infectedCount);\n infectPerson(contactedPerson);\n }\n }\n}\n\n\n\n\n10.10.4 Inspecting the output\n\n\n10.10.5 Person Property Report\nThe 36 scenarios result in a large amount of output in the person property report. It is effectively a trace log of the initial state and changes to each person.\n\n\n10.10.6 Group Population Report\nThe group population report shows the distribution of people at the end of the simulation. Since there is no stochastics dimension, each scenario has an identical distribution of people into homes. The report shows all the expected patterns as a response to initial policies. For example, higher school closure infection thresholds drive the ending school populations to less than 1/3 their initial values.\n\n\n10.10.7 Disease State Report\nThe disease state report shows the ending counts for each disease state. By analyzing the number of people recovered over the 36 scenarios, we observe that the disease threshold for triggering work places into a telework status is significant. If that threshold is set to 10%, the working population does not telework and the number of recovered is significantly higher that at the lower levels. School cohort and closure policies exhibit a similar pattern.\n\n\n10.10.8 Contagion Report\nThe contagion report shows then number of people infected by each infectious person as counts. The report shows that early decisions to close schools and institute telework policies can significantly reduce the spread of the disease." + }, + { + "objectID": "ch11-ResourcesPlugin.html#plugin-data-initialization", + "href": "ch11-ResourcesPlugin.html#plugin-data-initialization", + "title": "11  Resources Plugin", + "section": "11.1 Plugin Data Initialization", + "text": "11.1 Plugin Data Initialization\nThe plugin is initialized using a ResourcesPluginData object that:\n\nDefines resource ids\nDefines resource properties\nCollects resource property values\nSets initial resource amounts for regions\nSets initial resource amounts for people" + }, + { + "objectID": "ch11-ResourcesPlugin.html#plugin-behavior", + "href": "ch11-ResourcesPlugin.html#plugin-behavior", + "title": "11  Resources Plugin", + "section": "11.2 Plugin Behavior", + "text": "11.2 Plugin Behavior\nThe plugin adds a single data manager to the simulation as an instance of the ResourcesDataManager that is initialized with the ResourcesPluginData." + }, + { + "objectID": "ch11-ResourcesPlugin.html#data-manager", + "href": "ch11-ResourcesPlugin.html#data-manager", + "title": "11  Resources Plugin", + "section": "11.3 Data Manager", + "text": "11.3 Data Manager\nThe data manager manages person and region inventories of resources as well as any properties associated with resources.  The data manager provides public methods that:\n\nAdd a new resource id, thus defining a new resource type\nDefine a resource property\nAdd resource amounts to regions\nRemove resource amounts from regions\nTransfer resource amounts between regions\nTransfer resource amounts between regions and people\nRemove resource amounts from people\nAnswer various questions about:\n\nPeople who have or do not have a particular resource\nResource levels for people and regions\nProperty values, definitions, resource id values, etc.\n\n\nThe data manager also produces observable events:\n\nPersonResourceUpdateEvent – when a person has a change in a resource level\nRegionResourceUpdateEvent – when a region has a change in a resource level\nResourceIdAdditionEvent – when a new resource id is added\nResourcePropertyDefinitionEvent – when a new resource property definition is added\nResourcePropertyUpdateEvent – when a resource property value is assigned" + }, + { + "objectID": "ch11-ResourcesPlugin.html#example-code-lesson-18", + "href": "ch11-ResourcesPlugin.html#example-code-lesson-18", + "title": "11  Resources Plugin", + "section": "11.4 Example Code (Lesson 18)", + "text": "11.4 Example Code (Lesson 18)\nExample_18.java shows the use of the resources plugin. In it we will examine:\n\nThe initialization of the resource properties plugin\nThe flow of resources between regions and people\nThe observation of resource events\n\nThe example includes seven plugins:\n\nResources plugin – (GCM core plugin) used to manage resources\nPeople plugin – (GCM core plugin) used to manage people\nPerson properties plugin– (GCM core plugin) used to decorate properties onto people\nGlobal properties plugin– (GCM core plugin) used to store policies and initial conditions affecting resource use\nStochastics plugin – (GCM core plugin) used to generate random numbers used in various decisions\nRegions Plugin– (GCM core plugin) used for resource flow\nModel plugin – (local plugin) used to introduce three actors that will:\n\nLoad the population\nDistribute resources to regions\nManage the treatment of disease utilizing resources" + }, + { + "objectID": "ch11-ResourcesPlugin.html#model", + "href": "ch11-ResourcesPlugin.html#model", + "title": "11  Resources Plugin", + "section": "11.5 Model", + "text": "11.5 Model\nThe example’s model represents a disease that is treatable through antiviral medication and hospitalization. People at the start of the simulation are either immune or susceptible and all susceptible people are exposed and infected at random times over a given period. There is no transmission modeling. Initial treatment is with a single dose of an antiviral drug, if it is available. If the antiviral drug is not available or the drug is not effective, the person is hospitalized. If there are no hospital beds are available, the person dies. After a course of treatment in the hospital, the person is either immune or dead. Previous treatment with the antiviral alters the outcome probabilities of the hospital stay. Upon the person’s release from the hospital, the hospital bed is returned to the person’s region. Questionnaires are sent to people who have been successfully treated." + }, + { + "objectID": "ch11-ResourcesPlugin.html#model-execution", + "href": "ch11-ResourcesPlugin.html#model-execution", + "title": "11  Resources Plugin", + "section": "11.6 Model Execution", + "text": "11.6 Model Execution\nThe example’s execution is shown in Code Block 11.1 and Code Block 11.2.\n\n\nCode Block 11.1: Executing example 18 with an output directory.\npublic static void main(String[] args) throws IOException {\n if (args.length == 0) {\n throw new RuntimeException(\"One output directory argument is required\");\n }\n Path outputDirectory = Paths.get(args[0]);\n if (!Files.exists(outputDirectory)) {\n Files.createDirectory(outputDirectory);\n } else {\n if (!Files.isDirectory(outputDirectory)) {\n throw new IOException(\"Provided path is not a directory\");\n }\n }\n\n new Example_18(outputDirectory).execute();\n}\n\n\n\n\nCode Block 11.2: The various plugins are gathered from their initial data, dimensions are added and the experiment is executed over 864 scenarios using 8 threads.\nprivate void execute() {\n\n ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()//\n .setThreadCount(8)//\n .build();\n\n Experiment.builder()\n\n .addPlugin(getResourcesPlugin())//\n .addPlugin(getGlobalPropertiesPlugin())//\n .addPlugin(getPersonPropertiesPlugin())//\n .addPlugin(getRegionsPlugin())//\n .addPlugin(getPeoplePlugin())//\n .addPlugin(getStochasticsPlugin())//\n .addPlugin(ModelPlugin.getModelPlugin())//\n\n .addDimension(getMaximumSymptomOnsetTimeDimension())//\n .addDimension(getSusceptiblePopulationProportionDimension())//\n .addDimension(getAntiviralCoverageTimeDimension())//\n .addDimension(getAntiviralSuccessRateDimension())//\n .addDimension(getHospitalSuccessDimension())//\n .addDimension(getHospitalBedsPerPersonDimension())//\n .addDimension(getAntiviralDosesPerPersonDimension())//\n .addDimension(getHospitalStayDurationDimension())//\n\n .addExperimentContextConsumer(getNIOReportItemHandler())//\n .setExperimentParameterData(experimentParameterData)//\n .build()//\n .execute();//\n}\n\n\nThe first action is to add the resources plugin (Code Block 11.3, Code Block 11.4). Only the resource id values contained in the Resource enumeration are added to the plugin’s data. Region and person initial resource levels can also be added, but we will instead initialize them via an actor in the model plugin.\n\n\nCode Block 11.3: Resource ids are implemented as an enumeration.\npublic enum Resource implements ResourceId {\n ANTI_VIRAL_MED, HOSPITAL_BED;\n}\n\n\n\n\nCode Block 11.4: The resource plugin is initialized with defining the two resource ids at time zero with time tracking turned on. The person resource report is set to report at the end of the simulation.\nprivate Plugin getResourcesPlugin() {\n ResourcesPluginData.Builder builder = ResourcesPluginData.builder();\n for (ResourceId resourceId : Resource.values()) {\n builder.addResource(resourceId, 0.0, true);\n }\n ResourcesPluginData resourcesPluginData = builder.build();\n\n PersonResourceReportPluginData personResourceReportPluginData = PersonResourceReportPluginData//\n .builder()//\n .setReportLabel(ModelReportLabel.PERSON_RESOURCE_REPORT)//\n .setReportPeriod(ReportPeriod.END_OF_SIMULATION)//\n .build();\n\n return ResourcesPlugin.builder()//\n .setResourcesPluginData(resourcesPluginData)//\n .setPersonResourceReportPluginData(personResourceReportPluginData)//\n .getResourcesPlugin();//\n}\n\n\nThe next action is to load the global properties plugin (Code Block 11.5).  All of the global properties are marked as immutable since they will not change over the course of the simulation.  Further, most of the properties will participate in the dimensions of the experiment, so we can set the default values to zero.\n\nSUSCEPTIBLE_POPULATION_PROPORTION – The fraction of the population that is susceptible\nMAXIMUM_SYMPTOM_ONSET_TIME – The last time where any person will have onset of symptoms\nANTIVIRAL_COVERAGE_TIME – The amount of time for the antiviral to be effective\nANTIVIRAL_SUCCESS_RATE – The probability that the antiviral will be effective\nHOSPITAL_SUCCESS_WITH_ANTIVIRAL – The probability that hospital treatment will be effective for people who previously had antiviral treatment\nHOSPITAL_SUCCESS_WITHOUT_ANTIVIRAL – The probability that hospital treatment will be effective for people who previously had no antiviral treatment\nHOSPITAL_STAY_DURATION_MIN –  The minimum duration of a hospital stay\nHOSPITAL_STAY_DURATION_MAX – The maximum duration of a hospital stay\nPOPULATION_SIZE – The number of people across all regions.  Regions will not be uniformly populated.\nHOSPITAL_BEDS_PER_PERSON – The number of hospital beds per person on average stored in the regions.\nANTIVIRAL_DOSES_PER_PERSON – The number of antiviral doses per person on average stored in the regions.\n\n\n\nCode Block 11.5: The global properties plugin is initialized with several properties.\nprivate Plugin getGlobalPropertiesPlugin() {\n GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder();//\n\n PropertyDefinition propertyDefinition = PropertyDefinition.builder()//\n .setType(Double.class)//\n .setDefaultValue(0.0)//\n .setPropertyValueMutability(false)//\n .build();\n\n builder.defineGlobalProperty(GlobalProperty.SUSCEPTIBLE_POPULATION_PROPORTION, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.MAXIMUM_SYMPTOM_ONSET_TIME, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.ANTIVIRAL_COVERAGE_TIME, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.ANTIVIRAL_SUCCESS_RATE, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.HOSPITAL_SUCCESS_WITH_ANTIVIRAL, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.HOSPITAL_SUCCESS_WITHOUT_ANTIVIRAL, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.HOSPITAL_BEDS_PER_PERSON, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.ANTIVIRAL_DOSES_PER_PERSON, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.HOSPITAL_STAY_DURATION_MIN, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.HOSPITAL_STAY_DURATION_MAX, propertyDefinition, 0);\n\n propertyDefinition = PropertyDefinition.builder()//\n .setType(Integer.class)//\n .setDefaultValue(10000)//\n .setPropertyValueMutability(false)//\n .build();\n\n builder.defineGlobalProperty(GlobalProperty.POPULATION_SIZE, propertyDefinition, 0);\n\n GlobalPropertiesPluginData globalPropertiesPluginData = builder.build();\n\n return GlobalPropertiesPlugin.builder().setGlobalPropertiesPluginData(globalPropertiesPluginData)\n .getGlobalPropertiesPlugin();\n}\n\n\nThe person properties plugin is loaded (Code Block 11.6).  All person properties are Boolean values and defaulted to false. \n\nIMMUNE – The person is immune\nINFECTED – The person is infected\nTREATED_WITH_ANTIVIRAL – The person received antiviral treatment\nHOSPITALIZED – The person received hospital treatment\nDEAD_IN_HOSPITAL – The person dies in the hospital\nDEAD_IN_HOME – The person dies in the home\nRECEIVED_QUESTIONNAIRE – An infected person who successfully completes treatment receives a questionnaire\n\n\n\nCode Block 11.6: The person properties plugin is initialized with several properties.\nprivate Plugin getPersonPropertiesPlugin() {\n\n PersonPropertiesPluginData.Builder builder = PersonPropertiesPluginData.builder();\n\n PropertyDefinition propertyDefinition = PropertyDefinition.builder()//\n .setType(Boolean.class)//\n .setDefaultValue(false)//\n .build();\n\n builder.definePersonProperty(PersonProperty.IMMUNE, propertyDefinition, 0, false);//\n builder.definePersonProperty(PersonProperty.INFECTED, propertyDefinition, 0, false);//\n builder.definePersonProperty(PersonProperty.HOSPITALIZED, propertyDefinition, 0, false);//\n builder.definePersonProperty(PersonProperty.TREATED_WITH_ANTIVIRAL, propertyDefinition, 0, false);//\n builder.definePersonProperty(PersonProperty.DEAD_IN_HOME, propertyDefinition, 0, false);//\n builder.definePersonProperty(PersonProperty.DEAD_IN_HOSPITAL, propertyDefinition, 0, false);//\n\n propertyDefinition = PropertyDefinition.builder()//\n .setType(Boolean.class)//\n .setDefaultValue(false)//\n .build();\n builder.definePersonProperty(PersonProperty.RECEIVED_QUESTIONNAIRE, propertyDefinition, 0, true);//\n\n PersonPropertiesPluginData personPropertiesPluginData = builder.build();\n\n return PersonPropertiesPlugin.builder().setPersonPropertiesPluginData(personPropertiesPluginData)\n .getPersonPropertyPlugin();\n}\n\n\nThe are four reports:\n\nPersonResourceReport – Shows resource allocations by day and region.  Resources are possessed by a person during the course of treatment.\nTreatmentReport – Shows a summary of all person property value combinations at the end of the simulation\nDeathReport – Shows absolute and per capita death rate per region\nQuestionnaireReport – Shows statistics on the distribution of questionnaires\n\nThe stochastic plugin is initialized with a random seed and all simulations will start in the same stochastic state.  The Regions plugin has five regions and the people plugin is loaded in an empty state.  People are added by an actor in the model plugin.\nThe model plugin adds three actors\n\nPopulationLoader – Adds people to the simulation and initializes immunity\nResourceLoader – Initializes resources for regions. People have no allocated resources.\nTreatmentManager – Moves people through treatment states" + }, + { + "objectID": "ch11-ResourcesPlugin.html#experiment-dimensions", + "href": "ch11-ResourcesPlugin.html#experiment-dimensions", + "title": "11  Resources Plugin", + "section": "11.7 Experiment dimensions", + "text": "11.7 Experiment dimensions\nEight dimensions are added to the experiment that define alternate values for ten of the eleven global properties resulting in 864 scenarios. Only population size is fixed and is set to 10,000.  The values are:\n\nMax Symptom onset – 60 and 120 days\nSusceptibility – 25, 50 and 75 percent\nAntiviral treatment duration – 10 and 15 days\nAntiviral treatment success – 50 and 80 percent\nHospital success rate with and without antivirals\n\n75 and 50 percent\n50 and 30 percent\n\nHospital beds per capita – 1, 3 and 5 per thousand\nAntiviral doses per capita – 0.1, 0.2 and 0.5\nHospital stay\n\n2 to 5 days\n5 to 10 days" + }, + { + "objectID": "ch11-ResourcesPlugin.html#the-actors", + "href": "ch11-ResourcesPlugin.html#the-actors", + "title": "11  Resources Plugin", + "section": "11.8 The actors", + "text": "11.8 The actors\nWe will finish this chapter by reviewing the three actors of the model plugin and then examine the output.\n\n11.8.1 Population Loader\nThe PopulationLoader actor, in Code Block 11.7, adds people to the simulation based on the number in the POPULATION_SIZE global property.  Each person is assigned a random region and the person property, IMMUNE, is randomly assigned based on the SUSCEPTIBLE_POPULATION_PROPORTION global property. The selection of regions, although random, is designed to ensure that regions have different population sizes so that each region may have unique output statistics.\n\n\nCode Block 11.7: The population loader initializes the population by assigning to each person a randomly selected region id and immunity status.\npublic void init(ActorContext actorContext) {\n\n StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);\n randomGenerator = stochasticsDataManager.getRandomGenerator();\n PeopleDataManager peopleDataManager = actorContext.getDataManager(PeopleDataManager.class);\n GlobalPropertiesDataManager globalPropertiesDataManager = actorContext\n .getDataManager(GlobalPropertiesDataManager.class);\n regionsDataManager = actorContext.getDataManager(RegionsDataManager.class);\n\n int populationSize = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.POPULATION_SIZE);\n double susceptibleProbability = globalPropertiesDataManager\n .getGlobalPropertyValue(GlobalProperty.SUSCEPTIBLE_POPULATION_PROPORTION);\n double immuneProbabilty = 1 - susceptibleProbability;\n\n /*\n * Derive mapping from region to probability that a person will be assigned to\n * that region that will likely not put the same number of people in each\n * region.\n */\n buildUnbalancedRegions();\n\n /*\n * Add each person to the simulation. Determine their region id and the immune\n * state. The other person properties will have default values.\n */\n for (int i = 0; i < populationSize; i++) {\n RegionId regionId = getRandomRegionId();\n boolean immune = randomGenerator.nextDouble() < immuneProbabilty;\n PersonPropertyValueInitialization personPropertyInitialization = new PersonPropertyValueInitialization(\n PersonProperty.IMMUNE, immune);\n PersonConstructionData personConstructionData = PersonConstructionData.builder()//\n .add(personPropertyInitialization)//\n .add(regionId)//\n .build();\n peopleDataManager.addPerson(personConstructionData);\n }\n}\n\n\n\n\n11.8.2 Resource Loader\nThe ResourceLoader actor, in Code Block 11.8, determines the number of antiviral doses and hospital beds for each region.  Note that each region receives the same amounts, but that the regions will have very different population sizes.\n\n\nCode Block 11.8: The resource loader initializes the anti-viral medication doses and hospital beds for each region.\npublic void init(ActorContext actorContext) {\n\n RegionsDataManager regionsDataManager = actorContext.getDataManager(RegionsDataManager.class);\n ResourcesDataManager resourcesDataManager = actorContext.getDataManager(ResourcesDataManager.class);\n GlobalPropertiesDataManager globalPropertiesDataManager = actorContext\n .getDataManager(GlobalPropertiesDataManager.class);\n int populationSize = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.POPULATION_SIZE);\n Set regionIds = regionsDataManager.getRegionIds();\n\n double dosesPerPerson = globalPropertiesDataManager\n .getGlobalPropertyValue(GlobalProperty.ANTIVIRAL_DOSES_PER_PERSON);\n\n double totalDoses = dosesPerPerson * populationSize;\n int doseCount = (int) totalDoses;\n int doseCountPerRegion = doseCount / regionIds.size();\n\n double bedsPerPerson = globalPropertiesDataManager\n .getGlobalPropertyValue(GlobalProperty.HOSPITAL_BEDS_PER_PERSON);\n\n double totalBeds = bedsPerPerson * populationSize;\n int bedCount = (int) totalBeds;\n int bedCountPerRegion = bedCount / regionIds.size();\n\n for (RegionId regionId : regionIds) {\n resourcesDataManager.addResourceToRegion(Resource.ANTI_VIRAL_MED, regionId, doseCountPerRegion);\n resourcesDataManager.addResourceToRegion(Resource.HOSPITAL_BED, regionId, bedCountPerRegion);\n }\n}\n\n\n\n\n11.8.3 Treatment Manager\nThe TreatmentManager actor, in Code Block 11.9, initializes by first deriving several values from the global properties plugin that will be useful as it manages people. It then gets from the person properties manager a list of all people who are not currently immune and marks them as infected.  It further determines for each person when they will become symptomatic and schedules the allocation of the antiviral resource.  \n\n\nCode Block 11.9: The treatment manager establishes various constants and infects all non-immune people, scheduling them for anti-viral treatment.\npublic void init(ActorContext actorContext) {\n this.actorContext = actorContext;\n regionsDataManager = actorContext.getDataManager(RegionsDataManager.class);\n resourcesDataManager = actorContext.getDataManager(ResourcesDataManager.class);\n StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);\n randomGenerator = stochasticsDataManager.getRandomGenerator();\n personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class);\n GlobalPropertiesDataManager globalPropertiesDataManager = actorContext\n .getDataManager(GlobalPropertiesDataManager.class);\n\n double maximumSymptomOnsetTime = globalPropertiesDataManager\n .getGlobalPropertyValue(GlobalProperty.MAXIMUM_SYMPTOM_ONSET_TIME);\n antiviralCoverageTime = globalPropertiesDataManager\n .getGlobalPropertyValue(GlobalProperty.ANTIVIRAL_COVERAGE_TIME);\n antiviralSuccessRate = globalPropertiesDataManager\n .getGlobalPropertyValue(GlobalProperty.ANTIVIRAL_SUCCESS_RATE);\n hospitalSuccessWithAntiviral = globalPropertiesDataManager\n .getGlobalPropertyValue(GlobalProperty.HOSPITAL_SUCCESS_WITH_ANTIVIRAL);\n hospitalSuccessWithoutAntiviral = globalPropertiesDataManager\n .getGlobalPropertyValue(GlobalProperty.HOSPITAL_SUCCESS_WITHOUT_ANTIVIRAL);\n hospitalStayDurationMin = globalPropertiesDataManager\n .getGlobalPropertyValue(GlobalProperty.HOSPITAL_STAY_DURATION_MIN);\n hospitalStayDurationMax = globalPropertiesDataManager\n .getGlobalPropertyValue(GlobalProperty.HOSPITAL_STAY_DURATION_MAX);\n\n List susceptiblePeople = personPropertiesDataManager.getPeopleWithPropertyValue(PersonProperty.IMMUNE,\n false);\n for (PersonId personId : susceptiblePeople) {\n double symptomOnsetTime = randomGenerator.nextDouble() * maximumSymptomOnsetTime;\n personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.INFECTED, true);\n\n actorContext.addPlan((c) -> treatWithAntiviral(personId), symptomOnsetTime);\n }\n}\n\n\nWhen a person becomes symptomatic (Code Block 11.10), the treatment manager attempts to find a single dose of the antiviral medication for them.   The dose must come from the region associated with the person.  If the dose is available, the person is treated and a review of the effectiveness of the treatment is scheduled.  If no dose is available, the person is immediately sent to hospital treatment. \n\n\nCode Block 11.10: The treatment manager attempts to treat an infected person with anti-viral medication, if it is available. If no dose is available, the person is immediately hospitalized.\nprivate void treatWithAntiviral(PersonId personId) {\n\n RegionId regionId = regionsDataManager.getPersonRegion(personId);\n\n long regionResourceLevel = resourcesDataManager.getRegionResourceLevel(regionId, Resource.ANTI_VIRAL_MED);\n\n if (regionResourceLevel > 0) {\n resourcesDataManager.transferResourceToPersonFromRegion(Resource.ANTI_VIRAL_MED, personId, 1L);\n\n personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.TREATED_WITH_ANTIVIRAL, true);\n\n actorContext.addPlan((c) -> assessAntiviralTreatment(personId),\n actorContext.getTime() + antiviralCoverageTime);\n } else {\n hospitalizePerson(personId);\n }\n}\n\n\nTo assess the effectiveness of the antiviral treatment (Code Block 11.11), the treatment manager randomly makes a determination based on the global property settings.  If the treatment is successful, then the person is marked as immune. Otherwise, the person is immediately sent to hospital treatment.\n\n\nCode Block 11.11: The treatment manager assesses the success of the anti-viral treatment of a person and expends the medication. If the treatment was unsuccessful the person is immediately hospitalized.\nprivate void assessAntiviralTreatment(PersonId personId) {\n\n resourcesDataManager.removeResourceFromPerson(Resource.ANTI_VIRAL_MED, personId, 1L);\n if (randomGenerator.nextDouble() < antiviralSuccessRate) {\n personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.IMMUNE, true);\n } else {\n hospitalizePerson(personId);\n }\n}\n\n\nHospitalization starts (Code Block 11.12) by finding an available hospital bed for the person from the person’s current region.  If one is available, it is assigned to the person and a review of the effectiveness of the hospital treatment is scheduled for a random time based on the min and max hospital treatment durations.  If no hospital bed is available, then the person dies.\n\n\nCode Block 11.12: The treatment manager attempts to find a hospital bed for a person who has not been treated or for whom treatment failed. If no hospital bed is available, the person is set to die in their home.\nprivate void hospitalizePerson(PersonId personId) {\n RegionId regionId = regionsDataManager.getPersonRegion(personId);\n\n long availableHospitalBeds = resourcesDataManager.getRegionResourceLevel(regionId, Resource.HOSPITAL_BED);\n\n if (availableHospitalBeds > 0) {\n resourcesDataManager.transferResourceToPersonFromRegion(Resource.HOSPITAL_BED, personId, 1L);\n\n personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.HOSPITALIZED, true);\n\n double hospitalizationDuration = (hospitalStayDurationMax - hospitalStayDurationMin)\n * randomGenerator.nextDouble() + hospitalStayDurationMin;\n\n actorContext.addPlan((c) -> assessHospitalization(personId),\n actorContext.getTime() + hospitalizationDuration);\n } else {\n personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.DEAD_IN_HOME, true);\n }\n}\n\n\nReviewing the effectiveness of the hospital treatment is shown in Code Block 11.13.  The outcome depends the previous treatment of the person with the antiviral medication.  If the treatment is successful, then the person is marked immune.  Otherwise, the person dies. The hospital bed resource is returned to the person’s region either way.\n\n\nCode Block 11.13: The treatment manager determines whether the hospitalization was a success taking into account whether the person received an antiviral treatment before entering the hospital. If the treatment was successful then the person is marked as immune. Otherwise, the person is marked as a hospital death.\nprivate void assessHospitalization(PersonId personId) {\n\n boolean treatedWithAntiViral = personPropertiesDataManager.getPersonPropertyValue(personId,\n PersonProperty.TREATED_WITH_ANTIVIRAL);\n double probabilityOfSuccess;\n if (treatedWithAntiViral) {\n probabilityOfSuccess = hospitalSuccessWithAntiviral;\n } else {\n probabilityOfSuccess = hospitalSuccessWithoutAntiviral;\n }\n if (randomGenerator.nextDouble() < probabilityOfSuccess) {\n personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.IMMUNE, true);\n } else {\n personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.DEAD_IN_HOSPITAL, true);\n }\n resourcesDataManager.transferResourceFromPersonToRegion(Resource.HOSPITAL_BED, personId, 1L);\n}\n\n\n\n\n11.8.4 Questionnaire Manager\nThe QuestionnaireManager actor, in Code Block 11.14, initializes by subscribing to changes to the number of antivirals or hospital beds possessed by people.\n\n\nCode Block 11.14: The questionnaire distributor initializes by subscribing to anti-viral and hospital bed person resource updates.\npublic void init(ActorContext actorContext) {\n ResourcesDataManager resourcesDataManager = actorContext.getDataManager(ResourcesDataManager.class);\n personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class);\n\n EventFilter eventFilter = resourcesDataManager\n .getEventFilterForPersonResourceUpdateEvent(Resource.ANTI_VIRAL_MED);\n actorContext.subscribe(eventFilter, this::handleAntiViralDistribution);\n\n eventFilter = resourcesDataManager.getEventFilterForPersonResourceUpdateEvent(Resource.HOSPITAL_BED);\n actorContext.subscribe(eventFilter, this::handleHospitalBedDistribution);\n\n}\n\n\nUpon observing a change in the number of antivirals assigned to a person (Code Block 11.15), the questionnaire manager distributes the questionnaire to the person if that person has just ended antiviral treatment (their level is now zero) but has not been hospitalized.\n\n\nCode Block 11.15: The questionnaire distributor distributes a questionnaire to each person who ends their anti-viral treatment and is not also hospitalized.\nprivate void handleAntiViralDistribution(ActorContext actorContext,\n PersonResourceUpdateEvent personResourceUpdateEvent) {\n PersonId personId = personResourceUpdateEvent.personId();\n boolean hasAntiviral = personResourceUpdateEvent.currentResourceLevel() > 0;\n if (!hasAntiviral) {\n boolean hospitalized = personPropertiesDataManager.getPersonPropertyValue(personId,\n PersonProperty.HOSPITALIZED);\n if (!hospitalized) {\n distributeQuestionaire(personId);\n }\n }\n}\n\n\nFor those who receive treatment at a hospital (Code Block 11.16), questionnaire manager distributes the questionnaire to the them when their hospital bed is returned and they have not died.\n\n\nCode Block 11.16: The questionnaire distributor distributes a questionnaire to each person that leaves the hospital.\nprivate void handleHospitalBedDistribution(ActorContext actorContext,\n PersonResourceUpdateEvent personResourceUpdateEvent) {\n PersonId personId = personResourceUpdateEvent.personId();\n boolean hasBed = personResourceUpdateEvent.currentResourceLevel() > 0;\n boolean dead = personIsDead(personId);\n if (!hasBed && !dead) {\n distributeQuestionaire(personId);\n }\n}" + }, + { + "objectID": "ch11-ResourcesPlugin.html#inspecting-the-output", + "href": "ch11-ResourcesPlugin.html#inspecting-the-output", + "title": "11  Resources Plugin", + "section": "11.9 Inspecting the output", + "text": "11.9 Inspecting the output\n\n11.9.1 person resource report\nThe 864 scenarios result in a large amount of output in the person resource report.  The example code has this report set to only record the end state of the simulation, where all resources have either been expended or returned back to the regions.  Running the report in daily mode will produce over 900,000 data rows, but will show a more useful result. \n\n\n11.9.2 treatment report\nThe treatment report shows for each scenario the number or people matching any particular set of person property values at the end of the simulation.  Since there are six person properties and they are all Boolean valued, there are potentially 64 possible tuples.  However, some tuples are impossible since a person cannot be both immune and dead, etc.  The result is 6,240 rows of data for the 864 scenarios.\n\n\n11.9.3 death report\nThe death report summarizes the treatment report and calculates per capita death rates for each region.  Due to the uneven distribution of resources between the regions based on their populations, the per capita deaths vary between 0.25 and 75 percent.  The trends in the data match expectations with deaths being driven by earlier onset of symptoms, longer hospital stays, and lower effectiveness of both treatment capabilities.\n\n\n11.9.4 questionnaire report\nThe questionnaire report shows the proportion of infected people that received the questionnaire. It also shows the mean and standard deviation of the delivery times of those questionnaires." + }, + { + "objectID": "ch12-MaterialsPlugin.html#plugin-data-initialization", + "href": "ch12-MaterialsPlugin.html#plugin-data-initialization", + "title": "12  Materials Plugin", + "section": "12.1 Plugin Data Initialization", + "text": "12.1 Plugin Data Initialization\nThe plugin is initialized using a MaterialsPluginData object that:\n\nContains material producer ids\nDefines materials producer properties\nSets materials producer property values\nSets materials producer initial resource levels\nInitializes stage and batches in initial inventory\nDefines batch property properties\nSets initial batch property values" + }, + { + "objectID": "ch12-MaterialsPlugin.html#plugin-behavior", + "href": "ch12-MaterialsPlugin.html#plugin-behavior", + "title": "12  Materials Plugin", + "section": "12.2 Plugin Behavior", + "text": "12.2 Plugin Behavior\nThe plugin adds a single data manager to the simulation as an instance of the MaterialsDataManager that is initialized with the MaterialsPluginData." + }, + { + "objectID": "ch12-MaterialsPlugin.html#data-manager", + "href": "ch12-MaterialsPlugin.html#data-manager", + "title": "12  Materials Plugin", + "section": "12.3 Data Manager", + "text": "12.3 Data Manager\nThe data manager manages the batch and stage inventories of the materials producers as well as managing the relevant property values and material transformations to resources. The data manager provides public methods that:\n\nDefined a new material type\nAdd and remove batches\nAdd and remove stages\nMove batches to and from stages\nExchange materials between batches\nOffer stages for distribution to other materials producers\nMove stages between producers\nAdd materials producers\nDefine various properties for producers and batches\nSet various property values\nTransform stages into new batches\nTransform stages into resources\nDistribute resources to regions\nProvide a wide range of materials related queries\n\nDetails per batch and stage\nInventories by producer/material type\nOffered stages that can be transferred\n\n\nThe data manager also produces observable events:\n\nBatchAdditionEvent – when a batch is created\nBatchAmountUpdateEvent – when material is exchanged between batches\nBatchImminentRemovalEvent – when a batch is about to be removed from the simulation\nBatchPropertyDefinitionEvent – when a new material property is defined\nBatchPropertyUpdateEvent – when a batch property value is updated\nMaterialIdAdditionEvent. – when a new material type is added\nMaterialsProducerAdditionEvent – when a new materials producer is added\nMaterialsProducerPropertyDefinitionEvent – when a new materials producer property is defined\nMaterialsProducerPropertyUpdateEvent – when a material producer property value is updated\nMaterialsProducerResourceUpdateEvent – when a resource amount is added to a materials producer\nStageAdditionEvent – when a stage is created\nStageImminentRemovalEvent – when a stage is about to be removed from the simulation\nStageMaterialsProducerUpdateEvent – when a stage is exchanged between materials producers\nStageMembershipAdditionEvent – when a batch is added to a stage\nStageMembershipRemovalEvent – when a batch is removed from a stage\nStageOfferUpdateEvent – when a stage’s offer state is updated" + }, + { + "objectID": "ch12-MaterialsPlugin.html#example-code-lesson-19", + "href": "ch12-MaterialsPlugin.html#example-code-lesson-19", + "title": "12  Materials Plugin", + "section": "12.4 Example Code (Lesson 19)", + "text": "12.4 Example Code (Lesson 19)\nExample_19.java shows the use of the materials plugin. In it we will examine:\n\nThe initialization of the materials plugin\nThe flow of materials and resources between regions and materials producers\nThe observation of materials events\n\nThe example includes nine plugins:\n\nMaterials plugin – (GCM core plugin) used to manage materials\nResources plugin – (GCM core plugin) used to manage resources\nPeople plugin – (GCM core plugin) used to manage people\nGroups plugin – (GCM core plugin) used for spreading infections to people via their homes, schools and work places\nPerson properties plugin– (GCM core plugin) used to decorate properties onto people\nGlobal properties plugin– (GCM core plugin) used to store policies and initial conditions\nStochastics plugin – (GCM core plugin) used to generate random numbers used in various decisions\nRegions Plugin– (GCM core plugin) used for resource flow\nModel plugin – (local plugin) used to introduce five actors that will:\n\nLoad the population\nManage infectious contacts\nManage vaccinations\nProduce antigen used in vaccine production\nProduce vaccines" + }, + { + "objectID": "ch12-MaterialsPlugin.html#model", + "href": "ch12-MaterialsPlugin.html#model", + "title": "12  Materials Plugin", + "section": "12.5 Model", + "text": "12.5 Model\nThe example’s model represents a disease that is preventable through vaccination. People at the start of the simulation are either immune or susceptible and some of the susceptible adults are infected. The disease is transmitted via home, work, and school environments. Vaccination is the only disease mitigation strategy and vaccinated people do not infect others nor can be infected. Vaccine production is started once a threshold of infections is reached and continues until adequate vaccine has been produced to cover the entire population. Vaccines are modeled as a resource and are produced using several materials by two actors. The first of these actors produces antigen materials that are used in the production of the vaccine by the second actor." + }, + { + "objectID": "ch12-MaterialsPlugin.html#model-execution", + "href": "ch12-MaterialsPlugin.html#model-execution", + "title": "12  Materials Plugin", + "section": "12.6 Model Execution", + "text": "12.6 Model Execution\nThe example’s execution is shown in Code Block 12.1\n\n\nCode Block 12.1: The various plugins are gathered from their initial data, dimensions are added and the experiment is executed over 81 scenarios using 8 threads.\n private void execute() {\n\n ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()//\n .setThreadCount(8)//\n .build();\n\n Experiment.builder()//\n .addPlugin(getMaterialsPlugin())//\n .addPlugin(getResourcesPlugin())//\n .addPlugin(getGlobalPropertiesPlugin())//\n .addPlugin(getPersonPropertiesPlugin())//\n .addPlugin(getStochasticsPlugin())//\n .addPlugin(getRegionsPlugin())//\n .addPlugin(getPeoplePlugin())//\n .addPlugin(getGroupsPlugin())//\n .addPlugin(ModelPlugin.getModelPlugin())//\n\n .addDimension(getInfectionThresholdDimension())//\n .addDimension(getCommunityContactRateDimension())//\n .addDimension(getIntialInfectionsDimension())//\n .addDimension(getR0Dimension())//\n\n .addExperimentContextConsumer(getNIOReportItemHandler())//\n .setExperimentParameterData(experimentParameterData).build()//\n .execute();//\n\n }\n\n\nThe first action is to add the materials plugin (Code Block 12.2, Code Block 12.3, Code Block 12.4). Only the material producer id values contained in the MaterialsProducer enumeration and the material ids contained in the Material enumeration are added to the plugin’s data.\n\n\nCode Block 12.2: The materials plugin establishes the materials producer ids and the material types.\nprivate Plugin getMaterialsPlugin() {\n final MaterialsPluginData.Builder builder = MaterialsPluginData.builder();\n for (final MaterialsProducer materialsProducer : MaterialsProducer.values()) {\n builder.addMaterialsProducerId(materialsProducer);\n }\n for (final Material material : Material.values()) {\n builder.addMaterial(material);\n }\n final MaterialsPluginData materialsPluginData = builder.build();\n return MaterialsPlugin.builder().setMaterialsPluginData(materialsPluginData).getMaterialsPlugin();\n}\n\n\n\n\nCode Block 12.3: The ids of the materials producers are implemented via an enumeration.\npublic enum MaterialsProducer implements MaterialsProducerId {\n\n VACCINE_PRODUCER, //\n ANTIGEN_PRODUCER;\n\n}\n\n\n\n\nCode Block 12.4: The material ids are implemented via an enumeration.\npublic enum Material implements MaterialId {\n\n VIRUS, //\n GROWTH_MEDIUM, //\n ANTIGEN, //\n ADJUVANT, //\n PRESERVATIVE, //\n STABILIZER;//\n}\n\n\nThe next action is to add the resources plugin (Code Block 12.5, Code Block 12.6). Only the resource id values contained in the Resource enumeration are added to the plugin’s data. Region and person resource levels are initialized to zero.\n\n\nCode Block 12.5: The resources plugin is created with the single VACCINE resource id.\nprivate Plugin getResourcesPlugin() {\n final ResourcesPluginData.Builder builder = ResourcesPluginData.builder();\n for (final ResourceId resourcId : Resource.values()) {\n builder.addResource(resourcId, 0.0, true);\n }\n final ResourcesPluginData resourcesPluginData = builder.build();\n return ResourcesPlugin.builder().setResourcesPluginData(resourcesPluginData).getResourcesPlugin();\n}\n\n\n\n\nCode Block 12.6: The resource ids are implemented via an enumeration.\npublic enum Resource implements ResourceId {\n VACCINE;\n}\n\n\nThe next action is to load the global properties plugin (Code Block 12.7). All of the global properties are marked as immutable since they will not change over the course of the simulation.\n\nSUSCEPTIBLE_POPULATION_PROPORTION – The fraction of the population that is susceptible\nINITIAL_INFECTIONS – The number of adults initially infected\nMIN_INFECTIOUS_PERIOD – The minimum number of days a person is infectious\nMAX_INFECTIOUS_PERIOD – The maximum number of days a person is infectious\nPOPULATION_SIZE – The initial size of the population\nCHILD_POPULATION_PROPORTION – The fraction of the population between the ages of 0 and 18, inclusive\nSENIOR_POPULATION_PROPORTION – The fraction of the population 65 and older\nR0 – The expected number of people a single person will infect if all contacts are susceptible and transmission success is 100%\nAVERAGE_HOME_SIZE – The average number of people per household\nAVERAGE_SCHOOL_SIZE – The average number of students in a school\nAVERAGE_WORK_SIZE – The average number of people per work place\nCOMMUNITY_CONTACT_RATE – The proportion of contacts that will be randomly chosen from the entire population\nMANUFACTURE_VACCINE – Boolean that triggers vaccine manufacture\nINFECTION_THRESHOLD – The fraction of the population that are infected in order to start vaccine manufacture\n\n\n\nCode Block 12.7: The global properties plugin is initialized with several properties.\nprivate Plugin getGlobalPropertiesPlugin() {\n final GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder();//\n\n PropertyDefinition propertyDefinition = PropertyDefinition.builder()//\n .setType(Double.class)//\n .setPropertyValueMutability(false)//\n .setDefaultValue(0.0)//\n .build();\n\n builder.defineGlobalProperty(GlobalProperty.SUSCEPTIBLE_POPULATION_PROPORTION, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.AVERAGE_HOME_SIZE, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.AVERAGE_SCHOOL_SIZE, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.AVERAGE_WORK_SIZE, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.CHILD_POPULATION_PROPORTION, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.SENIOR_POPULATION_PROPORTION, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.R0, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.COMMUNITY_CONTACT_RATE, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.INFECTION_THRESHOLD, propertyDefinition, 0);\n\n propertyDefinition = PropertyDefinition.builder()//\n .setType(Integer.class)//\n .setPropertyValueMutability(false)//\n .build();\n builder.defineGlobalProperty(GlobalProperty.INITIAL_INFECTIONS, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.MIN_INFECTIOUS_PERIOD, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.MAX_INFECTIOUS_PERIOD, propertyDefinition, 0);\n builder.defineGlobalProperty(GlobalProperty.POPULATION_SIZE, propertyDefinition, 0);\n\n propertyDefinition = PropertyDefinition.builder()//\n .setType(Boolean.class)//\n .setDefaultValue(false)//\n .setPropertyValueMutability(true)//\n .build();\n builder.defineGlobalProperty(GlobalProperty.MANUFACTURE_VACCINE, propertyDefinition, 0);\n\n builder.setGlobalPropertyValue(GlobalProperty.POPULATION_SIZE, 10_000, 0);\n builder.setGlobalPropertyValue(GlobalProperty.SUSCEPTIBLE_POPULATION_PROPORTION, 1.0, 0);\n builder.setGlobalPropertyValue(GlobalProperty.INITIAL_INFECTIONS, 1, 0);\n builder.setGlobalPropertyValue(GlobalProperty.MIN_INFECTIOUS_PERIOD, 7, 0);\n builder.setGlobalPropertyValue(GlobalProperty.MAX_INFECTIOUS_PERIOD, 14, 0);\n builder.setGlobalPropertyValue(GlobalProperty.R0, 2.0, 0);\n builder.setGlobalPropertyValue(GlobalProperty.CHILD_POPULATION_PROPORTION, 0.235, 0);\n builder.setGlobalPropertyValue(GlobalProperty.SENIOR_POPULATION_PROPORTION, 0.169, 0);\n builder.setGlobalPropertyValue(GlobalProperty.AVERAGE_HOME_SIZE, 2.5, 0);\n builder.setGlobalPropertyValue(GlobalProperty.AVERAGE_SCHOOL_SIZE, 250.0, 0);\n builder.setGlobalPropertyValue(GlobalProperty.AVERAGE_WORK_SIZE, 30.0, 0);\n builder.setGlobalPropertyValue(GlobalProperty.INFECTION_THRESHOLD, 0.0, 0);\n builder.setGlobalPropertyValue(GlobalProperty.COMMUNITY_CONTACT_RATE, 0.0, 0);\n\n final GlobalPropertiesPluginData globalPropertiesPluginData = builder.build();\n\n return GlobalPropertiesPlugin.builder().setGlobalPropertiesPluginData(globalPropertiesPluginData)\n .getGlobalPropertiesPlugin();\n\n}\n\n\nThe person properties plugin is loaded (Code Block 12.8).\n\nAGE – The integer age of a person\nVACCINATED – Boolean vaccination status\nVACCINE_SCHEDULED – Boolean indicating whether a vaccination is scheduled for a person\nDISEASE_STATE – IMMUNE, SUSCEPTIBLE, INFECTIOUS or RECOVERED\n\n\n\nCode Block 12.8: The person properties plugin includes person property definitions and the data for the person property report.\nprivate Plugin getPersonPropertiesPlugin() {\n\n final PersonPropertiesPluginData.Builder builder = PersonPropertiesPluginData.builder();\n\n PropertyDefinition propertyDefinition = PropertyDefinition.builder()//\n .setType(Boolean.class)//\n .setDefaultValue(false)//\n .build();\n\n builder.definePersonProperty(PersonProperty.VACCINATED, propertyDefinition, 0, false);//\n builder.definePersonProperty(PersonProperty.VACCINE_SCHEDULED, propertyDefinition, 0, false);//\n\n propertyDefinition = PropertyDefinition.builder()//\n .setType(Integer.class)//\n .build();//\n builder.definePersonProperty(PersonProperty.AGE, propertyDefinition, 0, false);//\n\n propertyDefinition = PropertyDefinition.builder()//\n .setType(DiseaseState.class)//\n .setDefaultValue(DiseaseState.SUSCEPTIBLE)//\n .build();\n\n builder.definePersonProperty(PersonProperty.DISEASE_STATE, propertyDefinition, 0, false);//\n\n final PersonPropertiesPluginData personPropertiesPluginData = builder.build();\n\n PersonPropertyReportPluginData personPropertyReportPluginData = //\n PersonPropertyReportPluginData.builder()//\n .setReportLabel(ModelReportLabel.PERSON_PROPERTY_REPORT)//\n .setReportPeriod(ReportPeriod.DAILY)//\n .includePersonProperty(PersonProperty.VACCINATED)//\n .includePersonProperty(PersonProperty.VACCINE_SCHEDULED)//\n .build();\n\n return PersonPropertiesPlugin.builder()//\n .setPersonPropertiesPluginData(personPropertiesPluginData)//\n .setPersonPropertyReportPluginData(personPropertyReportPluginData)//\n .getPersonPropertyPlugin();\n\n}\n\n\nThere are four reports:\n\nDiseaseStateReport – Shows the number of people in each of the disease states at the end of each simulation\nPersonPropertyReport – Shows an hourly summary the number of people having various person property values\nVaccineReport – Shows a daily summary of the number of people who have been scheduled for or have received vaccination\nVaccineProductionReport – Shows a daily summary of the internal inventory of the antigen and vaccine producers\n\nThe stochastic plugin is initialized with a random seed and all simulations will start in the same stochastic state. The Regions plugin has one region and the people plugin is loaded in an empty state. People are added by an actor in the model plugin. The groups plugin is initialized with the three group types: HOME, SCHOOL, and WORK.\nThe model plugin adds three actors:\n\nPopulationLoader – Adds people to the simulation and initializes immunity\nContactManager – Manages person to person transmission of the disease\nVaccinator – Distributes vaccine to people" + }, + { + "objectID": "ch12-MaterialsPlugin.html#experiment-dimensions", + "href": "ch12-MaterialsPlugin.html#experiment-dimensions", + "title": "12  Materials Plugin", + "section": "12.7 Experiment dimensions", + "text": "12.7 Experiment dimensions\nFour dimensions are added to the experiment that define alternate values for some of the global properties resulting in 81 scenarios. The dimension values are:\n\nINFECTION_THRESHOLD – 0.01, 0.02, 0.05\nCOMMUNITY_CONTACT_RATE – 0.0, 0.01, 0.05\nINITIAL_INFECTIONS – 1, 10, 100\nR0 – 2.0, 2.5, 3.0\n\nThe fixed values are:\n\nPOPULATION_SIZE – 10,000\nSUSCEPTIBLE_POPULATION_PROPORTION - 100%\nMIN_INFECTIOUS_PERIOD – 7 days\nMAX_INFECTIOUS_PERIOD – 14 days\nCHILD_POPULATION_PROPORTION – 0.235\nSENIOR_POPULATION_PROPORTION – 0.169\nAVERAGE_HOME_SIZE – 2.5\nAVERAGE_SCHOOL_SIZE – 250.0\nAVERAGE_WORK_SIZE – 30.0\nMANUFACTURE_VACCINE – false" + }, + { + "objectID": "ch12-MaterialsPlugin.html#the-actors", + "href": "ch12-MaterialsPlugin.html#the-actors", + "title": "12  Materials Plugin", + "section": "12.8 The actors", + "text": "12.8 The actors\nWe will finish this chapter by reviewing the actors and then examine the output.\n\n12.8.1 Population Loader\nThe PopulationLoader actor adds people to the simulation based on the number in the POPULATION_SIZE global property. The people are evenly distributed among the regions and each region creates children, working age adults and seniors based on the relevant global variables. The people are assigned homes, schools and work places accordingly, with each home having at least one adult. All people start out as either IMMUNE or SUSCEPTIBLE to the disease based on the SUSCEPTIBLE_POPULATION_PROPORTION global property.\n\n\n12.8.2 Contact Manager\nThe ContactManager actor, Code Block 12.9, schedules infectious contacts between infected people and the susceptible population. On its initialization, it establishes various parameters from the global variables and schedules the initial infections over the first day of the simulation.\n\n\nCode Block 12.9: The contact manager initializes by infecting the initially infected people in the first day.\npublic void init(final ActorContext actorContext) {\n this.actorContext = actorContext;\n\n final StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);\n randomGenerator = stochasticsDataManager.getRandomGenerator();\n final Random random = new Random(randomGenerator.nextLong());\n\n peopleDataManager = actorContext.getDataManager(PeopleDataManager.class);\n\n groupsDataManager = actorContext.getDataManager(GroupsDataManager.class);\n final GlobalPropertiesDataManager globalPropertiesDataManager = actorContext\n .getDataManager(GlobalPropertiesDataManager.class);\n communityContactRate = globalPropertiesDataManager\n .getGlobalPropertyValue(GlobalProperty.COMMUNITY_CONTACT_RATE);\n\n personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class);\n final List susceptiblePeople = personPropertiesDataManager\n .getPeopleWithPropertyValue(PersonProperty.DISEASE_STATE, DiseaseState.SUSCEPTIBLE);\n final List susceptibleAdults = new ArrayList<>();\n for (final PersonId personId : susceptiblePeople) {\n final int age = personPropertiesDataManager.getPersonPropertyValue(personId, PersonProperty.AGE);\n if (age > 18) {\n susceptibleAdults.add(personId);\n }\n }\n\n Collections.shuffle(susceptibleAdults, random);\n\n minInfectiousPeriod = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.MIN_INFECTIOUS_PERIOD);\n maxInfectiousPeriod = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.MAX_INFECTIOUS_PERIOD);\n final double r0 = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.R0);\n infectionInterval = (minInfectiousPeriod + maxInfectiousPeriod) / (2 * r0);\n\n int initialInfections = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.INITIAL_INFECTIONS);\n initialInfections = FastMath.min(initialInfections, susceptibleAdults.size());\n\n for (int i = 0; i < initialInfections; i++) {\n final PersonId personId = susceptibleAdults.get(i);\n final double planTime = (randomGenerator.nextDouble() * 0.5) + 0.25;\n actorContext.addPlan((c) -> infectPerson(personId), planTime);\n }\n}\n\n\nWhen a person is infected (Code Block 12.10), the contact manager determines the number of days that the person will be infectious and the number of potential infections that person will cause over those days. For each potential infectious contact, the manager schedules that contact at the relevant time. Finally, it schedules the transition of the person from infectious to recovered.\n\n\nCode Block 12.10: When a person is infected, the number of possible infectious contacts is determined and planned. After the last infectious contact, the person is scheduled to become recovered.\nprivate void infectPerson(final PersonId personId) {\n personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.DISEASE_STATE,\n DiseaseState.INFECTIOUS);\n final int infectiousDays = randomGenerator.nextInt(maxInfectiousPeriod - minInfectiousPeriod)\n + minInfectiousPeriod;\n final int infectionCount = (int) FastMath.round((infectiousDays / infectionInterval));\n\n double planTime = actorContext.getTime();\n\n for (int j = 0; j < infectionCount; j++) {\n planTime += infectionInterval;\n actorContext.addPlan((c) -> infectContact(personId), planTime);\n }\n actorContext.addPlan((c) -> endInfectiousness(personId), planTime);\n}\n\n\nFor each potential infectious contact, the manager (Code Block 12.11) first determines if the contact is in one of the person’s groups or is in the general community. If it is a community contact, a person is selected from the general population. If it is a group contact, one of the person’s groups is chosen at random and a person is selected from that group. The selected person must be susceptible and unvaccinated for the contact to transfer the infection. Thus, as more people are either infected or vaccinated, the number of people each infectious person infects decreases.\n\n\nCode Block 12.11: The contact manager attempts to make an infectious contact from an infected person to a randomly selected susceptible person who is not vaccinated from either the general public or from one of the infected person’s groups.\nprivate void infectContact(final PersonId personId) {\n\n if (randomGenerator.nextDouble() < communityContactRate) {\n final List people = peopleDataManager.getPeople();\n people.remove(personId);\n if (people.size() > 0) {\n final PersonId contactedPerson = people.get(randomGenerator.nextInt(people.size()));\n final DiseaseState diseaseState = personPropertiesDataManager.getPersonPropertyValue(contactedPerson,\n PersonProperty.DISEASE_STATE);\n final boolean vaccinated = personPropertiesDataManager.getPersonPropertyValue(contactedPerson,\n PersonProperty.VACCINATED);\n if ((diseaseState == DiseaseState.SUSCEPTIBLE) && !vaccinated) {\n infectPerson(contactedPerson);\n }\n }\n } else {\n final List groupsForPerson = groupsDataManager.getGroupsForPerson(personId);\n final GroupId groupId = groupsForPerson.get(randomGenerator.nextInt(groupsForPerson.size()));\n final GroupSampler groupSampler = GroupSampler.builder().setExcludedPersonId(personId).build();\n final Optional optional = groupsDataManager.sampleGroup(groupId, groupSampler);\n if (optional.isPresent()) {\n final PersonId contactedPerson = optional.get();\n final DiseaseState diseaseState = personPropertiesDataManager.getPersonPropertyValue(contactedPerson,\n PersonProperty.DISEASE_STATE);\n final boolean vaccinated = personPropertiesDataManager.getPersonPropertyValue(contactedPerson,\n PersonProperty.VACCINATED);\n if ((diseaseState == DiseaseState.SUSCEPTIBLE) && !vaccinated) {\n infectPerson(contactedPerson);\n }\n }\n }\n}\n\n\n\n\n12.8.3 Vaccinator\nThe Vaccinator actor, in Code Block 12.12, creates the VaccineProducer and AntigenProducer actors and initializes various parameters and data structures that will help it distribute vaccines. It subscribes to changes in the disease state of people so that it can determine how many vaccines are needed. It subscribes to changes in the resource states of materials producers so that it can distribute vaccines from the VaccineProduer to the regions and then on to the people in those regions. The two producers will not start manufacturing materials until the Vaccinator determines that enough people have been infected. When the number of infected exceeds the required threshold the Vaccinator will set the MANUFACTURE_VACCINE global variable to true and the producers will begin material and resource production.\n\n\nCode Block 12.12: The vaccinator initializes by subscribing to changes in materials producer resource levels so that it can distribute vaccines to regions. It also subscribes to changes in person disease state to select people for vaccination.\npublic void init(final ActorContext actorContext) {\n this.actorContext = actorContext;\n actorContext.addActor(new VaccineProducer(MaterialsProducer.VACCINE_PRODUCER)::init);\n actorContext.addActor(new AntigenProducer(MaterialsProducer.ANTIGEN_PRODUCER)::init);\n\n globalPropertiesDataManager = actorContext.getDataManager(GlobalPropertiesDataManager.class);\n peopleDataManager = actorContext.getDataManager(PeopleDataManager.class);\n personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class);\n resourcesDataManager = actorContext.getDataManager(ResourcesDataManager.class);\n regionsDataManager = actorContext.getDataManager(RegionsDataManager.class);\n materialsDataManager = actorContext.getDataManager(MaterialsDataManager.class);\n actorContext.subscribe(materialsDataManager.getEventFilterForMaterialsProducerResourceUpdateEvent(),\n this::handleMaterialsProducerResourceUpdateEvent);\n\n for (final RegionId regionId : regionsDataManager.getRegionIds()) {\n vaccinationSchedules.put(regionId, new MutableDouble());\n availableVaccines.put(regionId, new MutableLong());\n }\n\n actorContext.subscribe(\n personPropertiesDataManager.getEventFilterForPersonPropertyUpdateEvent(PersonProperty.DISEASE_STATE),\n this::handlePersonPropertyUpdateEvent);\n\n final double infectionThreshold = globalPropertiesDataManager\n .getGlobalPropertyValue(GlobalProperty.INFECTION_THRESHOLD);\n infectedPersonCount = personPropertiesDataManager\n .getPeopleWithPropertyValue(PersonProperty.DISEASE_STATE, DiseaseState.INFECTIOUS).size();\n infectionPersonCountThreshold = (int) (peopleDataManager.getPopulationCount() * infectionThreshold);\n determineVaccineManufacutureStart();\n scheduleVaccinations();\n}\n\n\nAs people become infected (Code Block 12.13, Code Block 12.14), the Vaccinator will determine if the vaccine manufacture needs to be started. Once the decision is made to start manufacture, the Vaccinator no longer needs to observe people becoming infectious, so it unsubscribes to changes in the disease states of people.\n\n\nCode Block 12.13: If a person become infectious, the vaccinator reviews whether to start vaccine manufacture.\nprivate void handlePersonPropertyUpdateEvent(final ActorContext actorContext,\n final PersonPropertyUpdateEvent personPropertyUpdateEvent) {\n\n final DiseaseState diseaseState = (DiseaseState) personPropertyUpdateEvent.getCurrentPropertyValue();\n if (diseaseState == DiseaseState.INFECTIOUS) {\n infectedPersonCount++;\n determineVaccineManufacutureStart();\n }\n}\n\n\n\n\nCode Block 12.14: If vaccine manufacture has not yet started and the number of infected people exceeds a threshold, then the vaccinator set the MANUFACTURE_VACCINE global property to true, signaling to the vaccine related materials producers to start.\nprivate void determineVaccineManufacutureStart() {\n if (!manufactureStarted) {\n if (infectedPersonCount >= infectionPersonCountThreshold) {\n manufactureStarted = true;\n globalPropertiesDataManager.setGlobalPropertyValue(GlobalProperty.MANUFACTURE_VACCINE, true);\n actorContext.unsubscribe(personPropertiesDataManager\n .getEventFilterForPersonPropertyUpdateEvent(PersonProperty.DISEASE_STATE));\n }\n }\n}\n\n\nWhenever resources (vaccines in this case) are accumulated on a materials producer (Code Block 12.15), the Vaccinator must determine (Code Block 12.16) if the resource is a vaccine, whether the change to the resource level represents an addition of that resource to the inventory of the materials producer and whether any vaccine is needed. If the vaccine is available and needed, then the Vaccinator transfers the vaccines evenly amongst the regions and tries to schedule new vaccinations.\n\n\nCode Block 12.15: When a resource change occurs on a materials producer, the vaccinator determines if the change represents doses of vaccine and whether there is any remaining demand.\nprivate void handleMaterialsProducerResourceUpdateEvent(final ActorContext actorContext,\n final MaterialsProducerResourceUpdateEvent materialsProducerResourceUpdateEvent) {\n if (isCapturableResource(materialsProducerResourceUpdateEvent)) {\n\n final MaterialsProducerId materialsProducerId = materialsProducerResourceUpdateEvent.materialsProducerId();\n\n final long resourceLevel = materialsDataManager.getMaterialsProducerResourceLevel(materialsProducerId,\n Resource.VACCINE);\n final List regionIds = new ArrayList<>(regionsDataManager.getRegionIds());\n\n final long resourceToTransfer = resourceLevel / regionIds.size();\n long remainderResource = resourceLevel % regionIds.size();\n\n for (final RegionId regionId : regionIds) {\n final MutableLong availableVaccine = availableVaccines.get(regionId);\n if (remainderResource > 0) {\n materialsDataManager.transferResourceToRegion(materialsProducerId, Resource.VACCINE, regionId,\n resourceToTransfer + 1);\n remainderResource--;\n availableVaccine.increment(resourceToTransfer + 1);\n } else {\n materialsDataManager.transferResourceToRegion(materialsProducerId, Resource.VACCINE, regionId,\n resourceToTransfer);\n availableVaccine.increment(resourceToTransfer);\n }\n }\n scheduleVaccinations();\n }\n}\n\n\n\n\nCode Block 12.16: When a materials producer updates its resource level, the vaccinator confirms that the resource is vaccine doses that have been added to the producer’s inventory and that there is current demand for the vaccine.\nprivate boolean isCapturableResource(\n final MaterialsProducerResourceUpdateEvent materialsProducerResourceUpdateEvent) {\n if (!materialsProducerResourceUpdateEvent.resourceId().equals(Resource.VACCINE)) {\n return false;\n }\n final boolean isResourceAdditionToProducer = materialsProducerResourceUpdateEvent\n .currentResourceLevel() > materialsProducerResourceUpdateEvent.previousResourceLevel();\n if (!isResourceAdditionToProducer) {\n return false;\n }\n\n long distributedVaccineCount = personPropertiesDataManager\n .getPersonCountForPropertyValue(PersonProperty.VACCINATED, true);\n\n for (final RegionId regionId : regionsDataManager.getRegionIds()) {\n distributedVaccineCount += resourcesDataManager.getRegionResourceLevel(regionId, Resource.VACCINE);\n }\n if (distributedVaccineCount >= peopleDataManager.getPopulationCount()) {\n return false;\n }\n return true;\n}\n\n\nOnce vaccine doses are available, the Vaccinator schedules vaccinations (Code Block 12.17) at a fixed rate of 100 per day. The vaccines doses are distributed evenly amongst the regions and people are scheduled for vaccination throughout the day(s) following the receipt of vaccines from the VaccineProducer. Finally, the Vaccinator determines if enough vaccine has been received and to vaccinate the entire population and thus the manufacture of resource and materials can be halted.\n\n\nCode Block 12.17: The vaccinator schedules vaccinations at initialization and whenever a materials producer produces resources. The vaccinator tries to distribute the available doses of vaccine with a standard delay time between scheduled vaccinations.\nprivate void scheduleVaccinations() {\n final double delayTime = 1 / (double) vaccinationsPerRegionPerDay;\n\n for (final RegionId regionId : vaccinationSchedules.keySet()) {\n final MutableLong availableVaccine = availableVaccines.get(regionId);\n final MutableDouble vaccineTime = vaccinationSchedules.get(regionId);\n vaccineTime.increment(delayTime);\n if (vaccineTime.getValue() < actorContext.getTime()) {\n vaccineTime.setValue(actorContext.getTime());\n }\n final List peopleInRegion = regionsDataManager.getPeopleInRegion(regionId);\n for (final PersonId personId : peopleInRegion) {\n final boolean vaccine_scheduled = personPropertiesDataManager.getPersonPropertyValue(personId,\n PersonProperty.VACCINE_SCHEDULED);\n if (availableVaccine.getValue() <= 0) {\n break;\n }\n if (!vaccine_scheduled) {\n availableVaccine.decrement();\n personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.VACCINE_SCHEDULED,\n true);\n actorContext.addPlan((c) -> vaccinatePerson(personId), vaccineTime.getValue());\n vaccineTime.increment(delayTime);\n }\n }\n }\n final int populationSize = peopleDataManager.getPopulationCount();\n final int scheduledVaccinationCount = personPropertiesDataManager\n .getPersonCountForPropertyValue(PersonProperty.VACCINE_SCHEDULED, true);\n if (scheduledVaccinationCount >= populationSize) {\n globalPropertiesDataManager.setGlobalPropertyValue(GlobalProperty.MANUFACTURE_VACCINE, false);\n }\n}\n\n\nThe vaccination for each person (Code Block 12.18) sets the person’s VACCINATED property to true and transfers a single unit (dose) of vaccine from the person’s region to the person.\n\n\nCode Block 12.18: The vaccinator sets the person’s VACCINATED property to true and moves one unit of vaccine from the person’s region to the person.\nprivate void vaccinatePerson(final PersonId personId) {\n personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.VACCINATED, true);\n resourcesDataManager.transferResourceToPersonFromRegion(Resource.VACCINE, personId, 1L);\n}\n\n\n\n\n12.8.4 Antigen Producer\nThe AntigenProducer creates the ANTIGEN material from the GROWTH_MEDIUM and VIRUS materials. The generated ANTIGEN material is placed on stages in batches of 25 units of antigen and the stages are offered up to other materials producers. The VaccineProducer will transfer these stages to itself and use the ANTIGEN material in its own production of vaccine. Various factors limit the production of antigen. The GROWTH_MEDIUM and VIRUS materials initialize to a zero inventory and are scheduled to be received at regular intervals depending on ongoing demands and ordering limits. The AntigenProducer has a maximum stage storage capacity of 60 stages and production must halt once that limit is reached. The VaccineProducer must remove the stages for manufacture to continue. Each batch requires 6 hours to create and 15 days to ferment. The AntigenProducer can only start one batch of fermentation at a time.\nThe AntigenProducer initializes (Code Block 12.19) by adding specifications for the two materials used in the production of antigen. GROWTH_MEDIUM is ordered in allotments of 35 units and there is a 7 day delay from the time of order to receipt. VIRUS is ordered in allotments of 100 units with a 21 day delay. Both resources are utilized at one unit per unit of ANTIGEN produced. The AntigenProducer subscribes to the transfer of stages away from itself (capture by the VaccineProduer) so that it can possibly resume manufacture. It also subscribes to changes in the MANUFACTURE_VACCINE global property which must be true for any manufacturing of antigen to continue.\n\n\nCode Block 12.19: The antigen producer initializes by subscribing to stage transfers from itself as well as changes to the manufacturing policy.\npublic void init(final ActorContext actorContext) {\n this.actorContext = actorContext;\n materialsDataManager = actorContext.getDataManager(MaterialsDataManager.class);\n globalPropertiesDataManager = actorContext.getDataManager(GlobalPropertiesDataManager.class);\n addMaterialRec(Material.GROWTH_MEDIUM, MaterialManufactureSpecification.builder()//\n .setDeliveryAmount(35.0)//\n .setDeliveryDelay(7.0)//\n .setStageAmount(1.0));//\n\n addMaterialRec(Material.VIRUS, MaterialManufactureSpecification.builder()//\n .setDeliveryAmount(100.0)//\n .setDeliveryDelay(21.0)//\n .setStageAmount(1.0));//\n\n // each time a stage is transferred\n actorContext.subscribe(\n materialsDataManager.getEventFilterForStageMaterialsProducerUpdateEvent_BySource(materialsProducerId),\n (c, e) -> planFermentation());\n\n // each time the manufacture policy is changed\n actorContext.subscribe(globalPropertiesDataManager.getEventFilterForGlobalPropertyUpdateEvent(\n GlobalProperty.MANUFACTURE_VACCINE), (c, e) -> planFermentation());\n\n planFermentation();\n}\n\n\nThe planning of antigen batch creation and fermentation is triggered by several events:\n\nInitialization of the AntigenProducer\nTransfer of an offered stage to the VaccineProducer\nChange of the MANUFACTURE_VACCINE global property by the Vaccinator\nReceipt of ordered GROWTH_MEDIUM or VIRUS\nThe fermentation of a batch of ANTIGEN\n\nThe planning process (Code Block 12.20) starts by determining if manufacture has been halted by the Vaccinator. If manufacturing is allowed, then each of the required materials are analyzed and ordered as needed to build a full complement of offered stages. The process continues by constructing stages from the batches of GROWTH_MEDIUM and VIRUS that are held in inventory until there are either insufficient materials or no more capacity to store stages. The start of the fermentation process is determined and the end of each fermentation is scheduled.\n\n\nCode Block 12.20: Responding to events that may allow for additional manufacture of antigen stages, the antigen producer attempts to continue manufacturing.\nprivate void planFermentation() {\n\n final Boolean continueManufature = globalPropertiesDataManager\n .getGlobalPropertyValue(GlobalProperty.MANUFACTURE_VACCINE);\n if (!continueManufature) {\n return;\n }\n orderMaterials();\n\n while (!stagesAtCapacity() && hasSufficientMaterialsForNewStage()) {\n final StageId stageId = materialsDataManager.addStage(materialsProducerId);\n for (final MaterialId materialId : materialRecs.keySet()) {\n final MaterialManufactureSpecification materialManufactureSpecification = materialRecs.get(materialId);\n final BatchId newBatchId = materialsDataManager.addBatch(BatchConstructionInfo.builder()\n .setMaterialsProducerId(materialsProducerId).setMaterialId(materialId).build());\n materialsDataManager.transferMaterialBetweenBatches(materialManufactureSpecification.getBatchId(),\n newBatchId, materialManufactureSpecification.getStageAmount());\n materialsDataManager.moveBatchToStage(newBatchId, stageId);\n }\n final double batchAssemblyStartTime = FastMath.max(actorContext.getTime(), lastBatchAssemblyEndTime);\n final double fermentationStartTime = batchAssemblyStartTime + batchAssemblyDuration;\n lastBatchAssemblyEndTime = fermentationStartTime;\n final double planTime = fermentationStartTime + fermentationTime;\n actorContext.addPlan((c) -> endFermentationStage(stageId), planTime);\n }\n}\n\n\nOnce fermentation is complete (Code Block 12.21) for a stage containing the GROWTH_MEDIUM and VIRUS, the stage is converted into a batch of 25 ANTIGEN units and staged. The new stage is offered for transfer to the VaccineProducer.\n\n\nCode Block 12.21: When an antigen containing stage is ready for release, the antigen producer converts the stage into a batch of antigen and re-stages this batch on an offered stage.\nprivate void endFermentationStage(final StageId stageId) {\n final BatchId batch = materialsDataManager.convertStageToBatch(//\n StageConversionInfo.builder()//\n .setAmount(antigenUnits)//\n .setMaterialId(Material.ANTIGEN)//\n .setStageId(stageId)//\n .build());//\n\n final StageId antigenStage = materialsDataManager.addStage(materialsProducerId);\n materialsDataManager.moveBatchToStage(batch, antigenStage);\n materialsDataManager.setStageOfferState(antigenStage, true);\n planFermentation();\n}\n\n\n\n\n12.8.5 Vaccine Producer\nThe VaccineProducer creates 50 VACCINE resource units from staged ANTIGEN, ADJUVANT, PRESERVATIVE and STABILIZER materials and stores those vaccines. The Vaccinator gathers the stored vaccines and distributes them to the regions and people. The ANTIGEN material is gathered from the offered stages of the AntigenProducer and the other materials are ordered and received in a similar manner to the AntigenProducer. Vaccine production is limited by various factors. The non-ANTIGEN materials initialize to a zero inventory and are scheduled to be received at regular intervals depending on ongoing demands and ordering limits. The ANTIGEN material must be received from the AntigenProducer. There is a maximum stage storage capacity of 15 stages and production must halt once that limit is reached. Staged batches require 2.4 hours to assemble and 2 days to produce vaccine doses.\nThe VaccineProducer initializes (Code Block 12.22) by constructing an empty batch of ANTIGEN that will act as the inventory for all ANTIGEN used in stage creation. It then initializes the ordering constraints for the remaining materials. It subscribes to changes in stage offer states that will be used to detect when the AntigenProducer is offering stages containing ANTIGEN. Finally, it subscribes to changes in the MANUFACTURE_VACCINE global property which must be true for any manufacturing of vaccine to continue.\nThe planning of vaccine preparation and creation is triggered by several events:\n\nInitialization of the VaccineProducer\nThe offering of a stage containing ANTIGEN from the AntigenProducer\nChange of the MANUFACTURE_VACCINE global property by the Vaccinator\nReceipt of ordered ADJUVANT, PRESERVATIVE or STABILIZER\nThe completion of stage conversion into vaccine doses\n\nThe planning process (Code Block 12.23) starts by determining if manufacture has been halted by the Vaccinator. If manufacturing is allowed, then each of the required materials are analyzed and ordered as needed to build a full complement of offered stages. The process continues by constructing stages from the batches of ANTIGEN, ADJUVANT, PRESERVATIVE and STABILIZER that are held in inventory until there are either insufficient materials or no more capacity to store stages. The start of the vaccine preparation process is determined and the end of each preparation is scheduled.\n\n\nCode Block 12.22: The vaccine producer initializes by subscribing to offered stages(from the antigen producer) and subscribing to the start of vaccine manufacture.\npublic void init(final ActorContext actorContext) {\n this.actorContext = actorContext;\n materialsDataManager = actorContext.getDataManager(MaterialsDataManager.class);\n globalPropertiesDataManager = actorContext.getDataManager(GlobalPropertiesDataManager.class);\n\n final BatchConstructionInfo batchConstructionInfo = //\n BatchConstructionInfo.builder()//\n .setMaterialId(Material.ANTIGEN)//\n .setMaterialsProducerId(materialsProducerId)//\n .build();//\n antigenBatchId = materialsDataManager.addBatch(batchConstructionInfo);\n\n addMaterialRec(Material.ADJUVANT, MaterialManufactureSpecification.builder()//\n .setDeliveryAmount(150.0)//\n .setDeliveryDelay(3.0)//\n .setStageAmount(2.7));//\n\n addMaterialRec(Material.PRESERVATIVE, MaterialManufactureSpecification.builder()//\n .setDeliveryAmount(1000.0)//\n .setDeliveryDelay(14.0)//\n .setStageAmount(3.0));//\n\n addMaterialRec(Material.STABILIZER, MaterialManufactureSpecification.builder()//\n .setDeliveryAmount(100.0)//\n .setDeliveryDelay(14.0)//\n .setStageAmount(1.0));//\n\n actorContext.subscribe(materialsDataManager.getEventFilterForStageOfferUpdateEvent(),\n this::handleStageOfferUpdateEvent);\n actorContext.subscribe(globalPropertiesDataManager.getEventFilterForGlobalPropertyUpdateEvent(\n GlobalProperty.MANUFACTURE_VACCINE), (c, e) -> planVaccinePrepartion());\n\n planVaccinePrepartion();\n}\n\n\n\n\nCode Block 12.23: Responding to events that may allow for additional manufacture of vaccine doses, the vaccine producer attempts to continue manufacturing.\nprivate void planVaccinePrepartion() {\n final Boolean continueManufature = globalPropertiesDataManager\n .getGlobalPropertyValue(GlobalProperty.MANUFACTURE_VACCINE);\n if (!continueManufature) {\n return;\n }\n orderMaterials();\n\n while (!stagesAtCapacity() && hasSufficientMaterialsForNewStage() && vaccineLevelBelowCapacity()) {\n final StageId stageId = materialsDataManager.addStage(materialsProducerId);\n for (final MaterialId materialId : materialRecs.keySet()) {\n final MaterialManufactureSpecification materialManufactureSpecification = materialRecs.get(materialId);\n final BatchId newBatchId = materialsDataManager.addBatch(BatchConstructionInfo.builder()\n .setMaterialsProducerId(materialsProducerId).setMaterialId(materialId).build());\n materialsDataManager.transferMaterialBetweenBatches(materialManufactureSpecification.getBatchId(),\n newBatchId, materialManufactureSpecification.getStageAmount());\n materialsDataManager.moveBatchToStage(newBatchId, stageId);\n }\n\n BatchId newBatchId = materialsDataManager.addBatch(BatchConstructionInfo.builder()\n .setMaterialsProducerId(materialsProducerId).setMaterialId(Material.ANTIGEN).build());\n materialsDataManager.transferMaterialBetweenBatches(antigenBatchId, newBatchId, antigenAmountPerBatch);\n materialsDataManager.moveBatchToStage(newBatchId, stageId);\n\n final double batchAssemblyStartTime = FastMath.max(actorContext.getTime(), lastBatchAssemblyEndTime);\n final double fermentationStartTime = batchAssemblyStartTime + batchAssemblyDuration;\n lastBatchAssemblyEndTime = fermentationStartTime;\n final double planTime = fermentationStartTime + vaccinePreparationTime;\n actorContext.addPlan((c) -> endVaccinePreparation(stageId), planTime);\n }\n}\n\n\nOnce vaccine preparation is complete (Code Block 12.24) for a stage containing the ANTIGEN, ADJUVANT, PRESERVATIVE and STABILIZER materials, the stage is converted into 25 VACCINE units and stored on the producer. The Vaccinator will observe this change in inventory and may transfer the vaccine units to the regions and people as needed.\n\n\nCode Block 12.24: When a vaccine production stage is ready for release, the vaccine producer converts the stage doses of vaccine and places them in its resource inventory.\nprivate void endVaccinePreparation(final StageId stageId) {\n materialsDataManager.convertStageToResource(stageId, Resource.VACCINE, vaccineUnits);\n planVaccinePrepartion();\n}" + }, + { + "objectID": "ch12-MaterialsPlugin.html#inspecting-the-output", + "href": "ch12-MaterialsPlugin.html#inspecting-the-output", + "title": "12  Materials Plugin", + "section": "12.9 Inspecting the output", + "text": "12.9 Inspecting the output\n\n12.9.1 Person Property Report\nThe 81 scenarios result in a large amount of output (>1,000,000 rows) in the person property report. It shows the aggregate counts for people across all the person properties.\n\n\n12.9.2 Disease State Report\nAnalysis of the number of recovered people at the end of each simulation shows that R0 and infection threshold needed to start vaccine manufacture play a significant role in the outcome. The number of initial infections and the community contact rate also show the expected positive correlation to the number of recovered, but to a lesser extent.\n\n\n12.9.3 Vaccine Report\nThe vaccine report shows that lower infection thresholds drive an earlier start of vaccine manufacture. However, due to the relatively low production of the vaccine, it still takes 20-23 weeks to vaccinate the entire population.\n\n\n12.9.4 Vaccine Production Report\nThe vaccine production report records the slow build up of ordered materials and conversion of those into vaccines. From the start of the materials collection to the first doses of vaccine being administered is approximately 32 days." + }, + { + "objectID": "ch13-PartitionsPlugin.html#plugin-data-initialization", + "href": "ch13-PartitionsPlugin.html#plugin-data-initialization", + "title": "13  Partitions Plugin", + "section": "13.1 Plugin Data Initialization", + "text": "13.1 Plugin Data Initialization\nPartitions are built dynamically by actors and data managers. While serialization of simulation state exceeds the scope of this chapter, the PartitionsPluginData class contains a single argument to support run-continuity between related experiment executions. The default policy is to not support run continuity and should only be set to true when the need for run continuity exists." + }, + { + "objectID": "ch13-PartitionsPlugin.html#plugin-behavior", + "href": "ch13-PartitionsPlugin.html#plugin-behavior", + "title": "13  Partitions Plugin", + "section": "13.2 Plugin Behavior", + "text": "13.2 Plugin Behavior\nThe plugin adds a single data manager to the simulation as an instance of the PartitionsDataManager that is initialized with the PartitionsPluginData. The plugin has fixed dependencies on the People plugin and the Stochastic plugin. Unlike other plugins, its dependencies are dynamic and provided by the modeler. This topic will be covered later in the example code." + }, + { + "objectID": "ch13-PartitionsPlugin.html#data-manager", + "href": "ch13-PartitionsPlugin.html#data-manager", + "title": "13  Partitions Plugin", + "section": "13.3 Data Manager", + "text": "13.3 Data Manager\nThe PartitionsDataManager provides various operations with partitions. The data manager provides public methods that:\n\nAdd partitions\nRemove partitions\nProvide various queries about the state of partitions\nAllow random sampling of people from partitions\n\nPartitions are referenced by a client-assigned id and are maintained by the data manager. Each partition is composed of a multi-layered filter and zero to many labelers. A labeler is a mechanism for labeling a person based on that person’s characteristics. The labels form the dimensions of the cellular structure of the partition. For example, a labeler could label a person based on their integer age, but convert that age into labels such as “CHILD”, “ADULT” AND “SENIOR”. Labelers can span multiple personal charactertics such as combining regions with ages. For example, the labels might be “EASTERN_SHOOL_AGE_CHILDREN” or “WESTERN_SENIORS”, etc." + }, + { + "objectID": "ch13-PartitionsPlugin.html#example-code-lesson-20", + "href": "ch13-PartitionsPlugin.html#example-code-lesson-20", + "title": "13  Partitions Plugin", + "section": "13.4 Example Code (Lesson 20)", + "text": "13.4 Example Code (Lesson 20)\nExample_20.java shows the use of the partitions plugin. In it we will examine\n\nThe formation of partitions\nThe sampling of partitions\n\nThe example includes seven plugins:\n\nPeople plugin – (GCM core plugin) used to manage people\nPerson properties plugin– (GCM core plugin) used to decorate properties onto people\nGlobal properties plugin– (GCM core plugin) used to store policies and initial conditions\nStochastics plugin – (GCM core plugin) used to generate random numbers used in various decisions\nRegions Plugin– (GCM core plugin) supports the person properties plugin\nPartitions Plugin - (GCM core plugin) used for managing partitions\nModel plugin – (local plugin) used to introduce three actors that will:\n\nLoad the population\nManage infectious contacts\nManage vaccinations" + }, + { + "objectID": "ch13-PartitionsPlugin.html#model", + "href": "ch13-PartitionsPlugin.html#model", + "title": "13  Partitions Plugin", + "section": "13.5 Model", + "text": "13.5 Model\nThe example’s model represents a disease that is preventable through vaccination. A small number of people at the start of the simulation are infected and the rest are susceptible. Vaccination is limited to uninfected adults and three vaccinations are needed to confer full immunity. There is 30 day delay between vaccine doses. The disease is transmitted at random from the infected to the susceptible, there is no incubation period and infectiousness lasts from 3 to 12 days. Vaccine supply is unlimited, but the vaccination rate is fixed with vaccine being assigned using lottery assignment: Older adults and those with the least vaccine protection are given a statistical preference." + }, + { + "objectID": "ch13-PartitionsPlugin.html#model-execution", + "href": "ch13-PartitionsPlugin.html#model-execution", + "title": "13  Partitions Plugin", + "section": "13.6 Model Execution", + "text": "13.6 Model Execution\nThe example’s execution is shown in Code Block 13.1\n\n\nCode Block 13.1: The various plugins are gathered from their initial data and the experiment is executed.\nprivate void execute() {\n Experiment.builder()//\n .addPlugin(getGlobalPropertiesPlugin())//\n .addPlugin(getPersonPropertiesPlugin())//\n .addPlugin(getRegionsPlugin())//\n .addPlugin(getStochasticPlugin())//\n .addPlugin(getPeoplePlugin())//\n .addPlugin(getPartitionsPlugin())//\n .addPlugin(ModelPlugin.getModelPlugin())//\n .addExperimentContextConsumer(getNIOReportItemHandler())//\n .build()//\n .execute();\n}\n\n\nThere are several fixed global properties, Code Block 13.2 :\n\nVACCINATOR_TYPE – the type of vaccinator used by the model\nVACCINATIONS_PER_DAY – the number of vaccinations per day allowed\nTRANSMISSION_PROBABILTY – the base probabilty of disease transmission per infectious contact\nINFECTIOUS_CONTACT_RATE – the number of potentially infectious contacts per day per person\nMINIMUM_INFECTIOUS_PERIOD – then minimum number of days that a person is infectious\nMAXIMUM_INFECTIOUS_PERIOD – the maximum number of days that a person is infectious\nINITIAL_INFECTION_COUNT – the number of people who are infectious at the begining of the simulation\nINTER_VACCINATION_DELAY_TIME – the number of days required between a peson’s vaccinations\nPOPULATION_SIZE – the initial size of the population\n\n\n\nCode Block 13.2: The global properties are all fixed values.\nprivate Plugin getGlobalPropertiesPlugin() {\n GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder();\n PropertyDefinition propertyDefinition = PropertyDefinition.builder().setPropertyValueMutability(false)\n .setDefaultValue(10_000).setType(Integer.class).build();\n builder.defineGlobalProperty(GlobalProperty.POPULATION_SIZE, propertyDefinition, 0);\n\n propertyDefinition = PropertyDefinition.builder().setPropertyValueMutability(false).setDefaultValue(10)\n .setType(Integer.class).build();\n builder.defineGlobalProperty(GlobalProperty.INITIAL_INFECTION_COUNT, propertyDefinition, 0);\n\n propertyDefinition = PropertyDefinition.builder().setPropertyValueMutability(false)\n .setDefaultValue(VaccinatorType.PARTITION).setType(VaccinatorType.class).build();\n builder.defineGlobalProperty(GlobalProperty.VACCINATOR_TYPE, propertyDefinition, 0);\n\n propertyDefinition = PropertyDefinition.builder().setPropertyValueMutability(false).setDefaultValue(3.0)\n .setType(Double.class).build();\n builder.defineGlobalProperty(GlobalProperty.MINIMUM_INFECTIOUS_PERIOD, propertyDefinition, 0);\n\n propertyDefinition = PropertyDefinition.builder().setPropertyValueMutability(false).setDefaultValue(12.0)\n .setType(Double.class).build();\n builder.defineGlobalProperty(GlobalProperty.MAXIMUM_INFECTIOUS_PERIOD, propertyDefinition, 0);\n\n propertyDefinition = PropertyDefinition.builder().setPropertyValueMutability(false).setDefaultValue(2.0)\n .setType(Double.class).build();\n builder.defineGlobalProperty(GlobalProperty.INFECTIOUS_CONTACT_RATE, propertyDefinition, 0);\n\n propertyDefinition = PropertyDefinition.builder().setPropertyValueMutability(false).setDefaultValue(0.15)\n .setType(Double.class).build();\n builder.defineGlobalProperty(GlobalProperty.TRANSMISSION_PROBABILTY, propertyDefinition, 0);\n\n propertyDefinition = PropertyDefinition.builder().setPropertyValueMutability(false).setDefaultValue(100)\n .setType(Integer.class).build();\n builder.defineGlobalProperty(GlobalProperty.VACCINATIONS_PER_DAY, propertyDefinition, 0);\n\n propertyDefinition = PropertyDefinition.builder().setPropertyValueMutability(false).setDefaultValue(30.0)\n .setType(Double.class).build();\n builder.defineGlobalProperty(GlobalProperty.INTER_VACCINATION_DELAY_TIME, propertyDefinition, 0);\n\n GlobalPropertiesPluginData globalPropertiesPluginData = builder.build();\n return GlobalPropertiesPlugin.builder().setGlobalPropertiesPluginData(globalPropertiesPluginData)\n .getGlobalPropertiesPlugin();\n}\n\n\nThe person properties plugin contains four properties, Code Block 13.3:\n\nAGE – the integer age of a person\nWAITING_FOR_NEXT_DOSE – Boolean indicating that it is too soon to administer another dose of the vaccine\nVACCINATION_COUNT – the number or vaccinations received\nDISEASE_STATE – the state of the disease: SUSCEPTIBLE, INFECTIOUS or RECOVERED\n\n\n\nCode Block 13.3: The four person properties are defined.\nprivate Plugin getPersonPropertiesPlugin() {\n Builder builder = PersonPropertiesPluginData.builder();\n\n PropertyDefinition propertyDefinition = PropertyDefinition.builder()//\n .setPropertyValueMutability(false).setType(Integer.class).build();\n builder.definePersonProperty(PersonProperty.AGE, propertyDefinition, 0.0, false);\n\n propertyDefinition = PropertyDefinition.builder()//\n .setPropertyValueMutability(true).setType(Boolean.class)//\n .setDefaultValue(false).build();\n builder.definePersonProperty(PersonProperty.WAITING_FOR_NEXT_DOSE, propertyDefinition, 0.0, false);\n\n propertyDefinition = PropertyDefinition.builder()//\n .setPropertyValueMutability(true)//\n .setType(DiseaseState.class).setDefaultValue(DiseaseState.SUSCEPTIBLE).build();\n builder.definePersonProperty(PersonProperty.DISEASE_STATE, propertyDefinition, 0.0, false);\n\n propertyDefinition = PropertyDefinition.builder()//\n .setPropertyValueMutability(true)//\n .setType(Integer.class)//\n .setDefaultValue(0)//\n .build();\n builder.definePersonProperty(PersonProperty.VACCINATION_COUNT, propertyDefinition, 0.0, false);\n\n PersonPropertiesPluginData personPropertiesPluginData = builder.build();\n return PersonPropertiesPlugin.builder()//\n .setPersonPropertiesPluginData(personPropertiesPluginData)//\n .getPersonPropertyPlugin();\n}\n\n\nThe regions plugin contains three regions that will be randomly assigned to people. It exists to fulfill a dependency of the person properties plugin. The stochastics and people plugins are similarly minimal.\nThe partitions plugin is initialized in Code Block 13.4. It requires dependencies on those plugins that will be used to calculate partition filters and labelers. This runs counter to intuition since the model plugin (via its actors) is using the partitions plugin. However, the partitions data manager needs to keep the partitions current with the state of people. In the example model, all partitions use person properties, so the person properties plugin must process events before the partitions plugin.\n\n\nCode Block 13.4: The partitions plugin is simple, but requires that it has dependencies on those plugins that will be used to calculate partition filters and labelers.\nprivate Plugin getPartitionsPlugin() {\n PartitionsPluginData partitionsPluginData = PartitionsPluginData.builder()//\n .setRunContinuitySupport(false).build();\n\n return PartitionsPlugin.builder()//\n .setPartitionsPluginData(partitionsPluginData)//\n .addPluginDependency(PersonPropertiesPluginId.PLUGIN_ID)//\n .getPartitionsPlugin();\n}" + }, + { + "objectID": "ch13-PartitionsPlugin.html#experiment-dimensions", + "href": "ch13-PartitionsPlugin.html#experiment-dimensions", + "title": "13  Partitions Plugin", + "section": "13.7 Experiment dimensions", + "text": "13.7 Experiment dimensions\nThere are no experiment dimensions in this example with a single scenario being executed." + }, + { + "objectID": "ch13-PartitionsPlugin.html#the-actors", + "href": "ch13-PartitionsPlugin.html#the-actors", + "title": "13  Partitions Plugin", + "section": "13.8 The Actors", + "text": "13.8 The Actors\n\n13.8.1 Population Loader\nThe population loader adds 10,000 people to the simulation in Code Block 13.5. Each person is assigned a randomly chosen region. All person properties are left at default value except for AGE which is generated randomly.\n\n\nCode Block 13.5: People are added to the simulation with region and age assignments.\npublic class PopulationLoader {\n public void init(ActorContext actorContext) {\n StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);\n Well randomGenerator = stochasticsDataManager.getRandomGenerator();\n PeopleDataManager peopleDataManager = actorContext.getDataManager(PeopleDataManager.class);\n GlobalPropertiesDataManager globalPropertiesDataManager = actorContext\n .getDataManager(GlobalPropertiesDataManager.class);\n Integer populationSize = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.POPULATION_SIZE);\n for (int i = 0; i < populationSize; i++) {\n Region region = Region.getRandomRegion(randomGenerator);\n int age = AgeGroup.getRandomAge(randomGenerator);\n PersonPropertyValueInitialization personPropertyValueInitialization = new PersonPropertyValueInitialization(PersonProperty.AGE,age);\n PersonConstructionData personConstructionData = PersonConstructionData.builder()//\n .add(region)//\n .add(personPropertyValueInitialization)\n .build();//\n peopleDataManager.addPerson(personConstructionData);\n }\n }\n}\n\n\n\n\n13.8.2 Contact Manager\nThe ContactManager actor, Code Block 13.6, schedules infectious contacts between infected people and the susceptible population. On its initialization, it establishes various parameters from the global variables and schedules the initial infections. It uses a simple partition configured in its default state. It has no filter and no labelers, so it will include all people in the simulation. It is used to select random people for potential infectious contacts and is somewhat more efficient than storing a list of all people locally.\n\n\nCode Block 13.6: The Contact Manager uses a simple partition that contains the entire population and uses it to randomly select infectious contacts.\npublic void init(ActorContext actorContext) {\n this.actorContext = actorContext;\n partitionsDataManager = actorContext.getDataManager(PartitionsDataManager.class);\n personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class);\n peopleDataManager = actorContext.getDataManager(PeopleDataManager.class);\n globalPropertiesDataManager = actorContext.getDataManager(GlobalPropertiesDataManager.class);\n StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);\n randomGenerator = stochasticsDataManager.getRandomGenerator();\n\n loadGlobalProperties();\n establishPopulationPartition();\n initializeInfections();\n}\n\nprivate void establishPopulationPartition() {\n partitionsDataManager.addPartition(Partition.builder().build(), partitionKey);\n}\n\n\nInfection of a person, Code Block 13.7, results in the immediate scheduling of infectious contacts. The person is infectious for 3 to 12 days and will have 2 contacts per day. Each contact has a base 15% probability of infecting the contacted person, Code Block 13.8. Susceptible people who have been previously vaccinated have a reduced chance of contracting the disease. Note that the partition is used to select the contacted person and that the infectious person is excluded from contact since a person cannot contact themselves.\n\n\nCode Block 13.7: Each time a person is infected, the contact manager schedules several follow-on infectious contacts.\nprivate void infectPerson(PersonId personId) {\n personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.DISEASE_STATE,\n DiseaseState.INFECTIOUS);\n\n double infectiousPeriod = randomGenerator.nextDouble() * (maximumInfectiousPeriod - minimumInfectiousPeriod)\n + minimumInfectiousPeriod;\n\n double lastContactTime = actorContext.getTime() + infectiousPeriod;\n\n double infectionTime = actorContext.getTime();\n while (true) {\n double contactDelay = (1.0 / infectiousContactRate);\n contactDelay *= (1 + randomGenerator.nextDouble() / 5 - 0.1);\n infectionTime += contactDelay;\n\n if (infectionTime < lastContactTime) {\n actorContext.addPlan((c2) -> {\n processInfectiousContact(personId);\n }, infectionTime);\n } else {\n break;\n }\n }\n actorContext.addPlan((c2) -> {\n endInfectiousness(personId);\n }, lastContactTime);\n\n}\n\n\n\n\nCode Block 13.8: Infectious contacts are subject to mitigation. To be infected, the contacted person must be susceptible and may having varying degrees of protection from previous vaccinations.\nprivate void processInfectiousContact(PersonId personId) {\n \n PartitionSampler partitionSampler = PartitionSampler.builder().setExcludedPerson(personId).build();\n Optional optionalPersonId = partitionsDataManager.samplePartition(partitionKey, partitionSampler);\n if (optionalPersonId.isPresent()) {\n\n PersonId contactedPersonId = optionalPersonId.get();\n \n\n DiseaseState diseaseState = personPropertiesDataManager.getPersonPropertyValue(contactedPersonId,\n PersonProperty.DISEASE_STATE);\n if (diseaseState == DiseaseState.SUSCEPTIBLE) {\n\n int vaccinationCount = personPropertiesDataManager.getPersonPropertyValue(contactedPersonId,\n PersonProperty.VACCINATION_COUNT);\n double mitigatedTransmissionProbability;\n\n switch (vaccinationCount) {\n case 0:\n mitigatedTransmissionProbability = 1;\n break;\n case 1:\n mitigatedTransmissionProbability = 0.5;\n break;\n case 2:\n mitigatedTransmissionProbability = 0.2;\n break;\n default:\n mitigatedTransmissionProbability = 0;\n break;\n }\n\n mitigatedTransmissionProbability *= transmissionProbabilty;\n\n if (randomGenerator.nextDouble() < mitigatedTransmissionProbability) {\n infectPerson(contactedPersonId);\n }\n }\n }\n\n}\n\n\n\n\n13.8.3 Vaccinator Manager\nThis example contains three versions of the vaccinator actor that demonstrate an evolving solution to person selection. The vaccine manager, Code Block 13.9, uses the global property,VACCINATOR_TYPE, to create an instance of the vaccinator actor. The global property is set to the partition vaccinator and the remaining two possibilities are left for reader inspection.\n\n\nCode Block 13.9: The vaccine manager creates one of three vaccinator actors based on the global property,VACCINATOR_TYPE.\npublic class VaccinatorManager {\n public void init(ActorContext actorContext) {\n GlobalPropertiesDataManager globalPropertiesDataManager = actorContext.getDataManager(GlobalPropertiesDataManager.class);\n VaccinatorType vaccinatorType =\n globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.VACCINATOR_TYPE);\n switch (vaccinatorType) {\n case PARTITION:\n actorContext.addActor(new PartitionVaccinator()::init);\n break;\n case EVENT:\n actorContext.addActor(new EventVaccinator()::init);\n break;\n case INSPECTION:\n actorContext.addActor(new InspectionVaccinator()::init);\n break;\n default:\n throw new RuntimeException(\"unhandled case \"+vaccinatorType); \n }\n }\n}\n\n\n\n\n13.8.4 Inspection Vaccinator\nThe inspection vaccinator represents the most obvious approach to finding the next person to vaccinate. It initializes, Code Block 13.10, and immediately starts vaccinating the population.\n\n\nCode Block 13.10: The inspection-based vaccinator establishes its working variables and begins planning the next vaccination.\npublic void init(ActorContext actorContext) {\n this.actorContext = actorContext;\n\n StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);\n randomGenerator = stochasticsDataManager.getRandomGenerator();\n personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class);\n peopleDataManager = actorContext.getDataManager(PeopleDataManager.class);\n globalPropertiesDataManager = actorContext.getDataManager(GlobalPropertiesDataManager.class);\n\n establishWorkingVaribles();\n planNextVaccination();\n}\n\n\nEach vaccine attempt repeats the following steps, Code Block 13.11:\n\nGather the list of all people\nBuild data structures to hold those people in separate groups aligned to age and vaccination status priorities\nLoop through the population, selecting people who:\n\nAre adults\nAre not infectious or recovered\nHave fewer that 3 vaccinations\nAre not the in post-vaccination 30 day waiting period\n\nAssign each selected person to an age/vaccination count category\nNote if there are people who will need vaccination in the future after the waiting period is over\nSelect a category based on the weight, Code Block 13.12, of the category and the number of people who are associated with the category\nSelect a person at random from the selected category\nIf a person was selected or a future vaccination will be needed, choose to continue the vaccination process\n\n\n\nCode Block 13.11: The inspection-based vaccinator vaccinates 100 people per day. Each vaccination attempt considers every person in the simulation.\nprivate void planNextVaccination() {\n actorContext.addPlan(this::vaccinatePerson, interVaccinationTime + actorContext.getTime());\n}\n\n\nprivate void vaccinatePerson(ActorContext actorContext) {\n List people = peopleDataManager.getPeople();\n Map> candidates = new LinkedHashMap<>();\n Map weights = new LinkedHashMap<>();\n\n List eligibleAgeGroups = new ArrayList<>();\n eligibleAgeGroups.add(AgeGroup.ADULT_18_44);\n eligibleAgeGroups.add(AgeGroup.ADULT_45_64);\n eligibleAgeGroups.add(AgeGroup.SENIOR);\n\n List eligibleVaccineCounts = new ArrayList<>();\n eligibleVaccineCounts.add(0);\n eligibleVaccineCounts.add(1);\n eligibleVaccineCounts.add(2);\n\n for (AgeGroup ageGroup : eligibleAgeGroups) {\n for (Integer vaccineCount : eligibleVaccineCounts) {\n MultiKey multiKey = new MultiKey(ageGroup, vaccineCount);\n double weight = getWeight(ageGroup, vaccineCount);\n weights.put(multiKey, weight);\n candidates.put(multiKey, new ArrayList<>());\n }\n }\n\n potentialEligiblePeopleExist = false;\n\n for (PersonId personId : people) {\n int age = personPropertiesDataManager.getPersonPropertyValue(personId, PersonProperty.AGE);\n if (age < 18) {\n continue;\n }\n DiseaseState diseaseState = personPropertiesDataManager.getPersonPropertyValue(personId,\n PersonProperty.DISEASE_STATE);\n if (diseaseState != DiseaseState.SUSCEPTIBLE) {\n continue;\n }\n int vaccinationCount = personPropertiesDataManager.getPersonPropertyValue(personId,\n PersonProperty.VACCINATION_COUNT);\n if (vaccinationCount > 2) {\n continue;\n }\n\n boolean waitingFromPreviousVaccination = personPropertiesDataManager.getPersonPropertyValue(personId,\n PersonProperty.WAITING_FOR_NEXT_DOSE);\n\n if (waitingFromPreviousVaccination) {\n potentialEligiblePeopleExist = true;\n continue;\n }\n\n AgeGroup ageGroup = AgeGroup.getAgeGroup(age);\n MultiKey multiKey = new MultiKey(ageGroup, vaccinationCount);\n candidates.get(multiKey).add(personId);\n }\n\n Map extendedWeights = new LinkedHashMap<>();\n\n double sumOfExtendedWeights = 0;\n for (MultiKey multiKey : weights.keySet()) {\n Double weight = weights.get(multiKey);\n int candidateCount = candidates.get(multiKey).size();\n Double extenedWeight = weight * candidateCount;\n extendedWeights.put(multiKey, extenedWeight);\n sumOfExtendedWeights += extenedWeight;\n }\n\n PersonId selectedCandidate = null;\n\n double selectedWeight = sumOfExtendedWeights * randomGenerator.nextDouble();\n for (MultiKey multiKey : extendedWeights.keySet()) {\n Double extendedWeight = extendedWeights.get(multiKey);\n selectedWeight -= extendedWeight;\n if (selectedWeight <= 0) {\n List seletedCandidates = candidates.get(multiKey);\n if (!seletedCandidates.isEmpty()) {\n int index = randomGenerator.nextInt(seletedCandidates.size());\n selectedCandidate = seletedCandidates.get(index);\n }\n break;\n }\n }\n\n if (selectedCandidate != null) {\n\n int vaccinationCount = personPropertiesDataManager.getPersonPropertyValue(selectedCandidate,\n PersonProperty.VACCINATION_COUNT);\n vaccinationCount++;\n personPropertiesDataManager.setPersonPropertyValue(selectedCandidate, PersonProperty.VACCINATION_COUNT,\n vaccinationCount);\n if (vaccinationCount < 3) {\n personPropertiesDataManager.setPersonPropertyValue(selectedCandidate,\n PersonProperty.WAITING_FOR_NEXT_DOSE, true);\n planWaitTermination(selectedCandidate);\n }\n planNextVaccination();\n } else {\n if (potentialEligiblePeopleExist) {\n planNextVaccination();\n }\n }\n}\n\n\n\n\nCode Block 13.12: The inspection-based vaccinator assigns a probability weight to each person based on their age and number of vaccine doses administered.\nprivate double getWeight(AgeGroup ageGroup, int vaccineCount) {\n\n double result = 1;\n\n switch (ageGroup) {\n case ADULT_18_44:\n result += 0;\n break;\n case ADULT_45_64:\n result += 3;\n break;\n case CHILD:\n result += 0;\n break;\n case SENIOR:\n result += 10;\n break;\n default:\n break;\n }\n\n switch (vaccineCount) {\n case 0:\n result += 4;\n break;\n case 1:\n result += 3;\n break;\n case 2:\n result += 2;\n break;\n default:\n result += 0;\n break;\n }\n\n return result;\n}\n\n\nWhile this process is fairly straight forward, it does involve fairly complex and tricky calculations. Worse yet, it is extremely repetitive and does not scale well to realistic population sizes. It is orders of magnitude slower than the next two approaches.\n\n\n13.8.5 Event Vaccinator\nThe event vaccinator transforms the inspection vaccinator’s approach by retaining the category organizational structures and using the event system to maintain the lists of eligible people. It initializes, Code Block 13.13, by building the sub-populations of eligible people and subscribing to all events that may alter those populations.\n\n\nCode Block 13.13: The event-based vaccinator improves on the inspection-based vaccinator by maintaining the eligible sub-populations.\npublic void init(ActorContext actorContext) {\n this.actorContext = actorContext;\n\n StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);\n randomGenerator = stochasticsDataManager.getRandomGenerator();\n personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class);\n peopleDataManager = actorContext.getDataManager(PeopleDataManager.class);\n globalPropertiesDataManager = actorContext.getDataManager(GlobalPropertiesDataManager.class);\n\n establishWorkingVaribles();\n subscribeToPersonPropertyUpdateEvents();\n intializeCandidatesAndWeights();\n planNextVaccination();\n}\n\n\nThe vaccination process is very similar to the inspection-based method, but there is no recalculation of the sub-populations, Code Block 13.14. Instead, the event-based approach subscribes to all person property updates, Code Block 13.15 , and processes each update by:\n\nRemoving the relevant person from the sub-populations\nFiltering out people who :\n\nAre not susceptible\nAre not adults\nAre waiting from the last dose\nAlready have 3 vaccinations\n\nAdding the person back into the sub-populations. Note the person may have moved from one sub-population to another.\n\n\n\nCode Block 13.14: The event-based vaccinator selects from maintained sub-populations.\nprivate void planNextVaccination() {\n Plan plan = Plan.builder(ActorContext.class)//\n .setTime(interVaccinationTime + actorContext.getTime())//\n .setKey(planId).setCallbackConsumer(this::vaccinatePerson).build();\n\n actorContext.addPlan(plan);\n}\n\nprivate void vaccinatePerson(ActorContext actorContext) {\n\n Map extendedWeights = new LinkedHashMap<>();\n\n double sumOfExtendedWeights = 0;\n for (MultiKey multiKey : weights.keySet()) {\n Double weight = weights.get(multiKey);\n int candidateCount = candidates.get(multiKey).size();\n Double extenedWeight = weight * candidateCount;\n extendedWeights.put(multiKey, extenedWeight);\n sumOfExtendedWeights += extenedWeight;\n }\n\n PersonId selectedCandidate = null;\n\n double selectedWeight = sumOfExtendedWeights * randomGenerator.nextDouble();\n for (MultiKey multiKey : extendedWeights.keySet()) {\n Double extendedWeight = extendedWeights.get(multiKey);\n selectedWeight -= extendedWeight;\n if (selectedWeight <= 0) {\n List seletedCandidates = candidates.get(multiKey);\n if (!seletedCandidates.isEmpty()) {\n int index = randomGenerator.nextInt(seletedCandidates.size());\n selectedCandidate = seletedCandidates.get(index);\n }\n break;\n }\n }\n\n if (selectedCandidate != null) {\n\n int vaccinationCount = personPropertiesDataManager.getPersonPropertyValue(selectedCandidate,\n PersonProperty.VACCINATION_COUNT);\n vaccinationCount++;\n personPropertiesDataManager.setPersonPropertyValue(selectedCandidate, PersonProperty.VACCINATION_COUNT,\n vaccinationCount);\n if (vaccinationCount < 3) {\n personPropertiesDataManager.setPersonPropertyValue(selectedCandidate,\n PersonProperty.WAITING_FOR_NEXT_DOSE, true);\n planWaitTermination(selectedCandidate);\n }\n planNextVaccination();\n }\n\n}\n\n\n\n\nCode Block 13.15: The event-based vaccinator processes each person property update event by first removing the person from the sub-populations and then adding them back in if required.\nprivate void handlePersonPropertyChange(ActorContext actorContext,\n PersonPropertyUpdateEvent personPropertyUpdateEvent) {\n\n PersonId personId = personPropertyUpdateEvent.personId();\n\n // remove the person if they are being tracked\n MultiKey multiKey = groupMap.remove(personId);\n List list = candidates.get(multiKey);\n if (list != null) {\n list.remove(personId);\n }\n\n DiseaseState diseaseState = personPropertiesDataManager.getPersonPropertyValue(personId,\n PersonProperty.DISEASE_STATE);\n\n // the person must be susceptible\n if (diseaseState != DiseaseState.SUSCEPTIBLE) {\n return;\n }\n\n Integer vaccinationCount = personPropertiesDataManager.getPersonPropertyValue(personId,\n PersonProperty.VACCINATION_COUNT);\n\n if (vaccinationCount > 2) {\n return;\n }\n\n int age = personPropertiesDataManager.getPersonPropertyValue(personId, PersonProperty.AGE);\n AgeGroup ageGroup = AgeGroup.getAgeGroup(age);\n if (ageGroup == AgeGroup.CHILD) {\n return;\n }\n boolean waitingForNextDose = personPropertiesDataManager.getPersonPropertyValue(personId,\n PersonProperty.WAITING_FOR_NEXT_DOSE);\n if (waitingForNextDose) {\n return;\n }\n\n multiKey = new MultiKey(ageGroup, vaccinationCount);\n\n list = candidates.get(multiKey);\n if (list != null) {\n list.add(personId);\n groupMap.put(personId, multiKey);\n }\n\n}\n\n\nSome care must be given to properly terminating the vaccination process. When a vaccination attempt is made and there is no eligible candidate, no new attempt is scheduled. Instead, the vaccinator relies on knowing that a new candidate will appear only when a person has ended their post-vaccination waiting period. To accomplish this, Code Block 13.16, the vaccinator uses a plan id for vaccination plans. When it processes the end of the waiting period for a person it looks to the simulation and determines whether there is a future plan to vaccinate. If no such plan exists, it immediately vaccinates the person and re-starts the vaccination process.\n\n\nCode Block 13.16: The event-vaccinator can restart the vaccination process when a person becomes eligible after the post-vacination waiting period is over.\nprivate void endWaitTime(PersonId personId) {\n personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.WAITING_FOR_NEXT_DOSE, false);\n if (actorContext.getPlan(planId).isEmpty()) {\n vaccinatePerson(actorContext);\n }\n}\n\n\nThe performance of the code is multiple orders of magnitude better than the inspection approach. However, there are a few drawbacks:\n\nThe code is more complex and is more difficult to get right. Edge cases abound.\nIt still relies on list/map/set based data structures which can be slow and use too much memory.\nIf the selection criteria were to become more complex or if the actor needs to select people from subsets of the eligible people for special purposes as occurs in more realistic use cases, a great deal of effort would have to expended to ensure correctly functioning code.\n\n\n\n13.8.6 Partition Vaccinator\nThe partition-vaccinator improves on the event-vaccinator by using partitions. Internally, the partitions are performing similar event-triggered updates of the sub-populations. However, their approach is more sophisticated:\n\nSupports complex filtering\nAllows for categorization of data via labels\nSupports multi-dimensional labeling of the filtered population\nSupports nuanced sampling of the population that can be aligned to label based subsets\n\nThe vaccinator initializes, Code Block 13.17, by creating partitions that will manage the data structures and subscribe to the relevant events. It then proceeds with vaccination planning as in the previous versions.\n\n\nCode Block 13.17: The partition-vaccinator manages the eligible population via partitions.\npublic void init(ActorContext actorContext) {\n this.actorContext = actorContext;\n personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class);\n partitionsDataManager = actorContext.getDataManager(PartitionsDataManager.class);\n globalPropertiesDataManager = actorContext.getDataManager(GlobalPropertiesDataManager.class);\n establishWorkingVaribles();\n createPartitions();\n planNextVaccination();\n}\n\n\nThe vaccinator uses two partitions to manage vaccination, Code Block 13.18. The first partition is used to select currently eligible people and the second covers people who may become eligible in the future and is used to determine if vaccination of the population is complete.\n\n\nCode Block 13.18: The partition-vaccinator creates two partitions to help with person selection and termination of vaccinations.\nprivate void createPartitions() {\n\n PersonPropertyFilter ageFilter = new PersonPropertyFilter(PersonProperty.AGE, Equality.GREATER_THAN_EQUAL, 18);\n\n PersonPropertyFilter diseaseFilter = new PersonPropertyFilter(PersonProperty.DISEASE_STATE, Equality.EQUAL,\n DiseaseState.SUSCEPTIBLE);\n\n PersonPropertyFilter vaccineFilter = new PersonPropertyFilter(PersonProperty.VACCINATION_COUNT,\n Equality.LESS_THAN, 3);\n\n PersonPropertyFilter waitFilter = new PersonPropertyFilter(PersonProperty.WAITING_FOR_NEXT_DOSE, Equality.EQUAL,\n false);\n\n Filter filter = ageFilter.and(diseaseFilter).and(vaccineFilter).and(waitFilter);\n\n Labeler ageLabeler = new FunctionalPersonPropertyLabeler(PersonProperty.AGE,\n (value) -> AgeGroup.getAgeGroup((Integer) value));\n\n Labeler vaccineCountLabeler = new FunctionalPersonPropertyLabeler(PersonProperty.VACCINATION_COUNT,\n (value) -> value);\n\n Partition partition = Partition.builder()//\n .setFilter(filter)//\n .addLabeler(ageLabeler)//\n .addLabeler(vaccineCountLabeler)//\n .build();\n\n partitionsDataManager.addPartition(partition, currentlyEligibleKey);\n\n filter = ageFilter.and(diseaseFilter).and(vaccineFilter);\n partition = Partition.builder()//\n .setFilter(filter)//\n .build();\n\n partitionsDataManager.addPartition(partition, potentiallyEligibleKey);\n}\n\n\nThe first step in this process is to create the filter that will pass only eligible people. Filters are provided as extensions of the Filter.java class in the partitions plugin. The plugin contains base filter implementations for the logical operators of AND, OR, NOT, TRUE, and FALSE. Nearly all plugins provide more refined filters via the plugin’s support package. In the example code, we create four filters that select:\n\nAdults\nPeople who are susceptible\nPeople who have received fewer that 3 vaccinations\nPeople who have not been recently vaccinated\n\nAll these filters are based on person properties, so we use the PersonPropertyFilter class provided by the person properties plugin. We combine the filters using the AND operator native to all filters.\nWe need to select people not only on their membership in the partition, but also on their personal properties, preferring older people and those who have had fewer vaccinations. Thus we will be using weighted selection and will use labelers assign weight to the cells in the partition. The nine cells are formed from the combinations of :\n\nADULT_18_44, ADULT_45_64, SENIOR\nVACCINATION_COUNT = 0, 1, 2\n\nThe two labelers will use the FunctionalPersonPropertyLabeler class to specify the transformation of values into labels. Note that the age labeler is converting an integer age value into an AgeGroup while the vaccination count labeler is simply returning the vaccination count. The resulting partition is formed from the filter and the two labelers and is added to the partitions data manager under the ‘currentlyEligibleKey’ key value. This partition will be used to select a new person to vaccinate each time a vaccination comes due.\nThe next partition uses a reduced filter that allows for people who have been recently vaccinated but are not fully vaccinated. It does not require any labelers since we will use this partition only to determine if vaccinations should continue to be scheduled.\nThe vaccination process, Code Block 13.19, greatly simplifies the previous designs. The decision to continue vaccination is based on the potentially eligible population containing at least one person. Selection of a person from the currently eligible population uses a PartitionSampler, which specifies how the partition is to perform the sample. In this case we are only providing the weighting function that assigns a weighting value to each of the nine categories. Other capabilities of the PartitionSampler include:\n\nExcluding a particular person – useful for contact management\nLimiting sampling to a constrained portion of the cells that compose the partition\nSelecting a specific random number generator for the sampling process\n\nThe resulting code is easier to refactor, less error prone and executes much faster with far less memory than the previous implementations. Cells in the partition are dynamically allocated to multiple implementations that can approach 1.3 bits per person in the population while maintaining O(ln(n) ) performance for sampling.\n\n\nCode Block 13.19: The partition-vaccinator schedules and executes vaccinations using partitions.\nprivate void planNextVaccination() {\n if (partitionsDataManager.getPersonCount(potentiallyEligibleKey) == 0) {\n return;\n }\n actorContext.addPlan(this::vaccinatePerson, vaccinatorDelay + actorContext.getTime());\n}\n\nprivate void vaccinatePerson(ActorContext actorContext) {\n\n PartitionSampler partitionSampler = PartitionSampler.builder()//\n .setLabelSetWeightingFunction(this::getWeight)//\n .build();\n\n Optional optionalPersonId = partitionsDataManager.samplePartition(currentlyEligibleKey,\n partitionSampler);\n if (optionalPersonId.isPresent()) {\n PersonId personId = optionalPersonId.get();\n int vaccinationCount = personPropertiesDataManager.getPersonPropertyValue(personId,\n PersonProperty.VACCINATION_COUNT);\n vaccinationCount++;\n personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.VACCINATION_COUNT,\n vaccinationCount);\n if (vaccinationCount < 3) {\n personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.WAITING_FOR_NEXT_DOSE,\n true);\n planWaitTermination(personId);\n }\n\n }\n planNextVaccination();\n}" + }, + { + "objectID": "ch13-PartitionsPlugin.html#inspecting-the-output", + "href": "ch13-PartitionsPlugin.html#inspecting-the-output", + "title": "13  Partitions Plugin", + "section": "13.9 Inspecting the output", + "text": "13.9 Inspecting the output\n\n13.9.1 Disease State Report\nThe disease state report, Figure 13.1, records the number of people having various vaccine counts, disease state and age grouping at the end of the simulation. The results show that the vaccination rate is not sufficient to prevent the majority of infections, but does reflect vaccination eligibility and prioritization rules. The results here are for the partition vaccinator, but the results for the other vaccinators are similar. Since each implementation has subtle ordering differences when choosing people to vaccinate, the results vary by amounts that would correspond to changes in the stochastic seed value.\n\n\n\nFigure 13.1: The experiment's single scenario showing expected vaccinations for different age groups.\n\n\n\n\n\n\nscenario\n\n\nage_group\n\n\ndisease_state\n\n\nvaccinations\n\n\npeople\n\n\n\n\n\n\n0\n\n\nCHILD\n\n\nRECOVERED\n\n\n0\n\n\n1599\n\n\n\n\n0\n\n\nSENIOR\n\n\nRECOVERED\n\n\n0\n\n\n377\n\n\n\n\n0\n\n\nADULT_18_44\n\n\nRECOVERED\n\n\n0\n\n\n1745\n\n\n\n\n0\n\n\nADULT_45_64\n\n\nRECOVERED\n\n\n0\n\n\n1018\n\n\n\n\n0\n\n\nADULT_18_44\n\n\nRECOVERED\n\n\n1\n\n\n591\n\n\n\n\n0\n\n\nADULT_18_44\n\n\nSUSCEPTIBLE\n\n\n3\n\n\n1253\n\n\n\n\n0\n\n\nSENIOR\n\n\nSUSCEPTIBLE\n\n\n3\n\n\n742\n\n\n\n\n0\n\n\nADULT_45_64\n\n\nSUSCEPTIBLE\n\n\n3\n\n\n1026\n\n\n\n\n0\n\n\nCHILD\n\n\nSUSCEPTIBLE\n\n\n0\n\n\n553\n\n\n\n\n0\n\n\nSENIOR\n\n\nRECOVERED\n\n\n1\n\n\n484\n\n\n\n\n0\n\n\nADULT_45_64\n\n\nRECOVERED\n\n\n1\n\n\n568\n\n\n\n\n0\n\n\nADULT_45_64\n\n\nRECOVERED\n\n\n2\n\n\n19\n\n\n\n\n0\n\n\nADULT_18_44\n\n\nRECOVERED\n\n\n2\n\n\n10\n\n\n\n\n0\n\n\nSENIOR\n\n\nRECOVERED\n\n\n2\n\n\n15" + } +] \ No newline at end of file diff --git a/doc/site_libs/bootstrap/bootstrap-icons.css b/doc/site_libs/bootstrap/bootstrap-icons.css new file mode 100644 index 000000000..f51d04bc9 --- /dev/null +++ b/doc/site_libs/bootstrap/bootstrap-icons.css @@ -0,0 +1,1704 @@ +@font-face { + font-family: "bootstrap-icons"; + src: +url("./bootstrap-icons.woff?524846017b983fc8ded9325d94ed40f3") format("woff"); +} + +.bi::before, +[class^="bi-"]::before, +[class*=" bi-"]::before { + display: inline-block; + font-family: bootstrap-icons !important; + font-style: normal; + font-weight: normal !important; + font-variant: normal; + text-transform: none; + line-height: 1; + vertical-align: -.125em; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.bi-123::before { content: "\f67f"; } +.bi-alarm-fill::before { content: "\f101"; } +.bi-alarm::before { content: "\f102"; } +.bi-align-bottom::before { content: "\f103"; } +.bi-align-center::before { content: "\f104"; } +.bi-align-end::before { content: "\f105"; } +.bi-align-middle::before { content: "\f106"; } +.bi-align-start::before { content: "\f107"; } +.bi-align-top::before { content: "\f108"; } +.bi-alt::before { content: "\f109"; } +.bi-app-indicator::before { content: "\f10a"; } +.bi-app::before { content: "\f10b"; } +.bi-archive-fill::before { content: "\f10c"; } +.bi-archive::before { content: "\f10d"; } +.bi-arrow-90deg-down::before { content: "\f10e"; } +.bi-arrow-90deg-left::before { content: "\f10f"; } +.bi-arrow-90deg-right::before { content: "\f110"; } +.bi-arrow-90deg-up::before { content: "\f111"; } +.bi-arrow-bar-down::before { content: "\f112"; } +.bi-arrow-bar-left::before { content: "\f113"; } +.bi-arrow-bar-right::before { content: "\f114"; } +.bi-arrow-bar-up::before { content: "\f115"; } +.bi-arrow-clockwise::before { content: "\f116"; } +.bi-arrow-counterclockwise::before { content: "\f117"; } +.bi-arrow-down-circle-fill::before { content: "\f118"; } +.bi-arrow-down-circle::before { content: "\f119"; } +.bi-arrow-down-left-circle-fill::before { content: "\f11a"; } +.bi-arrow-down-left-circle::before { content: "\f11b"; } +.bi-arrow-down-left-square-fill::before { content: "\f11c"; } +.bi-arrow-down-left-square::before { content: "\f11d"; } +.bi-arrow-down-left::before { content: "\f11e"; } +.bi-arrow-down-right-circle-fill::before { content: "\f11f"; } +.bi-arrow-down-right-circle::before { content: "\f120"; } +.bi-arrow-down-right-square-fill::before { content: "\f121"; } +.bi-arrow-down-right-square::before { content: "\f122"; } +.bi-arrow-down-right::before { content: "\f123"; } +.bi-arrow-down-short::before { content: "\f124"; } +.bi-arrow-down-square-fill::before { content: "\f125"; } +.bi-arrow-down-square::before { content: "\f126"; } +.bi-arrow-down-up::before { content: "\f127"; } +.bi-arrow-down::before { content: "\f128"; } +.bi-arrow-left-circle-fill::before { content: "\f129"; } +.bi-arrow-left-circle::before { content: "\f12a"; } +.bi-arrow-left-right::before { content: "\f12b"; } +.bi-arrow-left-short::before { content: "\f12c"; } +.bi-arrow-left-square-fill::before { content: "\f12d"; } +.bi-arrow-left-square::before { content: "\f12e"; } +.bi-arrow-left::before { content: "\f12f"; } +.bi-arrow-repeat::before { content: "\f130"; } +.bi-arrow-return-left::before { content: "\f131"; } +.bi-arrow-return-right::before { content: "\f132"; } +.bi-arrow-right-circle-fill::before { content: "\f133"; } +.bi-arrow-right-circle::before { content: "\f134"; } +.bi-arrow-right-short::before { content: "\f135"; } +.bi-arrow-right-square-fill::before { content: "\f136"; } +.bi-arrow-right-square::before { content: "\f137"; } +.bi-arrow-right::before { content: "\f138"; } +.bi-arrow-up-circle-fill::before { content: "\f139"; } +.bi-arrow-up-circle::before { content: "\f13a"; } +.bi-arrow-up-left-circle-fill::before { content: "\f13b"; } +.bi-arrow-up-left-circle::before { content: "\f13c"; } +.bi-arrow-up-left-square-fill::before { content: "\f13d"; } +.bi-arrow-up-left-square::before { content: "\f13e"; } +.bi-arrow-up-left::before { content: "\f13f"; } +.bi-arrow-up-right-circle-fill::before { content: "\f140"; } +.bi-arrow-up-right-circle::before { content: "\f141"; } +.bi-arrow-up-right-square-fill::before { content: "\f142"; } +.bi-arrow-up-right-square::before { content: "\f143"; } +.bi-arrow-up-right::before { content: "\f144"; } +.bi-arrow-up-short::before { content: "\f145"; } +.bi-arrow-up-square-fill::before { content: "\f146"; } +.bi-arrow-up-square::before { content: "\f147"; } +.bi-arrow-up::before { content: "\f148"; } +.bi-arrows-angle-contract::before { content: "\f149"; } +.bi-arrows-angle-expand::before { content: "\f14a"; } +.bi-arrows-collapse::before { content: "\f14b"; } +.bi-arrows-expand::before { content: "\f14c"; } +.bi-arrows-fullscreen::before { content: "\f14d"; } +.bi-arrows-move::before { content: "\f14e"; } +.bi-aspect-ratio-fill::before { content: "\f14f"; } +.bi-aspect-ratio::before { content: "\f150"; } +.bi-asterisk::before { content: "\f151"; } +.bi-at::before { content: "\f152"; } +.bi-award-fill::before { content: "\f153"; } +.bi-award::before { content: "\f154"; } +.bi-back::before { content: "\f155"; } +.bi-backspace-fill::before { content: "\f156"; } +.bi-backspace-reverse-fill::before { content: "\f157"; } +.bi-backspace-reverse::before { content: "\f158"; } +.bi-backspace::before { content: "\f159"; } +.bi-badge-3d-fill::before { content: "\f15a"; } +.bi-badge-3d::before { content: "\f15b"; } +.bi-badge-4k-fill::before { content: "\f15c"; } +.bi-badge-4k::before { content: "\f15d"; } +.bi-badge-8k-fill::before { content: "\f15e"; } +.bi-badge-8k::before { content: "\f15f"; } +.bi-badge-ad-fill::before { content: "\f160"; } +.bi-badge-ad::before { content: "\f161"; } +.bi-badge-ar-fill::before { content: "\f162"; } +.bi-badge-ar::before { content: "\f163"; } +.bi-badge-cc-fill::before { content: "\f164"; } +.bi-badge-cc::before { content: "\f165"; } +.bi-badge-hd-fill::before { content: "\f166"; } +.bi-badge-hd::before { content: "\f167"; } +.bi-badge-tm-fill::before { content: "\f168"; } +.bi-badge-tm::before { content: "\f169"; } +.bi-badge-vo-fill::before { content: "\f16a"; } +.bi-badge-vo::before { content: "\f16b"; } +.bi-badge-vr-fill::before { content: "\f16c"; } +.bi-badge-vr::before { content: "\f16d"; } +.bi-badge-wc-fill::before { content: "\f16e"; } +.bi-badge-wc::before { content: "\f16f"; } +.bi-bag-check-fill::before { content: "\f170"; } +.bi-bag-check::before { content: "\f171"; } +.bi-bag-dash-fill::before { content: "\f172"; } +.bi-bag-dash::before { content: "\f173"; } +.bi-bag-fill::before { content: "\f174"; } +.bi-bag-plus-fill::before { content: "\f175"; } +.bi-bag-plus::before { content: "\f176"; } +.bi-bag-x-fill::before { content: "\f177"; } +.bi-bag-x::before { content: "\f178"; } +.bi-bag::before { content: "\f179"; } +.bi-bar-chart-fill::before { content: "\f17a"; } +.bi-bar-chart-line-fill::before { content: "\f17b"; } +.bi-bar-chart-line::before { content: "\f17c"; } +.bi-bar-chart-steps::before { content: "\f17d"; } +.bi-bar-chart::before { content: "\f17e"; } +.bi-basket-fill::before { content: "\f17f"; } +.bi-basket::before { content: "\f180"; } +.bi-basket2-fill::before { content: "\f181"; } +.bi-basket2::before { content: "\f182"; } +.bi-basket3-fill::before { content: "\f183"; } +.bi-basket3::before { content: "\f184"; } +.bi-battery-charging::before { content: "\f185"; } +.bi-battery-full::before { content: "\f186"; } +.bi-battery-half::before { content: "\f187"; } +.bi-battery::before { content: "\f188"; } +.bi-bell-fill::before { content: "\f189"; } +.bi-bell::before { content: "\f18a"; } +.bi-bezier::before { content: "\f18b"; } +.bi-bezier2::before { content: "\f18c"; } +.bi-bicycle::before { content: "\f18d"; } +.bi-binoculars-fill::before { content: "\f18e"; } +.bi-binoculars::before { content: "\f18f"; } +.bi-blockquote-left::before { content: "\f190"; } +.bi-blockquote-right::before { content: "\f191"; } +.bi-book-fill::before { content: "\f192"; } +.bi-book-half::before { content: "\f193"; } +.bi-book::before { content: "\f194"; } +.bi-bookmark-check-fill::before { content: "\f195"; } +.bi-bookmark-check::before { content: "\f196"; } +.bi-bookmark-dash-fill::before { content: "\f197"; } +.bi-bookmark-dash::before { content: "\f198"; } +.bi-bookmark-fill::before { content: "\f199"; } +.bi-bookmark-heart-fill::before { content: "\f19a"; } +.bi-bookmark-heart::before { content: "\f19b"; } +.bi-bookmark-plus-fill::before { content: "\f19c"; } +.bi-bookmark-plus::before { content: "\f19d"; } +.bi-bookmark-star-fill::before { content: "\f19e"; } +.bi-bookmark-star::before { content: "\f19f"; } +.bi-bookmark-x-fill::before { content: "\f1a0"; } +.bi-bookmark-x::before { content: "\f1a1"; } +.bi-bookmark::before { content: "\f1a2"; } +.bi-bookmarks-fill::before { content: "\f1a3"; } +.bi-bookmarks::before { content: "\f1a4"; } +.bi-bookshelf::before { content: "\f1a5"; } +.bi-bootstrap-fill::before { content: "\f1a6"; } +.bi-bootstrap-reboot::before { content: "\f1a7"; } +.bi-bootstrap::before { content: "\f1a8"; } +.bi-border-all::before { content: "\f1a9"; } +.bi-border-bottom::before { content: "\f1aa"; } +.bi-border-center::before { content: "\f1ab"; } +.bi-border-inner::before { content: "\f1ac"; } +.bi-border-left::before { content: "\f1ad"; } +.bi-border-middle::before { content: "\f1ae"; } +.bi-border-outer::before { content: "\f1af"; } +.bi-border-right::before { content: "\f1b0"; } +.bi-border-style::before { content: "\f1b1"; } +.bi-border-top::before { content: "\f1b2"; } +.bi-border-width::before { content: "\f1b3"; } +.bi-border::before { content: "\f1b4"; } +.bi-bounding-box-circles::before { content: "\f1b5"; } +.bi-bounding-box::before { content: "\f1b6"; } +.bi-box-arrow-down-left::before { content: "\f1b7"; } +.bi-box-arrow-down-right::before { content: "\f1b8"; } +.bi-box-arrow-down::before { content: "\f1b9"; } +.bi-box-arrow-in-down-left::before { content: "\f1ba"; } +.bi-box-arrow-in-down-right::before { content: "\f1bb"; } +.bi-box-arrow-in-down::before { content: "\f1bc"; } +.bi-box-arrow-in-left::before { content: "\f1bd"; } +.bi-box-arrow-in-right::before { content: "\f1be"; } +.bi-box-arrow-in-up-left::before { content: "\f1bf"; } +.bi-box-arrow-in-up-right::before { content: "\f1c0"; } +.bi-box-arrow-in-up::before { content: "\f1c1"; } +.bi-box-arrow-left::before { content: "\f1c2"; } +.bi-box-arrow-right::before { content: "\f1c3"; } +.bi-box-arrow-up-left::before { content: "\f1c4"; } +.bi-box-arrow-up-right::before { content: "\f1c5"; } +.bi-box-arrow-up::before { content: "\f1c6"; } +.bi-box-seam::before { content: "\f1c7"; } +.bi-box::before { content: "\f1c8"; } +.bi-braces::before { content: "\f1c9"; } +.bi-bricks::before { content: "\f1ca"; } +.bi-briefcase-fill::before { content: "\f1cb"; } +.bi-briefcase::before { content: "\f1cc"; } +.bi-brightness-alt-high-fill::before { content: "\f1cd"; } +.bi-brightness-alt-high::before { content: "\f1ce"; } +.bi-brightness-alt-low-fill::before { content: "\f1cf"; } +.bi-brightness-alt-low::before { content: "\f1d0"; } +.bi-brightness-high-fill::before { content: "\f1d1"; } +.bi-brightness-high::before { content: "\f1d2"; } +.bi-brightness-low-fill::before { content: "\f1d3"; } +.bi-brightness-low::before { content: "\f1d4"; } +.bi-broadcast-pin::before { content: "\f1d5"; } +.bi-broadcast::before { content: "\f1d6"; } +.bi-brush-fill::before { content: "\f1d7"; } +.bi-brush::before { content: "\f1d8"; } +.bi-bucket-fill::before { content: "\f1d9"; } +.bi-bucket::before { content: "\f1da"; } +.bi-bug-fill::before { content: "\f1db"; } +.bi-bug::before { content: "\f1dc"; } +.bi-building::before { content: "\f1dd"; } +.bi-bullseye::before { content: "\f1de"; } +.bi-calculator-fill::before { content: "\f1df"; } +.bi-calculator::before { content: "\f1e0"; } +.bi-calendar-check-fill::before { content: "\f1e1"; } +.bi-calendar-check::before { content: "\f1e2"; } +.bi-calendar-date-fill::before { content: "\f1e3"; } +.bi-calendar-date::before { content: "\f1e4"; } +.bi-calendar-day-fill::before { content: "\f1e5"; } +.bi-calendar-day::before { content: "\f1e6"; } +.bi-calendar-event-fill::before { content: "\f1e7"; } +.bi-calendar-event::before { content: "\f1e8"; } +.bi-calendar-fill::before { content: "\f1e9"; } +.bi-calendar-minus-fill::before { content: "\f1ea"; } +.bi-calendar-minus::before { content: "\f1eb"; } +.bi-calendar-month-fill::before { content: "\f1ec"; } +.bi-calendar-month::before { content: "\f1ed"; } +.bi-calendar-plus-fill::before { content: "\f1ee"; } +.bi-calendar-plus::before { content: "\f1ef"; } +.bi-calendar-range-fill::before { content: "\f1f0"; } +.bi-calendar-range::before { content: "\f1f1"; } +.bi-calendar-week-fill::before { content: "\f1f2"; } +.bi-calendar-week::before { content: "\f1f3"; } +.bi-calendar-x-fill::before { content: "\f1f4"; } +.bi-calendar-x::before { content: "\f1f5"; } +.bi-calendar::before { content: "\f1f6"; } +.bi-calendar2-check-fill::before { content: "\f1f7"; } +.bi-calendar2-check::before { content: "\f1f8"; } +.bi-calendar2-date-fill::before { content: "\f1f9"; } +.bi-calendar2-date::before { content: "\f1fa"; } +.bi-calendar2-day-fill::before { content: "\f1fb"; } +.bi-calendar2-day::before { content: "\f1fc"; } +.bi-calendar2-event-fill::before { content: "\f1fd"; } +.bi-calendar2-event::before { content: "\f1fe"; } +.bi-calendar2-fill::before { content: "\f1ff"; } +.bi-calendar2-minus-fill::before { content: "\f200"; } +.bi-calendar2-minus::before { content: "\f201"; } +.bi-calendar2-month-fill::before { content: "\f202"; } +.bi-calendar2-month::before { content: "\f203"; } +.bi-calendar2-plus-fill::before { content: "\f204"; } +.bi-calendar2-plus::before { content: "\f205"; } +.bi-calendar2-range-fill::before { content: "\f206"; } +.bi-calendar2-range::before { content: "\f207"; } +.bi-calendar2-week-fill::before { content: "\f208"; } +.bi-calendar2-week::before { content: "\f209"; } +.bi-calendar2-x-fill::before { content: "\f20a"; } +.bi-calendar2-x::before { content: "\f20b"; } +.bi-calendar2::before { content: "\f20c"; } +.bi-calendar3-event-fill::before { content: "\f20d"; } +.bi-calendar3-event::before { content: "\f20e"; } +.bi-calendar3-fill::before { content: "\f20f"; } +.bi-calendar3-range-fill::before { content: "\f210"; } +.bi-calendar3-range::before { content: "\f211"; } +.bi-calendar3-week-fill::before { content: "\f212"; } +.bi-calendar3-week::before { content: "\f213"; } +.bi-calendar3::before { content: "\f214"; } +.bi-calendar4-event::before { content: "\f215"; } +.bi-calendar4-range::before { content: "\f216"; } +.bi-calendar4-week::before { content: "\f217"; } +.bi-calendar4::before { content: "\f218"; } +.bi-camera-fill::before { content: "\f219"; } +.bi-camera-reels-fill::before { content: "\f21a"; } +.bi-camera-reels::before { content: "\f21b"; } +.bi-camera-video-fill::before { content: "\f21c"; } +.bi-camera-video-off-fill::before { content: "\f21d"; } +.bi-camera-video-off::before { content: "\f21e"; } +.bi-camera-video::before { content: "\f21f"; } +.bi-camera::before { content: "\f220"; } +.bi-camera2::before { content: "\f221"; } +.bi-capslock-fill::before { content: "\f222"; } +.bi-capslock::before { content: "\f223"; } +.bi-card-checklist::before { content: "\f224"; } +.bi-card-heading::before { content: "\f225"; } +.bi-card-image::before { content: "\f226"; } +.bi-card-list::before { content: "\f227"; } +.bi-card-text::before { content: "\f228"; } +.bi-caret-down-fill::before { content: "\f229"; } +.bi-caret-down-square-fill::before { content: "\f22a"; } +.bi-caret-down-square::before { content: "\f22b"; } +.bi-caret-down::before { content: "\f22c"; } +.bi-caret-left-fill::before { content: "\f22d"; } +.bi-caret-left-square-fill::before { content: "\f22e"; } +.bi-caret-left-square::before { content: "\f22f"; } +.bi-caret-left::before { content: "\f230"; } +.bi-caret-right-fill::before { content: "\f231"; } +.bi-caret-right-square-fill::before { content: "\f232"; } +.bi-caret-right-square::before { content: "\f233"; } +.bi-caret-right::before { content: "\f234"; } +.bi-caret-up-fill::before { content: "\f235"; } +.bi-caret-up-square-fill::before { content: "\f236"; } +.bi-caret-up-square::before { content: "\f237"; } +.bi-caret-up::before { content: "\f238"; } +.bi-cart-check-fill::before { content: "\f239"; } +.bi-cart-check::before { content: "\f23a"; } +.bi-cart-dash-fill::before { content: "\f23b"; } +.bi-cart-dash::before { content: "\f23c"; } +.bi-cart-fill::before { content: "\f23d"; } +.bi-cart-plus-fill::before { content: "\f23e"; } +.bi-cart-plus::before { content: "\f23f"; } +.bi-cart-x-fill::before { content: "\f240"; } +.bi-cart-x::before { content: "\f241"; } +.bi-cart::before { content: "\f242"; } +.bi-cart2::before { content: "\f243"; } +.bi-cart3::before { content: "\f244"; } +.bi-cart4::before { content: "\f245"; } +.bi-cash-stack::before { content: "\f246"; } +.bi-cash::before { content: "\f247"; } +.bi-cast::before { content: "\f248"; } +.bi-chat-dots-fill::before { content: "\f249"; } +.bi-chat-dots::before { content: "\f24a"; } +.bi-chat-fill::before { content: "\f24b"; } +.bi-chat-left-dots-fill::before { content: "\f24c"; } +.bi-chat-left-dots::before { content: "\f24d"; } +.bi-chat-left-fill::before { content: "\f24e"; } +.bi-chat-left-quote-fill::before { content: "\f24f"; } +.bi-chat-left-quote::before { content: "\f250"; } +.bi-chat-left-text-fill::before { content: "\f251"; } +.bi-chat-left-text::before { content: "\f252"; } +.bi-chat-left::before { content: "\f253"; } +.bi-chat-quote-fill::before { content: "\f254"; } +.bi-chat-quote::before { content: "\f255"; } +.bi-chat-right-dots-fill::before { content: "\f256"; } +.bi-chat-right-dots::before { content: "\f257"; } +.bi-chat-right-fill::before { content: "\f258"; } +.bi-chat-right-quote-fill::before { content: "\f259"; } +.bi-chat-right-quote::before { content: "\f25a"; } +.bi-chat-right-text-fill::before { content: "\f25b"; } +.bi-chat-right-text::before { content: "\f25c"; } +.bi-chat-right::before { content: "\f25d"; } +.bi-chat-square-dots-fill::before { content: "\f25e"; } +.bi-chat-square-dots::before { content: "\f25f"; } +.bi-chat-square-fill::before { content: "\f260"; } +.bi-chat-square-quote-fill::before { content: "\f261"; } +.bi-chat-square-quote::before { content: "\f262"; } +.bi-chat-square-text-fill::before { content: "\f263"; } +.bi-chat-square-text::before { content: "\f264"; } +.bi-chat-square::before { content: "\f265"; } +.bi-chat-text-fill::before { content: "\f266"; } +.bi-chat-text::before { content: "\f267"; } +.bi-chat::before { content: "\f268"; } +.bi-check-all::before { content: "\f269"; } +.bi-check-circle-fill::before { content: "\f26a"; } +.bi-check-circle::before { content: "\f26b"; } +.bi-check-square-fill::before { content: "\f26c"; } +.bi-check-square::before { content: "\f26d"; } +.bi-check::before { content: "\f26e"; } +.bi-check2-all::before { content: "\f26f"; } +.bi-check2-circle::before { content: "\f270"; } +.bi-check2-square::before { content: "\f271"; } +.bi-check2::before { content: "\f272"; } +.bi-chevron-bar-contract::before { content: "\f273"; } +.bi-chevron-bar-down::before { content: "\f274"; } +.bi-chevron-bar-expand::before { content: "\f275"; } +.bi-chevron-bar-left::before { content: "\f276"; } +.bi-chevron-bar-right::before { content: "\f277"; } +.bi-chevron-bar-up::before { content: "\f278"; } +.bi-chevron-compact-down::before { content: "\f279"; } +.bi-chevron-compact-left::before { content: "\f27a"; } +.bi-chevron-compact-right::before { content: "\f27b"; } +.bi-chevron-compact-up::before { content: "\f27c"; } +.bi-chevron-contract::before { content: "\f27d"; } +.bi-chevron-double-down::before { content: "\f27e"; } +.bi-chevron-double-left::before { content: "\f27f"; } +.bi-chevron-double-right::before { content: "\f280"; } +.bi-chevron-double-up::before { content: "\f281"; } +.bi-chevron-down::before { content: "\f282"; } +.bi-chevron-expand::before { content: "\f283"; } +.bi-chevron-left::before { content: "\f284"; } +.bi-chevron-right::before { content: "\f285"; } +.bi-chevron-up::before { content: "\f286"; } +.bi-circle-fill::before { content: "\f287"; } +.bi-circle-half::before { content: "\f288"; } +.bi-circle-square::before { content: "\f289"; } +.bi-circle::before { content: "\f28a"; } +.bi-clipboard-check::before { content: "\f28b"; } +.bi-clipboard-data::before { content: "\f28c"; } +.bi-clipboard-minus::before { content: "\f28d"; } +.bi-clipboard-plus::before { content: "\f28e"; } +.bi-clipboard-x::before { content: "\f28f"; } +.bi-clipboard::before { content: "\f290"; } +.bi-clock-fill::before { content: "\f291"; } +.bi-clock-history::before { content: "\f292"; } +.bi-clock::before { content: "\f293"; } +.bi-cloud-arrow-down-fill::before { content: "\f294"; } +.bi-cloud-arrow-down::before { content: "\f295"; } +.bi-cloud-arrow-up-fill::before { content: "\f296"; } +.bi-cloud-arrow-up::before { content: "\f297"; } +.bi-cloud-check-fill::before { content: "\f298"; } +.bi-cloud-check::before { content: "\f299"; } +.bi-cloud-download-fill::before { content: "\f29a"; } +.bi-cloud-download::before { content: "\f29b"; } +.bi-cloud-drizzle-fill::before { content: "\f29c"; } +.bi-cloud-drizzle::before { content: "\f29d"; } +.bi-cloud-fill::before { content: "\f29e"; } +.bi-cloud-fog-fill::before { content: "\f29f"; } +.bi-cloud-fog::before { content: "\f2a0"; } +.bi-cloud-fog2-fill::before { content: "\f2a1"; } +.bi-cloud-fog2::before { content: "\f2a2"; } +.bi-cloud-hail-fill::before { content: "\f2a3"; } +.bi-cloud-hail::before { content: "\f2a4"; } +.bi-cloud-haze-1::before { content: "\f2a5"; } +.bi-cloud-haze-fill::before { content: "\f2a6"; } +.bi-cloud-haze::before { content: "\f2a7"; } +.bi-cloud-haze2-fill::before { content: "\f2a8"; } +.bi-cloud-lightning-fill::before { content: "\f2a9"; } +.bi-cloud-lightning-rain-fill::before { content: "\f2aa"; } +.bi-cloud-lightning-rain::before { content: "\f2ab"; } +.bi-cloud-lightning::before { content: "\f2ac"; } +.bi-cloud-minus-fill::before { content: "\f2ad"; } +.bi-cloud-minus::before { content: "\f2ae"; } +.bi-cloud-moon-fill::before { content: "\f2af"; } +.bi-cloud-moon::before { content: "\f2b0"; } +.bi-cloud-plus-fill::before { content: "\f2b1"; } +.bi-cloud-plus::before { content: "\f2b2"; } +.bi-cloud-rain-fill::before { content: "\f2b3"; } +.bi-cloud-rain-heavy-fill::before { content: "\f2b4"; } +.bi-cloud-rain-heavy::before { content: "\f2b5"; } +.bi-cloud-rain::before { content: "\f2b6"; } +.bi-cloud-slash-fill::before { content: "\f2b7"; } +.bi-cloud-slash::before { content: "\f2b8"; } +.bi-cloud-sleet-fill::before { content: "\f2b9"; } +.bi-cloud-sleet::before { content: "\f2ba"; } +.bi-cloud-snow-fill::before { content: "\f2bb"; } +.bi-cloud-snow::before { content: "\f2bc"; } +.bi-cloud-sun-fill::before { content: "\f2bd"; } +.bi-cloud-sun::before { content: "\f2be"; } +.bi-cloud-upload-fill::before { content: "\f2bf"; } +.bi-cloud-upload::before { content: "\f2c0"; } +.bi-cloud::before { content: "\f2c1"; } +.bi-clouds-fill::before { content: "\f2c2"; } +.bi-clouds::before { content: "\f2c3"; } +.bi-cloudy-fill::before { content: "\f2c4"; } +.bi-cloudy::before { content: "\f2c5"; } +.bi-code-slash::before { content: "\f2c6"; } +.bi-code-square::before { content: "\f2c7"; } +.bi-code::before { content: "\f2c8"; } +.bi-collection-fill::before { content: "\f2c9"; } +.bi-collection-play-fill::before { content: "\f2ca"; } +.bi-collection-play::before { content: "\f2cb"; } +.bi-collection::before { content: "\f2cc"; } +.bi-columns-gap::before { content: "\f2cd"; } +.bi-columns::before { content: "\f2ce"; } +.bi-command::before { content: "\f2cf"; } +.bi-compass-fill::before { content: "\f2d0"; } +.bi-compass::before { content: "\f2d1"; } +.bi-cone-striped::before { content: "\f2d2"; } +.bi-cone::before { content: "\f2d3"; } +.bi-controller::before { content: "\f2d4"; } +.bi-cpu-fill::before { content: "\f2d5"; } +.bi-cpu::before { content: "\f2d6"; } +.bi-credit-card-2-back-fill::before { content: "\f2d7"; } +.bi-credit-card-2-back::before { content: "\f2d8"; } +.bi-credit-card-2-front-fill::before { content: "\f2d9"; } +.bi-credit-card-2-front::before { content: "\f2da"; } +.bi-credit-card-fill::before { content: "\f2db"; } +.bi-credit-card::before { content: "\f2dc"; } +.bi-crop::before { content: "\f2dd"; } +.bi-cup-fill::before { content: "\f2de"; } +.bi-cup-straw::before { content: "\f2df"; } +.bi-cup::before { content: "\f2e0"; } +.bi-cursor-fill::before { content: "\f2e1"; } +.bi-cursor-text::before { content: "\f2e2"; } +.bi-cursor::before { content: "\f2e3"; } +.bi-dash-circle-dotted::before { content: "\f2e4"; } +.bi-dash-circle-fill::before { content: "\f2e5"; } +.bi-dash-circle::before { content: "\f2e6"; } +.bi-dash-square-dotted::before { content: "\f2e7"; } +.bi-dash-square-fill::before { content: "\f2e8"; } +.bi-dash-square::before { content: "\f2e9"; } +.bi-dash::before { content: "\f2ea"; } +.bi-diagram-2-fill::before { content: "\f2eb"; } +.bi-diagram-2::before { content: "\f2ec"; } +.bi-diagram-3-fill::before { content: "\f2ed"; } +.bi-diagram-3::before { content: "\f2ee"; } +.bi-diamond-fill::before { content: "\f2ef"; } +.bi-diamond-half::before { content: "\f2f0"; } +.bi-diamond::before { content: "\f2f1"; } +.bi-dice-1-fill::before { content: "\f2f2"; } +.bi-dice-1::before { content: "\f2f3"; } +.bi-dice-2-fill::before { content: "\f2f4"; } +.bi-dice-2::before { content: "\f2f5"; } +.bi-dice-3-fill::before { content: "\f2f6"; } +.bi-dice-3::before { content: "\f2f7"; } +.bi-dice-4-fill::before { content: "\f2f8"; } +.bi-dice-4::before { content: "\f2f9"; } +.bi-dice-5-fill::before { content: "\f2fa"; } +.bi-dice-5::before { content: "\f2fb"; } +.bi-dice-6-fill::before { content: "\f2fc"; } +.bi-dice-6::before { content: "\f2fd"; } +.bi-disc-fill::before { content: "\f2fe"; } +.bi-disc::before { content: "\f2ff"; } +.bi-discord::before { content: "\f300"; } +.bi-display-fill::before { content: "\f301"; } +.bi-display::before { content: "\f302"; } +.bi-distribute-horizontal::before { content: "\f303"; } +.bi-distribute-vertical::before { content: "\f304"; } +.bi-door-closed-fill::before { content: "\f305"; } +.bi-door-closed::before { content: "\f306"; } +.bi-door-open-fill::before { content: "\f307"; } +.bi-door-open::before { content: "\f308"; } +.bi-dot::before { content: "\f309"; } +.bi-download::before { content: "\f30a"; } +.bi-droplet-fill::before { content: "\f30b"; } +.bi-droplet-half::before { content: "\f30c"; } +.bi-droplet::before { content: "\f30d"; } +.bi-earbuds::before { content: "\f30e"; } +.bi-easel-fill::before { content: "\f30f"; } +.bi-easel::before { content: "\f310"; } +.bi-egg-fill::before { content: "\f311"; } +.bi-egg-fried::before { content: "\f312"; } +.bi-egg::before { content: "\f313"; } +.bi-eject-fill::before { content: "\f314"; } +.bi-eject::before { content: "\f315"; } +.bi-emoji-angry-fill::before { content: "\f316"; } +.bi-emoji-angry::before { content: "\f317"; } +.bi-emoji-dizzy-fill::before { content: "\f318"; } +.bi-emoji-dizzy::before { content: "\f319"; } +.bi-emoji-expressionless-fill::before { content: "\f31a"; } +.bi-emoji-expressionless::before { content: "\f31b"; } +.bi-emoji-frown-fill::before { content: "\f31c"; } +.bi-emoji-frown::before { content: "\f31d"; } +.bi-emoji-heart-eyes-fill::before { content: "\f31e"; } +.bi-emoji-heart-eyes::before { content: "\f31f"; } +.bi-emoji-laughing-fill::before { content: "\f320"; } +.bi-emoji-laughing::before { content: "\f321"; } +.bi-emoji-neutral-fill::before { content: "\f322"; } +.bi-emoji-neutral::before { content: "\f323"; } +.bi-emoji-smile-fill::before { content: "\f324"; } +.bi-emoji-smile-upside-down-fill::before { content: "\f325"; } +.bi-emoji-smile-upside-down::before { content: "\f326"; } +.bi-emoji-smile::before { content: "\f327"; } +.bi-emoji-sunglasses-fill::before { content: "\f328"; } +.bi-emoji-sunglasses::before { content: "\f329"; } +.bi-emoji-wink-fill::before { content: "\f32a"; } +.bi-emoji-wink::before { content: "\f32b"; } +.bi-envelope-fill::before { content: "\f32c"; } +.bi-envelope-open-fill::before { content: "\f32d"; } +.bi-envelope-open::before { content: "\f32e"; } +.bi-envelope::before { content: "\f32f"; } +.bi-eraser-fill::before { content: "\f330"; } +.bi-eraser::before { content: "\f331"; } +.bi-exclamation-circle-fill::before { content: "\f332"; } +.bi-exclamation-circle::before { content: "\f333"; } +.bi-exclamation-diamond-fill::before { content: "\f334"; } +.bi-exclamation-diamond::before { content: "\f335"; } +.bi-exclamation-octagon-fill::before { content: "\f336"; } +.bi-exclamation-octagon::before { content: "\f337"; } +.bi-exclamation-square-fill::before { content: "\f338"; } +.bi-exclamation-square::before { content: "\f339"; } +.bi-exclamation-triangle-fill::before { content: "\f33a"; } +.bi-exclamation-triangle::before { content: "\f33b"; } +.bi-exclamation::before { content: "\f33c"; } +.bi-exclude::before { content: "\f33d"; } +.bi-eye-fill::before { content: "\f33e"; } +.bi-eye-slash-fill::before { content: "\f33f"; } +.bi-eye-slash::before { content: "\f340"; } +.bi-eye::before { content: "\f341"; } +.bi-eyedropper::before { content: "\f342"; } +.bi-eyeglasses::before { content: "\f343"; } +.bi-facebook::before { content: "\f344"; } +.bi-file-arrow-down-fill::before { content: "\f345"; } +.bi-file-arrow-down::before { content: "\f346"; } +.bi-file-arrow-up-fill::before { content: "\f347"; } +.bi-file-arrow-up::before { content: "\f348"; } +.bi-file-bar-graph-fill::before { content: "\f349"; } +.bi-file-bar-graph::before { content: "\f34a"; } +.bi-file-binary-fill::before { content: "\f34b"; } +.bi-file-binary::before { content: "\f34c"; } +.bi-file-break-fill::before { content: "\f34d"; } +.bi-file-break::before { content: "\f34e"; } +.bi-file-check-fill::before { content: "\f34f"; } +.bi-file-check::before { content: "\f350"; } +.bi-file-code-fill::before { content: "\f351"; } +.bi-file-code::before { content: "\f352"; } +.bi-file-diff-fill::before { content: "\f353"; } +.bi-file-diff::before { content: "\f354"; } +.bi-file-earmark-arrow-down-fill::before { content: "\f355"; } +.bi-file-earmark-arrow-down::before { content: "\f356"; } +.bi-file-earmark-arrow-up-fill::before { content: "\f357"; } +.bi-file-earmark-arrow-up::before { content: "\f358"; } +.bi-file-earmark-bar-graph-fill::before { content: "\f359"; } +.bi-file-earmark-bar-graph::before { content: "\f35a"; } +.bi-file-earmark-binary-fill::before { content: "\f35b"; } +.bi-file-earmark-binary::before { content: "\f35c"; } +.bi-file-earmark-break-fill::before { content: "\f35d"; } +.bi-file-earmark-break::before { content: "\f35e"; } +.bi-file-earmark-check-fill::before { content: "\f35f"; } +.bi-file-earmark-check::before { content: "\f360"; } +.bi-file-earmark-code-fill::before { content: "\f361"; } +.bi-file-earmark-code::before { content: "\f362"; } +.bi-file-earmark-diff-fill::before { content: "\f363"; } +.bi-file-earmark-diff::before { content: "\f364"; } +.bi-file-earmark-easel-fill::before { content: "\f365"; } +.bi-file-earmark-easel::before { content: "\f366"; } +.bi-file-earmark-excel-fill::before { content: "\f367"; } +.bi-file-earmark-excel::before { content: "\f368"; } +.bi-file-earmark-fill::before { content: "\f369"; } +.bi-file-earmark-font-fill::before { content: "\f36a"; } +.bi-file-earmark-font::before { content: "\f36b"; } +.bi-file-earmark-image-fill::before { content: "\f36c"; } +.bi-file-earmark-image::before { content: "\f36d"; } +.bi-file-earmark-lock-fill::before { content: "\f36e"; } +.bi-file-earmark-lock::before { content: "\f36f"; } +.bi-file-earmark-lock2-fill::before { content: "\f370"; } +.bi-file-earmark-lock2::before { content: "\f371"; } +.bi-file-earmark-medical-fill::before { content: "\f372"; } +.bi-file-earmark-medical::before { content: "\f373"; } +.bi-file-earmark-minus-fill::before { content: "\f374"; } +.bi-file-earmark-minus::before { content: "\f375"; } +.bi-file-earmark-music-fill::before { content: "\f376"; } +.bi-file-earmark-music::before { content: "\f377"; } +.bi-file-earmark-person-fill::before { content: "\f378"; } +.bi-file-earmark-person::before { content: "\f379"; } +.bi-file-earmark-play-fill::before { content: "\f37a"; } +.bi-file-earmark-play::before { content: "\f37b"; } +.bi-file-earmark-plus-fill::before { content: "\f37c"; } +.bi-file-earmark-plus::before { content: "\f37d"; } +.bi-file-earmark-post-fill::before { content: "\f37e"; } +.bi-file-earmark-post::before { content: "\f37f"; } +.bi-file-earmark-ppt-fill::before { content: "\f380"; } +.bi-file-earmark-ppt::before { content: "\f381"; } +.bi-file-earmark-richtext-fill::before { content: "\f382"; } +.bi-file-earmark-richtext::before { content: "\f383"; } +.bi-file-earmark-ruled-fill::before { content: "\f384"; } +.bi-file-earmark-ruled::before { content: "\f385"; } +.bi-file-earmark-slides-fill::before { content: "\f386"; } +.bi-file-earmark-slides::before { content: "\f387"; } +.bi-file-earmark-spreadsheet-fill::before { content: "\f388"; } +.bi-file-earmark-spreadsheet::before { content: "\f389"; } +.bi-file-earmark-text-fill::before { content: "\f38a"; } +.bi-file-earmark-text::before { content: "\f38b"; } +.bi-file-earmark-word-fill::before { content: "\f38c"; } +.bi-file-earmark-word::before { content: "\f38d"; } +.bi-file-earmark-x-fill::before { content: "\f38e"; } +.bi-file-earmark-x::before { content: "\f38f"; } +.bi-file-earmark-zip-fill::before { content: "\f390"; } +.bi-file-earmark-zip::before { content: "\f391"; } +.bi-file-earmark::before { content: "\f392"; } +.bi-file-easel-fill::before { content: "\f393"; } +.bi-file-easel::before { content: "\f394"; } +.bi-file-excel-fill::before { content: "\f395"; } +.bi-file-excel::before { content: "\f396"; } +.bi-file-fill::before { content: "\f397"; } +.bi-file-font-fill::before { content: "\f398"; } +.bi-file-font::before { content: "\f399"; } +.bi-file-image-fill::before { content: "\f39a"; } +.bi-file-image::before { content: "\f39b"; } +.bi-file-lock-fill::before { content: "\f39c"; } +.bi-file-lock::before { content: "\f39d"; } +.bi-file-lock2-fill::before { content: "\f39e"; } +.bi-file-lock2::before { content: "\f39f"; } +.bi-file-medical-fill::before { content: "\f3a0"; } +.bi-file-medical::before { content: "\f3a1"; } +.bi-file-minus-fill::before { content: "\f3a2"; } +.bi-file-minus::before { content: "\f3a3"; } +.bi-file-music-fill::before { content: "\f3a4"; } +.bi-file-music::before { content: "\f3a5"; } +.bi-file-person-fill::before { content: "\f3a6"; } +.bi-file-person::before { content: "\f3a7"; } +.bi-file-play-fill::before { content: "\f3a8"; } +.bi-file-play::before { content: "\f3a9"; } +.bi-file-plus-fill::before { content: "\f3aa"; } +.bi-file-plus::before { content: "\f3ab"; } +.bi-file-post-fill::before { content: "\f3ac"; } +.bi-file-post::before { content: "\f3ad"; } +.bi-file-ppt-fill::before { content: "\f3ae"; } +.bi-file-ppt::before { content: "\f3af"; } +.bi-file-richtext-fill::before { content: "\f3b0"; } +.bi-file-richtext::before { content: "\f3b1"; } +.bi-file-ruled-fill::before { content: "\f3b2"; } +.bi-file-ruled::before { content: "\f3b3"; } +.bi-file-slides-fill::before { content: "\f3b4"; } +.bi-file-slides::before { content: "\f3b5"; } +.bi-file-spreadsheet-fill::before { content: "\f3b6"; } +.bi-file-spreadsheet::before { content: "\f3b7"; } +.bi-file-text-fill::before { content: "\f3b8"; } +.bi-file-text::before { content: "\f3b9"; } +.bi-file-word-fill::before { content: "\f3ba"; } +.bi-file-word::before { content: "\f3bb"; } +.bi-file-x-fill::before { content: "\f3bc"; } +.bi-file-x::before { content: "\f3bd"; } +.bi-file-zip-fill::before { content: "\f3be"; } +.bi-file-zip::before { content: "\f3bf"; } +.bi-file::before { content: "\f3c0"; } +.bi-files-alt::before { content: "\f3c1"; } +.bi-files::before { content: "\f3c2"; } +.bi-film::before { content: "\f3c3"; } +.bi-filter-circle-fill::before { content: "\f3c4"; } +.bi-filter-circle::before { content: "\f3c5"; } +.bi-filter-left::before { content: "\f3c6"; } +.bi-filter-right::before { content: "\f3c7"; } +.bi-filter-square-fill::before { content: "\f3c8"; } +.bi-filter-square::before { content: "\f3c9"; } +.bi-filter::before { content: "\f3ca"; } +.bi-flag-fill::before { content: "\f3cb"; } +.bi-flag::before { content: "\f3cc"; } +.bi-flower1::before { content: "\f3cd"; } +.bi-flower2::before { content: "\f3ce"; } +.bi-flower3::before { content: "\f3cf"; } +.bi-folder-check::before { content: "\f3d0"; } +.bi-folder-fill::before { content: "\f3d1"; } +.bi-folder-minus::before { content: "\f3d2"; } +.bi-folder-plus::before { content: "\f3d3"; } +.bi-folder-symlink-fill::before { content: "\f3d4"; } +.bi-folder-symlink::before { content: "\f3d5"; } +.bi-folder-x::before { content: "\f3d6"; } +.bi-folder::before { content: "\f3d7"; } +.bi-folder2-open::before { content: "\f3d8"; } +.bi-folder2::before { content: "\f3d9"; } +.bi-fonts::before { content: "\f3da"; } +.bi-forward-fill::before { content: "\f3db"; } +.bi-forward::before { content: "\f3dc"; } +.bi-front::before { content: "\f3dd"; } +.bi-fullscreen-exit::before { content: "\f3de"; } +.bi-fullscreen::before { content: "\f3df"; } +.bi-funnel-fill::before { content: "\f3e0"; } +.bi-funnel::before { content: "\f3e1"; } +.bi-gear-fill::before { content: "\f3e2"; } +.bi-gear-wide-connected::before { content: "\f3e3"; } +.bi-gear-wide::before { content: "\f3e4"; } +.bi-gear::before { content: "\f3e5"; } +.bi-gem::before { content: "\f3e6"; } +.bi-geo-alt-fill::before { content: "\f3e7"; } +.bi-geo-alt::before { content: "\f3e8"; } +.bi-geo-fill::before { content: "\f3e9"; } +.bi-geo::before { content: "\f3ea"; } +.bi-gift-fill::before { content: "\f3eb"; } +.bi-gift::before { content: "\f3ec"; } +.bi-github::before { content: "\f3ed"; } +.bi-globe::before { content: "\f3ee"; } +.bi-globe2::before { content: "\f3ef"; } +.bi-google::before { content: "\f3f0"; } +.bi-graph-down::before { content: "\f3f1"; } +.bi-graph-up::before { content: "\f3f2"; } +.bi-grid-1x2-fill::before { content: "\f3f3"; } +.bi-grid-1x2::before { content: "\f3f4"; } +.bi-grid-3x2-gap-fill::before { content: "\f3f5"; } +.bi-grid-3x2-gap::before { content: "\f3f6"; } +.bi-grid-3x2::before { content: "\f3f7"; } +.bi-grid-3x3-gap-fill::before { content: "\f3f8"; } +.bi-grid-3x3-gap::before { content: "\f3f9"; } +.bi-grid-3x3::before { content: "\f3fa"; } +.bi-grid-fill::before { content: "\f3fb"; } +.bi-grid::before { content: "\f3fc"; } +.bi-grip-horizontal::before { content: "\f3fd"; } +.bi-grip-vertical::before { content: "\f3fe"; } +.bi-hammer::before { content: "\f3ff"; } +.bi-hand-index-fill::before { content: "\f400"; } +.bi-hand-index-thumb-fill::before { content: "\f401"; } +.bi-hand-index-thumb::before { content: "\f402"; } +.bi-hand-index::before { content: "\f403"; } +.bi-hand-thumbs-down-fill::before { content: "\f404"; } +.bi-hand-thumbs-down::before { content: "\f405"; } +.bi-hand-thumbs-up-fill::before { content: "\f406"; } +.bi-hand-thumbs-up::before { content: "\f407"; } +.bi-handbag-fill::before { content: "\f408"; } +.bi-handbag::before { content: "\f409"; } +.bi-hash::before { content: "\f40a"; } +.bi-hdd-fill::before { content: "\f40b"; } +.bi-hdd-network-fill::before { content: "\f40c"; } +.bi-hdd-network::before { content: "\f40d"; } +.bi-hdd-rack-fill::before { content: "\f40e"; } +.bi-hdd-rack::before { content: "\f40f"; } +.bi-hdd-stack-fill::before { content: "\f410"; } +.bi-hdd-stack::before { content: "\f411"; } +.bi-hdd::before { content: "\f412"; } +.bi-headphones::before { content: "\f413"; } +.bi-headset::before { content: "\f414"; } +.bi-heart-fill::before { content: "\f415"; } +.bi-heart-half::before { content: "\f416"; } +.bi-heart::before { content: "\f417"; } +.bi-heptagon-fill::before { content: "\f418"; } +.bi-heptagon-half::before { content: "\f419"; } +.bi-heptagon::before { content: "\f41a"; } +.bi-hexagon-fill::before { content: "\f41b"; } +.bi-hexagon-half::before { content: "\f41c"; } +.bi-hexagon::before { content: "\f41d"; } +.bi-hourglass-bottom::before { content: "\f41e"; } +.bi-hourglass-split::before { content: "\f41f"; } +.bi-hourglass-top::before { content: "\f420"; } +.bi-hourglass::before { content: "\f421"; } +.bi-house-door-fill::before { content: "\f422"; } +.bi-house-door::before { content: "\f423"; } +.bi-house-fill::before { content: "\f424"; } +.bi-house::before { content: "\f425"; } +.bi-hr::before { content: "\f426"; } +.bi-hurricane::before { content: "\f427"; } +.bi-image-alt::before { content: "\f428"; } +.bi-image-fill::before { content: "\f429"; } +.bi-image::before { content: "\f42a"; } +.bi-images::before { content: "\f42b"; } +.bi-inbox-fill::before { content: "\f42c"; } +.bi-inbox::before { content: "\f42d"; } +.bi-inboxes-fill::before { content: "\f42e"; } +.bi-inboxes::before { content: "\f42f"; } +.bi-info-circle-fill::before { content: "\f430"; } +.bi-info-circle::before { content: "\f431"; } +.bi-info-square-fill::before { content: "\f432"; } +.bi-info-square::before { content: "\f433"; } +.bi-info::before { content: "\f434"; } +.bi-input-cursor-text::before { content: "\f435"; } +.bi-input-cursor::before { content: "\f436"; } +.bi-instagram::before { content: "\f437"; } +.bi-intersect::before { content: "\f438"; } +.bi-journal-album::before { content: "\f439"; } +.bi-journal-arrow-down::before { content: "\f43a"; } +.bi-journal-arrow-up::before { content: "\f43b"; } +.bi-journal-bookmark-fill::before { content: "\f43c"; } +.bi-journal-bookmark::before { content: "\f43d"; } +.bi-journal-check::before { content: "\f43e"; } +.bi-journal-code::before { content: "\f43f"; } +.bi-journal-medical::before { content: "\f440"; } +.bi-journal-minus::before { content: "\f441"; } +.bi-journal-plus::before { content: "\f442"; } +.bi-journal-richtext::before { content: "\f443"; } +.bi-journal-text::before { content: "\f444"; } +.bi-journal-x::before { content: "\f445"; } +.bi-journal::before { content: "\f446"; } +.bi-journals::before { content: "\f447"; } +.bi-joystick::before { content: "\f448"; } +.bi-justify-left::before { content: "\f449"; } +.bi-justify-right::before { content: "\f44a"; } +.bi-justify::before { content: "\f44b"; } +.bi-kanban-fill::before { content: "\f44c"; } +.bi-kanban::before { content: "\f44d"; } +.bi-key-fill::before { content: "\f44e"; } +.bi-key::before { content: "\f44f"; } +.bi-keyboard-fill::before { content: "\f450"; } +.bi-keyboard::before { content: "\f451"; } +.bi-ladder::before { content: "\f452"; } +.bi-lamp-fill::before { content: "\f453"; } +.bi-lamp::before { content: "\f454"; } +.bi-laptop-fill::before { content: "\f455"; } +.bi-laptop::before { content: "\f456"; } +.bi-layer-backward::before { content: "\f457"; } +.bi-layer-forward::before { content: "\f458"; } +.bi-layers-fill::before { content: "\f459"; } +.bi-layers-half::before { content: "\f45a"; } +.bi-layers::before { content: "\f45b"; } +.bi-layout-sidebar-inset-reverse::before { content: "\f45c"; } +.bi-layout-sidebar-inset::before { content: "\f45d"; } +.bi-layout-sidebar-reverse::before { content: "\f45e"; } +.bi-layout-sidebar::before { content: "\f45f"; } +.bi-layout-split::before { content: "\f460"; } +.bi-layout-text-sidebar-reverse::before { content: "\f461"; } +.bi-layout-text-sidebar::before { content: "\f462"; } +.bi-layout-text-window-reverse::before { content: "\f463"; } +.bi-layout-text-window::before { content: "\f464"; } +.bi-layout-three-columns::before { content: "\f465"; } +.bi-layout-wtf::before { content: "\f466"; } +.bi-life-preserver::before { content: "\f467"; } +.bi-lightbulb-fill::before { content: "\f468"; } +.bi-lightbulb-off-fill::before { content: "\f469"; } +.bi-lightbulb-off::before { content: "\f46a"; } +.bi-lightbulb::before { content: "\f46b"; } +.bi-lightning-charge-fill::before { content: "\f46c"; } +.bi-lightning-charge::before { content: "\f46d"; } +.bi-lightning-fill::before { content: "\f46e"; } +.bi-lightning::before { content: "\f46f"; } +.bi-link-45deg::before { content: "\f470"; } +.bi-link::before { content: "\f471"; } +.bi-linkedin::before { content: "\f472"; } +.bi-list-check::before { content: "\f473"; } +.bi-list-nested::before { content: "\f474"; } +.bi-list-ol::before { content: "\f475"; } +.bi-list-stars::before { content: "\f476"; } +.bi-list-task::before { content: "\f477"; } +.bi-list-ul::before { content: "\f478"; } +.bi-list::before { content: "\f479"; } +.bi-lock-fill::before { content: "\f47a"; } +.bi-lock::before { content: "\f47b"; } +.bi-mailbox::before { content: "\f47c"; } +.bi-mailbox2::before { content: "\f47d"; } +.bi-map-fill::before { content: "\f47e"; } +.bi-map::before { content: "\f47f"; } +.bi-markdown-fill::before { content: "\f480"; } +.bi-markdown::before { content: "\f481"; } +.bi-mask::before { content: "\f482"; } +.bi-megaphone-fill::before { content: "\f483"; } +.bi-megaphone::before { content: "\f484"; } +.bi-menu-app-fill::before { content: "\f485"; } +.bi-menu-app::before { content: "\f486"; } +.bi-menu-button-fill::before { content: "\f487"; } +.bi-menu-button-wide-fill::before { content: "\f488"; } +.bi-menu-button-wide::before { content: "\f489"; } +.bi-menu-button::before { content: "\f48a"; } +.bi-menu-down::before { content: "\f48b"; } +.bi-menu-up::before { content: "\f48c"; } +.bi-mic-fill::before { content: "\f48d"; } +.bi-mic-mute-fill::before { content: "\f48e"; } +.bi-mic-mute::before { content: "\f48f"; } +.bi-mic::before { content: "\f490"; } +.bi-minecart-loaded::before { content: "\f491"; } +.bi-minecart::before { content: "\f492"; } +.bi-moisture::before { content: "\f493"; } +.bi-moon-fill::before { content: "\f494"; } +.bi-moon-stars-fill::before { content: "\f495"; } +.bi-moon-stars::before { content: "\f496"; } +.bi-moon::before { content: "\f497"; } +.bi-mouse-fill::before { content: "\f498"; } +.bi-mouse::before { content: "\f499"; } +.bi-mouse2-fill::before { content: "\f49a"; } +.bi-mouse2::before { content: "\f49b"; } +.bi-mouse3-fill::before { content: "\f49c"; } +.bi-mouse3::before { content: "\f49d"; } +.bi-music-note-beamed::before { content: "\f49e"; } +.bi-music-note-list::before { content: "\f49f"; } +.bi-music-note::before { content: "\f4a0"; } +.bi-music-player-fill::before { content: "\f4a1"; } +.bi-music-player::before { content: "\f4a2"; } +.bi-newspaper::before { content: "\f4a3"; } +.bi-node-minus-fill::before { content: "\f4a4"; } +.bi-node-minus::before { content: "\f4a5"; } +.bi-node-plus-fill::before { content: "\f4a6"; } +.bi-node-plus::before { content: "\f4a7"; } +.bi-nut-fill::before { content: "\f4a8"; } +.bi-nut::before { content: "\f4a9"; } +.bi-octagon-fill::before { content: "\f4aa"; } +.bi-octagon-half::before { content: "\f4ab"; } +.bi-octagon::before { content: "\f4ac"; } +.bi-option::before { content: "\f4ad"; } +.bi-outlet::before { content: "\f4ae"; } +.bi-paint-bucket::before { content: "\f4af"; } +.bi-palette-fill::before { content: "\f4b0"; } +.bi-palette::before { content: "\f4b1"; } +.bi-palette2::before { content: "\f4b2"; } +.bi-paperclip::before { content: "\f4b3"; } +.bi-paragraph::before { content: "\f4b4"; } +.bi-patch-check-fill::before { content: "\f4b5"; } +.bi-patch-check::before { content: "\f4b6"; } +.bi-patch-exclamation-fill::before { content: "\f4b7"; } +.bi-patch-exclamation::before { content: "\f4b8"; } +.bi-patch-minus-fill::before { content: "\f4b9"; } +.bi-patch-minus::before { content: "\f4ba"; } +.bi-patch-plus-fill::before { content: "\f4bb"; } +.bi-patch-plus::before { content: "\f4bc"; } +.bi-patch-question-fill::before { content: "\f4bd"; } +.bi-patch-question::before { content: "\f4be"; } +.bi-pause-btn-fill::before { content: "\f4bf"; } +.bi-pause-btn::before { content: "\f4c0"; } +.bi-pause-circle-fill::before { content: "\f4c1"; } +.bi-pause-circle::before { content: "\f4c2"; } +.bi-pause-fill::before { content: "\f4c3"; } +.bi-pause::before { content: "\f4c4"; } +.bi-peace-fill::before { content: "\f4c5"; } +.bi-peace::before { content: "\f4c6"; } +.bi-pen-fill::before { content: "\f4c7"; } +.bi-pen::before { content: "\f4c8"; } +.bi-pencil-fill::before { content: "\f4c9"; } +.bi-pencil-square::before { content: "\f4ca"; } +.bi-pencil::before { content: "\f4cb"; } +.bi-pentagon-fill::before { content: "\f4cc"; } +.bi-pentagon-half::before { content: "\f4cd"; } +.bi-pentagon::before { content: "\f4ce"; } +.bi-people-fill::before { content: "\f4cf"; } +.bi-people::before { content: "\f4d0"; } +.bi-percent::before { content: "\f4d1"; } +.bi-person-badge-fill::before { content: "\f4d2"; } +.bi-person-badge::before { content: "\f4d3"; } +.bi-person-bounding-box::before { content: "\f4d4"; } +.bi-person-check-fill::before { content: "\f4d5"; } +.bi-person-check::before { content: "\f4d6"; } +.bi-person-circle::before { content: "\f4d7"; } +.bi-person-dash-fill::before { content: "\f4d8"; } +.bi-person-dash::before { content: "\f4d9"; } +.bi-person-fill::before { content: "\f4da"; } +.bi-person-lines-fill::before { content: "\f4db"; } +.bi-person-plus-fill::before { content: "\f4dc"; } +.bi-person-plus::before { content: "\f4dd"; } +.bi-person-square::before { content: "\f4de"; } +.bi-person-x-fill::before { content: "\f4df"; } +.bi-person-x::before { content: "\f4e0"; } +.bi-person::before { content: "\f4e1"; } +.bi-phone-fill::before { content: "\f4e2"; } +.bi-phone-landscape-fill::before { content: "\f4e3"; } +.bi-phone-landscape::before { content: "\f4e4"; } +.bi-phone-vibrate-fill::before { content: "\f4e5"; } +.bi-phone-vibrate::before { content: "\f4e6"; } +.bi-phone::before { content: "\f4e7"; } +.bi-pie-chart-fill::before { content: "\f4e8"; } +.bi-pie-chart::before { content: "\f4e9"; } +.bi-pin-angle-fill::before { content: "\f4ea"; } +.bi-pin-angle::before { content: "\f4eb"; } +.bi-pin-fill::before { content: "\f4ec"; } +.bi-pin::before { content: "\f4ed"; } +.bi-pip-fill::before { content: "\f4ee"; } +.bi-pip::before { content: "\f4ef"; } +.bi-play-btn-fill::before { content: "\f4f0"; } +.bi-play-btn::before { content: "\f4f1"; } +.bi-play-circle-fill::before { content: "\f4f2"; } +.bi-play-circle::before { content: "\f4f3"; } +.bi-play-fill::before { content: "\f4f4"; } +.bi-play::before { content: "\f4f5"; } +.bi-plug-fill::before { content: "\f4f6"; } +.bi-plug::before { content: "\f4f7"; } +.bi-plus-circle-dotted::before { content: "\f4f8"; } +.bi-plus-circle-fill::before { content: "\f4f9"; } +.bi-plus-circle::before { content: "\f4fa"; } +.bi-plus-square-dotted::before { content: "\f4fb"; } +.bi-plus-square-fill::before { content: "\f4fc"; } +.bi-plus-square::before { content: "\f4fd"; } +.bi-plus::before { content: "\f4fe"; } +.bi-power::before { content: "\f4ff"; } +.bi-printer-fill::before { content: "\f500"; } +.bi-printer::before { content: "\f501"; } +.bi-puzzle-fill::before { content: "\f502"; } +.bi-puzzle::before { content: "\f503"; } +.bi-question-circle-fill::before { content: "\f504"; } +.bi-question-circle::before { content: "\f505"; } +.bi-question-diamond-fill::before { content: "\f506"; } +.bi-question-diamond::before { content: "\f507"; } +.bi-question-octagon-fill::before { content: "\f508"; } +.bi-question-octagon::before { content: "\f509"; } +.bi-question-square-fill::before { content: "\f50a"; } +.bi-question-square::before { content: "\f50b"; } +.bi-question::before { content: "\f50c"; } +.bi-rainbow::before { content: "\f50d"; } +.bi-receipt-cutoff::before { content: "\f50e"; } +.bi-receipt::before { content: "\f50f"; } +.bi-reception-0::before { content: "\f510"; } +.bi-reception-1::before { content: "\f511"; } +.bi-reception-2::before { content: "\f512"; } +.bi-reception-3::before { content: "\f513"; } +.bi-reception-4::before { content: "\f514"; } +.bi-record-btn-fill::before { content: "\f515"; } +.bi-record-btn::before { content: "\f516"; } +.bi-record-circle-fill::before { content: "\f517"; } +.bi-record-circle::before { content: "\f518"; } +.bi-record-fill::before { content: "\f519"; } +.bi-record::before { content: "\f51a"; } +.bi-record2-fill::before { content: "\f51b"; } +.bi-record2::before { content: "\f51c"; } +.bi-reply-all-fill::before { content: "\f51d"; } +.bi-reply-all::before { content: "\f51e"; } +.bi-reply-fill::before { content: "\f51f"; } +.bi-reply::before { content: "\f520"; } +.bi-rss-fill::before { content: "\f521"; } +.bi-rss::before { content: "\f522"; } +.bi-rulers::before { content: "\f523"; } +.bi-save-fill::before { content: "\f524"; } +.bi-save::before { content: "\f525"; } +.bi-save2-fill::before { content: "\f526"; } +.bi-save2::before { content: "\f527"; } +.bi-scissors::before { content: "\f528"; } +.bi-screwdriver::before { content: "\f529"; } +.bi-search::before { content: "\f52a"; } +.bi-segmented-nav::before { content: "\f52b"; } +.bi-server::before { content: "\f52c"; } +.bi-share-fill::before { content: "\f52d"; } +.bi-share::before { content: "\f52e"; } +.bi-shield-check::before { content: "\f52f"; } +.bi-shield-exclamation::before { content: "\f530"; } +.bi-shield-fill-check::before { content: "\f531"; } +.bi-shield-fill-exclamation::before { content: "\f532"; } +.bi-shield-fill-minus::before { content: "\f533"; } +.bi-shield-fill-plus::before { content: "\f534"; } +.bi-shield-fill-x::before { content: "\f535"; } +.bi-shield-fill::before { content: "\f536"; } +.bi-shield-lock-fill::before { content: "\f537"; } +.bi-shield-lock::before { content: "\f538"; } +.bi-shield-minus::before { content: "\f539"; } +.bi-shield-plus::before { content: "\f53a"; } +.bi-shield-shaded::before { content: "\f53b"; } +.bi-shield-slash-fill::before { content: "\f53c"; } +.bi-shield-slash::before { content: "\f53d"; } +.bi-shield-x::before { content: "\f53e"; } +.bi-shield::before { content: "\f53f"; } +.bi-shift-fill::before { content: "\f540"; } +.bi-shift::before { content: "\f541"; } +.bi-shop-window::before { content: "\f542"; } +.bi-shop::before { content: "\f543"; } +.bi-shuffle::before { content: "\f544"; } +.bi-signpost-2-fill::before { content: "\f545"; } +.bi-signpost-2::before { content: "\f546"; } +.bi-signpost-fill::before { content: "\f547"; } +.bi-signpost-split-fill::before { content: "\f548"; } +.bi-signpost-split::before { content: "\f549"; } +.bi-signpost::before { content: "\f54a"; } +.bi-sim-fill::before { content: "\f54b"; } +.bi-sim::before { content: "\f54c"; } +.bi-skip-backward-btn-fill::before { content: "\f54d"; } +.bi-skip-backward-btn::before { content: "\f54e"; } +.bi-skip-backward-circle-fill::before { content: "\f54f"; } +.bi-skip-backward-circle::before { content: "\f550"; } +.bi-skip-backward-fill::before { content: "\f551"; } +.bi-skip-backward::before { content: "\f552"; } +.bi-skip-end-btn-fill::before { content: "\f553"; } +.bi-skip-end-btn::before { content: "\f554"; } +.bi-skip-end-circle-fill::before { content: "\f555"; } +.bi-skip-end-circle::before { content: "\f556"; } +.bi-skip-end-fill::before { content: "\f557"; } +.bi-skip-end::before { content: "\f558"; } +.bi-skip-forward-btn-fill::before { content: "\f559"; } +.bi-skip-forward-btn::before { content: "\f55a"; } +.bi-skip-forward-circle-fill::before { content: "\f55b"; } +.bi-skip-forward-circle::before { content: "\f55c"; } +.bi-skip-forward-fill::before { content: "\f55d"; } +.bi-skip-forward::before { content: "\f55e"; } +.bi-skip-start-btn-fill::before { content: "\f55f"; } +.bi-skip-start-btn::before { content: "\f560"; } +.bi-skip-start-circle-fill::before { content: "\f561"; } +.bi-skip-start-circle::before { content: "\f562"; } +.bi-skip-start-fill::before { content: "\f563"; } +.bi-skip-start::before { content: "\f564"; } +.bi-slack::before { content: "\f565"; } +.bi-slash-circle-fill::before { content: "\f566"; } +.bi-slash-circle::before { content: "\f567"; } +.bi-slash-square-fill::before { content: "\f568"; } +.bi-slash-square::before { content: "\f569"; } +.bi-slash::before { content: "\f56a"; } +.bi-sliders::before { content: "\f56b"; } +.bi-smartwatch::before { content: "\f56c"; } +.bi-snow::before { content: "\f56d"; } +.bi-snow2::before { content: "\f56e"; } +.bi-snow3::before { content: "\f56f"; } +.bi-sort-alpha-down-alt::before { content: "\f570"; } +.bi-sort-alpha-down::before { content: "\f571"; } +.bi-sort-alpha-up-alt::before { content: "\f572"; } +.bi-sort-alpha-up::before { content: "\f573"; } +.bi-sort-down-alt::before { content: "\f574"; } +.bi-sort-down::before { content: "\f575"; } +.bi-sort-numeric-down-alt::before { content: "\f576"; } +.bi-sort-numeric-down::before { content: "\f577"; } +.bi-sort-numeric-up-alt::before { content: "\f578"; } +.bi-sort-numeric-up::before { content: "\f579"; } +.bi-sort-up-alt::before { content: "\f57a"; } +.bi-sort-up::before { content: "\f57b"; } +.bi-soundwave::before { content: "\f57c"; } +.bi-speaker-fill::before { content: "\f57d"; } +.bi-speaker::before { content: "\f57e"; } +.bi-speedometer::before { content: "\f57f"; } +.bi-speedometer2::before { content: "\f580"; } +.bi-spellcheck::before { content: "\f581"; } +.bi-square-fill::before { content: "\f582"; } +.bi-square-half::before { content: "\f583"; } +.bi-square::before { content: "\f584"; } +.bi-stack::before { content: "\f585"; } +.bi-star-fill::before { content: "\f586"; } +.bi-star-half::before { content: "\f587"; } +.bi-star::before { content: "\f588"; } +.bi-stars::before { content: "\f589"; } +.bi-stickies-fill::before { content: "\f58a"; } +.bi-stickies::before { content: "\f58b"; } +.bi-sticky-fill::before { content: "\f58c"; } +.bi-sticky::before { content: "\f58d"; } +.bi-stop-btn-fill::before { content: "\f58e"; } +.bi-stop-btn::before { content: "\f58f"; } +.bi-stop-circle-fill::before { content: "\f590"; } +.bi-stop-circle::before { content: "\f591"; } +.bi-stop-fill::before { content: "\f592"; } +.bi-stop::before { content: "\f593"; } +.bi-stoplights-fill::before { content: "\f594"; } +.bi-stoplights::before { content: "\f595"; } +.bi-stopwatch-fill::before { content: "\f596"; } +.bi-stopwatch::before { content: "\f597"; } +.bi-subtract::before { content: "\f598"; } +.bi-suit-club-fill::before { content: "\f599"; } +.bi-suit-club::before { content: "\f59a"; } +.bi-suit-diamond-fill::before { content: "\f59b"; } +.bi-suit-diamond::before { content: "\f59c"; } +.bi-suit-heart-fill::before { content: "\f59d"; } +.bi-suit-heart::before { content: "\f59e"; } +.bi-suit-spade-fill::before { content: "\f59f"; } +.bi-suit-spade::before { content: "\f5a0"; } +.bi-sun-fill::before { content: "\f5a1"; } +.bi-sun::before { content: "\f5a2"; } +.bi-sunglasses::before { content: "\f5a3"; } +.bi-sunrise-fill::before { content: "\f5a4"; } +.bi-sunrise::before { content: "\f5a5"; } +.bi-sunset-fill::before { content: "\f5a6"; } +.bi-sunset::before { content: "\f5a7"; } +.bi-symmetry-horizontal::before { content: "\f5a8"; } +.bi-symmetry-vertical::before { content: "\f5a9"; } +.bi-table::before { content: "\f5aa"; } +.bi-tablet-fill::before { content: "\f5ab"; } +.bi-tablet-landscape-fill::before { content: "\f5ac"; } +.bi-tablet-landscape::before { content: "\f5ad"; } +.bi-tablet::before { content: "\f5ae"; } +.bi-tag-fill::before { content: "\f5af"; } +.bi-tag::before { content: "\f5b0"; } +.bi-tags-fill::before { content: "\f5b1"; } +.bi-tags::before { content: "\f5b2"; } +.bi-telegram::before { content: "\f5b3"; } +.bi-telephone-fill::before { content: "\f5b4"; } +.bi-telephone-forward-fill::before { content: "\f5b5"; } +.bi-telephone-forward::before { content: "\f5b6"; } +.bi-telephone-inbound-fill::before { content: "\f5b7"; } +.bi-telephone-inbound::before { content: "\f5b8"; } +.bi-telephone-minus-fill::before { content: "\f5b9"; } +.bi-telephone-minus::before { content: "\f5ba"; } +.bi-telephone-outbound-fill::before { content: "\f5bb"; } +.bi-telephone-outbound::before { content: "\f5bc"; } +.bi-telephone-plus-fill::before { content: "\f5bd"; } +.bi-telephone-plus::before { content: "\f5be"; } +.bi-telephone-x-fill::before { content: "\f5bf"; } +.bi-telephone-x::before { content: "\f5c0"; } +.bi-telephone::before { content: "\f5c1"; } +.bi-terminal-fill::before { content: "\f5c2"; } +.bi-terminal::before { content: "\f5c3"; } +.bi-text-center::before { content: "\f5c4"; } +.bi-text-indent-left::before { content: "\f5c5"; } +.bi-text-indent-right::before { content: "\f5c6"; } +.bi-text-left::before { content: "\f5c7"; } +.bi-text-paragraph::before { content: "\f5c8"; } +.bi-text-right::before { content: "\f5c9"; } +.bi-textarea-resize::before { content: "\f5ca"; } +.bi-textarea-t::before { content: "\f5cb"; } +.bi-textarea::before { content: "\f5cc"; } +.bi-thermometer-half::before { content: "\f5cd"; } +.bi-thermometer-high::before { content: "\f5ce"; } +.bi-thermometer-low::before { content: "\f5cf"; } +.bi-thermometer-snow::before { content: "\f5d0"; } +.bi-thermometer-sun::before { content: "\f5d1"; } +.bi-thermometer::before { content: "\f5d2"; } +.bi-three-dots-vertical::before { content: "\f5d3"; } +.bi-three-dots::before { content: "\f5d4"; } +.bi-toggle-off::before { content: "\f5d5"; } +.bi-toggle-on::before { content: "\f5d6"; } +.bi-toggle2-off::before { content: "\f5d7"; } +.bi-toggle2-on::before { content: "\f5d8"; } +.bi-toggles::before { content: "\f5d9"; } +.bi-toggles2::before { content: "\f5da"; } +.bi-tools::before { content: "\f5db"; } +.bi-tornado::before { content: "\f5dc"; } +.bi-trash-fill::before { content: "\f5dd"; } +.bi-trash::before { content: "\f5de"; } +.bi-trash2-fill::before { content: "\f5df"; } +.bi-trash2::before { content: "\f5e0"; } +.bi-tree-fill::before { content: "\f5e1"; } +.bi-tree::before { content: "\f5e2"; } +.bi-triangle-fill::before { content: "\f5e3"; } +.bi-triangle-half::before { content: "\f5e4"; } +.bi-triangle::before { content: "\f5e5"; } +.bi-trophy-fill::before { content: "\f5e6"; } +.bi-trophy::before { content: "\f5e7"; } +.bi-tropical-storm::before { content: "\f5e8"; } +.bi-truck-flatbed::before { content: "\f5e9"; } +.bi-truck::before { content: "\f5ea"; } +.bi-tsunami::before { content: "\f5eb"; } +.bi-tv-fill::before { content: "\f5ec"; } +.bi-tv::before { content: "\f5ed"; } +.bi-twitch::before { content: "\f5ee"; } +.bi-twitter::before { content: "\f5ef"; } +.bi-type-bold::before { content: "\f5f0"; } +.bi-type-h1::before { content: "\f5f1"; } +.bi-type-h2::before { content: "\f5f2"; } +.bi-type-h3::before { content: "\f5f3"; } +.bi-type-italic::before { content: "\f5f4"; } +.bi-type-strikethrough::before { content: "\f5f5"; } +.bi-type-underline::before { content: "\f5f6"; } +.bi-type::before { content: "\f5f7"; } +.bi-ui-checks-grid::before { content: "\f5f8"; } +.bi-ui-checks::before { content: "\f5f9"; } +.bi-ui-radios-grid::before { content: "\f5fa"; } +.bi-ui-radios::before { content: "\f5fb"; } +.bi-umbrella-fill::before { content: "\f5fc"; } +.bi-umbrella::before { content: "\f5fd"; } +.bi-union::before { content: "\f5fe"; } +.bi-unlock-fill::before { content: "\f5ff"; } +.bi-unlock::before { content: "\f600"; } +.bi-upc-scan::before { content: "\f601"; } +.bi-upc::before { content: "\f602"; } +.bi-upload::before { content: "\f603"; } +.bi-vector-pen::before { content: "\f604"; } +.bi-view-list::before { content: "\f605"; } +.bi-view-stacked::before { content: "\f606"; } +.bi-vinyl-fill::before { content: "\f607"; } +.bi-vinyl::before { content: "\f608"; } +.bi-voicemail::before { content: "\f609"; } +.bi-volume-down-fill::before { content: "\f60a"; } +.bi-volume-down::before { content: "\f60b"; } +.bi-volume-mute-fill::before { content: "\f60c"; } +.bi-volume-mute::before { content: "\f60d"; } +.bi-volume-off-fill::before { content: "\f60e"; } +.bi-volume-off::before { content: "\f60f"; } +.bi-volume-up-fill::before { content: "\f610"; } +.bi-volume-up::before { content: "\f611"; } +.bi-vr::before { content: "\f612"; } +.bi-wallet-fill::before { content: "\f613"; } +.bi-wallet::before { content: "\f614"; } +.bi-wallet2::before { content: "\f615"; } +.bi-watch::before { content: "\f616"; } +.bi-water::before { content: "\f617"; } +.bi-whatsapp::before { content: "\f618"; } +.bi-wifi-1::before { content: "\f619"; } +.bi-wifi-2::before { content: "\f61a"; } +.bi-wifi-off::before { content: "\f61b"; } +.bi-wifi::before { content: "\f61c"; } +.bi-wind::before { content: "\f61d"; } +.bi-window-dock::before { content: "\f61e"; } +.bi-window-sidebar::before { content: "\f61f"; } +.bi-window::before { content: "\f620"; } +.bi-wrench::before { content: "\f621"; } +.bi-x-circle-fill::before { content: "\f622"; } +.bi-x-circle::before { content: "\f623"; } +.bi-x-diamond-fill::before { content: "\f624"; } +.bi-x-diamond::before { content: "\f625"; } +.bi-x-octagon-fill::before { content: "\f626"; } +.bi-x-octagon::before { content: "\f627"; } +.bi-x-square-fill::before { content: "\f628"; } +.bi-x-square::before { content: "\f629"; } +.bi-x::before { content: "\f62a"; } +.bi-youtube::before { content: "\f62b"; } +.bi-zoom-in::before { content: "\f62c"; } +.bi-zoom-out::before { content: "\f62d"; } +.bi-bank::before { content: "\f62e"; } +.bi-bank2::before { content: "\f62f"; } +.bi-bell-slash-fill::before { content: "\f630"; } +.bi-bell-slash::before { content: "\f631"; } +.bi-cash-coin::before { content: "\f632"; } +.bi-check-lg::before { content: "\f633"; } +.bi-coin::before { content: "\f634"; } +.bi-currency-bitcoin::before { content: "\f635"; } +.bi-currency-dollar::before { content: "\f636"; } +.bi-currency-euro::before { content: "\f637"; } +.bi-currency-exchange::before { content: "\f638"; } +.bi-currency-pound::before { content: "\f639"; } +.bi-currency-yen::before { content: "\f63a"; } +.bi-dash-lg::before { content: "\f63b"; } +.bi-exclamation-lg::before { content: "\f63c"; } +.bi-file-earmark-pdf-fill::before { content: "\f63d"; } +.bi-file-earmark-pdf::before { content: "\f63e"; } +.bi-file-pdf-fill::before { content: "\f63f"; } +.bi-file-pdf::before { content: "\f640"; } +.bi-gender-ambiguous::before { content: "\f641"; } +.bi-gender-female::before { content: "\f642"; } +.bi-gender-male::before { content: "\f643"; } +.bi-gender-trans::before { content: "\f644"; } +.bi-headset-vr::before { content: "\f645"; } +.bi-info-lg::before { content: "\f646"; } +.bi-mastodon::before { content: "\f647"; } +.bi-messenger::before { content: "\f648"; } +.bi-piggy-bank-fill::before { content: "\f649"; } +.bi-piggy-bank::before { content: "\f64a"; } +.bi-pin-map-fill::before { content: "\f64b"; } +.bi-pin-map::before { content: "\f64c"; } +.bi-plus-lg::before { content: "\f64d"; } +.bi-question-lg::before { content: "\f64e"; } +.bi-recycle::before { content: "\f64f"; } +.bi-reddit::before { content: "\f650"; } +.bi-safe-fill::before { content: "\f651"; } +.bi-safe2-fill::before { content: "\f652"; } +.bi-safe2::before { content: "\f653"; } +.bi-sd-card-fill::before { content: "\f654"; } +.bi-sd-card::before { content: "\f655"; } +.bi-skype::before { content: "\f656"; } +.bi-slash-lg::before { content: "\f657"; } +.bi-translate::before { content: "\f658"; } +.bi-x-lg::before { content: "\f659"; } +.bi-safe::before { content: "\f65a"; } +.bi-apple::before { content: "\f65b"; } +.bi-microsoft::before { content: "\f65d"; } +.bi-windows::before { content: "\f65e"; } +.bi-behance::before { content: "\f65c"; } +.bi-dribbble::before { content: "\f65f"; } +.bi-line::before { content: "\f660"; } +.bi-medium::before { content: "\f661"; } +.bi-paypal::before { content: "\f662"; } +.bi-pinterest::before { content: "\f663"; } +.bi-signal::before { content: "\f664"; } +.bi-snapchat::before { content: "\f665"; } +.bi-spotify::before { content: "\f666"; } +.bi-stack-overflow::before { content: "\f667"; } +.bi-strava::before { content: "\f668"; } +.bi-wordpress::before { content: "\f669"; } +.bi-vimeo::before { content: "\f66a"; } +.bi-activity::before { content: "\f66b"; } +.bi-easel2-fill::before { content: "\f66c"; } +.bi-easel2::before { content: "\f66d"; } +.bi-easel3-fill::before { content: "\f66e"; } +.bi-easel3::before { content: "\f66f"; } +.bi-fan::before { content: "\f670"; } +.bi-fingerprint::before { content: "\f671"; } +.bi-graph-down-arrow::before { content: "\f672"; } +.bi-graph-up-arrow::before { content: "\f673"; } +.bi-hypnotize::before { content: "\f674"; } +.bi-magic::before { content: "\f675"; } +.bi-person-rolodex::before { content: "\f676"; } +.bi-person-video::before { content: "\f677"; } +.bi-person-video2::before { content: "\f678"; } +.bi-person-video3::before { content: "\f679"; } +.bi-person-workspace::before { content: "\f67a"; } +.bi-radioactive::before { content: "\f67b"; } +.bi-webcam-fill::before { content: "\f67c"; } +.bi-webcam::before { content: "\f67d"; } +.bi-yin-yang::before { content: "\f67e"; } +.bi-bandaid-fill::before { content: "\f680"; } +.bi-bandaid::before { content: "\f681"; } +.bi-bluetooth::before { content: "\f682"; } +.bi-body-text::before { content: "\f683"; } +.bi-boombox::before { content: "\f684"; } +.bi-boxes::before { content: "\f685"; } +.bi-dpad-fill::before { content: "\f686"; } +.bi-dpad::before { content: "\f687"; } +.bi-ear-fill::before { content: "\f688"; } +.bi-ear::before { content: "\f689"; } +.bi-envelope-check-1::before { content: "\f68a"; } +.bi-envelope-check-fill::before { content: "\f68b"; } +.bi-envelope-check::before { content: "\f68c"; } +.bi-envelope-dash-1::before { content: "\f68d"; } +.bi-envelope-dash-fill::before { content: "\f68e"; } +.bi-envelope-dash::before { content: "\f68f"; } +.bi-envelope-exclamation-1::before { content: "\f690"; } +.bi-envelope-exclamation-fill::before { content: "\f691"; } +.bi-envelope-exclamation::before { content: "\f692"; } +.bi-envelope-plus-fill::before { content: "\f693"; } +.bi-envelope-plus::before { content: "\f694"; } +.bi-envelope-slash-1::before { content: "\f695"; } +.bi-envelope-slash-fill::before { content: "\f696"; } +.bi-envelope-slash::before { content: "\f697"; } +.bi-envelope-x-1::before { content: "\f698"; } +.bi-envelope-x-fill::before { content: "\f699"; } +.bi-envelope-x::before { content: "\f69a"; } +.bi-explicit-fill::before { content: "\f69b"; } +.bi-explicit::before { content: "\f69c"; } +.bi-git::before { content: "\f69d"; } +.bi-infinity::before { content: "\f69e"; } +.bi-list-columns-reverse::before { content: "\f69f"; } +.bi-list-columns::before { content: "\f6a0"; } +.bi-meta::before { content: "\f6a1"; } +.bi-mortorboard-fill::before { content: "\f6a2"; } +.bi-mortorboard::before { content: "\f6a3"; } +.bi-nintendo-switch::before { content: "\f6a4"; } +.bi-pc-display-horizontal::before { content: "\f6a5"; } +.bi-pc-display::before { content: "\f6a6"; } +.bi-pc-horizontal::before { content: "\f6a7"; } +.bi-pc::before { content: "\f6a8"; } +.bi-playstation::before { content: "\f6a9"; } +.bi-plus-slash-minus::before { content: "\f6aa"; } +.bi-projector-fill::before { content: "\f6ab"; } +.bi-projector::before { content: "\f6ac"; } +.bi-qr-code-scan::before { content: "\f6ad"; } +.bi-qr-code::before { content: "\f6ae"; } +.bi-quora::before { content: "\f6af"; } +.bi-quote::before { content: "\f6b0"; } +.bi-robot::before { content: "\f6b1"; } +.bi-send-check-fill::before { content: "\f6b2"; } +.bi-send-check::before { content: "\f6b3"; } +.bi-send-dash-fill::before { content: "\f6b4"; } +.bi-send-dash::before { content: "\f6b5"; } +.bi-send-exclamation-1::before { content: "\f6b6"; } +.bi-send-exclamation-fill::before { content: "\f6b7"; } +.bi-send-exclamation::before { content: "\f6b8"; } +.bi-send-fill::before { content: "\f6b9"; } +.bi-send-plus-fill::before { content: "\f6ba"; } +.bi-send-plus::before { content: "\f6bb"; } +.bi-send-slash-fill::before { content: "\f6bc"; } +.bi-send-slash::before { content: "\f6bd"; } +.bi-send-x-fill::before { content: "\f6be"; } +.bi-send-x::before { content: "\f6bf"; } +.bi-send::before { content: "\f6c0"; } +.bi-steam::before { content: "\f6c1"; } +.bi-terminal-dash-1::before { content: "\f6c2"; } +.bi-terminal-dash::before { content: "\f6c3"; } +.bi-terminal-plus::before { content: "\f6c4"; } +.bi-terminal-split::before { content: "\f6c5"; } +.bi-ticket-detailed-fill::before { content: "\f6c6"; } +.bi-ticket-detailed::before { content: "\f6c7"; } +.bi-ticket-fill::before { content: "\f6c8"; } +.bi-ticket-perforated-fill::before { content: "\f6c9"; } +.bi-ticket-perforated::before { content: "\f6ca"; } +.bi-ticket::before { content: "\f6cb"; } +.bi-tiktok::before { content: "\f6cc"; } +.bi-window-dash::before { content: "\f6cd"; } +.bi-window-desktop::before { content: "\f6ce"; } +.bi-window-fullscreen::before { content: "\f6cf"; } +.bi-window-plus::before { content: "\f6d0"; } +.bi-window-split::before { content: "\f6d1"; } +.bi-window-stack::before { content: "\f6d2"; } +.bi-window-x::before { content: "\f6d3"; } +.bi-xbox::before { content: "\f6d4"; } +.bi-ethernet::before { content: "\f6d5"; } +.bi-hdmi-fill::before { content: "\f6d6"; } +.bi-hdmi::before { content: "\f6d7"; } +.bi-usb-c-fill::before { content: "\f6d8"; } +.bi-usb-c::before { content: "\f6d9"; } +.bi-usb-fill::before { content: "\f6da"; } +.bi-usb-plug-fill::before { content: "\f6db"; } +.bi-usb-plug::before { content: "\f6dc"; } +.bi-usb-symbol::before { content: "\f6dd"; } +.bi-usb::before { content: "\f6de"; } +.bi-boombox-fill::before { content: "\f6df"; } +.bi-displayport-1::before { content: "\f6e0"; } +.bi-displayport::before { content: "\f6e1"; } +.bi-gpu-card::before { content: "\f6e2"; } +.bi-memory::before { content: "\f6e3"; } +.bi-modem-fill::before { content: "\f6e4"; } +.bi-modem::before { content: "\f6e5"; } +.bi-motherboard-fill::before { content: "\f6e6"; } +.bi-motherboard::before { content: "\f6e7"; } +.bi-optical-audio-fill::before { content: "\f6e8"; } +.bi-optical-audio::before { content: "\f6e9"; } +.bi-pci-card::before { content: "\f6ea"; } +.bi-router-fill::before { content: "\f6eb"; } +.bi-router::before { content: "\f6ec"; } +.bi-ssd-fill::before { content: "\f6ed"; } +.bi-ssd::before { content: "\f6ee"; } +.bi-thunderbolt-fill::before { content: "\f6ef"; } +.bi-thunderbolt::before { content: "\f6f0"; } +.bi-usb-drive-fill::before { content: "\f6f1"; } +.bi-usb-drive::before { content: "\f6f2"; } +.bi-usb-micro-fill::before { content: "\f6f3"; } +.bi-usb-micro::before { content: "\f6f4"; } +.bi-usb-mini-fill::before { content: "\f6f5"; } +.bi-usb-mini::before { content: "\f6f6"; } +.bi-cloud-haze2::before { content: "\f6f7"; } +.bi-device-hdd-fill::before { content: "\f6f8"; } +.bi-device-hdd::before { content: "\f6f9"; } +.bi-device-ssd-fill::before { content: "\f6fa"; } +.bi-device-ssd::before { content: "\f6fb"; } +.bi-displayport-fill::before { content: "\f6fc"; } +.bi-mortarboard-fill::before { content: "\f6fd"; } +.bi-mortarboard::before { content: "\f6fe"; } +.bi-terminal-x::before { content: "\f6ff"; } +.bi-arrow-through-heart-fill::before { content: "\f700"; } +.bi-arrow-through-heart::before { content: "\f701"; } +.bi-badge-sd-fill::before { content: "\f702"; } +.bi-badge-sd::before { content: "\f703"; } +.bi-bag-heart-fill::before { content: "\f704"; } +.bi-bag-heart::before { content: "\f705"; } +.bi-balloon-fill::before { content: "\f706"; } +.bi-balloon-heart-fill::before { content: "\f707"; } +.bi-balloon-heart::before { content: "\f708"; } +.bi-balloon::before { content: "\f709"; } +.bi-box2-fill::before { content: "\f70a"; } +.bi-box2-heart-fill::before { content: "\f70b"; } +.bi-box2-heart::before { content: "\f70c"; } +.bi-box2::before { content: "\f70d"; } +.bi-braces-asterisk::before { content: "\f70e"; } +.bi-calendar-heart-fill::before { content: "\f70f"; } +.bi-calendar-heart::before { content: "\f710"; } +.bi-calendar2-heart-fill::before { content: "\f711"; } +.bi-calendar2-heart::before { content: "\f712"; } +.bi-chat-heart-fill::before { content: "\f713"; } +.bi-chat-heart::before { content: "\f714"; } +.bi-chat-left-heart-fill::before { content: "\f715"; } +.bi-chat-left-heart::before { content: "\f716"; } +.bi-chat-right-heart-fill::before { content: "\f717"; } +.bi-chat-right-heart::before { content: "\f718"; } +.bi-chat-square-heart-fill::before { content: "\f719"; } +.bi-chat-square-heart::before { content: "\f71a"; } +.bi-clipboard-check-fill::before { content: "\f71b"; } +.bi-clipboard-data-fill::before { content: "\f71c"; } +.bi-clipboard-fill::before { content: "\f71d"; } +.bi-clipboard-heart-fill::before { content: "\f71e"; } +.bi-clipboard-heart::before { content: "\f71f"; } +.bi-clipboard-minus-fill::before { content: "\f720"; } +.bi-clipboard-plus-fill::before { content: "\f721"; } +.bi-clipboard-pulse::before { content: "\f722"; } +.bi-clipboard-x-fill::before { content: "\f723"; } +.bi-clipboard2-check-fill::before { content: "\f724"; } +.bi-clipboard2-check::before { content: "\f725"; } +.bi-clipboard2-data-fill::before { content: "\f726"; } +.bi-clipboard2-data::before { content: "\f727"; } +.bi-clipboard2-fill::before { content: "\f728"; } +.bi-clipboard2-heart-fill::before { content: "\f729"; } +.bi-clipboard2-heart::before { content: "\f72a"; } +.bi-clipboard2-minus-fill::before { content: "\f72b"; } +.bi-clipboard2-minus::before { content: "\f72c"; } +.bi-clipboard2-plus-fill::before { content: "\f72d"; } +.bi-clipboard2-plus::before { content: "\f72e"; } +.bi-clipboard2-pulse-fill::before { content: "\f72f"; } +.bi-clipboard2-pulse::before { content: "\f730"; } +.bi-clipboard2-x-fill::before { content: "\f731"; } +.bi-clipboard2-x::before { content: "\f732"; } +.bi-clipboard2::before { content: "\f733"; } +.bi-emoji-kiss-fill::before { content: "\f734"; } +.bi-emoji-kiss::before { content: "\f735"; } +.bi-envelope-heart-fill::before { content: "\f736"; } +.bi-envelope-heart::before { content: "\f737"; } +.bi-envelope-open-heart-fill::before { content: "\f738"; } +.bi-envelope-open-heart::before { content: "\f739"; } +.bi-envelope-paper-fill::before { content: "\f73a"; } +.bi-envelope-paper-heart-fill::before { content: "\f73b"; } +.bi-envelope-paper-heart::before { content: "\f73c"; } +.bi-envelope-paper::before { content: "\f73d"; } +.bi-filetype-aac::before { content: "\f73e"; } +.bi-filetype-ai::before { content: "\f73f"; } +.bi-filetype-bmp::before { content: "\f740"; } +.bi-filetype-cs::before { content: "\f741"; } +.bi-filetype-css::before { content: "\f742"; } +.bi-filetype-csv::before { content: "\f743"; } +.bi-filetype-doc::before { content: "\f744"; } +.bi-filetype-docx::before { content: "\f745"; } +.bi-filetype-exe::before { content: "\f746"; } +.bi-filetype-gif::before { content: "\f747"; } +.bi-filetype-heic::before { content: "\f748"; } +.bi-filetype-html::before { content: "\f749"; } +.bi-filetype-java::before { content: "\f74a"; } +.bi-filetype-jpg::before { content: "\f74b"; } +.bi-filetype-js::before { content: "\f74c"; } +.bi-filetype-jsx::before { content: "\f74d"; } +.bi-filetype-key::before { content: "\f74e"; } +.bi-filetype-m4p::before { content: "\f74f"; } +.bi-filetype-md::before { content: "\f750"; } +.bi-filetype-mdx::before { content: "\f751"; } +.bi-filetype-mov::before { content: "\f752"; } +.bi-filetype-mp3::before { content: "\f753"; } +.bi-filetype-mp4::before { content: "\f754"; } +.bi-filetype-otf::before { content: "\f755"; } +.bi-filetype-pdf::before { content: "\f756"; } +.bi-filetype-php::before { content: "\f757"; } +.bi-filetype-png::before { content: "\f758"; } +.bi-filetype-ppt-1::before { content: "\f759"; } +.bi-filetype-ppt::before { content: "\f75a"; } +.bi-filetype-psd::before { content: "\f75b"; } +.bi-filetype-py::before { content: "\f75c"; } +.bi-filetype-raw::before { content: "\f75d"; } +.bi-filetype-rb::before { content: "\f75e"; } +.bi-filetype-sass::before { content: "\f75f"; } +.bi-filetype-scss::before { content: "\f760"; } +.bi-filetype-sh::before { content: "\f761"; } +.bi-filetype-svg::before { content: "\f762"; } +.bi-filetype-tiff::before { content: "\f763"; } +.bi-filetype-tsx::before { content: "\f764"; } +.bi-filetype-ttf::before { content: "\f765"; } +.bi-filetype-txt::before { content: "\f766"; } +.bi-filetype-wav::before { content: "\f767"; } +.bi-filetype-woff::before { content: "\f768"; } +.bi-filetype-xls-1::before { content: "\f769"; } +.bi-filetype-xls::before { content: "\f76a"; } +.bi-filetype-xml::before { content: "\f76b"; } +.bi-filetype-yml::before { content: "\f76c"; } +.bi-heart-arrow::before { content: "\f76d"; } +.bi-heart-pulse-fill::before { content: "\f76e"; } +.bi-heart-pulse::before { content: "\f76f"; } +.bi-heartbreak-fill::before { content: "\f770"; } +.bi-heartbreak::before { content: "\f771"; } +.bi-hearts::before { content: "\f772"; } +.bi-hospital-fill::before { content: "\f773"; } +.bi-hospital::before { content: "\f774"; } +.bi-house-heart-fill::before { content: "\f775"; } +.bi-house-heart::before { content: "\f776"; } +.bi-incognito::before { content: "\f777"; } +.bi-magnet-fill::before { content: "\f778"; } +.bi-magnet::before { content: "\f779"; } +.bi-person-heart::before { content: "\f77a"; } +.bi-person-hearts::before { content: "\f77b"; } +.bi-phone-flip::before { content: "\f77c"; } +.bi-plugin::before { content: "\f77d"; } +.bi-postage-fill::before { content: "\f77e"; } +.bi-postage-heart-fill::before { content: "\f77f"; } +.bi-postage-heart::before { content: "\f780"; } +.bi-postage::before { content: "\f781"; } +.bi-postcard-fill::before { content: "\f782"; } +.bi-postcard-heart-fill::before { content: "\f783"; } +.bi-postcard-heart::before { content: "\f784"; } +.bi-postcard::before { content: "\f785"; } +.bi-search-heart-fill::before { content: "\f786"; } +.bi-search-heart::before { content: "\f787"; } +.bi-sliders2-vertical::before { content: "\f788"; } +.bi-sliders2::before { content: "\f789"; } +.bi-trash3-fill::before { content: "\f78a"; } +.bi-trash3::before { content: "\f78b"; } +.bi-valentine::before { content: "\f78c"; } +.bi-valentine2::before { content: "\f78d"; } +.bi-wrench-adjustable-circle-fill::before { content: "\f78e"; } +.bi-wrench-adjustable-circle::before { content: "\f78f"; } +.bi-wrench-adjustable::before { content: "\f790"; } +.bi-filetype-json::before { content: "\f791"; } +.bi-filetype-pptx::before { content: "\f792"; } +.bi-filetype-xlsx::before { content: "\f793"; } diff --git a/doc/site_libs/bootstrap/bootstrap-icons.woff b/doc/site_libs/bootstrap/bootstrap-icons.woff new file mode 100644 index 000000000..b26ccd1ac Binary files /dev/null and b/doc/site_libs/bootstrap/bootstrap-icons.woff differ diff --git a/doc/site_libs/bootstrap/bootstrap.min.css b/doc/site_libs/bootstrap/bootstrap.min.css new file mode 100644 index 000000000..8a34fddf7 --- /dev/null +++ b/doc/site_libs/bootstrap/bootstrap.min.css @@ -0,0 +1,10 @@ +/*! + * Bootstrap v5.1.3 (https://getbootstrap.com/) + * Copyright 2011-2021 The Bootstrap Authors + * Copyright 2011-2021 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */@import"https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@300;400;700&display=swap";:root{--bs-blue: #2780e3;--bs-indigo: #6610f2;--bs-purple: #613d7c;--bs-pink: #e83e8c;--bs-red: #ff0039;--bs-orange: #f0ad4e;--bs-yellow: #ff7518;--bs-green: #3fb618;--bs-teal: #20c997;--bs-cyan: #9954bb;--bs-white: #fff;--bs-gray: #6c757d;--bs-gray-dark: #373a3c;--bs-gray-100: #f8f9fa;--bs-gray-200: #e9ecef;--bs-gray-300: #dee2e6;--bs-gray-400: #ced4da;--bs-gray-500: #adb5bd;--bs-gray-600: #6c757d;--bs-gray-700: #495057;--bs-gray-800: #373a3c;--bs-gray-900: #212529;--bs-default: #373a3c;--bs-primary: #2780e3;--bs-secondary: #373a3c;--bs-success: #3fb618;--bs-info: #9954bb;--bs-warning: #ff7518;--bs-danger: #ff0039;--bs-light: #f8f9fa;--bs-dark: #373a3c;--bs-default-rgb: 55, 58, 60;--bs-primary-rgb: 39, 128, 227;--bs-secondary-rgb: 55, 58, 60;--bs-success-rgb: 63, 182, 24;--bs-info-rgb: 153, 84, 187;--bs-warning-rgb: 255, 117, 24;--bs-danger-rgb: 255, 0, 57;--bs-light-rgb: 248, 249, 250;--bs-dark-rgb: 55, 58, 60;--bs-white-rgb: 255, 255, 255;--bs-black-rgb: 0, 0, 0;--bs-body-color-rgb: 55, 58, 60;--bs-body-bg-rgb: 255, 255, 255;--bs-font-sans-serif: "Source Sans Pro", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-root-font-size: 18px;--bs-body-font-family: var(--bs-font-sans-serif);--bs-body-font-size: 1rem;--bs-body-font-weight: 400;--bs-body-line-height: 1.5;--bs-body-color: #373a3c;--bs-body-bg: #fff}*,*::before,*::after{box-sizing:border-box}:root{font-size:var(--bs-root-font-size)}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}h6,.h6,h5,.h5,h4,.h4,h3,.h3,h2,.h2,h1,.h1{margin-top:0;margin-bottom:.5rem;font-weight:400;line-height:1.2}h1,.h1{font-size:calc(1.345rem + 1.14vw)}@media(min-width: 1200px){h1,.h1{font-size:2.2rem}}h2,.h2{font-size:calc(1.3rem + 0.6vw)}@media(min-width: 1200px){h2,.h2{font-size:1.75rem}}h3,.h3{font-size:calc(1.275rem + 0.3vw)}@media(min-width: 1200px){h3,.h3{font-size:1.5rem}}h4,.h4{font-size:1.25rem}h5,.h5{font-size:1.1rem}h6,.h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[title],abbr[data-bs-original-title]{text-decoration:underline dotted;-webkit-text-decoration:underline dotted;-moz-text-decoration:underline dotted;-ms-text-decoration:underline dotted;-o-text-decoration:underline dotted;cursor:help;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}ol,ul,dl{margin-top:0;margin-bottom:1rem}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem;padding:.625rem 1.25rem;border-left:.25rem solid #e9ecef}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}b,strong{font-weight:bolder}small,.small{font-size:0.875em}mark,.mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:0.75em;line-height:0;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}a{color:#2780e3;text-decoration:underline;-webkit-text-decoration:underline;-moz-text-decoration:underline;-ms-text-decoration:underline;-o-text-decoration:underline}a:hover{color:#1f66b6}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}pre,code,kbd,samp{font-family:var(--bs-font-monospace);font-size:1em;direction:ltr /* rtl:ignore */;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:0.875em;color:#000;background-color:#f7f7f7;padding:.5rem;border:1px solid #dee2e6}pre code{background-color:transparent;font-size:inherit;color:inherit;word-break:normal}code{font-size:0.875em;color:#9753b8;background-color:#f7f7f7;padding:.125rem .25rem;word-wrap:break-word}a>code{color:inherit}kbd{padding:.4rem .4rem;font-size:0.875em;color:#fff;background-color:#212529}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}thead,tbody,tfoot,tr,td,th{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}input,button,select,optgroup,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button:not(:disabled),[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + 0.3vw);line-height:inherit}@media(min-width: 1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-text,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none !important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:0.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:0.875em;color:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:0.875em;color:#6c757d}.grid{display:grid;grid-template-rows:repeat(var(--bs-rows, 1), 1fr);grid-template-columns:repeat(var(--bs-columns, 12), 1fr);gap:var(--bs-gap, 1.5rem)}.grid .g-col-1{grid-column:auto/span 1}.grid .g-col-2{grid-column:auto/span 2}.grid .g-col-3{grid-column:auto/span 3}.grid .g-col-4{grid-column:auto/span 4}.grid .g-col-5{grid-column:auto/span 5}.grid .g-col-6{grid-column:auto/span 6}.grid .g-col-7{grid-column:auto/span 7}.grid .g-col-8{grid-column:auto/span 8}.grid .g-col-9{grid-column:auto/span 9}.grid .g-col-10{grid-column:auto/span 10}.grid .g-col-11{grid-column:auto/span 11}.grid .g-col-12{grid-column:auto/span 12}.grid .g-start-1{grid-column-start:1}.grid .g-start-2{grid-column-start:2}.grid .g-start-3{grid-column-start:3}.grid .g-start-4{grid-column-start:4}.grid .g-start-5{grid-column-start:5}.grid .g-start-6{grid-column-start:6}.grid .g-start-7{grid-column-start:7}.grid .g-start-8{grid-column-start:8}.grid .g-start-9{grid-column-start:9}.grid .g-start-10{grid-column-start:10}.grid .g-start-11{grid-column-start:11}@media(min-width: 576px){.grid .g-col-sm-1{grid-column:auto/span 1}.grid .g-col-sm-2{grid-column:auto/span 2}.grid .g-col-sm-3{grid-column:auto/span 3}.grid .g-col-sm-4{grid-column:auto/span 4}.grid .g-col-sm-5{grid-column:auto/span 5}.grid .g-col-sm-6{grid-column:auto/span 6}.grid .g-col-sm-7{grid-column:auto/span 7}.grid .g-col-sm-8{grid-column:auto/span 8}.grid .g-col-sm-9{grid-column:auto/span 9}.grid .g-col-sm-10{grid-column:auto/span 10}.grid .g-col-sm-11{grid-column:auto/span 11}.grid .g-col-sm-12{grid-column:auto/span 12}.grid .g-start-sm-1{grid-column-start:1}.grid .g-start-sm-2{grid-column-start:2}.grid .g-start-sm-3{grid-column-start:3}.grid .g-start-sm-4{grid-column-start:4}.grid .g-start-sm-5{grid-column-start:5}.grid .g-start-sm-6{grid-column-start:6}.grid .g-start-sm-7{grid-column-start:7}.grid .g-start-sm-8{grid-column-start:8}.grid .g-start-sm-9{grid-column-start:9}.grid .g-start-sm-10{grid-column-start:10}.grid .g-start-sm-11{grid-column-start:11}}@media(min-width: 768px){.grid .g-col-md-1{grid-column:auto/span 1}.grid .g-col-md-2{grid-column:auto/span 2}.grid .g-col-md-3{grid-column:auto/span 3}.grid .g-col-md-4{grid-column:auto/span 4}.grid .g-col-md-5{grid-column:auto/span 5}.grid .g-col-md-6{grid-column:auto/span 6}.grid .g-col-md-7{grid-column:auto/span 7}.grid .g-col-md-8{grid-column:auto/span 8}.grid .g-col-md-9{grid-column:auto/span 9}.grid .g-col-md-10{grid-column:auto/span 10}.grid .g-col-md-11{grid-column:auto/span 11}.grid .g-col-md-12{grid-column:auto/span 12}.grid .g-start-md-1{grid-column-start:1}.grid .g-start-md-2{grid-column-start:2}.grid .g-start-md-3{grid-column-start:3}.grid .g-start-md-4{grid-column-start:4}.grid .g-start-md-5{grid-column-start:5}.grid .g-start-md-6{grid-column-start:6}.grid .g-start-md-7{grid-column-start:7}.grid .g-start-md-8{grid-column-start:8}.grid .g-start-md-9{grid-column-start:9}.grid .g-start-md-10{grid-column-start:10}.grid .g-start-md-11{grid-column-start:11}}@media(min-width: 992px){.grid .g-col-lg-1{grid-column:auto/span 1}.grid .g-col-lg-2{grid-column:auto/span 2}.grid .g-col-lg-3{grid-column:auto/span 3}.grid .g-col-lg-4{grid-column:auto/span 4}.grid .g-col-lg-5{grid-column:auto/span 5}.grid .g-col-lg-6{grid-column:auto/span 6}.grid .g-col-lg-7{grid-column:auto/span 7}.grid .g-col-lg-8{grid-column:auto/span 8}.grid .g-col-lg-9{grid-column:auto/span 9}.grid .g-col-lg-10{grid-column:auto/span 10}.grid .g-col-lg-11{grid-column:auto/span 11}.grid .g-col-lg-12{grid-column:auto/span 12}.grid .g-start-lg-1{grid-column-start:1}.grid .g-start-lg-2{grid-column-start:2}.grid .g-start-lg-3{grid-column-start:3}.grid .g-start-lg-4{grid-column-start:4}.grid .g-start-lg-5{grid-column-start:5}.grid .g-start-lg-6{grid-column-start:6}.grid .g-start-lg-7{grid-column-start:7}.grid .g-start-lg-8{grid-column-start:8}.grid .g-start-lg-9{grid-column-start:9}.grid .g-start-lg-10{grid-column-start:10}.grid .g-start-lg-11{grid-column-start:11}}@media(min-width: 1200px){.grid .g-col-xl-1{grid-column:auto/span 1}.grid .g-col-xl-2{grid-column:auto/span 2}.grid .g-col-xl-3{grid-column:auto/span 3}.grid .g-col-xl-4{grid-column:auto/span 4}.grid .g-col-xl-5{grid-column:auto/span 5}.grid .g-col-xl-6{grid-column:auto/span 6}.grid .g-col-xl-7{grid-column:auto/span 7}.grid .g-col-xl-8{grid-column:auto/span 8}.grid .g-col-xl-9{grid-column:auto/span 9}.grid .g-col-xl-10{grid-column:auto/span 10}.grid .g-col-xl-11{grid-column:auto/span 11}.grid .g-col-xl-12{grid-column:auto/span 12}.grid .g-start-xl-1{grid-column-start:1}.grid .g-start-xl-2{grid-column-start:2}.grid .g-start-xl-3{grid-column-start:3}.grid .g-start-xl-4{grid-column-start:4}.grid .g-start-xl-5{grid-column-start:5}.grid .g-start-xl-6{grid-column-start:6}.grid .g-start-xl-7{grid-column-start:7}.grid .g-start-xl-8{grid-column-start:8}.grid .g-start-xl-9{grid-column-start:9}.grid .g-start-xl-10{grid-column-start:10}.grid .g-start-xl-11{grid-column-start:11}}@media(min-width: 1400px){.grid .g-col-xxl-1{grid-column:auto/span 1}.grid .g-col-xxl-2{grid-column:auto/span 2}.grid .g-col-xxl-3{grid-column:auto/span 3}.grid .g-col-xxl-4{grid-column:auto/span 4}.grid .g-col-xxl-5{grid-column:auto/span 5}.grid .g-col-xxl-6{grid-column:auto/span 6}.grid .g-col-xxl-7{grid-column:auto/span 7}.grid .g-col-xxl-8{grid-column:auto/span 8}.grid .g-col-xxl-9{grid-column:auto/span 9}.grid .g-col-xxl-10{grid-column:auto/span 10}.grid .g-col-xxl-11{grid-column:auto/span 11}.grid .g-col-xxl-12{grid-column:auto/span 12}.grid .g-start-xxl-1{grid-column-start:1}.grid .g-start-xxl-2{grid-column-start:2}.grid .g-start-xxl-3{grid-column-start:3}.grid .g-start-xxl-4{grid-column-start:4}.grid .g-start-xxl-5{grid-column-start:5}.grid .g-start-xxl-6{grid-column-start:6}.grid .g-start-xxl-7{grid-column-start:7}.grid .g-start-xxl-8{grid-column-start:8}.grid .g-start-xxl-9{grid-column-start:9}.grid .g-start-xxl-10{grid-column-start:10}.grid .g-start-xxl-11{grid-column-start:11}}.table{--bs-table-bg: transparent;--bs-table-accent-bg: transparent;--bs-table-striped-color: #373a3c;--bs-table-striped-bg: rgba(0, 0, 0, 0.05);--bs-table-active-color: #373a3c;--bs-table-active-bg: rgba(0, 0, 0, 0.1);--bs-table-hover-color: #373a3c;--bs-table-hover-bg: rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;color:#373a3c;vertical-align:top;border-color:#dee2e6}.table>:not(caption)>*>*{padding:.5rem .5rem;background-color:var(--bs-table-bg);border-bottom-width:1px;box-shadow:inset 0 0 0 9999px var(--bs-table-accent-bg)}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table>:not(:first-child){border-top:2px solid currentColor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:1px 0}.table-bordered>:not(caption)>*>*{border-width:0 1px}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-borderless>:not(:first-child){border-top-width:0}.table-striped>tbody>tr:nth-of-type(odd)>*{--bs-table-accent-bg: var(--bs-table-striped-bg);color:var(--bs-table-striped-color)}.table-active{--bs-table-accent-bg: var(--bs-table-active-bg);color:var(--bs-table-active-color)}.table-hover>tbody>tr:hover>*{--bs-table-accent-bg: var(--bs-table-hover-bg);color:var(--bs-table-hover-color)}.table-primary{--bs-table-bg: #d4e6f9;--bs-table-striped-bg: #c9dbed;--bs-table-striped-color: #000;--bs-table-active-bg: #bfcfe0;--bs-table-active-color: #000;--bs-table-hover-bg: #c4d5e6;--bs-table-hover-color: #000;color:#000;border-color:#bfcfe0}.table-secondary{--bs-table-bg: #d7d8d8;--bs-table-striped-bg: #cccdcd;--bs-table-striped-color: #000;--bs-table-active-bg: #c2c2c2;--bs-table-active-color: #000;--bs-table-hover-bg: #c7c8c8;--bs-table-hover-color: #000;color:#000;border-color:#c2c2c2}.table-success{--bs-table-bg: #d9f0d1;--bs-table-striped-bg: #cee4c7;--bs-table-striped-color: #000;--bs-table-active-bg: #c3d8bc;--bs-table-active-color: #000;--bs-table-hover-bg: #c9dec1;--bs-table-hover-color: #000;color:#000;border-color:#c3d8bc}.table-info{--bs-table-bg: #ebddf1;--bs-table-striped-bg: #dfd2e5;--bs-table-striped-color: #000;--bs-table-active-bg: #d4c7d9;--bs-table-active-color: #000;--bs-table-hover-bg: #d9ccdf;--bs-table-hover-color: #000;color:#000;border-color:#d4c7d9}.table-warning{--bs-table-bg: #ffe3d1;--bs-table-striped-bg: #f2d8c7;--bs-table-striped-color: #000;--bs-table-active-bg: #e6ccbc;--bs-table-active-color: #000;--bs-table-hover-bg: #ecd2c1;--bs-table-hover-color: #000;color:#000;border-color:#e6ccbc}.table-danger{--bs-table-bg: #ffccd7;--bs-table-striped-bg: #f2c2cc;--bs-table-striped-color: #000;--bs-table-active-bg: #e6b8c2;--bs-table-active-color: #000;--bs-table-hover-bg: #ecbdc7;--bs-table-hover-color: #000;color:#000;border-color:#e6b8c2}.table-light{--bs-table-bg: #f8f9fa;--bs-table-striped-bg: #ecedee;--bs-table-striped-color: #000;--bs-table-active-bg: #dfe0e1;--bs-table-active-color: #000;--bs-table-hover-bg: #e5e6e7;--bs-table-hover-color: #000;color:#000;border-color:#dfe0e1}.table-dark{--bs-table-bg: #373a3c;--bs-table-striped-bg: #414446;--bs-table-striped-color: #fff;--bs-table-active-bg: #4b4e50;--bs-table-active-color: #fff;--bs-table-hover-bg: #46494b;--bs-table-hover-color: #fff;color:#fff;border-color:#4b4e50}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media(max-width: 575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label,.shiny-input-container .control-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(0.375rem + 1px);padding-bottom:calc(0.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(0.5rem + 1px);padding-bottom:calc(0.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(0.25rem + 1px);padding-bottom:calc(0.25rem + 1px);font-size:0.875rem}.form-text{margin-top:.25rem;font-size:0.875em;color:#6c757d}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#373a3c;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;border-radius:0;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:#373a3c;background-color:#fff;border-color:#93c0f1;outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.25)}.form-control::-webkit-date-and-time-value{height:1.5em}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}.form-control::file-selector-button{padding:.375rem .75rem;margin:-0.375rem -0.75rem;margin-inline-end:.75rem;color:#373a3c;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:#dde0e3}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-0.375rem -0.75rem;margin-inline-end:.75rem;color:#373a3c;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-control::-webkit-file-upload-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#dde0e3}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#373a3c;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-sm,.form-control-plaintext.form-control-lg{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + 0.5rem + 2px);padding:.25rem .5rem;font-size:0.875rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-0.25rem -0.5rem;margin-inline-end:.5rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-0.25rem -0.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-0.5rem -1rem;margin-inline-end:1rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-0.5rem -1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + 0.75rem + 2px)}textarea.form-control-sm{min-height:calc(1.5em + 0.5rem + 2px)}textarea.form-control-lg{min-height:calc(1.5em + 1rem + 2px)}.form-control-color{width:3rem;height:auto;padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{height:1.5em}.form-control-color::-webkit-color-swatch{height:1.5em}.form-select{display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;-moz-padding-start:calc(0.75rem - 3px);font-size:1rem;font-weight:400;line-height:1.5;color:#373a3c;background-color:#fff;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23373a3c' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:1px solid #ced4da;border-radius:0;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none}@media(prefers-reduced-motion: reduce){.form-select{transition:none}}.form-select:focus{border-color:#93c0f1;outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:#e9ecef}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #373a3c}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:0.875rem}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.form-check,.shiny-input-container .checkbox,.shiny-input-container .radio{display:block;min-height:1.5rem;padding-left:0;margin-bottom:.125rem}.form-check .form-check-input,.form-check .shiny-input-container .checkbox input,.form-check .shiny-input-container .radio input,.shiny-input-container .checkbox .form-check-input,.shiny-input-container .checkbox .shiny-input-container .checkbox input,.shiny-input-container .checkbox .shiny-input-container .radio input,.shiny-input-container .radio .form-check-input,.shiny-input-container .radio .shiny-input-container .checkbox input,.shiny-input-container .radio .shiny-input-container .radio input{float:left;margin-left:0}.form-check-input,.shiny-input-container .checkbox input,.shiny-input-container .checkbox-inline input,.shiny-input-container .radio input,.shiny-input-container .radio-inline input{width:1em;height:1em;margin-top:.25em;vertical-align:top;background-color:#fff;background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid rgba(0,0,0,.25);appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;color-adjust:exact;-webkit-print-color-adjust:exact}.form-check-input[type=radio],.shiny-input-container .checkbox input[type=radio],.shiny-input-container .checkbox-inline input[type=radio],.shiny-input-container .radio input[type=radio],.shiny-input-container .radio-inline input[type=radio]{border-radius:50%}.form-check-input:active,.shiny-input-container .checkbox input:active,.shiny-input-container .checkbox-inline input:active,.shiny-input-container .radio input:active,.shiny-input-container .radio-inline input:active{filter:brightness(90%)}.form-check-input:focus,.shiny-input-container .checkbox input:focus,.shiny-input-container .checkbox-inline input:focus,.shiny-input-container .radio input:focus,.shiny-input-container .radio-inline input:focus{border-color:#93c0f1;outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.25)}.form-check-input:checked,.shiny-input-container .checkbox input:checked,.shiny-input-container .checkbox-inline input:checked,.shiny-input-container .radio input:checked,.shiny-input-container .radio-inline input:checked{background-color:#2780e3;border-color:#2780e3}.form-check-input:checked[type=checkbox],.shiny-input-container .checkbox input:checked[type=checkbox],.shiny-input-container .checkbox-inline input:checked[type=checkbox],.shiny-input-container .radio input:checked[type=checkbox],.shiny-input-container .radio-inline input:checked[type=checkbox]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio],.shiny-input-container .checkbox input:checked[type=radio],.shiny-input-container .checkbox-inline input:checked[type=radio],.shiny-input-container .radio input:checked[type=radio],.shiny-input-container .radio-inline input:checked[type=radio]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate,.shiny-input-container .checkbox input[type=checkbox]:indeterminate,.shiny-input-container .checkbox-inline input[type=checkbox]:indeterminate,.shiny-input-container .radio input[type=checkbox]:indeterminate,.shiny-input-container .radio-inline input[type=checkbox]:indeterminate{background-color:#2780e3;border-color:#2780e3;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled,.shiny-input-container .checkbox input:disabled,.shiny-input-container .checkbox-inline input:disabled,.shiny-input-container .radio input:disabled,.shiny-input-container .radio-inline input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input[disabled]~.form-check-label,.form-check-input[disabled]~span,.form-check-input:disabled~.form-check-label,.form-check-input:disabled~span,.shiny-input-container .checkbox input[disabled]~.form-check-label,.shiny-input-container .checkbox input[disabled]~span,.shiny-input-container .checkbox input:disabled~.form-check-label,.shiny-input-container .checkbox input:disabled~span,.shiny-input-container .checkbox-inline input[disabled]~.form-check-label,.shiny-input-container .checkbox-inline input[disabled]~span,.shiny-input-container .checkbox-inline input:disabled~.form-check-label,.shiny-input-container .checkbox-inline input:disabled~span,.shiny-input-container .radio input[disabled]~.form-check-label,.shiny-input-container .radio input[disabled]~span,.shiny-input-container .radio input:disabled~.form-check-label,.shiny-input-container .radio input:disabled~span,.shiny-input-container .radio-inline input[disabled]~.form-check-label,.shiny-input-container .radio-inline input[disabled]~span,.shiny-input-container .radio-inline input:disabled~.form-check-label,.shiny-input-container .radio-inline input:disabled~span{opacity:.5}.form-check-label,.shiny-input-container .checkbox label,.shiny-input-container .checkbox-inline label,.shiny-input-container .radio label,.shiny-input-container .radio-inline label{cursor:pointer}.form-switch{padding-left:2.5em}.form-switch .form-check-input{width:2em;margin-left:-2.5em;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");background-position:left center;transition:background-position .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2393c0f1'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-check-inline,.shiny-input-container .checkbox-inline,.shiny-input-container .radio-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.btn-check[disabled]+.btn,.btn-check:disabled+.btn{pointer-events:none;filter:none;opacity:.65}.form-range{width:100%;height:1.5rem;padding:0;background-color:transparent;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(39,128,227,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(39,128,227,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-0.25rem;background-color:#2780e3;border:0;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none}@media(prefers-reduced-motion: reduce){.form-range::-webkit-slider-thumb{transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#bed9f7}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent}.form-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#2780e3;border:0;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none}@media(prefers-reduced-motion: reduce){.form-range::-moz-range-thumb{transition:none}}.form-range::-moz-range-thumb:active{background-color:#bed9f7}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.form-range:disabled::-moz-range-thumb{background-color:#adb5bd}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-select{height:calc(3.5rem + 2px);line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;pointer-events:none;border:1px solid transparent;transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media(prefers-reduced-motion: reduce){.form-floating>label{transition:none}}.form-floating>.form-control{padding:1rem .75rem}.form-floating>.form-control::placeholder{color:transparent}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{opacity:.65;transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.form-floating>.form-control:-webkit-autofill~label{opacity:.65;transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.input-group{position:relative;display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;align-items:stretch;-webkit-align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select{position:relative;flex:1 1 auto;-webkit-flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus{z-index:3}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:3}.input-group-text{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#373a3c;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da}.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text,.input-group-lg>.btn{padding:.5rem 1rem;font-size:1.25rem}.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text,.input-group-sm>.btn{padding:.25rem .5rem;font-size:0.875rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:-1px}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:0.875em;color:#3fb618}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:0.875rem;color:#fff;background-color:rgba(63,182,24,.9)}.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip,.is-valid~.valid-feedback,.is-valid~.valid-tooltip{display:block}.was-validated .form-control:valid,.form-control.is-valid{border-color:#3fb618;padding-right:calc(1.5em + 0.75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%233fb618' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(0.375em + 0.1875rem) center;background-size:calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-control:valid:focus,.form-control.is-valid:focus{border-color:#3fb618;box-shadow:0 0 0 .25rem rgba(63,182,24,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + 0.75rem);background-position:top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem)}.was-validated .form-select:valid,.form-select.is-valid{border-color:#3fb618}.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"],.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23373a3c' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%233fb618' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-select:valid:focus,.form-select.is-valid:focus{border-color:#3fb618;box-shadow:0 0 0 .25rem rgba(63,182,24,.25)}.was-validated .form-check-input:valid,.form-check-input.is-valid{border-color:#3fb618}.was-validated .form-check-input:valid:checked,.form-check-input.is-valid:checked{background-color:#3fb618}.was-validated .form-check-input:valid:focus,.form-check-input.is-valid:focus{box-shadow:0 0 0 .25rem rgba(63,182,24,.25)}.was-validated .form-check-input:valid~.form-check-label,.form-check-input.is-valid~.form-check-label{color:#3fb618}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.was-validated .input-group .form-control:valid,.input-group .form-control.is-valid,.was-validated .input-group .form-select:valid,.input-group .form-select.is-valid{z-index:1}.was-validated .input-group .form-control:valid:focus,.input-group .form-control.is-valid:focus,.was-validated .input-group .form-select:valid:focus,.input-group .form-select.is-valid:focus{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:0.875em;color:#ff0039}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:0.875rem;color:#fff;background-color:rgba(255,0,57,.9)}.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip,.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip{display:block}.was-validated .form-control:invalid,.form-control.is-invalid{border-color:#ff0039;padding-right:calc(1.5em + 0.75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23ff0039'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23ff0039' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(0.375em + 0.1875rem) center;background-size:calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-control:invalid:focus,.form-control.is-invalid:focus{border-color:#ff0039;box-shadow:0 0 0 .25rem rgba(255,0,57,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + 0.75rem);background-position:top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem)}.was-validated .form-select:invalid,.form-select.is-invalid{border-color:#ff0039}.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"],.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23373a3c' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23ff0039'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23ff0039' stroke='none'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-select:invalid:focus,.form-select.is-invalid:focus{border-color:#ff0039;box-shadow:0 0 0 .25rem rgba(255,0,57,.25)}.was-validated .form-check-input:invalid,.form-check-input.is-invalid{border-color:#ff0039}.was-validated .form-check-input:invalid:checked,.form-check-input.is-invalid:checked{background-color:#ff0039}.was-validated .form-check-input:invalid:focus,.form-check-input.is-invalid:focus{box-shadow:0 0 0 .25rem rgba(255,0,57,.25)}.was-validated .form-check-input:invalid~.form-check-label,.form-check-input.is-invalid~.form-check-label{color:#ff0039}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.was-validated .input-group .form-control:invalid,.input-group .form-control.is-invalid,.was-validated .input-group .form-select:invalid,.input-group .form-select.is-invalid{z-index:2}.was-validated .input-group .form-control:invalid:focus,.input-group .form-control.is-invalid:focus,.was-validated .input-group .form-select:invalid:focus,.input-group .form-select.is-invalid:focus{z-index:3}.btn{display:inline-block;font-weight:400;line-height:1.5;color:#373a3c;text-align:center;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;vertical-align:middle;cursor:pointer;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.btn{transition:none}}.btn:hover{color:#373a3c}.btn-check:focus+.btn,.btn:focus{outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.25)}.btn:disabled,.btn.disabled,fieldset:disabled .btn{pointer-events:none;opacity:.65}.btn-default{color:#fff;background-color:#373a3c;border-color:#373a3c}.btn-default:hover{color:#fff;background-color:#2f3133;border-color:#2c2e30}.btn-check:focus+.btn-default,.btn-default:focus{color:#fff;background-color:#2f3133;border-color:#2c2e30;box-shadow:0 0 0 .25rem rgba(85,88,89,.5)}.btn-check:checked+.btn-default,.btn-check:active+.btn-default,.btn-default:active,.btn-default.active,.show>.btn-default.dropdown-toggle{color:#fff;background-color:#2c2e30;border-color:#292c2d}.btn-check:checked+.btn-default:focus,.btn-check:active+.btn-default:focus,.btn-default:active:focus,.btn-default.active:focus,.show>.btn-default.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(85,88,89,.5)}.btn-default:disabled,.btn-default.disabled{color:#fff;background-color:#373a3c;border-color:#373a3c}.btn-primary{color:#fff;background-color:#2780e3;border-color:#2780e3}.btn-primary:hover{color:#fff;background-color:#216dc1;border-color:#1f66b6}.btn-check:focus+.btn-primary,.btn-primary:focus{color:#fff;background-color:#216dc1;border-color:#1f66b6;box-shadow:0 0 0 .25rem rgba(71,147,231,.5)}.btn-check:checked+.btn-primary,.btn-check:active+.btn-primary,.btn-primary:active,.btn-primary.active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#1f66b6;border-color:#1d60aa}.btn-check:checked+.btn-primary:focus,.btn-check:active+.btn-primary:focus,.btn-primary:active:focus,.btn-primary.active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(71,147,231,.5)}.btn-primary:disabled,.btn-primary.disabled{color:#fff;background-color:#2780e3;border-color:#2780e3}.btn-secondary{color:#fff;background-color:#373a3c;border-color:#373a3c}.btn-secondary:hover{color:#fff;background-color:#2f3133;border-color:#2c2e30}.btn-check:focus+.btn-secondary,.btn-secondary:focus{color:#fff;background-color:#2f3133;border-color:#2c2e30;box-shadow:0 0 0 .25rem rgba(85,88,89,.5)}.btn-check:checked+.btn-secondary,.btn-check:active+.btn-secondary,.btn-secondary:active,.btn-secondary.active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#2c2e30;border-color:#292c2d}.btn-check:checked+.btn-secondary:focus,.btn-check:active+.btn-secondary:focus,.btn-secondary:active:focus,.btn-secondary.active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(85,88,89,.5)}.btn-secondary:disabled,.btn-secondary.disabled{color:#fff;background-color:#373a3c;border-color:#373a3c}.btn-success{color:#fff;background-color:#3fb618;border-color:#3fb618}.btn-success:hover{color:#fff;background-color:#369b14;border-color:#329213}.btn-check:focus+.btn-success,.btn-success:focus{color:#fff;background-color:#369b14;border-color:#329213;box-shadow:0 0 0 .25rem rgba(92,193,59,.5)}.btn-check:checked+.btn-success,.btn-check:active+.btn-success,.btn-success:active,.btn-success.active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#329213;border-color:#2f8912}.btn-check:checked+.btn-success:focus,.btn-check:active+.btn-success:focus,.btn-success:active:focus,.btn-success.active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(92,193,59,.5)}.btn-success:disabled,.btn-success.disabled{color:#fff;background-color:#3fb618;border-color:#3fb618}.btn-info{color:#fff;background-color:#9954bb;border-color:#9954bb}.btn-info:hover{color:#fff;background-color:#82479f;border-color:#7a4396}.btn-check:focus+.btn-info,.btn-info:focus{color:#fff;background-color:#82479f;border-color:#7a4396;box-shadow:0 0 0 .25rem rgba(168,110,197,.5)}.btn-check:checked+.btn-info,.btn-check:active+.btn-info,.btn-info:active,.btn-info.active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#7a4396;border-color:#733f8c}.btn-check:checked+.btn-info:focus,.btn-check:active+.btn-info:focus,.btn-info:active:focus,.btn-info.active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(168,110,197,.5)}.btn-info:disabled,.btn-info.disabled{color:#fff;background-color:#9954bb;border-color:#9954bb}.btn-warning{color:#fff;background-color:#ff7518;border-color:#ff7518}.btn-warning:hover{color:#fff;background-color:#d96314;border-color:#cc5e13}.btn-check:focus+.btn-warning,.btn-warning:focus{color:#fff;background-color:#d96314;border-color:#cc5e13;box-shadow:0 0 0 .25rem rgba(255,138,59,.5)}.btn-check:checked+.btn-warning,.btn-check:active+.btn-warning,.btn-warning:active,.btn-warning.active,.show>.btn-warning.dropdown-toggle{color:#fff;background-color:#cc5e13;border-color:#bf5812}.btn-check:checked+.btn-warning:focus,.btn-check:active+.btn-warning:focus,.btn-warning:active:focus,.btn-warning.active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(255,138,59,.5)}.btn-warning:disabled,.btn-warning.disabled{color:#fff;background-color:#ff7518;border-color:#ff7518}.btn-danger{color:#fff;background-color:#ff0039;border-color:#ff0039}.btn-danger:hover{color:#fff;background-color:#d90030;border-color:#cc002e}.btn-check:focus+.btn-danger,.btn-danger:focus{color:#fff;background-color:#d90030;border-color:#cc002e;box-shadow:0 0 0 .25rem rgba(255,38,87,.5)}.btn-check:checked+.btn-danger,.btn-check:active+.btn-danger,.btn-danger:active,.btn-danger.active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#cc002e;border-color:#bf002b}.btn-check:checked+.btn-danger:focus,.btn-check:active+.btn-danger:focus,.btn-danger:active:focus,.btn-danger.active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(255,38,87,.5)}.btn-danger:disabled,.btn-danger.disabled{color:#fff;background-color:#ff0039;border-color:#ff0039}.btn-light{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:focus+.btn-light,.btn-light:focus{color:#000;background-color:#f9fafb;border-color:#f9fafb;box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-check:checked+.btn-light,.btn-check:active+.btn-light,.btn-light:active,.btn-light.active,.show>.btn-light.dropdown-toggle{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:checked+.btn-light:focus,.btn-check:active+.btn-light:focus,.btn-light:active:focus,.btn-light.active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-light:disabled,.btn-light.disabled{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-dark{color:#fff;background-color:#373a3c;border-color:#373a3c}.btn-dark:hover{color:#fff;background-color:#2f3133;border-color:#2c2e30}.btn-check:focus+.btn-dark,.btn-dark:focus{color:#fff;background-color:#2f3133;border-color:#2c2e30;box-shadow:0 0 0 .25rem rgba(85,88,89,.5)}.btn-check:checked+.btn-dark,.btn-check:active+.btn-dark,.btn-dark:active,.btn-dark.active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#2c2e30;border-color:#292c2d}.btn-check:checked+.btn-dark:focus,.btn-check:active+.btn-dark:focus,.btn-dark:active:focus,.btn-dark.active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(85,88,89,.5)}.btn-dark:disabled,.btn-dark.disabled{color:#fff;background-color:#373a3c;border-color:#373a3c}.btn-outline-default{color:#373a3c;border-color:#373a3c;background-color:transparent}.btn-outline-default:hover{color:#fff;background-color:#373a3c;border-color:#373a3c}.btn-check:focus+.btn-outline-default,.btn-outline-default:focus{box-shadow:0 0 0 .25rem rgba(55,58,60,.5)}.btn-check:checked+.btn-outline-default,.btn-check:active+.btn-outline-default,.btn-outline-default:active,.btn-outline-default.active,.btn-outline-default.dropdown-toggle.show{color:#fff;background-color:#373a3c;border-color:#373a3c}.btn-check:checked+.btn-outline-default:focus,.btn-check:active+.btn-outline-default:focus,.btn-outline-default:active:focus,.btn-outline-default.active:focus,.btn-outline-default.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(55,58,60,.5)}.btn-outline-default:disabled,.btn-outline-default.disabled{color:#373a3c;background-color:transparent}.btn-outline-primary{color:#2780e3;border-color:#2780e3;background-color:transparent}.btn-outline-primary:hover{color:#fff;background-color:#2780e3;border-color:#2780e3}.btn-check:focus+.btn-outline-primary,.btn-outline-primary:focus{box-shadow:0 0 0 .25rem rgba(39,128,227,.5)}.btn-check:checked+.btn-outline-primary,.btn-check:active+.btn-outline-primary,.btn-outline-primary:active,.btn-outline-primary.active,.btn-outline-primary.dropdown-toggle.show{color:#fff;background-color:#2780e3;border-color:#2780e3}.btn-check:checked+.btn-outline-primary:focus,.btn-check:active+.btn-outline-primary:focus,.btn-outline-primary:active:focus,.btn-outline-primary.active:focus,.btn-outline-primary.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(39,128,227,.5)}.btn-outline-primary:disabled,.btn-outline-primary.disabled{color:#2780e3;background-color:transparent}.btn-outline-secondary{color:#373a3c;border-color:#373a3c;background-color:transparent}.btn-outline-secondary:hover{color:#fff;background-color:#373a3c;border-color:#373a3c}.btn-check:focus+.btn-outline-secondary,.btn-outline-secondary:focus{box-shadow:0 0 0 .25rem rgba(55,58,60,.5)}.btn-check:checked+.btn-outline-secondary,.btn-check:active+.btn-outline-secondary,.btn-outline-secondary:active,.btn-outline-secondary.active,.btn-outline-secondary.dropdown-toggle.show{color:#fff;background-color:#373a3c;border-color:#373a3c}.btn-check:checked+.btn-outline-secondary:focus,.btn-check:active+.btn-outline-secondary:focus,.btn-outline-secondary:active:focus,.btn-outline-secondary.active:focus,.btn-outline-secondary.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(55,58,60,.5)}.btn-outline-secondary:disabled,.btn-outline-secondary.disabled{color:#373a3c;background-color:transparent}.btn-outline-success{color:#3fb618;border-color:#3fb618;background-color:transparent}.btn-outline-success:hover{color:#fff;background-color:#3fb618;border-color:#3fb618}.btn-check:focus+.btn-outline-success,.btn-outline-success:focus{box-shadow:0 0 0 .25rem rgba(63,182,24,.5)}.btn-check:checked+.btn-outline-success,.btn-check:active+.btn-outline-success,.btn-outline-success:active,.btn-outline-success.active,.btn-outline-success.dropdown-toggle.show{color:#fff;background-color:#3fb618;border-color:#3fb618}.btn-check:checked+.btn-outline-success:focus,.btn-check:active+.btn-outline-success:focus,.btn-outline-success:active:focus,.btn-outline-success.active:focus,.btn-outline-success.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(63,182,24,.5)}.btn-outline-success:disabled,.btn-outline-success.disabled{color:#3fb618;background-color:transparent}.btn-outline-info{color:#9954bb;border-color:#9954bb;background-color:transparent}.btn-outline-info:hover{color:#fff;background-color:#9954bb;border-color:#9954bb}.btn-check:focus+.btn-outline-info,.btn-outline-info:focus{box-shadow:0 0 0 .25rem rgba(153,84,187,.5)}.btn-check:checked+.btn-outline-info,.btn-check:active+.btn-outline-info,.btn-outline-info:active,.btn-outline-info.active,.btn-outline-info.dropdown-toggle.show{color:#fff;background-color:#9954bb;border-color:#9954bb}.btn-check:checked+.btn-outline-info:focus,.btn-check:active+.btn-outline-info:focus,.btn-outline-info:active:focus,.btn-outline-info.active:focus,.btn-outline-info.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(153,84,187,.5)}.btn-outline-info:disabled,.btn-outline-info.disabled{color:#9954bb;background-color:transparent}.btn-outline-warning{color:#ff7518;border-color:#ff7518;background-color:transparent}.btn-outline-warning:hover{color:#fff;background-color:#ff7518;border-color:#ff7518}.btn-check:focus+.btn-outline-warning,.btn-outline-warning:focus{box-shadow:0 0 0 .25rem rgba(255,117,24,.5)}.btn-check:checked+.btn-outline-warning,.btn-check:active+.btn-outline-warning,.btn-outline-warning:active,.btn-outline-warning.active,.btn-outline-warning.dropdown-toggle.show{color:#fff;background-color:#ff7518;border-color:#ff7518}.btn-check:checked+.btn-outline-warning:focus,.btn-check:active+.btn-outline-warning:focus,.btn-outline-warning:active:focus,.btn-outline-warning.active:focus,.btn-outline-warning.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(255,117,24,.5)}.btn-outline-warning:disabled,.btn-outline-warning.disabled{color:#ff7518;background-color:transparent}.btn-outline-danger{color:#ff0039;border-color:#ff0039;background-color:transparent}.btn-outline-danger:hover{color:#fff;background-color:#ff0039;border-color:#ff0039}.btn-check:focus+.btn-outline-danger,.btn-outline-danger:focus{box-shadow:0 0 0 .25rem rgba(255,0,57,.5)}.btn-check:checked+.btn-outline-danger,.btn-check:active+.btn-outline-danger,.btn-outline-danger:active,.btn-outline-danger.active,.btn-outline-danger.dropdown-toggle.show{color:#fff;background-color:#ff0039;border-color:#ff0039}.btn-check:checked+.btn-outline-danger:focus,.btn-check:active+.btn-outline-danger:focus,.btn-outline-danger:active:focus,.btn-outline-danger.active:focus,.btn-outline-danger.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(255,0,57,.5)}.btn-outline-danger:disabled,.btn-outline-danger.disabled{color:#ff0039;background-color:transparent}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa;background-color:transparent}.btn-outline-light:hover{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:focus+.btn-outline-light,.btn-outline-light:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-check:checked+.btn-outline-light,.btn-check:active+.btn-outline-light,.btn-outline-light:active,.btn-outline-light.active,.btn-outline-light.dropdown-toggle.show{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:checked+.btn-outline-light:focus,.btn-check:active+.btn-outline-light:focus,.btn-outline-light:active:focus,.btn-outline-light.active:focus,.btn-outline-light.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-outline-light:disabled,.btn-outline-light.disabled{color:#f8f9fa;background-color:transparent}.btn-outline-dark{color:#373a3c;border-color:#373a3c;background-color:transparent}.btn-outline-dark:hover{color:#fff;background-color:#373a3c;border-color:#373a3c}.btn-check:focus+.btn-outline-dark,.btn-outline-dark:focus{box-shadow:0 0 0 .25rem rgba(55,58,60,.5)}.btn-check:checked+.btn-outline-dark,.btn-check:active+.btn-outline-dark,.btn-outline-dark:active,.btn-outline-dark.active,.btn-outline-dark.dropdown-toggle.show{color:#fff;background-color:#373a3c;border-color:#373a3c}.btn-check:checked+.btn-outline-dark:focus,.btn-check:active+.btn-outline-dark:focus,.btn-outline-dark:active:focus,.btn-outline-dark.active:focus,.btn-outline-dark.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(55,58,60,.5)}.btn-outline-dark:disabled,.btn-outline-dark.disabled{color:#373a3c;background-color:transparent}.btn-link{font-weight:400;color:#2780e3;text-decoration:underline;-webkit-text-decoration:underline;-moz-text-decoration:underline;-ms-text-decoration:underline;-o-text-decoration:underline}.btn-link:hover{color:#1f66b6}.btn-link:disabled,.btn-link.disabled{color:#6c757d}.btn-lg,.btn-group-lg>.btn{padding:.5rem 1rem;font-size:1.25rem;border-radius:0}.btn-sm,.btn-group-sm>.btn{padding:.25rem .5rem;font-size:0.875rem;border-radius:0}.fade{transition:opacity .15s linear}@media(prefers-reduced-motion: reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .2s ease}@media(prefers-reduced-motion: reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s ease}@media(prefers-reduced-motion: reduce){.collapsing.collapse-horizontal{transition:none}}.dropup,.dropend,.dropdown,.dropstart{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;z-index:1000;display:none;min-width:10rem;padding:.5rem 0;margin:0;font-size:1rem;color:#373a3c;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15)}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:.125rem}.dropdown-menu-start{--bs-position: start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position: end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media(min-width: 576px){.dropdown-menu-sm-start{--bs-position: start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position: end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 768px){.dropdown-menu-md-start{--bs-position: start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position: end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 992px){.dropdown-menu-lg-start{--bs-position: start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position: end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 1200px){.dropdown-menu-xl-start{--bs-position: start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position: end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 1400px){.dropdown-menu-xxl-start{--bs-position: start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position: end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid rgba(0,0,0,.15)}.dropdown-item{display:block;width:100%;padding:.25rem 1rem;clear:both;font-weight:400;color:#212529;text-align:inherit;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:hover,.dropdown-item:focus{color:#1e2125;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#2780e3}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1rem;margin-bottom:0;font-size:0.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1rem;color:#212529}.dropdown-menu-dark{color:#dee2e6;background-color:#373a3c;border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item{color:#dee2e6}.dropdown-menu-dark .dropdown-item:hover,.dropdown-menu-dark .dropdown-item:focus{color:#fff;background-color:rgba(255,255,255,.15)}.dropdown-menu-dark .dropdown-item.active,.dropdown-menu-dark .dropdown-item:active{color:#fff;background-color:#2780e3}.dropdown-menu-dark .dropdown-item.disabled,.dropdown-menu-dark .dropdown-item:disabled{color:#adb5bd}.dropdown-menu-dark .dropdown-divider{border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item-text{color:#dee2e6}.dropdown-menu-dark .dropdown-header{color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;flex:1 1 auto;-webkit-flex:1 1 auto}.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn:hover,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn.active{z-index:1}.btn-toolbar{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;justify-content:flex-start;-webkit-justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn:not(:first-child),.btn-group>.btn-group:not(:first-child){margin-left:-1px}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-sm+.dropdown-toggle-split,.btn-group-sm>.btn+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-lg+.dropdown-toggle-split,.btn-group-lg>.btn+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;-webkit-flex-direction:column;align-items:flex-start;-webkit-align-items:flex-start;justify-content:center;-webkit-justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn:not(:first-child),.btn-group-vertical>.btn-group:not(:first-child){margin-top:-1px}.nav{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem;color:#2780e3;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media(prefers-reduced-motion: reduce){.nav-link{transition:none}}.nav-link:hover,.nav-link:focus{color:#1f66b6}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;background:none;border:1px solid transparent}.nav-tabs .nav-link:hover,.nav-tabs .nav-link:focus{border-color:#e9ecef #e9ecef #dee2e6;isolation:isolate}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-link.active,.nav-tabs .nav-item.show .nav-link{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px}.nav-pills .nav-link{background:none;border:0}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#2780e3}.nav-fill>.nav-link,.nav-fill .nav-item{flex:1 1 auto;-webkit-flex:1 1 auto;text-align:center}.nav-justified>.nav-link,.nav-justified .nav-item{flex-basis:0;-webkit-flex-basis:0;flex-grow:1;-webkit-flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between;padding-top:.5rem;padding-bottom:.5rem}.navbar>.container-xxl,.navbar>.container-xl,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container,.navbar>.container-fluid{display:flex;display:-webkit-flex;flex-wrap:inherit;-webkit-flex-wrap:inherit;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between}.navbar-brand{padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;white-space:nowrap}.navbar-nav{display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;-webkit-flex-basis:100%;flex-grow:1;-webkit-flex-grow:1;align-items:center;-webkit-align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;transition:box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 .25rem}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height, 75vh);overflow-y:auto}@media(min-width: 576px){.navbar-expand-sm{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas-header{display:none}.navbar-expand-sm .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-sm .offcanvas-top,.navbar-expand-sm .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-sm .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 768px){.navbar-expand-md{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas-header{display:none}.navbar-expand-md .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-md .offcanvas-top,.navbar-expand-md .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-md .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 992px){.navbar-expand-lg{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas-header{display:none}.navbar-expand-lg .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-lg .offcanvas-top,.navbar-expand-lg .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-lg .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 1200px){.navbar-expand-xl{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas-header{display:none}.navbar-expand-xl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xl .offcanvas-top,.navbar-expand-xl .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-xl .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 1400px){.navbar-expand-xxl{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xxl .offcanvas-top,.navbar-expand-xxl .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-xxl .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas-header{display:none}.navbar-expand .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand .offcanvas-top,.navbar-expand .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}.navbar-light{background-color:#2780e3}.navbar-light .navbar-brand{color:#fdfeff}.navbar-light .navbar-brand:hover,.navbar-light .navbar-brand:focus{color:#fdfeff}.navbar-light .navbar-nav .nav-link{color:#fdfeff}.navbar-light .navbar-nav .nav-link:hover,.navbar-light .navbar-nav .nav-link:focus{color:rgba(253,254,255,.8)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(253,254,255,.75)}.navbar-light .navbar-nav .show>.nav-link,.navbar-light .navbar-nav .nav-link.active{color:#fdfeff}.navbar-light .navbar-toggler{color:#fdfeff;border-color:rgba(253,254,255,.4)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='%23fdfeff' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:#fdfeff}.navbar-light .navbar-text a,.navbar-light .navbar-text a:hover,.navbar-light .navbar-text a:focus{color:#fdfeff}.navbar-dark{background-color:#2780e3}.navbar-dark .navbar-brand{color:#fdfeff}.navbar-dark .navbar-brand:hover,.navbar-dark .navbar-brand:focus{color:#fdfeff}.navbar-dark .navbar-nav .nav-link{color:#fdfeff}.navbar-dark .navbar-nav .nav-link:hover,.navbar-dark .navbar-nav .nav-link:focus{color:rgba(253,254,255,.8)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(253,254,255,.75)}.navbar-dark .navbar-nav .show>.nav-link,.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active{color:#fdfeff}.navbar-dark .navbar-toggler{color:#fdfeff;border-color:rgba(253,254,255,.4)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='%23fdfeff' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:#fdfeff}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:hover,.navbar-dark .navbar-text a:focus{color:#fdfeff}.card{position:relative;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125)}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0}.card>.list-group:last-child{border-bottom-width:0}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;-webkit-flex:1 1 auto;padding:1rem 1rem}.card-title{margin-bottom:.5rem}.card-subtitle{margin-top:-0.25rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:1rem}.card-header{padding:.5rem 1rem;margin-bottom:0;background-color:#adb5bd;border-bottom:1px solid rgba(0,0,0,.125)}.card-footer{padding:.5rem 1rem;background-color:#adb5bd;border-top:1px solid rgba(0,0,0,.125)}.card-header-tabs{margin-right:-0.5rem;margin-bottom:-0.5rem;margin-left:-0.5rem;border-bottom:0}.card-header-pills{margin-right:-0.5rem;margin-left:-0.5rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem}.card-img,.card-img-top,.card-img-bottom{width:100%}.card-group>.card{margin-bottom:.75rem}@media(min-width: 576px){.card-group{display:flex;display:-webkit-flex;flex-flow:row wrap;-webkit-flex-flow:row wrap}.card-group>.card{flex:1 0 0%;-webkit-flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}}.accordion-button{position:relative;display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;width:100%;padding:1rem 1.25rem;font-size:1rem;color:#373a3c;text-align:left;background-color:#fff;border:0;overflow-anchor:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,border-radius .15s ease}@media(prefers-reduced-motion: reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:#2373cc;background-color:#e9f2fc;box-shadow:inset 0 -1px 0 rgba(0,0,0,.125)}.accordion-button:not(.collapsed)::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%232373cc'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");transform:rotate(-180deg)}.accordion-button::after{flex-shrink:0;-webkit-flex-shrink:0;width:1.25rem;height:1.25rem;margin-left:auto;content:"";background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23373a3c'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-size:1.25rem;transition:transform .2s ease-in-out}@media(prefers-reduced-motion: reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:#93c0f1;outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.25)}.accordion-header{margin-bottom:0}.accordion-item{background-color:#fff;border:1px solid rgba(0,0,0,.125)}.accordion-item:not(:first-of-type){border-top:0}.accordion-body{padding:1rem 1.25rem}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.breadcrumb{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;padding:0 0;margin-bottom:1rem;list-style:none}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:var(--bs-breadcrumb-divider, "/") /* rtl: var(--bs-breadcrumb-divider, "/") */}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;display:-webkit-flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;color:#2780e3;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;background-color:#fff;border:1px solid #dee2e6;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:#1f66b6;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;color:#1f66b6;background-color:#e9ecef;outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.25)}.page-item:not(:first-child) .page-link{margin-left:-1px}.page-item.active .page-link{z-index:3;color:#fff;background-color:#2780e3;border-color:#2780e3}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;background-color:#fff;border-color:#dee2e6}.page-link{padding:.375rem .75rem}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:0.875rem}.badge{display:inline-block;padding:.35em .65em;font-size:0.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{position:relative;padding:1rem 1rem;margin-bottom:1rem;border:0 solid transparent}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-default{color:#212324;background-color:#d7d8d8;border-color:#c3c4c5}.alert-default .alert-link{color:#1a1c1d}.alert-primary{color:#174d88;background-color:#d4e6f9;border-color:#bed9f7}.alert-primary .alert-link{color:#123e6d}.alert-secondary{color:#212324;background-color:#d7d8d8;border-color:#c3c4c5}.alert-secondary .alert-link{color:#1a1c1d}.alert-success{color:#266d0e;background-color:#d9f0d1;border-color:#c5e9ba}.alert-success .alert-link{color:#1e570b}.alert-info{color:#5c3270;background-color:#ebddf1;border-color:#e0cceb}.alert-info .alert-link{color:#4a285a}.alert-warning{color:#99460e;background-color:#ffe3d1;border-color:#ffd6ba}.alert-warning .alert-link{color:#7a380b}.alert-danger{color:#902;background-color:#ffccd7;border-color:#ffb3c4}.alert-danger .alert-link{color:#7a001b}.alert-light{color:#959596;background-color:#fefefe;border-color:#fdfdfe}.alert-light .alert-link{color:#777778}.alert-dark{color:#212324;background-color:#d7d8d8;border-color:#c3c4c5}.alert-dark .alert-link{color:#1a1c1d}@keyframes progress-bar-stripes{0%{background-position-x:.5rem}}.progress{display:flex;display:-webkit-flex;height:.5rem;overflow:hidden;font-size:0.75rem;background-color:#e9ecef}.progress-bar{display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;justify-content:center;-webkit-justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#2780e3;transition:width .6s ease}@media(prefers-reduced-motion: reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-size:.5rem .5rem}.progress-bar-animated{animation:1s linear infinite progress-bar-stripes}@media(prefers-reduced-motion: reduce){.progress-bar-animated{animation:none}}.list-group{display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;padding-left:0;margin-bottom:0}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>li::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:hover,.list-group-item-action:focus{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#373a3c;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.5rem 1rem;color:#212529;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#2780e3;border-color:#2780e3}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media(min-width: 576px){.list-group-horizontal-sm{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width: 768px){.list-group-horizontal-md{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width: 992px){.list-group-horizontal-lg{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width: 1200px){.list-group-horizontal-xl{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width: 1400px){.list-group-horizontal-xxl{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-default{color:#212324;background-color:#d7d8d8}.list-group-item-default.list-group-item-action:hover,.list-group-item-default.list-group-item-action:focus{color:#212324;background-color:#c2c2c2}.list-group-item-default.list-group-item-action.active{color:#fff;background-color:#212324;border-color:#212324}.list-group-item-primary{color:#174d88;background-color:#d4e6f9}.list-group-item-primary.list-group-item-action:hover,.list-group-item-primary.list-group-item-action:focus{color:#174d88;background-color:#bfcfe0}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#174d88;border-color:#174d88}.list-group-item-secondary{color:#212324;background-color:#d7d8d8}.list-group-item-secondary.list-group-item-action:hover,.list-group-item-secondary.list-group-item-action:focus{color:#212324;background-color:#c2c2c2}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#212324;border-color:#212324}.list-group-item-success{color:#266d0e;background-color:#d9f0d1}.list-group-item-success.list-group-item-action:hover,.list-group-item-success.list-group-item-action:focus{color:#266d0e;background-color:#c3d8bc}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#266d0e;border-color:#266d0e}.list-group-item-info{color:#5c3270;background-color:#ebddf1}.list-group-item-info.list-group-item-action:hover,.list-group-item-info.list-group-item-action:focus{color:#5c3270;background-color:#d4c7d9}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#5c3270;border-color:#5c3270}.list-group-item-warning{color:#99460e;background-color:#ffe3d1}.list-group-item-warning.list-group-item-action:hover,.list-group-item-warning.list-group-item-action:focus{color:#99460e;background-color:#e6ccbc}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#99460e;border-color:#99460e}.list-group-item-danger{color:#902;background-color:#ffccd7}.list-group-item-danger.list-group-item-action:hover,.list-group-item-danger.list-group-item-action:focus{color:#902;background-color:#e6b8c2}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#902;border-color:#902}.list-group-item-light{color:#959596;background-color:#fefefe}.list-group-item-light.list-group-item-action:hover,.list-group-item-light.list-group-item-action:focus{color:#959596;background-color:#e5e5e5}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#959596;border-color:#959596}.list-group-item-dark{color:#212324;background-color:#d7d8d8}.list-group-item-dark.list-group-item-action:hover,.list-group-item-dark.list-group-item-action:focus{color:#212324;background-color:#c2c2c2}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#212324;border-color:#212324}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:#000;background:transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;border:0;opacity:.5}.btn-close:hover{color:#000;text-decoration:none;opacity:.75}.btn-close:focus{outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.25);opacity:1}.btn-close:disabled,.btn-close.disabled{pointer-events:none;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;opacity:.25}.btn-close-white{filter:invert(1) grayscale(100%) brightness(200%)}.toast{width:350px;max-width:100%;font-size:0.875rem;pointer-events:auto;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .5rem 1rem rgba(0,0,0,.15)}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{width:max-content;width:-webkit-max-content;width:-moz-max-content;width:-ms-max-content;width:-o-max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:.75rem}.toast-header{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;padding:.5rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05)}.toast-header .btn-close{margin-right:-0.375rem;margin-left:.75rem}.toast-body{padding:.75rem;word-wrap:break-word}.modal{position:fixed;top:0;left:0;z-index:1055;display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0, -50px)}@media(prefers-reduced-motion: reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;min-height:calc(100% - 1rem)}.modal-content{position:relative;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1050;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;display:-webkit-flex;flex-shrink:0;-webkit-flex-shrink:0;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6}.modal-header .btn-close{padding:.5rem .5rem;margin:-0.5rem -0.5rem -0.5rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;-webkit-flex:1 1 auto;padding:1rem}.modal-footer{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;flex-shrink:0;-webkit-flex-shrink:0;align-items:center;-webkit-align-items:center;justify-content:flex-end;-webkit-justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6}.modal-footer>*{margin:.25rem}@media(min-width: 576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{height:calc(100% - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-sm{max-width:300px}}@media(min-width: 992px){.modal-lg,.modal-xl{max-width:800px}}@media(min-width: 1200px){.modal-xl{max-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0}.modal-fullscreen .modal-body{overflow-y:auto}@media(max-width: 575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}}@media(max-width: 767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}}@media(max-width: 991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}}@media(max-width: 1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}}@media(max-width: 1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}}.tooltip{position:absolute;z-index:1080;display:block;margin:0;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:0.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .tooltip-arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-top,.bs-tooltip-auto[data-popper-placement^=top]{padding:.4rem 0}.bs-tooltip-top .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow{bottom:0}.bs-tooltip-top .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before{top:-1px;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-end,.bs-tooltip-auto[data-popper-placement^=right]{padding:0 .4rem}.bs-tooltip-end .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-end .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before{right:-1px;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-bottom,.bs-tooltip-auto[data-popper-placement^=bottom]{padding:.4rem 0}.bs-tooltip-bottom .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow{top:0}.bs-tooltip-bottom .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before{bottom:-1px;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-start,.bs-tooltip-auto[data-popper-placement^=left]{padding:0 .4rem}.bs-tooltip-start .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-start .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before{left:-1px;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000}.popover{position:absolute;top:0;left:0 /* rtl:ignore */;z-index:1070;display:block;max-width:276px;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:0.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2)}.popover .popover-arrow{position:absolute;display:block;width:1rem;height:.5rem}.popover .popover-arrow::before,.popover .popover-arrow::after{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-top>.popover-arrow,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow{bottom:calc(-0.5rem - 1px)}.bs-popover-top>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-top>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-end>.popover-arrow,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow{left:calc(-0.5rem - 1px);width:.5rem;height:1rem}.bs-popover-end>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-end>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-bottom>.popover-arrow,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow{top:calc(-0.5rem - 1px)}.bs-popover-bottom>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-bottom>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-bottom .popover-header::before,.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-0.5rem;content:"";border-bottom:1px solid #f0f0f0}.bs-popover-start>.popover-arrow,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow{right:calc(-0.5rem - 1px);width:.5rem;height:1rem}.bs-popover-start>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-start>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem 1rem;margin-bottom:0;font-size:1rem;background-color:#f0f0f0;border-bottom:1px solid rgba(0,0,0,.2)}.popover-header:empty{display:none}.popover-body{padding:1rem 1rem;color:#373a3c}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y;-webkit-touch-action:pan-y;-moz-touch-action:pan-y;-ms-touch-action:pan-y;-o-touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;backface-visibility:hidden;-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;-ms-backface-visibility:hidden;-o-backface-visibility:hidden;transition:transform .6s ease-in-out}@media(prefers-reduced-motion: reduce){.carousel-item{transition:none}}.carousel-item.active,.carousel-item-next,.carousel-item-prev{display:block}.carousel-item-next:not(.carousel-item-start),.active.carousel-item-end{transform:translateX(100%)}.carousel-item-prev:not(.carousel-item-end),.active.carousel-item-start{transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item.active,.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end{z-index:1;opacity:1}.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{z-index:0;opacity:0;transition:opacity 0s .6s}@media(prefers-reduced-motion: reduce){.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{transition:none}}.carousel-control-prev,.carousel-control-next{position:absolute;top:0;bottom:0;z-index:1;display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;justify-content:center;-webkit-justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:none;border:0;opacity:.5;transition:opacity .15s ease}@media(prefers-reduced-motion: reduce){.carousel-control-prev,.carousel-control-next{transition:none}}.carousel-control-prev:hover,.carousel-control-prev:focus,.carousel-control-next:hover,.carousel-control-next:focus{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-prev-icon,.carousel-control-next-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;display:-webkit-flex;justify-content:center;-webkit-justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%;list-style:none}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;-webkit-flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media(prefers-reduced-motion: reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-prev-icon,.carousel-dark .carousel-control-next-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}@keyframes spinner-border{to{transform:rotate(360deg) /* rtl:ignore */}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:-0.125em;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-0.125em;background-color:currentColor;border-radius:50%;opacity:0;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media(prefers-reduced-motion: reduce){.spinner-border,.spinner-grow{animation-duration:1.5s;-webkit-animation-duration:1.5s;-moz-animation-duration:1.5s;-ms-animation-duration:1.5s;-o-animation-duration:1.5s}}.offcanvas{position:fixed;bottom:0;z-index:1045;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;max-width:100%;visibility:hidden;background-color:#fff;background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}@media(prefers-reduced-motion: reduce){.offcanvas{transition:none}}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between;padding:1rem 1rem}.offcanvas-header .btn-close{padding:.5rem .5rem;margin-top:-0.5rem;margin-right:-0.5rem;margin-bottom:-0.5rem}.offcanvas-title{margin-bottom:0;line-height:1.5}.offcanvas-body{flex-grow:1;-webkit-flex-grow:1;padding:1rem 1rem;overflow-y:auto}.offcanvas-start{top:0;left:0;width:400px;border-right:1px solid rgba(0,0,0,.2);transform:translateX(-100%)}.offcanvas-end{top:0;right:0;width:400px;border-left:1px solid rgba(0,0,0,.2);transform:translateX(100%)}.offcanvas-top{top:0;right:0;left:0;height:30vh;max-height:100%;border-bottom:1px solid rgba(0,0,0,.2);transform:translateY(-100%)}.offcanvas-bottom{right:0;left:0;height:30vh;max-height:100%;border-top:1px solid rgba(0,0,0,.2);transform:translateY(100%)}.offcanvas.show{transform:none}.placeholder{display:inline-block;min-height:1em;vertical-align:middle;cursor:wait;background-color:currentColor;opacity:.5}.placeholder.btn::before{display:inline-block;content:""}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{animation:placeholder-glow 2s ease-in-out infinite}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{mask-image:linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%);-webkit-mask-image:linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%);mask-size:200% 100%;-webkit-mask-size:200% 100%;animation:placeholder-wave 2s linear infinite}@keyframes placeholder-wave{100%{mask-position:-200% 0%;-webkit-mask-position:-200% 0%}}.clearfix::after{display:block;clear:both;content:""}.link-default{color:#373a3c}.link-default:hover,.link-default:focus{color:#2c2e30}.link-primary{color:#2780e3}.link-primary:hover,.link-primary:focus{color:#1f66b6}.link-secondary{color:#373a3c}.link-secondary:hover,.link-secondary:focus{color:#2c2e30}.link-success{color:#3fb618}.link-success:hover,.link-success:focus{color:#329213}.link-info{color:#9954bb}.link-info:hover,.link-info:focus{color:#7a4396}.link-warning{color:#ff7518}.link-warning:hover,.link-warning:focus{color:#cc5e13}.link-danger{color:#ff0039}.link-danger:hover,.link-danger:focus{color:#cc002e}.link-light{color:#f8f9fa}.link-light:hover,.link-light:focus{color:#f9fafb}.link-dark{color:#373a3c}.link-dark:hover,.link-dark:focus{color:#2c2e30}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio: 100%}.ratio-4x3{--bs-aspect-ratio: calc(3 / 4 * 100%)}.ratio-16x9{--bs-aspect-ratio: calc(9 / 16 * 100%)}.ratio-21x9{--bs-aspect-ratio: calc(9 / 21 * 100%)}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:sticky;top:0;z-index:1020}@media(min-width: 576px){.sticky-sm-top{position:sticky;top:0;z-index:1020}}@media(min-width: 768px){.sticky-md-top{position:sticky;top:0;z-index:1020}}@media(min-width: 992px){.sticky-lg-top{position:sticky;top:0;z-index:1020}}@media(min-width: 1200px){.sticky-xl-top{position:sticky;top:0;z-index:1020}}@media(min-width: 1400px){.sticky-xxl-top{position:sticky;top:0;z-index:1020}}.hstack{display:flex;display:-webkit-flex;flex-direction:row;-webkit-flex-direction:row;align-items:center;-webkit-align-items:center;align-self:stretch;-webkit-align-self:stretch}.vstack{display:flex;display:-webkit-flex;flex:1 1 auto;-webkit-flex:1 1 auto;flex-direction:column;-webkit-flex-direction:column;align-self:stretch;-webkit-align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute !important;width:1px !important;height:1px !important;padding:0 !important;margin:-1px !important;overflow:hidden !important;clip:rect(0, 0, 0, 0) !important;white-space:nowrap !important;border:0 !important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;-webkit-align-self:stretch;width:1px;min-height:1em;background-color:currentColor;opacity:.25}.align-baseline{vertical-align:baseline !important}.align-top{vertical-align:top !important}.align-middle{vertical-align:middle !important}.align-bottom{vertical-align:bottom !important}.align-text-bottom{vertical-align:text-bottom !important}.align-text-top{vertical-align:text-top !important}.float-start{float:left !important}.float-end{float:right !important}.float-none{float:none !important}.opacity-0{opacity:0 !important}.opacity-25{opacity:.25 !important}.opacity-50{opacity:.5 !important}.opacity-75{opacity:.75 !important}.opacity-100{opacity:1 !important}.overflow-auto{overflow:auto !important}.overflow-hidden{overflow:hidden !important}.overflow-visible{overflow:visible !important}.overflow-scroll{overflow:scroll !important}.d-inline{display:inline !important}.d-inline-block{display:inline-block !important}.d-block{display:block !important}.d-grid{display:grid !important}.d-table{display:table !important}.d-table-row{display:table-row !important}.d-table-cell{display:table-cell !important}.d-flex{display:flex !important}.d-inline-flex{display:inline-flex !important}.d-none{display:none !important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15) !important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075) !important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175) !important}.shadow-none{box-shadow:none !important}.position-static{position:static !important}.position-relative{position:relative !important}.position-absolute{position:absolute !important}.position-fixed{position:fixed !important}.position-sticky{position:sticky !important}.top-0{top:0 !important}.top-50{top:50% !important}.top-100{top:100% !important}.bottom-0{bottom:0 !important}.bottom-50{bottom:50% !important}.bottom-100{bottom:100% !important}.start-0{left:0 !important}.start-50{left:50% !important}.start-100{left:100% !important}.end-0{right:0 !important}.end-50{right:50% !important}.end-100{right:100% !important}.translate-middle{transform:translate(-50%, -50%) !important}.translate-middle-x{transform:translateX(-50%) !important}.translate-middle-y{transform:translateY(-50%) !important}.border{border:1px solid #dee2e6 !important}.border-0{border:0 !important}.border-top{border-top:1px solid #dee2e6 !important}.border-top-0{border-top:0 !important}.border-end{border-right:1px solid #dee2e6 !important}.border-end-0{border-right:0 !important}.border-bottom{border-bottom:1px solid #dee2e6 !important}.border-bottom-0{border-bottom:0 !important}.border-start{border-left:1px solid #dee2e6 !important}.border-start-0{border-left:0 !important}.border-default{border-color:#373a3c !important}.border-primary{border-color:#2780e3 !important}.border-secondary{border-color:#373a3c !important}.border-success{border-color:#3fb618 !important}.border-info{border-color:#9954bb !important}.border-warning{border-color:#ff7518 !important}.border-danger{border-color:#ff0039 !important}.border-light{border-color:#f8f9fa !important}.border-dark{border-color:#373a3c !important}.border-white{border-color:#fff !important}.border-1{border-width:1px !important}.border-2{border-width:2px !important}.border-3{border-width:3px !important}.border-4{border-width:4px !important}.border-5{border-width:5px !important}.w-25{width:25% !important}.w-50{width:50% !important}.w-75{width:75% !important}.w-100{width:100% !important}.w-auto{width:auto !important}.mw-100{max-width:100% !important}.vw-100{width:100vw !important}.min-vw-100{min-width:100vw !important}.h-25{height:25% !important}.h-50{height:50% !important}.h-75{height:75% !important}.h-100{height:100% !important}.h-auto{height:auto !important}.mh-100{max-height:100% !important}.vh-100{height:100vh !important}.min-vh-100{min-height:100vh !important}.flex-fill{flex:1 1 auto !important}.flex-row{flex-direction:row !important}.flex-column{flex-direction:column !important}.flex-row-reverse{flex-direction:row-reverse !important}.flex-column-reverse{flex-direction:column-reverse !important}.flex-grow-0{flex-grow:0 !important}.flex-grow-1{flex-grow:1 !important}.flex-shrink-0{flex-shrink:0 !important}.flex-shrink-1{flex-shrink:1 !important}.flex-wrap{flex-wrap:wrap !important}.flex-nowrap{flex-wrap:nowrap !important}.flex-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-0{gap:0 !important}.gap-1{gap:.25rem !important}.gap-2{gap:.5rem !important}.gap-3{gap:1rem !important}.gap-4{gap:1.5rem !important}.gap-5{gap:3rem !important}.justify-content-start{justify-content:flex-start !important}.justify-content-end{justify-content:flex-end !important}.justify-content-center{justify-content:center !important}.justify-content-between{justify-content:space-between !important}.justify-content-around{justify-content:space-around !important}.justify-content-evenly{justify-content:space-evenly !important}.align-items-start{align-items:flex-start !important}.align-items-end{align-items:flex-end !important}.align-items-center{align-items:center !important}.align-items-baseline{align-items:baseline !important}.align-items-stretch{align-items:stretch !important}.align-content-start{align-content:flex-start !important}.align-content-end{align-content:flex-end !important}.align-content-center{align-content:center !important}.align-content-between{align-content:space-between !important}.align-content-around{align-content:space-around !important}.align-content-stretch{align-content:stretch !important}.align-self-auto{align-self:auto !important}.align-self-start{align-self:flex-start !important}.align-self-end{align-self:flex-end !important}.align-self-center{align-self:center !important}.align-self-baseline{align-self:baseline !important}.align-self-stretch{align-self:stretch !important}.order-first{order:-1 !important}.order-0{order:0 !important}.order-1{order:1 !important}.order-2{order:2 !important}.order-3{order:3 !important}.order-4{order:4 !important}.order-5{order:5 !important}.order-last{order:6 !important}.m-0{margin:0 !important}.m-1{margin:.25rem !important}.m-2{margin:.5rem !important}.m-3{margin:1rem !important}.m-4{margin:1.5rem !important}.m-5{margin:3rem !important}.m-auto{margin:auto !important}.mx-0{margin-right:0 !important;margin-left:0 !important}.mx-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-3{margin-right:1rem !important;margin-left:1rem !important}.mx-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-5{margin-right:3rem !important;margin-left:3rem !important}.mx-auto{margin-right:auto !important;margin-left:auto !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-0{margin-top:0 !important}.mt-1{margin-top:.25rem !important}.mt-2{margin-top:.5rem !important}.mt-3{margin-top:1rem !important}.mt-4{margin-top:1.5rem !important}.mt-5{margin-top:3rem !important}.mt-auto{margin-top:auto !important}.me-0{margin-right:0 !important}.me-1{margin-right:.25rem !important}.me-2{margin-right:.5rem !important}.me-3{margin-right:1rem !important}.me-4{margin-right:1.5rem !important}.me-5{margin-right:3rem !important}.me-auto{margin-right:auto !important}.mb-0{margin-bottom:0 !important}.mb-1{margin-bottom:.25rem !important}.mb-2{margin-bottom:.5rem !important}.mb-3{margin-bottom:1rem !important}.mb-4{margin-bottom:1.5rem !important}.mb-5{margin-bottom:3rem !important}.mb-auto{margin-bottom:auto !important}.ms-0{margin-left:0 !important}.ms-1{margin-left:.25rem !important}.ms-2{margin-left:.5rem !important}.ms-3{margin-left:1rem !important}.ms-4{margin-left:1.5rem !important}.ms-5{margin-left:3rem !important}.ms-auto{margin-left:auto !important}.p-0{padding:0 !important}.p-1{padding:.25rem !important}.p-2{padding:.5rem !important}.p-3{padding:1rem !important}.p-4{padding:1.5rem !important}.p-5{padding:3rem !important}.px-0{padding-right:0 !important;padding-left:0 !important}.px-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-3{padding-right:1rem !important;padding-left:1rem !important}.px-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-5{padding-right:3rem !important;padding-left:3rem !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-0{padding-top:0 !important}.pt-1{padding-top:.25rem !important}.pt-2{padding-top:.5rem !important}.pt-3{padding-top:1rem !important}.pt-4{padding-top:1.5rem !important}.pt-5{padding-top:3rem !important}.pe-0{padding-right:0 !important}.pe-1{padding-right:.25rem !important}.pe-2{padding-right:.5rem !important}.pe-3{padding-right:1rem !important}.pe-4{padding-right:1.5rem !important}.pe-5{padding-right:3rem !important}.pb-0{padding-bottom:0 !important}.pb-1{padding-bottom:.25rem !important}.pb-2{padding-bottom:.5rem !important}.pb-3{padding-bottom:1rem !important}.pb-4{padding-bottom:1.5rem !important}.pb-5{padding-bottom:3rem !important}.ps-0{padding-left:0 !important}.ps-1{padding-left:.25rem !important}.ps-2{padding-left:.5rem !important}.ps-3{padding-left:1rem !important}.ps-4{padding-left:1.5rem !important}.ps-5{padding-left:3rem !important}.font-monospace{font-family:var(--bs-font-monospace) !important}.fs-1{font-size:calc(1.345rem + 1.14vw) !important}.fs-2{font-size:calc(1.3rem + 0.6vw) !important}.fs-3{font-size:calc(1.275rem + 0.3vw) !important}.fs-4{font-size:1.25rem !important}.fs-5{font-size:1.1rem !important}.fs-6{font-size:1rem !important}.fst-italic{font-style:italic !important}.fst-normal{font-style:normal !important}.fw-light{font-weight:300 !important}.fw-lighter{font-weight:lighter !important}.fw-normal{font-weight:400 !important}.fw-bold{font-weight:700 !important}.fw-bolder{font-weight:bolder !important}.lh-1{line-height:1 !important}.lh-sm{line-height:1.25 !important}.lh-base{line-height:1.5 !important}.lh-lg{line-height:2 !important}.text-start{text-align:left !important}.text-end{text-align:right !important}.text-center{text-align:center !important}.text-decoration-none{text-decoration:none !important}.text-decoration-underline{text-decoration:underline !important}.text-decoration-line-through{text-decoration:line-through !important}.text-lowercase{text-transform:lowercase !important}.text-uppercase{text-transform:uppercase !important}.text-capitalize{text-transform:capitalize !important}.text-wrap{white-space:normal !important}.text-nowrap{white-space:nowrap !important}.text-break{word-wrap:break-word !important;word-break:break-word !important}.text-default{--bs-text-opacity: 1;color:rgba(var(--bs-default-rgb), var(--bs-text-opacity)) !important}.text-primary{--bs-text-opacity: 1;color:rgba(var(--bs-primary-rgb), var(--bs-text-opacity)) !important}.text-secondary{--bs-text-opacity: 1;color:rgba(var(--bs-secondary-rgb), var(--bs-text-opacity)) !important}.text-success{--bs-text-opacity: 1;color:rgba(var(--bs-success-rgb), var(--bs-text-opacity)) !important}.text-info{--bs-text-opacity: 1;color:rgba(var(--bs-info-rgb), var(--bs-text-opacity)) !important}.text-warning{--bs-text-opacity: 1;color:rgba(var(--bs-warning-rgb), var(--bs-text-opacity)) !important}.text-danger{--bs-text-opacity: 1;color:rgba(var(--bs-danger-rgb), var(--bs-text-opacity)) !important}.text-light{--bs-text-opacity: 1;color:rgba(var(--bs-light-rgb), var(--bs-text-opacity)) !important}.text-dark{--bs-text-opacity: 1;color:rgba(var(--bs-dark-rgb), var(--bs-text-opacity)) !important}.text-black{--bs-text-opacity: 1;color:rgba(var(--bs-black-rgb), var(--bs-text-opacity)) !important}.text-white{--bs-text-opacity: 1;color:rgba(var(--bs-white-rgb), var(--bs-text-opacity)) !important}.text-body{--bs-text-opacity: 1;color:rgba(var(--bs-body-color-rgb), var(--bs-text-opacity)) !important}.text-muted{--bs-text-opacity: 1;color:#6c757d !important}.text-black-50{--bs-text-opacity: 1;color:rgba(0,0,0,.5) !important}.text-white-50{--bs-text-opacity: 1;color:rgba(255,255,255,.5) !important}.text-reset{--bs-text-opacity: 1;color:inherit !important}.text-opacity-25{--bs-text-opacity: 0.25}.text-opacity-50{--bs-text-opacity: 0.5}.text-opacity-75{--bs-text-opacity: 0.75}.text-opacity-100{--bs-text-opacity: 1}.bg-default{--bs-bg-opacity: 1;background-color:rgba(var(--bs-default-rgb), var(--bs-bg-opacity)) !important}.bg-primary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-primary-rgb), var(--bs-bg-opacity)) !important}.bg-secondary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-secondary-rgb), var(--bs-bg-opacity)) !important}.bg-success{--bs-bg-opacity: 1;background-color:rgba(var(--bs-success-rgb), var(--bs-bg-opacity)) !important}.bg-info{--bs-bg-opacity: 1;background-color:rgba(var(--bs-info-rgb), var(--bs-bg-opacity)) !important}.bg-warning{--bs-bg-opacity: 1;background-color:rgba(var(--bs-warning-rgb), var(--bs-bg-opacity)) !important}.bg-danger{--bs-bg-opacity: 1;background-color:rgba(var(--bs-danger-rgb), var(--bs-bg-opacity)) !important}.bg-light{--bs-bg-opacity: 1;background-color:rgba(var(--bs-light-rgb), var(--bs-bg-opacity)) !important}.bg-dark{--bs-bg-opacity: 1;background-color:rgba(var(--bs-dark-rgb), var(--bs-bg-opacity)) !important}.bg-black{--bs-bg-opacity: 1;background-color:rgba(var(--bs-black-rgb), var(--bs-bg-opacity)) !important}.bg-white{--bs-bg-opacity: 1;background-color:rgba(var(--bs-white-rgb), var(--bs-bg-opacity)) !important}.bg-body{--bs-bg-opacity: 1;background-color:rgba(var(--bs-body-bg-rgb), var(--bs-bg-opacity)) !important}.bg-transparent{--bs-bg-opacity: 1;background-color:transparent !important}.bg-opacity-10{--bs-bg-opacity: 0.1}.bg-opacity-25{--bs-bg-opacity: 0.25}.bg-opacity-50{--bs-bg-opacity: 0.5}.bg-opacity-75{--bs-bg-opacity: 0.75}.bg-opacity-100{--bs-bg-opacity: 1}.bg-gradient{background-image:var(--bs-gradient) !important}.user-select-all{user-select:all !important}.user-select-auto{user-select:auto !important}.user-select-none{user-select:none !important}.pe-none{pointer-events:none !important}.pe-auto{pointer-events:auto !important}.rounded{border-radius:.25rem !important}.rounded-0{border-radius:0 !important}.rounded-1{border-radius:.2em !important}.rounded-2{border-radius:.25rem !important}.rounded-3{border-radius:.3rem !important}.rounded-circle{border-radius:50% !important}.rounded-pill{border-radius:50rem !important}.rounded-top{border-top-left-radius:.25rem !important;border-top-right-radius:.25rem !important}.rounded-end{border-top-right-radius:.25rem !important;border-bottom-right-radius:.25rem !important}.rounded-bottom{border-bottom-right-radius:.25rem !important;border-bottom-left-radius:.25rem !important}.rounded-start{border-bottom-left-radius:.25rem !important;border-top-left-radius:.25rem !important}.visible{visibility:visible !important}.invisible{visibility:hidden !important}@media(min-width: 576px){.float-sm-start{float:left !important}.float-sm-end{float:right !important}.float-sm-none{float:none !important}.d-sm-inline{display:inline !important}.d-sm-inline-block{display:inline-block !important}.d-sm-block{display:block !important}.d-sm-grid{display:grid !important}.d-sm-table{display:table !important}.d-sm-table-row{display:table-row !important}.d-sm-table-cell{display:table-cell !important}.d-sm-flex{display:flex !important}.d-sm-inline-flex{display:inline-flex !important}.d-sm-none{display:none !important}.flex-sm-fill{flex:1 1 auto !important}.flex-sm-row{flex-direction:row !important}.flex-sm-column{flex-direction:column !important}.flex-sm-row-reverse{flex-direction:row-reverse !important}.flex-sm-column-reverse{flex-direction:column-reverse !important}.flex-sm-grow-0{flex-grow:0 !important}.flex-sm-grow-1{flex-grow:1 !important}.flex-sm-shrink-0{flex-shrink:0 !important}.flex-sm-shrink-1{flex-shrink:1 !important}.flex-sm-wrap{flex-wrap:wrap !important}.flex-sm-nowrap{flex-wrap:nowrap !important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-sm-0{gap:0 !important}.gap-sm-1{gap:.25rem !important}.gap-sm-2{gap:.5rem !important}.gap-sm-3{gap:1rem !important}.gap-sm-4{gap:1.5rem !important}.gap-sm-5{gap:3rem !important}.justify-content-sm-start{justify-content:flex-start !important}.justify-content-sm-end{justify-content:flex-end !important}.justify-content-sm-center{justify-content:center !important}.justify-content-sm-between{justify-content:space-between !important}.justify-content-sm-around{justify-content:space-around !important}.justify-content-sm-evenly{justify-content:space-evenly !important}.align-items-sm-start{align-items:flex-start !important}.align-items-sm-end{align-items:flex-end !important}.align-items-sm-center{align-items:center !important}.align-items-sm-baseline{align-items:baseline !important}.align-items-sm-stretch{align-items:stretch !important}.align-content-sm-start{align-content:flex-start !important}.align-content-sm-end{align-content:flex-end !important}.align-content-sm-center{align-content:center !important}.align-content-sm-between{align-content:space-between !important}.align-content-sm-around{align-content:space-around !important}.align-content-sm-stretch{align-content:stretch !important}.align-self-sm-auto{align-self:auto !important}.align-self-sm-start{align-self:flex-start !important}.align-self-sm-end{align-self:flex-end !important}.align-self-sm-center{align-self:center !important}.align-self-sm-baseline{align-self:baseline !important}.align-self-sm-stretch{align-self:stretch !important}.order-sm-first{order:-1 !important}.order-sm-0{order:0 !important}.order-sm-1{order:1 !important}.order-sm-2{order:2 !important}.order-sm-3{order:3 !important}.order-sm-4{order:4 !important}.order-sm-5{order:5 !important}.order-sm-last{order:6 !important}.m-sm-0{margin:0 !important}.m-sm-1{margin:.25rem !important}.m-sm-2{margin:.5rem !important}.m-sm-3{margin:1rem !important}.m-sm-4{margin:1.5rem !important}.m-sm-5{margin:3rem !important}.m-sm-auto{margin:auto !important}.mx-sm-0{margin-right:0 !important;margin-left:0 !important}.mx-sm-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-sm-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-sm-3{margin-right:1rem !important;margin-left:1rem !important}.mx-sm-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-sm-5{margin-right:3rem !important;margin-left:3rem !important}.mx-sm-auto{margin-right:auto !important;margin-left:auto !important}.my-sm-0{margin-top:0 !important;margin-bottom:0 !important}.my-sm-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-sm-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-sm-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-sm-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-sm-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-sm-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-sm-0{margin-top:0 !important}.mt-sm-1{margin-top:.25rem !important}.mt-sm-2{margin-top:.5rem !important}.mt-sm-3{margin-top:1rem !important}.mt-sm-4{margin-top:1.5rem !important}.mt-sm-5{margin-top:3rem !important}.mt-sm-auto{margin-top:auto !important}.me-sm-0{margin-right:0 !important}.me-sm-1{margin-right:.25rem !important}.me-sm-2{margin-right:.5rem !important}.me-sm-3{margin-right:1rem !important}.me-sm-4{margin-right:1.5rem !important}.me-sm-5{margin-right:3rem !important}.me-sm-auto{margin-right:auto !important}.mb-sm-0{margin-bottom:0 !important}.mb-sm-1{margin-bottom:.25rem !important}.mb-sm-2{margin-bottom:.5rem !important}.mb-sm-3{margin-bottom:1rem !important}.mb-sm-4{margin-bottom:1.5rem !important}.mb-sm-5{margin-bottom:3rem !important}.mb-sm-auto{margin-bottom:auto !important}.ms-sm-0{margin-left:0 !important}.ms-sm-1{margin-left:.25rem !important}.ms-sm-2{margin-left:.5rem !important}.ms-sm-3{margin-left:1rem !important}.ms-sm-4{margin-left:1.5rem !important}.ms-sm-5{margin-left:3rem !important}.ms-sm-auto{margin-left:auto !important}.p-sm-0{padding:0 !important}.p-sm-1{padding:.25rem !important}.p-sm-2{padding:.5rem !important}.p-sm-3{padding:1rem !important}.p-sm-4{padding:1.5rem !important}.p-sm-5{padding:3rem !important}.px-sm-0{padding-right:0 !important;padding-left:0 !important}.px-sm-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-sm-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-sm-3{padding-right:1rem !important;padding-left:1rem !important}.px-sm-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-sm-5{padding-right:3rem !important;padding-left:3rem !important}.py-sm-0{padding-top:0 !important;padding-bottom:0 !important}.py-sm-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-sm-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-sm-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-sm-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-sm-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-sm-0{padding-top:0 !important}.pt-sm-1{padding-top:.25rem !important}.pt-sm-2{padding-top:.5rem !important}.pt-sm-3{padding-top:1rem !important}.pt-sm-4{padding-top:1.5rem !important}.pt-sm-5{padding-top:3rem !important}.pe-sm-0{padding-right:0 !important}.pe-sm-1{padding-right:.25rem !important}.pe-sm-2{padding-right:.5rem !important}.pe-sm-3{padding-right:1rem !important}.pe-sm-4{padding-right:1.5rem !important}.pe-sm-5{padding-right:3rem !important}.pb-sm-0{padding-bottom:0 !important}.pb-sm-1{padding-bottom:.25rem !important}.pb-sm-2{padding-bottom:.5rem !important}.pb-sm-3{padding-bottom:1rem !important}.pb-sm-4{padding-bottom:1.5rem !important}.pb-sm-5{padding-bottom:3rem !important}.ps-sm-0{padding-left:0 !important}.ps-sm-1{padding-left:.25rem !important}.ps-sm-2{padding-left:.5rem !important}.ps-sm-3{padding-left:1rem !important}.ps-sm-4{padding-left:1.5rem !important}.ps-sm-5{padding-left:3rem !important}.text-sm-start{text-align:left !important}.text-sm-end{text-align:right !important}.text-sm-center{text-align:center !important}}@media(min-width: 768px){.float-md-start{float:left !important}.float-md-end{float:right !important}.float-md-none{float:none !important}.d-md-inline{display:inline !important}.d-md-inline-block{display:inline-block !important}.d-md-block{display:block !important}.d-md-grid{display:grid !important}.d-md-table{display:table !important}.d-md-table-row{display:table-row !important}.d-md-table-cell{display:table-cell !important}.d-md-flex{display:flex !important}.d-md-inline-flex{display:inline-flex !important}.d-md-none{display:none !important}.flex-md-fill{flex:1 1 auto !important}.flex-md-row{flex-direction:row !important}.flex-md-column{flex-direction:column !important}.flex-md-row-reverse{flex-direction:row-reverse !important}.flex-md-column-reverse{flex-direction:column-reverse !important}.flex-md-grow-0{flex-grow:0 !important}.flex-md-grow-1{flex-grow:1 !important}.flex-md-shrink-0{flex-shrink:0 !important}.flex-md-shrink-1{flex-shrink:1 !important}.flex-md-wrap{flex-wrap:wrap !important}.flex-md-nowrap{flex-wrap:nowrap !important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-md-0{gap:0 !important}.gap-md-1{gap:.25rem !important}.gap-md-2{gap:.5rem !important}.gap-md-3{gap:1rem !important}.gap-md-4{gap:1.5rem !important}.gap-md-5{gap:3rem !important}.justify-content-md-start{justify-content:flex-start !important}.justify-content-md-end{justify-content:flex-end !important}.justify-content-md-center{justify-content:center !important}.justify-content-md-between{justify-content:space-between !important}.justify-content-md-around{justify-content:space-around !important}.justify-content-md-evenly{justify-content:space-evenly !important}.align-items-md-start{align-items:flex-start !important}.align-items-md-end{align-items:flex-end !important}.align-items-md-center{align-items:center !important}.align-items-md-baseline{align-items:baseline !important}.align-items-md-stretch{align-items:stretch !important}.align-content-md-start{align-content:flex-start !important}.align-content-md-end{align-content:flex-end !important}.align-content-md-center{align-content:center !important}.align-content-md-between{align-content:space-between !important}.align-content-md-around{align-content:space-around !important}.align-content-md-stretch{align-content:stretch !important}.align-self-md-auto{align-self:auto !important}.align-self-md-start{align-self:flex-start !important}.align-self-md-end{align-self:flex-end !important}.align-self-md-center{align-self:center !important}.align-self-md-baseline{align-self:baseline !important}.align-self-md-stretch{align-self:stretch !important}.order-md-first{order:-1 !important}.order-md-0{order:0 !important}.order-md-1{order:1 !important}.order-md-2{order:2 !important}.order-md-3{order:3 !important}.order-md-4{order:4 !important}.order-md-5{order:5 !important}.order-md-last{order:6 !important}.m-md-0{margin:0 !important}.m-md-1{margin:.25rem !important}.m-md-2{margin:.5rem !important}.m-md-3{margin:1rem !important}.m-md-4{margin:1.5rem !important}.m-md-5{margin:3rem !important}.m-md-auto{margin:auto !important}.mx-md-0{margin-right:0 !important;margin-left:0 !important}.mx-md-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-md-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-md-3{margin-right:1rem !important;margin-left:1rem !important}.mx-md-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-md-5{margin-right:3rem !important;margin-left:3rem !important}.mx-md-auto{margin-right:auto !important;margin-left:auto !important}.my-md-0{margin-top:0 !important;margin-bottom:0 !important}.my-md-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-md-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-md-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-md-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-md-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-md-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-md-0{margin-top:0 !important}.mt-md-1{margin-top:.25rem !important}.mt-md-2{margin-top:.5rem !important}.mt-md-3{margin-top:1rem !important}.mt-md-4{margin-top:1.5rem !important}.mt-md-5{margin-top:3rem !important}.mt-md-auto{margin-top:auto !important}.me-md-0{margin-right:0 !important}.me-md-1{margin-right:.25rem !important}.me-md-2{margin-right:.5rem !important}.me-md-3{margin-right:1rem !important}.me-md-4{margin-right:1.5rem !important}.me-md-5{margin-right:3rem !important}.me-md-auto{margin-right:auto !important}.mb-md-0{margin-bottom:0 !important}.mb-md-1{margin-bottom:.25rem !important}.mb-md-2{margin-bottom:.5rem !important}.mb-md-3{margin-bottom:1rem !important}.mb-md-4{margin-bottom:1.5rem !important}.mb-md-5{margin-bottom:3rem !important}.mb-md-auto{margin-bottom:auto !important}.ms-md-0{margin-left:0 !important}.ms-md-1{margin-left:.25rem !important}.ms-md-2{margin-left:.5rem !important}.ms-md-3{margin-left:1rem !important}.ms-md-4{margin-left:1.5rem !important}.ms-md-5{margin-left:3rem !important}.ms-md-auto{margin-left:auto !important}.p-md-0{padding:0 !important}.p-md-1{padding:.25rem !important}.p-md-2{padding:.5rem !important}.p-md-3{padding:1rem !important}.p-md-4{padding:1.5rem !important}.p-md-5{padding:3rem !important}.px-md-0{padding-right:0 !important;padding-left:0 !important}.px-md-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-md-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-md-3{padding-right:1rem !important;padding-left:1rem !important}.px-md-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-md-5{padding-right:3rem !important;padding-left:3rem !important}.py-md-0{padding-top:0 !important;padding-bottom:0 !important}.py-md-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-md-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-md-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-md-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-md-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-md-0{padding-top:0 !important}.pt-md-1{padding-top:.25rem !important}.pt-md-2{padding-top:.5rem !important}.pt-md-3{padding-top:1rem !important}.pt-md-4{padding-top:1.5rem !important}.pt-md-5{padding-top:3rem !important}.pe-md-0{padding-right:0 !important}.pe-md-1{padding-right:.25rem !important}.pe-md-2{padding-right:.5rem !important}.pe-md-3{padding-right:1rem !important}.pe-md-4{padding-right:1.5rem !important}.pe-md-5{padding-right:3rem !important}.pb-md-0{padding-bottom:0 !important}.pb-md-1{padding-bottom:.25rem !important}.pb-md-2{padding-bottom:.5rem !important}.pb-md-3{padding-bottom:1rem !important}.pb-md-4{padding-bottom:1.5rem !important}.pb-md-5{padding-bottom:3rem !important}.ps-md-0{padding-left:0 !important}.ps-md-1{padding-left:.25rem !important}.ps-md-2{padding-left:.5rem !important}.ps-md-3{padding-left:1rem !important}.ps-md-4{padding-left:1.5rem !important}.ps-md-5{padding-left:3rem !important}.text-md-start{text-align:left !important}.text-md-end{text-align:right !important}.text-md-center{text-align:center !important}}@media(min-width: 992px){.float-lg-start{float:left !important}.float-lg-end{float:right !important}.float-lg-none{float:none !important}.d-lg-inline{display:inline !important}.d-lg-inline-block{display:inline-block !important}.d-lg-block{display:block !important}.d-lg-grid{display:grid !important}.d-lg-table{display:table !important}.d-lg-table-row{display:table-row !important}.d-lg-table-cell{display:table-cell !important}.d-lg-flex{display:flex !important}.d-lg-inline-flex{display:inline-flex !important}.d-lg-none{display:none !important}.flex-lg-fill{flex:1 1 auto !important}.flex-lg-row{flex-direction:row !important}.flex-lg-column{flex-direction:column !important}.flex-lg-row-reverse{flex-direction:row-reverse !important}.flex-lg-column-reverse{flex-direction:column-reverse !important}.flex-lg-grow-0{flex-grow:0 !important}.flex-lg-grow-1{flex-grow:1 !important}.flex-lg-shrink-0{flex-shrink:0 !important}.flex-lg-shrink-1{flex-shrink:1 !important}.flex-lg-wrap{flex-wrap:wrap !important}.flex-lg-nowrap{flex-wrap:nowrap !important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-lg-0{gap:0 !important}.gap-lg-1{gap:.25rem !important}.gap-lg-2{gap:.5rem !important}.gap-lg-3{gap:1rem !important}.gap-lg-4{gap:1.5rem !important}.gap-lg-5{gap:3rem !important}.justify-content-lg-start{justify-content:flex-start !important}.justify-content-lg-end{justify-content:flex-end !important}.justify-content-lg-center{justify-content:center !important}.justify-content-lg-between{justify-content:space-between !important}.justify-content-lg-around{justify-content:space-around !important}.justify-content-lg-evenly{justify-content:space-evenly !important}.align-items-lg-start{align-items:flex-start !important}.align-items-lg-end{align-items:flex-end !important}.align-items-lg-center{align-items:center !important}.align-items-lg-baseline{align-items:baseline !important}.align-items-lg-stretch{align-items:stretch !important}.align-content-lg-start{align-content:flex-start !important}.align-content-lg-end{align-content:flex-end !important}.align-content-lg-center{align-content:center !important}.align-content-lg-between{align-content:space-between !important}.align-content-lg-around{align-content:space-around !important}.align-content-lg-stretch{align-content:stretch !important}.align-self-lg-auto{align-self:auto !important}.align-self-lg-start{align-self:flex-start !important}.align-self-lg-end{align-self:flex-end !important}.align-self-lg-center{align-self:center !important}.align-self-lg-baseline{align-self:baseline !important}.align-self-lg-stretch{align-self:stretch !important}.order-lg-first{order:-1 !important}.order-lg-0{order:0 !important}.order-lg-1{order:1 !important}.order-lg-2{order:2 !important}.order-lg-3{order:3 !important}.order-lg-4{order:4 !important}.order-lg-5{order:5 !important}.order-lg-last{order:6 !important}.m-lg-0{margin:0 !important}.m-lg-1{margin:.25rem !important}.m-lg-2{margin:.5rem !important}.m-lg-3{margin:1rem !important}.m-lg-4{margin:1.5rem !important}.m-lg-5{margin:3rem !important}.m-lg-auto{margin:auto !important}.mx-lg-0{margin-right:0 !important;margin-left:0 !important}.mx-lg-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-lg-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-lg-3{margin-right:1rem !important;margin-left:1rem !important}.mx-lg-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-lg-5{margin-right:3rem !important;margin-left:3rem !important}.mx-lg-auto{margin-right:auto !important;margin-left:auto !important}.my-lg-0{margin-top:0 !important;margin-bottom:0 !important}.my-lg-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-lg-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-lg-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-lg-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-lg-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-lg-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-lg-0{margin-top:0 !important}.mt-lg-1{margin-top:.25rem !important}.mt-lg-2{margin-top:.5rem !important}.mt-lg-3{margin-top:1rem !important}.mt-lg-4{margin-top:1.5rem !important}.mt-lg-5{margin-top:3rem !important}.mt-lg-auto{margin-top:auto !important}.me-lg-0{margin-right:0 !important}.me-lg-1{margin-right:.25rem !important}.me-lg-2{margin-right:.5rem !important}.me-lg-3{margin-right:1rem !important}.me-lg-4{margin-right:1.5rem !important}.me-lg-5{margin-right:3rem !important}.me-lg-auto{margin-right:auto !important}.mb-lg-0{margin-bottom:0 !important}.mb-lg-1{margin-bottom:.25rem !important}.mb-lg-2{margin-bottom:.5rem !important}.mb-lg-3{margin-bottom:1rem !important}.mb-lg-4{margin-bottom:1.5rem !important}.mb-lg-5{margin-bottom:3rem !important}.mb-lg-auto{margin-bottom:auto !important}.ms-lg-0{margin-left:0 !important}.ms-lg-1{margin-left:.25rem !important}.ms-lg-2{margin-left:.5rem !important}.ms-lg-3{margin-left:1rem !important}.ms-lg-4{margin-left:1.5rem !important}.ms-lg-5{margin-left:3rem !important}.ms-lg-auto{margin-left:auto !important}.p-lg-0{padding:0 !important}.p-lg-1{padding:.25rem !important}.p-lg-2{padding:.5rem !important}.p-lg-3{padding:1rem !important}.p-lg-4{padding:1.5rem !important}.p-lg-5{padding:3rem !important}.px-lg-0{padding-right:0 !important;padding-left:0 !important}.px-lg-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-lg-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-lg-3{padding-right:1rem !important;padding-left:1rem !important}.px-lg-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-lg-5{padding-right:3rem !important;padding-left:3rem !important}.py-lg-0{padding-top:0 !important;padding-bottom:0 !important}.py-lg-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-lg-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-lg-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-lg-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-lg-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-lg-0{padding-top:0 !important}.pt-lg-1{padding-top:.25rem !important}.pt-lg-2{padding-top:.5rem !important}.pt-lg-3{padding-top:1rem !important}.pt-lg-4{padding-top:1.5rem !important}.pt-lg-5{padding-top:3rem !important}.pe-lg-0{padding-right:0 !important}.pe-lg-1{padding-right:.25rem !important}.pe-lg-2{padding-right:.5rem !important}.pe-lg-3{padding-right:1rem !important}.pe-lg-4{padding-right:1.5rem !important}.pe-lg-5{padding-right:3rem !important}.pb-lg-0{padding-bottom:0 !important}.pb-lg-1{padding-bottom:.25rem !important}.pb-lg-2{padding-bottom:.5rem !important}.pb-lg-3{padding-bottom:1rem !important}.pb-lg-4{padding-bottom:1.5rem !important}.pb-lg-5{padding-bottom:3rem !important}.ps-lg-0{padding-left:0 !important}.ps-lg-1{padding-left:.25rem !important}.ps-lg-2{padding-left:.5rem !important}.ps-lg-3{padding-left:1rem !important}.ps-lg-4{padding-left:1.5rem !important}.ps-lg-5{padding-left:3rem !important}.text-lg-start{text-align:left !important}.text-lg-end{text-align:right !important}.text-lg-center{text-align:center !important}}@media(min-width: 1200px){.float-xl-start{float:left !important}.float-xl-end{float:right !important}.float-xl-none{float:none !important}.d-xl-inline{display:inline !important}.d-xl-inline-block{display:inline-block !important}.d-xl-block{display:block !important}.d-xl-grid{display:grid !important}.d-xl-table{display:table !important}.d-xl-table-row{display:table-row !important}.d-xl-table-cell{display:table-cell !important}.d-xl-flex{display:flex !important}.d-xl-inline-flex{display:inline-flex !important}.d-xl-none{display:none !important}.flex-xl-fill{flex:1 1 auto !important}.flex-xl-row{flex-direction:row !important}.flex-xl-column{flex-direction:column !important}.flex-xl-row-reverse{flex-direction:row-reverse !important}.flex-xl-column-reverse{flex-direction:column-reverse !important}.flex-xl-grow-0{flex-grow:0 !important}.flex-xl-grow-1{flex-grow:1 !important}.flex-xl-shrink-0{flex-shrink:0 !important}.flex-xl-shrink-1{flex-shrink:1 !important}.flex-xl-wrap{flex-wrap:wrap !important}.flex-xl-nowrap{flex-wrap:nowrap !important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-xl-0{gap:0 !important}.gap-xl-1{gap:.25rem !important}.gap-xl-2{gap:.5rem !important}.gap-xl-3{gap:1rem !important}.gap-xl-4{gap:1.5rem !important}.gap-xl-5{gap:3rem !important}.justify-content-xl-start{justify-content:flex-start !important}.justify-content-xl-end{justify-content:flex-end !important}.justify-content-xl-center{justify-content:center !important}.justify-content-xl-between{justify-content:space-between !important}.justify-content-xl-around{justify-content:space-around !important}.justify-content-xl-evenly{justify-content:space-evenly !important}.align-items-xl-start{align-items:flex-start !important}.align-items-xl-end{align-items:flex-end !important}.align-items-xl-center{align-items:center !important}.align-items-xl-baseline{align-items:baseline !important}.align-items-xl-stretch{align-items:stretch !important}.align-content-xl-start{align-content:flex-start !important}.align-content-xl-end{align-content:flex-end !important}.align-content-xl-center{align-content:center !important}.align-content-xl-between{align-content:space-between !important}.align-content-xl-around{align-content:space-around !important}.align-content-xl-stretch{align-content:stretch !important}.align-self-xl-auto{align-self:auto !important}.align-self-xl-start{align-self:flex-start !important}.align-self-xl-end{align-self:flex-end !important}.align-self-xl-center{align-self:center !important}.align-self-xl-baseline{align-self:baseline !important}.align-self-xl-stretch{align-self:stretch !important}.order-xl-first{order:-1 !important}.order-xl-0{order:0 !important}.order-xl-1{order:1 !important}.order-xl-2{order:2 !important}.order-xl-3{order:3 !important}.order-xl-4{order:4 !important}.order-xl-5{order:5 !important}.order-xl-last{order:6 !important}.m-xl-0{margin:0 !important}.m-xl-1{margin:.25rem !important}.m-xl-2{margin:.5rem !important}.m-xl-3{margin:1rem !important}.m-xl-4{margin:1.5rem !important}.m-xl-5{margin:3rem !important}.m-xl-auto{margin:auto !important}.mx-xl-0{margin-right:0 !important;margin-left:0 !important}.mx-xl-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-xl-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-xl-3{margin-right:1rem !important;margin-left:1rem !important}.mx-xl-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-xl-5{margin-right:3rem !important;margin-left:3rem !important}.mx-xl-auto{margin-right:auto !important;margin-left:auto !important}.my-xl-0{margin-top:0 !important;margin-bottom:0 !important}.my-xl-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-xl-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-xl-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-xl-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-xl-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-xl-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-xl-0{margin-top:0 !important}.mt-xl-1{margin-top:.25rem !important}.mt-xl-2{margin-top:.5rem !important}.mt-xl-3{margin-top:1rem !important}.mt-xl-4{margin-top:1.5rem !important}.mt-xl-5{margin-top:3rem !important}.mt-xl-auto{margin-top:auto !important}.me-xl-0{margin-right:0 !important}.me-xl-1{margin-right:.25rem !important}.me-xl-2{margin-right:.5rem !important}.me-xl-3{margin-right:1rem !important}.me-xl-4{margin-right:1.5rem !important}.me-xl-5{margin-right:3rem !important}.me-xl-auto{margin-right:auto !important}.mb-xl-0{margin-bottom:0 !important}.mb-xl-1{margin-bottom:.25rem !important}.mb-xl-2{margin-bottom:.5rem !important}.mb-xl-3{margin-bottom:1rem !important}.mb-xl-4{margin-bottom:1.5rem !important}.mb-xl-5{margin-bottom:3rem !important}.mb-xl-auto{margin-bottom:auto !important}.ms-xl-0{margin-left:0 !important}.ms-xl-1{margin-left:.25rem !important}.ms-xl-2{margin-left:.5rem !important}.ms-xl-3{margin-left:1rem !important}.ms-xl-4{margin-left:1.5rem !important}.ms-xl-5{margin-left:3rem !important}.ms-xl-auto{margin-left:auto !important}.p-xl-0{padding:0 !important}.p-xl-1{padding:.25rem !important}.p-xl-2{padding:.5rem !important}.p-xl-3{padding:1rem !important}.p-xl-4{padding:1.5rem !important}.p-xl-5{padding:3rem !important}.px-xl-0{padding-right:0 !important;padding-left:0 !important}.px-xl-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-xl-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-xl-3{padding-right:1rem !important;padding-left:1rem !important}.px-xl-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-xl-5{padding-right:3rem !important;padding-left:3rem !important}.py-xl-0{padding-top:0 !important;padding-bottom:0 !important}.py-xl-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-xl-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-xl-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-xl-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-xl-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-xl-0{padding-top:0 !important}.pt-xl-1{padding-top:.25rem !important}.pt-xl-2{padding-top:.5rem !important}.pt-xl-3{padding-top:1rem !important}.pt-xl-4{padding-top:1.5rem !important}.pt-xl-5{padding-top:3rem !important}.pe-xl-0{padding-right:0 !important}.pe-xl-1{padding-right:.25rem !important}.pe-xl-2{padding-right:.5rem !important}.pe-xl-3{padding-right:1rem !important}.pe-xl-4{padding-right:1.5rem !important}.pe-xl-5{padding-right:3rem !important}.pb-xl-0{padding-bottom:0 !important}.pb-xl-1{padding-bottom:.25rem !important}.pb-xl-2{padding-bottom:.5rem !important}.pb-xl-3{padding-bottom:1rem !important}.pb-xl-4{padding-bottom:1.5rem !important}.pb-xl-5{padding-bottom:3rem !important}.ps-xl-0{padding-left:0 !important}.ps-xl-1{padding-left:.25rem !important}.ps-xl-2{padding-left:.5rem !important}.ps-xl-3{padding-left:1rem !important}.ps-xl-4{padding-left:1.5rem !important}.ps-xl-5{padding-left:3rem !important}.text-xl-start{text-align:left !important}.text-xl-end{text-align:right !important}.text-xl-center{text-align:center !important}}@media(min-width: 1400px){.float-xxl-start{float:left !important}.float-xxl-end{float:right !important}.float-xxl-none{float:none !important}.d-xxl-inline{display:inline !important}.d-xxl-inline-block{display:inline-block !important}.d-xxl-block{display:block !important}.d-xxl-grid{display:grid !important}.d-xxl-table{display:table !important}.d-xxl-table-row{display:table-row !important}.d-xxl-table-cell{display:table-cell !important}.d-xxl-flex{display:flex !important}.d-xxl-inline-flex{display:inline-flex !important}.d-xxl-none{display:none !important}.flex-xxl-fill{flex:1 1 auto !important}.flex-xxl-row{flex-direction:row !important}.flex-xxl-column{flex-direction:column !important}.flex-xxl-row-reverse{flex-direction:row-reverse !important}.flex-xxl-column-reverse{flex-direction:column-reverse !important}.flex-xxl-grow-0{flex-grow:0 !important}.flex-xxl-grow-1{flex-grow:1 !important}.flex-xxl-shrink-0{flex-shrink:0 !important}.flex-xxl-shrink-1{flex-shrink:1 !important}.flex-xxl-wrap{flex-wrap:wrap !important}.flex-xxl-nowrap{flex-wrap:nowrap !important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-xxl-0{gap:0 !important}.gap-xxl-1{gap:.25rem !important}.gap-xxl-2{gap:.5rem !important}.gap-xxl-3{gap:1rem !important}.gap-xxl-4{gap:1.5rem !important}.gap-xxl-5{gap:3rem !important}.justify-content-xxl-start{justify-content:flex-start !important}.justify-content-xxl-end{justify-content:flex-end !important}.justify-content-xxl-center{justify-content:center !important}.justify-content-xxl-between{justify-content:space-between !important}.justify-content-xxl-around{justify-content:space-around !important}.justify-content-xxl-evenly{justify-content:space-evenly !important}.align-items-xxl-start{align-items:flex-start !important}.align-items-xxl-end{align-items:flex-end !important}.align-items-xxl-center{align-items:center !important}.align-items-xxl-baseline{align-items:baseline !important}.align-items-xxl-stretch{align-items:stretch !important}.align-content-xxl-start{align-content:flex-start !important}.align-content-xxl-end{align-content:flex-end !important}.align-content-xxl-center{align-content:center !important}.align-content-xxl-between{align-content:space-between !important}.align-content-xxl-around{align-content:space-around !important}.align-content-xxl-stretch{align-content:stretch !important}.align-self-xxl-auto{align-self:auto !important}.align-self-xxl-start{align-self:flex-start !important}.align-self-xxl-end{align-self:flex-end !important}.align-self-xxl-center{align-self:center !important}.align-self-xxl-baseline{align-self:baseline !important}.align-self-xxl-stretch{align-self:stretch !important}.order-xxl-first{order:-1 !important}.order-xxl-0{order:0 !important}.order-xxl-1{order:1 !important}.order-xxl-2{order:2 !important}.order-xxl-3{order:3 !important}.order-xxl-4{order:4 !important}.order-xxl-5{order:5 !important}.order-xxl-last{order:6 !important}.m-xxl-0{margin:0 !important}.m-xxl-1{margin:.25rem !important}.m-xxl-2{margin:.5rem !important}.m-xxl-3{margin:1rem !important}.m-xxl-4{margin:1.5rem !important}.m-xxl-5{margin:3rem !important}.m-xxl-auto{margin:auto !important}.mx-xxl-0{margin-right:0 !important;margin-left:0 !important}.mx-xxl-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-xxl-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-xxl-3{margin-right:1rem !important;margin-left:1rem !important}.mx-xxl-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-xxl-5{margin-right:3rem !important;margin-left:3rem !important}.mx-xxl-auto{margin-right:auto !important;margin-left:auto !important}.my-xxl-0{margin-top:0 !important;margin-bottom:0 !important}.my-xxl-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-xxl-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-xxl-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-xxl-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-xxl-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-xxl-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-xxl-0{margin-top:0 !important}.mt-xxl-1{margin-top:.25rem !important}.mt-xxl-2{margin-top:.5rem !important}.mt-xxl-3{margin-top:1rem !important}.mt-xxl-4{margin-top:1.5rem !important}.mt-xxl-5{margin-top:3rem !important}.mt-xxl-auto{margin-top:auto !important}.me-xxl-0{margin-right:0 !important}.me-xxl-1{margin-right:.25rem !important}.me-xxl-2{margin-right:.5rem !important}.me-xxl-3{margin-right:1rem !important}.me-xxl-4{margin-right:1.5rem !important}.me-xxl-5{margin-right:3rem !important}.me-xxl-auto{margin-right:auto !important}.mb-xxl-0{margin-bottom:0 !important}.mb-xxl-1{margin-bottom:.25rem !important}.mb-xxl-2{margin-bottom:.5rem !important}.mb-xxl-3{margin-bottom:1rem !important}.mb-xxl-4{margin-bottom:1.5rem !important}.mb-xxl-5{margin-bottom:3rem !important}.mb-xxl-auto{margin-bottom:auto !important}.ms-xxl-0{margin-left:0 !important}.ms-xxl-1{margin-left:.25rem !important}.ms-xxl-2{margin-left:.5rem !important}.ms-xxl-3{margin-left:1rem !important}.ms-xxl-4{margin-left:1.5rem !important}.ms-xxl-5{margin-left:3rem !important}.ms-xxl-auto{margin-left:auto !important}.p-xxl-0{padding:0 !important}.p-xxl-1{padding:.25rem !important}.p-xxl-2{padding:.5rem !important}.p-xxl-3{padding:1rem !important}.p-xxl-4{padding:1.5rem !important}.p-xxl-5{padding:3rem !important}.px-xxl-0{padding-right:0 !important;padding-left:0 !important}.px-xxl-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-xxl-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-xxl-3{padding-right:1rem !important;padding-left:1rem !important}.px-xxl-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-xxl-5{padding-right:3rem !important;padding-left:3rem !important}.py-xxl-0{padding-top:0 !important;padding-bottom:0 !important}.py-xxl-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-xxl-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-xxl-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-xxl-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-xxl-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-xxl-0{padding-top:0 !important}.pt-xxl-1{padding-top:.25rem !important}.pt-xxl-2{padding-top:.5rem !important}.pt-xxl-3{padding-top:1rem !important}.pt-xxl-4{padding-top:1.5rem !important}.pt-xxl-5{padding-top:3rem !important}.pe-xxl-0{padding-right:0 !important}.pe-xxl-1{padding-right:.25rem !important}.pe-xxl-2{padding-right:.5rem !important}.pe-xxl-3{padding-right:1rem !important}.pe-xxl-4{padding-right:1.5rem !important}.pe-xxl-5{padding-right:3rem !important}.pb-xxl-0{padding-bottom:0 !important}.pb-xxl-1{padding-bottom:.25rem !important}.pb-xxl-2{padding-bottom:.5rem !important}.pb-xxl-3{padding-bottom:1rem !important}.pb-xxl-4{padding-bottom:1.5rem !important}.pb-xxl-5{padding-bottom:3rem !important}.ps-xxl-0{padding-left:0 !important}.ps-xxl-1{padding-left:.25rem !important}.ps-xxl-2{padding-left:.5rem !important}.ps-xxl-3{padding-left:1rem !important}.ps-xxl-4{padding-left:1.5rem !important}.ps-xxl-5{padding-left:3rem !important}.text-xxl-start{text-align:left !important}.text-xxl-end{text-align:right !important}.text-xxl-center{text-align:center !important}}.bg-default{color:#fff}.bg-primary{color:#fff}.bg-secondary{color:#fff}.bg-success{color:#fff}.bg-info{color:#fff}.bg-warning{color:#fff}.bg-danger{color:#fff}.bg-light{color:#000}.bg-dark{color:#fff}@media(min-width: 1200px){.fs-1{font-size:2.2rem !important}.fs-2{font-size:1.75rem !important}.fs-3{font-size:1.5rem !important}}@media print{.d-print-inline{display:inline !important}.d-print-inline-block{display:inline-block !important}.d-print-block{display:block !important}.d-print-grid{display:grid !important}.d-print-table{display:table !important}.d-print-table-row{display:table-row !important}.d-print-table-cell{display:table-cell !important}.d-print-flex{display:flex !important}.d-print-inline-flex{display:inline-flex !important}.d-print-none{display:none !important}}.sidebar-item .chapter-number{color:#373a3c}.quarto-container{min-height:calc(100vh - 132px)}footer.footer .nav-footer,#quarto-header nav{padding-left:1em;padding-right:1em}nav[role=doc-toc]{padding-left:.5em}#quarto-content>*{padding-top:14px}@media(max-width: 991.98px){#quarto-content>*{padding-top:0}#quarto-content .subtitle{padding-top:14px}#quarto-content section:first-of-type h2:first-of-type,#quarto-content section:first-of-type .h2:first-of-type{margin-top:1rem}}.headroom-target,header.headroom{will-change:transform;transition:transform 200ms linear;transition:position 200ms linear}header.headroom--pinned{transform:translateY(0%)}header.headroom--unpinned{transform:translateY(-100%)}.navbar-container{width:100%}.navbar-brand{overflow:hidden;text-overflow:ellipsis}.navbar-brand-container{max-width:calc(100% - 85px);min-width:0;display:flex;align-items:center;margin-right:1em}.navbar-brand.navbar-brand-logo{margin-right:4px;display:inline-flex}.navbar-toggler{flex-basis:content;flex-shrink:0}.navbar-logo{max-height:24px;width:auto;padding-right:4px}nav .nav-item:not(.compact){padding-top:1px}nav .nav-link i,nav .dropdown-item i{padding-right:1px}.navbar-expand-lg .navbar-nav .nav-link{padding-left:.6rem;padding-right:.6rem}nav .nav-item.compact .nav-link{padding-left:.5rem;padding-right:.5rem;font-size:1.1rem}.navbar-nav .dropdown-menu{min-width:220px;font-size:.9rem}.navbar .navbar-nav .nav-link.dropdown-toggle::after{opacity:.75;vertical-align:.175em}.navbar ul.dropdown-menu{padding-top:0;padding-bottom:0}.navbar .dropdown-header{text-transform:uppercase;font-size:.8rem;padding:0 .5rem}.navbar .dropdown-item{padding:.4rem .5rem}.navbar .dropdown-item>i.bi{margin-left:.1rem;margin-right:.25em}.sidebar #quarto-search{margin-top:-1px}.sidebar #quarto-search svg.aa-SubmitIcon{width:16px;height:16px}.sidebar-navigation a{color:inherit}.sidebar-title{margin-top:.25rem;padding-bottom:.5rem;font-size:1.3rem;line-height:1.6rem;visibility:visible}.sidebar-title>a{font-size:inherit;text-decoration:none}.sidebar-title .sidebar-tools-main{margin-top:-6px}.sidebar-header-stacked .sidebar-title{margin-top:.6rem}.sidebar-logo{max-width:90%;padding-bottom:.5rem}.sidebar-logo-link{text-decoration:none}.sidebar-navigation li a{text-decoration:none}.sidebar-navigation .sidebar-tool{opacity:.7;font-size:.875rem}#quarto-sidebar>nav>.sidebar-tools-main{margin-left:14px}.sidebar-tools-main{margin-left:0px}.sidebar-tools-main:not(.tools-wide){display:inline-block;vertical-align:middle}.sidebar-tools-main.tools-wide{padding-top:.3em}.sidebar-navigation .sidebar-tool.dropdown-toggle::after{display:none}.sidebar.sidebar-navigation>*{padding-top:1em}.sidebar-item{margin-bottom:.2em}.sidebar-section{margin-top:.2em;padding-left:.5em;padding-bottom:.2em}.sidebar-item .sidebar-item-container{display:flex;justify-content:space-between}.sidebar-item .sidebar-item-toggle .bi{font-size:.7rem;text-align:center}.sidebar-navigation .sidebar-divider{margin-left:0;margin-right:0;margin-top:.5rem;margin-bottom:.5rem}@media(max-width: 767.98px){.quarto-secondary-nav{display:block}}@media(min-width: 992px){.quarto-secondary-nav{display:none}}.quarto-secondary-nav .quarto-btn-toggle{color:#595959;padding-right:0}.quarto-secondary-nav[aria-expanded=false] .quarto-btn-toggle .bi-chevron-right::before{transform:none}.quarto-secondary-nav[aria-expanded=true] .quarto-btn-toggle .bi-chevron-right::before{transform:rotate(90deg)}.quarto-secondary-nav .quarto-btn-toggle .bi-chevron-right::before{transition:transform 200ms ease}.quarto-secondary-nav{cursor:pointer}.quarto-secondary-nav-title{margin-top:.3em;color:#595959;padding-top:4px}div.sidebar-item-container{color:#595959}div.sidebar-item-container:hover,div.sidebar-item-container:focus{color:rgba(27,88,157,.8)}div.sidebar-item-container.disabled{color:rgba(89,89,89,.75)}div.sidebar-item-container .active,div.sidebar-item-container .show>.nav-link,div.sidebar-item-container .sidebar-link>code{color:#1b589d}div.sidebar.sidebar-navigation.rollup.quarto-sidebar-toggle-contents,nav.sidebar.sidebar-navigation:not(.rollup){background-color:#fff}@media(max-width: 991.98px){.sidebar-navigation .sidebar-item a,.nav-page .nav-page-text,.sidebar-navigation{font-size:1rem}.sidebar-navigation ul.sidebar-section.depth1 .sidebar-section-item{font-size:1.1rem}.sidebar-logo{display:none}.sidebar.sidebar-navigation{position:static;border-bottom:1px solid #dee2e6}.sidebar.sidebar-navigation.collapsing{position:fixed;z-index:1000}.sidebar.sidebar-navigation.show{position:fixed;z-index:1000}.sidebar.sidebar-navigation{transition:height .15s linear;width:100%}nav.quarto-secondary-nav{background-color:#fff;border-bottom:1px solid #dee2e6}.sidebar .sidebar-footer{visibility:visible;padding-top:1rem;position:inherit}.sidebar-tools-collapse{display:block}}@media(min-width: 992px){#quarto-sidebar{display:flex;flex-direction:column}.nav-page .nav-page-text,.sidebar-navigation .sidebar-section .sidebar-item{font-size:.875rem}.sidebar-navigation .sidebar-item{font-size:.925rem}.sidebar.sidebar-navigation{display:block;position:sticky}.sidebar-search{width:100%}.sidebar .sidebar-footer{visibility:visible}}.sidebar .sidebar-footer{padding:.5rem 1rem;align-self:flex-end;color:#6c757d;width:100%}#quarto-sidebar{width:100%;padding-right:1em;color:#595959}.quarto-sidebar-footer{font-size:.875em}.sidebar-section .bi-chevron-right{vertical-align:middle}.sidebar-section a .bi-chevron-right::before{transform:rotate(90deg)}.sidebar-section a.collapsed .bi-chevron-right::before{transform:none}.sidebar-section .bi-chevron-right::before{font-size:.9em;transition:transform 200ms ease}.notransition{-webkit-transition:none !important;-moz-transition:none !important;-o-transition:none !important;transition:none !important}.btn:focus:not(:focus-visible){box-shadow:none}.page-navigation{display:flex;justify-content:space-between}.nav-page{padding-bottom:.75em}.nav-page .bi{font-size:1.8rem;vertical-align:middle}.nav-page .nav-page-text{padding-left:.25em;padding-right:.25em}.nav-page a{color:#6c757d;text-decoration:none;display:flex;align-items:center}.nav-page a:hover{color:#1f66b6}.toc-actions{display:flex}.toc-actions p{margin-block-start:0;margin-block-end:0}.toc-actions a{text-decoration:none;color:inherit;font-weight:400}.toc-actions a:hover{color:#1f66b6}.toc-actions .action-links{margin-left:4px}.sidebar nav[role=doc-toc] .toc-actions .bi{margin-left:-4px;font-size:.7rem;color:#6c757d}.sidebar nav[role=doc-toc] .toc-actions .bi:before{padding-top:3px}#quarto-margin-sidebar .toc-actions .bi:before{margin-top:.3rem;font-size:.7rem;color:#6c757d;vertical-align:top}.sidebar nav[role=doc-toc] .toc-actions>div:first-of-type{margin-top:-3px}#quarto-margin-sidebar .toc-actions p,.sidebar nav[role=doc-toc] .toc-actions p{font-size:.875rem}.nav-footer{display:flex;justify-content:center;align-items:center;text-align:center;padding-top:.5rem;padding-bottom:.5rem;background-color:#fff}body.nav-fixed{padding-top:64px}.nav-footer-contents{color:#6c757d;margin-top:.25rem}.nav-footer{min-height:3.5em;color:#757575}.nav-footer a{color:#757575}.nav-footer .nav-footer-left{font-size:.825em}.nav-footer .nav-footer-center{font-size:.825em}.nav-footer .nav-footer-right{font-size:.825em}.nav-footer-left .footer-items,.nav-footer-center .footer-items,.nav-footer-right .footer-items{display:flex;padding-top:.3em;padding-bottom:.3em;margin-bottom:0em}.nav-footer-left .footer-items .nav-link,.nav-footer-center .footer-items .nav-link,.nav-footer-right .footer-items .nav-link{padding-left:.6em;padding-right:.6em}.nav-footer-left{margin-right:auto}.nav-footer-center{min-height:3em;position:absolute;text-align:center}.nav-footer-center .footer-items{justify-content:center}@media(max-width: 767.98px){.nav-footer-center{margin-top:3em}}.nav-footer-right{margin-left:auto}.navbar .quarto-reader-toggle{padding-left:.4em;padding-right:0}.navbar .quarto-reader-toggle.reader .quarto-reader-toggle-btn{background-color:#fdfeff;border-radius:3px}.quarto-reader-toggle.reader.sidebar-tool .quarto-reader-toggle-btn{background-color:#595959;border-radius:3px}.quarto-reader-toggle.sidebar-tool{padding-left:.3em}.quarto-reader-toggle .quarto-reader-toggle-btn{display:inline-flex;padding-left:.1em;padding-right:.3em;text-align:center}.navbar .quarto-reader-toggle:not(.reader) .bi::before{background-image:url('data:image/svg+xml,')}.navbar .quarto-reader-toggle.reader .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-reader-toggle:not(.reader) .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-reader-toggle.reader .bi::before{background-image:url('data:image/svg+xml,')}.aa-DetachedOverlay ul.aa-List,#quarto-search-results ul.aa-List{list-style:none;padding-left:0}.aa-DetachedOverlay .aa-Panel,#quarto-search-results .aa-Panel{background-color:#fff;position:absolute;z-index:2000}#quarto-search-results .aa-Panel{max-width:400px}#quarto-search input{font-size:.925rem}@media(min-width: 992px){.navbar #quarto-search{margin-left:1rem}}#quarto-sidebar .sidebar-search .aa-Autocomplete{width:100%}.navbar .aa-Autocomplete .aa-Form{width:180px}.navbar #quarto-search.type-overlay .aa-Autocomplete{width:40px}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form{background-color:inherit;border:none}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form:focus-within{box-shadow:none;outline:none}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form .aa-InputWrapper{display:none}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form .aa-InputWrapper:focus-within{display:inherit}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form .aa-Label svg,.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form .aa-LoadingIndicator svg{width:26px;height:26px;color:#fdfeff;opacity:1}.navbar #quarto-search.type-overlay .aa-Autocomplete svg.aa-SubmitIcon{width:26px;height:26px;color:#fdfeff;opacity:1}.aa-Autocomplete .aa-Form,.aa-DetachedFormContainer .aa-Form{align-items:center;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem;color:#373a3c;display:flex;line-height:1em;margin:0;position:relative;width:100%}.aa-Autocomplete .aa-Form:focus-within,.aa-DetachedFormContainer .aa-Form:focus-within{box-shadow:rgba(39,128,227,.6) 0 0 0 1px;outline:currentColor none medium}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix{align-items:center;display:flex;flex-shrink:0;order:1}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-Label,.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-Label,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator{cursor:initial;flex-shrink:0;padding:0;text-align:left}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-Label svg,.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator svg,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-Label svg,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator svg{color:#373a3c;opacity:.5}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-SubmitButton,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-SubmitButton{appearance:none;background:none;border:0;margin:0}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator{align-items:center;display:flex;justify-content:center}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator[hidden],.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator[hidden]{display:none}.aa-Autocomplete .aa-Form .aa-InputWrapper,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper{order:3;position:relative;width:100%}.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input{appearance:none;background:none;border:0;color:#373a3c;font:inherit;height:calc(1.5em + (0.1rem + 2px));padding:0;width:100%}.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::placeholder,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::placeholder{color:#373a3c;opacity:.8}.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input:focus,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input:focus{border-color:none;box-shadow:none;outline:none}.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-decoration,.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-cancel-button,.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-results-button,.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-results-decoration,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-decoration,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-cancel-button,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-results-button,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-results-decoration{display:none}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix{align-items:center;display:flex;order:4}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton{align-items:center;background:none;border:0;color:#373a3c;opacity:.8;cursor:pointer;display:flex;margin:0;width:calc(1.5em + (0.1rem + 2px))}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton:hover,.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton:focus,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton:hover,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton:focus{color:#373a3c;opacity:.8}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton[hidden],.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton[hidden]{display:none}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton svg,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton svg{width:calc(1.5em + 0.75rem + 2px)}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-CopyButton,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-CopyButton{border:none;align-items:center;background:none;color:#373a3c;opacity:.4;font-size:.7rem;cursor:pointer;display:none;margin:0;width:calc(1em + (0.1rem + 2px))}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-CopyButton:hover,.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-CopyButton:focus,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-CopyButton:hover,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-CopyButton:focus{color:#373a3c;opacity:.8}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-CopyButton[hidden],.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-CopyButton[hidden]{display:none}#quarto-search-results .aa-Panel{border:solid #ced4da 1px}#quarto-search-results .aa-SourceNoResults{width:398px}.aa-DetachedOverlay .aa-Panel,#quarto-search-results .aa-Panel{max-height:65vh;overflow-y:auto;font-size:.925rem}.aa-DetachedOverlay .aa-SourceNoResults,#quarto-search-results .aa-SourceNoResults{height:60px;display:flex;justify-content:center;align-items:center}.aa-DetachedOverlay .search-error,#quarto-search-results .search-error{padding-top:10px;padding-left:20px;padding-right:20px;cursor:default}.aa-DetachedOverlay .search-error .search-error-title,#quarto-search-results .search-error .search-error-title{font-size:1.1rem;margin-bottom:.5rem}.aa-DetachedOverlay .search-error .search-error-title .search-error-icon,#quarto-search-results .search-error .search-error-title .search-error-icon{margin-right:8px}.aa-DetachedOverlay .search-error .search-error-text,#quarto-search-results .search-error .search-error-text{font-weight:300}.aa-DetachedOverlay .search-result-text,#quarto-search-results .search-result-text{font-weight:300;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;line-height:1.2rem;max-height:2.4rem}.aa-DetachedOverlay .aa-SourceHeader .search-result-header,#quarto-search-results .aa-SourceHeader .search-result-header{font-size:.875rem;background-color:#f2f2f2;padding-left:14px;padding-bottom:4px;padding-top:4px}.aa-DetachedOverlay .aa-SourceHeader .search-result-header-no-results,#quarto-search-results .aa-SourceHeader .search-result-header-no-results{display:none}.aa-DetachedOverlay .aa-SourceFooter .algolia-search-logo,#quarto-search-results .aa-SourceFooter .algolia-search-logo{width:110px;opacity:.85;margin:8px;float:right}.aa-DetachedOverlay .search-result-section,#quarto-search-results .search-result-section{font-size:.925em}.aa-DetachedOverlay a.search-result-link,#quarto-search-results a.search-result-link{color:inherit;text-decoration:none}.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item,#quarto-search-results li.aa-Item[aria-selected=true] .search-item{background-color:#2780e3}.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item.search-result-more,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-result-section,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-result-text,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-result-title-container,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-result-text-container,#quarto-search-results li.aa-Item[aria-selected=true] .search-item.search-result-more,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-result-section,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-result-text,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-result-title-container,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-result-text-container{color:#fff;background-color:#2780e3}.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item mark.search-match,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-match.mark,#quarto-search-results li.aa-Item[aria-selected=true] .search-item mark.search-match,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-match.mark{color:#fff;background-color:#4b95e8}.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item,#quarto-search-results li.aa-Item[aria-selected=false] .search-item{background-color:#fff}.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item.search-result-more,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-result-section,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-result-text,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-result-title-container,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-result-text-container,#quarto-search-results li.aa-Item[aria-selected=false] .search-item.search-result-more,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-result-section,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-result-text,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-result-title-container,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-result-text-container{color:#373a3c}.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item mark.search-match,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-match.mark,#quarto-search-results li.aa-Item[aria-selected=false] .search-item mark.search-match,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-match.mark{color:inherit;background-color:#e5effc}.aa-DetachedOverlay .aa-Item .search-result-doc:not(.document-selectable) .search-result-title-container,#quarto-search-results .aa-Item .search-result-doc:not(.document-selectable) .search-result-title-container{background-color:#fff;color:#373a3c}.aa-DetachedOverlay .aa-Item .search-result-doc:not(.document-selectable) .search-result-text-container,#quarto-search-results .aa-Item .search-result-doc:not(.document-selectable) .search-result-text-container{padding-top:0px}.aa-DetachedOverlay li.aa-Item .search-result-doc.document-selectable .search-result-text-container,#quarto-search-results li.aa-Item .search-result-doc.document-selectable .search-result-text-container{margin-top:-4px}.aa-DetachedOverlay .aa-Item,#quarto-search-results .aa-Item{cursor:pointer}.aa-DetachedOverlay .aa-Item .search-item,#quarto-search-results .aa-Item .search-item{border-left:none;border-right:none;border-top:none;background-color:#fff;border-color:#ced4da;color:#373a3c}.aa-DetachedOverlay .aa-Item .search-item p,#quarto-search-results .aa-Item .search-item p{margin-top:0;margin-bottom:0}.aa-DetachedOverlay .aa-Item .search-item i.bi,#quarto-search-results .aa-Item .search-item i.bi{padding-left:8px;padding-right:8px;font-size:1.3em}.aa-DetachedOverlay .aa-Item .search-item .search-result-title,#quarto-search-results .aa-Item .search-item .search-result-title{margin-top:.3em;margin-bottom:.1rem}.aa-DetachedOverlay .aa-Item .search-result-title-container,#quarto-search-results .aa-Item .search-result-title-container{font-size:1em;display:flex;padding:6px 4px 6px 4px}.aa-DetachedOverlay .aa-Item .search-result-text-container,#quarto-search-results .aa-Item .search-result-text-container{padding-bottom:8px;padding-right:8px;margin-left:44px}.aa-DetachedOverlay .aa-Item .search-result-doc-section,.aa-DetachedOverlay .aa-Item .search-result-more,#quarto-search-results .aa-Item .search-result-doc-section,#quarto-search-results .aa-Item .search-result-more{padding-top:8px;padding-bottom:8px;padding-left:44px}.aa-DetachedOverlay .aa-Item .search-result-more,#quarto-search-results .aa-Item .search-result-more{font-size:.8em;font-weight:400}.aa-DetachedOverlay .aa-Item .search-result-doc,#quarto-search-results .aa-Item .search-result-doc{border-top:1px solid #ced4da}.aa-DetachedSearchButton{background:none;border:none}.aa-DetachedSearchButton .aa-DetachedSearchButtonPlaceholder{display:none}.navbar .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon{color:#fdfeff}.sidebar-tools-collapse #quarto-search,.sidebar-tools-main #quarto-search{display:inline}.sidebar-tools-collapse #quarto-search .aa-Autocomplete,.sidebar-tools-main #quarto-search .aa-Autocomplete{display:inline}.sidebar-tools-collapse #quarto-search .aa-DetachedSearchButton,.sidebar-tools-main #quarto-search .aa-DetachedSearchButton{padding-left:4px;padding-right:4px}.sidebar-tools-collapse #quarto-search .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon,.sidebar-tools-main #quarto-search .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon{color:#595959}.sidebar-tools-collapse #quarto-search .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon .aa-SubmitIcon,.sidebar-tools-main #quarto-search .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon .aa-SubmitIcon{margin-top:-3px}.aa-DetachedContainer{background:rgba(255,255,255,.65);width:90%;bottom:0;box-shadow:rgba(206,212,218,.6) 0 0 0 1px;outline:currentColor none medium;display:flex;flex-direction:column;left:0;margin:0;overflow:hidden;padding:0;position:fixed;right:0;top:0;z-index:1101}.aa-DetachedContainer::after{height:32px}.aa-DetachedContainer .aa-SourceHeader{margin:var(--aa-spacing-half) 0 var(--aa-spacing-half) 2px}.aa-DetachedContainer .aa-Panel{background-color:#fff;border-radius:0;box-shadow:none;flex-grow:1;margin:0;padding:0;position:relative}.aa-DetachedContainer .aa-PanelLayout{bottom:0;box-shadow:none;left:0;margin:0;max-height:none;overflow-y:auto;position:absolute;right:0;top:0;width:100%}.aa-DetachedFormContainer{background-color:#fff;border-bottom:1px solid #ced4da;display:flex;flex-direction:row;justify-content:space-between;margin:0;padding:.5em}.aa-DetachedCancelButton{background:none;font-size:.8em;border:0;border-radius:3px;color:#373a3c;cursor:pointer;margin:0 0 0 .5em;padding:0 .5em}.aa-DetachedCancelButton:hover,.aa-DetachedCancelButton:focus{box-shadow:rgba(39,128,227,.6) 0 0 0 1px;outline:currentColor none medium}.aa-DetachedContainer--modal{border-radius:6px;bottom:inherit;height:auto;margin:0 auto;max-width:850px;position:absolute;top:100px}.aa-DetachedContainer--modal .aa-PanelLayout{max-height:var(--aa-detached-modal-max-height);padding-bottom:var(--aa-spacing-half);position:static}.aa-Detached{height:100vh;overflow:hidden}.aa-DetachedOverlay{background-color:rgba(55,58,60,.4);position:fixed;left:0;right:0;top:0;margin:0;padding:0;height:100vh;z-index:1100}.quarto-listing{padding-bottom:1em}.listing-pagination{padding-top:.5em}ul.pagination{float:right;padding-left:8px;padding-top:.5em}ul.pagination li{padding-right:.75em}ul.pagination li.disabled a,ul.pagination li.active a{color:#373a3c;text-decoration:none}ul.pagination li:last-of-type{padding-right:0}.listing-actions-group{display:flex}.quarto-listing-filter{margin-bottom:1em;width:200px;margin-left:auto}.quarto-listing-sort{margin-bottom:1em;margin-right:auto;width:auto}.quarto-listing-sort .input-group-text{font-size:.8em}.input-group-text{border-right:none}.quarto-listing-sort select.form-select{font-size:.8em}.listing-no-matching{text-align:center;padding-top:2em;padding-bottom:3em;font-size:1em}#quarto-margin-sidebar .quarto-listing-category{padding-top:0;font-size:1rem}#quarto-margin-sidebar .quarto-listing-category-title{cursor:pointer;font-weight:600;font-size:1rem}.quarto-listing-category .category{cursor:pointer}.quarto-listing-category .category.active{font-weight:600}.quarto-listing-category.category-cloud{display:flex;flex-wrap:wrap;align-items:baseline}.quarto-listing-category.category-cloud .category{padding-right:5px}.quarto-listing-category.category-cloud .category-cloud-1{font-size:.75em}.quarto-listing-category.category-cloud .category-cloud-2{font-size:.95em}.quarto-listing-category.category-cloud .category-cloud-3{font-size:1.15em}.quarto-listing-category.category-cloud .category-cloud-4{font-size:1.35em}.quarto-listing-category.category-cloud .category-cloud-5{font-size:1.55em}.quarto-listing-category.category-cloud .category-cloud-6{font-size:1.75em}.quarto-listing-category.category-cloud .category-cloud-7{font-size:1.95em}.quarto-listing-category.category-cloud .category-cloud-8{font-size:2.15em}.quarto-listing-category.category-cloud .category-cloud-9{font-size:2.35em}.quarto-listing-category.category-cloud .category-cloud-10{font-size:2.55em}.quarto-listing-cols-1{grid-template-columns:repeat(1, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-1{grid-template-columns:repeat(1, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-1{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-2{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-2{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-2{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-3{grid-template-columns:repeat(3, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-3{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-3{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-4{grid-template-columns:repeat(4, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-4{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-4{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-5{grid-template-columns:repeat(5, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-5{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-5{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-6{grid-template-columns:repeat(6, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-6{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-6{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-7{grid-template-columns:repeat(7, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-7{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-7{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-8{grid-template-columns:repeat(8, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-8{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-8{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-9{grid-template-columns:repeat(9, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-9{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-9{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-10{grid-template-columns:repeat(10, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-10{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-10{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-11{grid-template-columns:repeat(11, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-11{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-11{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-12{grid-template-columns:repeat(12, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-12{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-12{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-grid{gap:1.5em}.quarto-grid-item.borderless{border:none}.quarto-grid-item.borderless .listing-categories .listing-category:last-of-type,.quarto-grid-item.borderless .listing-categories .listing-category:first-of-type{padding-left:0}.quarto-grid-item.borderless .listing-categories .listing-category{border:0}.quarto-grid-link{text-decoration:none;color:inherit}.quarto-grid-link:hover{text-decoration:none;color:inherit}.quarto-grid-item h5.title,.quarto-grid-item .title.h5{margin-top:0;margin-bottom:0}.quarto-grid-item .card-footer{display:flex;justify-content:space-between;font-size:.8em}.quarto-grid-item .card-footer p{margin-bottom:0}.quarto-grid-item p.card-img-top{margin-bottom:0}.quarto-grid-item img.thumbnail-image{object-fit:cover}.quarto-grid-item .card-other-values{margin-top:.5em;font-size:.8em}.quarto-grid-item .card-other-values tr{margin-bottom:.5em}.quarto-grid-item .card-other-values tr>td:first-of-type{font-weight:600;padding-right:1em;padding-left:1em;vertical-align:top}.quarto-grid-item div.post-contents{display:flex;flex-direction:column;text-decoration:none;height:100%}.quarto-grid-item div.card-img-bg{background-color:#adb5bd;flex-shrink:0}.quarto-grid-item .card-attribution{padding-top:1em;display:flex;gap:1em;text-transform:uppercase;color:#6c757d;font-weight:500;flex-grow:10;align-items:flex-end}.quarto-grid-item .description{padding-bottom:1em}.quarto-grid-item .card-attribution .date{align-self:flex-end}.quarto-grid-item .card-attribution.justify{justify-content:space-between}.quarto-grid-item .card-attribution.start{justify-content:flex-start}.quarto-grid-item .card-attribution.end{justify-content:flex-end}.quarto-grid-item .card-title{margin-bottom:.1em}.quarto-grid-item .card-subtitle{padding-top:.25em}.quarto-grid-item .card-text{font-size:.9em}.quarto-grid-item .listing-reading-time{padding-bottom:.25em}.quarto-grid-item .card-text-small{font-size:.8em}.quarto-grid-item .card-subtitle.subtitle{font-size:.9em;font-weight:600;padding-bottom:.5em}.quarto-grid-item .listing-categories{display:flex;flex-wrap:wrap;padding-bottom:5px}.quarto-grid-item .listing-categories .listing-category{color:#6c757d;border:solid 1px #dee2e6;border-radius:.25rem;text-transform:uppercase;font-size:.65em;padding-left:.5em;padding-right:.5em;padding-top:.15em;padding-bottom:.15em;cursor:pointer;margin-right:4px;margin-bottom:4px}.quarto-grid-item.card-right{text-align:right}.quarto-grid-item.card-right .listing-categories{justify-content:flex-end}.quarto-grid-item.card-left{text-align:left}.quarto-grid-item.card-center{text-align:center}.quarto-grid-item.card-center .listing-description{text-align:justify}.quarto-grid-item.card-center .listing-categories{justify-content:center}table.quarto-listing-table td.image{padding:0px}table.quarto-listing-table td.image img{width:100%;max-width:50px;object-fit:contain}table.quarto-listing-table a{text-decoration:none}table.quarto-listing-table th a{color:inherit}table.quarto-listing-table th a.asc:after{margin-bottom:-2px;margin-left:5px;display:inline-block;height:1rem;width:1rem;background-repeat:no-repeat;background-size:1rem 1rem;background-image:url('data:image/svg+xml,');content:""}table.quarto-listing-table th a.desc:after{margin-bottom:-2px;margin-left:5px;display:inline-block;height:1rem;width:1rem;background-repeat:no-repeat;background-size:1rem 1rem;background-image:url('data:image/svg+xml,');content:""}table.quarto-listing-table.table-hover td{cursor:pointer}.quarto-post.image-left{flex-direction:row}.quarto-post.image-right{flex-direction:row-reverse}@media(max-width: 767.98px){.quarto-post.image-right,.quarto-post.image-left{gap:0em;flex-direction:column}.quarto-post .metadata{padding-bottom:1em;order:2}.quarto-post .body{order:1}.quarto-post .thumbnail{order:3}}.list.quarto-listing-default div:last-of-type{border-bottom:none}@media(min-width: 992px){.quarto-listing-container-default{margin-right:2em}}div.quarto-post{display:flex;gap:2em;margin-bottom:1.5em;border-bottom:1px solid #dee2e6}@media(max-width: 767.98px){div.quarto-post{padding-bottom:1em}}div.quarto-post .metadata{flex-basis:20%;flex-grow:0;margin-top:.2em;flex-shrink:10}div.quarto-post .thumbnail{flex-basis:30%;flex-grow:0;flex-shrink:0}div.quarto-post .thumbnail img{margin-top:.4em;width:100%;object-fit:cover}div.quarto-post .body{flex-basis:45%;flex-grow:1;flex-shrink:0}div.quarto-post .body h3.listing-title,div.quarto-post .body .listing-title.h3{margin-top:0px;margin-bottom:0px;border-bottom:none}div.quarto-post .body .listing-subtitle{font-size:.875em;margin-bottom:.5em;margin-top:.2em}div.quarto-post .body .description{font-size:.9em}div.quarto-post a{color:#373a3c;display:flex;flex-direction:column;text-decoration:none}div.quarto-post a div.description{flex-shrink:0}div.quarto-post .metadata{display:flex;flex-direction:column;font-size:.8em;font-family:var(--bs-font-sans-serif);flex-basis:33%}div.quarto-post .listing-categories{display:flex;flex-wrap:wrap;padding-bottom:5px}div.quarto-post .listing-categories .listing-category{color:#6c757d;border:solid 1px #dee2e6;border-radius:.25rem;text-transform:uppercase;font-size:.65em;padding-left:.5em;padding-right:.5em;padding-top:.15em;padding-bottom:.15em;cursor:pointer;margin-right:4px;margin-bottom:4px}div.quarto-post .listing-description{margin-bottom:.5em}div.quarto-about-jolla{display:flex !important;flex-direction:column;align-items:center;margin-top:10%;padding-bottom:1em}div.quarto-about-jolla .about-image{object-fit:cover;margin-left:auto;margin-right:auto;margin-bottom:1.5em}div.quarto-about-jolla img.round{border-radius:50%}div.quarto-about-jolla img.rounded{border-radius:10px}div.quarto-about-jolla .quarto-title h1.title,div.quarto-about-jolla .quarto-title .title.h1{text-align:center}div.quarto-about-jolla .quarto-title .description{text-align:center}div.quarto-about-jolla h2,div.quarto-about-jolla .h2{border-bottom:none}div.quarto-about-jolla .about-sep{width:60%}div.quarto-about-jolla main{text-align:center}div.quarto-about-jolla .about-links{display:flex}@media(min-width: 992px){div.quarto-about-jolla .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-jolla .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-jolla .about-link{color:#686d71;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-jolla .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-jolla .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-jolla .about-link:hover{color:#2780e3}div.quarto-about-jolla .about-link i.bi{margin-right:.15em}div.quarto-about-solana{display:flex !important;flex-direction:column;padding-top:3em !important;padding-bottom:1em}div.quarto-about-solana .about-entity{display:flex !important;align-items:start;justify-content:space-between}@media(min-width: 992px){div.quarto-about-solana .about-entity{flex-direction:row}}@media(max-width: 991.98px){div.quarto-about-solana .about-entity{flex-direction:column-reverse;align-items:center;text-align:center}}div.quarto-about-solana .about-entity .entity-contents{display:flex;flex-direction:column}@media(max-width: 767.98px){div.quarto-about-solana .about-entity .entity-contents{width:100%}}div.quarto-about-solana .about-entity .about-image{object-fit:cover}@media(max-width: 991.98px){div.quarto-about-solana .about-entity .about-image{margin-bottom:1.5em}}div.quarto-about-solana .about-entity img.round{border-radius:50%}div.quarto-about-solana .about-entity img.rounded{border-radius:10px}div.quarto-about-solana .about-entity .about-links{display:flex;justify-content:left;padding-bottom:1.2em}@media(min-width: 992px){div.quarto-about-solana .about-entity .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-solana .about-entity .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-solana .about-entity .about-link{color:#686d71;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-solana .about-entity .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-solana .about-entity .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-solana .about-entity .about-link:hover{color:#2780e3}div.quarto-about-solana .about-entity .about-link i.bi{margin-right:.15em}div.quarto-about-solana .about-contents{padding-right:1.5em;flex-basis:0;flex-grow:1}div.quarto-about-solana .about-contents main.content{margin-top:0}div.quarto-about-solana .about-contents h2,div.quarto-about-solana .about-contents .h2{border-bottom:none}div.quarto-about-trestles{display:flex !important;flex-direction:row;padding-top:3em !important;padding-bottom:1em}@media(max-width: 991.98px){div.quarto-about-trestles{flex-direction:column;padding-top:0em !important}}div.quarto-about-trestles .about-entity{display:flex !important;flex-direction:column;align-items:center;text-align:center;padding-right:1em}@media(min-width: 992px){div.quarto-about-trestles .about-entity{flex:0 0 42%}}div.quarto-about-trestles .about-entity .about-image{object-fit:cover;margin-bottom:1.5em}div.quarto-about-trestles .about-entity img.round{border-radius:50%}div.quarto-about-trestles .about-entity img.rounded{border-radius:10px}div.quarto-about-trestles .about-entity .about-links{display:flex;justify-content:center}@media(min-width: 992px){div.quarto-about-trestles .about-entity .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-trestles .about-entity .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-trestles .about-entity .about-link{color:#686d71;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-trestles .about-entity .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-trestles .about-entity .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-trestles .about-entity .about-link:hover{color:#2780e3}div.quarto-about-trestles .about-entity .about-link i.bi{margin-right:.15em}div.quarto-about-trestles .about-contents{flex-basis:0;flex-grow:1}div.quarto-about-trestles .about-contents h2,div.quarto-about-trestles .about-contents .h2{border-bottom:none}@media(min-width: 992px){div.quarto-about-trestles .about-contents{border-left:solid 1px #dee2e6;padding-left:1.5em}}div.quarto-about-trestles .about-contents main.content{margin-top:0}div.quarto-about-marquee{padding-bottom:1em}div.quarto-about-marquee .about-contents{display:flex;flex-direction:column}div.quarto-about-marquee .about-image{max-height:550px;margin-bottom:1.5em;object-fit:cover}div.quarto-about-marquee img.round{border-radius:50%}div.quarto-about-marquee img.rounded{border-radius:10px}div.quarto-about-marquee h2,div.quarto-about-marquee .h2{border-bottom:none}div.quarto-about-marquee .about-links{display:flex;justify-content:center;padding-top:1.5em}@media(min-width: 992px){div.quarto-about-marquee .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-marquee .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-marquee .about-link{color:#686d71;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-marquee .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-marquee .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-marquee .about-link:hover{color:#2780e3}div.quarto-about-marquee .about-link i.bi{margin-right:.15em}@media(min-width: 992px){div.quarto-about-marquee .about-link{border:none}}div.quarto-about-broadside{display:flex;flex-direction:column;padding-bottom:1em}div.quarto-about-broadside .about-main{display:flex !important;padding-top:0 !important}@media(min-width: 992px){div.quarto-about-broadside .about-main{flex-direction:row;align-items:flex-start}}@media(max-width: 991.98px){div.quarto-about-broadside .about-main{flex-direction:column}}@media(max-width: 991.98px){div.quarto-about-broadside .about-main .about-entity{flex-shrink:0;width:100%;height:450px;margin-bottom:1.5em;background-size:cover;background-repeat:no-repeat}}@media(min-width: 992px){div.quarto-about-broadside .about-main .about-entity{flex:0 10 50%;margin-right:1.5em;width:100%;height:100%;background-size:100%;background-repeat:no-repeat}}div.quarto-about-broadside .about-main .about-contents{padding-top:14px;flex:0 0 50%}div.quarto-about-broadside h2,div.quarto-about-broadside .h2{border-bottom:none}div.quarto-about-broadside .about-sep{margin-top:1.5em;width:60%;align-self:center}div.quarto-about-broadside .about-links{display:flex;justify-content:center;column-gap:20px;padding-top:1.5em}@media(min-width: 992px){div.quarto-about-broadside .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-broadside .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-broadside .about-link{color:#686d71;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-broadside .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-broadside .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-broadside .about-link:hover{color:#2780e3}div.quarto-about-broadside .about-link i.bi{margin-right:.15em}@media(min-width: 992px){div.quarto-about-broadside .about-link{border:none}}.tippy-box[data-theme~=quarto]{background-color:#fff;color:#373a3c;border-radius:.25rem;border:solid 1px #dee2e6;font-size:.875rem}.tippy-box[data-theme~=quarto] .tippy-arrow{color:#dee2e6}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:-1px}.tippy-box[data-placement^=bottom]>.tippy-content{padding:.75em 1em;z-index:1}.top-right{position:absolute;top:1em;right:1em}.hidden{display:none !important}.zindex-bottom{z-index:-1 !important}.quarto-layout-panel{margin-bottom:1em}.quarto-layout-panel>figure{width:100%}.quarto-layout-panel>figure>figcaption,.quarto-layout-panel>.panel-caption{margin-top:10pt}.quarto-layout-panel>.table-caption{margin-top:0px}.table-caption p{margin-bottom:.5em}.quarto-layout-row{display:flex;flex-direction:row;align-items:flex-start}.quarto-layout-valign-top{align-items:flex-start}.quarto-layout-valign-bottom{align-items:flex-end}.quarto-layout-valign-center{align-items:center}.quarto-layout-cell{position:relative;margin-right:20px}.quarto-layout-cell:last-child{margin-right:0}.quarto-layout-cell figure,.quarto-layout-cell>p{margin:.2em}.quarto-layout-cell img{max-width:100%}.quarto-layout-cell .html-widget{width:100% !important}.quarto-layout-cell div figure p{margin:0}.quarto-layout-cell figure{display:inline-block;margin-inline-start:0;margin-inline-end:0}.quarto-layout-cell table{display:inline-table}.quarto-layout-cell-subref figcaption,figure .quarto-layout-row figure figcaption{text-align:center;font-style:italic}.quarto-figure{position:relative;margin-bottom:1em}.quarto-figure>figure{width:100%;margin-bottom:0}.quarto-figure-left>figure>p{text-align:left}.quarto-figure-center>figure>p{text-align:center}.quarto-figure-right>figure>p{text-align:right}figure>p:empty{display:none}figure>p:first-child{margin-top:0;margin-bottom:0}figure>figcaption{margin-top:.5em}div[id^=tbl-]{position:relative}.quarto-figure>.anchorjs-link,div[id^=tbl-]>.anchorjs-link{position:absolute;top:0;right:0}.quarto-figure:hover>.anchorjs-link,div[id^=tbl-]:hover>.anchorjs-link,h2:hover>.anchorjs-link,.h2:hover>.anchorjs-link,h3:hover>.anchorjs-link,.h3:hover>.anchorjs-link,h4:hover>.anchorjs-link,.h4:hover>.anchorjs-link,h5:hover>.anchorjs-link,.h5:hover>.anchorjs-link,h6:hover>.anchorjs-link,.h6:hover>.anchorjs-link,.reveal-anchorjs-link>.anchorjs-link{opacity:1}#title-block-header{margin-block-end:1rem;position:relative;margin-top:-1px}#title-block-header .abstract{margin-block-start:1rem}#title-block-header .abstract .abstract-title{font-weight:600}#title-block-header a{text-decoration:none}#title-block-header .author,#title-block-header .date,#title-block-header .doi{margin-block-end:.2rem}#title-block-header .quarto-title-block>div{display:flex}#title-block-header .quarto-title-block>div>h1,#title-block-header .quarto-title-block>div>.h1{flex-grow:1}#title-block-header .quarto-title-block>div>button{flex-shrink:0;height:2.25rem;margin-top:0}@media(min-width: 992px){#title-block-header .quarto-title-block>div>button{margin-top:5px}}tr.header>th>p:last-of-type{margin-bottom:0px}table,.table{caption-side:top;margin-bottom:1.5rem}caption,.table-caption{padding-top:.5rem;padding-bottom:.5rem;text-align:center}.utterances{max-width:none;margin-left:-8px}iframe{margin-bottom:1em}details{margin-bottom:1em}details[show]{margin-bottom:0}details>summary{color:#6c757d}details>summary>p:only-child{display:inline}pre.sourceCode,code.sourceCode{position:relative}p code:not(.sourceCode){white-space:pre-wrap}code{white-space:pre}@media print{code{white-space:pre-wrap}}pre>code{display:block}pre>code.sourceCode{white-space:pre}pre>code.sourceCode>span>a:first-child::before{text-decoration:none}pre.code-overflow-wrap>code.sourceCode{white-space:pre-wrap}pre.code-overflow-scroll>code.sourceCode{white-space:pre}code a:any-link{color:inherit;text-decoration:none}code a:hover{color:inherit;text-decoration:underline}ul.task-list{padding-left:1em}[data-tippy-root]{display:inline-block}.tippy-content .footnote-back{display:none}.quarto-embedded-source-code{display:none}.quarto-unresolved-ref{font-weight:600}.quarto-cover-image{max-width:35%;float:right;margin-left:30px}.cell-output-display .widget-subarea{margin-bottom:1em}.cell-output-display:not(.no-overflow-x){overflow-x:auto}.panel-input{margin-bottom:1em}.panel-input>div,.panel-input>div>div{display:inline-block;vertical-align:top;padding-right:12px}.panel-input>p:last-child{margin-bottom:0}.layout-sidebar{margin-bottom:1em}.layout-sidebar .tab-content{border:none}.tab-content>.page-columns.active{display:grid}div.sourceCode>iframe{width:100%;height:300px;margin-bottom:-0.5em}div.ansi-escaped-output{font-family:monospace;display:block}/*! +* +* ansi colors from IPython notebook's +* +*/.ansi-black-fg{color:#3e424d}.ansi-black-bg{background-color:#3e424d}.ansi-black-intense-fg{color:#282c36}.ansi-black-intense-bg{background-color:#282c36}.ansi-red-fg{color:#e75c58}.ansi-red-bg{background-color:#e75c58}.ansi-red-intense-fg{color:#b22b31}.ansi-red-intense-bg{background-color:#b22b31}.ansi-green-fg{color:#00a250}.ansi-green-bg{background-color:#00a250}.ansi-green-intense-fg{color:#007427}.ansi-green-intense-bg{background-color:#007427}.ansi-yellow-fg{color:#ddb62b}.ansi-yellow-bg{background-color:#ddb62b}.ansi-yellow-intense-fg{color:#b27d12}.ansi-yellow-intense-bg{background-color:#b27d12}.ansi-blue-fg{color:#208ffb}.ansi-blue-bg{background-color:#208ffb}.ansi-blue-intense-fg{color:#0065ca}.ansi-blue-intense-bg{background-color:#0065ca}.ansi-magenta-fg{color:#d160c4}.ansi-magenta-bg{background-color:#d160c4}.ansi-magenta-intense-fg{color:#a03196}.ansi-magenta-intense-bg{background-color:#a03196}.ansi-cyan-fg{color:#60c6c8}.ansi-cyan-bg{background-color:#60c6c8}.ansi-cyan-intense-fg{color:#258f8f}.ansi-cyan-intense-bg{background-color:#258f8f}.ansi-white-fg{color:#c5c1b4}.ansi-white-bg{background-color:#c5c1b4}.ansi-white-intense-fg{color:#a1a6b2}.ansi-white-intense-bg{background-color:#a1a6b2}.ansi-default-inverse-fg{color:#fff}.ansi-default-inverse-bg{background-color:#000}.ansi-bold{font-weight:bold}.ansi-underline{text-decoration:underline}:root{--quarto-body-bg: #fff;--quarto-body-color: #373a3c;--quarto-text-muted: #6c757d;--quarto-border-color: #dee2e6;--quarto-border-width: 1px;--quarto-border-radius: 0.25rem}table.gt_table{color:var(--quarto-body-color);font-size:1em;width:100%;background-color:transparent;border-top-width:inherit;border-bottom-width:inherit;border-color:var(--quarto-border-color)}table.gt_table th.gt_column_spanner_outer{color:var(--quarto-body-color);background-color:transparent;border-top-width:inherit;border-bottom-width:inherit;border-color:var(--quarto-border-color)}table.gt_table th.gt_col_heading{color:var(--quarto-body-color);font-weight:bold;background-color:transparent}table.gt_table thead.gt_col_headings{border-bottom:1px solid currentColor;border-top-width:inherit;border-top-color:var(--quarto-border-color)}table.gt_table thead.gt_col_headings:not(:first-child){border-top-width:1px;border-top-color:var(--quarto-border-color)}table.gt_table td.gt_row{border-bottom-width:1px;border-bottom-color:var(--quarto-border-color);border-top-width:0px}table.gt_table tbody.gt_table_body{border-top-width:1px;border-bottom-width:1px;border-bottom-color:var(--quarto-border-color);border-top-color:currentColor}div.columns{display:initial;gap:initial}div.column{display:inline-block;overflow-x:initial;vertical-align:top;width:50%}@media print{:root{font-size:11pt}#quarto-sidebar,#TOC,.nav-page{display:none}.page-columns .content{grid-column-start:page-start}.fixed-top{position:relative}.panel-caption,.figure-caption,figcaption{color:#666}}.code-copy-button{position:absolute;top:0;right:0;border:0;margin-top:5px;margin-right:5px;background-color:transparent}.code-copy-button:focus{outline:none}.code-copy-button-tooltip{font-size:.75em}pre.sourceCode:hover>.code-copy-button>.bi::before{display:inline-block;height:1rem;width:1rem;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:1rem 1rem}pre.sourceCode:hover>.code-copy-button-checked>.bi::before{background-image:url('data:image/svg+xml,')}pre.sourceCode:hover>.code-copy-button:hover>.bi::before{background-image:url('data:image/svg+xml,')}pre.sourceCode:hover>.code-copy-button-checked:hover>.bi::before{background-image:url('data:image/svg+xml,')}main ol ol,main ul ul,main ol ul,main ul ol{margin-bottom:1em}body{margin:0}main.page-columns>header>h1.title,main.page-columns>header>.title.h1{margin-bottom:0}@media(min-width: 992px){body .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset] 35px [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(500px, calc(850px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.fullcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset] 35px [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(500px, calc(850px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] 35px [page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.slimcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset] 35px [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.listing:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc(1200px - 3em)) [body-content-end] 3em [body-end] 50px [body-end-outset] minmax(0px, 250px) [page-end-inset] 50px [page-end] 1fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 175px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 175px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] minmax(25px, 50px) [page-start-inset] minmax(50px, 150px) [body-start-outset] minmax(25px, 50px) [body-start] 1.5em [body-content-start] minmax(500px, calc(800px - 3em)) [body-content-end] 1.5em [body-end] minmax(25px, 50px) [body-end-outset] minmax(50px, 150px) [page-end-inset] minmax(25px, 50px) [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc( 1000px - 3em )) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 100px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc( 1000px - 3em )) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 50px [page-start-inset] minmax(50px, 150px) [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc(800px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(450px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc( 1000px - 3em )) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 100px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 50px [page-start-inset] minmax(50px, 150px) [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(450px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(50px, 150px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] minmax(25px, 50px) [page-start-inset] minmax(50px, 150px) [body-start-outset] minmax(25px, 50px) [body-start] 1.5em [body-content-start] minmax(500px, calc(800px - 3em)) [body-content-end] 1.5em [body-end] minmax(25px, 50px) [body-end-outset] minmax(50px, 150px) [page-end-inset] minmax(25px, 50px) [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}}@media(max-width: 991.98px){body .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.fullcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.slimcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.listing:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc(1200px - 3em)) [body-content-end body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 145px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc(750px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 145px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc(750px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(75px, 150px) [page-end-inset] 25px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(25px, 50px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc( 1000px - 3em )) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc(800px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(25px, 50px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 4fr [screen-end-inset] 1.5em [screen-end]}body.floating.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(75px, 150px) [page-end-inset] 25px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}}@media(max-width: 767.98px){body .page-columns,body.fullcontent:not(.floating):not(.docked) .page-columns,body.slimcontent:not(.floating):not(.docked) .page-columns,body.docked .page-columns,body.docked.slimcontent .page-columns,body.docked.fullcontent .page-columns,body.floating .page-columns,body.floating.slimcontent .page-columns,body.floating.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(0px, 1fr) [body-content-end body-end body-end-outset page-end-inset page-end screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(0px, 1fr) [body-content-end body-end body-end-outset page-end-inset page-end screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(0px, 1fr) [body-content-end body-end body-end-outset page-end-inset page-end screen-end-inset] 1.5em [screen-end]}nav[role=doc-toc]{display:none}}body,.page-row-navigation{grid-template-rows:[page-top] max-content [contents-top] max-content [contents-bottom] max-content [page-bottom]}.page-rows-contents{grid-template-rows:[content-top] minmax(max-content, 1fr) [content-bottom] minmax(60px, max-content) [page-bottom]}.page-full{grid-column:screen-start/screen-end !important}.page-columns>*{grid-column:body-content-start/body-content-end}.page-columns.column-page>*{grid-column:page-start/page-end}.page-columns.column-page-left>*{grid-column:page-start/body-content-end}.page-columns.column-page-right>*{grid-column:body-content-start/page-end}.page-rows{grid-auto-rows:auto}.header{grid-column:screen-start/screen-end;grid-row:page-top/contents-top}#quarto-content{padding:0;grid-column:screen-start/screen-end;grid-row:contents-top/contents-bottom}body.floating .sidebar.sidebar-navigation{grid-column:page-start/body-start;grid-row:content-top/page-bottom}body.docked .sidebar.sidebar-navigation{grid-column:screen-start/body-start;grid-row:content-top/page-bottom}.sidebar.toc-left{grid-column:page-start/body-start;grid-row:content-top/page-bottom}.sidebar.margin-sidebar{grid-column:body-end/page-end;grid-row:content-top/page-bottom}.page-columns .content{grid-column:body-content-start/body-content-end;grid-row:content-top/content-bottom;align-content:flex-start}.page-columns .page-navigation{grid-column:body-content-start/body-content-end;grid-row:content-bottom/page-bottom}.page-columns .footer{grid-column:screen-start/screen-end;grid-row:contents-bottom/page-bottom}.page-columns .column-body{grid-column:body-content-start/body-content-end}.page-columns .column-body-fullbleed{grid-column:body-start/body-end}.page-columns .column-body-outset{grid-column:body-start-outset/body-end-outset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-body-outset table{background:#fff}.page-columns .column-body-outset-left{grid-column:body-start-outset/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-body-outset-left table{background:#fff}.page-columns .column-body-outset-right{grid-column:body-content-start/body-end-outset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-body-outset-right table{background:#fff}.page-columns .column-page{grid-column:page-start/page-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page table{background:#fff}.page-columns .column-page-inset{grid-column:page-start-inset/page-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-inset table{background:#fff}.page-columns .column-page-inset-left{grid-column:page-start-inset/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-inset-left table{background:#fff}.page-columns .column-page-inset-right{grid-column:body-content-start/page-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-inset-right figcaption table{background:#fff}.page-columns .column-page-left{grid-column:page-start/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-left table{background:#fff}.page-columns .column-page-right{grid-column:body-content-start/page-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-right figcaption table{background:#fff}#quarto-content.page-columns #quarto-margin-sidebar,#quarto-content.page-columns #quarto-sidebar{z-index:1}@media(max-width: 991.98px){#quarto-content.page-columns #quarto-margin-sidebar.collapse,#quarto-content.page-columns #quarto-sidebar.collapse{z-index:1055}}#quarto-content.page-columns main.column-page,#quarto-content.page-columns main.column-page-right,#quarto-content.page-columns main.column-page-left{z-index:0}.page-columns .column-screen-inset{grid-column:screen-start-inset/screen-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset table{background:#fff}.page-columns .column-screen-inset-left{grid-column:screen-start-inset/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset-left table{background:#fff}.page-columns .column-screen-inset-right{grid-column:body-content-start/screen-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset-right table{background:#fff}.page-columns .column-screen{grid-column:screen-start/screen-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen table{background:#fff}.page-columns .column-screen-left{grid-column:screen-start/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-left table{background:#fff}.page-columns .column-screen-right{grid-column:body-content-start/screen-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-right table{background:#fff}.page-columns .column-screen-inset-shaded{grid-column:screen-start/screen-end;padding:1em;background:#f8f9fa;z-index:998;transform:translate3d(0, 0, 0);margin-bottom:1em}.zindex-content{z-index:998;transform:translate3d(0, 0, 0)}.zindex-modal{z-index:1055;transform:translate3d(0, 0, 0)}.zindex-over-content{z-index:999;transform:translate3d(0, 0, 0)}img.img-fluid.column-screen,img.img-fluid.column-screen-inset-shaded,img.img-fluid.column-screen-inset,img.img-fluid.column-screen-inset-left,img.img-fluid.column-screen-inset-right,img.img-fluid.column-screen-left,img.img-fluid.column-screen-right{width:100%}@media(min-width: 992px){.margin-caption,div.aside,aside,.column-margin{grid-column:body-end/page-end !important;z-index:998}.column-sidebar{grid-column:page-start/body-start !important;z-index:998}.column-leftmargin{grid-column:screen-start-inset/body-start !important;z-index:998}.no-row-height{height:1em;overflow:visible}}@media(max-width: 991.98px){.margin-caption,div.aside,aside,.column-margin{grid-column:body-end/page-end !important;z-index:998}.no-row-height{height:1em;overflow:visible}.page-columns.page-full{overflow:visible}.page-columns.toc-left .margin-caption,.page-columns.toc-left div.aside,.page-columns.toc-left aside,.page-columns.toc-left .column-margin{grid-column:body-content-start/body-content-end !important;z-index:998;transform:translate3d(0, 0, 0)}.page-columns.toc-left .no-row-height{height:initial;overflow:initial}}@media(max-width: 767.98px){.margin-caption,div.aside,aside,.column-margin{grid-column:body-content-start/body-content-end !important;z-index:998;transform:translate3d(0, 0, 0)}.no-row-height{height:initial;overflow:initial}#quarto-margin-sidebar{display:none}.hidden-sm{display:none}}.panel-grid{display:grid;grid-template-rows:repeat(1, 1fr);grid-template-columns:repeat(24, 1fr);gap:1em}.panel-grid .g-col-1{grid-column:auto/span 1}.panel-grid .g-col-2{grid-column:auto/span 2}.panel-grid .g-col-3{grid-column:auto/span 3}.panel-grid .g-col-4{grid-column:auto/span 4}.panel-grid .g-col-5{grid-column:auto/span 5}.panel-grid .g-col-6{grid-column:auto/span 6}.panel-grid .g-col-7{grid-column:auto/span 7}.panel-grid .g-col-8{grid-column:auto/span 8}.panel-grid .g-col-9{grid-column:auto/span 9}.panel-grid .g-col-10{grid-column:auto/span 10}.panel-grid .g-col-11{grid-column:auto/span 11}.panel-grid .g-col-12{grid-column:auto/span 12}.panel-grid .g-col-13{grid-column:auto/span 13}.panel-grid .g-col-14{grid-column:auto/span 14}.panel-grid .g-col-15{grid-column:auto/span 15}.panel-grid .g-col-16{grid-column:auto/span 16}.panel-grid .g-col-17{grid-column:auto/span 17}.panel-grid .g-col-18{grid-column:auto/span 18}.panel-grid .g-col-19{grid-column:auto/span 19}.panel-grid .g-col-20{grid-column:auto/span 20}.panel-grid .g-col-21{grid-column:auto/span 21}.panel-grid .g-col-22{grid-column:auto/span 22}.panel-grid .g-col-23{grid-column:auto/span 23}.panel-grid .g-col-24{grid-column:auto/span 24}.panel-grid .g-start-1{grid-column-start:1}.panel-grid .g-start-2{grid-column-start:2}.panel-grid .g-start-3{grid-column-start:3}.panel-grid .g-start-4{grid-column-start:4}.panel-grid .g-start-5{grid-column-start:5}.panel-grid .g-start-6{grid-column-start:6}.panel-grid .g-start-7{grid-column-start:7}.panel-grid .g-start-8{grid-column-start:8}.panel-grid .g-start-9{grid-column-start:9}.panel-grid .g-start-10{grid-column-start:10}.panel-grid .g-start-11{grid-column-start:11}.panel-grid .g-start-12{grid-column-start:12}.panel-grid .g-start-13{grid-column-start:13}.panel-grid .g-start-14{grid-column-start:14}.panel-grid .g-start-15{grid-column-start:15}.panel-grid .g-start-16{grid-column-start:16}.panel-grid .g-start-17{grid-column-start:17}.panel-grid .g-start-18{grid-column-start:18}.panel-grid .g-start-19{grid-column-start:19}.panel-grid .g-start-20{grid-column-start:20}.panel-grid .g-start-21{grid-column-start:21}.panel-grid .g-start-22{grid-column-start:22}.panel-grid .g-start-23{grid-column-start:23}@media(min-width: 576px){.panel-grid .g-col-sm-1{grid-column:auto/span 1}.panel-grid .g-col-sm-2{grid-column:auto/span 2}.panel-grid .g-col-sm-3{grid-column:auto/span 3}.panel-grid .g-col-sm-4{grid-column:auto/span 4}.panel-grid .g-col-sm-5{grid-column:auto/span 5}.panel-grid .g-col-sm-6{grid-column:auto/span 6}.panel-grid .g-col-sm-7{grid-column:auto/span 7}.panel-grid .g-col-sm-8{grid-column:auto/span 8}.panel-grid .g-col-sm-9{grid-column:auto/span 9}.panel-grid .g-col-sm-10{grid-column:auto/span 10}.panel-grid .g-col-sm-11{grid-column:auto/span 11}.panel-grid .g-col-sm-12{grid-column:auto/span 12}.panel-grid .g-col-sm-13{grid-column:auto/span 13}.panel-grid .g-col-sm-14{grid-column:auto/span 14}.panel-grid .g-col-sm-15{grid-column:auto/span 15}.panel-grid .g-col-sm-16{grid-column:auto/span 16}.panel-grid .g-col-sm-17{grid-column:auto/span 17}.panel-grid .g-col-sm-18{grid-column:auto/span 18}.panel-grid .g-col-sm-19{grid-column:auto/span 19}.panel-grid .g-col-sm-20{grid-column:auto/span 20}.panel-grid .g-col-sm-21{grid-column:auto/span 21}.panel-grid .g-col-sm-22{grid-column:auto/span 22}.panel-grid .g-col-sm-23{grid-column:auto/span 23}.panel-grid .g-col-sm-24{grid-column:auto/span 24}.panel-grid .g-start-sm-1{grid-column-start:1}.panel-grid .g-start-sm-2{grid-column-start:2}.panel-grid .g-start-sm-3{grid-column-start:3}.panel-grid .g-start-sm-4{grid-column-start:4}.panel-grid .g-start-sm-5{grid-column-start:5}.panel-grid .g-start-sm-6{grid-column-start:6}.panel-grid .g-start-sm-7{grid-column-start:7}.panel-grid .g-start-sm-8{grid-column-start:8}.panel-grid .g-start-sm-9{grid-column-start:9}.panel-grid .g-start-sm-10{grid-column-start:10}.panel-grid .g-start-sm-11{grid-column-start:11}.panel-grid .g-start-sm-12{grid-column-start:12}.panel-grid .g-start-sm-13{grid-column-start:13}.panel-grid .g-start-sm-14{grid-column-start:14}.panel-grid .g-start-sm-15{grid-column-start:15}.panel-grid .g-start-sm-16{grid-column-start:16}.panel-grid .g-start-sm-17{grid-column-start:17}.panel-grid .g-start-sm-18{grid-column-start:18}.panel-grid .g-start-sm-19{grid-column-start:19}.panel-grid .g-start-sm-20{grid-column-start:20}.panel-grid .g-start-sm-21{grid-column-start:21}.panel-grid .g-start-sm-22{grid-column-start:22}.panel-grid .g-start-sm-23{grid-column-start:23}}@media(min-width: 768px){.panel-grid .g-col-md-1{grid-column:auto/span 1}.panel-grid .g-col-md-2{grid-column:auto/span 2}.panel-grid .g-col-md-3{grid-column:auto/span 3}.panel-grid .g-col-md-4{grid-column:auto/span 4}.panel-grid .g-col-md-5{grid-column:auto/span 5}.panel-grid .g-col-md-6{grid-column:auto/span 6}.panel-grid .g-col-md-7{grid-column:auto/span 7}.panel-grid .g-col-md-8{grid-column:auto/span 8}.panel-grid .g-col-md-9{grid-column:auto/span 9}.panel-grid .g-col-md-10{grid-column:auto/span 10}.panel-grid .g-col-md-11{grid-column:auto/span 11}.panel-grid .g-col-md-12{grid-column:auto/span 12}.panel-grid .g-col-md-13{grid-column:auto/span 13}.panel-grid .g-col-md-14{grid-column:auto/span 14}.panel-grid .g-col-md-15{grid-column:auto/span 15}.panel-grid .g-col-md-16{grid-column:auto/span 16}.panel-grid .g-col-md-17{grid-column:auto/span 17}.panel-grid .g-col-md-18{grid-column:auto/span 18}.panel-grid .g-col-md-19{grid-column:auto/span 19}.panel-grid .g-col-md-20{grid-column:auto/span 20}.panel-grid .g-col-md-21{grid-column:auto/span 21}.panel-grid .g-col-md-22{grid-column:auto/span 22}.panel-grid .g-col-md-23{grid-column:auto/span 23}.panel-grid .g-col-md-24{grid-column:auto/span 24}.panel-grid .g-start-md-1{grid-column-start:1}.panel-grid .g-start-md-2{grid-column-start:2}.panel-grid .g-start-md-3{grid-column-start:3}.panel-grid .g-start-md-4{grid-column-start:4}.panel-grid .g-start-md-5{grid-column-start:5}.panel-grid .g-start-md-6{grid-column-start:6}.panel-grid .g-start-md-7{grid-column-start:7}.panel-grid .g-start-md-8{grid-column-start:8}.panel-grid .g-start-md-9{grid-column-start:9}.panel-grid .g-start-md-10{grid-column-start:10}.panel-grid .g-start-md-11{grid-column-start:11}.panel-grid .g-start-md-12{grid-column-start:12}.panel-grid .g-start-md-13{grid-column-start:13}.panel-grid .g-start-md-14{grid-column-start:14}.panel-grid .g-start-md-15{grid-column-start:15}.panel-grid .g-start-md-16{grid-column-start:16}.panel-grid .g-start-md-17{grid-column-start:17}.panel-grid .g-start-md-18{grid-column-start:18}.panel-grid .g-start-md-19{grid-column-start:19}.panel-grid .g-start-md-20{grid-column-start:20}.panel-grid .g-start-md-21{grid-column-start:21}.panel-grid .g-start-md-22{grid-column-start:22}.panel-grid .g-start-md-23{grid-column-start:23}}@media(min-width: 992px){.panel-grid .g-col-lg-1{grid-column:auto/span 1}.panel-grid .g-col-lg-2{grid-column:auto/span 2}.panel-grid .g-col-lg-3{grid-column:auto/span 3}.panel-grid .g-col-lg-4{grid-column:auto/span 4}.panel-grid .g-col-lg-5{grid-column:auto/span 5}.panel-grid .g-col-lg-6{grid-column:auto/span 6}.panel-grid .g-col-lg-7{grid-column:auto/span 7}.panel-grid .g-col-lg-8{grid-column:auto/span 8}.panel-grid .g-col-lg-9{grid-column:auto/span 9}.panel-grid .g-col-lg-10{grid-column:auto/span 10}.panel-grid .g-col-lg-11{grid-column:auto/span 11}.panel-grid .g-col-lg-12{grid-column:auto/span 12}.panel-grid .g-col-lg-13{grid-column:auto/span 13}.panel-grid .g-col-lg-14{grid-column:auto/span 14}.panel-grid .g-col-lg-15{grid-column:auto/span 15}.panel-grid .g-col-lg-16{grid-column:auto/span 16}.panel-grid .g-col-lg-17{grid-column:auto/span 17}.panel-grid .g-col-lg-18{grid-column:auto/span 18}.panel-grid .g-col-lg-19{grid-column:auto/span 19}.panel-grid .g-col-lg-20{grid-column:auto/span 20}.panel-grid .g-col-lg-21{grid-column:auto/span 21}.panel-grid .g-col-lg-22{grid-column:auto/span 22}.panel-grid .g-col-lg-23{grid-column:auto/span 23}.panel-grid .g-col-lg-24{grid-column:auto/span 24}.panel-grid .g-start-lg-1{grid-column-start:1}.panel-grid .g-start-lg-2{grid-column-start:2}.panel-grid .g-start-lg-3{grid-column-start:3}.panel-grid .g-start-lg-4{grid-column-start:4}.panel-grid .g-start-lg-5{grid-column-start:5}.panel-grid .g-start-lg-6{grid-column-start:6}.panel-grid .g-start-lg-7{grid-column-start:7}.panel-grid .g-start-lg-8{grid-column-start:8}.panel-grid .g-start-lg-9{grid-column-start:9}.panel-grid .g-start-lg-10{grid-column-start:10}.panel-grid .g-start-lg-11{grid-column-start:11}.panel-grid .g-start-lg-12{grid-column-start:12}.panel-grid .g-start-lg-13{grid-column-start:13}.panel-grid .g-start-lg-14{grid-column-start:14}.panel-grid .g-start-lg-15{grid-column-start:15}.panel-grid .g-start-lg-16{grid-column-start:16}.panel-grid .g-start-lg-17{grid-column-start:17}.panel-grid .g-start-lg-18{grid-column-start:18}.panel-grid .g-start-lg-19{grid-column-start:19}.panel-grid .g-start-lg-20{grid-column-start:20}.panel-grid .g-start-lg-21{grid-column-start:21}.panel-grid .g-start-lg-22{grid-column-start:22}.panel-grid .g-start-lg-23{grid-column-start:23}}@media(min-width: 1200px){.panel-grid .g-col-xl-1{grid-column:auto/span 1}.panel-grid .g-col-xl-2{grid-column:auto/span 2}.panel-grid .g-col-xl-3{grid-column:auto/span 3}.panel-grid .g-col-xl-4{grid-column:auto/span 4}.panel-grid .g-col-xl-5{grid-column:auto/span 5}.panel-grid .g-col-xl-6{grid-column:auto/span 6}.panel-grid .g-col-xl-7{grid-column:auto/span 7}.panel-grid .g-col-xl-8{grid-column:auto/span 8}.panel-grid .g-col-xl-9{grid-column:auto/span 9}.panel-grid .g-col-xl-10{grid-column:auto/span 10}.panel-grid .g-col-xl-11{grid-column:auto/span 11}.panel-grid .g-col-xl-12{grid-column:auto/span 12}.panel-grid .g-col-xl-13{grid-column:auto/span 13}.panel-grid .g-col-xl-14{grid-column:auto/span 14}.panel-grid .g-col-xl-15{grid-column:auto/span 15}.panel-grid .g-col-xl-16{grid-column:auto/span 16}.panel-grid .g-col-xl-17{grid-column:auto/span 17}.panel-grid .g-col-xl-18{grid-column:auto/span 18}.panel-grid .g-col-xl-19{grid-column:auto/span 19}.panel-grid .g-col-xl-20{grid-column:auto/span 20}.panel-grid .g-col-xl-21{grid-column:auto/span 21}.panel-grid .g-col-xl-22{grid-column:auto/span 22}.panel-grid .g-col-xl-23{grid-column:auto/span 23}.panel-grid .g-col-xl-24{grid-column:auto/span 24}.panel-grid .g-start-xl-1{grid-column-start:1}.panel-grid .g-start-xl-2{grid-column-start:2}.panel-grid .g-start-xl-3{grid-column-start:3}.panel-grid .g-start-xl-4{grid-column-start:4}.panel-grid .g-start-xl-5{grid-column-start:5}.panel-grid .g-start-xl-6{grid-column-start:6}.panel-grid .g-start-xl-7{grid-column-start:7}.panel-grid .g-start-xl-8{grid-column-start:8}.panel-grid .g-start-xl-9{grid-column-start:9}.panel-grid .g-start-xl-10{grid-column-start:10}.panel-grid .g-start-xl-11{grid-column-start:11}.panel-grid .g-start-xl-12{grid-column-start:12}.panel-grid .g-start-xl-13{grid-column-start:13}.panel-grid .g-start-xl-14{grid-column-start:14}.panel-grid .g-start-xl-15{grid-column-start:15}.panel-grid .g-start-xl-16{grid-column-start:16}.panel-grid .g-start-xl-17{grid-column-start:17}.panel-grid .g-start-xl-18{grid-column-start:18}.panel-grid .g-start-xl-19{grid-column-start:19}.panel-grid .g-start-xl-20{grid-column-start:20}.panel-grid .g-start-xl-21{grid-column-start:21}.panel-grid .g-start-xl-22{grid-column-start:22}.panel-grid .g-start-xl-23{grid-column-start:23}}@media(min-width: 1400px){.panel-grid .g-col-xxl-1{grid-column:auto/span 1}.panel-grid .g-col-xxl-2{grid-column:auto/span 2}.panel-grid .g-col-xxl-3{grid-column:auto/span 3}.panel-grid .g-col-xxl-4{grid-column:auto/span 4}.panel-grid .g-col-xxl-5{grid-column:auto/span 5}.panel-grid .g-col-xxl-6{grid-column:auto/span 6}.panel-grid .g-col-xxl-7{grid-column:auto/span 7}.panel-grid .g-col-xxl-8{grid-column:auto/span 8}.panel-grid .g-col-xxl-9{grid-column:auto/span 9}.panel-grid .g-col-xxl-10{grid-column:auto/span 10}.panel-grid .g-col-xxl-11{grid-column:auto/span 11}.panel-grid .g-col-xxl-12{grid-column:auto/span 12}.panel-grid .g-col-xxl-13{grid-column:auto/span 13}.panel-grid .g-col-xxl-14{grid-column:auto/span 14}.panel-grid .g-col-xxl-15{grid-column:auto/span 15}.panel-grid .g-col-xxl-16{grid-column:auto/span 16}.panel-grid .g-col-xxl-17{grid-column:auto/span 17}.panel-grid .g-col-xxl-18{grid-column:auto/span 18}.panel-grid .g-col-xxl-19{grid-column:auto/span 19}.panel-grid .g-col-xxl-20{grid-column:auto/span 20}.panel-grid .g-col-xxl-21{grid-column:auto/span 21}.panel-grid .g-col-xxl-22{grid-column:auto/span 22}.panel-grid .g-col-xxl-23{grid-column:auto/span 23}.panel-grid .g-col-xxl-24{grid-column:auto/span 24}.panel-grid .g-start-xxl-1{grid-column-start:1}.panel-grid .g-start-xxl-2{grid-column-start:2}.panel-grid .g-start-xxl-3{grid-column-start:3}.panel-grid .g-start-xxl-4{grid-column-start:4}.panel-grid .g-start-xxl-5{grid-column-start:5}.panel-grid .g-start-xxl-6{grid-column-start:6}.panel-grid .g-start-xxl-7{grid-column-start:7}.panel-grid .g-start-xxl-8{grid-column-start:8}.panel-grid .g-start-xxl-9{grid-column-start:9}.panel-grid .g-start-xxl-10{grid-column-start:10}.panel-grid .g-start-xxl-11{grid-column-start:11}.panel-grid .g-start-xxl-12{grid-column-start:12}.panel-grid .g-start-xxl-13{grid-column-start:13}.panel-grid .g-start-xxl-14{grid-column-start:14}.panel-grid .g-start-xxl-15{grid-column-start:15}.panel-grid .g-start-xxl-16{grid-column-start:16}.panel-grid .g-start-xxl-17{grid-column-start:17}.panel-grid .g-start-xxl-18{grid-column-start:18}.panel-grid .g-start-xxl-19{grid-column-start:19}.panel-grid .g-start-xxl-20{grid-column-start:20}.panel-grid .g-start-xxl-21{grid-column-start:21}.panel-grid .g-start-xxl-22{grid-column-start:22}.panel-grid .g-start-xxl-23{grid-column-start:23}}main{margin-top:1em;margin-bottom:1em}h1,.h1,h2,.h2{margin-top:2rem;margin-bottom:1rem}h1.title,.title.h1{margin-top:0}h2,.h2{border-bottom:1px solid #dee2e6;padding-bottom:.5rem}h3,.h3,h4,.h4{margin-top:1.5rem}.header-section-number{color:#747a7f}.nav-link.active .header-section-number{color:inherit}mark,.mark{padding:0em}.panel-caption,caption,.figure-caption{font-size:1rem}.panel-caption,.figure-caption,figcaption{color:#747a7f}.table-caption,caption{color:#373a3c}.quarto-layout-cell[data-ref-parent] caption{color:#747a7f}.column-margin figcaption,.margin-caption,div.aside,aside,.column-margin{color:#747a7f;font-size:.825rem}.panel-caption.margin-caption{text-align:inherit}.column-margin.column-container p{margin-bottom:0}.column-margin.column-container>*:not(.collapse){padding-top:.5em;padding-bottom:.5em;display:block}.column-margin.column-container>*.collapse:not(.show){display:none}@media(min-width: 768px){.column-margin.column-container .callout-margin-content:first-child{margin-top:4.5em}.column-margin.column-container .callout-margin-content-simple:first-child{margin-top:3.5em}}.margin-caption>*{padding-top:.5em;padding-bottom:.5em}@media(max-width: 767.98px){.quarto-layout-row{flex-direction:column}}.tab-content{margin-top:0px;border-left:#dee2e6 1px solid;border-right:#dee2e6 1px solid;border-bottom:#dee2e6 1px solid;margin-left:0;padding:1em;margin-bottom:1em}@media(max-width: 767.98px){.layout-sidebar{margin-left:0;margin-right:0}}.panel-sidebar,.panel-sidebar .form-control,.panel-input,.panel-input .form-control,.selectize-dropdown{font-size:.9rem}.panel-sidebar .form-control,.panel-input .form-control{padding-top:.1rem}.tab-pane div.sourceCode{margin-top:0px}.tab-pane>p{padding-top:1em}.tab-content>.tab-pane:not(.active){display:none !important}div.sourceCode{background-color:rgba(233,236,239,.65);border:1px solid rgba(233,236,239,.65);border-radius:.25rem}pre.sourceCode{background-color:transparent}pre.sourceCode{border:none;font-size:.875em;overflow:visible !important;padding:.4em}.callout pre.sourceCode{padding-left:0}div.sourceCode{overflow-y:hidden}.callout div.sourceCode{margin-left:initial}.blockquote{font-size:inherit;padding-left:1rem;padding-right:1.5rem;color:#747a7f}.blockquote h1:first-child,.blockquote .h1:first-child,.blockquote h2:first-child,.blockquote .h2:first-child,.blockquote h3:first-child,.blockquote .h3:first-child,.blockquote h4:first-child,.blockquote .h4:first-child,.blockquote h5:first-child,.blockquote .h5:first-child{margin-top:0}pre{background-color:initial;padding:initial;border:initial}p code:not(.sourceCode),li code:not(.sourceCode){background-color:#f7f7f7;padding:.2em}nav p code:not(.sourceCode),nav li code:not(.sourceCode){background-color:transparent;padding:0}#quarto-embedded-source-code-modal>.modal-dialog{max-width:1000px;padding-left:1.75rem;padding-right:1.75rem}#quarto-embedded-source-code-modal>.modal-dialog>.modal-content>.modal-body{padding:0}#quarto-embedded-source-code-modal>.modal-dialog>.modal-content>.modal-body div.sourceCode{margin:0;padding:.2rem .2rem;border-radius:0px;border:none}#quarto-embedded-source-code-modal>.modal-dialog>.modal-content>.modal-header{padding:.7rem}.code-tools-button{font-size:1rem;padding:.15rem .15rem;margin-left:5px;color:#6c757d;background-color:transparent;transition:initial;cursor:pointer}.code-tools-button>.bi::before{display:inline-block;height:1rem;width:1rem;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:1rem 1rem}.code-tools-button:hover>.bi::before{background-image:url('data:image/svg+xml,')}#quarto-embedded-source-code-modal .code-copy-button>.bi::before{background-image:url('data:image/svg+xml,')}#quarto-embedded-source-code-modal .code-copy-button-checked>.bi::before{background-image:url('data:image/svg+xml,')}.sidebar{will-change:top;transition:top 200ms linear;position:sticky;overflow-y:auto;padding-top:1.2em;max-height:100vh}.sidebar.toc-left,.sidebar.margin-sidebar{top:0px;padding-top:1em}.sidebar.toc-left>*,.sidebar.margin-sidebar>*{padding-top:.5em}.sidebar.quarto-banner-title-block-sidebar>*{padding-top:1.65em}.sidebar nav[role=doc-toc]>h2,.sidebar nav[role=doc-toc]>.h2{font-size:.875rem;font-weight:400;margin-bottom:.5rem;margin-top:.3rem;font-family:inherit;border-bottom:0;padding-bottom:0;padding-top:0px}.sidebar nav[role=doc-toc]>ul a{border-left:1px solid #e9ecef;padding-left:.6rem}.sidebar nav[role=doc-toc]>ul a:empty{display:none}.sidebar nav[role=doc-toc] ul{padding-left:0;list-style:none;font-size:.875rem;font-weight:300}.sidebar nav[role=doc-toc]>ul li a{line-height:1.1rem;padding-bottom:.2rem;padding-top:.2rem;color:inherit}.sidebar nav[role=doc-toc] ul>li>ul>li>a{padding-left:1.2em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>a{padding-left:2.4em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>ul>li>a{padding-left:3.6em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>ul>li>ul>li>a{padding-left:4.8em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>ul>li>ul>li>ul>li>a{padding-left:6em}.sidebar nav[role=doc-toc] ul>li>ul>li>a.active{border-left:1px solid #2780e3;color:#2780e3 !important}.sidebar nav[role=doc-toc] ul>li>a.active{border-left:1px solid #2780e3;color:#2780e3 !important}kbd,.kbd{color:#373a3c;background-color:#f8f9fa;border:1px solid;border-radius:5px;border-color:#dee2e6}div.hanging-indent{margin-left:1em;text-indent:-1em}.citation a,.footnote-ref{text-decoration:none}.footnotes ol{padding-left:1em}.tippy-content>*{margin-bottom:.7em}.tippy-content>*:last-child{margin-bottom:0}.table a{word-break:break-word}.table>:not(:first-child){border-top-width:1px;border-top-color:#dee2e6}.table>thead{border-bottom:1px solid currentColor}.table>tbody{border-top:1px solid #dee2e6}.callout{margin-top:1.25rem;margin-bottom:1.25rem;border-radius:.25rem}.callout.callout-style-simple{padding:.4em .7em;border-left:5px solid;border-right:1px solid #dee2e6;border-top:1px solid #dee2e6;border-bottom:1px solid #dee2e6}.callout.callout-style-default{border-left:5px solid;border-right:1px solid #dee2e6;border-top:1px solid #dee2e6;border-bottom:1px solid #dee2e6}.callout .callout-body-container{flex-grow:1}.callout.callout-style-simple .callout-body{font-size:.9rem;font-weight:400}.callout.callout-style-default .callout-body{font-size:.9rem;font-weight:400}.callout.callout-captioned .callout-body{margin-top:.2em}.callout:not(.no-icon).callout-captioned.callout-style-simple .callout-body{padding-left:1.6em}.callout.callout-captioned>.callout-header{padding-top:.2em;margin-bottom:-0.2em}.callout.callout-style-simple>div.callout-header{border-bottom:none;font-size:.9rem;font-weight:600;opacity:75%}.callout.callout-style-default>div.callout-header{border-bottom:none;font-weight:600;opacity:85%;font-size:.9rem;padding-left:.5em;padding-right:.5em}.callout.callout-style-default div.callout-body{padding-left:.5em;padding-right:.5em}.callout.callout-style-default div.callout-body>:first-child{margin-top:.5em}.callout>div.callout-header[data-bs-toggle=collapse]{cursor:pointer}.callout.callout-style-default .callout-header[aria-expanded=false],.callout.callout-style-default .callout-header[aria-expanded=true]{padding-top:0px;margin-bottom:0px;align-items:center}.callout.callout-captioned .callout-body>:last-child:not(.sourceCode),.callout.callout-captioned .callout-body>div>:last-child:not(.sourceCode){margin-bottom:.5rem}.callout:not(.callout-captioned) .callout-body>:first-child,.callout:not(.callout-captioned) .callout-body>div>:first-child{margin-top:.25rem}.callout:not(.callout-captioned) .callout-body>:last-child,.callout:not(.callout-captioned) .callout-body>div>:last-child{margin-bottom:.2rem}.callout.callout-style-simple .callout-icon::before,.callout.callout-style-simple .callout-toggle::before{height:1rem;width:1rem;display:inline-block;content:"";background-repeat:no-repeat;background-size:1rem 1rem}.callout.callout-style-default .callout-icon::before,.callout.callout-style-default .callout-toggle::before{height:.9rem;width:.9rem;display:inline-block;content:"";background-repeat:no-repeat;background-size:.9rem .9rem}.callout.callout-style-default .callout-toggle::before{margin-top:5px}.callout .callout-btn-toggle .callout-toggle::before{transition:transform .2s linear}.callout .callout-header[aria-expanded=false] .callout-toggle::before{transform:rotate(-90deg)}.callout .callout-header[aria-expanded=true] .callout-toggle::before{transform:none}.callout.callout-style-simple:not(.no-icon) div.callout-icon-container{padding-top:.2em;padding-right:.55em}.callout.callout-style-default:not(.no-icon) div.callout-icon-container{padding-top:.1em;padding-right:.35em}.callout.callout-style-default:not(.no-icon) div.callout-caption-container{margin-top:-1px}.callout.callout-style-default.callout-caution:not(.no-icon) div.callout-icon-container{padding-top:.3em;padding-right:.35em}.callout>.callout-body>.callout-icon-container>.no-icon,.callout>.callout-header>.callout-icon-container>.no-icon{display:none}div.callout.callout{border-left-color:#6c757d}div.callout.callout-style-default>.callout-header{background-color:#6c757d}div.callout-note.callout{border-left-color:#2780e3}div.callout-note.callout-style-default>.callout-header{background-color:#e9f2fc}div.callout-note:not(.callout-captioned) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-note.callout-captioned .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-note .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-tip.callout{border-left-color:#3fb618}div.callout-tip.callout-style-default>.callout-header{background-color:#ecf8e8}div.callout-tip:not(.callout-captioned) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-tip.callout-captioned .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-tip .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-warning.callout{border-left-color:#ff7518}div.callout-warning.callout-style-default>.callout-header{background-color:#fff1e8}div.callout-warning:not(.callout-captioned) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-warning.callout-captioned .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-warning .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-caution.callout{border-left-color:#f0ad4e}div.callout-caution.callout-style-default>.callout-header{background-color:#fef7ed}div.callout-caution:not(.callout-captioned) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-caution.callout-captioned .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-caution .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-important.callout{border-left-color:#ff0039}div.callout-important.callout-style-default>.callout-header{background-color:#ffe6eb}div.callout-important:not(.callout-captioned) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-important.callout-captioned .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-important .callout-toggle::before{background-image:url('data:image/svg+xml,')}.quarto-toggle-container{display:flex;align-items:center}@media(min-width: 992px){.navbar .quarto-color-scheme-toggle{padding-left:.5rem;padding-right:.5rem}}@media(max-width: 767.98px){.navbar .quarto-color-scheme-toggle{padding-left:0;padding-right:0;padding-bottom:.5em}}.quarto-reader-toggle .bi::before,.quarto-color-scheme-toggle .bi::before{display:inline-block;height:1rem;width:1rem;content:"";background-repeat:no-repeat;background-size:1rem 1rem}.navbar-collapse .quarto-color-scheme-toggle{padding-left:.6rem;padding-right:0;margin-top:-12px}.sidebar-navigation{padding-left:20px}.sidebar-navigation .quarto-color-scheme-toggle .bi::before{padding-top:.2rem;margin-bottom:-0.2rem}.sidebar-tools-main .quarto-color-scheme-toggle .bi::before{padding-top:.2rem;margin-bottom:-0.2rem}.navbar .quarto-color-scheme-toggle .bi::before{padding-top:7px;margin-bottom:-7px;padding-left:2px;margin-right:2px}.navbar .quarto-color-scheme-toggle:not(.alternate) .bi::before{background-image:url('data:image/svg+xml,')}.navbar .quarto-color-scheme-toggle.alternate .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-color-scheme-toggle:not(.alternate) .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-color-scheme-toggle.alternate .bi::before{background-image:url('data:image/svg+xml,')}.quarto-sidebar-toggle{border-color:#dee2e6;border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem;border-style:solid;border-width:1px;overflow:hidden;border-top-width:0px;padding-top:0px !important}.quarto-sidebar-toggle-title{cursor:pointer;padding-bottom:2px;margin-left:.25em;text-align:center;font-weight:400;font-size:.775em}#quarto-content .quarto-sidebar-toggle{background:#fafafa}#quarto-content .quarto-sidebar-toggle-title{color:#373a3c}.quarto-sidebar-toggle-icon{color:#dee2e6;margin-right:.5em;float:right;transition:transform .2s ease}.quarto-sidebar-toggle-icon::before{padding-top:5px}.quarto-sidebar-toggle.expanded .quarto-sidebar-toggle-icon{transform:rotate(-180deg)}.quarto-sidebar-toggle.expanded .quarto-sidebar-toggle-title{border-bottom:solid #dee2e6 1px}.quarto-sidebar-toggle-contents{background-color:#fff;padding-right:10px;padding-left:10px;margin-top:0px !important;transition:max-height .5s ease}.quarto-sidebar-toggle.expanded .quarto-sidebar-toggle-contents{padding-top:1em;padding-bottom:10px}.quarto-sidebar-toggle:not(.expanded) .quarto-sidebar-toggle-contents{padding-top:0px !important;padding-bottom:0px}nav[role=doc-toc]{z-index:1020}#quarto-sidebar>*,nav[role=doc-toc]>*{transition:opacity .1s ease,border .1s ease}#quarto-sidebar.slow>*,nav[role=doc-toc].slow>*{transition:opacity .4s ease,border .4s ease}.quarto-color-scheme-toggle:not(.alternate).top-right .bi::before{background-image:url('data:image/svg+xml,')}.quarto-color-scheme-toggle.alternate.top-right .bi::before{background-image:url('data:image/svg+xml,')}#quarto-appendix.default{border-top:1px solid #dee2e6}#quarto-appendix.default{background-color:#fff;padding-top:1.5em;margin-top:2em;z-index:998}#quarto-appendix.default .quarto-appendix-heading{margin-top:0;line-height:1.4em;font-weight:600;opacity:.9;border-bottom:none;margin-bottom:0}#quarto-appendix.default .footnotes ol,#quarto-appendix.default .footnotes ol li>p:last-of-type,#quarto-appendix.default .quarto-appendix-contents>p:last-of-type{margin-bottom:0}#quarto-appendix.default .quarto-appendix-secondary-label{margin-bottom:.4em}#quarto-appendix.default .quarto-appendix-bibtex{font-size:.7em;padding:1em;border:solid 1px #dee2e6;margin-bottom:1em}#quarto-appendix.default .quarto-appendix-bibtex code.sourceCode{white-space:pre-wrap}#quarto-appendix.default .quarto-appendix-citeas{font-size:.9em;padding:1em;border:solid 1px #dee2e6;margin-bottom:1em}#quarto-appendix.default .quarto-appendix-heading{font-size:1em !important}#quarto-appendix.default *[role=doc-endnotes]>ol,#quarto-appendix.default .quarto-appendix-contents>*:not(h2):not(.h2){font-size:.9em}#quarto-appendix.default section{padding-bottom:1.5em}#quarto-appendix.default section *[role=doc-endnotes],#quarto-appendix.default section>*:not(a){opacity:.9;word-wrap:break-word}.btn.btn-quarto,div.cell-output-display .btn-quarto{color:#cbcccc;background-color:#373a3c;border-color:#373a3c}.btn.btn-quarto:hover,div.cell-output-display .btn-quarto:hover{color:#cbcccc;background-color:#555859;border-color:#4b4e50}.btn-check:focus+.btn.btn-quarto,.btn.btn-quarto:focus,.btn-check:focus+div.cell-output-display .btn-quarto,div.cell-output-display .btn-quarto:focus{color:#cbcccc;background-color:#555859;border-color:#4b4e50;box-shadow:0 0 0 .25rem rgba(77,80,82,.5)}.btn-check:checked+.btn.btn-quarto,.btn-check:active+.btn.btn-quarto,.btn.btn-quarto:active,.btn.btn-quarto.active,.show>.btn.btn-quarto.dropdown-toggle,.btn-check:checked+div.cell-output-display .btn-quarto,.btn-check:active+div.cell-output-display .btn-quarto,div.cell-output-display .btn-quarto:active,div.cell-output-display .btn-quarto.active,.show>div.cell-output-display .btn-quarto.dropdown-toggle{color:#fff;background-color:#5f6163;border-color:#4b4e50}.btn-check:checked+.btn.btn-quarto:focus,.btn-check:active+.btn.btn-quarto:focus,.btn.btn-quarto:active:focus,.btn.btn-quarto.active:focus,.show>.btn.btn-quarto.dropdown-toggle:focus,.btn-check:checked+div.cell-output-display .btn-quarto:focus,.btn-check:active+div.cell-output-display .btn-quarto:focus,div.cell-output-display .btn-quarto:active:focus,div.cell-output-display .btn-quarto.active:focus,.show>div.cell-output-display .btn-quarto.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(77,80,82,.5)}.btn.btn-quarto:disabled,.btn.btn-quarto.disabled,div.cell-output-display .btn-quarto:disabled,div.cell-output-display .btn-quarto.disabled{color:#fff;background-color:#373a3c;border-color:#373a3c}nav.quarto-secondary-nav.color-navbar{background-color:#2780e3;color:#fdfeff}nav.quarto-secondary-nav.color-navbar h1,nav.quarto-secondary-nav.color-navbar .h1,nav.quarto-secondary-nav.color-navbar .quarto-btn-toggle{color:#fdfeff}@media(max-width: 991.98px){body.nav-sidebar .quarto-title-banner,body.nav-sidebar .quarto-title-banner{display:none}}p.subtitle{margin-top:.25em;margin-bottom:.5em}code a:any-link{color:inherit;text-decoration-color:#6c757d}/*! light */div.observablehq table thead tr th{background-color:var(--bs-body-bg)}input,button,select,optgroup,textarea{background-color:var(--bs-body-bg)}@media print{.page-columns .column-screen-inset{grid-column:page-start-inset/page-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset table{background:#fff}.page-columns .column-screen-inset-left{grid-column:page-start-inset/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset-left table{background:#fff}.page-columns .column-screen-inset-right{grid-column:body-content-start/page-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset-right table{background:#fff}.page-columns .column-screen{grid-column:page-start/page-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen table{background:#fff}.page-columns .column-screen-left{grid-column:page-start/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-left table{background:#fff}.page-columns .column-screen-right{grid-column:body-content-start/page-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-right table{background:#fff}.page-columns .column-screen-inset-shaded{grid-column:page-start-inset/page-end-inset;padding:1em;background:#f8f9fa;z-index:998;transform:translate3d(0, 0, 0);margin-bottom:1em}}.quarto-video{margin-bottom:1em}a.external:after{display:inline-block;height:.75rem;width:.75rem;margin-bottom:.15em;margin-left:.25em;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:.75rem .75rem}a.external:after:hover{cursor:pointer}.quarto-ext-icon{display:inline-block;font-size:.75em;padding-left:.3em}.code-with-filename .code-with-filename-file{margin-bottom:0;padding-bottom:2px;padding-top:2px;padding-left:.7em;border:var(--quarto-border-width) solid var(--quarto-border-color);border-radius:var(--quarto-border-radius);border-bottom:0;border-bottom-left-radius:0%;border-bottom-right-radius:0%}.code-with-filename div.sourceCode,.reveal .code-with-filename div.sourceCode{margin-top:0;border-top-left-radius:0%;border-top-right-radius:0%}.code-with-filename .code-with-filename-file pre{margin-bottom:0}.code-with-filename .code-with-filename-file,.code-with-filename .code-with-filename-file pre{background-color:rgba(219,219,219,.8)}.quarto-dark .code-with-filename .code-with-filename-file,.quarto-dark .code-with-filename .code-with-filename-file pre{background-color:#555}.code-with-filename .code-with-filename-file strong{font-weight:400}.quarto-title-banner{margin-bottom:1em;color:#fdfeff;background:#2780e3}.quarto-title-banner .code-tools-button{color:#97cbff}.quarto-title-banner .code-tools-button:hover{color:#fdfeff}.quarto-title-banner .code-tools-button>.bi::before{background-image:url('data:image/svg+xml,')}.quarto-title-banner .code-tools-button:hover>.bi::before{background-image:url('data:image/svg+xml,')}.quarto-title-banner .quarto-title .title{font-weight:600}.quarto-title-banner .quarto-categories{margin-top:.75em}@media(min-width: 992px){.quarto-title-banner{padding-top:2.5em;padding-bottom:2.5em}}@media(max-width: 991.98px){.quarto-title-banner{padding-top:1em;padding-bottom:1em}}main.quarto-banner-title-block section:first-of-type h2:first-of-type,main.quarto-banner-title-block section:first-of-type .h2:first-of-type,main.quarto-banner-title-block section:first-of-type h3:first-of-type,main.quarto-banner-title-block section:first-of-type .h3:first-of-type,main.quarto-banner-title-block section:first-of-type h4:first-of-type,main.quarto-banner-title-block section:first-of-type .h4:first-of-type{margin-top:0}.quarto-title .quarto-categories{display:flex;column-gap:.4em;padding-bottom:.5em;margin-top:.75em}.quarto-title .quarto-categories .quarto-category{padding:.25em .75em;font-size:.65em;text-transform:uppercase;border:solid 1px;border-radius:.25rem;opacity:.6}.quarto-title .quarto-categories .quarto-category a{color:inherit}#title-block-header.quarto-title-block.default .quarto-title-meta{display:grid;grid-template-columns:repeat(2, 1fr)}#title-block-header.quarto-title-block.default .quarto-title .title{margin-bottom:0}#title-block-header.quarto-title-block.default .quarto-title-author-orcid img{margin-top:-5px}#title-block-header.quarto-title-block.default .quarto-description p:last-of-type{margin-bottom:0}#title-block-header.quarto-title-block.default .quarto-title-meta-contents p,#title-block-header.quarto-title-block.default .quarto-title-authors p,#title-block-header.quarto-title-block.default .quarto-title-affiliations p{margin-bottom:.1em}#title-block-header.quarto-title-block.default .quarto-title-meta-heading{text-transform:uppercase;margin-top:1em;font-size:.8em;opacity:.8;font-weight:400}#title-block-header.quarto-title-block.default .quarto-title-meta-contents{font-size:.9em}#title-block-header.quarto-title-block.default .quarto-title-meta-contents a{color:#373a3c}#title-block-header.quarto-title-block.default .quarto-title-meta-contents p.affiliation:last-of-type{margin-bottom:.7em}#title-block-header.quarto-title-block.default p.affiliation{margin-bottom:.1em}#title-block-header.quarto-title-block.default .description,#title-block-header.quarto-title-block.default .abstract{margin-top:0}#title-block-header.quarto-title-block.default .description>p,#title-block-header.quarto-title-block.default .abstract>p{font-size:.9em}#title-block-header.quarto-title-block.default .description>p:last-of-type,#title-block-header.quarto-title-block.default .abstract>p:last-of-type{margin-bottom:0}#title-block-header.quarto-title-block.default .description .abstract-title,#title-block-header.quarto-title-block.default .abstract .abstract-title{margin-top:1em;text-transform:uppercase;font-size:.8em;opacity:.8;font-weight:400}#title-block-header.quarto-title-block.default .quarto-title-meta-author{display:grid;grid-template-columns:1fr 1fr}body{-webkit-font-smoothing:antialiased}.badge.bg-light{color:#373a3c}.progress .progress-bar{font-size:8px;line-height:8px}/*# sourceMappingURL=849e9d8c495892417e2ff45fdfb3c533.css.map */ diff --git a/doc/site_libs/bootstrap/bootstrap.min.js b/doc/site_libs/bootstrap/bootstrap.min.js new file mode 100644 index 000000000..cc0a25561 --- /dev/null +++ b/doc/site_libs/bootstrap/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v5.1.3 (https://getbootstrap.com/) + * Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,(function(){"use strict";const t="transitionend",e=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return e},i=t=>{const i=e(t);return i&&document.querySelector(i)?i:null},n=t=>{const i=e(t);return i?document.querySelector(i):null},s=e=>{e.dispatchEvent(new Event(t))},o=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),r=t=>o(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(t):null,a=(t,e,i)=>{Object.keys(i).forEach((n=>{const s=i[n],r=e[n],a=r&&o(r)?"element":null==(l=r)?`${l}`:{}.toString.call(l).match(/\s([a-z]+)/i)[1].toLowerCase();var l;if(!new RegExp(s).test(a))throw new TypeError(`${t.toUpperCase()}: Option "${n}" provided type "${a}" but expected type "${s}".`)}))},l=t=>!(!o(t)||0===t.getClientRects().length)&&"visible"===getComputedStyle(t).getPropertyValue("visibility"),c=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),h=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?h(t.parentNode):null},d=()=>{},u=t=>{t.offsetHeight},f=()=>{const{jQuery:t}=window;return t&&!document.body.hasAttribute("data-bs-no-jquery")?t:null},p=[],m=()=>"rtl"===document.documentElement.dir,g=t=>{var e;e=()=>{const e=f();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(p.length||document.addEventListener("DOMContentLoaded",(()=>{p.forEach((t=>t()))})),p.push(e)):e()},_=t=>{"function"==typeof t&&t()},b=(e,i,n=!0)=>{if(!n)return void _(e);const o=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(i)+5;let r=!1;const a=({target:n})=>{n===i&&(r=!0,i.removeEventListener(t,a),_(e))};i.addEventListener(t,a),setTimeout((()=>{r||s(i)}),o)},v=(t,e,i,n)=>{let s=t.indexOf(e);if(-1===s)return t[!i&&n?t.length-1:0];const o=t.length;return s+=i?1:-1,n&&(s=(s+o)%o),t[Math.max(0,Math.min(s,o-1))]},y=/[^.]*(?=\..*)\.|.*/,w=/\..*/,E=/::\d+$/,A={};let T=1;const O={mouseenter:"mouseover",mouseleave:"mouseout"},C=/^(mouseenter|mouseleave)/i,k=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function L(t,e){return e&&`${e}::${T++}`||t.uidEvent||T++}function x(t){const e=L(t);return t.uidEvent=e,A[e]=A[e]||{},A[e]}function D(t,e,i=null){const n=Object.keys(t);for(let s=0,o=n.length;sfunction(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};n?n=t(n):i=t(i)}const[o,r,a]=S(e,i,n),l=x(t),c=l[a]||(l[a]={}),h=D(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=L(r,e.replace(y,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(let a=o.length;a--;)if(o[a]===r)return s.delegateTarget=r,n.oneOff&&j.off(t,s.type,e,i),i.apply(r,[s]);return null}}(t,i,n):function(t,e){return function i(n){return n.delegateTarget=t,i.oneOff&&j.off(t,n.type,e),e.apply(t,[n])}}(t,i);u.delegationSelector=o?i:null,u.originalHandler=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function I(t,e,i,n,s){const o=D(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function P(t){return t=t.replace(w,""),O[t]||t}const j={on(t,e,i,n){N(t,e,i,n,!1)},one(t,e,i,n){N(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=S(e,i,n),a=r!==e,l=x(t),c=e.startsWith(".");if(void 0!==o){if(!l||!l[r])return;return void I(t,l,r,o,s?i:null)}c&&Object.keys(l).forEach((i=>{!function(t,e,i,n){const s=e[i]||{};Object.keys(s).forEach((o=>{if(o.includes(n)){const n=s[o];I(t,e,i,n.originalHandler,n.delegationSelector)}}))}(t,l,i,e.slice(1))}));const h=l[r]||{};Object.keys(h).forEach((i=>{const n=i.replace(E,"");if(!a||e.includes(n)){const e=h[i];I(t,l,r,e.originalHandler,e.delegationSelector)}}))},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=f(),s=P(e),o=e!==s,r=k.has(s);let a,l=!0,c=!0,h=!1,d=null;return o&&n&&(a=n.Event(e,i),n(t).trigger(a),l=!a.isPropagationStopped(),c=!a.isImmediatePropagationStopped(),h=a.isDefaultPrevented()),r?(d=document.createEvent("HTMLEvents"),d.initEvent(s,l,!0)):d=new CustomEvent(e,{bubbles:l,cancelable:!0}),void 0!==i&&Object.keys(i).forEach((t=>{Object.defineProperty(d,t,{get:()=>i[t]})})),h&&d.preventDefault(),c&&t.dispatchEvent(d),d.defaultPrevented&&void 0!==a&&a.preventDefault(),d}},M=new Map,H={set(t,e,i){M.has(t)||M.set(t,new Map);const n=M.get(t);n.has(e)||0===n.size?n.set(e,i):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(n.keys())[0]}.`)},get:(t,e)=>M.has(t)&&M.get(t).get(e)||null,remove(t,e){if(!M.has(t))return;const i=M.get(t);i.delete(e),0===i.size&&M.delete(t)}};class B{constructor(t){(t=r(t))&&(this._element=t,H.set(this._element,this.constructor.DATA_KEY,this))}dispose(){H.remove(this._element,this.constructor.DATA_KEY),j.off(this._element,this.constructor.EVENT_KEY),Object.getOwnPropertyNames(this).forEach((t=>{this[t]=null}))}_queueCallback(t,e,i=!0){b(t,e,i)}static getInstance(t){return H.get(r(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.1.3"}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}}const R=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,s=t.NAME;j.on(document,i,`[data-bs-dismiss="${s}"]`,(function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),c(this))return;const o=n(this)||this.closest(`.${s}`);t.getOrCreateInstance(o)[e]()}))};class W extends B{static get NAME(){return"alert"}close(){if(j.trigger(this._element,"close.bs.alert").defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback((()=>this._destroyElement()),this._element,t)}_destroyElement(){this._element.remove(),j.trigger(this._element,"closed.bs.alert"),this.dispose()}static jQueryInterface(t){return this.each((function(){const e=W.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}R(W,"close"),g(W);const $='[data-bs-toggle="button"]';class z extends B{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=z.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}function q(t){return"true"===t||"false"!==t&&(t===Number(t).toString()?Number(t):""===t||"null"===t?null:t)}function F(t){return t.replace(/[A-Z]/g,(t=>`-${t.toLowerCase()}`))}j.on(document,"click.bs.button.data-api",$,(t=>{t.preventDefault();const e=t.target.closest($);z.getOrCreateInstance(e).toggle()})),g(z);const U={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${F(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${F(e)}`)},getDataAttributes(t){if(!t)return{};const e={};return Object.keys(t.dataset).filter((t=>t.startsWith("bs"))).forEach((i=>{let n=i.replace(/^bs/,"");n=n.charAt(0).toLowerCase()+n.slice(1,n.length),e[n]=q(t.dataset[i])})),e},getDataAttribute:(t,e)=>q(t.getAttribute(`data-bs-${F(e)}`)),offset(t){const e=t.getBoundingClientRect();return{top:e.top+window.pageYOffset,left:e.left+window.pageXOffset}},position:t=>({top:t.offsetTop,left:t.offsetLeft})},V={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter((t=>t.matches(e))),parents(t,e){const i=[];let n=t.parentNode;for(;n&&n.nodeType===Node.ELEMENT_NODE&&3!==n.nodeType;)n.matches(e)&&i.push(n),n=n.parentNode;return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map((t=>`${t}:not([tabindex^="-"])`)).join(", ");return this.find(e,t).filter((t=>!c(t)&&l(t)))}},K="carousel",X={interval:5e3,keyboard:!0,slide:!1,pause:"hover",wrap:!0,touch:!0},Y={interval:"(number|boolean)",keyboard:"boolean",slide:"(boolean|string)",pause:"(string|boolean)",wrap:"boolean",touch:"boolean"},Q="next",G="prev",Z="left",J="right",tt={ArrowLeft:J,ArrowRight:Z},et="slid.bs.carousel",it="active",nt=".active.carousel-item";class st extends B{constructor(t,e){super(t),this._items=null,this._interval=null,this._activeElement=null,this._isPaused=!1,this._isSliding=!1,this.touchTimeout=null,this.touchStartX=0,this.touchDeltaX=0,this._config=this._getConfig(e),this._indicatorsElement=V.findOne(".carousel-indicators",this._element),this._touchSupported="ontouchstart"in document.documentElement||navigator.maxTouchPoints>0,this._pointerEvent=Boolean(window.PointerEvent),this._addEventListeners()}static get Default(){return X}static get NAME(){return K}next(){this._slide(Q)}nextWhenVisible(){!document.hidden&&l(this._element)&&this.next()}prev(){this._slide(G)}pause(t){t||(this._isPaused=!0),V.findOne(".carousel-item-next, .carousel-item-prev",this._element)&&(s(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null}cycle(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config&&this._config.interval&&!this._isPaused&&(this._updateInterval(),this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))}to(t){this._activeElement=V.findOne(nt,this._element);const e=this._getItemIndex(this._activeElement);if(t>this._items.length-1||t<0)return;if(this._isSliding)return void j.one(this._element,et,(()=>this.to(t)));if(e===t)return this.pause(),void this.cycle();const i=t>e?Q:G;this._slide(i,this._items[t])}_getConfig(t){return t={...X,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(K,t,Y),t}_handleSwipe(){const t=Math.abs(this.touchDeltaX);if(t<=40)return;const e=t/this.touchDeltaX;this.touchDeltaX=0,e&&this._slide(e>0?J:Z)}_addEventListeners(){this._config.keyboard&&j.on(this._element,"keydown.bs.carousel",(t=>this._keydown(t))),"hover"===this._config.pause&&(j.on(this._element,"mouseenter.bs.carousel",(t=>this.pause(t))),j.on(this._element,"mouseleave.bs.carousel",(t=>this.cycle(t)))),this._config.touch&&this._touchSupported&&this._addTouchEventListeners()}_addTouchEventListeners(){const t=t=>this._pointerEvent&&("pen"===t.pointerType||"touch"===t.pointerType),e=e=>{t(e)?this.touchStartX=e.clientX:this._pointerEvent||(this.touchStartX=e.touches[0].clientX)},i=t=>{this.touchDeltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this.touchStartX},n=e=>{t(e)&&(this.touchDeltaX=e.clientX-this.touchStartX),this._handleSwipe(),"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((t=>this.cycle(t)),500+this._config.interval))};V.find(".carousel-item img",this._element).forEach((t=>{j.on(t,"dragstart.bs.carousel",(t=>t.preventDefault()))})),this._pointerEvent?(j.on(this._element,"pointerdown.bs.carousel",(t=>e(t))),j.on(this._element,"pointerup.bs.carousel",(t=>n(t))),this._element.classList.add("pointer-event")):(j.on(this._element,"touchstart.bs.carousel",(t=>e(t))),j.on(this._element,"touchmove.bs.carousel",(t=>i(t))),j.on(this._element,"touchend.bs.carousel",(t=>n(t))))}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=tt[t.key];e&&(t.preventDefault(),this._slide(e))}_getItemIndex(t){return this._items=t&&t.parentNode?V.find(".carousel-item",t.parentNode):[],this._items.indexOf(t)}_getItemByOrder(t,e){const i=t===Q;return v(this._items,e,i,this._config.wrap)}_triggerSlideEvent(t,e){const i=this._getItemIndex(t),n=this._getItemIndex(V.findOne(nt,this._element));return j.trigger(this._element,"slide.bs.carousel",{relatedTarget:t,direction:e,from:n,to:i})}_setActiveIndicatorElement(t){if(this._indicatorsElement){const e=V.findOne(".active",this._indicatorsElement);e.classList.remove(it),e.removeAttribute("aria-current");const i=V.find("[data-bs-target]",this._indicatorsElement);for(let e=0;e{j.trigger(this._element,et,{relatedTarget:o,direction:d,from:s,to:r})};if(this._element.classList.contains("slide")){o.classList.add(h),u(o),n.classList.add(c),o.classList.add(c);const t=()=>{o.classList.remove(c,h),o.classList.add(it),n.classList.remove(it,h,c),this._isSliding=!1,setTimeout(f,0)};this._queueCallback(t,n,!0)}else n.classList.remove(it),o.classList.add(it),this._isSliding=!1,f();a&&this.cycle()}_directionToOrder(t){return[J,Z].includes(t)?m()?t===Z?G:Q:t===Z?Q:G:t}_orderToDirection(t){return[Q,G].includes(t)?m()?t===G?Z:J:t===G?J:Z:t}static carouselInterface(t,e){const i=st.getOrCreateInstance(t,e);let{_config:n}=i;"object"==typeof e&&(n={...n,...e});const s="string"==typeof e?e:n.slide;if("number"==typeof e)i.to(e);else if("string"==typeof s){if(void 0===i[s])throw new TypeError(`No method named "${s}"`);i[s]()}else n.interval&&n.ride&&(i.pause(),i.cycle())}static jQueryInterface(t){return this.each((function(){st.carouselInterface(this,t)}))}static dataApiClickHandler(t){const e=n(this);if(!e||!e.classList.contains("carousel"))return;const i={...U.getDataAttributes(e),...U.getDataAttributes(this)},s=this.getAttribute("data-bs-slide-to");s&&(i.interval=!1),st.carouselInterface(e,i),s&&st.getInstance(e).to(s),t.preventDefault()}}j.on(document,"click.bs.carousel.data-api","[data-bs-slide], [data-bs-slide-to]",st.dataApiClickHandler),j.on(window,"load.bs.carousel.data-api",(()=>{const t=V.find('[data-bs-ride="carousel"]');for(let e=0,i=t.length;et===this._element));null!==s&&o.length&&(this._selector=s,this._triggerArray.push(e))}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return rt}static get NAME(){return ot}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t,e=[];if(this._config.parent){const t=V.find(ut,this._config.parent);e=V.find(".collapse.show, .collapse.collapsing",this._config.parent).filter((e=>!t.includes(e)))}const i=V.findOne(this._selector);if(e.length){const n=e.find((t=>i!==t));if(t=n?pt.getInstance(n):null,t&&t._isTransitioning)return}if(j.trigger(this._element,"show.bs.collapse").defaultPrevented)return;e.forEach((e=>{i!==e&&pt.getOrCreateInstance(e,{toggle:!1}).hide(),t||H.set(e,"bs.collapse",null)}));const n=this._getDimension();this._element.classList.remove(ct),this._element.classList.add(ht),this._element.style[n]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const s=`scroll${n[0].toUpperCase()+n.slice(1)}`;this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(ht),this._element.classList.add(ct,lt),this._element.style[n]="",j.trigger(this._element,"shown.bs.collapse")}),this._element,!0),this._element.style[n]=`${this._element[s]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(j.trigger(this._element,"hide.bs.collapse").defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,u(this._element),this._element.classList.add(ht),this._element.classList.remove(ct,lt);const e=this._triggerArray.length;for(let t=0;t{this._isTransitioning=!1,this._element.classList.remove(ht),this._element.classList.add(ct),j.trigger(this._element,"hidden.bs.collapse")}),this._element,!0)}_isShown(t=this._element){return t.classList.contains(lt)}_getConfig(t){return(t={...rt,...U.getDataAttributes(this._element),...t}).toggle=Boolean(t.toggle),t.parent=r(t.parent),a(ot,t,at),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=V.find(ut,this._config.parent);V.find(ft,this._config.parent).filter((e=>!t.includes(e))).forEach((t=>{const e=n(t);e&&this._addAriaAndCollapsedClass([t],this._isShown(e))}))}_addAriaAndCollapsedClass(t,e){t.length&&t.forEach((t=>{e?t.classList.remove(dt):t.classList.add(dt),t.setAttribute("aria-expanded",e)}))}static jQueryInterface(t){return this.each((function(){const e={};"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1);const i=pt.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}}))}}j.on(document,"click.bs.collapse.data-api",ft,(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();const e=i(this);V.find(e).forEach((t=>{pt.getOrCreateInstance(t,{toggle:!1}).toggle()}))})),g(pt);var mt="top",gt="bottom",_t="right",bt="left",vt="auto",yt=[mt,gt,_t,bt],wt="start",Et="end",At="clippingParents",Tt="viewport",Ot="popper",Ct="reference",kt=yt.reduce((function(t,e){return t.concat([e+"-"+wt,e+"-"+Et])}),[]),Lt=[].concat(yt,[vt]).reduce((function(t,e){return t.concat([e,e+"-"+wt,e+"-"+Et])}),[]),xt="beforeRead",Dt="read",St="afterRead",Nt="beforeMain",It="main",Pt="afterMain",jt="beforeWrite",Mt="write",Ht="afterWrite",Bt=[xt,Dt,St,Nt,It,Pt,jt,Mt,Ht];function Rt(t){return t?(t.nodeName||"").toLowerCase():null}function Wt(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function $t(t){return t instanceof Wt(t).Element||t instanceof Element}function zt(t){return t instanceof Wt(t).HTMLElement||t instanceof HTMLElement}function qt(t){return"undefined"!=typeof ShadowRoot&&(t instanceof Wt(t).ShadowRoot||t instanceof ShadowRoot)}const Ft={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];zt(s)&&Rt(s)&&(Object.assign(s.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});zt(n)&&Rt(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function Ut(t){return t.split("-")[0]}function Vt(t,e){var i=t.getBoundingClientRect();return{width:i.width/1,height:i.height/1,top:i.top/1,right:i.right/1,bottom:i.bottom/1,left:i.left/1,x:i.left/1,y:i.top/1}}function Kt(t){var e=Vt(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function Xt(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&qt(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function Yt(t){return Wt(t).getComputedStyle(t)}function Qt(t){return["table","td","th"].indexOf(Rt(t))>=0}function Gt(t){return(($t(t)?t.ownerDocument:t.document)||window.document).documentElement}function Zt(t){return"html"===Rt(t)?t:t.assignedSlot||t.parentNode||(qt(t)?t.host:null)||Gt(t)}function Jt(t){return zt(t)&&"fixed"!==Yt(t).position?t.offsetParent:null}function te(t){for(var e=Wt(t),i=Jt(t);i&&Qt(i)&&"static"===Yt(i).position;)i=Jt(i);return i&&("html"===Rt(i)||"body"===Rt(i)&&"static"===Yt(i).position)?e:i||function(t){var e=-1!==navigator.userAgent.toLowerCase().indexOf("firefox");if(-1!==navigator.userAgent.indexOf("Trident")&&zt(t)&&"fixed"===Yt(t).position)return null;for(var i=Zt(t);zt(i)&&["html","body"].indexOf(Rt(i))<0;){var n=Yt(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function ee(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}var ie=Math.max,ne=Math.min,se=Math.round;function oe(t,e,i){return ie(t,ne(e,i))}function re(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function ae(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}const le={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,n=t.name,s=t.options,o=i.elements.arrow,r=i.modifiersData.popperOffsets,a=Ut(i.placement),l=ee(a),c=[bt,_t].indexOf(a)>=0?"height":"width";if(o&&r){var h=function(t,e){return re("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:ae(t,yt))}(s.padding,i),d=Kt(o),u="y"===l?mt:bt,f="y"===l?gt:_t,p=i.rects.reference[c]+i.rects.reference[l]-r[l]-i.rects.popper[c],m=r[l]-i.rects.reference[l],g=te(o),_=g?"y"===l?g.clientHeight||0:g.clientWidth||0:0,b=p/2-m/2,v=h[u],y=_-d[c]-h[f],w=_/2-d[c]/2+b,E=oe(v,w,y),A=l;i.modifiersData[n]=((e={})[A]=E,e.centerOffset=E-w,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&Xt(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function ce(t){return t.split("-")[1]}var he={top:"auto",right:"auto",bottom:"auto",left:"auto"};function de(t){var e,i=t.popper,n=t.popperRect,s=t.placement,o=t.variation,r=t.offsets,a=t.position,l=t.gpuAcceleration,c=t.adaptive,h=t.roundOffsets,d=!0===h?function(t){var e=t.x,i=t.y,n=window.devicePixelRatio||1;return{x:se(se(e*n)/n)||0,y:se(se(i*n)/n)||0}}(r):"function"==typeof h?h(r):r,u=d.x,f=void 0===u?0:u,p=d.y,m=void 0===p?0:p,g=r.hasOwnProperty("x"),_=r.hasOwnProperty("y"),b=bt,v=mt,y=window;if(c){var w=te(i),E="clientHeight",A="clientWidth";w===Wt(i)&&"static"!==Yt(w=Gt(i)).position&&"absolute"===a&&(E="scrollHeight",A="scrollWidth"),w=w,s!==mt&&(s!==bt&&s!==_t||o!==Et)||(v=gt,m-=w[E]-n.height,m*=l?1:-1),s!==bt&&(s!==mt&&s!==gt||o!==Et)||(b=_t,f-=w[A]-n.width,f*=l?1:-1)}var T,O=Object.assign({position:a},c&&he);return l?Object.assign({},O,((T={})[v]=_?"0":"",T[b]=g?"0":"",T.transform=(y.devicePixelRatio||1)<=1?"translate("+f+"px, "+m+"px)":"translate3d("+f+"px, "+m+"px, 0)",T)):Object.assign({},O,((e={})[v]=_?m+"px":"",e[b]=g?f+"px":"",e.transform="",e))}const ue={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:Ut(e.placement),variation:ce(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,de(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,de(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}};var fe={passive:!0};const pe={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=Wt(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,fe)})),a&&l.addEventListener("resize",i.update,fe),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,fe)})),a&&l.removeEventListener("resize",i.update,fe)}},data:{}};var me={left:"right",right:"left",bottom:"top",top:"bottom"};function ge(t){return t.replace(/left|right|bottom|top/g,(function(t){return me[t]}))}var _e={start:"end",end:"start"};function be(t){return t.replace(/start|end/g,(function(t){return _e[t]}))}function ve(t){var e=Wt(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function ye(t){return Vt(Gt(t)).left+ve(t).scrollLeft}function we(t){var e=Yt(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function Ee(t){return["html","body","#document"].indexOf(Rt(t))>=0?t.ownerDocument.body:zt(t)&&we(t)?t:Ee(Zt(t))}function Ae(t,e){var i;void 0===e&&(e=[]);var n=Ee(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=Wt(n),r=s?[o].concat(o.visualViewport||[],we(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(Ae(Zt(r)))}function Te(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function Oe(t,e){return e===Tt?Te(function(t){var e=Wt(t),i=Gt(t),n=e.visualViewport,s=i.clientWidth,o=i.clientHeight,r=0,a=0;return n&&(s=n.width,o=n.height,/^((?!chrome|android).)*safari/i.test(navigator.userAgent)||(r=n.offsetLeft,a=n.offsetTop)),{width:s,height:o,x:r+ye(t),y:a}}(t)):zt(e)?function(t){var e=Vt(t);return e.top=e.top+t.clientTop,e.left=e.left+t.clientLeft,e.bottom=e.top+t.clientHeight,e.right=e.left+t.clientWidth,e.width=t.clientWidth,e.height=t.clientHeight,e.x=e.left,e.y=e.top,e}(e):Te(function(t){var e,i=Gt(t),n=ve(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=ie(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=ie(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+ye(t),l=-n.scrollTop;return"rtl"===Yt(s||i).direction&&(a+=ie(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(Gt(t)))}function Ce(t){var e,i=t.reference,n=t.element,s=t.placement,o=s?Ut(s):null,r=s?ce(s):null,a=i.x+i.width/2-n.width/2,l=i.y+i.height/2-n.height/2;switch(o){case mt:e={x:a,y:i.y-n.height};break;case gt:e={x:a,y:i.y+i.height};break;case _t:e={x:i.x+i.width,y:l};break;case bt:e={x:i.x-n.width,y:l};break;default:e={x:i.x,y:i.y}}var c=o?ee(o):null;if(null!=c){var h="y"===c?"height":"width";switch(r){case wt:e[c]=e[c]-(i[h]/2-n[h]/2);break;case Et:e[c]=e[c]+(i[h]/2-n[h]/2)}}return e}function ke(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=void 0===n?t.placement:n,o=i.boundary,r=void 0===o?At:o,a=i.rootBoundary,l=void 0===a?Tt:a,c=i.elementContext,h=void 0===c?Ot:c,d=i.altBoundary,u=void 0!==d&&d,f=i.padding,p=void 0===f?0:f,m=re("number"!=typeof p?p:ae(p,yt)),g=h===Ot?Ct:Ot,_=t.rects.popper,b=t.elements[u?g:h],v=function(t,e,i){var n="clippingParents"===e?function(t){var e=Ae(Zt(t)),i=["absolute","fixed"].indexOf(Yt(t).position)>=0&&zt(t)?te(t):t;return $t(i)?e.filter((function(t){return $t(t)&&Xt(t,i)&&"body"!==Rt(t)})):[]}(t):[].concat(e),s=[].concat(n,[i]),o=s[0],r=s.reduce((function(e,i){var n=Oe(t,i);return e.top=ie(n.top,e.top),e.right=ne(n.right,e.right),e.bottom=ne(n.bottom,e.bottom),e.left=ie(n.left,e.left),e}),Oe(t,o));return r.width=r.right-r.left,r.height=r.bottom-r.top,r.x=r.left,r.y=r.top,r}($t(b)?b:b.contextElement||Gt(t.elements.popper),r,l),y=Vt(t.elements.reference),w=Ce({reference:y,element:_,strategy:"absolute",placement:s}),E=Te(Object.assign({},_,w)),A=h===Ot?E:y,T={top:v.top-A.top+m.top,bottom:A.bottom-v.bottom+m.bottom,left:v.left-A.left+m.left,right:A.right-v.right+m.right},O=t.modifiersData.offset;if(h===Ot&&O){var C=O[s];Object.keys(T).forEach((function(t){var e=[_t,gt].indexOf(t)>=0?1:-1,i=[mt,gt].indexOf(t)>=0?"y":"x";T[t]+=C[i]*e}))}return T}function Le(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,a=i.flipVariations,l=i.allowedAutoPlacements,c=void 0===l?Lt:l,h=ce(n),d=h?a?kt:kt.filter((function(t){return ce(t)===h})):yt,u=d.filter((function(t){return c.indexOf(t)>=0}));0===u.length&&(u=d);var f=u.reduce((function(e,i){return e[i]=ke(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[Ut(i)],e}),{});return Object.keys(f).sort((function(t,e){return f[t]-f[e]}))}const xe={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name;if(!e.modifiersData[n]._skip){for(var s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0===r||r,l=i.fallbackPlacements,c=i.padding,h=i.boundary,d=i.rootBoundary,u=i.altBoundary,f=i.flipVariations,p=void 0===f||f,m=i.allowedAutoPlacements,g=e.options.placement,_=Ut(g),b=l||(_!==g&&p?function(t){if(Ut(t)===vt)return[];var e=ge(t);return[be(t),e,be(e)]}(g):[ge(g)]),v=[g].concat(b).reduce((function(t,i){return t.concat(Ut(i)===vt?Le(e,{placement:i,boundary:h,rootBoundary:d,padding:c,flipVariations:p,allowedAutoPlacements:m}):i)}),[]),y=e.rects.reference,w=e.rects.popper,E=new Map,A=!0,T=v[0],O=0;O=0,D=x?"width":"height",S=ke(e,{placement:C,boundary:h,rootBoundary:d,altBoundary:u,padding:c}),N=x?L?_t:bt:L?gt:mt;y[D]>w[D]&&(N=ge(N));var I=ge(N),P=[];if(o&&P.push(S[k]<=0),a&&P.push(S[N]<=0,S[I]<=0),P.every((function(t){return t}))){T=C,A=!1;break}E.set(C,P)}if(A)for(var j=function(t){var e=v.find((function(e){var i=E.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return T=e,"break"},M=p?3:1;M>0&&"break"!==j(M);M--);e.placement!==T&&(e.modifiersData[n]._skip=!0,e.placement=T,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function De(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function Se(t){return[mt,_t,gt,bt].some((function(e){return t[e]>=0}))}const Ne={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,s=e.rects.popper,o=e.modifiersData.preventOverflow,r=ke(e,{elementContext:"reference"}),a=ke(e,{altBoundary:!0}),l=De(r,n),c=De(a,s,o),h=Se(l),d=Se(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:h,hasPopperEscaped:d},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":h,"data-popper-escaped":d})}},Ie={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.offset,o=void 0===s?[0,0]:s,r=Lt.reduce((function(t,i){return t[i]=function(t,e,i){var n=Ut(t),s=[bt,mt].indexOf(n)>=0?-1:1,o="function"==typeof i?i(Object.assign({},e,{placement:t})):i,r=o[0],a=o[1];return r=r||0,a=(a||0)*s,[bt,_t].indexOf(n)>=0?{x:a,y:r}:{x:r,y:a}}(i,e.rects,o),t}),{}),a=r[e.placement],l=a.x,c=a.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=l,e.modifiersData.popperOffsets.y+=c),e.modifiersData[n]=r}},Pe={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=Ce({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}},je={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0!==r&&r,l=i.boundary,c=i.rootBoundary,h=i.altBoundary,d=i.padding,u=i.tether,f=void 0===u||u,p=i.tetherOffset,m=void 0===p?0:p,g=ke(e,{boundary:l,rootBoundary:c,padding:d,altBoundary:h}),_=Ut(e.placement),b=ce(e.placement),v=!b,y=ee(_),w="x"===y?"y":"x",E=e.modifiersData.popperOffsets,A=e.rects.reference,T=e.rects.popper,O="function"==typeof m?m(Object.assign({},e.rects,{placement:e.placement})):m,C={x:0,y:0};if(E){if(o||a){var k="y"===y?mt:bt,L="y"===y?gt:_t,x="y"===y?"height":"width",D=E[y],S=E[y]+g[k],N=E[y]-g[L],I=f?-T[x]/2:0,P=b===wt?A[x]:T[x],j=b===wt?-T[x]:-A[x],M=e.elements.arrow,H=f&&M?Kt(M):{width:0,height:0},B=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},R=B[k],W=B[L],$=oe(0,A[x],H[x]),z=v?A[x]/2-I-$-R-O:P-$-R-O,q=v?-A[x]/2+I+$+W+O:j+$+W+O,F=e.elements.arrow&&te(e.elements.arrow),U=F?"y"===y?F.clientTop||0:F.clientLeft||0:0,V=e.modifiersData.offset?e.modifiersData.offset[e.placement][y]:0,K=E[y]+z-V-U,X=E[y]+q-V;if(o){var Y=oe(f?ne(S,K):S,D,f?ie(N,X):N);E[y]=Y,C[y]=Y-D}if(a){var Q="x"===y?mt:bt,G="x"===y?gt:_t,Z=E[w],J=Z+g[Q],tt=Z-g[G],et=oe(f?ne(J,K):J,Z,f?ie(tt,X):tt);E[w]=et,C[w]=et-Z}}e.modifiersData[n]=C}},requiresIfExists:["offset"]};function Me(t,e,i){void 0===i&&(i=!1);var n=zt(e);zt(e)&&function(t){var e=t.getBoundingClientRect();e.width,t.offsetWidth,e.height,t.offsetHeight}(e);var s,o,r=Gt(e),a=Vt(t),l={scrollLeft:0,scrollTop:0},c={x:0,y:0};return(n||!n&&!i)&&(("body"!==Rt(e)||we(r))&&(l=(s=e)!==Wt(s)&&zt(s)?{scrollLeft:(o=s).scrollLeft,scrollTop:o.scrollTop}:ve(s)),zt(e)?((c=Vt(e)).x+=e.clientLeft,c.y+=e.clientTop):r&&(c.x=ye(r))),{x:a.left+l.scrollLeft-c.x,y:a.top+l.scrollTop-c.y,width:a.width,height:a.height}}function He(t){var e=new Map,i=new Set,n=[];function s(t){i.add(t.name),[].concat(t.requires||[],t.requiresIfExists||[]).forEach((function(t){if(!i.has(t)){var n=e.get(t);n&&s(n)}})),n.push(t)}return t.forEach((function(t){e.set(t.name,t)})),t.forEach((function(t){i.has(t.name)||s(t)})),n}var Be={placement:"bottom",modifiers:[],strategy:"absolute"};function Re(){for(var t=arguments.length,e=new Array(t),i=0;ij.on(t,"mouseover",d))),this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.add(Je),this._element.classList.add(Je),j.trigger(this._element,"shown.bs.dropdown",t)}hide(){if(c(this._element)||!this._isShown(this._menu))return;const t={relatedTarget:this._element};this._completeHide(t)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_completeHide(t){j.trigger(this._element,"hide.bs.dropdown",t).defaultPrevented||("ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>j.off(t,"mouseover",d))),this._popper&&this._popper.destroy(),this._menu.classList.remove(Je),this._element.classList.remove(Je),this._element.setAttribute("aria-expanded","false"),U.removeDataAttribute(this._menu,"popper"),j.trigger(this._element,"hidden.bs.dropdown",t))}_getConfig(t){if(t={...this.constructor.Default,...U.getDataAttributes(this._element),...t},a(Ue,t,this.constructor.DefaultType),"object"==typeof t.reference&&!o(t.reference)&&"function"!=typeof t.reference.getBoundingClientRect)throw new TypeError(`${Ue.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);return t}_createPopper(t){if(void 0===Fe)throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org)");let e=this._element;"parent"===this._config.reference?e=t:o(this._config.reference)?e=r(this._config.reference):"object"==typeof this._config.reference&&(e=this._config.reference);const i=this._getPopperConfig(),n=i.modifiers.find((t=>"applyStyles"===t.name&&!1===t.enabled));this._popper=qe(e,this._menu,i),n&&U.setDataAttribute(this._menu,"popper","static")}_isShown(t=this._element){return t.classList.contains(Je)}_getMenuElement(){return V.next(this._element,ei)[0]}_getPlacement(){const t=this._element.parentNode;if(t.classList.contains("dropend"))return ri;if(t.classList.contains("dropstart"))return ai;const e="end"===getComputedStyle(this._menu).getPropertyValue("--bs-position").trim();return t.classList.contains("dropup")?e?ni:ii:e?oi:si}_detectNavbar(){return null!==this._element.closest(".navbar")}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return"static"===this._config.display&&(t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,..."function"==typeof this._config.popperConfig?this._config.popperConfig(t):this._config.popperConfig}}_selectMenuItem({key:t,target:e}){const i=V.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter(l);i.length&&v(i,e,t===Ye,!i.includes(e)).focus()}static jQueryInterface(t){return this.each((function(){const e=hi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}static clearMenus(t){if(t&&(2===t.button||"keyup"===t.type&&"Tab"!==t.key))return;const e=V.find(ti);for(let i=0,n=e.length;ie+t)),this._setElementAttributes(di,"paddingRight",(e=>e+t)),this._setElementAttributes(ui,"marginRight",(e=>e-t))}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const n=this.getWidth();this._applyManipulationCallback(t,(t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+n)return;this._saveInitialAttribute(t,e);const s=window.getComputedStyle(t)[e];t.style[e]=`${i(Number.parseFloat(s))}px`}))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,"paddingRight"),this._resetElementAttributes(di,"paddingRight"),this._resetElementAttributes(ui,"marginRight")}_saveInitialAttribute(t,e){const i=t.style[e];i&&U.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,(t=>{const i=U.getDataAttribute(t,e);void 0===i?t.style.removeProperty(e):(U.removeDataAttribute(t,e),t.style[e]=i)}))}_applyManipulationCallback(t,e){o(t)?e(t):V.find(t,this._element).forEach(e)}isOverflowing(){return this.getWidth()>0}}const pi={className:"modal-backdrop",isVisible:!0,isAnimated:!1,rootElement:"body",clickCallback:null},mi={className:"string",isVisible:"boolean",isAnimated:"boolean",rootElement:"(element|string)",clickCallback:"(function|null)"},gi="show",_i="mousedown.bs.backdrop";class bi{constructor(t){this._config=this._getConfig(t),this._isAppended=!1,this._element=null}show(t){this._config.isVisible?(this._append(),this._config.isAnimated&&u(this._getElement()),this._getElement().classList.add(gi),this._emulateAnimation((()=>{_(t)}))):_(t)}hide(t){this._config.isVisible?(this._getElement().classList.remove(gi),this._emulateAnimation((()=>{this.dispose(),_(t)}))):_(t)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_getConfig(t){return(t={...pi,..."object"==typeof t?t:{}}).rootElement=r(t.rootElement),a("backdrop",t,mi),t}_append(){this._isAppended||(this._config.rootElement.append(this._getElement()),j.on(this._getElement(),_i,(()=>{_(this._config.clickCallback)})),this._isAppended=!0)}dispose(){this._isAppended&&(j.off(this._element,_i),this._element.remove(),this._isAppended=!1)}_emulateAnimation(t){b(t,this._getElement(),this._config.isAnimated)}}const vi={trapElement:null,autofocus:!0},yi={trapElement:"element",autofocus:"boolean"},wi=".bs.focustrap",Ei="backward";class Ai{constructor(t){this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}activate(){const{trapElement:t,autofocus:e}=this._config;this._isActive||(e&&t.focus(),j.off(document,wi),j.on(document,"focusin.bs.focustrap",(t=>this._handleFocusin(t))),j.on(document,"keydown.tab.bs.focustrap",(t=>this._handleKeydown(t))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,j.off(document,wi))}_handleFocusin(t){const{target:e}=t,{trapElement:i}=this._config;if(e===document||e===i||i.contains(e))return;const n=V.focusableChildren(i);0===n.length?i.focus():this._lastTabNavDirection===Ei?n[n.length-1].focus():n[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?Ei:"forward")}_getConfig(t){return t={...vi,..."object"==typeof t?t:{}},a("focustrap",t,yi),t}}const Ti="modal",Oi="Escape",Ci={backdrop:!0,keyboard:!0,focus:!0},ki={backdrop:"(boolean|string)",keyboard:"boolean",focus:"boolean"},Li="hidden.bs.modal",xi="show.bs.modal",Di="resize.bs.modal",Si="click.dismiss.bs.modal",Ni="keydown.dismiss.bs.modal",Ii="mousedown.dismiss.bs.modal",Pi="modal-open",ji="show",Mi="modal-static";class Hi extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._dialog=V.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._ignoreBackdropClick=!1,this._isTransitioning=!1,this._scrollBar=new fi}static get Default(){return Ci}static get NAME(){return Ti}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||j.trigger(this._element,xi,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isAnimated()&&(this._isTransitioning=!0),this._scrollBar.hide(),document.body.classList.add(Pi),this._adjustDialog(),this._setEscapeEvent(),this._setResizeEvent(),j.on(this._dialog,Ii,(()=>{j.one(this._element,"mouseup.dismiss.bs.modal",(t=>{t.target===this._element&&(this._ignoreBackdropClick=!0)}))})),this._showBackdrop((()=>this._showElement(t))))}hide(){if(!this._isShown||this._isTransitioning)return;if(j.trigger(this._element,"hide.bs.modal").defaultPrevented)return;this._isShown=!1;const t=this._isAnimated();t&&(this._isTransitioning=!0),this._setEscapeEvent(),this._setResizeEvent(),this._focustrap.deactivate(),this._element.classList.remove(ji),j.off(this._element,Si),j.off(this._dialog,Ii),this._queueCallback((()=>this._hideModal()),this._element,t)}dispose(){[window,this._dialog].forEach((t=>j.off(t,".bs.modal"))),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new bi({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new Ai({trapElement:this._element})}_getConfig(t){return t={...Ci,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(Ti,t,ki),t}_showElement(t){const e=this._isAnimated(),i=V.findOne(".modal-body",this._dialog);this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0,i&&(i.scrollTop=0),e&&u(this._element),this._element.classList.add(ji),this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,j.trigger(this._element,"shown.bs.modal",{relatedTarget:t})}),this._dialog,e)}_setEscapeEvent(){this._isShown?j.on(this._element,Ni,(t=>{this._config.keyboard&&t.key===Oi?(t.preventDefault(),this.hide()):this._config.keyboard||t.key!==Oi||this._triggerBackdropTransition()})):j.off(this._element,Ni)}_setResizeEvent(){this._isShown?j.on(window,Di,(()=>this._adjustDialog())):j.off(window,Di)}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide((()=>{document.body.classList.remove(Pi),this._resetAdjustments(),this._scrollBar.reset(),j.trigger(this._element,Li)}))}_showBackdrop(t){j.on(this._element,Si,(t=>{this._ignoreBackdropClick?this._ignoreBackdropClick=!1:t.target===t.currentTarget&&(!0===this._config.backdrop?this.hide():"static"===this._config.backdrop&&this._triggerBackdropTransition())})),this._backdrop.show(t)}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(j.trigger(this._element,"hidePrevented.bs.modal").defaultPrevented)return;const{classList:t,scrollHeight:e,style:i}=this._element,n=e>document.documentElement.clientHeight;!n&&"hidden"===i.overflowY||t.contains(Mi)||(n||(i.overflowY="hidden"),t.add(Mi),this._queueCallback((()=>{t.remove(Mi),n||this._queueCallback((()=>{i.overflowY=""}),this._dialog)}),this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;(!i&&t&&!m()||i&&!t&&m())&&(this._element.style.paddingLeft=`${e}px`),(i&&!t&&!m()||!i&&t&&m())&&(this._element.style.paddingRight=`${e}px`)}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=Hi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}j.on(document,"click.bs.modal.data-api",'[data-bs-toggle="modal"]',(function(t){const e=n(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),j.one(e,xi,(t=>{t.defaultPrevented||j.one(e,Li,(()=>{l(this)&&this.focus()}))}));const i=V.findOne(".modal.show");i&&Hi.getInstance(i).hide(),Hi.getOrCreateInstance(e).toggle(this)})),R(Hi),g(Hi);const Bi="offcanvas",Ri={backdrop:!0,keyboard:!0,scroll:!1},Wi={backdrop:"boolean",keyboard:"boolean",scroll:"boolean"},$i="show",zi=".offcanvas.show",qi="hidden.bs.offcanvas";class Fi extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get NAME(){return Bi}static get Default(){return Ri}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||j.trigger(this._element,"show.bs.offcanvas",{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._element.style.visibility="visible",this._backdrop.show(),this._config.scroll||(new fi).hide(),this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add($i),this._queueCallback((()=>{this._config.scroll||this._focustrap.activate(),j.trigger(this._element,"shown.bs.offcanvas",{relatedTarget:t})}),this._element,!0))}hide(){this._isShown&&(j.trigger(this._element,"hide.bs.offcanvas").defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.remove($i),this._backdrop.hide(),this._queueCallback((()=>{this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._element.style.visibility="hidden",this._config.scroll||(new fi).reset(),j.trigger(this._element,qi)}),this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_getConfig(t){return t={...Ri,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(Bi,t,Wi),t}_initializeBackDrop(){return new bi({className:"offcanvas-backdrop",isVisible:this._config.backdrop,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:()=>this.hide()})}_initializeFocusTrap(){return new Ai({trapElement:this._element})}_addEventListeners(){j.on(this._element,"keydown.dismiss.bs.offcanvas",(t=>{this._config.keyboard&&"Escape"===t.key&&this.hide()}))}static jQueryInterface(t){return this.each((function(){const e=Fi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}j.on(document,"click.bs.offcanvas.data-api",'[data-bs-toggle="offcanvas"]',(function(t){const e=n(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),c(this))return;j.one(e,qi,(()=>{l(this)&&this.focus()}));const i=V.findOne(zi);i&&i!==e&&Fi.getInstance(i).hide(),Fi.getOrCreateInstance(e).toggle(this)})),j.on(window,"load.bs.offcanvas.data-api",(()=>V.find(zi).forEach((t=>Fi.getOrCreateInstance(t).show())))),R(Fi),g(Fi);const Ui=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Vi=/^(?:(?:https?|mailto|ftp|tel|file|sms):|[^#&/:?]*(?:[#/?]|$))/i,Ki=/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z]+=*$/i,Xi=(t,e)=>{const i=t.nodeName.toLowerCase();if(e.includes(i))return!Ui.has(i)||Boolean(Vi.test(t.nodeValue)||Ki.test(t.nodeValue));const n=e.filter((t=>t instanceof RegExp));for(let t=0,e=n.length;t{Xi(t,r)||i.removeAttribute(t.nodeName)}))}return n.body.innerHTML}const Qi="tooltip",Gi=new Set(["sanitize","allowList","sanitizeFn"]),Zi={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(array|string|function)",container:"(string|element|boolean)",fallbackPlacements:"array",boundary:"(string|element)",customClass:"(string|function)",sanitize:"boolean",sanitizeFn:"(null|function)",allowList:"object",popperConfig:"(null|object|function)"},Ji={AUTO:"auto",TOP:"top",RIGHT:m()?"left":"right",BOTTOM:"bottom",LEFT:m()?"right":"left"},tn={animation:!0,template:'',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:[0,0],container:!1,fallbackPlacements:["top","right","bottom","left"],boundary:"clippingParents",customClass:"",sanitize:!0,sanitizeFn:null,allowList:{"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},popperConfig:null},en={HIDE:"hide.bs.tooltip",HIDDEN:"hidden.bs.tooltip",SHOW:"show.bs.tooltip",SHOWN:"shown.bs.tooltip",INSERTED:"inserted.bs.tooltip",CLICK:"click.bs.tooltip",FOCUSIN:"focusin.bs.tooltip",FOCUSOUT:"focusout.bs.tooltip",MOUSEENTER:"mouseenter.bs.tooltip",MOUSELEAVE:"mouseleave.bs.tooltip"},nn="fade",sn="show",on="show",rn="out",an=".tooltip-inner",ln=".modal",cn="hide.bs.modal",hn="hover",dn="focus";class un extends B{constructor(t,e){if(void 0===Fe)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t),this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this._config=this._getConfig(e),this.tip=null,this._setListeners()}static get Default(){return tn}static get NAME(){return Qi}static get Event(){return en}static get DefaultType(){return Zi}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(t){if(this._isEnabled)if(t){const e=this._initializeOnDelegatedTarget(t);e._activeTrigger.click=!e._activeTrigger.click,e._isWithActiveTrigger()?e._enter(null,e):e._leave(null,e)}else{if(this.getTipElement().classList.contains(sn))return void this._leave(null,this);this._enter(null,this)}}dispose(){clearTimeout(this._timeout),j.off(this._element.closest(ln),cn,this._hideModalHandler),this.tip&&this.tip.remove(),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this.isWithContent()||!this._isEnabled)return;const t=j.trigger(this._element,this.constructor.Event.SHOW),e=h(this._element),i=null===e?this._element.ownerDocument.documentElement.contains(this._element):e.contains(this._element);if(t.defaultPrevented||!i)return;"tooltip"===this.constructor.NAME&&this.tip&&this.getTitle()!==this.tip.querySelector(an).innerHTML&&(this._disposePopper(),this.tip.remove(),this.tip=null);const n=this.getTipElement(),s=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME);n.setAttribute("id",s),this._element.setAttribute("aria-describedby",s),this._config.animation&&n.classList.add(nn);const o="function"==typeof this._config.placement?this._config.placement.call(this,n,this._element):this._config.placement,r=this._getAttachment(o);this._addAttachmentClass(r);const{container:a}=this._config;H.set(n,this.constructor.DATA_KEY,this),this._element.ownerDocument.documentElement.contains(this.tip)||(a.append(n),j.trigger(this._element,this.constructor.Event.INSERTED)),this._popper?this._popper.update():this._popper=qe(this._element,n,this._getPopperConfig(r)),n.classList.add(sn);const l=this._resolvePossibleFunction(this._config.customClass);l&&n.classList.add(...l.split(" ")),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>{j.on(t,"mouseover",d)}));const c=this.tip.classList.contains(nn);this._queueCallback((()=>{const t=this._hoverState;this._hoverState=null,j.trigger(this._element,this.constructor.Event.SHOWN),t===rn&&this._leave(null,this)}),this.tip,c)}hide(){if(!this._popper)return;const t=this.getTipElement();if(j.trigger(this._element,this.constructor.Event.HIDE).defaultPrevented)return;t.classList.remove(sn),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>j.off(t,"mouseover",d))),this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1;const e=this.tip.classList.contains(nn);this._queueCallback((()=>{this._isWithActiveTrigger()||(this._hoverState!==on&&t.remove(),this._cleanTipClass(),this._element.removeAttribute("aria-describedby"),j.trigger(this._element,this.constructor.Event.HIDDEN),this._disposePopper())}),this.tip,e),this._hoverState=""}update(){null!==this._popper&&this._popper.update()}isWithContent(){return Boolean(this.getTitle())}getTipElement(){if(this.tip)return this.tip;const t=document.createElement("div");t.innerHTML=this._config.template;const e=t.children[0];return this.setContent(e),e.classList.remove(nn,sn),this.tip=e,this.tip}setContent(t){this._sanitizeAndSetContent(t,this.getTitle(),an)}_sanitizeAndSetContent(t,e,i){const n=V.findOne(i,t);e||!n?this.setElementContent(n,e):n.remove()}setElementContent(t,e){if(null!==t)return o(e)?(e=r(e),void(this._config.html?e.parentNode!==t&&(t.innerHTML="",t.append(e)):t.textContent=e.textContent)):void(this._config.html?(this._config.sanitize&&(e=Yi(e,this._config.allowList,this._config.sanitizeFn)),t.innerHTML=e):t.textContent=e)}getTitle(){const t=this._element.getAttribute("data-bs-original-title")||this._config.title;return this._resolvePossibleFunction(t)}updateAttachment(t){return"right"===t?"end":"left"===t?"start":t}_initializeOnDelegatedTarget(t,e){return e||this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return"function"==typeof t?t.call(this._element):t}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"onChange",enabled:!0,phase:"afterWrite",fn:t=>this._handlePopperPlacementChange(t)}],onFirstUpdate:t=>{t.options.placement!==t.placement&&this._handlePopperPlacementChange(t)}};return{...e,..."function"==typeof this._config.popperConfig?this._config.popperConfig(e):this._config.popperConfig}}_addAttachmentClass(t){this.getTipElement().classList.add(`${this._getBasicClassPrefix()}-${this.updateAttachment(t)}`)}_getAttachment(t){return Ji[t.toUpperCase()]}_setListeners(){this._config.trigger.split(" ").forEach((t=>{if("click"===t)j.on(this._element,this.constructor.Event.CLICK,this._config.selector,(t=>this.toggle(t)));else if("manual"!==t){const e=t===hn?this.constructor.Event.MOUSEENTER:this.constructor.Event.FOCUSIN,i=t===hn?this.constructor.Event.MOUSELEAVE:this.constructor.Event.FOCUSOUT;j.on(this._element,e,this._config.selector,(t=>this._enter(t))),j.on(this._element,i,this._config.selector,(t=>this._leave(t)))}})),this._hideModalHandler=()=>{this._element&&this.hide()},j.on(this._element.closest(ln),cn,this._hideModalHandler),this._config.selector?this._config={...this._config,trigger:"manual",selector:""}:this._fixTitle()}_fixTitle(){const t=this._element.getAttribute("title"),e=typeof this._element.getAttribute("data-bs-original-title");(t||"string"!==e)&&(this._element.setAttribute("data-bs-original-title",t||""),!t||this._element.getAttribute("aria-label")||this._element.textContent||this._element.setAttribute("aria-label",t),this._element.setAttribute("title",""))}_enter(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusin"===t.type?dn:hn]=!0),e.getTipElement().classList.contains(sn)||e._hoverState===on?e._hoverState=on:(clearTimeout(e._timeout),e._hoverState=on,e._config.delay&&e._config.delay.show?e._timeout=setTimeout((()=>{e._hoverState===on&&e.show()}),e._config.delay.show):e.show())}_leave(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusout"===t.type?dn:hn]=e._element.contains(t.relatedTarget)),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=rn,e._config.delay&&e._config.delay.hide?e._timeout=setTimeout((()=>{e._hoverState===rn&&e.hide()}),e._config.delay.hide):e.hide())}_isWithActiveTrigger(){for(const t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1}_getConfig(t){const e=U.getDataAttributes(this._element);return Object.keys(e).forEach((t=>{Gi.has(t)&&delete e[t]})),(t={...this.constructor.Default,...e,..."object"==typeof t&&t?t:{}}).container=!1===t.container?document.body:r(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),a(Qi,t,this.constructor.DefaultType),t.sanitize&&(t.template=Yi(t.template,t.allowList,t.sanitizeFn)),t}_getDelegateConfig(){const t={};for(const e in this._config)this.constructor.Default[e]!==this._config[e]&&(t[e]=this._config[e]);return t}_cleanTipClass(){const t=this.getTipElement(),e=new RegExp(`(^|\\s)${this._getBasicClassPrefix()}\\S+`,"g"),i=t.getAttribute("class").match(e);null!==i&&i.length>0&&i.map((t=>t.trim())).forEach((e=>t.classList.remove(e)))}_getBasicClassPrefix(){return"bs-tooltip"}_handlePopperPlacementChange(t){const{state:e}=t;e&&(this.tip=e.elements.popper,this._cleanTipClass(),this._addAttachmentClass(this._getAttachment(e.placement)))}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null)}static jQueryInterface(t){return this.each((function(){const e=un.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}g(un);const fn={...un.Default,placement:"right",offset:[0,8],trigger:"click",content:"",template:''},pn={...un.DefaultType,content:"(string|element|function)"},mn={HIDE:"hide.bs.popover",HIDDEN:"hidden.bs.popover",SHOW:"show.bs.popover",SHOWN:"shown.bs.popover",INSERTED:"inserted.bs.popover",CLICK:"click.bs.popover",FOCUSIN:"focusin.bs.popover",FOCUSOUT:"focusout.bs.popover",MOUSEENTER:"mouseenter.bs.popover",MOUSELEAVE:"mouseleave.bs.popover"};class gn extends un{static get Default(){return fn}static get NAME(){return"popover"}static get Event(){return mn}static get DefaultType(){return pn}isWithContent(){return this.getTitle()||this._getContent()}setContent(t){this._sanitizeAndSetContent(t,this.getTitle(),".popover-header"),this._sanitizeAndSetContent(t,this._getContent(),".popover-body")}_getContent(){return this._resolvePossibleFunction(this._config.content)}_getBasicClassPrefix(){return"bs-popover"}static jQueryInterface(t){return this.each((function(){const e=gn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}g(gn);const _n="scrollspy",bn={offset:10,method:"auto",target:""},vn={offset:"number",method:"string",target:"(string|element)"},yn="active",wn=".nav-link, .list-group-item, .dropdown-item",En="position";class An extends B{constructor(t,e){super(t),this._scrollElement="BODY"===this._element.tagName?window:this._element,this._config=this._getConfig(e),this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,j.on(this._scrollElement,"scroll.bs.scrollspy",(()=>this._process())),this.refresh(),this._process()}static get Default(){return bn}static get NAME(){return _n}refresh(){const t=this._scrollElement===this._scrollElement.window?"offset":En,e="auto"===this._config.method?t:this._config.method,n=e===En?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),V.find(wn,this._config.target).map((t=>{const s=i(t),o=s?V.findOne(s):null;if(o){const t=o.getBoundingClientRect();if(t.width||t.height)return[U[e](o).top+n,s]}return null})).filter((t=>t)).sort(((t,e)=>t[0]-e[0])).forEach((t=>{this._offsets.push(t[0]),this._targets.push(t[1])}))}dispose(){j.off(this._scrollElement,".bs.scrollspy"),super.dispose()}_getConfig(t){return(t={...bn,...U.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}}).target=r(t.target)||document.documentElement,a(_n,t,vn),t}_getScrollTop(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop}_getScrollHeight(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)}_getOffsetHeight(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height}_process(){const t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),i=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=i){const t=this._targets[this._targets.length-1];this._activeTarget!==t&&this._activate(t)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(let e=this._offsets.length;e--;)this._activeTarget!==this._targets[e]&&t>=this._offsets[e]&&(void 0===this._offsets[e+1]||t`${e}[data-bs-target="${t}"],${e}[href="${t}"]`)),i=V.findOne(e.join(","),this._config.target);i.classList.add(yn),i.classList.contains("dropdown-item")?V.findOne(".dropdown-toggle",i.closest(".dropdown")).classList.add(yn):V.parents(i,".nav, .list-group").forEach((t=>{V.prev(t,".nav-link, .list-group-item").forEach((t=>t.classList.add(yn))),V.prev(t,".nav-item").forEach((t=>{V.children(t,".nav-link").forEach((t=>t.classList.add(yn)))}))})),j.trigger(this._scrollElement,"activate.bs.scrollspy",{relatedTarget:t})}_clear(){V.find(wn,this._config.target).filter((t=>t.classList.contains(yn))).forEach((t=>t.classList.remove(yn)))}static jQueryInterface(t){return this.each((function(){const e=An.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}j.on(window,"load.bs.scrollspy.data-api",(()=>{V.find('[data-bs-spy="scroll"]').forEach((t=>new An(t)))})),g(An);const Tn="active",On="fade",Cn="show",kn=".active",Ln=":scope > li > .active";class xn extends B{static get NAME(){return"tab"}show(){if(this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE&&this._element.classList.contains(Tn))return;let t;const e=n(this._element),i=this._element.closest(".nav, .list-group");if(i){const e="UL"===i.nodeName||"OL"===i.nodeName?Ln:kn;t=V.find(e,i),t=t[t.length-1]}const s=t?j.trigger(t,"hide.bs.tab",{relatedTarget:this._element}):null;if(j.trigger(this._element,"show.bs.tab",{relatedTarget:t}).defaultPrevented||null!==s&&s.defaultPrevented)return;this._activate(this._element,i);const o=()=>{j.trigger(t,"hidden.bs.tab",{relatedTarget:this._element}),j.trigger(this._element,"shown.bs.tab",{relatedTarget:t})};e?this._activate(e,e.parentNode,o):o()}_activate(t,e,i){const n=(!e||"UL"!==e.nodeName&&"OL"!==e.nodeName?V.children(e,kn):V.find(Ln,e))[0],s=i&&n&&n.classList.contains(On),o=()=>this._transitionComplete(t,n,i);n&&s?(n.classList.remove(Cn),this._queueCallback(o,t,!0)):o()}_transitionComplete(t,e,i){if(e){e.classList.remove(Tn);const t=V.findOne(":scope > .dropdown-menu .active",e.parentNode);t&&t.classList.remove(Tn),"tab"===e.getAttribute("role")&&e.setAttribute("aria-selected",!1)}t.classList.add(Tn),"tab"===t.getAttribute("role")&&t.setAttribute("aria-selected",!0),u(t),t.classList.contains(On)&&t.classList.add(Cn);let n=t.parentNode;if(n&&"LI"===n.nodeName&&(n=n.parentNode),n&&n.classList.contains("dropdown-menu")){const e=t.closest(".dropdown");e&&V.find(".dropdown-toggle",e).forEach((t=>t.classList.add(Tn))),t.setAttribute("aria-expanded",!0)}i&&i()}static jQueryInterface(t){return this.each((function(){const e=xn.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}j.on(document,"click.bs.tab.data-api",'[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),c(this)||xn.getOrCreateInstance(this).show()})),g(xn);const Dn="toast",Sn="hide",Nn="show",In="showing",Pn={animation:"boolean",autohide:"boolean",delay:"number"},jn={animation:!0,autohide:!0,delay:5e3};class Mn extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get DefaultType(){return Pn}static get Default(){return jn}static get NAME(){return Dn}show(){j.trigger(this._element,"show.bs.toast").defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(Sn),u(this._element),this._element.classList.add(Nn),this._element.classList.add(In),this._queueCallback((()=>{this._element.classList.remove(In),j.trigger(this._element,"shown.bs.toast"),this._maybeScheduleHide()}),this._element,this._config.animation))}hide(){this._element.classList.contains(Nn)&&(j.trigger(this._element,"hide.bs.toast").defaultPrevented||(this._element.classList.add(In),this._queueCallback((()=>{this._element.classList.add(Sn),this._element.classList.remove(In),this._element.classList.remove(Nn),j.trigger(this._element,"hidden.bs.toast")}),this._element,this._config.animation)))}dispose(){this._clearTimeout(),this._element.classList.contains(Nn)&&this._element.classList.remove(Nn),super.dispose()}_getConfig(t){return t={...jn,...U.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}},a(Dn,t,this.constructor.DefaultType),t}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout((()=>{this.hide()}),this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){j.on(this._element,"mouseover.bs.toast",(t=>this._onInteraction(t,!0))),j.on(this._element,"mouseout.bs.toast",(t=>this._onInteraction(t,!1))),j.on(this._element,"focusin.bs.toast",(t=>this._onInteraction(t,!0))),j.on(this._element,"focusout.bs.toast",(t=>this._onInteraction(t,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=Mn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}return R(Mn),g(Mn),{Alert:W,Button:z,Carousel:st,Collapse:pt,Dropdown:hi,Modal:Hi,Offcanvas:Fi,Popover:gn,ScrollSpy:An,Tab:xn,Toast:Mn,Tooltip:un}})); +//# sourceMappingURL=bootstrap.bundle.min.js.map \ No newline at end of file diff --git a/doc/site_libs/clipboard/clipboard.min.js b/doc/site_libs/clipboard/clipboard.min.js new file mode 100644 index 000000000..41c6a0f7b --- /dev/null +++ b/doc/site_libs/clipboard/clipboard.min.js @@ -0,0 +1,7 @@ +/*! + * clipboard.js v2.0.10 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return o}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),c=n.n(e);function a(t){try{return document.execCommand(t)}catch(t){return}}var f=function(t){t=c()(t);return a("cut"),t};var l=function(t){var e,n,o,r=1.anchorjs-link,.anchorjs-link:focus{opacity:1}",u.sheet.cssRules.length),u.sheet.insertRule("[data-anchorjs-icon]::after{content:attr(data-anchorjs-icon)}",u.sheet.cssRules.length),u.sheet.insertRule('@font-face{font-family:anchorjs-icons;src:url(data:n/a;base64,AAEAAAALAIAAAwAwT1MvMg8yG2cAAAE4AAAAYGNtYXDp3gC3AAABpAAAAExnYXNwAAAAEAAAA9wAAAAIZ2x5ZlQCcfwAAAH4AAABCGhlYWQHFvHyAAAAvAAAADZoaGVhBnACFwAAAPQAAAAkaG10eASAADEAAAGYAAAADGxvY2EACACEAAAB8AAAAAhtYXhwAAYAVwAAARgAAAAgbmFtZQGOH9cAAAMAAAAAunBvc3QAAwAAAAADvAAAACAAAQAAAAEAAHzE2p9fDzz1AAkEAAAAAADRecUWAAAAANQA6R8AAAAAAoACwAAAAAgAAgAAAAAAAAABAAADwP/AAAACgAAA/9MCrQABAAAAAAAAAAAAAAAAAAAAAwABAAAAAwBVAAIAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAMCQAGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAg//0DwP/AAEADwABAAAAAAQAAAAAAAAAAAAAAIAAAAAAAAAIAAAACgAAxAAAAAwAAAAMAAAAcAAEAAwAAABwAAwABAAAAHAAEADAAAAAIAAgAAgAAACDpy//9//8AAAAg6cv//f///+EWNwADAAEAAAAAAAAAAAAAAAAACACEAAEAAAAAAAAAAAAAAAAxAAACAAQARAKAAsAAKwBUAAABIiYnJjQ3NzY2MzIWFxYUBwcGIicmNDc3NjQnJiYjIgYHBwYUFxYUBwYGIwciJicmNDc3NjIXFhQHBwYUFxYWMzI2Nzc2NCcmNDc2MhcWFAcHBgYjARQGDAUtLXoWOR8fORYtLTgKGwoKCjgaGg0gEhIgDXoaGgkJBQwHdR85Fi0tOAobCgoKOBoaDSASEiANehoaCQkKGwotLXoWOR8BMwUFLYEuehYXFxYugC44CQkKGwo4GkoaDQ0NDXoaShoKGwoFBe8XFi6ALjgJCQobCjgaShoNDQ0NehpKGgobCgoKLYEuehYXAAAADACWAAEAAAAAAAEACAAAAAEAAAAAAAIAAwAIAAEAAAAAAAMACAAAAAEAAAAAAAQACAAAAAEAAAAAAAUAAQALAAEAAAAAAAYACAAAAAMAAQQJAAEAEAAMAAMAAQQJAAIABgAcAAMAAQQJAAMAEAAMAAMAAQQJAAQAEAAMAAMAAQQJAAUAAgAiAAMAAQQJAAYAEAAMYW5jaG9yanM0MDBAAGEAbgBjAGgAbwByAGoAcwA0ADAAMABAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAH//wAP) format("truetype")}',u.sheet.cssRules.length)),u=document.querySelectorAll("[id]"),t=[].map.call(u,function(A){return A.id}),i=0;i\]./()*\\\n\t\b\v\u00A0]/g,"-").replace(/-{2,}/g,"-").substring(0,this.options.truncate).replace(/^-+|-+$/gm,"").toLowerCase()},this.hasAnchorJSLink=function(A){var e=A.firstChild&&-1<(" "+A.firstChild.className+" ").indexOf(" anchorjs-link "),A=A.lastChild&&-1<(" "+A.lastChild.className+" ").indexOf(" anchorjs-link ");return e||A||!1}}}); +// @license-end \ No newline at end of file diff --git a/doc/site_libs/quarto-html/popper.min.js b/doc/site_libs/quarto-html/popper.min.js new file mode 100644 index 000000000..2269d6696 --- /dev/null +++ b/doc/site_libs/quarto-html/popper.min.js @@ -0,0 +1,6 @@ +/** + * @popperjs/core v2.11.4 - MIT License + */ + +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Popper={})}(this,(function(e){"use strict";function t(e){if(null==e)return window;if("[object Window]"!==e.toString()){var t=e.ownerDocument;return t&&t.defaultView||window}return e}function n(e){return e instanceof t(e).Element||e instanceof Element}function r(e){return e instanceof t(e).HTMLElement||e instanceof HTMLElement}function o(e){return"undefined"!=typeof ShadowRoot&&(e instanceof t(e).ShadowRoot||e instanceof ShadowRoot)}var i=Math.max,a=Math.min,s=Math.round;function f(e,t){void 0===t&&(t=!1);var n=e.getBoundingClientRect(),o=1,i=1;if(r(e)&&t){var a=e.offsetHeight,f=e.offsetWidth;f>0&&(o=s(n.width)/f||1),a>0&&(i=s(n.height)/a||1)}return{width:n.width/o,height:n.height/i,top:n.top/i,right:n.right/o,bottom:n.bottom/i,left:n.left/o,x:n.left/o,y:n.top/i}}function c(e){var n=t(e);return{scrollLeft:n.pageXOffset,scrollTop:n.pageYOffset}}function p(e){return e?(e.nodeName||"").toLowerCase():null}function u(e){return((n(e)?e.ownerDocument:e.document)||window.document).documentElement}function l(e){return f(u(e)).left+c(e).scrollLeft}function d(e){return t(e).getComputedStyle(e)}function h(e){var t=d(e),n=t.overflow,r=t.overflowX,o=t.overflowY;return/auto|scroll|overlay|hidden/.test(n+o+r)}function m(e,n,o){void 0===o&&(o=!1);var i,a,d=r(n),m=r(n)&&function(e){var t=e.getBoundingClientRect(),n=s(t.width)/e.offsetWidth||1,r=s(t.height)/e.offsetHeight||1;return 1!==n||1!==r}(n),v=u(n),g=f(e,m),y={scrollLeft:0,scrollTop:0},b={x:0,y:0};return(d||!d&&!o)&&(("body"!==p(n)||h(v))&&(y=(i=n)!==t(i)&&r(i)?{scrollLeft:(a=i).scrollLeft,scrollTop:a.scrollTop}:c(i)),r(n)?((b=f(n,!0)).x+=n.clientLeft,b.y+=n.clientTop):v&&(b.x=l(v))),{x:g.left+y.scrollLeft-b.x,y:g.top+y.scrollTop-b.y,width:g.width,height:g.height}}function v(e){var t=f(e),n=e.offsetWidth,r=e.offsetHeight;return Math.abs(t.width-n)<=1&&(n=t.width),Math.abs(t.height-r)<=1&&(r=t.height),{x:e.offsetLeft,y:e.offsetTop,width:n,height:r}}function g(e){return"html"===p(e)?e:e.assignedSlot||e.parentNode||(o(e)?e.host:null)||u(e)}function y(e){return["html","body","#document"].indexOf(p(e))>=0?e.ownerDocument.body:r(e)&&h(e)?e:y(g(e))}function b(e,n){var r;void 0===n&&(n=[]);var o=y(e),i=o===(null==(r=e.ownerDocument)?void 0:r.body),a=t(o),s=i?[a].concat(a.visualViewport||[],h(o)?o:[]):o,f=n.concat(s);return i?f:f.concat(b(g(s)))}function x(e){return["table","td","th"].indexOf(p(e))>=0}function w(e){return r(e)&&"fixed"!==d(e).position?e.offsetParent:null}function O(e){for(var n=t(e),i=w(e);i&&x(i)&&"static"===d(i).position;)i=w(i);return i&&("html"===p(i)||"body"===p(i)&&"static"===d(i).position)?n:i||function(e){var t=-1!==navigator.userAgent.toLowerCase().indexOf("firefox");if(-1!==navigator.userAgent.indexOf("Trident")&&r(e)&&"fixed"===d(e).position)return null;var n=g(e);for(o(n)&&(n=n.host);r(n)&&["html","body"].indexOf(p(n))<0;){var i=d(n);if("none"!==i.transform||"none"!==i.perspective||"paint"===i.contain||-1!==["transform","perspective"].indexOf(i.willChange)||t&&"filter"===i.willChange||t&&i.filter&&"none"!==i.filter)return n;n=n.parentNode}return null}(e)||n}var j="top",E="bottom",D="right",A="left",L="auto",P=[j,E,D,A],M="start",k="end",W="viewport",B="popper",H=P.reduce((function(e,t){return e.concat([t+"-"+M,t+"-"+k])}),[]),T=[].concat(P,[L]).reduce((function(e,t){return e.concat([t,t+"-"+M,t+"-"+k])}),[]),R=["beforeRead","read","afterRead","beforeMain","main","afterMain","beforeWrite","write","afterWrite"];function S(e){var t=new Map,n=new Set,r=[];function o(e){n.add(e.name),[].concat(e.requires||[],e.requiresIfExists||[]).forEach((function(e){if(!n.has(e)){var r=t.get(e);r&&o(r)}})),r.push(e)}return e.forEach((function(e){t.set(e.name,e)})),e.forEach((function(e){n.has(e.name)||o(e)})),r}function C(e){return e.split("-")[0]}function q(e,t){var n=t.getRootNode&&t.getRootNode();if(e.contains(t))return!0;if(n&&o(n)){var r=t;do{if(r&&e.isSameNode(r))return!0;r=r.parentNode||r.host}while(r)}return!1}function V(e){return Object.assign({},e,{left:e.x,top:e.y,right:e.x+e.width,bottom:e.y+e.height})}function N(e,r){return r===W?V(function(e){var n=t(e),r=u(e),o=n.visualViewport,i=r.clientWidth,a=r.clientHeight,s=0,f=0;return o&&(i=o.width,a=o.height,/^((?!chrome|android).)*safari/i.test(navigator.userAgent)||(s=o.offsetLeft,f=o.offsetTop)),{width:i,height:a,x:s+l(e),y:f}}(e)):n(r)?function(e){var t=f(e);return t.top=t.top+e.clientTop,t.left=t.left+e.clientLeft,t.bottom=t.top+e.clientHeight,t.right=t.left+e.clientWidth,t.width=e.clientWidth,t.height=e.clientHeight,t.x=t.left,t.y=t.top,t}(r):V(function(e){var t,n=u(e),r=c(e),o=null==(t=e.ownerDocument)?void 0:t.body,a=i(n.scrollWidth,n.clientWidth,o?o.scrollWidth:0,o?o.clientWidth:0),s=i(n.scrollHeight,n.clientHeight,o?o.scrollHeight:0,o?o.clientHeight:0),f=-r.scrollLeft+l(e),p=-r.scrollTop;return"rtl"===d(o||n).direction&&(f+=i(n.clientWidth,o?o.clientWidth:0)-a),{width:a,height:s,x:f,y:p}}(u(e)))}function I(e,t,o){var s="clippingParents"===t?function(e){var t=b(g(e)),o=["absolute","fixed"].indexOf(d(e).position)>=0&&r(e)?O(e):e;return n(o)?t.filter((function(e){return n(e)&&q(e,o)&&"body"!==p(e)})):[]}(e):[].concat(t),f=[].concat(s,[o]),c=f[0],u=f.reduce((function(t,n){var r=N(e,n);return t.top=i(r.top,t.top),t.right=a(r.right,t.right),t.bottom=a(r.bottom,t.bottom),t.left=i(r.left,t.left),t}),N(e,c));return u.width=u.right-u.left,u.height=u.bottom-u.top,u.x=u.left,u.y=u.top,u}function _(e){return e.split("-")[1]}function F(e){return["top","bottom"].indexOf(e)>=0?"x":"y"}function U(e){var t,n=e.reference,r=e.element,o=e.placement,i=o?C(o):null,a=o?_(o):null,s=n.x+n.width/2-r.width/2,f=n.y+n.height/2-r.height/2;switch(i){case j:t={x:s,y:n.y-r.height};break;case E:t={x:s,y:n.y+n.height};break;case D:t={x:n.x+n.width,y:f};break;case A:t={x:n.x-r.width,y:f};break;default:t={x:n.x,y:n.y}}var c=i?F(i):null;if(null!=c){var p="y"===c?"height":"width";switch(a){case M:t[c]=t[c]-(n[p]/2-r[p]/2);break;case k:t[c]=t[c]+(n[p]/2-r[p]/2)}}return t}function z(e){return Object.assign({},{top:0,right:0,bottom:0,left:0},e)}function X(e,t){return t.reduce((function(t,n){return t[n]=e,t}),{})}function Y(e,t){void 0===t&&(t={});var r=t,o=r.placement,i=void 0===o?e.placement:o,a=r.boundary,s=void 0===a?"clippingParents":a,c=r.rootBoundary,p=void 0===c?W:c,l=r.elementContext,d=void 0===l?B:l,h=r.altBoundary,m=void 0!==h&&h,v=r.padding,g=void 0===v?0:v,y=z("number"!=typeof g?g:X(g,P)),b=d===B?"reference":B,x=e.rects.popper,w=e.elements[m?b:d],O=I(n(w)?w:w.contextElement||u(e.elements.popper),s,p),A=f(e.elements.reference),L=U({reference:A,element:x,strategy:"absolute",placement:i}),M=V(Object.assign({},x,L)),k=d===B?M:A,H={top:O.top-k.top+y.top,bottom:k.bottom-O.bottom+y.bottom,left:O.left-k.left+y.left,right:k.right-O.right+y.right},T=e.modifiersData.offset;if(d===B&&T){var R=T[i];Object.keys(H).forEach((function(e){var t=[D,E].indexOf(e)>=0?1:-1,n=[j,E].indexOf(e)>=0?"y":"x";H[e]+=R[n]*t}))}return H}var G={placement:"bottom",modifiers:[],strategy:"absolute"};function J(){for(var e=arguments.length,t=new Array(e),n=0;n=0?-1:1,i="function"==typeof n?n(Object.assign({},t,{placement:e})):n,a=i[0],s=i[1];return a=a||0,s=(s||0)*o,[A,D].indexOf(r)>=0?{x:s,y:a}:{x:a,y:s}}(n,t.rects,i),e}),{}),s=a[t.placement],f=s.x,c=s.y;null!=t.modifiersData.popperOffsets&&(t.modifiersData.popperOffsets.x+=f,t.modifiersData.popperOffsets.y+=c),t.modifiersData[r]=a}},ie={left:"right",right:"left",bottom:"top",top:"bottom"};function ae(e){return e.replace(/left|right|bottom|top/g,(function(e){return ie[e]}))}var se={start:"end",end:"start"};function fe(e){return e.replace(/start|end/g,(function(e){return se[e]}))}function ce(e,t){void 0===t&&(t={});var n=t,r=n.placement,o=n.boundary,i=n.rootBoundary,a=n.padding,s=n.flipVariations,f=n.allowedAutoPlacements,c=void 0===f?T:f,p=_(r),u=p?s?H:H.filter((function(e){return _(e)===p})):P,l=u.filter((function(e){return c.indexOf(e)>=0}));0===l.length&&(l=u);var d=l.reduce((function(t,n){return t[n]=Y(e,{placement:n,boundary:o,rootBoundary:i,padding:a})[C(n)],t}),{});return Object.keys(d).sort((function(e,t){return d[e]-d[t]}))}var pe={name:"flip",enabled:!0,phase:"main",fn:function(e){var t=e.state,n=e.options,r=e.name;if(!t.modifiersData[r]._skip){for(var o=n.mainAxis,i=void 0===o||o,a=n.altAxis,s=void 0===a||a,f=n.fallbackPlacements,c=n.padding,p=n.boundary,u=n.rootBoundary,l=n.altBoundary,d=n.flipVariations,h=void 0===d||d,m=n.allowedAutoPlacements,v=t.options.placement,g=C(v),y=f||(g===v||!h?[ae(v)]:function(e){if(C(e)===L)return[];var t=ae(e);return[fe(e),t,fe(t)]}(v)),b=[v].concat(y).reduce((function(e,n){return e.concat(C(n)===L?ce(t,{placement:n,boundary:p,rootBoundary:u,padding:c,flipVariations:h,allowedAutoPlacements:m}):n)}),[]),x=t.rects.reference,w=t.rects.popper,O=new Map,P=!0,k=b[0],W=0;W=0,S=R?"width":"height",q=Y(t,{placement:B,boundary:p,rootBoundary:u,altBoundary:l,padding:c}),V=R?T?D:A:T?E:j;x[S]>w[S]&&(V=ae(V));var N=ae(V),I=[];if(i&&I.push(q[H]<=0),s&&I.push(q[V]<=0,q[N]<=0),I.every((function(e){return e}))){k=B,P=!1;break}O.set(B,I)}if(P)for(var F=function(e){var t=b.find((function(t){var n=O.get(t);if(n)return n.slice(0,e).every((function(e){return e}))}));if(t)return k=t,"break"},U=h?3:1;U>0;U--){if("break"===F(U))break}t.placement!==k&&(t.modifiersData[r]._skip=!0,t.placement=k,t.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function ue(e,t,n){return i(e,a(t,n))}var le={name:"preventOverflow",enabled:!0,phase:"main",fn:function(e){var t=e.state,n=e.options,r=e.name,o=n.mainAxis,s=void 0===o||o,f=n.altAxis,c=void 0!==f&&f,p=n.boundary,u=n.rootBoundary,l=n.altBoundary,d=n.padding,h=n.tether,m=void 0===h||h,g=n.tetherOffset,y=void 0===g?0:g,b=Y(t,{boundary:p,rootBoundary:u,padding:d,altBoundary:l}),x=C(t.placement),w=_(t.placement),L=!w,P=F(x),k="x"===P?"y":"x",W=t.modifiersData.popperOffsets,B=t.rects.reference,H=t.rects.popper,T="function"==typeof y?y(Object.assign({},t.rects,{placement:t.placement})):y,R="number"==typeof T?{mainAxis:T,altAxis:T}:Object.assign({mainAxis:0,altAxis:0},T),S=t.modifiersData.offset?t.modifiersData.offset[t.placement]:null,q={x:0,y:0};if(W){if(s){var V,N="y"===P?j:A,I="y"===P?E:D,U="y"===P?"height":"width",z=W[P],X=z+b[N],G=z-b[I],J=m?-H[U]/2:0,K=w===M?B[U]:H[U],Q=w===M?-H[U]:-B[U],Z=t.elements.arrow,$=m&&Z?v(Z):{width:0,height:0},ee=t.modifiersData["arrow#persistent"]?t.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},te=ee[N],ne=ee[I],re=ue(0,B[U],$[U]),oe=L?B[U]/2-J-re-te-R.mainAxis:K-re-te-R.mainAxis,ie=L?-B[U]/2+J+re+ne+R.mainAxis:Q+re+ne+R.mainAxis,ae=t.elements.arrow&&O(t.elements.arrow),se=ae?"y"===P?ae.clientTop||0:ae.clientLeft||0:0,fe=null!=(V=null==S?void 0:S[P])?V:0,ce=z+ie-fe,pe=ue(m?a(X,z+oe-fe-se):X,z,m?i(G,ce):G);W[P]=pe,q[P]=pe-z}if(c){var le,de="x"===P?j:A,he="x"===P?E:D,me=W[k],ve="y"===k?"height":"width",ge=me+b[de],ye=me-b[he],be=-1!==[j,A].indexOf(x),xe=null!=(le=null==S?void 0:S[k])?le:0,we=be?ge:me-B[ve]-H[ve]-xe+R.altAxis,Oe=be?me+B[ve]+H[ve]-xe-R.altAxis:ye,je=m&&be?function(e,t,n){var r=ue(e,t,n);return r>n?n:r}(we,me,Oe):ue(m?we:ge,me,m?Oe:ye);W[k]=je,q[k]=je-me}t.modifiersData[r]=q}},requiresIfExists:["offset"]};var de={name:"arrow",enabled:!0,phase:"main",fn:function(e){var t,n=e.state,r=e.name,o=e.options,i=n.elements.arrow,a=n.modifiersData.popperOffsets,s=C(n.placement),f=F(s),c=[A,D].indexOf(s)>=0?"height":"width";if(i&&a){var p=function(e,t){return z("number"!=typeof(e="function"==typeof e?e(Object.assign({},t.rects,{placement:t.placement})):e)?e:X(e,P))}(o.padding,n),u=v(i),l="y"===f?j:A,d="y"===f?E:D,h=n.rects.reference[c]+n.rects.reference[f]-a[f]-n.rects.popper[c],m=a[f]-n.rects.reference[f],g=O(i),y=g?"y"===f?g.clientHeight||0:g.clientWidth||0:0,b=h/2-m/2,x=p[l],w=y-u[c]-p[d],L=y/2-u[c]/2+b,M=ue(x,L,w),k=f;n.modifiersData[r]=((t={})[k]=M,t.centerOffset=M-L,t)}},effect:function(e){var t=e.state,n=e.options.element,r=void 0===n?"[data-popper-arrow]":n;null!=r&&("string"!=typeof r||(r=t.elements.popper.querySelector(r)))&&q(t.elements.popper,r)&&(t.elements.arrow=r)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function he(e,t,n){return void 0===n&&(n={x:0,y:0}),{top:e.top-t.height-n.y,right:e.right-t.width+n.x,bottom:e.bottom-t.height+n.y,left:e.left-t.width-n.x}}function me(e){return[j,D,E,A].some((function(t){return e[t]>=0}))}var ve={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(e){var t=e.state,n=e.name,r=t.rects.reference,o=t.rects.popper,i=t.modifiersData.preventOverflow,a=Y(t,{elementContext:"reference"}),s=Y(t,{altBoundary:!0}),f=he(a,r),c=he(s,o,i),p=me(f),u=me(c);t.modifiersData[n]={referenceClippingOffsets:f,popperEscapeOffsets:c,isReferenceHidden:p,hasPopperEscaped:u},t.attributes.popper=Object.assign({},t.attributes.popper,{"data-popper-reference-hidden":p,"data-popper-escaped":u})}},ge=K({defaultModifiers:[Z,$,ne,re]}),ye=[Z,$,ne,re,oe,pe,le,de,ve],be=K({defaultModifiers:ye});e.applyStyles=re,e.arrow=de,e.computeStyles=ne,e.createPopper=be,e.createPopperLite=ge,e.defaultModifiers=ye,e.detectOverflow=Y,e.eventListeners=Z,e.flip=pe,e.hide=ve,e.offset=oe,e.popperGenerator=K,e.popperOffsets=$,e.preventOverflow=le,Object.defineProperty(e,"__esModule",{value:!0})})); + diff --git a/doc/site_libs/quarto-html/quarto-syntax-highlighting.css b/doc/site_libs/quarto-html/quarto-syntax-highlighting.css new file mode 100644 index 000000000..36cb3287b --- /dev/null +++ b/doc/site_libs/quarto-html/quarto-syntax-highlighting.css @@ -0,0 +1,171 @@ +/* quarto syntax highlight colors */ +:root { + --quarto-hl-ot-color: #003B4F; + --quarto-hl-at-color: #657422; + --quarto-hl-ss-color: #20794D; + --quarto-hl-an-color: #5E5E5E; + --quarto-hl-fu-color: #4758AB; + --quarto-hl-st-color: #20794D; + --quarto-hl-cf-color: #003B4F; + --quarto-hl-op-color: #5E5E5E; + --quarto-hl-er-color: #AD0000; + --quarto-hl-bn-color: #AD0000; + --quarto-hl-al-color: #AD0000; + --quarto-hl-va-color: #111111; + --quarto-hl-bu-color: inherit; + --quarto-hl-ex-color: inherit; + --quarto-hl-pp-color: #AD0000; + --quarto-hl-in-color: #5E5E5E; + --quarto-hl-vs-color: #20794D; + --quarto-hl-wa-color: #5E5E5E; + --quarto-hl-do-color: #5E5E5E; + --quarto-hl-im-color: #00769E; + --quarto-hl-ch-color: #20794D; + --quarto-hl-dt-color: #AD0000; + --quarto-hl-fl-color: #AD0000; + --quarto-hl-co-color: #5E5E5E; + --quarto-hl-cv-color: #5E5E5E; + --quarto-hl-cn-color: #8f5902; + --quarto-hl-sc-color: #5E5E5E; + --quarto-hl-dv-color: #AD0000; + --quarto-hl-kw-color: #003B4F; +} + +/* other quarto variables */ +:root { + --quarto-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; +} + +pre > code.sourceCode > span { + color: #003B4F; +} + +code span { + color: #003B4F; +} + +code.sourceCode > span { + color: #003B4F; +} + +div.sourceCode, +div.sourceCode pre.sourceCode { + color: #003B4F; +} + +code span.ot { + color: #003B4F; +} + +code span.at { + color: #657422; +} + +code span.ss { + color: #20794D; +} + +code span.an { + color: #5E5E5E; +} + +code span.fu { + color: #4758AB; +} + +code span.st { + color: #20794D; +} + +code span.cf { + color: #003B4F; +} + +code span.op { + color: #5E5E5E; +} + +code span.er { + color: #AD0000; +} + +code span.bn { + color: #AD0000; +} + +code span.al { + color: #AD0000; +} + +code span.va { + color: #111111; +} + +code span.pp { + color: #AD0000; +} + +code span.in { + color: #5E5E5E; +} + +code span.vs { + color: #20794D; +} + +code span.wa { + color: #5E5E5E; + font-style: italic; +} + +code span.do { + color: #5E5E5E; + font-style: italic; +} + +code span.im { + color: #00769E; +} + +code span.ch { + color: #20794D; +} + +code span.dt { + color: #AD0000; +} + +code span.fl { + color: #AD0000; +} + +code span.co { + color: #5E5E5E; +} + +code span.cv { + color: #5E5E5E; + font-style: italic; +} + +code span.cn { + color: #8f5902; +} + +code span.sc { + color: #5E5E5E; +} + +code span.dv { + color: #AD0000; +} + +code span.kw { + color: #003B4F; +} + +.prevent-inlining { + content: " { + const sibling = el.previousElementSibling; + if (sibling && sibling.tagName === "A") { + return sibling.classList.contains("active"); + } else { + return false; + } + }; + + // fire slideEnter for bootstrap tab activations (for htmlwidget resize behavior) + function fireSlideEnter(e) { + const event = window.document.createEvent("Event"); + event.initEvent("slideenter", true, true); + window.document.dispatchEvent(event); + } + const tabs = window.document.querySelectorAll('a[data-bs-toggle="tab"]'); + tabs.forEach((tab) => { + tab.addEventListener("shown.bs.tab", fireSlideEnter); + }); + + // fire slideEnter for tabby tab activations (for htmlwidget resize behavior) + document.addEventListener("tabby", fireSlideEnter, false); + + // Track scrolling and mark TOC links as active + // get table of contents and sidebar (bail if we don't have at least one) + const tocLinks = tocEl + ? [...tocEl.querySelectorAll("a[data-scroll-target]")] + : []; + const makeActive = (link) => tocLinks[link].classList.add("active"); + const removeActive = (link) => tocLinks[link].classList.remove("active"); + const removeAllActive = () => + [...Array(tocLinks.length).keys()].forEach((link) => removeActive(link)); + + // activate the anchor for a section associated with this TOC entry + tocLinks.forEach((link) => { + link.addEventListener("click", () => { + if (link.href.indexOf("#") !== -1) { + const anchor = link.href.split("#")[1]; + const heading = window.document.querySelector( + `[data-anchor-id=${anchor}]` + ); + if (heading) { + // Add the class + heading.classList.add("reveal-anchorjs-link"); + + // function to show the anchor + const handleMouseout = () => { + heading.classList.remove("reveal-anchorjs-link"); + heading.removeEventListener("mouseout", handleMouseout); + }; + + // add a function to clear the anchor when the user mouses out of it + heading.addEventListener("mouseout", handleMouseout); + } + } + }); + }); + + const sections = tocLinks.map((link) => { + const target = link.getAttribute("data-scroll-target"); + if (target.startsWith("#")) { + return window.document.getElementById(decodeURI(`${target.slice(1)}`)); + } else { + return window.document.querySelector(decodeURI(`${target}`)); + } + }); + + const sectionMargin = 200; + let currentActive = 0; + // track whether we've initialized state the first time + let init = false; + + const updateActiveLink = () => { + // The index from bottom to top (e.g. reversed list) + let sectionIndex = -1; + if ( + window.innerHeight + window.pageYOffset >= + window.document.body.offsetHeight + ) { + sectionIndex = 0; + } else { + sectionIndex = [...sections].reverse().findIndex((section) => { + if (section) { + return window.pageYOffset >= section.offsetTop - sectionMargin; + } else { + return false; + } + }); + } + if (sectionIndex > -1) { + const current = sections.length - sectionIndex - 1; + if (current !== currentActive) { + removeAllActive(); + currentActive = current; + makeActive(current); + if (init) { + window.dispatchEvent(sectionChanged); + } + init = true; + } + } + }; + + const inHiddenRegion = (top, bottom, hiddenRegions) => { + for (const region of hiddenRegions) { + if (top <= region.bottom && bottom >= region.top) { + return true; + } + } + return false; + }; + + const categorySelector = "header.quarto-title-block .quarto-category"; + const activateCategories = (href) => { + // Find any categories + // Surround them with a link pointing back to: + // #category=Authoring + try { + const categoryEls = window.document.querySelectorAll(categorySelector); + for (const categoryEl of categoryEls) { + const categoryText = categoryEl.textContent; + if (categoryText) { + const link = `${href}#category=${encodeURIComponent(categoryText)}`; + const linkEl = window.document.createElement("a"); + linkEl.setAttribute("href", link); + for (const child of categoryEl.childNodes) { + linkEl.append(child); + } + categoryEl.appendChild(linkEl); + } + } + } catch { + // Ignore errors + } + }; + function hasTitleCategories() { + return window.document.querySelector(categorySelector) !== null; + } + + function offsetRelativeUrl(url) { + const offset = getMeta("quarto:offset"); + return offset ? offset + url : url; + } + + function offsetAbsoluteUrl(url) { + const offset = getMeta("quarto:offset"); + const baseUrl = new URL(offset, window.location); + + const projRelativeUrl = url.replace(baseUrl, ""); + if (projRelativeUrl.startsWith("/")) { + return projRelativeUrl; + } else { + return "/" + projRelativeUrl; + } + } + + // read a meta tag value + function getMeta(metaName) { + const metas = window.document.getElementsByTagName("meta"); + for (let i = 0; i < metas.length; i++) { + if (metas[i].getAttribute("name") === metaName) { + return metas[i].getAttribute("content"); + } + } + return ""; + } + + async function findAndActivateCategories() { + const currentPagePath = offsetAbsoluteUrl(window.location.href); + const response = await fetch(offsetRelativeUrl("listings.json")); + if (response.status == 200) { + return response.json().then(function (listingPaths) { + const listingHrefs = []; + for (const listingPath of listingPaths) { + const pathWithoutLeadingSlash = listingPath.listing.substring(1); + for (const item of listingPath.items) { + if ( + item === currentPagePath || + item === currentPagePath + "index.html" + ) { + // Resolve this path against the offset to be sure + // we already are using the correct path to the listing + // (this adjusts the listing urls to be rooted against + // whatever root the page is actually running against) + const relative = offsetRelativeUrl(pathWithoutLeadingSlash); + const baseUrl = window.location; + const resolvedPath = new URL(relative, baseUrl); + listingHrefs.push(resolvedPath.pathname); + break; + } + } + } + + // Look up the tree for a nearby linting and use that if we find one + const nearestListing = findNearestParentListing( + offsetAbsoluteUrl(window.location.pathname), + listingHrefs + ); + if (nearestListing) { + activateCategories(nearestListing); + } else { + // See if the referrer is a listing page for this item + const referredRelativePath = offsetAbsoluteUrl(document.referrer); + const referrerListing = listingHrefs.find((listingHref) => { + const isListingReferrer = + listingHref === referredRelativePath || + listingHref === referredRelativePath + "index.html"; + return isListingReferrer; + }); + + if (referrerListing) { + // Try to use the referrer if possible + activateCategories(referrerListing); + } else if (listingHrefs.length > 0) { + // Otherwise, just fall back to the first listing + activateCategories(listingHrefs[0]); + } + } + }); + } + } + if (hasTitleCategories()) { + findAndActivateCategories(); + } + + const findNearestParentListing = (href, listingHrefs) => { + if (!href || !listingHrefs) { + return undefined; + } + // Look up the tree for a nearby linting and use that if we find one + const relativeParts = href.substring(1).split("/"); + while (relativeParts.length > 0) { + const path = relativeParts.join("/"); + for (const listingHref of listingHrefs) { + if (listingHref.startsWith(path)) { + return listingHref; + } + } + relativeParts.pop(); + } + + return undefined; + }; + + const manageSidebarVisiblity = (el, placeholderDescriptor) => { + let isVisible = true; + + return (hiddenRegions) => { + if (el === null) { + return; + } + + // Find the last element of the TOC + const lastChildEl = el.lastElementChild; + + if (lastChildEl) { + // Find the top and bottom o the element that is being managed + const elTop = el.offsetTop; + const elBottom = + elTop + lastChildEl.offsetTop + lastChildEl.offsetHeight; + + // Converts the sidebar to a menu + const convertToMenu = () => { + for (const child of el.children) { + child.style.opacity = 0; + child.style.overflow = "hidden"; + } + + const toggleContainer = window.document.createElement("div"); + toggleContainer.style.width = "100%"; + toggleContainer.classList.add("zindex-over-content"); + toggleContainer.classList.add("quarto-sidebar-toggle"); + toggleContainer.classList.add("headroom-target"); // Marks this to be managed by headeroom + toggleContainer.id = placeholderDescriptor.id; + toggleContainer.style.position = "fixed"; + + const toggleIcon = window.document.createElement("i"); + toggleIcon.classList.add("quarto-sidebar-toggle-icon"); + toggleIcon.classList.add("bi"); + toggleIcon.classList.add("bi-caret-down-fill"); + + const toggleTitle = window.document.createElement("div"); + const titleEl = window.document.body.querySelector( + placeholderDescriptor.titleSelector + ); + if (titleEl) { + toggleTitle.append(titleEl.innerText, toggleIcon); + } + toggleTitle.classList.add("zindex-over-content"); + toggleTitle.classList.add("quarto-sidebar-toggle-title"); + toggleContainer.append(toggleTitle); + + const toggleContents = window.document.createElement("div"); + toggleContents.classList = el.classList; + toggleContents.classList.add("zindex-over-content"); + toggleContents.classList.add("quarto-sidebar-toggle-contents"); + for (const child of el.children) { + if (child.id === "toc-title") { + continue; + } + + const clone = child.cloneNode(true); + clone.style.opacity = 1; + clone.style.display = null; + toggleContents.append(clone); + } + toggleContents.style.height = "0px"; + toggleContainer.append(toggleContents); + el.parentElement.prepend(toggleContainer); + + // Process clicks + let tocShowing = false; + // Allow the caller to control whether this is dismissed + // when it is clicked (e.g. sidebar navigation supports + // opening and closing the nav tree, so don't dismiss on click) + const clickEl = placeholderDescriptor.dismissOnClick + ? toggleContainer + : toggleTitle; + + const closeToggle = () => { + if (tocShowing) { + toggleContainer.classList.remove("expanded"); + toggleContents.style.height = "0px"; + tocShowing = false; + } + }; + + const positionToggle = () => { + // position the element (top left of parent, same width as parent) + const elRect = el.getBoundingClientRect(); + toggleContainer.style.left = `${elRect.left}px`; + toggleContainer.style.top = `${elRect.top}px`; + toggleContainer.style.width = `${elRect.width}px`; + }; + + // Get rid of any expanded toggle if the user scrolls + window.document.addEventListener( + "scroll", + throttle(() => { + closeToggle(); + }, 50) + ); + + // Handle positioning of the toggle + window.addEventListener( + "resize", + throttle(() => { + positionToggle(); + }, 50) + ); + positionToggle(); + + // Process the click + clickEl.onclick = () => { + if (!tocShowing) { + toggleContainer.classList.add("expanded"); + toggleContents.style.height = null; + tocShowing = true; + } else { + closeToggle(); + } + }; + }; + + // Converts a sidebar from a menu back to a sidebar + const convertToSidebar = () => { + for (const child of el.children) { + child.style.opacity = 1; + child.style.overflow = null; + } + + const placeholderEl = window.document.getElementById( + placeholderDescriptor.id + ); + if (placeholderEl) { + placeholderEl.remove(); + } + + el.classList.remove("rollup"); + }; + + if (isReaderMode()) { + convertToMenu(); + isVisible = false; + } else { + if (!isVisible) { + // If the element is current not visible reveal if there are + // no conflicts with overlay regions + if (!inHiddenRegion(elTop, elBottom, hiddenRegions)) { + convertToSidebar(); + isVisible = true; + } + } else { + // If the element is visible, hide it if it conflicts with overlay regions + // and insert a placeholder toggle (or if we're in reader mode) + if (inHiddenRegion(elTop, elBottom, hiddenRegions)) { + convertToMenu(); + isVisible = false; + } + } + } + } + }; + }; + + // Find any conflicting margin elements and add margins to the + // top to prevent overlap + const marginChildren = window.document.querySelectorAll( + ".column-margin.column-container > * " + ); + + nexttick(() => { + let lastBottom = 0; + for (const marginChild of marginChildren) { + const top = marginChild.getBoundingClientRect().top + window.scrollY; + if (top < lastBottom) { + const margin = lastBottom - top; + marginChild.style.marginTop = `${margin}px`; + } + const styles = window.getComputedStyle(marginChild); + const marginTop = parseFloat(styles["marginTop"]); + + lastBottom = top + marginChild.getBoundingClientRect().height + marginTop; + } + }); + + // Manage the visibility of the toc and the sidebar + const marginScrollVisibility = manageSidebarVisiblity(marginSidebarEl, { + id: "quarto-toc-toggle", + titleSelector: "#toc-title", + dismissOnClick: true, + }); + const sidebarScrollVisiblity = manageSidebarVisiblity(sidebarEl, { + id: "quarto-sidebarnav-toggle", + titleSelector: ".title", + dismissOnClick: false, + }); + let tocLeftScrollVisibility; + if (leftTocEl) { + tocLeftScrollVisibility = manageSidebarVisiblity(leftTocEl, { + id: "quarto-lefttoc-toggle", + titleSelector: "#toc-title", + dismissOnClick: true, + }); + } + + // Find the first element that uses formatting in special columns + const conflictingEls = window.document.body.querySelectorAll( + '[class^="column-"], [class*=" column-"], aside, [class*="margin-caption"], [class*=" margin-caption"], [class*="margin-ref"], [class*=" margin-ref"]' + ); + + // Filter all the possibly conflicting elements into ones + // the do conflict on the left or ride side + const arrConflictingEls = Array.from(conflictingEls); + const leftSideConflictEls = arrConflictingEls.filter((el) => { + if (el.tagName === "ASIDE") { + return false; + } + return Array.from(el.classList).find((className) => { + return ( + className !== "column-body" && + className.startsWith("column-") && + !className.endsWith("right") && + !className.endsWith("container") && + className !== "column-margin" + ); + }); + }); + const rightSideConflictEls = arrConflictingEls.filter((el) => { + if (el.tagName === "ASIDE") { + return true; + } + + const hasMarginCaption = Array.from(el.classList).find((className) => { + return className == "margin-caption"; + }); + if (hasMarginCaption) { + return true; + } + + return Array.from(el.classList).find((className) => { + return ( + className !== "column-body" && + !className.endsWith("container") && + className.startsWith("column-") && + !className.endsWith("left") + ); + }); + }); + + const kOverlapPaddingSize = 10; + function toRegions(els) { + return els.map((el) => { + const top = + el.getBoundingClientRect().top + + document.documentElement.scrollTop - + kOverlapPaddingSize; + return { + top, + bottom: top + el.scrollHeight + 2 * kOverlapPaddingSize, + }; + }); + } + + const hideOverlappedSidebars = () => { + marginScrollVisibility(toRegions(rightSideConflictEls)); + sidebarScrollVisiblity(toRegions(leftSideConflictEls)); + if (tocLeftScrollVisibility) { + tocLeftScrollVisibility(toRegions(leftSideConflictEls)); + } + }; + + window.quartoToggleReader = () => { + // Applies a slow class (or removes it) + // to update the transition speed + const slowTransition = (slow) => { + const manageTransition = (id, slow) => { + const el = document.getElementById(id); + if (el) { + if (slow) { + el.classList.add("slow"); + } else { + el.classList.remove("slow"); + } + } + }; + + manageTransition("TOC", slow); + manageTransition("quarto-sidebar", slow); + }; + + const readerMode = !isReaderMode(); + setReaderModeValue(readerMode); + + // If we're entering reader mode, slow the transition + if (readerMode) { + slowTransition(readerMode); + } + highlightReaderToggle(readerMode); + hideOverlappedSidebars(); + + // If we're exiting reader mode, restore the non-slow transition + if (!readerMode) { + slowTransition(!readerMode); + } + }; + + const highlightReaderToggle = (readerMode) => { + const els = document.querySelectorAll(".quarto-reader-toggle"); + if (els) { + els.forEach((el) => { + if (readerMode) { + el.classList.add("reader"); + } else { + el.classList.remove("reader"); + } + }); + } + }; + + const setReaderModeValue = (val) => { + if (window.location.protocol !== "file:") { + window.localStorage.setItem("quarto-reader-mode", val); + } else { + localReaderMode = val; + } + }; + + const isReaderMode = () => { + if (window.location.protocol !== "file:") { + return window.localStorage.getItem("quarto-reader-mode") === "true"; + } else { + return localReaderMode; + } + }; + let localReaderMode = null; + + // Walk the TOC and collapse/expand nodes + // Nodes are expanded if: + // - they are top level + // - they have children that are 'active' links + // - they are directly below an link that is 'active' + const walk = (el, depth) => { + // Tick depth when we enter a UL + if (el.tagName === "UL") { + depth = depth + 1; + } + + // It this is active link + let isActiveNode = false; + if (el.tagName === "A" && el.classList.contains("active")) { + isActiveNode = true; + } + + // See if there is an active child to this element + let hasActiveChild = false; + for (child of el.children) { + hasActiveChild = walk(child, depth) || hasActiveChild; + } + + // Process the collapse state if this is an UL + if (el.tagName === "UL") { + if (depth === 1 || hasActiveChild || prevSiblingIsActiveLink(el)) { + el.classList.remove("collapse"); + } else { + el.classList.add("collapse"); + } + + // untick depth when we leave a UL + depth = depth - 1; + } + return hasActiveChild || isActiveNode; + }; + + // walk the TOC and expand / collapse any items that should be shown + + if (tocEl) { + walk(tocEl, 0); + updateActiveLink(); + } + + // Throttle the scroll event and walk peridiocally + window.document.addEventListener( + "scroll", + throttle(() => { + if (tocEl) { + updateActiveLink(); + walk(tocEl, 0); + } + if (!isReaderMode()) { + hideOverlappedSidebars(); + } + }, 5) + ); + window.addEventListener( + "resize", + throttle(() => { + if (!isReaderMode()) { + hideOverlappedSidebars(); + } + }, 10) + ); + hideOverlappedSidebars(); + highlightReaderToggle(isReaderMode()); +}); + +// grouped tabsets +window.addEventListener("pageshow", (_event) => { + function getTabSettings() { + const data = localStorage.getItem("quarto-persistent-tabsets-data"); + if (!data) { + localStorage.setItem("quarto-persistent-tabsets-data", "{}"); + return {}; + } + if (data) { + return JSON.parse(data); + } + } + + function setTabSettings(data) { + localStorage.setItem( + "quarto-persistent-tabsets-data", + JSON.stringify(data) + ); + } + + function setTabState(groupName, groupValue) { + const data = getTabSettings(); + data[groupName] = groupValue; + setTabSettings(data); + } + + function toggleTab(tab, active) { + const tabPanelId = tab.getAttribute("aria-controls"); + const tabPanel = document.getElementById(tabPanelId); + if (active) { + tab.classList.add("active"); + tabPanel.classList.add("active"); + } else { + tab.classList.remove("active"); + tabPanel.classList.remove("active"); + } + } + + function toggleAll(selectedGroup, selectorsToSync) { + for (const [thisGroup, tabs] of Object.entries(selectorsToSync)) { + const active = selectedGroup === thisGroup; + for (const tab of tabs) { + toggleTab(tab, active); + } + } + } + + function findSelectorsToSyncByLanguage() { + const result = {}; + const tabs = Array.from( + document.querySelectorAll(`div[data-group] a[id^='tabset-']`) + ); + for (const item of tabs) { + const div = item.parentElement.parentElement.parentElement; + const group = div.getAttribute("data-group"); + if (!result[group]) { + result[group] = {}; + } + const selectorsToSync = result[group]; + const value = item.innerHTML; + if (!selectorsToSync[value]) { + selectorsToSync[value] = []; + } + selectorsToSync[value].push(item); + } + return result; + } + + function setupSelectorSync() { + const selectorsToSync = findSelectorsToSyncByLanguage(); + Object.entries(selectorsToSync).forEach(([group, tabSetsByValue]) => { + Object.entries(tabSetsByValue).forEach(([value, items]) => { + items.forEach((item) => { + item.addEventListener("click", (_event) => { + setTabState(group, value); + toggleAll(value, selectorsToSync[group]); + }); + }); + }); + }); + return selectorsToSync; + } + + const selectorsToSync = setupSelectorSync(); + for (const [group, selectedName] of Object.entries(getTabSettings())) { + const selectors = selectorsToSync[group]; + // it's possible that stale state gives us empty selections, so we explicitly check here. + if (selectors) { + toggleAll(selectedName, selectors); + } + } +}); + +function throttle(func, wait) { + let waiting = false; + return function () { + if (!waiting) { + func.apply(this, arguments); + waiting = true; + setTimeout(function () { + waiting = false; + }, wait); + } + }; +} + +function nexttick(func) { + return setTimeout(func, 0); +} diff --git a/doc/site_libs/quarto-html/tippy.css b/doc/site_libs/quarto-html/tippy.css new file mode 100644 index 000000000..e6ae635cb --- /dev/null +++ b/doc/site_libs/quarto-html/tippy.css @@ -0,0 +1 @@ +.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{position:relative;background-color:#333;color:#fff;border-radius:4px;font-size:14px;line-height:1.4;white-space:normal;outline:0;transition-property:transform,visibility,opacity}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-7px;left:0;border-width:8px 8px 0;border-top-color:initial;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-7px;left:0;border-width:0 8px 8px;border-bottom-color:initial;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-width:8px 0 8px 8px;border-left-color:initial;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-7px;border-width:8px 8px 8px 0;border-right-color:initial;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{width:16px;height:16px;color:#333}.tippy-arrow:before{content:"";position:absolute;border-color:transparent;border-style:solid}.tippy-content{position:relative;padding:5px 9px;z-index:1} \ No newline at end of file diff --git a/doc/site_libs/quarto-html/tippy.umd.min.js b/doc/site_libs/quarto-html/tippy.umd.min.js new file mode 100644 index 000000000..ca292be32 --- /dev/null +++ b/doc/site_libs/quarto-html/tippy.umd.min.js @@ -0,0 +1,2 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("@popperjs/core")):"function"==typeof define&&define.amd?define(["@popperjs/core"],t):(e=e||self).tippy=t(e.Popper)}(this,(function(e){"use strict";var t={passive:!0,capture:!0},n=function(){return document.body};function r(e,t,n){if(Array.isArray(e)){var r=e[t];return null==r?Array.isArray(n)?n[t]:n:r}return e}function o(e,t){var n={}.toString.call(e);return 0===n.indexOf("[object")&&n.indexOf(t+"]")>-1}function i(e,t){return"function"==typeof e?e.apply(void 0,t):e}function a(e,t){return 0===t?e:function(r){clearTimeout(n),n=setTimeout((function(){e(r)}),t)};var n}function s(e,t){var n=Object.assign({},e);return t.forEach((function(e){delete n[e]})),n}function u(e){return[].concat(e)}function c(e,t){-1===e.indexOf(t)&&e.push(t)}function p(e){return e.split("-")[0]}function f(e){return[].slice.call(e)}function l(e){return Object.keys(e).reduce((function(t,n){return void 0!==e[n]&&(t[n]=e[n]),t}),{})}function d(){return document.createElement("div")}function v(e){return["Element","Fragment"].some((function(t){return o(e,t)}))}function m(e){return o(e,"MouseEvent")}function g(e){return!(!e||!e._tippy||e._tippy.reference!==e)}function h(e){return v(e)?[e]:function(e){return o(e,"NodeList")}(e)?f(e):Array.isArray(e)?e:f(document.querySelectorAll(e))}function b(e,t){e.forEach((function(e){e&&(e.style.transitionDuration=t+"ms")}))}function y(e,t){e.forEach((function(e){e&&e.setAttribute("data-state",t)}))}function w(e){var t,n=u(e)[0];return null!=n&&null!=(t=n.ownerDocument)&&t.body?n.ownerDocument:document}function E(e,t,n){var r=t+"EventListener";["transitionend","webkitTransitionEnd"].forEach((function(t){e[r](t,n)}))}function O(e,t){for(var n=t;n;){var r;if(e.contains(n))return!0;n=null==n.getRootNode||null==(r=n.getRootNode())?void 0:r.host}return!1}var x={isTouch:!1},C=0;function T(){x.isTouch||(x.isTouch=!0,window.performance&&document.addEventListener("mousemove",A))}function A(){var e=performance.now();e-C<20&&(x.isTouch=!1,document.removeEventListener("mousemove",A)),C=e}function L(){var e=document.activeElement;if(g(e)){var t=e._tippy;e.blur&&!t.state.isVisible&&e.blur()}}var D=!!("undefined"!=typeof window&&"undefined"!=typeof document)&&!!window.msCrypto,R=Object.assign({appendTo:n,aria:{content:"auto",expanded:"auto"},delay:0,duration:[300,250],getReferenceClientRect:null,hideOnClick:!0,ignoreAttributes:!1,interactive:!1,interactiveBorder:2,interactiveDebounce:0,moveTransition:"",offset:[0,10],onAfterUpdate:function(){},onBeforeUpdate:function(){},onCreate:function(){},onDestroy:function(){},onHidden:function(){},onHide:function(){},onMount:function(){},onShow:function(){},onShown:function(){},onTrigger:function(){},onUntrigger:function(){},onClickOutside:function(){},placement:"top",plugins:[],popperOptions:{},render:null,showOnCreate:!1,touch:!0,trigger:"mouseenter focus",triggerTarget:null},{animateFill:!1,followCursor:!1,inlinePositioning:!1,sticky:!1},{allowHTML:!1,animation:"fade",arrow:!0,content:"",inertia:!1,maxWidth:350,role:"tooltip",theme:"",zIndex:9999}),k=Object.keys(R);function P(e){var t=(e.plugins||[]).reduce((function(t,n){var r,o=n.name,i=n.defaultValue;o&&(t[o]=void 0!==e[o]?e[o]:null!=(r=R[o])?r:i);return t}),{});return Object.assign({},e,t)}function j(e,t){var n=Object.assign({},t,{content:i(t.content,[e])},t.ignoreAttributes?{}:function(e,t){return(t?Object.keys(P(Object.assign({},R,{plugins:t}))):k).reduce((function(t,n){var r=(e.getAttribute("data-tippy-"+n)||"").trim();if(!r)return t;if("content"===n)t[n]=r;else try{t[n]=JSON.parse(r)}catch(e){t[n]=r}return t}),{})}(e,t.plugins));return n.aria=Object.assign({},R.aria,n.aria),n.aria={expanded:"auto"===n.aria.expanded?t.interactive:n.aria.expanded,content:"auto"===n.aria.content?t.interactive?null:"describedby":n.aria.content},n}function M(e,t){e.innerHTML=t}function V(e){var t=d();return!0===e?t.className="tippy-arrow":(t.className="tippy-svg-arrow",v(e)?t.appendChild(e):M(t,e)),t}function I(e,t){v(t.content)?(M(e,""),e.appendChild(t.content)):"function"!=typeof t.content&&(t.allowHTML?M(e,t.content):e.textContent=t.content)}function S(e){var t=e.firstElementChild,n=f(t.children);return{box:t,content:n.find((function(e){return e.classList.contains("tippy-content")})),arrow:n.find((function(e){return e.classList.contains("tippy-arrow")||e.classList.contains("tippy-svg-arrow")})),backdrop:n.find((function(e){return e.classList.contains("tippy-backdrop")}))}}function N(e){var t=d(),n=d();n.className="tippy-box",n.setAttribute("data-state","hidden"),n.setAttribute("tabindex","-1");var r=d();function o(n,r){var o=S(t),i=o.box,a=o.content,s=o.arrow;r.theme?i.setAttribute("data-theme",r.theme):i.removeAttribute("data-theme"),"string"==typeof r.animation?i.setAttribute("data-animation",r.animation):i.removeAttribute("data-animation"),r.inertia?i.setAttribute("data-inertia",""):i.removeAttribute("data-inertia"),i.style.maxWidth="number"==typeof r.maxWidth?r.maxWidth+"px":r.maxWidth,r.role?i.setAttribute("role",r.role):i.removeAttribute("role"),n.content===r.content&&n.allowHTML===r.allowHTML||I(a,e.props),r.arrow?s?n.arrow!==r.arrow&&(i.removeChild(s),i.appendChild(V(r.arrow))):i.appendChild(V(r.arrow)):s&&i.removeChild(s)}return r.className="tippy-content",r.setAttribute("data-state","hidden"),I(r,e.props),t.appendChild(n),n.appendChild(r),o(e.props,e.props),{popper:t,onUpdate:o}}N.$$tippy=!0;var B=1,H=[],U=[];function _(o,s){var v,g,h,C,T,A,L,k,M=j(o,Object.assign({},R,P(l(s)))),V=!1,I=!1,N=!1,_=!1,F=[],W=a(we,M.interactiveDebounce),X=B++,Y=(k=M.plugins).filter((function(e,t){return k.indexOf(e)===t})),$={id:X,reference:o,popper:d(),popperInstance:null,props:M,state:{isEnabled:!0,isVisible:!1,isDestroyed:!1,isMounted:!1,isShown:!1},plugins:Y,clearDelayTimeouts:function(){clearTimeout(v),clearTimeout(g),cancelAnimationFrame(h)},setProps:function(e){if($.state.isDestroyed)return;ae("onBeforeUpdate",[$,e]),be();var t=$.props,n=j(o,Object.assign({},t,l(e),{ignoreAttributes:!0}));$.props=n,he(),t.interactiveDebounce!==n.interactiveDebounce&&(ce(),W=a(we,n.interactiveDebounce));t.triggerTarget&&!n.triggerTarget?u(t.triggerTarget).forEach((function(e){e.removeAttribute("aria-expanded")})):n.triggerTarget&&o.removeAttribute("aria-expanded");ue(),ie(),J&&J(t,n);$.popperInstance&&(Ce(),Ae().forEach((function(e){requestAnimationFrame(e._tippy.popperInstance.forceUpdate)})));ae("onAfterUpdate",[$,e])},setContent:function(e){$.setProps({content:e})},show:function(){var e=$.state.isVisible,t=$.state.isDestroyed,o=!$.state.isEnabled,a=x.isTouch&&!$.props.touch,s=r($.props.duration,0,R.duration);if(e||t||o||a)return;if(te().hasAttribute("disabled"))return;if(ae("onShow",[$],!1),!1===$.props.onShow($))return;$.state.isVisible=!0,ee()&&(z.style.visibility="visible");ie(),de(),$.state.isMounted||(z.style.transition="none");if(ee()){var u=re(),p=u.box,f=u.content;b([p,f],0)}A=function(){var e;if($.state.isVisible&&!_){if(_=!0,z.offsetHeight,z.style.transition=$.props.moveTransition,ee()&&$.props.animation){var t=re(),n=t.box,r=t.content;b([n,r],s),y([n,r],"visible")}se(),ue(),c(U,$),null==(e=$.popperInstance)||e.forceUpdate(),ae("onMount",[$]),$.props.animation&&ee()&&function(e,t){me(e,t)}(s,(function(){$.state.isShown=!0,ae("onShown",[$])}))}},function(){var e,t=$.props.appendTo,r=te();e=$.props.interactive&&t===n||"parent"===t?r.parentNode:i(t,[r]);e.contains(z)||e.appendChild(z);$.state.isMounted=!0,Ce()}()},hide:function(){var e=!$.state.isVisible,t=$.state.isDestroyed,n=!$.state.isEnabled,o=r($.props.duration,1,R.duration);if(e||t||n)return;if(ae("onHide",[$],!1),!1===$.props.onHide($))return;$.state.isVisible=!1,$.state.isShown=!1,_=!1,V=!1,ee()&&(z.style.visibility="hidden");if(ce(),ve(),ie(!0),ee()){var i=re(),a=i.box,s=i.content;$.props.animation&&(b([a,s],o),y([a,s],"hidden"))}se(),ue(),$.props.animation?ee()&&function(e,t){me(e,(function(){!$.state.isVisible&&z.parentNode&&z.parentNode.contains(z)&&t()}))}(o,$.unmount):$.unmount()},hideWithInteractivity:function(e){ne().addEventListener("mousemove",W),c(H,W),W(e)},enable:function(){$.state.isEnabled=!0},disable:function(){$.hide(),$.state.isEnabled=!1},unmount:function(){$.state.isVisible&&$.hide();if(!$.state.isMounted)return;Te(),Ae().forEach((function(e){e._tippy.unmount()})),z.parentNode&&z.parentNode.removeChild(z);U=U.filter((function(e){return e!==$})),$.state.isMounted=!1,ae("onHidden",[$])},destroy:function(){if($.state.isDestroyed)return;$.clearDelayTimeouts(),$.unmount(),be(),delete o._tippy,$.state.isDestroyed=!0,ae("onDestroy",[$])}};if(!M.render)return $;var q=M.render($),z=q.popper,J=q.onUpdate;z.setAttribute("data-tippy-root",""),z.id="tippy-"+$.id,$.popper=z,o._tippy=$,z._tippy=$;var G=Y.map((function(e){return e.fn($)})),K=o.hasAttribute("aria-expanded");return he(),ue(),ie(),ae("onCreate",[$]),M.showOnCreate&&Le(),z.addEventListener("mouseenter",(function(){$.props.interactive&&$.state.isVisible&&$.clearDelayTimeouts()})),z.addEventListener("mouseleave",(function(){$.props.interactive&&$.props.trigger.indexOf("mouseenter")>=0&&ne().addEventListener("mousemove",W)})),$;function Q(){var e=$.props.touch;return Array.isArray(e)?e:[e,0]}function Z(){return"hold"===Q()[0]}function ee(){var e;return!(null==(e=$.props.render)||!e.$$tippy)}function te(){return L||o}function ne(){var e=te().parentNode;return e?w(e):document}function re(){return S(z)}function oe(e){return $.state.isMounted&&!$.state.isVisible||x.isTouch||C&&"focus"===C.type?0:r($.props.delay,e?0:1,R.delay)}function ie(e){void 0===e&&(e=!1),z.style.pointerEvents=$.props.interactive&&!e?"":"none",z.style.zIndex=""+$.props.zIndex}function ae(e,t,n){var r;(void 0===n&&(n=!0),G.forEach((function(n){n[e]&&n[e].apply(n,t)})),n)&&(r=$.props)[e].apply(r,t)}function se(){var e=$.props.aria;if(e.content){var t="aria-"+e.content,n=z.id;u($.props.triggerTarget||o).forEach((function(e){var r=e.getAttribute(t);if($.state.isVisible)e.setAttribute(t,r?r+" "+n:n);else{var o=r&&r.replace(n,"").trim();o?e.setAttribute(t,o):e.removeAttribute(t)}}))}}function ue(){!K&&$.props.aria.expanded&&u($.props.triggerTarget||o).forEach((function(e){$.props.interactive?e.setAttribute("aria-expanded",$.state.isVisible&&e===te()?"true":"false"):e.removeAttribute("aria-expanded")}))}function ce(){ne().removeEventListener("mousemove",W),H=H.filter((function(e){return e!==W}))}function pe(e){if(!x.isTouch||!N&&"mousedown"!==e.type){var t=e.composedPath&&e.composedPath()[0]||e.target;if(!$.props.interactive||!O(z,t)){if(u($.props.triggerTarget||o).some((function(e){return O(e,t)}))){if(x.isTouch)return;if($.state.isVisible&&$.props.trigger.indexOf("click")>=0)return}else ae("onClickOutside",[$,e]);!0===$.props.hideOnClick&&($.clearDelayTimeouts(),$.hide(),I=!0,setTimeout((function(){I=!1})),$.state.isMounted||ve())}}}function fe(){N=!0}function le(){N=!1}function de(){var e=ne();e.addEventListener("mousedown",pe,!0),e.addEventListener("touchend",pe,t),e.addEventListener("touchstart",le,t),e.addEventListener("touchmove",fe,t)}function ve(){var e=ne();e.removeEventListener("mousedown",pe,!0),e.removeEventListener("touchend",pe,t),e.removeEventListener("touchstart",le,t),e.removeEventListener("touchmove",fe,t)}function me(e,t){var n=re().box;function r(e){e.target===n&&(E(n,"remove",r),t())}if(0===e)return t();E(n,"remove",T),E(n,"add",r),T=r}function ge(e,t,n){void 0===n&&(n=!1),u($.props.triggerTarget||o).forEach((function(r){r.addEventListener(e,t,n),F.push({node:r,eventType:e,handler:t,options:n})}))}function he(){var e;Z()&&(ge("touchstart",ye,{passive:!0}),ge("touchend",Ee,{passive:!0})),(e=$.props.trigger,e.split(/\s+/).filter(Boolean)).forEach((function(e){if("manual"!==e)switch(ge(e,ye),e){case"mouseenter":ge("mouseleave",Ee);break;case"focus":ge(D?"focusout":"blur",Oe);break;case"focusin":ge("focusout",Oe)}}))}function be(){F.forEach((function(e){var t=e.node,n=e.eventType,r=e.handler,o=e.options;t.removeEventListener(n,r,o)})),F=[]}function ye(e){var t,n=!1;if($.state.isEnabled&&!xe(e)&&!I){var r="focus"===(null==(t=C)?void 0:t.type);C=e,L=e.currentTarget,ue(),!$.state.isVisible&&m(e)&&H.forEach((function(t){return t(e)})),"click"===e.type&&($.props.trigger.indexOf("mouseenter")<0||V)&&!1!==$.props.hideOnClick&&$.state.isVisible?n=!0:Le(e),"click"===e.type&&(V=!n),n&&!r&&De(e)}}function we(e){var t=e.target,n=te().contains(t)||z.contains(t);"mousemove"===e.type&&n||function(e,t){var n=t.clientX,r=t.clientY;return e.every((function(e){var t=e.popperRect,o=e.popperState,i=e.props.interactiveBorder,a=p(o.placement),s=o.modifiersData.offset;if(!s)return!0;var u="bottom"===a?s.top.y:0,c="top"===a?s.bottom.y:0,f="right"===a?s.left.x:0,l="left"===a?s.right.x:0,d=t.top-r+u>i,v=r-t.bottom-c>i,m=t.left-n+f>i,g=n-t.right-l>i;return d||v||m||g}))}(Ae().concat(z).map((function(e){var t,n=null==(t=e._tippy.popperInstance)?void 0:t.state;return n?{popperRect:e.getBoundingClientRect(),popperState:n,props:M}:null})).filter(Boolean),e)&&(ce(),De(e))}function Ee(e){xe(e)||$.props.trigger.indexOf("click")>=0&&V||($.props.interactive?$.hideWithInteractivity(e):De(e))}function Oe(e){$.props.trigger.indexOf("focusin")<0&&e.target!==te()||$.props.interactive&&e.relatedTarget&&z.contains(e.relatedTarget)||De(e)}function xe(e){return!!x.isTouch&&Z()!==e.type.indexOf("touch")>=0}function Ce(){Te();var t=$.props,n=t.popperOptions,r=t.placement,i=t.offset,a=t.getReferenceClientRect,s=t.moveTransition,u=ee()?S(z).arrow:null,c=a?{getBoundingClientRect:a,contextElement:a.contextElement||te()}:o,p=[{name:"offset",options:{offset:i}},{name:"preventOverflow",options:{padding:{top:2,bottom:2,left:5,right:5}}},{name:"flip",options:{padding:5}},{name:"computeStyles",options:{adaptive:!s}},{name:"$$tippy",enabled:!0,phase:"beforeWrite",requires:["computeStyles"],fn:function(e){var t=e.state;if(ee()){var n=re().box;["placement","reference-hidden","escaped"].forEach((function(e){"placement"===e?n.setAttribute("data-placement",t.placement):t.attributes.popper["data-popper-"+e]?n.setAttribute("data-"+e,""):n.removeAttribute("data-"+e)})),t.attributes.popper={}}}}];ee()&&u&&p.push({name:"arrow",options:{element:u,padding:3}}),p.push.apply(p,(null==n?void 0:n.modifiers)||[]),$.popperInstance=e.createPopper(c,z,Object.assign({},n,{placement:r,onFirstUpdate:A,modifiers:p}))}function Te(){$.popperInstance&&($.popperInstance.destroy(),$.popperInstance=null)}function Ae(){return f(z.querySelectorAll("[data-tippy-root]"))}function Le(e){$.clearDelayTimeouts(),e&&ae("onTrigger",[$,e]),de();var t=oe(!0),n=Q(),r=n[0],o=n[1];x.isTouch&&"hold"===r&&o&&(t=o),t?v=setTimeout((function(){$.show()}),t):$.show()}function De(e){if($.clearDelayTimeouts(),ae("onUntrigger",[$,e]),$.state.isVisible){if(!($.props.trigger.indexOf("mouseenter")>=0&&$.props.trigger.indexOf("click")>=0&&["mouseleave","mousemove"].indexOf(e.type)>=0&&V)){var t=oe(!1);t?g=setTimeout((function(){$.state.isVisible&&$.hide()}),t):h=requestAnimationFrame((function(){$.hide()}))}}else ve()}}function F(e,n){void 0===n&&(n={});var r=R.plugins.concat(n.plugins||[]);document.addEventListener("touchstart",T,t),window.addEventListener("blur",L);var o=Object.assign({},n,{plugins:r}),i=h(e).reduce((function(e,t){var n=t&&_(t,o);return n&&e.push(n),e}),[]);return v(e)?i[0]:i}F.defaultProps=R,F.setDefaultProps=function(e){Object.keys(e).forEach((function(t){R[t]=e[t]}))},F.currentInput=x;var W=Object.assign({},e.applyStyles,{effect:function(e){var t=e.state,n={popper:{position:t.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};Object.assign(t.elements.popper.style,n.popper),t.styles=n,t.elements.arrow&&Object.assign(t.elements.arrow.style,n.arrow)}}),X={mouseover:"mouseenter",focusin:"focus",click:"click"};var Y={name:"animateFill",defaultValue:!1,fn:function(e){var t;if(null==(t=e.props.render)||!t.$$tippy)return{};var n=S(e.popper),r=n.box,o=n.content,i=e.props.animateFill?function(){var e=d();return e.className="tippy-backdrop",y([e],"hidden"),e}():null;return{onCreate:function(){i&&(r.insertBefore(i,r.firstElementChild),r.setAttribute("data-animatefill",""),r.style.overflow="hidden",e.setProps({arrow:!1,animation:"shift-away"}))},onMount:function(){if(i){var e=r.style.transitionDuration,t=Number(e.replace("ms",""));o.style.transitionDelay=Math.round(t/10)+"ms",i.style.transitionDuration=e,y([i],"visible")}},onShow:function(){i&&(i.style.transitionDuration="0ms")},onHide:function(){i&&y([i],"hidden")}}}};var $={clientX:0,clientY:0},q=[];function z(e){var t=e.clientX,n=e.clientY;$={clientX:t,clientY:n}}var J={name:"followCursor",defaultValue:!1,fn:function(e){var t=e.reference,n=w(e.props.triggerTarget||t),r=!1,o=!1,i=!0,a=e.props;function s(){return"initial"===e.props.followCursor&&e.state.isVisible}function u(){n.addEventListener("mousemove",f)}function c(){n.removeEventListener("mousemove",f)}function p(){r=!0,e.setProps({getReferenceClientRect:null}),r=!1}function f(n){var r=!n.target||t.contains(n.target),o=e.props.followCursor,i=n.clientX,a=n.clientY,s=t.getBoundingClientRect(),u=i-s.left,c=a-s.top;!r&&e.props.interactive||e.setProps({getReferenceClientRect:function(){var e=t.getBoundingClientRect(),n=i,r=a;"initial"===o&&(n=e.left+u,r=e.top+c);var s="horizontal"===o?e.top:r,p="vertical"===o?e.right:n,f="horizontal"===o?e.bottom:r,l="vertical"===o?e.left:n;return{width:p-l,height:f-s,top:s,right:p,bottom:f,left:l}}})}function l(){e.props.followCursor&&(q.push({instance:e,doc:n}),function(e){e.addEventListener("mousemove",z)}(n))}function d(){0===(q=q.filter((function(t){return t.instance!==e}))).filter((function(e){return e.doc===n})).length&&function(e){e.removeEventListener("mousemove",z)}(n)}return{onCreate:l,onDestroy:d,onBeforeUpdate:function(){a=e.props},onAfterUpdate:function(t,n){var i=n.followCursor;r||void 0!==i&&a.followCursor!==i&&(d(),i?(l(),!e.state.isMounted||o||s()||u()):(c(),p()))},onMount:function(){e.props.followCursor&&!o&&(i&&(f($),i=!1),s()||u())},onTrigger:function(e,t){m(t)&&($={clientX:t.clientX,clientY:t.clientY}),o="focus"===t.type},onHidden:function(){e.props.followCursor&&(p(),c(),i=!0)}}}};var G={name:"inlinePositioning",defaultValue:!1,fn:function(e){var t,n=e.reference;var r=-1,o=!1,i=[],a={name:"tippyInlinePositioning",enabled:!0,phase:"afterWrite",fn:function(o){var a=o.state;e.props.inlinePositioning&&(-1!==i.indexOf(a.placement)&&(i=[]),t!==a.placement&&-1===i.indexOf(a.placement)&&(i.push(a.placement),e.setProps({getReferenceClientRect:function(){return function(e){return function(e,t,n,r){if(n.length<2||null===e)return t;if(2===n.length&&r>=0&&n[0].left>n[1].right)return n[r]||t;switch(e){case"top":case"bottom":var o=n[0],i=n[n.length-1],a="top"===e,s=o.top,u=i.bottom,c=a?o.left:i.left,p=a?o.right:i.right;return{top:s,bottom:u,left:c,right:p,width:p-c,height:u-s};case"left":case"right":var f=Math.min.apply(Math,n.map((function(e){return e.left}))),l=Math.max.apply(Math,n.map((function(e){return e.right}))),d=n.filter((function(t){return"left"===e?t.left===f:t.right===l})),v=d[0].top,m=d[d.length-1].bottom;return{top:v,bottom:m,left:f,right:l,width:l-f,height:m-v};default:return t}}(p(e),n.getBoundingClientRect(),f(n.getClientRects()),r)}(a.placement)}})),t=a.placement)}};function s(){var t;o||(t=function(e,t){var n;return{popperOptions:Object.assign({},e.popperOptions,{modifiers:[].concat(((null==(n=e.popperOptions)?void 0:n.modifiers)||[]).filter((function(e){return e.name!==t.name})),[t])})}}(e.props,a),o=!0,e.setProps(t),o=!1)}return{onCreate:s,onAfterUpdate:s,onTrigger:function(t,n){if(m(n)){var o=f(e.reference.getClientRects()),i=o.find((function(e){return e.left-2<=n.clientX&&e.right+2>=n.clientX&&e.top-2<=n.clientY&&e.bottom+2>=n.clientY})),a=o.indexOf(i);r=a>-1?a:r}},onHidden:function(){r=-1}}}};var K={name:"sticky",defaultValue:!1,fn:function(e){var t=e.reference,n=e.popper;function r(t){return!0===e.props.sticky||e.props.sticky===t}var o=null,i=null;function a(){var s=r("reference")?(e.popperInstance?e.popperInstance.state.elements.reference:t).getBoundingClientRect():null,u=r("popper")?n.getBoundingClientRect():null;(s&&Q(o,s)||u&&Q(i,u))&&e.popperInstance&&e.popperInstance.update(),o=s,i=u,e.state.isMounted&&requestAnimationFrame(a)}return{onMount:function(){e.props.sticky&&a()}}}};function Q(e,t){return!e||!t||(e.top!==t.top||e.right!==t.right||e.bottom!==t.bottom||e.left!==t.left)}return F.setDefaultProps({plugins:[Y,J,G,K],render:N}),F.createSingleton=function(e,t){var n;void 0===t&&(t={});var r,o=e,i=[],a=[],c=t.overrides,p=[],f=!1;function l(){a=o.map((function(e){return u(e.props.triggerTarget||e.reference)})).reduce((function(e,t){return e.concat(t)}),[])}function v(){i=o.map((function(e){return e.reference}))}function m(e){o.forEach((function(t){e?t.enable():t.disable()}))}function g(e){return o.map((function(t){var n=t.setProps;return t.setProps=function(o){n(o),t.reference===r&&e.setProps(o)},function(){t.setProps=n}}))}function h(e,t){var n=a.indexOf(t);if(t!==r){r=t;var s=(c||[]).concat("content").reduce((function(e,t){return e[t]=o[n].props[t],e}),{});e.setProps(Object.assign({},s,{getReferenceClientRect:"function"==typeof s.getReferenceClientRect?s.getReferenceClientRect:function(){var e;return null==(e=i[n])?void 0:e.getBoundingClientRect()}}))}}m(!1),v(),l();var b={fn:function(){return{onDestroy:function(){m(!0)},onHidden:function(){r=null},onClickOutside:function(e){e.props.showOnCreate&&!f&&(f=!0,r=null)},onShow:function(e){e.props.showOnCreate&&!f&&(f=!0,h(e,i[0]))},onTrigger:function(e,t){h(e,t.currentTarget)}}}},y=F(d(),Object.assign({},s(t,["overrides"]),{plugins:[b].concat(t.plugins||[]),triggerTarget:a,popperOptions:Object.assign({},t.popperOptions,{modifiers:[].concat((null==(n=t.popperOptions)?void 0:n.modifiers)||[],[W])})})),w=y.show;y.show=function(e){if(w(),!r&&null==e)return h(y,i[0]);if(!r||null!=e){if("number"==typeof e)return i[e]&&h(y,i[e]);if(o.indexOf(e)>=0){var t=e.reference;return h(y,t)}return i.indexOf(e)>=0?h(y,e):void 0}},y.showNext=function(){var e=i[0];if(!r)return y.show(0);var t=i.indexOf(r);y.show(i[t+1]||e)},y.showPrevious=function(){var e=i[i.length-1];if(!r)return y.show(e);var t=i.indexOf(r),n=i[t-1]||e;y.show(n)};var E=y.setProps;return y.setProps=function(e){c=e.overrides||c,E(e)},y.setInstances=function(e){m(!0),p.forEach((function(e){return e()})),o=e,m(!1),v(),l(),p=g(y),y.setProps({triggerTarget:a})},p=g(y),y},F.delegate=function(e,n){var r=[],o=[],i=!1,a=n.target,c=s(n,["target"]),p=Object.assign({},c,{trigger:"manual",touch:!1}),f=Object.assign({touch:R.touch},c,{showOnCreate:!0}),l=F(e,p);function d(e){if(e.target&&!i){var t=e.target.closest(a);if(t){var r=t.getAttribute("data-tippy-trigger")||n.trigger||R.trigger;if(!t._tippy&&!("touchstart"===e.type&&"boolean"==typeof f.touch||"touchstart"!==e.type&&r.indexOf(X[e.type])<0)){var s=F(t,f);s&&(o=o.concat(s))}}}}function v(e,t,n,o){void 0===o&&(o=!1),e.addEventListener(t,n,o),r.push({node:e,eventType:t,handler:n,options:o})}return u(l).forEach((function(e){var n=e.destroy,a=e.enable,s=e.disable;e.destroy=function(e){void 0===e&&(e=!0),e&&o.forEach((function(e){e.destroy()})),o=[],r.forEach((function(e){var t=e.node,n=e.eventType,r=e.handler,o=e.options;t.removeEventListener(n,r,o)})),r=[],n()},e.enable=function(){a(),o.forEach((function(e){return e.enable()})),i=!1},e.disable=function(){s(),o.forEach((function(e){return e.disable()})),i=!0},function(e){var n=e.reference;v(n,"touchstart",d,t),v(n,"mouseover",d),v(n,"focusin",d),v(n,"click",d)}(e)})),l},F.hideAll=function(e){var t=void 0===e?{}:e,n=t.exclude,r=t.duration;U.forEach((function(e){var t=!1;if(n&&(t=g(n)?e.reference===n:e.popper===n.popper),!t){var o=e.props.duration;e.setProps({duration:r}),e.hide(),e.state.isDestroyed||e.setProps({duration:o})}}))},F.roundArrow='',F})); + diff --git a/doc/site_libs/quarto-nav/headroom.min.js b/doc/site_libs/quarto-nav/headroom.min.js new file mode 100644 index 000000000..b08f1dffb --- /dev/null +++ b/doc/site_libs/quarto-nav/headroom.min.js @@ -0,0 +1,7 @@ +/*! + * headroom.js v0.12.0 - Give your page some headroom. Hide your header until you need it + * Copyright (c) 2020 Nick Williams - http://wicky.nillia.ms/headroom.js + * License: MIT + */ + +!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(t=t||self).Headroom=n()}(this,function(){"use strict";function t(){return"undefined"!=typeof window}function d(t){return function(t){return t&&t.document&&function(t){return 9===t.nodeType}(t.document)}(t)?function(t){var n=t.document,o=n.body,s=n.documentElement;return{scrollHeight:function(){return Math.max(o.scrollHeight,s.scrollHeight,o.offsetHeight,s.offsetHeight,o.clientHeight,s.clientHeight)},height:function(){return t.innerHeight||s.clientHeight||o.clientHeight},scrollY:function(){return void 0!==t.pageYOffset?t.pageYOffset:(s||o.parentNode||o).scrollTop}}}(t):function(t){return{scrollHeight:function(){return Math.max(t.scrollHeight,t.offsetHeight,t.clientHeight)},height:function(){return Math.max(t.offsetHeight,t.clientHeight)},scrollY:function(){return t.scrollTop}}}(t)}function n(t,s,e){var n,o=function(){var n=!1;try{var t={get passive(){n=!0}};window.addEventListener("test",t,t),window.removeEventListener("test",t,t)}catch(t){n=!1}return n}(),i=!1,r=d(t),l=r.scrollY(),a={};function c(){var t=Math.round(r.scrollY()),n=r.height(),o=r.scrollHeight();a.scrollY=t,a.lastScrollY=l,a.direction=ls.tolerance[a.direction],e(a),l=t,i=!1}function h(){i||(i=!0,n=requestAnimationFrame(c))}var u=!!o&&{passive:!0,capture:!1};return t.addEventListener("scroll",h,u),c(),{destroy:function(){cancelAnimationFrame(n),t.removeEventListener("scroll",h,u)}}}function o(t){return t===Object(t)?t:{down:t,up:t}}function s(t,n){n=n||{},Object.assign(this,s.options,n),this.classes=Object.assign({},s.options.classes,n.classes),this.elem=t,this.tolerance=o(this.tolerance),this.offset=o(this.offset),this.initialised=!1,this.frozen=!1}return s.prototype={constructor:s,init:function(){return s.cutsTheMustard&&!this.initialised&&(this.addClass("initial"),this.initialised=!0,setTimeout(function(t){t.scrollTracker=n(t.scroller,{offset:t.offset,tolerance:t.tolerance},t.update.bind(t))},100,this)),this},destroy:function(){this.initialised=!1,Object.keys(this.classes).forEach(this.removeClass,this),this.scrollTracker.destroy()},unpin:function(){!this.hasClass("pinned")&&this.hasClass("unpinned")||(this.addClass("unpinned"),this.removeClass("pinned"),this.onUnpin&&this.onUnpin.call(this))},pin:function(){this.hasClass("unpinned")&&(this.addClass("pinned"),this.removeClass("unpinned"),this.onPin&&this.onPin.call(this))},freeze:function(){this.frozen=!0,this.addClass("frozen")},unfreeze:function(){this.frozen=!1,this.removeClass("frozen")},top:function(){this.hasClass("top")||(this.addClass("top"),this.removeClass("notTop"),this.onTop&&this.onTop.call(this))},notTop:function(){this.hasClass("notTop")||(this.addClass("notTop"),this.removeClass("top"),this.onNotTop&&this.onNotTop.call(this))},bottom:function(){this.hasClass("bottom")||(this.addClass("bottom"),this.removeClass("notBottom"),this.onBottom&&this.onBottom.call(this))},notBottom:function(){this.hasClass("notBottom")||(this.addClass("notBottom"),this.removeClass("bottom"),this.onNotBottom&&this.onNotBottom.call(this))},shouldUnpin:function(t){return"down"===t.direction&&!t.top&&t.toleranceExceeded},shouldPin:function(t){return"up"===t.direction&&t.toleranceExceeded||t.top},addClass:function(t){this.elem.classList.add.apply(this.elem.classList,this.classes[t].split(" "))},removeClass:function(t){this.elem.classList.remove.apply(this.elem.classList,this.classes[t].split(" "))},hasClass:function(t){return this.classes[t].split(" ").every(function(t){return this.classList.contains(t)},this.elem)},update:function(t){t.isOutOfBounds||!0!==this.frozen&&(t.top?this.top():this.notTop(),t.bottom?this.bottom():this.notBottom(),this.shouldUnpin(t)?this.unpin():this.shouldPin(t)&&this.pin())}},s.options={tolerance:{up:0,down:0},offset:0,scroller:t()?window:null,classes:{frozen:"headroom--frozen",pinned:"headroom--pinned",unpinned:"headroom--unpinned",top:"headroom--top",notTop:"headroom--not-top",bottom:"headroom--bottom",notBottom:"headroom--not-bottom",initial:"headroom"}},s.cutsTheMustard=!!(t()&&function(){}.bind&&"classList"in document.documentElement&&Object.assign&&Object.keys&&requestAnimationFrame),s}); diff --git a/doc/site_libs/quarto-nav/quarto-nav.js b/doc/site_libs/quarto-nav/quarto-nav.js new file mode 100644 index 000000000..b41b31e45 --- /dev/null +++ b/doc/site_libs/quarto-nav/quarto-nav.js @@ -0,0 +1,222 @@ +const headroomChanged = new CustomEvent("quarto-hrChanged", { + detail: {}, + bubbles: true, + cancelable: false, + composed: false, +}); + +window.document.addEventListener("DOMContentLoaded", function () { + let init = false; + + function throttle(func, wait) { + var timeout; + return function () { + const context = this; + const args = arguments; + const later = function () { + clearTimeout(timeout); + timeout = null; + func.apply(context, args); + }; + + if (!timeout) { + timeout = setTimeout(later, wait); + } + }; + } + + function headerOffset() { + // Set an offset if there is are fixed top navbar + const headerEl = window.document.querySelector("header.fixed-top"); + if (headerEl) { + return headerEl.clientHeight; + } else { + return 0; + } + } + + function footerOffset() { + const footerEl = window.document.querySelector("footer.footer"); + if (footerEl) { + return footerEl.clientHeight; + } else { + return 0; + } + } + + function updateDocumentOffsetWithoutAnimation() { + updateDocumentOffset(false); + } + + function updateDocumentOffset(animated) { + // set body offset + const topOffset = headerOffset(); + const bodyOffset = topOffset + footerOffset(); + const bodyEl = window.document.body; + bodyEl.setAttribute("data-bs-offset", topOffset); + bodyEl.style.paddingTop = topOffset + "px"; + + // deal with sidebar offsets + const sidebars = window.document.querySelectorAll( + ".sidebar, .headroom-target" + ); + sidebars.forEach((sidebar) => { + if (!animated) { + sidebar.classList.add("notransition"); + // Remove the no transition class after the animation has time to complete + setTimeout(function () { + sidebar.classList.remove("notransition"); + }, 201); + } + + if (window.Headroom && sidebar.classList.contains("sidebar-unpinned")) { + sidebar.style.top = "0"; + sidebar.style.maxHeight = "100vh"; + } else { + sidebar.style.top = topOffset + "px"; + sidebar.style.maxHeight = "calc(100vh - " + topOffset + "px)"; + } + }); + + // allow space for footer + const mainContainer = window.document.querySelector(".quarto-container"); + if (mainContainer) { + mainContainer.style.minHeight = "calc(100vh - " + bodyOffset + "px)"; + } + + // link offset + let linkStyle = window.document.querySelector("#quarto-target-style"); + if (!linkStyle) { + linkStyle = window.document.createElement("style"); + linkStyle.setAttribute("id", "quarto-target-style"); + window.document.head.appendChild(linkStyle); + } + while (linkStyle.firstChild) { + linkStyle.removeChild(linkStyle.firstChild); + } + if (topOffset > 0) { + linkStyle.appendChild( + window.document.createTextNode(` + section:target::before { + content: ""; + display: block; + height: ${topOffset}px; + margin: -${topOffset}px 0 0; + }`) + ); + } + if (init) { + window.dispatchEvent(headroomChanged); + } + init = true; + } + + // initialize headroom + var header = window.document.querySelector("#quarto-header"); + if (header && window.Headroom) { + const headroom = new window.Headroom(header, { + tolerance: 5, + onPin: function () { + const sidebars = window.document.querySelectorAll( + ".sidebar, .headroom-target" + ); + sidebars.forEach((sidebar) => { + sidebar.classList.remove("sidebar-unpinned"); + }); + updateDocumentOffset(); + }, + onUnpin: function () { + const sidebars = window.document.querySelectorAll( + ".sidebar, .headroom-target" + ); + sidebars.forEach((sidebar) => { + sidebar.classList.add("sidebar-unpinned"); + }); + updateDocumentOffset(); + }, + }); + headroom.init(); + + let frozen = false; + window.quartoToggleHeadroom = function () { + if (frozen) { + headroom.unfreeze(); + frozen = false; + } else { + headroom.freeze(); + frozen = true; + } + }; + } + + // Observe size changed for the header + const headerEl = window.document.querySelector("header.fixed-top"); + if (headerEl && window.ResizeObserver) { + const observer = new window.ResizeObserver( + updateDocumentOffsetWithoutAnimation + ); + observer.observe(headerEl, { + attributes: true, + childList: true, + characterData: true, + }); + } else { + window.addEventListener( + "resize", + throttle(updateDocumentOffsetWithoutAnimation, 50) + ); + } + setTimeout(updateDocumentOffsetWithoutAnimation, 250); + + // fixup index.html links if we aren't on the filesystem + if (window.location.protocol !== "file:") { + const links = window.document.querySelectorAll("a"); + for (let i = 0; i < links.length; i++) { + links[i].href = links[i].href.replace(/\/index\.html/, "/"); + } + + // Fixup any sharing links that require urls + // Append url to any sharing urls + const sharingLinks = window.document.querySelectorAll( + "a.sidebar-tools-main-item" + ); + for (let i = 0; i < sharingLinks.length; i++) { + const sharingLink = sharingLinks[i]; + const href = sharingLink.getAttribute("href"); + if (href) { + sharingLink.setAttribute( + "href", + href.replace("|url|", window.location.href) + ); + } + } + + // Scroll the active navigation item into view, if necessary + const navSidebar = window.document.querySelector("nav#quarto-sidebar"); + if (navSidebar) { + // Find the active item + const activeItem = navSidebar.querySelector("li.sidebar-item a.active"); + if (activeItem) { + // Wait for the scroll height and height to resolve by observing size changes on the + // nav element that is scrollable + const resizeObserver = new ResizeObserver((_entries) => { + // The bottom of the element + const elBottom = activeItem.offsetTop; + const viewBottom = navSidebar.scrollTop + navSidebar.clientHeight; + + // The element height and scroll height are the same, then we are still loading + if (viewBottom !== navSidebar.scrollHeight) { + // Determine if the item isn't visible and scroll to it + if (elBottom >= viewBottom) { + navSidebar.scrollTop = elBottom; + } + + // stop observing now since we've completed the scroll + resizeObserver.unobserve(navSidebar); + } + }); + resizeObserver.observe(navSidebar); + } + } + } +}); diff --git a/doc/site_libs/quarto-search/autocomplete.umd.js b/doc/site_libs/quarto-search/autocomplete.umd.js new file mode 100644 index 000000000..3f2dcf0d6 --- /dev/null +++ b/doc/site_libs/quarto-search/autocomplete.umd.js @@ -0,0 +1,3 @@ +/*! @algolia/autocomplete-js 1.5.3 | MIT License | © Algolia, Inc. and contributors | https://github.com/algolia/autocomplete */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self)["@algolia/autocomplete-js"]={})}(this,(function(e){"use strict";function t(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function n(e){for(var n=1;n=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function a(e){return function(e){if(Array.isArray(e))return c(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||function(e,t){if(!e)return;if("string"==typeof e)return c(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return c(e,t)}(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function c(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n=n?null===r?null:0:o}function j(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function w(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function S(e,t){var n=[];return Promise.resolve(e(t)).then((function(e){return Promise.all(e.filter((function(e){return Boolean(e)})).map((function(e){if(e.sourceId,n.includes(e.sourceId))throw new Error("[Autocomplete] The `sourceId` ".concat(JSON.stringify(e.sourceId)," is not unique."));n.push(e.sourceId);var t=function(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=new Array(t);ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}var oe,ie,ue,ae=null,ce=(oe=-1,ie=-1,ue=void 0,function(e){var t=++oe;return Promise.resolve(e).then((function(e){return ue&&t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}var me=["props","refresh","store"],he=["inputElement","formElement","panelElement"],ge=["inputElement"],ye=["inputElement","maxLength"],be=["item","source"];function Oe(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function _e(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function we(e){var t=e.props,n=e.refresh,r=e.store,o=je(e,me);return{getEnvironmentProps:function(e){var n=e.inputElement,o=e.formElement,i=e.panelElement;return _e({onTouchStart:function(e){!r.getState().isOpen&&r.pendingRequests.isEmpty()||e.target===n||!1===[o,i].some((function(t){return n=t,r=e.target,n===r||n.contains(r);var n,r}))&&(r.dispatch("blur",null),t.debug||r.pendingRequests.cancelAll())},onTouchMove:function(e){!1!==r.getState().isOpen&&n===t.environment.document.activeElement&&e.target!==n&&n.blur()}},je(e,he))},getRootProps:function(e){return _e({role:"combobox","aria-expanded":r.getState().isOpen,"aria-haspopup":"listbox","aria-owns":r.getState().isOpen?"".concat(t.id,"-list"):void 0,"aria-labelledby":"".concat(t.id,"-label")},e)},getFormProps:function(e){return e.inputElement,_e({action:"",noValidate:!0,role:"search",onSubmit:function(i){var u;i.preventDefault(),t.onSubmit(_e({event:i,refresh:n,state:r.getState()},o)),r.dispatch("submit",null),null===(u=e.inputElement)||void 0===u||u.blur()},onReset:function(i){var u;i.preventDefault(),t.onReset(_e({event:i,refresh:n,state:r.getState()},o)),r.dispatch("reset",null),null===(u=e.inputElement)||void 0===u||u.focus()}},je(e,ge))},getLabelProps:function(e){return _e({htmlFor:"".concat(t.id,"-input"),id:"".concat(t.id,"-label")},e)},getInputProps:function(e){function i(e){(t.openOnFocus||Boolean(r.getState().query))&&le(_e({event:e,props:t,query:r.getState().completion||r.getState().query,refresh:n,store:r},o)),r.dispatch("focus",null)}var u="ontouchstart"in t.environment,a=e||{};a.inputElement;var c=a.maxLength,l=void 0===c?512:c,s=je(a,ye),p=I(r.getState());return _e({"aria-autocomplete":"both","aria-activedescendant":r.getState().isOpen&&null!==r.getState().activeItemId?"".concat(t.id,"-item-").concat(r.getState().activeItemId):void 0,"aria-controls":r.getState().isOpen?"".concat(t.id,"-list"):void 0,"aria-labelledby":"".concat(t.id,"-label"),value:r.getState().completion||r.getState().query,id:"".concat(t.id,"-input"),autoComplete:"off",autoCorrect:"off",autoCapitalize:"off",enterKeyHint:null!=p&&p.itemUrl?"go":"search",spellCheck:"false",autoFocus:t.autoFocus,placeholder:t.placeholder,maxLength:l,type:"search",onChange:function(e){le(_e({event:e,props:t,query:e.currentTarget.value.slice(0,l),refresh:n,store:r},o))},onKeyDown:function(e){!function(e){var t=e.event,n=e.props,r=e.refresh,o=e.store,i=ve(e,se);if("ArrowUp"===t.key||"ArrowDown"===t.key){var u=function(){var e=n.environment.document.getElementById("".concat(n.id,"-item-").concat(o.getState().activeItemId));e&&(e.scrollIntoViewIfNeeded?e.scrollIntoViewIfNeeded(!1):e.scrollIntoView(!1))},a=function(){var e=I(o.getState());if(null!==o.getState().activeItemId&&e){var n=e.item,u=e.itemInputValue,a=e.itemUrl,c=e.source;c.onActive(fe({event:t,item:n,itemInputValue:u,itemUrl:a,refresh:r,source:c,state:o.getState()},i))}};t.preventDefault(),!1===o.getState().isOpen&&(n.openOnFocus||Boolean(o.getState().query))?le(fe({event:t,props:n,query:o.getState().query,refresh:r,store:o},i)).then((function(){o.dispatch(t.key,{nextActiveItemId:n.defaultActiveItemId}),a(),setTimeout(u,0)})):(o.dispatch(t.key,{}),a(),u())}else if("Escape"===t.key)t.preventDefault(),o.dispatch(t.key,null),o.pendingRequests.cancelAll();else if("Enter"===t.key){if(null===o.getState().activeItemId||o.getState().collections.every((function(e){return 0===e.items.length})))return;t.preventDefault();var c=I(o.getState()),l=c.item,s=c.itemInputValue,p=c.itemUrl,f=c.source;if(t.metaKey||t.ctrlKey)void 0!==p&&(f.onSelect(fe({event:t,item:l,itemInputValue:s,itemUrl:p,refresh:r,source:f,state:o.getState()},i)),n.navigator.navigateNewTab({itemUrl:p,item:l,state:o.getState()}));else if(t.shiftKey)void 0!==p&&(f.onSelect(fe({event:t,item:l,itemInputValue:s,itemUrl:p,refresh:r,source:f,state:o.getState()},i)),n.navigator.navigateNewWindow({itemUrl:p,item:l,state:o.getState()}));else if(t.altKey);else{if(void 0!==p)return f.onSelect(fe({event:t,item:l,itemInputValue:s,itemUrl:p,refresh:r,source:f,state:o.getState()},i)),void n.navigator.navigate({itemUrl:p,item:l,state:o.getState()});le(fe({event:t,nextState:{isOpen:!1},props:n,query:s,refresh:r,store:o},i)).then((function(){f.onSelect(fe({event:t,item:l,itemInputValue:s,itemUrl:p,refresh:r,source:f,state:o.getState()},i))}))}}}(_e({event:e,props:t,refresh:n,store:r},o))},onFocus:i,onBlur:function(){u||(r.dispatch("blur",null),t.debug||r.pendingRequests.cancelAll())},onClick:function(n){e.inputElement!==t.environment.document.activeElement||r.getState().isOpen||i(n)}},s)},getPanelProps:function(e){return _e({onMouseDown:function(e){e.preventDefault()},onMouseLeave:function(){r.dispatch("mouseleave",null)}},e)},getListProps:function(e){return _e({role:"listbox","aria-labelledby":"".concat(t.id,"-label"),id:"".concat(t.id,"-list")},e)},getItemProps:function(e){var i=e.item,u=e.source,a=je(e,be);return _e({id:"".concat(t.id,"-item-").concat(i.__autocomplete_id),role:"option","aria-selected":r.getState().activeItemId===i.__autocomplete_id,onMouseMove:function(e){if(i.__autocomplete_id!==r.getState().activeItemId){r.dispatch("mousemove",i.__autocomplete_id);var t=I(r.getState());if(null!==r.getState().activeItemId&&t){var u=t.item,a=t.itemInputValue,c=t.itemUrl,l=t.source;l.onActive(_e({event:e,item:u,itemInputValue:a,itemUrl:c,refresh:n,source:l,state:r.getState()},o))}}},onMouseDown:function(e){e.preventDefault()},onClick:function(e){var a=u.getItemInputValue({item:i,state:r.getState()}),c=u.getItemUrl({item:i,state:r.getState()});(c?Promise.resolve():le(_e({event:e,nextState:{isOpen:!1},props:t,query:a,refresh:n,store:r},o))).then((function(){u.onSelect(_e({event:e,item:i,itemInputValue:a,itemUrl:c,refresh:n,source:u,state:r.getState()},o))}))}},a)}}}function Se(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function Ie(e){for(var t=1;t0},reshape:function(e){return e.sources}},e),{},{id:null!==(n=e.id)&&void 0!==n?n:d(),plugins:o,initialState:F({activeItemId:null,query:"",completion:null,collections:[],isOpen:!1,status:"idle",context:{}},e.initialState),onStateChange:function(t){var n;null===(n=e.onStateChange)||void 0===n||n.call(e,t),o.forEach((function(e){var n;return null===(n=e.onStateChange)||void 0===n?void 0:n.call(e,t)}))},onSubmit:function(t){var n;null===(n=e.onSubmit)||void 0===n||n.call(e,t),o.forEach((function(e){var n;return null===(n=e.onSubmit)||void 0===n?void 0:n.call(e,t)}))},onReset:function(t){var n;null===(n=e.onReset)||void 0===n||n.call(e,t),o.forEach((function(e){var n;return null===(n=e.onReset)||void 0===n?void 0:n.call(e,t)}))},getSources:function(n){return Promise.all([].concat(R(o.map((function(e){return e.getSources}))),[e.getSources]).filter(Boolean).map((function(e){return S(e,n)}))).then((function(e){return p(e)})).then((function(e){return e.map((function(e){return F(F({},e),{},{onSelect:function(n){e.onSelect(n),t.forEach((function(e){var t;return null===(t=e.onSelect)||void 0===t?void 0:t.call(e,n)}))},onActive:function(n){e.onActive(n),t.forEach((function(e){var t;return null===(t=e.onActive)||void 0===t?void 0:t.call(e,n)}))}})}))}))},navigator:F({navigate:function(e){var t=e.itemUrl;r.location.assign(t)},navigateNewTab:function(e){var t=e.itemUrl,n=r.open(t,"_blank","noopener");null==n||n.focus()},navigateNewWindow:function(e){var t=e.itemUrl;r.open(t,"_blank","noopener")}},e.navigator)})}(e,t),r=x(qe,n,(function(e){var t=e.prevState,r=e.state;n.onStateChange(Le({prevState:t,state:r,refresh:u},o))})),o=function(e){var t=e.store;return{setActiveItemId:function(e){t.dispatch("setActiveItemId",e)},setQuery:function(e){t.dispatch("setQuery",e)},setCollections:function(e){var n=0,r=e.map((function(e){return N(N({},e),{},{items:p(e.items).map((function(e){return N(N({},e),{},{__autocomplete_id:n++})}))})}));t.dispatch("setCollections",r)},setIsOpen:function(e){t.dispatch("setIsOpen",e)},setStatus:function(e){t.dispatch("setStatus",e)},setContext:function(e){t.dispatch("setContext",e)}}}({store:r}),i=we(Le({props:n,refresh:u,store:r},o));function u(){return le(Le({event:new Event("input"),nextState:{isOpen:r.getState().isOpen},props:n,query:r.getState().query,refresh:u,store:r},o))}return n.plugins.forEach((function(e){var n;return null===(n=e.subscribe)||void 0===n?void 0:n.call(e,Le(Le({},o),{},{refresh:u,onSelect:function(e){t.push({onSelect:e})},onActive:function(e){t.push({onActive:e})}}))})),function(e){var t,n=e.metadata,r=e.environment;if(null===(t=r.navigator)||void 0===t?void 0:t.userAgent.includes("Algolia Crawler")){var o=r.document.createElement("meta"),i=r.document.querySelector("head");o.name="algolia:metadata",setTimeout((function(){o.content=JSON.stringify(n),i.appendChild(o)}),0)}}({metadata:Ae({plugins:n.plugins,options:e}),environment:n.environment}),Le(Le({refresh:u},i),o)}var Te=function(e){var t=e.environment,n=t.document.createElementNS("http://www.w3.org/2000/svg","svg");n.setAttribute("class","aa-ClearIcon"),n.setAttribute("viewBox","0 0 24 24"),n.setAttribute("width","18"),n.setAttribute("height","18"),n.setAttribute("fill","currentColor");var r=t.document.createElementNS("http://www.w3.org/2000/svg","path");return r.setAttribute("d","M5.293 6.707l5.293 5.293-5.293 5.293c-0.391 0.391-0.391 1.024 0 1.414s1.024 0.391 1.414 0l5.293-5.293 5.293 5.293c0.391 0.391 1.024 0.391 1.414 0s0.391-1.024 0-1.414l-5.293-5.293 5.293-5.293c0.391-0.391 0.391-1.024 0-1.414s-1.024-0.391-1.414 0l-5.293 5.293-5.293-5.293c-0.391-0.391-1.024-0.391-1.414 0s-0.391 1.024 0 1.414z"),n.appendChild(r),n};function Fe(e,t){if("string"==typeof t){var n=e.document.querySelector(t);return"The element ".concat(JSON.stringify(t)," is not in the document."),n}return t}function Ue(){for(var e=arguments.length,t=new Array(e),n=0;n2&&(u.children=arguments.length>3?tt.call(arguments,2):n),"function"==typeof e&&null!=e.defaultProps)for(i in e.defaultProps)void 0===u[i]&&(u[i]=e.defaultProps[i]);return dt(e,u,r,o,null)}function dt(e,t,n,r,o){var i={type:e,props:t,key:n,ref:r,__k:null,__:null,__b:0,__e:null,__d:void 0,__c:null,__h:null,constructor:void 0,__v:null==o?++rt:o};return null==o&&null!=nt.vnode&&nt.vnode(i),i}function vt(e){return e.children}function mt(e,t){this.props=e,this.context=t}function ht(e,t){if(null==t)return e.__?ht(e.__,e.__.__k.indexOf(e)+1):null;for(var n;t0?dt(d.type,d.props,d.key,null,d.__v):d)){if(d.__=n,d.__b=n.__b+1,null===(f=g[s])||f&&d.key==f.key&&d.type===f.type)g[s]=void 0;else for(p=0;p0&&void 0!==arguments[0]?arguments[0]:[];return{get:function(){return e},add:function(t){var n=e[e.length-1];(null==n?void 0:n.isHighlighted)===t.isHighlighted?e[e.length-1]={value:n.value+t.value,isHighlighted:n.isHighlighted}:e.push(t)}}}(n?[{value:n,isHighlighted:!1}]:[]);return t.forEach((function(e){var t=e.split(Nt);r.add({value:t[0],isHighlighted:!0}),""!==t[1]&&r.add({value:t[1],isHighlighted:!1})})),r.get()}function Rt(e){return function(e){if(Array.isArray(e))return Bt(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||function(e,t){if(!e)return;if("string"==typeof e)return Bt(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return Bt(e,t)}(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function Bt(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n",""":'"',"'":"'"},Ut=new RegExp(/\w/i),Mt=/&(amp|quot|lt|gt|#39);/g,Ht=RegExp(Mt.source);function Vt(e,t){var n,r,o,i=e[t],u=(null===(n=e[t+1])||void 0===n?void 0:n.isHighlighted)||!0,a=(null===(r=e[t-1])||void 0===r?void 0:r.isHighlighted)||!0;return Ut.test((o=i.value)&&Ht.test(o)?o.replace(Mt,(function(e){return Ft[e]})):o)||a!==u?i.isHighlighted:a}function Wt(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function Qt(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function un(e){return function(e){if(Array.isArray(e))return an(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||function(e,t){if(!e)return;if("string"==typeof e)return an(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return an(e,t)}(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function an(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n0;if(!O.value.core.openOnFocus&&!t.query)return n;var r=Boolean(g.current||O.value.renderer.renderNoResults);return!n&&r||n},__autocomplete_metadata:{userAgents:hn,options:e}}))})),j=l(n({collections:[],completion:null,context:{},isOpen:!1,query:"",activeItemId:null,status:"idle"},O.value.core.initialState)),w={getEnvironmentProps:O.value.renderer.getEnvironmentProps,getFormProps:O.value.renderer.getFormProps,getInputProps:O.value.renderer.getInputProps,getItemProps:O.value.renderer.getItemProps,getLabelProps:O.value.renderer.getLabelProps,getListProps:O.value.renderer.getListProps,getPanelProps:O.value.renderer.getPanelProps,getRootProps:O.value.renderer.getRootProps},S={setActiveItemId:P.value.setActiveItemId,setQuery:P.value.setQuery,setCollections:P.value.setCollections,setIsOpen:P.value.setIsOpen,setStatus:P.value.setStatus,setContext:P.value.setContext,refresh:P.value.refresh},I=v((function(){return et({autocomplete:P.value,autocompleteScopeApi:S,classNames:O.value.renderer.classNames,environment:O.value.core.environment,isDetached:_.value,placeholder:O.value.core.placeholder,propGetters:w,setIsModalOpen:D,state:j.current,translations:O.value.renderer.translations})}));function E(){ze(I.value.panel,{style:_.value?{}:mn({panelPlacement:O.value.renderer.panelPlacement,container:I.value.root,form:I.value.form,environment:O.value.core.environment})})}function A(e){j.current=e;var t={autocomplete:P.value,autocompleteScopeApi:S,classNames:O.value.renderer.classNames,components:O.value.renderer.components,container:O.value.renderer.container,createElement:O.value.renderer.renderer.createElement,dom:I.value,Fragment:O.value.renderer.renderer.Fragment,panelContainer:_.value?I.value.detachedContainer:O.value.renderer.panelContainer,propGetters:w,state:j.current},r=!m(e)&&!g.current&&O.value.renderer.renderNoResults||O.value.renderer.render;!function(e){var t=e.autocomplete,r=e.autocompleteScopeApi,o=e.dom,i=e.propGetters,u=e.state;Ge(o.root,i.getRootProps(n({state:u,props:t.getRootProps({})},r))),Ge(o.input,i.getInputProps(n({state:u,props:t.getInputProps({inputElement:o.input}),inputElement:o.input},r))),ze(o.label,{hidden:"stalled"===u.status}),ze(o.loadingIndicator,{hidden:"stalled"!==u.status}),ze(o.clearButton,{hidden:!u.query})}(t),function(e,t){var r=t.autocomplete,o=t.autocompleteScopeApi,u=t.classNames,a=t.createElement,c=t.dom,l=t.Fragment,s=t.panelContainer,p=t.propGetters,f=t.state,d=t.components;if(f.isOpen){s.contains(c.panel)||"loading"===f.status||s.appendChild(c.panel),c.panel.classList.toggle("aa-Panel--stalled","stalled"===f.status);var v=f.collections.filter((function(e){var t=e.source,n=e.items;return t.templates.noResults||n.length>0})).map((function(e,t){var c=e.source,s=e.items;return a("section",{key:t,className:u.source,"data-autocomplete-source-id":c.sourceId},c.templates.header&&a("div",{className:u.sourceHeader},c.templates.header({components:d,createElement:a,Fragment:l,items:s,source:c,state:f})),c.templates.noResults&&0===s.length?a("div",{className:u.sourceNoResults},c.templates.noResults({components:d,createElement:a,Fragment:l,source:c,state:f})):a("ul",i({className:u.list},p.getListProps(n({state:f,props:r.getListProps({})},o))),s.map((function(e){var t=r.getItemProps({item:e,source:c});return a("li",i({key:t.id,className:u.item},p.getItemProps(n({state:f,props:t},o))),c.templates.item({components:d,createElement:a,Fragment:l,item:e,state:f}))}))),c.templates.footer&&a("div",{className:u.sourceFooter},c.templates.footer({components:d,createElement:a,Fragment:l,items:s,source:c,state:f})))})),m=a(l,null,a("div",{className:u.panelLayout},v),a("div",{className:"aa-GradientBottom"})),h=v.reduce((function(e,t){return e[t.props["data-autocomplete-source-id"]]=t,e}),{});e(n({children:m,state:f,sections:v,elements:h,createElement:a,Fragment:l,components:d},o),c.panel)}else s.contains(c.panel)&&s.removeChild(c.panel)}(r,t)}function C(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};c(),y.current=He(O.value.renderer,O.value.core,{initialState:j.current},e),h(),p(),P.value.refresh().then((function(){A(j.current)}))}function D(e){requestAnimationFrame((function(){var t=O.value.core.environment.document.body.contains(I.value.detachedOverlay);e!==t&&(e?(O.value.core.environment.document.body.appendChild(I.value.detachedOverlay),O.value.core.environment.document.body.classList.add("aa-Detached"),I.value.input.focus()):(O.value.core.environment.document.body.removeChild(I.value.detachedOverlay),O.value.core.environment.document.body.classList.remove("aa-Detached"),P.value.setQuery(""),P.value.refresh()))}))}return a((function(){var e=P.value.getEnvironmentProps({formElement:I.value.form,panelElement:I.value.panel,inputElement:I.value.input});return ze(O.value.core.environment,e),function(){ze(O.value.core.environment,Object.keys(e).reduce((function(e,t){return n(n({},e),{},o({},t,void 0))}),{}))}})),a((function(){var e=_.value?O.value.core.environment.document.body:O.value.renderer.panelContainer,t=_.value?I.value.detachedOverlay:I.value.panel;return _.value&&j.current.isOpen&&D(!0),A(j.current),function(){e.contains(t)&&e.removeChild(t)}})),a((function(){var e=O.value.renderer.container;return e.appendChild(I.value.root),function(){e.removeChild(I.value.root)}})),a((function(){var e=s((function(e){A(e.state)}),0);return b.current=function(t){var n=t.state,r=t.prevState;(_.value&&r.isOpen!==n.isOpen&&D(n.isOpen),_.value||!n.isOpen||r.isOpen||E(),n.query!==r.query)&&O.value.core.environment.document.querySelectorAll(".aa-Panel--scrollable").forEach((function(e){0!==e.scrollTop&&(e.scrollTop=0)}));e({state:n})},function(){b.current=void 0}})),a((function(){var e=s((function(){var e=_.value;_.value=O.value.core.environment.matchMedia(O.value.renderer.detachedMediaQuery).matches,e!==_.value?C({}):requestAnimationFrame(E)}),20);return O.value.core.environment.addEventListener("resize",e),function(){O.value.core.environment.removeEventListener("resize",e)}})),a((function(){if(!_.value)return function(){};function e(e){I.value.detachedContainer.classList.toggle("aa-DetachedContainer--modal",e)}function t(t){e(t.matches)}var n=O.value.core.environment.matchMedia(getComputedStyle(O.value.core.environment.document.documentElement).getPropertyValue("--aa-detached-modal-media-query"));e(n.matches);var r=Boolean(n.addEventListener);return r?n.addEventListener("change",t):n.addListener(t),function(){r?n.removeEventListener("change",t):n.removeListener(t)}})),a((function(){return requestAnimationFrame(E),function(){}})),n(n({},S),{},{update:C,destroy:function(){c()}})},e.getAlgoliaFacets=function(e){var t=gn({transformResponse:function(e){return e.facetHits}}),r=e.queries.map((function(e){return n(n({},e),{},{type:"facet"})}));return t(n(n({},e),{},{queries:r}))},e.getAlgoliaResults=yn,Object.defineProperty(e,"__esModule",{value:!0})})); + diff --git a/doc/site_libs/quarto-search/fuse.min.js b/doc/site_libs/quarto-search/fuse.min.js new file mode 100644 index 000000000..ca37378c1 --- /dev/null +++ b/doc/site_libs/quarto-search/fuse.min.js @@ -0,0 +1,9 @@ +/** + * Fuse.js v6.5.3 - Lightweight fuzzy-search (http://fusejs.io) + * + * Copyright (c) 2021 Kiro Risk (http://kiro.me) + * All Rights Reserved. Apache Software License 2.0 + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +var e,t;e=this,t=function(){"use strict";function e(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function t(t){for(var n=1;ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n0&&void 0!==arguments[0]?arguments[0]:1,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:3,n=new Map,r=Math.pow(10,t);return{get:function(t){var i=t.match(C).length;if(n.has(i))return n.get(i);var o=1/Math.pow(i,.5*e),c=parseFloat(Math.round(o*r)/r);return n.set(i,c),c},clear:function(){n.clear()}}}var $=function(){function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=t.getFn,i=void 0===n?I.getFn:n,o=t.fieldNormWeight,c=void 0===o?I.fieldNormWeight:o;r(this,e),this.norm=E(c,3),this.getFn=i,this.isCreated=!1,this.setIndexRecords()}return o(e,[{key:"setSources",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.docs=e}},{key:"setIndexRecords",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.records=e}},{key:"setKeys",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.keys=t,this._keysMap={},t.forEach((function(t,n){e._keysMap[t.id]=n}))}},{key:"create",value:function(){var e=this;!this.isCreated&&this.docs.length&&(this.isCreated=!0,g(this.docs[0])?this.docs.forEach((function(t,n){e._addString(t,n)})):this.docs.forEach((function(t,n){e._addObject(t,n)})),this.norm.clear())}},{key:"add",value:function(e){var t=this.size();g(e)?this._addString(e,t):this._addObject(e,t)}},{key:"removeAt",value:function(e){this.records.splice(e,1);for(var t=e,n=this.size();t2&&void 0!==arguments[2]?arguments[2]:{},r=n.getFn,i=void 0===r?I.getFn:r,o=n.fieldNormWeight,c=void 0===o?I.fieldNormWeight:o,a=new $({getFn:i,fieldNormWeight:c});return a.setKeys(e.map(_)),a.setSources(t),a.create(),a}function F(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=t.errors,r=void 0===n?0:n,i=t.currentLocation,o=void 0===i?0:i,c=t.expectedLocation,a=void 0===c?0:c,s=t.distance,u=void 0===s?I.distance:s,h=t.ignoreLocation,f=void 0===h?I.ignoreLocation:h,l=r/e.length;if(f)return l;var d=Math.abs(a-o);return u?l+d/u:d?1:l}function N(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:I.minMatchCharLength,n=[],r=-1,i=-1,o=0,c=e.length;o=t&&n.push([r,i]),r=-1)}return e[o-1]&&o-r>=t&&n.push([r,o-1]),n}var P=32;function W(e){for(var t={},n=0,r=e.length;n1&&void 0!==arguments[1]?arguments[1]:{},o=i.location,c=void 0===o?I.location:o,a=i.threshold,s=void 0===a?I.threshold:a,u=i.distance,h=void 0===u?I.distance:u,f=i.includeMatches,l=void 0===f?I.includeMatches:f,d=i.findAllMatches,v=void 0===d?I.findAllMatches:d,g=i.minMatchCharLength,y=void 0===g?I.minMatchCharLength:g,p=i.isCaseSensitive,m=void 0===p?I.isCaseSensitive:p,k=i.ignoreLocation,M=void 0===k?I.ignoreLocation:k;if(r(this,e),this.options={location:c,threshold:s,distance:h,includeMatches:l,findAllMatches:v,minMatchCharLength:y,isCaseSensitive:m,ignoreLocation:M},this.pattern=m?t:t.toLowerCase(),this.chunks=[],this.pattern.length){var b=function(e,t){n.chunks.push({pattern:e,alphabet:W(e),startIndex:t})},x=this.pattern.length;if(x>P){for(var w=0,L=x%P,S=x-L;w3&&void 0!==arguments[3]?arguments[3]:{},i=r.location,o=void 0===i?I.location:i,c=r.distance,a=void 0===c?I.distance:c,s=r.threshold,u=void 0===s?I.threshold:s,h=r.findAllMatches,f=void 0===h?I.findAllMatches:h,l=r.minMatchCharLength,d=void 0===l?I.minMatchCharLength:l,v=r.includeMatches,g=void 0===v?I.includeMatches:v,y=r.ignoreLocation,p=void 0===y?I.ignoreLocation:y;if(t.length>P)throw new Error(w(P));for(var m,k=t.length,M=e.length,b=Math.max(0,Math.min(o,M)),x=u,L=b,S=d>1||g,_=S?Array(M):[];(m=e.indexOf(t,L))>-1;){var O=F(t,{currentLocation:m,expectedLocation:b,distance:a,ignoreLocation:p});if(x=Math.min(O,x),L=m+k,S)for(var j=0;j=z;q-=1){var B=q-1,J=n[e.charAt(B)];if(S&&(_[B]=+!!J),K[q]=(K[q+1]<<1|1)&J,R&&(K[q]|=(A[q+1]|A[q])<<1|1|A[q+1]),K[q]&$&&(C=F(t,{errors:R,currentLocation:B,expectedLocation:b,distance:a,ignoreLocation:p}))<=x){if(x=C,(L=B)<=b)break;z=Math.max(1,2*b-L)}}if(F(t,{errors:R+1,currentLocation:b,expectedLocation:b,distance:a,ignoreLocation:p})>x)break;A=K}var U={isMatch:L>=0,score:Math.max(.001,C)};if(S){var V=N(_,d);V.length?g&&(U.indices=V):U.isMatch=!1}return U}(e,n,i,{location:c+o,distance:a,threshold:s,findAllMatches:u,minMatchCharLength:h,includeMatches:r,ignoreLocation:f}),p=y.isMatch,m=y.score,k=y.indices;p&&(g=!0),v+=m,p&&k&&(d=[].concat(l(d),l(k)))}));var y={isMatch:g,score:g?v/this.chunks.length:1};return g&&r&&(y.indices=d),y}}]),e}(),z=function(){function e(t){r(this,e),this.pattern=t}return o(e,[{key:"search",value:function(){}}],[{key:"isMultiMatch",value:function(e){return D(e,this.multiRegex)}},{key:"isSingleMatch",value:function(e){return D(e,this.singleRegex)}}]),e}();function D(e,t){var n=e.match(t);return n?n[1]:null}var K=function(e){a(n,e);var t=f(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=e===this.pattern;return{isMatch:t,score:t?0:1,indices:[0,this.pattern.length-1]}}}],[{key:"type",get:function(){return"exact"}},{key:"multiRegex",get:function(){return/^="(.*)"$/}},{key:"singleRegex",get:function(){return/^=(.*)$/}}]),n}(z),q=function(e){a(n,e);var t=f(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=-1===e.indexOf(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}}],[{key:"type",get:function(){return"inverse-exact"}},{key:"multiRegex",get:function(){return/^!"(.*)"$/}},{key:"singleRegex",get:function(){return/^!(.*)$/}}]),n}(z),B=function(e){a(n,e);var t=f(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=e.startsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,this.pattern.length-1]}}}],[{key:"type",get:function(){return"prefix-exact"}},{key:"multiRegex",get:function(){return/^\^"(.*)"$/}},{key:"singleRegex",get:function(){return/^\^(.*)$/}}]),n}(z),J=function(e){a(n,e);var t=f(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=!e.startsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}}],[{key:"type",get:function(){return"inverse-prefix-exact"}},{key:"multiRegex",get:function(){return/^!\^"(.*)"$/}},{key:"singleRegex",get:function(){return/^!\^(.*)$/}}]),n}(z),U=function(e){a(n,e);var t=f(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=e.endsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[e.length-this.pattern.length,e.length-1]}}}],[{key:"type",get:function(){return"suffix-exact"}},{key:"multiRegex",get:function(){return/^"(.*)"\$$/}},{key:"singleRegex",get:function(){return/^(.*)\$$/}}]),n}(z),V=function(e){a(n,e);var t=f(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=!e.endsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}}],[{key:"type",get:function(){return"inverse-suffix-exact"}},{key:"multiRegex",get:function(){return/^!"(.*)"\$$/}},{key:"singleRegex",get:function(){return/^!(.*)\$$/}}]),n}(z),G=function(e){a(n,e);var t=f(n);function n(e){var i,o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},c=o.location,a=void 0===c?I.location:c,s=o.threshold,u=void 0===s?I.threshold:s,h=o.distance,f=void 0===h?I.distance:h,l=o.includeMatches,d=void 0===l?I.includeMatches:l,v=o.findAllMatches,g=void 0===v?I.findAllMatches:v,y=o.minMatchCharLength,p=void 0===y?I.minMatchCharLength:y,m=o.isCaseSensitive,k=void 0===m?I.isCaseSensitive:m,M=o.ignoreLocation,b=void 0===M?I.ignoreLocation:M;return r(this,n),(i=t.call(this,e))._bitapSearch=new T(e,{location:a,threshold:u,distance:f,includeMatches:d,findAllMatches:g,minMatchCharLength:p,isCaseSensitive:k,ignoreLocation:b}),i}return o(n,[{key:"search",value:function(e){return this._bitapSearch.searchIn(e)}}],[{key:"type",get:function(){return"fuzzy"}},{key:"multiRegex",get:function(){return/^"(.*)"$/}},{key:"singleRegex",get:function(){return/^(.*)$/}}]),n}(z),H=function(e){a(n,e);var t=f(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){for(var t,n=0,r=[],i=this.pattern.length;(t=e.indexOf(this.pattern,n))>-1;)n=t+i,r.push([t,n-1]);var o=!!r.length;return{isMatch:o,score:o?0:1,indices:r}}}],[{key:"type",get:function(){return"include"}},{key:"multiRegex",get:function(){return/^'"(.*)"$/}},{key:"singleRegex",get:function(){return/^'(.*)$/}}]),n}(z),Q=[K,H,B,J,V,U,q,G],X=Q.length,Y=/ +(?=([^\"]*\"[^\"]*\")*[^\"]*$)/;function Z(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return e.split("|").map((function(e){for(var n=e.trim().split(Y).filter((function(e){return e&&!!e.trim()})),r=[],i=0,o=n.length;i1&&void 0!==arguments[1]?arguments[1]:{},i=n.isCaseSensitive,o=void 0===i?I.isCaseSensitive:i,c=n.includeMatches,a=void 0===c?I.includeMatches:c,s=n.minMatchCharLength,u=void 0===s?I.minMatchCharLength:s,h=n.ignoreLocation,f=void 0===h?I.ignoreLocation:h,l=n.findAllMatches,d=void 0===l?I.findAllMatches:l,v=n.location,g=void 0===v?I.location:v,y=n.threshold,p=void 0===y?I.threshold:y,m=n.distance,k=void 0===m?I.distance:m;r(this,e),this.query=null,this.options={isCaseSensitive:o,includeMatches:a,minMatchCharLength:u,findAllMatches:d,ignoreLocation:f,location:g,threshold:p,distance:k},this.pattern=o?t:t.toLowerCase(),this.query=Z(this.pattern,this.options)}return o(e,[{key:"searchIn",value:function(e){var t=this.query;if(!t)return{isMatch:!1,score:1};var n=this.options,r=n.includeMatches;e=n.isCaseSensitive?e:e.toLowerCase();for(var i=0,o=[],c=0,a=0,s=t.length;a-1&&(n.refIndex=e.idx),t.matches.push(n)}}))}function ve(e,t){t.score=e.score}function ge(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r=n.includeMatches,i=void 0===r?I.includeMatches:r,o=n.includeScore,c=void 0===o?I.includeScore:o,a=[];return i&&a.push(de),c&&a.push(ve),e.map((function(e){var n=e.idx,r={item:t[n],refIndex:n};return a.length&&a.forEach((function(t){t(e,r)})),r}))}var ye=function(){function e(n){var i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},o=arguments.length>2?arguments[2]:void 0;r(this,e),this.options=t(t({},I),i),this.options.useExtendedSearch,this._keyStore=new S(this.options.keys),this.setCollection(n,o)}return o(e,[{key:"setCollection",value:function(e,t){if(this._docs=e,t&&!(t instanceof $))throw new Error("Incorrect 'index' type");this._myIndex=t||R(this.options.keys,this._docs,{getFn:this.options.getFn,fieldNormWeight:this.options.fieldNormWeight})}},{key:"add",value:function(e){k(e)&&(this._docs.push(e),this._myIndex.add(e))}},{key:"remove",value:function(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:function(){return!1},t=[],n=0,r=this._docs.length;n1&&void 0!==arguments[1]?arguments[1]:{},n=t.limit,r=void 0===n?-1:n,i=this.options,o=i.includeMatches,c=i.includeScore,a=i.shouldSort,s=i.sortFn,u=i.ignoreFieldNorm,h=g(e)?g(this._docs[0])?this._searchStringList(e):this._searchObjectList(e):this._searchLogical(e);return le(h,{ignoreFieldNorm:u}),a&&h.sort(s),y(r)&&r>-1&&(h=h.slice(0,r)),ge(h,this._docs,{includeMatches:o,includeScore:c})}},{key:"_searchStringList",value:function(e){var t=re(e,this.options),n=this._myIndex.records,r=[];return n.forEach((function(e){var n=e.v,i=e.i,o=e.n;if(k(n)){var c=t.searchIn(n),a=c.isMatch,s=c.score,u=c.indices;a&&r.push({item:n,idx:i,matches:[{score:s,value:n,norm:o,indices:u}]})}})),r}},{key:"_searchLogical",value:function(e){var t=this,n=function(e,t){var n=(arguments.length>2&&void 0!==arguments[2]?arguments[2]:{}).auto,r=void 0===n||n,i=function e(n){var i=Object.keys(n),o=ue(n);if(!o&&i.length>1&&!se(n))return e(fe(n));if(he(n)){var c=o?n[ce]:i[0],a=o?n[ae]:n[c];if(!g(a))throw new Error(x(c));var s={keyId:j(c),pattern:a};return r&&(s.searcher=re(a,t)),s}var u={children:[],operator:i[0]};return i.forEach((function(t){var r=n[t];v(r)&&r.forEach((function(t){u.children.push(e(t))}))})),u};return se(e)||(e=fe(e)),i(e)}(e,this.options),r=function e(n,r,i){if(!n.children){var o=n.keyId,c=n.searcher,a=t._findMatches({key:t._keyStore.get(o),value:t._myIndex.getValueForItemAtKeyId(r,o),searcher:c});return a&&a.length?[{idx:i,item:r,matches:a}]:[]}for(var s=[],u=0,h=n.children.length;u1&&void 0!==arguments[1]?arguments[1]:{},n=t.getFn,r=void 0===n?I.getFn:n,i=t.fieldNormWeight,o=void 0===i?I.fieldNormWeight:i,c=e.keys,a=e.records,s=new $({getFn:r,fieldNormWeight:o});return s.setKeys(c),s.setIndexRecords(a),s},ye.config=I,function(){ne.push.apply(ne,arguments)}(te),ye},"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).Fuse=t(); \ No newline at end of file diff --git a/doc/site_libs/quarto-search/quarto-search.js b/doc/site_libs/quarto-search/quarto-search.js new file mode 100644 index 000000000..6fd4b5b90 --- /dev/null +++ b/doc/site_libs/quarto-search/quarto-search.js @@ -0,0 +1,1123 @@ +const kQueryArg = "q"; +const kResultsArg = "show-results"; + +// If items don't provide a URL, then both the navigator and the onSelect +// function aren't called (and therefore, the default implementation is used) +// +// We're using this sentinel URL to signal to those handlers that this +// item is a more item (along with the type) and can be handled appropriately +const kItemTypeMoreHref = "0767FDFD-0422-4E5A-BC8A-3BE11E5BBA05"; + +window.document.addEventListener("DOMContentLoaded", function (_event) { + // Ensure that search is available on this page. If it isn't, + // should return early and not do anything + var searchEl = window.document.getElementById("quarto-search"); + if (!searchEl) return; + + const { autocomplete } = window["@algolia/autocomplete-js"]; + + let quartoSearchOptions = {}; + let language = {}; + const searchOptionEl = window.document.getElementById( + "quarto-search-options" + ); + if (searchOptionEl) { + const jsonStr = searchOptionEl.textContent; + quartoSearchOptions = JSON.parse(jsonStr); + language = quartoSearchOptions.language; + } + + // note the search mode + if (quartoSearchOptions.type === "overlay") { + searchEl.classList.add("type-overlay"); + } else { + searchEl.classList.add("type-textbox"); + } + + // Used to determine highlighting behavior for this page + // A `q` query param is expected when the user follows a search + // to this page + const currentUrl = new URL(window.location); + const query = currentUrl.searchParams.get(kQueryArg); + const showSearchResults = currentUrl.searchParams.get(kResultsArg); + const mainEl = window.document.querySelector("main"); + + // highlight matches on the page + if (query !== null && mainEl) { + // perform any highlighting + highlight(query, mainEl); + + // fix up the URL to remove the q query param + const replacementUrl = new URL(window.location); + replacementUrl.searchParams.delete(kQueryArg); + window.history.replaceState({}, "", replacementUrl); + } + + // function to clear highlighting on the page when the search query changes + // (e.g. if the user edits the query or clears it) + let highlighting = true; + const resetHighlighting = (searchTerm) => { + if (mainEl && highlighting && query !== null && searchTerm !== query) { + clearHighlight(query, mainEl); + highlighting = false; + } + }; + + // Clear search highlighting when the user scrolls sufficiently + const resetFn = () => { + resetHighlighting(""); + window.removeEventListener("quarto-hrChanged", resetFn); + window.removeEventListener("quarto-sectionChanged", resetFn); + }; + + // Register this event after the initial scrolling and settling of events + // on the page + window.addEventListener("quarto-hrChanged", resetFn); + window.addEventListener("quarto-sectionChanged", resetFn); + + // Responsively switch to overlay mode if the search is present on the navbar + // Note that switching the sidebar to overlay mode requires more coordinate (not just + // the media query since we generate different HTML for sidebar overlays than we do + // for sidebar input UI) + const detachedMediaQuery = + quartoSearchOptions.type === "overlay" + ? "all" + : quartoSearchOptions.location === "navbar" + ? "(max-width: 991px)" + : "none"; + + // If configured, include the analytics client to send insights + const plugins = configurePlugins(quartoSearchOptions); + + let lastState = null; + const { setIsOpen } = autocomplete({ + container: searchEl, + detachedMediaQuery: detachedMediaQuery, + defaultActiveItemId: 0, + panelContainer: "#quarto-search-results", + panelPlacement: quartoSearchOptions["panel-placement"], + debug: false, + plugins, + classNames: { + form: "d-flex", + }, + translations: { + clearButtonTitle: language["search-clear-button-title"], + detachedCancelButtonText: language["search-detached-cancel-button-title"], + submitButtonTitle: language["search-submit-button-title"], + }, + initialState: { + query, + }, + getItemUrl({ item }) { + return item.href; + }, + onStateChange({ state }) { + // Perhaps reset highlighting + resetHighlighting(state.query); + + // If the panel just opened, ensure the panel is positioned properly + if (state.isOpen) { + if (lastState && !lastState.isOpen) { + setTimeout(() => { + positionPanel(quartoSearchOptions["panel-placement"]); + }, 150); + } + } + + // Perhaps show the copy link + showCopyLink(state.query, quartoSearchOptions); + + lastState = state; + }, + reshape({ sources, state }) { + return sources.map((source) => { + try { + const items = source.getItems(); + + // Validate the items + validateItems(items); + + // group the items by document + const groupedItems = new Map(); + items.forEach((item) => { + const hrefParts = item.href.split("#"); + const baseHref = hrefParts[0]; + const isDocumentItem = hrefParts.length === 1; + + const items = groupedItems.get(baseHref); + if (!items) { + groupedItems.set(baseHref, [item]); + } else { + // If the href for this item matches the document + // exactly, place this item first as it is the item that represents + // the document itself + if (isDocumentItem) { + items.unshift(item); + } else { + items.push(item); + } + groupedItems.set(baseHref, items); + } + }); + + const reshapedItems = []; + let count = 1; + for (const [_key, value] of groupedItems) { + const firstItem = value[0]; + reshapedItems.push({ + ...firstItem, + type: kItemTypeDoc, + }); + + const collapseMatches = quartoSearchOptions["collapse-after"]; + const collapseCount = + typeof collapseMatches === "number" ? collapseMatches : 1; + + if (value.length > 1) { + const target = `search-more-${count}`; + const isExpanded = + state.context.expanded && + state.context.expanded.includes(target); + + const remainingCount = value.length - collapseCount; + + for (let i = 1; i < value.length; i++) { + if (collapseMatches && i === collapseCount) { + reshapedItems.push({ + target, + title: isExpanded + ? language["search-hide-matches-text"] + : remainingCount === 1 + ? `${remainingCount} ${language["search-more-match-text"]}` + : `${remainingCount} ${language["search-more-matches-text"]}`, + type: kItemTypeMore, + href: kItemTypeMoreHref, + }); + } + + if (isExpanded || !collapseMatches || i < collapseCount) { + reshapedItems.push({ + ...value[i], + type: kItemTypeItem, + target, + }); + } + } + } + count += 1; + } + + return { + ...source, + getItems() { + return reshapedItems; + }, + }; + } catch (error) { + // Some form of error occurred + return { + ...source, + getItems() { + return [ + { + title: error.name || "An Error Occurred While Searching", + text: + error.message || + "An unknown error occurred while attempting to perform the requested search.", + type: kItemTypeError, + }, + ]; + }, + }; + } + }); + }, + navigator: { + navigate({ itemUrl }) { + if (itemUrl !== offsetURL(kItemTypeMoreHref)) { + window.location.assign(itemUrl); + } + }, + navigateNewTab({ itemUrl }) { + if (itemUrl !== offsetURL(kItemTypeMoreHref)) { + const windowReference = window.open(itemUrl, "_blank", "noopener"); + if (windowReference) { + windowReference.focus(); + } + } + }, + navigateNewWindow({ itemUrl }) { + if (itemUrl !== offsetURL(kItemTypeMoreHref)) { + window.open(itemUrl, "_blank", "noopener"); + } + }, + }, + getSources({ state, setContext, setActiveItemId, refresh }) { + return [ + { + sourceId: "documents", + getItemUrl({ item }) { + if (item.href) { + return offsetURL(item.href); + } else { + return undefined; + } + }, + onSelect({ + item, + state, + setContext, + setIsOpen, + setActiveItemId, + refresh, + }) { + if (item.type === kItemTypeMore) { + toggleExpanded(item, state, setContext, setActiveItemId, refresh); + + // Toggle more + setIsOpen(true); + } + }, + getItems({ query }) { + const limit = quartoSearchOptions.limit; + if (quartoSearchOptions.algolia) { + return algoliaSearch(query, limit, quartoSearchOptions.algolia); + } else { + // Fuse search options + const fuseSearchOptions = { + isCaseSensitive: false, + shouldSort: true, + minMatchCharLength: 2, + limit: limit, + }; + + return readSearchData().then(function (fuse) { + return fuseSearch(query, fuse, fuseSearchOptions); + }); + } + }, + templates: { + noResults({ createElement }) { + return createElement( + "div", + { class: "quarto-search-no-results" }, + language["search-no-results-text"] + ); + }, + header({ items, createElement }) { + // count the documents + const count = items.filter((item) => { + return item.type === kItemTypeDoc; + }).length; + + if (count > 0) { + return createElement( + "div", + { class: "search-result-header" }, + `${count} ${language["search-matching-documents-text"]}` + ); + } else { + return createElement( + "div", + { class: "search-result-header-no-results" }, + `` + ); + } + }, + footer({ _items, createElement }) { + if ( + quartoSearchOptions.algolia && + quartoSearchOptions.algolia["show-logo"] + ) { + const libDir = quartoSearchOptions.algolia["libDir"]; + const logo = createElement("img", { + src: offsetURL( + `${libDir}/quarto-search/search-by-algolia.svg` + ), + class: "algolia-search-logo", + }); + return createElement( + "a", + { href: "http://www.algolia.com/" }, + logo + ); + } + }, + + item({ item, createElement }) { + return renderItem( + item, + createElement, + state, + setActiveItemId, + setContext, + refresh + ); + }, + }, + }, + ]; + }, + }); + + // Remove the labeleledby attribute since it is pointing + // to a non-existent label + if (quartoSearchOptions.type === "overlay") { + const inputEl = window.document.querySelector( + "#quarto-search .aa-Autocomplete" + ); + if (inputEl) { + inputEl.removeAttribute("aria-labelledby"); + } + } + + // If the main document scrolls dismiss the search results + // (otherwise, since they're floating in the document they can scroll with the document) + window.document.body.onscroll = () => { + setIsOpen(false); + }; + + if (showSearchResults) { + setIsOpen(true); + focusSearchInput(); + } +}); + +function configurePlugins(quartoSearchOptions) { + const autocompletePlugins = []; + const algoliaOptions = quartoSearchOptions.algolia; + if ( + algoliaOptions && + algoliaOptions["analytics-events"] && + algoliaOptions["search-only-api-key"] && + algoliaOptions["application-id"] + ) { + const apiKey = algoliaOptions["search-only-api-key"]; + const appId = algoliaOptions["application-id"]; + + // Aloglia insights may not be loaded because they require cookie consent + // Use deferred loading so events will start being recorded when/if consent + // is granted. + const algoliaInsightsDeferredPlugin = deferredLoadPlugin(() => { + if ( + window.aa && + window["@algolia/autocomplete-plugin-algolia-insights"] + ) { + window.aa("init", { + appId, + apiKey, + useCookie: true, + }); + + const { createAlgoliaInsightsPlugin } = + window["@algolia/autocomplete-plugin-algolia-insights"]; + // Register the insights client + const algoliaInsightsPlugin = createAlgoliaInsightsPlugin({ + insightsClient: window.aa, + onItemsChange({ insights, insightsEvents }) { + const events = insightsEvents.map((event) => { + const maxEvents = event.objectIDs.slice(0, 20); + return { + ...event, + objectIDs: maxEvents, + }; + }); + + insights.viewedObjectIDs(...events); + }, + }); + return algoliaInsightsPlugin; + } + }); + + // Add the plugin + autocompletePlugins.push(algoliaInsightsDeferredPlugin); + return autocompletePlugins; + } +} + +// For plugins that may not load immediately, create a wrapper +// plugin and forward events and plugin data once the plugin +// is initialized. This is useful for cases like cookie consent +// which may prevent the analytics insights event plugin from initializing +// immediately. +function deferredLoadPlugin(createPlugin) { + let plugin = undefined; + let subscribeObj = undefined; + const wrappedPlugin = () => { + if (!plugin && subscribeObj) { + plugin = createPlugin(); + if (plugin && plugin.subscribe) { + plugin.subscribe(subscribeObj); + } + } + return plugin; + }; + + return { + subscribe: (obj) => { + subscribeObj = obj; + }, + onStateChange: (obj) => { + const plugin = wrappedPlugin(); + if (plugin && plugin.onStateChange) { + plugin.onStateChange(obj); + } + }, + onSubmit: (obj) => { + const plugin = wrappedPlugin(); + if (plugin && plugin.onSubmit) { + plugin.onSubmit(obj); + } + }, + onReset: (obj) => { + const plugin = wrappedPlugin(); + if (plugin && plugin.onReset) { + plugin.onReset(obj); + } + }, + getSources: (obj) => { + const plugin = wrappedPlugin(); + if (plugin && plugin.getSources) { + return plugin.getSources(obj); + } else { + return Promise.resolve([]); + } + }, + data: (obj) => { + const plugin = wrappedPlugin(); + if (plugin && plugin.data) { + plugin.data(obj); + } + }, + }; +} + +function validateItems(items) { + // Validate the first item + if (items.length > 0) { + const item = items[0]; + const missingFields = []; + if (item.href == undefined) { + missingFields.push("href"); + } + if (!item.title == undefined) { + missingFields.push("title"); + } + if (!item.text == undefined) { + missingFields.push("text"); + } + + if (missingFields.length === 1) { + throw { + name: `Error: Search index is missing the ${missingFields[0]} field.`, + message: `The items being returned for this search do not include all the required fields. Please ensure that your index items include the ${missingFields[0]} field or use index-fields in your _quarto.yml file to specify the field names.`, + }; + } else if (missingFields.length > 1) { + const missingFieldList = missingFields + .map((field) => { + return `${field}`; + }) + .join(", "); + + throw { + name: `Error: Search index is missing the following fields: ${missingFieldList}.`, + message: `The items being returned for this search do not include all the required fields. Please ensure that your index items includes the following fields: ${missingFieldList}, or use index-fields in your _quarto.yml file to specify the field names.`, + }; + } + } +} + +let lastQuery = null; +function showCopyLink(query, options) { + const language = options.language; + lastQuery = query; + // Insert share icon + const inputSuffixEl = window.document.body.querySelector( + ".aa-Form .aa-InputWrapperSuffix" + ); + + if (inputSuffixEl) { + let copyButtonEl = window.document.body.querySelector( + ".aa-Form .aa-InputWrapperSuffix .aa-CopyButton" + ); + + if (copyButtonEl === null) { + copyButtonEl = window.document.createElement("button"); + copyButtonEl.setAttribute("class", "aa-CopyButton"); + copyButtonEl.setAttribute("type", "button"); + copyButtonEl.setAttribute("title", language["search-copy-link-title"]); + copyButtonEl.onmousedown = (e) => { + e.preventDefault(); + e.stopPropagation(); + }; + + const linkIcon = "bi-clipboard"; + const checkIcon = "bi-check2"; + + const shareIconEl = window.document.createElement("i"); + shareIconEl.setAttribute("class", `bi ${linkIcon}`); + copyButtonEl.appendChild(shareIconEl); + inputSuffixEl.prepend(copyButtonEl); + + const clipboard = new window.ClipboardJS(".aa-CopyButton", { + text: function (_trigger) { + const copyUrl = new URL(window.location); + copyUrl.searchParams.set(kQueryArg, lastQuery); + copyUrl.searchParams.set(kResultsArg, "1"); + return copyUrl.toString(); + }, + }); + clipboard.on("success", function (e) { + // Focus the input + + // button target + const button = e.trigger; + const icon = button.querySelector("i.bi"); + + // flash "checked" + icon.classList.add(checkIcon); + icon.classList.remove(linkIcon); + setTimeout(function () { + icon.classList.remove(checkIcon); + icon.classList.add(linkIcon); + }, 1000); + }); + } + + // If there is a query, show the link icon + if (copyButtonEl) { + if (lastQuery && options["copy-button"]) { + copyButtonEl.style.display = "flex"; + } else { + copyButtonEl.style.display = "none"; + } + } + } +} + +/* Search Index Handling */ +// create the index +var fuseIndex = undefined; +async function readSearchData() { + // Initialize the search index on demand + if (fuseIndex === undefined) { + // create fuse index + const options = { + keys: [ + { name: "title", weight: 20 }, + { name: "section", weight: 20 }, + { name: "text", weight: 10 }, + ], + ignoreLocation: true, + threshold: 0.1, + }; + const fuse = new window.Fuse([], options); + + // fetch the main search.json + const response = await fetch(offsetURL("search.json")); + if (response.status == 200) { + return response.json().then(function (searchDocs) { + searchDocs.forEach(function (searchDoc) { + fuse.add(searchDoc); + }); + fuseIndex = fuse; + return fuseIndex; + }); + } else { + return Promise.reject( + new Error( + "Unexpected status from search index request: " + response.status + ) + ); + } + } + return fuseIndex; +} + +function inputElement() { + return window.document.body.querySelector(".aa-Form .aa-Input"); +} + +function focusSearchInput() { + setTimeout(() => { + const inputEl = inputElement(); + if (inputEl) { + inputEl.focus(); + } + }, 50); +} + +/* Panels */ +const kItemTypeDoc = "document"; +const kItemTypeMore = "document-more"; +const kItemTypeItem = "document-item"; +const kItemTypeError = "error"; + +function renderItem( + item, + createElement, + state, + setActiveItemId, + setContext, + refresh +) { + switch (item.type) { + case kItemTypeDoc: + return createDocumentCard( + createElement, + "file-richtext", + item.title, + item.section, + item.text, + item.href + ); + case kItemTypeMore: + return createMoreCard( + createElement, + item, + state, + setActiveItemId, + setContext, + refresh + ); + case kItemTypeItem: + return createSectionCard( + createElement, + item.section, + item.text, + item.href + ); + case kItemTypeError: + return createErrorCard(createElement, item.title, item.text); + default: + return undefined; + } +} + +function createDocumentCard(createElement, icon, title, section, text, href) { + const iconEl = createElement("i", { + class: `bi bi-${icon} search-result-icon`, + }); + const titleEl = createElement("p", { class: "search-result-title" }, title); + const titleContainerEl = createElement( + "div", + { class: "search-result-title-container" }, + [iconEl, titleEl] + ); + + const textEls = []; + if (section) { + const sectionEl = createElement( + "p", + { class: "search-result-section" }, + section + ); + textEls.push(sectionEl); + } + const descEl = createElement("p", { + class: "search-result-text", + dangerouslySetInnerHTML: { + __html: text, + }, + }); + textEls.push(descEl); + + const textContainerEl = createElement( + "div", + { class: "search-result-text-container" }, + textEls + ); + + const containerEl = createElement( + "div", + { + class: "search-result-container", + }, + [titleContainerEl, textContainerEl] + ); + + const linkEl = createElement( + "a", + { + href: offsetURL(href), + class: "search-result-link", + }, + containerEl + ); + + const classes = ["search-result-doc", "search-item"]; + if (!section) { + classes.push("document-selectable"); + } + + return createElement( + "div", + { + class: classes.join(" "), + }, + linkEl + ); +} + +function createMoreCard( + createElement, + item, + state, + setActiveItemId, + setContext, + refresh +) { + const moreCardEl = createElement( + "div", + { + class: "search-result-more search-item", + onClick: (e) => { + // Handle expanding the sections by adding the expanded + // section to the list of expanded sections + toggleExpanded(item, state, setContext, setActiveItemId, refresh); + e.stopPropagation(); + }, + }, + item.title + ); + + return moreCardEl; +} + +function toggleExpanded(item, state, setContext, setActiveItemId, refresh) { + const expanded = state.context.expanded || []; + if (expanded.includes(item.target)) { + setContext({ + expanded: expanded.filter((target) => target !== item.target), + }); + } else { + setContext({ expanded: [...expanded, item.target] }); + } + + refresh(); + setActiveItemId(item.__autocomplete_id); +} + +function createSectionCard(createElement, section, text, href) { + const sectionEl = createSection(createElement, section, text, href); + return createElement( + "div", + { + class: "search-result-doc-section search-item", + }, + sectionEl + ); +} + +function createSection(createElement, title, text, href) { + const descEl = createElement("p", { + class: "search-result-text", + dangerouslySetInnerHTML: { + __html: text, + }, + }); + + const titleEl = createElement("p", { class: "search-result-section" }, title); + const linkEl = createElement( + "a", + { + href: offsetURL(href), + class: "search-result-link", + }, + [titleEl, descEl] + ); + return linkEl; +} + +function createErrorCard(createElement, title, text) { + const descEl = createElement("p", { + class: "search-error-text", + dangerouslySetInnerHTML: { + __html: text, + }, + }); + + const titleEl = createElement("p", { + class: "search-error-title", + dangerouslySetInnerHTML: { + __html: ` ${title}`, + }, + }); + const errorEl = createElement("div", { class: "search-error" }, [ + titleEl, + descEl, + ]); + return errorEl; +} + +function positionPanel(pos) { + const panelEl = window.document.querySelector( + "#quarto-search-results .aa-Panel" + ); + const inputEl = window.document.querySelector( + "#quarto-search .aa-Autocomplete" + ); + + if (panelEl && inputEl) { + panelEl.style.top = `${Math.round(panelEl.offsetTop)}px`; + if (pos === "start") { + panelEl.style.left = `${Math.round(inputEl.left)}px`; + } else { + panelEl.style.right = `${Math.round(inputEl.offsetRight)}px`; + } + } +} + +/* Highlighting */ +// highlighting functions +function highlightMatch(query, text) { + if (text) { + const start = text.toLowerCase().indexOf(query.toLowerCase()); + if (start !== -1) { + const startMark = ""; + const endMark = ""; + + const end = start + query.length; + text = + text.slice(0, start) + + startMark + + text.slice(start, end) + + endMark + + text.slice(end); + const startInfo = clipStart(text, start); + const endInfo = clipEnd( + text, + startInfo.position + startMark.length + endMark.length + ); + text = + startInfo.prefix + + text.slice(startInfo.position, endInfo.position) + + endInfo.suffix; + + return text; + } else { + return text; + } + } else { + return text; + } +} + +function clipStart(text, pos) { + const clipStart = pos - 50; + if (clipStart < 0) { + // This will just return the start of the string + return { + position: 0, + prefix: "", + }; + } else { + // We're clipping before the start of the string, walk backwards to the first space. + const spacePos = findSpace(text, pos, -1); + return { + position: spacePos.position, + prefix: "", + }; + } +} + +function clipEnd(text, pos) { + const clipEnd = pos + 200; + if (clipEnd > text.length) { + return { + position: text.length, + suffix: "", + }; + } else { + const spacePos = findSpace(text, clipEnd, 1); + return { + position: spacePos.position, + suffix: spacePos.clipped ? "…" : "", + }; + } +} + +function findSpace(text, start, step) { + let stepPos = start; + while (stepPos > -1 && stepPos < text.length) { + const char = text[stepPos]; + if (char === " " || char === "," || char === ":") { + return { + position: step === 1 ? stepPos : stepPos - step, + clipped: stepPos > 1 && stepPos < text.length, + }; + } + stepPos = stepPos + step; + } + + return { + position: stepPos - step, + clipped: false, + }; +} + +// removes highlighting as implemented by the mark tag +function clearHighlight(searchterm, el) { + const childNodes = el.childNodes; + for (let i = childNodes.length - 1; i >= 0; i--) { + const node = childNodes[i]; + if (node.nodeType === Node.ELEMENT_NODE) { + if ( + node.tagName === "MARK" && + node.innerText.toLowerCase() === searchterm.toLowerCase() + ) { + el.replaceChild(document.createTextNode(node.innerText), node); + } else { + clearHighlight(searchterm, node); + } + } + } +} + +// highlight matches +function highlight(term, el) { + const termRegex = new RegExp(term, "ig"); + const childNodes = el.childNodes; + + // walk back to front avoid mutating elements in front of us + for (let i = childNodes.length - 1; i >= 0; i--) { + const node = childNodes[i]; + + if (node.nodeType === Node.TEXT_NODE) { + // Search text nodes for text to highlight + const text = node.nodeValue; + + let startIndex = 0; + let matchIndex = text.search(termRegex); + if (matchIndex > -1) { + const markFragment = document.createDocumentFragment(); + while (matchIndex > -1) { + const prefix = text.slice(startIndex, matchIndex); + markFragment.appendChild(document.createTextNode(prefix)); + + const mark = document.createElement("mark"); + mark.appendChild( + document.createTextNode( + text.slice(matchIndex, matchIndex + term.length) + ) + ); + markFragment.appendChild(mark); + + startIndex = matchIndex + term.length; + matchIndex = text.slice(startIndex).search(new RegExp(term, "ig")); + if (matchIndex > -1) { + matchIndex = startIndex + matchIndex; + } + } + if (startIndex < text.length) { + markFragment.appendChild( + document.createTextNode(text.slice(startIndex, text.length)) + ); + } + + el.replaceChild(markFragment, node); + } + } else if (node.nodeType === Node.ELEMENT_NODE) { + // recurse through elements + highlight(term, node); + } + } +} + +/* Link Handling */ +// get the offset from this page for a given site root relative url +function offsetURL(url) { + var offset = getMeta("quarto:offset"); + return offset ? offset + url : url; +} + +// read a meta tag value +function getMeta(metaName) { + var metas = window.document.getElementsByTagName("meta"); + for (let i = 0; i < metas.length; i++) { + if (metas[i].getAttribute("name") === metaName) { + return metas[i].getAttribute("content"); + } + } + return ""; +} + +function algoliaSearch(query, limit, algoliaOptions) { + const { getAlgoliaResults } = window["@algolia/autocomplete-preset-algolia"]; + + const applicationId = algoliaOptions["application-id"]; + const searchOnlyApiKey = algoliaOptions["search-only-api-key"]; + const indexName = algoliaOptions["index-name"]; + const indexFields = algoliaOptions["index-fields"]; + const searchClient = window.algoliasearch(applicationId, searchOnlyApiKey); + const searchParams = algoliaOptions["params"]; + const searchAnalytics = !!algoliaOptions["analytics-events"]; + + return getAlgoliaResults({ + searchClient, + queries: [ + { + indexName: indexName, + query, + params: { + hitsPerPage: limit, + clickAnalytics: searchAnalytics, + ...searchParams, + }, + }, + ], + transformResponse: (response) => { + if (!indexFields) { + return response.hits.map((hit) => { + return hit.map((item) => { + return { + ...item, + text: highlightMatch(query, item.text), + }; + }); + }); + } else { + const remappedHits = response.hits.map((hit) => { + return hit.map((item) => { + const newItem = { ...item }; + ["href", "section", "title", "text"].forEach((keyName) => { + const mappedName = indexFields[keyName]; + if ( + mappedName && + item[mappedName] !== undefined && + mappedName !== keyName + ) { + newItem[keyName] = item[mappedName]; + delete newItem[mappedName]; + } + }); + newItem.text = highlightMatch(query, newItem.text); + return newItem; + }); + }); + return remappedHits; + } + }, + }); +} + +function fuseSearch(query, fuse, fuseOptions) { + return fuse.search(query, fuseOptions).map((result) => { + const addParam = (url, name, value) => { + const anchorParts = url.split("#"); + const baseUrl = anchorParts[0]; + const sep = baseUrl.search("\\?") > 0 ? "&" : "?"; + anchorParts[0] = baseUrl + sep + name + "=" + value; + return anchorParts.join("#"); + }; + + return { + title: result.item.title, + section: result.item.section, + href: addParam(result.item.href, kQueryArg, query), + text: highlightMatch(query, result.item.text), + }; + }); +} diff --git a/doc/styles.css b/doc/styles.css new file mode 100644 index 000000000..d3b6d8da0 --- /dev/null +++ b/doc/styles.css @@ -0,0 +1,36 @@ +.chapter-number::before { + content: "Chapter "; +} +.chapter-number::after { + content: ":"; +} +.header-section-number{ + color: black +} + +.table{ + background:cornsilk; + border-width:2px; + border-style:solid; + font-size:14px; +} + +.figure-img { + margin-top: 1.5rem; + margin-bottom: -.5rem; + line-height: 1; +} + +.listing p{ + font-weight:bold; + font-size:smaller; + color:black; + font-family: Arial, Helvetica, sans-serif; +} +.figure-caption { + font-weight:bold; + font-style:italic; + font-size:smaller; + color:black; + font-family: Arial, Helvetica, sans-serif; +} \ No newline at end of file diff --git a/gcm/pom.xml b/gcm/pom.xml new file mode 100644 index 000000000..77dfac7c2 --- /dev/null +++ b/gcm/pom.xml @@ -0,0 +1,232 @@ + + + 4.0.0 + + + gov.hhs.aspr.ms + gcm + 4.0.0 + jar + + General Computational Model + GCM is a Java based simulation framework for building disease progression models. + https://github.com/HHS/ASPR-8 + + + + + GNU GENERAL PUBLIC LICENSE v3 + https://www.gnu.org/licenses/gpl-3.0.en.html + + + + + + + Shawn Hatch + shawn.r.hatch@leidos.com + Leidos + https://www.leidos.com + + + Zachary Bischoff + Leidos + zachary.bischoff@leidos.com + https://www.leidos.com + + + + + + scm:git:git://github.com/HHS/ASPR-8.git + scm:git:ssh://github.com:HHS/ASPR-8.git + https://github.com/HHS/ASPR-8/tree/main + + + + + + UTF-8 + 17 + 17 + + + 3.0.0-M5 + 3.0.0-M5 + 3.2.1 + 3.5.0 + + + 4.0.0 + 3.11.2 + 0.8.9 + 5.8.2 + + + + + + gov.hhs.aspr.ms + util + ${util.version} + + + org.mockito + mockito-core + ${mockito-core.version} + test + + + org.jacoco + jacoco-maven-plugin + ${jacoco-maven-plugin.version} + test + + + org.junit.jupiter + junit-jupiter-api + ${junit-jupiter-engine.version} + test + + + org.junit.jupiter + junit-jupiter-engine + ${junit-jupiter-engine.version} + test + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + + AT_*.java + + + + + + + org.jacoco + jacoco-maven-plugin + ${jacoco-maven-plugin.version} + + + default-prepare-agent + + prepare-agent + + + + default-report + + report + + + + default-check + + check + + + + + BUNDLE + + + LINE + COVEREDRATIO + 0.0 + + + BRANCH + COVEREDRATIO + 0.0 + + + INSTRUCTION + COVEREDRATIO + 0.0 + + + + + + + + + + + + org.apache.maven.plugins + maven-source-plugin + ${maven-source-plugin.version} + + + attach-sources + verify + + jar-no-fork + + + + + + + + + + release + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + all,-missing + + + gov.hhs.aspr.ms + util + ${util.version} + + + org.junit.jupiter + junit-jupiter-api + ${junit-jupiter-engine.version} + + + false + + -Xmaxerrs + 65536 + -Xmaxwarns + 65536 + + + + + attach-javadocs + verify + + jar + + + + + + + + + + \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/ActorContext.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/ActorContext.java new file mode 100644 index 000000000..247d01207 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/ActorContext.java @@ -0,0 +1,243 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +import util.errors.ContractException; + +/** + * An actor context provides access to the nucleus engine and published data + * managers to actors. It is supplied by the engine each time it interacts with + * an actor. Actors are defined by this context. If this context is passed to a + * method invocation, then that method is an actor method. + */ +public final class ActorContext { + + private final Simulation simulation; + + protected ActorContext(Simulation simulation) { + this.simulation = simulation; + } + + /** + * Registers the given consumer to be executed at the end of the simulation. + * Activity associated with the consumer should be limited to querying data + * state and releasing output. + * + * @throws ContractException {@link NucleusError#NULL_ACTOR_CONTEXT_CONSUMER} if + * the consumer is null + */ + public void subscribeToSimulationClose(Consumer consumer) { + simulation.subscribeActorToSimulationClose(consumer); + } + + /** + * Schedules a plan that will be executed at the given time. + * + * @throws ContractException + *
      + *
    • {@link NucleusError#NULL_PLAN} if the plan is + * null
    • + *
    • {@link NucleusError#PAST_PLANNING_TIME} if the + * plan is scheduled for a time in the past
    • + *
    • {@link NucleusError#PLANNING_QUEUE_CLOSED} if + * the plan is added to the simulation after event + * processing is finished
    • + *
    + */ + public void addPlan(final Consumer consumer, final double planTime) { + Plan plan = Plan.builder(ActorContext.class)// + .setActive(true)// + .setCallbackConsumer(consumer)// + .setKey(null)// + .setPlanData(null)// + .setTime(planTime)// + .build();// + simulation.addActorPlan(plan); + } + + /** + * Schedules a plan. + * + * @throws ContractException + *
      + *
    • {@link NucleusError#NULL_PLAN} if the plan is + * null
    • + *
    • {@link NucleusError#PAST_PLANNING_TIME} if the + * plan is scheduled for a time in the past
    • + *
    • {@link NucleusError#PLANNING_QUEUE_CLOSED} if + * the plan is added to the simulation after event + * processing is finished
    • + *
    + */ + public void addPlan(final Plan plan) { + if (plan == null) { + throw new ContractException(NucleusError.NULL_PLAN); + } + simulation.addActorPlan(plan); + } + + /** + * Retrieves a plan stored for the given key. + * + * @throws ContractException {@link NucleusError#NULL_PLAN_KEY} if the plan key + * is null + */ + public Optional> getPlan(final Object key) { + return simulation.getActorPlan(key); + } + + /** + * Removes and returns the plan associated with the given key. + * + * @throws ContractException {@link NucleusError#NULL_PLAN_KEY} if the plan key + * is null + */ + public Optional> removePlan(final Object key) { + return simulation.removeActorPlan(key); + } + + /** + * Returns a list of the current plan keys associated with the current actor + */ + public List getPlanKeys() { + return simulation.getActorPlanKeys(); + } + + /** + * Returns the ActorId of the current actor + */ + public ActorId getActorId() { + return simulation.focalActorId; + } + + /** + * Subscribes the current actor to the given event filter. Events of the type T + * are processed by the event filter. If the event passes the filter the event + * will be consumed by the supplied event consumer. + * + * @throws ContractException + *
      + *
    • {@link NucleusError#NULL_EVENT_FILTER} if the + * event filter is null
    • + *
    • {@link NucleusError#NULL_EVENT_CONSUMER} if the + * event consumer is null
    • + *
    + */ + public void subscribe(EventFilter eventFilter, BiConsumer eventConsumer) { + simulation.subscribeActorToEventByFilter(eventFilter, eventConsumer); + } + + /** + * Unsubscribes the current actor from the given event filter. + * + * @throws ContractException {@link NucleusError#NULL_EVENT_FILTER} if the event + * filter is null + */ + public void unsubscribe(EventFilter eventFilter) { + simulation.unsubscribeActorFromEventByFilter(eventFilter); + } + + public boolean actorExists(final ActorId actorId) { + return simulation.actorExists(actorId); + } + + public T getDataManager(Class dataManagerClass) { + return simulation.getDataManagerForActor(dataManagerClass); + } + + public double getTime() { + return simulation.time; + } + + public void halt() { + simulation.halt(); + } + + public void releaseOutput(Object output) { + simulation.releaseOutput(output); + } + + /** + * Adds an actor to the simulation. The actor is added immediately, but the + * consumer of ActorContext is invoked after event resolution is finished and + * before time progresses. + * + * @throws ContractException {@link NucleusError#NULL_ACTOR_CONTEXT_CONSUMER} if + * the actor context consumer is null + */ + public ActorId addActor(Consumer consumer) { + return simulation.addActor(consumer); + } + + /** + * Returns true if and only if the actors should output their state as a plugin + * data instances at the end of the simulation. + */ + public boolean stateRecordingIsScheduled() { + return simulation.stateRecordingIsScheduled(); + } + + /** + * Returns the scheduled simulation halt time. Negative values indicate there is + * no scheduled halt time. + */ + public double getScheduledSimulationHaltTime() { + return simulation.getScheduledSimulationHaltTime(); + } + + /** + * Returns true if and only if there a state recording is scheduled and the + * given time exceeds the recording time. + */ + protected boolean plansRequirePlanData(double time) { + return simulation.plansRequirePlanData(time); + } + + /** + * Removes the given actor from the simulation. + * + * @throws ContractException + *
      + *
    • {@link NucleusError#NULL_ACTOR_ID} if the + * actorId is null
    • + *
    • {@link NucleusError#UNKNOWN_ACTOR_ID} if the + * actor id is negative
    • + *
    • {@link NucleusError#UNKNOWN_ACTOR_ID} if the + * actor id does not correspond to a known actor
    • + *
    + */ + public void removeActor(ActorId actorId) { + simulation.removeActor(actorId); + } + + /** + * Sets a function for converting plan data instances into consumers of actor + * context that will be used to convert stored plans from a previous simulation + * execution into current plans. Only used during the initialization of the + * simulation before time flows. + */ + public void setPlanDataConverter(Class planDataClass, + Function> conversionFunction) { + simulation.setActorPlanDataConverter(planDataClass, conversionFunction); + } + + /** + * Returns the time (floating point days) of simulation start. + */ + public double getStartTime() { + return simulation.getStartTime(); + } + + /** + * Returns the base date that synchronizes with simulation time zero. + */ + public LocalDate getBaseDate() { + return simulation.getBaseDate(); + } + +} diff --git a/gcm3/src/main/java/nucleus/ActorId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/ActorId.java similarity index 94% rename from gcm3/src/main/java/nucleus/ActorId.java rename to gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/ActorId.java index b994b9d45..49ce89d1f 100644 --- a/gcm3/src/main/java/nucleus/ActorId.java +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/ActorId.java @@ -1,13 +1,9 @@ -package nucleus; +package gov.hhs.aspr.ms.gcm.nucleus; /** * The unique identifier for actors. Actors are constructed dynamically and ids * are distributed contiguously from 0. - * - * @author Shawn Hatch - * */ - public final class ActorId { private final int id; @@ -27,8 +23,7 @@ public ActorId(int id) { } /** - * Returns string of the form "ActorId[X]" where the value of the actor id - * is X + * Returns string of the form "ActorId[X]" where the value of the actor id is X */ @Override public String toString() { diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/DataManager.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/DataManager.java new file mode 100644 index 000000000..349520ed8 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/DataManager.java @@ -0,0 +1,45 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import util.errors.ContractException; + +/** + * Base class for all data managers. + */ +public class DataManager { + /** + * Package access used by the simulation to help ensure that init() is invoked + * exactly once. + */ + boolean isInitialized() { + return initialized; + } + + private boolean initialized; + + /** + * Initializes the data manager. This method should only be invoked by the + * simulation. All data manager descendant classes that override this method + * must invoke the super.
    + * + * @param dataManagerContext + * @throws ContractException {@linkplain NucleusError#DATA_MANAGER_DUPLICATE_INITIALIZATION} + * if init() is invoked more than once + */ + public void init(DataManagerContext dataManagerContext) { + if (initialized) { + throw new ContractException(NucleusError.DATA_MANAGER_DUPLICATE_INITIALIZATION); + } + initialized = true; + } + + /** + * Returns the string representation of a data manager's internal DATA state + * that can be accessed via public methods. Note that this is not to include + * external references and is used primarily to test run continuity. + */ + @Override + public String toString() { + return "DataManager[]"; + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/DataManagerContext.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/DataManagerContext.java new file mode 100644 index 000000000..728460028 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/DataManagerContext.java @@ -0,0 +1,282 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +import util.errors.ContractException; + +/** + * A data manager context provides access to the nucleus engine and other data + * managers for data managers. It is supplied by the engine each time it + * interacts with a data manager. + */ +public final class DataManagerContext { + + private final Simulation simulation; + protected final DataManagerId dataManagerId; + + protected DataManagerContext(DataManagerId dataManagerId, Simulation simulation) { + this.dataManagerId = dataManagerId; + this.simulation = simulation; + } + + /** + * Schedules a plan that will be executed at the given time. + * + * @throws ContractException + *
      + *
    • {@link NucleusError#NULL_PLAN} if the plan is + * null
    • + *
    • {@link NucleusError#PAST_PLANNING_TIME} if the + * plan is scheduled for a time in the past *
    • + *
    • {@link NucleusError#PLANNING_QUEUE_CLOSED} if + * the plan is added to the simulation after event + * processing is finished
    • + *
    + */ + public void addPlan(final Consumer consumer, final double planTime) { + + Plan plan = Plan.builder(DataManagerContext.class)// + .setActive(true)// + .setCallbackConsumer(consumer)// + .setKey(null)// + .setPlanData(null)// + .setTime(planTime)// + .build();// + + simulation.addDataManagerPlan(dataManagerId, plan); + } + + /** + * Schedules a plan. + * + * @throws ContractException + *
      + *
    • {@link NucleusError#NULL_PLAN} if the plan is + * null
    • + *
    • {@link NucleusError#PAST_PLANNING_TIME} if the + * plan is scheduled for a time in the past *
    • + *
    • {@link NucleusError#PLANNING_QUEUE_CLOSED} if + * the plan is added to the simulation after event + * processing is finished
    • + *
    + */ + public void addPlan(Plan plan) { + if (plan == null) { + throw new ContractException(NucleusError.NULL_PLAN); + } + simulation.addDataManagerPlan(dataManagerId, plan); + } + + /** + * Registers the given consumer to be executed at the end of the simulation. + * Activity associated with the consumer should be limited to querying data + * state and releasing output. + * + * @throws ContractException {@link NucleusError#NULL_DATA_MANAGER_CONTEXT_CONSUMER} + * if the consumer is null + */ + public void subscribeToSimulationClose(Consumer consumer) { + simulation.subscribeDataManagerToSimulationClose(dataManagerId, consumer); + } + + /** + * Returns true if and only if the data managers should output their state as a + * plugin data instances at the end of the simulation. + */ + public boolean stateRecordingIsScheduled() { + return simulation.stateRecordingIsScheduled(); + } + + /** + * Returns the scheduled simulation halt time. Negative values indicate there is + * no scheduled halt time. + */ + public double getScheduledSimulationHaltTime() { + return simulation.getScheduledSimulationHaltTime(); + } + + /** + * Returns true if and only if there a state recording is scheduled and the + * given time exceeds the recording time. + */ + protected boolean plansRequirePlanData(double time) { + return simulation.plansRequirePlanData(time); + } + + /** + * Retrieves a plan for the given key. + * + * @throws ContractException {@link NucleusError#NULL_PLAN_KEY} if the plan key + * is null + */ + public Optional> getPlan(final Object key) { + return simulation.getDataManagerPlan(dataManagerId, key); + } + + /** + * Broadcasts the given event to all subscribers. Reports handle the event + * immediately. Data managers and actors will have the event queued for handling + * after the data manager is finished with its current activation. This is used + * for OBSERVATION events that are generated by the data managers. MUTATION + * events that are generated by the data managers as a proxy for actors and data + * managers should use releaseMutationEvent() instead. + * + * @throws ContractException {@link NucleusError#NULL_EVENT} if the event is + * null + */ + public void releaseObservationEvent(final Event event) { + simulation.releaseObservationEventForDataManager(event); + } + + /** + * Starts the event handling process for the given event This is used for + * MUTATION events. + * + * @throws ContractException {@link NucleusError#NULL_EVENT} if the event is + * null + */ + public void releaseMutationEvent(final Event event) { + simulation.releaseMutationEventForDataManager(event); + } + + /** + * Removes and returns the plan associated with the given key. + * + * @throws ContractException {@link NucleusError#NULL_PLAN_KEY} if the plan key + * is null + */ + public Optional> removePlan(final Object key) { + return simulation.removeDataManagerPlan(dataManagerId, key); + } + + /** + * Returns a list of the current plan keys associated with the current data + * manager + */ + public List getPlanKeys() { + return simulation.getDataManagerPlanKeys(dataManagerId); + } + + /** + * Subscribes the data manager to events of the given type for the purpose of + * execution of data changes. + * + * @throws ContractException + *
      + *
    • {@link NucleusError#NULL_EVENT_CLASS} if the + * event class is null
    • + *
    • {@link NucleusError#NULL_EVENT_CONSUMER} if the + * event consumer is null
    • + *
    • {@link NucleusError#DUPLICATE_EVENT_SUBSCRIPTION} + * if the data manager is already subscribed
    • + *
    + */ + public void subscribe(Class eventClass, BiConsumer eventConsumer) { + simulation.subscribeDataManagerToEvent(dataManagerId, eventClass, eventConsumer); + } + + /** + * Unsubscribes the data manager from events of the given type for all phases of + * event handling. + * + * @throws ContractException {@link NucleusError#NULL_EVENT_CLASS} if the event + * class is null + */ + public void unsubscribe(Class eventClass) { + simulation.unsubscribeDataManagerFromEvent(dataManagerId, eventClass); + } + + /** + * Returns true if and only if there are actor or data managers subscribed to + * the given event type. + */ + public boolean subscribersExist(Class eventClass) { + return simulation.subscribersExistForEvent(eventClass); + } + + /** + * Returns the ActorId of the current actor + */ + public ActorId getActorId() { + return simulation.focalActorId; + } + + /** + * Adds an actor to the simulation. The actor is added immediately, but the + * consumer of ActorContext is invoked after event resolution is finished and + * before time progresses. + * + * @throws ContractException {@link NucleusError#NULL_ACTOR_CONTEXT_CONSUMER} if + * the actor context consumer is null + */ + public ActorId addActor(Consumer consumer) { + return simulation.addActor(consumer); + } + + public boolean actorExists(final ActorId actorId) { + return simulation.actorExists(actorId); + } + + public T getDataManager(Class dataManagerClass) { + return simulation.getDataManagerForDataManager(dataManagerId, dataManagerClass); + } + + public double getTime() { + return simulation.time; + } + + public void halt() { + simulation.halt(); + } + + /** + * Removes the given actor from the simulation. + * + * @throws ContractException + *
      + *
    • {@link NucleusError#NULL_ACTOR_ID} if the + * actorId is null
    • + *
    • {@link NucleusError#UNKNOWN_ACTOR_ID} if the + * actor id is negative
    • + *
    • {@link NucleusError#UNKNOWN_ACTOR_ID} if the + * actor id does not correspond to a known actor
    • + *
    + */ + public void removeActor(final ActorId actorId) { + simulation.removeActor(actorId); + } + + public void releaseOutput(Object output) { + simulation.releaseOutput(output); + } + + /** + * Sets a function for converting plan data instances into consumers of actor + * context that will be used to convert stored plans from a previous simulation + * execution into current plans. Only used during the initialization of the + * simulation before time flows. + */ + public void setPlanDataConverter(Class planDataClass, + Function> conversionFunction) { + simulation.setDataManagerPlanDataConverter(dataManagerId, planDataClass, conversionFunction); + } + + /** + * Returns the time (floating point days) of simulation start. + */ + public double getStartTime() { + return simulation.getStartTime(); + } + + /** + * Returns the base date that synchronizes with simulation time zero. + */ + public LocalDate getBaseDate() { + return simulation.getBaseDate(); + } +} diff --git a/gcm3/src/main/java/nucleus/DataManagerId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/DataManagerId.java similarity index 84% rename from gcm3/src/main/java/nucleus/DataManagerId.java rename to gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/DataManagerId.java index 5c45f5f39..d79131d77 100644 --- a/gcm3/src/main/java/nucleus/DataManagerId.java +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/DataManagerId.java @@ -1,13 +1,10 @@ -package nucleus; +package gov.hhs.aspr.ms.gcm.nucleus; /** * The unique identifier for data managers. DataManagerId values are constructed * dynamically and are distributed contiguously from 0. - * - * @author Shawn Hatch - * */ -public final class DataManagerId { +public final class DataManagerId implements Comparable { private final int id; /** @@ -66,4 +63,9 @@ public String toString() { return builder.toString(); } + @Override + public int compareTo(DataManagerId dataManagerId) { + return Integer.compare(this.id, dataManagerId.id); + } + } \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/Dimension.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/Dimension.java new file mode 100644 index 000000000..b6fe19a49 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/Dimension.java @@ -0,0 +1,33 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import java.util.List; + +/** + * A Dimension represents a single independent dimension of an experiment. + * Dimensions are composed of a finite number of levels. Each level in a + * dimension updates the plugin data builders as alternate inputs for each + * scenario. + */ +public interface Dimension { + + /** + * Returns the meta data for the experiment that describes the scenario-level + * meta data. + */ + public List getExperimentMetaData(); + + /** + * Returns the number of levels in this dimension + */ + public int levelCount(); + + /** + * Executes mutations on the plugin data builders contained in the + * DimensionContext. Returns the scenario level meta data associated with the + * level. The length of the returned list must match the length of the list + * returned by the getExperimentMetaData() method. The content of the returned + * list must be identical for each invocation of the given level. + */ + public List executeLevel(DimensionContext dimensionContext, int level); + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/DimensionContext.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/DimensionContext.java new file mode 100644 index 000000000..d96872224 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/DimensionContext.java @@ -0,0 +1,132 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import util.errors.ContractException; + +/** + * A context containing PluginData and PluginDataBuilder instances that are used + * to build a particular scenario within an experiment. + */ +public final class DimensionContext implements PluginDataBuilderContainer { + + private Map, PluginDataBuilder> pluginDataBuilderBaseMap = new LinkedHashMap<>(); + private Map, PluginDataBuilder> pluginDataBuilderWorkingMap = new LinkedHashMap<>(); + private Map, PluginData> pluginDataBaseMap = new LinkedHashMap<>(); + private Map, PluginData> pluginDataWorkingMap = new LinkedHashMap<>(); + + private DimensionContext() { + } + + /** + * A builder class for DimensionContext + */ + public static class Builder { + private Builder() { + } + + private Map, PluginDataBuilder> pluginDataBuilderMap = new LinkedHashMap<>(); + private Map, PluginData> pluginDataMap = new LinkedHashMap<>(); + + /** + * Returns the DimensionContext instance composed from the inputs to this + * builder. + */ + public DimensionContext build() { + DimensionContext result = new DimensionContext(); + result.pluginDataBuilderBaseMap.putAll(pluginDataBuilderMap); + result.pluginDataBaseMap.putAll(pluginDataMap); + return result; + } + + /** + * Given a plugin Data, will add it and its clone builder to the internal map in + * this class + * + * @throws ContractException {@linkplain NucleusError#NULL_PLUGIN_DATA} if the + * plugin data builder is null + */ + public PluginDataBuilder add(T t) { + if (t == null) { + throw new ContractException(NucleusError.NULL_PLUGIN_DATA); + } + pluginDataMap.put(t.getClass(), t); + PluginDataBuilder builder = t.getCloneBuilder(); + + pluginDataBuilderMap.put(builder.getClass(), builder); + return builder; + } + } + + /** + * Returns a typed Builder instance for DimensionContext + */ + public static Builder builder() { + return new Builder(); + } + + @Override + public T getPluginDataBuilder(Class classRef) { + + PluginDataBuilder pluginDataBuilder = pluginDataBuilderWorkingMap.get(classRef); + if (pluginDataBuilder == null) { + List> candidates = new ArrayList<>(); + for (Class c : pluginDataBuilderBaseMap.keySet()) { + if (classRef.isAssignableFrom(c)) { + candidates.add(c); + } + } + if (candidates.isEmpty()) { + throw new ContractException(NucleusError.UNKNOWN_PLUGIN_DATA_BUILDER_CLASS); + } + if (candidates.size() > 1) { + throw new ContractException(NucleusError.AMBIGUOUS_PLUGIN_DATA_BUILDER_CLASS); + } + + pluginDataBuilder = pluginDataBuilderBaseMap.get(candidates.get(0)); + pluginDataBuilderWorkingMap.put(classRef, pluginDataBuilder); + } + + return classRef.cast(pluginDataBuilder); + } + + /** + * Returns the stored item matching the given class reference. + * + * @throws ContractException + *
      + *
    • {@linkplain NucleusError#AMBIGUOUS_PLUGIN_DATA_CLASS} + * if more than one plugin data matches the given + * class reference
    • + *
    • {@linkplain NucleusError#UNKNOWN_PLUGIN_DATA_CLASS} + * if no plugin data matches the given class + * reference
    • + *
    + */ + public T getPluginData(Class classRef) { + + PluginData pluginData = pluginDataWorkingMap.get(classRef); + if (pluginData == null) { + List> candidates = new ArrayList<>(); + for (Class c : pluginDataBaseMap.keySet()) { + if (classRef.isAssignableFrom(c)) { + candidates.add(c); + } + } + if (candidates.isEmpty()) { + throw new ContractException(NucleusError.UNKNOWN_PLUGIN_DATA_CLASS); + } + if (candidates.size() > 1) { + throw new ContractException(NucleusError.AMBIGUOUS_PLUGIN_DATA_CLASS); + } + + pluginData = pluginDataBaseMap.get(candidates.get(0)); + pluginDataWorkingMap.put(classRef, pluginData); + } + + return classRef.cast(pluginData); + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/Event.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/Event.java new file mode 100644 index 000000000..ceaad4928 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/Event.java @@ -0,0 +1,13 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +/** + * Represents an observable data change. Events can be produced by either actors + * or data managers. Both actors and data mangers can subscribe to events using + * various means. Data managers receive events immediately each time an event is + * released to a simulation context. Actors receive events only after the + * current actor and data managers have completed their current processing and + * before the next plan is processed from the simulation's planning queue. + */ +public interface Event { + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/EventFilter.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/EventFilter.java new file mode 100644 index 000000000..0f430f176 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/EventFilter.java @@ -0,0 +1,115 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.commons.math3.util.Pair; + +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * A generics-based data class for collecting an ordered list of predicates of + * the form F(event) = value that are used in conjunction to filter events. + */ +@Immutable +public final class EventFilter { + + private static class Data { + + private Class eventClass; + + private List, Object>> functionValuePairs = new ArrayList<>(); + + public Data() { + + } + + public Data(Data data) { + eventClass = data.eventClass; + functionValuePairs.addAll(data.functionValuePairs); + } + } + + /** + * Returns a new instance of the Builder class + * + * @throws ContractException {@linkplain NucleusError#NULL_EVENT_CLASS } if the + * class reference is null + */ + public static Builder builder(Class classReference) { + if (classReference == null) { + throw new ContractException(NucleusError.NULL_EVENT_CLASS); + } + return new Builder(classReference); + } + + /** + * Builder class for EventFilter + */ + public static class Builder { + + private Data data = new Data<>(); + + private final Class eventClass; + + private Builder(Class classReference) { + this.eventClass = classReference; + } + + /** + * Constructs a new EventLabel from the collected data. + */ + public EventFilter build() { + data.eventClass = this.eventClass; + return new EventFilter<>(new Data<>(data)); + } + + /** + * Adds an event function and its associated target value. + * + * @throws ContractException + *
      + *
    • {@linkplain NucleusError#NULL_IDENTIFIABLE_FUNCTION} + * if the identifiable function is null
    • + *
    • {@linkplain NucleusError#NULL_FUNCTION_VALUE} + * if the target value is null
    • + *
    + */ + public Builder addFunctionValuePair(IdentifiableFunction identifiableFunction, Object targetValue) { + if (identifiableFunction == null) { + throw new ContractException(NucleusError.NULL_IDENTIFIABLE_FUNCTION); + } + + if (targetValue == null) { + throw new ContractException(NucleusError.NULL_FUNCTION_VALUE); + } + + data.functionValuePairs.add(new Pair<>(identifiableFunction, targetValue)); + + return this; + } + } + + private final Data data; + + private EventFilter(Data data) { + this.data = data; + } + + /** + * Returns the event subclass associated with all function values + */ + public Class getEventClass() { + return data.eventClass; + } + + /** + * Returns an unmodifiable list of the function values + */ + public List, Object>> getFunctionValuePairs() { + return Collections.unmodifiableList(data.functionValuePairs); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/Experiment.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/Experiment.java new file mode 100644 index 000000000..6c5801d0e --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/Experiment.java @@ -0,0 +1,572 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletionService; +import java.util.concurrent.ExecutorCompletionService; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Consumer; + +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * An experiment provides a means for executing the simulation over variants of + * plugin data. Each such variant is referred to as a scenario. The scenarios + * correspond to the cross product of a finite number of dimensions, with each + * dimension having a finite number of variant levels. For example: An + * experiment contains several plugins and correspondingly several plugin data + * objects. The experiment has two dimensions. The first dimension varies one of + * the plugin data objects with 5 new values. The second dimension varies two + * values in two separate plugin data objects with 3 new values each. There will + * be 15 resulting scenarios numbered 0 to 14 corresponding to each combination + * of altered inputs. The experiment then executes the scenarios concurrently + * based on the number of threads chosen for the execution. + */ +public final class Experiment { + + private static class DimensionRec { + private final int index; + private final int levelCount; + private final Dimension dimension; + private List experimentMetaData = new ArrayList<>(); + private Map> scenarioMetaData = new LinkedHashMap<>(); + + public List getExperimentMetaData() { + return experimentMetaData; + } + + public DimensionRec(Dimension dimension, int index) { + this.dimension = dimension; + this.index = index; + for (int i = 0; i < dimension.levelCount(); i++) { + scenarioMetaData.put(i, null); + } + experimentMetaData.addAll(dimension.getExperimentMetaData()); + levelCount = dimension.levelCount(); + } + + public List executeLevel(DimensionContext dimensionContext, int level) { + List list = dimension.executeLevel(dimensionContext, level); + if (list.size() != experimentMetaData.size()) { + throw new ContractException(NucleusError.DIMENSION_LABEL_MISMATCH, + "dimension[" + index + "] has meta data: " + experimentMetaData + + " that does not match scenario meta data: " + list + " at level " + level); + } + + List baseList = scenarioMetaData.get(level); + if (baseList == null) { + scenarioMetaData.put(level, list); + } else { + if (!baseList.equals(list)) { + throw new ContractException(NucleusError.DIMENSION_LABEL_MISMATCH, "execution of level " + level + + " of dimension[" + index + "] resulted in inconsistent meta scenario data"); + } + } + return list; + } + + public int getLevelCount() { + return levelCount; + } + } + + public static class Builder { + private Data data = new Data(); + + private Builder() { + } + + /** + * Adds a non-empty dimension to the experiment + */ + public Builder addDimension(final Dimension dimension) { + if (dimension.levelCount() > 0) { + data.dimensions.add(dimension); + } + return this; + } + + /** + * Adds the output item handler to the experiment. Consumers of experiment + * context must be thread safe. + * + * @throws ContractException {@linkplain NucleusError#NULL_OUTPUT_HANDLER} if + * the output item handler is null + */ + public Builder addExperimentContextConsumer(final Consumer experimentContextConsumer) { + if (experimentContextConsumer == null) { + throw new ContractException(NucleusError.NULL_OUTPUT_HANDLER); + } + data.experimentContextConsumers.add(experimentContextConsumer); + return this; + } + + /** + * Adds a plugin to the experiment. + */ + public Builder addPlugin(final Plugin plugin) { + data.plugins.add(plugin); + return this; + } + + /** + * Builds an experiment from the collected plugins, dimensions and output + * handlers. + */ + public Experiment build() { + return new Experiment(new Data(data)); + } + + /** + * Sets the experiment parameters. Defaults to the default build of + * ExperimentParameterData. + */ + public Builder setExperimentParameterData(ExperimentParameterData experimentParameterData) { + data.experimentParameterData = experimentParameterData; + return this; + } + + /** + * Set the simulation state. Defaults to the current date and a start time of + * zero. + * + * @throws ContractException {@link NucleusError#NULL_SIMULATION_TIME} if the + * simulation time is null + */ + public Builder setSimulationState(SimulationState simulationState) { + if (simulationState == null) { + throw new ContractException(NucleusError.NULL_SIMULATION_TIME); + } + data.simulationState = simulationState; + return this; + } + + } + + /* + * A data class for holding the inputs to this builder from its client. + */ + private static class Data { + private final List dimensions = new ArrayList<>(); + private final List plugins = new ArrayList<>(); + private final List> experimentContextConsumers = new ArrayList<>(); + + private SimulationState simulationState = SimulationState.builder().build(); + + private ExperimentParameterData experimentParameterData = ExperimentParameterData.builder().build(); + + public Data() { + } + + public Data(Data data) { + dimensions.addAll(data.dimensions); + plugins.addAll(data.plugins); + experimentContextConsumers.addAll(data.experimentContextConsumers); + experimentParameterData = data.experimentParameterData; + simulationState = data.simulationState; + } + } + + /* + * The result of the execution of a SimulationCallable + */ + @Immutable + private static class SimResult { + private final boolean success; + private final int scenarioId; + private final Exception failureCause; + + public SimResult(final int scenarioId, final boolean success, Exception failureCause) { + this.scenarioId = scenarioId; + this.success = success; + this.failureCause = failureCause; + } + } + + /* + * A Callable implementor that runs the simulation in a thread from a completion + * service. + */ + private static class SimulationCallable implements Callable { + private final ExperimentStateManager experimentStateManager; + private final List plugins; + private final Integer scenarioId; + private final boolean produceSimulationStateOnHalt; + private final Double simulationHaltTime; + private final SimulationState simulationState; + + /* + * All construction arguments are thread safe implementations. + */ + private SimulationCallable(final Integer scenarioId, final ExperimentStateManager experimentStateManager, + final List plugins, final boolean produceSimulationStateOnHalt, final Double simulationHaltTime, + final SimulationState simulationState) { + this.scenarioId = scenarioId; + this.experimentStateManager = experimentStateManager; + this.plugins = new ArrayList<>(plugins); + this.produceSimulationStateOnHalt = produceSimulationStateOnHalt; + this.simulationHaltTime = simulationHaltTime; + this.simulationState = simulationState; + } + + /** + * Executes the simulation for a scenario. Returns a SimResult indicating + * success/failure. If the simulation throws an exception it is handled by + * printing a stack trace and reports a failure for the scenario. + */ + @Override + public SimResult call() throws Exception { + + final Simulation.Builder simBuilder = Simulation.builder(); + + // Load the plugins into the simulation builder + for (final Plugin plugin : plugins) { + simBuilder.addPlugin(plugin); + } + + // direct output from the simulation to the subscribed consumers + simBuilder.setOutputConsumer(experimentStateManager.getOutputConsumer(scenarioId)); + simBuilder.setRecordState(produceSimulationStateOnHalt); + simBuilder.setSimulationHaltTime(simulationHaltTime); + simBuilder.setSimulationState(simulationState); + // build the simulation + final Simulation simulation = simBuilder.build(); + + // run the simulation + boolean success = false; + Exception failureCause = null; + try { + simulation.execute(); + success = true; + } catch (final Exception e) { + failureCause = e; + } + return new SimResult(scenarioId, success, failureCause); + } + + } + + /** + * Returns a builder for Experiment + */ + public static Builder builder() { + return new Builder(); + } + + private final Data data; + + private List dimensionRecs = new ArrayList<>(); + + private ExperimentStateManager experimentStateManager; + + private Experiment(final Data data) { + this.data = data; + } + + /** + * Executes the experiment using the information collected by the builder. + */ + public void execute() { + + int scenarioCount = 1; + for (int i = 0; i < data.dimensions.size(); i++) { + Dimension dimension = data.dimensions.get(i); + DimensionRec dimensionRec = new DimensionRec(dimension, i); + dimensionRecs.add(dimensionRec); + scenarioCount *= dimensionRec.getLevelCount(); + } + + ExperimentStateManager.Builder builder = ExperimentStateManager.builder(); + builder.setScenarioCount(scenarioCount); + builder.setContinueFromProgressLog(data.experimentParameterData.continueFromProgressLog()); + Optional optionalExperimentProgressLogPath = data.experimentParameterData.getExperimentProgressLogPath(); + if (optionalExperimentProgressLogPath.isPresent()) { + builder.setScenarioProgressLogFile(optionalExperimentProgressLogPath.get()); + } + + final List experimentMetaData = new ArrayList<>(); + for (final DimensionRec dimensionRec : dimensionRecs) { + experimentMetaData.addAll(dimensionRec.getExperimentMetaData()); + } + + builder.setExperimentMetaData(experimentMetaData); + + // initialize the experiment context consumers so that they can + // subscribe to experiment level events + for (final Consumer consumer : data.experimentContextConsumers) { + builder.addExperimentContextConsumer(consumer); + } + for (Integer scenarioId : data.experimentParameterData.getExplicitScenarioIds()) { + builder.addExplicitScenarioId(scenarioId); + } + + experimentStateManager = builder.build(); + // announce to consumers that the experiment is starting + experimentStateManager.openExperiment(); + + try { + + int threadCount = data.experimentParameterData.getThreadCount(); + if (threadCount > 0) { + ExecutorService executorService = Executors.newFixedThreadPool(threadCount); + try { + executeMultiThreaded(executorService); + executorService.shutdown(); + } catch (Exception e) { + executorService.shutdownNow(); + throw new RuntimeException(e); + } + } else { + try { + executeSingleThreaded(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } finally { + // announce to consumers that the experiment has ended + experimentStateManager.closeExperiment(); + } + + } + + /* + * Executes the experiment utilizing multiple threads. If the simulation throws + * an exception it is caught and handled by reporting to standard error that the + * failure occurred as well as printing a stack trace. + */ + private void executeMultiThreaded(final ExecutorService executorService) throws Exception { + + // Determine the scenarios + final List jobs = new ArrayList<>(); + + final int scenarioCount = experimentStateManager.getScenarioCount(); + + for (int scenarioId = 0; scenarioId < scenarioCount; scenarioId++) { + final ScenarioStatus scenarioStatus = experimentStateManager.getScenarioStatus(scenarioId).get(); + if (scenarioStatus == ScenarioStatus.READY) { + jobs.add(scenarioId); + } + } + + /* + * If there is nothing to do, then do not engage. + */ + if (jobs.isEmpty()) { + return; + } + + int jobIndex = 0; + + // Create the Completion Service using the suggested thread + // count + // final ExecutorService executorService = + // Executors.newFixedThreadPool(data.threadCount); + + final CompletionService completionService = new ExecutorCompletionService<>(executorService); + + /* + * Start the initial threads. Don't exceed the thread count or the job count. + * Each time a thread is cleared, a new simulation will be processed through the + * CompletionService until we run out of simulations to run. + */ + int threadCount = data.experimentParameterData.getThreadCount(); + while (jobIndex < (Math.min(threadCount, jobs.size()) - 1)) { + final Integer scenarioId = jobs.get(jobIndex); + List plugins = getNewPluginInstancesFromScenarioId(scenarioId); + completionService.submit(new SimulationCallable(scenarioId, experimentStateManager, plugins, + data.experimentParameterData.stateRecordingIsScheduled(), + data.experimentParameterData.getSimulationHaltTime().orElse(null), data.simulationState)); + jobIndex++; + } + + /* + * While there are still jobs to be assigned to a thread, or jobs that have not + * yet completed processing, we check to see if a new job needs processing and + * see if a previous job has completed. + */ + int jobCompletionCount = 0; + while (jobCompletionCount < jobs.size()) { + if (jobIndex < jobs.size()) { + final Integer scenarioId = jobs.get(jobIndex); + List plugins = getNewPluginInstancesFromScenarioId(scenarioId); + completionService.submit(new SimulationCallable(scenarioId, experimentStateManager, plugins, + data.experimentParameterData.stateRecordingIsScheduled(), + data.experimentParameterData.getSimulationHaltTime().orElse(null), data.simulationState)); + jobIndex++; + } + + /* + * This call is blocking and waits for a job to complete and a thread to clear. + */ + // try { + final SimResult simResult = completionService.take().get(); + if (simResult.success) { + experimentStateManager.closeScenarioAsSuccess(simResult.scenarioId); + } else { + experimentStateManager.closeScenarioAsFailure(simResult.scenarioId, simResult.failureCause); + + if (data.experimentParameterData.haltOnException()) { + throw simResult.failureCause; + } + } + + /* + * Once the blocking call returns, we increment the jobCompletionCount + */ + jobCompletionCount++; + } + + } + + /* + * Executes the experiment using the main thread. If the simulation throws an + * exception it is caught and handled by reporting to standard error that the + * failure occurred as well as printing a stack trace. + */ + private void executeSingleThreaded() throws Exception { + + // Execute each scenario + final int scenarioCount = experimentStateManager.getScenarioCount(); + + for (int scenarioId = 0; scenarioId < scenarioCount; scenarioId++) { + Simulation.Builder simBuilder = Simulation.builder(); + final ScenarioStatus scenarioStatus = experimentStateManager.getScenarioStatus(scenarioId).get(); + if (scenarioStatus != ScenarioStatus.READY) { + continue; + } + + // generate the plugins that will form the simulation for the given + // scenario id + final List plugins = getNewPluginInstancesFromScenarioId(scenarioId); + + // Load the plugins into the simulation builder + for (final Plugin plugin : plugins) { + simBuilder.addPlugin(plugin); + } + + simBuilder.setRecordState(data.experimentParameterData.stateRecordingIsScheduled()); + simBuilder.setSimulationHaltTime(data.experimentParameterData.getSimulationHaltTime().orElse(null)); + simBuilder.setSimulationState(data.simulationState); + // direct output from the simulation to the subscribed consumers + simBuilder.setOutputConsumer(experimentStateManager.getOutputConsumer(scenarioId)); + + // build the simulation + final Simulation simulation = simBuilder.build(); + + // run the simulation + boolean success = false; + Exception failureCause = null; + + try { + simulation.execute(); + success = true; + } catch (final Exception e) { + failureCause = e; + } + + if (success) { + experimentStateManager.closeScenarioAsSuccess(scenarioId); + } else { + experimentStateManager.closeScenarioAsFailure(scenarioId, failureCause); + + if (data.experimentParameterData.haltOnException()) { + throw failureCause; + } + } + + } + } + + private List getNewPluginInstancesFromScenarioId(final int scenarioId) { + + final DimensionContext.Builder contextBuilder = DimensionContext.builder(); + + /* + * Set up a map that will allow us to associate with each plugin the new plugin + * data data builder instances associated with that plugin. + * + * We need to avoid using the plugin as a key. Plugins contain plugin datas, + * which may contain a huge amount of dataa and thus have expensive hash code + * costs. + */ + Map> dataBuilderMap = new LinkedHashMap<>(); + Map pluginMap = new LinkedHashMap<>(); + + for (final Plugin plugin : data.plugins) { + List list = new ArrayList<>(); + pluginMap.put(plugin.getPluginId(), plugin); + dataBuilderMap.put(plugin.getPluginId(), list); + for (final PluginData pluginData : plugin.getPluginDatas()) { + list.add(contextBuilder.add(pluginData)); + } + } + final DimensionContext dimensionContext = contextBuilder.build(); + + // initialize the scenario meta data + final List scenarioMetaData = new ArrayList<>(); + + /* + * From the scenario id select the functions from each dimension. Have the + * functions mutate the plugin builders and return meta data. + */ + int modulus = 1; + for (int i = 0; i < dimensionRecs.size(); i++) { + DimensionRec dimensionRec = dimensionRecs.get(i); + + /* + * Determine for the dimension the level within the dimension that corresponds + * to the scenario id + */ + int levelCount = dimensionRec.getLevelCount(); + final int level = (scenarioId / modulus) % levelCount; + modulus *= levelCount; + + // get the function from the dimension + List dimensionMetaData = dimensionRec.executeLevel(dimensionContext, level); + + scenarioMetaData.addAll(dimensionMetaData); + } + + // update the experiment state manager with the meta data for the + // scenario + experimentStateManager.openScenario(scenarioId, scenarioMetaData); + + /* + * Rebuild the plugins. + */ + final List result = new ArrayList<>(); + + for (PluginId pluginId : pluginMap.keySet()) { + Plugin plugin = pluginMap.get(pluginId); + List pluginDataBuilders = dataBuilderMap.get(pluginId); + + Plugin.Builder pluginBuilder = Plugin.builder(); + pluginBuilder.setPluginId(plugin.getPluginId()); + + Optional> optionalInitializer = plugin.getInitializer(); + if (optionalInitializer.isPresent()) { + pluginBuilder.setInitializer(optionalInitializer.get()); + } + + for (PluginId dependencyPluginId : plugin.getPluginDependencies()) { + pluginBuilder.addPluginDependency(dependencyPluginId); + } + + for (PluginDataBuilder pluginDataBuilder : pluginDataBuilders) { + pluginBuilder.addPluginData(pluginDataBuilder.build()); + } + result.add(pluginBuilder.build()); + } + + return result; + + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/ExperimentContext.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/ExperimentContext.java new file mode 100644 index 000000000..a737edf3f --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/ExperimentContext.java @@ -0,0 +1,140 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import java.util.List; +import java.util.Optional; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +import gov.hhs.aspr.ms.gcm.nucleus.util.TriConsumer; +import net.jcip.annotations.ThreadSafe; +import util.errors.ContractException; + +/** + * Interface for the thread safe access of experiment/scenario state and meta + * data. This context provides subscription for the five key events that occur + * outside of the simulation; 1) Experiment Open, 2) Scenario Open, 3) Scenario + * Output, 4) Scenario Close and 5) Experiment Close + */ +@ThreadSafe +public final class ExperimentContext { + private final ExperimentStateManager experimentStateManager; + + protected ExperimentContext(ExperimentStateManager experimentStateManager) { + this.experimentStateManager = experimentStateManager; + } + + /** + * Subscribes to the open of simulations + * + * @throws ContractException {@link NucleusError#NULL_EXPERIMENT_CONTEXT_CONSUMER} + * if the consumer is null + */ + public void subscribeToSimulationOpen(BiConsumer consumer) { + experimentStateManager.subscribeToSimulationOpen(consumer); + } + + /** + * Subscribes to the close of simulations + * + * @throws ContractException {@link NucleusError#NULL_EXPERIMENT_CONTEXT_CONSUMER} + * if the consumer is null + */ + public void subscribeToSimulationClose(BiConsumer consumer) { + experimentStateManager.subscribeToSimulationClose(consumer); + } + + /** + * Subscribes to the open of the experiment + * + * @throws ContractException {@link NucleusError#NULL_EXPERIMENT_CONTEXT_CONSUMER} + * if the consumer is null + */ + public void subscribeToExperimentOpen(Consumer consumer) { + experimentStateManager.subscribeToExperimentOpen(consumer); + } + + /** + * Subscribes to the close of the experiment + * + * @throws ContractException {@link NucleusError#NULL_EXPERIMENT_CONTEXT_CONSUMER} + * if the consumer is null + */ + public void subscribeToExperimentClose(Consumer consumer) { + experimentStateManager.subscribeToExperimentClose(consumer); + } + + /** + * Subscribes the output handler to output of the given type. + * + * @throws ContractException + *
      + *
    • {@link NucleusError#NULL_EXPERIMENT_CONTEXT_CONSUMER} + * if the consumer is null
    • + *
    • {@link NucleusError#NULL_EXPERIMENT_CONTEXT_CONSUMER} + * if the consumer is null
    • + *
    + */ + public void subscribeToOutput(Class outputClass, TriConsumer consumer) { + experimentStateManager.subscribeToOutput(outputClass, consumer); + } + + /** + * Returns the current status for the given scenario id if the scenario exists. + */ + public Optional getScenarioStatus(int scenarioId) { + return experimentStateManager.getScenarioStatus(scenarioId); + } + + /** + * Returns the current number of scenarios with the given status + */ + public int getStatusCount(ScenarioStatus scenarioStatus) { + return experimentStateManager.getStatusCount(scenarioStatus); + } + + /** + * Returns the number of seconds that have elapsed since the start of the + * experiment + */ + public double getElapsedSeconds() { + return experimentStateManager.getElapsedSeconds(); + } + + /** + * Returns the list of meta data for the given scenario if it is available. + */ + public List getScenarioMetaData(int scenarioId) { + return experimentStateManager.getScenarioMetaData(scenarioId); + } + + /** + * Returns the list of meta data for the experiment. These meta data are + * descriptors of the scenario meta data produced by each execution of the + * simulation. + */ + public List getExperimentMetaData() { + return experimentStateManager.getExperimentMetaData(); + } + + /** + * Returns the number of scenarios in the experiment + */ + public int getScenarioCount() { + return experimentStateManager.getScenarioCount(); + } + + /** + * Returns the current list of scenario ids for the given scenario status + */ + public List getScenarios(ScenarioStatus scenarioStatus) { + return experimentStateManager.getScenarios(scenarioStatus); + } + + /** + * Returns the exception associated with a failed sceario + */ + public Optional getScenarioFailureCause(int scenarioId) { + return experimentStateManager.getScenarioFailureCause(scenarioId); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/ExperimentParameterData.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/ExperimentParameterData.java new file mode 100644 index 000000000..61785e18f --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/ExperimentParameterData.java @@ -0,0 +1,294 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import java.nio.file.Path; +import java.util.LinkedHashSet; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +import util.errors.ContractException; + +/** + * An experiment provides a means for executing the simulation over variants of + * plugin data. Each such variant is referred to as a scenario. The scenarios + * correspond to the cross product of a finite number of dimensions, with each + * dimension having a finite number of variant levels. For example: An + * experiment contains several plugins and correspondingly several plugin data + * objects. The experiment has two dimensions. The first dimension varies one of + * the plugin data objects with 5 new values. The second dimension varies two + * values in two separate plugin data objects with 3 new values each. There will + * be 15 resulting scenarios numbered 0 to 14 corresponding to each combination + * of altered inputs. The experiment then executes the scenarios concurrently + * based on the number of threads chosen for the execution. + */ +public final class ExperimentParameterData { + + public static class Builder { + private Data data = new Data(); + + private Builder() { + } + + /** + * Marks the scenario to be explicitly run. All other scenarios will be ignored. + */ + public Builder addExplicitScenarioId(Integer scenarioId) { + data.explicitScenarioIds.add(scenarioId); + return this; + } + + /** + * Builds an experiment from the collected plugins, dimensions and output + * handlers. + */ + public ExperimentParameterData build() { + return new ExperimentParameterData(new Data(data)); + } + + /** + * Sets the path for experiment progress log. A null path turns off logging and + * run resumption. Default value is null. + */ + public Builder setExperimentProgressLog(final Path path) { + data.experimentProgressLogPath = path; + return this; + } + + /** + * Instructs the experiment to continue experiment progress from the experiment + * progress log. Defaults to false; + */ + public Builder setContinueFromProgressLog(boolean continueFromProgressLog) { + data.continueFromProgressLog = continueFromProgressLog; + return this; + } + + /** + * Sets the number of scenarios that may run concurrently. Generally this should + * be set to one less than the number of virtual processors on the machine that + * is running the experiment. Setting the thread count to zero causes the + * simulations to execute in the main thread. + * + * @throws ContractException {@linkplain NucleusError#NEGATIVE_THREAD_COUNT} if + * the thread count is negative + */ + public Builder setThreadCount(final int threadCount) { + if (threadCount < 0) { + throw new ContractException(NucleusError.NEGATIVE_THREAD_COUNT); + } + data.threadCount = threadCount; + return this; + } + + /** + * Signals to simulation components to record their state as plugin data as + * output to the experiment Defaults to false. + */ + public Builder setRecordState(boolean recordState) { + data.stateRecordingIsScheduled = recordState; + return this; + } + + /** + * Sets the halt time for the simulation. Defaults to -1, which is equivalent to + * not halting. If the simulation has been instructed to produce its state at + * halt, then the halt time must be set to a positive value. Setting this to a + * non-negative value that is less than the simulation time used to start the + * simulation will result in an exception. + */ + public Builder setSimulationHaltTime(Double simulationHaltTime) { + data.simulationHaltTime = simulationHaltTime; + return this; + } + + /** + * When true, the experiment halts on any exception thrown by any of the + * simulation instances. The experiment will attempt to gracefully terminate, + * halting any ongoing simulation instances and completing the experiment. When + * false, the experiment logs the failure with the experiment context and + * continues with the remaining simulation instances. Defaulted to true. + */ + public Builder setHaltOnException(final boolean haltOnException) { + data.haltOnException = haltOnException; + return this; + } + + } + + /* + * A data class for holding the inputs to this builder from its client. + */ + private static class Data { + + private int threadCount; + private boolean stateRecordingIsScheduled; + private Double simulationHaltTime = null; + private boolean haltOnException = true; + private Path experimentProgressLogPath; + private boolean continueFromProgressLog; + private Set explicitScenarioIds = new LinkedHashSet<>(); + + public Data() { + } + + public Data(Data data) { + threadCount = data.threadCount; + stateRecordingIsScheduled = data.stateRecordingIsScheduled; + simulationHaltTime = data.simulationHaltTime; + haltOnException = data.haltOnException; + experimentProgressLogPath = data.experimentProgressLogPath; + continueFromProgressLog = data.continueFromProgressLog; + explicitScenarioIds.addAll(data.explicitScenarioIds); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (continueFromProgressLog ? 1231 : 1237); + result = prime * result + ((experimentProgressLogPath == null) ? 0 : experimentProgressLogPath.hashCode()); + result = prime * result + ((explicitScenarioIds == null) ? 0 : explicitScenarioIds.hashCode()); + result = prime * result + (haltOnException ? 1231 : 1237); + result = prime * result + ((simulationHaltTime == null) ? 0 : simulationHaltTime.hashCode()); + result = prime * result + (stateRecordingIsScheduled ? 1231 : 1237); + result = prime * result + threadCount; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + if (continueFromProgressLog != other.continueFromProgressLog) { + return false; + } + if (experimentProgressLogPath == null) { + if (other.experimentProgressLogPath != null) { + return false; + } + } else if (!experimentProgressLogPath.equals(other.experimentProgressLogPath)) { + return false; + } + if (!explicitScenarioIds.equals(other.explicitScenarioIds)) { + return false; + } + if (haltOnException != other.haltOnException) { + return false; + } + if (simulationHaltTime == null) { + if (other.simulationHaltTime != null) { + return false; + } + } else if (!simulationHaltTime.equals(other.simulationHaltTime)) { + return false; + } + if (stateRecordingIsScheduled != other.stateRecordingIsScheduled) { + return false; + } + if (threadCount != other.threadCount) { + return false; + } + return true; + } + + } + + /** + * Returns a builder for Experiment + */ + public static Builder builder() { + return new Builder(); + } + + private final Data data; + + private ExperimentParameterData(final Data data) { + this.data = data; + } + + /** + * Returns the number of threads that the experiment should use to execute the + * scenarios. A thread count of zero indicates that the scenarios should be + * executed in the main thread. + */ + public int getThreadCount() { + return data.threadCount; + } + + /** + * Signals to the simulation components to record their state as plugin data as + * output to the experiment. + */ + public boolean stateRecordingIsScheduled() { + return data.stateRecordingIsScheduled; + } + + /** + * Return the halt time for the simulation. Returns empty optional to indicate + * that no halting time is specified. + */ + public Optional getSimulationHaltTime() { + return Optional.ofNullable(data.simulationHaltTime); + } + + /** + * When true, the experiment halts on any exception thrown by any of the + * simulation instances. The experiment will attempt to gracefully terminate, + * halting any ongoing simulation instances and completing the experiment. When + * false, the experiment logs the failure with the experiment context and + * continues with the remaining simulation instances. + */ + public boolean haltOnException() { + return data.haltOnException; + } + + /** + * Returns the optional path for experiment progress log. An empty optional path + * turns off logging and run resumption. + */ + public Optional getExperimentProgressLogPath() { + return Optional.ofNullable(data.experimentProgressLogPath); + } + + /** + * Instructs the experiment to continue experiment progress from the experiment + * progress log. + */ + public boolean continueFromProgressLog() { + return data.continueFromProgressLog; + } + + /** + * Returns the set of scenario id to be explicitly run. If the set is empty, + * then all scenarios are run. + */ + public Set getExplicitScenarioIds() { + return new LinkedHashSet<>(data.explicitScenarioIds); + } + + @Override + public int hashCode() { + return Objects.hash(data); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + ExperimentParameterData other = (ExperimentParameterData) obj; + return Objects.equals(data, other.data); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/ExperimentStateManager.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/ExperimentStateManager.java new file mode 100644 index 000000000..8ec06761f --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/ExperimentStateManager.java @@ -0,0 +1,815 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +import gov.hhs.aspr.ms.gcm.nucleus.util.TriConsumer; +import net.jcip.annotations.NotThreadSafe; +import net.jcip.annotations.ThreadSafe; +import util.errors.ContractException; +import util.time.TimeElapser; + +/** + * A utility class used by the experiment to manage experiment context consumers + * and ensure thread safe access to scenario related state. + */ +@ThreadSafe +public final class ExperimentStateManager { + + private ExperimentStatus experimentStatus = ExperimentStatus.READY; + + private static enum ExperimentStatus { + READY, OPENED, CLOSED; + } + + private ExperimentContext experimentContext; + + private TimeElapser timeElapser = new TimeElapser(); + + private Map scenarioRecords = new LinkedHashMap<>(); + + /** + * Updates the scenario's status to RUNNING and sets the meta data for the + * scenario. Announces the opening of the scenario to subscribed experiment + * context consumers. Should be preceded by openExperiment. + * + * @throws ContractException + *
      + *
    • {@linkplain NucleusError#NULL_SCENARIO_ID}if + * the scenario id is null
    • + *
    • {@linkplain NucleusError#NULL_META_DATA}if the + * meta data is null
    • + *
    • {@linkplain NucleusError#NULL_META_DATA}if the + * meta data contains a null datum
    • + *
    • {@linkplain NucleusError#UNKNOWN_SCENARIO_ID}if + * the scenario is not known
    • + *
    • {@linkplain NucleusError#SCENARIO_CANNOT_BE_EXECUTED}if + * the scenario's current status is not + * {@linkplain ScenarioStatus#READY}
    • + *
    + */ + public synchronized void openScenario(Integer scenarioId, List metaData) { + + if (scenarioId == null) { + throw new ContractException(NucleusError.NULL_SCENARIO_ID); + } + + ScenarioRecord scenarioRecord = scenarioRecords.get(scenarioId); + if (scenarioRecord == null) { + throw new ContractException(NucleusError.UNKNOWN_SCENARIO_ID, scenarioId); + } + + if (scenarioRecord.scenarioStatus != ScenarioStatus.READY) { + throw new ContractException(NucleusError.SCENARIO_CANNOT_BE_EXECUTED, + "scenario " + scenarioId + " " + scenarioRecord.scenarioStatus); + } + + if (metaData == null) { + throw new ContractException(NucleusError.NULL_META_DATA); + } + for (String metaDatum : metaData) { + if (metaDatum == null) { + throw new ContractException(NucleusError.NULL_META_DATA); + } + } + + scenarioRecord.scenarioStatus = ScenarioStatus.RUNNING; + scenarioRecord.metaData = new ArrayList<>(); + scenarioRecord.metaData.addAll(metaData); + + for (BiConsumer consumer : simOpenConsumers) { + consumer.accept(experimentContext, scenarioId); + } + } + + private static final String LINE_SEPARATOR = System.getProperty("line.separator"); + + /** + * Announces the closure of the scenario to subscribed experiment context + * consumers. Records the scenario in the scenario progress file if the file is + * active. + * + * @throws ContractException + *
      + *
    • {@linkplain NucleusError#NULL_SCENARIO_ID} if + * the scenario id is null
    • + *
    • {@linkplain NucleusError#UNKNOWN_SCENARIO_ID} + * if the scenario id is not in the range [0,scenario + * count)
    • + *
    + */ + public synchronized void closeScenarioAsSuccess(Integer scenarioId) { + + if (scenarioId == null) { + throw new ContractException(NucleusError.NULL_SCENARIO_ID); + } + + ScenarioRecord scenarioRecord = scenarioRecords.get(scenarioId); + + if (scenarioRecord == null) { + throw new ContractException(NucleusError.UNKNOWN_SCENARIO_ID, scenarioId); + } + + scenarioRecord.scenarioStatus = ScenarioStatus.SUCCEDED; + + for (BiConsumer consumer : simCloseConsumers) { + consumer.accept(experimentContext, scenarioId); + } + + if (writer != null) { + try { + writer.write(scenarioId.toString()); + for (String metaDatum : scenarioRecord.metaData) { + writer.write("\t"); + writer.write(metaDatum); + } + writer.write(LINE_SEPARATOR); + writer.flush(); + } catch (final IOException e) { + // re-thrown as runtime exception + throw new RuntimeException(e); + } + } + } + + /** + * Announces the closure of the scenario to the subscribed experiment context + * consumers. Records the failure status of the scenario and its failure cause. + * Failed scenarios are not reported to the progress log. + * + * @throws ContractException + *
      + *
    • {@linkplain NucleusError#NULL_SCENARIO_ID} if + * the scenario id is null
    • + *
    • {@linkplain NucleusError#UNKNOWN_SCENARIO_ID} + * if the scenario id is not in the range [0,scenario + * count)
    • + *
    + */ + public synchronized void closeScenarioAsFailure(Integer scenarioId, Exception exception) { + + if (scenarioId == null) { + throw new ContractException(NucleusError.NULL_SCENARIO_ID); + } + + ScenarioRecord scenarioRecord = scenarioRecords.get(scenarioId); + + if (scenarioRecord == null) { + throw new ContractException(NucleusError.UNKNOWN_SCENARIO_ID); + } + + scenarioRecord.scenarioStatus = ScenarioStatus.FAILED; + scenarioRecord.failureCause = exception; + + for (BiConsumer consumer : simCloseConsumers) { + consumer.accept(experimentContext, scenarioId); + } + + } + + /** + * Returns the current status for the given scenario id if the scenario exists. + */ + public synchronized Optional getScenarioStatus(int scenarioId) { + ScenarioRecord scenarioRecord = scenarioRecords.get(scenarioId); + ScenarioStatus result = null; + if (scenarioRecord != null) { + result = scenarioRecord.scenarioStatus; + } + return Optional.ofNullable(result); + } + + public synchronized Optional getScenarioFailureCause(int scenarioId) { + ScenarioRecord scenarioRecord = scenarioRecords.get(scenarioId); + Exception result = null; + if (scenarioRecord != null) { + result = scenarioRecord.failureCause; + } + return Optional.ofNullable(result); + } + + /** + * Returns the current number of scenarios with the given status + */ + public synchronized int getStatusCount(ScenarioStatus scenarioStatus) { + int result = 0; + for (Integer scenarioId : scenarioRecords.keySet()) { + ScenarioRecord scenarioRecord = scenarioRecords.get(scenarioId); + if (scenarioRecord.scenarioStatus == scenarioStatus) { + result++; + } + } + return result; + } + + /** + * Returns the list of meta data for the experiment. These meta data are + * descriptors of the scenario meta data produced by each execution of the + * simulation. + */ + public synchronized List getExperimentMetaData() { + return new ArrayList<>(data.experimentMetaData); + } + + /** + * Returns the list of currently available meta data for the given scenario id. + * Note that READY and SKIPPED scenarios will not have associated meta data. + */ + public synchronized List getScenarioMetaData(Integer scenarioId) { + ScenarioRecord scenarioRecord = scenarioRecords.get(scenarioId); + List result = new ArrayList<>(); + if (scenarioRecord != null) { + result.addAll(scenarioRecord.metaData); + } + return result; + } + + /** + * Returns the current list of scenario ids for the given scenario status + */ + public synchronized List getScenarios(ScenarioStatus scenarioStatus) { + List result = new ArrayList<>(); + for (Integer scenarioId : scenarioRecords.keySet()) { + ScenarioRecord scenarioRecord = scenarioRecords.get(scenarioId); + if (scenarioRecord.scenarioStatus == scenarioStatus) { + result.add(scenarioId); + } + } + return result; + } + + private List> simOpenConsumers = new ArrayList<>(); + + private List> simCloseConsumers = new ArrayList<>(); + + private List> experimentOpenConsumers = new ArrayList<>(); + + private List> experimentCloseConsumers = new ArrayList<>(); + + private Map, Set>> outputConsumerMap = new LinkedHashMap<>(); + + private static class MetaOutputConsumer { + private MetaOutputConsumer(TriConsumer consumer) { + this.consumer = consumer; + } + + private TriConsumer consumer; + + @SuppressWarnings("unchecked") + public void accept(ExperimentContext experimentContext, Integer scenarioId, Object object) { + consumer.accept(experimentContext, scenarioId, (T) object); + } + } + + /** + * Returns the number of seconds that have elapsed since the start of the + * experiment + */ + public synchronized double getElapsedSeconds() { + return timeElapser.getElapsedSeconds(); + } + + /** + * Returns the number of scenarios in the experiment + */ + public synchronized int getScenarioCount() { + return data.scenarioCount; + } + + private static class ScenarioRecord { + private ScenarioStatus scenarioStatus; + private List metaData = new ArrayList<>(); + private Exception failureCause; + } + + private static class Data { + private Integer scenarioCount = 0; + private Path progressLogFile; + private List experimentMetaData = new ArrayList<>(); + private List> contextConsumers = new ArrayList<>(); + private boolean continueFromProgressLog; + private Set explicitScenarioIds = new LinkedHashSet<>(); + + public Data() { + } + + public Data(Data data) { + scenarioCount = data.scenarioCount; + progressLogFile = data.progressLogFile; + experimentMetaData.addAll(data.experimentMetaData); + contextConsumers.addAll(data.contextConsumers); + continueFromProgressLog = data.continueFromProgressLog; + explicitScenarioIds.addAll(data.explicitScenarioIds); + } + } + + /** + * Returns a new Builder instance for the ExperimentStateManager + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder class for ExperimentStateManager + */ + public static class Builder { + private Data data = new Data(); + + private Builder() { + + } + + /** + * Builds the ExperimentStateManager from the collected data. + * + * @throws ContractException + *
      + *
    • {@linkplain NucleusError#UNKNOWN_SCENARIO_ID} + * if an explicit scenario id is not in the span of + * the experiment's scenario ids
    • + *
    • {@linkplain NucleusError#NULL_SCENARIO_PROGRESS_FILE} + * if continue from progress file was chosen, but the + * path to the file is null
    • + *
    • {@linkplain NucleusError#NON_EXISTANT_SCEANARIO_PROGRESS} + * if continue from progress file was chosen, but the + * path to the file does not exist
    • + *
    • {@linkplain NucleusError#UNREADABLE_SCEANARIO_PROGRESS} + * if continue from progress file was chosen, but the + * path lead to a non-file
    • + *
    • {@linkplain NucleusError#UNREADABLE_SCEANARIO_PROGRESS} + * if the lines of the file cannot be loaded
    • + *
    • {@linkplain NucleusError#INCOMPATIBLE_SCEANARIO_PROGRESS} + * if the header line of the file does not match the + * expected header line for the current + * experiment
    • + *
    • {@linkplain NucleusError#INCOMPATIBLE_SCEANARIO_PROGRESS} + * if a scenario id is encountered that is not valid + * for the the current experiment
    • + *
    + */ + public ExperimentStateManager build() { + ExperimentStateManager result = new ExperimentStateManager(new Data(data)); + result.init(); + return result; + } + + /** + * Sets the scenario count as calculated from the dimensions. + */ + public Builder setScenarioCount(Integer scenarioCount) { + data.scenarioCount = scenarioCount; + return this; + } + + /** + * Sets the file that is used to report scenario progress. + */ + public Builder setScenarioProgressLogFile(Path progressLogFile) { + data.progressLogFile = progressLogFile; + return this; + } + + /** + * Marks the scenario to be explicitly run. All other scenarios will be ignored. + */ + public Builder addExplicitScenarioId(Integer scenarioId) { + data.explicitScenarioIds.add(scenarioId); + return this; + } + + /** + * Sets the list of string meta data for the experiment. These meta data are + * descriptors of the scenario meta data produced by each execution of the + * simulation. + * + * @throws ContractException + *
      + *
    • {@linkplain NucleusError#NULL_META_DATA} if the + * experiment meta data is null
    • + *
    • {@linkplain NucleusError#NULL_META_DATA} if the + * experiment meta data is null
    • + *
    • {@linkplain NucleusError#NULL_META_DATA} if the + * experiment meta data is null
    • + *
    + */ + public Builder setExperimentMetaData(List experimentMetaData) { + if (experimentMetaData == null) { + throw new ContractException(NucleusError.NULL_META_DATA); + } + for (String metaDatum : experimentMetaData) { + if (metaDatum == null) { + throw new ContractException(NucleusError.NULL_META_DATA); + } + } + data.experimentMetaData = new ArrayList<>(experimentMetaData); + return this; + } + + /** + * Adds a experiment context consumer that will be initialized at the start of + * the experiment. + * + * @throws ContractException {@linkplain NucleusError#NULL_EXPERIMENT_CONTEXT_CONSUMER} + * if the context consumer is null + */ + public Builder addExperimentContextConsumer(Consumer contextConsumer) { + if (contextConsumer == null) { + throw new ContractException(NucleusError.NULL_EXPERIMENT_CONTEXT_CONSUMER); + } + data.contextConsumers.add(contextConsumer); + return this; + } + + /** + * Sets the option to continue the current experiment from the progress. + */ + public Builder setContinueFromProgressLog(boolean continueFromProgressLog) { + data.continueFromProgressLog = continueFromProgressLog; + return this; + } + } + + private Data data; + + private BufferedWriter writer; + + private ExperimentStateManager(Data data) { + this.data = data; + } + + private void init() { + experimentContext = new ExperimentContext(this); + + // validate the explicit scenario ids + for (int id : data.explicitScenarioIds) { + if ((id < 0) || (id >= data.scenarioCount)) { + throw new ContractException(NucleusError.UNKNOWN_SCENARIO_ID, id); + } + } + + for (int i = 0; i < data.scenarioCount; i++) { + ScenarioRecord scenarioRecord = new ScenarioRecord(); + scenarioRecord.scenarioStatus = ScenarioStatus.READY; + scenarioRecords.put(i, scenarioRecord); + } + + /* + * Mark any READY scenarios that were previously executed as + * PREVIOUSLY_SUCCEEDED + */ + if (data.continueFromProgressLog) { + readProgressFile(); + } + + /* + * If there are any explicit scenario ids, adjust the status of the sceanrios + */ + if (!data.explicitScenarioIds.isEmpty()) { + /* + * Force the explicit scenarios to READY. Explicitly selected scenarios must be + * executed. + */ + for (int id : data.explicitScenarioIds) { + ScenarioRecord scenarioRecord = scenarioRecords.get(id); + scenarioRecord.scenarioStatus = ScenarioStatus.READY; + } + + /* + * Any remaining scenarios that are currently marked READY are now marked + * SKIPPED + */ + for (int id = 0; id < data.scenarioCount; id++) { + ScenarioRecord scenarioRecord = scenarioRecords.get(id); + if (scenarioRecord.scenarioStatus == ScenarioStatus.READY) { + if (!data.explicitScenarioIds.contains(id)) { + scenarioRecord.scenarioStatus = ScenarioStatus.SKIPPED; + } + } + } + } + + /* + * refresh the progress file as needed -- some of the PREVIOUSLY_SUCCEEDED may + * have been marked as READY since explicitly included scenarios are always run + */ + writeProgressFile(); + + } + + protected synchronized void subscribeToSimulationOpen(BiConsumer consumer) { + simOpenConsumers.add(consumer); + } + + protected synchronized void subscribeToSimulationClose(BiConsumer consumer) { + if (consumer == null) { + throw new ContractException(NucleusError.NULL_EXPERIMENT_CONTEXT_CONSUMER); + } + simCloseConsumers.add(consumer); + } + + protected synchronized void subscribeToExperimentOpen(Consumer consumer) { + experimentOpenConsumers.add(consumer); + } + + protected synchronized void subscribeToExperimentClose(Consumer consumer) { + experimentCloseConsumers.add(consumer); + } + + protected synchronized void subscribeToOutput(Class outputClass, + TriConsumer consumer) { + Set> outputConsumers = outputConsumerMap.get(outputClass); + if (outputConsumers == null) { + outputConsumers = new LinkedHashSet<>(); + outputConsumerMap.put(outputClass, outputConsumers); + } + outputConsumers.add(new MetaOutputConsumer<>(consumer)::accept); + } + + private final static String SCENARIO_LABEL = "scenario"; + + private void readProgressFile() { + + /* + * if the client does not want to continue from the progress log, we return. + */ + if (!data.continueFromProgressLog) { + return; + } + + /* + * If the progress file is null, then we can't proceed. + */ + if (data.progressLogFile == null) { + throw new ContractException(NucleusError.NULL_SCENARIO_PROGRESS_FILE); + } + + if (!Files.exists(data.progressLogFile)) { + throw new ContractException(NucleusError.NON_EXISTANT_SCEANARIO_PROGRESS); + } + + if (!Files.isRegularFile(data.progressLogFile)) { + throw new ContractException(NucleusError.UNREADABLE_SCEANARIO_PROGRESS, "not a regular file"); + } + + /* + * Try to read in the tab delimited lines. + */ + List lines = new ArrayList<>(); + + try { + lines = Files.readAllLines(data.progressLogFile); + } catch (IOException e) { + e.printStackTrace(); + throw new ContractException(NucleusError.UNREADABLE_SCEANARIO_PROGRESS, " failed to load lines"); + } + + /* + * Corruption of lines in this file is expected since it may not have been + * closed properly. We will default to not loading input gracefully. + */ + if (lines.isEmpty()) { + return; + } + + /* + * If the header line does not match the current meta data, then we will throw + * an exception since the client may not want the existing file to be + * overwritten + */ + List expectedHeader = new ArrayList<>(); + expectedHeader.add(SCENARIO_LABEL); + expectedHeader.addAll(data.experimentMetaData); + + List actualHeader = Arrays.asList(lines.get(0).split("\t")); + if (!expectedHeader.equals(actualHeader)) { + throw new ContractException(NucleusError.INCOMPATIBLE_SCEANARIO_PROGRESS, "wrong file header"); + } + + // Load the remaining lines + int n = lines.size(); + for (int i = 1; i < n; i++) { + List entries = Arrays.asList(lines.get(i).split("\t")); + if (entries.size() != expectedHeader.size()) { + /* + * Potential ungraceful termination of previous experiment + */ + break; + } + + // try to get the scenario id + int scenarioId = -1; + try { + scenarioId = Integer.parseInt(entries.get(0)); + } catch (Exception e) { + /* + * Potential ungraceful termination of previous experiment + */ + break; + } + + ScenarioRecord scenarioRecord = scenarioRecords.get(scenarioId); + + /* + * If the scenario is not recognized, then we throw an exception so that the + * file is not overwritten + */ + if (scenarioRecord == null) { + throw new ContractException(NucleusError.INCOMPATIBLE_SCEANARIO_PROGRESS, + "scenario " + scenarioId + " is out of bounds"); + } + + // record the scenario status and meta data + scenarioRecord.scenarioStatus = ScenarioStatus.PREVIOUSLY_SUCCEEDED; + + scenarioRecord.metaData = new ArrayList<>(); + for (int j = 1; j < entries.size(); j++) { + scenarioRecord.metaData.add(entries.get(j)); + } + scenarioRecords.put(scenarioId, scenarioRecord); + } + + } + + private void writeProgressFile() { + if (data.progressLogFile == null) { + return; + } + /* + * We will clear out the old content from the file and replace it with the items + * in the experiment progress log. + */ + final CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder(); + OutputStream out; + try { + out = Files.newOutputStream(data.progressLogFile, StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING); + } catch (final IOException e) { + // re-thrown as runtime exception + throw new RuntimeException(e); + } + writer = new BufferedWriter(new OutputStreamWriter(out, encoder)); + try { + writer.write("scenario"); + for (String metaDatum : data.experimentMetaData) { + writer.write("\t"); + writer.write(metaDatum); + } + writer.write(LINE_SEPARATOR); + + for (Integer scenarioId : scenarioRecords.keySet()) { + ScenarioRecord scenarioRecord = scenarioRecords.get(scenarioId); + if (scenarioRecord.scenarioStatus == ScenarioStatus.PREVIOUSLY_SUCCEEDED) { + + writer.write(scenarioId.toString()); + for (String metaDatum : scenarioRecord.metaData) { + writer.write("\t"); + writer.write(metaDatum); + } + writer.write(LINE_SEPARATOR); + } + } + writer.flush(); + } catch (final IOException e) { + // re-thrown as runtime exception + throw new RuntimeException(e); + } + } + + /** + * Invokes all context consumers. Announces the opening of the experiment to + * subscribed experiment context consumers. Can only be called once. + * + * @throws ContractException {@linkplain NucleusError#DUPLICATE_EXPERIMENT_OPEN} + * if invoked more that once + */ + public synchronized void openExperiment() { + if (experimentStatus != ExperimentStatus.READY) { + throw new ContractException(NucleusError.DUPLICATE_EXPERIMENT_OPEN); + } + experimentStatus = ExperimentStatus.OPENED; + + // handshake with the consumers + for (Consumer consumer : data.contextConsumers) { + consumer.accept(experimentContext); + } + + // announce the opening of the experiment + for (Consumer consumer : experimentOpenConsumers) { + consumer.accept(experimentContext); + } + } + + /** + * Announces the closure of the experiment to subscribed experiment context + * consumers. Closes the experiment progress file if it is being used. + * + * @throws ContractException {@linkplain NucleusError#UNCLOSABLE_EXPERIMENT} if + * the experiment status manager is not currently in + * the open state + */ + public synchronized void closeExperiment() { + + if (experimentStatus != ExperimentStatus.OPENED) { + throw new ContractException(NucleusError.UNCLOSABLE_EXPERIMENT); + } + experimentStatus = ExperimentStatus.CLOSED; + + for (Consumer consumer : experimentCloseConsumers) { + consumer.accept(experimentContext); + } + + try { + if (writer != null) { + writer.close(); + } + } catch (final IOException e) { + throw new RuntimeException(e); + } + } + + @NotThreadSafe + private static class OutputItemConsumerManager { + + private final Map, Set>> baseMap; + private final Map, Set>> workingMap = new LinkedHashMap<>(); + private final Integer scenarioId; + private ExperimentContext experimentContext; + + public OutputItemConsumerManager(ExperimentContext experimentContext, Integer scenarioId, + Map, Set>> consumerMap) { + this.experimentContext = experimentContext; + this.scenarioId = scenarioId; + this.baseMap = new LinkedHashMap<>(consumerMap); + } + + public void handleOutput(Object output) { + if (output == null) { + throw new ContractException(NucleusError.NULL_OUTPUT_ITEM); + } + + Set> consumers = workingMap.get(output.getClass()); + if (consumers == null) { + consumers = new LinkedHashSet<>(); + Class outputClass = output.getClass(); + for (Class c : baseMap.keySet()) { + if (c.isAssignableFrom(outputClass)) { + consumers.addAll(baseMap.get(c)); + } + } + workingMap.put(outputClass, consumers); + } + + for (TriConsumer triConsumer : consumers) { + triConsumer.accept(experimentContext, scenarioId, output); + } + } + } + + /** + * Returns a non-threadsafe consumer of output that will distribute output + * objects to the appropriate class-mapped output consumers. Each simulation + * instance running a scenario should have its own instance of this consumer + * that is confined to the thread running the simulation. This limits the thread + * collisions to the specific output consumer end points. + * + * @throws ContractException + *
      + *
    • {@linkplain NucleusError#NULL_SCENARIO_ID} if + * the scenario id is null
    • + *
    • {@linkplain NucleusError#UNKNOWN_SCENARIO_ID} + * if the scenario id is not in the range [0,scenario + * count)
    • + *
    + */ + public synchronized Consumer getOutputConsumer(Integer scenarioId) { + if (scenarioId == null) { + throw new ContractException(NucleusError.NULL_SCENARIO_ID); + } + ScenarioRecord scenarioRecord = scenarioRecords.get(scenarioId); + + if (scenarioRecord == null) { + throw new ContractException(NucleusError.UNKNOWN_SCENARIO_ID); + } + + return new OutputItemConsumerManager(experimentContext, scenarioId, outputConsumerMap)::handleOutput; + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/ExperimentStatusConsole.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/ExperimentStatusConsole.java new file mode 100644 index 000000000..06b15daa1 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/ExperimentStatusConsole.java @@ -0,0 +1,245 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import java.io.PrintStream; +import java.util.List; +import java.util.function.Consumer; + +import net.jcip.annotations.ThreadSafe; + +/** + * A Consumer<{@link ExperimentContext}> implementation that can be used in + * an Experiment for reporting experiment progress to the console. + */ +@ThreadSafe +public final class ExperimentStatusConsole implements Consumer { + + // this is thread safe so long as there are no mutations + private final StatusConsoleState statusConsoleState; + + private ExperimentStatusConsole(StatusConsoleState statusConsoleState) { + this.statusConsoleState = statusConsoleState; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private StatusConsoleState statusConsoleState = getInitializedStatusConsoleState(); + + private StatusConsoleState getInitializedStatusConsoleState() { + + /* + * initialize the state + */ + StatusConsoleState result = new StatusConsoleState(); + result.setImmediateErrorReporting(false); + result.setReportScenarioProgress(true); + result.setStackTraceReportLimit(100); + result.setLastReportedCompletionPercentage(-1); + + return result; + } + + private StatusConsoleState getCopiedStatusConsoleState() { + + /* + * initialize the state + */ + StatusConsoleState result = new StatusConsoleState(); + result.setImmediateErrorReporting(statusConsoleState.immediateErrorReporting()); + result.setReportScenarioProgress(statusConsoleState.reportScenarioProgress()); + result.setStackTraceReportLimit(statusConsoleState.getStackTraceReportLimit()); + result.setLastReportedCompletionPercentage(statusConsoleState.getLastReportedCompletionPercentage()); + return result; + } + + private Builder() { + + } + + /** + * Builds the ExperimentStatusConsole from the arguments gathered by the + * builder. + */ + public ExperimentStatusConsole build() { + try { + return new ExperimentStatusConsole(getCopiedStatusConsoleState()); + } finally { + statusConsoleState = getInitializedStatusConsoleState(); + } + } + + /** + * Sets the immediate error reporting policy. When true, simulation exceptions + * will be written to the system error console with the scenario id and the + * corresponding stack trace. Defaulted to false. + */ + public Builder setImmediateErrorReporting(final boolean immediateErrorReporting) { + statusConsoleState.setImmediateErrorReporting(immediateErrorReporting); + return this; + } + + /** + * Sets the report scenario progress policy. When true, interim progress of the + * scenarios are reported as 1)the number of scenarios completed, 2)the + * percentage of scenarios completed and 3) as a projected remaining time until + * experiment completion. Defaulted to true. + */ + public Builder setReportScenarioProgress(final boolean reportScenarioProgress) { + statusConsoleState.setReportScenarioProgress(reportScenarioProgress); + return this; + } + + /** + * Sets the maximum number of stack traces that are printed to the console. This + * limit is applied independently to stack traces that are immediately reported + * and those that are reported in the experiment summary. Defaulted to 100. + */ + public Builder setStackTraceReportLimit(int stackTraceReportLimit) { + statusConsoleState.setStackTraceReportLimit(stackTraceReportLimit); + return this; + } + + } + + /* + * Returns the string representation of the value with a prepended "0" for + * numbers less than 10 + */ + private static String getBase60String(int value) { + if (value < 10) { + return "0" + Integer.toString(value); + } + return Integer.toString(value); + } + + /* + * Returns a colon delimited string representation for the number of seconds in + * the form HH:MM:SS + */ + private static String getTimeExpression(double seconds) { + int n = (int) Math.round(seconds); + int h = n / 3600; + n = n % 3600; + int m = n / 60; + int s = n % 60; + + return h + ":" + getBase60String(m) + ":" + getBase60String(s); + } + + private void handleExperimentClose(ExperimentContext experimentContext) { + String timeExpression = getTimeExpression(experimentContext.getElapsedSeconds()); + + int previousProgressCount = experimentContext.getStatusCount(ScenarioStatus.PREVIOUSLY_SUCCEEDED); + int totalSuccessCount = previousProgressCount + experimentContext.getStatusCount(ScenarioStatus.SUCCEDED); + int experimentCount = experimentContext.getScenarioCount(); + int failCount = experimentContext.getStatusCount(ScenarioStatus.FAILED); + + PrintStream printStream = System.out; + if (totalSuccessCount != experimentCount) { + printStream = System.err; + + } + String scenarioString = "scenarios"; + if (experimentCount == 1) { + scenarioString = "scenario"; + } + + printStream.println( + "Experiment completion of " + experimentCount + " " + scenarioString + " in " + timeExpression + ":"); + for (ScenarioStatus scenarioStatus : ScenarioStatus.values()) { + List scenarios = experimentContext.getScenarios(scenarioStatus); + if (!scenarios.isEmpty()) { + printStream.println("\t" + scenarioStatus + " : " + scenarios.size()); + } + } + + int maxFailureCount = statusConsoleState.getStackTraceReportLimit(); + + if (failCount > 0) { + printStream.println("failed scenarios:"); + List failedScenarios = experimentContext.getScenarios(ScenarioStatus.FAILED); + int count = Math.min(maxFailureCount, failedScenarios.size()); + for (int i = 0; i < count; i++) { + Integer scenarioId = failedScenarios.get(i); + Exception e = experimentContext.getScenarioFailureCause(scenarioId).get(); + printStream.println("Sceanrio " + scenarioId + " failed with stackTrace:"); + e.printStackTrace(); + } + if (failCount > maxFailureCount) { + int unprintedFailureCount = failCount - maxFailureCount; + printStream.println("..." + unprintedFailureCount + " more failed scenarios"); + } + } + printStream.println("end of experiment status console"); + } + + private void handleSimulationClose(ExperimentContext experimentContext, int scenarioId) { + + int completionCount = experimentContext.getStatusCount(ScenarioStatus.SUCCEDED) + + experimentContext.getStatusCount(ScenarioStatus.PREVIOUSLY_SUCCEEDED) + + experimentContext.getStatusCount(ScenarioStatus.FAILED); + + double completionProportion = completionCount; + completionProportion /= experimentContext.getScenarioCount(); + completionProportion *= 100; + + int percentComplete = (int) completionProportion; + + boolean reportToConsole = false; + + if (percentComplete > statusConsoleState.getLastReportedCompletionPercentage()) { + statusConsoleState.setLastReportedCompletionPercentage(percentComplete); + reportToConsole = true; + } + + reportToConsole &= statusConsoleState.reportScenarioProgress(); + + if (statusConsoleState.immediateErrorReporting()) { + + ScenarioStatus scenarioStatus = experimentContext.getScenarioStatus(scenarioId).get(); + if (scenarioStatus == ScenarioStatus.FAILED) { + boolean printStackTrace = false; + + if (statusConsoleState.getImmediateStackTraceCount() < statusConsoleState.getStackTraceReportLimit()) { + printStackTrace = true; + statusConsoleState.incrementImmediateStackTraceCount(); + } + + if (printStackTrace) { + Exception failureCause = experimentContext.getScenarioFailureCause(scenarioId).get(); + System.err.println("Simulation failure for scenario " + scenarioId); + failureCause.printStackTrace(); + } + } + } + + if (reportToConsole) { + int executedCountForThisRun = experimentContext.getStatusCount(ScenarioStatus.SUCCEDED) + + experimentContext.getStatusCount(ScenarioStatus.FAILED); + double averageTimePerExecution = experimentContext.getElapsedSeconds() / executedCountForThisRun; + int remainingExecutions = experimentContext.getScenarioCount() - completionCount; + double expectedRemainingTime = Math.round(averageTimePerExecution * remainingExecutions); + String timeExpression = getTimeExpression(expectedRemainingTime); + String scenarioString = "scenarios"; + if (experimentContext.getScenarioCount() == 1) { + scenarioString = "scenario"; + } + System.out.println(completionCount + " of " + experimentContext.getScenarioCount() + " " + scenarioString + + ", " + percentComplete + "% complete. Expected experiment completion in " + timeExpression); + } + + } + + /** + * Initializes this ExperimentStatusConsole, which registers for simulation and + * experiment close events. + */ + @Override + public void accept(ExperimentContext experimentContext) { + experimentContext.subscribeToSimulationClose(this::handleSimulationClose); + experimentContext.subscribeToExperimentClose(this::handleExperimentClose); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/FunctionalDimension.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/FunctionalDimension.java new file mode 100644 index 000000000..ffef0d8a4 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/FunctionalDimension.java @@ -0,0 +1,93 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +/** + * A Dimension implementation based on Functions as level implementations. + */ +public final class FunctionalDimension implements Dimension { + + private static class Data { + List metaData = new ArrayList<>(); + List>> levels = new ArrayList<>(); + + public Data() { + } + + public Data(Data data) { + metaData.addAll(data.metaData); + levels.addAll(data.levels); + } + } + + /** + * Returns a builder class for Dimension + */ + public static Builder builder() { + return new Builder(); + } + + /** + * A builder class for Dimension + */ + public static class Builder { + + private Data data = new Data(); + + private Builder() { + } + + /** + * Returns a Dimension from the collected data + */ + public FunctionalDimension build() { + return new FunctionalDimension(new Data(data)); + } + + /** + * Adds a level function to the dimension. Each such function consumes a + * DimensionContext of PluginDataBuilders and returns a list of scenario-level + * meta data that describes the changes performed on the PluginDataBuilders. The + * list of meta data is aligned to the experiment level meta data contained in + * the dimension and must contain the same number of elements. + */ + public Builder addLevel(Function> memberGenerator) { + data.levels.add(memberGenerator); + return this; + } + + /** + * Adds an experiment-level string meta datum value that describes the + * corresponding scenario-level meta data returned by the individual levels of + * this dimension. + */ + public Builder addMetaDatum(String idValue) { + data.metaData.add(idValue); + return this; + } + } + + private FunctionalDimension(Data data) { + this.data = data; + } + + private final Data data; + + @Override + public List getExperimentMetaData() { + return new ArrayList<>(data.metaData); + } + + @Override + public int levelCount() { + return data.levels.size(); + } + + @Override + public List executeLevel(DimensionContext dimensionContext, int level) { + return data.levels.get(level).apply(dimensionContext); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/IdentifiableFunction.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/IdentifiableFunction.java new file mode 100644 index 000000000..72d47f152 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/IdentifiableFunction.java @@ -0,0 +1,56 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import java.util.function.Function; + +import util.errors.ContractException; + +public final class IdentifiableFunction { + + private Object functionId; + + private Function function; + + public IdentifiableFunction(Object functionId, Function function) { + if (functionId == null) { + throw new ContractException(NucleusError.NULL_FUNCTION_ID); + } + if (function == null) { + throw new ContractException(NucleusError.NULL_FUNCTION); + } + + this.functionId = functionId; + this.function = function; + } + + public Function getFunction() { + return function; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((functionId == null) ? 0 : functionId.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof IdentifiableFunction)) { + return false; + } + IdentifiableFunction other = (IdentifiableFunction) obj; + if (functionId == null) { + if (other.functionId != null) { + return false; + } + } else if (!functionId.equals(other.functionId)) { + return false; + } + return true; + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/IdentifiableFunctionMap.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/IdentifiableFunctionMap.java new file mode 100644 index 000000000..334adc4d9 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/IdentifiableFunctionMap.java @@ -0,0 +1,98 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Function; + +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +@Immutable +public final class IdentifiableFunctionMap { + private static class Data { + private Map> functionMap = new LinkedHashMap<>(); + + public Data() { + } + + public Data(Data data) { + functionMap.putAll(data.functionMap); + } + } + + /** + * Returns a builder instance that will build an IdentifiableFunctionMap of the + * given type + */ + public static Builder builder(Class type) { + if (type == null) { + throw new ContractException(NucleusError.NULL_CLASS_REFERENCE); + } + return new Builder<>(); + } + + public static class Builder { + private Data data = new Data<>(); + + private Builder() { + } + + public IdentifiableFunctionMap build() { + return new IdentifiableFunctionMap<>(new Data<>(data)); + } + + /** + * Puts the function at the id, replacing any existing function. + * + * @throws ContractException + *
      + *
    • {@linkplain NucleusError#NULL_FUNCTION_ID} if + * the function id is null
    • + *
    • {@linkplain NucleusError#NULL_FUNCTION} if the + * function is null
    • + *
    + */ + public Builder put(Object id, Function function) { + if (id == null) { + throw new ContractException(NucleusError.NULL_FUNCTION_ID); + } + + if (function == null) { + throw new ContractException(NucleusError.NULL_FUNCTION); + } + + data.functionMap.put(id, new IdentifiableFunction<>(id, function)); + return this; + } + + } + + /** + * Gets the function associated with the given id + * + * @throws ContractException + *
      + *
    • {@linkplain NucleusError#NULL_FUNCTION_ID} if + * the function id is null
    • + *
    • {@linkplain NucleusError#UNKNOWN_FUNCTION_ID} + * if the function id is not in this map
    • + *
    + */ + public IdentifiableFunction get(Object id) { + if (id == null) { + throw new ContractException(NucleusError.NULL_FUNCTION_ID); + } + IdentifiableFunction result = data.functionMap.get(id); + if (result == null) { + throw new ContractException(NucleusError.UNKNOWN_FUNCTION_ID); + } + return result; + } + + private final Data data; + + private IdentifiableFunctionMap(Data data) { + this.data = data; + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/NucleusError.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/NucleusError.java new file mode 100644 index 000000000..3751906c5 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/NucleusError.java @@ -0,0 +1,103 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import util.errors.ContractError; +import util.errors.ContractException; + +/** + * An enumeration supporting {@link ContractException} that acts as a general + * description of the exception. + */ +public enum NucleusError implements ContractError { + ACCESS_VIOLATION("A contributed behavior is accessing locked state during a state change"), + DATA_MANAGER_ACCESS_VIOLATION( + "A data manager is attempting to access another data manager that is incompatible with the plugin dependencies"), + AMBIGUOUS_DATA_MANAGER_CLASS("Multiple data manager matches found"), + AMBIGUOUS_PLUGIN_DATA_BUILDER_CLASS("Multiple plugin data builder matches found"), + AMBIGUOUS_PLUGIN_DATA_CLASS("Multiple plugin data object matches found"), + CIRCULAR_PLUGIN_DEPENDENCIES("Circular plugin dependencies were found"), + DATA_MANAGER_DUPLICATE_INITIALIZATION("Data manager was already initialized"), + DATA_MANAGER_INITIALIZATION_FAILURE( + "Data manager base class was not properly initialized, be sure to call super()"), + DIMENSION_LABEL_MISMATCH( + "The number of scenario labels provided by a dimension level do not match the number of labels in the dimension's meta data"), + DUPLICATE_DATA_MANAGER_TYPE("Duplicate data manager type"), DUPLICATE_DATA_VIEW_TYPE("Duplicate data view type"), + DUPLICATE_EVENT_SUBSCRIPTION("An event subscription duplicates an existing event subscription"), + DUPLICATE_META_SUBSCRIPTION("An meta subscription duplicates an existing meta subscription"), + DUPLICATE_PLAN_PRIORITY( + "A plan priority was duplicated during the initialization phase of the simulation when plans from a previous simulation execution can be added to the current simulation"), + DUPLICATE_EXPERIMENT_OPEN("Duplicate opening of experiment"), + DUPLICATE_LABELER_ID_IN_EVENT_LABELER("Duplicate labeler id in labeler"), + DUPLICATE_PLAN_KEY("There is an existing plan currently scheduled with the same key"), + DUPLICATE_PLUGIN("There are two or more plugins with the same id"), + INCOMPATIBLE_SCEANARIO_PROGRESS("The scenario progress file is incompatible with the current experiment"), + LABLER_GENERATED_LABEL_WITH_INCORRECT_EVENT_CLASS("Event labler generated a label with an incorrect event class"), + LABLER_GENERATED_LABEL_WITH_INCORRECT_ID("Event labler generated a label with an incorrect event labeler id"), + LABLER_GENERATED_LABEL_WITH_INCORRECT_PRIMARY_KEY("Event labler generated a label with an incorrect primary key"), + MISSING_PLUGIN("A plugin is missing"), NEGATIVE_THREAD_COUNT("Negative thread count"), + NON_EXISTANT_SCEANARIO_PROGRESS( + "The scenario progress file does not exist, but is required when continuation from progress file is chosen"), + NULL_ACTOR_CONTEXT_CONSUMER("Null actor context consumer"), + NULL_REPORT_CONTEXT_CONSUMER("Null report context consumer"), NULL_ACTOR_ID("Null actor id"), + NULL_BASE_DATE("Null base date"), NULL_DATA_MANAGER("Null data manager"), NULL_DATA_VIEW("Null data view"), + NULL_DATA_MANAGER_CLASS("Null data manager class"), NULL_DATA_VIEW_CLASS("Null data view class"), + NULL_DATA_MANAGER_CONTEXT_CONSUMER("Null data manager context consumer"), + NULL_DATA_MANAGER_STATE_CONTEXT_CONSUMER("Null data manager state context consumer"), + NULL_REPORT_STATE_CONTEXT_CONSUMER("Null report state context consumer"), + NULL_ACTOR_STATE_CONTEXT_CONSUMER("Null actor state context consumer"), + NULL_CLASS_REFERENCE("Null class reference"), NULL_EVENT("Event is null"), NULL_EVENT_CLASS("Null event class"), + NULL_EVENT_CONSUMER("Null event consumer"), NULL_EVENT_LABEL("Null event label"), + NULL_EVENT_LABEL_FUNCTION("Null event label function"), NULL_EVENT_LABEL_KEY("Null event label key"), + NULL_EVENT_LABELER("Null event labeler"), NULL_EVENT_LABELER_ID("Null event labeler id"), + NULL_EXPERIMENT_CONTEXT_CONSUMER("Null experiment context consumer"), + NULL_LABELER_ID_IN_EVENT_LABEL("Event label returns a null event labeler id"), + NULL_LABELER_ID_IN_EVENT_LABELER("Event labeler returns a null id"), NULL_META_DATA("Null meta data"), + NULL_OUTPUT_HANDLER("Null output item handler"), NULL_OUTPUT_ITEM("Null output"), NULL_PLAN("Null plan"), + NULL_PLAN_CONSUMER("Null plan consumer"), NULL_PLAN_DATA("Null plan data"), + NULL_PLAN_QUEUE_DATA("Null plan queue data"), NULL_PLANNER("Null planner type"), NULL_PLAN_KEY("Null planning key"), + NULL_PLUGIN("Null plugin"), NULL_PLUGINS("Null collection of plugins"), NULL_PLUGIN_CONTEXT("Null plugin context"), + NULL_PLUGIN_DATA("Null plugin data"), + NULL_PLUGIN_DATA_BUILDER("A null plugin data builder instance was added to the context"), + NULL_PLUGIN_DATA_CLASS("Null plugin data class"), NULL_PLUGIN_ID("Null plugin id"), + NULL_PLUGIN_INITIALIZER("Null plugin initializer"), NULL_PRIMARY_KEY_VALUE("Null primary key value"), + NULL_SCENARIO_ID("Null scenario id"), NULL_SCENARIO_PROGRESS_FILE("Null scenario progress file"), + NULL_SIMULATION_CONTEXT("Null simulation context"), NULL_SIMULATION_TIME("Null simulation time"), + PAST_PLANNING_TIME("Plan execution time is in the past"), + PLANNING_QUEUE_CLOSED("The planning phase of the simulation is over and plans may not be added"), + PLANNING_QUEUE_ARRIVAL_INVALID( + "The planning queue arrival id must exceed the arrival id values for all stored plans"), + PLANNING_QUEUE_TIME("A planning time for a stored plan happens before the start time of the simulation"), + PLUGIN_INITIALIZATION_CLOSED("Plugin context is no longer valid"), + REPEATED_EXECUTION("Attempted repeat execution of simulation engine"), + REPORT_ATTEMPTING_MUTATION("A report is attempting to mutate data state"), + DATA_MANAGER_ATTEMPTING_MUTATION("A data manager is attempting to mutate data state after event flow has stopped"), + SCENARIO_CANNOT_BE_EXECUTED("Scenario cannot be executed"), + SCENARIO_FAILED("Scenario failed to complete execution"), + MISSING_SIM_HALT_TIME( + "Simulation halt time must be set when simulation state is being recorded at the end of a simulation run"), + SIM_HALT_TIME_TOO_EARLY( + "When a simulation halt time is non-negative, it must be greater than or equal to the start time of the simulation"), + TERMINAL_PLAN_DATA_ACCESS_VIOLATION("There is no access to terminal plan data until the simulation closes"), + UNCLOSABLE_EXPERIMENT("Cannot close an experiment not in the open state"), + UNKNOWN_ACTOR_ID("Actor id does not correspond to a known actor"), UNKNOWN_DATA_MANAGER("Unknown data manager"), + UNKNOWN_DATA_VIEW("Unknown data view"), + UNKNOWN_EVENT_LABELER("The labeler id an event label does not match a registered event labeler"), + UNKNOWN_PLUGIN_DATA_BUILDER_CLASS("The plugin data builder class was not found"), + UNKNOWN_PLUGIN_DATA_CLASS("The plugin data class was not found"), UNKNOWN_SCENARIO_ID("Unknown scenario id"), + UNREADABLE_SCEANARIO_PROGRESS("The scenario progress file is unreadable"), + OBSERVATION_EVENT_IMPROPER_RELEASE( + "An observation event is being released during a mutation by a data manager without the use of a corresponding mutation event"), + UNKNOWN_FUNCTION_ID("Unknown event function id"), NULL_FUNCTION_ID("Null function id"), + NULL_FUNCTION_VALUE("Null event function value"), NULL_FUNCTION("Null function"), + NULL_IDENTIFIABLE_FUNCTION("Null identifiable function"), NULL_EVENT_FILTER("Null event filter"),; + + private final String description; + + private NucleusError(final String description) { + this.description = description; + } + + @Override + public String getDescription() { + return description; + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/Plan.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/Plan.java new file mode 100644 index 000000000..6b6e4d4f0 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/Plan.java @@ -0,0 +1,152 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import java.util.function.Consumer; + +public class Plan { + + private static class Data { + private double time; + private Consumer callbackConsumer; + private boolean active = true; + private PlanData planData; + private Object key; + + public Data() { + } + + public Data(Data data) { + time = data.time; + callbackConsumer = data.callbackConsumer; + active = data.active; + planData = data.planData; + key = data.key; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Data [time="); + builder.append(time); + builder.append(", callbackConsumer="); + builder.append(callbackConsumer); + builder.append(", active="); + builder.append(active); + builder.append(", planData="); + builder.append(planData); + builder.append(", key="); + builder.append(key); + builder.append("]"); + return builder.toString(); + } + + } + + /** + * Returns a new instance of the Builder class + */ + public static Builder builder(Class classReference) { + return new Builder(); + } + + public static class Builder { + private Data data = new Data<>(); + + private Builder() { + + } + + /** + * Constructs a new EventLabel from the collected data. + */ + public Plan build() { + return new Plan<>(new Data<>(data)); + } + + /** + * Sets the time of the plan. + */ + public Builder setTime(double time) { + data.time = time; + return this; + } + + /** + * Sets the active state for the plan. The planning queue continues execution + * while there are active plans present. Passive plans should be used for + * recurring, non-event driven tasks that do not require the continued execution + * of the simulation.Defaults to true; + */ + public Builder setActive(boolean active) { + data.active = active; + return this; + } + + /** + * Sets the auxiliary plan data that will be used when the simulation is + * recording the content of the planning queue for use in a continued + * simulation. A non-null value is required when adding a plan that is scheduled + * at or after the simulation halt time when the simulation has been instructed + * to record state on halt. Defaults to false; + */ + public Builder setPlanData(PlanData planData) { + data.planData = planData; + return this; + } + + /** + * Sets the key value for this plan. The key value is optional and should only + * be used if the plan may be cancelled or retrieved before the plan time since + * it incurs a significant overhead memory cost. Defaults to null. + */ + public Builder setKey(Object key) { + data.key = key; + return this; + } + + /** + * Sets the required call back behavior for this plan. The call back is executed + * when the plan reaches the top of the queue and the simulation's time is set + * to the plan's time. No default is allowed. + */ + public Builder setCallbackConsumer(Consumer callbackConsumer) { + data.callbackConsumer = callbackConsumer; + return this; + } + } + + private final Data data; + + private Plan(Data data) { + this.data = data; + } + + public double getTime() { + return data.time; + } + + public Consumer getCallbackConsumer() { + return data.callbackConsumer; + } + + public boolean isActive() { + return data.active; + } + + public PlanData getPlanData() { + return data.planData; + } + + public Object getKey() { + return data.key; + } + + @Override + public String toString() { + StringBuilder builder2 = new StringBuilder(); + builder2.append("Plan [data="); + builder2.append(data); + builder2.append("]"); + return builder2.toString(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/PlanData.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/PlanData.java new file mode 100644 index 000000000..f642d97a6 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/PlanData.java @@ -0,0 +1,16 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import net.jcip.annotations.ThreadSafe; + +@ThreadSafe +public interface PlanData { + + @Override + public int hashCode(); + + @Override + public boolean equals(Object obj); + + @Override + public String toString(); +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/PlanQueueData.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/PlanQueueData.java new file mode 100644 index 000000000..40582729f --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/PlanQueueData.java @@ -0,0 +1,274 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import util.errors.ContractException; + +/** + * Class supporting serialization of plans + */ +public final class PlanQueueData { + + private static class Data { + private double time; + private boolean active = true; + private Object key; + private PlanData planData; + private Planner planner; + private int plannerId; + private long arrivalId; + + public Data() { + } + + public Data(Data data) { + time = data.time; + active = data.active; + key = data.key; + planData = data.planData; + planner = data.planner; + plannerId = data.plannerId; + arrivalId = data.arrivalId; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (active ? 1231 : 1237); + result = prime * result + (int) (arrivalId ^ (arrivalId >>> 32)); + result = prime * result + ((key == null) ? 0 : key.hashCode()); + result = prime * result + ((planData == null) ? 0 : planData.hashCode()); + result = prime * result + ((planner == null) ? 0 : planner.hashCode()); + result = prime * result + plannerId; + long temp; + temp = Double.doubleToLongBits(time); + result = prime * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + if (active != other.active) { + return false; + } + if (arrivalId != other.arrivalId) { + return false; + } + if (key == null) { + if (other.key != null) { + return false; + } + } else if (!key.equals(other.key)) { + return false; + } + if (planData == null) { + if (other.planData != null) { + return false; + } + } else if (!planData.equals(other.planData)) { + return false; + } + if (planner != other.planner) { + return false; + } + if (plannerId != other.plannerId) { + return false; + } + if (Double.doubleToLongBits(time) != Double.doubleToLongBits(other.time)) { + return false; + } + return true; + } + } + + /** + * Static constructor for the builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder class for PlanQueueData + */ + public static class Builder { + private Data data = new Data(); + + private Builder() { + + } + + private void validate() { + + if (data.planData == null) { + throw new ContractException(NucleusError.NULL_PLAN_DATA); + } + + if (data.planner == null) { + throw new ContractException(NucleusError.NULL_PLANNER); + } + } + + /** + * Returns the PlanQueueData built from the contributed data. + * + * @throws ContractException + *
      + *
    • {@linkplain NucleusError#NULL_PLAN_DATA} if the + * plan data is null
    • + *
    • {@linkplain NucleusError#NULL_PLANNER} if the + * planner type is null
    • + *
    + */ + public PlanQueueData build() { + validate(); + return new PlanQueueData(new Data(data)); + } + + /** + * Set the plan time. Defaults to zero. + */ + public Builder setTime(double time) { + data.time = time; + return this; + } + + /** + * Set the active state. Defaults to true. + */ + public Builder setActive(boolean active) { + data.active = active; + return this; + } + + /** + * Set the plan's key. Defaults to null. + */ + public Builder setKey(Object key) { + data.key = key; + return this; + } + + /** + * Set the plan's data. Defaults to null. + */ + public Builder setPlanData(PlanData planData) { + data.planData = planData; + return this; + } + + /** + * Set the plan's planner type. Defaults to null. + */ + public Builder setPlanner(Planner planner) { + data.planner = planner; + return this; + } + + /** + * Set the plan's planner id. Defaults to zero. + */ + public Builder setPlannerId(int plannerId) { + data.plannerId = plannerId; + return this; + } + + /** + * Set the plan's queue arrival id. Defaults to zero. + */ + public Builder setArrivalId(long arrivalId) { + data.arrivalId = arrivalId; + return this; + } + + } + + private final Data data; + + private PlanQueueData(Data data) { + this.data = data; + } + + /** + * Returns the time for the plan + */ + public double getTime() { + return data.time; + } + + /** + * Returns the active state for the plan + */ + public boolean isActive() { + return data.active; + } + + /** + * Returns the key for the plan + */ + public Object getKey() { + return data.key; + } + + /** + * Returns the plan data for the plan + */ + public PlanData getPlanData() { + return data.planData; + } + + /** + * Returns the planner type for the plan + */ + public Planner getPlanner() { + return data.planner; + } + + /** + * Returns the planner id for the plan + */ + public int getPlannerId() { + return data.plannerId; + } + + /** + * Returns the arrival id for the plan + */ + public long getArrivalId() { + return data.arrivalId; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof PlanQueueData)) { + return false; + } + PlanQueueData other = (PlanQueueData) obj; + if (data == null) { + if (other.data != null) { + return false; + } + } else if (!data.equals(other.data)) { + return false; + } + return true; + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/Planner.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/Planner.java new file mode 100644 index 000000000..cf706eea6 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/Planner.java @@ -0,0 +1,10 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +public enum Planner { + /* + * Defines the type of actor that has created a plan. THE ORDER OF THIS ENUM IS + * CRITICAL TO THE FUNCTION OF THE SIMULATION! + */ + DATA_MANAGER, ACTOR, REPORT; + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/Plugin.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/Plugin.java new file mode 100644 index 000000000..f29b15768 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/Plugin.java @@ -0,0 +1,245 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Consumer; + +import net.jcip.annotations.ThreadSafe; +import util.errors.ContractException; + +/** + * A plugin is the main compositional element of an experiment. Plugins contain + * the initial data state for simulations and add actors and data managers to + * each simulation at the startup. + */ +@ThreadSafe +public final class Plugin { + + private static class Data { + private PluginId pluginId; + private Set pluginDependencies = new LinkedHashSet<>(); + private List pluginDatas = new ArrayList<>(); + private Consumer initializer; + + public Data() { + } + + public Data(Data data) { + pluginId = data.pluginId; + pluginDependencies.addAll(data.pluginDependencies); + pluginDatas.addAll(data.pluginDatas); + initializer = data.initializer; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + LinkedHashSet set = new LinkedHashSet<>(pluginDatas); + result = prime * result + set.hashCode(); + result = prime * result + pluginDependencies.hashCode(); + result = prime * result + pluginId.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + LinkedHashSet set = new LinkedHashSet<>(pluginDatas); + LinkedHashSet otherSet = new LinkedHashSet<>(other.pluginDatas); + if (!set.equals(otherSet)) { + return false; + } + if (!pluginDependencies.equals(other.pluginDependencies)) { + return false; + } + if (!pluginId.equals(other.pluginId)) { + return false; + } + return true; + } + + } + + /** + * Returns an new instance of the Builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * A builder class for Plugin + */ + public static class Builder { + private Builder() { + } + + private void validate() { + if (data.pluginId == null) { + throw new ContractException(NucleusError.NULL_PLUGIN_ID); + } + } + + private Data data = new Data(); + + /** + * Returns the plugin formed by the inputs collected by this builder. + * + * @throws ContractException {@linkplain NucleusError#NULL_PLUGIN_ID} if the + * plugin id was not set or set to null + */ + public Plugin build() { + validate(); + return new Plugin(new Data(data)); + } + + public Builder setPluginId(PluginId pluginId) { + if (pluginId == null) { + throw new ContractException(NucleusError.NULL_PLUGIN_ID); + } + data.pluginId = pluginId; + return this; + } + + /** + * Establishes that the plugin using this plugin context depends upon the given + * plugin. Plugin dependencies are gathered by nucleus and used to determine + * that the simulation is well formed. Nucleus requires that: 1) there are no + * duplicate plugins, 2)there are no null plugins, 3)there are no missing + * plugins, and 4) the plugin dependencies form an acyclic, directed graph. + * Nucleus will initialize each plugin primarily in the order dictated by this + * graph and secondarily in the order each plugin was contributed to a + * simulation or experiment. + * + * @throws ContractException {@link NucleusError#NULL_PLUGIN_ID} if the plugin + * id is null + */ + public Builder addPluginDependency(PluginId pluginId) { + if (pluginId == null) { + throw new ContractException(NucleusError.NULL_PLUGIN_ID); + } + data.pluginDependencies.add(pluginId); + return this; + } + + /** + * Adds a plugin data object. Plugin data object must be thread-safe. It is best + * practice for a plugin data to be properly immutable: 1) its state cannot be + * altered after construction, 2) all its member fields are declared final and + * 3) it does not pass any reference to itself during its construction. + * + * @throws ContractException {@linkplain NucleusError#NULL_PLUGIN_DATA} if the + * plugin data is null + */ + public Builder addPluginData(PluginData pluginData) { + if (pluginData == null) { + throw new ContractException(NucleusError.NULL_PLUGIN_DATA); + } + data.pluginDatas.add(pluginData); + return this; + } + + /** + * Sets the consumer of plugin context that interacts with the simulation by + * adding actors and data mangers to the simulation on the simulation's startup. + * The initializer must be thread-safe. It is best practice for the initializer + * to be stateless. + * + * @throws ContractException {@linkplain NucleusError#NULL_PLUGIN_INITIALIZER} + * if the initializer is null + */ + public Builder setInitializer(Consumer initializer) { + if (initializer == null) { + throw new ContractException(NucleusError.NULL_PLUGIN_INITIALIZER); + } + data.initializer = initializer; + return this; + } + + } + + private final Data data; + + private Plugin(Data data) { + this.data = data; + } + + /** + * Returns the plugin id of this plugin. + */ + public final PluginId getPluginId() { + return data.pluginId; + } + + /** + * Returns the plugin id values of the other plugins that this plugin depends on + * to function correctly. These dependencies for a directed acyclic graph and + * determine the initialization order of plugins. + */ + public final Set getPluginDependencies() { + return new LinkedHashSet<>(data.pluginDependencies); + } + + /** + * Returns the set thread-safe plugin data objects collected by this plugin's + * builder. + */ + public final List getPluginDatas() { + return new ArrayList<>(data.pluginDatas); + } + + /** + * Returns a thread-safe consumer of plugin context. The initializer interacts + * with the simulation by adding actors and data mangers to the simulation on + * the simulation's startup. + */ + public final Optional> getInitializer() { + return Optional.ofNullable(data.initializer); + } + + /** + * Implementation consistent with equals() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + return result; + } + + /** + * Two Plugins are equal if and only if their plugin ids, plugin dependencies + * and plugin datas are equal. INITIALIZERS ARE NOT COMPARED. Initialization + * behavior can only be confirmed by executing the plugin via a simulation + * instance. + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Plugin)) { + return false; + } + Plugin other = (Plugin) obj; + if (data == null) { + if (other.data != null) { + return false; + } + } else if (!data.equals(other.data)) { + return false; + } + return true; + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/PluginContext.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/PluginContext.java new file mode 100644 index 000000000..a74549e13 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/PluginContext.java @@ -0,0 +1,83 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +import util.errors.ContractException; + +/** + * A plugin context provides plugin's the ability to add actors and data + * managers to the initialization of each simulation instance(scenario) in an + * experiment. It provides the set of plugin data objects gathered from the + * plugins that compose the experiment. + */ +public final class PluginContext { + private final Simulation simulation; + + protected PluginContext(Simulation simulation) { + this.simulation = simulation; + } + + /** + * Adds a data manager to the simulation. + * + * @throws ContractException {@link NucleusError#PLUGIN_INITIALIZATION_CLOSED} + * if plugin initialization is over + */ + public void addDataManager(DataManager dataManager) { + simulation.addDataManagerForPlugin(dataManager); + } + + /** + * Adds an actor to the simulation. + * + * @throws ContractException {@link NucleusError#PLUGIN_INITIALIZATION_CLOSED} + * if plugin initialization is over + */ + public ActorId addActor(Consumer init) { + return simulation.addActorForPlugin(init); + } + + /** + * Adds a report to the simulation. + * + * @throws ContractException + *
      + *
    • {@link NucleusError#PLUGIN_INITIALIZATION_CLOSED} + * if plugin initialization is over
    • + *
    • {@link NucleusError#NULL_REPORT_CONTEXT_CONSUMER} + * if the consumer is null
    • + *
    + */ + public void addReport(Consumer init) { + simulation.addReportForPlugin(init); + } + + /** + * Returns the plugin data object associated with the given class reference + * + * @throws ContractException + *
      + *
    • {@linkplain NucleusError#NULL_PLUGIN_DATA_CLASS} + * if the class reference is null
    • + *
    • {@linkplain NucleusError#AMBIGUOUS_PLUGIN_DATA_CLASS} + * if more than one plugin data object matches the + * class reference
    • + *
    + */ + public Optional getPluginData(Class pluginDataClass) { + return simulation.getPluginData(pluginDataClass); + } + + /** + * Returns the plugin data objects associated with the given class reference + * + * @throws ContractException {@linkplain NucleusError#NULL_PLUGIN_DATA_CLASS} if + * the class reference is null + */ + public List getPluginDatas(Class pluginDataClass) { + return simulation.getPluginDatas(pluginDataClass); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/PluginData.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/PluginData.java new file mode 100644 index 000000000..158698c3b --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/PluginData.java @@ -0,0 +1,51 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import net.jcip.annotations.ThreadSafe; + +/** + * Plugin data objects are thread-safe containers of initialization state for + * the scenarios of a simulation. Plugin data are contributed to plugins which + * are in turn added to the experiment. The experiment generates multiple + * scenarios by having the experiment dimensions alter copies of the plugin + * datas and then distributing those alternate copies to multiple simulation + * instances. It is best practice for a plugin data to be properly immutable: 1) + * its state cannot be altered after construction, 2) all its member fields are + * declared final and 3) it does not pass any reference to itself during its + * construction. Plugin data classes require a corresponding PluginDataBuilder + * that builds the plugin. + */ +/* + * start code_ref=plugin_data_interface|code_cap=The PluginData interface + * indicates that its implementors are immutable. Plugin data objects are shared + * between all simulation instances and thus must be thread safe. It introduces + * a single method used to copy plugin datas during the experiment process. + */ +@ThreadSafe +public interface PluginData { + /** + * Returns a PluginDataBuilder that can build the plugin data. The returned + * builder should be initialized with this plugin data object's internal state + * such that invocation of pluginData.getCloneBuilder().build() will generate a + * copy of the current plugin. + */ + public PluginDataBuilder getCloneBuilder(); + + @Override + public int hashCode(); + + /** + * Plugin datas are equal if they are implicitly equal. They contain the same + * implicit information without regard to order. + */ + @Override + public boolean equals(Object obj); + + /** + * A string representation of the plugin data implicit data and reflects the + * order of addition of the data. Equal plugin datas have equal strings in terms + * of content, but not necessarily order. + */ + @Override + public String toString(); +} +/* end */ diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/PluginDataBuilder.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/PluginDataBuilder.java new file mode 100644 index 000000000..407c29d2f --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/PluginDataBuilder.java @@ -0,0 +1,17 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +/** + * PlugingDataBuilder is an interface for the builders of plugins. + */ +/* + * start code_ref=plugin_data_plugin_builder|code_cap=Every plugin data class + * has a corresponding builder class to aid in the experiment's generation of + * alternate scenarios. + */ +public interface PluginDataBuilder { + /** + * Returns a plugin data + */ + public PluginData build(); +} +/* end */ \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/PluginDataBuilderContainer.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/PluginDataBuilderContainer.java new file mode 100644 index 000000000..e78a4dd4a --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/PluginDataBuilderContainer.java @@ -0,0 +1,27 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import util.errors.ContractException; + +/** + * A container for PluginDataBuilder instances. + */ +public interface PluginDataBuilderContainer { + + /** + * Returns the stored item matching the given class reference. Class reference + * matching is performed against all plugin data builders in this container. A + * ContractException is thrown if there is not exactly one matching instance. + * + * @throws ContractException + *
      + *
    • {@linkplain NucleusError#AMBIGUOUS_PLUGIN_DATA_BUILDER_CLASS} + * if more than one plugin data builder matches the + * given class reference
    • + *
    • {@linkplain NucleusError#UNKNOWN_PLUGIN_DATA_BUILDER_CLASS} + * if no plugin data builder matches the given class + * reference
    • + *
    + */ + public T getPluginDataBuilder(Class classRef); + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/PluginDataBuilderContext.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/PluginDataBuilderContext.java new file mode 100644 index 000000000..d55f1e2ef --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/PluginDataBuilderContext.java @@ -0,0 +1,88 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import util.errors.ContractException; + +/** + * An implementor of PluginDataBuilderContainer. + */ +public final class PluginDataBuilderContext implements PluginDataBuilderContainer { + + private Map, PluginDataBuilder> pluginDataBuilderBaseMap = new LinkedHashMap<>(); + private Map, PluginDataBuilder> pluginDataBuilderWorkingMap = new LinkedHashMap<>(); + + private PluginDataBuilderContext() { + } + + /** + * A builder class for DimensionContext + */ + public static class Builder { + private Builder() { + } + + private Map, PluginDataBuilder> pluginDataBuilderMap = new LinkedHashMap<>(); + + /** + * Returns the DimensionContext instance composed from the inputs to this + * builder. + */ + public PluginDataBuilderContext build() { + PluginDataBuilderContext result = new PluginDataBuilderContext(); + result.pluginDataBuilderBaseMap.putAll(pluginDataBuilderMap); + return result; + } + + /** + * Given a plugin Data, will add it and its clone builder to the internal map in + * this class + * + * @throws ContractException {@linkplain NucleusError#NULL_PLUGIN_DATA_BUILDER} + * if the plugin data builder is null + */ + public Builder add(T t) { + if (t == null) { + throw new ContractException(NucleusError.NULL_PLUGIN_DATA_BUILDER); + } + pluginDataBuilderMap.put(t.getClass(), t); + return this; + } + } + + /** + * Returns a typed Builder instance for DimensionContext + */ + public static Builder builder() { + return new Builder(); + } + + @Override + public T getPluginDataBuilder(Class classRef) { + + PluginDataBuilder pluginDataBuilder = pluginDataBuilderWorkingMap.get(classRef); + if (pluginDataBuilder == null) { + List> candidates = new ArrayList<>(); + for (Class c : pluginDataBuilderBaseMap.keySet()) { + if (classRef.isAssignableFrom(c)) { + candidates.add(c); + } + } + if (candidates.isEmpty()) { + throw new ContractException(NucleusError.UNKNOWN_PLUGIN_DATA_BUILDER_CLASS); + } + if (candidates.size() > 1) { + throw new ContractException(NucleusError.AMBIGUOUS_PLUGIN_DATA_BUILDER_CLASS); + } + + pluginDataBuilder = pluginDataBuilderBaseMap.get(candidates.get(0)); + pluginDataBuilderWorkingMap.put(classRef, pluginDataBuilder); + } + + return classRef.cast(pluginDataBuilder); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/PluginId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/PluginId.java new file mode 100644 index 000000000..9583169ce --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/PluginId.java @@ -0,0 +1,23 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import net.jcip.annotations.ThreadSafe; + +/** + * Marker interface for plugin identification. Plugins added to an experiment + * should have unique ids. + */ +@ThreadSafe +public interface PluginId { + /** + * Implementationn consistent with equals() + */ + @Override + public int hashCode(); + + /** + * Two plugin ids are equal if and only if they represent the same plugin. + * Plugin ids are generally implemented as static instances. + */ + @Override + public boolean equals(Object obj); +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/PrioritizedPlanData.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/PrioritizedPlanData.java new file mode 100644 index 000000000..aa80e1bf9 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/PrioritizedPlanData.java @@ -0,0 +1,50 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +/** + * Combines a PlanData with a priority value used to reconstruct the order of + * plans from de-serialized plans from a previous simulation execution. + */ +public final class PrioritizedPlanData { + private final PlanData planData; + private final long priority; + + /** + * Constructs this PrioritizedPlanData from the give PlanData and priority value + */ + public PrioritizedPlanData(PlanData planData, long priority) { + this.planData = planData; + this.priority = priority; + } + + /** + * Returns the plan data + */ + @SuppressWarnings("unchecked") + public T getPlanData() { + return (T) planData; + } + + /** + * Returns the priority + */ + public long getPriority() { + return priority; + } + + /** + * Returns the string representation of a prioritized plan data in the form + * PrioritizedPlanData [planData=X, priority=1234] where X= the relevant plan + * data's toString() + */ + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("PrioritizedPlanData [planData="); + builder.append(planData); + builder.append(", priority="); + builder.append(priority); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/ReportContext.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/ReportContext.java new file mode 100644 index 000000000..83a076ca7 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/ReportContext.java @@ -0,0 +1,217 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +import util.errors.ContractException; + +/** + * An report context provides access to the nucleus simulation. It is supplied + * by the simulation each time it interacts with an report. Reports are defined + * by this context. If this context is passed to a method invocation, then that + * method is a report method. + */ +public final class ReportContext { + + public ReportId getReportId() { + return simulation.focalReportId; + } + + private final Simulation simulation; + + protected ReportContext(Simulation simulation) { + this.simulation = simulation; + } + + /** + * Schedules a passive plan that will be executed at the given time. Passive + * plans are not required to execute and the simulation will terminate if only + * passive plans remain on the planning schedule. + * + * @throws ContractException + *
      + *
    • {@link NucleusError#NULL_PLAN} if the plan is + * null
    • + *
    • {@link NucleusError#PAST_PLANNING_TIME} if the + * plan is scheduled for a time in the past *
    • + *
    • {@link NucleusError#PLANNING_QUEUE_CLOSED} if + * the plan is added to the simulation after event + * processing is finished
    • + *
    + */ + public void addPlan(final Consumer consumer, final double planTime) { + Plan plan = Plan.builder(ReportContext.class)// + .setActive(false)// + .setCallbackConsumer(consumer)// + .setKey(null)// + .setPlanData(null)// + .setTime(planTime)// + .build();// + simulation.addReportPlan(plan); + } + + /** + * Schedules a plan. + * + * @throws ContractException + *
      + *
    • {@link NucleusError#NULL_PLAN} if the plan is + * null
    • + *
    • {@link NucleusError#PAST_PLANNING_TIME} if the + * plan is scheduled for a time in the past *
    • + *
    • {@link NucleusError#PLANNING_QUEUE_CLOSED} if + * the plan is added to the simulation after event + * processing is finished
    • + *
    + */ + public void addPlan(Plan plan) { + if (plan == null) { + throw new ContractException(NucleusError.NULL_PLAN); + } + simulation.addReportPlan(plan); + } + + /** + * Retrieves a plan stored for the given key. + * + * @throws ContractException {@link NucleusError#NULL_PLAN_KEY} if the plan key + * is null + */ + public Optional> getPlan(final Object key) { + return simulation.getReportPlan(key); + } + + /** + * Returns true if and only if the reports should output their state as a plugin + * data instances at the end of the simulation. + */ + public boolean stateRecordingIsScheduled() { + return simulation.stateRecordingIsScheduled(); + } + + /** + * Returns the scheduled simulation halt time. Negative values indicate there is + * no scheduled halt time. + */ + public double getScheduledSimulationHaltTime() { + return simulation.getScheduledSimulationHaltTime(); + } + + /** + * Returns true if and only if there a state recording is scheduled and the + * given time exceeds the recording time. + */ + protected boolean plansRequirePlanData(double time) { + return simulation.plansRequirePlanData(time); + } + + /** + * Removes and returns the plan associated with the given key. + * + * @throws ContractException {@link NucleusError#NULL_PLAN_KEY} if the plan key + * is null + */ + public Optional> removePlan(final Object key) { + return simulation.removeReportPlan(key); + } + + /** + * Returns a list of the current plan keys associated with the current report + */ + public List getPlanKeys() { + return simulation.getReportPlanKeys(); + } + + /** + * Subscribes the report to events of the given type for the purpose of + * execution of data changes. + * + * @throws ContractException + *
      + *
    • {@link NucleusError#NULL_EVENT_CLASS} if the + * event class is null
    • + *
    • {@link NucleusError#NULL_EVENT_CONSUMER} if the + * event consumer is null
    • + *
    • {@link NucleusError#DUPLICATE_EVENT_SUBSCRIPTION} + * if the data manager is already subscribed
    • + *
    + */ + public void subscribe(Class eventClass, BiConsumer eventConsumer) { + simulation.subscribeReportToEvent(eventClass, eventConsumer); + } + + /** + * Unsubscribes the report from events of the given type for all phases of event + * handling. + * + * @throws ContractException {@link NucleusError#NULL_EVENT_CLASS} if the event + * class is null + */ + public void unsubscribe(Class eventClass) { + simulation.unsubscribeReportFromEvent(eventClass); + } + + /** + * Registers the given consumer to be executed at the end of the simulation. + * + * @throws ContractException {@link NucleusError#NULL_REPORT_CONTEXT_CONSUMER} + * if the consumer is null + */ + public void subscribeToSimulationClose(Consumer consumer) { + simulation.subscribeReportToSimulationClose(consumer); + } + + /** + * Returns the DataManger instance from the given class reference + * + * @throws ContractException + *
      + *
    • {@linkplain NucleusError#NULL_DATA_VIEW_CLASS} + * if the class reference is null
    • + *
    • {@linkplain NucleusError#UNKNOWN_DATA_VIEW} if + * the class reference does not correspond to a + * contained data view
    • + *
    + */ + public T getDataManager(Class dataManagerClass) { + return simulation.getDataManagerForActor(dataManagerClass); + } + + public double getTime() { + return simulation.time; + } + + public void releaseOutput(Object output) { + simulation.releaseOutput(output); + } + + /** + * Sets a function for converting plan data instances into consumers of actor + * context that will be used to convert stored plans from a previous simulation + * execution into current plans. Only used during the initialization of the + * simulation before time flows. + */ + public void setPlanDataConverter(Class planDataClass, + Function> conversionFunction) { + simulation.setReportPlanDataConverter(planDataClass, conversionFunction); + } + + /** + * Returns the time (floating point days) of simulation start. + */ + public double getStartTime() { + return simulation.getStartTime(); + } + + /** + * Returns the base date that synchronizes with simulation time zero. + */ + public LocalDate getBaseDate() { + return simulation.getBaseDate(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/ReportId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/ReportId.java new file mode 100644 index 000000000..b1a1d8d06 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/ReportId.java @@ -0,0 +1,65 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +/** + * The unique identifier for reports. + */ +public final class ReportId { + + private final int id; + + /** + * Returns the int id of this ActorId + */ + public final int getValue() { + return id; + } + + /** + * Creates an ActorId having the value id + */ + public ReportId(int id) { + this.id = id; + } + + /** + * Returns string of the form "ReportId [X]" where the value of the actor id is + * X + */ + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("ReportId [id="); + builder.append(id); + builder.append("]"); + return builder.toString(); + } + + /** + * Standard hash code implementation + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + id; + return result; + } + + /** + * Report Id instances are equal if and only if their values are equal + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ReportId other = (ReportId) obj; + if (id != other.id) + return false; + return true; + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/ScenarioStatus.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/ScenarioStatus.java new file mode 100644 index 000000000..928158f17 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/ScenarioStatus.java @@ -0,0 +1,16 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +/** + * The status of a scenario from the perspective of an experiment. All scenarios + * start as READY. If the experiment is executed as a continuation of a previous + * execution, then the scenarios fully recorded in the scenario progress file + * are marked as PREVIOUSLY_SUCCEEDED. If there are any explicitly added + * scenarios, then only those scenarios can remain READY and all others are + * marked SKIPPED. As the experiment executes the READY scenarios, it updates + * the scenario status of each to RUNNING until the corresponding simulation + * completes. If the simulation runs without throwing an exception, the scenario + * is updated to SUCCEDED. Otherwise, it is updated to FAILED. + */ +public enum ScenarioStatus { + READY, SKIPPED, PREVIOUSLY_SUCCEEDED, RUNNING, SUCCEDED, FAILED; +} \ No newline at end of file diff --git a/gcm3/src/main/java/nucleus/SimplePluginId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/SimplePluginId.java similarity index 85% rename from gcm3/src/main/java/nucleus/SimplePluginId.java rename to gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/SimplePluginId.java index 42567855a..9b7e61b60 100644 --- a/gcm3/src/main/java/nucleus/SimplePluginId.java +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/SimplePluginId.java @@ -1,15 +1,10 @@ -package nucleus; +package gov.hhs.aspr.ms.gcm.nucleus; import util.errors.ContractException; /** * A convenience class for representing PluginId as a wrapped object. - * - * @author Shawn Hatch - * - * */ - public class SimplePluginId implements PluginId { private final Object value; @@ -18,11 +13,8 @@ public class SimplePluginId implements PluginId { * Constructs a SimplePluginId from the given value. The value class must * implement a proper equals contract. * - * @throws ContractException - *
  • {@linkplain NucleusError#NULL_PLUGIN_ID} if the value is - * null
  • - * - * + * @throws ContractException {@linkplain NucleusError#NULL_PLUGIN_ID} if the + * value is null */ public SimplePluginId(Object value) { if (value == null) { @@ -51,8 +43,7 @@ public int hashCode() { } /** - * Simple Plugin Ids are equal if and only if their contained values are - * equal + * Simple Plugin Ids are equal if and only if their contained values are equal */ @Override public boolean equals(Object obj) { diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/Simulation.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/Simulation.java new file mode 100644 index 000000000..6b167a624 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/Simulation.java @@ -0,0 +1,2138 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import java.time.LocalDate; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Deque; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.PriorityQueue; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.apache.commons.math3.util.Pair; + +import net.jcip.annotations.NotThreadSafe; +import util.errors.ContractException; +import util.graph.Graph; +import util.graph.GraphDepthEvaluator; +import util.graph.Graphs; +import util.graph.MutableGraph; +import util.path.MapPathSolver; +import util.path.Path; + +/** + * An instance of the Simulation orchestrates the execution of a scenario from a + * set of contributed plugins. Plugins are loaded primarily based on the + * directed acyclic graph implied by their dependencies and then secondarily on + * the order in which the plugins were added to the experiment or simulation. + * Each plugin contributes an initialization behavior that adds actors and data + * managers to the simulation at simulation startup. The data managers are + * initialized in the order they are added to the simulation. Actor + * initialization then follows in a similar order. After initialization is over, + * time flows based on the execution of planning. Plans are collected from both + * actors and data managers. When no more plans remain, the simulation halts. + */ +@NotThreadSafe +public class Simulation { + + private static final String LINE_SEPARATOR = System.getProperty("line.separator"); + + private static class PlanRec { + private Plan plan; + + private Planner planner; + private long arrivalId; + + private double time; + private boolean isActive; + private Object key; + + private Consumer reportPlan; + private ReportId reportId; + + private Consumer actorPlan; + private ActorId actorId; + + private Consumer dataManagerPlan; + private DataManagerId dataManagerId; + + @Override + public String toString() { + + StringBuilder builder = new StringBuilder(); + + builder.append("\t"); + builder.append("time = "); + builder.append(time); + builder.append(LINE_SEPARATOR); + + builder.append("\t"); + builder.append("arrivalId = "); + builder.append(arrivalId); + builder.append(LINE_SEPARATOR); + + builder.append("\t"); + builder.append("isActive = "); + builder.append(isActive); + builder.append(LINE_SEPARATOR); + + builder.append("\t"); + builder.append("key = "); + builder.append(key); + builder.append(LINE_SEPARATOR); + + builder.append("\t"); + builder.append(planner); + builder.append(" = "); + switch (planner) { + case ACTOR: + builder.append(actorId); + builder.append(LINE_SEPARATOR); + builder.append("\t"); + builder.append(actorPlan); + break; + case DATA_MANAGER: + builder.append(dataManagerId); + builder.append(LINE_SEPARATOR); + builder.append("\t"); + builder.append(dataManagerPlan); + break; + case REPORT: + builder.append(reportId); + builder.append("\t"); + builder.append(LINE_SEPARATOR); + builder.append("\t"); + builder.append(reportPlan); + break; + default: + throw new RuntimeException("unhandled planner case"); + } + + builder.append(LINE_SEPARATOR); + builder.append("\t"); + builder.append("plan = "); + builder.append(LINE_SEPARATOR); + builder.append("\t"); + builder.append(plan); + + builder.append(LINE_SEPARATOR); + builder.append(LINE_SEPARATOR); + + return builder.toString(); + } + } + + public static class Builder { + + private Data data = new Data(); + + private Builder() { + + } + + /** + * Sets the output consumer for the simulation. Tolerates null. + */ + public Builder setOutputConsumer(Consumer outputConsumer) { + data.outputConsumer = outputConsumer; + return this; + } + + /** + * Signals to simulation components to record their state as plugin data as + * output to the experiment Defaults to false. + */ + public Builder setRecordState(boolean recordState) { + data.stateRecordingIsScheduled = recordState; + return this; + } + + /** + * Sets the halt time for the simulation. Defaults to null, which is equivalent + * to not halting. If the simulation has been instructed to produce its state at + * halt, then the halt time must be set to a positive value. Setting this to a + * value that is less than the simulation time used to start the simulation will + * result in an exception. + */ + public Builder setSimulationHaltTime(Double simulationHaltTime) { + data.simulationHaltTime = simulationHaltTime; + return this; + } + + /** + * Set the simulation state. Defaults to the current date and a start time of + * zero. + * + * @throws ContractException {@link NucleusError#NULL_SIMULATION_TIME} if the + * simulation time is null + */ + public Builder setSimulationState(SimulationState simulationState) { + if (simulationState == null) { + throw new ContractException(NucleusError.NULL_SIMULATION_TIME); + } + data.simulationState = simulationState; + return this; + } + + /** + * Adds a plugin to this builder for inclusion in the simulation + * + * @throws ContractException {@link NucleusError#NULL_PLUGIN} if the plugin is + * null + */ + public Builder addPlugin(Plugin plugin) { + if (plugin == null) { + throw new ContractException(NucleusError.NULL_PLUGIN); + } + data.plugins.add(plugin); + return this; + } + + private void validate() { + if (data.stateRecordingIsScheduled) { + if (data.simulationHaltTime == null) { + throw new ContractException(NucleusError.MISSING_SIM_HALT_TIME); + } + } + if (data.simulationHaltTime != null) { + if (data.simulationHaltTime < data.simulationState.getStartTime()) { + throw new ContractException(NucleusError.SIM_HALT_TIME_TOO_EARLY); + } + } + } + + /** + * Returns an Engine instance that is initialized with the plugins and output + * consumer collected by this builder. + * + * @throws ContractException + *
      + *
    • {@linkplain NucleusError#SIM_HALT_TIME_TOO_EARLY} + * If the simulation halt time is non-negative and + * less than the start time of the simulation
    • + *
    • {@linkplain NucleusError#MISSING_SIM_HALT_TIME} + * If simulation state is being recorded and the + * simulation halt time is not set.
    • + *
    + */ + public Simulation build() { + validate(); + return new Simulation(new Data(data)); + } + } + + private static class Data { + private Double simulationHaltTime = null; + private boolean stateRecordingIsScheduled; + private List plugins = new ArrayList<>(); + private Consumer outputConsumer; + private SimulationState simulationState = SimulationState.builder().build(); + + public Data() { + } + + public Data(Data data) { + simulationHaltTime = data.simulationHaltTime; + stateRecordingIsScheduled = data.stateRecordingIsScheduled; + plugins.addAll(data.plugins); + outputConsumer = data.outputConsumer; + simulationState = data.simulationState; + } + } + + /** + * Returns a reusable EngineBuilder instance + */ + public static Builder builder() { + return new Builder(); + } + + private final Comparator futureComparable = new Comparator() { + + @Override + public int compare(final PlanRec plannedEvent1, final PlanRec plannedEvent2) { + int result = Double.compare(plannedEvent1.time, plannedEvent2.time); + if (result == 0) { + + result = plannedEvent1.planner.compareTo(plannedEvent2.planner); + + if (result == 0) { + result = Long.compare(plannedEvent1.arrivalId, plannedEvent2.arrivalId); + } + } + return result; + } + }; + + private static enum PlanningQueueMode { + READY, RUNNING, CLOSED + } + + // planning + private long masterPlanningArrivalId; + protected double time; + double simulationHaltTime; + boolean forcedHaltPresent; + private boolean eventProcessingAllowed; + private int activePlanCount; + private final PriorityQueue planningQueue = new PriorityQueue<>(futureComparable); + private PlanningQueueMode planningQueueMode = PlanningQueueMode.READY; + + // actors + private final Map>> simulationCloseReportCallbacks = new LinkedHashMap<>(); + + private final Map>> simulationCloseActorCallbacks = new LinkedHashMap<>(); + + private final Map>> simulationCloseDataManagerCallbacks = new LinkedHashMap<>(); + + private boolean started; + + private PluginContext pluginContext; + + private PluginId focalPluginId; + + private final Map, List> basePluginDataMap = new LinkedHashMap<>(); + private final Map, List> workingPluginDataMap = new LinkedHashMap<>(); + + private final Data data; + + private Simulation(Data data) { + this.data = data; + } + + private void validateActorPlan(final Consumer plan) { + if (plan == null) { + throw new ContractException(NucleusError.NULL_PLAN_CONSUMER); + } + } + + private void validateReportPlan(final Consumer plan) { + if (plan == null) { + throw new ContractException(NucleusError.NULL_PLAN_CONSUMER); + } + } + + private void validateDataManagerPlan(final Consumer plan) { + if (plan == null) { + throw new ContractException(NucleusError.NULL_PLAN_CONSUMER); + } + } + + @SuppressWarnings("unchecked") + protected Optional getPluginData(Class pluginDataClass) { + if (pluginDataClass == null) { + throw new ContractException(NucleusError.NULL_PLUGIN_DATA_CLASS); + } + + PluginData result = null; + + List pluginDatas = workingPluginDataMap.get(pluginDataClass); + if (pluginDatas == null) { + + pluginDatas = new ArrayList<>(); + + for (Class c : basePluginDataMap.keySet()) { + if (pluginDataClass.isAssignableFrom(c)) { + pluginDatas.addAll(basePluginDataMap.get(c)); + } + } + workingPluginDataMap.put(pluginDataClass, pluginDatas); + } + if (pluginDatas.size() > 1) { + throw new ContractException(NucleusError.AMBIGUOUS_PLUGIN_DATA_CLASS); + } + if (pluginDatas.size() == 1) { + result = pluginDatas.get(0); + } + + return Optional.ofNullable((T) result); + } + + @SuppressWarnings("unchecked") + protected List getPluginDatas(Class pluginDataClass) { + if (pluginDataClass == null) { + throw new ContractException(NucleusError.NULL_PLUGIN_DATA_CLASS); + } + + List pluginDatas = workingPluginDataMap.get(pluginDataClass); + if (pluginDatas == null) { + + pluginDatas = new ArrayList<>(); + + for (Class c : basePluginDataMap.keySet()) { + if (pluginDataClass.isAssignableFrom(c)) { + pluginDatas.addAll(basePluginDataMap.get(c)); + } + } + workingPluginDataMap.put(pluginDataClass, pluginDatas); + + } + List result = new ArrayList<>(); + for (PluginData pluginData : pluginDatas) { + result.add((T) pluginData); + } + return result; + } + + protected void addDataManagerForPlugin(DataManager dataManager) { + + if (focalPluginId == null) { + throw new ContractException(NucleusError.PLUGIN_INITIALIZATION_CLOSED); + } + + if (dataManager == null) { + throw new ContractException(NucleusError.NULL_DATA_MANAGER); + } + + if (baseClassToDataManagerMap.containsKey(dataManager.getClass())) { + throw new ContractException(NucleusError.DUPLICATE_DATA_MANAGER_TYPE, dataManager.getClass()); + } + + DataManagerId dataManagerId = new DataManagerId(dataManagerIds.size()); + dataManagerIds.add(dataManagerId); + + /* + * Used to ensure that there is at most one instance of each data manager since + * classes are the identifiers for data managers outside of the simulation + * class. Used to find data managers for actors and data managers. + */ + baseClassToDataManagerMap.put(dataManager.getClass(), dataManager); + + DataManagerContext dataManagerContext = new DataManagerContext(dataManagerId, this); + dataManagerIdToDataManagerContextMap.put(dataManagerId, dataManagerContext); + + dataManagerIdToDataManagerMap.put(dataManagerId, dataManager); + dataManagerToDataManagerIdMap.put(dataManager, dataManagerId); + + // used for establishing visibility of data managers to each other + dataManagerIdToPluginIdMap.put(dataManagerId, focalPluginId); + + DataManagerContentRec dataManagerContentRec = new DataManagerContentRec(); + dataManagerContentRec.dmPlan = dataManager::init; + dataManagerContentRec.dataManagerId = dataManagerId; + + dataManagerQueue.add(dataManagerContentRec); + + } + + protected void addActorPlan(Plan plan) { + + if (planningQueueMode == PlanningQueueMode.CLOSED) { + throw new ContractException(NucleusError.PLANNING_QUEUE_CLOSED); + } + + validatePlanTime(plan.getTime()); + validateActorPlan(plan.getCallbackConsumer()); + + final PlanRec planRec = new PlanRec(); + + planRec.arrivalId = masterPlanningArrivalId++; + + planRec.plan = plan; + planRec.isActive = plan.isActive(); + + planRec.planner = Planner.ACTOR; + planRec.time = plan.getTime(); + planRec.actorPlan = plan.getCallbackConsumer(); + planRec.key = plan.getKey(); + + Map map; + + planRec.actorId = focalActorId; + + if (planRec.key != null) { + map = actorPlanMap.get(focalActorId); + if (map == null) { + map = new LinkedHashMap<>(); + actorPlanMap.put(focalActorId, map); + } + map.put(planRec.key, planRec); + } + + if (planRec.isActive) { + activePlanCount++; + } + + planningQueue.add(planRec); + } + + protected void addReportPlan(Plan plan) { + + if (planningQueueMode == PlanningQueueMode.CLOSED) { + throw new ContractException(NucleusError.PLANNING_QUEUE_CLOSED); + } + + validatePlanTime(plan.getTime()); + validateReportPlan(plan.getCallbackConsumer()); + + final PlanRec planRec = new PlanRec(); + + planRec.arrivalId = masterPlanningArrivalId++; + + planRec.plan = plan; + planRec.isActive = false; + planRec.planner = Planner.REPORT; + planRec.time = plan.getTime(); + planRec.reportPlan = plan.getCallbackConsumer(); + planRec.key = plan.getKey(); + + Map map; + + planRec.reportId = focalReportId; + + if (planRec.key != null) { + map = reportPlanMap.get(focalReportId); + if (map == null) { + map = new LinkedHashMap<>(); + reportPlanMap.put(focalReportId, map); + } + map.put(planRec.key, planRec); + } + planningQueue.add(planRec); + + } + + protected void addDataManagerPlan(DataManagerId dataManagerId, Plan plan) { + + if (planningQueueMode == PlanningQueueMode.CLOSED) { + throw new ContractException(NucleusError.PLANNING_QUEUE_CLOSED); + } + + validateDataManagerPlan(plan.getCallbackConsumer()); + validatePlanTime(plan.getTime()); + + final PlanRec planRec = new PlanRec(); + + planRec.arrivalId = masterPlanningArrivalId++; + + planRec.plan = plan; + planRec.isActive = plan.isActive(); + planRec.planner = Planner.DATA_MANAGER; + planRec.time = plan.getTime(); + planRec.dataManagerPlan = plan.getCallbackConsumer(); + planRec.key = plan.getKey(); + + Map map; + + planRec.dataManagerId = dataManagerId; + if (planRec.key != null) { + map = dataManagerPlanMap.get(dataManagerId); + if (map == null) { + map = new LinkedHashMap<>(); + dataManagerPlanMap.put(dataManagerId, map); + } + map.put(planRec.key, planRec); + } + + if (planRec.isActive) { + activePlanCount++; + } + planningQueue.add(planRec); + } + + protected void validateDataManagerPlanKeyNotDuplicate(DataManagerId dataManagerId, final Object key) { + if (getDataManagerPlan(dataManagerId, key).isPresent()) { + throw new ContractException(NucleusError.DUPLICATE_PLAN_KEY); + } + } + + protected void validateActorPlanKeyNotDuplicate(final Object key) { + if (getActorPlan(key).isPresent()) { + throw new ContractException(NucleusError.DUPLICATE_PLAN_KEY); + } + } + + protected void validateReportPlanKeyNotDuplicate(final Object key) { + if (getReportPlan(key).isPresent()) { + throw new ContractException(NucleusError.DUPLICATE_PLAN_KEY); + } + } + + @SuppressWarnings("unchecked") + protected Optional> removeActorPlan(final Object key) { + validatePlanKeyNotNull(key); + + Map map = actorPlanMap.get(focalActorId); + + Plan result = null; + if (map != null) { + final PlanRec planRecord = map.remove(key); + if (planRecord != null) { + result = (Plan) planRecord.plan; + planRecord.actorPlan = null; + planRecord.plan = null; + } + } + return Optional.ofNullable(result); + + } + + @SuppressWarnings("unchecked") + protected Optional> removeReportPlan(final Object key) { + validatePlanKeyNotNull(key); + + Map map = reportPlanMap.get(focalReportId); + + Plan result = null; + if (map != null) { + final PlanRec planRecord = map.remove(key); + if (planRecord != null) { + result = (Plan) planRecord.plan; + planRecord.reportPlan = null; + planRecord.plan = null; + } + } + return Optional.ofNullable(result); + + } + + private Graph pluginDependencyGraph; + + private List getOrderedPlugins() { + + MutableGraph mutableGraph = new MutableGraph<>(); + + Map pluginMap = new LinkedHashMap<>(); + + /* + * Add the nodes to the graph, check for duplicate ids, build the mapping from + * plugin id back to plugin + */ + for (Plugin plugin : data.plugins) { + focalPluginId = plugin.getPluginId(); + pluginMap.put(focalPluginId, plugin); + // ensure that there are no duplicate plugins + if (mutableGraph.containsNode(focalPluginId)) { + throw new ContractException(NucleusError.DUPLICATE_PLUGIN, focalPluginId); + } + mutableGraph.addNode(focalPluginId); + focalPluginId = null; + } + + // Add the edges to the graph + for (Plugin plugin : data.plugins) { + focalPluginId = plugin.getPluginId(); + for (PluginId pluginId : plugin.getPluginDependencies()) { + mutableGraph.addEdge(new Object(), focalPluginId, pluginId); + } + focalPluginId = null; + } + + /* + * Check for missing plugins from the plugin dependencies that were collected + * from the known plugins. + */ + for (PluginId pluginId : mutableGraph.getNodes()) { + if (!pluginMap.containsKey(pluginId)) { + List inboundEdges = mutableGraph.getInboundEdges(pluginId); + StringBuilder sb = new StringBuilder(); + sb.append("cannot locate instance of "); + sb.append(pluginId); + sb.append(" needed for "); + boolean first = true; + for (Object edge : inboundEdges) { + if (first) { + first = false; + } else { + sb.append(", "); + } + PluginId dependentPluginId = mutableGraph.getOriginNode(edge); + sb.append(dependentPluginId); + } + throw new ContractException(NucleusError.MISSING_PLUGIN, sb.toString()); + } + } + + /* + * Determine whether the graph is acyclic and generate a graph depth evaluator + * for the graph so that we can determine the order of initialization. + */ + Optional> optional = GraphDepthEvaluator + .getGraphDepthEvaluator(mutableGraph.toGraph()); + + if (!optional.isPresent()) { + /* + * Explain in detail why there is a circular dependency + */ + Graph g = mutableGraph.toGraph(); + + g = Graphs.getSourceSinkReducedGraph(g); + g = Graphs.getEdgeReducedGraph(g); + g = Graphs.getSourceSinkReducedGraph(g); + + List> cutGraphs = Graphs.cutGraph(g); + StringBuilder sb = new StringBuilder(); + String lineSeparator = System.getProperty("line.separator"); + sb.append(lineSeparator); + boolean firstCutGraph = true; + + for (Graph cutGraph : cutGraphs) { + if (firstCutGraph) { + firstCutGraph = false; + } else { + sb.append(lineSeparator); + } + sb.append("Dependency group: "); + sb.append(lineSeparator); + Set nodes = cutGraph.getNodes().stream().collect(Collectors.toCollection(LinkedHashSet::new)); + + for (PluginId node : nodes) { + sb.append("\t"); + sb.append(node); + sb.append(" requires:"); + sb.append(lineSeparator); + for (Object edge : cutGraph.getInboundEdges(node)) { + PluginId dependencyNode = cutGraph.getOriginNode(edge); + if (nodes.contains(dependencyNode)) { + sb.append("\t"); + sb.append("\t"); + sb.append(dependencyNode); + sb.append(lineSeparator); + } + } + } + } + throw new ContractException(NucleusError.CIRCULAR_PLUGIN_DEPENDENCIES, sb.toString()); + } + + // the graph is acyclic, so the depth evaluator is present + GraphDepthEvaluator graphDepthEvaluator = optional.get(); + + List orderedPluginIds = graphDepthEvaluator.getNodesInRankOrder(); + + List orderedPlugins = new ArrayList<>(); + for (PluginId pluginId : orderedPluginIds) { + orderedPlugins.add(pluginMap.get(pluginId)); + } + + pluginDependencyGraph = mutableGraph.toGraph(); + + return orderedPlugins; + } + + protected double getScheduledSimulationHaltTime() { + return data.simulationHaltTime; + } + + protected boolean stateRecordingIsScheduled() { + return data.stateRecordingIsScheduled; + } + + protected boolean plansRequirePlanData(double time) { + if (data.stateRecordingIsScheduled) { + if (time > data.simulationHaltTime) { + return true; + } + } + return false; + } + + private final Map, Function>>> dataManagerPlanDataConversionMap = new LinkedHashMap<>(); + + @SuppressWarnings("unchecked") + protected void setDataManagerPlanDataConverter(DataManagerId dataManagerId, + Class planDataClass, Function> conversionFunction) { + Map, Function>> map = dataManagerPlanDataConversionMap + .get(dataManagerId); + + if (map == null) { + map = new LinkedHashMap<>(); + dataManagerPlanDataConversionMap.put(dataManagerId, map); + } + Function> f = (planData) -> { + return conversionFunction.apply((T) planData); + }; + map.put(planDataClass, f); + } + + private Consumer getDataManagerContextConsumer(DataManagerId dataManagerId, PlanData planData) { + Consumer result = null; + Map, Function>> map = dataManagerPlanDataConversionMap + .get(dataManagerId); + if (map != null) { + Function> function = map.get(planData.getClass()); + if (function != null) { + result = function.apply(planData); + } + } + return result; + } + + private final Map, Function>>> actorPlanDataConversionMap = new LinkedHashMap<>(); + + @SuppressWarnings("unchecked") + protected void setActorPlanDataConverter(Class planDataClass, + Function> conversionFunction) { + Map, Function>> map = actorPlanDataConversionMap + .get(focalActorId); + + if (map == null) { + map = new LinkedHashMap<>(); + actorPlanDataConversionMap.put(focalActorId, map); + } + Function> f = (planData) -> { + return conversionFunction.apply((T) planData); + }; + map.put(planDataClass, f); + } + + private Consumer getActorContextConsumer(ActorId actorId, PlanData planData) { + Consumer result = null; + Map, Function>> map = actorPlanDataConversionMap + .get(actorId); + if (map != null) { + Function> function = map.get(planData.getClass()); + if (function != null) { + result = function.apply(planData); + } + } + return result; + } + + private final Map, Function>>> reportPlanDataConversionMap = new LinkedHashMap<>(); + + @SuppressWarnings("unchecked") + protected void setReportPlanDataConverter(Class planDataClass, + Function> conversionFunction) { + Map, Function>> map = reportPlanDataConversionMap + .get(focalReportId); + + if (map == null) { + map = new LinkedHashMap<>(); + reportPlanDataConversionMap.put(focalReportId, map); + } + Function> f = (planData) -> { + return conversionFunction.apply((T) planData); + }; + map.put(planDataClass, f); + } + + private Consumer getReportContextConsumer(ReportId reportId, PlanData planData) { + Consumer result = null; + Map, Function>> map = reportPlanDataConversionMap + .get(reportId); + if (map != null) { + Function> function = map.get(planData.getClass()); + if (function != null) { + result = function.apply(planData); + } + } + return result; + } + + private void loadExistingPlans() { + List planQueueDatas = data.simulationState.getPlanQueueDatas(); + for (PlanQueueData planQueueData : planQueueDatas) { + PlanRec planRec = new PlanRec(); + // convert the plan data into a consumer + Planner planner = planQueueData.getPlanner(); + switch (planner) { + case ACTOR: + planRec.actorId = actorIds.get(planQueueData.getPlannerId()); + planRec.actorPlan = getActorContextConsumer(planRec.actorId, planQueueData.getPlanData()); + planRec.plan = Plan.builder(ActorContext.class)// + .setActive(planQueueData.isActive())// + .setKey(planQueueData.getKey())// + .setPlanData(planQueueData.getPlanData())// + .setTime(planQueueData.getTime())// + .setCallbackConsumer(planRec.actorPlan)// + .build(); + + break; + case DATA_MANAGER: + planRec.dataManagerId = dataManagerIds.get(planQueueData.getPlannerId()); + planRec.dataManagerPlan = getDataManagerContextConsumer(planRec.dataManagerId, + planQueueData.getPlanData()); + planRec.plan = Plan.builder(DataManagerContext.class)// + .setActive(planQueueData.isActive())// + .setKey(planQueueData.getKey())// + .setPlanData(planQueueData.getPlanData())// + .setTime(planQueueData.getTime())// + .setCallbackConsumer(planRec.dataManagerPlan)// + .build(); + + break; + case REPORT: + planRec.reportId = reportIds.get(planQueueData.getPlannerId()); + planRec.reportPlan = getReportContextConsumer(planRec.reportId, planQueueData.getPlanData()); + planRec.plan = Plan.builder(ReportContext.class)// + .setActive(planQueueData.isActive())// + .setKey(planQueueData.getKey())// + .setPlanData(planQueueData.getPlanData())// + .setTime(planQueueData.getTime())// + .setCallbackConsumer(planRec.reportPlan)// + .build(); + + break; + default: + throw new RuntimeException("unhandled case " + planner); + } + planRec.arrivalId = planQueueData.getArrivalId(); + planRec.isActive = planQueueData.isActive(); + planRec.key = planQueueData.getKey(); + planRec.planner = planQueueData.getPlanner(); + planRec.time = planQueueData.getTime(); + + if (planRec.isActive) { + activePlanCount++; + } + if (planRec.plan.getCallbackConsumer() != null) { + planningQueue.add(planRec); + Map map; + if (planRec.key != null) { + switch (planner) { + case ACTOR: + map = actorPlanMap.get(planRec.actorId); + if (map == null) { + map = new LinkedHashMap<>(); + actorPlanMap.put(planRec.actorId, map); + } + map.put(planRec.key, planRec); + break; + case DATA_MANAGER: + map = dataManagerPlanMap.get(planRec.dataManagerId); + if (map == null) { + map = new LinkedHashMap<>(); + dataManagerPlanMap.put(planRec.dataManagerId, map); + } + map.put(planRec.key, planRec); + break; + case REPORT: + map = reportPlanMap.get(planRec.reportId); + if (map == null) { + map = new LinkedHashMap<>(); + reportPlanMap.put(planRec.reportId, map); + } + map.put(planRec.key, planRec); + break; + default: + throw new RuntimeException("unhandled case " + planner); + } + } + } + } + } + + /** + * Executes this Simulation instance. Contributed plugin initializers are + * accessed in the order of their addition to the builder. Actors and data + * managers are organized based on their plugin dependency ordering. Time starts + * at zero and flows based on planning. When all plans are executed, time stops + * and the simulation halts. + * + * @throws ContractException + *
      + *
    • {@link NucleusError#REPEATED_EXECUTION} if + * execute is invoked more than once
    • + *
    • {@link NucleusError#MISSING_PLUGIN} if the + * contributed plugins contain dependencies on plugins + * that have not been added to the simulation
    • + *
    • {@link NucleusError#MISSING_PLUGIN} if the + * contributed plugins contain duplicate plugin + * ids
    • + *
    • {@link NucleusError#CIRCULAR_PLUGIN_DEPENDENCIES} + * if the contributed plugins form a circular chain of + * dependencies
    • + *
    • {@link NucleusError#DATA_MANAGER_INITIALIZATION_FAILURE} + * if a data manager does not invoke + * {@linkplain DataManager#init(DataManagerContext)} + * in its override of init().
    • + *
    + */ + public void execute() { + reportContext = new ReportContext(this); + actorContext = new ActorContext(this); + pluginContext = new PluginContext(this); + + // start the simulation + if (started) { + throw new ContractException(NucleusError.REPEATED_EXECUTION); + } + started = true; + + time = data.simulationState.getStartTime(); + + masterPlanningArrivalId = data.simulationState.getPlanningQueueArrivalId(); + + forcedHaltPresent = false; + if (data.simulationHaltTime != null) { + simulationHaltTime = data.simulationHaltTime; + forcedHaltPresent = true; + } + + // set the output consumer + outputConsumer = data.outputConsumer; + + /* + * Get the plugins listed in dependency order + */ + List orderedPlugins = getOrderedPlugins(); + + // Make the plugin data available + for (Plugin plugin : orderedPlugins) { + focalPluginId = plugin.getPluginId(); + for (PluginData pluginData : plugin.getPluginDatas()) { + List pluginDatas = basePluginDataMap.get(pluginData.getClass()); + if (pluginDatas == null) { + pluginDatas = new ArrayList<>(); + basePluginDataMap.put(pluginData.getClass(), pluginDatas); + } + pluginDatas.add(pluginData); + } + focalPluginId = null; + } + + // Have each plugin contribute data managers reports and actors + for (Plugin plugin : orderedPlugins) { + focalPluginId = plugin.getPluginId(); + Optional> optionalInitializer = plugin.getInitializer(); + if (optionalInitializer.isPresent()) { + optionalInitializer.get().accept(pluginContext); + } + focalPluginId = null; + } + int dataManagerCount = dataManagerIdToPluginIdMap.size(); + dataManagerAccessPermissions = new boolean[dataManagerCount][dataManagerCount]; + + MapPathSolver mapPathSolver = new MapPathSolver<>(pluginDependencyGraph, (e) -> 1, + (a, b) -> 0); + + // determine the access allowed between data managers + for (DataManagerId dataManagerId1 : dataManagerIdToPluginIdMap.keySet()) { + PluginId pluginId1 = dataManagerIdToPluginIdMap.get(dataManagerId1); + for (DataManagerId dataManagerId2 : dataManagerIdToPluginIdMap.keySet()) { + PluginId pluginId2 = dataManagerIdToPluginIdMap.get(dataManagerId2); + Optional> optionalPath = mapPathSolver.getPath(pluginId1, pluginId2); + if (optionalPath.isPresent()) { + dataManagerAccessPermissions[dataManagerId1.getValue()][dataManagerId2.getValue()] = true; + } else { + // within a plugin, data managers have access to previously + // added data managers in the same plugin. + if (pluginId1.equals(pluginId2)) { + if (dataManagerId1.getValue() > dataManagerId2.getValue()) { + dataManagerAccessPermissions[dataManagerId1.getValue()][dataManagerId2.getValue()] = true; + } + } + } + } + } + eventProcessingAllowed = true; + // execute the data manager queue -- this will in turn execute the + // report queue + executeDataManagerQueue(); + + for (DataManager dataManager : dataManagerToDataManagerIdMap.keySet()) { + if (!dataManager.isInitialized()) { + throw new ContractException(NucleusError.DATA_MANAGER_INITIALIZATION_FAILURE, + dataManager.getClass().getSimpleName()); + } + } + + // initialize the actors by flushing the actor queue + executeActorQueue(); + + loadExistingPlans(); + + planningQueueMode = PlanningQueueMode.RUNNING; + + while (activePlanCount > 0) { + if (forcedHaltPresent) { + if (planningQueue.peek().time > simulationHaltTime) { + break; + } + } + + final PlanRec planRec = planningQueue.poll(); + // System.out.println(planRec); + + time = planRec.time; + if (planRec.isActive) { + activePlanCount--; + } + switch (planRec.planner) { + case ACTOR: + if (planRec.actorPlan != null) { + if (planRec.key != null) { + actorPlanMap.get(planRec.actorId).remove(planRec.key); + } + ActorContentRec actorContentRec = new ActorContentRec(); + actorContentRec.actorId = planRec.actorId; + actorContentRec.plan = planRec.actorPlan; + actorQueue.add(actorContentRec); + executeActorQueue(); + } + break; + case DATA_MANAGER: + if (planRec.dataManagerPlan != null) { + if (planRec.key != null) { + dataManagerPlanMap.get(planRec.dataManagerId).remove(planRec.key); + } + DataManagerContentRec dataManagerContentRec = new DataManagerContentRec(); + dataManagerContentRec.dmPlan = planRec.dataManagerPlan; + dataManagerContentRec.dataManagerId = planRec.dataManagerId; + dataManagerQueue.add(dataManagerContentRec); + executeDataManagerQueue(); + executeActorQueue(); + } + break; + + case REPORT: + if (planRec.reportPlan != null) { + if (planRec.key != null) { + reportPlanMap.get(planRec.reportId).remove(planRec.key); + } + ReportContentRec reportContentRec = new ReportContentRec(); + reportContentRec.reportPlan = planRec.reportPlan; + reportContentRec.reportId = planRec.reportId; + reportQueue.add(reportContentRec); + executeReportQueue(); + } + + break; + + default: + throw new RuntimeException("unhandled planner type " + planRec.planner); + } + } + + if (forcedHaltPresent) { + time = simulationHaltTime; + } + + planningQueueMode = PlanningQueueMode.CLOSED; + + eventProcessingAllowed = false; + + // signal to the data managers that the simulation is closing + for (DataManagerId dataManagerId : simulationCloseDataManagerCallbacks.keySet()) { + DataManagerContext dataManagerContext = dataManagerIdToDataManagerContextMap.get(dataManagerId); + for (Consumer dataManagerCloseCallback : simulationCloseDataManagerCallbacks + .get(dataManagerId)) { + dataManagerCloseCallback.accept(dataManagerContext); + } + } + + // signal to the actors that the simulation is closing + for (ActorId actorId : simulationCloseActorCallbacks.keySet()) { + if (actorIds.get(actorId.getValue()) != null) { + focalActorId = actorId; + for (Consumer simulationCloseCallback : simulationCloseActorCallbacks.get(actorId)) { + simulationCloseCallback.accept(actorContext); + } + focalActorId = null; + + } + } + + // signal to the reports that the simulation is closing + for (ReportId reportId : simulationCloseReportCallbacks.keySet()) { + focalReportId = reportId; + for (Consumer simulationCloseCallback : simulationCloseReportCallbacks.get(reportId)) { + simulationCloseCallback.accept(reportContext); + } + focalReportId = null; + } + + if (data.stateRecordingIsScheduled && outputConsumer != null) { + SimulationState.Builder simulationStateBuilder = SimulationState.builder(); + simulationStateBuilder.setBaseDate(data.simulationState.getBaseDate()); + simulationStateBuilder.setStartTime(time); + simulationStateBuilder.setPlanningQueueArrivalId(masterPlanningArrivalId); + + PlanQueueData.Builder planQueueDataBuilder = PlanQueueData.builder(); + while (!planningQueue.isEmpty()) { + PlanRec planRec = planningQueue.poll(); + if (planRec.plan != null) { + + PlanData planData = planRec.plan.getPlanData(); + if (planData != null) { + planQueueDataBuilder.setActive(planRec.isActive)// + .setArrivalId(planRec.arrivalId)// + .setKey(planRec.key)// + .setPlanData(planData)// + .setPlanner(planRec.planner)// + .setTime(planRec.time);// + + switch (planRec.planner) { + case ACTOR: + planQueueDataBuilder.setPlannerId(planRec.actorId.getValue()); + break; + case DATA_MANAGER: + planQueueDataBuilder.setPlannerId(planRec.dataManagerId.getValue()); + break; + case REPORT: + planQueueDataBuilder.setPlannerId(planRec.reportId.getValue()); + break; + default: + throw new RuntimeException("unhandled case " + planRec.planner); + } + + PlanQueueData planQueueData = planQueueDataBuilder.build(); + simulationStateBuilder.addPlanQueueData(planQueueData); + } + } + } + + outputConsumer.accept(simulationStateBuilder.build()); + } + + } + + private void executeActorQueue() { + while (!actorQueue.isEmpty()) { + final ActorContentRec actorContentRec = actorQueue.pollFirst(); + + if (containsDeletedActors) { + /* + * we know that the actor id was valid at some point and that the actorMap never + * shrinks, so we do not have to range check the actor id + */ + if (actorIds.get(actorContentRec.actorId.getValue()) == null) { + continue; + } + } + + focalActorId = actorContentRec.actorId; + if (actorContentRec.event != null) { + actorContentRec.consumer.accept(actorContentRec.event); + } else { + actorContentRec.plan.accept(actorContext); + } + focalActorId = null; + } + + } + + private boolean dataManagerQueueActive; + + private void executeReportQueue() { + while (!reportQueue.isEmpty()) { + final ReportContentRec contentRec = reportQueue.pollFirst(); + if (contentRec.reportPlan != null) { + focalReportId = contentRec.reportId; + contentRec.reportPlan.accept(reportContext); + focalReportId = null; + } else { + focalReportId = contentRec.reportId; + contentRec.consumer.accept(contentRec.event); + focalReportId = null; + } + } + } + + protected void executeDataManagerQueue() { + if (dataManagerQueueActive) { + return; + } + dataManagerQueueActive = true; + try { + try { + while (!dataManagerQueue.isEmpty()) { + final DataManagerContentRec contentRec = dataManagerQueue.pollFirst(); + if (contentRec.dmPlan != null) { + DataManagerContext dataManagerContext = dataManagerIdToDataManagerContextMap + .get(contentRec.dataManagerId); + contentRec.dmPlan.accept(dataManagerContext); + } else { + contentRec.consumer.accept(contentRec.event); + } + } + executeReportQueue(); + } catch (Exception e) { + dataManagerQueue.clear(); + throw (e); + } + } finally { + dataManagerQueueActive = false; + } + } + + @SuppressWarnings("unchecked") + protected Optional> getReportPlan(final Object key) { + validatePlanKeyNotNull(key); + Map map = reportPlanMap.get(focalReportId); + Plan result = null; + if (map != null) { + final PlanRec planRecord = map.get(key); + if (planRecord != null) { + result = (Plan) planRecord.plan; + } + } + return Optional.ofNullable(result); + } + + @SuppressWarnings("unchecked") + protected Optional> getActorPlan(final Object key) { + validatePlanKeyNotNull(key); + Map map = actorPlanMap.get(focalActorId); + Plan result = null; + if (map != null) { + final PlanRec planRecord = map.get(key); + if (planRecord != null) { + result = (Plan) planRecord.plan; + } + } + return Optional.ofNullable(result); + } + + @SuppressWarnings("unchecked") + protected Optional> getDataManagerPlan(DataManagerId dataManagerId, final Object key) { + validatePlanKeyNotNull(key); + Map map = dataManagerPlanMap.get(dataManagerId); + Plan result = null; + if (map != null) { + final PlanRec planRecord = map.get(key); + if (planRecord != null) { + result = (Plan) planRecord.plan; + } + } + return Optional.ofNullable(result); + } + + protected List getActorPlanKeys() { + Map map = actorPlanMap.get(focalActorId); + if (map != null) { + return new ArrayList<>(map.keySet()); + } + return new ArrayList<>(); + } + + protected List getReportPlanKeys() { + Map map = reportPlanMap.get(focalReportId); + if (map != null) { + return new ArrayList<>(map.keySet()); + } + return new ArrayList<>(); + } + + protected List getDataManagerPlanKeys(DataManagerId dataManagerId) { + Map map = dataManagerPlanMap.get(dataManagerId); + if (map != null) { + return new ArrayList<>(map.keySet()); + } + return new ArrayList<>(); + } + + protected void halt() { + if (!data.stateRecordingIsScheduled) { + simulationHaltTime = time; + forcedHaltPresent = true; + } + } + + private Consumer outputConsumer; + + /* + * We drop the plan out of the plan map and thus have no way to reference the + * plan directly. However, we do not remove the plan from the planQueue and + * instead simply mark the plan record as cancelled. When the cancelled plan + * record reaches the top of the queue, it is popped off and ignored. This + * avoids the inefficiency of walking the queue and removing the plan. + * + * Note that we are allowing components to delete plans that do not exist. This + * was done to ease any bookkeeping burdens on the component and seems generally + * harmless. + */ + @SuppressWarnings("unchecked") + protected Optional> removeDataManagerPlan(DataManagerId dataManagerId, final Object key) { + validatePlanKeyNotNull(key); + + Map map = dataManagerPlanMap.get(dataManagerId); + Plan result = null; + if (map != null) { + final PlanRec planRecord = map.remove(key); + if (planRecord != null) { + result = (Plan) planRecord.plan; + planRecord.dataManagerPlan = null; + planRecord.plan = null; + } + } + return Optional.ofNullable(result); + } + + protected void validatePlanKeyNotNull(final Object key) { + if (key == null) { + throw new ContractException(NucleusError.NULL_PLAN_KEY, ""); + } + } + + private void validatePlanTime(final double planTime) { + if (planTime < time) { + throw new ContractException(NucleusError.PAST_PLANNING_TIME); + } + } + + protected void releaseOutput(Object output) { + if (outputConsumer != null) { + outputConsumer.accept(output); + } + } + + protected boolean actorExists(final ActorId actorId) { + if (actorId == null) { + return false; + } + int index = actorId.getValue(); + if (index < 0) { + return false; + } + if (index >= actorIds.size()) { + return false; + } + + return actorIds.get(index) != null; + } + + protected void subscribeReportToSimulationClose(Consumer consumer) { + if (consumer == null) { + throw new ContractException(NucleusError.NULL_REPORT_CONTEXT_CONSUMER); + } + List> list = simulationCloseReportCallbacks.get(focalReportId); + if (list == null) { + list = new ArrayList<>(); + simulationCloseReportCallbacks.put(focalReportId, list); + } + list.add(consumer); + } + + protected void subscribeActorToSimulationClose(Consumer consumer) { + if (consumer == null) { + throw new ContractException(NucleusError.NULL_ACTOR_CONTEXT_CONSUMER); + } + List> list = simulationCloseActorCallbacks.get(focalActorId); + if (list == null) { + list = new ArrayList<>(); + simulationCloseActorCallbacks.put(focalActorId, list); + } + list.add(consumer); + } + + protected void subscribeDataManagerToSimulationClose(DataManagerId dataManagerId, + Consumer consumer) { + if (consumer == null) { + throw new ContractException(NucleusError.NULL_DATA_MANAGER_CONTEXT_CONSUMER); + } + List> list = simulationCloseDataManagerCallbacks.get(dataManagerId); + if (list == null) { + list = new ArrayList<>(); + simulationCloseDataManagerCallbacks.put(dataManagerId, list); + } + list.add(consumer); + } + + @SuppressWarnings("unchecked") + protected void subscribeDataManagerToEvent(DataManagerId dataManagerId, Class eventClass, + BiConsumer eventConsumer) { + + if (eventClass == null) { + throw new ContractException(NucleusError.NULL_EVENT_CLASS); + } + + if (eventConsumer == null) { + throw new ContractException(NucleusError.NULL_EVENT_CONSUMER); + } + + List list = dataManagerEventMap.get(eventClass); + if (list == null) { + list = new ArrayList<>(); + dataManagerEventMap.put(eventClass, list); + } + + for (DataManagerEventConsumer dataManagerEventConsumer : list) { + if (dataManagerEventConsumer.dataManagerId.equals(dataManagerId)) { + throw new ContractException(NucleusError.DUPLICATE_EVENT_SUBSCRIPTION); + } + } + + DataManagerContext dataManagerContext = dataManagerIdToDataManagerContextMap.get(dataManagerId); + + DataManagerEventConsumer dataManagerEventConsumer = new DataManagerEventConsumer(dataManagerId, + event -> eventConsumer.accept(dataManagerContext, (T) event)); + + list.add(dataManagerEventConsumer); + Collections.sort(list); + + } + + protected void unsubscribeDataManagerFromEvent(DataManagerId dataManagerId, Class eventClass) { + if (eventClass == null) { + throw new ContractException(NucleusError.NULL_EVENT_CLASS); + } + + List list = dataManagerEventMap.get(eventClass); + + if (list != null) { + Iterator iterator = list.iterator(); + while (iterator.hasNext()) { + DataManagerEventConsumer dataManagerEventConsumer = iterator.next(); + if (dataManagerEventConsumer.dataManagerId.equals(dataManagerId)) { + iterator.remove(); + } + } + + if (list.isEmpty()) { + dataManagerEventMap.remove(eventClass); + } + } + + } + + @SuppressWarnings("unchecked") + protected void subscribeReportToEvent(Class eventClass, + BiConsumer eventConsumer) { + + if (eventClass == null) { + throw new ContractException(NucleusError.NULL_EVENT_CLASS); + } + + if (eventConsumer == null) { + throw new ContractException(NucleusError.NULL_EVENT_CONSUMER); + } + + List list = reportEventMap.get(eventClass); + if (list == null) { + list = new ArrayList<>(); + reportEventMap.put(eventClass, list); + } + + for (ReportEventConsumer reportEventConsumer : list) { + if (reportEventConsumer.reportId.equals(focalReportId)) { + throw new ContractException(NucleusError.DUPLICATE_EVENT_SUBSCRIPTION); + } + } + + ReportEventConsumer reportEventConsumer = new ReportEventConsumer(focalReportId, + event -> eventConsumer.accept(reportContext, (T) event)); + + list.add(reportEventConsumer); + + } + + protected void unsubscribeReportFromEvent(Class eventClass) { + if (eventClass == null) { + throw new ContractException(NucleusError.NULL_EVENT_CLASS); + } + + List list = reportEventMap.get(eventClass); + + if (list != null) { + Iterator iterator = list.iterator(); + while (iterator.hasNext()) { + ReportEventConsumer reportEventConsumer = iterator.next(); + if (reportEventConsumer.reportId.equals(focalReportId)) { + iterator.remove(); + } + } + + if (list.isEmpty()) { + reportEventMap.remove(eventClass); + } + } + + } + + protected void releaseObservationEventForDataManager(final Event event) { + + if (event == null) { + throw new ContractException(NucleusError.NULL_EVENT); + } + + if (!dataManagerQueueActive) { + throw new ContractException(NucleusError.OBSERVATION_EVENT_IMPROPER_RELEASE); + } + + // queue the event handling by reports + List reportConsumers = reportEventMap.get(event.getClass()); + if (reportConsumers != null) { + for (ReportEventConsumer reportEventConsumer : reportConsumers) { + ReportContentRec reportContentRec = new ReportContentRec(); + reportContentRec.event = event; + reportContentRec.consumer = reportEventConsumer; + reportContentRec.reportId = reportEventConsumer.reportId; + reportQueue.add(reportContentRec); + } + } + + // queue the event handling for actors + broadcastEventToFilterNode(event, rootNode); + + // queue the event handling by data managers + List dataManagerEventConsumers = dataManagerEventMap.get(event.getClass()); + if (dataManagerEventConsumers != null) { + for (DataManagerEventConsumer dataManagerEventConsumer : dataManagerEventConsumers) { + + DataManagerContentRec dataManagerContentRec = new DataManagerContentRec(); + dataManagerContentRec.event = event; + dataManagerContentRec.consumer = dataManagerEventConsumer.consumer; + dataManagerContentRec.dataManagerId = dataManagerEventConsumer.dataManagerId; + dataManagerQueue.add(dataManagerContentRec); + + } + } + } + + protected void releaseMutationEventForDataManager(final Event event) { + + if (event == null) { + throw new ContractException(NucleusError.NULL_EVENT); + } + + if (focalReportId != null) { + throw new ContractException(NucleusError.REPORT_ATTEMPTING_MUTATION, focalReportId); + } + + if (!eventProcessingAllowed) { + throw new ContractException(NucleusError.DATA_MANAGER_ATTEMPTING_MUTATION); + } + + // queue the event handling by data managers + List dataManagerEventConsumers = dataManagerEventMap.get(event.getClass()); + if (dataManagerEventConsumers != null) { + for (DataManagerEventConsumer dataManagerEventConsumer : dataManagerEventConsumers) { + + DataManagerContentRec dataManagerContentRec = new DataManagerContentRec(); + dataManagerContentRec.event = event; + dataManagerContentRec.consumer = dataManagerEventConsumer.consumer; + dataManagerContentRec.dataManagerId = dataManagerEventConsumer.dataManagerId; + dataManagerQueue.add(dataManagerContentRec); + + } + } + + executeDataManagerQueue(); + } + + protected ActorId addActor(Consumer consumer) { + + if (consumer == null) { + throw new ContractException(NucleusError.NULL_ACTOR_CONTEXT_CONSUMER); + } + + ActorId result = new ActorId(actorIds.size()); + actorIds.add(result); + + final ActorContentRec actorContentRec = new ActorContentRec(); + actorContentRec.actorId = result; + actorContentRec.plan = consumer; + actorQueue.add(actorContentRec); + + return result; + } + + protected ActorId addActorForPlugin(Consumer consumer) { + + if (focalPluginId == null) { + throw new ContractException(NucleusError.PLUGIN_INITIALIZATION_CLOSED); + } + return addActor(consumer); + + } + + protected void addReportForPlugin(Consumer consumer) { + if (focalPluginId == null) { + throw new ContractException(NucleusError.PLUGIN_INITIALIZATION_CLOSED); + } + + if (consumer == null) { + throw new ContractException(NucleusError.NULL_REPORT_CONTEXT_CONSUMER); + } + + ReportId reportId = new ReportId(reportIds.size()); + reportIds.add(reportId); + + final ReportContentRec reportContentRec = new ReportContentRec(); + reportContentRec.reportId = reportId; + reportContentRec.reportPlan = consumer; + reportQueue.add(reportContentRec); + + } + + protected void removeActor(final ActorId actorId) { + if (actorId == null) { + throw new ContractException(NucleusError.NULL_ACTOR_ID); + } + + int actorIndex = actorId.getValue(); + + if (actorIndex < 0) { + throw new ContractException(NucleusError.UNKNOWN_ACTOR_ID); + } + + if (actorIndex >= actorIds.size()) { + throw new ContractException(NucleusError.UNKNOWN_ACTOR_ID); + } + + ActorId existingActorId = actorIds.get(actorIndex); + + if (existingActorId == null) { + throw new ContractException(NucleusError.UNKNOWN_ACTOR_ID); + } + + actorIds.set(actorIndex, null); + + containsDeletedActors = true; + } + + @SuppressWarnings("unchecked") + protected T getDataManagerForActor(Class dataManagerClass) { + + if (dataManagerClass == null) { + throw new ContractException(NucleusError.NULL_DATA_MANAGER_CLASS); + } + + DataManager dataManager = workingClassToDataManagerMap.get(dataManagerClass); + /* + * If the working map does not contain the data manager, try to find a single + * match from the base map that was collected from the plugins. + * + * If two or more matches are found, then throw an exception. + * + * If exactly one match is found, update the working map. + * + * If no matches are found, nothing is done, but we are vulnerable to somewhat + * slower performance if the data manager is sought repeatedly. + */ + if (dataManager == null) { + List> candidates = new ArrayList<>(); + for (Class c : baseClassToDataManagerMap.keySet()) { + if (dataManagerClass.isAssignableFrom(c)) { + candidates.add(c); + } + } + if (candidates.size() > 1) { + throw new ContractException(NucleusError.AMBIGUOUS_DATA_MANAGER_CLASS); + } + if (candidates.size() == 1) { + dataManager = baseClassToDataManagerMap.get(candidates.get(0)); + workingClassToDataManagerMap.put(dataManagerClass, dataManager); + } + } + + if (dataManager == null) { + throw new ContractException(NucleusError.UNKNOWN_DATA_MANAGER, " : " + dataManagerClass.getSimpleName()); + } + return (T) dataManager; + } + + @SuppressWarnings("unchecked") + protected T getDataManagerForDataManager(DataManagerId dataManagerId, + Class dataManagerClass) { + + if (dataManagerClass == null) { + throw new ContractException(NucleusError.NULL_DATA_MANAGER_CLASS); + } + + DataManager dataManager = workingClassToDataManagerMap.get(dataManagerClass); + /* + * If the working map does not contain the data manager, try to find a single + * match from the base map that was collected from the plugins. + * + * If two or more matches are found, then throw an exception. + * + * If exactly one match is found, update the working map. + * + * If no matches are found, nothing is done, but we are vulnerable to somewhat + * slower performance if the data manager is sought repeatedly. + */ + if (dataManager == null) { + List> candidates = new ArrayList<>(); + for (Class c : baseClassToDataManagerMap.keySet()) { + if (dataManagerClass.isAssignableFrom(c)) { + candidates.add(c); + } + } + if (candidates.size() > 1) { + throw new ContractException(NucleusError.AMBIGUOUS_DATA_MANAGER_CLASS); + } + if (candidates.size() == 1) { + dataManager = baseClassToDataManagerMap.get(candidates.get(0)); + workingClassToDataManagerMap.put(dataManagerClass, dataManager); + } + } + + if (dataManager == null) { + throw new ContractException(NucleusError.UNKNOWN_DATA_MANAGER, " : " + dataManagerClass.getSimpleName()); + } + + int requestorId = dataManagerId.getValue(); + DataManagerId dataManagerId2 = dataManagerToDataManagerIdMap.get(dataManager); + int requesteeId = dataManagerId2.getValue(); + + boolean accessGranted = dataManagerAccessPermissions[requestorId][requesteeId]; + + if (!accessGranted) { + String dmName1 = dataManagerIdToDataManagerMap.get(dataManagerId).getClass().getSimpleName(); + String dmName2 = dataManagerClass.getSimpleName(); + throw new ContractException(NucleusError.DATA_MANAGER_ACCESS_VIOLATION, dmName1 + "-->" + dmName2); + } + return (T) dataManager; + } + + ///////////////////////////////// + // data manager support + ///////////////////////////////// + /* + * Combines a consumer of event with a data manager id so that the event + * consumers can be sorted to comply with the dependency DAG. Note that the + * consumer is itself a wrapper around another consumer that takes in a context + * and a specific event type. + */ + private static class DataManagerEventConsumer implements Comparable { + private final Consumer consumer; + private final DataManagerId dataManagerId; + + public DataManagerEventConsumer(DataManagerId dataManagerId, Consumer consumer) { + this.consumer = consumer; + this.dataManagerId = dataManagerId; + } + + @Override + public int compareTo(DataManagerEventConsumer other) { + return this.dataManagerId.compareTo(other.dataManagerId); + } + } + + private static class ReportEventConsumer implements Consumer { + + private final Consumer consumer; + private final ReportId reportId; + + public ReportEventConsumer(ReportId reportId, Consumer consumer) { + this.consumer = consumer; + this.reportId = reportId; + } + + @Override + public void accept(Event event) { + consumer.accept(event); + } + } + + // used for subscriptions + private final Map, List> dataManagerEventMap = new LinkedHashMap<>(); + private final Map, List> reportEventMap = new LinkedHashMap<>(); + + // used for retrieving and canceling plans owned by data managers + private final Map> dataManagerPlanMap = new LinkedHashMap<>(); + + // used to locate data managers by class type + private Map, DataManager> baseClassToDataManagerMap = new LinkedHashMap<>(); + private Map, DataManager> workingClassToDataManagerMap = new LinkedHashMap<>(); + + /* + * Maps of data manager id <--> data manager instances used primarily for access + * permissions between data managers + */ + private List dataManagerIds = new ArrayList<>(); + private Map dataManagerIdToDataManagerMap = new LinkedHashMap<>(); + private Map dataManagerToDataManagerIdMap = new LinkedHashMap<>(); + + private Map dataManagerIdToDataManagerContextMap = new LinkedHashMap<>(); + + private Map dataManagerIdToPluginIdMap = new LinkedHashMap<>(); + private boolean[][] dataManagerAccessPermissions; + + ////////////////////////////// + // actor support + ////////////////////////////// + + private ActorContext actorContext; + private ReportContext reportContext; + + private final List actorIds = new ArrayList<>(); + private final List reportIds = new ArrayList<>(); + + private boolean containsDeletedActors; + + private final Map> actorPlanMap = new LinkedHashMap<>(); + private final Map> reportPlanMap = new LinkedHashMap<>(); + + private final Deque actorQueue = new ArrayDeque<>(); + private final Deque dataManagerQueue = new ArrayDeque<>(); + private final Deque reportQueue = new ArrayDeque<>(); + + protected ActorId focalActorId; + protected ReportId focalReportId; + + private static class ActorContentRec { + + private Event event; + + private Consumer consumer; + + private Consumer plan; + + private ActorId actorId; + + } + + private static class ReportContentRec { + + private Event event; + + private Consumer consumer; + + private Consumer reportPlan; + + private ReportId reportId; + + } + + private static class DataManagerContentRec { + + private Event event; + + private Consumer consumer; + + private Consumer dmPlan; + + private DataManagerId dataManagerId; + + } + + protected boolean subscribersExistForEvent(Class eventClass) { + return (reportEventMap.containsKey(eventClass) || dataManagerEventMap.containsKey(eventClass) + || rootNode.children.containsKey(eventClass) || rootNode.consumers.containsKey(eventClass)); + } + + /* + * Recursively processes the event through the filter node . Events should be + * processed through the root filter node. Each node's consumers have each such + * consumer scheduled onto the actor queue for delayed execution of the + * consumer. + */ + private void broadcastEventToFilterNode(final Event event, FilterNode filterNode) { + + // determine the value of the function for the given event + Object value = filterNode.function.apply(event); + + // use that value to place any consumers that are matched to that value + // on the actor queue + Map> consumerMap = filterNode.consumers.get(value); + if (consumerMap != null) { + for (ActorId actorId : consumerMap.keySet()) { + Consumer consumer = consumerMap.get(actorId); + final ActorContentRec actorContentRec = new ActorContentRec(); + actorContentRec.event = event; + actorContentRec.actorId = actorId; + actorContentRec.consumer = consumer; + actorQueue.add(actorContentRec); + } + } + + // match the value to any child nodes and recursively call this method + // on that node + Map, FilterNode> childMap = filterNode.children.get(value); + if (childMap != null) { + for (Object id : childMap.keySet()) { + FilterNode childNode = childMap.get(id); + if (childNode != null) { + broadcastEventToFilterNode(event, childNode); + } + } + } + } + + /* + * Generates a filter node to be used as the root filter node. + */ + private FilterNode generateRootNode() { + FilterNode result = new FilterNode(); + result.function = (event) -> event.getClass(); + return result; + } + + private final FilterNode rootNode = generateRootNode(); + + /* + * A data structure for containing a function that process an event and returns + * a value. The node contains maps of the return value that either match actor + * consumers of the event or child nodes to this node. The nodes thus form a + * tree with a single root node that filters by event class type. The tree + * represents simple conjunctive event filters of the form: + * + * F1(e)=A & F2(e)=B &... + */ + private static class FilterNode { + + // the parent node is used during the unsubscribe process + private FilterNode parent; + + // the id is used during the unsubscribe process + private IdentifiableFunction identifiableFunction; + + private Function function; + + // value of function, id of child filter node, FilterNode + private Map, FilterNode>> children = new LinkedHashMap<>(); + + // value of function, actor id, Consumer + private Map>> consumers = new LinkedHashMap<>(); + + /* + * Integrates a function and its id and target value into the tree at this node + * as a child node if the node does not already exist. Note that functions + * cannot be compared for equality and that the id value takes on this role for + * the function. + */ + @SuppressWarnings("unchecked") + private FilterNode addChildNode(Object value, IdentifiableFunction identifiableFunction) { + Map, FilterNode> map = children.get(value); + if (map == null) { + map = new LinkedHashMap<>(); + children.put(value, map); + } + + FilterNode filterNode = map.get(identifiableFunction); + if (filterNode == null) { + filterNode = new FilterNode(); + filterNode.identifiableFunction = identifiableFunction; + filterNode.parent = this; + filterNode.function = event -> identifiableFunction.getFunction().apply((T) event); + map.put(identifiableFunction, filterNode); + } + return filterNode; + } + + } + + /* + * Subscribes the current actor (focalActorId) to the event subject to the event + * filter. This overwrites the current consumer associated with this event and + * filter if it is present. + */ + protected void subscribeActorToEventByFilter(EventFilter eventFilter, + BiConsumer eventConsumer) { + if (eventFilter == null) { + throw new ContractException(NucleusError.NULL_EVENT_FILTER); + } + + if (eventConsumer == null) { + throw new ContractException(NucleusError.NULL_EVENT_CONSUMER); + } + + /* + * We wrap the typed consumer with a consumer of event, knowing that the cast to + * (T) is safe. This simplifies the FilterNode class. + */ + @SuppressWarnings("unchecked") + Consumer consumer = event -> eventConsumer.accept(actorContext, (T) event); + + /* + * The event filter does not contain a FunctionValue associated with the root + * filter node, so we have to integrate it with the root node by assigning the + * associated value to the class type that matches the generics type of the + * event filter. + */ + Object value = eventFilter.getEventClass(); + FilterNode filterNode = rootNode; + + /* + * We loop through the (function,value) pairs, integrating each into the tree of + * filter nodes and creating new filter nodes as needed. + */ + for (Pair, Object> pair : eventFilter.getFunctionValuePairs()) { + filterNode = filterNode.addChildNode(value, pair.getFirst()); + value = pair.getSecond(); + } + + /* + * The final node will contain the consumer + */ + Map> consumerMap = filterNode.consumers.get(value); + if (consumerMap == null) { + consumerMap = new LinkedHashMap<>(); + filterNode.consumers.put(value, consumerMap); + } + Consumer previousConsumer = consumerMap.put(focalActorId, consumer); + if (previousConsumer != null) { + throw new ContractException(NucleusError.DUPLICATE_EVENT_SUBSCRIPTION); + } + + } + + /* + * Removes the consumer from the filter node tree and removes nodes that are + * empty(no children, no consumers), except for the root node. + */ + protected void unsubscribeActorFromEventByFilter(EventFilter eventFilter) { + if (eventFilter == null) { + throw new ContractException(NucleusError.NULL_EVENT_FILTER); + } + + // start at the root filter node + Object value = eventFilter.getEventClass(); + FilterNode filterNode = rootNode; + + /* + * Walk down the tree and if we find that any of the function id values are not + * present then we simply return since the consumer cannot exist + */ + for (Pair, Object> pair : eventFilter.getFunctionValuePairs()) { + Map, FilterNode> map = filterNode.children.get(value); + if (map == null) { + return; + } + Object id = pair.getFirst(); + filterNode = map.get(id); + if (filterNode == null) { + return; + } + value = pair.getSecond(); + } + + /* + * The last node may contain the consumer + */ + Map> consumerMap = filterNode.consumers.get(value); + if (consumerMap == null) { + return; + } + + consumerMap.remove(focalActorId); + if (consumerMap.isEmpty()) { + filterNode.consumers.remove(value); + } + + /* + * Walk back up the tree, removing nodes that have neither child nodes nor + * consumers. Once we hit a node that does not need removal we can stop since + * all ancestor nodes will also not be empty. + */ + while (filterNode.parent != null) { + boolean removeNode = filterNode.children.isEmpty() && filterNode.consumers.isEmpty(); + if (removeNode) { + filterNode.parent.children.remove(filterNode.identifiableFunction); + } else { + break; + } + filterNode = filterNode.parent; + } + + } + + /** + * Returns the time (floating point days) of simulation start. + */ + protected double getStartTime() { + return data.simulationState.getStartTime(); + } + + /** + * Returns the base date that synchronizes with simulation time zero. + */ + protected LocalDate getBaseDate() { + return data.simulationState.getBaseDate(); + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/SimulationState.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/SimulationState.java new file mode 100644 index 000000000..49c4db360 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/SimulationState.java @@ -0,0 +1,235 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * An immutable data class that holds 1) the base date aligned to simulation + * time zero and 2) the simulation start time as a floating point number of + * days. + */ +@Immutable +public class SimulationState { + + private static class Data { + private double startTime = 0; + private LocalDate baseDate = LocalDate.now(); + private long planningQueueArrivalId; + private List planQueueDatas = new ArrayList<>(); + + public Data() { + } + + public Data(Data data) { + startTime = data.startTime; + baseDate = data.baseDate; + planningQueueArrivalId = data.planningQueueArrivalId; + planQueueDatas.addAll(data.planQueueDatas); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((baseDate == null) ? 0 : baseDate.hashCode()); + result = prime * result + ((planQueueDatas == null) ? 0 : planQueueDatas.hashCode()); + result = prime * result + (int) (planningQueueArrivalId ^ (planningQueueArrivalId >>> 32)); + long temp; + temp = Double.doubleToLongBits(startTime); + result = prime * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + if (baseDate == null) { + if (other.baseDate != null) { + return false; + } + } else if (!baseDate.equals(other.baseDate)) { + return false; + } + if (planQueueDatas == null) { + if (other.planQueueDatas != null) { + return false; + } + } else if (!planQueueDatas.equals(other.planQueueDatas)) { + return false; + } + if (planningQueueArrivalId != other.planningQueueArrivalId) { + return false; + } + if (Double.doubleToLongBits(startTime) != Double.doubleToLongBits(other.startTime)) { + return false; + } + return true; + } + } + + private final Data data; + + private SimulationState(Data data) { + this.data = data; + } + + public static Builder builder() { + return new Builder(new Data()); + } + + /** + * Builder class for SimulationTime + */ + public static class Builder { + private Data data; + + private Builder(Data data) { + this.data = data; + } + + private void validate() { + + for (PlanQueueData planQueueData : data.planQueueDatas) { + if (planQueueData.getTime() < data.startTime) { + throw new ContractException(NucleusError.PLANNING_QUEUE_TIME); + + } + if (planQueueData.getArrivalId() >= data.planningQueueArrivalId) { + throw new ContractException(NucleusError.PLANNING_QUEUE_ARRIVAL_INVALID); + } + } + + } + + /** + * Builds the SimulationState from the collected data + * + * @throws ContractException + *
      + *
    • {@linkplain NucleusError#PLANNING_QUEUE_ARRIVAL_INVALID} + * if the planning queue arrival id does not exceed + * the arrival id values for all stored + * PlanQueueData
    • + *
    • {@linkplain NucleusError#PLANNING_QUEUE_TIME} + * if the simulation start time is exceeded by any + * time value stored for a plan
    • + *
    + */ + public SimulationState build() { + validate(); + return new SimulationState(new Data(data)); + } + + /** + * Sets the time (floating point days) of simulation start. Defaults to zero. + */ + public Builder setStartTime(double startTime) { + data.startTime = startTime; + return this; + } + + /** + * Sets the base date that synchronizes with simulation time zero. Defaults to + * the current date. + * + * @throws ContractException {@linkplain NucleusError#NULL_BASE_DATE} if the + * base date is null + */ + public Builder setBaseDate(LocalDate localDate) { + if (localDate == null) { + throw new ContractException(NucleusError.NULL_BASE_DATE); + } + data.baseDate = localDate; + return this; + } + + /** + * Adds a PlanQueueData used for plan queue reconstruction + * + * @throws ContractException {@linkplain NucleusError#NULL_PLAN_QUEUE_DATA} if + * the plan queue data is null + */ + public Builder addPlanQueueData(PlanQueueData planQueueData) { + if (planQueueData == null) { + throw new ContractException(NucleusError.NULL_PLAN_QUEUE_DATA); + } + data.planQueueDatas.add(planQueueData); + return this; + } + + /** + * Sets the next arrival id available to the planning queue + */ + public Builder setPlanningQueueArrivalId(long planningQueueArrivalId) { + data.planningQueueArrivalId = planningQueueArrivalId; + return this; + } + } + + /** + * Returns the time (floating point days) of simulation start. + */ + public double getStartTime() { + return data.startTime; + } + + /** + * Returns the base date that synchronizes with simulation time zero. + */ + public LocalDate getBaseDate() { + return data.baseDate; + } + + /** + * Returns the list of PlanQueueData objects. + */ + public List getPlanQueueDatas() { + return new ArrayList<>(data.planQueueDatas); + } + + /** + * Returns the planning queue arrival id that should be used as the first free + * arrival id. + */ + public long getPlanningQueueArrivalId() { + return data.planningQueueArrivalId; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof SimulationState)) { + return false; + } + SimulationState other = (SimulationState) obj; + if (data == null) { + if (other.data != null) { + return false; + } + } else if (!data.equals(other.data)) { + return false; + } + return true; + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/SimulationStateCollector.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/SimulationStateCollector.java new file mode 100644 index 000000000..1c71b0335 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/SimulationStateCollector.java @@ -0,0 +1,57 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +public class SimulationStateCollector implements Consumer { + private final BiConsumer> outputConsumer; + private final Consumer experimentOpenConsumer; + + private final Map> observedOutputObjects = new LinkedHashMap<>(); + + public SimulationStateCollector(BiConsumer> outputConsumer, + Consumer experimentOpenConsumer) { + this.outputConsumer = outputConsumer; + this.experimentOpenConsumer = experimentOpenConsumer; + } + + @Override + public synchronized void accept(ExperimentContext experimentContext) { + experimentContext.subscribeToExperimentOpen(this.experimentOpenConsumer); + experimentContext.subscribeToOutput(PluginData.class, this::handlePluginDataOuput); + experimentContext.subscribeToOutput(SimulationState.class, this::handleSimulationTimeOuput); + experimentContext.subscribeToSimulationClose(this::handleSimulationClose); + } + + private synchronized void handleSimulationClose(ExperimentContext experimentContext, Integer scenarioId) { + List list = observedOutputObjects.remove(scenarioId); + if (list != null) { + outputConsumer.accept(scenarioId, list); + } + } + + private synchronized void handlePluginDataOuput(ExperimentContext experimentContext, Integer scenarioId, + PluginData pluginData) { + List list = observedOutputObjects.get(scenarioId); + if (list == null) { + list = new ArrayList<>(); + observedOutputObjects.put(scenarioId, list); + } + list.add(pluginData); + } + + private synchronized void handleSimulationTimeOuput(ExperimentContext experimentContext, Integer scenarioId, + SimulationState simulationState) { + List list = observedOutputObjects.get(scenarioId); + if (list == null) { + list = new ArrayList<>(); + observedOutputObjects.put(scenarioId, list); + } + list.add(simulationState); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/StatusConsoleState.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/StatusConsoleState.java new file mode 100644 index 000000000..b81f81dcb --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/StatusConsoleState.java @@ -0,0 +1,56 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import net.jcip.annotations.ThreadSafe; + +/** + * Thread safe state container for the Experiment Status Console + */ +@ThreadSafe +public final class StatusConsoleState { + private boolean immediateErrorReporting; + private boolean reportScenarioProgress; + private int stackTraceReportLimit; + private int lastReportedCompletionPercentage; + private int immediateStackTraceCount; + + public synchronized boolean immediateErrorReporting() { + return immediateErrorReporting; + } + + public synchronized void setImmediateErrorReporting(boolean immediateErrorReportingx) { + this.immediateErrorReporting = immediateErrorReportingx; + } + + public synchronized boolean reportScenarioProgress() { + return reportScenarioProgress; + } + + public synchronized void setReportScenarioProgress(boolean reportScenarioProgressx) { + this.reportScenarioProgress = reportScenarioProgressx; + } + + public synchronized int getStackTraceReportLimit() { + return stackTraceReportLimit; + } + + public synchronized void setStackTraceReportLimit(int stackTraceReportLimitx) { + this.stackTraceReportLimit = stackTraceReportLimitx; + } + + public synchronized int getLastReportedCompletionPercentage() { + return lastReportedCompletionPercentage; + } + + public synchronized void setLastReportedCompletionPercentage(int lastReportPercentage) { + this.lastReportedCompletionPercentage = lastReportPercentage; + } + + public synchronized int getImmediateStackTraceCount() { + return immediateStackTraceCount; + } + + public synchronized void incrementImmediateStackTraceCount() { + this.immediateStackTraceCount++; + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/runcontinuityplugin/RunContinuityActor.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/runcontinuityplugin/RunContinuityActor.java new file mode 100644 index 000000000..69521699a --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/runcontinuityplugin/RunContinuityActor.java @@ -0,0 +1,73 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.runcontinuityplugin; + +import java.util.List; +import java.util.function.Consumer; + +import org.apache.commons.math3.util.Pair; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.Plan; +import util.wrappers.MutableInteger; + +public class RunContinuityActor implements Consumer { + private MutableInteger completionCount = new MutableInteger(); + private ActorContext actorContext; + private final RunContinuityPluginData runContinuityPluginData; + + public RunContinuityActor(RunContinuityPluginData runContinuityPluginData) { + this.runContinuityPluginData = runContinuityPluginData; + } + + public void accept(ActorContext actorContext) { + this.actorContext = actorContext; + actorContext.setPlanDataConverter(RunContinuityPlanData.class, this::getConsumerFromPlanData); + + completionCount.setValue(runContinuityPluginData.getCompletionCount()); + + if (!runContinuityPluginData.plansAreScheduled()) { + List>> consumers = runContinuityPluginData.getConsumers(); + for (int i = 0; i < consumers.size(); i++) { + Pair> pair = consumers.get(i); + double time = pair.getFirst(); + Consumer consumer = pair.getSecond(); + + RunContinuityPlanData continuityPluginData = new RunContinuityPlanData(i); + + Plan plan = Plan.builder(ActorContext.class)// + .setTime(time)// + .setCallbackConsumer((c) -> executePlan(consumer))// + .setPlanData(continuityPluginData)// + .build(); + + actorContext.addPlan(plan); + } + } + actorContext.subscribeToSimulationClose(this::recordState); + } + + private Consumer getConsumerFromPlanData(RunContinuityPlanData runContinuityPlanData) { + Consumer consumer = runContinuityPluginData.getConsumers().get(runContinuityPlanData.getId()) + .getSecond(); + return (c) -> executePlan(consumer); + } + + private void executePlan(Consumer consumer) { + completionCount.increment(); + consumer.accept(actorContext); + } + + private void recordState(ActorContext actorContext) { + RunContinuityPluginData.Builder builder = RunContinuityPluginData.builder(); + + builder.setCompletionCount(completionCount.getValue()); + List>> consumers = runContinuityPluginData.getConsumers(); + for (Pair> pair : consumers) { + double time = pair.getFirst(); + Consumer consumer = pair.getSecond(); + builder.addContextConsumer(time, consumer); + } + builder.setPlansAreScheduled(true); + actorContext.releaseOutput(builder.build()); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/runcontinuityplugin/RunContinuityError.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/runcontinuityplugin/RunContinuityError.java new file mode 100644 index 000000000..738f8f681 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/runcontinuityplugin/RunContinuityError.java @@ -0,0 +1,25 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.runcontinuityplugin; + +import util.errors.ContractError; +import util.errors.ContractException; + +/** + * An enumeration supporting {@link ContractException} that acts as a general + * description of the exception. + */ +public enum RunContinuityError implements ContractError { + + NULL_RUN_CONTINUITY_PLUGN_DATA("Null run continuity plugin data"), // + ; + + private final String description; + + private RunContinuityError(final String description) { + this.description = description; + } + + @Override + public String getDescription() { + return description; + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/runcontinuityplugin/RunContinuityPlanData.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/runcontinuityplugin/RunContinuityPlanData.java new file mode 100644 index 000000000..2b1276d33 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/runcontinuityplugin/RunContinuityPlanData.java @@ -0,0 +1,17 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.runcontinuityplugin; + +import gov.hhs.aspr.ms.gcm.nucleus.PlanData; + +public final class RunContinuityPlanData implements PlanData { + + private final int id; + + public RunContinuityPlanData(int id) { + this.id = id; + } + + public int getId() { + return id; + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/runcontinuityplugin/RunContinuityPlugin.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/runcontinuityplugin/RunContinuityPlugin.java new file mode 100644 index 000000000..9a83928de --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/runcontinuityplugin/RunContinuityPlugin.java @@ -0,0 +1,68 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.runcontinuityplugin; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import net.jcip.annotations.ThreadSafe; +import util.errors.ContractException; + +/** + * A plugin providing a person property management to the simulation. + */ +@ThreadSafe +public final class RunContinuityPlugin { + + private static class Data { + private RunContinuityPluginData runContinuityPluginData; + } + + private RunContinuityPlugin() { + + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Builder() { + } + + private Data data = new Data(); + + private void validate() { + if (data.runContinuityPluginData == null) { + throw new ContractException(RunContinuityError.NULL_RUN_CONTINUITY_PLUGN_DATA); + } + } + + /** + * Builds the RunContinuityPlugin from the collected inputs + * + * @throws ContractException {@linkplain RunContinuityError#NULL_RUN_CONTINUITY_PLUGN_DATA} + * if the runContinuityPluginData is null + */ + public Plugin build() { + + validate(); + Plugin.Builder builder = Plugin.builder();// + builder.setPluginId(RunContinuityPluginId.PLUGIN_ID);// + builder.addPluginData(data.runContinuityPluginData);// + + builder.setInitializer((c) -> { + RunContinuityPluginData runContinuityPluginData = c.getPluginData(RunContinuityPluginData.class).get(); + c.addActor(new RunContinuityActor(runContinuityPluginData)); + }); + return builder.build(); + + } + + /** + * Sets the RunContinuityPluginData + */ + public Builder setRunContinuityPluginData(RunContinuityPluginData runContinuityPluginData) { + data.runContinuityPluginData = runContinuityPluginData; + return this; + } + + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/runcontinuityplugin/RunContinuityPluginData.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/runcontinuityplugin/RunContinuityPluginData.java new file mode 100644 index 000000000..1d9b08b3a --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/runcontinuityplugin/RunContinuityPluginData.java @@ -0,0 +1,157 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.runcontinuityplugin; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import org.apache.commons.math3.util.Pair; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.PluginDataBuilder; +import net.jcip.annotations.Immutable; + +/** + * An immutable container of the initial state of continuity properties. + */ +@Immutable +public class RunContinuityPluginData implements PluginData { + + private static class Data { + + private boolean plansAreScheduled; + + private int completionCount; + + private List>> consumers = new ArrayList<>(); + + private boolean locked; + + private Data() { + } + + private Data(Data data) { + completionCount = data.completionCount; + consumers.addAll(data.consumers); + locked = data.locked; + } + + } + + /** + * Returns a new builder instance + */ + public static Builder builder() { + return new Builder(new Data()); + } + + /** + * Builder class for RunContinuityPluginData + */ + public static class Builder implements PluginDataBuilder { + private Data data; + + private void ensureDataMutability() { + if (data.locked) { + data = new Data(data); + data.locked = false; + } + } + + private void ensureImmutability() { + if (!data.locked) { + data.locked = true; + } + } + + private Builder(Data data) { + this.data = data; + } + + /** + * Builds the {@linkplain RunContinuityPluginData} from the collected data. + */ + public RunContinuityPluginData build() { + + if (!data.locked) { + validateData(); + } + ensureImmutability(); + return new RunContinuityPluginData(data); + } + + /** + * Sets the plan scheduling state. Defaults to false. + */ + public Builder setPlansAreScheduled(boolean plansAreScheduled) { + ensureDataMutability(); + data.plansAreScheduled = plansAreScheduled; + return this; + } + + /** + * Schedules a context consumer + */ + public Builder addContextConsumer(final double time, final Consumer consumer) { + ensureDataMutability(); + data.consumers.add(new Pair<>(time, consumer)); + return this; + } + + /** + * Sets the completion count + */ + public Builder setCompletionCount(final int completionCount) { + ensureDataMutability(); + data.completionCount = completionCount; + return this; + } + + private void validateData() { + // do nothing + } + + } + + private final Data data; + + private RunContinuityPluginData(Data data) { + this.data = data; + } + + /** + * Returns the completion count + */ + public int getCompletionCount() { + return data.completionCount; + } + + /** + * Returns the list scheduled consumers + */ + public List>> getConsumers() { + return new ArrayList<>(data.consumers); + } + + @Override + public PluginDataBuilder getCloneBuilder() { + return new Builder(data); + } + + /** + * Returns true if plans have been scheduled from the consumers + */ + public boolean plansAreScheduled() { + return data.plansAreScheduled; + } + + /** + * Returns true if the completion count is greater than or equal to the number + * of contained consumers. + */ + public boolean allPlansComplete() { + return data.completionCount >= data.consumers.size(); + + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/runcontinuityplugin/RunContinuityPluginId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/runcontinuityplugin/RunContinuityPluginId.java new file mode 100644 index 000000000..2ce1da7a3 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/runcontinuityplugin/RunContinuityPluginId.java @@ -0,0 +1,13 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.runcontinuityplugin; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; + +/** + * Static plugin id implementation for the person properties plugin + */ +public final class RunContinuityPluginId implements PluginId { + public final static PluginId PLUGIN_ID = new RunContinuityPluginId(); + + private RunContinuityPluginId() { + }; +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestActor.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestActor.java new file mode 100644 index 000000000..bf94f8afb --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestActor.java @@ -0,0 +1,39 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin; + +import java.util.List; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; + +/** + * Test Support actor implementation designed to execute test-defined behaviors + * from within the actor. The actor first registers its ActorId with its alias + * by registering it with the TestPlanDataManager. It then schedules the + * ActorActionPlans that were stored in the TestPluginData that were associated + * with its alias. Alias identification exists for the convenience of the test + * implementor so that tests can name actors and are not bound to the forced + * ordering pattern implied by ActorId values. + */ +public final class TestActor { + private final Object alias; + + /** + * Creates the test actor with its alias + */ + public TestActor(Object alias) { + this.alias = alias; + } + + /** + * Associates its ActorId. Schedules the ActorActionPlans that were stored in + * the ActionDataView that were associated with its alias. + */ + public void init(ActorContext actorContext) { + TestPlanDataManager testPlanDataManager = actorContext.getDataManager(TestPlanDataManager.class); + + List testActorPlans = testPlanDataManager.getTestActorPlans(alias); + for (final TestActorPlan testActorPlan : testActorPlans) { + actorContext.addPlan(testActorPlan::executeAction, testActorPlan.getScheduledTime()); + } + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestActorPlan.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestActorPlan.java new file mode 100644 index 000000000..4214ec45b --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestActorPlan.java @@ -0,0 +1,112 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin; + +import java.util.function.Consumer; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import util.errors.ContractException; + +/** + * Test Support class that describes an action for an actor as a scheduled plan + * with an optional key. + */ +public class TestActorPlan { + + private final double scheduledTime; + + private boolean executed; + + private final Consumer plan; + + /** + * Constructs an actor action plan. If assignKey is false, then this actor + * action plan will return an empty optional key. + * + * @throws ContractException {@linkplain TestError#NULL_PLAN} if the plan is + * null + */ + public TestActorPlan(final double scheduledTime, Consumer plan) { + + if (plan == null) { + throw new ContractException(TestError.NULL_PLAN); + } + + this.scheduledTime = scheduledTime; + + this.plan = plan; + } + + /** + * Boilerplate implementation of hashCode consistent with equals() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (executed ? 1231 : 1237); + long temp; + temp = Double.doubleToLongBits(scheduledTime); + result = prime * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + /** + * TestActorPlans are equal if and only they return the same values for + * 1)executed() and 2)getScheduledTime()This limited sense of equality is + * present simply to provide some reasonable evidence that the plugin data + * cloning method is working correctly for the test plugin data. + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof TestActorPlan)) { + return false; + } + TestActorPlan other = (TestActorPlan) obj; + if (executed != other.executed) { + return false; + } + + if (Double.doubleToLongBits(scheduledTime) != Double.doubleToLongBits(other.scheduledTime)) { + return false; + } + return true; + } + + /** + * Constructs an test actor plan from another test actor plan. + */ + public TestActorPlan(TestActorPlan testActorPlan) { + scheduledTime = testActorPlan.scheduledTime; + executed = testActorPlan.executed; + plan = testActorPlan.plan; + } + + /** + * Returns true if an only if this actor action plan was executed + */ + public boolean executed() { + return executed; + } + + /** + * Package access. Executes the embedded action and marks this action plan as + * executed. + */ + void executeAction(final ActorContext actorContext) { + try { + plan.accept(actorContext); + } finally { + executed = true; + } + } + + /** + * Returns the scheduled time for action execution + */ + public double getScheduledTime() { + return scheduledTime; + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestDataManager.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestDataManager.java new file mode 100644 index 000000000..7e56d6ed9 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestDataManager.java @@ -0,0 +1,41 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin; + +import java.util.List; + +import gov.hhs.aspr.ms.gcm.nucleus.DataManager; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; + +/** + * Test Support data manager implementation designed to execute test-defined + * behaviors from within the data manager. The data manager schedules the + * TestDataManagerPlans that were stored in the TestPluginData that were + * associated with its alias. Alias identification exists for the convenience of + * the test implementor so that tests can name data managers. + */ +public class TestDataManager extends DataManager { + private Object alias; + + /* + * Package level access for setting the alias of the data manager. This is done + * immediately after construction, before any integration into the experiment or + * simulation. + */ + void setAlias(Object alias) { + this.alias = alias; + } + + /** + * Associates its ActorId. Schedules the ActorActionPlans that were stored in + * the ActionDataView that were associated with its alias. + */ + @Override + public void init(DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + TestPlanDataManager testPlanDataManager = dataManagerContext.getDataManager(TestPlanDataManager.class); + + List testDataManagerPlans = testPlanDataManager.getTestDataManagerPlans(alias); + for (final TestDataManagerPlan testDataManagerPlan : testDataManagerPlans) { + dataManagerContext.addPlan(testDataManagerPlan::executeAction, testDataManagerPlan.getScheduledTime()); + } + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestDataManagerPlan.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestDataManagerPlan.java new file mode 100644 index 000000000..b57d5ffef --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestDataManagerPlan.java @@ -0,0 +1,110 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin; + +import java.util.function.Consumer; + +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; +import util.errors.ContractException; + +/** + * Test Support class that describes an action for a data manager as a scheduled + * plan with an optional key. + */ +public class TestDataManagerPlan { + + private final double scheduledTime; + + private boolean executed; + + private final Consumer plan; + + /** + * Constructs an test actor plan from another test actor plan. + */ + public TestDataManagerPlan(TestDataManagerPlan testDataManagerPlan) { + scheduledTime = testDataManagerPlan.scheduledTime; + executed = testDataManagerPlan.executed; + plan = testDataManagerPlan.plan; + } + + /** + * Constructs an data manager action plan. If assignKey is false, then this + * actor action plan will return an empty optional key. + * + * @throws ContractException {@linkplain TestError#NULL_PLAN} if the plan is + * null + */ + public TestDataManagerPlan(final double scheduledTime, Consumer plan) { + + if (plan == null) { + throw new ContractException(TestError.NULL_PLAN); + } + this.scheduledTime = scheduledTime; + this.plan = plan; + } + + /** + * Returns true if an only if this data manager action plan was executed + */ + public boolean executed() { + return executed; + } + + /** + * Package access. Executes the embedded action and marks this action plan as + * executed. + */ + void executeAction(final DataManagerContext dataManagerContext) { + try { + plan.accept(dataManagerContext); + } finally { + executed = true; + } + } + + /** + * Boilerplate implementation of hashCode consistent with equals() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (executed ? 1231 : 1237); + long temp; + temp = Double.doubleToLongBits(scheduledTime); + result = prime * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + /** + * TestDataManagerPlans are equal if and only they return the same values for + * 1)getKey(), 2)executed() and 3)getScheduledTime()This limited sense of + * equality is present simply to provide some reasonable evidence that the + * plugin data cloning method is working correctly for the test plugin data. + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof TestDataManagerPlan)) { + return false; + } + TestDataManagerPlan other = (TestDataManagerPlan) obj; + if (executed != other.executed) { + return false; + } + + if (Double.doubleToLongBits(scheduledTime) != Double.doubleToLongBits(other.scheduledTime)) { + return false; + } + return true; + } + + /** + * Returns the scheduled time for action execution + */ + public double getScheduledTime() { + return scheduledTime; + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestError.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestError.java new file mode 100644 index 000000000..41ca6d83e --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestError.java @@ -0,0 +1,33 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin; + +import util.errors.ContractError; +import util.errors.ContractException; + +/** + * An enumeration supporting {@link ContractException} that acts as a general + * description of the exception. + */ +public enum TestError implements ContractError { + MULTIPLE_MATCHING_ITEMS("Multiple items were found matching "), + MISSING_TEST_SCENARIO_REPORTS("Missing test scenario report likely due to not including a TestPlugin"), + DUPLICATE_TEST_SCENARIO_REPORTS("Duplicate test scenario reports"), NULL_ALIAS("Null alias value"), + NULL_OUTPUT_ITEM("Null released output item"), NULL_PLUGIN_ID("Null plugin id"), + NULL_DATA_MANAGER_SUPPLIER("Null data manager supplier"), NULL_PLAN("Null plan"), + TEST_EXECUTION_FAILURE("Not all action plans were executed or no action plans were added to the test plugin"), + UNKNOWN_DATA_MANAGER_ALIAS( + "A data manager test plan was submitted under an alias that does not have a test data manager class type"),; + + private final String description; + + private TestError(final String description) { + this.description = description; + } + + /** + * Returns the text description of the error + */ + @Override + public String getDescription() { + return description; + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestOutputConsumer.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestOutputConsumer.java new file mode 100644 index 000000000..3dbf2eda1 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestOutputConsumer.java @@ -0,0 +1,117 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Consumer; + +import util.errors.ContractException; +import util.wrappers.MutableInteger; + +/** + * A Testing utility class that will consume simulation output and includes a + * method to retrieve output items by class type + */ +public class TestOutputConsumer implements Consumer { + private List outputItems; + + public TestOutputConsumer() { + this.outputItems = new ArrayList<>(); + } + + /** + * Handles all output from a simulation, but processes only TestScenarioReport + * items. + * + * @throws ContractException {@linkplain TestError#NULL_OUTPUT_ITEM} if the obj + * is null + */ + public void accept(Object obj) { + if (obj == null) { + throw new ContractException(TestError.NULL_OUTPUT_ITEM); + } + this.outputItems.add(obj); + } + + /** + * Returns all outputs from a Simulation based on the Class Parameter in a map + * where the keys of the map are the output items and the values are the counts + * of how many times those items were encountered. The returned map contains no + * null keys or values. + * + * @param This type is derived from the class parameter and also + * determines the return type of this method. + * @param classRef The class for which you want to get output items of + * @return - returns a {@link Map} containing the output items as keys and the + * number of occurrences as the value + */ + public Map getOutputItemMap(Class classRef) { + Map sourceMap = new LinkedHashMap<>(); + Map retMap = new LinkedHashMap<>(); + + for (Object item : outputItems) { + if (classRef.isAssignableFrom(item.getClass())) { + T interestedItem = classRef.cast(item); + sourceMap.putIfAbsent(interestedItem, new MutableInteger()); + sourceMap.get(interestedItem).increment(); + } + } + + for (T item : sourceMap.keySet()) { + retMap.put(item, sourceMap.get(item).getValue()); + } + + return retMap; + } + + /** + * Returns the output from a Simulation based on the Class Parameter. + * + * @param This type is derived from the class parameter and also + * determines the return type of this method. + * @param classRef The class for which you want to get output items of + * @return - returns a {@link Map} containing the output items as keys and the + * number of occurrences as the value + * @throws ContractException {@linkplain TestError#MULTIPLE_MATCHING_ITEMS} if + * there are multiple items matching the given class + * reference + */ + public Optional getOutputItem(Class classRef) { + T result = null; + + for (Object item : outputItems) { + if (classRef.isAssignableFrom(item.getClass())) { + if (result == null) { + result = classRef.cast(item); + } else { + throw new ContractException(TestError.MULTIPLE_MATCHING_ITEMS); + } + } + } + + return Optional.ofNullable(result); + } + + /** + * Returns the output from a Simulation based on the Class Parameter. + * + * @param This type is derived from the class parameter and also + * determines the return type of this method. + * @param classRef The class for which you want to get output items of + * @return - returns a {@link Map} containing the output items as keys and the + * number of occurrences as the value + */ + public List getOutputItems(Class classRef) { + List result = new ArrayList<>(); + + for (Object item : outputItems) { + if (classRef.isAssignableFrom(item.getClass())) { + result.add(classRef.cast(item)); + } + } + + return result; + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestPlanDataManager.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestPlanDataManager.java new file mode 100644 index 000000000..5a099a18e --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestPlanDataManager.java @@ -0,0 +1,134 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import gov.hhs.aspr.ms.gcm.nucleus.DataManager; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; + +/** + * A data manager used by test actors and test data managers to retrieve plans + * by alias ids. + */ +public class TestPlanDataManager extends DataManager { + + private final Map> reportActionPlanMap = new LinkedHashMap<>(); + + private final Map> actorActionPlanMap = new LinkedHashMap<>(); + + private final Map> dataManagerActionPlanMap = new LinkedHashMap<>(); + + /** + * Constructs this data manager from the given test plugin data + */ + public TestPlanDataManager(TestPluginData testPluginData) { + + for (Object alias : testPluginData.getTestActorAliases()) { + List testActorPlans = testPluginData.getTestActorPlans(alias); + actorActionPlanMap.put(alias, testActorPlans); + } + + for (Object alias : testPluginData.getTestReportAliases()) { + List testReportPlans = testPluginData.getTestReportPlans(alias); + reportActionPlanMap.put(alias, testReportPlans); + } + + for (Object alias : testPluginData.getTestDataManagerAliases()) { + List testDataManagerPlans = testPluginData.getTestDataManagerPlans(alias); + dataManagerActionPlanMap.put(alias, testDataManagerPlans); + } + } + + /** + * Initializes this data manager by subscribing to simulation close. On close it + * releases a single TestScenarioReport that indicates success if there was at + * least one plan and all plans were executed. + */ + @Override + public void init(DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + dataManagerContext.subscribeToSimulationClose(this::sendActionCompletionReport); + } + + /** + * Returns all plans associated with the given actor alias + */ + public List getTestActorPlans(Object alias) { + List result = new ArrayList<>(); + List list = actorActionPlanMap.get(alias); + if (list != null) { + result.addAll(list); + } + return result; + } + + /** + * Returns all plans associated with the given report alias + */ + public List getTestReportPlans(Object alias) { + List result = new ArrayList<>(); + List list = reportActionPlanMap.get(alias); + if (list != null) { + result.addAll(list); + } + return result; + } + + /** + * Returns all plans associated with the given data manager alias + */ + public List getTestDataManagerPlans(Object alias) { + List result = new ArrayList<>(); + + List list = dataManagerActionPlanMap.get(alias); + if (list != null) { + result.addAll(list); + } + return result; + } + + /* + * Return true if and only if all actions that are stored in this action data + * view have been executed. Indicates that all injected behaviors in a unit test + * were actually executed. RETURNS FALSE IF THERE WERE NO ACTIONS STORED. + */ + private void sendActionCompletionReport(DataManagerContext context) { + context.releaseOutput(new TestScenarioReport(allActionsExecuted())); + } + + private boolean allActionsExecuted() { + int planCount = 0; + for (Object alias : actorActionPlanMap.keySet()) { + for (TestActorPlan testActorPlan : actorActionPlanMap.get(alias)) { + planCount++; + if (!testActorPlan.executed()) { + return false; + } + } + } + + for (Object alias : reportActionPlanMap.keySet()) { + for (TestReportPlan testReportPlan : reportActionPlanMap.get(alias)) { + planCount++; + if (!testReportPlan.executed()) { + return false; + } + } + } + + for (Object alias : dataManagerActionPlanMap.keySet()) { + for (TestDataManagerPlan testDataManagerPlan : dataManagerActionPlanMap.get(alias)) { + planCount++; + if (!testDataManagerPlan.executed()) { + return false; + } + } + } + + return planCount > 0; + + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestPlugin.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestPlugin.java new file mode 100644 index 000000000..de1a18eb9 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestPlugin.java @@ -0,0 +1,71 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin; + +import java.util.List; +import java.util.Optional; + +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.PluginContext; +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import util.errors.ContractException; + +/** + * Static test support plugin that is designed to work with a unit testing + * framework. It provides for the injection of behavior into actors and data + * managers to test various simulation behaviors in a function/system setting. + */ +public class TestPlugin { + + private TestPlugin() { + } + + /** + * Initializes a simulation via the given context. Using a TestPluginData + * retrieved from the context, this initializer adds test actor and test data + * manager instances that are used in testing. It also creates an + * TestPlanDataManager that is used internally to this plugin to help manage + * plan distribution for the aforementioned actors and data managers. + * + * @throws ContractException {@linkplain NucleusError#NULL_PLUGIN_CONTEXT} if + * the pluginContext is null + */ + private static void init(PluginContext pluginContext) { + if (pluginContext == null) { + throw new ContractException(NucleusError.NULL_PLUGIN_CONTEXT); + } + + TestPluginData testPluginData = pluginContext.getPluginData(TestPluginData.class).get(); + + TestPlanDataManager testPlanDataManager = new TestPlanDataManager(testPluginData); + pluginContext.addDataManager(testPlanDataManager); + + List dataManagerAliases = testPluginData.getTestDataManagerAliases(); + for (Object alias : dataManagerAliases) { + Optional optional = testPluginData.getTestDataManager(alias); + if (optional.isPresent()) { + pluginContext.addDataManager(optional.get()); + } + } + + for (Object alias : testPluginData.getTestActorAliases()) { + pluginContext.addActor(new TestActor(alias)::init); + } + + for (Object alias : testPluginData.getTestReportAliases()) { + pluginContext.addReport(new TestReport(alias)::init); + } + + } + + public static Plugin getTestPlugin(TestPluginData testPluginData) { + Plugin.Builder builder = Plugin.builder();// + builder.setInitializer(TestPlugin::init);// + for (PluginId pluginId : testPluginData.getPluginDependencies()) { + builder.addPluginDependency(pluginId); + } + builder.addPluginData(testPluginData);// + builder.setPluginId(TestPluginId.PLUGIN_ID);// + return builder.build();// + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestPluginData.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestPluginData.java new file mode 100644 index 000000000..96d320ba8 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestPluginData.java @@ -0,0 +1,432 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Supplier; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.PluginDataBuilder; +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import net.jcip.annotations.ThreadSafe; +import util.errors.ContractException; + +/** + * Thread safe plugin data container for associating plans with actor and data + * manager aliases. + */ +@ThreadSafe +public class TestPluginData implements PluginData { + + private static class Data { + + private Data() { + } + + private Data(Data data) { + + for (Object alias : data.testActorPlanMap.keySet()) { + List oldPlans = data.testActorPlanMap.get(alias); + List newPlans = new ArrayList<>(); + testActorPlanMap.put(alias, newPlans); + for (TestActorPlan oldPlan : oldPlans) { + TestActorPlan newPlan = new TestActorPlan(oldPlan); + newPlans.add(newPlan); + } + } + + for (Object alias : data.testReportPlanMap.keySet()) { + List oldPlans = data.testReportPlanMap.get(alias); + List newPlans = new ArrayList<>(); + testReportPlanMap.put(alias, newPlans); + for (TestReportPlan oldPlan : oldPlans) { + TestReportPlan newPlan = new TestReportPlan(oldPlan); + newPlans.add(newPlan); + } + } + + testDataManagerSuppliers.putAll(data.testDataManagerSuppliers); + + for (Object alias : data.testDataManagerPlanMap.keySet()) { + List oldPlans = data.testDataManagerPlanMap.get(alias); + List newPlans = new ArrayList<>(); + testDataManagerPlanMap.put(alias, newPlans); + for (TestDataManagerPlan oldPlan : oldPlans) { + TestDataManagerPlan newPlan = new TestDataManagerPlan(oldPlan); + newPlans.add(newPlan); + } + } + + this.pluginDependencies.addAll(data.pluginDependencies); + + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((pluginDependencies == null) ? 0 : pluginDependencies.hashCode()); + result = prime * result + ((testActorPlanMap == null) ? 0 : testActorPlanMap.hashCode()); + result = prime * result + ((testDataManagerPlanMap == null) ? 0 : testDataManagerPlanMap.hashCode()); + result = prime * result + ((testDataManagerSuppliers == null) ? 0 : testDataManagerSuppliers.hashCode()); + result = prime * result + ((testReportPlanMap == null) ? 0 : testReportPlanMap.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + if (pluginDependencies == null) { + if (other.pluginDependencies != null) { + return false; + } + } else if (!pluginDependencies.equals(other.pluginDependencies)) { + return false; + } + if (testActorPlanMap == null) { + if (other.testActorPlanMap != null) { + return false; + } + } else if (!testActorPlanMap.equals(other.testActorPlanMap)) { + return false; + } + if (testDataManagerPlanMap == null) { + if (other.testDataManagerPlanMap != null) { + return false; + } + } else if (!testDataManagerPlanMap.equals(other.testDataManagerPlanMap)) { + return false; + } + if (testDataManagerSuppliers == null) { + if (other.testDataManagerSuppliers != null) { + return false; + } + } else if (!testDataManagerSuppliers.equals(other.testDataManagerSuppliers)) { + return false; + } + if (testReportPlanMap == null) { + if (other.testReportPlanMap != null) { + return false; + } + } else if (!testReportPlanMap.equals(other.testReportPlanMap)) { + return false; + } + return true; + } + + private final Map> testReportPlanMap = new LinkedHashMap<>(); + + private final Map> testActorPlanMap = new LinkedHashMap<>(); + + private Map> testDataManagerSuppliers = new LinkedHashMap<>(); + + private final Map> testDataManagerPlanMap = new LinkedHashMap<>(); + + private final Set pluginDependencies = new LinkedHashSet<>(); + + } + + private TestPluginData(Data data) { + this.data = data; + + } + + /** + * Returns a builder for TestPluginData + */ + public static Builder builder() { + return new Builder(new Data()); + } + + public static class Builder implements PluginDataBuilder { + private Data data; + + private Builder(Data data) { + this.data = data; + } + + @Override + public TestPluginData build() { + validate(); + return new TestPluginData(new Data(data)); + } + + private void validate() { + + for (Object alias : data.testDataManagerPlanMap.keySet()) { + if (!data.testDataManagerSuppliers.containsKey(alias)) { + throw new ContractException(TestError.UNKNOWN_DATA_MANAGER_ALIAS, alias); + } + } + } + + /** + * Adds an actor action plan associated with the alias + * + * @throws ContractException + *
      + *
    • {@linkplain TestError#NULL_ALIAS} if the alias + * is null
    • + *
    • {@linkplain TestError#NULL_PLAN}if the actor + * action plan is null
    • + *
    + */ + public Builder addTestActorPlan(final Object alias, TestActorPlan testActorPlan) { + if (alias == null) { + throw new ContractException(TestError.NULL_ALIAS); + } + + if (testActorPlan == null) { + throw new ContractException(TestError.NULL_PLAN); + } + + List list = data.testActorPlanMap.get(alias); + + if (list == null) { + list = new ArrayList<>(); + data.testActorPlanMap.put(alias, list); + } + + list.add(testActorPlan); + + return this; + + } + + /** + * Adds an report action plan associated with the alias + * + * @throws ContractException + *
      + *
    • {@linkplain TestError#NULL_ALIAS} if the alias + * is null
    • + *
    • {@linkplain TestError#NULL_PLAN}if the actor + * action plan is null
    • + *
    + */ + public Builder addTestReportPlan(final Object alias, TestReportPlan testReportPlan) { + if (alias == null) { + throw new ContractException(TestError.NULL_ALIAS); + } + + if (testReportPlan == null) { + throw new ContractException(TestError.NULL_PLAN); + } + + List list = data.testReportPlanMap.get(alias); + + if (list == null) { + list = new ArrayList<>(); + data.testReportPlanMap.put(alias, list); + } + + list.add(testReportPlan); + + return this; + + } + + /** + * Adds a test data manager to the test plugin via a supplier of + * TestDataManager. The supplier must be threadsafe. + * + * @throws ContractException + *
      + *
    • {@link TestError#NULL_ALIAS} is the alias is + * null
    • + *
    • {@link TestError#NULL_DATA_MANAGER_SUPPLIER} if + * the supplier is null
    • + *
    + */ + public Builder addTestDataManager(Object alias, Supplier supplier) { + if (alias == null) { + throw new ContractException(TestError.NULL_ALIAS); + } + if (supplier == null) { + throw new ContractException(TestError.NULL_DATA_MANAGER_SUPPLIER); + } + data.testDataManagerSuppliers.put(alias, supplier); + return this; + } + + /** + * Adds an data manager action plan associated with the alias + * + * @throws ContractException + *
      + *
    • {@linkplain TestError#NULL_ALIAS} if the alias + * is null
    • + *
    • {@linkplain TestError#NULL_PLAN}if the actor + * action plan is null
    • + *
    + */ + public Builder addTestDataManagerPlan(final Object alias, TestDataManagerPlan testDataManagerPlan) { + + if (alias == null) { + throw new ContractException(TestError.NULL_ALIAS); + } + if (testDataManagerPlan == null) { + throw new ContractException(TestError.NULL_PLAN); + } + List list = data.testDataManagerPlanMap.get(alias); + + if (list == null) { + list = new ArrayList<>(); + data.testDataManagerPlanMap.put(alias, list); + } + + list.add(testDataManagerPlan); + + return this; + + } + + /** + * Adds a plugin dependency + * + * @throws ContractException {@linkplain TestError#NULL_PLUGIN_ID} if the plugin + * id is null + */ + public Builder addPluginDependency(final PluginId pluginId) { + if (pluginId == null) { + throw new ContractException(TestError.NULL_PLUGIN_ID); + } + data.pluginDependencies.add(pluginId); + return this; + } + } + + /** + * Returns a Builder that is initialized to contain the plans and suppliers of + * data managers contained in this TestPluginData. + */ + @Override + public Builder getCloneBuilder() { + return new Builder(new Data(data)); + } + + private final Data data; + + /** + * Returns a list of the test actor aliases + */ + public List getTestActorAliases() { + return new ArrayList<>(data.testActorPlanMap.keySet()); + } + + /** + * Returns a list of the test report aliases + */ + public List getTestReportAliases() { + return new ArrayList<>(data.testReportPlanMap.keySet()); + } + + /** + * Returns the test actor plans associated with the actor alias + */ + public List getTestActorPlans(Object alias) { + List result = new ArrayList<>(); + List list = data.testActorPlanMap.get(alias); + if (list != null) { + result.addAll(list); + } + return result; + } + + /** + * Returns the test report plans associated with the report alias + */ + public List getTestReportPlans(Object alias) { + List result = new ArrayList<>(); + List list = data.testReportPlanMap.get(alias); + if (list != null) { + result.addAll(list); + } + return result; + } + + /** + * Returns the plugin dependencies + */ + public Set getPluginDependencies() { + return new LinkedHashSet<>(data.pluginDependencies); + } + + /** + * Returns a test data manager instance from the given alias. + */ + @SuppressWarnings("unchecked") + public Optional getTestDataManager(Object alias) { + TestDataManager result = null; + Supplier supplier = data.testDataManagerSuppliers.get(alias); + if (supplier != null) { + result = supplier.get(); + result.setAlias(alias); + } + return Optional.ofNullable((T) result); + } + + /** + * Returns the test data manager plans associated with the actor alias + */ + public List getTestDataManagerPlans(Object alias) { + List list = data.testDataManagerPlanMap.get(alias); + List result = new ArrayList<>(); + if (list != null) { + result.addAll(list); + } + return result; + } + + /** + * Returns a list of the test data manager aliases + */ + public List getTestDataManagerAliases() { + return new ArrayList<>(data.testDataManagerSuppliers.keySet()); + } + + /** + * Hash code implementation consistent with equals() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + return result; + } + + /** + * TestPluginData instances are equal if and only if they contain identical + * plans and suppliers of test data managers. + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof TestPluginData)) { + return false; + } + TestPluginData other = (TestPluginData) obj; + if (data == null) { + if (other.data != null) { + return false; + } + } else if (!data.equals(other.data)) { + return false; + } + return true; + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestPluginFactory.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestPluginFactory.java new file mode 100644 index 000000000..3eb1cef11 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestPluginFactory.java @@ -0,0 +1,104 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import util.errors.ContractException; + +/** + * A static test support class for the {@linkplain TestPlugin}. Provides + * convenience methods for obtaining standarized PluginData for the listed + * Plugin. + *

    + * Also contains factory methods to obtain a list of plugins that is the minimal + * set needed to adequately test this Plugin that can be utilized with + *

    + * {@link TestSimulation#execute} + */ +public final class TestPluginFactory { + private TestPluginFactory() { + } + + private static class Data { + private TestPluginData testPluginData; + + private Data(TestPluginData testPluginData) { + this.testPluginData = testPluginData; + } + } + + /** + * Factory class that facilitates the building of {@linkplain PluginData} with + * the various setter methods. + */ + public static class Factory { + private Data data; + + private Factory(Data data) { + this.data = data; + } + + /** + * Returns a list of plugins containing a TestPlugin built from the contributed + * PluginDatas + *
      + *
    • TestPlugin is formed from the TestPluginData passed into + * {@link TestPluginFactory#factory}
    • + *
    + */ + public List getPlugins() { + List pluginsToAdd = new ArrayList<>(); + + Plugin testPlugin = TestPlugin.getTestPlugin(this.data.testPluginData); + pluginsToAdd.add(testPlugin); + + return pluginsToAdd; + } + } + + /** + * Creates a Factory that facilitates the creation of a minimal set of plugins + * needed to adequately test the {@link TestPlugin} by generating: + *
      + *
    • {@link TestPluginData}
    • + *
    + * via the {@link Factory#getPlugins()} method. + * + * @throws ContractException {@linkplain NucleusError#NULL_PLUGIN_DATA} if + * testPluginData is null + */ + public static Factory factory(TestPluginData testPluginData) { + if (testPluginData == null) { + throw new ContractException(NucleusError.NULL_PLUGIN_DATA); + } + return new Factory(new Data(testPluginData)); + } + + /** + * Returns a Factory that facilitates the creation of a minimal set of plugins + * needed to adequately test the {@link TestPlugin} by generating: + *
      + *
    • {@link TestPluginData}
    • + *
    + * via the {@link Factory#getPlugins()} method. + * + * @throws ContractException {@linkplain NucleusError#NULL_ACTOR_CONTEXT_CONSUMER} + * if consumer is null + */ + public static Factory factory(Consumer consumer) { + if (consumer == null) { + throw new ContractException(NucleusError.NULL_ACTOR_CONTEXT_CONSUMER); + } + TestPluginData testPluginData = TestPluginData.builder()// + .addTestActorPlan("actor", new TestActorPlan(0, consumer))// + .build(); + + return factory(testPluginData); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestPluginId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestPluginId.java new file mode 100644 index 000000000..22ef1551e --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestPluginId.java @@ -0,0 +1,14 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.nucleus.SimplePluginId; + +/** + * Static plugin id implementation for the TestPlugin + */ +public class TestPluginId { + private TestPluginId() { + } + + public final static PluginId PLUGIN_ID = new SimplePluginId(TestPluginId.class); +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestReport.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestReport.java new file mode 100644 index 000000000..e1d65189f --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestReport.java @@ -0,0 +1,39 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin; + +import java.util.List; + +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; + +/** + * Test Support report implementation designed to execute test-defined behaviors + * from within the report. The report first registers its ReportId with its + * alias by registering it with the TestPlanDataManager. It then schedules the + * ReportActionPlans that were stored in the TestPluginData that were associated + * with its alias. Alias identification exists for the convenience of the test + * implementor so that tests can name reports and are not bound to the forced + * ordering pattern implied by ReportId values. + */ +public final class TestReport { + private final Object alias; + + /** + * Creates the test actor with its alias + */ + public TestReport(Object alias) { + this.alias = alias; + } + + /** + * Associates its ReportId. Schedules the ReprtActionPlans that were stored in + * the ActionDataView that were associated with its alias. + */ + public void init(ReportContext reportContext) { + TestPlanDataManager testPlanDataManager = reportContext.getDataManager(TestPlanDataManager.class); + + List testReportPlans = testPlanDataManager.getTestReportPlans(alias); + for (final TestReportPlan testReportPlan : testReportPlans) { + reportContext.addPlan(testReportPlan::executeAction, testReportPlan.getScheduledTime()); + } + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestReportPlan.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestReportPlan.java new file mode 100644 index 000000000..5f5d783be --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestReportPlan.java @@ -0,0 +1,112 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin; + +import java.util.function.Consumer; + +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import util.errors.ContractException; + +/** + * Test Support class that describes an action for an actor as a scheduled plan + * with an optional key. + */ +public class TestReportPlan { + + private final double scheduledTime; + + private boolean executed; + + private final Consumer plan; + + /** + * Constructs an report action plan. If assignKey is false, then this report + * action plan will return an empty optional key. + * + * @throws ContractException {@linkplain TestError#NULL_PLAN} if the plan is + * null + */ + public TestReportPlan(final double scheduledTime, Consumer plan) { + + if (plan == null) { + throw new ContractException(TestError.NULL_PLAN); + } + + this.scheduledTime = scheduledTime; + + this.plan = plan; + } + + /** + * Boilerplate implementation of hashCode consistent with equals() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (executed ? 1231 : 1237); + long temp; + temp = Double.doubleToLongBits(scheduledTime); + result = prime * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + /** + * TestActorPlans are equal if and only they return the same values for + * 1)executed() and 2)getScheduledTime()This limited sense of equality is + * present simply to provide some reasonable evidence that the plugin data + * cloning method is working correctly for the test plugin data. + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof TestReportPlan)) { + return false; + } + TestReportPlan other = (TestReportPlan) obj; + if (executed != other.executed) { + return false; + } + + if (Double.doubleToLongBits(scheduledTime) != Double.doubleToLongBits(other.scheduledTime)) { + return false; + } + return true; + } + + /** + * Constructs an test actor plan from another test actor plan. + */ + public TestReportPlan(TestReportPlan testReportPlan) { + scheduledTime = testReportPlan.scheduledTime; + executed = testReportPlan.executed; + plan = testReportPlan.plan; + } + + /** + * Returns true if an only if this actor action plan was executed + */ + public boolean executed() { + return executed; + } + + /** + * Package access. Executes the embedded action and marks this action plan as + * executed. + */ + void executeAction(final ReportContext actorContext) { + try { + plan.accept(actorContext); + } finally { + executed = true; + } + } + + /** + * Returns the scheduled time for action execution + */ + public double getScheduledTime() { + return scheduledTime; + } + +} diff --git a/gcm3/src/main/java/nucleus/testsupport/testplugin/TestScenarioReport.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestScenarioReport.java similarity index 85% rename from gcm3/src/main/java/nucleus/testsupport/testplugin/TestScenarioReport.java rename to gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestScenarioReport.java index 163c8d6d1..9e8d9181f 100644 --- a/gcm3/src/main/java/nucleus/testsupport/testplugin/TestScenarioReport.java +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestScenarioReport.java @@ -1,4 +1,4 @@ -package nucleus.testsupport.testplugin; +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin; import net.jcip.annotations.ThreadSafe; @@ -7,11 +7,7 @@ * plugin scenario is considered complete if and only if the plugin contained at * least one plan for a test actor or test data manager and all such plans were * executed by the simulation. - * - * @author Shawn Hatch - * */ - @ThreadSafe public final class TestScenarioReport { private final boolean complete; @@ -22,11 +18,10 @@ public TestScenarioReport(boolean complete) { } /** - * Returns true if and only if the plugin contained at least one plan for a - * test actor or test data manager and all such plans were executed by the + * Returns true if and only if the plugin contained at least one plan for a test + * actor or test data manager and all such plans were executed by the * simulation. */ - public boolean isComplete() { return complete; } @@ -43,8 +38,8 @@ public int hashCode() { } /** - * Two TestScenarioReport instances are equal if and only if they have the - * same completion status. + * Two TestScenarioReport instances are equal if and only if they have the same + * completion status. */ @Override public boolean equals(Object obj) { diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestSimulation.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestSimulation.java new file mode 100644 index 000000000..47b5cd6cb --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/TestSimulation.java @@ -0,0 +1,172 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.Simulation; +import gov.hhs.aspr.ms.gcm.nucleus.SimulationState; +import util.errors.ContractException; + +/** + * A Testing utility class that will execute a simulation given a list of + * plugins and an outputConsumer + */ +public class TestSimulation { + + private static class Data { + private Double simulationHaltTime; + private boolean produceSimulationStateOnHalt; + private List plugins = new ArrayList<>(); + private TestOutputConsumer testOutputConsumer = new TestOutputConsumer(); + private SimulationState simulationState = SimulationState.builder().build(); + + public Data() { + } + + public Data(Data data) { + simulationHaltTime = data.simulationHaltTime; + produceSimulationStateOnHalt = data.produceSimulationStateOnHalt; + plugins.addAll(data.plugins); + testOutputConsumer = data.testOutputConsumer; + simulationState = data.simulationState; + } + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private Data data = new Data(); + + private Builder() { + + } + + /** + * If true and an output consumer is also assigned then the simulation will + * produce plugins and a SimulationTime that reflect the final state of the + * simulation. Defaults to false. + */ + public Builder setProduceSimulationStateOnHalt(boolean produceSimulationStateOnHalt) { + data.produceSimulationStateOnHalt = produceSimulationStateOnHalt; + return this; + } + + /** + * Set the simulation halt time. + */ + public Builder setSimulationHaltTime(double simulationHaltTime) { + data.simulationHaltTime = simulationHaltTime; + return this; + } + + /** + * Set the simulation time. Defaults to the current date and a start time of + * zero. + * + * @throws ContractException {@link NucleusError#NULL_SIMULATION_TIME} if the + * simulation time is null + */ + public Builder setSimulationState(SimulationState simulationState) { + if (simulationState == null) { + throw new ContractException(NucleusError.NULL_SIMULATION_TIME); + } + data.simulationState = simulationState; + return this; + } + + /** + * Adds a plugin to this builder for inclusion in the test simulation + * + * @throws ContractException {@link NucleusError#NULL_PLUGINS} if the plugin + * collection is null + */ + public Builder addPlugin(Plugin plugin) { + if (plugin == null) { + throw new ContractException(NucleusError.NULL_PLUGIN); + } + data.plugins.add(plugin); + return this; + } + + /** + * Add a plugin initializer to this builder for inclusion in the simulation + * + * @throws ContractException {@link NucleusError#NULL_PLUGIN} if the plugin is + * null + */ + public Builder addPlugins(Collection plugins) { + if (plugins == null) { + throw new ContractException(NucleusError.NULL_PLUGINS); + } + for (Plugin plugin : plugins) { + if (plugin == null) { + throw new ContractException(NucleusError.NULL_PLUGIN); + } + data.plugins.add(plugin); + } + return this; + } + + /** + * Returns an Engine instance that is initialized with the plugins and output + * consumer collected by this builder. + */ + public TestSimulation build() { + return new TestSimulation(new Data(data)); + } + } + + private final Data data; + + private TestSimulation(Data data) { + this.data = data; + } + + public TestOutputConsumer execute() { + Simulation.Builder builder = Simulation.builder(); + + for (Plugin plugin : data.plugins) { + builder.addPlugin(plugin); + } + + // build and execute the engine + builder// + .setRecordState(data.produceSimulationStateOnHalt)// + .setOutputConsumer(data.testOutputConsumer)// + .setSimulationState(data.simulationState)// + .setSimulationHaltTime(data.simulationHaltTime)// + .build().execute(); + + // show that all actions were executed + Map outputItems = data.testOutputConsumer + .getOutputItemMap(TestScenarioReport.class); + boolean complete = false; + + if (outputItems.size() < 1) { + throw new ContractException(TestError.MISSING_TEST_SCENARIO_REPORTS); + } + + if (outputItems.size() > 1) { + throw new ContractException(TestError.DUPLICATE_TEST_SCENARIO_REPORTS); + } + + TestScenarioReport testScenarioReport = outputItems.keySet().iterator().next(); + Integer count = outputItems.get(testScenarioReport); + if (count > 1) { + throw new ContractException(TestError.DUPLICATE_TEST_SCENARIO_REPORTS); + } + complete = testScenarioReport.isComplete(); + + if (!complete) { + throw new ContractException(TestError.TEST_EXECUTION_FAILURE); + } + return data.testOutputConsumer; + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/util/TriConsumer.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/util/TriConsumer.java new file mode 100644 index 000000000..3291cdc80 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/nucleus/util/TriConsumer.java @@ -0,0 +1,53 @@ +package gov.hhs.aspr.ms.gcm.nucleus.util; + +import java.util.Objects; +import java.util.function.Consumer; + +/** + * Represents an operation that accepts three input arguments and returns no + * result. This is the three-arity specialization of {@link Consumer}. Unlike + * most other functional interfaces, {@code TriConsumer} is expected to operate + * via side-effects. + *

    + * This is a functional interface whose + * functional method is {@link #accept(Object, Object, Object)}. + * + * @param the type of the first argument to the operation + * @param the type of the second argument to the operation + * @param the type of the third argument to the operation + * @see Consumer + * @since 1.8 + */ +@FunctionalInterface +public interface TriConsumer { + + /** + * Performs this operation on the given arguments. + * + * @param t the first input argument + * @param u the second input argument + * @param v the third input argument + */ + void accept(T t, U u, V v); + + /** + * Returns a composed {@code TriConsumer} that performs, in sequence, this + * operation followed by the {@code after} operation. If performing either + * operation throws an exception, it is relayed to the caller of the composed + * operation. If performing this operation throws an exception, the + * {@code after} operation will not be performed. + * + * @param after the operation to perform after this operation + * @return a composed {@code TriConsumer} that performs in sequence this + * operation followed by the {@code after} operation + * @throws NullPointerException if {@code after} is null + */ + default TriConsumer andThen(TriConsumer after) { + Objects.requireNonNull(after); + + return (l, r, q) -> { + accept(l, r, q); + after.accept(l, r, q); + }; + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/GlobalPropertiesPlugin.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/GlobalPropertiesPlugin.java new file mode 100644 index 000000000..a46579613 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/GlobalPropertiesPlugin.java @@ -0,0 +1,107 @@ +package gov.hhs.aspr.ms.gcm.plugins.globalproperties; + +import java.util.Optional; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.PluginContext; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.reports.GlobalPropertyReport; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.reports.GlobalPropertyReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertiesError; + +import util.errors.ContractException; + +/** + * A plugin providing a global property data manager to the simulation. + */ +public final class GlobalPropertiesPlugin { + + private static class Data { + private GlobalPropertyReportPluginData globalPropertyReportPluginData; + private GlobalPropertiesPluginData globalPropertiesPluginData; + } + + private GlobalPropertiesPlugin() { + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private Builder() { + } + + private Data data = new Data(); + + /** + * Returns the global plugin. + *

    + * Uses GlobalsPluginId.PLUGIN_ID as its id + *

    + *

    + * Depends on plugins: + *

      + *
    • Report Plugin
    • + *
    + *

    + * Provides data mangers: + *

      + *
    • {@linkplain GlobalPropertiesDataManager}
    • + *
    + *

    + * Provides reports: + *

      + *
    • {@linkplain GlobalPropertyReport}
    • + *
    + * + * @throws ContractException {@linkplain GlobalPropertiesError#NULL_GLOBAL_PLUGIN_DATA} + * if the global plugin data is null + */ + public Plugin getGlobalPropertiesPlugin() { + validate(); + Plugin.Builder builder = Plugin.builder();// + builder.addPluginData(data.globalPropertiesPluginData);// + + if (data.globalPropertyReportPluginData != null) { + builder.addPluginData(data.globalPropertyReportPluginData); + } + builder.setInitializer(GlobalPropertiesPlugin::init); + builder.setPluginId(GlobalPropertiesPluginId.PLUGIN_ID);// + return builder.build(); + } + + private void validate() { + if (data.globalPropertiesPluginData == null) { + throw new ContractException(GlobalPropertiesError.NULL_GLOBAL_PLUGIN_DATA); + } + } + + public Builder setGlobalPropertyReportPluginData( + GlobalPropertyReportPluginData globalPropertyReportPluginData) { + data.globalPropertyReportPluginData = globalPropertyReportPluginData; + return this; + } + + public Builder setGlobalPropertiesPluginData(GlobalPropertiesPluginData globalPropertiesPluginData) { + data.globalPropertiesPluginData = globalPropertiesPluginData; + return this; + } + + } + + private static void init(PluginContext pluginContext) { + GlobalPropertiesPluginData data = pluginContext.getPluginData(GlobalPropertiesPluginData.class).get(); + pluginContext.addDataManager(new GlobalPropertiesDataManager(data)); + + Optional optional = pluginContext + .getPluginData(GlobalPropertyReportPluginData.class); + if (optional.isPresent()) { + GlobalPropertyReportPluginData globalPropertyReportPluginData = optional.get(); + pluginContext.addReport(new GlobalPropertyReport(globalPropertyReportPluginData)::init); + } + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/GlobalPropertiesPluginId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/GlobalPropertiesPluginId.java new file mode 100644 index 000000000..a40e6b6e9 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/GlobalPropertiesPluginId.java @@ -0,0 +1,13 @@ +package gov.hhs.aspr.ms.gcm.plugins.globalproperties; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; + +/** + * Static plugin id implementation for the GlobalsPlugin + */ +public final class GlobalPropertiesPluginId implements PluginId { + public final static PluginId PLUGIN_ID = new GlobalPropertiesPluginId(); + + private GlobalPropertiesPluginId() { + }; +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/datamanagers/GlobalPropertiesDataManager.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/datamanagers/GlobalPropertiesDataManager.java new file mode 100644 index 000000000..e40e621c2 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/datamanagers/GlobalPropertiesDataManager.java @@ -0,0 +1,412 @@ +package gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers; + +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.nucleus.DataManager; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.nucleus.EventFilter; +import gov.hhs.aspr.ms.gcm.nucleus.IdentifiableFunctionMap; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.events.GlobalPropertyDefinitionEvent; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.events.GlobalPropertyUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertiesError; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertyInitialization; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.errors.ContractException; + +/** + * A mutable data manager for global properties. + */ +public final class GlobalPropertiesDataManager extends DataManager { + + private static enum EventFunctionId { + GLOBAL_PROPERTY_ID; // + + } + + private static record GlobalPropertyUpdateMutationEvent(GlobalPropertyId globalPropertyId, + Object globalPropertyValue) implements Event { + } + + private static record GlobalPropertyInitializationMutationEvent( + GlobalPropertyInitialization globalPropertyInitialization) implements Event { + } + + private DataManagerContext dataManagerContext; + + private Map globalPropertyValues; + + private Map globalPropertyTimes; + + private Map globalPropertyDefinitions; + + private Map globalPropertyDefinitionTimes; + + private final GlobalPropertiesPluginData globalPropertiesPluginData; + + private IdentifiableFunctionMap functionMap = // + IdentifiableFunctionMap.builder(GlobalPropertyUpdateEvent.class)// + .put(EventFunctionId.GLOBAL_PROPERTY_ID, e -> e.globalPropertyId())// + .build();// + + /** + * Constructs the data manager + * + * @throws ContractException {@linkplain GlobalPropertiesError#NULL_GLOBAL_PLUGIN_DATA} + * if the global plugin data is null + */ + public GlobalPropertiesDataManager(GlobalPropertiesPluginData globalPropertiesPluginData) { + if (globalPropertiesPluginData == null) { + throw new ContractException(GlobalPropertiesError.NULL_GLOBAL_PLUGIN_DATA); + } + this.globalPropertiesPluginData = globalPropertiesPluginData; + + } + + /** + * Returns an event that defines a new global property + * + * @throws ContractException + *
      + *
    • {@linkplain GlobalPropertiesError#NULL_GLOBAL_PROPERTY_INITIALIZATION} + * if the global property initialization is null
    • + *
    • {@linkplain PropertyError#DUPLICATE_PROPERTY_DEFINITION} + * if the global property already exists
    • + *
    + */ + public void defineGlobalProperty(GlobalPropertyInitialization globalPropertyInitialization) { + dataManagerContext + .releaseMutationEvent(new GlobalPropertyInitializationMutationEvent(globalPropertyInitialization)); + } + + /** + * Returns an event filter used to subscribe to + * {@link GlobalPropertyDefinitionEvent} events. Matches all such events. + */ + public EventFilter getEventFilterForGlobalPropertyDefinitionEvent() { + return EventFilter.builder(GlobalPropertyDefinitionEvent.class)// + .build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link GlobalPropertyUpdateEvent} events. Matches all such events. + */ + public EventFilter getEventFilterForGlobalPropertyUpdateEvent() { + return EventFilter.builder(GlobalPropertyUpdateEvent.class)// + .build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link GlobalPropertyUpdateEvent} events. Matches on global property id. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the global property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the global property id is not known
    • + *
    + */ + public EventFilter getEventFilterForGlobalPropertyUpdateEvent( + GlobalPropertyId globalPropertyId) { + validateGlobalPropertyId(globalPropertyId); + return EventFilter.builder(GlobalPropertyUpdateEvent.class)// + .addFunctionValuePair(functionMap.get(EventFunctionId.GLOBAL_PROPERTY_ID), globalPropertyId)// + .build(); + } + + /** + * Returns the property definition for the given {@link GlobalPropertyId} + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the global property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the global property id is unknown
    • + *
    + */ + public PropertyDefinition getGlobalPropertyDefinition(final GlobalPropertyId globalPropertyId) { + validateGlobalPropertyId(globalPropertyId); + return globalPropertyDefinitions.get(globalPropertyId); + } + + /** + * Returns the property definition for the given {@link GlobalPropertyId} + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the global property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the global property id is unknown
    • + *
    + */ + public double getGlobalPropertyDefinitionTime(final GlobalPropertyId globalPropertyId) { + validateGlobalPropertyId(globalPropertyId); + return globalPropertyDefinitionTimes.get(globalPropertyId); + } + + /** + * Returns the set of global property ids + */ + @SuppressWarnings("unchecked") + public Set getGlobalPropertyIds() { + Set result = new LinkedHashSet<>(globalPropertyDefinitions.keySet().size()); + for (GlobalPropertyId globalPropertyId : globalPropertyDefinitions.keySet()) { + result.add((T) globalPropertyId); + } + return result; + } + + /** + * Returns the time when the of the global property was last assigned. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the global property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the global property id is unknown
    • + *
    + */ + public double getGlobalPropertyTime(GlobalPropertyId globalPropertyId) { + validateGlobalPropertyId(globalPropertyId); + Double result = globalPropertyTimes.get(globalPropertyId); + if (result == null) { + result = globalPropertyDefinitionTimes.get(globalPropertyId); + } + return result; + } + + /** + * Returns the value of the global property. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the global property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the global property id is unknown
    • + *
    + */ + @SuppressWarnings("unchecked") + public T getGlobalPropertyValue(GlobalPropertyId globalPropertyId) { + validateGlobalPropertyId(globalPropertyId); + Object result = globalPropertyValues.get(globalPropertyId); + if (result == null) { + result = globalPropertyDefinitions.get(globalPropertyId).getDefaultValue().get(); + } + return (T) result; + }; + + /** + * Returns true if and only if the global property id exists. Returns false for + * null input. + */ + public boolean globalPropertyIdExists(final GlobalPropertyId globalPropertyId) { + return globalPropertyDefinitions.containsKey(globalPropertyId); + } + + private void handleGlobalPropertyInitializationMutationEvent(DataManagerContext dataManagerContext, + GlobalPropertyInitializationMutationEvent globalPropertyInitializationMutationEvent) { + GlobalPropertyInitialization globalPropertyInitialization = globalPropertyInitializationMutationEvent.globalPropertyInitialization; + validateGlobalPropertyInitializationNotNull(globalPropertyInitialization); + GlobalPropertyId globalPropertyId = globalPropertyInitialization.getGlobalPropertyId(); + validateGlobalPropertyIdIsUnknown(globalPropertyId); + + PropertyDefinition propertyDefinition = globalPropertyInitialization.getPropertyDefinition(); + globalPropertyDefinitions.put(globalPropertyId, propertyDefinition); + globalPropertyDefinitionTimes.put(globalPropertyId, dataManagerContext.getTime()); + + Object globalPropertyValue; + Optional optional = globalPropertyInitialization.getValue(); + if (optional.isEmpty()) { + globalPropertyValue = propertyDefinition.getDefaultValue().get(); + } else { + globalPropertyValue = optional.get(); + globalPropertyValues.put(globalPropertyId, globalPropertyValue); + globalPropertyTimes.put(globalPropertyId, dataManagerContext.getTime()); + } + + if (dataManagerContext.subscribersExist(GlobalPropertyDefinitionEvent.class)) { + dataManagerContext + .releaseObservationEvent(new GlobalPropertyDefinitionEvent(globalPropertyId, globalPropertyValue)); + } + + } + + private void handleGlobalPropertyUpdateMutationEvent(DataManagerContext dataManagerContext, + GlobalPropertyUpdateMutationEvent globalPropertyUpdateMutationEvent) { + GlobalPropertyId globalPropertyId = globalPropertyUpdateMutationEvent.globalPropertyId(); + Object globalPropertyValue = globalPropertyUpdateMutationEvent.globalPropertyValue(); + validateGlobalPropertyId(globalPropertyId); + validateGlobalPropertyValueNotNull(globalPropertyValue); + final PropertyDefinition propertyDefinition = getGlobalPropertyDefinition(globalPropertyId); + validatePropertyMutability(propertyDefinition); + validateValueCompatibility(globalPropertyId, propertyDefinition, globalPropertyValue); + final Object oldPropertyValue = getGlobalPropertyValue(globalPropertyId); + globalPropertyValues.put(globalPropertyId, globalPropertyValue); + globalPropertyTimes.put(globalPropertyId, dataManagerContext.getTime()); + if (dataManagerContext.subscribersExist(GlobalPropertyUpdateEvent.class)) { + dataManagerContext.releaseObservationEvent( + new GlobalPropertyUpdateEvent(globalPropertyId, oldPropertyValue, globalPropertyValue)); + } + } + + /** + * Initializes the global properties data manager + * + * @throws ContractException {@linkplain PropertyError#INCOMPATIBLE_DEF_TIME} if + * the Global Properties Plugin Data contains a + * property definition with a creation time that + * exceeds the current simulation time + */ + @Override + public void init(DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + + this.dataManagerContext = dataManagerContext; + validateGlobalPropertiesPluginData(); + + dataManagerContext.subscribe(GlobalPropertyInitializationMutationEvent.class, + this::handleGlobalPropertyInitializationMutationEvent); + dataManagerContext.subscribe(GlobalPropertyUpdateMutationEvent.class, + this::handleGlobalPropertyUpdateMutationEvent); + + globalPropertyValues = globalPropertiesPluginData.getGlobalPropertyValues(); + + globalPropertyTimes = globalPropertiesPluginData.getGlobalPropertyTimes(); + + globalPropertyDefinitions = globalPropertiesPluginData.getGlobalPropertyDefinitions(); + + globalPropertyDefinitionTimes = globalPropertiesPluginData.getGlobalPropertyDefinitionTimes(); + + if (dataManagerContext.stateRecordingIsScheduled()) { + dataManagerContext.subscribeToSimulationClose(this::recordSimulationState); + } + + } + + private void recordSimulationState(DataManagerContext dataManagerContext) { + GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder(); + + for (GlobalPropertyId globalPropertyId : globalPropertyDefinitions.keySet()) { + PropertyDefinition propertyDefinition = globalPropertyDefinitions.get(globalPropertyId); + Double propertyDefinitionCreationTime = globalPropertyDefinitionTimes.get(globalPropertyId); + builder.defineGlobalProperty(globalPropertyId, propertyDefinition, propertyDefinitionCreationTime); + } + for (GlobalPropertyId globalPropertyId : globalPropertyValues.keySet()) { + Object value = globalPropertyValues.get(globalPropertyId); + double time = globalPropertyTimes.get(globalPropertyId); + builder.setGlobalPropertyValue(globalPropertyId, value, time); + } + + dataManagerContext.releaseOutput(builder.build()); + + } + + /** + * Set the value of the global property and updates the property assignment + * time. + * + * @throws ContractException + *
      + *
    • {@link PropertyError#NULL_PROPERTY_ID} if the + * global property id is null
    • + *
    • {@link PropertyError#UNKNOWN_PROPERTY_ID} if + * the global property id is unknown
    • + *
    • {@link PropertyError#NULL_PROPERTY_VALUE} if + * the property value is null
    • + *
    • {@link PropertyError#IMMUTABLE_VALUE} if the + * global property definition indicates the property + * is not mutable
    • + *
    • {@link PropertyError#INCOMPATIBLE_VALUE} if the + * property value is incompatible with the property + * definition
    • + *
    + */ + public void setGlobalPropertyValue(GlobalPropertyId globalPropertyId, Object globalPropertyValue) { + + dataManagerContext + .releaseMutationEvent(new GlobalPropertyUpdateMutationEvent(globalPropertyId, globalPropertyValue)); + + } + + private void validateGlobalPropertyId(final GlobalPropertyId globalPropertyId) { + if (globalPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + + if (!globalPropertyDefinitions.containsKey(globalPropertyId)) { + throw new ContractException(PropertyError.UNKNOWN_PROPERTY_ID, globalPropertyId); + } + } + + private void validateGlobalPropertyIdIsUnknown(final GlobalPropertyId globalPropertyId) { + + if (globalPropertyDefinitions.containsKey(globalPropertyId)) { + throw new ContractException(PropertyError.DUPLICATE_PROPERTY_DEFINITION, globalPropertyId); + } + } + + private void validateGlobalPropertyInitializationNotNull( + GlobalPropertyInitialization globalPropertyInitialization) { + if (globalPropertyInitialization == null) { + throw new ContractException(GlobalPropertiesError.NULL_GLOBAL_PROPERTY_INITIALIZATION); + } + } + + private void validateGlobalPropertyValueNotNull(final Object propertyValue) { + if (propertyValue == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_VALUE); + } + } + + private void validatePropertyMutability(final PropertyDefinition propertyDefinition) { + if (!propertyDefinition.propertyValuesAreMutable()) { + throw new ContractException(PropertyError.IMMUTABLE_VALUE); + } + } + + private void validateValueCompatibility(final Object propertyId, final PropertyDefinition propertyDefinition, + final Object propertyValue) { + if (!propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())) { + throw new ContractException(PropertyError.INCOMPATIBLE_VALUE, + "Property value " + propertyValue + " is not of type " + propertyDefinition.getType().getName() + + " and does not match definition of " + propertyId); + } + } + + private void validateGlobalPropertiesPluginData() { + double currentTime = dataManagerContext.getTime(); + for (GlobalPropertyId globalPropertyId : globalPropertiesPluginData.getGlobalPropertyIds()) { + if (globalPropertiesPluginData.getGlobalPropertyDefinitionTime(globalPropertyId) > currentTime) { + throw new ContractException(PropertyError.INCOMPATIBLE_DEF_TIME); + } + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("GlobalPropertiesDataManager [globalPropertyDefinitions="); + builder.append(globalPropertyDefinitions); + builder.append(", globalPropertyDefinitionTimes="); + builder.append(globalPropertyDefinitionTimes); + builder.append(", globalPropertyValues="); + builder.append(globalPropertyValues); + builder.append(", globalPropertyTimes="); + builder.append(globalPropertyTimes); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/datamanagers/GlobalPropertiesPluginData.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/datamanagers/GlobalPropertiesPluginData.java new file mode 100644 index 000000000..42b35b9b3 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/datamanagers/GlobalPropertiesPluginData.java @@ -0,0 +1,464 @@ +package gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers; + +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.PluginDataBuilder; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * An immutable container of the initial state of global components and global + * properties. It contains:
    + *
      + *
    • global property definitions
    • + *
    • global property values
    • + *
    + */ +@Immutable +public final class GlobalPropertiesPluginData implements PluginData { + + /** + * Builder class for GloblaInitialData + */ + public static class Builder implements PluginDataBuilder { + private Data data; + + private Builder(Data data) { + this.data = data; + } + + /** + * Returns the {@link GlobalPropertiesPluginData} from the collected information + * supplied to this builder. Clears the builder's state. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if a global property value was associated with a + * global property id that was not defined
    • + *
    • {@linkplain PropertyError#INCOMPATIBLE_VALUE} + * if a global property value was associated with a + * global property id that is incompatible with the + * corresponding property definition.
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if a global property time was associated with a + * global property id that was not defined
    • + *
    • {@linkplain PropertyError#INCOMPATIBLE_TIME} if + * a global property assignment time was less than the + * associated property definition creation time.
    • + *
    • {@linkplain PropertyError#INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT} + * if a global property definition has no default + * value and there is also no corresponding property + * value assignment.
    • + *
    + */ + public GlobalPropertiesPluginData build() { + if (!data.locked) { + validateData(); + } + ensureImmutability(); + return new GlobalPropertiesPluginData(data); + } + + /** + * Defines a global property Duplicate inputs override previous inputs. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the global property id is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_DEFINITION} + * if the property definition is null
    • + *
    + */ + public Builder defineGlobalProperty(final GlobalPropertyId globalPropertyId, + final PropertyDefinition propertyDefinition, final double time) { + ensureDataMutability(); + validateGlobalPropertyIdNotNull(globalPropertyId); + validateGlobalPropertyDefinitionNotNull(propertyDefinition); + data.globalPropertyDefinitions.put(globalPropertyId, propertyDefinition); + data.globalPropertyDefinitionTimes.put(globalPropertyId, time); + return this; + } + + private void ensureDataMutability() { + if (data.locked) { + data = new Data(data); + data.locked = false; + } + } + + private void ensureImmutability() { + if (!data.locked) { + data.locked = true; + } + } + + /** + * Sets the global property value that overrides the default value of the + * corresponding property definition. Duplicate inputs override previous inputs. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID}if + * the global property id is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_VALUE}if + * the global property value is null
    • + *
    + */ + public Builder setGlobalPropertyValue(final GlobalPropertyId globalPropertyId, final Object propertyValue, + final double assignmentTime) { + ensureDataMutability(); + validateGlobalPropertyIdNotNull(globalPropertyId); + validateGlobalPropertyValueNotNull(propertyValue); + data.globalPropertyValues.put(globalPropertyId, propertyValue); + data.globalPropertyTimes.put(globalPropertyId, assignmentTime); + return this; + } + + private void validateData() { + + /* + * show that each recorded property value corresponds to a known property + * definition + */ + for (final GlobalPropertyId globalPropertyId : data.globalPropertyValues.keySet()) { + if (!data.globalPropertyDefinitions.containsKey(globalPropertyId)) { + throw new ContractException(PropertyError.UNKNOWN_PROPERTY_ID, globalPropertyId); + } + } + + /* + * show that each property value is compatible with the corresponding property + * definition + */ + for (final GlobalPropertyId globalPropertyId : data.globalPropertyValues.keySet()) { + final Object propertyValue = data.globalPropertyValues.get(globalPropertyId); + final PropertyDefinition propertyDefinition = data.globalPropertyDefinitions.get(globalPropertyId); + if (!propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())) { + throw new ContractException(PropertyError.INCOMPATIBLE_VALUE, + globalPropertyId + " = " + propertyValue); + } + } + + /* + * Show that each recorded property time corresponds to a known property + * definition + */ + for (final GlobalPropertyId globalPropertyId : data.globalPropertyTimes.keySet()) { + if (!data.globalPropertyDefinitions.containsKey(globalPropertyId)) { + throw new ContractException(PropertyError.UNKNOWN_PROPERTY_ID, globalPropertyId); + } + } + + /* + * Show that each recorded property time is compatible with the property + * definition's time + */ + for (final GlobalPropertyId globalPropertyId : data.globalPropertyTimes.keySet()) { + final Double propertyTime = data.globalPropertyTimes.get(globalPropertyId); + final Double definitionTime = data.globalPropertyDefinitionTimes.get(globalPropertyId); + if (propertyTime < definitionTime) { + throw new ContractException(PropertyError.INCOMPATIBLE_TIME, + globalPropertyId + " at " + propertyTime); + } + } + + /* + * For every global property definition that has no default value, ensure that + * there is a corresponding global property value assignment + */ + for (GlobalPropertyId globalPropertyId : data.globalPropertyDefinitions.keySet()) { + PropertyDefinition propertyDefinition = data.globalPropertyDefinitions.get(globalPropertyId); + if (!propertyDefinition.getDefaultValue().isPresent()) { + Object propertyValue = data.globalPropertyValues.get(globalPropertyId); + if (propertyValue == null) { + throw new ContractException(PropertyError.INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT, + globalPropertyId); + } + } + } + } + + } + + private static class Data { + + private final Map globalPropertyDefinitions = new LinkedHashMap<>(); + + private Map globalPropertyDefinitionTimes = new LinkedHashMap<>(); + + private Map globalPropertyValues = new LinkedHashMap<>(); + + private Map globalPropertyTimes = new LinkedHashMap<>(); + + private boolean locked; + + private Data() { + } + + private Data(Data data) { + globalPropertyDefinitions.putAll(data.globalPropertyDefinitions); + globalPropertyDefinitionTimes.putAll(data.globalPropertyDefinitionTimes); + globalPropertyValues.putAll(data.globalPropertyValues); + globalPropertyTimes.putAll(data.globalPropertyTimes); + locked = data.locked; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Data [globalPropertyDefinitions="); + builder.append(globalPropertyDefinitions); + builder.append(", globalPropertyDefinitionTimes="); + builder.append(globalPropertyDefinitionTimes); + builder.append(", globalPropertyValues="); + builder.append(globalPropertyValues); + builder.append(", globalPropertyTimes="); + builder.append(globalPropertyTimes); + builder.append(", locked="); + builder.append(locked); + builder.append("]"); + return builder.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + + ((globalPropertyDefinitionTimes == null) ? 0 : globalPropertyDefinitionTimes.hashCode()); + result = prime * result + ((globalPropertyDefinitions == null) ? 0 : globalPropertyDefinitions.hashCode()); + result = prime * result + ((globalPropertyTimes == null) ? 0 : globalPropertyTimes.hashCode()); + result = prime * result + ((globalPropertyValues == null) ? 0 : globalPropertyValues.hashCode()); + result = prime * result + (locked ? 1231 : 1237); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + if (globalPropertyDefinitionTimes == null) { + if (other.globalPropertyDefinitionTimes != null) { + return false; + } + } else if (!globalPropertyDefinitionTimes.equals(other.globalPropertyDefinitionTimes)) { + return false; + } + if (globalPropertyDefinitions == null) { + if (other.globalPropertyDefinitions != null) { + return false; + } + } else if (!globalPropertyDefinitions.equals(other.globalPropertyDefinitions)) { + return false; + } + if (globalPropertyTimes == null) { + if (other.globalPropertyTimes != null) { + return false; + } + } else if (!globalPropertyTimes.equals(other.globalPropertyTimes)) { + return false; + } + if (globalPropertyValues == null) { + if (other.globalPropertyValues != null) { + return false; + } + } else if (!globalPropertyValues.equals(other.globalPropertyValues)) { + return false; + } + if (locked != other.locked) { + return false; + } + return true; + } + + } + + /** + * Returns a Builder instance + */ + public static Builder builder() { + return new Builder(new Data()); + } + + private static void validateGlobalPropertyDefinitionNotNull(final PropertyDefinition propertyDefinition) { + if (propertyDefinition == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_DEFINITION); + } + } + + private static void validateGlobalPropertyIdNotNull(final GlobalPropertyId globalPropertyId) { + if (globalPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + } + + private static void validateGlobalPropertyValueNotNull(final Object propertyValue) { + if (propertyValue == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_VALUE); + } + } + + private final Data data; + + private GlobalPropertiesPluginData(final Data data) { + this.data = data; + } + + /** + * Returns the {@link PropertyDefinition} for the given + * {@link GlobalPropertyId}. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the global property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the global property id is known
    • + *
    + */ + public PropertyDefinition getGlobalPropertyDefinition(final GlobalPropertyId globalPropertyId) { + validateGlobalPropertyIdExists(data, globalPropertyId); + return data.globalPropertyDefinitions.get(globalPropertyId); + } + + /** + * Returns the creation time for the given {@link GlobalPropertyId}. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the global property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the global property id is known
    • + *
    + */ + public Double getGlobalPropertyDefinitionTime(final GlobalPropertyId globalPropertyId) { + validateGlobalPropertyIdExists(data, globalPropertyId); + return data.globalPropertyDefinitionTimes.get(globalPropertyId); + } + + /** + * Returns the set of {@link GlobalPropertyId} + */ + @SuppressWarnings("unchecked") + public Set getGlobalPropertyIds() { + final Set result = new LinkedHashSet<>(); + for (final GlobalPropertyId globalPropertyId : data.globalPropertyDefinitions.keySet()) { + result.add((T) globalPropertyId); + } + return result; + } + + /** + * Returns the optional property value for the given property id + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the global property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the global property id is known
    • + *
    + */ + @SuppressWarnings("unchecked") + public Optional getGlobalPropertyValue(final GlobalPropertyId globalPropertyId) { + validateGlobalPropertyIdExists(data, globalPropertyId); + T result = (T) data.globalPropertyValues.get(globalPropertyId); + return Optional.ofNullable(result); + } + + /** + * Returns the optional property assignment time for the given property id + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the global property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the global property id is known
    • + *
    + */ + public Optional getGlobalPropertyTime(final GlobalPropertyId globalPropertyId) { + validateGlobalPropertyIdExists(data, globalPropertyId); + Double result = data.globalPropertyTimes.get(globalPropertyId); + return Optional.ofNullable(result); + } + + private static void validateGlobalPropertyIdExists(final Data data, final GlobalPropertyId globalPropertyId) { + if (globalPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + if (!data.globalPropertyDefinitions.containsKey(globalPropertyId)) { + throw new ContractException(PropertyError.UNKNOWN_PROPERTY_ID, globalPropertyId); + } + } + + @Override + public PluginDataBuilder getCloneBuilder() { + return new Builder(data); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + data.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof GlobalPropertiesPluginData)) { + return false; + } + GlobalPropertiesPluginData other = (GlobalPropertiesPluginData) obj; + if (!data.equals(other.data)) { + return false; + } + return true; + } + + public Map getGlobalPropertyDefinitions() { + return new LinkedHashMap<>(data.globalPropertyDefinitions); + } + + public Map getGlobalPropertyDefinitionTimes() { + return new LinkedHashMap<>(data.globalPropertyDefinitionTimes); + } + + public Map getGlobalPropertyValues() { + return new LinkedHashMap<>(data.globalPropertyValues); + } + + public Map getGlobalPropertyTimes() { + return new LinkedHashMap<>(data.globalPropertyTimes); + } + + @Override + public String toString() { + StringBuilder builder2 = new StringBuilder(); + builder2.append("GlobalPropertiesPluginData [data="); + builder2.append(data); + builder2.append("]"); + return builder2.toString(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/events/GlobalPropertyDefinitionEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/events/GlobalPropertyDefinitionEvent.java new file mode 100644 index 000000000..a920f8a12 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/events/GlobalPropertyDefinitionEvent.java @@ -0,0 +1,36 @@ +package gov.hhs.aspr.ms.gcm.plugins.globalproperties.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.errors.ContractException; + +/** + * An event released by the global data manager whenever a global property + * definition is added to the simulation. + */ +public record GlobalPropertyDefinitionEvent(GlobalPropertyId globalPropertyId, Object initialPropertyValue) + implements Event { + + /** + * Creates the event. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the property id is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_VALUE} + * if the initial property value is null
    • + *
    + */ + public GlobalPropertyDefinitionEvent { + + if (globalPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + + if (initialPropertyValue == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_VALUE); + } + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/events/GlobalPropertyUpdateEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/events/GlobalPropertyUpdateEvent.java new file mode 100644 index 000000000..e90ac2315 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/events/GlobalPropertyUpdateEvent.java @@ -0,0 +1,14 @@ +package gov.hhs.aspr.ms.gcm.plugins.globalproperties.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertyId; +import net.jcip.annotations.Immutable; + +/** + * An event released by the global data manager whenever a global property is + * changed. + */ +@Immutable +public record GlobalPropertyUpdateEvent(GlobalPropertyId globalPropertyId, Object previousPropertyValue, + Object currentPropertyValue) implements Event { +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/reports/GlobalPropertyReport.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/reports/GlobalPropertyReport.java new file mode 100644 index 000000000..651db5dad --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/reports/GlobalPropertyReport.java @@ -0,0 +1,179 @@ +package gov.hhs.aspr.ms.gcm.plugins.globalproperties.reports; + +import java.util.LinkedHashSet; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.events.GlobalPropertyDefinitionEvent; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.events.GlobalPropertyUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertiesError; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import util.errors.ContractException; + +/** + * A Report that displays assigned global property values over time. Fields Time + * -- the time in days when the global property was set Property -- the global + * property identifier Value -- the value of the global property + */ +public final class GlobalPropertyReport { + + private final Set includedPropertyIds = new LinkedHashSet<>(); + private final Set currentProperties = new LinkedHashSet<>(); + private final Set excludedPropertyIds = new LinkedHashSet<>(); + private final ReportLabel reportLabel; + private final boolean includeNewPropertyIds; + + private final ReportHeader reportHeader = ReportHeader.builder()// + .add("time")// + .add("property")// + .add("value")// + .build();// + + private boolean isCurrentProperty(GlobalPropertyId globalPropertyId) { + return currentProperties.contains(globalPropertyId); + } + + private boolean addToCurrentProperties(GlobalPropertyId globalPropertyId) { + + // There are eight possibilities: + + /* + * P -- the default inclusion policy + * + * I -- the property is explicitly included + * + * X -- the property is explicitly excluded + * + * C -- the property should be on the current properties + * + * + * P I X C Table + * + * TRUE TRUE FALSE TRUE + * + * TRUE FALSE FALSE TRUE + * + * FALSE TRUE FALSE TRUE + * + * FALSE FALSE FALSE FALSE + * + * TRUE TRUE TRUE FALSE -- not possible + * + * TRUE FALSE TRUE FALSE + * + * FALSE TRUE TRUE FALSE -- not possible + * + * FALSE FALSE TRUE FALSE + * + * + * Two of the cases above are contradictory since a property cannot be both + * explicitly included and explicitly excluded + * + */ + // if X is true then we don't add the property + if (excludedPropertyIds.contains(globalPropertyId)) { + return false; + } + + // if both P and I are false we don't add the property + boolean included = includedPropertyIds.contains(globalPropertyId); + + if (!included && !includeNewPropertyIds) { + return false; + } + + // we have failed to reject the property + currentProperties.add(globalPropertyId); + + return true; + } + + /** + * @throws ContractException {@linkplain GlobalPropertiesError#NULL_GLOBAL_PROPERTY_REPORT_PLUGIN_DATA} + * if the plugin data is null + */ + public GlobalPropertyReport(GlobalPropertyReportPluginData globalPropertyReportPluginData) { + + if (globalPropertyReportPluginData == null) { + throw new ContractException(GlobalPropertiesError.NULL_GLOBAL_PROPERTY_REPORT_PLUGIN_DATA); + } + + reportLabel = globalPropertyReportPluginData.getReportLabel(); + includedPropertyIds.addAll(globalPropertyReportPluginData.getIncludedProperties()); + excludedPropertyIds.addAll(globalPropertyReportPluginData.getExcludedProperties()); + includeNewPropertyIds = globalPropertyReportPluginData.getDefaultInclusionPolicy(); + } + + private void handleGlobalPropertyDefinitionEvent(final ReportContext reportContext, + final GlobalPropertyDefinitionEvent globalPropertyDefinitionEvent) { + final GlobalPropertyId globalPropertyId = globalPropertyDefinitionEvent.globalPropertyId(); + if (addToCurrentProperties(globalPropertyId)) { + writeProperty(reportContext, globalPropertyId, globalPropertyDefinitionEvent.initialPropertyValue()); + } + } + + private void handleGlobalPropertyUpdateEvent(final ReportContext reportContext, + final GlobalPropertyUpdateEvent globalPropertyUpdateEvent) { + final GlobalPropertyId globalPropertyId = globalPropertyUpdateEvent.globalPropertyId(); + if (isCurrentProperty(globalPropertyId)) { + writeProperty(reportContext, globalPropertyId, globalPropertyUpdateEvent.currentPropertyValue()); + } + } + + /** + * Initialization of the report. + */ + public void init(final ReportContext reportContext) { + + final GlobalPropertiesDataManager globalPropertiesDataManager = reportContext + .getDataManager(GlobalPropertiesDataManager.class); + + reportContext.subscribe(GlobalPropertyDefinitionEvent.class, this::handleGlobalPropertyDefinitionEvent); + reportContext.subscribe(GlobalPropertyUpdateEvent.class, this::handleGlobalPropertyUpdateEvent); + if (reportContext.stateRecordingIsScheduled()) { + reportContext.subscribeToSimulationClose(this::recordSimulationState); + } + + for (GlobalPropertyId globalPropertyId : globalPropertiesDataManager.getGlobalPropertyIds()) { + addToCurrentProperties(globalPropertyId); + } + + /* + * We initialize the reporting with the current state of each global property + */ + for (final GlobalPropertyId globalPropertyId : currentProperties) { + final Object globalPropertyValue = globalPropertiesDataManager.getGlobalPropertyValue(globalPropertyId); + writeProperty(reportContext, globalPropertyId, globalPropertyValue); + } + + } + + private void recordSimulationState(ReportContext reportContext) { + GlobalPropertyReportPluginData.Builder builder = GlobalPropertyReportPluginData.builder(); + builder.setReportLabel(reportLabel); + for (GlobalPropertyId globalPropertyId : includedPropertyIds) { + builder.includeGlobalProperty(globalPropertyId); + } + for (GlobalPropertyId globalPropertyId : excludedPropertyIds) { + builder.excludeGlobalProperty(globalPropertyId); + } + builder.setDefaultInclusion(includeNewPropertyIds); + reportContext.releaseOutput(builder.build()); + } + + private void writeProperty(final ReportContext reportContext, final GlobalPropertyId globalPropertyId, + final Object globalPropertyValue) { + final ReportItem.Builder reportItemBuilder = ReportItem.builder(); + reportItemBuilder.setReportHeader(reportHeader); + reportItemBuilder.setReportLabel(reportLabel); + reportItemBuilder.addValue(reportContext.getTime()); + reportItemBuilder.addValue(globalPropertyId.toString()); + reportItemBuilder.addValue(globalPropertyValue); + reportContext.releaseOutput(reportItemBuilder.build()); + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/reports/GlobalPropertyReportPluginData.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/reports/GlobalPropertyReportPluginData.java new file mode 100644 index 000000000..ae164dfad --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/reports/GlobalPropertyReportPluginData.java @@ -0,0 +1,283 @@ +package gov.hhs.aspr.ms.gcm.plugins.globalproperties.reports; + +import java.util.LinkedHashSet; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.PluginDataBuilder; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportError; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import net.jcip.annotations.ThreadSafe; +import util.errors.ContractException; + +/** + * A PluginData class supporting GlobalPropertyReport construction. + */ +@ThreadSafe +public final class GlobalPropertyReportPluginData implements PluginData { + + /* + * Data class for collecting the inputs to the report + */ + private static class Data { + private ReportLabel reportLabel; + private Set includedProperties = new LinkedHashSet<>(); + private Set excludedProperties = new LinkedHashSet<>(); + private boolean defaultInclusionPolicy = true; + + private boolean locked; + + private Data() { + } + + private Data(Data data) { + reportLabel = data.reportLabel; + includedProperties.addAll(data.includedProperties); + excludedProperties.addAll(data.excludedProperties); + defaultInclusionPolicy = data.defaultInclusionPolicy; + locked = data.locked; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Data [reportLabel="); + builder.append(reportLabel); + builder.append(", includedProperties="); + builder.append(includedProperties); + builder.append(", excludedProperties="); + builder.append(excludedProperties); + builder.append(", defaultInclusionPolicy="); + builder.append(defaultInclusionPolicy); + builder.append(", locked="); + builder.append(locked); + builder.append("]"); + return builder.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (defaultInclusionPolicy ? 1231 : 1237); + result = prime * result + ((excludedProperties == null) ? 0 : excludedProperties.hashCode()); + result = prime * result + ((includedProperties == null) ? 0 : includedProperties.hashCode()); + result = prime * result + ((reportLabel == null) ? 0 : reportLabel.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + if (defaultInclusionPolicy != other.defaultInclusionPolicy) { + return false; + } + if (excludedProperties == null) { + if (other.excludedProperties != null) { + return false; + } + } else if (!excludedProperties.equals(other.excludedProperties)) { + return false; + } + if (includedProperties == null) { + if (other.includedProperties != null) { + return false; + } + } else if (!includedProperties.equals(other.includedProperties)) { + return false; + } + if (reportLabel == null) { + if (other.reportLabel != null) { + return false; + } + } else if (!reportLabel.equals(other.reportLabel)) { + return false; + } + return true; + } + + } + + /** + * Returns a new instance of the builder class + */ + public static Builder builder() { + return new Builder(new Data()); + } + + /** + * Builder class for the report + */ + public final static class Builder implements PluginDataBuilder { + private Builder(Data data) { + this.data = data; + } + + private void ensureDataMutability() { + if (data.locked) { + data = new Data(data); + data.locked = false; + } + } + + private void ensureImmutability() { + if (!data.locked) { + data.locked = true; + } + } + + private void validateData() { + if (data.reportLabel == null) { + throw new ContractException(ReportError.NULL_REPORT_LABEL); + } + } + + private Data data; + + /** + * Returns a PersonPropertyReportPluginData created from the collected inputs + * + * @throws ContractException {@linkplain ReportError#NULL_REPORT_LABEL} if the + * report label is not assigned + */ + public GlobalPropertyReportPluginData build() { + + if (!data.locked) { + validateData(); + } + ensureImmutability(); + return new GlobalPropertyReportPluginData(data); + + } + + /** + * Sets the default policy for inclusion of person properties in the report. + * This policy is used when a person property has not been explicitly included + * or excluded. Defaulted to true. + */ + public Builder setDefaultInclusion(boolean include) { + ensureDataMutability(); + data.defaultInclusionPolicy = include; + return this; + } + + /** + * Selects the given person property id to be included in the report. + * + * @throws ContractException {@linkplain PropertyError#NULL_PROPERTY_ID} if the + * person property id is null + */ + public Builder includeGlobalProperty(GlobalPropertyId globalPropertyId) { + ensureDataMutability(); + if (globalPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + data.includedProperties.add(globalPropertyId); + data.excludedProperties.remove(globalPropertyId); + return this; + } + + /** + * Selects the given person property id to be excluded from the report + * + * @throws ContractException {@linkplain PropertyError#NULL_PROPERTY_ID} if the + * person property id is null + */ + public Builder excludeGlobalProperty(GlobalPropertyId globalPropertyId) { + ensureDataMutability(); + if (globalPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + data.includedProperties.remove(globalPropertyId); + data.excludedProperties.add(globalPropertyId); + return this; + } + + /** + * Sets the report label + * + * @throws ContractException {@linkplain ReportError#NULL_REPORT_LABEL} if the + * report label is null + */ + public Builder setReportLabel(ReportLabel reportLabel) { + ensureDataMutability(); + if (reportLabel == null) { + throw new ContractException(ReportError.NULL_REPORT_LABEL); + } + data.reportLabel = reportLabel; + return this; + } + } + + private final Data data; + + private GlobalPropertyReportPluginData(Data data) { + this.data = data; + } + + @Override + public Builder getCloneBuilder() { + return new Builder(data); + } + + public ReportLabel getReportLabel() { + return data.reportLabel; + } + + public Set getIncludedProperties() { + return data.includedProperties; + } + + public Set getExcludedProperties() { + return data.excludedProperties; + } + + public boolean getDefaultInclusionPolicy() { + return data.defaultInclusionPolicy; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof GlobalPropertyReportPluginData)) { + return false; + } + GlobalPropertyReportPluginData other = (GlobalPropertyReportPluginData) obj; + if (data == null) { + if (other.data != null) { + return false; + } + } else if (!data.equals(other.data)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder2 = new StringBuilder(); + builder2.append("GlobalPropertyReportPluginData [data="); + builder2.append(data); + builder2.append("]"); + return builder2.toString(); + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/support/GlobalPropertiesError.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/support/GlobalPropertiesError.java new file mode 100644 index 000000000..2395d3318 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/support/GlobalPropertiesError.java @@ -0,0 +1,26 @@ +package gov.hhs.aspr.ms.gcm.plugins.globalproperties.support; + +import util.errors.ContractError; +import util.errors.ContractException; + +/** + * An enumeration supporting {@link ContractException} that acts as a general + * description of the exception. + */ +public enum GlobalPropertiesError implements ContractError { + + NULL_GLOBAL_DATA_MANGER("Null global data manager"), NULL_GLOBAL_PLUGIN_DATA("Null global plugin data"), + NULL_GLOBAL_PROPERTY_REPORT_PLUGIN_DATA("Null global property report plugin data"), + NULL_GLOBAL_PROPERTY_INITIALIZATION("Null global property initialization"),; + + private final String description; + + private GlobalPropertiesError(final String description) { + this.description = description; + } + + @Override + public String getDescription() { + return description; + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/support/GlobalPropertyDimension.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/support/GlobalPropertyDimension.java new file mode 100644 index 000000000..88eabcd3d --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/support/GlobalPropertyDimension.java @@ -0,0 +1,206 @@ +package gov.hhs.aspr.ms.gcm.plugins.globalproperties.support; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import gov.hhs.aspr.ms.gcm.nucleus.Dimension; +import gov.hhs.aspr.ms.gcm.nucleus.DimensionContext; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.errors.ContractException; + +/** + * Dimension implementation for setting a global property to a list of values in + * a global properties plugin data. + */ +public final class GlobalPropertyDimension implements Dimension { + private static class Data { + private GlobalPropertyId globalPropertyId; + private List values = new ArrayList<>(); + private double assignmentTime; + + private Data() { + } + + private Data(Data data) { + globalPropertyId = data.globalPropertyId; + values.addAll(data.values); + assignmentTime = data.assignmentTime; + } + + @Override + public int hashCode() { + return Objects.hash(globalPropertyId, values, assignmentTime); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Data other = (Data) obj; + return Objects.equals(globalPropertyId, other.globalPropertyId) && Objects.equals(values, other.values) + && assignmentTime == other.assignmentTime; + } + + } + + /** + * Returns a new builder for GlobalPropertyDimension + */ + public static Builder builder() { + return new Builder(new Data()); + } + + /** + * Builder class for GlobalPropertyDimension + */ + public static class Builder { + + private Data data; + + private Builder(Data data) { + this.data = data; + } + + /** + * Returns the GlobalPropertyDimension from the collected data. + * + * @throws ContractException {@linkplain PropertyError#NULL_PROPERTY_ID} if the + * global property id was not assigned + */ + public GlobalPropertyDimension build() { + validate(); + return new GlobalPropertyDimension(new Data(data)); + } + + private void validate() { + if (data.globalPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + } + + /** + * Sets the global property for the dimension. Defaults to null. + * + * @throws ContractException {@linkplain PropertyError#NULL_PROPERTY_ID} if the + * global property id is null + */ + public Builder setGlobalPropertyId(GlobalPropertyId globalPropertyId) { + validateGlobalPropertyId(globalPropertyId); + data.globalPropertyId = globalPropertyId; + return this; + } + + /** + * Adds a value to the dimension. + * + * @throws ContractException {@linkplain PropertyError#NULL_PROPERTY_VALUE} if + * the value is null + */ + public Builder addValue(Object value) { + validateValue(value); + data.values.add(value); + return this; + } + + /** + * Sets the assignment time. Defaults to zero. + */ + public Builder setAssignmentTime(double assignmentTime) { + data.assignmentTime = assignmentTime; + return this; + } + + } + + private final Data data; + + private GlobalPropertyDimension(Data data) { + this.data = data; + } + + @Override + public List getExperimentMetaData() { + List result = new ArrayList<>(); + result.add(data.globalPropertyId.toString()); + return result; + } + + @Override + public int levelCount() { + return data.values.size(); + } + + @Override + public List executeLevel(DimensionContext dimensionContext, int level) { + GlobalPropertiesPluginData.Builder builder = dimensionContext + .getPluginDataBuilder(GlobalPropertiesPluginData.Builder.class); + Object value = data.values.get(level); + builder.setGlobalPropertyValue(data.globalPropertyId, value, data.assignmentTime); + List result = new ArrayList<>(); + result.add(value.toString()); + return result; + } + + /** + * Returns the global property id for this dimension + */ + public GlobalPropertyId getGlobalPropertyId() { + return data.globalPropertyId; + } + + /** + * Returns the ordered list of global property values for this dimension + */ + public List getValues() { + return new ArrayList<>(data.values); + } + + /** + * Returns the assignment time for the global property value + */ + public double getAssignmentTime() { + return data.assignmentTime; + } + + private static void validateValue(Object value) { + if (value == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_VALUE); + } + } + + private static void validateGlobalPropertyId(GlobalPropertyId globalPropertyId) { + if (globalPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + } + + @Override + public int hashCode() { + return Objects.hash(data); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + GlobalPropertyDimension other = (GlobalPropertyDimension) obj; + return Objects.equals(data, other.data); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/support/GlobalPropertyId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/support/GlobalPropertyId.java new file mode 100644 index 000000000..0ccea1a09 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/support/GlobalPropertyId.java @@ -0,0 +1,15 @@ +package gov.hhs.aspr.ms.gcm.plugins.globalproperties.support; + +import net.jcip.annotations.ThreadSafe; + +/** + * Marker interface for global property identifiers + */ +@ThreadSafe +public interface GlobalPropertyId { + @Override + public int hashCode(); + + @Override + public boolean equals(Object obj); +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/support/GlobalPropertyInitialization.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/support/GlobalPropertyInitialization.java new file mode 100644 index 000000000..96b89bcd6 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/support/GlobalPropertyInitialization.java @@ -0,0 +1,155 @@ +package gov.hhs.aspr.ms.gcm.plugins.globalproperties.support; + +import java.util.Optional; + +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * Immutable data class that represents the addition of a global property + */ +@Immutable +public class GlobalPropertyInitialization { + + private static class Data { + private GlobalPropertyId globalPropertyId; + private PropertyDefinition propertyDefinition; + private Object value; + + public Data() { + } + + public Data(Data data) { + globalPropertyId = data.globalPropertyId; + propertyDefinition = data.propertyDefinition; + value = data.value; + } + + } + + /** + * Returns a new Builder instance + */ + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Data data = new Data(); + + private Builder() { + } + + private void validate() { + if (data.propertyDefinition == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_DEFINITION); + } + if (data.globalPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + if (data.value == null) { + if (data.propertyDefinition.getDefaultValue().isEmpty()) { + throw new ContractException(PropertyError.INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT); + } + } else { + if (!data.propertyDefinition.getType().isAssignableFrom(data.value.getClass())) { + throw new ContractException(PropertyError.INCOMPATIBLE_VALUE); + } + } + } + + /** + * Returns the GlobalPropertyInitialization formed from the inputs. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_DEFINITION} + * if no property definition was provided
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * no property id was provided
    • + *
    • {@linkplain PropertyError#INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT} + * if no property value was provided and the property + * definition does not contain a default value
    • + *
    • (@linkplain PropertyError#INCOMPATIBLE_VALUE) + * if the property value type is not compatible with + * the property definition
    • + *
    + */ + public GlobalPropertyInitialization build() { + validate(); + return new GlobalPropertyInitialization(new Data(data)); + } + + /** + * Sets the property id. + * + * @throws ContractException {@linkplain PropertyError#NULL_PROPERTY_ID} if the + * property id is null + */ + public Builder setGlobalPropertyId(GlobalPropertyId globalPropertyId) { + if (globalPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + data.globalPropertyId = globalPropertyId; + return this; + } + + /** + * Sets the property definition. + * + * @throws ContractException {@linkplain PropertyError#NULL_PROPERTY_DEFINITION} + * if the property definition is null + */ + public Builder setPropertyDefinition(PropertyDefinition propertyDefinition) { + if (propertyDefinition == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_DEFINITION); + } + data.propertyDefinition = propertyDefinition; + return this; + } + + /** + * Sets the value of the global property that overrides any default value + * provided by the property definition + * + * @throws ContractException {@linkplain PropertyError#NULL_PROPERTY_VALUE} if + * the value is null + */ + public Builder setValue(Object value) { + if (value == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_VALUE); + } + data.value = value; + return this; + } + } + + private final Data data; + + private GlobalPropertyInitialization(Data data) { + this.data = data; + } + + /** + * Returns the global property id + */ + public GlobalPropertyId getGlobalPropertyId() { + return data.globalPropertyId; + } + + /** + * Returns the property definition + */ + public PropertyDefinition getPropertyDefinition() { + return data.propertyDefinition; + } + + /** + * Returns the property value + */ + public Optional getValue() { + return Optional.ofNullable(data.value); + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/support/SimpleGlobalPropertyId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/support/SimpleGlobalPropertyId.java new file mode 100644 index 000000000..6f67fe875 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/support/SimpleGlobalPropertyId.java @@ -0,0 +1,70 @@ +package gov.hhs.aspr.ms.gcm.plugins.globalproperties.support; + +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.errors.ContractException; + +/** + * A simple implementor of {@link GlobalPropertyId} that wraps a value. + */ +public final class SimpleGlobalPropertyId implements GlobalPropertyId { + + private final Object value; + + /** + * Creates a global property id from the given value. The value must implement a + * proper equals contract. + * + * @throws ContractException {@linkplain PropertyError#NULL_PROPERTY_VALUE} if + * the value is null + */ + public SimpleGlobalPropertyId(Object value) { + if (value == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_VALUE); + } + this.value = value; + } + + public Object getValue() { + return this.value; + } + + /** + * Standard implementation + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((value == null) ? 0 : value.hashCode()); + return result; + } + + /** + * Two {@link SimpleGlobalPropertyId} instances are equal if and only if their + * inputs are equal. + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof SimpleGlobalPropertyId)) { + return false; + } + SimpleGlobalPropertyId other = (SimpleGlobalPropertyId) obj; + if (value == null) { + if (other.value != null) { + return false; + } + } else if (!value.equals(other.value)) { + return false; + } + return true; + } + + @Override + public String toString() { + return value.toString(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/testsupport/GlobalPropertiesTestPluginFactory.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/testsupport/GlobalPropertiesTestPluginFactory.java new file mode 100644 index 000000000..c700eb3de --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/testsupport/GlobalPropertiesTestPluginFactory.java @@ -0,0 +1,224 @@ +package gov.hhs.aspr.ms.gcm.plugins.globalproperties.testsupport; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.GlobalPropertiesPlugin; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.reports.GlobalPropertyReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertiesError; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +/** + * A static test support class for the {@linkplain GlobalPropertiesPlugin}. + * Provides convenience methods for obtaining standarized PluginData for the + * listed Plugin. + *

    + * Also contains factory methods to obtain a list of plugins that is the minimal + * set needed to adequately test this Plugin that can be utilized with + *

    + * {@link TestSimulation#execute} + */ +public final class GlobalPropertiesTestPluginFactory { + + private GlobalPropertiesTestPluginFactory() { + } + + private static class Data { + private GlobalPropertiesPluginData globalPropertiesPluginData; + private GlobalPropertyReportPluginData globalPropertyReportPluginData; + private TestPluginData testPluginData; + + private Data(long seed, TestPluginData testPluginData) { + this.globalPropertiesPluginData = getStandardGlobalPropertiesPluginData(seed); + this.testPluginData = testPluginData; + } + } + + /** + * Factory class that facilitates the building of {@linkplain PluginData} with + * the various setter methods. + */ + public static class Factory { + private Data data; + + private Factory(Data data) { + this.data = data; + } + + /** + * Returns a list of plugins containing a GlobalProperties and a Test Plugin + * built from the contributed PluginDatas.
      + *
    • GlobalPropertiesPlugin is defaulted to one formed from + * {@link GlobalPropertiesTestPluginFactory#getStandardGlobalPropertiesPluginData} + *
    • + *
    • TestPlugin is formed from the TestPluginData passed into + * {@link GlobalPropertiesTestPluginFactory#factory}
    • + *
    + */ + public List getPlugins() { + List pluginsToAdd = new ArrayList<>(); + + GlobalPropertiesPlugin.Builder builder = GlobalPropertiesPlugin.builder(); + + builder.setGlobalPropertiesPluginData(this.data.globalPropertiesPluginData); + if (this.data.globalPropertyReportPluginData != null) { + builder.setGlobalPropertyReportPluginData(this.data.globalPropertyReportPluginData); + } + + Plugin globalPropertiesPlugin = builder.getGlobalPropertiesPlugin(); + pluginsToAdd.add(globalPropertiesPlugin); + + Plugin testPlugin = TestPlugin.getTestPlugin(this.data.testPluginData); + pluginsToAdd.add(testPlugin); + + return pluginsToAdd; + } + + /** + * Sets the {@link GlobalPropertiesPluginData} in this Factory. This explicit + * instance of pluginData will be used to create a GlobalPropertiesPlugin + * + * @throws ContractException {@linkplain GlobalPropertiesError#NULL_GLOBAL_PLUGIN_DATA} + * if the passed in pluginData is null + */ + public Factory setGlobalPropertiesPluginData(GlobalPropertiesPluginData globalPropertiesPluginData) { + if (globalPropertiesPluginData == null) { + throw new ContractException(GlobalPropertiesError.NULL_GLOBAL_PLUGIN_DATA); + } + this.data.globalPropertiesPluginData = globalPropertiesPluginData; + return this; + } + + /** + * Sets the {@link GlobalPropertyReportPluginData} in this Factory. This + * explicit instance of pluginData will be used to create a + * GlobalPropertiesPlugin + * + * @throws ContractException {@linkplain GlobalPropertiesError#NULL_GLOBAL_PROPERTY_REPORT_PLUGIN_DATA} + * if the passed in pluginData is null + */ + public Factory setGlobalPropertyReportPluginData( + GlobalPropertyReportPluginData globalPropertyReportPluginData) { + if (globalPropertyReportPluginData == null) { + throw new ContractException(GlobalPropertiesError.NULL_GLOBAL_PROPERTY_REPORT_PLUGIN_DATA); + } + this.data.globalPropertyReportPluginData = globalPropertyReportPluginData; + return this; + } + + } + + /** + * Returns a Factory that facilitates the creation of a minimal set of plugins + * needed to adequately test the {@link GlobalPropertiesPlugin} by generating: + *
      + *
    • {@link GlobalPropertiesPluginData}
    • + *
    + * either directly (by default) via + *
      + *
    • {@link #getStandardGlobalPropertiesPluginData}
    • + *
    + * or explicitly set via + *
      + *
    • {@link Factory#setGlobalPropertiesPluginData}
    • + *
    + * via the {@link Factory#getPlugins()} method. + * + * @throws ContractException {@linkplain NucleusError#NULL_PLUGIN_DATA} if + * testPluginData is null + */ + public static Factory factory(long seed, TestPluginData testPluginData) { + if (testPluginData == null) { + throw new ContractException(NucleusError.NULL_PLUGIN_DATA); + } + return new Factory(new Data(seed, testPluginData)); + } + + /** + * Returns a Factory that facilitates the creation of a minimal set of plugins + * needed to adequately test the {@link GlobalPropertiesPlugin} by generating: + *
      + *
    • {@link GlobalPropertiesPluginData}
    • + *
    + * either directly (by default) via + *
      + *
    • {@link #getStandardGlobalPropertiesPluginData}
    • + *
    + * or explicitly set via + *
      + *
    • {@link Factory#setGlobalPropertiesPluginData}
    • + *
    + * via the {@link Factory#getPlugins()} method. + * + * @throws ContractException {@linkplain NucleusError#NULL_ACTOR_CONTEXT_CONSUMER} + * if consumer is null + */ + public static Factory factory(long seed, Consumer consumer) { + if (consumer == null) { + throw new ContractException(NucleusError.NULL_ACTOR_CONTEXT_CONSUMER); + } + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, consumer)); + TestPluginData testPluginData = pluginBuilder.build(); + return factory(seed, testPluginData); + } + + /** + * Returns a Standardized GlobalPropertiesPluginData that is minimally adequate + * for testing the GlobalPropertiesPlugin The resulting + * GlobalPropertiesPluginData will include: + *
      + *
    • Every GlobalPropertyId included in {@link TestGlobalPropertyId} + *
        + *
      • with the defined propertyDefinition for each
      • + *
      + *
    + */ + public static GlobalPropertiesPluginData getStandardGlobalPropertiesPluginData(long seed) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + + GlobalPropertiesPluginData.Builder globalsPluginBuilder = GlobalPropertiesPluginData.builder(); + + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.getGlobalPropertyIds()) { + globalsPluginBuilder.defineGlobalProperty(testGlobalPropertyId, + testGlobalPropertyId.getPropertyDefinition(), 0); + boolean hasDefaultValue = testGlobalPropertyId.getPropertyDefinition().getDefaultValue().isPresent(); + if (!hasDefaultValue) { + globalsPluginBuilder.setGlobalPropertyValue(testGlobalPropertyId, + testGlobalPropertyId.getRandomPropertyValue(randomGenerator), 0); + } + } + + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId + .getShuffledGlobalPropertyIds(randomGenerator)) { + + boolean hasDefaultValue = testGlobalPropertyId.getPropertyDefinition().getDefaultValue().isPresent(); + boolean setValue = randomGenerator.nextBoolean(); + if (hasDefaultValue && setValue) { + // set a value to the default + globalsPluginBuilder.setGlobalPropertyValue(testGlobalPropertyId, + testGlobalPropertyId.getPropertyDefinition().getDefaultValue().get(), 0); + } else if (setValue) { + // set a value to not the default + globalsPluginBuilder.setGlobalPropertyValue(testGlobalPropertyId, + testGlobalPropertyId.getRandomPropertyValue(randomGenerator), 0); + } + } + + return globalsPluginBuilder.build(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/testsupport/TestAuxiliaryGlobalPropertyId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/testsupport/TestAuxiliaryGlobalPropertyId.java new file mode 100644 index 000000000..6ddcd1fae --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/testsupport/TestAuxiliaryGlobalPropertyId.java @@ -0,0 +1,108 @@ +package gov.hhs.aspr.ms.gcm.plugins.globalproperties.testsupport; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; + +/** + * Enumeration that provides a variety of global property definitions for + * testing purposes + */ +public enum TestAuxiliaryGlobalPropertyId implements GlobalPropertyId { + + GLOBAL_AUX_PROPERTY_1_BOOLEAN_MUTABLE(PropertyDefinition.builder()// + .setType(Boolean.class)// + .setDefaultValue(false)// + .setPropertyValueMutability(true)// + .build() // + ), // + GLOBAL_AUX_PROPERTY_2_INTEGER_MUTABLE(PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(0)// + .setPropertyValueMutability(true)// + .build() // + ), // + GLOBAL_AUX_PROPERTY_3_DOUBLE_MUTABLE(PropertyDefinition.builder()// + .setType(Double.class)// + .setDefaultValue(0.0)// + .setPropertyValueMutability(true)// + .build() // + ), // + GLOBAL_AUX_PROPERTY_4_BOOLEAN_IMMUTABLE(PropertyDefinition.builder()// + .setType(Boolean.class)// + .setDefaultValue(false)// + .setPropertyValueMutability(false)// + .build() // + ), // + GLOBAL_AUX_PROPERTY_5_INTEGER_IMMUTABLE(PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(0)// + .setPropertyValueMutability(false)// + .build() // + ), // + GLOBAL_AUX_PROPERTY_6_DOUBLE_IMMUTABLE(PropertyDefinition.builder()// + .setType(Double.class)// + .setDefaultValue(0.0)// + .setPropertyValueMutability(false)// + .build() // + );// + + /** + * Returns a randomly selected member of this enumeration + */ + public static TestAuxiliaryGlobalPropertyId getRandomGlobalPropertyId(final RandomGenerator randomGenerator) { + return TestAuxiliaryGlobalPropertyId.values()[randomGenerator + .nextInt(TestAuxiliaryGlobalPropertyId.values().length)]; + } + + /** + * Returns a randomly selected value that is compatible with this member's + * associated property definition. + */ + @SuppressWarnings("unchecked") + public T getRandomPropertyValue(final RandomGenerator randomGenerator) { + switch (this) { + case GLOBAL_AUX_PROPERTY_1_BOOLEAN_MUTABLE: + Boolean b1 = randomGenerator.nextBoolean(); + return (T) b1; + case GLOBAL_AUX_PROPERTY_2_INTEGER_MUTABLE: + Integer i2 = randomGenerator.nextInt(); + return (T) i2; + case GLOBAL_AUX_PROPERTY_3_DOUBLE_MUTABLE: + Double d3 = randomGenerator.nextDouble(); + return (T) d3; + case GLOBAL_AUX_PROPERTY_4_BOOLEAN_IMMUTABLE: + Boolean b4 = randomGenerator.nextBoolean(); + return (T) b4; + case GLOBAL_AUX_PROPERTY_5_INTEGER_IMMUTABLE: + Integer i5 = randomGenerator.nextInt(); + return (T) i5; + case GLOBAL_AUX_PROPERTY_6_DOUBLE_IMMUTABLE: + Double d6 = randomGenerator.nextDouble(); + return (T) d6; + default: + throw new RuntimeException("unhandled case: " + this); + + } + } + + private final PropertyDefinition propertyDefinition; + + private TestAuxiliaryGlobalPropertyId(PropertyDefinition propertyDefinition) { + this.propertyDefinition = propertyDefinition; + } + + public PropertyDefinition getPropertyDefinition() { + return propertyDefinition; + } + + /** + * Returns a new {@link GlobalPropertyId} instance. + */ + public static GlobalPropertyId getUnknownGlobalPropertyId() { + return new GlobalPropertyId() { + }; + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/testsupport/TestGlobalPropertyId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/testsupport/TestGlobalPropertyId.java new file mode 100644 index 000000000..36210c941 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/testsupport/TestGlobalPropertyId.java @@ -0,0 +1,133 @@ +package gov.hhs.aspr.ms.gcm.plugins.globalproperties.testsupport; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; + +/** + * Enumeration that provides a variety of global property definitions for + * testing purposes + */ +public enum TestGlobalPropertyId implements GlobalPropertyId { + + GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE(PropertyDefinition.builder()// + .setType(Boolean.class)// + .setDefaultValue(false)// + .setPropertyValueMutability(true)// + .build() // + ), // + GLOBAL_PROPERTY_2_INTEGER_MUTABLE(PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(0)// + .setPropertyValueMutability(true)// + .build() // + ), // + GLOBAL_PROPERTY_3_DOUBLE_MUTABLE(PropertyDefinition.builder()// + .setType(Double.class)// + .setPropertyValueMutability(true)// + .build() // + ), // + GLOBAL_PROPERTY_4_BOOLEAN_IMMUTABLE(PropertyDefinition.builder()// + .setType(Boolean.class)// + .setDefaultValue(false)// + .setPropertyValueMutability(false)// + .build() // + ), // + GLOBAL_PROPERTY_5_INTEGER_IMMUTABLE(PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(0)// + .setPropertyValueMutability(false)// + .build() // + ), // + GLOBAL_PROPERTY_6_DOUBLE_IMMUTABLE(PropertyDefinition.builder()// + .setType(Double.class)// + .setDefaultValue(0.0)// + .setPropertyValueMutability(false)// + .build() // + );// + + /** + * Returns a randomly selected member of this enumeration + */ + public static TestGlobalPropertyId getRandomGlobalPropertyId(final RandomGenerator randomGenerator) { + return TestGlobalPropertyId.values()[randomGenerator.nextInt(TestGlobalPropertyId.values().length)]; + } + + public static TestGlobalPropertyId getRandomMutableGlobalPropertyId(final RandomGenerator randomGenerator) { + List candidates = new ArrayList<>(); + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + if (testGlobalPropertyId.getPropertyDefinition().propertyValuesAreMutable()) { + candidates.add(testGlobalPropertyId); + } + } + return candidates.get(randomGenerator.nextInt(candidates.size())); + } + + /** + * Returns a randomly selected value that is compatible with this member's + * associated property definition. + */ + @SuppressWarnings("unchecked") + public T getRandomPropertyValue(final RandomGenerator randomGenerator) { + switch (this) { + case GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE: + Boolean b1 = randomGenerator.nextBoolean(); + return (T) b1; + case GLOBAL_PROPERTY_2_INTEGER_MUTABLE: + Integer i2 = randomGenerator.nextInt(); + return (T) i2; + case GLOBAL_PROPERTY_3_DOUBLE_MUTABLE: + Double d3 = randomGenerator.nextDouble(); + return (T) d3; + case GLOBAL_PROPERTY_4_BOOLEAN_IMMUTABLE: + Boolean b4 = randomGenerator.nextBoolean(); + return (T) b4; + case GLOBAL_PROPERTY_5_INTEGER_IMMUTABLE: + Integer i5 = randomGenerator.nextInt(); + return (T) i5; + case GLOBAL_PROPERTY_6_DOUBLE_IMMUTABLE: + Double d6 = randomGenerator.nextDouble(); + return (T) d6; + default: + throw new RuntimeException("unhandled case: " + this); + + } + } + + private final PropertyDefinition propertyDefinition; + + private TestGlobalPropertyId(PropertyDefinition propertyDefinition) { + this.propertyDefinition = propertyDefinition; + } + + public PropertyDefinition getPropertyDefinition() { + return propertyDefinition; + } + + /** + * Returns a new {@link GlobalPropertyId} instance. + */ + public static GlobalPropertyId getUnknownGlobalPropertyId() { + return new GlobalPropertyId() { + }; + } + + public static List getGlobalPropertyIds() { + return Arrays.asList(TestGlobalPropertyId.values()); + } + + public static List getShuffledGlobalPropertyIds(RandomGenerator randomGenerator) { + List result = getGlobalPropertyIds(); + Random random = new Random(randomGenerator.nextLong()); + Collections.shuffle(result, random); + return result; + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/GroupsPlugin.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/GroupsPlugin.java new file mode 100644 index 000000000..8e7d01635 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/GroupsPlugin.java @@ -0,0 +1,110 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups; + +import java.util.Optional; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.plugins.groups.datamanagers.GroupsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.groups.datamanagers.GroupsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.groups.reports.GroupPopulationReport; +import gov.hhs.aspr.ms.gcm.plugins.groups.reports.GroupPopulationReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.groups.reports.GroupPropertyReport; +import gov.hhs.aspr.ms.gcm.plugins.groups.reports.GroupPropertyReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupError; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePluginId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPluginId; +import util.errors.ContractException; + +/** + * A plugin providing a group data manager to the simulation. + */ +public final class GroupsPlugin { + + private static class Data { + private GroupsPluginData groupsPluginData; + private GroupPopulationReportPluginData groupPopulationReportPluginData; + private GroupPropertyReportPluginData groupPropertyReportPluginData; + } + + private GroupsPlugin() { + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Builder() { + } + + private Data data = new Data(); + + private void validate() { + if (data.groupsPluginData == null) { + throw new ContractException(GroupError.NULL_GROUP_PLUGIN_DATA); + } + } + + public Builder setGroupsPluginData(GroupsPluginData groupsPluginData) { + data.groupsPluginData = groupsPluginData; + return this; + } + + public Builder setGroupPopulationReportPluginData( + GroupPopulationReportPluginData groupPopulationReportPluginData) { + data.groupPopulationReportPluginData = groupPopulationReportPluginData; + return this; + } + + public Builder setGroupPropertyReportPluginData(GroupPropertyReportPluginData groupPropertyReportPluginData) { + data.groupPropertyReportPluginData = groupPropertyReportPluginData; + return this; + } + + /** + * Builds the PersonPropertiesPlugin from the collected inputs + * + * @throws ContractException {@linkplain GroupError#NULL_GROUP_PLUGIN_DATA} if + * the groupsPluginData is null + */ + public Plugin getGroupsPlugin() { + + validate(); + Plugin.Builder builder = Plugin.builder();// + builder.setPluginId(GroupsPluginId.PLUGIN_ID);// + + if (data.groupPopulationReportPluginData != null) { + builder.addPluginData(data.groupPopulationReportPluginData); + } + + if (data.groupPropertyReportPluginData != null) { + builder.addPluginData(data.groupPropertyReportPluginData); + } + + builder.addPluginData(data.groupsPluginData);// + builder.addPluginDependency(PeoplePluginId.PLUGIN_ID);// + builder.addPluginDependency(StochasticsPluginId.PLUGIN_ID)// + .setInitializer((c) -> { + GroupsPluginData pluginData = c.getPluginData(GroupsPluginData.class).get(); + c.addDataManager(new GroupsDataManager(pluginData)); + + Optional optional1 = c + .getPluginData(GroupPopulationReportPluginData.class); + if (optional1.isPresent()) { + GroupPopulationReportPluginData groupPopulationReportPluginData = optional1.get(); + c.addReport(new GroupPopulationReport(groupPopulationReportPluginData)::init); + } + + Optional optional2 = c + .getPluginData(GroupPropertyReportPluginData.class); + if (optional2.isPresent()) { + GroupPropertyReportPluginData groupPropertyReportPluginData = optional2.get(); + c.addReport(new GroupPropertyReport(groupPropertyReportPluginData)::init); + } + + });// + return builder.build(); + + } + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/GroupsPluginId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/GroupsPluginId.java new file mode 100644 index 000000000..eedc82169 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/GroupsPluginId.java @@ -0,0 +1,13 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; + +/** + * Static plugin id implementation for the GroupPlugin + */ +public final class GroupsPluginId implements PluginId { + public final static PluginId PLUGIN_ID = new GroupsPluginId(); + + private GroupsPluginId() { + }; +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/datamanagers/GroupsDataManager.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/datamanagers/GroupsDataManager.java new file mode 100644 index 000000000..33528cfd0 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/datamanagers/GroupsDataManager.java @@ -0,0 +1,2426 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.datamanagers; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.Set; +import java.util.function.Supplier; + +import org.apache.commons.math3.random.RandomGenerator; +import org.apache.commons.math3.util.Pair; + +import gov.hhs.aspr.ms.gcm.nucleus.DataManager; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.nucleus.EventFilter; +import gov.hhs.aspr.ms.gcm.nucleus.IdentifiableFunctionMap; +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupImminentRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupMembershipAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupMembershipRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupPropertyDefinitionEvent; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupPropertyUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupTypeAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupConstructionInfo; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupError; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupId; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupPropertyDefinitionInitialization; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupPropertyValue; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupSampler; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupTypeId; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupWeightingFunction; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupsContext; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.events.PersonRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.RandomNumberGeneratorId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.StochasticsError; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.BooleanPropertyManager; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.DoublePropertyManager; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.EnumPropertyManager; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.FloatPropertyManager; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.IndexedPropertyManager; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.IntPropertyManager; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.ObjectPropertyManager; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.arraycontainers.DoubleValueContainer; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.arraycontainers.IntValueContainer; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.arraycontainers.ObjectValueContainer; +import net.jcip.annotations.GuardedBy; +import util.errors.ContractException; + +/** + *

    + * Mutable data manager for groups + *

    + * Implementation Notes + *

    + * Group membership is managed through four mappings: 1)People to Groups, + * 2)Groups to People, 3)Groups to GroupTypes and 4)GroupTypes to Groups. Except + * for groups to types, all are implemented as ObjectValueContainers over + * ArrayLists of Integers rather than as straight-forward maps. This design was + * chosen to help minimize the memory requirements for storing grouping data for + * millions of people. The principle assumptions that have driven this design + * are: 1) The number of people per group is usually fairly small and rarely + * exceeds 100 2) The number of groups per person is usually very small and + * rarely exceeds 10 3) The number of group types is fairly small and rarely + * exceeds 20 The mapping from groups to types is accomplished with an + * IntValueContainer since the number of group types is small and we can treat + * the group type ids as Bytes or Shorts. The typesToIndexesMap and + * indexesToTypesMap serve to help convert group-type Object references to and + * from integers. + *

    + */ +public final class GroupsDataManager extends DataManager { + + // container for group property values + private final Map> groupPropertyManagerMap = new LinkedHashMap<>(); + + private Map> groupPropertyDefinitions = new LinkedHashMap<>(); + + ////////////////////////////////////////////////// + + // typeIndex->List + private final ObjectValueContainer typesToGroupsMap = new ObjectValueContainer(null, this::getGroupIndexIterator); + + // groupIndex->typeIndex + private final IntValueContainer groupsToTypesMap = new IntValueContainer(-1, this::getGroupIndexIterator); + + // groupType -> typeIndex + private final Map typesToIndexesMap = new LinkedHashMap<>(); + + // typeIndex->groupType + private final List indexesToTypesMap = new ArrayList<>(); + + // groupIndex->List by order of addition + private ObjectValueContainer groupsToPeopleMap; + + // personIndex->List by order of addition + private final ObjectValueContainer peopleToGroupsMap = new ObjectValueContainer(null, this::getGroupIndexIterator); + + /////////////////////////////////////// + + /* + * Used to generate new group id values + */ + private int masterGroupId; + + private final Map> nonDefaultBearingPropertyIds = new LinkedHashMap<>(); + + private Map nonDefaultChecks = new LinkedHashMap<>(); + // Guard for both weights array and weightedPersonIds array + private boolean samplingIsLocked; + + @GuardedBy("samplingIsLocked") + // weights array that is reused during sampling + private double[] weights; + + @GuardedBy("samplingIsLocked") + // person array that is reused during sampling + private PersonId[] weightedPersonIds; + + private StochasticsDataManager stochasticsDataManager; + + private DataManagerContext dataManagerContext; + + private GroupsContext groupsContext; + + private final GroupsPluginData groupsPluginData; + + private PeopleDataManager peopleDataManager; + + /** + * Constructs this person group data manager + * + * @throws ContractException {@linkplain GroupError#NULL_GROUP_INITIALIZATION_DATA} + * if groupsPluginData is null + */ + public GroupsDataManager(GroupsPluginData groupsPluginData) { + if (groupsPluginData == null) { + throw new ContractException(GroupError.NULL_GROUP_INITIALIZATION_DATA); + } + this.groupsPluginData = groupsPluginData; + } + + /** + * Initial behavior + * Adds all event labelers defined by the following events + *
      + *
    • {@linkplain GroupMembershipAdditionEvent}
    • + *
    • {@linkplain GroupMembershipRemovalEvent}
    • + *
    • {@linkplain GroupAdditionEvent}
    • + *
    • {@linkplain GroupImminentRemovalEvent}
    • + *
    • {@linkplain GroupPropertyUpdateEvent}
    • + *
    + * Adds groups, group memberships, group properties from the + * {@linkplain GroupsPluginData} + * Subscribes to the following events: + *
      + *
    • {@linkplain PersonRemovalEvent} Removes the person from all groups by + * scheduling the removal for the current time. This allows references and group + * memberships to remain long enough for resolvers, agents and reports to have + * final reference to the person while still associated with any relevant + * groups.
    • + *
    + * + * @throws ContractException + *
      + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person id is unknown
    • + *
    + */ + @Override + public void init(DataManagerContext dataManagerContext) { + + super.init(dataManagerContext); + if (dataManagerContext == null) { + throw new ContractException(NucleusError.NULL_SIMULATION_CONTEXT); + } + + this.dataManagerContext = dataManagerContext; + groupsContext = new GroupsContextImpl(dataManagerContext); + + stochasticsDataManager = dataManagerContext.getDataManager(StochasticsDataManager.class); + peopleDataManager = dataManagerContext.getDataManager(PeopleDataManager.class); + + groupsToPeopleMap = new ObjectValueContainer(null, peopleDataManager::getPersonIndexIterator); + + dataManagerContext.subscribe(GroupAdditionMutationEvent.class, this::handleGroupAdditionMutationEvent); + dataManagerContext.subscribe(GroupTypeAdditionMutationEvent.class, this::handleGroupTypeAdditionMutationEvent); + dataManagerContext.subscribe(GroupMembershipAdditionMutationEvent.class, + this::handleGroupMembershipAdditionMutationEvent); + dataManagerContext.subscribe(GroupPropertyDefinitionMutationEvent.class, + this::handleGroupPropertyDefinitionMutationEvent); + dataManagerContext.subscribe(GroupRemovalMutationEvent.class, this::handleGroupRemovalMutationEvent); + dataManagerContext.subscribe(GroupMembershipRemovalMutationEvent.class, + this::handleGroupMembershipRemovalMutationEvent); + dataManagerContext.subscribe(GroupPropertyUpdateMutationEvent.class, + this::handleGroupPropertyUpdateMutationEvent); + + loadGroupTypes(); + + loadGroupPropertyDefinitions(); + + loadGroups(); + + loadGroupMembership(); + + loadGroupPropertyValues(); + + dataManagerContext.subscribe(PersonRemovalEvent.class, this::handlePersonRemovalEvent); + if (dataManagerContext.stateRecordingIsScheduled()) { + dataManagerContext.subscribeToSimulationClose(this::recordSimulationState); + } + } + + private void recordSimulationState(DataManagerContext dataManagerContext) { + GroupsPluginData.Builder builder = GroupsPluginData.builder(); + + builder.setNextGroupIdValue(masterGroupId); + + for (final GroupTypeId groupTypeId : typesToIndexesMap.keySet()) { + builder.addGroupTypeId(groupTypeId); + } + + for (GroupTypeId groupTypeId : groupPropertyDefinitions.keySet()) { + Map map = groupPropertyDefinitions.get(groupTypeId); + for (GroupPropertyId groupPropertyId : map.keySet()) { + PropertyDefinition propertyDefinition = map.get(groupPropertyId); + builder.defineGroupProperty(groupTypeId, groupPropertyId, propertyDefinition); + } + } + + for (final GroupTypeId groupTypeId : typesToIndexesMap.keySet()) { + Integer typeIndex = typesToIndexesMap.get(groupTypeId); + final List groups = typesToGroupsMap.getValue(typeIndex); + if (groups != null) { + for (GroupId groupId : groups) { + builder.addGroup(groupId, groupTypeId); + } + } + } + + // transferring group memberships + + List people = peopleDataManager.getPeople(); + for (final PersonId personId : people) { + List list = peopleToGroupsMap.getValue(personId.getValue()); + if (list != null) { + for (GroupId groupId : list) { + builder.addGroupToPerson(groupId, personId); + } + } + } + + for (final GroupTypeId groupTypeId : typesToIndexesMap.keySet()) { + Integer typeIndex = typesToIndexesMap.get(groupTypeId); + List groupIds = typesToGroupsMap.getValue(typeIndex); + if (groupIds != null) { + for (GroupId groupId : groupIds) { + List list = groupsToPeopleMap.getValue(groupId.getValue()); + if (list != null) { + for (PersonId personId : list) { + builder.addPersonToGroup(personId, groupId); + } + } + } + } + } + + // transferring group property values + for (final GroupTypeId groupTypeId : typesToIndexesMap.keySet()) { + Integer typeIndex = typesToIndexesMap.get(groupTypeId); + final List groups = typesToGroupsMap.getValue(typeIndex); + if (groups != null) { + Map map = groupPropertyManagerMap.get(groupTypeId); + for (GroupPropertyId groupPropertyId : map.keySet()) { + IndexedPropertyManager indexedPropertyManager = map.get(groupPropertyId); + PropertyDefinition propertyDefinition = groupPropertyDefinitions.get(groupTypeId) + .get(groupPropertyId); + Optional optional = propertyDefinition.getDefaultValue(); + + if (optional.isPresent()) { + Object defaultValue = optional.get(); + for (GroupId groupId : groups) { + Object propertyValue = indexedPropertyManager.getPropertyValue(groupId.getValue()); + if (!propertyValue.equals(defaultValue)) { + builder.setGroupPropertyValue(groupId, groupPropertyId, propertyValue); + } + } + } else { + for (GroupId groupId : groups) { + Object propertyValue = indexedPropertyManager.getPropertyValue(groupId.getValue()); + builder.setGroupPropertyValue(groupId, groupPropertyId, propertyValue); + } + } + } + } + } + + dataManagerContext.releaseOutput(builder.build()); + } + + private void loadGroupPropertyDefinitions() { + + groupPropertyDefinitions = groupsPluginData.getGroupPropertyDefinitions(); + + for (final GroupTypeId groupTypeId : groupPropertyDefinitions.keySet()) { + Map propMap = groupPropertyDefinitions.get(groupTypeId); + + for (final GroupPropertyId groupPropertyId : propMap.keySet()) { + final PropertyDefinition propertyDefinition = propMap.get(groupPropertyId); + + if (propertyDefinition.getDefaultValue().isEmpty()) { + nonDefaultBearingPropertyIds.get(groupTypeId).put(groupPropertyId, + nonDefaultBearingPropertyIds.size()); + } + + Map managerMap = groupPropertyManagerMap.get(groupTypeId); + final IndexedPropertyManager indexedPropertyManager = getIndexedPropertyManager(propertyDefinition, 0); + managerMap.put(groupPropertyId, indexedPropertyManager); + } + } + + // for (final GroupTypeId groupTypeId : groupsPluginData.getGroupTypeIds()) { + // final Set propertyIds = + // groupsPluginData.getGroupPropertyIds(groupTypeId); + // for (final GroupPropertyId groupPropertyId : propertyIds) { + // final PropertyDefinition propertyDefinition = + // groupsPluginData.getGroupPropertyDefinition(groupTypeId, + // groupPropertyId); + // if (propertyDefinition.getDefaultValue().isEmpty()) { + // nonDefaultBearingPropertyIds.get(groupTypeId).put(groupPropertyId, + // nonDefaultBearingPropertyIds.size()); + // } + // + // Map map = + // groupPropertyDefinitions.get(groupTypeId); + // map.put(groupPropertyId, propertyDefinition); + // + // Map managerMap = + // groupPropertyManagerMap.get(groupTypeId); + // final IndexedPropertyManager indexedPropertyManager = + // getIndexedPropertyManager(propertyDefinition, 0); + // managerMap.put(groupPropertyId, indexedPropertyManager); + // } + // } + for (GroupTypeId groupTypeId : nonDefaultBearingPropertyIds.keySet()) { + Map map = nonDefaultBearingPropertyIds.get(groupTypeId); + nonDefaultChecks.put(groupTypeId, new boolean[map.size()]); + } + } + + private void loadGroupTypes() { + for (final GroupTypeId groupTypeId : groupsPluginData.getGroupTypeIds()) { + final int index = typesToIndexesMap.size(); + typesToIndexesMap.put(groupTypeId, index); + indexesToTypesMap.add(groupTypeId); + groupPropertyManagerMap.put(groupTypeId, new LinkedHashMap<>()); + // groupPropertyDefinitions.put(groupTypeId, new LinkedHashMap<>()); + nonDefaultBearingPropertyIds.put(groupTypeId, new LinkedHashMap<>()); + } + } + + private static record GroupTypeAdditionMutationEvent(GroupTypeId groupTypeId) implements Event { + } + + /** + * Adds a group type id. + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if + * the group type id is null
    • + *
    • {@linkplain GroupError#DUPLICATE_GROUP_TYPE} if + * the group type id is already present
    • + *
    + */ + public void addGroupType(GroupTypeId groupTypeId) { + dataManagerContext.releaseMutationEvent(new GroupTypeAdditionMutationEvent(groupTypeId)); + } + + private void handleGroupTypeAdditionMutationEvent(DataManagerContext dataManagerContext, + GroupTypeAdditionMutationEvent groupTypeAdditionMutationEvent) { + GroupTypeId groupTypeId = groupTypeAdditionMutationEvent.groupTypeId(); + validateGroupTypeIdIsUnknown(groupTypeId); + final int index = typesToIndexesMap.size(); + typesToIndexesMap.put(groupTypeId, index); + indexesToTypesMap.add(groupTypeId); + groupPropertyManagerMap.put(groupTypeId, new LinkedHashMap<>()); + nonDefaultBearingPropertyIds.put(groupTypeId, new LinkedHashMap<>()); + nonDefaultChecks.put(groupTypeId, new boolean[0]); + + if (dataManagerContext.subscribersExist(GroupTypeAdditionEvent.class)) { + dataManagerContext.releaseObservationEvent(new GroupTypeAdditionEvent(groupTypeId)); + } + } + + private void clearNonDefaultChecks(GroupTypeId groupTypeId) { + boolean[] checkArray = nonDefaultChecks.get(groupTypeId); + for (int i = 0; i < checkArray.length; i++) { + checkArray[i] = false; + } + } + + private void markAssigned(GroupTypeId groupTypeId, GroupPropertyId groupPropertyId) { + Integer nonDefaultPropertyIndex = nonDefaultBearingPropertyIds.get(groupTypeId).get(groupPropertyId); + if (nonDefaultPropertyIndex != null) { + nonDefaultChecks.get(groupTypeId)[nonDefaultPropertyIndex] = true; + } + } + + private void verifyNonDefaultChecks(GroupTypeId groupTypeId) { + boolean[] checkArray = nonDefaultChecks.get(groupTypeId); + boolean missingPropertyAssignments = false; + + for (int i = 0; i < checkArray.length; i++) { + if (!checkArray[i]) { + missingPropertyAssignments = true; + break; + } + } + + if (missingPropertyAssignments) { + StringBuilder sb = new StringBuilder(); + int index = -1; + boolean firstMember = true; + Map map = nonDefaultBearingPropertyIds.get(groupTypeId); + for (GroupPropertyId groupPropertyId : map.keySet()) { + index++; + if (!checkArray[index]) { + if (firstMember) { + firstMember = false; + } else { + sb.append(", "); + } + sb.append(groupPropertyId); + } + } + throw new ContractException(PropertyError.INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT, sb.toString()); + } + } + + private static record GroupPropertyDefinitionMutationEvent( + GroupPropertyDefinitionInitialization groupPropertyDefinitionInitialization) implements Event { + } + + /** + * Defines a new group property + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} + * if the group type id is unknown
    • + *
    • {@linkplain PropertyError#DUPLICATE_PROPERTY_DEFINITION} + * if the group property id is already known
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_ID} if the + * groupPropertyDefinitionInitialization contains a + * property assignment for a group that does not + * exist.
    • + *
    • {@linkplain GroupError#INCORRECT_GROUP_TYPE_ID} + * if the groupPropertyDefinitionInitialization + * contains a property assignment for a group that is + * not of the correct group type.
    • + *
    • {@linkplain PropertyError#INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT} + * if the groupPropertyDefinitionInitialization does + * not contain property value assignments for every + * extant group when the property definition does not + * contain a default value
    • + *
    + */ + public void defineGroupProperty(GroupPropertyDefinitionInitialization groupPropertyDefinitionInitialization) { + dataManagerContext + .releaseMutationEvent(new GroupPropertyDefinitionMutationEvent(groupPropertyDefinitionInitialization)); + } + + private void handleGroupPropertyDefinitionMutationEvent(DataManagerContext dataManagerContext, + GroupPropertyDefinitionMutationEvent groupPropertyDefinitionMutationEvent) { + GroupPropertyDefinitionInitialization groupPropertyDefinitionInitialization = groupPropertyDefinitionMutationEvent.groupPropertyDefinitionInitialization; + GroupTypeId groupTypeId = groupPropertyDefinitionInitialization.getGroupTypeId(); + GroupPropertyId groupPropertyId = groupPropertyDefinitionInitialization.getPropertyId(); + PropertyDefinition propertyDefinition = groupPropertyDefinitionInitialization.getPropertyDefinition(); + + validateGroupTypeId(groupTypeId); + validateNewGroupPropertyId(groupTypeId, groupPropertyId); + validatePropertyDefinitionNotNull(propertyDefinition); + + int requiredGroupTypeIndex = typesToIndexesMap.get(groupTypeId); + + /* + * Validate the contained property value assignments. We do not have to validate + * the values since they are guaranteed to be consistent with the property + * definition by contract. + */ + for (Pair pair : groupPropertyDefinitionInitialization.getPropertyValues()) { + GroupId groupId = pair.getFirst(); + int gId = groupId.getValue(); + int groupTypeIndex = groupsToTypesMap.getValueAsInt(gId); + + if (groupTypeIndex < 0) { + throw new ContractException(GroupError.UNKNOWN_GROUP_ID, groupId); + } + if (groupTypeIndex != requiredGroupTypeIndex) { + throw new ContractException(GroupError.INCORRECT_GROUP_TYPE_ID, + groupId + " is not of type " + groupTypeId); + } + } + /* + * Determine whether we will need to check that every group of the given type + * has been assigned a value because the property definition does not contain a + * default value. + */ + boolean checkAllGroupsHaveValues = propertyDefinition.getDefaultValue().isEmpty(); + if (checkAllGroupsHaveValues) { + /* + * create a bit set for tracking which groups received a property value + */ + int idLimit = masterGroupId; + BitSet coverageSet = new BitSet(idLimit); + + /* + * record the property values and update the bit set that is tracking assignment + * coverate + */ + for (Pair pair : groupPropertyDefinitionInitialization.getPropertyValues()) { + GroupId groupId = pair.getFirst(); + int gId = groupId.getValue(); + coverageSet.set(gId); + } + + /* + * Show that all groups of the of group type did indeed get a value assignment + */ + for (int i = 0; i < idLimit; i++) { + // only check groups having the correct type + if (groupsToTypesMap.getValueAsInt(i) == requiredGroupTypeIndex) { + boolean groupCovered = coverageSet.get(i); + if (!groupCovered) { + throw new ContractException(PropertyError.INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT); + } + } + } + } + + // integrate the new group property id and definition + Map managerMap = groupPropertyManagerMap.get(groupTypeId); + IndexedPropertyManager indexedPropertyManager = getIndexedPropertyManager(propertyDefinition, 0); + managerMap.put(groupPropertyId, indexedPropertyManager); + DoubleValueContainer doubleValueContainer = new DoubleValueContainer(0, this::getGroupIndexIterator); + Map map = groupPropertyDefinitions.get(groupTypeId); + if (map == null) { + map = new LinkedHashMap<>(); + groupPropertyDefinitions.put(groupTypeId, map); + } + map.put(groupPropertyId, propertyDefinition); + + /* + * update internal tracking mechanisms for properties not having default values + */ + if (checkAllGroupsHaveValues) { + nonDefaultBearingPropertyIds.get(groupTypeId).put(groupPropertyId, nonDefaultBearingPropertyIds.size()); + nonDefaultChecks.put(groupTypeId, new boolean[nonDefaultBearingPropertyIds.size()]); + } + + for (Pair pair : groupPropertyDefinitionInitialization.getPropertyValues()) { + GroupId groupId = pair.getFirst(); + int gId = groupId.getValue(); + Object value = pair.getSecond(); + indexedPropertyManager.setPropertyValue(gId, value); + doubleValueContainer.setValue(gId, dataManagerContext.getTime()); + } + + if (dataManagerContext.subscribersExist(GroupPropertyDefinitionEvent.class)) { + dataManagerContext.releaseObservationEvent(new GroupPropertyDefinitionEvent(groupTypeId, groupPropertyId)); + } + + } + + private void validatePropertyDefinitionNotNull(PropertyDefinition propertyDefinition) { + if (propertyDefinition == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_DEFINITION); + } + } + + private void validateNewGroupPropertyId(final GroupTypeId groupTypeId, final GroupPropertyId groupPropertyId) { + if (groupPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + + final Map map = groupPropertyManagerMap.get(groupTypeId); + if (map == null || map.containsKey(groupPropertyId)) { + throw new ContractException(PropertyError.DUPLICATE_PROPERTY_DEFINITION); + } + + } + + private void validateGroupTypeIdIsUnknown(final GroupTypeId groupTypeId) { + + if (groupTypeId == null) { + throw new ContractException(GroupError.NULL_GROUP_TYPE_ID); + } + + if (typesToIndexesMap.keySet().contains(groupTypeId)) { + throw new ContractException(GroupError.DUPLICATE_GROUP_TYPE); + } + + } + + private void loadGroupMembership() { + List people = peopleDataManager.getPeople(); + for (final PersonId personId : people) { + List groupsForPerson = groupsPluginData.getGroupsForPerson(personId); + if (!groupsForPerson.isEmpty()) { + List list = peopleToGroupsMap.getValue(personId.getValue()); + if (list == null) { + list = new ArrayList<>(); + peopleToGroupsMap.setValue(personId.getValue(), list); + } + list.addAll(groupsForPerson); + } + } + + for (final GroupTypeId groupTypeId : typesToIndexesMap.keySet()) { + Integer typeIndex = typesToIndexesMap.get(groupTypeId); + List groupIds = typesToGroupsMap.getValue(typeIndex); + if (groupIds != null) { + for (GroupId groupId : groupIds) { + List peopleForGroup = groupsPluginData.getPeopleForGroup(groupId); + if (!peopleForGroup.isEmpty()) { + List list = groupsToPeopleMap.getValue(groupId.getValue()); + if (list == null) { + list = new ArrayList<>(); + groupsToPeopleMap.setValue(groupId.getValue(), list); + } + list.addAll(peopleForGroup); + } + } + } + } + + } + + private static record GroupMembershipAdditionMutationEvent(PersonId personId, GroupId groupId) implements Event { + + } + + /** + * Adds a person to a group. Generates the corresponding + * {@linkplain GroupMembershipAdditionEvent} + * + * @throws ContractException + *
      + *
    • {@link PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@link PersonError#UNKNOWN_PERSON_ID} if the + * person id is unknown
    • + *
    • {@link GroupError#NULL_GROUP_ID} if the group + * id is null
    • + *
    • {@link GroupError#UNKNOWN_GROUP_ID} if the + * group id is unknown
    • + *
    • {@link GroupError#DUPLICATE_GROUP_MEMBERSHIP} + * if the person is already a member of the group
    • + *
    + */ + public void addPersonToGroup(final PersonId personId, final GroupId groupId) { + dataManagerContext.releaseMutationEvent(new GroupMembershipAdditionMutationEvent(personId, groupId)); + } + + private void handleGroupMembershipAdditionMutationEvent(DataManagerContext dataManagerContext, + GroupMembershipAdditionMutationEvent groupMembershipAdditionMutationEvent) { + PersonId personId = groupMembershipAdditionMutationEvent.personId(); + GroupId groupId = groupMembershipAdditionMutationEvent.groupId(); + validatePersonExists(personId); + validateGroupExists(groupId); + validatePersonNotInGroup(personId, groupId); + + List people = groupsToPeopleMap.getValue(groupId.getValue()); + if (people == null) { + people = new ArrayList<>(); + groupsToPeopleMap.setValue(groupId.getValue(), people); + } + people.add(personId); + + List groups = peopleToGroupsMap.getValue(personId.getValue()); + if (groups == null) { + groups = new ArrayList<>(1); + peopleToGroupsMap.setValue(personId.getValue(), groups); + } + groups.add(groupId); + + if (dataManagerContext.subscribersExist(GroupMembershipAdditionEvent.class)) { + dataManagerContext.releaseObservationEvent(new GroupMembershipAdditionEvent(personId, groupId)); + } + + } + + /* + * Preconditions : the person and group exist + */ + private void validatePersonNotInGroup(final PersonId personId, final GroupId groupId) { + final List groups = peopleToGroupsMap.getValue(personId.getValue()); + if (groups != null && groups.contains(groupId)) { + throw new ContractException(GroupError.DUPLICATE_GROUP_MEMBERSHIP, + "Person " + personId + " is already a member of group " + groupId); + } + } + + private void loadGroupPropertyValues() { + for (final GroupId groupId : groupsPluginData.getGroupIds()) { + final GroupTypeId groupTypeId = groupsPluginData.getGroupTypeId(groupId); + Map defMap = groupPropertyDefinitions.get(groupTypeId); + for (GroupPropertyValue groupPropertyValue : groupsPluginData.getGroupPropertyValues(groupId)) { + GroupPropertyId groupPropertyId = groupPropertyValue.groupPropertyId(); + final Object value = groupPropertyValue.value(); + final PropertyDefinition propertyDefinition = defMap.get(groupPropertyId); + Object defaultValue = propertyDefinition.getDefaultValue(); + if (!value.equals(defaultValue)) { + final Map map = groupPropertyManagerMap.get(groupTypeId); + final IndexedPropertyManager indexedPropertyManager = map.get(groupPropertyId); + indexedPropertyManager.setPropertyValue(groupId.getValue(), value); + } + } + } + } + + private record GroupPropertyUpdateMutationEvent(GroupId groupId, GroupPropertyId groupPropertyId, + Object groupPropertyValue) implements Event { + } + + /** + * Sets a property value for a group. Generates the corresponding + * {@linkplain GroupPropertyUpdateEvent} event. + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_ID } if the + * group id is null
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_ID } if + * the group id is unknown
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID } if + * the group property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID } + * if the group property id is unknown
    • + *
    • {@linkplain PropertyError#IMMUTABLE_VALUE } if + * the corresponding property definition defines the + * property as immutable
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_VALUE } + * if the property value is null
    • + *
    • {@linkplain PropertyError#INCOMPATIBLE_VALUE } + * if property value is incompatible with the + * corresponding property definition
    • + *
    + */ + public void setGroupPropertyValue(final GroupId groupId, final GroupPropertyId groupPropertyId, + final Object groupPropertyValue) { + dataManagerContext.releaseMutationEvent( + new GroupPropertyUpdateMutationEvent(groupId, groupPropertyId, groupPropertyValue)); + } + + private void handleGroupPropertyUpdateMutationEvent(DataManagerContext dataManagerContext, + GroupPropertyUpdateMutationEvent groupPropertyUpdateMutationEvent) { + GroupId groupId = groupPropertyUpdateMutationEvent.groupId(); + GroupPropertyId groupPropertyId = groupPropertyUpdateMutationEvent.groupPropertyId(); + Object groupPropertyValue = groupPropertyUpdateMutationEvent.groupPropertyValue(); + validateGroupExists(groupId); + final GroupTypeId groupTypeId = indexesToTypesMap.get(groupsToTypesMap.getValueAsInt(groupId.getValue())); + validateGroupPropertyId(groupTypeId, groupPropertyId); + final PropertyDefinition propertyDefinition = groupPropertyDefinitions.get(groupTypeId).get(groupPropertyId); + validatePropertyMutability(propertyDefinition); + validateGroupPropertyValueNotNull(groupPropertyValue); + validateValueCompatibility(groupPropertyId, propertyDefinition, groupPropertyValue); + final Map map = groupPropertyManagerMap.get(groupTypeId); + final IndexedPropertyManager indexedPropertyManager = map.get(groupPropertyId); + + if (dataManagerContext.subscribersExist(GroupPropertyUpdateEvent.class)) { + Object oldValue = indexedPropertyManager.getPropertyValue(groupId.getValue()); + indexedPropertyManager.setPropertyValue(groupId.getValue(), groupPropertyValue); + dataManagerContext.releaseObservationEvent( + new GroupPropertyUpdateEvent(groupId, groupPropertyId, oldValue, groupPropertyValue)); + } else { + indexedPropertyManager.setPropertyValue(groupId.getValue(), groupPropertyValue); + } + + } + + private void validatePropertyMutability(final PropertyDefinition propertyDefinition) { + if (!propertyDefinition.propertyValuesAreMutable()) { + throw new ContractException(PropertyError.IMMUTABLE_VALUE); + } + } + + private void loadGroups() { + for (final GroupId groupId : groupsPluginData.getGroupIds()) { + final GroupTypeId groupTypeId = groupsPluginData.getGroupTypeId(groupId); + final Integer typeIndex = typesToIndexesMap.get(groupTypeId); + List groups = typesToGroupsMap.getValue(typeIndex); + if (groups == null) { + groups = new ArrayList<>(); + typesToGroupsMap.setValue(typeIndex, groups); + } + groups.add(groupId); + groupsToTypesMap.setIntValue(groupId.getValue(), typeIndex); + } + masterGroupId = groupsPluginData.getNextGroupIdValue(); + } + + private static record GroupAdditionMutationEvent(GroupId groupId, GroupConstructionInfo groupConstructionInfo) + implements Event { + } + + /** + * Adds groups with any group property initialization that is contained in the + * events's auxiliary data. Generates the corresponding + * {@linkplain GroupAdditionEvent} event. Returns the id of the first group + * added. + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_CONSTRUCTION_INFO} + * if the group construction info is null
    • + *
    • {@link GroupError#NULL_GROUP_TYPE_ID} if the + * group type id contained in the group construction + * info is null
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} + * if the group type id contained in the group + * construction info is unknown
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if a group property id contained in the group + * construction info is unknown
    • + *
    • {@linkplain PropertyError#INCOMPATIBLE_VALUE} + * if a group property value contained in the group + * construction info is incompatible with the + * corresponding property definition.
    • + *
    + */ + public GroupId addGroup(GroupConstructionInfo groupConstructionInfo) { + final GroupId groupId = new GroupId(masterGroupId++); + dataManagerContext.releaseMutationEvent(new GroupAdditionMutationEvent(groupId, groupConstructionInfo)); + return groupId; + } + + private void handleGroupAdditionMutationEvent(DataManagerContext dataManagerContext, + GroupAdditionMutationEvent groupAdditionMutationEvent) { + GroupConstructionInfo groupConstructionInfo = groupAdditionMutationEvent.groupConstructionInfo(); + GroupId groupId = groupAdditionMutationEvent.groupId(); + + validateGroupConstructionInfoNotNull(groupConstructionInfo); + final GroupTypeId groupTypeId = groupConstructionInfo.getGroupTypeId(); + validateGroupTypeId(groupConstructionInfo.getGroupTypeId()); + + // validate the property value assignments included in the event + final Map propertyValues = groupConstructionInfo.getPropertyValues(); + for (final GroupPropertyId groupPropertyId : propertyValues.keySet()) { + validateGroupPropertyId(groupTypeId, groupPropertyId); + final PropertyDefinition propertyDefinition = groupPropertyDefinitions.get(groupTypeId) + .get(groupPropertyId); + final Object groupPropertyValue = propertyValues.get(groupPropertyId); + validateGroupPropertyValueNotNull(groupPropertyValue); + validateValueCompatibility(groupPropertyId, propertyDefinition, groupPropertyValue); + } + + /* + * If there are properties without default values, then the event must include + * value assignments for those properties. + */ + boolean checkPropertyCoverage = !nonDefaultBearingPropertyIds.get(groupTypeId).isEmpty(); + if (checkPropertyCoverage) { + clearNonDefaultChecks(groupTypeId); + for (final GroupPropertyId groupPropertyId : propertyValues.keySet()) { + markAssigned(groupTypeId, groupPropertyId); + } + verifyNonDefaultChecks(groupTypeId); + } + + final Integer typeIndex = typesToIndexesMap.get(groupTypeId); + List groups = typesToGroupsMap.getValue(typeIndex); + if (groups == null) { + groups = new ArrayList<>(); + typesToGroupsMap.setValue(typeIndex, groups); + } + groups.add(groupId); + groupsToTypesMap.setIntValue(groupId.getValue(), typeIndex); + + for (final GroupPropertyId groupPropertyId : propertyValues.keySet()) { + final Object groupPropertyValue = propertyValues.get(groupPropertyId); + final Map map = groupPropertyManagerMap.get(groupTypeId); + final IndexedPropertyManager indexedPropertyManager = map.get(groupPropertyId); + indexedPropertyManager.setPropertyValue(groupId.getValue(), groupPropertyValue); + } + + if (dataManagerContext.subscribersExist(GroupAdditionEvent.class)) { + dataManagerContext.releaseObservationEvent(new GroupAdditionEvent(groupId)); + } + + } + + private void validateValueCompatibility(final Object propertyId, final PropertyDefinition propertyDefinition, + final Object propertyValue) { + if (!propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())) { + throw new ContractException(PropertyError.INCOMPATIBLE_VALUE, + "Property value " + propertyValue + " is not of type " + propertyDefinition.getType().getName() + + " and does not match definition of " + propertyId); + } + } + + private void validateGroupPropertyValueNotNull(final Object propertyValue) { + if (propertyValue == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_VALUE); + } + } + + /** + * Validates the group type id + * + * @throws ContractException {@link GroupError#NULL_GROUP_CONSTRUCTION_INFO} if + * the group construction info is null + */ + private void validateGroupConstructionInfoNotNull(final GroupConstructionInfo groupConstructionInfo) { + if (groupConstructionInfo == null) { + throw new ContractException(GroupError.NULL_GROUP_CONSTRUCTION_INFO); + } + + } + + /** + * Adds a group. Generates the corresponding {@linkplain GroupAdditionEvent} + * event. Returns the id of the new group. + * + * @throws ContractException + *
      + *
    • {@link GroupError#NULL_GROUP_TYPE_ID} if the + * group type id is null
    • + *
    • {@link GroupError#UNKNOWN_GROUP_TYPE_ID} if the + * group type id is unknown
    • + *
    + */ + public GroupId addGroup(final GroupTypeId groupTypeId) { + final GroupId groupId = new GroupId(masterGroupId++); + dataManagerContext.releaseMutationEvent(new GroupAdditionMutationEvent(groupId, + GroupConstructionInfo.builder().setGroupTypeId(groupTypeId).build())); + return groupId; + } + + /* + * Allocates the weights array to the given size or 50% larger than the current + * size, whichever is largest. Size must be non-negative + */ + private void allocateWeights(final int size) { + if (weights == null) { + weights = new double[size]; + weightedPersonIds = new PersonId[size]; + } + if (weights.length < size) { + final int newSize = Math.max(size, weights.length + (weights.length / 2)); + weights = new double[newSize]; + weightedPersonIds = new PersonId[newSize]; + } + } + + /** + * Attempts to acquire a lock on the sampling data structures. + * + * @throws ContractException {@linkplain NucleusError#ACCESS_VIOLATION} If + * another sampling is ongoing + */ + private void aquireSamplingLock() { + if (samplingIsLocked) { + throw new ContractException(NucleusError.ACCESS_VIOLATION, + "cannot access weighted sampling during the execution of a previous weighted sampling"); + } + samplingIsLocked = true; + } + + /* + * Returns the index in the weights array that is the first to meet or exceed + * the target value. Assumes a strictly increasing set of values for indices 0 + * through peopleCount. Decreasing values are strictly prohibited. Consecutive + * equal values may return an ambiguous result. The target value must not exceed + * weights[peopleCount]. + * + */ + private int findTargetIndex(final double targetValue, final int peopleCount) { + int low = 0; + int high = peopleCount - 1; + + while (low <= high) { + final int mid = (low + high) >>> 1; + final double midVal = weights[mid]; + if (midVal < targetValue) { + low = mid + 1; + } else if (midVal > targetValue) { + high = mid - 1; + } else { + return mid; + } + } + return low; + } + + /** + * Returns the number of groups there are for a particular group type. + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if + * the group type id is null
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} + * if the group type id is unknown
    • + *
    + */ + public int getGroupCountForGroupType(final GroupTypeId groupTypeId) { + + validateGroupTypeId(groupTypeId); + + final Integer typeIndex = typesToIndexesMap.get(groupTypeId); + final List groups = typesToGroupsMap.getValue(typeIndex); + if (groups != null) { + return groups.size(); + } + return 0; + } + + private void validateGroupTypeId(final GroupTypeId groupTypeId) { + + if (groupTypeId == null) { + throw new ContractException(GroupError.NULL_GROUP_TYPE_ID); + } + + if (!typesToIndexesMap.keySet().contains(groupTypeId)) { + throw new ContractException(GroupError.UNKNOWN_GROUP_TYPE_ID, groupTypeId); + } + + } + + /** + * Returns the number of groups associated with the given person where each + * group has the given group type. + * + * @throws ContractException + *
      + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person id is unknown
    • + *
    • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if + * the group type id is null
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} + * if the group type id is unknown
    • + *
    + */ + public int getGroupCountForGroupTypeAndPerson(final GroupTypeId groupTypeId, final PersonId personId) { + validatePersonExists(personId); + validateGroupTypeId(groupTypeId); + + int result = 0; + final List groups = peopleToGroupsMap.getValue(personId.getValue()); + if (groups != null) { + for (final GroupId groupId : groups) { + final GroupTypeId groupType = getGroupType(groupId); + if (groupType.equals(groupTypeId)) { + result++; + } + } + } + return result; + } + + private void validatePersonExists(final PersonId personId) { + if (personId == null) { + throw new ContractException(PersonError.NULL_PERSON_ID); + } + if (!peopleDataManager.personExists(personId)) { + throw new ContractException(PersonError.UNKNOWN_PERSON_ID); + } + } + + /** + * Returns the number of groups associated with the given person. The person id + * must be non-null and non-negative. + * + * @throws ContractException + *
      + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person id is unknown
    • + *
    + */ + public int getGroupCountForPerson(final PersonId personId) { + validatePersonExists(personId); + final List groups = peopleToGroupsMap.getValue(personId.getValue()); + if (groups != null) { + return groups.size(); + } + return 0; + } + + /** + * Returns the set of group ids as a list. + */ + public List getGroupIds() { + final List result = new ArrayList<>(); + for (final GroupTypeId groupTypeId : typesToIndexesMap.keySet()) { + result.addAll(getGroupsForGroupType(groupTypeId)); + } + return result; + } + + /** + * Returns the property definition for the given group type id and group + * property id + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if + * the group type id is null
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} + * if the group type id is unknown
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the group property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the group property id is unknown
    • + *
    + */ + public PropertyDefinition getGroupPropertyDefinition(GroupTypeId groupTypeId, GroupPropertyId groupPropertyId) { + validateGroupTypeId(groupTypeId); + validateGroupPropertyId(groupTypeId, groupPropertyId); + final Map map = groupPropertyDefinitions.get(groupTypeId); + return map.get(groupPropertyId); + } + + private void validateGroupPropertyId(final GroupTypeId groupTypeId, final GroupPropertyId groupPropertyId) { + if (groupPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + + final Map map = groupPropertyManagerMap.get(groupTypeId); + if (map == null || !map.containsKey(groupPropertyId)) { + throw new ContractException(PropertyError.UNKNOWN_PROPERTY_ID); + } + + } + + /** + * Returns true if and only if there is a property definition associated with + * the given group type id and group property id. Accepts all values. + */ + public boolean getGroupPropertyExists(final GroupTypeId groupTypeId, final GroupPropertyId groupPropertyId) { + final Map map = groupPropertyManagerMap.get(groupTypeId); + if (map == null) { + return false; + } + return map.containsKey(groupPropertyId); + } + + /** + * Returns the set of group property ids for the given group type id + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if + * the group type id is null
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} + * if the group type id is unknown
    • + *
    + */ + @SuppressWarnings("unchecked") + public Set getGroupPropertyIds(GroupTypeId groupTypeId) { + validateGroupTypeId(groupTypeId); + final Map map = groupPropertyDefinitions.get(groupTypeId); + final Set result = new LinkedHashSet<>(map.keySet().size()); + for (final GroupPropertyId groupPropertyId : map.keySet()) { + result.add((T) groupPropertyId); + } + return result; + } + + private void validateGroupExists(final GroupId groupId) { + if (groupId == null) { + throw new ContractException(GroupError.NULL_GROUP_ID); + } + + if (groupsToTypesMap.getValueAsLong(groupId.getValue()) < 0) { + throw new ContractException(GroupError.UNKNOWN_GROUP_ID); + } + } + + /** + * Returns the value of the group property. + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_ID} if the + * group id is null
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_ID} if the + * group id is unknown
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the group property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the group property id is unknown
    • + *
    + */ + public T getGroupPropertyValue(final GroupId groupId, GroupPropertyId groupPropertyId) { + validateGroupExists(groupId); + final GroupTypeId groupTypeId = getGroupType(groupId); + validateGroupPropertyId(groupTypeId, groupPropertyId); + final Map map = groupPropertyManagerMap.get(groupTypeId); + final IndexedPropertyManager indexedPropertyManager = map.get(groupPropertyId); + return indexedPropertyManager.getPropertyValue(groupId.getValue()); + } + + /** + * Returns the set groupIds associated with the given group type id as a list + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if + * the group type id is null
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} + * if the group type id is unknown
    • + *
    + */ + public List getGroupsForGroupType(final GroupTypeId groupTypeId) { + validateGroupTypeId(groupTypeId); + final Integer typeIndex = typesToIndexesMap.get(groupTypeId); + final List groups = typesToGroupsMap.getValue(typeIndex); + if (groups != null) { + return new ArrayList<>(groups); + } + return new ArrayList<>(); + } + + /** + * Returns the set of group ids associated with the given person and group type + * as a list. + * + * @throws ContractException + *
      + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person id is unknown
    • + *
    • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if + * the group type id is null
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} + * if the group type id is unknown
    • + *
    + */ + public List getGroupsForGroupTypeAndPerson(final GroupTypeId groupTypeId, final PersonId personId) { + validatePersonExists(personId); + validateGroupTypeId(groupTypeId); + final List result = new ArrayList<>(); + final List groups = peopleToGroupsMap.getValue(personId.getValue()); + if (groups != null) { + for (final GroupId groupId : groups) { + if (getGroupType(groupId).equals(groupTypeId)) { + result.add(groupId); + } + } + } + return result; + } + + /** + * Returns the set group ids associated the the given person as a list. + * + * @throws ContractException + *
      + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person id is unknown
    • + *
    + */ + public List getGroupsForPerson(final PersonId personId) { + validatePersonExists(personId); + final List groups = peopleToGroupsMap.getValue(personId.getValue()); + if (groups != null) { + return new ArrayList<>(groups); + } + return new ArrayList<>(); + } + + /** + * Returns the group type for the given group. + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_ID} if the + * group id is null
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_ID} if the + * group id is unknown
    • + *
    + */ + @SuppressWarnings("unchecked") + public T getGroupType(final GroupId groupId) { + validateGroupExists(groupId); + return (T) indexesToTypesMap.get(groupsToTypesMap.getValueAsInt(groupId.getValue())); + } + + /** + * Return the number of group types associated with the person via their group + * memberships. + * + * @throws ContractException + *
      + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person id is unknown
    • + *
    + */ + public int getGroupTypeCountForPersonId(final PersonId personId) { + validatePersonExists(personId); + final Set types = new LinkedHashSet<>(); + final List groups = peopleToGroupsMap.getValue(personId.getValue()); + if (groups != null) { + for (final GroupId groupId : groups) { + types.add(getGroupType(groupId)); + } + } + return types.size(); + } + + /** + * Returns the group type ids + */ + @SuppressWarnings("unchecked") + public Set getGroupTypeIds() { + final Set result = new LinkedHashSet<>(typesToIndexesMap.keySet().size()); + for (final GroupTypeId groupTypeId : typesToIndexesMap.keySet()) { + result.add((T) groupTypeId); + } + return result; + } + + /** + * Returns the set group types associated with the person's groups as a list. + * + * @throws ContractException + *
      + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person id is unknown
    • + *
    + */ + public List getGroupTypesForPerson(final PersonId personId) { + validatePersonExists(personId); + final Set types = new LinkedHashSet<>(); + final List groups = peopleToGroupsMap.getValue(personId.getValue()); + if (groups != null) { + for (final GroupId groupId : groups) { + types.add(getGroupType(groupId)); + } + } + return new ArrayList<>(types); + } + + private IndexedPropertyManager getIndexedPropertyManager(final PropertyDefinition propertyDefinition, + final int intialSize) { + Supplier> indexIteratorSupplier = this::getGroupIndexIterator; + + IndexedPropertyManager indexedPropertyManager; + if (propertyDefinition.getType() == Boolean.class) { + indexedPropertyManager = new BooleanPropertyManager(propertyDefinition, indexIteratorSupplier); + } else if (propertyDefinition.getType() == Float.class) { + indexedPropertyManager = new FloatPropertyManager(propertyDefinition, indexIteratorSupplier); + } else if (propertyDefinition.getType() == Double.class) { + indexedPropertyManager = new DoublePropertyManager(propertyDefinition, indexIteratorSupplier); + } else if (propertyDefinition.getType() == Byte.class) { + indexedPropertyManager = new IntPropertyManager(propertyDefinition, indexIteratorSupplier); + } else if (propertyDefinition.getType() == Short.class) { + indexedPropertyManager = new IntPropertyManager(propertyDefinition, indexIteratorSupplier); + } else if (propertyDefinition.getType() == Integer.class) { + indexedPropertyManager = new IntPropertyManager(propertyDefinition, indexIteratorSupplier); + } else if (propertyDefinition.getType() == Long.class) { + indexedPropertyManager = new IntPropertyManager(propertyDefinition, indexIteratorSupplier); + } else if (Enum.class.isAssignableFrom(propertyDefinition.getType())) { + indexedPropertyManager = new EnumPropertyManager(propertyDefinition, indexIteratorSupplier); + } else { + indexedPropertyManager = new ObjectPropertyManager(propertyDefinition, indexIteratorSupplier); + } + return indexedPropertyManager; + } + + /** + * Returns the set of people who are in the given group as a list. + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_ID} if the + * group id is null
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_ID} if the + * group id is unknown
    • + *
    + */ + public List getPeopleForGroup(final GroupId groupId) { + validateGroupExists(groupId); + final List people = groupsToPeopleMap.getValue(groupId.getValue()); + if (people != null) { + return new ArrayList<>(people); + } + return new ArrayList<>(); + } + + /** + * Returns a list of unique person ids for the given group type(i.e. all people + * in groups having that type). Group type id must be valid. + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if + * the group type id is null
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} + * if the group type id is unknown
    • + *
    + */ + public List getPeopleForGroupType(final GroupTypeId groupTypeId) { + validateGroupTypeId(groupTypeId); + final Set allPeople = new LinkedHashSet<>(); + final Integer typeIndex = typesToIndexesMap.get(groupTypeId); + final List groups = typesToGroupsMap.getValue(typeIndex); + if (groups != null) { + for (final GroupId groupId : groups) { + final List people = groupsToPeopleMap.getValue(groupId.getValue()); + if (people != null) { + allPeople.addAll(people); + } + } + } + return new ArrayList<>(allPeople); + } + + /** + * Returns the number of people in the given group. + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_ID} if the + * group id is null
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_ID} if the + * group id is unknown
    • + *
    + */ + public int getPersonCountForGroup(final GroupId groupId) { + validateGroupExists(groupId); + final List people = groupsToPeopleMap.getValue(groupId.getValue()); + if (people != null) { + return people.size(); + } + return 0; + } + + /** + * Returns the number of people who are associated with groups having the given + * group type. + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if + * the group type id is null
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} + * if the group type id is unknown
    • + *
    + */ + public int getPersonCountForGroupType(final GroupTypeId groupTypeId) { + validateGroupTypeId(groupTypeId); + final Set allPeople = new LinkedHashSet<>(); + final Integer typeIndex = typesToIndexesMap.get(groupTypeId); + final List groups = typesToGroupsMap.getValue(typeIndex); + if (groups != null) { + for (final GroupId groupId : groups) { + final List people = groupsToPeopleMap.getValue(groupId.getValue()); + if (people != null) { + allPeople.addAll(people); + } + } + } + return allPeople.size(); + } + + /** + * Returns true if and only if the group exists. Null tolerant. + */ + public boolean groupExists(final GroupId groupId) { + if ((groupId == null)) { + return false; + } + return groupsToTypesMap.getValueAsLong(groupId.getValue()) >= 0; + } + + /** + * Returns true if and only if the group type exists. Null tolerant. + */ + public boolean groupTypeIdExists(final GroupTypeId groupTypeId) { + return typesToIndexesMap.keySet().contains(groupTypeId); + } + + /** + * Returns true if and only if the person is in the group. + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_ID} if the + * group id is null
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_ID} if the + * group id is unknown
    • + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person id is unknown
    • + *
    + */ + public boolean isPersonInGroup(final PersonId personId, final GroupId groupId) { + validatePersonExists(personId); + validateGroupExists(groupId); + final List groups = peopleToGroupsMap.getValue(personId.getValue()); + if (groups != null) { + return groups.contains(groupId); + } + return false; + } + + private void releaseSamplingLock() { + if (!samplingIsLocked) { + throw new RuntimeException("cannot release sample locking when lock not present"); + } + samplingIsLocked = false; + } + + private static record GroupRemovalMutationEvent(GroupId groupId) implements Event { + } + + /** + * Removes the group. Generates the corresponding + * {@linkplain GroupImminentRemovalEvent} event. + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_ID} if the + * group id is null
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_ID} if the + * group id is unknown
    • + *
    + */ + public void removeGroup(final GroupId groupId) { + dataManagerContext.releaseMutationEvent(new GroupRemovalMutationEvent(groupId)); + } + + private void handleGroupRemovalMutationEvent(DataManagerContext dataManagerContext, + GroupRemovalMutationEvent groupRemovalMutationEvent) { + GroupId groupId = groupRemovalMutationEvent.groupId(); + validateGroupExists(groupId); + + dataManagerContext.addPlan((context) -> { + + final GroupTypeId groupTypeId = getGroupType(groupId); + + final Map map = groupPropertyManagerMap.get(groupTypeId); + for (final GroupPropertyId groupPropertyId : map.keySet()) { + final IndexedPropertyManager indexedPropertyManager = map.get(groupPropertyId); + indexedPropertyManager.removeId(groupId.getValue()); + } + + groupsToTypesMap.setIntValue(groupId.getValue(), -1); + final Integer typeIndex = typesToIndexesMap.get(groupTypeId); + List groups = typesToGroupsMap.getValue(typeIndex); + groups.remove(groupId); + if (groups.size() == 0) { + typesToGroupsMap.setValue(typeIndex, null); + } + final List people = groupsToPeopleMap.getValue(groupId.getValue()); + groupsToPeopleMap.setValue(groupId.getValue(), null); + if (people != null) { + for (final PersonId personId : people) { + groups = peopleToGroupsMap.getValue(personId.getValue()); + groups.remove(groupId); + if (groups.isEmpty()) { + peopleToGroupsMap.setValue(personId.getValue(), null); + } + } + } + + }, dataManagerContext.getTime()); + + if (dataManagerContext.subscribersExist(GroupImminentRemovalEvent.class)) { + dataManagerContext.releaseObservationEvent(new GroupImminentRemovalEvent(groupId)); + } + } + + private static record GroupMembershipRemovalMutationEvent(PersonId personId, GroupId groupId) implements Event { + } + + /** + * Removes the person from the group. Generates the corresponding + * {@linkplain GroupMembershipRemovalEvent} event. + * + * @throws ContractException + *
      + *
    • {@link PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@link PersonError#UNKNOWN_PERSON_ID} if the + * person id is unknown
    • + *
    • {@link GroupError#NULL_GROUP_ID} if the group + * id is null
    • + *
    • {@link GroupError#UNKNOWN_GROUP_ID} if the + * group id is unknown
    • + *
    • {@link GroupError#NON_GROUP_MEMBERSHIP} if the + * person is not a member of the group
    • + *
    + */ + public void removePersonFromGroup(final PersonId personId, final GroupId groupId) { + dataManagerContext.releaseMutationEvent(new GroupMembershipRemovalMutationEvent(personId, groupId)); + } + + private void handleGroupMembershipRemovalMutationEvent(DataManagerContext dataManagerContext, + GroupMembershipRemovalMutationEvent groupMembershipRemovalMutationEvent) { + PersonId personId = groupMembershipRemovalMutationEvent.personId(); + GroupId groupId = groupMembershipRemovalMutationEvent.groupId(); + validatePersonExists(personId); + validateGroupExists(groupId); + validatePersonInGroup(personId, groupId); + + final List people = groupsToPeopleMap.getValue(groupId.getValue()); + if (people != null) { + people.remove(personId); + if (people.size() == 0) { + groupsToPeopleMap.setValue(groupId.getValue(), null); + } + } + final List groups = peopleToGroupsMap.getValue(personId.getValue()); + groups.remove(groupId); + if (groups.isEmpty()) { + peopleToGroupsMap.setValue(personId.getValue(), null); + } + + if (dataManagerContext.subscribersExist(GroupMembershipRemovalEvent.class)) { + dataManagerContext.releaseObservationEvent(new GroupMembershipRemovalEvent(personId, groupId)); + } + + } + + /* + * Preconditions : the person and group exist + */ + private void validatePersonInGroup(final PersonId personId, final GroupId groupId) { + final List groups = peopleToGroupsMap.getValue(personId.getValue()); + if (groups == null || !groups.contains(groupId)) { + throw new ContractException(GroupError.NON_GROUP_MEMBERSHIP, + "Person " + personId + " is not a member of group " + groupId); + } + } + + /* + * Precondition : group sampler info is not null + */ + private void validateGroupSampler(final GroupSampler groupSampler) { + if (groupSampler == null) { + throw new ContractException(GroupError.NULL_GROUP_SAMPLER); + } + if (groupSampler.getExcludedPerson().isPresent()) { + final PersonId excludedPersonId = groupSampler.getExcludedPerson().get(); + validatePersonExists(excludedPersonId); + } + } + + /** + * Returns a randomly selected person from the group using the group sampler to + * determine the probability for each person's selection. Returns an empty + * optional if no person meets the requirements of the group sampler. + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_ID} if the + * group id is null
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_ID} if the + * group id is unknown
    • + *
    • {@linkplain GroupError#NULL_GROUP_SAMPLER} if + * the group sampler is null
    • + *
    • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the excluded person contained in the sampler is not + * null and is unknown
    • + *
    • {@linkplain StochasticsError#UNKNOWN_RANDOM_NUMBER_GENERATOR_ID} + * if the sampler contains an unknown random number + * generator id
    • + *
    + */ + public Optional sampleGroup(final GroupId groupId, final GroupSampler groupSampler) { + validateGroupExists(groupId); + validateGroupSampler(groupSampler); + + RandomGenerator randomGenerator; + if (groupSampler.getRandomNumberGeneratorId().isPresent()) { + final RandomNumberGeneratorId randomNumberGeneratorId = groupSampler.getRandomNumberGeneratorId().get(); + randomGenerator = stochasticsDataManager.getRandomGeneratorFromId(randomNumberGeneratorId); + } else { + randomGenerator = stochasticsDataManager.getRandomGenerator(); + } + final GroupWeightingFunction groupWeightingFunction = groupSampler.getWeightingFunction().orElse(null); + final PersonId excludedPersonId = groupSampler.getExcludedPerson().orElse(null); + + final boolean exclude = (excludedPersonId != null) && isPersonInGroup(excludedPersonId, groupId); + PersonId selectedPersonId = null; + final List people = groupsToPeopleMap.getValue(groupId.getValue()); + if (people == null || people.isEmpty()) { + return Optional.empty(); + } + + if (groupWeightingFunction != null) { + + int candidateCount = people.size(); + if (exclude) { + candidateCount--; + } + if ((people != null) && (candidateCount > 0)) { + + aquireSamplingLock(); + try { + allocateWeights(people.size()); + /* + * Initialize the sum of the weights to zero and set the index in the weights + * and weightedPersonId to zero. + */ + double sum = 0; + int weightsLength = 0; + /* + * Collect a weight for each person in the group + */ + for (final PersonId personId : people) { + if (personId.equals(excludedPersonId)) { + continue; + } + /* + * Determine the weight of the person. Any weight that is negative , infinite or + * NAN is cause to return immediately since no person may be legitimately + * selected. + */ + final double weight = groupWeightingFunction.getWeight(groupsContext, personId, groupId); + if (!Double.isFinite(weight) || (weight < 0)) { + throw new ContractException(GroupError.MALFORMED_GROUP_SAMPLE_WEIGHTING_FUNCTION); + + } + /* + * People having a zero weight are rejected for selection + */ + if (weight > 0) { + sum += weight; + weights[weightsLength] = sum; + weightedPersonIds[weightsLength] = personId; + weightsLength++; + } + } + + /* + * If at least one person was accepted for selection, then we attempt a random + * selection. + */ + if (weightsLength > 0) { + /* + * Although the individual weights may have been finite, if the sum of those + * weights is not finite no legitimate selection can be made + */ + if (!Double.isFinite(sum)) { + throw new ContractException(GroupError.MALFORMED_GROUP_SAMPLE_WEIGHTING_FUNCTION); + } + + final double targetValue = randomGenerator.nextDouble() * sum; + final int targetIndex = findTargetIndex(targetValue, weightsLength); + selectedPersonId = weightedPersonIds[targetIndex]; + } + + } finally { + releaseSamplingLock(); + } + } + } else { + if (exclude) { + if ((people != null) && (people.size() > 1)) { + while (true) { + final int selectedIndex = randomGenerator.nextInt(people.size()); + final PersonId personId = people.get(selectedIndex); + if (!personId.equals(excludedPersonId)) { + selectedPersonId = personId; + break; + } + } + } + } else { + if ((people != null) && (people.size() > 0)) { + final int selectedIndex = randomGenerator.nextInt(people.size()); + selectedPersonId = people.get(selectedIndex); + } + } + } + return Optional.ofNullable(selectedPersonId); + + } + + /** + * Removes the person from all group tracking. + */ + private void handlePersonRemovalEvent(final DataManagerContext dataManagerContext, + PersonRemovalEvent personRemovalEvent) { + PersonId personId = personRemovalEvent.personId(); + final List groups = peopleToGroupsMap.getValue(personId.getValue()); + peopleToGroupsMap.setValue(personId.getValue(), null); + if (groups != null) { + for (final GroupId groupId : groups) { + final List people = groupsToPeopleMap.getValue(groupId.getValue()); + if (people != null) { + people.remove(personId); + if (people.size() == 0) { + groupsToPeopleMap.setValue(groupId.getValue(), null); + } + } + } + } + } + + private static enum GroupAdditionEventFunctionId { + GROUP_TYPE + } + + private IdentifiableFunctionMap groupAdditionFunctionMap = // + IdentifiableFunctionMap.builder(GroupAdditionEvent.class)// + .put(GroupAdditionEventFunctionId.GROUP_TYPE, e -> getGroupType(e.groupId()))// + .build();// + + /** + * Returns an event filter used to subscribe to {@link GroupAdditionEvent} + * events. Matches on the group type id. + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if + * the group type id is null
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} + * if the group type id is not known
    • + *
    + */ + public EventFilter getEventFilterForGroupAdditionEvent(GroupTypeId groupTypeId) { + validateGroupTypeId(groupTypeId); + return EventFilter.builder(GroupAdditionEvent.class)// + .addFunctionValuePair(groupAdditionFunctionMap.get(GroupAdditionEventFunctionId.GROUP_TYPE), + groupTypeId)// + .build(); + } + + /** + * Returns an event filter used to subscribe to {@link GroupAdditionEvent} + * events. Matches on all such events. + */ + public EventFilter getEventFilterForGroupAdditionEvent() { + + return EventFilter.builder(GroupAdditionEvent.class)// + .build(); + } + + private static enum GroupImminentRemovalEventId { + GROUP_TYPE, GROUP_ID + } + + private IdentifiableFunctionMap groupImminentRemovalMap = // + IdentifiableFunctionMap.builder(GroupImminentRemovalEvent.class)// + .put(GroupImminentRemovalEventId.GROUP_TYPE, e -> getGroupType(e.groupId()))// + .put(GroupImminentRemovalEventId.GROUP_ID, e -> e.groupId())// + .build();// + + /** + * Returns an event filter used to subscribe to + * {@link GroupImminentRemovalEvent} events. Matches on the group type id. + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if + * the group type id is null
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} + * if the group type id is not known
    • + *
    + */ + public EventFilter getEventFilterForGroupImminentRemovalEvent(GroupTypeId groupTypeId) { + validateGroupTypeId(groupTypeId); + return EventFilter.builder(GroupImminentRemovalEvent.class)// + .addFunctionValuePair(groupImminentRemovalMap.get(GroupImminentRemovalEventId.GROUP_TYPE), groupTypeId)// + .build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link GroupImminentRemovalEvent} events. Matches on the group id. + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_ID} if the + * group id is null
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_ID} if the + * group id is not known
    • + *
    + */ + public EventFilter getEventFilterForGroupImminentRemovalEvent(GroupId groupId) { + validateGroupExists(groupId); + return EventFilter.builder(GroupImminentRemovalEvent.class)// + .addFunctionValuePair(groupImminentRemovalMap.get(GroupImminentRemovalEventId.GROUP_ID), groupId)// + .build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link GroupImminentRemovalEvent} events. Matches all such events. + */ + public EventFilter getEventFilterForGroupImminentRemovalEvent() { + return EventFilter.builder(GroupImminentRemovalEvent.class)// + .build(); + } + + private static enum GroupMembershipAdditionEventId { + GROUP_TYPE, GROUP_ID, PERSON_ID; + } + + private IdentifiableFunctionMap groupMembershipAdditionMap = // + IdentifiableFunctionMap.builder(GroupMembershipAdditionEvent.class)// + .put(GroupMembershipAdditionEventId.GROUP_TYPE, e -> getGroupType(e.groupId()))// + .put(GroupMembershipAdditionEventId.GROUP_ID, e -> e.groupId())// + .put(GroupMembershipAdditionEventId.PERSON_ID, e -> e.personId())// + .build();// + + /** + * Returns an event filter used to subscribe to + * {@link GroupMembershipAdditionEvent} events. Matches on group id. + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_ID} if the + * group id is null
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_ID} if the + * group id is not known
    • + *
    + */ + public EventFilter getEventFilterForGroupMembershipAdditionEvent(GroupId groupId) { + + validateGroupExists(groupId); + return EventFilter.builder(GroupMembershipAdditionEvent.class)// + .addFunctionValuePair(groupMembershipAdditionMap.get(GroupMembershipAdditionEventId.GROUP_ID), groupId)// + .build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link GroupMembershipAdditionEvent} events. Matches on group id and person + * id. + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_ID} if the + * group id is null
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_ID} if the + * group id is not known
    • + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person id is not known
    • + *
    + */ + public EventFilter getEventFilterForGroupMembershipAdditionEvent(GroupId groupId, + PersonId personId) { + validateGroupExists(groupId); + validatePersonExists(personId); + return EventFilter.builder(GroupMembershipAdditionEvent.class)// + .addFunctionValuePair(groupMembershipAdditionMap.get(GroupMembershipAdditionEventId.GROUP_ID), groupId)// + .addFunctionValuePair(groupMembershipAdditionMap.get(GroupMembershipAdditionEventId.PERSON_ID), + personId)// + .build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link GroupMembershipAdditionEvent} events. Matches on group type id. + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if + * the group type id is null
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} + * if the group type id is not known
    • + *
    + */ + public EventFilter getEventFilterForGroupMembershipAdditionEvent( + GroupTypeId groupTypeId) { + validateGroupTypeId(groupTypeId); + return EventFilter.builder(GroupMembershipAdditionEvent.class)// + .addFunctionValuePair(groupMembershipAdditionMap.get(GroupMembershipAdditionEventId.GROUP_TYPE), + groupTypeId)// + .build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link GroupMembershipAdditionEvent} events. Matches on group type id and + * person id. + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if + * the group type id is null
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} + * if the group type id is not known
    • + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person id is not known
    • + *
    + */ + public EventFilter getEventFilterForGroupMembershipAdditionEvent( + GroupTypeId groupTypeId, PersonId personId) { + validateGroupTypeId(groupTypeId); + validatePersonExists(personId); + return EventFilter.builder(GroupMembershipAdditionEvent.class)// + .addFunctionValuePair(groupMembershipAdditionMap.get(GroupMembershipAdditionEventId.GROUP_TYPE), + groupTypeId)// + .addFunctionValuePair(groupMembershipAdditionMap.get(GroupMembershipAdditionEventId.PERSON_ID), + personId)// + .build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link GroupMembershipAdditionEvent} events. Matches on person id. + * + * @throws ContractException + *
      + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person id is not known
    • + *
    + */ + public EventFilter getEventFilterForGroupMembershipAdditionEvent(PersonId personId) { + validatePersonExists(personId); + return EventFilter.builder(GroupMembershipAdditionEvent.class)// + .addFunctionValuePair(groupMembershipAdditionMap.get(GroupMembershipAdditionEventId.PERSON_ID), + personId)// + .build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link GroupMembershipAdditionEvent} events. Matches on all such events. + */ + public EventFilter getEventFilterForGroupMembershipAdditionEvent() { + return EventFilter.builder(GroupMembershipAdditionEvent.class)// + .build(); + } + + private static enum GroupMembershipRemovalEventId { + GROUP_TYPE, GROUP_ID, PERSON_ID; + } + + private IdentifiableFunctionMap groupMembershipRemovalMap = // + IdentifiableFunctionMap.builder(GroupMembershipRemovalEvent.class)// + .put(GroupMembershipRemovalEventId.GROUP_TYPE, e -> getGroupType(e.groupId()))// + .put(GroupMembershipRemovalEventId.GROUP_ID, e -> e.groupId())// + .put(GroupMembershipRemovalEventId.PERSON_ID, e -> e.personId())// + .build();// + + /** + * Returns an event filter used to subscribe to + * {@link GroupMembershipRemovalEvent} events. Matches on group id. + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_ID} if the + * group id is null
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_ID} if the + * group id is not known
    • + *
    + */ + public EventFilter getEventFilterForGroupMembershipRemovalEvent(GroupId groupId) { + + validateGroupExists(groupId); + return EventFilter.builder(GroupMembershipRemovalEvent.class)// + .addFunctionValuePair(groupMembershipRemovalMap.get(GroupMembershipRemovalEventId.GROUP_ID), groupId)// + .build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link GroupMembershipRemovalEvent} events. Matches on group id and person + * id. + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_ID} if the + * group id is null
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_ID} if the + * group id is not known
    • + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person id is not known
    • + *
    + */ + public EventFilter getEventFilterForGroupMembershipRemovalEvent(GroupId groupId, + PersonId personId) { + validateGroupExists(groupId); + validatePersonExists(personId); + return EventFilter.builder(GroupMembershipRemovalEvent.class)// + .addFunctionValuePair(groupMembershipRemovalMap.get(GroupMembershipRemovalEventId.GROUP_ID), groupId)// + .addFunctionValuePair(groupMembershipRemovalMap.get(GroupMembershipRemovalEventId.PERSON_ID), personId)// + .build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link GroupMembershipRemovalEvent} events. Matches on group type id. + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if + * the group type id is null
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} + * if the group type id is not known
    • + *
    + */ + public EventFilter getEventFilterForGroupMembershipRemovalEvent( + GroupTypeId groupTypeId) { + validateGroupTypeId(groupTypeId); + return EventFilter.builder(GroupMembershipRemovalEvent.class)// + .addFunctionValuePair(groupMembershipRemovalMap.get(GroupMembershipRemovalEventId.GROUP_TYPE), + groupTypeId)// + .build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link GroupMembershipRemovalEvent} events. Matches on group type id and + * person id. + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if + * the group type id is null
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} + * if the group type id is not known
    • + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person id is not known
    • + *
    + */ + public EventFilter getEventFilterForGroupMembershipRemovalEvent( + GroupTypeId groupTypeId, PersonId personId) { + validateGroupTypeId(groupTypeId); + validatePersonExists(personId); + return EventFilter.builder(GroupMembershipRemovalEvent.class)// + .addFunctionValuePair(groupMembershipRemovalMap.get(GroupMembershipRemovalEventId.GROUP_TYPE), + groupTypeId)// + .addFunctionValuePair(groupMembershipRemovalMap.get(GroupMembershipRemovalEventId.PERSON_ID), personId)// + .build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link GroupMembershipRemovalEvent} events. Matches on person id. + * + * @throws ContractException + *
      + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person id is not known
    • + *
    + */ + public EventFilter getEventFilterForGroupMembershipRemovalEvent(PersonId personId) { + validatePersonExists(personId); + return EventFilter.builder(GroupMembershipRemovalEvent.class)// + .addFunctionValuePair(groupMembershipRemovalMap.get(GroupMembershipRemovalEventId.PERSON_ID), personId)// + .build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link GroupMembershipRemovalEvent} events. Matches on all such events. + */ + public EventFilter getEventFilterForGroupMembershipRemovalEvent() { + return EventFilter.builder(GroupMembershipRemovalEvent.class)// + .build(); + } + + private static enum GroupPropertyUpdateEventId { + GROUP_PROPERTY, GROUP_TYPE, GROUP_ID; + } + + private IdentifiableFunctionMap groupPropertyUpdateMap = // + IdentifiableFunctionMap.builder(GroupPropertyUpdateEvent.class)// + .put(GroupPropertyUpdateEventId.GROUP_PROPERTY, e -> e.groupPropertyId())// + .put(GroupPropertyUpdateEventId.GROUP_TYPE, e -> getGroupType(e.groupId()))// + .put(GroupPropertyUpdateEventId.GROUP_ID, e -> e.groupId())// + .build();// + + /** + * Returns an event filter used to subscribe to {@link GroupPropertyUpdateEvent} + * events. Matches on group id. + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_ID} if the + * group id is null
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_ID} if the + * group id is not known
    • + *
    + */ + public EventFilter getEventFilterForGroupPropertyUpdateEvent(GroupId groupId) { + validateGroupExists(groupId); + return EventFilter.builder(GroupPropertyUpdateEvent.class)// + .addFunctionValuePair(groupPropertyUpdateMap.get(GroupPropertyUpdateEventId.GROUP_ID), groupId)// + .build(); + } + + /** + * Returns an event filter used to subscribe to {@link GroupPropertyUpdateEvent} + * events. Matches on group property id and group id. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the group property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the group property id is not known
    • + *
    • {@linkplain GroupError#NULL_GROUP_ID} if the + * group id is null
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_ID} if the + * group id is not known
    • + *
    + */ + public EventFilter getEventFilterForGroupPropertyUpdateEvent( + GroupPropertyId groupPropertyId, GroupId groupId) { + validateGroupExists(groupId); + GroupTypeId groupTypeId = indexesToTypesMap.get(groupsToTypesMap.getValueAsInt(groupId.getValue())); + validateGroupPropertyId(groupTypeId, groupPropertyId); + + return EventFilter.builder(GroupPropertyUpdateEvent.class)// + .addFunctionValuePair(groupPropertyUpdateMap.get(GroupPropertyUpdateEventId.GROUP_PROPERTY), + groupPropertyId)// + .addFunctionValuePair(groupPropertyUpdateMap.get(GroupPropertyUpdateEventId.GROUP_ID), groupId)// + .build(); + } + + /** + * Returns an event filter used to subscribe to {@link GroupPropertyUpdateEvent} + * events. Matches on group type id. + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if + * the group type id is null
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} + * if the group type id is not known
    • + *
    + */ + public EventFilter getEventFilterForGroupPropertyUpdateEvent(GroupTypeId groupTypeId) { + validateGroupTypeId(groupTypeId); + return EventFilter.builder(GroupPropertyUpdateEvent.class)// + .addFunctionValuePair(groupPropertyUpdateMap.get(GroupPropertyUpdateEventId.GROUP_TYPE), groupTypeId)// + .build(); + } + + /** + * Returns an event filter used to subscribe to {@link GroupPropertyUpdateEvent} + * events. Matches on group property id and group type id. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the group property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the group property id is not known
    • + *
    • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if + * the group type id is null
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} + * if the group type id is not known
    • + *
    + */ + public EventFilter getEventFilterForGroupPropertyUpdateEvent( + GroupPropertyId groupPropertyId, GroupTypeId groupTypeId) { + validateGroupTypeId(groupTypeId); + validateGroupPropertyId(groupTypeId, groupPropertyId); + + return EventFilter.builder(GroupPropertyUpdateEvent.class)// + .addFunctionValuePair(groupPropertyUpdateMap.get(GroupPropertyUpdateEventId.GROUP_PROPERTY), + groupPropertyId)// + .addFunctionValuePair(groupPropertyUpdateMap.get(GroupPropertyUpdateEventId.GROUP_TYPE), groupTypeId)// + .build(); + } + + /** + * Returns an event filter used to subscribe to {@link GroupPropertyUpdateEvent} + * events. Matches on all such events. + */ + public EventFilter getEventFilterForGroupPropertyUpdateEvent() { + + return EventFilter.builder(GroupPropertyUpdateEvent.class)// + .build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link GroupPropertyDefinitionEvent} events. Matches all such events. + */ + public EventFilter getEventFilterForGroupPropertyDefinitionEvent() { + return EventFilter.builder(GroupPropertyDefinitionEvent.class)// + .build(); + } + + /** + * Returns an event filter used to subscribe to {@link GroupTypeAdditionEvent} + * events. Matches all such events. + */ + public EventFilter getEventFilterForGroupTypeAdditionEvent() { + return EventFilter.builder(GroupTypeAdditionEvent.class)// + .build(); + } + + private static class GroupsContextImpl implements GroupsContext { + + private final DataManagerContext dataManagerContext; + + public GroupsContextImpl(DataManagerContext dataManagerContext) { + this.dataManagerContext = dataManagerContext; + } + + @Override + public T getDataManager(Class dataManagerClass) { + return dataManagerContext.getDataManager(dataManagerClass); + } + + @Override + public double getTime() { + return dataManagerContext.getTime(); + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("GroupsDataManager [groupPropertyManagerMap="); + builder.append(groupPropertyManagerMap); + builder.append(", groupPropertyDefinitions="); + builder.append(groupPropertyDefinitions); + builder.append(", typesToGroupsMap="); + builder.append(typesToGroupsMap); + builder.append(", groupsToTypesMap="); + builder.append(groupsToTypesMap); + builder.append(", typesToIndexesMap="); + builder.append(typesToIndexesMap); + builder.append(", indexesToTypesMap="); + builder.append(indexesToTypesMap); + builder.append(", groupsToPeopleMap="); + builder.append(groupsToPeopleMap); + builder.append(", peopleToGroupsMap="); + builder.append(peopleToGroupsMap); + builder.append(", masterGroupId="); + builder.append(masterGroupId); + builder.append("]"); + return builder.toString(); + } + + private static class GroupIndexIterator implements Iterator { + + private Integer next; + private final Iterator iterator; + + public GroupIndexIterator(Iterator iterator) { + this.iterator = iterator; + increment(); + } + + private void increment() { + next = null; + while (iterator.hasNext()) { + GroupId groupId = iterator.next(); + if (groupId != null) { + next = groupId.getValue(); + break; + } + } + } + + @Override + public boolean hasNext() { + return next != null; + } + + @Override + public Integer next() { + if (next == null) { + throw new NoSuchElementException(); + } + Integer result = next; + increment(); + return result; + } + } + + public Iterator getGroupIndexIterator() { + List groupIds = getGroupIds(); + Comparator comparator = (g1, g2) -> Integer.compare(g1.getValue(), g2.getValue()); + Collections.sort(groupIds, comparator); + return new GroupIndexIterator(groupIds.iterator()); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/datamanagers/GroupsPluginData.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/datamanagers/GroupsPluginData.java new file mode 100644 index 000000000..b6a73039c --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/datamanagers/GroupsPluginData.java @@ -0,0 +1,1082 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.datamanagers; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.math3.util.FastMath; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.PluginDataBuilder; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupError; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupId; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupPropertyValue; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupTypeId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; +import util.wrappers.MultiKey; + +/** + * An immutable container of the initial state of the GroupDataManager. It + * contains:
    + *
      + *
    • group type ids
    • + *
    • group ids
    • + *
    • group property definitions: each group type has its own set of properties + * and all property definitions have default values
    • + *
    • group property values
    • + *
    • person group assignments
    • + *
    + */ +@Immutable +public final class GroupsPluginData implements PluginData { + + static class GroupSpecification { + GroupId groupId; + GroupTypeId groupTypeId; + List groupPropertyValues; + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("GroupSpecification [groupId="); + builder.append(groupId); + builder.append(", groupTypeId="); + builder.append(groupTypeId); + builder.append(", groupPropertyValues="); + builder.append(groupPropertyValues); + builder.append("]"); + return builder.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((groupId == null) ? 0 : groupId.hashCode()); + result = prime * result + ((groupPropertyValues == null) ? 0 : groupPropertyValues.hashCode()); + result = prime * result + ((groupTypeId == null) ? 0 : groupTypeId.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof GroupSpecification)) { + return false; + } + GroupSpecification other = (GroupSpecification) obj; + if (groupId == null) { + if (other.groupId != null) { + return false; + } + } else if (!groupId.equals(other.groupId)) { + return false; + } + if (groupPropertyValues == null) { + if (other.groupPropertyValues != null) { + return false; + } + } else if (!groupPropertyValues.equals(other.groupPropertyValues)) { + return false; + } + if (groupTypeId == null) { + if (other.groupTypeId != null) { + return false; + } + } else if (!groupTypeId.equals(other.groupTypeId)) { + return false; + } + return true; + } + + } + + private static class Data { + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Data [nextGroupIdValue="); + builder.append(nextGroupIdValue); + builder.append(", groupPropertyDefinitions="); + builder.append(groupPropertyDefinitions); + builder.append(", groupTypeIds="); + builder.append(groupTypeIds); + builder.append(", groupSpecifications="); + builder.append(groupSpecifications); + builder.append(", personToGroupsMemberships="); + builder.append(personToGroupsMemberships); + builder.append(", groupToPeopleMemberships="); + builder.append(groupToPeopleMemberships); + builder.append("]"); + return builder.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((groupPropertyDefinitions == null) ? 0 : groupPropertyDefinitions.hashCode()); + result = prime * result + ((groupSpecifications == null) ? 0 : groupSpecifications.hashCode()); + result = prime * result + ((groupToPeopleMemberships == null) ? 0 : groupToPeopleMemberships.hashCode()); + result = prime * result + ((groupTypeIds == null) ? 0 : groupTypeIds.hashCode()); + result = prime * result + nextGroupIdValue; + result = prime * result + ((personToGroupsMemberships == null) ? 0 : personToGroupsMemberships.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + if (!groupPropertyDefinitions.equals(other.groupPropertyDefinitions) + || !groupSpecifications.equals(other.groupSpecifications) + || !groupToPeopleMemberships.equals(other.groupToPeopleMemberships) + || !groupTypeIds.equals(other.groupTypeIds)) { + return false; + } + if (nextGroupIdValue != other.nextGroupIdValue) { + return false; + } + if (!personToGroupsMemberships.equals(other.personToGroupsMemberships)) { + return false; + } + return true; + } + + private int nextGroupIdValue = -1; + private Map> groupPropertyDefinitions; + private final Set groupTypeIds; + private final List emptyGroupList; + private final List emptyPersonList; + + private boolean locked; + + private List groupSpecifications; + private List emptyGroupPropertyValues; + + // indexed by person id + private boolean asymmetricMemberships; + private final List> personToGroupsMemberships; + private final List> groupToPeopleMemberships; + + public Data() { + groupPropertyDefinitions = new LinkedHashMap<>(); + groupTypeIds = new LinkedHashSet<>(); + personToGroupsMemberships = new ArrayList<>(); + groupToPeopleMemberships = new ArrayList<>(); + groupSpecifications = new ArrayList<>(); + emptyGroupList = Collections.unmodifiableList(new ArrayList<>()); + emptyPersonList = Collections.unmodifiableList(new ArrayList<>()); + emptyGroupPropertyValues = Collections.unmodifiableList(new ArrayList<>()); + } + + public Data(Data data) { + emptyPersonList = Collections.unmodifiableList(new ArrayList<>()); + emptyGroupList = Collections.unmodifiableList(new ArrayList<>()); + emptyGroupPropertyValues = Collections.unmodifiableList(new ArrayList<>()); + groupPropertyDefinitions = new LinkedHashMap<>(); + for (GroupTypeId groupTypeId : data.groupPropertyDefinitions.keySet()) { + Map map = data.groupPropertyDefinitions.get(groupTypeId); + Map newMap = new LinkedHashMap<>(); + newMap.putAll(map); + groupPropertyDefinitions.put(groupTypeId, newMap); + } + groupTypeIds = new LinkedHashSet<>(data.groupTypeIds); + + groupSpecifications = new ArrayList<>(data.groupSpecifications.size()); + for (GroupSpecification groupSpecification : data.groupSpecifications) { + GroupSpecification newGroupSpecification = new GroupSpecification(); + newGroupSpecification.groupId = groupSpecification.groupId; + newGroupSpecification.groupTypeId = groupSpecification.groupTypeId; + if (groupSpecification.groupPropertyValues != null) { + newGroupSpecification.groupPropertyValues = new ArrayList<>(groupSpecification.groupPropertyValues); + } + groupSpecifications.add(newGroupSpecification); + } + + int n = data.personToGroupsMemberships.size(); + personToGroupsMemberships = new ArrayList<>(n); + for (int i = 0; i < n; i++) { + List list = data.personToGroupsMemberships.get(i); + List newList = null; + if (list != null) { + newList = new ArrayList<>(list); + } + personToGroupsMemberships.add(newList); + } + n = data.groupToPeopleMemberships.size(); + groupToPeopleMemberships = new ArrayList<>(n); + for (int i = 0; i < n; i++) { + List list = data.groupToPeopleMemberships.get(i); + List newList = null; + if (list != null) { + newList = new ArrayList<>(list); + } + groupToPeopleMemberships.add(newList); + } + + nextGroupIdValue = data.nextGroupIdValue; + + asymmetricMemberships = data.asymmetricMemberships; + + locked = data.locked; + } + + } + + private final Data data; + + private GroupsPluginData(Data data) { + this.data = data; + } + + /** + * Returns a new builder instance + */ + public static Builder builder() { + return new Builder(new Data()); + } + + private static void validatePropertyDefinitionNotNull(PropertyDefinition propertyDefinition) { + if (propertyDefinition == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_DEFINITION); + } + } + + private static void validateGroupPropertyIdNotNull(GroupPropertyId groupPropertyId) { + if (groupPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + } + + private static void validateGroupTypeIdNotNull(GroupTypeId groupTypeId) { + if (groupTypeId == null) { + throw new ContractException(GroupError.NULL_GROUP_TYPE_ID); + } + } + + private static void validateGroupPropertyValueNotNull(Object groupPropertyValue) { + if (groupPropertyValue == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_VALUE); + } + } + + private static void validateGroupIdIsLegal(GroupId groupId) { + if (groupId == null) { + throw new ContractException(GroupError.NULL_GROUP_ID); + } + + } + + /** + * Builder class for GroupInitialData + */ + public static class Builder implements PluginDataBuilder { + + private Data data; + + private void ensureDataMutability() { + if (data.locked) { + data = new Data(data); + data.locked = false; + } + } + + private void ensureImmutability() { + if (!data.locked) { + data.locked = true; + } + } + + private Builder(Data data) { + this.data = data; + + } + + /** + * Return the GroupInitialData from the data collected by this builder. + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#DUPLICATE_GROUP_MEMBERSHIP} + * if a person was assigned to a group more than + * once
    • + *
    • {@linkplain GroupError#DUPLICATE_GROUP_MEMBERSHIP} + * if a group was assigned to a person more than + * once
    • + *
    • {@linkplain GroupError#GROUP_MEMBERSHIP_ASYMMETRY} + * if groups and people are not symmetrically + * assigned
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} + * if a group was added with a group type id that was + * not defined
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} + * if a group property definition was defined for a + * group type id that was not defined.
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_ID} if a + * group property value was set for a group id that + * was not defined.
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_ID} if a + * group membership was set for a group id that was + * not defined.
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if a group property value is added for a group + * property id that is not associated with the + * group.
    • + *
    • {@linkplain PropertyError#INCOMPATIBLE_VALUE} + * if a group property value is added that is + * incompatible with the corresponding property + * definition
    • + *
    • {@linkplain PropertyError#INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT} + * if a group does not have a group property value + * assigned when the corresponding property definition + * lacks a default value.
    • + *
    + */ + @Override + public GroupsPluginData build() { + + if (!data.locked) { + validateData(); + } + ensureImmutability(); + return new GroupsPluginData(data); + + } + + /** + * Adds a person to a group and the group to the person. Use this method when + * order within memberships is not important. + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_ID} if the + * group id is null
    • + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    + */ + public Builder associatePersonToGroup(final GroupId groupId, final PersonId personId) { + + ensureDataMutability(); + validateGroupIdIsLegal(groupId); + validatePersonId(personId); + + int personIndex = personId.getValue(); + + while (personIndex >= data.personToGroupsMemberships.size()) { + data.personToGroupsMemberships.add(null); + } + List groups = data.personToGroupsMemberships.get(personIndex); + if (groups == null) { + groups = new ArrayList<>(); + data.personToGroupsMemberships.set(personIndex, groups); + } + + groups.add(groupId); + + int groupIndex = groupId.getValue(); + + while (groupIndex >= data.groupToPeopleMemberships.size()) { + data.groupToPeopleMemberships.add(null); + } + List people = data.groupToPeopleMemberships.get(groupIndex); + if (people == null) { + people = new ArrayList<>(); + data.groupToPeopleMemberships.set(groupIndex, people); + } + + people.add(personId); + + return this; + } + + /** + * Adds the group to the person, but not the person to the group. + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_ID} if the + * group id is null
    • + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null + *
    + */ + public Builder addGroupToPerson(final GroupId groupId, final PersonId personId) { + + ensureDataMutability(); + validateGroupIdIsLegal(groupId); + validatePersonId(personId); + + int personIndex = personId.getValue(); + + while (personIndex >= data.personToGroupsMemberships.size()) { + data.personToGroupsMemberships.add(null); + } + List groups = data.personToGroupsMemberships.get(personIndex); + if (groups == null) { + groups = new ArrayList<>(); + data.personToGroupsMemberships.set(personIndex, groups); + } + + data.asymmetricMemberships = true; + groups.add(groupId); + + return this; + } + + /** + * Adds the group to the person, but not the person to the group. + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_ID} if the + * group id is null
    • + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null + *
    + */ + public Builder addPersonToGroup(final PersonId personId, final GroupId groupId) { + + ensureDataMutability(); + validateGroupIdIsLegal(groupId); + validatePersonId(personId); + + int groupIndex = groupId.getValue(); + + while (groupIndex >= data.groupToPeopleMemberships.size()) { + data.groupToPeopleMemberships.add(null); + } + List people = data.groupToPeopleMemberships.get(groupIndex); + if (people == null) { + people = new ArrayList<>(); + data.groupToPeopleMemberships.set(groupIndex, people); + } + data.asymmetricMemberships = true; + people.add(personId); + + return this; + } + + /** + * Adds a group type id Duplicate inputs override previous inputs + * + * @throws ContractException {@linkplain GroupError#NULL_GROUP_TYPE_ID} if the + * group type id is null + */ + public Builder addGroupTypeId(final GroupTypeId groupTypeId) { + ensureDataMutability(); + validateGroupTypeIdNotNull(groupTypeId); + data.groupTypeIds.add(groupTypeId); + return this; + } + + /** + * Adds a group with the given group type Duplicate inputs override previous + * inputs + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_ID} if the + * group id is null
    • + *
    • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if + * the group type id is null
    • + *
    + */ + public Builder addGroup(final GroupId groupId, final GroupTypeId groupTypeId) { + ensureDataMutability(); + validateGroupIdIsLegal(groupId); + validateGroupTypeIdNotNull(groupTypeId); + int groupIndex = groupId.getValue(); + while (groupIndex >= data.groupSpecifications.size()) { + data.groupSpecifications.add(null); + } + + GroupSpecification groupSpecification = data.groupSpecifications.get(groupIndex); + if (groupSpecification == null) { + groupSpecification = new GroupSpecification(); + groupSpecification.groupId = groupId; + data.groupSpecifications.set(groupIndex, groupSpecification); + } + groupSpecification.groupTypeId = groupTypeId; + return this; + } + + /** + * Defines a group property Duplicate inputs override previous inputs + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if + * the group type id is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the group property id is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_DEFINITION} + * if the property definition is null
    • + *
    + */ + public Builder defineGroupProperty(final GroupTypeId groupTypeId, final GroupPropertyId groupPropertyId, + final PropertyDefinition propertyDefinition) { + ensureDataMutability(); + validateGroupTypeIdNotNull(groupTypeId); + validateGroupPropertyIdNotNull(groupPropertyId); + validatePropertyDefinitionNotNull(propertyDefinition); + Map propertyDefinitionsMap = data.groupPropertyDefinitions + .get(groupTypeId); + if (propertyDefinitionsMap == null) { + propertyDefinitionsMap = new LinkedHashMap<>(); + data.groupPropertyDefinitions.put(groupTypeId, propertyDefinitionsMap); + } + propertyDefinitionsMap.put(groupPropertyId, propertyDefinition); + return this; + } + + /** + * Sets the group property value that overrides the default value of the + * corresponding property definition Duplicate inputs override previous inputs + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_ID}if the + * group id is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID}if + * the group property id is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_VALUE}if + * the group property value is null
    • + *
    + */ + public Builder setGroupPropertyValue(final GroupId groupId, final GroupPropertyId groupPropertyId, + final Object value) { + ensureDataMutability(); + validateGroupIdIsLegal(groupId); + validateGroupPropertyIdNotNull(groupPropertyId); + validateGroupPropertyValueNotNull(value); + + int groupIndex = groupId.getValue(); + while (groupIndex >= data.groupSpecifications.size()) { + data.groupSpecifications.add(null); + } + + GroupSpecification groupSpecification = data.groupSpecifications.get(groupIndex); + if (groupSpecification == null) { + groupSpecification = new GroupSpecification(); + groupSpecification.groupId = groupId; + data.groupSpecifications.set(groupIndex, groupSpecification); + } + + List groupPropertyValues = groupSpecification.groupPropertyValues; + if (groupPropertyValues == null) { + groupPropertyValues = new ArrayList<>(); + groupSpecification.groupPropertyValues = groupPropertyValues; + } + + Iterator iterator = groupSpecification.groupPropertyValues.iterator(); + while (iterator.hasNext()) { + GroupPropertyValue next = iterator.next(); + if (next.groupPropertyId().equals(groupPropertyId)) { + iterator.remove(); + break; + } + } + + GroupPropertyValue groupPropertyValue = new GroupPropertyValue(groupPropertyId, value); + groupPropertyValues.add(groupPropertyValue); + + return this; + } + + /** + * Sets the next available group id. This value needs to exceed all extant group + * ids. If the nextGroupRecordId is not set explicitly, the nextGroupRecordId is + * assigned to either zero or the next integer value that exceeds the highest + * valued group added to this builder. + * + * @throws ContractException {@linkplain GroupError#NEGATIVE_GROUP_ID} if the + * next group record id is negative + */ + public Builder setNextGroupIdValue(int nextGroupIdValue) { + ensureDataMutability(); + validateGroupIdValue(nextGroupIdValue); + data.nextGroupIdValue = nextGroupIdValue; + return this; + } + + private void validateData() { + + for (int i = 0; i < data.personToGroupsMemberships.size(); i++) { + List groupIds = data.personToGroupsMemberships.get(i); + if (groupIds != null) { + + if (new HashSet<>(groupIds).size() != groupIds.size()) { + throw new ContractException(GroupError.DUPLICATE_GROUP_MEMBERSHIP, + new PersonId(i) + " has groups " + groupIds); + } + + for (GroupId groupId : groupIds) { + int groupIndex = groupId.getValue(); + if (groupIndex >= data.groupSpecifications.size()) { + throw new ContractException(GroupError.UNKNOWN_GROUP_ID, + "A group membership contains the unknown group " + groupId); + } + GroupSpecification groupSpecification = data.groupSpecifications.get(groupIndex); + if (groupSpecification == null) { + throw new ContractException(GroupError.UNKNOWN_GROUP_ID, + "A group membership contains the unknown group " + groupId); + } + } + } + } + + for (int i = 0; i < data.groupToPeopleMemberships.size(); i++) { + GroupId groupId = new GroupId(i); + List people = data.groupToPeopleMemberships.get(i); + if (people != null) { + int groupIndex = groupId.getValue(); + if (groupIndex >= data.groupSpecifications.size()) { + throw new ContractException(GroupError.UNKNOWN_GROUP_ID, + "A group membership contains the unknown group " + groupId); + } + + GroupSpecification groupSpecification = data.groupSpecifications.get(groupIndex); + if (groupSpecification == null) { + throw new ContractException(GroupError.UNKNOWN_GROUP_ID, + "A group membership contains the unknown group " + groupId); + } + + if (new HashSet<>(people).size() != people.size()) { + throw new ContractException(GroupError.DUPLICATE_GROUP_MEMBERSHIP, + groupId + " has people " + people); + } + } + } + + if (data.asymmetricMemberships) { + Set set = new LinkedHashSet<>(); + + for (int i = 0; i < data.personToGroupsMemberships.size(); i++) { + PersonId personId = new PersonId(i); + List groupIds = data.personToGroupsMemberships.get(i); + if (groupIds != null) { + for (GroupId groupId : groupIds) { + set.add(new MultiKey(personId, groupId)); + } + } + } + + for (int i = 0; i < data.groupToPeopleMemberships.size(); i++) { + GroupId groupId = new GroupId(i); + List people = data.groupToPeopleMemberships.get(i); + if (people != null) { + for (PersonId personId : people) { + MultiKey multiKey = new MultiKey(personId, groupId); + if (!set.remove(multiKey)) { + throw new ContractException(GroupError.GROUP_MEMBERSHIP_ASYMMETRY); + } + } + } + } + + if (!set.isEmpty()) { + throw new ContractException(GroupError.GROUP_MEMBERSHIP_ASYMMETRY); + } + + } + + for (GroupSpecification groupSpecification : data.groupSpecifications) { + if (groupSpecification != null) { + GroupTypeId groupTypeId = groupSpecification.groupTypeId; + if (groupTypeId == null) { + throw new ContractException(GroupError.UNKNOWN_GROUP_ID, + "A group property contains the unknown group " + groupSpecification.groupId); + } + if (!data.groupTypeIds.contains(groupTypeId)) { + throw new ContractException(GroupError.UNKNOWN_GROUP_TYPE_ID, + groupSpecification.groupId + " has unknown group type " + groupTypeId); + } + } + } + + for (GroupTypeId groupTypeId : data.groupPropertyDefinitions.keySet()) { + if (!data.groupTypeIds.contains(groupTypeId)) { + throw new ContractException(GroupError.UNKNOWN_GROUP_TYPE_ID, + "group property definitions have unknown group type " + groupTypeId); + } + } + + for (GroupSpecification groupSpecification : data.groupSpecifications) { + if (groupSpecification != null) { + GroupTypeId groupTypeId = groupSpecification.groupTypeId; + Map propDefMap = data.groupPropertyDefinitions + .get(groupTypeId); + if (groupSpecification.groupPropertyValues != null) { + for (GroupPropertyValue groupPropertyValue : groupSpecification.groupPropertyValues) { + GroupPropertyId groupPropertyId = groupPropertyValue.groupPropertyId(); + PropertyDefinition propertyDefinition = propDefMap.get(groupPropertyId); + if (propertyDefinition == null) { + throw new ContractException(PropertyError.UNKNOWN_PROPERTY_ID, + groupPropertyId + " under group type " + groupTypeId); + } + Object propertyValue = groupPropertyValue.value(); + if (!propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())) { + throw new ContractException(PropertyError.INCOMPATIBLE_VALUE, + groupSpecification.groupId + ": " + groupPropertyId + ": " + propertyValue); + } + } + } + } + } + + /* + * All group property definitions that do not have a default value must have + * corresponding property value assignments for added groups. + */ + Map> propertyDefsWithoutDefaults = new LinkedHashMap<>(); + + for (GroupTypeId groupTypeId : data.groupTypeIds) { + Set set = new LinkedHashSet<>(); + propertyDefsWithoutDefaults.put(groupTypeId, set); + Map propertyDefinitionMap = data.groupPropertyDefinitions + .get(groupTypeId); + if (propertyDefinitionMap != null) { + for (GroupPropertyId groupPropertyId : propertyDefinitionMap.keySet()) { + PropertyDefinition propertyDefinition = propertyDefinitionMap.get(groupPropertyId); + if (propertyDefinition.getDefaultValue().isEmpty()) { + set.add(groupPropertyId); + } + } + } + } + + /* + * The use of a coverage counter below is dependent on every GroupPropertyValue + * associated with a particular group having a unique property id + */ + for (GroupSpecification groupSpecification : data.groupSpecifications) { + if (groupSpecification != null) { + Set set = propertyDefsWithoutDefaults.get(groupSpecification.groupTypeId); + if (set.size() > 0) { + int coverageCount = 0; + if (groupSpecification.groupPropertyValues != null) { + for (GroupPropertyValue groupPropertyValue : groupSpecification.groupPropertyValues) { + if (set.contains(groupPropertyValue.groupPropertyId())) { + coverageCount++; + } + } + } + if (coverageCount != set.size()) { + throw new ContractException(PropertyError.INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT); + } + } + } + } + + if (data.nextGroupIdValue < 0) { + for (GroupSpecification groupSpecification : data.groupSpecifications) { + if (groupSpecification != null) { + data.nextGroupIdValue = FastMath.max(data.nextGroupIdValue, + groupSpecification.groupId.getValue()); + } + } + data.nextGroupIdValue++; + } else { + for (GroupSpecification groupSpecification : data.groupSpecifications) { + if (groupSpecification != null) { + if (groupSpecification.groupId.getValue() >= data.nextGroupIdValue) { + throw new ContractException(GroupError.NEXT_GROUP_ID_TOO_SMALL); + } + } + } + + } + } + } + + private static void validateGroupTypeExists(final Data data, final GroupTypeId groupTypeId) { + if (groupTypeId == null) { + throw new ContractException(GroupError.NULL_GROUP_TYPE_ID); + } + if (!data.groupTypeIds.contains(groupTypeId)) { + throw new ContractException(GroupError.UNKNOWN_GROUP_TYPE_ID, groupTypeId); + } + } + + private static void validateGroupPropertyIsDefined(final Data data, final GroupTypeId groupTypeId, + final GroupPropertyId groupPropertyId) { + final Map map = data.groupPropertyDefinitions.get(groupTypeId); + if (map == null) { + throw new ContractException(PropertyError.UNKNOWN_PROPERTY_ID, groupPropertyId); + } + final PropertyDefinition propertyDefinition = map.get(groupPropertyId); + if (propertyDefinition == null) { + throw new ContractException(PropertyError.UNKNOWN_PROPERTY_ID, groupPropertyId); + } + } + + /** + * Returns the property definition for the given group type id and group + * property id + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if + * the group type id is null
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} + * if the group type id is unknown
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the group property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the group property id is not associated with the + * group type id via a property definition
    • + *
    + */ + public PropertyDefinition getGroupPropertyDefinition(final GroupTypeId groupTypeId, + final GroupPropertyId groupPropertyId) { + validateGroupTypeExists(data, groupTypeId); + validateGroupPropertyIdNotNull(groupPropertyId); + validateGroupPropertyIsDefined(data, groupTypeId, groupPropertyId); + final Map map = data.groupPropertyDefinitions.get(groupTypeId); + final PropertyDefinition propertyDefinition = map.get(groupPropertyId); + return propertyDefinition; + } + + /** + * Returns the set of group property ids for the given group type id + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if + * the group type id is null
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} + * if the group type id is unknown
    • + *
    + */ + @SuppressWarnings("unchecked") + public Set getGroupPropertyIds(final GroupTypeId groupTypeId) { + validateGroupTypeExists(data, groupTypeId); + final Set result = new LinkedHashSet<>(); + final Map map = data.groupPropertyDefinitions.get(groupTypeId); + if (map != null) { + for (GroupPropertyId groupPropertyId : map.keySet()) { + result.add((T) groupPropertyId); + } + } + return result; + } + + private static void validateGroupExists(final Data data, final GroupId groupId) { + if (groupId == null) { + throw new ContractException(GroupError.NULL_GROUP_ID); + } + int groupIndex = groupId.getValue(); + if (groupIndex >= data.groupSpecifications.size()) { + throw new ContractException(GroupError.UNKNOWN_GROUP_ID, groupId); + } + GroupSpecification groupSpecification = data.groupSpecifications.get(groupIndex); + + if (groupSpecification == null) { + throw new ContractException(GroupError.UNKNOWN_GROUP_ID, groupId); + } + } + + /** + * Returns the property value associated with the given group id and group + * property id + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_ID} if the + * group id is null
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_ID} if the + * group id is unknown
    • + *
    + */ + public List getGroupPropertyValues(final GroupId groupId) { + validateGroupExists(data, groupId); + int groupIndex = groupId.getValue(); + GroupSpecification groupSpecification = data.groupSpecifications.get(groupIndex); + if (groupSpecification.groupPropertyValues == null) { + return data.emptyGroupPropertyValues; + } + return Collections.unmodifiableList(groupSpecification.groupPropertyValues); + } + + /** + * Returns the set of group type ids + */ + @SuppressWarnings("unchecked") + public Set getGroupTypeIds() { + Set result = new LinkedHashSet<>(data.groupTypeIds.size()); + for (GroupTypeId groupTypeId : data.groupTypeIds) { + result.add((T) groupTypeId); + } + return result; + } + + /** + * Returns the group ids as a list + */ + public List getGroupIds() { + List result = new ArrayList<>(); + for (GroupSpecification groupSpecification : data.groupSpecifications) { + if (groupSpecification != null) { + result.add(groupSpecification.groupId); + } + } + return result; + } + + /** + * Returns the group type id associated with the given group id + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_ID} if the + * group id is null
    • + *
    • {@linkplain GroupError#UNKNOWN_GROUP_ID} if the + * group id is unknown
    • + *
    + */ + @SuppressWarnings("unchecked") + public T getGroupTypeId(final GroupId groupId) { + validateGroupExists(data, groupId); + int groupIndex = groupId.getValue(); + GroupSpecification groupSpecification = data.groupSpecifications.get(groupIndex); + final GroupTypeId result = groupSpecification.groupTypeId; + return (T) result; + } + + /** + * Returns the unmodifiable list of groups associated with the person id + */ + public List getGroupsForPerson(final PersonId personId) { + if (personId == null) { + return data.emptyGroupList; + } + int personIndex = personId.getValue(); + if (personIndex >= data.personToGroupsMemberships.size()) { + return data.emptyGroupList; + } + List list = data.personToGroupsMemberships.get(personIndex); + if (list == null) { + return data.emptyGroupList; + } + return Collections.unmodifiableList(list); + } + + /** + * Returns the unmodifiable list of people associated with the group id + */ + public List getPeopleForGroup(final GroupId groupId) { + if (groupId == null) { + return data.emptyPersonList; + } + int groupIndex = groupId.getValue(); + if (groupIndex >= data.groupToPeopleMemberships.size()) { + return data.emptyPersonList; + } + List list = data.groupToPeopleMemberships.get(groupIndex); + if (list == null) { + return data.emptyPersonList; + } + return Collections.unmodifiableList(list); + } + + private static void validatePersonId(PersonId personId) { + if (personId == null) { + throw new ContractException(PersonError.NULL_PERSON_ID); + } + } + + private static void validateGroupIdValue(int groupIdValue) { + if (groupIdValue < 0) { + throw new ContractException(GroupError.NEGATIVE_GROUP_ID, groupIdValue); + } + } + + /** + * Returns the int value that exceeds by one the highest person id value + * encountered while associating people with groups. + */ + public int getPersonCount() { + return data.personToGroupsMemberships.size(); + } + + /** + * Returns the int value that exceeds by one the highest group id value + * encountered while associating groups with people. + */ + public int getGroupCount() { + return data.groupToPeopleMemberships.size(); + } + + @Override + public PluginDataBuilder getCloneBuilder() { + return new Builder(data); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + data.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof GroupsPluginData)) { + return false; + } + GroupsPluginData other = (GroupsPluginData) obj; + if (!data.equals(other.data)) { + return false; + } + return true; + } + + @Override + public String toString() { + return "GroupsPluginData [" + "data=" + data + ']'; + } + + /** + * Returns the next available group id. + */ + public int getNextGroupIdValue() { + return data.nextGroupIdValue; + } + + public Map> getGroupPropertyDefinitions() { + Map> result = new LinkedHashMap<>(); + for (GroupTypeId groupTypeId : data.groupPropertyDefinitions.keySet()) { + Map map = data.groupPropertyDefinitions.get(groupTypeId); + Map newMap = new LinkedHashMap<>(map); + result.put(groupTypeId, newMap); + } + return result; + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/GroupAdditionEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/GroupAdditionEvent.java new file mode 100644 index 000000000..b6079bca4 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/GroupAdditionEvent.java @@ -0,0 +1,12 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupId; +import net.jcip.annotations.Immutable; + +/** + * An event indicating that a group has been created + */ +@Immutable +public record GroupAdditionEvent(GroupId groupId) implements Event { +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/GroupImminentRemovalEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/GroupImminentRemovalEvent.java new file mode 100644 index 000000000..9dc104bb7 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/GroupImminentRemovalEvent.java @@ -0,0 +1,12 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupId; +import net.jcip.annotations.Immutable; + +/** + * Event to signal the imminent removal of a group from the simulation + */ +@Immutable +public record GroupImminentRemovalEvent(GroupId groupId) implements Event { +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/GroupMembershipAdditionEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/GroupMembershipAdditionEvent.java new file mode 100644 index 000000000..1cf1d84b3 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/GroupMembershipAdditionEvent.java @@ -0,0 +1,13 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import net.jcip.annotations.Immutable; + +/** + * Event to indicating that person was added to a group + */ +@Immutable +public record GroupMembershipAdditionEvent(PersonId personId, GroupId groupId) implements Event { +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/GroupMembershipRemovalEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/GroupMembershipRemovalEvent.java new file mode 100644 index 000000000..b802e24f0 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/GroupMembershipRemovalEvent.java @@ -0,0 +1,13 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import net.jcip.annotations.Immutable; + +/** + * Event to indicating that person was removed from a group + */ +@Immutable +public record GroupMembershipRemovalEvent(PersonId personId, GroupId groupId) implements Event { +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/GroupPropertyDefinitionEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/GroupPropertyDefinitionEvent.java new file mode 100644 index 000000000..d72f26c6c --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/GroupPropertyDefinitionEvent.java @@ -0,0 +1,41 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupError; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupTypeId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * An event released by the groups data manager whenever a group property + * definition is added to the simulation. + */ +@Immutable +public record GroupPropertyDefinitionEvent(GroupTypeId groupTypeId, GroupPropertyId groupPropertyId) implements Event { + + /** + * Creates the event. + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if + * the group type id is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the group property id is null
    • + *
    + */ + public GroupPropertyDefinitionEvent { + + if (groupTypeId == null) { + throw new ContractException(GroupError.NULL_GROUP_TYPE_ID); + } + + if (groupPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/GroupPropertyUpdateEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/GroupPropertyUpdateEvent.java new file mode 100644 index 000000000..1eca5334a --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/GroupPropertyUpdateEvent.java @@ -0,0 +1,14 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupId; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupPropertyId; +import net.jcip.annotations.Immutable; + +/** + * Event to indicating that a group had a property value change + */ +@Immutable +public record GroupPropertyUpdateEvent(GroupId groupId, GroupPropertyId groupPropertyId, Object previousPropertyValue, + Object currentPropertyValue) implements Event { +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/GroupTypeAdditionEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/GroupTypeAdditionEvent.java new file mode 100644 index 000000000..81d78d8ad --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/GroupTypeAdditionEvent.java @@ -0,0 +1,12 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupTypeId; +import net.jcip.annotations.Immutable; + +/** + * An event indicating that a group type has been created + */ +@Immutable +public record GroupTypeAdditionEvent(GroupTypeId groupTypeId) implements Event { +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/reports/GroupPopulationReport.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/reports/GroupPopulationReport.java new file mode 100644 index 000000000..65ee71b9d --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/reports/GroupPopulationReport.java @@ -0,0 +1,110 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.reports; + +import java.util.LinkedHashMap; +import java.util.Map; + +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.plugins.groups.datamanagers.GroupsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupId; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupTypeId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.PeriodicReport; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; + +/** + * A periodic Report that displays the number of groups having a particular + * number of people for a given group type. Fields GroupType -- the group type + * of group PersonCount -- the number of people in each group GroupCount -- the + * number of groups having the person count + */ +public final class GroupPopulationReport extends PeriodicReport { + + public GroupPopulationReport(GroupPopulationReportPluginData groupPopulationReportPluginData) { + super(groupPopulationReportPluginData.getReportLabel(), groupPopulationReportPluginData.getReportPeriod()); + } + + /* + * Count of the number of groups having a particular person count for a + * particular group type + */ + private static class Counter { + int count; + } + + private ReportHeader reportHeader; + + private ReportHeader getReportHeader() { + if (reportHeader == null) { + ReportHeader.Builder reportHeaderBuilder = ReportHeader.builder(); + reportHeader = addTimeFieldHeaders(reportHeaderBuilder)// + .add("group_type")// + .add("person_count")// + .add("group_count")// + .build();// + } + return reportHeader; + } + + @Override + protected void flush(ReportContext reportContext) { + + /* + * Count the number of groups of each size that exist for each group type + */ + Map> groupTypePopulationMap = new LinkedHashMap<>(); + for (GroupTypeId groupTypeId : groupsDataManager.getGroupTypeIds()) { + Map groupSizeMap = new LinkedHashMap<>(); + groupTypePopulationMap.put(groupTypeId, groupSizeMap); + for (GroupId groupId : groupsDataManager.getGroupsForGroupType(groupTypeId)) { + Integer personCountForGroup = groupsDataManager.getPersonCountForGroup(groupId); + Counter counter = groupSizeMap.get(personCountForGroup); + if (counter == null) { + counter = new Counter(); + groupSizeMap.put(personCountForGroup, counter); + } + counter.count++; + } + } + + /* + * Report the collected group counters + */ + for (final GroupTypeId groupTypeId : groupTypePopulationMap.keySet()) { + Map groupSizeMap = groupTypePopulationMap.get(groupTypeId); + for (final Integer personCount : groupSizeMap.keySet()) { + ReportItem.Builder reportItemBuilder = ReportItem.builder(); + Counter counter = groupSizeMap.get(personCount); + + final int groupCount = counter.count; + reportItemBuilder.setReportHeader(getReportHeader()); + reportItemBuilder.setReportLabel(getReportLabel()); + fillTimeFields(reportItemBuilder); + reportItemBuilder.addValue(groupTypeId.toString()); + reportItemBuilder.addValue(personCount); + reportItemBuilder.addValue(groupCount); + ReportItem reportItem = reportItemBuilder.build(); + reportContext.releaseOutput(reportItem); + + } + } + + } + + private GroupsDataManager groupsDataManager; + + @Override + protected void prepare(ReportContext reportContext) { + groupsDataManager = reportContext.getDataManager(GroupsDataManager.class); + if (reportContext.stateRecordingIsScheduled()) { + reportContext.subscribeToSimulationClose(this::recordSimulationState); + } + } + + private void recordSimulationState(ReportContext reportContext) { + GroupPopulationReportPluginData.Builder builder = GroupPopulationReportPluginData.builder(); + builder.setReportLabel(getReportLabel()); + builder.setReportPeriod(getReportPeriod()); + reportContext.releaseOutput(builder.build()); + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/reports/GroupPopulationReportPluginData.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/reports/GroupPopulationReportPluginData.java new file mode 100644 index 000000000..1432a6656 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/reports/GroupPopulationReportPluginData.java @@ -0,0 +1,115 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.reports; + +import gov.hhs.aspr.ms.gcm.plugins.reports.support.PeriodicReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportError; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import net.jcip.annotations.ThreadSafe; +import util.errors.ContractException; + +/** + * A PluginData class supporting GroupPopulationReport construction. + */ +@ThreadSafe +public final class GroupPopulationReportPluginData extends PeriodicReportPluginData { + + private final Data data; + + private GroupPopulationReportPluginData(Data data) { + super(data); + this.data = data; + } + + private static class Data extends PeriodicReportPluginData.Data { + + private Data() { + super(); + } + + private Data(Data data) { + super(data); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(super.toString()); + builder.append("]"); + return builder.toString(); + } + } + + /** + * Builder class for the report + */ + public final static class Builder extends PeriodicReportPluginData.Builder { + + private Data data; + + private Builder(Data data) { + super(data); + this.data = data; + } + + /** + * Returns a PersonPropertyReportPluginData created from the collected inputs + * + * @throws ContractException + *
      + *
    • {@linkplain ReportError#NULL_REPORT_LABEL} if + * the report label is not assigned
    • + *
    • {@linkplain ReportError#NULL_REPORT_PERIOD} if + * the report period is not assigned
    • + *
    + */ + @Override + public GroupPopulationReportPluginData build() { + return new GroupPopulationReportPluginData(data); + } + + /** + * Sets the report label + * + * @throws ContractException {@linkplain ReportError#NULL_REPORT_LABEL} if the + * report label is null + */ + @Override + public Builder setReportLabel(ReportLabel reportLabel) { + super.setReportLabel(reportLabel); + return this; + } + + /** + * Sets the report period id + * + * @throws ContractException {@linkplain ReportError#NULL_REPORT_PERIOD} if the + * report period is null + */ + @Override + public Builder setReportPeriod(ReportPeriod reportPeriod) { + super.setReportPeriod(reportPeriod); + return this; + } + } + + /** + * Returns a new instance of the builder class + */ + public static Builder builder() { + return new Builder(new Data()); + } + + @Override + public Builder getCloneBuilder() { + return new Builder(new Data(data)); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("GroupPopulationReportPluginData [data="); + builder.append(data); + builder.append("]"); + return builder.toString(); + } +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/reports/GroupPropertyReport.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/reports/GroupPropertyReport.java new file mode 100644 index 000000000..93dd7a4c7 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/reports/GroupPropertyReport.java @@ -0,0 +1,338 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.reports; + +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.plugins.groups.datamanagers.GroupsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupImminentRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupPropertyDefinitionEvent; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupPropertyUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupId; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupTypeId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.PeriodicReport; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; + +/** + * A periodic Report that displays the number of groups having particular values + * for each group property for a given group type. Only non-zero person counts + * are reported. The report is further limited to the + * (GroupType,GroupPropertyId) pairs added to the builder. Fields GroupType -- + * the group type of group Property -- the group property identifier Value -- + * the value of the property GroupCount -- the number of groups having the + * property value for the given group type + */ +public final class GroupPropertyReport extends PeriodicReport { + + public GroupPropertyReport(GroupPropertyReportPluginData groupPropertyReportPluginData) { + super(groupPropertyReportPluginData.getReportLabel(), groupPropertyReportPluginData.getReportPeriod()); + + for (GroupTypeId groupTypeId : groupPropertyReportPluginData.getGroupTypeIds()) { + includedProperties.put(groupTypeId, + new LinkedHashSet<>(groupPropertyReportPluginData.getIncludedProperties(groupTypeId))); + excludedProperties.put(groupTypeId, + new LinkedHashSet<>(groupPropertyReportPluginData.getExcludedProperties(groupTypeId))); + } + includeNewProperties = groupPropertyReportPluginData.getDefaultInclusionPolicy(); + } + + private static class Counter { + int count; + } + + /* + * For each (GroupTypeId,GroupPropertyId,property value) triplet, count the + * number of groups having that triplet + */ + private final Map>> groupTypeMap = new LinkedHashMap<>(); + + private ReportHeader reportHeader; + + private ReportHeader getReportHeader() { + if (reportHeader == null) { + ReportHeader.Builder reportHeaderBuilder = ReportHeader.builder(); + reportHeader = addTimeFieldHeaders(reportHeaderBuilder)// + .add("group_type")// + .add("property")// + .add("value")// + .add("group_count")// + .build();// + } + return reportHeader; + } + + /* + * Decrement the number of groups for the given + * (GroupTypeId,GroupPropertyId,property value) triplet + */ + private void decrement(final GroupTypeId groupTypeId, final GroupPropertyId groupPropertyId, + final Object groupPropertyValue) { + getCounter(groupTypeId, groupPropertyId, groupPropertyValue).count--; + } + + @Override + protected void flush(ReportContext reportContext) { + + for (final GroupTypeId groupTypeId : groupTypeMap.keySet()) { + final Map> propertyIdMap = groupTypeMap.get(groupTypeId); + for (final GroupPropertyId groupPropertyId : propertyIdMap.keySet()) { + final Map groupPropertyValueMap = propertyIdMap.get(groupPropertyId); + for (final Object groupPropertyValue : groupPropertyValueMap.keySet()) { + final Counter counter = groupPropertyValueMap.get(groupPropertyValue); + if (counter.count > 0) { + final int personCount = counter.count; + ReportItem.Builder reportItemBuilder = ReportItem.builder(); + reportItemBuilder.setReportHeader(getReportHeader()); + reportItemBuilder.setReportLabel(getReportLabel()); + + fillTimeFields(reportItemBuilder); + reportItemBuilder.addValue(groupTypeId.toString()); + reportItemBuilder.addValue(groupPropertyId.toString()); + reportItemBuilder.addValue(groupPropertyValue); + reportItemBuilder.addValue(personCount); + + reportContext.releaseOutput(reportItemBuilder.build()); + } + } + } + } + } + + /* + * Returns the counter corresponding to the given group type, group property id + * and group property value. Adds the counter if it does not already exist + */ + private Counter getCounter(final GroupTypeId groupTypeId, final GroupPropertyId groupPropertyId, + final Object groupPropertyValue) { + Map> map1 = groupTypeMap.get(groupTypeId); + if (map1 == null) { + map1 = new LinkedHashMap<>(); + groupTypeMap.put(groupTypeId, map1); + } + Map map2 = map1.get(groupPropertyId); + if (map2 == null) { + map2 = new LinkedHashMap<>(); + map1.put(groupPropertyId, map2); + } + Counter counter = map2.get(groupPropertyValue); + if (counter == null) { + counter = new Counter(); + map2.put(groupPropertyValue, counter); + } + return counter; + } + + /* + * Increment the number of groups for the given + * (GroupTypeId,GroupPropertyId,property value) triplet + */ + private void increment(final GroupTypeId groupTypeId, final GroupPropertyId groupPropertyId, + final Object groupPropertyValue) { + getCounter(groupTypeId, groupPropertyId, groupPropertyValue).count++; + } + + private GroupsDataManager groupsDataManager; + + private final Map> includedProperties = new LinkedHashMap<>(); + private final Map> currentProperties = new LinkedHashMap<>(); + private final Map> excludedProperties = new LinkedHashMap<>(); + private final boolean includeNewProperties; + + private boolean isCurrentProperty(GroupTypeId groupTypeId, GroupPropertyId groupPropertyId) { + boolean result = false; + Set set = currentProperties.get(groupTypeId); + if (set != null) { + result = set.contains(groupPropertyId); + } + return result; + } + + private boolean addToCurrentProperties(GroupTypeId groupTypeId, GroupPropertyId groupPropertyId) { + + // There are eight possibilities: + + /* + * P -- the default inclusion policy + * + * I -- the property is explicitly included + * + * X -- the property is explicitly excluded + * + * C -- the property should be on the current properties + * + * + * P I X C Table + * + * TRUE TRUE FALSE TRUE + * + * TRUE FALSE FALSE TRUE + * + * FALSE TRUE FALSE TRUE + * + * FALSE FALSE FALSE FALSE + * + * TRUE TRUE TRUE FALSE -- not possible + * + * TRUE FALSE TRUE FALSE + * + * FALSE TRUE TRUE FALSE -- not possible + * + * FALSE FALSE TRUE FALSE + * + * + * Two of the cases above are contradictory since a property cannot be both + * explicitly included and explicitly excluded + * + */ + // if X is true then we don't add the property + Set set = excludedProperties.get(groupTypeId); + if (set != null) { + if (set.contains(groupPropertyId)) { + return false; + } + } + + // if both P and I are false we don't add the property + boolean included = false; + set = includedProperties.get(groupTypeId); + if (set != null) { + included = set.contains(groupPropertyId); + } + + if (!included && !includeNewProperties) { + return false; + } + + // we have failed to reject the property + set = currentProperties.get(groupTypeId); + if (set == null) { + set = new LinkedHashSet<>(); + currentProperties.put(groupTypeId, set); + } + set.add(groupPropertyId); + + return true; + } + + @Override + protected void prepare(final ReportContext reportContext) { + + groupsDataManager = reportContext.getDataManager(GroupsDataManager.class); + + // subscribe to events + reportContext.subscribe(GroupAdditionEvent.class, this::handleGroupAdditionEvent); + reportContext.subscribe(GroupImminentRemovalEvent.class, this::handleGroupImminentRemovalEvent); + reportContext.subscribe(GroupPropertyUpdateEvent.class, this::handleGroupPropertyUpdateEvent); + reportContext.subscribe(GroupPropertyDefinitionEvent.class, this::handleGroupPropertyDefinitionEvent); + if (reportContext.stateRecordingIsScheduled()) { + reportContext.subscribeToSimulationClose(this::recordSimulationState); + } + + // update the current properties from the existing properties found in + // the data manager + for (GroupTypeId groupTypeId : groupsDataManager.getGroupTypeIds()) { + for (GroupPropertyId groupPropertyId : groupsDataManager.getGroupPropertyIds(groupTypeId)) { + addToCurrentProperties(groupTypeId, groupPropertyId); + } + } + + /* + * Initialize the buckets containing what we will report + * + */ + for (GroupId groupId : groupsDataManager.getGroupIds()) { + GroupTypeId groupType = groupsDataManager.getGroupType(groupId); + if (currentProperties.containsKey(groupType)) { + for (GroupPropertyId groupPropertyId : currentProperties.get(groupType)) { + Object groupPropertyValue = groupsDataManager.getGroupPropertyValue(groupId, groupPropertyId); + increment(groupType, groupPropertyId, groupPropertyValue); + } + } + } + } + + private void recordSimulationState(ReportContext reportContext) { + GroupPropertyReportPluginData.Builder builder = GroupPropertyReportPluginData.builder(); + builder.setReportLabel(getReportLabel()); + builder.setReportPeriod(getReportPeriod()); + builder.setDefaultInclusion(includeNewProperties); + + for (GroupTypeId groupTypeId : includedProperties.keySet()) { + for (GroupPropertyId groupPropertyId : includedProperties.get(groupTypeId)) { + builder.includeGroupProperty(groupTypeId, groupPropertyId); + } + } + + for (GroupTypeId groupTypeId : excludedProperties.keySet()) { + for (GroupPropertyId groupPropertyId : excludedProperties.get(groupTypeId)) { + builder.excludeGroupProperty(groupTypeId, groupPropertyId); + } + } + reportContext.releaseOutput(builder.build()); + + } + + private void handleGroupPropertyDefinitionEvent(ReportContext reportContext, + GroupPropertyDefinitionEvent groupPropertyDefinitionEvent) { + + final GroupTypeId groupTypeId = groupPropertyDefinitionEvent.groupTypeId(); + final GroupPropertyId groupPropertyId = groupPropertyDefinitionEvent.groupPropertyId(); + final boolean added = addToCurrentProperties(groupTypeId, groupPropertyId); + + if (added) { + List groups = groupsDataManager.getGroupsForGroupType(groupPropertyDefinitionEvent.groupTypeId()); + for (GroupId groupId : groups) { + Object groupPropertyValue = groupsDataManager.getGroupPropertyValue(groupId, groupPropertyId); + increment(groupTypeId, groupPropertyId, groupPropertyValue); + } + } + } + + private void handleGroupPropertyUpdateEvent(ReportContext reportContext, + GroupPropertyUpdateEvent groupPropertyUpdateEvent) { + + final GroupId groupId = groupPropertyUpdateEvent.groupId(); + final GroupTypeId groupTypeId = groupsDataManager.getGroupType(groupId); + final GroupPropertyId groupPropertyId = groupPropertyUpdateEvent.groupPropertyId(); + + if (isCurrentProperty(groupTypeId, groupPropertyId)) { + Object previousPropertyValue = groupPropertyUpdateEvent.previousPropertyValue(); + Object currentPropertyValue = groupPropertyUpdateEvent.currentPropertyValue(); + increment(groupTypeId, groupPropertyId, currentPropertyValue); + decrement(groupTypeId, groupPropertyId, previousPropertyValue); + } + } + + private void handleGroupAdditionEvent(ReportContext reportContext, GroupAdditionEvent groupAdditionEvent) { + final GroupId groupId = groupAdditionEvent.groupId(); + final GroupTypeId groupTypeId = groupsDataManager.getGroupType(groupId); + + Set groupPropertyIds = currentProperties.get(groupTypeId); + if (groupPropertyIds != null) { + for (GroupPropertyId groupPropertyId : groupPropertyIds) { + final Object groupPropertyValue = groupsDataManager.getGroupPropertyValue(groupId, groupPropertyId); + increment(groupTypeId, groupPropertyId, groupPropertyValue); + } + } + } + + private void handleGroupImminentRemovalEvent(ReportContext reportContext, + GroupImminentRemovalEvent groupImminentRemovalEvent) { + final GroupId groupId = groupImminentRemovalEvent.groupId(); + final GroupTypeId groupTypeId = groupsDataManager.getGroupType(groupId); + + Set groupPropertyIds = currentProperties.get(groupTypeId); + if (groupPropertyIds != null) { + for (GroupPropertyId groupPropertyId : groupPropertyIds) { + final Object groupPropertyValue = groupsDataManager.getGroupPropertyValue(groupId, groupPropertyId); + decrement(groupTypeId, groupPropertyId, groupPropertyValue); + } + } + } +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/reports/GroupPropertyReportPluginData.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/reports/GroupPropertyReportPluginData.java new file mode 100644 index 000000000..bd2d8a2e4 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/reports/GroupPropertyReportPluginData.java @@ -0,0 +1,341 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.reports; + +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupError; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupTypeId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.PeriodicReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportError; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import net.jcip.annotations.ThreadSafe; +import util.errors.ContractException; + +/** + * A PluginData class supporting GroupPropertyReport construction. + */ +@ThreadSafe +public final class GroupPropertyReportPluginData extends PeriodicReportPluginData { + + private final Data data; + + private GroupPropertyReportPluginData(Data data) { + super(data); + this.data = data; + } + + /* + * Data class for collecting the inputs to the report + */ + private static class Data extends PeriodicReportPluginData.Data { + private Map> includedProperties = new LinkedHashMap<>(); + private Map> excludedProperties = new LinkedHashMap<>(); + private boolean defaultInclusionPolicy = true; + + private Data() { + super(); + } + + private Data(Data data) { + super(data); + for (GroupTypeId groupTypeId : data.includedProperties.keySet()) { + Set set = data.includedProperties.get(groupTypeId); + Set newSet = new LinkedHashSet<>(set); + includedProperties.put(groupTypeId, newSet); + } + for (GroupTypeId groupTypeId : data.excludedProperties.keySet()) { + Set set = data.excludedProperties.get(groupTypeId); + Set newSet = new LinkedHashSet<>(set); + excludedProperties.put(groupTypeId, newSet); + } + defaultInclusionPolicy = data.defaultInclusionPolicy; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + (defaultInclusionPolicy ? 1231 : 1237); + result = prime * result + ((excludedProperties == null) ? 0 : excludedProperties.hashCode()); + result = prime * result + ((includedProperties == null) ? 0 : includedProperties.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + if (defaultInclusionPolicy != other.defaultInclusionPolicy) { + return false; + } + if (excludedProperties == null) { + if (other.excludedProperties != null) { + return false; + } + } else if (!excludedProperties.equals(other.excludedProperties)) { + return false; + } + if (includedProperties == null) { + if (other.includedProperties != null) { + return false; + } + } else if (!includedProperties.equals(other.includedProperties)) { + return false; + } + + return super.equals(other); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(super.toString()); + builder.append(", includedProperties="); + builder.append(includedProperties); + builder.append(", excludedProperties="); + builder.append(excludedProperties); + builder.append(", defaultInclusionPolicy="); + builder.append(defaultInclusionPolicy); + builder.append("]"); + return builder.toString(); + } + + } + + /** + * Builder class for the report + */ + public final static class Builder extends PeriodicReportPluginData.Builder { + + private Data data; + + private Builder(Data data) { + super(data); + this.data = data; + } + + /** + * Returns a GroupPropertyReportPluginData created from the collected inputs + * + * @throws ContractException + *
      + *
    • {@linkplain ReportError#NULL_REPORT_LABEL} if + * the report label is not assigned
    • + *
    • {@linkplain ReportError#NULL_REPORT_PERIOD} if + * the report period is not assigned
    • + *
    + */ + public GroupPropertyReportPluginData build() { + return new GroupPropertyReportPluginData(data); + } + + /** + * Sets the default policy for inclusion of group properties in the report. This + * policy is used when a group property has not been explicitly included or + * excluded. Defaulted to true. + */ + public Builder setDefaultInclusion(boolean include) { + data.defaultInclusionPolicy = include; + return this; + } + + /** + * Selects the given group property id to be included in the report. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the group property id is null
    • + *
    • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if + * the group type id is null
    • + *
    + */ + public Builder includeGroupProperty(GroupTypeId groupTypeId, GroupPropertyId groupPropertyId) { + if (groupTypeId == null) { + throw new ContractException(GroupError.NULL_GROUP_TYPE_ID); + } + if (groupPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + Set set = data.includedProperties.get(groupTypeId); + if (set == null) { + set = new LinkedHashSet<>(); + data.includedProperties.put(groupTypeId, set); + } + set.add(groupPropertyId); + set = data.excludedProperties.get(groupTypeId); + if (set != null) { + set.remove(groupPropertyId); + } + return this; + } + + /** + * Selects the given group property id to be excluded from the report + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the group property id is null
    • + *
    • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if + * the group type id is null
    • + *
    + */ + public Builder excludeGroupProperty(GroupTypeId groupTypeId, GroupPropertyId groupPropertyId) { + if (groupPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + if (groupTypeId == null) { + throw new ContractException(GroupError.NULL_GROUP_TYPE_ID); + } + Set set = data.excludedProperties.get(groupTypeId); + if (set == null) { + set = new LinkedHashSet<>(); + data.excludedProperties.put(groupTypeId, set); + } + set.add(groupPropertyId); + set = data.includedProperties.get(groupTypeId); + if (set != null) { + set.remove(groupPropertyId); + } + return this; + } + + /** + * Sets the report label + * + * @throws ContractException {@linkplain ReportError#NULL_REPORT_LABEL} if the + * report label is null + */ + @Override + public Builder setReportLabel(ReportLabel reportLabel) { + super.setReportLabel(reportLabel); + return this; + } + + /** + * Sets the report period id + * + * @throws ContractException {@linkplain ReportError#NULL_REPORT_PERIOD} if the + * report period is null + */ + @Override + public Builder setReportPeriod(ReportPeriod reportPeriod) { + super.setReportPeriod(reportPeriod); + return this; + } + + } + + /** + * Returns a new instance of the builder class + */ + public static Builder builder() { + return new Builder(new Data()); + } + + @Override + public Builder getCloneBuilder() { + return new Builder(new Data(data)); + } + + /** + * Returns the included group property values for the given group type id + * + * @throws ContractException {@linkplain GroupError#NULL_GROUP_TYPE_ID} if the + * group type id is null + */ + public Set getIncludedProperties(GroupTypeId groupTypeId) { + if (groupTypeId == null) { + throw new ContractException(GroupError.NULL_GROUP_TYPE_ID); + } + Set result = new LinkedHashSet<>(); + Set set = data.includedProperties.get(groupTypeId); + if (set != null) { + result.addAll(set); + } + return result; + } + + /** + * Returns the included group property values for the given group type id + * + * @throws ContractException {@linkplain GroupError#NULL_GROUP_TYPE_ID} if the + * group type id is null + */ + public Set getGroupTypeIds() { + Set result = new LinkedHashSet<>(); + result.addAll(data.includedProperties.keySet()); + result.addAll(data.excludedProperties.keySet()); + return result; + } + + /** + * Returns the excluded group property values for the given group type id + * + * @throws ContractException {@linkplain GroupError#NULL_GROUP_TYPE_ID} if the + * group type id is null + */ + public Set getExcludedProperties(GroupTypeId groupTypeId) { + if (groupTypeId == null) { + throw new ContractException(GroupError.NULL_GROUP_TYPE_ID); + } + Set result = new LinkedHashSet<>(); + Set set = data.excludedProperties.get(groupTypeId); + if (set != null) { + result.addAll(set); + } + return result; + } + + public boolean getDefaultInclusionPolicy() { + return data.defaultInclusionPolicy; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof GroupPropertyReportPluginData)) { + return false; + } + GroupPropertyReportPluginData other = (GroupPropertyReportPluginData) obj; + if (data == null) { + if (other.data != null) { + return false; + } + } else if (!data.equals(other.data)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder2 = new StringBuilder(); + builder2.append("GroupPropertyReportPluginData [data="); + builder2.append(data); + builder2.append("]"); + return builder2.toString(); + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupConstructionInfo.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupConstructionInfo.java new file mode 100644 index 000000000..40063b5b2 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupConstructionInfo.java @@ -0,0 +1,121 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.support; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import net.jcip.annotations.Immutable; +import net.jcip.annotations.NotThreadSafe; +import util.errors.ContractException; + +/** + * Represents the information to add a group, but not its relationship to + * people. + */ +@Immutable +public final class GroupConstructionInfo { + private final Data data; + + private static class Data { + private GroupTypeId groupTypeId; + private Map propertyValues = new LinkedHashMap<>(); + + public Data() { + } + + public Data(Data data) { + groupTypeId = data.groupTypeId; + propertyValues.putAll(data.propertyValues); + } + } + + /** + * Returns the group Type of the group + */ + @SuppressWarnings("unchecked") + public T getGroupTypeId() { + return (T) data.groupTypeId; + } + + /** + * Returns a map of the group property values for the group + */ + public Map getPropertyValues() { + return Collections.unmodifiableMap(data.propertyValues); + } + + /* + * Hidden constructor + */ + private GroupConstructionInfo(Data data) { + this.data = data; + } + + public static Builder builder() { + return new Builder(); + } + + @NotThreadSafe + public static class Builder { + + private Builder() { + } + + private Data data = new Data(); + + private void validate() { + if (data.groupTypeId == null) { + throw new ContractException(GroupError.NULL_GROUP_TYPE_ID); + } + } + + /** + * Builds the {@link GroupConstructionInfo} from the collected data + * + * @throws ContractException {@linkplain GroupError#NULL_GROUP_TYPE_ID} if no + * group type id was collected + */ + public GroupConstructionInfo build() { + validate(); + return new GroupConstructionInfo(new Data(data)); + } + + /** + * Sets the group type id + * + * @throws ContractException if the group type id is null + */ + public Builder setGroupTypeId(GroupTypeId groupTypeId) { + if (groupTypeId == null) { + throw new ContractException(GroupError.NULL_GROUP_TYPE_ID); + } + data.groupTypeId = groupTypeId; + return this; + } + + /** + * Sets the group property value. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the group property id is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_VALUE} + * if the group property value is null
    • + *
    + */ + public Builder setGroupPropertyValue(GroupPropertyId groupPropertyId, Object groupPropertyValue) { + if (groupPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + if (groupPropertyValue == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_VALUE); + } + data.propertyValues.put(groupPropertyId, groupPropertyValue); + return this; + } + + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupError.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupError.java new file mode 100644 index 000000000..8c9fea88b --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupError.java @@ -0,0 +1,40 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.support; + +import util.errors.ContractError; +import util.errors.ContractException; + +/** + * An enumeration supporting {@link ContractException} that acts as a general + * description of the exception. + */ +public enum GroupError implements ContractError { + + NEGATIVE_GROUP_ID("group id is negative"), NEGATIVE_GROUP_COUNT("group count is negative"), + NEXT_GROUP_ID_TOO_SMALL("The next gropu id must exceed all extant group ids"), + NULL_GROUP_INITIALIZATION_DATA("Null group initialization data"), + NULL_GROUP_DATA_MANAGER("Null group data manager"), NULL_GROUP_PLUGIN_DATA("null groupsplugin data"), + NULL_GROUP_POPULATION_REPORT_PLUGIN_DATA("Null group population report plugin data"), + NULL_GROUP_PROPERTY_REPORT_PLUGIN_DATA("Null group property report plugin data"), + DUPLICATE_GROUP_MEMBERSHIP("Person was previously assigned to group"), + GROUP_MEMBERSHIP_ASYMMETRY("Person-group relationships are asymmetric"), + DUPLICATE_GROUP_TYPE("Duplicate group type"), + MALFORMED_GROUP_SAMPLE_WEIGHTING_FUNCTION( + "Data used to form an enumerated distribution for group sampling was malformed"), + NON_GROUP_MEMBERSHIP("Person is not currently assigned to group"), + NULL_GROUP_CONSTRUCTION_INFO("Null group construction info"), NULL_GROUP_ID("Null group id"), + NULL_GROUP_SAMPLER("Null group sampler"), NULL_GROUP_TYPE_ID("Null group type id"), + UNKNOWN_GROUP_ID("Unknown group id"), INCORRECT_GROUP_TYPE_ID("incorrect group type id for a group"), + UNKNOWN_GROUP_TYPE_ID("Unknown group type id"),; + + private final String description; + + private GroupError(final String description) { + this.description = description; + } + + @Override + public String getDescription() { + return description; + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupId.java new file mode 100644 index 000000000..d897cda9e --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupId.java @@ -0,0 +1,60 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.support; + +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * Identifier for all groups + */ +@Immutable +public final class GroupId implements Comparable { + + private final int id; + + /** + * Constructs the groupId + * + * @throws ContractException {@linkplain GroupError#NEGATIVE_GROUP_ID} + */ + public GroupId(int id) { + if (id < 0) { + throw new ContractException(GroupError.NEGATIVE_GROUP_ID); + } + this.id = id; + } + + public int getValue() { + return id; + } + + @Override + public int compareTo(GroupId groupId) { + return Integer.compare(id, groupId.id); + } + + @Override + public int hashCode() { + return id; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof GroupId)) { + return false; + } + GroupId other = (GroupId) obj; + if (id != other.id) { + return false; + } + return true; + } + + @Override + public String toString() { + return Integer.toString(id); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupLabeler.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupLabeler.java new file mode 100644 index 000000000..517684681 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupLabeler.java @@ -0,0 +1,130 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.support; + +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.groups.datamanagers.GroupsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupMembershipAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupMembershipRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.Labeler; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.LabelerSensitivity; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import util.errors.ContractException; + +/** + * A labeler for groups. The dimension of the labeler is + * {@linkplain GroupTypeId}, the events that stimulates a label update are + * {@linkplain GroupMembershipAdditionEvent} and + * {@linkplain GroupMembershipRemovalEvent} and the labeling function is + * composed from the given Function. + */ +public abstract class GroupLabeler implements Labeler { + + protected GroupLabeler() { + } + + protected abstract Object getLabelFromGroupTypeCountMap(GroupTypeCountMap groupTypeCountMap); + + private GroupsDataManager groupsDataManager; + + private Optional getPersonId(GroupMembershipAdditionEvent groupMembershipAdditionEvent) { + return Optional.of(groupMembershipAdditionEvent.personId()); + } + + private Optional getPersonId(GroupMembershipRemovalEvent groupMembershipRemovalEvent) { + return Optional.of(groupMembershipRemovalEvent.personId()); + } + + /** + * Returns a set of labeler sensitivitites for GroupMembershipAdditionEvent and + * GroupMembershipRemovalEvent. All group changes will effect the partition. + */ + @Override + public final Set> getLabelerSensitivities() { + Set> result = new LinkedHashSet<>(); + result.add(new LabelerSensitivity(GroupMembershipAdditionEvent.class, + this::getPersonId)); + result.add(new LabelerSensitivity(GroupMembershipRemovalEvent.class, + this::getPersonId)); + return result; + } + + /** + * Returns the label for the given person id + * + * @throws ContractException + *
      + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person id is unknown
    • + *
    + */ + @Override + public final Object getCurrentLabel(PartitionsContext partitionsContext, PersonId personId) { + if (groupsDataManager == null) { + groupsDataManager = partitionsContext.getDataManager(GroupsDataManager.class); + } + + GroupTypeCountMap.Builder groupTypeCountMapBuilder = GroupTypeCountMap.builder(); + for (GroupTypeId groupTypeId : groupsDataManager.getGroupTypeIds()) { + int count = groupsDataManager.getGroupCountForGroupTypeAndPerson(groupTypeId, personId); + groupTypeCountMapBuilder.setCount(groupTypeId, count); + } + GroupTypeCountMap groupTypeCountMap = groupTypeCountMapBuilder.build(); + return getLabelFromGroupTypeCountMap(groupTypeCountMap); + } + + /** + * Returns {@link GroupTypeId} class as the dimension. + */ + @Override + public final Object getId() { + return GroupTypeId.class; + } + + @Override + public final Object getPastLabel(PartitionsContext partitionsContext, Event event) { + if (groupsDataManager == null) { + groupsDataManager = partitionsContext.getDataManager(GroupsDataManager.class); + } + + PersonId personId; + GroupTypeId eventGroupTypeId; + int delta; + if (event instanceof GroupMembershipAdditionEvent) { + GroupMembershipAdditionEvent groupMembershipAdditionEvent = (GroupMembershipAdditionEvent) event; + personId = groupMembershipAdditionEvent.personId(); + GroupId groupId = groupMembershipAdditionEvent.groupId(); + eventGroupTypeId = groupsDataManager.getGroupType(groupId); + delta = -1; + } else { + GroupMembershipRemovalEvent groupMembershipRemovalEvent = (GroupMembershipRemovalEvent) event; + personId = groupMembershipRemovalEvent.personId(); + GroupId groupId = groupMembershipRemovalEvent.groupId(); + eventGroupTypeId = groupsDataManager.getGroupType(groupId); + delta = +1; + } + + GroupTypeCountMap.Builder groupTypeCountMapBuilder = GroupTypeCountMap.builder(); + for (GroupTypeId groupTypeId : groupsDataManager.getGroupTypeIds()) { + int count = groupsDataManager.getGroupCountForGroupTypeAndPerson(groupTypeId, personId); + if (groupTypeId.equals(eventGroupTypeId)) { + count += delta; + } + groupTypeCountMapBuilder.setCount(groupTypeId, count); + } + GroupTypeCountMap groupTypeCountMap = groupTypeCountMapBuilder.build(); + return getLabelFromGroupTypeCountMap(groupTypeCountMap); + } + + @Override + public String toString() { + return "GroupLabeler []"; + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupMemberFilter.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupMemberFilter.java new file mode 100644 index 000000000..8422c280c --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupMemberFilter.java @@ -0,0 +1,115 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.support; + +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.plugins.groups.datamanagers.GroupsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupMembershipAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupMembershipRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.FilterSensitivity; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters.Filter; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import util.errors.ContractException; + +public class GroupMemberFilter extends Filter { + final GroupId groupId; + private GroupsDataManager groupsDataManager; + + private void validateGroupIdNotNull(PartitionsContext partitionsContext, final GroupId groupId) { + if (groupId == null) { + throw new ContractException(GroupError.NULL_GROUP_ID); + } + } + + public GroupId getGroupId() { + return groupId; + } + + public GroupMemberFilter(final GroupId groupId) { + this.groupId = groupId; + } + + @Override + public void validate(PartitionsContext partitionsContext) { + validateGroupIdNotNull(partitionsContext, groupId); + } + + private Optional additionRequiresRefresh(PartitionsContext partitionsContext, + GroupMembershipAdditionEvent event) { + if (event.groupId().equals(groupId)) { + return Optional.of(event.personId()); + } + return Optional.empty(); + } + + private Optional removalRequiresRefresh(PartitionsContext partitionsContext, + GroupMembershipRemovalEvent event) { + if (event.groupId().equals(groupId)) { + return Optional.of(event.personId()); + } + return Optional.empty(); + + } + + @Override + public Set> getFilterSensitivities() { + Set> result = new LinkedHashSet<>(); + result.add(new FilterSensitivity(GroupMembershipAdditionEvent.class, + this::additionRequiresRefresh)); + result.add(new FilterSensitivity(GroupMembershipRemovalEvent.class, + this::removalRequiresRefresh)); + + return result; + } + + @Override + public boolean evaluate(PartitionsContext partitionsContext, PersonId personId) { + if (partitionsContext == null) { + throw new ContractException(NucleusError.NULL_SIMULATION_CONTEXT); + } + if (groupsDataManager == null) { + groupsDataManager = partitionsContext.getDataManager(GroupsDataManager.class); + } + return groupsDataManager.isPersonInGroup(personId, groupId); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((groupId == null) ? 0 : groupId.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof GroupMemberFilter)) { + return false; + } + GroupMemberFilter other = (GroupMemberFilter) obj; + if (groupId == null) { + if (other.groupId != null) { + return false; + } + } else if (!groupId.equals(other.groupId)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("GroupMemberFilter [groupId="); + builder.append(groupId); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupPropertyDefinitionInitialization.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupPropertyDefinitionInitialization.java new file mode 100644 index 000000000..92ac26445 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupPropertyDefinitionInitialization.java @@ -0,0 +1,204 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.support; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.commons.math3.util.Pair; + +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * A class for defining a group property with an associated property id and + * property values for extant groups. + */ +@Immutable +public final class GroupPropertyDefinitionInitialization { + + private static class Data { + GroupTypeId groupTypeId; + GroupPropertyId groupPropertyId; + PropertyDefinition propertyDefinition; + List> propertyValues = new ArrayList<>(); + + public Data() { + } + + public Data(Data data) { + groupTypeId = data.groupTypeId; + groupPropertyId = data.groupPropertyId; + propertyDefinition = data.propertyDefinition; + propertyValues.addAll(data.propertyValues); + } + } + + private final Data data; + + private GroupPropertyDefinitionInitialization(Data data) { + this.data = data; + } + + /** + * Returns a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder class for a GroupPropertyDefinitionInitialization + */ + public final static class Builder { + private Builder() { + } + + private Data data = new Data(); + + private void validate() { + if (data.propertyDefinition == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_DEFINITION); + } + + if (data.groupPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + + if (data.groupTypeId == null) { + throw new ContractException(GroupError.NULL_GROUP_TYPE_ID); + } + + Class type = data.propertyDefinition.getType(); + for (Pair pair : data.propertyValues) { + Object value = pair.getSecond(); + if (!type.isAssignableFrom(value.getClass())) { + String message = "Definition Type " + type.getName() + " is not compatible with value = " + value; + throw new ContractException(PropertyError.INCOMPATIBLE_VALUE, message); + } + } + } + + /** + * Constructs the PropertyDefinitionInitialization from the collected data + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_DEFINITION} + * if no property definition was assigned to the + * builder
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * no property id was assigned to the builder
    • + *
    • {@linkplain PropertyError#INCOMPATIBLE_VALUE} + * if a collected property value is incompatible with + * the property definition
    • + *
    • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if + * no group type id was assigned to the builder
    • + *
    + */ + public GroupPropertyDefinitionInitialization build() { + validate(); + return new GroupPropertyDefinitionInitialization(new Data(data)); + } + + /** + * Sets the group property id + * + * @throws ContractException {@linkplain PropertyError#NULL_PROPERTY_ID} if the + * property id is null + */ + public Builder setPropertyId(GroupPropertyId groupPropertyId) { + if (groupPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + data.groupPropertyId = groupPropertyId; + return this; + } + + /** + * Sets the group type id + * + * @throws ContractException {@linkplain GroupError#NULL_GROUP_TYPE_ID} if the + * group type id is null + */ + public Builder setGroupTypeId(GroupTypeId groupTypeId) { + if (groupTypeId == null) { + throw new ContractException(GroupError.NULL_GROUP_TYPE_ID); + } + data.groupTypeId = groupTypeId; + return this; + } + + /** + * Sets the property definition + * + * @throws ContractException {@linkplain PropertyError#NULL_PROPERTY_DEFINITION} + * if the property definition is null + */ + public Builder setPropertyDefinition(PropertyDefinition propertyDefinition) { + if (propertyDefinition == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_DEFINITION); + } + data.propertyDefinition = propertyDefinition; + return this; + } + + /** + * Adds a property value + * + * @throws ContractException + *
      + *
    • {@linkplain GroupError#NULL_GROUP_ID} if the + * group id is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_VALUE} + * if the property value is null
    • + *
    + */ + public Builder addPropertyValue(GroupId groupId, Object value) { + if (groupId == null) { + throw new ContractException(GroupError.NULL_GROUP_ID); + } + + if (value == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_VALUE); + } + + data.propertyValues.add(new Pair<>(groupId, value)); + return this; + } + + } + + /** + * Returns the (non-null)property id. + */ + public GroupPropertyId getPropertyId() { + return data.groupPropertyId; + } + + /** + * Returns the (non-null) group type id. + */ + public GroupTypeId getGroupTypeId() { + return data.groupTypeId; + } + + /** + * Returns the (non-null)property definition. + */ + public PropertyDefinition getPropertyDefinition() { + return data.propertyDefinition; + } + + /** + * Returns the list of (groupId,value) pairs collected by the builder in the + * order of their addition. All pairs have non-null entries and the values are + * compatible with the contained property definition. Duplicate assignments of + * values to the same group may be present. + */ + public List> getPropertyValues() { + return Collections.unmodifiableList(data.propertyValues); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupPropertyDimension.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupPropertyDimension.java new file mode 100644 index 000000000..2e80ea95e --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupPropertyDimension.java @@ -0,0 +1,223 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.support; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import gov.hhs.aspr.ms.gcm.nucleus.Dimension; +import gov.hhs.aspr.ms.gcm.nucleus.DimensionContext; +import gov.hhs.aspr.ms.gcm.plugins.groups.datamanagers.GroupsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.errors.ContractException; + +/** + * Dimension implementation for setting a group property to a list of values in + * a groups plugin data. + */ +public class GroupPropertyDimension implements Dimension { + private final Data data; + + private GroupPropertyDimension(Data data) { + this.data = data; + } + + private static class Data { + private GroupId groupId; + private GroupPropertyId groupPropertyId; + private List values = new ArrayList<>(); + + private Data() { + } + + private Data(Data data) { + groupPropertyId = data.groupPropertyId; + groupId = data.groupId; + values.addAll(data.values); + } + + @Override + public int hashCode() { + return Objects.hash(groupId, groupPropertyId, values); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Data other = (Data) obj; + return Objects.equals(groupId, other.groupId) && Objects.equals(groupPropertyId, other.groupPropertyId) + && Objects.equals(values, other.values); + } + + } + + /** + * Returns a new builder for GroupPropertyDimension + */ + public static Builder builder() { + return new Builder(new Data()); + } + + /** + * Builder class for GroupPropertyDimension + */ + public static class Builder { + + private Data data; + + private Builder(Data data) { + this.data = data; + } + + /** + * Returns the GroupPropertyDimension from the collected data. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the group property id was not assigned
    • + *
    • {@linkplain GroupError#NULL_GROUP_ID} if the + * groupId was not assigned
    • + *
    + */ + public GroupPropertyDimension build() { + validate(); + return new GroupPropertyDimension(new Data(data)); + } + + private void validate() { + if (data.groupId == null) { + throw new ContractException(GroupError.NULL_GROUP_ID); + } + + if (data.groupPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + } + + /** + * Sets the group id for the dimension. Defaults to null. + * + * @throws ContractException {@linkplain GroupError#NULL_GROUP_ID} if the + * groupId is null + */ + public Builder setGroupId(GroupId groupId) { + validateGroupId(groupId); + data.groupId = groupId; + return this; + } + + /** + * Sets the group property for the dimension. Defaults to null. + * + * @throws ContractException {@linkplain PropertyError#NULL_PROPERTY_ID} if the + * property id is null + */ + public Builder setGroupPropertyId(GroupPropertyId groupPropertyId) { + validateGroupPropertyId(groupPropertyId); + data.groupPropertyId = groupPropertyId; + return this; + } + + /** + * Adds a value to the dimension. + * + * @throws ContractException {@linkplain PropertyError#NULL_PROPERTY_VALUE} if + * the value is null + */ + public Builder addValue(Object value) { + validateValue(value); + data.values.add(value); + return this; + } + } + + @Override + public List getExperimentMetaData() { + List result = new ArrayList<>(); + result.add(data.groupPropertyId.toString()); + return result; + } + + @Override + public int levelCount() { + return data.values.size(); + } + + @Override + public List executeLevel(DimensionContext dimensionContext, int level) { + GroupsPluginData.Builder builder = dimensionContext.getPluginDataBuilder(GroupsPluginData.Builder.class); + Object value = data.values.get(level); + builder.setGroupPropertyValue(data.groupId, data.groupPropertyId, value); + List result = new ArrayList<>(); + result.add(value.toString()); + return result; + } + + /** + * Returns the group id for this dimension + */ + public GroupId getGroupId() { + return data.groupId; + } + + /** + * Returns the group property id for this dimension + */ + public GroupPropertyId getGroupPropertyId() { + return data.groupPropertyId; + } + + /** + * Returns the ordered list of group property values for this dimension + */ + public List getValues() { + return new ArrayList<>(data.values); + } + + private static void validateValue(Object value) { + if (value == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_VALUE); + } + } + + private static void validateGroupPropertyId(GroupPropertyId groupPropertyId) { + if (groupPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + } + + private static void validateGroupId(GroupId groupId) { + if (groupId == null) { + throw new ContractException(GroupError.NULL_GROUP_ID); + } + } + + @Override + public int hashCode() { + return Objects.hash(data); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + GroupPropertyDimension other = (GroupPropertyDimension) obj; + return Objects.equals(data, other.data); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupPropertyId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupPropertyId.java new file mode 100644 index 000000000..8450f358a --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupPropertyId.java @@ -0,0 +1,11 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.support; + +import net.jcip.annotations.ThreadSafe; + +/** + * Marker interface for group property identifiers + */ +@ThreadSafe +public interface GroupPropertyId { + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupPropertyValue.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupPropertyValue.java new file mode 100644 index 000000000..de3d5ff2e --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupPropertyValue.java @@ -0,0 +1,28 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.support; + +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.errors.ContractException; + +public record GroupPropertyValue(GroupPropertyId groupPropertyId, Object value) { + /** + * Creates the record. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the property id is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_VALUE} + * if the property value is null
    • + *
    + */ + public GroupPropertyValue { + + if (groupPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + + if (value == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_VALUE); + } + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupSampler.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupSampler.java new file mode 100644 index 000000000..fb468e792 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupSampler.java @@ -0,0 +1,86 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.support; + +import java.util.Optional; + +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.RandomNumberGeneratorId; + +public final class GroupSampler { + private final PersonId excludedPerson; + + private final RandomNumberGeneratorId randomNumberGeneratorId; + + private final GroupWeightingFunction weightingFunction; + + private GroupSampler(Data data) { + this.excludedPerson = data.excludedPerson; + this.weightingFunction = data.weightingFunction; + this.randomNumberGeneratorId = data.randomNumberGeneratorId; + } + + private static class Data { + + private PersonId excludedPerson; + + private RandomNumberGeneratorId randomNumberGeneratorId; + + private GroupWeightingFunction weightingFunction; + + public Data() { + } + + public Data(Data data) { + excludedPerson = data.excludedPerson; + + randomNumberGeneratorId = data.randomNumberGeneratorId; + + weightingFunction = data.weightingFunction; + + } + } + + public static Builder builder() { + return new Builder(); + } + + public final static class Builder { + Data data = new Data(); + + private Builder() { + + } + + public GroupSampler build() { + return new GroupSampler(new Data(data)); + } + + public Builder setExcludedPersonId(PersonId personId) { + data.excludedPerson = personId; + return this; + } + + public Builder setRandomNumberGeneratorId(RandomNumberGeneratorId randomNumberGeneratorId) { + data.randomNumberGeneratorId = randomNumberGeneratorId; + return this; + } + + public Builder setGroupWeightingFunction(GroupWeightingFunction weightingFunction) { + data.weightingFunction = weightingFunction; + return this; + } + + } + + public Optional getExcludedPerson() { + return Optional.ofNullable(excludedPerson); + } + + public Optional getRandomNumberGeneratorId() { + return Optional.ofNullable(randomNumberGeneratorId); + } + + public Optional getWeightingFunction() { + return Optional.ofNullable(weightingFunction); + } + +} diff --git a/gcm3/src/main/java/plugins/groups/support/GroupTypeCountMap.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupTypeCountMap.java similarity index 75% rename from gcm3/src/main/java/plugins/groups/support/GroupTypeCountMap.java rename to gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupTypeCountMap.java index 95cb0cc2f..2f907449f 100644 --- a/gcm3/src/main/java/plugins/groups/support/GroupTypeCountMap.java +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupTypeCountMap.java @@ -1,13 +1,13 @@ -package plugins.groups.support; +package gov.hhs.aspr.ms.gcm.plugins.groups.support; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.Partition; import net.jcip.annotations.Immutable; import net.jcip.annotations.NotThreadSafe; -import plugins.partitions.support.Partition; import util.errors.ContractException; /** @@ -15,19 +15,24 @@ * number of such groups a particular person is contained in. These are used to * match people in a {@link Partition} who are associated with some specific * numbers of groups of specific group types. - * - * @author Shawn Hatch - * */ @Immutable public final class GroupTypeCountMap { - private static class Scaffold { + private static class Data { private Map map = new LinkedHashMap<>(); + + public Data() { + } + + public Data(Data data) { + map.putAll(data.map); + } + } - private GroupTypeCountMap(Scaffold scaffold) { - map.putAll(scaffold.map); + private GroupTypeCountMap(Data data) { + map.putAll(data.map); groupTypeIds = Collections.unmodifiableSet(map.keySet()); } @@ -47,17 +52,16 @@ public int hashCode() { } /** - * Returns an unmodifiable set of the {@link GroupTypeId} values contained - * in this {@link GroupTypeCountMap} - * + * Returns an unmodifiable set of the {@link GroupTypeId} values contained in + * this {@link GroupTypeCountMap} */ public Set getGroupTypeIds() { return groupTypeIds; } /** - * Two {@link GroupTypeCountMap} objects are considered equal if the - * POSITIVE values associated with their group type ids are equal. + * Two {@link GroupTypeCountMap} objects are considered equal if the POSITIVE + * values associated with their group type ids are equal. */ @Override public boolean equals(Object obj) { @@ -108,53 +112,46 @@ public static Builder builder() { } /** - * Standard builder class for group type count maps. All inputs are - * optional. - * - * @author Shawn Hatch - * + * Standard builder class for group type count maps. All inputs are optional. */ @NotThreadSafe public static class Builder { - private Scaffold scaffold = new Scaffold(); + private Data data = new Data(); private Builder() { } public GroupTypeCountMap build() { - try { - return new GroupTypeCountMap(scaffold); - } finally { - scaffold = new Scaffold(); - } + return new GroupTypeCountMap(new Data(data)); } /** * Sets the count for the given group type id * * @throws ContractException - *
  • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if groupTypeId is null
  • - *
  • {@linkplain GroupError#NEGATIVE_GROUP_COUNT}if the count is negative
  • - * + *
      + *
    • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if + * groupTypeId is null
    • + *
    • {@linkplain GroupError#NEGATIVE_GROUP_COUNT}if + * the count is negative
    • + *
    */ public Builder setCount(GroupTypeId groupTypeId, int count) { if (groupTypeId == null) { throw new ContractException(GroupError.NULL_GROUP_TYPE_ID); } if (count < 0) { - throw new ContractException(GroupError.NEGATIVE_GROUP_COUNT); + throw new ContractException(GroupError.NEGATIVE_GROUP_COUNT); } - scaffold.map.put(groupTypeId, count); + data.map.put(groupTypeId, count); return this; } } /** - * Returns a standard string implementation of the form: - * - * GroupTypeCountMap [GROUP_TYPE_1=2, GROUP_TYPE_2=1] - * - * that includes only non-zero group type counts. + * Returns a standard string implementation of the form: GroupTypeCountMap + * [GROUP_TYPE_1=2, GROUP_TYPE_2=1] that includes only non-zero group type + * counts. */ @Override public String toString() { diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupTypeId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupTypeId.java new file mode 100644 index 000000000..883c272b6 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupTypeId.java @@ -0,0 +1,16 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.support; + +import net.jcip.annotations.ThreadSafe; + +/** + * Marker interface for group type identifiers + */ +@ThreadSafe +public interface GroupTypeId { + + @Override + public boolean equals(Object obj); + + @Override + public int hashCode(); +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupTypesForPersonFilter.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupTypesForPersonFilter.java new file mode 100644 index 000000000..e2ddd8527 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupTypesForPersonFilter.java @@ -0,0 +1,120 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.support; + +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.plugins.groups.datamanagers.GroupsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupMembershipAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupMembershipRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.Equality; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.FilterSensitivity; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters.Filter; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import util.errors.ContractException; + +public final class GroupTypesForPersonFilter extends Filter { + + private final Equality equality; + private final int groupTypeCount; + private GroupsDataManager groupsDataManager; + + private void validateEquality(final PartitionsContext partitionsContext, final Equality equality) { + if (equality == null) { + throw new ContractException(PartitionError.NULL_EQUALITY_OPERATOR); + } + } + + public Equality getEquality() { + return equality; + } + + public int getGroupTypeCount() { + return groupTypeCount; + } + + public GroupTypesForPersonFilter(final Equality equality, final int groupTypeCount) { + this.equality = equality; + this.groupTypeCount = groupTypeCount; + } + + @Override + public void validate(PartitionsContext partitionsContext) { + validateEquality(partitionsContext, equality); + } + + @Override + public boolean evaluate(PartitionsContext partitionsContext, PersonId personId) { + if (partitionsContext == null) { + throw new ContractException(NucleusError.NULL_SIMULATION_CONTEXT); + } + if (groupsDataManager == null) { + groupsDataManager = partitionsContext.getDataManager(GroupsDataManager.class); + } + final int count = groupsDataManager.getGroupTypeCountForPersonId(personId); + return equality.isCompatibleComparisonValue(Integer.compare(count, groupTypeCount)); + } + + private Optional additionRequiresRefresh(PartitionsContext partitionsContext, + GroupMembershipAdditionEvent event) { + return Optional.of(event.personId()); + } + + private Optional removalRequiresRefresh(PartitionsContext partitionsContext, + GroupMembershipRemovalEvent event) { + return Optional.of(event.personId()); + } + + @Override + public Set> getFilterSensitivities() { + Set> result = new LinkedHashSet<>(); + result.add(new FilterSensitivity(GroupMembershipAdditionEvent.class, + this::additionRequiresRefresh)); + result.add(new FilterSensitivity(GroupMembershipRemovalEvent.class, + this::removalRequiresRefresh)); + + return result; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((equality == null) ? 0 : equality.hashCode()); + result = prime * result + groupTypeCount; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof GroupTypesForPersonFilter)) { + return false; + } + GroupTypesForPersonFilter other = (GroupTypesForPersonFilter) obj; + if (equality != other.equality) { + return false; + } + if (groupTypeCount != other.groupTypeCount) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("GroupTypesForPersonFilter [equality="); + builder.append(equality); + builder.append(", groupTypeCount="); + builder.append(groupTypeCount); + builder.append("]"); + return builder.toString(); + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupWeightingFunction.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupWeightingFunction.java new file mode 100644 index 000000000..0e1235769 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupWeightingFunction.java @@ -0,0 +1,17 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.support; + +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; + +/** + * A functional interface for selecting people from a group based on assigning a + * weighting value to a person. + */ +public interface GroupWeightingFunction { + /** + * Returns a non-negative, finite and stable value for the given inputs. + * Repeated invocations with the same arguments should return the same value + * while no mutations to simulation state have taken place. The person will be a + * member of the group. + */ + public double getWeight(GroupsContext groupsContext, PersonId personId, GroupId groupId); +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupsContext.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupsContext.java new file mode 100644 index 000000000..855004d57 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupsContext.java @@ -0,0 +1,31 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.support; + +import gov.hhs.aspr.ms.gcm.nucleus.DataManager; +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import util.errors.ContractException; + +/** + * A limited context that grants access to the simulation via the groups data + * manager's own data manager context. + */ +public interface GroupsContext { + + /** + * Returns the data manager from the given class reference + * + * @throws ContractException + *
      + *
    • {@linkplain NucleusError#NULL_DATA_MANAGER_CLASS} + * if data manager class is null
    • + *
    • {@linkplain NucleusError#AMBIGUOUS_DATA_MANAGER_CLASS} + * if more than one data manager matches the given + * class
    • + *
    + */ + public T getDataManager(Class dataManagerClass); + + /** + * Returns the current time in the simulation + */ + public double getTime(); +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupsForPersonAndGroupTypeFilter.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupsForPersonAndGroupTypeFilter.java new file mode 100644 index 000000000..6fd35dfe0 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupsForPersonAndGroupTypeFilter.java @@ -0,0 +1,163 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.support; + +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.plugins.groups.datamanagers.GroupsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupMembershipAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupMembershipRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.Equality; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.FilterSensitivity; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters.Filter; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import util.errors.ContractException; + +public final class GroupsForPersonAndGroupTypeFilter extends Filter { + private final GroupTypeId groupTypeId; + private final Equality equality; + private final int groupCount; + private GroupsDataManager groupsDataManager; + + private void validateEquality(final PartitionsContext partitionsContext, final Equality equality) { + if (equality == null) { + throw new ContractException(PartitionError.NULL_EQUALITY_OPERATOR); + } + } + + private void validateGroupTypeId(final PartitionsContext partitionsContext, final GroupTypeId groupTypeId) { + if (groupTypeId == null) { + throw new ContractException(GroupError.NULL_GROUP_TYPE_ID); + } + + if (groupsDataManager == null) { + groupsDataManager = partitionsContext.getDataManager(GroupsDataManager.class); + } + + if (!groupsDataManager.groupTypeIdExists(groupTypeId)) { + throw new ContractException(GroupError.UNKNOWN_GROUP_TYPE_ID, groupTypeId); + } + } + + public GroupTypeId getGroupTypeId() { + return groupTypeId; + } + + public Equality getEquality() { + return equality; + } + + public int getGroupCount() { + return groupCount; + } + + public GroupsForPersonAndGroupTypeFilter(final GroupTypeId groupTypeId, final Equality equality, + final int groupCount) { + this.equality = equality; + this.groupCount = groupCount; + this.groupTypeId = groupTypeId; + } + + @Override + public void validate(PartitionsContext partitionsContext) { + validateEquality(partitionsContext, equality); + validateGroupTypeId(partitionsContext, groupTypeId); + } + + @Override + public boolean evaluate(PartitionsContext partitionsContext, PersonId personId) { + if (groupsDataManager == null) { + groupsDataManager = partitionsContext.getDataManager(GroupsDataManager.class); + } + final int count = groupsDataManager.getGroupCountForGroupTypeAndPerson(groupTypeId, personId); + return evaluate(count); + } + + private boolean evaluate(int count) { + return equality.isCompatibleComparisonValue(Integer.compare(count, groupCount)); + } + + private Optional additionRequiresRefresh(PartitionsContext partitionsContext, + GroupMembershipAdditionEvent event) { + if (groupsDataManager == null) { + groupsDataManager = partitionsContext.getDataManager(GroupsDataManager.class); + } + if (groupsDataManager.getGroupType(event.groupId()).equals(groupTypeId)) { + return Optional.of(event.personId()); + } + return Optional.empty(); + } + + private Optional removalRequiresRefresh(PartitionsContext partitionsContext, + GroupMembershipRemovalEvent event) { + if (groupsDataManager == null) { + groupsDataManager = partitionsContext.getDataManager(GroupsDataManager.class); + } + if (groupsDataManager.getGroupType(event.groupId()).equals(groupTypeId)) { + return Optional.of(event.personId()); + } + return Optional.empty(); + } + + @Override + public Set> getFilterSensitivities() { + Set> result = new LinkedHashSet<>(); + result.add(new FilterSensitivity(GroupMembershipAdditionEvent.class, + this::additionRequiresRefresh)); + result.add(new FilterSensitivity(GroupMembershipRemovalEvent.class, + this::removalRequiresRefresh)); + + return result; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((equality == null) ? 0 : equality.hashCode()); + result = prime * result + groupCount; + result = prime * result + ((groupTypeId == null) ? 0 : groupTypeId.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof GroupsForPersonAndGroupTypeFilter)) { + return false; + } + GroupsForPersonAndGroupTypeFilter other = (GroupsForPersonAndGroupTypeFilter) obj; + if (equality != other.equality) { + return false; + } + if (groupCount != other.groupCount) { + return false; + } + if (groupTypeId == null) { + if (other.groupTypeId != null) { + return false; + } + } else if (!groupTypeId.equals(other.groupTypeId)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("GroupsForPersonAndGroupTypeFilter [groupTypeId="); + builder.append(groupTypeId); + builder.append(", equality="); + builder.append(equality); + builder.append(", groupCount="); + builder.append(groupCount); + builder.append("]"); + return builder.toString(); + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupsForPersonFilter.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupsForPersonFilter.java new file mode 100644 index 000000000..82ac9241f --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/GroupsForPersonFilter.java @@ -0,0 +1,120 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.support; + +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.plugins.groups.datamanagers.GroupsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupMembershipAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupMembershipRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.Equality; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.FilterSensitivity; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters.Filter; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import util.errors.ContractException; + +public class GroupsForPersonFilter extends Filter { + + private final Equality equality; + private final int groupCount; + private GroupsDataManager groupsDataManager; + + private void validateEquality(final PartitionsContext partitionsContext, final Equality equality) { + if (equality == null) { + throw new ContractException(PartitionError.NULL_EQUALITY_OPERATOR); + } + } + + public Equality getEquality() { + return equality; + } + + public int getGroupCount() { + return groupCount; + } + + public GroupsForPersonFilter(final Equality equality, final int groupCount) { + this.equality = equality; + this.groupCount = groupCount; + } + + @Override + public void validate(PartitionsContext partitionsContext) { + validateEquality(partitionsContext, equality); + } + + @Override + public boolean evaluate(PartitionsContext partitionsContext, PersonId personId) { + if (partitionsContext == null) { + throw new ContractException(NucleusError.NULL_SIMULATION_CONTEXT); + } + if (groupsDataManager == null) { + groupsDataManager = partitionsContext.getDataManager(GroupsDataManager.class); + } + final int count = groupsDataManager.getGroupCountForPerson(personId); + return equality.isCompatibleComparisonValue(Integer.compare(count, groupCount)); + } + + private Optional additionRequiresRefresh(PartitionsContext partitionsContext, + GroupMembershipAdditionEvent event) { + return Optional.of(event.personId()); + } + + private Optional removalRequiresRefresh(PartitionsContext partitionsContext, + GroupMembershipRemovalEvent event) { + return Optional.of(event.personId()); + } + + @Override + public Set> getFilterSensitivities() { + Set> result = new LinkedHashSet<>(); + result.add(new FilterSensitivity(GroupMembershipAdditionEvent.class, + this::additionRequiresRefresh)); + result.add(new FilterSensitivity(GroupMembershipRemovalEvent.class, + this::removalRequiresRefresh)); + + return result; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((equality == null) ? 0 : equality.hashCode()); + result = prime * result + groupCount; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof GroupsForPersonFilter)) { + return false; + } + GroupsForPersonFilter other = (GroupsForPersonFilter) obj; + if (equality != other.equality) { + return false; + } + if (groupCount != other.groupCount) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("GroupsForPersonFilter [equality="); + builder.append(equality); + builder.append(", groupCount="); + builder.append(groupCount); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/testsupport/GroupsTestPluginFactory.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/testsupport/GroupsTestPluginFactory.java new file mode 100644 index 000000000..b408c1df5 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/testsupport/GroupsTestPluginFactory.java @@ -0,0 +1,388 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.testsupport; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.function.Consumer; + +import org.apache.commons.math3.random.RandomGenerator; +import org.apache.commons.math3.util.FastMath; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.groups.GroupsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.groups.datamanagers.GroupsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.groups.reports.GroupPopulationReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.groups.reports.GroupPropertyReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupError; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupId; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePlugin; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeoplePluginData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonRange; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.StochasticsError; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; +import util.wrappers.MultiKey; + +/** + * A static test support class for the {@linkplain GroupsPlugin}. Provides + * convenience methods for obtaining standarized PluginData for the listed + * Plugin. + *

    + * Also contains factory methods to obtain a list of plugins that is the minimal + * set needed to adequately test this Plugin that can be utilized with + *

    + * {@link TestSimulation#execute} + */ +public final class GroupsTestPluginFactory { + + private GroupsTestPluginFactory() { + } + + private static class Data { + private GroupPopulationReportPluginData groupPopulationReportPluginData; + private GroupPropertyReportPluginData groupPropertyReportPluginData; + private GroupsPluginData groupsPluginData; + private PeoplePluginData peoplePluginData; + private StochasticsPluginData stochasticsPluginData; + private TestPluginData testPluginData; + + private Data(int initialPopulation, double expectedGroupsPerPerson, double expectedPeoplePerGroup, long seed, + TestPluginData testPluginData) { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + + this.peoplePluginData = getStandardPeoplePluginData(initialPopulation); + this.groupsPluginData = getStandardGroupsPluginData(expectedGroupsPerPerson, expectedPeoplePerGroup, + this.peoplePluginData.getPersonIds(), randomGenerator.nextLong()); + this.stochasticsPluginData = getStandardStochasticsPluginData(randomGenerator.nextLong()); + this.testPluginData = testPluginData; + } + } + + /** + * Factory class that facilitates the building of {@linkplain PluginData} with + * the various setter methods. + */ + public static class Factory { + private Data data; + + private Factory(Data data) { + this.data = data; + } + + /** + * Returns a list of plugins containing a Groups, People, Stocastics and a Test + * Plugin built from the contributed PluginDatas. + *
      + *
    • GroupsPlugin is defaulted to one formed from + * {@link GroupsTestPluginFactory#getStandardGroupsPluginData}
    • + *
    • PeoplePlugin is defaulted to one formed from + * {@link GroupsTestPluginFactory#getStandardPeoplePluginData}
    • + *
    • StochasticsPlugin is defaulted to one formed from + * {@link GroupsTestPluginFactory#getStandardStochasticsPluginData}
    • + *
    • TestPlugin is formed from the TestPluginData passed into + * {@link GroupsTestPluginFactory#factory}
    • + *
    + */ + public List getPlugins() { + List pluginsToAdd = new ArrayList<>(); + GroupsPlugin.Builder groupsPluginBuilder = GroupsPlugin.builder(); + groupsPluginBuilder.setGroupsPluginData(this.data.groupsPluginData); + if (data.groupPopulationReportPluginData != null) { + groupsPluginBuilder.setGroupPopulationReportPluginData(data.groupPopulationReportPluginData); + } + if (data.groupPropertyReportPluginData != null) { + groupsPluginBuilder.setGroupPropertyReportPluginData(data.groupPropertyReportPluginData); + } + + Plugin groupPlugin = groupsPluginBuilder.getGroupsPlugin(); + + Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(this.data.peoplePluginData); + + Plugin stochasticPlugin = StochasticsPlugin.getStochasticsPlugin(this.data.stochasticsPluginData); + + Plugin testPlugin = TestPlugin.getTestPlugin(this.data.testPluginData); + + pluginsToAdd.add(groupPlugin); + pluginsToAdd.add(peoplePlugin); + pluginsToAdd.add(stochasticPlugin); + pluginsToAdd.add(testPlugin); + + return pluginsToAdd; + } + + /** + * Sets the {@link GroupsPluginData} in this Factory. This explicit instance of + * pluginData will be used to create a GroupsPlugin + * + * @throws ContractException {@linkplain GroupError#NULL_GROUP_PLUGIN_DATA} if + * the passed in pluginData is null + */ + public Factory setGroupsPluginData(GroupsPluginData groupsPluginData) { + if (groupsPluginData == null) { + throw new ContractException(GroupError.NULL_GROUP_PLUGIN_DATA); + } + this.data.groupsPluginData = groupsPluginData; + return this; + } + + /** + * Sets the {@link GroupPopulationReportPluginData} in this Factory. This + * explicit instance of pluginData will be used to create a GroupsPlugin + * + * @throws ContractException {@linkplain GroupError#NULL_GROUP_POPULATION_REPORT_PLUGIN_DATA} + * if the passed in pluginData is null + */ + public Factory setGroupPopulationReportPluginData( + GroupPopulationReportPluginData groupPopulationReportPluginData) { + if (groupPopulationReportPluginData == null) { + throw new ContractException(GroupError.NULL_GROUP_POPULATION_REPORT_PLUGIN_DATA); + } + this.data.groupPopulationReportPluginData = groupPopulationReportPluginData; + return this; + } + + /** + * Sets the {@link GroupPopulationReportPluginData} in this Factory. This + * explicit instance of pluginData will be used to create a GroupsPlugin + * + * @throws ContractException {@linkplain GroupError#NULL_GROUP_PROPERTY_REPORT_PLUGIN_DATA} + * if the passed in pluginData is null + */ + public Factory setGroupPropertyReportPluginData(GroupPropertyReportPluginData groupPropertyReportPluginData) { + if (groupPropertyReportPluginData == null) { + throw new ContractException(GroupError.NULL_GROUP_PROPERTY_REPORT_PLUGIN_DATA); + } + this.data.groupPropertyReportPluginData = groupPropertyReportPluginData; + return this; + } + + /** + * Sets the {@link PeoplePluginData} in this Factory. This explicit instance of + * pluginData will be used to create a PeoplePlugin + * + * @throws ContractException {@linkplain PersonError#NULL_PEOPLE_PLUGIN_DATA} if + * the passed in pluginData is null + */ + public Factory setPeoplePluginData(PeoplePluginData peoplePluginData) { + if (peoplePluginData == null) { + throw new ContractException(PersonError.NULL_PEOPLE_PLUGIN_DATA); + } + this.data.peoplePluginData = peoplePluginData; + return this; + } + + /** + * Sets the {@link StochasticsPluginData} in this Factory. This explicit + * instance of pluginData will be used to create a StochasticsPlugin + * + * @throws ContractException {@linkplain StochasticsError#NULL_STOCHASTICS_PLUGIN_DATA} + * if the passed in pluginData is null + */ + public Factory setStochasticsPluginData(StochasticsPluginData stochasticsPluginData) { + if (stochasticsPluginData == null) { + throw new ContractException(StochasticsError.NULL_STOCHASTICS_PLUGIN_DATA); + } + this.data.stochasticsPluginData = stochasticsPluginData; + return this; + } + + } + + /** + * Returns a Factory that facilitates the creation of a minimal set of plugins + * needed to adequately test the {@link GroupsPlugin} by generating: + *
      + *
    • {@link GroupsPluginData}
    • + *
    • {@link PeoplePluginData}
    • + *
    • {@link StochasticsPluginData}
    • + *
    + * either directly (by default) via + *
      + *
    • {@link #getStandardGroupsPluginData}, + *
    • {@link #getStandardPeoplePluginData}, + *
    • {@link #getStandardStochasticsPluginData}
    • + *
    + * or explicitly set via + *
      + *
    • {@link Factory#setGroupsPluginData}, + *
    • {@link Factory#setPeoplePluginData}, + *
    • {@link Factory#setStochasticsPluginData}
    • + *
    + * via the {@link Factory#getPlugins()} method. + * + * @throws ContractException {@linkplain NucleusError#NULL_PLUGIN_DATA} if + * testPluginData is null + */ + public static Factory factory(int initialPopulation, double expectedGroupsPerPerson, double expectedPeoplePerGroup, + long seed, TestPluginData testPluginData) { + if (testPluginData == null) { + throw new ContractException(NucleusError.NULL_PLUGIN_DATA); + } + return new Factory( + new Data(initialPopulation, expectedGroupsPerPerson, expectedPeoplePerGroup, seed, testPluginData)); + } + + /** + * Creates a Factory that facilitates the creation of a minimal set of plugins + * needed to adequately test the {@link GroupsPlugin} by generating: + *
      + *
    • {@link GroupsPluginData}
    • + *
    • {@link PeoplePluginData}
    • + *
    • {@link StochasticsPluginData}
    • + *
    + * either directly (by default) via + *
      + *
    • {@link #getStandardGroupsPluginData}, + *
    • {@link #getStandardPeoplePluginData}, + *
    • {@link #getStandardStochasticsPluginData}
    • + *
    + * or explicitly set via + *
      + *
    • {@link Factory#setGroupsPluginData}, + *
    • {@link Factory#setPeoplePluginData}, + *
    • {@link Factory#setStochasticsPluginData}
    • + *
    + * via the {@link Factory#getPlugins()} method. + * + * @throws ContractException {@linkplain NucleusError#NULL_ACTOR_CONTEXT_CONSUMER} + * if consumer is null + */ + public static Factory factory(int initialPopulation, double expectedGroupsPerPerson, double expectedPeoplePerGroup, + long seed, Consumer consumer) { + if (consumer == null) { + throw new ContractException(NucleusError.NULL_ACTOR_CONTEXT_CONSUMER); + } + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, consumer)); + TestPluginData testPluginData = pluginBuilder.build(); + return factory(initialPopulation, expectedGroupsPerPerson, expectedPeoplePerGroup, seed, testPluginData); + } + + /** + * Returns a standardized GroupsPluginData that is minimally adequate for + * testing the GroupsPlugin The resulting GroupsPluginData will include: + *
      + *
    • Every GroupTypeId included in {@link TestGroupTypeId}
    • + *
    • Every GroupPropertyId included in {@link TestGroupPropertyId} + *
        + *
      • along with the groupTypeId and propertyDefinition for each
      • + *
      + *
    • A number of groups equal to the passed in groupCount + *
        + *
      • each group will get a random groupTypeId based on a RandomGenerator + * seeded by the passed in seed
      • + *
      • every GroupPropertyId included in {@link TestGroupPropertyId} with a + * randomPropertyValue obtained from each based on the same RandomGenerator
      • + *
      + *
    • an average group membership based on the passed in membershipCount and + * passed in people. + *
        + *
      • This is determined based on the above RandomGenerator. + *
      + *
    + */ + public static GroupsPluginData getStandardGroupsPluginData(double expectedGroupsPerPerson, + double expectedPeoplePerGroup, List people, long seed) { + + int membershipCount = (int) FastMath.round(people.size() * expectedGroupsPerPerson); + int groupCount = (int) FastMath.round(membershipCount / expectedPeoplePerGroup); + membershipCount = FastMath.min(membershipCount, groupCount * people.size()); + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + GroupsPluginData.Builder groupBuilder = GroupsPluginData.builder(); + + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + groupBuilder.addGroupTypeId(testGroupTypeId); + } + + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { + groupBuilder.defineGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId, + testGroupPropertyId.getPropertyDefinition()); + + } + + List groups = new ArrayList<>(); + + TestGroupTypeId testGroupTypeId = TestGroupTypeId.GROUP_TYPE_1; + for (int i = 0; i < groupCount; i++) { + GroupId groupId = new GroupId(i); + groups.add(groupId); + groupBuilder.addGroup(groupId, testGroupTypeId); + + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId + .getShuffledTestGroupPropertyIds(testGroupTypeId, randomGenerator)) { + PropertyDefinition propertyDefinition = testGroupPropertyId.getPropertyDefinition(); + boolean hasDefaultValue = propertyDefinition.getDefaultValue().isPresent(); + boolean setValue = randomGenerator.nextBoolean(); + + if (!hasDefaultValue || setValue) { + groupBuilder.setGroupPropertyValue(groupId, testGroupPropertyId, + testGroupPropertyId.getRandomPropertyValue(randomGenerator)); + } + } + testGroupTypeId = testGroupTypeId.next(); + } + + List groupMemeberships = new ArrayList<>(); + for (PersonId personId : people) { + for (GroupId groupId : groups) { + groupMemeberships.add(new MultiKey(groupId, personId)); + } + } + Collections.shuffle(groupMemeberships, new Random(randomGenerator.nextLong())); + + for (int i = 0; i < membershipCount; i++) { + MultiKey multiKey = groupMemeberships.get(i); + GroupId groupId = multiKey.getKey(0); + PersonId personId = multiKey.getKey(1); + groupBuilder.associatePersonToGroup(groupId, personId); + } + return groupBuilder.build(); + } + + /** + * Returns a standardized PeoplePluginData that is minimally adequate for + * testing the GroupsPlugin The resulting PeoplePluginData will include: + *
      + *
    • a number of people equal to the passed in intialPopulation
    • + *
    + */ + public static PeoplePluginData getStandardPeoplePluginData(int initialPopulation) { + PeoplePluginData.Builder peopleBuilder = PeoplePluginData.builder(); + + if (initialPopulation > 0) { + peopleBuilder.addPersonRange(new PersonRange(0, initialPopulation - 1)); + } + return peopleBuilder.build(); + } + + /** + * Returns a standardized StochasticsPluginData that is minimally adequate for + * testing the GroupsPlugin The resulting StochasticsPluginData will include: + *
      + *
    • a seed based on the nextLong of a RandomGenerator seeded from the passed + * in seed
    • + *
    + */ + public static StochasticsPluginData getStandardStochasticsPluginData(long seed) { + WellState wellState = WellState.builder().setSeed(seed).build(); + return StochasticsPluginData.builder().setMainRNGState(wellState).build(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/testsupport/TestAuxiliaryGroupPropertyId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/testsupport/TestAuxiliaryGroupPropertyId.java new file mode 100644 index 000000000..8121642df --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/testsupport/TestAuxiliaryGroupPropertyId.java @@ -0,0 +1,155 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.testsupport; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; + +public enum TestAuxiliaryGroupPropertyId implements GroupPropertyId { + + GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK(TestAuxiliaryGroupTypeId.GROUP_AUX_TYPE_1, // + PropertyDefinition.builder()// + .setType(Boolean.class)// + .setDefaultValue(false)// + .setPropertyValueMutability(true)// + .build()), // + GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK(TestAuxiliaryGroupTypeId.GROUP_AUX_TYPE_1, // + PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(0)// + .setPropertyValueMutability(true)// + .build() // + ), // + GROUP_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK(TestAuxiliaryGroupTypeId.GROUP_AUX_TYPE_1, // + PropertyDefinition.builder()// + .setType(Double.class)// + .setDefaultValue(0.0)// + .setPropertyValueMutability(true)// + .build() // + ), // + GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK(TestAuxiliaryGroupTypeId.GROUP_AUX_TYPE_2, // + PropertyDefinition.builder()// + .setType(Boolean.class)// + .setDefaultValue(false)// + .setPropertyValueMutability(true)// + .build() // + ), // + + GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK(TestAuxiliaryGroupTypeId.GROUP_AUX_TYPE_2, // + PropertyDefinition.builder()// + .setType(Integer.class)// + // .setDefaultValue(0)// + .setPropertyValueMutability(true)// + .build() // + ), // + GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK(TestAuxiliaryGroupTypeId.GROUP_AUX_TYPE_2, // + PropertyDefinition.builder()// + .setType(Double.class)// + .setDefaultValue(0.0)// + .setPropertyValueMutability(true)// + .build() // + ), // + GROUP_PROPERTY_3_1_BOOLEAN_IMMUTABLE_NO_TRACK(TestAuxiliaryGroupTypeId.GROUP_AUX_TYPE_3, // + PropertyDefinition.builder()// + .setType(Boolean.class)// + .setDefaultValue(false)// + .setPropertyValueMutability(false)// + .build() // + ), // + GROUP_PROPERTY_3_2_INTEGER_IMMUTABLE_NO_TRACK(TestAuxiliaryGroupTypeId.GROUP_AUX_TYPE_3, // + PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(0)// + .setPropertyValueMutability(false)// + .build() // + ), // + GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK(TestAuxiliaryGroupTypeId.GROUP_AUX_TYPE_3, // + PropertyDefinition.builder()// + .setType(Double.class)// + .setDefaultValue(0.0)// + .setPropertyValueMutability(false)// + .build() // + );// + + private final PropertyDefinition propertyDefinition; + private final TestAuxiliaryGroupTypeId testAuxiliaryGroupTypeId; + + /** + * Returns the property definition associated with this member + */ + public PropertyDefinition getPropertyDefinition() { + return propertyDefinition; + } + + private TestAuxiliaryGroupPropertyId(TestAuxiliaryGroupTypeId testAuxiliaryGroupTypeId, + PropertyDefinition propertyDefinition) { + this.testAuxiliaryGroupTypeId = testAuxiliaryGroupTypeId; + this.propertyDefinition = propertyDefinition; + } + + /** + * Returns the TestGroupTypeId that should be the type associated with the + * property + */ + public TestAuxiliaryGroupTypeId getTestGroupTypeId() { + return testAuxiliaryGroupTypeId; + } + + /** + * Returns the TestAuxiliaryGroupPropertyId associated with the given + * TestAuxiliaryGroupTypeId Preconditions: The TestAuxiliaryGroupTypeId should + * not be null + */ + public static Set getTestGroupPropertyIds( + TestAuxiliaryGroupTypeId testAuxiliaryGroupTypeId) { + Set result = new LinkedHashSet<>(); + for (TestAuxiliaryGroupPropertyId testGroupPropertyId : TestAuxiliaryGroupPropertyId.values()) { + if (testGroupPropertyId.testAuxiliaryGroupTypeId == testAuxiliaryGroupTypeId) { + result.add(testGroupPropertyId); + } + } + return result; + } + + /** + * Returns a unique GroupPropertyId instance that is not a member of this + * enumeration + */ + public static GroupPropertyId getUnknownGroupPropertyId() { + return new GroupPropertyId() { + }; + } + + /** + * Returns a randomly selected value that is compatible with this member's + * associated property definition. + */ + public Object getRandomPropertyValue(final RandomGenerator randomGenerator) { + switch (this) { + case GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK: + return randomGenerator.nextBoolean(); + case GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK: + return randomGenerator.nextInt(); + case GROUP_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK: + return randomGenerator.nextDouble(); + case GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK: + return randomGenerator.nextBoolean(); + case GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK: + return randomGenerator.nextInt(); + case GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK: + return randomGenerator.nextDouble(); + case GROUP_PROPERTY_3_1_BOOLEAN_IMMUTABLE_NO_TRACK: + return randomGenerator.nextBoolean(); + case GROUP_PROPERTY_3_2_INTEGER_IMMUTABLE_NO_TRACK: + return randomGenerator.nextInt(); + case GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK: + return randomGenerator.nextDouble(); + default: + throw new RuntimeException("unhandled case: " + this); + + } + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/testsupport/TestAuxiliaryGroupTypeId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/testsupport/TestAuxiliaryGroupTypeId.java new file mode 100644 index 000000000..79c692509 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/testsupport/TestAuxiliaryGroupTypeId.java @@ -0,0 +1,45 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.testsupport; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupTypeId; + +/** + * Enumeration of GroupTypeId to support unit testing + */ +public enum TestAuxiliaryGroupTypeId implements GroupTypeId { + GROUP_AUX_TYPE_1, GROUP_AUX_TYPE_2, GROUP_AUX_TYPE_3; + + /** + * Returns a randomly selected member of this enumeration. Precondition: The + * random generator must not be null + */ + public static TestAuxiliaryGroupTypeId getRandomGroupTypeId(final RandomGenerator randomGenerator) { + return TestAuxiliaryGroupTypeId.values()[randomGenerator.nextInt(TestAuxiliaryGroupTypeId.values().length)]; + } + + public static int size() { + return values().length; + } + + private TestAuxiliaryGroupTypeId next; + + /** + * Returns the next member of this enumeration + */ + public TestAuxiliaryGroupTypeId next() { + if (next == null) { + next = TestAuxiliaryGroupTypeId.values()[(ordinal() + 1) % TestAuxiliaryGroupTypeId.values().length]; + } + return next; + } + + /** + * Returns a new {@link GroupTypeId} instance. + */ + public static GroupTypeId getUnknownGroupTypeId() { + return new GroupTypeId() { + }; + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/testsupport/TestGroupPropertyId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/testsupport/TestGroupPropertyId.java new file mode 100644 index 000000000..9ed7146fb --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/testsupport/TestGroupPropertyId.java @@ -0,0 +1,181 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.testsupport; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; + +public enum TestGroupPropertyId implements GroupPropertyId { + + GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK(TestGroupTypeId.GROUP_TYPE_1, // + PropertyDefinition.builder()// + .setType(Boolean.class)// + .setDefaultValue(false)// + .setPropertyValueMutability(true)// + .build()), // + GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK(TestGroupTypeId.GROUP_TYPE_1, // + PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(0)// + .setPropertyValueMutability(true)// + .build() // + ), // + GROUP_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK(TestGroupTypeId.GROUP_TYPE_1, // + PropertyDefinition.builder()// + .setType(Double.class)// + .setDefaultValue(0.0)// + .setPropertyValueMutability(true)// + .build() // + ), // + GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK(TestGroupTypeId.GROUP_TYPE_2, // + PropertyDefinition.builder()// + .setType(Boolean.class)// + .setDefaultValue(false)// + .setPropertyValueMutability(true)// + .build() // + ), // + GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK(TestGroupTypeId.GROUP_TYPE_2, // + PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(0)// + .setPropertyValueMutability(true)// + .build() // + ), // + GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK(TestGroupTypeId.GROUP_TYPE_2, // + PropertyDefinition.builder()// + .setType(Double.class)// + .setDefaultValue(0.0)// + .setPropertyValueMutability(true)// + .build() // + ), // + GROUP_PROPERTY_3_1_BOOLEAN_IMMUTABLE_NO_TRACK(TestGroupTypeId.GROUP_TYPE_3, // + PropertyDefinition.builder()// + .setType(Boolean.class)// + .setDefaultValue(false)// + .setPropertyValueMutability(false)// + .build() // + ), // + GROUP_PROPERTY_3_2_INTEGER_IMMUTABLE_NO_TRACK(TestGroupTypeId.GROUP_TYPE_3, // + PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(0)// + .setPropertyValueMutability(false)// + .build() // + ), // + GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK(TestGroupTypeId.GROUP_TYPE_3, // + PropertyDefinition.builder()// + .setType(Double.class)// + .setDefaultValue(0.0)// + .setPropertyValueMutability(false)// + .build() // + );// + + private final PropertyDefinition propertyDefinition; + private final TestGroupTypeId testGroupTypeId; + + /** + * Returns the property definition associated with this member + */ + public PropertyDefinition getPropertyDefinition() { + return propertyDefinition; + } + + private TestGroupPropertyId(TestGroupTypeId testGroupTypeId, PropertyDefinition propertyDefinition) { + this.testGroupTypeId = testGroupTypeId; + this.propertyDefinition = propertyDefinition; + } + + /** + * Returns the TestGroupTypeId that should be the type associated with the + * property + */ + public TestGroupTypeId getTestGroupTypeId() { + return testGroupTypeId; + } + + /** + * Returns the TestGroupPropertyId associated with the given TestGroupTypeId + * Preconditions: The TestGroupTypeId should not be null + */ + public static Set getTestGroupPropertyIds(TestGroupTypeId testGroupTypeId) { + Set result = new LinkedHashSet<>(); + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { + if (testGroupPropertyId.testGroupTypeId == testGroupTypeId) { + result.add(testGroupPropertyId); + } + } + return result; + } + + /** + * Returns a unique GroupPropertyId instance that is not a member of this + * enumeration + */ + public static GroupPropertyId getUnknownGroupPropertyId() { + return new GroupPropertyId() { + }; + } + + /** + * Returns a randomly selected value that is compatible with this member's + * associated property definition. + */ + public Object getRandomPropertyValue(final RandomGenerator randomGenerator) { + switch (this) { + case GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK: + return randomGenerator.nextBoolean(); + case GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK: + return randomGenerator.nextInt(); + case GROUP_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK: + return randomGenerator.nextDouble(); + case GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK: + return randomGenerator.nextBoolean(); + case GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK: + return randomGenerator.nextInt(); + case GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK: + return randomGenerator.nextDouble(); + case GROUP_PROPERTY_3_1_BOOLEAN_IMMUTABLE_NO_TRACK: + return randomGenerator.nextBoolean(); + case GROUP_PROPERTY_3_2_INTEGER_IMMUTABLE_NO_TRACK: + return randomGenerator.nextInt(); + case GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK: + return randomGenerator.nextDouble(); + default: + throw new RuntimeException("unhandled case: " + this); + + } + } + + /* + * Returns the next GroupPropertyId with wrap around + */ + public TestGroupPropertyId next() { + int index = this.ordinal(); + index++; + index %= TestGroupPropertyId.values().length; + return TestGroupPropertyId.values()[index]; + } + + /** + * Returns a randomly selected TestGroupPropertyId. + */ + public static TestGroupPropertyId getRandomTestGroupPropertyId(final RandomGenerator randomGenerator) { + int index = randomGenerator.nextInt(TestGroupPropertyId.values().length); + return TestGroupPropertyId.values()[index]; + } + + public static List getShuffledTestGroupPropertyIds(TestGroupTypeId testGroupTypeId, + RandomGenerator randomGenerator) { + List result = new ArrayList<>(getTestGroupPropertyIds(testGroupTypeId)); + + Collections.shuffle(result, new Random(randomGenerator.nextLong())); + return result; + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/testsupport/TestGroupTypeId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/testsupport/TestGroupTypeId.java new file mode 100644 index 000000000..a5e54e2cb --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/groups/testsupport/TestGroupTypeId.java @@ -0,0 +1,61 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.testsupport; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupTypeId; + +/** + * Enumeration of GroupTypeId to support unit testing + */ +public enum TestGroupTypeId implements GroupTypeId { + GROUP_TYPE_1, GROUP_TYPE_2, GROUP_TYPE_3; + + /** + * Returns a randomly selected member of this enumeration. Precondition: The + * random generator must not be null + */ + public static TestGroupTypeId getRandomGroupTypeId(final RandomGenerator randomGenerator) { + return TestGroupTypeId.values()[randomGenerator.nextInt(TestGroupTypeId.values().length)]; + } + + public static int size() { + return values().length; + } + + private TestGroupTypeId next; + + /** + * Returns the next member of this enumeration + */ + public TestGroupTypeId next() { + if (next == null) { + next = TestGroupTypeId.values()[(ordinal() + 1) % TestGroupTypeId.values().length]; + } + return next; + } + + /** + * Returns a new {@link GroupTypeId} instance. + */ + public static GroupTypeId getUnknownGroupTypeId() { + return new GroupTypeId() { + }; + } + + public static List getTestGroupTypeIds() { + return Arrays.asList(TestGroupTypeId.values()); + } + + public static List getShuffledTestGroupTypeIds(RandomGenerator randomGenerator) { + List result = getTestGroupTypeIds(); + Random random = new Random(randomGenerator.nextLong()); + Collections.shuffle(result, random); + return result; + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/MaterialsPlugin.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/MaterialsPlugin.java new file mode 100644 index 000000000..383359f92 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/MaterialsPlugin.java @@ -0,0 +1,165 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials; + +import java.util.Optional; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.plugins.materials.datamangers.MaterialsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.materials.datamangers.MaterialsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.materials.reports.BatchStatusReport; +import gov.hhs.aspr.ms.gcm.plugins.materials.reports.BatchStatusReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.materials.reports.MaterialsProducerPropertyReport; +import gov.hhs.aspr.ms.gcm.plugins.materials.reports.MaterialsProducerPropertyReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.materials.reports.MaterialsProducerResourceReport; +import gov.hhs.aspr.ms.gcm.plugins.materials.reports.MaterialsProducerResourceReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.materials.reports.StageReport; +import gov.hhs.aspr.ms.gcm.plugins.materials.reports.StageReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsError; +import gov.hhs.aspr.ms.gcm.plugins.regions.RegionsPluginId; +import gov.hhs.aspr.ms.gcm.plugins.resources.ResourcesPluginId; +import util.errors.ContractException; + +/** + * A plugin providing a materials data manager to the simulation. + */ +public final class MaterialsPlugin { + + private static class Data { + private BatchStatusReportPluginData batchStatusReportPluginData; + private MaterialsProducerPropertyReportPluginData materialsProducerPropertyReportPluginData; + private MaterialsProducerResourceReportPluginData materialsProducerResourceReportPluginData; + private StageReportPluginData stageReportPluginData; + private MaterialsPluginData materialsPluginData; + } + + private MaterialsPlugin() { + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Builder() { + } + + private Data data = new Data(); + + private void validate() { + if (data.materialsPluginData == null) { + throw new ContractException(MaterialsError.NULL_MATERIALS_PLUGIN_DATA); + } + } + + /** + * Builds the PersonPropertiesPlugin from the collected inputs + * + * @throws ContractException {@linkplain MaterialsError#NULL_MATERIALS_PLUGIN_DATA} + * if the materials plugin data is null + */ + public Plugin getMaterialsPlugin() { + + validate(); + Plugin.Builder builder = Plugin.builder();// + builder.setPluginId(MaterialsPluginId.PLUGIN_ID);// + + builder.addPluginData(data.materialsPluginData);// + + if (data.batchStatusReportPluginData != null) { + builder.addPluginData(data.batchStatusReportPluginData);// + } + if (data.materialsProducerPropertyReportPluginData != null) { + builder.addPluginData(data.materialsProducerPropertyReportPluginData);// + } + if (data.materialsProducerResourceReportPluginData != null) { + builder.addPluginData(data.materialsProducerResourceReportPluginData);// + } + if (data.stageReportPluginData != null) { + builder.addPluginData(data.stageReportPluginData);// + } + + builder.addPluginDependency(RegionsPluginId.PLUGIN_ID);// + builder.addPluginDependency(ResourcesPluginId.PLUGIN_ID);// + + builder.setInitializer((c) -> { + + MaterialsPluginData pluginData = c.getPluginData(MaterialsPluginData.class).get(); + c.addDataManager(new MaterialsDataManager(pluginData)); + + Optional optional1 = c.getPluginData(BatchStatusReportPluginData.class); + if (optional1.isPresent()) { + BatchStatusReportPluginData batchStatusReportPluginData = optional1.get(); + c.addReport(new BatchStatusReport(batchStatusReportPluginData)::init); + } + + Optional optional2 = c + .getPluginData(MaterialsProducerPropertyReportPluginData.class); + if (optional2.isPresent()) { + MaterialsProducerPropertyReportPluginData materialsProducerPropertyReportPluginData = optional2 + .get(); + c.addReport(new MaterialsProducerPropertyReport(materialsProducerPropertyReportPluginData)::init); + } + + Optional optional3 = c + .getPluginData(MaterialsProducerResourceReportPluginData.class); + if (optional3.isPresent()) { + MaterialsProducerResourceReportPluginData materialsProducerResourceReportPluginData = optional3 + .get(); + c.addReport(new MaterialsProducerResourceReport(materialsProducerResourceReportPluginData)::init); + } + + Optional optional4 = c.getPluginData(StageReportPluginData.class); + if (optional4.isPresent()) { + StageReportPluginData stageReportPluginData = optional4.get(); + c.addReport(new StageReport(stageReportPluginData)::init); + } + + }); + return builder.build(); + + } + + public Builder setBatchStatusReportPluginData(BatchStatusReportPluginData batchStatusReportPluginData) { + data.batchStatusReportPluginData = batchStatusReportPluginData; + return this; + } + + public Builder setMaterialsProducerPropertyReportPluginData( + MaterialsProducerPropertyReportPluginData materialsProducerPropertyReportPluginData) { + data.materialsProducerPropertyReportPluginData = materialsProducerPropertyReportPluginData; + return this; + } + + public Builder setMaterialsProducerResourceReportPluginData( + MaterialsProducerResourceReportPluginData materialsProducerResourceReportPluginData) { + data.materialsProducerResourceReportPluginData = materialsProducerResourceReportPluginData; + return this; + } + + public Builder setStageReportPluginData(StageReportPluginData stageReportPluginData) { + data.stageReportPluginData = stageReportPluginData; + return this; + } + + public Builder setMaterialsPluginData(MaterialsPluginData materialsPluginData) { + data.materialsPluginData = materialsPluginData; + return this; + } + } + + // public static Plugin getMaterialsPlugin(MaterialsPluginData + // materialsPluginData) { + // + // return Plugin .builder()// + // .setPluginId(MaterialsPluginId.PLUGIN_ID)// + // .addPluginData(materialsPluginData)// + // .addPluginDependency(RegionsPluginId.PLUGIN_ID)// + // .addPluginDependency(ResourcesPluginId.PLUGIN_ID)// + // .setInitializer((c) -> { + // MaterialsPluginData pluginData = + // c.getPluginData(MaterialsPluginData.class).get(); + // c.addDataManager(new MaterialsDataManager(pluginData)); + // }).build(); + // + // } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/MaterialsPluginId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/MaterialsPluginId.java new file mode 100644 index 000000000..53d889556 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/MaterialsPluginId.java @@ -0,0 +1,13 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; + +/** + * Static plugin id implementation for the materials plugin + */ +public final class MaterialsPluginId implements PluginId { + public final static PluginId PLUGIN_ID = new MaterialsPluginId(); + + private MaterialsPluginId() { + }; +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/datamangers/MaterialsDataManager.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/datamangers/MaterialsDataManager.java new file mode 100644 index 000000000..ea1bae4a4 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/datamangers/MaterialsDataManager.java @@ -0,0 +1,3083 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.datamangers; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import org.apache.commons.math3.util.Pair; + +import gov.hhs.aspr.ms.gcm.nucleus.DataManager; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.nucleus.EventFilter; +import gov.hhs.aspr.ms.gcm.nucleus.IdentifiableFunctionMap; +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.BatchAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.BatchAmountUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.BatchImminentRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.BatchPropertyDefinitionEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.BatchPropertyUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.MaterialIdAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.MaterialsProducerAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.MaterialsProducerPropertyDefinitionEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.MaterialsProducerPropertyUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.MaterialsProducerResourceUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.StageAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.StageImminentRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.StageMaterialsProducerUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.StageMembershipAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.StageMembershipRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.StageOfferUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchConstructionInfo; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchPropertyDefinitionInitialization; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsError; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerConstructionData; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerPropertyDefinitionInitialization; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.StageConversionInfo; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.StageId; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionError; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.resources.datamanagers.ResourcesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.resources.events.RegionResourceUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.resources.events.ResourceIdAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceError; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.errors.ContractException; +import util.wrappers.MutableLong; + +/** + * General manager for all material activities. + */ +public final class MaterialsDataManager extends DataManager { + + /* + * Represents the batch + */ + private static class BatchRecord { + + /* + * The non-negative amount of this batch + */ + private double amount; + + private final BatchId batchId; + + /* + * The non-null material for this batch + */ + private MaterialId materialId; + /* + * The owning material producer + */ + private MaterialsProducerRecord materialsProducerRecord; + /* + * The stage on which this batch is staged -- may be null + */ + private StageRecord stageRecord; + + private BatchRecord(BatchId batchId) { + this.batchId = batchId; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("BatchRecord [amount="); + builder.append(amount); + builder.append(", batchId="); + builder.append(batchId); + builder.append(", materialId="); + builder.append(materialId); + builder.append(", materialsProducerRecord="); + builder.append(materialsProducerRecord.materialProducerId); + + builder.append(", stageRecord="); + if (stageRecord != null) { + builder.append(stageRecord.stageId); + } else { + builder.append("null"); + } + + builder.append("]"); + return builder.toString(); + } + + } + + /* + * Represents the stage + */ + private static class StageRecord { + /* + * The set of batches that are staged on this stage + */ + private final Set batchRecords = new LinkedHashSet<>(); + + /* + * The owning material producer + */ + private MaterialsProducerRecord materialsProducerRecord; + /* + * Flag marking that the stage has been offered up to other components. While + * true, this stage and its batches are immutable. + */ + private boolean offered; + private final StageId stageId; + + private StageRecord(StageId stageId) { + this.stageId = stageId; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("StageRecord [offered="); + builder.append(offered); + builder.append(", stageId="); + builder.append(stageId); + builder.append(", materialsProducerRecord="); + builder.append(materialsProducerRecord.materialProducerId); + builder.append(", batchRecords="); + List batchIds = new ArrayList<>(); + for (BatchRecord batchRecord : batchRecords) { + batchIds.add(batchRecord.batchId); + } + builder.append(batchIds); + builder.append("]"); + return builder.toString(); + } + + } + + private static record MaterialsProducerPropertyDefinitionMutationEvent( + MaterialsProducerPropertyDefinitionInitialization materialsProducerPropertyDefinitionInitialization) + implements Event { + } + + private static record BatchPropertyDefinitionMutationEvent( + BatchPropertyDefinitionInitialization batchPropertyDefinitionInitialization) implements Event { + } + + private static record MaterialsProducerAdditionMutationEvent( + MaterialsProducerConstructionData materialsProducerConstructionData) implements Event { + } + + private record BatchAdditionMutationEvent(BatchId batchId, BatchConstructionInfo batchConstructionInfo) + implements Event { + } + + private static record BatchMaterialTransferMutionEvent(BatchId sourceBatchId, BatchId destinationBatchId, + double amount) implements Event { + } + + private static record BatchRemovalMutationEvent(BatchId batchId) implements Event { + } + + private static record BatchPropertyUpdateMutationEvent(BatchId batchId, BatchPropertyId batchPropertyId, + Object batchPropertyValue) implements Event { + } + + private static record MaterialsProducerPropertyUpdateMutationEvent(MaterialsProducerId materialsProducerId, + MaterialsProducerPropertyId materialsProducerPropertyId, Object materialsProducerPropertyValue) + implements Event { + } + + private static record MoveBatchToInventoryMutationEvent(BatchId batchId) implements Event { + } + + private static record MoveBatchToStageMutationEvent(BatchId batchId, StageId stageId) implements Event { + } + + private static record TransferOfferedStageMutationEvent(StageId stageId, MaterialsProducerId materialsProducerId) + implements Event { + } + + private static record TransferResourceToRegionMutationEvent(MaterialsProducerId materialsProducerId, + ResourceId resourceId, RegionId regionId, long amount) implements Event { + } + + private static record StageAdditionMutationEvent(StageId stageId, MaterialsProducerId materialsProducerId) + implements Event { + } + + private static record StageRemovalMutationEvent(StageId stageId, boolean destroyBatches) implements Event { + } + + private static record StageOfferUpdateMutationEvent(StageId stageId, boolean offer) implements Event { + } + + private static record ConvertStageToBatchMutationEvent(StageConversionInfo stageConversionInfo, BatchId batchId) + implements Event { + } + + private static record ConvertStageToResourceMutationEvent(StageId stageId, ResourceId resourceId, long amount) + implements Event { + } + + private static record MaterialIdAdditionMutationEvent(MaterialId materialId) implements Event { + } + + private static enum MaterialsProducerPropertyUpdateEventFunctionId { + PRODUCER, PROPERTY + } + + /* + * Represents the materials producer + */ + private static class MaterialsProducerRecord { + + /* + * Those batches owned by this materials producer that are not staged + */ + private final Set inventory = new LinkedHashSet<>(); + + private MaterialsProducerId materialProducerId; + + private final Map materialProducerResources = new LinkedHashMap<>(); + + /* + * Those batches owned by this materials producer that are staged + */ + private final Set stageRecords = new LinkedHashSet<>(); + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("MaterialsProducerRecord [materialProducerId="); + builder.append(materialProducerId); + builder.append(", materialProducerResources="); + builder.append(materialProducerResources); + builder.append(", stageRecords="); + + List stageIds = new ArrayList<>(); + for (StageRecord stageRecord : stageRecords) { + stageIds.add(stageRecord.stageId); + } + builder.append(stageIds); + builder.append(", inventory="); + + List batchIds = new ArrayList<>(); + for (BatchRecord batchRecord : inventory) { + batchIds.add(batchRecord.batchId); + } + builder.append(batchIds); + builder.append("]"); + return builder.toString(); + } + + } + + private static enum MaterialsProducerResourceUpdateEventFunctionId { + PRODUCER, RESOURCE + } + + private static enum StageMaterialsProducerUpdateEventFunctionId { + DESTINATION, SOURCE, STAGE + } + + private static enum StageOfferUpdateEventFunctionId { + STAGE + } + + private final Map> batchPropertyDefinitions = new LinkedHashMap<>(); + + private final Map> batchPropertyMap = new LinkedHashMap<>(); + + private final Map batchRecords = new LinkedHashMap<>(); + + private DataManagerContext dataManagerContext; + + private final Set materialIds = new LinkedHashSet<>(); + + private final MaterialsPluginData materialsPluginData; + + private final Map materialsProducerMap = new LinkedHashMap<>(); + + private final Map materialsProducerPropertyDefinitions = new LinkedHashMap<>(); + + private final Map> materialsProducerPropertyMap = new LinkedHashMap<>(); + + private IdentifiableFunctionMap materialsProducerPropertyUpdateMap = // + IdentifiableFunctionMap.builder(MaterialsProducerPropertyUpdateEvent.class)// + .put(MaterialsProducerPropertyUpdateEventFunctionId.PRODUCER, e -> e.materialsProducerId())// + .put(MaterialsProducerPropertyUpdateEventFunctionId.PROPERTY, e -> e.materialsProducerPropertyId())// + .build();// + + private IdentifiableFunctionMap materialsProducerResourceUpdateMap = // + IdentifiableFunctionMap.builder(MaterialsProducerResourceUpdateEvent.class)// + .put(MaterialsProducerResourceUpdateEventFunctionId.RESOURCE, e -> e.resourceId())// + .put(MaterialsProducerResourceUpdateEventFunctionId.PRODUCER, e -> e.materialsProducerId())// + .build();// + + /* + * The identifier for the next created batch + */ + private int nextBatchRecordId; + + /* + * The identifier for the next created stage + */ + private int nextStageRecordId; + + private final Map> nonDefaultBearingBatchPropertyIds = new LinkedHashMap<>(); + + private final Map nonDefaultBearingProducerPropertyIds = new LinkedHashMap<>(); + + private Map nonDefaultChecksForBatches = new LinkedHashMap<>(); + + private boolean[] nonDefaultChecksForProducers = new boolean[0]; + + private RegionsDataManager regionsDataManager; + + private final Set resourceIds = new LinkedHashSet<>(); + + private ResourcesDataManager resourcesDataManager; + + private IdentifiableFunctionMap stageMaterialsProducerUpdateMap = // + IdentifiableFunctionMap.builder(StageMaterialsProducerUpdateEvent.class)// + .put(StageMaterialsProducerUpdateEventFunctionId.SOURCE, e -> e.previousMaterialsProducerId())// + .put(StageMaterialsProducerUpdateEventFunctionId.DESTINATION, e -> e.currentMaterialsProducerId())// + .put(StageMaterialsProducerUpdateEventFunctionId.STAGE, e -> e.stageId())// + .build();// + + private IdentifiableFunctionMap stageOfferUpdateMap = // + IdentifiableFunctionMap.builder(StageOfferUpdateEvent.class)// + .put(StageOfferUpdateEventFunctionId.STAGE, e -> e.stageId())// + .build();// + + /* + * + */ + private final Map stageRecords = new LinkedHashMap<>(); + + /** + * Constructs the data manager. + * + * @throws ContractException {@linkplain MaterialsError#NULL_MATERIALS_PLUGIN_DATA} + * if the material plugin data is null + */ + public MaterialsDataManager(MaterialsPluginData materialsPluginData) { + if (materialsPluginData == null) { + throw new ContractException(MaterialsError.NULL_MATERIALS_PLUGIN_DATA); + } + this.materialsPluginData = materialsPluginData; + } + + /** + * Creates a batch from the {@linkplain BatchConstructionInfo} contained in the + * event. Sets batch properties found in the batch construction info. Generates + * a corresponding {@linkplain BatchAdditionEvent} event + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} + * if the materials producer id is unknown
    • + *
    • {@linkplain MaterialsError#NULL_BATCH_CONSTRUCTION_INFO} + * if the batch construction info in the event is + * null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_MATERIAL_ID} + * if the material id in the batch construction info + * is unknown
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the batch construction info contains an unknown + * batch property id
    • + *
    • {@linkplain PropertyError#INCOMPATIBLE_VALUE} + * if the batch construction info contains a batch + * property value that is incompatible with the + * corresponding property def
    • + *
    • {@linkplain PropertyError#INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT} + * if the batch construction does not contain a batch + * property value assignment for a batch property that + * does not have a default value
    • + *
    + */ + public BatchId addBatch(BatchConstructionInfo batchConstructionInfo) { + + BatchId batchId = new BatchId(nextBatchRecordId++); + dataManagerContext.releaseMutationEvent(new BatchAdditionMutationEvent(batchId, batchConstructionInfo)); + return batchId; + } + + /** + * Adds a new material type + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_MATERIAL_ID} if + * the material id is null
    • + *
    • {@linkplain MaterialsError#DUPLICATE_MATERIAL} + * if the material id is already present
    • + *
    + */ + public void addMaterialId(MaterialId materialId) { + dataManagerContext.releaseMutationEvent(new MaterialIdAdditionMutationEvent(materialId)); + } + + /** + * Add a material producer + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#DUPLICATE_MATERIALS_PRODUCER_ID} + * if the materials producer id is already + * present
    • + *
    • {@linkplain PropertyError#INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT} + * if the materialsProducerConstructionData does not + * contain a property value for any corresponding + * materials producer property definition that lacks a + * default value
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the materialsProducerConstructionData contains a + * property value assignment for an unknown materials + * producer property id.
    • + *
    + */ + public void addMaterialsProducer(MaterialsProducerConstructionData materialsProducerConstructionData) { + dataManagerContext + .releaseMutationEvent(new MaterialsProducerAdditionMutationEvent(materialsProducerConstructionData)); + } + + private void addNonDefaultBatchProperty(MaterialId materialId, BatchPropertyId batchPropertyId) { + + Map map = nonDefaultBearingBatchPropertyIds.get(materialId); + map.put(batchPropertyId, map.size()); + + nonDefaultChecksForBatches.put(materialId, new boolean[map.size()]); + } + + private void addNonDefaultProducerProperty(MaterialsProducerPropertyId materialsProducerPropertyId) { + nonDefaultBearingProducerPropertyIds.put(materialsProducerPropertyId, + nonDefaultBearingProducerPropertyIds.size()); + nonDefaultChecksForProducers = new boolean[nonDefaultBearingProducerPropertyIds.size()]; + } + + /** + * Creates a stage. Generates a corresponding {@linkplain StageAdditionEvent} + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} + * if the materials producer id is unknown
    • + *
    • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} + * if the materials producer id is unknown
    • + *
    + */ + public StageId addStage(final MaterialsProducerId materialsProducerId) { + StageId stageId = new StageId(nextStageRecordId++); + dataManagerContext.releaseMutationEvent(new StageAdditionMutationEvent(stageId, materialsProducerId)); + return stageId; + } + + /** + * Returns true if and only if the batch exists. Null tolerant. + */ + public boolean batchExists(final BatchId batchId) { + return batchRecords.containsKey(batchId); + } + + /** + * Returns true if and only if the batch property exists. Null tolerant. + */ + public boolean batchPropertyIdExists(final MaterialId materialId, final BatchPropertyId batchPropertyId) { + final Map map = batchPropertyDefinitions.get(materialId); + if (map == null) { + return false; + } + return map.containsKey(batchPropertyId); + } + + private void clearNonDefaultBatchChecks(MaterialId materialId) { + boolean[] checkArray = nonDefaultChecksForBatches.get(materialId); + for (int i = 0; i < checkArray.length; i++) { + checkArray[i] = false; + } + } + + private void clearNonDefaultProducerChecks() { + for (int i = 0; i < nonDefaultChecksForProducers.length; i++) { + nonDefaultChecksForProducers[i] = false; + } + } + + /** + * Converts a non-offered stage, including its associated batches, into a new + * batch of the given material. The new batch is placed into inventory. + * Generates a corresponding {@linkplain BatchImminentRemovalEvent}, + * {@linkplain BatchAdditionEvent} and {@linkplain StageImminentRemovalEvent} + * events + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#UNKNOWN_MATERIAL_ID} + * if the material id is unknown
    • + *
    • {@linkplain MaterialsError#UNKNOWN_STAGE_ID} if + * stage id is unknown
    • + *
    • {@linkplain MaterialsError#OFFERED_STAGE_UNALTERABLE} + * if the stage is offered
    • + *
    • {@linkplain MaterialsError#NULL_STAGE_CONVERSION_INFO} + * if the stage conversion info in the event is + * null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the batch construction info contains an unknown + * batch property id
    • + *
    • {@linkplain PropertyError#INCOMPATIBLE_VALUE} + * if the batch construction info contains a batch + * property value that is incompatible with the + * corresponding property def
    • + *
    • {@linkplain PropertyError#INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT} + * if the batch construction does not contain a batch + * property value assignment for a batch property that + * does not have a default value
    • + *
    + */ + public BatchId convertStageToBatch(StageConversionInfo stageConversionInfo) { + BatchId batchId = new BatchId(nextBatchRecordId++); + dataManagerContext.releaseMutationEvent(new ConvertStageToBatchMutationEvent(stageConversionInfo, batchId)); + return batchId; + } + + /** + * Converts a non-offered stage, including its associated batches, into an + * amount of resource. The resulting resource is owned by the associated + * material producer. Generates the corresponding + * {@linkplain BatchImminentRemovalEvent}, + * {@linkplain MaterialsProducerResourceUpdateEvent} and + * {@linkplain StageImminentRemovalEvent} events. + * + * @throws ContractException + *
      + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if the resource id is unknown
    • + *
    • {@linkplain MaterialsError#NULL_STAGE_ID} if + * the stage id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_STAGE_ID} if + * the stage id is unknown
    • + *
    • {@linkplain MaterialsError#OFFERED_STAGE_UNALTERABLE} + * if the stage is offered
    • + *
    • {@linkplain ResourceError#NEGATIVE_RESOURCE_AMOUNT} + * if the the resource amount is negative
    • + *
    • {@linkplain ResourceError#RESOURCE_ARITHMETIC_EXCEPTION} + * if the resource amount would cause an overflow of + * the materials producer's resource level
    • + *
    + */ + public void convertStageToResource(StageId stageId, ResourceId resourceId, long amount) { + dataManagerContext.releaseMutationEvent(new ConvertStageToResourceMutationEvent(stageId, resourceId, amount)); + } + + /** + * Defines a new batch property + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#UNKNOWN_MATERIAL_ID} + * if the material id is unknown
    • + *
    • {@linkplain PropertyError#DUPLICATE_PROPERTY_DEFINITION} + * if the batch property id is already present
    • + *
    • {@linkplain MaterialsError#UNKNOWN_BATCH_ID} if + * a batch property value assignment has an unknown + * batch id
    • + *
    • {@linkplain MaterialsError#MATERIAL_TYPE_MISMATCH} + * if a batch property value assignment has a batch id + * associated with a different material id type
    • + *
    + */ + public void defineBatchProperty(BatchPropertyDefinitionInitialization batchPropertyDefinitionInitialization) { + dataManagerContext + .releaseMutationEvent(new BatchPropertyDefinitionMutationEvent(batchPropertyDefinitionInitialization)); + } + + /** + * Defines a new person property + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_PROPERTY_DEFINITION_INITIALIZATION} + * if the materials producer property definition + * initialization is null
    • + *
    • {@linkplain PropertyError#DUPLICATE_PROPERTY_DEFINITION} + * if the person property already exists
    • + *
    • {@linkplain PropertyError#INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT} + * if the property definition has no default value and + * there is no included value assignment for some + * extant person
    • + *
    + */ + public void defineMaterialsProducerProperty( + MaterialsProducerPropertyDefinitionInitialization materialsProducerPropertyDefinitionInitialization) { + dataManagerContext.releaseMutationEvent(new MaterialsProducerPropertyDefinitionMutationEvent( + materialsProducerPropertyDefinitionInitialization)); + } + + private void destroyBatch(final BatchId batchId) { + final BatchRecord batchRecord = batchRecords.get(batchId); + batchPropertyMap.remove(batchId); + /* + * Remove the batch from its stage and the master batch tracking map + */ + if (batchRecord.stageRecord != null) { + batchRecord.stageRecord.batchRecords.remove(batchRecord); + } + batchRecord.materialsProducerRecord.inventory.remove(batchRecord); + batchRecords.remove(batchId); + } + + /** + * Returns the amount of material in the batch. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_BATCH_ID} if + * the batch id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_BATCH_ID} if + * the batch id is unknown
    • + *
    + */ + public double getBatchAmount(final BatchId batchId) { + validateBatchId(batchId); + final BatchRecord batchRecord = batchRecords.get(batchId); + return batchRecord.amount; + } + + /** + * Returns the type of material in the batch. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_BATCH_ID} if + * the batch id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_BATCH_ID} if + * the batch id is unknown
    • + *
    + */ + @SuppressWarnings("unchecked") + public T getBatchMaterial(final BatchId batchId) { + validateBatchId(batchId); + return (T) batchRecords.get(batchId).materialId; + } + + /** + * Returns the materials producer identifier of the batch + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_BATCH_ID} if + * the batch id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_BATCH_ID} if + * the batch id is unknown
    • + *
    + */ + @SuppressWarnings("unchecked") + public T getBatchProducer(final BatchId batchId) { + validateBatchId(batchId); + return (T) batchRecords.get(batchId).materialsProducerRecord.materialProducerId; + } + + /** + * Returns the property definition for the given {@link MaterialId} and + * {@link BatchPropertyId} + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_MATERIAL_ID} if + * the material id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_MATERIAL_ID} + * if the material id is unknown
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the batch property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the batch property id is unknown
    • + *
    + */ + public PropertyDefinition getBatchPropertyDefinition(final MaterialId materialId, + final BatchPropertyId batchPropertyId) { + validateMaterialId(materialId); + validateBatchPropertyId(materialId, batchPropertyId); + final Map map = batchPropertyDefinitions.get(materialId); + return map.get(batchPropertyId); + } + + /** + * Returns the {@link BatchPropertyId} values for the given {@link MaterialId} + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_MATERIAL_ID} if + * the materials id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_MATERIAL_ID + * S_PRODUCER_ID} if the materials id is unknown
    • + *
    + */ + @SuppressWarnings("unchecked") + public Set getBatchPropertyIds(final MaterialId materialId) { + validateMaterialId(materialId); + final Map map = batchPropertyDefinitions.get(materialId); + final Set result = new LinkedHashSet<>(map.keySet().size()); + for (final BatchPropertyId batchPropertyId : map.keySet()) { + result.add((T) batchPropertyId); + } + return result; + } + + /** + * Returns the value of the batch property. It is the caller's responsibility to + * validate the inputs. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_BATCH_ID} if + * the batch id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_BATCH_ID} if + * the batch id is unknown
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the batch property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the batch property id is unknown
    • + *
    + */ + @SuppressWarnings("unchecked") + public T getBatchPropertyValue(BatchId batchId, BatchPropertyId batchPropertyId) { + validateBatchId(batchId); + final MaterialId batchMaterial = batchRecords.get(batchId).materialId; + validateBatchPropertyId(batchMaterial, batchPropertyId); + final Map map = batchPropertyMap.get(batchId); + final Object propertyValue = map.get(batchPropertyId); + if (propertyValue != null) { + return (T) propertyValue; + } + PropertyDefinition propertyDefinition = batchPropertyDefinitions.get(batchMaterial).get(batchPropertyId); + return (T) propertyDefinition.getDefaultValue().get(); + } + + /** + * Returns the stage id the batch. Returns null if the batch is not in a stage. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_BATCH_ID} if + * the batch id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_BATCH_ID} if + * the batch id is unknown
    • + *
    + */ + public Optional getBatchStageId(final BatchId batchId) { + validateBatchId(batchId); + final BatchRecord batchRecord = batchRecords.get(batchId); + StageId stageId; + if (batchRecord.stageRecord == null) { + stageId = null; + } else { + stageId = batchRecord.stageRecord.stageId; + } + return Optional.ofNullable(stageId); + } + + /** + * Returns an event filter used to subscribe to {@link BatchAdditionEvent} + * events. Matches on all such events. + */ + public EventFilter getEventFilterForBatchAdditionEvent() { + return EventFilter.builder(BatchAdditionEvent.class).build(); + } + + /** + * Returns an event filter used to subscribe to {@link BatchAmountUpdateEvent} + * events. Matches on all such events. + */ + public EventFilter getEventFilterForBatchAmountUpdateEvent() { + return EventFilter.builder(BatchAmountUpdateEvent.class).build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link BatchImminentRemovalEvent} events. Matches on all such events. + */ + public EventFilter getEventFilterForBatchImminentRemovalEvent() { + return EventFilter.builder(BatchImminentRemovalEvent.class).build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link BatchPropertyDefinitionEvent} events. Matches on all such events. + */ + public EventFilter getEventFilterForBatchPropertyDefinitionEvent() { + return EventFilter.builder(BatchPropertyDefinitionEvent.class).build(); + } + + /** + * Returns an event filter used to subscribe to {@link BatchPropertyUpdateEvent} + * events. Matches on all such events. + */ + public EventFilter getEventFilterForBatchPropertyUpdateEvent() { + return EventFilter.builder(BatchPropertyUpdateEvent.class).build(); + } + + /** + * Returns an event filter used to subscribe to {@link MaterialIdAdditionEvent} + * events. Matches on all such events. + */ + public EventFilter getEventFilterForMaterialIdAdditionEvent() { + return EventFilter.builder(MaterialIdAdditionEvent.class).build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link MaterialsProducerAdditionEvent} events. Matches on all such events. + */ + public EventFilter getEventFilterForMaterialsProducerAdditionEvent() { + return EventFilter.builder(MaterialsProducerAdditionEvent.class).build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link MaterialsProducerPropertyDefinitionEvent} events. Matches on all such + * events. + */ + public EventFilter getEventFilterForMaterialsProducerPropertyDefinitionEvent() { + return EventFilter.builder(MaterialsProducerPropertyDefinitionEvent.class).build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link MaterialsProducerPropertyUpdateEvent} events. Matches all such events. + */ + public EventFilter getEventFilterForMaterialsProducerPropertyUpdateEvent() { + return EventFilter.builder(MaterialsProducerPropertyUpdateEvent.class)// + .build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link MaterialsProducerPropertyUpdateEvent} events. Matches on the materials + * producer id and materials producer property id. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} + * if the materials producer id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} + * if the materials producer id is not known
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the materials producer property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the materials producer property id is not + * known
    • + *
    + */ + public EventFilter getEventFilterForMaterialsProducerPropertyUpdateEvent( + MaterialsProducerId materialsProducerId, MaterialsProducerPropertyId materialsProducerPropertyId) { + validateMaterialsProducerId(materialsProducerId); + validateMaterialsProducerPropertyId(materialsProducerPropertyId); + return EventFilter.builder(MaterialsProducerPropertyUpdateEvent.class)// + .addFunctionValuePair( + materialsProducerPropertyUpdateMap.get(MaterialsProducerPropertyUpdateEventFunctionId.PROPERTY), + materialsProducerPropertyId)// + .addFunctionValuePair( + materialsProducerPropertyUpdateMap.get(MaterialsProducerPropertyUpdateEventFunctionId.PRODUCER), + materialsProducerId)// + .build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link MaterialsProducerResourceUpdateEvent} events. Matches on all such + * events. + */ + public EventFilter getEventFilterForMaterialsProducerResourceUpdateEvent() { + return EventFilter.builder(MaterialsProducerResourceUpdateEvent.class)// + .build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link MaterialsProducerResourceUpdateEvent} events. Matches on the materials + * producer id and resource id. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} + * if the materials producer id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} + * if the materials producer id is not known
    • + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if the resource id is not known
    • + *
    + */ + public EventFilter getEventFilterForMaterialsProducerResourceUpdateEvent( + MaterialsProducerId materialsProducerId, ResourceId resourceId) { + validateMaterialsProducerId(materialsProducerId); + validateResourceId(resourceId); + return EventFilter.builder(MaterialsProducerResourceUpdateEvent.class)// + .addFunctionValuePair( + materialsProducerResourceUpdateMap.get(MaterialsProducerResourceUpdateEventFunctionId.RESOURCE), + resourceId)// + .addFunctionValuePair( + materialsProducerResourceUpdateMap.get(MaterialsProducerResourceUpdateEventFunctionId.PRODUCER), + materialsProducerId)// + .build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link MaterialsProducerResourceUpdateEvent} events. Matches on the resource + * id. + * + * @throws ContractException + *
      + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if the resource id is not known
    • + *
    + */ + public EventFilter getEventFilterForMaterialsProducerResourceUpdateEvent( + ResourceId resourceId) { + validateResourceId(resourceId); + return EventFilter.builder(MaterialsProducerResourceUpdateEvent.class)// + .addFunctionValuePair( + materialsProducerResourceUpdateMap.get(MaterialsProducerResourceUpdateEventFunctionId.RESOURCE), + resourceId)// + .build(); + } + + /** + * Returns an event filter used to subscribe to {@link StageAdditionEvent} + * events. Matches on all such events. + */ + public EventFilter getEventFilterForStageAdditionEvent() { + return EventFilter.builder(StageAdditionEvent.class).build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link StageImminentRemovalEvent} events. Matches on all such events. + */ + public EventFilter getEventFilterForStageImminentRemovalEvent() { + return EventFilter.builder(StageImminentRemovalEvent.class).build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link StageMaterialsProducerUpdateEvent} events. Matches on all such events. + */ + public EventFilter getEventFilterForStageMaterialsProducerUpdateEvent() { + return EventFilter.builder(StageMaterialsProducerUpdateEvent.class)// + .build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link StageMaterialsProducerUpdateEvent} events. Matches on the stage id. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_STAGE_ID} if + * the stage id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_STAGE_ID} if + * the stage id is not known
    • + *
    + */ + public EventFilter getEventFilterForStageMaterialsProducerUpdateEvent( + StageId stageId) { + validateStageId(stageId); + return EventFilter.builder(StageMaterialsProducerUpdateEvent.class)// + .addFunctionValuePair( + stageMaterialsProducerUpdateMap.get(StageMaterialsProducerUpdateEventFunctionId.STAGE), stageId)// + .build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link StageMaterialsProducerUpdateEvent} events. Matches on the destination + * materials producer id. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} + * if the materials producer id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} + * if the materials producer id is not known
    • + *
    + */ + public EventFilter getEventFilterForStageMaterialsProducerUpdateEvent_ByDestination( + MaterialsProducerId materialsProducerId) { + validateMaterialsProducerId(materialsProducerId); + return EventFilter.builder(StageMaterialsProducerUpdateEvent.class)// + .addFunctionValuePair( + stageMaterialsProducerUpdateMap.get(StageMaterialsProducerUpdateEventFunctionId.DESTINATION), + materialsProducerId)// + .build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link StageMaterialsProducerUpdateEvent} events. Matches on the source + * materials producer id. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} + * if the materials producer id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} + * if the materials producer id is not known
    • + *
    + */ + public EventFilter getEventFilterForStageMaterialsProducerUpdateEvent_BySource( + MaterialsProducerId materialsProducerId) { + validateMaterialsProducerId(materialsProducerId); + return EventFilter.builder(StageMaterialsProducerUpdateEvent.class)// + .addFunctionValuePair( + stageMaterialsProducerUpdateMap.get(StageMaterialsProducerUpdateEventFunctionId.SOURCE), + materialsProducerId)// + .build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link StageMembershipAdditionEvent} events. Matches on all such events. + */ + public EventFilter getEventFilterForStageMembershipAdditionEvent() { + return EventFilter.builder(StageMembershipAdditionEvent.class).build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link StageMembershipRemovalEvent} events. Matches on all such events. + */ + public EventFilter getEventFilterForStageMembershipRemovalEvent() { + return EventFilter.builder(StageMembershipRemovalEvent.class).build(); + } + + /** + * Returns an event filter used to subscribe to {@link StageOfferUpdateEvent} + * events. Matches on all such events. + */ + public EventFilter getEventFilterForStageOfferUpdateEvent() { + return EventFilter.builder(StageOfferUpdateEvent.class)// + .build(); + } + + /** + * Returns an event filter used to subscribe to {@link StageOfferUpdateEvent} + * events. Matches on the stage id. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_STAGE_ID} if + * the stage id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_STAGE_ID} if + * the stage id is not known
    • + *
    + */ + public EventFilter getEventFilterForStageOfferUpdateEvent(StageId stageId) { + validateStageId(stageId); + return EventFilter.builder(StageOfferUpdateEvent.class)// + .addFunctionValuePair(stageOfferUpdateMap.get(StageOfferUpdateEventFunctionId.STAGE), stageId)// + .build(); + } + + /** + * Returns as a list the set of batch ids matching the materials producer where + * the batches are not staged. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} + * if the materials producer id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} + * if the materials producer id is unknown
    • + *
    + */ + public List getInventoryBatches(final MaterialsProducerId materialsProducerId) { + validateMaterialsProducerId(materialsProducerId); + final MaterialsProducerRecord materialsProducerRecord = materialsProducerMap.get(materialsProducerId); + final List result = new ArrayList<>(materialsProducerRecord.inventory.size()); + for (final BatchRecord batchRecord : materialsProducerRecord.inventory) { + result.add(batchRecord.batchId); + } + return result; + } + + /** + * Returns as a list the set of batch ids matching the materials producer and + * material id where the batches are not staged. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} + * if the materials producer id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} + * if the materials producer id is unknown
    • + *
    • {@linkplain MaterialsError#NULL_MATERIAL_ID} if + * the material id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_MATERIAL_ID} + * if the material id is unknown
    • + *
    + */ + public List getInventoryBatchesByMaterialId(final MaterialsProducerId materialsProducerId, + final MaterialId materialId) { + validateMaterialsProducerId(materialsProducerId); + validateMaterialId(materialId); + final MaterialsProducerRecord materialsProducerRecord = materialsProducerMap.get(materialsProducerId); + final List result = new ArrayList<>(); + for (final BatchRecord batchRecord : materialsProducerRecord.inventory) { + if (batchRecord.materialId.equals(materialId)) { + result.add(batchRecord.batchId); + } + } + return result; + } + + /** + * Returns the material id values for the simulation + */ + @SuppressWarnings("unchecked") + public Set getMaterialIds() { + final Set result = new LinkedHashSet<>(materialIds.size()); + for (final MaterialId materialId : materialIds) { + result.add((T) materialId); + } + return result; + } + + /** + * Returns the set of material producer ids + */ + @SuppressWarnings("unchecked") + public Set getMaterialsProducerIds() { + final Set result = new LinkedHashSet<>(materialsProducerMap.size()); + for (final MaterialsProducerId materialsProducerId : materialsProducerMap.keySet()) { + result.add((T) materialsProducerId); + } + return result; + } + + /** + * Returns the property definition for the given + * {@link MaterialsProducerPropertyId} + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the materials producer property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the materials producer property id is + * unknown
    • + *
    + */ + public PropertyDefinition getMaterialsProducerPropertyDefinition( + final MaterialsProducerPropertyId materialsProducerPropertyId) { + validateMaterialsProducerPropertyId(materialsProducerPropertyId); + return materialsProducerPropertyDefinitions.get(materialsProducerPropertyId); + } + + /** + * Returns the set materials producer property ids + */ + @SuppressWarnings("unchecked") + public Set getMaterialsProducerPropertyIds() { + final Set result = new LinkedHashSet<>(materialsProducerPropertyDefinitions.keySet().size()); + for (final MaterialsProducerPropertyId materialsProducerPropertyId : materialsProducerPropertyDefinitions + .keySet()) { + result.add((T) materialsProducerPropertyId); + } + return result; + } + + /** + * Returns the value of the materials producer property. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} + * if the materials producer id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} + * if the materials producer id is unknown
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the materials producer property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the materials producer property id is + * unknown
    • + *
    + */ + @SuppressWarnings("unchecked") + public T getMaterialsProducerPropertyValue(MaterialsProducerId materialsProducerId, + MaterialsProducerPropertyId materialsProducerPropertyId) { + validateMaterialsProducerId(materialsProducerId); + validateMaterialsProducerPropertyId(materialsProducerPropertyId); + + Object propertyValue = materialsProducerPropertyMap.get(materialsProducerId).get(materialsProducerPropertyId); + if (propertyValue != null) { + return (T) propertyValue; + } + PropertyDefinition propertyDefinition = materialsProducerPropertyDefinitions.get(materialsProducerPropertyId); + return (T) propertyDefinition.getDefaultValue().get(); + } + + /** + * Returns the materials producer resource level. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} + * if the materials producer id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} + * if the materials producer id is unknown
    • + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if the resource id is unknown
    • + *
    + */ + public long getMaterialsProducerResourceLevel(MaterialsProducerId materialsProducerId, ResourceId resourceId) { + validateMaterialsProducerId(materialsProducerId); + validateResourceId(resourceId); + final MaterialsProducerRecord materialsProducerRecord = materialsProducerMap.get(materialsProducerId); + MutableLong mutableLong = materialsProducerRecord.materialProducerResources.get(resourceId); + return mutableLong.getValue(); + } + + /** + * Returns as a list the set of stage ids owned by the material producer where + * the stage is being offered. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} + * if the materials producer id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} + * if the materials producer id is unknown
    • + *
    + */ + public List getOfferedStages(final MaterialsProducerId materialsProducerId) { + validateMaterialsProducerId(materialsProducerId); + final List result = new ArrayList<>(); + final MaterialsProducerRecord materialsProducerRecord = materialsProducerMap.get(materialsProducerId); + for (final StageRecord stageRecord : materialsProducerRecord.stageRecords) { + if (stageRecord.offered) { + result.add(stageRecord.stageId); + } + } + return result; + } + + /** + * Returns as a list the set of batch ids matching the stage . + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_STAGE_ID} if + * the stage id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_STAGE_ID} if + * the stage id is unknown
    • + *
    + */ + public List getStageBatches(final StageId stageId) { + validateStageId(stageId); + final StageRecord stageRecord = stageRecords.get(stageId); + final List result = new ArrayList<>(stageRecord.batchRecords.size()); + for (final BatchRecord batchRecord : stageRecord.batchRecords) { + result.add(batchRecord.batchId); + } + return result; + } + + /** + * Returns as a list the set of batch ids matching the stage and material type. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_STAGE_ID} if + * the stage id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_STAGE_ID} if + * the stage id is unknown
    • + *
    • {@linkplain MaterialsError#NULL_MATERIAL_ID} if + * the material id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_MATERIAL_ID} + * if the material id is unknown
    • + *
    + */ + public List getStageBatchesByMaterialId(final StageId stageId, final MaterialId materialId) { + validateStageId(stageId); + validateMaterialId(materialId); + final StageRecord stageRecord = stageRecords.get(stageId); + final List result = new ArrayList<>(); + for (final BatchRecord batchRecord : stageRecord.batchRecords) { + if (batchRecord.materialId.equals(materialId)) { + result.add(batchRecord.batchId); + } + } + return result; + } + + /** + * Returns the materials producer id for the stage. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_STAGE_ID} if + * the stage id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_STAGE_ID} if + * the stage id is unknown
    • + *
    + */ + @SuppressWarnings("unchecked") + public T getStageProducer(final StageId stageId) { + validateStageId(stageId); + return (T) stageRecords.get(stageId).materialsProducerRecord.materialProducerId; + } + + /** + * Returns as a list the set of stage ids owned by the material producer. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} + * if the materials producer id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} + * if the materials producer id is unknown
    • + *
    + */ + public List getStages(final MaterialsProducerId materialsProducerId) { + validateMaterialsProducerId(materialsProducerId); + final MaterialsProducerRecord materialsProducerRecord = materialsProducerMap.get(materialsProducerId); + final List result = new ArrayList<>(materialsProducerRecord.stageRecords.size()); + for (final StageRecord stageRecord : materialsProducerRecord.stageRecords) { + result.add(stageRecord.stageId); + } + return result; + } + + private void handleBatchAdditionMutationEvent(DataManagerContext dataManagerContext, + BatchAdditionMutationEvent batchAdditionMutationEvent) { + BatchConstructionInfo batchConstructionInfo = batchAdditionMutationEvent.batchConstructionInfo(); + BatchId batchId = batchAdditionMutationEvent.batchId(); + validateBatchConstructionInfoNotNull(batchConstructionInfo); + final MaterialId materialId = batchConstructionInfo.getMaterialId(); + MaterialsProducerId materialsProducerId = batchConstructionInfo.getMaterialsProducerId(); + validateMaterialsProducerId(materialsProducerId); + validateMaterialId(materialId); + final double amount = batchConstructionInfo.getAmount(); + + final Map propertyValues = batchConstructionInfo.getPropertyValues(); + boolean checkPropertyCoverage = !nonDefaultBearingBatchPropertyIds.get(materialId).isEmpty(); + + for (final BatchPropertyId batchPropertyId : propertyValues.keySet()) { + validateBatchPropertyId(materialId, batchPropertyId); + final Object batchPropertyValue = propertyValues.get(batchPropertyId); + final PropertyDefinition propertyDefinition = batchPropertyDefinitions.get(materialId).get(batchPropertyId); + validateBatchPropertyValueNotNull(batchPropertyValue); + validateValueCompatibility(batchPropertyId, propertyDefinition, batchPropertyValue); + } + + if (checkPropertyCoverage) { + clearNonDefaultBatchChecks(materialId); + for (final BatchPropertyId batchPropertyId : propertyValues.keySet()) { + markBatchPropertyAssigned(materialId, batchPropertyId); + } + verifyNonDefaultBatchChecks(materialId); + } + + final MaterialsProducerRecord materialsProducerRecord = materialsProducerMap.get(materialsProducerId); + final BatchRecord batchRecord = new BatchRecord(batchId); + batchRecord.amount = amount; + batchRecord.materialId = materialId; + batchRecord.materialsProducerRecord = materialsProducerRecord; + materialsProducerRecord.inventory.add(batchRecord); + batchRecords.put(batchRecord.batchId, batchRecord); + + final Map map = new LinkedHashMap<>(); + batchPropertyMap.put(batchRecord.batchId, map); + + for (final BatchPropertyId batchPropertyId : propertyValues.keySet()) { + final Object batchPropertyValue = propertyValues.get(batchPropertyId); + map.put(batchPropertyId, batchPropertyValue); + } + + if (dataManagerContext.subscribersExist(BatchAdditionEvent.class)) { + dataManagerContext.releaseObservationEvent(new BatchAdditionEvent(batchRecord.batchId)); + } + + } + + private void handleBatchMaterialTransferMutionEvent(DataManagerContext dataManagerContext, + BatchMaterialTransferMutionEvent batchMaterialTransferMutionEvent) { + BatchId sourceBatchId = batchMaterialTransferMutionEvent.sourceBatchId(); + BatchId destinationBatchId = batchMaterialTransferMutionEvent.destinationBatchId(); + double amount = batchMaterialTransferMutionEvent.amount(); + validateBatchId(sourceBatchId); + validateBatchId(destinationBatchId); + validateDifferentBatchesForShift(sourceBatchId, destinationBatchId); + validateMaterialsMatchForShift(sourceBatchId, destinationBatchId); + validateProducersMatchForShift(sourceBatchId, destinationBatchId); + validateBatchIsNotOnOfferedStage(sourceBatchId); + validateBatchIsNotOnOfferedStage(destinationBatchId); + validateNonnegativeFiniteMaterialAmount(amount); + validateBatchHasSufficientMaterial(sourceBatchId, amount); + validateBatchHasSufficientUllage(destinationBatchId, amount); + + BatchRecord sourceBatchRecord = batchRecords.get(sourceBatchId); + BatchRecord destinationBatchRecord = batchRecords.get(destinationBatchId); + + if (dataManagerContext.subscribersExist(BatchAmountUpdateEvent.class)) { + double previousSourceAmount = sourceBatchRecord.amount; + double previousDestinationAmount = destinationBatchRecord.amount; + sourceBatchRecord.amount -= amount; + destinationBatchRecord.amount += amount; + dataManagerContext.releaseObservationEvent( + new BatchAmountUpdateEvent(sourceBatchId, previousSourceAmount, sourceBatchRecord.amount)); + dataManagerContext.releaseObservationEvent(new BatchAmountUpdateEvent(destinationBatchId, + previousDestinationAmount, destinationBatchRecord.amount)); + } else { + sourceBatchRecord.amount -= amount; + destinationBatchRecord.amount += amount; + } + + } + + private void handleBatchPropertyDefinitionMutationEvent(DataManagerContext dataManagerContext, + BatchPropertyDefinitionMutationEvent batchPropertyDefinitionMutationEvent) { + BatchPropertyDefinitionInitialization batchPropertyDefinitionInitialization = batchPropertyDefinitionMutationEvent + .batchPropertyDefinitionInitialization(); + MaterialId materialId = batchPropertyDefinitionInitialization.getMaterialId(); + PropertyDefinition propertyDefinition = batchPropertyDefinitionInitialization.getPropertyDefinition(); + BatchPropertyId batchPropertyId = batchPropertyDefinitionInitialization.getPropertyId(); + + validateMaterialId(materialId); + validateNewBatchPropertyId(materialId, batchPropertyId); + validateBatchPropertyDefinition(propertyDefinition); + + batchPropertyDefinitions.get(materialId).put(batchPropertyId, propertyDefinition); + + // validate the value assignments + for (Pair pair : batchPropertyDefinitionInitialization.getPropertyValues()) { + /* + * We know that the batch id and value are non-null and that the value is + * compatible with the property definition + */ + BatchId batchId = pair.getFirst(); + validateBatchId(batchId); + MaterialId batchMaterialId = batchRecords.get(batchId).materialId; + if (!materialId.equals(batchMaterialId)) { + throw new ContractException(MaterialsError.MATERIAL_TYPE_MISMATCH); + } + } + + boolean checkAllBatchesHaveValues = propertyDefinition.getDefaultValue().isEmpty(); + + /* + * if the property definition does not have a default value then every batch + * will need a property value assignment + */ + if (checkAllBatchesHaveValues) { + BitSet coverageSet = new BitSet(batchRecords.size()); + + for (Pair pair : batchPropertyDefinitionInitialization.getPropertyValues()) { + BatchId batchId = pair.getFirst(); + coverageSet.set(batchId.getValue()); + } + + for (BatchId batchId : batchRecords.keySet()) { + BatchRecord batchRecord = batchRecords.get(batchId); + if (batchRecord.materialId.equals(materialId)) { + if (!coverageSet.get(batchId.getValue())) { + throw new ContractException(PropertyError.INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT); + } + } + } + } + + // if the property definition does not define a default value + if (checkAllBatchesHaveValues) { + addNonDefaultBatchProperty(materialId, batchPropertyId); + } + // integrate the batch property value assignments + for (Pair pair : batchPropertyDefinitionInitialization.getPropertyValues()) { + BatchId batchId = pair.getFirst(); + Object value = pair.getSecond(); + Map map = batchPropertyMap.get(batchId); + map.put(batchPropertyId, value); + } + + if (dataManagerContext.subscribersExist(BatchPropertyDefinitionEvent.class)) { + dataManagerContext.releaseObservationEvent(new BatchPropertyDefinitionEvent(materialId, batchPropertyId)); + } + + } + + private void handleBatchPropertyUpdateMutationEvent(DataManagerContext dataManagerContext, + BatchPropertyUpdateMutationEvent batchPropertyUpdateMutationEvent) { + BatchId batchId = batchPropertyUpdateMutationEvent.batchId(); + BatchPropertyId batchPropertyId = batchPropertyUpdateMutationEvent.batchPropertyId(); + Object batchPropertyValue = batchPropertyUpdateMutationEvent.batchPropertyValue(); + + validateBatchId(batchId); + BatchRecord batchRecord = batchRecords.get(batchId); + final MaterialId materialId = batchRecord.materialId; + validateBatchPropertyId(materialId, batchPropertyId); + final PropertyDefinition propertyDefinition = batchPropertyDefinitions.get(materialId).get(batchPropertyId); + validatePropertyMutability(propertyDefinition); + validateBatchPropertyValueNotNull(batchPropertyValue); + validateValueCompatibility(batchPropertyId, propertyDefinition, batchPropertyValue); + validateBatchIsNotOnOfferedStage(batchId); + final Map map = batchPropertyMap.get(batchId); + + if (dataManagerContext.subscribersExist(BatchPropertyUpdateEvent.class)) { + Object previousPropertyValue = map.get(batchPropertyId); + if (previousPropertyValue == null) { + previousPropertyValue = propertyDefinition.getDefaultValue().get(); + } + map.put(batchPropertyId, batchPropertyValue); + dataManagerContext.releaseObservationEvent( + new BatchPropertyUpdateEvent(batchId, batchPropertyId, previousPropertyValue, batchPropertyValue)); + } else { + map.put(batchPropertyId, batchPropertyValue); + } + } + + private void handleBatchRemovalMutationEvent(DataManagerContext dataManagerContext, + BatchRemovalMutationEvent batchRemovalMutationEvent) { + BatchId batchId = batchRemovalMutationEvent.batchId(); + validateBatchId(batchId); + validateBatchIsNotOnOfferedStage(batchId); + dataManagerContext.addPlan((context) -> { + destroyBatch(batchId); + }, dataManagerContext.getTime()); + if (dataManagerContext.subscribersExist(BatchImminentRemovalEvent.class)) { + dataManagerContext.releaseObservationEvent(new BatchImminentRemovalEvent(batchId)); + } + } + + private void handleConvertStageToBatchMutationEvent(DataManagerContext dataManagerContext, + ConvertStageToBatchMutationEvent convertStageToBatchMutationEvent) { + StageConversionInfo stageConversionInfo = convertStageToBatchMutationEvent.stageConversionInfo; + validateStageConversionInfoNotNull(stageConversionInfo); + MaterialId materialId = stageConversionInfo.getMaterialId(); + BatchId batchId = convertStageToBatchMutationEvent.batchId(); + StageId stageId = stageConversionInfo.getStageId(); + double amount = stageConversionInfo.getAmount(); + + validateMaterialId(materialId); + validateStageId(stageId); + StageRecord stageRecord = stageRecords.get(stageId); + final MaterialsProducerId materialsProducerId = stageRecord.materialsProducerRecord.materialProducerId; + validateStageIsNotOffered(stageId); + validateNonnegativeFiniteMaterialAmount(amount); + + final Map propertyValues = stageConversionInfo.getPropertyValues(); + boolean checkPropertyCoverage = !nonDefaultBearingBatchPropertyIds.get(materialId).isEmpty(); + + for (final BatchPropertyId batchPropertyId : propertyValues.keySet()) { + validateBatchPropertyId(materialId, batchPropertyId); + final Object batchPropertyValue = propertyValues.get(batchPropertyId); + final PropertyDefinition propertyDefinition = batchPropertyDefinitions.get(materialId).get(batchPropertyId); + validateBatchPropertyValueNotNull(batchPropertyValue); + validateValueCompatibility(batchPropertyId, propertyDefinition, batchPropertyValue); + } + + if (checkPropertyCoverage) { + clearNonDefaultBatchChecks(materialId); + for (final BatchPropertyId batchPropertyId : propertyValues.keySet()) { + markBatchPropertyAssigned(materialId, batchPropertyId); + } + verifyNonDefaultBatchChecks(materialId); + } + + // add the new batch + final MaterialsProducerRecord materialsProducerRecord = materialsProducerMap.get(materialsProducerId); + + final BatchRecord newBatchRecord = new BatchRecord(batchId); + newBatchRecord.amount = amount; + newBatchRecord.materialId = materialId; + newBatchRecord.materialsProducerRecord = materialsProducerRecord; + materialsProducerRecord.inventory.add(newBatchRecord); + batchRecords.put(newBatchRecord.batchId, newBatchRecord); + + final Map map = new LinkedHashMap<>(); + for (final BatchPropertyId batchPropertyId : propertyValues.keySet()) { + Object propertyValue = propertyValues.get(batchPropertyId); + map.put(batchPropertyId, propertyValue); + } + batchPropertyMap.put(newBatchRecord.batchId, map); + + dataManagerContext.addPlan((context) -> { + for (final BatchRecord batchRecord : stageRecord.batchRecords) { + batchPropertyMap.remove(batchRecord.batchId); + batchRecords.remove(batchRecord.batchId); + } + stageRecord.materialsProducerRecord.stageRecords.remove(stageRecord); + stageRecords.remove(stageId); + + }, dataManagerContext.getTime()); + + if (dataManagerContext.subscribersExist(BatchAdditionEvent.class)) { + dataManagerContext.releaseObservationEvent(new BatchAdditionEvent(batchId)); + } + + if (dataManagerContext.subscribersExist(BatchImminentRemovalEvent.class)) { + for (BatchRecord batchRec : stageRecord.batchRecords) { + dataManagerContext.releaseObservationEvent(new BatchImminentRemovalEvent(batchRec.batchId)); + } + } + if (dataManagerContext.subscribersExist(StageImminentRemovalEvent.class)) { + dataManagerContext.releaseObservationEvent(new StageImminentRemovalEvent(stageId)); + } + + } + + private void handleConvertStageToResource(DataManagerContext dataManagerContext, + ConvertStageToResourceMutationEvent convertStageToResourceMutationEvent) { + ResourceId resourceId = convertStageToResourceMutationEvent.resourceId(); + StageId stageId = convertStageToResourceMutationEvent.stageId(); + long amount = convertStageToResourceMutationEvent.amount(); + validateResourceId(resourceId); + validateStageId(stageId); + validateStageIsNotOffered(stageId); + StageRecord stageRecord = stageRecords.get(stageId); + final MaterialsProducerId materialsProducerId = stageRecord.materialsProducerRecord.materialProducerId; + validateNonnegativeResourceAmount(amount); + final MaterialsProducerRecord materialsProducerRecord = materialsProducerMap.get(materialsProducerId); + MutableLong mutableLong = materialsProducerRecord.materialProducerResources.get(resourceId); + long previousResourceLevel = mutableLong.getValue(); + validateResourceAdditionValue(previousResourceLevel, amount); + + dataManagerContext.addPlan((context) -> { + for (final BatchRecord batchRecord : stageRecord.batchRecords) { + batchPropertyMap.remove(batchRecord.batchId); + batchRecords.remove(batchRecord.batchId); + } + stageRecord.materialsProducerRecord.stageRecords.remove(stageRecord); + stageRecords.remove(stageId); + + }, dataManagerContext.getTime()); + + mutableLong.increment(amount); + + if (dataManagerContext.subscribersExist(MaterialsProducerResourceUpdateEvent.class)) { + dataManagerContext.releaseObservationEvent(new MaterialsProducerResourceUpdateEvent(materialsProducerId, + resourceId, previousResourceLevel, mutableLong.getValue())); + } + + if (dataManagerContext.subscribersExist(BatchImminentRemovalEvent.class)) { + for (BatchRecord batchRecord : stageRecord.batchRecords) { + dataManagerContext.releaseObservationEvent(new BatchImminentRemovalEvent(batchRecord.batchId)); + } + } + if (dataManagerContext.subscribersExist(StageImminentRemovalEvent.class)) { + dataManagerContext.releaseObservationEvent(new StageImminentRemovalEvent(stageId)); + } + + } + + private void handleMaterialIdAdditionMutationEvent(DataManagerContext dataManagerContext, + MaterialIdAdditionMutationEvent materialIdAdditionMutationEvent) { + MaterialId materialId = materialIdAdditionMutationEvent.materialId(); + validateNewMaterialId(materialId); + + materialIds.add(materialId); + + batchPropertyDefinitions.put(materialId, new LinkedHashMap<>()); + nonDefaultBearingBatchPropertyIds.put(materialId, new LinkedHashMap<>()); + nonDefaultChecksForBatches.put(materialId, new boolean[0]); + + if (dataManagerContext.subscribersExist(MaterialIdAdditionEvent.class)) { + dataManagerContext.releaseObservationEvent(new MaterialIdAdditionEvent(materialId)); + } + } + + private void handleMaterialsProducerAdditionMutationEvent(DataManagerContext dataManagerContext, + MaterialsProducerAdditionMutationEvent materialsProducerAdditionMutationEvent) { + MaterialsProducerConstructionData materialsProducerConstructionData = materialsProducerAdditionMutationEvent + .materialsProducerConstructionData(); + + // validate the new producer id + MaterialsProducerId materialsProducerId = materialsProducerConstructionData.getMaterialsProducerId(); + validateNewMaterialsProducerId(materialsProducerId); + + // validate the included property value assignments + Map materialsProducerPropertyValues = materialsProducerConstructionData + .getMaterialsProducerPropertyValues(); + for (MaterialsProducerPropertyId materialsProducerPropertyId : materialsProducerPropertyValues.keySet()) { + validateMaterialsProducerPropertyId(materialsProducerPropertyId); + Object propertyValue = materialsProducerPropertyValues.get(materialsProducerPropertyId); + final PropertyDefinition propertyDefinition = materialsProducerPropertyDefinitions + .get(materialsProducerPropertyId); + validateValueCompatibility(materialsProducerPropertyId, propertyDefinition, propertyValue); + } + + /* + * if any of the property definitions don't have a default value, then the event + * must include those assignments + */ + boolean checkPropertyCoverage = !nonDefaultBearingProducerPropertyIds.isEmpty(); + if (checkPropertyCoverage) { + clearNonDefaultProducerChecks(); + for (MaterialsProducerPropertyId materialsProducerPropertyId : materialsProducerPropertyValues.keySet()) { + markProducerPropertyAssigned(materialsProducerPropertyId); + } + verifyNonDefaultChecksForProducers(); + } + + // integrate the new producer into the resources + final MaterialsProducerRecord materialsProducerRecord = new MaterialsProducerRecord(); + materialsProducerRecord.materialProducerId = materialsProducerId; + for (final ResourceId resourceId : resourceIds) { + materialsProducerRecord.materialProducerResources.put(resourceId, new MutableLong()); + } + materialsProducerMap.put(materialsProducerId, materialsProducerRecord); + + // integrate the new producer into the property values + Map propertyValueMap = new LinkedHashMap<>(); + materialsProducerPropertyMap.put(materialsProducerId, propertyValueMap); + + for (MaterialsProducerPropertyId materialsProducerPropertyId : materialsProducerPropertyValues.keySet()) { + Object propertyValue = materialsProducerPropertyValues.get(materialsProducerPropertyId); + propertyValueMap.put(materialsProducerPropertyId, propertyValue); + } + + Map resourceLevels = materialsProducerConstructionData.getResourceLevels(); + for (ResourceId resourceId : resourceLevels.keySet()) { + Long resourceLevel = resourceLevels.get(resourceId); + MutableLong mutableLong = materialsProducerRecord.materialProducerResources.get(resourceId); + mutableLong.increment(resourceLevel); + } + + if (dataManagerContext.subscribersExist(MaterialsProducerAdditionEvent.class)) { + // release notification of the materials producer addition + MaterialsProducerAdditionEvent.Builder materialsProducerAdditionEventBuilder = // + MaterialsProducerAdditionEvent.builder()// + .setMaterialsProducerId(materialsProducerId);// + + List values = materialsProducerConstructionData.getValues(Object.class); + for (Object value : values) { + materialsProducerAdditionEventBuilder.addValue(value); + } + + MaterialsProducerAdditionEvent materialsProducerAdditionEvent = materialsProducerAdditionEventBuilder + .build(); + dataManagerContext.releaseObservationEvent(materialsProducerAdditionEvent); + } + + } + + private void handleMaterialsProducerPropertyDefinitionMutationEvent(DataManagerContext dataManagerContext, + MaterialsProducerPropertyDefinitionMutationEvent materialsProducerPropertyDefinitionMutationEvent) { + + MaterialsProducerPropertyDefinitionInitialization materialsProducerPropertyDefinitionInitialization = materialsProducerPropertyDefinitionMutationEvent + .materialsProducerPropertyDefinitionInitialization(); + validateMaterialsProducerPropertyDefinitionInitializationNotNull( + materialsProducerPropertyDefinitionInitialization); + MaterialsProducerPropertyId materialsProducerPropertyId = materialsProducerPropertyDefinitionInitialization + .getMaterialsProducerPropertyId(); + PropertyDefinition propertyDefinition = materialsProducerPropertyDefinitionInitialization + .getPropertyDefinition(); + + validateMaterialsProducerPropertyIdIsUnknown(materialsProducerPropertyId); + boolean checkAllProducersHaveValues = propertyDefinition.getDefaultValue().isEmpty(); + + // validate the producer property value assignments + for (Pair pair : materialsProducerPropertyDefinitionInitialization + .getPropertyValues()) { + MaterialsProducerId materialsProducerId = pair.getFirst(); + validateMaterialsProducerId(materialsProducerId); + } + + /* + * If the property definition does not have a default value then we need to have + * a property assignment for each producer + */ + if (checkAllProducersHaveValues) { + + final Map coverageSet = new LinkedHashMap<>(); + for (final MaterialsProducerId materialsProducerId : materialsProducerMap.keySet()) { + coverageSet.put(materialsProducerId, false); + } + + for (Pair pair : materialsProducerPropertyDefinitionInitialization + .getPropertyValues()) { + MaterialsProducerId materialsProducerId = pair.getFirst(); + coverageSet.put(materialsProducerId, true); + } + for (MaterialsProducerId materialsProducerId : coverageSet.keySet()) { + if (!coverageSet.get(materialsProducerId)) { + throw new ContractException(PropertyError.INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT); + } + } + } + + materialsProducerPropertyDefinitions.put(materialsProducerPropertyId, propertyDefinition); + + if (checkAllProducersHaveValues) { + addNonDefaultProducerProperty(materialsProducerPropertyId); + } + + for (Pair pair : materialsProducerPropertyDefinitionInitialization + .getPropertyValues()) { + MaterialsProducerId materialsProducerId = pair.getFirst(); + /* + * we do not have to validate the value since it is guaranteed to be consistent + * with the property definition by contract. + */ + Object value = pair.getSecond(); + Map propertyMap = materialsProducerPropertyMap + .get(materialsProducerId); + propertyMap.put(materialsProducerPropertyId, value); + } + + if (dataManagerContext.subscribersExist(MaterialsProducerPropertyDefinitionEvent.class)) { + dataManagerContext + .releaseObservationEvent(new MaterialsProducerPropertyDefinitionEvent(materialsProducerPropertyId)); + } + + } + + private void handleMaterialsProducerPropertyUpdateMutationEvent(DataManagerContext dataManagerContext, + MaterialsProducerPropertyUpdateMutationEvent materialsProducerPropertyUpdateMutationEvent) { + MaterialsProducerId materialsProducerId = materialsProducerPropertyUpdateMutationEvent.materialsProducerId(); + MaterialsProducerPropertyId materialsProducerPropertyId = materialsProducerPropertyUpdateMutationEvent + .materialsProducerPropertyId(); + Object materialsProducerPropertyValue = materialsProducerPropertyUpdateMutationEvent + .materialsProducerPropertyValue(); + + validateMaterialsProducerId(materialsProducerId); + validateMaterialsProducerPropertyId(materialsProducerPropertyId); + final PropertyDefinition propertyDefinition = materialsProducerPropertyDefinitions + .get(materialsProducerPropertyId); + validatePropertyMutability(propertyDefinition); + validateMaterialProducerPropertyValueNotNull(materialsProducerPropertyValue); + validateValueCompatibility(materialsProducerPropertyId, propertyDefinition, materialsProducerPropertyValue); + Map propertyValueMap = materialsProducerPropertyMap + .get(materialsProducerId); + Object currentPopertyValue = propertyValueMap.get(materialsProducerPropertyId); + + if (dataManagerContext.subscribersExist(MaterialsProducerPropertyUpdateEvent.class)) { + if (currentPopertyValue == null) { + currentPopertyValue = propertyDefinition.getDefaultValue().get(); + } + propertyValueMap.put(materialsProducerPropertyId, materialsProducerPropertyValue); + dataManagerContext.releaseObservationEvent(new MaterialsProducerPropertyUpdateEvent(materialsProducerId, + materialsProducerPropertyId, currentPopertyValue, materialsProducerPropertyValue)); + } else { + propertyValueMap.put(materialsProducerPropertyId, materialsProducerPropertyValue); + } + + } + + private void handleMoveBatchToInventoryMutationEvent(DataManagerContext dataManagerContext, + MoveBatchToInventoryMutationEvent moveBatchToInventoryMutationEvent) { + + BatchId batchId = moveBatchToInventoryMutationEvent.batchId(); + validateBatchId(batchId); + BatchRecord batchRecord = batchRecords.get(batchId); + validateBatchIsStaged(batchId); + final StageId stageId = batchRecord.stageRecord.stageId; + validateStageIsNotOffered(stageId); + batchRecord.stageRecord.batchRecords.remove(batchRecord); + batchRecord.stageRecord = null; + batchRecord.materialsProducerRecord.inventory.add(batchRecord); + if (dataManagerContext.subscribersExist(StageMembershipRemovalEvent.class)) { + dataManagerContext.releaseObservationEvent(new StageMembershipRemovalEvent(batchId, stageId)); + } + } + + private void handleMoveBatchToStageMutationEvent(DataManagerContext dataManagerContext, + MoveBatchToStageMutationEvent moveBatchToStageMutationEvent) { + BatchId batchId = moveBatchToStageMutationEvent.batchId(); + StageId stageId = moveBatchToStageMutationEvent.stageId(); + validateBatchId(batchId); + validateBatchIsNotStaged(batchId); + validateStageId(stageId); + validateStageIsNotOffered(stageId); + validateBatchAndStageOwnersMatch(batchId, stageId); + + final BatchRecord batchRecord = batchRecords.get(batchId); + final StageRecord stageRecord = stageRecords.get(stageId); + batchRecord.stageRecord = stageRecord; + stageRecord.batchRecords.add(batchRecord); + batchRecord.materialsProducerRecord.inventory.remove(batchRecord); + if (dataManagerContext.subscribersExist(StageMembershipAdditionEvent.class)) { + dataManagerContext.releaseObservationEvent(new StageMembershipAdditionEvent(batchId, stageId)); + } + } + + private void handleResourceIdAdditionEvent(DataManagerContext dataManagerContext, + ResourceIdAdditionEvent resourceIdAdditionEvent) { + ResourceId resourceId = resourceIdAdditionEvent.resourceId(); + if (resourceId == null || resourceIds.contains(resourceId)) { + return; + } + + resourceIds.add(resourceId); + + for (MaterialsProducerRecord materialsProducerRecord : materialsProducerMap.values()) { + materialsProducerRecord.materialProducerResources.put(resourceId, new MutableLong()); + } + } + + private void handleStageAdditionMutationEvent(DataManagerContext dataManagerContext, + StageAdditionMutationEvent stageAdditionMutationEvent) { + StageId stageId = stageAdditionMutationEvent.stageId(); + MaterialsProducerId materialsProducerId = stageAdditionMutationEvent.materialsProducerId(); + validateMaterialsProducerId(materialsProducerId); + final MaterialsProducerRecord materialsProducerRecord = materialsProducerMap.get(materialsProducerId); + final StageRecord stageRecord = new StageRecord(stageId); + stageRecord.materialsProducerRecord = materialsProducerRecord; + materialsProducerRecord.stageRecords.add(stageRecord); + stageRecords.put(stageRecord.stageId, stageRecord); + if (dataManagerContext.subscribersExist(StageAdditionEvent.class)) { + dataManagerContext.releaseObservationEvent(new StageAdditionEvent(stageRecord.stageId)); + } + } + + private void handleStageOfferUpdateMutationEvent(DataManagerContext dataManagerContext, + StageOfferUpdateMutationEvent stageOfferUpdateMutationEvent) { + + StageId stageId = stageOfferUpdateMutationEvent.stageId(); + boolean offer = stageOfferUpdateMutationEvent.offer(); + validateStageId(stageId); + final StageRecord stageRecord = stageRecords.get(stageId); + if (dataManagerContext.subscribersExist(StageOfferUpdateEvent.class)) { + boolean previousState = stageRecord.offered; + stageRecord.offered = offer; + dataManagerContext.releaseObservationEvent(new StageOfferUpdateEvent(stageId, previousState, offer)); + } else { + stageRecord.offered = offer; + } + + } + + private void handleStageRemovalMutationEvent(DataManagerContext dataManagerContext, + StageRemovalMutationEvent stageRemovalMutationEvent) { + boolean destroyBatches = stageRemovalMutationEvent.destroyBatches(); + StageId stageId = stageRemovalMutationEvent.stageId(); + validateStageId(stageId); + validateStageIsNotOffered(stageId); + StageRecord stageRecord = stageRecords.get(stageId); + + if (destroyBatches) { + // plan for the removal of the stage and the batches + dataManagerContext.addPlan((context) -> { + for (final BatchRecord batchRecord : stageRecord.batchRecords) { + batchPropertyMap.remove(batchRecord.batchId); + batchRecords.remove(batchRecord.batchId); + } + stageRecord.materialsProducerRecord.stageRecords.remove(stageRecord); + stageRecords.remove(stageId); + }, dataManagerContext.getTime()); + + if (dataManagerContext.subscribersExist(BatchImminentRemovalEvent.class)) { + for (BatchRecord batchRecord : stageRecord.batchRecords) { + dataManagerContext.releaseObservationEvent(new BatchImminentRemovalEvent(batchRecord.batchId)); + } + } + if (dataManagerContext.subscribersExist(StageImminentRemovalEvent.class)) { + dataManagerContext.releaseObservationEvent(new StageImminentRemovalEvent(stageId)); + } + } else { + // plan for the removal of the stage and return of the batches to + // inventory + dataManagerContext.addPlan((context) -> { + for (final BatchRecord batchRecord : stageRecord.batchRecords) { + batchRecord.stageRecord = null; + batchRecord.materialsProducerRecord.inventory.add(batchRecord); + } + stageRecord.materialsProducerRecord.stageRecords.remove(stageRecord); + stageRecords.remove(stageId); + }, dataManagerContext.getTime()); + + if (dataManagerContext.subscribersExist(StageImminentRemovalEvent.class)) { + dataManagerContext.releaseObservationEvent(new StageImminentRemovalEvent(stageId)); + } + } + + } + + private void handleTransferOfferedStageMutationEvent(DataManagerContext dataManagerContext, + TransferOfferedStageMutationEvent transferOfferedStageMutationEvent) { + MaterialsProducerId materialsProducerId = transferOfferedStageMutationEvent.materialsProducerId(); + StageId stageId = transferOfferedStageMutationEvent.stageId(); + + validateStageId(stageId); + validateMaterialsProducerId(materialsProducerId); + validateStageIsOffered(stageId); + validateStageNotOwnedByReceivingMaterialsProducer(stageId, materialsProducerId); + final StageRecord stageRecord = stageRecords.get(stageId); + final MaterialsProducerRecord currentMaterialsProducerRecord = stageRecord.materialsProducerRecord; + MaterialsProducerId stageProducer = currentMaterialsProducerRecord.materialProducerId; + final MaterialsProducerRecord newMaterialsProducerRecord = materialsProducerMap.get(materialsProducerId); + currentMaterialsProducerRecord.stageRecords.remove(stageRecord); + newMaterialsProducerRecord.stageRecords.add(stageRecord); + stageRecord.materialsProducerRecord = newMaterialsProducerRecord; + for (final BatchRecord batchRecord : stageRecord.batchRecords) { + batchRecord.materialsProducerRecord = newMaterialsProducerRecord; + } + stageRecord.offered = false; + if (dataManagerContext.subscribersExist(StageMaterialsProducerUpdateEvent.class)) { + dataManagerContext.releaseObservationEvent( + new StageMaterialsProducerUpdateEvent(stageId, stageProducer, materialsProducerId)); + } + if (dataManagerContext.subscribersExist(StageOfferUpdateEvent.class)) { + dataManagerContext.releaseObservationEvent(new StageOfferUpdateEvent(stageId, true, false)); + } + + } + + private void handleTransferResourceToRegionMutationEvent(DataManagerContext dataManagerContext, + TransferResourceToRegionMutationEvent transferResourceToRegionMutationEvent) { + MaterialsProducerId materialsProducerId = transferResourceToRegionMutationEvent.materialsProducerId(); + ResourceId resourceId = transferResourceToRegionMutationEvent.resourceId(); + RegionId regionId = transferResourceToRegionMutationEvent.regionId(); + long amount = transferResourceToRegionMutationEvent.amount(); + validateResourceId(resourceId); + validateRegionId(regionId); + validateMaterialsProducerId(materialsProducerId); + validateNonnegativeResourceAmount(amount); + validateMaterialsProducerHasSufficientResource(materialsProducerId, resourceId, amount); + final long currentResourceLevel = resourcesDataManager.getRegionResourceLevel(regionId, resourceId); + validateResourceAdditionValue(currentResourceLevel, amount); + + final MaterialsProducerRecord materialsProducerRecord = materialsProducerMap.get(materialsProducerId); + final MutableLong mutableLong = materialsProducerRecord.materialProducerResources.get(resourceId); + + if (dataManagerContext.subscribersExist(MaterialsProducerResourceUpdateEvent.class)) { + long previousMaterialsProducerResourceLevel = mutableLong.getValue(); + mutableLong.decrement(amount); + long currentMaterialsProducerResourceLevel = mutableLong.getValue(); + resourcesDataManager.addResourceToRegion(resourceId, regionId, amount); + dataManagerContext.releaseObservationEvent(new MaterialsProducerResourceUpdateEvent(materialsProducerId, + resourceId, previousMaterialsProducerResourceLevel, currentMaterialsProducerResourceLevel)); + } else { + mutableLong.decrement(amount); + resourcesDataManager.addResourceToRegion(resourceId, regionId, amount); + } + } + + @Override + public void init(DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + if (dataManagerContext == null) { + throw new ContractException(NucleusError.NULL_SIMULATION_CONTEXT); + } + resourcesDataManager = dataManagerContext.getDataManager(ResourcesDataManager.class); + regionsDataManager = dataManagerContext.getDataManager(RegionsDataManager.class); + + this.dataManagerContext = dataManagerContext; + + dataManagerContext.subscribe(BatchAdditionMutationEvent.class, this::handleBatchAdditionMutationEvent); + dataManagerContext.subscribe(MaterialIdAdditionMutationEvent.class, + this::handleMaterialIdAdditionMutationEvent); + dataManagerContext.subscribe(MaterialsProducerAdditionMutationEvent.class, + this::handleMaterialsProducerAdditionMutationEvent); + dataManagerContext.subscribe(StageAdditionMutationEvent.class, this::handleStageAdditionMutationEvent); + dataManagerContext.subscribe(ConvertStageToBatchMutationEvent.class, + this::handleConvertStageToBatchMutationEvent); + dataManagerContext.subscribe(ConvertStageToResourceMutationEvent.class, this::handleConvertStageToResource); + dataManagerContext.subscribe(BatchPropertyDefinitionMutationEvent.class, + this::handleBatchPropertyDefinitionMutationEvent); + dataManagerContext.subscribe(MaterialsProducerPropertyDefinitionMutationEvent.class, + this::handleMaterialsProducerPropertyDefinitionMutationEvent); + dataManagerContext.subscribe(MoveBatchToInventoryMutationEvent.class, + this::handleMoveBatchToInventoryMutationEvent); + dataManagerContext.subscribe(MoveBatchToStageMutationEvent.class, this::handleMoveBatchToStageMutationEvent); + dataManagerContext.subscribe(BatchRemovalMutationEvent.class, this::handleBatchRemovalMutationEvent); + dataManagerContext.subscribe(StageRemovalMutationEvent.class, this::handleStageRemovalMutationEvent); + dataManagerContext.subscribe(BatchPropertyUpdateMutationEvent.class, + this::handleBatchPropertyUpdateMutationEvent); + dataManagerContext.subscribe(MaterialsProducerPropertyUpdateMutationEvent.class, + this::handleMaterialsProducerPropertyUpdateMutationEvent); + dataManagerContext.subscribe(StageOfferUpdateMutationEvent.class, this::handleStageOfferUpdateMutationEvent); + dataManagerContext.subscribe(BatchMaterialTransferMutionEvent.class, + this::handleBatchMaterialTransferMutionEvent); + dataManagerContext.subscribe(TransferOfferedStageMutationEvent.class, + this::handleTransferOfferedStageMutationEvent); + dataManagerContext.subscribe(TransferResourceToRegionMutationEvent.class, + this::handleTransferResourceToRegionMutationEvent); + + for (final MaterialId materialId : materialsPluginData.getMaterialIds()) { + materialIds.add(materialId); + + Map propMap = new LinkedHashMap<>(); + batchPropertyDefinitions.put(materialId, propMap); + nonDefaultBearingBatchPropertyIds.put(materialId, new LinkedHashMap<>()); + for (final BatchPropertyId batchPropertyId : materialsPluginData.getBatchPropertyIds(materialId)) { + final PropertyDefinition propertyDefinition = materialsPluginData.getBatchPropertyDefinition(materialId, + batchPropertyId); + propMap.put(batchPropertyId, propertyDefinition); + Map map = nonDefaultBearingBatchPropertyIds.get(materialId); + if (propertyDefinition.getDefaultValue().isEmpty()) { + map.put(batchPropertyId, map.size()); + } + } + int nonDefaultPropertyCount = nonDefaultBearingBatchPropertyIds.get(materialId).size(); + nonDefaultChecksForBatches.put(materialId, new boolean[nonDefaultPropertyCount]); + } + + for (ResourceId resourceId : resourcesDataManager.getResourceIds()) { + resourceIds.add(resourceId); + } + for (ResourceId resourceId : materialsPluginData.getResourceIds()) { + if (!resourceIds.contains(resourceId)) { + throw new ContractException(ResourceError.UNKNOWN_RESOURCE_ID, + resourceId + " in resource levels of materials producers"); + } + } + + for (MaterialsProducerPropertyId materialsProducerPropertyId : materialsPluginData + .getMaterialsProducerPropertyIds()) { + PropertyDefinition propertyDefinition = materialsPluginData + .getMaterialsProducerPropertyDefinition(materialsProducerPropertyId); + materialsProducerPropertyDefinitions.put(materialsProducerPropertyId, propertyDefinition); + if (propertyDefinition.getDefaultValue().isEmpty()) { + nonDefaultBearingProducerPropertyIds.put(materialsProducerPropertyId, + nonDefaultBearingProducerPropertyIds.size()); + } + } + nonDefaultChecksForProducers = new boolean[nonDefaultBearingProducerPropertyIds.size()]; + + for (final MaterialsProducerId materialsProducerId : materialsPluginData.getMaterialsProducerIds()) { + final MaterialsProducerRecord materialsProducerRecord = new MaterialsProducerRecord(); + materialsProducerRecord.materialProducerId = materialsProducerId; + for (final ResourceId resourceId : resourceIds) { + materialsProducerRecord.materialProducerResources.put(resourceId, new MutableLong()); + } + materialsProducerMap.put(materialsProducerId, materialsProducerRecord); + materialsProducerPropertyMap.put(materialsProducerId, new LinkedHashMap<>()); + } + + for (final MaterialsProducerId materialsProducerId : materialsPluginData.getMaterialsProducerIds()) { + Map materialsProducerPropertyValues = materialsPluginData + .getMaterialsProducerPropertyValues(materialsProducerId); + for (final MaterialsProducerPropertyId materialsProducerPropertyId : materialsProducerPropertyValues + .keySet()) { + final Object value = materialsProducerPropertyValues.get(materialsProducerPropertyId); + Map propertyToValueMap = materialsProducerPropertyMap + .get(materialsProducerId); + propertyToValueMap.put(materialsProducerPropertyId, value); + } + } + + for (final StageId stageId : materialsPluginData.getStageIds()) { + final StageRecord stageRecord = new StageRecord(stageId); + stageRecord.offered = materialsPluginData.isStageOffered(stageId); + stageRecords.put(stageRecord.stageId, stageRecord); + } + + for (final MaterialsProducerId materialsProducerId : materialsPluginData.getMaterialsProducerIds()) { + final MaterialsProducerRecord materialsProducerRecord = materialsProducerMap.get(materialsProducerId); + List materialsProducerStages = materialsPluginData.getMaterialsProducerStages(materialsProducerId); + for (StageId stageId : materialsProducerStages) { + StageRecord stageRecord = stageRecords.get(stageId); + stageRecord.materialsProducerRecord = materialsProducerRecord; + materialsProducerRecord.stageRecords.add(stageRecord); + } + } + + nextStageRecordId = materialsPluginData.getNextStageRecordId(); + + for (final BatchId batchId : materialsPluginData.getBatchIds()) { + + final MaterialId materialId = materialsPluginData.getBatchMaterial(batchId); + final double amount = materialsPluginData.getBatchAmount(batchId); + final BatchRecord batchRecord = new BatchRecord(batchId); + batchRecord.amount = amount; + batchRecord.materialId = materialId; + batchRecords.put(batchRecord.batchId, batchRecord); + final Map map = new LinkedHashMap<>(); + batchPropertyMap.put(batchRecord.batchId, map); + + Map batchPropertyValues = materialsPluginData.getBatchPropertyValues(batchId); + for (BatchPropertyId batchPropertyId : batchPropertyValues.keySet()) { + final Object batchPropertyValue = batchPropertyValues.get(batchPropertyId); + map.put(batchPropertyId, batchPropertyValue); + } + } + nextBatchRecordId = materialsPluginData.getNextBatchRecordId(); + + for (MaterialsProducerId materialsProducerId : materialsPluginData.getMaterialsProducerIds()) { + List materialsProducerInventoryBatches = materialsPluginData + .getMaterialsProducerInventoryBatches(materialsProducerId); + for (BatchId batchId : materialsProducerInventoryBatches) { + BatchRecord batchRecord = batchRecords.get(batchId); + MaterialsProducerRecord materialsProducerRecord = materialsProducerMap.get(materialsProducerId); + batchRecord.materialsProducerRecord = materialsProducerRecord; + materialsProducerRecord.inventory.add(batchRecord); + } + + } + + for (final StageId stageId : materialsPluginData.getStageIds()) { + final Set batches = materialsPluginData.getStageBatches(stageId); + for (final BatchId batchId : batches) { + final BatchRecord batchRecord = batchRecords.get(batchId); + if (batchRecord.stageRecord != null) { + throw new ContractException(MaterialsError.BATCH_ALREADY_STAGED); + } + final StageRecord stageRecord = stageRecords.get(stageId); + batchRecord.materialsProducerRecord = stageRecord.materialsProducerRecord; + batchRecord.stageRecord = stageRecord; + stageRecord.batchRecords.add(batchRecord); + batchRecord.materialsProducerRecord.inventory.remove(batchRecord); + } + } + + for (final MaterialsProducerId materialsProducerId : materialsPluginData.getMaterialsProducerIds()) { + for (final ResourceId resourceId : resourceIds) { + final Long amount = materialsPluginData.getMaterialsProducerResourceLevel(materialsProducerId, + resourceId); + if (amount != null) { + final MaterialsProducerRecord materialsProducerRecord = materialsProducerMap + .get(materialsProducerId); + final MutableLong mutableLong = materialsProducerRecord.materialProducerResources.get(resourceId); + mutableLong.increment(amount); + } + } + } + dataManagerContext.subscribe(ResourceIdAdditionEvent.class, this::handleResourceIdAdditionEvent); + if (dataManagerContext.stateRecordingIsScheduled()) { + dataManagerContext.subscribeToSimulationClose(this::recordSimulationState); + } + } + + /** + * Returns true if and only if the stage is offered. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_STAGE_ID} if + * the stage id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_STAGE_ID} if + * the stage id is unknown
    • + *
    + */ + public boolean isStageOffered(final StageId stageId) { + validateStageId(stageId); + final StageRecord stageRecord = stageRecords.get(stageId); + return stageRecord.offered; + } + + private void markBatchPropertyAssigned(MaterialId materialId, BatchPropertyId batchPropertyId) { + Map map = nonDefaultBearingBatchPropertyIds.get(materialId); + Integer index = map.get(batchPropertyId); + if (index != null) { + boolean[] checkArray = nonDefaultChecksForBatches.get(materialId); + checkArray[index] = true; + } + } + + private void markProducerPropertyAssigned(MaterialsProducerPropertyId materialsProducerPropertyId) { + Integer nonDefaultPropertyIndex = nonDefaultBearingProducerPropertyIds.get(materialsProducerPropertyId); + if (nonDefaultPropertyIndex != null) { + nonDefaultChecksForProducers[nonDefaultPropertyIndex] = true; + } + } + + /** + * Returns true if and only if the material exists. Tolerates null. + */ + public boolean materialIdExists(final MaterialId materialId) { + return (materialIds.contains(materialId)); + } + + /** + * Returns true if and only if the material producer id exists. Null tolerant. + */ + public boolean materialsProducerIdExists(final MaterialsProducerId materialsProducerId) { + return materialsProducerMap.containsKey(materialsProducerId); + } + + /** + * Returns true if and only if the material producer property id exists. Null + * tolerant. + */ + public boolean materialsProducerPropertyIdExists(final MaterialsProducerPropertyId materialsProducerPropertyId) { + return materialsProducerPropertyDefinitions.keySet().contains(materialsProducerPropertyId); + } + + /** + * Removes a batch from a non-offered stage, placing it into the materials + * producer's inventory. Generates a corresponding + * {@linkplain StageMembershipRemovalEvent} + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_BATCH_ID} if + * the batch id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_BATCH_ID} if + * the batch id is unknown
    • + *
    • {@linkplain MaterialsError#BATCH_NOT_STAGED} if + * the batch is not staged
    • + *
    • {@linkplain MaterialsError#OFFERED_STAGE_UNALTERABLE } + * if the stage containing the batch is offered
    • + *
    + */ + public void moveBatchToInventory(BatchId batchId) { + dataManagerContext.releaseMutationEvent(new MoveBatchToInventoryMutationEvent(batchId)); + } + + /** + * Removes a batch frominventory, placing it on a stage. Generates a + * corresponding {@linkplain StageMembershipAdditionEvent} + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_BATCH_ID} if + * the batch id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_BATCH_ID} if + * the batch id is unknown
    • + *
    • {@linkplain MaterialsError#BATCH_ALREADY_STAGED} + * if the batch is already staged
    • + *
    • {@linkplain MaterialsError#NULL_STAGE_ID} if + * the stage id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_STAGE_ID} if + * the stage id is unknown
    • + *
    • {@linkplain MaterialsError#OFFERED_STAGE_UNALTERABLE} + * if the stage is offered
    • + *
    • {@linkplain MaterialsError#BATCH_STAGED_TO_DIFFERENT_OWNER} + * if batch and stage do not have the same owning + * materials producer
    • + *
    + */ + public void moveBatchToStage(BatchId batchId, StageId stageId) { + dataManagerContext.releaseMutationEvent(new MoveBatchToStageMutationEvent(batchId, stageId)); + } + + private void recordSimulationState(DataManagerContext dataManagerContext) { + + MaterialsPluginData.Builder builder = MaterialsPluginData.builder(); + + builder.setNextBatchRecordId(nextBatchRecordId); + builder.setNextStageRecordId(nextStageRecordId); + + for (MaterialsProducerId materialsProducerId : materialsProducerPropertyMap.keySet()) { + Map map = materialsProducerPropertyMap.get(materialsProducerId); + for (MaterialsProducerPropertyId materialsProducerPropertyId : map.keySet()) { + Object propertyValue = map.get(materialsProducerPropertyId); + builder.setMaterialsProducerPropertyValue(materialsProducerId, materialsProducerPropertyId, + propertyValue); + } + } + + for (MaterialsProducerId materialsProducerId : materialsProducerMap.keySet()) { + builder.addMaterialsProducerId(materialsProducerId); + final MaterialsProducerRecord materialsProducerRecord = materialsProducerMap.get(materialsProducerId); + for (ResourceId resourId : materialsProducerRecord.materialProducerResources.keySet()) { + MutableLong mutableLong = materialsProducerRecord.materialProducerResources.get(resourId); + builder.setMaterialsProducerResourceLevel(materialsProducerId, resourId, mutableLong.getValue()); + } + } + + for (MaterialsProducerPropertyId materialsProducerPropertyId : materialsProducerPropertyDefinitions.keySet()) { + PropertyDefinition propertyDefinition = materialsProducerPropertyDefinitions + .get(materialsProducerPropertyId); + builder.defineMaterialsProducerProperty(materialsProducerPropertyId, propertyDefinition); + } + + for (MaterialId materialId : materialIds) { + builder.addMaterial(materialId); + } + + for (MaterialId materialId : batchPropertyDefinitions.keySet()) { + Map map = batchPropertyDefinitions.get(materialId); + for (BatchPropertyId batchPropertyId : map.keySet()) { + PropertyDefinition propertyDefinition = map.get(batchPropertyId); + builder.defineBatchProperty(materialId, batchPropertyId, propertyDefinition); + } + } + + for (BatchId batchId : batchRecords.keySet()) { + BatchRecord batchRecord = batchRecords.get(batchId); + builder.addBatch(batchId, batchRecord.materialId, batchRecord.amount); + } + + for (MaterialsProducerId materialsProducerId : materialsProducerMap.keySet()) { + MaterialsProducerRecord materialsProducerRecord = materialsProducerMap.get(materialsProducerId); + for (BatchRecord batchRecord : materialsProducerRecord.inventory) { + builder.addBatchToMaterialsProducerInventory(batchRecord.batchId, materialsProducerId); + } + } + + for (BatchId batchId : batchPropertyMap.keySet()) { + Map map = batchPropertyMap.get(batchId); + for (BatchPropertyId batchPropertyId : map.keySet()) { + Object propertyValue = map.get(batchPropertyId); + builder.setBatchPropertyValue(batchId, batchPropertyId, propertyValue); + } + } + + for (StageId stageId : stageRecords.keySet()) { + StageRecord stageRecord = stageRecords.get(stageId); + builder.addStage(stageId, stageRecord.offered); + for (BatchRecord batchRecord : stageRecord.batchRecords) { + builder.addBatchToStage(stageId, batchRecord.batchId); + } + } + + for (final MaterialsProducerId materialsProducerId : materialsProducerMap.keySet()) { + final MaterialsProducerRecord materialsProducerRecord = materialsProducerMap.get(materialsProducerId); + + for (StageRecord stageRecord : materialsProducerRecord.stageRecords) { + StageId stageId = stageRecord.stageId; + builder.addStageToMaterialProducer(stageId, materialsProducerId); + } + } + + dataManagerContext.releaseOutput(builder.build()); + + } + + /** + * Removes the given batch. Generates a corresponding + * {@linkplain BatchImminentRemovalEvent} + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_BATCH_ID} if + * the batch id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_BATCH_ID} if + * the batch id is unknown
    • + *
    • {@linkplain MaterialsError#OFFERED_STAGE_UNALTERABLE} + * if the batch is on an offered stage
    • + *
    + */ + public void removeBatch(BatchId batchId) { + dataManagerContext.releaseMutationEvent(new BatchRemovalMutationEvent(batchId)); + } + + /** + * Destroys a non-offered stage and optionally destroys any associated batches. + * Generates the corresponding {@linkplain BatchImminentRemovalEvent} and + * {@linkplain StageImminentRemovalEvent} events. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_STAGE_ID} if + * the stage id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_STAGE_ID} if + * the stage id is unknown
    • + *
    • {@linkplain MaterialsError#OFFERED_STAGE_UNALTERABLE} + * if stage is offered
    • + *
    + */ + public void removeStage(StageId stageId, boolean destroyBatches) { + dataManagerContext.releaseMutationEvent(new StageRemovalMutationEvent(stageId, destroyBatches)); + } + + /** + * Set a batch's property value. Generates a corresponding + * {@linkplain BatchPropertyUpdateEvent} + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_BATCH_ID} if + * the batch id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_BATCH_ID} if + * the batch id is unknown
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the batch property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the batch property id is unknown
    • + *
    • {@linkplain PropertyError#IMMUTABLE_VALUE} if + * batch property is not mutable
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_VALUE} + * if the batch property value is null
    • + *
    • {@linkplain PropertyError#INCOMPATIBLE_VALUE} + * if the batch property value is not compatible with + * the corresponding property definition
    • + *
    • {@linkplain MaterialsError#OFFERED_STAGE_UNALTERABLE} + * if the batch in on an offered stage
    • + *
    + */ + public void setBatchPropertyValue(BatchId batchId, BatchPropertyId batchPropertyId, Object batchPropertyValue) { + dataManagerContext.releaseMutationEvent( + new BatchPropertyUpdateMutationEvent(batchId, batchPropertyId, batchPropertyValue)); + } + + /** + * Assigns a property value to a materials producer. Generates a corresponding + * {@linkplain MaterialsProducerPropertyUpdateEvent} + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} + * if the materials producer id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} + * if the materials producer id is unknown
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the materials producer property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the materials producer property id is + * unknown
    • + *
    • {@linkplain PropertyError#IMMUTABLE_VALUE} if + * the materials producer property is immutable
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_VALUE} + * if the property value is null
    • + *
    • {@linkplain PropertyError#INCOMPATIBLE_VALUE} + * if the property value is incompatible with the + * corresponding property definition
    • + *
    + */ + public void setMaterialsProducerPropertyValue(MaterialsProducerId materialsProducerId, + MaterialsProducerPropertyId materialsProducerPropertyId, Object materialsProducerPropertyValue) { + dataManagerContext.releaseMutationEvent(new MaterialsProducerPropertyUpdateMutationEvent(materialsProducerId, + materialsProducerPropertyId, materialsProducerPropertyValue)); + } + + /** + * Sets the offer state of a stage. Generates a corresponding + * {@linkplain StageOfferUpdateEvent} + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_STAGE_ID } if + * the stage id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_STAGE_ID } + * if the stage id is unknown
    • + *
    + */ + public void setStageOfferState(StageId stageId, boolean offer) { + dataManagerContext.releaseMutationEvent(new StageOfferUpdateMutationEvent(stageId, offer)); + } + + /** + * Returns true if and only if the stage exists. Null tolerant. + */ + public boolean stageExists(final StageId stageId) { + return stageRecords.containsKey(stageId); + } + + /** + * Transfers an amount of material from one batch to another batch of the same + * material type and material producer. Generates a corresponding + * {@linkplain BatchAmountUpdateEvent} for each batch. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_BATCH_ID} if + * the source batch id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_BATCH_ID} if + * the source batch id is unknown
    • + *
    • {@linkplain MaterialsError#NULL_BATCH_ID} if + * the destination batch id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_BATCH_ID} if + * the destination batch id is unknown
    • + *
    • {@linkplain MaterialsError#REFLEXIVE_BATCH_SHIFT} + * if the source and destination batches are the + * same
    • + *
    • {@linkplain MaterialsError#MATERIAL_TYPE_MISMATCH} + * if the batches do not have the same material + * type
    • + *
    • {@linkplain MaterialsError#BATCH_SHIFT_WITH_MULTIPLE_OWNERS} + * if the batches have different owning materials + * producers
    • + *
    • {@linkplain MaterialsError#OFFERED_STAGE_UNALTERABLE} + * if the source batch is on a stage is offered
    • + *
    • {@linkplain MaterialsError#OFFERED_STAGE_UNALTERABLE} + * if the destination batch is on a stage is + * offered
    • + *
    • {@linkplain MaterialsError#NON_FINITE_MATERIAL_AMOUNT} + * if the shift amount is not a finite number
    • + *
    • {@linkplain MaterialsError#NEGATIVE_MATERIAL_AMOUNT} + * if the shift amount is negative
    • + *
    • {@linkplain MaterialsError#INSUFFICIENT_MATERIAL_AVAILABLE} + * if the shift amount exceeds the available material + * on the source batch
    • + *
    • {@linkplain MaterialsError#MATERIAL_ARITHMETIC_EXCEPTION} + * if the shift amount would cause an overflow on the + * destination batch
    • + *
    + */ + public void transferMaterialBetweenBatches(BatchId sourceBatchId, BatchId destinationBatchId, double amount) { + + dataManagerContext + .releaseMutationEvent(new BatchMaterialTransferMutionEvent(sourceBatchId, destinationBatchId, amount)); + } + + /** + * Transfers an offered stage from the current owning materials producer to + * another and marks the stage as not offered. Generates the corresponding + * {@linkplain StageMaterialsProducerUpdateEvent} + * {@linkplain StageOfferUpdateEvent} + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_STAGE_ID } if + * the stage id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_STAGE_ID } + * if the stage id is unknown
    • + *
    • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID } + * if the materials producer id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID } + * if the materials producer id is unknown
    • + *
    • {@linkplain MaterialsError#UNOFFERED_STAGE_NOT_TRANSFERABLE } + * if the stage is not offered
    • + *
    • {@linkplain MaterialsError#REFLEXIVE_STAGE_TRANSFER } + * if the source and destination materials producers + * are the same
    • + *
    + */ + public void transferOfferedStage(StageId stageId, MaterialsProducerId materialsProducerId) { + dataManagerContext.releaseMutationEvent(new TransferOfferedStageMutationEvent(stageId, materialsProducerId)); + } + + /** + * Transfers a resource amount from a materials producer to a region. Generates + * a corresponding {@linkplain RegionResourceUpdateEvent} and + * {@linkplain MaterialsProducerResourceUpdateEvent} + * + * @throws ContractException + *
      + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if the resource id is unknown
    • + *
    • {@linkplain RegionError#NULL_REGION_ID} if the + * region id is null
    • + *
    • {@linkplain RegionError#UNKNOWN_REGION_ID} if + * the region id is unknown
    • + *
    • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} + * if the materials producer id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} + * if the materials producer id is unknown
    • + *
    • {@linkplain ResourceError#NEGATIVE_RESOURCE_AMOUNT} + * if the materials amount is negative
    • + *
    • {@linkplain ResourceError#INSUFFICIENT_RESOURCES_AVAILABLE} + * if the materials amount exceeds the resource level + * of the materials producer
    • + *
    • {@linkplain ResourceError#RESOURCE_ARITHMETIC_EXCEPTION} + * if the materials amount would cause an overflow of + * the regions resource level
    • + *
    + */ + public void transferResourceToRegion(MaterialsProducerId materialsProducerId, ResourceId resourceId, + RegionId regionId, long amount) { + dataManagerContext.releaseMutationEvent( + new TransferResourceToRegionMutationEvent(materialsProducerId, resourceId, regionId, amount)); + } + + private void validateBatchAndStageOwnersMatch(final BatchId batchId, final StageId stageId) { + final MaterialsProducerId batchProducerId = batchRecords + .get(batchId).materialsProducerRecord.materialProducerId; + final MaterialsProducerId stageProducerId = stageRecords + .get(stageId).materialsProducerRecord.materialProducerId; + if (!batchProducerId.equals(stageProducerId)) { + throw new ContractException(MaterialsError.BATCH_STAGED_TO_DIFFERENT_OWNER); + } + } + + private void validateBatchConstructionInfoNotNull(final BatchConstructionInfo batchConstructionInfo) { + if (batchConstructionInfo == null) { + throw new ContractException(MaterialsError.NULL_BATCH_CONSTRUCTION_INFO); + } + } + + /* + * Precondition : batch must exist + */ + private void validateBatchHasSufficientMaterial(final BatchId batchId, final double amount) { + if (batchRecords.get(batchId).amount < amount) { + throw new ContractException(MaterialsError.INSUFFICIENT_MATERIAL_AVAILABLE); + } + } + + private void validateBatchHasSufficientUllage(BatchId batchId, double amount) { + if (!Double.isFinite(batchRecords.get(batchId).amount + amount)) { + throw new ContractException(MaterialsError.MATERIAL_ARITHMETIC_EXCEPTION); + } + } + + private void validateBatchId(final BatchId batchId) { + + if (batchId == null) { + throw new ContractException(MaterialsError.NULL_BATCH_ID); + } + + if (!batchExists(batchId)) { + throw new ContractException(MaterialsError.UNKNOWN_BATCH_ID, batchId); + } + } + + /* + * Preconditions : batch must exist + */ + private void validateBatchIsNotOnOfferedStage(final BatchId batchId) { + final BatchRecord batchRecord = batchRecords.get(batchId); + if (batchRecord.stageRecord != null) { + if (batchRecord.stageRecord.offered) { + throw new ContractException(MaterialsError.OFFERED_STAGE_UNALTERABLE); + } + } + } + + private void validateBatchIsNotStaged(final BatchId batchId) { + final BatchRecord batchRecord = batchRecords.get(batchId); + if (batchRecord.stageRecord != null) { + throw new ContractException(MaterialsError.BATCH_ALREADY_STAGED); + } + } + + private void validateBatchIsStaged(final BatchId batchId) { + final BatchRecord batchRecord = batchRecords.get(batchId); + if (batchRecord.stageRecord == null) { + throw new ContractException(MaterialsError.BATCH_NOT_STAGED); + } + } + + private void validateBatchPropertyDefinition(PropertyDefinition propertyDefinition) { + if (propertyDefinition == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_DEFINITION); + } + } + + private void validateBatchPropertyId(final MaterialId materialId, final BatchPropertyId batchPropertyId) { + if (batchPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + + final Map map = batchPropertyDefinitions.get(materialId); + if (map == null || !map.containsKey(batchPropertyId)) { + throw new ContractException(PropertyError.UNKNOWN_PROPERTY_ID, batchPropertyId); + } + + } + + private void validateBatchPropertyValueNotNull(final Object propertyValue) { + if (propertyValue == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_VALUE); + } + } + + private void validateDifferentBatchesForShift(final BatchId sourceBatchId, final BatchId destinationBatchId) { + if (sourceBatchId.equals(destinationBatchId)) { + throw new ContractException(MaterialsError.REFLEXIVE_BATCH_SHIFT); + } + } + + private void validateMaterialId(final MaterialId materialId) { + if (materialId == null) { + throw new ContractException(MaterialsError.NULL_MATERIAL_ID); + } + + if (!materialIds.contains(materialId)) { + throw new ContractException(MaterialsError.UNKNOWN_MATERIAL_ID, materialId); + } + } + + private void validateMaterialProducerPropertyValueNotNull(final Object propertyValue) { + if (propertyValue == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_VALUE); + } + } + + /* + * Preconditions: The batches must exist + */ + private void validateMaterialsMatchForShift(final BatchId sourceBatchId, final BatchId destinationBatchId) { + final MaterialId sourceMaterialId = batchRecords.get(sourceBatchId).materialId; + final MaterialId destinationMaterialId = batchRecords.get(destinationBatchId).materialId; + + if (!sourceMaterialId.equals(destinationMaterialId)) { + throw new ContractException(MaterialsError.MATERIAL_TYPE_MISMATCH); + } + + } + + private void validateMaterialsProducerHasSufficientResource(final MaterialsProducerId materialsProducerId, + final ResourceId resourceId, final long amount) { + final MaterialsProducerRecord materialsProducerRecord = materialsProducerMap.get(materialsProducerId); + MutableLong mutableLong = materialsProducerRecord.materialProducerResources.get(resourceId); + if (mutableLong.getValue() < amount) { + throw new ContractException(ResourceError.INSUFFICIENT_RESOURCES_AVAILABLE); + } + } + + private void validateMaterialsProducerId(final MaterialsProducerId materialsProducerId) { + if (materialsProducerId == null) { + throw new ContractException(MaterialsError.NULL_MATERIALS_PRODUCER_ID); + } + + if (!materialsProducerMap.containsKey(materialsProducerId)) { + throw new ContractException(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, materialsProducerId); + } + } + + private void validateMaterialsProducerPropertyDefinitionInitializationNotNull( + MaterialsProducerPropertyDefinitionInitialization materialsProducerPropertyDefinitionInitialization) { + if (materialsProducerPropertyDefinitionInitialization == null) { + throw new ContractException(MaterialsError.NULL_MATERIALS_PRODUCER_PROPERTY_DEFINITION_INITIALIZATION); + } + } + + private void validateMaterialsProducerPropertyId(final MaterialsProducerPropertyId materialsProducerPropertyId) { + if (materialsProducerPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + + if (!materialsProducerPropertyDefinitions.keySet().contains(materialsProducerPropertyId)) { + throw new ContractException(PropertyError.UNKNOWN_PROPERTY_ID, materialsProducerPropertyId); + } + } + + private void validateMaterialsProducerPropertyIdIsUnknown(MaterialsProducerPropertyId materialsProducerPropertyId) { + if (materialsProducerPropertyDefinitions.keySet().contains(materialsProducerPropertyId)) { + throw new ContractException(MaterialsError.DUPLICATE_MATERIALS_PRODUCER_PROPERTY_ID); + } + } + + private void validateNewBatchPropertyId(final MaterialId materialId, final BatchPropertyId batchPropertyId) { + if (batchPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + + final Map map = batchPropertyDefinitions.get(materialId); + if (map == null || map.containsKey(batchPropertyId)) { + throw new ContractException(PropertyError.DUPLICATE_PROPERTY_DEFINITION); + } + + } + + private void validateNewMaterialId(final MaterialId materialId) { + if (materialId == null) { + throw new ContractException(MaterialsError.NULL_MATERIAL_ID); + } + + if (materialIds.contains(materialId)) { + throw new ContractException(MaterialsError.DUPLICATE_MATERIAL, materialId); + } + } + + private void validateNewMaterialsProducerId(final MaterialsProducerId materialsProducerId) { + if (materialsProducerId == null) { + throw new ContractException(MaterialsError.NULL_MATERIALS_PRODUCER_ID); + } + + if (materialsProducerMap.containsKey(materialsProducerId)) { + throw new ContractException(MaterialsError.DUPLICATE_MATERIALS_PRODUCER_ID, materialsProducerId); + } + } + + private void validateNonnegativeFiniteMaterialAmount(final double amount) { + if (!Double.isFinite(amount)) { + throw new ContractException(MaterialsError.NON_FINITE_MATERIAL_AMOUNT); + } + if (amount < 0) { + throw new ContractException(MaterialsError.NEGATIVE_MATERIAL_AMOUNT); + } + } + + private void validateNonnegativeResourceAmount(final long amount) { + if (amount < 0) { + throw new ContractException(ResourceError.NEGATIVE_RESOURCE_AMOUNT); + } + } + + /* + * Preconditions : the batches must exist + */ + private void validateProducersMatchForShift(final BatchId sourceBatchId, final BatchId destinationBatchId) { + final MaterialsProducerId sourceMaterialsProducerId = batchRecords + .get(sourceBatchId).materialsProducerRecord.materialProducerId; + final MaterialsProducerId destinationMaterialsProducerId = batchRecords + .get(destinationBatchId).materialsProducerRecord.materialProducerId; + + if (!sourceMaterialsProducerId.equals(destinationMaterialsProducerId)) { + throw new ContractException(MaterialsError.BATCH_SHIFT_WITH_MULTIPLE_OWNERS); + } + + } + + private void validatePropertyMutability(final PropertyDefinition propertyDefinition) { + if (!propertyDefinition.propertyValuesAreMutable()) { + throw new ContractException(PropertyError.IMMUTABLE_VALUE); + } + } + + private void validateRegionId(final RegionId regionId) { + + if (regionId == null) { + throw new ContractException(RegionError.NULL_REGION_ID); + } + + if (!regionsDataManager.regionIdExists(regionId)) { + throw new ContractException(RegionError.UNKNOWN_REGION_ID, regionId); + } + } + + private void validateResourceAdditionValue(final long currentResourceLevel, final long amount) { + try { + Math.addExact(currentResourceLevel, amount); + } catch (final ArithmeticException e) { + throw new ContractException(ResourceError.RESOURCE_ARITHMETIC_EXCEPTION); + } + } + + private void validateResourceId(final ResourceId resourceId) { + if (resourceId == null) { + throw new ContractException(ResourceError.NULL_RESOURCE_ID); + } + + if (!resourcesDataManager.resourceIdExists(resourceId)) { + throw new ContractException(ResourceError.UNKNOWN_RESOURCE_ID, resourceId); + } + } + + private void validateStageConversionInfoNotNull(final StageConversionInfo stageConversionInfo) { + if (stageConversionInfo == null) { + throw new ContractException(MaterialsError.NULL_STAGE_CONVERSION_INFO); + } + } + + private void validateStageId(final StageId stageId) { + if (stageId == null) { + throw new ContractException(MaterialsError.NULL_STAGE_ID); + } + + if (!stageRecords.containsKey(stageId)) { + throw new ContractException(MaterialsError.UNKNOWN_STAGE_ID, stageId); + } + } + + private void validateStageIsNotOffered(final StageId stageId) { + + final StageRecord stageRecord = stageRecords.get(stageId); + + if (stageRecord.offered) { + throw new ContractException(MaterialsError.OFFERED_STAGE_UNALTERABLE); + } + } + + private void validateStageIsOffered(final StageId stageId) { + StageRecord stageRecord = stageRecords.get(stageId); + if (!stageRecord.offered) { + throw new ContractException(MaterialsError.UNOFFERED_STAGE_NOT_TRANSFERABLE); + } + } + + private void validateStageNotOwnedByReceivingMaterialsProducer(final StageId stageId, + final MaterialsProducerId materialsProducerId) { + final MaterialsProducerId stageProducer = stageRecords.get(stageId).materialsProducerRecord.materialProducerId; + if (materialsProducerId.equals(stageProducer)) { + throw new ContractException(MaterialsError.REFLEXIVE_STAGE_TRANSFER); + } + } + + private void validateValueCompatibility(final Object propertyId, final PropertyDefinition propertyDefinition, + final Object propertyValue) { + if (!propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())) { + throw new ContractException(PropertyError.INCOMPATIBLE_VALUE, + "Property value " + propertyValue + " is not of type " + propertyDefinition.getType().getName() + + " and does not match definition of " + propertyId); + } + } + + private void verifyNonDefaultBatchChecks(MaterialId materialId) { + + boolean missingPropertyAssignments = false; + + boolean[] checkArray = nonDefaultChecksForBatches.get(materialId); + + for (boolean element : checkArray) { + if (!element) { + missingPropertyAssignments = true; + break; + } + } + + if (missingPropertyAssignments) { + StringBuilder sb = new StringBuilder(); + int index = -1; + boolean firstMember = true; + Map propertyMap = nonDefaultBearingBatchPropertyIds.get(materialId); + for (BatchPropertyId batchPropertyId : propertyMap.keySet()) { + index++; + if (!checkArray[index]) { + if (firstMember) { + firstMember = false; + } else { + sb.append(", "); + } + sb.append(batchPropertyId); + } + } + throw new ContractException(PropertyError.INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT, sb.toString()); + } + } + + private void verifyNonDefaultChecksForProducers() { + + boolean missingPropertyAssignments = false; + + for (boolean nonDefaultChecksForProducer : nonDefaultChecksForProducers) { + if (!nonDefaultChecksForProducer) { + missingPropertyAssignments = true; + break; + } + } + + if (missingPropertyAssignments) { + StringBuilder sb = new StringBuilder(); + int index = -1; + boolean firstMember = true; + for (MaterialsProducerPropertyId materialsProducerPropertyId : nonDefaultBearingProducerPropertyIds + .keySet()) { + index++; + if (!nonDefaultChecksForProducers[index]) { + if (firstMember) { + firstMember = false; + } else { + sb.append(", "); + } + sb.append(materialsProducerPropertyId); + } + } + throw new ContractException(PropertyError.INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT, sb.toString()); + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("MaterialsDataManager [batchPropertyDefinitions="); + builder.append(batchPropertyDefinitions); + builder.append(", nextBatchRecordId="); + builder.append(nextBatchRecordId); + builder.append(", nextStageRecordId="); + builder.append(nextStageRecordId); + builder.append(", materialIds="); + builder.append(materialIds); + builder.append(", materialsProducerPropertyDefinitions="); + builder.append(materialsProducerPropertyDefinitions); + builder.append(", materialsProducerMap="); + builder.append(materialsProducerMap); + builder.append(", materialsProducerPropertyMap="); + builder.append(materialsProducerPropertyMap); + builder.append(", batchPropertyMap="); + builder.append(batchPropertyMap); + builder.append(", batchRecords="); + builder.append(batchRecords); + builder.append(", resourceIds="); + builder.append(resourceIds); + builder.append(", stageRecords="); + builder.append(stageRecords); + builder.append("]"); + return builder.toString(); + + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/datamangers/MaterialsPluginData.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/datamangers/MaterialsPluginData.java new file mode 100644 index 000000000..9f930b93a --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/datamangers/MaterialsPluginData.java @@ -0,0 +1,1896 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.datamangers; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.math3.util.FastMath; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.PluginDataBuilder; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsError; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.StageId; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceError; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * An immutable container of the initial state materials producers. It contains: + *
    + *
      + *
    • material producer ids
    • + *
    • materials producer property definitions
    • + *
    • materials producer property values
    • + *
    • materials producer resource levels
    • + *
    • stage ids
    • + *
    • batch ids
    • + *
    • batch property definitions
    • + *
    • batch property values
    • + *
    • batch stage assignments
    • + *
    + * Construction is conducted via the contained builder class. Builder methods + * can be invoked in any order and relational validation is delayed until + * build() is invoked. + */ +@Immutable +public final class MaterialsPluginData implements PluginData { + + /** + * Builder class for MaterialsInitialization + */ + public static class Builder implements PluginDataBuilder { + private Data data; + + private void ensureDataMutability() { + if (data.locked) { + data = new Data(data); + data.locked = false; + } + } + + private void ensureImmutability() { + if (!data.locked) { + data.locked = true; + } + } + + private Builder(Data data) { + this.data = data; + } + + /** + * Adds the batch. Duplicate inputs override previous inputs. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_BATCH_ID} if + * the batch id is null
    • + *
    • {@linkplain MaterialsError#NULL_MATERIAL_ID} if + * the material id is null
    • + *
    • {@linkplain MaterialsError#NON_FINITE_MATERIAL_AMOUNT} + * if the material amount is infinite
    • + *
    • {@linkplain MaterialsError#NEGATIVE_MATERIAL_AMOUNT} + * if the material amount is negative
    • + *
    + */ + public Builder addBatch(final BatchId batchId, final MaterialId materialId, final double amount) { + ensureDataMutability(); + validateBatchIdNotNull(batchId); + validateMaterialIdNotNull(materialId); + validateBatchAmount(amount); + data.batchIds.add(batchId); + data.batchMaterials.put(batchId, materialId); + data.batchAmounts.put(batchId, amount); + return this; + } + + /** + * Adds the batch to inventory of the materials producer. Duplicate inputs + * override previous inputs. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_BATCH_ID} if + * the batch id is null
    • + *
    • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} + * if the materials producer id is null
    • + *
    + */ + public Builder addBatchToMaterialsProducerInventory(final BatchId batchId, + final MaterialsProducerId materialsProducerId) { + ensureDataMutability(); + validateBatchIdNotNull(batchId); + validateMaterialsProducerIdNotNull(materialsProducerId); + Set batches = data.materialsProducerInventoryBatches.get(materialsProducerId); + if (batches == null) { + batches = new LinkedHashSet<>(); + data.materialsProducerInventoryBatches.put(materialsProducerId, batches); + } + batches.add(batchId); + return this; + } + + /** + * Adds a batch to stage. Duplicate inputs override previous inputs. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_BATCH_ID} if + * the batch id is null
    • + *
    • {@linkplain MaterialsError#NULL_STAGE_ID} if + * the stage id is null
    • + *
    + */ + public Builder addBatchToStage(final StageId stageId, final BatchId batchId) { + ensureDataMutability(); + validateStageIdNotNull(stageId); + validateBatchIdNotNull(batchId); + + Set batches = data.stageBatches.get(stageId); + if (batches == null) { + batches = new LinkedHashSet<>(); + data.stageBatches.put(stageId, batches); + } + batches.add(batchId); + + return this; + } + + /** + * Adds a batch to stage. Duplicate inputs override previous inputs. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_MATERIAL_ID} if + * the material id is null
    • + *
    + */ + public Builder addMaterial(final MaterialId materialId) { + ensureDataMutability(); + validateMaterialIdNotNull(materialId); + data.materialIds.add(materialId); + return this; + } + + /** + * Adds a batch to stage. Duplicate inputs override previous inputs. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} + * if the material producer id is null
    • + *
    + */ + public Builder addMaterialsProducerId(final MaterialsProducerId materialsProducerId) { + ensureDataMutability(); + validateMaterialsProducerIdNotNull(materialsProducerId); + data.materialsProducerIds.add(materialsProducerId); + return this; + } + + /** + * Adds a stage. + * + * @throws ContractException {@linkplain MaterialsError#NULL_STAGE_ID} if the + * stage id is null + */ + public Builder addStage(final StageId stageId, final boolean offered) { + ensureDataMutability(); + validateStageIdNotNull(stageId); + data.stageIds.add(stageId); + data.stageOffers.put(stageId, offered); + return this; + } + + /** + * Associates a stage with a materials producer. Duplicate inputs override + * previous inputs. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_STAGE_ID} if + * the stage id is null
    • + *
    • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} + * if the materials producer id is null
    • + *
    + */ + public Builder addStageToMaterialProducer(final StageId stageId, + final MaterialsProducerId materialsProducerId) { + ensureDataMutability(); + validateStageIdNotNull(stageId); + validateMaterialsProducerIdNotNull(materialsProducerId); + data.stageIds.add(stageId); + Set stages = data.materialsProducerStages.get(materialsProducerId); + if (stages == null) { + stages = new LinkedHashSet<>(); + data.materialsProducerStages.put(materialsProducerId, stages); + } + stages.add(stageId); + return this; + } + + /** + * Builds the MaterialsPluginData from the collected inputs + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#UNKNOWN_MATERIAL_ID} + * if a batch property is associated with a material + * id that was not properly added
    • + *
    • {@linkplain PropertyError#INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT} + * if a batch is added without assigned property + * values for each property definition that lacks a + * default value
    • + *
    • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} + * if a materials property value is associated with a + * materials producer id that was not properly + * added
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if a materials property value is associated with a + * materials producer property id that was not + * properly defined
    • + *
    • {@linkplain PropertyError#INCOMPATIBLE_VALUE} + * if a materials property value is associated with a + * value that is not compatible with the corresponding + * property definition
    • + *
    • {@linkplain PropertyError#INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT} + * if a materials property is defined without a + * default value and there is not an assigned property + * value for each added materials producer
    • + *
    • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} + * if a materials resource level is set for a material + * producer id that was not properly added
    • + *
    • {@linkplain MaterialsError#UNKNOWN_MATERIAL_ID} + * if a batch is associated with at material that was + * not properly added
    • + *
    • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} + * if a batch is associated with at material producer + * that was not properly added
    • + *
    • {@linkplain MaterialsError#UNKNOWN_BATCH_ID} if + * a batch property is associated with batch id that + * was not properly added
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if a batch property is associated with batch + * property id that was not properly defined
    • + *
    • {@linkplain PropertyError#INCOMPATIBLE_VALUE} + * if a batch property value is incompatible with the + * corresponding property definition
    • + *
    • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} + * if a stage is associated with a materials producer + * id that was not properly added
    • + *
    • {@linkplain MaterialsError#UNKNOWN_STAGE_ID} if + * a batch is associated with a stage id that was not + * properly added
    • + *
    • {@linkplain MaterialsError#UNKNOWN_BATCH_ID} if + * a stage is associated with a batch id that was not + * properly added
    • + *
    • {@linkplain MaterialsError#BATCH_ALREADY_STAGED} + * if a batch is associated with more than one + * stage
    • + *
    • {@linkplain MaterialsError#BATCH_STAGED_TO_DIFFERENT_OWNER} + * if a batch is associated with a stage that is not + * owned by the same materials producer as the + * batch
    • + *
    • {@linkplain MaterialsError#NEXT_BATCH_ID_TOO_SMALL} + * if a batch is greater than or equal to the next + * batch id assigned for the entire plugin data
    • + *
    • {@linkplain MaterialsError#NEXT_STAGE_ID_TOO_SMALL} + * if a stage is greater than or equal to the next + * stage id assigned for the entire plugin data
    • + *
    + */ + @Override + public MaterialsPluginData build() { + + if (!data.locked) { + // sortData(); + validateData(); + } + ensureImmutability(); + return new MaterialsPluginData(data); + + } + + /** + * Adds a batch to stage. Duplicate inputs override previous inputs. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the batch property id is null
    • + *
    • {@linkplain MaterialsError#NULL_MATERIAL_ID} if + * the material id is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_DEFINITION} + * if the property definition is null
    • + *
    + */ + public Builder defineBatchProperty(final MaterialId materialId, final BatchPropertyId batchPropertyId, + final PropertyDefinition propertyDefinition) { + ensureDataMutability(); + validateBatchPropertyIdNotNull(batchPropertyId); + validateMaterialIdNotNull(materialId); + validatePropertyDefinitionNotNull(propertyDefinition); + Map propertyDefinitionsMap = data.batchPropertyDefinitions + .get(materialId); + if (propertyDefinitionsMap == null) { + propertyDefinitionsMap = new LinkedHashMap<>(); + data.batchPropertyDefinitions.put(materialId, propertyDefinitionsMap); + } + propertyDefinitionsMap.put(batchPropertyId, propertyDefinition); + return this; + } + + /** + * Adds a batch to stage. Duplicate inputs override previous inputs. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the materials producer property id is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_DEFINITION} + * if the property definition is null
    • + *
    + */ + public Builder defineMaterialsProducerProperty(final MaterialsProducerPropertyId materialsProducerPropertyId, + final PropertyDefinition propertyDefinition) { + ensureDataMutability(); + validateMaterialsProducerPropertyIdNotNull(materialsProducerPropertyId); + validatePropertyDefinitionNotNull(propertyDefinition); + data.materialsProducerPropertyDefinitions.put(materialsProducerPropertyId, propertyDefinition); + return this; + } + + /** + * Set the batch property value. Duplicate inputs override previous inputs. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_BATCH_ID} if + * the batch id is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the batch property id is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_VALUE} + * if the batch property value is null
    • + *
    + */ + public Builder setBatchPropertyValue(final BatchId batchId, final BatchPropertyId batchPropertyId, + final Object batchPropertyValue) { + ensureDataMutability(); + validateBatchIdNotNull(batchId); + validateBatchPropertyIdNotNull(batchPropertyId); + validateBatchPropertyValueNotNull(batchPropertyValue); + Map propertyMap = data.batchPropertyValues.get(batchId); + if (propertyMap == null) { + propertyMap = new LinkedHashMap<>(); + data.batchPropertyValues.put(batchId, propertyMap); + } + propertyMap.put(batchPropertyId, batchPropertyValue); + return this; + } + + /** + * Set the materials producer property value. Duplicate inputs override previous + * inputs. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} + * if the materials producer id is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the materials producer property id is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_VALUE} + * if the materials producer property value is + * null
    • + *
    + */ + public Builder setMaterialsProducerPropertyValue(final MaterialsProducerId materialsProducerId, + final MaterialsProducerPropertyId materialsProducerPropertyId, + final Object materialsProducerPropertyValue) { + ensureDataMutability(); + validateMaterialsProducerIdNotNull(materialsProducerId); + validateMaterialsProducerPropertyIdNotNull(materialsProducerPropertyId); + validateMaterialsProducerPropertyValueNotNull(materialsProducerPropertyValue); + Map propertyMap = data.materialsProducerPropertyValues + .get(materialsProducerId); + if (propertyMap == null) { + propertyMap = new LinkedHashMap<>(); + data.materialsProducerPropertyValues.put(materialsProducerId, propertyMap); + } + propertyMap.put(materialsProducerPropertyId, materialsProducerPropertyValue); + return this; + } + + /** + * Set the materials producer resource value. Duplicate inputs override previous + * inputs. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} + * if the materials producer id is null
    • + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#NEGATIVE_RESOURCE_AMOUNT} + * if the resource amount is negative
    • + *
    + */ + public Builder setMaterialsProducerResourceLevel(final MaterialsProducerId materialsProducerId, + final ResourceId resourceId, final long amount) { + ensureDataMutability(); + validateMaterialsProducerIdNotNull(materialsProducerId); + validateResourceIdNotNull(resourceId); + validateResourceAmount(amount); + Map resourceLevelMap = data.materialsProducerResourceLevels.get(materialsProducerId); + if (resourceLevelMap == null) { + resourceLevelMap = new LinkedHashMap<>(); + data.materialsProducerResourceLevels.put(materialsProducerId, resourceLevelMap); + } + resourceLevelMap.put(resourceId, amount); + return this; + } + + /** + * Sets the next available batch id. This value needs to exceed all extant batch + * ids. If the nextBatchRecordId is not set explicitly, the nextBatchRecordId is + * assigned to either zero or the next integer value that exceeds the highest + * valued batch added to this builder. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NEGATIVE_BATCH_ID} + * if the next batch record id is negative
    • + *
    + */ + public Builder setNextBatchRecordId(int nextBatchRecordId) { + ensureDataMutability(); + validateBatchIdValue(nextBatchRecordId); + data.nextBatchRecordId = nextBatchRecordId; + return this; + } + + /** + * Sets the next available stage id. This value needs to exceed all extant stage + * ids. If the nextStageRecordId is not set explicitly, the nextStageRecordId is + * assigned to either zero or the next integer value that exceeds the highest + * valued batch added to this builder. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NEGATIVE_BATCH_ID} + * if the next stage record id is negative
    • + *
    + */ + public Builder setNextStageRecordId(int nextStageRecordId) { + ensureDataMutability(); + validateStageIdValue(nextStageRecordId); + data.nextStageRecordId = nextStageRecordId; + return this; + } + + /* + * Set stageIds; + * + * Defines the valid stage ids, no test needed + */ + private void validateData_StageIds() { + + } + + /* + * Set batchIds; + * + * Defines the valid batch ids, no test needed + */ + private void validateData_BatchIds() { + + } + + /* + * Set materialIds; + * + * Defines the valid material ids, no test needed + */ + private void validateData_MaterialIds() { + + } + + /* + * Set materialsProducerIds; + * + * Defines the valid materials producer ids, no test needed + */ + private void validateData_MaterialsProducerIds() { + + } + + /* + * Map + * materialsProducerPropertyDefinitions; + * + * Defines the valid materials producer property ids, no test needed + */ + private void validateData_MaterialsProducerPropertyDefinitions() { + + } + + /* + * Map> + * batchPropertyDefinitions; + * + * Defines the valid batch property ids. + * + * Need to show that each material id is valid + * + */ + private void validateData_BatchPropertyDefinitions() { + for (final MaterialId materialId : data.batchPropertyDefinitions.keySet()) { + if (!data.materialIds.contains(materialId)) { + throw new ContractException(MaterialsError.UNKNOWN_MATERIAL_ID, + materialId + " in batch property definitions"); + } + } + } + + /* + * Map> + * materialsProducerPropertyValues; + * + * Need to show that 1)each MaterialsProducerId is valid 2)each + * MaterialsProducerPropertyId is valid 3) each value is compatible with the + * associated property definition and 4) that there are sufficient values to + * cover any property definitions that do not have a default value + * + */ + private void validateData_MaterialsProducerPropertyValues() { + for (final MaterialsProducerId materialsProducerId : data.materialsProducerPropertyValues.keySet()) { + if (!data.materialsProducerIds.contains(materialsProducerId)) { + throw new ContractException(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, + materialsProducerId + " in materials producer property values"); + } + final Map propMap = data.materialsProducerPropertyValues + .get(materialsProducerId); + for (final MaterialsProducerPropertyId materialsProducerPropertyId : propMap.keySet()) { + final PropertyDefinition propertyDefinition = data.materialsProducerPropertyDefinitions + .get(materialsProducerPropertyId); + if (propertyDefinition == null) { + throw new ContractException(PropertyError.UNKNOWN_PROPERTY_ID, + materialsProducerId + " in materials producer property values"); + } + final Object propertyValue = propMap.get(materialsProducerPropertyId); + if (!propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())) { + throw new ContractException(PropertyError.INCOMPATIBLE_VALUE, + materialsProducerId + ": " + materialsProducerPropertyId + ": " + propertyValue); + } + } + } + + /* + * For every materials producer property definition that has a null default + * value, ensure that all corresponding materials producer property values are + * not null and repair the definition. + */ + for (final MaterialsProducerPropertyId materialsProducerPropertyId : data.materialsProducerPropertyDefinitions + .keySet()) { + final PropertyDefinition propertyDefinition = data.materialsProducerPropertyDefinitions + .get(materialsProducerPropertyId); + if (!propertyDefinition.getDefaultValue().isPresent()) { + for (final MaterialsProducerId materialsProducerId : data.materialsProducerIds) { + Object propertyValue = null; + final Map propertyValueMap = data.materialsProducerPropertyValues + .get(materialsProducerId); + if (propertyValueMap != null) { + propertyValue = propertyValueMap.get(materialsProducerPropertyId); + } + if (propertyValue == null) { + throw new ContractException(PropertyError.INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT, + materialsProducerPropertyId); + } + } + } + } + } + + /* + * Map> + * materialsProducerResourceLevels; + * + * Need to show that each MaterialsProducerId is valid + */ + private void validateData_MaterialsProducerResourceLevels() { + + for (final MaterialsProducerId materialsProducerId : data.materialsProducerResourceLevels.keySet()) { + if (!data.materialsProducerIds.contains(materialsProducerId)) { + throw new ContractException(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, + materialsProducerId + " in materials producer resource levels"); + } + } + } + + /* + * Map batchAmounts; + * + * Need to show that each BatchId is valid + */ + private void validateData_BatchAmounts() { + for (final BatchId batchId : data.batchAmounts.keySet()) { + + if (!data.batchIds.contains(batchId)) { + throw new ContractException(MaterialsError.UNKNOWN_BATCH_ID, batchId + " in batch amounts"); + } + } + } + + /* + * Map batchMaterials; + * + * Need to show that 1) each BatchId is valid and 2) each material id is valid + */ + private void validateData_BatchMaterials() { + for (final BatchId batchId : data.batchMaterials.keySet()) { + if (!data.batchIds.contains(batchId)) { + throw new ContractException(MaterialsError.UNKNOWN_BATCH_ID, batchId + " in batch materials"); + } + + final MaterialId materialId = data.batchMaterials.get(batchId); + + if (!data.materialIds.contains(materialId)) { + throw new ContractException(MaterialsError.UNKNOWN_MATERIAL_ID, + materialId + " in batch addition of " + batchId); + } + } + } + + /* + * Map> stageBatches; + * + * Need to show that 1) each stage id is valid and 2) each batch id is valid + */ + private void validateData_StageBatches() { + for (final StageId stageId : data.stageBatches.keySet()) { + if (!data.stageIds.contains(stageId)) { + throw new ContractException(MaterialsError.UNKNOWN_STAGE_ID, + stageId + " in batch additions to stages"); + } + final Set batches = data.stageBatches.get(stageId); + for (final BatchId batchId : batches) { + if (!data.batchIds.contains(batchId)) { + throw new ContractException(MaterialsError.UNKNOWN_BATCH_ID, + stageId + ": " + batchId + " in batch additions to stages"); + } + } + } + } + + /* + * private final Map stageOffers; + * + * Need to show that each stage id is valid + */ + private void validateData_stageOffers() { + for (final StageId stageId : data.stageOffers.keySet()) { + if (!data.stageIds.contains(stageId)) { + throw new ContractException(MaterialsError.UNKNOWN_STAGE_ID, stageId + " in stage offers"); + } + } + } + + /* + * Map> materialsProducerInventoryBatches; + * + * + * Need to validate that 1) each producer id is valid, 2) each batch id is valid + */ + private void validateData_MaterialsProducerInventoryBatches() { + for (final MaterialsProducerId materialsProducerId : data.materialsProducerInventoryBatches.keySet()) { + if (!data.materialsProducerIds.contains(materialsProducerId)) { + throw new ContractException(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, + materialsProducerId + " in batch materials producer inventory batches"); + } + for (BatchId batchId : data.materialsProducerInventoryBatches.get(materialsProducerId)) { + if (!data.batchIds.contains(batchId)) { + throw new ContractException(MaterialsError.UNKNOWN_BATCH_ID, + batchId + " in batch materials producer inventory batches"); + } + } + } + } + + /* + * Map> batchPropertyValues; + * + * + * Need to show that 1) each batch id is valid, 2) each batch property id is + * valid, 3) each value is compatible with the property definition and 4) that + * there are sufficient values to cover any property definitions that do not + * have a default value + */ + private void validateData_BatchPropertyValues() { + for (final BatchId batchId : data.batchPropertyValues.keySet()) { + if (!data.batchIds.contains(batchId)) { + throw new ContractException(MaterialsError.UNKNOWN_BATCH_ID, batchId + " in batch property values"); + } + + final MaterialId materialId = data.batchMaterials.get(batchId); + final Map defMap = data.batchPropertyDefinitions.get(materialId); + + final Map propMap = data.batchPropertyValues.get(batchId); + for (final BatchPropertyId batchPropertyId : propMap.keySet()) { + if (defMap == null) { + throw new ContractException(PropertyError.UNKNOWN_PROPERTY_ID, + batchPropertyId + " in batch property values"); + } + final PropertyDefinition propertyDefinition = defMap.get(batchPropertyId); + if (propertyDefinition == null) { + throw new ContractException(PropertyError.UNKNOWN_PROPERTY_ID, + batchPropertyId + " in batch property values"); + } + final Object propertyValue = propMap.get(batchPropertyId); + if (!propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())) { + throw new ContractException(PropertyError.INCOMPATIBLE_VALUE, + batchId + ": " + batchPropertyId + ": " + propertyValue); + } + } + } + + /* + * establish two maps to aid in checking batch property coverage for those + * property definitions that do not have defaults + */ + Map> nonDefaultBatchPropertiesMap = new LinkedHashMap<>(); + Map nonDefaultBatchCheckArrayMap = new LinkedHashMap<>(); + + for (final MaterialId materialId : data.materialIds) { + Map nonDefaultBatchProperties = new LinkedHashMap<>(); + nonDefaultBatchPropertiesMap.put(materialId, nonDefaultBatchProperties); + final Map propertyDefinitionMap = data.batchPropertyDefinitions + .get(materialId); + if (propertyDefinitionMap != null) { + for (final BatchPropertyId batchPropertyId : propertyDefinitionMap.keySet()) { + final PropertyDefinition propertyDefinition = propertyDefinitionMap.get(batchPropertyId); + if (!propertyDefinition.getDefaultValue().isPresent()) { + nonDefaultBatchProperties.put(batchPropertyId, nonDefaultBatchProperties.size()); + } + } + } + nonDefaultBatchCheckArrayMap.put(materialId, new boolean[nonDefaultBatchProperties.size()]); + } + + for (final BatchId batchId : data.batchIds) { + + final MaterialId materialId = data.batchMaterials.get(batchId); + Map propertyIndexMap = nonDefaultBatchPropertiesMap.get(materialId); + boolean[] checkArray = nonDefaultBatchCheckArrayMap.get(materialId); + // clear the check array + for (int i = 0; i < checkArray.length; i++) { + checkArray[i] = false; + } + + // fill the check array + final Map propMap = data.batchPropertyValues.get(batchId); + if (propMap != null) { + for (final BatchPropertyId batchPropertyId : propMap.keySet()) { + Integer checkIndex = propertyIndexMap.get(batchPropertyId); + if (checkIndex != null) { + checkArray[checkIndex] = true; + } + } + } + // show the check array contains no false values + for (boolean element : checkArray) { + if (!element) { + throw new ContractException(PropertyError.INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT); + } + } + } + } + + /* + * Map> materialsProducerStages; + * + * Need to show that 1) each producer id is valid 2) that each stage id is valid + * and 3) that every stage id is associated with exactly one materials producer + * id + * + */ + private void validateData_MaterialsProducerStages() { + for (final MaterialsProducerId materialsProducerId : data.materialsProducerStages.keySet()) { + if (!data.materialsProducerIds.contains(materialsProducerId)) { + throw new ContractException(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, + materialsProducerId + " in materials producer stages"); + } + for (StageId stageId : data.materialsProducerStages.get(materialsProducerId)) { + if (!data.stageIds.contains(stageId)) { + throw new ContractException(MaterialsError.UNKNOWN_STAGE_ID, + stageId + " in materials producer stages"); + } + } + } + + Map smMap = new LinkedHashMap<>(); + for (final MaterialsProducerId materialsProducerId : data.materialsProducerStages.keySet()) { + + Set stages = data.materialsProducerStages.get(materialsProducerId); + for (StageId stageId : stages) { + MaterialsProducerId replacedMaterialsProducerId = smMap.put(stageId, materialsProducerId); + if (replacedMaterialsProducerId != null) { + throw new ContractException(MaterialsError.DUPLICATE_STAGE_ASSIGNMENT, + "stage " + stageId + " has been assigned to multiple materials producers"); + } + } + } + + for (StageId stageId : data.stageIds) { + if (!smMap.containsKey(stageId)) { + throw new ContractException(MaterialsError.STAGE_WITHOUT_MATERIALS_PRODUCER, "stage " + stageId); + } + } + } + + /* + * Map> materialsProducerInventoryBatches; + * Map> stageBatches; + * + * Need to show that each batch is assigned to either a single stage or to a + * single inventory + * + */ + private void validateData_BatchLocations() { + /* + * Show that every batch is in exactly one inventory or on exactly one stage + */ + Map bmMap = new LinkedHashMap<>(); + for (MaterialsProducerId materialsProducerId : data.materialsProducerInventoryBatches.keySet()) { + Set batches = data.materialsProducerInventoryBatches.get(materialsProducerId); + for (BatchId batchId : batches) { + MaterialsProducerId replacedMaterialsProducerId = bmMap.put(batchId, materialsProducerId); + if (replacedMaterialsProducerId != null) { + throw new ContractException(MaterialsError.BATCH_ALREADY_INVENTORIED, + batchId + " has been assigned to multiple materials producer inventories"); + } + } + } + + Map bsMap = new LinkedHashMap<>(); + for (final StageId stageId : data.stageBatches.keySet()) { + final Set batches = data.stageBatches.get(stageId); + for (final BatchId batchId : batches) { + StageId replaceStageId = bsMap.put(batchId, stageId); + if (replaceStageId != null) { + throw new ContractException(MaterialsError.BATCH_ALREADY_STAGED, + batchId + " has been assigned to multiple stages"); + } + } + } + + for (BatchId batchId : data.batchIds) { + boolean inventoried = bmMap.containsKey(batchId); + boolean staged = bsMap.containsKey(batchId); + + if (!inventoried && !staged) { + throw new ContractException(MaterialsError.BATCH_NOT_OWNED, batchId); + } + if (inventoried && staged) { + throw new ContractException(MaterialsError.BATCH_IN_MULTIPLE_LOCATIONS, batchId); + } + } + } + + /* + * int nextBatchRecordId + * + * If the next batch record id has not been set (is negative), we are free to + * set it to the next available int value higher than all other batch ids. + * Otherwise, it must exceed those existing batch ids. + * + */ + private void validateData_NextBatchRecordId() { + if (data.nextBatchRecordId < 0) { + for (final BatchId batchId : data.batchIds) { + data.nextBatchRecordId = FastMath.max(data.nextBatchRecordId, batchId.getValue()); + } + data.nextBatchRecordId++; + } else { + for (final BatchId batchId : data.batchIds) { + if (batchId.getValue() >= data.nextBatchRecordId) { + throw new ContractException(MaterialsError.NEXT_BATCH_ID_TOO_SMALL); + } + } + } + } + + /* + * int nextStageRecordId + * + * If the next batch record id has not been set (is negative), we are free to + * set it to the next available int value higher than all other batch ids. + * Otherwise, it must exceed those existing batch ids. + * + */ + private void validateData_NextStageRecordId() { + if (data.nextStageRecordId < 0) { + for (final StageId stageId : data.stageIds) { + data.nextStageRecordId = FastMath.max(data.nextStageRecordId, stageId.getValue()); + } + data.nextStageRecordId++; + } else { + for (final StageId stageId : data.stageIds) { + if (stageId.getValue() >= data.nextStageRecordId) { + throw new ContractException(MaterialsError.NEXT_STAGE_ID_TOO_SMALL); + } + } + } + } + + /* + * + * + * Validates and makes a few adjustments to the data as needed. + */ + private void validateData() { + + /* + * We will work with each of the data structures, validating them in dependency + * order. We will take advantage of the validation performed during data + * collection, such as null checks and will assume that those checks are + * functioning properly. + * + */ + validateData_StageIds(); + validateData_BatchIds(); + validateData_MaterialIds(); + validateData_MaterialsProducerIds(); + validateData_MaterialsProducerPropertyDefinitions(); + validateData_BatchPropertyDefinitions(); + validateData_MaterialsProducerPropertyValues(); + validateData_MaterialsProducerResourceLevels(); + validateData_BatchAmounts(); + validateData_BatchMaterials(); + validateData_StageBatches(); + validateData_stageOffers(); + validateData_MaterialsProducerInventoryBatches(); + validateData_BatchPropertyValues(); + validateData_MaterialsProducerStages(); + validateData_BatchLocations(); + validateData_NextBatchRecordId(); + validateData_NextStageRecordId(); + } + + } + + private static class Data { + + private final Set materialsProducerIds; + + private final Set materialIds; + + private Map> batchPropertyDefinitions; + + private final Map materialsProducerPropertyDefinitions; + + private Map> materialsProducerPropertyValues; + + private final Map emptyMaterialsProducerPropertyValuesMap = Collections + .unmodifiableMap(new LinkedHashMap<>()); + + private Map> materialsProducerResourceLevels; + + private final Set batchIds; + + private final Set stageIds; + + private Map> batchPropertyValues; + + private final Map emptyBatchPropertyValues; + + private final Map stageOffers; + + private final Map batchMaterials; + + private final Map batchAmounts; + + private Map> materialsProducerStages; + + private Map> stageBatches; + + private Map> materialsProducerInventoryBatches; + + private int nextBatchRecordId = -1; + + private int nextStageRecordId = -1; + + private boolean locked; + + public Data() { + materialsProducerIds = new LinkedHashSet<>(); + + materialIds = new LinkedHashSet<>(); + + batchPropertyDefinitions = new LinkedHashMap<>(); + + materialsProducerPropertyDefinitions = new LinkedHashMap<>(); + + materialsProducerPropertyValues = new LinkedHashMap<>(); + + materialsProducerResourceLevels = new LinkedHashMap<>(); + + batchIds = new LinkedHashSet<>(); + + batchMaterials = new LinkedHashMap<>(); + + batchAmounts = new LinkedHashMap<>(); + + materialsProducerInventoryBatches = new LinkedHashMap<>(); + + batchPropertyValues = new LinkedHashMap<>(); + + stageIds = new LinkedHashSet<>(); + + stageOffers = new LinkedHashMap<>(); + + materialsProducerStages = new LinkedHashMap<>(); + + stageBatches = new LinkedHashMap<>(); + + emptyBatchPropertyValues = Collections.unmodifiableMap(new LinkedHashMap<>()); + } + + public Data(Data data) { + + materialsProducerIds = new LinkedHashSet<>(data.materialsProducerIds); + + materialIds = new LinkedHashSet<>(data.materialIds); + + batchPropertyDefinitions = new LinkedHashMap<>(); + for (MaterialId materialId : data.batchPropertyDefinitions.keySet()) { + Map map = data.batchPropertyDefinitions.get(materialId); + Map newMap = new LinkedHashMap<>(map); + batchPropertyDefinitions.put(materialId, newMap); + } + + materialsProducerPropertyDefinitions = new LinkedHashMap<>(data.materialsProducerPropertyDefinitions); + + materialsProducerPropertyValues = new LinkedHashMap<>(); + for (MaterialsProducerId materialsProducerId : data.materialsProducerPropertyValues.keySet()) { + Map map = data.materialsProducerPropertyValues + .get(materialsProducerId); + Map newMap = new LinkedHashMap<>(map); + materialsProducerPropertyValues.put(materialsProducerId, newMap); + } + + materialsProducerResourceLevels = new LinkedHashMap<>(); + for (MaterialsProducerId materialsProducerId : data.materialsProducerResourceLevels.keySet()) { + Map map = data.materialsProducerResourceLevels.get(materialsProducerId); + Map newMap = new LinkedHashMap<>(map); + materialsProducerResourceLevels.put(materialsProducerId, newMap); + } + + batchIds = new LinkedHashSet<>(data.batchIds); + + batchMaterials = new LinkedHashMap<>(data.batchMaterials); + + batchAmounts = new LinkedHashMap<>(data.batchAmounts); + + materialsProducerInventoryBatches = new LinkedHashMap<>(); + + for (MaterialsProducerId materialsProducerId : data.materialsProducerInventoryBatches.keySet()) { + Set set = data.materialsProducerInventoryBatches.get(materialsProducerId); + Set newSet = new LinkedHashSet<>(set); + materialsProducerInventoryBatches.put(materialsProducerId, newSet); + } + + batchPropertyValues = new LinkedHashMap<>(); + for (BatchId batchId : data.batchPropertyValues.keySet()) { + Map map = data.batchPropertyValues.get(batchId); + Map newMap = new LinkedHashMap<>(map); + batchPropertyValues.put(batchId, newMap); + } + + stageIds = new LinkedHashSet<>(data.stageIds); + + stageOffers = new LinkedHashMap<>(data.stageOffers); + + materialsProducerStages = new LinkedHashMap<>(); + for (MaterialsProducerId materialsProducerId : data.materialsProducerStages.keySet()) { + Set set = data.materialsProducerStages.get(materialsProducerId); + Set newSet = new LinkedHashSet<>(set); + materialsProducerStages.put(materialsProducerId, newSet); + } + + stageBatches = new LinkedHashMap<>(); + for (StageId stageId : data.stageBatches.keySet()) { + Set set = data.stageBatches.get(stageId); + Set newSet = new LinkedHashSet<>(set); + stageBatches.put(stageId, newSet); + } + + emptyBatchPropertyValues = Collections.unmodifiableMap(new LinkedHashMap<>()); + + locked = data.locked; + } + + /* + * This is not a boiler plate hash code contract. See notes below + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + + result = prime * result + batchAmounts.hashCode(); + result = prime * result + batchIds.hashCode(); + result = prime * result + batchMaterials.hashCode(); + result = prime * result + materialsProducerInventoryBatches.hashCode(); + result = prime * result + batchPropertyDefinitions.hashCode(); + result = prime * result + materialIds.hashCode(); + result = prime * result + materialsProducerIds.hashCode(); + result = prime * result + materialsProducerPropertyDefinitions.hashCode(); + result = prime * result + stageBatches.hashCode(); + result = prime * result + stageIds.hashCode(); + result = prime * result + materialsProducerStages.hashCode(); + result = prime * result + stageOffers.hashCode(); + result = prime * result + nextBatchRecordId; + result = prime * result + nextStageRecordId; + result = prime * result + batchPropertyValues.hashCode(); + result = prime * result + materialsProducerPropertyValues.hashCode(); + result = prime * result + materialsProducerResourceLevels.hashCode(); + return result; + } + + /* + * This is not a boiler plate equals contract. See notes below + */ + @Override + public boolean equals(Object obj) { + + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + + /* + * We exclude the following fields: + * + * emptyBatchPropertyValues -- just an empty list + * + * emptyMaterialsProducerPropertyValuesMap -- just an empty map + * + * locked -- should be locked when equals is invoked + */ + // most of the fields use normal comparison + + if (!batchAmounts.equals(other.batchAmounts)) { + return false; + } + + if (!batchIds.equals(other.batchIds)) { + return false; + } + + if (!batchMaterials.equals(other.batchMaterials)) { + return false; + } + + if (!materialsProducerInventoryBatches.equals(other.materialsProducerInventoryBatches)) { + return false; + } + + if (!batchPropertyDefinitions.equals(other.batchPropertyDefinitions)) { + return false; + } + + if (!materialIds.equals(other.materialIds)) { + return false; + } + + if (!materialsProducerIds.equals(other.materialsProducerIds)) { + return false; + } + + if (!materialsProducerPropertyDefinitions.equals(other.materialsProducerPropertyDefinitions)) { + return false; + } + + if (!stageBatches.equals(other.stageBatches)) { + return false; + } + + if (!stageIds.equals(other.stageIds)) { + return false; + } + + if (!materialsProducerStages.equals(other.materialsProducerStages)) { + return false; + } + + if (!stageOffers.equals(other.stageOffers)) { + return false; + } + + if (nextBatchRecordId != other.nextBatchRecordId) { + return false; + } + if (nextStageRecordId != other.nextStageRecordId) { + return false; + } + + if (!batchPropertyValues.equals(other.batchPropertyValues)) { + return false; + } + + if (!materialsProducerPropertyValues.equals(other.materialsProducerPropertyValues)) { + return false; + } + + if (!materialsProducerResourceLevels.equals(other.materialsProducerResourceLevels)) { + return false; + } + + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Data [materialsProducerIds="); + builder.append(materialsProducerIds); + builder.append(", materialIds="); + builder.append(materialIds); + builder.append(", batchPropertyDefinitions="); + builder.append(batchPropertyDefinitions); + builder.append(", materialsProducerPropertyDefinitions="); + builder.append(materialsProducerPropertyDefinitions); + builder.append(", materialsProducerPropertyValues="); + builder.append(materialsProducerPropertyValues); + builder.append(", materialsProducerResourceLevels="); + builder.append(materialsProducerResourceLevels); + builder.append(", batchIds="); + builder.append(batchIds); + builder.append(", batchMaterials="); + builder.append(batchMaterials); + builder.append(", batchAmounts="); + builder.append(batchAmounts); + builder.append(", materialsProducerInventoryBatches="); + builder.append(materialsProducerInventoryBatches); + builder.append(", batchPropertyValues="); + builder.append(batchPropertyValues); + builder.append(", stageIds="); + builder.append(stageIds); + builder.append(", stageOffers="); + builder.append(stageOffers); + builder.append(", materialsProducerStages="); + builder.append(materialsProducerStages); + builder.append(", stageBatches="); + builder.append(stageBatches); + builder.append(", nextBatchRecordId="); + builder.append(nextBatchRecordId); + builder.append(", nextStageRecordId="); + builder.append(nextStageRecordId); + builder.append("]"); + return builder.toString(); + } + + } + + /** + * Returns a new builder instance + */ + public static Builder builder() { + return new Builder(new Data()); + } + + private static void validateBatchAmount(final double amount) { + if (!Double.isFinite(amount)) { + throw new ContractException(MaterialsError.NON_FINITE_MATERIAL_AMOUNT, amount); + } + if (amount < 0) { + throw new ContractException(MaterialsError.NEGATIVE_MATERIAL_AMOUNT, amount); + } + + } + + private static void validateBatchExists(final Data data, final Object batchId) { + + if (!data.batchIds.contains(batchId)) { + throw new ContractException(MaterialsError.UNKNOWN_BATCH_ID, batchId); + } + } + + private static void validateBatchIdNotNull(final BatchId batchId) { + if (batchId == null) { + throw new ContractException(MaterialsError.NULL_BATCH_ID); + } + } + + private static void validateBatchPropertyIdNotNull(final BatchPropertyId batchPropertyId) { + if (batchPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + } + + private static void validateBatchPropertyIsDefined(final Data data, final MaterialId materialId, + final BatchPropertyId batchPropertyId) { + validateBatchPropertyIdNotNull(batchPropertyId); + final Map map = data.batchPropertyDefinitions.get(materialId); + if (map == null) { + throw new ContractException(PropertyError.UNKNOWN_PROPERTY_ID, batchPropertyId); + } + final PropertyDefinition propertyDefinition = map.get(batchPropertyId); + if (propertyDefinition == null) { + throw new ContractException(PropertyError.UNKNOWN_PROPERTY_ID, batchPropertyId); + } + } + + private static void validateBatchPropertyValueNotNull(final Object batchPropertyValue) { + if (batchPropertyValue == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_VALUE); + } + } + + private static void validateMaterialExists(final Data data, final MaterialId materialId) { + if (materialId == null) { + throw new ContractException(MaterialsError.NULL_MATERIAL_ID); + } + if (!data.materialIds.contains(materialId)) { + throw new ContractException(MaterialsError.UNKNOWN_MATERIAL_ID, materialId); + } + } + + private static void validateMaterialIdNotNull(final MaterialId materialId) { + if (materialId == null) { + throw new ContractException(MaterialsError.NULL_MATERIAL_ID); + } + } + + private static void validateMaterialsProducerExists(final Data data, + final MaterialsProducerId materialsProducerId) { + if (materialsProducerId == null) { + throw new ContractException(MaterialsError.NULL_MATERIALS_PRODUCER_ID); + } + if (!data.materialsProducerIds.contains(materialsProducerId)) { + throw new ContractException(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, materialsProducerId); + } + } + + private static void validateMaterialsProducerIdNotNull(final MaterialsProducerId materialsProducerId) { + if (materialsProducerId == null) { + throw new ContractException(MaterialsError.NULL_MATERIALS_PRODUCER_ID); + } + } + + private static void validateMaterialsProducerPropertyIdNotNull( + final MaterialsProducerPropertyId materialsProducerPropertyId) { + if (materialsProducerPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + } + + private static void validateMaterialsProducerPropertyValueNotNull(final Object materialsProducerPropertyValue) { + if (materialsProducerPropertyValue == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_VALUE); + } + } + + private static void validatePropertyDefinitionNotNull(final PropertyDefinition propertyDefinition) { + if (propertyDefinition == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_DEFINITION); + } + } + + private static void validateResourceAmount(final long amount) { + if (amount < 0) { + throw new ContractException(ResourceError.NEGATIVE_RESOURCE_AMOUNT); + } + } + + private static void validateResourceIdNotNull(final ResourceId resourceId) { + if (resourceId == null) { + throw new ContractException(ResourceError.NULL_RESOURCE_ID); + } + } + + private static void validateStageExists(final Data data, final StageId stageId) { + validateStageIdNotNull(stageId); + if (!data.stageIds.contains(stageId)) { + throw new ContractException(MaterialsError.UNKNOWN_STAGE_ID, stageId); + } + } + + private static void validateBatchIdValue(int batchIdValue) { + if (batchIdValue < 0) { + throw new ContractException(MaterialsError.NEGATIVE_BATCH_ID, batchIdValue); + } + } + + private static void validateStageIdValue(int stageIdValue) { + if (stageIdValue < 0) { + throw new ContractException(MaterialsError.NEGATIVE_STAGE_ID, stageIdValue); + } + } + + private static void validateStageIdNotNull(final StageId stageId) { + if (stageId == null) { + throw new ContractException(MaterialsError.NULL_STAGE_ID); + } + } + + private final Data data; + + private MaterialsPluginData(final Data data) { + this.data = data; + } + + /** + * Returns the material amount for the given batch. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_BATCH_ID} if + * the batch id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_BATCH_ID} if + * the batch id is unknown
    • + *
    + */ + public Double getBatchAmount(final BatchId batchId) { + validateBatchIdNotNull(batchId); + validateBatchExists(data, batchId); + return data.batchAmounts.get(batchId); + } + + /** + * Returns the collected batch ids. + */ + public Set getBatchIds() { + return new LinkedHashSet<>(data.batchIds); + } + + /** + * Returns the material type for the given batch id. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_BATCH_ID} if + * the batch id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_BATCH_ID} if + * the batch id is unknown
    • + *
    + */ + @SuppressWarnings("unchecked") + public T getBatchMaterial(final BatchId batchId) { + validateBatchIdNotNull(batchId); + validateBatchExists(data, batchId); + return (T) data.batchMaterials.get(batchId); + } + + /** + * Returns the materials producer id for the given batch id. + * + * @throws ContractException {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} + * if the materials producer id is null + */ + public List getMaterialsProducerInventoryBatches(final MaterialsProducerId materialsProducerId) { + validateMaterialsProducerIdNotNull(materialsProducerId); + Set batches = data.materialsProducerInventoryBatches.get(materialsProducerId); + List result = new ArrayList<>(); + if (batches != null) { + result.addAll(batches); + } + return result; + } + + /** + * Returns the property definition for the given batch property id and material + * id + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_MATERIAL_ID} if + * the material id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_MATERIAL_ID} + * if the material id is unknown
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the batch property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the batch property id is unknown
    • + *
    + */ + public PropertyDefinition getBatchPropertyDefinition(final MaterialId materialId, + final BatchPropertyId batchPropertyId) { + validateMaterialExists(data, materialId); + validateBatchPropertyIsDefined(data, materialId, batchPropertyId); + + final Map map = data.batchPropertyDefinitions.get(materialId); + final PropertyDefinition propertyDefinition = map.get(batchPropertyId); + return propertyDefinition; + } + + /** + * Returns the property ids associated with the given material id + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_MATERIAL_ID} if + * the material id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_MATERIAL_ID} + * if the material id is unknown
    • + *
    + */ + @SuppressWarnings("unchecked") + public Set getBatchPropertyIds(final MaterialId materialId) { + validateMaterialExists(data, materialId); + final Set result = new LinkedHashSet<>(); + final Map map = data.batchPropertyDefinitions.get(materialId); + if (map != null) { + final Set batchPropertyIds = map.keySet(); + for (final BatchPropertyId batchPropertyId : batchPropertyIds) { + result.add((T) batchPropertyId); + } + } + return result; + } + + /** + * Returns a map of the property values associated collected for the given batch + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_BATCH_ID} if + * the batch id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_BATCH_ID} if + * the batch id is unknown
    • + *
    + */ + public Map getBatchPropertyValues(final BatchId batchId) { + validateBatchIdNotNull(batchId); + validateBatchExists(data, batchId); + final Map map = data.batchPropertyValues.get(batchId); + if (map != null) { + return Collections.unmodifiableMap(map); + } + return data.emptyBatchPropertyValues; + } + + /** + * Returns the collected material ids + */ + @SuppressWarnings("unchecked") + public Set getMaterialIds() { + final Set result = new LinkedHashSet<>(data.materialIds.size()); + for (final MaterialId materialId : data.materialIds) { + result.add((T) materialId); + } + return result; + } + + /** + * Returns the collected material producer ids + */ + @SuppressWarnings("unchecked") + public Set getMaterialsProducerIds() { + final Set result = new LinkedHashSet<>(data.materialsProducerIds.size()); + for (final MaterialsProducerId materialsProducerId : data.materialsProducerIds) { + result.add((T) materialsProducerId); + } + return result; + } + + /** + * Returns the property definition associated with the given materials producer + * property id + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the materials producer property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the materials producer property id is + * unknown
    • + *
    + */ + public PropertyDefinition getMaterialsProducerPropertyDefinition( + final MaterialsProducerPropertyId materialsProducerPropertyId) { + validateMaterialsProducerPropertyIdNotNull(materialsProducerPropertyId); + + final PropertyDefinition propertyDefinition = data.materialsProducerPropertyDefinitions + .get(materialsProducerPropertyId); + if (propertyDefinition == null) { + throw new ContractException(PropertyError.UNKNOWN_PROPERTY_ID, materialsProducerPropertyId); + } + return propertyDefinition; + } + + /** + * Returns the materials producer property ids + */ + @SuppressWarnings("unchecked") + public Set getMaterialsProducerPropertyIds() { + final Set result = new LinkedHashSet<>(); + for (final MaterialsProducerPropertyId materialsProducerPropertyId : data.materialsProducerPropertyDefinitions + .keySet()) { + result.add((T) materialsProducerPropertyId); + } + return result; + } + + /** + * Returns a map of property id to value collected for the given materials + * producer + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} + * if the materials producer id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} + * if the materials producer id is unknown
    • + *
    + */ + public Map getMaterialsProducerPropertyValues( + final MaterialsProducerId materialsProducerId) { + validateMaterialsProducerExists(data, materialsProducerId); + final Map map = data.materialsProducerPropertyValues + .get(materialsProducerId); + if (map == null) { + return data.emptyMaterialsProducerPropertyValuesMap; + } + return Collections.unmodifiableMap(map); + } + + /** + * Returns the resource level the given materials producer id and resource. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} + * if the materials producer id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} + * if the materials producer id is unknown
    • + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is unknown
    • + *
    + */ + public Long getMaterialsProducerResourceLevel(final MaterialsProducerId materialsProducerId, + final ResourceId resourceId) { + validateMaterialsProducerExists(data, materialsProducerId); + validateResourceIdNotNull(resourceId); + Long result = null; + final Map map = data.materialsProducerResourceLevels.get(materialsProducerId); + if (map != null) { + result = map.get(resourceId); + } + if (result == null) { + result = 0L; + } + return result; + } + + /** + * Returns the collected resource ids + */ + public Set getResourceIds() { + final Set result = new LinkedHashSet<>(); + for (final MaterialsProducerId materialsProducerId : data.materialsProducerResourceLevels.keySet()) { + final Map map = data.materialsProducerResourceLevels.get(materialsProducerId); + result.addAll(map.keySet()); + } + return result; + } + + /** + * Returns the batch ids that are assigned to the given stage id. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_STAGE_ID} if + * the stage id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_STAGE_ID} if + * the stage id is unknown
    • + *
    + */ + public Set getStageBatches(final StageId stageId) { + validateStageExists(data, stageId); + final Set result = new LinkedHashSet<>(); + final Set set = data.stageBatches.get(stageId); + if (set != null) { + result.addAll(set); + } + return result; + } + + /** + * Returns the collected stage ids + */ + public Set getStageIds() { + return new LinkedHashSet<>(data.stageIds); + } + + /** + * Returns the materials producer id associated with the given stage id. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} + * if the stage id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} + * if the stage id is unknown
    • + *
    + */ + public List getMaterialsProducerStages(final MaterialsProducerId materialsProducerId) { + validateMaterialsProducerExists(data, materialsProducerId); + List result = new ArrayList<>(); + Set stages = data.materialsProducerStages.get(materialsProducerId); + if (stages != null) { + result.addAll(stages); + } + return result; + } + + /** + * Returns the offer state of the given stage id. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_STAGE_ID} if + * the stage id is null
    • + *
    • {@linkplain MaterialsError#UNKNOWN_STAGE_ID} if + * the stage id is unknown
    • + *
    + */ + public Boolean isStageOffered(final StageId stageId) { + validateStageExists(data, stageId); + return data.stageOffers.get(stageId); + } + + @Override + public PluginDataBuilder getCloneBuilder() { + return new Builder(data); + } + + /** + * Returns the next available batch id. + */ + public int getNextBatchRecordId() { + return data.nextBatchRecordId; + } + + /** + * Returns the next available stage id. + */ + public int getNextStageRecordId() { + return data.nextStageRecordId; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + data.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof MaterialsPluginData)) { + return false; + } + MaterialsPluginData other = (MaterialsPluginData) obj; + if (!data.equals(other.data)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder2 = new StringBuilder(); + builder2.append("MaterialsPluginData [data="); + builder2.append(data); + builder2.append("]"); + return builder2.toString(); + } + + public Map> getBatchPropertyDefinitions() { + Map> result = new LinkedHashMap<>(); + for (MaterialId materialId : data.batchPropertyDefinitions.keySet()) { + Map map = data.batchPropertyDefinitions.get(materialId); + Map newMap = new LinkedHashMap<>(map); + result.put(materialId, newMap); + } + return result; + } + + public Map getMaterialsProducerPropertyDefinitions() { + return new LinkedHashMap<>(data.materialsProducerPropertyDefinitions); + } + + public Map> getMaterialsProducerPropertyValues() { + Map> result = new LinkedHashMap<>(); + for (MaterialsProducerId materialsProducerId : data.materialsProducerPropertyValues.keySet()) { + Map map = data.materialsProducerPropertyValues + .get(materialsProducerId); + Map newMap = new LinkedHashMap<>(map); + result.put(materialsProducerId, newMap); + } + return result; + } + + public Map> getMaterialsProducerResourceLevels() { + Map> result = new LinkedHashMap<>(); + for (MaterialsProducerId materialsProducerId : data.materialsProducerResourceLevels.keySet()) { + Map map = data.materialsProducerResourceLevels.get(materialsProducerId); + Map newMap = new LinkedHashMap<>(map); + result.put(materialsProducerId, newMap); + } + return result; + } + + public Map> getBatchPropertyValues() { + Map> result = new LinkedHashMap<>(); + for (BatchId batchId : data.batchPropertyValues.keySet()) { + Map map = data.batchPropertyValues.get(batchId); + Map newMap = new LinkedHashMap<>(map); + result.put(batchId, newMap); + } + return result; + } + + public Map getStageOffers() { + return new LinkedHashMap<>(data.stageOffers); + } + + public Map getBatchMaterials() { + return new LinkedHashMap<>(data.batchMaterials); + } + + public Map getBatchAmounts() { + return new LinkedHashMap<>(data.batchAmounts); + } + + public Map> getMaterialsProducerStages() { + Map> result = new LinkedHashMap<>(); + + for (MaterialsProducerId materialsProducerId : data.materialsProducerStages.keySet()) { + Set set = data.materialsProducerStages.get(materialsProducerId); + Set newSet = new LinkedHashSet<>(set); + result.put(materialsProducerId, newSet); + } + return result; + } + + public Map> getStageBatches() { + Map> result = new LinkedHashMap<>(); + for (StageId stageId : data.stageBatches.keySet()) { + Set set = data.stageBatches.get(stageId); + Set newSet = new LinkedHashSet<>(set); + result.put(stageId, newSet); + } + return result; + } + + public Map> getMaterialsProducerInventoryBatches() { + Map> result = new LinkedHashMap<>(); + for (MaterialsProducerId materialsProducerId : data.materialsProducerInventoryBatches.keySet()) { + Set set = data.materialsProducerInventoryBatches.get(materialsProducerId); + Set newSet = new LinkedHashSet<>(set); + result.put(materialsProducerId, newSet); + } + return result; + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/BatchAdditionEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/BatchAdditionEvent.java new file mode 100644 index 000000000..551832d85 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/BatchAdditionEvent.java @@ -0,0 +1,9 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchId; +import net.jcip.annotations.Immutable; + +@Immutable +public record BatchAdditionEvent(BatchId batchId) implements Event { +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/BatchAmountUpdateEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/BatchAmountUpdateEvent.java new file mode 100644 index 000000000..0a661aa9d --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/BatchAmountUpdateEvent.java @@ -0,0 +1,9 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchId; +import net.jcip.annotations.Immutable; + +@Immutable +public record BatchAmountUpdateEvent(BatchId batchId, double previousAmount, double currentAmount) implements Event { +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/BatchImminentRemovalEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/BatchImminentRemovalEvent.java new file mode 100644 index 000000000..5e6873247 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/BatchImminentRemovalEvent.java @@ -0,0 +1,9 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchId; +import net.jcip.annotations.Immutable; + +@Immutable +public record BatchImminentRemovalEvent(BatchId batchId) implements Event { +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/BatchPropertyDefinitionEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/BatchPropertyDefinitionEvent.java new file mode 100644 index 000000000..4ec5ab327 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/BatchPropertyDefinitionEvent.java @@ -0,0 +1,37 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsError; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * An event indicating the addition of a batch property for the given material. + */ +@Immutable +public record BatchPropertyDefinitionEvent(MaterialId materialId, BatchPropertyId batchPropertyId) implements Event { + + /** + * Constructs the event + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_MATERIAL_ID} if + * the material id is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the batch property id is null
    • + *
    + */ + public BatchPropertyDefinitionEvent { + if (materialId == null) { + throw new ContractException(MaterialsError.NULL_MATERIAL_ID); + } + if (batchPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/BatchPropertyUpdateEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/BatchPropertyUpdateEvent.java new file mode 100644 index 000000000..19ba85806 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/BatchPropertyUpdateEvent.java @@ -0,0 +1,32 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsError; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +@Immutable +public record BatchPropertyUpdateEvent(BatchId batchId, BatchPropertyId batchPropertyId, Object previousPropertyValue, + Object currentPropertyValue) implements Event { + public BatchPropertyUpdateEvent { + if (batchId == null) { + throw new ContractException(MaterialsError.NULL_BATCH_ID); + } + + if (batchPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + + if (previousPropertyValue == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_VALUE); + } + + if (currentPropertyValue == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_VALUE); + } + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/MaterialIdAdditionEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/MaterialIdAdditionEvent.java new file mode 100644 index 000000000..ce60cfa40 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/MaterialIdAdditionEvent.java @@ -0,0 +1,28 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsError; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * An event indicating that a material type has been added + */ +@Immutable +public record MaterialIdAdditionEvent(MaterialId materialId) implements Event { + + /** + * Constructs the event + * + * @throws ContractException {@linkplain MaterialsError#NULL_MATERIAL_ID} if the + * material id is null + */ + public MaterialIdAdditionEvent { + if (materialId == null) { + throw new ContractException(MaterialsError.NULL_MATERIAL_ID); + } + + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/MaterialsProducerAdditionEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/MaterialsProducerAdditionEvent.java new file mode 100644 index 000000000..11fc62f43 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/MaterialsProducerAdditionEvent.java @@ -0,0 +1,124 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.events; + +import java.util.ArrayList; +import java.util.List; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsError; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerId; +import gov.hhs.aspr.ms.gcm.plugins.regions.events.RegionAdditionEvent; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * An event indicating that a materials producer has been added + */ +@Immutable +public class MaterialsProducerAdditionEvent implements Event { + + private static class Data { + private MaterialsProducerId materialsProducerId; + private List values = new ArrayList<>(); + + public Data() { + } + + public Data(Data data) { + materialsProducerId = data.materialsProducerId; + values.addAll(data.values); + } + } + + /** + * Returns a new Builder instance + */ + public static Builder builder() { + return new Builder(); + } + + private final Data data; + + /** + * Builder class for {@link RegionAdditionEvent} + */ + public static class Builder { + + private Data data = new Data(); + + private Builder() { + } + + private void validate() { + if (data.materialsProducerId == null) { + throw new ContractException(MaterialsError.NULL_MATERIALS_PRODUCER_ID); + } + } + + /** + * Builds the Region addition event from the inputs + * + * @throws ContractException {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} + * if the materials producer id was not set + */ + public MaterialsProducerAdditionEvent build() { + validate(); + return new MaterialsProducerAdditionEvent(new Data(data)); + } + + /** + * Sets the materials producer id + * + * @throws ContractException {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} + * if the materials producer id is null + */ + public Builder setMaterialsProducerId(MaterialsProducerId materialsProducerId) { + if (materialsProducerId == null) { + throw new ContractException(MaterialsError.NULL_MATERIALS_PRODUCER_ID); + } + data.materialsProducerId = materialsProducerId; + return this; + } + + /** + * Adds an auxiliary value to be used by observers of materials producer + * addition + * + * @throws ContractException {@linkplain MaterialsError#NULL_AUXILIARY_DATA} if + * the value is null + */ + public Builder addValue(Object value) { + if (value == null) { + throw new ContractException(MaterialsError.NULL_AUXILIARY_DATA); + } + data.values.add(value); + return this; + } + } + + private MaterialsProducerAdditionEvent(Data data) { + this.data = data; + } + + /** + * Returns the region id. + */ + public MaterialsProducerId getMaterialsProducerId() { + return data.materialsProducerId; + } + + /** + * Returns the (non-null) auxiliary objects that are instances of the given + * class in the order of their addition to the builder. + */ + @SuppressWarnings("unchecked") + public List getValues(Class c) { + List result = new ArrayList<>(); + for (Object value : data.values) { + if (c.isAssignableFrom(value.getClass())) { + result.add((T) value); + } + } + return result; + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/MaterialsProducerPropertyDefinitionEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/MaterialsProducerPropertyDefinitionEvent.java new file mode 100644 index 000000000..0573e51db --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/MaterialsProducerPropertyDefinitionEvent.java @@ -0,0 +1,30 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * An event released by the materials data manager whenever a materials producer + * property definition is added to the simulation. + */ +@Immutable +public record MaterialsProducerPropertyDefinitionEvent(MaterialsProducerPropertyId materialsProducerPropertyId) + implements Event { + + /** + * Creates the event. + * + * @throws ContractException {@linkplain PropertyError#NULL_PROPERTY_ID} if the + * property id is null + */ + public MaterialsProducerPropertyDefinitionEvent { + + if (materialsProducerPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/MaterialsProducerPropertyUpdateEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/MaterialsProducerPropertyUpdateEvent.java new file mode 100644 index 000000000..8c9acce19 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/MaterialsProducerPropertyUpdateEvent.java @@ -0,0 +1,12 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerPropertyId; +import net.jcip.annotations.Immutable; + +@Immutable +public record MaterialsProducerPropertyUpdateEvent(MaterialsProducerId materialsProducerId, + MaterialsProducerPropertyId materialsProducerPropertyId, Object previousPropertyValue, + Object currentPropertyValue) implements Event { +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/MaterialsProducerResourceUpdateEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/MaterialsProducerResourceUpdateEvent.java new file mode 100644 index 000000000..091cd1c48 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/MaterialsProducerResourceUpdateEvent.java @@ -0,0 +1,11 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerId; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; +import net.jcip.annotations.Immutable; + +@Immutable +public record MaterialsProducerResourceUpdateEvent(MaterialsProducerId materialsProducerId, ResourceId resourceId, + long previousResourceLevel, long currentResourceLevel) implements Event { +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/StageAdditionEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/StageAdditionEvent.java new file mode 100644 index 000000000..38a2e0dba --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/StageAdditionEvent.java @@ -0,0 +1,9 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.StageId; +import net.jcip.annotations.Immutable; + +@Immutable +public record StageAdditionEvent(StageId stageId) implements Event { +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/StageImminentRemovalEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/StageImminentRemovalEvent.java new file mode 100644 index 000000000..093da40e3 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/StageImminentRemovalEvent.java @@ -0,0 +1,10 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.StageId; +import net.jcip.annotations.Immutable; + +@Immutable + +public record StageImminentRemovalEvent(StageId stageId) implements Event { +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/StageMaterialsProducerUpdateEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/StageMaterialsProducerUpdateEvent.java new file mode 100644 index 000000000..5bb514f84 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/StageMaterialsProducerUpdateEvent.java @@ -0,0 +1,11 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.StageId; +import net.jcip.annotations.Immutable; + +@Immutable +public record StageMaterialsProducerUpdateEvent(StageId stageId, MaterialsProducerId previousMaterialsProducerId, + MaterialsProducerId currentMaterialsProducerId) implements Event { +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/StageMembershipAdditionEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/StageMembershipAdditionEvent.java new file mode 100644 index 000000000..704702cff --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/StageMembershipAdditionEvent.java @@ -0,0 +1,10 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.StageId; +import net.jcip.annotations.Immutable; + +@Immutable +public record StageMembershipAdditionEvent(BatchId batchId, StageId stageId) implements Event { +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/StageMembershipRemovalEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/StageMembershipRemovalEvent.java new file mode 100644 index 000000000..b802d566f --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/StageMembershipRemovalEvent.java @@ -0,0 +1,10 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.StageId; +import net.jcip.annotations.Immutable; + +@Immutable +public record StageMembershipRemovalEvent(BatchId batchId, StageId stageId) implements Event { +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/StageOfferUpdateEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/StageOfferUpdateEvent.java new file mode 100644 index 000000000..59803af7f --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/StageOfferUpdateEvent.java @@ -0,0 +1,10 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.StageId; +import net.jcip.annotations.Immutable; + +@Immutable +public record StageOfferUpdateEvent(StageId stageId, boolean previousOfferState, boolean currentOfferState) + implements Event { +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/BatchStatusReport.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/BatchStatusReport.java new file mode 100644 index 000000000..867747a95 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/BatchStatusReport.java @@ -0,0 +1,240 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.reports; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.plugins.materials.datamangers.MaterialsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.BatchAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.BatchAmountUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.BatchImminentRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.BatchPropertyUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.StageMembershipAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.StageMembershipRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.StageId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; + +/** + * A Report that displays the state of batches over time. The batch properties + * included in this report are limited to those present during initialization. + * Fields Time -- the time in days when batch state was updated Batch -- the + * batch identifier Stage -- the stage associated with the batch + * MaterialsProducer -- the materials producer of the owner of the batch Offered + * -- the offered state of the batch Material -- the material of the batch + * Amount -- the amount of material in the batch Material.PropertyId -- multiple + * columns for the batch properties selected for the report + */ +public final class BatchStatusReport { + + private static class BatchRecord { + private double time; + private BatchId batchId; + private MaterialsProducerId materialsProducerId; + private StageId stageId; + private MaterialId materialId; + private double amount; + private Map propertyValues = new LinkedHashMap<>(); + + } + + private final ReportLabel reportLabel; + + public BatchStatusReport(BatchStatusReportPluginData batchStatusReportPluginData) { + this.reportLabel = batchStatusReportPluginData.getReportLabel(); + } + + private Map batchRecords = new LinkedHashMap<>(); + + private Map> batchPropertyMap = new LinkedHashMap<>(); + + /* + * Releases a report item for each updated batch that still exists + */ + private void reportBatch(ReportContext reportContext, BatchRecord batchRecord) { + + // report the batch - make sure batch exists + + final ReportItem.Builder reportItemBuilder = ReportItem.builder(); + reportItemBuilder.setReportHeader(getReportHeader()); + reportItemBuilder.setReportLabel(reportLabel); + reportItemBuilder.addValue(batchRecord.time); + reportItemBuilder.addValue(batchRecord.batchId); + reportItemBuilder.addValue(batchRecord.materialsProducerId); + + if (batchRecord.stageId != null) { + reportItemBuilder.addValue(batchRecord.stageId); + } else { + reportItemBuilder.addValue(""); + } + + reportItemBuilder.addValue(batchRecord.materialId); + reportItemBuilder.addValue(batchRecord.amount); + + for (MaterialId materialId : batchPropertyMap.keySet()) { + boolean matchingMaterial = batchRecord.materialId.equals(materialId); + Set batchPropertyIds = batchPropertyMap.get(materialId); + for (BatchPropertyId batchPropertyId : batchPropertyIds) { + if (matchingMaterial) { + reportItemBuilder.addValue(batchRecord.propertyValues.get(batchPropertyId)); + } else { + reportItemBuilder.addValue(""); + } + } + } + reportContext.releaseOutput(reportItemBuilder.build()); + + } + + private ReportHeader reportHeader; + + /* + * Returns the ReportHeader based on the batch properties selected by the + * client. + */ + private ReportHeader getReportHeader() { + if (reportHeader == null) { + ReportHeader.Builder builder = ReportHeader.builder()// + .add("time")// + .add("batch")// + .add("materials_producer")// + .add("stage")// + .add("material")// + .add("amount");// + Set materialIds = materialsDataManager.getMaterialIds(); + for (MaterialId materialId : materialIds) { + Set batchPropertyIds = materialsDataManager.getBatchPropertyIds(materialId); + for (BatchPropertyId batchPropertyId : batchPropertyIds) { + builder.add(materialId + "." + batchPropertyId); + } + } + reportHeader = builder.build(); + } + return reportHeader; + + } + + private BatchRecord createBatchRecord(ReportContext reportContext, BatchId batchId) { + + BatchRecord batchRecord = new BatchRecord(); + + batchRecord.time = reportContext.getTime(); + batchRecord.batchId = batchId; + batchRecord.materialsProducerId = materialsDataManager.getBatchProducer(batchId); + Optional optionalStageId = materialsDataManager.getBatchStageId(batchId); + if (optionalStageId.isPresent()) { + batchRecord.stageId = optionalStageId.get(); + } else { + batchRecord.stageId = null; + } + batchRecord.materialId = materialsDataManager.getBatchMaterial(batchId); + batchRecord.amount = materialsDataManager.getBatchAmount(batchId); + + Set batchPropertyIds = materialsDataManager.getBatchPropertyIds(batchRecord.materialId); + for (BatchPropertyId batchPropertyId : batchPropertyIds) { + Object batchPropertyValue = materialsDataManager.getBatchPropertyValue(batchId, batchPropertyId); + batchRecord.propertyValues.put(batchPropertyId, batchPropertyValue); + } + batchRecords.put(batchId, batchRecord); + return batchRecord; + } + + private void handleBatchAdditionEvent(ReportContext reportContext, BatchAdditionEvent batchAdditionEvent) { + BatchId batchId = batchAdditionEvent.batchId(); + BatchRecord batchRecord = createBatchRecord(reportContext, batchId); + reportBatch(reportContext, batchRecord); + } + + private void handleBatchImminentRemovalEvent(ReportContext reportContext, + BatchImminentRemovalEvent batchImminentRemovalEvent) { + BatchId batchId = batchImminentRemovalEvent.batchId(); + BatchRecord batchRecord = batchRecords.remove(batchId); + batchRecord.time = reportContext.getTime(); + reportBatch(reportContext, batchRecord); + } + + private void handleBatchAmountUpdateEvent(ReportContext reportContext, + BatchAmountUpdateEvent batchAmountUpdateEvent) { + BatchId batchId = batchAmountUpdateEvent.batchId(); + BatchRecord batchRecord = batchRecords.get(batchId); + batchRecord.amount = batchAmountUpdateEvent.currentAmount(); + batchRecord.time = reportContext.getTime(); + reportBatch(reportContext, batchRecord); + } + + private void handleStageMembershipAdditionEvent(ReportContext reportContext, + StageMembershipAdditionEvent stageMembershipAdditionEvent) { + BatchId batchId = stageMembershipAdditionEvent.batchId(); + BatchRecord batchRecord = batchRecords.get(batchId); + batchRecord.stageId = stageMembershipAdditionEvent.stageId(); + batchRecord.time = reportContext.getTime(); + reportBatch(reportContext, batchRecord); + } + + private void handleStageMembershipRemovalEvent(ReportContext reportContext, + StageMembershipRemovalEvent stageMembershipRemovalEvent) { + BatchId batchId = stageMembershipRemovalEvent.batchId(); + BatchRecord batchRecord = batchRecords.get(batchId); + batchRecord.stageId = null; + batchRecord.time = reportContext.getTime(); + reportBatch(reportContext, batchRecord); + } + + private void handleBatchPropertyUpdateEvent(ReportContext reportContext, + BatchPropertyUpdateEvent batchPropertyUpdateEvent) { + BatchId batchId = batchPropertyUpdateEvent.batchId(); + BatchRecord batchRecord = batchRecords.get(batchId); + batchRecord.propertyValues.put(batchPropertyUpdateEvent.batchPropertyId(), + batchPropertyUpdateEvent.currentPropertyValue()); + batchRecord.time = reportContext.getTime(); + reportBatch(reportContext, batchRecord); + } + + private MaterialsDataManager materialsDataManager; + + public void init(final ReportContext reportContext) { + + reportContext.subscribe(BatchAdditionEvent.class, this::handleBatchAdditionEvent); + reportContext.subscribe(BatchImminentRemovalEvent.class, this::handleBatchImminentRemovalEvent); + reportContext.subscribe(BatchAmountUpdateEvent.class, this::handleBatchAmountUpdateEvent); + reportContext.subscribe(BatchPropertyUpdateEvent.class, this::handleBatchPropertyUpdateEvent); + reportContext.subscribe(StageMembershipAdditionEvent.class, this::handleStageMembershipAdditionEvent); + reportContext.subscribe(StageMembershipRemovalEvent.class, this::handleStageMembershipRemovalEvent); + if (reportContext.stateRecordingIsScheduled()) { + reportContext.subscribeToSimulationClose(this::recordSimulationState); + } + + materialsDataManager = reportContext.getDataManager(MaterialsDataManager.class); + + for (MaterialId materialId : materialsDataManager.getMaterialIds()) { + this.batchPropertyMap.put(materialId, materialsDataManager.getBatchPropertyIds(materialId)); + } + + for (MaterialsProducerId materialsProducerId : materialsDataManager.getMaterialsProducerIds()) { + for (BatchId inventoryBatchId : materialsDataManager.getInventoryBatches(materialsProducerId)) { + BatchRecord batchRecord = createBatchRecord(reportContext, inventoryBatchId); + reportBatch(reportContext, batchRecord); + } + for (StageId stageId : materialsDataManager.getStages(materialsProducerId)) { + for (BatchId stageBatchId : materialsDataManager.getStageBatches(stageId)) { + BatchRecord batchRecord = createBatchRecord(reportContext, stageBatchId); + reportBatch(reportContext, batchRecord); + } + } + } + } + + private void recordSimulationState(ReportContext reportContext) { + BatchStatusReportPluginData.Builder builder = BatchStatusReportPluginData.builder(); + builder.setReportLabel(reportLabel); + reportContext.releaseOutput(builder.build()); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/BatchStatusReportPluginData.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/BatchStatusReportPluginData.java new file mode 100644 index 000000000..f0a6ee5ad --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/BatchStatusReportPluginData.java @@ -0,0 +1,192 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.reports; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.PluginDataBuilder; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportError; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import net.jcip.annotations.ThreadSafe; +import util.errors.ContractException; + +/** + * A PluginData class supporting BatchStatusReport construction. + */ +@ThreadSafe +public final class BatchStatusReportPluginData implements PluginData { + + /* + * Data class for collecting the inputs to the report + */ + private static class Data { + private ReportLabel reportLabel; + + private boolean locked; + + private Data() { + } + + private Data(Data data) { + reportLabel = data.reportLabel; + locked = data.locked; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((reportLabel == null) ? 0 : reportLabel.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + if (reportLabel == null) { + if (other.reportLabel != null) { + return false; + } + } else if (!reportLabel.equals(other.reportLabel)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Data [reportLabel="); + builder.append(reportLabel); + builder.append(", locked="); + builder.append(locked); + builder.append("]"); + return builder.toString(); + } + + } + + /** + * Returns a new instance of the builder class + */ + public static Builder builder() { + return new Builder(new Data()); + } + + /** + * Builder class for the report + */ + public final static class Builder implements PluginDataBuilder { + private Builder(Data data) { + this.data = data; + } + + private void ensureDataMutability() { + if (data.locked) { + data = new Data(data); + data.locked = false; + } + } + + private void ensureImmutability() { + if (!data.locked) { + data.locked = true; + } + } + + private void validateData() { + if (data.reportLabel == null) { + throw new ContractException(ReportError.NULL_REPORT_LABEL); + } + } + + private Data data; + + /** + * Returns a PersonPropertyReportPluginData created from the collected inputs + * + * @throws ContractException {@linkplain ReportError#NULL_REPORT_LABEL} if the + * report label is not assigned + */ + public BatchStatusReportPluginData build() { + + if (!data.locked) { + validateData(); + } + ensureImmutability(); + return new BatchStatusReportPluginData(data); + + } + + /** + * Sets the report label + * + * @throws ContractException {@linkplain ReportError#NULL_REPORT_LABEL} if the + * report label is null + */ + public Builder setReportLabel(ReportLabel reportLabel) { + ensureDataMutability(); + if (reportLabel == null) { + throw new ContractException(ReportError.NULL_REPORT_LABEL); + } + data.reportLabel = reportLabel; + return this; + } + + } + + private final Data data; + + private BatchStatusReportPluginData(Data data) { + this.data = data; + } + + @Override + public Builder getCloneBuilder() { + return new Builder(data); + } + + public ReportLabel getReportLabel() { + return data.reportLabel; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof BatchStatusReportPluginData)) { + return false; + } + BatchStatusReportPluginData other = (BatchStatusReportPluginData) obj; + if (data == null) { + if (other.data != null) { + return false; + } + } else if (!data.equals(other.data)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder2 = new StringBuilder(); + builder2.append("BatchStatusReportPluginData [data="); + builder2.append(data); + builder2.append("]"); + return builder2.toString(); + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/MaterialsProducerPropertyReport.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/MaterialsProducerPropertyReport.java new file mode 100644 index 000000000..d518f3104 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/MaterialsProducerPropertyReport.java @@ -0,0 +1,104 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.reports; + +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.plugins.materials.datamangers.MaterialsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.MaterialsProducerAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.MaterialsProducerPropertyUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; + +/** + * A Report that displays assigned materials producer property values over time. + * Fields Time -- the time in days when the materials producer property was set + * MaterialsProducer -- the materials producer identifier Property -- the region + * property identifier Value -- the value of the region property + */ +public final class MaterialsProducerPropertyReport { + + private final ReportLabel reportLabel; + + public MaterialsProducerPropertyReport( + MaterialsProducerPropertyReportPluginData materialsProducerPropertyReportPluginData) { + this.reportLabel = materialsProducerPropertyReportPluginData.getReportLabel(); + } + + private ReportHeader reportHeader; + + private ReportHeader getReportHeader() { + if (reportHeader == null) { + reportHeader = ReportHeader.builder()// + .add("time")// + .add("materials_producer")// + .add("property")// + .add("value")// + .build();// + } + return reportHeader; + } + + private void handleMaterialsProducerPropertyUpdateEvent(ReportContext reportContext, + MaterialsProducerPropertyUpdateEvent materialsProducerPropertyUpdateEvent) { + MaterialsProducerId materialsProducerId = materialsProducerPropertyUpdateEvent.materialsProducerId(); + MaterialsProducerPropertyId materialsProducerPropertyId = materialsProducerPropertyUpdateEvent + .materialsProducerPropertyId(); + Object currentPropertyValue = materialsProducerPropertyUpdateEvent.currentPropertyValue(); + writeProperty(reportContext, materialsProducerId, materialsProducerPropertyId, currentPropertyValue); + } + + public void init(final ReportContext reportContext) { + + reportContext.subscribe(MaterialsProducerPropertyUpdateEvent.class, + this::handleMaterialsProducerPropertyUpdateEvent); + reportContext.subscribe(MaterialsProducerAdditionEvent.class, this::handleMaterialsProducerAdditionEvent); + if (reportContext.stateRecordingIsScheduled()) { + reportContext.subscribeToSimulationClose(this::recordSimulationState); + } + + MaterialsDataManager materialsDataManager = reportContext.getDataManager(MaterialsDataManager.class); + + for (final MaterialsProducerId materialsProducerId : materialsDataManager.getMaterialsProducerIds()) { + for (final MaterialsProducerPropertyId materialsProducerPropertyId : materialsDataManager + .getMaterialsProducerPropertyIds()) { + final Object materialsProducerPropertyValue = materialsDataManager + .getMaterialsProducerPropertyValue(materialsProducerId, materialsProducerPropertyId); + writeProperty(reportContext, materialsProducerId, materialsProducerPropertyId, + materialsProducerPropertyValue); + } + } + } + + private void recordSimulationState(ReportContext reportContext) { + MaterialsProducerPropertyReportPluginData.Builder builder = MaterialsProducerPropertyReportPluginData.builder(); + builder.setReportLabel(reportLabel); + reportContext.releaseOutput(builder.build()); + } + + private void handleMaterialsProducerAdditionEvent(ReportContext reportContext, + MaterialsProducerAdditionEvent materialsProducerAdditionEvent) { + MaterialsDataManager materialsDataManager = reportContext.getDataManager(MaterialsDataManager.class); + MaterialsProducerId materialsProducerId = materialsProducerAdditionEvent.getMaterialsProducerId(); + for (final MaterialsProducerPropertyId materialsProducerPropertyId : materialsDataManager + .getMaterialsProducerPropertyIds()) { + final Object materialsProducerPropertyValue = materialsDataManager + .getMaterialsProducerPropertyValue(materialsProducerId, materialsProducerPropertyId); + writeProperty(reportContext, materialsProducerId, materialsProducerPropertyId, + materialsProducerPropertyValue); + } + } + + private void writeProperty(ReportContext reportContext, final MaterialsProducerId materialsProducerId, + final MaterialsProducerPropertyId materialsProducerPropertyId, Object materialsProducerPropertyValue) { + final ReportItem.Builder reportItemBuilder = ReportItem.builder(); + reportItemBuilder.setReportHeader(getReportHeader()); + reportItemBuilder.setReportLabel(reportLabel); + reportItemBuilder.addValue(reportContext.getTime()); + reportItemBuilder.addValue(materialsProducerId.toString()); + reportItemBuilder.addValue(materialsProducerPropertyId.toString()); + reportItemBuilder.addValue(materialsProducerPropertyValue); + reportContext.releaseOutput(reportItemBuilder.build()); + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/MaterialsProducerPropertyReportPluginData.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/MaterialsProducerPropertyReportPluginData.java new file mode 100644 index 000000000..0f3a2f8fa --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/MaterialsProducerPropertyReportPluginData.java @@ -0,0 +1,192 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.reports; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.PluginDataBuilder; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportError; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import net.jcip.annotations.ThreadSafe; +import util.errors.ContractException; + +/** + * A PluginData class supporting MaterialsProducerProperty construction. + */ +@ThreadSafe +public final class MaterialsProducerPropertyReportPluginData implements PluginData { + + /* + * Data class for collecting the inputs to the report + */ + private static class Data { + private ReportLabel reportLabel; + + private boolean locked; + + private Data() { + } + + private Data(Data data) { + reportLabel = data.reportLabel; + locked = data.locked; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((reportLabel == null) ? 0 : reportLabel.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + if (reportLabel == null) { + if (other.reportLabel != null) { + return false; + } + } else if (!reportLabel.equals(other.reportLabel)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Data [reportLabel="); + builder.append(reportLabel); + builder.append(", locked="); + builder.append(locked); + builder.append("]"); + return builder.toString(); + } + + } + + /** + * Returns a new instance of the builder class + */ + public static Builder builder() { + return new Builder(new Data()); + } + + /** + * Builder class for the report + */ + public final static class Builder implements PluginDataBuilder { + private Builder(Data data) { + this.data = data; + } + + private void ensureDataMutability() { + if (data.locked) { + data = new Data(data); + data.locked = false; + } + } + + private void ensureImmutability() { + if (!data.locked) { + data.locked = true; + } + } + + private void validateData() { + if (data.reportLabel == null) { + throw new ContractException(ReportError.NULL_REPORT_LABEL); + } + } + + private Data data; + + /** + * Returns a PersonPropertyReportPluginData created from the collected inputs + * + * @throws ContractException {@linkplain ReportError#NULL_REPORT_LABEL} if the + * report label is not assigned + */ + public MaterialsProducerPropertyReportPluginData build() { + + if (!data.locked) { + validateData(); + } + ensureImmutability(); + return new MaterialsProducerPropertyReportPluginData(data); + + } + + /** + * Sets the report label + * + * @throws ContractException {@linkplain ReportError#NULL_REPORT_LABEL} if the + * report label is null + */ + public Builder setReportLabel(ReportLabel reportLabel) { + ensureDataMutability(); + if (reportLabel == null) { + throw new ContractException(ReportError.NULL_REPORT_LABEL); + } + data.reportLabel = reportLabel; + return this; + } + + } + + private final Data data; + + private MaterialsProducerPropertyReportPluginData(Data data) { + this.data = data; + } + + @Override + public Builder getCloneBuilder() { + return new Builder(data); + } + + public ReportLabel getReportLabel() { + return data.reportLabel; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof MaterialsProducerPropertyReportPluginData)) { + return false; + } + MaterialsProducerPropertyReportPluginData other = (MaterialsProducerPropertyReportPluginData) obj; + if (data == null) { + if (other.data != null) { + return false; + } + } else if (!data.equals(other.data)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder2 = new StringBuilder(); + builder2.append("MaterialsProducerPropertyReportPluginData [data="); + builder2.append(data); + builder2.append("]"); + return builder2.toString(); + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/MaterialsProducerResourceReport.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/MaterialsProducerResourceReport.java new file mode 100644 index 000000000..d4fe6bb69 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/MaterialsProducerResourceReport.java @@ -0,0 +1,145 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.reports; + +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.plugins.materials.datamangers.MaterialsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.MaterialsProducerAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.MaterialsProducerResourceUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.resources.datamanagers.ResourcesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.resources.events.ResourceIdAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; + +/** + * A Report that displays materials producer resource changes over time. Fields + * Time -- the time in days when the materials producer resource level was set + * Resource -- the resource identifier MaterialsProducer -- the materials + * producer identifier Action -- the action taken on the resource Amount -- the + * amount of resource + */ +public final class MaterialsProducerResourceReport { + private final ReportLabel reportLabel; + + public MaterialsProducerResourceReport( + MaterialsProducerResourceReportPluginData materialsProducerResourceReportPluginData) { + this.reportLabel = materialsProducerResourceReportPluginData.getReportLabel(); + } + + private static enum Action { + /* + * Used when a resource is directly added to a materials producer which only + * happens when the materials producer is being initialized from the scenario + */ + ADDED("Added"), + + /* + * Used when a materials producer transfers resource to a region + */ + REMOVED("Removed"); + + private final String displayName; + + private Action(final String displayName) { + this.displayName = displayName; + } + } + + private ReportHeader reportHeader; + + private ReportHeader getReportHeader() { + if (reportHeader == null) { + reportHeader = ReportHeader.builder()// + .add("time")// + .add("resource")// + .add("materials_producer")// + .add("action")// + .add("amount")// + .build();// + } + return reportHeader; + } + + private void handleMaterialsProducerResourceUpdateEvent(ReportContext reportContext, + MaterialsProducerResourceUpdateEvent materialsProducerResourceUpdateEvent) { + long currentResourceLevel = materialsProducerResourceUpdateEvent.currentResourceLevel(); + long previousResourceLevel = materialsProducerResourceUpdateEvent.previousResourceLevel(); + long amount = currentResourceLevel - previousResourceLevel; + ResourceId resourceId = materialsProducerResourceUpdateEvent.resourceId(); + MaterialsProducerId materialsProducerId = materialsProducerResourceUpdateEvent.materialsProducerId(); + if (amount > 0) { + writeReportItem(reportContext, resourceId, materialsProducerId, Action.ADDED, amount); + } else { + amount = -amount; + writeReportItem(reportContext, resourceId, materialsProducerId, Action.REMOVED, amount); + } + } + + private void writeReportItem(ReportContext reportContext, final ResourceId resourceId, + final MaterialsProducerId materialsProducerId, final Action action, final long amount) { + final ReportItem.Builder reportItemBuilder = ReportItem.builder(); + reportItemBuilder.setReportHeader(getReportHeader()); + reportItemBuilder.setReportLabel(reportLabel); + reportItemBuilder.addValue(reportContext.getTime()); + reportItemBuilder.addValue(resourceId.toString()); + reportItemBuilder.addValue(materialsProducerId.toString()); + reportItemBuilder.addValue(action.displayName); + reportItemBuilder.addValue(amount); + reportContext.releaseOutput(reportItemBuilder.build()); + } + + private void handleResourceIdAdditionEvent(ReportContext reportContext, + ResourceIdAdditionEvent resourceIdAdditionEvent) { + ResourceId resourceId = resourceIdAdditionEvent.resourceId(); + MaterialsDataManager materialsDataManager = reportContext.getDataManager(MaterialsDataManager.class); + for (MaterialsProducerId materialsProducerId : materialsDataManager.getMaterialsProducerIds()) { + long materialsProducerResourceLevel = materialsDataManager + .getMaterialsProducerResourceLevel(materialsProducerId, resourceId); + writeReportItem(reportContext, resourceId, materialsProducerId, Action.ADDED, + materialsProducerResourceLevel); + } + } + + public void init(final ReportContext reportContext) { + + reportContext.subscribe(MaterialsProducerResourceUpdateEvent.class, + this::handleMaterialsProducerResourceUpdateEvent); + reportContext.subscribe(ResourceIdAdditionEvent.class, this::handleResourceIdAdditionEvent); + reportContext.subscribe(MaterialsProducerAdditionEvent.class, this::handleMaterialsProducerAdditionEvent); + if (reportContext.stateRecordingIsScheduled()) { + reportContext.subscribeToSimulationClose(this::recordSimulationState); + } + + ResourcesDataManager resourcesDataManager = reportContext.getDataManager(ResourcesDataManager.class); + MaterialsDataManager materialsDataManager = reportContext.getDataManager(MaterialsDataManager.class); + for (MaterialsProducerId materialsProducerId : materialsDataManager.getMaterialsProducerIds()) { + for (ResourceId resourceId : resourcesDataManager.getResourceIds()) { + long materialsProducerResourceLevel = materialsDataManager + .getMaterialsProducerResourceLevel(materialsProducerId, resourceId); + writeReportItem(reportContext, resourceId, materialsProducerId, Action.ADDED, + materialsProducerResourceLevel); + } + } + } + + private void recordSimulationState(ReportContext reportContext) { + MaterialsProducerResourceReportPluginData.Builder builder = MaterialsProducerResourceReportPluginData.builder(); + builder.setReportLabel(reportLabel); + reportContext.releaseOutput(builder.build()); + } + + private void handleMaterialsProducerAdditionEvent(ReportContext reportContext, + MaterialsProducerAdditionEvent materialsProducerAdditionEvent) { + MaterialsProducerId materialsProducerId = materialsProducerAdditionEvent.getMaterialsProducerId(); + ResourcesDataManager resourcesDataManager = reportContext.getDataManager(ResourcesDataManager.class); + MaterialsDataManager materialsDataManager = reportContext.getDataManager(MaterialsDataManager.class); + for (ResourceId resourceId : resourcesDataManager.getResourceIds()) { + long materialsProducerResourceLevel = materialsDataManager + .getMaterialsProducerResourceLevel(materialsProducerId, resourceId); + writeReportItem(reportContext, resourceId, materialsProducerId, Action.ADDED, + materialsProducerResourceLevel); + } + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/MaterialsProducerResourceReportPluginData.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/MaterialsProducerResourceReportPluginData.java new file mode 100644 index 000000000..a4584c5f5 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/MaterialsProducerResourceReportPluginData.java @@ -0,0 +1,192 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.reports; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.PluginDataBuilder; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportError; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import net.jcip.annotations.ThreadSafe; +import util.errors.ContractException; + +/** + * A PluginData class supporting BatchStatusReport construction. + */ +@ThreadSafe +public final class MaterialsProducerResourceReportPluginData implements PluginData { + + /* + * Data class for collecting the inputs to the report + */ + private static class Data { + private ReportLabel reportLabel; + + private boolean locked; + + private Data() { + } + + private Data(Data data) { + reportLabel = data.reportLabel; + locked = data.locked; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((reportLabel == null) ? 0 : reportLabel.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + if (reportLabel == null) { + if (other.reportLabel != null) { + return false; + } + } else if (!reportLabel.equals(other.reportLabel)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Data [reportLabel="); + builder.append(reportLabel); + builder.append(", locked="); + builder.append(locked); + builder.append("]"); + return builder.toString(); + } + + } + + /** + * Returns a new instance of the builder class + */ + public static Builder builder() { + return new Builder(new Data()); + } + + /** + * Builder class for the report + */ + public final static class Builder implements PluginDataBuilder { + private Builder(Data data) { + this.data = data; + } + + private void ensureDataMutability() { + if (data.locked) { + data = new Data(data); + data.locked = false; + } + } + + private void ensureImmutability() { + if (!data.locked) { + data.locked = true; + } + } + + private void validateData() { + if (data.reportLabel == null) { + throw new ContractException(ReportError.NULL_REPORT_LABEL); + } + } + + private Data data; + + /** + * Returns a PersonPropertyReportPluginData created from the collected inputs + * + * @throws ContractException {@linkplain ReportError#NULL_REPORT_LABEL} if the + * report label is not assigned + */ + public MaterialsProducerResourceReportPluginData build() { + + if (!data.locked) { + validateData(); + } + ensureImmutability(); + return new MaterialsProducerResourceReportPluginData(data); + + } + + /** + * Sets the report label + * + * @throws ContractException {@linkplain ReportError#NULL_REPORT_LABEL} if the + * report label is null + */ + public Builder setReportLabel(ReportLabel reportLabel) { + ensureDataMutability(); + if (reportLabel == null) { + throw new ContractException(ReportError.NULL_REPORT_LABEL); + } + data.reportLabel = reportLabel; + return this; + } + + } + + private final Data data; + + private MaterialsProducerResourceReportPluginData(Data data) { + this.data = data; + } + + @Override + public Builder getCloneBuilder() { + return new Builder(data); + } + + public ReportLabel getReportLabel() { + return data.reportLabel; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof MaterialsProducerResourceReportPluginData)) { + return false; + } + MaterialsProducerResourceReportPluginData other = (MaterialsProducerResourceReportPluginData) obj; + if (data == null) { + if (other.data != null) { + return false; + } + } else if (!data.equals(other.data)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder2 = new StringBuilder(); + builder2.append("MaterialsProducerResourceReportPluginData [data="); + builder2.append(data); + builder2.append("]"); + return builder2.toString(); + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/StageReport.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/StageReport.java new file mode 100644 index 000000000..98410d739 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/StageReport.java @@ -0,0 +1,162 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.reports; + +import java.util.LinkedHashMap; +import java.util.Map; + +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.plugins.materials.datamangers.MaterialsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.StageAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.StageImminentRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.StageMaterialsProducerUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.StageOfferUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.StageId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; + +/** + * A Report that displays the creation, destruction, offering, batch conversion, + * resource conversion and transfer of stages. Fields Time -- the time in days + * when the global resource was set Stage -- the stage identifier + * MaterialsProducer -- the acting materials producer Action -- One of Create, + * Destroy, Offer, BatchConversion, ResourceConversion, Transfer Offered -- the + * offered state of the stage ResourceMaterial Amount + */ +public final class StageReport { + private final ReportLabel reportLabel; + + public StageReport(StageReportPluginData stageReportPluginData) { + this.reportLabel = stageReportPluginData.getReportLabel(); + } + + private static class StageRecord { + StageId stageId; + MaterialsProducerId materialsProducerId; + boolean isOffered; + Action lastAction; + } + + private Map stageRecords = new LinkedHashMap<>(); + + /* + * An enumeration mirroring the cause of a change to a stage + */ + private static enum Action { + CREATED("Create"), + + DESTROYED("Destroy"), + + OFFERED("Offer"), + + TRANSFERRED("Transfer"); + + private final String displayName; + + private Action(final String displayName) { + this.displayName = displayName; + } + } + + /* + * The derived header for this report + */ + private ReportHeader reportHeader; + + private ReportHeader getReportHeader() { + if (reportHeader == null) { + reportHeader = ReportHeader.builder()// + .add("time")// + .add("stage")// + .add("materials_producer")// + .add("action")// + .add("offered")// + .build();// + } + return reportHeader; + } + + private void handleStageAdditionEvent(ReportContext reportContext, StageAdditionEvent stageAdditionEvent) { + + StageRecord stageRecord = new StageRecord(); + stageRecord.stageId = stageAdditionEvent.stageId(); + stageRecord.isOffered = materialsDataManager.isStageOffered(stageRecord.stageId); + stageRecord.materialsProducerId = materialsDataManager.getStageProducer(stageRecord.stageId); + stageRecord.lastAction = Action.CREATED; + stageRecords.put(stageRecord.stageId, stageRecord); + writeReportItem(reportContext, stageRecord); + } + + private void handleStageImminentRemovalEvent(ReportContext reportContext, + StageImminentRemovalEvent stageImminentRemovalEvent) { + StageId stageId = stageImminentRemovalEvent.stageId(); + StageRecord stageRecord = stageRecords.remove(stageId); + stageRecord.lastAction = Action.DESTROYED; + writeReportItem(reportContext, stageRecord); + } + + private void handleStageOfferUpdateEvent(ReportContext reportContext, StageOfferUpdateEvent stageOfferUpdateEvent) { + StageId stageId = stageOfferUpdateEvent.stageId(); + StageRecord stageRecord = stageRecords.get(stageId); + stageRecord.isOffered = stageOfferUpdateEvent.currentOfferState(); + stageRecord.lastAction = Action.OFFERED; + writeReportItem(reportContext, stageRecord); + } + + private void handleStageMaterialsProducerUpdateEvent(ReportContext reportContext, + StageMaterialsProducerUpdateEvent stageMaterialsProducerUpdateEvent) { + StageId stageId = stageMaterialsProducerUpdateEvent.stageId(); + StageRecord stageRecord = stageRecords.get(stageId); + stageRecord.materialsProducerId = stageMaterialsProducerUpdateEvent.currentMaterialsProducerId(); + stageRecord.lastAction = Action.TRANSFERRED; + writeReportItem(reportContext, stageRecord); + } + + private void writeReportItem(ReportContext reportContext, final StageRecord stageRecord) { + final ReportItem.Builder reportItemBuilder = ReportItem.builder(); + reportItemBuilder.setReportHeader(getReportHeader()); + reportItemBuilder.setReportLabel(reportLabel); + reportItemBuilder.addValue(reportContext.getTime()); + reportItemBuilder.addValue(stageRecord.stageId); + reportItemBuilder.addValue(stageRecord.materialsProducerId.toString()); + reportItemBuilder.addValue(stageRecord.lastAction.displayName); + reportItemBuilder.addValue(stageRecord.isOffered); + reportContext.releaseOutput(reportItemBuilder.build()); + } + + private MaterialsDataManager materialsDataManager; + + public void init(final ReportContext reportContext) { + + reportContext.subscribe(StageOfferUpdateEvent.class, this::handleStageOfferUpdateEvent); + reportContext.subscribe(StageAdditionEvent.class, this::handleStageAdditionEvent); + reportContext.subscribe(StageImminentRemovalEvent.class, this::handleStageImminentRemovalEvent); + reportContext.subscribe(StageMaterialsProducerUpdateEvent.class, this::handleStageMaterialsProducerUpdateEvent); + if (reportContext.stateRecordingIsScheduled()) { + reportContext.subscribeToSimulationClose(this::recordSimulationState); + } + + materialsDataManager = reportContext.getDataManager(MaterialsDataManager.class); + + for (MaterialsProducerId materialsProducerId : materialsDataManager.getMaterialsProducerIds()) { + for (StageId stageId : materialsDataManager.getStages(materialsProducerId)) { + + StageRecord stageRecord = new StageRecord(); + stageRecord.stageId = stageId; + stageRecord.isOffered = materialsDataManager.isStageOffered(stageId); + stageRecord.materialsProducerId = materialsProducerId; + stageRecord.lastAction = Action.CREATED; + stageRecords.put(stageRecord.stageId, stageRecord); + + writeReportItem(reportContext, stageRecord); + } + } + } + + private void recordSimulationState(ReportContext reportContext) { + StageReportPluginData.Builder builder = StageReportPluginData.builder(); + builder.setReportLabel(reportLabel); + reportContext.releaseOutput(builder.build()); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/StageReportPluginData.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/StageReportPluginData.java new file mode 100644 index 000000000..aef57a926 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/StageReportPluginData.java @@ -0,0 +1,192 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.reports; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.PluginDataBuilder; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportError; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import net.jcip.annotations.ThreadSafe; +import util.errors.ContractException; + +/** + * A PluginData class supporting StageReport construction. + */ +@ThreadSafe +public final class StageReportPluginData implements PluginData { + + /* + * Data class for collecting the inputs to the report + */ + private static class Data { + private ReportLabel reportLabel; + + private boolean locked; + + private Data() { + } + + private Data(Data data) { + reportLabel = data.reportLabel; + locked = data.locked; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((reportLabel == null) ? 0 : reportLabel.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + if (reportLabel == null) { + if (other.reportLabel != null) { + return false; + } + } else if (!reportLabel.equals(other.reportLabel)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Data [reportLabel="); + builder.append(reportLabel); + builder.append(", locked="); + builder.append(locked); + builder.append("]"); + return builder.toString(); + } + + } + + /** + * Returns a new instance of the builder class + */ + public static Builder builder() { + return new Builder(new Data()); + } + + /** + * Builder class for the report + */ + public final static class Builder implements PluginDataBuilder { + private Builder(Data data) { + this.data = data; + } + + private void ensureDataMutability() { + if (data.locked) { + data = new Data(data); + data.locked = false; + } + } + + private void ensureImmutability() { + if (!data.locked) { + data.locked = true; + } + } + + private void validateData() { + if (data.reportLabel == null) { + throw new ContractException(ReportError.NULL_REPORT_LABEL); + } + } + + private Data data; + + /** + * Returns a PersonPropertyReportPluginData created from the collected inputs + * + * @throws ContractException {@linkplain ReportError#NULL_REPORT_LABEL} if the + * report label is not assigned + */ + public StageReportPluginData build() { + + if (!data.locked) { + validateData(); + } + ensureImmutability(); + return new StageReportPluginData(data); + + } + + /** + * Sets the report label + * + * @throws ContractException {@linkplain ReportError#NULL_REPORT_LABEL} if the + * report label is null + */ + public Builder setReportLabel(ReportLabel reportLabel) { + ensureDataMutability(); + if (reportLabel == null) { + throw new ContractException(ReportError.NULL_REPORT_LABEL); + } + data.reportLabel = reportLabel; + return this; + } + + } + + private final Data data; + + private StageReportPluginData(Data data) { + this.data = data; + } + + @Override + public Builder getCloneBuilder() { + return new Builder(data); + } + + public ReportLabel getReportLabel() { + return data.reportLabel; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof StageReportPluginData)) { + return false; + } + StageReportPluginData other = (StageReportPluginData) obj; + if (data == null) { + if (other.data != null) { + return false; + } + } else if (!data.equals(other.data)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder2 = new StringBuilder(); + builder2.append("StageReportPluginData [data="); + builder2.append(data); + builder2.append("]"); + return builder2.toString(); + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/BatchConstructionInfo.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/BatchConstructionInfo.java new file mode 100644 index 000000000..3eb5b49cc --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/BatchConstructionInfo.java @@ -0,0 +1,186 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.support; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * Represents the information to fully specify a batch, but not its relationship + * to stages + */ +@Immutable +public class BatchConstructionInfo { + + private BatchConstructionInfo(Data data) { + this.data = data; + } + + private final Data data; + + private static class Data { + private MaterialsProducerId materialsProducerId; + private MaterialId materialId; + private double amount; + private Map propertyValues = new LinkedHashMap<>(); + + public Data() { + } + + public Data(Data data) { + materialsProducerId = data.materialsProducerId; + materialId = data.materialId; + amount = data.amount; + propertyValues.putAll(data.propertyValues); + } + } + + /** + * Returns a builder instance + */ + public static Builder builder() { + return new Builder(); + } + + /** + * A builder class for BatchConstructionInfo + */ + public static class Builder { + private Data data = new Data(); + + private Builder() { + } + + private void validate() { + if (data.materialId == null) { + throw new ContractException(MaterialsError.NULL_MATERIAL_ID); + } + if (data.materialsProducerId == null) { + throw new ContractException(MaterialsError.NULL_MATERIALS_PRODUCER_ID); + } + } + + /** + * Builds the BatchConstructionInfo from the collected data + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_MATERIAL_ID} if + * the material id was not set
    • + *
    • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} + * if the materials producer id was not set
    • + *
    + */ + public BatchConstructionInfo build() { + validate(); + return new BatchConstructionInfo(new Data(data)); + } + + /** + * Sets the amount. Defaulted to zero. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NEGATIVE_MATERIAL_AMOUNT} + * * if the amount is negative
    • + *
    • {@linkplain MaterialsError#NON_FINITE_MATERIAL_AMOUNT} + * if the amount is not finite
    • + *
    + */ + public Builder setAmount(double amount) { + if (amount < 0) { + throw new ContractException(MaterialsError.NEGATIVE_MATERIAL_AMOUNT); + } + if (!Double.isFinite(amount)) { + throw new ContractException(MaterialsError.NON_FINITE_MATERIAL_AMOUNT); + } + data.amount = amount; + return this; + } + + /** + * Sets the material id. Defaulted to null. + * + * @throws ContractException {@linkplain MaterialsError#NULL_MATERIAL_ID} if the + * material id is null + */ + public Builder setMaterialId(MaterialId materialId) { + if (materialId == null) { + throw new ContractException(MaterialsError.NULL_MATERIAL_ID); + } + data.materialId = materialId; + return this; + } + + /** + * Sets the materials producer id. Defaulted to null. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} + * if the materials producer id is null
    • + *
    + */ + public Builder setMaterialsProducerId(MaterialsProducerId materialsProducerId) { + if (materialsProducerId == null) { + throw new ContractException(MaterialsError.NULL_MATERIALS_PRODUCER_ID); + } + data.materialsProducerId = materialsProducerId; + return this; + } + + /** + * Sets a property value; + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the property id is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_VALUE} + * if the property value is null
    • + *
    + */ + public Builder setPropertyValue(BatchPropertyId batchPropertyId, Object propertyValue) { + if (batchPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + if (propertyValue == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_VALUE); + } + data.propertyValues.put(batchPropertyId, propertyValue); + return this; + } + } + + /** + * Return the material id of the new batch + */ + public MaterialId getMaterialId() { + return data.materialId; + } + + /** + * Return the materials producer id of the new batch + */ + public MaterialsProducerId getMaterialsProducerId() { + return data.materialsProducerId; + } + + /** + * Returns the amount of the new batch + */ + public double getAmount() { + return data.amount; + } + + /** + * Returns a map of the batch property values of the new batch + */ + public Map getPropertyValues() { + return Collections.unmodifiableMap(data.propertyValues); + } + +} diff --git a/gcm3/src/main/java/plugins/materials/support/BatchId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/BatchId.java similarity index 84% rename from gcm3/src/main/java/plugins/materials/support/BatchId.java rename to gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/BatchId.java index 70f2401c6..4927ed438 100644 --- a/gcm3/src/main/java/plugins/materials/support/BatchId.java +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/BatchId.java @@ -1,12 +1,9 @@ -package plugins.materials.support; +package gov.hhs.aspr.ms.gcm.plugins.materials.support; import net.jcip.annotations.Immutable; /** * Identifier for all batches - * - * @author Shawn Hatch - * */ @Immutable public final class BatchId implements Comparable { @@ -23,7 +20,7 @@ public int getValue() { @Override public int compareTo(BatchId personId) { - return Integer.compare(id,personId.id); + return Integer.compare(id, personId.id); } @Override @@ -45,8 +42,8 @@ public boolean equals(Object obj) { } return true; } - - @Override + + @Override public String toString() { return Integer.toString(id); } diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/BatchPropertyDefinitionInitialization.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/BatchPropertyDefinitionInitialization.java new file mode 100644 index 000000000..cfd6e82c5 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/BatchPropertyDefinitionInitialization.java @@ -0,0 +1,205 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.support; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.commons.math3.util.Pair; + +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * A class for defining a batch property with an associated property id and + * property values for extant batches. + */ +@Immutable +public final class BatchPropertyDefinitionInitialization { + + private static class Data { + MaterialId materialId; + BatchPropertyId batchPropertyId; + PropertyDefinition propertyDefinition; + List> propertyValues = new ArrayList<>(); + + public Data() { + } + + public Data(Data data) { + materialId = data.materialId; + batchPropertyId = data.batchPropertyId; + propertyDefinition = data.propertyDefinition; + propertyValues.addAll(data.propertyValues); + + } + } + + private final Data data; + + private BatchPropertyDefinitionInitialization(Data data) { + this.data = data; + } + + /** + * Returns a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder class for a BatchPropertyDefinitionInitialization + */ + public final static class Builder { + private Builder() { + } + + private Data data = new Data(); + + private void validate() { + if (data.propertyDefinition == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_DEFINITION); + } + + if (data.batchPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + + if (data.materialId == null) { + throw new ContractException(MaterialsError.NULL_MATERIAL_ID); + } + + Class type = data.propertyDefinition.getType(); + for (Pair pair : data.propertyValues) { + Object value = pair.getSecond(); + if (!type.isAssignableFrom(value.getClass())) { + String message = "Definition Type " + type.getName() + " is not compatible with value = " + value; + throw new ContractException(PropertyError.INCOMPATIBLE_VALUE, message); + } + } + } + + /** + * Constructs the BatchPropertyDefinitionInitialization from the collected data + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_DEFINITION} + * if no property definition was assigned to the + * builder
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * no property id was assigned to the builder
    • + *
    • {@linkplain PropertyError#INCOMPATIBLE_VALUE} + * if a collected property value is incompatible with + * the property definition
    • + *
    • {@linkplain MaterialsError#NULL_MATERIAL_ID} if + * no material id was assigned to the builder
    • + *
    + */ + public BatchPropertyDefinitionInitialization build() { + validate(); + return new BatchPropertyDefinitionInitialization(new Data(data)); + } + + /** + * Sets the batch property id + * + * @throws ContractException {@linkplain PropertyError#NULL_PROPERTY_ID} if the + * property id is null + */ + public Builder setPropertyId(BatchPropertyId batchPropertyId) { + if (batchPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + data.batchPropertyId = batchPropertyId; + return this; + } + + /** + * Sets the material id + * + * @throws ContractException {@linkplain MaterialsError#NULL_MATERIAL_ID} if the + * material id is null + */ + public Builder setMaterialId(MaterialId materialId) { + if (materialId == null) { + throw new ContractException(MaterialsError.NULL_MATERIAL_ID); + } + data.materialId = materialId; + return this; + } + + /** + * Sets the property definition + * + * @throws ContractException {@linkplain PropertyError#NULL_PROPERTY_DEFINITION} + * if the property definition is null + */ + public Builder setPropertyDefinition(PropertyDefinition propertyDefinition) { + if (propertyDefinition == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_DEFINITION); + } + data.propertyDefinition = propertyDefinition; + return this; + } + + /** + * Adds a property value + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_BATCH_ID} if + * the batch id is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_VALUE} + * if the property value is null
    • + *
    + */ + public Builder addPropertyValue(BatchId batchId, Object value) { + if (batchId == null) { + throw new ContractException(MaterialsError.NULL_BATCH_ID); + } + + if (value == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_VALUE); + } + + data.propertyValues.add(new Pair<>(batchId, value)); + return this; + } + + } + + /** + * Returns the (non-null)property id. + */ + public BatchPropertyId getPropertyId() { + return data.batchPropertyId; + } + + /** + * Returns the (non-null) material id. + */ + public MaterialId getMaterialId() { + return data.materialId; + } + + /** + * Returns the (non-null)property definition. + */ + public PropertyDefinition getPropertyDefinition() { + return data.propertyDefinition; + } + + /** + * Returns the list of (batchId,value) pairs collected by the builder in the + * order of their addition. All pairs have non-null entries and the values are + * compatible with the contained property definition. Duplicate assignments of + * values to the same batch may be present. + */ + public List> getPropertyValues() { + return Collections.unmodifiableList(data.propertyValues); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/BatchPropertyId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/BatchPropertyId.java new file mode 100644 index 000000000..4063d5bff --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/BatchPropertyId.java @@ -0,0 +1,11 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.support; + +import net.jcip.annotations.ThreadSafe; + +/** + * Marker interface for batch property identifiers + */ +@ThreadSafe +public interface BatchPropertyId { + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/MaterialId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/MaterialId.java new file mode 100644 index 000000000..3713268e7 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/MaterialId.java @@ -0,0 +1,15 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.support; + +import net.jcip.annotations.ThreadSafe; + +/** + * Marker interface for material identifiers + */ +@ThreadSafe +public interface MaterialId { + @Override + public int hashCode(); + + @Override + public boolean equals(Object obj); +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/MaterialsError.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/MaterialsError.java new file mode 100644 index 000000000..17f9ffda9 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/MaterialsError.java @@ -0,0 +1,59 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.support; + +import util.errors.ContractError; +import util.errors.ContractException; + +/** + * An enumeration supporting {@link ContractException} that acts as a general + * description of the exception. + */ +public enum MaterialsError implements ContractError { + + BATCH_ALREADY_INVENTORIED("Batch is already in another inventory"), BATCH_ALREADY_STAGED("Batch is already staged"), + BATCH_IN_MULTIPLE_LOCATIONS("Batch is in a materials producer inventory and on a stage"), + BATCH_NOT_OWNED("Batch is neither in a materials producer inventory nor on a stage"), + BATCH_NOT_STAGED("Batch is not currently staged"), + BATCH_SHIFT_WITH_MULTIPLE_OWNERS( + "Cannot shift material from a batch to another batch not owned by the same materials producer"), + BATCH_STAGED_TO_DIFFERENT_OWNER("Cannot stage a batch onto a stage not owned by the same materials producer"), + DUPLICATE_MATERIAL("Duplicate material addition"), + DUPLICATE_MATERIALS_PRODUCER_ID("Duplicate materials producer id"), + DUPLICATE_MATERIALS_PRODUCER_PROPERTY_ID("Duplicate materials producer property id"), + DUPLICATE_STAGE_ASSIGNMENT("Duplicate stage assignment to different materials producers"), + INSUFFICIENT_MATERIAL_AVAILABLE("Material level is insufficient for transaction amount"), + MATERIAL_ARITHMETIC_EXCEPTION("Material arithmetic error due to non finite sum"), + MATERIAL_TYPE_MISMATCH("Material identifiers do not match"), NEGATIVE_BATCH_ID("Negative batch id"), + NEGATIVE_MATERIAL_AMOUNT("Material amount is negative"), NEGATIVE_STAGE_ID("Negative stage id"), + NEXT_BATCH_ID_TOO_SMALL("The next batch id must exceed all extant batch ids"), + NEXT_STAGE_ID_TOO_SMALL("The next stage id must exceed all extant stage ids"), + NON_FINITE_MATERIAL_AMOUNT("Material amount is not finite"), NULL_AUXILIARY_DATA("Null auxiliary data"), + NULL_BATCH_CONSTRUCTION_INFO("Null batch construction info"), NULL_BATCH_ID("Null batch id"), + NULL_BATCH_STATUS_REPORT_PLUGIN_DATA("Null batch status report plugin data"), NULL_MATERIAL_ID("Null material id"), + NULL_MATERIALS_PLUGIN_DATA("Null materials initial data"), NULL_MATERIALS_PRODUCER_ID("Null materials producer id"), + NULL_MATERIALS_PRODUCER_PROPERTY_DEFINITION_INITIALIZATION( + "Null materials producer property definition initialization"), + NULL_MATERIALS_PRODUCER_PROPERTY_REPORT_PLUGIN_DATA("Null materials producer property report plugin data"), + NULL_MATERIALS_PRODUCER_RESOURCE_REPORT_PLUGIN_DATA("Null materials producer resource report plugin data"), + NULL_STAGE_CONVERSION_INFO("Null stage conversion info"), NULL_STAGE_ID("Null stage id"), + NULL_STAGE_REPORT_PLUGIN_DATA("Null stage report plugin data"), + OFFERED_STAGE_UNALTERABLE("An offered stage and its batches cannot be altered"), + REFLEXIVE_BATCH_SHIFT("Cannot shift material from a batch to itself"), + REFLEXIVE_STAGE_TRANSFER("Producer cannot transfer a stage to itself"), + STAGE_WITHOUT_MATERIALS_PRODUCER("A stage lacks an assignment to a materials producer"), + UNKNOWN_BATCH_ID("Unknown batch id"), UNKNOWN_MATERIAL_ID("Unknown material id"), + UNKNOWN_MATERIALS_PRODUCER_ID("Unknown materials producer id"), UNKNOWN_STAGE_ID("Unknown stage id"), + UNOFFERED_STAGE_NOT_TRANSFERABLE("Unoffered stages are not transferable"), + + ; + + private final String description; + + private MaterialsError(final String description) { + this.description = description; + } + + @Override + public String getDescription() { + return description; + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/MaterialsProducerConstructionData.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/MaterialsProducerConstructionData.java new file mode 100644 index 000000000..d528c9f63 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/MaterialsProducerConstructionData.java @@ -0,0 +1,195 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.support; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceError; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.errors.ContractException; + +public class MaterialsProducerConstructionData { + + private static class Data { + private MaterialsProducerId materialsProducerId; + private List values = new ArrayList<>(); + private Map propertyValues = new LinkedHashMap<>(); + private Map resourceLevels = new LinkedHashMap<>(); + + public Data() { + } + + public Data(Data data) { + materialsProducerId = data.materialsProducerId; + values.addAll(data.values); + propertyValues.putAll(data.propertyValues); + resourceLevels.putAll(data.resourceLevels); + } + } + + /** + * Returns a new Builder instance + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Static builder class for {@link MaterialsProducerConstructionData} + */ + public static class Builder { + private Data data = new Data(); + + private Builder() { + } + + private void validate() { + if (data.materialsProducerId == null) { + throw new ContractException(MaterialsError.NULL_MATERIALS_PRODUCER_ID); + } + } + + /** + * Builds the MaterialsProducerConstructionData from the given inputs. + * + * @throws ContractException {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} + * if the materials producer id was not set + */ + public MaterialsProducerConstructionData build() { + validate(); + return new MaterialsProducerConstructionData(new Data(data)); + } + + /** + * Sets the materials producer id + * + * @throws ContractException {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} + * if the materials producer id is null + */ + public Builder setMaterialsProducerId(MaterialsProducerId materialsProducerId) { + if (materialsProducerId == null) { + throw new ContractException(MaterialsError.NULL_MATERIALS_PRODUCER_ID); + } + data.materialsProducerId = materialsProducerId; + return this; + } + + /** + * Adds an auxiliary value to be used by observers of materials producer + * addition + * + * @throws ContractException {@linkplain MaterialsError#NULL_AUXILIARY_DATA} if + * the value is null + */ + public Builder addValue(Object value) { + if (value == null) { + throw new ContractException(MaterialsError.NULL_AUXILIARY_DATA); + } + data.values.add(value); + return this; + } + + /** + * Sets a materials producer property value + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the materials producer property id is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_VALUE} + * if the value is null
    • + *
    • {@linkplain PropertyError#DUPLICATE_PROPERTY_VALUE_ASSIGNMENT} + * if the materials producer property was previously + * set
    • + *
    + */ + public Builder setMaterialsProducerPropertyValue(MaterialsProducerPropertyId materialsProducerPropertyId, + Object value) { + if (materialsProducerPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + if (value == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_VALUE); + } + if (data.propertyValues.containsKey(materialsProducerPropertyId)) { + throw new ContractException(PropertyError.DUPLICATE_PROPERTY_VALUE_ASSIGNMENT); + } + data.propertyValues.put(materialsProducerPropertyId, value); + return this; + } + + /** + * Sets a materials producer resource level + * + * @throws ContractException + *
      + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#NEGATIVE_RESOURCE_AMOUNT} + * if the level is negative
    • + *
    • {@linkplain ResourceError#DUPLICATE_REGION_RESOURCE_LEVEL_ASSIGNMENT} + * if the resource level was previously set
    • + *
    + */ + public Builder setResourceLevel(ResourceId resourceId, long level) { + + if (resourceId == null) { + throw new ContractException(ResourceError.NULL_RESOURCE_ID); + } + if (level < 0) { + throw new ContractException(ResourceError.NEGATIVE_RESOURCE_AMOUNT); + } + if (data.resourceLevels.containsKey(resourceId)) { + throw new ContractException(ResourceError.DUPLICATE_REGION_RESOURCE_LEVEL_ASSIGNMENT); + } + data.resourceLevels.put(resourceId, level); + return this; + } + + } + + private final Data data; + + private MaterialsProducerConstructionData(Data data) { + this.data = data; + } + + /** + * Returns a non-null materials producer id + */ + public MaterialsProducerId getMaterialsProducerId() { + return data.materialsProducerId; + } + + /** + * Returns the (non-null) auxiliary objects that are instances of the given + * class in the order of their addition to the builder. + */ + @SuppressWarnings("unchecked") + public List getValues(Class c) { + List result = new ArrayList<>(); + for (Object value : data.values) { + if (c.isAssignableFrom(value.getClass())) { + result.add((T) value); + } + } + return result; + } + + /** + * Returns an unmodifiable map of materials producer property ids to values + */ + public Map getMaterialsProducerPropertyValues() { + return Collections.unmodifiableMap(data.propertyValues); + } + + /** + * Returns an unmodifiable map of materials producer resource levels + */ + public Map getResourceLevels() { + return Collections.unmodifiableMap(data.resourceLevels); + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/MaterialsProducerId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/MaterialsProducerId.java new file mode 100644 index 000000000..6d0279f66 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/MaterialsProducerId.java @@ -0,0 +1,15 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.support; + +import net.jcip.annotations.ThreadSafe; + +/** + * Marker interface for materials producer identifiers + */ +@ThreadSafe +public interface MaterialsProducerId { + @Override + public int hashCode(); + + @Override + public boolean equals(Object obj); +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/MaterialsProducerPropertyDefinitionInitialization.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/MaterialsProducerPropertyDefinitionInitialization.java new file mode 100644 index 000000000..2786054ad --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/MaterialsProducerPropertyDefinitionInitialization.java @@ -0,0 +1,176 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.support; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.commons.math3.util.Pair; + +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * A class for defining a material producer property with an associated property + * id and property values for extant materials producers. + */ +@Immutable +public final class MaterialsProducerPropertyDefinitionInitialization { + + private static class Data { + MaterialsProducerPropertyId materialsProducerPropertyId; + PropertyDefinition propertyDefinition; + List> propertyValues = new ArrayList<>(); + + public Data() { + } + + public Data(Data data) { + materialsProducerPropertyId = data.materialsProducerPropertyId; + propertyDefinition = data.propertyDefinition; + propertyValues.addAll(data.propertyValues); + } + } + + private final Data data; + + private MaterialsProducerPropertyDefinitionInitialization(Data data) { + this.data = data; + } + + /** + * Returns a new Builder instance + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder class for a MaterialsProducerPropertyDefinitionInitialization + */ + public final static class Builder { + + private Builder() { + } + + private Data data = new Data(); + + private void validate() { + if (data.propertyDefinition == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_DEFINITION); + } + + if (data.materialsProducerPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + + Class type = data.propertyDefinition.getType(); + for (Pair pair : data.propertyValues) { + Object value = pair.getSecond(); + if (!type.isAssignableFrom(value.getClass())) { + String message = "Definition Type " + type.getName() + " is not compatible with value = " + value; + throw new ContractException(PropertyError.INCOMPATIBLE_VALUE, message); + } + } + } + + /** + * Constructs the MaterialsProducerPropertyDefinitionInitialization from the + * collected data + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_DEFINITION} + * if no property definition was assigned to the + * builder
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * no property id was assigned to the builder
    • + *
    • {@linkplain PropertyError#INCOMPATIBLE_VALUE} + * if a collected property value is incompatible with + * the property definition
    • + *
    + */ + public MaterialsProducerPropertyDefinitionInitialization build() { + validate(); + return new MaterialsProducerPropertyDefinitionInitialization(new Data(data)); + } + + /** + * Sets the materials producer property id + * + * @throws ContractException {@linkplain PropertyError#NULL_PROPERTY_ID} if the + * materials producer propertyId id is null + */ + public Builder setMaterialsProducerPropertyId(MaterialsProducerPropertyId materialsProducerPropertyId) { + if (materialsProducerPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + data.materialsProducerPropertyId = materialsProducerPropertyId; + return this; + } + + /** + * Sets the property definition + * + * @throws ContractException {@linkplain PropertyError#NULL_PROPERTY_DEFINITION} + * if the property definition is null + */ + public Builder setPropertyDefinition(PropertyDefinition propertyDefinition) { + if (propertyDefinition == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_DEFINITION); + } + data.propertyDefinition = propertyDefinition; + return this; + } + + /** + * Adds a property value + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} + * if the material producer id is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_VALUE} + * if the property value is null
    • + *
    + */ + public Builder addPropertyValue(MaterialsProducerId materialsProducerId, Object value) { + if (materialsProducerId == null) { + throw new ContractException(MaterialsError.NULL_MATERIALS_PRODUCER_ID); + } + if (value == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_VALUE); + } + + data.propertyValues.add(new Pair<>(materialsProducerId, value)); + return this; + } + + } + + /** + * Returns the (non-null) materials producer property id. + */ + public MaterialsProducerPropertyId getMaterialsProducerPropertyId() { + return data.materialsProducerPropertyId; + } + + /** + * Returns the (non-null) property definition. + */ + public PropertyDefinition getPropertyDefinition() { + return data.propertyDefinition; + } + + /** + * Returns the list of (MaterialsProducerId,value) pairs collected by the + * builder in the order of their addition. All pairs have non-null entries and + * the values are compatible with the contained property definition. Duplicate + * assignments of values to the same materials producer may be present. + */ + public List> getPropertyValues() { + return Collections.unmodifiableList(data.propertyValues); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/MaterialsProducerPropertyId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/MaterialsProducerPropertyId.java new file mode 100644 index 000000000..d9cf4d7ad --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/MaterialsProducerPropertyId.java @@ -0,0 +1,11 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.support; + +import net.jcip.annotations.ThreadSafe; + +/** + * Marker interface for materials producer property identifiers + */ +@ThreadSafe +public interface MaterialsProducerPropertyId { + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/StageConversionInfo.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/StageConversionInfo.java new file mode 100644 index 000000000..d0717f520 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/StageConversionInfo.java @@ -0,0 +1,187 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.support; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * Represents the information to fully specify the conversion of a stage into a + * batch + */ +@Immutable +public class StageConversionInfo { + + private StageConversionInfo(Data data) { + this.data = data; + } + + private final Data data; + + private static class Data { + private StageId stageId; + private MaterialId materialId; + private double amount; + private Map propertyValues = new LinkedHashMap<>(); + + public Data() { + } + + public Data(Data data) { + stageId = data.stageId; + materialId = data.materialId; + amount = data.amount; + propertyValues.putAll(data.propertyValues); + } + } + + /** + * Returns a builder instance + */ + public static Builder builder() { + return new Builder(); + } + + /** + * A builder class for BatchConstructionInfo + */ + public static class Builder { + private Data data = new Data(); + + private Builder() { + } + + private void validate() { + if (data.materialId == null) { + throw new ContractException(MaterialsError.NULL_MATERIAL_ID); + } + if (data.stageId == null) { + throw new ContractException(MaterialsError.NULL_STAGE_ID); + } + } + + /** + * Builds the BatchConstructionInfo from the collected data + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_MATERIAL_ID} if + * the material id was not set
    • + *
    • {@linkplain MaterialsError#NULL_STAGE_ID} if + * the stage id was not set
    • + *
    + */ + public StageConversionInfo build() { + validate(); + return new StageConversionInfo(new Data(data)); + } + + /** + * Sets the amount. Defaulted to zero. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NEGATIVE_MATERIAL_AMOUNT} + * * if the amount is negative
    • + *
    • {@linkplain MaterialsError#NON_FINITE_MATERIAL_AMOUNT} + * if the amount is not finite
    • + *
    + */ + public Builder setAmount(double amount) { + if (!Double.isFinite(amount)) { + throw new ContractException(MaterialsError.NON_FINITE_MATERIAL_AMOUNT); + } + if (amount < 0) { + throw new ContractException(MaterialsError.NEGATIVE_MATERIAL_AMOUNT); + } + + data.amount = amount; + return this; + } + + /** + * Sets the material id. Defaulted to null. + * + * @throws ContractException {@linkplain MaterialsError#NULL_MATERIAL_ID} if the + * material id is null + */ + public Builder setMaterialId(MaterialId materialId) { + if (materialId == null) { + throw new ContractException(MaterialsError.NULL_MATERIAL_ID); + } + data.materialId = materialId; + return this; + } + + /** + * Sets the stager id. Defaulted to null. + * + * @throws ContractException + *
      + *
    • {@linkplain MaterialsError#NULL_STAGE_ID} if + * the stage id is null
    • + *
    + */ + public Builder setStageId(StageId stageId) { + if (stageId == null) { + throw new ContractException(MaterialsError.NULL_STAGE_ID); + } + data.stageId = stageId; + return this; + } + + /** + * Sets a property value; + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the property id is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_VALUE} + * if the property value is null
    • + *
    + */ + public Builder setPropertyValue(BatchPropertyId batchPropertyId, Object propertyValue) { + if (batchPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + if (propertyValue == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_VALUE); + } + data.propertyValues.put(batchPropertyId, propertyValue); + return this; + } + } + + /** + * Return the material id of the new batch + */ + public MaterialId getMaterialId() { + return data.materialId; + } + + /** + * Return the stage id being converted into a batch + */ + public StageId getStageId() { + return data.stageId; + } + + /** + * Returns the amount of the new batch + */ + public double getAmount() { + return data.amount; + } + + /** + * Returns a map of the batch property values of the new batch + */ + public Map getPropertyValues() { + return Collections.unmodifiableMap(data.propertyValues); + } + +} diff --git a/gcm3/src/main/java/plugins/materials/support/StageId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/StageId.java similarity index 77% rename from gcm3/src/main/java/plugins/materials/support/StageId.java rename to gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/StageId.java index eb791722a..d85adebd0 100644 --- a/gcm3/src/main/java/plugins/materials/support/StageId.java +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/StageId.java @@ -1,15 +1,12 @@ -package plugins.materials.support; +package gov.hhs.aspr.ms.gcm.plugins.materials.support; import net.jcip.annotations.Immutable; /** * Identifier for all material stages - * - * @author Shawn Hatch - * */ @Immutable -public final class StageId implements Comparable{ +public final class StageId implements Comparable { private final int id; @@ -23,7 +20,7 @@ public int getValue() { @Override public int compareTo(StageId personId) { - return Integer.compare(id,personId.id); + return Integer.compare(id, personId.id); } @Override @@ -45,8 +42,8 @@ public boolean equals(Object obj) { } return true; } - - @Override + + @Override public String toString() { return Integer.toString(id); } diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/testsupport/MaterialsTestPluginFactory.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/testsupport/MaterialsTestPluginFactory.java new file mode 100644 index 000000000..9ee3dd026 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/testsupport/MaterialsTestPluginFactory.java @@ -0,0 +1,597 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.testsupport; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.Set; +import java.util.function.Consumer; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.materials.MaterialsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.materials.datamangers.MaterialsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.materials.reports.BatchStatusReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.materials.reports.MaterialsProducerPropertyReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.materials.reports.MaterialsProducerResourceReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.materials.reports.StageReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsError; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.StageId; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePlugin; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeoplePluginData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.regions.RegionsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionError; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.TestRegionId; +import gov.hhs.aspr.ms.gcm.plugins.resources.ResourcesPlugin; +import gov.hhs.aspr.ms.gcm.plugins.resources.datamanagers.ResourcesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceError; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.TestResourceId; +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.TestResourcePropertyId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.StochasticsError; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +/** + * A static test support class for the {@linkplain MaterialsPlugin}. Provides + * convenience methods for obtaining standarized PluginData for the listed + * Plugin. + *

    + * Also contains factory methods to obtain a list of plugins that is the minimal + * set needed to adequately test this Plugin that can be utilized with + *

    + * {@link TestSimulation#execute} + */ +public class MaterialsTestPluginFactory { + + private MaterialsTestPluginFactory() { + } + + private static class Data { + private MaterialsPluginData materialsPluginData; + private BatchStatusReportPluginData batchStatusReportPluginData; + private MaterialsProducerPropertyReportPluginData materialsProducerPropertyReportPluginData; + private MaterialsProducerResourceReportPluginData materialsProducerResourceReportPluginData; + private StageReportPluginData stageReportPluginData; + private ResourcesPluginData resourcesPluginData; + private RegionsPluginData regionsPluginData; + private PeoplePluginData peoplePluginData; + private StochasticsPluginData stochasticsPluginData; + private TestPluginData testPluginData; + + private Data(int numBatches, int numStages, int numBatchesInStage, long seed, TestPluginData testPluginData) { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + + this.materialsPluginData = getStandardMaterialsPluginData(numBatches, numStages, numBatchesInStage, seed); + this.resourcesPluginData = getStandardResourcesPluginData(randomGenerator.nextLong()); + this.regionsPluginData = getStandardRegionsPluginData(); + this.peoplePluginData = getStandardPeoplePluginData(); + this.stochasticsPluginData = getStandardStochasticsPluginData(randomGenerator.nextLong()); + this.testPluginData = testPluginData; + } + } + + /** + * Factory class that facilitates the building of {@linkplain PluginData} with + * the various setter methods. + */ + public static class Factory { + private Data data; + + private Factory(Data data) { + this.data = data; + } + + /** + * Returns a list of plugins containing a Materials, Resources, Regions, People, + * Stochastic and Test Plugin built from the contributed PluginDatas + *
      + *
    • MaterialsPlugin is defaulted to one formed from + * {@link MaterialsTestPluginFactory#getStandardMaterialsPluginData}
    • + *
    • ResourcesPlugin is defaulted to one formed from + * {@link MaterialsTestPluginFactory#getStandardResourcesPluginData}
    • + *
    • RegionsPlugin is defaulted to one formed from + * {@link MaterialsTestPluginFactory#getStandardRegionsPluginData}
    • + *
    • PeoplePlugin is defaulted to one formed from + * {@link MaterialsTestPluginFactory#getStandardPeoplePluginData}
    • + *
    • StochasticsPlugin is defaulted to one formed from + * {@link MaterialsTestPluginFactory#getStandardStochasticsPluginData}
    • + *
    • TestPlugin is formed from the TestPluginData passed into + * {@link MaterialsTestPluginFactory#factory}
    • + *
    + */ + public List getPlugins() { + List pluginsToAdd = new ArrayList<>(); + + MaterialsPlugin.Builder materialsPluginBuilder = MaterialsPlugin.builder(); + + materialsPluginBuilder.setMaterialsPluginData(this.data.materialsPluginData); + + if (data.batchStatusReportPluginData != null) { + materialsPluginBuilder.setBatchStatusReportPluginData(this.data.batchStatusReportPluginData); + } + if (data.materialsProducerPropertyReportPluginData != null) { + materialsPluginBuilder.setMaterialsProducerPropertyReportPluginData( + this.data.materialsProducerPropertyReportPluginData); + } + if (data.materialsProducerResourceReportPluginData != null) { + materialsPluginBuilder.setMaterialsProducerResourceReportPluginData( + this.data.materialsProducerResourceReportPluginData); + } + if (data.stageReportPluginData != null) { + materialsPluginBuilder.setStageReportPluginData(this.data.stageReportPluginData); + } + + Plugin materialsPlugin = materialsPluginBuilder.getMaterialsPlugin(); + + Plugin resourcesPlugin = ResourcesPlugin.builder().setResourcesPluginData(this.data.resourcesPluginData) + .getResourcesPlugin(); + + Plugin regionsPlugin = RegionsPlugin.builder().setRegionsPluginData(this.data.regionsPluginData) + .getRegionsPlugin(); + + Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(this.data.peoplePluginData); + + Plugin stochasticPlugin = StochasticsPlugin.getStochasticsPlugin(this.data.stochasticsPluginData); + + Plugin testPlugin = TestPlugin.getTestPlugin(this.data.testPluginData); + + pluginsToAdd.add(materialsPlugin); + pluginsToAdd.add(resourcesPlugin); + pluginsToAdd.add(regionsPlugin); + pluginsToAdd.add(peoplePlugin); + pluginsToAdd.add(stochasticPlugin); + pluginsToAdd.add(testPlugin); + + return pluginsToAdd; + } + + /** + * Sets the {@link MaterialsPluginData} in this Factory. This explicit instance + * of pluginData will be used to create a MaterialsPlugin + * + * @throws ContractException {@linkplain MaterialsError#NULL_MATERIALS_PLUGIN_DATA} + * if the passed in pluginData is null + */ + public Factory setMaterialsPluginData(MaterialsPluginData materialsPluginData) { + if (materialsPluginData == null) { + throw new ContractException(MaterialsError.NULL_MATERIALS_PLUGIN_DATA); + } + this.data.materialsPluginData = materialsPluginData; + return this; + } + + /** + * Sets the {@link BatchStatusReportPluginData} in this Factory. This explicit + * instance of pluginData will be used to create a MaterialsPlugin + * + * @throws ContractException {@linkplain MaterialsError#NULL_MATERIALS_PLUGIN_DATA} + * if the passed in pluginData is null + */ + public Factory setBatchStatusReportPluginData(BatchStatusReportPluginData batchStatusReportPluginData) { + if (batchStatusReportPluginData == null) { + throw new ContractException(MaterialsError.NULL_BATCH_STATUS_REPORT_PLUGIN_DATA); + } + this.data.batchStatusReportPluginData = batchStatusReportPluginData; + return this; + } + + /** + * Sets the {@link MaterialsProducerPropertyReportPluginData} in this Factory. + * This explicit instance of pluginData will be used to create a MaterialsPlugin + * + * @throws ContractException {@linkplain MaterialsError#NULL_MATERIALS_PLUGIN_DATA} + * if the passed in pluginData is null + */ + public Factory setMaterialsProducerPropertyReportPluginData( + MaterialsProducerPropertyReportPluginData materialsProducerPropertyReportPluginData) { + if (materialsProducerPropertyReportPluginData == null) { + throw new ContractException(MaterialsError.NULL_MATERIALS_PRODUCER_PROPERTY_REPORT_PLUGIN_DATA); + } + this.data.materialsProducerPropertyReportPluginData = materialsProducerPropertyReportPluginData; + return this; + } + + /** + * Sets the {@link MaterialsProducerResourceReportPluginData} in this Factory. + * This explicit instance of pluginData will be used to create a MaterialsPlugin + * + * @throws ContractException {@linkplain MaterialsError#NULL_MATERIALS_PLUGIN_DATA} + * if the passed in pluginData is null + */ + public Factory setMaterialsProducerResourceReportPluginData( + MaterialsProducerResourceReportPluginData materialsProducerResourceReportPluginData) { + if (materialsProducerResourceReportPluginData == null) { + throw new ContractException(MaterialsError.NULL_MATERIALS_PRODUCER_RESOURCE_REPORT_PLUGIN_DATA); + } + this.data.materialsProducerResourceReportPluginData = materialsProducerResourceReportPluginData; + return this; + } + + /** + * Sets the {@link MaterialsProducerResourceReportPluginData} in this Factory. + * This explicit instance of pluginData will be used to create a MaterialsPlugin + * + * @throws ContractException {@linkplain MaterialsError#NULL_MATERIALS_PLUGIN_DATA} + * if the passed in pluginData is null + */ + public Factory setStageReportPluginData(StageReportPluginData stageReportPluginData) { + if (stageReportPluginData == null) { + throw new ContractException(MaterialsError.NULL_STAGE_REPORT_PLUGIN_DATA); + } + this.data.stageReportPluginData = stageReportPluginData; + return this; + } + + /** + * Sets the {@link ResourcesPluginData} in this Factory. This explicit instance + * of pluginData will be used to create a ResourcesPlugin + * + * @throws ContractException {@linkplain ResourceError#NULL_RESOURCE_PLUGIN_DATA} + * if the passed in pluginData is null + */ + public Factory setResourcesPluginData(ResourcesPluginData resourcesPluginData) { + if (resourcesPluginData == null) { + throw new ContractException(ResourceError.NULL_RESOURCE_PLUGIN_DATA); + } + this.data.resourcesPluginData = resourcesPluginData; + return this; + } + + /** + * Sets the {@link RegionsPluginData} in this Factory. This explicit instance of + * pluginData will be used to create a RegionsPlugin + * + * @throws ContractException {@linkplain RegionError#NULL_REGION_PLUGIN_DATA} if + * the passed in pluginData is null + */ + public Factory setRegionsPluginData(RegionsPluginData regionsPluginData) { + if (regionsPluginData == null) { + throw new ContractException(RegionError.NULL_REGION_PLUGIN_DATA); + } + this.data.regionsPluginData = regionsPluginData; + return this; + } + + /** + * Sets the {@link PeoplePluginData} in this Factory. This explicit instance of + * pluginData will be used to create a PeoplePlugin + * + * @throws ContractException {@linkplain PersonError#NULL_PEOPLE_PLUGIN_DATA} if + * the passed in pluginData is null + */ + public Factory setPeoplePluginData(PeoplePluginData peoplePluginData) { + if (peoplePluginData == null) { + throw new ContractException(PersonError.NULL_PEOPLE_PLUGIN_DATA); + } + this.data.peoplePluginData = peoplePluginData; + return this; + } + + /** + * Sets the {@link StochasticsPluginData} in this Factory. This explicit + * instance of pluginData will be used to create a StochasticsPlugin + * + * @throws ContractException {@linkplain StochasticsError#NULL_STOCHASTICS_PLUGIN_DATA} + * if the passed in pluginData is null + */ + public Factory setStochasticsPluginData(StochasticsPluginData stochasticsPluginData) { + if (stochasticsPluginData == null) { + throw new ContractException(StochasticsError.NULL_STOCHASTICS_PLUGIN_DATA); + } + this.data.stochasticsPluginData = stochasticsPluginData; + return this; + } + + } + + /** + * Creates a Factory that facilitates the creation of a minimal set of plugins + * needed to adequately test the {@link MaterialsPlugin} by generating: + *
      + *
    • {@link MaterialsPluginData}
    • + *
    • {@link ResourcesPluginData}
    • + *
    • {@link RegionsPluginData}
    • + *
    • {@link PeoplePluginData}
    • + *
    • {@link StochasticsPluginData}
    • + *
    + * either directly (by default) via + *
      + *
    • {@link #getStandardMaterialsPluginData}, + *
    • {@link #getStandardResourcesPluginData}, + *
    • {@link #getStandardPeoplePluginData}, + *
    • {@link #getStandardRegionsPluginData}, + *
    • {@link #getStandardStochasticsPluginData}
    • + *
    + * or explicitly set via + *
      + *
    • {@link Factory#setMaterialsPluginData}
    • + *
    • {@link Factory#setResourcesPluginData}, + *
    • {@link Factory#setPeoplePluginData}, + *
    • {@link Factory#setRegionsPluginData}, + *
    • {@link Factory#setStochasticsPluginData}
    • + *
    + * via the {@link Factory#getPlugins()} method. + * + * @throws ContractException {@linkplain NucleusError#NULL_PLUGIN_DATA} if + * testPluginData is null + */ + public static Factory factory(int numBatches, int numStages, int numBatchesInStage, long seed, + TestPluginData testPluginData) { + if (testPluginData == null) { + throw new ContractException(NucleusError.NULL_PLUGIN_DATA); + } + return new Factory(new Data(numBatches, numStages, numBatchesInStage, seed, testPluginData)); + } + + /** + * Creates a Factory that facilitates the creation of a minimal set of plugins + * needed to adequately test the {@link MaterialsPlugin} by generating: + *
      + *
    • {@link MaterialsPluginData}
    • + *
    • {@link ResourcesPluginData}
    • + *
    • {@link RegionsPluginData}
    • + *
    • {@link PeoplePluginData}
    • + *
    • {@link StochasticsPluginData}
    • + *
    + * either directly (by default) via + *
      + *
    • {@link #getStandardMaterialsPluginData}, + *
    • {@link #getStandardResourcesPluginData}, + *
    • {@link #getStandardPeoplePluginData}, + *
    • {@link #getStandardRegionsPluginData}, + *
    • {@link #getStandardStochasticsPluginData}
    • + *
    + * or explicitly set via + *
      + *
    • {@link Factory#setMaterialsPluginData}
    • + *
    • {@link Factory#setResourcesPluginData}, + *
    • {@link Factory#setPeoplePluginData}, + *
    • {@link Factory#setRegionsPluginData}, + *
    • {@link Factory#setStochasticsPluginData}
    • + *
    + * via the {@link Factory#getPlugins()} method. + * + * @throws ContractException {@linkplain NucleusError#NULL_ACTOR_CONTEXT_CONSUMER} + * if consumer is null + */ + public static Factory factory(int numBatches, int numStages, int numBatchesInStage, long seed, + Consumer consumer) { + if (consumer == null) { + throw new ContractException(NucleusError.NULL_ACTOR_CONTEXT_CONSUMER); + } + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, consumer)); + TestPluginData testPluginData = pluginBuilder.build(); + return factory(numBatches, numStages, numBatchesInStage, seed, testPluginData); + } + + /** + * Returns a standardized MaterialsPluginData that is minimally adequate for + * testing the MaterialsPlugin The resulting MaterialsPluginData will include: + *
      + *
    • Every MaterialId included in {@link TestMaterialId} + *
        + *
      • Each MaterialId will be used to define a BatchProperty via + * {@link TestBatchPropertyId#getTestBatchPropertyIds} along with the + * propertyDefinition for each
      • + *
      + *
    • Every MaterialProducerId included in {@link TestMaterialsProducerId} With + * each one containing: + *
        + *
      • The specified number of batches. Each batch will have the following: + *
          + *
        • a random materialId gotten from + * {@link TestMaterialId#getRandomMaterialId} based on a RandomGenerator seeded + * by the passed in seed
        • + *
        • a random amount based on the same RandomGenerator.nextDouble + *
        • Every BatchPropertyId included in {@link TestBatchPropertyId} where the + * batchPropertyValue will be set if the batchPropertyDefinition does not have a + * default value OR if randomGenerator.nextBoolean is true. Either way, the + * value will be set to the result from + * {@link TestBatchPropertyId#getRandomPropertyValue}
        • + *
        + *
      • the specified number of stages. Exactly half will be offered. + *
      • the specified number of batches in a stage dervied from a shuffled list + * of batches via nextLong and a random stageId via nextInt
      • + *
      + *
    • Every MaterialProducerPropertyId included in + * {@link TestMaterialsProducerPropertyId} along with the defined + * propertyDefinition for each. + *
        + *
      • The MaterialProducerPropertyIds that do not have default values will have + * a property value set derived from + * {@link TestMaterialsProducerPropertyId#getRandomPropertyValue}
      • + *
      + *
    + * + * @throws RuntimeException if numBatches < numBatchesInStage + */ + public static MaterialsPluginData getStandardMaterialsPluginData(int numBatches, int numStages, + int numBatchesInStage, long seed) { + + if (numBatches < numBatchesInStage) { + throw new RuntimeException("numBatchesInStage exceeds " + numBatches); + } + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + MaterialsPluginData.Builder materialsBuilder = MaterialsPluginData.builder(); + + int bId = 0; + int sId = 0; + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + materialsBuilder.addMaterial(testMaterialId); + } + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + + List batches = new ArrayList<>(); + + for (int i = 0; i < numBatches; i++) { + + TestMaterialId testMaterialId = TestMaterialId.getRandomMaterialId(randomGenerator); + double amount = randomGenerator.nextDouble(); + BatchId batchId = new BatchId(bId++); + materialsBuilder.addBatch(batchId, testMaterialId, amount); + + batches.add(batchId); + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId + .getTestBatchPropertyIds(testMaterialId)) { + boolean required = testBatchPropertyId.getPropertyDefinition().getDefaultValue().isEmpty(); + if (required || randomGenerator.nextBoolean()) { + materialsBuilder.setBatchPropertyValue(batchId, testBatchPropertyId, + testBatchPropertyId.getRandomPropertyValue(randomGenerator)); + } + } + + } + + List stages = new ArrayList<>(); + + for (int i = 0; i < numStages; i++) { + StageId stageId = new StageId(sId++); + stages.add(stageId); + boolean offered = i % 2 == 0; + materialsBuilder.addStage(stageId, offered); + materialsBuilder.addStageToMaterialProducer(stageId, testMaterialsProducerId); + } + + Collections.shuffle(batches, new Random(randomGenerator.nextLong())); + for (int i = 0; i < numBatches; i++) { + BatchId batchId = batches.get(i); + if (i < numBatchesInStage) { + StageId stageId = stages.get(randomGenerator.nextInt(stages.size())); + materialsBuilder.addBatchToStage(stageId, batchId); + } else { + materialsBuilder.addBatchToMaterialsProducerInventory(batchId, testMaterialsProducerId); + } + + } + materialsBuilder.addMaterialsProducerId(testMaterialsProducerId); + + for (ResourceId resourceId : TestResourceId.values()) { + if (randomGenerator.nextBoolean()) { + materialsBuilder.setMaterialsProducerResourceLevel(testMaterialsProducerId, resourceId, + randomGenerator.nextInt(10)); + } else { + materialsBuilder.setMaterialsProducerResourceLevel(testMaterialsProducerId, resourceId, 0); + } + } + } + + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .values()) { + materialsBuilder.defineMaterialsProducerProperty(testMaterialsProducerPropertyId, + testMaterialsProducerPropertyId.getPropertyDefinition()); + } + + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .getPropertiesWithoutDefaultValues()) { + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + Object randomPropertyValue = testMaterialsProducerPropertyId.getRandomPropertyValue(randomGenerator); + materialsBuilder.setMaterialsProducerPropertyValue(testMaterialsProducerId, + testMaterialsProducerPropertyId, randomPropertyValue); + } + } + + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + Set testBatchPropertyIds = TestBatchPropertyId.getTestBatchPropertyIds(testMaterialId); + for (TestBatchPropertyId testBatchPropertyId : testBatchPropertyIds) { + materialsBuilder.defineBatchProperty(testMaterialId, testBatchPropertyId, + testBatchPropertyId.getPropertyDefinition()); + } + } + return materialsBuilder.build(); + } + + /** + * Returns a standardized ResourcesPluginData that is minimally adequate for + * testing the MaterialsPlugin The resulting ResourcesPluginData will include: + *
      + *
    • Every ResourceId included in {@link TestResourceId} along with the + * defined timeTrackingPolicy for each
    • + *
    • Every ResourcePropertyId included in {@link TestResourcePropertyId} along + * with the defined propertyDefinition for each. + *
        + *
      • Each Resource will have a random property value assigned based on a + * RandomGenerator that is created with the passed in seed
      • + *
      + *
    + */ + public static ResourcesPluginData getStandardResourcesPluginData(long seed) { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + ResourcesPluginData.Builder resourcesBuilder = ResourcesPluginData.builder(); + + for (TestResourceId testResourceId : TestResourceId.values()) { + resourcesBuilder.addResource(testResourceId, 0.0, testResourceId.getTimeTrackingPolicy()); + } + + for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId.values()) { + TestResourceId testResourceId = testResourcePropertyId.getTestResourceId(); + PropertyDefinition propertyDefinition = testResourcePropertyId.getPropertyDefinition(); + Object propertyValue = testResourcePropertyId.getRandomPropertyValue(randomGenerator); + resourcesBuilder.defineResourceProperty(testResourceId, testResourcePropertyId, propertyDefinition); + resourcesBuilder.setResourcePropertyValue(testResourceId, testResourcePropertyId, propertyValue); + } + + return resourcesBuilder.build(); + } + + /** + * Returns a standardized PeoplePluginData that is minimally adequate for + * testing the MaterialsPlugin The resulting PeoplePluginData will be empty + *
      + *
    • the equivalent of PeoplePluginData.builder().build() + *
    + */ + public static PeoplePluginData getStandardPeoplePluginData() { + PeoplePluginData.Builder peopleBuilder = PeoplePluginData.builder(); + return peopleBuilder.build(); + } + + /** + * Returns a standardized RegionsPluginData that is minimally adequate for + * testing the MaterialsPlugin The resulting RegionsPluginData will include: + *
      + *
    • Every RegionId included in {@link TestRegionId}
    • + *
    + */ + public static RegionsPluginData getStandardRegionsPluginData() { + RegionsPluginData.Builder regionsBuilder = RegionsPluginData.builder(); + for (TestRegionId testRegionId : TestRegionId.values()) { + regionsBuilder.addRegion(testRegionId); + } + return regionsBuilder.build(); + } + + /** + * Returns a standardized StochasticsPluginData that is minimally adequate for + * testing the MaterialsPlugin The resulting StochasticsPluginData will include: + *
      + *
    • a seed based on the nextLong of a RandomGenerator seeded from the passed + * in seed
    • + *
    + */ + public static StochasticsPluginData getStandardStochasticsPluginData(long seed) { + return StochasticsPluginData.builder().setMainRNGState(WellState.builder().setSeed(seed).build()).build(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/testsupport/TestBatchConstructionInfo.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/testsupport/TestBatchConstructionInfo.java new file mode 100644 index 000000000..1c814e9a9 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/testsupport/TestBatchConstructionInfo.java @@ -0,0 +1,31 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.testsupport; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchConstructionInfo; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerId; + +public class TestBatchConstructionInfo { + + private TestBatchConstructionInfo() { + } + + public static BatchConstructionInfo getBatchConstructionInfo(MaterialsProducerId materialsProducerId, + MaterialId materialId, double amount, RandomGenerator randomGenerator) { + BatchConstructionInfo.Builder builder = // + BatchConstructionInfo.builder()// + .setMaterialId(materialId)// + .setMaterialsProducerId(materialsProducerId)// + .setAmount(amount);// + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.values()) { + if (testBatchPropertyId.getTestMaterialId().equals(materialId)) { + if (testBatchPropertyId.getPropertyDefinition().getDefaultValue().isEmpty()) { + Object value = testBatchPropertyId.getRandomPropertyValue(randomGenerator); + builder.setPropertyValue(testBatchPropertyId, value); + } + } + } + return builder.build(); + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/testsupport/TestBatchPropertyId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/testsupport/TestBatchPropertyId.java new file mode 100644 index 000000000..4e71af142 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/testsupport/TestBatchPropertyId.java @@ -0,0 +1,193 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.testsupport; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; + +/** + * A test support enumeration that contains a variety of batch property id + * values with corresponding property definitions and associations to Test + * Material Ids. Supports random selection, random property value generation and + * generation of unique, unknown batch property ids. + */ +public enum TestBatchPropertyId implements BatchPropertyId { + + BATCH_PROPERTY_1_1_BOOLEAN_IMMUTABLE_NO_TRACK(TestMaterialId.MATERIAL_1, // + PropertyDefinition.builder()// + .setType(Boolean.class)// + .setDefaultValue(false)// + .setPropertyValueMutability(false)// + .build()), // + BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK(TestMaterialId.MATERIAL_1, // + PropertyDefinition.builder()// + .setType(Integer.class)// + // .setDefaultValue(0)//no default value + .setPropertyValueMutability(true)// + .build() // + ), // + BATCH_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK(TestMaterialId.MATERIAL_1, // + PropertyDefinition.builder()// + .setType(Double.class)// + // .setDefaultValue(0.0)//no default + .setPropertyValueMutability(true)// + .build() // + ), // + BATCH_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK(TestMaterialId.MATERIAL_2, // + PropertyDefinition.builder()// + .setType(Boolean.class)// + .setDefaultValue(false)// + .setPropertyValueMutability(true)// + .build() // + ), // + BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK(TestMaterialId.MATERIAL_2, // + PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(0)// + .setPropertyValueMutability(false)// + .build() // + ), // + BATCH_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK(TestMaterialId.MATERIAL_2, // + PropertyDefinition.builder()// + .setType(Double.class)// + .setDefaultValue(0.0)// + .setPropertyValueMutability(true)// + .build() // + ), // + BATCH_PROPERTY_3_1_BOOLEAN_MUTABLE_NO_TRACK(TestMaterialId.MATERIAL_3, // + PropertyDefinition.builder()// + .setType(Boolean.class)// + .setDefaultValue(false)// + .setPropertyValueMutability(true)// + .build() // + ), // + BATCH_PROPERTY_3_2_INTEGER_MUTABLE_NO_TRACK(TestMaterialId.MATERIAL_3, // + PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(0)// + .setPropertyValueMutability(true)// + .build() // + ), // + BATCH_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK(TestMaterialId.MATERIAL_3, // + PropertyDefinition.builder()// + .setType(Double.class)// + .setDefaultValue(0.0)// + .setPropertyValueMutability(false)// + .build() // + );// + + private final PropertyDefinition propertyDefinition; + private final TestMaterialId testMaterialId; + + /** + * Returns the property definition associated with this member + */ + public PropertyDefinition getPropertyDefinition() { + return propertyDefinition; + } + + private TestBatchPropertyId(TestMaterialId testMaterialId, PropertyDefinition propertyDefinition) { + this.testMaterialId = testMaterialId; + this.propertyDefinition = propertyDefinition; + } + + /** + * Returns the TestMaterialId that should be associated with the property + */ + public TestMaterialId getTestMaterialId() { + return testMaterialId; + } + + /** + * Returns the TestBatchPropertyIds associated with the given TestMaterialId + * Preconditions: The TestMaterialId should not be null + */ + public static Set getTestBatchPropertyIds(TestMaterialId testMaterialId) { + Set result = new LinkedHashSet<>(); + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.values()) { + if (testBatchPropertyId.testMaterialId == testMaterialId) { + result.add(testBatchPropertyId); + } + } + return result; + } + + /** + * Returns a unique BatchPropertyId instance that is not a member of this + * enumeration + */ + public static BatchPropertyId getUnknownBatchPropertyId() { + return new BatchPropertyId() { + }; + + } + + /** + * Returns a TestBatchPropertyId that is associated with the given material id + * and whose associated property definition is marked as mutable. + */ + public static TestBatchPropertyId getRandomMutableBatchPropertyId(final TestMaterialId testMaterialId, + final RandomGenerator randomGenerator) { + Set set = getTestBatchPropertyIds(testMaterialId); + int count = 1; + TestBatchPropertyId selectedTestBatchPropertyId = null; + for (TestBatchPropertyId testBatchPropertyId : set) { + if (testBatchPropertyId.propertyDefinition.propertyValuesAreMutable()) { + if (randomGenerator.nextDouble() < (1.0 / count)) { + selectedTestBatchPropertyId = testBatchPropertyId; + } + } + } + return selectedTestBatchPropertyId; + + } + + /** + * Returns a randomly selected value that is compatible with this member's + * associated property definition. + */ + public Object getRandomPropertyValue(final RandomGenerator randomGenerator) { + switch (this) { + case BATCH_PROPERTY_1_1_BOOLEAN_IMMUTABLE_NO_TRACK: + return randomGenerator.nextBoolean(); + case BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK: + return randomGenerator.nextInt(); + case BATCH_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK: + return randomGenerator.nextDouble(); + case BATCH_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK: + return randomGenerator.nextBoolean(); + case BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK: + return randomGenerator.nextInt(); + case BATCH_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK: + return randomGenerator.nextDouble(); + case BATCH_PROPERTY_3_1_BOOLEAN_MUTABLE_NO_TRACK: + return randomGenerator.nextBoolean(); + case BATCH_PROPERTY_3_2_INTEGER_MUTABLE_NO_TRACK: + return randomGenerator.nextInt(); + case BATCH_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK: + return randomGenerator.nextDouble(); + default: + + throw new RuntimeException("unhandled case: " + this); + + } + } + + public static List getBatchPropertyIds() { + return Arrays.asList(TestBatchPropertyId.values()); + } + + public static List getBatchPropertyIds(RandomGenerator randomGenerator) { + List result = getBatchPropertyIds(); + Random random = new Random(randomGenerator.nextLong()); + Collections.shuffle(result, random); + return result; + } +} diff --git a/gcm3/src/main/java/plugins/materials/testsupport/TestMaterialId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/testsupport/TestMaterialId.java similarity index 88% rename from gcm3/src/main/java/plugins/materials/testsupport/TestMaterialId.java rename to gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/testsupport/TestMaterialId.java index f57982264..2f6b8133a 100644 --- a/gcm3/src/main/java/plugins/materials/testsupport/TestMaterialId.java +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/testsupport/TestMaterialId.java @@ -1,15 +1,12 @@ -package plugins.materials.testsupport; +package gov.hhs.aspr.ms.gcm.plugins.materials.testsupport; import org.apache.commons.math3.random.RandomGenerator; -import plugins.materials.support.MaterialId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialId; /** * A test support enumeration that contains a variety of material id values. * Supports random selection and generation of unique, unknown material ids. - * - * @author Shawn Hatch - * */ public enum TestMaterialId implements MaterialId { MATERIAL_1, MATERIAL_2, MATERIAL_3; @@ -31,7 +28,8 @@ public static int size() { private TestMaterialId next; /** - * Returns the next member of this enumeration in the natural order with roll over. + * Returns the next member of this enumeration in the natural order with roll + * over. */ public TestMaterialId next() { if (next == null) { diff --git a/gcm3/src/main/java/plugins/materials/testsupport/TestMaterialsProducerId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/testsupport/TestMaterialsProducerId.java similarity index 81% rename from gcm3/src/main/java/plugins/materials/testsupport/TestMaterialsProducerId.java rename to gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/testsupport/TestMaterialsProducerId.java index e588a10a0..20f354b8f 100644 --- a/gcm3/src/main/java/plugins/materials/testsupport/TestMaterialsProducerId.java +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/testsupport/TestMaterialsProducerId.java @@ -1,15 +1,13 @@ -package plugins.materials.testsupport; +package gov.hhs.aspr.ms.gcm.plugins.materials.testsupport; import org.apache.commons.math3.random.RandomGenerator; -import plugins.materials.support.MaterialsProducerId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerId; /** - * A test support enumeration that contains a variety of materials producer id values. - * Supports random selection and generation of unique, unknown material producer ids. - * - * @author Shawn Hatch - * + * A test support enumeration that contains a variety of materials producer id + * values. Supports random selection and generation of unique, unknown material + * producer ids. */ public enum TestMaterialsProducerId implements MaterialsProducerId { MATERIALS_PRODUCER_1, MATERIALS_PRODUCER_2, MATERIALS_PRODUCER_3; diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/testsupport/TestMaterialsProducerPropertyId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/testsupport/TestMaterialsProducerPropertyId.java new file mode 100644 index 000000000..dd2f5dd8f --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/materials/testsupport/TestMaterialsProducerPropertyId.java @@ -0,0 +1,201 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.testsupport; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; + +/** + * A test support enumeration that contains a variety of materials producer + * property id values with corresponding property definitions. Supports random + * selection, random property value generation and generation of unique, unknown + * batch property ids. + */ +public enum TestMaterialsProducerPropertyId implements MaterialsProducerPropertyId { + + MATERIALS_PRODUCER_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK( + + PropertyDefinition.builder()// + .setType(Boolean.class)// + .setDefaultValue(false)// + .setPropertyValueMutability(true)// + .build()), // + MATERIALS_PRODUCER_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK( + + PropertyDefinition.builder()// + .setType(Integer.class)// + // .setDefaultValue(0)//no default value + .setPropertyValueMutability(true)// + .build() // + ), // + MATERIALS_PRODUCER_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK( + + PropertyDefinition.builder()// + .setType(Double.class)// + .setDefaultValue(0.0)// + .setPropertyValueMutability(true)// + .build() // + ), // + MATERIALS_PRODUCER_PROPERTY_4_BOOLEAN_MUTABLE_TRACK( + + PropertyDefinition.builder()// + .setType(Boolean.class)// + .setDefaultValue(false)// + .setPropertyValueMutability(true)// + .build() // + ), // + MATERIALS_PRODUCER_PROPERTY_5_INTEGER_MUTABLE_TRACK( + + PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(0)// + .setPropertyValueMutability(true)// + .build() // + ), // + MATERIALS_PRODUCER_PROPERTY_6_DOUBLE_MUTABLE_TRACK( + + PropertyDefinition.builder()// + .setType(Double.class)// + .setDefaultValue(0.0)// + .setPropertyValueMutability(true)// + .build() // + ), // + MATERIALS_PRODUCER_PROPERTY_7_BOOLEAN_IMMUTABLE_NO_TRACK( + + PropertyDefinition.builder()// + .setType(Boolean.class)// + .setDefaultValue(false)// + .setPropertyValueMutability(false)// + .build() // + ), // + MATERIALS_PRODUCER_PROPERTY_8_INTEGER_IMMUTABLE_NO_TRACK( + + PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(0)// + .setPropertyValueMutability(false)// + .build() // + ), // + MATERIALS_PRODUCER_PROPERTY_9_DOUBLE_IMMUTABLE_NO_TRACK( + + PropertyDefinition.builder()// + .setType(Double.class)// + .setDefaultValue(0.0)// + .setPropertyValueMutability(false)// + .build() // + );// + + private final PropertyDefinition propertyDefinition; + + /** + * Returns the property definition associated with this member. The property + * definition will contain a default value. + */ + public PropertyDefinition getPropertyDefinition() { + return propertyDefinition; + } + + private TestMaterialsProducerPropertyId(PropertyDefinition propertyDefinition) { + this.propertyDefinition = propertyDefinition; + } + + /** + * Returns a unique MaterialsProducerPropertyId instance that is not a member of + * this enumeration + */ + public static MaterialsProducerPropertyId getUnknownMaterialsProducerPropertyId() { + return new MaterialsProducerPropertyId() { + }; + } + + /** + * Returns a randomly selected value that is compatible with this member's + * associated property definition. + */ + public Object getRandomPropertyValue(final RandomGenerator randomGenerator) { + switch (this) { + case MATERIALS_PRODUCER_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK: + return randomGenerator.nextBoolean(); + case MATERIALS_PRODUCER_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK: + return randomGenerator.nextInt(); + case MATERIALS_PRODUCER_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK: + return randomGenerator.nextDouble(); + case MATERIALS_PRODUCER_PROPERTY_4_BOOLEAN_MUTABLE_TRACK: + return randomGenerator.nextBoolean(); + case MATERIALS_PRODUCER_PROPERTY_5_INTEGER_MUTABLE_TRACK: + return randomGenerator.nextInt(); + case MATERIALS_PRODUCER_PROPERTY_6_DOUBLE_MUTABLE_TRACK: + return randomGenerator.nextDouble(); + case MATERIALS_PRODUCER_PROPERTY_7_BOOLEAN_IMMUTABLE_NO_TRACK: + return randomGenerator.nextBoolean(); + case MATERIALS_PRODUCER_PROPERTY_8_INTEGER_IMMUTABLE_NO_TRACK: + return randomGenerator.nextInt(); + case MATERIALS_PRODUCER_PROPERTY_9_DOUBLE_IMMUTABLE_NO_TRACK: + return randomGenerator.nextDouble(); + default: + + throw new RuntimeException("unhandled case: " + this); + + } + } + + /** + * Returns a randomly selected member of this enumeration. + */ + public static TestMaterialsProducerPropertyId getRandomMaterialsProducerPropertyId( + final RandomGenerator randomGenerator) { + return TestMaterialsProducerPropertyId.values()[randomGenerator + .nextInt(TestMaterialsProducerPropertyId.values().length)]; + } + + /** + * Returns a randomly selected member of this enumeration whose associated + * property definition is marked as mutable. + */ + public static TestMaterialsProducerPropertyId getRandomMutableMaterialsProducerPropertyId( + final RandomGenerator randomGenerator) { + return TestMaterialsProducerPropertyId.values()[randomGenerator.nextInt(6)]; + } + + /** + * Returns the number of members in this enumeration. + */ + public static int size() { + return TestMaterialsProducerPropertyId.values().length; + } + + /** + * Returns the test property ids associated with a default value + */ + public static List getPropertiesWithDefaultValues() { + List result = new ArrayList<>(); + + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .values()) { + if (testMaterialsProducerPropertyId.getPropertyDefinition().getDefaultValue().isPresent()) { + result.add(testMaterialsProducerPropertyId); + } + } + + return result; + } + + /** + * Returns the test property ids associated without a default value + */ + public static List getPropertiesWithoutDefaultValues() { + List result = new ArrayList<>(); + + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .values()) { + if (testMaterialsProducerPropertyId.getPropertyDefinition().getDefaultValue().isEmpty()) { + result.add(testMaterialsProducerPropertyId); + } + } + + return result; + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/PartitionsPlugin.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/PartitionsPlugin.java new file mode 100644 index 000000000..f2b726c92 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/PartitionsPlugin.java @@ -0,0 +1,109 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions; + +import java.util.LinkedHashSet; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.plugins.partitions.datamanagers.PartitionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.partitions.datamanagers.PartitionsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionError; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePluginId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPluginId; +import util.errors.ContractException; + +/** + * A nucleus plugin for the management of population partitions. A population + * partition represents a filtered and partitioned subset of the people in the + * simulation. + */ +public final class PartitionsPlugin { + + private static class Data { + private Set pluginDependencies = new LinkedHashSet<>(); + private PartitionsPluginData partitionsPluginData; + } + + private PartitionsPlugin() { + } + + public static Builder builder() { + return new Builder(); + } + + /** + * Returns the partitions plugin. + *

    + * Uses PartitionsPluginId.PLUGIN_ID as its id + *

    + *

    + * Depends on plugins: + *

      + *
    • Stochastics Plugin
    • + *
    • People Plugin
    • + *
    + *

    + * Provides data mangers: {@linkplain PartitionsDataManager} + *

    + *

    + * Provides no actors: + *

    + */ + public static class Builder { + private Builder() { + } + + private Data data = new Data(); + + private void validate() { + if (data.partitionsPluginData == null) { + throw new ContractException(PartitionError.NULL_PARTITION_PLUGIN_DATA); + } + } + + /** + * Builds the PartitionsPlugin from the collected inputs + * + * @throws ContractException + *
      + *
    • {@linkplain PartitionError#NULL_PARTITION_PLUGIN_DATA} + * if the partitionsPluginData is null
    • + *
    • {@linkplain NucleusError#NULL_PLUGIN_ID} if an + * included plugin dependency id null
    • + *
    + */ + public Plugin getPartitionsPlugin() { + + validate(); + Plugin.Builder builder = Plugin.builder();// + builder.setPluginId(PartitionsPluginId.PLUGIN_ID);// + builder.addPluginData(data.partitionsPluginData);// + builder.addPluginDependency(PeoplePluginId.PLUGIN_ID);// + builder.addPluginDependency(StochasticsPluginId.PLUGIN_ID);// + for (PluginId pluginId : data.pluginDependencies) { + builder.addPluginDependency(pluginId);// + } + + builder.setInitializer((c) -> { + PartitionsPluginData partitionsPluginData = c.getPluginData(PartitionsPluginData.class).get(); + + c.addDataManager(new PartitionsDataManager(partitionsPluginData)); + });// + + return builder.build(); + } + + public Builder setPartitionsPluginData(PartitionsPluginData partitionsPluginData) { + data.partitionsPluginData = partitionsPluginData; + return this; + } + + public Builder addPluginDependency(PluginId pluginId) { + data.pluginDependencies.add(pluginId); + return this; + } + + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/PartitionsPluginId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/PartitionsPluginId.java new file mode 100644 index 000000000..3fae44ad4 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/PartitionsPluginId.java @@ -0,0 +1,13 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; + +/** + * Static plugin id implementation for the GlobalsPlugin + */ +public final class PartitionsPluginId implements PluginId { + public final static PluginId PLUGIN_ID = new PartitionsPluginId(); + + private PartitionsPluginId() { + }; +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/datamanagers/PartitionsDataManager.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/datamanagers/PartitionsDataManager.java new file mode 100644 index 000000000..8968e68ac --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/datamanagers/PartitionsDataManager.java @@ -0,0 +1,612 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.datamanagers; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.nucleus.DataManager; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.DegeneratePopulationPartitionImpl; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.FilterSensitivity; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.LabelSet; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.LabelSetFunction; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.Labeler; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.LabelerSensitivity; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.Partition; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionSampler; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PopulationPartition; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PopulationPartitionImpl; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters.Filter; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters.TrueFilter; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.events.PersonAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.events.PersonRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.StochasticsError; +import util.errors.ContractException; + +/** + * Mutable data manager for managing population partitions that are maintained + * as data changes are made to people. + *

    + * Subscribes to the following events for all partitions: + *

    + *
      + *
    • {@linkplain PersonAdditionEvent}
      Adds the person to all + * relevant population partitions after event validation and execution phases + * are complete.
    • + *
    • {@linkplain PersonRemovalEvent}
      Removes the person from all + * population partitions by scheduling the removal for the current time. This + * allows references and partition memberships to remain long enough for + * resolvers, agents and reports to have final reference to the person while + * still associated with any relevant partitions.
    • + *
    + *

    + * Subscribes to other events as needed to support the population partition + * maintenance + *

    + */ +public final class PartitionsDataManager extends DataManager { + + private final boolean supportRunContinuity; + + public PartitionsDataManager(PartitionsPluginData partitionsPluginData) { + this.supportRunContinuity = partitionsPluginData.supportsRunContinuity(); + } + + private final Map>> keyToEventClassesMap = new LinkedHashMap<>(); + + private final Set> reservedEventClasses = new LinkedHashSet<>(); + + private final Map, Set> eventClassToKeyMap = new LinkedHashMap<>(); + + private DataManagerContext dataManagerContext; + + private PartitionsContext partitionsContext; + + private PeopleDataManager peopleDataManager; + + private final Map keyToPopulationPartitionMap = new LinkedHashMap<>(); + private final Set safePartitionKeys = Collections.unmodifiableSet(keyToPopulationPartitionMap.keySet()); + + /* + * Returns the set of keys for population partitions + */ + private Set getKeys() { + return safePartitionKeys; + } + + /** + * Returns the list of person identifiers in the population partition for the + * given key. + * + * @throws ContractException + *
      + *
    • {@link PartitionError#NULL_PARTITION_KEY} if + * the key is null
    • + *
    • {@link PartitionError#UNKNOWN_POPULATION_PARTITION_KEY} + * if the key is unknown
    • + *
    + */ + public List getPeople(final Object key) { + validateKeyExists(key); + return getPopulationPartition(key).getPeople(); + } + + /** + * Returns the list of person identifiers in the population partition for the + * given key. A person is included if every label in the label set is equal to + * the corresponding label for the person. + * + * @throws ContractException + *
      + *
    • {@link PartitionError#NULL_PARTITION_KEY} if + * the key is null
    • + *
    • {@link PartitionError#UNKNOWN_POPULATION_PARTITION_KEY} + * if the key is unknown
    • + *
    • {@link PartitionError#NULL_LABEL_SET} if the + * label set is null
    • + *
    • {@link PartitionError#INCOMPATIBLE_LABEL_SET} + * if the label set contains dimensions not contained + * in the population partition
    • + *
    + */ + public List getPeople(final Object key, LabelSet labelSet) { + validateKeyExists(key); + PopulationPartition populationPartition = getPopulationPartition(key); + validateLabelSet(populationPartition, labelSet); + return populationPartition.getPeople(labelSet); + } + + /** + * Returns the number of people in the population partition for the given key. + * + * @throws ContractException + *
      + *
    • {@link PartitionError#NULL_PARTITION_KEY} if + * the key is null
    • + *
    • {@link PartitionError#UNKNOWN_POPULATION_PARTITION_KEY} + * if the key is unknown
    • + *
    + */ + public int getPersonCount(final Object key) { + validateKeyExists(key); + PopulationPartition populationPartition = getPopulationPartition(key); + return populationPartition.getPeopleCount(); + } + + /** + * Returns the number of people in the population partition for the given key + * under the given label set. A person is counted if every label in the label + * set is equal to the corresponding label for the person. + * + * @throws ContractException + *
      + *
    • {@link PartitionError#NULL_PARTITION_KEY} if + * the key is null
    • + *
    • {@link PartitionError#UNKNOWN_POPULATION_PARTITION_KEY} + * if the key is unknown
    • + *
    • {@link PartitionError#NULL_LABEL_SET} if the + * label set is null
    • + *
    • {@link PartitionError#INCOMPATIBLE_LABEL_SET} + * if the label set contains dimensions not contained + * in the population partition
    • + *
    + */ + public int getPersonCount(final Object key, LabelSet labelSet) { + validateKeyExists(key); + PopulationPartition populationPartition = getPopulationPartition(key); + validateLabelSet(populationPartition, labelSet); + return populationPartition.getPeopleCount(labelSet); + } + + /** + * Returns a mapping from LabelSet to Integer whose keys are all the label sets + * in the population partition that match the given label set. The values are + * the number of people matching that label set. A person is counted if every + * label in the label set is equal to the corresponding label for the person. + * All values will be positive. + * + * @throws ContractException + *
      + *
    • {@link PartitionError#NULL_PARTITION_KEY} if + * the key is null
    • + *
    • {@link PartitionError#UNKNOWN_POPULATION_PARTITION_KEY} + * if the key is unknown
    • + *
    • {@link PartitionError#NULL_LABEL_SET} if the + * label set is null
    • + *
    • {@link PartitionError#INCOMPATIBLE_LABEL_SET} + * if the label set contains dimensions not contained + * in the population partition
    • + *
    + */ + public Map getPeopleCountMap(final Object key, LabelSet labelSet) { + validateKeyExists(key); + PopulationPartition populationPartition = getPopulationPartition(key); + validateLabelSet(populationPartition, labelSet); + return populationPartition.getPeopleCountMap(labelSet); + } + + /** + * Returns a randomly selected person from the given population partition using + * the given PartitionSampler. + * + * @throws ContractException + *
      + *
    • {@link PartitionError#NULL_PARTITION_KEY} if + * the key is null
    • + *
    • {@link PartitionError#UNKNOWN_POPULATION_PARTITION_KEY} + * if the key is unknown
    • + *
    • {@link PartitionError#NULL_PARTITION_SAMPLER} + * if the partition sampler is null
    • + *
    • {@link PartitionError#INCOMPATIBLE_LABEL_SET} + * if the partition sampler has a label set containing + * dimensions not present in the population + * partition
    • + *
    • {@link PersonError#UNKNOWN_PERSON_ID} if the + * partition sampler has an excluded person that does + * not exist
    • + *
    • {@link StochasticsError#UNKNOWN_RANDOM_NUMBER_GENERATOR_ID} + * if the partition sampler has a random number + * generator id that is unknown
    • + *
    + */ + public Optional samplePartition(Object key, PartitionSampler partitionSampler) { + validateKeyExists(key); + PopulationPartition populationPartition = getPopulationPartition(key); + validatePartitionSampler(populationPartition, partitionSampler); + return populationPartition.samplePartition(partitionSampler); + } + + /** + * Returns an optional value by applying the given function to the label set + * associated with the person. If the person is not contained in the population + * partition the method returns an empty optional. Note that the + * labelSetFunction must be consistent with the partition definition used to + * create this population partition. No precondition tests will be performed. + * + * @throws ContractException + *
      + *
    • {@link PartitionError#NULL_PARTITION_KEY} if + * the key is null
    • + *
    • {@link PartitionError#UNKNOWN_POPULATION_PARTITION_KEY} + * if the key is unknown
    • + *
    • {@link PersonError#UNKNOWN_PERSON_ID} if the + * person does not exist
    • + *
    • {@link PartitionError#NULL_LABEL_SET_FUNCTION} + * if the label set function is null
    • + *
    + */ + public Optional getPersonValue(Object key, LabelSetFunction labelSetFunction, PersonId personId) { + validateKeyExists(key); + validatePersonExists(personId); + validateLabelSetFunction(labelSetFunction); + PopulationPartition populationPartition = getPopulationPartition(key); + return populationPartition.getPersonValue(labelSetFunction, personId); + } + + /** + * Returns true if and only if the person is contained in the population + * partition corresponding to the key. + * + * @throws ContractException + *
      + *
    • {@link PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@link PersonError#UNKNOWN_PERSON_ID} if the + * person id is null
    • + *
    • {@link PartitionError#NULL_PARTITION_KEY} if + * the key is null
    • + *
    • {@link PartitionError#UNKNOWN_POPULATION_PARTITION_KEY} + * if the key is unknown
    • + *
    + */ + public boolean contains(final PersonId personId, Object key) { + validatePersonNotNull(personId); + validatePersonExists(personId); + validateKeyExists(key); + PopulationPartition populationPartition = getPopulationPartition(key); + return populationPartition.contains(personId); + } + + /** + * Returns true if and only if the person is contained in the population + * corresponding to the key. A person is counted if every label in the label set + * is equal to the corresponding label for the person. + * + * @throws ContractException + *
      + *
    • {@link PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@link PersonError#UNKNOWN_PERSON_ID} if the + * person id is null
    • + *
    • {@link PartitionError#NULL_PARTITION_KEY} if + * the key is null
    • + *
    • {@link PartitionError#UNKNOWN_POPULATION_PARTITION_KEY} + * if the key is unknown
    • + *
    • {@link PartitionError#NULL_LABEL_SET} if the + * label set is null
    • + *
    • {@link PartitionError#INCOMPATIBLE_LABEL_SET} + * if the label contains a dimension not present in + * the partition
    • + *
    + */ + public boolean contains(PersonId personId, LabelSet labelSet, Object key) { + validatePersonNotNull(personId); + validatePersonExists(personId); + validateKeyExists(key); + PopulationPartition populationPartition = getPopulationPartition(key); + validateLabelSet(populationPartition, labelSet); + + return populationPartition.contains(personId, labelSet); + } + + /** + * Returns true if and only if a partition is associated with the given key. + * Null tolerant. + */ + public boolean partitionExists(final Object key) { + return keyToPopulationPartitionMap.containsKey(key); + } + + private void validateLabelSet(final PopulationPartition populationPartition, final LabelSet labelSet) { + if (labelSet == null) { + throw new ContractException(PartitionError.NULL_LABEL_SET); + } + + if (!populationPartition.validateLabelSetInfo(labelSet)) { + throw new ContractException(PartitionError.INCOMPATIBLE_LABEL_SET); + } + } + + private void validatePartitionSampler(PopulationPartition populationPartition, + final PartitionSampler partitionSampler) { + if (partitionSampler == null) { + throw new ContractException(PartitionError.NULL_PARTITION_SAMPLER); + } + + if (partitionSampler.getLabelSet().isPresent()) { + final LabelSet labelSet = partitionSampler.getLabelSet().get(); + if (!populationPartition.validateLabelSetInfo(labelSet)) { + throw new ContractException(PartitionError.INCOMPATIBLE_LABEL_SET); + } + } + + if (partitionSampler.getExcludedPerson().isPresent()) { + final PersonId excludedPersonId = partitionSampler.getExcludedPerson().get(); + validatePersonExists(excludedPersonId); + } + + } + + /* + * Precondition : person id is not null + */ + private void validatePersonExists(final PersonId personId) { + if (!peopleDataManager.personExists(personId)) { + throw new ContractException(PersonError.UNKNOWN_PERSON_ID); + } + } + + private void validateLabelSetFunction(LabelSetFunction labelSetFunction) { + if (labelSetFunction == null) { + throw new ContractException(PartitionError.NULL_LABEL_SET_FUNCTION); + } + } + + private void validatePersonNotNull(final PersonId personId) { + if (personId == null) { + throw new ContractException(PersonError.NULL_PERSON_ID); + } + } + + private void validateKeyExists(final Object key) { + if (key == null) { + throw new ContractException(PartitionError.NULL_PARTITION_KEY); + } + + if (!keyToPopulationPartitionMap.containsKey(key)) { + throw new ContractException(PartitionError.UNKNOWN_POPULATION_PARTITION_KEY, key); + } + } + + @Override + public void init(DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + this.dataManagerContext = dataManagerContext; + + this.partitionsContext = new PartitionsContextImpl(dataManagerContext); + + peopleDataManager = dataManagerContext.getDataManager(PeopleDataManager.class); + + dataManagerContext.subscribe(PersonAdditionEvent.class, this::handlePersonAdditionEvent); + + dataManagerContext.subscribe(PersonRemovalEvent.class, this::handlePersonRemovalEvent); + + reservedEventClasses.add(PersonAdditionEvent.class); + reservedEventClasses.add(PersonRemovalEvent.class); + + if (dataManagerContext.stateRecordingIsScheduled()) { + dataManagerContext.subscribeToSimulationClose(this::recordSimulationState); + } + + } + + private void recordSimulationState(DataManagerContext dataManagerContext) { + PartitionsPluginData partitionsPluginData = PartitionsPluginData.builder() + .setRunContinuitySupport(supportRunContinuity).build(); + dataManagerContext.releaseOutput(partitionsPluginData); + } + + /* + * Returns the population partition associated with the given key. Returns null + * if the key is not recognized. + */ + private PopulationPartition getPopulationPartition(final Object key) { + return keyToPopulationPartitionMap.get(key); + } + + private void validatePopulationPartitionDoesNotExist(final Object key) { + if (keyToPopulationPartitionMap.containsKey(key)) { + throw new ContractException(PartitionError.DUPLICATE_PARTITION, key); + } + } + + /* + * Precondition : the key is not null + */ + private void validatePopulationPartitionExists(final Object key) { + if (!keyToPopulationPartitionMap.containsKey(key)) { + throw new ContractException(PartitionError.UNKNOWN_POPULATION_PARTITION_KEY, key); + } + } + + private void validatePopulationPartitionKeyNotNull(final Object key) { + if (key == null) { + throw new ContractException(PartitionError.NULL_PARTITION_KEY); + } + } + + private void validatePopulationPartitionNotNull(final Partition partition) { + if (partition == null) { + throw new ContractException(PartitionError.NULL_PARTITION); + } + } + + private void handlePersonAdditionEvent(final DataManagerContext dataManagerContext, + final PersonAdditionEvent personAdditionEvent) { + final PersonId personId = personAdditionEvent.personId(); + for (final Object key : getKeys()) { + final PopulationPartition populationPartition = getPopulationPartition(key); + populationPartition.attemptPersonAddition(personId); + } + } + + private void handlePersonRemovalEvent(final DataManagerContext dataManagerContext, + final PersonRemovalEvent personRemovalEvent) { + + for (final Object key : getKeys()) { + final PopulationPartition populationPartition = getPopulationPartition(key); + populationPartition.attemptPersonRemoval(personRemovalEvent.personId()); + } + + } + + /** + * Adds a population partition for the given key and component id. The key must + * not duplicate an existing key. + * + * @throws ContractException + *
      + *
    • {@linkplain PartitionError#NULL_PARTITION} if + * the partition is null
    • + *
    • {@linkplain PartitionError#NULL_PARTITION_KEY} + * if the key is null
    • + *
    • {@linkplain PartitionError#DUPLICATE_PARTITION} + * if a partition is currently associated with the + * key
    • + *
    + */ + public void addPartition(final Partition partition, final Object key) { + + validatePopulationPartitionNotNull(partition); + validatePopulationPartitionKeyNotNull(key); + validatePopulationPartitionDoesNotExist(key); + + /* + * determine the event classes that will trigger refreshes on the partition + */ + final Set> eventClasses = new LinkedHashSet<>(); + + final Filter filter = partition.getFilter().orElse(new TrueFilter()); + filter.validate(partitionsContext); + for (final FilterSensitivity filterSensitivity : filter.getFilterSensitivities()) { + eventClasses.add(filterSensitivity.getEventClass()); + } + + for (final Labeler labeler : partition.getLabelers()) { + final Set> labelerSensitivities = labeler.getLabelerSensitivities(); + for (final LabelerSensitivity labelerSensitivity : labelerSensitivities) { + eventClasses.add(labelerSensitivity.getEventClass()); + } + } + + // show that these classes do not include the events that this resolver + // processes + for (final Class reservedEventClass : reservedEventClasses) { + if (eventClasses.contains(reservedEventClass)) { + throw new ContractException(PartitionError.RESERVED_PARTITION_TRIGGER, reservedEventClass); + } + } + + keyToEventClassesMap.put(key, eventClasses); + + /* + * Integrate this into the subscription management maps and subscribe for the + * event class if needed + */ + for (final Class eventClass : eventClasses) { + Set keys = eventClassToKeyMap.get(eventClass); + if (keys == null) { + keys = new LinkedHashSet<>(); + eventClassToKeyMap.put(eventClass, keys); + dataManagerContext.subscribe(eventClass, this::handleEvent); + } + keys.add(key); + } + + // create the population partition + + PopulationPartition populationPartition; + if (partition.isDegenerate()) { + populationPartition = new DegeneratePopulationPartitionImpl(partitionsContext, partition, + supportRunContinuity); + } else { + populationPartition = new PopulationPartitionImpl(partitionsContext, partition, supportRunContinuity); + } + keyToPopulationPartitionMap.put(key, populationPartition); + + } + + private void handleEvent(final DataManagerContext dataManagerContext, final Event event) { + final Set keys = eventClassToKeyMap.get(event.getClass()); + if (keys != null) { + for (final Object key : keys) { + final PopulationPartition populationPartition = getPopulationPartition(key); + populationPartition.handleEvent(event); + } + } else { + throw new RuntimeException("received unhandled event " + event); + } + } + + /** + * Removes the population index for the given key if that key is present. + * Returns true if and only if the partition was removed. + * + * @throws ContractException + *
      + *
    • {@linkplain PartitionError#NULL_PARTITION_KEY} + * if the key is null
    • + *
    • {@linkplain PartitionError#UNKNOWN_POPULATION_PARTITION_KEY} + * if the key is unknown
    • + *
    + */ + public void removePartition(final Object key) { + + validatePopulationPartitionKeyNotNull(key); + validatePopulationPartitionExists(key); + keyToPopulationPartitionMap.remove(key); + + final Set> eventClasses = keyToEventClassesMap.get(key); + for (final Class eventClass : eventClasses) { + final Set keys = eventClassToKeyMap.get(eventClass); + keys.remove(key); + if (keys.isEmpty()) { + eventClassToKeyMap.remove(eventClass); + dataManagerContext.unsubscribe(eventClass); + } + } + } + + private final class PartitionsContextImpl implements PartitionsContext { + private final DataManagerContext dataManagerContext; + + public PartitionsContextImpl(DataManagerContext dataManagerContext) { + this.dataManagerContext = dataManagerContext; + } + + /** + * Returns the data manager from the given class reference + * + * @throws ContractException + *
      + *
    • {@linkplain NucleusError#NULL_DATA_MANAGER_CLASS} + * if data manager class is null
    • + *
    • {@linkplain NucleusError#AMBIGUOUS_DATA_MANAGER_CLASS} + * if more than one data manager matches the given + * class
    • + *
    + */ + public T getDataManager(Class dataManagerClass) { + return dataManagerContext.getDataManager(dataManagerClass); + } + + /** + * Returns the current time in the simulation + */ + public double getTime() { + return dataManagerContext.getTime(); + } + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/datamanagers/PartitionsPluginData.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/datamanagers/PartitionsPluginData.java new file mode 100644 index 000000000..82c30b660 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/datamanagers/PartitionsPluginData.java @@ -0,0 +1,148 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.datamanagers; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.PluginDataBuilder; +import net.jcip.annotations.Immutable; + +/** + * An immutable container of the initial state of partitions. It contains:
    + * partitions + */ +@Immutable +public final class PartitionsPluginData implements PluginData { + + /** + * Builder class for PartitionsPluginData + */ + public static class Builder implements PluginDataBuilder { + private Data data; + + private Builder(Data data) { + this.data = data; + } + + /** + * Returns the {@link PartitionsPluginData} from the collected information + * supplied to this builder. + */ + public PartitionsPluginData build() { + return new PartitionsPluginData(new Data(data)); + } + + /** + * Set the run continuity support policy. Defaults to false. Supporting run + * continuity may increase the memory requirements for partitions, but will + * guarantee run continuity. + */ + public Builder setRunContinuitySupport(final boolean supportRunContinuity) { + + data.supportRunContinuity = supportRunContinuity; + return this; + } + } + + private static class Data { + + private boolean supportRunContinuity; + + private Data() { + } + + private Data(Data data) { + supportRunContinuity = data.supportRunContinuity; + } + + @Override + public int hashCode() { + + final int prime = 31; + int result = 1; + result = prime * result + (supportRunContinuity ? 1231 : 1237); + return result; + } + + @Override + public boolean equals(Object obj) { + + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + if (supportRunContinuity != other.supportRunContinuity) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Data [supportRunContinuity="); + builder.append(supportRunContinuity); + builder.append("]"); + return builder.toString(); + } + + } + + /** + * Returns a Builder instance + */ + public static Builder builder() { + return new Builder(new Data()); + } + + private final Data data; + + private PartitionsPluginData(final Data data) { + this.data = data; + } + + /** + * Returns the run continuity support policy + */ + public boolean supportsRunContinuity() { + return data.supportRunContinuity; + } + + @Override + public PluginDataBuilder getCloneBuilder() { + return new Builder(new Data(data)); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + data.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof PartitionsPluginData)) { + return false; + } + PartitionsPluginData other = (PartitionsPluginData) obj; + if (!data.equals(other.data)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder2 = new StringBuilder(); + builder2.append("PartitionsPluginData [data="); + builder2.append(data); + builder2.append("]"); + return builder2.toString(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/DegeneratePopulationPartitionImpl.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/DegeneratePopulationPartitionImpl.java new file mode 100644 index 000000000..b3cba8673 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/DegeneratePopulationPartitionImpl.java @@ -0,0 +1,222 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.containers.BasePeopleContainer; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.containers.PeopleContainer; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters.Filter; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters.TrueFilter; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.RandomNumberGeneratorId; +import util.errors.ContractException; + +/** + * Implementation of PopulationPartition for degenerate partitions having only a + * filter and a single cell in its partition space, i.e. the partition was + * specified with no labeling functions. + */ +public class DegeneratePopulationPartitionImpl implements PopulationPartition { + + private final StochasticsDataManager stochasticsDataManager; + + private final PeopleContainer peopleContainer; + + private final PartitionsContext partitionsContext; + + private final Filter filter; + + private final Map, List>> eventClassToFilterSensitivityMap = new LinkedHashMap<>(); + + /** + * Constructs an DegeneratePopulationPartitionImpl + * + * @throws ContractException {@linkplain PartitionError#NON_DEGENERATE_PARTITION} + * if the partition contains labelers + * @throws RuntimeException + *
      + *
    • if context is null
    • + *
    • if partition is null
    • + *
    • if the partition contains labelers
    • + *
    + */ + public DegeneratePopulationPartitionImpl(final PartitionsContext partitionsContext, final Partition partition, + boolean supportRunContinuity) { + this.partitionsContext = partitionsContext; + stochasticsDataManager = partitionsContext.getDataManager(StochasticsDataManager.class); + filter = partition.getFilter().orElse(new TrueFilter()); + + if (!partition.isDegenerate()) { + throw new ContractException(PartitionError.NON_DEGENERATE_PARTITION); + } + + for (final FilterSensitivity filterSensitivity : filter.getFilterSensitivities()) { + List> list = eventClassToFilterSensitivityMap + .get(filterSensitivity.getEventClass()); + if (list == null) { + list = new ArrayList<>(); + eventClassToFilterSensitivityMap.put(filterSensitivity.getEventClass(), list); + } + list.add(filterSensitivity); + } + + peopleContainer = new BasePeopleContainer(partitionsContext, supportRunContinuity); + + final PeopleDataManager peopleDataManager = partitionsContext.getDataManager(PeopleDataManager.class); + final int personIdLimit = peopleDataManager.getPersonIdLimit(); + for (int i = 0; i < personIdLimit; i++) { + if (peopleDataManager.personIndexExists(i)) { + final PersonId personId = peopleDataManager.getBoxedPersonId(i).get(); + if (filter.evaluate(partitionsContext, personId)) { + /* + * Using unsafe add since this is in the constructor, we are sure that the + * person is not already contained + */ + peopleContainer.unsafeAdd(personId); + } + } + } + + } + + @Override + public void attemptPersonAddition(final PersonId personId) { + if (filter.evaluate(partitionsContext, personId)) { + /* + * By contract, this method is only invoked with person ids that are new to the + * simulation or new to this population partition and thus cannot already in + * members of this population partition. + */ + peopleContainer.unsafeAdd(personId); + } + } + + @Override + public void attemptPersonRemoval(final PersonId personId) { + peopleContainer.remove(personId); + } + + @Override + public boolean contains(final PersonId personId) { + return peopleContainer.contains(personId); + } + + @Override + public boolean contains(final PersonId personId, final LabelSet labelSet) { + return peopleContainer.contains(personId); + } + + /** + * Returns the people identifiers of this index + */ + @Override + public List getPeople() { + return peopleContainer.getPeople(); + } + + @Override + public List getPeople(final LabelSet labelSet) { + return peopleContainer.getPeople(); + } + + @Override + public int getPeopleCount() { + return peopleContainer.size(); + } + + @Override + public int getPeopleCount(final LabelSet labelSet) { + return peopleContainer.size(); + } + + /** + * Returns a map whose single key is an empty label set and whose single value + * is the number of people in the population partition. Precondition: the label + * set should be empty or null. + */ + @Override + public Map getPeopleCountMap(LabelSet labelSet) { + Map result = new LinkedHashMap<>(); + result.put(LabelSet.builder().build(), peopleContainer.size()); + return result; + } + + @Override + public void handleEvent(final Event event) { + PersonId personId = null; + final List> filterSensitivities = eventClassToFilterSensitivityMap + .get(event.getClass()); + if (filterSensitivities != null) { + for (final FilterSensitivity filterSensitivity : filterSensitivities) { + final Optional optionalPersonId = filterSensitivity.requiresRefresh(partitionsContext, event); + if (optionalPersonId.isPresent()) { + personId = optionalPersonId.get(); + break; + } + } + } + + if (personId != null) { + if (filter.evaluate(partitionsContext, personId)) { + peopleContainer.safeAdd(personId); + } else { + peopleContainer.remove(personId); + } + } + + } + + /** + * Forces the index to evaluate a person's membership in this index. + */ + @Override + public Optional samplePartition(final PartitionSampler partitionSampler) { + RandomGenerator randomGenerator; + final RandomNumberGeneratorId randomNumberGeneratorId = partitionSampler.getRandomNumberGeneratorId() + .orElse(null); + if (randomNumberGeneratorId != null) { + randomGenerator = stochasticsDataManager.getRandomGeneratorFromId(randomNumberGeneratorId); + } else { + randomGenerator = stochasticsDataManager.getRandomGenerator(); + } + + final PersonId excludedPersonId = partitionSampler.getExcludedPerson().orElse(null); + + int candidateCount = peopleContainer.size(); + if (excludedPersonId != null) { + if (peopleContainer.contains(excludedPersonId)) { + candidateCount--; + } + } + PersonId result = null; + if (candidateCount > 0) { + while (true) { + result = peopleContainer.getRandomPersonId(randomGenerator); + if (!result.equals(excludedPersonId)) { + break; + } + } + } + + return Optional.ofNullable(result); + } + + @Override + public boolean validateLabelSetInfo(final LabelSet labelSet) { + return labelSet.isEmpty(); + } + + @Override + public Optional getPersonValue(LabelSetFunction labelSetFunction, PersonId personId) { + return Optional.ofNullable(null); + } + +} diff --git a/gcm3/src/main/java/plugins/partitions/support/Equality.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/Equality.java similarity index 85% rename from gcm3/src/main/java/plugins/partitions/support/Equality.java rename to gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/Equality.java index 13ccf45ee..7df9754fa 100644 --- a/gcm3/src/main/java/plugins/partitions/support/Equality.java +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/Equality.java @@ -1,10 +1,9 @@ -package plugins.partitions.support; +package gov.hhs.aspr.ms.gcm.plugins.partitions.support; + +import org.apache.commons.math3.random.RandomGenerator; /** * Enumeration used by various filters to perform equality comparisons - * - * @author Shawn Hatch - * */ public enum Equality { LESS_THAN { @@ -78,4 +77,9 @@ public static Equality getNegation(Equality equality) { } } + public static Equality getRandomEquality(RandomGenerator randomGenerator) { + int index = randomGenerator.nextInt(Equality.values().length); + return Equality.values()[index]; + } + } diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/EventPredicate.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/EventPredicate.java new file mode 100644 index 000000000..63c1524e7 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/EventPredicate.java @@ -0,0 +1,20 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support; + +import java.util.Optional; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; + +/** + * A generics based function that returns an optional {@link PersonId} from a + * {@link PartitionsContext} and {@link Event}. Used by + * {@link FilterSensitivity} to ascertain the person id from an event and + * whether that event would effect the filter that owns the filter sensitivity. + */ +public interface EventPredicate { + /** + * Returns an optional of the person id associated with the event if that event + * would effect that filter. Returns an empty optional otherwise. + */ + public Optional requiresRefresh(PartitionsContext partitionsContext, T event); +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/FilterSensitivity.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/FilterSensitivity.java new file mode 100644 index 000000000..25c96e012 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/FilterSensitivity.java @@ -0,0 +1,65 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support; + +import java.util.Optional; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import net.jcip.annotations.Immutable; + +/** + * Partitions are maintained as events relating to people are resolved. To do so + * efficiently, we need to determine whether a particular event will trigger a + * change to either a person's membership in the partition or their position + * within the partition space. Partition filters describe their sensitivity to + * events via this generics-base class. + */ +@Immutable +public final class FilterSensitivity { + + private class MetaPredicate { + + private final EventPredicate eventPredicate; + + public MetaPredicate(EventPredicate eventPredicate) { + this.eventPredicate = eventPredicate; + } + + @SuppressWarnings("unchecked") + public Optional requiresRefresh(PartitionsContext partitionsContext, Event event) { + return eventPredicate.requiresRefresh(partitionsContext, (K) event); + } + } + + private final Class eventClass; + + private final MetaPredicate metaPredicate; + + /** + * Creates this FilterSensitivity with the generic event type and given event + * predicate. + */ + public FilterSensitivity(Class eventClass, EventPredicate eventPredicate) { + super(); + this.eventClass = eventClass; + this.metaPredicate = new MetaPredicate(eventPredicate); + } + + /** + * Returns the event class for this filter sensitivity + */ + public Class getEventClass() { + return eventClass; + } + + /** + * Returns a PersonId if and only if the event would require a refresh from the + * owning filter. The partition resolver is not able to determine from an event + * the person id of an event that possibly changes the membership of a person in + * a partition. The person id returned is the person that will trigger the + * refresh and is thus the person id associated with the event. + */ + public Optional requiresRefresh(PartitionsContext partitionsContext, Event event) { + return metaPredicate.requiresRefresh(partitionsContext, event); + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/LabelSet.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/LabelSet.java new file mode 100644 index 000000000..e7b71b5f2 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/LabelSet.java @@ -0,0 +1,148 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import util.errors.ContractException; + +/** + * A {@linkplain LabelSet} is a set of labels that are used to specify a sub-set + * of the cell space of a partition during sampling. Partitions are composed of + * cells that are associated with combinations of labels associated with the + * various attributes of people. The label set specifies a subset of that space + * by value. For example: Suppose a partition is formed by the regions and two + * person properties. The regions are grouped together under state labels. The + * first property is the Integer AGE and is grouped by PRESCHOOL, SCHOOL and + * ADULT. The second property is the Integer VACCINE_DOSES_RECEIVED and ranges + * from 0 to 3 inclusive. The {@link LabelSet} [REGION = VIRGINIA, AGE=PRESHOOL, + * VACCINE_DOSES_RECEIVED=2] will match the single partition cell that + * represents Virginia preschoolers who have received 2 doses of vaccine. The + * {@link LabelSet} [REGION = VIRGINIA, AGE=PRESHOOL] will match all partition + * cells that represent Virginia preschoolers, without regard to doses of + * vaccine received. Label Sets are built by the modeler via the supplied + * Builder class. + */ +public final class LabelSet { + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((labels == null) ? 0 : labels.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + LabelSet other = (LabelSet) obj; + if (labels == null) { + if (other.labels != null) + return false; + } else if (!labels.equals(other.labels)) + return false; + return true; + } + + /** + * Returns a new Builder instance + */ + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private Builder() { + + } + + private Data data = new Data(); + + public LabelSet build() { + return new LabelSet(new Data(data)); + } + + /** + * Sets the label value for the given label id. Replaces any existing label + * value for the same id. + * + * @throws ContractException + *
      + *
    • {@linkplain PartitionError#NULL_PARTITION_LABEL_DIMENSION} + * if the dimension is null
    • + *
    • {@linkplain PartitionError#NULL_PARTITION_LABEL} + * if the label is null
    • + *
    + */ + public Builder setLabel(Object id, Object label) { + if (id == null) { + throw new ContractException(PartitionError.NULL_PARTITION_LABEL_DIMENSION); + } + if (label == null) { + throw new ContractException(PartitionError.NULL_PARTITION_LABEL); + } + data.labels.put(id, label); + return this; + } + + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("LabelSet [labels="); + builder.append(labels); + builder.append("]"); + return builder.toString(); + } + + private static class Data { + private Map labels = new LinkedHashMap<>(); + + public Data() { + } + + public Data(Data data) { + labels.putAll(data.labels); + } + } + + /** + * Returns the dimension label for this {@link LabelSet} + */ + public Optional getLabel(Object dimension) { + return Optional.ofNullable(this.labels.get(dimension)); + } + + /** + * Returns an unmodifiable list of dimensions + */ + public Set getDimensions() { + return dimensions; + } + + /** + * Returns true if and only if this {@link LabelSet} has no label values + */ + public boolean isEmpty() { + return labels.isEmpty(); + } + + private LabelSet(Data data) { + this.labels = data.labels; + this.dimensions = Collections.unmodifiableSet(new LinkedHashSet<>(labels.keySet())); + } + + private final Map labels; + private final Set dimensions; +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/LabelSetFunction.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/LabelSetFunction.java new file mode 100644 index 000000000..d1c07ab40 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/LabelSetFunction.java @@ -0,0 +1,15 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support; + +/** + * A functional interface for selecting people from a {@link Partition} based on + * assigning a weighting value to a {@link LabelSet}. + */ +public interface LabelSetFunction { + /** + * Returns a non-negative, finite and stable value for the given inputs. This + * function should be stable: repeated invocations with the same arguments + * should return the same value during the span of a single sample of a + * {@link Partition}. + */ + public T getValue(PartitionsContext partitionsContext, LabelSet labelSet); +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/LabelSetWeightingFunction.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/LabelSetWeightingFunction.java new file mode 100644 index 000000000..ac2bfe16b --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/LabelSetWeightingFunction.java @@ -0,0 +1,15 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support; + +/** + * A functional interface for selecting people from a {@link Partition} based on + * assigning a weighting value to a {@link LabelSet}. + */ +public interface LabelSetWeightingFunction { + /** + * Returns a non-negative, finite and stable value for the given inputs. This + * function should be stable: repeated invocations with the same arguments + * should return the same value during the span of a single sample of a + * {@link Partition}. + */ + public double getWeight(PartitionsContext partitionsContext, LabelSet labelSet); +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/Labeler.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/Labeler.java new file mode 100644 index 000000000..39cf6a555 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/Labeler.java @@ -0,0 +1,58 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support; + +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; + +/** + * A partition labeler creates an object label for a person. The labeler has an + * object dimension which is a dimension within a partition and the label + * occupies a point in that dimension. + */ +public interface Labeler { + + /** + * Returns the labeler sensitivities associated with this label. + */ + public Set> getLabelerSensitivities(); + + /** + * Returns the label for the person based on the person's current information + */ + public Object getCurrentLabel(PartitionsContext partitionsContext, PersonId personId); + + /** + * Returns the label for the person based on the previous value recorded in the + * event. Partitions can be built with an optional policy to record the cell + * location of each person in the partition's cells. This can require a + * significant amount of memory to track. To help reduce this cost, the policy + * can be set to not track the current cell for each person. However, when an + * observation event occurs that would have the partition move the person from + * one cell to another, the run-time cost of locating the person's current cell + * is high. To address this, the labeler is asked to determine the current label + * for the person based on the event's data. Person related events are expected + * to always carry the associated recent value that was just updated. + */ + public Object getPastLabel(PartitionsContext partitionsContext, Event event); + + /** + * Returns the id for this labeler. + */ + public Object getId(); + + /** + * Labelers are equal if they label every person identically + */ + @Override + public int hashCode(); + + /** + * Labelers are equal if they label every person identically + */ + @Override + public boolean equals(Object obj); + + @Override + public String toString(); +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/LabelerSensitivity.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/LabelerSensitivity.java new file mode 100644 index 000000000..001b03806 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/LabelerSensitivity.java @@ -0,0 +1,54 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support; + +import java.util.Optional; +import java.util.function.Function; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import net.jcip.annotations.Immutable; + +/** + * Partitions are maintained as events relating to people are resolved. To do so + * efficiently, we need to determine whether a particular event will trigger a + * change to either a person's membership in the partition or their position + * within the partition space. Partition labelers describe their sensitivity to + * events via this generics-base class. + */ +@Immutable +public final class LabelerSensitivity { + private final Class eventClass; + private final Function> personFunction; + + /** + * Creates the labeler sensitivity from the event type and person function. The + * person function should return and empty optional if an event will not effect + * a person's label and an optional of the person id otherwise. + */ + public LabelerSensitivity(Class eventClass, Function> personFunction) { + super(); + this.eventClass = eventClass; + this.personFunction = personFunction; + } + + /** + * Returns the event class of this labeler sensitivity. This will be same type + * and the generic type of the class. + */ + public Class getEventClass() { + return eventClass; + } + + /** + * Returns the person id from the event if the event will effect that person's + * label value and an empty optional otherwise. The partition resolver is not + * able to determine from an event the person id of an event that possibly + * changes the label cell of a person in a partition. The person id returned is + * the person that will trigger the refresh and is thus the person id associated + * with the event. + */ + @SuppressWarnings("unchecked") + public Optional getPersonId(Event event) { + return personFunction.apply((T) event); + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/Partition.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/Partition.java new file mode 100644 index 000000000..7a0a4690b --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/Partition.java @@ -0,0 +1,258 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support; + +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters.Filter; +import net.jcip.annotations.Immutable; +import net.jcip.annotations.NotThreadSafe; + +/** + * A {@linkplain Partition} is the general description of a partitioning of the + * people contained in the simulation. It is composed of a filter and various + * functions that map values associated with each person to labels. The space + * formed by the labels forms the formal partition. Partitions significantly + * reduce the runtime required to sample/select people from the simulation who + * meet some set of criteria. This class functions as a description of a + * population partition and is immutable. However, the implementation of the + * partition within the simulation is dynamic and the contents(people) of the + * partition remain consistent with the filter and label mapping functions as + * people change their properties, group associations, resources and regions. + * Partitions are built by the modeler via the supplied Builder class. + * Partitions may be added and removed from the simulation and are identified by + * a unique identifier. Only the Component that adds a partition may remove that + * partition. The filter: The role of the filter supplied to the partition is + * simply to determine which people will be included in the partition's cells. + * If no filter is supplied, the resulting partition will include all people. + * For example, suppose there is a Boolean person property IS_VACCINTATED. If + * the filter is [IS_VACCINATED EQUALS == FALSE] then only people who had not + * been vaccinated would be included in the cells of the partition. The labeling + * functions: The labeling functions serve to group people into the cells of the + * partition. For example, suppose their are numerous regions in the simulation + * with each representing a single census tract. The modeler may wish to group + * these regions by state and would thus provide a function that accepts a + * region and returns a state name as the label. Labels are not required to be + * of any particular type or even to be of the same type for any particular + * function. In this example, the modeler could create an enumeration composed + * of values for each of the states and territories(ALABAMA, ALASKA, ...) and + * would thus return the associated enumeration member as the label value. The + * inclusion of multiple labeling functions serves to refine the cells of the + * partition. For example, suppose that there is a person property for the + * (Integer) age of each person. The modeler may want to group people under age + * 30 as "YOUNG" and those older as "OLD". Combined with the previous region + * labeling function, the partition will have cells such as [UTAH,OLD], + * [OHIO,YOUNG], etc. Thus to retrieve or randomly sample from the simulation + * those people who are unvaccinated, live in Maryland, and are below the age of + * 30 the modeler queries the environment with the the id of the partition and + * the label values [MARYLAND,YOUNG]. When implementing a supplied labeling + * function, the modeler must take some care to only consider the inputs of the + * function and to guarantee the stability of the return value. For example, in + * the age labeling function above, the function will receive an Integer (such + * as 35) and must always return the same label(OLD) in every invocation of the + * function. Without this the partition will not function correctly. + */ +@Immutable +public final class Partition { + + /** + * Returns a new Builder instance + */ + public static Builder builder() { + return new Builder(); + } + + @NotThreadSafe + /** + * Standard builder class for partitions. All inputs are optional. + */ + public static class Builder { + + private Data data = new Data(); + + private Builder() { + } + + /** + * Returns the {@linkplain Partition} formed from the inputs collected by this + * builder and resets the state of the builder to empty. + */ + public Partition build() { + return new Partition(new Data(data)); + } + + /** + * Adds a labeler. Replaces any existing labeler with the same id. + */ + public Builder addLabeler(Labeler labeler) { + data.labelers.put(labeler.getId(), labeler); + return this; + } + + /** + * Sets the filter for the {@linkplain Partition}. If no filter is provided, a + * default filter that accepts all people is used instead. + */ + public Builder setFilter(Filter filter) { + data.filter = filter; + return this; + } + + /** + * Set the retention policy for derived partition cell keys for people. Defaults + * to true. + */ + public Builder setRetainPersonKeys(boolean retainPersonKeys) { + data.retainPersonKeys = retainPersonKeys; + return this; + } + + } + + private final Data data; + + private Partition(Data data) { + this.data = data; + } + + /** + * Returns true if and only if the {@linkplain Partition} contains no labeling + * functions. + */ + public boolean isDegenerate() { + return data.labelers.isEmpty(); + } + + /** + * Returns true if and only if partition cell keys should be retained by the + * partition for faster performance at the cost of higher memory usage. + */ + public boolean retainPersonKeys() { + return data.retainPersonKeys; + } + + private static class Data { + private boolean retainPersonKeys = true; + + private Map labelers = new LinkedHashMap<>(); + + private Filter filter; + + public Data() { + } + + public Data(Data data) { + retainPersonKeys = data.retainPersonKeys; + labelers.putAll(data.labelers); + filter = data.filter; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((filter == null) ? 0 : filter.hashCode()); + result = prime * result + ((labelers == null) ? 0 : labelers.hashCode()); + result = prime * result + (retainPersonKeys ? 1231 : 1237); + return result; + } + + @Override + public boolean equals(Object obj) { + + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + if (filter == null) { + if (other.filter != null) { + return false; + } + } else if (!filter.equals(other.filter)) { + return false; + } + if (labelers == null) { + if (other.labelers != null) { + return false; + } + } else if (!labelers.equals(other.labelers)) { + return false; + } + if (retainPersonKeys != other.retainPersonKeys) { + return false; + } + return true; + } + + @Override + public String toString() { + + StringBuilder builder = new StringBuilder(); + builder.append("Data [filter="); + builder.append(filter); + builder.append(", labelers="); + builder.append(labelers); + builder.append(", retainPersonKeys="); + builder.append(retainPersonKeys); + builder.append("]"); + return builder.toString(); + } + + } + + /** + * Returns the filter contained in this {@linkplain Partition} + */ + public Optional getFilter() { + return Optional.ofNullable(data.filter); + } + + /** + * Returns the collected labelsers. Each labelers will have a unique id. + */ + public Set getLabelers() { + return new LinkedHashSet<>(data.labelers.values()); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Partition)) { + return false; + } + Partition other = (Partition) obj; + if (data == null) { + if (other.data != null) { + return false; + } + } else if (!data.equals(other.data)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder2 = new StringBuilder(); + builder2.append("Partition [data="); + builder2.append(data); + builder2.append("]"); + return builder2.toString(); + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/PartitionError.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/PartitionError.java new file mode 100644 index 000000000..16fee993d --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/PartitionError.java @@ -0,0 +1,47 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support; + +import util.errors.ContractError; +import util.errors.ContractException; + +/** + * An enumeration supporting {@link ContractException} that acts as a general + * description of the exception. + */ +public enum PartitionError implements ContractError { + + PAST_LABEL_FAILURE("A labeler has failed to properly identify a person's past label from an event"),// + DUPLICATE_PARTITION("Duplicate partition key"),// + NON_DEGENERATE_PARTITION("Requires a degenerate partition"),// + INCOMPATIBLE_LABEL_SET("The label set is incompatible with the selected population partition definition"), + MALFORMED_PARTITION_SAMPLE_WEIGHTING_FUNCTION( + "Data used to form an enumerated distribution for partition sampling was malformed"),// + NULL_LABEL_SET_FUNCTION("Null labelset function"), + RESERVED_PARTITION_TRIGGER( + "An event class is being used to trigger refreshes as part of a partition that is reserved for the partition resolver"),// + NON_COMPARABLE_ATTRIBUTE("The attribute definition is not compatible with innequality comparisons"),// + NULL_EQUALITY_OPERATOR("Null equality operator"),// + NULL_LABEL_SET("Null label set"),// + NULL_PARTITION("Null partition"),// + NULL_PARTITION_PLUGIN("null partitions plugin"),// + NULL_PARTITION_PLUGIN_DATA("null partitions plugin data"),// + NULL_PARTITION_LABEL("Null partition label"),// + NULL_PARTITION_LABEL_DIMENSION("Null partition label dimension"),// + NULL_FILTER("Null filter"),// + NULL_PARTITION_KEY("Null population partition key"),// + NULL_POPULATION_PARTITION("Null population partition"),// + NULL_PARTITION_DATA_MANAGER("Null partition data manager"),// + NULL_PARTITION_SAMPLER("Null partition sampler"),// + UNKNOWN_POPULATION_PARTITION_KEY("No population partition found"),// + NULL_PERSON_DATA_VIEW("Null person data view"); + + private final String description; + + private PartitionError(final String description) { + this.description = description; + } + + @Override + public String getDescription() { + return description; + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/PartitionSampler.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/PartitionSampler.java new file mode 100644 index 000000000..631bf4923 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/PartitionSampler.java @@ -0,0 +1,140 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support; + +import java.util.Optional; + +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.RandomNumberGeneratorId; + +/** + * A {@link PartitionSampler} represents the details of a sample query for a + * {@link Partition}. All inputs to the {@link PartitionSampler} are optional. + */ +public final class PartitionSampler { + + private final Data data; + + private static class Data { + + private PersonId excludedPersonId; + + private RandomNumberGeneratorId randomNumberGeneratorId; + + private LabelSet labelSet; + + private LabelSetWeightingFunction labelSetWeightingFunction; + + public Data() { + } + + public Data(Data data) { + excludedPersonId = data.excludedPersonId; + randomNumberGeneratorId = data.randomNumberGeneratorId; + labelSet = data.labelSet; + labelSetWeightingFunction = data.labelSetWeightingFunction; + } + } + + /** + * Returns a new Builder instance + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Standard builder class for partition samplers. All inputs are optional. + */ + public static class Builder { + private Data data = new Data(); + + private Builder() { + } + + /** + * Returns the {@linkplain PartitionSampler} formed from the inputs collected by + * this builder and resets the state of the builder to empty. + */ + public PartitionSampler build() { + return new PartitionSampler(new Data(data)); + } + + /** + * Sets the {@link PersonId} to be excluded as a sample result. + */ + public Builder setExcludedPerson(PersonId excludedPersonId) { + data.excludedPersonId = excludedPersonId; + return this; + } + + /** + * Sets the {@link RandomNumberGeneratorId} to be used when sampling from a + * {@link Partition}. If no {@link RandomNumberGeneratorId} is provided, the + * default random number generator for the simulation is used. + */ + public Builder setRandomNumberGeneratorId(RandomNumberGeneratorId randomNumberGeneratorId) { + data.randomNumberGeneratorId = randomNumberGeneratorId; + return this; + } + + /** + * Sets the {@link LabelSet} for this {@link PartitionSampler}. If no + * {@link LabelSet} is provided, all cells of the {@link Partition} participate + * in the sampling. + */ + public Builder setLabelSet(LabelSet labelSet) { + data.labelSet = labelSet; + return this; + } + + /** + * Sets the {@link LabelSetWeightingFunction} for this {@link PartitionSampler}. + * If no {@link LabelSetWeightingFunction} is provided, all cells of the + * {@link Partition} are weighted uniformly. + */ + public Builder setLabelSetWeightingFunction(LabelSetWeightingFunction labelSetWeightingFunction) { + data.labelSetWeightingFunction = labelSetWeightingFunction; + return this; + } + + } + + /** + * Returns the PersonId to be excluded as a result of sampling a + * {@link Partition} + */ + public Optional getExcludedPerson() { + return Optional.ofNullable(data.excludedPersonId); + } + + /** + * Returns the {@link RandomNumberGeneratorId} to be used when generating the + * random sample. If no {@link RandomNumberGeneratorId} is provided, the default + * random number generator for the simulation is used. + */ + public Optional getRandomNumberGeneratorId() { + return Optional.ofNullable(data.randomNumberGeneratorId); + } + + /** + * Returns the {@link LabelSet} for this {@link PartitionSampler}. If no + * {@link LabelSet} is provided, all cells of the {@link Partition} participate + * in the sampling. + */ + public Optional getLabelSet() { + return Optional.ofNullable(data.labelSet); + } + + /** + * Returns the {@link LabelSetWeightingFunction} for this + * {@link PartitionSampler}. If no {@link LabelSetWeightingFunction} is + * provided, all cells of the {@link Partition} are weighted uniformly. + */ + public Optional getLabelSetWeightingFunction() { + return Optional.ofNullable(data.labelSetWeightingFunction); + } + + private PartitionSampler(Data data) { + this.data = data; + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/PartitionsContext.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/PartitionsContext.java new file mode 100644 index 000000000..2f6940953 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/PartitionsContext.java @@ -0,0 +1,31 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support; + +import gov.hhs.aspr.ms.gcm.nucleus.DataManager; +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import util.errors.ContractException; + +/** + * A limited context that grants access to the simulation via the partition data + * manager's own data manager context. + */ +public interface PartitionsContext { + + /** + * Returns the data manager from the given class reference + * + * @throws ContractException + *
      + *
    • {@linkplain NucleusError#NULL_DATA_MANAGER_CLASS} + * if data manager class is null
    • + *
    • {@linkplain NucleusError#AMBIGUOUS_DATA_MANAGER_CLASS} + * if more than one data manager matches the given + * class
    • + *
    + */ + public T getDataManager(Class dataManagerClass); + + /** + * Returns the current time in the simulation + */ + public double getTime(); +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/PopulationPartition.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/PopulationPartition.java new file mode 100644 index 000000000..90270247f --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/PopulationPartition.java @@ -0,0 +1,107 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; + +/** + * A {@link PopulationPartition} is the interface for implementors of + * partitions, maintaining the people associated with the partition's cells by + * handling individual mutation events. + */ +public interface PopulationPartition { + + /** + * Handles the addition of a person to the simulation. Preconditions : Should + * only be used to add people who have just been created and thus cannot already + * be members. + * + * @throws RuntimeException if the person id is null + */ + public void attemptPersonAddition(PersonId personId); + + /** + * Handles the removal of a person from the simulation Precondition: Person must + * exist + */ + public void attemptPersonRemoval(PersonId personId); + + /** + * Handles the relevant data change to a person preconditions: the event must + * not be null + */ + public void handleEvent(Event event); + + /** + * Returns true if and only if the given {@link LabelSet} is compatible with + * this {@link PopulationPartition}. To be consistent, the {@link LabelSet} must + * not contain label values for label dimensions not contained in this + * partition. Precondition: label set may not be null + */ + public boolean validateLabelSetInfo(LabelSet labelSet); + + /** + * Returns the number of people contained in this {@link PopulationPartition} + */ + public int getPeopleCount(); + + /** + * Returns the number of people contained in this {@link PopulationPartition} + * that are contained in cells that match the given {@link LabelSet} + */ + public int getPeopleCount(LabelSet labelSet); + + /** + * Returns the number of people contained in this {@link PopulationPartition} + * that are contained in cells that match the given {@link LabelSet}, mapped to + * each cell. + */ + public Map getPeopleCountMap(LabelSet labelSet); + + /** + * Returns true if and only if the person is contained in this + * {@link PopulationPartition} + */ + public boolean contains(PersonId personId); + + /** + * Returns true if and only if the person is contained in this + * {@link PopulationPartition} under the cells consistent with the given + * {@link LabelSet} + */ + public boolean contains(PersonId personId, LabelSet labelSet); + + /** + * Returns the people contained in this {@link PopulationPartition} that are + * contained in cells that match the given {@link LabelSet} Precondition : the + * label set must be non-null and contain only compatible dimensions with this + * population partition. + */ + public List getPeople(LabelSet labelSet); + + /** + * Returns the people contained in this {@link PopulationPartition} + */ + public List getPeople(); + + /** + * Returns a randomly chosen person identifier from the partition consistent + * with the partition sampler info. Note that the sampler must be consistent + * with the partition definition used to create this population partition. No + * precondition tests will be performed. + */ + public Optional samplePartition(final PartitionSampler partitionSampler); + + /** + * Returns an optional value by applying the given function to the label set + * associated with the person. If the person is not contained in the population + * partition the method returns an empty optional. Note that the + * labelSetFunction must be consistent with the partition definition used to + * create this population partition. No precondition tests will be performed. + */ + public Optional getPersonValue(LabelSetFunction labelSetFunction, PersonId personId); + +} diff --git a/gcm3/src/main/java/plugins/partitions/support/PopulationPartitionImpl.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/PopulationPartitionImpl.java similarity index 77% rename from gcm3/src/main/java/plugins/partitions/support/PopulationPartitionImpl.java rename to gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/PopulationPartitionImpl.java index 5181e3f7f..045067e41 100644 --- a/gcm3/src/main/java/plugins/partitions/support/PopulationPartitionImpl.java +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/PopulationPartitionImpl.java @@ -1,4 +1,4 @@ -package plugins.partitions.support; +package gov.hhs.aspr.ms.gcm.plugins.partitions.support; import java.util.ArrayList; import java.util.Arrays; @@ -14,23 +14,20 @@ import org.apache.commons.math3.random.RandomGenerator; import org.apache.commons.math3.util.FastMath; -import nucleus.Event; -import nucleus.NucleusError; -import nucleus.SimulationContext; -import plugins.partitions.support.containers.BasePeopleContainer; -import plugins.partitions.support.containers.PeopleContainer; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.PersonId; -import plugins.stochastics.StochasticsDataManager; -import plugins.stochastics.support.RandomNumberGeneratorId; +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.containers.BasePeopleContainer; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.containers.PeopleContainer; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters.Filter; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters.TrueFilter; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.RandomNumberGeneratorId; import util.errors.ContractException; /** * Primary implementor for {@link PopulationPartition} - * - * - * @author Shawn Hatch - * */ public final class PopulationPartitionImpl implements PopulationPartition { @@ -69,9 +66,8 @@ private Key(final Key key) { } /* - * We are using the older Arrays.equals and Arrays.hashCode since it - * suffices for our use case and uses half the runtime of the newer deep - * versions + * We are using the older Arrays.equals and Arrays.hashCode since it suffices + * for our use case and uses half the runtime of the newer deep versions */ public void calculateHashCode() { hashCode = Arrays.hashCode(keys); @@ -255,7 +251,7 @@ public boolean hasNext() { @Override public Key next() { - if (nextKey == null) { + if (nextKey == null) { throw new NoSuchElementException(); } final Key result = nextKey; @@ -286,7 +282,7 @@ public int size() { private final LabelManager[] labelManagers; - private final Map dimensions = new LinkedHashMap<>(); + private final Map labelerIds = new LinkedHashMap<>(); private final Filter filter; @@ -301,7 +297,8 @@ public int size() { private final StochasticsDataManager stochasticsDataManager; - private final SimulationContext simulationContext; + private final boolean supportRunContinuity; + private final PartitionsContext partitionsContext; private final Map, List>> eventClassToFilterSensitivityMap = new LinkedHashMap<>(); private final Map, List>> eventClassToLabelerSensitivityMap = new LinkedHashMap<>(); @@ -313,22 +310,26 @@ public int size() { private double[] weights; private Key[] weightedKeys; + private final PeopleDataManager peopleDataManager; /** * Constructs a PopulationPartitionImpl * - * * @throws RuntimeException - *
  • if context is null
  • - *
  • if partition is null
  • - *
  • if the partition contains labelers
  • + *
      + *
    • if context is null
    • + *
    • if partition is null
    • + *
    • if the partition contains labelers
    • + *
    */ - public PopulationPartitionImpl(final SimulationContext simulationContext, final Partition partition) { - this.simulationContext = simulationContext; - + public PopulationPartitionImpl(final PartitionsContext partitionsContext, final Partition partition, + boolean supportRunContinuity) { + this.supportRunContinuity = supportRunContinuity; + this.partitionsContext = partitionsContext; + retainPersonKeys = partition.retainPersonKeys(); - peopleDataManager = simulationContext.getDataManager(PeopleDataManager.class); + peopleDataManager = partitionsContext.getDataManager(PeopleDataManager.class); if (retainPersonKeys) { personToKeyMap = new ArrayList<>(peopleDataManager.getPersonIdLimit()); @@ -336,9 +337,10 @@ public PopulationPartitionImpl(final SimulationContext simulationContext, final personMembership = new BitSet(peopleDataManager.getPersonIdLimit()); } - filter = partition.getFilter().orElse(Filter.allPeople()); + filter = partition.getFilter().orElse(new TrueFilter()); for (final FilterSensitivity filterSensitivity : filter.getFilterSensitivities()) { - List> list = eventClassToFilterSensitivityMap.get(filterSensitivity.getEventClass()); + List> list = eventClassToFilterSensitivityMap + .get(filterSensitivity.getEventClass()); if (list == null) { list = new ArrayList<>(); eventClassToFilterSensitivityMap.put(filterSensitivity.getEventClass(), list); @@ -365,17 +367,16 @@ public PopulationPartitionImpl(final SimulationContext simulationContext, final } labelerSensitivities = new LabelerSensitivity[maxLabelerSensitivityCount]; - stochasticsDataManager = simulationContext.getDataManager(StochasticsDataManager.class); + stochasticsDataManager = partitionsContext.getDataManager(StochasticsDataManager.class); keySize = labelers.size(); tempKeyForLabelSets = new Key(keySize); tempKeyForPeople = new Key(keySize); - labelManagers = new LabelManager[keySize]; for (int i = 0; i < keySize; i++) { final Labeler labeler = labelers.get(i); - dimensions.put(labeler.getDimension(), i); + labelerIds.put(labeler.getId(), i); labelManagers[i] = new LabelManager(labeler); } @@ -383,22 +384,21 @@ public PopulationPartitionImpl(final SimulationContext simulationContext, final for (int i = 0; i < personIdLimit; i++) { if (peopleDataManager.personIndexExists(i)) { final PersonId personId = peopleDataManager.getBoxedPersonId(i).get(); - if (filter.evaluate(simulationContext, personId)) { + if (filter.evaluate(partitionsContext, personId)) { /* - * By contract, we know that the person id should not - * already be a member of this container + * By contract, we know that the person id should not already be a member of + * this container */ addPerson(personId); } } } - - + } /* - * Preconditions: The personId is not null, is not currently a member and - * passes the filter + * Preconditions: The personId is not null, is not currently a member and passes + * the filter */ private void addPerson(final PersonId personId) { @@ -408,12 +408,11 @@ private void addPerson(final PersonId personId) { } } - final Key key = tempKeyForPeople; final int n = labelManagers.length; for (int i = 0; i < n; i++) { final LabelManager labelManager = labelManagers[i]; - final Object label = labelManager.labeler.getLabel(simulationContext, personId); + final Object label = labelManager.labeler.getCurrentLabel(partitionsContext, personId); // unsafe mutation add person key.keys[i] = label; } @@ -422,11 +421,12 @@ private void addPerson(final PersonId personId) { Key cleanedKey = keyMap.get(key); if (cleanedKey == null) { - //key construction -- for add person + // key construction -- for add person cleanedKey = new Key(key); cleanedKey.calculateHashCode(); keyMap.put(cleanedKey, cleanedKey); - final BasePeopleContainer basePeopleContainer = new BasePeopleContainer(simulationContext); + final BasePeopleContainer basePeopleContainer = new BasePeopleContainer(partitionsContext, + supportRunContinuity); keyToPeopleMap.put(cleanedKey, basePeopleContainer); final LabelSet labelSet = getLabelSet(cleanedKey); labelSetInfoMap.put(cleanedKey, labelSet); @@ -448,8 +448,8 @@ private void addPerson(final PersonId personId) { } /* - * Allocates the weights array to the given size or 50% larger than the - * current size, whichever is largest. Size must be non-negative + * Allocates the weights array to the given size or 50% larger than the current + * size, whichever is largest. Size must be non-negative */ private void allocateWeights(final int size) { if (weights == null) { @@ -465,7 +465,8 @@ private void allocateWeights(final int size) { private void aquireWeightsLock() { if (weightsAreLocked) { - throw new ContractException(NucleusError.ACCESS_VIOLATION, "cannot access weighted sampling during the execution of a previous weighted sampling"); + throw new ContractException(NucleusError.ACCESS_VIOLATION, + "cannot access weighted sampling during the execution of a previous weighted sampling"); } weightsAreLocked = true; } @@ -474,7 +475,8 @@ private void aquireWeightsLock() { private void aquireEventHandlingLock() { if (eventHandlingLocked) { - throw new ContractException(NucleusError.ACCESS_VIOLATION, "cannot access event handling during the execution of a event"); + throw new ContractException(NucleusError.ACCESS_VIOLATION, + "cannot access event handling during the execution of an event"); } eventHandlingLocked = true; } @@ -484,10 +486,10 @@ private void aquireEventHandlingLock() { */ @Override public void attemptPersonAddition(final PersonId personId) { - if (filter.evaluate(simulationContext, personId)) { + if (filter.evaluate(partitionsContext, personId)) { /* - * By contract, we know that the person id should not already be a - * member of this container + * By contract, we know that the person id should not already be a member of + * this container */ addPerson(personId); } @@ -495,7 +497,6 @@ public void attemptPersonAddition(final PersonId personId) { /** * Precondition: Person must exist - * */ @Override public void attemptPersonRemoval(final PersonId personId) { @@ -529,16 +530,15 @@ public void attemptPersonRemoval(final PersonId personId) { @Override public boolean contains(final PersonId personId) { + int id = personId.getValue(); if (retainPersonKeys) { - int id = personId.getValue(); if (personToKeyMap.size() <= id) { return false; } return personToKeyMap.get(id) != null; } else { - + return personMembership.get(id); } - return getKeyForPerson(personId) != null; } @Override @@ -550,7 +550,7 @@ public boolean contains(final PersonId personId, final LabelSet labelSet) { } for (final Object dimension : labelSet.getDimensions()) { - final Integer index = dimensions.get(dimension); + final Integer index = labelerIds.get(dimension); if (index == null) { return false; } @@ -563,11 +563,11 @@ public boolean contains(final PersonId personId, final LabelSet labelSet) { } /* - * Returns the index in the weights array that is the first to meet or - * exceed the target value. Assumes a strictly increasing set of values for - * indices 0 through keyCount. Decreasing values are strictly prohibited. - * Consecutive equal values may return an ambiguous result. The target value - * must not exceed weights[peopleCount]. + * Returns the index in the weights array that is the first to meet or exceed + * the target value. Assumes a strictly increasing set of values for indices 0 + * through keyCount. Decreasing values are strictly prohibited. Consecutive + * equal values may return an ambiguous result. The target value must not exceed + * weights[peopleCount]. * */ private int findTargetIndex(final double targetValue, final int keyCount) { @@ -587,21 +587,21 @@ private int findTargetIndex(final double targetValue, final int keyCount) { } return low; } - + private Key tempKeyForLabelSets; private Key tempKeyForPeople; - private double getDefaultWeight(final SimulationContext simulationContext, final LabelSet labelSet) { + private double getDefaultWeight(final PartitionsContext partitionsContext, final LabelSet labelSet) { return 1; } private Key getKeyForLabelSet(final LabelSet labelSet) { - + final Key key = tempKeyForLabelSets; - + for (int i = 0; i < keySize; i++) { - final Object dimension = labelManagers[i].labeler.getDimension(); - final Object label = labelSet.getLabel(dimension).orElse(null); + final Object labelerId = labelManagers[i].labeler.getId(); + final Object label = labelSet.getLabel(labelerId).orElse(null); // unsafe mutation get key key.keys[i] = label; } @@ -612,8 +612,8 @@ private Key getKeyForLabelSet(final LabelSet labelSet) { } /* - * Returns the key for the personId, if the person is already a member of - * this partition. Otherwise, return null. + * Returns the key for the personId, if the person is already a member of this + * partition. Otherwise, return null. */ private Key getKeyForPerson(final PersonId personId) { @@ -633,12 +633,11 @@ private Key getKeyForPerson(final PersonId personId) { return null; } - Key key = tempKeyForPeople; final int n = labelManagers.length; for (int i = 0; i < n; i++) { final LabelManager labelManager = labelManagers[i]; - final Object label = labelManager.labeler.getLabel(simulationContext, personId); + final Object label = labelManager.labeler.getCurrentLabel(partitionsContext, personId); // unsafe mutation add person key.keys[i] = label; } @@ -664,9 +663,9 @@ private KeyIterator getKeyIterator(final LabelSet labelSet) { private LabelSet getLabelSet(final Key key) { final LabelSet.Builder builder = LabelSet.builder(); for (int i = 0; i < keySize; i++) { - final Object dimension = labelManagers[i].labeler.getDimension(); + final Object labelerId = labelManagers[i].labeler.getId(); final Object label = key.keys[i]; - builder.setLabel(dimension, label); + builder.setLabel(labelerId, label); } return builder.build(); } @@ -677,14 +676,14 @@ public List getPeople() { if (retainPersonKeys) { int n = personToKeyMap.size(); for (int i = 0; i < n; i++) { - if(personToKeyMap.get(i)!=null) { + if (personToKeyMap.get(i) != null) { result.add(peopleDataManager.getBoxedPersonId(i).get()); } - } + } } else { int n = peopleDataManager.getPersonIdLimit(); for (int i = 0; i < n; i++) { - if(personMembership.get(i)) { + if (personMembership.get(i)) { result.add(peopleDataManager.getBoxedPersonId(i).get()); } } @@ -693,11 +692,8 @@ public List getPeople() { } /** - * - * * Precondition: the population partition query must match the population * partition definition - * */ @Override public List getPeople(final LabelSet labelSet) { @@ -795,10 +791,11 @@ public void handleEvent(final Event event) { PersonId personId = null; // can the filter state have possibly changed? - final List> filterSensitivities = eventClassToFilterSensitivityMap.get(event.getClass()); + final List> filterSensitivities = eventClassToFilterSensitivityMap + .get(event.getClass()); if (filterSensitivities != null) { for (final FilterSensitivity filterSensitivity : filterSensitivities) { - final Optional optionalPersonId = filterSensitivity.requiresRefresh(simulationContext, event); + final Optional optionalPersonId = filterSensitivity.requiresRefresh(partitionsContext, event); if (optionalPersonId.isPresent()) { personId = optionalPersonId.get(); break; @@ -808,7 +805,8 @@ public void handleEvent(final Event event) { final boolean filterSensitivityFound = personId != null; // determine the sensitivity of the labelers - final List> eventlabelerSensitivities = eventClassToLabelerSensitivityMap.get(event.getClass()); + final List> eventlabelerSensitivities = eventClassToLabelerSensitivityMap + .get(event.getClass()); int labelerSensitivityCount = 0; if (eventlabelerSensitivities != null) { @@ -825,10 +823,9 @@ public void handleEvent(final Event event) { } /* - * If neither the filter nor the labelers are sensitive to the event, - * then the person id will be null and we know that the person's - * membership in this partition and possible cell assignment will not - * have changed. + * If neither the filter nor the labelers are sensitive to the event, then the + * person id will be null and we know that the person's membership in this + * partition and possible cell assignment will not have changed. */ if (personId == null) { return; @@ -837,8 +834,8 @@ public void handleEvent(final Event event) { aquireEventHandlingLock(); /* - * At this point we must have found the person. We must determine the - * key associated with their current place in this population partition. + * At this point we must have found the person. We must determine the key + * associated with their current place in this population partition. */ Key currentKeyForPerson = null; @@ -847,40 +844,78 @@ public void handleEvent(final Event event) { currentKeyForPerson = personToKeyMap.get(personId.getValue()); } } else { - if (personMembership.get(personId.getValue())) { + if (personMembership.get(personId.getValue())) { currentKeyForPerson = tempKeyForPeople; final int n = labelManagers.length; for (int i = 0; i < n; i++) { final LabelManager labelManager = labelManagers[i]; - final Object label = labelManager.labeler.getLabel(simulationContext, personId); + final Object label = labelManager.labeler.getCurrentLabel(partitionsContext, personId); // unsafe mutation add person currentKeyForPerson.keys[i] = label; } for (int i = 0; i < labelerSensitivityCount; i++) { LabelerSensitivity labelerSensitivity = labelerSensitivities[i]; final Labeler labeler = labelerSensitivityToLabelerMap.get(labelerSensitivity); - final Object pastLabel = labeler.getPastLabel(simulationContext, event); - final Object dimension = labeler.getDimension(); - final int dimensionIndex = dimensions.get(dimension); + final Object pastLabel = labeler.getPastLabel(partitionsContext, event); + final Object labelerId = labeler.getId(); + final int dimensionIndex = labelerIds.get(labelerId); // unsafe mutation currentKeyForPerson.keys[dimensionIndex] = pastLabel; } currentKeyForPerson.calculateHashCode(); - currentKeyForPerson = keyMap.get(currentKeyForPerson); + currentKeyForPerson = keyMap.get(currentKeyForPerson); + + PeopleContainer peopleContainer = keyToPeopleMap.get(currentKeyForPerson); + if (!peopleContainer.contains(personId)) { + + Key actualKey = null; + for (Key key : keyToPeopleMap.keySet()) { + peopleContainer = keyToPeopleMap.get(key); + if (peopleContainer.contains(personId)) { + actualKey = key; + break; + } + } + + StringBuilder sb = new StringBuilder(); + sb.append("["); + boolean firstElement = true; + for (int i = 0; i < keySize; i++) { + Object calculatedValue = currentKeyForPerson.keys[i]; + Object actualValue = actualKey.keys[i]; + if (!calculatedValue.equals(actualValue)) { + if (firstElement) { + firstElement = false; + } else { + sb.append(","); + } + Object labelerId = labelManagers[i].labeler.getId(); + sb.append("("); + sb.append("labelerId="); + sb.append(labelerId); + sb.append(" calculated value ='"); + sb.append(calculatedValue); + sb.append("' actual value ='"); + sb.append(actualValue); + sb.append("')"); + } + } + sb.append("]"); + throw new ContractException(PartitionError.PAST_LABEL_FAILURE, sb.toString()); + } } } /* - * If either the person is new to this partition or the the person needs - * to be re-evaluated by the filter, then we will have the filter - * evaluate the person. + * If either the person is new to this partition or the the person needs to be + * re-evaluated by the filter, then we will have the filter evaluate the person. */ final boolean personIsCurrentlyInPartition = currentKeyForPerson != null; // determine whether the person should be in the partition boolean personShouldBeInPartition; if (filterSensitivityFound) { - personShouldBeInPartition = filter.evaluate(simulationContext, personId); + personShouldBeInPartition = filter.evaluate(partitionsContext, personId); } else { personShouldBeInPartition = personIsCurrentlyInPartition; } @@ -888,22 +923,22 @@ public void handleEvent(final Event event) { if (personShouldBeInPartition) { if (personIsCurrentlyInPartition) { /* - * does their key need to be updated? if so, calculate the new - * key and move the person, updating labeler managers as well + * does their key need to be updated? if so, calculate the new key and move the + * person, updating labeler managers as well */ // personShouldBeInPartition == true // personIsCurrentlyInPartition == true if (labelerSensitivityCount > 0) { - //key construction -- (copy) handle event + // key construction -- (copy) handle event final Key newKey = new Key(currentKeyForPerson); for (int i = 0; i < labelerSensitivityCount; i++) { LabelerSensitivity labelerSensitivity = labelerSensitivities[i]; // for (final LabelerSensitivity // labelerSensitivity : labelerSensitivities) { final Labeler labeler = labelerSensitivityToLabelerMap.get(labelerSensitivity); - final Object newLabel = labeler.getLabel(simulationContext, personId); - final Object dimension = labeler.getDimension(); - final int dimensionIndex = dimensions.get(dimension); + final Object newLabel = labeler.getCurrentLabel(partitionsContext, personId); + final Object labelerId = labeler.getId(); + final int dimensionIndex = labelerIds.get(labelerId); final LabelManager labelManager = labelManagers[dimensionIndex]; labelManager.removeLabel(newKey.keys[dimensionIndex]); labelManager.addLabel(newLabel); @@ -922,14 +957,13 @@ public void handleEvent(final Event event) { // personIsCurrentlyInPartition == false /* - * Generate a full key for the person and add them into the - * partition cell and then exit much like an add person. + * Generate a full key for the person and add them into the partition cell and + * then exit much like an add person. * - * We know that the person is not currently a member of this - * population partition and thus it is safe to add them. + * We know that the person is not currently a member of this population + * partition and thus it is safe to add them. * */ - addPerson(personId); } } else { @@ -985,7 +1019,7 @@ private void move(final Key currentKey, final Key newKey, final PersonId personI if (cleanedNewKey == null) { cleanedNewKey = newKey; keyMap.put(cleanedNewKey, cleanedNewKey); - keyToPeopleMap.put(cleanedNewKey, new BasePeopleContainer(simulationContext)); + keyToPeopleMap.put(cleanedNewKey, new BasePeopleContainer(partitionsContext, supportRunContinuity)); final LabelSet labelSet = getLabelSet(cleanedNewKey); labelSetInfoMap.put(cleanedNewKey, labelSet); } @@ -998,9 +1032,9 @@ private void move(final Key currentKey, final Key newKey, final PersonId personI labelSetInfoMap.remove(currentKey); } /* - * We use unsafe add since we know that the person id is not already a - * member of the people container associated with the new key as it is - * not equal to the old key + * We use unsafe add since we know that the person id is not already a member of + * the people container associated with the new key as it is not equal to the + * old key */ keyToPeopleMap.get(cleanedNewKey).unsafeAdd(personId); @@ -1011,7 +1045,7 @@ private void move(final Key currentKey, final Key newKey, final PersonId personI private void releaseWeightsLock() { if (!weightsAreLocked) { - + throw new RuntimeException("cannot release sample locking when lock not present"); } weightsAreLocked = false; @@ -1019,7 +1053,8 @@ private void releaseWeightsLock() { private void releaseEventHandlingLock() { if (!eventHandlingLocked) { - throw new ContractException(NucleusError.ACCESS_VIOLATION, "cannot release event handling lock when the lock is not present"); + throw new ContractException(NucleusError.ACCESS_VIOLATION, + "cannot release event handling lock when the lock is not present"); } eventHandlingLocked = false; } @@ -1027,8 +1062,8 @@ private void releaseEventHandlingLock() { /** * Returns a randomly chosen person identifier from the partition consistent * with the partition sampler info. Note that the sampler must be consistent - * with the partition definition used to create this population partition. - * No precondition tests will be performed. + * with the partition definition used to create this population partition. No + * precondition tests will be performed. */ @Override public Optional samplePartition(final PartitionSampler partitionSampler) { @@ -1037,7 +1072,8 @@ public Optional samplePartition(final PartitionSampler partitionSample } RandomGenerator randomGenerator; - final RandomNumberGeneratorId randomNumberGeneratorId = partitionSampler.getRandomNumberGeneratorId().orElse(null); + final RandomNumberGeneratorId randomNumberGeneratorId = partitionSampler.getRandomNumberGeneratorId() + .orElse(null); if (randomNumberGeneratorId == null) { randomGenerator = stochasticsDataManager.getRandomGenerator(); } else { @@ -1048,7 +1084,8 @@ public Optional samplePartition(final PartitionSampler partitionSample final LabelSet labelSet = partitionSampler.getLabelSet().orElse(LabelSet.builder().build()); - final LabelSetWeightingFunction labelSetWeightingFunction = partitionSampler.getLabelSetWeightingFunction().orElse(this::getDefaultWeight); + final LabelSetWeightingFunction labelSetWeightingFunction = partitionSampler.getLabelSetWeightingFunction() + .orElse(this::getDefaultWeight); Key selectedKey = null; @@ -1061,17 +1098,16 @@ public Optional samplePartition(final PartitionSampler partitionSample keyIterator = getKeyIterator(labelSet); allocateWeights(keyIterator.size()); /* - * Initialize the sum of the weights to zero and set the index in - * the weights and weightedKeys to zero. + * Initialize the sum of the weights to zero and set the index in the weights + * and weightedKeys to zero. */ - double sum = 0; int weightsLength = 0; while (keyIterator.hasNext()) { final Key fullKey = keyIterator.next(); final LabelSet fullLableSet = labelSetInfoMap.get(fullKey); final PeopleContainer peopleContainer = keyToPeopleMap.get(fullKey); - double weight = labelSetWeightingFunction.getWeight(simulationContext, fullLableSet); + double weight = labelSetWeightingFunction.getWeight(partitionsContext, fullLableSet); if (fullKey != keyForExcludedPersonId) { weight *= peopleContainer.size(); } else { @@ -1095,14 +1131,13 @@ public Optional samplePartition(final PartitionSampler partitionSample } /* - * If at least one identifierKey was accepted for selection, then we - * attempt a random selection. + * If at least one identifierKey was accepted for selection, then we attempt a + * random selection. */ if (weightsLength > 0) { /* - * Although the individual weights may have been finite, if the - * sum of those weights is not finite no legitimate selection - * can be made + * Although the individual weights may have been finite, if the sum of those + * weights is not finite no legitimate selection can be made */ if (!Double.isFinite(sum)) { throw new ContractException(PartitionError.MALFORMED_PARTITION_SAMPLE_WEIGHTING_FUNCTION); @@ -1121,14 +1156,14 @@ public Optional samplePartition(final PartitionSampler partitionSample } /* - * We know that the selected key will correspond to a non-empty people - * container that includes at least one person who is not the excluded - * person. This is due to 1) People containers that become empty are - * removed and 2) the key selection algorithm above will adjust the - * weight of a key to zero if the corresponding container contains only - * the excluded person. Zero weighted keys are ignored and thus the - * selected key cannot be associated with the excluded person alone. - * Therefore, the person selection process will eventually terminate. + * We know that the selected key will correspond to a non-empty people container + * that includes at least one person who is not the excluded person. This is due + * to 1) People containers that become empty are removed and 2) the key + * selection algorithm above will adjust the weight of a key to zero if the + * corresponding container contains only the excluded person. Zero weighted keys + * are ignored and thus the selected key cannot be associated with the excluded + * person alone. Therefore, the person selection process will eventually + * terminate. */ final PeopleContainer peopleContainer = keyToPeopleMap.get(selectedKey); PersonId selectedPerson = null; @@ -1148,11 +1183,25 @@ public Optional samplePartition(final PartitionSampler partitionSample @Override public boolean validateLabelSetInfo(final LabelSet labelSet) { for (final Object dimension : labelSet.getDimensions()) { - if (!dimensions.containsKey(dimension)) { + if (!labelerIds.containsKey(dimension)) { return false; } } return true; } + @Override + public Optional getPersonValue(LabelSetFunction labelSetFunction, PersonId personId) { + if (isEmpty()) { + return Optional.empty(); + } + Key keyForPerson = getKeyForPerson(personId); + if (keyForPerson == null) { + return Optional.empty(); + } + LabelSet labelSet = labelSetInfoMap.get(keyForPerson); + T value = labelSetFunction.getValue(partitionsContext, labelSet); + return Optional.of(value); + } + } \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/Tuplator.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/Tuplator.java new file mode 100644 index 000000000..bd3cf99e1 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/Tuplator.java @@ -0,0 +1,98 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support; + +import java.util.ArrayList; +import java.util.List; + +/** + * Utility class for generating tuples from a fixed set of finite ranges of the + * form [0,n). Uses a standard builder pattern for construction. + */ +public final class Tuplator { + private final int[] moduli; + private final int[] dimensions; + private final int size; + + private Tuplator(List dimensionSizes) { + dimensions = new int[dimensionSizes.size()]; + moduli = new int[dimensionSizes.size()]; + + int count = 1; + for (int i = 0; i < dimensionSizes.size(); i++) { + int dimSize = dimensionSizes.get(i); + dimensions[i] = dimSize; + moduli[i] = count; + count *= dimSize; + } + size = count; + } + + /** + * Returns the number of dimensions in this {@link Tuplator}. + */ + public int dimensions() { + return dimensions.length; + } + + /** + * Returns the number of tuples that can be generated by this {@link Tuplator} + */ + public int size() { + return size; + } + + /** + * Fills the given tuple with the values that correspond to the given index. + * + * @throws IndexOutOfBoundsException + *
      + *
    • if index < 0
    • + *
    • if index >= size()
    • + *
    + * @throws IllegalArgumentException + *
      + *
    • if the tuple is null
    • + *
    • if the tuple's length is not equal to + * dimensions()
    • + *
    + */ + public void fillTuple(int index, int[] tuple) { + if ((index < 0) || (index >= size)) { + throw new IndexOutOfBoundsException("index out of bounds"); + } + + if (tuple == null) { + throw new IllegalArgumentException("null array"); + } + + if (tuple.length != dimensions.length) { + throw new IllegalArgumentException("wrong number of dimensions"); + } + + for (int i = 0; i < dimensions.length; i++) { + tuple[i] = (index / moduli[i]) % dimensions[i]; + } + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private List dimensionSizes = new ArrayList<>(); + + private Builder() { + } + + public Tuplator build() { + return new Tuplator(dimensionSizes); + } + + public Builder addDimension(int dimensionSize) { + if (dimensionSize <= 0) { + throw new IllegalArgumentException("Non positive dimension size"); + } + dimensionSizes.add(dimensionSize); + return this; + } + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/containers/BasePeopleContainer.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/containers/BasePeopleContainer.java new file mode 100644 index 000000000..6b62ccadf --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/containers/BasePeopleContainer.java @@ -0,0 +1,146 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support.containers; + +import java.util.List; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; + +/** + * Implementor of PeopleContainer that acts as a dynamic switching mechanism + * between the two lower-level PeopleContainer implementors + */ +public class BasePeopleContainer implements PeopleContainer { + /* + * Enumeration for the two ways that people are stored in a partition + */ + private static enum PeopleContainerMode { + INTSET, TREE_BIT_SET; + } + + private static final int TREE_BIT_SET_SLOW_THRESHOLD = 28; + + private static final int INT_SET_THRESHOLD = 33; + + private PeopleContainerMode mode; + + private final PeopleDataManager peopleDataManager; + + private PeopleContainer internalPeopleContainer; + + private final boolean supportRunContinuity; + + public BasePeopleContainer(PartitionsContext partitionsContext, boolean supportRunContinuity) { + this.peopleDataManager = partitionsContext.getDataManager(PeopleDataManager.class); + this.supportRunContinuity = supportRunContinuity; + + if (supportRunContinuity) { + internalPeopleContainer = new TreeBitSetPeopleContainer(peopleDataManager); + mode = PeopleContainerMode.TREE_BIT_SET; + } else { + mode = PeopleContainerMode.INTSET; + internalPeopleContainer = new IntSetPeopleContainer(); + } + + } + + /* + * Switches the internal container between BooleanPeopleContainer and + * SetPeopleContainer as needed whenever the appropriate threshold has been + * crossed. If the size of the container is less than 0.5% of the total world + * population, then the SetPeopleContainer should be chosen. If the size of the + * container is greater than 1% of the total world population, then the + * BooleanPeopleContainer should be chosen. By setting two separate thresholds, + * we avoid modality thrash. + * + * If supportRunContinuity is true, then the mode remains fixed at TREE_BIT_SET + * without regard to the relative size of the cell + */ + private void determineMode(int size) { + if (supportRunContinuity) { + return; + } + switch (mode) { + + case TREE_BIT_SET: + if (size <= peopleDataManager.getPersonIdLimit() / INT_SET_THRESHOLD) { + mode = PeopleContainerMode.INTSET; + List people = internalPeopleContainer.getPeople(); + internalPeopleContainer = new IntSetPeopleContainer(); + for (PersonId personId : people) { + /* + * We use unsafe add as it faster and we know that the person id cannot already + * be contained + */ + internalPeopleContainer.unsafeAdd(personId); + } + } + break; + case INTSET: + if (size >= peopleDataManager.getPersonIdLimit() / TREE_BIT_SET_SLOW_THRESHOLD) { + mode = PeopleContainerMode.TREE_BIT_SET; + List people = internalPeopleContainer.getPeople(); + internalPeopleContainer = new TreeBitSetPeopleContainer(peopleDataManager); + for (PersonId personId : people) { + // we use the safe add as it is faster + internalPeopleContainer.safeAdd(personId); + } + } + break; + default: + throw new RuntimeException("unhandled mode " + mode); + } + } + + @Override + public List getPeople() { + return internalPeopleContainer.getPeople(); + } + + @Override + public boolean safeAdd(PersonId personId) { + boolean result = internalPeopleContainer.safeAdd(personId); + if (result) { + determineMode(size()); + } + return result; + } + + @Override + public boolean unsafeAdd(PersonId personId) { + boolean result = internalPeopleContainer.unsafeAdd(personId); + if (result) { + determineMode(size()); + } + return result; + } + + @Override + public boolean remove(PersonId personId) { + boolean result = internalPeopleContainer.remove(personId); + determineMode(size()); + return result; + } + + @Override + public int size() { + return internalPeopleContainer.size(); + } + + @Override + public boolean contains(PersonId personId) { + return internalPeopleContainer.contains(personId); + } + + /* + * Returns a randomly selected person if this container has any people. Returns + * null otherwise. + */ + @Override + public PersonId getRandomPersonId(RandomGenerator randomGenerator) { + return internalPeopleContainer.getRandomPersonId(randomGenerator); + } + +} \ No newline at end of file diff --git a/gcm3/src/main/java/plugins/partitions/support/containers/IntSetPeopleContainer.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/containers/IntSetPeopleContainer.java similarity index 84% rename from gcm3/src/main/java/plugins/partitions/support/containers/IntSetPeopleContainer.java rename to gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/containers/IntSetPeopleContainer.java index 7dd847e28..9f7af00ff 100644 --- a/gcm3/src/main/java/plugins/partitions/support/containers/IntSetPeopleContainer.java +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/containers/IntSetPeopleContainer.java @@ -1,23 +1,21 @@ -package plugins.partitions.support.containers; +package gov.hhs.aspr.ms.gcm.plugins.partitions.support.containers; import java.util.ArrayList; import java.util.List; import org.apache.commons.math3.random.RandomGenerator; -import plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; /** * PeopleContainer implementor that uses hash-bucketed ArrayLists and an * array-based tree to contain the people. Uses ~ 45 bits per person contained. - * - * @author Shawn Hatch */ public final class IntSetPeopleContainer implements PeopleContainer { /** - * The general best practice bucket depth for ArrayIntSets containing - * millions of entries. + * The general best practice bucket depth for ArrayIntSets containing millions + * of entries. */ public final static float DEFAULT_TARGET_DEPTH = 80; @@ -29,10 +27,10 @@ public final class IntSetPeopleContainer implements PeopleContainer { private int[] tree; /* - * An array that is the same length as the values array that tracks the - * maximum size each ArrayList instance in values has reached during its - * life-span. This value is used to trigger an occasional rebuild of these - * ArrayLists to reduce instance size. + * An array that is the same length as the values array that tracks the maximum + * size each ArrayList instance in values has reached during its life-span. This + * value is used to trigger an occasional rebuild of these ArrayLists to reduce + * instance size. */ // private int[] maxSizes; private static enum MaxMode { @@ -93,7 +91,7 @@ public void setMaxSize(int index, int size) { } break; default: - + throw new RuntimeException("unhandled case " + maxMode); } @@ -108,7 +106,7 @@ public void setMaxSize(int index, int size) { sMaxSizes[index] = (short) size; break; default: - + throw new RuntimeException("unhandled case " + maxMode); } } @@ -124,7 +122,7 @@ public int getMaxSize(int index) { case SHORT: return sMaxSizes[index]; default: - + throw new RuntimeException("unhandled case " + maxMode); } } @@ -138,25 +136,24 @@ public int getMaxSize(int index) { private int size; /* - * The targeted list depth that is used to determine the number of - * buckets(i.e. the length of the values array) in this set. + * The targeted list depth that is used to determine the number of buckets(i.e. + * the length of the values array) in this set. */ private final float targetDepth; /** - * Constructs an ArrayIntSet having the DEFAULT_TARGET_DEPTH and tolerance - * of duplicate values. + * Constructs an ArrayIntSet having the DEFAULT_TARGET_DEPTH and tolerance of + * duplicate values. * - * @throws IllegalArgumentException - *
  • the target depth is not positive + * @throws IllegalArgumentException the target depth is not positive */ public IntSetPeopleContainer() { this.targetDepth = DEFAULT_TARGET_DEPTH; } /* - * Grows the values and maxSizes arrays to achieve an average bucket depth - * that closer to the target bucket depth. + * Grows the values and maxSizes arrays to achieve an average bucket depth that + * closer to the target bucket depth. */ @SuppressWarnings("unchecked") private void grow() { @@ -194,15 +191,14 @@ private void rebuild(int newSize) { */ List[] newValues = new List[newSize]; /* - * Rebuild the maxSizes array to the correct length. The old values in - * the maxSizes array can be forgotten. + * Rebuild the maxSizes array to the correct length. The old values in the + * maxSizes array can be forgotten. */ // maxSizes = new int[newValues.length]; maxSizeManager = new MaxSizeManager(newValues.length); /* * Place the values from the old values array into the new values array. */ - tree = new int[newSize * 2]; for (int i = 0; i < buckets.length; i++) { @@ -210,15 +206,14 @@ private void rebuild(int newSize) { if (list != null) { for (PersonId value : list) { /* - * Since the length of the values array is always a power of - * 2, we can use a bit-wise math trick to calculate the - * modulus of value with values.length to derive the index - * of the bucket where the value should be stored. + * Since the length of the values array is always a power of 2, we can use a + * bit-wise math trick to calculate the modulus of value with values.length to + * derive the index of the bucket where the value should be stored. */ int index = value.getValue() & (newValues.length - 1); /* - * Get the list where the value should be stored or create - * it if it does not yet exist. + * Get the list where the value should be stored or create it if it does not yet + * exist. */ List newList = newValues[index]; if (newList == null) { @@ -238,16 +233,15 @@ private void rebuild(int newSize) { newList.add(value); } /* - * Null out the ArrayList instance to encourage the GC to - * collect the list now. + * Null out the ArrayList instance to encourage the GC to collect the list now. * */ buckets[i] = null; } } /* - * We no longer need the old values array, so we replace it with the new - * values array. + * We no longer need the old values array, so we replace it with the new values + * array. */ buckets = newValues; /* @@ -268,8 +262,8 @@ public boolean safeAdd(PersonId personId) { return false; } /* - * We have show that the person id is not contained so we can invoke the - * unsafe add + * We have show that the person id is not contained so we can invoke the unsafe + * add */ return unsafeAdd(personId); } @@ -281,8 +275,8 @@ public boolean unsafeAdd(PersonId personId) { grow(); } /* - * The bucket index is value % values.length, but this is a bit faster - * since we know the values array length is a power of two. + * The bucket index is value % values.length, but this is a bit faster since we + * know the values array length is a power of two. */ int index = personId.getValue() & (buckets.length - 1); @@ -334,8 +328,8 @@ public boolean remove(PersonId personId) { return false; } /* - * The bucket index is value % values.length, but this is a bit faster - * since we know the values array length is a power of two. + * The bucket index is value % values.length, but this is a bit faster since we + * know the values array length is a power of two. */ int index = personId.getValue() & (buckets.length - 1); /* @@ -350,11 +344,9 @@ public boolean remove(PersonId personId) { if (list.remove(personId)) { size--; /* - * If the list is now less than half its maxSize in the past, then - * we should rebuild the list and record the new maxSize for the - * list. + * If the list is now less than half its maxSize in the past, then we should + * rebuild the list and record the new maxSize for the list. */ - // decrement the values int the tree int treeIndex = index + buckets.length; while (treeIndex > 0) { @@ -372,8 +364,8 @@ public boolean remove(PersonId personId) { } } /* - * If the averageDepth is less than half the target depth then we - * should shrink. (size/values.length)*2 getPeople() { @Override public PersonId getRandomPersonId(RandomGenerator randomGenerator) { - - if(size==0) { + + if (size == 0) { return null; } int targetCount = randomGenerator.nextInt(size); /* - * Find the mid point of the tree. Think of the tree array as a triangle - * with a single root node at the top. This will be the first array - * element in the last row(last half) in the tree. This is the row that - * maps to the blocks in the bitset. + * Find the mid point of the tree. Think of the tree array as a triangle with a + * single root node at the top. This will be the first array element in the last + * row(last half) in the tree. This is the row that maps to the blocks in the + * bitset. */ int midTreeIndex = buckets.length; int treeIndex = 1; /* - * Walk downward in the tree. If we move to the right, we have to reduce - * the target value. + * Walk downward in the tree. If we move to the right, we have to reduce the + * target value. */ while (treeIndex < midTreeIndex) { // move to the left child @@ -432,8 +424,8 @@ public PersonId getRandomPersonId(RandomGenerator randomGenerator) { } } /* - * We have arrived at the element of the tree array that corresponds to - * desired bucket + * We have arrived at the element of the tree array that corresponds to desired + * bucket */ List targetList = buckets[treeIndex - midTreeIndex]; return targetList.get(targetCount); diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/containers/PeopleContainer.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/containers/PeopleContainer.java new file mode 100644 index 000000000..f9918b8c5 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/containers/PeopleContainer.java @@ -0,0 +1,54 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support.containers; + +import java.util.List; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; + +/** + * Interface for abstracting the details of how people ids are stored as either + * a Set or a Boolean container. + */ +public interface PeopleContainer { + + /** + * Returns a list of the people in the set with no duplicates + */ + public List getPeople(); + + /** + * Returns true if and only if the person was successfully added. + */ + public boolean safeAdd(PersonId personId); + + /** + * Returns true if and only if the person was successfully added. To use unsafe + * adding, the caller MUST guarantee that the person id being added does not + * already exist in this people container. Depending on the implementor, this + * can reduce the time for addition significantly. + */ + public boolean unsafeAdd(PersonId personId); + + /** + * Returns true if and only if the person was successfully removed. + * Precondition: The person cannot be null. + */ + public boolean remove(PersonId personId); + + /** + * Returns the number of people in this container + */ + public int size(); + + /** + * Returns true if and only if the person is contained. + */ + public boolean contains(PersonId personId); + + /** + * Returns a randomly selected person if this container has any people. Returns + * null otherwise. Precondition : random generator cannot be null + */ + public PersonId getRandomPersonId(RandomGenerator randomGenerator); +} \ No newline at end of file diff --git a/gcm3/src/main/java/plugins/partitions/support/containers/TreeBitSetPeopleContainer.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/containers/TreeBitSetPeopleContainer.java similarity index 89% rename from gcm3/src/main/java/plugins/partitions/support/containers/TreeBitSetPeopleContainer.java rename to gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/containers/TreeBitSetPeopleContainer.java index 18d303fa1..b02299340 100644 --- a/gcm3/src/main/java/plugins/partitions/support/containers/TreeBitSetPeopleContainer.java +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/containers/TreeBitSetPeopleContainer.java @@ -1,4 +1,4 @@ -package plugins.partitions.support.containers; +package gov.hhs.aspr.ms.gcm.plugins.partitions.support.containers; import java.util.ArrayList; import java.util.BitSet; @@ -7,19 +7,15 @@ import org.apache.commons.math3.random.RandomGenerator; import org.apache.commons.math3.util.FastMath; -import plugins.partitions.support.PartitionError; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionError; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; import util.errors.ContractException; - /** - * * PeopleContainer implementor that uses a BitSet to record a boolean value of * true for each person contained. Uses ~1.3 bits for each person in the ENTIRE * POPULATION OF THE SIMULATION. - * - * @author Shawn Hatch */ public class TreeBitSetPeopleContainer implements PeopleContainer { @@ -39,16 +35,14 @@ public class TreeBitSetPeopleContainer implements PeopleContainer { private final PeopleDataManager peopleDataManager; - /** * Constructs the people container * - * @throws ContractException - *
  • {@linkplain PartitionError#NULL_PERSON_DATA_VIEW} if the person data view is null
  • - * + * @throws ContractException {@linkplain PartitionError#NULL_PERSON_DATA_VIEW} + * if the person data view is null */ public TreeBitSetPeopleContainer(PeopleDataManager personDataManger) { - if(personDataManger==null) { + if (personDataManger == null) { throw new ContractException(PartitionError.NULL_PERSON_DATA_VIEW); } blockSize = 63; @@ -255,8 +249,8 @@ private void increment(int index) { public boolean remove(PersonId personId) { int value = personId.getValue(); /* - * If the person is not contained, then don't try to remove them. This - * protects us from removals that are >= exclusizeMaxId. + * If the person is not contained, then don't try to remove them. This protects + * us from removals that are >= exclusizeMaxId. */ if (bitSet.get(value)) { bitSet.set(value, false); @@ -304,37 +298,36 @@ public boolean contains(PersonId personId) { @Override public PersonId getRandomPersonId(RandomGenerator randomGenerator) { - if(size==0) { + if (size == 0) { return null; } - + int index = randomGenerator.nextInt(size); /* - * We need to use an integer that is at least one, so we add one to the - * selected index. We will reduce this amount until it reaches zero. + * We need to use an integer that is at least one, so we add one to the selected + * index. We will reduce this amount until it reaches zero. */ int targetCount = index + 1; /* - * Find the mid point of the tree. Think of the tree array as a triangle - * with a single root node at the top. This will be the first array - * element in the last row(last half) in the tree. This is the row that - * maps to the blocks in the bitset. + * Find the mid point of the tree. Think of the tree array as a triangle with a + * single root node at the top. This will be the first array element in the last + * row(last half) in the tree. This is the row that maps to the blocks in the + * bitset. */ int treeIndex = 1; /* - * Walk downward in the tree. If we move to the right, we have to reduce - * the target value. + * Walk downward in the tree. If we move to the right, we have to reduce the + * target value. */ while (treeIndex < blockStartingIndex) { // move to the left child treeIndex = treeIndex << 1; /* - * If the left child is less than the target count, then reduce the - * target count by the number in the left child and move to the - * right child + * If the left child is less than the target count, then reduce the target count + * by the number in the left child and move to the right child */ int nodeValue = get(treeIndex); if (nodeValue < targetCount) { @@ -343,16 +336,15 @@ public PersonId getRandomPersonId(RandomGenerator randomGenerator) { } } /* - * We have arrived at the element of the tree array that corresponds to - * the desired block in the bitset. We will need to determine the - * positions to scan in the bitset + * We have arrived at the element of the tree array that corresponds to the + * desired block in the bitset. We will need to determine the positions to scan + * in the bitset */ // int bitSetStartIndex = (treeIndex - midTreeIndex) << BLOCK_POWER; int bitSetStartIndex = (treeIndex - blockStartingIndex) * blockSize; int bitSetStopIndex = bitSetStartIndex + blockSize; /* - * Finally, we scan the bits and reduce the target count until it - * reaches zero. + * Finally, we scan the bits and reduce the target count until it reaches zero. */ for (int i = bitSetStartIndex; i < bitSetStopIndex; i++) { if (bitSet.get(i)) { diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/filters/AndFilter.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/filters/AndFilter.java new file mode 100644 index 000000000..7ce051d55 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/filters/AndFilter.java @@ -0,0 +1,89 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters; + +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.FilterSensitivity; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +@Immutable +public final class AndFilter extends Filter { + final Filter a; + final Filter b; + + /** + * Constructs a filter that is the conjunction of two filters. + * + * @throws ContractException {@linkplain PartitionError#NULL_FILTER} if either + * filter is null + */ + public AndFilter(Filter a, Filter b) { + if (a == null) { + throw new ContractException(PartitionError.NULL_FILTER); + } + if (b == null) { + throw new ContractException(PartitionError.NULL_FILTER); + } + + this.a = a; + this.b = b; + } + + public Filter getFirstFilter() { + return a; + } + + public Filter getSecondFilter() { + return b; + } + + @Override + public boolean evaluate(PartitionsContext partitionsContext, PersonId personId) { + return a.evaluate(partitionsContext, personId) && b.evaluate(partitionsContext, personId); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("AndFilter [a="); + builder.append(a); + builder.append(", b="); + builder.append(b); + builder.append("]"); + return builder.toString(); + } + + @Override + public Set> getFilterSensitivities() { + Set> result = a.getFilterSensitivities(); + result.addAll(b.getFilterSensitivities()); + return result; + } + + @Override + public void validate(PartitionsContext partitionsContext) { + a.validate(partitionsContext); + b.validate(partitionsContext); + } + + @Override + public int hashCode() { + return a.hashCode() + b.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof AndFilter)) { + return false; + } + AndFilter other = (AndFilter) obj; + return a.equals(other.a) && b.equals(other.b) || a.equals(other.b) && b.equals(other.a); + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/filters/FalseFilter.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/filters/FalseFilter.java new file mode 100644 index 000000000..3dd7e058c --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/filters/FalseFilter.java @@ -0,0 +1,57 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters; + +import java.util.LinkedHashSet; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.FilterSensitivity; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import net.jcip.annotations.Immutable; + +/** + * A filter that passes no people. Used for concatenating filters into an + * OrFilter over a loop of filters that might be empty. OR operating over an + * empty set should always be false. + */ +@Immutable +public final class FalseFilter extends Filter { + + @Override + public boolean evaluate(PartitionsContext partitionsContext, PersonId personId) { + return false; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("FalseFilter []"); + return builder.toString(); + } + + @Override + public Set> getFilterSensitivities() { + return new LinkedHashSet<>(); + } + + @Override + public void validate(PartitionsContext partitionsContext) { + + } + + @Override + public int hashCode() { + return 0; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof FalseFilter)) { + return false; + } + return true; + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/filters/Filter.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/filters/Filter.java new file mode 100644 index 000000000..75f536429 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/filters/Filter.java @@ -0,0 +1,86 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters; + +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.FilterSensitivity; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import util.errors.ContractException; + +public abstract class Filter { + + protected Filter() { + } + + /** + * Returns a filter that is the conjunction of this and the given filter. + * + * @throws ContractException {@linkplain PartitionError#NULL_FILTER} if the + * filter is null + */ + public final Filter and(Filter filter) { + return new AndFilter(this, filter); + } + + /** + * Returns a filter that is the disjunction of this and the given filter. + * + * @throws ContractException {@linkplain PartitionError#NULL_FILTER} if the + * filter is null + */ + public final Filter or(Filter filter) { + return new OrFilter(this, filter); + } + + /** + * Returns a filter that is the negation of this filter. + */ + public final Filter not() { + return new NotFilter(this); + } + + /** + * Evaluates the person against the filter. + * + * @throws ContractException + *
      + *
    • {@linkplain NucleusError#NULL_SIMULATION_CONTEXT} + * if the context is null
    • + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person id is unknown
    • + *
    + */ + public abstract boolean evaluate(PartitionsContext partitionsContext, PersonId personId); + + /** + * Validates the filter from the given context. Preconditions: the context is + * not null + */ + public abstract void validate(PartitionsContext partitionsContext); + + /** + * Returns the filter sensitivities + */ + public abstract Set> getFilterSensitivities(); + + /** + * Filters are equal if they represent the same logical operation + */ + @Override + public abstract int hashCode(); + + /** + * Filters are equal if they represent the same logical operation + */ + @Override + public abstract boolean equals(Object obj); + + @Override + public abstract String toString(); + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/filters/NotFilter.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/filters/NotFilter.java new file mode 100644 index 000000000..d90e8334f --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/filters/NotFilter.java @@ -0,0 +1,80 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters; + +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.FilterSensitivity; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +@Immutable +public final class NotFilter extends Filter { + final Filter a; + + /** + * Constructs a filter that negates another filter. + * + * @throws ContractException {@linkplain PartitionError#NULL_FILTER} if the + * filter is null + */ + public NotFilter(Filter a) { + if (a == null) { + throw new ContractException(PartitionError.NULL_FILTER); + } + this.a = a; + } + + public Filter getSubFilter() { + return a; + } + + @Override + public void validate(PartitionsContext partitionsContext) { + a.validate(partitionsContext); + } + + @Override + public boolean evaluate(PartitionsContext partitionsContext, PersonId personId) { + return !a.evaluate(partitionsContext, personId); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("NotFilter [a="); + builder.append(a); + builder.append("]"); + return builder.toString(); + } + + @Override + public Set> getFilterSensitivities() { + return a.getFilterSensitivities(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + a.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof NotFilter)) { + return false; + } + NotFilter other = (NotFilter) obj; + if (!a.equals(other.a)) { + return false; + } + return true; + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/filters/OrFilter.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/filters/OrFilter.java new file mode 100644 index 000000000..f9e12c87c --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/filters/OrFilter.java @@ -0,0 +1,88 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters; + +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.FilterSensitivity; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +@Immutable +public final class OrFilter extends Filter { + final Filter a; + final Filter b; + + /** + * Constructs a filter that is the disjunction of two filters. + * + * @throws ContractException {@linkplain PartitionError#NULL_FILTER} if either + * filter is null + */ + public OrFilter(Filter a, Filter b) { + if (a == null) { + throw new ContractException(PartitionError.NULL_FILTER); + } + if (b == null) { + throw new ContractException(PartitionError.NULL_FILTER); + } + this.a = a; + this.b = b; + } + + public Filter getFirstFilter() { + return a; + } + + public Filter getSecondFilter() { + return b; + } + + @Override + public boolean evaluate(PartitionsContext partitionsContext, PersonId personId) { + return a.evaluate(partitionsContext, personId) || b.evaluate(partitionsContext, personId); + } + + @Override + public Set> getFilterSensitivities() { + Set> result = a.getFilterSensitivities(); + result.addAll(b.getFilterSensitivities()); + return result; + } + + @Override + public void validate(PartitionsContext partitionsContext) { + a.validate(partitionsContext); + b.validate(partitionsContext); + } + + @Override + public int hashCode() { + return a.hashCode() + b.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof OrFilter)) { + return false; + } + OrFilter other = (OrFilter) obj; + return a.equals(other.a) && b.equals(other.b) || a.equals(other.b) && b.equals(other.a); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("OrFilter [a="); + builder.append(a); + builder.append(", b="); + builder.append(b); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/filters/TrueFilter.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/filters/TrueFilter.java new file mode 100644 index 000000000..310e518a2 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/filters/TrueFilter.java @@ -0,0 +1,57 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters; + +import java.util.LinkedHashSet; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.FilterSensitivity; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import net.jcip.annotations.Immutable; + +/** + * A filter that passes all people. Used for concatenating filters into an + * AndFilter over a loop of filters that might be empty. AND operating over an + * empty set should always be true. + */ +@Immutable +public final class TrueFilter extends Filter { + + @Override + public boolean evaluate(PartitionsContext partitionsContext, PersonId personId) { + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("TrueFilter []"); + return builder.toString(); + } + + @Override + public Set> getFilterSensitivities() { + return new LinkedHashSet<>(); + } + + @Override + public void validate(PartitionsContext partitionsContext) { + + } + + @Override + public int hashCode() { + return 1; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof TrueFilter)) { + return false; + } + return true; + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/FunctionalAttributeLabeler.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/FunctionalAttributeLabeler.java new file mode 100644 index 000000000..4d6356486 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/FunctionalAttributeLabeler.java @@ -0,0 +1,26 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport; + +import java.util.function.Function; + +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support.AttributeId; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support.AttributeLabeler; + +/** + * A Function-based implementor of AttributeLabeler. Not suitable for + * serialization. + */ +public final class FunctionalAttributeLabeler extends AttributeLabeler { + private final Function labelingFunction; + + public FunctionalAttributeLabeler(AttributeId attributeId, Function labelingFunction) { + super(attributeId); + + this.labelingFunction = labelingFunction; + } + + @Override + protected Object getLabelFromValue(Object value) { + return labelingFunction.apply(value); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/PartitionsTestPluginFactory.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/PartitionsTestPluginFactory.java new file mode 100644 index 000000000..2a9e60e02 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/PartitionsTestPluginFactory.java @@ -0,0 +1,316 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.partitions.PartitionsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.partitions.datamanagers.PartitionsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.AttributesPlugin; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.AttributesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.AttributesPluginId; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support.AttributeError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support.TestAttributeId; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePlugin; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeoplePluginData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonRange; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.StochasticsError; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import util.errors.ContractException; + +/** + * A static test support class for the {@linkplain PartitionsPlugin}. Provides + * convenience methods for obtaining standarized PluginData for the listed + * Plugin. + *

    + * Also contains factory methods to obtain a list of plugins that is the minimal + * set needed to adequately test this Plugin that can be utilized with + *

    + * {@link TestSimulation#execute} + */ +public class PartitionsTestPluginFactory { + + private PartitionsTestPluginFactory() { + } + + private static class Data { + private AttributesPluginData attributesPluginData; + private Plugin partitionsPlugin; + private PeoplePluginData peoplePluginData; + private StochasticsPluginData stochasticsPluginData; + private TestPluginData testPluginData; + + private Data(int initialPopulation, long seed, TestPluginData testPluginData) { + this.attributesPluginData = getStandardAttributesPluginData(); + this.partitionsPlugin = getStandardPartitionsPlugin(); + this.peoplePluginData = getStandardPeoplePluginData(initialPopulation); + this.stochasticsPluginData = getStandardStochasticsPluginData(seed); + this.testPluginData = testPluginData; + } + } + + /** + * Factory class that facilitates the building of {@linkplain PluginData} with + * the various setter methods. + */ + public static class Factory { + private Data data; + + private Factory(Data data) { + this.data = data; + } + + /** + * Method that will get the PluginData for the Attributes, Partitions, People, + * Stochastic and Test Plugins and use the respective PluginData to build + * Plugins Returns a list of plugins containing a Attributes, Partitions, + * People, Stochastic and Test Plugin built from the contributed PluginDatas + *
      + *
    • AttributesPlugin is defaulted to one formed from + * {@link PartitionsTestPluginFactory#getStandardAttributesPluginData}
    • + *
    • PartitionsPlugin is defaulted to one formed from + * {@link PartitionsTestPluginFactory#getStandardPartitionsPlugin}
    • + *
    • PeoplePlugin is defaulted to one formed from + * {@link PartitionsTestPluginFactory#getStandardPeoplePluginData}
    • + *
    • StochasticsPlugin is defaulted to one formed from + * {@link PartitionsTestPluginFactory#getStandardStochasticsPluginData}
    • + *
    • TestPlugin is formed from the TestPluginData passed into + * {@link PartitionsTestPluginFactory#factory}
    • + *
    + */ + public List getPlugins() { + List pluginsToAdd = new ArrayList<>(); + + Plugin attributesPlugin = AttributesPlugin.getAttributesPlugin(this.data.attributesPluginData); + + Plugin partitionsPlugin = this.data.partitionsPlugin; + + Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(this.data.peoplePluginData); + + Plugin stochasticPlugin = StochasticsPlugin.getStochasticsPlugin(this.data.stochasticsPluginData); + + Plugin testPlugin = TestPlugin.getTestPlugin(this.data.testPluginData); + + pluginsToAdd.add(attributesPlugin); + pluginsToAdd.add(partitionsPlugin); + pluginsToAdd.add(peoplePlugin); + pluginsToAdd.add(stochasticPlugin); + pluginsToAdd.add(testPlugin); + + return pluginsToAdd; + } + + /** + * Sets the {@link AttributesPluginData} in this Factory. This explicit instance + * of pluginData will be used to create a AttributesPlugin + * + * @throws ContractException {@linkplain AttributeError#NULL_ATTRIBUTES_PLUGIN_DATA} + * if the passed in pluginData is null + */ + public Factory setAttributesPluginData(AttributesPluginData attributesPluginData) { + if (attributesPluginData == null) { + throw new ContractException(AttributeError.NULL_ATTRIBUTES_PLUGIN_DATA); + } + this.data.attributesPluginData = attributesPluginData; + return this; + } + + /** + * Sets the {@link PartitionsPlugin} in this Factory. This explicit instance of + * pluginData will be used to create a PartitionsPlugin + * + * @throws ContractException {@linkplain PartitionError#NULL_PARTITION_PLUGIN} + * if the passed in pluginData is null + */ + public Factory setPartitionsPlugin(Plugin partitionsPlugin) { + if (partitionsPlugin == null) { + throw new ContractException(PartitionError.NULL_PARTITION_PLUGIN); + } + this.data.partitionsPlugin = partitionsPlugin; + return this; + } + + /** + * Sets the {@link PeoplePluginData} in this Factory. This explicit instance of + * pluginData will be used to create a PeoplePlugin + * + * @throws ContractException {@linkplain PersonError#NULL_PEOPLE_PLUGIN_DATA} if + * the passed in pluginData is null + */ + public Factory setPeoplePluginData(PeoplePluginData peoplePluginData) { + if (peoplePluginData == null) { + throw new ContractException(PersonError.NULL_PEOPLE_PLUGIN_DATA); + } + this.data.peoplePluginData = peoplePluginData; + return this; + } + + /** + * Sets the {@link StochasticsPluginData} in this Factory. This explicit + * instance of pluginData will be used to create a StochasticsPlugin + * + * @throws ContractException {@linkplain StochasticsError#NULL_STOCHASTICS_PLUGIN_DATA} + * if the passed in pluginData is null + */ + public Factory setStochasticsPluginData(StochasticsPluginData stochasticsPluginData) { + if (stochasticsPluginData == null) { + throw new ContractException(StochasticsError.NULL_STOCHASTICS_PLUGIN_DATA); + } + this.data.stochasticsPluginData = stochasticsPluginData; + return this; + } + + } + + /** + * Creates a Factory that facilitates the creation of a minimal set of plugins + * needed to adequately test the {@link PartitionsPlugin} by generating: + *
      + *
    • {@link AttributesPluginData}
    • + *
    • {@link PartitionsPlugin}
    • + *
    • {@link PeoplePluginData}
    • + *
    • {@link StochasticsPluginData}
    • + *
    + * either directly (by default) via + *
      + *
    • {@link #getStandardAttributesPluginData}
    • + *
    • {@link #getStandardPartitionsPlugin}
    • + *
    • {@link #getStandardPeoplePluginData}
    • + *
    • {@link #getStandardStochasticsPluginData}
    • + *
    + * or explicitly set via + *
      + *
    • {@link Factory#setAttributesPluginData}
    • + *
    • {@link Factory#setPartitionsPlugin}
    • + *
    • {@link Factory#setPeoplePluginData}
    • + *
    • {@link Factory#setStochasticsPluginData}
    • + *
    + * via the {@link Factory#getPlugins()} method. + * + * @throws ContractException {@linkplain NucleusError#NULL_PLUGIN_DATA} if + * testPluginData is null + */ + public static Factory factory(int initialPopulation, long seed, TestPluginData testPluginData) { + if (testPluginData == null) { + throw new ContractException(NucleusError.NULL_PLUGIN_DATA); + } + return new Factory(new Data(initialPopulation, seed, testPluginData)); + } + + /** + * Creates a Factory that facilitates the creation of a minimal set of plugins + * needed to adequately test the {@link PartitionsPlugin} by generating: + *
      + *
    • {@link AttributesPluginData}
    • + *
    • {@link PartitionsPlugin}
    • + *
    • {@link PeoplePluginData}
    • + *
    • {@link StochasticsPluginData}
    • + *
    + * either directly (by default) via + *
      + *
    • {@link #getStandardAttributesPluginData}
    • + *
    • {@link #getStandardPartitionsPlugin}
    • + *
    • {@link #getStandardPeoplePluginData}
    • + *
    • {@link #getStandardStochasticsPluginData}
    • + *
    + * or explicitly set via + *
      + *
    • {@link Factory#setAttributesPluginData}
    • + *
    • {@link Factory#setPartitionsPlugin}
    • + *
    • {@link Factory#setPeoplePluginData}
    • + *
    • {@link Factory#setStochasticsPluginData}
    • + *
    + * via the {@link Factory#getPlugins()} method. + * + * @throws ContractException {@linkplain NucleusError#NULL_ACTOR_CONTEXT_CONSUMER} + * if consumer is null + */ + public static Factory factory(int initialPopulation, long seed, Consumer consumer) { + if (consumer == null) { + throw new ContractException(NucleusError.NULL_ACTOR_CONTEXT_CONSUMER); + } + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, consumer)); + TestPluginData testPluginData = pluginDataBuilder.build(); + + return factory(initialPopulation, seed, testPluginData); + } + + /** + * Returns a standardized AttributesPluginData that is minimally adequate for + * testing the PartitionsPlugin The resulting AttributesPluginData will include: + *
      + *
    • Every AttributeId included in {@link TestAttributeId} + *
        + *
      • along with the attributeDefinition for each
      • + *
      + *
    + */ + public static AttributesPluginData getStandardAttributesPluginData() { + AttributesPluginData.Builder attributesBuilder = AttributesPluginData.builder(); + for (TestAttributeId testAttributeId : TestAttributeId.values()) { + attributesBuilder.defineAttribute(testAttributeId, testAttributeId.getAttributeDefinition()); + } + return attributesBuilder.build(); + } + + /** + * Returns a Standardized PartitionsPlugin that is minimally adequate for + * testing the PartitionsPlugin The resulting PartitionsPlugin will include: + *
      + *
    • the basic PartitionsPlugin from + * {@link PartitionsPlugin.Builder#getPartitionsPlugin()}
    • + *
    • An additional pluginDependency on {@link AttributesPluginId}
    • + *
    + */ + public static Plugin getStandardPartitionsPlugin() { + + return PartitionsPlugin.builder()// + .addPluginDependency(AttributesPluginId.PLUGIN_ID)// + .setPartitionsPluginData(PartitionsPluginData.builder().build())// + .getPartitionsPlugin(); + } + + /** + * Returns a standardized PeoplePluginData that is minimally adequate for + * testing the PartitionsPlugin The resulting PeoplePluginData will include: + *
      + *
    • a number of people equal to the passed in intialPopulation
    • + *
    + */ + public static PeoplePluginData getStandardPeoplePluginData(int initialPopulation) { + // add the people plugin + PeoplePluginData.Builder peopleBuilder = PeoplePluginData.builder(); + if (initialPopulation > 0) { + peopleBuilder.addPersonRange(new PersonRange(0, initialPopulation - 1)); + } + return peopleBuilder.build(); + } + + /** + * Returns a standardized StochasticsPluginData that is minimally adequate for + * testing the PartitionsPlugin The resulting StochasticsPluginData will + * include: + *
      + *
    • a seed based on the nextLong of a RandomGenerator seeded from the passed + * in seed
    • + *
    + */ + public static StochasticsPluginData getStandardStochasticsPluginData(long seed) { + WellState wellState = WellState.builder().setSeed(seed).build(); + return StochasticsPluginData.builder().setMainRNGState(wellState).build(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/TestPartitionsContext.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/TestPartitionsContext.java new file mode 100644 index 000000000..638a0ef99 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/TestPartitionsContext.java @@ -0,0 +1,42 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.DataManager; +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import util.errors.ContractException; + +/** + * Test support implementor of PartitionsContext that uses an ActorContext + * instead of the usual DataManagerContext to ease testing. + */ +public final class TestPartitionsContext implements PartitionsContext { + private final ActorContext actorContext; + + public TestPartitionsContext(ActorContext actorContext) { + this.actorContext = actorContext; + } + + /** + * Returns the data manager from the given class reference + * + * @throws ContractException + *
      + *
    • {@linkplain NucleusError#NULL_DATA_MANAGER_CLASS} + * if data manager class is null
    • + *
    • {@linkplain NucleusError#AMBIGUOUS_DATA_MANAGER_CLASS} + * if more than one data manager matches the given + * class
    • + *
    + */ + public T getDataManager(Class dataManagerClass) { + return actorContext.getDataManager(dataManagerClass); + } + + /** + * Returns the current time in the simulation + */ + public double getTime() { + return actorContext.getTime(); + } +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/AttributesDataManager.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/AttributesDataManager.java new file mode 100644 index 000000000..998855c2f --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/AttributesDataManager.java @@ -0,0 +1,345 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes; + +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.math3.util.FastMath; + +import gov.hhs.aspr.ms.gcm.nucleus.DataManager; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.nucleus.EventFilter; +import gov.hhs.aspr.ms.gcm.nucleus.IdentifiableFunctionMap; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.events.AttributeUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support.AttributeDefinition; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support.AttributeError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support.AttributeId; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.events.PersonRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import util.errors.ContractException; + +/** + * Published data view that provides attribute information. + */ +public final class AttributesDataManager extends DataManager { + + private DataManagerContext dataManagerContext; + + private Map attributeDefinitions = new LinkedHashMap<>(); + + private final Map> attributeValues = new LinkedHashMap<>(); + + /** + * Returns the attribute definition associated with the given attribute id + * without validation. + * + * @throws ContractException + *
      + *
    • {@linkplain AttributeError#NULL_ATTRIBUTE_ID} + * if the attribute id is null
    • + *
    • {@linkplain AttributeError#UNKNOWN_ATTRIBUTE_ID} + * if the attribute id unknown
    • + *
    + */ + public AttributeDefinition getAttributeDefinition(final AttributeId attributeId) { + validateAttributeId(attributeId); + return attributeDefinitions.get(attributeId); + } + + private void validateAttributeId(AttributeId attributeId) { + if (attributeId == null) { + throw new ContractException(AttributeError.NULL_ATTRIBUTE_ID); + } + + if (!attributeExists(attributeId)) { + throw new ContractException(AttributeError.UNKNOWN_ATTRIBUTE_ID); + } + + } + + /** + * Returns the attribute ids + */ + @SuppressWarnings("unchecked") + public Set getAttributeIds() { + final Set result = new LinkedHashSet<>(attributeDefinitions.keySet().size()); + for (final AttributeId attributeId : attributeDefinitions.keySet()) { + result.add((T) attributeId); + } + return result; + } + + /** + * Returns the attribute value associated with the given attribute id + * + * @throws ContractException + *
      + *
    • {@linkplain AttributeError#NULL_ATTRIBUTE_ID} + * if the attribute id is null
    • + *
    • {@linkplain AttributeError#UNKNOWN_ATTRIBUTE_ID} + * if the attribute id unknown
    • + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person id unknown
    • + *
    + */ + @SuppressWarnings("unchecked") + public T getAttributeValue(final PersonId personId, final AttributeId attributeId) { + validateAttributeId(attributeId); + validatePersonId(personId); + Object value = null; + Map map = attributeValues.get(attributeId); + if (map != null) { + value = attributeValues.get(attributeId).get(personId); + } + if (value == null) { + value = attributeDefinitions.get(attributeId).getDefaultValue(); + } + return (T) value; + } + + private void validatePersonId(PersonId personId) { + if (personId == null) { + throw new ContractException(PersonError.NULL_PERSON_ID); + } + if (!peopleDataManager.personExists(personId)) { + throw new ContractException(PersonError.UNKNOWN_PERSON_ID); + } + } + + private PeopleDataManager peopleDataManager; + + /** + * Returns true if and only if the attribute is contained + */ + public boolean attributeExists(final AttributeId attributeId) { + return attributeDefinitions.containsKey(attributeId); + } + + private final AttributesPluginData attributesPluginData; + + /** + * Constructs this data manager from the given context + */ + public AttributesDataManager(AttributesPluginData attributesPluginData) { + if (attributesPluginData == null) { + throw new ContractException(AttributeError.NULL_ATTRIBUTE_INITIAL_DATA); + } + this.attributesPluginData = attributesPluginData; + } + + @Override + public void init(DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + this.dataManagerContext = dataManagerContext; + peopleDataManager = dataManagerContext.getDataManager(PeopleDataManager.class); + + attributeDefinitions = attributesPluginData.getAttributeDefinitions(); + + Map> attributeValues2 = attributesPluginData.getAttributeValues(); + + for (AttributeId attributeId : attributeValues2.keySet()) { + Map personAttributesMap = new LinkedHashMap<>(); + attributeValues.put(attributeId, personAttributesMap); + List propertyValues = attributeValues2.get(attributeId); + int n = FastMath.max(peopleDataManager.getPersonIdLimit(), propertyValues.size()); + for (int i = 0; i < n; i++) { + if (peopleDataManager.personIndexExists(i)) { + if (i < propertyValues.size()) { + Object propertyValue = propertyValues.get(i); + if (propertyValue != null) { + PersonId personId = peopleDataManager.getBoxedPersonId(i).get(); + personAttributesMap.put(personId, propertyValue); + } + } + } else { + if (i < propertyValues.size()) { + Object propertyValue = propertyValues.get(i); + if (propertyValue != null) { + throw new ContractException(AttributeError.UNKNOWN_PERSON_HAS_ATTRIBUTE_VALUE_ASSIGNMENT, + "unknown person(" + i + ") has attribute value for " + attributeId); + } + } + } + } + } + + dataManagerContext.subscribe(PersonRemovalEvent.class, this::handlePersonRemovalEvent); + dataManagerContext.subscribe(AttributeUpdateMutationEvent.class, this::handleAttributeUpdateMutationEvent); + + if (dataManagerContext.stateRecordingIsScheduled()) { + dataManagerContext.subscribeToSimulationClose(this::recordSimulationState); + } + + } + + private void recordSimulationState(DataManagerContext dataManagerContext) { + + AttributesPluginData.Builder builder = AttributesPluginData.builder(); + + for (AttributeId attributeId : attributeDefinitions.keySet()) { + AttributeDefinition attributeDefinition = attributeDefinitions.get(attributeId); + builder.defineAttribute(attributeId, attributeDefinition); + } + + for (AttributeId attributeId : attributeValues.keySet()) { + Map map = attributeValues.get(attributeId); + if (map != null) { + for (PersonId personId : map.keySet()) { + Object value = map.get(personId); + builder.setPersonAttributeValue(personId, attributeId, value); + } + } + } + + dataManagerContext.releaseOutput(builder.build()); + } + + private void handlePersonRemovalEvent(final DataManagerContext dataManagerContext, + final PersonRemovalEvent personRemovalEvent) { + for (AttributeId attributeId : attributeValues.keySet()) { + Map map = attributeValues.get(attributeId); + if (map != null) { + map.remove(personRemovalEvent.personId()); + } + } + } + + private static record AttributeUpdateMutationEvent(PersonId personId, AttributeId attributeId, Object value) + implements Event { + } + + /** + * Updates the person's current attribute value. Generates a corresponding + * {@linkplain AttributeUpdateEvent} Throws {@link ContractException} + *
      + *
    • {@link PersonError#NULL_PERSON_ID} if the person id is null
    • + *
    • {@link PersonError#UNKNOWN_PERSON_ID} if the person id is unknown
    • + *
    • {@link AttributeError#NULL_ATTRIBUTE_ID} if the attribute id is null
    • + *
    • {@link AttributeError#UNKNOWN_ATTRIBUTE_ID} if the attribute id is + * unknown
    • + *
    • {@link AttributeError#NULL_ATTRIBUTE_VALUE} if the attribute value is + * null
    • + *
    • {@link AttributeError#INCOMPATIBLE_VALUE} if the attribute value is + * incompatible with the associated attribute definition
    • + *
    + */ + public void setAttributeValue(final PersonId personId, final AttributeId attributeId, final Object value) { + + dataManagerContext.releaseMutationEvent(new AttributeUpdateMutationEvent(personId, attributeId, value)); + } + + private void handleAttributeUpdateMutationEvent(DataManagerContext dataManagerContext, + AttributeUpdateMutationEvent attributeUpdateMutationEvent) { + AttributeId attributeId = attributeUpdateMutationEvent.attributeId(); + PersonId personId = attributeUpdateMutationEvent.personId(); + Object value = attributeUpdateMutationEvent.value(); + + validatePersonExists(dataManagerContext, personId); + validateAttributeId(dataManagerContext, attributeId); + validateValueNotNull(dataManagerContext, value); + final AttributeDefinition attributeDefinition = getAttributeDefinition(attributeId); + validateValueCompatibility(dataManagerContext, attributeId, attributeDefinition, value); + Object previousValue = getAttributeValue(personId, attributeId); + Map map = attributeValues.get(attributeId); + if (map == null) { + map = new LinkedHashMap<>(); + attributeValues.put(attributeId, map); + } + map.put(personId, value); + if (dataManagerContext.subscribersExist(AttributeUpdateEvent.class)) { + dataManagerContext + .releaseObservationEvent(new AttributeUpdateEvent(personId, attributeId, previousValue, value)); + } + + } + + private void validatePersonExists(final DataManagerContext dataManagerContext, final PersonId personId) { + if (personId == null) { + throw new ContractException(PersonError.NULL_PERSON_ID); + } + if (!peopleDataManager.personExists(personId)) { + throw new ContractException(PersonError.UNKNOWN_PERSON_ID); + } + } + + private static void validateValueNotNull(final DataManagerContext dataManagerContext, final Object value) { + if (value == null) { + throw new ContractException(AttributeError.NULL_ATTRIBUTE_VALUE); + } + } + + private static void validateValueCompatibility(final DataManagerContext dataManagerContext, + final AttributeId attributeId, final AttributeDefinition attributeDefinition, final Object value) { + if (!attributeDefinition.getType().isAssignableFrom(value.getClass())) { + throw new ContractException(AttributeError.INCOMPATIBLE_VALUE, + "Attribute value " + value + " is not of type " + attributeDefinition.getType().getName() + + " and does not match definition of " + attributeId); + } + } + + private void validateAttributeId(DataManagerContext dataManagerContext, final AttributeId attributeId) { + if (attributeId == null) { + throw new ContractException(AttributeError.NULL_ATTRIBUTE_ID); + } + if (!attributeExists(attributeId)) { + throw new ContractException(AttributeError.UNKNOWN_ATTRIBUTE_ID, attributeId); + } + } + + private static enum EventFunctionId { + ATTRIBUTE_ID, // + } + + private IdentifiableFunctionMap functionMap = // + IdentifiableFunctionMap.builder(AttributeUpdateEvent.class)// + .put(EventFunctionId.ATTRIBUTE_ID, e -> e.attributeId())// + .build();// + + /** + * Returns an event filter used to subscribe to {@link AttributeUpdateEvent} + * events. Matches on attribute id. + * + * @throws ContractException + *
      + *
    • {@linkplain AttributeError#NULL_ATTRIBUTE_ID} + * if the attribute id is null
    • + *
    • {@linkplain AttributeError#UNKNOWN_ATTRIBUTE_ID} + * if the attribute id is not known
    • + *
    + */ + public EventFilter getEventFilterForAttributeUpdateEvent(AttributeId attributeId) { + validateAttributeId(attributeId); + return EventFilter.builder(AttributeUpdateEvent.class)// + .addFunctionValuePair(functionMap.get(EventFunctionId.ATTRIBUTE_ID), attributeId)// + .build(); + } + + /** + * Returns an event filter used to subscribe to {@link AttributeUpdateEvent} + * events. Matches all such events. + */ + public EventFilter getEventFilterForAttributeUpdateEvent() { + + return EventFilter.builder(AttributeUpdateEvent.class)// + .build(); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("AttributesDataManager [attributeDefinitions="); + builder.append(attributeDefinitions); + builder.append(", attributeValues="); + builder.append(attributeValues); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/AttributesPlugin.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/AttributesPlugin.java new file mode 100644 index 000000000..49b3adc20 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/AttributesPlugin.java @@ -0,0 +1,94 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support.AttributeError; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePluginId; +import util.errors.ContractError; +import util.errors.ContractException; + +/** + *

    + * Summary A nucleus test support plugin for testing the partitions + * plugin. Introduces the concept of attributes that can be assigned to people. + *

    + *

    + * Events See each event class for details. + *

      + *
    • AttributeValueAssignmentEvent: Sets an attribute value for a + * person
    • + *
    • AttributeUpdateEvent: Notifies subscribed oberservers of a person + * attribute value change
    • + *
    + *

    + * Resolvers + *

      + *
    • AttributesEventResolver: Uses initializing data to create and + * publish data view. Handles all plugin-defined events.
    • + *
    + *

    + * Data Views The attributes plugin supplies one data view. + *

      + *
    • Attributes Data View: Supplies attribute values, ids and + * definitions.
    • + *
    + *

    + * Reports The plugin defines no reports. + *

    + *

    + * Agents: This plugin defines no agent implementations. + *

    + *

    + * Initializing data: An immutable container of the attribute + * definitions. + *

    + *

    + * Support classes + *

      + *
    • AttributeError:Enumeration implementing {@linkplain ContractError} + * for this plugin.
    • + *
    • AttributeDefinition: Class for defining the type and default value + * of an attribute
    • + *
    • AttributeFilter: Defines a filter used in partitions.
    • + *
    • AttributeId: Marker interface that defines attribute id + * values.
    • + *
    • AttributeLabeler: Provides dimension labeling for person + * attributes in partitions.
    • + *
    + *

    + * Required Plugins + *

      + *
    • PartitionsPlugin: Uses support classes in the partitions plugin to + * define filtering and labeling behaviors. + *
    • PeoplePlugin: Used throughout the plugin since the plugin is + * focused on person attributes
    • + *
    + */ +public final class AttributesPlugin { + + private AttributesPlugin() { + } + + /** + * Constructs this plugin + * + * @throws ContractException {@linkplain AttributeError#NULL_ATTRIBUTE_INITIAL_DATA} + * if the initial data is null + */ + public static Plugin getAttributesPlugin(AttributesPluginData attributesPluginData) { + if (attributesPluginData == null) { + throw new ContractException(AttributeError.NULL_ATTRIBUTE_INITIAL_DATA); + } + + return Plugin.builder()// + .setPluginId(AttributesPluginId.PLUGIN_ID)// + .addPluginDependency(PeoplePluginId.PLUGIN_ID)// + .setInitializer((c) -> { + AttributesPluginData pluginData = c.getPluginData(AttributesPluginData.class).get(); + c.addDataManager(new AttributesDataManager(pluginData)); + })// + .addPluginData(attributesPluginData)// + .build(); + + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/AttributesPluginData.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/AttributesPluginData.java new file mode 100644 index 000000000..4d61e2dde --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/AttributesPluginData.java @@ -0,0 +1,400 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.PluginDataBuilder; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support.AttributeDefinition; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support.AttributeError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support.AttributeId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +@Immutable +public class AttributesPluginData implements PluginData { + + private static class Data { + private Map attributeDefinitions = new LinkedHashMap<>(); + private Map> personAttributeValues = new LinkedHashMap<>(); + private List emptyValueList = Collections.unmodifiableList(new ArrayList<>()); + private boolean locked; + + public Data() { + } + + public Data(Data data) { + locked = data.locked; + attributeDefinitions.putAll(data.attributeDefinitions); + for (AttributeId attributeId : data.personAttributeValues.keySet()) { + List list = data.personAttributeValues.get(attributeId); + List newList = new ArrayList<>(list); + personAttributeValues.put(attributeId, newList); + } + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((attributeDefinitions == null) ? 0 : attributeDefinitions.hashCode()); + result = prime * result + ((personAttributeValues == null) ? 0 : personAttributeValues.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + if (attributeDefinitions == null) { + if (other.attributeDefinitions != null) { + return false; + } + } else if (!attributeDefinitions.equals(other.attributeDefinitions)) { + return false; + } + if (personAttributeValues == null) { + if (other.personAttributeValues != null) { + return false; + } + } else if (!personAttributeValues.equals(other.personAttributeValues)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Data [attributeDefinitions="); + builder.append(attributeDefinitions); + builder.append(", personAttributeValues="); + builder.append(personAttributeValues); + builder.append("]"); + return builder.toString(); + } + + } + + public static Builder builder() { + return new Builder(new Data()); + } + + public static class Builder implements PluginDataBuilder { + private Data data = new Data(); + + private Builder(Data data) { + this.data = data; + } + + private void ensureDataMutability() { + if (data.locked) { + data = new Data(data); + data.locked = false; + } + } + + private void ensureImmutability() { + if (!data.locked) { + data.locked = true; + } + } + + private void validateData() { + + // show all property ids agree with the definitions + for (AttributeId attributeId : data.personAttributeValues.keySet()) { + if (!data.attributeDefinitions.keySet().contains(attributeId)) { + throw new ContractException(AttributeError.UNKNOWN_ATTRIBUTE_ID, attributeId); + } + } + + // add lists where needed + for (AttributeId attributeId : data.attributeDefinitions.keySet()) { + if (!data.personAttributeValues.keySet().contains(attributeId)) { + data.personAttributeValues.put(attributeId, new ArrayList<>()); + } + } + + /* + * show that each value is compatible with the property definition + */ + for (AttributeId attributeId : data.attributeDefinitions.keySet()) { + AttributeDefinition attributeDefinition = data.attributeDefinitions.get(attributeId); + + List list = data.personAttributeValues.get(attributeId); + for (int i = 0; i < list.size(); i++) { + Object value = list.get(i); + if (value != null) { + if (!attributeDefinition.getType().isAssignableFrom(value.getClass())) { + throw new ContractException(AttributeError.INCOMPATIBLE_VALUE, attributeId + " = " + value); + } + } + } + + } + + // reorder property values map to match the definitions order + Map> temp = new LinkedHashMap<>(); + + for (AttributeId attributeId : data.attributeDefinitions.keySet()) { + if (data.personAttributeValues.containsKey(attributeId)) { + temp.put(attributeId, data.personAttributeValues.get(attributeId)); + } + } + data.personAttributeValues = temp; + + } + + /** + * Returns the {@linkplain AttributesPluginData} from the collected data + * + * @throws ContractException + *
      + *
    • {@linkplain AttributeError#UNKNOWN_ATTRIBUTE_ID} + * if a person attribute value was recorded for an + * unknown attribute id
    • + *
    • {@linkplain AttributeError#INCOMPATIBLE_VALUE} + * if a person attribute value was recorded that is + * not compatible witht he corresponding attribute + * definition
    • + *
    + */ + public AttributesPluginData build() { + if (!data.locked) { + validateData(); + } + ensureImmutability(); + return new AttributesPluginData(new Data(data)); + } + + /** + * Adds an attribute definition. + * + * @throws ContractException + *
      + *
    • {@linkplain AttributeError#NULL_ATTRIBUTE_ID} + * if the attribute id is null
    • + *
    • {@linkplain AttributeError#NULL_ATTRIBUTE_DEFINITION} + * if the attribute definition is null
    • + *
    • {@linkplain AttributeError#DUPLICATE_ATTRIBUTE_DEFINITION} + * if the attribute id was previously added
    • + *
    + */ + public Builder defineAttribute(final AttributeId attributeId, final AttributeDefinition attributeDefinition) { + ensureDataMutability(); + validateAttributeIdNotNull(attributeId); + validateAttributeDefinitionNotNull(attributeDefinition); + validateAttributeIsNotDefined(data, attributeId); + data.attributeDefinitions.put(attributeId, attributeDefinition); + return this; + } + + /** + * Sets the person's attribute value. Duplicate inputs override previous inputs. + * + * @throws ContractException + *
      + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain AttributeError#NULL_ATTRIBUTE_ID} + * if the attribute property id is null
    • + *
    • {@linkplain AttributeError#NULL_ATTRIBUTE_VALUE} + * if the attribute property value is null
    • + *
    + */ + public Builder setPersonAttributeValue(final PersonId personId, final AttributeId attributeId, + final Object attributeValue) { + ensureDataMutability(); + validatePersonId(personId); + validateAttributeIdNotNull(attributeId); + validateAttributeValueNotNull(attributeValue); + + List list = data.personAttributeValues.get(attributeId); + if (list == null) { + list = new ArrayList<>(); + data.personAttributeValues.put(attributeId, list); + } + + int personIndex = personId.getValue(); + while (list.size() <= personIndex) { + list.add(null); + } + list.set(personIndex, attributeValue); + + return this; + } + + } + + private static void validateAttributeIsNotDefined(final Data data, final AttributeId attributeId) { + AttributeDefinition attributeDefinition = data.attributeDefinitions.get(attributeId); + if (attributeDefinition != null) { + throw new ContractException(AttributeError.DUPLICATE_ATTRIBUTE_DEFINITION, attributeId); + } + } + + private static void validateAttributeDefinitionNotNull(AttributeDefinition attributeDefinition) { + if (attributeDefinition == null) { + throw new ContractException(AttributeError.NULL_ATTRIBUTE_DEFINITION); + } + } + + private static void validateAttributeIdNotNull(AttributeId attributeId) { + if (attributeId == null) { + throw new ContractException(AttributeError.NULL_ATTRIBUTE_ID); + } + } + + private final Data data; + + private AttributesPluginData(Data data) { + this.data = data; + } + + /** + * Returns the attribute definition for the given attribute id + * + * @throws ContractException + *
      + *
    • {@linkplain AttributeError#NULL_ATTRIBUTE_ID} + * if the attribute id is null
    • + *
    • {@linkplain AttributeError#UNKNOWN_ATTRIBUTE_ID} + * if the attribute id is unknown
    • + *
    + */ + public AttributeDefinition getAttributeDefinition(final AttributeId attributeId) { + validateAttributeIdNotNull(attributeId); + AttributeDefinition attributeDefinition = data.attributeDefinitions.get(attributeId); + if (attributeDefinition == null) { + throw new ContractException(AttributeError.UNKNOWN_ATTRIBUTE_ID, attributeId); + } + return attributeDefinition; + } + + /** + * Returns the attribute ids + */ + @SuppressWarnings("unchecked") + public Set getAttributeIds() { + Set result = new LinkedHashSet<>(); + for (AttributeId attributeId : data.attributeDefinitions.keySet()) { + result.add((T) attributeId); + } + return result; + } + + @Override + public PluginDataBuilder getCloneBuilder() { + + return new Builder(data); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof AttributesPluginData)) { + return false; + } + AttributesPluginData other = (AttributesPluginData) obj; + if (data == null) { + if (other.data != null) { + return false; + } + } else if (!data.equals(other.data)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder2 = new StringBuilder(); + builder2.append("AttributesPluginData [data="); + builder2.append(data); + builder2.append("]"); + return builder2.toString(); + } + + private static void validatePersonId(PersonId personId) { + if (personId == null) { + throw new ContractException(PersonError.NULL_PERSON_ID); + } + } + + private static void validateAttributeValueNotNull(Object attributeValue) { + if (attributeValue == null) { + throw new ContractException(AttributeError.NULL_ATTRIBUTE_VALUE); + } + } + + /** + * Returns the attribute values for the given attribute id as an unmodifiable + * list. Each object in the list corresponds to a PersonId in ascending order + * starting from zero. + * + * @throws ContractException + *
      + *
    • {@linkplain AttributeError#NULL_ATTRIBUTE_ID} + * if the attribute id is null
    • + *
    • {@linkplain AttributeError#UNKNOWN_ATTRIBUTE_ID} + * if the attribute id is unknown
    • + *
    + */ + public List getAttributeValues(AttributeId attributeId) { + validateAttributeId(attributeId); + List list = data.personAttributeValues.get(attributeId); + if (list == null) { + return data.emptyValueList; + } + return Collections.unmodifiableList(list); + } + + private void validateAttributeId(AttributeId attributeId) { + if (attributeId == null) { + throw new ContractException(AttributeError.NULL_ATTRIBUTE_ID); + } + if (!data.attributeDefinitions.containsKey(attributeId)) { + throw new ContractException(AttributeError.UNKNOWN_ATTRIBUTE_ID); + } + } + + public Map getAttributeDefinitions() { + return new LinkedHashMap<>(data.attributeDefinitions); + } + + public Map> getAttributeValues() { + + Map> result = new LinkedHashMap<>(); + + for (AttributeId attributeId : data.personAttributeValues.keySet()) { + List list = data.personAttributeValues.get(attributeId); + List newList = new ArrayList<>(list); + result.put(attributeId, newList); + } + + return result; + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/AttributesPluginId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/AttributesPluginId.java new file mode 100644 index 000000000..ee3ff70bb --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/AttributesPluginId.java @@ -0,0 +1,13 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; + +/** + * Static plugin id implementation for the Attributes Plugin + */ +public final class AttributesPluginId implements PluginId { + public final static PluginId PLUGIN_ID = new AttributesPluginId(); + + private AttributesPluginId() { + }; +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/events/AttributeUpdateEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/events/AttributeUpdateEvent.java new file mode 100644 index 000000000..5e0135265 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/events/AttributeUpdateEvent.java @@ -0,0 +1,22 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support.AttributeId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import net.jcip.annotations.Immutable; + +@Immutable +public record AttributeUpdateEvent(PersonId personId, AttributeId attributeId, Object previousValue, + Object currentValue) implements Event { + + @SuppressWarnings("unchecked") + public T getPreviousValue() { + return (T) previousValue; + } + + @SuppressWarnings("unchecked") + public T getCurrentValue() { + return (T) currentValue; + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/AttributeDefinition.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/AttributeDefinition.java new file mode 100644 index 000000000..0c2fb25c3 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/AttributeDefinition.java @@ -0,0 +1,171 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support; + +import net.jcip.annotations.ThreadSafe; +import util.errors.ContractException; + +/** + * A thread-safe, immutable class that defines an attribute, but does not + * indicate the role that attribute is playing or the identifier of the + * attribute. + */ +@ThreadSafe +public final class AttributeDefinition { + + public static Builder builder() { + return new Builder(); + } + + private static class Data { + + private Class type = null; + + private Object defaultValue = null; + + public Data() { + } + + public Data(Data data) { + type = data.type; + defaultValue = data.defaultValue; + } + + } + + /** + * Builder class for {@linkplain AttributeDefinition} + */ + public static class Builder { + + private Data data = new Data(); + + private Builder() { + } + + /** + * Builds the attribute definition + * + * @throws ContractException + *
      + *
    • {@linkplain AttributeError#NULL_ATTRIBUTE_TYPE} + * if the class type of the definition is not assigned + * or null
    • + *
    • {@linkplain AttributeError#NULL_DEFAULT_VALUE}if + * the default value null
    • + *
    • {@linkplain AttributeError#INCOMPATIBLE_DEFAULT_VALUE}if + * the class type is not a super-type of the default + * value
    • + *
    + */ + public AttributeDefinition build() { + return new AttributeDefinition(new Data(data)); + } + + /** + * Sets the class type. Value must be set by client. + */ + public Builder setType(final Class type) { + data.type = type; + return this; + } + + /** + * Sets the default value for the attribute. + */ + public Builder setDefaultValue(Object defaultValue) { + data.defaultValue = defaultValue; + return this; + } + + } + + private final Class type; + + private final Object defaultValue; + + private AttributeDefinition(Data data) { + + if (data.type == null) { + throw new ContractException(AttributeError.NULL_ATTRIBUTE_TYPE); + } + + if (data.defaultValue == null) { + throw new ContractException(AttributeError.NULL_DEFAULT_VALUE); + } + + if (!data.type.isInstance(data.defaultValue)) { + throw new ContractException(AttributeError.INCOMPATIBLE_DEFAULT_VALUE); + } + + this.type = data.type; + + this.defaultValue = data.defaultValue; + + } + + /** + * Returns the default value. + */ + public Object getDefaultValue() { + return defaultValue; + } + + /** + * Returns that class type of this definition. + */ + public Class getType() { + return type; + } + + /** + * Boilerplate implementation that uses all fields. + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((defaultValue == null) ? 0 : defaultValue.hashCode()); + result = prime * result + ((type == null) ? 0 : type.hashCode()); + return result; + } + + /** + * Attribute definitions are equal if they have the same type and default value. + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + AttributeDefinition other = (AttributeDefinition) obj; + + if (defaultValue == null) { + if (other.defaultValue != null) + return false; + } else if (!defaultValue.equals(other.defaultValue)) + return false; + + if (type == null) { + return other.type == null; + } else + return type.equals(other.type); + } + + /** + * Standard string representation in the form: AttributeDefinition + * [type=someType,mapOption=mapOption,defaultValue=someValue] + */ + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("AttributeDefinition [type="); + builder.append(type); + builder.append(", defaultValue="); + builder.append(defaultValue); + builder.append("]"); + return builder.toString(); + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/AttributeError.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/AttributeError.java new file mode 100644 index 000000000..c56db0a04 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/AttributeError.java @@ -0,0 +1,35 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support; + +import util.errors.ContractError; +import util.errors.ContractException; + +/** + * An enumeration supporting {@link ContractException} that acts as a general + * description of the exception. + */ +public enum AttributeError implements ContractError { + + UNKNOWN_PERSON_HAS_ATTRIBUTE_VALUE_ASSIGNMENT("Unknown person has attribute value assignment"), // + NULL_ATTRIBUTE_INITIAL_DATA("Null attribute initial data"), // + NULL_ATTRIBUTE_DATA_MANAGER("Null attributes data manager"), // + NULL_ATTRIBUTE_ID("Null attribute id"), // + NULL_ATTRIBUTE_DEFINITION("Null attribute definition"), // + NULL_ATTRIBUTE_VALUE("Null attribute value"), // + NULL_ATTRIBUTE_TYPE("Null attribute type"), // + NULL_ATTRIBUTES_PLUGIN_DATA("null attributes plugin data"), + INCOMPATIBLE_DEFAULT_VALUE("Incompatible default value"), NULL_DEFAULT_VALUE("Null default value"), + UNKNOWN_ATTRIBUTE_ID("Unknown attribute id"), // + INCOMPATIBLE_VALUE("Atrribute value is incompatible with the attribute definition"), // + DUPLICATE_ATTRIBUTE_DEFINITION("Duplicate attribute definition");// + + private final String description; + + private AttributeError(final String description) { + this.description = description; + } + + @Override + public String getDescription() { + return description; + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/AttributeFilter.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/AttributeFilter.java new file mode 100644 index 000000000..febd93459 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/AttributeFilter.java @@ -0,0 +1,236 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support; + +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.Equality; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.FilterSensitivity; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters.Filter; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.AttributesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.events.AttributeUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import util.errors.ContractException; + +public final class AttributeFilter extends Filter { + + private final AttributeId attributeId; + private final Object value; + private final Equality equality; + private AttributesDataManager attributesDataManager; + + private void validateAttributeId(PartitionsContext partitionsContext, final AttributeId attributeId) { + if (attributeId == null) { + throw new ContractException(AttributeError.NULL_ATTRIBUTE_ID); + } + if (attributesDataManager == null) { + attributesDataManager = partitionsContext.getDataManager(AttributesDataManager.class); + } + + if (!attributesDataManager.attributeExists(attributeId)) { + throw new ContractException(AttributeError.UNKNOWN_ATTRIBUTE_ID, attributeId); + } + } + + private void validateEquality(PartitionsContext partitionsContext, final Equality equality) { + if (equality == null) { + throw new ContractException(PartitionError.NULL_EQUALITY_OPERATOR); + } + } + + private void validateValueNotNull(PartitionsContext partitionsContext, final Object value) { + if (value == null) { + throw new ContractException(AttributeError.NULL_ATTRIBUTE_VALUE); + } + } + + private void validateValueCompatibility(PartitionsContext partitionsContext, final AttributeId attributeId, + final AttributeDefinition attributeDefinition, final Object value) { + if (!attributeDefinition.getType().isAssignableFrom(value.getClass())) { + throw new ContractException(AttributeError.INCOMPATIBLE_VALUE, + "Attribute value " + value + " is not of type " + attributeDefinition.getType().getName() + + " and does not match definition of " + attributeId); + } + } + + private void validateEqualityCompatibility(PartitionsContext partitionsContext, final AttributeId attributeId, + final AttributeDefinition attributeDefinition, final Equality equality) { + + if (equality == Equality.EQUAL) { + return; + } + if (equality == Equality.NOT_EQUAL) { + return; + } + + if (!Comparable.class.isAssignableFrom(attributeDefinition.getType())) { + throw new ContractException(PartitionError.NON_COMPARABLE_ATTRIBUTE, + "Values for " + attributeId + " are not comparable via " + equality); + } + } + + public AttributeId getAttributeId() { + return attributeId; + } + + public Equality getEquality() { + return equality; + } + + public Object getValue() { + return value; + } + + public AttributeFilter(final AttributeId attributeId, final Equality equality, final Object value) { + this.attributeId = attributeId; + this.value = value; + this.equality = equality; + } + + /** + * Validates this attribute filter + * + * @throws ContractException + *
      + *
    • {@linkplain AttributeError#NULL_ATTRIBUTE_ID} + * if the filter's attribute id is null
    • + *
    • {@linkplain AttributeError#UNKNOWN_ATTRIBUTE_ID} + * if the filter's attribute id is unknown
    • + *
    • {@linkplain PartitionError#NULL_EQUALITY_OPERATOR} + * if the filter's equality operator is null
    • + *
    • {@linkplain AttributeError#NULL_ATTRIBUTE_VALUE} + * if the filter's value is null
    • + *
    • {@linkplain AttributeError#INCOMPATIBLE_VALUE} + * if the filter's value is incompatible with the + * attribute definition associated with the filter's + * attribute id.
    • + *
    • {@linkplain PartitionError#NON_COMPARABLE_ATTRIBUTE} + * if the filter's value is not a COMPARABLE when the + * filter's equality operator is not EQUALS or + * NOT_EQUALS.
    • + *
    + */ + @Override + public void validate(PartitionsContext partitionsContext) { + validateAttributeId(partitionsContext, attributeId); + validateEquality(partitionsContext, equality); + validateValueNotNull(partitionsContext, value); + if (attributesDataManager == null) { + attributesDataManager = partitionsContext.getDataManager(AttributesDataManager.class); + } + final AttributeDefinition attributeDefinition = attributesDataManager.getAttributeDefinition(attributeId); + validateValueCompatibility(partitionsContext, attributeId, attributeDefinition, value); + validateEqualityCompatibility(partitionsContext, attributeId, attributeDefinition, equality); + } + + @Override + public boolean evaluate(PartitionsContext partitionsContext, PersonId personId) { + + if (partitionsContext == null) { + throw new ContractException(NucleusError.NULL_SIMULATION_CONTEXT); + } + + if (attributesDataManager == null) { + attributesDataManager = partitionsContext.getDataManager(AttributesDataManager.class); + } + + // we do not assume that the returned attribute value is + // comparable unless we are forced to. + final Object attValue = attributesDataManager.getAttributeValue(personId, attributeId); + + return evaluate(attValue); + + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private boolean evaluate(Object propVal) { + if (equality.equals(Equality.EQUAL)) { + return propVal.equals(value); + } else if (equality.equals(Equality.NOT_EQUAL)) { + return !propVal.equals(value); + } else { + Comparable comparableAttributeValue = (Comparable) propVal; + int evaluation = comparableAttributeValue.compareTo(value); + return equality.isCompatibleComparisonValue(evaluation); + } + } + + private Optional requiresRefresh(PartitionsContext partitionsContext, AttributeUpdateEvent event) { + if (event.attributeId().equals(attributeId)) { + if (evaluate(event.previousValue()) != evaluate(event.currentValue())) { + return Optional.of(event.personId()); + } + } + return Optional.empty(); + } + + /** + * Returns a single filter sensitivity for AttributeUpdateEvent events. This + * sensitivity will require refreshes for events with the same attribute id and + * where the event where the event has different previous and current values. + */ + @Override + public Set> getFilterSensitivities() { + Set> result = new LinkedHashSet<>(); + result.add(new FilterSensitivity(AttributeUpdateEvent.class, this::requiresRefresh)); + return result; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((attributeId == null) ? 0 : attributeId.hashCode()); + result = prime * result + ((equality == null) ? 0 : equality.hashCode()); + result = prime * result + ((value == null) ? 0 : value.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof AttributeFilter)) { + return false; + } + AttributeFilter other = (AttributeFilter) obj; + if (attributeId == null) { + if (other.attributeId != null) { + return false; + } + } else if (!attributeId.equals(other.attributeId)) { + return false; + } + if (equality != other.equality) { + return false; + } + if (value == null) { + if (other.value != null) { + return false; + } + } else if (!value.equals(other.value)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("AttributeFilter [attributeId="); + builder.append(attributeId); + builder.append(", value="); + builder.append(value); + builder.append(", equality="); + builder.append(equality); + builder.append(", attributesDataManager="); + builder.append(attributesDataManager); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/AttributeId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/AttributeId.java new file mode 100644 index 000000000..6d8caf83d --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/AttributeId.java @@ -0,0 +1,15 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support; + +import net.jcip.annotations.ThreadSafe; + +/** + * Marker interface for attribute identifiers + */ +@ThreadSafe +public interface AttributeId { + @Override + public int hashCode(); + + @Override + public boolean equals(Object obj); +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/AttributeLabeler.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/AttributeLabeler.java new file mode 100644 index 000000000..f7f121e66 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/AttributeLabeler.java @@ -0,0 +1,96 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support; + +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.Labeler; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.LabelerSensitivity; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.AttributesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.events.AttributeUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import util.errors.ContractException; + +/** + * A labeler for attributes. The dimension of the labeler is the given + * {@linkplain AttributeId}, the event that stimulates a label update is + * {@linkplain AttributeUpdateEvent} and the labeling function is composed from + * the given Function. + */ +public abstract class AttributeLabeler implements Labeler { + + private final AttributeId attributeId; + + private AttributesDataManager attributesDataManager; + + protected abstract Object getLabelFromValue(Object value); + + public AttributeLabeler(AttributeId attributeId) { + this.attributeId = attributeId; + } + + private Optional getPersonId(AttributeUpdateEvent attributeUpdateEvent) { + PersonId result = null; + if (attributeUpdateEvent.attributeId().equals(attributeId)) { + result = attributeUpdateEvent.personId(); + } + return Optional.ofNullable(result); + } + + /** + * Returns one LabelerSensitivity of AttributeUpdateEvent + */ + @Override + public final Set> getLabelerSensitivities() { + Set> result = new LinkedHashSet<>(); + result.add(new LabelerSensitivity(AttributeUpdateEvent.class, this::getPersonId)); + return result; + } + + /** + * Returns the label for the person precondition: the context should not be null + * + * @throws ContractException + *
      + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person id is unknown + *
    + */ + @Override + public final Object getCurrentLabel(PartitionsContext partitionsContext, PersonId personId) { + if (attributesDataManager == null) { + attributesDataManager = partitionsContext.getDataManager(AttributesDataManager.class); + } + Object value = attributesDataManager.getAttributeValue(personId, attributeId); + return getLabelFromValue(value); + } + + /** + * Returns the attribute id as the dimension + */ + @Override + public final Object getId() { + return attributeId; + } + + @Override + public final Object getPastLabel(PartitionsContext partitionsContext, Event event) { + AttributeUpdateEvent attributeUpdateEvent = (AttributeUpdateEvent) event; + return getLabelFromValue(attributeUpdateEvent.previousValue()); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("AttributeLabeler [attributeId="); + builder.append(attributeId); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/gcm3/src/main/java/plugins/partitions/testsupport/attributes/support/SimpleAttributeId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/SimpleAttributeId.java similarity index 86% rename from gcm3/src/main/java/plugins/partitions/testsupport/attributes/support/SimpleAttributeId.java rename to gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/SimpleAttributeId.java index 8237bee9c..9f8f89044 100644 --- a/gcm3/src/main/java/plugins/partitions/testsupport/attributes/support/SimpleAttributeId.java +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/SimpleAttributeId.java @@ -1,6 +1,4 @@ -package plugins.partitions.testsupport.attributes.support; - - +package gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support; public final class SimpleAttributeId implements AttributeId { private final Object value; @@ -8,8 +6,7 @@ public final class SimpleAttributeId implements AttributeId { /** * Creates an attribute id from the given value * - * @throws RuntimeException - * if the value is null + * @throws RuntimeException if the value is null */ public SimpleAttributeId(Object value) { if (value == null) { @@ -30,8 +27,8 @@ public int hashCode() { } /** - * Two {@link SimpleAttributeId} instances are equal if and only if their - * inputs are equal. + * Two {@link SimpleAttributeId} instances are equal if and only if their inputs + * are equal. */ @Override public boolean equals(Object obj) { diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/TestAttributeId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/TestAttributeId.java new file mode 100644 index 000000000..746f34c27 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/TestAttributeId.java @@ -0,0 +1,52 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support; + +import org.apache.commons.math3.random.RandomGenerator; + +/** + * A test support enumeration of attribute id value with associated attribute + * definitions. + */ +public enum TestAttributeId implements AttributeId { + + INT_0(AttributeDefinition.builder().setType(Integer.class).setDefaultValue(0).build()), + INT_1(AttributeDefinition.builder().setType(Integer.class).setDefaultValue(1).build()), + DOUBLE_0(AttributeDefinition.builder().setType(Double.class).setDefaultValue(0.0).build()), + DOUBLE_1(AttributeDefinition.builder().setType(Double.class).setDefaultValue(1.0).build()), + BOOLEAN_0(AttributeDefinition.builder().setType(Boolean.class).setDefaultValue(false).build()), + BOOLEAN_1(AttributeDefinition.builder().setType(Boolean.class).setDefaultValue(true).build()); + + private final AttributeDefinition attributeDefinition; + + public AttributeDefinition getAttributeDefinition() { + return attributeDefinition; + } + + private TestAttributeId(AttributeDefinition attributeDefinition) { + this.attributeDefinition = attributeDefinition; + } + + public static AttributeId getUnknownAttributeId() { + return new AttributeId() { + }; + } + + public static TestAttributeId getRandomAttributeId(RandomGenerator randomGenerator) { + int index = randomGenerator.nextInt(TestAttributeId.values().length); + return TestAttributeId.values()[index]; + } + + public Object getRandomPropertyValue(final RandomGenerator randomGenerator) { + switch (this) { + case INT_0, INT_1: + return randomGenerator.nextInt(); + case DOUBLE_0, DOUBLE_1: + return randomGenerator.nextDouble(); + case BOOLEAN_0, BOOLEAN_1: + return randomGenerator.nextBoolean(); + default: + throw new RuntimeException("unhandled case: " + this); + + } + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/PeoplePlugin.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/PeoplePlugin.java new file mode 100644 index 000000000..b58e1f991 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/PeoplePlugin.java @@ -0,0 +1,39 @@ +package gov.hhs.aspr.ms.gcm.plugins.people; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeoplePluginData; + +/** + * A nucleus plugin for representing people, dealing only with their existence. + */ +public final class PeoplePlugin { + + private PeoplePlugin() { + + } + + /** + * Returns the people plugin. + *

    + * Uses PeoplePluginId.PLUGIN_ID as its id + *

    + *

    + * Depends on plugins: none + *

    + * Provides data mangers: {@linkplain PeopleDataManager} + *

    + */ + public static Plugin getPeoplePlugin(PeoplePluginData peoplePluginData) { + + return Plugin.builder()// + .addPluginData(peoplePluginData)// + .setPluginId(PeoplePluginId.PLUGIN_ID)// + .setInitializer((c) -> { + PeoplePluginData pluginData = c.getPluginData(PeoplePluginData.class).get(); + c.addDataManager(new PeopleDataManager(pluginData)); + })// + .build(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/PeoplePluginId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/PeoplePluginId.java new file mode 100644 index 000000000..0c490e50f --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/PeoplePluginId.java @@ -0,0 +1,13 @@ +package gov.hhs.aspr.ms.gcm.plugins.people; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; + +/** + * Static plugin id implementation for the GlobalsPlugin + */ +public final class PeoplePluginId implements PluginId { + public final static PluginId PLUGIN_ID = new PeoplePluginId(); + + private PeoplePluginId() { + }; +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/datamanagers/PeopleDataManager.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/datamanagers/PeopleDataManager.java new file mode 100644 index 000000000..fbf5024ba --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/datamanagers/PeopleDataManager.java @@ -0,0 +1,397 @@ +package gov.hhs.aspr.ms.gcm.plugins.people.datamanagers; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Optional; + +import gov.hhs.aspr.ms.gcm.nucleus.DataManager; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.nucleus.EventFilter; +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.plugins.people.events.PersonAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.events.PersonImminentAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.events.PersonImminentRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.events.PersonRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonConstructionData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonRange; +import util.errors.ContractException; + +/** + * Mutable data manager for people. + */ +public final class PeopleDataManager extends DataManager { + + private static class PopulationRecord { + private int populationCount; + private double assignmentTime; + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("PopulationRecord [populationCount="); + builder.append(populationCount); + builder.append(", assignmentTime="); + builder.append(assignmentTime); + builder.append("]"); + return builder.toString(); + } + + } + + private static record PersonAdditionMutationEvent(PersonId personId, PersonConstructionData personConstructionData) + implements Event { + } + + private static record PersonRemovalMutationEvent(PersonId personId) implements Event { + } + + private final PeoplePluginData peoplePluginData; + + /* + * We keep the person records in a list rather than a map so that we can + * retrieve a person record by index (personId). + */ + private List personIds = new ArrayList<>(); + + private DataManagerContext dataManagerContext; + + private final PopulationRecord globalPopulationRecord = new PopulationRecord(); + + public PeopleDataManager(PeoplePluginData peoplePluginData) { + this.peoplePluginData = peoplePluginData; + } + + /** + * Returns a new person id that has been added to the simulation. The returned + * PersonId is unique and will wrap the int value returned by getPersonIdLimit() + * just prior to invoking this method. + * + * @throws ContractException + *
      + *
    • {@linkplain PersonError#NULL_PERSON_CONSTRUCTION_DATA} + * if the person construction data is null
    • + *
    + */ + public PersonId addPerson(final PersonConstructionData personConstructionData) { + PersonId personId = new PersonId(personIds.size()); + dataManagerContext.releaseMutationEvent(new PersonAdditionMutationEvent(personId, personConstructionData)); + return personId; + } + + /** + * Returns the PersonId that corresponds to the given int value. + */ + public Optional getBoxedPersonId(final int personId) { + PersonId result = null; + if (personIds.size() > personId) { + result = personIds.get(personId); + } + return Optional.ofNullable(result); + } + + /** + * Returns an event filter used to subscribe to {@link PersonAdditionEvent} + * events. Matches all such events. + */ + public EventFilter getEventFilterForPersonAdditionEvent() { + return EventFilter.builder(PersonAdditionEvent.class)// + .build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link PersonImminentRemovalEvent} events. Matches all such events. + */ + public EventFilter getEventFilterForPersonImminentRemovalEvent() { + return EventFilter.builder(PersonImminentRemovalEvent.class)// + .build(); + } + + /** + * Returns a list of PersonId for each person that exists. + */ + public List getPeople() { + + int count = 0; + for (final PersonId boxedPersonId : personIds) { + if (boxedPersonId != null) { + count++; + } + } + final List result = new ArrayList<>(count); + + for (final PersonId boxedPersonId : personIds) { + if (boxedPersonId != null) { + result.add(boxedPersonId); + } + } + + return result; + } + + /** + * Returns the lowest int id that has yet to be associated with a person. Lower + * values will correspond to existing, removed or unused id values. + */ + public int getPersonIdLimit() { + return personIds.size(); + } + + /** + * Returns the number of existing people + */ + public int getPopulationCount() { + return globalPopulationRecord.populationCount; + } + + /** + * Returns the time of the last added or removed person. Returns zero if no + * people have been added. + */ + public double getPopulationTime() { + return globalPopulationRecord.assignmentTime; + } + + private void handlePersonAdditionMutationEvent(DataManagerContext dataManagerContext, + PersonAdditionMutationEvent personAdditionMutationEvent) { + PersonConstructionData personConstructionData = personAdditionMutationEvent.personConstructionData(); + PersonId personId = personAdditionMutationEvent.personId(); + validatePersonConstructionDataNotNull(personConstructionData); + + if (personId.getValue() != personIds.size()) { + throw new RuntimeException("unexpected person id during person addition " + personId); + } + + personIds.add(personId); + globalPopulationRecord.populationCount++; + globalPopulationRecord.assignmentTime = dataManagerContext.getTime(); + + /* + * It is very likely that the PersonImminentAdditionEvent will have subscribers, + * so we don't waste time asking if there are any. + * + */ + final PersonImminentAdditionEvent personImminentAdditionEvent = new PersonImminentAdditionEvent(personId, + personConstructionData); + dataManagerContext.releaseObservationEvent(personImminentAdditionEvent); + + if (dataManagerContext.subscribersExist(PersonAdditionEvent.class)) { + dataManagerContext.releaseObservationEvent(new PersonAdditionEvent(personId)); + } + + } + + private void handlePersonRemovalMutationEvent(DataManagerContext dataManagerContext, + PersonRemovalMutationEvent personRemovalMutationEvent) { + PersonId personId = personRemovalMutationEvent.personId(); + + validatePersonExists(personId); + + dataManagerContext.addPlan((context) -> { + globalPopulationRecord.populationCount--; + globalPopulationRecord.assignmentTime = dataManagerContext.getTime(); + personIds.set(personId.getValue(), null); + + // it is very likely that there are observers, so we don't ask + // before creating the event + context.releaseObservationEvent(new PersonRemovalEvent(personId)); + + }, dataManagerContext.getTime()); + + if (dataManagerContext.subscribersExist(PersonImminentRemovalEvent.class)) { + dataManagerContext.releaseObservationEvent(new PersonImminentRemovalEvent(personId)); + } + } + + /** + * Initializes the data manager. This method should only be invoked by the + * simulation. All data manager descendant classes that override this method + * must invoke the super. + * + * @throws ContractException + *
      + *
    • {@linkplain NucleusError#DATA_MANAGER_DUPLICATE_INITIALIZATION} + * if init() is invoked more than once
    • + *
    • {@linkplain PersonError#PERSON_ASSIGNMENT_TIME_IN_FUTURE} + * if the plugin data person assignment time exceeds + * the start time of the simulation
    • + *
    + */ + @Override + public void init(final DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + this.dataManagerContext = dataManagerContext; + + dataManagerContext.subscribe(PersonAdditionMutationEvent.class, this::handlePersonAdditionMutationEvent); + dataManagerContext.subscribe(PersonRemovalMutationEvent.class, this::handlePersonRemovalMutationEvent); + + int personCount = peoplePluginData.getPersonCount(); + + personIds = new ArrayList<>(personCount); + for (int i = 0; i < personCount; i++) { + personIds.add(null); + } + + List personIdsFromPluginData = peoplePluginData.getPersonIds(); + globalPopulationRecord.populationCount = personIdsFromPluginData.size(); + + for (PersonId personId : personIdsFromPluginData) { + personIds.set(personId.getValue(), personId); + } + + if (peoplePluginData.getAssignmentTime() > dataManagerContext.getTime()) { + throw new ContractException(PersonError.PERSON_ASSIGNMENT_TIME_IN_FUTURE); + } + + globalPopulationRecord.assignmentTime = peoplePluginData.getAssignmentTime(); + if (dataManagerContext.stateRecordingIsScheduled()) { + dataManagerContext.subscribeToSimulationClose(this::recordSimulationState); + } + } + + /** + * Returns true if and only if the person exists in the simulation. + */ + public boolean personExists(final PersonId personId) { + if (personId != null) { + int personIndex = personId.getValue(); + if (personIndex < personIds.size()) { + return personIds.get(personId.getValue()) != null; + } + } + return false; + } + + /** + * Returns true if and only if there is an existing person associated with the + * given index. The PersonId is a wrapper around an int index. + */ + public boolean personIndexExists(final int personId) { + boolean result = false; + if (personId >= 0 && personId < personIds.size()) { + result = personIds.get(personId) != null; + } + return result; + + } + + private void recordSimulationState(DataManagerContext dataManagerContext) { + PeoplePluginData.Builder builder = PeoplePluginData.builder(); + builder.setPersonCount(personIds.size()); + builder.setAssignmentTime(globalPopulationRecord.assignmentTime); + + int a = -1; + int b = -1; + PersonId lastPersonId = null; + for (int i = 0; i < personIds.size(); i++) { + PersonId personId = personIds.get(i); + if (personId != null) { + if (lastPersonId == null) { + a = i; + b = i; + } else { + b = i; + } + } else { + if (lastPersonId != null) { + builder.addPersonRange(new PersonRange(a, b)); + } + } + lastPersonId = personId; + } + if (a >= 0) { + builder.addPersonRange(new PersonRange(a, b)); + } + dataManagerContext.releaseOutput(builder.build()); + } + + /** + * Removes the person from the simulation. + * + * @throws ContractException + *
      + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • + *
    • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person does not exist
    • + *
    • + *
    + */ + public void removePerson(final PersonId personId) { + dataManagerContext.releaseMutationEvent(new PersonRemovalMutationEvent(personId)); + } + + private void validatePersonConstructionDataNotNull(final PersonConstructionData personConstructionData) { + if (personConstructionData == null) { + throw new ContractException(PersonError.NULL_PERSON_CONSTRUCTION_DATA); + } + } + + private void validatePersonExists(final PersonId personId) { + if (personId == null) { + throw new ContractException(PersonError.NULL_PERSON_ID); + } + if (!personExists(personId)) { + throw new ContractException(PersonError.UNKNOWN_PERSON_ID); + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("PeopleDataManager [personIds="); + builder.append(personIds); + builder.append(", globalPopulationRecord="); + builder.append(globalPopulationRecord); + builder.append("]"); + return builder.toString(); + } + + private static class PersonIndexIterator implements Iterator { + + private Integer next; + private final Iterator iterator; + + public PersonIndexIterator(Iterator iterator) { + this.iterator = iterator; + increment(); + } + + private void increment() { + next = null; + while (iterator.hasNext()) { + PersonId personId = iterator.next(); + if (personId != null) { + next = personId.getValue(); + break; + } + } + } + + @Override + public boolean hasNext() { + return next != null; + } + + @Override + public Integer next() { + if (next == null) { + throw new NoSuchElementException(); + } + Integer result = next; + increment(); + return result; + } + } + + public Iterator getPersonIndexIterator() { + return new PersonIndexIterator(personIds.iterator()); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/datamanagers/PeoplePluginData.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/datamanagers/PeoplePluginData.java new file mode 100644 index 000000000..0ac39dee0 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/datamanagers/PeoplePluginData.java @@ -0,0 +1,361 @@ +package gov.hhs.aspr.ms.gcm.plugins.people.datamanagers; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.PluginDataBuilder; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonRange; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * An immutable container of the initial state of people containing person ids. + * All other person initialization data is provided by other plugins. + */ +@Immutable +public final class PeoplePluginData implements PluginData { + + /* + * The person ids are calculated when the data is being locked and are retained + * here instead of in the PeoplePluginData instance for efficiency. We do not + * copy the person ids in the copy constructor of the data class since we will + * be setting the locked to false and will just have to recalculate the person + * ids later. + */ + private static class Data { + private int personCount = -1; + private List personRanges = new ArrayList<>(); + private List personIds; + private double assignmentTime; + private boolean locked; + + public Data() { + } + + public Data(Data data) { + locked = data.locked; + this.personCount = data.personCount; + this.personRanges.addAll(data.personRanges); + this.assignmentTime = data.assignmentTime; + } + + @Override + public int hashCode() { + /* + * See notes in equals() + */ + final int prime = 31; + int result = 1; + long temp = Double.doubleToLongBits(assignmentTime); + result = prime * result + (int) (temp ^ (temp >>> 32)); + result = prime * result + personCount; + result = prime * result + personRanges.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + /* + * This boilerplate implementation works since the person ranges are sorted and + * joined during the build process, leaving the person ranges unambiguously + * ordered. + */ + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + if (Double.doubleToLongBits(assignmentTime) != Double.doubleToLongBits(other.assignmentTime)) { + return false; + } + if (personCount != other.personCount) { + return false; + } + if (!personRanges.equals(other.personRanges)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Data [personCount="); + builder.append(personCount); + builder.append(", personRanges="); + builder.append(personRanges); + builder.append(", assignmentTime="); + builder.append(assignmentTime); + builder.append(", locked="); + builder.append(locked); + builder.append("]"); + return builder.toString(); + } + + } + + private final Data data; + + /** + * Returns a new builder instance for this class + */ + public static Builder builder() { + return new Builder(new Data()); + } + + /** + * Builder class for PeoplePluginData + */ + public static class Builder implements PluginDataBuilder { + private Data data; + + private void ensureDataMutability() { + if (data.locked) { + data = new Data(data); + } + } + + private void validate() { + if (data.personCount >= 0) { + for (PersonRange personRange : data.personRanges) { + if (personRange.getHighPersonId() >= data.personCount) { + throw new ContractException(PersonError.INVALID_PERSON_COUNT); + } + } + } + } + + private void ensureImmutability() { + if (!data.locked) { + + /* + * Copy the person ranges and sort them by their low values. + * + * These entries can overlap, so we will rebuild them so that all overlaps are + * gone + * + */ + List list = new ArrayList<>(data.personRanges); + Collections.sort(list); + + // create a list of person ranges that will hold the + // non-overlapping entries + List list2 = new ArrayList<>(); + + // a and b are the low and high values of a person range that + // has yet to be added + int a = -1; + int b = -1; + + /* + * low and high are the values from the current person range + */ + int low; + int high; + + /* + * Count is the number of person id values we will be recording It will be used + * to set the size of the person ids array list so that it won't have wasted + * allocations + */ + int count = 0; + for (PersonRange personRange : list) { + low = personRange.getLowPersonId(); + high = personRange.getHighPersonId(); + if (a < 0) { + a = low; + b = high; + } else { + if (low > b + 1) { + count += (b - a + 1); + list2.add(new PersonRange(a, b)); + a = low; + b = high; + } else { + if (high > b) { + b = high; + } + } + } + } + + /* + * The last values of a and b may not have been converted onto the second list + */ + if (a >= 0) { + count += (b - a + 1); + list2.add(new PersonRange(a, b)); + } + + data.personRanges = list2; + data.personIds = new ArrayList<>(count); + + if (data.personCount < 0) { + data.personCount = 0; + if (!data.personRanges.isEmpty()) { + data.personCount = data.personRanges.get(data.personRanges.size() - 1).getHighPersonId() + 1; + } + } + + // We now transfer the non-overlapping person ranges + for (PersonRange personRange : data.personRanges) { + for (int id = personRange.getLowPersonId(); id <= personRange.getHighPersonId(); id++) { + data.personIds.add(new PersonId(id)); + } + } + + // Finally, we mark the data as locked + data.locked = true; + } + } + + private Builder(Data data) { + this.data = data; + } + + /** + * Returns the PeopleInitialData resulting from the person ids collected by this + * builder. + * + * @throws ContractException + *
      + *
    • {@linkplain PersonError#INVALID_PERSON_COUNT} + * if the person count does not exceed all person + * range values
    • + *
    + */ + public PeoplePluginData build() { + validate(); + ensureImmutability(); + return new PeoplePluginData(data); + } + + /** + * Adds a person range. Overlapping person ranges are tolerated. + * + * @throws ContractException + *
      + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    + */ + public Builder addPersonRange(PersonRange personRange) { + ensureDataMutability(); + validatePersonRangeIsValid(personRange); + data.personRanges.add(personRange); + return this; + } + + /** + * Sets the person count. Defaults to one more than the maximum person id of any + * of the person ranges added. If no person ranges are added, the default is + * zero. This reflects the number of person id values that have been issued. + * Note that this is not the same as the number of people and will be greater + * than the highest id value of any existing person. + */ + public Builder setPersonCount(int personCount) { + ensureDataMutability(); + validatePersonCount(personCount); + data.personCount = personCount; + return this; + } + + /** + * Sets the time for the last person added to the population. Defaults to zero. + */ + public Builder setAssignmentTime(double assignmentTime) { + ensureDataMutability(); + data.assignmentTime = assignmentTime; + return this; + } + } + + private PeoplePluginData(Data data) { + this.data = data; + } + + /** + * Returns an unmodifiable, ordered list person ids contained by this people + * plugin data. + */ + public List getPersonIds() { + return Collections.unmodifiableList(data.personIds); + } + + /** + * Returns an unmodifiable, ordered, non-overlapping, list person ranges + * contained by this people plugin data. + */ + public List getPersonRanges() { + return Collections.unmodifiableList(data.personRanges); + } + + @Override + public PluginDataBuilder getCloneBuilder() { + return new Builder(data); + } + + private static void validatePersonRangeIsValid(PersonRange personRange) { + if (personRange == null) { + throw new ContractException(PersonError.NULL_PERSON_RANGE); + } + } + + private static void validatePersonCount(int personCount) { + if (personCount < 0) { + throw new ContractException(PersonError.NEGATIVE_PERSON_COUNT); + } + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + data.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof PeoplePluginData)) { + return false; + } + PeoplePluginData other = (PeoplePluginData) obj; + if (!data.equals(other.data)) { + return false; + } + return true; + } + + /** + * Returns the number of person id values that have been issued. Note that this + * is not the same as the number of people and will be greater than the highest + * id value of any existing person. + */ + public int getPersonCount() { + return data.personCount; + } + + public double getAssignmentTime() { + return data.assignmentTime; + } + + @Override + public String toString() { + StringBuilder builder2 = new StringBuilder(); + builder2.append("PeoplePluginData [data="); + builder2.append(data); + builder2.append("]"); + return builder2.toString(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/events/PersonAdditionEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/events/PersonAdditionEvent.java new file mode 100644 index 000000000..b9c7a7c60 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/events/PersonAdditionEvent.java @@ -0,0 +1,27 @@ +package gov.hhs.aspr.ms.gcm.plugins.people.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * An event to notify the plugins that a person has been fully added to the + * simulation. + */ +@Immutable +public record PersonAdditionEvent(PersonId personId) implements Event { + /** + * Constructs the event from the given person id and person construction data + * + * @throws ContractException {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null + */ + public PersonAdditionEvent { + + if (personId == null) { + throw new ContractException(PersonError.NULL_PERSON_ID); + } + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/events/PersonImminentAdditionEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/events/PersonImminentAdditionEvent.java new file mode 100644 index 000000000..65beca9df --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/events/PersonImminentAdditionEvent.java @@ -0,0 +1,38 @@ +package gov.hhs.aspr.ms.gcm.plugins.people.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonConstructionData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * An event for notifying plugins that a person is being added to the + * simulation. During this process, the person is not fully formed until all + * subscribers to the event have been invoked. + */ +@Immutable +public record PersonImminentAdditionEvent(PersonId personId, PersonConstructionData personConstructionData) + implements Event { + /** + * Constructs the event from the given person id and person construction data + * + * @throws ContractException + *
      + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain PersonError#NULL_PERSON_CONSTRUCTION_DATA} + * if the person construction data is null
    • + *
    + */ + public PersonImminentAdditionEvent { + + if (personId == null) { + throw new ContractException(PersonError.NULL_PERSON_ID); + } + if (personConstructionData == null) { + throw new ContractException(PersonError.NULL_PERSON_CONSTRUCTION_DATA); + } + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/events/PersonImminentRemovalEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/events/PersonImminentRemovalEvent.java new file mode 100644 index 000000000..62d8111e9 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/events/PersonImminentRemovalEvent.java @@ -0,0 +1,27 @@ +package gov.hhs.aspr.ms.gcm.plugins.people.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * Indicates that the given person will be removed from the simulation + * imminently, but all references to the person will still function at the time + * this event is received. No further events or plans should be generated that + * reference the person. + */ +@Immutable +public record PersonImminentRemovalEvent(PersonId personId) implements Event { + /** + * Constructs the event from the give person id + * + * @throws ContractException {@linkplain PersonError#NULL_PERSON_ID} + */ + public PersonImminentRemovalEvent { + if (personId == null) { + throw new ContractException(PersonError.NULL_PERSON_ID); + } + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/events/PersonRemovalEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/events/PersonRemovalEvent.java new file mode 100644 index 000000000..08b281f17 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/events/PersonRemovalEvent.java @@ -0,0 +1,25 @@ +package gov.hhs.aspr.ms.gcm.plugins.people.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * Indicates that the given person has been removed from the simulation. All + * references to the person are invalid. + */ +@Immutable +public record PersonRemovalEvent(PersonId personId) implements Event { + /** + * Constructs the event from the give person id + * + * @throws ContractException {@linkplain PersonError#NULL_PERSON_ID} + */ + public PersonRemovalEvent { + if (personId == null) { + throw new ContractException(PersonError.NULL_PERSON_ID); + } + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/support/PersonConstructionData.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/support/PersonConstructionData.java new file mode 100644 index 000000000..f30cce495 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/support/PersonConstructionData.java @@ -0,0 +1,106 @@ +package gov.hhs.aspr.ms.gcm.plugins.people.support; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * Container for values used to in the construction of an agent. Values are + * retrievable by class type and are ordered. + */ +@Immutable +public final class PersonConstructionData { + + private static class Data { + private List values = new ArrayList<>(); + + public Data() { + } + + public Data(Data data) { + values.addAll(data.values); + } + + } + + private final Data data; + + private PersonConstructionData(Data data) { + this.data = data; + } + + public static Builder builder() { + return new Builder(); + } + + /** + * Builder class for {@link PersonConstructionData} + */ + public static class Builder { + private Data data = new Data(); + + private Builder() { + + } + + /** + * Returns the {@link PersonConstructionData} formed from the inputs to this + * builder. + */ + public PersonConstructionData build() { + return new PersonConstructionData(new Data(data)); + } + + /** + * Adds a value to the builder. + * + * @throws ContractException {@linkplain PersonError#NULL_AUXILIARY_DATA} if the + * value is null + */ + public Builder add(Object value) { + if (value == null) { + throw new ContractException(PersonError.NULL_AUXILIARY_DATA); + } + data.values.add(value); + return this; + } + + } + + /** + * Returns the first auxiliary object that is an instance of the given class. + * Should be used only getValues() is expected to have at most one member. + */ + @SuppressWarnings("unchecked") + public Optional getValue(Class c) { + T result = null; + for (Object value : data.values) { + if (c.isAssignableFrom(value.getClass())) { + result = (T) value; + break; + } + } + return Optional.ofNullable(result); + + } + + /** + * Returns the auxiliary objects that are instances of the given class in the + * order of their addition to the builder. + */ + @SuppressWarnings("unchecked") + public List getValues(Class c) { + List result = new ArrayList<>(); + for (Object value : data.values) { + if (c.isAssignableFrom(value.getClass())) { + result.add((T) value); + } + } + return result; + + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/support/PersonError.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/support/PersonError.java new file mode 100644 index 000000000..935a86cdb --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/support/PersonError.java @@ -0,0 +1,37 @@ +package gov.hhs.aspr.ms.gcm.plugins.people.support; + +import util.errors.ContractError; +import util.errors.ContractException; + +/** + * An enumeration supporting {@link ContractException} that acts as a general + * description of the exception. + */ +public enum PersonError implements ContractError { + // person + NEGATIVE_PERSON_ID("Negative person id"), NULL_PERSON_DATA_MANAGER("Null person data manager"), + NULL_AUXILIARY_DATA("Null auxiliary data"), NULL_PERSON_CONSTRUCTION_DATA("Null person construction data"), + NULL_PERSON_ID("Null person id"), NULL_PERSON_RANGE("Null person range"), UNKNOWN_PERSON_ID("Unknown person id"), + DUPLICATE_PERSON_ID("Duplicate person addition"), ILLEGAL_PERSON_RANGE("Illegal person range"), + INVALID_PERSON_COUNT("The person count must exceed the highest person range value"), + NEGATIVE_PERSON_COUNT("Negative person count"), + PERSON_ASSIGNMENT_TIME_IN_FUTURE( + "The person assignment time from the plugin data is greater than the current time in the simulation"), + NON_ONE_TO_ONE_MAPPING("Mapping of initial data person ids to simulation person is not one-to-one"), + NULL_PEOPLE_PLUGIN_DATA("null people plugin data"), + + NULL_SUGGESTED_POPULATION_SIZE("Scenario identifier is null"), + NEGATIVE_SUGGGESTED_POPULATION("Suggested population size is negative"), + NEGATIVE_GROWTH_PROJECTION("Growth projection count is negative"); + + private final String description; + + private PersonError(final String description) { + this.description = description; + } + + @Override + public String getDescription() { + return description; + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/support/PersonId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/support/PersonId.java new file mode 100644 index 000000000..0e71f3308 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/support/PersonId.java @@ -0,0 +1,59 @@ +package gov.hhs.aspr.ms.gcm.plugins.people.support; + +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * Identifier for all people + */ +@Immutable +public final class PersonId implements Comparable { + + private final int id; + + /** + * Consructs the person id + * + * @throws ContractException {@linkplain PersonError#NEGATIVE_PERSON_ID} + */ + public PersonId(int id) { + if (id < 0) { + throw new ContractException(PersonError.NEGATIVE_PERSON_ID); + } + this.id = id; + } + + public int getValue() { + return id; + } + + @Override + public int compareTo(PersonId personId) { + return Integer.compare(id, personId.id); + } + + @Override + public int hashCode() { + return id; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof PersonId)) { + return false; + } + PersonId other = (PersonId) obj; + if (id != other.id) { + return false; + } + return true; + } + + @Override + public String toString() { + return Integer.toString(id); + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/support/PersonRange.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/support/PersonRange.java new file mode 100644 index 000000000..fdb336757 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/support/PersonRange.java @@ -0,0 +1,117 @@ +package gov.hhs.aspr.ms.gcm.plugins.people.support; + +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * Represents a range of person id values + */ +@Immutable +public class PersonRange implements Comparable { + + private final int lowPersonId; + private final int highPersonId; + + /** + * Constructs an inclusive range of person id values + * + * @throws ContractException + *
      + *
    • {@linkplain PersonError#NEGATIVE_PERSON_ID} if + * a negative id is used
    • + *
    • + *
    • {@linkplain PersonError#ILLEGAL_PERSON_RANGE} + * if the low person id exceeds the high person id + *
    • + *
    • + *
    + */ + public PersonRange(int lowPersonId, int highPersonId) { + + if (lowPersonId > highPersonId) { + throw new ContractException(PersonError.ILLEGAL_PERSON_RANGE); + } + if (lowPersonId < 0) { + throw new ContractException(PersonError.NEGATIVE_PERSON_ID); + } + this.lowPersonId = lowPersonId; + this.highPersonId = highPersonId; + } + + /** + * Returns the lowest person id (inclusive) of this range + */ + public int getLowPersonId() { + return lowPersonId; + } + + /** + * Returns the highest person id (inclusive) of this range + */ + public int getHighPersonId() { + return highPersonId; + } + + /** + * Compares to another person range by ascending order for lower bound and then + * ascending order by upper bound + */ + @Override + public int compareTo(PersonRange personRange) { + int result = Integer.compare(this.lowPersonId, personRange.lowPersonId); + if (result == 0) { + result = Integer.compare(this.highPersonId, personRange.highPersonId); + } + return result; + } + + /** + * Boiler plate implementation of hash code + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + highPersonId; + result = prime * result + lowPersonId; + return result; + } + + /** + * Two person ranges are equal if and only if they have the same upper and lower + * bounds. + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof PersonRange)) { + return false; + } + PersonRange other = (PersonRange) obj; + if (highPersonId != other.highPersonId) { + return false; + } + if (lowPersonId != other.lowPersonId) { + return false; + } + return true; + } + + /** + * Returns the string version of a person range in the form: PersonRange + * [lowPersonId=4, highPersonId=10] + */ + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("PersonRange [lowPersonId="); + builder.append(lowPersonId); + builder.append(", highPersonId="); + builder.append(highPersonId); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/testsupport/PeopleTestPluginFactory.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/testsupport/PeopleTestPluginFactory.java new file mode 100644 index 000000000..cc54d1155 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/people/testsupport/PeopleTestPluginFactory.java @@ -0,0 +1,211 @@ +package gov.hhs.aspr.ms.gcm.plugins.people.testsupport; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePlugin; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeoplePluginData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.StochasticsError; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import util.errors.ContractException; + +/** + * A static test support class for the {@linkplain PeoplePlugin}. Provides + * convenience methods for obtaining standarized PluginData for the listed + * Plugin. + *

    + * Also contains factory methods to obtain a list of plugins that is the minimal + * set needed to adequately test this Plugin that can be utilized with + *

    + * {@link TestSimulation#execute} + */ +public class PeopleTestPluginFactory { + + private PeopleTestPluginFactory() { + } + + private static class Data { + private PeoplePluginData peoplePluginData; + private StochasticsPluginData stochasticsPluginData; + private TestPluginData testPluginData; + + private Data(long seed, TestPluginData testPluginData) { + + this.peoplePluginData = getStandardPeoplePluginData(); + this.stochasticsPluginData = getStandardStochasticsPluginData(seed); + this.testPluginData = testPluginData; + } + } + + /** + * Factory class that facilitates the building of {@linkplain PluginData} with + * the various setter methods. + */ + public static class Factory { + private Data data; + + private Factory(Data data) { + this.data = data; + } + + /** + * Returns a list of plugins containing a People, Stochastic and Test Plugin + * built from the contributed PluginDatas. + *
      + *
    • PeoplePlugin is defaulted to one formed from + * {@link PeopleTestPluginFactory#getStandardPeoplePluginData}
    • + *
    • StochasticsPlugin is defaulted to one formed from + * {@link PeopleTestPluginFactory#getStandardStochasticsPluginData}
    • + *
    • TestPlugin is formed from the TestPluginData passed into + * {@link PeopleTestPluginFactory#factory}
    • + *
    + */ + public List getPlugins() { + List pluginsToAdd = new ArrayList<>(); + + Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(this.data.peoplePluginData); + + Plugin stochasticPlugin = StochasticsPlugin.getStochasticsPlugin(this.data.stochasticsPluginData); + + Plugin testPlugin = TestPlugin.getTestPlugin(this.data.testPluginData); + + pluginsToAdd.add(peoplePlugin); + pluginsToAdd.add(stochasticPlugin); + pluginsToAdd.add(testPlugin); + + return pluginsToAdd; + } + + /** + * Sets the {@link PeoplePluginData} in this Factory. This explicit instance of + * pluginData will be used to create a PeoplePlugin + * + * @throws ContractException {@linkplain PersonError#NULL_PEOPLE_PLUGIN_DATA} if + * the passed in pluginData is null + */ + public Factory setPeoplePluginData(PeoplePluginData peoplePluginData) { + if (peoplePluginData == null) { + throw new ContractException(PersonError.NULL_PEOPLE_PLUGIN_DATA); + } + this.data.peoplePluginData = peoplePluginData; + return this; + } + + /** + * Sets the {@link StochasticsPluginData} in this Factory. This explicit + * instance of pluginData will be used to create a StochasticsPlugin + * + * @throws ContractException {@linkplain StochasticsError#NULL_STOCHASTICS_PLUGIN_DATA} + * if the passed in pluginData is null + */ + public Factory setStochasticsPluginData(StochasticsPluginData stochasticsPluginData) { + if (stochasticsPluginData == null) { + throw new ContractException(StochasticsError.NULL_STOCHASTICS_PLUGIN_DATA); + } + this.data.stochasticsPluginData = stochasticsPluginData; + return this; + } + + } + + /** + * Creates a Factory that facilitates the creation of a minimal set of plugins + * needed to adequately test the {@link PeoplePlugin} by generating: + *
      + *
    • {@link PeoplePluginData}
    • + *
    • {@link StochasticsPluginData}
    • + *
    + * either directly (by default) via + *
      + *
    • {@link #getStandardPeoplePluginData}, + *
    • {@link #getStandardStochasticsPluginData}
    • + *
    + * or explicitly set via + *
      + *
    • {@link Factory#setPeoplePluginData}, + *
    • {@link Factory#setStochasticsPluginData}
    • + *
    + * via the {@link Factory#getPlugins()} method. + * + * @throws ContractException {@linkplain NucleusError#NULL_PLUGIN_DATA} if + * testPluginData is null + */ + public static Factory factory(long seed, TestPluginData testPluginData) { + if (testPluginData == null) { + throw new ContractException(NucleusError.NULL_PLUGIN_DATA); + } + return new Factory(new Data(seed, testPluginData)); + } + + /** + * Creates a Factory that facilitates the creation of a minimal set of plugins + * needed to adequately test the {@link PeoplePlugin} by generating: + *
      + *
    • {@link PeoplePluginData}
    • + *
    • {@link StochasticsPluginData}
    • + *
    + * either directly (by default) via + *
      + *
    • {@link #getStandardPeoplePluginData}, + *
    • {@link #getStandardStochasticsPluginData}
    • + *
    + * or explicitly set via + *
      + *
    • {@link Factory#setPeoplePluginData}, + *
    • {@link Factory#setStochasticsPluginData}
    • + *
    + * via the {@link Factory#getPlugins()} method. + * + * @throws ContractException {@linkplain NucleusError#NULL_ACTOR_CONTEXT_CONSUMER} + * if consumer is null + */ + public static Factory factory(long seed, Consumer consumer) { + if (consumer == null) { + throw new ContractException(NucleusError.NULL_ACTOR_CONTEXT_CONSUMER); + } + TestPluginData testPluginData = TestPluginData.builder()// + .addTestActorPlan("actor", new TestActorPlan(0, consumer))// + .build(); + + return factory(seed, testPluginData); + } + + /** + * Returns a standardized PeoplePluginData that is minimally adequate for + * testing the PeoplePlugin. The resulting PeoplePluginData will be empty + *
      + *
    • the equivalent of PeoplePluginData.builder().build() + *
    + */ + public static PeoplePluginData getStandardPeoplePluginData() { + PeoplePluginData.Builder peopleBuilder = PeoplePluginData.builder(); + + return peopleBuilder.build(); + } + + /** + * Returns a standardized StochasticsPluginData that is minimally adequate for + * testing the PeoplePlugin The resulting StochasticsPluginData will include: + *
      + *
    • a seed based on the nextLong of a RandomGenerator seeded from the passed + * in seed
    • + *
    + */ + public static StochasticsPluginData getStandardStochasticsPluginData(long seed) { + WellState wellState = WellState.builder().setSeed(seed).build(); + return StochasticsPluginData.builder().setMainRNGState(wellState).build(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/PersonPropertiesPlugin.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/PersonPropertiesPlugin.java new file mode 100644 index 000000000..456e35df5 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/PersonPropertiesPlugin.java @@ -0,0 +1,112 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties; + +import java.util.Optional; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePluginId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.reports.PersonPropertyInteractionReport; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.reports.PersonPropertyInteractionReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.reports.PersonPropertyReport; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.reports.PersonPropertyReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyError; +import gov.hhs.aspr.ms.gcm.plugins.regions.RegionsPluginId; +import net.jcip.annotations.ThreadSafe; +import util.errors.ContractException; + +/** + * A plugin providing a person property management to the simulation. + */ +@ThreadSafe +public final class PersonPropertiesPlugin { + + private static class Data { + private PersonPropertiesPluginData personPropertiesPluginData; + private PersonPropertyReportPluginData personPropertyReportPluginData; + private PersonPropertyInteractionReportPluginData personPropertyInteractionReportPluginData; + } + + private PersonPropertiesPlugin() { + + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Builder() { + } + + private Data data = new Data(); + + private void validate() { + if (data.personPropertiesPluginData == null) { + throw new ContractException(PersonPropertyError.NULL_PERSON_PROPERTY_PLUGN_DATA); + } + } + + /** + * Builds the PersonPropertiesPlugin from the collected inputs + * + * @throws ContractException {@linkplain PersonPropertyError#NULL_PERSON_PROPERTY_PLUGN_DATA} + * if the personPropertiesPluginData is null + */ + public Plugin getPersonPropertyPlugin() { + + validate(); + Plugin.Builder builder = Plugin.builder();// + builder.setPluginId(PersonPropertiesPluginId.PLUGIN_ID);// + builder.addPluginData(data.personPropertiesPluginData);// + if (data.personPropertyInteractionReportPluginData != null) { + builder.addPluginData(data.personPropertyInteractionReportPluginData);// + } + if (data.personPropertyReportPluginData != null) { + builder.addPluginData(data.personPropertyReportPluginData);// + } + builder.addPluginDependency(PeoplePluginId.PLUGIN_ID);// + builder.addPluginDependency(RegionsPluginId.PLUGIN_ID);// + builder.setInitializer((c) -> { + PersonPropertiesPluginData pluginData = c.getPluginData(PersonPropertiesPluginData.class).get(); + c.addDataManager(new PersonPropertiesDataManager(pluginData)); + + Optional optional1 = c + .getPluginData(PersonPropertyReportPluginData.class); + if (optional1.isPresent()) { + PersonPropertyReportPluginData personPropertyReportPluginData = optional1.get(); + c.addReport(new PersonPropertyReport(personPropertyReportPluginData)::init); + } + + Optional optional2 = c + .getPluginData(PersonPropertyInteractionReportPluginData.class); + if (optional2.isPresent()) { + PersonPropertyInteractionReportPluginData personPropertyInteractionReportPluginData = optional2 + .get(); + c.addReport(new PersonPropertyInteractionReport(personPropertyInteractionReportPluginData)::init); + } + + }); + return builder.build(); + + } + + public Builder setPersonPropertiesPluginData(PersonPropertiesPluginData personPropertiesPluginData) { + data.personPropertiesPluginData = personPropertiesPluginData; + return this; + } + + public Builder setPersonPropertyReportPluginData( + PersonPropertyReportPluginData personPropertyReportPluginData) { + data.personPropertyReportPluginData = personPropertyReportPluginData; + return this; + } + + public Builder setPersonPropertyInteractionReportPluginData( + PersonPropertyInteractionReportPluginData personPropertyInteractionReportPluginData) { + data.personPropertyInteractionReportPluginData = personPropertyInteractionReportPluginData; + return this; + } + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/PersonPropertiesPluginId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/PersonPropertiesPluginId.java new file mode 100644 index 000000000..7d0e097d5 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/PersonPropertiesPluginId.java @@ -0,0 +1,13 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; + +/** + * Static plugin id implementation for the person properties plugin + */ +public final class PersonPropertiesPluginId implements PluginId { + public final static PluginId PLUGIN_ID = new PersonPropertiesPluginId(); + + private PersonPropertiesPluginId() { + }; +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/datamanagers/PersonPropertiesDataManager.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/datamanagers/PersonPropertiesDataManager.java new file mode 100644 index 000000000..f5061e302 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/datamanagers/PersonPropertiesDataManager.java @@ -0,0 +1,1083 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Supplier; + +import org.apache.commons.math3.util.FastMath; +import org.apache.commons.math3.util.Pair; + +import gov.hhs.aspr.ms.gcm.nucleus.DataManager; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.nucleus.EventFilter; +import gov.hhs.aspr.ms.gcm.nucleus.IdentifiableFunctionMap; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.events.PersonImminentAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.events.PersonRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonConstructionData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.events.PersonPropertyDefinitionEvent; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.events.PersonPropertyUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyDefinitionInitialization; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyError; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyValueInitialization; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionError; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.BooleanPropertyManager; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.DoublePropertyManager; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.EnumPropertyManager; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.FloatPropertyManager; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.IndexedPropertyManager; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.IntPropertyManager; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.ObjectPropertyManager; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.arraycontainers.DoubleValueContainer; +import util.errors.ContractException; + +/** + * Mutable data manager for person properties + */ +public final class PersonPropertiesDataManager extends DataManager { + private static enum EventFunctionId { + PERSON_PROPERTY_ID, // + REGION_ID, // + PERSON_ID, CURRENT_VALUE, PREVIOUS_VALUE;// + } + + private static record PersonPropertyDefinitionMutationEvent( + PersonPropertyDefinitionInitialization propertyDefinitionInitialization) implements Event { + } + + private static record PersonPropertyUpdateMutationEvent(PersonId personId, PersonPropertyId personPropertyId, + Object personPropertyValue) implements Event { + } + + private static void validatePropertyMutability(final PropertyDefinition propertyDefinition) { + if (!propertyDefinition.propertyValuesAreMutable()) { + throw new ContractException(PropertyError.IMMUTABLE_VALUE); + } + } + + private Map propertyDefinitions; + + private Map propertyDefinitionTimes; + + private final Map propertyValues = new LinkedHashMap<>(); + + private Map propertyTrackingPolicies; + + private final Map propertyTimes = new LinkedHashMap<>(); + + private final Map nonDefaultBearingPropertyIds = new LinkedHashMap<>(); + + private boolean[] nonDefaultChecks = new boolean[0]; + + private PeopleDataManager peopleDataManager; + + private RegionsDataManager regionsDataManager; + + private DataManagerContext dataManagerContext; + private final PersonPropertiesPluginData personPropertiesPluginData; + + /* + * We keep the person records in a list rather than a map so that we can + * retrieve a person record by index (personId). + */ + private IdentifiableFunctionMap functionMap = // + IdentifiableFunctionMap.builder(PersonPropertyUpdateEvent.class)// + .put(EventFunctionId.PERSON_PROPERTY_ID, e -> e.personPropertyId())// + .put(EventFunctionId.REGION_ID, e -> regionsDataManager.getPersonRegion(e.personId()))// + .put(EventFunctionId.PERSON_ID, e -> e.personId())// + .put(EventFunctionId.CURRENT_VALUE, e -> e.currentPropertyValue()) + .put(EventFunctionId.PREVIOUS_VALUE, e -> e.previousPropertyValue()).build();// + + /** + * Constructs the person property data manager from the given plugin data + * + * @throws ContractException {@linkplain PersonPropertyError#NULL_PERSON_PROPERTY_PLUGN_DATA} + * if the plugin data is null + */ + public PersonPropertiesDataManager(PersonPropertiesPluginData personPropertiesPluginData) { + if (personPropertiesPluginData == null) { + throw new ContractException(PersonPropertyError.NULL_PERSON_PROPERTY_PLUGN_DATA); + } + this.personPropertiesPluginData = personPropertiesPluginData; + } + + private void addNonDefaultProperty(PersonPropertyId personPropertyId) { + nonDefaultBearingPropertyIds.put(personPropertyId, nonDefaultBearingPropertyIds.size()); + nonDefaultChecks = new boolean[nonDefaultBearingPropertyIds.size()]; + } + + private void clearNonDefaultChecks() { + for (int i = 0; i < nonDefaultChecks.length; i++) { + nonDefaultChecks[i] = false; + } + } + + /** + * Defines a new person property + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_DEFINITION_INITIALIZATION} + * if the property definition initialization is + * null
    • + *
    • {@linkplain PropertyError#DUPLICATE_PROPERTY_DEFINITION} + * if the person property already exists
    • + *
    • {@linkplain PropertyError#INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT} + * if the property definition has no default value and + * there is no included value assignment for some + * extant person
    • + *
    + */ + public void definePersonProperty(PersonPropertyDefinitionInitialization propertyDefinitionInitialization) { + dataManagerContext + .releaseMutationEvent(new PersonPropertyDefinitionMutationEvent(propertyDefinitionInitialization)); + } + + /** + * Expands the capacity of data structures to hold people by the given count. + * Used to more efficiently prepare for multiple population additions. + * + * @throws ContractException {@linkplain PersonError#NEGATIVE_GROWTH_PROJECTION} + * if the count is negative + */ + public void expandCapacity(final int count) { + if (count < 0) { + throw new ContractException(PersonError.NEGATIVE_GROWTH_PROJECTION); + } + if (count > 0) { + for (final PersonPropertyId personPropertyId : propertyValues.keySet()) { + IndexedPropertyManager indexedPropertyManager = propertyValues.get(personPropertyId); + indexedPropertyManager.incrementCapacity(count); + + DoubleValueContainer doubleValueContainer = propertyTimes.get(personPropertyId); + if (doubleValueContainer != null) { + doubleValueContainer.setCapacity(doubleValueContainer.getCapacity() + count); + } + } + } + } + + /** + * Returns an event filter used to subscribe to + * {@link PersonPropertyDefinitionEvent} events. Matches all such events. + */ + public EventFilter getEventFilterForPersonPropertyDefinitionEvent() { + return EventFilter.builder(PersonPropertyDefinitionEvent.class)// + .build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link PersonPropertyUpdateEvent} events. Matches all such events. + */ + public EventFilter getEventFilterForPersonPropertyUpdateEvent() { + return EventFilter.builder(PersonPropertyUpdateEvent.class).build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link PersonPropertyUpdateEvent} events. Matches on person property id and + * person id. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the person property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the person property id is not known
    • + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person id is not known
    • + *
    + */ + public EventFilter getEventFilterForPersonPropertyUpdateEvent(PersonId personId, + PersonPropertyId personPropertyId) { + validatePersonPropertyId(personPropertyId); + validatePersonExists(personId); + return EventFilter.builder(PersonPropertyUpdateEvent.class)// + .addFunctionValuePair(functionMap.get(EventFunctionId.PERSON_PROPERTY_ID), personPropertyId)// + .addFunctionValuePair(functionMap.get(EventFunctionId.PERSON_ID), personId)// + .build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link PersonPropertyUpdateEvent} events. Matches on person property id. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the person property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the person property id is not known
    • + *
    + */ + public EventFilter getEventFilterForPersonPropertyUpdateEvent( + PersonPropertyId personPropertyId) { + validatePersonPropertyId(personPropertyId); + return EventFilter.builder(PersonPropertyUpdateEvent.class)// + .addFunctionValuePair(functionMap.get(EventFunctionId.PERSON_PROPERTY_ID), personPropertyId)// + .build(); + + } + + /** + * Returns an event filter used to subscribe to + * {@link PersonPropertyUpdateEvent} events. Matches on person property id and + * property value. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the person property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the person property id is not known
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_VALUE} + * if the person property value is null
    • + *
    + */ + public EventFilter getEventFilterForPersonPropertyUpdateEvent( + PersonPropertyId personPropertyId, Object propertyValue, boolean useCurrentValue) { + validatePersonPropertyId(personPropertyId); + validatePersonPropertyValueNotNull(propertyValue); + EventFunctionId propertyValueEnum = useCurrentValue ? EventFunctionId.CURRENT_VALUE + : EventFunctionId.PREVIOUS_VALUE; + return EventFilter.builder(PersonPropertyUpdateEvent.class) + .addFunctionValuePair(functionMap.get(EventFunctionId.PERSON_PROPERTY_ID), personPropertyId)// + .addFunctionValuePair(functionMap.get(propertyValueEnum), propertyValue).build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link PersonPropertyUpdateEvent} events. Matches on region id and person + * property id. + * + * @throws ContractException + *
      + *
    • {@linkplain RegionError#NULL_REGION_ID} if the + * region id is null
    • + *
    • {@linkplain RegionError#UNKNOWN_REGION_ID} if + * the region id is not known
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the person property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the person property id is not known
    • + *
    + */ + public EventFilter getEventFilterForPersonPropertyUpdateEvent(RegionId regionId, + PersonPropertyId personPropertyId) { + validatePersonPropertyId(personPropertyId); + validateRegionId(regionId); + return EventFilter.builder(PersonPropertyUpdateEvent.class)// + .addFunctionValuePair(functionMap.get(EventFunctionId.PERSON_PROPERTY_ID), personPropertyId)// + .addFunctionValuePair(functionMap.get(EventFunctionId.REGION_ID), regionId)// + .build(); + } + + private IndexedPropertyManager getIndexedPropertyManager(final PropertyDefinition propertyDefinition) { + + Supplier> indexIteratorSupplier = peopleDataManager::getPersonIndexIterator; + + IndexedPropertyManager indexedPropertyManager; + if (propertyDefinition.getType() == Boolean.class) { + indexedPropertyManager = new BooleanPropertyManager(propertyDefinition, indexIteratorSupplier); + } else if (propertyDefinition.getType() == Float.class) { + indexedPropertyManager = new FloatPropertyManager(propertyDefinition, indexIteratorSupplier); + } else if (propertyDefinition.getType() == Double.class) { + indexedPropertyManager = new DoublePropertyManager(propertyDefinition, indexIteratorSupplier); + } else if (propertyDefinition.getType() == Byte.class) { + indexedPropertyManager = new IntPropertyManager(propertyDefinition, indexIteratorSupplier); + } else if (propertyDefinition.getType() == Short.class) { + indexedPropertyManager = new IntPropertyManager(propertyDefinition, indexIteratorSupplier); + } else if (propertyDefinition.getType() == Integer.class) { + indexedPropertyManager = new IntPropertyManager(propertyDefinition, indexIteratorSupplier); + } else if (propertyDefinition.getType() == Long.class) { + indexedPropertyManager = new IntPropertyManager(propertyDefinition, indexIteratorSupplier); + } else if (Enum.class.isAssignableFrom(propertyDefinition.getType())) { + indexedPropertyManager = new EnumPropertyManager(propertyDefinition, indexIteratorSupplier); + } else { + indexedPropertyManager = new ObjectPropertyManager(propertyDefinition, indexIteratorSupplier); + } + return indexedPropertyManager; + } + + /** + * Returns the list(no duplicates) people who have the given person property + * value. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the person property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the person property id is unknown
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_VALUE} + * if the person property value is null
    • + *
    • {@linkplain PropertyError#INCOMPATIBLE_VALUE} + * if the person property value is not compatible with + * the property definition associated with the given + * person property id
    • + *
    + */ + public List getPeopleWithPropertyValue(final PersonPropertyId personPropertyId, + final Object personPropertyValue) { + validatePersonPropertyId(personPropertyId); + final PropertyDefinition propertyDefinition = propertyDefinitions.get(personPropertyId); + validatePersonPropertyValueNotNull(personPropertyValue); + validateValueCompatibility(personPropertyId, propertyDefinition, personPropertyValue); + + List result; + + int count = 0; + + final IndexedPropertyManager indexedPropertyManager = propertyValues.get(personPropertyId); + + /* + * We are not maintaining a map from property values to people. We first + * determine the number of people who will be returned so that we can size the + * resulting ArrayList properly. + */ + if (indexedPropertyManager != null) { + final int n = peopleDataManager.getPersonIdLimit(); + + for (int personIndex = 0; personIndex < n; personIndex++) { + if (peopleDataManager.personIndexExists(personIndex)) { + final PersonId personId = peopleDataManager.getBoxedPersonId(personIndex).get(); + final Object propertyValue = indexedPropertyManager.getPropertyValue(personId.getValue()); + if (propertyValue.equals(personPropertyValue)) { + count++; + } + } + } + + /* + * Now we fill the list. + */ + result = new ArrayList<>(count); + + for (int personIndex = 0; personIndex < n; personIndex++) { + if (peopleDataManager.personIndexExists(personIndex)) { + final PersonId personId = peopleDataManager.getBoxedPersonId(personIndex).get(); + final Object propertyValue = indexedPropertyManager.getPropertyValue(personId.getValue()); + if (propertyValue.equals(personPropertyValue)) { + result.add(personId); + } + } + } + } else { + result = new ArrayList<>(); + } + return result; + + } + + /** + * Returns the number of people who have the given person property value. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the person property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the person property id is unknown
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_VALUE} + * if the person property value is null
    • + *
    • {@linkplain PropertyError#INCOMPATIBLE_VALUE} + * if the person property value is not compatible with + * the property definition associated with the given + * person property id
    • + *
    + */ + public int getPersonCountForPropertyValue(final PersonPropertyId personPropertyId, + final Object personPropertyValue) { + + validatePersonPropertyId(personPropertyId); + final PropertyDefinition propertyDefinition = propertyDefinitions.get(personPropertyId); + validatePersonPropertyValueNotNull(personPropertyValue); + validateValueCompatibility(personPropertyId, propertyDefinition, personPropertyValue); + + /* + * We are not maintaining a map from property values to people. We first + * determine the number of people who will be returned so that we can size the + * resulting ArrayList properly. + */ + int count = 0; + + final IndexedPropertyManager indexedPropertyManager = propertyValues.get(personPropertyId); + + if (indexedPropertyManager != null) { + final int n = peopleDataManager.getPersonIdLimit(); + + for (int personIndex = 0; personIndex < n; personIndex++) { + if (peopleDataManager.personIndexExists(personIndex)) { + final PersonId personId = peopleDataManager.getBoxedPersonId(personIndex).get(); + final Object propertyValue = indexedPropertyManager.getPropertyValue(personId.getValue()); + if (propertyValue.equals(personPropertyValue)) { + count++; + } + } + } + } + return count; + + } + + /** + * Returns the property definition for the given person property id + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the person property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the person property id is unknown
    • + *
    + */ + public PropertyDefinition getPersonPropertyDefinition(final PersonPropertyId personPropertyId) { + validatePersonPropertyId(personPropertyId); + return propertyDefinitions.get(personPropertyId); + } + + /** + * Returns the person property ids + */ + @SuppressWarnings("unchecked") + public Set getPersonPropertyIds() { + + final Set result = new LinkedHashSet<>(propertyDefinitions.keySet().size()); + for (final PersonPropertyId personPropertyId : propertyDefinitions.keySet()) { + result.add((T) personPropertyId); + } + + return result; + } + + /** + * Returns the time when the person's property was last assigned or zero if the + * value has never been assigned. + * + * @throws ContractException + *
      + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person id is unknown
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the person property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the person property id is unknown
    • + *
    • {@linkplain PersonPropertyError#PROPERTY_ASSIGNMENT_TIME_NOT_TRACKED} + * if the person property does not have time tracking + * turned on in the associated property + * definition
    • + *
    + */ + public double getPersonPropertyTime(final PersonId personId, final PersonPropertyId personPropertyId) { + validatePersonExists(personId); + validatePersonPropertyId(personPropertyId); + validatePersonPropertyAssignmentTimesTracked(personPropertyId); + DoubleValueContainer doubleValueContainer = propertyTimes.get(personPropertyId); + if(doubleValueContainer == null) { + return propertyDefinitionTimes.get(personPropertyId); + } + return doubleValueContainer.getValue(personId.getValue()); + } + + /** + * Returns true if and only if the property assignment times are being tracked + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the person property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the person property id is unknown
    • + *
    + */ + public boolean isPropertyTimeTracked(final PersonPropertyId personPropertyId) { + validatePersonPropertyId(personPropertyId); + return propertyTrackingPolicies.get(personPropertyId); + } + + /** + * Returns the default property definition time + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the person property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the person property id is unknown
    • + *
    + */ + public double getPropertyDefinitionTime(final PersonPropertyId personPropertyId) { + validatePersonPropertyId(personPropertyId); + return propertyDefinitionTimes.get(personPropertyId); + } + + /** + * Returns the current value of the person's property + * + * @throws ContractException + *
      + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person id is unknown
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the person property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the person property id is unknown
    • + *
    + */ + @SuppressWarnings("unchecked") + public T getPersonPropertyValue(final PersonId personId, final PersonPropertyId personPropertyId) { + validatePersonExists(personId); + validatePersonPropertyId(personPropertyId); + IndexedPropertyManager indexedPropertyManager = propertyValues.get(personPropertyId); + if (indexedPropertyManager != null) { + return indexedPropertyManager.getPropertyValue(personId.getValue()); + } + PropertyDefinition propertyDefinition = propertyDefinitions.get(personPropertyId); + return (T) propertyDefinition.getDefaultValue().get(); + } + + private void handlePersonImminentAdditionEvent(final DataManagerContext dataManagerContext, + final PersonImminentAdditionEvent personImminentAdditionEvent) { + + PersonConstructionData personConstructionData = personImminentAdditionEvent.personConstructionData(); + + PersonId personId = personImminentAdditionEvent.personId(); + + List personPropertyAssignments = personConstructionData + .getValues(PersonPropertyValueInitialization.class); + + for (final PersonPropertyValueInitialization personPropertyAssignment : personPropertyAssignments) { + PersonPropertyId personPropertyId = personPropertyAssignment.getPersonPropertyId(); + final Object personPropertyValue = personPropertyAssignment.getValue(); + validatePersonPropertyId(personPropertyId); + validatePersonPropertyValueNotNull(personPropertyValue); + final PropertyDefinition propertyDefinition = propertyDefinitions.get(personPropertyId); + validateValueCompatibility(personPropertyId, propertyDefinition, personPropertyValue); + } + + if (!nonDefaultBearingPropertyIds.isEmpty()) { + clearNonDefaultChecks(); + for (final PersonPropertyValueInitialization personPropertyAssignment : personPropertyAssignments) { + PersonPropertyId personPropertyId = personPropertyAssignment.getPersonPropertyId(); + markAssigned(personPropertyId); + } + verifyNonDefaultChecks(); + } + + for (final PersonPropertyValueInitialization personPropertyAssignment : personPropertyAssignments) { + PersonPropertyId personPropertyId = personPropertyAssignment.getPersonPropertyId(); + final Object personPropertyValue = personPropertyAssignment.getValue(); + int pId = personId.getValue(); + IndexedPropertyManager propertyManager = propertyValues.get(personPropertyId); + if (propertyManager == null) { + PropertyDefinition propertyDefinition = propertyDefinitions.get(personPropertyId); + propertyManager = getIndexedPropertyManager(propertyDefinition); + propertyValues.put(personPropertyId, propertyManager); + } + propertyManager.setPropertyValue(pId, personPropertyValue); + + Boolean trackTimes = propertyTrackingPolicies.get(personPropertyId); + if (trackTimes) { + DoubleValueContainer doubleValueContainer = propertyTimes.get(personPropertyId); + if (doubleValueContainer == null) { + Double defaultTime = propertyDefinitionTimes.get(personPropertyId); + doubleValueContainer = new DoubleValueContainer(defaultTime, + peopleDataManager::getPersonIndexIterator); + propertyTimes.put(personPropertyId, doubleValueContainer); + } + doubleValueContainer.setValue(pId, dataManagerContext.getTime()); + } + } + + } + + private void handlePersonImminentRemovalEvent(final DataManagerContext dataManagerContext, + final PersonRemovalEvent personRemovalEvent) { + PersonId personId = personRemovalEvent.personId(); + + for (final PersonPropertyId personPropertyId : propertyValues.keySet()) { + final IndexedPropertyManager indexedPropertyManager = propertyValues.get(personPropertyId); + indexedPropertyManager.removeId(personId.getValue()); + } + } + + private void handlePersonPropertyDefinitionMutationEvent(DataManagerContext dataManagerContext, + PersonPropertyDefinitionMutationEvent personPropertyDefinitionMutationEvent) { + PersonPropertyDefinitionInitialization propertyDefinitionInitialization = personPropertyDefinitionMutationEvent + .propertyDefinitionInitialization(); + validatePropertyDefinitionInitializationNotNull(propertyDefinitionInitialization); + PersonPropertyId personPropertyId = propertyDefinitionInitialization.getPersonPropertyId(); + PropertyDefinition propertyDefinition = propertyDefinitionInitialization.getPropertyDefinition(); + + validatePersonPropertyIdIsUnknown(personPropertyId); + boolean checkAllPeopleHaveValues = propertyDefinition.getDefaultValue().isEmpty(); + List> pairs = propertyDefinitionInitialization.getPropertyValues(); + for (Pair pair : pairs) { + PersonId personId = pair.getFirst(); + validatePersonExists(personId); + } + + if (checkAllPeopleHaveValues) { + addNonDefaultProperty(personPropertyId); + int idLimit = peopleDataManager.getPersonIdLimit(); + BitSet coverageSet = new BitSet(idLimit); + + for (Pair pair : pairs) { + PersonId personId = pair.getFirst(); + int pId = personId.getValue(); + coverageSet.set(pId); + } + for (int i = 0; i < idLimit; i++) { + if (peopleDataManager.personIndexExists(i)) { + boolean personCovered = coverageSet.get(i); + if (!personCovered) { + throw new ContractException(PropertyError.INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT); + } + } + } + } + + propertyDefinitions.put(personPropertyId, propertyDefinition); + propertyDefinitionTimes.put(personPropertyId, dataManagerContext.getTime()); + IndexedPropertyManager propertyManager = null; + if (!pairs.isEmpty()) { + propertyManager = getIndexedPropertyManager(propertyDefinition); + propertyValues.put(personPropertyId, propertyManager); + } + + DoubleValueContainer doubleValueContainer = null; + boolean trackTimes = propertyDefinitionInitialization.trackTimes(); + propertyTrackingPolicies.put(personPropertyId, trackTimes); + if (trackTimes) { + doubleValueContainer = new DoubleValueContainer(dataManagerContext.getTime(), + peopleDataManager::getPersonIndexIterator); + propertyTimes.put(personPropertyId, doubleValueContainer); + } + + for (Pair pair : pairs) { + PersonId personId = pair.getFirst(); + int pId = personId.getValue(); + /* + * we do not have to validate the value since it is guaranteed to be consistent + * with the property definition by contract. + */ + Object value = pair.getSecond(); + propertyManager.setPropertyValue(pId, value); + } + + if (dataManagerContext.subscribersExist(PersonPropertyDefinitionEvent.class)) { + dataManagerContext.releaseObservationEvent(new PersonPropertyDefinitionEvent(personPropertyId)); + } + + } + + private void handlePersonPropertyUpdateMutationEvent(DataManagerContext dataManagerContext, + PersonPropertyUpdateMutationEvent personPropertyUpdateMutationEvent) { + PersonId personId = personPropertyUpdateMutationEvent.personId(); + PersonPropertyId personPropertyId = personPropertyUpdateMutationEvent.personPropertyId(); + Object personPropertyValue = personPropertyUpdateMutationEvent.personPropertyValue(); + validatePersonExists(personId); + validatePersonPropertyId(personPropertyId); + validatePersonPropertyValueNotNull(personPropertyValue); + final PropertyDefinition propertyDefinition = propertyDefinitions.get(personPropertyId); + validateValueCompatibility(personPropertyId, propertyDefinition, personPropertyValue); + validatePropertyMutability(propertyDefinition); + + int pId = personId.getValue(); + IndexedPropertyManager propertyManager = propertyValues.get(personPropertyId); + + if (propertyManager == null) { + propertyManager = getIndexedPropertyManager(propertyDefinition); + propertyValues.put(personPropertyId, propertyManager); + } + + Object oldValue = propertyManager.getPropertyValue(pId); + propertyManager.setPropertyValue(pId, personPropertyValue); + + DoubleValueContainer doubleValueContainer = propertyTimes.get(personPropertyId); + if (doubleValueContainer != null) { + doubleValueContainer.setValue(pId, dataManagerContext.getTime()); + } + + if (dataManagerContext.subscribersExist(PersonPropertyUpdateEvent.class)) { + dataManagerContext.releaseObservationEvent( + new PersonPropertyUpdateEvent(personId, personPropertyId, oldValue, personPropertyValue)); + } + + } + + private void loadPropertyTrackingPolicies() { + propertyTrackingPolicies = personPropertiesPluginData.getPropertyTrackingPolicies(); + } + + private void loadPropertyDefinitions() { + propertyDefinitions = personPropertiesPluginData.getPropertyDefinitions(); + for (final PersonPropertyId personPropertyId : propertyDefinitions.keySet()) { + PropertyDefinition propertyDefinition = personPropertiesPluginData + .getPersonPropertyDefinition(personPropertyId); + if (propertyDefinition.getDefaultValue().isEmpty()) { + nonDefaultBearingPropertyIds.put(personPropertyId, nonDefaultBearingPropertyIds.size()); + } + } + nonDefaultChecks = new boolean[nonDefaultBearingPropertyIds.size()]; + } + + private void loadPropertyDefinitionTimes() { + propertyDefinitionTimes = personPropertiesPluginData.getPropertyDefinitionTimes(); + + for (PersonPropertyId personPropertyId : propertyDefinitionTimes.keySet()) { + Double defaultTime = propertyDefinitionTimes.get(personPropertyId); + if (defaultTime > dataManagerContext.getTime()) { + throw new ContractException(PersonPropertyError.PROPERTY_DEFAULT_TIME_EXCEEDS_SIM_TIME, + personPropertyId); + } + } + } + + private void loadPropertyValues() { + /* + * There are four cases to consider: + * + * 1) person exists, property value is present -- adopt the property value + * + * 2) person exists, property value is not present -- if the default is present, + * there is nothing to do. Otherwise, throw an exception since there is no + * default value present + * + * 3) person does not exist, property value is present -- throw an exception + * since it appears data was collected for a non-existent person + * + * 4) person does not exist, property value is not present -- nothing to do + */ + Map> map = personPropertiesPluginData.getPropertyValues(); + for (PersonPropertyId personPropertyId : map.keySet()) { + List list = map.get(personPropertyId); + PropertyDefinition propertyDefinition = propertyDefinitions.get(personPropertyId); + final IndexedPropertyManager indexedPropertyManager = getIndexedPropertyManager(propertyDefinition); + propertyValues.put(personPropertyId, indexedPropertyManager); + boolean defaultIsPresent = propertyDefinition.getDefaultValue().isPresent(); + + int n = FastMath.max(peopleDataManager.getPersonIdLimit(), list.size()); + for (int i = 0; i < n; i++) { + if (peopleDataManager.personIndexExists(i)) { + Object propertyValue = null; + if (i < list.size()) { + propertyValue = list.get(i); + } + if (propertyValue != null) { + indexedPropertyManager.setPropertyValue(i, propertyValue); + } else { + if (!defaultIsPresent) { + throw new ContractException(PropertyError.INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT, + "person(" + i + ") lacks a property value for " + personPropertyId); + } + } + } else { + if (i < list.size()) { + Object propertyValue = list.get(i); + if (propertyValue != null) { + throw new ContractException( + PersonPropertyError.UNKNOWN_PERSON_HAS_PROPERTY_VALUE_ASSIGNMENT, + "unknown person(" + i + ") has property value for " + personPropertyId); + } + } + } + } + } + + } + + private void loadPropertyTimes() { + + /* + * There are four cases to consider: + * + * 1) person exists, time value is present -- adopt the time value + * + * 2) person exists, time value is not present -- adopt the default -- do + * nothing since the default is already present in the DoubleValueContainer + * + * 3) person does not exist, time value is present -- throw an exception since + * it appears data was collected for a non-existent person + * + * 4) person does not exist, time value is not present -- nothing to do + */ + Map> map = personPropertiesPluginData.getPropertyTimes(); + for (PersonPropertyId personPropertyId : map.keySet()) { + List list = map.get(personPropertyId); + double defaultTime = propertyDefinitionTimes.get(personPropertyId); + + DoubleValueContainer doubleValueContainer = new DoubleValueContainer(defaultTime, + peopleDataManager::getPersonIndexIterator); + propertyTimes.put(personPropertyId, doubleValueContainer); + int n = FastMath.max(peopleDataManager.getPersonIdLimit(), list.size()); + for (int i = 0; i < n; i++) { + if (peopleDataManager.personIndexExists(i)) { + if (i < list.size()) { + Double propertyTime = list.get(i); + if (propertyTime != null) { + if (propertyTime > dataManagerContext.getTime()) { + throw new ContractException( + PersonPropertyError.PROPERTY_ASSIGNMENT_TIME_EXCEEDS_SIM_TIME, + "person(" + i + ") " + personPropertyId); + } + doubleValueContainer.setValue(i, propertyTime); + } + } + } else { + if (i < list.size()) { + Double propertyTime = list.get(i); + if (propertyTime != null) { + throw new ContractException(PersonPropertyError.UNKNOWN_PERSON_HAS_PROPERTY_ASSIGNMENT_TIME, + "unknown person(" + i + ") has property time for " + personPropertyId); + } + } + } + } + } + } + + @Override + public void init(DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + this.dataManagerContext = dataManagerContext; + + peopleDataManager = dataManagerContext.getDataManager(PeopleDataManager.class); + regionsDataManager = dataManagerContext.getDataManager(RegionsDataManager.class); + + dataManagerContext.subscribe(PersonPropertyDefinitionMutationEvent.class, + this::handlePersonPropertyDefinitionMutationEvent); + dataManagerContext.subscribe(PersonPropertyUpdateMutationEvent.class, + this::handlePersonPropertyUpdateMutationEvent); + dataManagerContext.subscribe(PersonImminentAdditionEvent.class, this::handlePersonImminentAdditionEvent); + dataManagerContext.subscribe(PersonRemovalEvent.class, this::handlePersonImminentRemovalEvent); + + loadPropertyTrackingPolicies(); + loadPropertyDefinitions(); + loadPropertyDefinitionTimes(); + loadPropertyValues(); + loadPropertyTimes(); + + if (dataManagerContext.stateRecordingIsScheduled()) { + dataManagerContext.subscribeToSimulationClose(this::recordSimulationState); + } + } + + private void markAssigned(PersonPropertyId personPropertyId) { + Integer nonDefaultPropertyIndex = nonDefaultBearingPropertyIds.get(personPropertyId); + if (nonDefaultPropertyIndex != null) { + nonDefaultChecks[nonDefaultPropertyIndex] = true; + } + } + + /** + * Returns true if and only if the person property id is valid. + */ + public boolean personPropertyIdExists(final PersonPropertyId personPropertyId) { + return propertyDefinitions.containsKey(personPropertyId); + } + + private void recordSimulationState(DataManagerContext dataManagerContext) { + PersonPropertiesPluginData.Builder builder = PersonPropertiesPluginData.builder(); + + List people = peopleDataManager.getPeople(); + + for (PersonPropertyId personPropertyId : propertyDefinitions.keySet()) { + PropertyDefinition personPropertyDefinition = propertyDefinitions.get(personPropertyId); + Double definitionTime = propertyDefinitionTimes.get(personPropertyId); + boolean tracked = propertyTrackingPolicies.get(personPropertyId); + builder.definePersonProperty(personPropertyId, personPropertyDefinition, definitionTime, tracked); + } + + for (PersonPropertyId personPropertyId : propertyValues.keySet()) { + IndexedPropertyManager indexedPropertyManager = propertyValues.get(personPropertyId); + for (PersonId personId : people) { + Object propertyValue = indexedPropertyManager.getPropertyValue(personId.getValue()); + builder.setPersonPropertyValue(personId, personPropertyId, propertyValue); + } + } + + for (PersonPropertyId personPropertyId : propertyTimes.keySet()) { + DoubleValueContainer doubleValueContainer = propertyTimes.get(personPropertyId); + for (PersonId personId : people) { + double propertyTime = doubleValueContainer.getValue(personId.getValue()); + builder.setPersonPropertyTime(personId, personPropertyId, propertyTime); + } + } + + dataManagerContext.releaseOutput(builder.build()); + } + + /** + * Updates the value of a person's property. Generates a corresponding + * {@linkplain PersonPropertyUpdateEvent} + * + * @throws ContractException + *
      + *
    • {@link PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@link PersonError#UNKNOWN_PERSON_ID} if the + * person id is unknown
    • + *
    • {@link PropertyError#NULL_PROPERTY_ID} if the + * person property id is null
    • + *
    • {@link PropertyError#UNKNOWN_PROPERTY_ID} if + * the person property id is unknown
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_VALUE} + * if the property value is null
    • + *
    • {@link PropertyError#INCOMPATIBLE_VALUE} if the + * property value is not compatible with the + * corresponding property definition
    • + *
    • {@link PropertyError#IMMUTABLE_VALUE} if the + * corresponding property definition marks the + * property as immutable
    • + *
    + */ + public void setPersonPropertyValue(final PersonId personId, final PersonPropertyId personPropertyId, + final Object personPropertyValue) { + dataManagerContext.releaseMutationEvent( + new PersonPropertyUpdateMutationEvent(personId, personPropertyId, personPropertyValue)); + } + + private void validatePersonExists(final PersonId personId) { + if (personId == null) { + throw new ContractException(PersonError.NULL_PERSON_ID); + } + if (!peopleDataManager.personExists(personId)) { + throw new ContractException(PersonError.UNKNOWN_PERSON_ID); + } + } + + /* + * Precondition : the person property id is valid + */ + private void validatePersonPropertyAssignmentTimesTracked(final PersonPropertyId personPropertyId) { + Boolean tracked = propertyTrackingPolicies.get(personPropertyId); + if (!tracked) { + throw new ContractException(PersonPropertyError.PROPERTY_ASSIGNMENT_TIME_NOT_TRACKED); + } + } + + private void validatePersonPropertyId(final PersonPropertyId personPropertyId) { + if (personPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + + if (!propertyDefinitions.containsKey(personPropertyId)) { + throw new ContractException(PropertyError.UNKNOWN_PROPERTY_ID, personPropertyId); + } + } + + private void validatePersonPropertyIdIsUnknown(final PersonPropertyId personPropertyId) { + if (personPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + + if (propertyDefinitions.containsKey(personPropertyId)) { + throw new ContractException(PropertyError.DUPLICATE_PROPERTY_DEFINITION, personPropertyId); + } + } + + private void validatePersonPropertyValueNotNull(final Object propertyValue) { + if (propertyValue == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_VALUE); + } + } + + private void validatePropertyDefinitionInitializationNotNull( + PersonPropertyDefinitionInitialization propertyDefinitionInitialization) { + if (propertyDefinitionInitialization == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_DEFINITION_INITIALIZATION); + } + } + + private void validateRegionId(RegionId regionId) { + if (regionId == null) { + throw new ContractException(RegionError.NULL_REGION_ID); + } + + if (!regionsDataManager.regionIdExists(regionId)) { + throw new ContractException(RegionError.UNKNOWN_REGION_ID); + } + } + + /* + * Preconditions: all arguments are non-null + */ + private void validateValueCompatibility(final Object propertyId, final PropertyDefinition propertyDefinition, + final Object propertyValue) { + if (!propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())) { + throw new ContractException(PropertyError.INCOMPATIBLE_VALUE, + "Property value " + propertyValue + " is not of type " + propertyDefinition.getType().getName() + + " and does not match definition of " + propertyId); + } + } + + private void verifyNonDefaultChecks() { + + boolean missingPropertyAssignments = false; + + for (boolean nonDefaultCheck : nonDefaultChecks) { + if (!nonDefaultCheck) { + missingPropertyAssignments = true; + break; + } + } + + if (missingPropertyAssignments) { + StringBuilder sb = new StringBuilder(); + int index = -1; + boolean firstMember = true; + for (PersonPropertyId personPropertyId : nonDefaultBearingPropertyIds.keySet()) { + index++; + if (!nonDefaultChecks[index]) { + if (firstMember) { + firstMember = false; + } else { + sb.append(", "); + } + sb.append(personPropertyId); + } + } + throw new ContractException(PropertyError.INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT, sb.toString()); + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("PersonPropertiesDataManager [propertyDefinitions="); + builder.append(propertyDefinitions); + builder.append(", propertyDefinitionTimes="); + builder.append(propertyDefinitionTimes); + builder.append(", propertyValues="); + builder.append(propertyValues); + builder.append(", propertyTrackingPolicies="); + builder.append(propertyTrackingPolicies); + builder.append(", propertyTimes="); + builder.append(propertyTimes); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/datamanagers/PersonPropertiesPluginData.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/datamanagers/PersonPropertiesPluginData.java new file mode 100644 index 000000000..def3f7d7a --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/datamanagers/PersonPropertiesPluginData.java @@ -0,0 +1,591 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.PluginDataBuilder; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyError; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * An immutable container of the initial state of person properties. Contains: + *
    + *
      + *
    • person property ids
    • + *
    • person property definitions
    • + *
    + */ +@Immutable +public class PersonPropertiesPluginData implements PluginData { + + private static class Data { + + private Map propertyDefinitions = new LinkedHashMap<>(); + + private Map propertyDefinitionTimes = new LinkedHashMap<>(); + + private Map propertyTrackingPolicies = new LinkedHashMap<>(); + + private Map> propertyValues = new LinkedHashMap<>(); + + private Map> propertyTimes = new LinkedHashMap<>(); + + private List emptyValueList = Collections.unmodifiableList(new ArrayList<>()); + + private List emptyTimeList = Collections.unmodifiableList(new ArrayList<>()); + + private boolean locked; + + private Data() { + } + + private Data(Data data) { + propertyDefinitions.putAll(data.propertyDefinitions); + propertyDefinitionTimes.putAll(data.propertyDefinitionTimes); + propertyTrackingPolicies.putAll(data.propertyTrackingPolicies); + for (PersonPropertyId personPropertyId : data.propertyValues.keySet()) { + List list = new ArrayList<>(); + propertyValues.put(personPropertyId, list); + list.addAll(data.propertyValues.get(personPropertyId)); + } + for (PersonPropertyId personPropertyId : data.propertyTimes.keySet()) { + List list = new ArrayList<>(); + propertyTimes.put(personPropertyId, list); + list.addAll(data.propertyTimes.get(personPropertyId)); + } + locked = data.locked; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + propertyDefinitions.hashCode(); + result = prime * result + propertyDefinitionTimes.hashCode(); + result = prime * result + propertyTrackingPolicies.hashCode(); + result = prime * result + propertyValues.hashCode(); + result = prime * result + propertyTimes.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + + if (!propertyDefinitions.equals(other.propertyDefinitions)) { + return false; + } + + if (!propertyDefinitionTimes.equals(other.propertyDefinitionTimes)) { + return false; + } + + if (!propertyTrackingPolicies.equals(other.propertyTrackingPolicies)) { + return false; + } + + if (!propertyValues.equals(other.propertyValues)) { + return false; + } + + if (!propertyTimes.equals(other.propertyTimes)) { + return false; + } + + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Data [personPropertyDefinitions="); + builder.append(propertyDefinitions); + builder.append(", propertyDefinitionTimes="); + builder.append(propertyDefinitionTimes); + builder.append(", propertyTrackingPolicies="); + builder.append(propertyTrackingPolicies); + builder.append(", personPropertyValues="); + builder.append(propertyValues); + builder.append(", personPropertyTimes="); + builder.append(propertyTimes); + builder.append("]"); + return builder.toString(); + } + + } + + /** + * Returns a new builder instance + */ + public static Builder builder() { + return new Builder(new Data()); + } + + /** + * Builder class for PersonPropertyInitialData + */ + public static class Builder implements PluginDataBuilder { + private Data data; + + private void ensureDataMutability() { + if (data.locked) { + data = new Data(data); + data.locked = false; + } + } + + private void ensureImmutability() { + if (!data.locked) { + data.locked = true; + } + } + + private Builder(Data data) { + this.data = data; + } + + /** + * Builds the {@linkplain PersonPropertiesPluginData} from the collected data. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if a person is assigned a property value or time + * for a property that was not defined.
    • + *
    • {@linkplain PropertyError#INCOMPATIBLE_VALUE} + * if a person is assigned a property value that is + * incompatible with the associated property + * definition
    • + *
    • {@linkplain PropertyError#TIME_TRACKING_OFF} if + * a person is assigned a property assignment time, + * but the corresponding property is not marked for + * time tracking
    • + *
    • {@linkplain PersonPropertyError#PROPERTY_TIME_PRECEDES_DEFAULT} + * if a person is assigned a property assignment time, + * but that value precedes default tracking time for + * the corresponding property id
    • + *
    + */ + public PersonPropertiesPluginData build() { + + if (!data.locked) { + validateData(); + } + ensureImmutability(); + return new PersonPropertiesPluginData(data); + + } + + /** + * Defines a person property definition. Duplicate inputs override previous + * inputs. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the person property id is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_DEFINITION} + * if the person property definition value is + * null
    • + *
    • {@linkplain PersonPropertyError#NON_FINITE_TIME} + * if the default property time is not finite
    • + *
    + */ + public Builder definePersonProperty(final PersonPropertyId personPropertyId, + final PropertyDefinition propertyDefinition, double time, boolean trackTimes) { + ensureDataMutability(); + validatePersonPropertyIdNotNull(personPropertyId); + validatePersonPropertyDefinitionNotNull(propertyDefinition); + validateTime(time); + data.propertyDefinitions.put(personPropertyId, propertyDefinition); + data.propertyDefinitionTimes.put(personPropertyId, time); + data.propertyTrackingPolicies.put(personPropertyId, trackTimes); + return this; + } + + /** + * Sets the person's property value. Duplicate inputs override previous inputs. + * Avoid setting the value to the default value of the corresponding property + * definition. + * + * @throws ContractException + *
      + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the person property id is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_VALUE} + * if the person property value is null
    • + *
    + */ + public Builder setPersonPropertyValue(final PersonId personId, final PersonPropertyId personPropertyId, + final Object personPropertyValue) { + ensureDataMutability(); + validatePersonId(personId); + validatePersonPropertyIdNotNull(personPropertyId); + validatePersonPropertyValueNotNull(personPropertyValue); + + List list = data.propertyValues.get(personPropertyId); + if (list == null) { + list = new ArrayList<>(); + data.propertyValues.put(personPropertyId, list); + } + + int personIndex = personId.getValue(); + while (list.size() <= personIndex) { + list.add(null); + } + list.set(personIndex, personPropertyValue); + + return this; + } + + /** + * Sets the person's property time. Duplicate inputs override previous inputs. + * Avoid setting the time to the default tracking time. + * + * @throws ContractException + *
      + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the person property id is null
    • + *
    • {@linkplain PersonPropertyError#NULL_TIME} if + * the person property time is null
    • + *
    • {@linkplain PersonPropertyError#NON_FINITE_TIME} + * if the person property time is not finite
    • + *
    + */ + public Builder setPersonPropertyTime(final PersonId personId, final PersonPropertyId personPropertyId, + final Double personPropertyTime) { + ensureDataMutability(); + validatePersonId(personId); + validatePersonPropertyIdNotNull(personPropertyId); + validateTime(personPropertyTime); + + List list = data.propertyTimes.get(personPropertyId); + if (list == null) { + list = new ArrayList<>(); + data.propertyTimes.put(personPropertyId, list); + } + + int personIndex = personId.getValue(); + while (list.size() <= personIndex) { + list.add(null); + } + list.set(personIndex, personPropertyTime); + + return this; + } + + private void validateData() { + + // show all property ids agree with the definitions + + for (PersonPropertyId personPropertyId : data.propertyValues.keySet()) { + if (!data.propertyDefinitions.keySet().contains(personPropertyId)) { + throw new ContractException(PropertyError.UNKNOWN_PROPERTY_ID, personPropertyId); + } + } + for (PersonPropertyId personPropertyId : data.propertyTimes.keySet()) { + if (!data.propertyDefinitions.keySet().contains(personPropertyId)) { + throw new ContractException(PropertyError.UNKNOWN_PROPERTY_ID, personPropertyId); + } + } + + /* + * show that each value is compatible with the property definition + */ + for (PersonPropertyId personPropertyId : data.propertyDefinitions.keySet()) { + PropertyDefinition propertyDefinition = data.propertyDefinitions.get(personPropertyId); + + List list = data.propertyValues.get(personPropertyId); + if (list != null) { + for (int i = 0; i < list.size(); i++) { + Object value = list.get(i); + if (value != null) { + if (!propertyDefinition.getType().isAssignableFrom(value.getClass())) { + throw new ContractException(PropertyError.INCOMPATIBLE_VALUE, + personPropertyId + " = " + value); + } + } + } + } + } + + // show that any property that is not time tracked has no time + // values + for (PersonPropertyId personPropertyId : data.propertyTrackingPolicies.keySet()) { + Boolean tracked = data.propertyTrackingPolicies.get(personPropertyId); + if (!tracked) { + if (data.propertyTimes.containsKey(personPropertyId)) { + throw new ContractException(PropertyError.TIME_TRACKING_OFF, personPropertyId + + " has tracking times collected, but is not itself marked for tracking"); + } + } + } + + // show that any property that is time tracked has no time + // value less than the default time value + for (PersonPropertyId personPropertyId : data.propertyTrackingPolicies.keySet()) { + Boolean tracked = data.propertyTrackingPolicies.get(personPropertyId); + if (tracked) { + Double defaultTime = data.propertyDefinitionTimes.get(personPropertyId); + List list = data.propertyTimes.get(personPropertyId); + if (list != null) { + for (Double time : list) { + if (time != null) { + if (time < defaultTime) { + throw new ContractException(PersonPropertyError.PROPERTY_TIME_PRECEDES_DEFAULT); + } + } + } + } + } + } + } + } + + private static void validatePersonPropertyDefinitionNotNull(PropertyDefinition propertyDefinition) { + if (propertyDefinition == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_DEFINITION); + } + } + + private static void validatePersonPropertyIdNotNull(PersonPropertyId personPropertyId) { + if (personPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + } + + private final Data data; + + private PersonPropertiesPluginData(Data data) { + this.data = data; + } + + /** + * Returns the {@link PropertyDefinition} for the given {@link PersonPropertyId} + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the person property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the person property id is unknown
    • + *
    + */ + public PropertyDefinition getPersonPropertyDefinition(final PersonPropertyId personPropertyId) { + validatePersonPropertyIdNotNull(personPropertyId); + final PropertyDefinition propertyDefinition = data.propertyDefinitions.get(personPropertyId); + if (propertyDefinition == null) { + throw new ContractException(PropertyError.UNKNOWN_PROPERTY_ID, personPropertyId); + } + return propertyDefinition; + } + + /** + * Returns the time when the person property id was added. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the person property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the person property id is unknown
    • + *
    + */ + public double getPropertyDefinitionTime(final PersonPropertyId personPropertyId) { + validatePersonPropertyId(personPropertyId); + return data.propertyDefinitionTimes.get(personPropertyId); + } + + /** + * Returns true if the person property assignment times are tracked. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the person property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the person property id is unknown
    • + *
    + */ + public boolean propertyAssignmentTimesTracked(final PersonPropertyId personPropertyId) { + validatePersonPropertyId(personPropertyId); + return data.propertyTrackingPolicies.get(personPropertyId); + } + + /** + * Returns the set of {@link PersonPropertyId} ids + */ + @SuppressWarnings("unchecked") + public Set getPersonPropertyIds() { + Set result = new LinkedHashSet<>(); + for (PersonPropertyId personPropertyId : data.propertyDefinitions.keySet()) { + result.add((T) personPropertyId); + } + return result; + } + + @Override + public PluginDataBuilder getCloneBuilder() { + return new Builder(data); + } + + private void validatePersonPropertyId(PersonPropertyId personPropertyId) { + if (personPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + if (!data.propertyDefinitions.containsKey(personPropertyId)) { + throw new ContractException(PropertyError.UNKNOWN_PROPERTY_ID); + } + } + + private static void validatePersonPropertyValueNotNull(Object personPropertyValue) { + if (personPropertyValue == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_VALUE); + } + } + + private static void validatePersonId(PersonId personId) { + if (personId == null) { + throw new ContractException(PersonError.NULL_PERSON_ID); + } + } + + private static void validateTime(double time) { + if (!Double.isFinite(time)) { + throw new ContractException(PersonPropertyError.NON_FINITE_TIME); + } + } + + private static void validateTime(Double time) { + if (time == null) { + throw new ContractException(PersonPropertyError.NULL_TIME); + } + validateTime(time.doubleValue()); + } + + /** + * Returns the property values for the given person property id as an + * unmodifiable list. Each object in the list corresponds to a PersonId in + * ascending order starting from zero. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the person property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the person property id is unknown
    • + *
    + */ + public List getPropertyValues(PersonPropertyId personPropertyId) { + validatePersonPropertyId(personPropertyId); + List list = data.propertyValues.get(personPropertyId); + if (list == null) { + return data.emptyValueList; + } + return Collections.unmodifiableList(list); + } + + /** + * Returns the property values for the given person property id as an + * unmodifiable list. Each Double in the list corresponds to a PersonId in + * ascending order starting from zero. + */ + public List getPropertyTimes(PersonPropertyId personPropertyId) { + validatePersonPropertyId(personPropertyId); + List list = data.propertyTimes.get(personPropertyId); + if (list == null) { + return data.emptyTimeList; + } + return Collections.unmodifiableList(list); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + data.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (!(obj instanceof PersonPropertiesPluginData)) { + return false; + } + + PersonPropertiesPluginData other = (PersonPropertiesPluginData) obj; + + if (!data.equals(other.data)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder2 = new StringBuilder(); + builder2.append("PersonPropertiesPluginData [data="); + builder2.append(data); + builder2.append("]"); + return builder2.toString(); + } + + public Map getPropertyTrackingPolicies() { + return new LinkedHashMap<>(data.propertyTrackingPolicies); + } + + public Map getPropertyDefinitions() { + return new LinkedHashMap<>(data.propertyDefinitions); + } + + public Map getPropertyDefinitionTimes() { + return new LinkedHashMap<>(data.propertyDefinitionTimes); + } + + public Map> getPropertyValues() { + Map> result = new LinkedHashMap<>(); + for (PersonPropertyId personPropertyId : data.propertyValues.keySet()) { + List list = data.propertyValues.get(personPropertyId); + List newList = new ArrayList<>(list); + result.put(personPropertyId, newList); + } + return result; + } + + public Map> getPropertyTimes() { + Map> result = new LinkedHashMap<>(); + for (PersonPropertyId personPropertyId : data.propertyTimes.keySet()) { + List list = data.propertyTimes.get(personPropertyId); + List newList = new ArrayList<>(list); + result.put(personPropertyId, newList); + } + return result; + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/events/PersonPropertyDefinitionEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/events/PersonPropertyDefinitionEvent.java new file mode 100644 index 000000000..c3b6365a6 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/events/PersonPropertyDefinitionEvent.java @@ -0,0 +1,29 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * An event released by the people data manager whenever a person property + * definition is added to the simulation. + */ +@Immutable +public record PersonPropertyDefinitionEvent(PersonPropertyId personPropertyId) implements Event { + + /** + * Creates the event. + * + * @throws ContractException {@linkplain PropertyError#NULL_PROPERTY_ID} if the + * property id is null + */ + public PersonPropertyDefinitionEvent { + + if (personPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/events/PersonPropertyUpdateEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/events/PersonPropertyUpdateEvent.java new file mode 100644 index 000000000..56cc3b82e --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/events/PersonPropertyUpdateEvent.java @@ -0,0 +1,26 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyId; +import net.jcip.annotations.Immutable; + +/** + * An observation event indicating that a person's property assignment has + * changed. + */ +@Immutable +public record PersonPropertyUpdateEvent(PersonId personId, PersonPropertyId personPropertyId, + Object previousPropertyValue, Object currentPropertyValue) implements Event { + + @SuppressWarnings("unchecked") + public T getCurrentPropertyValue() { + return (T) currentPropertyValue; + } + + @SuppressWarnings("unchecked") + public T getPreviousPropertyValue() { + return (T) previousPropertyValue; + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/reports/PersonPropertyInteractionReport.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/reports/PersonPropertyInteractionReport.java new file mode 100644 index 000000000..51540b9dd --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/reports/PersonPropertyInteractionReport.java @@ -0,0 +1,306 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.reports; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.events.PersonAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.events.PersonImminentRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.events.PersonPropertyUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.regions.events.PersonRegionUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.PeriodicReport; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; + +/** + * A periodic Report that displays the number of people exhibiting a tuple of + * person property values for a given region. Only non-zero person counts are + * reported. Fields region -- the region identifier [propertyIds] -- the tuple + * of property field values person count -- the number of people having the + * tuple of property values within the region pair + */ +public final class PersonPropertyInteractionReport extends PeriodicReport { + + public PersonPropertyInteractionReport( + PersonPropertyInteractionReportPluginData personPropertyInteractionReportPluginData) { + super(personPropertyInteractionReportPluginData.getReportLabel(), + personPropertyInteractionReportPluginData.getReportPeriod()); + for (PersonPropertyId personPropertyId : personPropertyInteractionReportPluginData.getPersonPropertyIds()) { + propertyIds.add(personPropertyId); + } + } + + /* + * Represents a count of people in a particular region and having a particular + * tuple of property values. + */ + private static class Counter { + int count; + } + + private final List propertyIds = new ArrayList<>(); + + /* + * Map of ...>> + * + * A map of map of map... that starts with regions, each property id in order + * and ends with Counter + */ + private final Map regionMap = new LinkedHashMap<>(); + + private ReportHeader reportHeader; + + /* + * Returns the report header for this report having columns for the selected + * property id values + */ + private ReportHeader getReportHeader() { + if (reportHeader == null) { + ReportHeader.Builder reportHeaderBuilder = ReportHeader.builder(); + addTimeFieldHeaders(reportHeaderBuilder).add("region"); + for (final PersonPropertyId personPropertyId : propertyIds) { + reportHeaderBuilder.add(personPropertyId.toString().toLowerCase()); + } + reportHeaderBuilder.add("person_Count"); + reportHeader = reportHeaderBuilder.build(); + } + return reportHeader; + } + + /* + * Decrements the Counter for the given region and person property values + * associated with the person + */ + private void decrement(final Object regionId, final PersonId personId) { + getCounter(regionId, personId, null, null).count--; + } + + /* + * Decrements the Counter for the given region and person property values + * associated with the person with the old property value being used instead of + * the current property value. + */ + private void decrementOldPropertyValue(final Object regionId, final PersonId personId, final Object oldPropertyId, + final Object oldPropertyValue) { + getCounter(regionId, personId, oldPropertyId, oldPropertyValue).count--; + } + + @Override + protected void flush(ReportContext reportContext) { + + /* + * For each region pair, execute the recursive propertyFlush + */ + final Object[] propertyValues = new Object[propertyIds.size()]; + for (final Object regionId : regionMap.keySet()) { + + @SuppressWarnings("unchecked") + final Map map = (Map) regionMap.get(regionId); + propertyFlush(reportContext, regionId, map, propertyValues, 0); + + } + } + + /* + * Selects the counter that is accounting for the people in the region who have + * the same tuple of property values that the given person currently has. If the + * selectedPropertyId is not null, then the formerPropertyValue is used instead + * for forming the tuple. This is done to select the counter for the previous + * property value so that the counter may decremented. + */ + private Counter getCounter(final Object regionId, final PersonId personId, final Object selectedPropertyId, + final Object formerPropertyValue) { + + /* + * First, push through the region map with the region to arrive at a nested map + * of maps for the properties + */ + @SuppressWarnings("unchecked") + Map propertyValueMap = (Map) regionMap.get(regionId); + if (propertyValueMap == null) { + propertyValueMap = new LinkedHashMap<>(); + regionMap.put(regionId, propertyValueMap); + } + + /* + * Push downward through the mapping layers until all property values have been + * used. The last layer will have Counters as its values. + */ + final int n = propertyIds.size(); + for (int i = 0; i < n; i++) { + final PersonPropertyId personPropertyId = propertyIds.get(i); + Object personPropertyValue; + /* + * When this method is being used to decrement a counter for a previous value of + * a property, we select the former property value instead of the current + * property value. + */ + if (personPropertyId.equals(selectedPropertyId)) { + personPropertyValue = formerPropertyValue; + } else { + personPropertyValue = personPropertiesDataManager.getPersonPropertyValue(personId, personPropertyId); + } + + /* + * The last map level has Counters as its values. All other levels will have + * maps as their values. + */ + if (i == (n - 1)) { + Counter counter = (Counter) propertyValueMap.get(personPropertyValue); + if (counter == null) { + counter = new Counter(); + propertyValueMap.put(personPropertyValue, counter); + } + return counter; + } + @SuppressWarnings("unchecked") + Map subMap = (Map) propertyValueMap.get(personPropertyValue); + if (subMap == null) { + subMap = new LinkedHashMap<>(); + propertyValueMap.put(personPropertyValue, subMap); + } + propertyValueMap = subMap; + } + return null; + } + + private void handlePersonAdditionEvent(ReportContext reportContext, PersonAdditionEvent personAdditionEvent) { + PersonId personId = personAdditionEvent.personId(); + final Object regionId = regionsDataManager.getPersonRegion(personId); + increment(regionId, personId); + } + + private void handlePersonPropertyUpdateEvent(ReportContext reportContext, + PersonPropertyUpdateEvent personPropertyUpdateEvent) { + PersonPropertyId personPropertyId = personPropertyUpdateEvent.personPropertyId(); + if (propertyIds.contains(personPropertyId)) { + PersonId personId = personPropertyUpdateEvent.personId(); + Object previousPropertyValue = personPropertyUpdateEvent.previousPropertyValue(); + final Object regionId = regionsDataManager.getPersonRegion(personId); + increment(regionId, personId); + decrementOldPropertyValue(regionId, personId, personPropertyId, previousPropertyValue); + } + } + + private void handlePersonImminentRemovalEvent(ReportContext reportContext, + PersonImminentRemovalEvent personImminentRemovalEvent) { + PersonId personId = personImminentRemovalEvent.personId(); + RegionId regionId = regionsDataManager.getPersonRegion(personId); + decrement(regionId, personId); + } + + private void handlePersonRegionUpdateEvent(ReportContext reportContext, + PersonRegionUpdateEvent personRegionUpdateEvent) { + PersonId personId = personRegionUpdateEvent.personId(); + RegionId sourceRegionId = personRegionUpdateEvent.previousRegionId(); + final Object regionId = personRegionUpdateEvent.currentRegionId(); + increment(regionId, personId); + decrement(sourceRegionId, personId); + } + + /* + * Increments the Counter for the given region and person property values + * associated with the person + */ + private void increment(final Object regionId, final PersonId personId) { + getCounter(regionId, personId, null, null).count++; + } + + private RegionsDataManager regionsDataManager; + private PeopleDataManager peopleDataManager; + private PersonPropertiesDataManager personPropertiesDataManager; + + @Override + protected void prepare(final ReportContext reportContext) { + + personPropertiesDataManager = reportContext.getDataManager(PersonPropertiesDataManager.class); + peopleDataManager = reportContext.getDataManager(PeopleDataManager.class); + regionsDataManager = reportContext.getDataManager(RegionsDataManager.class); + + reportContext.subscribe(PersonAdditionEvent.class, this::handlePersonAdditionEvent); + reportContext.subscribe(PersonImminentRemovalEvent.class, this::handlePersonImminentRemovalEvent); + reportContext.subscribe(PersonRegionUpdateEvent.class, this::handlePersonRegionUpdateEvent); + if (reportContext.stateRecordingIsScheduled()) { + reportContext.subscribeToSimulationClose(this::recordSimulationState); + } + + /* + * Validate the client's property ids and ignore any that are not known to the + * environment + */ + final Set validPersonPropertyIds = personPropertiesDataManager.getPersonPropertyIds(); + + final Iterator iterator = propertyIds.iterator(); + while (iterator.hasNext()) { + if (!validPersonPropertyIds.contains(iterator.next())) { + iterator.remove(); + } + } + + reportContext.subscribe(PersonPropertyUpdateEvent.class, this::handlePersonPropertyUpdateEvent); + + for (PersonId personId : peopleDataManager.getPeople()) { + final Object regionId = regionsDataManager.getPersonRegion(personId); + increment(regionId, personId); + } + } + + private void recordSimulationState(ReportContext reportContext) { + PersonPropertyInteractionReportPluginData.Builder builder = PersonPropertyInteractionReportPluginData.builder(); + for (PersonPropertyId personPropertyId : propertyIds) { + builder.addPersonPropertyId(personPropertyId); + } + builder.setReportLabel(getReportLabel()); + builder.setReportPeriod(getReportPeriod()); + reportContext.releaseOutput(builder.build()); + } + + /* + * Flushes the positive counters recursively. + */ + private void propertyFlush(ReportContext reportContext, final Object regionId, final Map map, + final Object[] personPropertyValues, final int level) { + + for (final Object personPropertyValue : map.keySet()) { + personPropertyValues[level] = personPropertyValue; + if (level < (propertyIds.size() - 1)) { + @SuppressWarnings("unchecked") + final Map subMap = (Map) map.get(personPropertyValue); + propertyFlush(reportContext, regionId, subMap, personPropertyValues, level + 1); + } else { + final Counter counter = (Counter) map.get(personPropertyValue); + + if (counter.count > 0) { + final Map propertyIdsAndValues = new LinkedHashMap<>(); + for (int i = 0; i < propertyIds.size(); i++) { + propertyIdsAndValues.put(propertyIds.get(i).toString(), personPropertyValues[i]); + } + final ReportItem.Builder reportItemBuilder = ReportItem.builder(); + reportItemBuilder.setReportHeader(getReportHeader()); + reportItemBuilder.setReportLabel(getReportLabel()); + + fillTimeFields(reportItemBuilder); + reportItemBuilder.addValue(regionId.toString()); + + for (int i = 0; i < propertyIds.size(); i++) { + reportItemBuilder.addValue(personPropertyValues[i]); + } + reportItemBuilder.addValue(counter.count); + + reportContext.releaseOutput(reportItemBuilder.build()); + } + } + } + + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/reports/PersonPropertyInteractionReportPluginData.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/reports/PersonPropertyInteractionReportPluginData.java new file mode 100644 index 000000000..fc81235b6 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/reports/PersonPropertyInteractionReportPluginData.java @@ -0,0 +1,156 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.reports; + +import java.util.LinkedHashSet; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.PeriodicReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import net.jcip.annotations.ThreadSafe; + +@ThreadSafe +public class PersonPropertyInteractionReportPluginData extends PeriodicReportPluginData { + + private final Data data; + + private PersonPropertyInteractionReportPluginData(Data data) { + super(data); + this.data = data; + } + + private static class Data extends PeriodicReportPluginData.Data { + private final Set personPropertyIds = new LinkedHashSet<>(); + + public Data() { + super(); + } + + public Data(Data data) { + super(data); + personPropertyIds.addAll(data.personPropertyIds); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((personPropertyIds == null) ? 0 : personPropertyIds.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + if (personPropertyIds == null) { + if (other.personPropertyIds != null) { + return false; + } + } else if (!personPropertyIds.equals(other.personPropertyIds)) { + return false; + } + return super.equals(other); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(super.toString()); + builder.append(", personPropertyIds="); + builder.append(personPropertyIds); + builder.append("]"); + return builder.toString(); + } + } + + public static class Builder extends PeriodicReportPluginData.Builder { + private Data data; + + private Builder(Data data) { + super(data); + this.data = data; + } + + @Override + public PersonPropertyInteractionReportPluginData build() { + return new PersonPropertyInteractionReportPluginData(data); + } + + public Builder addPersonPropertyId(PersonPropertyId personPropertyId) { + if (personPropertyId != null) { + data.personPropertyIds.add(personPropertyId); + } + return this; + } + + public Builder removePersonPropertyId(PersonPropertyId personPropertyId) { + data.personPropertyIds.remove(personPropertyId); + return this; + } + + public Builder setReportLabel(ReportLabel reportLabel) { + super.setReportLabel(reportLabel); + return this; + } + + public Builder setReportPeriod(ReportPeriod reportPeriod) { + super.setReportPeriod(reportPeriod); + return this; + } + } + + public static Builder builder() { + return new Builder(new Data()); + } + + @Override + public Builder getCloneBuilder() { + return new Builder(new Data(data)); + } + + public Set getPersonPropertyIds() { + return new LinkedHashSet<>(data.personPropertyIds); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof PersonPropertyInteractionReportPluginData)) { + return false; + } + PersonPropertyInteractionReportPluginData other = (PersonPropertyInteractionReportPluginData) obj; + if (data == null) { + if (other.data != null) { + return false; + } + } else if (!data.equals(other.data)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder2 = new StringBuilder(); + builder2.append("PersonPropertyInteractionReportPluginData [data="); + builder2.append(data); + builder2.append("]"); + return builder2.toString(); + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/reports/PersonPropertyReport.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/reports/PersonPropertyReport.java new file mode 100644 index 000000000..1200b792d --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/reports/PersonPropertyReport.java @@ -0,0 +1,319 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.reports; + +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.events.PersonAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.events.PersonImminentRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.events.PersonPropertyDefinitionEvent; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.events.PersonPropertyUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.regions.events.PersonRegionUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.PeriodicReport; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; + +/** + * A periodic Report that displays the number of people exhibiting a particular + * value for each person property for a given region pair. Only non-zero person + * counts are reported. Fields region -- the region identifier property -- the + * person property identifier value -- the value of the property person_count -- + * the number of people having the property value within the region + */ +public final class PersonPropertyReport extends PeriodicReport { + + private final boolean includeNewProperties; + + public PersonPropertyReport(PersonPropertyReportPluginData personPropertyReportPluginData) { + super(personPropertyReportPluginData.getReportLabel(), personPropertyReportPluginData.getReportPeriod()); + includedPersonPropertyIds.addAll(personPropertyReportPluginData.getIncludedProperties()); + excludedPersonPropertyIds.addAll(personPropertyReportPluginData.getExcludedProperties()); + includeNewProperties = personPropertyReportPluginData.getDefaultInclusionPolicy(); + } + + /* + * A counter for people having the tuple (Region, Person Property, Property + * Value) + */ + private final static class Counter { + int count; + } + + /* + * The constrained set of person properties that will be used in this report. + * They are set during init() + */ + private final Set includedPersonPropertyIds = new LinkedHashSet<>(); + private final Set currentProperties = new LinkedHashSet<>(); + private final Set excludedPersonPropertyIds = new LinkedHashSet<>(); + + /* + * The tuple mapping to person counts that is maintained via handling of events. + */ + private final Map>> tupleMap = new LinkedHashMap<>(); + + private ReportHeader reportHeader; + + private ReportHeader getReportHeader() { + if (reportHeader == null) { + ReportHeader.Builder reportHeaderBuilder = ReportHeader.builder(); + reportHeader = addTimeFieldHeaders(reportHeaderBuilder)// + .add("region")// + .add("property")// + .add("value")// + .add("person_count")// + .build();// + } + return reportHeader; + } + + /* + * Decrements the population for the given tuple + */ + private void decrement(final RegionId regionId, final PersonPropertyId personPropertyId, + final Object personPropertyValue) { + getCounter(regionId, personPropertyId, personPropertyValue).count--; + } + + @Override + protected void flush(ReportContext reportContext) { + + /* + * For each tuple having a positive population, report the tuple + */ + for (final RegionId regionId : tupleMap.keySet()) { + final Map> propertyIdMap = tupleMap.get(regionId); + for (final PersonPropertyId personPropertyId : propertyIdMap.keySet()) { + final Map personPropertyValueMap = propertyIdMap.get(personPropertyId); + for (final Object personPropertyValue : personPropertyValueMap.keySet()) { + final Counter counter = personPropertyValueMap.get(personPropertyValue); + if (counter.count > 0) { + final int personCount = counter.count; + ReportItem.Builder reportItemBuilder = ReportItem.builder(); + reportItemBuilder.setReportHeader(getReportHeader()); + reportItemBuilder.setReportLabel(getReportLabel()); + + fillTimeFields(reportItemBuilder); + reportItemBuilder.addValue(regionId.toString()); + reportItemBuilder.addValue(personPropertyId.toString()); + reportItemBuilder.addValue(personPropertyValue); + reportItemBuilder.addValue(personCount); + + reportContext.releaseOutput(reportItemBuilder.build()); + } + } + } + + } + } + + /* + * Returns the counter for the give tuple. Creates the counter if it does not + * already exist. + */ + private Counter getCounter(final RegionId regionId, final PersonPropertyId personPropertyId, + final Object personPropertyValue) { + Map> propertyIdMap = tupleMap.get(regionId); + if (propertyIdMap == null) { + propertyIdMap = new LinkedHashMap<>(); + tupleMap.put(regionId, propertyIdMap); + } + Map propertyValueMap = propertyIdMap.get(personPropertyId); + if (propertyValueMap == null) { + propertyValueMap = new LinkedHashMap<>(); + propertyIdMap.put(personPropertyId, propertyValueMap); + } + Counter counter = propertyValueMap.get(personPropertyValue); + if (counter == null) { + counter = new Counter(); + propertyValueMap.put(personPropertyValue, counter); + } + return counter; + + } + + private void handlePersonAdditionEvent(ReportContext reportContext, PersonAdditionEvent personAdditionEvent) { + PersonId personId = personAdditionEvent.personId(); + final RegionId regionId = regionsDataManager.getPersonRegion(personId); + for (final PersonPropertyId personPropertyId : currentProperties) { + final Object personPropertyValue = personPropertiesDataManager.getPersonPropertyValue(personId, + personPropertyId); + increment(regionId, personPropertyId, personPropertyValue); + } + } + + private void handlePersonPropertyUpdateEvent(ReportContext reportContext, + PersonPropertyUpdateEvent personPropertyUpdateEvent) { + PersonPropertyId personPropertyId = personPropertyUpdateEvent.personPropertyId(); + if (isCurrentProperty(personPropertyId)) { + PersonId personId = personPropertyUpdateEvent.personId(); + Object previousPropertyValue = personPropertyUpdateEvent.previousPropertyValue(); + final RegionId regionId = regionsDataManager.getPersonRegion(personId); + final Object currentValue = personPropertiesDataManager.getPersonPropertyValue(personId, personPropertyId); + increment(regionId, personPropertyId, currentValue); + decrement(regionId, personPropertyId, previousPropertyValue); + } + } + + private void handlePersonImminentRemovalEvent(ReportContext reportContext, + PersonImminentRemovalEvent personImminentRemovalEvent) { + PersonId personId = personImminentRemovalEvent.personId(); + RegionId regionId = regionsDataManager.getPersonRegion(personId); + for (PersonPropertyId personPropertyId : currentProperties) { + final Object personPropertyValue = personPropertiesDataManager.getPersonPropertyValue(personId, + personPropertyId); + decrement(regionId, personPropertyId, personPropertyValue); + } + } + + private void handlePersonRegionUpdateEvent(ReportContext reportContext, + PersonRegionUpdateEvent personRegionUpdateEvent) { + PersonId personId = personRegionUpdateEvent.personId(); + RegionId previousRegionId = personRegionUpdateEvent.previousRegionId(); + RegionId regionId = personRegionUpdateEvent.currentRegionId(); + for (final PersonPropertyId personPropertyId : currentProperties) { + final Object personPropertyValue = personPropertiesDataManager.getPersonPropertyValue(personId, + personPropertyId); + increment(regionId, personPropertyId, personPropertyValue); + decrement(previousRegionId, personPropertyId, personPropertyValue); + } + } + + /* + * Increments the population for the given tuple + */ + private void increment(final RegionId regionId, final PersonPropertyId personPropertyId, + final Object personPropertyValue) { + getCounter(regionId, personPropertyId, personPropertyValue).count++; + } + + private PersonPropertiesDataManager personPropertiesDataManager; + + private RegionsDataManager regionsDataManager; + + private PeopleDataManager peopleDataManager; + + private boolean isCurrentProperty(PersonPropertyId personPropertyId) { + return currentProperties.contains(personPropertyId); + } + + private boolean addToCurrentProperties(PersonPropertyId personPropertyId) { + + // There are eight possibilities: + + /* + * P -- the default inclusion policy + * + * I -- the property is explicitly included + * + * X -- the property is explicitly excluded + * + * C -- the property should be on the current properties + * + * + * P I X C Table + * + * TRUE TRUE FALSE TRUE + * + * TRUE FALSE FALSE TRUE + * + * FALSE TRUE FALSE TRUE + * + * FALSE FALSE FALSE FALSE + * + * TRUE TRUE TRUE FALSE -- not possible + * + * TRUE FALSE TRUE FALSE + * + * FALSE TRUE TRUE FALSE -- not possible + * + * FALSE FALSE TRUE FALSE + * + * + * Two of the cases above are contradictory since a property cannot be both + * explicitly included and explicitly excluded + * + */ + // if X is true then we don't add the property + if (excludedPersonPropertyIds.contains(personPropertyId)) { + return false; + } + + // if both P and I are false we don't add the property + boolean included = includedPersonPropertyIds.contains(personPropertyId); + + if (!included && !includeNewProperties) { + return false; + } + + // we have failed to reject the property + currentProperties.add(personPropertyId); + + return true; + } + + @Override + protected void prepare(final ReportContext reportContext) { + regionsDataManager = reportContext.getDataManager(RegionsDataManager.class); + personPropertiesDataManager = reportContext.getDataManager(PersonPropertiesDataManager.class); + peopleDataManager = reportContext.getDataManager(PeopleDataManager.class); + + reportContext.subscribe(PersonAdditionEvent.class, this::handlePersonAdditionEvent); + reportContext.subscribe(PersonImminentRemovalEvent.class, this::handlePersonImminentRemovalEvent); + reportContext.subscribe(PersonRegionUpdateEvent.class, this::handlePersonRegionUpdateEvent); + if (reportContext.stateRecordingIsScheduled()) { + reportContext.subscribeToSimulationClose(this::recordSimulationState); + } + reportContext.subscribe(PersonPropertyDefinitionEvent.class, this::handlePersonPropertyDefinitionEvent); + reportContext.subscribe(PersonPropertyUpdateEvent.class, this::handlePersonPropertyUpdateEvent); + + for (PersonPropertyId personPropertyId : personPropertiesDataManager.getPersonPropertyIds()) { + addToCurrentProperties(personPropertyId); + } + + for (PersonId personId : peopleDataManager.getPeople()) { + final RegionId regionId = regionsDataManager.getPersonRegion(personId); + for (final PersonPropertyId personPropertyId : currentProperties) { + final Object personPropertyValue = personPropertiesDataManager.getPersonPropertyValue(personId, + personPropertyId); + increment(regionId, personPropertyId, personPropertyValue); + } + } + } + + private void recordSimulationState(ReportContext reportContext) { + PersonPropertyReportPluginData.Builder builder = PersonPropertyReportPluginData.builder(); + builder.setDefaultInclusion(includeNewProperties); + builder.setReportLabel(getReportLabel()); + builder.setReportPeriod(getReportPeriod()); + for (PersonPropertyId personPropertyId : includedPersonPropertyIds) { + builder.includePersonProperty(personPropertyId); + } + for (PersonPropertyId personPropertyId : excludedPersonPropertyIds) { + builder.excludePersonProperty(personPropertyId); + } + reportContext.releaseOutput(builder.build()); + } + + private void handlePersonPropertyDefinitionEvent(ReportContext actorContext, + PersonPropertyDefinitionEvent personPropertyDefinitionEvent) { + PersonPropertyId personPropertyId = personPropertyDefinitionEvent.personPropertyId(); + if (addToCurrentProperties(personPropertyId)) { + for (PersonId personId : peopleDataManager.getPeople()) { + final RegionId regionId = regionsDataManager.getPersonRegion(personId); + final Object personPropertyValue = personPropertiesDataManager.getPersonPropertyValue(personId, + personPropertyId); + increment(regionId, personPropertyId, personPropertyValue); + } + } + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/reports/PersonPropertyReportPluginData.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/reports/PersonPropertyReportPluginData.java new file mode 100644 index 000000000..5bb560a3b --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/reports/PersonPropertyReportPluginData.java @@ -0,0 +1,250 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.reports; + +import java.util.LinkedHashSet; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.PeriodicReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportError; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import net.jcip.annotations.ThreadSafe; +import util.errors.ContractException; + +/** + * A PluginData class supporting PersonPropertyReport construction. + */ +@ThreadSafe +public final class PersonPropertyReportPluginData extends PeriodicReportPluginData { + + private final Data data; + + private PersonPropertyReportPluginData(Data data) { + super(data); + this.data = data; + } + + /* + * Data class for collecting the inputs to the report + */ + private static class Data extends PeriodicReportPluginData.Data { + private Set includedProperties = new LinkedHashSet<>(); + private Set excludedProperties = new LinkedHashSet<>(); + private boolean defaultInclusionPolicy = true; + + private Data() { + super(); + } + + private Data(Data data) { + super(data); + includedProperties.addAll(data.includedProperties); + excludedProperties.addAll(data.excludedProperties); + defaultInclusionPolicy = data.defaultInclusionPolicy; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + (defaultInclusionPolicy ? 1231 : 1237); + result = prime * result + ((excludedProperties == null) ? 0 : excludedProperties.hashCode()); + result = prime * result + ((includedProperties == null) ? 0 : includedProperties.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + if (defaultInclusionPolicy != other.defaultInclusionPolicy) { + return false; + } + if (excludedProperties == null) { + if (other.excludedProperties != null) { + return false; + } + } else if (!excludedProperties.equals(other.excludedProperties)) { + return false; + } + if (includedProperties == null) { + if (other.includedProperties != null) { + return false; + } + } else if (!includedProperties.equals(other.includedProperties)) { + return false; + } + return super.equals(other); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(super.toString()); + builder.append(", includedProperties="); + builder.append(includedProperties); + builder.append(", excludedProperties="); + builder.append(excludedProperties); + builder.append(", defaultInclusionPolicy="); + builder.append(defaultInclusionPolicy); + builder.append("]"); + return builder.toString(); + } + } + + /** + * Builder class for the report + */ + public final static class Builder extends PeriodicReportPluginData.Builder { + private Data data; + + private Builder(Data data) { + super(data); + this.data = data; + } + + /** + * Returns a PersonPropertyReportPluginData created from the collected inputs + * + * @throws ContractException + *
      + *
    • {@linkplain ReportError#NULL_REPORT_LABEL} if + * the report label is not assigned
    • + *
    • {@linkplain ReportError#NULL_REPORT_PERIOD} if + * the report period is not assigned
    • + *
    + */ + public PersonPropertyReportPluginData build() { + return new PersonPropertyReportPluginData(data); + } + + /** + * Sets the default policy for inclusion of person properties in the report. + * This policy is used when a person property has not been explicitly included + * or excluded. Defaulted to true. + */ + public Builder setDefaultInclusion(boolean include) { + data.defaultInclusionPolicy = include; + return this; + } + + /** + * Selects the given person property id to be included in the report. + * + * @throws ContractException {@linkplain PropertyError#NULL_PROPERTY_ID} if the + * person property id is null + */ + public Builder includePersonProperty(PersonPropertyId personPropertyId) { + if (personPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + data.includedProperties.add(personPropertyId); + data.excludedProperties.remove(personPropertyId); + return this; + } + + /** + * Selects the given person property id to be excluded from the report + * + * @throws ContractException {@linkplain PropertyError#NULL_PROPERTY_ID} if the + * person property id is null + */ + public Builder excludePersonProperty(PersonPropertyId personPropertyId) { + if (personPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + data.includedProperties.remove(personPropertyId); + data.excludedProperties.add(personPropertyId); + return this; + } + + /** + * Sets the report label + * + * @throws ContractException {@linkplain ReportError#NULL_REPORT_LABEL} if the + * report label is null + */ + public Builder setReportLabel(ReportLabel reportLabel) { + super.setReportLabel(reportLabel); + return this; + } + + /** + * Sets the report period id + * + * @throws ContractException {@linkplain ReportError#NULL_REPORT_PERIOD} if the + * report period is null + */ + public Builder setReportPeriod(ReportPeriod reportPeriod) { + super.setReportPeriod(reportPeriod); + return this; + } + } + + /** + * Returns a new instance of the builder class + */ + public static Builder builder() { + return new Builder(new Data()); + } + + @Override + public Builder getCloneBuilder() { + return new Builder(new Data(data)); + } + + public Set getIncludedProperties() { + return new LinkedHashSet<>(data.includedProperties); + } + + public Set getExcludedProperties() { + return new LinkedHashSet<>(data.excludedProperties); + } + + public boolean getDefaultInclusionPolicy() { + return data.defaultInclusionPolicy; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof PersonPropertyReportPluginData)) { + return false; + } + PersonPropertyReportPluginData other = (PersonPropertyReportPluginData) obj; + if (data == null) { + if (other.data != null) { + return false; + } + } else if (!data.equals(other.data)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder2 = new StringBuilder(); + builder2.append("PersonPropertyReportPluginData [data="); + builder2.append(data); + builder2.append("]"); + return builder2.toString(); + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/FunctionalPersonPropertyLabeler.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/FunctionalPersonPropertyLabeler.java new file mode 100644 index 000000000..65e38579e --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/FunctionalPersonPropertyLabeler.java @@ -0,0 +1,23 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.support; + +import java.util.function.Function; + +/** + * + * A function-based implementor of PersonPropertyLabeler. + * + */ +public class FunctionalPersonPropertyLabeler extends PersonPropertyLabeler { + private final Function labelingFunction; + + public FunctionalPersonPropertyLabeler(PersonPropertyId personPropertyId, + Function labelingFunction) { + super(personPropertyId); + this.labelingFunction = labelingFunction; + } + + @Override + protected Object getLabelFromValue(Object value) { + return labelingFunction.apply(value); + } +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/PersonPropertyDefinitionInitialization.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/PersonPropertyDefinitionInitialization.java new file mode 100644 index 000000000..235f99207 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/PersonPropertyDefinitionInitialization.java @@ -0,0 +1,194 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.support; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.commons.math3.util.Pair; + +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * A class for defining a person property with an associated property id and + * property values for extant people. + */ +@Immutable +public final class PersonPropertyDefinitionInitialization { + + private static class Data { + private PersonPropertyId personPropertyId; + private PropertyDefinition propertyDefinition; + private boolean trackTimes; + private List> propertyValues = new ArrayList<>(); + + public Data() { + } + + public Data(Data data) { + personPropertyId = data.personPropertyId; + propertyDefinition = data.propertyDefinition; + propertyValues.addAll(data.propertyValues); + trackTimes = data.trackTimes; + } + } + + private final Data data; + + private PersonPropertyDefinitionInitialization(Data data) { + this.data = data; + } + + /** + * Returns a new Builder instance + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder class for a PersonPropertyDefinitionInitialization + */ + public final static class Builder { + + private Builder() { + } + + private Data data = new Data(); + + private void validate() { + if (data.propertyDefinition == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_DEFINITION); + } + + if (data.personPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + + Class type = data.propertyDefinition.getType(); + for (Pair pair : data.propertyValues) { + Object value = pair.getSecond(); + if (!type.isAssignableFrom(value.getClass())) { + String message = "Definition Type " + type.getName() + " is not compatible with value = " + value; + throw new ContractException(PropertyError.INCOMPATIBLE_VALUE, message); + } + } + } + + /** + * Constructs the PersonPropertyDefinitionInitialization from the collected data + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_DEFINITION} + * if no property definition was assigned to the + * builder
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * no property id was assigned to the builder
    • + *
    • {@linkplain PropertyError#INCOMPATIBLE_VALUE} + * if a collected property value is incompatible with + * the property definition
    • + *
    + */ + public PersonPropertyDefinitionInitialization build() { + validate(); + return new PersonPropertyDefinitionInitialization(new Data(data)); + } + + /** + * Sets the property id + * + * @throws ContractException {@linkplain PropertyError#NULL_PROPERTY_ID} if the + * property id is null + */ + public Builder setPersonPropertyId(PersonPropertyId propertyId) { + if (propertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + data.personPropertyId = propertyId; + return this; + } + + /** + * Sets the property definition + * + * @throws ContractException {@linkplain PropertyError#NULL_PROPERTY_DEFINITION} + * if the property definition is null + */ + public Builder setPropertyDefinition(PropertyDefinition propertyDefinition) { + if (propertyDefinition == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_DEFINITION); + } + data.propertyDefinition = propertyDefinition; + return this; + } + + /** + * Sets the time tracking policy. Defaults to false; + */ + public Builder setTrackTimes(boolean trackTimes) { + data.trackTimes = trackTimes; + return this; + } + + /** + * Adds a property value + * + * @throws ContractException + *
      + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_VALUE} + * if the property value is null
    • + *
    + */ + public Builder addPropertyValue(PersonId personId, Object value) { + if (personId == null) { + throw new ContractException(PersonError.NULL_PERSON_ID); + } + if (value == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_VALUE); + } + + data.propertyValues.add(new Pair<>(personId, value)); + return this; + } + + } + + /** + * Returns the (non-null) person property id. + */ + public PersonPropertyId getPersonPropertyId() { + return data.personPropertyId; + } + + /** + * Returns the (non-null) property definition. + */ + public PropertyDefinition getPropertyDefinition() { + return data.propertyDefinition; + } + + /** + * Returns the list of (person,value) pairs collected by the builder in the + * order of their addition. All pairs have non-null entries and the values are + * compatible with the contained property definition. Duplicate assignments of + * values to the same person may be present. + */ + public List> getPropertyValues() { + return Collections.unmodifiableList(data.propertyValues); + } + + /** + * Returns the time tracking policy. + */ + public boolean trackTimes() { + return data.trackTimes; + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/PersonPropertyDimension.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/PersonPropertyDimension.java new file mode 100644 index 000000000..443762e57 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/PersonPropertyDimension.java @@ -0,0 +1,217 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.support; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import gov.hhs.aspr.ms.gcm.nucleus.Dimension; +import gov.hhs.aspr.ms.gcm.nucleus.DimensionContext; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.errors.ContractException; + +/** + * Dimension implementation for setting a person property to a list of values in + * a person properties plugin data. + */ +public class PersonPropertyDimension implements Dimension { + + private static class Data { + private PersonPropertyId personPropertyId; + private boolean trackTimes = false; + private List values = new ArrayList<>(); + + private Data() { + } + + private Data(Data data) { + personPropertyId = data.personPropertyId; + trackTimes = data.trackTimes; + values.addAll(data.values); + } + + @Override + public int hashCode() { + return Objects.hash(personPropertyId, values, trackTimes); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Data other = (Data) obj; + return Objects.equals(personPropertyId, other.personPropertyId) && Objects.equals(values, other.values) + && trackTimes == other.trackTimes; + } + + } + + /** + * Returns a new builder for PersonPropertyDimension + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder class for PersonPropertyDimension + */ + public static class Builder { + private Builder() { + } + + private Data data = new Data(); + + /** + * Returns the PersonPropertyDimension from the collected data. + * + * @throws ContractException {@linkplain PropertyError#NULL_PROPERTY_ID} if the + * person property id was not assigned + */ + public PersonPropertyDimension build() { + validate(); + return new PersonPropertyDimension(new Data(data)); + } + + private void validate() { + if (data.personPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + } + + /** + * Sets the time tracking policy for this dimension. defaults to false + */ + public Builder setTrackTimes(boolean trackTimes) { + data.trackTimes = trackTimes; + return this; + } + + /** + * Sets the person property for the dimension. Defaults to null. + * + * @throws ContractException {@linkplain PropertyError#NULL_PROPERTY_ID} if the + * id is null + */ + public Builder setPersonPropertyId(PersonPropertyId personPropertyId) { + validatePersonPropertyId(personPropertyId); + data.personPropertyId = personPropertyId; + return this; + } + + /** + * Adds a value to the dimension. + * + * @throws ContractException {@linkplain PropertyError#NULL_PROPERTY_VALUE} if + * the value is null + */ + public Builder addValue(Object value) { + validateValue(value); + data.values.add(value); + return this; + } + } + + private final Data data; + + private PersonPropertyDimension(Data data) { + this.data = data; + } + + @Override + public List getExperimentMetaData() { + List result = new ArrayList<>(); + result.add(data.personPropertyId.toString()); + return result; + } + + @Override + public int levelCount() { + return data.values.size(); + } + + @Override + public List executeLevel(DimensionContext dimensionContext, int level) { + PersonPropertiesPluginData personPropertiesPluginData = dimensionContext + .getPluginData(PersonPropertiesPluginData.class); + PersonPropertiesPluginData.Builder builder = dimensionContext + .getPluginDataBuilder(PersonPropertiesPluginData.Builder.class); + Object value = data.values.get(level); + + PropertyDefinition existingPropDef = personPropertiesPluginData + .getPersonPropertyDefinition(data.personPropertyId); + double existingTime = personPropertiesPluginData.getPropertyDefinitionTime(data.personPropertyId); + + PropertyDefinition newPropDef = PropertyDefinition.builder().setDefaultValue(value) + .setPropertyValueMutability(existingPropDef.propertyValuesAreMutable()) + .setType(existingPropDef.getType()).build(); + + builder.definePersonProperty(data.personPropertyId, newPropDef, existingTime, data.trackTimes); + + List result = new ArrayList<>(); + result.add(value.toString()); + return result; + } + + /** + * Returns the time tracking policy for this dimension + */ + public boolean getTrackTimes() { + return data.trackTimes; + } + + /** + * Returns the person property id for this dimension + */ + public PersonPropertyId getPersonPropertyId() { + return data.personPropertyId; + } + + /** + * Returns the ordered list of person property values for this dimension + */ + public List getValues() { + return new ArrayList<>(data.values); + } + + private static void validateValue(Object value) { + if (value == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_VALUE); + } + } + + private static void validatePersonPropertyId(PersonPropertyId personPropertyId) { + if (personPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + } + + @Override + public int hashCode() { + return Objects.hash(data); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + PersonPropertyDimension other = (PersonPropertyDimension) obj; + return Objects.equals(data, other.data); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/PersonPropertyError.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/PersonPropertyError.java new file mode 100644 index 000000000..f530cccd1 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/PersonPropertyError.java @@ -0,0 +1,36 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.support; + +import util.errors.ContractError; +import util.errors.ContractException; + +/** + * An enumeration supporting {@link ContractException} that acts as a general + * description of the exception. + */ +public enum PersonPropertyError implements ContractError { + + NULL_PERSON_PROPERTY_PLUGN_DATA("Null person property plugin data"), // + NULL_PERSON_PROPERTY_INTERACTION_REPORT_PLUGIN_DATA("Null person property interaction report plugin data"), // + NULL_PERSON_PROPERTY_REPORT_PLUGIN_DATA("Null person property report plugin data"), + NULL_PERSON_PROPERTY_DATA_MANAGER("Null person property data manager"), // + UNKNOWN_PERSON_ID("Unknown person id"), // + PROPERTY_ASSIGNMENT_TIME_NOT_TRACKED("Property assignment time not actively tracked"), // + PROPERTY_TIME_PRECEDES_DEFAULT( + "A property value assignment time precedes the default assignment time for the associated property"), + UNKNOWN_PERSON_HAS_PROPERTY_ASSIGNMENT_TIME("Unknown person has property assignment time"), + UNKNOWN_PERSON_HAS_PROPERTY_VALUE_ASSIGNMENT("Unknown person has property value assignment"), + NULL_TIME("Null time"), NON_FINITE_TIME("Non-finite time value"), + PROPERTY_DEFAULT_TIME_EXCEEDS_SIM_TIME("Property default time exceeds current simulation time"), + PROPERTY_ASSIGNMENT_TIME_EXCEEDS_SIM_TIME("Property assignment time exceeds current simulation time"),; + + private final String description; + + private PersonPropertyError(final String description) { + this.description = description; + } + + @Override + public String getDescription() { + return description; + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/PersonPropertyFilter.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/PersonPropertyFilter.java new file mode 100644 index 000000000..1940fdfb7 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/PersonPropertyFilter.java @@ -0,0 +1,217 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.support; + +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.Equality; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.FilterSensitivity; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters.Filter; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.events.PersonPropertyUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.errors.ContractException; + +public final class PersonPropertyFilter extends Filter { + + private final PersonPropertyId personPropertyId; + private final Object personPropertyValue; + private final Equality equality; + private PersonPropertiesDataManager personPropertiesDataManager; + + private void validatePersonPropertyId(PartitionsContext partitionsContext, + final PersonPropertyId personPropertyId) { + + if (personPropertiesDataManager == null) { + personPropertiesDataManager = partitionsContext.getDataManager(PersonPropertiesDataManager.class); + } + + if (personPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + + if (!personPropertiesDataManager.personPropertyIdExists(personPropertyId)) { + throw new ContractException(PropertyError.UNKNOWN_PROPERTY_ID, personPropertyId); + } + } + + private void validateEquality(PartitionsContext partitionsContext, final Equality equality) { + if (equality == null) { + throw new ContractException(PartitionError.NULL_EQUALITY_OPERATOR); + } + } + + private void validatePersonPropertyValueNotNull(PartitionsContext partitionsContext, final Object propertyValue) { + if (propertyValue == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_VALUE); + } + } + + private void validateValueCompatibility(PartitionsContext partitionsContext, final Object propertyId, + final PropertyDefinition propertyDefinition, final Object propertyValue) { + if (!propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())) { + throw new ContractException(PropertyError.INCOMPATIBLE_VALUE, + "Property value " + propertyValue + " is not of type " + propertyDefinition.getType().getName() + + " and does not match definition of " + propertyId); + } + } + + private void validateEqualityCompatibility(PartitionsContext partitionsContext, final Object propertyId, + final PropertyDefinition propertyDefinition, final Equality equality) { + + if (equality == Equality.EQUAL) { + return; + } + if (equality == Equality.NOT_EQUAL) { + return; + } + + if (!Comparable.class.isAssignableFrom(propertyDefinition.getType())) { + throw new ContractException(PartitionError.NON_COMPARABLE_ATTRIBUTE, + "Property values for " + propertyId + " are not comparable via " + equality); + } + } + + public PersonPropertyId getPersonPropertyId() { + return personPropertyId; + } + + public Equality getEquality() { + return equality; + } + + public Object getPersonPropertyValue() { + return personPropertyValue; + } + + public PersonPropertyFilter(final PersonPropertyId personPropertyId, final Equality equality, + final Object personPropertyValue) { + this.personPropertyId = personPropertyId; + this.personPropertyValue = personPropertyValue; + this.equality = equality; + } + + @Override + public void validate(PartitionsContext partitionsContext) { + if (partitionsContext == null) { + throw new ContractException(NucleusError.NULL_SIMULATION_CONTEXT); + } + if (personPropertiesDataManager == null) { + personPropertiesDataManager = partitionsContext.getDataManager(PersonPropertiesDataManager.class); + } + + validatePersonPropertyId(partitionsContext, personPropertyId); + validateEquality(partitionsContext, equality); + validatePersonPropertyValueNotNull(partitionsContext, personPropertyValue); + final PropertyDefinition propertyDefinition = personPropertiesDataManager + .getPersonPropertyDefinition(personPropertyId); + validateValueCompatibility(partitionsContext, personPropertyId, propertyDefinition, personPropertyValue); + validateEqualityCompatibility(partitionsContext, personPropertyId, propertyDefinition, equality); + } + + @Override + public boolean evaluate(PartitionsContext partitionsContext, PersonId personId) { + + if (partitionsContext == null) { + throw new ContractException(NucleusError.NULL_SIMULATION_CONTEXT); + } + if (personPropertiesDataManager == null) { + personPropertiesDataManager = partitionsContext.getDataManager(PersonPropertiesDataManager.class); + } + + // we do not assume that the returned property value is + // comparable unless we are forced to. + final Object propVal = personPropertiesDataManager.getPersonPropertyValue(personId, personPropertyId); + + return evaluate(propVal); + + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private boolean evaluate(Object propVal) { + if (equality.equals(Equality.EQUAL)) { + return propVal.equals(personPropertyValue); + } else if (equality.equals(Equality.NOT_EQUAL)) { + return !propVal.equals(personPropertyValue); + } else { + Comparable comparablePropertyValue = (Comparable) propVal; + int evaluation = comparablePropertyValue.compareTo(personPropertyValue); + return equality.isCompatibleComparisonValue(evaluation); + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("PropertyFilter [personPropertyId="); + builder.append(personPropertyId); + builder.append(", personPropertyValue="); + builder.append(personPropertyValue); + builder.append(", equality="); + builder.append(equality); + builder.append("]"); + return builder.toString(); + } + + private Optional requiresRefresh(PartitionsContext partitionsContext, PersonPropertyUpdateEvent event) { + if (event.personPropertyId().equals(personPropertyId)) { + if (evaluate(event.previousPropertyValue()) != evaluate(event.currentPropertyValue())) { + return Optional.of(event.personId()); + } + } + return Optional.empty(); + } + + @Override + public Set> getFilterSensitivities() { + Set> result = new LinkedHashSet<>(); + result.add(new FilterSensitivity(PersonPropertyUpdateEvent.class, + this::requiresRefresh)); + return result; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((equality == null) ? 0 : equality.hashCode()); + result = prime * result + ((personPropertyId == null) ? 0 : personPropertyId.hashCode()); + result = prime * result + ((personPropertyValue == null) ? 0 : personPropertyValue.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof PersonPropertyFilter)) { + return false; + } + PersonPropertyFilter other = (PersonPropertyFilter) obj; + if (equality != other.equality) { + return false; + } + if (personPropertyId == null) { + if (other.personPropertyId != null) { + return false; + } + } else if (!personPropertyId.equals(other.personPropertyId)) { + return false; + } + if (personPropertyValue == null) { + if (other.personPropertyValue != null) { + return false; + } + } else if (!personPropertyValue.equals(other.personPropertyValue)) { + return false; + } + return true; + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/PersonPropertyId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/PersonPropertyId.java new file mode 100644 index 000000000..2087b0f52 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/PersonPropertyId.java @@ -0,0 +1,15 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.support; + +import net.jcip.annotations.ThreadSafe; + +/** + * Marker interface for person property identifiers + */ +@ThreadSafe +public interface PersonPropertyId { + @Override + public int hashCode(); + + @Override + public boolean equals(Object obj); +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/PersonPropertyLabeler.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/PersonPropertyLabeler.java new file mode 100644 index 000000000..69b0efce8 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/PersonPropertyLabeler.java @@ -0,0 +1,86 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.support; + +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.Labeler; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.LabelerSensitivity; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.events.PersonPropertyUpdateEvent; +import util.errors.ContractException; + +/** + * A labeler for person properties. The dimension of the labeler is the given + * {@linkplain PersonPropertyId}, the event that stimulates a label update is + * {@linkplain PersonPropertyUpdateEvent} and the labeling function is composed + * from the given Function. + */ +public abstract class PersonPropertyLabeler implements Labeler { + + private final PersonPropertyId personPropertyId; + private PersonPropertiesDataManager personPropertiesDataManager; + + public PersonPropertyId getPersonPropertyId() { + return personPropertyId; + } + + public PersonPropertyLabeler(PersonPropertyId personPropertyId) { + this.personPropertyId = personPropertyId; + } + + protected abstract Object getLabelFromValue(Object value); + + private Optional getPersonId(PersonPropertyUpdateEvent personPropertyUpdateEvent) { + PersonId result = null; + if (personPropertyUpdateEvent.personPropertyId().equals(personPropertyId)) { + result = personPropertyUpdateEvent.personId(); + } + return Optional.ofNullable(result); + } + + @Override + public final Set> getLabelerSensitivities() { + Set> result = new LinkedHashSet<>(); + result.add( + new LabelerSensitivity(PersonPropertyUpdateEvent.class, this::getPersonId)); + return result; + } + + @Override + public final Object getCurrentLabel(PartitionsContext partitionsContext, PersonId personId) { + if (partitionsContext == null) { + throw new ContractException(NucleusError.NULL_SIMULATION_CONTEXT); + } + if (personPropertiesDataManager == null) { + personPropertiesDataManager = partitionsContext.getDataManager(PersonPropertiesDataManager.class); + } + Object personPropertyValue = personPropertiesDataManager.getPersonPropertyValue(personId, personPropertyId); + return getLabelFromValue(personPropertyValue); + } + + @Override + public final Object getId() { + return personPropertyId; + } + + @Override + public final Object getPastLabel(PartitionsContext partitionsContext, Event event) { + PersonPropertyUpdateEvent personPropertyUpdateEvent = (PersonPropertyUpdateEvent) event; + return getLabelFromValue(personPropertyUpdateEvent.previousPropertyValue()); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("PersonPropertyLabeler [personPropertyId="); + builder.append(personPropertyId); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/PersonPropertyValueInitialization.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/PersonPropertyValueInitialization.java new file mode 100644 index 000000000..81d0dfa72 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/PersonPropertyValueInitialization.java @@ -0,0 +1,67 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.support; + +public class PersonPropertyValueInitialization { + private final PersonPropertyId personPropertyId; + private final Object value; + + public PersonPropertyValueInitialization(PersonPropertyId personPropertyId, Object value) { + super(); + this.personPropertyId = personPropertyId; + this.value = value; + } + + public PersonPropertyId getPersonPropertyId() { + return personPropertyId; + } + + public Object getValue() { + return value; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("PersonPropertyAssignment [personPropertyId="); + builder.append(personPropertyId); + builder.append(", value="); + builder.append(value); + builder.append("]"); + return builder.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((personPropertyId == null) ? 0 : personPropertyId.hashCode()); + result = prime * result + ((value == null) ? 0 : value.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof PersonPropertyValueInitialization)) { + return false; + } + PersonPropertyValueInitialization other = (PersonPropertyValueInitialization) obj; + if (personPropertyId == null) { + if (other.personPropertyId != null) { + return false; + } + } else if (!personPropertyId.equals(other.personPropertyId)) { + return false; + } + if (value == null) { + if (other.value != null) { + return false; + } + } else if (!value.equals(other.value)) { + return false; + } + return true; + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/testsupport/PersonPropertiesTestPluginFactory.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/testsupport/PersonPropertiesTestPluginFactory.java new file mode 100644 index 000000000..6163ddca4 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/testsupport/PersonPropertiesTestPluginFactory.java @@ -0,0 +1,425 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.testsupport; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePlugin; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeoplePluginData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonRange; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.PersonPropertiesPlugin; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.reports.PersonPropertyInteractionReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.reports.PersonPropertyReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyError; +import gov.hhs.aspr.ms.gcm.plugins.regions.RegionsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionError; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.TestRegionId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.StochasticsError; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +/** + * A static test support class for the {@linkplain PersonPropertiesPlugin}. + * Provides convenience methods for obtaining standarized PluginData for the + * listed Plugin. + *

    + * Also contains factory methods to obtain a list of plugins that is the minimal + * set needed to adequately test this Plugin that can be utilized with + *

    + * {@link TestSimulation#execute} + */ +public class PersonPropertiesTestPluginFactory { + + private PersonPropertiesTestPluginFactory() { + } + + private static class Data { + private PersonPropertyInteractionReportPluginData personPropertyInteractionReportPluginData; + private PersonPropertyReportPluginData personPropertyReportPluginData; + private PersonPropertiesPluginData personPropertiesPluginData; + private RegionsPluginData regionsPluginData; + private PeoplePluginData peoplePluginData; + private StochasticsPluginData stochasticsPluginData; + private TestPluginData testPluginData; + + private Data(int initialPopulation, long seed, TestPluginData testPluginData) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + this.peoplePluginData = getStandardPeoplePluginData(initialPopulation); + this.personPropertiesPluginData = PersonPropertiesTestPluginFactory.getStandardPersonPropertiesPluginData( + this.peoplePluginData.getPersonIds(), randomGenerator.nextLong()); + this.regionsPluginData = PersonPropertiesTestPluginFactory + .getStandardRegionsPluginData(this.peoplePluginData.getPersonIds(), randomGenerator.nextLong()); + this.stochasticsPluginData = getStandardStochasticsPluginData(randomGenerator.nextLong()); + this.testPluginData = testPluginData; + } + } + + /** + * Factory class that facilitates the building of {@linkplain PluginData} with + * the various setter methods. + */ + public static class Factory { + private Data data; + + private Factory(Data data) { + this.data = data; + } + + /** + * Returns a list of plugins containing a PersonProperties, Regions, People, + * Stochastics and Test Plugin built from the contributed PluginDatas. + *
      + *
    • PersonPropertiesPlugin is defaulted to one formed from + * {@link PersonPropertiesTestPluginFactory#getStandardPersonPropertiesPluginData} + *
    • + *
    • RegionsPlugin is defaulted to one formed from + * {@link PersonPropertiesTestPluginFactory#getStandardRegionsPluginData}
    • + *
    • PeoplePlugin is defaulted to one formed from + * {@link PersonPropertiesTestPluginFactory#getStandardPeoplePluginData}
    • + *
    • StochasticsPlugin is defaulted to one formed from + * {@link PersonPropertiesTestPluginFactory#getStandardStochasticsPluginData} + *
    • + *
    • TestPlugin is formed from the TestPluginData passed into + * {@link PersonPropertiesTestPluginFactory#factory}
    • + *
    + */ + public List getPlugins() { + List pluginsToAdd = new ArrayList<>(); + + PersonPropertiesPlugin.Builder personPropertiesPluginBuilder = PersonPropertiesPlugin.builder() + .setPersonPropertiesPluginData(data.personPropertiesPluginData); + if (data.personPropertyInteractionReportPluginData != null) { + personPropertiesPluginBuilder + .setPersonPropertyInteractionReportPluginData(data.personPropertyInteractionReportPluginData); + } + if (data.personPropertyReportPluginData != null) { + personPropertiesPluginBuilder.setPersonPropertyReportPluginData(data.personPropertyReportPluginData); + } + Plugin personPropertiesPlugin = personPropertiesPluginBuilder.getPersonPropertyPlugin(); + + Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(this.data.peoplePluginData); + + Plugin regionsPlugin = RegionsPlugin.builder().setRegionsPluginData(this.data.regionsPluginData) + .getRegionsPlugin(); + + Plugin stochasticPlugin = StochasticsPlugin.getStochasticsPlugin(this.data.stochasticsPluginData); + + Plugin testPlugin = TestPlugin.getTestPlugin(this.data.testPluginData); + + pluginsToAdd.add(personPropertiesPlugin); + pluginsToAdd.add(peoplePlugin); + pluginsToAdd.add(regionsPlugin); + pluginsToAdd.add(stochasticPlugin); + pluginsToAdd.add(testPlugin); + + return pluginsToAdd; + } + + /** + * Sets the {@link PersonPropertiesPluginData} in this Factory. This explicit + * instance of pluginData will be used to create a PersonPropertiesPlugin + * + * @throws ContractException {@linkplain PersonPropertyError#NULL_PERSON_PROPERTY_PLUGN_DATA} + * if the passed in pluginData is null + */ + public Factory setPersonPropertiesPluginData(PersonPropertiesPluginData personPropertiesPluginData) { + if (personPropertiesPluginData == null) { + throw new ContractException(PersonPropertyError.NULL_PERSON_PROPERTY_PLUGN_DATA); + } + this.data.personPropertiesPluginData = personPropertiesPluginData; + return this; + } + + /** + * Sets the {@link PersonPropertyInteractionReportPluginData} in this Factory. + * This explicit instance of pluginData will be used to create a + * PersonPropertiesPlugin + * + * @throws ContractException {@linkplain PersonPropertyError#NULL_PERSON_PROPERTY_INTERACTION_REPORT_PLUGIN_DATA} + * if the passed in pluginData is null + */ + public Factory setPersonPropertyInteractionReportPluginData( + PersonPropertyInteractionReportPluginData personPropertyInteractionReportPluginData) { + if (personPropertyInteractionReportPluginData == null) { + throw new ContractException(PersonPropertyError.NULL_PERSON_PROPERTY_INTERACTION_REPORT_PLUGIN_DATA); + } + this.data.personPropertyInteractionReportPluginData = personPropertyInteractionReportPluginData; + return this; + } + + /** + * Sets the {@link PersonPropertyInteractionReportPluginData} in this Factory. + * This explicit instance of pluginData will be used to create a + * PersonPropertiesPlugin + * + * @throws ContractException {@linkplain PersonPropertyError#NULL_PERSON_PROPERTY_REPORT_PLUGIN_DATA} + * if the passed in pluginData is null + */ + public Factory setPersonPropertyReportPluginData( + PersonPropertyReportPluginData personPropertyReportPluginData) { + if (personPropertyReportPluginData == null) { + throw new ContractException(PersonPropertyError.NULL_PERSON_PROPERTY_REPORT_PLUGIN_DATA); + } + this.data.personPropertyReportPluginData = personPropertyReportPluginData; + return this; + } + + /** + * Sets the {@link PeoplePluginData} in this Factory. This explicit instance of + * pluginData will be used to create a PeoplePlugin + * + * @throws ContractException {@linkplain PersonError#NULL_PEOPLE_PLUGIN_DATA} if + * the passed in pluginData is null + */ + public Factory setPeoplePluginData(PeoplePluginData peoplePluginData) { + if (peoplePluginData == null) { + throw new ContractException(PersonError.NULL_PEOPLE_PLUGIN_DATA); + } + this.data.peoplePluginData = peoplePluginData; + return this; + } + + /** + * Sets the {@link RegionsPluginData} in this Factory. This explicit instance of + * pluginData will be used to create a RegionsPlugin + * + * @throws ContractException {@linkplain RegionError#NULL_REGION_PLUGIN_DATA} if + * the passed in pluginData is null + */ + public Factory setRegionsPluginData(RegionsPluginData regionsPluginData) { + if (regionsPluginData == null) { + throw new ContractException(RegionError.NULL_REGION_PLUGIN_DATA); + } + this.data.regionsPluginData = regionsPluginData; + return this; + } + + /** + * Sets the {@link StochasticsPluginData} in this Factory. This explicit + * instance of pluginData will be used to create a StochasticsPlugin + * + * @throws ContractException {@linkplain StochasticsError#NULL_STOCHASTICS_PLUGIN_DATA} + * if the passed in pluginData is null + */ + public Factory setStochasticsPluginData(StochasticsPluginData stochasticsPluginData) { + if (stochasticsPluginData == null) { + throw new ContractException(StochasticsError.NULL_STOCHASTICS_PLUGIN_DATA); + } + this.data.stochasticsPluginData = stochasticsPluginData; + return this; + } + + } + + /** + * Creates a Factory that facilitates the creation of a minimal set of plugins + * needed to adequately test the {@link PersonPropertiesPlugin} by generating: + *
      + *
    • {@link PersonPropertiesPluginData}
    • + *
    • {@link RegionsPluginData}
    • + *
    • {@link PeoplePluginData}
    • + *
    • {@link StochasticsPluginData}
    • + *
    + * either directly (by default) via + *
      + *
    • {@link #getStandardPersonPropertiesPluginData}
    • + *
    • {@link #getStandardPeoplePluginData}, + *
    • {@link #getStandardRegionsPluginData}, + *
    • {@link #getStandardStochasticsPluginData}
    • + *
    + * or explicitly set via + *
      + *
    • {@link Factory#setPersonPropertiesPluginData}
    • + *
    • {@link Factory#setPeoplePluginData}, + *
    • {@link Factory#setRegionsPluginData}, + *
    • {@link Factory#setStochasticsPluginData}
    • + *
    + * via the {@link Factory#getPlugins()} method. + * + * @throws ContractException {@linkplain NucleusError#NULL_PLUGIN_DATA} if + * testPluginData is null + */ + public static Factory factory(int initialPopulation, long seed, TestPluginData testPluginData) { + if (testPluginData == null) { + throw new ContractException(NucleusError.NULL_PLUGIN_DATA); + } + return new Factory(new Data(initialPopulation, seed, testPluginData)); + } + + /** + * Creates a Factory that facilitates the creation of a minimal set of plugins + * needed to adequately test the {@link PersonPropertiesPlugin} by generating: + *
      + *
    • {@link PersonPropertiesPluginData}
    • + *
    • {@link RegionsPluginData}
    • + *
    • {@link PeoplePluginData}
    • + *
    • {@link StochasticsPluginData}
    • + *
    + * either directly (by default) via + *
      + *
    • {@link #getStandardPersonPropertiesPluginData}
    • + *
    • {@link #getStandardPeoplePluginData}, + *
    • {@link #getStandardRegionsPluginData}, + *
    • {@link #getStandardStochasticsPluginData}
    • + *
    + * or explicitly set via + *
      + *
    • {@link Factory#setPersonPropertiesPluginData}
    • + *
    • {@link Factory#setPeoplePluginData}, + *
    • {@link Factory#setRegionsPluginData}, + *
    • {@link Factory#setStochasticsPluginData}
    • + *
    + * via the {@link Factory#getPlugins()} method. + * + * @throws ContractException {@linkplain NucleusError#NULL_ACTOR_CONTEXT_CONSUMER} + * if consumer is null + */ + public static Factory factory(int initialPopulation, long seed, Consumer consumer) { + if (consumer == null) { + throw new ContractException(NucleusError.NULL_ACTOR_CONTEXT_CONSUMER); + } + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, consumer)); + TestPluginData testPluginData = pluginBuilder.build(); + return factory(initialPopulation, seed, testPluginData); + } + + /** + * Returns a standardized PersonPropertiesPluginData that is minimally adequate + * for testing the PersonPropertiesPlugin. The resulting + * PersonPropertiesPluginData will include: + *
      + *
    • Every PersonPropertyId included in {@link TestPersonPropertyId} + *
        + *
      • along with the propertyDefinition for each
      • + *
      + *
    • Every person in the list of passed in people. + *
        + *
      • Each person will have a property value if the propertyDefinition does not + * have a defaultValue, OR via a RandomGenerator seeded by the passed in seed + * via nextBoolean.
      • + *
      • The property value is gotten from + * {@link TestPersonPropertyId#getRandomPropertyValue}
      • + *
      + *
    + */ + public static PersonPropertiesPluginData getStandardPersonPropertiesPluginData(List people, long seed, + double... propertyTime) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + + double actualPropertyTime = 0; + if (propertyTime.length > 0) { + actualPropertyTime = propertyTime[0]; + } + + PersonPropertiesPluginData.Builder personPropertyBuilder = PersonPropertiesPluginData.builder(); + + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.getPersonPropertyIds()) { + personPropertyBuilder.definePersonProperty(testPersonPropertyId, + testPersonPropertyId.getPropertyDefinition(), 0.0, testPersonPropertyId.isTimeTracked()); + } + + for (PersonId personId : people) { + + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId + .getShuffledPersonPropertyIds(randomGenerator)) { + + boolean hasDefault = testPersonPropertyId.getPropertyDefinition().getDefaultValue().isPresent(); + boolean setValue = randomGenerator.nextBoolean(); + + if (!hasDefault || setValue) { + Object randomPropertyValue = testPersonPropertyId.getRandomPropertyValue(randomGenerator); + personPropertyBuilder.setPersonPropertyValue(personId, testPersonPropertyId, randomPropertyValue); + } else if (hasDefault && personId.getValue() % 5 == 0) { + personPropertyBuilder.setPersonPropertyValue(personId, testPersonPropertyId, + testPersonPropertyId.getPropertyDefinition().getDefaultValue().get()); + } + + if (testPersonPropertyId.isTimeTracked() && personId.getValue() % 5 == 0) { + personPropertyBuilder.setPersonPropertyTime(personId, testPersonPropertyId, actualPropertyTime); + } + } + } + + return personPropertyBuilder.build(); + } + + /** + * Returns a standardized PeoplePluginData that is minimally adequate for + * testing the PersonPropertiesPlugin The resulting PeoplePluginData will + * include: + *
      + *
    • a number of people equal to the passed in intialPopulation
    • + *
    + */ + public static PeoplePluginData getStandardPeoplePluginData(int initialPopulation) { + PeoplePluginData.Builder peopleBuilder = PeoplePluginData.builder(); + if (initialPopulation > 0) { + peopleBuilder.addPersonRange(new PersonRange(0, initialPopulation - 1)); + } + return peopleBuilder.build(); + } + + /** + * Returns a standardized RegionsPluginData that is minimally adequate for + * testing the PersonPropertiesPlugin The resulting RegionsPluginData will + * include: + *
      + *
    • Every RegionId included in {@link TestRegionId}
    • + *
    • Every person in the list of passed in people. + *
        + *
      • Each person will be assigned to a random region based on a + * RandomGenerator seeded by the passed in seed and selected via the + * {@link TestRegionId#getRandomRegionId}
      • + *
      + *
    + */ + public static RegionsPluginData getStandardRegionsPluginData(List people, long seed) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + + RegionsPluginData.Builder regionBuilder = RegionsPluginData.builder(); + // add the regions + for (TestRegionId testRegionId : TestRegionId.values()) { + regionBuilder.addRegion(testRegionId); + } + for (PersonId personId : people) { + TestRegionId randomRegionId = TestRegionId.getRandomRegionId(randomGenerator); + regionBuilder.addPerson(personId, randomRegionId); + } + return regionBuilder.build(); + + } + + /** + * Returns a standardized StochasticsPluginData that is minimally adequate for + * testing the PersonPropertiesPlugin The resulting StochasticsPluginData will + * include: + *
      + *
    • a seed based on the nextLong of a RandomGenerator seeded from the passed + * in seed
    • + *
    + */ + public static StochasticsPluginData getStandardStochasticsPluginData(long seed) { + WellState wellState = WellState.builder().setSeed(seed).build(); + return StochasticsPluginData.builder().setMainRNGState(wellState).build(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/testsupport/TestAuxiliaryPersonPropertyId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/testsupport/TestAuxiliaryPersonPropertyId.java new file mode 100644 index 000000000..4d8da36b9 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/testsupport/TestAuxiliaryPersonPropertyId.java @@ -0,0 +1,121 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.testsupport; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; + +/** + * Enumeration that identifies person property definitions + */ +public enum TestAuxiliaryPersonPropertyId implements PersonPropertyId { + PERSON_AUX_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK(PropertyDefinition.builder()// + .setType(Boolean.class)// + .setDefaultValue(false)// + .setPropertyValueMutability(true)// + .build()), // + PERSON_AUX_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK(PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(0)// + .setPropertyValueMutability(true)// + .build() // + ), // + PERSON_AUX_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK(PropertyDefinition.builder()// + .setType(Double.class)// + .setDefaultValue(0.0)// + .setPropertyValueMutability(true)// + .build() // + ), // + PERSON_AUX_PROPERTY_4_BOOLEAN_MUTABLE_TRACK(PropertyDefinition.builder()// + .setType(Boolean.class)// + .setDefaultValue(false)// + .setPropertyValueMutability(true)// + .build() // + ), // + PERSON_AUX_PROPERTY_5_INTEGER_MUTABLE_TRACK(PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(0)// + .setPropertyValueMutability(true)// + .build() // + ), // + PERSON_AUX_PROPERTY_6_DOUBLE_MUTABLE_TRACK(PropertyDefinition.builder()// + .setType(Double.class)// + .setDefaultValue(0.0)// + .setPropertyValueMutability(true)// + .build() // + ), // + PERSON_AUX_PROPERTY_7_BOOLEAN_IMMUTABLE_NO_TRACK(PropertyDefinition.builder()// + .setType(Boolean.class)// + .setDefaultValue(false)// + .setPropertyValueMutability(false)// + .build() // + ), // + PERSON_AUX_PROPERTY_8_INTEGER_IMMUTABLE_NO_TRACK(PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(0)// + .setPropertyValueMutability(false)// + .build() // + ), // + PERSON_AUX_PROPERTY_9_DOUBLE_IMMUTABLE_NO_TRACK(PropertyDefinition.builder()// + .setType(Double.class)// + .setDefaultValue(0.0)// + .setPropertyValueMutability(false)// + .build() // + );// + + /** + * Returns a randomly selected member of this enumeration + */ + public static TestAuxiliaryPersonPropertyId getRandomPersonPropertyId(final RandomGenerator randomGenerator) { + return TestAuxiliaryPersonPropertyId.values()[randomGenerator + .nextInt(TestAuxiliaryPersonPropertyId.values().length)]; + } + + /** + * Returns a randomly selected value that is compatible with this member's + * associated property definition. + */ + public Object getRandomPropertyValue(final RandomGenerator randomGenerator) { + switch (this) { + case PERSON_AUX_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK: + return randomGenerator.nextBoolean(); + case PERSON_AUX_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK: + return randomGenerator.nextInt(); + case PERSON_AUX_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK: + return randomGenerator.nextDouble(); + case PERSON_AUX_PROPERTY_4_BOOLEAN_MUTABLE_TRACK: + return randomGenerator.nextBoolean(); + case PERSON_AUX_PROPERTY_5_INTEGER_MUTABLE_TRACK: + return randomGenerator.nextInt(); + case PERSON_AUX_PROPERTY_6_DOUBLE_MUTABLE_TRACK: + return randomGenerator.nextDouble(); + case PERSON_AUX_PROPERTY_7_BOOLEAN_IMMUTABLE_NO_TRACK: + return randomGenerator.nextBoolean(); + case PERSON_AUX_PROPERTY_8_INTEGER_IMMUTABLE_NO_TRACK: + return randomGenerator.nextInt(); + case PERSON_AUX_PROPERTY_9_DOUBLE_IMMUTABLE_NO_TRACK: + return randomGenerator.nextDouble(); + default: + throw new RuntimeException("unhandled case: " + this); + + } + } + + private final PropertyDefinition propertyDefinition; + + private TestAuxiliaryPersonPropertyId(PropertyDefinition propertyDefinition) { + this.propertyDefinition = propertyDefinition; + } + + public PropertyDefinition getPropertyDefinition() { + return propertyDefinition; + } + + /** + * Returns a new {@link PersonPropertyId} instance. + */ + public static PersonPropertyId getUnknownPersonPropertyId() { + return new PersonPropertyId() { + }; + } +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/testsupport/TestPersonPropertyId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/testsupport/TestPersonPropertyId.java new file mode 100644 index 000000000..a1321e7f5 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/testsupport/TestPersonPropertyId.java @@ -0,0 +1,182 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.testsupport; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; + +/** + * Enumeration that identifies person property definitions + */ +public enum TestPersonPropertyId implements PersonPropertyId { + PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK(PropertyDefinition.builder()// + .setType(Boolean.class)// + .setDefaultValue(false)// + .setPropertyValueMutability(true)// + .build(), false), // + PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK(PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(0)// + .setPropertyValueMutability(true)// + .build() // + , false), // + PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK(PropertyDefinition.builder()// + .setType(Double.class)// + .setDefaultValue(0.0)// + .setPropertyValueMutability(true)// + .build() // + , false), // + PERSON_PROPERTY_4_BOOLEAN_MUTABLE_TRACK(PropertyDefinition.builder()// + .setType(Boolean.class)// + .setDefaultValue(false)// + .setPropertyValueMutability(true)// + .build() // + , true), // + PERSON_PROPERTY_5_INTEGER_MUTABLE_TRACK(PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(0)// + .setPropertyValueMutability(true)// + .build() // + , true), // + PERSON_PROPERTY_6_DOUBLE_MUTABLE_TRACK(PropertyDefinition.builder()// + .setType(Double.class)// + .setDefaultValue(0.0)// + .setPropertyValueMutability(true)// + .build() // + , true), // + PERSON_PROPERTY_7_BOOLEAN_IMMUTABLE_NO_TRACK(PropertyDefinition.builder()// + .setType(Boolean.class)// + .setDefaultValue(false)// + .setPropertyValueMutability(false)// + .build() // + , false), // + PERSON_PROPERTY_8_INTEGER_IMMUTABLE_NO_TRACK(PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(0)// + .setPropertyValueMutability(false)// + .build() // + , false), // + PERSON_PROPERTY_9_DOUBLE_IMMUTABLE_NO_TRACK(PropertyDefinition.builder()// + .setType(Double.class)// + // .setDefaultValue(0.0)// + .setPropertyValueMutability(false)// + .build() // + , false);// + + /** + * Returns the test property ids associated with a default value + */ + public static List getPropertiesWithDefaultValues() { + List result = new ArrayList<>(); + + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + if (testPersonPropertyId.getPropertyDefinition().getDefaultValue().isPresent()) { + result.add(testPersonPropertyId); + } + } + + return result; + } + + /** + * Returns the test property ids not associated with a default value + */ + public static List getPropertiesWithoutDefaultValues() { + List result = new ArrayList<>(); + + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + if (testPersonPropertyId.getPropertyDefinition().getDefaultValue().isEmpty()) { + result.add(testPersonPropertyId); + } + } + + return result; + } + + /** + * Returns a randomly selected member of this enumeration + */ + public static TestPersonPropertyId getRandomPersonPropertyId(final RandomGenerator randomGenerator) { + return TestPersonPropertyId.values()[randomGenerator.nextInt(TestPersonPropertyId.values().length)]; + } + + /** + * Returns a randomly selected value that is compatible with this member's + * associated property definition. + */ + public Object getRandomPropertyValue(final RandomGenerator randomGenerator) { + switch (this) { + case PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK: + return randomGenerator.nextBoolean(); + case PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK: + return randomGenerator.nextInt(); + case PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK: + return randomGenerator.nextDouble(); + case PERSON_PROPERTY_4_BOOLEAN_MUTABLE_TRACK: + return randomGenerator.nextBoolean(); + case PERSON_PROPERTY_5_INTEGER_MUTABLE_TRACK: + return randomGenerator.nextInt(); + case PERSON_PROPERTY_6_DOUBLE_MUTABLE_TRACK: + return randomGenerator.nextDouble(); + case PERSON_PROPERTY_7_BOOLEAN_IMMUTABLE_NO_TRACK: + return randomGenerator.nextBoolean(); + case PERSON_PROPERTY_8_INTEGER_IMMUTABLE_NO_TRACK: + return randomGenerator.nextInt(); + case PERSON_PROPERTY_9_DOUBLE_IMMUTABLE_NO_TRACK: + return randomGenerator.nextDouble(); + default: + throw new RuntimeException("unhandled case: " + this); + + } + } + + private final PropertyDefinition propertyDefinition; + private final boolean timeTracked; + + private TestPersonPropertyId(PropertyDefinition propertyDefinition, boolean timeTracked) { + this.propertyDefinition = propertyDefinition; + this.timeTracked = timeTracked; + } + + public boolean isTimeTracked() { + return timeTracked; + } + + public PropertyDefinition getPropertyDefinition() { + return propertyDefinition; + } + + /** + * Returns a new {@link PersonPropertyId} instance. + */ + public static PersonPropertyId getUnknownPersonPropertyId() { + return new PersonPropertyId() { + }; + } + + private TestPersonPropertyId next; + + public TestPersonPropertyId next() { + if (next == null) { + next = TestPersonPropertyId.values()[(ordinal() + 1) % TestPersonPropertyId.values().length]; + } + return next; + } + + public static List getPersonPropertyIds() { + return Arrays.asList(TestPersonPropertyId.values()); + } + + public static List getShuffledPersonPropertyIds(RandomGenerator randomGenerator) { + List result = getPersonPropertyIds(); + Collections.shuffle(result, new Random(randomGenerator.nextLong())); + + return result; + } +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/testsupport/TestPersonPropertyLabeler.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/testsupport/TestPersonPropertyLabeler.java new file mode 100644 index 000000000..6e1f2b8e1 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/testsupport/TestPersonPropertyLabeler.java @@ -0,0 +1,60 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.testsupport; + +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.Equality; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyLabeler; + +/** + * A person property labeler that labels people as either true or false based on + * an Equality based comparison with an integer value and the specified value. + * Should only be used with properties that are comparable to integers when the + * equality type is not Equality.EQUAL or Equality.NOT_EQUAL. + */ +public class TestPersonPropertyLabeler extends PersonPropertyLabeler { + + private final Equality equality; + + private final Integer value; + + public int getValue() { + return value; + } + + public Equality getEquality() { + return equality; + } + + public TestPersonPropertyLabeler(PersonPropertyId personPropertyId, Equality equality, int value) { + super(personPropertyId); + this.equality = equality; + this.value = value; + } + + @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) + protected Object getLabelFromValue(Object value) { + if (equality.equals(Equality.EQUAL)) { + return this.value.equals(value); + } else if (equality.equals(Equality.NOT_EQUAL)) { + return !this.value.equals(value); + } else { + Comparable comparableAttributeValue = (Comparable) value; + int evaluation = comparableAttributeValue.compareTo(this.value); + return equality.isCompatibleComparisonValue(evaluation); + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("TestPersonPropertyLabeler [equality="); + builder.append(equality); + builder.append(", value="); + builder.append(value); + builder.append(", personPropertyId="); + builder.append(getPersonPropertyId()); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/RegionsPlugin.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/RegionsPlugin.java new file mode 100644 index 000000000..fb13cde38 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/RegionsPlugin.java @@ -0,0 +1,122 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions; + +import java.util.Optional; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePluginId; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.regions.reports.RegionPropertyReport; +import gov.hhs.aspr.ms.gcm.plugins.regions.reports.RegionPropertyReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.regions.reports.RegionTransferReport; +import gov.hhs.aspr.ms.gcm.plugins.regions.reports.RegionTransferReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionError; +import util.errors.ContractException; + +public final class RegionsPlugin { + + private RegionsPlugin() { + } + + private static class Data { + private RegionsPluginData regionsPluginData; + private RegionPropertyReportPluginData regionPropertyReportPluginData; + private RegionTransferReportPluginData regionTransferReportPluginData; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Builder() { + } + + private Data data = new Data(); + + private void validate() { + if (data.regionsPluginData == null) { + throw new ContractException(RegionError.NULL_REGION_PLUGIN_DATA); + } + } + + /** + * Builds the RegionsPlugin from the collected inputs + * + * @throws ContractException {@linkplain RegionError#NULL_REGION_PLUGIN_DATA} if + * the regionsPluginData is null + */ + public Plugin getRegionsPlugin() { + + validate(); + Plugin.Builder builder = Plugin.builder();// + builder.setPluginId(RegionsPluginId.PLUGIN_ID);// + builder.addPluginData(data.regionsPluginData);// + if (data.regionPropertyReportPluginData != null) { + builder.addPluginData(data.regionPropertyReportPluginData);// + } + if (data.regionTransferReportPluginData != null) { + builder.addPluginData(data.regionTransferReportPluginData);// + } + builder.addPluginDependency(PeoplePluginId.PLUGIN_ID);// + + builder.setInitializer((c) -> { + RegionsPluginData pluginData = c.getPluginData(RegionsPluginData.class).get(); + c.addDataManager(new RegionsDataManager(pluginData)); + + Optional optional1 = c + .getPluginData(RegionPropertyReportPluginData.class); + if (optional1.isPresent()) { + RegionPropertyReportPluginData regionPropertyReportPluginData = optional1.get(); + c.addReport(new RegionPropertyReport(regionPropertyReportPluginData)::init); + } + + Optional optional2 = c + .getPluginData(RegionTransferReportPluginData.class); + if (optional2.isPresent()) { + RegionTransferReportPluginData regionTransferReportPluginData = optional2.get(); + c.addReport(new RegionTransferReport(regionTransferReportPluginData)::init); + } + + }); + return builder.build(); + + } + + public Builder setRegionsPluginData(RegionsPluginData regionsPluginData) { + data.regionsPluginData = regionsPluginData; + return this; + } + + public Builder setRegionPropertyReportPluginData( + RegionPropertyReportPluginData regionPropertyReportPluginData) { + data.regionPropertyReportPluginData = regionPropertyReportPluginData; + return this; + } + + public Builder setRegionTransferReportPluginData( + RegionTransferReportPluginData regionTransferReportPluginData) { + data.regionTransferReportPluginData = regionTransferReportPluginData; + return this; + } + } + + // public static Plugin getRegionsPlugin(RegionsPluginData + // regionsPluginData) { + // + // return Plugin .builder()// + + // + // + // .setInitializer((c) -> { + // RegionsPluginData pluginData = + // c.getPluginData(RegionsPluginData.class).get(); + // c.addDataManager(new RegionsDataManager(pluginData)); + // + // + // })// + // .build(); + // + // } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/RegionsPluginId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/RegionsPluginId.java new file mode 100644 index 000000000..e27a89fd3 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/RegionsPluginId.java @@ -0,0 +1,13 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; + +/** + * Static plugin id implementation for the GlobalsPlugin + */ +public final class RegionsPluginId implements PluginId { + public final static PluginId PLUGIN_ID = new RegionsPluginId(); + + private RegionsPluginId() { + }; +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/datamanagers/RegionsDataManager.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/datamanagers/RegionsDataManager.java new file mode 100644 index 000000000..7c936ab90 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/datamanagers/RegionsDataManager.java @@ -0,0 +1,1172 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import org.apache.commons.math3.util.Pair; + +import gov.hhs.aspr.ms.gcm.nucleus.DataManager; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.nucleus.EventFilter; +import gov.hhs.aspr.ms.gcm.nucleus.IdentifiableFunctionMap; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.events.PersonImminentAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.events.PersonRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonConstructionData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.regions.RegionsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.regions.events.PersonRegionUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.regions.events.RegionAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.regions.events.RegionPropertyDefinitionEvent; +import gov.hhs.aspr.ms.gcm.plugins.regions.events.RegionPropertyUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionConstructionData; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionError; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionPropertyDefinitionInitialization; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.arraycontainers.DoubleValueContainer; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.arraycontainers.IntValueContainer; +import util.errors.ContractException; +import util.wrappers.MutableInteger; + +/** + * Mutable data manager for regions. This data manager is for internal use by + * the {@link RegionsPlugin} and should not be published. All regions and region + * properties are established during construction and cannot be changed. Region + * property values are mutable. Limited validation of inputs are performed and + * mutation methods have invocation ordering requirements. + */ +public final class RegionsDataManager extends DataManager { + + private DataManagerContext dataManagerContext; + + private final RegionsPluginData regionsPluginData; + + private PeopleDataManager peopleDataManager; + + private final Map nonDefaultBearingPropertyIds = new LinkedHashMap<>(); + + private boolean[] nonDefaultChecks = new boolean[0]; + + private Map> regionPropertyMap = new LinkedHashMap<>(); + + private final Set regionPropertyIds = new LinkedHashSet<>(); + private final Map regionPropertyDefinitions = new LinkedHashMap<>(); + + /* + * Tracking record for the total number of people in each region. + */ + private final Map regionPopulationRecordMap = new LinkedHashMap<>(); + + /* + * Supports the conversion of region ids into int values. + */ + private final Map regionToIndexMap = new LinkedHashMap<>(); + + /* + * Supports conversion of int into RegionId values + */ + private final List indexToRegionMap = new ArrayList<>(); + + /* + * Stores region identifiers as int values indexed by person id values + */ + private IntValueContainer regionValues; + + /* + * Stores double region arrival values indexed by person id values. Maintenance + * depends upon tracking policy. + */ + private DoubleValueContainer regionArrivalTimes; + + /** + * Creates a Region Data Manager from the given resolver context. Preconditions: + * The context must be a valid and non-null. + */ + public RegionsDataManager(final RegionsPluginData regionsPluginData) { + if (regionsPluginData == null) { + throw new ContractException(RegionError.NULL_REGION_PLUGIN_DATA); + } + this.regionsPluginData = regionsPluginData; + } + + private void addNonDefaultProperty(final RegionPropertyId regionPropertyId) { + nonDefaultBearingPropertyIds.put(regionPropertyId, nonDefaultBearingPropertyIds.size()); + nonDefaultChecks = new boolean[nonDefaultBearingPropertyIds.size()]; + } + + private void clearNonDefaultChecks() { + for (int i = 0; i < nonDefaultChecks.length; i++) { + nonDefaultChecks[i] = false; + } + } + + private void markAssigned(RegionPropertyId regionPropertyId) { + Integer nonDefaultPropertyIndex = nonDefaultBearingPropertyIds.get(regionPropertyId); + if (nonDefaultPropertyIndex != null) { + nonDefaultChecks[nonDefaultPropertyIndex] = true; + } + } + + private void verifyNonDefaultChecks() { + + boolean missingPropertyAssignments = false; + + for (boolean nonDefaultCheck : nonDefaultChecks) { + if (!nonDefaultCheck) { + missingPropertyAssignments = true; + break; + } + } + + if (missingPropertyAssignments) { + StringBuilder sb = new StringBuilder(); + int index = -1; + boolean firstMember = true; + for (RegionPropertyId regionPropertyId : nonDefaultBearingPropertyIds.keySet()) { + index++; + if (!nonDefaultChecks[index]) { + if (firstMember) { + firstMember = false; + } else { + sb.append(", "); + } + sb.append(regionPropertyId); + } + } + throw new ContractException(PropertyError.INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT, sb.toString()); + } + } + + private void validateRegionConstructionDataNotNull(RegionConstructionData regionConstructionData) { + if (regionConstructionData == null) { + throw new ContractException(RegionError.NULL_REGION_CONSTRUCTION_DATA); + } + } + + private static record RegionAdditionMutationEvent(RegionConstructionData regionConstructionData) implements Event { + } + + /** + * Adds a new region id + * + * @throws ContractException + *
      + *
    • {@linkplain RegionError#NULL_REGION_CONSTRUCTION_DATA} + * if the region construction data is null
    • + *
    • {@linkplain RegionError#DUPLICATE_REGION_ID} if + * the region is already present
    • + *
    • {@linkplain PropertyError#INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT} + * if for any property that lacks default value there + * is a region that has not had a value assigned
    • + *
    + */ + public void addRegion(final RegionConstructionData regionConstructionData) { + dataManagerContext.releaseMutationEvent(new RegionAdditionMutationEvent(regionConstructionData)); + } + + private void handleRegionAdditionMutationEvent(DataManagerContext dataManagerContext, + RegionAdditionMutationEvent regionAdditionMutationEvent) { + RegionConstructionData regionConstructionData = regionAdditionMutationEvent.regionConstructionData(); + validateRegionConstructionDataNotNull(regionConstructionData); + RegionId regionId = regionConstructionData.getRegionId(); + validateNewRegionId(regionId); + Map regionPropertyValues = regionConstructionData.getRegionPropertyValues(); + for (RegionPropertyId regionPropertyId : regionPropertyValues.keySet()) { + validateRegionPropertyId(regionPropertyId); + Object regionPropertyValue = regionPropertyValues.get(regionPropertyId); + final PropertyDefinition propertyDefinition = regionPropertyDefinitions.get(regionPropertyId); + validateValueCompatibility(regionPropertyId, propertyDefinition, regionPropertyValue); + } + + if (!nonDefaultBearingPropertyIds.isEmpty()) { + clearNonDefaultChecks(); + for (RegionPropertyId regionPropertyId : regionPropertyValues.keySet()) { + markAssigned(regionPropertyId); + } + verifyNonDefaultChecks(); + } + + regionPopulationRecordMap.put(regionId, new MutableInteger()); + regionToIndexMap.put(regionId, regionToIndexMap.size() + 1); + indexToRegionMap.add(regionId); + + if (!regionPropertyValues.isEmpty()) { + final Map map = new LinkedHashMap<>(); + regionPropertyMap.put(regionId, map); + for (RegionPropertyId regionPropertyId : regionPropertyValues.keySet()) { + Object regionPropertyValue = regionPropertyValues.get(regionPropertyId); + map.put(regionPropertyId, regionPropertyValue); + } + } + + if (dataManagerContext.subscribersExist(RegionAdditionEvent.class)) { + RegionAdditionEvent.Builder regionAdditionEventBuilder = RegionAdditionEvent.builder() + .setRegionId(regionId); + for (Object value : regionConstructionData.getValues(Object.class)) { + regionAdditionEventBuilder.addValue(value); + } + RegionAdditionEvent regionAdditionEvent = regionAdditionEventBuilder.build(); + dataManagerContext.releaseObservationEvent(regionAdditionEvent); + } + + } + + private static record RegionPropertyDefinitionMutationEvent( + RegionPropertyDefinitionInitialization regionPropertyDefinitionInitialization) implements Event { + } + + /** + * Adds a new region property + * + * @throws ContractException + *
      + *
    • {@linkplain RegionError#NULL_REGION_PROPERTY_DEFINITION_INITIALIZATION} + * if the region property definition initialization is + * null
    • + *
    • {@linkplain PropertyError#DUPLICATE_PROPERTY_DEFINITION} + * if the region property is already defined
    • + *
    • {@linkplain RegionError#UNKNOWN_REGION_ID} if + * the RegionPropertyDefinitionInitialization contains + * region property assignments for unknown + * regions
    • + *
    • {@linkplain PropertyError#INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT} + * if the region property definition has no default + * and a property value for some region is missing + * from the + * RegionPropertyDefinitionInitialization
    • + *
    + */ + public void defineRegionProperty( + final RegionPropertyDefinitionInitialization regionPropertyDefinitionInitialization) { + dataManagerContext.releaseMutationEvent( + new RegionPropertyDefinitionMutationEvent(regionPropertyDefinitionInitialization)); + } + + private void handleRegionPropertyDefinitionMutationEvent(DataManagerContext dataManagerContext, + RegionPropertyDefinitionMutationEvent regionPropertyDefinitionMutationEvent) { + + RegionPropertyDefinitionInitialization regionPropertyDefinitionInitialization = regionPropertyDefinitionMutationEvent + .regionPropertyDefinitionInitialization(); + validateregionPropertyDefinitionInitializationNotNull(regionPropertyDefinitionInitialization); + final RegionPropertyId regionPropertyId = regionPropertyDefinitionInitialization.getRegionPropertyId(); + final PropertyDefinition propertyDefinition = regionPropertyDefinitionInitialization.getPropertyDefinition(); + validateNewRegionPropertyId(regionPropertyId); + validateNewPropertyDefinition(propertyDefinition); + + final boolean checkAllRegionsHaveValues = propertyDefinition.getDefaultValue().isEmpty(); + + if (checkAllRegionsHaveValues) { + final Map coverageSet = new LinkedHashMap<>(); + for (final RegionId regionId : regionPopulationRecordMap.keySet()) { + coverageSet.put(regionId, false); + } + for (final Pair pair : regionPropertyDefinitionInitialization.getPropertyValues()) { + final RegionId regionId = pair.getFirst(); + coverageSet.put(regionId, true); + } + for (final RegionId regionId : coverageSet.keySet()) { + if (!coverageSet.get(regionId)) { + throw new ContractException(PropertyError.INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT); + } + } + } + + // validate each region property assignment belongs to a known region + for (final Pair pair : regionPropertyDefinitionInitialization.getPropertyValues()) { + validateRegionId(pair.getFirst()); + } + + if (checkAllRegionsHaveValues) { + addNonDefaultProperty(regionPropertyId); + } + + regionPropertyIds.add(regionPropertyId); + regionPropertyDefinitions.put(regionPropertyId, propertyDefinition); + + for (final Pair pair : regionPropertyDefinitionInitialization.getPropertyValues()) { + final RegionId regionId = pair.getFirst(); + + /* + * we do not have to validate the value since it is guaranteed to be consistent + * with the property definition by contract. + */ + final Object value = pair.getSecond(); + Map map = regionPropertyMap.get(regionId); + if (map == null) { + map = new LinkedHashMap<>(); + regionPropertyMap.put(regionId, map); + } + map.put(regionPropertyId, value); + } + + if (dataManagerContext.subscribersExist(RegionPropertyDefinitionEvent.class)) { + dataManagerContext.releaseObservationEvent(new RegionPropertyDefinitionEvent(regionPropertyId)); + } + + } + + /** + * Expands the capacity of data structures to hold people by the given count. + * Used to more efficiently prepare for multiple population additions. + * + * @throws ContractException {@linkplain PersonError#NEGATIVE_GROWTH_PROJECTION} + * if the count is negative + */ + public void expandCapacity(final int count) { + if (count < 0) { + throw new ContractException(PersonError.NEGATIVE_GROWTH_PROJECTION); + } + if (count > 0) { + regionValues.setCapacity(regionValues.getCapacity() + count); + if (regionArrivalTimes != null) { + regionArrivalTimes.setCapacity(regionArrivalTimes.getCapacity() + count); + } + } + } + + /** + * Returns as a List the person identifiers of the people in the given region. + * List elements are unique. + * + * @throws ContractException + *
      + *
    • {@linkplain RegionError#NULL_REGION_ID} if the + * c id is null
    • + *
    • {@linkplain RegionError#UNKNOWN_REGION_ID} if + * the region id is not known
    • + *
    + */ + public List getPeopleInRegion(final RegionId regionId) { + validateRegionId(regionId); + + final int targetRegionIndex = regionToIndexMap.get(regionId).intValue(); + + final List result = new ArrayList<>(); + + final int n = peopleDataManager.getPersonIdLimit(); + for (int personIndex = 0; personIndex < n; personIndex++) { + final int regionIndex = regionValues.getValueAsInt(personIndex); + /* + * a region index of zero will not match any valid region, indicating that + * person does not exist + */ + if (targetRegionIndex == regionIndex) { + final PersonId personId = peopleDataManager.getBoxedPersonId(personIndex).get(); + result.add(personId); + } + } + + return result; + } + + /** + * Returns the region associated with the given person id. + * + * @throws ContractException + *
      + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person id is unknown
    • + *
    + */ + @SuppressWarnings("unchecked") + public T getPersonRegion(final PersonId personId) { + validatePersonExists(personId); + final int r = regionValues.getValueAsInt(personId.getValue()); + return (T) indexToRegionMap.get(r); + } + + /** + * Returns the time when then person arrived at their current region. + * + * @throws ContractException + *
      + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person id is unknown
    • + *
    • {@linkplain RegionError#REGION_ARRIVAL_TIMES_NOT_TRACKED} + * if the region arrival times are not being + * tracked
    • + *
    + */ + public double getPersonRegionArrivalTime(final PersonId personId) { + validatePersonExists(personId); + validatePersonRegionArrivalsTimesTracked(); + return regionArrivalTimes.getValue(personId.getValue()); + } + + /** + * Returns the policy for tracking the last region arrival time for each person + */ + public boolean regionArrivalsAreTracked() { + return regionArrivalTimes != null; + } + + /** + * Returns the set of {@link RegionId} values that are defined by the + * {@link RegionsPluginData}. + */ + @SuppressWarnings("unchecked") + public Set getRegionIds() { + final Set result = new LinkedHashSet<>(regionPopulationRecordMap.size()); + for (final RegionId regionId : regionPopulationRecordMap.keySet()) { + result.add((T) regionId); + } + return result; + } + + /** + * Returns the number of people currently in the given region. + * + * @throws ContractException + *
      + *
    • {@linkplain RegionError#NULL_REGION_ID} if the + * region id is null
    • + *
    • {@linkplain RegionError#UNKNOWN_REGION_ID} if + * the region id is not known
    • + *
    + */ + public int getRegionPopulationCount(final RegionId regionId) { + validateRegionId(regionId); + return regionPopulationRecordMap.get(regionId).getValue(); + } + + /** + * Returns the property definition for the given {@link RegionPropertyId} + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the region property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the region property id is unknown
    • + *
    + */ + public PropertyDefinition getRegionPropertyDefinition(final RegionPropertyId regionPropertyId) { + validateRegionPropertyId(regionPropertyId); + return regionPropertyDefinitions.get(regionPropertyId); + } + + /** + * Returns the {@link RegionPropertyId} values + */ + @SuppressWarnings("unchecked") + public Set getRegionPropertyIds() { + final Set result = new LinkedHashSet<>(regionPropertyDefinitions.keySet().size()); + for (final RegionPropertyId regionPropertyId : regionPropertyDefinitions.keySet()) { + result.add((T) regionPropertyId); + } + return result; + } + + /** + * Returns the value of the region property. + * + * @throws ContractException + *
      + *
    • {@linkplain RegionError#NULL_REGION_ID} if the + * region id is null
    • + *
    • {@linkplain RegionError#UNKNOWN_REGION_ID} if + * the region id is not known
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the region property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the region property id is unknown
    • + *
    + */ + @SuppressWarnings("unchecked") + public T getRegionPropertyValue(final RegionId regionId, final RegionPropertyId regionPropertyId) { + validateRegionId(regionId); + validateRegionPropertyId(regionPropertyId); + Object propertyValue = null; + Map map = regionPropertyMap.get(regionId); + if (map != null) { + propertyValue = map.get(regionPropertyId); + } + if (propertyValue != null) { + return (T) propertyValue; + } + /* + * If the value is missing, then we are guaranteed that the property definition + * will have a default value. + */ + PropertyDefinition propertyDefinition = regionPropertyDefinitions.get(regionPropertyId); + return (T) propertyDefinition.getDefaultValue().get(); + } + + private void handlePersonImminentAdditionEvent(final DataManagerContext dataManagerContext, + final PersonImminentAdditionEvent personImminentAdditionEvent) { + final PersonConstructionData personConstructionData = personImminentAdditionEvent.personConstructionData(); + final RegionId regionId = personConstructionData.getValue(RegionId.class).orElse(null); + validateRegionId(regionId); + final PersonId personId = personImminentAdditionEvent.personId(); + + validatePersonExists(personId); + validateRegionId(regionId); + validatePersonNotContained(personId); + + /* + * Update the population count of the new region + */ + regionPopulationRecordMap.get(regionId).increment(); + + final Integer regionIndex = regionToIndexMap.get(regionId).intValue(); + regionValues.setIntValue(personId.getValue(), regionIndex); + + if (regionArrivalTimes != null) { + regionArrivalTimes.setValue(personId.getValue(), dataManagerContext.getTime()); + } + } + + /** + * Removes the person from this data manager. Precondition : the person must + * exist and be stored in this manager + * + * @throws ContractException + *
      + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person id is unknown
    • + *
    + */ + private void handlePersonRemovalEvent(final DataManagerContext dataManagerContext, + final PersonRemovalEvent personRemovalEvent) { + final PersonId personId = personRemovalEvent.personId(); + validatePersonContained(personId); + final int regionIndex = regionValues.getValueAsInt(personId.getValue()); + final RegionId oldRegionId = indexToRegionMap.get(regionIndex); + regionPopulationRecordMap.get(oldRegionId).decrement(); + regionValues.setIntValue(personId.getValue(), 0); + } + + /** + * Initializes the state of regions related data from the RegionsPluginData. + * + * @throws ContractException + *
      + *
    • {@linkplain RegionError#UNKNOWN_REGION_ID} if a + * person in the people plugin does not have an + * assigned region id in the region plugin data
    • + *
    • {@linkplain RegionError#REGION_ARRIVAL_TIME_EXCEEDS_SIM_TIME} + * if a person's region arrival time exceeds the + * current simulation time
    • + *
    • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the regions plugin data contains information for an + * unknown person id
    • + *
    + */ + @Override + public void init(final DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + + this.dataManagerContext = dataManagerContext; + + peopleDataManager = dataManagerContext.getDataManager(PeopleDataManager.class); + + /* + * By setting the default value to 0, we are allowing the container to grow + * without having to set values in its array. HOWEVER, THIS IMPLIES THAT REGIONS + * MUST BE CONVERTED TO INTEGER VALUES STARTING AT ONE, NOT ZERO. + * + * + */ + regionValues = new IntValueContainer(0, peopleDataManager::getPersonIndexIterator); + + boolean trackRegionArrivals = regionsPluginData.getPersonRegionArrivalTrackingPolicy(); + if (trackRegionArrivals) { + regionArrivalTimes = new DoubleValueContainer(0, peopleDataManager::getPersonIndexIterator); + } + + final Set regionIds = regionsPluginData.getRegionIds(); + for (final RegionId regionId : regionIds) { + regionPopulationRecordMap.put(regionId, new MutableInteger()); + } + + int index = 1; + for (final RegionId regionId : regionIds) { + regionToIndexMap.put(regionId, index++); + } + + indexToRegionMap.add(null); + indexToRegionMap.addAll(regionIds); + + for (final RegionPropertyId regionPropertyId : regionsPluginData.getRegionPropertyIds()) { + final PropertyDefinition propertyDefinition = regionsPluginData + .getRegionPropertyDefinition(regionPropertyId); + regionPropertyIds.add(regionPropertyId); + regionPropertyDefinitions.put(regionPropertyId, propertyDefinition); + if (propertyDefinition.getDefaultValue().isEmpty()) { + nonDefaultBearingPropertyIds.put(regionPropertyId, nonDefaultBearingPropertyIds.size()); + } + } + nonDefaultChecks = new boolean[nonDefaultBearingPropertyIds.size()]; + + regionPropertyMap = regionsPluginData.getRegionPropertyValues(); + + // for (final RegionId regionId : regionIds) { + // final Map map = new LinkedHashMap<>(); + // regionPropertyMap.put(regionId, map); + // Map regionPropertyValues = + // regionsPluginData.getRegionPropertyValues(regionId); + // for (final RegionPropertyId regionPropertyId : regionPropertyValues.keySet()) + // { + // final Object regionPropertyValue = + // regionPropertyValues.get(regionPropertyId); + // map.put(regionPropertyId, regionPropertyValue); + // } + // } + + double currentTime = dataManagerContext.getTime(); + final List people = peopleDataManager.getPeople(); + for (final PersonId personId : people) { + final Optional optional = regionsPluginData.getPersonRegion(personId); + if (optional.isEmpty()) { + throw new ContractException(RegionError.UNKNOWN_REGION_ID); + } + final RegionId regionId = optional.get(); + regionPopulationRecordMap.get(regionId).increment(); + final Integer regionIndex = regionToIndexMap.get(regionId).intValue(); + regionValues.setIntValue(personId.getValue(), regionIndex); + + if (regionArrivalTimes != null) { + Optional optionalTime = regionsPluginData.getPersonRegionArrivalTime(personId); + // we know that since the person exists, the regionsPluginData + // guarantees that the arrival time will be present + double arrivalTime = optionalTime.get(); + if (arrivalTime > currentTime) { + throw new ContractException(RegionError.REGION_ARRIVAL_TIME_EXCEEDS_SIM_TIME); + } + + regionArrivalTimes.setValue(personId.getValue(), arrivalTime); + } + } + + int n = regionsPluginData.getPersonCount(); + for (int i = 0; i < n; i++) { + if (!peopleDataManager.personIndexExists(i)) { + Optional optional = regionsPluginData.getPersonRegion(new PersonId(i)); + if (optional.isPresent()) { + throw new ContractException(PersonError.UNKNOWN_PERSON_ID); + } + } + } + + dataManagerContext.subscribe(PersonImminentAdditionEvent.class, this::handlePersonImminentAdditionEvent); + dataManagerContext.subscribe(PersonRemovalEvent.class, this::handlePersonRemovalEvent); + dataManagerContext.subscribe(RegionAdditionMutationEvent.class, this::handleRegionAdditionMutationEvent); + dataManagerContext.subscribe(RegionPropertyDefinitionMutationEvent.class, + this::handleRegionPropertyDefinitionMutationEvent); + dataManagerContext.subscribe(PersonRegionUpdateMutationEvent.class, + this::handlePersonRegionUpdateMutationEvent); + dataManagerContext.subscribe(RegionPropertyUpdateMutationEvent.class, + this::handleRegionPropertyUpdateMutationEvent); + + if (dataManagerContext.stateRecordingIsScheduled()) { + dataManagerContext.subscribeToSimulationClose(this::recordSimulationState); + } + } + + private void recordSimulationState(DataManagerContext dataManagerContext) { + + RegionsPluginData.Builder builder = RegionsPluginData.builder(); + + Set regionIds = getRegionIds(); + for (RegionId regionId : regionIds) { + builder.addRegion(regionId); + } + + for (RegionPropertyId regionPropertyId : regionPropertyDefinitions.keySet()) { + PropertyDefinition propertyDefinition = regionPropertyDefinitions.get(regionPropertyId); + builder.defineRegionProperty(regionPropertyId, propertyDefinition); + } + + for (RegionId regionId : regionPropertyMap.keySet()) { + Map map = regionPropertyMap.get(regionId); + for (RegionPropertyId regionPropertyId : map.keySet()) { + Object value = map.get(regionPropertyId); + builder.setRegionPropertyValue(regionId, regionPropertyId, value); + } + } + + List people = peopleDataManager.getPeople(); + + if (regionArrivalTimes != null) { + builder.setPersonRegionArrivalTracking(true); + for (PersonId personId : people) { + RegionId regionId = getPersonRegion(personId); + double arrivalTime = regionArrivalTimes.getValue(personId.getValue()); + builder.addPerson(personId, regionId, arrivalTime); + } + } else { + builder.setPersonRegionArrivalTracking(false); + for (PersonId personId : people) { + RegionId regionId = getPersonRegion(personId); + builder.addPerson(personId, regionId); + } + } + + dataManagerContext.releaseOutput(builder.build()); + } + + /** + * Return true if and only if the given {@link RegionId} exits. Null tolerant. + */ + public boolean regionIdExists(final RegionId regionId) { + return regionPopulationRecordMap.containsKey(regionId); + } + + /** + * Returns true if and only if the given {@link RegionPropertyId} exists. + * Tolerates nulls. + */ + public boolean regionPropertyIdExists(final RegionPropertyId regionPropertyId) { + return regionPropertyDefinitions.containsKey(regionPropertyId); + } + + private static record PersonRegionUpdateMutationEvent(PersonId personId, RegionId regionId) implements Event { + } + + /** + * Updates the person's current region and region arrival time. Generates a + * corresponding {@linkplain PersonRegionUpdateEvent} + * + * @throws ContractException + *
      + *
    • {@link PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@link PersonError#UNKNOWN_PERSON_ID} if the + * person id is unknown
    • + *
    • {@link RegionError#NULL_REGION_ID} if the + * region id is null
    • + *
    • {@link RegionError#UNKNOWN_REGION_ID} if the + * region id is unknown
    • + *
    + */ + public void setPersonRegion(final PersonId personId, final RegionId regionId) { + dataManagerContext.releaseMutationEvent(new PersonRegionUpdateMutationEvent(personId, regionId)); + } + + private void handlePersonRegionUpdateMutationEvent(DataManagerContext dataManagerContext, + PersonRegionUpdateMutationEvent personRegionUpdateMutationEvent) { + PersonId personId = personRegionUpdateMutationEvent.personId(); + RegionId regionId = personRegionUpdateMutationEvent.regionId(); + validatePersonExists(personId); + validateRegionId(regionId); + + /* + * Retrieve the int value that represents the current region of the person + */ + int regionIndex = regionValues.getValueAsInt(personId.getValue()); + final RegionId oldRegionId = indexToRegionMap.get(regionIndex); + /* + * Update the population count associated with the old region + */ + regionPopulationRecordMap.get(oldRegionId).decrement(); + + /* + * Update the population count of the new region + */ + regionPopulationRecordMap.get(regionId).increment(); + + /* + * Convert the new region id into an int + */ + regionIndex = regionToIndexMap.get(regionId).intValue(); + /* + * Store in the int at the person's index + */ + regionValues.setIntValue(personId.getValue(), regionIndex); + /* + * If region arrival times are being tracked, do so. + */ + if (regionArrivalTimes != null) { + regionArrivalTimes.setValue(personId.getValue(), dataManagerContext.getTime()); + } + + if (dataManagerContext.subscribersExist(PersonRegionUpdateEvent.class)) { + dataManagerContext.releaseObservationEvent(new PersonRegionUpdateEvent(personId, oldRegionId, regionId)); + } + + } + + private static record RegionPropertyUpdateMutationEvent(RegionId regionId, RegionPropertyId regionPropertyId, + Object regionPropertyValue) implements Event { + } + + /** + * Updates the region's property value and time. Generates a corresponding + * {@linkplain RegionPropertyUpdateEvent} + * + * @throws ContractException + *
      + *
    • {@link RegionError#NULL_REGION_ID} if the + * region id is null
    • + *
    • {@link RegionError#UNKNOWN_REGION_ID} if the + * region id is unknown
    • + *
    • {@link PropertyError#NULL_PROPERTY_ID} if the + * property id is null
    • + *
    • {@link PropertyError#UNKNOWN_PROPERTY_ID} if + * the property id is unknown
    • + *
    • {@link PropertyError#NULL_PROPERTY_VALUE} if + * the value is null
    • + *
    • {@link PropertyError#INCOMPATIBLE_VALUE} if the + * value is incompatible with the defined type for the + * property
    • + *
    • {@link PropertyError#IMMUTABLE_VALUE} if the + * property has been defined as immutable
    • + *
    + */ + public void setRegionPropertyValue(final RegionId regionId, final RegionPropertyId regionPropertyId, + final Object regionPropertyValue) { + dataManagerContext.releaseMutationEvent( + new RegionPropertyUpdateMutationEvent(regionId, regionPropertyId, regionPropertyValue)); + } + + private void handleRegionPropertyUpdateMutationEvent(DataManagerContext dataManagerContext, + RegionPropertyUpdateMutationEvent regionPropertyUpdateMutationEvent) { + RegionId regionId = regionPropertyUpdateMutationEvent.regionId(); + RegionPropertyId regionPropertyId = regionPropertyUpdateMutationEvent.regionPropertyId(); + Object regionPropertyValue = regionPropertyUpdateMutationEvent.regionPropertyValue(); + + validateRegionId(regionId); + validateRegionPropertyId(regionPropertyId); + validateRegionPropertyValueNotNull(regionPropertyValue); + final PropertyDefinition propertyDefinition = getRegionPropertyDefinition(regionPropertyId); + validateValueCompatibility(regionPropertyId, propertyDefinition, regionPropertyValue); + validatePropertyMutability(propertyDefinition); + Map map = regionPropertyMap.get(regionId); + if (map == null) { + map = new LinkedHashMap<>(); + regionPropertyMap.put(regionId, map); + } + + if (dataManagerContext.subscribersExist(RegionPropertyUpdateEvent.class)) { + Object previousPropertyValue = map.get(regionPropertyId); + if (previousPropertyValue == null) { + previousPropertyValue = regionPropertyDefinitions.get(regionPropertyId).getDefaultValue().get(); + } + + map.put(regionPropertyId, regionPropertyValue); + + dataManagerContext.releaseObservationEvent(new RegionPropertyUpdateEvent(regionId, regionPropertyId, + previousPropertyValue, regionPropertyValue)); + } else { + map.put(regionPropertyId, regionPropertyValue); + + } + + } + + private void validateNewPropertyDefinition(final PropertyDefinition propertyDefinition) { + if (propertyDefinition == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_DEFINITION); + } + } + + private void validateNewRegionId(final RegionId regionId) { + + if (regionId == null) { + throw new ContractException(RegionError.NULL_REGION_ID); + } + + if (regionIdExists(regionId)) { + throw new ContractException(RegionError.DUPLICATE_REGION_ID, regionId); + } + } + + private void validateNewRegionPropertyId(final RegionPropertyId regionPropertyId) { + if (regionPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + if (regionPropertyIdExists(regionPropertyId)) { + throw new ContractException(PropertyError.DUPLICATE_PROPERTY_DEFINITION, regionPropertyId); + } + } + + private void validatePersonContained(final PersonId personId) { + + final int regionIndex = regionValues.getValueAsInt(personId.getValue()); + if (regionIndex == 0) { + throw new ContractException(PersonError.UNKNOWN_PERSON_ID); + } + } + + private void validatePersonExists(final PersonId personId) { + if (personId == null) { + throw new ContractException(PersonError.NULL_PERSON_ID); + } + + if (!peopleDataManager.personExists(personId)) { + throw new ContractException(PersonError.UNKNOWN_PERSON_ID); + } + } + + private void validatePersonNotContained(final PersonId personId) { + + final int regionIndex = regionValues.getValueAsInt(personId.getValue()); + if (regionIndex != 0) { + throw new ContractException(RegionError.DUPLICATE_PERSON_ADDITION); + } + } + + private void validatePersonRegionArrivalsTimesTracked() { + if (regionArrivalTimes == null) { + throw new ContractException(RegionError.REGION_ARRIVAL_TIMES_NOT_TRACKED); + } + } + + private void validatePropertyMutability(final PropertyDefinition propertyDefinition) { + if (!propertyDefinition.propertyValuesAreMutable()) { + throw new ContractException(PropertyError.IMMUTABLE_VALUE); + } + } + + private void validateRegionId(final RegionId regionId) { + + if (regionId == null) { + throw new ContractException(RegionError.NULL_REGION_ID); + } + + if (!regionIdExists(regionId)) { + throw new ContractException(RegionError.UNKNOWN_REGION_ID, regionId); + } + } + + private void validateregionPropertyDefinitionInitializationNotNull( + final RegionPropertyDefinitionInitialization regionPropertyDefinitionInitialization) { + if (regionPropertyDefinitionInitialization == null) { + throw new ContractException(RegionError.NULL_REGION_PROPERTY_DEFINITION_INITIALIZATION); + } + } + + private void validateRegionPropertyId(final RegionPropertyId regionPropertyId) { + if (regionPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + if (!regionPropertyIdExists(regionPropertyId)) { + throw new ContractException(PropertyError.UNKNOWN_PROPERTY_ID, regionPropertyId); + } + } + + private void validateRegionPropertyValueNotNull(final Object propertyValue) { + if (propertyValue == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_VALUE); + } + } + + private void validateValueCompatibility(final Object propertyId, final PropertyDefinition propertyDefinition, + final Object propertyValue) { + if (!propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())) { + throw new ContractException(PropertyError.INCOMPATIBLE_VALUE, + "Property value " + propertyValue + " is not of type " + propertyDefinition.getType().getName() + + " and does not match definition of " + propertyId); + } + } + + private static enum PersonRegionUpdateFunctionId { + ARRIVAL, DEPARTURE, PERSON; + + } + + private IdentifiableFunctionMap personRegionUpdateFunctionMap = // + IdentifiableFunctionMap.builder(PersonRegionUpdateEvent.class)// + .put(PersonRegionUpdateFunctionId.ARRIVAL, e -> e.currentRegionId())// + .put(PersonRegionUpdateFunctionId.DEPARTURE, e -> e.previousRegionId())// + .put(PersonRegionUpdateFunctionId.PERSON, e -> e.personId())// + .build();// + + /** + * Returns an event filter used to subscribe to {@link PersonRegionUpdateEvent} + * events. Matches on the arrival region id. + * + * @throws ContractException + *
      + *
    • {@linkplain RegionError#NULL_REGION_ID} if the + * region id is null
    • + *
    • {@linkplain RegionError#UNKNOWN_REGION_ID} if + * the region id is not known
    • + *
    + */ + public EventFilter getEventFilterForPersonRegionUpdateEvent_ByArrivalRegion( + RegionId arrivalRegionId) { + validateRegionId(arrivalRegionId); + return EventFilter.builder(PersonRegionUpdateEvent.class)// + .addFunctionValuePair(personRegionUpdateFunctionMap.get(PersonRegionUpdateFunctionId.ARRIVAL), + arrivalRegionId)// + .build(); + } + + /** + * Returns an event filter used to subscribe to {@link PersonRegionUpdateEvent} + * events. Matches on the departure region id. + * + * @throws ContractException + *
      + *
    • {@linkplain RegionError#NULL_REGION_ID} if the + * region id is null
    • + *
    • {@linkplain RegionError#UNKNOWN_REGION_ID} if + * the region id is not known
    • + *
    + */ + public EventFilter getEventFilterForPersonRegionUpdateEvent_ByDepartureRegion( + RegionId departureRegionId) { + validateRegionId(departureRegionId); + return EventFilter.builder(PersonRegionUpdateEvent.class)// + .addFunctionValuePair(personRegionUpdateFunctionMap.get(PersonRegionUpdateFunctionId.DEPARTURE), + departureRegionId)// + .build(); + } + + /** + * Returns an event filter used to subscribe to {@link PersonRegionUpdateEvent} + * events. Matches on the person id. + * + * @throws ContractException + *
      + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person id is not known
    • + *
    + */ + public EventFilter getEventFilterForPersonRegionUpdateEvent(PersonId personId) { + validatePersonExists(personId); + return EventFilter.builder(PersonRegionUpdateEvent.class)// + .addFunctionValuePair(personRegionUpdateFunctionMap.get(PersonRegionUpdateFunctionId.PERSON), personId)// + .build(); + + } + + /** + * Returns an event filter used to subscribe to {@link PersonRegionUpdateEvent} + * events. Matches on all such events. + */ + public EventFilter getEventFilterForPersonRegionUpdateEvent() { + return EventFilter.builder(PersonRegionUpdateEvent.class)// + .build(); + } + + private static enum RegionPropertyUpdateEventFunctionId { + PROPERTY, REGION; + } + + private IdentifiableFunctionMap regionPropertyUpdateFunctionMap = // + IdentifiableFunctionMap.builder(RegionPropertyUpdateEvent.class)// + .put(RegionPropertyUpdateEventFunctionId.PROPERTY, e -> e.regionPropertyId())// + .put(RegionPropertyUpdateEventFunctionId.REGION, e -> e.regionId())// + .build();// + + /** + * Returns an event filter used to subscribe to + * {@link RegionPropertyUpdateEvent} events. Matches on the region id. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the region property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the region property id is not known
    • + *
    + */ + public EventFilter getEventFilterForRegionPropertyUpdateEvent( + RegionPropertyId regionPropertyId) { + validateRegionPropertyId(regionPropertyId); + return EventFilter.builder(RegionPropertyUpdateEvent.class)// + .addFunctionValuePair(regionPropertyUpdateFunctionMap.get(RegionPropertyUpdateEventFunctionId.PROPERTY), + regionPropertyId)// + .build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link RegionPropertyUpdateEvent} events. Matches on the region property id + * and region id. + * + * @throws ContractException + *
      + *
    • {@linkplain RegionError#NULL_REGION_ID} if the + * region id is null
    • + *
    • {@linkplain RegionError#UNKNOWN_REGION_ID} if + * the region id is not known
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the region property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the region property id is not known
    • + *
    + */ + public EventFilter getEventFilterForRegionPropertyUpdateEvent(RegionId regionId, + RegionPropertyId regionPropertyId) { + validateRegionId(regionId); + validateRegionPropertyId(regionPropertyId); + return EventFilter.builder(RegionPropertyUpdateEvent.class)// + .addFunctionValuePair(regionPropertyUpdateFunctionMap.get(RegionPropertyUpdateEventFunctionId.PROPERTY), + regionPropertyId)// + .addFunctionValuePair(regionPropertyUpdateFunctionMap.get(RegionPropertyUpdateEventFunctionId.REGION), + regionId)// + .build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link RegionPropertyUpdateEvent} events. Matches all such events. + */ + public EventFilter getEventFilterForRegionPropertyUpdateEvent() { + return EventFilter.builder(RegionPropertyUpdateEvent.class)// + .build(); + } + + /** + * Returns an event filter used to subscribe to {@link RegionAdditionEvent} + * events. Matches all such events. + */ + public EventFilter getEventFilterForRegionAdditionEvent() { + return EventFilter.builder(RegionAdditionEvent.class)// + .build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link RegionPropertyDefinitionEvent} events. Matches all such events. + */ + public EventFilter getEventFilterForRegionPropertyDefinitionEvent() { + return EventFilter.builder(RegionPropertyDefinitionEvent.class)// + .build(); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("RegionsDataManager [regionPropertyMap="); + builder.append(regionPropertyMap); + builder.append(", regionPropertyIds="); + builder.append(regionPropertyIds); + builder.append(", regionPropertyDefinitions="); + builder.append(regionPropertyDefinitions); + builder.append(", regionPopulationRecordMap="); + builder.append(regionPopulationRecordMap); + builder.append(", regionToIndexMap="); + builder.append(regionToIndexMap); + builder.append(", indexToRegionMap="); + builder.append(indexToRegionMap); + builder.append(", regionValues="); + builder.append(regionValues); + builder.append(", regionArrivalTimes="); + builder.append(regionArrivalTimes); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/datamanagers/RegionsPluginData.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/datamanagers/RegionsPluginData.java new file mode 100644 index 000000000..01716e648 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/datamanagers/RegionsPluginData.java @@ -0,0 +1,675 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import org.apache.commons.math3.util.FastMath; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.PluginDataBuilder; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionError; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * An immutable container of the initial state of regions. It contains:
    + *
      + *
    • region ids
    • + *
    • suppliers of consumers of {@linkplain ActorContext} for region + * initialization
    • + *
    • region property definitions: all regions share a set of property + * definitions with default values, but have individual property values
    • + *
    • region property values
    • + *
    • person region assignments
    • + *
    • person region arrival time tracking policy
    • + *
    + */ +@Immutable +public class RegionsPluginData implements PluginData { + + private static enum PersonAdditionMode { + UNDETERMINED, REGION_ONLY, REGION_AND_TIME + } + + private static class Data { + + private final Map regionPropertyDefinitions = new LinkedHashMap<>(); + + private final Set regionIds = new LinkedHashSet<>(); + + private boolean trackRegionArrivalTimes; + + private Map> regionPropertyValues = new LinkedHashMap<>(); + + private final Map emptyRegionPropertyMap = Collections + .unmodifiableMap(new LinkedHashMap<>()); + + private final List personRegions = new ArrayList<>(); + + private final List personArrivalTimes = new ArrayList<>(); + + private PersonAdditionMode personAdditionMode = PersonAdditionMode.UNDETERMINED; + + private boolean locked; + + public Data() { + } + + public Data(Data data) { + regionPropertyDefinitions.putAll(data.regionPropertyDefinitions); + regionIds.addAll(data.regionIds); + trackRegionArrivalTimes = data.trackRegionArrivalTimes; + for (RegionId regionId : data.regionPropertyValues.keySet()) { + Map map = new LinkedHashMap<>(data.regionPropertyValues.get(regionId)); + regionPropertyValues.put(regionId, map); + } + personRegions.addAll(data.personRegions); + personAdditionMode = data.personAdditionMode; + + locked = data.locked; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (locked ? 1231 : 1237); + result = prime * result + personRegions.hashCode(); + result = prime * result + personArrivalTimes.hashCode(); + + result = prime * result + regionIds.hashCode(); + result = prime * result + regionPropertyDefinitions.hashCode(); + result = prime * result + regionPropertyValues.hashCode(); + + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + + /* + * We exclude the following fields: + * + * locked -- two Datas are only compared when they are both locked -- there are + * no equality comparisons in this class. + * + * personAdditionMode -- is a function of the existing data or is in the + * UNDETERMINED state + * + * emptyRegionPropertyMap -- just an empty map + */ + if (trackRegionArrivalTimes != other.trackRegionArrivalTimes) { + return false; + } + if (!personRegions.equals(other.personRegions)) { + return false; + } + if (!personRegions.equals(other.personRegions)) { + return false; + } + if (!personArrivalTimes.equals(other.personArrivalTimes)) { + return false; + } + if (!regionIds.equals(other.regionIds)) { + return false; + } + if (!regionPropertyDefinitions.equals(other.regionPropertyDefinitions)) { + return false; + } + + if (!regionPropertyValues.equals(other.regionPropertyValues)) { + return false; + } + + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Data [regionPropertyDefinitions="); + builder.append(regionPropertyDefinitions); + builder.append(", regionIds="); + builder.append(regionIds); + builder.append(", trackRegionArrivalTimes="); + builder.append(trackRegionArrivalTimes); + builder.append(", regionPropertyValues="); + builder.append(regionPropertyValues); + builder.append(", personRegions="); + builder.append(personRegions); + builder.append(", personArrivalTimes="); + builder.append(personArrivalTimes); + builder.append(", locked="); + builder.append(locked); + builder.append("]"); + return builder.toString(); + } + + } + + private static void validateRegionExists(final Data data, final RegionId regionId) { + if (regionId == null) { + throw new ContractException(RegionError.NULL_REGION_ID); + } + if (!data.regionIds.contains(regionId)) { + throw new ContractException(RegionError.UNKNOWN_REGION_ID, regionId); + } + } + + public static Builder builder() { + return new Builder(new Data()); + } + + private static void validateRegionIdNotNull(RegionId regionId) { + if (regionId == null) { + throw new ContractException(RegionError.NULL_REGION_ID); + } + } + + private static void validateRegionPropertyIdNotNull(RegionPropertyId regionPropertyId) { + if (regionPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + } + + private static void validateRegionPropertyDefinitionNotNull(PropertyDefinition propertyDefinition) { + if (propertyDefinition == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_DEFINITION); + } + + } + + private static void validateRegionPropertyIsDefined(Data data, RegionPropertyId regionPropertyId) { + + if (!data.regionPropertyDefinitions.containsKey(regionPropertyId)) { + throw new ContractException(PropertyError.UNKNOWN_PROPERTY_ID); + } + } + + public static class Builder implements PluginDataBuilder { + private Data data; + + private void ensureDataMutability() { + if (data.locked) { + data = new Data(data); + data.locked = false; + } + } + + private void ensureImmutability() { + if (!data.locked) { + data.locked = true; + } + } + + private Builder(Data data) { + this.data = data; + } + + /** + * Returns the {@link RegionsPluginData} from the collected information supplied + * to this builder. + * + * @throws ContractException + *
      + *
    • {@linkplain RegionError#UNKNOWN_REGION_ID} if a + * region property value was associated with a region + * id that was not properly added with an initial + * agent behavior.
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if a region property value was associated with a + * region property id that was not defined
    • + *
    • {@linkplain PropertyError#INCOMPATIBLE_VALUE} + * if a region property value was associated with a + * region and region property id that is incompatible + * with the corresponding property definition.
    • + *
    • {@linkplain PropertyError#INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT} + * if a region property definition does not have a + * default value and there are no property values + * added to replace that default.
    • + *
    • {@linkplain RegionError#PERSON_ARRIVAL_DATA_PRESENT} + * if a person region arrival data was collected, but + * the region arrival tracking policy is true
    • + *
    • {@linkplain RegionError#MISSING_PERSON_ARRIVAL_DATA} + * if person region arrival data was collected, but + * the region arrival time tracking policy is + * false
    • + *
    + */ + public RegionsPluginData build() { + if (!data.locked) { + validateData(); + } + ensureImmutability(); + return new RegionsPluginData(data); + } + + /** + * Sets the region property value that overrides the default value of the + * corresponding property definition + * + * @throws ContractException + *
      + *
    • {@linkplain RegionError#NULL_REGION_ID} if the + * region id is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the region property id is null
    • + *
    + */ + public Builder setRegionPropertyValue(final RegionId regionId, final RegionPropertyId regionPropertyId, + final Object regionPropertyValue) { + ensureDataMutability(); + validateRegionIdNotNull(regionId); + validateRegionPropertyIdNotNull(regionPropertyId); + Map propertyMap = data.regionPropertyValues.get(regionId); + if (propertyMap == null) { + propertyMap = new LinkedHashMap<>(); + data.regionPropertyValues.put(regionId, propertyMap); + } + propertyMap.put(regionPropertyId, regionPropertyValue); + return this; + } + + private void setPersonAdditionMode(PersonAdditionMode personAdditionMode) { + if (personAdditionMode == PersonAdditionMode.UNDETERMINED) { + throw new RuntimeException("person addition mode cannot be set to " + PersonAdditionMode.UNDETERMINED); + } + switch (data.personAdditionMode) { + case REGION_AND_TIME: + case REGION_ONLY: + if (personAdditionMode != data.personAdditionMode) { + throw new ContractException(RegionError.REGION_ARRIVAL_TIMES_MISMATCHED); + } + break; + case UNDETERMINED: + data.personAdditionMode = personAdditionMode; + break; + default: + throw new RuntimeException("unhandled case " + data.personAdditionMode); + } + } + + /** + * Sets the person's region. Should be used exclusively when time tracking will + * be set to false. + * + * @throws ContractException + *
      + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain RegionError#NULL_REGION_ID} if the + * region id is null
    • + *
    • {@linkplain RegionError#REGION_ARRIVAL_TIMES_MISMATCHED} + * if other people have been added using region + * arrival times
    • + *
    + */ + public Builder addPerson(final PersonId personId, final RegionId regionId) { + ensureDataMutability(); + validatePersonId(personId); + validateRegionIdNotNull(regionId); + setPersonAdditionMode(PersonAdditionMode.REGION_ONLY); + int personIndex = personId.getValue(); + while (personIndex >= data.personRegions.size()) { + data.personRegions.add(null); + } + data.personRegions.set(personIndex, regionId); + return this; + } + + /** + * Sets the person's region and region arrival time. Should be used exclusively + * when time tracking will be set to true. + * + * @throws ContractException + *
      + *
    • {@linkplain PersonError#NULL_PERSON_ID}if the + * person id is null
    • + *
    • {@linkplain RegionError#NON_FINITE_TIME}if the + * arrival time is not finite
    • + *
    • {@linkplain RegionError#NULL_TIME}if the + * arrival time is null
    • + *
    • {@linkplain RegionError#REGION_ARRIVAL_TIMES_MISMATCHED} + * if other people have been added without using + * region arrival times
    • + *
    + */ + public Builder addPerson(final PersonId personId, final RegionId regionId, final Double arrivalTime) { + ensureDataMutability(); + validatePersonId(personId); + validateRegionIdNotNull(regionId); + validateTime(arrivalTime); + setPersonAdditionMode(PersonAdditionMode.REGION_AND_TIME); + int personIndex = personId.getValue(); + while (personIndex >= data.personRegions.size()) { + data.personRegions.add(null); + data.personArrivalTimes.add(null); + } + data.personRegions.set(personIndex, regionId); + data.personArrivalTimes.set(personIndex, arrivalTime); + return this; + } + + /** + * Sets the tracking policy for region arrival times. Defaults to false. Must be + * set to true if people are added with arrival times. + * + * @throws ContractException + */ + public Builder setPersonRegionArrivalTracking(boolean trackRegionArrivalTimes) { + ensureDataMutability(); + data.trackRegionArrivalTimes = trackRegionArrivalTimes; + return this; + } + + /** + * Adds the region id and its associated agent initial behavior. + * + * @throws ContractException {@linkplain RegionError#NULL_REGION_ID} if the + * region id is null + */ + public Builder addRegion(final RegionId regionId) { + ensureDataMutability(); + validateRegionIdNotNull(regionId); + data.regionIds.add(regionId); + return this; + } + + /** + * Defines a region property + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the region property id is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_DEFINITION} + * if the property definition is null
    • + *
    + */ + public Builder defineRegionProperty(final RegionPropertyId regionPropertyId, + final PropertyDefinition propertyDefinition) { + ensureDataMutability(); + validateRegionPropertyIdNotNull(regionPropertyId); + validateRegionPropertyDefinitionNotNull(propertyDefinition); + data.regionPropertyDefinitions.put(regionPropertyId, propertyDefinition); + return this; + } + + private void validateData() { + + // if the time tracking policy is off, then there should be no times + // recorded + + switch (data.personAdditionMode) { + case REGION_AND_TIME: + if (!data.trackRegionArrivalTimes) { + throw new ContractException(RegionError.PERSON_ARRIVAL_DATA_PRESENT); + } + break; + case REGION_ONLY: + if (data.trackRegionArrivalTimes) { + throw new ContractException(RegionError.MISSING_PERSON_ARRIVAL_DATA); + } + break; + case UNDETERMINED: + // nothing to check + break; + default: + throw new RuntimeException("unhandled case " + data.personAdditionMode); + } + + for (RegionId regionId : data.personRegions) { + if (regionId != null) { + if (!data.regionIds.contains(regionId)) { + throw new ContractException(RegionError.UNKNOWN_REGION_ID, + regionId + " in person region assignments"); + } + } + } + + for (RegionId regionId : data.regionPropertyValues.keySet()) { + if (!data.regionIds.contains(regionId)) { + throw new ContractException(RegionError.UNKNOWN_REGION_ID, regionId + " in region property values"); + } + Map map = data.regionPropertyValues.get(regionId); + if (map != null) { + for (RegionPropertyId regionPropertyId : map.keySet()) { + PropertyDefinition propertyDefinition = data.regionPropertyDefinitions.get(regionPropertyId); + if (propertyDefinition == null) { + throw new ContractException(PropertyError.UNKNOWN_PROPERTY_ID, + regionPropertyId + " for region " + regionId); + } + Object propertyValue = map.get(regionPropertyId); + if (!propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())) { + throw new ContractException(PropertyError.INCOMPATIBLE_VALUE, + regionId + ":" + regionPropertyId + " = " + propertyValue); + } + } + } + } + + /* + * For every region property definition that has a null default value, ensure + * that there all corresponding region property values are not null. + */ + for (RegionPropertyId regionPropertyId : data.regionPropertyDefinitions.keySet()) { + PropertyDefinition propertyDefinition = data.regionPropertyDefinitions.get(regionPropertyId); + if (!propertyDefinition.getDefaultValue().isPresent()) { + for (RegionId regionId : data.regionIds) { + Object propertyValue = null; + Map propertyValueMap = data.regionPropertyValues.get(regionId); + if (propertyValueMap != null) { + propertyValue = propertyValueMap.get(regionPropertyId); + } + if (propertyValue == null) { + throw new ContractException(PropertyError.INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT, + regionPropertyId); + } + } + } + } + + } + + } + + private final Data data; + + private RegionsPluginData(Data data) { + this.data = data; + } + + /** + * Returns the {@link PropertyDefinition} for the given + * {@link RegionPropertyId}. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the region property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the region property id is known
    • + *
    + */ + public PropertyDefinition getRegionPropertyDefinition(final RegionPropertyId regionPropertyId) { + validateRegionPropertyIdNotNull(regionPropertyId); + validateRegionPropertyIsDefined(data, regionPropertyId); + return data.regionPropertyDefinitions.get(regionPropertyId); + } + + /** + * Returns the set of {@link RegionPropertyId} + */ + @SuppressWarnings("unchecked") + public Set getRegionPropertyIds() { + Set result = new LinkedHashSet<>(); + for (RegionPropertyId regionPropertyId : data.regionPropertyDefinitions.keySet()) { + result.add((T) regionPropertyId); + } + return result; + } + + /** + * Returns the property value for the given {@link RegionId} and + * {@link RegionPropertyId}. + * + * @throws ContractException + *
      + *
    • {@linkplain RegionError#NULL_REGION_ID} if the + * region id is null
    • + *
    • {@linkplain RegionError#UNKNOWN_REGION_ID} if + * the region id is unknown
    • + *
    + */ + public Map getRegionPropertyValues(final RegionId regionId) { + validateRegionExists(data, regionId); + final Map map = data.regionPropertyValues.get(regionId); + if (map == null) { + return data.emptyRegionPropertyMap; + } + return Collections.unmodifiableMap(map); + } + + /** + * Returns the set of {@link RegionId} values contained in this initial data. + * Each region id will correspond to a region agent that is automatically added + * to the simulation during initialization. + */ + public Set getRegionIds() { + return Collections.unmodifiableSet(data.regionIds); + } + + /** + * Returns the time tracking Policy}. Defaulted to false if not set in the + * builder. + */ + public boolean getPersonRegionArrivalTrackingPolicy() { + return data.trackRegionArrivalTimes; + } + + @Override + public PluginDataBuilder getCloneBuilder() { + return new Builder(data); + } + + private static void validatePersonId(PersonId personId) { + if (personId == null) { + throw new ContractException(PersonError.NULL_PERSON_ID); + } + + } + + private static void validateTime(Double time) { + if (time == null) { + throw new ContractException(RegionError.NULL_TIME); + } + if (!Double.isFinite(time)) { + throw new ContractException(RegionError.NON_FINITE_TIME); + } + } + + /** + * Returns the largest id value of any person assigned a region. + */ + public int getPersonCount() { + return FastMath.max(data.personRegions.size(), data.personArrivalTimes.size()); + } + + /** + * Returns the {@link RegionId} for the given {@link PersonId}. + * + * @throws ContractException {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null + */ + @SuppressWarnings("unchecked") + public Optional getPersonRegion(final PersonId personId) { + validatePersonId(personId); + + int personIndex = personId.getValue(); + if (personIndex < data.personRegions.size()) { + return Optional.ofNullable((T) data.personRegions.get(personIndex)); + } + return Optional.empty(); + } + + /** + * Returns the region arrival time for the given {@link PersonId}. + * + * @throws ContractException {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null + */ + public Optional getPersonRegionArrivalTime(final PersonId personId) { + validatePersonId(personId); + int personIndex = personId.getValue(); + if (personIndex < data.personArrivalTimes.size()) { + return Optional.ofNullable(data.personArrivalTimes.get(personIndex)); + } + return Optional.empty(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + data.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof RegionsPluginData)) { + return false; + } + RegionsPluginData other = (RegionsPluginData) obj; + if (!data.equals(other.data)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder2 = new StringBuilder(); + builder2.append("RegionsPluginData [data="); + builder2.append(data); + builder2.append("]"); + return builder2.toString(); + } + + /** + * Returns the property values + */ + public Map> getRegionPropertyValues() { + Map> result = new LinkedHashMap<>(); + for (RegionId regionId : data.regionPropertyValues.keySet()) { + Map map = data.regionPropertyValues.get(regionId); + Map newMap = new LinkedHashMap<>(map); + result.put(regionId, newMap); + } + return result; + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/events/PersonRegionUpdateEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/events/PersonRegionUpdateEvent.java new file mode 100644 index 000000000..f547cc615 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/events/PersonRegionUpdateEvent.java @@ -0,0 +1,11 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import net.jcip.annotations.Immutable; + +@Immutable +public record PersonRegionUpdateEvent(PersonId personId, RegionId previousRegionId, RegionId currentRegionId) + implements Event { +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/events/RegionAdditionEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/events/RegionAdditionEvent.java new file mode 100644 index 000000000..c7b543052 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/events/RegionAdditionEvent.java @@ -0,0 +1,123 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.events; + +import java.util.ArrayList; +import java.util.List; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionError; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * An event indicating that a region has been added + */ +@Immutable +public class RegionAdditionEvent implements Event { + + private static class Data { + private RegionId regionId; + private List values = new ArrayList<>(); + + public Data() { + } + + public Data(Data data) { + regionId = data.regionId; + values.addAll(data.values); + } + } + + /** + * Returns a new Builder instance + */ + public static Builder builder() { + return new Builder(new Data()); + } + + private final Data data; + + /** + * Builder class for {@link RegionAdditionEvent} + */ + public static class Builder { + + private Data data = new Data(); + + private Builder(Data data) { + this.data = data; + } + + private void validate() { + if (data.regionId == null) { + throw new ContractException(RegionError.NULL_REGION_ID); + } + } + + /** + * Builds the Region addition event from the inputs + * + * @throws ContractException {@linkplain RegionError#NULL_REGION_ID} if the + * region id was not set + */ + public RegionAdditionEvent build() { + validate(); + return new RegionAdditionEvent(new Data(data)); + } + + /** + * Sets the region id + * + * @throws ContractException {@linkplain RegionError#NULL_REGION_ID} if the + * region id is null + */ + public Builder setRegionId(RegionId regionId) { + if (regionId == null) { + throw new ContractException(RegionError.NULL_REGION_ID); + } + data.regionId = regionId; + return this; + } + + /** + * Adds an auxiliary value to be used by observers of region addition + * + * @throws ContractException {@linkplain RegionError#NULL_AUXILIARY_DATA} if the + * value is null + */ + public Builder addValue(Object value) { + if (value == null) { + throw new ContractException(RegionError.NULL_AUXILIARY_DATA); + } + data.values.add(value); + return this; + } + } + + private RegionAdditionEvent(Data data) { + this.data = data; + } + + /** + * Returns the region id. + */ + public RegionId getRegionId() { + return data.regionId; + } + + /** + * Returns the (non-null) auxiliary objects that are instances of the given + * class in the order of their addition to the builder. + */ + @SuppressWarnings("unchecked") + public List getValues(Class c) { + List result = new ArrayList<>(); + for (Object value : data.values) { + if (c.isAssignableFrom(value.getClass())) { + result.add((T) value); + } + } + return result; + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/events/RegionPropertyDefinitionEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/events/RegionPropertyDefinitionEvent.java new file mode 100644 index 000000000..726fb63e5 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/events/RegionPropertyDefinitionEvent.java @@ -0,0 +1,28 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * Event indicating the addition of a region property + */ +@Immutable +public record RegionPropertyDefinitionEvent(RegionPropertyId regionPropertyId) implements Event { + + /** + * Constructs the event + * + * @throws ContractException {@linkplain PropertyError#NULL_PROPERTY_ID} if the + * region property id is null + */ + public RegionPropertyDefinitionEvent { + if (regionPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/events/RegionPropertyUpdateEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/events/RegionPropertyUpdateEvent.java new file mode 100644 index 000000000..c0b843f34 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/events/RegionPropertyUpdateEvent.java @@ -0,0 +1,11 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionPropertyId; +import net.jcip.annotations.Immutable; + +@Immutable +public record RegionPropertyUpdateEvent(RegionId regionId, RegionPropertyId regionPropertyId, + Object previousPropertyValue, Object currentPropertyValue) implements Event { +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/reports/RegionPropertyReport.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/reports/RegionPropertyReport.java new file mode 100644 index 000000000..5f37f84a6 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/reports/RegionPropertyReport.java @@ -0,0 +1,210 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.reports; + +import java.util.LinkedHashSet; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.regions.events.RegionAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.regions.events.RegionPropertyDefinitionEvent; +import gov.hhs.aspr.ms.gcm.plugins.regions.events.RegionPropertyUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionError; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.errors.ContractException; + +/** + * A Report that displays assigned region property values over time. Fields Time + * -- the time in days when the region property was set Region -- the region + * identifier Property -- the region property identifier Value -- the value of + * the region property + */ +public final class RegionPropertyReport { + + private final Set includedPropertyIds = new LinkedHashSet<>(); + private final Set currentProperties = new LinkedHashSet<>(); + private final Set excludedPropertyIds = new LinkedHashSet<>(); + private final ReportLabel reportLabel; + private final boolean includeNewPropertyIds; + + private final ReportHeader reportHeader = ReportHeader.builder()// + .add("Time")// + .add("Region")// + .add("Property")// + .add("Value")// + .build();// + + private boolean isCurrentProperty(RegionPropertyId regionPropertyId) { + return currentProperties.contains(regionPropertyId); + } + + private boolean addToCurrentProperties(RegionPropertyId regionPropertyId) { + + // There are eight possibilities: + + /* + * P -- the default inclusion policy + * + * I -- the property is explicitly included + * + * X -- the property is explicitly excluded + * + * C -- the property should be on the current properties + * + * + * P I X C Table + * + * TRUE TRUE FALSE TRUE + * + * TRUE FALSE FALSE TRUE + * + * FALSE TRUE FALSE TRUE + * + * FALSE FALSE FALSE FALSE + * + * TRUE TRUE TRUE FALSE -- not possible + * + * TRUE FALSE TRUE FALSE + * + * FALSE TRUE TRUE FALSE -- not possible + * + * FALSE FALSE TRUE FALSE + * + * + * Two of the cases above are contradictory since a property cannot be both + * explicitly included and explicitly excluded + * + */ + // if X is true then we don't add the property + if (excludedPropertyIds.contains(regionPropertyId)) { + return false; + } + + // if both P and I are false we don't add the property + boolean included = includedPropertyIds.contains(regionPropertyId); + + if (!included && !includeNewPropertyIds) { + return false; + } + + // we have failed to reject the property + currentProperties.add(regionPropertyId); + + return true; + } + + /** + * @throws ContractException + *
      + *
    • {@linkplain RegionError#NULL_REGION_PROPERTY_REPORT_PLUGIN_DATA} + * if the plugin data is null
    • + *
    + */ + public RegionPropertyReport(RegionPropertyReportPluginData regionPropertyReportPluginData) { + if (regionPropertyReportPluginData == null) { + throw new ContractException(RegionError.NULL_REGION_PROPERTY_REPORT_PLUGIN_DATA); + } + + reportLabel = regionPropertyReportPluginData.getReportLabel(); + includedPropertyIds.addAll(regionPropertyReportPluginData.getIncludedProperties()); + excludedPropertyIds.addAll(regionPropertyReportPluginData.getExcludedProperties()); + includeNewPropertyIds = regionPropertyReportPluginData.getDefaultInclusionPolicy(); + } + + private void handleRegionPropertyUpdateEvent(ReportContext reportContext, + RegionPropertyUpdateEvent regionPropertyUpdateEvent) { + RegionPropertyId regionPropertyId = regionPropertyUpdateEvent.regionPropertyId(); + if (isCurrentProperty(regionPropertyId)) { + RegionId regionId = regionPropertyUpdateEvent.regionId(); + Object propertyValue = regionPropertyUpdateEvent.currentPropertyValue(); + writeProperty(reportContext, regionId, regionPropertyId, propertyValue); + } + } + + private void handleRegionAdditionEvent(ReportContext reportContext, RegionAdditionEvent regionAdditionEvent) { + RegionsDataManager regionsDataManager = reportContext.getDataManager(RegionsDataManager.class); + RegionId regionId = regionAdditionEvent.getRegionId(); + + for (final RegionPropertyId regionPropertyId : currentProperties) { + Object propertyValue = regionsDataManager.getRegionPropertyValue(regionId, regionPropertyId); + writeProperty(reportContext, regionId, regionPropertyId, propertyValue); + } + } + + private void handleRegionPropertyDefinitionEvent(ReportContext reportContext, + RegionPropertyDefinitionEvent regionPropertyDefinitionEvent) { + RegionsDataManager regionsDataManager = reportContext.getDataManager(RegionsDataManager.class); + RegionPropertyId regionPropertyId = regionPropertyDefinitionEvent.regionPropertyId(); + if (addToCurrentProperties(regionPropertyId)) { + for (final RegionId regionId : regionsDataManager.getRegionIds()) { + Object propertyValue = regionsDataManager.getRegionPropertyValue(regionId, regionPropertyId); + writeProperty(reportContext, regionId, regionPropertyId, propertyValue); + } + } + } + + /** + * Initial behavior for this report. The report subscribes to + * {@linkplain RegionPropertyUpdateEvent} and releases a {@link ReportItem} for + * each region property's initial value. + * + * @throws ContractException {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} if a + * region property id used in the constructor is + * unknown + */ + public void init(final ReportContext reportContext) { + final RegionsDataManager regionsDataManager = reportContext.getDataManager(RegionsDataManager.class); + + reportContext.subscribe(RegionPropertyDefinitionEvent.class, this::handleRegionPropertyDefinitionEvent); + reportContext.subscribe(RegionPropertyUpdateEvent.class, this::handleRegionPropertyUpdateEvent); + reportContext.subscribe(RegionAdditionEvent.class, this::handleRegionAdditionEvent); + if (reportContext.stateRecordingIsScheduled()) { + reportContext.subscribeToSimulationClose(this::recordSimulationState); + } + + for (RegionPropertyId regionPropertyId : regionsDataManager.getRegionPropertyIds()) { + addToCurrentProperties(regionPropertyId); + } + + for (RegionId regionId : regionsDataManager.getRegionIds()) { + for (final RegionPropertyId regionPropertyId : currentProperties) { + final Object regionPropertyValue = regionsDataManager.getRegionPropertyValue(regionId, + regionPropertyId); + writeProperty(reportContext, regionId, regionPropertyId, regionPropertyValue); + } + } + } + + private void recordSimulationState(ReportContext reportContext) { + RegionPropertyReportPluginData.Builder builder = RegionPropertyReportPluginData.builder(); + builder.setReportLabel(reportLabel); + for (RegionPropertyId regionPropertyId : includedPropertyIds) { + builder.includeRegionProperty(regionPropertyId); + } + for (RegionPropertyId regionPropertyId : excludedPropertyIds) { + builder.excludeRegionProperty(regionPropertyId); + } + builder.setDefaultInclusion(includeNewPropertyIds); + + reportContext.releaseOutput(builder.build()); + } + + private void writeProperty(ReportContext reportContext, final RegionId regionId, + final RegionPropertyId regionPropertyId, final Object regionPropertyValue) { + + final ReportItem.Builder reportItemBuilder = ReportItem.builder(); + reportItemBuilder.setReportHeader(reportHeader); + reportItemBuilder.setReportLabel(reportLabel); + + reportItemBuilder.addValue(reportContext.getTime()); + reportItemBuilder.addValue(regionId.toString()); + reportItemBuilder.addValue(regionPropertyId.toString()); + reportItemBuilder.addValue(regionPropertyValue); + reportContext.releaseOutput(reportItemBuilder.build()); + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/reports/RegionPropertyReportPluginData.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/reports/RegionPropertyReportPluginData.java new file mode 100644 index 000000000..3b2359c1a --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/reports/RegionPropertyReportPluginData.java @@ -0,0 +1,286 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.reports; + +import java.util.LinkedHashSet; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.PluginDataBuilder; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportError; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import net.jcip.annotations.ThreadSafe; +import util.errors.ContractException; + +/** + * A PluginData class supporting RegionPropertyReport construction. + */ +@ThreadSafe +public final class RegionPropertyReportPluginData implements PluginData { + + /* + * Data class for collecting the inputs to the report + */ + private static class Data { + private ReportLabel reportLabel; + private Set includedProperties = new LinkedHashSet<>(); + private Set excludedProperties = new LinkedHashSet<>(); + private boolean defaultInclusionPolicy = true; + + private boolean locked; + + private Data() { + } + + private Data(Data data) { + reportLabel = data.reportLabel; + includedProperties.addAll(data.includedProperties); + excludedProperties.addAll(data.excludedProperties); + defaultInclusionPolicy = data.defaultInclusionPolicy; + locked = data.locked; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (defaultInclusionPolicy ? 1231 : 1237); + result = prime * result + ((excludedProperties == null) ? 0 : excludedProperties.hashCode()); + result = prime * result + ((includedProperties == null) ? 0 : includedProperties.hashCode()); + result = prime * result + ((reportLabel == null) ? 0 : reportLabel.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + if (defaultInclusionPolicy != other.defaultInclusionPolicy) { + return false; + } + if (excludedProperties == null) { + if (other.excludedProperties != null) { + return false; + } + } else if (!excludedProperties.equals(other.excludedProperties)) { + return false; + } + if (includedProperties == null) { + if (other.includedProperties != null) { + return false; + } + } else if (!includedProperties.equals(other.includedProperties)) { + return false; + } + if (reportLabel == null) { + if (other.reportLabel != null) { + return false; + } + } else if (!reportLabel.equals(other.reportLabel)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Data [reportLabel="); + builder.append(reportLabel); + builder.append(", includedProperties="); + builder.append(includedProperties); + builder.append(", excludedProperties="); + builder.append(excludedProperties); + builder.append(", defaultInclusionPolicy="); + builder.append(defaultInclusionPolicy); + builder.append("]"); + return builder.toString(); + } + + } + + /** + * Returns a new instance of the builder class + */ + public static Builder builder() { + return new Builder(new Data()); + } + + /** + * Builder class for the report + */ + public final static class Builder implements PluginDataBuilder { + private Builder(Data data) { + this.data = data; + } + + private void ensureDataMutability() { + if (data.locked) { + data = new Data(data); + data.locked = false; + } + } + + private void ensureImmutability() { + if (!data.locked) { + data.locked = true; + } + } + + private void validateData() { + if (data.reportLabel == null) { + throw new ContractException(ReportError.NULL_REPORT_LABEL); + } + } + + private Data data; + + /** + * Returns a RegionPropertyPluginData created from the collected inputs + * + * @throws ContractException + *
      + *
    • {@linkplain ReportError#NULL_REPORT_LABEL} if + * the report label is not assigned
    • + *
    • {@linkplain ReportError#NULL_REPORT_PERIOD} if + * the report period is not assigned
    • + *
    + */ + public RegionPropertyReportPluginData build() { + + if (!data.locked) { + validateData(); + } + ensureImmutability(); + return new RegionPropertyReportPluginData(data); + + } + + /** + * Sets the default policy for inclusion of region properties in the report. + * This policy is used when a region property has not been explicitly included + * or excluded. Defaulted to true. + */ + public Builder setDefaultInclusion(boolean include) { + ensureDataMutability(); + data.defaultInclusionPolicy = include; + return this; + } + + /** + * Selects the given region property id to be included in the report. + * + * @throws ContractException {@linkplain PropertyError#NULL_PROPERTY_ID} if the + * person property id is null + */ + public Builder includeRegionProperty(RegionPropertyId regionPropertyId) { + ensureDataMutability(); + if (regionPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + data.includedProperties.add(regionPropertyId); + data.excludedProperties.remove(regionPropertyId); + return this; + } + + /** + * Selects the given region property id to be excluded from the report + * + * @throws ContractException {@linkplain PropertyError#NULL_PROPERTY_ID} if the + * person property id is null + */ + public Builder excludeRegionProperty(RegionPropertyId regionPropertyId) { + ensureDataMutability(); + if (regionPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + data.includedProperties.remove(regionPropertyId); + data.excludedProperties.add(regionPropertyId); + return this; + } + + /** + * Sets the report label + * + * @throws ContractException {@linkplain ReportError#NULL_REPORT_LABEL} if the + * report label is null + */ + public Builder setReportLabel(ReportLabel reportLabel) { + ensureDataMutability(); + if (reportLabel == null) { + throw new ContractException(ReportError.NULL_REPORT_LABEL); + } + data.reportLabel = reportLabel; + return this; + } + } + + private final Data data; + + private RegionPropertyReportPluginData(Data data) { + this.data = data; + } + + @Override + public Builder getCloneBuilder() { + return new Builder(data); + } + + public ReportLabel getReportLabel() { + return data.reportLabel; + } + + public Set getIncludedProperties() { + return data.includedProperties; + } + + public Set getExcludedProperties() { + return data.excludedProperties; + } + + public boolean getDefaultInclusionPolicy() { + return data.defaultInclusionPolicy; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof RegionPropertyReportPluginData)) { + return false; + } + RegionPropertyReportPluginData other = (RegionPropertyReportPluginData) obj; + if (data == null) { + if (other.data != null) { + return false; + } + } else if (!data.equals(other.data)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder2 = new StringBuilder(); + builder2.append("RegionPropertyReportPluginData [data="); + builder2.append(data); + builder2.append("]"); + return builder2.toString(); + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/reports/RegionTransferReport.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/reports/RegionTransferReport.java new file mode 100644 index 000000000..1d2858185 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/reports/RegionTransferReport.java @@ -0,0 +1,130 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.reports; + +import java.util.LinkedHashMap; +import java.util.Map; + +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.events.PersonAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.regions.events.PersonRegionUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.PeriodicReport; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import util.wrappers.MultiKey; +import util.wrappers.MutableInteger; + +/** + * A periodic Report that displays the number of times a person transferred from + * one region to another. Transfers from a region to itself are interpreted as + * the addition of people at that region. Removal of people is not reflected in + * this report. Fields SourceRegion -- the source region identifier + * DestinationRegion -- the destination region property identifier Transfers -- + * the number of transfers from the source region to the destination region + */ +public final class RegionTransferReport extends PeriodicReport { + + public RegionTransferReport(RegionTransferReportPluginData regionTransferReportPluginData) { + super(regionTransferReportPluginData.getReportLabel(), regionTransferReportPluginData.getReportPeriod()); + } + + /* + * A mapping from a (Region, Region) tuple to a count of the number of + * transfers. + */ + private final Map baseMap = new LinkedHashMap<>(); + + /* + * The derived header for this report + */ + private ReportHeader reportHeader; + + private ReportHeader getReportHeader() { + if (reportHeader == null) { + ReportHeader.Builder reportHeaderBuilder = ReportHeader.builder(); + reportHeader = addTimeFieldHeaders(reportHeaderBuilder)// + + .add("source_region")// + .add("destination_region")// + .add("transfers")// + .build();// + } + return reportHeader; + } + + @Override + protected void flush(ReportContext reportContext) { + + for (final MultiKey multiKey : baseMap.keySet()) { + RegionId sourceRegionId = multiKey.getKey(0); + RegionId destinationRegionId = multiKey.getKey(1); + MutableInteger mutableInteger = baseMap.get(multiKey); + ReportItem.Builder reportItemBuilder = ReportItem.builder(); + reportItemBuilder.setReportHeader(getReportHeader()); + reportItemBuilder.setReportLabel(getReportLabel()); + fillTimeFields(reportItemBuilder); + reportItemBuilder.addValue(sourceRegionId.toString()); + reportItemBuilder.addValue(destinationRegionId.toString()); + reportItemBuilder.addValue(mutableInteger.getValue()); + reportContext.releaseOutput(reportItemBuilder.build()); + } + + baseMap.clear(); + + } + + private void handlePersonAdditionEvent(ReportContext ReportContext, PersonAdditionEvent personAdditionEvent) { + PersonId personId = personAdditionEvent.personId(); + final RegionId regionId = regionsDataManager.getPersonRegion(personId); + increment(regionId, regionId); + } + + private void handlePersonRegionUpdateEvent(ReportContext ReportContext, + PersonRegionUpdateEvent personRegionUpdateEvent) { + RegionId previousRegionId = personRegionUpdateEvent.previousRegionId(); + RegionId currentRegionId = personRegionUpdateEvent.currentRegionId(); + increment(previousRegionId, currentRegionId); + } + + /* + * Increments the number of region transfers for the give tuple + */ + private void increment(final RegionId sourceRegionId, final RegionId destinationRegionId) { + MultiKey multiKey = new MultiKey(sourceRegionId, destinationRegionId); + MutableInteger mutableInteger = baseMap.get(multiKey); + if (mutableInteger == null) { + mutableInteger = new MutableInteger(); + baseMap.put(multiKey, mutableInteger); + } + mutableInteger.increment(); + } + + private RegionsDataManager regionsDataManager; + + @Override + protected void prepare(final ReportContext reportContext) { + PeopleDataManager peopleDataManager = reportContext.getDataManager(PeopleDataManager.class); + regionsDataManager = reportContext.getDataManager(RegionsDataManager.class); + + reportContext.subscribe(PersonAdditionEvent.class, this::handlePersonAdditionEvent); + reportContext.subscribe(PersonRegionUpdateEvent.class, this::handlePersonRegionUpdateEvent); + if (reportContext.stateRecordingIsScheduled()) { + reportContext.subscribeToSimulationClose(this::recordSimulationState); + } + + for (PersonId personId : peopleDataManager.getPeople()) { + final RegionId regionId = regionsDataManager.getPersonRegion(personId); + increment(regionId, regionId); + } + } + + private void recordSimulationState(ReportContext reportContext) { + RegionTransferReportPluginData.Builder builder = RegionTransferReportPluginData.builder(); + builder.setReportLabel(getReportLabel()); + builder.setReportPeriod(getReportPeriod()); + reportContext.releaseOutput(builder.build()); + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/reports/RegionTransferReportPluginData.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/reports/RegionTransferReportPluginData.java new file mode 100644 index 000000000..f87eb1bba --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/reports/RegionTransferReportPluginData.java @@ -0,0 +1,142 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.reports; + +import gov.hhs.aspr.ms.gcm.plugins.reports.support.PeriodicReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportError; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import net.jcip.annotations.ThreadSafe; +import util.errors.ContractException; + +/** + * A PluginData class supporting PersonPropertyReport construction. + */ +@ThreadSafe +public final class RegionTransferReportPluginData extends PeriodicReportPluginData { + + private final Data data; + + private RegionTransferReportPluginData(Data data) { + super(data); + this.data = data; + } + + /* + * Data class for collecting the inputs to the report + */ + private static class Data extends PeriodicReportPluginData.Data { + + private Data() { + super(); + } + + private Data(Data data) { + super(data); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(super.toString()); + builder.append("]"); + return builder.toString(); + } + } + + /** + * Builder class for the report + */ + public final static class Builder extends PeriodicReportPluginData.Builder { + private Data data; + + private Builder(Data data) { + super(data); + this.data = data; + } + + /** + * Returns a PersonPropertyReportPluginData created from the collected inputs + * + * @throws ContractException + *
      + *
    • {@linkplain ReportError#NULL_REPORT_LABEL} if + * the report label is not assigned
    • + *
    • {@linkplain ReportError#NULL_REPORT_PERIOD} if + * the report period is not assigned
    • + *
    + */ + public RegionTransferReportPluginData build() { + return new RegionTransferReportPluginData(data); + } + + /** + * Sets the report label + * + * @throws ContractException {@linkplain ReportError#NULL_REPORT_LABEL} if the + * report label is null + */ + public Builder setReportLabel(ReportLabel reportLabel) { + super.setReportLabel(reportLabel); + return this; + } + + /** + * Sets the report period id + * + * @throws ContractException {@linkplain ReportError#NULL_REPORT_PERIOD} if the + * report period is null + */ + public Builder setReportPeriod(ReportPeriod reportPeriod) { + super.setReportPeriod(reportPeriod); + return this; + } + } + + /** + * Returns a new instance of the builder class + */ + public static Builder builder() { + return new Builder(new Data()); + } + + @Override + public Builder getCloneBuilder() { + return new Builder(new Data(data)); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof RegionTransferReportPluginData)) { + return false; + } + RegionTransferReportPluginData other = (RegionTransferReportPluginData) obj; + if (data == null) { + if (other.data != null) { + return false; + } + } else if (!data.equals(other.data)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder2 = new StringBuilder(); + builder2.append("RegionTransferReportPluginData [data="); + builder2.append(data); + builder2.append("]"); + return builder2.toString(); + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/RegionConstructionData.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/RegionConstructionData.java new file mode 100644 index 000000000..ac5f9564c --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/RegionConstructionData.java @@ -0,0 +1,154 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.support; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.errors.ContractException; + +public class RegionConstructionData { + + private static class Data { + private RegionId regionId; + private List values = new ArrayList<>(); + private Map propertyValues = new LinkedHashMap<>(); + + public Data() { + } + + public Data(Data data) { + regionId = data.regionId; + values.addAll(data.values); + propertyValues.putAll(data.propertyValues); + } + } + + /** + * Returns a new Builder instance + */ + public static Builder builder() { + return new Builder(new Data()); + } + + /** + * Static builder class for {@link RegionConstructionData} + */ + public static class Builder { + private Data data = new Data(); + + private void validate() { + if (data.regionId == null) { + throw new ContractException(RegionError.NULL_REGION_ID); + } + } + + private Builder(Data data) { + this.data = data; + } + + /** + * Builds the Region Construction Data from the given inputs. + * + * @throws ContractException {@linkplain RegionError#NULL_REGION_ID} if the + * region id was not set + */ + public RegionConstructionData build() { + validate(); + return new RegionConstructionData(new Data(data)); + } + + /** + * Sets the region id + * + * @throws ContractException {@linkplain RegionError#NULL_REGION_ID} if the + * region id is null + */ + public Builder setRegionId(RegionId regionId) { + if (regionId == null) { + throw new ContractException(RegionError.NULL_REGION_ID); + } + data.regionId = regionId; + return this; + } + + /** + * Adds an auxiliary value to be used by observers of region addition + * + * @throws ContractException {@linkplain RegionError#NULL_AUXILIARY_DATA} if the + * value is null + */ + public Builder addValue(Object value) { + if (value == null) { + throw new ContractException(RegionError.NULL_AUXILIARY_DATA); + } + data.values.add(value); + return this; + } + + /** + * Sets a region property value + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the region property id is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_VALUE} + * if the value is null
    • + *
    • {@linkplain PropertyError#DUPLICATE_PROPERTY_VALUE_ASSIGNMENT} + * if the region property was previously set
    • + *
    + */ + public Builder setRegionPropertyValue(RegionPropertyId regionPropertyId, Object value) { + if (regionPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + if (value == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_VALUE); + } + if (data.propertyValues.containsKey(regionPropertyId)) { + throw new ContractException(PropertyError.DUPLICATE_PROPERTY_VALUE_ASSIGNMENT); + } + data.propertyValues.put(regionPropertyId, value); + return this; + } + + } + + private final Data data; + + private RegionConstructionData(Data data) { + this.data = data; + } + + /** + * Returns a non-null region id + */ + public RegionId getRegionId() { + return data.regionId; + } + + /** + * Returns the (non-null) auxiliary objects that are instances of the given + * class in the order of their addition to the builder. + */ + @SuppressWarnings("unchecked") + public List getValues(Class c) { + List result = new ArrayList<>(); + for (Object value : data.values) { + if (c.isAssignableFrom(value.getClass())) { + result.add((T) value); + } + } + return result; + } + + /** + * Returns an unmodifiable map of region property ids to values + */ + public Map getRegionPropertyValues() { + return Collections.unmodifiableMap(data.propertyValues); + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/RegionError.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/RegionError.java new file mode 100644 index 000000000..ce666e828 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/RegionError.java @@ -0,0 +1,41 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.support; + +import util.errors.ContractError; +import util.errors.ContractException; + +/** + * An enumeration supporting {@link ContractException} that acts as a general + * description of the exception. + */ +public enum RegionError implements ContractError { + DUPLICATE_PERSON_ADDITION("Duplicate person region addition"), DUPLICATE_REGION_ID("Duplicate region id"), + MISSING_REGION_ASSIGNMENT("Region assignment is missing"), NON_FINITE_TIME("Not a finite time value"), + NULL_TIME("Not time value"), NULL_AUXILIARY_DATA("Null auxiliary data"), + NULL_REGION_CONSTRUCTION_DATA("Null region construction data"), NULL_REGION_ID("Null region id"), + NULL_REGION_PLUGIN_DATA("Null region plugin data"), + NULL_REGION_PROPERTY_DEFINITION_INITIALIZATION("Null region property definition initialization"), + NULL_TIME_TRACKING_POLICY("Null time tracking policy"), + REGION_ARRIVAL_TIMES_NOT_TRACKED("Person region arrival times not actively tracked"), + REGION_ARRIVAL_TIMES_MISMATCHED( + "Person region arrival times are actively tracked, but region assignment and region arrival assignments do not align"), + UNKNOWN_REGION_ID("Unknown region id"), + REGION_ARRIVAL_TIME_EXCEEDS_SIM_TIME("A person region arrival time exceeds the current simulation time"), + PERSON_ARRIVAL_DATA_PRESENT("Person arrival times were recorded while the tracking policy is false"), + MISSING_PERSON_ARRIVAL_DATA( + "Person regions were recorded and no arrival times were recorded while the tracking policy is true"), + NULL_REGION_PROPERTY_REPORT_PLUGIN_DATA("Null region property report plugin data"), + NULL_REGION_TRANSFER_REPORT_PLUGIN_DATA("Null region transfer report plugin data"), + + ; + + private final String description; + + private RegionError(final String description) { + this.description = description; + } + + @Override + public String getDescription() { + return description; + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/RegionFilter.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/RegionFilter.java new file mode 100644 index 000000000..74719fff5 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/RegionFilter.java @@ -0,0 +1,128 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.support; + +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.FilterSensitivity; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters.Filter; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.regions.events.PersonRegionUpdateEvent; +import util.errors.ContractException; + +public final class RegionFilter extends Filter { + + private final Set regionIds = new LinkedHashSet<>(); + + private RegionsDataManager regionsDataManager; + + private void validateRegionId(final RegionId regionId) { + + if (regionId == null) { + throw new ContractException(RegionError.NULL_REGION_ID); + } + + if (!regionsDataManager.regionIdExists(regionId)) { + throw new ContractException(RegionError.UNKNOWN_REGION_ID, regionId); + } + } + + public RegionFilter(final RegionId... regionIds) { + for (RegionId regionId : regionIds) { + this.regionIds.add(regionId); + } + } + + @Override + public void validate(PartitionsContext partitionsContext) { + if (partitionsContext == null) { + throw new ContractException(NucleusError.NULL_SIMULATION_CONTEXT); + } + + if (regionsDataManager == null) { + regionsDataManager = partitionsContext.getDataManager(RegionsDataManager.class); + } + + for (RegionId regionId : regionIds) { + validateRegionId(regionId); + } + + } + + public Set getRegionIds() { + return new LinkedHashSet<>(regionIds); + } + + public RegionFilter(final Set regionIds) { + this.regionIds.addAll(regionIds); + } + + @Override + public boolean evaluate(PartitionsContext partitionsContext, PersonId personId) { + if (partitionsContext == null) { + throw new ContractException(NucleusError.NULL_SIMULATION_CONTEXT); + } + + if (regionsDataManager == null) { + regionsDataManager = partitionsContext.getDataManager(RegionsDataManager.class); + } + return regionIds.contains(regionsDataManager.getPersonRegion(personId)); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("RegionFilter [regionIds="); + builder.append(regionIds); + builder.append("]"); + return builder.toString(); + } + + private Optional requiresRefresh(PartitionsContext partitionsContext, PersonRegionUpdateEvent event) { + boolean previousRegionIdContained = regionIds.contains(event.previousRegionId()); + boolean currentRegionIdContained = regionIds.contains(event.currentRegionId()); + if (previousRegionIdContained != currentRegionIdContained) { + return Optional.of(event.personId()); + } + return Optional.empty(); + } + + @Override + public Set> getFilterSensitivities() { + Set> result = new LinkedHashSet<>(); + result.add( + new FilterSensitivity(PersonRegionUpdateEvent.class, this::requiresRefresh)); + return result; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((regionIds == null) ? 0 : regionIds.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof RegionFilter)) { + return false; + } + RegionFilter other = (RegionFilter) obj; + if (regionIds == null) { + if (other.regionIds != null) { + return false; + } + } else if (!regionIds.equals(other.regionIds)) { + return false; + } + return true; + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/RegionId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/RegionId.java new file mode 100644 index 000000000..e9c0c0854 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/RegionId.java @@ -0,0 +1,16 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.support; + +import net.jcip.annotations.ThreadSafe; + +/** + * Marker interface for region identifiers. Each region id is associated with a + * region agent in the simulation. + */ +@ThreadSafe +public interface RegionId { + @Override + public int hashCode(); + + @Override + public boolean equals(Object obj); +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/RegionLabeler.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/RegionLabeler.java new file mode 100644 index 000000000..1c2528f90 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/RegionLabeler.java @@ -0,0 +1,68 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.support; + +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.Labeler; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.LabelerSensitivity; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.regions.events.PersonRegionUpdateEvent; +import util.errors.ContractException; + +/** + * A labeler for regions. The dimension of the labeler is the + * {@linkplain RegionId} class, the event that stimulates a label update is + * {@linkplain PersonRegionUpdateEvent} and the labeling function is composed + * from the given Function. + */ +public abstract class RegionLabeler implements Labeler { + + protected abstract Object getLabelFromRegionId(RegionId regionId); + + private RegionsDataManager regionsDataManager; + + private Optional getPersonId(PersonRegionUpdateEvent personRegionUpdateEvent) { + return Optional.of(personRegionUpdateEvent.personId()); + } + + @Override + public final Set> getLabelerSensitivities() { + Set> result = new LinkedHashSet<>(); + result.add(new LabelerSensitivity(PersonRegionUpdateEvent.class, this::getPersonId)); + return result; + } + + @Override + public final Object getCurrentLabel(PartitionsContext partitionsContext, PersonId personId) { + if (partitionsContext == null) { + throw new ContractException(NucleusError.NULL_SIMULATION_CONTEXT); + } + if (regionsDataManager == null) { + regionsDataManager = partitionsContext.getDataManager(RegionsDataManager.class); + } + RegionId regionId = regionsDataManager.getPersonRegion(personId); + return getLabelFromRegionId(regionId); + } + + @Override + public final Object getId() { + return RegionId.class; + } + + @Override + public final Object getPastLabel(PartitionsContext partitionsContext, Event event) { + PersonRegionUpdateEvent personRegionUpdateEvent = (PersonRegionUpdateEvent) event; + return getLabelFromRegionId(personRegionUpdateEvent.previousRegionId()); + } + + @Override + public String toString() { + return "RegionLabeler []"; + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/RegionPropertyDefinitionInitialization.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/RegionPropertyDefinitionInitialization.java new file mode 100644 index 000000000..22124158b --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/RegionPropertyDefinitionInitialization.java @@ -0,0 +1,175 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.support; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.commons.math3.util.Pair; + +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * A class for defining a person property with an associated property id and + * property values for extant people. + */ +@Immutable +public final class RegionPropertyDefinitionInitialization { + + private static class Data { + RegionPropertyId regionPropertyId; + PropertyDefinition propertyDefinition; + List> propertyValues = new ArrayList<>(); + + public Data() { + } + + public Data(Data data) { + regionPropertyId = data.regionPropertyId; + propertyDefinition = data.propertyDefinition; + propertyValues.addAll(data.propertyValues); + } + } + + private final Data data; + + private RegionPropertyDefinitionInitialization(Data data) { + this.data = data; + } + + /** + * Returns a new Builder instance + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder class for a PropertyDefinitionInitialization + */ + public final static class Builder { + + private Builder() { + } + + private Data data = new Data(); + + private void validate() { + if (data.propertyDefinition == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_DEFINITION); + } + + if (data.regionPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + + Class type = data.propertyDefinition.getType(); + for (Pair pair : data.propertyValues) { + Object value = pair.getSecond(); + if (!type.isAssignableFrom(value.getClass())) { + String message = "Definition Type " + type.getName() + " is not compatible with value = " + value; + throw new ContractException(PropertyError.INCOMPATIBLE_VALUE, message); + } + } + } + + /** + * Constructs the PersonPropertyDefinitionInitialization from the collected data + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_DEFINITION} + * if no property definition was assigned to the + * builder
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * no property id was assigned to the builder
    • + *
    • {@linkplain PropertyError#INCOMPATIBLE_VALUE} + * if a collected property value is incompatible with + * the property definition
    • + *
    + */ + public RegionPropertyDefinitionInitialization build() { + validate(); + return new RegionPropertyDefinitionInitialization(new Data(data)); + } + + /** + * Sets the property id + * + * @throws ContractException {@linkplain PropertyError#NULL_PROPERTY_ID} if the + * property id is null + */ + public Builder setRegionPropertyId(RegionPropertyId regionPropertyId) { + if (regionPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + data.regionPropertyId = regionPropertyId; + return this; + } + + /** + * Sets the property definition + * + * @throws ContractException {@linkplain PropertyError#NULL_PROPERTY_DEFINITION} + * if the property definition is null + */ + public Builder setPropertyDefinition(PropertyDefinition propertyDefinition) { + if (propertyDefinition == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_DEFINITION); + } + data.propertyDefinition = propertyDefinition; + return this; + } + + /** + * Adds a property value + * + * @throws ContractException + *
      + *
    • {@linkplain RegionError#NULL_REGION_ID} if the + * regionId is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_VALUE} + * if the property value is null
    • + *
    + */ + public Builder addPropertyValue(RegionId regionId, Object value) { + if (regionId == null) { + throw new ContractException(RegionError.NULL_REGION_ID); + } + if (value == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_VALUE); + } + + data.propertyValues.add(new Pair<>(regionId, value)); + return this; + } + + } + + /** + * Returns the (non-null) region property id. + */ + public RegionPropertyId getRegionPropertyId() { + return data.regionPropertyId; + } + + /** + * Returns the (non-null) property definition. + */ + public PropertyDefinition getPropertyDefinition() { + return data.propertyDefinition; + } + + /** + * Returns the list of (region,value) pairs collected by the builder in the + * order of their addition. All pairs have non-null entries and the values are + * compatible with the contained property definition. Duplicate assignments of + * values to the same owner may be present. + */ + public List> getPropertyValues() { + return Collections.unmodifiableList(data.propertyValues); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/RegionPropertyDimension.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/RegionPropertyDimension.java new file mode 100644 index 000000000..115c082ba --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/RegionPropertyDimension.java @@ -0,0 +1,228 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.support; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import gov.hhs.aspr.ms.gcm.nucleus.Dimension; +import gov.hhs.aspr.ms.gcm.nucleus.DimensionContext; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.errors.ContractException; + +public class RegionPropertyDimension implements Dimension { + private final Data data; + + private RegionPropertyDimension(Data data) { + this.data = data; + } + + private static class Data { + private RegionId regionId; + private RegionPropertyId regionPropertyId; + private List values = new ArrayList<>(); + + private Data() { + } + + private Data(Data data) { + regionPropertyId = data.regionPropertyId; + regionId = data.regionId; + values.addAll(data.values); + } + + @Override + public int hashCode() { + return Objects.hash(regionId, regionPropertyId, values); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Data other = (Data) obj; + return Objects.equals(regionId, other.regionId) && Objects.equals(regionPropertyId, other.regionPropertyId) + && Objects.equals(values, other.values); + } + + } + + /** + * Returns a new builder for RegionPropertyDimension + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder class for RegionPropertyDimension + */ + public static class Builder { + + private Builder() { + } + + private Data data = new Data(); + + /** + * Returns the RegionPropertyDimension from the collected data. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the region property id was not assigned
    • + *
    • {@linkplain RegionError#NULL_REGION_ID} if the + * RegionId was not assigned
    • + *
    + */ + public RegionPropertyDimension build() { + validate(); + return new RegionPropertyDimension(data); + } + + private void validate() { + if (data.regionId == null) { + throw new ContractException(RegionError.NULL_REGION_ID); + } + + if (data.regionPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + } + + /** + * Sets the group id for the dimension. Defaults to null. + * + * @throws ContractException + *
      + *
    • {@linkplain RegionError#NULL_REGION_ID} if the + * regionId is null
    • + *
    + */ + public Builder setRegionId(RegionId RegionId) { + validateRegionId(RegionId); + data.regionId = RegionId; + return this; + } + + /** + * Sets the region property for the dimension. Defaults to null. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the property id is null
    • + *
    + */ + public Builder setRegionPropertyId(RegionPropertyId RegionPropertyId) { + validateRegionPropertyId(RegionPropertyId); + data.regionPropertyId = RegionPropertyId; + return this; + } + + /** + * Adds a value to the dimension. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_VALUE} + * if the value is null
    • + *
    + */ + public Builder addValue(Object value) { + validateValue(value); + data.values.add(value); + return this; + } + } + + @Override + public List getExperimentMetaData() { + List result = new ArrayList<>(); + result.add(data.regionPropertyId.toString()); + return result; + } + + @Override + public int levelCount() { + return data.values.size(); + } + + @Override + public List executeLevel(DimensionContext dimensionContext, int level) { + RegionsPluginData.Builder builder = dimensionContext.getPluginDataBuilder(RegionsPluginData.Builder.class); + Object value = data.values.get(level); + + builder.setRegionPropertyValue(data.regionId, data.regionPropertyId, value); + List result = new ArrayList<>(); + result.add(value.toString()); + return result; + } + + /** + * Returns the group id for this dimension + */ + public RegionId getRegionId() { + return data.regionId; + } + + /** + * Returns the region property id for this dimension + */ + public RegionPropertyId getRegionPropertyId() { + return data.regionPropertyId; + } + + /** + * Returns the ordered list of region property values for this dimension + */ + public List getValues() { + return new ArrayList<>(data.values); + } + + private static void validateValue(Object value) { + if (value == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_VALUE); + } + } + + private static void validateRegionPropertyId(RegionPropertyId RegionPropertyId) { + if (RegionPropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + } + + private static void validateRegionId(RegionId RegionId) { + if (RegionId == null) { + throw new ContractException(RegionError.NULL_REGION_ID); + } + } + + @Override + public int hashCode() { + return Objects.hash(data); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + RegionPropertyDimension other = (RegionPropertyDimension) obj; + return Objects.equals(data, other.data); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/RegionPropertyId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/RegionPropertyId.java new file mode 100644 index 000000000..6aa95d627 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/RegionPropertyId.java @@ -0,0 +1,11 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.support; + +import net.jcip.annotations.ThreadSafe; + +/** + * Marker interface for region property identifiers + */ +@ThreadSafe +public interface RegionPropertyId { + +} diff --git a/gcm3/src/main/java/plugins/regions/support/SimpleRegionId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/SimpleRegionId.java similarity index 77% rename from gcm3/src/main/java/plugins/regions/support/SimpleRegionId.java rename to gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/SimpleRegionId.java index cc78f45d6..53d799571 100644 --- a/gcm3/src/main/java/plugins/regions/support/SimpleRegionId.java +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/SimpleRegionId.java @@ -1,4 +1,4 @@ -package plugins.regions.support; +package gov.hhs.aspr.ms.gcm.plugins.regions.support; public final class SimpleRegionId implements RegionId { private final Object value; @@ -6,16 +6,19 @@ public final class SimpleRegionId implements RegionId { /** * Creates a region id from the given value * - * @throws NullPointerException - * if the value is null + * @throws NullPointerException if the value is null */ public SimpleRegionId(Object value) { - if (value == null) { + if (value == null) { throw new NullPointerException("null value for simple region id"); } this.value = value; } + public Object getValue() { + return this.value; + } + /** * Standard implementation */ @@ -28,8 +31,8 @@ public int hashCode() { } /** - * Two {@link SimpleREGIONId} instances are equal if and only if their - * inputs are equal. + * Two {@link SimpleRegionId} instances are equal if and only if their inputs + * are equal. */ @Override public boolean equals(Object obj) { diff --git a/gcm3/src/main/java/plugins/regions/support/SimpleRegionPropertyId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/SimpleRegionPropertyId.java similarity index 85% rename from gcm3/src/main/java/plugins/regions/support/SimpleRegionPropertyId.java rename to gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/SimpleRegionPropertyId.java index 2599cc23a..b1ca8a5a2 100644 --- a/gcm3/src/main/java/plugins/regions/support/SimpleRegionPropertyId.java +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/SimpleRegionPropertyId.java @@ -1,20 +1,25 @@ -package plugins.regions.support; +package gov.hhs.aspr.ms.gcm.plugins.regions.support; public final class SimpleRegionPropertyId implements RegionPropertyId { - + private final Object value; - + /** * Creates a region property id from the given value * * @throws NullPointerException if the value is null */ public SimpleRegionPropertyId(Object value) { - if (value == null) { + if (value == null) { throw new NullPointerException("null value in simple region property id"); } this.value = value; } + + public Object getValue() { + return this.value; + } + @Override public int hashCode() { final int prime = 31; @@ -22,7 +27,7 @@ public int hashCode() { result = prime * result + ((value == null) ? 0 : value.hashCode()); return result; } - + @Override public boolean equals(Object obj) { if (this == obj) { @@ -41,11 +46,10 @@ public boolean equals(Object obj) { } return true; } - + @Override public String toString() { - return value.toString(); + return value.toString(); } - } diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/testsupport/RegionsTestPluginFactory.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/testsupport/RegionsTestPluginFactory.java new file mode 100644 index 000000000..21b216bb2 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/testsupport/RegionsTestPluginFactory.java @@ -0,0 +1,370 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.testsupport; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePlugin; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeoplePluginData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonRange; +import gov.hhs.aspr.ms.gcm.plugins.regions.RegionsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.regions.reports.RegionPropertyReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.regions.reports.RegionTransferReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionError; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.StochasticsError; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +/** + * A static test support class for the {@linkplain RegionsPlugin}. Provides + * convenience methods for obtaining standarized PluginData for the listed + * Plugin. + *

    + * Also contains factory methods to obtain a list of plugins that is the minimal + * set needed to adequately test this Plugin that can be utilized with + *

    + * {@link TestSimulation#execute} + */ +public final class RegionsTestPluginFactory { + + private RegionsTestPluginFactory() { + } + + private static class Data { + private RegionPropertyReportPluginData regionPropertyReportPluginData; + private RegionTransferReportPluginData regionTransferReportPluginData; + private RegionsPluginData regionsPluginData; + private PeoplePluginData peoplePluginData; + private StochasticsPluginData stochasticsPluginData; + private TestPluginData testPluginData; + + private Data(int initialPopulation, boolean trackTimes, long seed, TestPluginData testPluginData) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + this.peoplePluginData = getStandardPeoplePluginData(initialPopulation); + this.regionsPluginData = getStandardRegionsPluginData(this.peoplePluginData.getPersonIds(), trackTimes, + randomGenerator.nextLong()); + this.stochasticsPluginData = getStandardStochasticsPluginData(randomGenerator.nextLong()); + this.testPluginData = testPluginData; + } + } + + /** + * Factory class that facilitates the building of {@linkplain PluginData} with + * the various setter methods. + */ + public static class Factory { + private Data data; + + private Factory(Data data) { + this.data = data; + } + + /** + * Returns a list of plugins containing a People, Regions, Stochastics and Test + * Plugin built from the contributed PluginDatas. + *
      + *
    • RegionsPlugin is defaulted to one formed from + * {@link RegionsTestPluginFactory#getStandardRegionsPluginData}
    • + *
    • PeoplePlugin is defaulted to one formed from + * {@link RegionsTestPluginFactory#getStandardPeoplePluginData}
    • + *
    • StochasticsPlugin is defaulted to one formed from + * {@link RegionsTestPluginFactory#getStandardStochasticsPluginData}
    • + *
    • TestPlugin is formed from the TestPluginData passed into + * {@link RegionsTestPluginFactory#factory}
    • + *
    + */ + public List getPlugins() { + List pluginsToAdd = new ArrayList<>(); + + Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(this.data.peoplePluginData); + + Plugin regionsPlugin = RegionsPlugin.builder()// + .setRegionsPluginData(this.data.regionsPluginData)// + .setRegionPropertyReportPluginData(this.data.regionPropertyReportPluginData)// + .setRegionTransferReportPluginData(this.data.regionTransferReportPluginData)// + .getRegionsPlugin(); + + Plugin stochasticPlugin = StochasticsPlugin.getStochasticsPlugin(this.data.stochasticsPluginData); + + Plugin testPlugin = TestPlugin.getTestPlugin(this.data.testPluginData); + + pluginsToAdd.add(peoplePlugin); + pluginsToAdd.add(regionsPlugin); + pluginsToAdd.add(stochasticPlugin); + pluginsToAdd.add(testPlugin); + + return pluginsToAdd; + } + + /** + * Sets the {@link PeoplePluginData} in this Factory. This explicit instance of + * pluginData will be used to create a PeoplePlugin + * + * @throws ContractException {@linkplain PersonError#NULL_PEOPLE_PLUGIN_DATA} if + * the passed in pluginData is null + */ + public Factory setPeoplePluginData(PeoplePluginData peoplePluginData) { + if (peoplePluginData == null) { + throw new ContractException(PersonError.NULL_PEOPLE_PLUGIN_DATA); + } + this.data.peoplePluginData = peoplePluginData; + return this; + } + + /** + * Sets the {@link RegionPropertyReportPluginData} in this Factory. This + * explicit instance of pluginData will be used to create a PeoplePlugin + * + * @throws ContractException {@linkplain RegionError#NULL_REGION_PROPERTY_REPORT_PLUGIN_DATA} + * if the passed in pluginData is null + */ + public Factory setRegionPropertyReportPluginData( + RegionPropertyReportPluginData regionPropertyReportPluginData) { + if (regionPropertyReportPluginData == null) { + throw new ContractException(RegionError.NULL_REGION_PROPERTY_REPORT_PLUGIN_DATA); + } + this.data.regionPropertyReportPluginData = regionPropertyReportPluginData; + return this; + } + + /** + * Sets the {@link RegionTransferReportPluginData} in this Factory. This + * explicit instance of pluginData will be used to create a PeoplePlugin + * + * @throws ContractException {@linkplain RegionError#NULL_REGION_TRANSFER_REPORT_PLUGIN_DATA} + * if the passed in pluginData is null + */ + public Factory setRegionTransferReportPluginData( + RegionTransferReportPluginData regionTransferReportPluginData) { + if (regionTransferReportPluginData == null) { + throw new ContractException(RegionError.NULL_REGION_TRANSFER_REPORT_PLUGIN_DATA); + } + this.data.regionTransferReportPluginData = regionTransferReportPluginData; + return this; + } + + /** + * Sets the {@link RegionsPluginData} in this Factory. This explicit instance of + * pluginData will be used to create a RegionsPlugin + * + * @throws ContractException {@linkplain RegionError#NULL_REGION_PLUGIN_DATA} if + * the passed in pluginData is null + */ + public Factory setRegionsPluginData(RegionsPluginData regionsPluginData) { + if (regionsPluginData == null) { + throw new ContractException(RegionError.NULL_REGION_PLUGIN_DATA); + } + this.data.regionsPluginData = regionsPluginData; + return this; + } + + /** + * Sets the {@link StochasticsPluginData} in this Factory. This explicit + * instance of pluginData will be used to create a StochasticsPlugin + * + * @throws ContractException {@linkplain StochasticsError#NULL_STOCHASTICS_PLUGIN_DATA} + * if the passed in pluginData is null + */ + public Factory setStochasticsPluginData(StochasticsPluginData stochasticsPluginData) { + if (stochasticsPluginData == null) { + throw new ContractException(StochasticsError.NULL_STOCHASTICS_PLUGIN_DATA); + } + this.data.stochasticsPluginData = stochasticsPluginData; + return this; + } + + } + + /** + * Creates a Factory that facilitates the creation of a minimal set of plugins + * needed to adequately test the {@link RegionsPlugin} by generating: + *
      + *
    • {@link RegionsPluginData}
    • + *
    • {@link PeoplePluginData}
    • + *
    • {@link StochasticsPluginData}
    • + *
    + * either directly (by default) via + *
      + *
    • {@link #getStandardPeoplePluginData}
    • + *
    • {@link #getStandardRegionsPluginData}
    • + *
    • {@link #getStandardStochasticsPluginData}
    • + *
    + * or explicitly set via + *
      + *
    • {@link Factory#setPeoplePluginData}
    • + *
    • {@link Factory#setRegionsPluginData}
    • + *
    • {@link Factory#setStochasticsPluginData}
    • + *
    + * via the {@link Factory#getPlugins()} method. + * + * @throws ContractException {@linkplain NucleusError#NULL_PLUGIN_DATA} if + * testPluginData is null + */ + public static Factory factory(int initialPopulation, long seed, boolean trackTimes, TestPluginData testPluginData) { + if (testPluginData == null) { + throw new ContractException(NucleusError.NULL_PLUGIN_DATA); + } + + return new Factory(new Data(initialPopulation, trackTimes, seed, testPluginData)); + + } + + /** + * Creates a Factory that facilitates the creation of a minimal set of plugins + * needed to adequately test the {@link RegionsPlugin} by generating: + *
      + *
    • {@link RegionsPluginData}
    • + *
    • {@link PeoplePluginData}
    • + *
    • {@link StochasticsPluginData}
    • + *
    + * either directly (by default) via + *
      + *
    • {@link #getStandardPeoplePluginData}, + *
    • {@link #getStandardRegionsPluginData}, + *
    • {@link #getStandardStochasticsPluginData}
    • + *
    + * or explicitly set via + *
      + *
    • {@link Factory#setPeoplePluginData}, + *
    • {@link Factory#setRegionsPluginData}, + *
    • {@link Factory#setStochasticsPluginData}
    • + *
    + * via the {@link Factory#getPlugins()} method. + * + * @throws ContractException {@linkplain NucleusError#NULL_ACTOR_CONTEXT_CONSUMER} + * if consumer is null + */ + public static Factory factory(int initialPopulation, long seed, boolean trackTimes, + Consumer consumer) { + if (consumer == null) { + throw new ContractException(NucleusError.NULL_ACTOR_CONTEXT_CONSUMER); + } + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, consumer)); + TestPluginData testPluginData = pluginBuilder.build(); + return factory(initialPopulation, seed, trackTimes, testPluginData); + } + + /** + * Returns a standardized RegionsPluginData that is minimally adequate for + * testing the RegionsPlugin The resulting RegionsPluginData will include: + *
      + *
    • Every RegionId in {@link TestRegionId}
    • + *
    • Every RegionPropertyId in {@link TestRegionPropertyId} + *
        + *
      • along with the propertyDefinition for each.
      • + *
      • If the propertyDefinition has a default value, that value is used. + * Otherwise a randomPropertyValue is set using a RandomGenerator seeded by the + * passed in seed via {@link TestRegionPropertyId#getRandomPropertyValue} + *
      + *
    • the passed in timeTrackingPolicy
    • + *
    • Every person in the passed in list will be added to a RegionId + *
        + *
      • starting with RegionId_1 and looping through all possible RegionIds in + * {@link TestRegionId} + *
      + *
    + */ + public static RegionsPluginData getStandardRegionsPluginData(List people, boolean trackTimes, long seed) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + + RegionsPluginData.Builder regionPluginBuilder = RegionsPluginData.builder(); + for (TestRegionId regionId : TestRegionId.values()) { + regionPluginBuilder.addRegion(regionId); + } + + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.getTestRegionPropertyIds()) { + PropertyDefinition propertyDefinition = testRegionPropertyId.getPropertyDefinition(); + regionPluginBuilder.defineRegionProperty(testRegionPropertyId, propertyDefinition); + boolean hasDeaultValue = propertyDefinition.getDefaultValue().isPresent(); + + if (!hasDeaultValue) { + for (TestRegionId regionId : TestRegionId.values()) { + regionPluginBuilder.setRegionPropertyValue(regionId, testRegionPropertyId, + testRegionPropertyId.getRandomPropertyValue(randomGenerator)); + } + } + } + + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId + .getTestShuffledRegionPropertyIds(randomGenerator)) { + PropertyDefinition propertyDefinition = testRegionPropertyId.getPropertyDefinition(); + boolean hasDeaultValue = propertyDefinition.getDefaultValue().isPresent(); + boolean setValue = randomGenerator.nextBoolean(); + + if (hasDeaultValue && setValue) { + for (TestRegionId regionId : TestRegionId.values()) { + regionPluginBuilder.setRegionPropertyValue(regionId, testRegionPropertyId, + propertyDefinition.getDefaultValue().get()); + } + } else if (setValue) { + for (TestRegionId regionId : TestRegionId.values()) { + regionPluginBuilder.setRegionPropertyValue(regionId, testRegionPropertyId, + testRegionPropertyId.getRandomPropertyValue(randomGenerator)); + } + } + } + + regionPluginBuilder.setPersonRegionArrivalTracking(trackTimes); + TestRegionId testRegionId = TestRegionId.REGION_1; + if (trackTimes) { + for (PersonId personId : people) { + regionPluginBuilder.addPerson(personId, testRegionId, 0.0); + testRegionId = testRegionId.next(); + } + } else { + for (PersonId personId : people) { + regionPluginBuilder.addPerson(personId, testRegionId); + testRegionId = testRegionId.next(); + } + } + + return regionPluginBuilder.build(); + } + + /** + * Returns a standardized PeoplePluginData that is minimally adequate for + * testing the RegionsPlugin The resulting PeoplePluginData will include: + *
      + *
    • a number of people equal to the passed in intialPopulation
    • + *
    + */ + public static PeoplePluginData getStandardPeoplePluginData(int initialPopulation) { + PeoplePluginData.Builder peopleBuilder = PeoplePluginData.builder(); + if (initialPopulation > 0) { + peopleBuilder.addPersonRange(new PersonRange(0, initialPopulation - 1)); + } + return peopleBuilder.build(); + } + + /** + * Returns a standardized StochasticsPluginData that is minimally adequate for + * testing the RegionsPlugin The resulting StochasticsPluginData will include: + *
      + *
    • a seed based on the nextLong of a RandomGenerator seeded from the passed + * in seed
    • + *
    + */ + public static StochasticsPluginData getStandardStochasticsPluginData(long seed) { + WellState wellState = WellState.builder().setSeed(seed).build(); + return StochasticsPluginData.builder().setMainRNGState(wellState).build(); + } +} diff --git a/gcm3/src/main/java/plugins/regions/testsupport/TestRegionId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/testsupport/TestRegionId.java similarity index 87% rename from gcm3/src/main/java/plugins/regions/testsupport/TestRegionId.java rename to gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/testsupport/TestRegionId.java index c7346b99d..3f22a997d 100644 --- a/gcm3/src/main/java/plugins/regions/testsupport/TestRegionId.java +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/testsupport/TestRegionId.java @@ -1,8 +1,8 @@ -package plugins.regions.testsupport; +package gov.hhs.aspr.ms.gcm.plugins.regions.testsupport; import org.apache.commons.math3.random.RandomGenerator; -import plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; /** * Enumeration that identifies region components for all tests diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/testsupport/TestRegionPropertyId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/testsupport/TestRegionPropertyId.java new file mode 100644 index 000000000..12e9a58d0 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/regions/testsupport/TestRegionPropertyId.java @@ -0,0 +1,168 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.testsupport; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; + +/** + * Enumeration that identifies region property definitions + */ +public enum TestRegionPropertyId implements RegionPropertyId { + REGION_PROPERTY_1_BOOLEAN_MUTABLE(PropertyDefinition.builder()// + .setType(Boolean.class)// + .setDefaultValue(false)// + .setPropertyValueMutability(true)// + .build()), // + REGION_PROPERTY_2_INTEGER_MUTABLE(PropertyDefinition.builder()// + .setType(Integer.class)// + // .setDefaultValue(0)//no default value + .setPropertyValueMutability(true)// + .build()// + ), // + REGION_PROPERTY_3_DOUBLE_MUTABLE(PropertyDefinition.builder()// + .setType(Double.class)// + .setDefaultValue(0.0)// + .setPropertyValueMutability(true)// + .build()// + ), // + REGION_PROPERTY_4_BOOLEAN_IMMUTABLE(PropertyDefinition.builder()// + .setType(Boolean.class)// + .setDefaultValue(false)// + .setPropertyValueMutability(false)// + .build()// + ), // + REGION_PROPERTY_5_INTEGER_IMMUTABLE(PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(0)// + .setPropertyValueMutability(false)// + .build()// + ), // + REGION_PROPERTY_6_DOUBLE_IMMUTABLE(PropertyDefinition.builder()// + .setType(Double.class)// + .setDefaultValue(0.0)// + .setPropertyValueMutability(false)// + .build()// + ), // + + ; + + private final PropertyDefinition propertyDefinition; + + private TestRegionPropertyId(PropertyDefinition propertyDefinition) { + this.propertyDefinition = propertyDefinition; + } + + public PropertyDefinition getPropertyDefinition() { + return propertyDefinition; + } + + /** + * Returns a randomly selected member of this enumeration + */ + public static TestRegionPropertyId getRandomRegionPropertyId(final RandomGenerator randomGenerator) { + return TestRegionPropertyId.values()[randomGenerator.nextInt(TestRegionPropertyId.values().length)]; + } + + public static TestRegionPropertyId getRandomMutableRegionPropertyId(final RandomGenerator randomGenerator) { + List candidates = new ArrayList<>(); + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { + if (testRegionPropertyId.getPropertyDefinition().propertyValuesAreMutable()) { + candidates.add(testRegionPropertyId); + } + } + return candidates.get(randomGenerator.nextInt(candidates.size())); + } + + /** + * Return the size of this enum + */ + public static int size() { + return values().length; + } + + /** + * Returns a new {@link RegionPropertyId} instance. + */ + public static RegionPropertyId getUnknownRegionPropertyId() { + return new RegionPropertyId() { + }; + } + + /** + * Returns a randomly selected value that is compatible with this member's + * associated property definition. + */ + @SuppressWarnings("unchecked") + public T getRandomPropertyValue(final RandomGenerator randomGenerator) { + switch (this) { + case REGION_PROPERTY_1_BOOLEAN_MUTABLE: + Boolean b1 = randomGenerator.nextBoolean(); + return (T) b1; + case REGION_PROPERTY_2_INTEGER_MUTABLE: + Integer i2 = randomGenerator.nextInt(); + return (T) i2; + case REGION_PROPERTY_3_DOUBLE_MUTABLE: + Double d3 = randomGenerator.nextDouble(); + return (T) d3; + case REGION_PROPERTY_4_BOOLEAN_IMMUTABLE: + Boolean b4 = randomGenerator.nextBoolean(); + return (T) b4; + case REGION_PROPERTY_5_INTEGER_IMMUTABLE: + Integer i5 = randomGenerator.nextInt(); + return (T) i5; + case REGION_PROPERTY_6_DOUBLE_IMMUTABLE: + Double d6 = randomGenerator.nextDouble(); + return (T) d6; + default: + throw new RuntimeException("unhandled case: " + this); + + } + } + + /** + * Returns the test region property id values associated with property + * definitions that have default values. + */ + public static List getPropertiesWithDefaultValues() { + List result = new ArrayList<>(); + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { + if (testRegionPropertyId.propertyDefinition.getDefaultValue().isPresent()) { + result.add(testRegionPropertyId); + } + } + return result; + } + + /** + * Returns the test region property id values associated with property + * definitions that do not have default values. + */ + public static List getPropertiesWithoutDefaultValues() { + List result = new ArrayList<>(); + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { + if (testRegionPropertyId.propertyDefinition.getDefaultValue().isEmpty()) { + result.add(testRegionPropertyId); + } + } + return result; + } + + public static List getTestRegionPropertyIds() { + return Arrays.asList(TestRegionPropertyId.values()); + } + + public static List getTestShuffledRegionPropertyIds(RandomGenerator randomGenerator) { + List result = getTestRegionPropertyIds(); + Collections.shuffle(result, new Random(randomGenerator.nextLong())); + + return result; + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/ReportsPlugin.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/ReportsPlugin.java new file mode 100644 index 000000000..36a7d7672 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/ReportsPlugin.java @@ -0,0 +1,31 @@ +package gov.hhs.aspr.ms.gcm.plugins.reports; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; + +/** + * A plugin providing a report actors to the simulation. + */ +public class ReportsPlugin { + + private ReportsPlugin() { + } + + /** + * Returns the report plugin. + *

    + * Uses ReportsPluginId.PLUGIN_ID as its id + *

    + *

    + * Depends on no plugins + *

    + *

    + * Provides no actors, reports or data mangers: + *

    + */ + public static Plugin getReportsPlugin() { + return Plugin.builder()// + .setPluginId(ReportsPluginId.PLUGIN_ID)// + .build();// + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/ReportsPluginId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/ReportsPluginId.java new file mode 100644 index 000000000..ff5737aa6 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/ReportsPluginId.java @@ -0,0 +1,13 @@ +package gov.hhs.aspr.ms.gcm.plugins.reports; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; + +/** + * Static plugin id implementation for the ReportsPlugin + */ +public final class ReportsPluginId implements PluginId { + public final static PluginId PLUGIN_ID = new ReportsPluginId(); + + private ReportsPluginId() { + }; +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/ExperimentLineWriter.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/ExperimentLineWriter.java new file mode 100644 index 000000000..790b94e9e --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/ExperimentLineWriter.java @@ -0,0 +1,232 @@ +package gov.hhs.aspr.ms.gcm.plugins.reports.support; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +import gov.hhs.aspr.ms.gcm.nucleus.ExperimentContext; +import gov.hhs.aspr.ms.gcm.nucleus.ScenarioStatus; +import net.jcip.annotations.GuardedBy; +import net.jcip.annotations.ThreadSafe; + +/** + * A thread-safe utility that supports tab delimited text based files that have + * a header. This utility manages the writing of report items to a single file. + * It assumes that all such items share a uniform header and establishes the + * header of the output file on the first report item. If the writer is resuming + * from a previous experiment, the header remains at originally written. + * Supports continuation of experiment progress across multiple experiment runs. + */ +@ThreadSafe +public final class ExperimentLineWriter { + + private static final String lineSeparator = System.getProperty("line.separator"); + private final Object headerLock = new Object(); + private BufferedWriter writer; + private final String delimiter; + + @GuardedBy(value = "headerLock") + private boolean headerWritten; + + /** + * Creates this {@link ExperimentLineWriter} The path to the file that may or + * may not exist and may contain some complete or partial content from a + * previous execution of the experiment. If not empty, this file must have a + * header, be tab delimited and have as its first column be the scenario id. + * Partial lines at the end of the file due to an ungraceful halt to the + * previous execution are tolerated. If the file does not exist, then its parent + * directory must exist. + * + * @throws RuntimeException + *
      + *
    • if an {@link IOException} is thrown during file + * initialization
    • + *
    • if the simulation run is continuing from a + * progress log and the path is not a regular file + * (path does not exist) during file + * initialization
    • + *
    + */ + public ExperimentLineWriter(final ExperimentContext experimentContext, final Path path, String delimiter) { + + if (Files.exists(path)) { + if (!Files.isRegularFile(path)) { + throw new RuntimeException("Non-regular file at: " + path); + } + } + + this.delimiter = delimiter; + + boolean loadedWithPreviousData = !experimentContext.getScenarios(ScenarioStatus.PREVIOUSLY_SUCCEEDED).isEmpty(); + loadedWithPreviousData &= Files.exists(path); + + if (loadedWithPreviousData) { + initializeWithPreviousContent(path, experimentContext); + } else { + initializeWithNoPreviousContent(path); + } + } + + /* + * The path must correspond to an existing regular file. + */ + private void initializeWithPreviousContent(Path path, ExperimentContext experimentContext) { + + try { + + /* + * Remove the old file and write to the file the header and any retained lines + * from the previous execution. + */ + Path tempPath = path.getParent().resolve("temp.txt"); + Files.deleteIfExists(tempPath); + CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder(); + OutputStream out = Files.newOutputStream(tempPath, StandardOpenOption.CREATE); + writer = new BufferedWriter(new OutputStreamWriter(out, encoder)); + Stream lines = Files.lines(path); + boolean[] header = new boolean[] { true }; + lines.forEach((line) -> { + if (!header[0]) { + String[] fields = line.split(delimiter); + /* + * It is possible that the last line of a file was only partially written + * because neither the writer's close or flush was called during an abrupt + * shutdown. We expect that such cases will not correspond to successfully + * completed simulation execution, but must ensure that the parsing of the + * scenario and replication ids can still be performed + */ + if (fields.length > 1) { + int scenarioId = Integer.parseInt(fields[0]); + Optional optional = experimentContext.getScenarioStatus(scenarioId); + if (optional.isPresent() && optional.get().equals(ScenarioStatus.PREVIOUSLY_SUCCEEDED)) { + try { + writer.write(line); + writer.newLine(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + } else { + try { + writer.write(line); + writer.newLine(); + headerWritten = true; + header[0] = false; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + }); + lines.close(); + + writer.close(); + + tempPath.toFile().renameTo(path.toFile()); + + encoder = StandardCharsets.UTF_8.newEncoder(); + out = Files.newOutputStream(path, StandardOpenOption.APPEND); + writer = new BufferedWriter(new OutputStreamWriter(out, encoder)); + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void initializeWithNoPreviousContent(Path path) { + + try { + /* + * Remove the old file and write to the file the header and any retained lines + * from the previous execution. + */ + Files.deleteIfExists(path); + CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder(); + OutputStream out = Files.newOutputStream(path, StandardOpenOption.CREATE); + writer = new BufferedWriter(new OutputStreamWriter(out, encoder)); + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Closes the writer, flushing all buffered outputs. + * + * @throws RuntimeException if an {@link IOException} is thrown + */ + public void close() { + + try { + writer.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Writes the report item to file recorded under the given scenario. + * + * @throws RuntimeException if an {@link IOException} is thrown + */ + public void write(ExperimentContext experimentContext, int scenarioId) { + + try { + synchronized (headerLock) { + if (!headerWritten) { + final StringBuilder sb = new StringBuilder(); + + sb.append("scenario"); + + for (String item : experimentContext.getExperimentMetaData()) { + sb.append(delimiter); + sb.append(item); + } + + sb.append(lineSeparator); + writer.write(sb.toString()); + headerWritten = true; + } + } + + final StringBuilder sb = new StringBuilder(); + + sb.append(scenarioId); + + List metaData = experimentContext.getScenarioMetaData(scenarioId); + for (String item : metaData) { + sb.append(delimiter); + sb.append(item); + } + + sb.append(lineSeparator); + writer.write(sb.toString()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Flushes buffered output. Generally used to force the last the full reporting + * of a closed scenario. + * + * @throws RuntimeException if an {@link IOException} is thrown + */ + public void flush() { + try { + writer.flush(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/LineWriter.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/LineWriter.java new file mode 100644 index 000000000..ee6fa9cfb --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/LineWriter.java @@ -0,0 +1,248 @@ +package gov.hhs.aspr.ms.gcm.plugins.reports.support; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +import gov.hhs.aspr.ms.gcm.nucleus.ExperimentContext; +import gov.hhs.aspr.ms.gcm.nucleus.ScenarioStatus; +import net.jcip.annotations.GuardedBy; +import net.jcip.annotations.ThreadSafe; + +/** + * A thread-safe utility that supports tab delimited text based files that have + * a header. This utility manages the writing of report items to a single file. + * It assumes that all such items share a uniform header and establishes the + * header of the output file on the first report item. If the writer is resuming + * from a previous experiment, the header remains at originally written. + * Supports continuation of experiment progress across multiple experiment runs. + */ +@ThreadSafe +public final class LineWriter { + + private final boolean useExperimentColumns; + private static final String lineSeparator = System.getProperty("line.separator"); + private final Object headerLock = new Object(); + private BufferedWriter writer; + private final String delimiter; + + @GuardedBy(value = "headerLock") + private boolean headerWritten; + + /** + * Creates this {@link LineWriter} The path to the file that may or may not + * exist and may contain some complete or partial content from a previous + * execution of the experiment. If not empty, this file must have a header, be + * tab delimited and have as its first column be the scenario id. Partial lines + * at the end of the file due to an ungraceful halt to the previous execution + * are tolerated. If the file does not exist, then its parent directory must + * exist. + * + * @throws RuntimeException + *
      + *
    • if an {@link IOException} is thrown during file + * initialization
    • + *
    • if the simulation run is continuing from a + * progress log and the path is not a regular file + * (path does not exist) during file + * initialization
    • + *
    + */ + public LineWriter(final ExperimentContext experimentContext, final Path path, + final boolean displayExperimentColumnsInReports, String delimiter) { + + if (Files.exists(path)) { + if (!Files.isRegularFile(path)) { + throw new RuntimeException("Non-regular file at: " + path); + } + } + + this.delimiter = delimiter; + this.useExperimentColumns = displayExperimentColumnsInReports; + + boolean loadedWithPreviousData = !experimentContext.getScenarios(ScenarioStatus.PREVIOUSLY_SUCCEEDED).isEmpty(); + loadedWithPreviousData &= Files.exists(path); + + if (loadedWithPreviousData) { + initializeWithPreviousContent(path, experimentContext); + } else { + initializeWithNoPreviousContent(path); + } + } + + /* + * The path must correspond to an existing regular file. + */ + private void initializeWithPreviousContent(Path path, ExperimentContext experimentContext) { + + try { + + /* + * Remove the old file and write to the file the header and any retained lines + * from the previous execution. + */ + Path tempPath = path.getParent().resolve("temp.txt"); + Files.deleteIfExists(tempPath); + CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder(); + OutputStream out = Files.newOutputStream(tempPath, StandardOpenOption.CREATE); + writer = new BufferedWriter(new OutputStreamWriter(out, encoder)); + Stream lines = Files.lines(path); + boolean[] header = new boolean[] { true }; + lines.forEach((line) -> { + if (!header[0]) { + String[] fields = line.split(delimiter); + /* + * It is possible that the last line of a file was only partially written + * because neither the writer's close or flush was called during an abrupt + * shutdown. We expect that such cases will not correspond to successfully + * completed simulation execution, but must ensure that the parsing of the + * scenario and replication ids can still be performed + */ + if (fields.length > 1) { + int scenarioId = Integer.parseInt(fields[0]); + Optional optional = experimentContext.getScenarioStatus(scenarioId); + if (optional.isPresent() && optional.get().equals(ScenarioStatus.PREVIOUSLY_SUCCEEDED)) { + try { + writer.write(line); + writer.newLine(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + } else { + try { + writer.write(line); + writer.newLine(); + headerWritten = true; + header[0] = false; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + }); + lines.close(); + + writer.close(); + + tempPath.toFile().renameTo(path.toFile()); + + encoder = StandardCharsets.UTF_8.newEncoder(); + out = Files.newOutputStream(path, StandardOpenOption.APPEND); + writer = new BufferedWriter(new OutputStreamWriter(out, encoder)); + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void initializeWithNoPreviousContent(Path path) { + + try { + /* + * Remove the old file and write to the file the header and any retained lines + * from the previous execution. + */ + Files.deleteIfExists(path); + CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder(); + OutputStream out = Files.newOutputStream(path, StandardOpenOption.CREATE); + writer = new BufferedWriter(new OutputStreamWriter(out, encoder)); + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Closes the writer, flushing all buffered outputs. + * + * @throws RuntimeException if an {@link IOException} is thrown + */ + public void close() { + + try { + writer.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Writes the report item to file recorded under the given scenario. + * + * @throws RuntimeException if an {@link IOException} is thrown + */ + public void write(ExperimentContext experimentContext, int scenarioId, ReportItem reportItem) { + + try { + synchronized (headerLock) { + if (!headerWritten) { + final StringBuilder sb = new StringBuilder(); + + sb.append("scenario"); + + if (useExperimentColumns) { + for (String item : experimentContext.getExperimentMetaData()) { + sb.append(delimiter); + sb.append(item); + } + } + + final List headerStrings = reportItem.getReportHeader().getHeaderStrings(); + for (final String headerString : headerStrings) { + sb.append(delimiter); + sb.append(headerString); + } + + sb.append(lineSeparator); + writer.write(sb.toString()); + headerWritten = true; + } + } + + final StringBuilder sb = new StringBuilder(); + + sb.append(scenarioId); + if (useExperimentColumns) { + List metaData = experimentContext.getScenarioMetaData(scenarioId); + for (String item : metaData) { + sb.append(delimiter); + sb.append(item); + } + } + + for (int i = 0; i < reportItem.size(); i++) { + sb.append(delimiter); + sb.append(reportItem.getValue(i)); + } + sb.append(lineSeparator); + writer.write(sb.toString()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Flushes buffered output. Generally used to force the last the full reporting + * of a closed scenario. + * + * @throws RuntimeException if an {@link IOException} is thrown + */ + public void flush() { + try { + writer.flush(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/NIOReportItemHandler.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/NIOReportItemHandler.java new file mode 100644 index 000000000..2decd4621 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/NIOReportItemHandler.java @@ -0,0 +1,223 @@ +package gov.hhs.aspr.ms.gcm.plugins.reports.support; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Consumer; + +import gov.hhs.aspr.ms.gcm.nucleus.ExperimentContext; +import util.errors.ContractException; + +/** + * An experiment-level output management utility for writing report items to + * multiple files. + */ +public final class NIOReportItemHandler implements Consumer { + + public static Builder builder() { + return new Builder(); + } + + /** + * Builder class for NIOReportItemHandlerImpl + */ + public static class Builder { + + private Builder() { + } + + private Data data = new Data(); + + /** + * Add a report by class reference to the NIOReportItemHandler + * + * @throws ContractException + *
      + *
    • {@linkplain ReportError#NULL_REPORT_LABEL} if + * the report label is null
    • + *
    • {@linkplain ReportError#NULL_REPORT_PATH} if + * the path is null
    • + *
    + */ + public Builder addReport(final ReportLabel reportLabel, final Path path) { + if (path == null) { + throw new ContractException(ReportError.NULL_REPORT_PATH); + } + if (reportLabel == null) { + throw new ContractException(ReportError.NULL_REPORT_LABEL); + } + data.reportMap.put(reportLabel, path); + return this; + } + + public Builder addExperimentReport(final Path path) { + if (path == null) { + throw new ContractException(ReportError.NULL_REPORT_PATH); + } + data.experimentReportPath = path; + return this; + } + + private void validate() { + /* + * Ensure that each path is associated with exactly one report label + */ + final Map pathMap = new LinkedHashMap<>(); + for (final ReportLabel reportLabel : data.reportMap.keySet()) { + final Path path = data.reportMap.get(reportLabel); + if (pathMap.containsKey(path)) { + throw new ContractException(ReportError.PATH_COLLISION, path); + } + pathMap.put(path, reportLabel); + } + + } + + /** + * Builds the NIOReportItemHandlerImpl from the information gathered and resets + * the internal state of this builder. + * + * @throws ContractException {@linkplain ReportError#PATH_COLLISION} if multiple + * reports are assigned the same path + */ + public NIOReportItemHandler build() { + validate(); + return new NIOReportItemHandler(new Data(data)); + } + + /** + * Sets the display of experiment columns in all reports. Default value is true. + */ + public Builder setDisplayExperimentColumnsInReports(final boolean displayExperimentColumnsInReports) { + data.displayExperimentColumnsInReports = displayExperimentColumnsInReports; + return this; + } + + /** + * Sets the delimiter for an experiment. + */ + public Builder setDelimiter(String delimiter) { + data.delimiter = delimiter; + return this; + } + } + + private static class Data { + private String delimiter = "\t"; + private Path experimentReportPath; + private final Map reportMap = new LinkedHashMap<>(); + private boolean displayExperimentColumnsInReports = DEFAULT_DISPLAY_EXPERIMENT_COLUMNS; + + public Data() { + } + + public Data(Data data) { + delimiter = data.delimiter; + reportMap.putAll(data.reportMap); + experimentReportPath = data.experimentReportPath; + displayExperimentColumnsInReports = data.displayExperimentColumnsInReports; + } + + } + + private final static boolean DEFAULT_DISPLAY_EXPERIMENT_COLUMNS = true; + + private final Map lineWriterMap = Collections.synchronizedMap(new LinkedHashMap<>()); + + private ExperimentLineWriter experimentLineWriter; + + private final Map reportMap; + + private final String delimiter; + + private final boolean displayExperimentColumnsInReports; + + private final Path experimentReportPath; + + private NIOReportItemHandler(final Data data) { + delimiter = data.delimiter; + reportMap = data.reportMap; + experimentReportPath = data.experimentReportPath; + displayExperimentColumnsInReports = data.displayExperimentColumnsInReports; + } + + private void closeExperiment(ExperimentContext experimentContext) { + synchronized (lineWriterMap) { + for (final LineWriter lineWriter : lineWriterMap.values()) { + lineWriter.close(); + } + if (experimentLineWriter != null) { + experimentLineWriter.close(); + } + } + } + + private void closeSimulation(ExperimentContext experimentContext, final Integer scenarioId) { + synchronized (lineWriterMap) { + for (final LineWriter lineWriter : lineWriterMap.values()) { + lineWriter.flush(); + } + if (experimentLineWriter != null) { + experimentLineWriter.write(experimentContext, scenarioId); + experimentLineWriter.flush(); + } + } + } + + private void handleOutput(ExperimentContext experimentContext, Integer scenarioId, ReportItem reportItem) { + final LineWriter lineWriter = lineWriterMap.get(reportItem.getReportLabel()); + if (lineWriter != null) { + lineWriter.write(experimentContext, scenarioId, reportItem); + } + } + + private void openExperiment(ExperimentContext experimentContext) { + synchronized (lineWriterMap) { + for (final ReportLabel reportLabel : reportMap.keySet()) { + final Path path = reportMap.get(reportLabel); + final LineWriter lineWriter = new LineWriter(experimentContext, path, displayExperimentColumnsInReports, + delimiter); + lineWriterMap.put(reportLabel, lineWriter); + } + if (experimentReportPath != null) { + this.experimentLineWriter = new ExperimentLineWriter(experimentContext, experimentReportPath, + delimiter); + } + } + } + + /** + * Initializes this report item handler. It subscribes to the following + * experiment level events: + *
      + *
    • Experiment Open : Reads and initializes all report files. All content + * that doesn't correspond to a previously fully executed scenario is + * removed.
    • + *
    • Simulation Output : directs report items to the appropriate file + * writer
    • + *
    • Simulation Close : ensures all files are flushed so that the content of + * each file is complete for each closed scenario
    • + *
    • Experiment Close : closes all file writers
    • + *
    + * + * @throws RuntimeException + *
      + *
    • if an {@link IOException} is thrown during file + * initialization
    • + *
    • if the simulation run is continuing from a + * progress log and the path is not a regular file + * (path does not exist) during file + * initialization
    • + *
    + */ + @Override + public void accept(ExperimentContext experimentContext) { + experimentContext.subscribeToExperimentOpen(this::openExperiment); + experimentContext.subscribeToExperimentClose(this::closeExperiment); + experimentContext.subscribeToSimulationClose(this::closeSimulation); + experimentContext.subscribeToOutput(ReportItem.class, this::handleOutput); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/PeriodicReport.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/PeriodicReport.java new file mode 100644 index 000000000..90d0c96f9 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/PeriodicReport.java @@ -0,0 +1,198 @@ +package gov.hhs.aspr.ms.gcm.plugins.reports.support; + +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import util.errors.ContractException; + +/** + * The abstract base class for reports that aggregate reporting aligned to a + * {@link ReportPeriod}. The periodic report continually schedules reporting on + * a regular cycle, invoking the flush method and allowing descendant + * implementors to release collected data. This report registers via the context + * to be alerted when the simulation terminates and will perform a final flush + * invocation if the report was not already flushed at that time. + */ +public abstract class PeriodicReport { + + /** + * Creates the periodic report from the given report period + * + * @throws ContractException + *
      + *
    • {@linkplain ReportError#NULL_REPORT_PERIOD} if + * the report period is null
    • + *
    • {@linkplain ReportError#NULL_REPORT_LABEL} if + * the report period is null
    • + *
    + */ + public PeriodicReport(ReportLabel reportLabel, ReportPeriod reportPeriod) { + if (reportPeriod == null) { + throw new ContractException(ReportError.NULL_REPORT_PERIOD); + } + this.reportPeriod = reportPeriod; + + if (reportLabel == null) { + throw new ContractException(ReportError.NULL_REPORT_LABEL); + } + this.reportLabel = reportLabel; + } + + private ReportPeriod reportPeriod; + + private ReportLabel reportLabel; + + /* + * The day value to be used in report lines + */ + private Integer reportingDay = 0; + + /* + * The hour value to be used in report lines + */ + private Integer reportingHour = 0; + + /** + * Adds the time field column(s) to the given {@link ReportHeader.Builder} as + * appropriate to the {@link ReportPeriod} specified during construction. DAILY + * : Day HOURLY : Day, Hour END_OF_SIMULATION has no header additions + */ + protected final ReportHeader.Builder addTimeFieldHeaders(ReportHeader.Builder reportHeaderBuilder) { + switch (reportPeriod) { + case DAILY: + reportHeaderBuilder.add("day"); + break; + case END_OF_SIMULATION: + // do nothing + break; + case HOURLY: + reportHeaderBuilder.add("day"); + reportHeaderBuilder.add("hour"); + break; + default: + throw new RuntimeException("unknown report period " + reportPeriod); + } + return reportHeaderBuilder; + } + + protected final ReportLabel getReportLabel() { + return reportLabel; + } + + protected final ReportPeriod getReportPeriod() { + return reportPeriod; + } + + /** + * Places the current reporting day and report hour on the report as appropriate + * to the {@link ReportPeriod} specified during construction. + */ + protected final void fillTimeFields(final ReportItem.Builder reportItemBuilder) { + + switch (reportPeriod) { + case DAILY: + reportItemBuilder.addValue(reportingDay); + break; + case END_OF_SIMULATION: + // do nothing + break; + case HOURLY: + reportItemBuilder.addValue(reportingDay); + reportItemBuilder.addValue(reportingHour); + break; + default: + throw new RuntimeException("unknown report period " + reportPeriod); + } + } + + /** + * Subscribes to simulation close. Initializes periodic flushing of report + * contents with the first flush scheduled for one time period from simulation + * start. Descendant implementors of PeriodicReport must invoke super.init(). + * + * @throws ContractException if the report context is null + */ + public final void init(ReportContext reportContext) { + + if (reportContext == null) { + throw new ContractException(ReportError.NULL_CONTEXT); + } + + reportContext.subscribeToSimulationClose(this::close); + + reportingDay = (int) reportContext.getTime(); + reportingHour = (int) (24 * (reportContext.getTime() - reportingDay)); + if (reportingHour > 23) { + reportingHour = 23; + } + + prepare(reportContext); + + if (reportContext.getTime() == 0) { + if (reportPeriod != ReportPeriod.END_OF_SIMULATION) { + flush(reportContext); + incrementReportingTimeFields(); + reportContext.addPlan(this::executePlan, getNextPlanTime()); + } + } + } + + /** + * Called by the init() to allow descendant report classes to initialize. The + * init() will invoke a flush() command after the prepare() + */ + protected void prepare(ReportContext reportContext) { + } + + private void close(final ReportContext reportContext) { + if (lastFlushTime == null || reportContext.getTime() > lastFlushTime) { + flush(reportContext); + } + } + + /** + * Provides descendant implementors the opportunity to releases report items + * from the data stored during the time since the last invocation of flush(). + */ + protected abstract void flush(final ReportContext reportContext); + + private Double lastFlushTime; + + private double getNextPlanTime() { + switch (reportPeriod) { + case DAILY: + return reportingDay; + + case HOURLY: + return reportingDay + (double) (reportingHour) / 24; + + default: + throw new RuntimeException("unhandled report period " + reportPeriod); + } + } + + private void incrementReportingTimeFields() { + switch (reportPeriod) { + case DAILY: + reportingDay++; + break; + case HOURLY: + reportingHour++; + if (reportingHour == 24) { + reportingHour = 0; + reportingDay++; + } + break; + case END_OF_SIMULATION: + // do nothing + break; + default: + throw new RuntimeException("unhandled report period " + reportPeriod); + } + } + + private void executePlan(final ReportContext reportContext) { + lastFlushTime = reportContext.getTime(); + flush(reportContext); + incrementReportingTimeFields(); + reportContext.addPlan(this::executePlan, getNextPlanTime()); + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/PeriodicReportPluginData.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/PeriodicReportPluginData.java new file mode 100644 index 000000000..789fe25a9 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/PeriodicReportPluginData.java @@ -0,0 +1,158 @@ +package gov.hhs.aspr.ms.gcm.plugins.reports.support; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.PluginDataBuilder; +import util.errors.ContractException; + +public abstract class PeriodicReportPluginData implements PluginData { + + protected final Data data; + + protected PeriodicReportPluginData(Data data) { + validateBaseData(data); + this.data = data; + } + + /* + * Data class for collecting the inputs to the report + */ + protected static class Data { + public ReportLabel reportLabel; + public ReportPeriod reportPeriod; + + public Data() { + } + + public Data(Data data) { + reportLabel = data.reportLabel; + reportPeriod = data.reportPeriod; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((reportLabel == null) ? 0 : reportLabel.hashCode()); + result = prime * result + ((reportPeriod == null) ? 0 : reportPeriod.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + if (reportLabel == null) { + if (other.reportLabel != null) { + return false; + } + } else if (!reportLabel.equals(other.reportLabel)) { + return false; + } + if (reportPeriod != other.reportPeriod) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Data [reportLabel="); + builder.append(reportLabel); + builder.append(", reportPeriod="); + builder.append(reportPeriod); + return builder.toString(); + } + } + + /** + * Builder class for the report + */ + public static abstract class Builder implements PluginDataBuilder { + + protected Data data; + + protected Builder(Data data) { + this.data = data; + } + + public abstract PluginData build(); + + /** + * Sets the report label + * + * @throws ContractException {@linkplain ReportError#NULL_REPORT_LABEL} if the + * report label is null + */ + public Builder setReportLabel(ReportLabel reportLabel) { + if (reportLabel == null) { + throw new ContractException(ReportError.NULL_REPORT_LABEL); + } + data.reportLabel = reportLabel; + return this; + } + + /** + * Sets the report period id + * + * @throws ContractException {@linkplain ReportError#NULL_REPORT_PERIOD} if the + * report period is null + */ + public Builder setReportPeriod(ReportPeriod reportPeriod) { + if (reportPeriod == null) { + throw new ContractException(ReportError.NULL_REPORT_PERIOD); + } + data.reportPeriod = reportPeriod; + return this; + } + } + + protected final void validateBaseData(Data data) { + if (data.reportLabel == null) { + throw new ContractException(ReportError.NULL_REPORT_LABEL); + } + if (data.reportPeriod == null) { + throw new ContractException(ReportError.NULL_REPORT_PERIOD); + } + } + + public ReportLabel getReportLabel() { + return data.reportLabel; + } + + public ReportPeriod getReportPeriod() { + return data.reportPeriod; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof PeriodicReportPluginData)) { + return false; + } + PeriodicReportPluginData other = (PeriodicReportPluginData) obj; + if (data == null) { + if (other.data != null) { + return false; + } + } else if (!data.equals(other.data)) { + return false; + } + return true; + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/ReportError.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/ReportError.java new file mode 100644 index 000000000..97bd9b2cd --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/ReportError.java @@ -0,0 +1,30 @@ +package gov.hhs.aspr.ms.gcm.plugins.reports.support; + +import util.errors.ContractError; +import util.errors.ContractException; + +/** + * An enumeration supporting {@link ContractException} that acts as a general + * description of the exception. + */ +public enum ReportError implements ContractError { + NULL_CONSUMER("Supplier of Consumer of ActorContext supplied a null consumer"), + NULL_SUPPLIER("Supplier of Consumer of ActorContext is null"), NULL_REPORT_LABEL("Null report label"), + NULL_REPORT_PATH("Null report path"), PATH_COLLISION("Report path shared between multiple reports"), + UNKNOWN_REPORT_LABEL("Unknown report label"), NULL_REPORT_INITIAL_DATA("Null report initial data"), + DUPLICATE_REPORT_LABEL("Duplicate report label"), NULL_CONTEXT("Null context"), + NULL_REPORT_PERIOD("Null report period"), NULL_REPORT_HEADER_STRING("Null report header string"), + NULL_REPORT_HEADER("Null report header"), NULL_REPORT_ITEM_ENTRY("Null report item entry"), + UNSUPPORTED_REPORT_PERIOD("Unsupported report period"); + + @Override + public String getDescription() { + return description; + } + + private final String description; + + private ReportError(String description) { + this.description = description; + } +} diff --git a/gcm3/src/main/java/plugins/reports/support/ReportHeader.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/ReportHeader.java similarity index 83% rename from gcm3/src/main/java/plugins/reports/support/ReportHeader.java rename to gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/ReportHeader.java index 5f45f3307..8c9436da4 100644 --- a/gcm3/src/main/java/plugins/reports/support/ReportHeader.java +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/ReportHeader.java @@ -1,4 +1,4 @@ -package plugins.reports.support; +package gov.hhs.aspr.ms.gcm.plugins.reports.support; import java.util.ArrayList; import java.util.List; @@ -10,9 +10,6 @@ /** * An immutable, ordered container for the string values in the header of a * report. Constructed via the contained builder class. - * - * @author Shawn Hatch - * */ @Immutable public final class ReportHeader { @@ -32,9 +29,6 @@ public static Builder builder() { /** * Builder class for ReportHeader - * - * @author Shawn Hatch - * */ @NotThreadSafe public final static class Builder { @@ -48,9 +42,8 @@ private Builder() { /** * Add a string to the list of strings in the header in the order added. * - * @throws ContractException - *
  • {@linkplain ReportError#NULL_REPORT_HEADER_STRING} if the - * header string is null
  • + * @throws ContractException {@linkplain ReportError#NULL_REPORT_HEADER_STRING} + * if the header string is null */ public Builder add(String headerString) { if (headerString == null) { @@ -61,15 +54,11 @@ public Builder add(String headerString) { } /** - * Returns a report header from the collected header strings. Clears the - * state of the builder. + * Returns a report header from the collected header strings. Clears the state + * of the builder. */ public ReportHeader build() { - try { - return new ReportHeader(headerStrings); - } finally { - headerStrings = new ArrayList<>(); - } + return new ReportHeader(headerStrings); } } @@ -81,10 +70,8 @@ public List getHeaderStrings() { } /** - * String representation that preserves the order of the added strings - * presented as: - * - * ReportHeader [headerStrings=[string1, string2...] + * String representation that preserves the order of the added strings presented + * as: ReportHeader [headerStrings=[string1, string2...] */ @Override public String toString() { diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/ReportItem.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/ReportItem.java new file mode 100644 index 000000000..a7acdba5a --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/ReportItem.java @@ -0,0 +1,261 @@ +package gov.hhs.aspr.ms.gcm.plugins.reports.support; + +import java.util.ArrayList; +import java.util.List; + +import net.jcip.annotations.NotThreadSafe; +import net.jcip.annotations.ThreadSafe; +import util.errors.ContractException; + +/** + * A thread safe(immutable), container that supports output lines for multiple + * reports. The values contained in a report item should be immutable and + * support toString(). + */ +@ThreadSafe +public final class ReportItem { + + /** + * Returns a new Builder instance. + */ + public static Builder builder() { + return new Builder(); + } + + @NotThreadSafe + public final static class Builder { + private Builder() { + } + + private Data data = new Data(); + + /** + * Adds an entry's string value to the report item. Order should follow the + * order in the {@link ReportHeader} + * + * @throws ContractException if the entry is null + */ + public Builder addValue(final Object entry) { + if (entry == null) { + throw new ContractException(ReportError.NULL_REPORT_ITEM_ENTRY); + } + data.values.add(entry.toString()); + return this; + } + + /* + * Null checks for the various fields. + */ + private void validateData() { + + if (data.reportHeader == null) { + throw new ContractException(ReportError.NULL_REPORT_HEADER); + } + + if (data.reportLabel == null) { + throw new ContractException(ReportError.NULL_REPORT_LABEL); + } + + } + + /** + * Builds the {@link ReportItem} from the colleced data. + * + * @throws ContractException + *
      + *
    • {@linkplain ReportError#NULL_REPORT_HEADER} if + * the collected report header is null
    • + *
    • {@linkplain ReportError#NULL_REPORT_LABEL} if + * the collected report label is null
    • + *
    + */ + public ReportItem build() { + validateData(); + return new ReportItem(new Data(data)); + } + + /** + * Sets the associated {@link ReportHeader} for this {@link ReportItem}. The + * report header and the report item should have the same order of added fiels + * values. + */ + public Builder setReportHeader(ReportHeader reportHeader) { + if (reportHeader == null) { + throw new ContractException(ReportError.NULL_REPORT_HEADER); + } + data.reportHeader = reportHeader; + return this; + } + + /** + * Sets the report type for this {@link ReportItem}. The report type should be + * the class type of the report that authors the report item. + */ + public Builder setReportLabel(ReportLabel reportLabel) { + if (reportLabel == null) { + throw new ContractException(ReportError.NULL_REPORT_LABEL); + } + data.reportLabel = reportLabel; + return this; + } + + } + + private static class Data { + private ReportLabel reportLabel; + private ReportHeader reportHeader; + private final List values = new ArrayList<>(); + + public Data() { + + } + + public Data(Data data) { + reportLabel = data.reportLabel; + reportHeader = data.reportHeader; + values.addAll(data.values); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((reportHeader == null) ? 0 : reportHeader.hashCode()); + result = prime * result + ((reportLabel == null) ? 0 : reportLabel.hashCode()); + result = prime * result + ((values == null) ? 0 : values.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + if (reportHeader == null) { + if (other.reportHeader != null) { + return false; + } + } else if (!reportHeader.equals(other.reportHeader)) { + return false; + } + if (reportLabel == null) { + if (other.reportLabel != null) { + return false; + } + } else if (!reportLabel.equals(other.reportLabel)) { + return false; + } + if (values == null) { + if (other.values != null) { + return false; + } + } else if (!values.equals(other.values)) { + return false; + } + return true; + } + } + + private final Data data; + + private ReportItem(final Data data) { + this.data = data; + } + + /** + * Returns the report label for this report item + */ + public ReportLabel getReportLabel() { + return data.reportLabel; + } + + /** + * Returns the report header for this report item + */ + public ReportHeader getReportHeader() { + return data.reportHeader; + } + + /** + * Returns the string value stored at the given index + * + * @throws IndexOutOfBoundsException + *
      + *
    • if the index < 0
    • + *
    • if the index >= size()
    • + *
    + */ + public String getValue(final int index) { + return data.values.get(index); + } + + /** + * Returns the number of values stored in this report item + * + * @return + */ + public int size() { + return data.values.size(); + } + + /** + * A string listing the values as added to this ReportItem delimited by commas + * in the form: ReportItem + * [reportType=reportType,reportHeader=reportHeader,values=[value1, value2...]] + */ + @Override + public String toString() { + StringBuilder builder2 = new StringBuilder(); + builder2.append("ReportItem [reportLabel="); + builder2.append(data.reportLabel); + builder2.append(", reportHeader="); + builder2.append(data.reportHeader); + builder2.append(", values="); + builder2.append(data.values); + builder2.append("]"); + return builder2.toString(); + } + + /** + * Returns the values in the form [value[0], value[1], ... ,value[N-1]] + */ + public String toValueString() { + return data.values.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + return result; + } + + /** + * Two report items are equal iff and only if their ids, headers and ordered + * values are equal. + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof ReportItem)) { + return false; + } + ReportItem other = (ReportItem) obj; + if (data == null) { + if (other.data != null) { + return false; + } + } else if (!data.equals(other.data)) { + return false; + } + return true; + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/ReportLabel.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/ReportLabel.java new file mode 100644 index 000000000..8ca83d97b --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/ReportLabel.java @@ -0,0 +1,20 @@ +package gov.hhs.aspr.ms.gcm.plugins.reports.support; + +import net.jcip.annotations.ThreadSafe; + +/** + * Marker interface for the unique report labels. Report items are marked with a + * report label that allows an output manager to determine the final disposition + * of the report item. Report labels must be thread-safe. + */ +@ThreadSafe +public interface ReportLabel { + @Override + public int hashCode(); + + @Override + public boolean equals(Object obj); + + @Override + public String toString(); +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/ReportPeriod.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/ReportPeriod.java new file mode 100644 index 000000000..7adc85df0 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/ReportPeriod.java @@ -0,0 +1,9 @@ +package gov.hhs.aspr.ms.gcm.plugins.reports.support; + +/** + * An enumeration supporting {@link PeriodicReport} that represents the + * periodicity of the report. + */ +public enum ReportPeriod { + HOURLY, DAILY, END_OF_SIMULATION +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/SimpleReportLabel.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/SimpleReportLabel.java new file mode 100644 index 000000000..bb3e6bcfb --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/SimpleReportLabel.java @@ -0,0 +1,68 @@ +package gov.hhs.aspr.ms.gcm.plugins.reports.support; + +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * A convenience implementor of ReportLabel that wraps a value. + */ +@Immutable +public final class SimpleReportLabel implements ReportLabel { + + private final Object value; + + /** + * Creates a ReportLabel from a value. The value must implement a proper equals + * contract and be immutable. + * + * @throws ContractException {@linkplain ReportError#NULL_REPORT_LABEL} if the + * value is null + */ + public SimpleReportLabel(Object value) { + if (value == null) { + throw new ContractException(ReportError.NULL_REPORT_LABEL); + } + this.value = value; + } + + public Object getValue() { + return this.value; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("SimpleReportLabel [value="); + builder.append(value); + builder.append("]"); + return builder.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((value == null) ? 0 : value.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof SimpleReportLabel)) { + return false; + } + SimpleReportLabel other = (SimpleReportLabel) obj; + if (value == null) { + if (other.value != null) { + return false; + } + } else if (!value.equals(other.value)) { + return false; + } + return true; + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/ResourcesPlugin.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/ResourcesPlugin.java new file mode 100644 index 000000000..4ed5fb7e2 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/ResourcesPlugin.java @@ -0,0 +1,127 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources; + +import java.util.Optional; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePluginId; +import gov.hhs.aspr.ms.gcm.plugins.regions.RegionsPluginId; +import gov.hhs.aspr.ms.gcm.plugins.resources.datamanagers.ResourcesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.resources.datamanagers.ResourcesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.resources.reports.PersonResourceReport; +import gov.hhs.aspr.ms.gcm.plugins.resources.reports.PersonResourceReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.resources.reports.ResourcePropertyReport; +import gov.hhs.aspr.ms.gcm.plugins.resources.reports.ResourcePropertyReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.resources.reports.ResourceReport; +import gov.hhs.aspr.ms.gcm.plugins.resources.reports.ResourceReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceError; +import util.errors.ContractException; + +public final class ResourcesPlugin { + + private static class Data { + private ResourcesPluginData resourcesPluginData; + private PersonResourceReportPluginData personResourceReportPluginData; + private ResourcePropertyReportPluginData resourcePropertyReportPluginData; + private ResourceReportPluginData resourceReportPluginData; + } + + private ResourcesPlugin() { + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Builder() { + } + + private Data data = new Data(); + + private void validate() { + if (data.resourcesPluginData == null) { + throw new ContractException(ResourceError.NULL_RESOURCE_PLUGIN_DATA); + } + } + + /** + * Builds the PersonPropertiesPlugin from the collected inputs + * + * @throws ContractException {@linkplain ResourceError#NULL_RESOURCE_PLUGIN_DATA} + * if the personPropertiesPluginData is null + */ + public Plugin getResourcesPlugin() { + + validate(); + Plugin.Builder builder = Plugin.builder();// + builder.setPluginId(ResourcesPluginId.PLUGIN_ID);// + + builder.addPluginData(data.resourcesPluginData);// + + if (data.personResourceReportPluginData != null) { + builder.addPluginData(data.personResourceReportPluginData);// + } + if (data.resourcePropertyReportPluginData != null) { + builder.addPluginData(data.resourcePropertyReportPluginData);// + } + if (data.resourceReportPluginData != null) { + builder.addPluginData(data.resourceReportPluginData);// + } + + builder.addPluginDependency(PeoplePluginId.PLUGIN_ID);// + builder.addPluginDependency(RegionsPluginId.PLUGIN_ID);// + + builder.setInitializer((c) -> { + + ResourcesPluginData pluginData = c.getPluginData(ResourcesPluginData.class).get(); + c.addDataManager(new ResourcesDataManager(pluginData)); + + Optional optional1 = c + .getPluginData(PersonResourceReportPluginData.class); + if (optional1.isPresent()) { + PersonResourceReportPluginData personResourceReportPluginData = optional1.get(); + c.addReport(new PersonResourceReport(personResourceReportPluginData)::init); + } + + Optional optional2 = c + .getPluginData(ResourcePropertyReportPluginData.class); + if (optional2.isPresent()) { + ResourcePropertyReportPluginData resourcePropertyReportPluginData = optional2.get(); + c.addReport(new ResourcePropertyReport(resourcePropertyReportPluginData)::init); + } + + Optional optional3 = c.getPluginData(ResourceReportPluginData.class); + if (optional3.isPresent()) { + ResourceReportPluginData resourceReportPluginData = optional3.get(); + c.addReport(new ResourceReport(resourceReportPluginData)::init); + } + + }); + return builder.build(); + + } + + public Builder setResourcesPluginData(ResourcesPluginData resourcesPluginData) { + data.resourcesPluginData = resourcesPluginData; + return this; + } + + public Builder setPersonResourceReportPluginData( + PersonResourceReportPluginData personResourceReportPluginData) { + data.personResourceReportPluginData = personResourceReportPluginData; + return this; + } + + public Builder setResourcePropertyReportPluginData( + ResourcePropertyReportPluginData resourcePropertyReportPluginData) { + data.resourcePropertyReportPluginData = resourcePropertyReportPluginData; + return this; + } + + public Builder setResourceReportPluginData(ResourceReportPluginData resourceReportPluginData) { + data.resourceReportPluginData = resourceReportPluginData; + return this; + } + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/ResourcesPluginId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/ResourcesPluginId.java new file mode 100644 index 000000000..31cbddc34 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/ResourcesPluginId.java @@ -0,0 +1,13 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; + +/** + * Static plugin id implementation for the GlobalsPlugin + */ +public final class ResourcesPluginId implements PluginId { + public final static PluginId PLUGIN_ID = new ResourcesPluginId(); + + private ResourcesPluginId() { + }; +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/datamanagers/ResourcesDataManager.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/datamanagers/ResourcesDataManager.java new file mode 100644 index 000000000..775b68a91 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/datamanagers/ResourcesDataManager.java @@ -0,0 +1,1857 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.datamanagers; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import org.apache.commons.math3.util.FastMath; + +import gov.hhs.aspr.ms.gcm.nucleus.DataManager; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.nucleus.EventFilter; +import gov.hhs.aspr.ms.gcm.nucleus.IdentifiableFunctionMap; +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.events.PersonImminentAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.events.PersonRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonConstructionData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.regions.events.RegionAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionError; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.resources.events.PersonResourceUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.resources.events.RegionResourceUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.resources.events.ResourceIdAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.resources.events.ResourcePropertyDefinitionEvent; +import gov.hhs.aspr.ms.gcm.plugins.resources.events.ResourcePropertyUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceError; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceInitialization; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourcePropertyId; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourcePropertyInitialization; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.arraycontainers.DoubleValueContainer; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.arraycontainers.IntValueContainer; +import util.errors.ContractException; +import util.wrappers.MutableLong; + +/** + * Data manager for resources. Resource property values are generally mutable + * and specific to the type of resource. + */ +public final class ResourcesDataManager extends DataManager { + + private PeopleDataManager peopleDataManager; + private RegionsDataManager regionsDataManager; + + // resources + private Map> resourcePropertyValues = new LinkedHashMap<>(); + + /* + * Stores resource amounts per person keyed by the resourceId + */ + private final Map personResourceLevels = new LinkedHashMap<>(); + + private Map> resourcePropertyDefinitions = new LinkedHashMap<>(); + + /* + * Stores resource assignment times per person keyed by the resourceId. Key + * existence subject to time recording policies specified by the scenario. + */ + private Map resourceDefaultTimes = new LinkedHashMap<>(); + + private Map resourceTimeTrackingPolicies = new LinkedHashMap<>(); + + private final Map personResourceTimes = new LinkedHashMap<>(); + + private final Map> regionResources = new LinkedHashMap<>(); + + private final ResourcesPluginData resourcesPluginData; + + private DataManagerContext dataManagerContext; + + /** + * Constructs the PersonResourceManager from the context + * + * @throws ContractException {@linkplain ResourceError#NULL_RESOURCE_PLUGIN_DATA} + * if the plugin data is null + */ + public ResourcesDataManager(final ResourcesPluginData resourcesPluginData) { + if (resourcesPluginData == null) { + throw new ContractException(ResourceError.NULL_RESOURCE_PLUGIN_DATA); + } + this.resourcesPluginData = resourcesPluginData; + } + + /** + * Reduces the resource for the particular person and resource by the amount. + * + * @throws RuntimeException + *
      + *
    • if the resource id is null
    • + *
    • if the resource id is unknown
    • + *
    • if the person id null
    • + *
    • if the amount causes an overflow
    • + *
    + */ + private void decrementPersonResourceLevel(final ResourceId resourceId, final PersonId personId, + final long resourceAmount) { + personResourceLevels.get(resourceId).decrementLongValue(personId.getValue(), resourceAmount); + /* + * if the resource assignment times are being tracked, then record the resource + * time. + */ + Boolean trackTimes = resourceTimeTrackingPolicies.get(resourceId); + if (trackTimes) { + DoubleValueContainer doubleValueContainer = personResourceTimes.get(resourceId); + if (doubleValueContainer == null) { + double resourceDefinitionTime = resourceDefaultTimes.get(resourceId); + doubleValueContainer = new DoubleValueContainer(resourceDefinitionTime, + peopleDataManager::getPersonIndexIterator); + personResourceTimes.put(resourceId, doubleValueContainer); + } + doubleValueContainer.setValue(personId.getValue(), dataManagerContext.getTime()); + } + + } + + /** + * Reduces the resource for the particular region and resource by the amount. + * + * @throws RuntimeException + *
  • if the resource id is null
  • + *
  • if the resource id is unknown
  • + *
  • if the region id null
  • + *
  • if the region id is unknown
  • + * @throws ContractException + *
      + *
    • {@linkplain ResourceError#NEGATIVE_RESOURCE_AMOUNT} + * if the amount is negative
    • + *
    • {@linkplain ResourceError#INSUFFICIENT_RESOURCES_AVAILABLE} + * if the amount exceeds the current balance
    • + *
    + */ + private void decrementRegionResourceLevel(final RegionId regionId, final ResourceId resourceId, final long amount) { + Map map = regionResources.get(regionId); + if (map == null) { + map = new LinkedHashMap<>(); + regionResources.put(regionId, map); + } + MutableLong mutableLong = map.get(resourceId); + if (mutableLong == null) { + mutableLong = new MutableLong(); + map.put(resourceId, mutableLong); + } + mutableLong.decrement(amount); + } + + /** + * Expands the capacity of data structures to hold people by the given count. + * Used to more efficiently prepare for multiple population additions. + * + * @throws ContractException {@linkplain PersonError#NEGATIVE_GROWTH_PROJECTION} + * if the count is negative + */ + public void expandCapacity(final int count) { + if (count < 0) { + throw new ContractException(PersonError.NEGATIVE_GROWTH_PROJECTION); + } + if (count > 0) { + + for (final ResourceId resourceId : personResourceLevels.keySet()) { + final IntValueContainer intValueContainer = personResourceLevels.get(resourceId); + intValueContainer.setCapacity(intValueContainer.getCapacity() + count); + } + + for (final ResourceId resourceId : personResourceTimes.keySet()) { + final DoubleValueContainer doubleValueContainer = personResourceTimes.get(resourceId); + doubleValueContainer.setCapacity(doubleValueContainer.getCapacity() + count); + } + } + } + + /** + * Returns the set of people who do not have any of the given resource as a + * list. + * + * @throws ContractException + *
      + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if the resource id is unknown
    • + *
    + */ + public List getPeopleWithoutResource(final ResourceId resourceId) { + validateResourceId(resourceId); + /* + * First, we loop through all possible person id values and determine the exact + * size of the returned list. + */ + final IntValueContainer intValueContainer = personResourceLevels.get(resourceId); + if (intValueContainer == null) { + return new ArrayList<>(); + } + + int count = 0; + final int n = peopleDataManager.getPersonIdLimit(); + for (int personIndex = 0; personIndex < n; personIndex++) { + if (peopleDataManager.personIndexExists(personIndex)) { + final long resourceLevel = intValueContainer.getValueAsLong(personIndex); + if (resourceLevel == 0) { + count++; + } + } + } + + /* + * Now we create the list + */ + final List result = new ArrayList<>(count); + + /* + * We loop again and add the people to the list + */ + for (int personId = 0; personId < n; personId++) { + if (peopleDataManager.personIndexExists(personId)) { + final long resourceLevel = intValueContainer.getValueAsLong(personId); + if (resourceLevel == 0) { + result.add(peopleDataManager.getBoxedPersonId(personId).get()); + } + } + } + return result; + } + + /** + * Returns the list of people who have a non-zero level of the resource + * + * @throws ContractException + *
      + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if the resource id is unknown
    • + *
    + */ + public List getPeopleWithResource(final ResourceId resourceId) { + validateResourceId(resourceId); + /* + * First, we loop through all possible person id values and determine the exact + * size of the returned list. + */ + final IntValueContainer intValueContainer = personResourceLevels.get(resourceId); + + if (intValueContainer == null) { + return new ArrayList<>(); + } + + int count = 0; + final int n = peopleDataManager.getPersonIdLimit(); + for (int personId = 0; personId < n; personId++) { + if (peopleDataManager.personIndexExists(personId)) { + final long resourceLevel = intValueContainer.getValueAsLong(personId); + if (resourceLevel > 0) { + count++; + } + } + } + /* + * Now we create the list + */ + final List result = new ArrayList<>(count); + /* + * We loop again and add the people to the list + */ + for (int personIndex = 0; personIndex < n; personIndex++) { + if (peopleDataManager.personIndexExists(personIndex)) { + final long resourceLevel = intValueContainer.getValueAsLong(personIndex); + if (resourceLevel > 0) { + result.add(peopleDataManager.getBoxedPersonId(personIndex).get()); + } + } + } + return result; + + } + + /** + * Returns the region resource level. + * + * @throws ContractException + *
      + *
    • {@linkplain RegionError#NULL_REGION_ID} if the + * region id is null
    • + *
    • {@linkplain RegionError#UNKNOWN_REGION_ID} if + * the region id is unknown
    • + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if the resource id is unknown
    • + *
    + */ + public long getPersonResourceLevel(final ResourceId resourceId, final PersonId personId) { + validatePersonExists(personId); + validateResourceId(resourceId); + long result = 0; + IntValueContainer intValueContainer = personResourceLevels.get(resourceId); + if (intValueContainer != null) { + result = intValueContainer.getValueAsLong(personId.getValue()); + } + return result; + + } + + private long _getPersonResourceLevel(final ResourceId resourceId, final PersonId personId) { + + long result = 0; + IntValueContainer intValueContainer = personResourceLevels.get(resourceId); + if (intValueContainer != null) { + result = intValueContainer.getValueAsLong(personId.getValue()); + } + return result; + + } + + /** + * Returns the time when the resource level was last assigned for the given + * person and resource + * + * @throws ContractException + *
      + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person id is unknown
    • + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if the resource id is unknown
    • + *
    • {@linkplain ResourceError#RESOURCE_ASSIGNMENT_TIME_NOT_TRACKED} + * if assignment times are not tracked for the + * resource when applied to people
    • + *
    + */ + public double getPersonResourceTime(final ResourceId resourceId, final PersonId personId) { + validatePersonExists(personId); + validateResourceId(resourceId); + validatePersonResourceTimesTracked(resourceId); + final DoubleValueContainer doubleValueContainer = personResourceTimes.get(resourceId); + if (doubleValueContainer == null) { + return resourceDefaultTimes.get(resourceId); + } else { + return doubleValueContainer.getValue(personId.getValue()); + } + } + + /** + * Returns the time tracking policy for the given resource + * + * @throws ContractException + *
      + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if the resource id is unknown
    • + *
    + */ + public boolean getPersonResourceTimeTrackingPolicy(final ResourceId resourceId) { + validateResourceId(resourceId); + return resourceTimeTrackingPolicies.get(resourceId); + } + + /** + * Returns the current resource level for the given resource id and region id + * + * @throws ContractException + *
      + *
    • {@linkplain RegionError#NULL_REGION_ID} if the + * region id is null
    • + *
    • {@linkplain RegionError#UNKNOWN_REGION_ID} if + * the region id is unknown
    • + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if the resource id is unknown
    • + *
    + */ + public long getRegionResourceLevel(final RegionId regionId, final ResourceId resourceId) { + validateRegionId(regionId); + validateResourceId(resourceId); + + return _getRegionResourceLevel(regionId, resourceId); + } + + private long _getRegionResourceLevel(final RegionId regionId, final ResourceId resourceId) { + + long result = 0; + + Map map = regionResources.get(regionId); + if (map != null) { + MutableLong mutableLong = map.get(resourceId); + if (mutableLong != null) { + result = mutableLong.getValue(); + } + } + return result; + } + + /** + * Returns the resource ids + */ + @SuppressWarnings("unchecked") + public Set getResourceIds() { + final Set result = new LinkedHashSet<>(resourceDefaultTimes.size()); + for (final ResourceId resourceId : resourceDefaultTimes.keySet()) { + result.add((T) resourceId); + } + return result; + } + + private void validateResourceTypeIsUnknown(final ResourceId resourceId) { + if (resourceId == null) { + throw new ContractException(ResourceError.NULL_RESOURCE_ID); + } + if (resourceDefaultTimes.containsKey(resourceId)) { + throw new ContractException(ResourceError.DUPLICATE_RESOURCE_ID, resourceId); + } + } + + /* + * Precondition : the resource id must exist + */ + private void validateNewResourcePropertyId(final ResourceId resourceId, + final ResourcePropertyId resourcePropertyId) { + + final Map map = resourcePropertyDefinitions.get(resourceId); + + if ((map != null) && map.containsKey(resourcePropertyId)) { + throw new ContractException(PropertyError.DUPLICATE_PROPERTY_DEFINITION, resourcePropertyId); + } + } + + private static record ResourcePropertyDefinitionMutationEvent( + ResourcePropertyInitialization resourcePropertyInitialization) implements Event { + } + + /** + * Defines a new resource property. Generates the corresponding + * ResourcePropertyAdditionEvent. + * + * @throws ContractException + *
      + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if the resource id is unknown
    • + *
    • {@linkplain PropertyError#DUPLICATE_PROPERTY_DEFINITION} + * if the resource property is already defined
    • + *
    + */ + public void defineResourceProperty(ResourcePropertyInitialization resourcePropertyInitialization) { + dataManagerContext + .releaseMutationEvent(new ResourcePropertyDefinitionMutationEvent(resourcePropertyInitialization)); + } + + private void handleResourcePropertyDefinitionMutationEvent(DataManagerContext dataManagerContext, + ResourcePropertyDefinitionMutationEvent resourcePropertyDefinitionMutationEvent) { + ResourcePropertyInitialization resourcePropertyInitialization = resourcePropertyDefinitionMutationEvent + .resourcePropertyInitialization(); + + ResourceId resourceId = resourcePropertyInitialization.getResourceId(); + ResourcePropertyId resourcePropertyId = resourcePropertyInitialization.getResourcePropertyId(); + PropertyDefinition propertyDefinition = resourcePropertyInitialization.getPropertyDefinition(); + + validateResourceId(resourceId); + validateNewResourcePropertyId(resourceId, resourcePropertyId); + + Map defMap = resourcePropertyDefinitions.get(resourceId); + if (defMap == null) { + defMap = new LinkedHashMap<>(); + resourcePropertyDefinitions.put(resourceId, defMap); + } + defMap.put(resourcePropertyId, propertyDefinition); + + Object propertyValue; + Optional optionalValue = resourcePropertyInitialization.getValue(); + if (optionalValue.isPresent()) { + propertyValue = optionalValue.get(); + Map map = resourcePropertyValues.get(resourceId); + if (map == null) { + map = new LinkedHashMap<>(); + resourcePropertyValues.put(resourceId, map); + } + map.put(resourcePropertyId, propertyValue); + } else { + propertyValue = propertyDefinition.getDefaultValue().get(); + } + + if (dataManagerContext.subscribersExist(ResourcePropertyDefinitionEvent.class)) { + dataManagerContext.releaseObservationEvent( + new ResourcePropertyDefinitionEvent(resourceId, resourcePropertyId, propertyValue)); + } + + } + + private record ResourceIdAdditionMutationEvent(ResourceId resourceId, boolean timeTrackingPolicy) implements Event { + } + + /** + * Adds a resource type. Generates a corresponding ResourceIdAdditionEvent. + * + * @throws ContractException + *
      + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#DUPLICATE_RESOURCE_ID} + * if the resource type is already present
    • + *
    + */ + public void addResourceId(ResourceId resourceId, boolean timeTrackingPolicy) { + + dataManagerContext.releaseMutationEvent(new ResourceIdAdditionMutationEvent(resourceId, timeTrackingPolicy)); + } + + private void handleResourceIdAdditionMutationEvent(DataManagerContext dataManagerContext, + ResourceIdAdditionMutationEvent resourceIdAdditionMutationEvent) { + ResourceId resourceId = resourceIdAdditionMutationEvent.resourceId(); + validateResourceTypeIsUnknown(resourceId); + boolean trackTimes = resourceIdAdditionMutationEvent.timeTrackingPolicy(); + double resourceDefinitionTime = dataManagerContext.getTime(); + resourceDefaultTimes.put(resourceId, resourceDefinitionTime); + + resourceTimeTrackingPolicies.put(resourceId, trackTimes); + + // release notice that a new resource id has been added + if (dataManagerContext.subscribersExist(ResourceIdAdditionEvent.class)) { + dataManagerContext.releaseObservationEvent(new ResourceIdAdditionEvent(resourceId, trackTimes)); + } + + } + + /** + * Returns the property definition for the given resource id and resource + * property id + * + * @throws ContractException + *
      + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if the resource id is unknown
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the resource property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the resource property id is unknown
    • + *
    + */ + public PropertyDefinition getResourcePropertyDefinition(final ResourceId resourceId, + final ResourcePropertyId resourcePropertyId) { + validateResourceId(resourceId); + validateResourcePropertyId(resourceId, resourcePropertyId); + return resourcePropertyDefinitions.get(resourceId).get(resourcePropertyId); + } + + /** + * Returns the resource property id values for the given resource id + * + * @throws ContractException + *
      + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if the resource id is unknown
    • + *
    + */ + @SuppressWarnings("unchecked") + public Set getResourcePropertyIds(final ResourceId resourceId) { + validateResourceId(resourceId); + Set result; + final Map defMap = resourcePropertyDefinitions.get(resourceId); + if (defMap != null) { + result = new LinkedHashSet<>(defMap.keySet().size()); + for (final ResourcePropertyId resourcePropertyId : defMap.keySet()) { + result.add((T) resourcePropertyId); + } + } else { + result = new LinkedHashSet<>(); + } + return result; + } + + /** + * Returns the value of the resource property. + * + * @throws ContractException + *
      + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if the resource id is unknown
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the resource property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the resource property id is unknown
    • + *
    + */ + @SuppressWarnings("unchecked") + public T getResourcePropertyValue(final ResourceId resourceId, final ResourcePropertyId resourcePropertyId) { + validateResourceId(resourceId); + validateResourcePropertyId(resourceId, resourcePropertyId); + Object result = null; + Map valueMap = resourcePropertyValues.get(resourceId); + if (valueMap != null) { + result = valueMap.get(resourcePropertyId); + } + if (result == null) { + Map defMap = resourcePropertyDefinitions.get(resourceId); + PropertyDefinition propertyDefinition = defMap.get(resourcePropertyId); + result = propertyDefinition.getDefaultValue().get(); + } + return (T) result; + } + + /** + * Increase the resource for the particular person and resource by the amount. + * + * @throws RuntimeException + *
  • if the resource id is null
  • + *
  • if the resource id is unknown
  • + *
  • if the person id null
  • + *
  • if the amount causes an overflow
  • + * + */ + private void incrementPersonResourceLevel(final ResourceId resourceId, final PersonId personId, + final long resourceAmount) { + IntValueContainer intValueContainer = personResourceLevels.get(resourceId); + if (intValueContainer == null) { + intValueContainer = new IntValueContainer(0L, peopleDataManager::getPersonIndexIterator); + personResourceLevels.put(resourceId, intValueContainer); + } + intValueContainer.incrementLongValue(personId.getValue(), resourceAmount); + /* + * if the resource assignment times are being tracked, then record the resource + * time. + */ + Boolean trackTimes = resourceTimeTrackingPolicies.get(resourceId); + if (trackTimes) { + DoubleValueContainer doubleValueContainer = personResourceTimes.get(resourceId); + if (doubleValueContainer == null) { + double resourceDefinitionTime = resourceDefaultTimes.get(resourceId); + doubleValueContainer = new DoubleValueContainer(resourceDefinitionTime, + peopleDataManager::getPersonIndexIterator); + personResourceTimes.put(resourceId, doubleValueContainer); + } + doubleValueContainer.setValue(personId.getValue(), dataManagerContext.getTime()); + } + + } + + /** + * Increases the resource for the particular region and resource by the amount. + * + * @throws RuntimeException + *
      + *
    • if the resource id is null
    • + *
    • if the resource id is unknown
    • + *
    • if the region id null
    • + *
    • if the region id is unknown
    • + *
    • if the amount is negative
    • + *
    • if the amount causes an overflow
    • + *
    + * @throws ContractException {@linkplain ResourceError#NEGATIVE_RESOURCE_AMOUNT} + * if the amount is negative + */ + private void incrementRegionResourceLevel(final RegionId regionId, final ResourceId resourceId, final long amount) { + Map map = regionResources.get(regionId); + if (map == null) { + map = new LinkedHashMap<>(); + regionResources.put(regionId, map); + } + MutableLong mutableLong = map.get(resourceId); + if (mutableLong == null) { + mutableLong = new MutableLong(); + map.put(resourceId, mutableLong); + } + mutableLong.increment(amount); + } + + private void loadResourcePropertyDefinitions() { + resourcePropertyDefinitions = resourcesPluginData.getResourcePropertyDefinitions(); + } + + private void loadResourcePropertyValues() { + resourcePropertyValues = resourcesPluginData.getResourcePropertyValues(); + } + + private void loadResourceDefaultTimes() { + resourceDefaultTimes = resourcesPluginData.getResourceDefaultTimes(); + for (ResourceId resourceId : resourceDefaultTimes.keySet()) { + Double resourceDefaultTime = resourceDefaultTimes.get(resourceId); + if (resourceDefaultTime > dataManagerContext.getTime()) { + throw new ContractException(ResourceError.RESOURCE_CREATION_TIME_EXCEEDS_SIM_TIME); + } + } + } + + private void loadResourceTimeTrackingPolicies() { + resourceTimeTrackingPolicies = resourcesPluginData.getResourceTimeTrackingPolicies(); + } + + private void loadPersonResourceLevels() { + + Map> map = resourcesPluginData.getPersonResourceLevels(); + + for (ResourceId resourceId : map.keySet()) { + + final IntValueContainer intValueContainer = new IntValueContainer(0L, + peopleDataManager::getPersonIndexIterator); + personResourceLevels.put(resourceId, intValueContainer); + List personResourceLevels = map.get(resourceId); + // load the person levels here + int n = FastMath.max(personResourceLevels.size(), peopleDataManager.getPersonIdLimit()); + for (int i = 0; i < n; i++) { + + Long value = null; + if (i < personResourceLevels.size()) { + value = personResourceLevels.get(i); + } + + if (peopleDataManager.personIndexExists(i)) { + if (value != null) { + if (value != 0) { + intValueContainer.setLongValue(i, value); + } + } + } else { + if (value != null) { + throw new ContractException(PersonError.UNKNOWN_PERSON_ID, + "A non-null resource level for person " + i + " for resource " + resourceId + + " was found, but that person does not exist"); + } + + } + } + } + } + + private void loadPersonResourceTimes() { + + Map> map = resourcesPluginData.getPersonResourceTimes(); + for (ResourceId resourceId : map.keySet()) { + double resourceDefinitionTime = resourceDefaultTimes.get(resourceId); + final DoubleValueContainer doubleValueContainer = new DoubleValueContainer(resourceDefinitionTime, + peopleDataManager::getPersonIndexIterator); + personResourceTimes.put(resourceId, doubleValueContainer); + + List personResourceTimes = map.get(resourceId); + int n = FastMath.max(personResourceTimes.size(), peopleDataManager.getPersonIdLimit()); + for (int i = 0; i < n; i++) { + + Double value = null; + if (i < personResourceTimes.size()) { + value = personResourceTimes.get(i); + } + + if (value != null && resourceDefinitionTime > value) { + throw new ContractException(ResourceError.RESOURCE_CREATION_TIME_EXCEEDS_SIM_TIME); + } + + if (peopleDataManager.personIndexExists(i)) { + if (value != null) { + if (value != 0) { + doubleValueContainer.setValue(i, value); + } + } + } else { + if (value != null) { + throw new ContractException(PersonError.UNKNOWN_PERSON_ID, + "A non-null resource assignment time for person " + i + " for resource " + resourceId + + " was found, but that person does not exist"); + } + + } + } + + } + } + + private void loadRegionResourceLevels() { + Set totalRegionIds = regionsDataManager.getRegionIds(); + Set regionIds = resourcesPluginData.getRegionIds(); + + for (final RegionId regionId : regionIds) { + if (!totalRegionIds.contains(regionId)) { + throw new ContractException(RegionError.UNKNOWN_REGION_ID, + regionId + " is an unknown region with initial resources"); + } + } + + Map> regionResourceLevels = resourcesPluginData.getRegionResourceLevels(); + + for (final RegionId regionId : regionResourceLevels.keySet()) { + Map externalMap = regionResourceLevels.get(regionId); + Map internalMap = new LinkedHashMap<>(); + regionResources.put(regionId, internalMap); + for (ResourceId resourceId : externalMap.keySet()) { + Long value = externalMap.get(resourceId); + internalMap.put(resourceId, new MutableLong(value)); + } + } + } + + /** + *
      + *
    • Adds all event labelers defined by the following events + *
        + *
      • {@linkplain PersonResourceUpdateEvent}
      • + *
      • {@linkplain RegionResourceUpdateEvent}
      • + *
      • {@linkplain ResourcePropertyUpdateEvent}
      • + *
      + *
    • + *
    • Sets resource property values from the + * {@linkplain ResourcesPluginData}
    • + *
    • Sets region resource levels from the + * {@linkplain ResourcesPluginData}
    • + *
    • Sets person resource levels from the + * {@linkplain ResourcesPluginData}
    • + *
    + * Subscribes to the following events: + *
      + *
    • {@linkplain PersonImminentAdditionEvent} + *
        + *
      • Sets the person's initial resource levels in the + * {@linkplain ResourcesDataManager} from the ResourceInitialization references + * in the auxiliary data of the event. + *
      + *
    • {@linkplain PersonRemovalEvent} + *
        + *
      • Removes the resource assignment data for the person from the + * {@linkplain ResourcesDataManager} + *
      + *
    + * + * @throws ContractException + *
      + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person id is unknown
    • + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the auxiliary data contains a + * ResourceInitialization that has a null resource + * id
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if the auxiliary data contains a + * ResourceInitialization that has an unknown resource + * id
    • + *
    • {@linkplain ResourceError#NEGATIVE_RESOURCE_AMOUNT} + * if the auxiliary data contains a + * ResourceInitialization that has a negative resource + * level
    • + *
    + */ + @Override + public void init(final DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + if (dataManagerContext == null) { + throw new ContractException(NucleusError.NULL_SIMULATION_CONTEXT); + } + this.dataManagerContext = dataManagerContext; + peopleDataManager = dataManagerContext.getDataManager(PeopleDataManager.class); + regionsDataManager = dataManagerContext.getDataManager(RegionsDataManager.class); + + loadResourceDefaultTimes(); + loadResourceTimeTrackingPolicies(); + loadRegionResourceLevels(); + loadPersonResourceTimes(); + loadPersonResourceLevels(); + loadResourcePropertyDefinitions(); + loadResourcePropertyValues(); + + dataManagerContext.subscribe(RegionAdditionEvent.class, this::handleRegionAdditionEvent); + dataManagerContext.subscribe(PersonImminentAdditionEvent.class, this::handlePersonAdditionEvent); + dataManagerContext.subscribe(PersonRemovalEvent.class, this::handlePersonRemovalEvent); + dataManagerContext.subscribe(ResourceIdAdditionMutationEvent.class, + this::handleResourceIdAdditionMutationEvent); + dataManagerContext.subscribe(RegionResourceUpdateMutationEvent.class, + this::handleRegionResourceUpdateMutationEvent); + dataManagerContext.subscribe(ResourcePropertyDefinitionMutationEvent.class, + this::handleResourcePropertyDefinitionMutationEvent); + dataManagerContext.subscribe(PersonResourceUpdateMutationEvent.class, + this::handlePersonResourceUpdateMutationEvent); + dataManagerContext.subscribe(RegionResourceRemovalMutationEvent.class, + this::handleRegionResourceRemovalMutationEvent); + dataManagerContext.subscribe(ResourcePropertyUpdateMutationEvent.class, + this::handleResourcePropertyUpdateMutationEvent); + dataManagerContext.subscribe(InterRegionalResourceTransferMutationEvent.class, + this::handleInterRegionalResourceTransferMutationEvent); + dataManagerContext.subscribe(PersonToRegionResourceTransferMutationEvent.class, + this::handlePersonToRegionResourceTransferMutationEvent); + dataManagerContext.subscribe(RegionToPersonResourceTransferMutationEvent.class, + this::handleRegionToPersonResourceTransferMutationEvent); + + if (dataManagerContext.stateRecordingIsScheduled()) { + dataManagerContext.subscribeToSimulationClose(this::recordSimulationState); + } + } + + private void recordSimulationState(DataManagerContext dataManagerContext) { + ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); + + for (ResourceId resourceId : resourceDefaultTimes.keySet()) { + Boolean trackTimes = resourceTimeTrackingPolicies.get(resourceId); + Double defaultResourceTime = resourceDefaultTimes.get(resourceId); + builder.addResource(resourceId, defaultResourceTime, trackTimes); + } + + List people = peopleDataManager.getPeople(); + + for (ResourceId resourceId : personResourceTimes.keySet()) { + Double defaultResourceTime = resourceDefaultTimes.get(resourceId); + DoubleValueContainer doubleValueContainer = personResourceTimes.get(resourceId); + for (PersonId personId : people) { + double personResourceTime = doubleValueContainer.getValue(personId.getValue()); + if (personResourceTime != defaultResourceTime) { + builder.setPersonResourceTime(personId, resourceId, personResourceTime); + } + } + } + + for (ResourceId resourceId : personResourceLevels.keySet()) { + IntValueContainer intValueContainer = personResourceLevels.get(resourceId); + for (PersonId personId : people) { + long resourceLevel = intValueContainer.getValueAsLong(personId.getValue()); + if (resourceLevel != 0) { + builder.setPersonResourceLevel(personId, resourceId, resourceLevel); + } + } + } + + for (ResourceId resourceId : resourcePropertyDefinitions.keySet()) { + Map map = resourcePropertyDefinitions.get(resourceId); + for (ResourcePropertyId resourcePropertyId : map.keySet()) { + PropertyDefinition propertyDefinition = map.get(resourcePropertyId); + builder.defineResourceProperty(resourceId, resourcePropertyId, propertyDefinition); + } + } + + for (RegionId regionId : regionResources.keySet()) { + Map map = regionResources.get(regionId); + for (ResourceId resourceId : map.keySet()) { + MutableLong mutableLong = map.get(resourceId); + builder.setRegionResourceLevel(regionId, resourceId, mutableLong.getValue()); + } + } + + for (ResourceId resourceId : resourcePropertyValues.keySet()) { + Map map = resourcePropertyValues.get(resourceId); + for (ResourcePropertyId resourcePropertyId : map.keySet()) { + Object propertyValue = map.get(resourcePropertyId); + builder.setResourcePropertyValue(resourceId, resourcePropertyId, propertyValue); + } + } + + dataManagerContext.releaseOutput(builder.build()); + + } + + private void handleRegionAdditionEvent(DataManagerContext dataManagerContext, + RegionAdditionEvent regionAdditionEvent) { + RegionId regionId = regionAdditionEvent.getRegionId(); + if (regionResources.containsKey(regionId)) { + throw new ContractException(RegionError.DUPLICATE_REGION_ID); + } + List resourceInitializations = regionAdditionEvent + .getValues(ResourceInitialization.class); + for (ResourceInitialization resourceInitialization : resourceInitializations) { + ResourceId resourceId = resourceInitialization.getResourceId(); + validateResourceId(resourceId); + Long amount = resourceInitialization.getAmount(); + validateNonnegativeResourceAmount(amount); + incrementRegionResourceLevel(regionId, resourceId, amount); + } + } + + /** + * Returns true if and only if the given resource id is known + */ + public boolean resourceIdExists(final ResourceId resourceId) { + return resourceDefaultTimes.containsKey(resourceId); + } + + /** + * Returns true if and only if there is a resource property defined for the + * given resource id and resource property id + */ + public boolean resourcePropertyIdExists(final ResourceId resourceId, final ResourcePropertyId resourcePropertyId) { + if ((resourceId == null) || (resourcePropertyId == null)) { + return false; + } + + Map map = resourcePropertyDefinitions.get(resourceId); + + if (map == null) { + return false; + } + + return map.containsKey(resourcePropertyId); + } + + private void validatePersonExists(final PersonId personId) { + if (personId == null) { + throw new ContractException(PersonError.NULL_PERSON_ID); + } + if (!peopleDataManager.personExists(personId)) { + throw new ContractException(PersonError.UNKNOWN_PERSON_ID); + } + } + + /* + * Preconditions: the resource id must exist + */ + private void validatePersonResourceTimesTracked(final ResourceId resourceId) { + Boolean tracked = resourceTimeTrackingPolicies.get(resourceId); + if (!tracked) { + throw new ContractException(ResourceError.RESOURCE_ASSIGNMENT_TIME_NOT_TRACKED); + } + } + + private void validateRegionId(final RegionId regionId) { + + if (regionId == null) { + throw new ContractException(RegionError.NULL_REGION_ID); + } + + if (!regionsDataManager.regionIdExists(regionId)) { + throw new ContractException(RegionError.UNKNOWN_REGION_ID, regionId); + } + } + + private void validateResourceId(final ResourceId resourceId) { + if (resourceId == null) { + throw new ContractException(ResourceError.NULL_RESOURCE_ID); + } + if (!resourceDefaultTimes.containsKey(resourceId)) { + throw new ContractException(ResourceError.UNKNOWN_RESOURCE_ID, resourceId); + } + } + + /* + * Precondition : the resource id must exist + */ + private void validateResourcePropertyId(final ResourceId resourceId, final ResourcePropertyId resourcePropertyId) { + if (resourcePropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + + Map map = resourcePropertyDefinitions.get(resourceId); + + if ((map == null) || !map.containsKey(resourcePropertyId)) { + throw new ContractException(PropertyError.UNKNOWN_PROPERTY_ID, resourcePropertyId); + } + + } + + private static record InterRegionalResourceTransferMutationEvent(ResourceId resourceId, RegionId sourceRegionId, + RegionId destinationRegionId, long amount) implements Event { + } + + /** + * Transfers resources from one region to another. Generates the corresponding + * {@linkplain RegionResourceUpdateEvent} events for each region. + * + * @throws ContractException + *
      + *
    • {@linkplain RegionError#NULL_REGION_ID} if the + * source region is null
    • + *
    • {@linkplain RegionError#UNKNOWN_REGION_ID} if + * the source region is unknown
    • + *
    • {@linkplain RegionError#NULL_REGION_ID} if the + * destination region is null
    • + *
    • {@linkplain RegionError#UNKNOWN_REGION_ID} if + * the destination region is unknown
    • + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if the resource id is unknown
    • + *
    • {@linkplain ResourceError#NEGATIVE_RESOURCE_AMOUNT} + * if the resource amount is negative
    • + *
    • {@linkplain ResourceError#REFLEXIVE_RESOURCE_TRANSFER} + * if the source and destination region are equal
    • + *
    • {@linkplain ResourceError#INSUFFICIENT_RESOURCES_AVAILABLE} + * if the source region does not have sufficient + * resources to support the transfer
    • + *
    • {@linkplain ResourceError#RESOURCE_ARITHMETIC_EXCEPTION} + * if the transfer will cause a numeric overflow in + * the destination region
    • + *
    + */ + public void transferResourceBetweenRegions(ResourceId resourceId, RegionId sourceRegionId, + RegionId destinationRegionId, long amount) { + dataManagerContext.releaseMutationEvent(new InterRegionalResourceTransferMutationEvent(resourceId, + sourceRegionId, destinationRegionId, amount)); + } + + private void handleInterRegionalResourceTransferMutationEvent(DataManagerContext dataManagerContext, + InterRegionalResourceTransferMutationEvent interRegionalResourceTransferMutationEvent) { + ResourceId resourceId = interRegionalResourceTransferMutationEvent.resourceId(); + RegionId sourceRegionId = interRegionalResourceTransferMutationEvent.sourceRegionId(); + RegionId destinationRegionId = interRegionalResourceTransferMutationEvent.destinationRegionId(); + long amount = interRegionalResourceTransferMutationEvent.amount(); + validateRegionId(sourceRegionId); + validateRegionId(destinationRegionId); + validateResourceId(resourceId); + validateNonnegativeResourceAmount(amount); + validateDifferentRegionsForResourceTransfer(sourceRegionId, destinationRegionId); + validateRegionHasSufficientResources(resourceId, sourceRegionId, amount); + + final long previousDestinationRegionResourceLevel = _getRegionResourceLevel(destinationRegionId, resourceId); + validateResourceAdditionValue(previousDestinationRegionResourceLevel, amount); + + if (dataManagerContext.subscribersExist(RegionResourceUpdateEvent.class)) { + final long previousSourceRegionResourceLevel = _getRegionResourceLevel(sourceRegionId, resourceId); + + decrementRegionResourceLevel(sourceRegionId, resourceId, amount); + incrementRegionResourceLevel(destinationRegionId, resourceId, amount); + + long currentSourceRegionResourceLevel = _getRegionResourceLevel(sourceRegionId, resourceId); + long currentDestinationRegionResourceLevel = _getRegionResourceLevel(destinationRegionId, resourceId); + + dataManagerContext.releaseObservationEvent(new RegionResourceUpdateEvent(sourceRegionId, resourceId, + previousSourceRegionResourceLevel, currentSourceRegionResourceLevel)); + dataManagerContext.releaseObservationEvent(new RegionResourceUpdateEvent(destinationRegionId, resourceId, + previousDestinationRegionResourceLevel, currentDestinationRegionResourceLevel)); + } else { + + decrementRegionResourceLevel(sourceRegionId, resourceId, amount); + incrementRegionResourceLevel(destinationRegionId, resourceId, amount); + + } + + } + + private void validateNonnegativeResourceAmount(final long amount) { + if (amount < 0) { + throw new ContractException(ResourceError.NEGATIVE_RESOURCE_AMOUNT); + } + } + + private void validateDifferentRegionsForResourceTransfer(final RegionId sourceRegionId, + final RegionId destinationRegionId) { + if (sourceRegionId.equals(destinationRegionId)) { + throw new ContractException(ResourceError.REFLEXIVE_RESOURCE_TRANSFER); + } + } + + /* + * Preconditions : the region and resource must exist + */ + private void validateRegionHasSufficientResources(final ResourceId resourceId, final RegionId regionId, + final long amount) { + long currentAmount = 0; + Map map = regionResources.get(regionId); + if (map != null) { + MutableLong mutableLong = map.get(resourceId); + if (mutableLong != null) { + currentAmount = mutableLong.getValue(); + } + } + if (currentAmount < amount) { + throw new ContractException(ResourceError.INSUFFICIENT_RESOURCES_AVAILABLE); + } + } + + private void validateResourceAdditionValue(final long currentResourceLevel, final long amount) { + try { + Math.addExact(currentResourceLevel, amount); + } catch (final ArithmeticException e) { + throw new ContractException(ResourceError.RESOURCE_ARITHMETIC_EXCEPTION); + } + } + + private static record PersonResourceUpdateMutationEvent(ResourceId resourceId, PersonId personId, long amount) + implements Event { + } + + /** + * Expends an amount of resource from a person. Generates the corresponding + * {@linkplain PersonResourceUpdateEvent} event + * + * @throws ContractException + *
      + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person does not exist
    • + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if the resource id is unknown
    • + *
    • {@linkplain ResourceError#NEGATIVE_RESOURCE_AMOUNT} + * if the amount is negative
    • + *
    • {@linkplain ResourceError#INSUFFICIENT_RESOURCES_AVAILABLE} + * if the person does not have the required amount of + * the resource
    • + *
    + */ + public void removeResourceFromPerson(ResourceId resourceId, PersonId personId, long amount) { + dataManagerContext.releaseMutationEvent(new PersonResourceUpdateMutationEvent(resourceId, personId, amount)); + } + + private void handlePersonResourceUpdateMutationEvent(DataManagerContext dataManagerContext, + PersonResourceUpdateMutationEvent personResourceUpdateMutationEvent) { + ResourceId resourceId = personResourceUpdateMutationEvent.resourceId(); + PersonId personId = personResourceUpdateMutationEvent.personId(); + long amount = personResourceUpdateMutationEvent.amount(); + validatePersonExists(personId); + validateResourceId(resourceId); + validateNonnegativeResourceAmount(amount); + validatePersonHasSufficientResources(resourceId, personId, amount); + + if (dataManagerContext.subscribersExist(PersonResourceUpdateEvent.class)) { + IntValueContainer intValueContainer = personResourceLevels.get(resourceId); + if (intValueContainer != null) { + final long oldLevel = personResourceLevels.get(resourceId).getValueAsLong(personId.getValue()); + decrementPersonResourceLevel(resourceId, personId, amount); + final long newLevel = personResourceLevels.get(resourceId).getValueAsLong(personId.getValue()); + dataManagerContext.releaseObservationEvent( + new PersonResourceUpdateEvent(personId, resourceId, oldLevel, newLevel)); + } else { + // validation above guarantees that the values are all zero + dataManagerContext.releaseObservationEvent(new PersonResourceUpdateEvent(personId, resourceId, 0, 0)); + } + } else { + decrementPersonResourceLevel(resourceId, personId, amount); + } + } + + /* + * Preconditions : the resource and person must exist + */ + private void validatePersonHasSufficientResources(final ResourceId resourceId, final PersonId personId, + final long amount) { + + long value = 0; + IntValueContainer intValueContainer = personResourceLevels.get(resourceId); + if (intValueContainer != null) { + value = intValueContainer.getValueAsLong(personId.getValue()); + } + if (value < amount) { + throw new ContractException(ResourceError.INSUFFICIENT_RESOURCES_AVAILABLE); + } + } + + private static record RegionResourceUpdateMutationEvent(ResourceId resourceId, RegionId regionId, long amount) + implements Event { + } + + /** + * Adds an amount of resource to a region. Generates the corresponding + * {@linkplain RegionResourceUpdateEvent} event. + * + * @throws ContractException + *
      + *
    • {@linkplain RegionError#NULL_REGION_ID} if the + * region id is null
    • + *
    • {@linkplain RegionError#UNKNOWN_REGION_ID} if + * the region id is unknown
    • + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if the resource id is unknown
    • + *
    • {@linkplain ResourceError#NEGATIVE_RESOURCE_AMOUNT} + * if the amount is negative
    • + *
    • {@linkplain ResourceError#RESOURCE_ARITHMETIC_EXCEPTION} + * if the addition results in an overflow
    • + *
    + */ + public void addResourceToRegion(ResourceId resourceId, RegionId regionId, long amount) { + dataManagerContext.releaseMutationEvent(new RegionResourceUpdateMutationEvent(resourceId, regionId, amount)); + } + + private void handleRegionResourceUpdateMutationEvent(DataManagerContext dataManagerContext, + RegionResourceUpdateMutationEvent regionResourceUpdateMutationEvent) { + RegionId regionId = regionResourceUpdateMutationEvent.regionId(); + ResourceId resourceId = regionResourceUpdateMutationEvent.resourceId(); + long amount = regionResourceUpdateMutationEvent.amount(); + + validateRegionId(regionId); + validateResourceId(resourceId); + validateNonnegativeResourceAmount(amount); + + if (dataManagerContext.subscribersExist(RegionResourceUpdateEvent.class)) { + final long previousResourceLevel = _getRegionResourceLevel(regionId, resourceId); + validateResourceAdditionValue(previousResourceLevel, amount); + incrementRegionResourceLevel(regionId, resourceId, amount); + long currentResourceLevel = _getRegionResourceLevel(regionId, resourceId); + dataManagerContext.releaseObservationEvent( + new RegionResourceUpdateEvent(regionId, resourceId, previousResourceLevel, currentResourceLevel)); + } else { + final long previousResourceLevel = _getRegionResourceLevel(regionId, resourceId); + validateResourceAdditionValue(previousResourceLevel, amount); + incrementRegionResourceLevel(regionId, resourceId, amount); + } + + } + + private static record RegionResourceRemovalMutationEvent(ResourceId resourceId, RegionId regionId, long amount) + implements Event { + } + + /** + * Removes an amount of resource from a region.Generates the corresponding + * {@linkplain RegionResourceUpdateEvent} event + * + * @throws ContractException + *
      + *
    • {@linkplain RegionError#NULL_REGION_ID} if the + * region id is null
    • + *
    • {@linkplain RegionError#UNKNOWN_REGION_ID} if + * the region id is unknown
    • + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if the resource id is unknown
    • + *
    • {@linkplain ResourceError#NEGATIVE_RESOURCE_AMOUNT} + * if the amount is negative
    • + *
    • {@linkplain ResourceError#INSUFFICIENT_RESOURCES_AVAILABLE} + * if the region does not have the required amount of + * the resource
    • + *
    + */ + public void removeResourceFromRegion(ResourceId resourceId, RegionId regionId, long amount) { + dataManagerContext.releaseMutationEvent(new RegionResourceRemovalMutationEvent(resourceId, regionId, amount)); + } + + private void handleRegionResourceRemovalMutationEvent(DataManagerContext dataManagerContext, + RegionResourceRemovalMutationEvent regionResourceRemovalMutationEvent) { + ResourceId resourceId = regionResourceRemovalMutationEvent.resourceId(); + RegionId regionId = regionResourceRemovalMutationEvent.regionId(); + long amount = regionResourceRemovalMutationEvent.amount(); + validateRegionId(regionId); + validateResourceId(resourceId); + validateNonnegativeResourceAmount(amount); + validateRegionHasSufficientResources(resourceId, regionId, amount); + if (dataManagerContext.subscribersExist(RegionResourceUpdateEvent.class)) { + final long previousResourceLevel = _getRegionResourceLevel(regionId, resourceId); + decrementRegionResourceLevel(regionId, resourceId, amount); + long currentResourceLevel = _getRegionResourceLevel(regionId, resourceId); + dataManagerContext.releaseObservationEvent( + new RegionResourceUpdateEvent(regionId, resourceId, previousResourceLevel, currentResourceLevel)); + } else { + decrementRegionResourceLevel(regionId, resourceId, amount); + } + + } + + private static record ResourcePropertyUpdateMutationEvent(ResourceId resourceId, + ResourcePropertyId resourcePropertyId, Object resourcePropertyValue) implements Event { + } + + /** + * Assigns a value to a resource property. Generates the corresponding + * {@linkplain ResourcePropertyUpdateEvent} event + * + * @throws ContractException + *
      + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if the resource id is unknown
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the resource property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the resource property id is unknown
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_VALUE} + * if the resource property value is null
    • + *
    • {@linkplain PropertyError#INCOMPATIBLE_VALUE} + * if the resource property value is incompatible with + * the corresponding property definition
    • + *
    • {@linkplain PropertyError#IMMUTABLE_VALUE} if + * the property has been defined as immutable
    • + *
    + */ + public void setResourcePropertyValue(ResourceId resourceId, ResourcePropertyId resourcePropertyId, + Object resourcePropertyValue) { + dataManagerContext.releaseMutationEvent( + new ResourcePropertyUpdateMutationEvent(resourceId, resourcePropertyId, resourcePropertyValue)); + } + + private void handleResourcePropertyUpdateMutationEvent(DataManagerContext dataManagerContext, + ResourcePropertyUpdateMutationEvent resourcePropertyUpdateMutationEvent) { + ResourceId resourceId = resourcePropertyUpdateMutationEvent.resourceId(); + ResourcePropertyId resourcePropertyId = resourcePropertyUpdateMutationEvent.resourcePropertyId(); + Object resourcePropertyValue = resourcePropertyUpdateMutationEvent.resourcePropertyValue(); + validateResourceId(resourceId); + validateResourcePropertyId(resourceId, resourcePropertyId); + validateResourcePropertyValueNotNull(resourcePropertyValue); + final PropertyDefinition propertyDefinition = resourcePropertyDefinitions.get(resourceId) + .get(resourcePropertyId); + validateValueCompatibility(resourcePropertyId, propertyDefinition, resourcePropertyValue); + validatePropertyMutability(propertyDefinition); + + Map map = resourcePropertyValues.get(resourceId); + if (map == null) { + map = new LinkedHashMap<>(); + resourcePropertyValues.put(resourceId, map); + } + + Object oldPropertyValue = map.get(resourcePropertyId); + + if (oldPropertyValue == null) { + oldPropertyValue = propertyDefinition.getDefaultValue().get(); + } + + map.put(resourcePropertyId, resourcePropertyValue); + if (dataManagerContext.subscribersExist(ResourcePropertyUpdateEvent.class)) { + dataManagerContext.releaseObservationEvent(new ResourcePropertyUpdateEvent(resourceId, resourcePropertyId, + oldPropertyValue, resourcePropertyValue)); + } + } + + private void validateResourcePropertyValueNotNull(final Object propertyValue) { + if (propertyValue == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_VALUE); + } + } + + private void validateValueCompatibility(final Object propertyId, final PropertyDefinition propertyDefinition, + final Object propertyValue) { + if (!propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())) { + throw new ContractException(PropertyError.INCOMPATIBLE_VALUE, + "Property value " + propertyValue + " is not of type " + propertyDefinition.getType().getName() + + " and does not match definition of " + propertyId); + } + } + + private void validatePropertyMutability(final PropertyDefinition propertyDefinition) { + if (!propertyDefinition.propertyValuesAreMutable()) { + throw new ContractException(PropertyError.IMMUTABLE_VALUE); + } + } + + private static record PersonToRegionResourceTransferMutationEvent(ResourceId resourceId, PersonId personId, + long amount) implements Event { + } + + /** + * Transfers an amount of resource from a person to the person's current region. + * Generates the corresponding {@linkplain RegionResourceUpdateEvent} and + * {@linkplain PersonResourceUpdateEvent} events + * + * @throws ContractException + *
      + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person does not exist
    • + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if the resource id is unknown
    • + *
    • {@linkplain ResourceError#NEGATIVE_RESOURCE_AMOUNT} + * if the amount is negative
    • + *
    • {@linkplain ResourceError#INSUFFICIENT_RESOURCES_AVAILABLE} + * if the person does not have the required amount of + * the resource
    • + *
    • {@linkplain ResourceError#RESOURCE_ARITHMETIC_EXCEPTION} + * if the transfer results in an overflow of the + * region's resource level
    • + *
    + */ + public void transferResourceFromPersonToRegion(ResourceId resourceId, PersonId personId, long amount) { + dataManagerContext + .releaseMutationEvent(new PersonToRegionResourceTransferMutationEvent(resourceId, personId, amount)); + } + + private void handlePersonToRegionResourceTransferMutationEvent(DataManagerContext dataManagerContext, + PersonToRegionResourceTransferMutationEvent personToRegionResourceTransferMutationEvent) { + ResourceId resourceId = personToRegionResourceTransferMutationEvent.resourceId(); + PersonId personId = personToRegionResourceTransferMutationEvent.personId(); + long amount = personToRegionResourceTransferMutationEvent.amount(); + validatePersonExists(personId); + validateResourceId(resourceId); + validateNonnegativeResourceAmount(amount); + validatePersonHasSufficientResources(resourceId, personId, amount); + final RegionId regionId = regionsDataManager.getPersonRegion(personId); + final long previousRegionResourceLevel = _getRegionResourceLevel(regionId, resourceId); + validateResourceAdditionValue(previousRegionResourceLevel, amount); + final long oldLevel = _getPersonResourceLevel(resourceId, personId); + decrementPersonResourceLevel(resourceId, personId, amount); + final long newLevel = _getPersonResourceLevel(resourceId, personId); + incrementRegionResourceLevel(regionId, resourceId, amount); + long currentRegionResourceLevel = _getRegionResourceLevel(regionId, resourceId); + if (dataManagerContext.subscribersExist(PersonResourceUpdateEvent.class)) { + dataManagerContext + .releaseObservationEvent(new PersonResourceUpdateEvent(personId, resourceId, oldLevel, newLevel)); + } + if (dataManagerContext.subscribersExist(RegionResourceUpdateEvent.class)) { + dataManagerContext.releaseObservationEvent(new RegionResourceUpdateEvent(regionId, resourceId, + previousRegionResourceLevel, currentRegionResourceLevel)); + } + + } + + private static record RegionToPersonResourceTransferMutationEvent(ResourceId resourceId, PersonId personId, + long amount) implements Event { + } + + /** + * Transfers an amount of resource to a person from the person's current region. + * Generates the corresponding {@linkplain RegionResourceUpdateEvent} and + * {@linkplain PersonResourceUpdateEvent} events + * + * @throws ContractException + *
      + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person does not exist
    • + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if the resource id is unknown
    • + *
    • {@linkplain ResourceError#NEGATIVE_RESOURCE_AMOUNT} + * if the amount is negative
    • + *
    • {@linkplain ResourceError#INSUFFICIENT_RESOURCES_AVAILABLE} + * if the region does not have the required amount of + * the resource
    • + *
    • {@linkplain ResourceError#RESOURCE_ARITHMETIC_EXCEPTION} + * if the transfer results in an overflow of the + * person's resource level
    • + *
    + */ + public void transferResourceToPersonFromRegion(ResourceId resourceId, PersonId personId, long amount) { + dataManagerContext + .releaseMutationEvent(new RegionToPersonResourceTransferMutationEvent(resourceId, personId, amount)); + } + + private void handleRegionToPersonResourceTransferMutationEvent(DataManagerContext dataManagerContext, + RegionToPersonResourceTransferMutationEvent regionToPersonResourceTransferMutationEvent) { + ResourceId resourceId = regionToPersonResourceTransferMutationEvent.resourceId(); + PersonId personId = regionToPersonResourceTransferMutationEvent.personId(); + long amount = regionToPersonResourceTransferMutationEvent.amount(); + validatePersonExists(personId); + validateResourceId(resourceId); + validateNonnegativeResourceAmount(amount); + final RegionId regionId = regionsDataManager.getPersonRegion(personId); + validateRegionHasSufficientResources(resourceId, regionId, amount); + final long personResourceLevel = _getPersonResourceLevel(resourceId, personId); + validateResourceAdditionValue(personResourceLevel, amount); + + final long previousRegionResourceLevel = _getRegionResourceLevel(regionId, resourceId); + + decrementRegionResourceLevel(regionId, resourceId, amount); + incrementPersonResourceLevel(resourceId, personId, amount); + final long newLevel = _getPersonResourceLevel(resourceId, personId); + long currentRegionResourceLevel = _getRegionResourceLevel(regionId, resourceId); + + if (dataManagerContext.subscribersExist(RegionResourceUpdateEvent.class)) { + dataManagerContext.releaseObservationEvent(new RegionResourceUpdateEvent(regionId, resourceId, + previousRegionResourceLevel, currentRegionResourceLevel)); + } + if (dataManagerContext.subscribersExist(PersonResourceUpdateEvent.class)) { + dataManagerContext.releaseObservationEvent( + new PersonResourceUpdateEvent(personId, resourceId, personResourceLevel, newLevel)); + } + } + + private void handlePersonAdditionEvent(final DataManagerContext dataManagerContext, + final PersonImminentAdditionEvent personImminentAdditionEvent) { + PersonId personId = personImminentAdditionEvent.personId(); + PersonConstructionData personConstructionData = personImminentAdditionEvent.personConstructionData(); + validatePersonExists(personId); + List resourceAssignments = personConstructionData + .getValues(ResourceInitialization.class); + for (final ResourceInitialization resourceAssignment : resourceAssignments) { + ResourceId resourceId = resourceAssignment.getResourceId(); + Long amount = resourceAssignment.getAmount(); + validateResourceId(resourceId); + validateNonnegativeResourceAmount(amount); + } + + for (final ResourceInitialization resourceAssignment : resourceAssignments) { + incrementPersonResourceLevel(resourceAssignment.getResourceId(), personId, resourceAssignment.getAmount()); + } + } + + private void handlePersonRemovalEvent(final DataManagerContext dataManagerContext, + final PersonRemovalEvent personRemovalEvent) { + + PersonId personId = personRemovalEvent.personId(); + for (final IntValueContainer intValueContainer : personResourceLevels.values()) { + intValueContainer.setLongValue(personId.getValue(), 0); + } + + } + + private static enum PersonResourceUpdateEventFunctionId { + RESOURCE, REGION, PERSON + } + + private IdentifiableFunctionMap personResourceUpdateFunctionMap = // + IdentifiableFunctionMap.builder(PersonResourceUpdateEvent.class)// + .put(PersonResourceUpdateEventFunctionId.RESOURCE, e -> e.resourceId())// + .put(PersonResourceUpdateEventFunctionId.REGION, + e -> regionsDataManager.getPersonRegion(e.personId()))// + .put(PersonResourceUpdateEventFunctionId.PERSON, e -> e.personId())// + .build();// + + /** + * Returns an event filter used to subscribe to + * {@link PersonResourceUpdateEvent} events. Matches on the resource id. + * + * @throws ContractException + *
      + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if the resource id is not known
    • + *
    + */ + public EventFilter getEventFilterForPersonResourceUpdateEvent(ResourceId resourceId) { + validateResourceId(resourceId); + return EventFilter.builder(PersonResourceUpdateEvent.class)// + .addFunctionValuePair(personResourceUpdateFunctionMap.get(PersonResourceUpdateEventFunctionId.RESOURCE), + resourceId)// + .build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link PersonResourceUpdateEvent} events. Matches on the resource id and + * person id. + * + * @throws ContractException + *
      + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if the resource id is not known
    • + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person id is not known
    • + *
    + */ + public EventFilter getEventFilterForPersonResourceUpdateEvent(ResourceId resourceId, + PersonId personId) { + validateResourceId(resourceId); + validatePersonExists(personId); + return EventFilter.builder(PersonResourceUpdateEvent.class)// + .addFunctionValuePair(personResourceUpdateFunctionMap.get(PersonResourceUpdateEventFunctionId.RESOURCE), + resourceId)// + .addFunctionValuePair(personResourceUpdateFunctionMap.get(PersonResourceUpdateEventFunctionId.PERSON), + personId)// + .build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link PersonResourceUpdateEvent} events. Matches on the resource id and + * person id. + * + * @throws ContractException + *
      + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if the resource id is not known
    • + *
    • {@linkplain RegionError#NULL_REGION_ID} if the + * region id is null
    • + *
    • {@linkplain RegionError#UNKNOWN_REGION_ID} if + * the region id is not known
    • + *
    + */ + public EventFilter getEventFilterForPersonResourceUpdateEvent(ResourceId resourceId, + RegionId regionId) { + validateResourceId(resourceId); + validateRegionId(regionId); + return EventFilter.builder(PersonResourceUpdateEvent.class)// + .addFunctionValuePair(personResourceUpdateFunctionMap.get(PersonResourceUpdateEventFunctionId.RESOURCE), + resourceId)// + .addFunctionValuePair(personResourceUpdateFunctionMap.get(PersonResourceUpdateEventFunctionId.REGION), + regionId)// + .build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link PersonResourceUpdateEvent} events. Matches on all such events. + */ + public EventFilter getEventFilterForPersonResourceUpdateEvent() { + return EventFilter.builder(PersonResourceUpdateEvent.class)// + .build(); + } + + private static enum RegionResourceUpdateEventFunctionId { + RESOURCE, REGION + } + + private IdentifiableFunctionMap regionResourceUpdateMap = // + IdentifiableFunctionMap.builder(RegionResourceUpdateEvent.class)// + .put(RegionResourceUpdateEventFunctionId.RESOURCE, e -> e.resourceId())// + .put(RegionResourceUpdateEventFunctionId.REGION, e -> e.regionId())// + .build();// + + /** + * Returns an event filter used to subscribe to + * {@link RegionResourceUpdateEvent} events. Matches on the resource id. + * + * @throws ContractException + *
      + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if the resource id is not known
    • + *
    + */ + public EventFilter getEventFilterForRegionResourceUpdateEvent(ResourceId resourceId) { + validateResourceId(resourceId); + return EventFilter.builder(RegionResourceUpdateEvent.class)// + .addFunctionValuePair(regionResourceUpdateMap.get(RegionResourceUpdateEventFunctionId.RESOURCE), + resourceId)// + .build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link RegionResourceUpdateEvent} events. Matches on the resource id and + * region id. + * + * @throws ContractException + *
      + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if the resource id is not known
    • + *
    • {@linkplain RegionError#NULL_REGION_ID} if the + * region id is null
    • + *
    • {@linkplain RegionError#UNKNOWN_REGION_ID} if + * the region id is not known
    • + *
    + */ + public EventFilter getEventFilterForRegionResourceUpdateEvent(ResourceId resourceId, + RegionId regionId) { + validateResourceId(resourceId); + validateRegionId(regionId); + return EventFilter.builder(RegionResourceUpdateEvent.class)// + .addFunctionValuePair(regionResourceUpdateMap.get(RegionResourceUpdateEventFunctionId.RESOURCE), + resourceId)// + .addFunctionValuePair(regionResourceUpdateMap.get(RegionResourceUpdateEventFunctionId.REGION), regionId)// + .build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link RegionResourceUpdateEvent} events. Matches on all such events. + */ + public EventFilter getEventFilterForRegionResourceUpdateEvent() { + return EventFilter.builder(RegionResourceUpdateEvent.class)// + .build(); + } + + private static enum ResourcePropertyUpdateEventFunctionId { + RESOURCE, PROPERTY + } + + private IdentifiableFunctionMap resourcePropertyUpdateMap = // + IdentifiableFunctionMap.builder(ResourcePropertyUpdateEvent.class)// + .put(ResourcePropertyUpdateEventFunctionId.RESOURCE, e -> e.resourceId())// + .put(ResourcePropertyUpdateEventFunctionId.PROPERTY, e -> e.resourcePropertyId())// + .build();// + + /** + * Returns an event filter used to subscribe to + * {@link ResourcePropertyUpdateEvent} events. Matches on the resource id and + * resource property id. + * + * @throws ContractException + *
      + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if the resource id is not known
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the resource property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the resource property id is not known
    • + *
    + */ + public EventFilter getEventFilterForResourcePropertyUpdateEvent(ResourceId resourceId, + ResourcePropertyId resourcePropertyId) { + validateResourceId(resourceId); + validateResourcePropertyId(resourceId, resourcePropertyId); + return EventFilter.builder(ResourcePropertyUpdateEvent.class)// + .addFunctionValuePair(resourcePropertyUpdateMap.get(ResourcePropertyUpdateEventFunctionId.RESOURCE), + resourceId)// + .addFunctionValuePair(resourcePropertyUpdateMap.get(ResourcePropertyUpdateEventFunctionId.PROPERTY), + resourcePropertyId)// + .build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link ResourcePropertyUpdateEvent} events. Matches all such events. + */ + public EventFilter getEventFilterForResourcePropertyUpdateEvent() { + return EventFilter.builder(ResourcePropertyUpdateEvent.class)// + .build(); + } + + /** + * Returns an event filter used to subscribe to {@link ResourceIdAdditionEvent} + * events. Matches all such events. + */ + public EventFilter getEventFilterForResourceIdAdditionEvent() { + return EventFilter.builder(ResourceIdAdditionEvent.class)// + .build(); + } + + /** + * Returns an event filter used to subscribe to + * {@link ResourcePropertyDefinitionEvent} events. Matches all such events. + */ + public EventFilter getEventFilterForResourcePropertyDefinitionEvent() { + return EventFilter.builder(ResourcePropertyDefinitionEvent.class)// + .build(); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("ResourcesDataManager ["); + + builder.append("resourceDefaultTimes="); + builder.append(resourceDefaultTimes); + + builder.append(", resourcePropertyValues="); + builder.append(resourcePropertyValues); + + builder.append(", personResourceLevels="); + builder.append(personResourceLevels); + + builder.append(", resourcePropertyDefinitions="); + builder.append(resourcePropertyDefinitions); + + builder.append(", resourceTimeTrackingPolicies="); + builder.append(resourceTimeTrackingPolicies); + + builder.append(", personResourceTimes="); + builder.append(personResourceTimes); + + builder.append(", regionResources="); + builder.append(regionResources); + + builder.append("]"); + return builder.toString(); + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/datamanagers/ResourcesPluginData.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/datamanagers/ResourcesPluginData.java new file mode 100644 index 000000000..3c3249c12 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/datamanagers/ResourcesPluginData.java @@ -0,0 +1,944 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.datamanagers; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.PluginDataBuilder; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionError; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceError; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourcePropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * An immutable container of the initial state of resources. It contains:
    + *
      + *
    • resource ids
    • + *
    • resource property definitions
    • + *
    • region resource levels
    • + *
    • person resource levels
    • + *
    + */ +@Immutable +public final class ResourcesPluginData implements PluginData { + + private static class Data { + + private final Map resourceDefaultTimes; + private Map resourceTimeTrackingPolicies; + private Map> resourcePropertyDefinitions; + private Map> resourcePropertyValues; + private Map> personResourceLevels; + private Map> personResourceTimes; + private Map> regionResourceLevels; + + private boolean locked; + + public Data() { + resourceDefaultTimes = new LinkedHashMap<>(); + resourceTimeTrackingPolicies = new LinkedHashMap<>(); + resourcePropertyDefinitions = new LinkedHashMap<>(); + resourcePropertyValues = new LinkedHashMap<>(); + personResourceLevels = new LinkedHashMap<>(); + personResourceTimes = new LinkedHashMap<>(); + regionResourceLevels = new LinkedHashMap<>(); + } + + public Data(Data data) { + resourceDefaultTimes = new LinkedHashMap<>(data.resourceDefaultTimes); + resourceTimeTrackingPolicies = new LinkedHashMap<>(data.resourceTimeTrackingPolicies); + resourcePropertyDefinitions = new LinkedHashMap<>(); + for (ResourceId resourceId : data.resourcePropertyDefinitions.keySet()) { + Map map = data.resourcePropertyDefinitions.get(resourceId); + Map newMap = new LinkedHashMap<>(map); + resourcePropertyDefinitions.put(resourceId, newMap); + } + + resourcePropertyValues = new LinkedHashMap<>(); + for (ResourceId resourceId : data.resourcePropertyValues.keySet()) { + Map map = data.resourcePropertyValues.get(resourceId); + Map newMap = new LinkedHashMap<>(map); + resourcePropertyValues.put(resourceId, newMap); + } + + personResourceLevels = new LinkedHashMap<>(); + for (ResourceId resourceId : data.personResourceLevels.keySet()) { + List list = data.personResourceLevels.get(resourceId); + List newlist = new ArrayList<>(list); + personResourceLevels.put(resourceId, newlist); + } + + personResourceTimes = new LinkedHashMap<>(); + for (ResourceId resourceId : data.personResourceTimes.keySet()) { + List list = data.personResourceTimes.get(resourceId); + List newlist = new ArrayList<>(list); + personResourceTimes.put(resourceId, newlist); + } + + regionResourceLevels = new LinkedHashMap<>(); + for (RegionId regionId : data.regionResourceLevels.keySet()) { + Map sourceMap = data.regionResourceLevels.get(regionId); + Map destinationMap = new LinkedHashMap<>(sourceMap); + regionResourceLevels.put(regionId, destinationMap); + } + + locked = data.locked; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Data [resourceDefaultTimes="); + builder.append(resourceDefaultTimes); + builder.append(", resourceTimeTrackingPolicies="); + builder.append(resourceTimeTrackingPolicies); + builder.append(", resourcePropertyDefinitions="); + builder.append(resourcePropertyDefinitions); + builder.append(", resourcePropertyValues="); + builder.append(resourcePropertyValues); + builder.append(", personResourceLevels="); + builder.append(personResourceLevels); + builder.append(", personResourceTimes="); + builder.append(personResourceTimes); + builder.append(", regionResourceLevels="); + builder.append(regionResourceLevels); + builder.append("]"); + return builder.toString(); + } + + /** + * + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + resourceDefaultTimes.hashCode(); + result = prime * result + resourceTimeTrackingPolicies.hashCode(); + result = prime * result + resourcePropertyDefinitions.hashCode(); + result = prime * result + resourcePropertyValues.hashCode(); + result = prime * result + personResourceLevels.hashCode(); + result = prime * result + personResourceTimes.hashCode(); + result = prime * result + regionResourceLevels.hashCode(); + + return result; + } + + /** + * + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + + /* + * We exclude the following fields: + * + * locked -- two Datas are only compared when they are both locked -- there are + * no equality comparisons in this class. + * + * + */ + // These are simply compared: + if (!resourceDefaultTimes.equals(other.resourceDefaultTimes)) { + return false; + } + + if (!resourceTimeTrackingPolicies.equals(other.resourceTimeTrackingPolicies)) { + return false; + } + + if (!resourcePropertyDefinitions.equals(other.resourcePropertyDefinitions)) { + return false; + } + + if (!resourcePropertyValues.equals(other.resourcePropertyValues)) { + return false; + } + + if (!personResourceLevels.equals(other.personResourceLevels)) { + return false; + } + + if (!personResourceTimes.equals(other.personResourceTimes)) { + return false; + } + + if (!regionResourceLevels.equals(other.regionResourceLevels)) { + return false; + } + + return true; + } + + } + + private final Data data; + + private ResourcesPluginData(Data data) { + this.data = data; + } + + /** + * Returns a new builder instance + */ + public static Builder builder() { + return new Builder(new Data()); + } + + private static void validateResourceIdNotNull(ResourceId resourceId) { + if (resourceId == null) { + throw new ContractException(ResourceError.NULL_RESOURCE_ID); + } + } + + private static void validateRegionIdNotNull(RegionId regionId) { + if (regionId == null) { + throw new ContractException(RegionError.NULL_REGION_ID); + } + } + + private static void validatePersonId(PersonId personId) { + if (personId == null) { + throw new ContractException(PersonError.NULL_PERSON_ID); + } + } + + private static void validateResourceAmount(final long amount) { + if (amount < 0) { + throw new ContractException(ResourceError.NEGATIVE_RESOURCE_AMOUNT, amount); + } + } + + private static void validateResourcePropertyIdNotNull(ResourcePropertyId resourcePropertyId) { + if (resourcePropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + } + + private static void validateResourcePropertyValueNotNull(Object resourcePropertyValue) { + if (resourcePropertyValue == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_VALUE); + } + } + + private static void validateResourcePropertyDefintionNotNull(PropertyDefinition propertyDefinition) { + if (propertyDefinition == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_DEFINITION); + } + } + + /** + * Builder class for ResourceInitialData + */ + public static class Builder implements PluginDataBuilder { + private Data data; + + private void ensureDataMutability() { + if (data.locked) { + data = new Data(data); + data.locked = false; + } + } + + private void ensureImmutability() { + if (!data.locked) { + data.locked = true; + } + } + + private Builder(Data data) { + this.data = data; + } + + /** + * Returns the ResourceInitialData built from the collected data. + * + * @throws ContractException + *
      + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if a resource tracking policy was collected for a + * resource that was not added
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if a resource property definition was collected for + * a resource that was not added
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if a resource property value was collected for a + * resource that was not added
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if a resource property value was collected for a + * resource property that is not associated with the + * given resource id
    • + *
    • {@linkplain PropertyError#INCOMPATIBLE_VALUE} + * if a resource property value was collected for a + * resource property that is not compatible with the + * associated resource property definition
    • + *
    • {@linkplain PropertyError#INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT} + * if a resource property definition has a null + * default value and there is no assigned resource + * property value for that resource
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if a resource level was collected for a person that + * is an unknown resource id
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if a resource level was collected for a region that + * is an unknown resource id
    • + *
    + */ + public ResourcesPluginData build() { + + if (!data.locked) { + validateData(); + } + ensureImmutability(); + return new ResourcesPluginData(data); + } + + /** + * Adds the given resouce id with default time value. Sets the time tracking + * policy for a resource. Duplicate inputs override previous inputs. + * + * @throws ContractException + *
      + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#NULL_TIME} if the + * time is null
    • + *
    + */ + public Builder addResource(final ResourceId resourceId, Double time, final boolean trackValueAssignmentTimes) { + ensureDataMutability(); + validateResourceIdNotNull(resourceId); + validateTime(time); + data.resourceDefaultTimes.put(resourceId, time); + data.resourceTimeTrackingPolicies.put(resourceId, trackValueAssignmentTimes); + return this; + } + + /** + * Defines a resource property Duplicate inputs override previous inputs. + * + * @throws ContractException + *
      + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the resource property id is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_DEFINITION} + * if the property definition is null
    • + *
    + */ + public Builder defineResourceProperty(final ResourceId resourceId, final ResourcePropertyId resourcePropertyId, + final PropertyDefinition propertyDefinition) { + ensureDataMutability(); + validateResourceIdNotNull(resourceId); + validateResourcePropertyIdNotNull(resourcePropertyId); + validateResourcePropertyDefintionNotNull(propertyDefinition); + Map map = data.resourcePropertyDefinitions.get(resourceId); + if (map == null) { + map = new LinkedHashMap<>(); + data.resourcePropertyDefinitions.put(resourceId, map); + } + map.put(resourcePropertyId, propertyDefinition); + return this; + } + + /** + * Sets a resource property value. Duplicate inputs override previous inputs. + * + * @throws ContractException + *
      + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the resource property id is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_VALUE} + * if the resource property value is null
    • + *
    + */ + public Builder setResourcePropertyValue(final ResourceId resourceId, + final ResourcePropertyId resourcePropertyId, final Object resourcePropertyValue) { + ensureDataMutability(); + validateResourceIdNotNull(resourceId); + validateResourcePropertyIdNotNull(resourcePropertyId); + validateResourcePropertyValueNotNull(resourcePropertyValue); + + Map propertyMap = data.resourcePropertyValues.get(resourceId); + if (propertyMap == null) { + propertyMap = new LinkedHashMap<>(); + data.resourcePropertyValues.put(resourceId, propertyMap); + } + propertyMap.put(resourcePropertyId, resourcePropertyValue); + return this; + } + + /** + * Sets a person's initial resource level. Duplicate inputs override previous + * inputs. + * + * @throws ContractException + *
      + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#NEGATIVE_RESOURCE_AMOUNT} + * if the resource amount is negative
    • + *
    + */ + public Builder setPersonResourceLevel(final PersonId personId, final ResourceId resourceId, final long amount) { + ensureDataMutability(); + validatePersonId(personId); + validateResourceIdNotNull(resourceId); + validateResourceAmount(amount); + + List list = data.personResourceLevels.get(resourceId); + if (list == null) { + list = new ArrayList<>(); + data.personResourceLevels.put(resourceId, list); + } + + int personIndex = personId.getValue(); + while (list.size() <= personIndex) { + list.add(null); + } + list.set(personIndex, amount); + + return this; + + } + + /** + * Sets a person's initial resource time. Duplicate inputs override previous + * inputs. + * + * @throws ContractException + *
      + *
    • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
    • + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#NULL_TIME} if the + * time is null
    • + *
    + */ + public Builder setPersonResourceTime(final PersonId personId, final ResourceId resourceId, final Double time) { + ensureDataMutability(); + validatePersonId(personId); + validateResourceIdNotNull(resourceId); + validateTime(time); + + List list = data.personResourceTimes.get(resourceId); + if (list == null) { + list = new ArrayList<>(); + data.personResourceTimes.put(resourceId, list); + } + + int personIndex = personId.getValue(); + while (list.size() <= personIndex) { + list.add(null); + } + list.set(personIndex, time); + + return this; + + } + + /** + * Sets a region's initial resource level. Duplicate inputs override previous + * inputs. + * + * @throws ContractException + *
      + *
    • {@linkplain RegionError#NULL_REGION_ID} if the + * region id is null
    • + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#NEGATIVE_RESOURCE_AMOUNT} + * if the resource amount is negative
    • + *
    + */ + public Builder setRegionResourceLevel(final RegionId regionId, final ResourceId resourceId, final long amount) { + ensureDataMutability(); + validateRegionIdNotNull(regionId); + validateResourceIdNotNull(resourceId); + validateResourceAmount(amount); + Map map = data.regionResourceLevels.get(regionId); + if (map == null) { + map = new LinkedHashMap<>(); + data.regionResourceLevels.put(regionId, map); + } + map.put(resourceId, amount); + return this; + } + + private void validateData() { + + /* + * validate resourcePropertyDefinitions + */ + for (ResourceId resourceId : data.resourcePropertyDefinitions.keySet()) { + if (!data.resourceDefaultTimes.containsKey(resourceId)) { + throw new ContractException(ResourceError.UNKNOWN_RESOURCE_ID, + resourceId + " has a property definitions but is not a known resource id"); + } + } + + /* + * validate resourcePropertyValues + * + * show that the resource ids are in data.resourceids + * + * show that the resource property ids are in the + * data.resourcePropertyDefinitions + * + * show that the values are compatible with the property definitions + */ + for (ResourceId resourceId : data.resourcePropertyValues.keySet()) { + if (!data.resourceDefaultTimes.containsKey(resourceId)) { + throw new ContractException(ResourceError.UNKNOWN_RESOURCE_ID, + resourceId + " for a collected resource property value"); + } + Map propMap = data.resourcePropertyDefinitions.get(resourceId); + Map map = data.resourcePropertyValues.get(resourceId); + for (ResourcePropertyId resourcePropertyId : map.keySet()) { + if (propMap == null || !propMap.containsKey(resourcePropertyId)) { + throw new ContractException(PropertyError.UNKNOWN_PROPERTY_ID, + resourcePropertyId + " has a resource property value under resource " + resourceId + + " but there is no corresponding property definition"); + } + Object propertyValue = map.get(resourcePropertyId); + PropertyDefinition propertyDefinition = propMap.get(resourcePropertyId); + if (!propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())) { + throw new ContractException(PropertyError.INCOMPATIBLE_VALUE, + resourcePropertyId + " has a resource property value of " + propertyValue + + " under resource " + resourceId + + " that is incompatible with the corresponding property definition"); + } + } + } + + // show that property definitions without default values have + // complete value coverage + for (ResourceId resourceId : data.resourcePropertyDefinitions.keySet()) { + Map propertyDefinitionMap = data.resourcePropertyDefinitions + .get(resourceId); + for (ResourcePropertyId resourcePropertyId : propertyDefinitionMap.keySet()) { + PropertyDefinition propertyDefinition = propertyDefinitionMap.get(resourcePropertyId); + if (!propertyDefinition.getDefaultValue().isPresent()) { + Object propertyValue = null; + Map propertyValueMap = data.resourcePropertyValues.get(resourceId); + if (propertyValueMap != null) { + propertyValue = propertyValueMap.get(resourcePropertyId); + } + if (propertyValue == null) { + throw new ContractException(PropertyError.INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT, + resourceId + ": " + resourcePropertyId); + } + } + } + } + + /* + * validate regionResourceLevels + */ + for (RegionId regionId : data.regionResourceLevels.keySet()) { + Map map = data.regionResourceLevels.get(regionId); + for (ResourceId resourceId : map.keySet()) { + if (!data.resourceDefaultTimes.containsKey(resourceId)) { + throw new ContractException(ResourceError.UNKNOWN_RESOURCE_ID, + resourceId + " for region " + regionId + " has a level, but is not a known resource"); + } + } + } + + /* + * validate personResourceLevels + */ + for (ResourceId resourceId : data.personResourceLevels.keySet()) { + if (!data.resourceDefaultTimes.containsKey(resourceId)) { + throw new ContractException(ResourceError.UNKNOWN_RESOURCE_ID, + resourceId + " has person resource levels, but is not a known resource id"); + } + } + + /* + * validate personResourceTimes + */ + // private final Map> personResourceTimes; + for (ResourceId resourceId : data.personResourceTimes.keySet()) { + if (!data.resourceDefaultTimes.containsKey(resourceId)) { + throw new ContractException(ResourceError.UNKNOWN_RESOURCE_ID, + resourceId + " has person resource times, but is not a known resource id"); + } + } + + for (ResourceId resourceId : data.personResourceTimes.keySet()) { + Boolean trackTimes = data.resourceTimeTrackingPolicies.get(resourceId); + if (!trackTimes) { + throw new ContractException(ResourceError.RESOURCE_ASSIGNMENT_TIME_NOT_TRACKED, + resourceId + " has person resource times, but the time tracking policy is false"); + } + } + + for (ResourceId resourceId : data.personResourceTimes.keySet()) { + Double creationTime = data.resourceDefaultTimes.get(resourceId); + List times = data.personResourceTimes.get(resourceId); + for (Double time : times) { + if (time != null) { + if (time < creationTime) { + throw new ContractException( + ResourceError.RESOURCE_ASSIGNMENT_TIME_PRECEEDS_RESOURCE_CREATION_TIME); + } + } + } + } + + } + + } + + private static void validateTime(Double time) { + if (time == null) { + throw new ContractException(ResourceError.NULL_TIME); + } + } + + private static void validateResourcePropertyIsDefined(final Data data, final ResourceId resourceId, + final ResourcePropertyId resourcePropertyId) { + Map map = data.resourcePropertyDefinitions.get(resourceId); + if (map == null) { + throw new ContractException(PropertyError.UNKNOWN_PROPERTY_ID, resourceId); + } + + if (!map.containsKey(resourcePropertyId)) { + throw new ContractException(PropertyError.UNKNOWN_PROPERTY_ID, resourcePropertyId); + } + } + + /** + * Returns the property definition associated with the resource id and resource + * property id. + * + * @throws ContractException + *
      + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if the resource id is unknown
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the resource property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the resource property id is unknown
    • + *
    + */ + public PropertyDefinition getResourcePropertyDefinition(final ResourceId resourceId, + final ResourcePropertyId resourcePropertyId) { + validateResourceExists(resourceId); + validateResourcePropertyIdNotNull(resourcePropertyId); + validateResourcePropertyIsDefined(data, resourceId, resourcePropertyId); + final Map defMap = data.resourcePropertyDefinitions.get(resourceId); + final PropertyDefinition propertyDefinition = defMap.get(resourcePropertyId); + return propertyDefinition; + } + + /** + * Returns the resource property id associated with the resource. + * + * @throws ContractException + *
      + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if the resource id is unknown
    • + *
    + */ + @SuppressWarnings("unchecked") + public Set getResourcePropertyIds(final ResourceId resourceId) { + validateResourceExists(resourceId); + Set result = new LinkedHashSet<>(); + Map defMap = data.resourcePropertyDefinitions.get(resourceId); + if (defMap != null) { + for (ResourcePropertyId resourcePropertyId : defMap.keySet()) { + result.add((T) resourcePropertyId); + } + } + return result; + } + + private void validateResourceExists(final ResourceId resourceId) { + if (resourceId == null) { + throw new ContractException(ResourceError.NULL_RESOURCE_ID); + } + if (!data.resourceDefaultTimes.containsKey(resourceId)) { + throw new ContractException(ResourceError.UNKNOWN_RESOURCE_ID, resourceId); + } + } + + /** + * Returns the resource property value associated with the resource id and + * resource property id. Returns the default value of the associated property + * definition if now value was assigned. + * + * @throws ContractException + *
      + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if the * resource id is unknown
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the resource property id is null
    • + *
    • {@linkplain PropertyError#UNKNOWN_PROPERTY_ID} + * if the resource property id is unknown
    • + *
    + */ + @SuppressWarnings("unchecked") + public Optional getResourcePropertyValue(final ResourceId resourceId, + final ResourcePropertyId resourcePropertyId) { + validateResourceExists(resourceId); + validateResourcePropertyIdNotNull(resourcePropertyId); + validateResourcePropertyIsDefined(data, resourceId, resourcePropertyId); + + Object result = null; + final Map map = data.resourcePropertyValues.get(resourceId); + if (map != null) { + result = map.get(resourcePropertyId); + } + return Optional.ofNullable((T) result); + } + + /** + * Returns an unmodifiable list of the initial resource levels for the given + * resource id. May contain null, may be empty. + * + * @throws ContractException + *
      + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if the resource id is unknown
    • + *
    + */ + public List getPersonResourceLevels(final ResourceId resourcId) { + validateResourceExists(resourcId); + List list = data.personResourceLevels.get(resourcId); + if (list != null) { + return Collections.unmodifiableList(list); + } + return new ArrayList<>(); + } + + /** + * Returns an unmodifiable list of the initial resource levels for the given + * resource id. May contain null, may be empty. + * + * @throws ContractException + *
      + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if the resource id is unknown
    • + *
    + */ + public List getPersonResourceTimes(final ResourceId resourcId) { + validateResourceExists(resourcId); + List list = data.personResourceTimes.get(resourcId); + if (list != null) { + return Collections.unmodifiableList(list); + } + return new ArrayList<>(); + } + + /** + * Returns the resource ids + */ + @SuppressWarnings("unchecked") + public Set getResourceIds() { + Set result = new LinkedHashSet<>(data.resourceDefaultTimes.size()); + for (ResourceId resourceId : data.resourceDefaultTimes.keySet()) { + result.add((T) resourceId); + } + return result; + } + + /** + * Returns the resource ids + * + * @throws ContractException + *
      + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if the resource id is unknown
    • + *
    + */ + public Double getResourceDefaultTime(ResourceId resourceId) { + validateResourceExists(resourceId); + return data.resourceDefaultTimes.get(resourceId); + } + + /** + * Returns the region's initial resource level. Returns 0 if no value was + * assigned during the build process. + * + * @throws ContractException + *
      + *
    • {@linkplain RegionError#NULL_REGION_ID} if the + * region id is null
    • + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if the resource id is unknown
    • + *
    + */ + public Optional getRegionResourceLevel(final RegionId regionId, final ResourceId resourceId) { + validateRegionIdNotNull(regionId); + validateResourceExists(resourceId); + Long result = null; + Map map = data.regionResourceLevels.get(regionId); + if (map != null) { + result = map.get(resourceId); + } + return Optional.ofNullable(result); + } + + /** + * Returns the tracking policy associated with the resource. + * + * @throws ContractException + *
      + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if the resource id is unknown
    • + *
    + */ + public boolean getResourceTimeTrackingPolicy(final ResourceId resourceId) { + validateResourceExists(resourceId); + return data.resourceTimeTrackingPolicies.get(resourceId); + } + + /** + * Returns the person ids associated with assigned resources + */ + public Set getRegionIds() { + return new LinkedHashSet<>(data.regionResourceLevels.keySet()); + } + + @Override + public PluginDataBuilder getCloneBuilder() { + return new Builder(data); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * data.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof ResourcesPluginData)) { + return false; + } + ResourcesPluginData other = (ResourcesPluginData) obj; + if (!data.equals(other.data)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder2 = new StringBuilder(); + builder2.append("ResourcesPluginData [data="); + builder2.append(data); + builder2.append("]"); + return builder2.toString(); + } + + public Map> getRegionResourceLevels() { + Map> result = new LinkedHashMap<>(); + for (RegionId regionId : data.regionResourceLevels.keySet()) { + Map map = data.regionResourceLevels.get(regionId); + Map newMap = new LinkedHashMap<>(map); + result.put(regionId, newMap); + } + return result; + } + + public Map getResourceDefaultTimes() { + return new LinkedHashMap<>(data.resourceDefaultTimes); + } + + public Map getResourceTimeTrackingPolicies() { + return new LinkedHashMap<>(data.resourceTimeTrackingPolicies); + } + + public Map> getPersonResourceTimes() { + Map> result = new LinkedHashMap<>(); + for (ResourceId resourceId : data.personResourceTimes.keySet()) { + List values = data.personResourceTimes.get(resourceId); + List newValues = new ArrayList<>(values); + result.put(resourceId, newValues); + } + return result; + } + + public Map> getPersonResourceLevels() { + Map> result = new LinkedHashMap<>(); + for (ResourceId resourceId : data.personResourceLevels.keySet()) { + List values = data.personResourceLevels.get(resourceId); + List newValues = new ArrayList<>(values); + result.put(resourceId, newValues); + } + return result; + } + + public Map> getResourcePropertyDefinitions() { + Map> result = new LinkedHashMap<>(); + + for (ResourceId resourceId : data.resourcePropertyDefinitions.keySet()) { + Map map = data.resourcePropertyDefinitions.get(resourceId); + Map newMap = new LinkedHashMap<>(map); + result.put(resourceId, newMap); + } + return result; + } + + public Map> getResourcePropertyValues() { + Map> result = new LinkedHashMap<>(); + + for (ResourceId resourceId : data.resourcePropertyValues.keySet()) { + Map map = data.resourcePropertyValues.get(resourceId); + Map newMap = new LinkedHashMap<>(map); + result.put(resourceId, newMap); + } + return result; + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/events/PersonResourceUpdateEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/events/PersonResourceUpdateEvent.java new file mode 100644 index 000000000..76d8b6759 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/events/PersonResourceUpdateEvent.java @@ -0,0 +1,14 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; +import net.jcip.annotations.Immutable; + +/** + * An observation event indicating that a person's resource level has changed. + */ +@Immutable +public record PersonResourceUpdateEvent(PersonId personId, ResourceId resourceId, long previousResourceLevel, + long currentResourceLevel) implements Event { +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/events/RegionResourceUpdateEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/events/RegionResourceUpdateEvent.java new file mode 100644 index 000000000..b8029a7bc --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/events/RegionResourceUpdateEvent.java @@ -0,0 +1,14 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; +import net.jcip.annotations.Immutable; + +/** + * An observation event indicating that a region's resource level has changed. + */ +@Immutable +public record RegionResourceUpdateEvent(RegionId regionId, ResourceId resourceId, long previousResourceLevel, + long currentResourceLevel) implements Event { +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/events/ResourceIdAdditionEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/events/ResourceIdAdditionEvent.java new file mode 100644 index 000000000..787938788 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/events/ResourceIdAdditionEvent.java @@ -0,0 +1,28 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceError; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * An observation event indicating that a resource id has been added. + */ +@Immutable +public record ResourceIdAdditionEvent(ResourceId resourceId, boolean timeTrackingPolicy) implements Event { + /** + * Constructs the event + * + * @throws ContractException {@linkplain ResourceError#NULL_RESOURCE_ID} if the + * resource id is null + */ + public ResourceIdAdditionEvent { + + if (resourceId == null) { + throw new ContractException(ResourceError.NULL_RESOURCE_ID); + } + + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/events/ResourcePropertyDefinitionEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/events/ResourcePropertyDefinitionEvent.java new file mode 100644 index 000000000..3580b10f3 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/events/ResourcePropertyDefinitionEvent.java @@ -0,0 +1,39 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceError; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourcePropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +@Immutable +public record ResourcePropertyDefinitionEvent(ResourceId resourceId, ResourcePropertyId resourcePropertyId, + Object resourcePropertyValue) implements Event { + /** + * Constructs the event. + * + * @throws ContractException + *
      + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * the resource id is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * the resource property id is null
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_VALUE} + * if the resource property value is null
    • + *
    + */ + public ResourcePropertyDefinitionEvent { + if (resourceId == null) { + throw new ContractException(ResourceError.NULL_RESOURCE_ID); + } + if (resourcePropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + if (resourcePropertyValue == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_VALUE); + } + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/events/ResourcePropertyUpdateEvent.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/events/ResourcePropertyUpdateEvent.java new file mode 100644 index 000000000..c5bed3980 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/events/ResourcePropertyUpdateEvent.java @@ -0,0 +1,14 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.events; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourcePropertyId; +import net.jcip.annotations.Immutable; + +/** + * An observation event indicating that a resource property has changed. + */ +@Immutable +public record ResourcePropertyUpdateEvent(ResourceId resourceId, ResourcePropertyId resourcePropertyId, + Object previousPropertyValue, Object currentPropertyValue) implements Event { +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/reports/PersonResourceReport.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/reports/PersonResourceReport.java new file mode 100644 index 000000000..c88a4e2fe --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/reports/PersonResourceReport.java @@ -0,0 +1,334 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.reports; + +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.events.PersonAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.events.PersonImminentRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.regions.events.PersonRegionUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.regions.events.RegionAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.PeriodicReport; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import gov.hhs.aspr.ms.gcm.plugins.resources.datamanagers.ResourcesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.resources.events.PersonResourceUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.resources.events.ResourceIdAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceError; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; +import util.errors.ContractException; +import util.wrappers.MutableInteger; + +/** + * A periodic Report that displays number of people who have/do not have any + * units of a particular resource with a region. Fields region -- the region + * identifier resource -- the resource identifier people_with_resource -- the + * number of people in the region who have at least one unit of the given + * resource people_without_resource -- the number of people in the region pair + * who do not have any units of the given resource + */ +public final class PersonResourceReport extends PeriodicReport { + public PersonResourceReport(PersonResourceReportPluginData personResourceReportPluginData) { + super(personResourceReportPluginData.getReportLabel(), personResourceReportPluginData.getReportPeriod()); + this.includedResourceIds.addAll(personResourceReportPluginData.getIncludedResourceIds()); + this.excludedResourceIds.addAll(personResourceReportPluginData.getExcludedResourceIds()); + this.includeNewResourceIds = personResourceReportPluginData.getDefaultInclusionPolicy(); + } + + /* + * The resources that will be used in this report. They are derived from the + * values passed in the init() method. + */ + private final boolean includeNewResourceIds; + private final Set includedResourceIds = new LinkedHashSet<>(); + private final Set currentResourceIds = new LinkedHashSet<>(); + private final Set excludedResourceIds = new LinkedHashSet<>(); + + // Mapping of the (regionId, resource Id, InventoryType) to + // sets of person id. Maintained via the processing of events. + private final Map> regionMap = new LinkedHashMap<>(); + + /* + * The derived header for this report + */ + private ReportHeader reportHeader; + + private ReportHeader getReportHeader() { + if (reportHeader == null) { + ReportHeader.Builder reportHeaderBuilder = ReportHeader.builder(); + addTimeFieldHeaders(reportHeaderBuilder)// + .add("region")// + .add("resource")// + .add("people_with_resource"); + reportHeaderBuilder.add("people_without_resource"); + reportHeader = reportHeaderBuilder.build(); + } + return reportHeader; + } + + /* + * Adds a person to the set of people associated with the given tuple + */ + private void inc(final RegionId regionId, final ResourceId resourceId) { + get(regionId, resourceId).increment(); + } + + private void dec(final RegionId regionId, final ResourceId resourceId) { + get(regionId, resourceId).decrement(); + } + + private MutableInteger get(final RegionId regionId, final ResourceId resourceId) { + Map map = regionMap.get(regionId); + if (map == null) { + map = new LinkedHashMap<>(); + regionMap.put(regionId, map); + } + MutableInteger mutableInteger = map.get(resourceId); + + if (mutableInteger == null) { + mutableInteger = new MutableInteger(); + map.put(resourceId, mutableInteger); + } + return mutableInteger; + } + + @Override + protected void flush(ReportContext reportContext) { + + for (final RegionId regionId : regionMap.keySet()) { + int populationCount = regionsDataManager.getRegionPopulationCount(regionId); + Map resourceMap = regionMap.get(regionId); + for (final ResourceId resourceId : resourceMap.keySet()) { + MutableInteger mutableInteger = resourceMap.get(resourceId); + ReportItem.Builder reportItemBuilder = ReportItem.builder(); + reportItemBuilder.setReportHeader(getReportHeader()); + reportItemBuilder.setReportLabel(getReportLabel()); + fillTimeFields(reportItemBuilder); + reportItemBuilder.addValue(regionId.toString()); + reportItemBuilder.addValue(resourceId.toString()); + reportItemBuilder.addValue(mutableInteger.getValue()); + reportItemBuilder.addValue(populationCount - mutableInteger.getValue()); + reportContext.releaseOutput(reportItemBuilder.build()); + } + + } + } + + private void handlePersonAdditionEvent(ReportContext reportContext, PersonAdditionEvent personAdditionEvent) { + PersonId personId = personAdditionEvent.personId(); + final RegionId regionId = regionsDataManager.getPersonRegion(personId); + + for (final ResourceId resourceId : currentResourceIds) { + final long personResourceLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); + if (personResourceLevel > 0) { + inc(regionId, resourceId); + } + } + } + + private void handlePersonImminentRemovalEvent(ReportContext reportContext, + PersonImminentRemovalEvent personImminentRemovalEvent) { + + PersonId personId = personImminentRemovalEvent.personId(); + + RegionId regionId = regionsDataManager.getPersonRegion(personId); + + for (ResourceId resourceId : currentResourceIds) { + Long amount = resourcesDataManager.getPersonResourceLevel(resourceId, personId); + if (amount > 0) { + dec(regionId, resourceId); + } + } + } + + private void handlePersonResourceUpdateEvent(ReportContext reportContext, + PersonResourceUpdateEvent personResourceUpdateEvent) { + ResourceId resourceId = personResourceUpdateEvent.resourceId(); + if (isCurrentProperty(resourceId)) { + PersonId personId = personResourceUpdateEvent.personId(); + RegionId regionId = regionsDataManager.getPersonRegion(personId); + long currentLevel = personResourceUpdateEvent.currentResourceLevel(); + long previousLevel = personResourceUpdateEvent.previousResourceLevel(); + if (previousLevel > 0 && currentLevel == 0) { + dec(regionId, resourceId); + } else if (previousLevel == 0 && currentLevel > 0) { + inc(regionId, resourceId); + } + } + } + + private void handlePersonRegionUpdateEvent(ReportContext reportContext, + PersonRegionUpdateEvent personRegionUpdateEvent) { + PersonId personId = personRegionUpdateEvent.personId(); + RegionId previousRegionId = personRegionUpdateEvent.previousRegionId(); + RegionId currentRegionId = personRegionUpdateEvent.currentRegionId(); + + for (final ResourceId resourceId : currentResourceIds) { + final long personResourceLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); + if (personResourceLevel > 0) { + dec(previousRegionId, resourceId); + inc(currentRegionId, resourceId); + } + } + } + + private RegionsDataManager regionsDataManager; + private ResourcesDataManager resourcesDataManager; + + private boolean isCurrentProperty(ResourceId resourceId) { + return currentResourceIds.contains(resourceId); + } + + private boolean addToCurrentResourceIds(ResourceId resourceId) { + + // There are eight possibilities: + + /* + * P -- the default inclusion policy + * + * I -- the property is explicitly included + * + * X -- the property is explicitly excluded + * + * C -- the property should be on the current properties + * + * + * P I X C Table + * + * TRUE TRUE FALSE TRUE + * + * TRUE FALSE FALSE TRUE + * + * FALSE TRUE FALSE TRUE + * + * FALSE FALSE FALSE FALSE + * + * TRUE TRUE TRUE FALSE -- not possible + * + * TRUE FALSE TRUE FALSE + * + * FALSE TRUE TRUE FALSE -- not possible + * + * FALSE FALSE TRUE FALSE + * + * + * Two of the cases above are contradictory since a property cannot be both + * explicitly included and explicitly excluded + * + */ + // if X is true then we don't add the property + if (excludedResourceIds.contains(resourceId)) { + return false; + } + + // if both P and I are false we don't add the property + boolean included = includedResourceIds.contains(resourceId); + + if (!included && !includeNewResourceIds) { + return false; + } + + // we have failed to reject the property + currentResourceIds.add(resourceId); + + return true; + } + + private PeopleDataManager peopleDataManager; + + /** + * @throws ContractException + *
      + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if a resource id passed to the constructor is + * unknown
    • + *
    • + *
    + */ + @Override + protected void prepare(final ReportContext reportContext) { + resourcesDataManager = reportContext.getDataManager(ResourcesDataManager.class); + peopleDataManager = reportContext.getDataManager(PeopleDataManager.class); + regionsDataManager = reportContext.getDataManager(RegionsDataManager.class); + + reportContext.subscribe(PersonAdditionEvent.class, this::handlePersonAdditionEvent); + reportContext.subscribe(PersonImminentRemovalEvent.class, this::handlePersonImminentRemovalEvent); + reportContext.subscribe(PersonRegionUpdateEvent.class, this::handlePersonRegionUpdateEvent); + reportContext.subscribe(RegionAdditionEvent.class, this::handleRegionAdditionEvent); + reportContext.subscribe(PersonResourceUpdateEvent.class, this::handlePersonResourceUpdateEvent); + reportContext.subscribe(ResourceIdAdditionEvent.class, this::handleResourceIdAdditionEvent); + + if (reportContext.stateRecordingIsScheduled()) { + reportContext.subscribeToSimulationClose(this::recordSimulationState); + } + + for (final ResourceId resourceId : resourcesDataManager.getResourceIds()) { + addToCurrentResourceIds(resourceId); + } + + /* + * Place the initial population in the mapping + */ + for (final PersonId personId : peopleDataManager.getPeople()) { + final RegionId regionId = regionsDataManager.getPersonRegion(personId); + for (final ResourceId resourceId : currentResourceIds) { + + final long personResourceLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); + if (personResourceLevel > 0) { + inc(regionId, resourceId); + } + } + } + + } + + private void recordSimulationState(ReportContext reportContext) { + PersonResourceReportPluginData.Builder builder = PersonResourceReportPluginData.builder(); + for (ResourceId resourceId : includedResourceIds) { + builder.includeResource(resourceId); + } + for (ResourceId resourceId : excludedResourceIds) { + builder.excludeResource(resourceId); + } + builder.setDefaultInclusion(includeNewResourceIds); + builder.setReportLabel(getReportLabel()); + builder.setReportPeriod(getReportPeriod()); + reportContext.releaseOutput(builder.build()); + } + + private void handleRegionAdditionEvent(ReportContext reportContext, RegionAdditionEvent regionAdditionEvent) { + RegionId regionId = regionAdditionEvent.getRegionId(); + + if (!regionMap.containsKey(regionId)) { + for (PersonId personId : regionsDataManager.getPeopleInRegion(regionId)) { + for (final ResourceId resourceId : currentResourceIds) { + long personResourceLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); + if (personResourceLevel > 0) { + inc(regionId, resourceId); + } + } + } + } + } + + private void handleResourceIdAdditionEvent(ReportContext reportContext, + ResourceIdAdditionEvent resourceIdAdditionEvent) { + ResourceId resourceId = resourceIdAdditionEvent.resourceId(); + if (addToCurrentResourceIds(resourceId)) { + for (PersonId personId : peopleDataManager.getPeople()) { + long personResourceLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); + if (personResourceLevel > 0) { + RegionId regionId = regionsDataManager.getPersonRegion(personId); + inc(regionId, resourceId); + } + } + } + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/reports/PersonResourceReportPluginData.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/reports/PersonResourceReportPluginData.java new file mode 100644 index 000000000..0538fbee6 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/reports/PersonResourceReportPluginData.java @@ -0,0 +1,250 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.reports; + +import java.util.LinkedHashSet; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.plugins.reports.support.PeriodicReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportError; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceError; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; +import net.jcip.annotations.ThreadSafe; +import util.errors.ContractException; + +/** + * A PluginData class supporting PersonResourceReport construction. + */ +@ThreadSafe +public final class PersonResourceReportPluginData extends PeriodicReportPluginData { + + private final Data data; + + private PersonResourceReportPluginData(Data data) { + super(data); + this.data = data; + } + + /* + * Data class for collecting the inputs to the report + */ + private static class Data extends PeriodicReportPluginData.Data { + private Set includedResourceIds = new LinkedHashSet<>(); + private Set excludedResourceIds = new LinkedHashSet<>(); + private boolean defaultInclusionPolicy = true; + + private Data() { + super(); + } + + private Data(Data data) { + super(data); + includedResourceIds.addAll(data.includedResourceIds); + excludedResourceIds.addAll(data.excludedResourceIds); + defaultInclusionPolicy = data.defaultInclusionPolicy; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + (defaultInclusionPolicy ? 1231 : 1237); + result = prime * result + ((excludedResourceIds == null) ? 0 : excludedResourceIds.hashCode()); + result = prime * result + ((includedResourceIds == null) ? 0 : includedResourceIds.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + if (defaultInclusionPolicy != other.defaultInclusionPolicy) { + return false; + } + if (excludedResourceIds == null) { + if (other.excludedResourceIds != null) { + return false; + } + } else if (!excludedResourceIds.equals(other.excludedResourceIds)) { + return false; + } + if (includedResourceIds == null) { + if (other.includedResourceIds != null) { + return false; + } + } else if (!includedResourceIds.equals(other.includedResourceIds)) { + return false; + } + + return super.equals(other); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(super.toString()); + builder.append(", includedResourceIds="); + builder.append(includedResourceIds); + builder.append(", excludedResourceIds="); + builder.append(excludedResourceIds); + builder.append(", defaultInclusionPolicy="); + builder.append(defaultInclusionPolicy); + builder.append("]"); + return builder.toString(); + } + } + + /** + * Builder class for the report + */ + public final static class Builder extends PeriodicReportPluginData.Builder { + private Data data; + + private Builder(Data data) { + super(data); + this.data = data; + } + + /** + * Returns a PersonPropertyReportPluginData created from the collected inputs + * + * @throws ContractException + *
      + *
    • {@linkplain ReportError#NULL_REPORT_LABEL} if + * the report label is not assigned
    • + *
    • {@linkplain ReportError#NULL_REPORT_PERIOD} if + * the report period is not assigned
    • + *
    + */ + public PersonResourceReportPluginData build() { + return new PersonResourceReportPluginData(data); + } + + /** + * Sets the default policy for inclusion of person properties in the report. + * This policy is used when a person property has not been explicitly included + * or excluded. Defaulted to true. + */ + public Builder setDefaultInclusion(boolean include) { + data.defaultInclusionPolicy = include; + return this; + } + + /** + * Selects the given resource id to be included in the report. + * + * @throws ContractException {@linkplain ResourceError#NULL_RESOURCE_ID} if the + * resource id is null + */ + public Builder includeResource(ResourceId resourceId) { + if (resourceId == null) { + throw new ContractException(ResourceError.NULL_RESOURCE_ID); + } + data.includedResourceIds.add(resourceId); + data.excludedResourceIds.remove(resourceId); + return this; + } + + /** + * Selects the given resource id to be excluded from the report + * + * @throws ContractException {@linkplain ResourceError#NULL_RESOURCE_ID} if the + * resource id is null + */ + public Builder excludeResource(ResourceId resourceId) { + if (resourceId == null) { + throw new ContractException(ResourceError.NULL_RESOURCE_ID); + } + data.includedResourceIds.remove(resourceId); + data.excludedResourceIds.add(resourceId); + return this; + } + + /** + * Sets the report label + * + * @throws ContractException {@linkplain ReportError#NULL_REPORT_LABEL} if the + * report label is null + */ + public Builder setReportLabel(ReportLabel reportLabel) { + super.setReportLabel(reportLabel); + return this; + } + + /** + * Sets the report period id + * + * @throws ContractException {@linkplain ReportError#NULL_REPORT_PERIOD} if the + * report period is null + */ + public Builder setReportPeriod(ReportPeriod reportPeriod) { + super.setReportPeriod(reportPeriod); + return this; + } + } + + /** + * Returns a new instance of the builder class + */ + public static Builder builder() { + return new Builder(new Data()); + } + + @Override + public Builder getCloneBuilder() { + return new Builder(new Data(data)); + } + + public Set getIncludedResourceIds() { + return new LinkedHashSet<>(data.includedResourceIds); + } + + public Set getExcludedResourceIds() { + return new LinkedHashSet<>(data.excludedResourceIds); + } + + public boolean getDefaultInclusionPolicy() { + return data.defaultInclusionPolicy; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof PersonResourceReportPluginData)) { + return false; + } + PersonResourceReportPluginData other = (PersonResourceReportPluginData) obj; + if (data == null) { + if (other.data != null) { + return false; + } + } else if (!data.equals(other.data)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder2 = new StringBuilder(); + builder2.append("PersonResourceReportPluginData [data="); + builder2.append(data); + builder2.append("]"); + return builder2.toString(); + } +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/reports/ResourcePropertyReport.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/reports/ResourcePropertyReport.java new file mode 100644 index 000000000..f9516b1a5 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/reports/ResourcePropertyReport.java @@ -0,0 +1,94 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.reports; + +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.resources.datamanagers.ResourcesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.resources.events.ResourcePropertyDefinitionEvent; +import gov.hhs.aspr.ms.gcm.plugins.resources.events.ResourcePropertyUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourcePropertyId; + +/** + * A Report that displays assigned resource property values over time. Fields + * Time -- the time in days when the resource property was set Resource -- the + * resource identifier Property -- the resource property identifier Value -- the + * value of the resource property + */ +public final class ResourcePropertyReport { + private final ReportLabel reportLabel; + + public ResourcePropertyReport(ResourcePropertyReportPluginData resourcePropertyReportPluginData) { + this.reportLabel = resourcePropertyReportPluginData.getReportLabel(); + } + + private ReportHeader reportHeader; + + private ReportHeader getReportHeader() { + if (reportHeader == null) { + reportHeader = ReportHeader.builder()// + .add("time")// + .add("resource")// + .add("property")// + .add("value")// + .build();// + } + return reportHeader; + } + + private void handleResourcePropertyUpdateEvent(ReportContext reportContext, + ResourcePropertyUpdateEvent resourcePropertyUpdateEvent) { + ResourceId resourceId = resourcePropertyUpdateEvent.resourceId(); + ResourcePropertyId resourcePropertyId = resourcePropertyUpdateEvent.resourcePropertyId(); + Object currentPropertyValue = resourcePropertyUpdateEvent.currentPropertyValue(); + writeProperty(reportContext, resourceId, resourcePropertyId, currentPropertyValue); + } + + public void init(final ReportContext reportContext) { + + reportContext.subscribe(ResourcePropertyUpdateEvent.class, this::handleResourcePropertyUpdateEvent); + reportContext.subscribe(ResourcePropertyDefinitionEvent.class, this::handleResourcePropertyAdditionEvent); + if (reportContext.stateRecordingIsScheduled()) { + reportContext.subscribeToSimulationClose(this::recordSimulationState); + } + + ResourcesDataManager resourcesDataManager = reportContext.getDataManager(ResourcesDataManager.class); + for (final ResourceId resourceId : resourcesDataManager.getResourceIds()) { + for (final ResourcePropertyId resourcePropertyId : resourcesDataManager + .getResourcePropertyIds(resourceId)) { + Object resourcePropertyValue = resourcesDataManager.getResourcePropertyValue(resourceId, + resourcePropertyId); + writeProperty(reportContext, resourceId, resourcePropertyId, resourcePropertyValue); + } + } + } + + private void recordSimulationState(ReportContext reportContext) { + ResourcePropertyReportPluginData.Builder builder = ResourcePropertyReportPluginData.builder(); + builder.setReportLabel(reportLabel); + reportContext.releaseOutput(builder.build()); + } + + private void handleResourcePropertyAdditionEvent(ReportContext reportContext, + ResourcePropertyDefinitionEvent resourcePropertyDefinitionEvent) { + ResourceId resourceId = resourcePropertyDefinitionEvent.resourceId(); + ResourcePropertyId resourcePropertyId = resourcePropertyDefinitionEvent.resourcePropertyId(); + Object resourcePropertyValue = resourcePropertyDefinitionEvent.resourcePropertyValue(); + writeProperty(reportContext, resourceId, resourcePropertyId, resourcePropertyValue); + } + + private void writeProperty(ReportContext reportContext, final ResourceId resourceId, + final ResourcePropertyId resourcePropertyId, Object resourcePropertyValue) { + final ReportItem.Builder reportItemBuilder = ReportItem.builder(); + reportItemBuilder.setReportHeader(getReportHeader()); + reportItemBuilder.setReportLabel(reportLabel); + + reportItemBuilder.addValue(reportContext.getTime()); + reportItemBuilder.addValue(resourceId.toString()); + reportItemBuilder.addValue(resourcePropertyId.toString()); + reportItemBuilder.addValue(resourcePropertyValue); + reportContext.releaseOutput(reportItemBuilder.build()); + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/reports/ResourcePropertyReportPluginData.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/reports/ResourcePropertyReportPluginData.java new file mode 100644 index 000000000..afabb88d3 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/reports/ResourcePropertyReportPluginData.java @@ -0,0 +1,192 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.reports; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.PluginDataBuilder; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportError; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import net.jcip.annotations.ThreadSafe; +import util.errors.ContractException; + +/** + * A PluginData class supporting PersonResourceReport construction. + */ +@ThreadSafe +public final class ResourcePropertyReportPluginData implements PluginData { + + /* + * Data class for collecting the inputs to the report + */ + private static class Data { + private ReportLabel reportLabel; + + private boolean locked; + + private Data() { + } + + private Data(Data data) { + reportLabel = data.reportLabel; + locked = data.locked; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((reportLabel == null) ? 0 : reportLabel.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + if (reportLabel == null) { + if (other.reportLabel != null) { + return false; + } + } else if (!reportLabel.equals(other.reportLabel)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Data [reportLabel="); + builder.append(reportLabel); + builder.append(", locked="); + builder.append(locked); + builder.append("]"); + return builder.toString(); + } + + } + + /** + * Returns a new instance of the builder class + */ + public static Builder builder() { + return new Builder(new Data()); + } + + /** + * Builder class for the report + */ + public final static class Builder implements PluginDataBuilder { + private Builder(Data data) { + this.data = data; + } + + private void ensureDataMutability() { + if (data.locked) { + data = new Data(data); + data.locked = false; + } + } + + private void ensureImmutability() { + if (!data.locked) { + data.locked = true; + } + } + + private void validateData() { + if (data.reportLabel == null) { + throw new ContractException(ReportError.NULL_REPORT_LABEL); + } + } + + private Data data; + + /** + * Returns a PersonPropertyReportPluginData created from the collected inputs + * + * @throws ContractException {@linkplain ReportError#NULL_REPORT_LABEL} if the + * report label is not assigned + */ + public ResourcePropertyReportPluginData build() { + + if (!data.locked) { + validateData(); + } + ensureImmutability(); + return new ResourcePropertyReportPluginData(data); + + } + + /** + * Sets the report label + * + * @throws ContractException {@linkplain ReportError#NULL_REPORT_LABEL} if the + * report label is null + */ + public Builder setReportLabel(ReportLabel reportLabel) { + ensureDataMutability(); + if (reportLabel == null) { + throw new ContractException(ReportError.NULL_REPORT_LABEL); + } + data.reportLabel = reportLabel; + return this; + } + + } + + private final Data data; + + private ResourcePropertyReportPluginData(Data data) { + this.data = data; + } + + @Override + public Builder getCloneBuilder() { + return new Builder(data); + } + + public ReportLabel getReportLabel() { + return data.reportLabel; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof ResourcePropertyReportPluginData)) { + return false; + } + ResourcePropertyReportPluginData other = (ResourcePropertyReportPluginData) obj; + if (data == null) { + if (other.data != null) { + return false; + } + } else if (!data.equals(other.data)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder2 = new StringBuilder(); + builder2.append("ResourcePropertyReportPluginData [data="); + builder2.append(data); + builder2.append("]"); + return builder2.toString(); + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/reports/ResourceReport.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/reports/ResourceReport.java new file mode 100644 index 000000000..a6b5cc851 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/reports/ResourceReport.java @@ -0,0 +1,412 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.reports; + +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.events.PersonAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.events.PersonImminentRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.regions.events.PersonRegionUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.regions.events.RegionAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.PeriodicReport; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import gov.hhs.aspr.ms.gcm.plugins.resources.datamanagers.ResourcesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.resources.events.PersonResourceUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.resources.events.RegionResourceUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.resources.events.ResourceIdAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceError; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; +import util.errors.ContractException; + +/** + * A periodic Report that displays the creation, transfer or consumption of + * resources within a region. Only activities with non-zero action counts are + * reported. Fields region -- the region identifier resource -- the resource + * identifier activity -- the activity that leads to the creation, transfer or + * consumption of a resource unit(s) actions -- the number of individual actions + * that were associated with the activity items -- the number of units of the + * resource that were associated with the activity Activities person_addition -- + * the addition of a person to the simulation person_departure -- the removal of + * a person from the simulation person_region_arrival -- the arrival of a person + * into the region from another region person_region_departure -- the departure + * of a person from the region to another region region_resource_addition -- the + * creation of a resource unit(s) on the region person_resource_addition -- the + * creation of a resource unit(s) on a person(associate with simulation + * bootstrap) region_resource_removal -- the destruction of a resource unit(s) + * on the region resource_transfer_into_region -- the transfer of units of + * resource from another region resource_transfer_out_of_region -- the transfer + * of units of resource to another region resource_transfer_from_person -- the + * return of resource units from a person in the region to the region + * resource_transfer_to_person -- the distribution of resource units to a person + * in the region from the region resource_removal_from_person -- the destruction + * of a resource unit(s) on a person + */ +public final class ResourceReport extends PeriodicReport { + private final boolean includeNewResourceIds; + + private final Set includedResourceIds = new LinkedHashSet<>(); + private final Set currentResourceIds = new LinkedHashSet<>(); + private final Set excludedResourceIds = new LinkedHashSet<>(); + + public ResourceReport(ResourceReportPluginData resourceReportPluginData) { + super(resourceReportPluginData.getReportLabel(), resourceReportPluginData.getReportPeriod()); + this.includedResourceIds.addAll(resourceReportPluginData.getIncludedResourceIds()); + this.excludedResourceIds.addAll(resourceReportPluginData.getExcludedResourceIds()); + this.includeNewResourceIds = resourceReportPluginData.getDefaultInclusionPolicy(); + + } + + private static enum Activity { + PERSON_ARRIVAL("person_arrival"), PERSON_DEPARTURE("person_departure"), + PERSON_REGION_ARRIVAL("person_region_arrival"), PERSON_REGION_DEPARTURE("person_region_departure"), + REGION_RESOURCE_ADDITION("region_resource_addition"), PERSON_RESOURCE_ADDITION("person_resource_addition"), + REGION_RESOURCE_REMOVAL("region_resource_removal"), + RESOURCE_TRANSFER_INTO_REGION("resource_transfer_into_region"), + RESOURCE_TRANSFER_OUT_OF_REGION("resource_transfer_out_of_region"), + RESOURCE_TRANSFER_FROM_MATERIALS_PRODUCER("resource_transfer_from_materials_producer"), + TRANSFER_RESOURCE_FROM_PERSON("transfer_resource_from_person"), + TRANSFER_RESOURCE_TO_PERSON("transfer_resource_to_person"), + REMOVE_RESOURCE_FROM_PERSON("remove_resource_from_person"); + + private final String displayName; + + Activity(final String displayName) { + this.displayName = displayName; + } + } + + private static class Counter { + private int actionCount; + private long itemCount; + + private void reset() { + actionCount = 0; + itemCount = 0; + } + } + + /* + * The mapping of (Region, Resource, Activity) tuples to counters that record + * the number of actions and the number of items handled across those actions. + */ + private final Map>> regionMap = new LinkedHashMap<>(); + + /* + * The derived header for this report + */ + private ReportHeader reportHeader; + + private ReportHeader getReportHeader() { + if (reportHeader == null) { + ReportHeader.Builder reportHeaderBuilder = ReportHeader.builder(); + reportHeader = addTimeFieldHeaders(reportHeaderBuilder).add("region")// + .add("resource")// + .add("activity")// + .add("actions")// + .add("items")// + .build();// + } + return reportHeader; + } + + @Override + protected void flush(ReportContext reportContext) { + + for (final RegionId regionId : regionMap.keySet()) { + + final Map> resourceMap = regionMap.get(regionId); + for (final ResourceId resourceId : resourceMap.keySet()) { + final Map activityMap = resourceMap.get(resourceId); + for (final Activity activity : activityMap.keySet()) { + final Counter counter = activityMap.get(activity); + if (counter.actionCount > 0) { + ReportItem.Builder reportItemBuilder = ReportItem.builder(); + reportItemBuilder.setReportHeader(getReportHeader()); + reportItemBuilder.setReportLabel(getReportLabel()); + fillTimeFields(reportItemBuilder); + + reportItemBuilder.addValue(regionId.toString()); + reportItemBuilder.addValue(resourceId.toString()); + reportItemBuilder.addValue(activity.displayName); + reportItemBuilder.addValue(counter.actionCount); + reportItemBuilder.addValue(counter.itemCount); + reportContext.releaseOutput(reportItemBuilder.build()); + counter.reset(); + } + } + } + + } + } + + private void handlePersonAdditionEvent(ReportContext reportContext, PersonAdditionEvent personAdditionEvent) { + PersonId personId = personAdditionEvent.personId(); + final RegionId regionId = regionsDataManager.getPersonRegion(personId); + for (final ResourceId resourceId : currentResourceIds) { + final long personResourceLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); + if (personResourceLevel > 0) { + increment(regionId, resourceId, Activity.PERSON_ARRIVAL, personResourceLevel); + } + } + } + + private void handlePersonImminentRemovalEvent(ReportContext reportContext, + PersonImminentRemovalEvent personImminentRemovalEvent) { + + PersonId personId = personImminentRemovalEvent.personId(); + RegionId regionId = regionsDataManager.getPersonRegion(personId); + + for (ResourceId resourceId : currentResourceIds) { + final Long personResourceLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); + if (personResourceLevel > 0) { + increment(regionId, resourceId, Activity.PERSON_DEPARTURE, personResourceLevel); + } + } + } + + private void handlePersonResourceUpdateEvent(ReportContext reportContext, + PersonResourceUpdateEvent personResourceUpdateEvent) { + + final PersonId personId = personResourceUpdateEvent.personId(); + final ResourceId resourceId = personResourceUpdateEvent.resourceId(); + final long previousLevel = personResourceUpdateEvent.previousResourceLevel(); + final long currentLevel = personResourceUpdateEvent.currentResourceLevel(); + if (isCurrentProperty(resourceId)) { + + long amount = currentLevel - previousLevel; + if (amount > 0) { + final RegionId regionId = regionsDataManager.getPersonRegion(personId); + increment(regionId, resourceId, Activity.PERSON_RESOURCE_ADDITION, amount); + } else { + amount = -amount; + final RegionId regionId = regionsDataManager.getPersonRegion(personId); + increment(regionId, resourceId, Activity.REMOVE_RESOURCE_FROM_PERSON, amount); + } + } + } + + private void handlePersonRegionUpdateEvent(ReportContext reportContext, + PersonRegionUpdateEvent personRegionUpdateEvent) { + PersonId personId = personRegionUpdateEvent.personId(); + RegionId previousRegionId = personRegionUpdateEvent.previousRegionId(); + RegionId currentRegionId = personRegionUpdateEvent.currentRegionId(); + + for (final ResourceId resourceId : currentResourceIds) { + final long personResourceLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); + if (personResourceLevel > 0) { + increment(currentRegionId, resourceId, Activity.PERSON_REGION_ARRIVAL, personResourceLevel); + increment(previousRegionId, resourceId, Activity.PERSON_REGION_DEPARTURE, personResourceLevel); + } + } + } + + private void handleResourceIdAdditionEvent(ReportContext reportContext, + ResourceIdAdditionEvent resourceIdAdditionEvent) { + ResourceId resourceId = resourceIdAdditionEvent.resourceId(); + + if (addToCurrentResourceIds(resourceId)) { + for (RegionId regionId : regionMap.keySet()) { + Map> map = regionMap.get(regionId); + Map activityMap = new LinkedHashMap<>(); + for (Activity activity : Activity.values()) { + activityMap.put(activity, new Counter()); + } + map.put(resourceId, activityMap); + } + } + } + + private void handleRegionResourceUpdateEvent(ReportContext reportContext, + RegionResourceUpdateEvent regionResourceUpdateEvent) { + + ResourceId resourceId = regionResourceUpdateEvent.resourceId(); + if (isCurrentProperty(resourceId)) { + RegionId regionId = regionResourceUpdateEvent.regionId(); + long previousResourceLevel = regionResourceUpdateEvent.previousResourceLevel(); + long currentResourceLevel = regionResourceUpdateEvent.currentResourceLevel(); + long amount = currentResourceLevel - previousResourceLevel; + if (amount > 0) { + increment(regionId, resourceId, Activity.REGION_RESOURCE_ADDITION, amount); + } else { + amount = -amount; + increment(regionId, resourceId, Activity.REGION_RESOURCE_REMOVAL, amount); + } + } + } + + /* + * Increments the counter for the given tuple + */ + private void increment(final RegionId regionId, final ResourceId resourceId, final Activity activity, + final long count) { + final Map> resourceMap = regionMap.get(regionId); + final Map activityMap = resourceMap.get(resourceId); + final Counter counter = activityMap.get(activity); + counter.actionCount++; + counter.itemCount += count; + } + + private RegionsDataManager regionsDataManager; + private ResourcesDataManager resourcesDataManager; + + private boolean isCurrentProperty(ResourceId resourceId) { + return currentResourceIds.contains(resourceId); + } + + private boolean addToCurrentResourceIds(ResourceId resourceId) { + + // There are eight possibilities: + + /* + * P -- the default inclusion policy + * + * I -- the property is explicitly included + * + * X -- the property is explicitly excluded + * + * C -- the property should be on the current properties + * + * + * P I X C Table + * + * TRUE TRUE FALSE TRUE + * + * TRUE FALSE FALSE TRUE + * + * FALSE TRUE FALSE TRUE + * + * FALSE FALSE FALSE FALSE + * + * TRUE TRUE TRUE FALSE -- not possible + * + * TRUE FALSE TRUE FALSE + * + * FALSE TRUE TRUE FALSE -- not possible + * + * FALSE FALSE TRUE FALSE + * + * + * Two of the cases above are contradictory since a property cannot be both + * explicitly included and explicitly excluded + * + */ + // if X is true then we don't add the property + if (excludedResourceIds.contains(resourceId)) { + return false; + } + + // if both P and I are false we don't add the property + boolean included = includedResourceIds.contains(resourceId); + + if (!included && !includeNewResourceIds) { + return false; + } + + // we have failed to reject the property + currentResourceIds.add(resourceId); + + return true; + } + + /** + * @throws ContractException + *
      + *
    • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} + * if a resource id passed to the constructor is + * unknown
    • + *
    • + *
    + */ + @Override + protected void prepare(final ReportContext reportContext) { + resourcesDataManager = reportContext.getDataManager(ResourcesDataManager.class); + PeopleDataManager peopleDataManager = reportContext.getDataManager(PeopleDataManager.class); + RegionsDataManager regionsDataManager = reportContext.getDataManager(RegionsDataManager.class); + + reportContext.subscribe(PersonAdditionEvent.class, this::handlePersonAdditionEvent); + reportContext.subscribe(PersonImminentRemovalEvent.class, this::handlePersonImminentRemovalEvent); + reportContext.subscribe(PersonRegionUpdateEvent.class, this::handlePersonRegionUpdateEvent); + reportContext.subscribe(RegionResourceUpdateEvent.class, this::handleRegionResourceUpdateEvent); + reportContext.subscribe(RegionAdditionEvent.class, this::handleRegionAdditionEvent); + reportContext.subscribe(PersonResourceUpdateEvent.class, this::handlePersonResourceUpdateEvent); + reportContext.subscribe(ResourceIdAdditionEvent.class, this::handleResourceIdAdditionEvent); + if (reportContext.stateRecordingIsScheduled()) { + reportContext.subscribeToSimulationClose(this::recordSimulationState); + } + + for (final ResourceId resourceId : resourcesDataManager.getResourceIds()) { + addToCurrentResourceIds(resourceId); + } + + /* + * Filling the region map with empty counters + */ + for (final RegionId regionId : regionsDataManager.getRegionIds()) { + final Map> resourceMap = new LinkedHashMap<>(); + regionMap.put(regionId, resourceMap); + for (final ResourceId resourceId : currentResourceIds) { + final Map activityMap = new LinkedHashMap<>(); + resourceMap.put(resourceId, activityMap); + for (final Activity activity : Activity.values()) { + final Counter counter = new Counter(); + activityMap.put(activity, counter); + } + } + } + + for (PersonId personId : peopleDataManager.getPeople()) { + final RegionId regionId = regionsDataManager.getPersonRegion(personId); + + for (final ResourceId resourceId : currentResourceIds) { + final long personResourceLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); + if (personResourceLevel > 0) { + increment(regionId, resourceId, Activity.PERSON_ARRIVAL, personResourceLevel); + } + } + } + + for (RegionId regionId : regionsDataManager.getRegionIds()) { + for (ResourceId resourceId : currentResourceIds) { + long regionResourceLevel = resourcesDataManager.getRegionResourceLevel(regionId, resourceId); + increment(regionId, resourceId, Activity.REGION_RESOURCE_ADDITION, regionResourceLevel); + } + } + } + + private void recordSimulationState(ReportContext reportContext) { + ResourceReportPluginData.Builder builder = ResourceReportPluginData.builder(); + for (ResourceId resourceId : includedResourceIds) { + builder.includeResource(resourceId); + } + for (ResourceId resourceId : excludedResourceIds) { + builder.excludeResource(resourceId); + } + builder.setDefaultInclusion(includeNewResourceIds); + builder.setReportLabel(getReportLabel()); + builder.setReportPeriod(getReportPeriod()); + reportContext.releaseOutput(builder.build()); + } + + private void handleRegionAdditionEvent(ReportContext reportContext, RegionAdditionEvent regionAdditionEvent) { + RegionId regionId = regionAdditionEvent.getRegionId(); + + final Map> resourceMap = new LinkedHashMap<>(); + regionMap.put(regionId, resourceMap); + for (final ResourceId resourceId : currentResourceIds) { + final Map activityMap = new LinkedHashMap<>(); + resourceMap.put(resourceId, activityMap); + for (final Activity activity : Activity.values()) { + final Counter counter = new Counter(); + activityMap.put(activity, counter); + } + } + + } +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/reports/ResourceReportPluginData.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/reports/ResourceReportPluginData.java new file mode 100644 index 000000000..2590fa1cf --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/reports/ResourceReportPluginData.java @@ -0,0 +1,253 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.reports; + +import java.util.LinkedHashSet; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.plugins.reports.support.PeriodicReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportError; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceError; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; +import net.jcip.annotations.ThreadSafe; +import util.errors.ContractException; + +/** + * A PluginData class supporting PersonResourceReport construction. + */ +@ThreadSafe +public final class ResourceReportPluginData extends PeriodicReportPluginData { + + private final Data data; + + private ResourceReportPluginData(Data data) { + super(data); + this.data = data; + } + + /* + * Data class for collecting the inputs to the report + */ + private static class Data extends PeriodicReportPluginData.Data { + + private Set includedResourceIds = new LinkedHashSet<>(); + private Set excludedResourceIds = new LinkedHashSet<>(); + private boolean defaultInclusionPolicy = true; + + private Data() { + super(); + } + + private Data(Data data) { + super(data); + includedResourceIds.addAll(data.includedResourceIds); + excludedResourceIds.addAll(data.excludedResourceIds); + defaultInclusionPolicy = data.defaultInclusionPolicy; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + (defaultInclusionPolicy ? 1231 : 1237); + result = prime * result + ((excludedResourceIds == null) ? 0 : excludedResourceIds.hashCode()); + result = prime * result + ((includedResourceIds == null) ? 0 : includedResourceIds.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + if (defaultInclusionPolicy != other.defaultInclusionPolicy) { + return false; + } + if (excludedResourceIds == null) { + if (other.excludedResourceIds != null) { + return false; + } + } else if (!excludedResourceIds.equals(other.excludedResourceIds)) { + return false; + } + if (includedResourceIds == null) { + if (other.includedResourceIds != null) { + return false; + } + } else if (!includedResourceIds.equals(other.includedResourceIds)) { + return false; + } + return super.equals(other); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(super.toString()); + builder.append(", includedResourceIds="); + builder.append(includedResourceIds); + builder.append(", excludedResourceIds="); + builder.append(excludedResourceIds); + builder.append(", defaultInclusionPolicy="); + builder.append(defaultInclusionPolicy); + builder.append("]"); + return builder.toString(); + } + + } + + /** + * Builder class for the report + */ + public final static class Builder extends PeriodicReportPluginData.Builder { + + private Data data; + + private Builder(Data data) { + super(data); + this.data = data; + } + + /** + * Returns a PersonPropertyReportPluginData created from the collected inputs + * + * @throws ContractException + *
      + *
    • {@linkplain ReportError#NULL_REPORT_LABEL} if + * the report label is not assigned
    • + *
    • {@linkplain ReportError#NULL_REPORT_PERIOD} if + * the report period is not assigned
    • + *
    + */ + public ResourceReportPluginData build() { + return new ResourceReportPluginData(data); + } + + /** + * Sets the default policy for inclusion of person properties in the report. + * This policy is used when a person property has not been explicitly included + * or excluded. Defaulted to true. + */ + public Builder setDefaultInclusion(boolean include) { + data.defaultInclusionPolicy = include; + return this; + } + + /** + * Selects the given resource id to be included in the report. + * + * @throws ContractException {@linkplain ResourceError#NULL_RESOURCE_ID} if the + * resource id is null + */ + public Builder includeResource(ResourceId resourceId) { + if (resourceId == null) { + throw new ContractException(ResourceError.NULL_RESOURCE_ID); + } + data.includedResourceIds.add(resourceId); + data.excludedResourceIds.remove(resourceId); + return this; + } + + /** + * Selects the given resource id to be excluded from the report + * + * @throws ContractException {@linkplain ResourceError#NULL_RESOURCE_ID} if the + * resource id is null + */ + public Builder excludeResource(ResourceId resourceId) { + if (resourceId == null) { + throw new ContractException(ResourceError.NULL_RESOURCE_ID); + } + data.includedResourceIds.remove(resourceId); + data.excludedResourceIds.add(resourceId); + return this; + } + + /** + * Sets the report label + * + * @throws ContractException {@linkplain ReportError#NULL_REPORT_LABEL} if the + * report label is null + */ + public Builder setReportLabel(ReportLabel reportLabel) { + super.setReportLabel(reportLabel); + return this; + } + + /** + * Sets the report period id + * + * @throws ContractException {@linkplain ReportError#NULL_REPORT_PERIOD} if the + * report period is null + */ + public Builder setReportPeriod(ReportPeriod reportPeriod) { + super.setReportPeriod(reportPeriod); + return this; + } + } + + /** + * Returns a new instance of the builder class + */ + public static Builder builder() { + return new Builder(new Data()); + } + + @Override + public Builder getCloneBuilder() { + return new Builder(new Data(data)); + } + + public Set getIncludedResourceIds() { + return new LinkedHashSet<>(data.includedResourceIds); + } + + public Set getExcludedResourceIds() { + return new LinkedHashSet<>(data.excludedResourceIds); + } + + public boolean getDefaultInclusionPolicy() { + return data.defaultInclusionPolicy; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof ResourceReportPluginData)) { + return false; + } + ResourceReportPluginData other = (ResourceReportPluginData) obj; + if (data == null) { + if (other.data != null) { + return false; + } + } else if (!data.equals(other.data)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder2 = new StringBuilder(); + builder2.append("ResourceReportPluginData [data="); + builder2.append(data); + builder2.append("]"); + return builder2.toString(); + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/support/ResourceError.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/support/ResourceError.java new file mode 100644 index 000000000..93ddbd358 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/support/ResourceError.java @@ -0,0 +1,43 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.support; + +import util.errors.ContractError; +import util.errors.ContractException; + +/** + * An enumeration supporting {@link ContractException} that acts as a general + * description of the exception. + */ +public enum ResourceError implements ContractError { + + INSUFFICIENT_RESOURCES_AVAILABLE("Resource level is insufficient for transaction amount"), + DUPLICATE_RESOURCE_ID("Duplicate resource"), + DUPLICATE_REGION_RESOURCE_LEVEL_ASSIGNMENT("Duplicate region resource level assignment"), + NEGATIVE_RESOURCE_AMOUNT("Resource amount is negative"), NULL_AMOUNT("Null amount"), NULL_TIME("Null time"), + NULL_RESOURCE_ID("Null resource id"), NULL_RESOURCE_DATA_MANAGER("Null resource data manager"), + NULL_RESOURCE_PLUGIN_DATA("Null resource plugin data"), + NULL_PERSON_RESOURCE_REPORT_PLUGIN_DATA("Null person resource report plugin data"), + NULL_RESOURCE_PROPERTY_REPORT_PLUGIN_DATA("Null resource property report plugin data"), + NULL_RESOURCE_REPORT_PLUGIN_DATA("Null resource report plugin data"), + NULL_TIME_TRACKING_POLICY("Null time tracking policy"), + REFLEXIVE_RESOURCE_TRANSFER("Cannot transfer resources from a region to itself"), + RESOURCE_ARITHMETIC_EXCEPTION("Resource arithmetic resulting in underflow/overflow"), + RESOURCE_ASSIGNMENT_TIME_NOT_TRACKED("Resource assignment time not actively tracked"), + RESOURCE_CREATION_TIME_EXCEEDS_SIM_TIME("Resource creation time exceeds current simulation time"), + RESOURCE_ASSIGNMENT_TIME_PRECEEDS_RESOURCE_CREATION_TIME( + "Resource assignment time preceeds resource creation time"), + RESOURCE_ASSIGNMENT_TIME_EXCEEDS_SIM_TIME("Resource assignment time exceeds current simulation time"), + UNKNOWN_RESOURCE_ID("Unknown resource id"), + + ; + + private final String description; + + private ResourceError(final String description) { + this.description = description; + } + + @Override + public String getDescription() { + return description; + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/support/ResourceFilter.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/support/ResourceFilter.java new file mode 100644 index 000000000..9db468d04 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/support/ResourceFilter.java @@ -0,0 +1,154 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.support; + +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.Equality; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.FilterSensitivity; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters.Filter; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.resources.datamanagers.ResourcesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.resources.events.PersonResourceUpdateEvent; +import util.errors.ContractException; + +public final class ResourceFilter extends Filter { + private final ResourceId resourceId; + private final long resourceValue; + private final Equality equality; + private ResourcesDataManager resourcesDataManager; + + private void validateResourceId(PartitionsContext partitionsContext, final ResourceId resourceId) { + if (resourceId == null) { + throw new ContractException(ResourceError.NULL_RESOURCE_ID); + } + + if (partitionsContext == null) { + throw new ContractException(NucleusError.NULL_SIMULATION_CONTEXT); + } + + if (resourcesDataManager == null) { + resourcesDataManager = partitionsContext.getDataManager(ResourcesDataManager.class); + } + + if (!resourcesDataManager.resourceIdExists(resourceId)) { + throw new ContractException(ResourceError.UNKNOWN_RESOURCE_ID, resourceId); + } + } + + private void validateEquality(PartitionsContext partitionsContext, final Equality equality) { + if (equality == null) { + throw new ContractException(PartitionError.NULL_EQUALITY_OPERATOR); + } + } + + public ResourceId getResourceId() { + return resourceId; + } + + public Equality getEquality() { + return equality; + } + + public long getResourceValue() { + return resourceValue; + } + + public ResourceFilter(final ResourceId resourceId, final Equality equality, final long resourceValue) { + this.resourceId = resourceId; + this.resourceValue = resourceValue; + this.equality = equality; + } + + @Override + public void validate(PartitionsContext partitionsContext) { + validateEquality(partitionsContext, equality); + validateResourceId(partitionsContext, resourceId); + } + + @Override + public boolean evaluate(PartitionsContext partitionsContext, PersonId personId) { + if (partitionsContext == null) { + throw new ContractException(NucleusError.NULL_SIMULATION_CONTEXT); + } + + if (resourcesDataManager == null) { + resourcesDataManager = partitionsContext.getDataManager(ResourcesDataManager.class); + } + + final long level = resourcesDataManager.getPersonResourceLevel(resourceId, personId); + return equality.isCompatibleComparisonValue(Long.compare(level, resourceValue)); + } + + private Optional requiresRefresh(PartitionsContext partitionsContext, PersonResourceUpdateEvent event) { + if (event.resourceId().equals(resourceId)) { + long previousResourceLevel = event.previousResourceLevel(); + long currentResourceLevel = event.currentResourceLevel(); + if (equality.isCompatibleComparisonValue(Long.compare(previousResourceLevel, resourceValue)) != equality + .isCompatibleComparisonValue(Long.compare(currentResourceLevel, resourceValue))) { + return Optional.of(event.personId()); + } + } + return Optional.empty(); + } + + @Override + public Set> getFilterSensitivities() { + Set> result = new LinkedHashSet<>(); + result.add(new FilterSensitivity(PersonResourceUpdateEvent.class, + this::requiresRefresh)); + return result; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((equality == null) ? 0 : equality.hashCode()); + result = prime * result + ((resourceId == null) ? 0 : resourceId.hashCode()); + result = prime * result + (int) (resourceValue ^ (resourceValue >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof ResourceFilter)) { + return false; + } + ResourceFilter other = (ResourceFilter) obj; + if (equality != other.equality) { + return false; + } + if (resourceId == null) { + if (other.resourceId != null) { + return false; + } + } else if (!resourceId.equals(other.resourceId)) { + return false; + } + if (resourceValue != other.resourceValue) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("ResourceFilter [resourceId="); + builder.append(resourceId); + builder.append(", resourceValue="); + builder.append(resourceValue); + builder.append(", equality="); + builder.append(equality); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/support/ResourceId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/support/ResourceId.java new file mode 100644 index 000000000..9b7ac8b0c --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/support/ResourceId.java @@ -0,0 +1,15 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.support; + +import net.jcip.annotations.ThreadSafe; + +/** + * Marker interface for resource identifiers + */ +@ThreadSafe +public interface ResourceId { + @Override + public int hashCode(); + + @Override + public boolean equals(Object obj); +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/support/ResourceInitialization.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/support/ResourceInitialization.java new file mode 100644 index 000000000..5a4e5356b --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/support/ResourceInitialization.java @@ -0,0 +1,70 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.support; + +import net.jcip.annotations.Immutable; + +@Immutable +public final class ResourceInitialization { + private final ResourceId resourceId; + private final Long amount; + + public ResourceInitialization(ResourceId resourceId, Long amount) { + super(); + this.resourceId = resourceId; + this.amount = amount; + } + + public ResourceId getResourceId() { + return resourceId; + } + + public Long getAmount() { + return amount; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("ResourceAssignment [resourceId="); + builder.append(resourceId); + builder.append(", amount="); + builder.append(amount); + builder.append("]"); + return builder.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((amount == null) ? 0 : amount.hashCode()); + result = prime * result + ((resourceId == null) ? 0 : resourceId.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof ResourceInitialization)) { + return false; + } + ResourceInitialization other = (ResourceInitialization) obj; + if (amount == null) { + if (other.amount != null) { + return false; + } + } else if (!amount.equals(other.amount)) { + return false; + } + if (resourceId == null) { + if (other.resourceId != null) { + return false; + } + } else if (!resourceId.equals(other.resourceId)) { + return false; + } + return true; + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/support/ResourceLabeler.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/support/ResourceLabeler.java new file mode 100644 index 000000000..be529905a --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/support/ResourceLabeler.java @@ -0,0 +1,88 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.support; + +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.Labeler; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.LabelerSensitivity; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.resources.datamanagers.ResourcesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.resources.events.PersonResourceUpdateEvent; +import util.errors.ContractException; + +/** + * A a labeler for resources. The dimension of the labeler is the given + * {@linkplain ResourceId}, the event that stimulates a label update is + * {@linkplain PersonResourceUpdateEvent} and the labeling function is composed + * from the given Function. + */ +public abstract class ResourceLabeler implements Labeler { + + private final ResourceId resourceId; + + protected abstract Object getLabelFromAmount(long amount); + + private ResourcesDataManager resourcesDataManager; + + public ResourceId getResourceId() { + return resourceId; + } + + public ResourceLabeler(ResourceId resourceId) { + this.resourceId = resourceId; + } + + private Optional getPersonId(PersonResourceUpdateEvent personResourceUpdateEvent) { + PersonId result = null; + if (personResourceUpdateEvent.resourceId().equals(resourceId)) { + result = personResourceUpdateEvent.personId(); + } + return Optional.ofNullable(result); + } + + @Override + public final Set> getLabelerSensitivities() { + Set> result = new LinkedHashSet<>(); + result.add( + new LabelerSensitivity(PersonResourceUpdateEvent.class, this::getPersonId)); + return result; + } + + @Override + public final Object getCurrentLabel(PartitionsContext partitionsContext, PersonId personId) { + if (partitionsContext == null) { + throw new ContractException(NucleusError.NULL_SIMULATION_CONTEXT); + } + + if (resourcesDataManager == null) { + resourcesDataManager = partitionsContext.getDataManager(ResourcesDataManager.class); + } + long personResourceLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); + return getLabelFromAmount(personResourceLevel); + } + + @Override + public final Object getId() { + return resourceId; + } + + @Override + public final Object getPastLabel(PartitionsContext partitionsContext, Event event) { + PersonResourceUpdateEvent personResourceUpdateEvent = (PersonResourceUpdateEvent) event; + return getLabelFromAmount(personResourceUpdateEvent.previousResourceLevel()); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("ResourceLabeler [resourceId="); + builder.append(resourceId); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/support/ResourcePropertyId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/support/ResourcePropertyId.java new file mode 100644 index 000000000..b4403f8f8 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/support/ResourcePropertyId.java @@ -0,0 +1,15 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.support; + +import net.jcip.annotations.ThreadSafe; + +/** + * Marker interface for resource property identifiers + */ +@ThreadSafe +public interface ResourcePropertyId { + @Override + public int hashCode(); + + @Override + public boolean equals(Object obj); +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/support/ResourcePropertyInitialization.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/support/ResourcePropertyInitialization.java new file mode 100644 index 000000000..23682e7ce --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/support/ResourcePropertyInitialization.java @@ -0,0 +1,178 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.support; + +import java.util.Optional; + +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.errors.ContractException; + +/** + * Immutable data class that represents the addition of a resource property + */ +public class ResourcePropertyInitialization { + + private static class Data { + private ResourceId resourceId; + private ResourcePropertyId resourcePropertyId; + private PropertyDefinition propertyDefinition; + private Object value; + + public Data() { + } + + public Data(Data data) { + resourceId = data.resourceId; + resourcePropertyId = data.resourcePropertyId; + propertyDefinition = data.propertyDefinition; + value = data.value; + } + + } + + /** + * Returns a new Builder instance + */ + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Data data = new Data(); + + private Builder() { + } + + private void validate() { + if (data.propertyDefinition == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_DEFINITION); + } + if (data.resourcePropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + if (data.resourceId == null) { + throw new ContractException(ResourceError.NULL_RESOURCE_ID); + } + if (data.value == null) { + if (data.propertyDefinition.getDefaultValue().isEmpty()) { + throw new ContractException(PropertyError.INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT); + } + } else { + if (!data.propertyDefinition.getType().isAssignableFrom(data.value.getClass())) { + throw new ContractException(PropertyError.INCOMPATIBLE_VALUE); + } + } + } + + /** + * Returns the ResourcePropertyInitialization formed from the inputs. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_DEFINITION} + * if no property definition was provided
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_ID} if + * no property id was provided
    • + *
    • {@linkplain ResourceError#NULL_RESOURCE_ID} if + * no resource id was provided
    • + *
    • {@linkplain PropertyError#INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT} + * if no property value was provided and the property + * definition does not contain a default value
    • + *
    + */ + public ResourcePropertyInitialization build() { + validate(); + return new ResourcePropertyInitialization(new Data(data)); + } + + /** + * Sets the resource id. + * + * @throws ContractException {@linkplain ResourceError#NULL_RESOURCE_ID} if the + * resource id is null + */ + public Builder setResourceId(ResourceId resourceId) { + if (resourceId == null) { + throw new ContractException(ResourceError.NULL_RESOURCE_ID); + } + data.resourceId = resourceId; + return this; + } + + /** + * Sets the resource property id. + * + * @throws ContractException {@linkplain PropertyError#NULL_PROPERTY_ID} if the + * property id is null + */ + public Builder setResourcePropertyId(ResourcePropertyId resourcePropertyId) { + if (resourcePropertyId == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_ID); + } + data.resourcePropertyId = resourcePropertyId; + return this; + } + + /** + * Sets the resource property definition. + * + * @throws ContractException {@linkplain PropertyError#NULL_PROPERTY_DEFINITION} + * if the property definition is null + */ + public Builder setPropertyDefinition(PropertyDefinition propertyDefinition) { + if (propertyDefinition == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_DEFINITION); + } + data.propertyDefinition = propertyDefinition; + return this; + } + + /** + * Sets the value of the global property that overrides any default value + * provided by the property definition + * + * @throws ContractException {@linkplain PropertyError#NULL_PROPERTY_VALUE} if + * the value is null + */ + public Builder setValue(Object value) { + if (value == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_VALUE); + } + data.value = value; + return this; + } + } + + private final Data data; + + private ResourcePropertyInitialization(Data data) { + this.data = data; + } + + /** + * Returns the resource property id + */ + public ResourcePropertyId getResourcePropertyId() { + return data.resourcePropertyId; + } + + /** + * Returns the resource id + */ + public ResourceId getResourceId() { + return data.resourceId; + } + + /** + * Returns the property definition + */ + public PropertyDefinition getPropertyDefinition() { + return data.propertyDefinition; + } + + /** + * Returns the property value + */ + public Optional getValue() { + return Optional.ofNullable(data.value); + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/testsupport/ResourcesTestPluginFactory.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/testsupport/ResourcesTestPluginFactory.java new file mode 100644 index 000000000..277624a79 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/testsupport/ResourcesTestPluginFactory.java @@ -0,0 +1,444 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.testsupport; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePlugin; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeoplePluginData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonRange; +import gov.hhs.aspr.ms.gcm.plugins.regions.RegionsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionError; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.TestRegionId; +import gov.hhs.aspr.ms.gcm.plugins.resources.ResourcesPlugin; +import gov.hhs.aspr.ms.gcm.plugins.resources.datamanagers.ResourcesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.resources.reports.PersonResourceReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.resources.reports.ResourcePropertyReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.resources.reports.ResourceReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceError; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.StochasticsError; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +/** + * A static test support class for the {@linkplain ResourcesPlugin}. Provides + * convenience methods for obtaining standarized PluginData for the listed + * Plugin. + *

    + * Also contains factory methods to obtain a list of plugins that is the minimal + * set needed to adequately test this Plugin that can be utilized with + *

    + * {@link TestSimulation#execute} + */ +public class ResourcesTestPluginFactory { + + private ResourcesTestPluginFactory() { + } + + private static class Data { + private ResourcesPluginData resourcesPluginData; + private PersonResourceReportPluginData personResourceReportPluginData; + private ResourcePropertyReportPluginData resourcePropertyReportPluginData; + private ResourceReportPluginData resourceReportPluginData; + + private RegionsPluginData regionsPluginData; + private PeoplePluginData peoplePluginData; + private StochasticsPluginData stochasticsPluginData; + private TestPluginData testPluginData; + + private Data(int initialPopulation, long seed, TestPluginData testPluginData) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + this.peoplePluginData = getStandardPeoplePluginData(initialPopulation); + this.resourcesPluginData = getStandardResourcesPluginData(this.peoplePluginData.getPersonIds(), + randomGenerator.nextLong()); + this.regionsPluginData = getStandardRegionsPluginData(this.peoplePluginData.getPersonIds(), + randomGenerator.nextLong()); + this.stochasticsPluginData = getStandardStochasticsPluginData(randomGenerator.nextLong()); + this.testPluginData = testPluginData; + } + } + + /** + * Factory class that facilitates the building of {@linkplain PluginData} with + * the various setter methods. + */ + public static class Factory { + private Data data; + + private Factory(Data data) { + this.data = data; + } + + /** + * Returns a list of plugins containing a Resources, People, Regions, + * Stochastics and Test Plugin built from the contributed PluginDatas. + *
      + *
    • ResourcesPlugin is defaulted to one formed from + * {@link ResourcesTestPluginFactory#getStandardResourcesPluginData}
    • + *
    • RegionsPlugin is defaulted to one formed from + * {@link ResourcesTestPluginFactory#getStandardRegionsPluginData}
    • + *
    • PeoplePlugin is defaulted to one formed from + * {@link ResourcesTestPluginFactory#getStandardPeoplePluginData}
    • + *
    • StochasticsPlugin is defaulted to one formed from + * {@link ResourcesTestPluginFactory#getStandardStochasticsPluginData}
    • + *
    • TestPlugin is formed from the TestPluginData passed into + * {@link ResourcesTestPluginFactory#factory}
    • + *
    + */ + public List getPlugins() { + List pluginsToAdd = new ArrayList<>(); + Plugin resourcesPlugin = // + ResourcesPlugin.builder()// + .setResourcesPluginData(this.data.resourcesPluginData)// + .setPersonResourceReportPluginData(data.personResourceReportPluginData)// + .setResourcePropertyReportPluginData(data.resourcePropertyReportPluginData)// + .setResourceReportPluginData(data.resourceReportPluginData)// + .getResourcesPlugin();// + + Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(this.data.peoplePluginData); + + Plugin regionsPlugin = RegionsPlugin.builder().setRegionsPluginData(this.data.regionsPluginData) + .getRegionsPlugin(); + + Plugin stochasticPlugin = StochasticsPlugin.getStochasticsPlugin(this.data.stochasticsPluginData); + + Plugin testPlugin = TestPlugin.getTestPlugin(this.data.testPluginData); + + pluginsToAdd.add(resourcesPlugin); + pluginsToAdd.add(peoplePlugin); + pluginsToAdd.add(regionsPlugin); + pluginsToAdd.add(stochasticPlugin); + pluginsToAdd.add(testPlugin); + + return pluginsToAdd; + } + + /** + * Sets the {@link ResourcesPluginData} in this Factory. This explicit instance + * of pluginData will be used to create a ResourcesPlugin + * + * @throws ContractException {@linkplain ResourceError#NULL_RESOURCE_PLUGIN_DATA} + * if the passed in pluginData is null + */ + public Factory setResourcesPluginData(ResourcesPluginData resourcesPluginData) { + if (resourcesPluginData == null) { + throw new ContractException(ResourceError.NULL_RESOURCE_PLUGIN_DATA); + } + this.data.resourcesPluginData = resourcesPluginData; + return this; + } + + /** + * Sets the {@link PersonResourceReportPluginData} in this Factory. This + * explicit instance of pluginData will be used to create a ResourcesPlugin + * + * @throws ContractException {@linkplain ResourceError#NULL_RESOURCE_PLUGIN_DATA} + * if the passed in pluginData is null + */ + public Factory setPersonResourceReportPluginData( + PersonResourceReportPluginData personResourceReportPluginData) { + if (personResourceReportPluginData == null) { + throw new ContractException(ResourceError.NULL_PERSON_RESOURCE_REPORT_PLUGIN_DATA); + } + this.data.personResourceReportPluginData = personResourceReportPluginData; + return this; + } + + /** + * Sets the {@link ResourcePropertyReportPluginData} in this Factory. This + * explicit instance of pluginData will be used to create a ResourcesPlugin + * + * @throws ContractException {@linkplain ResourceError#NULL_RESOURCE_PLUGIN_DATA} + * if the passed in pluginData is null + */ + public Factory setResourcePropertyReportPluginData( + ResourcePropertyReportPluginData resourcePropertyReportPluginData) { + if (resourcePropertyReportPluginData == null) { + throw new ContractException(ResourceError.NULL_RESOURCE_PROPERTY_REPORT_PLUGIN_DATA); + } + this.data.resourcePropertyReportPluginData = resourcePropertyReportPluginData; + return this; + } + + /** + * Sets the {@link ResourceReportPluginData} in this Factory. This explicit + * instance of pluginData will be used to create a ResourcesPlugin + * + * @throws ContractException {@linkplain ResourceError#NULL_RESOURCE_PLUGIN_DATA} + * if the passed in pluginData is null + */ + public Factory setResourceReportPluginData(ResourceReportPluginData resourceReportPluginData) { + if (resourceReportPluginData == null) { + throw new ContractException(ResourceError.NULL_RESOURCE_REPORT_PLUGIN_DATA); + } + this.data.resourceReportPluginData = resourceReportPluginData; + return this; + } + + /** + * Sets the {@link PeoplePluginData} in this Factory. This explicit instance of + * pluginData will be used to create a PeoplePlugin + * + * @throws ContractException {@linkplain PersonError#NULL_PEOPLE_PLUGIN_DATA} if + * the passed in pluginData is null + */ + public Factory setPeoplePluginData(PeoplePluginData peoplePluginData) { + if (peoplePluginData == null) { + throw new ContractException(PersonError.NULL_PEOPLE_PLUGIN_DATA); + } + this.data.peoplePluginData = peoplePluginData; + return this; + } + + /** + * Sets the {@link RegionsPluginData} in this Factory. This explicit instance of + * pluginData will be used to create a RegionsPlugin + * + * @throws ContractException {@linkplain RegionError#NULL_REGION_PLUGIN_DATA} if + * the passed in pluginData is null + */ + public Factory setRegionsPluginData(RegionsPluginData regionsPluginData) { + if (regionsPluginData == null) { + throw new ContractException(RegionError.NULL_REGION_PLUGIN_DATA); + } + this.data.regionsPluginData = regionsPluginData; + return this; + } + + /** + * Sets the {@link StochasticsPluginData} in this Factory. This explicit + * instance of pluginData will be used to create a StochasticsPlugin + * + * @throws ContractException {@linkplain StochasticsError#NULL_STOCHASTICS_PLUGIN_DATA} + * if the passed in pluginData is null + */ + public Factory setStochasticsPluginData(StochasticsPluginData stochasticsPluginData) { + if (stochasticsPluginData == null) { + throw new ContractException(StochasticsError.NULL_STOCHASTICS_PLUGIN_DATA); + } + this.data.stochasticsPluginData = stochasticsPluginData; + return this; + } + + } + + /** + * Creates a Factory that facilitates the creation of a minimal set of plugins + * needed to adequately test the {@link ResourcesPlugin} by generating: + *
      + *
    • {@link ResourcesPluginData}
    • + *
    • {@link RegionsPluginData}
    • + *
    • {@link PeoplePluginData}
    • + *
    • {@link StochasticsPluginData}
    • + *
    + * either directly (by default) via + *
      + *
    • {@link #getStandardResourcesPluginData}
    • + *
    • {@link #getStandardPeoplePluginData}, + *
    • {@link #getStandardRegionsPluginData}, + *
    • {@link #getStandardStochasticsPluginData}
    • + *
    + * or explicitly set via + *
      + *
    • {@link Factory#setResourcesPluginData}, + *
    • {@link Factory#setPeoplePluginData}, + *
    • {@link Factory#setRegionsPluginData}, + *
    • {@link Factory#setStochasticsPluginData}
    • + *
    + * via the {@link Factory#getPlugins()} method. + * + * @throws ContractException {@linkplain NucleusError#NULL_PLUGIN_DATA} if + * testPluginData is null + */ + public static Factory factory(int initialPopulation, long seed, TestPluginData testPluginData) { + if (testPluginData == null) { + throw new ContractException(NucleusError.NULL_PLUGIN_DATA); + } + return new Factory(new Data(initialPopulation, seed, testPluginData)); + } + + /** + * Creates a Factory that facilitates the creation of a minimal set of plugins + * needed to adequately test the {@link ResourcesPlugin} by generating: + *
      + *
    • {@link ResourcesPluginData}
    • + *
    • {@link RegionsPluginData}
    • + *
    • {@link PeoplePluginData}
    • + *
    • {@link StochasticsPluginData}
    • + *
    + * either directly (by default) via + *
      + *
    • {@link #getStandardResourcesPluginData}
    • + *
    • {@link #getStandardPeoplePluginData}, + *
    • {@link #getStandardRegionsPluginData}, + *
    • {@link #getStandardStochasticsPluginData}
    • + *
    + * or explicitly set via + *
      + *
    • {@link Factory#setResourcesPluginData}, + *
    • {@link Factory#setPeoplePluginData}, + *
    • {@link Factory#setRegionsPluginData}, + *
    • {@link Factory#setStochasticsPluginData}
    • + *
    + * via the {@link Factory#getPlugins()} method. + * + * @throws ContractException {@linkplain NucleusError#NULL_ACTOR_CONTEXT_CONSUMER} + * if consumer is null + */ + public static Factory factory(int initialPopulation, long seed, Consumer consumer) { + if (consumer == null) { + throw new ContractException(NucleusError.NULL_ACTOR_CONTEXT_CONSUMER); + } + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, consumer)); + TestPluginData testPluginData = pluginBuilder.build(); + return factory(initialPopulation, seed, testPluginData); + } + + /** + * Returns a standardized PeoplePluginData that is minimally adequate for + * testing the ResourcesPlugin The resutling PeoplePluginData will include: + *
      + *
    • A number of people equal to the value of initialPopulation
    • + *
    + */ + public static PeoplePluginData getStandardPeoplePluginData(int initialPopulation) { + PeoplePluginData.Builder peopleBuilder = PeoplePluginData.builder(); + if (initialPopulation > 0) { + peopleBuilder.addPersonRange(new PersonRange(0, initialPopulation - 1)); + } + return peopleBuilder.build(); + } + + /** + * Returns a standardized RegionsPluginData that is minimally adequate for + * testing the ResourcesPlugin The resulting RegionsPluginData will include: + *
      + *
    • Every RegionId included in {@link TestRegionId}
    • + *
    • Every person passed in via people. + *
        + *
      • Each person will be assigned a random region based on the passed in seed + *
      • + *
      + *
    + */ + public static RegionsPluginData getStandardRegionsPluginData(List people, long seed) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + + RegionsPluginData.Builder regionBuilder = RegionsPluginData.builder(); + // add the regions + for (TestRegionId testRegionId : TestRegionId.values()) { + regionBuilder.addRegion(testRegionId); + } + for (PersonId personId : people) { + TestRegionId randomRegionId = TestRegionId.getRandomRegionId(randomGenerator); + regionBuilder.addPerson(personId, randomRegionId); + } + return regionBuilder.build(); + + } + + /** + * Returns a standardized ResourcesPluginData that is minimally adequate for + * testing the ResourcesPlugin The resulting ResourcesPluginData will include: + *
      + *
    • Every ResourceId included in {@link TestResourceId} + *
        + *
      • along with the defined timeTrackingPolicy for each
      • + *
      + *
    • Every ResourcePropertyId included in {@link TestResourcePropertyId} along + * with the defined propertyDefinition for each.
    • + *
    • Each Resource will have a random property value assigned based on a + * RandomGenerator that is created with the passed in seed
    • + *
    + */ + public static ResourcesPluginData getStandardResourcesPluginData(List people, long seed) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + + ResourcesPluginData.Builder resourcesBuilder = ResourcesPluginData.builder(); + + for (TestResourceId testResourceId : TestResourceId.values()) { + resourcesBuilder.addResource(testResourceId, 0.0, testResourceId.getTimeTrackingPolicy()); + + for (PersonId personId : people) { + if (randomGenerator.nextBoolean()) { + resourcesBuilder.setPersonResourceLevel(personId, testResourceId, randomGenerator.nextInt(10)); + } + boolean trackTimes = testResourceId.getTimeTrackingPolicy(); + if (trackTimes && randomGenerator.nextBoolean()) { + resourcesBuilder.setPersonResourceTime(personId, testResourceId, 0.0); + } + } + + for (RegionId regionId : TestRegionId.values()) { + if (randomGenerator.nextBoolean()) { + resourcesBuilder.setRegionResourceLevel(regionId, testResourceId, randomGenerator.nextInt(10)); + } else { + resourcesBuilder.setRegionResourceLevel(regionId, testResourceId, 0); + } + } + } + + for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId.getTestResourcePropertyIds()) { + TestResourceId testResourceId = testResourcePropertyId.getTestResourceId(); + PropertyDefinition propertyDefinition = testResourcePropertyId.getPropertyDefinition(); + boolean hasDeaultValue = propertyDefinition.getDefaultValue().isPresent(); + + resourcesBuilder.defineResourceProperty(testResourceId, testResourcePropertyId, propertyDefinition); + + if (!hasDeaultValue) { + Object propertyValue = testResourcePropertyId.getRandomPropertyValue(randomGenerator); + resourcesBuilder.setResourcePropertyValue(testResourceId, testResourcePropertyId, propertyValue); + } + } + + for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId + .getShuffledTestResourcePropertyIds(randomGenerator)) { + TestResourceId testResourceId = testResourcePropertyId.getTestResourceId(); + PropertyDefinition propertyDefinition = testResourcePropertyId.getPropertyDefinition(); + boolean hasDefault = propertyDefinition.getDefaultValue().isPresent(); + boolean setValue = randomGenerator.nextBoolean(); + if (hasDefault && setValue) { + resourcesBuilder.setResourcePropertyValue(testResourceId, testResourcePropertyId, + propertyDefinition.getDefaultValue().get()); + } else if (setValue) { + Object propertyValue = testResourcePropertyId.getRandomPropertyValue(randomGenerator); + resourcesBuilder.setResourcePropertyValue(testResourceId, testResourcePropertyId, propertyValue); + } + } + + return resourcesBuilder.build(); + } + + /** + * Returns a standardized StochasticsPluginData that is minimally adequate for + * testing the ResourcesPlugin The resulting StochasticsPluginData will include: + *
      + *
    • a seed based on the nextLong of a RandomGenerator seeded from the passed + * in seed
    • + *
    + */ + public static StochasticsPluginData getStandardStochasticsPluginData(long seed) { + WellState wellState = WellState.builder().setSeed(seed).build(); + return StochasticsPluginData.builder().setMainRNGState(wellState).build(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/testsupport/TestResourceId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/testsupport/TestResourceId.java new file mode 100644 index 000000000..4db0b61b4 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/testsupport/TestResourceId.java @@ -0,0 +1,48 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.testsupport; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; + +/** + * Enumeration that identifies resources for all tests + */ +public enum TestResourceId implements ResourceId { + RESOURCE_1(true), RESOURCE_2(false), RESOURCE_3(true), RESOURCE_4(false), RESOURCE_5(true); + + private final boolean timeTrackingPolicy; + + private TestResourceId(boolean timeTrackingPolicy) { + this.timeTrackingPolicy = timeTrackingPolicy; + } + + public boolean getTimeTrackingPolicy() { + return timeTrackingPolicy; + } + + public static TestResourceId getRandomResourceId(final RandomGenerator randomGenerator) { + return TestResourceId.values()[randomGenerator.nextInt(TestResourceId.values().length)]; + } + + /** + * Returns a new {@link ResourceId} instance. + */ + public static ResourceId getUnknownResourceId() { + return new ResourceId() { + }; + } + + public static int size() { + return values().length; + } + + private TestResourceId next; + + public TestResourceId next() { + if (next == null) { + next = TestResourceId.values()[(ordinal() + 1) % TestResourceId.values().length]; + } + return next; + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/testsupport/TestResourcePropertyId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/testsupport/TestResourcePropertyId.java new file mode 100644 index 000000000..0447d8ea4 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/resources/testsupport/TestResourcePropertyId.java @@ -0,0 +1,130 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.testsupport; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourcePropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; + +/** + * Enumeration that identifies resources for all tests + */ +public enum TestResourcePropertyId implements ResourcePropertyId { + + ResourceProperty_1_1_BOOLEAN_MUTABLE(TestResourceId.RESOURCE_1, + PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(false).build()), + ResourceProperty_1_2_INTEGER_MUTABLE(TestResourceId.RESOURCE_1, + PropertyDefinition.builder().setType(Integer.class).setDefaultValue(0).build()), + ResourceProperty_1_3_DOUBLE_MUTABLE(TestResourceId.RESOURCE_1, + PropertyDefinition.builder().setType(Double.class).setDefaultValue(0.0).build()), + ResourceProperty_2_1_BOOLEAN_MUTABLE(TestResourceId.RESOURCE_2, + PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(true).build()), + ResourceProperty_2_2_INTEGER_MUTABLE(TestResourceId.RESOURCE_2, + PropertyDefinition.builder().setType(Integer.class).setDefaultValue(5).build()), + ResourceProperty_3_1_BOOLEAN_MUTABLE(TestResourceId.RESOURCE_3, + PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(false).build()), + ResourceProperty_3_2_STRING_MUTABLE(TestResourceId.RESOURCE_3, + PropertyDefinition.builder().setType(String.class).setDefaultValue("").build()), + ResourceProperty_4_1_BOOLEAN_MUTABLE(TestResourceId.RESOURCE_4, + PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(true).build()), + ResourceProperty_5_1_INTEGER_IMMUTABLE(TestResourceId.RESOURCE_5, + PropertyDefinition.builder().setType(Integer.class).setDefaultValue(7).setPropertyValueMutability(false) + .build()), + ResourceProperty_5_2_DOUBLE_IMMUTABLE(TestResourceId.RESOURCE_5, PropertyDefinition.builder().setType(Double.class) + .setDefaultValue(2.7).setPropertyValueMutability(false).build()); + + private final TestResourceId testResourceId; + private final PropertyDefinition propertyDefinition; + + public PropertyDefinition getPropertyDefinition() { + return propertyDefinition; + } + + private TestResourcePropertyId(TestResourceId testResourceId, PropertyDefinition propertyDefinition) { + this.testResourceId = testResourceId; + this.propertyDefinition = propertyDefinition; + } + + public TestResourceId getTestResourceId() { + return testResourceId; + } + + /** + * Returns a new {@link ResourcePropertyId} instance. + */ + public static ResourcePropertyId getUnknownResourcePropertyId() { + return new ResourcePropertyId() { + }; + } + + public static Set getTestResourcePropertyIds(TestResourceId testResourceId) { + Set result = new LinkedHashSet<>(); + for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId.values()) { + if (testResourcePropertyId.testResourceId == testResourceId) { + result.add(testResourcePropertyId); + } + } + return result; + } + + public static TestResourcePropertyId getRandomResourcePropertyId(TestResourceId testResourceId, + final RandomGenerator randomGenerator) { + List list = new ArrayList<>(getTestResourcePropertyIds(testResourceId)); + return list.get(randomGenerator.nextInt(list.size())); + } + + public Object getRandomPropertyValue(final RandomGenerator randomGenerator) { + switch (this) { + case ResourceProperty_1_1_BOOLEAN_MUTABLE: + return randomGenerator.nextBoolean(); + case ResourceProperty_1_2_INTEGER_MUTABLE: + return randomGenerator.nextInt(); + case ResourceProperty_1_3_DOUBLE_MUTABLE: + return randomGenerator.nextDouble(); + case ResourceProperty_2_1_BOOLEAN_MUTABLE: + return randomGenerator.nextBoolean(); + case ResourceProperty_2_2_INTEGER_MUTABLE: + return randomGenerator.nextInt(); + case ResourceProperty_3_1_BOOLEAN_MUTABLE: + return randomGenerator.nextBoolean(); + case ResourceProperty_3_2_STRING_MUTABLE: + return Integer.toString(randomGenerator.nextInt()); + case ResourceProperty_4_1_BOOLEAN_MUTABLE: + return randomGenerator.nextBoolean(); + case ResourceProperty_5_2_DOUBLE_IMMUTABLE: + return randomGenerator.nextDouble(); + case ResourceProperty_5_1_INTEGER_IMMUTABLE: + return randomGenerator.nextInt(); + default: + throw new RuntimeException("unhandled case: " + this); + + } + } + + private TestResourcePropertyId next; + + public TestResourcePropertyId next() { + if (next == null) { + next = TestResourcePropertyId.values()[(ordinal() + 1) % TestResourcePropertyId.values().length]; + } + return next; + } + + public static List getTestResourcePropertyIds() { + return Arrays.asList(TestResourcePropertyId.values()); + } + + public static List getShuffledTestResourcePropertyIds(RandomGenerator randomGenerator) { + List result = getTestResourcePropertyIds(); + Collections.shuffle(result, new Random(randomGenerator.nextLong())); + + return result; + } +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/StochasticsPlugin.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/StochasticsPlugin.java new file mode 100644 index 000000000..4e1287058 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/StochasticsPlugin.java @@ -0,0 +1,70 @@ +package gov.hhs.aspr.ms.gcm.plugins.stochastics; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import net.jcip.annotations.ThreadSafe; +import util.errors.ContractError; + +/** + *

    + * Summary A nucleus plugin for managing random number generators. The + * plugin provides a general random generator as well as a set of random + * generators mapped to a set of identifiers. All random generators are + * implemented by org.apache.commons.math3.random.Well44497b + *

    + *

    + * Plugin Datas + *

      + *
    • Stochastics Plugin Data: Provides initial state for the data + * manager
    • + *
    + *

    + * Events The plugin supports no events. + **

    + *

    + * Data Managers + *

      + *
    • StochasticsDataManger: Manages the random generators and provides + * various related capabilities. + *
    + *

    + * Reports The plugin defines no reports + *

    + *

    + * Actors: No actors provided. + *

    + *

    + * Support classes + *

      + *
    • StochasticsError: Enumeration implementing + * {@linkplain ContractError} for this plugin.
    • + *
    • RandomNumberGeneratorId: Marker interface for generator id + * values
    • + *
    + *

    + * Required Plugins This plugin has no plugin dependencies + *

    + */ +@ThreadSafe +public final class StochasticsPlugin { + + private StochasticsPlugin() { + + } + + /** + * Returns a plugin that will add a StochasticsDataManager to the simulation at + * initialization + */ + public static Plugin getStochasticsPlugin(StochasticsPluginData stochasticsPluginData) { + + return Plugin.builder()// + .addPluginData(stochasticsPluginData)// + .setPluginId(StochasticsPluginId.PLUGIN_ID)// + .setInitializer((c) -> { + StochasticsPluginData pluginData = c.getPluginData(StochasticsPluginData.class).get(); + c.addDataManager(new StochasticsDataManager(pluginData)); + }).build();// + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/StochasticsPluginId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/StochasticsPluginId.java new file mode 100644 index 000000000..2309eb067 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/StochasticsPluginId.java @@ -0,0 +1,13 @@ +package gov.hhs.aspr.ms.gcm.plugins.stochastics; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; + +/** + * Static plugin id implementation for the StochasticsPlugin + */ +public final class StochasticsPluginId implements PluginId { + public final static PluginId PLUGIN_ID = new StochasticsPluginId(); + + private StochasticsPluginId() { + }; +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/datamanagers/StochasticsDataManager.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/datamanagers/StochasticsDataManager.java new file mode 100644 index 000000000..0275a70cc --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/datamanagers/StochasticsDataManager.java @@ -0,0 +1,169 @@ +package gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers; + +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.nucleus.DataManager; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.RandomNumberGeneratorId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.StochasticsError; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.Well; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import util.errors.ContractException; + +/** + * A mutable data manager for random number generators. + */ +public final class StochasticsDataManager extends DataManager { + + private Map randomGeneratorMap = new LinkedHashMap<>(); + + private Well randomGenerator; + + /** + * Returns the general, non-identified, random number generator was initialized + * with the current base seed value that was initialized from the + * {@linkplain StochasticsPluginData} + */ + public Well getRandomGenerator() { + return randomGenerator; + } + + /** + * Returns the random generator associated with the given id. + * + * @throws ContractException + *
      + *
    • {@linkplain StochasticsError#NULL_RANDOM_NUMBER_GENERATOR_ID} + * if the random number generator is null
    • + *
    • {@linkplain StochasticsError#UNKNOWN_RANDOM_NUMBER_GENERATOR_ID} + * if the random number generator is unknown
    • + *
    + */ + public Well getRandomGeneratorFromId(RandomNumberGeneratorId randomNumberGeneratorId) { + validateRandomNumberGeneratorId(randomNumberGeneratorId); + return randomGeneratorMap.get(randomNumberGeneratorId); + } + + /** + * Returns the random number generator ids. + */ + @SuppressWarnings("unchecked") + public Set getRandomNumberGeneratorIds() { + Set result = new LinkedHashSet<>(randomGeneratorMap.size()); + for (RandomNumberGeneratorId randomNumberGeneratorId : randomGeneratorMap.keySet()) { + result.add((T) randomNumberGeneratorId); + } + return result; + } + + /** + * Returns true if and only if the random generator id is known + */ + public boolean randomNumberGeneratorIdExists(RandomNumberGeneratorId randomNumberGeneratorId) { + return randomGeneratorMap.containsKey(randomNumberGeneratorId); + } + + /** + * Adds a new RNG. + * + * @throws ContractException + *
      + *
    • {@linkplain StochasticsError#NULL_RANDOM_NUMBER_GENERATOR_ID} + * if the randomNumberGeneratorId is null
    • + *
    • {@linkplain StochasticsError#RANDOM_NUMBER_GENERATOR_ID_ALREADY_EXISTS} + * if the randomNumberGeneratorId was previously + * added
    • + *
    • {@linkplain StochasticsError#NULL_WELL_STATE} + * if the wellstate is null
    • + *
    + */ + public RandomGenerator addRandomNumberGenerator(RandomNumberGeneratorId randomNumberGeneratorId, + WellState wellState) { + validateNewRandomNumberGeneratorId(randomNumberGeneratorId); + validateWellStateNotNull(wellState); + Well result = new Well(wellState); + randomGeneratorMap.put(randomNumberGeneratorId, result); + return result; + } + + private void validateRandomNumberGeneratorId(RandomNumberGeneratorId randomNumberGeneratorId) { + if (randomNumberGeneratorId == null) { + throw new ContractException(StochasticsError.NULL_RANDOM_NUMBER_GENERATOR_ID); + } + if (!randomGeneratorMap.containsKey(randomNumberGeneratorId)) { + throw new ContractException(StochasticsError.UNKNOWN_RANDOM_NUMBER_GENERATOR_ID); + } + } + + private void validateWellStateNotNull(WellState wellState) { + if (wellState == null) { + throw new ContractException(StochasticsError.NULL_WELL_STATE); + } + } + + private void validateNewRandomNumberGeneratorId(RandomNumberGeneratorId randomNumberGeneratorId) { + if (randomNumberGeneratorId == null) { + throw new ContractException(StochasticsError.NULL_RANDOM_NUMBER_GENERATOR_ID); + } + if (randomGeneratorMap.containsKey(randomNumberGeneratorId)) { + throw new ContractException(StochasticsError.RANDOM_NUMBER_GENERATOR_ID_ALREADY_EXISTS); + } + } + + /** + * Creates the StochasticsDataManager from the given + * {@linkplain StochasticsPluginData} Random generators associated with + * predefined ids in the StochasticsPluginData are generated in the same manner + * as the method + * {@linkplain StochasticsDataManager#getRandomGeneratorFromId(RandomNumberGeneratorId)} + */ + public StochasticsDataManager(StochasticsPluginData stochasticsPluginData) { + + // create RandomGenerators for each of the ids using a hash built from + // the id and the replication seed + Set randomNumberGeneratorIds = stochasticsPluginData.getRandomNumberGeneratorIds(); + randomGenerator = new Well(stochasticsPluginData.getWellState()); + for (RandomNumberGeneratorId randomNumberGeneratorId : randomNumberGeneratorIds) { + WellState wellState = stochasticsPluginData.getWellState(randomNumberGeneratorId); + randomGeneratorMap.put(randomNumberGeneratorId, new Well(wellState)); + } + + // finally, set up the standard RandomGenerator + randomGenerator = new Well(stochasticsPluginData.getWellState()); + } + + @Override + public void init(DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + if (dataManagerContext.stateRecordingIsScheduled()) { + dataManagerContext.subscribeToSimulationClose(this::recordSimulationState); + } + } + + private void recordSimulationState(DataManagerContext dataManagerContext) { + StochasticsPluginData.Builder builder = StochasticsPluginData.builder(); + for (RandomNumberGeneratorId randomNumberGeneratorId : randomGeneratorMap.keySet()) { + Well wellRNG = randomGeneratorMap.get(randomNumberGeneratorId); + builder.addRNG(randomNumberGeneratorId, wellRNG.getWellState()); + } + builder.setMainRNGState(randomGenerator.getWellState()); + dataManagerContext.releaseOutput(builder.build()); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("StochasticsDataManager [randomGeneratorMap="); + builder.append(randomGeneratorMap); + builder.append(", randomGenerator="); + builder.append(randomGenerator); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/datamanagers/StochasticsPluginData.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/datamanagers/StochasticsPluginData.java new file mode 100644 index 000000000..cd7b2d2dc --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/datamanagers/StochasticsPluginData.java @@ -0,0 +1,278 @@ +package gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers; + +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.PluginDataBuilder; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.RandomNumberGeneratorId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.StochasticsError; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import net.jcip.annotations.ThreadSafe; +import util.errors.ContractException; + +/** + * A thread-safe container for the initial state of random generators. + */ +@ThreadSafe +public final class StochasticsPluginData implements PluginData { + + /** + * Constructs this plugin from the initial data and seed + */ + private StochasticsPluginData(Data data) { + this.data = data; + } + + @Override + public PluginDataBuilder getCloneBuilder() { + return new Builder(data); + } + + /* + * State container class for collecting random number generator ids. + */ + private static class Data { + public Data() { + } + + public Data(Data data) { + this.wellState = data.wellState; + randomNumberGeneratorIds.putAll(data.randomNumberGeneratorIds); + locked = data.locked; + } + + private WellState wellState; + private Map randomNumberGeneratorIds = new LinkedHashMap<>(); + private boolean locked; + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + randomNumberGeneratorIds.hashCode(); + result = prime * result + wellState.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + /* + * We exclude: locked -- both should be locked when equals is invoked + */ + if (randomNumberGeneratorIds == null) { + if (other.randomNumberGeneratorIds != null) { + return false; + } + } else if (!randomNumberGeneratorIds.equals(other.randomNumberGeneratorIds)) { + return false; + } + if (wellState == null) { + if (other.wellState != null) { + return false; + } + } else if (!wellState.equals(other.wellState)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Data [wellState="); + builder.append(wellState); + builder.append(", randomNumberGeneratorIds="); + builder.append(randomNumberGeneratorIds); + builder.append(", locked="); + builder.append(locked); + builder.append("]"); + return builder.toString(); + } + + } + + /** + * Returns a new builder instance + */ + public static Builder builder() { + return new Builder(new Data()); + } + + /** + * Builder class for StochasticsPluginData + */ + public static class Builder implements PluginDataBuilder { + private Data data; + + private void ensureDataMutability() { + if (data.locked) { + data = new Data(data); + data.locked = false; + } + } + + private void ensureImmutability() { + if (!data.locked) { + data.locked = true; + } + } + + private Builder(Data data) { + this.data = data; + + } + + private void validateData() { + if (data.wellState == null) { + throw new ContractException(StochasticsError.NULL_SEED); + } + } + + /** + * Returns the StochasticsInitialData formed from the collected + * RandomNumberGeneratorId values. Clears the builder's state. + * + * @throws ContractException + *
      + *
    • {@linkplain StochasticsError#NULL_SEED} if the + * seed was not set
    • + *
    + */ + public StochasticsPluginData build() { + + if (!data.locked) { + validateData(); + } + ensureImmutability(); + return new StochasticsPluginData(data); + + } + + /** + * Adds the given RandomNumberGeneratorId to this builder. + * + * @throws ContractException + *
      + *
    • {@linkplain StochasticsError#NULL_RANDOM_NUMBER_GENERATOR_ID} + * if the id is null
    • + *
    • {@linkplain StochasticsError#NULL_WELL_STATE} + * if the well state is null
    • + *
    + */ + public Builder addRNG(RandomNumberGeneratorId randomNumberGeneratorId, WellState wellState) { + ensureDataMutability(); + validateRandomNumberGeneratorIdNotNull(randomNumberGeneratorId); + validateWellStateNull(wellState); + data.randomNumberGeneratorIds.put(randomNumberGeneratorId, wellState); + return this; + } + + /** + * Sets the seed value. + */ + public Builder setMainRNGState(WellState wellState) { + ensureDataMutability(); + data.wellState = wellState; + return this; + } + + } + + private static void validateRandomNumberGeneratorIdNotNull(final Object value) { + if (value == null) { + throw new ContractException(StochasticsError.NULL_RANDOM_NUMBER_GENERATOR_ID); + } + } + + private static void validateWellStateNull(final WellState wellState) { + if (wellState == null) { + throw new ContractException(StochasticsError.NULL_WELL_STATE); + } + } + + private final Data data; + + /** + * Returns the RandomNumberGeneratorId values contained in this container + */ + @SuppressWarnings("unchecked") + public Set getRandomNumberGeneratorIds() { + Set result = new LinkedHashSet<>(); + for (RandomNumberGeneratorId randomNumberGeneratorId : data.randomNumberGeneratorIds.keySet()) { + result.add((T) randomNumberGeneratorId); + } + return result; + } + + /** + * Returns the well state for the give RandomNumberGeneratorId + * + * @throws ContractException {@linkplain StochasticsError#UNKNOWN_RANDOM_NUMBER_GENERATOR_ID} + * if the randomNumberGeneratorId is not known + */ + public WellState getWellState(RandomNumberGeneratorId randomNumberGeneratorId) { + + WellState result = data.randomNumberGeneratorIds.get(randomNumberGeneratorId); + if (result == null) { + throw new ContractException(StochasticsError.UNKNOWN_RANDOM_NUMBER_GENERATOR_ID); + } + return result; + } + + /** + * Returns the main well state + * + * @throws ContractException {@linkplain StochasticsError#UNKNOWN_RANDOM_NUMBER_GENERATOR_ID} + * if the randomNumberGeneratorId is not known + */ + public WellState getWellState() { + return data.wellState; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof StochasticsPluginData)) { + return false; + } + StochasticsPluginData other = (StochasticsPluginData) obj; + if (data == null) { + if (other.data != null) { + return false; + } + } else if (!data.equals(other.data)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder2 = new StringBuilder(); + builder2.append("StochasticsPluginData [data="); + builder2.append(data); + builder2.append("]"); + return builder2.toString(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/support/RandomNumberGeneratorId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/support/RandomNumberGeneratorId.java new file mode 100644 index 000000000..97d4c6d76 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/support/RandomNumberGeneratorId.java @@ -0,0 +1,17 @@ +package gov.hhs.aspr.ms.gcm.plugins.stochastics.support; + +import net.jcip.annotations.ThreadSafe; + +/** + * Marker interface for random number generator identifiers + */ +@ThreadSafe +public interface RandomNumberGeneratorId { + + /** + * Returns the string representation of the generator id + */ + @Override + public String toString(); + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/support/SimpleRandomNumberGeneratorId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/support/SimpleRandomNumberGeneratorId.java new file mode 100644 index 000000000..bb54c777b --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/support/SimpleRandomNumberGeneratorId.java @@ -0,0 +1,65 @@ +package gov.hhs.aspr.ms.gcm.plugins.stochastics.support; + +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.SimpleGlobalPropertyId; +import util.errors.ContractException; + +public class SimpleRandomNumberGeneratorId implements RandomNumberGeneratorId { + private final Object value; + + /** + * @throws ContractException {@linkplain StochasticsError#NULL_RANDOM_NUMBER_GENERATOR_ID} + * if the value is null + */ + public SimpleRandomNumberGeneratorId(Object value) { + if (value == null) { + throw new ContractException(StochasticsError.NULL_RANDOM_NUMBER_GENERATOR_ID); + } + this.value = value; + } + + public Object getValue() { + return this.value; + } + + /** + * Standard implementation + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((value == null) ? 0 : value.hashCode()); + return result; + } + + /** + * Two {@link SimpleGlobalPropertyId} instances are equal if and only if their + * inputs are equal. + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof SimpleRandomNumberGeneratorId)) { + return false; + } + SimpleRandomNumberGeneratorId other = (SimpleRandomNumberGeneratorId) obj; + if (value == null) { + if (other.value != null) { + return false; + } + } else if (!value.equals(other.value)) { + return false; + } + return true; + } + + /** + * Returns the string representation of the generator's input + */ + @Override + public String toString() { + return value.toString(); + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/support/StochasticsError.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/support/StochasticsError.java new file mode 100644 index 000000000..0d3b32804 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/support/StochasticsError.java @@ -0,0 +1,30 @@ +package gov.hhs.aspr.ms.gcm.plugins.stochastics.support; + +import util.errors.ContractError; +import util.errors.ContractException; + +/** + * An enumeration supporting {@link ContractException} that acts as a general + * description of the exception. + */ +public enum StochasticsError implements ContractError { + NULL_SEED("Null seed value"), // + NULL_RANDOM_NUMBER_GENERATOR("Null random number generator"), // + NULL_RANDOM_NUMBER_GENERATOR_ID("Null random number generator id"), // + UNKNOWN_RANDOM_NUMBER_GENERATOR_ID("Unknown random number generator id"), // + RANDOM_NUMBER_GENERATOR_ID_ALREADY_EXISTS("The random number generator id is already known"), // + NULL_WELL_STATE("Null well state"), // + NULL_STOCHASTICS_PLUGIN_DATA("null stochastics plugin data"), + ILLEGAL_SEED_ININITIAL_STATE("Illegal seed initial state"),; + + private final String description; + + private StochasticsError(final String description) { + this.description = description; + } + + @Override + public String getDescription() { + return description; + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/support/Well.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/support/Well.java new file mode 100644 index 000000000..3de4ddea7 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/support/Well.java @@ -0,0 +1,66 @@ +package gov.hhs.aspr.ms.gcm.plugins.stochastics.support; + +import java.util.Arrays; + +import org.apache.commons.math3.random.Well44497b; + +public class Well extends Well44497b { + + private static final long serialVersionUID = -6456786712498941886L; + + private final long seed; + + public Well(WellState wellState) { + super(wellState.getSeed()); + + int[] vArray = wellState.getVArray(); + for (int i = 0; i < vArray.length; i++) { + v[i] = vArray[i]; + } + this.index = wellState.getIndex(); + + this.seed = wellState.getSeed(); + } + + Well(long seed) { + super(seed); + this.seed = seed; + } + + public WellState getWellState() { + return WellState.builder().setSeed(seed).setInternals(index, v).build(); + } + + @Override + public int hashCode() { + return getWellState().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Well)) { + return false; + } + Well other = (Well) obj; + + return getWellState().equals(other.getWellState()); + + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Well [seed="); + builder.append(seed); + builder.append(", index="); + builder.append(index); + builder.append(", v="); + builder.append(Arrays.toString(v)); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/support/WellState.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/support/WellState.java new file mode 100644 index 000000000..0525ae5d5 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/support/WellState.java @@ -0,0 +1,164 @@ +package gov.hhs.aspr.ms.gcm.plugins.stochastics.support; + +import java.util.Arrays; + +import net.jcip.annotations.ThreadSafe; +import util.errors.ContractException; + +@ThreadSafe +public class WellState { + + private static class Data { + long seed; + int index; + int[] vArray; + + public Data() { + } + + public Data(Data data) { + seed = data.seed; + index = data.index; + vArray = Arrays.copyOf(data.vArray, data.vArray.length); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + index; + result = prime * result + (int) (seed ^ (seed >>> 32)); + result = prime * result + Arrays.hashCode(vArray); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + if (index != other.index) { + return false; + } + if (seed != other.seed) { + return false; + } + if (!Arrays.equals(vArray, other.vArray)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Data [seed="); + builder.append(seed); + builder.append(", index="); + builder.append(index); + builder.append(", vArray="); + builder.append(Arrays.toString(vArray)); + builder.append("]"); + return builder.toString(); + } + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Data data = new Data(); + + private Builder() { + + } + + public WellState build() { + if (data.vArray == null) { + return new Well(data.seed).getWellState(); + } + return new WellState(new Data(data)); + } + + public Builder setSeed(long seed) { + data.seed = seed; + return this; + } + + public Builder setInternals(int index, int[] vArray) { + if (vArray == null) { + throw new ContractException(StochasticsError.ILLEGAL_SEED_ININITIAL_STATE); + } + if (vArray.length != 1391) { + throw new ContractException(StochasticsError.ILLEGAL_SEED_ININITIAL_STATE); + } + if (index < 0 || index > 1390) { + throw new ContractException(StochasticsError.ILLEGAL_SEED_ININITIAL_STATE); + } + data.index = index; + data.vArray = Arrays.copyOf(vArray, vArray.length); + return this; + } + + } + + private WellState(Data data) { + this.data = data; + } + + private final Data data; + + public long getSeed() { + return data.seed; + } + + public int getIndex() { + return data.index; + } + + public int[] getVArray() { + return Arrays.copyOf(data.vArray, data.vArray.length); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof WellState)) { + return false; + } + WellState other = (WellState) obj; + if (data == null) { + if (other.data != null) { + return false; + } + } else if (!data.equals(other.data)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder2 = new StringBuilder(); + builder2.append("WellState [data="); + builder2.append(data); + builder2.append("]"); + return builder2.toString(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/testsupport/StochasticsTestPluginFactory.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/testsupport/StochasticsTestPluginFactory.java new file mode 100644 index 000000000..b41ca84ea --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/testsupport/StochasticsTestPluginFactory.java @@ -0,0 +1,177 @@ +package gov.hhs.aspr.ms.gcm.plugins.stochastics.testsupport; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.StochasticsError; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +/** + * A static test support class for the {@linkplain StochasticsPlugin}. Provides + * convenience methods for obtaining standarized PluginData for the listed + * Plugin. + *

    + * Also contains factory methods to obtain a list of plugins that is the minimal + * set needed to adequately test this Plugin that can be utilized with + *

    + * {@link TestSimulation#execute} + */ +public class StochasticsTestPluginFactory { + + private StochasticsTestPluginFactory() { + } + + private static class Data { + private StochasticsPluginData stochasticsPluginData; + private TestPluginData testPluginData; + + private Data(long seed, TestPluginData testPluginData) { + this.stochasticsPluginData = getStandardStochasticsPluginData(seed); + this.testPluginData = testPluginData; + } + } + + /** + * Factory class that facilitates the building of {@linkplain PluginData} with + * the various setter methods. + */ + public static class Factory { + private Data data; + + private Factory(Data data) { + this.data = data; + } + + /** + * Returns a list of plugins containing a Stochastics and Test Plugin built from + * the contributed PluginDatas. + *
      + *
    • StochasticsPlugin is defaulted to one formed from + * {@link StochasticsTestPluginFactory#getStandardStochasticsPluginData}
    • + *
    • TestPlugin is formed from the TestPluginData passed into + * {@link StochasticsTestPluginFactory#factory}
    • + *
    + */ + public List getPlugins() { + List pluginsToAdd = new ArrayList<>(); + + // add the stochastics plugin + Plugin stochasticPlugin = StochasticsPlugin.getStochasticsPlugin(this.data.stochasticsPluginData); + + Plugin testPlugin = TestPlugin.getTestPlugin(this.data.testPluginData); + + pluginsToAdd.add(stochasticPlugin); + pluginsToAdd.add(testPlugin); + + return pluginsToAdd; + } + + /** + * Sets the {@link StochasticsPluginData} in this Factory. This explicit + * instance of pluginData will be used to create a StochasticsPlugin + * + * @throws ContractException {@linkplain StochasticsError#NULL_STOCHASTICS_PLUGIN_DATA} + * if the passed in pluginData is null + */ + public Factory setStochasticsPluginData(StochasticsPluginData stochasticsPluginData) { + if (stochasticsPluginData == null) { + throw new ContractException(StochasticsError.NULL_STOCHASTICS_PLUGIN_DATA); + } + this.data.stochasticsPluginData = stochasticsPluginData; + return this; + } + + } + + /** + * Creates a Factory that facilitates the creation of a minimal set of plugins + * needed to adequately test the {@link StochasticsPlugin} by generating: + *
      + *
    • {@link StochasticsPluginData}
    • + *
    + * either directly (by default) via + *
      + *
    • {@link #getStandardStochasticsPluginData}
    • + *
    + * or explicitly set via + *
      + *
    • {@link Factory#setStochasticsPluginData}
    • + *
    + * via the {@link Factory#getPlugins()} method. + * + * @throws ContractException {@linkplain NucleusError#NULL_PLUGIN_DATA} if + * testPluginData is null + */ + public static Factory factory(long seed, TestPluginData testPluginData) { + if (testPluginData == null) { + throw new ContractException(NucleusError.NULL_PLUGIN_DATA); + } + return new Factory(new Data(seed, testPluginData)); + + } + + /** + * Creates a Factory that facilitates the creation of a minimal set of plugins + * needed to adequately test the {@link StochasticsPlugin} by generating: + *
      + *
    • {@link StochasticsPluginData}
    • + *
    + * either directly (by default) via + *
      + *
    • {@link #getStandardStochasticsPluginData}
    • + *
    + * or explicitly set via + *
      + *
    • {@link Factory#setStochasticsPluginData}
    • + *
    + * via the {@link Factory#getPlugins()} method. + * + * @throws ContractException {@linkplain NucleusError#NULL_ACTOR_CONTEXT_CONSUMER} + * if consumer is null + */ + public static Factory factory(long seed, Consumer consumer) { + if (consumer == null) { + throw new ContractException(NucleusError.NULL_ACTOR_CONTEXT_CONSUMER); + } + TestPluginData testPluginData = TestPluginData.builder()// + .addTestActorPlan("actor", new TestActorPlan(0, consumer))// + .build(); + + return factory(seed, testPluginData); + } + + /** + * Creates a Standardized StocasticsPluginData that is minimally adequate for + * testing the StochasticsPlugin. The resulting StocasticsPluginData will + * include: + *
      + *
    • Every randomGeneratorId included in {@link TestRandomGeneratorId}
    • + *
    + */ + public static StochasticsPluginData getStandardStochasticsPluginData(long seed) { + StochasticsPluginData.Builder builder = StochasticsPluginData.builder(); + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + WellState wellState = WellState.builder().setSeed(randomGenerator.nextLong()).build(); + builder.setMainRNGState(wellState); + for (TestRandomGeneratorId testRandomGeneratorId : TestRandomGeneratorId.values()) { + wellState = WellState.builder().setSeed(randomGenerator.nextLong()).build(); + builder.addRNG(testRandomGeneratorId, wellState); + } + return builder.build(); + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/testsupport/TestRandomGeneratorId.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/testsupport/TestRandomGeneratorId.java new file mode 100644 index 000000000..c1b9c5365 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/testsupport/TestRandomGeneratorId.java @@ -0,0 +1,16 @@ +package gov.hhs.aspr.ms.gcm.plugins.stochastics.testsupport; + +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.RandomNumberGeneratorId; + +/** + * Enumeration that provides a variety of RandomNumberGeneratorId values for + * testing purposes + */ +public enum TestRandomGeneratorId implements RandomNumberGeneratorId { + DASHER, DANCER, PRANCER, VIXEN, COMET, CUPID, DONNER, BLITZEN; + + public static RandomNumberGeneratorId getUnknownRandomNumberGeneratorId() { + return new RandomNumberGeneratorId() { + }; + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/BooleanPropertyManager.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/BooleanPropertyManager.java new file mode 100644 index 000000000..33255f0b6 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/BooleanPropertyManager.java @@ -0,0 +1,93 @@ +package gov.hhs.aspr.ms.gcm.plugins.util.properties; + +import java.util.Iterator; +import java.util.function.Supplier; + +import gov.hhs.aspr.ms.gcm.plugins.util.properties.arraycontainers.BooleanContainer; +import util.errors.ContractException; + +/** + * Implementor of IndexedPropertyManager that compresses Boolean property values + * into a bit-based data structure. + */ +public final class BooleanPropertyManager implements IndexedPropertyManager { + + /* + * A container, indexed by person id, that stores boolean values as bits. + */ + private BooleanContainer boolContainer; + + /** + * Constructs this BooleanPropertyManager. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_DEFINITION} + * if the property definition is null
    • + *
    • {@linkplain PropertyError#PROPERTY_DEFINITION_IMPROPER_TYPE} + * if the property definition's type is not + * Boolean
    • + *
    + */ + public BooleanPropertyManager(PropertyDefinition propertyDefinition, + Supplier> indexIteratorSupplier) { + if (propertyDefinition == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_DEFINITION); + } + + if (propertyDefinition.getType() != Boolean.class) { + throw new ContractException(PropertyError.PROPERTY_DEFINITION_IMPROPER_TYPE, + "Requires a property definition with Boolean type "); + } + boolean defaultValue = false; + if (propertyDefinition.getDefaultValue().isPresent()) { + defaultValue = (Boolean) propertyDefinition.getDefaultValue().get(); + } + + boolContainer = new BooleanContainer(defaultValue, indexIteratorSupplier); + } + + @Override + @SuppressWarnings("unchecked") + public T getPropertyValue(int id) { + if (id < 0) { + throw new ContractException(PropertyError.NEGATIVE_INDEX); + } + Boolean result = boolContainer.get(id); + return (T) result; + } + + @Override + public void setPropertyValue(int id, Object propertyValue) { + if (id < 0) { + throw new ContractException(PropertyError.NEGATIVE_INDEX); + } + Boolean b = (Boolean) propertyValue; + boolContainer.set(id, b.booleanValue()); + } + + @Override + public void incrementCapacity(int count) { + if (count < 0) { + throw new ContractException(PropertyError.NEGATIVE_CAPACITY_INCREMENT); + } + boolContainer.expandCapacity(count); + } + + @Override + public void removeId(int id) { + if (id < 0) { + throw new ContractException(PropertyError.NEGATIVE_INDEX); + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("BooleanPropertyManager [boolContainer="); + builder.append(boolContainer); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/DoublePropertyManager.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/DoublePropertyManager.java new file mode 100644 index 000000000..e2071c558 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/DoublePropertyManager.java @@ -0,0 +1,95 @@ +package gov.hhs.aspr.ms.gcm.plugins.util.properties; + +import java.util.Iterator; +import java.util.function.Supplier; + +import gov.hhs.aspr.ms.gcm.plugins.util.properties.arraycontainers.DoubleValueContainer; +import util.errors.ContractException; + +/** + * Implementor of IndexedPropertyManager that compresses Double property values + * into a double[]-based data structure. + */ +public final class DoublePropertyManager implements IndexedPropertyManager { + + /* + * A container, indexed by person id, that stores Double values as an array of + * double. + */ + private DoubleValueContainer doubleValueContainer; + + /** + * Constructs this DoublePropertyManager. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NEGATIVE_INITIAL_SIZE} + * if the initial size is negative
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_DEFINITION} + * if the property definition is null
    • + *
    • {@linkplain PropertyError#PROPERTY_DEFINITION_IMPROPER_TYPE} + * if the property definition's type is not + * Double
    • + *
    + */ + public DoublePropertyManager(PropertyDefinition propertyDefinition, + Supplier> indexIteratorSupplier) { + if (propertyDefinition == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_DEFINITION); + } + + if (propertyDefinition.getType() != Double.class) { + throw new ContractException(PropertyError.PROPERTY_DEFINITION_IMPROPER_TYPE, + "Requires a property definition with Double type "); + } + Double defaultValue = 0D; + if (propertyDefinition.getDefaultValue().isPresent()) { + defaultValue = (Double) propertyDefinition.getDefaultValue().get(); + } + doubleValueContainer = new DoubleValueContainer(defaultValue, indexIteratorSupplier); + } + + @Override + @SuppressWarnings("unchecked") + public T getPropertyValue(int id) { + if (id < 0) { + throw new ContractException(PropertyError.NEGATIVE_INDEX); + } + Double result = doubleValueContainer.getValue(id); + return (T) result; + } + + @Override + public void setPropertyValue(int id, Object propertyValue) { + if (id < 0) { + throw new ContractException(PropertyError.NEGATIVE_INDEX); + } + Double d = (Double) propertyValue; + doubleValueContainer.setValue(id, d); + } + + @Override + public void incrementCapacity(int count) { + if (count < 0) { + throw new ContractException(PropertyError.NEGATIVE_CAPACITY_INCREMENT); + } + doubleValueContainer.setCapacity(doubleValueContainer.getCapacity() + count); + } + + @Override + public void removeId(int id) { + if (id < 0) { + throw new ContractException(PropertyError.NEGATIVE_INDEX); + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("DoublePropertyManager [doubleValueContainer="); + builder.append(doubleValueContainer); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/EnumPropertyManager.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/EnumPropertyManager.java new file mode 100644 index 000000000..e16a2bbe5 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/EnumPropertyManager.java @@ -0,0 +1,93 @@ +package gov.hhs.aspr.ms.gcm.plugins.util.properties; + +import java.util.Iterator; +import java.util.function.Supplier; + +import gov.hhs.aspr.ms.gcm.plugins.util.properties.arraycontainers.EnumContainer; +import util.errors.ContractException; + +/** + * Implementor of IndexedPropertyManager that compresses Enum property values + * into a byte-based data structure of the various int-like primitives. + */ +public final class EnumPropertyManager implements IndexedPropertyManager { + /* + * The storage container. + */ + private EnumContainer enumContainer; + + /** + * Constructs this EnumPropertyManager. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NEGATIVE_INITIAL_SIZE} + * if the initial size is negative
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_DEFINITION} + * if the property definition is null
    • + *
    • {@linkplain PropertyError#PROPERTY_DEFINITION_IMPROPER_TYPE} + * if the property definition's type is not an + * enumeration
    • + *
    + */ + public EnumPropertyManager(PropertyDefinition propertyDefinition, + Supplier> indexIteratorSupplier) { + if (propertyDefinition == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_DEFINITION); + } + + Object defaultValue = null; + if (propertyDefinition.getDefaultValue().isPresent()) { + defaultValue = propertyDefinition.getDefaultValue().get(); + } + + if (!Enum.class.isAssignableFrom(propertyDefinition.getType())) { + throw new ContractException(PropertyError.PROPERTY_DEFINITION_IMPROPER_TYPE, + "cannot construct from class " + propertyDefinition.getClass().getName()); + } + + enumContainer = new EnumContainer(propertyDefinition.getType(), defaultValue, indexIteratorSupplier); + } + + @Override + @SuppressWarnings("unchecked") + public T getPropertyValue(int id) { + if (id < 0) { + throw new ContractException(PropertyError.NEGATIVE_INDEX); + } + return (T) enumContainer.getValue(id); + } + + @Override + public void setPropertyValue(int id, Object propertyValue) { + if (id < 0) { + throw new ContractException(PropertyError.NEGATIVE_INDEX); + } + enumContainer.setValue(id, propertyValue); + } + + @Override + public void incrementCapacity(int count) { + if (count < 0) { + throw new ContractException(PropertyError.NEGATIVE_CAPACITY_INCREMENT); + } + enumContainer.setCapacity(enumContainer.getCapacity() + count); + } + + @Override + public void removeId(int id) { + if (id < 0) { + throw new ContractException(PropertyError.NEGATIVE_INDEX); + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("EnumPropertyManager [enumContainer="); + builder.append(enumContainer); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/FloatPropertyManager.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/FloatPropertyManager.java new file mode 100644 index 000000000..cd59f96c0 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/FloatPropertyManager.java @@ -0,0 +1,94 @@ +package gov.hhs.aspr.ms.gcm.plugins.util.properties; + +import java.util.Iterator; +import java.util.function.Supplier; + +import gov.hhs.aspr.ms.gcm.plugins.util.properties.arraycontainers.FloatValueContainer; +import util.errors.ContractException; + +/** + * Implementor of IndexedPropertyManager that compresses Float property values + * into a float[]-based data structure. + */ +public final class FloatPropertyManager implements IndexedPropertyManager { + /* + * A container, indexed by person id, that stores Double values as an array of + * float. + */ + private FloatValueContainer floatValueContainer; + + /** + * Constructs this FloatPropertyManager. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NEGATIVE_INITIAL_SIZE} + * if the initial size is negative
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_DEFINITION} + * if the property definition is null
    • + *
    • {@linkplain PropertyError#PROPERTY_DEFINITION_IMPROPER_TYPE} + * if the property definition's type is not + * Boolean
    • + *
    + */ + public FloatPropertyManager(PropertyDefinition propertyDefinition, + Supplier> indexIteratorSupplier) { + if (propertyDefinition == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_DEFINITION); + } + + if (propertyDefinition.getType() != Float.class) { + throw new ContractException(PropertyError.PROPERTY_DEFINITION_IMPROPER_TYPE, + "Requires a property definition with float type"); + } + Float defaultValue = 0F; + if (propertyDefinition.getDefaultValue().isPresent()) { + defaultValue = (Float) propertyDefinition.getDefaultValue().get(); + } + floatValueContainer = new FloatValueContainer(defaultValue, indexIteratorSupplier); + } + + @Override + @SuppressWarnings("unchecked") + public T getPropertyValue(int id) { + if (id < 0) { + throw new ContractException(PropertyError.NEGATIVE_INDEX); + } + Float result = floatValueContainer.getValue(id); + return (T) result; + } + + @Override + public void setPropertyValue(int id, Object propertyValue) { + if (id < 0) { + throw new ContractException(PropertyError.NEGATIVE_INDEX); + } + Float f = (Float) propertyValue; + floatValueContainer.setValue(id, f); + } + + @Override + public void incrementCapacity(int count) { + if (count < 0) { + throw new ContractException(PropertyError.NEGATIVE_CAPACITY_INCREMENT); + } + floatValueContainer.setCapacity(floatValueContainer.getCapacity() + count); + } + + @Override + public void removeId(int id) { + if (id < 0) { + throw new ContractException(PropertyError.NEGATIVE_INDEX); + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("FloatPropertyManager [floatValueContainer="); + builder.append(floatValueContainer); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/IndexedPropertyManager.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/IndexedPropertyManager.java new file mode 100644 index 000000000..0b6309110 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/IndexedPropertyManager.java @@ -0,0 +1,54 @@ +package gov.hhs.aspr.ms.gcm.plugins.util.properties; + +import util.errors.ContractException; + +/** + * Common interface to all property managers. A property manager manages + * property values associated with int-based identifiers. + */ +public interface IndexedPropertyManager { + + /** + * Returns the property value stored for the given id. Does not return null. + * Note that this does not imply that the id exists in the simulation. + * + * @throws ContractException {@linkplain PropertyError#NEGATIVE_INDEX} if the id + * is negative + */ + public T getPropertyValue(int id); + + /** + * Sets the property value stored for the given person. Note that this does not + * imply that the person exists in the simulation. The environment must guard + * against access to removed people. + * + * @throws ContractException {@linkplain PropertyError#NEGATIVE_INDEX} if the id + * is negative + */ + public void setPropertyValue(int id, Object propertyValue); + + /** + * Removes non-primitive property values for the given id -- use only when + * removing the indicated id from the simulation. + * + * @throws ContractException {@linkplain PropertyError#NEGATIVE_INDEX} if the id + * is negative + */ + public void removeId(int id); + + /** + * Sets the capacity for this manager. Indicates to the manager an anticipated + * near term growth so that the manager might more efficiently expand to hold + * more data. + * + * @throws ContractException {@linkplain PropertyError#NEGATIVE_CAPACITY_INCREMENT} + * if the count is negative + */ + public void incrementCapacity(int count); + + /** + * Returns the string version of this indexed property manager + */ + public String toString(); + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/IntPropertyManager.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/IntPropertyManager.java new file mode 100644 index 000000000..cc78bacf4 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/IntPropertyManager.java @@ -0,0 +1,168 @@ +package gov.hhs.aspr.ms.gcm.plugins.util.properties; + +import java.util.Iterator; +import java.util.function.Supplier; + +import gov.hhs.aspr.ms.gcm.plugins.util.properties.arraycontainers.IntValueContainer; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.arraycontainers.IntValueContainer.IntValueType; +import util.errors.ContractException; + +/** + * Implementor of IndexedPropertyManager that compresses Byte, Short, Integer or + * Long property values into a byte-based array data structure. + */ +public final class IntPropertyManager implements IndexedPropertyManager { + + /* + * A container, indexed by person id, that stores the various Boxed integral + * types values as bytes. + */ + private IntValueContainer intValueContainer; + + /* + * The particular IntValueType for this property manager as determined by the + * class type associated with the corresponding property definition. + */ + private IntValueType intValueType; + + /** + * Constructs this IntPropertyManager. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NEGATIVE_INITIAL_SIZE} + * if the initial size is negative
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_DEFINITION} + * if the property definition is null
    • + *
    • {@linkplain PropertyError#PROPERTY_DEFINITION_IMPROPER_TYPE} + * if the property definition's type is not a Byte, + * Short, Integer or Long
    • + *
    + */ + public IntPropertyManager(PropertyDefinition propertyDefinition, + Supplier> indexIteratorSupplier) { + if (propertyDefinition == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_DEFINITION); + } + + long longDefaultValue = 0L; + if (propertyDefinition.getType() == Byte.class) { + intValueType = IntValueType.BYTE; + } else if (propertyDefinition.getType() == Short.class) { + intValueType = IntValueType.SHORT; + } else if (propertyDefinition.getType() == Integer.class) { + intValueType = IntValueType.INT; + } else if (propertyDefinition.getType() == Long.class) { + intValueType = IntValueType.LONG; + } else { + throw new ContractException(PropertyError.PROPERTY_DEFINITION_IMPROPER_TYPE, + "Requires a property definition type of Byte, Short, Integer or Long"); + } + if (propertyDefinition.getDefaultValue().isPresent()) { + Object defaultValue = propertyDefinition.getDefaultValue().get(); + + switch (intValueType) { + case BYTE: + Byte b = (Byte) defaultValue; + longDefaultValue = b.longValue(); + break; + case INT: + Integer i = (Integer) defaultValue; + longDefaultValue = i.longValue(); + break; + case LONG: + Long l = (Long) defaultValue; + longDefaultValue = l.longValue(); + break; + case SHORT: + Short s = (Short) defaultValue; + longDefaultValue = s.longValue(); + break; + default: + throw new RuntimeException("unhandled type " + intValueType); + } + } + + intValueContainer = new IntValueContainer(longDefaultValue, indexIteratorSupplier); + } + + @Override + @SuppressWarnings("unchecked") + public T getPropertyValue(int id) { + if (id < 0) { + throw new ContractException(PropertyError.NEGATIVE_INDEX); + } + + switch (intValueType) { + case BYTE: + Byte b = intValueContainer.getValueAsByte(id); + return (T) b; + case INT: + Integer i = intValueContainer.getValueAsInt(id); + return (T) i; + case LONG: + Long l = intValueContainer.getValueAsLong(id); + return (T) l; + case SHORT: + Short s = intValueContainer.getValueAsShort(id); + return (T) s; + default: + throw new RuntimeException("unhandled type"); + } + } + + @Override + public void setPropertyValue(int id, Object propertyValue) { + if (id < 0) { + throw new ContractException(PropertyError.NEGATIVE_INDEX); + } + + switch (intValueType) { + case BYTE: + Byte b = (Byte) propertyValue; + intValueContainer.setByteValue(id, b); + break; + case INT: + Integer i = (Integer) propertyValue; + intValueContainer.setIntValue(id, i); + break; + case LONG: + Long l = (Long) propertyValue; + intValueContainer.setLongValue(id, l); + break; + case SHORT: + Short s = (Short) propertyValue; + intValueContainer.setShortValue(id, s); + break; + default: + throw new RuntimeException("unhandled type " + intValueType); + } + } + + @Override + public void incrementCapacity(int count) { + if (count < 0) { + throw new ContractException(PropertyError.NEGATIVE_CAPACITY_INCREMENT); + } + intValueContainer.setCapacity(intValueContainer.getCapacity() + count); + } + + @Override + public void removeId(int id) { + if (id < 0) { + throw new ContractException(PropertyError.NEGATIVE_INDEX); + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("IntPropertyManager [intValueContainer="); + builder.append(intValueContainer); + builder.append(", intValueType="); + builder.append(intValueType); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/ObjectPropertyManager.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/ObjectPropertyManager.java new file mode 100644 index 000000000..5c25bec2d --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/ObjectPropertyManager.java @@ -0,0 +1,92 @@ +package gov.hhs.aspr.ms.gcm.plugins.util.properties; + +import java.util.Iterator; +import java.util.function.Supplier; + +import gov.hhs.aspr.ms.gcm.plugins.util.properties.arraycontainers.ObjectValueContainer; +import util.errors.ContractException; + +/** + * Implementor of IndexedPropertyManager that stores Object property values in + * an Object array based data structure. + */ +public final class ObjectPropertyManager implements IndexedPropertyManager { + + /* + * A container, indexed by person id, that stores Objects as an array. + */ + private ObjectValueContainer objectValueContainer; + private final Object defaultValue; + + /** + * Constructs this IntPropertyManager. + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NEGATIVE_INITIAL_SIZE} + * if the initial size is negative
    • + *
    • {@linkplain PropertyError#NULL_PROPERTY_DEFINITION} + * if the property definition is null
    • + *
    + */ + public ObjectPropertyManager(PropertyDefinition propertyDefinition, + Supplier> indexIteratorSupplier) { + if (propertyDefinition == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_DEFINITION); + } + + if (propertyDefinition.getDefaultValue().isPresent()) { + defaultValue = propertyDefinition.getDefaultValue().get(); + } else { + defaultValue = null; + } + + objectValueContainer = new ObjectValueContainer(defaultValue, indexIteratorSupplier); + } + + @Override + public T getPropertyValue(int id) { + if (id < 0) { + throw new ContractException(PropertyError.NEGATIVE_INDEX); + } + return objectValueContainer.getValue(id); + } + + @Override + public void setPropertyValue(int id, Object propertyValue) { + if (id < 0) { + throw new ContractException(PropertyError.NEGATIVE_INDEX); + } + objectValueContainer.setValue(id, propertyValue); + } + + @Override + public void removeId(int id) { + // dropping reference to the currently stored value for potential + // garbage collection + if (id < 0) { + throw new ContractException(PropertyError.NEGATIVE_INDEX); + } + objectValueContainer.setValue(id, defaultValue); + } + + @Override + public void incrementCapacity(int count) { + if (count < 0) { + throw new ContractException(PropertyError.NEGATIVE_CAPACITY_INCREMENT); + } + objectValueContainer.setCapacity(objectValueContainer.getCapacity() + count); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("ObjectPropertyManager [objectValueContainer="); + builder.append(objectValueContainer); + builder.append(", defaultValue="); + builder.append(defaultValue); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/PropertyDefinition.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/PropertyDefinition.java new file mode 100644 index 000000000..8bb905022 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/PropertyDefinition.java @@ -0,0 +1,227 @@ +package gov.hhs.aspr.ms.gcm.plugins.util.properties; + +import java.util.Optional; + +import net.jcip.annotations.ThreadSafe; +import util.errors.ContractException; + +/** + * A thread-safe, immutable class that defines a property, but does not indicate + * the role that property is playing or the identifier of the property. + */ +@ThreadSafe +public final class PropertyDefinition { + + public static Builder builder() { + return new Builder(); + } + + private static class Data { + + private Class type = null; + + private boolean propertyValuesAreMutable = true; + + private Object defaultValue = null; + + public Data() { + } + + public Data(Data data) { + type = data.type; + propertyValuesAreMutable = data.propertyValuesAreMutable; + defaultValue = data.defaultValue; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((defaultValue == null) ? 0 : defaultValue.hashCode()); + result = prime * result + (propertyValuesAreMutable ? 1231 : 1237); + result = prime * result + type.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + if (defaultValue == null) { + if (other.defaultValue != null) { + return false; + } + } else if (!defaultValue.equals(other.defaultValue)) { + return false; + } + if (propertyValuesAreMutable != other.propertyValuesAreMutable) { + return false; + } + if (!type.equals(other.type)) { + return false; + } + return true; + } + + } + + /** + * Builder class for {@linkplain PropertyDefinition} + */ + public static class Builder { + + private Data data = new Data(); + + private Builder() { + } + + private void validate() { + if (data.type == null) { + throw new ContractException(PropertyError.NULL_PROPERTY_TYPE); + } + + if (data.defaultValue != null) { + if (!data.type.isInstance(data.defaultValue)) { + throw new ContractException(PropertyError.INCOMPATIBLE_DEFAULT_VALUE); + } + } + } + + /** + * Builds the property definition + * + * @throws ContractException + *
      + *
    • {@linkplain PropertyError#NULL_PROPERTY_TYPE} + * if the class type of the definition is not assigned + * or null
    • + *
    • {@linkplain PropertyError#INCOMPATIBLE_DEFAULT_VALUE}if + * the default value is not null and the class type is + * not a super-type of the default value
    • + *
    + */ + public PropertyDefinition build() { + validate(); + return new PropertyDefinition(new Data(data)); + } + + /** + * Sets the class type. Value must be set by client. + */ + public Builder setType(final Class type) { + data.type = type; + return this; + } + + /** + * Sets property value mutability during simulation run time. Default value is + * true. + */ + public Builder setPropertyValueMutability(boolean propertyValuesAreMutable) { + data.propertyValuesAreMutable = propertyValuesAreMutable; + return this; + } + + /** + * Sets the default value for property values of this definition. Value must be + * set(non-null) by client. + */ + public Builder setDefaultValue(Object defaultValue) { + data.defaultValue = defaultValue; + return this; + } + + } + + private final Data data; + + private PropertyDefinition(Data data) { + this.data = data; + } + + /** + * Returns the Optional containing default value for the property definition. + * Null values are allowed as a convenience. Any property definition that has a + * null default value must have corresponding property value assignments within + * plugin initial data that cover all cases. Property definitions for + * dynamically generated relationships MUST contain non-null default values + * since they are created after data initialization of the plugins. + */ + public Optional getDefaultValue() { + if (data.defaultValue == null) { + return Optional.empty(); + } + return Optional.of(data.defaultValue); + } + + /** + * Returns that class type of this definition. It is used to ensure that all + * values assigned to properties have a predictable type from the modeler's + * perspective. Property assignments are descendant class tolerant. For example, + * a property having a defined type of Number, would accept values that are + * Double, Integer or any other descendant type. + */ + public Class getType() { + return data.type; + } + + /** + * The modeler may define a property such that all associated property values + * must be equal to the default value of this property definition. Any attempt + * to assign a value to a property so defined will result in an error. This can + * be used to ensure that some variables remain constant throughout the run of a + * simulation instance. Returns true if and only if the value of the property + * must remain constant. + */ + public boolean propertyValuesAreMutable() { + return data.propertyValuesAreMutable; + } + + /** + * Standard string representation in the form: PropertyDefinition + * [type=someType,mapOption=mapOption, constantPropertyValues=true, + * defaultValue=someValue, timeTrackingPolicy=policy] + */ + @Override + public String toString() { + StringBuilder builder2 = new StringBuilder(); + builder2.append("PropertyDefinition [type="); + builder2.append(data.type); + builder2.append(", propertyValuesAreMutable="); + builder2.append(data.propertyValuesAreMutable); + builder2.append(", defaultValue="); + builder2.append(data.defaultValue); + builder2.append("]"); + return builder2.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof PropertyDefinition)) { + return false; + } + PropertyDefinition other = (PropertyDefinition) obj; + + if (!data.equals(other.data)) { + return false; + } + return true; + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/PropertyError.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/PropertyError.java new file mode 100644 index 000000000..7b52d49d1 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/PropertyError.java @@ -0,0 +1,49 @@ +package gov.hhs.aspr.ms.gcm.plugins.util.properties; + +import util.errors.ContractError; +import util.errors.ContractException; + +/** + * An enumeration supporting {@link ContractException} that acts as a general + * description of the exception. + */ +public enum PropertyError implements ContractError { + + INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT( + "A property definition lacks a default value and there are not sufficient property value assignments to cover the missing default value"), // + NULL_PROPERTY_ID("Null property id"), NULL_PROPERTY_DEFINITION("Null property definition"), // + NULL_PROPERTY_DEFINITION_INITIALIZATION("Null property definition initialization"), // + NULL_PROPERTY_VALUE("Null property value"), + DUPLICATE_PROPERTY_DEFINITION("Duplicate assignment of a property definition to a property id"), + UNKNOWN_PROPERTY_ID("Unknown property id"), + INCOMPATIBLE_VALUE("Property value is incompatible with the property definition"), // + INCOMPATIBLE_TIME("Property assignment time is less than property definition creation time"), // + INCOMPATIBLE_DEF_TIME("Property definition creation time exceeds simulation time"), // + DUPLICATE_PROPERTY_VALUE_ASSIGNMENT("Duplicate property value assignment"), + PROPERTY_CREATION_TIME_NOT_CURRENT("A property creation time does not agree with the current time"), + + NULL_PROPERTY_TYPE("Type for property definition is null"), // + IMMUTABLE_VALUE("This property is defined as immutable"), // + INCOMPATIBLE_DEFAULT_VALUE("Default value is incompatible with the class type"), // + PROPERTY_DEFINITION_IMPROPER_TYPE("Property definition has improper data type"), // + + NEGATIVE_INITIAL_SIZE("Negative initial size"), // + NEGATIVE_CAPACITY_INCREMENT("Negative capacity increment"), // + NEGATIVE_INDEX("Negative index"), // + + NULL_TIME_TRACKING_POLICY("Time tracking policy is null"), // + TIME_TRACKING_OFF("Time tracking is off"),// + + ; + + private final String description; + + private PropertyError(final String description) { + this.description = description; + } + + @Override + public String getDescription() { + return description; + } +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/BooleanContainer.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/BooleanContainer.java new file mode 100644 index 000000000..3a1c4321f --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/BooleanContainer.java @@ -0,0 +1,116 @@ +package gov.hhs.aspr.ms.gcm.plugins.util.properties.arraycontainers; + +import java.util.BitSet; +import java.util.Iterator; +import java.util.function.Supplier; + +/** + * A container that maps non-negative int index values to booleans by storing + * each boolean as a single bit in a BitSet. Returns a default boolean value for + * every non-negative int index value until the value is explicitly set by an + * invocation to the set() method. + */ +public final class BooleanContainer { + /* + * The default value to return for all indexes that are greater than or equal to + * the bounding index. + */ + private final boolean defaultValue; + + /* + * The lowest index value that has not been the subject of an explicit + * invocation of the set() method. + */ + private int boundingIndex; + + public BooleanContainer(boolean defaultValue, Supplier> indexIteratorSupplier) { + this.defaultValue = defaultValue; + bitSet = new BitSet(); + this.indexIteratorSupplier = indexIteratorSupplier; + } + + public void expandCapacity(int count) { + if (count > 0) { + set(boundingIndex + count - 1, defaultValue); + } + } + + /* + * The bitSet containing the bit level representation of the Booleans. + */ + private BitSet bitSet; + + private final Supplier> indexIteratorSupplier; + + /** + * Returns the boolean value associated with the given index + * + * @throws IndexOutOfBoundsException if the specified index is negative + */ + public boolean get(int index) { + /* + * If the index is beyond any we have had set, then return the default value. + */ + if (index >= boundingIndex) { + return defaultValue; + } + return bitSet.get(index); + } + + /** + * Set the boolean value associated with the given index + * + * @throws IndexOutOfBoundsException if the specified index is negative + * @param index + * @param value + */ + public void set(int index, boolean value) { + if (index < 0) { + throw new IndexOutOfBoundsException("index = " + index); + } + // if the index is new to us, then fill the bitSet with the default from + // the bounding index to the index. + if (index >= boundingIndex) { + int newBoundingIndex = index + 1; + bitSet.set(boundingIndex, newBoundingIndex, defaultValue); + boundingIndex = newBoundingIndex; + } + bitSet.set(index, value); + } + + private String getElementsString() { + Iterator iterator = indexIteratorSupplier.get(); + boolean first = true; + + StringBuilder sb = new StringBuilder(); + sb.append('['); + + while (iterator.hasNext()) { + Integer index = iterator.next(); + boolean value = get(index); + if (first) { + first = false; + } else { + sb.append(", "); + } + sb.append(index); + sb.append("="); + sb.append(value); + } + + sb.append(']'); + return sb.toString(); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("BooleanContainer [defaultValue="); + builder.append(defaultValue); + builder.append(", bitSet="); + builder.append(getElementsString()); + builder.append("]"); + return builder.toString(); + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/DoubleValueContainer.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/DoubleValueContainer.java new file mode 100644 index 000000000..cfc42cea5 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/DoubleValueContainer.java @@ -0,0 +1,162 @@ +package gov.hhs.aspr.ms.gcm.plugins.util.properties.arraycontainers; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.function.Supplier; + +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.errors.ContractException; + +/** + * A container that maps non-negative int index values to doubles by storing + * each double in an array. Returns a default double value for every + * non-negative int index value until the value is explicitly set by an + * invocation to the set() method. + */ +public final class DoubleValueContainer { + + /* + * The array for storing the values + */ + private double[] values; + + private final Supplier> indexIteratorSupplier; + + /* + * The value returned for any non-negative index that has not been set via an + * invocation of setValue(). + */ + private double defaultValue; + + /* + * Grows the length of the values array to be the greater of the given capacity + * and 125% of its current length, filling empty elements in the array with the + * default value. + */ + private void grow(int capacity) { + int oldCapacity = values.length; + int newCapacity = Math.max(capacity, values.length + (values.length >> 2)); + values = Arrays.copyOf(values, newCapacity); + if (defaultValue != 0) { + for (int i = oldCapacity; i < newCapacity; i++) { + values[i] = defaultValue; + } + } + } + + /** + * Returns the value at index + * + * @param index + * @return + * @throws ContractException {@linkplain PropertyError#NEGATIVE_INDEX} if index + * is negative + */ + public double getValue(int index) { + double result; + + if (index < 0) { + throw new ContractException(PropertyError.NEGATIVE_INDEX); + } + + if (index < values.length) { + result = values[index]; + } else { + result = defaultValue; + } + + return result; + } + + /** + * Sets the capacity to the given capacity if the current capacity is less than + * the one given. + */ + public void setCapacity(int capacity) { + if (capacity > values.length) { + grow(capacity); + } + } + + /** + * Returns the capacity of this container. Capacity is guaranteed to be greater + * than or equal to size. + */ + public int getCapacity() { + return values.length; + } + + /** + * Returns the default value + */ + public double getDefaultValue() { + return defaultValue; + } + + /** + * Constructs the DoubleValueContainer with the given default value and initial + * capacity + * + * @throws NegativeArraySizeException if the capacity is negative + */ + public DoubleValueContainer(double defaultValue, Supplier> indexIteratorSupplier) { + values = new double[0]; + this.defaultValue = defaultValue; + this.indexIteratorSupplier = indexIteratorSupplier; + } + + /** + * Sets the value at the index to the given value + * + * @throws ContractException {@linkplain PropertyError#NEGATIVE_INDEX} if index + * is negative + */ + public void setValue(int index, double value) { + if (index < 0) { + throw new ContractException(PropertyError.NEGATIVE_INDEX); + } + if (index >= values.length) { + grow(index + 1); + } + + values[index] = value; + } + + private String getElementsString() { + Iterator iterator = indexIteratorSupplier.get(); + StringBuilder sb = new StringBuilder(); + + boolean first = true; + sb.append('['); + + while (iterator.hasNext()) { + Integer index = iterator.next(); + double value = getValue(index); + + if (first) { + first = false; + } else { + sb.append(", "); + } + + sb.append(index); + sb.append("="); + sb.append(value); + + } + sb.append(']'); + return sb.toString(); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("DoubleValueContainer [values="); + builder.append(getElementsString()); + builder.append(", defaultValue="); + builder.append(defaultValue); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/EnumContainer.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/EnumContainer.java new file mode 100644 index 000000000..c8640c7c5 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/EnumContainer.java @@ -0,0 +1,161 @@ +package gov.hhs.aspr.ms.gcm.plugins.util.properties.arraycontainers; + +import java.util.Iterator; +import java.util.function.Supplier; + +/** + * A container for retaining enum values from a single enumeration class indexed + * by int index. It is designed to generally require approximately 1 byte per + * index rather than a 4 or 8 byte object reference. + */ +public final class EnumContainer { + + /* + * We store the enum values by their ord equivalents. + */ + private IntValueContainer intValueContainer; + + /* + * We convert an ord value for an enum into enum member instance via an + * ObjectValueContainer. + */ + private ObjectValueContainer objectValueContainer; + + /* + * The class reference for the enum type + */ + private final Class enumClass; + private final Supplier> indexIteratorSupplier; + + /** + * Constructs an instance of EnumContainer. + * + * @throws IllegalArgumentException + *
      + *
    • if the class is null
    • + *
    • if the class is not an enumeration
    • + *
    • if the default is not null and not a + * member of the enumeration
    • + *
    + */ + public EnumContainer(Class c, Object defaultValue, Supplier> indexIteratorSupplier) { + if (c == null) { + throw new IllegalArgumentException("null class reference"); + } + + if (!Enum.class.isAssignableFrom(c)) { + throw new IllegalArgumentException("cannot construct from class " + c.getName()); + } + + if (defaultValue != null && defaultValue.getClass() != c) { + throw new IllegalArgumentException("default value " + defaultValue + " does not match enum class " + c); + } + + this.indexIteratorSupplier = indexIteratorSupplier; + + enumClass = c; + Enum e = (Enum) defaultValue; + objectValueContainer = new ObjectValueContainer(null, null); + objectValueContainer.setValue(e.ordinal(), defaultValue); + intValueContainer = new IntValueContainer(e.ordinal(), null); + } + + /** + * Set the value at the given index. + * + * @throws IllegalArgumentException + *
      + *
    • if the index is negative
    • + *
    • if the value is null
    • + *
    • if the value is not a member of the + * enumeration
    • + *
    + */ + public void setValue(int index, Object value) { + + if (index < 0) { + throw new IllegalArgumentException("negative index: " + index); + } + + if (value == null) { + throw new IllegalArgumentException("null value"); + } + if (value.getClass() != enumClass) { + throw new IllegalArgumentException("improper class type for value " + value + ", expected " + enumClass); + } + + /* + * Convert the object value into an instance of Enum + */ + Enum e = (Enum) value; + + if (objectValueContainer.getValue(e.ordinal()) == null) { + objectValueContainer.setValue(e.ordinal(), value); + } + + intValueContainer.setIntValue(index, e.ordinal()); + + } + + /** + * Set the value at the given index. + * + * @throws ArrayIndexOutOfBoundsException if the index is negative + */ + public Object getValue(int index) { + /* + * Retrieve the ordinal value from the index + */ + int ordinal = intValueContainer.getValueAsInt(index); + /* + * Convert the ordinal value into the enum member instance. + */ + return objectValueContainer.getValue(ordinal); + } + + public int getCapacity() { + return intValueContainer.getCapacity(); + } + + public void setCapacity(int capacity) { + intValueContainer.setCapacity(capacity); + } + + private String getElementsString() { + Iterator iterator = indexIteratorSupplier.get(); + StringBuilder sb = new StringBuilder(); + + boolean first = true; + sb.append('['); + + while (iterator.hasNext()) { + Integer index = iterator.next(); + Object value = getValue(index); + + if (first) { + first = false; + } else { + sb.append(", "); + } + + sb.append(index); + sb.append("="); + sb.append(value); + + } + sb.append(']'); + return sb.toString(); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("EnumContainer [values="); + builder.append(getElementsString()); + builder.append(", enumClass="); + builder.append(enumClass); + builder.append("]"); + return builder.toString(); + } + +} \ No newline at end of file diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/FloatValueContainer.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/FloatValueContainer.java new file mode 100644 index 000000000..8013df6f8 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/FloatValueContainer.java @@ -0,0 +1,159 @@ +package gov.hhs.aspr.ms.gcm.plugins.util.properties.arraycontainers; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.function.Supplier; + +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.errors.ContractException; + +/** + * A container that maps non-negative int index values to floats by storing each + * float in an array. Returns a default float value for every non-negative int + * index value until the value is explicitly set by an invocation to the set() + * method. + */ +public final class FloatValueContainer { + /* + * The array for storing the values + */ + private float[] values; + + private Supplier> indexIteratorSupplier; + + /* + * The value returned for any non-negative index that has not been set via an + * invocation of setValue(). + */ + private float defaultValue; + + /* + * Grows the length of the values array to be the greater of the given capacity + * and 125% of its current length, filling empty elements in the array with the + * default value. + */ + private void grow(int capacity) { + int oldCapacity = values.length; + int newCapacity = Math.max(capacity, values.length + (values.length >> 2)); + values = Arrays.copyOf(values, newCapacity); + if (defaultValue != 0) { + for (int i = oldCapacity; i < newCapacity; i++) { + values[i] = defaultValue; + } + } + } + + /** + * Returns the value at index + * + * @param index + * @return + * @throws ContractException {@linkplain PropertyError#NEGATIVE_INDEX} if index + * is negative + */ + public float getValue(int index) { + if (index < 0) { + throw new ContractException(PropertyError.NEGATIVE_INDEX); + } + + float result; + if (index < values.length) { + result = values[index]; + } else { + result = defaultValue; + } + + return result; + } + + /** + * Sets the capacity to the given capacity if the current capacity is less than + * the one given. + */ + public void setCapacity(int capacity) { + if (capacity > values.length) { + grow(capacity); + } + } + + /** + * Returns the capacity of this container. Capacity is guaranteed to be greater + * than or equal to size. + */ + public int getCapacity() { + return values.length; + } + + /** + * Returns the default value + */ + public float getDefaultValue() { + return defaultValue; + } + + /** + * Constructs the floatValueContainer with the given default value and initial + * capacity + * + * @throws NegativeArraySizeException if the capacity is negative + */ + public FloatValueContainer(float defaultValue, Supplier> indexIteratorSupplier) { + values = new float[0]; + this.defaultValue = defaultValue; + this.indexIteratorSupplier = indexIteratorSupplier; + } + + /** + * Sets the value at the index to the given value + * + * @throws ContractException {@linkplain PropertyError#NEGATIVE_INDEX} if index + * is negative + */ + public void setValue(int index, float value) { + if (index < 0) { + throw new ContractException(PropertyError.NEGATIVE_INDEX); + } + if (index >= values.length) { + grow(index + 1); + } + + values[index] = value; + } + + private String getElementsString() { + Iterator iterator = indexIteratorSupplier.get(); + StringBuilder sb = new StringBuilder(); + + boolean first = true; + sb.append('['); + + while (iterator.hasNext()) { + + if (first) { + first = false; + } else { + sb.append(", "); + } + Integer index = iterator.next(); + float value = getValue(index); + + sb.append(index); + sb.append("="); + sb.append(value); + } + sb.append(']'); + return sb.toString(); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("FloatValueContainer [values="); + builder.append(getElementsString()); + builder.append(", defaultValue="); + builder.append(defaultValue); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/IntValueContainer.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/IntValueContainer.java new file mode 100644 index 000000000..cf7c3e319 --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/IntValueContainer.java @@ -0,0 +1,922 @@ +package gov.hhs.aspr.ms.gcm.plugins.util.properties.arraycontainers; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.function.Supplier; + +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.errors.ContractException; + +/** + * A container that maps non-negative int index values to bytes, shorts, ints or + * longs. Values are stored internally as whatever int-type that logically + * represents the highest value in this container. For example, if this + * container contains the values [1,0,14,-20,300] then these would be stored in + * a short array since 300 exceeds the highest value of byte. + */ +public final class IntValueContainer { + + /** + * An enumeration representing the four int-based primitive data types in Java. + */ + public static enum IntValueType { + + BYTE(Byte.MIN_VALUE, Byte.MAX_VALUE), // + SHORT(Short.MIN_VALUE, Short.MAX_VALUE), // + INT(Integer.MIN_VALUE, Integer.MAX_VALUE), // + LONG(Long.MIN_VALUE, Long.MAX_VALUE);// + + private final long min; + + private final long max; + + /* + * Constructs the IntValueType with its min and max values + */ + private IntValueType(long min, long max) { + this.min = min; + this.max = max; + } + + /* + * Returns true if and only if this IntValueType represents an array whose min + * and max values inclusively bound the given value. + */ + private boolean isCompatibleValue(long value) { + return value >= min && value <= max; + } + + } + + /* + * Rebuilds the subTypeArray to be the most compact representation that is + * value-compatible with the given long. + */ + private SubTypeArray rebuildSubTypeArray(long value) { + if (IntValueType.SHORT.isCompatibleValue(value)) { + return new ShortArray(subTypeArray); + } + if (IntValueType.INT.isCompatibleValue(value)) { + return new IntArray(subTypeArray); + } + return new LongArray(subTypeArray); + } + + /* + * Common interface for the four array wrapper classes. + */ + private static interface SubTypeArray { + public Supplier> getIteratorSupplier(); + + public IntValueType getIntValueType(); + + public long getDefaultValue(); + + public long getValue(int index); + + public void setValue(int index, long value); + + public void setCapacity(int capacity); + + public int getCapacity(); + + public String toString(); + + } + + /* + * SubTypeArray implementor for longs + */ + private static class LongArray implements SubTypeArray { + private long[] values; + private long defaultValue; + private final Supplier> indexIteratorSupplier; + + public LongArray(long defaultValue, Supplier> indexIteratorSupplier) { + values = new long[0]; + this.defaultValue = defaultValue; + this.indexIteratorSupplier = indexIteratorSupplier; + } + + public LongArray(SubTypeArray subTypeArray) { + this.defaultValue = subTypeArray.getDefaultValue(); + this.indexIteratorSupplier = subTypeArray.getIteratorSupplier(); + values = new long[subTypeArray.getCapacity()]; + for (int i = 0; i < values.length; i++) { + values[i] = (int) subTypeArray.getValue(i); + } + } + + private void grow(int capacity) { + int oldCapacity = values.length; + int newCapacity = Math.max(capacity, values.length + (values.length >> 2)); + values = Arrays.copyOf(values, newCapacity); + if (defaultValue != 0) { + for (int i = oldCapacity; i < newCapacity; i++) { + values[i] = defaultValue; + } + } + } + + @Override + public long getValue(int index) { + return values[index]; + } + + @Override + public void setValue(int index, long value) { + if (index >= values.length) { + grow(index + 1); + } + + values[index] = value; + + } + + @Override + public IntValueType getIntValueType() { + return IntValueType.LONG; + } + + @Override + public void setCapacity(int capacity) { + if (capacity > values.length) { + grow(capacity); + } + } + + @Override + public long getDefaultValue() { + return defaultValue; + } + + @Override + public int getCapacity() { + return values.length; + } + + private String getElementsString() { + Iterator iterator = indexIteratorSupplier.get(); + + StringBuilder sb = new StringBuilder(); + + boolean first = true; + sb.append('['); + int n = values.length; + while (iterator.hasNext()) { + + if (first) { + first = false; + } else { + sb.append(", "); + } + Integer index = iterator.next(); + sb.append(index); + sb.append("="); + if (index < 0 || index >= n) { + sb.append(defaultValue); + } else { + sb.append(values[index]); + } + + } + sb.append(']'); + return sb.toString(); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(getElementsString()); + builder.append(Arrays.toString(values)); + builder.append(", defaultValue="); + builder.append(defaultValue); + builder.append("]"); + return builder.toString(); + } + + @Override + public Supplier> getIteratorSupplier() { + return indexIteratorSupplier; + } + } + + /* + * SubTypeArray implementor for ints + */ + private static class IntArray implements SubTypeArray { + + private int[] values; + private int defaultValue; + private final Supplier> indexIteratorSupplier; + + public IntArray(int defaultValue, Supplier> indexIteratorSupplier) { + values = new int[0]; + this.defaultValue = defaultValue; + this.indexIteratorSupplier = indexIteratorSupplier; + } + + public IntArray(SubTypeArray subTypeArray) { + this.defaultValue = (int) subTypeArray.getDefaultValue(); + this.indexIteratorSupplier = subTypeArray.getIteratorSupplier(); + values = new int[subTypeArray.getCapacity()]; + for (int i = 0; i < values.length; i++) { + values[i] = (int) subTypeArray.getValue(i); + } + } + + private void grow(int capacity) { + int oldCapacity = values.length; + int newCapacity = Math.max(capacity, values.length + (values.length >> 2)); + values = Arrays.copyOf(values, newCapacity); + if (defaultValue != 0) { + for (int i = oldCapacity; i < newCapacity; i++) { + values[i] = defaultValue; + } + } + } + + @Override + public long getValue(int index) { + return values[index]; + } + + @Override + public void setValue(int index, long value) { + if (index >= values.length) { + grow(index + 1); + } + + values[index] = (int) value; + } + + @Override + public IntValueType getIntValueType() { + return IntValueType.INT; + } + + @Override + public void setCapacity(int capacity) { + if (capacity > values.length) { + grow(capacity); + } + } + + @Override + public long getDefaultValue() { + return defaultValue; + } + + @Override + public int getCapacity() { + return values.length; + } + + private String getElementsString() { + Iterator iterator = indexIteratorSupplier.get(); + + StringBuilder sb = new StringBuilder(); + + boolean first = true; + sb.append('['); + int n = values.length; + while (iterator.hasNext()) { + + if (first) { + first = false; + } else { + sb.append(", "); + } + Integer index = iterator.next(); + sb.append(index); + sb.append("="); + if (index < 0 || index >= n) { + sb.append(defaultValue); + } else { + sb.append(values[index]); + } + + } + sb.append(']'); + return sb.toString(); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("IntArray [values="); + builder.append(getElementsString()); + builder.append(", defaultValue="); + builder.append(defaultValue); + builder.append("]"); + return builder.toString(); + } + + @Override + public Supplier> getIteratorSupplier() { + return indexIteratorSupplier; + } + + } + + /* + * SubTypeArray implementor for shorts + */ + private static class ShortArray implements SubTypeArray { + + private short[] values; + private short defaultValue; + private final Supplier> indexIteratorSupplier; + + public ShortArray(short defaultValue, Supplier> indexIteratorSupplier) { + values = new short[0]; + this.defaultValue = defaultValue; + this.indexIteratorSupplier = indexIteratorSupplier; + } + + public ShortArray(SubTypeArray subTypeArray) { + this.defaultValue = (short) subTypeArray.getDefaultValue(); + this.indexIteratorSupplier = subTypeArray.getIteratorSupplier(); + + values = new short[subTypeArray.getCapacity()]; + for (int i = 0; i < values.length; i++) { + values[i] = (short) subTypeArray.getValue(i); + } + } + + private void grow(int capacity) { + int oldCapacity = values.length; + int newCapacity = Math.max(capacity, values.length + (values.length >> 2)); + values = Arrays.copyOf(values, newCapacity); + if (defaultValue != 0) { + for (int i = oldCapacity; i < newCapacity; i++) { + values[i] = defaultValue; + } + } + } + + @Override + public long getValue(int index) { + return values[index]; + } + + @Override + public void setValue(int index, long value) { + if (index >= values.length) { + grow(index + 1); + } + + values[index] = (short) value; + } + + @Override + public IntValueType getIntValueType() { + return IntValueType.SHORT; + } + + @Override + public void setCapacity(int capacity) { + if (capacity > values.length) { + grow(capacity); + } + } + + @Override + public long getDefaultValue() { + return defaultValue; + } + + @Override + public int getCapacity() { + return values.length; + } + + private String getElementsString() { + Iterator iterator = indexIteratorSupplier.get(); + + StringBuilder sb = new StringBuilder(); + + boolean first = true; + sb.append('['); + int n = values.length; + while (iterator.hasNext()) { + + if (first) { + first = false; + } else { + sb.append(", "); + } + + Integer index = iterator.next(); + sb.append(index); + sb.append("="); + if (index < 0 || index >= n) { + sb.append(defaultValue); + } else { + sb.append(values[index]); + } + + } + sb.append(']'); + return sb.toString(); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("ShortArray [values="); + builder.append(getElementsString()); + builder.append(", defaultValue="); + builder.append(defaultValue); + builder.append("]"); + return builder.toString(); + } + + @Override + public Supplier> getIteratorSupplier() { + return indexIteratorSupplier; + } + + } + + /* + * SubTypeArray implementor for bytes + */ + private static class ByteArray implements SubTypeArray { + + private byte[] values; + private byte defaultValue; + private final Supplier> indexIteratorSupplier; + + public ByteArray(byte defaultValue, Supplier> indexIteratorSupplier) { + values = new byte[0]; + this.defaultValue = defaultValue; + this.indexIteratorSupplier = indexIteratorSupplier; + } + + @Override + public long getValue(int index) { + return values[index]; + } + + @Override + public void setValue(int index, long value) { + if (index >= values.length) { + grow(index + 1); + } + values[index] = (byte) value; + } + + private void grow(int capacity) { + int oldCapacity = values.length; + int newCapacity = Math.max(capacity, values.length + (values.length >> 2)); + values = Arrays.copyOf(values, newCapacity); + if (defaultValue != 0) { + for (int i = oldCapacity; i < newCapacity; i++) { + values[i] = defaultValue; + } + } + } + + @Override + public IntValueType getIntValueType() { + return IntValueType.BYTE; + } + + @Override + public void setCapacity(int capacity) { + if (capacity > values.length) { + grow(capacity); + } + } + + @Override + public long getDefaultValue() { + return defaultValue; + } + + @Override + public int getCapacity() { + return values.length; + } + + private String getElementsString() { + + Iterator iterator = indexIteratorSupplier.get(); + + StringBuilder sb = new StringBuilder(); + + boolean first = true; + sb.append('['); + int n = values.length; + while (iterator.hasNext()) { + Integer index = iterator.next(); + if (first) { + first = false; + } else { + sb.append(", "); + } + sb.append(index); + sb.append("="); + if (index < 0 || index >= n) { + sb.append(defaultValue); + } else { + sb.append(values[index]); + } + } + sb.append(']'); + return sb.toString(); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("ByteArray [values="); + builder.append(getElementsString()); + builder.append(", defaultValue="); + builder.append(defaultValue); + builder.append("]"); + return builder.toString(); + } + + @Override + public Supplier> getIteratorSupplier() { + return indexIteratorSupplier; + } + + } + + /* + * The array holding instance. + */ + private SubTypeArray subTypeArray; + + /** + * Returns the default value as a byte. + * + * @throws RuntimeException if the default value is not compatible with byte + */ + public byte getDefaultValueAsByte() { + long result = subTypeArray.getDefaultValue(); + if (!IntValueType.BYTE.isCompatibleValue(result)) { + throw new RuntimeException("incompatible value found " + result); + } + return (byte) result; + } + + /** + * Returns the default value as a short. + * + * @throws RuntimeException if the default value is not compatible with short + */ + public short getDefaultValueAsShort() { + long result = subTypeArray.getDefaultValue(); + if (!IntValueType.SHORT.isCompatibleValue(result)) { + throw new RuntimeException("incompatible value found " + result); + } + return (short) result; + } + + /** + * Returns the default value as an int. + * + * @throws RuntimeException if the default value is not compatible with int + */ + public int getDefaultValueAsInt() { + long result = subTypeArray.getDefaultValue(); + if (!IntValueType.INT.isCompatibleValue(result)) { + throw new RuntimeException("incompatible value found " + result); + } + return (int) result; + } + + /** + * Returns the default value as a long. + */ + public long getDefaultValueAsLong() { + long result = subTypeArray.getDefaultValue(); + return result; + } + + /** + * Constructs the IntValueContainer with the given default value and initial + * capacity + * + * @throws NegativeArraySizeException if the capacity is negative + */ + public IntValueContainer(long defaultValue, Supplier> indexIteratorSupplier) { + + if (IntValueType.BYTE.isCompatibleValue(defaultValue)) { + subTypeArray = new ByteArray((byte) defaultValue, indexIteratorSupplier); + } else if (IntValueType.SHORT.isCompatibleValue(defaultValue)) { + subTypeArray = new ShortArray((short) defaultValue, indexIteratorSupplier); + } else if (IntValueType.INT.isCompatibleValue(defaultValue)) { + subTypeArray = new IntArray((int) defaultValue, indexIteratorSupplier); + } else { + subTypeArray = new LongArray(defaultValue, indexIteratorSupplier); + } + } + + /** + * Sets the capacity to the given capacity if the current capacity is less than + * the one given. + */ + public void setCapacity(int capacity) { + subTypeArray.setCapacity(capacity); + } + + /** + * Returns the value at index as a byte. + * + * @throws ContractException {@linkplain PropertyError#NEGATIVE_INDEX} if index + * is negative + * @throws RuntimeException + *
      + *
    • if index < 0
    • + *
    • if the value to return is not compatible with + * byte
    • + *
    + */ + public byte getValueAsByte(int index) { + if (index < 0) { + throw new ContractException(PropertyError.NEGATIVE_INDEX); + } + long result; + if (index < subTypeArray.getCapacity()) { + result = subTypeArray.getValue(index); + } else { + result = subTypeArray.getDefaultValue(); + } + if (!IntValueType.BYTE.isCompatibleValue(result)) { + throw new RuntimeException("incompatible value found " + result); + } + return (byte) result; + } + + /** + * Returns the value at index as a short. + * + * @throws ContractException {@linkplain PropertyError#NEGATIVE_INDEX} if index + * is negative + * @throws RuntimeException + *
      + *
    • if index < 0
    • + *
    • if the value to return is not compatible with + * short
    • + *
    + */ + public short getValueAsShort(int index) { + if (index < 0) { + throw new ContractException(PropertyError.NEGATIVE_INDEX); + } + long result; + if (index < subTypeArray.getCapacity()) { + result = subTypeArray.getValue(index); + } else { + result = subTypeArray.getDefaultValue(); + } + if (!IntValueType.SHORT.isCompatibleValue(result)) { + throw new RuntimeException("incompatible value found " + result); + } + return (short) result; + + } + + /** + * Returns the value at index as a long. + * + * @throws ContractException {@linkplain PropertyError#NEGATIVE_INDEX} if index + * is negative + * @throws RuntimeException + *
      + *
    • if the value to return is not compatible with + * long
    • + *
    + */ + public int getValueAsInt(int index) { + long result; + if (index < 0) { + throw new ContractException(PropertyError.NEGATIVE_INDEX); + } + if (index < subTypeArray.getCapacity()) { + result = subTypeArray.getValue(index); + } else { + result = subTypeArray.getDefaultValue(); + } + if (!IntValueType.INT.isCompatibleValue(result)) { + throw new RuntimeException("incompatible value found " + result + " at index " + index); + } + return (int) result; + } + + /** + * Returns the value at index as a long. + * + * @throws ContractException {@linkplain PropertyError#NEGATIVE_INDEX} if index + * is negative + */ + public long getValueAsLong(int index) { + if (index < 0) { + throw new ContractException(PropertyError.NEGATIVE_INDEX); + } + long result; + if (index < subTypeArray.getCapacity()) { + result = subTypeArray.getValue(index); + } else { + result = subTypeArray.getDefaultValue(); + } + + return result; + } + + /** + * Sets the value at the index to the given byte + * + * @throws ContractException {@linkplain PropertyError#NEGATIVE_INDEX} if index + * is negative + */ + public void setByteValue(int index, byte value) { + if (index < 0) { + throw new ContractException(PropertyError.NEGATIVE_INDEX); + } + subTypeArray.setValue(index, value); + } + + /** + * Sets the value at the index to the given short + * + * @throws ContractException {@linkplain PropertyError#NEGATIVE_INDEX} if index + * is negative + */ + public void setShortValue(int index, short value) { + if (index < 0) { + throw new ContractException(PropertyError.NEGATIVE_INDEX); + } + if (!subTypeArray.getIntValueType().isCompatibleValue(value)) { + subTypeArray = rebuildSubTypeArray(value); + } + + subTypeArray.setValue(index, value); + } + + /** + * Sets the value at the index to the given int + * + * @throws ContractException {@linkplain PropertyError#NEGATIVE_INDEX} if index + * is negative + */ + public void setIntValue(int index, int value) { + if (index < 0) { + throw new ContractException(PropertyError.NEGATIVE_INDEX); + } + if (!subTypeArray.getIntValueType().isCompatibleValue(value)) { + subTypeArray = rebuildSubTypeArray(value); + } + + subTypeArray.setValue(index, value); + } + + /** + * Sets the value at the index to the given long + * + * @throws ContractException {@linkplain PropertyError#NEGATIVE_INDEX} if index + * is negative + */ + public void setLongValue(int index, long value) { + if (index < 0) { + throw new ContractException(PropertyError.NEGATIVE_INDEX); + } + if (!subTypeArray.getIntValueType().isCompatibleValue(value)) { + subTypeArray = rebuildSubTypeArray(value); + } + + subTypeArray.setValue(index, value); + } + + /** + * Returns the IntValueType for this container. Each IntValueType corresponds to + * the current implementation type of the underlying array of primitives. + */ + public IntValueType getIntValueType() { + return subTypeArray.getIntValueType(); + } + + /** + * Returns the capacity of this container. Capacity is guaranteed to be greater + * than or equal to size. + */ + public int getCapacity() { + return subTypeArray.getCapacity(); + } + + /** + * Increments the value at the index by the given byte + * + * @throws ContractException {@linkplain PropertyError#NEGATIVE_INDEX} if index + * is negative + */ + public void incrementByteValue(int index, byte value) { + + long incrementedValue = Math.addExact(getValueAsLong(index), value); + setLongValue(index, incrementedValue); + } + + /** + * Increments the value at the index by the given short + * + * @throws ContractException {@linkplain PropertyError#NEGATIVE_INDEX} if index + * is negative + */ + public void incrementShortValue(int index, short value) { + + long incrementedValue = Math.addExact(getValueAsLong(index), value); + setLongValue(index, incrementedValue); + } + + /** + * Increments the value at the index by the given int + * + * @throws ContractException {@linkplain PropertyError#NEGATIVE_INDEX} if index + * is negative + */ + public void incrementIntValue(int index, int value) { + + long incrementedValue = Math.addExact(getValueAsLong(index), value); + setLongValue(index, incrementedValue); + } + + /** + * Increments the value at the index by the given long + * + * @throws ContractException {@linkplain PropertyError#NEGATIVE_INDEX} if index + * is negative + */ + public void incrementLongValue(int index, long value) { + + long incrementedValue = Math.addExact(getValueAsLong(index), value); + setLongValue(index, incrementedValue); + } + + /** + * Decrements the value at the index by the given byte + * + * @throws ContractException {@linkplain PropertyError#NEGATIVE_INDEX} if index + * is negative + */ + public void decrementByteValue(int index, byte value) { + + long decrementedValue = Math.subtractExact(getValueAsLong(index), value); + setLongValue(index, decrementedValue); + } + + /** + * Decrements the value at the index by the given short + * + * @throws ContractException {@linkplain PropertyError#NEGATIVE_INDEX} if index + * is negative + */ + public void decrementShortValue(int index, short value) { + + long decrementedValue = Math.subtractExact(getValueAsLong(index), value); + setLongValue(index, decrementedValue); + } + + /** + * Decrements the value at the index by the given int + * + * @throws ContractException {@linkplain PropertyError#NEGATIVE_INDEX} if index + * is negative + */ + public void decrementIntValue(int index, int value) { + + long decrementedValue = Math.subtractExact(getValueAsLong(index), value); + setLongValue(index, decrementedValue); + } + + /** + * Decrements the value at the index by the given long + * + * @throws ContractException {@linkplain PropertyError#NEGATIVE_INDEX} if index + * is negative + * @throws RuntimeException + *
      + *
    • if index is negative
    • + *
    • if the value causes an overflow
    • + *
    + */ + public void decrementLongValue(int index, long value) { + + long decrementedValue = Math.subtractExact(getValueAsLong(index), value); + setLongValue(index, decrementedValue); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("IntValueContainer [subTypeArray="); + builder.append(subTypeArray); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/ObjectValueContainer.java b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/ObjectValueContainer.java new file mode 100644 index 000000000..3fd36965e --- /dev/null +++ b/gcm/src/main/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/ObjectValueContainer.java @@ -0,0 +1,138 @@ +package gov.hhs.aspr.ms.gcm.plugins.util.properties.arraycontainers; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.function.Supplier; + +/** + * An array-based container for Objects that associates non-negative int indices + * with Objects and returns a default value when no value has been previously + * set for a particular index. This allows this container to return the default + * value for indices outside of the range of the internal array. + */ +public final class ObjectValueContainer { + + private Object[] elements; + + private final Object defaultValue; + + private final Supplier> indexIteratorSupplier; + + /** + * Constructs a new ObjectValueContainer with the given default value and + * initial capacity. The default value may be null. + * + * @throws IllegalArgumentException if capacity is negative + */ + public ObjectValueContainer(Object defaultValue, Supplier> indexIteratorSupplier) { + elements = new Object[0]; + this.defaultValue = defaultValue; + this.indexIteratorSupplier = indexIteratorSupplier; + } + + /** + * Sets the value at the index. + * + * @throws IllegalArgumentException if the index is negative + */ + public void setValue(int index, Object value) { + + if (index < 0) { + throw new IllegalArgumentException("negative index: " + index); + } + if (index >= elements.length) { + grow(index + 1); + } + + elements[index] = value; + } + + /** + * Returns the current capacity of this container + */ + public int getCapacity() { + return elements.length; + } + + public void setCapacity(int capacity) { + if (capacity > elements.length) { + grow(capacity); + } + } + + /* + * Grows the capacity of the elements array by at least 1/4 or to the new + * desired capacity, whichever is larger. The empty indices of the array are + * filled with the default value. + */ + private void grow(int capacity) { + int oldCapacity = elements.length; + int newCapacity = Math.max(capacity, elements.length + (elements.length >> 2)); + elements = Arrays.copyOf(elements, newCapacity); + if (defaultValue != null) { + for (int i = oldCapacity; i < newCapacity; i++) { + elements[i] = defaultValue; + } + } + } + + /** + * Returns the Object value associated with the given index. If no object value + * has been associated with the index, returns the default value. + * + * @param index + * @return + */ + @SuppressWarnings("unchecked") + public T getValue(int index) { + if (index < 0) { + throw new IllegalArgumentException("negative index: " + index); + } + if (index >= elements.length) { + return (T) defaultValue; + } + return (T) elements[index]; + } + + private String getElementsString() { + + Iterator iterator = indexIteratorSupplier.get(); + + StringBuilder sb = new StringBuilder(); + sb.append('['); + boolean first = true; + while (iterator.hasNext()) { + Integer index = iterator.next(); + Object value = getValue(index); + if (value == null) { + continue; + } + if (first) { + first = false; + } else { + sb.append(", "); + } + sb.append(index); + sb.append("="); + sb.append(value); + } + sb.append("]"); + return sb.toString(); + } + + /** + * Returns the string representation of this container. Excludes the ullage + * values beyond the highest value that was explicitly set. + */ + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("ObjectValueContainer [elements="); + builder.append(getElementsString()); + builder.append(", defaultValue="); + builder.append(defaultValue); + builder.append("]"); + return builder.toString(); + } + +} \ No newline at end of file diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_ActorContext.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_ActorContext.java new file mode 100644 index 000000000..b5bc40428 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_ActorContext.java @@ -0,0 +1,1121 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestDataManager; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestDataManagerPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestScenarioReport; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import util.annotations.UnitTag; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.wrappers.MultiKey; +import util.wrappers.MutableBoolean; + +public class AT_ActorContext { + + /* + * DataView implementor to support tests + */ + private static class TestDataManager1 extends TestDataManager { + } + + private static class TestDataManager3 extends TestDataManager { + + } + + private static class TestDataManager3A extends TestDataManager3 { + + } + + private static class TestDataManager3B extends TestDataManager3 { + + } + + private static class TestDataManager4 extends TestDataManager { + + } + + private static class TestDataManager4A extends TestDataManager4 { + + } + + private static class DataChangeEvent implements Event { + private final DatumType datumType; + private final int value; + + public DataChangeEvent(final DatumType datumType, final int value) { + super(); + this.datumType = datumType; + this.value = value; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof DataChangeEvent)) { + return false; + } + final DataChangeEvent other = (DataChangeEvent) obj; + if ((datumType != other.datumType) || (value != other.value)) { + return false; + } + return true; + } + + public DatumType getDatumType() { + return datumType; + } + + public int getValue() { + return value; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = (prime * result) + ((datumType == null) ? 0 : datumType.hashCode()); + result = (prime * result) + value; + return result; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("DataChangeEvent [datumType="); + builder.append(datumType); + builder.append(", value="); + builder.append(value); + builder.append("]"); + return builder.toString(); + } + } + + private static enum DatumType { + TYPE_1, TYPE_2 + } + + private static enum Local_Function_ID { + DATUM, VALUE; + } + + private static class BaseEvent implements Event { + + } + + private static class TestEvent implements Event { + + } + + private static enum ValueType { + HIGH, LOW + } + + /** + * Tests {@link AgentContext#agentExists(AgentId) + */ + @Test + @UnitTestMethod(target = ActorContext.class, name = "actorExists", args = { ActorId.class }) + public void testActorExists() { + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + double testTime = 1; + // there are no precondition tests + + // have the test agent show it exists and that other agents do not + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(testTime++, (context) -> { + assertTrue(context.actorExists(new ActorId(0))); + assertFalse(context.actorExists(new ActorId(1))); + assertFalse(context.actorExists(new ActorId(2))); + })); + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + TestSimulation.builder().addPlugin(testPlugin).build().execute(); + + } + + @Test + @UnitTestMethod(target = ActorContext.class, name = "addActor", args = { Consumer.class }) + public void testAddActor() { + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + MutableBoolean actorWasAdded = new MutableBoolean(); + + // there are no precondition tests + + // have the test agent show it exists and that other agents do not + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + c.addActor((c2) -> actorWasAdded.setValue(true)); + })); + + // build the plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + TestSimulation.builder().addPlugin(testPlugin).build().execute(); + + // show that the action plans got executed + assertTrue(actorWasAdded.getValue()); + } + + // + + /** + * Tests {@link AgentContext#addPlan(Consumer, double) + */ + @Test + @UnitTestMethod(target = ActorContext.class, name = "addPlan", args = { Consumer.class, double.class }) + public void testAddPlan_Consumer() { + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // test preconditions + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (context) -> { + double scheduledTime = context.getTime() + 1; + + ContractException contractException = assertThrows(ContractException.class, + () -> context.addPlan(null, scheduledTime)); + assertEquals(NucleusError.NULL_PLAN_CONSUMER, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, () -> context.addPlan((c) -> { + }, 0)); + assertEquals(NucleusError.PAST_PLANNING_TIME, contractException.getErrorType()); + + })); + + /* + * Have the actor add a plan and show that that plan executes + */ + + MutableBoolean planExecuted = new MutableBoolean(); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(4, (context) -> { + // schedule two passive plans + context.addPlan((c) -> { + planExecuted.setValue(true); + }, 5); + })); + + // build the plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // run the simulation + Simulation.builder()// + .addPlugin(testPlugin)// + .build()// + .execute();// + + // we do not need to show that all plans executed + + // show that the last two passive plans did not execute + assertTrue(planExecuted.getValue()); + } + + @Test + @UnitTestMethod(target = ActorContext.class, name = "addPlan", args = { Plan.class }) + public void testAddPlan_Plan() { + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // test preconditions + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (context) -> { + + ContractException contractException = assertThrows(ContractException.class, () -> context.addPlan(null)); + assertEquals(NucleusError.NULL_PLAN, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, + () -> context.addPlan(Plan.builder(ActorContext.class)// + .setActive(true)// + .setCallbackConsumer(null)// + .setKey(null)// + .setPlanData(null)// + .setTime(2)// + .build())); + assertEquals(NucleusError.NULL_PLAN_CONSUMER, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, + () -> context.addPlan(Plan.builder(ActorContext.class)// + .setActive(true)// + .setCallbackConsumer((c) -> { + })// + .setKey(null)// + .setPlanData(null)// + .setTime(0)// + .build())); + assertEquals(NucleusError.PAST_PLANNING_TIME, contractException.getErrorType()); + })); + + /* + * Have the actor add a plan and show that that plan executes + */ + + MutableBoolean planExecuted = new MutableBoolean(); + + Plan plan = Plan.builder(ActorContext.class)// + .setActive(true)// + .setCallbackConsumer((c) -> { + planExecuted.setValue(true); + })// + .setKey(null)// + .setPlanData(null)// + .setTime(5)// + .build();// + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(4, (context) -> { + // schedule two passive plans + context.addPlan(plan); + })); + + // build the plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // run the simulation + Simulation.builder()// + .addPlugin(testPlugin)// + .build()// + .execute();// + + // we do not need to show that all plans executed + + // show that the last two passive plans did not execute + assertTrue(planExecuted.getValue()); + } + + @Test + @UnitTestMethod(target = ActorContext.class, name = "getActorId", args = {}) + public void testGetActorId() { + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + double testTime = 1; + // there are no precondition tests + + Set observedActorIds = new LinkedHashSet<>(); + + /* + * Have actors get their own actor ids and show that these ids match the + * expected values established duing the initialization of the TestActors. + */ + pluginDataBuilder.addTestActorPlan("Alpha", new TestActorPlan(testTime++, (c) -> { + ActorId actorId = c.getActorId(); + observedActorIds.add(actorId); + assertNotNull(actorId); + + })); + + pluginDataBuilder.addTestActorPlan("Beta", new TestActorPlan(testTime++, (c) -> { + ActorId actorId = c.getActorId(); + observedActorIds.add(actorId); + assertNotNull(actorId); + + })); + + pluginDataBuilder.addTestActorPlan("Gamma", new TestActorPlan(testTime++, (c) -> { + ActorId actorId = c.getActorId(); + observedActorIds.add(actorId); + assertNotNull(actorId); + + })); + + // build the plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + TestSimulation.builder().addPlugin(testPlugin).build().execute(); + + // show that the number of actor ids matches the number of actor aliases + assertEquals(3, observedActorIds.size()); + } + + @Test + @UnitTestMethod(target = ActorContext.class, name = "getDataManager", args = { Class.class }) + public void testGetDataManager() { + + // create the test plugin data builder + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // create a data manager for the actor to find + + pluginDataBuilder.addTestDataManager("dm1", () -> new TestDataManager1()); + pluginDataBuilder.addTestDataManager("dm3A", () -> new TestDataManager3A()); + pluginDataBuilder.addTestDataManager("dm3B", () -> new TestDataManager3B()); + pluginDataBuilder.addTestDataManager("dm4A", () -> new TestDataManager4A()); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + c.getDataManager(TestDataManager1.class); + c.getDataManager(TestDataManager3A.class); + c.getDataManager(TestDataManager3B.class); + c.getDataManager(TestDataManager4A.class); + })); + + // build the action plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + TestSimulation.builder().addPlugin(testPlugin).build().execute(); + + // Precondition test 1 + pluginDataBuilder.addTestDataManager("dm3A", () -> new TestDataManager3A()); + pluginDataBuilder.addTestDataManager("dm3B", () -> new TestDataManager3B()); + + // show that ambiguous class matching throws an exception + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + ContractException contractException = assertThrows(ContractException.class, + () -> c.getDataManager(TestDataManager3.class)); + assertEquals(NucleusError.AMBIGUOUS_DATA_MANAGER_CLASS, contractException.getErrorType()); + })); + + // build the action plugin + testPluginData = pluginDataBuilder.build(); + testPlugin = TestPlugin.getTestPlugin(testPluginData); + + TestSimulation.builder().addPlugin(testPlugin).build().execute(); + + // Precondition test 2 + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + ContractException contractException = assertThrows(ContractException.class, () -> c.getDataManager(null)); + assertEquals(NucleusError.NULL_DATA_MANAGER_CLASS, contractException.getErrorType()); + })); + + // build the action plugin + testPluginData = pluginDataBuilder.build(); + testPlugin = TestPlugin.getTestPlugin(testPluginData); + + TestSimulation.builder().addPlugin(testPlugin).build().execute(); + + } + + /** + * Tests {@link AgentContext#getPlan(Object) + */ + @Test + @UnitTestMethod(target = ActorContext.class, name = "getPlan", args = { Object.class }, tags = { + UnitTag.INCOMPLETE }) + public void testGetPlan() { + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // test preconditions + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (context) -> { + ContractException contractException = assertThrows(ContractException.class, () -> context.getPlan(null)); + assertEquals(NucleusError.NULL_PLAN_KEY, contractException.getErrorType()); + })); + + /* + * have the added test agent add a plan that can be retrieved and thus was added + * successfully + */ + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(2, (context) -> { + Object key = new Object(); + assertFalse(context.getPlan(key).isPresent()); + Plan plan = Plan.builder(ActorContext.class)// + .setKey(key)// + .setTime(100).setCallbackConsumer((c) -> { + })// + .build();// + context.addPlan(plan); + assertTrue(context.getPlan(key).isPresent()); + })); + + // build the plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // run the simulation + TestSimulation.builder().addPlugin(testPlugin).build().execute(); + + } + + /** + * Tests {@link AgentContext#getPlanKeys() + */ + @Test + @UnitTestMethod(target = ActorContext.class, name = "getPlanKeys", args = {}) + public void testGetPlanKeys() { + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // There are no precondition tests + Set expectedKeys = new LinkedHashSet<>(); + int keyCount = 20; + for (int i = 0; i < keyCount; i++) { + expectedKeys.add(new Object()); + } + + // have the test agent add some plans + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (context) -> { + for (Object key : expectedKeys) { + Plan plan = Plan.builder(ActorContext.class)// + .setKey(key)// + .setTime(100)// + .setCallbackConsumer((c) -> { + })// + .build();// + + context.addPlan(plan); + } + + Set actualKeys = context.getPlanKeys().stream() + .collect(Collectors.toCollection(LinkedHashSet::new)); + assertEquals(expectedKeys, actualKeys); + + })); + + // build the plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // run the simulation + TestSimulation.builder().addPlugin(testPlugin).build().execute(); + } + + @Test + @UnitTestMethod(target = ActorContext.class, name = "getScheduledSimulationHaltTime", args = {}) + public void testGetScheduledSimulationHaltTime() { + Set stopTimes = new LinkedHashSet<>(); + + stopTimes.add(4.6); + stopTimes.add(13.0); + stopTimes.add(554.3); + stopTimes.add(7.9); + stopTimes.add(400.2); + stopTimes.add(3000.1); + + for (Double stopTime : stopTimes) { + TestPluginData testPluginData = TestPluginData.builder() + .addTestActorPlan("actor 1", new TestActorPlan(0, (context) -> { + assertEquals(stopTime, context.getScheduledSimulationHaltTime()); + })).build(); + + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + TestSimulation.builder().setSimulationHaltTime(stopTime).addPlugin(testPlugin).build().execute(); + } + } + + @Test + @UnitTestMethod(target = ActorContext.class, name = "getTime", args = {}) + public void testGetTime() { + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + Set planTimes = new LinkedHashSet<>(); + + planTimes.add(4.6); + planTimes.add(13.8764); + planTimes.add(554.345); + planTimes.add(7.95346); + planTimes.add(400.234234); + planTimes.add(3000.12422346); + + /* + * Have the agent build plans to check the time in the simulation against the + * planning time + */ + pluginDataBuilder.addTestActorPlan("Actor 1", new TestActorPlan(0, (context1) -> { + for (Double planTime : planTimes) { + context1.addPlan((context2) -> { + assertEquals(planTime.doubleValue(), context2.getTime(), 0); + }, planTime); + } + })); + + // build the action plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // execute the engine + TestSimulation.builder().addPlugin(testPlugin).build().execute(); + } + + /** + * Tests {@link AgentContext#halt() + */ + @Test + @UnitTestMethod(target = ActorContext.class, name = "halt", args = {}) + public void testHalt() { + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + Set expectedValues = new LinkedHashSet<>(); + expectedValues.add(1); + expectedValues.add(2); + expectedValues.add(3); + + Set actualValues = new LinkedHashSet<>(); + + // have the test agent execute several tasks, with one of the tasks + // halting the simulation + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (context) -> { + actualValues.add(1); + })); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(2, (context) -> { + actualValues.add(2); + })); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(3, (context) -> { + actualValues.add(3); + context.halt(); + })); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(4, (context) -> { + actualValues.add(4); + })); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(5, (context) -> { + actualValues.add(5); + })); + + // build the plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // run the simulation + Simulation.builder()// + .addPlugin(testPlugin)// + .build()// + .execute();// + + // show that the plans that were scheduled after the halt did not + // execute + assertEquals(expectedValues, actualValues); + + } + + @Test + @UnitTestMethod(target = ActorContext.class, name = "releaseOutput", args = { Object.class }) + public void testReleaseOutput() { + + // begin building the action plugin + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // set up the expected output + Set expectedOutput = new LinkedHashSet<>(); + expectedOutput.add("the sly fox"); + expectedOutput.add(15); + expectedOutput.add("the lazy, brown dog"); + expectedOutput.add(45.34513453); + + // have the agent release the output + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + for (Object outputValue : expectedOutput) { + c.releaseOutput(outputValue); + } + })); + + // build the plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + Set actualOutput = new LinkedHashSet<>(); + + /* + * Add an output consumer that will place the output into the actualOutput set + * above and then execute the simulation + */ + Simulation.builder()// + .addPlugin(testPlugin)// + .setOutputConsumer((o) -> { + if (!(o instanceof TestScenarioReport)) { + actualOutput.add(o); + } + })// + .build()// + .execute();// + + // show that the output matches expectations + assertEquals(expectedOutput, actualOutput); + + } + + @Test + @UnitTestMethod(target = ActorContext.class, name = "removeActor", args = { ActorId.class }) + public void testRemoveActor() { + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // have the resolver execute the precondition tests + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + + ContractException contractException = assertThrows(ContractException.class, () -> c.removeActor(null)); + assertEquals(NucleusError.NULL_ACTOR_ID, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, () -> c.removeActor(new ActorId(1000))); + assertEquals(NucleusError.UNKNOWN_ACTOR_ID, contractException.getErrorType()); + + })); + + List addedActorIds = new ArrayList<>(); + + // have the add a few agents + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + for (int i = 0; i < 10; i++) { + ActorId actorId = c.addActor((c2) -> { + }); + assertTrue(c.actorExists(actorId)); + addedActorIds.add(actorId); + } + })); + + // have the actor remove the added actors + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + for (ActorId actorId : addedActorIds) { + c.removeActor(actorId); + assertFalse(c.actorExists(actorId)); + } + })); + + // build the plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // build and execute the engine + TestSimulation.builder().addPlugin(testPlugin).build().execute(); + + } + + /** + * Tests {@link AgentContext#removePlan(Object) + */ + @Test + @UnitTestMethod(target = ActorContext.class, name = "removePlan", args = { Object.class }, tags = { + UnitTag.INCOMPLETE }) + public void testRemovePlan() { + + /* + * The test does not show that the plan is returned through the remove + * invocation + */ + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // test preconditions + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (context) -> { + ContractException contractException = assertThrows(ContractException.class, () -> context.removePlan(null)); + assertEquals(NucleusError.NULL_PLAN_KEY, contractException.getErrorType()); + })); + + Object key = new Object(); + MutableBoolean removedPlanHasExecuted = new MutableBoolean(); + + // have the added test agent add a plan + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(2, (context) -> { + + Plan plan = Plan.builder(ActorContext.class)// + .setKey(key)// + .setTime(4)// + .setCallbackConsumer((c) -> { + removedPlanHasExecuted.setValue(true); + })// + .build();// + + context.addPlan(plan); + })); + + // have the test agent remove the plan and show the plan no longer + // exists + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(3, (context) -> { + assertTrue(context.getPlan(key).isPresent()); + + context.removePlan(key); + + assertFalse(context.getPlan(key).isPresent()); + + })); + + // build the plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // run the simulation + TestSimulation.builder().addPlugin(testPlugin).build().execute(); + + // show that the remove plan was not executed + assertFalse(removedPlanHasExecuted.getValue()); + } + + @Test + @UnitTestMethod(target = ActorContext.class, name = "stateRecordingIsScheduled", args = {}) + public void testStateRecordingIsScheduled() { + Set stateRecordingList = new LinkedHashSet<>(); + + stateRecordingList.add(false); + stateRecordingList.add(false); + stateRecordingList.add(true); + stateRecordingList.add(false); + stateRecordingList.add(true); + stateRecordingList.add(true); + + for (Boolean stateRecording : stateRecordingList) { + TestPluginData testPluginData = TestPluginData.builder() + .addTestActorPlan("actor 1", new TestActorPlan(0, (context) -> { + assertEquals(stateRecording, context.stateRecordingIsScheduled()); + })).build(); + + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + TestSimulation.builder().setSimulationHaltTime(1).setProduceSimulationStateOnHalt(stateRecording) + .addPlugin(testPlugin).build().execute(); + } + } + + @Test + @UnitTestMethod(target = ActorContext.class, name = "subscribeToSimulationClose", args = { Consumer.class }) + public void testSubscribeToSimulationClose() { + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + MutableBoolean simCloseEventHandled = new MutableBoolean(); + + // have an actor schedule a few events and subscribe to simulation close + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + c.addPlan((c2) -> { + }, 1); + c.addPlan((c2) -> { + }, 2); + c.addPlan((c2) -> { + }, 3); + c.subscribeToSimulationClose((c2) -> { + simCloseEventHandled.setValue(true); + }); + })); + + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + Simulation.builder()// + .addPlugin(testPlugin)// + .build()// + .execute();// + + // show that the subscription to simulation close was successful + assertTrue(simCloseEventHandled.getValue()); + + } + + @Test + @UnitTestMethod(target = ActorContext.class, name = "subscribe", args = { EventFilter.class, BiConsumer.class }) + public void testSubscribe() { + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // have an actor perform precondition tests + pluginDataBuilder.addTestActorPlan("precondition checker", new TestActorPlan(0, (context) -> { + EventFilter eventFilter = EventFilter.builder(TestEvent.class).build(); + + // if the event filter is null + EventFilter nullEventFilter = null; + ContractException contractException = assertThrows(ContractException.class, + () -> context.subscribe(nullEventFilter, (c, e) -> { + })); + assertEquals(NucleusError.NULL_EVENT_FILTER, contractException.getErrorType()); + + // if the event consumer is null + contractException = assertThrows(ContractException.class, () -> context.subscribe(eventFilter, null)); + assertEquals(NucleusError.NULL_EVENT_CONSUMER, contractException.getErrorType()); + + })); + + Set receivedEvents = new LinkedHashSet<>(); + + /* + * Have an actor add an event filter for DataChangeObservation events. Then have + * it subscribe to data change events that are of type 1 and high value. When it + * receives a data change observation, it records it as a multi-key in the + * received events set. + */ + + pluginDataBuilder.addTestActorPlan("subscriber", new TestActorPlan(1, (context) -> { + + EventFilter eventFilter = // + EventFilter.builder(DataChangeEvent.class)// + .addFunctionValuePair(new IdentifiableFunction(Local_Function_ID.DATUM, + (e) -> e.getDatumType()), DatumType.TYPE_1)// + .addFunctionValuePair( + new IdentifiableFunction(Local_Function_ID.VALUE, (e) -> { + if (e.getValue() > 10) { + return ValueType.HIGH; + } else { + return ValueType.LOW; + } + }), ValueType.HIGH)// + .build();// + context.subscribe(eventFilter, (c, e) -> { + receivedEvents.add(new MultiKey(c.getTime(), e)); + }); + })); + + /* + * Have a data manager generate several data change observation events with + * differing types and values. + */ + pluginDataBuilder.addTestDataManager("generator", () -> new TestDataManager()); + pluginDataBuilder.addTestDataManagerPlan("generator", new TestDataManagerPlan(2, (c) -> { + c.releaseObservationEvent(new DataChangeEvent(DatumType.TYPE_1, 0)); + c.releaseObservationEvent(new DataChangeEvent(DatumType.TYPE_2, 5)); + c.releaseObservationEvent(new DataChangeEvent(DatumType.TYPE_1, 20)); + c.releaseObservationEvent(new DataChangeEvent(DatumType.TYPE_2, 0)); + c.releaseObservationEvent(new DataChangeEvent(DatumType.TYPE_1, 5)); + c.releaseObservationEvent(new DataChangeEvent(DatumType.TYPE_2, 25)); + c.releaseObservationEvent(new DataChangeEvent(DatumType.TYPE_1, 38)); + c.releaseObservationEvent(new DataChangeEvent(DatumType.TYPE_2, 234)); + })); + + // build the plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // run the simulation + TestSimulation.builder().addPlugin(testPlugin).build().execute(); + + // show that all and only the observations corresponding to the + // subscribed event label were delivered to the subscriber actor + Set expectedEvents = new LinkedHashSet<>(); + expectedEvents.add(new MultiKey(2.0, new DataChangeEvent(DatumType.TYPE_1, 20))); + expectedEvents.add(new MultiKey(2.0, new DataChangeEvent(DatumType.TYPE_1, 38))); + + assertEquals(expectedEvents, receivedEvents); + } + + @Test + @UnitTestMethod(target = ActorContext.class, name = "unsubscribe", args = { EventFilter.class }) + public void testUnsubscribe() { + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + /* + * Generate an event label that will match all TestEvents. This will be used + * throughout. + */ + EventFilter eventFilter = EventFilter.builder(BaseEvent.class).build();// + + // create some times for the resolver to generate events + List eventGenerationTimes = new ArrayList<>(); + eventGenerationTimes.add(1.0); + eventGenerationTimes.add(2.0); + eventGenerationTimes.add(3.0); + eventGenerationTimes.add(4.0); + eventGenerationTimes.add(5.0); + eventGenerationTimes.add(6.0); + eventGenerationTimes.add(7.0); + eventGenerationTimes.add(8.0); + eventGenerationTimes.add(9.0); + + /* + * At time 0, have the test data manager generate plans to generate events at + * various times + */ + pluginDataBuilder.addTestDataManager("generator", () -> new TestDataManager1()); + pluginDataBuilder.addTestDataManagerPlan("generator", new TestDataManagerPlan(0, (c) -> { + + for (Double time : eventGenerationTimes) { + c.addPlan((c2) -> { + c2.releaseObservationEvent(new BaseEvent()); + }, time); + } + })); + + /* + * precondition tests -- have the first actor test all the precondition + * exceptions + */ + pluginDataBuilder.addTestActorPlan("precondition tester", new TestActorPlan(0, (context) -> { + + // if the event filter is null + EventFilter nullEventFilter = null; + ContractException contractException = assertThrows(ContractException.class, + () -> context.unsubscribe(nullEventFilter)); + assertEquals(NucleusError.NULL_EVENT_FILTER, contractException.getErrorType()); + + })); + + // create a container for the events that are received by the three + // actors + Set recievedEvents = new LinkedHashSet<>(); + + // have the Alpha actor subscribe to the Test Event at time 0 + pluginDataBuilder.addTestActorPlan("Alpha", new TestActorPlan(0.1, (context) -> { + context.subscribe(eventFilter, (c, e) -> { + recievedEvents.add(new MultiKey("Alpha", c.getTime())); + }); + })); + + // have the Alpha actor unsubscribe to the Test Event at time 5 + pluginDataBuilder.addTestActorPlan("Alpha", new TestActorPlan(5.1, (context) -> { + context.unsubscribe(eventFilter); + })); + + // have the Beta actor subscribe to the Test Event at time 4 + pluginDataBuilder.addTestActorPlan("Beta", new TestActorPlan(4.1, (context) -> { + context.subscribe(eventFilter, (c, e) -> { + recievedEvents.add(new MultiKey("Beta", c.getTime())); + }); + })); + + // have the Beta actor unsubscribe to the Test Event at time 8 + pluginDataBuilder.addTestActorPlan("Beta", new TestActorPlan(8.1, (context) -> { + context.unsubscribe(eventFilter); + })); + + // have the Gamma actor subscribe to the Test Event at time 6 + pluginDataBuilder.addTestActorPlan("Gamma", new TestActorPlan(6.1, (context) -> { + context.subscribe(eventFilter, (c, e) -> { + recievedEvents.add(new MultiKey("Gamma", c.getTime())); + }); + })); + + // build the plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // run the simulation + TestSimulation.builder().addPlugin(testPlugin).build().execute(); + + // show that all and only the observations corresponding to the + // subscribed event label were delivered to the actors + Set expectedEvents = new LinkedHashSet<>(); + expectedEvents.add(new MultiKey("Alpha", 1.0)); + expectedEvents.add(new MultiKey("Alpha", 2.0)); + expectedEvents.add(new MultiKey("Alpha", 3.0)); + expectedEvents.add(new MultiKey("Alpha", 4.0)); + expectedEvents.add(new MultiKey("Alpha", 5.0)); + expectedEvents.add(new MultiKey("Beta", 5.0)); + expectedEvents.add(new MultiKey("Beta", 6.0)); + expectedEvents.add(new MultiKey("Beta", 7.0)); + expectedEvents.add(new MultiKey("Beta", 8.0)); + expectedEvents.add(new MultiKey("Gamma", 7.0)); + expectedEvents.add(new MultiKey("Gamma", 8.0)); + expectedEvents.add(new MultiKey("Gamma", 9.0)); + + // show that the expected and actual event records are the same + assertEquals(expectedEvents, recievedEvents); + + } + + @Test + @UnitTestMethod(target = ActorContext.class, name = "setPlanDataConverter", args = { Class.class, Function.class }) + public void testSetPlanDataConverter() { + MutableBoolean called = new MutableBoolean(false); + + class TestPlanData1 implements PlanData { + + } + + Function> planDataConverter = t -> { + return context -> called.setValue(true); + }; + + class TestActor1 { + public void init(ActorContext actorContext) { + actorContext.setPlanDataConverter(TestPlanData1.class, planDataConverter); + } + } + + Plugin actorPlugin = Plugin.builder().setPluginId(new SimplePluginId("TestActor1")) + .setInitializer((pContext) -> { + pContext.addActor(new TestActor1()::init); + }).build(); + + TestPluginData testPluginData = TestPluginData.builder() + .addTestActorPlan("test actor 2", new TestActorPlan(2, (context) -> { + + })).build(); + + PlanQueueData planQueueData = PlanQueueData.builder().setPlanData(new TestPlanData1()).setTime(1) + .setPlanner(Planner.ACTOR).build(); + + SimulationState simulationState = SimulationState.builder().addPlanQueueData(planQueueData).setStartTime(1) + .setPlanningQueueArrivalId(2).build(); + + TestSimulation.builder().addPlugin(actorPlugin).addPlugin(TestPlugin.getTestPlugin(testPluginData)) + .setSimulationState(simulationState).build().execute(); + + assertTrue(called.getValue()); + } + + @Test + @UnitTestMethod(target = ActorContext.class, name = "getBaseDate", args = {}) + public void testGetBaseDate() { + + // create some base dates to test + List localDates = new ArrayList<>(); + + localDates.add(LocalDate.of(2023, 1, 10)); + localDates.add(LocalDate.of(2024, 6, 13)); + localDates.add(LocalDate.of(2020, 3, 15)); + localDates.add(LocalDate.of(2023, 12, 25)); + + // loop over the base dates + IntStream.range(0, localDates.size()).forEach((i) -> { + LocalDate localDate = localDates.get(i); + // build a single actor that will show that the base date returned by the + // context is correct + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + assertEquals(localDate, c.getBaseDate()); + })); + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // execute the engine + SimulationState simulationState = SimulationState.builder().setBaseDate(localDate).build(); + TestSimulation.builder().setSimulationState(simulationState).addPlugin(testPlugin).build().execute(); + }); + + } + + @Test + @UnitTestMethod(target = ActorContext.class, name = "getStartTime", args = {}) + public void testGetStartTime() { + + // create some start times to test + List startTimes = new ArrayList<>(); + + startTimes.add(-100.0); + startTimes.add(30.23); + startTimes.add(17.63); + startTimes.add(45.5); + + // loop over the base dates + IntStream.range(0, startTimes.size()).forEach((i) -> { + Double startTime = startTimes.get(i); + // build a single actor that will show that the start time returned by the + // context is correct + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(startTime+10, (c) -> { + assertEquals(startTime, c.getStartTime()); + })); + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // execute the engine + SimulationState simulationState = SimulationState.builder().setStartTime(startTime).build(); + TestSimulation.builder().setSimulationState(simulationState).addPlugin(testPlugin).build().execute(); + }); + + } +} \ No newline at end of file diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_ActorId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_ActorId.java new file mode 100644 index 000000000..80ea6ab6e --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_ActorId.java @@ -0,0 +1,78 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public final class AT_ActorId { + + @UnitTestConstructor(target = ActorId.class, args = { int.class }) + @Test + public void testConstructor() { + for (int i = 0; i < 100; i++) { + assertEquals(i, new ActorId(i).getValue()); + } + } + + @UnitTestMethod(target = ActorId.class, name = "getValue", args = {}) + @Test + public void testGetValue() { + for (int i = 0; i < 100; i++) { + assertEquals(i, new ActorId(i).getValue()); + } + } + + @UnitTestMethod(target = ActorId.class, name = "toString", args = {}) + @Test + public void testToString() { + for (int i = 0; i < 100; i++) { + assertEquals("ActorId [id=" + i + "]", new ActorId(i).toString()); + } + } + + @UnitTestMethod(target = ActorId.class, name = "hashCode", args = {}) + @Test + public void testHashCode() { + // show equal objects have equal hashcodes + for (int i = 0; i < 10; i++) { + ActorId a = new ActorId(i); + ActorId b = new ActorId(i); + assertEquals(a, b); + assertEquals(a.hashCode(), b.hashCode()); + } + + // show that hash codes are dispersed + Set hashcodes = new LinkedHashSet<>(); + for (int i = 0; i < 1000; i++) { + hashcodes.add(new ActorId(i).hashCode()); + } + assertEquals(1000, hashcodes.size()); + + } + + @UnitTestMethod(target = ActorId.class, name = "equals", args = { Object.class }) + @Test + public void testEquals() { + // show actor ids are equal if and only if they have the same base int + // value + for (int i = 0; i < 10; i++) { + ActorId a = new ActorId(i); + for (int j = 0; j < 10; j++) { + ActorId b = new ActorId(j); + if (i == j) { + assertEquals(a, b); + } else { + assertNotEquals(a, b); + } + } + } + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_DataManager.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_DataManager.java new file mode 100644 index 000000000..bfec5e0dd --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_DataManager.java @@ -0,0 +1,36 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + + +public class AT_DataManager { + + + + @Test + @UnitTestMethod(target = DataManager.class ,name = "init", args = {DataManagerContext.class}) + public void testInit() { + DataManager dataManager = new DataManager(); + assertFalse(dataManager.isInitialized()); + dataManager.init(null); + assertTrue(dataManager.isInitialized()); + } + + @UnitTestConstructor(target = DataManager.class ,args = {}) + @Test + public void testConstructor() { + //nothing to test + } + + @Test + @UnitTestMethod(target = DataManager.class ,name = "toString", args = {}) + public void testToString() { + //nothing to test + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_DataManagerContext.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_DataManagerContext.java new file mode 100644 index 000000000..8bef2216b --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_DataManagerContext.java @@ -0,0 +1,1239 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.apache.commons.math3.util.Pair; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestDataManager; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestDataManagerPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestOutputConsumer; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestScenarioReport; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import util.annotations.UnitTag; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.wrappers.MutableBoolean; +import util.wrappers.MutableInteger; + +public class AT_DataManagerContext { + + @Test + @UnitTestMethod(target = DataManagerContext.class, name = "getTime", args = {}) + public void testGetTime() { + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + Set planTimes = new LinkedHashSet<>(); + + planTimes.add(4.6); + planTimes.add(13.8764); + planTimes.add(554.345); + planTimes.add(7.95346); + planTimes.add(400.234234); + planTimes.add(3000.12422346); + + /* + * Have the data manager build plans to check the time in the simulation + * against the planning time + */ + pluginDataBuilder.addTestDataManager("dm", () -> new TestDataManager1()); + pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(0, (context1) -> { + for (Double planTime : planTimes) { + context1.addPlan((context2) -> { + assertEquals(planTime.doubleValue(), context2.getTime(), 0); + }, planTime); + } + })); + + // build the action plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // execute the engine + TestSimulation.builder().addPlugin(testPlugin).build().execute(); + + } + + @Test + @UnitTestMethod(target = DataManagerContext.class, name = "releaseOutput", args = { Object.class }) + public void testReleaseOutput() { + + // begin building the action plugin + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // set up the expected output + Set expectedOutput = new LinkedHashSet<>(); + expectedOutput.add("the sly fox"); + expectedOutput.add(15); + expectedOutput.add("the lazy, brown dog"); + expectedOutput.add(45.34513453); + + // have the data manager release the output + pluginDataBuilder.addTestDataManager("dm", () -> new TestDataManager1()); + pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(1, (c) -> { + for (Object outputValue : expectedOutput) { + c.releaseOutput(outputValue); + } + })); + + // build the plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + Set actualOutput = new LinkedHashSet<>(); + + /* + * Add an output consumer that will place the output into the + * actualOutput set above and then execute the simulation + */ + Simulation.builder()// + .addPlugin(testPlugin)// + .setOutputConsumer((o) -> { + if (!(o instanceof TestScenarioReport)) { + actualOutput.add(o); + } + })// + .build()// + .execute();// + + // show that the output matches expectations + assertEquals(expectedOutput, actualOutput); + + } + + @Test + @UnitTestMethod(target = DataManagerContext.class, name = "subscribeToSimulationClose", args = { Consumer.class }) + public void testSubscribeToSimulationClose() { + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + MutableBoolean simCloseEventHandled = new MutableBoolean(); + + // have a data manager schedule a few events and subscribe to simulation + // close + pluginDataBuilder.addTestDataManager("dm", () -> new TestDataManager1()); + pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(0, (c) -> { + c.addPlan((c2) -> { + }, 1); + c.addPlan((c2) -> { + }, 2); + c.addPlan((c2) -> { + }, 3); + c.subscribeToSimulationClose((c2) -> { + simCloseEventHandled.setValue(true); + }); + })); + + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + Simulation.builder()// + .addPlugin(testPlugin)// + .build()// + .execute();// + + // show that the subscription to simulation close was successful + assertTrue(simCloseEventHandled.getValue()); + + } + + @Test + @UnitTestMethod(target = DataManagerContext.class, name = "getDataManager", args = { Class.class }) + public void testGetDataManager() { + + // create the test plugin data builder + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // create a data manager for the actor to find + + pluginDataBuilder.addTestDataManager("dm1", () -> new TestDataManager1()); + pluginDataBuilder.addTestDataManager("dm3A", () -> new TestDataManager3A()); + pluginDataBuilder.addTestDataManager("dm3B", () -> new TestDataManager3B()); + pluginDataBuilder.addTestDataManager("dm4A", () -> new TestDataManager4A()); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + TestDataManager1 testDataManager1 = c.getDataManager(TestDataManager1.class); + assertNotNull(testDataManager1); + + TestDataManager3A testDataManager3A = c.getDataManager(TestDataManager3A.class); + assertNotNull(testDataManager3A); + + TestDataManager3B testDataManager3B = c.getDataManager(TestDataManager3B.class); + assertNotNull(testDataManager3B); + + TestDataManager4A testDataManager4A = c.getDataManager(TestDataManager4A.class); + assertNotNull(testDataManager4A); + + })); + + // build the action plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // execute the engine + TestSimulation.builder().addPlugin(testPlugin).build().execute(); + + // Precondition test 1 + + pluginDataBuilder.addTestDataManager("dm3A", () -> new TestDataManager3A()); + pluginDataBuilder.addTestDataManager("dm3B", () -> new TestDataManager3B()); + + pluginDataBuilder.addTestDataManagerPlan("dm3A", new TestDataManagerPlan(4, (c) -> { + ContractException contractException = assertThrows(ContractException.class, + () -> c.getDataManager(TestDataManager3.class)); + assertEquals(NucleusError.AMBIGUOUS_DATA_MANAGER_CLASS, contractException.getErrorType()); + })); + + // build the action plugin + testPluginData = pluginDataBuilder.build(); + testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // execute the engine + TestSimulation.builder().addPlugin(testPlugin).build().execute(); + + // Precondition test 2 + + pluginDataBuilder.addTestDataManager("dm3B", () -> new TestDataManager3B()); + + pluginDataBuilder.addTestDataManagerPlan("dm3B", new TestDataManagerPlan(4, (c) -> { + ContractException contractException = assertThrows(ContractException.class, () -> c.getDataManager(null)); + assertEquals(NucleusError.NULL_DATA_MANAGER_CLASS, contractException.getErrorType()); + })); + + // build the action plugin + testPluginData = pluginDataBuilder.build(); + testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // execute the engine + TestSimulation.builder().addPlugin(testPlugin).build().execute(); + + } + + @Test + @UnitTestMethod(target = DataManagerContext.class, name = "addPlan", args = { Consumer.class, double.class }) + public void testAddPlan_Consumer() { + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // test preconditions + pluginDataBuilder.addTestDataManager("dm", () -> new TestDataManager1()); + pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(1, (context) -> { + double scheduledTime = context.getTime() + 1; + + ContractException contractException = assertThrows(ContractException.class, + () -> context.addPlan(null, scheduledTime)); + assertEquals(NucleusError.NULL_PLAN_CONSUMER, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, () -> context.addPlan((c) -> { + }, 0)); + assertEquals(NucleusError.PAST_PLANNING_TIME, contractException.getErrorType()); + + })); + + /* + * Have the actor add a plan and show that that plan executes + */ + + MutableBoolean planExecuted = new MutableBoolean(); + + pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(4, (context) -> { + // schedule two passive plans + context.addPlan((c) -> { + planExecuted.setValue(true); + }, 5); + })); + + // build the plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // run the simulation + Simulation.builder()// + .addPlugin(testPlugin)// + .build()// + .execute();// + + // we do not need to show that all plans executed + + // show that the last two passive plans did not execute + assertTrue(planExecuted.getValue()); + + } + + @Test + @UnitTestMethod(target = DataManagerContext.class, name = "addPlan", args = { Plan.class }) + public void testAddPlan_Plan() { + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // test preconditions + pluginDataBuilder.addTestDataManager("dm", () -> new TestDataManager1()); + pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(1, (context) -> { + double scheduledTime = context.getTime() + 1; + + ContractException contractException = assertThrows(ContractException.class, + () -> context.addPlan(Plan.builder(DataManagerContext.class)// + .setActive(true)// + .setCallbackConsumer(null)// + .setKey(null)// + .setPlanData(null)// + .setTime(scheduledTime)// + .build())); + assertEquals(NucleusError.NULL_PLAN_CONSUMER, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, () -> context.addPlan(null)); + assertEquals(NucleusError.NULL_PLAN, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, + () -> context.addPlan(Plan.builder(DataManagerContext.class)// + .setActive(true)// + .setCallbackConsumer((c) -> { + })// + .setKey(null)// + .setPlanData(null)// + .setTime(-1)// + .build())); + assertEquals(NucleusError.PAST_PLANNING_TIME, contractException.getErrorType()); + + })); + + /* + * Have the actor add a plan and show that that plan executes + */ + + MutableBoolean planExecuted = new MutableBoolean(); + + pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(4, (context) -> { + // schedule two passive plans + context.addPlan(Plan.builder(DataManagerContext.class)// + .setActive(true)// + .setCallbackConsumer((c) -> { + planExecuted.setValue(true); + })// + .setKey(null)// + .setPlanData(null)// + .setTime(5)// + .build()); + })); + + // build the plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // run the simulation + Simulation.builder()// + .addPlugin(testPlugin)// + .build()// + .execute();// + + // we do not need to show that all plans executed + + // show that the last two passive plans did not execute + assertTrue(planExecuted.getValue()); + + } + + @Test + @UnitTestMethod(target = DataManagerContext.class, name = "getPlan", args = { Object.class }, tags = { + UnitTag.INCOMPLETE }) + public void testGetPlan() { + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // test preconditions + pluginDataBuilder.addTestDataManager("dm", () -> new TestDataManager1()); + pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(1, (context) -> { + ContractException contractException = assertThrows(ContractException.class, () -> context.getPlan(null)); + assertEquals(NucleusError.NULL_PLAN_KEY, contractException.getErrorType()); + })); + + /* + * have the added test agent add a plan that can be retrieved and thus + * was added successfully + */ + pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(2, (context) -> { + Object key = new Object(); + assertFalse(context.getPlan(key).isPresent()); + Plan plan = Plan.builder(DataManagerContext.class)// + .setCallbackConsumer((c) -> { + })// + .setTime(100)// + .setKey(key).build(); + context.addPlan(plan); + assertTrue(context.getPlan(key).isPresent()); + })); + + // build the plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // run the simulation + TestSimulation.builder().addPlugin(testPlugin).build().execute(); + + } + + @Test + @UnitTestMethod(target = DataManagerContext.class, name = "removePlan", args = { Object.class }, tags = { + UnitTag.INCOMPLETE }) + public void testRemovePlan() { + /* + * The test does not show that the plan is returned through the remove + * invocation + */ + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // test preconditions + pluginDataBuilder.addTestDataManager("dm", () -> new TestDataManager1()); + pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(1, (context) -> { + ContractException contractException = assertThrows(ContractException.class, () -> context.removePlan(null)); + assertEquals(NucleusError.NULL_PLAN_KEY, contractException.getErrorType()); + })); + + Object key = new Object(); + MutableBoolean removedPlanHasExecuted = new MutableBoolean(); + + // have the added test agent add a plan + pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(2, (context) -> { + Plan plan = Plan.builder(DataManagerContext.class)// + .setCallbackConsumer((c) -> { + removedPlanHasExecuted.setValue(true); + })// + .setTime(4)// + .setKey(key)// + .build(); + + context.addPlan(plan); + })); + + // have the test agent remove the plan and show the plan no longer + // exists + pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(3, (context) -> { + assertTrue(context.getPlan(key).isPresent()); + + context.removePlan(key); + + assertFalse(context.getPlan(key).isPresent()); + + })); + + // build the plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // run the simulation + TestSimulation.builder().addPlugin(testPlugin).build().execute(); + + // show that the remove plan was not executed + assertFalse(removedPlanHasExecuted.getValue()); + } + + @Test + @UnitTestMethod(target = DataManagerContext.class, name = "getPlanKeys", args = {}) + public void testGetPlanKeys() { + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // There are no precondition tests + Set expectedKeys = new LinkedHashSet<>(); + int keyCount = 20; + for (int i = 0; i < keyCount; i++) { + expectedKeys.add(new Object()); + } + + // have the test agent add some plans + pluginDataBuilder.addTestDataManager("dm", () -> new TestDataManager1()); + pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(1, (context) -> { + for (Object key : expectedKeys) { + + Plan plan = Plan.builder(DataManagerContext.class)// + .setCallbackConsumer((c) -> { + })// + .setTime(100)// + .setKey(key)// + .build(); + + context.addPlan(plan); + } + + Set actualKeys = context.getPlanKeys().stream() + .collect(Collectors.toCollection(LinkedHashSet::new)); + assertEquals(expectedKeys, actualKeys); + + })); + + // build the plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // run the simulation + TestSimulation.builder().addPlugin(testPlugin).build().execute(); + } + + @Test + @UnitTestMethod(target = DataManagerContext.class, name = "getScheduledSimulationHaltTime", args = {}) + public void testGetScheduledSimulationHaltTime() { + Set stopTimes = new LinkedHashSet<>(); + + stopTimes.add(4.6); + stopTimes.add(13.0); + stopTimes.add(554.3); + stopTimes.add(7.9); + stopTimes.add(400.2); + stopTimes.add(3000.1); + + for (Double stopTime : stopTimes) { + TestPluginData testPluginData = TestPluginData + .builder() + .addTestDataManagerPlan("dm", new TestDataManagerPlan(0, (context) -> { + assertEquals(stopTime, context.getScheduledSimulationHaltTime()); + })) + .addTestDataManager("dm", () -> new TestDataManager1()) + .build(); + + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + TestSimulation.builder().setSimulationHaltTime(stopTime).addPlugin(testPlugin).build().execute(); + } + } + + @Test + @UnitTestMethod(target = DataManagerContext.class, name = "releaseObservationEvent", args = { Event.class }) + public void testReleaseObservationEvent() { + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + MutableBoolean eventResolved = new MutableBoolean(); + + // Have the data manager subscribe to test event and then set the + // eventResolved to true + pluginDataBuilder.addTestDataManager("dm1", () -> new TestDataManager1()); + pluginDataBuilder.addTestDataManagerPlan("dm1", new TestDataManagerPlan(0, (c) -> { + c.subscribe(TestEvent1.class, (c2, e) -> { + eventResolved.setValue(true); + }); + })); + + // have another data manager resolve a test event + pluginDataBuilder.addTestDataManager("dm2", () -> new TestDataManager2()); + pluginDataBuilder.addTestDataManagerPlan("dm2", new TestDataManagerPlan(1, (context) -> { + context.releaseObservationEvent(new TestEvent1()); + })); + + // precondition tests + pluginDataBuilder.addTestDataManagerPlan("dm1", new TestDataManagerPlan(1, (context) -> { + ContractException contractException = assertThrows(ContractException.class, + () -> context.releaseObservationEvent(null)); + assertEquals(NucleusError.NULL_EVENT, contractException.getErrorType()); + })); + + // build the plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // run the simulation + Simulation.builder()// + .addPlugin(testPlugin)// + .build()// + .execute();// + + // show that event actually resolved + assertTrue(eventResolved.getValue()); + + } + + @Test + @UnitTestMethod(target = DataManagerContext.class, name = "releaseMutationEvent", args = { Event.class }) + public void testReleaseMutationEvent() { + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + MutableBoolean eventResolved = new MutableBoolean(); + + // Have the data manager subscribe to test event and then set the + // eventResolved to true + pluginDataBuilder.addTestDataManager("dm", () -> new TestDataManager1()); + pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(0, (c) -> { + c.subscribe(TestEvent1.class, (c2, e) -> { + eventResolved.setValue(true); + }); + })); + + // have the data manager release the mutation event + pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(1, (context) -> { + context.releaseMutationEvent(new TestEvent1()); + })); + + // have the data manager show the event was handled + pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(2, (context) -> { + assertTrue(eventResolved.getValue()); + })); + + // build the plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // run the simulation + Simulation.builder()// + .addPlugin(testPlugin)// + .build()// + .execute();// + + // precondition test: if the event is null + pluginDataBuilder.addTestDataManager("dm", () -> new TestDataManager1()); + pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(1, (context) -> { + ContractException contractException = assertThrows(ContractException.class, + () -> context.releaseObservationEvent(null)); + assertEquals(NucleusError.NULL_EVENT, contractException.getErrorType()); + })); + + // build the plugin + testPluginData = pluginDataBuilder.build(); + testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // run the simulation + Simulation.builder()// + .addPlugin(testPlugin)// + .build()// + .execute();// + + } + + @Test + @UnitTestMethod(target = DataManagerContext.class, name = "stateRecordingIsScheduled", args = {}) + public void testStateRecordingIsScheduled() { + Set stateRecordingList = new LinkedHashSet<>(); + + stateRecordingList.add(false); + stateRecordingList.add(false); + stateRecordingList.add(true); + stateRecordingList.add(false); + stateRecordingList.add(true); + stateRecordingList.add(true); + + for (Boolean stateRecording : stateRecordingList) { + TestPluginData testPluginData = TestPluginData + .builder() + .addTestDataManagerPlan("dm", new TestDataManagerPlan(0, (context) -> { + assertEquals(stateRecording, context.stateRecordingIsScheduled()); + })) + .addTestDataManager("dm", () -> new TestDataManager1()) + .build(); + + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + TestSimulation.builder().setSimulationHaltTime(1).setProduceSimulationStateOnHalt(stateRecording) + .addPlugin(testPlugin).build().execute(); + } + } + + private static class TestEvent1 implements Event { + + } + + private static class TestDataManager1 extends TestDataManager { + } + + private static class TestDataManager2 extends TestDataManager { + } + + private static class TestDataManager3 extends TestDataManager { + + } + + private static class TestDataManager3A extends TestDataManager3 { + + } + + private static class TestDataManager3B extends TestDataManager3 { + + } + + private static class TestDataManager4 extends TestDataManager { + + } + + private static class TestDataManager4A extends TestDataManager4 { + + } + + @Test + @UnitTestMethod(target = DataManagerContext.class, name = "actorExists", args = { ActorId.class }) + public void testActorExists() { + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + double testTime = 1; + // there are no precondition tests + + pluginDataBuilder.addTestDataManager("dm", () -> new TestDataManager1()); + + // have the test agent show it exists and that other agents do not + pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(testTime++, (context) -> { + for (int i = 0; i < 3; i++) { + ActorId actorId = new ActorId(i); + assertFalse(context.actorExists(actorId)); + } + for (int i = 0; i < 3; i++) { + ActorId actorId = context.addActor((c) -> { + }); + assertTrue(context.actorExists(actorId)); + } + + })); + + // build the plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // run the simulation + TestSimulation.builder().addPlugin(testPlugin).build().execute(); + + } + + @Test + + @UnitTestMethod(target = DataManagerContext.class, name = "addActor", args = { Consumer.class }) + public void testAddActor() { + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + MutableBoolean actorWasAdded = new MutableBoolean(); + + // there are no precondition tests + + // have the test agent show it exists and that other agents do not + pluginDataBuilder.addTestDataManager("dm", () -> new TestDataManager1()); + pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(1, (c) -> { + c.addActor((c2) -> actorWasAdded.setValue(true)); + })); + + // build the plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // run the simulation + Simulation.builder()// + .addPlugin(testPlugin)// + .build()// + .execute();// + + // show that the action plans got executed + assertTrue(actorWasAdded.getValue()); + + } + + @Test + @UnitTestMethod(target = DataManagerContext.class, name = "removeActor", args = { ActorId.class }) + public void testRemoveActor() { + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // have the resolver execute the precondition tests + pluginDataBuilder.addTestDataManager("dm", () -> new TestDataManager1()); + pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(0, (c) -> { + + ContractException contractException = assertThrows(ContractException.class, () -> c.removeActor(null)); + assertEquals(NucleusError.NULL_ACTOR_ID, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, () -> c.removeActor(new ActorId(1000))); + assertEquals(NucleusError.UNKNOWN_ACTOR_ID, contractException.getErrorType()); + + })); + + List addedActorIds = new ArrayList<>(); + + // have the add a few agents + pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(1, (c) -> { + for (int i = 0; i < 10; i++) { + ActorId actorId = c.addActor((c2) -> { + }); + assertTrue(c.actorExists(actorId)); + addedActorIds.add(actorId); + } + })); + + // have the actor remove the added actors + pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(2, (c) -> { + for (ActorId actorId : addedActorIds) { + c.removeActor(actorId); + assertFalse(c.actorExists(actorId)); + } + })); + + // build the plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // build and execute the engine + TestSimulation.builder().addPlugin(testPlugin).build().execute(); + } + + @Test + @UnitTestMethod(target = DataManagerContext.class, name = "halt", args = {}) + public void testHalt() { + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + Set expectedValues = new LinkedHashSet<>(); + expectedValues.add(1); + expectedValues.add(2); + expectedValues.add(3); + + Set actualValues = new LinkedHashSet<>(); + + pluginDataBuilder.addTestDataManager("dm", () -> new TestDataManager1()); + + // have the test agent execute several tasks, with one of the tasks + // halting the simulation + + pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(1, (context) -> { + actualValues.add(1); + })); + + pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(2, (context) -> { + actualValues.add(2); + })); + + pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(3, (context) -> { + actualValues.add(3); + context.halt(); + })); + + pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(4, (context) -> { + actualValues.add(4); + })); + + pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(5, (context) -> { + actualValues.add(5); + })); + + // build the plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + TestOutputConsumer testOutputConsumer = new TestOutputConsumer(); + // run the simulation + Simulation.builder()// + .addPlugin(testPlugin)// + .setOutputConsumer(testOutputConsumer) + .build()// + .execute();// + + // show that the plans that were scheduled after the halt did not + // execute + assertEquals(expectedValues, actualValues); + + } + + @Test + @UnitTestMethod(target = DataManagerContext.class, name = "subscribe", args = { Class.class, BiConsumer.class }) + public void testSubscribe() { + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + MutableBoolean observed = new MutableBoolean(); + + // have the resolver test preconditions for all the phases + pluginDataBuilder.addTestDataManager("dm", () -> new TestDataManager1()); + pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(0, (c) -> { + + ContractException contractException = assertThrows(ContractException.class, + () -> c.subscribe(null, (c2, e) -> { + })); + assertEquals(NucleusError.NULL_EVENT_CLASS, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, () -> c.subscribe(TestEvent1.class, null)); + assertEquals(NucleusError.NULL_EVENT_CONSUMER, contractException.getErrorType()); + + })); + + // have the resolver subscribe for test events. + pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(0, (c) -> { + + c.subscribe(TestEvent1.class, (c2, e) -> { + observed.setValue(true); + }); + + ContractException contractException = assertThrows(ContractException.class, + () -> c.subscribe(TestEvent1.class, (c2, e) -> { + })); + assertEquals(NucleusError.DUPLICATE_EVENT_SUBSCRIPTION, contractException.getErrorType()); + + })); + + // create a data manager that will generate a test event + + pluginDataBuilder.addTestDataManager("generator", () -> new TestDataManager()); + pluginDataBuilder.addTestDataManagerPlan("generator", new TestDataManagerPlan(1, (c) -> { + c.releaseObservationEvent(new TestEvent1()); + })); + + // build the plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // build and execute the engine + Simulation.builder()// + .addPlugin(testPlugin)// + .build()// + .execute();// + + /* + * show that the resolver engaged in the three event resolution phases + * in the proper order + */ + assertTrue(observed.getValue()); + + } + + @Test + @UnitTestMethod(target = DataManagerContext.class, name = "unsubscribe", args = { Class.class }) + public void testUnSubscribeToEvent() { + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // have the resolver test preconditions + pluginDataBuilder.addTestDataManager("dm", () -> new TestDataManager1()); + pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(0, (c) -> { + ContractException contractException = assertThrows(ContractException.class, () -> c.unsubscribe(null)); + assertEquals(NucleusError.NULL_EVENT_CLASS, contractException.getErrorType()); + })); + + /* + * Create a container to count then number of times a subscription + * execution occured + */ + MutableInteger phaseExecutionCount = new MutableInteger(); + + /* + * have the resolver subscribe to the test event and have it handle each + * type of event handling by incrementing a counter + */ + + pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(0, (c) -> { + + c.subscribe(TestEvent1.class, (c2, e) -> { + phaseExecutionCount.increment(); + }); + + })); + + // create a data manager that will produce a test event + pluginDataBuilder.addTestDataManager("generator", () -> new TestDataManager()); + pluginDataBuilder.addTestDataManagerPlan("generator", new TestDataManagerPlan(1, (c) -> { + c.releaseObservationEvent(new TestEvent1()); + })); + + /* + * Show that the phaseExecutionCount is three after the the agent is + * done + */ + pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(2, (c) -> { + assertEquals(1, phaseExecutionCount.getValue()); + })); + + // have the resolver unsubscribe + pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(3, (c) -> { + c.unsubscribe(TestEvent1.class); + })); + + // have the data manager generate another test event + pluginDataBuilder.addTestDataManagerPlan("generator", new TestDataManagerPlan(4, (c) -> { + c.releaseObservationEvent(new TestEvent1()); + })); + + /* + * Show that the phaseExecutionCount is still three after the the agent + * is done and thus the resolver is no longer subscribed + */ + pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(5, (c) -> { + assertEquals(1, phaseExecutionCount.getValue()); + })); + + // build the plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // build and execute the engine + TestSimulation.builder().addPlugin(testPlugin).build().execute(); + } + + @Test + @UnitTestMethod(target = DataManagerContext.class, name = "subscribersExist", args = { Class.class }) + public void testSubscribersExist() { + + /* + * create a simple event label as a place holder -- all test events will + * be matched + */ + EventFilter eventFilter = EventFilter.builder(TestEvent1.class)// + .build();// + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // add the first data manager + + /* + * Have the test resolver show that there are initially no subscribers + * to test events. + */ + pluginDataBuilder.addTestDataManager("dm1", () -> new TestDataManager1()); + pluginDataBuilder.addTestDataManagerPlan("dm1", new TestDataManagerPlan(0, (c) -> { + assertFalse(c.subscribersExist(TestEvent1.class)); + })); + + // create an agent and have it subscribe to test events at time 1 + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + // subscribe to the event label + c.subscribe(eventFilter, (c2, e) -> { + }); + })); + + // show that the resolver now sees that there are subscribers + pluginDataBuilder.addTestDataManagerPlan("dm1", new TestDataManagerPlan(2, (c) -> { + assertTrue(c.subscribersExist(TestEvent1.class)); + })); + + // have the agent unsubscribe + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(3, (c) -> { + c.unsubscribe(eventFilter); + })); + + // show that the resolver see no subscribers + pluginDataBuilder.addTestDataManagerPlan("dm1", new TestDataManagerPlan(4, (c) -> { + assertFalse(c.subscribersExist(TestEvent1.class)); + })); + + // add a second data manager + + pluginDataBuilder.addTestDataManager("dm2", () -> new TestDataManager2()); + + pluginDataBuilder.addTestDataManagerPlan("dm2", new TestDataManagerPlan(5, (c) -> { + c.subscribe(TestEvent1.class, (c2, e) -> { + }); + })); + + // show that the test resolver now sees that there are subscribers + pluginDataBuilder.addTestDataManagerPlan("dm1", new TestDataManagerPlan(6, (c) -> { + assertTrue(c.subscribersExist(TestEvent1.class)); + })); + + // have the second data manager unsubscribe + pluginDataBuilder.addTestDataManagerPlan("dm2", new TestDataManagerPlan(7, (c) -> { + c.unsubscribe(TestEvent1.class); + })); + + // show that dm1 now sees that there are no subscribers + pluginDataBuilder.addTestDataManagerPlan("dm1", new TestDataManagerPlan(8, (c) -> { + assertFalse(c.subscribersExist(TestEvent1.class)); + })); + + // build the plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // build and execute the engine + TestSimulation.builder().addPlugin(testPlugin).build().execute(); + } + + private static class ActorObservingDataManager extends TestDataManager { + private List> observedPairs; + private DataManagerContext dataManagerContext; + + public ActorObservingDataManager(List> observedPairs) { + this.observedPairs = observedPairs; + } + + public void init(DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + this.dataManagerContext = dataManagerContext; + } + + public void observe() { + observedPairs.add(new Pair<>(dataManagerContext.getTime(), dataManagerContext.getActorId())); + } + } + + @Test + @UnitTestMethod(target = DataManagerContext.class, name = "getActorId", args = {}) + public void testGetActorId() { + List> expectedPairs = new ArrayList<>(); + List> observedPairs = new ArrayList<>(); + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + double testTime = 1; + // there are no precondition tests + + pluginDataBuilder.addTestDataManager("dm", () -> new ActorObservingDataManager(observedPairs)); + + /* + * Have actors get their own actor ids and show that these ids match the + * expected values established duing the initialization of the + * TestActors. + */ + pluginDataBuilder.addTestActorPlan("Alpha", new TestActorPlan(testTime++, (c) -> { + c.getDataManager(ActorObservingDataManager.class).observe(); + expectedPairs.add(new Pair<>(c.getTime(), c.getActorId())); + })); + + pluginDataBuilder.addTestActorPlan("Beta", new TestActorPlan(testTime++, (c) -> { + c.getDataManager(ActorObservingDataManager.class).observe(); + expectedPairs.add(new Pair<>(c.getTime(), c.getActorId())); + })); + + pluginDataBuilder.addTestActorPlan("Gamma", new TestActorPlan(testTime++, (c) -> { + c.getDataManager(ActorObservingDataManager.class).observe(); + expectedPairs.add(new Pair<>(c.getTime(), c.getActorId())); + })); + + // build the plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // run the simulation + TestSimulation.builder().addPlugin(testPlugin).build().execute(); + + // show that the number of actor ids matches the number of actor aliases + assertEquals(expectedPairs, observedPairs); + } + + @Test + @UnitTestMethod(target = DataManagerContext.class, name = "setPlanDataConverter", args = { Class.class, + Function.class }) + public void testSetPlanDataConverter() { + MutableBoolean called = new MutableBoolean(false); + + class TestPlanData1 implements PlanData { + + } + + Function> planDataConverter = t -> { + return context -> called.setValue(true); + }; + + class TestDM1 extends DataManager { + public void init(DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + dataManagerContext.setPlanDataConverter(TestPlanData1.class, planDataConverter); + } + } + + Plugin actorPlugin = Plugin.builder() + .setPluginId(new SimplePluginId("TestDM1")) + .setInitializer((pContext) -> { + pContext.addDataManager(new TestDM1()); + }) + .build(); + + TestPluginData testPluginData = TestPluginData.builder() + .addTestDataManagerPlan("dm1", new TestDataManagerPlan(2, (context) -> { + + })) + .addTestDataManager("dm1", () -> new TestDataManager()) + .build(); + + PlanQueueData planQueueData = PlanQueueData.builder() + .setPlanData(new TestPlanData1()) + .setTime(1) + .setPlanner(Planner.DATA_MANAGER) + .build(); + + SimulationState simulationState = SimulationState.builder() + .addPlanQueueData(planQueueData) + .setStartTime(1) + .setPlanningQueueArrivalId(2) + .build(); + + TestSimulation.builder() + .addPlugin(actorPlugin) + .addPlugin(TestPlugin.getTestPlugin(testPluginData)) + .setSimulationState(simulationState) + .build() + .execute(); + + assertTrue(called.getValue()); + } + + + + @Test + @UnitTestMethod(target = DataManagerContext.class, name = "getBaseDate", args = {}) + public void testGetBaseDate() { + + // create some base dates to test + List localDates = new ArrayList<>(); + + localDates.add(LocalDate.of(2023, 1, 10)); + localDates.add(LocalDate.of(2024, 6, 13)); + localDates.add(LocalDate.of(2020, 3, 15)); + localDates.add(LocalDate.of(2023, 12, 25)); + + // loop over the base dates + IntStream.range(0, localDates.size()).forEach((i) -> { + LocalDate localDate = localDates.get(i); + // build a single data manager that will show that the base date returned by the + // context is correct + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + pluginDataBuilder.addTestDataManager("dm",()->new TestDataManager1()); + pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(1, (c) -> { + assertEquals(localDate, c.getBaseDate()); + })); + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // execute the engine + SimulationState simulationState = SimulationState.builder().setBaseDate(localDate).build(); + TestSimulation.builder().setSimulationState(simulationState).addPlugin(testPlugin).build().execute(); + }); + + } + + @Test + @UnitTestMethod(target = DataManagerContext.class, name = "getStartTime", args = {}) + public void testGetStartTime() { + + // create some start times to test + List startTimes = new ArrayList<>(); + + startTimes.add(-100.0); + startTimes.add(30.23); + startTimes.add(17.63); + startTimes.add(45.5); + + // loop over the base dates + IntStream.range(0, startTimes.size()).forEach((i) -> { + Double startTime = startTimes.get(i); + // build a single report that will show that the start time returned by the + // context is correct + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + pluginDataBuilder.addTestDataManager("dm",()->new TestDataManager1()); + pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(startTime+10, (c) -> { + assertEquals(startTime, c.getStartTime()); + })); + + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // execute the engine + SimulationState simulationState = SimulationState.builder().setStartTime(startTime).build(); + TestSimulation.builder().setSimulationState(simulationState).addPlugin(testPlugin).build().execute(); + }); + + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_DataManagerId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_DataManagerId.java new file mode 100644 index 000000000..16d15fc77 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_DataManagerId.java @@ -0,0 +1,101 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.random.RandomGeneratorProvider; + +public final class AT_DataManagerId { + + @UnitTestMethod(target = DataManagerId.class, name = "getValue", args = {}) + @Test + public void testGetValue() { + for (int i = 0; i < 100; i++) { + assertEquals(i, new DataManagerId(i).getValue()); + } + } + + @UnitTestMethod(target = DataManagerId.class, name = "toString", args = {}) + @Test + public void testToString() { + for (int i = 0; i < 100; i++) { + assertEquals("DataManagerId [id=" + i + "]", new DataManagerId(i).toString()); + } + } + + @UnitTestMethod(target = DataManagerId.class, name = "hashCode", args = {}) + @Test + public void testHashCode() { + // show equal objects have equal hashcodes + for (int i = 0; i < 10; i++) { + DataManagerId a = new DataManagerId(i); + DataManagerId b = new DataManagerId(i); + assertEquals(a, b); + assertEquals(a.hashCode(), b.hashCode()); + } + + // show that hash codes are dispersed + Set hashcodes = new LinkedHashSet<>(); + for (int i = 0; i < 1000; i++) { + hashcodes.add(new DataManagerId(i).hashCode()); + } + assertEquals(1000, hashcodes.size()); + + } + + @UnitTestMethod(target = DataManagerId.class, name = "equals", args = { Object.class }) + @Test + public void testEquals() { + // show data manager ids are equal if and only if they have the same + // base int + // value + for (int i = 0; i < 10; i++) { + DataManagerId a = new DataManagerId(i); + for (int j = 0; j < 10; j++) { + DataManagerId b = new DataManagerId(j); + if (i == j) { + assertEquals(a, b); + } else { + assertNotEquals(a, b); + } + } + } + } + + @Test + @UnitTestMethod(target = DataManagerId.class, name = "compareTo", args = { DataManagerId.class }) + public void testCompareTo() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8041307094727012939L); + for (int i = 0; i < 100; i++) { + int a = randomGenerator.nextInt(); + int b = randomGenerator.nextInt(); + + int expectedComparison = Integer.compare(a, b); + int actualComparison = new DataManagerId(a).compareTo(new DataManagerId(b)); + assertEquals(expectedComparison, actualComparison); + } + + } + + @Test + @UnitTestConstructor(target = DataManagerId.class, args = { int.class }) + public void testConstructor() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7051188045588654915L); + for (int i = 0; i < 100; i++) { + int expectedIdValue = randomGenerator.nextInt(); + DataManagerId dataManagerId = new DataManagerId(expectedIdValue); + int actualIdValue = dataManagerId.getValue(); + assertEquals(expectedIdValue, actualIdValue); + } + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_Dimension.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_Dimension.java new file mode 100644 index 000000000..9b818587e --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_Dimension.java @@ -0,0 +1,152 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestMethod; + +public class AT_Dimension { + + @Test + @UnitTestMethod(target = FunctionalDimension.class, name = "builder", args = {}) + public void testBuilder() { + assertNotNull(FunctionalDimension.builder()); + } + + private void testGetMetaDataValues(String... values) { + List expectedMetaData = new ArrayList<>(); + for (String value : values) { + expectedMetaData.add(value); + } + + FunctionalDimension.Builder builder = FunctionalDimension.builder(); + for (String metaDatum : expectedMetaData) { + builder.addMetaDatum(metaDatum); + } + FunctionalDimension dimension = builder.build(); + List actualMetaData = dimension.getExperimentMetaData(); + assertEquals(expectedMetaData, actualMetaData); + } + + @Test + @UnitTestMethod(target = FunctionalDimension.class, name = "getExperimentMetaData", args = {}) + public void testGetExperimentMetaData() { + // test several numbers and duplications of meta data + testGetMetaDataValues(); + testGetMetaDataValues("A"); + testGetMetaDataValues("B", "A"); + testGetMetaDataValues("B", "B", "Z"); + testGetMetaDataValues("A", "B", "C", "A"); + } + + private List getValuesAsList(String... values) { + List result = new ArrayList<>(); + for (String value : values) { + result.add(value); + } + return result; + } + + @Test + @UnitTestMethod(target = FunctionalDimension.class, name = "executeLevel", args = { DimensionContext.class, int.class }) + public void testExecuteLevel() { + + Map> expectedScenarioMetaData = new LinkedHashMap<>(); + expectedScenarioMetaData.put(0, getValuesAsList("A")); + expectedScenarioMetaData.put(1, getValuesAsList("A", "B")); + expectedScenarioMetaData.put(2, getValuesAsList("A", "B", "C")); + expectedScenarioMetaData.put(3, getValuesAsList("A", "B", "C", "D")); + + FunctionalDimension.Builder builder = FunctionalDimension.builder(); + for (Integer i : expectedScenarioMetaData.keySet()) { + builder.addLevel((map) -> { + return expectedScenarioMetaData.get(i); + }); + } + + FunctionalDimension dimension = builder.build(); + + for (int i = 0; i < dimension.levelCount(); i++) { + List expectedValues = expectedScenarioMetaData.get(i); + List actualValues = dimension.executeLevel(null, i); + assertEquals(expectedValues, actualValues); + } + + } + + @Test + @UnitTestMethod(target = FunctionalDimension.class, name = "levelCount", args = {}) + public void testLevelCount() { + + FunctionalDimension dimension = FunctionalDimension.builder().build(); + assertEquals(0, dimension.levelCount()); + + dimension = FunctionalDimension .builder()// + .addLevel((map) -> { + return new ArrayList<>(); + })// + .build(); + assertEquals(1, dimension.levelCount()); + + dimension = FunctionalDimension .builder()// + .addLevel((map) -> { + return new ArrayList<>(); + })// + .addLevel((map) -> { + return new ArrayList<>(); + })// + .build(); + assertEquals(2, dimension.levelCount()); + } + + @Test + @UnitTestMethod(target = FunctionalDimension.Builder.class, name = "addMetaDatum", args = { String.class }) + public void testAddMetaDatum() { + // test several numbers and duplications of meta data + testGetMetaDataValues(); + testGetMetaDataValues("A"); + testGetMetaDataValues("B", "A"); + testGetMetaDataValues("B", "B", "Z"); + testGetMetaDataValues("A", "B", "C", "A"); + } + + @Test + @UnitTestMethod(target = FunctionalDimension.Builder.class, name = "addLevel", args = { Function.class }) + public void testAddLevel() { + Map> expectedScenarioMetaData = new LinkedHashMap<>(); + expectedScenarioMetaData.put(0, getValuesAsList("A")); + expectedScenarioMetaData.put(1, getValuesAsList("A", "B")); + expectedScenarioMetaData.put(2, getValuesAsList("A", "B", "C")); + expectedScenarioMetaData.put(3, getValuesAsList("A", "B", "C", "D")); + + FunctionalDimension.Builder builder = FunctionalDimension.builder(); + for (Integer i : expectedScenarioMetaData.keySet()) { + builder.addLevel((map) -> { + return expectedScenarioMetaData.get(i); + }); + } + + FunctionalDimension dimension = builder.build(); + + for (int i = 0; i < dimension.levelCount(); i++) { + List expectedValues = expectedScenarioMetaData.get(i); + List actualValues = dimension.executeLevel(null, i); + assertEquals(expectedValues, actualValues); + } + } + + @Test + @UnitTestMethod(target = FunctionalDimension.Builder.class, name = "build", args = {}) + public void testBuild() { + assertNotNull(FunctionalDimension.builder().build()); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_DimesionContext.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_DimesionContext.java new file mode 100644 index 000000000..bce2e63fd --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_DimesionContext.java @@ -0,0 +1,265 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + +public class AT_DimesionContext { + + private static class PluginData1 implements PluginData { + + private static class Builder implements PluginDataBuilder { + + @Override + public PluginData build() { + return null; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof Builder)) { + return false; + } + + return true; + } + } + + @Override + public PluginDataBuilder getCloneBuilder() { + return new Builder(); + } + } + + private static class PluginData2 implements PluginData { + + private static class Builder implements PluginDataBuilder { + + @Override + public PluginData build() { + return null; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof Builder)) { + return false; + } + + return true; + } + } + + @Override + public PluginDataBuilder getCloneBuilder() { + return new Builder(); + } + } + + private static class PluginData3 implements PluginData { + + private static class Builder implements PluginDataBuilder { + + @Override + public PluginData build() { + return null; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof Builder)) { + return false; + } + + return true; + } + } + + @Override + public PluginDataBuilder getCloneBuilder() { + return new Builder(); + } + } + + @Test + @UnitTestMethod(target = DimensionContext.class, name = "builder", args = {}) + public void testBuilder() { + assertNotNull(DimensionContext.builder()); + } + + @Test + @UnitTestMethod(target = DimensionContext.class, name = "getPluginDataBuilder", args = { Class.class }) + public void testGetPluginDataBuilder() { + PluginData p1 = new PluginData1(); + + PluginData p2 = new PluginData2(); + + PluginData p3 = new PluginData1(); + + PluginData p4 = new PluginData2(); + + DimensionContext.Builder dimensionContextBuilder = DimensionContext.builder(); + + dimensionContextBuilder.add(p1); + dimensionContextBuilder.add(p2); + dimensionContextBuilder.add(p3); + dimensionContextBuilder.add(p4); + + DimensionContext dimensionContext = dimensionContextBuilder.build(); + + PluginDataBuilder p = dimensionContext.getPluginDataBuilder(PluginData1.Builder.class); + assertEquals(p3.getCloneBuilder(), p); + + p = dimensionContext.getPluginDataBuilder(PluginData2.Builder.class); + assertEquals(p4.getCloneBuilder(), p); + + /* + * precondition test : if more than one plugin data builder matches the + * given class reference + */ + ContractException contractException = assertThrows(ContractException.class, + () -> dimensionContext.getPluginDataBuilder(PluginDataBuilder.class)); + assertEquals(NucleusError.AMBIGUOUS_PLUGIN_DATA_BUILDER_CLASS, contractException.getErrorType()); + + /* + * precondition test : if no plugin data builder matches the given class + * reference + */ + contractException = assertThrows(ContractException.class, + () -> dimensionContext.getPluginDataBuilder(PluginData3.Builder.class)); + assertEquals(NucleusError.UNKNOWN_PLUGIN_DATA_BUILDER_CLASS, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = DimensionContext.class, name = "getPluginData", args = { Class.class }) + public void testGetPluginData() { + PluginData p1 = new PluginData1(); + + PluginData p2 = new PluginData2(); + + PluginData p3 = new PluginData1(); + + PluginData p4 = new PluginData2(); + + DimensionContext.Builder dimensionContextBuilder = DimensionContext.builder(); + + dimensionContextBuilder.add(p1); + dimensionContextBuilder.add(p2); + dimensionContextBuilder.add(p3); + dimensionContextBuilder.add(p4); + + DimensionContext dimensionContext = dimensionContextBuilder.build(); + + PluginData p = dimensionContext.getPluginData(PluginData1.class); + assertEquals(p3, p); + + p = dimensionContext.getPluginData(PluginData2.class); + assertEquals(p4, p); + + /* + * precondition test : if more than one plugin data builder matches the + * given class reference + */ + ContractException contractException = assertThrows(ContractException.class, + () -> dimensionContext.getPluginData(PluginData.class)); + assertEquals(NucleusError.AMBIGUOUS_PLUGIN_DATA_CLASS, contractException.getErrorType()); + + /* + * precondition test : if no plugin data builder matches the given class + * reference + */ + contractException = assertThrows(ContractException.class, + () -> dimensionContext.getPluginData(PluginData3.class)); + assertEquals(NucleusError.UNKNOWN_PLUGIN_DATA_CLASS, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = DimensionContext.Builder.class, name = "add", args = { PluginData.class }) + public void testAdd() { + PluginData p1 = new PluginData1(); + + PluginData p2 = new PluginData2(); + + Set expectedContents = new LinkedHashSet<>(); + expectedContents.add(p1); + expectedContents.add(p2); + + DimensionContext.Builder builder = DimensionContext.builder(); + + PluginDataBuilder p1b = builder.add(p1); + PluginDataBuilder p2b = builder.add(p2); + + assertEquals(p1.getCloneBuilder(), p1b); + assertEquals(p2.getCloneBuilder(), p2b); + + DimensionContext dimensionContext = builder.build(); + Set actualContents = new LinkedHashSet<>(); + assertDoesNotThrow(() -> actualContents.add(dimensionContext.getPluginData(PluginData1.class))); + assertDoesNotThrow(() -> actualContents.add(dimensionContext.getPluginData(PluginData2.class))); + + assertEquals(expectedContents, actualContents); + + ContractException contractException = assertThrows(ContractException.class, + () -> DimensionContext.builder().add(null)); + assertEquals(NucleusError.NULL_PLUGIN_DATA, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = DimensionContext.Builder.class, name = "build", args = {}) + public void testBuild() { + PluginData p1 = new PluginData1(); + + PluginData p2 = new PluginData2(); + + Set expectedContents = new LinkedHashSet<>(); + expectedContents.add(p1); + expectedContents.add(p2); + + DimensionContext.Builder builder = DimensionContext.builder(); + + PluginDataBuilder p1b = builder.add(p1); + PluginDataBuilder p2b = builder.add(p2); + + assertEquals(p1.getCloneBuilder(), p1b); + assertEquals(p2.getCloneBuilder(), p2b); + + DimensionContext dimensionContext = builder.build(); + + Set actualContents = new LinkedHashSet<>(); + assertDoesNotThrow(() -> actualContents.add(dimensionContext.getPluginData(PluginData1.class))); + assertDoesNotThrow(() -> actualContents.add(dimensionContext.getPluginData(PluginData2.class))); + + assertEquals(expectedContents, actualContents); + + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_EventFilter.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_EventFilter.java new file mode 100644 index 000000000..f6a16b024 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_EventFilter.java @@ -0,0 +1,130 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.math3.util.Pair; +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + +public class AT_EventFilter { + + private static enum functionId { + X, Y; + } + + private static class EventA implements Event { + private int x; + private double y; + + } + + private static class EventB implements Event { + + } + + private static class EventC implements Event { + + } + + @Test + @UnitTestMethod(target = EventFilter.class,name = "builder", args = { Class.class }) + public void testBuilder() { + // show that a builder instance is returned + assertNotNull(EventFilter.builder(EventA.class)); + + // precondition test: if the class reference is null + ContractException contractException = assertThrows(ContractException.class, () -> EventFilter.builder(null)); + assertEquals(NucleusError.NULL_EVENT_CLASS, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = EventFilter.class,name = "getEventClass", args = {}) + public void testGetEventClass() { + /* + * Show that the event class used to build the event filter can be + * retrieved from the event filter for a few event classes. + */ + + EventFilter eventFilterA = EventFilter.builder(EventA.class).build(); + assertEquals(EventA.class, eventFilterA.getEventClass()); + + EventFilter eventFilterB = EventFilter.builder(EventB.class).build(); + assertEquals(EventB.class, eventFilterB.getEventClass()); + + EventFilter eventFilterC = EventFilter.builder(EventC.class).build(); + assertEquals(EventC.class, eventFilterC.getEventClass()); + + } + + @Test + @UnitTestMethod(target = EventFilter.class,name = "getFunctionValuePairs", args = {}) + public void testGetFunctionValuePairs() { + // create two identifiable functions -- one for each field of EventA + IdentifiableFunction xFunction = new IdentifiableFunction<>(functionId.X, (e) -> e.x); + IdentifiableFunction yFunction = new IdentifiableFunction<>(functionId.Y, (e) -> e.y); + /* + * Create the list of expected pairs that we should be able to retrieve + * from the constructed event filter. It will start out empty. + */ + List, Object>> expectedFunctionValuePairs = new ArrayList<>(); + + /* + * show that an event filter constructed without any function will + * result in an empty list of function value pairs. + */ + EventFilter eventFilter = EventFilter.builder(EventA.class).build(); + List, Object>> functionValuePairs = eventFilter.getFunctionValuePairs(); + assertNotNull(functionValuePairs); + assertEquals(expectedFunctionValuePairs, functionValuePairs); + + /* + * Add a function and value for the x field of the event and show that + * the expected function value pairs can be retrieved from the event + * filter. + */ + eventFilter = EventFilter .builder(EventA.class)// + .addFunctionValuePair(xFunction, 2)// + .build(); + functionValuePairs = eventFilter.getFunctionValuePairs(); + expectedFunctionValuePairs.add(new Pair<>(xFunction, 2)); + assertNotNull(functionValuePairs); + assertEquals(expectedFunctionValuePairs, functionValuePairs); + + /* + * Add another function and value for the y field of the event and show + * that the expected function value pairs can be retrieved from the + * event filter. + */ + + eventFilter = EventFilter .builder(EventA.class)// + .addFunctionValuePair(xFunction, 2)// + .addFunctionValuePair(yFunction, 3.0)// + .build(); + functionValuePairs = eventFilter.getFunctionValuePairs(); + expectedFunctionValuePairs.add(new Pair<>(yFunction, 3.0)); + assertNotNull(functionValuePairs); + assertEquals(expectedFunctionValuePairs, functionValuePairs); + + } + + @Test + @UnitTestMethod(target = EventFilter.Builder.class, name = "addFunctionValuePair", args = { IdentifiableFunction.class, Object.class }) + public void addFunctionValuePair() { + testGetFunctionValuePairs(); + } + + @Test + @UnitTestMethod(target = EventFilter.Builder.class, name = "build", args = {}) + public void testBuild() { + testGetEventClass(); + testGetFunctionValuePairs(); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_Experiment.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_Experiment.java new file mode 100644 index 000000000..e91b0c541 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_Experiment.java @@ -0,0 +1,602 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.IntStream; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import util.annotations.UnitTag; +import util.annotations.UnitTestMethod; +import util.wrappers.MultiKey; +import util.wrappers.MutableInteger; +import util.wrappers.MutableObject; + +public class AT_Experiment { + + @Test + @UnitTestMethod(target = Experiment.Builder.class, name = "addDimension", args = { Dimension.class }) + public void testAddDimension() { + + Dimension dimension1 = FunctionalDimension.builder()// + .addMetaDatum("Alpha")// + .addLevel((context) -> { + List result = new ArrayList<>(); + result.add("alpha1"); + return result; + })// + .addLevel((context) -> { + List result = new ArrayList<>(); + result.add("alpha2"); + return result; + })// + .build();// + + Dimension dimension2 = FunctionalDimension.builder()// + .addMetaDatum("Beta")// + .addLevel((context) -> { + List result = new ArrayList<>(); + result.add("beta1"); + return result; + })// + .addLevel((context) -> { + List result = new ArrayList<>(); + result.add("beta2"); + return result; + })// + .addLevel((context) -> { + List result = new ArrayList<>(); + result.add("beta3"); + return result; + })// + .build();// + + Set expectedExperimentInstances = new LinkedHashSet<>(); + expectedExperimentInstances.add(new MultiKey("alpha1", "beta1")); + expectedExperimentInstances.add(new MultiKey("alpha2", "beta1")); + expectedExperimentInstances.add(new MultiKey("alpha1", "beta2")); + expectedExperimentInstances.add(new MultiKey("alpha2", "beta2")); + expectedExperimentInstances.add(new MultiKey("alpha1", "beta3")); + expectedExperimentInstances.add(new MultiKey("alpha2", "beta3")); + + Set actualExperimentInstances = new LinkedHashSet<>(); + + Plugin plugin = Plugin.builder()// + .setPluginId(new SimplePluginId("plugin")).setInitializer((c) -> { + c.addActor((c2) -> { + c2.releaseOutput(new Object()); + }); + }).build();// + + Experiment.builder()// + .addExperimentContextConsumer(c -> { + c.subscribeToOutput(Object.class, (c2, s, e) -> { + List scenarioMetaData = c2.getScenarioMetaData(s); + MultiKey.Builder builder = MultiKey.builder(); + for (String scenarioMetaDatum : scenarioMetaData) { + builder.addKey(scenarioMetaDatum); + } + actualExperimentInstances.add(builder.build()); + }); + }).addPlugin(plugin)// + .addDimension(dimension1)// + .addDimension(dimension2)// + .build()// + .execute();// + + assertEquals(expectedExperimentInstances, actualExperimentInstances); + + } + + @Test + @UnitTestMethod(target = Experiment.Builder.class, name = "addExperimentContextConsumer", args = { Consumer.class }) + public void testAddExperimentContextConsumer() { + + /* + * Show that an output handler receives data released by the simulation by + * adding a few actors that release data. Add output handlers that show that the + * output is correctly directed. Note the use of overlapping types for the + * output consumers and that the output type may be a subclass of the consumer's + * type. + */ + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + pluginBuilder.addTestActorPlan("Integer Actor", new TestActorPlan(0, (c) -> { + c.releaseOutput(56); + })); + + pluginBuilder.addTestActorPlan("String Actor", new TestActorPlan(0, (c) -> { + c.releaseOutput("string 1"); + })); + + pluginBuilder.addTestActorPlan("Double Actor", new TestActorPlan(0, (c) -> { + c.releaseOutput(34.5); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + Set actualOutput = new LinkedHashSet<>(); + + Consumer integerOutputHandler = (c) -> { + c.subscribeToOutput(Integer.class, (c2, s, e) -> { + MultiKey.Builder builder = MultiKey.builder(); + List metaData = c2.getScenarioMetaData(s); + builder.addKey("Integer Output Handler"); + for (String metaDatum : metaData) { + builder.addKey(metaDatum); + } + builder.addKey(s); + builder.addKey(e); + actualOutput.add(builder.build()); + }); + }; + + Consumer stringOutputHandler = (c) -> { + c.subscribeToOutput(String.class, (c2, s, e) -> { + actualOutput.add(new MultiKey("String Output Handler", s, e)); + }); + }; + + Consumer doubleOutputHandler = (c) -> { + c.subscribeToOutput(Double.class, (c2, s, e) -> { + actualOutput.add(new MultiKey("Double Output Handler", s, e)); + }); + }; + + Consumer numberOutputHandler = (c) -> { + c.subscribeToOutput(Number.class, (c2, s, e) -> { + actualOutput.add(new MultiKey("Number Output Handler", s, e)); + }); + }; + + Dimension dimension1 = FunctionalDimension.builder()// + .addMetaDatum("dim1")// + .addLevel((tmap) -> { + List result = new ArrayList<>(); + result.add("var_1_1"); + return result; + })// + .addLevel((tmap) -> { + List result = new ArrayList<>(); + result.add("var_1_2"); + return result; + })// + .build();// + + Dimension dimension2 = FunctionalDimension.builder()// + .addMetaDatum("dim2")// + .addLevel((tmap) -> { + List result = new ArrayList<>(); + result.add("var_2_1"); + return result; + })// + .addLevel((tmap) -> { + List result = new ArrayList<>(); + result.add("var_2_2"); + return result; + })// + .build();// + + Experiment.builder()// + .addDimension(dimension1)// + .addDimension(dimension2)// + .addExperimentContextConsumer(integerOutputHandler)// + .addExperimentContextConsumer(stringOutputHandler)// + .addExperimentContextConsumer(doubleOutputHandler)// + .addExperimentContextConsumer(numberOutputHandler)// + .addPlugin(testPlugin)// + .build()// + .execute();// + + Set expectedOutput = new LinkedHashSet<>(); + + expectedOutput.add(new MultiKey("Integer Output Handler", "var_1_1", "var_2_1", 0, 56)); + expectedOutput.add(new MultiKey("Number Output Handler", 0, 56)); + expectedOutput.add(new MultiKey("String Output Handler", 0, "string 1")); + expectedOutput.add(new MultiKey("Double Output Handler", 0, 34.5)); + expectedOutput.add(new MultiKey("Number Output Handler", 0, 34.5)); + expectedOutput.add(new MultiKey("Integer Output Handler", "var_1_2", "var_2_1", 1, 56)); + expectedOutput.add(new MultiKey("Number Output Handler", 1, 56)); + expectedOutput.add(new MultiKey("String Output Handler", 1, "string 1")); + expectedOutput.add(new MultiKey("Double Output Handler", 1, 34.5)); + expectedOutput.add(new MultiKey("Number Output Handler", 1, 34.5)); + expectedOutput.add(new MultiKey("Integer Output Handler", "var_1_1", "var_2_2", 2, 56)); + expectedOutput.add(new MultiKey("Number Output Handler", 2, 56)); + expectedOutput.add(new MultiKey("String Output Handler", 2, "string 1")); + expectedOutput.add(new MultiKey("Double Output Handler", 2, 34.5)); + expectedOutput.add(new MultiKey("Number Output Handler", 2, 34.5)); + expectedOutput.add(new MultiKey("Integer Output Handler", "var_1_2", "var_2_2", 3, 56)); + expectedOutput.add(new MultiKey("Number Output Handler", 3, 56)); + expectedOutput.add(new MultiKey("String Output Handler", 3, "string 1")); + expectedOutput.add(new MultiKey("Double Output Handler", 3, 34.5)); + expectedOutput.add(new MultiKey("Number Output Handler", 3, 34.5)); + + assertEquals(expectedOutput, actualOutput); + + } + + @Test + @UnitTestMethod(target = Experiment.Builder.class, name = "addPlugin", args = { Plugin.class }) + public void testAddPlugin() { + + // show that several plugins can be added and that they execute in the + // correct order + + // create plugin ids + PluginId aId = new SimplePluginId("plugin A"); + PluginId bId = new SimplePluginId("plugin B"); + PluginId cId = new SimplePluginId("plugin C"); + + /* + * Build the expected order of initialization based on the dependencies between + * the plugins + */ + List expectedExecutedPlugins = new ArrayList<>(); + expectedExecutedPlugins.add(cId); + expectedExecutedPlugins.add(bId); + expectedExecutedPlugins.add(aId); + + // build a container for the actual initialization order + List actualExecutedPlugins = new ArrayList<>(); + + // create the three plugins with A depending on B and C and B depending + // on C alone. + Plugin pluginA = Plugin.builder()// + .setPluginId(aId)// + .addPluginDependency(cId)// + .addPluginDependency(bId)// + .setInitializer((c) -> { + actualExecutedPlugins.add(aId); + }).build();// + + Plugin pluginB = Plugin.builder()// + .setPluginId(bId)// + .addPluginDependency(cId)// + .setInitializer((c) -> { + actualExecutedPlugins.add(bId); + }).build();// + + Plugin pluginC = Plugin.builder()// + .setPluginId(cId)// + .setInitializer((c) -> { + actualExecutedPlugins.add(cId); + }).build();// + + // create the simulation + Experiment.builder()// + .addPlugin(pluginA)// + .addPlugin(pluginB)// + .addPlugin(pluginC)// + .build()// + .execute();// + + // show that the plugins initialized in the correct order + assertEquals(expectedExecutedPlugins, actualExecutedPlugins); + } + + @Test + @UnitTestMethod(target = Experiment.Builder.class, name = "build", args = {}) + public void testBuild() { + // show that an empty experiment will executed + Experiment experiment = Experiment.builder().build(); + experiment.execute(); + + // Other aspects of the build are covered in the remaining capability + // specific tests + } + + @Test + @UnitTestMethod(target = Experiment.Builder.class, name = "setExperimentParameterData", args = { + ExperimentParameterData.class }, tags = { UnitTag.INCOMPLETE }) + public void testSetExperimentParameterData() { + testSetHaltOnException(); + testThreadCount(); + } + + private void testThreadCount() { + + // add two dimensions that will cause the experiment to execute 40 + // simulation instances + FunctionalDimension.Builder dimBuilder = FunctionalDimension.builder().addMetaDatum("alpha");// + IntStream.range(0, 5).forEach((i) -> { + dimBuilder.addLevel((context) -> { + List result = new ArrayList<>(); + result.add(Integer.toString(i)); + return result; + }); + }); + + Dimension dimension1 = dimBuilder.build(); + + FunctionalDimension.Builder dimBuilder2 = FunctionalDimension.builder(); + IntStream.range(0, 8).forEach((i) -> { + dimBuilder2.addLevel((context) -> { + List result = new ArrayList<>(); + result.add(Integer.toString(i)); + return result; + }); + }); + + Dimension dimension2 = dimBuilder2.addMetaDatum("beta").build(); + + /* + * Create a thread safe set to record the thread ids that are used by each + * simulation + */ + Set threadIds = Collections.synchronizedSet(new LinkedHashSet<>()); + + /* + * Add a plugin that will add an actor that records the thread id of the + * simulation running that actor + */ + Plugin plugin = Plugin.builder()// + .setPluginId(new SimplePluginId("plugin"))// + .setInitializer((c) -> { + c.addActor((c2) -> { + threadIds.add(Thread.currentThread().getId()); + }); + }).build();// + ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()// + .setThreadCount(6)// + .build(); + + // Run the experiment using several threads + Experiment.builder()// + .addPlugin(plugin)// + .addDimension(dimension1)// + .addDimension(dimension2)// + .setExperimentParameterData(experimentParameterData)// + .build()// + .execute();// + + // We show that more than one thread was used. It is very difficult, + // especially with simulation instances that run very quickly, to reason + // out the number of threads that will be allocated or reused. The best + // we can do is show that the main thread was not used for any + // simulation instance. In practice, only a long running manual test can + // demonstrate that the experiment thread management is working as + // intended. + assertFalse(threadIds.contains(Thread.currentThread().getId())); + } + + @Test + @UnitTestMethod(target = Experiment.class, name = "builder", args = {}) + public void testBuilder() { + // show that a builder is returned + assertNotNull(Experiment.builder()); + } + + @Test + @UnitTestMethod(target = Experiment.class, name = "execute", args = {}) + public void testExecute() { + // Covered by remaining tests that execute the experiment. + } + + private void testSetHaltOnException() { + testSetHaltOnException_Explicit_True(); + testSetHaltOnException_Explicit_False(); + testSetHaltOnException_Implicit(); + } + + @Test + private void testSetHaltOnException_Explicit_True() { + // This test will run the experiment two times in single thread mode + + // we create a counter that will be incremented by each actor on + // initialization + MutableInteger actorInitializationCounter = new MutableInteger(); + MutableObject experimentContext = new MutableObject<>(); + + // we create a dimension with two levels + Dimension dimension = FunctionalDimension.builder()// + .addLevel((c) -> new ArrayList<>())// + .addLevel((c) -> new ArrayList<>())// + .build(); + + // we create a plugin that will instantiate three actors, with the + // second actor throwing a runtime exception + Plugin plugin = Plugin.builder().setPluginId(new SimplePluginId("plugin")).setInitializer((c) -> { + c.addActor((c2) -> { + actorInitializationCounter.increment(); + }); + c.addActor((c2) -> { + actorInitializationCounter.increment(); + throw new RuntimeException(); + }); + c.addActor((c2) -> { + actorInitializationCounter.increment(); + }); + }).build(); + + Consumer experimentContextConsumer = (c) -> { + experimentContext.setValue(c); + }; + + /* + * By setting the haltOnException to true, the simulation should execute both + * scenarios, with each scenario executing only two of the actors before the + * simulation fails. Thus we expect that the counter will be equal to four after + * the experiment executes and that there will be no exception bubbling out of + * the execute() invocation + */ + + ExperimentParameterData experimentParameterData = ExperimentParameterData.builder().setHaltOnException(true) + .build(); + + // Explicitly setting the haltOnException to true + assertThrows(RuntimeException.class, () -> Experiment.builder()// + .addDimension(dimension)// + .addPlugin(plugin)// + .addExperimentContextConsumer(experimentContextConsumer)// + .setExperimentParameterData(experimentParameterData)// + .build()// + .execute()); + + // since the experiment should halt on the first failure, we expect only + // two of the actors to have initialized + assertEquals(2, actorInitializationCounter.getValue()); + + // show that only the first scenario executed and failed. The second + // scenario should still be in READY status. + assertEquals(ScenarioStatus.FAILED, experimentContext.getValue().getScenarioStatus(0).get()); + assertEquals(ScenarioStatus.READY, experimentContext.getValue().getScenarioStatus(1).get()); + } + + private void testSetHaltOnException_Explicit_False() { + // This test will run the experiment two times in single thread mode + + // we create a counter that will be incremented by each actor on + // initialization + MutableInteger actorInitializationCounter = new MutableInteger(); + MutableObject experimentContext = new MutableObject<>(); + + // we create a dimension with two levels + Dimension dimension = FunctionalDimension.builder()// + .addLevel((c) -> new ArrayList<>())// + .addLevel((c) -> new ArrayList<>())// + .build(); + + // we create a plugin that will instantiate three actors, with the + // second actor throwing a runtime exception + Plugin plugin = Plugin.builder().setPluginId(new SimplePluginId("plugin")).setInitializer((c) -> { + c.addActor((c2) -> { + actorInitializationCounter.increment(); + }); + c.addActor((c2) -> { + actorInitializationCounter.increment(); + throw new RuntimeException(); + }); + c.addActor((c2) -> { + actorInitializationCounter.increment(); + }); + }).build(); + + Consumer experimentContextConsumer = (c) -> { + experimentContext.setValue(c); + }; + + ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()// + .setHaltOnException(false)// + .build(); + + // Explicitly setting the haltOnException to false + Experiment.builder()// + .addDimension(dimension)// + .addPlugin(plugin)// + .addExperimentContextConsumer(experimentContextConsumer)// + .setExperimentParameterData(experimentParameterData)// + .build()// + .execute(); + assertEquals(4, actorInitializationCounter.getValue()); + + // show that both scenarios executed and failed + assertEquals(ScenarioStatus.FAILED, experimentContext.getValue().getScenarioStatus(0).get()); + assertEquals(ScenarioStatus.FAILED, experimentContext.getValue().getScenarioStatus(1).get()); + + } + + private void testSetHaltOnException_Implicit() { + // This test will run the experiment two times in single thread mode + + // we create a counter that will be incremented by each actor on + // initialization + MutableInteger actorInitializationCounter = new MutableInteger(); + MutableObject experimentContext = new MutableObject<>(); + + // we create a dimension with two levels + Dimension dimension = FunctionalDimension.builder()// + .addLevel((c) -> new ArrayList<>())// + .addLevel((c) -> new ArrayList<>())// + .build(); + + // we create a plugin that will instantiate three actors, with the + // second actor throwing a runtime exception + Plugin plugin = Plugin.builder().setPluginId(new SimplePluginId("plugin")).setInitializer((c) -> { + c.addActor((c2) -> { + actorInitializationCounter.increment(); + }); + c.addActor((c2) -> { + actorInitializationCounter.increment(); + throw new RuntimeException(); + }); + c.addActor((c2) -> { + actorInitializationCounter.increment(); + }); + }).build(); + + Consumer experimentContextConsumer = (c) -> { + experimentContext.setValue(c); + }; + + /* + * By setting the haltOnException to true, the simulation should execute both + * scenarios, with each scenario executing only two of the actors before the + * simulation fails. Thus we expect that the counter will be equal to four after + * the experiment executes and that there will be no exception bubbling out of + * the execute() invocation + */ + + // Implicitly setting the haltOnException to true + assertThrows(RuntimeException.class, () -> Experiment.builder()// + .addDimension(dimension)// + .addPlugin(plugin)// + .addExperimentContextConsumer(experimentContextConsumer)// + .build()// + .execute()); + + // since the experiment should halt on the first failure, we expect only + // two of the actors to have initialized + assertEquals(2, actorInitializationCounter.getValue()); + + // show that only the first scenario executed and failed. The second + // scenario should still be in READY status. + assertEquals(ScenarioStatus.FAILED, experimentContext.getValue().getScenarioStatus(0).get()); + assertEquals(ScenarioStatus.READY, experimentContext.getValue().getScenarioStatus(1).get()); + + } + + @Test + @UnitTestMethod(target = Experiment.Builder.class, name = "setSimulationState", args = { SimulationState.class }) + public void testSetSimulationState() { + + //set a time for the simulation to start + double expectedStartTime = 456.789; + + //create the simulation state that will dictate the start time + SimulationState simulationState = SimulationState.builder()// + .setStartTime(expectedStartTime)// + .build(); + + /* + * create a plugin that contains an actor. This actor will confirm that the + * simulation starts at the expected time. + */ + Plugin plugin = Plugin.builder().setPluginId(new SimplePluginId("plugin id")).setInitializer((c) -> { + c.addActor((c2) -> { + assertEquals(expectedStartTime, c2.getTime()); + }); + }).build(); + + // execute the experiment + Experiment.builder()// + .addPlugin(plugin)// + .setSimulationState(simulationState)// + .build()// + .execute();// + + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_ExperimentContext.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_ExperimentContext.java new file mode 100644 index 000000000..c80702dda --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_ExperimentContext.java @@ -0,0 +1,724 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.stream.IntStream; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.util.TriConsumer; +import util.annotations.UnitTestMethod; +import util.wrappers.MultiKey; +import util.wrappers.MutableDouble; +import util.wrappers.MutableInteger; + +public class AT_ExperimentContext { + + @Test + @UnitTestMethod(target = ExperimentContext.class, name = "getElapsedSeconds", args = {}) + public void testGetElapsedSeconds() { + /* + * This is a very limited test of the elapsed time and requires a manual + * test to perform a more robust test. + */ + MutableDouble elapsedSeconds = new MutableDouble(); + Experiment .builder()// + .addExperimentContextConsumer(c -> { + elapsedSeconds.setValue(c.getElapsedSeconds()); + }).build()// + .execute();// + + // show that some time elapsed + assertTrue(elapsedSeconds.getValue() > 0); + } + + @Test + @UnitTestMethod(target = ExperimentContext.class, name = "getExperimentMetaData", args = {}) + public void testGetExperimentMetaData() { + + List actualMetaData = new ArrayList<>(); + Experiment .builder()// + .addExperimentContextConsumer(c -> { + actualMetaData.addAll(c.getExperimentMetaData()); + }).build()// + .execute();// + + // show that the meta data is empty in the absence of dimensions + assertTrue(actualMetaData.isEmpty()); + + // create an experiment with two dimensions with some experiment meta + // data + Dimension dimension1 = FunctionalDimension .builder()// + .addMetaDatum("A")// + .addMetaDatum("B")// + .addLevel((c) -> { + List result = new ArrayList<>(); + result.add("a"); + result.add("b"); + return result; + })// + .build(); + Dimension dimension2 = FunctionalDimension .builder()// + .addMetaDatum("C")// + .addMetaDatum("D")// + .build(); + + Dimension dimension3 = FunctionalDimension .builder()// + .addMetaDatum("E")// + .addMetaDatum("F")// + .addLevel((c) -> { + List result = new ArrayList<>(); + result.add("e"); + result.add("f"); + return result; + })// + .build(); + + Experiment .builder()// + .addDimension(dimension1)// + .addDimension(dimension2)// + .addDimension(dimension3)// + .addExperimentContextConsumer(c -> { + // this should execute exactly once since there is one + // scenario + actualMetaData.addAll(c.getExperimentMetaData()); + }).build()// + .execute();// + + /* + * The meta data should be added in the order the meta data were added + * to the dimension and the order that the dimensions were added to the + * experiment. Empty dimensions should be ignored. + */ + List expectedMetaData = new ArrayList<>(); + expectedMetaData.add("A"); + expectedMetaData.add("B"); + expectedMetaData.add("E"); + expectedMetaData.add("F"); + + assertEquals(expectedMetaData, actualMetaData); + + /* + * Show that a different ordering of the dimensions works as expected + */ + + actualMetaData.clear(); + expectedMetaData.clear(); + expectedMetaData.add("E"); + expectedMetaData.add("F"); + expectedMetaData.add("A"); + expectedMetaData.add("B"); + + Experiment .builder()// + .addDimension(dimension3)// + .addDimension(dimension2)// + .addDimension(dimension1)// + .addExperimentContextConsumer(c -> { + actualMetaData.addAll(c.getExperimentMetaData()); + }).build()// + .execute();// + + assertEquals(expectedMetaData, actualMetaData); + + } + + @Test + @UnitTestMethod(target = ExperimentContext.class, name = "getScenarioCount", args = {}) + public void testGetScenarioCount() { + + // create an experiment that has no dimension and show that the scenario + // count is 1 + MutableInteger scenarioCount = new MutableInteger(); + + Experiment .builder()// + .addExperimentContextConsumer((c) -> { + scenarioCount.setValue(c.getScenarioCount()); + })// + .build()// + .execute();// + assertEquals(1, scenarioCount.getValue()); + + // create an experiment with two dimensions, having 10 and 7 levels each + FunctionalDimension.Builder dimBuilder = FunctionalDimension.builder().addMetaDatum("A").addMetaDatum("B"); + IntStream.range(0, 10).forEach((i) -> { + dimBuilder.addLevel((context) -> { + List result = new ArrayList<>(); + result.add(Integer.toString(i)); + result.add(Integer.toString(3 * i)); + return result; + }); + }); + Dimension dimension1 = dimBuilder.build(); + + FunctionalDimension.Builder dimBuilder2 = FunctionalDimension.builder(); + dimBuilder2.addMetaDatum("X"); + IntStream.range(0, 7).forEach((i) -> { + dimBuilder2.addLevel((context) -> { + List result = new ArrayList<>(); + result.add(Integer.toString(i * i)); + return result; + }); + }); + Dimension dimension2 = dimBuilder2.build(); + + // execute the experiment + Experiment .builder()// + .addDimension(dimension1)// + .addDimension(dimension2)// + .addExperimentContextConsumer((c) -> { + scenarioCount.setValue(c.getScenarioCount()); + })// + .build()// + .execute();// + + // show that the experiment has the expected number of scenarios + assertEquals(70, scenarioCount.getValue()); + } + + @Test + @UnitTestMethod(target = ExperimentContext.class, name = "getScenarioMetaData", args = { int.class }) + public void testGetScenarioMetaData() { + + // create an experiment with two dimensions with some experiment meta + // data + FunctionalDimension.Builder dimBuilder = FunctionalDimension.builder().addMetaDatum("A").addMetaDatum("B"); + IntStream.range(0, 10).forEach((i) -> { + dimBuilder.addLevel((context) -> { + List result = new ArrayList<>(); + result.add(Integer.toString(i)); + result.add(Integer.toString(3 * i)); + return result; + }); + }); + Dimension dimension1 = dimBuilder.build(); + + FunctionalDimension.Builder dimBuilder2 = FunctionalDimension.builder(); + dimBuilder2.addMetaDatum("X"); + IntStream.range(0, 7).forEach((i) -> { + dimBuilder2.addLevel((context) -> { + List result = new ArrayList<>(); + result.add(Integer.toString(i * i)); + return result; + }); + }); + Dimension dimension2 = dimBuilder2.build(); + + /* + * Create a plugin that will put a single actor into the simulation and + * have that actor release an object to output + */ + Plugin plugin = Plugin .builder()// + .setPluginId(new SimplePluginId("plugin"))// + .setInitializer((c) -> { + c.addActor((c2) -> { + c2.releaseOutput(new Object()); + });// + }).build();// + + /* + * Execute an experiment from the dimension and an output handler that + * will be stimulated exactly once per scenario and record the + * corresponding scenario meta data + */ + + // make a container for the collected scenario id and scenario meta data + // information + Set combinedMetaData = new LinkedHashSet<>(); + Experiment .builder()// + .addPlugin(plugin)// + .addDimension(dimension1)// + .addDimension(dimension2)// + .addExperimentContextConsumer(c -> { + c.subscribeToOutput(Object.class, (c2, s, e) -> { + MultiKey.Builder builder = MultiKey.builder(); + builder.addKey(s); + for (String metaDatum : c2.getScenarioMetaData(s)) { + builder.addKey(Integer.parseInt(metaDatum)); + } + combinedMetaData.add(builder.build()); + }); + })// + .build()// + .execute();// + + /* + * There is no black-box contract provided by the experiment that any + * particular scenario id will be associated with any particular values + * from the dimensions. + */ + + // First, we will show that the scenario ids were 0 to 69, inclusive + assertEquals(70, combinedMetaData.size()); + Set observedScenarioIds = new LinkedHashSet<>(); + for (MultiKey multiKey : combinedMetaData) { + Integer scenarioId = multiKey.getKey(0); + assertTrue(scenarioId >= 0); + assertTrue(scenarioId < 70); + observedScenarioIds.add(scenarioId); + } + assertEquals(70, observedScenarioIds.size()); + + // Next we will create a container for the expected meta data + Set expectedMetaData = new LinkedHashSet<>(); + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 7; j++) { + expectedMetaData.add(new MultiKey(i, 3 * i, j * j)); + } + } + + // We derive the actual meta data by dropping the scenario ids from the + // combinedMetaData + Set actualMetaData = new LinkedHashSet<>(); + for (MultiKey multiKey : combinedMetaData) { + actualMetaData.add(new MultiKey(multiKey.getKey(1), multiKey.getKey(2), multiKey.getKey(3))); + } + + assertEquals(expectedMetaData, actualMetaData); + + } + + @Test + @UnitTestMethod(target = ExperimentContext.class, name = "getScenarios", args = { ScenarioStatus.class }) + public void testGetScenarios() { + // create an experiment with two dimensions with some experiment meta + // data + FunctionalDimension.Builder dimBuilder = FunctionalDimension.builder()// + .addMetaDatum("A")// + .addMetaDatum("B"); + IntStream.range(0, 10).forEach((i) -> { + dimBuilder.addLevel((context) -> { + List result = new ArrayList<>(); + result.add(Integer.toString(i)); + result.add(Integer.toString(3 * i)); + return result; + }); + }); + Dimension dimension1 = dimBuilder.build(); + + FunctionalDimension.Builder dimBuilder2 = FunctionalDimension.builder(); + dimBuilder2.addMetaDatum("X"); + IntStream.range(0, 7).forEach((i) -> { + dimBuilder2.addLevel((context) -> { + List result = new ArrayList<>(); + result.add(Integer.toString(i * i)); + return result; + }); + }); + Dimension dimension2 = dimBuilder2.build(); + + /* + * Create a plugin that will put a single actor into the simulation and + * have that actor release the scenario id to output + */ + Plugin plugin = Plugin .builder()// + .setPluginId(new SimplePluginId("plugin"))// + .setInitializer((c) -> { + c.addActor((c2) -> { + c2.releaseOutput(new Object()); + });// + }).build();// + + /* + * Execute an experiment from the dimension and an output handler that + * will be stimulated exactly once per scenario and record the scenario + * id + */ + + // make a container for the collected scenario id values + Set actualScenarioIds = new LinkedHashSet<>(); + + // execute the experiment + Experiment .builder()// + .addPlugin(plugin)// + .addDimension(dimension1)// + .addDimension(dimension2)// + .addExperimentContextConsumer(c -> { + c.subscribeToOutput(Object.class, (c2, s, e) -> { + actualScenarioIds.add(s); + }); + }).build()// + .execute();// + + // show that the scenarios were as expected + Set expectedScenarioIds = new LinkedHashSet<>(); + for (int i = 0; i < 70; i++) { + expectedScenarioIds.add(i); + } + + assertEquals(expectedScenarioIds, actualScenarioIds); + + } + + /* + * An enum support scenario status tests + */ + private static enum StatusPhase { + EXP_OPEN, EXP_CLOSE, SIM_OPEN, SIM_CLOSE; + } + + @Test + @UnitTestMethod(target = ExperimentContext.class, name = "getScenarioStatus", args = { int.class }) + public void testGetScenarioStatus() { + + /* + * Create a container for the scenario id, ASSUMING that the experiment + * executes scenarios in scenario id order, starting from zero. The + * plugin instance that initializes the simulation does not have access + * to the scenario id. + */ + MutableInteger expectedScenarioId = new MutableInteger(); + + // create a plugin that will cause an exception to be thrown during + // plugin initialization for even-numbered scenarios. + Plugin plugin = Plugin .builder()// + .setPluginId(new SimplePluginId("plugin"))// + .setInitializer((c) -> { + int value = expectedScenarioId.getValue(); + expectedScenarioId.increment(); + if (value % 2 == 0) { + throw new RuntimeException(); + } + }).build();// + + // Create a dimension with integer values. We expect these values to + // match the scenario id. + FunctionalDimension.Builder dimBuilder = FunctionalDimension.builder().addMetaDatum("value"); + IntStream.range(0, 10).forEach((i) -> { + dimBuilder.addLevel((context) -> { + List result = new ArrayList<>(); + result.add(Integer.toString(i)); + return result; + }); + }); + Dimension dimension = dimBuilder.build(); + + // create a container for observed scenario status values + Set observedScenarioStatusInfo = new LinkedHashSet<>(); + + ExperimentParameterData experimentParameterData = ExperimentParameterData .builder()// + .setHaltOnException(false)// + .build(); + + /* + * Execute an experiment that uses the dimension and plugin. Add an + * output handler that will record the status of each scenario at each + * of the phases of the simulation and experiment. Turn off console + * notification of scenario failures. + */ + Experiment .builder()// + .addDimension(dimension)// + .addPlugin(plugin)// + .addExperimentContextConsumer((c) -> { + c.subscribeToExperimentOpen((c2) -> { + int scenarioCount = c2.getScenarioCount(); + for (int scenarioId = 0; scenarioId < scenarioCount; scenarioId++) { + ScenarioStatus scenarioStatus = c2.getScenarioStatus(scenarioId).get(); + observedScenarioStatusInfo.add(new MultiKey(StatusPhase.EXP_OPEN, scenarioId, scenarioStatus)); + } + }); + c.subscribeToSimulationOpen((c2, s) -> { + ScenarioStatus scenarioStatus = c2.getScenarioStatus(s).get(); + observedScenarioStatusInfo.add(new MultiKey(StatusPhase.SIM_OPEN, s, scenarioStatus)); + }); + c.subscribeToSimulationClose((c2, s) -> { + ScenarioStatus scenarioStatus = c2.getScenarioStatus(s).get(); + observedScenarioStatusInfo.add(new MultiKey(StatusPhase.SIM_CLOSE, s, scenarioStatus)); + }); + c.subscribeToExperimentClose((c2) -> { + int scenarioCount = c2.getScenarioCount(); + Map> scenariosByStatus = new LinkedHashMap<>(); + for (ScenarioStatus scenarioStatus : ScenarioStatus.values()) { + scenariosByStatus.put(scenarioStatus, new LinkedHashSet<>()); + } + for (int scenarioId = 0; scenarioId < scenarioCount; scenarioId++) { + ScenarioStatus scenarioStatus = c2.getScenarioStatus(scenarioId).get(); + observedScenarioStatusInfo.add(new MultiKey(StatusPhase.EXP_CLOSE, scenarioId, scenarioStatus)); + scenariosByStatus.get(scenarioStatus).add(scenarioId); + } + + /* + * show that the scenarios retrieved by status match + * expectations + */ + for (ScenarioStatus scenarioStatus : ScenarioStatus.values()) { + Set actualScenarios = new LinkedHashSet<>(c2.getScenarios(scenarioStatus)); + assertEquals(scenariosByStatus.get(scenarioStatus), actualScenarios); + } + + }); + })// + .setExperimentParameterData(experimentParameterData)// + .build()// + .execute();// + + /* + * Build the expected observations + */ + + Set expectedScenarioStatusInfo = new LinkedHashSet<>(); + for (int i = 0; i < dimension.levelCount(); i++) { + ScenarioStatus finalStatus = ScenarioStatus.SUCCEDED; + if (i % 2 == 0) { + finalStatus = ScenarioStatus.FAILED; + } + expectedScenarioStatusInfo.add(new MultiKey(StatusPhase.EXP_OPEN, i, ScenarioStatus.READY)); + expectedScenarioStatusInfo.add(new MultiKey(StatusPhase.SIM_OPEN, i, ScenarioStatus.RUNNING)); + expectedScenarioStatusInfo.add(new MultiKey(StatusPhase.SIM_CLOSE, i, finalStatus)); + expectedScenarioStatusInfo.add(new MultiKey(StatusPhase.EXP_CLOSE, i, finalStatus)); + } + + // show that the observations of scenario status match expectations. + assertEquals(expectedScenarioStatusInfo, observedScenarioStatusInfo); + + } + + @Test + @UnitTestMethod(target = ExperimentContext.class, name = "getStatusCount", args = { ScenarioStatus.class }) + public void testGetStatusCount() { + // covered by the test method : testScenarioStatus() + } + + @Test + @UnitTestMethod(target = ExperimentContext.class, name = "subscribeToExperimentClose", args = { Consumer.class }) + public void testSubscribeToExperimentClose() { + /* + * Run an experiment that has several clients of the experiment context + * subscribe to experiment open and show that each one is stimulated + * correctly. + */ + MutableInteger simulationCloseExperimentCount = new MutableInteger(); + int expectedCloseExperimentCount = 5; + + /* + * Begin building the experiment + */ + Experiment.Builder builder = Experiment.builder();// + + /* + * Add the output handlers that will subscribe to the close of the + * experiment and respond by incrementing a counter + */ + for (int i = 0; i < expectedCloseExperimentCount; i++) { + builder.addExperimentContextConsumer((c) -> { + c.subscribeToExperimentClose((c2) -> { + simulationCloseExperimentCount.increment(); + }); + });// + } + + // execute the experiment + builder .build()// + .execute();// + + // show the subscribers did observe the opening of the simulation + assertEquals(expectedCloseExperimentCount, simulationCloseExperimentCount.getValue()); + } + + @Test + @UnitTestMethod(target = ExperimentContext.class, name = "subscribeToExperimentOpen", args = { Consumer.class }) + public void testSubscribeToExperimentOpen() { + /* + * Run an experiment that has several clients of the experiment context + * subscribe to experiment open and show that each one is stimulated + * correctly. + */ + MutableInteger simulationOpenExperimentCount = new MutableInteger(); + int expectedOpenExperimentCount = 5; + + /* + * Begin building the experiment + */ + Experiment.Builder builder = Experiment.builder();// + + /* + * Add the output handlers that will subscribe to the opening of the + * experiment and respond by incrementing a counter + */ + for (int i = 0; i < expectedOpenExperimentCount; i++) { + builder.addExperimentContextConsumer((c) -> { + c.subscribeToExperimentOpen((c2) -> { + simulationOpenExperimentCount.increment(); + }); + });// + } + + // execute the experiment + builder .build()// + .execute();// + + // show the subscribers did observe the opening of the simulation + assertEquals(expectedOpenExperimentCount, simulationOpenExperimentCount.getValue()); + } + + @Test + @UnitTestMethod(target = ExperimentContext.class, name = "subscribeToOutput", args = { Class.class, TriConsumer.class }) + public void testSubscribeToOutput() { + + /* + * Create a plugin that will add a single actor that in turn releases a + * few integers, strings and doubles as output + */ + Plugin plugin = Plugin .builder()// + .setPluginId(new SimplePluginId("plugin"))// + .setInitializer((c) -> { + c.addActor((c2) -> { + c2.releaseOutput(45); + c2.releaseOutput("alpha"); + c2.releaseOutput(16); + c2.releaseOutput("beta"); + c2.releaseOutput(2.0345); + }); + })// + .build();// + + /* + * Execute an experiment having two output handlers for integers and + * strings, but not doubles + */ + Set observedOutput = new LinkedHashSet<>(); + Experiment .builder()// + .addPlugin(plugin)// + .addExperimentContextConsumer((c) -> { + c.subscribeToOutput(Integer.class, (c2, s, o) -> { + observedOutput.add(new MultiKey("int handler", o)); + }); + })// + .addExperimentContextConsumer((c) -> { + c.subscribeToOutput(String.class, (c2, s, o) -> { + observedOutput.add(new MultiKey("string handler", o)); + }); + })// + .build()// + .execute();// + + // show that the handlers received the expected output + Set expectedOutput = new LinkedHashSet<>(); + expectedOutput.add(new MultiKey("int handler", 45)); + expectedOutput.add(new MultiKey("int handler", 16)); + expectedOutput.add(new MultiKey("string handler", "alpha")); + expectedOutput.add(new MultiKey("string handler", "beta")); + + assertEquals(expectedOutput, observedOutput); + + } + + @Test + @UnitTestMethod(target = ExperimentContext.class, name = "subscribeToSimulationClose", args = { BiConsumer.class }) + public void testSubscribeToSimulationClose() { + /* + * Run an experiment that has several clients of the experiment context + * subscribe to simulation close and show that each one is stimulated + * correctly. + */ + MutableInteger simulationCloseObservationCount = new MutableInteger(); + int expectedCloseObservationCount = 5; + + /* + * Begin building the experiment + */ + Experiment.Builder builder = Experiment.builder();// + + /* + * Add the output handlers that will subscribe to the opening of the + * simulation and respond by incrementing a counter + */ + for (int i = 0; i < expectedCloseObservationCount; i++) { + builder.addExperimentContextConsumer((c) -> { + c.subscribeToSimulationClose((c2, s) -> { + simulationCloseObservationCount.increment(); + }); + });// + } + + // execute the experiment + builder .build()// + .execute();// + + // show the subscribers did observe the opening of the simulation + assertEquals(expectedCloseObservationCount, simulationCloseObservationCount.getValue()); + } + + @Test + @UnitTestMethod(target = ExperimentContext.class, name = "subscribeToSimulationOpen", args = { BiConsumer.class }) + public void testSubscribeToSimulationOpen() { + + /* + * Run an experiment that has several clients of the experiment context + * subscribe to simulation open and show that each one is stimulated + * correctly. + */ + MutableInteger simulationOpenObservationCount = new MutableInteger(); + int expectedOpenObservationCount = 5; + + /* + * Begin building the experiment + */ + Experiment.Builder builder = Experiment.builder();// + + /* + * Add the output handlers that will subscribe to the opening of the + * simulation and respond by incrementing a counter + */ + for (int i = 0; i < expectedOpenObservationCount; i++) { + builder.addExperimentContextConsumer((c) -> { + c.subscribeToSimulationOpen((c2, s) -> { + simulationOpenObservationCount.increment(); + }); + });// + } + + // execute the experiment + builder .build()// + .execute();// + + // show the subscribers did observe the opening of the simulation + assertEquals(expectedOpenObservationCount, simulationOpenObservationCount.getValue()); + } + + @Test + @UnitTestMethod(target = ExperimentContext.class, name = "getScenarioFailureCause", args = { int.class }) + public void testGetScenarioFailureCause() { + RuntimeException e = new RuntimeException(); + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + throw e; + })); + Plugin testPlugin = TestPlugin.getTestPlugin(pluginDataBuilder.build()); + + Consumer experimentContestConsumer = (c) -> { + c.subscribeToExperimentClose((c2) -> { + Optional optional = c2.getScenarioFailureCause(0); + assertTrue(optional.isPresent()); + Exception e2 = optional.get(); + assertEquals(e, e2); + }); + }; + + ExperimentParameterData experimentParameterData = ExperimentParameterData .builder()// + .setHaltOnException(false)// + .build(); + + Experiment .builder()// + .addPlugin(testPlugin)// + .addExperimentContextConsumer(experimentContestConsumer)// + .setExperimentParameterData(experimentParameterData)// + .build()// + .execute(); + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_ExperimentParameterData.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_ExperimentParameterData.java new file mode 100644 index 000000000..30873288a --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_ExperimentParameterData.java @@ -0,0 +1,461 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTag; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_ExperimentParameterData { + + @Test + @UnitTestMethod(target = ExperimentParameterData.Builder.class, name = "build", args = {}) + public void testBuild() { + ExperimentParameterData experimentParameterData = ExperimentParameterData.builder().build(); + assertNotNull(experimentParameterData); + + // default values are covered by the other tests + } + + @Test + @UnitTestMethod(target = ExperimentParameterData.Builder.class, name = "setExperimentProgressLog", args = { + Path.class }, tags = { UnitTag.MANUAL }) + public void testSetExperimentProgressLog() { + // if no path is specified + ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()// + .build(); + + Optional optional = experimentParameterData.getExperimentProgressLogPath(); + assertFalse(optional.isPresent()); + + // if a path is specified + for (int i = 0; i < 10; i++) { + String pathName = "somePath_" + i; + Path expectedPath = Paths.get(pathName); + + experimentParameterData = ExperimentParameterData.builder()// + .setExperimentProgressLog(expectedPath)// + .build(); + optional = experimentParameterData.getExperimentProgressLogPath(); + assertTrue(optional.isPresent()); + Path actualPath = optional.get(); + assertEquals(expectedPath, actualPath); + } + } + + @Test + @UnitTestMethod(target = ExperimentParameterData.Builder.class, name = "setContinueFromProgressLog", args = { + boolean.class }, tags = { UnitTag.MANUAL }) + public void testSetContinueFromProgressLog() { + // if the policy is not set + ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()// + .build(); + + assertFalse(experimentParameterData.continueFromProgressLog()); + + // if the policy is set to false + experimentParameterData = ExperimentParameterData.builder()// + .setContinueFromProgressLog(false)// + .build(); + + assertFalse(experimentParameterData.continueFromProgressLog()); + + // if the policy is set to true + experimentParameterData = ExperimentParameterData.builder()// + .setContinueFromProgressLog(true)// + .build(); + + assertTrue(experimentParameterData.continueFromProgressLog()); + + } + + @Test + @UnitTestMethod(target = ExperimentParameterData.Builder.class, name = "setThreadCount", args = { int.class }) + public void testSetThreadCount() { + + for (int i = 0; i < 10; i++) { + int expectedThreadCount = i; + + ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()// + .setThreadCount(expectedThreadCount)// + .build(); + + int actualThreadCount = experimentParameterData.getThreadCount(); + assertEquals(expectedThreadCount, actualThreadCount); + } + + // precondition test: if the thread count is negative + ContractException contractException = assertThrows(ContractException.class, + () -> ExperimentParameterData.builder().setThreadCount(-1).build()); + assertEquals(NucleusError.NEGATIVE_THREAD_COUNT, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ExperimentParameterData.class, name = "builder", args = {}) + public void testBuilder() { + assertNotNull(ExperimentParameterData.builder()); + } + + @Test + @UnitTestMethod(target = ExperimentParameterData.Builder.class, name = "setHaltOnException", args = { + boolean.class }) + public void testSetHaltOnException() { + ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()// + .setHaltOnException(true)// + .build(); + + assertTrue(experimentParameterData.haltOnException()); + + experimentParameterData = ExperimentParameterData.builder()// + .setHaltOnException(false)// + .build(); + + assertFalse(experimentParameterData.haltOnException()); + } + + @Test + @UnitTestMethod(target = ExperimentParameterData.Builder.class, name = "addExplicitScenarioId", args = { + Integer.class }) + public void testAddExplicitScenarioId() { + // if no scenarios are added + ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()// + .build(); + Set expectedScenarioIds = new LinkedHashSet<>(); + Set actualScenarioIds = experimentParameterData.getExplicitScenarioIds(); + assertEquals(expectedScenarioIds, actualScenarioIds); + + // if some scenarios are added + experimentParameterData = ExperimentParameterData.builder()// + .addExplicitScenarioId(12)// + .addExplicitScenarioId(45)// + .addExplicitScenarioId(12)// + .addExplicitScenarioId(11)// + .addExplicitScenarioId(-3)// + .addExplicitScenarioId(45)// + .build(); + + expectedScenarioIds.add(-3); + expectedScenarioIds.add(11); + expectedScenarioIds.add(12); + expectedScenarioIds.add(45); + + actualScenarioIds = experimentParameterData.getExplicitScenarioIds(); + assertEquals(expectedScenarioIds, actualScenarioIds); + + } + + @Test + @UnitTestMethod(target = ExperimentParameterData.Builder.class, name = "setRecordState", args = { boolean.class }) + public void testSetRecordState() { + ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()// + .setRecordState(true)// + .build(); + + assertTrue(experimentParameterData.haltOnException()); + + experimentParameterData = ExperimentParameterData.builder()// + .setRecordState(false)// + .build(); + + assertFalse(experimentParameterData.stateRecordingIsScheduled()); + } + + @Test + @UnitTestMethod(target = ExperimentParameterData.Builder.class, name = "setSimulationHaltTime", args = { + Double.class }) + public void testSetSimulationHaltTime() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4483845375009238350L); + + // if the scenario halt time is not set + ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()// + .build(); + + Optional optional = experimentParameterData.getSimulationHaltTime(); + assertTrue(optional.isEmpty()); + + for (int i = 0; i < 10; i++) { + double expectedTime = randomGenerator.nextDouble() - 0.5; + experimentParameterData = ExperimentParameterData.builder()// + .setSimulationHaltTime(expectedTime)// + .build(); + + optional = experimentParameterData.getSimulationHaltTime(); + assertTrue(optional.isPresent()); + double actualTime = optional.get(); + + assertEquals(expectedTime, actualTime); + } + + } + + @Test + @UnitTestMethod(target = ExperimentParameterData.class, name = "continueFromProgressLog", args = {}) + public void testContinueFromProgressLog() { + // if the policy is not set + ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()// + .build(); + + assertFalse(experimentParameterData.continueFromProgressLog()); + + // if the policy is set to false + experimentParameterData = ExperimentParameterData.builder()// + .setContinueFromProgressLog(false)// + .build(); + + assertFalse(experimentParameterData.continueFromProgressLog()); + + // if the policy is set to true + experimentParameterData = ExperimentParameterData.builder()// + .setContinueFromProgressLog(true)// + .build(); + + assertTrue(experimentParameterData.continueFromProgressLog()); + } + + @Test + @UnitTestMethod(target = ExperimentParameterData.class, name = "getExperimentProgressLogPath", args = {}) + public void testGetExperimentProgressLogPath() { + // if no path is specified + ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()// + .build(); + + Optional optional = experimentParameterData.getExperimentProgressLogPath(); + assertFalse(optional.isPresent()); + + // if a path is specified + for (int i = 0; i < 10; i++) { + String pathName = "somePath_" + i; + Path expectedPath = Paths.get(pathName); + + experimentParameterData = ExperimentParameterData.builder()// + .setExperimentProgressLog(expectedPath)// + .build(); + optional = experimentParameterData.getExperimentProgressLogPath(); + assertTrue(optional.isPresent()); + Path actualPath = optional.get(); + assertEquals(expectedPath, actualPath); + } + } + + @Test + @UnitTestMethod(target = ExperimentParameterData.class, name = "getExplicitScenarioIds", args = {}) + public void testGetExplicitScenarioIds() { + // if no scenarios are added + ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()// + .build(); + Set expectedScenarioIds = new LinkedHashSet<>(); + Set actualScenarioIds = experimentParameterData.getExplicitScenarioIds(); + assertEquals(expectedScenarioIds, actualScenarioIds); + + // if some scenarios are added + experimentParameterData = ExperimentParameterData.builder()// + .addExplicitScenarioId(12)// + .addExplicitScenarioId(45)// + .addExplicitScenarioId(12)// + .addExplicitScenarioId(11)// + .addExplicitScenarioId(-3)// + .addExplicitScenarioId(45)// + .build(); + + expectedScenarioIds.add(-3); + expectedScenarioIds.add(11); + expectedScenarioIds.add(12); + expectedScenarioIds.add(45); + + actualScenarioIds = experimentParameterData.getExplicitScenarioIds(); + assertEquals(expectedScenarioIds, actualScenarioIds); + } + + @Test + @UnitTestMethod(target = ExperimentParameterData.class, name = "getSimulationHaltTime", args = {}) + public void testGetSimulationHaltTime() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4483845375009238350L); + + // if the scenario halt time is not set + ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()// + .build(); + + Optional optional = experimentParameterData.getSimulationHaltTime(); + assertTrue(optional.isEmpty()); + + for (int i = 0; i < 10; i++) { + double expectedTime = randomGenerator.nextDouble() - 0.5; + experimentParameterData = ExperimentParameterData.builder()// + .setSimulationHaltTime(expectedTime)// + .build(); + + optional = experimentParameterData.getSimulationHaltTime(); + assertTrue(optional.isPresent()); + double actualTime = optional.get(); + + assertEquals(expectedTime, actualTime); + } + } + + @Test + @UnitTestMethod(target = ExperimentParameterData.class, name = "getThreadCount", args = {}) + public void testGetThreadCount() { + + ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()// + .build(); + + assertEquals(0, experimentParameterData.getThreadCount()); + + for (int i = 0; i < 10; i++) { + int expectedThreadCount = i; + + experimentParameterData = ExperimentParameterData.builder()// + .setThreadCount(expectedThreadCount)// + .build(); + + int actualThreadCount = experimentParameterData.getThreadCount(); + assertEquals(expectedThreadCount, actualThreadCount); + } + + } + + @Test + @UnitTestMethod(target = ExperimentParameterData.class, name = "haltOnException", args = {}) + public void testHaltOnException() { + + ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()// + .build(); + assertTrue(experimentParameterData.haltOnException()); + + experimentParameterData = ExperimentParameterData.builder()// + .setHaltOnException(true)// + .build(); + + assertTrue(experimentParameterData.haltOnException()); + + experimentParameterData = ExperimentParameterData.builder()// + .setHaltOnException(false)// + .build(); + + assertFalse(experimentParameterData.haltOnException()); + + } + + @Test + @UnitTestMethod(target = ExperimentParameterData.class, name = "stateRecordingIsScheduled", args = {}) + public void testStateRecordingIsScheduled() { + ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()// + .build(); + + assertFalse(experimentParameterData.stateRecordingIsScheduled()); + + experimentParameterData = ExperimentParameterData.builder()// + .setRecordState(true)// + .build(); + + assertTrue(experimentParameterData.stateRecordingIsScheduled()); + + experimentParameterData = ExperimentParameterData.builder()// + .setRecordState(false)// + .build(); + + assertFalse(experimentParameterData.stateRecordingIsScheduled()); + } + + private ExperimentParameterData getRandomExperimentParameterData(long seed) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + + ExperimentParameterData.Builder builder = ExperimentParameterData.builder(); + if (randomGenerator.nextBoolean()) { + builder.addExplicitScenarioId(randomGenerator.nextInt(1000)); + } + builder.setContinueFromProgressLog(randomGenerator.nextBoolean()); + if (randomGenerator.nextBoolean()) { + builder.setExperimentProgressLog(Paths.get("")); + } + builder.setHaltOnException(randomGenerator.nextBoolean()); + builder.setRecordState(randomGenerator.nextBoolean()); + builder.setSimulationHaltTime(randomGenerator.nextDouble()); + builder.setThreadCount(randomGenerator.nextInt(8)); + + return builder.build(); + } + + @Test + @UnitTestMethod(target = ExperimentParameterData.class, name = "equals", args = { Object.class }) + public void testEquals() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2179495435117370503L); + + // never equal null + for (int i = 0; i < 30; i++) { + ExperimentParameterData experimentParameterData = getRandomExperimentParameterData( + randomGenerator.nextLong()); + assertFalse(experimentParameterData.equals(null)); + } + + // reflexive + for (int i = 0; i < 30; i++) { + ExperimentParameterData experimentParameterData = getRandomExperimentParameterData( + randomGenerator.nextLong()); + assertTrue(experimentParameterData.equals(experimentParameterData)); + } + + // symmetric, transitive, consistent + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + ExperimentParameterData experimentParameterData1 = getRandomExperimentParameterData(seed); + ExperimentParameterData experimentParameterData2 = getRandomExperimentParameterData(seed); + for (int j = 0; j < 5; j++) { + assertTrue(experimentParameterData1.equals(experimentParameterData2)); + assertTrue(experimentParameterData2.equals(experimentParameterData1)); + } + } + + // different inputs yields non-equal objects + Set experimentParameterDatas = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + ExperimentParameterData experimentParameterData = getRandomExperimentParameterData( + randomGenerator.nextLong()); + experimentParameterDatas.add(experimentParameterData); + } + assertEquals(100, experimentParameterDatas.size()); + + } + + @Test + @UnitTestMethod(target = ExperimentParameterData.class, name = "hashCode", args = {}) + public void testHashCode() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6135009215375700684L); + /* + * equals objects have equal hash codes symmetric, transitive, consistent + */ + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + ExperimentParameterData experimentParameterData1 = getRandomExperimentParameterData(seed); + ExperimentParameterData experimentParameterData2 = getRandomExperimentParameterData(seed); + + assertEquals(experimentParameterData1, experimentParameterData2); + assertEquals(experimentParameterData1.hashCode(), experimentParameterData2.hashCode()); + } + + //hash codes are reasonably distributed + Set hashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + ExperimentParameterData experimentParameterData = getRandomExperimentParameterData( + randomGenerator.nextLong()); + hashCodes.add(experimentParameterData.hashCode()); + } + assertEquals(100, hashCodes.size()); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_ExperimentStateManager.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_ExperimentStateManager.java new file mode 100644 index 000000000..272aec3c4 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_ExperimentStateManager.java @@ -0,0 +1,1170 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Consumer; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.wrappers.MutableBoolean; +import util.wrappers.MutableInteger; + +public class AT_ExperimentStateManager { + + private final static Path PROGRESS_LOG_PATH = Paths.get("src/test/resources/nucleus/progress_log.txt"); + private final static Path NONEXISTENT_PROGRESS_LOG_PATH = Paths + .get("src/test/resources/nucleus/nonexistentfile.txt"); + private final static Path NONFILE_PROGRESS_LOG_PATH = Paths.get("src/test/resources/nucleus"); + + /* + * Tests that the set of scenario ids found in the progress log matches the + * given expected scenario ids. + */ + private static void testProgressLogContents(Set expectedScenarioIds) { + Set actualScenarioIds = new LinkedHashSet<>(); + try { + List lines = Files.readAllLines(PROGRESS_LOG_PATH); + for (int i = 1; i < lines.size(); i++) { + int scenarioId = Integer.parseInt(lines.get(i)); + actualScenarioIds.add(scenarioId); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + + assertEquals(expectedScenarioIds, actualScenarioIds); + } + + /* + * Clears the progress log in the test resources folder + */ + private static void clearProgressLog() { + try { + + /* + * Remove the old file and write to the file the header and any retained lines + * from the previous execution. + */ + + Files.deleteIfExists(PROGRESS_LOG_PATH); + Files.createFile(PROGRESS_LOG_PATH); + // CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder(); + // OutputStream out = Files.newOutputStream(path, StandardOpenOption.CREATE); + // writer = new BufferedWriter(new OutputStreamWriter(out, encoder)); + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /* + * Builds the progress log from the expected scenario ids without any meta data + */ + private static void initializeProgressLog(Set expectedScenarioIds) { + try { + List scenarioIds = new ArrayList<>(); + scenarioIds.add("scenario"); + for (Integer scenarioId : expectedScenarioIds) { + scenarioIds.add(Integer.toString(scenarioId)); + } + Files.write(PROGRESS_LOG_PATH, scenarioIds); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /* + * Builds the progress log from the expected scenario ids with two columns of + * meta data, A and B + */ + private static void initializeProgressLogWithMetaData(Set expectedScenarioIds) { + try { + List lines = new ArrayList<>(); + StringBuilder sb = new StringBuilder(); + sb.append("scenario"); + sb.append("\t"); + sb.append("A"); + sb.append("\t"); + sb.append("B"); + lines.add(sb.toString()); + + for (Integer scenarioId : expectedScenarioIds) { + sb = new StringBuilder(); + sb.append(scenarioId); + sb.append("\t"); + sb.append("1"); + sb.append("\t"); + sb.append("2"); + lines.add(sb.toString()); + } + Files.write(PROGRESS_LOG_PATH, lines); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @BeforeEach + public void beforeEach() { + clearProgressLog(); + } + + @AfterAll + public static void afterAll() { + clearProgressLog(); + } + + @Test + @UnitTestMethod(target = ExperimentStateManager.class, name = "builder", args = {}) + public void testBuilder() { + assertNotNull(ExperimentStateManager.builder()); + } + + @Test + @UnitTestMethod(target = ExperimentStateManager.class, name = "closeExperiment", args = {}) + public void testCloseExperiment() { + + MutableInteger consumerActivationCounter = new MutableInteger(); + + // precondition test: if the experiment is not currently open, it cannot be + // closed. + ContractException contractException = assertThrows(ContractException.class, + () -> ExperimentStateManager.builder().build().closeExperiment()); + + // post condition tests + assertEquals(NucleusError.UNCLOSABLE_EXPERIMENT, contractException.getErrorType()); + ExperimentStateManager experimentStateManager = ExperimentStateManager// + .builder()// + .setScenarioCount(5)// + .setScenarioProgressLogFile(PROGRESS_LOG_PATH).addExperimentContextConsumer((c1) -> { + c1.subscribeToExperimentClose((c2 -> consumerActivationCounter.increment())); + })// + .addExperimentContextConsumer((c1) -> { + c1.subscribeToExperimentClose((c2 -> consumerActivationCounter.increment(2))); + })// + .addExperimentContextConsumer((c1) -> { + c1.subscribeToExperimentClose((c2 -> consumerActivationCounter.increment(4))); + })// + .build(); + experimentStateManager.openExperiment(); + + experimentStateManager.closeScenarioAsSuccess(0); + experimentStateManager.closeScenarioAsSuccess(1); + experimentStateManager.closeScenarioAsSuccess(2); + + experimentStateManager.closeExperiment(); + + // show that each experiment context consumer that subscribed to close got the + // notification + assertEquals(7, consumerActivationCounter.getValue()); + + // show that the progress log closed and thus flushed out the three completed + // scenarios + Set expectedScenarioIds = new LinkedHashSet<>(); + expectedScenarioIds.add(0); + expectedScenarioIds.add(1); + expectedScenarioIds.add(2); + testProgressLogContents(expectedScenarioIds); + + } + + @Test + @UnitTestMethod(target = ExperimentStateManager.class, name = "closeScenarioAsFailure", args = { Integer.class, + Exception.class }) + public void testCloseScenarioAsFailure() { + Set observedClosedScenarios = new LinkedHashSet<>(); + + // precondition test: if the scenario id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + ExperimentStateManager experimentStateManager = ExperimentStateManager.builder().setScenarioCount(2) + .build();// + experimentStateManager.closeScenarioAsFailure(null, null); + }); + assertEquals(NucleusError.NULL_SCENARIO_ID, contractException.getErrorType()); + + // precondition test: if the scenario id is unknown + contractException = assertThrows(ContractException.class, () -> { + ExperimentStateManager experimentStateManager = ExperimentStateManager.builder().setScenarioCount(2) + .build();// + experimentStateManager.closeScenarioAsFailure(2, null); + }); + assertEquals(NucleusError.UNKNOWN_SCENARIO_ID, contractException.getErrorType()); + + // post condition tests + ExperimentStateManager experimentStateManager = ExperimentStateManager// + .builder()// + .setScenarioCount(5)// + .setScenarioProgressLogFile(PROGRESS_LOG_PATH)// + .addExperimentContextConsumer((c1) -> { + c1.subscribeToSimulationClose((c2, s) -> observedClosedScenarios.add(s)); + })// + .build(); + experimentStateManager.openExperiment(); + + // close all scenarios, with scenarios 0 and 2 failing + experimentStateManager.closeScenarioAsFailure(0, null); + experimentStateManager.closeScenarioAsSuccess(1); + experimentStateManager.closeScenarioAsFailure(2, new ContractException(NucleusError.ACCESS_VIOLATION)); + experimentStateManager.closeScenarioAsSuccess(3); + experimentStateManager.closeScenarioAsSuccess(4); + + experimentStateManager.closeExperiment(); + + // show that the experiment context consumer that subscribed to simulation close + // got the + // notification + Set expectedClosedScenarios = new LinkedHashSet<>(); + expectedClosedScenarios.add(0); + expectedClosedScenarios.add(1); + expectedClosedScenarios.add(2); + expectedClosedScenarios.add(3); + expectedClosedScenarios.add(4); + assertEquals(expectedClosedScenarios, observedClosedScenarios); + + // show that scenario 0 failed and has no known cause + Optional optionalScenarioStatus = experimentStateManager.getScenarioStatus(0); + assertTrue(optionalScenarioStatus.isPresent()); + assertEquals(ScenarioStatus.FAILED, optionalScenarioStatus.get()); + Optional optionalFailureCause = experimentStateManager.getScenarioFailureCause(0); + assertFalse(optionalFailureCause.isPresent()); + + // show that scenario 2 failed and has known cause + // ContractException(NucleusError.ACCESS_VIOLATION) + optionalScenarioStatus = experimentStateManager.getScenarioStatus(2); + assertTrue(optionalScenarioStatus.isPresent()); + assertEquals(ScenarioStatus.FAILED, optionalScenarioStatus.get()); + optionalFailureCause = experimentStateManager.getScenarioFailureCause(2); + assertTrue(optionalFailureCause.isPresent()); + Exception exception = optionalFailureCause.get(); + assertTrue(exception instanceof ContractException); + contractException = (ContractException) exception; + assertEquals(NucleusError.ACCESS_VIOLATION, contractException.getErrorType()); + + // show that the progress log does not contain the failed scenarios + Set expectedScenarioIds = new LinkedHashSet<>(); + expectedScenarioIds.add(1); + expectedScenarioIds.add(3); + expectedScenarioIds.add(4); + testProgressLogContents(expectedScenarioIds); + } + + @Test + @UnitTestMethod(target = ExperimentStateManager.class, name = "closeScenarioAsSuccess", args = { Integer.class }) + public void testCloseScenarioAsSuccess() { + Set observedClosedScenarios = new LinkedHashSet<>(); + + // precondition test: if the scenario id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + ExperimentStateManager experimentStateManager = ExperimentStateManager.builder().setScenarioCount(2) + .build();// + experimentStateManager.closeScenarioAsSuccess(null); + }); + assertEquals(NucleusError.NULL_SCENARIO_ID, contractException.getErrorType()); + + // precondition test: if the scenario id is unknown + contractException = assertThrows(ContractException.class, () -> { + ExperimentStateManager experimentStateManager = ExperimentStateManager.builder().setScenarioCount(2) + .build();// + experimentStateManager.closeScenarioAsSuccess(2); + }); + assertEquals(NucleusError.UNKNOWN_SCENARIO_ID, contractException.getErrorType()); + + // post condition tests + ExperimentStateManager experimentStateManager = ExperimentStateManager// + .builder()// + .setScenarioCount(5)// + .setScenarioProgressLogFile(PROGRESS_LOG_PATH)// + .addExperimentContextConsumer((c1) -> { + c1.subscribeToSimulationClose((c2, s) -> observedClosedScenarios.add(s)); + })// + .build(); + experimentStateManager.openExperiment(); + + // close all scenarios, with scenarios 0 and 2 failing + experimentStateManager.closeScenarioAsFailure(0, null); + experimentStateManager.closeScenarioAsSuccess(1); + experimentStateManager.closeScenarioAsFailure(2, new ContractException(NucleusError.ACCESS_VIOLATION)); + experimentStateManager.closeScenarioAsSuccess(3); + experimentStateManager.closeScenarioAsSuccess(4); + + experimentStateManager.closeExperiment(); + + /* + * show that the experiment context consumer that subscribed to simulation close + * got the notification + */ + Set expectedClosedScenarios = new LinkedHashSet<>(); + expectedClosedScenarios.add(0); + expectedClosedScenarios.add(1); + expectedClosedScenarios.add(2); + expectedClosedScenarios.add(3); + expectedClosedScenarios.add(4); + assertEquals(expectedClosedScenarios, observedClosedScenarios); + + // show that scenarios 1,3, and 4 succeeded. + for (int scenarioId : new int[] { 1, 3, 4 }) { + Optional optionalScenarioStatus = experimentStateManager.getScenarioStatus(scenarioId); + assertTrue(optionalScenarioStatus.isPresent()); + assertEquals(ScenarioStatus.SUCCEDED, optionalScenarioStatus.get()); + Optional optionalFailureCause = experimentStateManager.getScenarioFailureCause(scenarioId); + assertFalse(optionalFailureCause.isPresent()); + } + // show that the progress log contain only the successful scenarios + Set expectedScenarioIds = new LinkedHashSet<>(); + expectedScenarioIds.add(1); + expectedScenarioIds.add(3); + expectedScenarioIds.add(4); + testProgressLogContents(expectedScenarioIds); + } + + @Test + @UnitTestMethod(target = ExperimentStateManager.class, name = "getElapsedSeconds", args = {}) + public void testGetElapsedSeconds() { + ExperimentStateManager experimentStateManager = ExperimentStateManager// + .builder()// + .build(); + experimentStateManager.openExperiment(); + experimentStateManager.closeExperiment(); + assertTrue(experimentStateManager.getElapsedSeconds() > 0); + assertTrue(experimentStateManager.getElapsedSeconds() < 0.01); + } + + @Test + @UnitTestMethod(target = ExperimentStateManager.class, name = "getExperimentMetaData", args = {}) + public void testGetExperimentMetaData() { + + List scenarioMetaData = new ArrayList<>(); + scenarioMetaData.add("Alpha"); + scenarioMetaData.add("Beta"); + + ExperimentStateManager experimentStateManager = ExperimentStateManager// + .builder()// + .setExperimentMetaData(scenarioMetaData).build(); + assertEquals(scenarioMetaData, experimentStateManager.getExperimentMetaData()); + + } + + @Test + @UnitTestMethod(target = ExperimentStateManager.class, name = "getOutputConsumer", args = { Integer.class }) + public void testGetOutputConsumer() { + + // precondition test: if the scenario id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + ExperimentStateManager experimentStateManager = ExperimentStateManager.builder().setScenarioCount(2) + .build();// + experimentStateManager.getOutputConsumer(null); + }); + assertEquals(NucleusError.NULL_SCENARIO_ID, contractException.getErrorType()); + + // precondition test: if the scenario id is unknown + contractException = assertThrows(ContractException.class, () -> { + ExperimentStateManager experimentStateManager = ExperimentStateManager.builder().setScenarioCount(2) + .build();// + experimentStateManager.getOutputConsumer(2); + }); + assertEquals(NucleusError.UNKNOWN_SCENARIO_ID, contractException.getErrorType()); + + // post condition test: + ExperimentStateManager experimentStateManager = ExperimentStateManager// + .builder()// + .setScenarioCount(3)// + .build(); + + for (int scenarioId = 0; scenarioId < 3; scenarioId++) { + assertNotNull(experimentStateManager.getOutputConsumer(scenarioId)); + } + } + + @Test + @UnitTestMethod(target = ExperimentStateManager.class, name = "getScenarioCount", args = {}) + public void testGetScenarioCount() { + + for (int i = 0; i < 10; i++) { + ExperimentStateManager experimentStateManager = ExperimentStateManager// + .builder()// + .setScenarioCount(i)// + .build(); + + assertEquals(i, experimentStateManager.getScenarioCount()); + } + } + + @Test + @UnitTestMethod(target = ExperimentStateManager.class, name = "getScenarioFailureCause", args = { int.class }) + public void testGetScenarioFailureCause() { + + ExperimentStateManager experimentStateManager = ExperimentStateManager// + .builder()// + .setScenarioCount(5)// + .build(); + experimentStateManager.openExperiment(); + + // close all scenarios, with scenarios 0 and 2 failing + experimentStateManager.closeScenarioAsFailure(0, null); + experimentStateManager.closeScenarioAsSuccess(1); + experimentStateManager.closeScenarioAsFailure(2, new ContractException(NucleusError.ACCESS_VIOLATION)); + experimentStateManager.closeScenarioAsSuccess(3); + experimentStateManager.closeScenarioAsSuccess(4); + + experimentStateManager.closeExperiment(); + + // show that scenario 0 failed and has no known cause + Optional optionalScenarioStatus = experimentStateManager.getScenarioStatus(0); + assertTrue(optionalScenarioStatus.isPresent()); + assertEquals(ScenarioStatus.FAILED, optionalScenarioStatus.get()); + Optional optionalFailureCause = experimentStateManager.getScenarioFailureCause(0); + assertFalse(optionalFailureCause.isPresent()); + + // show that scenario 2 failed and has known cause + // ContractException(NucleusError.ACCESS_VIOLATION) + optionalScenarioStatus = experimentStateManager.getScenarioStatus(2); + assertTrue(optionalScenarioStatus.isPresent()); + assertEquals(ScenarioStatus.FAILED, optionalScenarioStatus.get()); + optionalFailureCause = experimentStateManager.getScenarioFailureCause(2); + assertTrue(optionalFailureCause.isPresent()); + Exception exception = optionalFailureCause.get(); + assertTrue(exception instanceof ContractException); + ContractException contractException = (ContractException) exception; + assertEquals(NucleusError.ACCESS_VIOLATION, contractException.getErrorType()); + + // show that scenarios 1,3,and 4 did not fail and have no know cause + + for (int i : new int[] { 1, 3, 4 }) { + optionalScenarioStatus = experimentStateManager.getScenarioStatus(i); + assertTrue(optionalScenarioStatus.isPresent()); + assertEquals(ScenarioStatus.SUCCEDED, optionalScenarioStatus.get()); + optionalFailureCause = experimentStateManager.getScenarioFailureCause(0); + assertFalse(optionalFailureCause.isPresent()); + } + + } + + @Test + @UnitTestMethod(target = ExperimentStateManager.class, name = "getScenarioMetaData", args = { Integer.class }) + public void testGetScenarioMetaData() { + ExperimentStateManager experimentStateManager = ExperimentStateManager// + .builder()// + .setScenarioCount(5)// + .build(); + experimentStateManager.openExperiment(); + + // open scenarios 0 and 2 and provide them with meta data + List scenarioMetaData0 = new ArrayList<>(); + scenarioMetaData0.add("3"); + scenarioMetaData0.add("7"); + experimentStateManager.openScenario(0, scenarioMetaData0); + + List scenarioMetaData2 = new ArrayList<>(); + scenarioMetaData2.add("4"); + scenarioMetaData2.add("6"); + experimentStateManager.openScenario(2, scenarioMetaData2); + + // show that a null scenario id is tolerated, but has no associated meta data + assertTrue(experimentStateManager.getScenarioMetaData(null).isEmpty()); + + // show that each scenario has the expected meta data + assertEquals(scenarioMetaData0, experimentStateManager.getScenarioMetaData(0)); + assertTrue(experimentStateManager.getScenarioMetaData(1).isEmpty()); + assertEquals(scenarioMetaData2, experimentStateManager.getScenarioMetaData(2)); + assertTrue(experimentStateManager.getScenarioMetaData(3).isEmpty()); + assertTrue(experimentStateManager.getScenarioMetaData(4).isEmpty()); + } + + @Test + @UnitTestMethod(target = ExperimentStateManager.class, name = "getScenarios", args = { ScenarioStatus.class }) + public void testGetScenarios() { + + /* + * We will have 10 scenarios spanning the various ScenarioStatus types + * + * 0 PREVIOUSLY_SUCCEEDED + * + * 1 FAILED + * + * 2 SUCCEDED + * + * 3 PREVIOUSLY_SUCCEEDED + * + * 4 FAILED + * + * 5 SUCCEDED + * + * 6 RUNNING + * + * 7 RUNNING + * + * 8 READY + * + * 9 SKIPPED + * + */ + + // we fill the progress log with 0 and 3 as PREVIOUSLY_SUCCEEDED + Set previouslyCompleteScenarios = new LinkedHashSet<>(); + previouslyCompleteScenarios.add(0); + previouslyCompleteScenarios.add(3); + initializeProgressLog(previouslyCompleteScenarios); + + // we will use explicit scenarios to force 9 to be SKIPPED + ExperimentStateManager experimentStateManager = ExperimentStateManager// + .builder()// + .setContinueFromProgressLog(true)// + .setScenarioProgressLogFile(PROGRESS_LOG_PATH)// + .setScenarioCount(10)// + .addExplicitScenarioId(1)// + .addExplicitScenarioId(2)// + .addExplicitScenarioId(4)// + .addExplicitScenarioId(5)// + .addExplicitScenarioId(6)// + .addExplicitScenarioId(7)// + .addExplicitScenarioId(8)// + .build(); + + // we open some of the scenarios to move them from READY to RUNNING + experimentStateManager.openScenario(1, new ArrayList<>()); + experimentStateManager.openScenario(2, new ArrayList<>()); + experimentStateManager.openScenario(4, new ArrayList<>()); + experimentStateManager.openScenario(5, new ArrayList<>()); + experimentStateManager.openScenario(6, new ArrayList<>()); + experimentStateManager.openScenario(7, new ArrayList<>()); + + // we close some of the RUNNING to create SUCCEDED and FAILED scenarios + experimentStateManager.closeScenarioAsFailure(1, null); + experimentStateManager.closeScenarioAsSuccess(2); + experimentStateManager.closeScenarioAsFailure(4, null); + experimentStateManager.closeScenarioAsSuccess(5); + + // we build a map of lists of scenarios that is what we expect to find -- note + // that we expect the lists to be ordered. + Map> expectedStatusMap = new LinkedHashMap<>(); + expectedStatusMap.put(ScenarioStatus.READY, Arrays.asList(new Integer[] { 8 })); + expectedStatusMap.put(ScenarioStatus.SKIPPED, Arrays.asList(new Integer[] { 9 })); + expectedStatusMap.put(ScenarioStatus.PREVIOUSLY_SUCCEEDED, Arrays.asList(new Integer[] { 0, 3 })); + expectedStatusMap.put(ScenarioStatus.RUNNING, Arrays.asList(new Integer[] { 6, 7 })); + expectedStatusMap.put(ScenarioStatus.SUCCEDED, Arrays.asList(new Integer[] { 2, 5 })); + expectedStatusMap.put(ScenarioStatus.FAILED, Arrays.asList(new Integer[] { 1, 4 })); + + // we show that the scenarios for each status are as expected. + for (ScenarioStatus scenarioStatus : ScenarioStatus.values()) { + List actualScenarios = experimentStateManager.getScenarios(scenarioStatus); + List expectedScenarios = expectedStatusMap.get(scenarioStatus); + assertEquals(expectedScenarios, actualScenarios); + } + + } + + @Test + @UnitTestMethod(target = ExperimentStateManager.class, name = "getScenarioStatus", args = { int.class }) + public void testGetScenarioStatus() { + /* + * We will have 10 scenarios spanning the various ScenarioStatus types + * + * 0 PREVIOUSLY_SUCCEEDED + * + * 1 FAILED + * + * 2 SUCCEDED + * + * 3 PREVIOUSLY_SUCCEEDED + * + * 4 FAILED + * + * 5 SUCCEDED + * + * 6 RUNNING + * + * 7 RUNNING + * + * 8 READY + * + * 9 SKIPPED + * + */ + + // we fill the progress log with 0 and 3 as PREVIOUSLY_SUCCEEDED + Set previouslyCompleteScenarios = new LinkedHashSet<>(); + previouslyCompleteScenarios.add(0); + previouslyCompleteScenarios.add(3); + initializeProgressLog(previouslyCompleteScenarios); + + // we will use explicit scenarios to force 9 to be SKIPPED + ExperimentStateManager experimentStateManager = ExperimentStateManager// + .builder()// + .setContinueFromProgressLog(true)// + .setScenarioProgressLogFile(PROGRESS_LOG_PATH)// + .setScenarioCount(10)// + .addExplicitScenarioId(1)// + .addExplicitScenarioId(2)// + .addExplicitScenarioId(4)// + .addExplicitScenarioId(5)// + .addExplicitScenarioId(6)// + .addExplicitScenarioId(7)// + .addExplicitScenarioId(8)// + .build(); + + // we open some of the scenarios to move them from READY to RUNNING + experimentStateManager.openScenario(1, new ArrayList<>()); + experimentStateManager.openScenario(2, new ArrayList<>()); + experimentStateManager.openScenario(4, new ArrayList<>()); + experimentStateManager.openScenario(5, new ArrayList<>()); + experimentStateManager.openScenario(6, new ArrayList<>()); + experimentStateManager.openScenario(7, new ArrayList<>()); + + // we close some of the RUNNING to create SUCCEDED and FAILED scenarios + experimentStateManager.closeScenarioAsFailure(1, null); + experimentStateManager.closeScenarioAsSuccess(2); + experimentStateManager.closeScenarioAsFailure(4, null); + experimentStateManager.closeScenarioAsSuccess(5); + + // we build a map of lists of scenarios that is what we expect to find -- note + // that we expect the lists to be ordered. + Map expectedStatusMap = new LinkedHashMap<>(); + expectedStatusMap.put(0, ScenarioStatus.PREVIOUSLY_SUCCEEDED); + expectedStatusMap.put(1, ScenarioStatus.FAILED); + expectedStatusMap.put(2, ScenarioStatus.SUCCEDED); + expectedStatusMap.put(3, ScenarioStatus.PREVIOUSLY_SUCCEEDED); + expectedStatusMap.put(4, ScenarioStatus.FAILED); + expectedStatusMap.put(5, ScenarioStatus.SUCCEDED); + expectedStatusMap.put(6, ScenarioStatus.RUNNING); + expectedStatusMap.put(7, ScenarioStatus.RUNNING); + expectedStatusMap.put(8, ScenarioStatus.READY); + expectedStatusMap.put(9, ScenarioStatus.SKIPPED); + + // we show that the scenarios for each status are as expected. + for (Integer scenarioId : expectedStatusMap.keySet()) { + Optional optionalScenarioStatus = experimentStateManager.getScenarioStatus(scenarioId); + assertTrue(optionalScenarioStatus.isPresent()); + ScenarioStatus actualScenarioStatus = optionalScenarioStatus.get(); + ScenarioStatus expectedScenarioStatus = expectedStatusMap.get(scenarioId); + assertEquals(expectedScenarioStatus, actualScenarioStatus); + } + Optional optionalScenarioStatus = experimentStateManager.getScenarioStatus(10); + assertFalse(optionalScenarioStatus.isPresent()); + + } + + @Test + @UnitTestMethod(target = ExperimentStateManager.class, name = "getStatusCount", args = { + ScenarioStatus.class }) + public void testGetStatusCount() { + /* + * We will have 10 scenarios spanning the various ScenarioStatus types + * + * 0 PREVIOUSLY_SUCCEEDED + * + * 1 FAILED + * + * 2 SUCCEDED + * + * 3 PREVIOUSLY_SUCCEEDED + * + * 4 FAILED + * + * 5 SUCCEDED + * + * 6 RUNNING + * + * 7 RUNNING + * + * 8 READY + * + * 9 SKIPPED + * + */ + + // we fill the progress log with 0 and 3 as PREVIOUSLY_SUCCEEDED + Set previouslyCompleteScenarios = new LinkedHashSet<>(); + previouslyCompleteScenarios.add(0); + previouslyCompleteScenarios.add(3); + initializeProgressLog(previouslyCompleteScenarios); + + // we will use explicit scenarios to force 9 to be SKIPPED + ExperimentStateManager experimentStateManager = ExperimentStateManager// + .builder()// + .setContinueFromProgressLog(true)// + .setScenarioProgressLogFile(PROGRESS_LOG_PATH)// + .setScenarioCount(10)// + .addExplicitScenarioId(1)// + .addExplicitScenarioId(2)// + .addExplicitScenarioId(4)// + .addExplicitScenarioId(5)// + .addExplicitScenarioId(6)// + .addExplicitScenarioId(7)// + .addExplicitScenarioId(8)// + .build(); + + // we open some of the scenarios to move them from READY to RUNNING + experimentStateManager.openScenario(1, new ArrayList<>()); + experimentStateManager.openScenario(2, new ArrayList<>()); + experimentStateManager.openScenario(4, new ArrayList<>()); + experimentStateManager.openScenario(5, new ArrayList<>()); + experimentStateManager.openScenario(6, new ArrayList<>()); + experimentStateManager.openScenario(7, new ArrayList<>()); + + // we close some of the RUNNING to create SUCCEDED and FAILED scenarios + experimentStateManager.closeScenarioAsFailure(1, null); + experimentStateManager.closeScenarioAsSuccess(2); + experimentStateManager.closeScenarioAsFailure(4, null); + experimentStateManager.closeScenarioAsSuccess(5); + + // we build a map of lists of scenarios that is what we expect to find -- note + // that we expect the lists to be ordered. + Map> expectedStatusMap = new LinkedHashMap<>(); + expectedStatusMap.put(ScenarioStatus.READY, Arrays.asList(new Integer[] { 8 })); + expectedStatusMap.put(ScenarioStatus.SKIPPED, Arrays.asList(new Integer[] { 9 })); + expectedStatusMap.put(ScenarioStatus.PREVIOUSLY_SUCCEEDED, Arrays.asList(new Integer[] { 0, 3 })); + expectedStatusMap.put(ScenarioStatus.RUNNING, Arrays.asList(new Integer[] { 6, 7 })); + expectedStatusMap.put(ScenarioStatus.SUCCEDED, Arrays.asList(new Integer[] { 2, 5 })); + expectedStatusMap.put(ScenarioStatus.FAILED, Arrays.asList(new Integer[] { 1, 4 })); + + // we show that the scenarios for each status are as expected. + for (ScenarioStatus scenarioStatus : ScenarioStatus.values()) { + int actualCount = experimentStateManager.getStatusCount(scenarioStatus); + List expectedScenarios = expectedStatusMap.get(scenarioStatus); + int expectedCount = expectedScenarios.size(); + assertEquals(expectedCount, actualCount); + } + } + + @Test + @UnitTestMethod(target = ExperimentStateManager.class, name = "openExperiment", args = {}) + public void testOpenExperiment() { + + /* + * Create a local consumer of ExperimentContext implementation. Each implementor + * is signaled via its constructor to possibly subscribe to the experiment open + * event. + */ + class LocalConsumer implements Consumer { + LocalConsumer(boolean subscribeToExperimentOpen) { + this.subscribeToExperimentOpen = subscribeToExperimentOpen; + } + + private final boolean subscribeToExperimentOpen; + + private boolean baseInvocation; + + private boolean experimentOpenInvocation; + + @Override + public void accept(ExperimentContext c) { + baseInvocation = true; + if (subscribeToExperimentOpen) { + c.subscribeToExperimentOpen((c2) -> { + experimentOpenInvocation = true; + }); + } + } + + } + + // create a few local consumers that will sometimes subscribe to the experiment + // open + List localConsumers = new ArrayList<>(); + localConsumers.add(new LocalConsumer(false)); + localConsumers.add(new LocalConsumer(true)); + localConsumers.add(new LocalConsumer(false)); + localConsumers.add(new LocalConsumer(true)); + localConsumers.add(new LocalConsumer(true)); + localConsumers.add(new LocalConsumer(false)); + + // build the experiment state manager with the local consumers + ExperimentStateManager.Builder builder = ExperimentStateManager// + .builder();// + for (LocalConsumer localConsumer : localConsumers) { + builder.addExperimentContextConsumer(localConsumer); + } + + ExperimentStateManager experimentStateManager = builder.build(); + + // open the experiment + experimentStateManager.openExperiment(); + + // show that all consumers are initialized + for (LocalConsumer localConsumer : localConsumers) { + assertTrue(localConsumer.baseInvocation); + } + + // show that the opening of the experiment was announced to all consumers that + // subscribed to that event + for (LocalConsumer localConsumer : localConsumers) { + assertEquals(localConsumer.subscribeToExperimentOpen, localConsumer.experimentOpenInvocation); + } + + // precondition test: if invoked more that once + ContractException contractException = assertThrows(ContractException.class, + () -> experimentStateManager.openExperiment()); + assertEquals(NucleusError.DUPLICATE_EXPERIMENT_OPEN, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = ExperimentStateManager.class, name = "openScenario", args = { Integer.class, List.class }) + public void testOpenScenario() { + List metaData = new ArrayList<>(); + metaData.add("alpha"); + metaData.add("beta"); + + List metaDataWithNull = new ArrayList<>(); + metaDataWithNull.add("alpha"); + metaDataWithNull.add(null); + + // precondition test: if the scenario id is null + ContractException contractException = assertThrows(ContractException.class, + () -> ExperimentStateManager.builder().setScenarioCount(5).build().openScenario(null, metaData)); + assertEquals(NucleusError.NULL_SCENARIO_ID, contractException.getErrorType()); + + // precondition test: if the meta data is null + contractException = assertThrows(ContractException.class, + () -> ExperimentStateManager.builder().setScenarioCount(5).build().openScenario(0, null)); + assertEquals(NucleusError.NULL_META_DATA, contractException.getErrorType()); + + // precondition test: if the meta data contains a null datum + contractException = assertThrows(ContractException.class, + () -> ExperimentStateManager.builder().setScenarioCount(5).build().openScenario(0, metaDataWithNull)); + assertEquals(NucleusError.NULL_META_DATA, contractException.getErrorType()); + + // precondition test: if the scenario is not known + contractException = assertThrows(ContractException.class, + () -> ExperimentStateManager.builder().setScenarioCount(5).build().openScenario(10, metaData)); + assertEquals(NucleusError.UNKNOWN_SCENARIO_ID, contractException.getErrorType()); + + // precondition test: if the scenario's current status is not READY + contractException = assertThrows(ContractException.class, () -> { + ExperimentStateManager experimentStateManager = ExperimentStateManager.builder().setScenarioCount(5) + .build(); + experimentStateManager.closeScenarioAsFailure(0, null); + experimentStateManager.openScenario(0, metaData); + }); + assertEquals(NucleusError.SCENARIO_CANNOT_BE_EXECUTED, contractException.getErrorType()); + + // post condition test: Updates the scenario's status to RUNNING + ExperimentStateManager experimentStateManager = ExperimentStateManager.builder().setScenarioCount(5).build(); + + experimentStateManager.openScenario(0, metaData); + experimentStateManager.openScenario(1, metaData); + experimentStateManager.openScenario(4, metaData); + + assertEquals(ScenarioStatus.RUNNING, experimentStateManager.getScenarioStatus(0).get()); + assertEquals(ScenarioStatus.RUNNING, experimentStateManager.getScenarioStatus(1).get()); + assertEquals(ScenarioStatus.READY, experimentStateManager.getScenarioStatus(2).get()); + assertEquals(ScenarioStatus.READY, experimentStateManager.getScenarioStatus(3).get()); + assertEquals(ScenarioStatus.RUNNING, experimentStateManager.getScenarioStatus(4).get()); + + // post condition test: Sets the meta data for the scenario + experimentStateManager = ExperimentStateManager.builder().setScenarioCount(5).build(); + + experimentStateManager.openScenario(0, metaData); + experimentStateManager.openScenario(1, metaData); + experimentStateManager.openScenario(4, metaData); + + assertEquals(metaData, experimentStateManager.getScenarioMetaData(0)); + assertEquals(metaData, experimentStateManager.getScenarioMetaData(1)); + assertTrue(experimentStateManager.getScenarioMetaData(2).isEmpty()); + assertTrue(experimentStateManager.getScenarioMetaData(3).isEmpty()); + assertEquals(metaData, experimentStateManager.getScenarioMetaData(4)); + + // post condition test: Announces the opening of the scenario to subscribed + // experiment context consumers. + Set observedSceanrioOpens = new LinkedHashSet<>(); + + experimentStateManager = ExperimentStateManager.builder()// + .addExperimentContextConsumer((c) -> { + c.subscribeToSimulationOpen((c2, s) -> { + observedSceanrioOpens.add(s); + }); + }).setScenarioCount(5)// + .build();// + + experimentStateManager.openExperiment(); + + experimentStateManager.openScenario(0, metaData); + experimentStateManager.openScenario(1, metaData); + experimentStateManager.openScenario(4, metaData); + + Set expectedSceanrioOpens = new LinkedHashSet<>(); + expectedSceanrioOpens.add(0); + expectedSceanrioOpens.add(1); + expectedSceanrioOpens.add(4); + + assertEquals(expectedSceanrioOpens, observedSceanrioOpens); + } + + @Test + @UnitTestMethod(target = ExperimentStateManager.Builder.class, name = "addExperimentContextConsumer", args = { + Consumer.class }) + public void testAddExperimentContextConsumer() { + + // precondition test: if the context consumer is null + ContractException contractException = assertThrows(ContractException.class, + () -> ExperimentStateManager.builder().addExperimentContextConsumer(null)); + assertEquals(NucleusError.NULL_EXPERIMENT_CONTEXT_CONSUMER, contractException.getErrorType()); + + /* + * post condition test: Adds a experiment context consumer that will be + * initialized at the start of the experiment. + */ + MutableBoolean consumerInitialized = new MutableBoolean(); + + ExperimentStateManager experimentStateManager = ExperimentStateManager.builder()// + .addExperimentContextConsumer((c) -> { + c.subscribeToSimulationOpen((c2, s) -> { + consumerInitialized.setValue(true); + }); + }).setScenarioCount(5)// + .build();// + + experimentStateManager.openExperiment(); + } + + @Test + @UnitTestMethod(target = ExperimentStateManager.Builder.class, name = "build", args = {}) + public void testBuild() { + + // precondition test: if an explicit scenario id is not in the span of the + // experiment's scenario ids + ContractException contractException = assertThrows(ContractException.class, () -> { + ExperimentStateManager.builder()// + .setScenarioCount(5)// + .addExplicitScenarioId(10).build();// + }); + assertEquals(NucleusError.UNKNOWN_SCENARIO_ID, contractException.getErrorType()); + + // precondition test: if continue from progress file was chosen, but the path to + // the file is null + contractException = assertThrows(ContractException.class, () -> { + ExperimentStateManager.builder()// + .setContinueFromProgressLog(true)// + .build();// + }); + assertEquals(NucleusError.NULL_SCENARIO_PROGRESS_FILE, contractException.getErrorType()); + + // precondition test: if continue from progress file was chosen, but the path to + // the file does not exist + contractException = assertThrows(ContractException.class, () -> { + ExperimentStateManager.builder()// + .setContinueFromProgressLog(true)// + .setScenarioProgressLogFile(NONEXISTENT_PROGRESS_LOG_PATH).build();// + }); + assertEquals(NucleusError.NON_EXISTANT_SCEANARIO_PROGRESS, contractException.getErrorType()); + + // precondition test: if continue from progress file was chosen, but the path + // lead to a non-file + contractException = assertThrows(ContractException.class, () -> { + ExperimentStateManager.builder()// + .setContinueFromProgressLog(true)// + .setScenarioProgressLogFile(NONFILE_PROGRESS_LOG_PATH).build();// + }); + assertEquals(NucleusError.UNREADABLE_SCEANARIO_PROGRESS, contractException.getErrorType()); + + /* + * precondition test: if the lines of the file cannot be loaded : + * NucleusError.UNREADABLE_SCEANARIO_PROGRESS + * + * It is not feasible to test this precondition since it requires a file that is + * somehow corrupt, enormous or otherwise malformed. + * + */ + + // precondition test: if the header line of the file does not match the expected + // header line for the current experiment + contractException = assertThrows(ContractException.class, () -> { + Set previouslyExecutedScenarios = new LinkedHashSet<>(); + previouslyExecutedScenarios.add(0); + previouslyExecutedScenarios.add(3); + initializeProgressLogWithMetaData(previouslyExecutedScenarios); + + List metaData = new ArrayList<>(); + metaData.add("A"); + metaData.add("B"); + metaData.add("C"); + + ExperimentStateManager.builder()// + .setContinueFromProgressLog(true)// + .setScenarioCount(7)// + .setExperimentMetaData(metaData) + .setScenarioProgressLogFile(PROGRESS_LOG_PATH)// + .build();// + }); + assertEquals(NucleusError.INCOMPATIBLE_SCEANARIO_PROGRESS, contractException.getErrorType()); + + // precondition test: if a scenario id is encountered that is not valid for the + // the current experiment + contractException = assertThrows(ContractException.class, () -> { + Set previouslyExecutedScenarios = new LinkedHashSet<>(); + previouslyExecutedScenarios.add(0); + previouslyExecutedScenarios.add(3); + initializeProgressLogWithMetaData(previouslyExecutedScenarios); + + List metaData = new ArrayList<>(); + metaData.add("A"); + metaData.add("B"); + + ExperimentStateManager.builder()// + .setContinueFromProgressLog(true)// + .setScenarioCount(2)// + .setExperimentMetaData(metaData) + .setScenarioProgressLogFile(PROGRESS_LOG_PATH)// + .build();// + + + }); + assertEquals(NucleusError.INCOMPATIBLE_SCEANARIO_PROGRESS, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ExperimentStateManager.Builder.class, name = "setContinueFromProgressLog", args = { + boolean.class }) + public void testSetContinueFromProgressLog() { + + Set previouslyCompletedScenarios = new LinkedHashSet<>(); + previouslyCompletedScenarios.add(0); + previouslyCompletedScenarios.add(1); + previouslyCompletedScenarios.add(3); + initializeProgressLog(previouslyCompletedScenarios); + + ExperimentStateManager experimentStateManager = ExperimentStateManager.builder() + .setScenarioProgressLogFile(PROGRESS_LOG_PATH)// + .setContinueFromProgressLog(true)// + .setScenarioCount(5).build(); + + assertEquals(ScenarioStatus.PREVIOUSLY_SUCCEEDED, experimentStateManager.getScenarioStatus(0).get()); + assertEquals(ScenarioStatus.PREVIOUSLY_SUCCEEDED, experimentStateManager.getScenarioStatus(1).get()); + assertEquals(ScenarioStatus.READY, experimentStateManager.getScenarioStatus(2).get()); + assertEquals(ScenarioStatus.PREVIOUSLY_SUCCEEDED, experimentStateManager.getScenarioStatus(3).get()); + assertEquals(ScenarioStatus.READY, experimentStateManager.getScenarioStatus(4).get()); + + experimentStateManager = ExperimentStateManager.builder().setScenarioProgressLogFile(PROGRESS_LOG_PATH) + .setContinueFromProgressLog(false).setScenarioCount(5).build(); + + assertEquals(ScenarioStatus.READY, experimentStateManager.getScenarioStatus(0).get()); + assertEquals(ScenarioStatus.READY, experimentStateManager.getScenarioStatus(1).get()); + assertEquals(ScenarioStatus.READY, experimentStateManager.getScenarioStatus(2).get()); + assertEquals(ScenarioStatus.READY, experimentStateManager.getScenarioStatus(3).get()); + assertEquals(ScenarioStatus.READY, experimentStateManager.getScenarioStatus(4).get()); + + } + + @Test + @UnitTestMethod(target = ExperimentStateManager.Builder.class, name = "setExperimentMetaData", args = { + List.class }) + public void testSetExperimentMetaData() { + /* + * Covered by tests for Experiment and ExperimentContext classes + */ + + List expectedMetaData = new ArrayList<>(); + + expectedMetaData.add("A"); + expectedMetaData.add("B"); + expectedMetaData.add("C"); + expectedMetaData.add("D"); + + ExperimentStateManager experimentStateManager = ExperimentStateManager.builder() + .setExperimentMetaData(expectedMetaData)// + .build(); + + assertEquals(expectedMetaData, experimentStateManager.getExperimentMetaData()); + + } + + @Test + @UnitTestMethod(target = ExperimentStateManager.Builder.class, name = "setScenarioCount", args = { Integer.class }) + public void testSetScenarioCount() { + for (int i = 0; i < 10; i++) { + ExperimentStateManager experimentStateManager = ExperimentStateManager.builder().setScenarioCount(i) + .build(); + assertEquals(i, experimentStateManager.getScenarioCount()); + } + } + + @Test + @UnitTestMethod(target = ExperimentStateManager.Builder.class, name = "setScenarioProgressLogFile", args = { + Path.class }) + public void testSetScenarioProgressLogFile() { + + // we fill the progress log with 0 and 3 as PREVIOUSLY_SUCCEEDED + Set previouslyCompleteScenarios = new LinkedHashSet<>(); + previouslyCompleteScenarios.add(0); + previouslyCompleteScenarios.add(3); + initializeProgressLog(previouslyCompleteScenarios); + + ExperimentStateManager experimentStateManager = ExperimentStateManager// + .builder()// + .setContinueFromProgressLog(true)// + .setScenarioProgressLogFile(PROGRESS_LOG_PATH)// + .setScenarioCount(5)// + .build(); + + // We demonstrate that the progress file was utilized as expected + assertEquals(ScenarioStatus.PREVIOUSLY_SUCCEEDED, experimentStateManager.getScenarioStatus(0).get()); + assertEquals(ScenarioStatus.PREVIOUSLY_SUCCEEDED, experimentStateManager.getScenarioStatus(3).get()); + + } + + @Test + @UnitTestMethod(target = ExperimentStateManager.Builder.class, name = "addExplicitScenarioId", args = { + Integer.class }) + public void testAddExplicitScenarioId() { + // we fill the progress log with 0 and 3 as PREVIOUSLY_SUCCEEDED + Set previouslyCompleteScenarios = new LinkedHashSet<>(); + previouslyCompleteScenarios.add(0); + previouslyCompleteScenarios.add(3); + initializeProgressLog(previouslyCompleteScenarios); + + /* + * We use some explicit values. Note that 3 is explicit, which overrides it + * having been previously executed. + */ + ExperimentStateManager experimentStateManager = ExperimentStateManager// + .builder()// + .setContinueFromProgressLog(true)// + .setScenarioProgressLogFile(PROGRESS_LOG_PATH)// + .setScenarioCount(10)// + .addExplicitScenarioId(1)// + .addExplicitScenarioId(2)// + .addExplicitScenarioId(3)// + .addExplicitScenarioId(4)// + .addExplicitScenarioId(5)// + .build(); + + // build our expectations + Map expectedStatusValues = new LinkedHashMap<>(); + expectedStatusValues.put(0, ScenarioStatus.PREVIOUSLY_SUCCEEDED); + expectedStatusValues.put(1, ScenarioStatus.READY); + expectedStatusValues.put(2, ScenarioStatus.READY); + expectedStatusValues.put(3, ScenarioStatus.READY); + expectedStatusValues.put(4, ScenarioStatus.READY); + expectedStatusValues.put(5, ScenarioStatus.READY); + expectedStatusValues.put(6, ScenarioStatus.SKIPPED); + expectedStatusValues.put(7, ScenarioStatus.SKIPPED); + expectedStatusValues.put(8, ScenarioStatus.SKIPPED); + expectedStatusValues.put(9, ScenarioStatus.SKIPPED); + + // We demonstrate that each scenario has the expected status. + for (int i = 0; i < 10; i++) { + ScenarioStatus actualScenarioStatus = experimentStateManager.getScenarioStatus(i).get(); + ScenarioStatus expectedScenarioStatus = expectedStatusValues.get(i); + assertEquals(expectedScenarioStatus, actualScenarioStatus); + } + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_ExperimentStatusConsole.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_ExperimentStatusConsole.java new file mode 100644 index 000000000..1267de0fd --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_ExperimentStatusConsole.java @@ -0,0 +1,57 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTag; +import util.annotations.UnitTestMethod; + +public class AT_ExperimentStatusConsole { + + @Test + @UnitTestMethod(target = ExperimentStatusConsole.class, name = "accept", args = { ExperimentContext.class }, tags = { UnitTag.MANUAL }) + public void testAccept() { + // deferred to manual test + } + + @Test + @UnitTestMethod(target = ExperimentStatusConsole.class, name = "builder", args = {}, tags = { UnitTag.LOCAL_PROXY }) + public void testBuilder() { + assertNotNull(ExperimentStatusConsole.builder().build()); + } + + @Test + @UnitTestMethod(target = ExperimentStatusConsole.Builder.class, name = "build", args = {}, tags = { UnitTag.LOCAL_PROXY }) + public void testBuild() { + /* + * Implied behaviors of the resulting ExperimentStatusConsole are + * covered by the remaining tests + */ + } + + @Test + @UnitTestMethod(target = ExperimentStatusConsole.Builder.class, name = "setImmediateErrorReporting", args = { boolean.class }, tags = { UnitTag.LOCAL_PROXY }) + public void testSetImmediateErrorReporting() { + /* + * test covered by testAccept(); + */ + } + + @Test + @UnitTestMethod(target = ExperimentStatusConsole.Builder.class, name = "setReportScenarioProgress", args = { boolean.class }, tags = { UnitTag.LOCAL_PROXY }) + public void testSetReportScenarioProgress() { + /* + * test covered by testAccept(); + */ + } + + @Test + @UnitTestMethod(target = ExperimentStatusConsole.Builder.class, name = "setStackTraceReportLimit", args = { int.class }, tags = { UnitTag.LOCAL_PROXY }) + public void testSetStackTraceReportLimit() { + /* + * test covered by testAccept(); + */ + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_IdentifiableFunction.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_IdentifiableFunction.java new file mode 100644 index 000000000..c8afe8a22 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_IdentifiableFunction.java @@ -0,0 +1,90 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import java.util.function.Function; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_IdentifiableFunction { + + @Test + @UnitTestConstructor(target = IdentifiableFunction.class, args = { Object.class, Function.class }) + public void testConstructor() { + for (int i = 0; i < 30; i++) { + int input = i; + String expectedValue = Integer.toString(input); + IdentifiableFunction f = new IdentifiableFunction<>("A", (n) -> Integer.toString(n)); + Object actualValue = f.getFunction().apply(input); + assertEquals(expectedValue, actualValue); + } + } + + @Test + @UnitTestMethod(target = IdentifiableFunction.class, name = "equals", args = { Object.class }) + public void testEquals() { + // identifiable functions are equal if and only if their internal id + // values are equal + IdentifiableFunction a1 = new IdentifiableFunction<>("A", (n) -> Integer.toString(n)); + IdentifiableFunction a2 = new IdentifiableFunction<>("A", (n) -> Integer.toString(n)); + IdentifiableFunction a3 = new IdentifiableFunction<>("A", (n) -> Integer.toString(n)); + + IdentifiableFunction b = new IdentifiableFunction<>("B", (n) -> Integer.toString(n)); + + // reflexive + assertEquals(a1, a1); + + // symmetric + assertEquals(a1, a2); + assertEquals(a2, a1); + + // transitive + assertEquals(a2, a3); + assertEquals(a1, a3); + + // non-equal ids + + assertNotEquals(a1, b); + assertNotEquals(a2, b); + assertNotEquals(a3, b); + + } + + @Test + @UnitTestMethod(target = IdentifiableFunction.class, name = "getFunction", args = {}) + public void testGetFunction() { + /* + * Show that the event function is retrievable by executing that + * function against some input + */ + + for (int i = 0; i < 30; i++) { + int input = i; + String expectedValue = Integer.toString(input); + IdentifiableFunction f = new IdentifiableFunction<>("A", (n) -> Integer.toString(n)); + Object actualValue = f.getFunction().apply(input); + assertEquals(expectedValue, actualValue); + } + } + + @Test + @UnitTestMethod(target = IdentifiableFunction.class, name = "hashCode", args = {}) + public void testHashCode() { + + IdentifiableFunction a1 = new IdentifiableFunction<>("A", (n) -> Integer.toString(n)); + IdentifiableFunction a2 = new IdentifiableFunction<>("A", (n) -> Integer.toString(n)); + + IdentifiableFunction b1 = new IdentifiableFunction<>("B", (n) -> Integer.toString(n)); + IdentifiableFunction b2 = new IdentifiableFunction<>("B", (n) -> Integer.toString(n)); + + // show equal objects have equal hash codes + assertEquals(a1.hashCode(), a2.hashCode()); + assertEquals(b1.hashCode(), b2.hashCode()); + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_IdentifiableFunctionMap.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_IdentifiableFunctionMap.java new file mode 100644 index 000000000..1fc112253 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_IdentifiableFunctionMap.java @@ -0,0 +1,87 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.function.Function; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + +public class AT_IdentifiableFunctionMap { + @Test + @UnitTestMethod(target = IdentifiableFunctionMap.class, name = "builder", args = { Class.class }) + public void testBuilder() { + // show that the builder is returned + assertNotNull(IdentifiableFunctionMap.builder(Object.class)); + + // precondition test: if the class reference is null + ContractException contractException = assertThrows(ContractException.class, () -> IdentifiableFunctionMap.builder(null)); + assertEquals(NucleusError.NULL_CLASS_REFERENCE, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = IdentifiableFunctionMap.class, name = "get", args = { Object.class }) + public void testGet() { + IdentifiableFunctionMap functionMap = // + IdentifiableFunctionMap .builder(Integer.class)// + .put("A", (n) -> false)// + .put("B", (n) -> "b")// + .put("C", (n) -> n)// + .build(); + IdentifiableFunction identifiableFunction = functionMap.get("A"); + assertEquals(false, identifiableFunction.getFunction().apply(13)); + + identifiableFunction = functionMap.get("B"); + assertEquals("b", identifiableFunction.getFunction().apply(45)); + + identifiableFunction = functionMap.get("C"); + assertEquals(88, identifiableFunction.getFunction().apply(88)); + + // if the function id is null + ContractException contractException = assertThrows(ContractException.class, () -> IdentifiableFunctionMap.builder(Integer.class).build().get(null));// + assertEquals(NucleusError.NULL_FUNCTION_ID, contractException.getErrorType()); + + // if the function id is unknown + contractException = assertThrows(ContractException.class, () -> IdentifiableFunctionMap.builder(Integer.class).build().get("unknown id"));// + assertEquals(NucleusError.UNKNOWN_FUNCTION_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = IdentifiableFunctionMap.Builder.class, name = "put", args = { Object.class, Function.class }) + public void testPut() { + IdentifiableFunctionMap functionMap = // + IdentifiableFunctionMap .builder(Integer.class)// + .put("A", (n) -> false)// + .put("B", (n) -> "b")// + .put("C", (n) -> n)// + .build(); + IdentifiableFunction identifiableFunction = functionMap.get("A"); + assertEquals(false, identifiableFunction.getFunction().apply(13)); + + identifiableFunction = functionMap.get("B"); + assertEquals("b", identifiableFunction.getFunction().apply(45)); + + identifiableFunction = functionMap.get("C"); + assertEquals(88, identifiableFunction.getFunction().apply(88)); + + // if the function id is null + ContractException contractException = assertThrows(ContractException.class, () -> IdentifiableFunctionMap.builder(Integer.class).put(null, (n) -> 3));// + assertEquals(NucleusError.NULL_FUNCTION_ID, contractException.getErrorType()); + + // if the function is null + contractException = assertThrows(ContractException.class, () -> IdentifiableFunctionMap.builder(Integer.class).put("A", null));// + assertEquals(NucleusError.NULL_FUNCTION, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = IdentifiableFunctionMap.Builder.class, name = "build", args = {}) + public void testBuild() { + // covered by the other tests + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_NucleusError.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_NucleusError.java new file mode 100644 index 000000000..29d200e2b --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_NucleusError.java @@ -0,0 +1,36 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestMethod; + + +public class AT_NucleusError { + + /** + * Tests {@link NucleusError#getDescription()} + */ + @Test + @UnitTestMethod(target = NucleusError.class, name = "getDescription", args = {}) + public void testGetDescription() { + // show that each ErrorType has a non-null, non-empty description + for (NucleusError nucleusError : NucleusError.values()) { + assertNotNull(nucleusError.getDescription()); + assertTrue(nucleusError.getDescription().length() > 0); + } + + // show that each description is unique (ignoring case as well) + Set descriptions = new LinkedHashSet<>(); + for (NucleusError nucleusError : NucleusError.values()) { + boolean isUnique = descriptions.add(nucleusError.getDescription().toLowerCase()); + assertTrue(isUnique, nucleusError + " duplicates the description of another member"); + } + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_Plan.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_Plan.java new file mode 100644 index 000000000..7019be572 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_Plan.java @@ -0,0 +1,196 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.function.Consumer; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestMethod; +import util.random.RandomGeneratorProvider; +import util.wrappers.MutableBoolean; + +public class AT_Plan { + + class TestPlanData1 implements PlanData { + } + + @Test + @UnitTestMethod(target = Plan.Builder.class, name = "build", args = {}) + public void testBuild() { + assertNotNull(Plan.builder(ActorContext.class).build()); + } + + @Test + @UnitTestMethod(target = Plan.Builder.class, name = "setTime", args = { double.class }) + public void testSetTime() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3070829833293509127L); + + for (int i = 0; i < 10; i++) { + double time = randomGenerator.nextDouble() * 100; + Plan plan = Plan.builder(ActorContext.class).setTime(time).build(); + + assertEquals(time, plan.getTime()); + } + + } + + @Test + @UnitTestMethod(target = Plan.Builder.class, name = "setActive", args = { boolean.class }) + public void testSetActive() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2020906151186894101L); + + for (int i = 0; i < 10; i++) { + boolean active = randomGenerator.nextBoolean(); + Plan plan = Plan.builder(ActorContext.class).setActive(active).build(); + + assertEquals(active, plan.isActive()); + } + + Plan plan = Plan.builder(ActorContext.class).build(); + + assertTrue(plan.isActive()); + } + + @Test + @UnitTestMethod(target = Plan.Builder.class, name = "setPlanData", args = { PlanData.class }) + public void testSetPlanData() { + for (int i = 0; i < 10; i++) { + TestPlanData1 testPlanData1 = new TestPlanData1(); + Plan plan = Plan.builder(ActorContext.class).setPlanData(testPlanData1).build(); + + assertEquals(testPlanData1, plan.getPlanData()); + } + + Plan plan = Plan.builder(ActorContext.class).build(); + + assertEquals(null, plan.getPlanData()); + } + + @Test + @UnitTestMethod(target = Plan.Builder.class, name = "setKey", args = { Object.class }) + public void testSetKey() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2020906151186894101L); + + for (int i = 0; i < 10; i++) { + String key = "TestKey" + randomGenerator.nextInt(100) + i % 2; + Plan plan = Plan.builder(ActorContext.class).setKey(key).build(); + + assertEquals(key, plan.getKey()); + } + + Plan plan = Plan.builder(ActorContext.class).build(); + + assertEquals(null, plan.getKey()); + } + + @Test + @UnitTestMethod(target = Plan.Builder.class, name = "setCallbackConsumer", args = { Consumer.class }) + public void testSetCallbackConsumer() { + for (int i = 0; i < 10; i++) { + MutableBoolean called = new MutableBoolean(false); + Consumer callbackConsumer = (c) -> called.setValue(true); + Plan plan = Plan.builder(ActorContext.class).setCallbackConsumer(callbackConsumer).build(); + + assertEquals(callbackConsumer, plan.getCallbackConsumer()); + + plan.getCallbackConsumer().accept(null); + + assertTrue(called.getValue()); + } + } + + @Test + @UnitTestMethod(target = Plan.class, name = "builder", args = { Class.class }) + public void testBuilder() { + assertNotNull(Plan.builder(ActorContext.class)); + } + + @Test + @UnitTestMethod(target = Plan.class, name = "getCallbackConsumer", args = {}) + public void testGetCallbackConsumer() { + for (int i = 0; i < 10; i++) { + MutableBoolean called = new MutableBoolean(false); + Consumer callbackConsumer = (c) -> called.setValue(true); + Plan plan = Plan.builder(ActorContext.class).setCallbackConsumer(callbackConsumer).build(); + + assertEquals(callbackConsumer, plan.getCallbackConsumer()); + + plan.getCallbackConsumer().accept(null); + + assertTrue(called.getValue()); + } + } + + @Test + @UnitTestMethod(target = Plan.class, name = "getKey", args = {}) + public void testGetKey() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(544728351791286073L); + + for (int i = 0; i < 10; i++) { + String key = "TestKey" + randomGenerator.nextInt(100) + i % 2; + Plan plan = Plan.builder(ActorContext.class).setKey(key).build(); + + assertEquals(key, plan.getKey()); + } + + Plan plan = Plan.builder(ActorContext.class).build(); + + assertEquals(null, plan.getKey()); + } + + @Test + @UnitTestMethod(target = Plan.class, name = "getPlanData", args = {}) + public void testGetPlanData() { + for (int i = 0; i < 10; i++) { + TestPlanData1 testPlanData1 = new TestPlanData1(); + Plan plan = Plan.builder(ActorContext.class).setPlanData(testPlanData1).build(); + + assertEquals(testPlanData1, plan.getPlanData()); + } + + Plan plan = Plan.builder(ActorContext.class).build(); + + assertEquals(null, plan.getPlanData()); + } + + @Test + @UnitTestMethod(target = Plan.class, name = "getTime", args = {}) + public void testGetTime() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6688329851268154810L); + + for (int i = 0; i < 10; i++) { + double time = randomGenerator.nextDouble() * 100; + Plan plan = Plan.builder(ActorContext.class).setTime(time).build(); + + assertEquals(time, plan.getTime()); + } + } + + @Test + @UnitTestMethod(target = Plan.class, name = "isActive", args = {}) + public void testIsActive() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1531401382595524054L); + + for (int i = 0; i < 10; i++) { + boolean active = randomGenerator.nextBoolean(); + Plan plan = Plan.builder(ActorContext.class).setActive(active).build(); + + assertEquals(active, plan.isActive()); + } + + Plan plan = Plan.builder(ActorContext.class).build(); + + assertTrue(plan.isActive()); + } + + @Test + @UnitTestMethod(target = Plan.class, name = "toString", args = {}) + public void testToString() { + // is essentially a debug capability and does not warrant a test since its + // implementation is subject to a wide variety of implementations + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_PlanQueueData.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_PlanQueueData.java new file mode 100644 index 000000000..5e29c5441 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_PlanQueueData.java @@ -0,0 +1,408 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_PlanQueueData { + class TestPlanData1 implements PlanData { + } + + @Test + @UnitTestMethod(target = PlanQueueData.Builder.class, name = "build", args = {}) + public void testBuild() { + assertNotNull(PlanQueueData.builder().setPlanData(new TestPlanData1()).setPlanner(Planner.ACTOR).build()); + + // preconditions: + // PlanData is null + ContractException contractException = assertThrows(ContractException.class, () -> { + PlanQueueData.builder().setPlanner(Planner.ACTOR).build(); + }); + + assertEquals(NucleusError.NULL_PLAN_DATA, contractException.getErrorType()); + + // Planner is null + contractException = assertThrows(ContractException.class, () -> { + PlanQueueData.builder().setPlanData(new TestPlanData1()).build(); + }); + + assertEquals(NucleusError.NULL_PLANNER, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PlanQueueData.Builder.class, name = "setTime", args = { double.class }) + public void testSetTime() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(594478369345316212L); + + for (int i = 0; i < 10; i++) { + double time = randomGenerator.nextDouble() * 100; + PlanQueueData planQueueData = PlanQueueData.builder().setPlanData(new TestPlanData1()) + .setPlanner(Planner.ACTOR).setTime(time).build(); + + assertEquals(time, planQueueData.getTime()); + } + + PlanQueueData planQueueData = PlanQueueData.builder().setPlanData(new TestPlanData1()).setPlanner(Planner.ACTOR) + .build(); + + assertEquals(0, planQueueData.getTime()); + } + + @Test + @UnitTestMethod(target = PlanQueueData.Builder.class, name = "setActive", args = { boolean.class }) + public void testSetActive() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2212750783748698797L); + + for (int i = 0; i < 10; i++) { + boolean active = randomGenerator.nextBoolean(); + PlanQueueData planQueueData = PlanQueueData.builder().setPlanData(new TestPlanData1()) + .setPlanner(Planner.ACTOR).setActive(active).build(); + + assertEquals(active, planQueueData.isActive()); + } + + PlanQueueData planQueueData = PlanQueueData.builder().setPlanData(new TestPlanData1()).setPlanner(Planner.ACTOR) + .build(); + + assertTrue(planQueueData.isActive()); + } + + @Test + @UnitTestMethod(target = PlanQueueData.Builder.class, name = "setKey", args = { Object.class }) + public void testSetKey() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6278569817385464648L); + + for (int i = 0; i < 10; i++) { + String key = "TestKey" + (i * randomGenerator.nextInt(100) + 1) + i % 2; + PlanQueueData planQueueData = PlanQueueData.builder().setPlanData(new TestPlanData1()) + .setPlanner(Planner.ACTOR).setKey(key).build(); + + assertEquals(key, planQueueData.getKey()); + } + + PlanQueueData planQueueData = PlanQueueData.builder().setPlanData(new TestPlanData1()).setPlanner(Planner.ACTOR) + .build(); + + assertTrue(planQueueData.getKey() == null); + } + + @Test + @UnitTestMethod(target = PlanQueueData.Builder.class, name = "setPlanData", args = { PlanData.class }) + public void testSetPlanData() { + for (int i = 0; i < 10; i++) { + TestPlanData1 testPlanData1 = new TestPlanData1(); + PlanQueueData planQueueData = PlanQueueData.builder().setPlanData(testPlanData1).setPlanner(Planner.ACTOR) + .build(); + + assertEquals(testPlanData1, planQueueData.getPlanData()); + } + } + + @Test + @UnitTestMethod(target = PlanQueueData.Builder.class, name = "setPlanner", args = { Planner.class }) + public void testSetPlanner() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5508769779925956678L); + + for (int i = 0; i < 10; i++) { + Planner planner = Planner.values()[randomGenerator.nextInt(3)]; + + PlanQueueData planQueueData = PlanQueueData.builder().setPlanData(new TestPlanData1()).setPlanner(planner) + .build(); + + assertEquals(planner, planQueueData.getPlanner()); + } + } + + @Test + @UnitTestMethod(target = PlanQueueData.Builder.class, name = "setPlannerId", args = { int.class }) + public void testSetPlannerId() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(594478369345316212L); + + for (int i = 0; i < 10; i++) { + int plannerId = randomGenerator.nextInt(100); + PlanQueueData planQueueData = PlanQueueData.builder().setPlanData(new TestPlanData1()) + .setPlanner(Planner.ACTOR).setPlannerId(plannerId).build(); + + assertEquals(plannerId, planQueueData.getPlannerId()); + } + + PlanQueueData planQueueData = PlanQueueData.builder().setPlanData(new TestPlanData1()).setPlanner(Planner.ACTOR) + .build(); + + assertEquals(0, planQueueData.getPlannerId()); + } + + @Test + @UnitTestMethod(target = PlanQueueData.Builder.class, name = "setArrivalId", args = { long.class }) + public void testSetArrivalId() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5508769779925956678L); + + for (int i = 0; i < 10; i++) { + long arrivalId = randomGenerator.nextLong(); + PlanQueueData planQueueData = PlanQueueData.builder().setPlanData(new TestPlanData1()) + .setPlanner(Planner.ACTOR).setArrivalId(arrivalId).build(); + + assertEquals(arrivalId, planQueueData.getArrivalId()); + } + + PlanQueueData planQueueData = PlanQueueData.builder().setPlanData(new TestPlanData1()).setPlanner(Planner.ACTOR) + .build(); + + assertEquals(0, planQueueData.getArrivalId()); + } + + @Test + @UnitTestMethod(target = PlanQueueData.class, name = "builder", args = {}) + public void testBuilder() { + assertNotNull(PlanQueueData.builder()); + } + + @Test + @UnitTestMethod(target = PlanQueueData.class, name = "getArrivalId", args = {}) + public void testGetArrivalId() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5508769779925956678L); + + for (int i = 0; i < 10; i++) { + long arrivalId = randomGenerator.nextLong(); + PlanQueueData planQueueData = PlanQueueData.builder().setPlanData(new TestPlanData1()) + .setPlanner(Planner.ACTOR).setArrivalId(arrivalId).build(); + + assertEquals(arrivalId, planQueueData.getArrivalId()); + } + + PlanQueueData planQueueData = PlanQueueData.builder().setPlanData(new TestPlanData1()).setPlanner(Planner.ACTOR) + .build(); + + assertEquals(0, planQueueData.getArrivalId()); + } + + @Test + @UnitTestMethod(target = PlanQueueData.class, name = "getKey", args = {}) + public void testGetKey() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6278569817385464648L); + + for (int i = 0; i < 10; i++) { + String key = "TestKey" + (i * randomGenerator.nextInt(100) + 1) + i % 2; + PlanQueueData planQueueData = PlanQueueData.builder().setPlanData(new TestPlanData1()) + .setPlanner(Planner.ACTOR).setKey(key).build(); + + assertEquals(key, planQueueData.getKey()); + } + + PlanQueueData planQueueData = PlanQueueData.builder().setPlanData(new TestPlanData1()).setPlanner(Planner.ACTOR) + .build(); + + assertTrue(planQueueData.getKey() == null); + } + + @Test + @UnitTestMethod(target = PlanQueueData.class, name = "getPlanData", args = {}) + public void testGetPlanData() { + for (int i = 0; i < 10; i++) { + TestPlanData1 testPlanData1 = new TestPlanData1(); + PlanQueueData planQueueData = PlanQueueData.builder().setPlanData(testPlanData1).setPlanner(Planner.ACTOR) + .build(); + + assertEquals(testPlanData1, planQueueData.getPlanData()); + } + } + + @Test + @UnitTestMethod(target = PlanQueueData.class, name = "getPlanner", args = {}) + public void testGetPlanner() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5508769779925956678L); + + for (int i = 0; i < 10; i++) { + Planner planner = Planner.values()[randomGenerator.nextInt(3)]; + + PlanQueueData planQueueData = PlanQueueData.builder().setPlanData(new TestPlanData1()).setPlanner(planner) + .build(); + + assertEquals(planner, planQueueData.getPlanner()); + } + } + + @Test + @UnitTestMethod(target = PlanQueueData.class, name = "getPlannerId", args = {}) + public void testGetPlannerId() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(594478369345316212L); + + for (int i = 0; i < 10; i++) { + int plannerId = randomGenerator.nextInt(100); + PlanQueueData planQueueData = PlanQueueData.builder().setPlanData(new TestPlanData1()) + .setPlanner(Planner.ACTOR).setPlannerId(plannerId).build(); + + assertEquals(plannerId, planQueueData.getPlannerId()); + } + + PlanQueueData planQueueData = PlanQueueData.builder().setPlanData(new TestPlanData1()).setPlanner(Planner.ACTOR) + .build(); + + assertEquals(0, planQueueData.getPlannerId()); + } + + @Test + @UnitTestMethod(target = PlanQueueData.class, name = "getTime", args = {}) + public void testGetTime() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(594478369345316212L); + + for (int i = 0; i < 10; i++) { + double time = randomGenerator.nextDouble() * 100; + PlanQueueData planQueueData = PlanQueueData.builder().setPlanData(new TestPlanData1()) + .setPlanner(Planner.ACTOR).setTime(time).build(); + + assertEquals(time, planQueueData.getTime()); + } + + PlanQueueData planQueueData = PlanQueueData.builder().setPlanData(new TestPlanData1()).setPlanner(Planner.ACTOR) + .build(); + + assertEquals(0, planQueueData.getTime()); + } + + @Test + @UnitTestMethod(target = PlanQueueData.class, name = "isActive", args = {}) + public void testIsActive() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2212750783748698797L); + + for (int i = 0; i < 10; i++) { + boolean active = randomGenerator.nextBoolean(); + PlanQueueData planQueueData = PlanQueueData.builder().setPlanData(new TestPlanData1()) + .setPlanner(Planner.ACTOR).setActive(active).build(); + + assertEquals(active, planQueueData.isActive()); + } + + PlanQueueData planQueueData = PlanQueueData.builder().setPlanData(new TestPlanData1()).setPlanner(Planner.ACTOR) + .build(); + + assertTrue(planQueueData.isActive()); + } + + private static class TestPlanData implements PlanData { + private final int index; + + public TestPlanData(int index) { + this.index = index; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + index; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof TestPlanData)) { + return false; + } + TestPlanData other = (TestPlanData) obj; + if (index != other.index) { + return false; + } + return true; + } + + } + + private PlanQueueData getRandomPlanQueueData(long seed) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + return PlanQueueData.builder()// + .setActive(randomGenerator.nextBoolean())// + .setArrivalId(randomGenerator.nextLong())// + .setKey(Integer.toString(randomGenerator.nextInt()))// + .setPlanData(new TestPlanData(randomGenerator.nextInt())) + .setPlanner(Planner.values()[randomGenerator.nextInt(Planner.values().length)])// + .setPlannerId(randomGenerator.nextInt())// + .setTime(randomGenerator.nextDouble())// + .build(); + } + + @Test + @UnitTestMethod(target = PlanQueueData.class, name = "hashCode", args = {}) + public void testHashCode() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3433493259546771854L); + + // show equal objects have equal hash codes + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + + PlanQueueData pqd1 = getRandomPlanQueueData(seed); + PlanQueueData pqd2 = getRandomPlanQueueData(seed); + + assertEquals(pqd1, pqd2); + assertEquals(pqd1.hashCode(), pqd2.hashCode()); + } + + // show hash codes are reasonably distributed + Set hashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + long seed = randomGenerator.nextLong(); + + PlanQueueData pqd = getRandomPlanQueueData(seed); + hashCodes.add(pqd.hashCode()); + } + + assertTrue(hashCodes.size() > 95); + + } + + @Test + @UnitTestMethod(target = PlanQueueData.class, name = "equals", args = {Object.class}) + public void testEquals() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4135063462491592422L); + + // show not equal to null + for (int i = 0; i < 30; i++) { + PlanQueueData pqd = getRandomPlanQueueData(randomGenerator.nextLong()); + assertFalse(pqd.equals(null)); + } + + // reflexive + for (int i = 0; i < 30; i++) { + PlanQueueData pqd = getRandomPlanQueueData(randomGenerator.nextLong()); + assertTrue(pqd.equals(pqd)); + } + + // symmetric/transitive and consistent + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + PlanQueueData pqd1 = getRandomPlanQueueData(seed); + PlanQueueData pqd2 = getRandomPlanQueueData(seed); + for (int j = 0; j < 10; j++) { + assertTrue(pqd1.equals(pqd2)); + assertTrue(pqd2.equals(pqd1)); + } + } + + //show that different inputs lead to non-equality + for (int i = 0; i < 30; i++) { + + PlanQueueData pqd1 = getRandomPlanQueueData(randomGenerator.nextLong()); + PlanQueueData pqd2 = getRandomPlanQueueData(randomGenerator.nextLong()); + + //VERY low probability they are equal + assertNotEquals(pqd1,pqd2); + } + + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_Plugin.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_Plugin.java new file mode 100644 index 000000000..3f9214657 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_Plugin.java @@ -0,0 +1,402 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.function.Consumer; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_Plugin { + + private static final class XPluginData implements PluginData { + + @Override + public PluginDataBuilder getCloneBuilder() { + throw new UnsupportedOperationException(); + } + + } + + private static final class YPluginData implements PluginData { + private final int value; + + public YPluginData(int value) { + this.value = value; + } + + @Override + public PluginDataBuilder getCloneBuilder() { + throw new UnsupportedOperationException(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + value; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof YPluginData)) { + return false; + } + YPluginData other = (YPluginData) obj; + if (value != other.value) { + return false; + } + return true; + } + + } + + private static enum PluginIds implements PluginId { + PLUGIN_ID_1, PLUGIN_ID_2, PLUGIN_ID_3, PLUGIN_ID_4, PLUGIN_ID_5; + + public static PluginIds getRandomPluginId(RandomGenerator randomGenerator) { + int index = randomGenerator.nextInt(PluginIds.values().length); + return PluginIds.values()[index]; + } + + public static Set getRandomPluginIds(RandomGenerator randomGenerator) { + Set result = new LinkedHashSet<>(); + for (PluginIds pluginIds : PluginIds.values()) { + if (randomGenerator.nextBoolean()) { + result.add(pluginIds); + } + } + return result; + } + } + + @Test + @UnitTestMethod(target = Plugin.class, name = "builder", args = {}) + public void testBuilder() { + assertNotNull(Plugin.builder()); + } + + @Test + @UnitTestMethod(target = Plugin.class, name = "getInitializer", args = {}) + public void testGetInitializer() { + Plugin plugin = Plugin .builder()// + .setPluginId(PluginIds.PLUGIN_ID_1)// + .build();// + + assertFalse(plugin.getInitializer().isPresent()); + + Consumer initializer = (c) -> { + }; + + plugin = Plugin .builder()// + .setPluginId(PluginIds.PLUGIN_ID_1)// + .setInitializer(initializer)// + .build();// + + assertTrue(plugin.getInitializer().isPresent()); + assertEquals(initializer, plugin.getInitializer().get()); + + } + + @Test + @UnitTestMethod(target = Plugin.class, name = "getPluginDatas", args = {}) + public void testGetPluginDatas() { + Plugin plugin = Plugin .builder()// + .setPluginId(PluginIds.PLUGIN_ID_1)// + .build();// + + assertTrue(plugin.getPluginDatas().isEmpty()); + + XPluginData xPluginData1 = new XPluginData(); + XPluginData xPluginData2 = new XPluginData(); + Set expectedPluginDatas = new LinkedHashSet<>(); + expectedPluginDatas.add(xPluginData1); + expectedPluginDatas.add(xPluginData2); + + plugin = Plugin .builder()// + .setPluginId(PluginIds.PLUGIN_ID_1)// + .addPluginData(xPluginData1)// + .addPluginData(xPluginData2)// + .build();// + + assertEquals(expectedPluginDatas, new LinkedHashSet<>(plugin.getPluginDatas())); + } + + @Test + @UnitTestMethod(target = Plugin.class, name = "getPluginDependencies", args = {}) + public void testGetPluginDependencies() { + Plugin plugin = Plugin .builder()// + .setPluginId(PluginIds.PLUGIN_ID_1)// + .build();// + + assertTrue(plugin.getPluginDependencies().isEmpty()); + + Set expectedPluginIds = new LinkedHashSet<>(); + expectedPluginIds.add(PluginIds.PLUGIN_ID_2); + expectedPluginIds.add(PluginIds.PLUGIN_ID_3); + + plugin = Plugin .builder()// + .setPluginId(PluginIds.PLUGIN_ID_1)// + .addPluginDependency(PluginIds.PLUGIN_ID_2)// + .addPluginDependency(PluginIds.PLUGIN_ID_3)// + .build();// + + assertEquals(expectedPluginIds, plugin.getPluginDependencies()); + } + + @Test + @UnitTestMethod(target = Plugin.class, name = "getPluginId", args = {}) + public void testGetPluginId() { + + Plugin plugin = Plugin .builder()// + .setPluginId(PluginIds.PLUGIN_ID_1)// + .build();// + + assertEquals(PluginIds.PLUGIN_ID_1, plugin.getPluginId()); + + plugin = Plugin .builder()// + .setPluginId(PluginIds.PLUGIN_ID_2)// + .build();// + + assertEquals(PluginIds.PLUGIN_ID_2, plugin.getPluginId()); + + } + + @Test + @UnitTestMethod(target = Plugin.Builder.class, name = "addPluginData", args = { PluginData.class }) + public void testAddPluginData() { + + XPluginData xPluginData1 = new XPluginData(); + XPluginData xPluginData2 = new XPluginData(); + Set expectedPluginDatas = new LinkedHashSet<>(); + expectedPluginDatas.add(xPluginData1); + expectedPluginDatas.add(xPluginData2); + + Plugin plugin = Plugin .builder()// + .setPluginId(PluginIds.PLUGIN_ID_1)// + .addPluginData(xPluginData1)// + .addPluginData(xPluginData2)// + .build();// + + assertEquals(expectedPluginDatas, new LinkedHashSet<>(plugin.getPluginDatas())); + + // precondition test: if the plugin data is null + ContractException contractException = assertThrows(ContractException.class, () -> Plugin.builder().addPluginData(null)); + assertEquals(NucleusError.NULL_PLUGIN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = Plugin.Builder.class, name = "addPluginDependency", args = { PluginId.class }) + public void testAddPluginDependency() { + + Set expectedPluginIds = new LinkedHashSet<>(); + expectedPluginIds.add(PluginIds.PLUGIN_ID_2); + expectedPluginIds.add(PluginIds.PLUGIN_ID_3); + + Plugin plugin = Plugin .builder()// + .setPluginId(PluginIds.PLUGIN_ID_1)// + .addPluginDependency(PluginIds.PLUGIN_ID_2)// + .addPluginDependency(PluginIds.PLUGIN_ID_3)// + .build();// + + assertEquals(expectedPluginIds, plugin.getPluginDependencies()); + + // precondition test: if a plugin dependency is null + ContractException contractException = assertThrows(ContractException.class, () -> Plugin.builder().addPluginDependency(null)); + assertEquals(NucleusError.NULL_PLUGIN_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = Plugin.Builder.class, name = "build", args = {}) + public void testBuild() { + + Plugin plugin = Plugin.builder().setPluginId(PluginIds.PLUGIN_ID_1).build(); + assertNotNull(plugin); + + // precondition test: if the plugin id was not set + ContractException contractException = assertThrows(ContractException.class, () -> Plugin.builder().build()); + assertEquals(NucleusError.NULL_PLUGIN_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = Plugin.Builder.class, name = "setInitializer", args = { Consumer.class }) + public void testSetInitializer() { + + Consumer initializer = (c) -> { + }; + + Plugin plugin = Plugin .builder()// + .setPluginId(PluginIds.PLUGIN_ID_1)// + .setInitializer(initializer)// + .build();// + + assertTrue(plugin.getInitializer().isPresent()); + assertEquals(initializer, plugin.getInitializer().get()); + + // precondition test: if the initializer is null + ContractException contractException = assertThrows(ContractException.class, () -> Plugin.builder().setInitializer(null)); + assertEquals(NucleusError.NULL_PLUGIN_INITIALIZER, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = Plugin.Builder.class, name = "setPluginId", args = { PluginId.class }) + public void testSetPluginId() { + Plugin plugin = Plugin .builder()// + .setPluginId(PluginIds.PLUGIN_ID_1)// + .build();// + + assertEquals(PluginIds.PLUGIN_ID_1, plugin.getPluginId()); + + plugin = Plugin .builder()// + .setPluginId(PluginIds.PLUGIN_ID_2)// + .build();// + + assertEquals(PluginIds.PLUGIN_ID_2, plugin.getPluginId()); + + // precondition test: if the plugin id is null + assertThrows(ContractException.class, () -> Plugin.builder().setPluginId(null)); + + } + + @UnitTestMethod(target = Plugin.class, name = "hashCode", args = {}) + @Test + public void testHashCode() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1667890710500097680L); + + Set hashcodes = new LinkedHashSet<>(); + // show equal objects have equal hash codes + + for (int i = 0; i < 100; i++) { + Plugin.Builder builder1 = Plugin.builder(); + Plugin.Builder builder2 = Plugin.builder(); + + Set pluginDatas = new LinkedHashSet<>(); + int pluginDataCount = randomGenerator.nextInt(5) + 1; + for (int j = 0; j < pluginDataCount; j++) { + pluginDatas.add(new YPluginData(randomGenerator.nextInt(100))); + } + + for (PluginData pluginData : pluginDatas) { + builder1.addPluginData(pluginData); + builder2.addPluginData(pluginData); + } + + PluginIds pluginId = PluginIds.getRandomPluginId(randomGenerator); + builder1.setPluginId(pluginId); + builder2.setPluginId(pluginId); + + Set randomPluginIds = PluginIds.getRandomPluginIds(randomGenerator); + for (PluginIds pluginIds : randomPluginIds) { + builder1.addPluginDependency(pluginIds); + builder2.addPluginDependency(pluginIds); + } + + builder1.setInitializer((c) -> { + }); + builder2.setInitializer((c) -> { + }); + + Plugin plugin1 = builder1.build(); + Plugin plugin2 = builder2.build(); + + assertEquals(plugin1.hashCode(), plugin2.hashCode()); + hashcodes.add(plugin1.hashCode()); + + } + + // show that hash codes are dispersed -- low collision rate even for + // these very simple examples + assertTrue(hashcodes.size() > 95); + + } + + private Plugin getRandomPlugin(long seed) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + + // show equal objects have equal hash codes + + Plugin.Builder builder = Plugin.builder(); + + Set pluginDatas = new LinkedHashSet<>(); + int pluginDataCount = randomGenerator.nextInt(5) + 1; + for (int j = 0; j < pluginDataCount; j++) { + pluginDatas.add(new YPluginData(randomGenerator.nextInt(100))); + } + + for (PluginData pluginData : pluginDatas) { + builder.addPluginData(pluginData); + + } + + PluginIds pluginId = PluginIds.getRandomPluginId(randomGenerator); + builder.setPluginId(pluginId); + + Set randomPluginIds = PluginIds.getRandomPluginIds(randomGenerator); + for (PluginIds pluginIds : randomPluginIds) { + builder.addPluginDependency(pluginIds); + + } + + builder.setInitializer((c) -> { + }); + + return builder.build(); + + } + + @UnitTestMethod(target = Plugin.class, name = "equals", args = { Object.class }) + @Test + public void testEquals() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1276670681120545443L); + + // show equal objects have equal hash codes + for (int i = 0; i < 100; i++) { + long seed = randomGenerator.nextLong(); + Plugin plugin1 = getRandomPlugin(seed); + Plugin plugin2 = getRandomPlugin(seed); + assertEquals(plugin1, plugin2); + } + + // show that non-equal objects are not equal + + int nonEqualityCheck = 0; + for (int i = 0; i < 100; i++) { + long seed = randomGenerator.nextLong(); + Plugin plugin1 = getRandomPlugin(seed); + seed = randomGenerator.nextLong(); + Plugin plugin2 = getRandomPlugin(seed); + + boolean equals = plugin1.getPluginDatas().equals(plugin2.getPluginDatas())// + && plugin1.getPluginId().equals(plugin2.getPluginId())// + && plugin1.getPluginDependencies().equals(plugin2.getPluginDependencies());// + + if (!equals) { + nonEqualityCheck++; + assertNotEquals(plugin1, plugin2); + } + } + + // show that we generated sufficient non-equal comparisons + assertTrue(nonEqualityCheck > 90); + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_PluginContext.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_PluginContext.java new file mode 100644 index 000000000..2f2198656 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_PluginContext.java @@ -0,0 +1,267 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.function.Consumer; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestDataManager; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.wrappers.MutableBoolean; + +public class AT_PluginContext { + + private static class TestDataManager1 extends TestDataManager { + + @Override + public void init(final DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + } + } + + private static class TestDataManager2 extends TestDataManager { + + @Override + public void init(final DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + } + } + + private static class TestDataManager3 extends TestDataManager { + + @Override + public void init(final DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + } + } + + @Test + @UnitTestMethod(target = PluginContext.class, name = "addActor", args = { Consumer.class }) + public void testAddActor() { + + /* + * Create a plugin initializer that will add a few actors. Each actor + * will signal when it has initialized and the initializer will record + * that signal. + */ + Set addedActors = new LinkedHashSet<>(); + int numberOfActorsToAdd = 5; + + /* + * Create a plugin that has its initializer add 5 actors. Each actor + * will retrieve its own actor id and record them + */ + Plugin plugin = Plugin.builder()// + .setPluginId(new SimplePluginId("plugin id"))// + .setInitializer((c) -> { + for (int i = 0; i < numberOfActorsToAdd; i++) { + c.addActor((c2) -> { + addedActors.add(c2.getActorId()); + }); + } + })// + .build();// + + // build and execute the simulation + Simulation.builder()// + .addPlugin(plugin)// + .build()// + .execute();// + + // show that the correct number of actors were added to the simulation + assertEquals(numberOfActorsToAdd, addedActors.size()); + } + + @Test + @UnitTestMethod(target = PluginContext.class, name = "addDataManager", args = { DataManager.class }) + public void testAddDataManager() { + + /* + * The TestPluginInitialzer uses the PluginContext to add data managers. + * If we add data managers via the TestPlugin and have an actor show + * that each data manager exists during the simulation run, we can infer + * that the addDataManager of the plugin context must be working + * correctly. + */ + + final MutableBoolean actorExecuted = new MutableBoolean(); + + // add the actors to the action plugin + final TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + pluginDataBuilder.addTestDataManager("A", () -> new TestDataManager1()); + pluginDataBuilder.addTestDataManager("B", () -> new TestDataManager2()); + pluginDataBuilder.addTestDataManager("C", () -> new TestDataManager3()); + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + actorExecuted.setValue(true); + })); + + // build the action plugin + final TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // build and execute the engine + Simulation.builder()// + .addPlugin(testPlugin)// + .build()// + .execute();// + + // show that the assertions were executed + assertTrue(actorExecuted.getValue()); + + } + + private static class PluginData1 implements PluginData { + + @Override + public PluginDataBuilder getCloneBuilder() { + throw new UnsupportedOperationException(); + } + + } + + private static class PluginData2 implements PluginData { + + @Override + public PluginDataBuilder getCloneBuilder() { + throw new UnsupportedOperationException(); + } + } + + private static class PluginData3 implements PluginData { + @Override + public PluginDataBuilder getCloneBuilder() { + throw new UnsupportedOperationException(); + } + } + + @Test + @UnitTestMethod(target = PluginContext.class, name = "getPluginData", args = { Class.class }) + public void testGetPluginData() { + + MutableBoolean assertionsExecuted = new MutableBoolean(); + + /* + * Create a plugin with an initialization method that that execute the + * getPluginData method for each three plugin data items. + */ + Plugin plugin = Plugin.builder()// + .setPluginId(new SimplePluginId("plugin id"))// + .setInitializer((c) -> { + assertNotNull(c.getPluginData(PluginData1.class)); + assertNotNull(c.getPluginData(PluginData2.class)); + assertNotNull(c.getPluginData(PluginData3.class)); + + ContractException contractException = assertThrows(ContractException.class, () -> { + c.getPluginData(PluginData.class); + }); + + assertEquals(NucleusError.AMBIGUOUS_PLUGIN_DATA_CLASS, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, () -> { + c.getPluginData(null); + }); + + assertEquals(NucleusError.NULL_PLUGIN_DATA_CLASS, contractException.getErrorType()); + + assertionsExecuted.setValue(true); + })// + .addPluginData(new PluginData1())// + .addPluginData(new PluginData2())// + .addPluginData(new PluginData3())// + .build();// + + Simulation.builder()// + .addPlugin(plugin)// + .build()// + .execute();// + + // show that the initializer assertions were executed + assertTrue(assertionsExecuted.getValue()); + + } + + @Test + @UnitTestMethod(target = PluginContext.class, name = "addReport", args = { Consumer.class }) + public void testAddReport() { + + /* + * Create a plugin initializer that will add a few reports. Each report + * will signal when it has initialized and the initializer will record + * that signal. + */ + Set addedReports = new LinkedHashSet<>(); + int numberOfReportsToAdd = 5; + + /* + * Create a plugin that has its initializer add 5 actors. Each actor + * will retrieve its own actor id and record them + */ + Plugin plugin = Plugin.builder()// + .setPluginId(new SimplePluginId("plugin id"))// + .setInitializer((c) -> { + for (int i = 0; i < numberOfReportsToAdd; i++) { + c.addReport((c2) -> { + addedReports.add(c2.getReportId()); + }); + } + })// + .build();// + + // build and execute the simulation + Simulation.builder()// + .addPlugin(plugin)// + .build()// + .execute();// + + // show that the correct number of actors were added to the simulation + assertEquals(numberOfReportsToAdd, addedReports.size()); + } + + @Test + @UnitTestMethod(target = PluginContext.class, name = "getPluginDatas", args = { Class.class }) + public void testGetPluginDatas() { + MutableBoolean assertionsExecuted = new MutableBoolean(); + + /* + * Create a plugin with an initialization method that that execute the + * getPluginData method for each three plugin data items. + */ + Plugin plugin = Plugin.builder()// + .setPluginId(new SimplePluginId("plugin id"))// + .setInitializer((c) -> { + assertNotNull(c.getPluginDatas(PluginData1.class)); + assertNotNull(c.getPluginDatas(PluginData2.class)); + assertNotNull(c.getPluginDatas(PluginData3.class)); + + ContractException contractException = assertThrows(ContractException.class, () -> { + c.getPluginDatas(null); + }); + + assertEquals(NucleusError.NULL_PLUGIN_DATA_CLASS, contractException.getErrorType()); + + assertionsExecuted.setValue(true); + })// + .addPluginData(new PluginData1())// + .addPluginData(new PluginData2())// + .addPluginData(new PluginData3())// + .build();// + + Simulation.builder()// + .addPlugin(plugin)// + .build()// + .execute();// + + // show that the initializer assertions were executed + assertTrue(assertionsExecuted.getValue()); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_PluginDataBuilderContext.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_PluginDataBuilderContext.java new file mode 100644 index 000000000..c52482c85 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_PluginDataBuilderContext.java @@ -0,0 +1,233 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + +public class AT_PluginDataBuilderContext { + + private static class PluginData1 implements PluginData { + + private static class Builder implements PluginDataBuilder { + + @Override + public PluginData build() { + return null; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof Builder)) { + return false; + } + + return true; + } + } + + @Override + public PluginDataBuilder getCloneBuilder() { + return new Builder(); + } + } + + private static class PluginData2 implements PluginData { + + private static class Builder implements PluginDataBuilder { + + @Override + public PluginData build() { + return null; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof Builder)) { + return false; + } + + return true; + } + } + + @Override + public PluginDataBuilder getCloneBuilder() { + return new Builder(); + } + } + + private static class PluginData3 implements PluginData { + + private static class Builder implements PluginDataBuilder { + + @Override + public PluginData build() { + return null; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof Builder)) { + return false; + } + + return true; + } + } + + @Override + public PluginDataBuilder getCloneBuilder() { + return new Builder(); + } + } + + @Test + @UnitTestMethod(target = PluginDataBuilderContext.class, name = "builder", args = {}) + public void testBuilder() { + assertNotNull(PluginDataBuilderContext.builder()); + } + + @Test + @UnitTestMethod(target = PluginDataBuilderContext.class, name = "getPluginDataBuilder", args = { Class.class }) + public void testGetPluginDataBuilder() { + PluginDataBuilder p1 = new PluginData1.Builder(); + + PluginDataBuilder p2 = new PluginData2.Builder(); + + Set expectedContents = new LinkedHashSet<>(); + expectedContents.add(p1); + expectedContents.add(p2); + + PluginDataBuilderContext.Builder builder = PluginDataBuilderContext.builder(); + + builder.add(p1); + builder.add(p2); + + + + PluginDataBuilderContext pluginDataBuilderContext = builder.build(); + + + Set actualContents = new LinkedHashSet<>(); + + + + assertDoesNotThrow(() -> actualContents.add(pluginDataBuilderContext.getPluginDataBuilder(PluginData1.Builder.class))); + assertDoesNotThrow(() -> actualContents.add(pluginDataBuilderContext.getPluginDataBuilder(PluginData2.Builder.class))); + + assertEquals(expectedContents, actualContents); + + /* + * precondition test : if more than one plugin data builder matches the + * given class reference + */ + ContractException contractException = assertThrows(ContractException.class, + () -> pluginDataBuilderContext.getPluginDataBuilder(PluginDataBuilder.class)); + assertEquals(NucleusError.AMBIGUOUS_PLUGIN_DATA_BUILDER_CLASS, contractException.getErrorType()); + + /* + * precondition test : if no plugin data builder matches the given class + * reference + */ + contractException = assertThrows(ContractException.class, + () -> pluginDataBuilderContext.getPluginDataBuilder(PluginData3.Builder.class)); + assertEquals(NucleusError.UNKNOWN_PLUGIN_DATA_BUILDER_CLASS, contractException.getErrorType()); + + } + + + @Test + @UnitTestMethod(target = PluginDataBuilderContext.Builder.class, name = "add", args = { PluginDataBuilder.class }) + public void testAdd() { + PluginDataBuilder p1 = new PluginData1.Builder(); + + PluginDataBuilder p2 = new PluginData2.Builder(); + + Set expectedContents = new LinkedHashSet<>(); + expectedContents.add(p1); + expectedContents.add(p2); + + PluginDataBuilderContext.Builder builder = PluginDataBuilderContext.builder(); + + builder.add(p1); + builder.add(p2); + + + + PluginDataBuilderContext pluginDataBuilderContext = builder.build(); + + + Set actualContents = new LinkedHashSet<>(); + + + + assertDoesNotThrow(() -> actualContents.add(pluginDataBuilderContext.getPluginDataBuilder(PluginData1.Builder.class))); + assertDoesNotThrow(() -> actualContents.add(pluginDataBuilderContext.getPluginDataBuilder(PluginData2.Builder.class))); + + assertEquals(expectedContents, actualContents); + + ContractException contractException = assertThrows(ContractException.class, + () -> PluginDataBuilderContext.builder().add(null)); + assertEquals(NucleusError.NULL_PLUGIN_DATA_BUILDER, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = PluginDataBuilderContext.Builder.class, name = "build", args = {}) + public void testBuild() { + PluginDataBuilder p1 = new PluginData1.Builder(); + + PluginDataBuilder p2 = new PluginData2.Builder(); + + Set expectedContents = new LinkedHashSet<>(); + expectedContents.add(p1); + expectedContents.add(p2); + + PluginDataBuilderContext.Builder builder = PluginDataBuilderContext.builder(); + + builder.add(p1); + builder.add(p2); + + + + PluginDataBuilderContext pluginDataBuilderContext = builder.build(); + + + Set actualContents = new LinkedHashSet<>(); + + + + assertDoesNotThrow(() -> actualContents.add(pluginDataBuilderContext.getPluginDataBuilder(PluginData1.Builder.class))); + assertDoesNotThrow(() -> actualContents.add(pluginDataBuilderContext.getPluginDataBuilder(PluginData2.Builder.class))); + + assertEquals(expectedContents, actualContents); + + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_PrioritizedPlanData.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_PrioritizedPlanData.java new file mode 100644 index 000000000..de9d6b17b --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_PrioritizedPlanData.java @@ -0,0 +1,74 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_PrioritizedPlanData { + + private static class LocalPlanData implements PlanData{ + + @Override + public int hashCode() { + return 31; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (!(obj instanceof LocalPlanData)) { + return false; + } + return true; + } + + @Override + public String toString() { + return "LocalPlanData []"; + } + + + } + + @Test + @UnitTestConstructor(target = PrioritizedPlanData.class,args= {PlanData.class, long.class}) + public void testPrioritizedPlanData() { + //nothing to test + } + + @Test + @UnitTestMethod(target = PrioritizedPlanData.class,name="getPlanData",args= {}) + public void testGetPlanData() { + LocalPlanData localPlanData = new LocalPlanData(); + PrioritizedPlanData prioritizedPlanData = new PrioritizedPlanData(localPlanData,22345L); + assertEquals(localPlanData, prioritizedPlanData.getPlanData()); + } + + @Test + @UnitTestMethod(target = PrioritizedPlanData.class,name="getPriority",args= {}) + public void testGetPriority() { + LocalPlanData localPlanData = new LocalPlanData(); + PrioritizedPlanData prioritizedPlanData = new PrioritizedPlanData(localPlanData,22345L); + assertEquals(22345L, prioritizedPlanData.getPriority()); + } + + @Test + @UnitTestMethod(target = PrioritizedPlanData.class,name="toString",args= {}) + public void testToString() { + LocalPlanData localPlanData = new LocalPlanData(); + PrioritizedPlanData prioritizedPlanData = new PrioritizedPlanData(localPlanData,1234L); + assertEquals("PrioritizedPlanData [planData=LocalPlanData [], priority=1234]", prioritizedPlanData.toString()); + } + + + +// toString() +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_ReportContext.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_ReportContext.java new file mode 100644 index 000000000..aa02fb8d9 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_ReportContext.java @@ -0,0 +1,911 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestDataManager; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestDataManagerPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestOutputConsumer; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestReportPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.wrappers.MutableBoolean; +import util.wrappers.MutableInteger; + +public class AT_ReportContext { + + private static class TestDataManager1 extends TestDataManager { + } + + private static class TestDataManager3 extends TestDataManager { + + } + + private static class TestDataManager3A extends TestDataManager3 { + + } + + private static class TestDataManager3B extends TestDataManager3 { + + } + + private static class TestDataManager4 extends TestDataManager { + + } + + private static class TestDataManager4A extends TestDataManager4 { + + } + + /* + * Executes the simulation by adding TestReport that executes the give + * consumer in a task planned at time zero. Also adds a TestActor with a + * task scheduled at positive infinity to guarantee the execution of the + * report's task. + */ + private void testConsumer(Consumer consumer) { + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + pluginDataBuilder.addTestReportPlan("report", new TestReportPlan(0, consumer)); + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(Double.POSITIVE_INFINITY, (c) -> { + })); + + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + TestSimulation.builder().addPlugin(testPlugin).build().execute(); + + } + + @Test + @UnitTestMethod(target = ReportContext.class, name = "addPlan", args = { Consumer.class, double.class }) + public void testAddPlan_Consumer() { + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + /* + * Show that passive plans do not execute if there are no remaining + * active plans. To do this, we will schedule a few passive plans, one + * active plan and then a few more passive plans. We will then show that + * the passive plans that come after the last active plan never execute + */ + + // create some containers for passive keys + Set expectedOutput = new LinkedHashSet<>(); + expectedOutput.add("A"); + expectedOutput.add("B"); + Set actualOuput = new LinkedHashSet<>(); + + pluginDataBuilder.addTestReportPlan("actor", new TestReportPlan(4, (context) -> { + + // schedule two passive plans + context.addPlan((c) -> { + actualOuput.add("A"); + }, 5); + context.addPlan((c) -> { + actualOuput.add("B"); + }, 6); + + // schedule two more passive plans + context.addPlan((c) -> { + actualOuput.add("C"); + }, 8); + context.addPlan((c) -> { + actualOuput.add("D"); + }, 9); + + })); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(7, (context) -> { + // place holder active plan that drives time to 7.0 + })); + + // build the plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // run the simulation -- we do not need to show that all plans executed + Simulation.builder()// + .addPlugin(testPlugin)// + .build()// + .execute();// + + // show that the last two passive plans did not execute + assertEquals(expectedOutput, actualOuput); + + // precondition test : if the plan is null + ContractException contractException = assertThrows(ContractException.class, () -> testConsumer((c) -> { + c.addPlan(null, 0); + })); + assertEquals(NucleusError.NULL_PLAN_CONSUMER, contractException.getErrorType()); + + // precondition test : if the plan is scheduled for the past + contractException = assertThrows(ContractException.class, () -> testConsumer((c) -> { + c.addPlan((c2) -> { + }, -1); + })); + assertEquals(NucleusError.PAST_PLANNING_TIME, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ReportContext.class, name = "addPlan", args = { Plan.class }) + public void testAddPlan_Plan() { + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + /* + * Show that passive plans do not execute if there are no remaining + * active plans. To do this, we will schedule a few passive plans, one + * active plan and then a few more passive plans. We will then show that + * the passive plans that come after the last active plan never execute + */ + + // create some containers for passive keys + Set expectedOutput = new LinkedHashSet<>(); + expectedOutput.add("A"); + expectedOutput.add("B"); + Set actualOuput = new LinkedHashSet<>(); + + pluginDataBuilder.addTestReportPlan("actor", new TestReportPlan(4, (context) -> { + + // schedule two passive plans + context.addPlan(Plan.builder(ReportContext.class)// + .setActive(true)// + .setCallbackConsumer((c) -> { + actualOuput.add("A"); + })// + .setKey(null)// + .setPlanData(null)// + .setTime(5)// + .build()); + + context.addPlan(Plan.builder(ReportContext.class)// + .setActive(true)// + .setCallbackConsumer((c) -> { + actualOuput.add("B"); + })// + .setKey(null)// + .setPlanData(null)// + .setTime(6)// + .build()); + + // schedule two more passive plans + context.addPlan(Plan.builder(ReportContext.class)// + .setActive(true)// + .setCallbackConsumer((c) -> { + actualOuput.add("C"); + })// + .setKey(null)// + .setPlanData(null)// + .setTime(8)// + .build());// + + context.addPlan(Plan.builder(ReportContext.class)// + .setActive(true)// + .setCallbackConsumer((c) -> { + actualOuput.add("D"); + })// + .setKey(null)// + .setPlanData(null)// + .setTime(9)// + .build()); + })); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(7, (context) -> + + { + // place holder active plan that drives time to 7.0 + })); + + // build the plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // run the simulation -- we do not need to show that all plans executed + Simulation.builder()// + .addPlugin(testPlugin)// + .build()// + .execute();// + + // show that the last two passive plans did not execute + assertEquals(expectedOutput, actualOuput); + + // precondition test : if the plan is null + ContractException contractException = assertThrows(ContractException.class, () -> testConsumer((c) -> { + c.addPlan(Plan.builder(ReportContext.class)// + .setActive(true)// + .setCallbackConsumer(null)// + .setKey(null)// + .setPlanData(null)// + .setTime(9)// + .build()); + })); + assertEquals(NucleusError.NULL_PLAN_CONSUMER, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, () -> testConsumer((c) -> { + c.addPlan(null); + })); + assertEquals(NucleusError.NULL_PLAN, contractException.getErrorType()); + + // precondition test : if the plan is scheduled for the past + contractException = assertThrows(ContractException.class, () -> testConsumer((c) -> { + c.addPlan(Plan.builder(ReportContext.class)// + .setActive(true)// + .setCallbackConsumer(null)// + .setKey(null)// + .setPlanData(null)// + .setTime(-1)// + .build()); + })); + assertEquals(NucleusError.PAST_PLANNING_TIME, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ReportContext.class, name = "getDataManager", args = { Class.class }) + public void testGetDataManager() { + // create the test plugin data builder + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // create a data manager for the report to find + + pluginDataBuilder.addTestDataManager("dm1", () -> new TestDataManager1()); + pluginDataBuilder.addTestDataManager("dm3A", () -> new TestDataManager3A()); + pluginDataBuilder.addTestDataManager("dm3B", () -> new TestDataManager3B()); + pluginDataBuilder.addTestDataManager("dm4A", () -> new TestDataManager4A()); + + pluginDataBuilder.addTestReportPlan("report", new TestReportPlan(0, (c) -> { + c.getDataManager(TestDataManager1.class); + c.getDataManager(TestDataManager3A.class); + c.getDataManager(TestDataManager3B.class); + c.getDataManager(TestDataManager4A.class); + })); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(Double.POSITIVE_INFINITY, (c) -> { + })); + + // build the action plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + TestSimulation.builder().addPlugin(testPlugin).build().execute(); + + // precondition test : if the class reference is ambiguous + pluginDataBuilder.addTestDataManager("dm3A", () -> new TestDataManager3A()); + pluginDataBuilder.addTestDataManager("dm3B", () -> new TestDataManager3B()); + + // show that ambiguous class matching throws an exception + pluginDataBuilder.addTestReportPlan("report", new TestReportPlan(0, (c) -> { + ContractException contractException = assertThrows(ContractException.class, + () -> c.getDataManager(TestDataManager3.class)); + assertEquals(NucleusError.AMBIGUOUS_DATA_MANAGER_CLASS, contractException.getErrorType()); + })); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(Double.POSITIVE_INFINITY, (c) -> { + })); + + // build the action plugin + testPluginData = pluginDataBuilder.build(); + testPlugin = TestPlugin.getTestPlugin(testPluginData); + + TestSimulation.builder().addPlugin(testPlugin).build().execute(); + + // Precondition test 2 + pluginDataBuilder.addTestReportPlan("report", new TestReportPlan(0, (c) -> { + ContractException contractException = assertThrows(ContractException.class, () -> c.getDataManager(null)); + assertEquals(NucleusError.NULL_DATA_MANAGER_CLASS, contractException.getErrorType()); + })); + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(Double.POSITIVE_INFINITY, (c) -> { + })); + // build the action plugin + testPluginData = pluginDataBuilder.build(); + testPlugin = TestPlugin.getTestPlugin(testPluginData); + + TestSimulation.builder().addPlugin(testPlugin).build().execute(); + } + + @Test + @UnitTestMethod(target = ReportContext.class, name = "getPlan", args = { Object.class }) + public void testGetPlan() { + /* + * Have a test report show that a plan added with a key can be retrieved + */ + testConsumer((context) -> { + Object key = new Object(); + assertFalse(context.getPlan(key).isPresent()); + + Plan plan = Plan.builder(ReportContext.class)// + .setCallbackConsumer((c) -> { + })// + .setTime(100)// + .setKey(key)// + .build(); + + context.addPlan(plan); + assertTrue(context.getPlan(key).isPresent()); + }); + + // precondition test : if the plan key is null + ContractException contractException = assertThrows(ContractException.class, + () -> testConsumer((c) -> c.getPlan(null))); + assertEquals(NucleusError.NULL_PLAN_KEY, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = ReportContext.class, name = "getPlanKeys", args = {}) + public void testGetPlanKeys() { + + // There are no precondition tests + Set expectedKeys = new LinkedHashSet<>(); + int keyCount = 20; + for (int i = 0; i < keyCount; i++) { + expectedKeys.add(new Object()); + } + + testConsumer((context) -> { + for (Object key : expectedKeys) { + + Plan plan = Plan.builder(ReportContext.class)// + .setCallbackConsumer((c) -> { + })// + .setTime(100)// + .setKey(key)// + .build();// + + context.addPlan(plan); + } + + Set actualKeys = context.getPlanKeys().stream() + .collect(Collectors.toCollection(LinkedHashSet::new)); + assertEquals(expectedKeys, actualKeys); + }); + } + + @Test + @UnitTestMethod(target = ReportContext.class, name = "getReportId", args = {}) + public void testGetReportId() { + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + double testTime = 1; + // there are no precondition tests + + Set observedReportIds = new LinkedHashSet<>(); + Set expectedReportIds = new LinkedHashSet<>(); + for (int i = 0; i < 3; i++) { + expectedReportIds.add(new ReportId(i)); + } + + /* + * Have actors get their own actor ids and show that these ids match the + * expected values established during the initialization of the + * TestReports. + */ + pluginDataBuilder.addTestReportPlan("Alpha", new TestReportPlan(testTime++, (c) -> { + ReportId reportId = c.getReportId(); + observedReportIds.add(reportId); + assertNotNull(reportId); + + })); + + pluginDataBuilder.addTestReportPlan("Beta", new TestReportPlan(testTime++, (c) -> { + ReportId reportId = c.getReportId(); + observedReportIds.add(reportId); + assertNotNull(reportId); + + })); + + pluginDataBuilder.addTestReportPlan("Gamma", new TestReportPlan(testTime++, (c) -> { + ReportId reportId = c.getReportId(); + observedReportIds.add(reportId); + assertNotNull(reportId); + + })); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(Double.POSITIVE_INFINITY, (c) -> { + assertEquals(expectedReportIds, observedReportIds); + })); + + // build the plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + TestSimulation.builder().addPlugin(testPlugin).build().execute(); + } + + @Test + @UnitTestMethod(target = ReportContext.class, name = "getScheduledSimulationHaltTime", args = {}) + public void testGetScheduledSimulationHaltTime() { + Set stopTimes = new LinkedHashSet<>(); + + stopTimes.add(4.6); + stopTimes.add(13.0); + stopTimes.add(554.3); + stopTimes.add(7.9); + stopTimes.add(400.2); + stopTimes.add(3000.1); + + for (Double stopTime : stopTimes) { + TestPluginData testPluginData = TestPluginData + .builder() + .addTestReportPlan("report plan", new TestReportPlan(0, (context) -> { + assertEquals(stopTime, context.getScheduledSimulationHaltTime()); + })) + .addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + })) + .build(); + + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + TestSimulation.builder().setSimulationHaltTime(stopTime).addPlugin(testPlugin).build().execute(); + } + } + + @Test + @UnitTestMethod(target = ReportContext.class, name = "getTime", args = {}) + public void testGetTime() { + Set planTimes = new LinkedHashSet<>(); + + planTimes.add(4.6); + planTimes.add(13.8764); + planTimes.add(554.345); + planTimes.add(7.95346); + planTimes.add(400.234234); + planTimes.add(3000.12422346); + + /* + * Have a report build plans to check the time in the simulation against + * the planning time + */ + testConsumer((context1) -> { + for (Double planTime : planTimes) { + context1.addPlan((context2) -> { + assertEquals(planTime.doubleValue(), context2.getTime(), 0); + }, planTime); + } + }); + } + + @Test + @UnitTestMethod(target = ReportContext.class, name = "releaseOutput", args = { Object.class }) + public void testReleaseOutput() { + // begin building the action plugin + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // set up the expected output + Set expectedOutput = new LinkedHashSet<>(); + expectedOutput.add("the sly fox"); + expectedOutput.add(15); + expectedOutput.add("the lazy, brown dog"); + expectedOutput.add(45.34513453); + + // have the agent release the output + pluginDataBuilder.addTestReportPlan("report", new TestReportPlan(1, (c) -> { + for (Object outputValue : expectedOutput) { + c.releaseOutput(outputValue); + } + })); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(Double.POSITIVE_INFINITY, (c) -> { + })); + + // build the plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + /* + * Add an output consumer that will place the output into the + * actualOutput set above and then execute the simulation + */ + TestOutputConsumer testOutputConsumer = TestSimulation.builder().addPlugin(testPlugin).build().execute(); + + Map outputItems = testOutputConsumer.getOutputItemMap(Object.class); + for (Object key : outputItems.keySet()) { + Integer count = outputItems.get(key); + assertEquals(1, count.intValue()); + } + + // show that the output matches expectations + assertTrue(outputItems.keySet().containsAll(expectedOutput)); + } + + @Test + @UnitTestMethod(target = ReportContext.class, name = "removePlan", args = { Object.class }) + public void testRemovePlan() { + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // test preconditions + pluginDataBuilder.addTestReportPlan("report", new TestReportPlan(1, (context) -> { + ContractException contractException = assertThrows(ContractException.class, () -> context.removePlan(null)); + assertEquals(NucleusError.NULL_PLAN_KEY, contractException.getErrorType()); + })); + + Object key = new Object(); + MutableBoolean removedPlanHasExecuted = new MutableBoolean(); + + // have the added test report add a plan + pluginDataBuilder.addTestReportPlan("report", new TestReportPlan(2, (context) -> { + Plan plan = Plan.builder(ReportContext.class)// + .setCallbackConsumer((c2) -> { + removedPlanHasExecuted.setValue(true); + })// + .setTime(4)// + .setKey(key)// + .build();// + + context.addPlan(plan); + })); + + // have the test report remove the plan and show the plan no longer + // exists + pluginDataBuilder.addTestReportPlan("report", new TestReportPlan(3, (context) -> { + assertTrue(context.getPlan(key).isPresent()); + + context.removePlan(key); + + assertFalse(context.getPlan(key).isPresent()); + + })); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(Double.POSITIVE_INFINITY, (c) -> { + })); + + // build the plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // run the simulation + TestSimulation.builder().addPlugin(testPlugin).build().execute(); + + // show that the remove plan was not executed + assertFalse(removedPlanHasExecuted.getValue()); + } + + private static class TestEvent1 implements Event { + + } + + @Test + @UnitTestMethod(target = ReportContext.class, name = "subscribe", args = { Class.class, BiConsumer.class }) + public void testSubscribe() { + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + MutableBoolean observed = new MutableBoolean(); + + // have the report subscribe for test events. + pluginDataBuilder.addTestReportPlan("report", new TestReportPlan(0, (c) -> { + c.subscribe(TestEvent1.class, (c2, e) -> { + observed.setValue(true); + }); + })); + + // create a data manager that will generate a test event + + pluginDataBuilder.addTestDataManager("generator", () -> new TestDataManager()); + pluginDataBuilder.addTestDataManagerPlan("generator", new TestDataManagerPlan(1, (c) -> { + c.releaseObservationEvent(new TestEvent1()); + })); + + /* + * show that report received the event + */ + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(Double.POSITIVE_INFINITY, (c) -> { + assertTrue(observed.getValue()); + })); + + // build the plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // build and execute the engine + TestSimulation.builder().addPlugin(testPlugin).build().execute(); + + // precondition test: if the event class is null + ContractException contractException = assertThrows(ContractException.class, () -> testConsumer((c) -> { + c.subscribe(null, (c2, e) -> { + }); + })); + assertEquals(NucleusError.NULL_EVENT_CLASS, contractException.getErrorType()); + + // precondition test: if the event consumer is null + contractException = assertThrows(ContractException.class, () -> testConsumer((c) -> { + c.subscribe(TestEvent1.class, null); + })); + assertEquals(NucleusError.NULL_EVENT_CONSUMER, contractException.getErrorType()); + + // precondition test: if the subsciption duplicates another subscription + contractException = assertThrows(ContractException.class, () -> testConsumer((c) -> { + c.subscribe(TestEvent1.class, (c2, e) -> { + }); + c.subscribe(TestEvent1.class, (c2, e) -> { + }); + })); + assertEquals(NucleusError.DUPLICATE_EVENT_SUBSCRIPTION, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ReportContext.class, name = "subscribeToSimulationClose", args = { Consumer.class }) + public void testSubscribeToSimulationClose() { + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + MutableBoolean simCloseEventHandled = new MutableBoolean(); + + // have a report schedule a few plans and subscribe to simulation close + pluginDataBuilder.addTestReportPlan("report", new TestReportPlan(0, (c) -> { + c.addPlan((c2) -> { + }, 1); + c.addPlan((c2) -> { + }, 2); + c.addPlan((c2) -> { + }, 3); + c.subscribeToSimulationClose((c2) -> { + simCloseEventHandled.setValue(true); + }); + })); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(Double.POSITIVE_INFINITY, (c) -> { + })); + + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + TestSimulation.builder().addPlugin(testPlugin).build().execute(); + + // show that the subscription to simulation close was successful + assertTrue(simCloseEventHandled.getValue()); + + } + + @Test + @UnitTestMethod(target = ReportContext.class, name = "stateRecordingIsScheduled", args = {}) + public void testStateRecordingIsScheduled() { + Set stateRecordingList = new LinkedHashSet<>(); + + stateRecordingList.add(false); + stateRecordingList.add(false); + stateRecordingList.add(true); + stateRecordingList.add(false); + stateRecordingList.add(true); + stateRecordingList.add(true); + + for (Boolean stateRecording : stateRecordingList) { + TestPluginData testPluginData = TestPluginData + .builder() + .addTestReportPlan("actor 1", new TestReportPlan(0, (context) -> { + assertEquals(stateRecording, context.stateRecordingIsScheduled()); + })) + .addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + })) + .build(); + + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + TestSimulation.builder().setSimulationHaltTime(2).setProduceSimulationStateOnHalt(stateRecording) + .addPlugin(testPlugin).build().execute(); + } + } + + @Test + @UnitTestMethod(target = ReportContext.class, name = "unsubscribe", args = { Class.class }) + public void testUnsubscribe() { + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + /* + * Create a container to count the number of times a subscription + * execution occurred + */ + MutableInteger observationCount = new MutableInteger(); + + /* + * have the resolver subscribe to the test event and have it handle each + * type of event handling by incrementing a counter + */ + + int taskTime = 0; + + pluginDataBuilder.addTestReportPlan("report", new TestReportPlan(taskTime++, (c) -> { + c.subscribe(TestEvent1.class, (c2, e) -> { + observationCount.increment(); + }); + })); + + // create a data manager that will produce a test event + pluginDataBuilder.addTestDataManager("generator", () -> new TestDataManager()); + pluginDataBuilder.addTestDataManagerPlan("generator", new TestDataManagerPlan(taskTime++, (c) -> { + c.releaseObservationEvent(new TestEvent1()); + })); + + pluginDataBuilder.addTestDataManagerPlan("generator", new TestDataManagerPlan(taskTime++, (c) -> { + c.releaseObservationEvent(new TestEvent1()); + })); + + pluginDataBuilder.addTestDataManagerPlan("generator", new TestDataManagerPlan(taskTime++, (c) -> { + c.releaseObservationEvent(new TestEvent1()); + })); + + /* + * Show that the phaseExecutionCount is three after the the agent is + * done + */ + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(taskTime++, (c) -> { + assertEquals(3, observationCount.getValue()); + })); + + // have the report unsubscribe + pluginDataBuilder.addTestReportPlan("report", new TestReportPlan(taskTime++, (c) -> { + c.unsubscribe(TestEvent1.class); + })); + + // have the data manager generate another test event + pluginDataBuilder.addTestDataManagerPlan("generator", new TestDataManagerPlan(taskTime++, (c) -> { + c.releaseObservationEvent(new TestEvent1()); + })); + + /* + * Show that the observation count is still three + */ + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(taskTime++, (c) -> { + assertEquals(3, observationCount.getValue()); + })); + + // build the plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // build and execute the engine + TestSimulation.builder().addPlugin(testPlugin).build().execute(); + + // precondition test: if the event class reference is null + ContractException contractException = assertThrows(ContractException.class, () -> testConsumer((c) -> { + c.unsubscribe(null); + })); + assertEquals(NucleusError.NULL_EVENT_CLASS, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ReportContext.class, name = "setPlanDataConverter", args = { Class.class, + Function.class }) + public void testSetPlanDataConverter() { + MutableBoolean called = new MutableBoolean(false); + + class TestPlanData1 implements PlanData { + + } + + Function> planDataConverter = t -> { + return context -> called.setValue(true); + }; + + class TestReport1 { + public void init(ReportContext reportContext) { + reportContext.setPlanDataConverter(TestPlanData1.class, planDataConverter); + } + } + + Plugin actorPlugin = Plugin.builder() + .setPluginId(new SimplePluginId("TestReport1")) + .setInitializer((pContext) -> { + pContext.addReport(new TestReport1()::init); + }) + .build(); + + TestPluginData testPluginData = TestPluginData.builder() + .addTestReportPlan("report plan", new TestReportPlan(1, (rContext) -> { + })) + .addTestActorPlan("actor plan", new TestActorPlan(2, (aContext) -> { + })) + .build(); + + PlanQueueData planQueueData = PlanQueueData.builder() + .setPlanData(new TestPlanData1()) + .setTime(1) + .setPlanner(Planner.REPORT) + .build(); + + SimulationState simulationState = SimulationState.builder() + .addPlanQueueData(planQueueData) + .setStartTime(1) + .setPlanningQueueArrivalId(2) + .build(); + + TestSimulation.builder() + .addPlugin(actorPlugin) + .addPlugin(TestPlugin.getTestPlugin(testPluginData)) + .setSimulationState(simulationState) + .build() + .execute(); + + assertTrue(called.getValue()); + } + + @Test + @UnitTestMethod(target = ReportContext.class, name = "getBaseDate", args = {}) + public void testGetBaseDate() { + + // create some base dates to test + List localDates = new ArrayList<>(); + + localDates.add(LocalDate.of(2023, 1, 10)); + localDates.add(LocalDate.of(2024, 6, 13)); + localDates.add(LocalDate.of(2020, 3, 15)); + localDates.add(LocalDate.of(2023, 12, 25)); + + // loop over the base dates + IntStream.range(0, localDates.size()).forEach((i) -> { + LocalDate localDate = localDates.get(i); + // build a single actor that will show that the base date returned by the + // context is correct + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + pluginDataBuilder.addTestReportPlan("report", new TestReportPlan(0, (c) -> { + assertEquals(localDate, c.getBaseDate()); + })); + //add an actor plan to force the simulation to flow time past the report's task time + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + + })); + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // execute the engine + SimulationState simulationState = SimulationState.builder().setBaseDate(localDate).build(); + TestSimulation.builder().setSimulationState(simulationState).addPlugin(testPlugin).build().execute(); + }); + + } + + @Test + @UnitTestMethod(target = ReportContext.class, name = "getStartTime", args = {}) + public void testGetStartTime() { + + // create some start times to test + List startTimes = new ArrayList<>(); + + startTimes.add(-100.0); + startTimes.add(30.23); + startTimes.add(17.63); + startTimes.add(45.5); + + // loop over the base dates + IntStream.range(0, startTimes.size()).forEach((i) -> { + Double startTime = startTimes.get(i); + // build a single report that will show that the start time returned by the + // context is correct + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + pluginDataBuilder.addTestReportPlan("report", new TestReportPlan(startTime+10,(c)->{ + assertEquals(startTime, c.getStartTime()); + })); + + //add an actor plan to force the simulation to flow time past the report's task time + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(startTime+20,(c)->{})); + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // execute the engine + SimulationState simulationState = SimulationState.builder().setStartTime(startTime).build(); + TestSimulation.builder().setSimulationState(simulationState).addPlugin(testPlugin).build().execute(); + }); + + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_ReportId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_ReportId.java new file mode 100644 index 000000000..0f428d010 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_ReportId.java @@ -0,0 +1,78 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public final class AT_ReportId { + + @UnitTestConstructor(target = ReportId.class, args = { int.class }) + @Test + public void testConstructor() { + for (int i = 0; i < 100; i++) { + assertEquals(i, new ReportId(i).getValue()); + } + } + + @UnitTestMethod(target = ReportId.class, name = "getValue", args = {}) + @Test + public void testGetValue() { + for (int i = 0; i < 100; i++) { + assertEquals(i, new ReportId(i).getValue()); + } + } + + @UnitTestMethod(target = ReportId.class, name = "toString", args = {}) + @Test + public void testToString() { + for (int i = 0; i < 100; i++) { + assertEquals("ReportId [id=" + i + "]", new ReportId(i).toString()); + } + } + + @UnitTestMethod(target = ReportId.class, name = "hashCode", args = {}) + @Test + public void testHashCode() { + // show equal objects have equal hashcodes + for (int i = 0; i < 10; i++) { + ReportId a = new ReportId(i); + ReportId b = new ReportId(i); + assertEquals(a, b); + assertEquals(a.hashCode(), b.hashCode()); + } + + // show that hash codes are dispersed + Set hashcodes = new LinkedHashSet<>(); + for (int i = 0; i < 1000; i++) { + hashcodes.add(new ReportId(i).hashCode()); + } + assertEquals(1000, hashcodes.size()); + + } + + @UnitTestMethod(target = ReportId.class, name = "equals", args = { Object.class }) + @Test + public void testEquals() { + // show actor ids are equal if and only if they have the same base int + // value + for (int i = 0; i < 10; i++) { + ReportId a = new ReportId(i); + for (int j = 0; j < 10; j++) { + ReportId b = new ReportId(j); + if (i == j) { + assertEquals(a, b); + } else { + assertNotEquals(a, b); + } + } + } + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_SimplePluginId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_SimplePluginId.java new file mode 100644 index 000000000..e61c1b29d --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_SimplePluginId.java @@ -0,0 +1,56 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_SimplePluginId { + @Test + @UnitTestConstructor(target = SimplePluginId.class, args = { Object.class }) + public void testConstructor() { + assertThrows(RuntimeException.class, () -> new SimplePluginId(null)); + } + + @Test + @UnitTestMethod(target = SimplePluginId.class, name = "equals", args = { Object.class }) + public void testEquals() { + /* + * SimplePluginIds are equal if and only if their contained values are + * equal + */ + + SimplePluginId simplePluginId_1 = new SimplePluginId("A"); + SimplePluginId simplePluginId_2 = new SimplePluginId("B"); + SimplePluginId simplePluginId_3 = new SimplePluginId("A"); + + assertEquals(simplePluginId_1, simplePluginId_3); + assertNotEquals(simplePluginId_1, simplePluginId_2); + + } + + @Test + @UnitTestMethod(target = SimplePluginId.class, name = "hashCode", args = {}) + public void testHashCode() { + /* + * Equal objects have equal hash codes + */ + for (int i = 0; i < 20; i++) { + SimplePluginId simplePluginId_1 = new SimplePluginId(i); + SimplePluginId simplePluginId_2 = new SimplePluginId(i); + assertEquals(simplePluginId_1.hashCode(), simplePluginId_2.hashCode()); + } + } + + @Test + @UnitTestMethod(target = SimplePluginId.class, name = "toString", args = {}) + public void testToString() { + assertEquals("A", new SimplePluginId("A").toString()); + assertEquals("ASDF", new SimplePluginId("ASDF").toString()); + assertEquals(Integer.toString(12), new SimplePluginId(12).toString()); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_Simulation.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_Simulation.java new file mode 100644 index 000000000..fce5e24d6 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_Simulation.java @@ -0,0 +1,440 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.time.LocalDate; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestOutputConsumer; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import util.annotations.UnitTag; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.wrappers.MutableBoolean; +import util.wrappers.MutableInteger; + +public class AT_Simulation { + + @Test + @UnitTestMethod(target = Simulation.class, name = "execute", args = {}) + public void testExecute() { + + // run the simulation + Simulation simulation = Simulation.builder().build(); + simulation.execute(); + + // precondition test + ContractException contractException = assertThrows(ContractException.class, () -> simulation.execute()); + assertEquals(NucleusError.REPEATED_EXECUTION, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = Simulation.class, name = "builder", args = {}) + public void testBuilder() { + assertNotNull(Simulation.builder()); + } + + @Test + @UnitTestMethod(target = Simulation.Builder.class, name = "build", args = {}, tags = { UnitTag.INCOMPLETE }) + public void testbuild() { + /* + * There is no test that reflects circular dependency + */ + + assertNotNull(Simulation.builder().build()); + } + + private static class PluginData1 implements PluginData { + @Override + public PluginDataBuilder getCloneBuilder() { + throw new UnsupportedOperationException(); + } + } + + private static class PluginData2 implements PluginData { + @Override + public PluginDataBuilder getCloneBuilder() { + throw new UnsupportedOperationException(); + } + } + + @Test + @UnitTestMethod(target = Simulation.Builder.class, name = "addPlugin", args = { Plugin.class }) + public void testAddPlugin() { + + /* + * Show that the plugin is added correctly by showing that its init + * method is invoked and that the plugin data are available from the + * plugin context. + */ + + MutableBoolean pluginAssertionsExecuted = new MutableBoolean(); + + Plugin plugin = Plugin .builder()// + .setPluginId(new SimplePluginId("plugin"))// + .addPluginData(new PluginData1())// + .addPluginData(new PluginData2())// + .setInitializer((c) -> { + assertNotNull(c.getPluginData(PluginData1.class)); + assertNotNull(c.getPluginData(PluginData2.class)); + pluginAssertionsExecuted.setValue(true); + })// + .build();// + + Simulation .builder()// + .addPlugin(plugin)// + .build()// + .execute(); + + /* + * Show that the initializer containing the assertions was executed + */ + assertTrue(pluginAssertionsExecuted.getValue()); + + } + + private static class LocalOutputConsumer implements Consumer { + + private final Set receivedItems = new LinkedHashSet<>(); + + @Override + public void accept(Object t) { + receivedItems.add(t); + } + + } + + @Test + @UnitTestMethod(target = Simulation.Builder.class, name = "setOutputConsumer", args = { Consumer.class }) + public void testSetOutputConsumer() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + /* + * Case 1 : there is a non-null output consumer added to the builder + */ + + Set expectedValues = new LinkedHashSet<>(); + expectedValues.add("A"); + expectedValues.add(4.5); + expectedValues.add(424.75F); + expectedValues.add(12); + expectedValues.add(122423533423423453L); + expectedValues.add(false); + + // have the added test agent produce some output + pluginBuilder.addTestActorPlan("Alpha", new TestActorPlan(1, (context) -> { + for (Object value : expectedValues) { + context.releaseOutput(value); + } + })); + + // create two output consumers to show that the builder will only use + // the last one + LocalOutputConsumer localOutputConsumer1 = new LocalOutputConsumer(); + LocalOutputConsumer localOutputConsumer2 = new LocalOutputConsumer(); + + TestPluginData testPluginData = pluginBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // run the simulation + Simulation .builder()// + .addPlugin(testPlugin)// + .setOutputConsumer(localOutputConsumer1)// + .setOutputConsumer(localOutputConsumer2)// + .build()// + .execute();// + + // show that the first local output consumer did not receive any values + assertTrue(localOutputConsumer1.receivedItems.isEmpty()); + + // show that the second local output consumer received the expected + // values + assertTrue(localOutputConsumer2.receivedItems.containsAll(expectedValues)); + + /* + * Case 2 : there is null output consumer added to the builder + */ + + // show that the setting of null for the output consumer will yield no + // output + + LocalOutputConsumer localOutputConsumer3 = new LocalOutputConsumer(); + + Simulation .builder()// + .addPlugin(testPlugin)// + .setOutputConsumer(localOutputConsumer3)// + .setOutputConsumer(null)// + .build()// + .execute();// + + // show that the first local output consumer did not receive any values + assertTrue(localOutputConsumer3.receivedItems.isEmpty()); + + } + + + //support class for testSetProduceSimulationStateOnHalt() test method + private static class AlphaPluginDataBuilder implements PluginDataBuilder { + private AlphaPluginData alphaPluginData = new AlphaPluginData(); + + @Override + public AlphaPluginData build() { + try { + return alphaPluginData; + } finally { + alphaPluginData = new AlphaPluginData(); + } + } + + public AlphaPluginDataBuilder setX(int x) { + alphaPluginData.x = x; + return this; + } + + } + //support class for testSetProduceSimulationStateOnHalt() test method + private static class AlphaPluginData implements PluginData { + private int x; + + @Override + public PluginDataBuilder getCloneBuilder() { + AlphaPluginDataBuilder result = new AlphaPluginDataBuilder(); + result.setX(x); + return result; + } + + public int getX() { + return x; + } + } + //support class for testSetProduceSimulationStateOnHalt() test method + private static class AlphaDataManager extends DataManager { + private int x; + + public AlphaDataManager(AlphaPluginData alphaPluginData) { + x = alphaPluginData.getX(); + } + + @Override + public void init(DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + dataManagerContext.subscribeToSimulationClose((c) -> { + c.releaseOutput(new AlphaPluginDataBuilder().setX(x).build()); + }); + } + + public void setX(int x) { + + this.x = x; + } + + } + //support for testSetProduceSimulationStateOnHalt() test method + private static final PluginId ALPHA_PLUGIN_ID = new SimplePluginId("Alpha Plugin Id"); + + //support for testSetProduceSimulationStateOnHalt() test method + private static Plugin getAlphaPlugin(AlphaPluginData alphaPluginData) { + return Plugin .builder()// + .setPluginId(ALPHA_PLUGIN_ID)// + .addPluginData(alphaPluginData)// + .setInitializer(c -> { + AlphaPluginData pluginData = c.getPluginData(AlphaPluginData.class).get(); + c.addDataManager(new AlphaDataManager(pluginData)); + }).build();// + } + + @Test + @UnitTestMethod(target = Simulation.Builder.class, name = "setSimulationState", args = { SimulationState.class }) + public void testSetSimulationState() { + + AlphaPluginData alphaPluginData = new AlphaPluginDataBuilder().setX(10).build(); + Plugin alphaPlugin = getAlphaPlugin(alphaPluginData); + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + MutableInteger rollingXValue = new MutableInteger(); + + // have actor set the value of x a few times + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + AlphaDataManager alphaDataManager = c.getDataManager(AlphaDataManager.class); + rollingXValue.setValue(55); + alphaDataManager.setX(rollingXValue.getValue()); + + })); + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + AlphaDataManager alphaDataManager = c.getDataManager(AlphaDataManager.class); + rollingXValue.setValue(12); + alphaDataManager.setX(rollingXValue.getValue()); + })); + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(3, (c) -> { + AlphaDataManager alphaDataManager = c.getDataManager(AlphaDataManager.class); + rollingXValue.setValue(87); + alphaDataManager.setX(rollingXValue.getValue()); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + TestOutputConsumer testOutputConsumer = new TestOutputConsumer(); + LocalDate localDate = LocalDate.of(2023, 03, 11); + SimulationState startingSimulationState = SimulationState.builder().setBaseDate(localDate).build(); + + // run the simulation + Simulation .builder()// + .addPlugin(alphaPlugin)// + .addPlugin(testPlugin)// + .setSimulationState(startingSimulationState)// + .setOutputConsumer(testOutputConsumer)// + .setRecordState(true)// + .setSimulationHaltTime(20.0)// + .build()// + .execute();// + + // show that the simulation time data is correct + Map simulationStateItems = testOutputConsumer.getOutputItemMap(SimulationState.class); + assertEquals(1, simulationStateItems.size()); + SimulationState simulationTime = simulationStateItems.keySet().iterator().next(); + Integer count = simulationStateItems.get(simulationTime); + assertEquals(1, count); + assertEquals(localDate, simulationTime.getBaseDate()); + assertEquals(20.0, simulationTime.getStartTime()); + + // show that there are two plugins and that the AlphaPluginData contains + // the last value of x + Map pluginDataItems = testOutputConsumer.getOutputItemMap(AlphaPluginData.class); + + assertEquals(1, pluginDataItems.size()); + AlphaPluginData outputAlphaPluginData = pluginDataItems.keySet().iterator().next(); + assertEquals(rollingXValue.getValue(), outputAlphaPluginData.getX()); + + // show that if we explicitly set the production to false that nothing + // is produced + testOutputConsumer = new TestOutputConsumer(); + Simulation .builder()// + .addPlugin(alphaPlugin)// + .addPlugin(testPlugin)// + .setSimulationState(startingSimulationState)// + .setOutputConsumer(testOutputConsumer)// + .setRecordState(false)// + .build()// + .execute();// + + assertTrue(testOutputConsumer.getOutputItemMap(SimulationState.class).isEmpty()); + assertTrue(testOutputConsumer.getOutputItemMap(Plugin.class).isEmpty()); + + // show that if we do not set the production to false that nothing + // is produced + testOutputConsumer = new TestOutputConsumer(); + Simulation .builder()// + .addPlugin(alphaPlugin)// + .addPlugin(testPlugin)// + .setSimulationState(startingSimulationState)// + .setOutputConsumer(testOutputConsumer)// + .build()// + .execute();// + + assertTrue(testOutputConsumer.getOutputItemMap(SimulationState.class).isEmpty()); + assertTrue(testOutputConsumer.getOutputItemMap(Plugin.class).isEmpty()); + + } + + + + @Test + @UnitTestMethod(target = Simulation.Builder.class, name = "setRecordState", args = { boolean.class }) + public void testSetRecordState() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + + + String expectedStateRecording = "expectedStateRecording"; + + // have the added test agent produce some output + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (context) -> { + context.subscribeToSimulationClose((c)->c.releaseOutput(expectedStateRecording)); + })); + + + TestOutputConsumer testOutputConsumer = new TestOutputConsumer(); + + + TestPluginData testPluginData = pluginBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + double haltTime = 20; + + // run the simulation + Simulation .builder()// + .addPlugin(testPlugin)// + .setOutputConsumer(testOutputConsumer)// + .setRecordState(true)// + .setSimulationHaltTime(haltTime)// + .build()// + .execute();// + + //show that the simulation halts at the given time + SimulationState simulationState = testOutputConsumer.getOutputItem(SimulationState.class).get(); + assertEquals(haltTime, simulationState.getStartTime()); + + + //show that the actor records its state in the output of the simulation + assertTrue(testOutputConsumer.getOutputItems(Object.class).contains(expectedStateRecording)); + } + + + @Test + @UnitTestMethod(target = Simulation.Builder.class, name = "setSimulationHaltTime", args = { Double.class }) + public void testSetSimulationHaltTime() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + + + String expectedStateRecording = "expectedStateRecording"; + + // have the added test agent produce some output + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (context) -> { + context.subscribeToSimulationClose((c)->c.releaseOutput(expectedStateRecording)); + })); + + + TestOutputConsumer testOutputConsumer = new TestOutputConsumer(); + + + TestPluginData testPluginData = pluginBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + double haltTime = 20; + + // run the simulation + Simulation .builder()// + .addPlugin(testPlugin)// + .setOutputConsumer(testOutputConsumer)// + .setRecordState(true)// + .setSimulationHaltTime(haltTime)// + .build()// + .execute();// + + //show that the simulation halts at the given time + SimulationState simulationState = testOutputConsumer.getOutputItem(SimulationState.class).get(); + assertEquals(haltTime, simulationState.getStartTime()); + + + //show that the actor records its state in the output of the simulation + assertTrue(testOutputConsumer.getOutputItems(Object.class).contains(expectedStateRecording)); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_SimulationState.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_SimulationState.java new file mode 100644 index 000000000..a9f467cab --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_SimulationState.java @@ -0,0 +1,332 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_SimulationState { + + class TestPlanData1 implements PlanData { + + } + + @Test + @UnitTestMethod(target = SimulationState.Builder.class, name = "build", args = {}) + public void testBuild() { + assertNotNull(SimulationState.builder().build()); + + // preconditions + ContractException contractException = assertThrows(ContractException.class, () -> { + + SimulationState.builder() + .addPlanQueueData( + PlanQueueData.builder().setPlanner(Planner.ACTOR).setPlanData(new TestPlanData1()).build()) + .build(); + }); + + assertEquals(NucleusError.PLANNING_QUEUE_ARRIVAL_INVALID, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, () -> { + + SimulationState + .builder().addPlanQueueData(PlanQueueData.builder().setPlanner(Planner.ACTOR) + .setPlanData(new TestPlanData1()).setTime(1).build()) + .setPlanningQueueArrivalId(1).setStartTime(5).build(); + }); + + assertEquals(NucleusError.PLANNING_QUEUE_TIME, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = SimulationState.Builder.class, name = "setPlanningQueueArrivalId", args = { long.class }) + public void testSetPlanningQueueArrivalId() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(594478369345316212L); + + for (int i = 0; i < 10; i++) { + long arrivalId = randomGenerator.nextLong(); + SimulationState simulationState = SimulationState.builder().setPlanningQueueArrivalId(arrivalId).build(); + + assertEquals(arrivalId, simulationState.getPlanningQueueArrivalId()); + } + } + + @Test + @UnitTestMethod(target = SimulationState.Builder.class, name = "setStartTime", args = { double.class }) + public void testSetStartTime() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6278569817385464648L); + + for (int i = 0; i < 10; i++) { + double startTime = randomGenerator.nextDouble() * 10; + SimulationState simulationState = SimulationState.builder().setStartTime(startTime).build(); + + assertEquals(startTime, simulationState.getStartTime()); + } + } + + @Test + @UnitTestMethod(target = SimulationState.Builder.class, name = "setBaseDate", args = { LocalDate.class }) + public void testSetBaseDate() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(196036555746621355L); + + for (int i = 0; i < 10; i++) { + int month = randomGenerator.nextInt(12) + 1; + int day; + int year = 2023 + randomGenerator.nextInt(2); + + if (month == 2) { + day = randomGenerator.nextInt(28) + 1; + } else if (month == 9 || month == 4 || month == 6 || month == 11) { + day = randomGenerator.nextInt(30) + 1; + } else { + day = randomGenerator.nextInt(31) + 1; + } + + LocalDate localDate = LocalDate.of(year, month, day); + SimulationState simulationState = SimulationState.builder().setBaseDate(localDate).build(); + + assertEquals(localDate, simulationState.getBaseDate()); + } + + // precondition: + // LocalDate is null + ContractException contractException = assertThrows(ContractException.class, () -> { + SimulationState.builder().setBaseDate(null); + }); + + assertEquals(NucleusError.NULL_BASE_DATE, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = SimulationState.Builder.class, name = "addPlanQueueData", args = { PlanQueueData.class }) + public void testAddPlanQueueData() { + for (int i = 0; i < 10; i++) { + + SimulationState.Builder simulationStateBuilder = SimulationState.builder(); + List expectedPlanQueueDatas = new ArrayList<>(); + for (int j = 0; j < i; j++) { + PlanQueueData planQueueData = PlanQueueData.builder().setPlanData(new TestPlanData1()).setTime(i) + .setPlanner(Planner.ACTOR).build(); + + simulationStateBuilder.addPlanQueueData(planQueueData); + expectedPlanQueueDatas.add(planQueueData); + } + + SimulationState simulationState = simulationStateBuilder.setPlanningQueueArrivalId(1).build(); + + List actualPlanQueueDatas = simulationState.getPlanQueueDatas(); + assertEquals(expectedPlanQueueDatas, actualPlanQueueDatas); + } + + // precondition: + // LocalDate is null + ContractException contractException = assertThrows(ContractException.class, () -> { + SimulationState.builder().addPlanQueueData(null); + }); + + assertEquals(NucleusError.NULL_PLAN_QUEUE_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = SimulationState.class, name = "builder", args = {}) + public void testBuilder() { + assertNotNull(SimulationState.builder()); + } + + @Test + @UnitTestMethod(target = SimulationState.class, name = "getPlanningQueueArrivalId", args = {}) + public void testGetPlanningQueueArrivalId() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2212750783748698797L); + + for (int i = 0; i < 10; i++) { + long arrivalId = randomGenerator.nextLong(); + SimulationState simulationState = SimulationState.builder().setPlanningQueueArrivalId(arrivalId).build(); + + assertEquals(arrivalId, simulationState.getPlanningQueueArrivalId()); + } + } + + @Test + @UnitTestMethod(target = SimulationState.class, name = "getStartTime", args = {}) + public void testGetStartTime() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5508769779925956678L); + + for (int i = 0; i < 10; i++) { + double startTime = randomGenerator.nextDouble() * 10; + SimulationState simulationState = SimulationState.builder().setStartTime(startTime).build(); + + assertEquals(startTime, simulationState.getStartTime()); + } + } + + @Test + @UnitTestMethod(target = SimulationState.class, name = "getPlanQueueDatas", args = {}) + public void testGetPlanQueueDatas() { + for (int i = 0; i < 10; i++) { + + SimulationState.Builder simulationStateBuilder = SimulationState.builder(); + List expectedPlanQueueDatas = new ArrayList<>(); + for (int j = 0; j < i; j++) { + PlanQueueData planQueueData = PlanQueueData.builder().setPlanData(new TestPlanData1()).setTime(i) + .setPlanner(Planner.ACTOR).build(); + + simulationStateBuilder.addPlanQueueData(planQueueData); + expectedPlanQueueDatas.add(planQueueData); + } + + SimulationState simulationState = simulationStateBuilder.setPlanningQueueArrivalId(1).build(); + + List actualPlanQueueDatas = simulationState.getPlanQueueDatas(); + assertEquals(expectedPlanQueueDatas, actualPlanQueueDatas); + } + } + + @Test + @UnitTestMethod(target = SimulationState.class, name = "getBaseDate", args = {}) + public void testGetBaseDate() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1840591792412124210L); + + for (int i = 0; i < 10; i++) { + int month = randomGenerator.nextInt(12) + 1; + int day; + int year = 2023 + randomGenerator.nextInt(2); + + if (month == 2) { + day = randomGenerator.nextInt(28) + 1; + } else if (month == 9 || month == 4 || month == 6 || month == 11) { + day = randomGenerator.nextInt(30) + 1; + } else { + day = randomGenerator.nextInt(31) + 1; + } + + LocalDate localDate = LocalDate.of(year, month, day); + SimulationState simulationState = SimulationState.builder().setBaseDate(localDate).build(); + + assertEquals(localDate, simulationState.getBaseDate()); + } + } + + private static class TestPlanData implements PlanData { + private final int index; + + public TestPlanData(int index) { + this.index = index; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + index; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof TestPlanData)) { + return false; + } + TestPlanData other = (TestPlanData) obj; + if (index != other.index) { + return false; + } + return true; + } + + } + + private PlanQueueData getRandomPlanQueueData(long seed) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + return PlanQueueData.builder()// + .setActive(randomGenerator.nextBoolean())// + .setArrivalId(randomGenerator.nextInt(1000000))// + .setKey(Integer.toString(randomGenerator.nextInt()))// + .setPlanData(new TestPlanData(randomGenerator.nextInt())) + .setPlanner(Planner.values()[randomGenerator.nextInt(Planner.values().length)])// + .setPlannerId(randomGenerator.nextInt())// + .setTime(randomGenerator.nextDouble()*10+100)// + .build(); + } + + private SimulationState getRandomSimulationState(long seed) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + return SimulationState.builder()// + .setBaseDate(LocalDate.of(randomGenerator.nextInt(20) + 2000, randomGenerator.nextInt(12) + 1, + randomGenerator.nextInt(28) + 1)) + .setPlanningQueueArrivalId(randomGenerator.nextInt(10000)+1000000)// + .setStartTime(randomGenerator.nextDouble() * 100) + .addPlanQueueData(getRandomPlanQueueData(randomGenerator.nextLong()))// + .addPlanQueueData(getRandomPlanQueueData(randomGenerator.nextLong()))// + .addPlanQueueData(getRandomPlanQueueData(randomGenerator.nextLong()))// + .setStartTime(randomGenerator.nextDouble()*100) + .build(); + } + + @Test + @UnitTestMethod(target = SimulationState.class, name = "equals", args = { Object.class }) + public void testEquals() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1814317894811919552L); + + // show not equal to null + for (int i = 0; i < 30; i++) { + SimulationState simulationState = getRandomSimulationState(randomGenerator.nextLong()); + assertFalse(simulationState.equals(null)); + } + + // show reflexivity + for (int i = 0; i < 30; i++) { + SimulationState simulationState = getRandomSimulationState(randomGenerator.nextLong()); + assertTrue(simulationState.equals(simulationState)); + } + + // show symmetry/transitivity + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + + SimulationState simulationState1 = getRandomSimulationState(seed); + SimulationState simulationState2 = getRandomSimulationState(seed); + assertTrue(simulationState1.equals(simulationState2)); + assertTrue(simulationState2.equals(simulationState1)); + } + + //show different inputs cause non-equality + for (int i = 0; i < 30; i++) { + SimulationState simulationState1 = getRandomSimulationState(randomGenerator.nextLong()); + SimulationState simulationState2 = getRandomSimulationState(randomGenerator.nextLong()); + assertNotEquals(simulationState1,simulationState2); + } + + } + + @Test + @UnitTestMethod(target = SimulationState.class, name = "hashCode", args = {}) + public void testHashCode() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3169333378872001748L); + + //show equal objects have equal hash codes + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + SimulationState simulationState1 = getRandomSimulationState(seed); + SimulationState simulationState2 = getRandomSimulationState(seed); + assertEquals(simulationState1,simulationState2); + assertEquals(simulationState1.hashCode(),simulationState2.hashCode()); + } + + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_SimulationStateCollector.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_SimulationStateCollector.java new file mode 100644 index 000000000..8c0ed1fb8 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_SimulationStateCollector.java @@ -0,0 +1,318 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.wrappers.MutableBoolean; + +public class AT_SimulationStateCollector { + @Test + @UnitTestConstructor(target = SimulationStateCollector.class, args = { BiConsumer.class, Consumer.class }) + public void testSimulationStateCollector() { + // nothing to test + } + + private static class AlphaPluginDataBuilder implements PluginDataBuilder { + private int alpha; + + @Override + public AlphaPluginData build() { + return new AlphaPluginData(alpha); + } + + public void setAlpha(int alpha) { + this.alpha = alpha; + } + + } + + private static class AlphaPluginData implements PluginData { + private final int alpha; + + public AlphaPluginData(int alpha) { + this.alpha = alpha; + } + + @Override + public PluginDataBuilder getCloneBuilder() { + AlphaPluginDataBuilder result = new AlphaPluginDataBuilder(); + result.setAlpha(alpha); + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("AlphaPluginData [alpha="); + builder.append(alpha); + builder.append("]"); + return builder.toString(); + } + + } + + private static class AlphaActor implements Consumer { + private final AlphaPluginData alphaPluginData; + + public AlphaActor(AlphaPluginData alphaPluginData) { + this.alphaPluginData = alphaPluginData; + } + + @Override + public void accept(ActorContext c) { + c.subscribeToSimulationClose((c2) -> { + c2.releaseOutput(alphaPluginData); + }); + } + + } + + private static Plugin getAlphaPlugin() { + AlphaPluginDataBuilder alphaPluginDataBuilder = new AlphaPluginDataBuilder(); + alphaPluginDataBuilder.setAlpha(0); + AlphaPluginData alphaPluginData = alphaPluginDataBuilder.build(); + + return Plugin.builder()// + .setPluginId(new SimplePluginId("Alpha_Plugin_Id"))// + .addPluginData(alphaPluginData)// + .setInitializer((c) -> { + AlphaPluginData pluginData = c.getPluginData(AlphaPluginData.class).get(); + c.addActor(new AlphaActor(pluginData)); + }).build(); + } + + private static Dimension getAlphaDimension() { + return FunctionalDimension.builder()// + .addMetaDatum("Alpha")// + .addLevel((context) -> { + AlphaPluginDataBuilder alphaPluginDataBuilder = context + .getPluginDataBuilder(AlphaPluginDataBuilder.class); + int value = 17; + alphaPluginDataBuilder.setAlpha(value); + List result = new ArrayList<>(); + result.add(Integer.toString(value)); + return result; + })// + .addLevel((context) -> { + AlphaPluginDataBuilder alphaPluginDataBuilder = context + .getPluginDataBuilder(AlphaPluginDataBuilder.class); + int value = 25; + alphaPluginDataBuilder.setAlpha(value); + List result = new ArrayList<>(); + result.add(Integer.toString(value)); + return result; + })// + .build();// + } + + private static class BetaPluginDataBuilder implements PluginDataBuilder { + private double beta; + + @Override + public BetaPluginData build() { + return new BetaPluginData(beta); + } + + public void setBeta(double beta) { + this.beta = beta; + } + + } + + private static class BetaPluginData implements PluginData { + private final double beta; + + public BetaPluginData(double beta) { + this.beta = beta; + } + + @Override + public PluginDataBuilder getCloneBuilder() { + BetaPluginDataBuilder result = new BetaPluginDataBuilder(); + result.setBeta(beta); + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("BetaPluginData [beta="); + builder.append(beta); + builder.append("]"); + return builder.toString(); + } + + } + + private static class BetaActor implements Consumer { + private final BetaPluginData betaPluginData; + + public BetaActor(BetaPluginData betaPluginData) { + this.betaPluginData = betaPluginData; + } + + @Override + public void accept(ActorContext c) { + c.subscribeToSimulationClose((c2) -> { + c2.releaseOutput(betaPluginData); + }); + } + + } + + private static Plugin getBetaPlugin() { + BetaPluginDataBuilder betaPluginDataBuilder = new BetaPluginDataBuilder(); + betaPluginDataBuilder.setBeta(0.0); + BetaPluginData betaPluginData = betaPluginDataBuilder.build(); + + return Plugin.builder()// + .setPluginId(new SimplePluginId("Beta_Plugin_Id"))// + .addPluginData(betaPluginData)// + .setInitializer((c) -> { + BetaPluginData pluginData = c.getPluginData(BetaPluginData.class).get(); + c.addActor(new BetaActor(pluginData)); + }).build(); + } + + private static Dimension getBetaDimension() { + return FunctionalDimension.builder()// + .addMetaDatum("Beta")// + .addLevel((context) -> { + BetaPluginDataBuilder betaPluginDataBuilder = context + .getPluginDataBuilder(BetaPluginDataBuilder.class); + double value = 12.9; + betaPluginDataBuilder.setBeta(value); + List result = new ArrayList<>(); + result.add(Double.toString(value)); + return result; + })// + .addLevel((context) -> { + BetaPluginDataBuilder betaPluginDataBuilder = context + .getPluginDataBuilder(BetaPluginDataBuilder.class); + double value = 16.8; + betaPluginDataBuilder.setBeta(value); + List result = new ArrayList<>(); + result.add(Double.toString(value)); + return result; + })// + .addLevel((context) -> { + BetaPluginDataBuilder betaPluginDataBuilder = context + .getPluginDataBuilder(BetaPluginDataBuilder.class); + double value = 38.6; + betaPluginDataBuilder.setBeta(value); + List result = new ArrayList<>(); + result.add(Double.toString(value)); + return result; + })// + .build();// + } + + @Test + @UnitTestMethod(target = SimulationStateCollector.class, name = "accept", args = {ExperimentContext.class}) + public void testAccept() { + + // create a few maps to hold information collected from the execution of the + // experiment + MutableBoolean experimentOpenConsumerInvoked = new MutableBoolean(); + Map outputSimulationStates = new LinkedHashMap<>(); + Map>> outputPluginDataClasses = new LinkedHashMap<>(); + + /* + * Create the two consumers that form the SimulationStateCollector. These + * consumers will fill in the maps above. + */ + BiConsumer> scenarioConsumer = (scenarioId, list) -> { + + for (Object item : list) { + if (item instanceof SimulationState) { + SimulationState simulationState = (SimulationState) item; + outputSimulationStates.put(scenarioId, simulationState); + } else if (item instanceof PluginData) { + PluginData pluginData = (PluginData) item; + Set> pluginClasses = outputPluginDataClasses.get(scenarioId); + if (pluginClasses == null) { + pluginClasses = new LinkedHashSet<>(); + outputPluginDataClasses.put(scenarioId, pluginClasses); + } + pluginClasses.add(pluginData.getClass()); + } + } + }; + + Consumer experimentOpenConsumer = (c) -> { + experimentOpenConsumerInvoked.setValue(true); + }; + + // create the SimulationStateCollector + SimulationStateCollector simulationStateCollector = new SimulationStateCollector(scenarioConsumer, + experimentOpenConsumer); + + // Collect the experiment parameter data + ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()// + .setRecordState(true)// + .setSimulationHaltTime(100.0)// + .build();// + + /* + * Execute the experiment. We will execute 6 scenarios over two dimensions -- + * there will be two values for the Alpha plugin and three values for the Beta + * plugin + */ + Experiment.builder()// + .setExperimentParameterData(experimentParameterData)// + .addDimension(getAlphaDimension())// + .addPlugin(getAlphaPlugin())// + .addPlugin(getBetaPlugin())// + .addDimension(getBetaDimension())// + .addExperimentContextConsumer(simulationStateCollector)// + .build()// + .execute();// + + // show that the experiment consumer associated with the experiment opening was + // invoked + assertTrue(experimentOpenConsumerInvoked.getValue()); + + /* + * show that the collected SimulationState objects all indicate that the + * simulation terminated at time 100 and that there is one such value for each + * scenario + */ + Set expectedScearioIds = new LinkedHashSet<>(); + for (int i = 0; i < 6; i++) { + expectedScearioIds.add(i); + } + assertEquals(expectedScearioIds, outputSimulationStates.keySet()); + + for (SimulationState simulationState : outputSimulationStates.values()) { + assertEquals(100.0, simulationState.getStartTime()); + } + + // show that the plugin data classes were collected for each scenario + assertEquals(expectedScearioIds, outputPluginDataClasses.keySet()); + + Set> expectedPluginDataClasses = new LinkedHashSet<>(); + expectedPluginDataClasses.add(AlphaPluginData.class); + expectedPluginDataClasses.add(BetaPluginData.class); + + // show that the correct plugin data classes were returned from each scenario + // execution + for (Set> classes : outputPluginDataClasses.values()) { + assertEquals(expectedPluginDataClasses, classes); + } + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_StatusConsoleState.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_StatusConsoleState.java new file mode 100644 index 000000000..06fef8903 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/AT_StatusConsoleState.java @@ -0,0 +1,154 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_StatusConsoleState { + + @Test + @UnitTestConstructor(target = StatusConsoleState.class, args = {}) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = StatusConsoleState.class, name = "immediateErrorReporting", args = {}) + public void testImmediateErrorReporting() { + StatusConsoleState statusConsoleState = new StatusConsoleState(); + assertFalse(statusConsoleState.immediateErrorReporting()); + + statusConsoleState.setImmediateErrorReporting(true); + assertTrue(statusConsoleState.immediateErrorReporting()); + + statusConsoleState.setImmediateErrorReporting(false); + assertFalse(statusConsoleState.immediateErrorReporting()); + } + + @Test + @UnitTestMethod(target = StatusConsoleState.class, name = "setImmediateErrorReporting", args = { boolean.class }) + public void testSetImmediateErrorReporting() { + StatusConsoleState statusConsoleState = new StatusConsoleState(); + assertFalse(statusConsoleState.immediateErrorReporting()); + + statusConsoleState.setImmediateErrorReporting(true); + assertTrue(statusConsoleState.immediateErrorReporting()); + + statusConsoleState.setImmediateErrorReporting(false); + assertFalse(statusConsoleState.immediateErrorReporting()); + } + + @Test + @UnitTestMethod(target = StatusConsoleState.class, name = "reportScenarioProgress", args = {}) + public void testReportScenarioProgress() { + StatusConsoleState statusConsoleState = new StatusConsoleState(); + assertFalse(statusConsoleState.reportScenarioProgress()); + + statusConsoleState.setReportScenarioProgress(true); + assertTrue(statusConsoleState.reportScenarioProgress()); + + statusConsoleState.setReportScenarioProgress(false); + assertFalse(statusConsoleState.reportScenarioProgress()); + } + + @Test + @UnitTestMethod(target = StatusConsoleState.class, name = "setReportScenarioProgress", args = { boolean.class }) + public void testSetReportScenarioProgress() { + StatusConsoleState statusConsoleState = new StatusConsoleState(); + + statusConsoleState.setReportScenarioProgress(true); + assertTrue(statusConsoleState.reportScenarioProgress()); + + statusConsoleState.setReportScenarioProgress(false); + assertFalse(statusConsoleState.reportScenarioProgress()); + } + + @Test + @UnitTestMethod(target = StatusConsoleState.class, name = "getStackTraceReportLimit", args = {}) + public void testGetStackTraceReportLimit() { + StatusConsoleState statusConsoleState = new StatusConsoleState(); + assertEquals(0, statusConsoleState.getStackTraceReportLimit()); + + statusConsoleState.setStackTraceReportLimit(35); + assertEquals(35, statusConsoleState.getStackTraceReportLimit()); + + statusConsoleState.setStackTraceReportLimit(10); + assertEquals(10, statusConsoleState.getStackTraceReportLimit()); + } + + @Test + @UnitTestMethod(target = StatusConsoleState.class, name = "setStackTraceReportLimit", args = { int.class }) + public void testSetStackTraceReportLimit() { + StatusConsoleState statusConsoleState = new StatusConsoleState(); + + statusConsoleState.setStackTraceReportLimit(35); + assertEquals(35, statusConsoleState.getStackTraceReportLimit()); + + statusConsoleState.setStackTraceReportLimit(10); + assertEquals(10, statusConsoleState.getStackTraceReportLimit()); + } + + @Test + @UnitTestMethod(target = StatusConsoleState.class, name = "getLastReportedCompletionPercentage", args = {}) + public void testGetLastReportedCompletionPercentage() { + StatusConsoleState statusConsoleState = new StatusConsoleState(); + assertEquals(0, statusConsoleState.getLastReportedCompletionPercentage()); + + statusConsoleState.setLastReportedCompletionPercentage(14); + assertEquals(14, statusConsoleState.getLastReportedCompletionPercentage()); + + } + + @Test + @UnitTestMethod(target = StatusConsoleState.class, name = "setLastReportedCompletionPercentage", args = { int.class }) + public void testSetLastReportedCompletionPercentage() { + StatusConsoleState statusConsoleState = new StatusConsoleState(); + + statusConsoleState.setLastReportedCompletionPercentage(14); + assertEquals(14, statusConsoleState.getLastReportedCompletionPercentage()); + + statusConsoleState.setLastReportedCompletionPercentage(88); + assertEquals(88, statusConsoleState.getLastReportedCompletionPercentage()); + + statusConsoleState.setLastReportedCompletionPercentage(57); + assertEquals(57, statusConsoleState.getLastReportedCompletionPercentage()); + } + + @Test + @UnitTestMethod(target = StatusConsoleState.class, name = "getImmediateStackTraceCount", args = {}) + public void testGetImmediateStackTraceCount() { + StatusConsoleState statusConsoleState = new StatusConsoleState(); + assertEquals(0, statusConsoleState.getImmediateStackTraceCount()); + + statusConsoleState.incrementImmediateStackTraceCount(); + assertEquals(1, statusConsoleState.getImmediateStackTraceCount()); + + statusConsoleState.incrementImmediateStackTraceCount(); + assertEquals(2, statusConsoleState.getImmediateStackTraceCount()); + + statusConsoleState.incrementImmediateStackTraceCount(); + assertEquals(3, statusConsoleState.getImmediateStackTraceCount()); + + } + + @Test + @UnitTestMethod(target = StatusConsoleState.class, name = "incrementImmediateStackTraceCount", args = {}) + public void testIncrementImmediateStackTraceCount() { + StatusConsoleState statusConsoleState = new StatusConsoleState(); + + statusConsoleState.incrementImmediateStackTraceCount(); + assertEquals(1, statusConsoleState.getImmediateStackTraceCount()); + + statusConsoleState.incrementImmediateStackTraceCount(); + assertEquals(2, statusConsoleState.getImmediateStackTraceCount()); + + statusConsoleState.incrementImmediateStackTraceCount(); + assertEquals(3, statusConsoleState.getImmediateStackTraceCount()); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/MT_Experiment.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/MT_Experiment.java new file mode 100644 index 000000000..44cf87f3f --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/MT_Experiment.java @@ -0,0 +1,80 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import java.util.ArrayList; + +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; + +/** + * Manual experiment test focusing on the generation and handling of exceptions. + * + */ +public class MT_Experiment { + + //@Test + public void test() { + main(new String[] {}); + } + + private MT_Experiment() { + + } + + public static void main(String[] args) { + new MT_Experiment().excecute(); + } + + private int counter; + + private final Object LOCK = new Object(); + + private Dimension getDimension(final int dimSize) { + FunctionalDimension.Builder builder = FunctionalDimension.builder(); + for (int i = 0; i < dimSize; i++) { + builder.addLevel((c) -> new ArrayList<>()); + } + return builder.build(); + } + + private void excecute() { + + // use the test plugin to generate an agent + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // have one of the six actors throw an exception + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + synchronized (LOCK) { + counter++; + if (counter % 5 == 1 ) { + throw new RuntimeException("test exception"); + } + } + })); + + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + + ExperimentStatusConsole experimentStatusConsole = // + ExperimentStatusConsole .builder()// + .setImmediateErrorReporting(true)// + .setReportScenarioProgress(false)// + .setStackTraceReportLimit(3)// + .build();// + + ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()// + .setHaltOnException(false)// + .setThreadCount(10)// + .build(); + + Experiment .builder()// + .addPlugin(testPlugin)// + .addDimension(getDimension(100))// + .addExperimentContextConsumer(experimentStatusConsole)// + .setExperimentParameterData(experimentParameterData)// + .build()// + .execute();// + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/MT_ExperimentStatusConsole.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/MT_ExperimentStatusConsole.java new file mode 100644 index 000000000..f55282063 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/MT_ExperimentStatusConsole.java @@ -0,0 +1,291 @@ +package gov.hhs.aspr.ms.gcm.nucleus; + +import java.util.ArrayList; + +public class MT_ExperimentStatusConsole { + private MT_ExperimentStatusConsole() { + } + + private Dimension getDimension(int size) { + FunctionalDimension.Builder builder = FunctionalDimension.builder(); + for (int i = 0; i < size; i++) { + builder.addLevel((c) -> new ArrayList<>()); + } + return builder.build(); + } + + private void execute() { + // uncomment each test method individually to perform the tests + + /* + * tests for reporting scenario progress + */ + + // testSetReportScenarioProgressDefault(); + // testSetReportScenarioProgressOn(); + // testSetReportScenarioProgressOff(); + + /* + * tests for immediate error reporting + */ + + // testSetImmediateErrorReporting_Default(); + // testSetImmediateErrorReporting_False(); + // testSetImmediateErrorReporting_True(); + + /* + * tests for stack trace report limit + */ + // testSetStackTraceReportLimit_Default(); + // testSetStackTraceReportLimit_5(); + // testSetStackTraceReportLimit_0(); + + } + + @SuppressWarnings("unused") + private void testSetReportScenarioProgress_Default() { + ExperimentStatusConsole experimentStatusConsole = // + ExperimentStatusConsole// + .builder()// + .build(); + + Experiment .builder()// + .addExperimentContextConsumer(experimentStatusConsole)// + .addDimension(getDimension(5))// + .build()// + .execute();// + + /* + * Observe: the five scenarios should be reported in the console. + * + */ + + } + + @SuppressWarnings("unused") + private void testSetReportScenarioProgress_On() { + ExperimentStatusConsole experimentStatusConsole = // + ExperimentStatusConsole// + .builder()// + .setReportScenarioProgress(true)// + .build(); + + Experiment .builder()// + .addExperimentContextConsumer(experimentStatusConsole)// + .addDimension(getDimension(5))// + .build()// + .execute();// + /* + * Observe: the five scenarios should be reported in the console. + * + */ + + } + + @SuppressWarnings("unused") + private void testSetReportScenarioProgress_Off() { + ExperimentStatusConsole experimentStatusConsole = // + ExperimentStatusConsole// + .builder()// + .setReportScenarioProgress(false)// + .build(); + + Experiment .builder()// + .addExperimentContextConsumer(experimentStatusConsole)// + .addDimension(getDimension(5))// + .build()// + .execute();// + + /* + * Observe: the five scenarios are not reported in the console. Only the + * summary and exit of the console are printed. + * + */ + } + + @SuppressWarnings("unused") + private void testSetImmediateErrorReporting_Default() { + ExperimentStatusConsole experimentStatusConsole = // + ExperimentStatusConsole// + .builder()// + .build(); + + Plugin plugin = Plugin .builder()// + .setPluginId(new SimplePluginId("plugin"))// + .setInitializer((c) -> { + c.addActor((c2) -> { + throw new RuntimeException(); + }); + }).build(); + + Experiment .builder()// + .addExperimentContextConsumer(experimentStatusConsole)// + .addDimension(getDimension(1000))// + .addPlugin(plugin)// + .build()// + .execute();// + + /* + * Observe: The errors are not reported during the run, but are reported + * as part of the summary. + * + */ + } + + @SuppressWarnings("unused") + private void testSetImmediateErrorReporting_False() { + ExperimentStatusConsole experimentStatusConsole = // + ExperimentStatusConsole// + .builder()// + .setImmediateErrorReporting(false)// + .build(); + + Plugin plugin = Plugin .builder()// + .setPluginId(new SimplePluginId("plugin"))// + .setInitializer((c) -> { + c.addActor((c2) -> { + throw new RuntimeException(); + }); + }).build(); + + Experiment .builder()// + .addExperimentContextConsumer(experimentStatusConsole)// + .addDimension(getDimension(1000))// + .addPlugin(plugin)// + .build()// + .execute();// + + /* + * Observe: The errors are not reported during the run, but are reported + * as part of the summary. + * + */ + } + + @SuppressWarnings("unused") + private void testSetImmediateErrorReporting_True() { + ExperimentStatusConsole experimentStatusConsole = // + ExperimentStatusConsole// + .builder()// + .setImmediateErrorReporting(true)// + .build(); + + Plugin plugin = Plugin .builder()// + .setPluginId(new SimplePluginId("plugin"))// + .setInitializer((c) -> { + c.addActor((c2) -> { + throw new RuntimeException(); + }); + }).build(); + + Experiment .builder()// + .addExperimentContextConsumer(experimentStatusConsole)// + .addDimension(getDimension(1000))// + .addPlugin(plugin)// + .build()// + .execute();// + + /* + * Observe: The errors are reported during the run as well as part of + * the summary. + * + */ + } + + @SuppressWarnings("unused") + private void testSetStackTraceReportLimit_Default() { + ExperimentStatusConsole experimentStatusConsole = // + ExperimentStatusConsole// + .builder()// + .setImmediateErrorReporting(true)// + .build(); + + Plugin plugin = Plugin .builder()// + .setPluginId(new SimplePluginId("plugin"))// + .setInitializer((c) -> { + c.addActor((c2) -> { + throw new RuntimeException(); + }); + }).build(); + + Experiment .builder()// + .addExperimentContextConsumer(experimentStatusConsole)// + .addDimension(getDimension(1000))// + .addPlugin(plugin)// + .build()// + .execute();// + + /* + * Observe: There are 1000 exceptions but reporting is limited to 100 + * stack traces. The are 100 stack traces reported immediately. There + * are 100 stack traces reported in the summary. + */ + } + + @SuppressWarnings("unused") + private void testSetStackTraceReportLimit_5() { + ExperimentStatusConsole experimentStatusConsole = // + ExperimentStatusConsole// + .builder()// + .setImmediateErrorReporting(true)// + .setStackTraceReportLimit(5)// + .build(); + + Plugin plugin = Plugin .builder()// + .setPluginId(new SimplePluginId("plugin"))// + .setInitializer((c) -> { + c.addActor((c2) -> { + throw new RuntimeException(); + }); + }).build(); + + Experiment .builder()// + .addExperimentContextConsumer(experimentStatusConsole)// + .addDimension(getDimension(1000))// + .addPlugin(plugin)// + .build()// + .execute();// + + /* + * Observe: There are 1000 exceptions but reporting is limited to 5 + * stack traces. The are 5 stack traces reported immediately. There are + * 5 stack traces reported in the summary. + */ + } + + @SuppressWarnings("unused") + private void testSetStackTraceReportLimit_0() { + ExperimentStatusConsole experimentStatusConsole = // + ExperimentStatusConsole// + .builder()// + .setImmediateErrorReporting(true)// + .setStackTraceReportLimit(0)// + .build(); + + Plugin plugin = Plugin .builder()// + .setPluginId(new SimplePluginId("plugin"))// + .setInitializer((c) -> { + c.addActor((c2) -> { + throw new RuntimeException(); + }); + }).build(); + + Experiment .builder()// + .addExperimentContextConsumer(experimentStatusConsole)// + .addDimension(getDimension(1000))// + .addPlugin(plugin)// + .build()// + .execute();// + + /* + * Observe: There are 1000 exceptions but reporting is limited to 0 + * stack traces. The are no stack traces reported. + */ + } + + public static void main(String[] args) { + // go to execute() method for instructions + new MT_ExperimentStatusConsole().execute(); + } + +} diff --git a/gcm3/src/test/java/nucleus/MT_ProgressFileTest.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/MT_ProgressFileTest.java similarity index 76% rename from gcm3/src/test/java/nucleus/MT_ProgressFileTest.java rename to gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/MT_ProgressFileTest.java index 44ecc9779..901976e20 100644 --- a/gcm3/src/test/java/nucleus/MT_ProgressFileTest.java +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/MT_ProgressFileTest.java @@ -1,4 +1,4 @@ -package nucleus; +package gov.hhs.aspr.ms.gcm.nucleus; import java.nio.file.Path; import java.nio.file.Paths; @@ -9,7 +9,7 @@ public class MT_ProgressFileTest { public static void main(String[] args) { Path experimentProgressPath = Paths.get(args[0]); - Dimension dimension1 = Dimension.builder()// + Dimension dimension1 = FunctionalDimension.builder()// .addMetaDatum("alpha")// .addMetaDatum("beta")// .addLevel((t) -> { @@ -27,7 +27,7 @@ public static void main(String[] args) { .build(); - Dimension dimension2 = Dimension.builder()// + Dimension dimension2 = FunctionalDimension.builder()// .addMetaDatum("comet")// .addMetaDatum("cupid")// .addMetaDatum("vixen")// @@ -55,12 +55,17 @@ public static void main(String[] args) { .build(); + ExperimentParameterData experimentParameterData = ExperimentParameterData .builder()// + .setContinueFromProgressLog(true)// + .setExperimentProgressLog(experimentProgressPath)// + .build();// + Experiment// .builder()// .addDimension(dimension1)// .addDimension(dimension2)// - .setContinueFromProgressLog(true)// - .setExperimentProgressLog(experimentProgressPath).build()// + .setExperimentParameterData(experimentParameterData)// + .build()// .execute();// } diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/TestFactoryUtil.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/TestFactoryUtil.java new file mode 100644 index 000000000..c2404f7ed --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/TestFactoryUtil.java @@ -0,0 +1,43 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; + +public class TestFactoryUtil { + /* + * Given a list of plugins, will show that the plugin with the given pluginId + * exists, and exists EXACTLY once. + */ + public static Plugin checkPluginExists(List plugins, PluginId pluginId) { + Plugin actualPlugin = null; + for (Plugin plugin : plugins) { + if (plugin.getPluginId().equals(pluginId)) { + assertNull(actualPlugin); + actualPlugin = plugin; + } + } + assertNotNull(actualPlugin); + + return actualPlugin; + } + + /** + * Given a list of plugins, will show that the explicit plugindata for the given + * pluginid exists, and exists EXACTLY once. + */ + public static void checkPluginDataExists(List plugins, T expectedPluginData, + PluginId pluginId) { + assertNotNull(expectedPluginData); + Plugin plugin = checkPluginExists(plugins, pluginId); + List pluginDatas = plugin.getPluginDatas(); + assertNotNull(pluginDatas); + assertTrue(pluginDatas.contains(expectedPluginData)); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/runcontinuityplugin/AT_RunContinuityActor.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/runcontinuityplugin/AT_RunContinuityActor.java new file mode 100644 index 000000000..f9d9b1db9 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/runcontinuityplugin/AT_RunContinuityActor.java @@ -0,0 +1,65 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.runcontinuityplugin; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.Simulation; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestOutputConsumer; +import util.annotations.UnitTag; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_RunContinuityActor { + + @Test + @UnitTestConstructor(target = RunContinuityActor.class, args = { RunContinuityPluginData.class }, tags = { + UnitTag.LOCAL_PROXY }) + public void testRunContinuityPluginData() { + // test covered by test of RunContinuityActor.accept() + } + + @Test + @UnitTestMethod(target = RunContinuityActor.class, name = "accept", args = { ActorContext.class }) + public void testAccept() { + + Set expectedOutput = new LinkedHashSet<>(); + expectedOutput.add(0.0); + expectedOutput.add(1.0); + expectedOutput.add(4.5); + expectedOutput.add(7.23); + expectedOutput.add(90.60); + expectedOutput.add(100.0); + + RunContinuityPluginData.Builder builder = RunContinuityPluginData.builder();// + for (Double time : expectedOutput) { + builder.addContextConsumer(time, (c) -> c.releaseOutput(c.getTime())); + } + RunContinuityPluginData runContinuityPluginData = builder.build(); + + Plugin plugin = RunContinuityPlugin.builder()// + .setRunContinuityPluginData(runContinuityPluginData)// + .build(); + + TestOutputConsumer outputConsumer = new TestOutputConsumer(); + Simulation.builder()// + .addPlugin(plugin)// + .setOutputConsumer(outputConsumer)// + .build()// + .execute(); + + runContinuityPluginData = outputConsumer.getOutputItem(RunContinuityPluginData.class).get(); + assertEquals(expectedOutput.size(), runContinuityPluginData.getCompletionCount()); + + + Set actualOutput = new LinkedHashSet<>(outputConsumer.getOutputItems(Double.class)); + + assertEquals(expectedOutput, actualOutput); + + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/runcontinuityplugin/AT_RunContinuityError.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/runcontinuityplugin/AT_RunContinuityError.java new file mode 100644 index 000000000..a8c37f1b8 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/runcontinuityplugin/AT_RunContinuityError.java @@ -0,0 +1,32 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.runcontinuityplugin; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestMethod; + +public class AT_RunContinuityError { + + @Test + @UnitTestMethod(target = RunContinuityError.class,name = "getDescription", args = {}) + public void testGetDescription() { + // show that each ErrorType has a non-null, non-empty description + for (RunContinuityError runContinuityError : RunContinuityError.values()) { + assertNotNull(runContinuityError.getDescription()); + assertTrue(runContinuityError.getDescription().length() > 0); + } + + // show that each description is unique (ignoring case as well) + Set descriptions = new LinkedHashSet<>(); + for (RunContinuityError runContinuityError : RunContinuityError.values()) { + assertTrue(descriptions.add(runContinuityError.getDescription().toLowerCase()), runContinuityError+": "+"Duplicate ErrorType description: " + runContinuityError.getDescription()); + } + } + + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/runcontinuityplugin/AT_RunContinuityPlanData.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/runcontinuityplugin/AT_RunContinuityPlanData.java new file mode 100644 index 000000000..e506ae18a --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/runcontinuityplugin/AT_RunContinuityPlanData.java @@ -0,0 +1,29 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.runcontinuityplugin; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_RunContinuityPlanData { + + @Test + @UnitTestConstructor(target = RunContinuityPlanData.class, args = { int.class }) + public void testRunContinuityPlanData() { + for (int i = 0; i < 10; i++) { + RunContinuityPlanData runContinuityPlanData = new RunContinuityPlanData(i); + assertEquals(i, runContinuityPlanData.getId()); + } + } + + @Test + @UnitTestMethod(target = RunContinuityPlanData.class, name = "getId", args = {}) + public void testGetId() { + for (int i = 0; i < 10; i++) { + RunContinuityPlanData runContinuityPlanData = new RunContinuityPlanData(i); + assertEquals(i, runContinuityPlanData.getId()); + } + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/runcontinuityplugin/AT_RunContinuityPlugin.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/runcontinuityplugin/AT_RunContinuityPlugin.java new file mode 100644 index 000000000..d40f30c3d --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/runcontinuityplugin/AT_RunContinuityPlugin.java @@ -0,0 +1,57 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.runcontinuityplugin; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import util.annotations.UnitTag; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + +public class AT_RunContinuityPlugin { + + @Test + @UnitTestMethod(target = RunContinuityPlugin.class, name = "builder", args = {}) + public void testBuilder() { + assertNotNull(RunContinuityPlugin.builder()); + } + + @Test + @UnitTestMethod(target = RunContinuityPlugin.Builder.class, name = "build", args = {}) + public void testBuild() { + + + RunContinuityPluginData runContinuityPluginData = RunContinuityPluginData.builder().build(); + + Plugin plugin = RunContinuityPlugin.builder() + .setRunContinuityPluginData(runContinuityPluginData) + .build(); + assertNotNull(plugin); + assertEquals(RunContinuityPluginId.PLUGIN_ID, plugin.getPluginId()); + + List pluginDatas = plugin.getPluginDatas(); + assertNotNull(pluginDatas); + assertEquals(1,pluginDatas.size()); + PluginData pluginData = pluginDatas.get(0); + assertTrue(pluginData==runContinuityPluginData); + assertNotNull(plugin.getInitializer()); + + //precondition test: if no plugin data was included + ContractException contractException = assertThrows(ContractException.class, ()->RunContinuityPlugin.builder().build()); + assertEquals(RunContinuityError.NULL_RUN_CONTINUITY_PLUGN_DATA,contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = RunContinuityPlugin.Builder.class, name = "setRunContinuityPluginData", args = {RunContinuityPluginData.class}, tags= {UnitTag.LOCAL_PROXY}) + public void testSetRunContinuityPluginData() { + //covered by test of RunContinuityPlugin.Builder.build() + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/runcontinuityplugin/AT_RunContinuityPluginData.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/runcontinuityplugin/AT_RunContinuityPluginData.java new file mode 100644 index 000000000..5ae6005af --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/runcontinuityplugin/AT_RunContinuityPluginData.java @@ -0,0 +1,204 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.runcontinuityplugin; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import org.apache.commons.math3.random.RandomGenerator; +import org.apache.commons.math3.util.Pair; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import util.annotations.UnitTag; +import util.annotations.UnitTestMethod; +import util.random.RandomGeneratorProvider; + +public class AT_RunContinuityPluginData { + + @Test + @UnitTestMethod(target = RunContinuityPluginData.class, name = "builder", args = {}) + public void testBuilder() { + assertNotNull(RunContinuityPluginData.builder()); + } + + @Test + @UnitTestMethod(target = RunContinuityPluginData.class, name = "getCompletionCount", args = {}) + public void testGetCompletionCount() { + for (int i = 0; i < 10; i++) { + RunContinuityPluginData runContinuityPluginData = RunContinuityPluginData.builder().setCompletionCount(i) + .build(); + assertEquals(i, runContinuityPluginData.getCompletionCount()); + } + + } + + @Test + @UnitTestMethod(target = RunContinuityPluginData.class, name = "getConsumers", args = {}) + public void testGetConsumers() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5172593709525641500L); + List>> expectedPairs = new ArrayList<>(); + + for (int i = 0; i < 10; i++) { + Pair> pair = new Pair>( + randomGenerator.nextDouble(), (c) -> { + }); + expectedPairs.add(pair); + } + + RunContinuityPluginData.Builder builder = RunContinuityPluginData.builder();// + for (Pair> pair : expectedPairs) { + builder.addContextConsumer(pair.getFirst(), pair.getSecond()); + } + RunContinuityPluginData runContinuityPluginData = builder.build(); + + assertEquals(expectedPairs, runContinuityPluginData.getConsumers()); + + } + + @Test + @UnitTestMethod(target = RunContinuityPluginData.class, name = "getCloneBuilder", args = {}) + public void testGetCloneBuilder() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5885906221170851986L); + + for (int i = 0; i < 10; i++) { + + RunContinuityPluginData.Builder builder = RunContinuityPluginData.builder()// + .setCompletionCount(randomGenerator.nextInt(3))// + .setPlansAreScheduled(randomGenerator.nextBoolean());// + for (int j = 0; j < 3; j++) { + builder.addContextConsumer(randomGenerator.nextDouble(), (c) -> { + }); + } + RunContinuityPluginData runContinuityPluginData = builder.build(); + + RunContinuityPluginData cloneRunContinuityPluginData = // + (RunContinuityPluginData) runContinuityPluginData.getCloneBuilder().build(); + + assertEquals(runContinuityPluginData.getCompletionCount(), + cloneRunContinuityPluginData.getCompletionCount()); + + assertEquals(runContinuityPluginData.getConsumers(), cloneRunContinuityPluginData.getConsumers()); + + } + } + + @Test + @UnitTestMethod(target = RunContinuityPluginData.class, name = "plansAreScheduled", args = {}) + public void testPlansAreScheduled() { + RunContinuityPluginData runContinuityPluginData = RunContinuityPluginData.builder().setPlansAreScheduled(true) + .build(); + assertTrue(runContinuityPluginData.plansAreScheduled()); + + runContinuityPluginData = RunContinuityPluginData.builder().setPlansAreScheduled(false).build(); + assertFalse(runContinuityPluginData.plansAreScheduled()); + + } + + @Test + @UnitTestMethod(target = RunContinuityPluginData.class, name = "allPlansComplete", args = {}) + public void testAllPlansComplete() { + assertTrue(RunContinuityPluginData.builder()// + .setCompletionCount(0)// + .setPlansAreScheduled(true)// + .build().allPlansComplete()); + + assertTrue(RunContinuityPluginData.builder()// + .setCompletionCount(3)// + .setPlansAreScheduled(true)// + .build().allPlansComplete()); + + + assertFalse(RunContinuityPluginData.builder()// + .setCompletionCount(0)// + .setPlansAreScheduled(true)// + .addContextConsumer(1.0, (c) -> { + })// + .build().allPlansComplete()); + + assertTrue(RunContinuityPluginData.builder()// + .setCompletionCount(1)// + .setPlansAreScheduled(true)// + .addContextConsumer(1.0, (c) -> { + })// + .build().allPlansComplete()); + + + assertFalse(RunContinuityPluginData.builder()// + .setCompletionCount(1)// + .setPlansAreScheduled(true)// + .addContextConsumer(1.0, (c) -> { + })// + .addContextConsumer(1.0, (c) -> { + })// + .build().allPlansComplete()); + + assertTrue(RunContinuityPluginData.builder()// + .setCompletionCount(2)// + .setPlansAreScheduled(true)// + .addContextConsumer(1.0, (c) -> { + })// + .addContextConsumer(1.0, (c) -> { + })// + .build().allPlansComplete()); + + + + } + + @Test + @UnitTestMethod(target = RunContinuityPluginData.Builder.class, name = "build", args = {}, tags = {UnitTag.LOCAL_PROXY}) + public void testBuild() { + //covered by other tests + } + + @Test + @UnitTestMethod(target = RunContinuityPluginData.Builder.class, name = "setPlansAreScheduled", args = { + boolean.class }) + public void testSetPlansAreScheduled() { + RunContinuityPluginData runContinuityPluginData = RunContinuityPluginData.builder().setPlansAreScheduled(true) + .build(); + assertTrue(runContinuityPluginData.plansAreScheduled()); + + runContinuityPluginData = RunContinuityPluginData.builder().setPlansAreScheduled(false).build(); + assertFalse(runContinuityPluginData.plansAreScheduled()); + } + + @Test + @UnitTestMethod(target = RunContinuityPluginData.Builder.class, name = "addContextConsumer", args = { double.class, + Consumer.class }) + public void testAddContextConsumer() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7652825578007143462L); + List>> expectedPairs = new ArrayList<>(); + + for (int i = 0; i < 10; i++) { + Pair> pair = new Pair>( + randomGenerator.nextDouble(), (c) -> { + }); + expectedPairs.add(pair); + } + + RunContinuityPluginData.Builder builder = RunContinuityPluginData.builder();// + for (Pair> pair : expectedPairs) { + builder.addContextConsumer(pair.getFirst(), pair.getSecond()); + } + RunContinuityPluginData runContinuityPluginData = builder.build(); + + assertEquals(expectedPairs, runContinuityPluginData.getConsumers()); + } + + @Test + @UnitTestMethod(target = RunContinuityPluginData.Builder.class, name = "setCompletionCount", args = { int.class }) + public void testSetCompletionCount() { + for (int i = 0; i < 10; i++) { + RunContinuityPluginData runContinuityPluginData = RunContinuityPluginData.builder().setCompletionCount(i) + .build(); + assertEquals(i, runContinuityPluginData.getCompletionCount()); + } + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/runcontinuityplugin/AT_RunContinuityPluginId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/runcontinuityplugin/AT_RunContinuityPluginId.java new file mode 100644 index 000000000..c3c8c5063 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/runcontinuityplugin/AT_RunContinuityPluginId.java @@ -0,0 +1,17 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.runcontinuityplugin; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestField; + +public class AT_RunContinuityPluginId { + + @Test + @UnitTestField(target = RunContinuityPluginId.class, name = "PLUGIN_ID") + public void testPluginId() { + assertNotNull(RunContinuityPluginId.PLUGIN_ID); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestActor.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestActor.java new file mode 100644 index 000000000..ddcd9daf3 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestActor.java @@ -0,0 +1,79 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.wrappers.MultiKey; + +public class AT_TestActor { + + @Test + @UnitTestMethod(target = TestActor.class, name = "init", args = { ActorContext.class }) + public void testInit() { + // create two aliases + Object alias1 = "actor alias 1"; + Object alias2 = "actor alias 2"; + + // create containers for expected and actual observations + Set expectedObservations = new LinkedHashSet<>(); + expectedObservations.add(new MultiKey(alias1, 3.0)); + expectedObservations.add(new MultiKey(alias2, 3.0)); + expectedObservations.add(new MultiKey(alias1, 4.212)); + expectedObservations.add(new MultiKey(alias1, 5.123)); + expectedObservations.add(new MultiKey(alias2, 43.0)); + expectedObservations.add(new MultiKey(alias1, 12.123)); + expectedObservations.add(new MultiKey(alias1, 8.534)); + expectedObservations.add(new MultiKey(alias2, 1.423)); + + Set actualObservations = new LinkedHashSet<>(); + + // add the actors to the action plugin + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + /* + * Create ActorActionPlans from the expected observations. Each action + * plan will record a Multikey into the actual observations. + */ + for (MultiKey multiKey : expectedObservations) { + Object expectedAlias = multiKey.getKey(0); + Double expectedTime = multiKey.getKey(1); + pluginDataBuilder.addTestActorPlan(expectedAlias, new TestActorPlan(expectedTime, (c) -> { + actualObservations.add(new MultiKey(expectedAlias, c.getTime())); + })); + } + + // build the action plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + //execute the simulation + List plugins = new ArrayList<>(); + plugins.add(testPlugin); + + TestSimulation.builder().addPlugins(plugins).build().execute(); + + // show that the actors executed the expected actions + assertEquals(expectedObservations, actualObservations); + + } + + @Test + @UnitTestConstructor(target = TestActor.class, args = { Object.class }) + public void testConstructor() { + /* + * The test of the init() method suffices to show that the alias value + * passed in the constructor is utilized as designed. + */ + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestActorPlan.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestActorPlan.java new file mode 100644 index 000000000..fd845110d --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestActorPlan.java @@ -0,0 +1,137 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.function.Consumer; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.random.RandomGeneratorProvider; + +public class AT_TestActorPlan { + + @Test + @UnitTestConstructor(target = TestActorPlan.class, args = { double.class, Consumer.class }) + public void testConstructor() { + + TestActorPlan testActorPlan = new TestActorPlan(0.0, (c) -> { + }); + assertEquals(0.0, testActorPlan.getScheduledTime()); + assertFalse(testActorPlan.executed()); + } + + @Test + @UnitTestConstructor(target = TestActorPlan.class, args = { TestActorPlan.class }) + public void testConstructor_fromExistingPlan() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7814286176804755234L); + + for (int i = 0; i < 100; i++) { + double scheduledTime = randomGenerator.nextDouble(); + + TestActorPlan originalTestActorPlan = new TestActorPlan(scheduledTime, (c) -> { + }); + TestActorPlan newTestActorPlan = new TestActorPlan(originalTestActorPlan); + + assertEquals(scheduledTime, newTestActorPlan.getScheduledTime()); + + } + } + + /** + * Show that the agent action plan can be executed and will result in + * executed() returning true even if an exception is thrown in the plan. + */ + @Test + @UnitTestMethod(target = TestActorPlan.class, name = "executed", args = {}) + public void testExecuted() { + + TestActorPlan testActorPlan = new TestActorPlan(0.0, (c) -> { + }); + assertFalse(testActorPlan.executed()); + testActorPlan.executeAction(null); + assertTrue(testActorPlan.executed()); + + TestActorPlan testActorPlanWithException = new TestActorPlan(0.0, (c) -> { + throw new RuntimeException(); + }); + assertFalse(testActorPlanWithException.executed()); + assertThrows(RuntimeException.class, () -> testActorPlanWithException.executeAction(null)); + assertTrue(testActorPlanWithException.executed()); + } + + + + @Test + @UnitTestMethod(target = TestActorPlan.class, name = "getScheduledTime", args = {}) + public void testGetScheduledTime() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(918257164535899051L); + + // use the various constructors + for (int i = 0; i < 300; i++) { + double planTime = randomGenerator.nextDouble() * 1000; + TestActorPlan testActorPlan = new TestActorPlan(planTime, (c) -> { + }); + assertEquals(planTime, testActorPlan.getScheduledTime()); + + testActorPlan = new TestActorPlan(planTime, (c) -> { + }); + assertEquals(planTime, testActorPlan.getScheduledTime()); + + testActorPlan = new TestActorPlan(planTime, (c) -> { + }); + assertEquals(planTime, testActorPlan.getScheduledTime()); + } + + } + + @Test + @UnitTestMethod(target = TestActorPlan.class, name = "equals", args = { Object.class }) + public void testEquals() { + + + TestActorPlan plan1 = new TestActorPlan(4.5, (c) -> { + }); + TestActorPlan plan2 = new TestActorPlan(4.5, (c) -> { + }); + assertEquals(plan1, plan2); + + + plan1 = new TestActorPlan(6.5, (c) -> { + }); + plan2 = new TestActorPlan(4.5, (c) -> { + }); + assertNotEquals(plan1, plan2); + + + + } + + @Test + @UnitTestMethod(target = TestActorPlan.class, name = "hashCode", args = {}) + public void testHashCode() { + /* + * show that equal objects have equal hash codes + */ + TestActorPlan plan1 = new TestActorPlan(4.5, (c) -> { + }); + TestActorPlan plan2 = new TestActorPlan(4.5, (c) -> { + }); + assertEquals(plan1.hashCode(), plan2.hashCode()); + + // via the copy constructor + plan1 = new TestActorPlan(4.5, (c) -> { + }); + plan2 = new TestActorPlan(plan1); + assertEquals(plan1.hashCode(), plan2.hashCode()); + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestDataManager.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestDataManager.java new file mode 100644 index 000000000..1f12fdc1e --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestDataManager.java @@ -0,0 +1,86 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import util.annotations.UnitTag; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.wrappers.MultiKey; + +public class AT_TestDataManager { + + private static class TestDataManagerType1 extends TestDataManager { + + } + + private static class TestDataManagerType2 extends TestDataManager { + + } + + @Test + @UnitTestMethod(target = TestDataManager.class,name = "init", args = { DataManagerContext.class }) + public void testInit() { + // create two aliases + Object alias1 = "alias 1"; + Object alias2 = "alias 2"; + + // create containers for expected and actual observations + Set expectedObservations = new LinkedHashSet<>(); + expectedObservations.add(new MultiKey(alias1, 3.0)); + expectedObservations.add(new MultiKey(alias2, 3.0)); + expectedObservations.add(new MultiKey(alias1, 4.212)); + expectedObservations.add(new MultiKey(alias1, 5.123)); + expectedObservations.add(new MultiKey(alias2, 43.0)); + expectedObservations.add(new MultiKey(alias1, 12.123)); + expectedObservations.add(new MultiKey(alias1, 8.534)); + expectedObservations.add(new MultiKey(alias2, 1.423)); + + Set actualObservations = new LinkedHashSet<>(); + + // add the actors to the action plugin + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + pluginDataBuilder.addTestDataManager(alias1, () -> new TestDataManagerType1()); + pluginDataBuilder.addTestDataManager(alias2, () -> new TestDataManagerType2()); + + /* + * Create ActorActionPlans from the expected observations. Each action + * plan will record a Multikey into the actual observations. + */ + for (MultiKey multiKey : expectedObservations) { + Object expectedAlias = multiKey.getKey(0); + Double expectedTime = multiKey.getKey(1); + pluginDataBuilder.addTestDataManagerPlan(expectedAlias, new TestDataManagerPlan(expectedTime, (c) -> { + actualObservations.add(new MultiKey(expectedAlias, c.getTime())); + })); + } + + // build the action plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + List plugins = new ArrayList<>(); + plugins.add(testPlugin); + + TestSimulation.builder().addPlugins(plugins).build().execute(); + + // show that the actors executed the expected actions + assertEquals(expectedObservations, actualObservations); + + } + + @Test + @UnitTestConstructor(target = TestDataManager.class, args = {}, tags = { UnitTag.INCOMPLETE }) + public void testConstructor() { + // nothing to test + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestDataManagerPlan.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestDataManagerPlan.java new file mode 100644 index 000000000..2ecffc133 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestDataManagerPlan.java @@ -0,0 +1,139 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.function.Consumer; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.random.RandomGeneratorProvider; + +public class AT_TestDataManagerPlan { + + @Test + @UnitTestConstructor(target = TestDataManagerPlan.class, args = { double.class, Consumer.class }) + public void testConstructor() { + + TestDataManagerPlan testDataManagerPlan = new TestDataManagerPlan(2.3, (c) -> { + }); + assertEquals(2.3, testDataManagerPlan.getScheduledTime()); + assertFalse(testDataManagerPlan.executed()); + + } + + @Test + @UnitTestConstructor(target = TestDataManagerPlan.class, args = { TestDataManagerPlan.class }) + public void testConstructor_fromExistingPlan() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(340643142108466727L); + + for (int i = 0; i < 10; i++) { + double scheduledTime = randomGenerator.nextDouble(); + + TestDataManagerPlan originalTestDataManagerPlan = new TestDataManagerPlan(scheduledTime, (c) -> { + }); + TestDataManagerPlan newTestDataManagerPlan = new TestDataManagerPlan(originalTestDataManagerPlan); + + assertEquals(scheduledTime, newTestDataManagerPlan.getScheduledTime()); + + } + } + + @Test + @UnitTestMethod(target = TestDataManagerPlan.class, name = "executed", args = {}) + public void testExecuted() { + + TestDataManagerPlan testDataManagerPlan = new TestDataManagerPlan(0.0, (c) -> { + }); + assertFalse(testDataManagerPlan.executed()); + testDataManagerPlan.executeAction(null); + assertTrue(testDataManagerPlan.executed()); + + TestDataManagerPlan testDataManagerPlanWithException = new TestDataManagerPlan(0.0, (c) -> { + throw new RuntimeException(); + }); + assertFalse(testDataManagerPlanWithException.executed()); + assertThrows(RuntimeException.class, () -> testDataManagerPlanWithException.executeAction(null)); + assertTrue(testDataManagerPlanWithException.executed()); + } + + @Test + @UnitTestMethod(target = TestDataManagerPlan.class, name = "getScheduledTime", args = {}) + public void testGetScheduledTime() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(9009925072863118451L); + + // use the various constructors + for (int i = 0; i < 300; i++) { + double planTime = randomGenerator.nextDouble() * 1000; + TestDataManagerPlan testDataManagerPlan = new TestDataManagerPlan(planTime, (c) -> { + }); + assertEquals(planTime, testDataManagerPlan.getScheduledTime()); + + testDataManagerPlan = new TestDataManagerPlan(planTime, (c) -> { + }); + assertEquals(planTime, testDataManagerPlan.getScheduledTime()); + + testDataManagerPlan = new TestDataManagerPlan(planTime, (c) -> { + }); + assertEquals(planTime, testDataManagerPlan.getScheduledTime()); + } + + } + + @Test + @UnitTestMethod(target = TestDataManagerPlan.class, name = "equals", args = { Object.class }) + public void testEquals() { + + /* + * we must set the key release to false so that the auto generated key + * values won't cause the two plans to be unique + */ + TestDataManagerPlan plan1 = new TestDataManagerPlan(4.5, (c) -> { + }); + TestDataManagerPlan plan2 = new TestDataManagerPlan(4.5, (c) -> { + }); + assertEquals(plan1, plan2); + + // with auto generated keys, there is no way to force them to be equal + plan1 = new TestDataManagerPlan(4.5, (c) -> { + }); + plan2 = new TestDataManagerPlan(7.5, (c) -> { + }); + assertNotEquals(plan1, plan2); + + // unless we use the copy constructor + plan1 = new TestDataManagerPlan(4.5, (c) -> { + }); + plan2 = new TestDataManagerPlan(plan1); + assertEquals(plan1, plan2); + + } + + @Test + @UnitTestMethod(target = TestDataManagerPlan.class, name = "hashCode", args = {}) + public void testHashCode() { + /* + * show that equal objects have equal hash codes + */ + TestDataManagerPlan plan1 = new TestDataManagerPlan(4.5, (c) -> { + }); + TestDataManagerPlan plan2 = new TestDataManagerPlan(4.5, (c) -> { + }); + assertEquals(plan1.hashCode(), plan2.hashCode()); + + // via the copy constructor + plan1 = new TestDataManagerPlan(4.5, (c) -> { + }); + plan2 = new TestDataManagerPlan(plan1); + assertEquals(plan1.hashCode(), plan2.hashCode()); + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestError.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestError.java new file mode 100644 index 000000000..ec056382b --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestError.java @@ -0,0 +1,32 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestMethod; + +public class AT_TestError { + + @Test + @UnitTestMethod(target = TestError.class,name = "getDescription", args = {}) + public void testGetDescription() { + // show that each ErrorType has a non-null, non-empty description + for (TestError testError : TestError.values()) { + assertNotNull(testError.getDescription()); + assertTrue(testError.getDescription().length() > 0); + } + + // show that each description is unique (ignoring case as well) + Set descriptions = new LinkedHashSet<>(); + for (TestError testError : TestError.values()) { + assertTrue(descriptions.add(testError.getDescription().toLowerCase()), testError+": "+"Duplicate ErrorType description: " + testError.getDescription()); + } + } + + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestOutputConsumer.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestOutputConsumer.java new file mode 100644 index 000000000..944e2c0d5 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestOutputConsumer.java @@ -0,0 +1,189 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; +import util.wrappers.MutableInteger; + +public class AT_TestOutputConsumer { + + @Test + @UnitTestConstructor(target = TestOutputConsumer.class, args = {}) + public void testConstructor() { + assertNotNull(new TestOutputConsumer()); + } + + @Test + @UnitTestMethod(target = TestOutputConsumer.class, name = "accept", args = { Object.class }) + public void testAccept() { + TestOutputConsumer outputConsumer = new TestOutputConsumer(); + + outputConsumer.accept(true); + outputConsumer.accept(false); + outputConsumer.accept(true); + outputConsumer.accept(false); + outputConsumer.accept(true); + + Map expectedOutput = new LinkedHashMap<>(); + expectedOutput.put(true, 3); + expectedOutput.put(false, 2); + + ContractException contractException = assertThrows(ContractException.class, () -> outputConsumer.accept(null)); + assertEquals(TestError.NULL_OUTPUT_ITEM, contractException.getErrorType()); + + Map actualOutput = outputConsumer.getOutputItemMap(Boolean.class); + assertEquals(expectedOutput, actualOutput); + + } + + @Test + @UnitTestMethod(target = TestOutputConsumer.class, name = "getOutputItemMap", args = { Class.class }) + public void testGetOutputItemMap() { + TestOutputConsumer outputConsumer = new TestOutputConsumer(); + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7830499412883085962L); + + Map expectedIntValuesM = new LinkedHashMap<>(); + Map expectedDoubleValuesM = new LinkedHashMap<>(); + Map expectedFloatValuesM = new LinkedHashMap<>(); + Map expectedLongValuesM = new LinkedHashMap<>(); + + for (int i = 0; i < 1; i++) { + boolean shouldAdd = randomGenerator.nextBoolean(); + + if (shouldAdd) { + int intVal = randomGenerator.nextInt(100); + double doubleVal = randomGenerator.nextDouble() * 100; + float floatVal = randomGenerator.nextFloat() * 100; + long longVal = randomGenerator.nextLong(); + + outputConsumer.accept(intVal); + outputConsumer.accept(doubleVal); + outputConsumer.accept(floatVal); + outputConsumer.accept(longVal); + + expectedIntValuesM.putIfAbsent(intVal, new MutableInteger()); + expectedDoubleValuesM.putIfAbsent(doubleVal, new MutableInteger()); + expectedFloatValuesM.putIfAbsent(floatVal, new MutableInteger()); + expectedLongValuesM.putIfAbsent(longVal, new MutableInteger()); + + expectedIntValuesM.get(intVal).increment(); + expectedDoubleValuesM.get(doubleVal).increment(); + expectedFloatValuesM.get(floatVal).increment(); + expectedLongValuesM.get(longVal).increment(); + } + } + + Map expectedIntValues = new LinkedHashMap<>(); + Map expectedDoubleValues = new LinkedHashMap<>(); + Map expectedFloatValues = new LinkedHashMap<>(); + Map expectedLongValues = new LinkedHashMap<>(); + + for (Integer i : expectedIntValuesM.keySet()) { + expectedIntValues.put(i, expectedIntValuesM.get(i).getValue()); + } + + for (Double i : expectedDoubleValuesM.keySet()) { + expectedDoubleValues.put(i, expectedDoubleValuesM.get(i).getValue()); + } + + for (Float i : expectedFloatValuesM.keySet()) { + expectedFloatValues.put(i, expectedFloatValuesM.get(i).getValue()); + } + + for (Long i : expectedLongValuesM.keySet()) { + expectedLongValues.put(i, expectedLongValuesM.get(i).getValue()); + } + + assertEquals(expectedIntValues, outputConsumer.getOutputItemMap(Integer.class)); + assertEquals(expectedDoubleValues, outputConsumer.getOutputItemMap(Double.class)); + assertEquals(expectedFloatValues, outputConsumer.getOutputItemMap(Float.class)); + assertEquals(expectedLongValues, outputConsumer.getOutputItemMap(Long.class)); + } + + @Test + @UnitTestMethod(target = TestOutputConsumer.class, name = "getOutputItems", args = { Class.class }) + public void testGetOutputItems() { + TestOutputConsumer outputConsumer = new TestOutputConsumer(); + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3616113008200960520L); + + List expectedIntegers = new ArrayList<>(); + List expectedDoubles = new ArrayList<>(); + List expectedStrings = new ArrayList<>(); + List expectedBooleans = new ArrayList<>(); + List expectedFloats = new ArrayList<>(); + + for (int i = 0; i < 100; i++) { + + int type = randomGenerator.nextInt(4); + + switch (type) { + case 0: + Integer ivalue = randomGenerator.nextInt(); + expectedIntegers.add(ivalue); + outputConsumer.accept(ivalue); + break; + case 1: + Double dvalue = randomGenerator.nextDouble(); + expectedDoubles.add(dvalue); + outputConsumer.accept(dvalue); + break; + case 2: + String svalue = Integer.toString(randomGenerator.nextInt()); + expectedStrings.add(svalue); + outputConsumer.accept(svalue); + break; + case 3: + Boolean bvalue = randomGenerator.nextBoolean(); + expectedBooleans.add(bvalue); + outputConsumer.accept(bvalue); + break; + default: + throw new RuntimeException("unhandled case"); + } + } + + assertEquals(expectedIntegers, outputConsumer.getOutputItems(Integer.class)); + assertEquals(expectedDoubles, outputConsumer.getOutputItems(Double.class)); + assertEquals(expectedFloats, outputConsumer.getOutputItems(Float.class)); + assertEquals(expectedStrings, outputConsumer.getOutputItems(String.class)); + assertEquals(expectedBooleans, outputConsumer.getOutputItems(Boolean.class)); + + } + + + @Test + @UnitTestMethod(target = TestOutputConsumer.class, name = "getOutputItem", args = { Class.class }) + public void testGetOutputItem() { + TestOutputConsumer outputConsumer = new TestOutputConsumer(); + + Optional optional = outputConsumer.getOutputItem(Integer.class); + assertTrue(optional.isEmpty()); + + outputConsumer.accept(5); + optional = outputConsumer.getOutputItem(Integer.class); + assertTrue(optional.isPresent()); + assertEquals(5, optional.get()); + + outputConsumer.accept(5); + ContractException contractException = assertThrows(ContractException.class,()->outputConsumer.getOutputItem(Integer.class)); + assertEquals(TestError.MULTIPLE_MATCHING_ITEMS, contractException.getErrorType()); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestPlanDataManager.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestPlanDataManager.java new file mode 100644 index 000000000..8bf26b9a1 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestPlanDataManager.java @@ -0,0 +1,173 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; +import util.annotations.UnitTag; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_TestPlanDataManager { + private static class TestDataManager1 extends TestDataManager { + + } + + private static class TestDataManager2 extends TestDataManager { + + } + + @Test + @UnitTestMethod(target = TestPlanDataManager.class, name = "init", args = { DataManagerContext.class }, tags = { UnitTag.INCOMPLETE}) + public void testInit() { + + + // test needs to demonstrate that the TestPlanDataManager releases the TestScenarioReport at the end of the simulation + } + + @Test + @UnitTestConstructor(target = TestPlanDataManager.class, args = { TestPluginData.class }, tags = { UnitTag.LOCAL_PROXY }) + public void testConstructor() { + // covered by other tests + } + + @Test + @UnitTestMethod(target = TestPlanDataManager.class, name = "getTestActorPlans", args = { Object.class }) + public void testGetActorActionPlans() { + // create a few TestActorPlan items associated with two aliases + Map> expectedTestActorPlans = new LinkedHashMap<>(); + Set testActorPlans = new LinkedHashSet<>(); + expectedTestActorPlans.put("actor1", testActorPlans); + + testActorPlans.add(new TestActorPlan(1, (c) -> { + })); + testActorPlans.add(new TestActorPlan(2, (c) -> { + })); + testActorPlans.add(new TestActorPlan(3, (c) -> { + })); + + testActorPlans = new LinkedHashSet<>(); + expectedTestActorPlans.put("actor2", testActorPlans); + testActorPlans.add(new TestActorPlan(4, (c) -> { + })); + testActorPlans.add(new TestActorPlan(5, (c) -> { + })); + + // Build the plugin data from the items above + TestPluginData.Builder builder = TestPluginData.builder(); + + for (String alias : expectedTestActorPlans.keySet()) { + testActorPlans = expectedTestActorPlans.get(alias); + for (TestActorPlan testActorPlan : testActorPlans) { + builder.addTestActorPlan(alias, testActorPlan); + } + } + + TestPluginData testPluginData = builder.build(); + TestPlanDataManager testPlanDataManager = new TestPlanDataManager(testPluginData); + + // show that the plans associated with each actors are correct + for (String alias : expectedTestActorPlans.keySet()) { + Set expectedPlans = expectedTestActorPlans.get(alias); + Set actualPlans = new LinkedHashSet<>(testPlanDataManager.getTestActorPlans(alias)); + assertEquals(expectedPlans, actualPlans); + } + } + + + @Test + @UnitTestMethod(target = TestPlanDataManager.class, name = "getTestReportPlans", args = { Object.class }) + public void testGetTestReportPlans() { + // create a few TestRepoertPlan items associated with two aliases + Map> expectedTestReportPlans = new LinkedHashMap<>(); + Set testReportPlans = new LinkedHashSet<>(); + expectedTestReportPlans.put("actor1", testReportPlans); + + testReportPlans.add(new TestReportPlan(1, (c) -> { + })); + testReportPlans.add(new TestReportPlan(2, (c) -> { + })); + testReportPlans.add(new TestReportPlan(3, (c) -> { + })); + + testReportPlans = new LinkedHashSet<>(); + expectedTestReportPlans.put("actor2", testReportPlans); + testReportPlans.add(new TestReportPlan(4, (c) -> { + })); + testReportPlans.add(new TestReportPlan(5, (c) -> { + })); + + // Build the plugin data from the items above + TestPluginData.Builder builder = TestPluginData.builder(); + + for (String alias : expectedTestReportPlans.keySet()) { + testReportPlans = expectedTestReportPlans.get(alias); + for (TestReportPlan testReportPlan : testReportPlans) { + builder.addTestReportPlan(alias, testReportPlan); + } + } + + TestPluginData testPluginData = builder.build(); + TestPlanDataManager testPlanDataManager = new TestPlanDataManager(testPluginData); + + // show that the plans associated with each actors are correct + for (String alias : expectedTestReportPlans.keySet()) { + Set expectedPlans = expectedTestReportPlans.get(alias); + Set actualPlans = new LinkedHashSet<>(testPlanDataManager.getTestReportPlans(alias)); + assertEquals(expectedPlans, actualPlans); + } + } + + @Test + @UnitTestMethod(target = TestPlanDataManager.class, name = "getTestDataManagerPlans", args = { Object.class }) + public void testGetTestDataManagerPlans() { + // build a few test data manager plans + Map> planMap = new LinkedHashMap<>(); + Set planSet = new LinkedHashSet<>(); + planSet.add(new TestDataManagerPlan(0, (c) -> { + })); + planSet.add(new TestDataManagerPlan(1, (c) -> { + })); + planMap.put("A", planSet); + planSet = new LinkedHashSet<>(); + planSet.add(new TestDataManagerPlan(2, (c) -> { + })); + planSet.add(new TestDataManagerPlan(3, (c) -> { + })); + planSet.add(new TestDataManagerPlan(4, (c) -> { + })); + planMap.put("B", planSet); + + // add them to a test plugin data + TestPluginData.Builder builder = TestPluginData.builder(); + for (Object alias : planMap.keySet()) { + + planSet = planMap.get(alias); + for (TestDataManagerPlan testDataManagerPlan : planSet) { + builder.addTestDataManagerPlan(alias, testDataManagerPlan); + } + } + builder.addTestDataManager("A", () -> new TestDataManager1()); + + builder.addTestDataManager("B", () -> new TestDataManager2()); + + TestPluginData testPluginData = builder.build(); + + // create the test plugin data manager + TestPlanDataManager testPlanDataManager = new TestPlanDataManager(testPluginData); + + // show that the correct plans are stored + for (Object alias : planMap.keySet()) { + Set expectedPlans = planMap.get(alias); + Set actualPlans = new LinkedHashSet<>(testPlanDataManager.getTestDataManagerPlans(alias)); + assertEquals(expectedPlans, actualPlans); + } + } + +} diff --git a/gcm3/src/test/java/nucleus/testsupport/testplugin/AT_TestPlugin.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestPlugin.java similarity index 79% rename from gcm3/src/test/java/nucleus/testsupport/testplugin/AT_TestPlugin.java rename to gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestPlugin.java index be4738a2d..7f8eff59c 100644 --- a/gcm3/src/test/java/nucleus/testsupport/testplugin/AT_TestPlugin.java +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestPlugin.java @@ -1,4 +1,4 @@ -package nucleus.testsupport.testplugin; +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -9,18 +9,16 @@ import org.junit.jupiter.api.Test; -import nucleus.Plugin; -import nucleus.PluginData; -import nucleus.Simulation; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.Simulation; +import util.annotations.UnitTestMethod; import util.wrappers.MutableBoolean; -@UnitTest(target = TestPlugin.class) public class AT_TestPlugin { @Test - @UnitTestMethod(name = "getTestPlugin", args = { TestPluginData.class }) + @UnitTestMethod(target = TestPlugin.class,name = "getTestPlugin", args = { TestPluginData.class }) public void testGetTestPlugin() { TestPluginData testPluginData = TestPluginData.builder().build(); Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); @@ -29,7 +27,7 @@ public void testGetTestPlugin() { // show that the test plugin data is returned by the plugin Set expectedPluginDatas = new LinkedHashSet<>(); expectedPluginDatas.add(testPluginData); - assertEquals(expectedPluginDatas, testPlugin.getPluginDatas()); + assertEquals(expectedPluginDatas, new LinkedHashSet<>(testPlugin.getPluginDatas())); // show that the initializer exists assertNotNull(testPlugin.getInitializer()); diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestPluginData.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestPluginData.java new file mode 100644 index 000000000..ce61cd971 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestPluginData.java @@ -0,0 +1,682 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Supplier; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.nucleus.SimplePluginId; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_TestPluginData { + + private static class TestDataManager1 extends TestDataManager { + + } + + private static class TestDataManager2 extends TestDataManager { + + } + + private static class TestDataManager3 extends TestDataManager { + + } + + @Test + @UnitTestMethod(target = TestPluginData.class, name = "builder", args = {}) + public void testBuilder() { + assertNotNull(TestPluginData.builder()); + } + + @Test + @UnitTestMethod(target = TestPluginData.class, name = "getTestDataManager", args = { Object.class }) + public void testGetTestDataManagerType() { + TestPluginData testPluginData = TestPluginData .builder()// + .addTestDataManager("A", () -> new TestDataManager1())// + .addTestDataManager("B", () -> new TestDataManager2())// + .addTestDataManager("C", () -> new TestDataManager3())// + .build();// + + // show that the aliased data manager types are retrievable + Optional optional1 = testPluginData.getTestDataManager("A"); + assertTrue(optional1.isPresent()); + + Optional optional2 = testPluginData.getTestDataManager("B"); + assertTrue(optional2.isPresent()); + + Optional optional3 = testPluginData.getTestDataManager("C"); + assertTrue(optional3.isPresent()); + + Optional optional4 = testPluginData.getTestDataManager("D"); + assertFalse(optional4.isPresent()); + + } + + @Test + @UnitTestMethod(target = TestPluginData.class, name = "getTestActorPlans", args = { Object.class }) + public void testGetTestActorPlans() { + // create a few TestActorPlan items associated with two aliases + Map> expectedTestActorPlans = new LinkedHashMap<>(); + Set testActorPlans = new LinkedHashSet<>(); + expectedTestActorPlans.put("actor1", testActorPlans); + + testActorPlans.add(new TestActorPlan(1, (c) -> { + })); + testActorPlans.add(new TestActorPlan(2, (c) -> { + })); + testActorPlans.add(new TestActorPlan(3, (c) -> { + })); + + testActorPlans = new LinkedHashSet<>(); + expectedTestActorPlans.put("actor2", testActorPlans); + testActorPlans.add(new TestActorPlan(4, (c) -> { + })); + testActorPlans.add(new TestActorPlan(5, (c) -> { + })); + + // Build the plugin data from the items above + TestPluginData.Builder builder = TestPluginData.builder();// + + for (String alias : expectedTestActorPlans.keySet()) { + testActorPlans = expectedTestActorPlans.get(alias); + for (TestActorPlan testActorPlan : testActorPlans) { + builder.addTestActorPlan(alias, testActorPlan); + } + } + + TestPluginData testPluginData = builder.build(); + + // show that the plans associated with each actors are correct + for (String alias : expectedTestActorPlans.keySet()) { + Set expectedPlans = expectedTestActorPlans.get(alias); + Set actualPlans = new LinkedHashSet<>(testPluginData.getTestActorPlans(alias)); + assertEquals(expectedPlans, actualPlans); + } + + } + + @Test + @UnitTestMethod(target = TestPluginData.class, name = "getTestReportPlans", args = { Object.class }) + public void testGetTestReportPlans() { + // create a few TestReportPlan items associated with two aliases + Map> expectedTestReportPlans = new LinkedHashMap<>(); + Set testReportPlans = new LinkedHashSet<>(); + expectedTestReportPlans.put("report1", testReportPlans); + + testReportPlans.add(new TestReportPlan(1, (c) -> { + })); + testReportPlans.add(new TestReportPlan(2, (c) -> { + })); + testReportPlans.add(new TestReportPlan(3, (c) -> { + })); + + testReportPlans = new LinkedHashSet<>(); + expectedTestReportPlans.put("report2", testReportPlans); + testReportPlans.add(new TestReportPlan(4, (c) -> { + })); + testReportPlans.add(new TestReportPlan(5, (c) -> { + })); + + // Build the plugin data from the items above + TestPluginData.Builder builder = TestPluginData.builder();// + + for (String alias : expectedTestReportPlans.keySet()) { + testReportPlans = expectedTestReportPlans.get(alias); + for (TestReportPlan testReportPlan : testReportPlans) { + builder.addTestReportPlan(alias, testReportPlan); + } + } + + TestPluginData testPluginData = builder.build(); + + // show that the plans associated with each reports are correct + for (String alias : expectedTestReportPlans.keySet()) { + Set expectedPlans = expectedTestReportPlans.get(alias); + Set actualPlans = new LinkedHashSet<>(testPluginData.getTestReportPlans(alias)); + assertEquals(expectedPlans, actualPlans); + } + + } + + @Test + @UnitTestMethod(target = TestPluginData.class, name = "getTestActorAliases", args = {}) + public void testGetTestActorAliases() { + + Set expectedAliases = new LinkedHashSet<>(); + expectedAliases.add("A"); + expectedAliases.add("B"); + expectedAliases.add("C"); + + TestPluginData.Builder builder = TestPluginData.builder();// + for (Object alias : expectedAliases) { + builder.addTestActorPlan(alias, new TestActorPlan(0, (c) -> { + })); + } + + TestPluginData testPluginData = builder.build(); + + LinkedHashSet actualAliases = new LinkedHashSet<>(testPluginData.getTestActorAliases()); + assertEquals(expectedAliases, actualAliases); + + } + + @Test + @UnitTestMethod(target = TestPluginData.class, name = "getTestReportAliases", args = {}) + public void testGetTestReportAliases() { + + Set expectedAliases = new LinkedHashSet<>(); + expectedAliases.add("A"); + expectedAliases.add("B"); + expectedAliases.add("C"); + + TestPluginData.Builder builder = TestPluginData.builder();// + for (Object alias : expectedAliases) { + builder.addTestReportPlan(alias, new TestReportPlan(0, (c) -> { + })); + } + + TestPluginData testPluginData = builder.build(); + + LinkedHashSet actualAliases = new LinkedHashSet<>(testPluginData.getTestReportAliases()); + assertEquals(expectedAliases, actualAliases); + + } + + @Test + @UnitTestMethod(target = TestPluginData.class, name = "getCloneBuilder", args = {}) + public void testGetCloneBuilder() { + + TestPluginData.Builder builder = TestPluginData.builder();// + // add actors + + builder.addTestActorPlan("A", new TestActorPlan(0, (c) -> { + })); + + builder.addTestActorPlan("B", new TestActorPlan(0, (c) -> { + })); + builder.addTestActorPlan("B", new TestActorPlan(0, (c) -> { + })); + builder.addTestActorPlan("C", new TestActorPlan(0, (c) -> { + })); + + // add data managers + builder.addTestDataManager("D", () -> new TestDataManager1()); + builder.addTestDataManagerPlan("D", new TestDataManagerPlan(0, (c) -> { + })); + builder.addTestDataManager("E", () -> new TestDataManager2()); + builder.addTestDataManagerPlan("E", new TestDataManagerPlan(0, (c) -> { + })); + builder.addTestDataManagerPlan("E", new TestDataManagerPlan(0, (c) -> { + })); + builder.addTestDataManager("F", () -> new TestDataManager3()); + + // build the plugin data + TestPluginData testPluginData = builder.build(); + + // show that the clone builder is properly initialized -- i.e. it will + // immediately build a clone of the plugin data + TestPluginData.Builder cloneBuilder = testPluginData.getCloneBuilder(); + assertNotNull(cloneBuilder); + TestPluginData testPluginData2 = cloneBuilder.build(); + assertEquals(testPluginData, testPluginData2); + + } + + @Test + @UnitTestMethod(target = TestPluginData.class, name = "getTestDataManagerPlans", args = { Object.class }) + public void testGetTestDataManagerPlans() { + // create a few plans + Map> testDataManagerPlanMap = new LinkedHashMap<>(); + Set testDataManagerPlans = new LinkedHashSet<>(); + testDataManagerPlans.add(new TestDataManagerPlan(0, (c) -> { + })); + testDataManagerPlans.add(new TestDataManagerPlan(1, (c) -> { + })); + testDataManagerPlanMap.put("A", testDataManagerPlans); + + testDataManagerPlans = new LinkedHashSet<>(); + testDataManagerPlans.add(new TestDataManagerPlan(2, (c) -> { + })); + testDataManagerPlans.add(new TestDataManagerPlan(3, (c) -> { + })); + testDataManagerPlans.add(new TestDataManagerPlan(4, (c) -> { + })); + testDataManagerPlanMap.put("B", testDataManagerPlans); + + // add those plans to the builder + TestPluginData.Builder builder = TestPluginData.builder(); + for (Object alias : testDataManagerPlanMap.keySet()) { + testDataManagerPlans = testDataManagerPlanMap.get(alias); + for (TestDataManagerPlan testDataManagerPlan : testDataManagerPlans) { + builder.addTestDataManagerPlan(alias, testDataManagerPlan); + } + } + + builder.addTestDataManager("A", () -> new TestDataManager1()); + builder.addTestDataManager("B", () -> new TestDataManager2()); + + TestPluginData testPluginData = builder.build(); + + // show that the plugin data contains the expected plans + for (Object alias : testDataManagerPlanMap.keySet()) { + Set expectedPlans = testDataManagerPlanMap.get(alias); + Set actualPlans = new LinkedHashSet<>(testPluginData.getTestDataManagerPlans(alias)); + assertEquals(expectedPlans, actualPlans); + } + + } + + @Test + @UnitTestMethod(target = TestPluginData.class, name = "getTestDataManagerAliases", args = {}) + public void testGetTestDataManagerAliases() { + Set expectedAliases = new LinkedHashSet<>(); + + TestPluginData.Builder builder = TestPluginData.builder(); + expectedAliases.add("A"); + builder.addTestDataManager("A", () -> new TestDataManager1()); + expectedAliases.add("B"); + builder.addTestDataManager("B", () -> new TestDataManager2()); + expectedAliases.add("C"); + builder.addTestDataManager("C", () -> new TestDataManager3()); + TestPluginData testPluginData = builder.build(); + + LinkedHashSet actualAliases = new LinkedHashSet<>(testPluginData.getTestDataManagerAliases()); + assertEquals(expectedAliases, actualAliases); + + } + + @Test + @UnitTestMethod(target = TestPluginData.Builder.class, name = "addTestActorPlan", args = { Object.class, TestActorPlan.class }) + public void testAddTestActorPlan() { + // create a few plans + Map> testActorPlanMap = new LinkedHashMap<>(); + Set testActorPlans = new LinkedHashSet<>(); + testActorPlans.add(new TestActorPlan(0, (c) -> { + })); + testActorPlans.add(new TestActorPlan(1, (c) -> { + })); + testActorPlanMap.put("A", testActorPlans); + + testActorPlans = new LinkedHashSet<>(); + testActorPlans.add(new TestActorPlan(2, (c) -> { + })); + testActorPlans.add(new TestActorPlan(3, (c) -> { + })); + testActorPlans.add(new TestActorPlan(4, (c) -> { + })); + testActorPlanMap.put("B", testActorPlans); + + // add those plans to the builder + TestPluginData.Builder builder = TestPluginData.builder(); + for (Object alias : testActorPlanMap.keySet()) { + testActorPlans = testActorPlanMap.get(alias); + for (TestActorPlan testActorPlan : testActorPlans) { + builder.addTestActorPlan(alias, testActorPlan); + } + } + + TestPluginData testPluginData = builder.build(); + + // show that the plugin data contains the expected plans + for (Object alias : testActorPlanMap.keySet()) { + Set expectedPlans = testActorPlanMap.get(alias); + Set actualPlans = new LinkedHashSet<>(testPluginData.getTestActorPlans(alias)); + assertEquals(expectedPlans, actualPlans); + } + } + + @Test + @UnitTestMethod(target = TestPluginData.Builder.class, name = "addTestReportPlan", args = { Object.class, TestReportPlan.class }) + public void testAddTestReportPlan() { + // create a few plans + Map> testReportPlanMap = new LinkedHashMap<>(); + Set testReportPlans = new LinkedHashSet<>(); + testReportPlans.add(new TestReportPlan(0, (c) -> { + })); + testReportPlans.add(new TestReportPlan(1, (c) -> { + })); + testReportPlanMap.put("A", testReportPlans); + + testReportPlans = new LinkedHashSet<>(); + testReportPlans.add(new TestReportPlan(2, (c) -> { + })); + testReportPlans.add(new TestReportPlan(3, (c) -> { + })); + testReportPlans.add(new TestReportPlan(4, (c) -> { + })); + testReportPlanMap.put("B", testReportPlans); + + // add those plans to the builder + TestPluginData.Builder builder = TestPluginData.builder(); + for (Object alias : testReportPlanMap.keySet()) { + testReportPlans = testReportPlanMap.get(alias); + for (TestReportPlan testReportPlan : testReportPlans) { + builder.addTestReportPlan(alias, testReportPlan); + } + } + + TestPluginData testPluginData = builder.build(); + + // show that the plugin data contains the expected plans + for (Object alias : testReportPlanMap.keySet()) { + Set expectedPlans = testReportPlanMap.get(alias); + Set actualPlans = new LinkedHashSet<>(testPluginData.getTestReportPlans(alias)); + assertEquals(expectedPlans, actualPlans); + } + } + + + @Test + @UnitTestMethod(target = TestPluginData.Builder.class, name = "addTestDataManager", args = { Object.class, Supplier.class }) + + public void testAddTestDataManager() { + // create a few plans + LinkedHashSet expectedDataManagerAliases = new LinkedHashSet<>(); + expectedDataManagerAliases.add("A"); + expectedDataManagerAliases.add("B"); + expectedDataManagerAliases.add("C"); + + // add those plans to the builder + TestPluginData.Builder builder = TestPluginData.builder(); + builder.addTestDataManager("A", () -> new TestDataManager1()); + builder.addTestDataManager("B", () -> new TestDataManager2()); + builder.addTestDataManager("C", () -> new TestDataManager3()); + + TestPluginData testPluginData = builder.build(); + + // show that the plugin data contains the expected plans + LinkedHashSet actualDataManagerAliases = new LinkedHashSet<>(testPluginData.getTestDataManagerAliases()); + assertEquals(expectedDataManagerAliases, actualDataManagerAliases); + } + + @Test + @UnitTestMethod(target = TestPluginData.Builder.class, name = "addTestDataManagerPlan", args = { Object.class, TestDataManagerPlan.class }) + public void testAddTestDataManagerPlan() { + // create a few plans + Map> testDataManagerPlanMap = new LinkedHashMap<>(); + Set testDataManagerPlans = new LinkedHashSet<>(); + testDataManagerPlans.add(new TestDataManagerPlan(0, (c) -> { + })); + testDataManagerPlans.add(new TestDataManagerPlan(1, (c) -> { + })); + testDataManagerPlanMap.put("A", testDataManagerPlans); + + testDataManagerPlans = new LinkedHashSet<>(); + testDataManagerPlans.add(new TestDataManagerPlan(2, (c) -> { + })); + testDataManagerPlans.add(new TestDataManagerPlan(3, (c) -> { + })); + testDataManagerPlans.add(new TestDataManagerPlan(4, (c) -> { + })); + testDataManagerPlanMap.put("B", testDataManagerPlans); + + // add those plans to the builder + TestPluginData.Builder builder = TestPluginData.builder(); + for (Object alias : testDataManagerPlanMap.keySet()) { + testDataManagerPlans = testDataManagerPlanMap.get(alias); + for (TestDataManagerPlan testDataManagerPlan : testDataManagerPlans) { + builder.addTestDataManagerPlan(alias, testDataManagerPlan); + } + } + + builder.addTestDataManager("A", () -> new TestDataManager1()); + builder.addTestDataManager("B", () -> new TestDataManager2()); + + TestPluginData testPluginData = builder.build(); + + // show that the plugin data contains the expected plans + for (Object alias : testDataManagerPlanMap.keySet()) { + Set expectedPlans = testDataManagerPlanMap.get(alias); + Set actualPlans = new LinkedHashSet<>(testPluginData.getTestDataManagerPlans(alias)); + assertEquals(expectedPlans, actualPlans); + } + } + + @Test + @UnitTestMethod(target = TestPluginData.Builder.class, name = "addPluginDependency", args = { PluginId.class }) + public void testAddPluginDependency() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6227272133886959116L); + Set candidatePluginIds = new LinkedHashSet<>(); + candidatePluginIds.add(new SimplePluginId("A")); + candidatePluginIds.add(new SimplePluginId("B")); + candidatePluginIds.add(new SimplePluginId("C")); + candidatePluginIds.add(new SimplePluginId("D")); + candidatePluginIds.add(new SimplePluginId("E")); + + for (int i = 0; i < 30; i++) { + + Set expectedPluginIds = new LinkedHashSet<>(); + TestPluginData.Builder builder = TestPluginData.builder(); + for (PluginId pluginId : candidatePluginIds) { + if (randomGenerator.nextBoolean()) { + builder.addPluginDependency(pluginId); + expectedPluginIds.add(pluginId); + } + } + + Set actualPluginIds = builder.build().getPluginDependencies(); + assertEquals(expectedPluginIds, actualPluginIds); + } + + // precondition test: if the plugin id is null + ContractException contractException = assertThrows(ContractException.class, () -> TestPluginData.builder().addPluginDependency(null)); + assertEquals(TestError.NULL_PLUGIN_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = TestPluginData.Builder.class, name = "build", args = {}) + public void testBuild() { + // covered by other tests + } + + @Test + @UnitTestMethod(target = TestPluginData.class, name = "equals", args = { Object.class }) + public void testEquals() { + + SimplePluginId simplePluginIdA = new SimplePluginId("A"); + SimplePluginId simplePluginIdB = new SimplePluginId("B"); + Supplier supplier1 = () -> new TestDataManager(); + Supplier supplier2 = () -> new TestDataManager(); + TestActorPlan testActorPlan1 = new TestActorPlan(0, (c) -> { + }); + TestActorPlan testActorPlan2 = new TestActorPlan(0, (c) -> { + }); + TestDataManagerPlan testDataManagerPlan1 = new TestDataManagerPlan(0, (c) -> { + }); + TestDataManagerPlan testDataManagerPlan2 = new TestDataManagerPlan(0, (c) -> { + }); + + TestPluginData testPluginData1 = TestPluginData .builder()// + .addPluginDependency(simplePluginIdA)// + .addTestActorPlan("actor", testActorPlan1)// + .addTestDataManagerPlan("dm", testDataManagerPlan1)// + .addTestDataManager("dm", supplier1)// + .build(); + + // identical inputs + TestPluginData testPluginData2 = TestPluginData .builder()// + .addPluginDependency(simplePluginIdA)// + .addTestActorPlan("actor", testActorPlan1)// + .addTestDataManagerPlan("dm", testDataManagerPlan1)// + .addTestDataManager("dm", supplier1)// + .build(); + + assertEquals(testPluginData1, testPluginData2); + + // with different plugin dependencies + TestPluginData testPluginData3 = TestPluginData .builder()// + .addPluginDependency(simplePluginIdB)// + .addTestActorPlan("actor", testActorPlan1)// + .addTestDataManagerPlan("dm", testDataManagerPlan1)// + .addTestDataManager("dm", supplier1)// + .build(); + assertNotEquals(testPluginData1, testPluginData3); + + testPluginData3 = TestPluginData.builder()// + .addPluginDependency(simplePluginIdA)// + .addPluginDependency(simplePluginIdB)// + .addTestActorPlan("actor", testActorPlan1)// + .addTestDataManagerPlan("dm", testDataManagerPlan1)// + .addTestDataManager("dm", supplier1)// + .build(); + assertNotEquals(testPluginData1, testPluginData3); + + // with different actor plans + TestPluginData testPluginData4 = TestPluginData .builder()// + .addPluginDependency(simplePluginIdA)// + .addTestActorPlan("actor", testActorPlan2)// + .addTestDataManagerPlan("dm", testDataManagerPlan1)// + .addTestDataManager("dm", supplier1)// + .build(); + assertEquals(testPluginData1, testPluginData4); + + testPluginData4 = TestPluginData.builder()// + .addPluginDependency(simplePluginIdA)// + .addTestActorPlan("actor2", testActorPlan1)// + .addTestDataManagerPlan("dm", testDataManagerPlan1)// + .addTestDataManager("dm", supplier1)// + .build(); + assertNotEquals(testPluginData1, testPluginData4); + + // with different data manager plans + TestPluginData testPluginData5 = TestPluginData .builder()// + .addPluginDependency(simplePluginIdA)// + .addTestActorPlan("actor", testActorPlan1)// + .addTestDataManagerPlan("dm", testDataManagerPlan2)// + .addTestDataManager("dm", supplier1)// + .build(); + assertEquals(testPluginData1, testPluginData5); + + // with different data manager suppliers + TestPluginData testPluginData6 = TestPluginData .builder()// + .addPluginDependency(simplePluginIdA)// + .addTestActorPlan("actor", testActorPlan1)// + .addTestDataManagerPlan("dm", testDataManagerPlan1)// + .addTestDataManager("dm", supplier2)// + .build(); + assertNotEquals(testPluginData1, testPluginData6); + + } + + @Test + @UnitTestMethod(target = TestPluginData.class, name = "hashCode", args = {}) + public void testHashCode() { + /* + * Show that equal objects have equal hash codes over a few example + * cases + */ + + SimplePluginId simplePluginIdA = new SimplePluginId("A"); + SimplePluginId simplePluginIdB = new SimplePluginId("B"); + Supplier supplier1 = () -> new TestDataManager(); + Supplier supplier2 = () -> new TestDataManager(); + TestActorPlan testActorPlan1 = new TestActorPlan(0, (c) -> { + }); + TestActorPlan testActorPlan2 = new TestActorPlan(0, (c) -> { + }); + TestDataManagerPlan testDataManagerPlan1 = new TestDataManagerPlan(0, (c) -> { + }); + TestDataManagerPlan testDataManagerPlan2 = new TestDataManagerPlan(0, (c) -> { + }); + + TestPluginData testPluginData1 = TestPluginData .builder()// + .addPluginDependency(simplePluginIdA)// + .addTestActorPlan("actor", testActorPlan1)// + .addTestDataManagerPlan("dm", testDataManagerPlan1)// + .addTestDataManager("dm", supplier1)// + .build(); + + // identical inputs + TestPluginData testPluginData2 = TestPluginData .builder()// + .addPluginDependency(simplePluginIdA)// + .addTestActorPlan("actor", testActorPlan1)// + .addTestDataManagerPlan("dm", testDataManagerPlan1)// + .addTestDataManager("dm", supplier1)// + .build(); + + assertEquals(testPluginData1.hashCode(), testPluginData2.hashCode()); + + testPluginData1 = TestPluginData.builder()// + .addPluginDependency(simplePluginIdA)// + .addPluginDependency(simplePluginIdB)// + .addTestActorPlan("actor", testActorPlan2)// + .addTestDataManagerPlan("dm", testDataManagerPlan1)// + .addTestDataManager("dm", supplier2)// + .build(); + + testPluginData2 = TestPluginData.builder()// + .addPluginDependency(simplePluginIdA)// + .addPluginDependency(simplePluginIdB)// + .addTestActorPlan("actor", testActorPlan2)// + .addTestDataManagerPlan("dm", testDataManagerPlan1)// + .addTestDataManager("dm", supplier2)// + .build(); + + assertEquals(testPluginData1.hashCode(), testPluginData2.hashCode()); + + testPluginData1 = TestPluginData.builder()// + .addPluginDependency(simplePluginIdA)// + .addPluginDependency(simplePluginIdB)// + .addTestActorPlan("actor", testActorPlan1)// + .addTestActorPlan("actor", testActorPlan2)// + .addTestDataManagerPlan("dm", testDataManagerPlan2)// + .addTestDataManager("dm", supplier2)// + .build(); + + testPluginData2 = TestPluginData.builder()// + .addPluginDependency(simplePluginIdA)// + .addPluginDependency(simplePluginIdB)// + .addTestActorPlan("actor", testActorPlan1)// + .addTestActorPlan("actor", testActorPlan2)// + .addTestDataManagerPlan("dm", testDataManagerPlan2)// + .addTestDataManager("dm", supplier2)// + .build(); + + assertEquals(testPluginData1.hashCode(), testPluginData2.hashCode()); + + } + + @Test + @UnitTestMethod(target = TestPluginData.class, name = "getPluginDependencies", args = {}) + public void testGetPluginDependencies() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4923209038525994062L); + Set candidatePluginIds = new LinkedHashSet<>(); + candidatePluginIds.add(new SimplePluginId("A")); + candidatePluginIds.add(new SimplePluginId("B")); + candidatePluginIds.add(new SimplePluginId("C")); + candidatePluginIds.add(new SimplePluginId("D")); + candidatePluginIds.add(new SimplePluginId("E")); + + for (int i = 0; i < 30; i++) { + + Set expectedPluginIds = new LinkedHashSet<>(); + TestPluginData.Builder builder = TestPluginData.builder(); + for (PluginId pluginId : candidatePluginIds) { + if (randomGenerator.nextBoolean()) { + builder.addPluginDependency(pluginId); + expectedPluginIds.add(pluginId); + } + } + + Set actualPluginIds = builder.build().getPluginDependencies(); + assertEquals(expectedPluginIds, actualPluginIds); + } + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestPluginFactory.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestPluginFactory.java new file mode 100644 index 000000000..49e574f03 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestPluginFactory.java @@ -0,0 +1,77 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.function.Consumer; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginFactory.Factory; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.wrappers.MutableBoolean; + +public class AT_TestPluginFactory { + + @Test + @UnitTestMethod(target = TestPluginFactory.class, name = "factory", args = { Consumer.class }) + public void testFactory_Consumer() { + MutableBoolean executed = new MutableBoolean(); + Factory factory = TestPluginFactory.factory(c -> executed.setValue(true)); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + assertTrue(executed.getValue()); + + // precondition: consumer is null + Consumer nullConsumer = null; + ContractException contractException = assertThrows(ContractException.class, + () -> TestPluginFactory.factory(nullConsumer)); + assertEquals(NucleusError.NULL_ACTOR_CONTEXT_CONSUMER, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = TestPluginFactory.class, name = "factory", args = { TestPluginData.class }) + public void testFactory_TestPluginData() { + MutableBoolean executed = new MutableBoolean(); + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, c -> executed.setValue(true))); + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = TestPluginFactory.factory(testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + assertTrue(executed.getValue()); + + // precondition: testPluginData is null + TestPluginData nullTestPluginData = null; + ContractException contractException = assertThrows(ContractException.class, + () -> TestPluginFactory.factory(nullTestPluginData)); + assertEquals(NucleusError.NULL_PLUGIN_DATA, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = TestPluginFactory.Factory.class, name = "getPlugins", args = {}) + public void testGetPlugins() { + + List plugins = TestPluginFactory.factory((c) -> { + }).getPlugins(); + assertEquals(1, plugins.size()); + + Plugin testPlugin = null; + + for (Plugin plugin : plugins) { + if (plugin.getPluginId().equals(TestPluginId.PLUGIN_ID)) { + assertNull(testPlugin); + testPlugin = plugin; + } + } + + assertNotNull(testPlugin); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestPluginId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestPluginId.java new file mode 100644 index 000000000..d90e79437 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestPluginId.java @@ -0,0 +1,17 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestField; + +public class AT_TestPluginId { + + @Test + @UnitTestField(target = TestPluginId.class, name = "PLUGIN_ID") + public void testPluginId() { + assertNotNull(TestPluginId.PLUGIN_ID); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestReport.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestReport.java new file mode 100644 index 000000000..6ec4a5fa5 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestReport.java @@ -0,0 +1,83 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.wrappers.MultiKey; + +public class AT_TestReport { + + @Test + @UnitTestMethod(target = TestReport.class, name = "init", args = { ReportContext.class }) + public void testInit() { + // create two aliases + Object alias1 = "report alias 1"; + Object alias2 = "report alias 2"; + + // create containers for expected and actual observations + Set expectedObservations = new LinkedHashSet<>(); + expectedObservations.add(new MultiKey(alias1, 3.0)); + expectedObservations.add(new MultiKey(alias2, 3.0)); + expectedObservations.add(new MultiKey(alias1, 4.212)); + expectedObservations.add(new MultiKey(alias1, 5.123)); + expectedObservations.add(new MultiKey(alias2, 43.0)); + expectedObservations.add(new MultiKey(alias1, 12.123)); + expectedObservations.add(new MultiKey(alias1, 8.534)); + expectedObservations.add(new MultiKey(alias2, 1.423)); + + Set actualObservations = new LinkedHashSet<>(); + + // add the reports to the action plugin + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + /* + * Create ReportActionPlans from the expected observations. Each action + * plan will record a Multikey into the actual observations. + */ + for (MultiKey multiKey : expectedObservations) { + Object expectedAlias = multiKey.getKey(0); + Double expectedTime = multiKey.getKey(1); + pluginDataBuilder.addTestReportPlan(expectedAlias, new TestReportPlan(expectedTime, (c) -> { + actualObservations.add(new MultiKey(expectedAlias, c.getTime())); + })); + } + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(Double.POSITIVE_INFINITY,(c)->{})); + + // build the action plugin + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + //execute the simulation + List plugins = new ArrayList<>(); + plugins.add(testPlugin); + + TestSimulation.builder().addPlugins(plugins).build().execute(); + + + + // show that the reports executed the expected actions + assertEquals(expectedObservations, actualObservations); + + } + + @Test + @UnitTestConstructor(target = TestReport.class, args = { Object.class }) + public void testConstructor() { + /* + * The test of the init() method suffices to show that the alias value + * passed in the constructor is utilized as designed. + */ + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestReportPlan.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestReportPlan.java new file mode 100644 index 000000000..fdc5f8e31 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestReportPlan.java @@ -0,0 +1,137 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.function.Consumer; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.random.RandomGeneratorProvider; + +public class AT_TestReportPlan { + + @Test + @UnitTestConstructor(target = TestReportPlan.class, args = { double.class, Consumer.class }) + public void testConstructor() { + + TestReportPlan testReportPlan = new TestReportPlan(0.0, (c) -> { + }); + assertEquals(0.0, testReportPlan.getScheduledTime()); + assertFalse(testReportPlan.executed()); + } + + @Test + @UnitTestConstructor(target = TestReportPlan.class, args = { TestReportPlan.class }) + public void testConstructor_fromExistingPlan() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7814286176804755234L); + + for (int i = 0; i < 100; i++) { + double scheduledTime = randomGenerator.nextDouble(); + + TestReportPlan originalTestReportPlan = new TestReportPlan(scheduledTime, (c) -> { + }); + TestReportPlan newTestReportPlan = new TestReportPlan(originalTestReportPlan); + + assertEquals(scheduledTime, newTestReportPlan.getScheduledTime()); + + } + } + + /** + * Show that the agent action plan can be executed and will result in + * executed() returning true even if an exception is thrown in the plan. + */ + @Test + @UnitTestMethod(target = TestReportPlan.class, name = "executed", args = {}) + public void testExecuted() { + + TestReportPlan testReportPlan = new TestReportPlan(0.0, (c) -> { + }); + assertFalse(testReportPlan.executed()); + testReportPlan.executeAction(null); + assertTrue(testReportPlan.executed()); + + TestReportPlan testReportPlanWithException = new TestReportPlan(0.0, (c) -> { + throw new RuntimeException(); + }); + assertFalse(testReportPlanWithException.executed()); + assertThrows(RuntimeException.class, () -> testReportPlanWithException.executeAction(null)); + assertTrue(testReportPlanWithException.executed()); + } + + + + @Test + @UnitTestMethod(target = TestReportPlan.class, name = "getScheduledTime", args = {}) + public void testGetScheduledTime() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(918257164535899051L); + + // use the various constructors + for (int i = 0; i < 300; i++) { + double planTime = randomGenerator.nextDouble() * 1000; + TestReportPlan testReportPlan = new TestReportPlan(planTime, (c) -> { + }); + assertEquals(planTime, testReportPlan.getScheduledTime()); + + testReportPlan = new TestReportPlan(planTime, (c) -> { + }); + assertEquals(planTime, testReportPlan.getScheduledTime()); + + testReportPlan = new TestReportPlan(planTime, (c) -> { + }); + assertEquals(planTime, testReportPlan.getScheduledTime()); + } + + } + + @Test + @UnitTestMethod(target = TestReportPlan.class, name = "equals", args = { Object.class }) + public void testEquals() { + + + TestReportPlan plan1 = new TestReportPlan(4.5, (c) -> { + }); + TestReportPlan plan2 = new TestReportPlan(4.5, (c) -> { + }); + assertEquals(plan1, plan2); + + + plan1 = new TestReportPlan(6.5, (c) -> { + }); + plan2 = new TestReportPlan(4.5, (c) -> { + }); + assertNotEquals(plan1, plan2); + + + + } + + @Test + @UnitTestMethod(target = TestReportPlan.class, name = "hashCode", args = {}) + public void testHashCode() { + /* + * show that equal objects have equal hash codes + */ + TestReportPlan plan1 = new TestReportPlan(4.5, (c) -> { + }); + TestReportPlan plan2 = new TestReportPlan(4.5, (c) -> { + }); + assertEquals(plan1.hashCode(), plan2.hashCode()); + + // via the copy constructor + plan1 = new TestReportPlan(4.5, (c) -> { + }); + plan2 = new TestReportPlan(plan1); + assertEquals(plan1.hashCode(), plan2.hashCode()); + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestScenarioReport.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestScenarioReport.java new file mode 100644 index 000000000..4642611ea --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestScenarioReport.java @@ -0,0 +1,78 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_TestScenarioReport { + + @Test + @UnitTestConstructor(target = TestScenarioReport.class, args = boolean.class) + public void testConstructor() { + // covered by other tests + } + + @Test + @UnitTestMethod(target = TestScenarioReport.class, name = "equals", args = { Object.class }) + public void testEquals() { + + // create a few reports with different completion states + TestScenarioReport report1 = new TestScenarioReport(true); + TestScenarioReport report2 = new TestScenarioReport(true); + TestScenarioReport report3 = new TestScenarioReport(true); + + TestScenarioReport report4 = new TestScenarioReport(false); + TestScenarioReport report5 = new TestScenarioReport(false); + TestScenarioReport report6 = new TestScenarioReport(false); + + // show reflexive equality + assertEquals(report1, report1); + assertEquals(report2, report2); + assertEquals(report3, report3); + assertEquals(report4, report4); + assertEquals(report5, report5); + assertEquals(report6, report6); + + // show symmetric equality + assertEquals(report1, report2); + assertEquals(report2, report1); + + // show transitivity + assertEquals(report1, report2); + assertEquals(report2, report3); + assertEquals(report1, report3); + + // show that equality is equivalent to completion + assertNotEquals(report1, report4); + + } + + @Test + @UnitTestMethod(target = TestScenarioReport.class, name = "hashCode", args = {}) + public void testHashCode() { + // equal objects have equal hash codes + TestScenarioReport report1 = new TestScenarioReport(true); + TestScenarioReport report2 = new TestScenarioReport(true); + TestScenarioReport report3 = new TestScenarioReport(false); + TestScenarioReport report4 = new TestScenarioReport(false); + + assertEquals(report1.hashCode(), report2.hashCode()); + assertEquals(report3.hashCode(), report4.hashCode()); + } + + @Test + @UnitTestMethod(target = TestScenarioReport.class, name = "isComplete", args = {}) + public void testIsComplete() { + TestScenarioReport report1 = new TestScenarioReport(true); + assertTrue(report1.isComplete()); + TestScenarioReport report2 = new TestScenarioReport(false); + assertFalse(report2.isComplete()); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestSimulation.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestSimulation.java new file mode 100644 index 000000000..62429c477 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/AT_TestSimulation.java @@ -0,0 +1,310 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.PlanQueueData; +import gov.hhs.aspr.ms.gcm.nucleus.Planner; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.SimulationState; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.runcontinuityplugin.RunContinuityPlanData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.runcontinuityplugin.RunContinuityPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.runcontinuityplugin.RunContinuityPluginData; +import util.annotations.UnitTestMethod; +import util.random.RandomGeneratorProvider; +import util.wrappers.MutableBoolean; + +public class AT_TestSimulation { + + @Test + @UnitTestMethod(target = TestSimulation.Builder.class, name = "build", args = {}) + public void testBuild() { + assertNotNull(TestSimulation.builder().build()); + } + + @Test + @UnitTestMethod(target = TestSimulation.Builder.class, name = "setProduceSimulationStateOnHalt", args = { + boolean.class }) + public void testProduceSimulationStateOnHalt() { + + for (int i = 0; i < 10; i++) { + TestPluginData testPluginData = TestPluginData.builder() + .addTestActorPlan("testActor", new TestActorPlan(i, (c) -> { + })).build(); + + RunContinuityPluginData runContinuityPluginData = RunContinuityPluginData.builder() + .addContextConsumer(i, (c) -> { + }).build(); + + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + Plugin plugin = RunContinuityPlugin.builder().setRunContinuityPluginData(runContinuityPluginData).build(); + + double haltTime = 10; + TestSimulation testSimulation = TestSimulation.builder().setProduceSimulationStateOnHalt(true) + .setSimulationHaltTime(haltTime).addPlugin(testPlugin).addPlugin(plugin).build(); + + TestOutputConsumer testOutputConsumer = testSimulation.execute(); + + Optional simState = testOutputConsumer.getOutputItem(SimulationState.class); + assertTrue(simState.isPresent()); + + List pluginDatas = testOutputConsumer + .getOutputItems(RunContinuityPluginData.class); + + assertFalse(pluginDatas.isEmpty()); + assertTrue(pluginDatas.size() == 1); + + RunContinuityPluginData pluginData = pluginDatas.get(0); + assertTrue(pluginData.allPlansComplete()); + assertEquals(1, pluginData.getCompletionCount()); + } + + } + + @Test + @UnitTestMethod(target = TestSimulation.Builder.class, name = "setSimulationHaltTime", args = { double.class }) + public void testSetSimulationHaltTime() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2020906151186894101L); + + for (int i = 0; i < 10; i++) { + double haltTime = randomGenerator.nextInt(100) + 1; + + TestPluginData testPluginData = TestPluginData.builder() + .addTestActorPlan("testActor", new TestActorPlan(haltTime - 1, (c) -> { + })).build(); + + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + TestSimulation testSimulation = TestSimulation.builder().setProduceSimulationStateOnHalt(true) + .setSimulationHaltTime(haltTime).addPlugin(testPlugin).build(); + + TestOutputConsumer testOutputConsumer = testSimulation.execute(); + + Optional osimulationState = testOutputConsumer.getOutputItem(SimulationState.class); + + assertTrue(osimulationState.isPresent()); + + SimulationState simState = osimulationState.get(); + + assertEquals(haltTime, simState.getStartTime()); + } + + } + + @Test + @UnitTestMethod(target = TestSimulation.Builder.class, name = "setSimulationState", args = { + SimulationState.class }) + public void testSetSimulationState() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3070829833293509127L); + + for (int i = 0; i < 10; i++) { + double startTime = randomGenerator.nextInt(10) + 1; + double planTime = startTime + 2; + MutableBoolean called = new MutableBoolean(false); + + RunContinuityPlanData runContinuityPlanData = new RunContinuityPlanData(0); + RunContinuityPluginData runContinuityPluginData = RunContinuityPluginData.builder() + .addContextConsumer(planTime, (c) -> called.setValue(true)).setPlansAreScheduled(true).build(); + + Plugin actorPlugin = RunContinuityPlugin.builder().setRunContinuityPluginData(runContinuityPluginData) + .build(); + + TestPluginData testPluginData = TestPluginData.builder() + .addTestActorPlan("test actor 2", new TestActorPlan(planTime++, (context) -> { + + })).build(); + + PlanQueueData planQueueData = PlanQueueData.builder().setPlanData(runContinuityPlanData).setTime(planTime) + .setPlanner(Planner.ACTOR).build(); + + SimulationState simulationState = SimulationState.builder().addPlanQueueData(planQueueData) + .setStartTime(startTime).setPlanningQueueArrivalId(2).build(); + + TestSimulation testSimulation = TestSimulation.builder().addPlugin(actorPlugin) + .addPlugin(TestPlugin.getTestPlugin(testPluginData)).setSimulationState(simulationState).build(); + + TestOutputConsumer testOutputConsumer = testSimulation.execute(); + + assertTrue(called.getValue()); + + List pluginDatas = testOutputConsumer + .getOutputItems(RunContinuityPluginData.class); + + assertTrue(pluginDatas.size() == 1); + + RunContinuityPluginData pluginData = pluginDatas.get(0); + + assertEquals(1, pluginData.getCompletionCount()); + assertTrue(pluginData.allPlansComplete()); + } + + } + + @Test + @UnitTestMethod(target = TestSimulation.Builder.class, name = "addPlugin", args = { Plugin.class }) + public void testAddPlugin() { + + /* + * We will create two plugins and show that they are exeucted by the simulation, + * and thus the plugin addition is working properly + */ + RunContinuityPluginData runContinuityPluginData = RunContinuityPluginData.builder() + .addContextConsumer(0, (c) -> { + })// + .build(); + + Plugin runContinuityPlugin = RunContinuityPlugin.builder()// + .setRunContinuityPluginData(runContinuityPluginData).build(); + + TestPluginData testPluginData = TestPluginData.builder() + .addTestActorPlan("test actor 2", new TestActorPlan(1, (context) -> { + })).build(); + + Plugin testActorPlugin = TestPlugin.getTestPlugin(testPluginData); + + TestSimulation testSimulation = TestSimulation.builder()// + .addPlugin(runContinuityPlugin)// + .addPlugin(testActorPlugin)// + .build(); + + TestOutputConsumer testOutputConsumer = testSimulation.execute(); + + /* + * we show that the TestPlugin was added properly and executed + */ + + Optional optionalTestScenarioReport = testOutputConsumer + .getOutputItem(TestScenarioReport.class); + assertTrue(optionalTestScenarioReport.isPresent()); + TestScenarioReport testScenarioReport = optionalTestScenarioReport.get(); + assertTrue(testScenarioReport.isComplete()); + + // we show that the RunContinuityPlugin was added properly and executed + Optional optionalRunContinuityPluginData = testOutputConsumer + .getOutputItem(RunContinuityPluginData.class); + assertTrue(optionalRunContinuityPluginData.isPresent()); + runContinuityPluginData = optionalRunContinuityPluginData.get(); + assertEquals(1, runContinuityPluginData.getCompletionCount()); + assertTrue(runContinuityPluginData.allPlansComplete()); + + } + + @Test + @UnitTestMethod(target = TestSimulation.Builder.class, name = "addPlugins", args = { Collection.class }) + public void testAddPlugins() { + /* + * We will create two plugins and show that they are exeucted by the simulation, + * and thus the plugin addition is working properly + */ + RunContinuityPluginData runContinuityPluginData = RunContinuityPluginData.builder() + .addContextConsumer(0, (c) -> { + })// + .build(); + + Plugin runContinuityPlugin = RunContinuityPlugin.builder()// + .setRunContinuityPluginData(runContinuityPluginData).build(); + + TestPluginData testPluginData = TestPluginData.builder() + .addTestActorPlan("test actor 2", new TestActorPlan(1, (context) -> { + })).build(); + + Plugin testActorPlugin = TestPlugin.getTestPlugin(testPluginData); + + List plugins = new ArrayList<>(); + + plugins.add(runContinuityPlugin); + plugins.add(testActorPlugin); + + TestSimulation testSimulation = TestSimulation.builder()// + .addPlugins(plugins)// + .build(); + + TestOutputConsumer testOutputConsumer = testSimulation.execute(); + + /* + * we show that the TestPlugin was added properly and executed + */ + + Optional optionalTestScenarioReport = testOutputConsumer + .getOutputItem(TestScenarioReport.class); + assertTrue(optionalTestScenarioReport.isPresent()); + TestScenarioReport testScenarioReport = optionalTestScenarioReport.get(); + assertTrue(testScenarioReport.isComplete()); + + // we show that the RunContinuityPlugin was added properly and executed + Optional optionalRunContinuityPluginData = testOutputConsumer + .getOutputItem(RunContinuityPluginData.class); + assertTrue(optionalRunContinuityPluginData.isPresent()); + runContinuityPluginData = optionalRunContinuityPluginData.get(); + assertEquals(1, runContinuityPluginData.getCompletionCount()); + assertTrue(runContinuityPluginData.allPlansComplete()); + } + + @Test + @UnitTestMethod(target = TestSimulation.class, name = "builder", args = {}) + public void testBuilder() { + assertNotNull(TestSimulation.builder()); + } + + @Test + @UnitTestMethod(target = TestSimulation.class, name = "execute", args = {}) + public void testExecute() { + /* + * We will create two plugins and show that they are exeucted by the simulation, + * and thus the plugin addition is working properly + */ + RunContinuityPluginData runContinuityPluginData = RunContinuityPluginData.builder() + .addContextConsumer(0, (c) -> { + })// + .build(); + + Plugin runContinuityPlugin = RunContinuityPlugin.builder()// + .setRunContinuityPluginData(runContinuityPluginData).build(); + + TestPluginData testPluginData = TestPluginData.builder() + .addTestActorPlan("test actor 2", new TestActorPlan(1, (context) -> { + })).build(); + + Plugin testActorPlugin = TestPlugin.getTestPlugin(testPluginData); + + List plugins = new ArrayList<>(); + + plugins.add(runContinuityPlugin); + plugins.add(testActorPlugin); + + TestSimulation testSimulation = TestSimulation.builder()// + .addPlugins(plugins)// + .build(); + + TestOutputConsumer testOutputConsumer = testSimulation.execute(); + + /* + * we show that the TestPlugin was added properly and executed + */ + + Optional optionalTestScenarioReport = testOutputConsumer + .getOutputItem(TestScenarioReport.class); + assertTrue(optionalTestScenarioReport.isPresent()); + TestScenarioReport testScenarioReport = optionalTestScenarioReport.get(); + assertTrue(testScenarioReport.isComplete()); + + // we show that the RunContinuityPlugin was added properly and executed + Optional optionalRunContinuityPluginData = testOutputConsumer + .getOutputItem(RunContinuityPluginData.class); + assertTrue(optionalRunContinuityPluginData.isPresent()); + runContinuityPluginData = optionalRunContinuityPluginData.get(); + assertEquals(1, runContinuityPluginData.getCompletionCount()); + assertTrue(runContinuityPluginData.allPlansComplete()); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/PluginEquivalence.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/PluginEquivalence.java new file mode 100644 index 000000000..1c5a130e7 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/testsupport/testplugin/PluginEquivalence.java @@ -0,0 +1,62 @@ +package gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; + +/** + * A static utility class for comparing the content of two lists of plugins. + * + * + * + */ + +public final class PluginEquivalence { + + private PluginEquivalence() {} + /** + * Asserts that two lists of plugins are equivalent. Two lists of plugins + * are considered equivalent they are not null, contain no null entries and + * contain the same set of plugins. + */ + public static void assertEquivalent(List expectedPlugins, List actualPlugins) { + + assertNotNull(expectedPlugins, "expected plugins list is null"); + assertNotNull(actualPlugins, "actual plugins list is null"); + + Map expectedMap = new LinkedHashMap<>(); + for (Plugin plugin : expectedPlugins) { + assertNotNull(plugin, "expected plugins contains a null plugin"); + assertNull(expectedMap.put(plugin.getPluginId(), plugin), "expected plugins contains a duplicate plugin for " + plugin.getPluginId()); + } + for (Plugin plugin : actualPlugins) { + assertNotNull(plugin, "actual plugins contains a null plugin"); + assertNull(expectedMap.put(plugin.getPluginId(), plugin), "actual plugins contains a duplicate plugin for " + plugin.getPluginId()); + } + + Map actualMap = new LinkedHashMap<>(); + for (Plugin plugin : actualPlugins) { + assertNotNull(plugin, "actual plugins contains a null plugin"); + assertNull(expectedMap.put(plugin.getPluginId(), plugin), "actual plugins contains a duplicate plugin for " + plugin.getPluginId()); + } + for (Plugin plugin : actualPlugins) { + assertNotNull(plugin, "actual plugins contains a null plugin"); + assertNull(expectedMap.put(plugin.getPluginId(), plugin), "actual plugins contains a duplicate plugin for " + plugin.getPluginId()); + } + + assertEquals(expectedMap.keySet(), actualMap.keySet(), "expected and actual plugins do not contain the same plugin ids"); + + for (PluginId pluginId : expectedMap.keySet()) { + Plugin expectedPlugin = expectedMap.get(pluginId); + Plugin actualPlugin = actualMap.get(pluginId); + assertEquals(expectedPlugin, actualPlugin, "Plugin equality failure for " + pluginId); + } + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/util/AT_TriConsumer.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/util/AT_TriConsumer.java new file mode 100644 index 000000000..665f1a6d4 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/nucleus/util/AT_TriConsumer.java @@ -0,0 +1,31 @@ +package gov.hhs.aspr.ms.gcm.nucleus.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestMethod; +import util.wrappers.MutableInteger; + +public class AT_TriConsumer { + + @Test + @UnitTestMethod(target = TriConsumer.class, name = "andThen", args = { TriConsumer.class }) + public void testAndThen() { + + MutableInteger value = new MutableInteger(); + + TriConsumer t1 = (i, s, d) -> { + value.increment(5); + }; + TriConsumer t2 = (i, s, d) -> { + value.increment(17); + }; + + t1.andThen(t2).accept(6, "4", 2.6); + ; + + assertEquals(22, value.getValue()); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/AT_GlobalPropertiesPlugin.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/AT_GlobalPropertiesPlugin.java new file mode 100644 index 000000000..7663f2ae7 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/AT_GlobalPropertiesPlugin.java @@ -0,0 +1,73 @@ +package gov.hhs.aspr.ms.gcm.plugins.globalproperties; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.reports.GlobalPropertyReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import util.annotations.UnitTestMethod; + +public class AT_GlobalPropertiesPlugin { + + @Test + @UnitTestMethod(target = GlobalPropertiesPlugin.class, name = "builder", args = {}) + public void testBuilder() { + assertNotNull(GlobalPropertiesPlugin.builder()); + } + + @Test + @UnitTestMethod(target = GlobalPropertiesPlugin.Builder.class, name = "setGlobalPropertiesPluginData", args = { + GlobalPropertiesPluginData.class }) + public void testSetGlobalPropertiesPluginData() { + + GlobalPropertiesPluginData globalPropertiesPluginData = GlobalPropertiesPluginData.builder().build(); + Plugin globalsPlugin = GlobalPropertiesPlugin.builder() + .setGlobalPropertiesPluginData(globalPropertiesPluginData).getGlobalPropertiesPlugin(); + + assertTrue(globalsPlugin.getPluginDatas().contains(globalPropertiesPluginData)); + } + + @Test + @UnitTestMethod(target = GlobalPropertiesPlugin.Builder.class, name = "setGlobalPropertyReportPluginData", args = { + GlobalPropertyReportPluginData.class }) + public void testSetGlobalPropertyReportPluginData() { + GlobalPropertiesPluginData globalPropertiesPluginData = GlobalPropertiesPluginData.builder().build(); + GlobalPropertyReportPluginData globalPropertyReportPluginData = GlobalPropertyReportPluginData.builder() + .setReportLabel(new SimpleReportLabel("test")).build(); + Plugin globalsPlugin = GlobalPropertiesPlugin.builder() + .setGlobalPropertiesPluginData(globalPropertiesPluginData) + .setGlobalPropertyReportPluginData(globalPropertyReportPluginData).getGlobalPropertiesPlugin(); + + assertTrue(globalsPlugin.getPluginDatas().contains(globalPropertyReportPluginData)); + } + + @Test + @UnitTestMethod(target = GlobalPropertiesPlugin.Builder.class, name = "getGlobalPropertiesPlugin", args = {}) + public void testGetGlobalPropertiesPlugin() { + /* + * Show that the plugin contains the plugin data and has the property id and + * dependencies + */ + + GlobalPropertiesPluginData globalPropertiesPluginData = GlobalPropertiesPluginData.builder().build(); + Plugin globalsPlugin = GlobalPropertiesPlugin.builder() + .setGlobalPropertiesPluginData(globalPropertiesPluginData).getGlobalPropertiesPlugin(); + + assertTrue(globalsPlugin.getPluginDatas().contains(globalPropertiesPluginData)); + assertEquals(GlobalPropertiesPluginId.PLUGIN_ID, globalsPlugin.getPluginId()); + + Set expectedDependencies = new LinkedHashSet<>(); + assertEquals(expectedDependencies, globalsPlugin.getPluginDependencies()); + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/AT_GlobalPropertiesPluginId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/AT_GlobalPropertiesPluginId.java new file mode 100644 index 000000000..13dc48c76 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/AT_GlobalPropertiesPluginId.java @@ -0,0 +1,17 @@ +package gov.hhs.aspr.ms.gcm.plugins.globalproperties; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestField; + + +public class AT_GlobalPropertiesPluginId { + + @Test + @UnitTestField(target = GlobalPropertiesPluginId.class,name = "PLUGIN_ID") + public void testPluginId() { + assertNotNull(GlobalPropertiesPluginId.PLUGIN_ID); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/datamanagers/AT_GlobalPropertiesDataManager.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/datamanagers/AT_GlobalPropertiesDataManager.java new file mode 100644 index 000000000..6fb0c07af --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/datamanagers/AT_GlobalPropertiesDataManager.java @@ -0,0 +1,1169 @@ +package gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.IntStream; + +import org.apache.commons.math3.random.RandomGenerator; +import org.apache.commons.math3.util.FastMath; +import org.apache.commons.math3.util.Pair; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; +import gov.hhs.aspr.ms.gcm.nucleus.EventFilter; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.Simulation; +import gov.hhs.aspr.ms.gcm.nucleus.SimulationState; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.runcontinuityplugin.RunContinuityPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.runcontinuityplugin.RunContinuityPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestOutputConsumer; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.GlobalPropertiesPlugin; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.events.GlobalPropertyDefinitionEvent; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.events.GlobalPropertyUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertiesError; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertyInitialization; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.SimpleGlobalPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.testsupport.GlobalPropertiesTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.testsupport.GlobalPropertiesTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.testsupport.TestAuxiliaryGlobalPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.testsupport.TestGlobalPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; +import util.wrappers.MultiKey; + +public final class AT_GlobalPropertiesDataManager { + /** + * Demonstrates that the data manager exhibits run continuity. The state of the + * data manager is not effected by repeatedly starting and stopping the + * simulation. + */ + @Test + @UnitTestMethod(target = GlobalPropertiesDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testStateContinuity() { + + /* + * Note that we are not testing the content of the plugin datas -- that is + * covered by the other state tests. We show here only that the resulting plugin + * data state is the same without regard to how we break up the run. + */ + + Set pluginDatas = new LinkedHashSet<>(); + + pluginDatas.add(testStateContinuity(1)); + pluginDatas.add(testStateContinuity(5)); + pluginDatas.add(testStateContinuity(10)); + + assertEquals(1, pluginDatas.size()); + + } + + /* + * Returns the GlobalPropertiesPluginData resulting from several global + * properties related events over several days. Attempt to stop and start the + * simulation by the given number of increments. + */ + private String testStateContinuity(int incrementCount) { + + String result = null; + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5369912793633438426L); + + /* + * Build the RunContinuityPluginData with context consumers that will add and + * set global property values + */ + RunContinuityPluginData.Builder continuityBuilder = RunContinuityPluginData.builder(); + + int taskTime = 0; + + for (int i = 0; i < 50; i++) { + continuityBuilder.addContextConsumer(taskTime++, (c) -> { + + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + + // try to add a new property definition + List candidates = new ArrayList<>(); + for (TestGlobalPropertyId globalPropertyId : TestGlobalPropertyId.values()) { + if (!globalPropertiesDataManager.globalPropertyIdExists(globalPropertyId)) { + candidates.add(globalPropertyId); + } + } + + if (!candidates.isEmpty()) { + TestGlobalPropertyId testGlobalPropertyId = candidates + .get(randomGenerator.nextInt(candidates.size())); + PropertyDefinition propertyDefinition = testGlobalPropertyId.getPropertyDefinition(); + GlobalPropertyInitialization globalPropertyInitialization; + if (propertyDefinition.getDefaultValue().isEmpty()) { + Object propertyValue = testGlobalPropertyId.getRandomPropertyValue(randomGenerator); + globalPropertyInitialization = // + GlobalPropertyInitialization.builder()// + .setGlobalPropertyId(testGlobalPropertyId)// + .setPropertyDefinition(propertyDefinition)// + .setValue(propertyValue)// + .build(); + } else { + globalPropertyInitialization = // + GlobalPropertyInitialization.builder()// + .setGlobalPropertyId(testGlobalPropertyId)// + .setPropertyDefinition(propertyDefinition)// + .build(); + } + globalPropertiesDataManager.defineGlobalProperty(globalPropertyInitialization); + + } + + // find a global property to update + + candidates.clear(); + + for (TestGlobalPropertyId globalPropertyId : TestGlobalPropertyId.values()) { + if (globalPropertiesDataManager.globalPropertyIdExists(globalPropertyId)) { + if (globalPropertiesDataManager.getGlobalPropertyDefinition(globalPropertyId) + .propertyValuesAreMutable()) { + candidates.add(globalPropertyId); + } + } + } + + if (!candidates.isEmpty()) { + TestGlobalPropertyId testGlobalPropertyId = candidates + .get(randomGenerator.nextInt(candidates.size())); + Object propertyValue = testGlobalPropertyId.getRandomPropertyValue(randomGenerator); + globalPropertiesDataManager.setGlobalPropertyValue(testGlobalPropertyId, propertyValue); + } + + }); + } + + continuityBuilder.addContextConsumer(taskTime++, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + c.releaseOutput(globalPropertiesDataManager.toString()); + }); + + RunContinuityPluginData runContinuityPluginData = continuityBuilder.build(); + + // Build an empty global properties plugin data for time zero + GlobalPropertiesPluginData globalPropertiesPluginData = GlobalPropertiesPluginData.builder().build(); + + // build the initial simulation state data -- time starts at zero + SimulationState simulationState = SimulationState.builder().build(); + + /* + * Run the simulation in one day increments until all the plans in the run + * continuity plugin data have been executed + */ + double haltTime = 0; + double maxTime = Double.NEGATIVE_INFINITY; + for (Pair> pair : runContinuityPluginData.getConsumers()) { + Double time = pair.getFirst(); + maxTime = FastMath.max(maxTime, time); + } + double timeIncrement = maxTime / incrementCount; + while (!runContinuityPluginData.allPlansComplete()) { + haltTime += timeIncrement; + + // build the run continuity plugin + Plugin runContinuityPlugin = RunContinuityPlugin.builder()// + .setRunContinuityPluginData(runContinuityPluginData)// + .build(); + + // build the people plugin + Plugin globalPropertiesPlugin = GlobalPropertiesPlugin.builder()// + .setGlobalPropertiesPluginData(globalPropertiesPluginData)// + .getGlobalPropertiesPlugin(); + + TestOutputConsumer outputConsumer = new TestOutputConsumer(); + + // execute the simulation so that it produces a people plugin data + Simulation simulation = Simulation.builder()// + .addPlugin(globalPropertiesPlugin)// + .addPlugin(runContinuityPlugin)// + .setSimulationHaltTime(haltTime)// + .setRecordState(true)// + .setOutputConsumer(outputConsumer)// + .setSimulationState(simulationState)// + .build();// + simulation.execute(); + + // retrieve the people plugin data + globalPropertiesPluginData = outputConsumer.getOutputItem(GlobalPropertiesPluginData.class).get(); + + // retrieve the simulation state + simulationState = outputConsumer.getOutputItem(SimulationState.class).get(); + + // retrieve the run continuity plugin data + runContinuityPluginData = outputConsumer.getOutputItem(RunContinuityPluginData.class).get(); + + Optional optional = outputConsumer.getOutputItem(String.class); + if (optional.isPresent()) { + result = optional.get(); + } + } + + // show that the result is a large string + assertNotNull(result); + assertTrue(result.length() > 100); + + return result; + + } + + @Test + @UnitTestConstructor(target = GlobalPropertiesDataManager.class, args = { GlobalPropertiesPluginData.class }) + public void testConstructor() { + ContractException contractException = assertThrows(ContractException.class, + () -> new GlobalPropertiesDataManager(null)); + assertEquals(GlobalPropertiesError.NULL_GLOBAL_PLUGIN_DATA, contractException.getErrorType()); + } + + /** + * Demonstrates that the data manager's initial state reflects its plugin data + */ + @Test + @UnitTestMethod(target = GlobalPropertiesDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testStateInitialization() { + + Map expectedPropertyValues = new LinkedHashMap<>(); + GlobalPropertiesPluginData.Builder globalsPluginBuilder = GlobalPropertiesPluginData.builder(); + long seed = 5100286389011347218L; + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + PropertyDefinition propertyDefinition = testGlobalPropertyId.getPropertyDefinition(); + globalsPluginBuilder.defineGlobalProperty(testGlobalPropertyId, propertyDefinition, 0); + if (propertyDefinition.getDefaultValue().isPresent()) { + expectedPropertyValues.put(testGlobalPropertyId, propertyDefinition.getDefaultValue().get()); + } else { + Object value = testGlobalPropertyId.getRandomPropertyValue(randomGenerator); + globalsPluginBuilder.setGlobalPropertyValue(testGlobalPropertyId, value, 0); + expectedPropertyValues.put(testGlobalPropertyId, value); + } + } + // change two of the properties from their default values + globalsPluginBuilder.setGlobalPropertyValue(TestGlobalPropertyId.GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE, true, 0); + expectedPropertyValues.put(TestGlobalPropertyId.GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE, true); + + globalsPluginBuilder.setGlobalPropertyValue(TestGlobalPropertyId.GLOBAL_PROPERTY_2_INTEGER_MUTABLE, 456, 0); + expectedPropertyValues.put(TestGlobalPropertyId.GLOBAL_PROPERTY_2_INTEGER_MUTABLE, 456); + + GlobalPropertiesPluginData globalPropertiesPluginData = globalsPluginBuilder.build(); + + /* + * show that the Global Plugin Data is reflected in the initial state of the + * data manager + */ + TestPluginData.Builder testPluginDataBuilder = TestPluginData.builder(); + + testPluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + // show that the data manager exists + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + + // show that the global property ids are present + Set globalPropertyIds = globalPropertiesDataManager.getGlobalPropertyIds(); + assertEquals(TestGlobalPropertyId.values().length, globalPropertyIds.size()); + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + assertTrue(globalPropertyIds.contains(testGlobalPropertyId)); + } + + for (GlobalPropertyId globalPropertyId : expectedPropertyValues.keySet()) { + assertEquals(expectedPropertyValues.get(globalPropertyId), + globalPropertiesDataManager.getGlobalPropertyValue(globalPropertyId)); + } + + })); + + TestPluginData testPluginData = testPluginDataBuilder.build(); + Factory factory = GlobalPropertiesTestPluginFactory.factory(seed, testPluginData) + .setGlobalPropertiesPluginData(globalPropertiesPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + /** + * Demonstrates that the data manager produces plugin data that reflects its + * final state + */ + @Test + @UnitTestMethod(target = GlobalPropertiesDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testStateFinalization() { + + GlobalPropertiesPluginData.Builder globalsPluginBuilder = GlobalPropertiesPluginData.builder(); + GlobalPropertiesPluginData globalPropertiesPluginData = globalsPluginBuilder.build(); + + // add a property definition + PropertyDefinition propertyDefinition = TestGlobalPropertyId.GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE + .getPropertyDefinition(); + GlobalPropertyInitialization globalPropertyInitialization = GlobalPropertyInitialization.builder() + .setGlobalPropertyId(TestGlobalPropertyId.GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE) + .setPropertyDefinition(propertyDefinition).build(); + + TestPluginData.Builder testPluginDataBuilder = TestPluginData.builder(); + + // define property definition with the data manager + testPluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + globalPropertiesDataManager.defineGlobalProperty(globalPropertyInitialization); + globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyInitialization.getGlobalPropertyId(), + true); + })); + + // show that the plugin data contains what we defined + TestPluginData testPluginData = testPluginDataBuilder.build(); + Factory factory = GlobalPropertiesTestPluginFactory.factory(5100286389011347218L, testPluginData) + .setGlobalPropertiesPluginData(globalPropertiesPluginData); + TestOutputConsumer testOutputConsumer = TestSimulation.builder().addPlugins(factory.getPlugins()) + .setSimulationHaltTime(2).setProduceSimulationStateOnHalt(true).build().execute(); + Map outputItems = testOutputConsumer + .getOutputItemMap(GlobalPropertiesPluginData.class); + assertEquals(1, outputItems.size()); + GlobalPropertiesPluginData actualPluginData = outputItems.keySet().iterator().next(); + GlobalPropertiesPluginData expectedPluginData = GlobalPropertiesPluginData.builder() + .defineGlobalProperty(globalPropertyInitialization.getGlobalPropertyId(), + globalPropertyInitialization.getPropertyDefinition(), 0) + .setGlobalPropertyValue(globalPropertyInitialization.getGlobalPropertyId(), true, 0).build(); + assertEquals(expectedPluginData, actualPluginData); + + // show that the plugin data persists after multiple actions + PropertyDefinition propertyDefinition2 = TestGlobalPropertyId.GLOBAL_PROPERTY_2_INTEGER_MUTABLE + .getPropertyDefinition(); + GlobalPropertyInitialization globalPropertyInitialization2 = GlobalPropertyInitialization.builder() + .setGlobalPropertyId(TestGlobalPropertyId.GLOBAL_PROPERTY_2_INTEGER_MUTABLE) + .setPropertyDefinition(propertyDefinition2).build(); + + PropertyDefinition propertyDefinition3 = TestGlobalPropertyId.GLOBAL_PROPERTY_3_DOUBLE_MUTABLE + .getPropertyDefinition(); + GlobalPropertyInitialization globalPropertyInitialization3 = GlobalPropertyInitialization.builder() + .setGlobalPropertyId(TestGlobalPropertyId.GLOBAL_PROPERTY_3_DOUBLE_MUTABLE).setValue(10.0) + .setPropertyDefinition(propertyDefinition3).build(); + + testPluginDataBuilder = TestPluginData.builder(); + + testPluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + globalPropertiesDataManager.defineGlobalProperty(globalPropertyInitialization2); + globalPropertiesDataManager.defineGlobalProperty(globalPropertyInitialization3); + globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyInitialization2.getGlobalPropertyId(), 5); + globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyInitialization2.getGlobalPropertyId(), 3); + globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyInitialization3.getGlobalPropertyId(), + 14.5); + globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyInitialization3.getGlobalPropertyId(), + 32.8); + })); + + testPluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyInitialization2.getGlobalPropertyId(), 15); + globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyInitialization3.getGlobalPropertyId(), + 15.9); + })); + + testPluginData = testPluginDataBuilder.build(); + factory = GlobalPropertiesTestPluginFactory.factory(5100286389011347218L, testPluginData) + .setGlobalPropertiesPluginData(globalPropertiesPluginData); + testOutputConsumer = TestSimulation.builder().addPlugins(factory.getPlugins()).setSimulationHaltTime(2) + .setProduceSimulationStateOnHalt(true).build().execute(); + outputItems = testOutputConsumer.getOutputItemMap(GlobalPropertiesPluginData.class); + assertEquals(1, outputItems.size()); + actualPluginData = outputItems.keySet().iterator().next(); + expectedPluginData = GlobalPropertiesPluginData.builder()// + .defineGlobalProperty(globalPropertyInitialization2.getGlobalPropertyId(), + globalPropertyInitialization2.getPropertyDefinition(), 0)// + .defineGlobalProperty(globalPropertyInitialization3.getGlobalPropertyId(), + globalPropertyInitialization3.getPropertyDefinition(), 0)// + .setGlobalPropertyValue(globalPropertyInitialization3.getGlobalPropertyId(), 15.9, 1)// + .setGlobalPropertyValue(globalPropertyInitialization2.getGlobalPropertyId(), 15, 1)// + .build(); + assertEquals(expectedPluginData, actualPluginData); + } + + @Test + @UnitTestMethod(target = GlobalPropertiesDataManager.class, name = "globalPropertyIdExists", args = { + GlobalPropertyId.class }) + public void testGlobalPropertyIdExists() { + + Factory factory = GlobalPropertiesTestPluginFactory.factory(5100286389011347218L, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + assertTrue(globalPropertiesDataManager.globalPropertyIdExists(testGlobalPropertyId)); + } + + // show that a null global property id will return false + assertFalse(globalPropertiesDataManager.globalPropertyIdExists(null)); + + // show that an unknown global property id will return false + assertFalse(globalPropertiesDataManager.globalPropertyIdExists(new SimpleGlobalPropertyId("bad prop"))); + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = GlobalPropertiesDataManager.class, name = "setGlobalPropertyValue", args = { + GlobalPropertyId.class, Object.class }) + public void testSetGlobalPropertyValue() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7837412421821851663L); + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + TestGlobalPropertyId globalPropertyId = TestGlobalPropertyId.GLOBAL_PROPERTY_2_INTEGER_MUTABLE; + + // create some containers to hold the expected and actual observations + List expectedObservations = new ArrayList<>(); + List actualObservations = new ArrayList<>(); + + // have an observer record changes to the property + pluginDataBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + EventFilter eventFilter = globalPropertiesDataManager + .getEventFilterForGlobalPropertyUpdateEvent(globalPropertyId); + c.subscribe(eventFilter, (c2, e) -> { + MultiKey multiKey = new MultiKey(c2.getTime(), e.globalPropertyId(), e.previousPropertyValue(), + e.currentPropertyValue()); + actualObservations.add(multiKey); + }); + })); + + // Have the actor set the value of the global property 1 a few times + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + Integer currentValue = globalPropertiesDataManager.getGlobalPropertyValue(globalPropertyId); + Integer newValue = globalPropertyId.getRandomPropertyValue(randomGenerator); + globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId, newValue); + expectedObservations.add(new MultiKey(c.getTime(), globalPropertyId, currentValue, newValue)); + })); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + Integer currentValue = globalPropertiesDataManager.getGlobalPropertyValue(globalPropertyId); + Integer newValue = globalPropertyId.getRandomPropertyValue(randomGenerator); + globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId, newValue); + expectedObservations.add(new MultiKey(c.getTime(), globalPropertyId, currentValue, newValue)); + + })); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(3, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + Integer currentValue = globalPropertiesDataManager.getGlobalPropertyValue(globalPropertyId); + Integer newValue = globalPropertyId.getRandomPropertyValue(randomGenerator); + globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId, newValue); + expectedObservations.add(new MultiKey(c.getTime(), globalPropertyId, currentValue, newValue)); + })); + + TestPluginData testPluginData = pluginDataBuilder.build(); + Factory factory = GlobalPropertiesTestPluginFactory.factory(5100286389011347218L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // show that the observations were correct + assertEquals(3, expectedObservations.size()); + assertEquals(expectedObservations.size(), actualObservations.size()); + assertEquals(new LinkedHashSet<>(expectedObservations), new LinkedHashSet<>(actualObservations)); + + // precondition test: if the global property id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GlobalPropertiesTestPluginFactory.factory(5100286389011347218L, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + globalPropertiesDataManager.setGlobalPropertyValue(null, 15); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // if the global property id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GlobalPropertiesTestPluginFactory.factory(5100286389011347218L, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + globalPropertiesDataManager.setGlobalPropertyValue(TestGlobalPropertyId.getUnknownGlobalPropertyId(), + 15); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + // if the property value is null + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GlobalPropertiesTestPluginFactory.factory(5100286389011347218L, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + globalPropertiesDataManager + .setGlobalPropertyValue(TestGlobalPropertyId.GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE, null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_VALUE, contractException.getErrorType()); + + // if the global property definition indicates the property is not + // mutable + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GlobalPropertiesTestPluginFactory.factory(5100286389011347218L, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + globalPropertiesDataManager + .setGlobalPropertyValue(TestGlobalPropertyId.GLOBAL_PROPERTY_5_INTEGER_IMMUTABLE, 55); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.IMMUTABLE_VALUE, contractException.getErrorType()); + + // if the property value is incompatible with the property definition + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GlobalPropertiesTestPluginFactory.factory(5100286389011347218L, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + globalPropertiesDataManager + .setGlobalPropertyValue(TestGlobalPropertyId.GLOBAL_PROPERTY_2_INTEGER_MUTABLE, "value"); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GlobalPropertiesDataManager.class, name = "getGlobalPropertyValue", args = { + GlobalPropertyId.class }) + public void testGetGlobalPropertyValue() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1059537118783693383L); + + // show that values can be retrieved + Factory factory = GlobalPropertiesTestPluginFactory.factory(5100286389011347218L, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + PropertyDefinition propertyDefinition = globalPropertiesDataManager + .getGlobalPropertyDefinition(testGlobalPropertyId); + if (propertyDefinition.propertyValuesAreMutable()) { + Object expectedValue = testGlobalPropertyId.getRandomPropertyValue(randomGenerator); + globalPropertiesDataManager.setGlobalPropertyValue(testGlobalPropertyId, expectedValue); + Object actualValue = globalPropertiesDataManager.getGlobalPropertyValue(testGlobalPropertyId); + assertEquals(expectedValue, actualValue); + } + } + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition test : if the property id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GlobalPropertiesTestPluginFactory.factory(5100286389011347218L, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + globalPropertiesDataManager.getGlobalPropertyValue(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // precondition test : if the property id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GlobalPropertiesTestPluginFactory.factory(5100286389011347218L, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + globalPropertiesDataManager.getGlobalPropertyValue(TestGlobalPropertyId.getUnknownGlobalPropertyId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GlobalPropertiesDataManager.class, name = "getGlobalPropertyTime", args = { + GlobalPropertyId.class }) + public void testGetGlobalPropertyTime() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5323616867741088481L); + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + IntStream.range(0, 10).forEach((i) -> { + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(i, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + TestGlobalPropertyId globalPropertyId = TestGlobalPropertyId.GLOBAL_PROPERTY_3_DOUBLE_MUTABLE; + Double newValue = globalPropertyId.getRandomPropertyValue(randomGenerator); + globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId, newValue); + double globalPropertyTime = globalPropertiesDataManager.getGlobalPropertyTime(globalPropertyId); + assertEquals(c.getTime(), globalPropertyTime); + })); + }); + TestPluginData testPluginData = pluginDataBuilder.build(); + + Factory factory = GlobalPropertiesTestPluginFactory.factory(5100286389011347218L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GlobalPropertiesTestPluginFactory.factory(5100286389011347218L, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + globalPropertiesDataManager.getGlobalPropertyTime(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GlobalPropertiesTestPluginFactory.factory(5100286389011347218L, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + globalPropertiesDataManager.getGlobalPropertyTime(TestGlobalPropertyId.getUnknownGlobalPropertyId()); + + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GlobalPropertiesDataManager.class, name = "getGlobalPropertyIds", args = {}) + public void testGetGlobalPropertyIds() { + + Factory factory = GlobalPropertiesTestPluginFactory.factory(5100286389011347218L, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + + Set expectedGlobalPropertyIds = new LinkedHashSet<>(); + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + expectedGlobalPropertyIds.add(testGlobalPropertyId); + } + assertEquals(expectedGlobalPropertyIds, globalPropertiesDataManager.getGlobalPropertyIds()); + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = GlobalPropertiesDataManager.class, name = "getGlobalPropertyDefinition", args = { + GlobalPropertyId.class }) + public void testGetGlobalPropertyDefinition() { + Factory factory = GlobalPropertiesTestPluginFactory.factory(5100286389011347218L, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + assertEquals(testGlobalPropertyId.getPropertyDefinition(), + globalPropertiesDataManager.getGlobalPropertyDefinition(testGlobalPropertyId)); + } + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition : if the global property id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GlobalPropertiesTestPluginFactory.factory(5100286389011347218L, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + globalPropertiesDataManager.getGlobalPropertyDefinition(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // precondition : if the global property id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GlobalPropertiesTestPluginFactory.factory(5100286389011347218L, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + globalPropertiesDataManager + .getGlobalPropertyDefinition(TestGlobalPropertyId.getUnknownGlobalPropertyId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GlobalPropertiesDataManager.class, name = "defineGlobalProperty", args = { + GlobalPropertyInitialization.class }) + public void testDefineGlobalProperty() { + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // show that new global properties can be defined + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + + double planTime = 1; + for (TestAuxiliaryGlobalPropertyId auxPropertyId : TestAuxiliaryGlobalPropertyId.values()) { + + c.addPlan((c2) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c2 + .getDataManager(GlobalPropertiesDataManager.class); + PropertyDefinition expectedPropertyDefinition = auxPropertyId.getPropertyDefinition(); + GlobalPropertyInitialization globalPropertyInitialization = GlobalPropertyInitialization.builder() + .setGlobalPropertyId(auxPropertyId).setPropertyDefinition(expectedPropertyDefinition) + .build(); + globalPropertiesDataManager.defineGlobalProperty(globalPropertyInitialization); + + // show that the definition was added + PropertyDefinition actualPopertyDefinition = globalPropertiesDataManager + .getGlobalPropertyDefinition(auxPropertyId); + assertEquals(expectedPropertyDefinition, actualPopertyDefinition); + + // record the expected observation + MultiKey multiKey = new MultiKey(c2.getTime(), auxPropertyId, + expectedPropertyDefinition.getDefaultValue().get()); + expectedObservations.add(multiKey); + + // show that the property has the correct initial value + Object expectedValue = expectedPropertyDefinition.getDefaultValue().get(); + Object actualValue = globalPropertiesDataManager.getGlobalPropertyValue(auxPropertyId); + assertEquals(expectedValue, actualValue); + + // show that the property has the correct initial time + double expectedTime = c2.getTime(); + double actualTime = globalPropertiesDataManager.getGlobalPropertyTime(auxPropertyId); + assertEquals(expectedTime, actualTime); + + }, planTime++); + } + })); + + // have an observer collect the observations + pluginDataBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + c.subscribe(EventFilter.builder(GlobalPropertyDefinitionEvent.class).build(), (c2, e) -> { + // record the actual observation + MultiKey multiKey = new MultiKey(c2.getTime(), e.globalPropertyId(), e.initialPropertyValue()); + actualObservations.add(multiKey); + }); + })); + + /* + * Have the observer show the the expected and actual observations match after + * all the new property definitions have been added. + */ + double planTime = TestAuxiliaryGlobalPropertyId.values().length + 1; + pluginDataBuilder.addTestActorPlan("observer", new TestActorPlan(planTime, (c) -> { + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginDataBuilder.build(); + List plugins = GlobalPropertiesTestPluginFactory.factory(5100286389011347218L, testPluginData) + .getPlugins(); + TestSimulation.builder().addPlugins(plugins).build().execute(); + + // precondition test: if the global property initialization is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory = GlobalPropertiesTestPluginFactory.factory(5100286389011347218L, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + globalPropertiesDataManager.defineGlobalProperty(null); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + }); + assertEquals(GlobalPropertiesError.NULL_GLOBAL_PROPERTY_INITIALIZATION, contractException.getErrorType()); + + // precondition test: if the global property already exists + contractException = assertThrows(ContractException.class, () -> { + + Factory factory = GlobalPropertiesTestPluginFactory.factory(5100286389011347218L, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + GlobalPropertyId globalPropertyId = TestGlobalPropertyId.GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE; + PropertyDefinition propertyDefinition = TestGlobalPropertyId.GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE + .getPropertyDefinition(); + GlobalPropertyInitialization globalPropertyInitialization = // + GlobalPropertyInitialization.builder()// + .setGlobalPropertyId(globalPropertyId)// + .setPropertyDefinition(propertyDefinition)// + .build(); + globalPropertiesDataManager.defineGlobalProperty(globalPropertyInitialization); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.DUPLICATE_PROPERTY_DEFINITION, contractException.getErrorType()); + + } + + private static class LocalGlobalPropertyId implements GlobalPropertyId { + private final int id; + + public LocalGlobalPropertyId(int id) { + this.id = id; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + id; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof LocalGlobalPropertyId)) { + return false; + } + LocalGlobalPropertyId other = (LocalGlobalPropertyId) obj; + if (id != other.id) { + return false; + } + return true; + } + + } + + @Test + @UnitTestMethod(target = GlobalPropertiesDataManager.class, name = "getEventFilterForGlobalPropertyDefinitionEvent", args = {}) + public void testGetEventFilterForGlobalPropertyDefinitionEvent() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + /* + * have an observer subscribe to global property definition events + */ + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + EventFilter eventFilter = globalPropertiesDataManager + .getEventFilterForGlobalPropertyDefinitionEvent(); + assertNotNull(eventFilter); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c.getTime(), e.globalPropertyId())); + }); + + })); + + /* + * Have an actor add several new global property definitions at various times. + */ + + PropertyDefinition propertyDefinition = PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(0)// + .build(); + IntStream.range(1, 4).forEach((i) -> { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(i, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + GlobalPropertyId globalPropertyId = new LocalGlobalPropertyId(i); + + GlobalPropertyInitialization globalPropertyInitialization = // + + GlobalPropertyInitialization.builder()// + .setGlobalPropertyId(globalPropertyId)// + .setPropertyDefinition(propertyDefinition)// + .build(); + globalPropertiesDataManager.defineGlobalProperty(globalPropertyInitialization); + expectedObservations.add(new MultiKey(c.getTime(), globalPropertyId)); + + })); + }); + + /* + * have the observer show that the expected and actual observations are equal + */ + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(4, (c) -> { + assertEquals(3, expectedObservations.size()); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = GlobalPropertiesTestPluginFactory.factory(5100286389011347218L, testPluginData); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = GlobalPropertiesDataManager.class, name = "getEventFilterForGlobalPropertyUpdateEvent", args = {}) + public void testGetEventFilterForGlobalPropertyUpdateEvent() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5410948605660305794L); + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + /* + * have an observer subscribe to global property update events + */ + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + EventFilter eventFilter = globalPropertiesDataManager + .getEventFilterForGlobalPropertyUpdateEvent(); + assertNotNull(eventFilter); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c.getTime(), e.globalPropertyId(), e.currentPropertyValue())); + }); + + })); + + /* + * Have an actor update several global property values + */ + IntStream.range(1, 4).forEach((i) -> { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(i, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + + TestGlobalPropertyId testGlobalPropertyId = TestGlobalPropertyId + .getRandomMutableGlobalPropertyId(randomGenerator); + Object propertyValue = testGlobalPropertyId.getRandomPropertyValue(randomGenerator); + globalPropertiesDataManager.setGlobalPropertyValue(testGlobalPropertyId, propertyValue); + + expectedObservations.add(new MultiKey(c.getTime(), testGlobalPropertyId, propertyValue)); + + })); + }); + + /* + * have the observer show that the expected and actual observations are equal + */ + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(4, (c) -> { + assertEquals(3, expectedObservations.size()); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = GlobalPropertiesTestPluginFactory.factory(5100286389011347218L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = GlobalPropertiesDataManager.class, name = "getEventFilterForGlobalPropertyUpdateEvent", args = { + GlobalPropertyId.class }) + public void testGetEventFilterForGlobalPropertyUpdateEvent_property() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2014699212749132531L); + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + /* + * have an observer subscribe to two of the global property update events that + * correspond to mutable properties + */ + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + EventFilter eventFilter = globalPropertiesDataManager + .getEventFilterForGlobalPropertyUpdateEvent(TestGlobalPropertyId.GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE); + assertNotNull(eventFilter); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c.getTime(), e.globalPropertyId(), e.currentPropertyValue())); + }); + + eventFilter = globalPropertiesDataManager + .getEventFilterForGlobalPropertyUpdateEvent(TestGlobalPropertyId.GLOBAL_PROPERTY_2_INTEGER_MUTABLE); + assertNotNull(eventFilter); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c.getTime(), e.globalPropertyId(), e.currentPropertyValue())); + }); + + })); + + /* + * Have an actor update all of the mutable global property values + */ + IntStream.range(1, 4).forEach((i) -> { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(i, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + + TestGlobalPropertyId testGlobalPropertyId = TestGlobalPropertyId.GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE; + Object propertyValue = testGlobalPropertyId.getRandomPropertyValue(randomGenerator); + globalPropertiesDataManager.setGlobalPropertyValue(testGlobalPropertyId, propertyValue); + expectedObservations.add(new MultiKey(c.getTime(), testGlobalPropertyId, propertyValue)); + + testGlobalPropertyId = TestGlobalPropertyId.GLOBAL_PROPERTY_2_INTEGER_MUTABLE; + propertyValue = testGlobalPropertyId.getRandomPropertyValue(randomGenerator); + globalPropertiesDataManager.setGlobalPropertyValue(testGlobalPropertyId, propertyValue); + expectedObservations.add(new MultiKey(c.getTime(), testGlobalPropertyId, propertyValue)); + + testGlobalPropertyId = TestGlobalPropertyId.GLOBAL_PROPERTY_3_DOUBLE_MUTABLE; + propertyValue = testGlobalPropertyId.getRandomPropertyValue(randomGenerator); + globalPropertiesDataManager.setGlobalPropertyValue(testGlobalPropertyId, propertyValue); + // not that we do not add an expected value here + + })); + }); + + /* + * have the observer show that the expected and actual observations are equal + */ + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(4, (c) -> { + assertEquals(6, expectedObservations.size()); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = GlobalPropertiesTestPluginFactory.factory(5100286389011347218L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition test: if the global property id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GlobalPropertiesTestPluginFactory.factory(5100286389011347218L, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + GlobalPropertyId globalPropertyId = null; + globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId, new Object()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // precondition test: if the global property id is not known + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GlobalPropertiesTestPluginFactory.factory(5100286389011347218L, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + GlobalPropertyId globalPropertyId = TestGlobalPropertyId.getUnknownGlobalPropertyId(); + globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId, new Object()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GlobalPropertiesDataManager.class, name = "getGlobalPropertyDefinitionTime", args = { + GlobalPropertyId.class }) + public void testGetGlobalPropertyDefinitionTime() { + Factory factory = GlobalPropertiesTestPluginFactory.factory(5100286389011347218L, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + assertEquals(0.0, globalPropertiesDataManager.getGlobalPropertyDefinitionTime(testGlobalPropertyId)); + } + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition : if the global property id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GlobalPropertiesTestPluginFactory.factory(5100286389011347218L, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + globalPropertiesDataManager.getGlobalPropertyDefinitionTime(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // precondition : if the global property id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GlobalPropertiesTestPluginFactory.factory(5100286389011347218L, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + globalPropertiesDataManager + .getGlobalPropertyDefinitionTime(TestGlobalPropertyId.getUnknownGlobalPropertyId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GlobalPropertiesDataManager.class, name = "toString", args = {}) + public void testToString() { + Map globalPropertyDefinitions = new LinkedHashMap<>(); + Map globalPropertyValues = new LinkedHashMap<>(); + Map globalPropertyDefinitionTimes = new LinkedHashMap<>(); + Map globalPropertyTimes = new LinkedHashMap<>(); + + GlobalPropertiesPluginData.Builder globalsPluginBuilder = GlobalPropertiesPluginData.builder(); + long seed = 5100286389011347218L; + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + PropertyDefinition propertyDefinition = testGlobalPropertyId.getPropertyDefinition(); + globalsPluginBuilder.defineGlobalProperty(testGlobalPropertyId, propertyDefinition, 0); + + globalPropertyDefinitions.put(testGlobalPropertyId, propertyDefinition); + globalPropertyDefinitionTimes.put(testGlobalPropertyId, 0.0); + + if (propertyDefinition.getDefaultValue().isEmpty()) { + Object value = testGlobalPropertyId.getRandomPropertyValue(randomGenerator); + globalsPluginBuilder.setGlobalPropertyValue(testGlobalPropertyId, value, 0); + globalPropertyValues.put(testGlobalPropertyId, value); + globalPropertyTimes.put(testGlobalPropertyId, 0.0); + } + } + // change two of the properties from their default values + globalsPluginBuilder.setGlobalPropertyValue(TestGlobalPropertyId.GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE, true, 0); + globalPropertyValues.put(TestGlobalPropertyId.GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE, true); + globalPropertyTimes.put(TestGlobalPropertyId.GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE, 0.0); + + globalsPluginBuilder.setGlobalPropertyValue(TestGlobalPropertyId.GLOBAL_PROPERTY_2_INTEGER_MUTABLE, 456, 0); + globalPropertyValues.put(TestGlobalPropertyId.GLOBAL_PROPERTY_2_INTEGER_MUTABLE, 456); + globalPropertyTimes.put(TestGlobalPropertyId.GLOBAL_PROPERTY_2_INTEGER_MUTABLE, 0.0); + + GlobalPropertiesPluginData globalPropertiesPluginData = globalsPluginBuilder.build(); + + TestPluginData.Builder testPluginDataBuilder = TestPluginData.builder(); + + testPluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + // show that the data manager exists + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + + StringBuilder builder = new StringBuilder(); + builder.append("GlobalPropertiesDataManager [globalPropertyDefinitions="); + builder.append(globalPropertyDefinitions); + builder.append(", globalPropertyDefinitionTimes="); + builder.append(globalPropertyDefinitionTimes); + builder.append(", globalPropertyValues="); + builder.append(globalPropertyValues); + builder.append(", globalPropertyTimes="); + builder.append(globalPropertyTimes); + builder.append("]"); + + assertEquals(builder.toString(), globalPropertiesDataManager.toString()); + })); + + TestPluginData testPluginData = testPluginDataBuilder.build(); + Factory factory = GlobalPropertiesTestPluginFactory.factory(seed, testPluginData) + .setGlobalPropertiesPluginData(globalPropertiesPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/datamanagers/AT_GlobalPropertiesPluginData.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/datamanagers/AT_GlobalPropertiesPluginData.java new file mode 100644 index 000000000..4e7707405 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/datamanagers/AT_GlobalPropertiesPluginData.java @@ -0,0 +1,958 @@ +package gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.PluginDataBuilder; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.SimpleGlobalPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.testsupport.TestGlobalPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_GlobalPropertiesPluginData { + + @Test + @UnitTestMethod(target = GlobalPropertiesPluginData.Builder.class, name = "build", args = {}) + public void testBuild() { + + GlobalPropertiesPluginData globalInitialData = GlobalPropertiesPluginData.builder().build(); + assertNotNull(globalInitialData); + + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(34) + .build(); + GlobalPropertyId globalPropertyId1 = new SimpleGlobalPropertyId("id 1"); + GlobalPropertyId globalPropertyId2 = new SimpleGlobalPropertyId("id 2"); + + // show that the builder clears its contents on build + GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder();// + builder.defineGlobalProperty(globalPropertyId1, propertyDefinition, 0)// + .build(); + + builder = GlobalPropertiesPluginData.builder();// + GlobalPropertiesPluginData globalPropertiesPluginData = builder.build(); + assertTrue(globalPropertiesPluginData.getGlobalPropertyIds().isEmpty()); + + /* + * precondition test: if a global property value was associated with a global + * property id that was not defined + */ + + ContractException contractException = assertThrows(ContractException.class, () -> { + GlobalPropertiesPluginData.builder()// + .defineGlobalProperty(globalPropertyId1, propertyDefinition, 0)// + .setGlobalPropertyValue(globalPropertyId2, 67, 0)// + .build(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + /* + * precondition test: if a global property value was associated with a global + * property id that is incompatible with the corresponding property definition. + */ + + contractException = assertThrows(ContractException.class, () -> { + GlobalPropertiesPluginData.builder()// + .defineGlobalProperty(globalPropertyId1, propertyDefinition, 0)// + .setGlobalPropertyValue(globalPropertyId1, "bad value", 0)// + .build(); + }); + assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); + + /* + * precondition test: if a global property time is less than the corresponding + * property definition creation time. + */ + + contractException = assertThrows(ContractException.class, () -> { + GlobalPropertiesPluginData.builder()// + .defineGlobalProperty(globalPropertyId1, propertyDefinition, 3.6)// + .setGlobalPropertyValue(globalPropertyId1, 12, 2.2)// + .build(); + }); + assertEquals(PropertyError.INCOMPATIBLE_TIME, contractException.getErrorType()); + + /* + * precondition test: if a global property definition has no default value and + * there is also no corresponding property value assignment. + */ + contractException = assertThrows(ContractException.class, () -> { + GlobalPropertiesPluginData.builder()// + .defineGlobalProperty(globalPropertyId1, + PropertyDefinition.builder().setType(Integer.class).build(), 0)// + .build(); + }); + assertEquals(PropertyError.INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GlobalPropertiesPluginData.Builder.class, name = "defineGlobalProperty", args = { + GlobalPropertyId.class, PropertyDefinition.class, double.class }) + public void testDefineGlobalProperty() { + GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder(); + + // create a container to hold the expected property definitions + Map expectedPropertyDefinitions = new LinkedHashMap<>(); + Map expectedPropertyDefinitionCreationTimes = new LinkedHashMap<>(); + + // define a few global properties + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(34) + .build(); + PropertyDefinition propertyDefinition2 = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(57) + .build(); + GlobalPropertyId globalPropertyId = new SimpleGlobalPropertyId("id 1"); + builder.defineGlobalProperty(globalPropertyId, propertyDefinition2, 2.5); + // replacing data to show that the value persists + builder.defineGlobalProperty(globalPropertyId, propertyDefinition, 0); + // adding duplicate data to show that the value persists + builder.defineGlobalProperty(globalPropertyId, propertyDefinition, 3.4); + expectedPropertyDefinitions.put(globalPropertyId, propertyDefinition); + expectedPropertyDefinitionCreationTimes.put(globalPropertyId, 3.4); + + propertyDefinition = PropertyDefinition.builder().setType(Double.class).setDefaultValue(234.34).build(); + propertyDefinition2 = PropertyDefinition.builder().setType(Double.class).setDefaultValue(795.88).build(); + globalPropertyId = new SimpleGlobalPropertyId("id 2"); + builder.defineGlobalProperty(globalPropertyId, propertyDefinition2, 2.0); + // replacing data to show that the value persists + builder.defineGlobalProperty(globalPropertyId, propertyDefinition, 2.9); + // adding duplicate data to show that the value persists + builder.defineGlobalProperty(globalPropertyId, propertyDefinition, 3.1); + expectedPropertyDefinitions.put(globalPropertyId, propertyDefinition); + expectedPropertyDefinitionCreationTimes.put(globalPropertyId, 3.1); + + propertyDefinition = PropertyDefinition.builder().setType(String.class).setDefaultValue("default value") + .build(); + propertyDefinition2 = PropertyDefinition.builder().setType(String.class).setDefaultValue("second default") + .build(); + globalPropertyId = new SimpleGlobalPropertyId("id 3"); + builder.defineGlobalProperty(globalPropertyId, propertyDefinition2, 0.5); + // replacing data to show that the value persists + builder.defineGlobalProperty(globalPropertyId, propertyDefinition, 6.8); + // adding duplicate data to show that the value persists + builder.defineGlobalProperty(globalPropertyId, propertyDefinition, 2.1); + expectedPropertyDefinitions.put(globalPropertyId, propertyDefinition); + expectedPropertyDefinitionCreationTimes.put(globalPropertyId, 2.1); + + // build the initial data + GlobalPropertiesPluginData globalInitialData = builder.build(); + + // show that the expected property ids are there + Set actualGlobalPropertyIds = globalInitialData.getGlobalPropertyIds(); + Set expectedGlobalPropertyIds = expectedPropertyDefinitions.keySet(); + assertEquals(expectedGlobalPropertyIds, actualGlobalPropertyIds); + + // show that the property definitions are retrieved by their ids + for (GlobalPropertyId gpid : expectedPropertyDefinitions.keySet()) { + PropertyDefinition expectedPropertyDefinition = expectedPropertyDefinitions.get(gpid); + PropertyDefinition actualPropertyDefinition = globalInitialData.getGlobalPropertyDefinition(gpid); + assertEquals(expectedPropertyDefinition, actualPropertyDefinition); + } + + // show that the property creation times are correct + for (GlobalPropertyId gpid : expectedPropertyDefinitionCreationTimes.keySet()) { + Double expectedTime = expectedPropertyDefinitionCreationTimes.get(gpid); + Double actualTime = globalInitialData.getGlobalPropertyDefinitionTime(gpid); + assertEquals(expectedTime, actualTime); + } + + // precondition tests + + // if the global property id is null + PropertyDefinition propDef = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(17).build(); + ContractException contractException = assertThrows(ContractException.class, + () -> builder.defineGlobalProperty(null, propDef, 0)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // if the property definition is null + contractException = assertThrows(ContractException.class, + () -> builder.defineGlobalProperty(new SimpleGlobalPropertyId("id"), null, 0)); + assertEquals(PropertyError.NULL_PROPERTY_DEFINITION, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GlobalPropertiesPluginData.Builder.class, name = "setGlobalPropertyValue", args = { + GlobalPropertyId.class, Object.class, double.class }) + public void testSetGlobalPropertyValue() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(170390875787254562L); + + GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder(); + + // show there are some properties in the support enum + assertTrue(TestGlobalPropertyId.values().length > 0); + + // define some properties + Set expectedGlobalPropertyIds = new LinkedHashSet<>(); + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class) + .setDefaultValue(0).build(); + builder.defineGlobalProperty(testGlobalPropertyId, propertyDefinition, 0); + expectedGlobalPropertyIds.add(testGlobalPropertyId); + } + // create a container for the expected values of the properties + Map expectedValues = new LinkedHashMap<>(); + Map expectedTimes = new LinkedHashMap<>(); + + // set the values + boolean useProperty = true; + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + if (useProperty) { + int value = randomGenerator.nextInt(); + int value2 = randomGenerator.nextInt(); + double time = randomGenerator.nextDouble(); + builder.setGlobalPropertyValue(testGlobalPropertyId, value2, time); + // replacing data to show that the value persists + time = randomGenerator.nextDouble(); + builder.setGlobalPropertyValue(testGlobalPropertyId, value, time); + // duplicating data to show that the value persists + time = randomGenerator.nextDouble(); + builder.setGlobalPropertyValue(testGlobalPropertyId, value, time); + expectedValues.put(testGlobalPropertyId, value); + expectedTimes.put(testGlobalPropertyId, time); + } + useProperty = !useProperty; + } + + // build the initial data + GlobalPropertiesPluginData globalInitialData = builder.build(); + + // show that the expected property ids are there + Set actualGlobalPropertyIds = globalInitialData.getGlobalPropertyIds(); + + assertEquals(expectedGlobalPropertyIds, actualGlobalPropertyIds); + + // show that the expected values are present + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + Object expectedValue = expectedValues.get(testGlobalPropertyId); + Optional optionalValue = globalInitialData.getGlobalPropertyValue(testGlobalPropertyId); + + if (expectedValue == null) { + assertFalse(optionalValue.isPresent()); + } else { + assertTrue(optionalValue.isPresent()); + Object actualValue = optionalValue.get(); + assertEquals(expectedValue, actualValue); + } + } + + // show that the expected times are present + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + Double expectedTime = expectedTimes.get(testGlobalPropertyId); + Optional optionalTime = globalInitialData.getGlobalPropertyTime(testGlobalPropertyId); + + if (expectedTime == null) { + assertFalse(optionalTime.isPresent()); + } else { + assertTrue(optionalTime.isPresent()); + Double actualTime = optionalTime.get(); + assertEquals(expectedTime, actualTime); + } + } + + /* + * precondition tests -- Note that invalid values are not covered here. The + * build() validates the values to see if they are compatible with the + * corresponding definitions. + */ + + // if the global property id is null + ContractException contractException = assertThrows(ContractException.class, + () -> builder.setGlobalPropertyValue(null, 5, 0)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // if the global property value is null + contractException = assertThrows(ContractException.class, + () -> builder.setGlobalPropertyValue(TestGlobalPropertyId.GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE, null, 0)); + assertEquals(PropertyError.NULL_PROPERTY_VALUE, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GlobalPropertiesPluginData.class, name = "builder", args = {}) + public void testBuilder() { + // show that the builder can be created + assertNotNull(GlobalPropertiesPluginData.builder()); + } + + @Test + @UnitTestMethod(target = GlobalPropertiesPluginData.class, name = "getGlobalPropertyDefinition", args = { + GlobalPropertyId.class }) + public void testGetGlobalPropertyDefinition() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5427251266091264753L); + + GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder(); + + // show there are some properties in the support enum + assertTrue(TestGlobalPropertyId.values().length > 0); + + // create a container for the expected values of the properties + Map expectedGlobalPropertyDefinitions = new LinkedHashMap<>(); + Map expectedGlobalPropertyDefinitionTimes = new LinkedHashMap<>(); + + // define some properties + + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class) + .setDefaultValue(0).build(); + double time = randomGenerator.nextDouble(); + builder.defineGlobalProperty(testGlobalPropertyId, propertyDefinition, time); + expectedGlobalPropertyDefinitions.put(testGlobalPropertyId, propertyDefinition); + expectedGlobalPropertyDefinitionTimes.put(testGlobalPropertyId, time); + } + + // show that the expected property definitions are present + GlobalPropertiesPluginData globalInitialData = builder.build(); + + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + PropertyDefinition expectedPropertyDefinition = expectedGlobalPropertyDefinitions.get(testGlobalPropertyId); + PropertyDefinition actualPropertyDefinition = globalInitialData + .getGlobalPropertyDefinition(testGlobalPropertyId); + assertEquals(expectedPropertyDefinition, actualPropertyDefinition); + + Double expetedTime = expectedGlobalPropertyDefinitionTimes.get(testGlobalPropertyId); + Double actualTime = globalInitialData.getGlobalPropertyDefinitionTime(testGlobalPropertyId); + assertEquals(expetedTime, actualTime); + + } + + // precondition tests + ContractException contractException = assertThrows(ContractException.class, + () -> globalInitialData.getGlobalPropertyDefinition(null)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, + () -> globalInitialData.getGlobalPropertyDefinition(TestGlobalPropertyId.getUnknownGlobalPropertyId())); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GlobalPropertiesPluginData.class, name = "getGlobalPropertyDefinitions", args = {}) + public void testGetGlobalPropertyDefinitions() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5427251266091264753L); + + GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder(); + + // show there are some properties in the support enum + assertTrue(TestGlobalPropertyId.values().length > 0); + + // create a container for the expected values of the properties + Map expectedGlobalPropertyDefinitions = new LinkedHashMap<>(); + + // define some properties + + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class) + .setDefaultValue(0).build(); + double time = randomGenerator.nextDouble(); + builder.defineGlobalProperty(testGlobalPropertyId, propertyDefinition, time); + expectedGlobalPropertyDefinitions.put(testGlobalPropertyId, propertyDefinition); + } + + // show that the expected property definitions are present + GlobalPropertiesPluginData globalInitialData = builder.build(); + + assertEquals(expectedGlobalPropertyDefinitions, globalInitialData.getGlobalPropertyDefinitions()); + } + + @Test + @UnitTestMethod(target = GlobalPropertiesPluginData.class, name = "getGlobalPropertyDefinitionTimes", args = {}) + public void testGetGlobalPropertyDefinitionTimes() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5427251266091264753L); + + GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder(); + + // show there are some properties in the support enum + assertTrue(TestGlobalPropertyId.values().length > 0); + + // create a container for the expected values of the properties + Map expectedGlobalPropertyDefinitionTimes = new LinkedHashMap<>(); + + // define some properties + + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class) + .setDefaultValue(0).build(); + double time = randomGenerator.nextDouble(); + builder.defineGlobalProperty(testGlobalPropertyId, propertyDefinition, time); + expectedGlobalPropertyDefinitionTimes.put(testGlobalPropertyId, time); + } + + // show that the expected property definitions are present + GlobalPropertiesPluginData globalInitialData = builder.build(); + + assertEquals(expectedGlobalPropertyDefinitionTimes, globalInitialData.getGlobalPropertyDefinitionTimes()); + } + + @Test + @UnitTestMethod(target = GlobalPropertiesPluginData.class, name = "getGlobalPropertyIds", args = {}) + public void testGetGlobalPropertyIds() { + + GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder(); + + // show there are some properties in the support enum + assertTrue(TestGlobalPropertyId.values().length > 0); + + // create a container for the expected values of the properties + Set expectedGlobalPropertyIds = new LinkedHashSet<>(); + + // define some properties + + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + PropertyDefinition propertyDefinition = PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(0)// + .build(); + builder.defineGlobalProperty(testGlobalPropertyId, propertyDefinition, 0); + expectedGlobalPropertyIds.add(testGlobalPropertyId); + } + + // show that the expected values are present + GlobalPropertiesPluginData globalInitialData = builder.build(); + assertEquals(expectedGlobalPropertyIds, globalInitialData.getGlobalPropertyIds()); + } + + @Test + @UnitTestMethod(target = GlobalPropertiesPluginData.class, name = "getGlobalPropertyValue", args = { + GlobalPropertyId.class }) + public void testGetGlobalPropertyValue() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4250048639082754761L); + + GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder(); + + // show there are some properties in the support enum + assertTrue(TestGlobalPropertyId.values().length > 0); + + // define some properties + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class) + .setDefaultValue(0).build(); + builder.defineGlobalProperty(testGlobalPropertyId, propertyDefinition, 0); + } + // create a container for the expected values of the properties + Map expectedValues = new LinkedHashMap<>(); + + // set about half of the values + boolean shouldSetValue = true; + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + if (shouldSetValue) { + int value = randomGenerator.nextInt(); + builder.setGlobalPropertyValue(testGlobalPropertyId, value, 0); + expectedValues.put(testGlobalPropertyId, value); + } + shouldSetValue = !shouldSetValue; + } + + // show that the expected values are present + GlobalPropertiesPluginData globalPropertiesPluginData = builder.build(); + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + Object expectedValue = expectedValues.get(testGlobalPropertyId); + Optional optionalValue = globalPropertiesPluginData.getGlobalPropertyValue(testGlobalPropertyId); + if (expectedValue == null) { + assertFalse(optionalValue.isPresent()); + } else { + assertTrue(optionalValue.isPresent()); + Object actualValue = optionalValue.get(); + assertEquals(expectedValue, actualValue); + } + } + + /* + * precondition tests -- Note that invalid values are not covered here. The + * build() validates the values to see if they are compatible with the + * corresponding definitions. + */ + + // if the global property id is null + ContractException contractException = assertThrows(ContractException.class, + () -> globalPropertiesPluginData.getGlobalPropertyValue(null)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // if the global property value is null + contractException = assertThrows(ContractException.class, () -> globalPropertiesPluginData + .getGlobalPropertyValue(TestGlobalPropertyId.getUnknownGlobalPropertyId())); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GlobalPropertiesPluginData.class, name = "getGlobalPropertyValues", args = {}) + public void testGetGlobalPropertyValues() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4250048639082754761L); + + GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder(); + + // show there are some properties in the support enum + assertTrue(TestGlobalPropertyId.values().length > 0); + + // define some properties + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class) + .setDefaultValue(0).build(); + builder.defineGlobalProperty(testGlobalPropertyId, propertyDefinition, 0); + } + // create a container for the expected values of the properties + Map expectedValues = new LinkedHashMap<>(); + + // set about half of the values + boolean shouldSetValue = true; + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + if (shouldSetValue) { + int value = randomGenerator.nextInt(); + builder.setGlobalPropertyValue(testGlobalPropertyId, value, 0); + expectedValues.put(testGlobalPropertyId, value); + } + shouldSetValue = !shouldSetValue; + } + + GlobalPropertiesPluginData globalPropertiesPluginData = builder.build(); + + assertEquals(expectedValues, globalPropertiesPluginData.getGlobalPropertyValues()); + } + + @Test + @UnitTestMethod(target = GlobalPropertiesPluginData.class, name = "getCloneBuilder", args = {}) + public void testGetCloneBuilder() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(9113503089361379130L); + GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder(); + + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + double time = randomGenerator.nextDouble(); + builder.defineGlobalProperty(testGlobalPropertyId, testGlobalPropertyId.getPropertyDefinition(), time); + time += randomGenerator.nextDouble(); + builder.setGlobalPropertyValue(testGlobalPropertyId, + testGlobalPropertyId.getRandomPropertyValue(randomGenerator), time); + } + + GlobalPropertiesPluginData globalPropertiesPluginData = builder.build(); + PluginDataBuilder cloneBuilder = globalPropertiesPluginData.getCloneBuilder(); + assertNotNull(cloneBuilder); + PluginData pluginData = cloneBuilder.build(); + assertTrue(pluginData instanceof GlobalPropertiesPluginData); + GlobalPropertiesPluginData cloneGlobalPropertiesPluginData = (GlobalPropertiesPluginData) pluginData; + + assertEquals(globalPropertiesPluginData, cloneGlobalPropertiesPluginData); + + } + + @Test + @UnitTestMethod(target = GlobalPropertiesPluginData.class, name = "getGlobalPropertyDefinitionTime", args = { + GlobalPropertyId.class }) + public void testGetGlobalPropertyDefinitionTime() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5487507072126661304L); + + GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder(); + + // show there are some properties in the support enum + assertTrue(TestGlobalPropertyId.values().length > 0); + + // create a container for the expected values of the properties + Map expectedGlobalPropertyDefinitions = new LinkedHashMap<>(); + Map expectedGlobalPropertyDefinitionTimes = new LinkedHashMap<>(); + + // define some properties + + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class) + .setDefaultValue(0).build(); + double time = randomGenerator.nextDouble(); + builder.defineGlobalProperty(testGlobalPropertyId, propertyDefinition, time); + expectedGlobalPropertyDefinitions.put(testGlobalPropertyId, propertyDefinition); + expectedGlobalPropertyDefinitionTimes.put(testGlobalPropertyId, time); + } + + // show that the expected property definitions are present + GlobalPropertiesPluginData globalInitialData = builder.build(); + + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + PropertyDefinition expectedPropertyDefinition = expectedGlobalPropertyDefinitions.get(testGlobalPropertyId); + PropertyDefinition actualPropertyDefinition = globalInitialData + .getGlobalPropertyDefinition(testGlobalPropertyId); + assertEquals(expectedPropertyDefinition, actualPropertyDefinition); + + Double expetedTime = expectedGlobalPropertyDefinitionTimes.get(testGlobalPropertyId); + Double actualTime = globalInitialData.getGlobalPropertyDefinitionTime(testGlobalPropertyId); + assertEquals(expetedTime, actualTime); + + } + + // precondition tests + ContractException contractException = assertThrows(ContractException.class, + () -> globalInitialData.getGlobalPropertyDefinition(null)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, + () -> globalInitialData.getGlobalPropertyDefinition(TestGlobalPropertyId.getUnknownGlobalPropertyId())); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GlobalPropertiesPluginData.class, name = "getGlobalPropertyTime", args = { + GlobalPropertyId.class }) + public void testGetGlobalPropertyTime() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4250048639082754761L); + + GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder(); + + // show there are some properties in the support enum + assertTrue(TestGlobalPropertyId.values().length > 0); + + // define some properties -- note that we do not set default values to + // test that the values provided explicitly will properly replace the + // default values. + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + PropertyDefinition propertyDefinition = PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(0)// + .build(); + builder.defineGlobalProperty(testGlobalPropertyId, propertyDefinition, 0.0); + } + // create a container for the expected values of the properties + Map expectedTimes = new LinkedHashMap<>(); + + // set the times for half of the properties + int counter = 0; + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + if (counter % 2 == 0) { + int value = randomGenerator.nextInt(); + double time = randomGenerator.nextDouble() + 0.1; + builder.setGlobalPropertyValue(testGlobalPropertyId, value, time); + expectedTimes.put(testGlobalPropertyId, time); + } + counter++; + } + + // show that the expected times are present + GlobalPropertiesPluginData globalPropertiesPluginData = builder.build(); + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + Double expectedTime = expectedTimes.get(testGlobalPropertyId); + Optional optionalTime = globalPropertiesPluginData.getGlobalPropertyTime(testGlobalPropertyId); + if (expectedTime == null) { + assertFalse(optionalTime.isPresent()); + } else { + assertTrue(optionalTime.isPresent()); + Double actualTime = optionalTime.get(); + assertEquals(expectedTime, actualTime); + } + } + + /* + * precondition tests -- Note that invalid values are not covered here. The + * build() validates the values to see if they are compatible with the + * corresponding definitions. + */ + + // if the global property id is null + ContractException contractException = assertThrows(ContractException.class, + () -> globalPropertiesPluginData.getGlobalPropertyValue(null)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // if the global property value is null + contractException = assertThrows(ContractException.class, () -> globalPropertiesPluginData + .getGlobalPropertyValue(TestGlobalPropertyId.getUnknownGlobalPropertyId())); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GlobalPropertiesPluginData.class, name = "getGlobalPropertyTimes", args = {}) + public void testGetGlobalPropertyTimes() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4250048639082754761L); + + GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder(); + + // show there are some properties in the support enum + assertTrue(TestGlobalPropertyId.values().length > 0); + + // define some properties -- note that we do not set default values to + // test that the values provided explicitly will properly replace the + // default values. + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + PropertyDefinition propertyDefinition = PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(0)// + .build(); + builder.defineGlobalProperty(testGlobalPropertyId, propertyDefinition, 0.0); + } + // create a container for the expected values of the properties + Map expectedTimes = new LinkedHashMap<>(); + + // set the times for half of the properties + int counter = 0; + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + if (counter % 2 == 0) { + int value = randomGenerator.nextInt(); + double time = randomGenerator.nextDouble() + 0.1; + builder.setGlobalPropertyValue(testGlobalPropertyId, value, time); + expectedTimes.put(testGlobalPropertyId, time); + } + counter++; + } + + // show that the expected times are present + GlobalPropertiesPluginData globalPropertiesPluginData = builder.build(); + + assertEquals(expectedTimes, globalPropertiesPluginData.getGlobalPropertyTimes()); + } + + @Test + @UnitTestMethod(target = GlobalPropertiesPluginData.class, name = "equals", args = { Object.class }) + public void testEquals() { + // we first set up a few items that will clarify the production of + // plugin datas + + Object v_5 = 5; + Object v_12 = 12; + Object v_25 = 15; + Object v_a = "a"; + Object v_b = "b"; + + GlobalPropertyId p1 = TestGlobalPropertyId.getUnknownGlobalPropertyId(); + GlobalPropertyId p2 = TestGlobalPropertyId.getUnknownGlobalPropertyId(); + + PropertyDefinition d1 = PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(v_5)// + .build(); + + PropertyDefinition d2 = PropertyDefinition.builder()// + .setType(String.class)// + .setDefaultValue(v_a)// + .build(); + + Double t_0 = 0.0; + Double t_1 = 1.0; + Double t_2 = 2.0; + + // just a single definition -- this will act as our base case + GlobalPropertiesPluginData g1 = GlobalPropertiesPluginData.builder()// + .defineGlobalProperty(p1, d1, t_0)// + .build(); + + // set the property value that has the same time and value as the + // property definition + GlobalPropertiesPluginData g2 = GlobalPropertiesPluginData.builder()// + .defineGlobalProperty(p1, d1, t_0)// + .setGlobalPropertyValue(p1, v_5, t_0)// + .build(); + + GlobalPropertiesPluginData g3 = GlobalPropertiesPluginData.builder()// + .setGlobalPropertyValue(p1, v_5, t_0)// + .defineGlobalProperty(p1, d1, t_0)// + .build(); + + // change the value of the property + GlobalPropertiesPluginData g4 = GlobalPropertiesPluginData.builder()// + .defineGlobalProperty(p1, d1, t_0)// + .setGlobalPropertyValue(p1, v_25, t_0)// + .build(); + + // change the time of the property + GlobalPropertiesPluginData g5 = GlobalPropertiesPluginData.builder()// + .defineGlobalProperty(p1, d1, t_0)// + .setGlobalPropertyValue(p1, v_5, t_1)// + .build(); + + // introduce a new property definition + GlobalPropertiesPluginData g6 = GlobalPropertiesPluginData.builder()// + .defineGlobalProperty(p1, d1, t_0)// + .defineGlobalProperty(p2, d2, t_1)// + .build(); + + // add several values and definitions + GlobalPropertiesPluginData g7 = GlobalPropertiesPluginData.builder()// + .defineGlobalProperty(p1, d1, t_0)// + .defineGlobalProperty(p2, d2, t_1)// + .setGlobalPropertyValue(p1, v_25, t_2)// + .setGlobalPropertyValue(p2, v_b, t_2)// + .setGlobalPropertyValue(p1, v_12, t_1)// + .build(); + + // add the same details, but in a different order, preserving the last + // assignments + GlobalPropertiesPluginData g8 = GlobalPropertiesPluginData.builder()// + .setGlobalPropertyValue(p1, v_25, t_2)// + .defineGlobalProperty(p2, d2, t_1)// + .defineGlobalProperty(p1, d1, t_0)// + .setGlobalPropertyValue(p1, v_12, t_1)// + .setGlobalPropertyValue(p2, v_b, t_2)// + .build(); + + // reflexive + assertEquals(g1, g1); + assertEquals(g2, g2); + assertEquals(g4, g4); + assertEquals(g5, g5); + assertEquals(g6, g6); + assertEquals(g7, g7); + assertEquals(g8, g8); + + // symmetric and transitive + + assertEquals(g2, g3); + assertEquals(g3, g2); + + // non-equality from small changes + assertNotEquals(g1, g2); + assertNotEquals(g1, g3); + assertNotEquals(g1, g4); + assertNotEquals(g1, g5); + assertNotEquals(g1, g6); + + // ordering of action should have no effect + assertEquals(g7, g8); + + } + + @Test + @UnitTestMethod(target = GlobalPropertiesPluginData.class, name = "hashCode", args = {}) + public void testHashCode() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2653491508465183354L); + Object v_5 = 5; + Object v_12 = 12; + Object v_25 = 15; + Object v_a = "a"; + Object v_b = "b"; + + GlobalPropertyId p1 = TestGlobalPropertyId.getUnknownGlobalPropertyId(); + GlobalPropertyId p2 = TestGlobalPropertyId.getUnknownGlobalPropertyId(); + + PropertyDefinition d1 = PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(v_5)// + .build(); + + PropertyDefinition d2 = PropertyDefinition.builder()// + .setType(String.class)// + .setDefaultValue(v_a)// + .build(); + + Double t_0 = 0.0; + Double t_1 = 1.0; + Double t_2 = 2.0; + + // equal objects have equal hash codes + // add several values and definitions + GlobalPropertiesPluginData g7 = GlobalPropertiesPluginData.builder()// + .defineGlobalProperty(p1, d1, t_0)// + .defineGlobalProperty(p2, d2, t_1)// + .setGlobalPropertyValue(p1, v_25, t_2).setGlobalPropertyValue(p2, v_b, t_2) + .setGlobalPropertyValue(p1, v_12, t_1).build(); + + // add the same details, but in a different order, preserving the last + // assignments + GlobalPropertiesPluginData g8 = GlobalPropertiesPluginData.builder()// + .setGlobalPropertyValue(p1, v_25, t_2).defineGlobalProperty(p2, d2, t_1)// + .defineGlobalProperty(p1, d1, t_0)// + .setGlobalPropertyValue(p1, v_12, t_1).setGlobalPropertyValue(p2, v_b, t_2).build(); + + assertEquals(g7, g8); + assertEquals(g7.hashCode(), g8.hashCode()); + + // hash codes are reasonably distributed + + Set hashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder(); + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + if (randomGenerator.nextBoolean()) { + double time = randomGenerator.nextDouble(); + builder.defineGlobalProperty(testGlobalPropertyId, testGlobalPropertyId.getPropertyDefinition(), + time); + + if (randomGenerator.nextBoolean() + || testGlobalPropertyId.getPropertyDefinition().getDefaultValue().isEmpty()) { + time += 0.1; + builder.setGlobalPropertyValue(testGlobalPropertyId, + testGlobalPropertyId.getRandomPropertyValue(randomGenerator), time); + } + } + } + GlobalPropertiesPluginData globalPropertiesPluginData = builder.build(); + hashCodes.add(globalPropertiesPluginData.hashCode()); + } + assertTrue(hashCodes.size() > 90); + } + + @Test + @UnitTestMethod(target = GlobalPropertiesPluginData.class, name = "toString", args = {}) + public void testToString() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8337912786649642023L); + + /* + * Demonstrate a typical example with a full string. We will add all of the + * standard test definitions in the usual order, but will only add a few of the + * property values in reverse order. Note that we will cover the #3 member which + * does not have a corresponding default value. + */ + GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder(); + + Map definitionTimes = new LinkedHashMap<>(); + + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + double time = randomGenerator.nextInt(1000); + definitionTimes.put(testGlobalPropertyId, time); + builder.defineGlobalProperty(testGlobalPropertyId, testGlobalPropertyId.getPropertyDefinition(), time); + } + + List propertiesForValueSetting = new ArrayList<>(); + propertiesForValueSetting.add(TestGlobalPropertyId.GLOBAL_PROPERTY_6_DOUBLE_IMMUTABLE); + propertiesForValueSetting.add(TestGlobalPropertyId.GLOBAL_PROPERTY_3_DOUBLE_MUTABLE); + propertiesForValueSetting.add(TestGlobalPropertyId.GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE); + + for (TestGlobalPropertyId testGlobalPropertyId : propertiesForValueSetting) { + double time = definitionTimes.get(testGlobalPropertyId) + randomGenerator.nextInt(100); + builder.setGlobalPropertyValue(testGlobalPropertyId, + testGlobalPropertyId.getRandomPropertyValue(randomGenerator), time); + } + + GlobalPropertiesPluginData globalPropertiesPluginData = builder.build(); + + String expectedValue = "GlobalPropertiesPluginData [data=Data [globalPropertyDefinitions" + + "={GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE=PropertyDefinition [type=class java.lang.Boolean" + + ", propertyValuesAreMutable=true, defaultValue=false], GLOBAL_PROPERTY_2_INTEGER_MUTABLE" + + "=PropertyDefinition [type=class java.lang.Integer, propertyValuesAreMutable=true, " + + "defaultValue=0], GLOBAL_PROPERTY_3_DOUBLE_MUTABLE=PropertyDefinition [type=class " + + "java.lang.Double, propertyValuesAreMutable=true, defaultValue=null], " + + "GLOBAL_PROPERTY_4_BOOLEAN_IMMUTABLE=PropertyDefinition [type=class java.lang.Boolean, " + + "propertyValuesAreMutable=false, defaultValue=false], GLOBAL_PROPERTY_5_INTEGER_IMMUTABLE=" + + "PropertyDefinition [type=class java.lang.Integer, propertyValuesAreMutable=false" + + ", defaultValue=0], GLOBAL_PROPERTY_6_DOUBLE_IMMUTABLE=PropertyDefinition [type=class " + + "java.lang.Double, propertyValuesAreMutable=false, defaultValue=0.0]}, " + + "globalPropertyDefinitionTimes={GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE=852.0, " + + "GLOBAL_PROPERTY_2_INTEGER_MUTABLE=835.0, GLOBAL_PROPERTY_3_DOUBLE_MUTABLE=156.0, " + + "GLOBAL_PROPERTY_4_BOOLEAN_IMMUTABLE=505.0, GLOBAL_PROPERTY_5_INTEGER_IMMUTABLE=956.0, " + + "GLOBAL_PROPERTY_6_DOUBLE_IMMUTABLE=191.0}, globalPropertyValues={" + + "GLOBAL_PROPERTY_6_DOUBLE_IMMUTABLE=0.09917206486092223, GLOBAL_PROPERTY_3_DOUBLE_MUTABLE=" + + "0.07709107250291058, GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE=false}, globalPropertyTimes={" + + "GLOBAL_PROPERTY_6_DOUBLE_IMMUTABLE=255.0, GLOBAL_PROPERTY_3_DOUBLE_MUTABLE=168.0, " + + "GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE=877.0}, locked=true]]"; + + String actualValue = globalPropertiesPluginData.toString(); + + assertEquals(expectedValue, actualValue); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/events/AT_GlobalPropertyDefinitionEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/events/AT_GlobalPropertyDefinitionEvent.java new file mode 100644 index 000000000..aeadc2dec --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/events/AT_GlobalPropertyDefinitionEvent.java @@ -0,0 +1,61 @@ +package gov.hhs.aspr.ms.gcm.plugins.globalproperties.events; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.SimpleGlobalPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + +public class AT_GlobalPropertyDefinitionEvent { + + @Test + @UnitTestConstructor(target = GlobalPropertyDefinitionEvent.class, args = { GlobalPropertyId.class, Object.class }) + public void testConstructor() { + + ContractException contractException = assertThrows(ContractException.class, () -> new GlobalPropertyDefinitionEvent(null, 7)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + SimpleGlobalPropertyId goodId = new SimpleGlobalPropertyId(5); + + ContractException contractException2 = assertThrows(ContractException.class, () -> new GlobalPropertyDefinitionEvent(goodId, null)); + assertEquals(PropertyError.NULL_PROPERTY_VALUE, contractException2.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GlobalPropertyDefinitionEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GlobalPropertyDefinitionEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GlobalPropertyDefinitionEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GlobalPropertyDefinitionEvent.class, name = "globalPropertyId", args = {}) + public void testGlobalPropertyId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GlobalPropertyDefinitionEvent.class, name = "initialPropertyValue", args = {}) + public void testInitialPropertyValue() { + // nothing to test + } + +} \ No newline at end of file diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/events/AT_GlobalPropertyUpdateEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/events/AT_GlobalPropertyUpdateEvent.java new file mode 100644 index 000000000..a5cb73409 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/events/AT_GlobalPropertyUpdateEvent.java @@ -0,0 +1,55 @@ +package gov.hhs.aspr.ms.gcm.plugins.globalproperties.events; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertyId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_GlobalPropertyUpdateEvent { + + @Test + @UnitTestConstructor(target = GlobalPropertyUpdateEvent.class, args = { GlobalPropertyId.class, Object.class, Object.class }) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GlobalPropertyUpdateEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GlobalPropertyUpdateEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GlobalPropertyUpdateEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GlobalPropertyUpdateEvent.class, name = "globalPropertyId", args = {}) + public void testGlobalPropertyId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GlobalPropertyUpdateEvent.class, name = "previousPropertyValue", args = {}) + public void testPreviousPropertyValue() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GlobalPropertyUpdateEvent.class, name = "currentPropertyValue", args = {}) + public void testCurrentPropertyValue() { + // nothing to test + } + + + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/reports/AT_GlobalPropertyReport.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/reports/AT_GlobalPropertyReport.java new file mode 100644 index 000000000..860745527 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/reports/AT_GlobalPropertyReport.java @@ -0,0 +1,637 @@ +package gov.hhs.aspr.ms.gcm.plugins.globalproperties.reports; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.nucleus.Simulation; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestOutputConsumer; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.GlobalPropertiesPlugin; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertiesError; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertyInitialization; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.SimpleGlobalPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.testsupport.GlobalPropertiesTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.testsupport.GlobalPropertiesTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.testsupport.TestGlobalPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + +public class AT_GlobalPropertyReport { + + @Test + @UnitTestConstructor(target = GlobalPropertyReport.class, args = { GlobalPropertyReportPluginData.class }, tags = {}) + public void testConstructor() { + // construction is covered by the other tests + + // precondition test: if the GlobalPropertyReportPluginData is null + ContractException contractException = assertThrows(ContractException.class, () -> new GlobalPropertyReport(null)); + assertEquals(GlobalPropertiesError.NULL_GLOBAL_PROPERTY_REPORT_PLUGIN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GlobalPropertyReport.class, name = "init", args = { ReportContext.class }) + public void testInit_Content() { + + /* + * We will add one actor and the global property report to the engine. + * We will define a few global properties and the actor will alter + * various global properties over time. Report items from the report + * will be collected in an output consumer. The expected report items + * will be collected in a separate consumer and the consumers will be + * compared for equality. + */ + + GlobalPropertyReportPluginData globalPropertyReportPluginData = GlobalPropertyReportPluginData .builder()// + .setReportLabel(REPORT_LABEL)// + .setDefaultInclusion(true)// + .build(); + + // add the global property definitions + + GlobalPropertiesPluginData.Builder initialDatabuilder = GlobalPropertiesPluginData.builder(); + + GlobalPropertyId globalPropertyId_1 = new SimpleGlobalPropertyId("id_1"); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(3).build(); + initialDatabuilder.defineGlobalProperty(globalPropertyId_1, propertyDefinition,0); + + GlobalPropertyId globalPropertyId_2 = new SimpleGlobalPropertyId("id_2"); + propertyDefinition = PropertyDefinition.builder().setType(Double.class).setDefaultValue(6.78).build(); + initialDatabuilder.defineGlobalProperty(globalPropertyId_2, propertyDefinition,0); + + GlobalPropertyId globalPropertyId_3 = new SimpleGlobalPropertyId("id_3"); + propertyDefinition = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(true).build(); + initialDatabuilder.defineGlobalProperty(globalPropertyId_3, propertyDefinition,0); + + GlobalPropertiesPluginData globalPropertiesPluginData = initialDatabuilder.build(); + + /* + * Define two more properties that are not included in the plugin data + * and will be added by an actor + */ + GlobalPropertyId globalPropertyId_4 = new SimpleGlobalPropertyId("id_4"); + PropertyDefinition propertyDefinition_4 = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(true).build(); + + GlobalPropertyId globalPropertyId_5 = new SimpleGlobalPropertyId("id_5"); + PropertyDefinition propertyDefinition_5 = PropertyDefinition.builder().setType(Double.class).setDefaultValue(199.16).build(); + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create an agent and have it assign various global properties at + // various times + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0.0, (c) -> { + /* + * note that this is time 0 and should show that property initial + * values are still reported correctly + */ + GlobalPropertiesDataManager globalPropertiesDataManager = c.getDataManager(GlobalPropertiesDataManager.class); + globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId_1, 67); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1.0, (c) -> { + // two settings of the same property + GlobalPropertiesDataManager globalPropertiesDataManager = c.getDataManager(GlobalPropertiesDataManager.class); + globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId_2, 88.88); + globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId_3, false); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2.0, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c.getDataManager(GlobalPropertiesDataManager.class); + globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId_1, 100); + globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId_2, 3.45); + globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId_3, true); + GlobalPropertyInitialization globalPropertyInitialization = GlobalPropertyInitialization.builder().setGlobalPropertyId(globalPropertyId_4).setPropertyDefinition(propertyDefinition_4) + .build(); + globalPropertiesDataManager.defineGlobalProperty(globalPropertyInitialization); + + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(3.0, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c.getDataManager(GlobalPropertiesDataManager.class); + + globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId_3, false); + // note the duplicated value + globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId_2, 99.7); + globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId_2, 99.7); + // and now a third setting of the same property to a new value + globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId_2, 100.0); + globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId_3, true); + GlobalPropertyInitialization globalPropertyInitialization = GlobalPropertyInitialization.builder().setGlobalPropertyId(globalPropertyId_5).setPropertyDefinition(propertyDefinition_5) + .build(); + globalPropertiesDataManager.defineGlobalProperty(globalPropertyInitialization); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + + /* + * Collect the expected report items. Note that order does not matter. * + */ + Map expectedReportItems = new LinkedHashMap<>(); + + expectedReportItems.put(getReportItem(0.0, globalPropertyId_1, 3), 1); + expectedReportItems.put(getReportItem(0.0, globalPropertyId_2, 6.78), 1); + expectedReportItems.put(getReportItem(0.0, globalPropertyId_3, true), 1); + expectedReportItems.put(getReportItem(0.0, globalPropertyId_1, 67), 1); + expectedReportItems.put(getReportItem(1.0, globalPropertyId_2, 88.88), 1); + expectedReportItems.put(getReportItem(1.0, globalPropertyId_3, false), 1); + expectedReportItems.put(getReportItem(2.0, globalPropertyId_1, 100), 1); + expectedReportItems.put(getReportItem(2.0, globalPropertyId_2, 3.45), 1); + expectedReportItems.put(getReportItem(2.0, globalPropertyId_3, true), 1); + expectedReportItems.put(getReportItem(2.0, globalPropertyId_4, true), 1); + expectedReportItems.put(getReportItem(3.0, globalPropertyId_3, false), 1); + expectedReportItems.put(getReportItem(3.0, globalPropertyId_2, 99.7), 2); + expectedReportItems.put(getReportItem(3.0, globalPropertyId_2, 100.0), 1); + expectedReportItems.put(getReportItem(3.0, globalPropertyId_3, true), 1); + expectedReportItems.put(getReportItem(3.0, globalPropertyId_5, 199.16), 1); + + Factory factory = GlobalPropertiesTestPluginFactory .factory(5359348175134903328L, testPluginData)// + .setGlobalPropertiesPluginData(globalPropertiesPluginData)// + .setGlobalPropertyReportPluginData(globalPropertyReportPluginData);// + + TestOutputConsumer outputConsumer = TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + assertEquals(expectedReportItems, outputConsumer.getOutputItemMap(ReportItem.class)); + + } + + private static ReportItem getReportItem(Object... values) { + ReportItem.Builder builder = ReportItem.builder(); + builder.setReportLabel(REPORT_LABEL); + builder.setReportHeader(REPORT_HEADER); + for (Object value : values) { + builder.addValue(value); + } + return builder.build(); + } + + @Test + @UnitTestMethod(target = GlobalPropertyReport.class, name = "init", args = { ReportContext.class }) + public void testInit_IncludeProperty() { + /* + * This test shows that the report produces report items with the + * correct selected properties as a function of the explicitly included + * properties + */ + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // create a test actor plan where we set several global property values + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + + GlobalPropertiesDataManager globalPropertiesDataManager = c.getDataManager(GlobalPropertiesDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + if (testGlobalPropertyId.getPropertyDefinition().propertyValuesAreMutable()) { + Object globalPropertyValue = testGlobalPropertyId.getRandomPropertyValue(randomGenerator); + globalPropertiesDataManager.setGlobalPropertyValue(testGlobalPropertyId, globalPropertyValue); + } + } + + })); + + GlobalPropertyId unknownGlobalPropertyId = TestGlobalPropertyId.getUnknownGlobalPropertyId(); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c.getDataManager(GlobalPropertiesDataManager.class); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(1).build(); + + GlobalPropertyInitialization globalPropertyInitialization = GlobalPropertyInitialization// + .builder()// + .setGlobalPropertyId(unknownGlobalPropertyId)// + .setPropertyDefinition(propertyDefinition).build(); + globalPropertiesDataManager.defineGlobalProperty(globalPropertyInitialization); + + globalPropertiesDataManager.setGlobalPropertyValue(unknownGlobalPropertyId, 2); + })); + + ReportLabel reportLabel = new SimpleReportLabel("report label"); + TestGlobalPropertyId testGlobalPropertyId = TestGlobalPropertyId.GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE; + + GlobalPropertyReportPluginData.Builder builder = GlobalPropertyReportPluginData.builder(); + builder.setReportLabel(reportLabel); + builder.setDefaultInclusion(false); + builder.includeGlobalProperty(testGlobalPropertyId); + builder.includeGlobalProperty(unknownGlobalPropertyId); + GlobalPropertyReportPluginData globalPropertyReportPluginData = builder.build(); + + TestPluginData testPluginData = pluginDataBuilder.build(); + GlobalPropertiesTestPluginFactory.Factory factory = // + GlobalPropertiesTestPluginFactory// + .factory(7732209566614544073L, testPluginData)// + .setGlobalPropertyReportPluginData(globalPropertyReportPluginData); + + WellState wellState = WellState.builder().setSeed(4059891083116386869L).build(); + StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder().setMainRNGState(wellState).build(); + Plugin stochasticsPlugin = StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData); + + // tell the builder to include a specific global property id + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .addPlugin(stochasticsPlugin)// + .build()// + .execute(); + + // show that our report items include the chosen property id + Map outputItems = testOutputConsumer.getOutputItemMap(ReportItem.class); + assertFalse(outputItems.isEmpty()); + + Set outputPropertyStrings = new LinkedHashSet<>(); + for (ReportItem reportItem : outputItems.keySet()) { + outputPropertyStrings.add(reportItem.getValue(1)); + } + assertTrue(outputPropertyStrings.contains(testGlobalPropertyId.toString())); + assertTrue(outputPropertyStrings.contains(unknownGlobalPropertyId.toString())); + } + + @Test + @UnitTestMethod(target = GlobalPropertyReport.class, name = "init", args = { ReportContext.class }) + public void testInit_DefaultInclusion() { + + // group the properties into explicitly included, explicitly excluded, + // and those that are not specified + GlobalPropertyId includedPropertyId = TestGlobalPropertyId.GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE; + GlobalPropertyId excludedPropertyId = TestGlobalPropertyId.GLOBAL_PROPERTY_6_DOUBLE_IMMUTABLE; + Set middlePropertyIds = new LinkedHashSet<>(); + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + middlePropertyIds.add(testGlobalPropertyId); + } + middlePropertyIds.remove(includedPropertyId); + middlePropertyIds.remove(excludedPropertyId); + + // create an enum to represent setting the default inclusion policy + enum DefaultInclusionPolicy { + TRUE, FALSE, UNSPECIFIED + } + ; + + // loop over the three policies + for (DefaultInclusionPolicy defaultInclusionPolicy : DefaultInclusionPolicy.values()) { + + // build the report plugin data + GlobalPropertyReportPluginData.Builder reportBuilder = GlobalPropertyReportPluginData.builder(); + reportBuilder.setReportLabel(new SimpleReportLabel("report label")); + switch (defaultInclusionPolicy) { + case FALSE: + reportBuilder.setDefaultInclusion(false); + break; + case TRUE: + reportBuilder.setDefaultInclusion(true); + break; + default: + // do nothing + } + + reportBuilder.includeGlobalProperty(includedPropertyId); + reportBuilder.excludeGlobalProperty(excludedPropertyId); + GlobalPropertyReportPluginData globalPropertyReportPluginData = reportBuilder.build(); + + // build the global plugin using the report plugin data and the + // standard global plugin data build + Plugin globalPropertiesPlugin = GlobalPropertiesPlugin .builder()// + .setGlobalPropertiesPluginData(GlobalPropertiesTestPluginFactory.getStandardGlobalPropertiesPluginData(2724159492705609113L))// + .setGlobalPropertyReportPluginData(globalPropertyReportPluginData)// + .getGlobalPropertiesPlugin();// + + // create an output consumer to gather the report items + TestOutputConsumer testOutputConsumer = new TestOutputConsumer(); + + // execute the simulation + Simulation .builder()// + .addPlugin(globalPropertiesPlugin)// + .setOutputConsumer(testOutputConsumer).build()// + .execute();// + + // gather from the report items the property ids that were actually + // included in the report + Set actualPropertyIds = new LinkedHashSet<>(); + Map outputItems = testOutputConsumer.getOutputItemMap(ReportItem.class); + for (ReportItem reportItem : outputItems.keySet()) { + Integer count = outputItems.get(reportItem); + assertEquals(1, count); + TestGlobalPropertyId testGlobalPropertyId = TestGlobalPropertyId.valueOf(reportItem.getValue(1)); + actualPropertyIds.add(testGlobalPropertyId); + } + + // build the expected property ids based on the policy + Set expectedPropertyIds = new LinkedHashSet<>(); + expectedPropertyIds.add(includedPropertyId); + + switch (defaultInclusionPolicy) { + case FALSE: + // only the single included property + break; + default: + expectedPropertyIds.addAll(middlePropertyIds); + break; + } + + // show that the property id sets are equals + assertEquals(expectedPropertyIds, actualPropertyIds); + + } + + } + + @Test + @UnitTestMethod(target = GlobalPropertyReport.class, name = "init", args = { ReportContext.class }) + public void testInit_ReportHeader() { + /* + * This test shows that the report produces report items with the + * correct header + */ + + GlobalPropertyReportPluginData globalPropertyReportPluginData = // + GlobalPropertyReportPluginData .builder()// + .setReportLabel(new SimpleReportLabel("report label"))// + .build(); + + Plugin globalPropertiesPlugin = GlobalPropertiesPlugin .builder()// + .setGlobalPropertiesPluginData(GlobalPropertiesTestPluginFactory.getStandardGlobalPropertiesPluginData(3725147417442242772L)) + .setGlobalPropertyReportPluginData(globalPropertyReportPluginData).getGlobalPropertiesPlugin(); + + TestOutputConsumer testOutputConsumer = new TestOutputConsumer(); + + Simulation .builder()// + .setOutputConsumer(testOutputConsumer)// + .addPlugin(globalPropertiesPlugin)// + .build()// + .execute(); + + // show that the report labels are what we expect for each report item + Map outputItems = testOutputConsumer.getOutputItemMap(ReportItem.class); + assertFalse(outputItems.isEmpty()); + + for (ReportItem reportItem : outputItems.keySet()) { + assertEquals(REPORT_HEADER, reportItem.getReportHeader()); + } + } + + @Test + @UnitTestMethod(target = GlobalPropertyReport.class, name = "init", args = { ReportContext.class }) + public void testInit_ExcludeProperty() { + /* + * This test shows that the report produces report items with the + * correct selected properties as a function of the explicitly included + * properties + */ + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // create a test actor plan where we set several global property values + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + + GlobalPropertiesDataManager globalPropertiesDataManager = c.getDataManager(GlobalPropertiesDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + if (testGlobalPropertyId.getPropertyDefinition().propertyValuesAreMutable()) { + Object globalPropertyValue = testGlobalPropertyId.getRandomPropertyValue(randomGenerator); + globalPropertiesDataManager.setGlobalPropertyValue(testGlobalPropertyId, globalPropertyValue); + } + } + + })); + + GlobalPropertyId unknownGlobalPropertyId = TestGlobalPropertyId.getUnknownGlobalPropertyId(); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c.getDataManager(GlobalPropertiesDataManager.class); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(1).build(); + + GlobalPropertyInitialization globalPropertyInitialization = GlobalPropertyInitialization// + .builder()// + .setGlobalPropertyId(unknownGlobalPropertyId)// + .setPropertyDefinition(propertyDefinition).build(); + globalPropertiesDataManager.defineGlobalProperty(globalPropertyInitialization); + + globalPropertiesDataManager.setGlobalPropertyValue(unknownGlobalPropertyId, 2); + })); + + ReportLabel reportLabel = new SimpleReportLabel("report label"); + TestGlobalPropertyId testGlobalPropertyId = TestGlobalPropertyId.GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE; + + GlobalPropertyReportPluginData.Builder builder = GlobalPropertyReportPluginData.builder(); + builder.setReportLabel(reportLabel); + builder.setDefaultInclusion(true); + builder.excludeGlobalProperty(testGlobalPropertyId); + builder.excludeGlobalProperty(unknownGlobalPropertyId); + GlobalPropertyReportPluginData globalPropertyReportPluginData = builder.build(); + + TestPluginData testPluginData = pluginDataBuilder.build(); + GlobalPropertiesTestPluginFactory.Factory factory = // + GlobalPropertiesTestPluginFactory// + .factory(5412964944403149023L, testPluginData)// + .setGlobalPropertyReportPluginData(globalPropertyReportPluginData); + + WellState wellState = WellState.builder().setSeed(4059891083116386869L).build(); + StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder().setMainRNGState(wellState).build(); + Plugin stochasticsPlugin = StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData); + + // tell the builder to include a specific global property id + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .addPlugin(stochasticsPlugin)// + .build()// + .execute(); + + // show that our report items exclude the chosen property id + Map outputItems = testOutputConsumer.getOutputItemMap(ReportItem.class); + assertFalse(outputItems.isEmpty()); + + Set outputPropertyStrings = new LinkedHashSet<>(); + for (ReportItem reportItem : outputItems.keySet()) { + outputPropertyStrings.add(reportItem.getValue(1)); + } + assertFalse(outputPropertyStrings.contains(testGlobalPropertyId.toString())); + assertFalse(outputPropertyStrings.contains(unknownGlobalPropertyId.toString())); + + } + + @Test + @UnitTestMethod(target = GlobalPropertyReport.class, name = "init", args = { ReportContext.class }) + public void testInit_ReportLabel() { + /* + * This test shows that the report produces report items with the + * correct header + */ + + ReportLabel reportLabel = new SimpleReportLabel("report label"); + + GlobalPropertyReportPluginData globalPropertyReportPluginData = // + GlobalPropertyReportPluginData .builder()// + .setReportLabel(reportLabel)// + .build(); + + Plugin globalPropertiesPlugin = GlobalPropertiesPlugin .builder()// + .setGlobalPropertiesPluginData(GlobalPropertiesTestPluginFactory.getStandardGlobalPropertiesPluginData(6242589534933962341L)) + .setGlobalPropertyReportPluginData(globalPropertyReportPluginData).getGlobalPropertiesPlugin(); + + TestOutputConsumer testOutputConsumer = new TestOutputConsumer(); + + Simulation .builder()// + .setOutputConsumer(testOutputConsumer)// + .addPlugin(globalPropertiesPlugin)// + .build()// + .execute(); + + // show that the report labels are what we expect for each report item + Map outputItems = testOutputConsumer.getOutputItemMap(ReportItem.class); + assertFalse(outputItems.isEmpty()); + + for (ReportItem reportItem : outputItems.keySet()) { + assertEquals(reportLabel, reportItem.getReportLabel()); + } + } + + @Test + @UnitTestMethod(target = GlobalPropertyReport.class, name = "init", args = { ReportContext.class }) + public void testStateFinalization() { + + /* + * We will add one actor and the global property report to the engine. + * We will define a few global properties and the actor will alter + * various global properties over time. Report items from the report + * will be collected in an output consumer. The expected report items + * will be collected in a separate consumer and the consumers will be + * compared for equality. + */ + + GlobalPropertyReportPluginData globalPropertyReportPluginData = GlobalPropertyReportPluginData .builder()// + .setReportLabel(REPORT_LABEL)// + .setDefaultInclusion(true)// + .build(); + + // add the global property definitions + + GlobalPropertiesPluginData.Builder initialDatabuilder = GlobalPropertiesPluginData.builder(); + + GlobalPropertyId globalPropertyId_1 = new SimpleGlobalPropertyId("id_1"); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(3).build(); + initialDatabuilder.defineGlobalProperty(globalPropertyId_1, propertyDefinition,0); + + GlobalPropertyId globalPropertyId_2 = new SimpleGlobalPropertyId("id_2"); + propertyDefinition = PropertyDefinition.builder().setType(Double.class).setDefaultValue(6.78).build(); + initialDatabuilder.defineGlobalProperty(globalPropertyId_2, propertyDefinition,0); + + GlobalPropertyId globalPropertyId_3 = new SimpleGlobalPropertyId("id_3"); + propertyDefinition = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(true).build(); + initialDatabuilder.defineGlobalProperty(globalPropertyId_3, propertyDefinition,0); + + GlobalPropertiesPluginData globalPropertiesPluginData = initialDatabuilder.build(); + + /* + * Define two more properties that are not included in the plugin data + * and will be added by an actor + */ + GlobalPropertyId globalPropertyId_4 = new SimpleGlobalPropertyId("id_4"); + PropertyDefinition propertyDefinition_4 = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(true).build(); + + GlobalPropertyId globalPropertyId_5 = new SimpleGlobalPropertyId("id_5"); + PropertyDefinition propertyDefinition_5 = PropertyDefinition.builder().setType(Double.class).setDefaultValue(199.16).build(); + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create an agent and have it assign various global properties at + // various times + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0.0, (c) -> { + /* + * note that this is time 0 and should show that property initial + * values are still reported correctly + */ + GlobalPropertiesDataManager globalPropertiesDataManager = c.getDataManager(GlobalPropertiesDataManager.class); + globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId_1, 67); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1.0, (c) -> { + // two settings of the same property + GlobalPropertiesDataManager globalPropertiesDataManager = c.getDataManager(GlobalPropertiesDataManager.class); + globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId_2, 88.88); + globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId_3, false); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2.0, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c.getDataManager(GlobalPropertiesDataManager.class); + globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId_1, 100); + globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId_2, 3.45); + globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId_3, true); + GlobalPropertyInitialization globalPropertyInitialization = GlobalPropertyInitialization.builder().setGlobalPropertyId(globalPropertyId_4).setPropertyDefinition(propertyDefinition_4) + .build(); + globalPropertiesDataManager.defineGlobalProperty(globalPropertyInitialization); + + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(3.0, (c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c.getDataManager(GlobalPropertiesDataManager.class); + + globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId_3, false); + // note the duplicated value + globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId_2, 99.7); + globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId_2, 99.7); + // and now a third setting of the same property to a new value + globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId_2, 100.0); + globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId_3, true); + GlobalPropertyInitialization globalPropertyInitialization = GlobalPropertyInitialization.builder().setGlobalPropertyId(globalPropertyId_5).setPropertyDefinition(propertyDefinition_5) + .build(); + globalPropertiesDataManager.defineGlobalProperty(globalPropertyInitialization); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + + /* + * Collect the expected report items. Note that order does not matter. * + */ + + Factory factory = GlobalPropertiesTestPluginFactory .factory(7510926938098915111L, testPluginData)// + .setGlobalPropertiesPluginData(globalPropertiesPluginData)// + .setGlobalPropertyReportPluginData(globalPropertyReportPluginData);// + + TestOutputConsumer outputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .setProduceSimulationStateOnHalt(true)// + .setSimulationHaltTime(20)// + .build()// + .execute(); + + // show that the GlobalPropertyReportPluginData produced by the + // simulation is equal to the one used to form the report + Map outputItems = outputConsumer.getOutputItemMap(GlobalPropertyReportPluginData.class); + assertEquals(1,outputItems.size()); + GlobalPropertyReportPluginData globalPropertyReportPluginData2 = outputItems.keySet().iterator().next(); + assertEquals(globalPropertyReportPluginData, globalPropertyReportPluginData2); + } + + private static final ReportLabel REPORT_LABEL = new SimpleReportLabel("global property report"); + + private static final ReportHeader REPORT_HEADER = ReportHeader.builder().add("time").add("property").add("value").build(); +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/reports/AT_GlobalPropertyReportPluginData.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/reports/AT_GlobalPropertyReportPluginData.java new file mode 100644 index 000000000..d7a6ea7df --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/reports/AT_GlobalPropertyReportPluginData.java @@ -0,0 +1,570 @@ +package gov.hhs.aspr.ms.gcm.plugins.globalproperties.reports; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.testsupport.TestGlobalPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportError; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_GlobalPropertyReportPluginData { + + @Test + @UnitTestMethod(target = GlobalPropertyReportPluginData.class, name = "builder", args = {}) + public void testBuilder() { + GlobalPropertyReportPluginData.Builder builder = GlobalPropertyReportPluginData.builder(); + assertNotNull(builder); + } + + @Test + @UnitTestMethod(target = GlobalPropertyReportPluginData.Builder.class, name = "build", args = {}) + public void testBuild() { + // the specific capabilities are covered elsewhere + + // precondition test: if the report period is not assigned + ContractException contractException = assertThrows(ContractException.class, () -> // + GlobalPropertyReportPluginData.builder()// + .build()); + assertEquals(ReportError.NULL_REPORT_LABEL, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GlobalPropertyReportPluginData.Builder.class, name = "setReportLabel", args = { + ReportLabel.class }) + public void testSetReportLabel() { + + for (int i = 0; i < 30; i++) { + ReportLabel expectedReportLabel = new SimpleReportLabel(i); + GlobalPropertyReportPluginData personPropertyReportPluginData = // + GlobalPropertyReportPluginData.builder()// + .setReportLabel(expectedReportLabel)// + .build(); + + assertEquals(expectedReportLabel, personPropertyReportPluginData.getReportLabel()); + } + + // precondition: if the report label is null + ContractException contractException = assertThrows(ContractException.class, () -> { + GlobalPropertyReportPluginData.builder().setReportLabel(null); + }); + assertEquals(ReportError.NULL_REPORT_LABEL, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GlobalPropertyReportPluginData.Builder.class, name = "setDefaultInclusion", args = { + boolean.class }) + public void testSetDefaultInclusion() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + + // show the default value is true + GlobalPropertyReportPluginData globalPropertyReportPluginData = // + GlobalPropertyReportPluginData.builder()// + .setReportLabel(reportLabel)// + .build(); + assertEquals(true, globalPropertyReportPluginData.getDefaultInclusionPolicy()); + + globalPropertyReportPluginData = // + GlobalPropertyReportPluginData.builder()// + .setReportLabel(reportLabel)// + .setDefaultInclusion(true)// + .build(); + assertEquals(true, globalPropertyReportPluginData.getDefaultInclusionPolicy()); + + globalPropertyReportPluginData = // + GlobalPropertyReportPluginData.builder()// . + .setReportLabel(reportLabel)// + .setDefaultInclusion(false)// + .build(); + assertEquals(false, globalPropertyReportPluginData.getDefaultInclusionPolicy()); + + } + + @Test + @UnitTestMethod(target = GlobalPropertyReportPluginData.Builder.class, name = "includeGlobalProperty", args = { + GlobalPropertyId.class }) + public void testIncludeGlobalProperty() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + + // show the default is non-inclusion + GlobalPropertyReportPluginData personPropertyReportPluginData = // + GlobalPropertyReportPluginData.builder()// + .setReportLabel(reportLabel)// + .build(); + assertTrue(personPropertyReportPluginData.getIncludedProperties().isEmpty()); + + // show that inclusion alone works + Set expectedGlobalPropertyIds = new LinkedHashSet<>(); + + expectedGlobalPropertyIds.add(TestGlobalPropertyId.GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE); + expectedGlobalPropertyIds.add(TestGlobalPropertyId.GLOBAL_PROPERTY_4_BOOLEAN_IMMUTABLE); + expectedGlobalPropertyIds.add(TestGlobalPropertyId.GLOBAL_PROPERTY_2_INTEGER_MUTABLE); + + GlobalPropertyReportPluginData.Builder builder = GlobalPropertyReportPluginData.builder()// + .setReportLabel(reportLabel);// + + for (GlobalPropertyId globalPropertyId : expectedGlobalPropertyIds) { + builder.includeGlobalProperty(globalPropertyId); + } + + personPropertyReportPluginData = builder.build(); + assertEquals(expectedGlobalPropertyIds, personPropertyReportPluginData.getIncludedProperties()); + + // show that inclusion will override exclusion + expectedGlobalPropertyIds.clear(); + + expectedGlobalPropertyIds.add(TestGlobalPropertyId.GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE); + expectedGlobalPropertyIds.add(TestGlobalPropertyId.GLOBAL_PROPERTY_4_BOOLEAN_IMMUTABLE); + expectedGlobalPropertyIds.add(TestGlobalPropertyId.GLOBAL_PROPERTY_2_INTEGER_MUTABLE); + + builder = GlobalPropertyReportPluginData.builder()// + .setReportLabel(reportLabel);// + + for (GlobalPropertyId globalPropertyId : expectedGlobalPropertyIds) { + builder.excludeGlobalProperty(globalPropertyId); + builder.includeGlobalProperty(globalPropertyId); + } + + personPropertyReportPluginData = builder.build(); + assertEquals(expectedGlobalPropertyIds, personPropertyReportPluginData.getIncludedProperties()); + + // precondition: if the person property id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + GlobalPropertyReportPluginData.builder().includeGlobalProperty(null); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GlobalPropertyReportPluginData.Builder.class, name = "excludeGlobalProperty", args = { + GlobalPropertyId.class }) + public void testExcludePersonProperty() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + + // show the default is non-exclusion + GlobalPropertyReportPluginData personPropertyReportPluginData = // + GlobalPropertyReportPluginData.builder()// + .setReportLabel(reportLabel)// + .build(); + assertTrue(personPropertyReportPluginData.getExcludedProperties().isEmpty()); + + // show that exclusion alone works + Set expectedGlobalPropertyIds = new LinkedHashSet<>(); + + expectedGlobalPropertyIds.add(TestGlobalPropertyId.GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE); + expectedGlobalPropertyIds.add(TestGlobalPropertyId.GLOBAL_PROPERTY_4_BOOLEAN_IMMUTABLE); + expectedGlobalPropertyIds.add(TestGlobalPropertyId.GLOBAL_PROPERTY_2_INTEGER_MUTABLE); + + GlobalPropertyReportPluginData.Builder builder = GlobalPropertyReportPluginData.builder()// + .setReportLabel(reportLabel);// + + for (GlobalPropertyId globalPropertyId : expectedGlobalPropertyIds) { + builder.excludeGlobalProperty(globalPropertyId); + } + + personPropertyReportPluginData = builder.build(); + assertEquals(expectedGlobalPropertyIds, personPropertyReportPluginData.getExcludedProperties()); + + // show that exclusion will override inclusion + expectedGlobalPropertyIds.clear(); + + expectedGlobalPropertyIds.add(TestGlobalPropertyId.GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE); + expectedGlobalPropertyIds.add(TestGlobalPropertyId.GLOBAL_PROPERTY_4_BOOLEAN_IMMUTABLE); + expectedGlobalPropertyIds.add(TestGlobalPropertyId.GLOBAL_PROPERTY_2_INTEGER_MUTABLE); + + builder = GlobalPropertyReportPluginData.builder()// + .setReportLabel(reportLabel);// + + for (GlobalPropertyId globalPropertyId : expectedGlobalPropertyIds) { + builder.includeGlobalProperty(globalPropertyId); + builder.excludeGlobalProperty(globalPropertyId); + } + + personPropertyReportPluginData = builder.build(); + assertEquals(expectedGlobalPropertyIds, personPropertyReportPluginData.getExcludedProperties()); + + // precondition: if the person property id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + GlobalPropertyReportPluginData.builder().excludeGlobalProperty(null); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GlobalPropertyReportPluginData.class, name = "getReportLabel", args = {}) + public void testGetReportLabel() { + for (int i = 0; i < 30; i++) { + ReportLabel expectedReportLabel = new SimpleReportLabel(i); + GlobalPropertyReportPluginData globalPropertyReportPluginData = // + GlobalPropertyReportPluginData.builder()// + .setReportLabel(expectedReportLabel)// + .build(); + + assertEquals(expectedReportLabel, globalPropertyReportPluginData.getReportLabel()); + } + + } + + @Test + @UnitTestMethod(target = GlobalPropertyReportPluginData.class, name = "getIncludedProperties", args = {}) + public void testGetIncludedProperties() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + + // show the default is non-inclusion + GlobalPropertyReportPluginData personPropertyReportPluginData = // + GlobalPropertyReportPluginData.builder()// + + .setReportLabel(reportLabel)// + .build(); + assertTrue(personPropertyReportPluginData.getIncludedProperties().isEmpty()); + + // show that inclusion alone works + Set expectedGlobalPropertyIds = new LinkedHashSet<>(); + + expectedGlobalPropertyIds.add(TestGlobalPropertyId.GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE); + expectedGlobalPropertyIds.add(TestGlobalPropertyId.GLOBAL_PROPERTY_4_BOOLEAN_IMMUTABLE); + expectedGlobalPropertyIds.add(TestGlobalPropertyId.GLOBAL_PROPERTY_2_INTEGER_MUTABLE); + + GlobalPropertyReportPluginData.Builder builder = GlobalPropertyReportPluginData.builder()// + .setReportLabel(reportLabel);// + + for (GlobalPropertyId globalPropertyId : expectedGlobalPropertyIds) { + builder.includeGlobalProperty(globalPropertyId); + } + + personPropertyReportPluginData = builder.build(); + assertEquals(expectedGlobalPropertyIds, personPropertyReportPluginData.getIncludedProperties()); + + // show that inclusion will override exclusion + expectedGlobalPropertyIds.clear(); + + expectedGlobalPropertyIds.add(TestGlobalPropertyId.GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE); + expectedGlobalPropertyIds.add(TestGlobalPropertyId.GLOBAL_PROPERTY_4_BOOLEAN_IMMUTABLE); + expectedGlobalPropertyIds.add(TestGlobalPropertyId.GLOBAL_PROPERTY_2_INTEGER_MUTABLE); + + builder = GlobalPropertyReportPluginData.builder()// + .setReportLabel(reportLabel);// + + for (GlobalPropertyId globalPropertyId : expectedGlobalPropertyIds) { + builder.excludeGlobalProperty(globalPropertyId); + builder.includeGlobalProperty(globalPropertyId); + } + + personPropertyReportPluginData = builder.build(); + assertEquals(expectedGlobalPropertyIds, personPropertyReportPluginData.getIncludedProperties()); + + } + + @Test + @UnitTestMethod(target = GlobalPropertyReportPluginData.class, name = "getExcludedProperties", args = {}) + public void testGetExcludedProperties() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + + // show the default is non-exclusion + GlobalPropertyReportPluginData globalPropertyReportPluginData = // + GlobalPropertyReportPluginData.builder()// + .setReportLabel(reportLabel)// + .build(); + assertTrue(globalPropertyReportPluginData.getExcludedProperties().isEmpty()); + + // show that exclusion alone works + Set expectedGlobalPropertyIds = new LinkedHashSet<>(); + + expectedGlobalPropertyIds.add(TestGlobalPropertyId.GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE); + expectedGlobalPropertyIds.add(TestGlobalPropertyId.GLOBAL_PROPERTY_4_BOOLEAN_IMMUTABLE); + expectedGlobalPropertyIds.add(TestGlobalPropertyId.GLOBAL_PROPERTY_4_BOOLEAN_IMMUTABLE); + + GlobalPropertyReportPluginData.Builder builder = GlobalPropertyReportPluginData.builder()// + .setReportLabel(reportLabel);// + + for (GlobalPropertyId globalPropertyId : expectedGlobalPropertyIds) { + builder.excludeGlobalProperty(globalPropertyId); + } + + globalPropertyReportPluginData = builder.build(); + assertEquals(expectedGlobalPropertyIds, globalPropertyReportPluginData.getExcludedProperties()); + + // show that exclusion will override inclusion + expectedGlobalPropertyIds.clear(); + + expectedGlobalPropertyIds.add(TestGlobalPropertyId.GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE); + expectedGlobalPropertyIds.add(TestGlobalPropertyId.GLOBAL_PROPERTY_4_BOOLEAN_IMMUTABLE); + expectedGlobalPropertyIds.add(TestGlobalPropertyId.GLOBAL_PROPERTY_4_BOOLEAN_IMMUTABLE); + + builder = GlobalPropertyReportPluginData.builder()// + .setReportLabel(reportLabel);// + + for (GlobalPropertyId globalPropertyId : expectedGlobalPropertyIds) { + builder.includeGlobalProperty(globalPropertyId); + builder.excludeGlobalProperty(globalPropertyId); + } + + globalPropertyReportPluginData = builder.build(); + assertEquals(expectedGlobalPropertyIds, globalPropertyReportPluginData.getExcludedProperties()); + + } + + @Test + @UnitTestMethod(target = GlobalPropertyReportPluginData.class, name = "getDefaultInclusionPolicy", args = {}) + public void testGetDefaultInclusionPolicy() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + + // show the default value is true + GlobalPropertyReportPluginData globalPropertyReportPluginData = // + GlobalPropertyReportPluginData.builder()// + .setReportLabel(reportLabel)// + .build(); + assertEquals(true, globalPropertyReportPluginData.getDefaultInclusionPolicy()); + + globalPropertyReportPluginData = // + GlobalPropertyReportPluginData.builder()// + .setReportLabel(reportLabel)// + .setDefaultInclusion(true)// + .build(); + assertEquals(true, globalPropertyReportPluginData.getDefaultInclusionPolicy()); + + globalPropertyReportPluginData = // + GlobalPropertyReportPluginData.builder()// + .setReportLabel(reportLabel)// + .setDefaultInclusion(false)// + .build(); + assertEquals(false, globalPropertyReportPluginData.getDefaultInclusionPolicy()); + } + + @Test + @UnitTestMethod(target = GlobalPropertyReportPluginData.class, name = "getCloneBuilder", args = {}) + public void testGetCloneBuilder() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7759639255438669162L); + for (int i = 0; i < 10; i++) { + + // build a GlobalPropertyReportPluginData from random inputs + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt()); + + GlobalPropertyReportPluginData.Builder builder = // + GlobalPropertyReportPluginData.builder()// + .setReportLabel(reportLabel); + + for (int j = 0; j < 10; j++) { + TestGlobalPropertyId testGlobalPropertyId = TestGlobalPropertyId + .getRandomGlobalPropertyId(randomGenerator); + if (randomGenerator.nextBoolean()) { + builder.includeGlobalProperty(testGlobalPropertyId); + } else { + builder.excludeGlobalProperty(testGlobalPropertyId); + } + } + + builder.setDefaultInclusion(randomGenerator.nextBoolean()).build(); + + GlobalPropertyReportPluginData globalPropertyReportPluginData = builder.build(); + + // create the clone builder and have it build + GlobalPropertyReportPluginData cloneGlobalPropertyReportPluginData = globalPropertyReportPluginData + .getCloneBuilder().build(); + + // the result should equal the original if the clone builder was + // initialized with the correct state + assertEquals(globalPropertyReportPluginData, cloneGlobalPropertyReportPluginData); + + } + } + + @Test + @UnitTestMethod(target = GlobalPropertyReportPluginData.class, name = "equals", args = { Object.class }) + public void testEquals() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7376599865331451959L); + for (int i = 0; i < 10; i++) { + // build a GlobalPropertyReportPluginData from the same random + // inputs + GlobalPropertyReportPluginData.Builder builder1 = GlobalPropertyReportPluginData.builder(); + GlobalPropertyReportPluginData.Builder builder2 = GlobalPropertyReportPluginData.builder(); + + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt(100)); + builder1.setReportLabel(reportLabel); + builder2.setReportLabel(reportLabel); + + for (int j = 0; j < 10; j++) { + TestGlobalPropertyId testGlobalPropertyId = TestGlobalPropertyId + .getRandomGlobalPropertyId(randomGenerator); + if (randomGenerator.nextBoolean()) { + builder1.includeGlobalProperty(testGlobalPropertyId); + builder2.includeGlobalProperty(testGlobalPropertyId); + } else { + builder1.excludeGlobalProperty(testGlobalPropertyId); + builder2.excludeGlobalProperty(testGlobalPropertyId); + } + } + + boolean defaultInclusion = randomGenerator.nextBoolean(); + builder1.setDefaultInclusion(defaultInclusion).build(); + builder2.setDefaultInclusion(defaultInclusion).build(); + + GlobalPropertyReportPluginData globalPropertyReportPluginData1 = builder1.build(); + GlobalPropertyReportPluginData globalPropertyReportPluginData2 = builder2.build(); + + assertEquals(globalPropertyReportPluginData1, globalPropertyReportPluginData2); + + // show that plugin datas with different inputs are not equal + + // change the default inclusion + globalPropertyReportPluginData2 = // + globalPropertyReportPluginData1.getCloneBuilder()// + .setDefaultInclusion(!defaultInclusion)// + .build(); + assertNotEquals(globalPropertyReportPluginData2, globalPropertyReportPluginData1); + + // change the report label + reportLabel = new SimpleReportLabel(1000); + globalPropertyReportPluginData2 = // + globalPropertyReportPluginData1.getCloneBuilder()// + .setReportLabel(reportLabel)// + .build(); + assertNotEquals(globalPropertyReportPluginData2, globalPropertyReportPluginData1); + + // change an included property id + if (!globalPropertyReportPluginData1.getIncludedProperties().isEmpty()) { + GlobalPropertyId globalPropertyId = globalPropertyReportPluginData1.getIncludedProperties().iterator() + .next(); + globalPropertyReportPluginData2 = // + globalPropertyReportPluginData1.getCloneBuilder()// + .excludeGlobalProperty(globalPropertyId)// + .build(); + assertNotEquals(globalPropertyReportPluginData2, globalPropertyReportPluginData1); + } + // change an excluded property id + if (!globalPropertyReportPluginData1.getExcludedProperties().isEmpty()) { + GlobalPropertyId globalPropertyId = globalPropertyReportPluginData1.getExcludedProperties().iterator() + .next(); + globalPropertyReportPluginData2 = // + globalPropertyReportPluginData1.getCloneBuilder()// + .includeGlobalProperty(globalPropertyId)// + .build(); + assertNotEquals(globalPropertyReportPluginData2, globalPropertyReportPluginData1); + } + + } + + } + + @Test + @UnitTestMethod(target = GlobalPropertyReportPluginData.class, name = "hashCode", args = {}) + public void testHashCode() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8483458328908100435L); + + Set observedHashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 50; i++) { + // build a GlobalPropertyReportPluginData from the same random + // inputs + GlobalPropertyReportPluginData.Builder builder1 = GlobalPropertyReportPluginData.builder(); + GlobalPropertyReportPluginData.Builder builder2 = GlobalPropertyReportPluginData.builder(); + + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt(100)); + builder1.setReportLabel(reportLabel); + builder2.setReportLabel(reportLabel); + + for (int j = 0; j < 10; j++) { + TestGlobalPropertyId testGlobalPropertyId = TestGlobalPropertyId + .getRandomGlobalPropertyId(randomGenerator); + if (randomGenerator.nextBoolean()) { + builder1.includeGlobalProperty(testGlobalPropertyId); + builder2.includeGlobalProperty(testGlobalPropertyId); + } else { + builder1.excludeGlobalProperty(testGlobalPropertyId); + builder2.excludeGlobalProperty(testGlobalPropertyId); + } + } + + boolean defaultInclusion = randomGenerator.nextBoolean(); + builder1.setDefaultInclusion(defaultInclusion).build(); + builder2.setDefaultInclusion(defaultInclusion).build(); + + GlobalPropertyReportPluginData globalPropertyReportPluginData1 = builder1.build(); + GlobalPropertyReportPluginData globalPropertyReportPluginData2 = builder2.build(); + + // show that the hash code is stable + int hashCode = globalPropertyReportPluginData1.hashCode(); + assertEquals(hashCode, globalPropertyReportPluginData1.hashCode()); + assertEquals(hashCode, globalPropertyReportPluginData1.hashCode()); + assertEquals(hashCode, globalPropertyReportPluginData1.hashCode()); + assertEquals(hashCode, globalPropertyReportPluginData1.hashCode()); + + // show that equal objects have equal hash codes + assertEquals(globalPropertyReportPluginData1.hashCode(), globalPropertyReportPluginData2.hashCode()); + + // collect the hashcode + observedHashCodes.add(globalPropertyReportPluginData1.hashCode()); + } + + /* + * The hash codes should be dispersed -- we only show that they are unique + * values -- this is dependent on the random seed + */ + assertEquals(50, observedHashCodes.size()); + + } + + @Test + @UnitTestMethod(target = GlobalPropertyReportPluginData.class, name = "toString", args = {}) + public void testToString() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7759639255438669162L); + for (int i = 0; i < 10; i++) { + + // build a GlobalPropertyReportPluginData from random inputs + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt()); + + GlobalPropertyReportPluginData.Builder builder = // + GlobalPropertyReportPluginData.builder()// + .setReportLabel(reportLabel); + + StringBuilder sb = new StringBuilder(); + + sb.append("GlobalPropertyReportPluginData [data=").append("Data [reportLabel=").append(reportLabel) + .append(", includedProperties="); + + Set includedProperties = new LinkedHashSet<>(); + Set excludedProperties = new LinkedHashSet<>(); + + for (int j = 0; j < 10; j++) { + TestGlobalPropertyId testGlobalPropertyId = TestGlobalPropertyId + .getRandomGlobalPropertyId(randomGenerator); + if (randomGenerator.nextBoolean()) { + builder.includeGlobalProperty(testGlobalPropertyId); + includedProperties.add(testGlobalPropertyId); + excludedProperties.remove(testGlobalPropertyId); + } else { + builder.excludeGlobalProperty(testGlobalPropertyId); + excludedProperties.add(testGlobalPropertyId); + includedProperties.remove(testGlobalPropertyId); + } + } + + boolean defaultInclusionPolicy = randomGenerator.nextBoolean(); + builder.setDefaultInclusion(defaultInclusionPolicy).build(); + + sb.append(includedProperties).append(", excludedProperties=").append(excludedProperties) + .append(", defaultInclusionPolicy=").append(defaultInclusionPolicy).append(", locked=").append(true) + .append("]").append("]"); + + GlobalPropertyReportPluginData globalPropertyReportPluginData = builder.build(); + + assertEquals(sb.toString(), globalPropertyReportPluginData.toString()); + + } + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/support/AT_GlobalPropertiesError.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/support/AT_GlobalPropertiesError.java new file mode 100644 index 000000000..ad3a9189a --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/support/AT_GlobalPropertiesError.java @@ -0,0 +1,28 @@ +package gov.hhs.aspr.ms.gcm.plugins.globalproperties.support; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestMethod; + +public class AT_GlobalPropertiesError { + + @Test + @UnitTestMethod(target = GlobalPropertiesError.class, name = "getDescription", args = {}) + public void test() { + // show that each description is a unique, non-null and non-empty string + Set descriptions = new LinkedHashSet<>(); + for (GlobalPropertiesError globalPropertiesError : GlobalPropertiesError.values()) { + String description = globalPropertiesError.getDescription(); + assertNotNull(description, "null description for " + globalPropertiesError); + assertTrue(description.length() > 0, "empty string for " + globalPropertiesError); + boolean unique = descriptions.add(description); + assertTrue(unique, "description for " + globalPropertiesError + " is not unique"); + } + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/support/AT_GlobalPropertyDimension.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/support/AT_GlobalPropertyDimension.java new file mode 100644 index 000000000..75610a6dd --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/support/AT_GlobalPropertyDimension.java @@ -0,0 +1,383 @@ +package gov.hhs.aspr.ms.gcm.plugins.globalproperties.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.DimensionContext; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.testsupport.TestGlobalPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_GlobalPropertyDimension { + + @Test + @UnitTestMethod(target = GlobalPropertyDimension.Builder.class, name = "addValue", args = { Object.class }) + public void testAddValue() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3468803942988565031L); + + for (int i = 0; i < 50; i++) { + List expectedValues = new ArrayList<>(); + + GlobalPropertyDimension.Builder builder = GlobalPropertyDimension.builder()// + .setGlobalPropertyId(TestGlobalPropertyId.GLOBAL_PROPERTY_6_DOUBLE_IMMUTABLE); + int n = randomGenerator.nextInt(10); + for (int j = 0; j < n; j++) { + double value = randomGenerator.nextDouble(); + expectedValues.add(value); + builder.addValue(value); + } + GlobalPropertyDimension globalPropertyDimension = builder.build(); + + List actualValues = globalPropertyDimension.getValues(); + assertEquals(expectedValues, actualValues); + } + + // precondition test : if the value is null + ContractException contractException = assertThrows(ContractException.class, + () -> GlobalPropertyDimension.builder().addValue(null)); + assertEquals(PropertyError.NULL_PROPERTY_VALUE, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GlobalPropertyDimension.Builder.class, name = "build", args = {}) + public void testBuild() { + GlobalPropertyDimension globalPropertyDimension = // + GlobalPropertyDimension.builder()// + .setGlobalPropertyId(TestGlobalPropertyId.GLOBAL_PROPERTY_6_DOUBLE_IMMUTABLE)// + .build(); + assertNotNull(globalPropertyDimension); + + // precondition test : if the global property id is not assigned + ContractException contractException = assertThrows(ContractException.class, + () -> GlobalPropertyDimension.builder().build()); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GlobalPropertyDimension.Builder.class, name = "setAssignmentTime", args = { double.class }) + public void testSetAssignmentTime() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7384717734933740607L); + + for (int i = 0; i < 50; i++) { + GlobalPropertyDimension.Builder builder = GlobalPropertyDimension.builder()// + .setGlobalPropertyId(TestGlobalPropertyId.GLOBAL_PROPERTY_6_DOUBLE_IMMUTABLE); + double assignmentTime = randomGenerator.nextDouble(); + builder.setAssignmentTime(assignmentTime); + GlobalPropertyDimension globalPropertyDimension = builder.build(); + + assertEquals(assignmentTime, globalPropertyDimension.getAssignmentTime()); + } + } + + @Test + @UnitTestMethod(target = GlobalPropertyDimension.Builder.class, name = "setGlobalPropertyId", args = { + GlobalPropertyId.class }) + public void testSetGlobalPropertyId() { + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + + GlobalPropertyDimension.Builder builder = GlobalPropertyDimension.builder()// + .setGlobalPropertyId(testGlobalPropertyId); + + GlobalPropertyDimension globalPropertyDimension = builder.build(); + assertEquals(testGlobalPropertyId, globalPropertyDimension.getGlobalPropertyId()); + } + + // precondition test : if the value is null + ContractException contractException = assertThrows(ContractException.class, + () -> GlobalPropertyDimension.builder().setGlobalPropertyId(null)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GlobalPropertyDimension.class, name = "builder", args = {}) + public void testBuilder() { + assertNotNull(GlobalPropertyDimension.builder()); + } + + @Test + @UnitTestMethod(target = GlobalPropertyDimension.class, name = "executeLevel", args = { DimensionContext.class, + int.class }) + public void testExecuteLevel() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2924521933883974690L); + + // run several test cases + for (int i = 0; i < 30; i++) { + + // select a random number of levels + int levelCount = randomGenerator.nextInt(10); + // select a random property id + TestGlobalPropertyId targetPropertyId = TestGlobalPropertyId.getRandomGlobalPropertyId(randomGenerator); + + // generate random values for the level + List exectedValues = new ArrayList<>(); + for (int j = 0; j < levelCount; j++) { + exectedValues.add(targetPropertyId.getRandomPropertyValue(randomGenerator)); + } + + // create a GlobalPropertyDimension with the level values + GlobalPropertyDimension.Builder dimBuilder = GlobalPropertyDimension.builder()// + .setGlobalPropertyId(targetPropertyId); + + for (Object value : exectedValues) { + dimBuilder.addValue(value); + } + + GlobalPropertyDimension globalPropertyDimension = dimBuilder.build(); + + // show that for each level the dimension properly assigns the value + // to a global property data builder + for (int level = 0; level < levelCount; level++) { + /* + * Create a GlobalPropertiesPluginData, filling it with the test property + * definitions and any values that are required + */ + GlobalPropertiesPluginData.Builder pluginDataBuilder = GlobalPropertiesPluginData.builder(); + for (TestGlobalPropertyId propertyId : TestGlobalPropertyId.values()) { + PropertyDefinition propertyDefinition = propertyId.getPropertyDefinition(); + pluginDataBuilder.defineGlobalProperty(propertyId, propertyDefinition, 0.0); + if (propertyDefinition.getDefaultValue().isEmpty()) { + pluginDataBuilder.setGlobalPropertyValue(propertyId, + propertyId.getRandomPropertyValue(randomGenerator), 0.0); + } + } + + // Create a dimension context that contain the plugin data + // builder + DimensionContext.Builder dimensionContextBuilder = DimensionContext.builder(); + + pluginDataBuilder = (GlobalPropertiesPluginData.Builder) dimensionContextBuilder + .add(pluginDataBuilder.build()); + DimensionContext dimensionContext = dimensionContextBuilder.build(); + + // execute the dimension with the level + globalPropertyDimension.executeLevel(dimensionContext, level); + + /* + * get the GlobalPropertiesPluginData from the corresponding builder + */ + GlobalPropertiesPluginData globalPropertiesPluginData = pluginDataBuilder.build(); + + /* + * show that the GlobalPropertiesPluginData has the value we expect for the + * given level + */ + Optional optionalValue = globalPropertiesPluginData.getGlobalPropertyValue(targetPropertyId); + assertTrue(optionalValue.isPresent()); + Object actualValue = optionalValue.get(); + Object expectedValue = exectedValues.get(level); + assertEquals(expectedValue, actualValue); + } + } + + } + + @Test + @UnitTestMethod(target = GlobalPropertyDimension.class, name = "getExperimentMetaData", args = {}) + public void testGetExperimentMetaData() { + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + + List expectedExperimentMetaData = new ArrayList<>(); + expectedExperimentMetaData.add(testGlobalPropertyId.toString()); + + GlobalPropertyDimension.Builder builder = GlobalPropertyDimension.builder()// + .setGlobalPropertyId(testGlobalPropertyId); + + GlobalPropertyDimension globalPropertyDimension = builder.build(); + assertEquals(expectedExperimentMetaData, globalPropertyDimension.getExperimentMetaData()); + } + + } + + @Test + @UnitTestMethod(target = GlobalPropertyDimension.class, name = "getGlobalPropertyId", args = {}) + public void testGetGlobalPropertyId() { + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + + GlobalPropertyDimension.Builder builder = GlobalPropertyDimension.builder()// + .setGlobalPropertyId(testGlobalPropertyId); + + GlobalPropertyDimension globalPropertyDimension = builder.build(); + assertEquals(testGlobalPropertyId, globalPropertyDimension.getGlobalPropertyId()); + } + } + + @Test + @UnitTestMethod(target = GlobalPropertyDimension.class, name = "getValues", args = {}) + public void testGetValues() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4581428044056639458L); + + for (int i = 0; i < 50; i++) { + List expectedValues = new ArrayList<>(); + + GlobalPropertyDimension.Builder builder = GlobalPropertyDimension.builder()// + .setGlobalPropertyId(TestGlobalPropertyId.GLOBAL_PROPERTY_6_DOUBLE_IMMUTABLE); + int n = randomGenerator.nextInt(10); + for (int j = 0; j < n; j++) { + double value = randomGenerator.nextDouble(); + expectedValues.add(value); + builder.addValue(value); + } + GlobalPropertyDimension globalPropertyDimension = builder.build(); + + List actualValues = globalPropertyDimension.getValues(); + assertEquals(expectedValues, actualValues); + } + + } + + @Test + @UnitTestMethod(target = GlobalPropertyDimension.class, name = "levelCount", args = {}) + public void testLevelCount() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8913942504408118065L); + + for (int i = 0; i < 50; i++) { + + GlobalPropertyDimension.Builder builder = GlobalPropertyDimension.builder()// + .setGlobalPropertyId(TestGlobalPropertyId.GLOBAL_PROPERTY_6_DOUBLE_IMMUTABLE); + int n = randomGenerator.nextInt(10); + for (int j = 0; j < n; j++) { + double value = randomGenerator.nextDouble(); + builder.addValue(value); + } + GlobalPropertyDimension globalPropertyDimension = builder.build(); + + assertEquals(n, globalPropertyDimension.levelCount()); + } + } + + @Test + @UnitTestMethod(target = GlobalPropertyDimension.class, name = "getAssignmentTime", args = {}) + public void testGetAssignmentTime() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1528599899244176790L); + + for (int i = 0; i < 50; i++) { + GlobalPropertyDimension.Builder builder = GlobalPropertyDimension.builder()// + .setGlobalPropertyId(TestGlobalPropertyId.GLOBAL_PROPERTY_6_DOUBLE_IMMUTABLE); + double assignmentTime = randomGenerator.nextDouble(); + builder.setAssignmentTime(assignmentTime); + GlobalPropertyDimension globalPropertyDimension = builder.build(); + + assertEquals(assignmentTime, globalPropertyDimension.getAssignmentTime()); + } + } + + @Test + @UnitTestMethod(target = GlobalPropertyDimension.class, name = "hashCode", args = {}) + public void testHashCode() { + GlobalPropertyDimension dimension1 = GlobalPropertyDimension.builder() + .setGlobalPropertyId(TestGlobalPropertyId.GLOBAL_PROPERTY_6_DOUBLE_IMMUTABLE).setAssignmentTime(0) + .build(); + + GlobalPropertyDimension dimension2 = GlobalPropertyDimension.builder() + .setGlobalPropertyId(TestGlobalPropertyId.GLOBAL_PROPERTY_6_DOUBLE_IMMUTABLE).setAssignmentTime(1) + .build(); + + GlobalPropertyDimension dimension3 = GlobalPropertyDimension.builder() + .setGlobalPropertyId(TestGlobalPropertyId.GLOBAL_PROPERTY_2_INTEGER_MUTABLE).setAssignmentTime(0) + .build(); + + GlobalPropertyDimension dimension4 = GlobalPropertyDimension.builder() + .setGlobalPropertyId(TestGlobalPropertyId.GLOBAL_PROPERTY_3_DOUBLE_MUTABLE).setAssignmentTime(1) + .build(); + + GlobalPropertyDimension dimension5 = GlobalPropertyDimension.builder() + .setGlobalPropertyId(TestGlobalPropertyId.GLOBAL_PROPERTY_4_BOOLEAN_IMMUTABLE).setAssignmentTime(1) + .build(); + + GlobalPropertyDimension dimension6 = GlobalPropertyDimension.builder() + .setGlobalPropertyId(TestGlobalPropertyId.GLOBAL_PROPERTY_6_DOUBLE_IMMUTABLE).setAssignmentTime(0) + .build(); + + assertEquals(dimension1.hashCode(), dimension1.hashCode()); + + assertNotEquals(dimension1.hashCode(), dimension2.hashCode()); + assertNotEquals(dimension1.hashCode(), dimension3.hashCode()); + assertNotEquals(dimension1.hashCode(), dimension4.hashCode()); + assertNotEquals(dimension1.hashCode(), dimension5.hashCode()); + + assertNotEquals(dimension2.hashCode(), dimension3.hashCode()); + assertNotEquals(dimension2.hashCode(), dimension4.hashCode()); + assertNotEquals(dimension2.hashCode(), dimension5.hashCode()); + assertNotEquals(dimension2.hashCode(), dimension6.hashCode()); + + assertNotEquals(dimension3.hashCode(), dimension4.hashCode()); + assertNotEquals(dimension3.hashCode(), dimension5.hashCode()); + assertNotEquals(dimension3.hashCode(), dimension6.hashCode()); + + assertNotEquals(dimension4.hashCode(), dimension5.hashCode()); + assertNotEquals(dimension4.hashCode(), dimension6.hashCode()); + + assertNotEquals(dimension5.hashCode(), dimension6.hashCode()); + + assertEquals(dimension1.hashCode(), dimension6.hashCode()); + } + + @Test + @UnitTestMethod(target = GlobalPropertyDimension.class, name = "equals", args = { Object.class }) + public void testEquals() { + GlobalPropertyDimension dimension1 = GlobalPropertyDimension.builder() + .setGlobalPropertyId(TestGlobalPropertyId.GLOBAL_PROPERTY_6_DOUBLE_IMMUTABLE).setAssignmentTime(0) + .build(); + + GlobalPropertyDimension dimension2 = GlobalPropertyDimension.builder() + .setGlobalPropertyId(TestGlobalPropertyId.GLOBAL_PROPERTY_6_DOUBLE_IMMUTABLE).setAssignmentTime(1) + .build(); + + GlobalPropertyDimension dimension3 = GlobalPropertyDimension.builder() + .setGlobalPropertyId(TestGlobalPropertyId.GLOBAL_PROPERTY_2_INTEGER_MUTABLE).setAssignmentTime(0) + .build(); + + GlobalPropertyDimension dimension4 = GlobalPropertyDimension.builder() + .setGlobalPropertyId(TestGlobalPropertyId.GLOBAL_PROPERTY_3_DOUBLE_MUTABLE).setAssignmentTime(1) + .build(); + + GlobalPropertyDimension dimension5 = GlobalPropertyDimension.builder() + .setGlobalPropertyId(TestGlobalPropertyId.GLOBAL_PROPERTY_4_BOOLEAN_IMMUTABLE).setAssignmentTime(1) + .build(); + + GlobalPropertyDimension dimension6 = GlobalPropertyDimension.builder() + .setGlobalPropertyId(TestGlobalPropertyId.GLOBAL_PROPERTY_6_DOUBLE_IMMUTABLE).setAssignmentTime(0) + .build(); + + assertEquals(dimension1, dimension1); + + assertNotEquals(dimension1, null); + assertNotEquals(dimension1, new Object()); + + assertNotEquals(dimension1, dimension2); + assertNotEquals(dimension1, dimension3); + assertNotEquals(dimension1, dimension4); + assertNotEquals(dimension1, dimension5); + + assertNotEquals(dimension2, dimension3); + assertNotEquals(dimension2, dimension4); + assertNotEquals(dimension2, dimension5); + assertNotEquals(dimension2, dimension6); + + assertNotEquals(dimension3, dimension4); + assertNotEquals(dimension3, dimension5); + assertNotEquals(dimension3, dimension6); + + assertNotEquals(dimension4, dimension5); + assertNotEquals(dimension4, dimension6); + + assertNotEquals(dimension5, dimension6); + + assertEquals(dimension1, dimension6); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/support/AT_GlobalPropertyInitialization.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/support/AT_GlobalPropertyInitialization.java new file mode 100644 index 000000000..d1d8a4261 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/support/AT_GlobalPropertyInitialization.java @@ -0,0 +1,150 @@ +package gov.hhs.aspr.ms.gcm.plugins.globalproperties.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + +public class AT_GlobalPropertyInitialization { + + @Test + @UnitTestMethod(target = GlobalPropertyInitialization.class, name = "builder", args = {}) + public void testBuilder() { + assertNotNull(GlobalPropertyInitialization.builder()); + } + + @Test + @UnitTestMethod(target = GlobalPropertyInitialization.Builder.class, name = "build", args = {}) + public void testBuild() { + + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).build(); + + assertNotNull(GlobalPropertyInitialization .builder()// + .setValue(5)// + .setGlobalPropertyId(new SimpleGlobalPropertyId("firstTestId"))// + .setPropertyDefinition(propertyDefinition).build()); + + // precondition test: if property id is not set + ContractException idContractException = assertThrows(ContractException.class, () -> { + GlobalPropertyInitialization.builder()// + .setPropertyDefinition(propertyDefinition)// + .setValue(5)// + .build(); + }); + + assertEquals(PropertyError.NULL_PROPERTY_ID, idContractException.getErrorType()); + + // precondition test: if property definition is not set + ContractException propertyContractException = assertThrows(ContractException.class, () -> { + GlobalPropertyInitialization.builder()// + .setGlobalPropertyId(new SimpleGlobalPropertyId("secondTestId"))// + .setValue(6).build(); + }); + assertEquals(PropertyError.NULL_PROPERTY_DEFINITION, propertyContractException.getErrorType()); + + // precondition test: if the property value is not set + ContractException valueContractException = assertThrows(ContractException.class, () -> { + GlobalPropertyInitialization.builder()// + .setGlobalPropertyId(new SimpleGlobalPropertyId("thirdTestId"))// + .setPropertyDefinition(propertyDefinition)// + .build(); + }); + + assertEquals(PropertyError.INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT, valueContractException.getErrorType()); + + // precondition test: if the property definition and value types are + // incompatible + ContractException incompatibleContractException = assertThrows(ContractException.class, () -> { + GlobalPropertyInitialization.builder()// + .setGlobalPropertyId(new SimpleGlobalPropertyId("fourthTestId"))// + .setPropertyDefinition(propertyDefinition)// + .setValue(15.5f).build(); + }); + + assertEquals(PropertyError.INCOMPATIBLE_VALUE, incompatibleContractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GlobalPropertyInitialization.Builder.class, name = "setGlobalPropertyId", args = { GlobalPropertyId.class }) + public void testSetGlobalPropertyId() { + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).build(); + + // precondition test: if the property id is null + ContractException idContractException = assertThrows(ContractException.class, + () -> GlobalPropertyInitialization.builder().setPropertyDefinition(propertyDefinition).setValue(5).setGlobalPropertyId(null)); + assertEquals(PropertyError.NULL_PROPERTY_ID, idContractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GlobalPropertyInitialization.Builder.class, name = "setPropertyDefinition", args = { PropertyDefinition.class }) + public void testSetPropertyDefinition() { + // precondition test: if the property definition is null + ContractException propertyContractException = assertThrows(ContractException.class, + () -> GlobalPropertyInitialization.builder().setGlobalPropertyId(new SimpleGlobalPropertyId("fifthTestId")).setValue(5).setPropertyDefinition(null)); + assertEquals(PropertyError.NULL_PROPERTY_DEFINITION, propertyContractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GlobalPropertyInitialization.Builder.class, name = "setValue", args = { Object.class }) + public void testSetValue() { + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).build(); + + // precondition test: if the property value is null + ContractException valueContractException = assertThrows(ContractException.class, + () -> GlobalPropertyInitialization.builder().setGlobalPropertyId(new SimpleGlobalPropertyId("sixthTestId")).setPropertyDefinition(propertyDefinition).setValue(null)); + assertEquals(PropertyError.NULL_PROPERTY_VALUE, valueContractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GlobalPropertyInitialization.class, name = "getGlobalPropertyId", args = {}) + public void testGetGlobalPropertyId() { + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).build(); + GlobalPropertyInitialization.Builder builder = GlobalPropertyInitialization.builder(); + GlobalPropertyId globalPropertyId = new SimpleGlobalPropertyId("seventhTestId"); + Integer value = 6; + + builder.setGlobalPropertyId(globalPropertyId).setPropertyDefinition(propertyDefinition).setValue(value); + GlobalPropertyInitialization globalPropertyInitialization = builder.build(); + + assertNotNull(globalPropertyInitialization.getGlobalPropertyId()); + assertEquals(globalPropertyId, globalPropertyInitialization.getGlobalPropertyId()); + } + + @Test + @UnitTestMethod(target = GlobalPropertyInitialization.class, name = "getPropertyDefinition", args = {}) + public void testGetPropertyDefinition() { + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).build(); + GlobalPropertyInitialization.Builder builder = GlobalPropertyInitialization.builder(); + GlobalPropertyId globalPropertyId = new SimpleGlobalPropertyId("eighthTestId"); + Integer value = 6; + + builder.setGlobalPropertyId(globalPropertyId).setPropertyDefinition(propertyDefinition).setValue(value); + GlobalPropertyInitialization globalPropertyInitialization = builder.build(); + + assertNotNull(globalPropertyInitialization.getPropertyDefinition()); + assertEquals(propertyDefinition, globalPropertyInitialization.getPropertyDefinition()); + + } + + @Test + @UnitTestMethod(target = GlobalPropertyInitialization.class, name = "getValue", args = {}) + public void testGetValue() { + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).build(); + GlobalPropertyInitialization.Builder builder = GlobalPropertyInitialization.builder(); + GlobalPropertyId globalPropertyId = new SimpleGlobalPropertyId("eighthTestId"); + Integer value = 6; + + builder.setGlobalPropertyId(globalPropertyId).setPropertyDefinition(propertyDefinition).setValue(value); + GlobalPropertyInitialization globalPropertyInitialization = builder.build(); + + assertNotNull(globalPropertyInitialization.getValue()); + assertEquals(value, globalPropertyInitialization.getValue().get()); + } +} \ No newline at end of file diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/support/AT_SimpleGlobalPropertyId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/support/AT_SimpleGlobalPropertyId.java new file mode 100644 index 000000000..efa3ea374 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/support/AT_SimpleGlobalPropertyId.java @@ -0,0 +1,137 @@ +package gov.hhs.aspr.ms.gcm.plugins.globalproperties.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_SimpleGlobalPropertyId { + + @Test + @UnitTestConstructor(target = SimpleGlobalPropertyId.class, args = { Object.class }) + public void testConstructor() { + assertNotNull(new SimpleGlobalPropertyId(5)); + + assertThrows(RuntimeException.class, () -> new SimpleGlobalPropertyId(null)); + } + + @Test + @UnitTestMethod(target = SimpleGlobalPropertyId.class, name = "getValue", args = {}) + public void testGetValue() { + for (int i = 0; i < 10; i++) { + Object value = i * 10 % 5; + + SimpleGlobalPropertyId simpleGlobalPropertyId = new SimpleGlobalPropertyId(value); + + assertEquals(value, simpleGlobalPropertyId.getValue()); + } + } + + @Test + @UnitTestMethod(target = SimpleGlobalPropertyId.class, name = "toString", args = {}) + public void testToString() { + /* + * Show that the toString of the SimpleGlobalPropertyId equals its + * input's toString + */ + + assertEquals(Integer.toString(5), new SimpleGlobalPropertyId(5).toString()); + assertEquals("table", new SimpleGlobalPropertyId("table").toString()); + assertEquals(Double.toString(2345.5345), new SimpleGlobalPropertyId(2345.5345).toString()); + + } + + @Test + @UnitTestMethod(target = SimpleGlobalPropertyId.class, name = "equals", args = { Object.class }) + public void testEquals() { + SimpleGlobalPropertyId id_1 = new SimpleGlobalPropertyId(2); + SimpleGlobalPropertyId id_2 = new SimpleGlobalPropertyId(5); + SimpleGlobalPropertyId id_3 = new SimpleGlobalPropertyId(2); + SimpleGlobalPropertyId id_4 = new SimpleGlobalPropertyId("A"); + SimpleGlobalPropertyId id_5 = new SimpleGlobalPropertyId("A"); + SimpleGlobalPropertyId id_6 = new SimpleGlobalPropertyId("B"); + SimpleGlobalPropertyId id_7 = new SimpleGlobalPropertyId("A"); + + assertEquals(id_1, id_1); // testing reflexive property + assertNotEquals(id_1, id_2); + assertEquals(id_1, id_3); // part of reflective property test + assertNotEquals(id_1, id_4); + assertNotEquals(id_1, id_5); + assertNotEquals(id_1, id_6); + + assertNotEquals(id_2, id_1); + assertEquals(id_2, id_2); + assertNotEquals(id_2, id_3); + assertNotEquals(id_2, id_4); + assertNotEquals(id_2, id_5); + assertNotEquals(id_2, id_6); + + assertEquals(id_3, id_1); // part of reflective property test + assertNotEquals(id_3, id_2); + assertEquals(id_3, id_3); + assertNotEquals(id_3, id_4); + assertNotEquals(id_3, id_5); + assertNotEquals(id_3, id_6); + + assertNotEquals(id_4, id_1); + assertNotEquals(id_4, id_2); + assertNotEquals(id_4, id_3); + assertEquals(id_4, id_4); + assertEquals(id_4, id_5); // part of transitive property test + assertNotEquals(id_4, id_6); + assertEquals(id_4, id_7); // part of transitive property test + + assertNotEquals(id_5, id_1); + assertNotEquals(id_5, id_2); + assertNotEquals(id_5, id_3); + assertEquals(id_5, id_4); + assertEquals(id_5, id_5); + assertNotEquals(id_5, id_6); + assertEquals(id_5, id_7); // part of transitive property test + + assertNotEquals(id_6, id_1); + assertNotEquals(id_6, id_2); + assertNotEquals(id_6, id_3); + assertNotEquals(id_6, id_4); + assertNotEquals(id_6, id_5); + assertEquals(id_6, id_6); + + // null tests + assertNotEquals(id_1, null); + assertNotEquals(id_2, null); + assertNotEquals(id_3, null); + assertNotEquals(id_4, null); + assertNotEquals(id_5, null); + assertNotEquals(id_6, null); + + } + + @Test + @UnitTestMethod(target = SimpleGlobalPropertyId.class, name = "hashCode", args = {}) + public void testHashCode() { + + // equal objects have equal hash codes + for (int i = 0; i < 30; i++) { + SimpleGlobalPropertyId s1 = new SimpleGlobalPropertyId(i); + SimpleGlobalPropertyId s2 = new SimpleGlobalPropertyId(i); + assertEquals(s1.hashCode(), s2.hashCode()); + } + + Set hashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 30; i++) { + boolean unique = hashCodes.add(new SimpleGlobalPropertyId(i).hashCode()); + assertTrue(unique); + } + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/testsupport/AT_GlobalPropertiesTestPluginFactory.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/testsupport/AT_GlobalPropertiesTestPluginFactory.java new file mode 100644 index 000000000..442dec628 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/testsupport/AT_GlobalPropertiesTestPluginFactory.java @@ -0,0 +1,203 @@ +package gov.hhs.aspr.ms.gcm.plugins.globalproperties.testsupport; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.EnumSet; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.TestFactoryUtil; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginId; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.GlobalPropertiesPluginId; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.reports.GlobalPropertyReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertiesError; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.SimpleGlobalPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.testsupport.GlobalPropertiesTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; +import util.wrappers.MutableBoolean; + +public class AT_GlobalPropertiesTestPluginFactory { + + @Test + @UnitTestMethod(target = GlobalPropertiesTestPluginFactory.class, name = "factory", args = { long.class, + Consumer.class }) + public void testFactory_Consumer() { + MutableBoolean executed = new MutableBoolean(); + Factory factory = GlobalPropertiesTestPluginFactory.factory(2050026532065791481L, c -> executed.setValue(true)); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + assertTrue(executed.getValue()); + + // precondition: consumer is null + Consumer nullConsumer = null; + ContractException contractException = assertThrows(ContractException.class, + () -> GlobalPropertiesTestPluginFactory.factory(0, nullConsumer)); + assertEquals(NucleusError.NULL_ACTOR_CONTEXT_CONSUMER, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GlobalPropertiesTestPluginFactory.class, name = "factory", args = { long.class, + TestPluginData.class }) + public void testFactory_TestPluginData() { + MutableBoolean executed = new MutableBoolean(); + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, c -> executed.setValue(true))); + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = GlobalPropertiesTestPluginFactory.factory(2050026532065791481L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + assertTrue(executed.getValue()); + + // precondition: testPluginData is null + TestPluginData nullTestPluginData = null; + ContractException contractException = assertThrows(ContractException.class, + () -> GlobalPropertiesTestPluginFactory.factory(0, nullTestPluginData)); + assertEquals(NucleusError.NULL_PLUGIN_DATA, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GlobalPropertiesTestPluginFactory.Factory.class, name = "getPlugins", args = {}) + public void testGetPlugins() { + + List plugins = GlobalPropertiesTestPluginFactory.factory(2050026532065791481L, t -> { + }).getPlugins(); + assertEquals(2, plugins.size()); + + TestFactoryUtil.checkPluginExists(plugins, GlobalPropertiesPluginId.PLUGIN_ID); + TestFactoryUtil.checkPluginExists(plugins, TestPluginId.PLUGIN_ID); + } + + @Test + @UnitTestMethod(target = GlobalPropertiesTestPluginFactory.Factory.class, name = "setGlobalPropertyReportPluginData", args = { + GlobalPropertyReportPluginData.class }) + public void testSetGlobalPropertyReportPluginData() { + GlobalPropertyReportPluginData.Builder builder = GlobalPropertyReportPluginData.builder(); + + builder.setDefaultInclusion(false).setReportLabel(new SimpleReportLabel("global property report")); + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1086184935572375203L); + for (GlobalPropertyId globalPropertyId : TestGlobalPropertyId.values()) { + if (randomGenerator.nextBoolean()) { + builder.includeGlobalProperty(globalPropertyId); + } else { + builder.excludeGlobalProperty(globalPropertyId); + } + } + + GlobalPropertyReportPluginData pluginData = builder.build(); + + List plugins = GlobalPropertiesTestPluginFactory.factory(2050026532065791481L, t -> { + }).setGlobalPropertyReportPluginData(pluginData).getPlugins(); + + TestFactoryUtil.checkPluginDataExists(plugins, pluginData, GlobalPropertiesPluginId.PLUGIN_ID); + + // precondition: globalPropReportPluginData is null + ContractException contractException = assertThrows(ContractException.class, + () -> GlobalPropertiesTestPluginFactory.factory(2050026532065791481L, t -> { + }).setGlobalPropertyReportPluginData(null)); + assertEquals(GlobalPropertiesError.NULL_GLOBAL_PROPERTY_REPORT_PLUGIN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GlobalPropertiesTestPluginFactory.Factory.class, name = "setGlobalPropertiesPluginData", args = { + GlobalPropertiesPluginData.class }) + public void testSetGlobalPropertiesPluginData() { + GlobalPropertiesPluginData.Builder initialDatabuilder = GlobalPropertiesPluginData.builder(); + + GlobalPropertyId globalPropertyId_1 = new SimpleGlobalPropertyId("id_1"); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(3) + .build(); + initialDatabuilder.defineGlobalProperty(globalPropertyId_1, propertyDefinition, 0); + + GlobalPropertyId globalPropertyId_2 = new SimpleGlobalPropertyId("id_2"); + propertyDefinition = PropertyDefinition.builder().setType(Double.class).setDefaultValue(6.78).build(); + initialDatabuilder.defineGlobalProperty(globalPropertyId_2, propertyDefinition, 0); + + GlobalPropertyId globalPropertyId_3 = new SimpleGlobalPropertyId("id_3"); + propertyDefinition = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(true).build(); + initialDatabuilder.defineGlobalProperty(globalPropertyId_3, propertyDefinition, 0); + + GlobalPropertiesPluginData globalPropertiesPluginData = initialDatabuilder.build(); + + List plugins = GlobalPropertiesTestPluginFactory.factory(2050026532065791481L, t -> { + }).setGlobalPropertiesPluginData(globalPropertiesPluginData).getPlugins(); + + TestFactoryUtil.checkPluginDataExists(plugins, globalPropertiesPluginData, GlobalPropertiesPluginId.PLUGIN_ID); + + // precondition: globalPropertiesPluginData is not null + ContractException contractException = assertThrows(ContractException.class, + () -> GlobalPropertiesTestPluginFactory.factory(2050026532065791481L, t -> { + }).setGlobalPropertiesPluginData(null)); + assertEquals(GlobalPropertiesError.NULL_GLOBAL_PLUGIN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GlobalPropertiesTestPluginFactory.class, name = "getStandardGlobalPropertiesPluginData", args = { + long.class }) + public void testGetStandardGlobalPropertiesPluginData() { + long seed = 2376369840099946020L; + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + + GlobalPropertiesPluginData actualPluginData = GlobalPropertiesTestPluginFactory + .getStandardGlobalPropertiesPluginData(seed); + + Set expectedPropertyIds = EnumSet.allOf(TestGlobalPropertyId.class); + assertFalse(expectedPropertyIds.isEmpty()); + + Set actualGlobalPropertyIds = actualPluginData.getGlobalPropertyIds(); + assertEquals(expectedPropertyIds, actualGlobalPropertyIds); + + GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder(); + + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.getGlobalPropertyIds()) { + builder.defineGlobalProperty(testGlobalPropertyId, testGlobalPropertyId.getPropertyDefinition(), 0); + boolean hasDefaultValue = testGlobalPropertyId.getPropertyDefinition().getDefaultValue().isPresent(); + if (!hasDefaultValue) { + builder.setGlobalPropertyValue(testGlobalPropertyId, + testGlobalPropertyId.getRandomPropertyValue(randomGenerator), 0); + } + } + + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId + .getShuffledGlobalPropertyIds(randomGenerator)) { + + boolean hasDefaultValue = testGlobalPropertyId.getPropertyDefinition().getDefaultValue().isPresent(); + boolean setValue = randomGenerator.nextBoolean(); + if (hasDefaultValue && setValue) { + // set a value to the default + builder.setGlobalPropertyValue(testGlobalPropertyId, + testGlobalPropertyId.getPropertyDefinition().getDefaultValue().get(), 0); + } else if (setValue) { + // set a value to not the default + builder.setGlobalPropertyValue(testGlobalPropertyId, + testGlobalPropertyId.getRandomPropertyValue(randomGenerator), 0); + } + } + + GlobalPropertiesPluginData expectedPluginData = builder.build(); + + assertEquals(expectedPluginData, actualPluginData); + + } + +} \ No newline at end of file diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/testsupport/AT_TestAuxiliaryGlobalPropertyId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/testsupport/AT_TestAuxiliaryGlobalPropertyId.java new file mode 100644 index 000000000..af5d3d322 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/testsupport/AT_TestAuxiliaryGlobalPropertyId.java @@ -0,0 +1,102 @@ +package gov.hhs.aspr.ms.gcm.plugins.globalproperties.testsupport; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import util.annotations.UnitTestMethod; +import util.random.RandomGeneratorProvider; +import util.wrappers.MutableInteger; + +public class AT_TestAuxiliaryGlobalPropertyId { + + @Test + @UnitTestMethod(target = TestAuxiliaryGlobalPropertyId.class, name = "getRandomGlobalPropertyId", args = { RandomGenerator.class }) + public void testGetRandomGlobalPropertyId() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5005107416828888981L); + Map idCounter = new LinkedHashMap<>(); + Set hashSetOfRandomIds = new LinkedHashSet<>(); + + for (TestAuxiliaryGlobalPropertyId testAuxiliaryGlobalPropertyId : TestAuxiliaryGlobalPropertyId.values()) { + idCounter.put(testAuxiliaryGlobalPropertyId, new MutableInteger()); + } + + // show that generated values are reasonably unique + for (int i = 0; i < 600; i++) { + TestAuxiliaryGlobalPropertyId testAuxiliaryGlobalPropertyId = TestAuxiliaryGlobalPropertyId.getRandomGlobalPropertyId(randomGenerator); + hashSetOfRandomIds.add(testAuxiliaryGlobalPropertyId); + idCounter.get(testAuxiliaryGlobalPropertyId).increment(); + } + for (TestAuxiliaryGlobalPropertyId propertyId : idCounter.keySet()) { + assertTrue(idCounter.get(propertyId).getValue() >= 30 && idCounter.get(propertyId).getValue() <= 150); + } + + assertEquals(idCounter.values().stream().mapToInt(a -> a.getValue()).sum(), 600); + assertEquals(hashSetOfRandomIds.size(), 6); + + } + + @Test + @UnitTestMethod(target = TestAuxiliaryGlobalPropertyId.class, name = "getRandomPropertyValue", args = { RandomGenerator.class }) + public void testGetRandomPropertyValue() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6173923848365818813L); + /* + * Show that randomly generated values are compatible with the + * associated property definition. Show that the values are reasonably + * unique + */ + for (TestAuxiliaryGlobalPropertyId testAuxiliaryGlobalPropertyId : TestAuxiliaryGlobalPropertyId.values()) { + PropertyDefinition propertyDefinition = testAuxiliaryGlobalPropertyId.getPropertyDefinition(); + Set values = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + Object propertyValue = testAuxiliaryGlobalPropertyId.getRandomPropertyValue(randomGenerator); + values.add(propertyValue); + assertTrue(propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())); + } + // show that the values are reasonable unique + if (propertyDefinition.getType() != Boolean.class) { + assertTrue(values.size() > 10); + } else { + assertEquals(2, values.size()); + } + } + } + + @Test + @UnitTestMethod(target = TestAuxiliaryGlobalPropertyId.class, name = "getPropertyDefinition", args = {}) + public void testGetPropertyDefinition() { + for (TestAuxiliaryGlobalPropertyId testAuxiliaryGlobalPropertyId : TestAuxiliaryGlobalPropertyId.values()) { + assertNotNull(testAuxiliaryGlobalPropertyId.getPropertyDefinition()); + } + } + + @Test + @UnitTestMethod(target = TestAuxiliaryGlobalPropertyId.class, name = "getUnknownGlobalPropertyId", args = {}) + public void testGetUnknownGlobalPropertyId() { + /* + * Shows that a generated unknown group property id is unique, not null + * and not a member of the enum + */ + Set testProperties = EnumSet.allOf(TestAuxiliaryGlobalPropertyId.class); + Set unknownGroupPropertyIds = new LinkedHashSet<>(); // ???? + for (int i = 0; i < 30; i++) { + GlobalPropertyId unknownGlobalPropertyId = TestAuxiliaryGlobalPropertyId.getUnknownGlobalPropertyId(); + assertNotNull(unknownGlobalPropertyId); + boolean unique = unknownGroupPropertyIds.add(unknownGlobalPropertyId); + assertTrue(unique); + assertFalse(testProperties.contains(unknownGlobalPropertyId)); + } + } +} \ No newline at end of file diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/testsupport/AT_TestGlobalPropertyId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/testsupport/AT_TestGlobalPropertyId.java new file mode 100644 index 000000000..7803e5571 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/globalproperties/testsupport/AT_TestGlobalPropertyId.java @@ -0,0 +1,147 @@ +package gov.hhs.aspr.ms.gcm.plugins.globalproperties.testsupport; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import util.annotations.UnitTestMethod; +import util.random.RandomGeneratorProvider; +import util.wrappers.MutableInteger; + +public class AT_TestGlobalPropertyId { + + @Test + @UnitTestMethod(target = TestGlobalPropertyId.class,name = "getRandomGlobalPropertyId", args = {RandomGenerator.class}) + public void testGetRandomGlobalPropertyId() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(242770195073333036L); + HashMap idCounter = new HashMap<>(); + Set setOfRandomIds = new LinkedHashSet<>(); + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + idCounter.put(testGlobalPropertyId, new MutableInteger()); + } + + // show that generated values are reasonably unique + for (int i = 0; i < 600; i++) { + TestGlobalPropertyId globalPropertyId = TestGlobalPropertyId.getRandomGlobalPropertyId(randomGenerator); + setOfRandomIds.add(globalPropertyId); + idCounter.get(globalPropertyId).increment(); + } + + for (TestGlobalPropertyId propertyId : idCounter.keySet()) { + assertTrue(idCounter.get(propertyId).getValue() >= 30 && idCounter.get(propertyId).getValue() <= 150); + } + + assertEquals(idCounter.values().stream().mapToInt(a -> a.getValue()).sum(), 600); + assertEquals(setOfRandomIds.size(), 6); + } + + @Test + @UnitTestMethod(target = TestGlobalPropertyId.class,name = "getRandomMutableGlobalPropertyId", args = {RandomGenerator.class}) + public void testGetRandomMutableGlobalPropertyId() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6104930304058715301L); + HashMap idCounter = new HashMap<>(); + Set setOfRandomMutableIds = new LinkedHashSet<>(); + + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + if (testGlobalPropertyId.getPropertyDefinition().propertyValuesAreMutable()) { + idCounter.put(testGlobalPropertyId, new MutableInteger()); + } + } + + // show that generated values are reasonably unique + for (int i = 0; i < 300; i++){ + TestGlobalPropertyId mutableGlobalPropertyId = TestGlobalPropertyId.getRandomMutableGlobalPropertyId(randomGenerator); + setOfRandomMutableIds.add(mutableGlobalPropertyId); + assertTrue(mutableGlobalPropertyId.getPropertyDefinition().propertyValuesAreMutable()); + idCounter.get(mutableGlobalPropertyId).increment(); + } + + for (TestGlobalPropertyId propertyId : idCounter.keySet()) { + assertTrue(idCounter.get(propertyId).getValue() >= 30 && idCounter.get(propertyId).getValue() <= 150); + } + + assertEquals(idCounter.values().stream().mapToInt(a -> a.getValue()).sum(), 300); + assertEquals(setOfRandomMutableIds.size(), 3); + } + + @Test + @UnitTestMethod(target = TestGlobalPropertyId.class,name = "getPropertyDefinition", args = {}) + public void testGetPropertyDefinition() { + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + assertNotNull(testGlobalPropertyId.getPropertyDefinition()); + } + } + + @Test + @UnitTestMethod(target = TestGlobalPropertyId.class,name = "getRandomPropertyValue", args = { RandomGenerator.class }) + public void testGetRandomPropertyValue() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3456870569545355468L); + + /* + * Show that randomly generated values are compatible with the + * associated property definition. Show that the values are reasonably + * unique + */ + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + PropertyDefinition propertyDefinition = testGlobalPropertyId.getPropertyDefinition(); + Set values = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + Object propertyValue = testGlobalPropertyId.getRandomPropertyValue(randomGenerator); + values.add(propertyValue); + assertTrue(propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())); + } + //show that the values are reasonable unique + if (propertyDefinition.getType() != Boolean.class) { + assertTrue(values.size() > 10); + } else { + assertEquals(2, values.size()); + } + } + } + + @Test + @UnitTestMethod(target = TestGlobalPropertyId.class,name = "getUnknownGlobalPropertyId", args = {}) + public void testGetUnknownRegionId() { + Set unknownGlobalPropertyIds = new LinkedHashSet<>(); + for (int i = 0; i < 30; i++) { + GlobalPropertyId unknownGlobalPropertyId = TestGlobalPropertyId.getUnknownGlobalPropertyId(); + assertNotNull(unknownGlobalPropertyId); + boolean unique = unknownGlobalPropertyIds.add(unknownGlobalPropertyId); + assertTrue(unique); + for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { + assertNotEquals(testGlobalPropertyId, unknownGlobalPropertyId); + } + } + } + + @Test + @UnitTestMethod(target = TestGlobalPropertyId.class, name = "getGlobalPropertyIds", args={}) + public void testGetGlobalPropertyIds() { + assertEquals(Arrays.asList(TestGlobalPropertyId.values()), TestGlobalPropertyId.getGlobalPropertyIds()); + } + + @Test + @UnitTestMethod(target = TestGlobalPropertyId.class, name = "getShuffledGlobalPropertyIds", args={RandomGenerator.class}) + public void testGetShuffledGlobalPropertyIds() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(503706897966130759L); + List ids = TestGlobalPropertyId.getGlobalPropertyIds(); + Collections.shuffle(ids, new Random(randomGenerator.nextLong())); + + assertEquals(ids, TestGlobalPropertyId.getShuffledGlobalPropertyIds(RandomGeneratorProvider.getRandomGenerator(503706897966130759L))); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/AT_GroupsPlugin.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/AT_GroupsPlugin.java new file mode 100644 index 000000000..02b818b93 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/AT_GroupsPlugin.java @@ -0,0 +1,98 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.plugins.groups.datamanagers.GroupsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.groups.reports.GroupPopulationReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.groups.reports.GroupPropertyReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePluginId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPluginId; +import util.annotations.UnitTestMethod; + +public class AT_GroupsPlugin { + + @Test + @UnitTestMethod(target = GroupsPlugin.class, name = "builder", args = {}) + public void testBuilder() { + assertNotNull(GroupsPlugin.builder()); + } + + @Test + @UnitTestMethod(target = GroupsPlugin.Builder.class, name = "setGroupPropertyReportPluginData", args = { + GroupPropertyReportPluginData.class }) + public void testSetGroupPropertyReportPluginData() { + GroupsPluginData groupsPluginData = GroupsPluginData.builder().build(); + GroupPropertyReportPluginData groupPropertyReportPluginData = GroupPropertyReportPluginData.builder() + .setReportLabel(new SimpleReportLabel("test")) + .setReportPeriod(ReportPeriod.DAILY) + .build(); + + Plugin groupPlugin = GroupsPlugin.builder() + .setGroupsPluginData(groupsPluginData) + .setGroupPropertyReportPluginData(groupPropertyReportPluginData) + .getGroupsPlugin(); + + assertTrue(groupPlugin.getPluginDatas().contains(groupPropertyReportPluginData)); + } + + @Test + @UnitTestMethod(target = GroupsPlugin.Builder.class, name = "setGroupPopulationReportPluginData", args = { + GroupPopulationReportPluginData.class }) + public void testSetGroupPopulationReportPluginData() { + GroupsPluginData groupsPluginData = GroupsPluginData.builder().build(); + GroupPopulationReportPluginData groupPopulationReportPluginData = GroupPopulationReportPluginData.builder() + .setReportLabel(new SimpleReportLabel("test")) + .setReportPeriod(ReportPeriod.DAILY) + .build(); + + Plugin groupPlugin = GroupsPlugin.builder() + .setGroupsPluginData(groupsPluginData) + .setGroupPopulationReportPluginData(groupPopulationReportPluginData) + .getGroupsPlugin(); + + assertTrue(groupPlugin.getPluginDatas().contains(groupPopulationReportPluginData)); + } + + @Test + @UnitTestMethod(target = GroupsPlugin.Builder.class, name = "setGroupsPluginData", args = { + GroupsPluginData.class }) + public void testSetGroupsPluginData() { + + GroupsPluginData groupsPluginData = GroupsPluginData.builder().build(); + Plugin groupPlugin = GroupsPlugin.builder().setGroupsPluginData(groupsPluginData).getGroupsPlugin(); + + assertTrue(groupPlugin.getPluginDatas().contains(groupsPluginData)); + } + + @Test + @UnitTestMethod(target = GroupsPlugin.Builder.class, name = "getGroupsPlugin", args = {}) + public void testGetGroupsPlugin() { + + GroupsPluginData groupsPluginData = GroupsPluginData.builder().build(); + Plugin groupPlugin = GroupsPlugin.builder().setGroupsPluginData(groupsPluginData).getGroupsPlugin(); + + assertEquals(1, groupPlugin.getPluginDatas().size()); + assertTrue(groupPlugin.getPluginDatas().contains(groupsPluginData)); + + assertEquals(GroupsPluginId.PLUGIN_ID, groupPlugin.getPluginId()); + + Set expectedDependencies = new LinkedHashSet<>(); + expectedDependencies.add(PeoplePluginId.PLUGIN_ID); + expectedDependencies.add(StochasticsPluginId.PLUGIN_ID); + + assertEquals(expectedDependencies, groupPlugin.getPluginDependencies()); + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/AT_GroupsPluginId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/AT_GroupsPluginId.java new file mode 100644 index 000000000..ac19fe287 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/AT_GroupsPluginId.java @@ -0,0 +1,16 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestField; + +public class AT_GroupsPluginId { + + @Test + @UnitTestField(target = GroupsPluginId.class, name = "PLUGIN_ID") + public void testPluginId() { + assertNotNull(GroupsPluginId.PLUGIN_ID); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/datamanagers/AT_GroupsDataManager.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/datamanagers/AT_GroupsDataManager.java new file mode 100644 index 000000000..7a3ea2291 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/datamanagers/AT_GroupsDataManager.java @@ -0,0 +1,5236 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.datamanagers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Random; +import java.util.Set; +import java.util.function.Consumer; + +import org.apache.commons.math3.random.RandomGenerator; +import org.apache.commons.math3.util.Pair; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; +import gov.hhs.aspr.ms.gcm.nucleus.EventFilter; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestOutputConsumer; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupImminentRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupMembershipAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupMembershipRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupPropertyDefinitionEvent; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupPropertyUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupTypeAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupConstructionInfo; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupError; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupId; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupPropertyDefinitionInitialization; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupPropertyValue; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupSampler; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupTypeId; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupWeightingFunction; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.GroupsTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.GroupsTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.TestAuxiliaryGroupPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.TestAuxiliaryGroupTypeId; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.TestGroupPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.TestGroupTypeId; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonConstructionData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; +import util.wrappers.MultiKey; +import util.wrappers.MutableInteger; +import util.wrappers.MutableObject; + +public class AT_GroupsDataManager { + /** + * Demonstrates that the data manager produces plugin data that reflects its + * final state + */ + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testStateFinalization() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(385296335335709376L); + GroupsPluginData.Builder builder = GroupsPluginData.builder(); + GroupsPluginData groupsPluginData = builder.build(); + + // add a property definition + PropertyDefinition propertyDefinition = TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK + .getPropertyDefinition(); + + GroupPropertyDefinitionInitialization groupPropertyDefinitionInitialization = GroupPropertyDefinitionInitialization + .builder().setGroupTypeId(TestGroupTypeId.GROUP_TYPE_1).setPropertyDefinition(propertyDefinition) + .setPropertyId(TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK).build(); + + List expectedGroupIds = new ArrayList<>(); + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // define property definition with the data manager + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.addGroupType(groupPropertyDefinitionInitialization.getGroupTypeId()); + groupsDataManager.defineGroupProperty(groupPropertyDefinitionInitialization); + GroupConstructionInfo groupConstructionInfo = GroupConstructionInfo.builder() + .setGroupTypeId(groupPropertyDefinitionInitialization.getGroupTypeId()) + .setGroupPropertyValue(TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, true) + .build(); + GroupId groupId = groupsDataManager.addGroup(groupConstructionInfo); + expectedGroupIds.add(groupId); + groupsDataManager.setGroupPropertyValue(groupId, groupPropertyDefinitionInitialization.getPropertyId(), + true); + })); + + // show that the plugin data contains what we defined + TestPluginData testPluginData = pluginBuilder.build(); + Long seed = randomGenerator.nextLong(); + Factory factory = GroupsTestPluginFactory.factory(30, 1, 10, seed, testPluginData) + .setGroupsPluginData(groupsPluginData); + TestOutputConsumer testOutputConsumer = TestSimulation.builder().addPlugins(factory.getPlugins()) + .setSimulationHaltTime(2).setProduceSimulationStateOnHalt(true).build().execute(); + Map outputItems = testOutputConsumer.getOutputItemMap(GroupsPluginData.class); + assertEquals(1, outputItems.size()); + GroupsPluginData actualPluginData = outputItems.keySet().iterator().next(); + GroupsPluginData expectedPluginData = GroupsPluginData.builder() + .defineGroupProperty(groupPropertyDefinitionInitialization.getGroupTypeId(), + groupPropertyDefinitionInitialization.getPropertyId(), + groupPropertyDefinitionInitialization.getPropertyDefinition()) + .addGroupTypeId(groupPropertyDefinitionInitialization.getGroupTypeId()) + .addGroup(expectedGroupIds.get(0), groupPropertyDefinitionInitialization.getGroupTypeId()) + .setGroupPropertyValue(expectedGroupIds.get(0), groupPropertyDefinitionInitialization.getPropertyId(), + true) + .build(); + + assertEquals(expectedPluginData, actualPluginData); + + // show that the plugin data persists after multiple actions + PropertyDefinition propertyDefinition2 = TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK + .getPropertyDefinition(); + GroupPropertyDefinitionInitialization groupPropertyDefinitionInitialization2 = GroupPropertyDefinitionInitialization + .builder().setGroupTypeId(TestGroupTypeId.GROUP_TYPE_2).setPropertyDefinition(propertyDefinition2) + .setPropertyId(TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK).build(); + + PropertyDefinition propertyDefinition3 = TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK + .getPropertyDefinition(); + GroupPropertyDefinitionInitialization groupPropertyDefinitionInitialization3 = GroupPropertyDefinitionInitialization + .builder().setGroupTypeId(TestGroupTypeId.GROUP_TYPE_3).setPropertyDefinition(propertyDefinition3) + .setPropertyId(TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK).build(); + + expectedGroupIds.clear(); + pluginBuilder = TestPluginData.builder(); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.addGroupType(groupPropertyDefinitionInitialization2.getGroupTypeId()); + groupsDataManager.defineGroupProperty(groupPropertyDefinitionInitialization2); + GroupConstructionInfo groupConstructionInfo = GroupConstructionInfo.builder() + .setGroupTypeId(groupPropertyDefinitionInitialization2.getGroupTypeId()) + .setGroupPropertyValue(TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 43).build(); + GroupId groupId = groupsDataManager.addGroup(groupConstructionInfo); + expectedGroupIds.add(groupId); + groupsDataManager.setGroupPropertyValue(groupId, groupPropertyDefinitionInitialization2.getPropertyId(), + 43); + groupsDataManager.setGroupPropertyValue(groupId, groupPropertyDefinitionInitialization2.getPropertyId(), + 57); + + groupsDataManager.addGroupType(groupPropertyDefinitionInitialization3.getGroupTypeId()); + groupsDataManager.defineGroupProperty(groupPropertyDefinitionInitialization3); + GroupConstructionInfo groupConstructionInfo2 = GroupConstructionInfo.builder() + .setGroupTypeId(groupPropertyDefinitionInitialization3.getGroupTypeId()) + .setGroupPropertyValue(TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK, 15.9).build(); + GroupId groupId2 = groupsDataManager.addGroup(groupConstructionInfo2); + expectedGroupIds.add(groupId2); + groupsDataManager.setGroupPropertyValue(groupId2, groupPropertyDefinitionInitialization3.getPropertyId(), + 15.9); + groupsDataManager.setGroupPropertyValue(groupId2, groupPropertyDefinitionInitialization3.getPropertyId(), + 34.2); + })); + + testPluginData = pluginBuilder.build(); + seed = randomGenerator.nextLong(); + factory = GroupsTestPluginFactory.factory(30, 1, 10, seed, testPluginData) + .setGroupsPluginData(groupsPluginData); + testOutputConsumer = TestSimulation.builder().addPlugins(factory.getPlugins()).setSimulationHaltTime(2) + .setProduceSimulationStateOnHalt(true).build().execute(); + outputItems = testOutputConsumer.getOutputItemMap(GroupsPluginData.class); + assertEquals(1, outputItems.size()); + actualPluginData = outputItems.keySet().iterator().next(); + expectedPluginData = GroupsPluginData.builder() + .defineGroupProperty(groupPropertyDefinitionInitialization2.getGroupTypeId(), + groupPropertyDefinitionInitialization2.getPropertyId(), + groupPropertyDefinitionInitialization2.getPropertyDefinition()) + .defineGroupProperty(groupPropertyDefinitionInitialization3.getGroupTypeId(), + groupPropertyDefinitionInitialization3.getPropertyId(), + groupPropertyDefinitionInitialization3.getPropertyDefinition()) + .addGroupTypeId(groupPropertyDefinitionInitialization3.getGroupTypeId()) + .addGroupTypeId(groupPropertyDefinitionInitialization2.getGroupTypeId()) + + .addGroup(expectedGroupIds.get(0), groupPropertyDefinitionInitialization2.getGroupTypeId()) + .addGroup(expectedGroupIds.get(1), groupPropertyDefinitionInitialization3.getGroupTypeId()) + .setGroupPropertyValue(expectedGroupIds.get(0), groupPropertyDefinitionInitialization2.getPropertyId(), + 57) + .setGroupPropertyValue(expectedGroupIds.get(1), groupPropertyDefinitionInitialization3.getPropertyId(), + 34.2) + .build(); + assertEquals(expectedPluginData, actualPluginData); + + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "removeGroup", args = { GroupId.class }) + public void testRemoveGroup() { + + Set removedGroups = new LinkedHashSet<>(); + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + // add a group + GroupId groupId = groupsDataManager.addGroup(testGroupTypeId); + + // show that the returned group id is not null + assertNotNull(groupId); + + // show that the manager indicates the group id exists + assertTrue(groupsDataManager.groupExists(groupId)); + + // remove the group + groupsDataManager.removeGroup(groupId); + + removedGroups.add(groupId); + + } + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + // show that the group is no long present + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + for (GroupId groupId : removedGroups) { + assertFalse(groupsDataManager.groupExists(groupId)); + } + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = GroupsTestPluginFactory.factory(30, 3, 5, 8204685090168544876L, testPluginData); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition test: if the group id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 5, 1164752712088660908L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.removeGroup(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); + + // precondition test: if the group id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 5, 6321229743136171684L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.removeGroup(new GroupId(100000)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "removePersonFromGroup", args = { PersonId.class, + GroupId.class }) + public void testRemovePersonFromGroup() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create containers for observations + Set actualObservations = new LinkedHashSet<>(); + Set expectedObservations = new LinkedHashSet<>(); + + // add an agent to observe the group membership additions + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + c.subscribe(EventFilter.builder(GroupMembershipAdditionEvent.class).build(), (c2, e) -> { + actualObservations.add(new MultiKey(e.groupId(), e.personId())); + }); + + })); + + // add an agent to add members to groups + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + List people = peopleDataManager.getPeople(); + Collections.shuffle(people, new Random(randomGenerator.nextLong())); + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + for (GroupId groupId : groupsDataManager.getGroupIds()) { + Set peopleForGroup = new LinkedHashSet<>(groupsDataManager.getPeopleForGroup(groupId)); + int count = 0; + for (PersonId personId : people) { + if (!peopleForGroup.contains(personId)) { + groupsDataManager.addPersonToGroup(personId, groupId); + expectedObservations.add(new MultiKey(groupId, personId)); + count++; + } + if (count == 3) { + break; + } + } + } + + })); + + // have the observer verify that the observations were correct + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(2, (c) -> { + assertEquals(27, expectedObservations.size()); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = GroupsTestPluginFactory.factory(30, 3, 10, 2733223420384068616L, testPluginData); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the person id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 667206327628089405L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); + groupsDataManager.removePersonFromGroup(null, groupId); + + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + /* precondition test: if the person id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 283038490401536931L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); + groupsDataManager.removePersonFromGroup(new PersonId(10000), groupId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + /* precondition test: if the group id is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 6913106996750459497L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); + groupsDataManager.removePersonFromGroup(personId, null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); + + /* precondition test: if the group id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 4632472396816795419L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); + groupsDataManager.removePersonFromGroup(personId, new GroupId(10000)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); + + /* precondition test: if the person is not a member of the group */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 8295961559327801013L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); + PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); + groupsDataManager.removePersonFromGroup(personId, groupId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NON_GROUP_MEMBERSHIP, contractException.getErrorType()); + } + + private static enum ExcludedPersonType { + NULL, MEMBER, NON_MEMBER; + } + + @Test + @UnitTestConstructor(target = GroupsDataManager.class, args = { GroupsPluginData.class }) + public void testConstructor() { + ContractException contractException = assertThrows(ContractException.class, () -> new GroupsDataManager(null)); + assertEquals(GroupError.NULL_GROUP_INITIALIZATION_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "addGroup", args = { GroupConstructionInfo.class }) + public void testAddGroup_GroupConstructionInfo() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create containers for observations + Set expectedGroupObservations = new LinkedHashSet<>(); + Set actualGroupObservations = new LinkedHashSet<>(); + + // have the observer subscribe to group creation + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + c.subscribe(EventFilter.builder(GroupAdditionEvent.class).build(), (c2, e) -> { + actualGroupObservations.add(e.groupId()); + }); + + })); + + // have the agent create add a few groups and collect expected + // observations + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); + + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + GroupConstructionInfo.Builder builder = GroupConstructionInfo.builder(); + builder.setGroupTypeId(testGroupTypeId); + Map expectedPropertyValues = new LinkedHashMap<>(); + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId + .getTestGroupPropertyIds(testGroupTypeId)) { + Object value = testGroupPropertyId.getRandomPropertyValue(randomGenerator); + builder.setGroupPropertyValue(testGroupPropertyId, value); + expectedPropertyValues.put(testGroupPropertyId, value); + } + GroupConstructionInfo groupConstructionInfo = builder.build(); + GroupId groupId = groupsDataManager.addGroup(groupConstructionInfo); + + expectedGroupObservations.add(groupId); + + // show that the group was created, has the correct type and has + // the correct property values + assertTrue(groupsDataManager.groupExists(groupId)); + assertEquals(testGroupTypeId, groupsDataManager.getGroupType(groupId)); + for (TestGroupPropertyId testGroupPropertyId : expectedPropertyValues.keySet()) { + Object expectedValue = expectedPropertyValues.get(testGroupPropertyId); + Object actualValue = groupsDataManager.getGroupPropertyValue(groupId, testGroupPropertyId); + assertEquals(expectedValue, actualValue); + } + + } + })); + + // show that the group creations were observed + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(3, (c) -> { + assertTrue(expectedGroupObservations.size() > 0); + assertEquals(expectedGroupObservations, actualGroupObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = GroupsTestPluginFactory.factory(40, 5.0, 20.0, 5865498314869329641L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition test: if the group construction info is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(40, 5.0, 20.0, 5229546252018518751L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupConstructionInfo nullGroupConstructionInfo = null; + groupsDataManager.addGroup(nullGroupConstructionInfo); + + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NULL_GROUP_CONSTRUCTION_INFO, contractException.getErrorType()); + + /* + * precondition test: if the group type id contained in the group construction + * info is unknown + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(40, 5.0, 20.0, 7404840971962130072L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.addGroup(GroupConstructionInfo.builder() + .setGroupTypeId(TestGroupTypeId.getUnknownGroupTypeId()).build()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); + + /* + * precondition test:if a group property id contained in the group construction + * info is unknown + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(40, 5.0, 20.0, 8782123343145389682L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupConstructionInfo groupConstructionInfo = GroupConstructionInfo.builder()// + .setGroupTypeId(TestGroupTypeId.GROUP_TYPE_1)// + .setGroupPropertyValue(TestGroupPropertyId.getUnknownGroupPropertyId(), 1)// + .build();// + groupsDataManager.addGroup(groupConstructionInfo); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + /* + * precondition test: if a group property value contained in the group + * construction info is incompatible with the corresponding property definition + */ + + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(40, 5.0, 20.0, 8782123343145389682L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupConstructionInfo groupConstructionInfo = GroupConstructionInfo.builder()// + .setGroupTypeId(TestGroupTypeId.GROUP_TYPE_1)// + .setGroupPropertyValue(TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, 1)// + .build();// + groupsDataManager.addGroup(groupConstructionInfo); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "addGroup", args = { GroupTypeId.class }) + public void testAddGroup_GroupTypeId() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create containers for observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // have the observer subscribe to group creation + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + c.subscribe(EventFilter.builder(GroupAdditionEvent.class).build(), (c2, e) -> { + actualObservations.add(e.groupId()); + }); + + })); + + // have the agent create add a few groups and collect expected + // observations + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + + GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); + expectedObservations.add(groupId); + + groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_2); + expectedObservations.add(groupId); + + groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_3); + expectedObservations.add(groupId); + + })); + + // show that the group creations were observed + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(3, (c) -> { + assertEquals(3, expectedObservations.size()); + assertEquals(expectedObservations, actualObservations); + })); + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = GroupsTestPluginFactory.factory(30, 4.0, 10.0, 8137195527612056024L, testPluginData); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition tests + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 4.0, 10.0, 5229546252018518751L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupTypeId groupTypeId = null; + groupsDataManager.addGroup(groupTypeId); + + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); + + // precondition tests + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 4.0, 10.0, 5229546252018518751L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.addGroup(TestGroupTypeId.getUnknownGroupTypeId()); + + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "addPersonToGroup", args = { PersonId.class, + GroupId.class }) + public void testAddPersonToGroup() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create containers for observations + Set actualObservations = new LinkedHashSet<>(); + Set expectedObservations = new LinkedHashSet<>(); + + // add an agent to observe the group membership additions + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + c.subscribe(EventFilter.builder(GroupMembershipAdditionEvent.class).build(), (c2, e) -> { + actualObservations.add(new MultiKey(e.groupId(), e.personId())); + }); + + })); + + // add an agent to add members to groups + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + List people = peopleDataManager.getPeople(); + Collections.shuffle(people, new Random(randomGenerator.nextLong())); + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + for (GroupId groupId : groupsDataManager.getGroupIds()) { + Set peopleForGroup = new LinkedHashSet<>(groupsDataManager.getPeopleForGroup(groupId)); + int count = 0; + + for (PersonId personId : people) { + + if (!peopleForGroup.contains(personId)) { + groupsDataManager.addPersonToGroup(personId, groupId); + assertTrue(groupsDataManager.isPersonInGroup(personId, groupId)); + expectedObservations.add(new MultiKey(groupId, personId)); + count++; + } + if (count == 3) { + break; + } + } + } + })); + + // have the observer verify that the observations were correct + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(2, (c) -> { + assertEquals(27, expectedObservations.size()); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = GroupsTestPluginFactory.factory(30, 3, 10, 2733223420384068616L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition tests: if the person id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 2886293572900391101L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); + groupsDataManager.addPersonToGroup(null, groupId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + // precondition tests: if the person id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 5604775963632692909L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); + groupsDataManager.addPersonToGroup(new PersonId(10000), groupId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + // precondition tests: if the group id is null + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 3853147120254074375L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); + groupsDataManager.addPersonToGroup(personId, null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); + + // precondition tests: if the group id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 7259750239550962667L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); + groupsDataManager.addPersonToGroup(personId, new GroupId(10000)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); + + // precondition tests: if the person is already a member of the group + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 3285943689624298882L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); + PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); + groupsDataManager.addPersonToGroup(personId, groupId); + groupsDataManager.addPersonToGroup(personId, groupId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.DUPLICATE_GROUP_MEMBERSHIP, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "groupExists", args = { GroupId.class }) + public void testGroupExists() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + List removedGroupIds = new ArrayList<>(); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0.0, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + + // add a group and show it exists + GroupId groupId = groupsDataManager.addGroup(testGroupTypeId); + assertTrue(groupsDataManager.groupExists(groupId)); + // remove the group and record it for later verification + groupsDataManager.removeGroup(groupId); + removedGroupIds.add(groupId); + } + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0.0, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + for (GroupId groupId : removedGroupIds) { + // show that the removed groups don't exist + assertFalse(groupsDataManager.groupExists(groupId)); + } + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = GroupsTestPluginFactory.factory(30, 3, 5, 2946647177720026906L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "sampleGroup", args = { GroupId.class, + GroupSampler.class }) + public void testSampleGroup() { + + Consumer consumer = (c) -> { + // establish data views and the lists to groups and people in the + // simulation + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + List groupIds = groupsDataManager.getGroupIds(); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + List people = peopleDataManager.getPeople(); + + /* + * Set up boolean looping over the use of weighting functions in the + * GroupSampler + */ + Set weightingFunctionValues = new LinkedHashSet<>(); + weightingFunctionValues.add(false); + weightingFunctionValues.add(true); + + /* + * Create a weight function that will allow us to exclude about half of the + * people from any group from being selected + */ + GroupWeightingFunction gwf = (c2, p, g) -> { + if (p.getValue() % 2 == 0) { + return 0; + } + return 1.0; + }; + + /* + * Test every group against every excluded person category and use of the + * weighting function + */ + for (GroupId groupId : groupIds) { + for (ExcludedPersonType excludedPersonType : ExcludedPersonType.values()) { + for (Boolean useWeightingFunction : weightingFunctionValues) { + // start building the group sampler + GroupSampler.Builder groupSamplerBuilder = GroupSampler.builder(); + + // Determine the sets of people in and out of the group + List peopleForGroup = groupsDataManager.getPeopleForGroup(groupId); + Set peopleNotInGroupSet = new LinkedHashSet<>(people); + peopleNotInGroupSet.removeAll(peopleForGroup); + List peopleNotInGroupList = new ArrayList<>(peopleNotInGroupSet); + + // Add the weighting function if needed + if (useWeightingFunction) { + groupSamplerBuilder.setGroupWeightingFunction(gwf); + } + + // Add the excluded person based on the category for + // choosing the excluded person + PersonId excludedPersonId = null; + switch (excludedPersonType) { + case MEMBER: + if (!peopleForGroup.isEmpty()) { + excludedPersonId = peopleForGroup.get(randomGenerator.nextInt(peopleForGroup.size())); + } + break; + case NON_MEMBER: + if (!peopleNotInGroupList.isEmpty()) { + excludedPersonId = peopleNotInGroupList + .get(randomGenerator.nextInt(peopleNotInGroupList.size())); + } + break; + case NULL: + break; + default: + throw new RuntimeException("unhandled case " + excludedPersonType); + } + groupSamplerBuilder.setExcludedPersonId(excludedPersonId); + + // build the group sampler + GroupSampler groupSampler = groupSamplerBuilder.build(); + + Set eligiblePeople = new LinkedHashSet<>(); + /* + * If we are using the weighting function, then only select the odd people as + * eligible, otherwise select everyone in the group + */ + if (useWeightingFunction) { + for (PersonId personId : peopleForGroup) { + if (personId.getValue() % 2 == 1) { + eligiblePeople.add(personId); + } + } + } else { + eligiblePeople.addAll(peopleForGroup); + } + + // Remove the excluded person from the eligible people + eligiblePeople.remove(excludedPersonId); + + /* + * If there are no eligible people, then the sampleGroup() method should return + * an empty optional + */ + if (eligiblePeople.isEmpty()) { + Optional optional = groupsDataManager.sampleGroup(groupId, groupSampler); + assertFalse(optional.isPresent()); + } else { + // Draw a reasonable number of people from the group + // and show that they are all eligible people + for (int i = 0; i < eligiblePeople.size(); i++) { + Optional optional = groupsDataManager.sampleGroup(groupId, groupSampler); + assertTrue(optional.isPresent()); + PersonId selectedPersonId = optional.get(); + assertTrue(eligiblePeople.contains(selectedPersonId)); + } + } + + } + } + } + + }; + Factory factory = GroupsTestPluginFactory.factory(30, 3, 5, 9211292135944399530L, consumer); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the group id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 5, 5080244401642933835L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.sampleGroup(null, GroupSampler.builder().build()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); + + /* precondition test: if the group id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 5, 8782123343145389682L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.sampleGroup(new GroupId(1000000), GroupSampler.builder().build()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); + + /* precondition test: if the group sampler is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 5, 4175298436277522063L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.sampleGroup(new GroupId(0), null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NULL_GROUP_SAMPLER, contractException.getErrorType()); + + /* precondition test: if the group sampler is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 5, 7404840971962130072L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.sampleGroup(new GroupId(0), + GroupSampler.builder().setExcludedPersonId(new PersonId(1000000)).build()); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "setGroupPropertyValue", args = { GroupId.class, + GroupPropertyId.class, Object.class }) + public void testSetGroupPropertyValue() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create data structures for observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + c.subscribe(EventFilter.builder(GroupPropertyUpdateEvent.class).build(), (c2, e) -> { + actualObservations.add(new MultiKey(e.groupId(), e.groupPropertyId(), e.previousPropertyValue(), + e.currentPropertyValue())); + + }); + })); + + // have an agent change a few group property values + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + List groupIds = groupsDataManager.getGroupIds(); + + // show that there are some groups + assertTrue(groupIds.size() > 0); + + for (GroupId groupId : groupIds) { + TestGroupTypeId testGroupTypeId = groupsDataManager.getGroupType(groupId); + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId + .getTestGroupPropertyIds(testGroupTypeId)) { + if (testGroupPropertyId.getPropertyDefinition().propertyValuesAreMutable()) { + Object currentValue = groupsDataManager.getGroupPropertyValue(groupId, testGroupPropertyId); + Object expectedValue = testGroupPropertyId.getRandomPropertyValue(randomGenerator); + groupsDataManager.setGroupPropertyValue(groupId, testGroupPropertyId, expectedValue); + Object actualValue = groupsDataManager.getGroupPropertyValue(groupId, testGroupPropertyId); + assertEquals(expectedValue, actualValue); + expectedObservations + .add(new MultiKey(groupId, testGroupPropertyId, currentValue, expectedValue)); + + } + } + } + })); + + // show that the observations of the group property value assignments + // were correct + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(3, (c) -> { + assertEquals(expectedObservations, actualObservations); + })); + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = GroupsTestPluginFactory.factory(100, 3, 5, 4653012806568812031L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test if the group id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 3, 5, 3285943689624298882L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + TestGroupPropertyId testGroupPropertyId = TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK; + groupsDataManager.setGroupPropertyValue(null, testGroupPropertyId, true); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); + + /* precondition test if the group id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 3, 5, 3853147120254074375L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + TestGroupPropertyId testGroupPropertyId = TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK; + groupsDataManager.setGroupPropertyValue(new GroupId(100000), testGroupPropertyId, true); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); + + /* precondition test if the group property id is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 3, 5, 5118884606334935158L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); + groupsDataManager.setGroupPropertyValue(groupId, null, true); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + /* precondition test if the group property id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 3, 5, 6389640203066924425L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); + groupsDataManager.setGroupPropertyValue(groupId, TestGroupPropertyId.getUnknownGroupPropertyId(), true); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + /* precondition test if the property value is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 3, 5, 6323361964403648167L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); + TestGroupPropertyId testGroupPropertyId = TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK; + groupsDataManager.setGroupPropertyValue(groupId, testGroupPropertyId, null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_VALUE, contractException.getErrorType()); + + /* + * precondition test if property value is incompatible with the corresponding + * property definition + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 3, 5, 3728888495166492963L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); + TestGroupPropertyId testGroupPropertyId = TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK; + groupsDataManager.setGroupPropertyValue(groupId, testGroupPropertyId, 5); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); + + /* + * precondition test if the corresponding property definition defines the + * property as immutable + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 3, 5, 7440937277837294440L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_3); + TestGroupPropertyId testGroupPropertyId = TestGroupPropertyId.GROUP_PROPERTY_3_1_BOOLEAN_IMMUTABLE_NO_TRACK; + groupsDataManager.setGroupPropertyValue(groupId, testGroupPropertyId, true); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.IMMUTABLE_VALUE, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getGroupCountForGroupType", args = { GroupTypeId.class }) + public void testGetGroupCountForGroupType() { + + Factory factory = GroupsTestPluginFactory.factory(300, 3, 5, 2910747162784803859L, (c) -> { + + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + List groupIds = groupsDataManager.getGroupIds(); + + // show that there are some groups -- we expect about 180 + assertTrue(groupIds.size() > 100); + + // construct containers to hold expectations and actual counts + Map actualCounts = new LinkedHashMap<>(); + Map expectedCounts = new LinkedHashMap<>(); + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + actualCounts.put(testGroupTypeId, new MutableInteger()); + int count = groupsDataManager.getGroupCountForGroupType(testGroupTypeId); + expectedCounts.put(testGroupTypeId, new MutableInteger(count)); + } + + // poll through the groups and increment the corresponding counters + for (GroupId groupId : groupIds) { + actualCounts.get(groupsDataManager.getGroupType(groupId)).increment(); + } + // show that expectation were met + assertEquals(expectedCounts, actualCounts); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the group type is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(300, 3, 5, 8342387507356594823L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getGroupCountForGroupType(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); + + /* precondition test: if the group type is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(300, 3, 5, 4573510051341354320L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getGroupCountForGroupType(TestGroupTypeId.getUnknownGroupTypeId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getGroupCountForGroupTypeAndPerson", args = { + GroupTypeId.class, PersonId.class }) + public void testGetGroupCountForGroupTypeAndPerson() { + + Factory factory = GroupsTestPluginFactory.factory(100, 0, 5, 6434309925268726988L, (c) -> { + + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + List groupIds = groupsDataManager.getGroupIds(); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + List people = peopleDataManager.getPeople(); + + /* + * Show that there are no groups since we selected 0 groups per person + */ + assertEquals(0, groupIds.size()); + + Map> expectedDataStructure = new LinkedHashMap<>(); + + // create 60 groups + for (int i = 0; i < 60; i++) { + TestGroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + GroupId groupId = groupsDataManager.addGroup(groupTypeId); + groupIds.add(groupId); + } + + /* + * For each person pick three groups at random and add the person to each group, + * recording this in the expected data structure + */ + for (PersonId personId : people) { + Collections.shuffle(groupIds, new Random(randomGenerator.nextLong())); + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + MultiKey multiKey = new MultiKey(testGroupTypeId, personId); + expectedDataStructure.put(multiKey, new LinkedHashSet<>()); + } + + for (int i = 0; i < 3; i++) { + GroupId groupId = groupIds.get(i); + groupsDataManager.addPersonToGroup(personId, groupId); + GroupTypeId groupTypeId = groupsDataManager.getGroupType(groupId); + MultiKey multiKey = new MultiKey(groupTypeId, personId); + Set groups = expectedDataStructure.get(multiKey); + groups.add(groupId); + } + } + + // show that the group ids match the expected group ids + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + for (PersonId personId : people) { + int actualGroupCount = groupsDataManager.getGroupCountForGroupTypeAndPerson(testGroupTypeId, + personId); + MultiKey multiKey = new MultiKey(testGroupTypeId, personId); + int expectedGroupCount = expectedDataStructure.get(multiKey).size(); + assertEquals(expectedGroupCount, actualGroupCount); + } + } + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the group id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 0, 5, 3966867633401336210L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getPeopleForGroupType(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); + + /* precondition test: if the group id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 0, 5, 4582534442214781870L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getPeopleForGroupType(TestGroupTypeId.getUnknownGroupTypeId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getGroupCountForPerson", args = { PersonId.class }) + public void testGetGroupCountForPerson() { + + Factory factory = GroupsTestPluginFactory.factory(300, 3, 5, 6371809280692201768L, (c) -> { + + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + List people = peopleDataManager.getPeople(); + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + + List groupIds = groupsDataManager.getGroupIds(); + + // show that there are some groups -- we expect about 180 + assertTrue(groupIds.size() > 100); + + // construct a container to hold expectations + Map expectedCounts = new LinkedHashMap<>(); + for (PersonId personId : people) { + expectedCounts.put(personId, new MutableInteger()); + } + + // poll through the groups and build the expectations + for (GroupId groupId : groupIds) { + List peopleInGroup = groupsDataManager.getPeopleForGroup(groupId); + for (PersonId personId : peopleInGroup) { + expectedCounts.get(personId).increment(); + } + } + + // show that the counts match the expected counts + for (PersonId personId : people) { + int expectedValue = expectedCounts.get(personId).getValue(); + int actualValue = groupsDataManager.getGroupCountForPerson(personId); + assertEquals(expectedValue, actualValue); + } + + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the person id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(300, 3, 5, 3920152432964044129L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getGroupCountForPerson(null); + + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + /* precondition test: if the person id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(300, 3, 5, 6739633613106510243L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getGroupCountForPerson(new PersonId(10000)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getGroupIds", args = {}) + public void testGetGroupIds() { + + Factory factory = GroupsTestPluginFactory.factory(10, 0, 5, 6455798573295403809L, (c) -> { + + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + + List groupIds = groupsDataManager.getGroupIds(); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + /* + * Show that there are no groups since we selected 0 groups per person + */ + assertEquals(0, groupIds.size()); + Set expectedGroupIds = new LinkedHashSet<>(); + for (int i = 0; i < 10; i++) { + GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.getRandomGroupTypeId(randomGenerator)); + expectedGroupIds.add(groupId); + } + + // show that the group ids match the expected group ids + List actualGroupIds = groupsDataManager.getGroupIds(); + assertEquals(expectedGroupIds.size(), actualGroupIds.size()); + assertEquals(expectedGroupIds, new LinkedHashSet<>(actualGroupIds)); + + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getGroupPropertyDefinition", args = { GroupTypeId.class, + GroupPropertyId.class }) + public void testGetGroupPropertyDefinition() { + + Factory factory = GroupsTestPluginFactory.factory(10, 0, 5, 4462836951642761957L, (c) -> { + + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + + // show that the personGroupDataManger has the expected property + // definitions + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { + PropertyDefinition expectedPropertyDefinition = testGroupPropertyId.getPropertyDefinition(); + PropertyDefinition actualPropertyDefinition = groupsDataManager + .getGroupPropertyDefinition(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId); + assertEquals(expectedPropertyDefinition, actualPropertyDefinition); + } + + // precondition tests + + // if the group type id is null + ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager + .getGroupPropertyDefinition(null, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK)); + assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); + + // if the group type id is unknown + contractException = assertThrows(ContractException.class, + () -> groupsDataManager.getGroupPropertyDefinition(TestGroupTypeId.getUnknownGroupTypeId(), + TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK)); + assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); + + // if the group property id is null + contractException = assertThrows(ContractException.class, + () -> groupsDataManager.getGroupPropertyDefinition(TestGroupTypeId.GROUP_TYPE_1, null)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // if the group property id is unknown + contractException = assertThrows(ContractException.class, + () -> groupsDataManager.getGroupPropertyDefinition(TestGroupTypeId.GROUP_TYPE_1, + TestGroupPropertyId.getUnknownGroupPropertyId())); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + // if the group property id is unknown + contractException = assertThrows(ContractException.class, + () -> groupsDataManager.getGroupPropertyDefinition(TestGroupTypeId.GROUP_TYPE_1, + TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK)); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the group type id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(10, 0, 5, 5959643517439959298L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getGroupPropertyDefinition(null, + TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + + }); + assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); + + /* precondition test: if the group type id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(10, 0, 5, 9138791522018557245L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getGroupPropertyDefinition(TestGroupTypeId.getUnknownGroupTypeId(), + TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); + + /* precondition test: if the group property id is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(10, 0, 5, 9138791522018557245L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getGroupPropertyDefinition(TestGroupTypeId.GROUP_TYPE_1, null); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + /* precondition test: if the group property id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(10, 0, 5, 9138791522018557245L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getGroupPropertyDefinition(TestGroupTypeId.GROUP_TYPE_1, + TestGroupPropertyId.getUnknownGroupPropertyId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + /* precondition tests: if the group property id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(10, 0, 5, 9138791522018557245L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getGroupPropertyDefinition(TestGroupTypeId.GROUP_TYPE_1, + TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getGroupPropertyExists", args = { GroupTypeId.class, + GroupPropertyId.class }) + public void testGetGroupPropertyExists() { + + Factory factory = GroupsTestPluginFactory.factory(10, 0, 5, 8858123829776885259L, (c) -> { + + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + + // show that the personGroupDataManger returns true for the group + // properties that should be present + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { + assertTrue(groupsDataManager.getGroupPropertyExists(testGroupPropertyId.getTestGroupTypeId(), + testGroupPropertyId)); + } + + // show that other group properties do not exist + assertFalse(groupsDataManager.getGroupPropertyExists(null, null)); + assertFalse( + groupsDataManager.getGroupPropertyExists(null, TestGroupPropertyId.getUnknownGroupPropertyId())); + assertFalse(groupsDataManager.getGroupPropertyExists(TestGroupTypeId.getUnknownGroupTypeId(), null)); + assertFalse(groupsDataManager.getGroupPropertyExists(TestGroupTypeId.getUnknownGroupTypeId(), + TestGroupPropertyId.getUnknownGroupPropertyId())); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getGroupPropertyIds", args = { GroupTypeId.class }) + public void testGetGroupPropertyIds() { + + Factory factory = GroupsTestPluginFactory.factory(10, 0, 5, 1205481410658607626L, (c) -> { + + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + + // show that the personGroupDataManger returns the correct group + // property ids + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + Set expectedPropertyIds = TestGroupPropertyId + .getTestGroupPropertyIds(testGroupTypeId); + Set actualPropertyIds = groupsDataManager.getGroupPropertyIds(testGroupTypeId); + assertEquals(expectedPropertyIds, actualPropertyIds); + } + + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the group type id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(10, 0, 5, 8498668590902665283L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getGroupPropertyIds(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); + + /* precondition test: if the group type id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(10, 0, 5, 3809094168724176083L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getGroupPropertyIds(TestGroupTypeId.getUnknownGroupTypeId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getGroupPropertyValue", args = { GroupId.class, + GroupPropertyId.class }) + public void testGetGroupPropertyValue() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + /* + * Create a container to hold our expectations. The MultiKey will be + * (GroupId,GroupPropertyId) pairs and the Object will hold the most recent + * property value. + */ + Map expectedValues = new LinkedHashMap<>(); + + /* + * At time = 1, have the agent establish the expected values. + */ + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + + List groupIds = groupsDataManager.getGroupIds(); + + // show that we have enough groups to conduct the test + assertTrue(groupIds.size() > 10); + + Set mutableTrackablePropertyIds = new LinkedHashSet<>(); + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { + PropertyDefinition propertyDefinition = testGroupPropertyId.getPropertyDefinition(); + if (propertyDefinition.propertyValuesAreMutable()) { + + mutableTrackablePropertyIds.add(testGroupPropertyId); + + } + } + + // show that we have at least one mutable, trackable property + assertTrue(mutableTrackablePropertyIds.size() > 0); + + // Change all the mutable property values and record + // those values. + for (TestGroupPropertyId testGroupPropertyId : mutableTrackablePropertyIds) { + TestGroupTypeId testGroupTypeId = testGroupPropertyId.getTestGroupTypeId(); + List groupsForGroupType = groupsDataManager.getGroupsForGroupType(testGroupTypeId); + for (GroupId groupId : groupsForGroupType) { + Object value = groupsDataManager.getGroupPropertyValue(groupId, testGroupPropertyId); + expectedValues.put(new MultiKey(groupId, testGroupPropertyId), value); + } + } + })); + + /* + * At time = 2, have the agent show that the property values still have their + * expected values and then set those properties to new values. + */ + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + for (MultiKey multiKey : expectedValues.keySet()) { + GroupId groupId = multiKey.getKey(0); + TestGroupPropertyId testGroupPropertyId = multiKey.getKey(1); + Object actualValue = groupsDataManager.getGroupPropertyValue(groupId, testGroupPropertyId); + Object expectedValue = expectedValues.get(multiKey); + assertEquals(expectedValue, actualValue); + + Object newValue = testGroupPropertyId.getRandomPropertyValue(randomGenerator); + groupsDataManager.setGroupPropertyValue(groupId, testGroupPropertyId, newValue); + expectedValues.put(multiKey, newValue); + } + + })); + + /* + * At time = 2, have the agent show that the property values still have their + * expected values. + */ + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(3, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + + for (MultiKey multiKey : expectedValues.keySet()) { + GroupId groupId = multiKey.getKey(0); + TestGroupPropertyId testGroupPropertyId = multiKey.getKey(1); + Object actualValue = groupsDataManager.getGroupPropertyValue(groupId, testGroupPropertyId); + Object expectedValue = expectedValues.get(multiKey); + assertEquals(expectedValue, actualValue); + } + + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = GroupsTestPluginFactory.factory(30, 3, 5, 649112407534985381L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* + * precondition test: if the group id is null + */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 5, 1071603906331418640L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getGroupPropertyValue(null, + TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); + + /* + * precondition test: if the group id is null + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 5, 7115328473763483106L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getGroupPropertyValue(new GroupId(1000000), + TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); + + /* + * precondition test: if the group property id is null + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 5, 2444842488298604050L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getGroupPropertyValue(new GroupId(0), null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + /* + * precondition test: if the group property id is unknown + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 5, 1772465526096544640L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); + groupsDataManager.getGroupPropertyValue(groupId, TestGroupPropertyId.getUnknownGroupPropertyId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + /* + * precondition test: if the group property id is unknown + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 5, 6994832854288891414L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); + groupsDataManager.getGroupPropertyValue(groupId, + TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getGroupsForGroupType", args = { GroupTypeId.class }) + public void testGetGroupsForGroupType() { + + Factory factory = GroupsTestPluginFactory.factory(10, 0, 5, 3948247844369837305L, (c) -> { + + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + List groupIds = groupsDataManager.getGroupIds(); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + /* + * Show that there are no groups since we selected 0 groups per person + */ + assertEquals(0, groupIds.size()); + Map> expectedTypeToGroupIds = new LinkedHashMap<>(); + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + expectedTypeToGroupIds.put(testGroupTypeId, new LinkedHashSet<>()); + } + for (int i = 0; i < 30; i++) { + TestGroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + GroupId groupId = groupsDataManager.addGroup(groupTypeId); + expectedTypeToGroupIds.get(groupTypeId).add(groupId); + } + + // show that the group ids match the expected group ids + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + List actualGroupIds = groupsDataManager.getGroupsForGroupType(testGroupTypeId); + Set expectedGroupIds = expectedTypeToGroupIds.get(testGroupTypeId); + assertEquals(expectedGroupIds.size(), actualGroupIds.size()); + assertEquals(expectedGroupIds, new LinkedHashSet<>(actualGroupIds)); + } + + // precondition tests + + // if the group type id is null + ContractException contractException = assertThrows(ContractException.class, + () -> groupsDataManager.getGroupsForGroupType(null)); + assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); + + // if the group type id is unknown + contractException = assertThrows(ContractException.class, + () -> groupsDataManager.getGroupsForGroupType(TestGroupTypeId.getUnknownGroupTypeId())); + assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); + + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the group type id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(10, 0, 5, 2441670244909950371L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getGroupsForGroupType(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); + + /* precondition test: if the group type id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(10, 0, 5, 8938160844024056358L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getGroupsForGroupType(TestGroupTypeId.getUnknownGroupTypeId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getGroupsForGroupTypeAndPerson", args = { + GroupTypeId.class, PersonId.class }) + public void testGetGroupsForGroupTypeAndPerson() { + + Factory factory = GroupsTestPluginFactory.factory(100, 0, 5, 4847183275886938594L, (c) -> { + + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + List groupIds = groupsDataManager.getGroupIds(); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + List people = peopleDataManager.getPeople(); + + /* + * Show that there are no groups since we selected 0 groups per person + */ + assertEquals(0, groupIds.size()); + + Map> expectedDataStructure = new LinkedHashMap<>(); + + // create 60 groups + for (int i = 0; i < 60; i++) { + TestGroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + GroupId groupId = groupsDataManager.addGroup(groupTypeId); + groupIds.add(groupId); + } + + /* + * For each person pick three groups at random and add the person to each group, + * recording this in the expected data structure + */ + for (PersonId personId : people) { + Collections.shuffle(groupIds, new Random(randomGenerator.nextLong())); + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + MultiKey multiKey = new MultiKey(testGroupTypeId, personId); + expectedDataStructure.put(multiKey, new LinkedHashSet<>()); + } + + for (int i = 0; i < 3; i++) { + GroupId groupId = groupIds.get(i); + groupsDataManager.addPersonToGroup(personId, groupId); + GroupTypeId groupTypeId = groupsDataManager.getGroupType(groupId); + MultiKey multiKey = new MultiKey(groupTypeId, personId); + Set groups = expectedDataStructure.get(multiKey); + groups.add(groupId); + } + } + + // show that the group ids match the expected group ids + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + for (PersonId personId : people) { + List actualGroupIds = groupsDataManager.getGroupsForGroupTypeAndPerson(testGroupTypeId, + personId); + MultiKey multiKey = new MultiKey(testGroupTypeId, personId); + Set expectedGroupIds = expectedDataStructure.get(multiKey); + assertEquals(expectedGroupIds.size(), actualGroupIds.size()); + assertEquals(expectedGroupIds, new LinkedHashSet<>(actualGroupIds)); + } + } + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the person id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 0, 5, 5248499346426314201L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getGroupsForGroupTypeAndPerson(TestGroupTypeId.GROUP_TYPE_1, null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + /* precondition test: if the person id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 0, 5, 1445347293441431961L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getGroupsForGroupTypeAndPerson(TestGroupTypeId.GROUP_TYPE_1, new PersonId(100000)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + /* precondition test: if the group type id is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 0, 5, 1445347293441431961L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getGroupsForGroupTypeAndPerson(null, new PersonId(0)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); + + /* precondition test: if the group type id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 0, 5, 1445347293441431961L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getGroupsForGroupTypeAndPerson(TestGroupTypeId.getUnknownGroupTypeId(), + new PersonId(0)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getGroupsForPerson", args = { PersonId.class }) + public void testGetGroupsForPerson() { + + Factory factory = GroupsTestPluginFactory.factory(100, 0, 5, 1095418957424488372L, (c) -> { + + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + List groupIds = groupsDataManager.getGroupIds(); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + List people = peopleDataManager.getPeople(); + + /* + * Show that there are no groups since we selected 0 groups per person + */ + assertEquals(0, groupIds.size()); + + Map> expectedDataStructure = new LinkedHashMap<>(); + + // create 60 groups + for (int i = 0; i < 60; i++) { + TestGroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + GroupId groupId = groupsDataManager.addGroup(groupTypeId); + groupIds.add(groupId); + } + + /* + * For each person pick three groups at random and add the person to each group, + * recording this in the expected data structure + */ + for (PersonId personId : people) { + Collections.shuffle(groupIds, new Random(randomGenerator.nextLong())); + for (int i = 0; i < 3; i++) { + GroupId groupId = groupIds.get(i); + groupsDataManager.addPersonToGroup(personId, groupId); + Set groups = expectedDataStructure.get(personId); + if (groups == null) { + groups = new LinkedHashSet<>(); + expectedDataStructure.put(personId, groups); + } + groups.add(groupId); + } + } + + // show that the group ids match the expected group ids + + for (PersonId personId : people) { + List actualGroupIds = groupsDataManager.getGroupsForPerson(personId); + Set expectedGroupIds = expectedDataStructure.get(personId); + assertNotNull(expectedGroupIds); + assertEquals(expectedGroupIds.size(), actualGroupIds.size()); + assertEquals(expectedGroupIds, new LinkedHashSet<>(actualGroupIds)); + } + + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition tests: if the person id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 0, 5, 4037186565913379048L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getGroupsForPerson(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + /* precondition tests: if the person id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 0, 5, 5901067879853942202L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getGroupsForPerson(new PersonId(100000)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getGroupType", args = { GroupId.class }) + public void testGetGroupType() { + + Factory factory = GroupsTestPluginFactory.factory(100, 0, 5, 5910635654466929788L, (c) -> { + + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + List groupIds = groupsDataManager.getGroupIds(); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + /* + * Show that there are no groups since we selected 0 groups per person + */ + assertEquals(0, groupIds.size()); + + Map expectedDataStructure = new LinkedHashMap<>(); + + // create 60 groups + for (int i = 0; i < 60; i++) { + TestGroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + GroupId groupId = groupsDataManager.addGroup(groupTypeId); + expectedDataStructure.put(groupId, groupTypeId); + } + + // show that the group have the expected types + for (GroupId groupId : expectedDataStructure.keySet()) { + GroupTypeId actualGroupTypeId = groupsDataManager.getGroupType(groupId); + GroupTypeId expectedGroupTypeId = expectedDataStructure.get(groupId); + assertEquals(expectedGroupTypeId, actualGroupTypeId); + } + + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the group id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 0, 5, 4697608906151940983L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getGroupType(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); + + /* precondition test: if the group id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 0, 5, 5074440747148359344L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getGroupType(new GroupId(100000)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getGroupTypeCountForPersonId", args = { PersonId.class }) + public void testGetGroupTypeCountForPersonId() { + + Factory factory = GroupsTestPluginFactory.factory(100, 0, 5, 1561008711822589907L, (c) -> { + + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + List groupIds = groupsDataManager.getGroupIds(); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + List people = peopleDataManager.getPeople(); + + /* + * Show that there are no groups since we selected 0 groups per person + */ + assertEquals(0, groupIds.size()); + + Map expectedDataStructure = new LinkedHashMap<>(); + + // create 60 groups + TestGroupTypeId groupTypeId = TestGroupTypeId.GROUP_TYPE_1; + for (int i = 0; i < 60; i++) { + GroupId groupId = groupsDataManager.addGroup(groupTypeId); + groupIds.add(groupId); + groupTypeId = groupTypeId.next(); + } + + /* + * For each person pick either one two or three group types and record the + * expected group type count person person. + */ + for (PersonId personId : people) { + int groupTypeCount = randomGenerator.nextInt(3) + 1; + expectedDataStructure.put(personId, groupTypeCount); + groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + for (int i = 0; i < groupTypeCount; i++) { + List groupsForGroupType = groupsDataManager.getGroupsForGroupType(groupTypeId); + GroupId groupId = groupsForGroupType.get(randomGenerator.nextInt(groupsForGroupType.size())); + groupsDataManager.addPersonToGroup(personId, groupId); + groupTypeId = groupTypeId.next(); + } + } + + // show that the group ids match the expected group ids + + for (PersonId personId : people) { + int actualCount = groupsDataManager.getGroupTypeCountForPersonId(personId); + Integer expectedCount = expectedDataStructure.get(personId); + assertEquals(expectedCount.intValue(), actualCount); + } + + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the person id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 0, 5, 2733980118690868605L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getGroupTypeCountForPersonId(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + /* precondition test: if the person id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 0, 5, 7646517978722507404L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getGroupTypeCountForPersonId(new PersonId(100000)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getGroupTypeIds", args = {}) + public void testGetGroupTypeIds() { + Factory factory = GroupsTestPluginFactory.factory(10, 3, 5, 1999263877784730672L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + // show that the group ids match the expected group ids + Set groupTypeIds = groupsDataManager.getGroupTypeIds(); + assertEquals(EnumSet.allOf(TestGroupTypeId.class), groupTypeIds); + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getGroupTypesForPerson", args = { PersonId.class }) + public void testGetGroupTypesForPerson() { + + Factory factory = GroupsTestPluginFactory.factory(100, 0, 5, 2999448198567478958L, (c) -> { + + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + List groupIds = groupsDataManager.getGroupIds(); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + List people = peopleDataManager.getPeople(); + + /* + * Show that there are no groups since we selected 0 groups per person + */ + assertEquals(0, groupIds.size()); + + Map> expectedDataStructure = new LinkedHashMap<>(); + + // create 60 groups + TestGroupTypeId groupTypeId = TestGroupTypeId.GROUP_TYPE_1; + for (int i = 0; i < 60; i++) { + GroupId groupId = groupsDataManager.addGroup(groupTypeId); + groupIds.add(groupId); + groupTypeId = groupTypeId.next(); + } + + /* + * For each person pick either one two or three group types and record the + * expected group type count person person. + */ + for (PersonId personId : people) { + Set groupTypes = new LinkedHashSet<>(); + expectedDataStructure.put(personId, groupTypes); + int groupTypeCount = randomGenerator.nextInt(3) + 1; + groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + for (int i = 0; i < groupTypeCount; i++) { + groupTypes.add(groupTypeId); + List groupsForGroupType = groupsDataManager.getGroupsForGroupType(groupTypeId); + GroupId groupId = groupsForGroupType.get(randomGenerator.nextInt(groupsForGroupType.size())); + groupsDataManager.addPersonToGroup(personId, groupId); + groupTypeId = groupTypeId.next(); + } + } + + // show that the group ids match the expected group ids + + for (PersonId personId : people) { + List actualGroupTypesForPerson = groupsDataManager.getGroupTypesForPerson(personId); + Set expectedGroupTypesForPerson = expectedDataStructure.get(personId); + assertEquals(expectedGroupTypesForPerson.size(), actualGroupTypesForPerson.size()); + assertEquals(expectedGroupTypesForPerson, new LinkedHashSet<>(actualGroupTypesForPerson)); + } + + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition tests if the person id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 0, 5, 5882134079494817898L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getGroupTypesForPerson(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + /* precondition tests if the person id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 0, 5, 4598510399026722120L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getGroupTypesForPerson(new PersonId(100000)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getPeopleForGroup", args = { GroupId.class }) + public void testGetPeopleForGroup() { + + Factory factory = GroupsTestPluginFactory.factory(100, 0, 5, 4550534695972929193L, (c) -> { + + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + + List groupIds = groupsDataManager.getGroupIds(); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + List people = peopleDataManager.getPeople(); + + /* + * Show that there are no groups since we selected 0 groups per person + */ + assertEquals(0, groupIds.size()); + + Map> expectedDataStructure = new LinkedHashMap<>(); + + // create 60 groups + TestGroupTypeId groupTypeId = TestGroupTypeId.GROUP_TYPE_1; + for (int i = 0; i < 60; i++) { + GroupId groupId = groupsDataManager.addGroup(groupTypeId); + groupIds.add(groupId); + groupTypeId = groupTypeId.next(); + expectedDataStructure.put(groupId, new LinkedHashSet<>()); + } + groupIds = new ArrayList<>(expectedDataStructure.keySet()); + + /* + * For each person pick either one two or three group types and record. + */ + for (PersonId personId : people) { + Collections.shuffle(groupIds, new Random(randomGenerator.nextLong())); + int groupCount = randomGenerator.nextInt(3) + 1; + for (int i = 0; i < groupCount; i++) { + GroupId groupId = groupIds.get(i); + groupsDataManager.addPersonToGroup(personId, groupId); + expectedDataStructure.get(groupId).add(personId); + } + } + + // show that the person ids match the expected person ids + + for (GroupId groupId : groupIds) { + List actualPeople = groupsDataManager.getPeopleForGroup(groupId); + Set expectedPeople = expectedDataStructure.get(groupId); + assertEquals(expectedPeople.size(), actualPeople.size()); + assertEquals(expectedPeople, new LinkedHashSet<>(actualPeople)); + } + + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the group id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 0, 5, 1054111866998260759L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getPeopleForGroup(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); + + /* precondition test: if the group id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 0, 5, 976385337250084757L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getPeopleForGroup(new GroupId(100000)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getPeopleForGroupType", args = { GroupTypeId.class }) + public void testGetPeopleForGroupType() { + + Factory factory = GroupsTestPluginFactory.factory(100, 0, 5, 8576174021026036673L, (c) -> { + + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + List groupIds = groupsDataManager.getGroupIds(); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + List people = peopleDataManager.getPeople(); + + /* + * Show that there are no groups since we selected 0 groups per person + */ + assertEquals(0, groupIds.size()); + + Map> expectedDataStructure = new LinkedHashMap<>(); + + // create 60 groups + TestGroupTypeId groupTypeId = TestGroupTypeId.GROUP_TYPE_1; + for (int i = 0; i < 60; i++) { + GroupId groupId = groupsDataManager.addGroup(groupTypeId); + groupIds.add(groupId); + groupTypeId = groupTypeId.next(); + expectedDataStructure.put(groupTypeId, new LinkedHashSet<>()); + } + + /* + * For each person pick either one two or three group types and record. + */ + for (PersonId personId : people) { + Collections.shuffle(groupIds, new Random(randomGenerator.nextLong())); + int groupCount = randomGenerator.nextInt(3) + 1; + for (int i = 0; i < groupCount; i++) { + GroupId groupId = groupIds.get(i); + groupTypeId = groupsDataManager.getGroupType(groupId); + groupsDataManager.addPersonToGroup(personId, groupId); + expectedDataStructure.get(groupTypeId).add(personId); + } + } + + // show that the person ids match the expected person ids + + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + List actualPeople = groupsDataManager.getPeopleForGroupType(testGroupTypeId); + Set expectedPeople = expectedDataStructure.get(testGroupTypeId); + assertEquals(expectedPeople.size(), actualPeople.size()); + assertEquals(expectedPeople, new LinkedHashSet<>(actualPeople)); + } + + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the group id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 0, 5, 3966867633401336210L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getPeopleForGroupType(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); + + /* precondition test: if the group id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 0, 5, 4582534442214781870L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getPeopleForGroupType(TestGroupTypeId.getUnknownGroupTypeId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getPersonCountForGroup", args = { GroupId.class }) + public void testGetPersonCountForGroup() { + + Factory factory = GroupsTestPluginFactory.factory(100, 0, 5, 1763603697244834578L, (c) -> { + + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + + List groupIds = groupsDataManager.getGroupIds(); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + List people = peopleDataManager.getPeople(); + + /* + * Show that there are no groups since we selected 0 groups per person + */ + assertEquals(0, groupIds.size()); + + Map expectedDataStructure = new LinkedHashMap<>(); + + // create 60 groups + TestGroupTypeId groupTypeId = TestGroupTypeId.GROUP_TYPE_1; + for (int i = 0; i < 60; i++) { + GroupId groupId = groupsDataManager.addGroup(groupTypeId); + groupIds.add(groupId); + groupTypeId = groupTypeId.next(); + expectedDataStructure.put(groupId, new MutableInteger()); + } + + /* + * For each person pick either one two or three group types and record. + */ + for (PersonId personId : people) { + Collections.shuffle(groupIds, new Random(randomGenerator.nextLong())); + int groupCount = randomGenerator.nextInt(3) + 1; + for (int i = 0; i < groupCount; i++) { + GroupId groupId = groupIds.get(i); + groupsDataManager.addPersonToGroup(personId, groupId); + expectedDataStructure.get(groupId).increment(); + } + } + + // show that number of people matches expectations + + for (GroupId groupId : groupIds) { + int actualCount = groupsDataManager.getPersonCountForGroup(groupId); + int expectedCount = expectedDataStructure.get(groupId).getValue(); + assertEquals(expectedCount, actualCount); + } + + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the group id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 0, 5, 2981746189003482663L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getPersonCountForGroup(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); + + /* precondition test: if the group id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 0, 5, 3438693482743062795L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getPersonCountForGroup(new GroupId(10000000)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getPersonCountForGroupType", args = { GroupTypeId.class }) + public void testGetPersonCountForGroupType() { + + Factory factory = GroupsTestPluginFactory.factory(100, 0, 5, 5794665230130343350L, (c) -> { + + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + List groupIds = groupsDataManager.getGroupIds(); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + List people = peopleDataManager.getPeople(); + + /* + * Show that there are no groups since we selected 0 groups per person + */ + assertEquals(0, groupIds.size()); + + Map> expectedDataStructure = new LinkedHashMap<>(); + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + expectedDataStructure.put(testGroupTypeId, new LinkedHashSet<>()); + } + + // create 60 groups + TestGroupTypeId groupTypeId = TestGroupTypeId.GROUP_TYPE_1; + + for (int i = 0; i < 60; i++) { + GroupId groupId = groupsDataManager.addGroup(groupTypeId); + groupIds.add(groupId); + groupTypeId = groupTypeId.next(); + } + + /* + * For each person pick either one two or three group types and record. + */ + for (PersonId personId : people) { + Collections.shuffle(groupIds, new Random(randomGenerator.nextLong())); + int groupCount = randomGenerator.nextInt(3) + 1; + for (int i = 0; i < groupCount; i++) { + GroupId groupId = groupIds.get(i); + groupTypeId = groupsDataManager.getGroupType(groupId); + groupsDataManager.addPersonToGroup(personId, groupId); + expectedDataStructure.get(groupTypeId).add(personId); + } + } + + // show that number of people matches expectations + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + int actualCount = groupsDataManager.getPersonCountForGroupType(testGroupTypeId); + int expectedCount = expectedDataStructure.get(testGroupTypeId).size(); + assertEquals(expectedCount, actualCount); + } + + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the group id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 0, 5, 5829408984346963563L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getPersonCountForGroupType(null); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); + + /* precondition test: if the group id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 0, 5, 3769874950212938109L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.getPersonCountForGroupType(TestGroupTypeId.getUnknownGroupTypeId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "groupTypeIdExists", args = { GroupTypeId.class }) + public void testGroupTypeIdExists() { + + Factory factory = GroupsTestPluginFactory.factory(10, 3, 5, 1172766215251823083L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + assertTrue(groupsDataManager.groupTypeIdExists(testGroupTypeId)); + } + assertFalse(groupsDataManager.groupTypeIdExists(TestGroupTypeId.getUnknownGroupTypeId())); + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "isPersonInGroup", args = { PersonId.class, + GroupId.class }) + public void testIsPersonInGroup() { + + Factory factory = GroupsTestPluginFactory.factory(100, 0, 5, 8319627382232144625L, (c) -> { + + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + List groupIds = groupsDataManager.getGroupIds(); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + List people = peopleDataManager.getPeople(); + + /* + * Show that there are no groups since we selected 0 groups per person + */ + assertEquals(0, groupIds.size()); + + Map> expectedDataStructure = new LinkedHashMap<>(); + + // create 60 groups + TestGroupTypeId groupTypeId = TestGroupTypeId.GROUP_TYPE_1; + for (int i = 0; i < 60; i++) { + GroupId groupId = groupsDataManager.addGroup(groupTypeId); + groupIds.add(groupId); + groupTypeId = groupTypeId.next(); + expectedDataStructure.put(groupId, new LinkedHashSet<>()); + } + groupIds = new ArrayList<>(expectedDataStructure.keySet()); + + /* + * For each person pick either one two or three group types and record. + */ + for (PersonId personId : people) { + Collections.shuffle(groupIds, new Random(randomGenerator.nextLong())); + int groupCount = randomGenerator.nextInt(3) + 1; + for (int i = 0; i < groupCount; i++) { + GroupId groupId = groupIds.get(i); + groupsDataManager.addPersonToGroup(personId, groupId); + expectedDataStructure.get(groupId).add(personId); + } + } + + // show that the person ids match the expected person ids + + for (GroupId groupId : groupIds) { + Set expectedPeople = expectedDataStructure.get(groupId); + for (PersonId personId : people) { + if (expectedPeople.contains(personId)) { + assertTrue(groupsDataManager.isPersonInGroup(personId, groupId)); + } else { + assertFalse(groupsDataManager.isPersonInGroup(personId, groupId)); + } + } + } + + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the group id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 0, 5, 3623255510968295889L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.isPersonInGroup(new PersonId(0), null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); + + /* precondition test: if the group id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 0, 5, 825983259283758140L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.isPersonInGroup(new PersonId(0), new GroupId(10000)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); + + /* precondition test: if the person id is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 0, 5, 1009864608566885897L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.isPersonInGroup(null, new GroupId(0)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + /* precondition test: if the person id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 0, 5, 5275459426147794240L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.isPersonInGroup(new PersonId(1000000), new GroupId(0)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testPersonRemovalEvent() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + MutableObject pId = new MutableObject<>(); + + /* + * Have the agent add a person and then remove it. There will be a delay of 0 + * time for the person to be removed. + */ + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + + // add a new person + PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); + pId.setValue(personId); + // place the person in all groups + for (GroupId groupId : groupsDataManager.getGroupIds()) { + groupsDataManager.addPersonToGroup(personId, groupId); + } + + // remove the person + peopleDataManager.removePerson(personId); + + })); + + /* + * Have the agent show that the person is no longer present in the groups + * + */ + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(3, (c) -> { + + GroupsDataManager personGroupDataManager = c.getDataManager(GroupsDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + + // get a list of all the groups + List groupIds = personGroupDataManager.getGroupIds(); + + // get the last added person + PersonId personId = pId.getValue(); + + // show that the person does not exist + assertFalse(peopleDataManager.personExists(personId)); + + // show that none of the groups contain the person + for (GroupId groupId : groupIds) { + List people = personGroupDataManager.getPeopleForGroup(groupId); + assertFalse(people.contains(personId)); + } + + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = GroupsTestPluginFactory.factory(30, 3, 10, 2908277607868593618L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + /** + * Demonstrates that the data manager's initial state reflects its plugin data + */ + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testStateInitialization() { + long seed = 7212690164088198082L; + + int initialPopulation = 40; + double expectedGroupsPerPerson = 3; + double expectedPeoplePerGroup = 5; + + // create a list of people + List people = new ArrayList<>(); + for (int i = 0; i < initialPopulation; i++) { + people.add(new PersonId(i)); + } + + GroupsPluginData groupsPluginData = GroupsTestPluginFactory.getStandardGroupsPluginData(expectedGroupsPerPerson, + expectedPeoplePerGroup, people, seed); + + // add the action plugin + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // add an agent that will demonstrate that the state of the data manager + // reflects the contents of the group plugin data. + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + GroupsDataManager personGroupDataManager = c.getDataManager(GroupsDataManager.class); + + // show the groups are as expected + List actualGroupIds = personGroupDataManager.getGroupIds(); + Set expectedGroupIds = new LinkedHashSet<>(groupsPluginData.getGroupIds()); + assertEquals(expectedGroupIds.size(), actualGroupIds.size()); + assertEquals(expectedGroupIds, new LinkedHashSet<>(actualGroupIds)); + + // show that each group has the expected type + for (GroupId groupId : personGroupDataManager.getGroupIds()) { + GroupTypeId expectedGroupTypeId = groupsPluginData.getGroupTypeId(groupId); + GroupTypeId actualGroupTypeId = personGroupDataManager.getGroupType(groupId); + assertEquals(expectedGroupTypeId, actualGroupTypeId); + } + + // show the group memberships are the same + for (PersonId personId : peopleDataManager.getPeople()) { + int expectedListSize = groupsPluginData.getGroupsForPerson(personId).size(); + Set expectedGroups = new LinkedHashSet<>(groupsPluginData.getGroupsForPerson(personId)); + + int actualListSize = personGroupDataManager.getGroupsForPerson(personId).size(); + Set actualGroups = new LinkedHashSet<>(personGroupDataManager.getGroupsForPerson(personId)); + + assertEquals(expectedListSize, actualListSize); + assertEquals(expectedGroups, actualGroups); + } + + // show that the group types are the same + Set expectedGroupTypeIds = groupsPluginData.getGroupTypeIds(); + Set actualGroupTypeIds = personGroupDataManager.getGroupTypeIds(); + assertEquals(expectedGroupTypeIds, actualGroupTypeIds); + + // show that the property definitions are the same + for (GroupTypeId groupTypeId : personGroupDataManager.getGroupTypeIds()) { + Set expectedGroupPropertyIds = groupsPluginData.getGroupPropertyIds(groupTypeId); + Set actualGroupPropertyIds = personGroupDataManager.getGroupPropertyIds(groupTypeId); + assertEquals(expectedGroupPropertyIds, actualGroupPropertyIds); + for (GroupPropertyId groupPropertyId : actualGroupPropertyIds) { + PropertyDefinition expectedPropertyDefinition = groupsPluginData + .getGroupPropertyDefinition(groupTypeId, groupPropertyId); + PropertyDefinition actualPropertyDefinition = personGroupDataManager + .getGroupPropertyDefinition(groupTypeId, groupPropertyId); + assertEquals(expectedPropertyDefinition, actualPropertyDefinition); + } + } + + // show that the group property values are the same + Map expectedGroupPropertyValues = new LinkedHashMap<>(); + + // fill in the default values + for (GroupId groupId : groupsPluginData.getGroupIds()) { + GroupTypeId groupTypeId = groupsPluginData.getGroupTypeId(groupId); + Set groupPropertyIds = groupsPluginData.getGroupPropertyIds(groupTypeId); + for (GroupPropertyId groupPropertyId : groupPropertyIds) { + PropertyDefinition propertyDefinition = groupsPluginData.getGroupPropertyDefinition(groupTypeId, + groupPropertyId); + Optional optional = propertyDefinition.getDefaultValue(); + if (optional.isPresent()) { + expectedGroupPropertyValues.put(new MultiKey(groupId, groupPropertyId), optional.get()); + } + } + } + // overwrite the default values with the values from the plugin data + for (GroupId groupId : groupsPluginData.getGroupIds()) { + for (GroupPropertyValue groupPropertyValue : groupsPluginData.getGroupPropertyValues(groupId)) { + MultiKey multiKey = new MultiKey(groupId, groupPropertyValue.groupPropertyId()); + expectedGroupPropertyValues.put(multiKey, groupPropertyValue.value()); + } + } + + // retrieve the actual values -- this will include defaults + Map actualGroupPropertyValues = new LinkedHashMap<>(); + for (GroupId groupId : personGroupDataManager.getGroupIds()) { + GroupTypeId groupTypeId = personGroupDataManager.getGroupType(groupId); + Set groupPropertyIds = personGroupDataManager.getGroupPropertyIds(groupTypeId); + for (GroupPropertyId groupPropertyId : groupPropertyIds) { + Object actualValue = personGroupDataManager.getGroupPropertyValue(groupId, groupPropertyId); + MultiKey multiKey = new MultiKey(groupId, groupPropertyId); + actualGroupPropertyValues.put(multiKey, actualValue); + } + } + + assertEquals(expectedGroupPropertyValues, actualGroupPropertyValues); + + })); + + TestPluginData testPluginData = pluginBuilder.build(); + + Factory factory = GroupsTestPluginFactory.factory(initialPopulation, expectedGroupsPerPerson, + expectedPeoplePerGroup, seed, testPluginData); + factory.setGroupsPluginData(groupsPluginData); + // build and execute the engine + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "addGroupType", args = { GroupTypeId.class }) + public void testAddGroupType() { + Set expectedGroupTypeIds = new LinkedHashSet<>(); + Set actualGroupTypeIds = new LinkedHashSet<>(); + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + c.subscribe(EventFilter.builder(GroupTypeAdditionEvent.class).build(), (c2, e) -> { + actualGroupTypeIds.add(e.groupTypeId()); + }); + })); + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + for (TestAuxiliaryGroupTypeId testAuxiliaryGroupTypeId : TestAuxiliaryGroupTypeId.values()) { + expectedGroupTypeIds.add(testAuxiliaryGroupTypeId); + groupsDataManager.addGroupType(testAuxiliaryGroupTypeId); + } + })); + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(2, (c) -> { + assertEquals(expectedGroupTypeIds, actualGroupTypeIds); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = GroupsTestPluginFactory.factory(100, 3, 10, 5324000203933399469L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition test: if the group type id is already present + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 3, 10, 6531281946960607184L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.addGroupType(TestGroupTypeId.GROUP_TYPE_1); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.DUPLICATE_GROUP_TYPE, contractException.getErrorType()); + + // precondition test: if the group type id is null + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 3, 10, 2160259964191783423L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.addGroupType(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "defineGroupProperty", args = { + GroupPropertyDefinitionInitialization.class }) + public void testDefineGroupProperty() { + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // have an observer observe new group property definitions being created + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + c.subscribe(EventFilter.builder(GroupPropertyDefinitionEvent.class).build(), (c2, e) -> { + MultiKey multiKey = new MultiKey(c2.getTime(), e.groupTypeId(), e.groupPropertyId()); + actualObservations.add(multiKey); + }); + })); + + // have an actor add group property definitions + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + for (TestAuxiliaryGroupTypeId testAuxiliaryGroupTypeId : TestAuxiliaryGroupTypeId.values()) { + groupsDataManager.addGroupType(testAuxiliaryGroupTypeId); + for (TestAuxiliaryGroupPropertyId testAuxiliaryGroupPropertyId : TestAuxiliaryGroupPropertyId + .getTestGroupPropertyIds(testAuxiliaryGroupTypeId)) { + PropertyDefinition propertyDefinition = testAuxiliaryGroupPropertyId.getPropertyDefinition(); + GroupPropertyDefinitionInitialization groupPropertyDefinitionInitialization = // + GroupPropertyDefinitionInitialization.builder()// + .setGroupTypeId(testAuxiliaryGroupTypeId)// + .setPropertyId(testAuxiliaryGroupPropertyId)// + .setPropertyDefinition(propertyDefinition)// + .build(); + + groupsDataManager.defineGroupProperty(groupPropertyDefinitionInitialization); + MultiKey multiKey = new MultiKey(c.getTime(), testAuxiliaryGroupTypeId, + testAuxiliaryGroupPropertyId); + expectedObservations.add(multiKey); + PropertyDefinition actualPropertyDefinition = groupsDataManager + .getGroupPropertyDefinition(testAuxiliaryGroupTypeId, testAuxiliaryGroupPropertyId); + assertEquals(propertyDefinition, actualPropertyDefinition); + } + } + })); + + // have the observer verify that the observations were correct + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = GroupsTestPluginFactory.factory(100, 3, 10, 7089101878335134553L, testPluginData); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition test: if the group type id is unknown + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 3, 10, 8347881582083929312L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupTypeId groupTypeId = TestAuxiliaryGroupTypeId.GROUP_AUX_TYPE_1; + GroupPropertyId groupPropertyId = TestAuxiliaryGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK; + PropertyDefinition propertyDefinition = TestAuxiliaryGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK + .getPropertyDefinition(); + GroupPropertyDefinitionInitialization groupPropertyDefinitionInitialization = // + GroupPropertyDefinitionInitialization.builder()// + .setGroupTypeId(groupTypeId)// + .setPropertyId(groupPropertyId)// + .setPropertyDefinition(propertyDefinition)// + .build(); + groupsDataManager.defineGroupProperty(groupPropertyDefinitionInitialization); + + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); + + // precondition test: if the group property id is already known + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 3, 10, 3203453010151124575L, (c) -> { + + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupTypeId groupTypeId = TestAuxiliaryGroupTypeId.GROUP_AUX_TYPE_1; + groupsDataManager.addGroupType(groupTypeId); + GroupPropertyId groupPropertyId = TestAuxiliaryGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK; + PropertyDefinition propertyDefinition = TestAuxiliaryGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK + .getPropertyDefinition(); + GroupPropertyDefinitionInitialization groupPropertyDefinitionInitialization = // + GroupPropertyDefinitionInitialization.builder()// + .setGroupTypeId(groupTypeId)// + .setPropertyId(groupPropertyId)// + .setPropertyDefinition(propertyDefinition)// + .build(); + + groupsDataManager.defineGroupProperty(groupPropertyDefinitionInitialization); + groupsDataManager.defineGroupProperty(groupPropertyDefinitionInitialization); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.DUPLICATE_PROPERTY_DEFINITION, contractException.getErrorType()); + + /* + * precondition test: if the groupPropertyDefinitionInitialization contains a + * property assignment for a group that does not exist. + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(0, 3, 10, 1757700723640970863L, (c) -> { + + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupTypeId groupTypeId = TestAuxiliaryGroupTypeId.GROUP_AUX_TYPE_1; + groupsDataManager.addGroupType(groupTypeId); + GroupPropertyId groupPropertyId = TestAuxiliaryGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK; + PropertyDefinition propertyDefinition = TestAuxiliaryGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK + .getPropertyDefinition(); + GroupPropertyDefinitionInitialization groupPropertyDefinitionInitialization = // + GroupPropertyDefinitionInitialization.builder()// + .setGroupTypeId(groupTypeId)// + .setPropertyId(groupPropertyId)// + .setPropertyDefinition(propertyDefinition)// + .addPropertyValue(new GroupId(0), true)// + .build(); + groupsDataManager.defineGroupProperty(groupPropertyDefinitionInitialization); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + + }); + assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); + + /* + * if the groupPropertyDefinitionInitialization contains a property assignment + * for a group that is not of the correct group type. + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 3, 10, 8541542687515887761L, (c) -> { + + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.addGroupType(TestAuxiliaryGroupTypeId.GROUP_AUX_TYPE_2); + GroupId groupId = groupsDataManager.addGroup(TestAuxiliaryGroupTypeId.GROUP_AUX_TYPE_2); + GroupTypeId groupTypeId = TestAuxiliaryGroupTypeId.GROUP_AUX_TYPE_1; + groupsDataManager.addGroupType(groupTypeId); + GroupPropertyId groupPropertyId = TestAuxiliaryGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK; + PropertyDefinition propertyDefinition = TestAuxiliaryGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK + .getPropertyDefinition(); + GroupPropertyDefinitionInitialization groupPropertyDefinitionInitialization = // + GroupPropertyDefinitionInitialization.builder()// + .setGroupTypeId(groupTypeId)// + .setPropertyId(groupPropertyId)// + .setPropertyDefinition(propertyDefinition)// + .addPropertyValue(groupId, true)// + .build(); + groupsDataManager.defineGroupProperty(groupPropertyDefinitionInitialization); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.INCORRECT_GROUP_TYPE_ID, contractException.getErrorType()); + + /* + * precondition test: if the groupPropertyDefinitionInitialization does not + * contain property value assignments for every extant group when the property + * definition does not contain a default value + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(0, 3, 10, 244590355339669479L, (c) -> { + + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupTypeId groupTypeId = TestAuxiliaryGroupTypeId.GROUP_AUX_TYPE_1; + groupsDataManager.addGroupType(groupTypeId); + groupsDataManager.addGroup(TestAuxiliaryGroupTypeId.GROUP_AUX_TYPE_1); + GroupPropertyId groupPropertyId = TestAuxiliaryGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK; + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Boolean.class).build(); + GroupPropertyDefinitionInitialization groupPropertyDefinitionInitialization = // + GroupPropertyDefinitionInitialization.builder()// + .setGroupTypeId(groupTypeId)// + .setPropertyId(groupPropertyId)// + .setPropertyDefinition(propertyDefinition)// + .build(); + groupsDataManager.defineGroupProperty(groupPropertyDefinitionInitialization); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getEventFilterForGroupAdditionEvent", args = { + GroupTypeId.class }) + public void testGetEventFilterForGroupAdditionEvent_GroupType() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create containers for observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + Set selectedGroupTypes = new LinkedHashSet<>(); + selectedGroupTypes.add(TestGroupTypeId.GROUP_TYPE_1); + selectedGroupTypes.add(TestGroupTypeId.GROUP_TYPE_2); + + // have the observer subscribe to group creation for the selected groups + // types + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + for (TestGroupTypeId testGroupTypeId : selectedGroupTypes) { + EventFilter eventFilter = groupsDataManager + .getEventFilterForGroupAdditionEvent(testGroupTypeId); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(e.groupId()); + }); + } + })); + + // have the actor create add a few groups and collect expected + // observations + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + for (int i = 0; i < 100; i++) { + TestGroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + GroupId groupId = groupsDataManager.addGroup(groupTypeId); + if (selectedGroupTypes.contains(groupTypeId)) { + expectedObservations.add(groupId); + } + } + + })); + + // show that the group creations were observed + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(3, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = GroupsTestPluginFactory.factory(30, 4.0, 10.0, 5589772229734037226L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition test: if the group type id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 4.0, 10.0, 7641347481169234356L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupTypeId groupTypeId = null; + groupsDataManager.getEventFilterForGroupAdditionEvent(groupTypeId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); + + // precondition tests: if the group type id is not known + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 4.0, 10.0, 5165611005555046251L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupTypeId groupTypeId = TestGroupTypeId.getUnknownGroupTypeId(); + groupsDataManager.getEventFilterForGroupAdditionEvent(groupTypeId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getEventFilterForGroupAdditionEvent", args = {}) + public void testGetEventFilterForGroupAdditionEvent() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create containers for observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // have the observer subscribe to group creation for the selected groups + // types + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + + EventFilter eventFilter = groupsDataManager.getEventFilterForGroupAdditionEvent(); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(e.groupId()); + }); + + })); + + // have the actor create add a few groups and collect expected + // observations + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + for (int i = 0; i < 100; i++) { + TestGroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + GroupId groupId = groupsDataManager.addGroup(groupTypeId); + expectedObservations.add(groupId); + } + + })); + + // show that the group creations were observed + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(3, (c) -> { + assertEquals(100, expectedObservations.size()); + assertEquals(expectedObservations, actualObservations); + })); + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = GroupsTestPluginFactory.factory(30, 4.0, 10.0, 4873414306435646846L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition test: if the group type id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 4.0, 10.0, 1195149554612948377L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupTypeId groupTypeId = null; + groupsDataManager.getEventFilterForGroupAdditionEvent(groupTypeId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); + + // precondition tests: if the group type id is not known + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 4.0, 10.0, 4200302716872534102L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupTypeId groupTypeId = TestGroupTypeId.getUnknownGroupTypeId(); + groupsDataManager.getEventFilterForGroupAdditionEvent(groupTypeId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getEventFilterForGroupImminentRemovalEvent", args = { + GroupTypeId.class }) + public void testGetEventFilterForGroupImminentRemovalEvent_GroupType() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + Set selectedGroupTypes = new LinkedHashSet<>(); + selectedGroupTypes.add(TestGroupTypeId.GROUP_TYPE_1); + selectedGroupTypes.add(TestGroupTypeId.GROUP_TYPE_2); + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // have an actor observe imminent group removals for the selected group + // types + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + for (TestGroupTypeId testGroupTypeId : selectedGroupTypes) { + EventFilter eventFilter = groupsDataManager + .getEventFilterForGroupImminentRemovalEvent(testGroupTypeId); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c.getTime(), e.groupId())); + }); + } + })); + + int comparisonDay = 100; + + // have an actor add a few new groups and immediately remove them + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + for (int i = 1; i < comparisonDay; i++) { + c.addPlan((c2) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + TestGroupTypeId testGroupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + GroupId groupId = groupsDataManager.addGroup(testGroupTypeId); + GroupTypeId groupTypeId = groupsDataManager.getGroupType(groupId); + groupsDataManager.removeGroup(groupId); + if (selectedGroupTypes.contains(groupTypeId)) { + expectedObservations.add(new MultiKey(c2.getTime(), groupId)); + } + }, i); + } + })); + + // have the observer show that the expected and actual observations are + // the same + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(comparisonDay, (c) -> { + assertFalse(expectedObservations.isEmpty()); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = GroupsTestPluginFactory.factory(30, 3, 5, 5220753097952239863L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition test: if the group type id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 5, 9054394261904590543L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupTypeId groupTypeId = null; + groupsDataManager.getEventFilterForGroupImminentRemovalEvent(groupTypeId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); + + // precondition test: if the group type id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 5, 1762165471886047056L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupTypeId groupTypeId = TestGroupTypeId.getUnknownGroupTypeId(); + groupsDataManager.getEventFilterForGroupImminentRemovalEvent(groupTypeId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getEventFilterForGroupImminentRemovalEvent", args = { + GroupId.class }) + public void testGetEventFilterForGroupImminentRemovalEvent_GroupId() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + List selectedGroups = new ArrayList<>(); + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + int groupCount = 30; + + // have an actor add a few new groups, selecting about half for later + // removal + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + for (int i = 0; i < groupCount; i++) { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + TestGroupTypeId testGroupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + GroupId groupId = groupsDataManager.addGroup(testGroupTypeId); + if (randomGenerator.nextBoolean()) { + selectedGroups.add(groupId); + } + } + })); + + // have an actor observe imminent group removals for the selected group + // ids + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(1, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + for (GroupId groupId : selectedGroups) { + + EventFilter eventFilter = groupsDataManager + .getEventFilterForGroupImminentRemovalEvent(groupId); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(e.groupId()); + }); + } + })); + + // have an actor remove all the groups + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + List groupIds = groupsDataManager.getGroupIds(); + for (GroupId groupId : groupIds) { + if (selectedGroups.contains(groupId)) { + expectedObservations.add(groupId); + } + groupsDataManager.removeGroup(groupId); + } + })); + + // have the observer show that the expected and actual observations are + // the same + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(3, (c) -> { + assertFalse(expectedObservations.isEmpty()); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = GroupsTestPluginFactory.factory(30, 3, 5, 8387884383247064182L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition test: if the group id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 5, 4632329546403944029L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = null; + groupsDataManager.getEventFilterForGroupImminentRemovalEvent(groupId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); + + // precondition test: if the group id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 5, 6422007986358180059L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = new GroupId(100000); + groupsDataManager.getEventFilterForGroupImminentRemovalEvent(groupId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getEventFilterForGroupImminentRemovalEvent", args = {}) + public void testGetEventFilterForGroupImminentRemovalEvent() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + int groupCount = 30; + + // have an actor add a few new groups, selecting about half for later + // removal + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + for (int i = 0; i < groupCount; i++) { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + TestGroupTypeId testGroupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + groupsDataManager.addGroup(testGroupTypeId); + } + })); + + // have an actor observe imminent group removals + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(1, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + EventFilter eventFilter = groupsDataManager + .getEventFilterForGroupImminentRemovalEvent(); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(e.groupId()); + }); + })); + + // have an actor remove all the groups + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + List groupIds = groupsDataManager.getGroupIds(); + for (GroupId groupId : groupIds) { + if (randomGenerator.nextBoolean()) { + expectedObservations.add(groupId); + groupsDataManager.removeGroup(groupId); + } + } + })); + + // have the observer show that the expected and actual observations are + // the same + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(3, (c) -> { + assertFalse(expectedObservations.isEmpty()); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = GroupsTestPluginFactory.factory(30, 3, 5, 5769859947365341767L, testPluginData); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getEventFilterForGroupMembershipAdditionEvent", args = { + GroupId.class }) + public void testGetEventFilterForGroupMembershipAdditionEvent_Group() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create containers for observations + Set actualObservations = new LinkedHashSet<>(); + Set expectedObservations = new LinkedHashSet<>(); + + Set selectedGroups = new LinkedHashSet<>(); + + int groupCount = 20; + /* + * have the actor create some groups and selected about half of them they will + * then be used for filtering membership addition observations + */ + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + for (int i = 0; i < groupCount; i++) { + TestGroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + GroupId groupId = groupsDataManager.addGroup(groupTypeId); + if (i % 2 == 0) { + selectedGroups.add(groupId); + } + } + })); + + // add an agent to observe the group membership additions to the + // selected groups + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(1, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + for (GroupId groupId : selectedGroups) { + EventFilter eventFilter = groupsDataManager + .getEventFilterForGroupMembershipAdditionEvent(groupId); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.groupId(), e.personId())); + }); + } + })); + + int comparisonDay = 100; + + // have the actor plan the addition of people to groups + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(3, (c) -> { + + for (int i = 3; i < comparisonDay; i++) { + c.addPlan((c2) -> { + RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class) + .getRandomGenerator(); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + + // create a person + PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); + + // select a group + List groupIds = groupsDataManager.getGroupIds(); + GroupId groupId = groupIds.get(randomGenerator.nextInt(groupIds.size())); + + // add the person to the group + groupsDataManager.addPersonToGroup(personId, groupId); + + if (selectedGroups.contains(groupId)) { + expectedObservations.add(new MultiKey(c2.getTime(), groupId, personId)); + } + }, i); + } + + })); + + // have the observer verify that the observations were correct + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(comparisonDay, (c) -> { + assertFalse(expectedObservations.isEmpty()); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = GroupsTestPluginFactory.factory(0, 3, 10, 4356365020352320873L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition tests: if the group id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 3554135401743252689L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = null; + groupsDataManager.getEventFilterForGroupMembershipAdditionEvent(groupId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); + + // precondition tests: if the group id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 7801862262246131770L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = new GroupId(1000000); + groupsDataManager.getEventFilterForGroupMembershipAdditionEvent(groupId); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getEventFilterForGroupMembershipAdditionEvent", args = { + GroupId.class, PersonId.class }) + public void testGetEventFilterForGroupMembershipAdditionEvent_Group_Person() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create containers for observations + Set actualObservations = new LinkedHashSet<>(); + Set expectedObservations = new LinkedHashSet<>(); + + Set> selectedPairs = new LinkedHashSet<>(); + Set> nonSelectedPairs = new LinkedHashSet<>(); + + int groupCount = 20; + int peopleCount = 5; + /* + * have the actor create some groups and selected about half of them they will + * then be used for filtering membership addition observations + */ + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + for (int i = 0; i < groupCount; i++) { + TestGroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + GroupId groupId = groupsDataManager.addGroup(groupTypeId); + for (int j = 0; j < peopleCount; j++) { + PersonId selectedPersonId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); + PersonId nonselectedPersonId = peopleDataManager + .addPerson(PersonConstructionData.builder().build()); + selectedPairs.add(new Pair<>(groupId, selectedPersonId)); + nonSelectedPairs.add(new Pair<>(groupId, nonselectedPersonId)); + } + } + })); + + // add an agent to observe the group membership additions to the + // selected pairs + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(1, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + for (Pair pair : selectedPairs) { + GroupId groupId = pair.getFirst(); + PersonId personId = pair.getSecond(); + EventFilter eventFilter = groupsDataManager + .getEventFilterForGroupMembershipAdditionEvent(groupId, personId); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.groupId(), e.personId())); + }); + } + })); + + // have the actor plan the addition of people to groups + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(3, (c) -> { + + RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + + List> totalPairs = new ArrayList<>(); + totalPairs.addAll(selectedPairs); + totalPairs.addAll(nonSelectedPairs); + + Collections.shuffle(totalPairs, new Random(randomGenerator.nextLong())); + for (Pair pair : totalPairs) { + GroupId groupId = pair.getFirst(); + PersonId personId = pair.getSecond(); + groupsDataManager.addPersonToGroup(personId, groupId); + + if (selectedPairs.contains(pair)) { + expectedObservations.add(new MultiKey(c.getTime(), groupId, personId)); + } + } + })); + + // have the observer verify that the observations were correct + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(4, (c) -> { + assertFalse(expectedObservations.isEmpty()); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = GroupsTestPluginFactory.factory(0, 3, 10, 4356365020352320873L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition tests: if the group id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 3554135401743252689L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = null; + PersonId personId = new PersonId(0); + groupsDataManager.getEventFilterForGroupMembershipAdditionEvent(groupId, personId); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); + + // precondition tests: if the group id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 7801862262246131770L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = new GroupId(1000000); + PersonId personId = new PersonId(0); + groupsDataManager.getEventFilterForGroupMembershipAdditionEvent(groupId, personId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); + + // precondition tests: if the person id is null + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 3554135401743252689L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = new GroupId(0); + PersonId personId = null; + groupsDataManager.getEventFilterForGroupMembershipAdditionEvent(groupId, personId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + // precondition tests: if the person id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 3554135401743252689L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = new GroupId(0); + PersonId personId = new PersonId(1000000); + groupsDataManager.getEventFilterForGroupMembershipAdditionEvent(groupId, personId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getEventFilterForGroupMembershipAdditionEvent", args = { + GroupTypeId.class }) + public void testGetEventFilterForGroupMembershipAdditionEvent_GroupType() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create containers for observations + Set actualObservations = new LinkedHashSet<>(); + Set expectedObservations = new LinkedHashSet<>(); + + Set selectedGroupTypes = new LinkedHashSet<>(); + selectedGroupTypes.add(TestGroupTypeId.GROUP_TYPE_1); + selectedGroupTypes.add(TestGroupTypeId.GROUP_TYPE_2); + + // add an agent to observe the group membership additions to the + // selected groups + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + for (GroupTypeId groupTypeId : selectedGroupTypes) { + EventFilter eventFilter = groupsDataManager + .getEventFilterForGroupMembershipAdditionEvent(groupTypeId); + + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(e.groupId(), e.personId())); + + }); + } + })); + + int groupCount = 100; + /* + * have the actor create some groups and add some people to them + */ + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + for (int i = 0; i < groupCount; i++) { + TestGroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + GroupId groupId = groupsDataManager.addGroup(groupTypeId); + + for (int j = 0; j < 3; j++) { + PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); + groupsDataManager.addPersonToGroup(personId, groupId); + if (selectedGroupTypes.contains(groupTypeId)) { + expectedObservations.add(new MultiKey(groupId, personId)); + } + } + + } + })); + + // have the observer verify that the observations were correct + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(2, (c) -> { + assertFalse(expectedObservations.isEmpty()); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = GroupsTestPluginFactory.factory(0, 3, 10, 8388981611967284165L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition tests: if the group type id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 8821737193954784979L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupTypeId groupTypeId = null; + groupsDataManager.getEventFilterForGroupMembershipAdditionEvent(groupTypeId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); + + // precondition tests: if the group type id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 8185554283901963798L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupTypeId groupTypeId = TestGroupTypeId.getUnknownGroupTypeId(); + groupsDataManager.getEventFilterForGroupMembershipAdditionEvent(groupTypeId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getEventFilterForGroupMembershipAdditionEvent", args = { + GroupTypeId.class, PersonId.class }) + public void testGetEventFilterForGroupMembershipAdditionEvent_GroupType_Person() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create containers for observations + Set actualObservations = new LinkedHashSet<>(); + Set expectedObservations = new LinkedHashSet<>(); + + Set selectedGroupTypes = new LinkedHashSet<>(); + selectedGroupTypes.add(TestGroupTypeId.GROUP_TYPE_1); + selectedGroupTypes.add(TestGroupTypeId.GROUP_TYPE_2); + Set> selectedPairs = new LinkedHashSet<>(); + Set> nonSelectedPairs = new LinkedHashSet<>(); + + int groupCount = 100; + int peopleCount = 5; + /* + * have the actor create some groups and people who will eventually move into + * those groups + */ + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + for (int i = 0; i < groupCount; i++) { + TestGroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + GroupId groupId = groupsDataManager.addGroup(groupTypeId); + for (int j = 0; j < peopleCount; j++) { + PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); + if (selectedGroupTypes.contains(groupTypeId)) { + selectedPairs.add(new Pair<>(groupId, personId)); + } else { + nonSelectedPairs.add(new Pair<>(groupId, personId)); + } + } + } + })); + + // add an agent to observe the group membership additions to the + // selected pairs by way of the group type id + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(1, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + for (Pair pair : selectedPairs) { + GroupId groupId = pair.getFirst(); + PersonId personId = pair.getSecond(); + GroupTypeId groupTypeId = groupsDataManager.getGroupType(groupId); + EventFilter eventFilter = groupsDataManager + .getEventFilterForGroupMembershipAdditionEvent(groupTypeId, personId); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.groupId(), e.personId())); + }); + } + })); + + // have the actor add the people to the groups + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(3, (c) -> { + + RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + + List> totalPairs = new ArrayList<>(); + totalPairs.addAll(selectedPairs); + totalPairs.addAll(nonSelectedPairs); + + Collections.shuffle(totalPairs, new Random(randomGenerator.nextLong())); + for (Pair pair : totalPairs) { + GroupId groupId = pair.getFirst(); + PersonId personId = pair.getSecond(); + groupsDataManager.addPersonToGroup(personId, groupId); + + if (selectedPairs.contains(pair)) { + expectedObservations.add(new MultiKey(c.getTime(), groupId, personId)); + } + } + })); + + // have the observer verify that the observations were correct + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(4, (c) -> { + assertFalse(expectedObservations.isEmpty()); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = GroupsTestPluginFactory.factory(0, 3, 10, 4289325374116700754L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition tests: if the group type id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 8053944455114188764L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupTypeId groupTypeId = null; + PersonId personId = new PersonId(0); + groupsDataManager.getEventFilterForGroupMembershipAdditionEvent(groupTypeId, personId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); + + // precondition tests: if the group id type is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 2656849630874291785L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupTypeId groupTypeId = TestGroupTypeId.getUnknownGroupTypeId(); + PersonId personId = new PersonId(0); + groupsDataManager.getEventFilterForGroupMembershipAdditionEvent(groupTypeId, personId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); + + // precondition tests: if the person id is null + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 4303859622582466624L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = new GroupId(0); + PersonId personId = null; + groupsDataManager.getEventFilterForGroupMembershipAdditionEvent(groupId, personId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + // precondition tests: if the person id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 763446688943355921L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = new GroupId(0); + PersonId personId = new PersonId(1000000); + groupsDataManager.getEventFilterForGroupMembershipAdditionEvent(groupId, personId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getEventFilterForGroupMembershipAdditionEvent", args = { + PersonId.class }) + public void testGetEventFilterForGroupMembershipAdditionEvent_Person() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create containers for observations + Set actualObservations = new LinkedHashSet<>(); + Set expectedObservations = new LinkedHashSet<>(); + + Set> selectedPairs = new LinkedHashSet<>(); + Set> nonSelectedPairs = new LinkedHashSet<>(); + + int groupCount = 100; + int peopleCount = 5; + /* + * have the actor create some groups and people who will eventually move into + * those groups + */ + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + for (int i = 0; i < groupCount; i++) { + TestGroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + GroupId groupId = groupsDataManager.addGroup(groupTypeId); + for (int j = 0; j < peopleCount; j++) { + PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); + selectedPairs.add(new Pair<>(groupId, personId)); + personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); + nonSelectedPairs.add(new Pair<>(groupId, personId)); + } + } + })); + + // add an agent to observe the group membership additions to the + // selected pairs by way of the group type id + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(1, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + for (Pair pair : selectedPairs) { + PersonId personId = pair.getSecond(); + EventFilter eventFilter = groupsDataManager + .getEventFilterForGroupMembershipAdditionEvent(personId); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.groupId(), e.personId())); + }); + } + })); + + // have the actor add the people to the groups + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(3, (c) -> { + + RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + + List> totalPairs = new ArrayList<>(); + totalPairs.addAll(selectedPairs); + totalPairs.addAll(nonSelectedPairs); + + Collections.shuffle(totalPairs, new Random(randomGenerator.nextLong())); + for (Pair pair : totalPairs) { + GroupId groupId = pair.getFirst(); + PersonId personId = pair.getSecond(); + groupsDataManager.addPersonToGroup(personId, groupId); + if (selectedPairs.contains(pair)) { + expectedObservations.add(new MultiKey(c.getTime(), groupId, personId)); + } + } + })); + + // have the observer verify that the observations were correct + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(4, (c) -> { + assertFalse(expectedObservations.isEmpty()); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = GroupsTestPluginFactory.factory(0, 3, 10, 7894583767324913975L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition tests: if the person id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 8185554283901963798L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + PersonId personId = null; + groupsDataManager.getEventFilterForGroupMembershipAdditionEvent(personId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + // precondition tests: if the person id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 8821737193954784979L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + PersonId personId = new PersonId(1000000); + groupsDataManager.getEventFilterForGroupMembershipAdditionEvent(personId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getEventFilterForGroupMembershipAdditionEvent", args = {}) + public void testGetEventFilterForGroupMembershipAdditionEvent() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create containers for observations + Set actualObservations = new LinkedHashSet<>(); + Set expectedObservations = new LinkedHashSet<>(); + + // add an agent to observe the group membership additions to the + // selected pairs by way of the group type id + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + EventFilter eventFilter = groupsDataManager + .getEventFilterForGroupMembershipAdditionEvent(); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.groupId(), e.personId())); + }); + })); + + /* + * have the actor create some groups and people + */ + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + for (int i = 0; i < 100; i++) { + TestGroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + GroupId groupId = groupsDataManager.addGroup(groupTypeId); + for (int j = 0; j < 10; j++) { + PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); + groupsDataManager.addPersonToGroup(personId, groupId); + expectedObservations.add(new MultiKey(c.getTime(), groupId, personId)); + } + } + })); + + // have the observer verify that the observations were correct + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(4, (c) -> { + assertFalse(expectedObservations.isEmpty()); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = GroupsTestPluginFactory.factory(0, 3, 10, 8388981611967284165L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getEventFilterForGroupMembershipRemovalEvent", args = { + GroupId.class }) + public void testGetEventFilterForGroupMembershipRemovalEvent_Group() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create containers for observations + Set actualObservations = new LinkedHashSet<>(); + Set expectedObservations = new LinkedHashSet<>(); + + Set selectedGroups = new LinkedHashSet<>(); + + int groupCount = 20; + /* + * have the actor create some groups and selected about half of them they will + * then be used for filtering membership removal observations + */ + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + for (int i = 0; i < groupCount; i++) { + TestGroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + GroupId groupId = groupsDataManager.addGroup(groupTypeId); + if (i % 2 == 0) { + selectedGroups.add(groupId); + } + } + })); + + // add an agent to observe the group membership removals to the + // selected groups + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(1, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + for (GroupId groupId : selectedGroups) { + EventFilter eventFilter = groupsDataManager + .getEventFilterForGroupMembershipRemovalEvent(groupId); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.groupId(), e.personId())); + }); + } + })); + + int comparisonDay = 100; + + // have the actor plan the removal of people to groups + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(3, (c) -> { + + for (int i = 3; i < comparisonDay; i++) { + c.addPlan((c2) -> { + RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class) + .getRandomGenerator(); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + + // create a person + PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); + + // select a group + List groupIds = groupsDataManager.getGroupIds(); + GroupId groupId = groupIds.get(randomGenerator.nextInt(groupIds.size())); + + // add the person to the group + groupsDataManager.addPersonToGroup(personId, groupId); + groupsDataManager.removePersonFromGroup(personId, groupId); + + if (selectedGroups.contains(groupId)) { + expectedObservations.add(new MultiKey(c2.getTime(), groupId, personId)); + } + }, i); + } + + })); + + // have the observer verify that the observations were correct + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(comparisonDay, (c) -> { + assertFalse(expectedObservations.isEmpty()); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = GroupsTestPluginFactory.factory(0, 3, 10, 1408892559376906541L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition tests: if the group id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 7647888786891229419L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = null; + groupsDataManager.getEventFilterForGroupMembershipRemovalEvent(groupId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); + + // precondition tests: if the group id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 4061098775259808370L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = new GroupId(1000000); + groupsDataManager.getEventFilterForGroupMembershipRemovalEvent(groupId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getEventFilterForGroupMembershipRemovalEvent", args = { + GroupId.class, PersonId.class }) + public void testGetEventFilterForGroupMembershipRemovalEvent_Group_Person() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create containers for observations + Set actualObservations = new LinkedHashSet<>(); + Set expectedObservations = new LinkedHashSet<>(); + + Set> selectedPairs = new LinkedHashSet<>(); + Set> nonSelectedPairs = new LinkedHashSet<>(); + + int groupCount = 20; + int peopleCount = 5; + /* + * have the actor create some groups and selected about half of them they will + * then be used for filtering membership removal observations + */ + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + for (int i = 0; i < groupCount; i++) { + TestGroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + GroupId groupId = groupsDataManager.addGroup(groupTypeId); + for (int j = 0; j < peopleCount; j++) { + PersonId selectedPersonId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); + PersonId nonselectedPersonId = peopleDataManager + .addPerson(PersonConstructionData.builder().build()); + selectedPairs.add(new Pair<>(groupId, selectedPersonId)); + nonSelectedPairs.add(new Pair<>(groupId, nonselectedPersonId)); + } + } + })); + + // add an agent to observe the group membership removals to the + // selected pairs + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(1, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + for (Pair pair : selectedPairs) { + GroupId groupId = pair.getFirst(); + PersonId personId = pair.getSecond(); + EventFilter eventFilter = groupsDataManager + .getEventFilterForGroupMembershipRemovalEvent(groupId, personId); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.groupId(), e.personId())); + }); + } + })); + + // have the actor plan the removal of people to groups + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(3, (c) -> { + + RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + + List> totalPairs = new ArrayList<>(); + totalPairs.addAll(selectedPairs); + totalPairs.addAll(nonSelectedPairs); + + Collections.shuffle(totalPairs, new Random(randomGenerator.nextLong())); + for (Pair pair : totalPairs) { + GroupId groupId = pair.getFirst(); + PersonId personId = pair.getSecond(); + groupsDataManager.addPersonToGroup(personId, groupId); + groupsDataManager.removePersonFromGroup(personId, groupId); + if (selectedPairs.contains(pair)) { + expectedObservations.add(new MultiKey(c.getTime(), groupId, personId)); + } + } + })); + + // have the observer verify that the observations were correct + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(4, (c) -> { + assertFalse(expectedObservations.isEmpty()); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + + Factory factory = GroupsTestPluginFactory.factory(0, 3, 10, 850494789248046062L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition tests: if the group id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 3817909950120643137L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = null; + PersonId personId = new PersonId(0); + groupsDataManager.getEventFilterForGroupMembershipRemovalEvent(groupId, personId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); + + // precondition tests: if the group id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 1158315623391808977L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = new GroupId(1000000); + PersonId personId = new PersonId(0); + groupsDataManager.getEventFilterForGroupMembershipRemovalEvent(groupId, personId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); + + // precondition tests: if the person id is null + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 7381815331080809057L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = new GroupId(0); + PersonId personId = null; + groupsDataManager.getEventFilterForGroupMembershipRemovalEvent(groupId, personId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + // precondition tests: if the person id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 730725479830632841L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = new GroupId(0); + PersonId personId = new PersonId(1000000); + groupsDataManager.getEventFilterForGroupMembershipRemovalEvent(groupId, personId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getEventFilterForGroupMembershipRemovalEvent", args = { + GroupTypeId.class }) + public void testGetEventFilterForGroupMembershipRemovalEvent_GroupType() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create containers for observations + Set actualObservations = new LinkedHashSet<>(); + Set expectedObservations = new LinkedHashSet<>(); + + Set selectedGroupTypes = new LinkedHashSet<>(); + selectedGroupTypes.add(TestGroupTypeId.GROUP_TYPE_1); + selectedGroupTypes.add(TestGroupTypeId.GROUP_TYPE_2); + + // add an agent to observe the group membership removals to the + // selected groups + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + for (GroupTypeId groupTypeId : selectedGroupTypes) { + EventFilter eventFilter = groupsDataManager + .getEventFilterForGroupMembershipRemovalEvent(groupTypeId); + + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(e.groupId(), e.personId())); + + }); + } + })); + + int groupCount = 100; + /* + * have the actor create some groups and add some people to them + */ + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + for (int i = 0; i < groupCount; i++) { + TestGroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + GroupId groupId = groupsDataManager.addGroup(groupTypeId); + + for (int j = 0; j < 3; j++) { + PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); + groupsDataManager.addPersonToGroup(personId, groupId); + groupsDataManager.removePersonFromGroup(personId, groupId); + if (selectedGroupTypes.contains(groupTypeId)) { + expectedObservations.add(new MultiKey(groupId, personId)); + } + } + + } + })); + + // have the observer verify that the observations were correct + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(2, (c) -> { + assertFalse(expectedObservations.isEmpty()); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + + Factory factory = GroupsTestPluginFactory.factory(0, 3, 10, 5729813629540803760L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition tests: if the group type id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 1847985412434537556L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupTypeId groupTypeId = null; + groupsDataManager.getEventFilterForGroupMembershipRemovalEvent(groupTypeId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); + + // precondition tests: if the group type id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 7066368881974432975L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupTypeId groupTypeId = TestGroupTypeId.getUnknownGroupTypeId(); + groupsDataManager.getEventFilterForGroupMembershipRemovalEvent(groupTypeId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getEventFilterForGroupMembershipRemovalEvent", args = { + GroupTypeId.class, PersonId.class }) + public void testGetEventFilterForGroupMembershipRemovalEvent_GroupType_Person() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create containers for observations + Set actualObservations = new LinkedHashSet<>(); + Set expectedObservations = new LinkedHashSet<>(); + + Set selectedGroupTypes = new LinkedHashSet<>(); + selectedGroupTypes.add(TestGroupTypeId.GROUP_TYPE_1); + selectedGroupTypes.add(TestGroupTypeId.GROUP_TYPE_2); + Set> selectedPairs = new LinkedHashSet<>(); + Set> nonSelectedPairs = new LinkedHashSet<>(); + + int groupCount = 100; + int peopleCount = 5; + /* + * have the actor create some groups and people who will eventually move into + * those groups + */ + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + for (int i = 0; i < groupCount; i++) { + TestGroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + GroupId groupId = groupsDataManager.addGroup(groupTypeId); + for (int j = 0; j < peopleCount; j++) { + PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); + if (selectedGroupTypes.contains(groupTypeId)) { + selectedPairs.add(new Pair<>(groupId, personId)); + } else { + nonSelectedPairs.add(new Pair<>(groupId, personId)); + } + } + } + })); + + // add an agent to observe the group membership removals to the + // selected pairs by way of the group type id + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(1, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + for (Pair pair : selectedPairs) { + GroupId groupId = pair.getFirst(); + PersonId personId = pair.getSecond(); + GroupTypeId groupTypeId = groupsDataManager.getGroupType(groupId); + EventFilter eventFilter = groupsDataManager + .getEventFilterForGroupMembershipRemovalEvent(groupTypeId, personId); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.groupId(), e.personId())); + }); + } + })); + + // have the actor add the people to the groups + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(3, (c) -> { + + RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + + List> totalPairs = new ArrayList<>(); + totalPairs.addAll(selectedPairs); + totalPairs.addAll(nonSelectedPairs); + + Collections.shuffle(totalPairs, new Random(randomGenerator.nextLong())); + for (Pair pair : totalPairs) { + GroupId groupId = pair.getFirst(); + PersonId personId = pair.getSecond(); + groupsDataManager.addPersonToGroup(personId, groupId); + groupsDataManager.removePersonFromGroup(personId, groupId); + if (selectedPairs.contains(pair)) { + expectedObservations.add(new MultiKey(c.getTime(), groupId, personId)); + } + } + })); + + // have the observer verify that the observations were correct + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(4, (c) -> { + assertFalse(expectedObservations.isEmpty()); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = GroupsTestPluginFactory.factory(0, 3, 10, 4777272524165165597L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition tests: if the group type id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 5144779074591935394L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupTypeId groupTypeId = null; + PersonId personId = new PersonId(0); + groupsDataManager.getEventFilterForGroupMembershipRemovalEvent(groupTypeId, personId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); + + // precondition tests: if the group id type is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 4113468214758049896L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupTypeId groupTypeId = TestGroupTypeId.getUnknownGroupTypeId(); + PersonId personId = new PersonId(0); + groupsDataManager.getEventFilterForGroupMembershipRemovalEvent(groupTypeId, personId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); + + // precondition tests: if the person id is null + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 8361722504062590493L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = new GroupId(0); + PersonId personId = null; + groupsDataManager.getEventFilterForGroupMembershipRemovalEvent(groupId, personId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + // precondition tests: if the person id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 6049511004252974580L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = new GroupId(0); + PersonId personId = new PersonId(1000000); + groupsDataManager.getEventFilterForGroupMembershipRemovalEvent(groupId, personId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getEventFilterForGroupMembershipRemovalEvent", args = { + PersonId.class }) + public void testGetEventFilterForGroupMembershipRemovalEvent_Person() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create containers for observations + Set actualObservations = new LinkedHashSet<>(); + Set expectedObservations = new LinkedHashSet<>(); + + Set> selectedPairs = new LinkedHashSet<>(); + Set> nonSelectedPairs = new LinkedHashSet<>(); + + int groupCount = 100; + int peopleCount = 5; + /* + * have the actor create some groups and people who will eventually move into + * those groups + */ + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + for (int i = 0; i < groupCount; i++) { + TestGroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + GroupId groupId = groupsDataManager.addGroup(groupTypeId); + for (int j = 0; j < peopleCount; j++) { + PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); + selectedPairs.add(new Pair<>(groupId, personId)); + personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); + nonSelectedPairs.add(new Pair<>(groupId, personId)); + } + } + })); + + // add an agent to observe the group membership removals to the + // selected pairs by way of the group type id + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(1, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + for (Pair pair : selectedPairs) { + PersonId personId = pair.getSecond(); + EventFilter eventFilter = groupsDataManager + .getEventFilterForGroupMembershipRemovalEvent(personId); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.groupId(), e.personId())); + }); + } + })); + + // have the actor add the people to the groups + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(3, (c) -> { + + RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + + List> totalPairs = new ArrayList<>(); + totalPairs.addAll(selectedPairs); + totalPairs.addAll(nonSelectedPairs); + + Collections.shuffle(totalPairs, new Random(randomGenerator.nextLong())); + for (Pair pair : totalPairs) { + GroupId groupId = pair.getFirst(); + PersonId personId = pair.getSecond(); + groupsDataManager.addPersonToGroup(personId, groupId); + groupsDataManager.removePersonFromGroup(personId, groupId); + if (selectedPairs.contains(pair)) { + expectedObservations.add(new MultiKey(c.getTime(), groupId, personId)); + } + } + })); + + // have the observer verify that the observations were correct + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(4, (c) -> { + assertFalse(expectedObservations.isEmpty()); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + + Factory factory = GroupsTestPluginFactory.factory(0, 3, 10, 4102753717872340366L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition tests: if the person id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 3402841194395285411L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + PersonId personId = null; + groupsDataManager.getEventFilterForGroupMembershipRemovalEvent(personId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + // precondition tests: if the person id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(30, 3, 10, 7511275143655411369L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + PersonId personId = new PersonId(1000000); + groupsDataManager.getEventFilterForGroupMembershipRemovalEvent(personId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getEventFilterForGroupMembershipRemovalEvent", args = {}) + public void testGetEventFilterForGroupMembershipRemovalEvent() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create containers for observations + Set actualObservations = new LinkedHashSet<>(); + Set expectedObservations = new LinkedHashSet<>(); + + // add an agent to observe the group membership removals to the + // selected pairs by way of the group type id + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + EventFilter eventFilter = groupsDataManager + .getEventFilterForGroupMembershipRemovalEvent(); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.groupId(), e.personId())); + }); + })); + + /* + * have the actor create some groups and people + */ + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + for (int i = 0; i < 100; i++) { + TestGroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + GroupId groupId = groupsDataManager.addGroup(groupTypeId); + for (int j = 0; j < 10; j++) { + PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); + groupsDataManager.addPersonToGroup(personId, groupId); + groupsDataManager.removePersonFromGroup(personId, groupId); + expectedObservations.add(new MultiKey(c.getTime(), groupId, personId)); + } + } + })); + + // have the observer verify that the observations were correct + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(4, (c) -> { + assertFalse(expectedObservations.isEmpty()); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + + Factory factory = GroupsTestPluginFactory.factory(0, 3, 10, 617923429956310846L, testPluginData); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getEventFilterForGroupPropertyUpdateEvent", args = { + GroupId.class }) + public void testGetEventFilterForGroupPropertyUpdateEvent_Group() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create data structures for observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + Set selectedGroups = new LinkedHashSet<>(); + + // create an actor to select about half of the existing groups for the + // observer to watch + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + List groupIds = groupsDataManager.getGroupIds(); + // show that there are some groups + assertTrue(groupIds.size() > 0); + boolean shouldSelect = false; + for (GroupId groupId : groupIds) { + if (shouldSelect) { + selectedGroups.add(groupId); + } + shouldSelect = !shouldSelect; + } + })); + + // create an actor to observe group properties being changed + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + for (GroupId groupId : selectedGroups) { + EventFilter eventFilter = groupsDataManager + .getEventFilterForGroupPropertyUpdateEvent(groupId); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(e.groupId(), e.groupPropertyId())); + }); + } + })); + + // have an agent change a few group property values + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + List groupIds = groupsDataManager.getGroupIds(); + + for (GroupId groupId : groupIds) { + TestGroupTypeId testGroupTypeId = groupsDataManager.getGroupType(groupId); + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId + .getTestGroupPropertyIds(testGroupTypeId)) { + if (testGroupPropertyId.getPropertyDefinition().propertyValuesAreMutable()) { + Object propertyValue = testGroupPropertyId.getRandomPropertyValue(randomGenerator); + groupsDataManager.setGroupPropertyValue(groupId, testGroupPropertyId, propertyValue); + if (selectedGroups.contains(groupId)) { + expectedObservations.add(new MultiKey(groupId, testGroupPropertyId)); + } + } + } + } + })); + + // show that the observations of the group property value assignments + // were correct + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(3, (c) -> { + assertEquals(expectedObservations, actualObservations); + })); + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = GroupsTestPluginFactory.factory(100, 3, 5, 5968311683269278335L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition test: if the group id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 3, 5, 862611649140739209L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = null; + groupsDataManager.getEventFilterForGroupPropertyUpdateEvent(groupId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); + + // precondition test: if the group id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 3, 5, 4350585872528673625L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = new GroupId(10000000); + groupsDataManager.getEventFilterForGroupPropertyUpdateEvent(groupId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getEventFilterForGroupPropertyUpdateEvent", args = { + GroupPropertyId.class, GroupId.class }) + public void testGetEventFilterForGroupPropertyUpdateEvent_Property_Group() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create data structures for observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + Set> selectedPairs = new LinkedHashSet<>(); + + // create an actor to select about half of the existing groups and about + // half of their properties for the + // observer to watch + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + List groupIds = groupsDataManager.getGroupIds(); + // show that there are some groups + assertTrue(groupIds.size() > 0); + + for (GroupId groupId : groupIds) { + if (randomGenerator.nextBoolean()) { + GroupTypeId groupTypeId = groupsDataManager.getGroupType(groupId); + Set groupPropertyIds = groupsDataManager.getGroupPropertyIds(groupTypeId); + for (GroupPropertyId groupPropertyId : groupPropertyIds) { + if (randomGenerator.nextBoolean()) { + Pair pair = new Pair<>(groupPropertyId, groupId); + selectedPairs.add(pair); + } + } + } + } + })); + + // create an actor to observe the selected group properties being + // changed + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + for (Pair pair : selectedPairs) { + GroupPropertyId groupPropertyId = pair.getFirst(); + GroupId groupId = pair.getSecond(); + EventFilter eventFilter = groupsDataManager + .getEventFilterForGroupPropertyUpdateEvent(groupPropertyId, groupId); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(e.groupId(), e.groupPropertyId())); + }); + } + })); + + // have an agent change a few group property values + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + List groupIds = groupsDataManager.getGroupIds(); + + for (GroupId groupId : groupIds) { + TestGroupTypeId testGroupTypeId = groupsDataManager.getGroupType(groupId); + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId + .getTestGroupPropertyIds(testGroupTypeId)) { + if (testGroupPropertyId.getPropertyDefinition().propertyValuesAreMutable()) { + Object propertyValue = testGroupPropertyId.getRandomPropertyValue(randomGenerator); + groupsDataManager.setGroupPropertyValue(groupId, testGroupPropertyId, propertyValue); + Pair pair = new Pair<>(testGroupPropertyId, groupId); + if (selectedPairs.contains(pair)) { + expectedObservations.add(new MultiKey(groupId, testGroupPropertyId)); + } + } + } + } + })); + + // show that the observations of the group property value assignments + // were correct + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(3, (c) -> { + assertFalse(expectedObservations.isEmpty()); + assertEquals(expectedObservations, actualObservations); + })); + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = GroupsTestPluginFactory.factory(100, 3, 5, 4475568313075757118L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition test: if the group property id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 3, 5, 7069545924217450784L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = new GroupId(0); + GroupPropertyId groupPropertyId = null; + groupsDataManager.getEventFilterForGroupPropertyUpdateEvent(groupPropertyId, groupId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // precondition test: if the group property id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 3, 5, 7612521001250321841L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = new GroupId(0); + GroupPropertyId groupPropertyId = TestGroupPropertyId.getUnknownGroupPropertyId(); + groupsDataManager.getEventFilterForGroupPropertyUpdateEvent(groupPropertyId, groupId); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + // precondition test: if the group id is null + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 3, 5, 6231606808502548902L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = null; + GroupPropertyId groupPropertyId = TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK; + groupsDataManager.getEventFilterForGroupPropertyUpdateEvent(groupPropertyId, groupId); + + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); + + // precondition test: if the group id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 3, 5, 7604482353903847440L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = new GroupId(10000000); + GroupPropertyId groupPropertyId = TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK; + groupsDataManager.getEventFilterForGroupPropertyUpdateEvent(groupPropertyId, groupId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getEventFilterForGroupPropertyUpdateEvent", args = { + GroupTypeId.class }) + public void testGetEventFilterForGroupPropertyUpdateEvent_GroupType() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create data structures for observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + Set selectedGroupTypes = new LinkedHashSet<>(); + selectedGroupTypes.add(TestGroupTypeId.GROUP_TYPE_1); + selectedGroupTypes.add(TestGroupTypeId.GROUP_TYPE_2); + + // create an actor to observe the selected group properties being + // changed + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + for (GroupTypeId groupTypeId : selectedGroupTypes) { + EventFilter eventFilter = groupsDataManager + .getEventFilterForGroupPropertyUpdateEvent(groupTypeId); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(e.groupId(), e.groupPropertyId())); + }); + } + })); + + // have an agent change a few group property values + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + List groupIds = groupsDataManager.getGroupIds(); + + for (GroupId groupId : groupIds) { + TestGroupTypeId testGroupTypeId = groupsDataManager.getGroupType(groupId); + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId + .getTestGroupPropertyIds(testGroupTypeId)) { + if (testGroupPropertyId.getPropertyDefinition().propertyValuesAreMutable()) { + Object propertyValue = testGroupPropertyId.getRandomPropertyValue(randomGenerator); + groupsDataManager.setGroupPropertyValue(groupId, testGroupPropertyId, propertyValue); + + if (selectedGroupTypes.contains(testGroupTypeId)) { + expectedObservations.add(new MultiKey(groupId, testGroupPropertyId)); + } + } + } + } + })); + + // show that the observations of the group property value assignments + // were correct + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(3, (c) -> { + assertFalse(expectedObservations.isEmpty()); + assertEquals(expectedObservations, actualObservations); + })); + TestPluginData testPluginData = pluginBuilder.build(); + + Factory factory = GroupsTestPluginFactory.factory(100, 3, 5, 7860201282796014649L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition test: if the group type id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 3, 5, 3217133270896467859L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupTypeId groupTypeId = null; + groupsDataManager.getEventFilterForGroupPropertyUpdateEvent(groupTypeId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); + + // precondition test: if the group type id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 3, 5, 289438765224007761L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupTypeId groupTypeId = TestGroupTypeId.getUnknownGroupTypeId(); + groupsDataManager.getEventFilterForGroupPropertyUpdateEvent(groupTypeId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getEventFilterForGroupPropertyUpdateEvent", args = { + GroupPropertyId.class, GroupTypeId.class }) + public void testGetEventFilterForGroupPropertyUpdateEvent_GroupProperty_GroupType() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create data structures for observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + Set> selectedPairs = new LinkedHashSet<>(); + + // create an actor to observe about half of all properties + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + Set groupTypeIds = groupsDataManager.getGroupTypeIds(); + for (GroupTypeId groupTypeId : groupTypeIds) { + Set groupPropertyIds = groupsDataManager.getGroupPropertyIds(groupTypeId); + for (GroupPropertyId groupPropertyId : groupPropertyIds) { + if (randomGenerator.nextBoolean()) { + Pair pair = new Pair<>(groupPropertyId, groupTypeId); + selectedPairs.add(pair); + EventFilter eventFilter = groupsDataManager + .getEventFilterForGroupPropertyUpdateEvent(groupPropertyId, groupTypeId); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(e.groupId(), e.groupPropertyId())); + }); + } + } + } + })); + + // have an agent change a few group property values + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + List groupIds = groupsDataManager.getGroupIds(); + + for (GroupId groupId : groupIds) { + TestGroupTypeId testGroupTypeId = groupsDataManager.getGroupType(groupId); + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId + .getTestGroupPropertyIds(testGroupTypeId)) { + if (testGroupPropertyId.getPropertyDefinition().propertyValuesAreMutable()) { + Object propertyValue = testGroupPropertyId.getRandomPropertyValue(randomGenerator); + groupsDataManager.setGroupPropertyValue(groupId, testGroupPropertyId, propertyValue); + Pair pair = new Pair<>(testGroupPropertyId, testGroupTypeId); + if (selectedPairs.contains(pair)) { + expectedObservations.add(new MultiKey(groupId, testGroupPropertyId)); + } + } + } + } + })); + + // show that the observations of the group property value assignments + // were correct + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(3, (c) -> { + assertFalse(expectedObservations.isEmpty()); + assertEquals(expectedObservations, actualObservations); + })); + TestPluginData testPluginData = pluginBuilder.build(); + + Factory factory = GroupsTestPluginFactory.factory(100, 3, 5, 806128103582088681L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition test: if the group property id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 3, 5, 3969612657033464070L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupTypeId groupTypeId = TestGroupTypeId.GROUP_TYPE_1; + GroupPropertyId groupPropertyId = null; + groupsDataManager.getEventFilterForGroupPropertyUpdateEvent(groupPropertyId, groupTypeId); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // precondition test: if the group property id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 3, 5, 2325782745323432023L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupTypeId groupTypeId = TestGroupTypeId.GROUP_TYPE_1; + GroupPropertyId groupPropertyId = TestGroupPropertyId.getUnknownGroupPropertyId(); + groupsDataManager.getEventFilterForGroupPropertyUpdateEvent(groupPropertyId, groupTypeId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + // precondition test: if the group type id is null + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 3, 5, 1401012088879526006L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupTypeId groupTypeId = null; + GroupPropertyId groupPropertyId = TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK; + groupsDataManager.getEventFilterForGroupPropertyUpdateEvent(groupPropertyId, groupTypeId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); + + // precondition test: if the group id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = GroupsTestPluginFactory.factory(100, 3, 5, 7868034517160861554L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupTypeId groupTypeId = TestGroupTypeId.getUnknownGroupTypeId(); + GroupPropertyId groupPropertyId = TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK; + groupsDataManager.getEventFilterForGroupPropertyUpdateEvent(groupPropertyId, groupTypeId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getEventFilterForGroupPropertyUpdateEvent", args = {}) + public void testGetEventFilterForGroupPropertyUpdateEvent() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create data structures for observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // create an actor to observe all group property changes + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + EventFilter eventFilter = groupsDataManager + .getEventFilterForGroupPropertyUpdateEvent(); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(e.groupId(), e.groupPropertyId())); + }); + + })); + + // have an agent change a few group property values + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + List groupIds = groupsDataManager.getGroupIds(); + + for (GroupId groupId : groupIds) { + TestGroupTypeId testGroupTypeId = groupsDataManager.getGroupType(groupId); + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId + .getTestGroupPropertyIds(testGroupTypeId)) { + if (testGroupPropertyId.getPropertyDefinition().propertyValuesAreMutable()) { + Object propertyValue = testGroupPropertyId.getRandomPropertyValue(randomGenerator); + groupsDataManager.setGroupPropertyValue(groupId, testGroupPropertyId, propertyValue); + expectedObservations.add(new MultiKey(groupId, testGroupPropertyId)); + } + } + } + })); + + // show that the observations of the group property value assignments + // were correct + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(3, (c) -> { + assertFalse(expectedObservations.isEmpty()); + assertEquals(expectedObservations, actualObservations); + })); + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = GroupsTestPluginFactory.factory(100, 3, 5, 2633645086420948220L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getEventFilterForGroupPropertyDefinitionEvent", args = {}) + public void testGetEventFilterForGroupPropertyDefinitionEvent() { + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // have an observer observe new group property definitions being created + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + EventFilter eventFilter = groupsDataManager + .getEventFilterForGroupPropertyDefinitionEvent(); + c.subscribe(eventFilter, (c2, e) -> { + MultiKey multiKey = new MultiKey(c2.getTime(), e.groupTypeId(), e.groupPropertyId()); + actualObservations.add(multiKey); + }); + })); + + // have an actor add group property definitions + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + for (TestAuxiliaryGroupTypeId testAuxiliaryGroupTypeId : TestAuxiliaryGroupTypeId.values()) { + groupsDataManager.addGroupType(testAuxiliaryGroupTypeId); + for (TestAuxiliaryGroupPropertyId testAuxiliaryGroupPropertyId : TestAuxiliaryGroupPropertyId + .getTestGroupPropertyIds(testAuxiliaryGroupTypeId)) { + PropertyDefinition propertyDefinition = testAuxiliaryGroupPropertyId.getPropertyDefinition(); + GroupPropertyDefinitionInitialization groupPropertyDefinitionInitialization = // + GroupPropertyDefinitionInitialization.builder()// + .setGroupTypeId(testAuxiliaryGroupTypeId)// + .setPropertyId(testAuxiliaryGroupPropertyId)// + .setPropertyDefinition(propertyDefinition)// + .build(); + + groupsDataManager.defineGroupProperty(groupPropertyDefinitionInitialization); + MultiKey multiKey = new MultiKey(c.getTime(), testAuxiliaryGroupTypeId, + testAuxiliaryGroupPropertyId); + expectedObservations.add(multiKey); + PropertyDefinition actualPropertyDefinition = groupsDataManager + .getGroupPropertyDefinition(testAuxiliaryGroupTypeId, testAuxiliaryGroupPropertyId); + assertEquals(propertyDefinition, actualPropertyDefinition); + } + } + })); + + // have the observer verify that the observations were correct + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + + Factory factory = GroupsTestPluginFactory.factory(100, 3, 10, 2918652330043276295L, testPluginData); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getEventFilterForGroupTypeAdditionEvent", args = {}) + public void testGetEventFilterForGroupTypeAdditionEvent() { + Set expectedGroupTypeIds = new LinkedHashSet<>(); + Set actualGroupTypeIds = new LinkedHashSet<>(); + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // have an actor observe the addition of new group types + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + EventFilter eventFilter = groupsDataManager + .getEventFilterForGroupTypeAdditionEvent(); + c.subscribe(eventFilter, (c2, e) -> { + actualGroupTypeIds.add(e.groupTypeId()); + }); + })); + + // have an actor add some new group types + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + for (TestAuxiliaryGroupTypeId testAuxiliaryGroupTypeId : TestAuxiliaryGroupTypeId.values()) { + expectedGroupTypeIds.add(testAuxiliaryGroupTypeId); + groupsDataManager.addGroupType(testAuxiliaryGroupTypeId); + } + })); + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(2, (c) -> { + assertFalse(expectedGroupTypeIds.size() == 0); + assertEquals(expectedGroupTypeIds, actualGroupTypeIds); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + + Factory factory = GroupsTestPluginFactory.factory(100, 3, 10, 7349170569580375646L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "getGroupIndexIterator", args = {}) + public void testGetGroupIndexIterator() { + + Factory factory = GroupsTestPluginFactory.factory(0, 3, 10, 7388964335248251429L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + + // + + + // show that the iterator yields the list of existing people in the usual order + List expectedGroups = groupsDataManager.getGroupIds(); + List actualGroups = new ArrayList<>(); + Iterator groupIndexIterator = groupsDataManager.getGroupIndexIterator(); + while (groupIndexIterator.hasNext()) { + Integer id = groupIndexIterator.next(); + GroupId groupId = new GroupId(id); + assertTrue(groupsDataManager.groupExists(groupId)); + actualGroups.add(groupId); + } + assertEquals(expectedGroups, actualGroups); + }); + + + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "toString", args = {}) + public void testToString() { + + Factory factory = GroupsTestPluginFactory.factory(30, 2, 5, 929934281474564292L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + String actualValue = groupsDataManager.toString(); + + //expected value verified by inspection + StringBuilder sb = new StringBuilder(); + sb.append("GroupsDataManager ["); + sb.append("groupPropertyManagerMap={GROUP_TYPE_1={GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK=BooleanPropertyManager [boolContainer=BooleanContainer [defaultValue=false, bitSet=[0=false, 1=false, 2=false, 3=true, 4=false, 5=false, 6=true, 7=false, 8=false, 9=true, 10=false, 11=false]]], GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK=IntPropertyManager [intValueContainer=IntValueContainer [subTypeArray=IntArray [values=[0=0, 1=0, 2=0, 3=0, 4=0, 5=0, 6=-1566878986, 7=0, 8=0, 9=235907035, 10=0, 11=0], defaultValue=0]], intValueType=INT], GROUP_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK=DoublePropertyManager [doubleValueContainer=DoubleValueContainer [values=[0=0.3425156570357295, 1=0.0, 2=0.0, 3=0.0, 4=0.0, 5=0.0, 6=0.9311195847064178, 7=0.0, 8=0.0, 9=0.0, 10=0.0, 11=0.0], defaultValue=0.0]]}, GROUP_TYPE_2={GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK=BooleanPropertyManager [boolContainer=BooleanContainer [defaultValue=false, bitSet=[0=false, 1=false, 2=false, 3=false, 4=false, 5=false, 6=false, 7=false, 8=false, 9=false, 10=true, 11=false]]], GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK=IntPropertyManager [intValueContainer=IntValueContainer [subTypeArray=IntArray [values=[0=0, 1=0, 2=0, 3=0, 4=-449998779, 5=0, 6=0, 7=0, 8=0, 9=0, 10=1041083617, 11=0], defaultValue=0]], intValueType=INT], GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK=DoublePropertyManager [doubleValueContainer=DoubleValueContainer [values=[0=0.0, 1=0.0, 2=0.0, 3=0.0, 4=0.16116841623093192, 5=0.0, 6=0.0, 7=0.0, 8=0.0, 9=0.0, 10=0.0, 11=0.0], defaultValue=0.0]]}, GROUP_TYPE_3={GROUP_PROPERTY_3_1_BOOLEAN_IMMUTABLE_NO_TRACK=BooleanPropertyManager [boolContainer=BooleanContainer [defaultValue=false, bitSet=[0=false, 1=false, 2=true, 3=false, 4=false, 5=true, 6=false, 7=false, 8=false, 9=false, 10=false, 11=true]]], GROUP_PROPERTY_3_2_INTEGER_IMMUTABLE_NO_TRACK=IntPropertyManager [intValueContainer=IntValueContainer [subTypeArray=IntArray [values=[0=0, 1=0, 2=0, 3=0, 4=0, 5=-348594628, 6=0, 7=0, 8=0, 9=0, 10=0, 11=-205917896], defaultValue=0]], intValueType=INT], GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK=DoublePropertyManager [doubleValueContainer=DoubleValueContainer [values=[0=0.0, 1=0.0, 2=0.8943039222106994, 3=0.0, 4=0.0, 5=0.9032000604182797, 6=0.0, 7=0.0, 8=0.12721615664212016, 9=0.0, 10=0.0, 11=0.0], defaultValue=0.0]]}}, "); + sb.append("groupPropertyDefinitions={GROUP_TYPE_1={GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK=PropertyDefinition [type=class java.lang.Boolean, propertyValuesAreMutable=true, defaultValue=false], GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK=PropertyDefinition [type=class java.lang.Integer, propertyValuesAreMutable=true, defaultValue=0], GROUP_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK=PropertyDefinition [type=class java.lang.Double, propertyValuesAreMutable=true, defaultValue=0.0]}, GROUP_TYPE_2={GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK=PropertyDefinition [type=class java.lang.Boolean, propertyValuesAreMutable=true, defaultValue=false], GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK=PropertyDefinition [type=class java.lang.Integer, propertyValuesAreMutable=true, defaultValue=0], GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK=PropertyDefinition [type=class java.lang.Double, propertyValuesAreMutable=true, defaultValue=0.0]}, GROUP_TYPE_3={GROUP_PROPERTY_3_1_BOOLEAN_IMMUTABLE_NO_TRACK=PropertyDefinition [type=class java.lang.Boolean, propertyValuesAreMutable=false, defaultValue=false], GROUP_PROPERTY_3_2_INTEGER_IMMUTABLE_NO_TRACK=PropertyDefinition [type=class java.lang.Integer, propertyValuesAreMutable=false, defaultValue=0], GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK=PropertyDefinition [type=class java.lang.Double, propertyValuesAreMutable=false, defaultValue=0.0]}}, "); + sb.append("typesToGroupsMap=ObjectValueContainer [elements=[0=[0, 3, 6, 9], 1=[1, 4, 7, 10], 2=[2, 5, 8, 11]], defaultValue=null], "); + sb.append("groupsToTypesMap=IntValueContainer [subTypeArray=ByteArray [values=[0=0, 1=1, 2=2, 3=0, 4=1, 5=2, 6=0, 7=1, 8=2, 9=0, 10=1, 11=2], defaultValue=-1]], "); + sb.append("typesToIndexesMap={GROUP_TYPE_1=0, GROUP_TYPE_2=1, GROUP_TYPE_3=2}, "); + sb.append("indexesToTypesMap=[GROUP_TYPE_1, GROUP_TYPE_2, GROUP_TYPE_3], "); + sb.append("groupsToPeopleMap=ObjectValueContainer [elements=[0=[10, 2, 9, 4, 28, 24, 0], 1=[19, 26, 28, 13], 2=[1, 2, 21, 26, 8, 29, 15, 19, 0], 3=[6, 4], 4=[16, 18], 5=[8, 2, 5, 12, 3, 28, 20, 14], 6=[15, 20], 7=[20, 25, 27], 8=[2, 6, 8, 4, 0, 7, 12, 16, 21], 9=[10, 7], 10=[13, 23, 26, 28, 18, 12, 27, 22], 11=[17, 19, 14, 12]], defaultValue=null], "); + sb.append("peopleToGroupsMap=ObjectValueContainer [elements=[0=[8, 0, 2], 1=[2], 2=[2, 5, 0, 8], 3=[5], 4=[8, 0, 3], 5=[5], 6=[8, 3], 7=[8, 9], 8=[5, 8, 2], 9=[0], 10=[0, 9]], defaultValue=null], "); + sb.append("masterGroupId=12]"); + String expectedValue = sb.toString(); + + + assertEquals(expectedValue, actualValue); + + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/datamanagers/AT_GroupsDataManager_Continuity.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/datamanagers/AT_GroupsDataManager_Continuity.java new file mode 100644 index 000000000..8be2d95cd --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/datamanagers/AT_GroupsDataManager_Continuity.java @@ -0,0 +1,473 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.datamanagers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Optional; +import java.util.Random; +import java.util.Set; +import java.util.function.Consumer; + +import org.apache.commons.math3.random.RandomGenerator; +import org.apache.commons.math3.util.FastMath; +import org.apache.commons.math3.util.Pair; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.Simulation; +import gov.hhs.aspr.ms.gcm.nucleus.SimulationState; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.runcontinuityplugin.RunContinuityPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.runcontinuityplugin.RunContinuityPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestOutputConsumer; +import gov.hhs.aspr.ms.gcm.plugins.groups.GroupsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupConstructionInfo; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupId; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupPropertyDefinitionInitialization; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupSampler; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupTypeId; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.TestGroupPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.TestGroupTypeId; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePlugin; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeoplePluginData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonConstructionData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import util.annotations.UnitTestMethod; + +public class AT_GroupsDataManager_Continuity { + + /** + * Demonstrates that the data manager exhibits run continuity. The state of the + * data manager is not effected by repeatedly starting and stopping the + * simulation. + */ + + @Test + @UnitTestMethod(target = GroupsDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testStateContinuity() { + + /* + * The returned string is the ordered state of the groups data manager. We + * generate this state at the end of each batch of simulation runs. + */ + + Set pluginDatas = new LinkedHashSet<>(); + pluginDatas.add(testStateContinuity(1)); + pluginDatas.add(testStateContinuity(5)); + pluginDatas.add(testStateContinuity(10)); + + /* + * Show that breaking up a simulation run into multiple increments has no effect + * on the final state of the groups data manager. + */ + assertEquals(1, pluginDatas.size()); + + } + + /* + * Contains the current plugin data state of the simulation + * + */ + private static class StateData { + private RunContinuityPluginData runContinuityPluginData; + private PeoplePluginData peoplePluginData; + private StochasticsPluginData stochasticsPluginData; + private GroupsPluginData groupsPluginData; + private SimulationState simulationState; + private double haltTime; + private String output; + } + + /* + * Returns the default Simulation state -- time starts at zero synchronized to + * the beginning of the epoch. + */ + private static SimulationState getSimulationState() { + return SimulationState.builder().build(); + } + + /* + * Returns an empty people plugin data + */ + private static PeoplePluginData getPeoplePluginData() { + return PeoplePluginData.builder().build(); + } + + /* + * Returns the stochastics plugin data with only the main random generator. + */ + private static StochasticsPluginData getStochasticsPluginData(long seed) { + + WellState wellState = WellState.builder()// + .setSeed(seed)// + .build(); + + return StochasticsPluginData.builder().setMainRNGState(wellState).build(); + } + + private static GroupsPluginData getGroupsPluginData() { + return GroupsPluginData.builder().build(); + } + + private static RunContinuityPluginData getRunContinuityPluginData() { + RunContinuityPluginData.Builder continuityBuilder = RunContinuityPluginData.builder(); + double actionTime = 0; + + // define group types + continuityBuilder.addContextConsumer(actionTime++, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + List groupTypeIds = Arrays.asList(TestGroupTypeId.values()); + Collections.reverse(groupTypeIds); + for (TestGroupTypeId testGroupTypeId : groupTypeIds) { + groupsDataManager.addGroupType(testGroupTypeId); + } + }); + + // add some groups + continuityBuilder.addContextConsumer(actionTime++, (c) -> { + // addGroup(GroupConstructionInfo) + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + List testGroupTypeIds = new ArrayList<>(); + testGroupTypeIds.add(TestGroupTypeId.GROUP_TYPE_2); + testGroupTypeIds.add(TestGroupTypeId.GROUP_TYPE_1); + testGroupTypeIds.add(TestGroupTypeId.GROUP_TYPE_3); + for (TestGroupTypeId testGroupTypeId : testGroupTypeIds) { + int n = randomGenerator.nextInt(10) + 1; + for (int i = 0; i < n; i++) { + GroupConstructionInfo groupConstructionInfo = GroupConstructionInfo.builder() + .setGroupTypeId(testGroupTypeId).build(); + groupsDataManager.addGroup(groupConstructionInfo); + } + } + }); + + // define the group properties + continuityBuilder.addContextConsumer(actionTime++, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + List testGroupPropertyIds = new ArrayList<>(); + testGroupPropertyIds.add(TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK); + testGroupPropertyIds.add(TestGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK); + testGroupPropertyIds.add(TestGroupPropertyId.GROUP_PROPERTY_3_2_INTEGER_IMMUTABLE_NO_TRACK); + testGroupPropertyIds.add(TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK); + testGroupPropertyIds.add(TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK); + testGroupPropertyIds.add(TestGroupPropertyId.GROUP_PROPERTY_3_1_BOOLEAN_IMMUTABLE_NO_TRACK); + testGroupPropertyIds.add(TestGroupPropertyId.GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK); + testGroupPropertyIds.add(TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK); + testGroupPropertyIds.add(TestGroupPropertyId.GROUP_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK); + + for (TestGroupPropertyId testGroupPropertyId : testGroupPropertyIds) { + TestGroupTypeId testGroupTypeId = testGroupPropertyId.getTestGroupTypeId(); + + GroupPropertyDefinitionInitialization.Builder defBuilder = GroupPropertyDefinitionInitialization + .builder(); + defBuilder.setGroupTypeId(testGroupTypeId)// + .setPropertyDefinition(testGroupPropertyId.getPropertyDefinition())// + .setPropertyId(testGroupPropertyId);// + boolean defaultValuePresent = testGroupPropertyId.getPropertyDefinition().getDefaultValue().isPresent(); + + if (!defaultValuePresent) { + List groupIds = groupsDataManager.getGroupsForGroupType(testGroupTypeId); + for (GroupId groupId : groupIds) { + Object propertyValue = testGroupPropertyId.getRandomPropertyValue(randomGenerator); + defBuilder.addPropertyValue(groupId, propertyValue); + } + } + + groupsDataManager.defineGroupProperty(defBuilder.build()); + } + + }); + + // add people to groups + continuityBuilder.addContextConsumer(actionTime++, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + List groupIds = groupsDataManager.getGroupIds(); + + int n = 100; + for (int i = 0; i < n; i++) { + peopleDataManager.addPerson(PersonConstructionData.builder().build()); + } + + List people = peopleDataManager.getPeople(); + Collections.shuffle(people,new Random(randomGenerator.nextLong())); + + for (PersonId personId : people) { + int groupCount = randomGenerator.nextInt(FastMath.min(3, groupIds.size())); + Collections.shuffle(groupIds, new Random(randomGenerator.nextLong())); + for (int j = 0; j < groupCount; j++) { + GroupId groupId = groupIds.get(j); + groupsDataManager.addPersonToGroup(personId, groupId); + } + } + }); + + // add more groups + continuityBuilder.addContextConsumer(actionTime++, (c) -> { + // addGroup(GroupTypeId) + + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + Set groupTypeIds = groupsDataManager.getGroupTypeIds(); + Set groupTypesWithFullDefaults = new LinkedHashSet<>(); + for (GroupTypeId groupTypeId : groupTypeIds) { + Set groupPropertyIds = groupsDataManager.getGroupPropertyIds(groupTypeId); + boolean hasDefaults = true; + for (GroupPropertyId groupPropertyId : groupPropertyIds) { + PropertyDefinition propertyDefinition = groupsDataManager.getGroupPropertyDefinition(groupTypeId, + groupPropertyId); + hasDefaults &= propertyDefinition.getDefaultValue().isPresent(); + } + if (hasDefaults) { + groupTypesWithFullDefaults.add(groupTypeId); + } + } + + assertFalse(groupTypesWithFullDefaults.isEmpty()); + + for (GroupTypeId groupTypeId : groupTypesWithFullDefaults) { + int count = randomGenerator.nextInt(5) + 1; + for (int i = 0; i < count; i++) { + groupsDataManager.addGroup(groupTypeId); + } + } + + }); + + // remove some people from groups + continuityBuilder.addContextConsumer(actionTime++, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + for (PersonId personId : peopleDataManager.getPeople()) { + int groupCount = groupsDataManager.getGroupCountForPerson(personId); + if (groupCount > 0) { + int remainingRemovals = randomGenerator.nextInt(groupCount); + while (remainingRemovals > 0) { + List groupsForPerson = groupsDataManager.getGroupsForPerson(personId); + GroupId groupId = groupsForPerson.get(randomGenerator.nextInt(groupsForPerson.size())); + groupsDataManager.removePersonFromGroup(personId, groupId); + remainingRemovals--; + } + } + } + }); + + // remove some groups + continuityBuilder.addContextConsumer(actionTime++, (c) -> { + // removeGroup(GroupId) + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + Random random = new Random(randomGenerator.nextLong()); + + List groupIds = groupsDataManager.getGroupIds(); + Collections.shuffle(groupIds, random); + int removalCount = groupIds.size() / 5; + for (int i = 0; i < removalCount; i++) { + groupsDataManager.removeGroup(groupIds.get(i)); + } + + }); + + // set some group properties + continuityBuilder.addContextConsumer(actionTime++, (c) -> { + // setGroupPropertyValue(GroupId, GroupPropertyId, Object) + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + Random random = new Random(randomGenerator.nextLong()); + List groupTypeIds = new ArrayList<>(groupsDataManager.getGroupTypeIds()); + Collections.shuffle(groupTypeIds,random); + + for (GroupTypeId groupTypeId : groupTypeIds) { + List groupIds = groupsDataManager.getGroupsForGroupType(groupTypeId); + + + List groupPropertyIds = new ArrayList<>(groupsDataManager.getGroupPropertyIds(groupTypeId)); + Collections.shuffle(groupPropertyIds,random); + for (TestGroupPropertyId groupPropertyId : groupPropertyIds) { + PropertyDefinition propertyDefinition = groupsDataManager.getGroupPropertyDefinition(groupTypeId, + groupPropertyId); + if (propertyDefinition.propertyValuesAreMutable()) { + Collections.shuffle(groupIds,random); + for (GroupId groupId : groupIds) { + Object propertyValue = groupPropertyId.getRandomPropertyValue(randomGenerator); + groupsDataManager.setGroupPropertyValue(groupId, groupPropertyId, propertyValue); + } + } + } + } + }); + + // set some groups and remove a few people + continuityBuilder.addContextConsumer(actionTime++, (c) -> { + // sampleGroup(GroupId, GroupSampler) + + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + + List groupIds = groupsDataManager.getGroupIds(); + Collections.sort(groupIds); + + for (GroupId groupId : groupIds) { + GroupSampler groupSampler = GroupSampler.builder().build(); + Optional optional = groupsDataManager.sampleGroup(groupId, groupSampler); + if(optional.isPresent()) { + PersonId personId = optional.get(); + groupsDataManager.removePersonFromGroup(personId, groupId); + } + + } + }); + + // release the string state of the groups data manager as output + continuityBuilder.addContextConsumer(actionTime++, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + c.releaseOutput(groupsDataManager.toString()); + }); + + return continuityBuilder.build(); + + } + + private static StateData getInitialState(long seed) { + StateData result = new StateData(); + result.runContinuityPluginData = getRunContinuityPluginData(); + result.peoplePluginData = getPeoplePluginData(); + result.stochasticsPluginData = getStochasticsPluginData(seed); + result.groupsPluginData = getGroupsPluginData(); + result.simulationState = getSimulationState(); + + return result; + } + + /* + * Returns the duration for a single incremented run of the simulation. This is + * determined by finding the last scheduled task in the run continuity plugin + * data and dividing that by the number of increments. + */ + private static double getSimulationTimeIncrement(StateData stateData, int incrementCount) { + double maxTime = Double.NEGATIVE_INFINITY; + for (Pair> pair : stateData.runContinuityPluginData.getConsumers()) { + Double time = pair.getFirst(); + maxTime = FastMath.max(maxTime, time); + } + + return maxTime / incrementCount; + } + + /* + * Returns the ordered state of the Groups Data Manager as a string + */ + private String testStateContinuity(int incrementCount) { + + long seed = 6160901456257930728L; + + /* + * We initialize the various plugin datas needed for the simulation + */ + StateData stateData = getInitialState(seed); + + // We will break up the simulation run into several runs, each lasting a + // fixed duration + double timeIncrement = getSimulationTimeIncrement(stateData, incrementCount); + + while (!stateData.runContinuityPluginData.allPlansComplete()) { + stateData.haltTime += timeIncrement; + runSimulation(stateData); + } + + /* + * When the simulation has finished -- the plans contained in the run continuity + * plugin data have been completed, the string state of the attributes data + * manager is returned + */ + + // show that the groups data manager toString() is returning something + // reasonable + assertNotNull(stateData.output); + assertTrue(stateData.output.length() > 100); + + return stateData.output; + } + + private static void runSimulation(StateData stateData) { + // build the run continuity plugin + Plugin runContinuityPlugin = RunContinuityPlugin.builder()// + .setRunContinuityPluginData(stateData.runContinuityPluginData)// + .build(); + + // build the people plugin + Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(stateData.peoplePluginData); + + // build the stochastics plugin + Plugin stochasticsPlugin = StochasticsPlugin.getStochasticsPlugin(stateData.stochasticsPluginData); + + // build the groups plugin + Plugin groupsPlugin = GroupsPlugin.builder().setGroupsPluginData(stateData.groupsPluginData).getGroupsPlugin(); + + TestOutputConsumer outputConsumer = new TestOutputConsumer(); + + // execute the simulation so that it produces a people plugin data + Simulation simulation = Simulation.builder()// + .addPlugin(peoplePlugin)// + .addPlugin(stochasticsPlugin)// + .addPlugin(runContinuityPlugin)// + .addPlugin(groupsPlugin)// + .setSimulationHaltTime(stateData.haltTime)// + .setRecordState(true)// + .setOutputConsumer(outputConsumer)// + .setSimulationState(stateData.simulationState)// + .build();// + simulation.execute(); + + // retrieve the people plugin data + stateData.peoplePluginData = outputConsumer.getOutputItem(PeoplePluginData.class).get(); + + // retrieve the groups plugin data + stateData.groupsPluginData = outputConsumer.getOutputItem(GroupsPluginData.class).get(); + + // retrieve the stochastics plugin data + stateData.stochasticsPluginData = outputConsumer.getOutputItem(StochasticsPluginData.class).get(); + + // retrieve the simulation state + stateData.simulationState = outputConsumer.getOutputItem(SimulationState.class).get(); + + // retrieve the run continuity plugin data + stateData.runContinuityPluginData = outputConsumer.getOutputItem(RunContinuityPluginData.class).get(); + + Optional optional = outputConsumer.getOutputItem(String.class); + if (optional.isPresent()) { + stateData.output = optional.get(); + } + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/datamanagers/AT_GroupsPluginData.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/datamanagers/AT_GroupsPluginData.java new file mode 100644 index 000000000..11676f127 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/datamanagers/AT_GroupsPluginData.java @@ -0,0 +1,1557 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.datamanagers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.apache.commons.math3.util.FastMath; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.plugins.groups.datamanagers.GroupsPluginData.GroupSpecification; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupError; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupId; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupPropertyValue; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupTypeId; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.GroupsTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.TestGroupPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.TestGroupTypeId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; +import util.wrappers.MultiKey; +import util.wrappers.MutableInteger; + +public class AT_GroupsPluginData { + + @Test + @UnitTestMethod(target = GroupsPluginData.class, name = "builder", args = {}) + public void testBuilder() { + assertNotNull(GroupsPluginData.builder()); + } + + @Test + @UnitTestMethod(target = GroupsPluginData.Builder.class, name = "build", args = {}) + public void testBuild() { + // show that the builder does not return null + assertNotNull(GroupsPluginData.builder().build()); + + // show that the builder clears its state on build invocation + + GroupsPluginData groupInitialData = GroupsPluginData.builder() // + .associatePersonToGroup(new GroupId(0), new PersonId(0))// + .addGroupTypeId(TestGroupTypeId.GROUP_TYPE_1)// + .addGroup(new GroupId(0), TestGroupTypeId.GROUP_TYPE_1)// + .defineGroupProperty(TestGroupTypeId.GROUP_TYPE_1, + TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, + TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK.getPropertyDefinition())// + .setGroupPropertyValue(new GroupId(0), TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, + true)// + .build();// + + assertFalse(groupInitialData.getGroupIds().isEmpty()); + assertFalse(groupInitialData.getGroupTypeIds().isEmpty()); + + groupInitialData = GroupsPluginData.builder().build(); + assertTrue(groupInitialData.getGroupIds().isEmpty()); + assertTrue(groupInitialData.getGroupTypeIds().isEmpty()); + + // precondition test: if a person was added to a group that was not + // defined + ContractException contractException = assertThrows(ContractException.class, () -> { + GroupsPluginData.Builder builder = GroupsPluginData.builder(); + builder.associatePersonToGroup(new GroupId(0), new PersonId(0)).build(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); + + // precondition test: if a group was added with a group type id that was + // not + // defined + contractException = assertThrows(ContractException.class, () -> { + GroupsPluginData.Builder builder = GroupsPluginData.builder(); + builder.addGroup(new GroupId(0), TestGroupTypeId.GROUP_TYPE_1).build(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); + + // precondition test: if a group property definition was defined for a + // group + // type id that + // was not defined. + contractException = assertThrows(ContractException.class, () -> { + GroupsPluginData.Builder builder = GroupsPluginData.builder(); + builder.defineGroupProperty(TestGroupTypeId.GROUP_TYPE_1, // + TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, // + TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK.getPropertyDefinition()// + ).build(); + }); + + assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); + + // precondition test: if a group property value was set for a group id + // that was + // not + // defined. + contractException = assertThrows(ContractException.class, () -> { + GroupsPluginData.Builder builder = GroupsPluginData.builder(); + builder.addGroupTypeId(TestGroupTypeId.GROUP_TYPE_1); + builder.defineGroupProperty(TestGroupTypeId.GROUP_TYPE_1, + TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, + TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK.getPropertyDefinition()); + builder.setGroupPropertyValue(new GroupId(0), + TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, true); + builder.build(); + }); + assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); + + // precondition test: if a group property value is added for a group + // property id + // that is + // not associated with the group. + contractException = assertThrows(ContractException.class, () -> { + GroupsPluginData.Builder builder = GroupsPluginData.builder(); + builder.addGroupTypeId(TestGroupTypeId.GROUP_TYPE_1); + builder.defineGroupProperty(TestGroupTypeId.GROUP_TYPE_1, + TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, + TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK.getPropertyDefinition()); + builder.setGroupPropertyValue(new GroupId(0), + TestGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, 15); + builder.addGroup(new GroupId(0), TestGroupTypeId.GROUP_TYPE_1); + builder.build(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + // precondition test: if a group property value is added that is + // incompatible + // with the + // corresponding property definition + contractException = assertThrows(ContractException.class, () -> { + GroupsPluginData.Builder builder = GroupsPluginData.builder(); + builder.addGroupTypeId(TestGroupTypeId.GROUP_TYPE_1); + builder.defineGroupProperty(TestGroupTypeId.GROUP_TYPE_1, + TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, + TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK.getPropertyDefinition()); + builder.setGroupPropertyValue(new GroupId(0), + TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, 15); + builder.addGroup(new GroupId(0), TestGroupTypeId.GROUP_TYPE_1); + builder.build(); + }); + assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); + + /* + * precondition test: if a group does not have a group property value assigned + * when the corresponding property definition lacks a default value. + */ + contractException = assertThrows(ContractException.class, () -> { + GroupsPluginData.Builder builder = GroupsPluginData.builder(); + builder.addGroupTypeId(TestGroupTypeId.GROUP_TYPE_1); + builder.defineGroupProperty(TestGroupTypeId.GROUP_TYPE_1, + TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, + PropertyDefinition.builder().setType(Boolean.class).build()); + builder.addGroup(new GroupId(0), TestGroupTypeId.GROUP_TYPE_1); + builder.build(); + }); + assertEquals(PropertyError.INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupsPluginData.Builder.class, name = "addGroupTypeId", args = { GroupTypeId.class }) + public void testAddGroupTypeId() { + GroupsPluginData.Builder builder = GroupsPluginData.builder(); + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + builder.addGroupTypeId(testGroupTypeId); + builder.addGroupTypeId(testGroupTypeId); + } + GroupsPluginData groupInitialData = builder.build(); + + // show that the group type ids exist in the groupInitialData + assertEquals(EnumSet.allOf(TestGroupTypeId.class), groupInitialData.getGroupTypeIds()); + + // precondition test: if the group type id is null + ContractException contractException = assertThrows(ContractException.class, + () -> GroupsPluginData.builder().addGroupTypeId(null)); + assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupsPluginData.Builder.class, name = "addGroup", args = { GroupId.class, + GroupTypeId.class }) + public void testAddGroup() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6428717083105095287L); + GroupsPluginData.Builder builder = GroupsPluginData.builder(); + Map> expectedGroupIds = new LinkedHashMap<>(); + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + expectedGroupIds.put(testGroupTypeId, new LinkedHashSet<>()); + builder.addGroupTypeId(testGroupTypeId); + } + + for (int i = 0; i < 100; i++) { + TestGroupTypeId randomGroupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + + GroupId groupId = new GroupId(i); + builder.addGroup(groupId, randomGroupTypeId); + // adding duplicate group data to show the last value persists + randomGroupTypeId = randomGroupTypeId.next(); + builder.addGroup(groupId, randomGroupTypeId); + expectedGroupIds.get(randomGroupTypeId).add(groupId); + } + + Map> actualGroupIds = new LinkedHashMap<>(); + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + actualGroupIds.put(testGroupTypeId, new LinkedHashSet<>()); + } + + GroupsPluginData groupsPluginData = builder.build(); + for (GroupId groupId : groupsPluginData.getGroupIds()) { + GroupTypeId groupTypeId = groupsPluginData.getGroupTypeId(groupId); + actualGroupIds.get(groupTypeId).add(groupId); + } + + // show that the group ids that were added are present in the + // groupInitialData + assertEquals(expectedGroupIds, actualGroupIds); + + // precondition test: if the group id is null + ContractException contractException = assertThrows(ContractException.class, + () -> GroupsPluginData.builder().addGroup(null, TestGroupTypeId.GROUP_TYPE_1)); + assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); + + // precondition test: if the group type id is null + contractException = assertThrows(ContractException.class, + () -> GroupsPluginData.builder().addGroup(new GroupId(0), null)); + assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupsPluginData.Builder.class, name = "defineGroupProperty", args = { GroupTypeId.class, + GroupPropertyId.class, PropertyDefinition.class }) + public void testDefineGroupProperty() { + GroupsPluginData.Builder builder = GroupsPluginData.builder(); + + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + builder.addGroupTypeId(testGroupTypeId); + } + + // showing that duplicate values persist + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { + PropertyDefinition propertyDefinition = testGroupPropertyId.next().getPropertyDefinition(); + builder.defineGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId, + propertyDefinition); + propertyDefinition = testGroupPropertyId.getPropertyDefinition(); + builder.defineGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId, + propertyDefinition); + } + + GroupsPluginData groupInitialData = builder.build(); + + // show that each property definition that was added is present in the + // groupInitialData + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { + PropertyDefinition expectedPropertyDefinition = testGroupPropertyId.getPropertyDefinition(); + PropertyDefinition actualPropertyDefinition = groupInitialData + .getGroupPropertyDefinition(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId); + assertEquals(expectedPropertyDefinition, actualPropertyDefinition); + } + + TestGroupTypeId testGroupTypeId = TestGroupTypeId.GROUP_TYPE_1; + TestGroupPropertyId groupPropertyId = TestGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK; + PropertyDefinition propertyDefinition = groupPropertyId.getPropertyDefinition(); + + // precondition test: if the group type id is null + ContractException contractException = assertThrows(ContractException.class, + () -> GroupsPluginData.builder().defineGroupProperty(null, groupPropertyId, propertyDefinition)); + assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); + + // precondition test: if the group property id is null + contractException = assertThrows(ContractException.class, + () -> GroupsPluginData.builder().defineGroupProperty(testGroupTypeId, null, propertyDefinition)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // precondition test: if the property definition is null + contractException = assertThrows(ContractException.class, + () -> GroupsPluginData.builder().defineGroupProperty(testGroupTypeId, groupPropertyId, null)); + assertEquals(PropertyError.NULL_PROPERTY_DEFINITION, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupsPluginData.Builder.class, name = "setGroupPropertyValue", args = { GroupId.class, + GroupPropertyId.class, Object.class }) + public void testSetGroupPropertyValue() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(206512993284256660L); + + GroupsPluginData.Builder builder = GroupsPluginData.builder(); + + // add in the group types + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + builder.addGroupTypeId(testGroupTypeId); + } + + // define the group properties + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { + builder.defineGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId, + testGroupPropertyId.getPropertyDefinition()); + } + + // create a container to hold expected values + Set expectedValues = new LinkedHashSet<>(); + + /* + * Add a few groups and set about half of the property values, leaving the other + * half to be defined by the default values of the corresponding property + * definitions. + */ + TestGroupTypeId testGroupTypeId = TestGroupTypeId.GROUP_TYPE_1; + for (int i = 0; i < 10; i++) { + GroupId groupId = new GroupId(i); + builder.addGroup(groupId, testGroupTypeId); + + Set testGroupPropertyIds = TestGroupPropertyId + .getTestGroupPropertyIds(testGroupTypeId); + for (TestGroupPropertyId testGroupPropertyId : testGroupPropertyIds) { + if (randomGenerator.nextBoolean()) { + Object value = testGroupPropertyId.getRandomPropertyValue(randomGenerator); + builder.setGroupPropertyValue(groupId, testGroupPropertyId, value); + value = testGroupPropertyId.getRandomPropertyValue(randomGenerator); + builder.setGroupPropertyValue(groupId, testGroupPropertyId, value); + expectedValues.add(new MultiKey(groupId, testGroupPropertyId, value)); + } + } + // move to the next group type id + testGroupTypeId = testGroupTypeId.next(); + } + + // build the group initial data + GroupsPluginData groupInitialData = builder.build(); + + // show that the expected group property values are present + Set actualValues = new LinkedHashSet<>(); + for (GroupId groupId : groupInitialData.getGroupIds()) { + for (GroupPropertyValue groupPropertyValue : groupInitialData.getGroupPropertyValues(groupId)) { + MultiKey multiKey = new MultiKey(groupId, groupPropertyValue.groupPropertyId(), + groupPropertyValue.value()); + actualValues.add(multiKey); + } + } + + assertEquals(expectedValues, actualValues); + + TestGroupPropertyId groupPropertyId = TestGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK; + + // precondition test: if the group id is null + ContractException contractException = assertThrows(ContractException.class, + () -> GroupsPluginData.builder().setGroupPropertyValue(null, groupPropertyId, 10)); + assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); + + // precondition test: if the group property id is null + contractException = assertThrows(ContractException.class, + () -> GroupsPluginData.builder().setGroupPropertyValue(new GroupId(0), null, 10)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // precondition test: if the group property value is null + contractException = assertThrows(ContractException.class, + () -> GroupsPluginData.builder().setGroupPropertyValue(new GroupId(0), groupPropertyId, null)); + assertEquals(PropertyError.NULL_PROPERTY_VALUE, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupsPluginData.Builder.class, name = "setNextGroupIdValue", args = { int.class }) + public void testSetNextGroupIdValue() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2784010859295212357L); + + GroupsPluginData.Builder groupPluginDataBuilder = GroupsPluginData.builder(); + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + groupPluginDataBuilder.addGroupTypeId(testGroupTypeId); + } + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { + groupPluginDataBuilder.defineGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId, + testGroupPropertyId.getPropertyDefinition()); + } + + int nextGroupId = randomGenerator.nextInt(100); + groupPluginDataBuilder.setNextGroupIdValue(nextGroupId); + + GroupsPluginData pluginData = groupPluginDataBuilder.build(); + + assertEquals(nextGroupId, pluginData.getNextGroupIdValue()); + + // preconditions: + // value is negative + ContractException contractException = assertThrows(ContractException.class, () -> { + GroupsPluginData.builder().setNextGroupIdValue(-1); + }); + + assertEquals(GroupError.NEGATIVE_GROUP_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupsPluginData.Builder.class, name = "associatePersonToGroup", args = { GroupId.class, + PersonId.class }) + public void testAssociatePersonToGroup() { + + Random random = new Random(7282493148489771700L); + + Map expectedGroupAssignments = new LinkedHashMap<>(); + + GroupsPluginData.Builder builder = GroupsPluginData.builder(); + // add in the group types + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + builder.addGroupTypeId(testGroupTypeId); + } + + // create some people + List personIds = new ArrayList<>(); + for (int i = 0; i < 100; i++) { + personIds.add(i); + } + + /* + * Add a few groups and add to those groups 0 to 9 randomly selected people. + * Record the assignments in the expected data structure. + */ + TestGroupTypeId testGroupTypeId = TestGroupTypeId.GROUP_TYPE_1; + for (int i = 0; i < 20; i++) { + // add the group + + builder.addGroup(new GroupId(i), testGroupTypeId); + testGroupTypeId = testGroupTypeId.next(); + // select some people and add them to the group + Collections.shuffle(personIds, random); + int count = random.nextInt(10); + for (int j = 0; j < count; j++) { + builder.associatePersonToGroup(new GroupId(i), new PersonId(personIds.get(j))); + MultiKey multiKey = new MultiKey(new GroupId(i), new PersonId(personIds.get(j))); + expectedGroupAssignments.putIfAbsent(multiKey, new MutableInteger()); + expectedGroupAssignments.get(multiKey).increment(); + } + } + + // build the group initial data + GroupsPluginData groupsPluginData = builder.build(); + + // show that the group memberships are as expected + Map actualGroupAssignments = new LinkedHashMap<>(); + + for (int i = 0; i < groupsPluginData.getPersonCount(); i++) { + PersonId personId = new PersonId(i); + for (GroupId groupId : groupsPluginData.getGroupsForPerson(personId)) { + MultiKey multiKey = new MultiKey(groupId, personId); + actualGroupAssignments.putIfAbsent(multiKey, new MutableInteger()); + actualGroupAssignments.get(multiKey).increment(); + } + } + + assertEquals(expectedGroupAssignments, actualGroupAssignments); + + // precondition test: if the group id is null + ContractException contractException = assertThrows(ContractException.class, + () -> GroupsPluginData.builder().associatePersonToGroup(null, new PersonId(0))); + assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); + + // precondition test: if the person id is null + contractException = assertThrows(ContractException.class, + () -> GroupsPluginData.builder().associatePersonToGroup(new GroupId(0), null)); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupsPluginData.class, name = "getGroupPropertyDefinition", args = { GroupTypeId.class, + GroupPropertyId.class }) + public void testGetGroupPropertyDefinition() { + + GroupsPluginData.Builder builder = GroupsPluginData.builder(); + + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + builder.addGroupTypeId(testGroupTypeId); + } + + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { + + builder.defineGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId, + testGroupPropertyId.getPropertyDefinition()); + } + + GroupsPluginData groupInitialData = builder.build(); + + // show that each property definition that was added is present in the + // groupInitialData + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { + PropertyDefinition expectedPropertyDefinition = testGroupPropertyId.getPropertyDefinition(); + PropertyDefinition actualPropertyDefinition = groupInitialData + .getGroupPropertyDefinition(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId); + assertEquals(expectedPropertyDefinition, actualPropertyDefinition); + } + + // precondition tests + + // if the group type id is null + ContractException contractException = assertThrows(ContractException.class, () -> groupInitialData + .getGroupPropertyDefinition(null, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK)); + assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); + + // if the group type id is unknown + contractException = assertThrows(ContractException.class, + () -> groupInitialData.getGroupPropertyDefinition(TestGroupTypeId.getUnknownGroupTypeId(), + TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK)); + assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); + + // if the group property id is null + contractException = assertThrows(ContractException.class, + () -> groupInitialData.getGroupPropertyDefinition(TestGroupTypeId.GROUP_TYPE_1, null)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // if the group property id is not associated with the group type id via + // a property definition + contractException = assertThrows(ContractException.class, + () -> groupInitialData.getGroupPropertyDefinition(TestGroupTypeId.GROUP_TYPE_1, + TestGroupPropertyId.getUnknownGroupPropertyId())); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + // if the group property id is not associated with the group type id via + // a property definition + contractException = assertThrows(ContractException.class, + () -> groupInitialData.getGroupPropertyDefinition(TestGroupTypeId.GROUP_TYPE_1, + TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK)); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupsPluginData.class, name = "getGroupPropertyIds", args = { GroupTypeId.class }) + public void testGetGroupPropertyIds() { + + GroupsPluginData.Builder builder = GroupsPluginData.builder(); + + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + builder.addGroupTypeId(testGroupTypeId); + } + + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { + builder.defineGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId, + testGroupPropertyId.getPropertyDefinition()); + } + + GroupsPluginData groupInitialData = builder.build(); + + // show that the group properties for each group type match expectations + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + Set actualGroupPropertyIds = groupInitialData.getGroupPropertyIds(testGroupTypeId); + Set expectedGroupPropertyIds = TestGroupPropertyId + .getTestGroupPropertyIds(testGroupTypeId); + assertEquals(expectedGroupPropertyIds, actualGroupPropertyIds); + } + + // precondition tests + // if the group type id is null + ContractException contractException = assertThrows(ContractException.class, + () -> groupInitialData.getGroupPropertyIds(null)); + assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); + + // if the group type id is unknown + contractException = assertThrows(ContractException.class, + () -> groupInitialData.getGroupPropertyIds(TestGroupTypeId.getUnknownGroupTypeId())); + assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupsPluginData.class, name = "getGroupPropertyValues", args = { GroupId.class }) + public void testGetGroupPropertyValues() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8435308203966252001L); + + GroupsPluginData.Builder builder = GroupsPluginData.builder(); + + // add in the group types + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + builder.addGroupTypeId(testGroupTypeId); + } + + // define the group properties + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { + builder.defineGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId, + testGroupPropertyId.getPropertyDefinition()); + } + + // create a container to hold expected values + Set expectedValues = new LinkedHashSet<>(); + + /* + * Add a few groups and set about half of the property values, leaving the other + * half to be defined by the default values of the corresponding property + * definitions. + */ + int groupCount = 10; + + TestGroupTypeId testGroupTypeId = TestGroupTypeId.GROUP_TYPE_1; + for (int i = 0; i < groupCount; i++) { + GroupId groupId = new GroupId(i); + builder.addGroup(groupId, testGroupTypeId); + + Set testGroupPropertyIds = TestGroupPropertyId + .getTestGroupPropertyIds(testGroupTypeId); + for (TestGroupPropertyId testGroupPropertyId : testGroupPropertyIds) { + if (randomGenerator.nextBoolean()) { + Object value = testGroupPropertyId.getRandomPropertyValue(randomGenerator); + builder.setGroupPropertyValue(groupId, testGroupPropertyId, value); + expectedValues.add(new MultiKey(groupId, testGroupPropertyId, value)); + } + } + // move to the next group type id + testGroupTypeId = testGroupTypeId.next(); + } + + // build the group initial data + GroupsPluginData groupsPluginData = builder.build(); + + // show that the expected group property values are present + Set actualValues = new LinkedHashSet<>(); + for (GroupId groupId : groupsPluginData.getGroupIds()) { + for (GroupPropertyValue groupPropertyValue : groupsPluginData.getGroupPropertyValues(groupId)) { + MultiKey multiKey = new MultiKey(groupId, groupPropertyValue.groupPropertyId(), + groupPropertyValue.value()); + actualValues.add(multiKey); + } + } + + assertEquals(expectedValues, actualValues); + + // precondition tests + + // if the group id is null + ContractException contractException = assertThrows(ContractException.class, + () -> groupsPluginData.getGroupPropertyValues(null)); + assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); + + // if the group id is unknown + contractException = assertThrows(ContractException.class, + () -> groupsPluginData.getGroupPropertyValues(new GroupId(10000))); + assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupsPluginData.class, name = "getGroupTypeIds", args = {}) + public void testGetGroupTypeIds() { + GroupsPluginData.Builder builder = GroupsPluginData.builder(); + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + builder.addGroupTypeId(testGroupTypeId); + } + GroupsPluginData groupInitialData = builder.build(); + + // show that the group type ids exist in the groupInitialData + assertEquals(EnumSet.allOf(TestGroupTypeId.class), groupInitialData.getGroupTypeIds()); + } + + @Test + @UnitTestMethod(target = GroupsPluginData.class, name = "getGroupIds", args = {}) + public void testGetGroupIds() { + GroupsPluginData.Builder builder = GroupsPluginData.builder(); + int masterGroupId = 0; + Set expectedGroupIds = new LinkedHashSet<>(); + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + builder.addGroupTypeId(testGroupTypeId); + + GroupId groupId = new GroupId(masterGroupId++); + builder.addGroup(groupId, testGroupTypeId); + expectedGroupIds.add(groupId); + + groupId = new GroupId(masterGroupId++); + builder.addGroup(groupId, testGroupTypeId); + expectedGroupIds.add(groupId); + } + GroupsPluginData groupInitialData = builder.build(); + + // show that the group ids that were added are present in the + // groupInitialData + assertEquals(expectedGroupIds, new LinkedHashSet<>(groupInitialData.getGroupIds())); + + } + + @Test + @UnitTestMethod(target = GroupsPluginData.class, name = "getGroupsForPerson", args = { PersonId.class }) + public void testGetGroupsForPerson() { + + Random random = new Random(4685636461674441597L); + + Set expectedGroupAssignments = new LinkedHashSet<>(); + + GroupsPluginData.Builder builder = GroupsPluginData.builder(); + // add in the group types + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + builder.addGroupTypeId(testGroupTypeId); + } + + // create some people + List people = new ArrayList<>(); + for (int i = 0; i < 100; i++) { + people.add(new PersonId(i)); + } + + /* + * Add a few groups and add to those groups 0 to 9 randomly selected people. + * Record the assignments in the expected data structure. + */ + TestGroupTypeId testGroupTypeId = TestGroupTypeId.GROUP_TYPE_1; + for (int i = 0; i < 20; i++) { + // add the group + GroupId groupId = new GroupId(i); + builder.addGroup(groupId, testGroupTypeId); + + testGroupTypeId = testGroupTypeId.next(); + + // select some people and add them to the group + Collections.shuffle(people, random); + int count = random.nextInt(10); + for (int j = 0; j < count; j++) { + PersonId personId = people.get(j); + builder.associatePersonToGroup(groupId, personId); + MultiKey multiKey = new MultiKey(groupId, personId); + expectedGroupAssignments.add(multiKey); + } + } + + // build the group initial data + GroupsPluginData groupInitialData = builder.build(); + + // show that the group memberships are as expected + Set actualGroupAssignments = new LinkedHashSet<>(); + for (int i = 0; i < groupInitialData.getPersonCount(); i++) { + PersonId personId = new PersonId(i); + for (GroupId groupId : groupInitialData.getGroupsForPerson(personId)) { + MultiKey multiKey = new MultiKey(groupId, personId); + actualGroupAssignments.add(multiKey); + } + } + + assertEquals(expectedGroupAssignments, actualGroupAssignments); + + } + + @Test + @UnitTestMethod(target = GroupsPluginData.class, name = "getGroupTypeId", args = { GroupId.class }) + public void testGetGroupTypeId() { + + GroupsPluginData.Builder builder = GroupsPluginData.builder(); + int masterGroupId = 0; + Map expectedGroupTypes = new LinkedHashMap<>(); + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + builder.addGroupTypeId(testGroupTypeId); + + GroupId groupId = new GroupId(masterGroupId++); + builder.addGroup(groupId, testGroupTypeId); + expectedGroupTypes.put(groupId, testGroupTypeId); + + groupId = new GroupId(masterGroupId++); + builder.addGroup(groupId, testGroupTypeId); + expectedGroupTypes.put(groupId, testGroupTypeId); + } + GroupsPluginData groupInitialData = builder.build(); + + for (GroupId groupId : expectedGroupTypes.keySet()) { + GroupTypeId expecctedGroupTypeId = expectedGroupTypes.get(groupId); + GroupTypeId actualGroupTypeId = groupInitialData.getGroupTypeId(groupId); + assertEquals(expecctedGroupTypeId, actualGroupTypeId); + } + + // precondition tests + + // if the group id is null + ContractException contractException = assertThrows(ContractException.class, + () -> groupInitialData.getGroupTypeId(null)); + assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); + + // if the group id is unknown + contractException = assertThrows(ContractException.class, + () -> groupInitialData.getGroupTypeId(new GroupId(1000000))); + assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupsPluginData.class, name = "getCloneBuilder", args = {}) + public void testGetCloneBuilder() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(9130589441333999144L); + + GroupsPluginData.Builder groupPluginDataBuilder = GroupsPluginData.builder(); + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + groupPluginDataBuilder.addGroupTypeId(testGroupTypeId); + } + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { + groupPluginDataBuilder.defineGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId, + testGroupPropertyId.getPropertyDefinition()); + } + int groupCount = 10; + List groups = new ArrayList<>(); + for (int i = 0; i < groupCount; i++) { + GroupId groupId = new GroupId(i); + groups.add(groupId); + TestGroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + groupPluginDataBuilder.addGroup(groupId, groupTypeId); + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.getTestGroupPropertyIds(groupTypeId)) { + if (randomGenerator.nextBoolean()) { + Object randomPropertyValue = testGroupPropertyId.getRandomPropertyValue(randomGenerator); + groupPluginDataBuilder.setGroupPropertyValue(groupId, testGroupPropertyId, randomPropertyValue); + } + } + } + + int personCount = 100; + + for (int i = 0; i < personCount; i++) { + PersonId personId = new PersonId(i); + int numberOfGroups = randomGenerator.nextInt(5); + Collections.sort(groups); + for (int j = 0; j < numberOfGroups; j++) { + GroupId groupId = groups.get(j); + groupPluginDataBuilder.associatePersonToGroup(groupId, personId); + } + } + GroupsPluginData groupsPluginData = groupPluginDataBuilder.build(); + + PluginData pluginData = groupsPluginData.getCloneBuilder().build(); + + // show that the clone plugin data has the correct type + assertTrue(pluginData instanceof GroupsPluginData); + + GroupsPluginData cloneGroupPluginData = (GroupsPluginData) pluginData; + + // show that the two plugin datas have the same groups + assertEquals(groupsPluginData.getGroupIds(), cloneGroupPluginData.getGroupIds()); + + // show that the two plugin datas have the same group types + assertEquals(groupsPluginData.getGroupTypeIds(), cloneGroupPluginData.getGroupTypeIds()); + + // show that the two plugin datas have the same group property ids + for (GroupTypeId groupTypeId : groupsPluginData.getGroupTypeIds()) { + assertEquals(groupsPluginData.getGroupPropertyIds(groupTypeId), + cloneGroupPluginData.getGroupPropertyIds(groupTypeId)); + // show that the two plugin datas have the same group property + // definitions + for (GroupPropertyId groupPropertyId : groupsPluginData.getGroupPropertyIds(groupTypeId)) { + PropertyDefinition expectedPropertyDefinition = groupsPluginData.getGroupPropertyDefinition(groupTypeId, + groupPropertyId); + PropertyDefinition actualPropertyDefinition = cloneGroupPluginData + .getGroupPropertyDefinition(groupTypeId, groupPropertyId); + assertEquals(expectedPropertyDefinition, actualPropertyDefinition); + } + } + + // show that the two plugin datas have the same groups + assertEquals(groupsPluginData.getGroupIds(), cloneGroupPluginData.getGroupIds()); + + // show that the groups have the same types + for (GroupId groupId : groupsPluginData.getGroupIds()) { + GroupTypeId expectedGroupTypeId = groupsPluginData.getGroupTypeId(groupId); + GroupTypeId actualGroupTypeId = cloneGroupPluginData.getGroupTypeId(groupId); + assertEquals(expectedGroupTypeId, actualGroupTypeId); + // show that the groups have the property values + Map expectedPropertyValues = new LinkedHashMap<>(); + Map actualPropertyValues = new LinkedHashMap<>(); + for (GroupPropertyValue groupPropertyValue : groupsPluginData.getGroupPropertyValues(groupId)) { + GroupPropertyId groupPropertyId = groupPropertyValue.groupPropertyId(); + Object value = groupPropertyValue.value(); + expectedPropertyValues.put(groupPropertyId, value); + } + for (GroupPropertyValue groupPropertyValue : cloneGroupPluginData.getGroupPropertyValues(groupId)) { + GroupPropertyId groupPropertyId = groupPropertyValue.groupPropertyId(); + Object value = groupPropertyValue.value(); + actualPropertyValues.put(groupPropertyId, value); + } + assertEquals(expectedPropertyValues, actualPropertyValues); + + // show that the groups have the members + assertEquals(groupsPluginData.getPersonCount(), cloneGroupPluginData.getPersonCount()); + for (int i = 0; i < groupsPluginData.getPersonCount(); i++) { + PersonId personId = new PersonId(i); + Set expectedGroups = new LinkedHashSet<>(groupsPluginData.getGroupsForPerson(personId)); + Set actualGroups = new LinkedHashSet<>(cloneGroupPluginData.getGroupsForPerson(personId)); + assertEquals(expectedGroups, actualGroups); + } + } + } + + @Test + @UnitTestMethod(target = GroupsPluginData.class, name = "getPersonCount", args = {}) + public void testGetPersonCount() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7910883193674079461L); + + GroupsPluginData.Builder groupPluginDataBuilder = GroupsPluginData.builder(); + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + groupPluginDataBuilder.addGroupTypeId(testGroupTypeId); + } + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { + groupPluginDataBuilder.defineGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId, + testGroupPropertyId.getPropertyDefinition()); + } + int groupCount = 10; + List groups = new ArrayList<>(); + for (int i = 0; i < groupCount; i++) { + GroupId groupId = new GroupId(i); + groups.add(groupId); + TestGroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + groupPluginDataBuilder.addGroup(groupId, groupTypeId); + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.getTestGroupPropertyIds(groupTypeId)) { + if (randomGenerator.nextBoolean()) { + Object randomPropertyValue = testGroupPropertyId.getRandomPropertyValue(randomGenerator); + groupPluginDataBuilder.setGroupPropertyValue(groupId, testGroupPropertyId, randomPropertyValue); + } + } + } + + int totalPeopleCount = 0; + for (GroupId groupId : groups) { + int peopleCount = randomGenerator.nextInt(100); + int currTotalPeopleCount = totalPeopleCount; + totalPeopleCount += peopleCount; + for (int i = currTotalPeopleCount; i < totalPeopleCount; i++) { + PersonId personId = new PersonId(i); + groupPluginDataBuilder.associatePersonToGroup(groupId, personId); + } + } + + GroupsPluginData groupsPluginData = groupPluginDataBuilder.build(); + + // show that the total people count is NOT 0 + assertTrue(totalPeopleCount > 0); + + // show that the group person count is equal to the total people count + assertEquals(totalPeopleCount, groupsPluginData.getPersonCount()); + } + + @Test + @UnitTestMethod(target = GroupsPluginData.class, name = "getNextGroupIdValue", args = {}) + public void testGetNextGroupIdValue() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2784010859295212357L); + + GroupsPluginData.Builder groupPluginDataBuilder = GroupsPluginData.builder(); + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + groupPluginDataBuilder.addGroupTypeId(testGroupTypeId); + } + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { + groupPluginDataBuilder.defineGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId, + testGroupPropertyId.getPropertyDefinition()); + } + int groupCount = 10; + List groups = new ArrayList<>(); + for (int i = 0; i < groupCount; i++) { + GroupId groupId = new GroupId(i); + groups.add(groupId); + TestGroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + groupPluginDataBuilder.addGroup(groupId, groupTypeId); + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.getTestGroupPropertyIds(groupTypeId)) { + if (randomGenerator.nextBoolean()) { + Object randomPropertyValue = testGroupPropertyId.getRandomPropertyValue(randomGenerator); + groupPluginDataBuilder.setGroupPropertyValue(groupId, testGroupPropertyId, randomPropertyValue); + } + } + } + + GroupsPluginData pluginData = groupPluginDataBuilder.build(); + + assertEquals(groupCount, pluginData.getNextGroupIdValue()); + + pluginData = groupPluginDataBuilder.setNextGroupIdValue(groupCount + 10).build(); + + assertEquals(groupCount + 10, pluginData.getNextGroupIdValue()); + } + + @Test + @UnitTestMethod(target = GroupsPluginData.class, name = "getGroupCount", args = {}) + public void testGetGroupCount() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(669154335225022584L); + + for (int i = 0; i < 10; i++) { + GroupsPluginData.Builder groupPluginDataBuilder = GroupsPluginData.builder(); + + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.getShuffledTestGroupTypeIds(randomGenerator)) { + groupPluginDataBuilder.addGroupTypeId(testGroupTypeId); + } + + int groupCount = randomGenerator.nextInt(50) + 10; + List groups = new ArrayList<>(); + for (int j = 0; j < groupCount; j++) { + GroupId groupId = new GroupId(j); + groups.add(groupId); + TestGroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + groupPluginDataBuilder.addGroup(groupId, groupTypeId); + } + + int totalPeopleCount = 0; + for (GroupId groupId : groups) { + int peopleCount = randomGenerator.nextInt(100); + int currTotalPeopleCount = totalPeopleCount; + totalPeopleCount += peopleCount; + for (int j = currTotalPeopleCount; j < totalPeopleCount; j++) { + PersonId personId = new PersonId(j); + groupPluginDataBuilder.associatePersonToGroup(groupId, personId); + } + } + + GroupsPluginData pluginData = groupPluginDataBuilder.build(); + + assertEquals(groupCount, pluginData.getGroupCount()); + } + } + + @Test + @UnitTestMethod(target = GroupsPluginData.class, name = "getPeopleForGroup", args = { GroupId.class }) + public void testGetPeopleForGroup() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4825370554814301264L); + + for (int i = 0; i < 10; i++) { + GroupsPluginData.Builder groupPluginDataBuilder = GroupsPluginData.builder(); + + List> groupToPeopleMemberships = new ArrayList<>(); + + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.getShuffledTestGroupTypeIds(randomGenerator)) { + groupPluginDataBuilder.addGroupTypeId(testGroupTypeId); + } + + int groupCount = 10; + List groups = new ArrayList<>(); + for (int j = 0; j < groupCount; j++) { + GroupId groupId = new GroupId(j); + groups.add(groupId); + TestGroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + groupPluginDataBuilder.addGroup(groupId, groupTypeId); + } + + int totalPeopleCount = 0; + for (GroupId groupId : groups) { + int peopleCount = randomGenerator.nextInt(100); + int currTotalPeopleCount = totalPeopleCount; + totalPeopleCount += peopleCount; + for (int j = currTotalPeopleCount; j < totalPeopleCount; j++) { + PersonId personId = new PersonId(j); + groupPluginDataBuilder.associatePersonToGroup(groupId, personId); + + int groupIndex = groupId.getValue(); + + while (groupIndex >= groupToPeopleMemberships.size()) { + groupToPeopleMemberships.add(null); + } + + List people = groupToPeopleMemberships.get(groupIndex); + if (people == null) { + people = new ArrayList<>(); + groupToPeopleMemberships.set(groupIndex, people); + } + + people.add(personId); + } + } + + GroupsPluginData pluginData = groupPluginDataBuilder.build(); + + for (int j = 0; j < groupToPeopleMemberships.size(); j++) { + List personIds = groupToPeopleMemberships.get(j); + + if (personIds == null) { + assertEquals(Collections.unmodifiableList(new ArrayList<>()), + pluginData.getPeopleForGroup(new GroupId(j))); + } else { + assertEquals(personIds, pluginData.getPeopleForGroup(new GroupId(j))); + } + } + } + } + + @Test + @UnitTestMethod(target = GroupsPluginData.class, name = "getGroupPropertyDefinitions", args = {}) + public void testGetPropertyDefinitions() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(9107021630953219230L); + + for (int i = 0; i < 10; i++) { + GroupsPluginData.Builder groupPluginDataBuilder = GroupsPluginData.builder(); + + Map> groupPropertyDefinitions = new LinkedHashMap<>(); + + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.getShuffledTestGroupTypeIds(randomGenerator)) { + groupPluginDataBuilder.addGroupTypeId(testGroupTypeId); + + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId + .getShuffledTestGroupPropertyIds(testGroupTypeId, randomGenerator)) { + groupPluginDataBuilder.defineGroupProperty(testGroupPropertyId.getTestGroupTypeId(), + testGroupPropertyId, testGroupPropertyId.getPropertyDefinition()); + + Map propertyDefinitionsMap = groupPropertyDefinitions + .get(testGroupTypeId); + if (propertyDefinitionsMap == null) { + propertyDefinitionsMap = new LinkedHashMap<>(); + groupPropertyDefinitions.put(testGroupTypeId, propertyDefinitionsMap); + } + propertyDefinitionsMap.put(testGroupPropertyId, testGroupPropertyId.getPropertyDefinition()); + } + } + + GroupsPluginData pluginData = groupPluginDataBuilder.build(); + + assertEquals(groupPropertyDefinitions, pluginData.getGroupPropertyDefinitions()); + } + } + + @Test + @UnitTestMethod(target = GroupsPluginData.class, name = "hashCode", args = {}) + public void testHashCode() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(386194196593528301L); + + List people = new ArrayList<>(); + + for (int i = 0; i < 100; i++) { + people.add(new PersonId(i)); + } + + long sameSeed = randomGenerator.nextLong(); + GroupsPluginData groupsPluginData1 = GroupsTestPluginFactory.getStandardGroupsPluginData(2, 10, people, + sameSeed); + GroupsPluginData groupsPluginData2 = GroupsTestPluginFactory.getStandardGroupsPluginData(2, 10, people, + randomGenerator.nextLong()); + GroupsPluginData groupsPluginData3 = GroupsTestPluginFactory.getStandardGroupsPluginData(2, 5, people, + sameSeed); + GroupsPluginData groupsPluginData4 = GroupsTestPluginFactory.getStandardGroupsPluginData(2, 5, people, + randomGenerator.nextLong()); + GroupsPluginData groupsPluginData5 = GroupsTestPluginFactory.getStandardGroupsPluginData(1, 10, people, + sameSeed); + GroupsPluginData groupsPluginData6 = GroupsTestPluginFactory.getStandardGroupsPluginData(3, 10, people, + randomGenerator.nextLong()); + GroupsPluginData groupsPluginData7 = GroupsTestPluginFactory.getStandardGroupsPluginData(3, 5, people, + sameSeed); + GroupsPluginData groupsPluginData8 = GroupsTestPluginFactory.getStandardGroupsPluginData(3, 5, people, + randomGenerator.nextLong()); + GroupsPluginData groupsPluginData9 = GroupsTestPluginFactory.getStandardGroupsPluginData(2, 10, people, + sameSeed); + + assertEquals(groupsPluginData1.hashCode(), groupsPluginData1.hashCode()); + + assertNotEquals(groupsPluginData1.hashCode(), groupsPluginData2.hashCode()); + assertNotEquals(groupsPluginData1.hashCode(), groupsPluginData3.hashCode()); + assertNotEquals(groupsPluginData1.hashCode(), groupsPluginData4.hashCode()); + assertNotEquals(groupsPluginData1.hashCode(), groupsPluginData5.hashCode()); + assertNotEquals(groupsPluginData1.hashCode(), groupsPluginData6.hashCode()); + assertNotEquals(groupsPluginData1.hashCode(), groupsPluginData7.hashCode()); + assertNotEquals(groupsPluginData1.hashCode(), groupsPluginData8.hashCode()); + + assertNotEquals(groupsPluginData1.hashCode(), groupsPluginData3.hashCode()); + assertNotEquals(groupsPluginData1.hashCode(), groupsPluginData4.hashCode()); + assertNotEquals(groupsPluginData1.hashCode(), groupsPluginData5.hashCode()); + assertNotEquals(groupsPluginData1.hashCode(), groupsPluginData6.hashCode()); + assertNotEquals(groupsPluginData1.hashCode(), groupsPluginData7.hashCode()); + assertNotEquals(groupsPluginData1.hashCode(), groupsPluginData8.hashCode()); + + assertNotEquals(groupsPluginData3.hashCode(), groupsPluginData4.hashCode()); + assertNotEquals(groupsPluginData3.hashCode(), groupsPluginData5.hashCode()); + assertNotEquals(groupsPluginData3.hashCode(), groupsPluginData6.hashCode()); + assertNotEquals(groupsPluginData3.hashCode(), groupsPluginData7.hashCode()); + assertNotEquals(groupsPluginData3.hashCode(), groupsPluginData8.hashCode()); + + assertNotEquals(groupsPluginData4.hashCode(), groupsPluginData5.hashCode()); + assertNotEquals(groupsPluginData4.hashCode(), groupsPluginData6.hashCode()); + assertNotEquals(groupsPluginData4.hashCode(), groupsPluginData7.hashCode()); + assertNotEquals(groupsPluginData4.hashCode(), groupsPluginData8.hashCode()); + + assertNotEquals(groupsPluginData5.hashCode(), groupsPluginData6.hashCode()); + assertNotEquals(groupsPluginData5.hashCode(), groupsPluginData7.hashCode()); + assertNotEquals(groupsPluginData5.hashCode(), groupsPluginData8.hashCode()); + + assertNotEquals(groupsPluginData6.hashCode(), groupsPluginData7.hashCode()); + assertNotEquals(groupsPluginData6.hashCode(), groupsPluginData8.hashCode()); + + assertNotEquals(groupsPluginData7.hashCode(), groupsPluginData8.hashCode()); + + assertEquals(groupsPluginData1.hashCode(), groupsPluginData9.hashCode()); + } + + @Test + @UnitTestMethod(target = GroupsPluginData.class, name = "equals", args = { Object.class }) + public void testEquals() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1974882207275712576L); + + List people = new ArrayList<>(); + + for (int i = 0; i < 100; i++) { + people.add(new PersonId(i)); + } + + long sameSeed = randomGenerator.nextLong(); + GroupsPluginData groupsPluginData1 = GroupsTestPluginFactory.getStandardGroupsPluginData(2, 10, people, + sameSeed); + GroupsPluginData groupsPluginData2 = GroupsTestPluginFactory.getStandardGroupsPluginData(2, 10, people, + randomGenerator.nextLong()); + GroupsPluginData groupsPluginData3 = GroupsTestPluginFactory.getStandardGroupsPluginData(2, 5, people, + sameSeed); + GroupsPluginData groupsPluginData4 = GroupsTestPluginFactory.getStandardGroupsPluginData(2, 5, people, + randomGenerator.nextLong()); + GroupsPluginData groupsPluginData5 = GroupsTestPluginFactory.getStandardGroupsPluginData(1, 10, people, + sameSeed); + GroupsPluginData groupsPluginData6 = GroupsTestPluginFactory.getStandardGroupsPluginData(3, 10, people, + randomGenerator.nextLong()); + GroupsPluginData groupsPluginData7 = GroupsTestPluginFactory.getStandardGroupsPluginData(3, 5, people, + sameSeed); + GroupsPluginData groupsPluginData8 = GroupsTestPluginFactory.getStandardGroupsPluginData(3, 5, people, + randomGenerator.nextLong()); + GroupsPluginData groupsPluginData9 = GroupsTestPluginFactory.getStandardGroupsPluginData(2, 10, people, + sameSeed); + + assertEquals(groupsPluginData1, groupsPluginData1); + + assertNotEquals(groupsPluginData1, null); + + assertNotEquals(groupsPluginData1, new Object()); + + assertNotEquals(groupsPluginData1, groupsPluginData2); + assertNotEquals(groupsPluginData1, groupsPluginData3); + assertNotEquals(groupsPluginData1, groupsPluginData4); + assertNotEquals(groupsPluginData1, groupsPluginData5); + assertNotEquals(groupsPluginData1, groupsPluginData6); + assertNotEquals(groupsPluginData1, groupsPluginData7); + assertNotEquals(groupsPluginData1, groupsPluginData8); + + assertNotEquals(groupsPluginData1, groupsPluginData3); + assertNotEquals(groupsPluginData1, groupsPluginData4); + assertNotEquals(groupsPluginData1, groupsPluginData5); + assertNotEquals(groupsPluginData1, groupsPluginData6); + assertNotEquals(groupsPluginData1, groupsPluginData7); + assertNotEquals(groupsPluginData1, groupsPluginData8); + + assertNotEquals(groupsPluginData3, groupsPluginData4); + assertNotEquals(groupsPluginData3, groupsPluginData5); + assertNotEquals(groupsPluginData3, groupsPluginData6); + assertNotEquals(groupsPluginData3, groupsPluginData7); + assertNotEquals(groupsPluginData3, groupsPluginData8); + + assertNotEquals(groupsPluginData4, groupsPluginData5); + assertNotEquals(groupsPluginData4, groupsPluginData6); + assertNotEquals(groupsPluginData4, groupsPluginData7); + assertNotEquals(groupsPluginData4, groupsPluginData8); + + assertNotEquals(groupsPluginData5, groupsPluginData6); + assertNotEquals(groupsPluginData5, groupsPluginData7); + assertNotEquals(groupsPluginData5, groupsPluginData8); + + assertNotEquals(groupsPluginData6, groupsPluginData7); + assertNotEquals(groupsPluginData6, groupsPluginData8); + + assertNotEquals(groupsPluginData7, groupsPluginData8); + + assertEquals(groupsPluginData1, groupsPluginData9); + + } + + @Test + @UnitTestMethod(target = GroupsPluginData.class, name = "toString", args = {}) + public void testToString() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(523034536833646355L); + + for (int i = 0; i < 10; i++) { + GroupsPluginData.Builder groupPluginDataBuilder = GroupsPluginData.builder(); + + int nextGroupIdValue = -1; + Map> groupPropertyDefinitions = new LinkedHashMap<>(); + Set groupTypeIds = new LinkedHashSet<>(); + List> personToGroupsMemberships = new ArrayList<>(); + List> groupToPeopleMemberships = new ArrayList<>(); + List groupSpecifications = new ArrayList<>(); + + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.getShuffledTestGroupTypeIds(randomGenerator)) { + groupPluginDataBuilder.addGroupTypeId(testGroupTypeId); + groupTypeIds.add(testGroupTypeId); + + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId + .getShuffledTestGroupPropertyIds(testGroupTypeId, randomGenerator)) { + groupPluginDataBuilder.defineGroupProperty(testGroupPropertyId.getTestGroupTypeId(), + testGroupPropertyId, testGroupPropertyId.getPropertyDefinition()); + + Map propertyDefinitionsMap = groupPropertyDefinitions + .get(testGroupTypeId); + if (propertyDefinitionsMap == null) { + propertyDefinitionsMap = new LinkedHashMap<>(); + groupPropertyDefinitions.put(testGroupTypeId, propertyDefinitionsMap); + } + propertyDefinitionsMap.put(testGroupPropertyId, testGroupPropertyId.getPropertyDefinition()); + } + } + + int groupCount = 10; + List groups = new ArrayList<>(); + for (int j = 0; j < groupCount; j++) { + GroupId groupId = new GroupId(j); + groups.add(groupId); + TestGroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + groupPluginDataBuilder.addGroup(groupId, groupTypeId); + + while (j >= groupSpecifications.size()) { + groupSpecifications.add(null); + } + + GroupSpecification groupSpecification = groupSpecifications.get(j); + if (groupSpecification == null) { + groupSpecification = new GroupSpecification(); + groupSpecification.groupId = groupId; + groupSpecifications.set(j, groupSpecification); + } + groupSpecification.groupTypeId = groupTypeId; + + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId + .getTestGroupPropertyIds(groupTypeId)) { + if (randomGenerator.nextBoolean()) { + Object randomPropertyValue = testGroupPropertyId.getRandomPropertyValue(randomGenerator); + groupPluginDataBuilder.setGroupPropertyValue(groupId, testGroupPropertyId, randomPropertyValue); + + while (j >= groupSpecifications.size()) { + groupSpecifications.add(null); + } + + List groupPropertyValues = groupSpecification.groupPropertyValues; + if (groupPropertyValues == null) { + groupPropertyValues = new ArrayList<>(); + groupSpecification.groupPropertyValues = groupPropertyValues; + } + + Iterator iterator = groupSpecification.groupPropertyValues.iterator(); + while (iterator.hasNext()) { + GroupPropertyValue next = iterator.next(); + if (next.groupPropertyId().equals(testGroupPropertyId)) { + iterator.remove(); + break; + } + } + + GroupPropertyValue groupPropertyValue = new GroupPropertyValue(testGroupPropertyId, + randomPropertyValue); + groupPropertyValues.add(groupPropertyValue); + } + } + } + + int totalPeopleCount = 0; + for (GroupId groupId : groups) { + int peopleCount = randomGenerator.nextInt(100); + int currTotalPeopleCount = totalPeopleCount; + totalPeopleCount += peopleCount; + for (int j = currTotalPeopleCount; j < totalPeopleCount; j++) { + PersonId personId = new PersonId(j); + groupPluginDataBuilder.associatePersonToGroup(groupId, personId); + + while (j >= personToGroupsMemberships.size()) { + personToGroupsMemberships.add(null); + } + + List personToGroupMembership = personToGroupsMemberships.get(j); + if (personToGroupMembership == null) { + personToGroupMembership = new ArrayList<>(); + personToGroupsMemberships.set(j, personToGroupMembership); + } + + personToGroupMembership.add(groupId); + + int groupIndex = groupId.getValue(); + + while (groupIndex >= groupToPeopleMemberships.size()) { + groupToPeopleMemberships.add(null); + } + + List people = groupToPeopleMemberships.get(groupIndex); + if (people == null) { + people = new ArrayList<>(); + groupToPeopleMemberships.set(groupIndex, people); + } + + people.add(personId); + } + } + + for (GroupSpecification groupSpecification : groupSpecifications) { + if (groupSpecification != null) { + nextGroupIdValue = FastMath.max(nextGroupIdValue, groupSpecification.groupId.getValue()); + } + } + nextGroupIdValue++; + + GroupsPluginData pluginData = groupPluginDataBuilder.build(); + + StringBuilder dataSb = new StringBuilder(); + dataSb.append("Data [nextGroupIdValue="); + dataSb.append(nextGroupIdValue); + dataSb.append(", groupPropertyDefinitions="); + dataSb.append(groupPropertyDefinitions); + dataSb.append(", groupTypeIds="); + dataSb.append(groupTypeIds); + dataSb.append(", groupSpecifications="); + dataSb.append(groupSpecifications); + dataSb.append(", personToGroupsMemberships="); + dataSb.append(personToGroupsMemberships); + dataSb.append(", groupToPeopleMemberships="); + dataSb.append(groupToPeopleMemberships); + dataSb.append("]"); + + StringBuilder pluginDataSb = new StringBuilder(); + pluginDataSb.append("GroupsPluginData [data="); + pluginDataSb.append(dataSb.toString()); + pluginDataSb.append("]"); + + assertEquals(pluginDataSb.toString(), pluginData.toString()); + } + } + + + + @Test + @UnitTestMethod(target = GroupsPluginData.Builder.class, name = "addGroupToPerson", args = {GroupId.class, PersonId.class }) + public void testAddGroupToPerson() { + + Random random = new Random(7282493148489771700L); + + Map expectedGroupAssignments = new LinkedHashMap<>(); + + GroupsPluginData.Builder builder = GroupsPluginData.builder(); + // add in the group types + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + builder.addGroupTypeId(testGroupTypeId); + } + + // create some people + List personIds = new ArrayList<>(); + for (int i = 0; i < 100; i++) { + personIds.add(i); + } + + /* + * Add a few groups and add to those groups 0 to 9 randomly selected people. + * Record the assignments in the expected data structure. + */ + TestGroupTypeId testGroupTypeId = TestGroupTypeId.GROUP_TYPE_1; + for (int i = 0; i < 20; i++) { + // add the group + + builder.addGroup(new GroupId(i), testGroupTypeId); + testGroupTypeId = testGroupTypeId.next(); + // select some people and add them to the group + Collections.shuffle(personIds, random); + int count = random.nextInt(10); + for (int j = 0; j < count; j++) { + builder.addGroupToPerson(new GroupId(i), new PersonId(personIds.get(j))); + builder.addPersonToGroup(new PersonId(personIds.get(j)), new GroupId(i)); + MultiKey multiKey = new MultiKey(new GroupId(i), new PersonId(personIds.get(j))); + expectedGroupAssignments.putIfAbsent(multiKey, new MutableInteger()); + expectedGroupAssignments.get(multiKey).increment(); + } + } + + // build the group initial data + GroupsPluginData groupsPluginData = builder.build(); + + // show that the group memberships are as expected + Map actualGroupAssignments = new LinkedHashMap<>(); + + for (int i = 0; i < groupsPluginData.getPersonCount(); i++) { + PersonId personId = new PersonId(i); + for (GroupId groupId : groupsPluginData.getGroupsForPerson(personId)) { + MultiKey multiKey = new MultiKey(groupId, personId); + actualGroupAssignments.putIfAbsent(multiKey, new MutableInteger()); + actualGroupAssignments.get(multiKey).increment(); + } + } + + assertEquals(expectedGroupAssignments, actualGroupAssignments); + + // precondition test: if the group id is null + ContractException contractException = assertThrows(ContractException.class, + () -> GroupsPluginData.builder().associatePersonToGroup(null, new PersonId(0))); + assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); + + // precondition test: if the person id is null + contractException = assertThrows(ContractException.class, + () -> GroupsPluginData.builder().associatePersonToGroup(new GroupId(0), null)); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupsPluginData.Builder.class, name = "addPersonToGroup", args = {PersonId.class, GroupId.class }) + public void testAddPersonToGroup() { + + Random random = new Random(7282493148489771700L); + + Map expectedGroupAssignments = new LinkedHashMap<>(); + + GroupsPluginData.Builder builder = GroupsPluginData.builder(); + // add in the group types + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + builder.addGroupTypeId(testGroupTypeId); + } + + // create some people + List personIds = new ArrayList<>(); + for (int i = 0; i < 100; i++) { + personIds.add(i); + } + + /* + * Add a few groups and add to those groups 0 to 9 randomly selected people. + * Record the assignments in the expected data structure. + */ + TestGroupTypeId testGroupTypeId = TestGroupTypeId.GROUP_TYPE_1; + for (int i = 0; i < 20; i++) { + // add the group + + builder.addGroup(new GroupId(i), testGroupTypeId); + testGroupTypeId = testGroupTypeId.next(); + // select some people and add them to the group + Collections.shuffle(personIds, random); + int count = random.nextInt(10); + for (int j = 0; j < count; j++) { + builder.addGroupToPerson(new GroupId(i), new PersonId(personIds.get(j))); + builder.addPersonToGroup(new PersonId(personIds.get(j)), new GroupId(i)); + MultiKey multiKey = new MultiKey(new GroupId(i), new PersonId(personIds.get(j))); + expectedGroupAssignments.putIfAbsent(multiKey, new MutableInteger()); + expectedGroupAssignments.get(multiKey).increment(); + } + } + + // build the group initial data + GroupsPluginData groupsPluginData = builder.build(); + + // show that the group memberships are as expected + Map actualGroupAssignments = new LinkedHashMap<>(); + + for (int i = 0; i < groupsPluginData.getPersonCount(); i++) { + PersonId personId = new PersonId(i); + for (GroupId groupId : groupsPluginData.getGroupsForPerson(personId)) { + MultiKey multiKey = new MultiKey(groupId, personId); + actualGroupAssignments.putIfAbsent(multiKey, new MutableInteger()); + actualGroupAssignments.get(multiKey).increment(); + } + } + + assertEquals(expectedGroupAssignments, actualGroupAssignments); + + // precondition test: if the group id is null + ContractException contractException = assertThrows(ContractException.class, + () -> GroupsPluginData.builder().associatePersonToGroup(null, new PersonId(0))); + assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); + + // precondition test: if the person id is null + contractException = assertThrows(ContractException.class, + () -> GroupsPluginData.builder().associatePersonToGroup(new GroupId(0), null)); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/AT_GroupAdditionEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/AT_GroupAdditionEvent.java new file mode 100644 index 000000000..e817619c2 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/AT_GroupAdditionEvent.java @@ -0,0 +1,42 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.events; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_GroupAdditionEvent { + + @Test + @UnitTestConstructor(target = GroupAdditionEvent.class, args = { GroupId.class }) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupAdditionEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupAdditionEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupAdditionEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupAdditionEvent.class, name = "groupId", args = {}) + public void testGroupId() { + // nothing to test + } + + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/AT_GroupImminentRemovalEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/AT_GroupImminentRemovalEvent.java new file mode 100644 index 000000000..ef0e9ae10 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/AT_GroupImminentRemovalEvent.java @@ -0,0 +1,41 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.events; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_GroupImminentRemovalEvent { + + @Test + @UnitTestConstructor(target = GroupImminentRemovalEvent.class, args = { GroupId.class }) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupImminentRemovalEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupImminentRemovalEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupImminentRemovalEvent.class, name = "groupId", args = {}) + public void testGroupId() { + // nothing to test + } + + + @Test + @UnitTestMethod(target = GroupImminentRemovalEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/AT_GroupMembershipAdditionEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/AT_GroupMembershipAdditionEvent.java new file mode 100644 index 000000000..63473632c --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/AT_GroupMembershipAdditionEvent.java @@ -0,0 +1,48 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.events; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_GroupMembershipAdditionEvent { + + @Test + @UnitTestConstructor(target = GroupMembershipAdditionEvent.class, args = { PersonId.class, GroupId.class }) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupMembershipAdditionEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupMembershipAdditionEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupMembershipAdditionEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupMembershipAdditionEvent.class, name = "personId", args = {}) + public void testPersonId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupMembershipAdditionEvent.class, name = "groupId", args = {}) + public void testGroupId() { + // nothing to test + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/AT_GroupMembershipRemovalEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/AT_GroupMembershipRemovalEvent.java new file mode 100644 index 000000000..4cdab8ca7 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/AT_GroupMembershipRemovalEvent.java @@ -0,0 +1,48 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.events; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_GroupMembershipRemovalEvent { + + @Test + @UnitTestConstructor(target = GroupMembershipRemovalEvent.class, args = { PersonId.class, GroupId.class }) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupMembershipRemovalEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupMembershipRemovalEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupMembershipRemovalEvent.class, name = "personId", args = {}) + public void testPersonId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupMembershipRemovalEvent.class, name = "groupId", args = {}) + public void testGroupId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupMembershipRemovalEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/AT_GroupPropertyDefinitionEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/AT_GroupPropertyDefinitionEvent.java new file mode 100644 index 000000000..98484c584 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/AT_GroupPropertyDefinitionEvent.java @@ -0,0 +1,69 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.events; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupError; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupTypeId; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.TestGroupPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.TestGroupTypeId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + +public class AT_GroupPropertyDefinitionEvent { + + @Test + @UnitTestConstructor(target = GroupPropertyDefinitionEvent.class, args = { GroupTypeId.class, GroupPropertyId.class }) + public void testConstructor() { + GroupTypeId groupTypeId = TestGroupTypeId.GROUP_TYPE_1; + GroupPropertyId groupPropertyId = TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK; + + assertNotNull(new GroupPropertyDefinitionEvent(groupTypeId, groupPropertyId)); + + // precondition: group type id is null + ContractException contractException = assertThrows(ContractException.class, () -> new GroupPropertyDefinitionEvent(null, groupPropertyId)); + assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); + + // precondition: group property id is null + contractException = assertThrows(ContractException.class, () -> new GroupPropertyDefinitionEvent(groupTypeId, null)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupPropertyDefinitionEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupPropertyDefinitionEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupPropertyDefinitionEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupPropertyDefinitionEvent.class, name = "groupTypeId", args = {}) + public void testGroupTypeId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupPropertyDefinitionEvent.class, name = "groupPropertyId", args = {}) + public void testGroupPropertyId() { + // nothing to test + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/AT_GroupPropertyUpdateEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/AT_GroupPropertyUpdateEvent.java new file mode 100644 index 000000000..02157ef1b --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/AT_GroupPropertyUpdateEvent.java @@ -0,0 +1,60 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.events; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupId; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupPropertyId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_GroupPropertyUpdateEvent { + + @Test + @UnitTestConstructor(target = GroupPropertyUpdateEvent.class, args = { GroupId.class, GroupPropertyId.class, Object.class, Object.class }) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupPropertyUpdateEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupPropertyUpdateEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupPropertyUpdateEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupPropertyUpdateEvent.class, name = "groupId", args = {}) + public void testGroupId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupPropertyUpdateEvent.class, name = "groupPropertyId", args = {}) + public void testGroupPropertyId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupPropertyUpdateEvent.class, name = "previousPropertyValue", args = {}) + public void testPreviousPropertyValue() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupPropertyUpdateEvent.class, name = "currentPropertyValue", args = {}) + public void testCurrentPropertyValue() { + // nothing to test + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/AT_GroupTypeAdditionEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/AT_GroupTypeAdditionEvent.java new file mode 100644 index 000000000..cbc8989be --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/events/AT_GroupTypeAdditionEvent.java @@ -0,0 +1,41 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.events; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupTypeId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_GroupTypeAdditionEvent { + + @Test + @UnitTestConstructor(target = GroupTypeAdditionEvent.class, args = { GroupTypeId.class }) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupTypeAdditionEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupTypeAdditionEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupTypeAdditionEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupTypeAdditionEvent.class, name = "groupTypeId", args = {}) + public void testGroupTypeId() { + // nothing to test + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/reports/AT_GroupPopulationReport.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/reports/AT_GroupPopulationReport.java new file mode 100644 index 000000000..c28cabd2f --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/reports/AT_GroupPopulationReport.java @@ -0,0 +1,498 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.reports; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestOutputConsumer; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.groups.datamanagers.GroupsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.groups.datamanagers.GroupsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupId; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.GroupsTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.GroupsTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.TestAuxiliaryGroupTypeId; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.TestGroupPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.TestGroupTypeId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_GroupPopulationReport { + + @Test + @UnitTestConstructor(target = GroupPopulationReport.class, args = { GroupPopulationReportPluginData.class }) + public void testConstructor() { + + /* + * Nothing to test -- it is not possible to pass a null report label nor + * a null report period + */ + + } + + @Test + @UnitTestMethod(target = GroupPopulationReport.class, name = "init", args = { ReportContext.class }) + public void testHourlyReport() { + + /* + * We will add one agent to move people in and out of groups and create + * and remove groups. Report items from the report will be collected in + * an output consumer and compared to the expected output. + */ + + // add the action plugin + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // have the agent add a new group of type 1 with three people + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(getTime(0, 0, 0), (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); + assertEquals(3, groupId.getValue()); + groupsDataManager.addPersonToGroup(new PersonId(4), groupId); + groupsDataManager.addPersonToGroup(new PersonId(5), groupId); + groupsDataManager.addPersonToGroup(new PersonId(6), groupId); + })); + + // have the agent add a new group of type 1 with three people + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(getTime(1, 1, 15), (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.removePersonFromGroup(new PersonId(4), new GroupId(3)); + groupsDataManager.removeGroup(new GroupId(0)); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(getTime(1, 2, 48), (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.removeGroup(new GroupId(2)); + + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(getTime(1, 5, 14), (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.addPersonToGroup(new PersonId(7), new GroupId(3)); + groupsDataManager.addPersonToGroup(new PersonId(8), new GroupId(3)); + groupsDataManager.addPersonToGroup(new PersonId(9), new GroupId(3)); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(getTime(1, 5, 34), (c) -> { + + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); + assertEquals(4, groupId.getValue()); + groupsDataManager.addPersonToGroup(new PersonId(3), new GroupId(4)); + groupsDataManager.addPersonToGroup(new PersonId(4), new GroupId(4)); + groupsDataManager.addPersonToGroup(new PersonId(5), new GroupId(4)); + groupsDataManager.addPersonToGroup(new PersonId(6), new GroupId(4)); + groupsDataManager.addPersonToGroup(new PersonId(7), new GroupId(4)); + + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(getTime(1, 6, 40), (c) -> { + + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.addGroupType(TestAuxiliaryGroupTypeId.GROUP_AUX_TYPE_1); + GroupId groupId = groupsDataManager.addGroup(TestAuxiliaryGroupTypeId.GROUP_AUX_TYPE_1); + assertEquals(5, groupId.getValue()); + groupsDataManager.addPersonToGroup(new PersonId(4), new GroupId(5)); + groupsDataManager.addPersonToGroup(new PersonId(5), new GroupId(5)); + groupsDataManager.addPersonToGroup(new PersonId(6), new GroupId(5)); + groupsDataManager.addPersonToGroup(new PersonId(7), new GroupId(5)); + + })); + + TestPluginData testPluginData = pluginBuilder.build(); + + // place the initial data into the expected output consumer + TestOutputConsumer expectedConsumer = new TestOutputConsumer(); + + expectedConsumer.accept(getReportItem(ReportPeriod.HOURLY, 0, 0, TestGroupTypeId.GROUP_TYPE_1, 3, 1)); + expectedConsumer.accept(getReportItem(ReportPeriod.HOURLY, 0, 0, TestGroupTypeId.GROUP_TYPE_2, 3, 1)); + expectedConsumer.accept(getReportItem(ReportPeriod.HOURLY, 0, 0, TestGroupTypeId.GROUP_TYPE_3, 0, 1)); + + for (int hour = 1; hour < 24; hour++) { + expectedConsumer.accept(getReportItem(ReportPeriod.HOURLY, 0, hour, TestGroupTypeId.GROUP_TYPE_1, 3, 2)); + expectedConsumer.accept(getReportItem(ReportPeriod.HOURLY, 0, hour, TestGroupTypeId.GROUP_TYPE_2, 3, 1)); + expectedConsumer.accept(getReportItem(ReportPeriod.HOURLY, 0, hour, TestGroupTypeId.GROUP_TYPE_3, 0, 1)); + } + + expectedConsumer.accept(getReportItem(ReportPeriod.HOURLY, 1, 0, TestGroupTypeId.GROUP_TYPE_1, 3, 2)); + expectedConsumer.accept(getReportItem(ReportPeriod.HOURLY, 1, 0, TestGroupTypeId.GROUP_TYPE_2, 3, 1)); + expectedConsumer.accept(getReportItem(ReportPeriod.HOURLY, 1, 0, TestGroupTypeId.GROUP_TYPE_3, 0, 1)); + + expectedConsumer.accept(getReportItem(ReportPeriod.HOURLY, 1, 1, TestGroupTypeId.GROUP_TYPE_1, 3, 2)); + expectedConsumer.accept(getReportItem(ReportPeriod.HOURLY, 1, 1, TestGroupTypeId.GROUP_TYPE_2, 3, 1)); + expectedConsumer.accept(getReportItem(ReportPeriod.HOURLY, 1, 1, TestGroupTypeId.GROUP_TYPE_3, 0, 1)); + + expectedConsumer.accept(getReportItem(ReportPeriod.HOURLY, 1, 2, TestGroupTypeId.GROUP_TYPE_1, 2, 1)); + expectedConsumer.accept(getReportItem(ReportPeriod.HOURLY, 1, 2, TestGroupTypeId.GROUP_TYPE_2, 3, 1)); + expectedConsumer.accept(getReportItem(ReportPeriod.HOURLY, 1, 2, TestGroupTypeId.GROUP_TYPE_3, 0, 1)); + + expectedConsumer.accept(getReportItem(ReportPeriod.HOURLY, 1, 3, TestGroupTypeId.GROUP_TYPE_1, 2, 1)); + expectedConsumer.accept(getReportItem(ReportPeriod.HOURLY, 1, 3, TestGroupTypeId.GROUP_TYPE_2, 3, 1)); + + expectedConsumer.accept(getReportItem(ReportPeriod.HOURLY, 1, 4, TestGroupTypeId.GROUP_TYPE_1, 2, 1)); + expectedConsumer.accept(getReportItem(ReportPeriod.HOURLY, 1, 4, TestGroupTypeId.GROUP_TYPE_2, 3, 1)); + + expectedConsumer.accept(getReportItem(ReportPeriod.HOURLY, 1, 5, TestGroupTypeId.GROUP_TYPE_1, 2, 1)); + expectedConsumer.accept(getReportItem(ReportPeriod.HOURLY, 1, 5, TestGroupTypeId.GROUP_TYPE_2, 3, 1)); + + expectedConsumer.accept(getReportItem(ReportPeriod.HOURLY, 1, 6, TestGroupTypeId.GROUP_TYPE_1, 5, 2)); + expectedConsumer.accept(getReportItem(ReportPeriod.HOURLY, 1, 6, TestGroupTypeId.GROUP_TYPE_2, 3, 1)); + + expectedConsumer.accept(getReportItem(ReportPeriod.HOURLY, 1, 7, TestGroupTypeId.GROUP_TYPE_1, 5, 2)); + expectedConsumer.accept(getReportItem(ReportPeriod.HOURLY, 1, 7, TestGroupTypeId.GROUP_TYPE_2, 3, 1)); + + expectedConsumer.accept(getReportItem(ReportPeriod.HOURLY, 1, 7, TestAuxiliaryGroupTypeId.GROUP_AUX_TYPE_1, 4, 1)); + + GroupPopulationReportPluginData groupPopulationReportPluginData = GroupPopulationReportPluginData.builder().setReportLabel(REPORT_LABEL).setReportPeriod(ReportPeriod.HOURLY).build(); // (REPORT_LABEL, + // ReportPeriod.HOURLY); + + Factory factory = GroupsTestPluginFactory .factory(10, 0, 3, 5524610980534223950L, testPluginData)// + .setGroupsPluginData(getGroupsPluginData())// + .setGroupPopulationReportPluginData(groupPopulationReportPluginData); + + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .build()// + .execute(); + + Map expectedReportItems = expectedConsumer.getOutputItemMap(ReportItem.class); + Map actualReportItems = testOutputConsumer.getOutputItemMap(ReportItem.class); + + assertEquals(expectedReportItems, actualReportItems); + + } + + /* + * Returns the conversion into double valued days + * + * preconditions: all entries are non-negative and in their natural ranges + */ + private double getTime(int days, int hours, int minutes) { + return days + (double) hours / 24 + (double) minutes / 1440; + } + + @Test + @UnitTestMethod(target = GroupPopulationReport.class, name = "init", args = { ReportContext.class }) + public void testDailyReport() { + + /* + * We will add one agent to move people in and out of groups and create + * and remove groups. Report items from the report will be collected in + * an output consumer and compared to the expected output. + */ + + // add the action plugin + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // have the agent add a new group of type 1 with three people + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); + + assertEquals(3, groupId.getValue()); + groupsDataManager.addPersonToGroup(new PersonId(4), groupId); + groupsDataManager.addPersonToGroup(new PersonId(5), groupId); + groupsDataManager.addPersonToGroup(new PersonId(6), groupId); + + })); + + // have the agent add a new group of type 1 with three people + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1.1, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.removePersonFromGroup(new PersonId(4), new GroupId(3)); + groupsDataManager.removeGroup(new GroupId(0)); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2.5, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.removeGroup(new GroupId(2)); + + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(5.7, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.addPersonToGroup(new PersonId(7), new GroupId(3)); + groupsDataManager.addPersonToGroup(new PersonId(8), new GroupId(3)); + groupsDataManager.addPersonToGroup(new PersonId(9), new GroupId(3)); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(5.8, (c) -> { + + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); + assertEquals(4, groupId.getValue()); + + groupsDataManager.addPersonToGroup(new PersonId(3), new GroupId(4)); + groupsDataManager.addPersonToGroup(new PersonId(4), new GroupId(4)); + groupsDataManager.addPersonToGroup(new PersonId(5), new GroupId(4)); + groupsDataManager.addPersonToGroup(new PersonId(6), new GroupId(4)); + groupsDataManager.addPersonToGroup(new PersonId(7), new GroupId(4)); + + })); + + TestPluginData testPluginData = pluginBuilder.build(); + + // create a container to hold expected results + TestOutputConsumer expectedConsumer = new TestOutputConsumer(); + + // place the initial data into the expected output consumer + + expectedConsumer.accept(getReportItem(ReportPeriod.DAILY, 0, TestGroupTypeId.GROUP_TYPE_1, 3, 1)); + expectedConsumer.accept(getReportItem(ReportPeriod.DAILY, 0, TestGroupTypeId.GROUP_TYPE_2, 3, 1)); + expectedConsumer.accept(getReportItem(ReportPeriod.DAILY, 0, TestGroupTypeId.GROUP_TYPE_3, 0, 1)); + + expectedConsumer.accept(getReportItem(ReportPeriod.DAILY, 1, TestGroupTypeId.GROUP_TYPE_1, 3, 2)); + expectedConsumer.accept(getReportItem(ReportPeriod.DAILY, 1, TestGroupTypeId.GROUP_TYPE_2, 3, 1)); + expectedConsumer.accept(getReportItem(ReportPeriod.DAILY, 1, TestGroupTypeId.GROUP_TYPE_3, 0, 1)); + + expectedConsumer.accept(getReportItem(ReportPeriod.DAILY, 2, TestGroupTypeId.GROUP_TYPE_1, 2, 1)); + expectedConsumer.accept(getReportItem(ReportPeriod.DAILY, 2, TestGroupTypeId.GROUP_TYPE_2, 3, 1)); + expectedConsumer.accept(getReportItem(ReportPeriod.DAILY, 2, TestGroupTypeId.GROUP_TYPE_3, 0, 1)); + + expectedConsumer.accept(getReportItem(ReportPeriod.DAILY, 3, TestGroupTypeId.GROUP_TYPE_1, 2, 1)); + expectedConsumer.accept(getReportItem(ReportPeriod.DAILY, 3, TestGroupTypeId.GROUP_TYPE_2, 3, 1)); + + expectedConsumer.accept(getReportItem(ReportPeriod.DAILY, 4, TestGroupTypeId.GROUP_TYPE_1, 2, 1)); + expectedConsumer.accept(getReportItem(ReportPeriod.DAILY, 4, TestGroupTypeId.GROUP_TYPE_2, 3, 1)); + + expectedConsumer.accept(getReportItem(ReportPeriod.DAILY, 5, TestGroupTypeId.GROUP_TYPE_1, 2, 1)); + expectedConsumer.accept(getReportItem(ReportPeriod.DAILY, 5, TestGroupTypeId.GROUP_TYPE_2, 3, 1)); + + expectedConsumer.accept(getReportItem(ReportPeriod.DAILY, 6, TestGroupTypeId.GROUP_TYPE_1, 5, 2)); + expectedConsumer.accept(getReportItem(ReportPeriod.DAILY, 6, TestGroupTypeId.GROUP_TYPE_2, 3, 1)); + + GroupPopulationReportPluginData groupPopulationReportPluginData = GroupPopulationReportPluginData.builder().setReportLabel(REPORT_LABEL).setReportPeriod(ReportPeriod.DAILY).build(); // (REPORT_LABEL, + // ReportPeriod.HOURLY); + + Factory factory = GroupsTestPluginFactory .factory(10, 0, 3, 4023600052052959521L, testPluginData)// + .setGroupsPluginData(getGroupsPluginData())// + .setGroupPopulationReportPluginData(groupPopulationReportPluginData); + + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .build()// + .execute(); + + Map expectedReportItems = expectedConsumer.getOutputItemMap(ReportItem.class); + Map actualReportItems = testOutputConsumer.getOutputItemMap(ReportItem.class); + + assertEquals(expectedReportItems, actualReportItems); + + } + + @Test + @UnitTestMethod(target = GroupPopulationReport.class, name = "init", args = { ReportContext.class }) + public void testEndOfSimReport() { + + /* + * We will add one agent to move people in and out of groups and create + * and remove groups. Report items from the report will be collected in + * an output consumer and compared to the expected output. + */ + + // add the action plugin + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // have the agent add a new group of type 1 with three people + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); + assertEquals(3, groupId.getValue()); + groupsDataManager.addPersonToGroup(new PersonId(4), groupId); + groupsDataManager.addPersonToGroup(new PersonId(5), groupId); + groupsDataManager.addPersonToGroup(new PersonId(6), groupId); + })); + + // have the agent add a new group of type 1 with three people + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1.1, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.removePersonFromGroup(new PersonId(4), new GroupId(3)); + groupsDataManager.removeGroup(new GroupId(0)); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2.5, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.removeGroup(new GroupId(2)); + + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(5.7, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.addPersonToGroup(new PersonId(7), new GroupId(3)); + groupsDataManager.addPersonToGroup(new PersonId(8), new GroupId(3)); + groupsDataManager.addPersonToGroup(new PersonId(9), new GroupId(3)); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(5.8, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); + assertEquals(4, groupId.getValue()); + groupsDataManager.addPersonToGroup(new PersonId(3), new GroupId(4)); + groupsDataManager.addPersonToGroup(new PersonId(4), new GroupId(4)); + groupsDataManager.addPersonToGroup(new PersonId(5), new GroupId(4)); + groupsDataManager.addPersonToGroup(new PersonId(6), new GroupId(4)); + groupsDataManager.addPersonToGroup(new PersonId(7), new GroupId(4)); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + // place the initial data into the expected output consumer + // create a container to hold expected results + TestOutputConsumer expectedConsumer = new TestOutputConsumer(); + expectedConsumer.accept(getReportItem(ReportPeriod.END_OF_SIMULATION, TestGroupTypeId.GROUP_TYPE_1, 5, 2)); + expectedConsumer.accept(getReportItem(ReportPeriod.END_OF_SIMULATION, TestGroupTypeId.GROUP_TYPE_2, 3, 1)); + + GroupPopulationReportPluginData groupPopulationReportPluginData = GroupPopulationReportPluginData .builder().setReportLabel(REPORT_LABEL).setReportPeriod(ReportPeriod.END_OF_SIMULATION) + .build(); + + Factory factory = GroupsTestPluginFactory .factory(10, 0, 3, 6092832510476200219L, testPluginData)// + .setGroupsPluginData(getGroupsPluginData())// + .setGroupPopulationReportPluginData(groupPopulationReportPluginData); + + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .build()// + .execute(); + + Map expectedReportItems = expectedConsumer.getOutputItemMap(ReportItem.class); + Map actualReportItems = testOutputConsumer.getOutputItemMap(ReportItem.class); + assertEquals(expectedReportItems, actualReportItems); + } + + private GroupsPluginData getGroupsPluginData() { + + // add the group plugin + GroupsPluginData.Builder groupBuilder = GroupsPluginData.builder(); + // add group types + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + groupBuilder.addGroupTypeId(testGroupTypeId); + } + // define group properties + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { + groupBuilder.defineGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId, testGroupPropertyId.getPropertyDefinition()); + } + + groupBuilder.addGroup(new GroupId(0), TestGroupTypeId.GROUP_TYPE_1); + groupBuilder.addGroup(new GroupId(1), TestGroupTypeId.GROUP_TYPE_2); + groupBuilder.addGroup(new GroupId(2), TestGroupTypeId.GROUP_TYPE_3); + + // add a few of the people to the groups + + groupBuilder.associatePersonToGroup(new GroupId(0), new PersonId(0)); + groupBuilder.associatePersonToGroup(new GroupId(0), new PersonId(1)); + groupBuilder.associatePersonToGroup(new GroupId(0), new PersonId(2)); + + groupBuilder.associatePersonToGroup(new GroupId(1), new PersonId(1)); + groupBuilder.associatePersonToGroup(new GroupId(1), new PersonId(2)); + groupBuilder.associatePersonToGroup(new GroupId(1), new PersonId(3)); + return groupBuilder.build(); + } + + @Test + @UnitTestMethod(target = GroupPopulationReport.class, name = "init", args = { ReportContext.class }) + public void testInit_State() { + // Test with producing simulation + + // add the action plugin + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // have the agent add a new group of type 1 with three people + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); + assertEquals(3, groupId.getValue()); + groupsDataManager.addPersonToGroup(new PersonId(4), groupId); + groupsDataManager.addPersonToGroup(new PersonId(5), groupId); + groupsDataManager.addPersonToGroup(new PersonId(6), groupId); + })); + + // have the agent add a new group of type 1 with three people + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.removeGroup(new GroupId(2)); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.addPersonToGroup(new PersonId(7), new GroupId(3)); + groupsDataManager.addPersonToGroup(new PersonId(8), new GroupId(3)); + groupsDataManager.addPersonToGroup(new PersonId(9), new GroupId(3)); + })); + + GroupPopulationReportPluginData groupPopulationReportPluginData = GroupPopulationReportPluginData.builder().setReportLabel(REPORT_LABEL).setReportPeriod(ReportPeriod.HOURLY).build(); + + TestPluginData testPluginData = pluginBuilder.build(); + + Factory factory = GroupsTestPluginFactory .factory(10, 0, 3, 4023600052052959521L, testPluginData)// + .setGroupsPluginData(getGroupsPluginData())// + .setGroupPopulationReportPluginData(groupPopulationReportPluginData); + + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .setProduceSimulationStateOnHalt(true)// + .setSimulationHaltTime(20)// + .build()// + .execute(); + + // show that the output plugin data is similar to the input plugin data + Map outputItems = testOutputConsumer.getOutputItemMap(GroupPopulationReportPluginData.class); + assertEquals(1, outputItems.size()); + GroupPopulationReportPluginData groupPopulationReportPluginData2 = outputItems.keySet().iterator().next(); + assertEquals(groupPopulationReportPluginData, groupPopulationReportPluginData2); + + // Test without producing simulation + + testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .setProduceSimulationStateOnHalt(false)// + .setSimulationHaltTime(20)// + .build()// + .execute(); + + // show that when the simulation state is not being produced, there is + // no output plugin data+ + outputItems = testOutputConsumer.getOutputItemMap(GroupPopulationReportPluginData.class); + assertEquals(0, outputItems.size()); + } + + private static ReportItem getReportItem(ReportPeriod reportPeriod, Object... values) { + ReportItem.Builder builder = ReportItem.builder(); + builder.setReportLabel(REPORT_LABEL); + + switch (reportPeriod) { + case DAILY: + builder.setReportHeader(REPORT_DAILY_HEADER); + break; + case END_OF_SIMULATION: + builder.setReportHeader(REPORT_EOS_HEADER); + break; + case HOURLY: + builder.setReportHeader(REPORT_HOURLY_HEADER); + break; + default: + throw new RuntimeException("unhandled case " + reportPeriod); + + } + + for (Object value : values) { + builder.addValue(value); + } + return builder.build(); + } + + private static final ReportLabel REPORT_LABEL = new SimpleReportLabel("group population property report"); + + private static final ReportHeader REPORT_DAILY_HEADER = ReportHeader.builder().add("day").add("group_type").add("person_count").add("group_count").build(); + private static final ReportHeader REPORT_HOURLY_HEADER = ReportHeader.builder().add("day").add("hour").add("group_type").add("person_count").add("group_count").build(); + private static final ReportHeader REPORT_EOS_HEADER = ReportHeader.builder().add("group_type").add("person_count").add("group_count").build(); +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/reports/AT_GroupPopulationReportPluginData.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/reports/AT_GroupPopulationReportPluginData.java new file mode 100644 index 000000000..69d572593 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/reports/AT_GroupPopulationReportPluginData.java @@ -0,0 +1,282 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.reports; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportError; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_GroupPopulationReportPluginData { + + @Test + @UnitTestMethod(target = GroupPopulationReportPluginData.class, name = "builder", args = {}) + public void testBuilder() { + GroupPopulationReportPluginData.Builder builder = GroupPopulationReportPluginData.builder(); + assertNotNull(builder); + } + + @Test + @UnitTestMethod(target = GroupPopulationReportPluginData.Builder.class, name = "build", args = {}) + public void testBuild() { + // the specific capabilities are covered elsewhere + + // precondition test: if the report period is not assigned + ContractException contractException = assertThrows(ContractException.class, () -> // + GroupPopulationReportPluginData .builder()// + .setReportLabel(new SimpleReportLabel(getClass()))// + .build()); + assertEquals(ReportError.NULL_REPORT_PERIOD, contractException.getErrorType()); + + // precondition test: if the report label is not assigned + contractException = assertThrows(ContractException.class, () -> // + GroupPopulationReportPluginData .builder()// + .setReportPeriod(ReportPeriod.DAILY)// + .build()); + assertEquals(ReportError.NULL_REPORT_LABEL, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupPopulationReportPluginData.Builder.class, name = "setReportLabel", args = { ReportLabel.class }) + public void testSetReportLabel() { + + for (int i = 0; i < 30; i++) { + ReportLabel expectedReportLabel = new SimpleReportLabel(i); + GroupPopulationReportPluginData proupPopulationReportPluginData = // + GroupPopulationReportPluginData .builder()// + .setReportPeriod(ReportPeriod.DAILY)// + .setReportLabel(expectedReportLabel)// + .build(); + + assertEquals(expectedReportLabel, proupPopulationReportPluginData.getReportLabel()); + } + + // precondition: if the report label is null + ContractException contractException = assertThrows(ContractException.class, () -> { + GroupPopulationReportPluginData.builder().setReportLabel(null); + }); + assertEquals(ReportError.NULL_REPORT_LABEL, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupPopulationReportPluginData.Builder.class, name = "setReportPeriod", args = { ReportPeriod.class }) + public void testSetReportPeriod() { + + ReportLabel reportLabel = new SimpleReportLabel("report label"); + for (ReportPeriod reportPeriod : ReportPeriod.values()) { + + GroupPopulationReportPluginData proupPopulationReportPluginData = // + GroupPopulationReportPluginData .builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .build(); + + assertEquals(reportPeriod, proupPopulationReportPluginData.getReportPeriod()); + } + + // precondition: if the report period is null + ContractException contractException = assertThrows(ContractException.class, () -> { + GroupPopulationReportPluginData.builder().setReportPeriod(null); + }); + assertEquals(ReportError.NULL_REPORT_PERIOD, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupPopulationReportPluginData.class, name = "getReportLabel", args = {}) + public void testGetReportLabel() { + for (int i = 0; i < 30; i++) { + ReportLabel expectedReportLabel = new SimpleReportLabel(i); + GroupPopulationReportPluginData proupPopulationReportPluginData = // + GroupPopulationReportPluginData .builder()// + .setReportPeriod(ReportPeriod.DAILY)// + .setReportLabel(expectedReportLabel)// + .build(); + + assertEquals(expectedReportLabel, proupPopulationReportPluginData.getReportLabel()); + } + + } + + @Test + @UnitTestMethod(target = GroupPopulationReportPluginData.class, name = "getReportPeriod", args = {}) + public void testGetReportPeriod() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + for (ReportPeriod reportPeriod : ReportPeriod.values()) { + + GroupPopulationReportPluginData proupPopulationReportPluginData = // + GroupPopulationReportPluginData .builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .build(); + + assertEquals(reportPeriod, proupPopulationReportPluginData.getReportPeriod()); + } + } + + @Test + @UnitTestMethod(target = GroupPopulationReportPluginData.class, name = "getCloneBuilder", args = {}) + public void testGetCloneBuilder() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1807247797909883254L); + for (int i = 0; i < 10; i++) { + + // build a GroupPopulationReportPluginData from random inputs + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt()); + ReportPeriod reportPeriod = ReportPeriod.values()[randomGenerator.nextInt(ReportPeriod.values().length)]; + + GroupPopulationReportPluginData proupPopulationReportPluginData = // + GroupPopulationReportPluginData .builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel).build(); + + // create the clone builder and have it build + GroupPopulationReportPluginData cloneGroupPopulationReportPluginData = proupPopulationReportPluginData.getCloneBuilder().build(); + + // the result should equal the original if the clone builder was + // initialized with the correct state + assertEquals(proupPopulationReportPluginData, cloneGroupPopulationReportPluginData); + + } + } + + @Test + @UnitTestMethod(target = GroupPopulationReportPluginData.class, name = "equals", args = { Object.class }) + public void testEquals() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7759639255438669162L); + for (int i = 0; i < 10; i++) { + // build a GroupPopulationReportPluginData from the same random + // inputs + GroupPopulationReportPluginData.Builder builder1 = GroupPopulationReportPluginData.builder(); + GroupPopulationReportPluginData.Builder builder2 = GroupPopulationReportPluginData.builder(); + + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt(100)); + builder1.setReportLabel(reportLabel); + builder2.setReportLabel(reportLabel); + + ReportPeriod reportPeriod = ReportPeriod.values()[randomGenerator.nextInt(ReportPeriod.values().length)]; + builder1.setReportPeriod(reportPeriod); + builder2.setReportPeriod(reportPeriod); + + GroupPopulationReportPluginData proupPopulationReportPluginData1 = builder1.build(); + GroupPopulationReportPluginData proupPopulationReportPluginData2 = builder2.build(); + + assertEquals(proupPopulationReportPluginData1, proupPopulationReportPluginData2); + + // show that plugin datas with different inputs are not equal + + // change the report period + int ord = reportPeriod.ordinal() + 1; + ord = ord % ReportPeriod.values().length; + reportPeriod = ReportPeriod.values()[ord]; + proupPopulationReportPluginData2 = // + proupPopulationReportPluginData1.getCloneBuilder()// + .setReportPeriod(reportPeriod)// + .build(); + assertNotEquals(proupPopulationReportPluginData2, proupPopulationReportPluginData1); + + // change the report label + reportLabel = new SimpleReportLabel(1000); + proupPopulationReportPluginData2 = // + proupPopulationReportPluginData1.getCloneBuilder()// + .setReportLabel(reportLabel)// + .build(); + assertNotEquals(proupPopulationReportPluginData2, proupPopulationReportPluginData1); + + } + } + + @Test + @UnitTestMethod(target = GroupPopulationReportPluginData.class, name = "hashCode", args = {}) + public void testHashCode() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(9178672375367646465L); + + Set observedHashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 50; i++) { + // build a GroupPopulationReportPluginData from the same random + // inputs + GroupPopulationReportPluginData.Builder builder1 = GroupPopulationReportPluginData.builder(); + GroupPopulationReportPluginData.Builder builder2 = GroupPopulationReportPluginData.builder(); + + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt(100)); + builder1.setReportLabel(reportLabel); + builder2.setReportLabel(reportLabel); + + ReportPeriod reportPeriod = ReportPeriod.values()[randomGenerator.nextInt(ReportPeriod.values().length)]; + builder1.setReportPeriod(reportPeriod); + builder2.setReportPeriod(reportPeriod); + + GroupPopulationReportPluginData proupPopulationReportPluginData1 = builder1.build(); + GroupPopulationReportPluginData proupPopulationReportPluginData2 = builder2.build(); + + // show that the hash code is stable + int hashCode = proupPopulationReportPluginData1.hashCode(); + assertEquals(hashCode, proupPopulationReportPluginData1.hashCode()); + assertEquals(hashCode, proupPopulationReportPluginData1.hashCode()); + assertEquals(hashCode, proupPopulationReportPluginData1.hashCode()); + assertEquals(hashCode, proupPopulationReportPluginData1.hashCode()); + + // show that equal objects have equal hash codes + assertEquals(proupPopulationReportPluginData1.hashCode(), proupPopulationReportPluginData2.hashCode()); + + // collect the hashcode + observedHashCodes.add(proupPopulationReportPluginData1.hashCode()); + } + + /* + * The hash codes should be dispersed -- we only show that they are + * unique values -- this is dependent on the random seed + */ + assertTrue(observedHashCodes.size()>45); + + } + + @Test + @UnitTestMethod(target = GroupPopulationReportPluginData.class, name = "toString", args = {}) + public void testToString() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2394011517139293620L); + for (int i = 0; i < 10; i++) { + GroupPopulationReportPluginData.Builder builder = GroupPopulationReportPluginData.builder(); + + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt(100)); + builder.setReportLabel(reportLabel); + + ReportPeriod reportPeriod = ReportPeriod.values()[randomGenerator.nextInt(ReportPeriod.values().length)]; + builder.setReportPeriod(reportPeriod); + + GroupPopulationReportPluginData groupPopulationReportPluginData = builder.build(); + + StringBuilder sb = new StringBuilder(); + sb.append("GroupPopulationReportPluginData [data="); + + StringBuilder superDataBuilder = new StringBuilder(); + superDataBuilder.append("Data [reportLabel="); + superDataBuilder.append(reportLabel); + superDataBuilder.append(", reportPeriod="); + superDataBuilder.append(reportPeriod); + + StringBuilder dataBuilder = new StringBuilder(); + dataBuilder.append(superDataBuilder.toString()); + dataBuilder.append("]"); + + sb.append(dataBuilder.toString()); + sb.append("]"); + + assertEquals(sb.toString(), groupPopulationReportPluginData.toString()); + } + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/reports/AT_GroupPropertyReport.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/reports/AT_GroupPropertyReport.java new file mode 100644 index 000000000..05a7f9586 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/reports/AT_GroupPropertyReport.java @@ -0,0 +1,963 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.reports; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestOutputConsumer; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.groups.datamanagers.GroupsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupId; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupPropertyDefinitionInitialization; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupTypeId; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.GroupsTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.GroupsTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.TestAuxiliaryGroupPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.TestAuxiliaryGroupTypeId; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.TestGroupPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.TestGroupTypeId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.wrappers.MultiKey; + +public class AT_GroupPropertyReport { + + @Test + @UnitTestMethod(target = GroupPropertyReport.class, name = "init", args = { ReportContext.class }, tags = {}) + public void testInit_IncludeGroupProperty() { + /* + * This test shows that the report produces report items with the + * correct selected properties as a function of the explicitly included + * properties + */ + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // Define a new property to be added at time = 1 + GroupPropertyId newPropertyId = new GroupPropertyId() { + @Override + public String toString() { + return "newPropertyId"; + } + }; + + // have the actor add the new property + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + PropertyDefinition propertyDefinition = PropertyDefinition .builder()// + .setType(Integer.class)// + .setDefaultValue(1)// + .build(); + GroupPropertyDefinitionInitialization groupPropertyDefinitionInitialization = // + GroupPropertyDefinitionInitialization .builder()// + .setGroupTypeId(TestGroupTypeId.GROUP_TYPE_1).setPropertyId(newPropertyId).setPropertyDefinition(propertyDefinition).build(); + groupsDataManager.defineGroupProperty(groupPropertyDefinitionInitialization); + + })); + TestPluginData testPluginData = pluginDataBuilder.build(); + + // create a set of multi keys to hold the expected property ids that we + // will explicitly add to the report + Set expectedOutput = new LinkedHashSet<>(); + expectedOutput.add(new MultiKey(TestGroupTypeId.GROUP_TYPE_3.toString(), TestGroupPropertyId.GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK.toString())); + expectedOutput.add(new MultiKey(TestGroupTypeId.GROUP_TYPE_2.toString(), TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK.toString())); + expectedOutput.add(new MultiKey(TestGroupTypeId.GROUP_TYPE_1.toString(), newPropertyId.toString())); + + /* + * Create the plugin data for the report using the same explicit + * property ids + */ + GroupPropertyReportPluginData.Builder builder = GroupPropertyReportPluginData.builder(); + builder.setReportLabel(new SimpleReportLabel("report label")); + builder.setReportPeriod(ReportPeriod.HOURLY); + builder.setDefaultInclusion(false); + TestGroupPropertyId testGroupPropertyId = TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK; + builder.includeGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId); + testGroupPropertyId = TestGroupPropertyId.GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK; + builder.includeGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId); + builder.includeGroupProperty(TestGroupTypeId.GROUP_TYPE_1, newPropertyId); + GroupPropertyReportPluginData groupPropertyReportPluginData = builder.build(); + + // use the factory to set up the necessary plugins, remembering to add + // the report plugin data + GroupsTestPluginFactory.Factory factory = // + GroupsTestPluginFactory// + .factory(30, 3.0, 10.0, 5029722593563249954L, testPluginData)// + .setGroupPropertyReportPluginData(groupPropertyReportPluginData); + + // execute the simulation with an output consumer + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .build()// + .execute(); + + // show that the report items have the chosen property ids + Map outputItems = testOutputConsumer.getOutputItemMap(ReportItem.class); + Set actualOutput = new LinkedHashSet<>(); + for (ReportItem reportItem : outputItems.keySet()) { + actualOutput.add(new MultiKey(reportItem.getValue(2), reportItem.getValue(3))); + } + assertEquals(expectedOutput, actualOutput); + } + + @Test + @UnitTestMethod(target = GroupPropertyReport.class, name = "init", args = { ReportContext.class }, tags = {}) + public void testInit_ExcludeGroupProperty() { + + /* + * This test shows that the report produces report items with the + * correct selected properties as a function of the explicitly excluded + * properties + */ + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // Define two new properties to be added at time = 1, one that will be + // included and one that will be explicitly excluded + GroupPropertyId newPropertyId1 = new GroupPropertyId() { + @Override + public String toString() { + return "newPropertyId1"; + } + }; + GroupPropertyId newPropertyId2 = new GroupPropertyId() { + @Override + public String toString() { + return "newPropertyId2"; + } + }; + + // have the actor add the new properties + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + PropertyDefinition propertyDefinition = PropertyDefinition .builder()// + .setType(Integer.class)// + .setDefaultValue(1)// + .build(); + GroupPropertyDefinitionInitialization groupPropertyDefinitionInitialization = // + GroupPropertyDefinitionInitialization .builder()// + .setGroupTypeId(TestGroupTypeId.GROUP_TYPE_1)// + .setPropertyId(newPropertyId1)// + .setPropertyDefinition(propertyDefinition)// + .build(); + + groupsDataManager.defineGroupProperty(groupPropertyDefinitionInitialization); + + propertyDefinition = PropertyDefinition .builder()// + .setType(Double.class)// + .setDefaultValue(88.0)// + .build(); + groupPropertyDefinitionInitialization = // + GroupPropertyDefinitionInitialization .builder()// + .setGroupTypeId(TestGroupTypeId.GROUP_TYPE_2)// + .setPropertyId(newPropertyId2)// + .setPropertyDefinition(propertyDefinition)// + .build(); + + groupsDataManager.defineGroupProperty(groupPropertyDefinitionInitialization); + + })); + TestPluginData testPluginData = pluginDataBuilder.build(); + + // create a set of multi keys to hold the expected property ids that we + // will implicitly add to the report + Set expectedOutput = new LinkedHashSet<>(); + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { + if (testGroupPropertyId == TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK) { + continue; + } + if (testGroupPropertyId == TestGroupPropertyId.GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK) { + continue; + } + expectedOutput.add(new MultiKey(testGroupPropertyId.getTestGroupTypeId().toString(), testGroupPropertyId.toString())); + } + expectedOutput.add(new MultiKey(TestGroupTypeId.GROUP_TYPE_2.toString(), newPropertyId2.toString())); + + /* + * Create the plugin data for the report using the same explicitly + * excluded property ids + */ + GroupPropertyReportPluginData.Builder builder = GroupPropertyReportPluginData.builder(); + builder.setReportLabel(new SimpleReportLabel("report label")); + builder.setReportPeriod(ReportPeriod.DAILY); + builder.setDefaultInclusion(true); + TestGroupPropertyId testGroupPropertyId = TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK; + builder.excludeGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId); + testGroupPropertyId = TestGroupPropertyId.GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK; + builder.excludeGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId); + builder.excludeGroupProperty(TestGroupTypeId.GROUP_TYPE_1, newPropertyId1); + GroupPropertyReportPluginData groupPropertyReportPluginData = builder.build(); + + // use the factory to set up the necessary plugins, remembering to add + // the report plugin data + GroupsTestPluginFactory.Factory factory = // + GroupsTestPluginFactory// + .factory(30, 3.0, 10.0, 5029722593563249954L, testPluginData)// + .setGroupPropertyReportPluginData(groupPropertyReportPluginData); + + // execute the simulation with an output consumer + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .build()// + .execute(); + + // show that the report items have the chosen property ids + Map outputItems = testOutputConsumer.getOutputItemMap(ReportItem.class); + Set actualOutput = new LinkedHashSet<>(); + for (ReportItem reportItem : outputItems.keySet()) { + actualOutput.add(new MultiKey(reportItem.getValue(1), reportItem.getValue(2))); + } + + assertEquals(expectedOutput, actualOutput); + } + + @Test + @UnitTestMethod(target = GroupPropertyReport.class, name = "init", args = { ReportContext.class }, tags = {}) + public void testInit_DefaultInclusion() { + + /* + * This test shows that the report produces report items with the + * correct selected properties as a function of the default property + * inclusion policy + */ + + /* + * We will explicitly include and exclude one property and gather the + * remaining properties in a set + */ + TestGroupPropertyId includedPropertyId = TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK; + TestGroupPropertyId excludedPropertyId = TestGroupPropertyId.GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK; + + Set middlePropertyIds = new LinkedHashSet<>(); + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { + middlePropertyIds.add(testGroupPropertyId); + } + middlePropertyIds.remove(includedPropertyId); + middlePropertyIds.remove(excludedPropertyId); + + // There are three possibilities for setting the default inclusion + // policy + enum DefaultInclusionPolicy { + TRUE, FALSE, UNSPECIFIED + } + ; + + for (DefaultInclusionPolicy defaultInclusionPolicy : DefaultInclusionPolicy.values()) { + // build the report plugin data + GroupPropertyReportPluginData.Builder reportBuilder = GroupPropertyReportPluginData.builder(); + reportBuilder.setReportPeriod(ReportPeriod.DAILY); + reportBuilder.setReportLabel(new SimpleReportLabel("report label")); + switch (defaultInclusionPolicy) { + case FALSE: + reportBuilder.setDefaultInclusion(false); + break; + case TRUE: + reportBuilder.setDefaultInclusion(true); + break; + default: + // do nothing + } + + reportBuilder.includeGroupProperty(includedPropertyId.getTestGroupTypeId(), includedPropertyId); + reportBuilder.excludeGroupProperty(excludedPropertyId.getTestGroupTypeId(), excludedPropertyId); + GroupPropertyReportPluginData groupPropertyReportPluginData = reportBuilder.build(); + + /* + * Add a single test actor plan that does nothing so that we can use + * the factory for convenience + */ + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + })); + TestPluginData testPluginData = pluginDataBuilder.build(); + + // build the global plugin using the report plugin data and the + // standard global plugin data build + GroupsTestPluginFactory.Factory factory = // + GroupsTestPluginFactory// + .factory(30, 3.0, 10.0, 274849177891016889L, testPluginData)// + .setGroupPropertyReportPluginData(groupPropertyReportPluginData); + + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .build()// + .execute(); + + // gather from the report items the property ids that were actually + // included in the report + Set actualPropertyIds = new LinkedHashSet<>(); + Map outputItems = testOutputConsumer.getOutputItemMap(ReportItem.class); + for (ReportItem reportItem : outputItems.keySet()) { + TestGroupPropertyId testGroupPropertyId = TestGroupPropertyId.valueOf(reportItem.getValue(2)); + actualPropertyIds.add(testGroupPropertyId); + } + + // build the expected property ids based on the policy + Set expectedPropertyIds = new LinkedHashSet<>(); + expectedPropertyIds.add(includedPropertyId); + + switch (defaultInclusionPolicy) { + case FALSE: + // only the single included property + break; + default: + expectedPropertyIds.addAll(middlePropertyIds); + break; + } + + // show that the property id sets are equals + assertEquals(expectedPropertyIds, actualPropertyIds); + } + + } + + @Test + @UnitTestConstructor(target = GroupPropertyReport.class, args = { GroupPropertyReportPluginData.class }) + public void testConstructor() { + // construction is covered by the other tests + + /* + * Due to this being a periodic report with a super constructor, it is + * not possible for the report to throw a Contract exception when given + * a null GroupPropertyReportPluginData. + */ + + // precondition test: if the GroupPropertyReportPluginData is null + assertThrows(NullPointerException.class, () -> new GroupPropertyReport(null)); + } + + /* + * Returns the conversion into double valued days + * + * preconditions: all entries are non-negative and in their natural ranges + */ + private double getTime(int days, int hours, int minutes) { + return days + (double) hours / 24 + (double) minutes / 1440; + } + + @Test + @UnitTestMethod(target = GroupPropertyReport.class, name = "init", args = { ReportContext.class }) + public void testInit_ReportPeriod() { + /* + * This test shows that the report produces report items with the + * correct report period time values in the header and body lines of the + * report. + */ + for (ReportPeriod reportPeriod : ReportPeriod.values()) { + + // add a test actor so that we can use the factory + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + })); + TestPluginData testPluginData = pluginDataBuilder.build(); + + /* + * create the report with the report period + */ + GroupPropertyReportPluginData.Builder builder = GroupPropertyReportPluginData.builder(); + builder.setReportLabel(new SimpleReportLabel("report label")); + builder.setReportPeriod(reportPeriod); + GroupPropertyReportPluginData groupPropertyReportPluginData = builder.build(); + + // use the factory to set up the necessary plugins, remembering to + // add + // the report plugin data + GroupsTestPluginFactory.Factory factory = // + GroupsTestPluginFactory// + .factory(30, 3.0, 10.0, 5029722593563249954L, testPluginData)// + .setGroupPropertyReportPluginData(groupPropertyReportPluginData); + + // execute the simulation with an output consumer + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .build()// + .execute(); + // show that the report items have the chosen property ids + Map outputItems = testOutputConsumer.getOutputItemMap(ReportItem.class); + + for (ReportItem reportItem : outputItems.keySet()) { + ReportHeader reportHeader = reportItem.getReportHeader(); + switch (reportPeriod) { + case DAILY: + assertEquals(REPORT_DAILY_HEADER, reportHeader); + assertDoesNotThrow(() -> Integer.parseInt(reportItem.getValue(0))); + assertDoesNotThrow(() -> TestGroupTypeId.valueOf(reportItem.getValue(1))); + break; + case END_OF_SIMULATION: + assertEquals(REPORT_END_OF_SIMULATION_HEADER, reportHeader); + assertDoesNotThrow(() -> TestGroupTypeId.valueOf(reportItem.getValue(0))); + break; + case HOURLY: + assertEquals(REPORT_HOURLY_HEADER, reportHeader); + assertDoesNotThrow(() -> Integer.parseInt(reportItem.getValue(0))); + assertDoesNotThrow(() -> Integer.parseInt(reportItem.getValue(1))); + assertDoesNotThrow(() -> TestGroupTypeId.valueOf(reportItem.getValue(2))); + break; + default: + throw new RuntimeException("unhandled case " + reportPeriod); + } + } + } + } + + @Test + @UnitTestMethod(target = GroupPropertyReport.class, name = "init", args = { ReportContext.class }) + public void testInit_ReportLabel() { + + /* + * This test shows that the report produces report items with the + * correct report label. + */ + for (int i = 0; i < 5; i++) { + + ReportLabel reportLabel = new SimpleReportLabel(i); + + // add a test actor so that we can use the factory + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + })); + TestPluginData testPluginData = pluginDataBuilder.build(); + + /* + * create the report with the report period + */ + GroupPropertyReportPluginData.Builder builder = GroupPropertyReportPluginData.builder(); + builder.setReportLabel(reportLabel); + builder.setReportPeriod(ReportPeriod.DAILY); + GroupPropertyReportPluginData groupPropertyReportPluginData = builder.build(); + + // use the factory to set up the necessary plugins, remembering to + // add + // the report plugin data + GroupsTestPluginFactory.Factory factory = // + GroupsTestPluginFactory// + .factory(30, 3.0, 10.0, 5029722593563249954L, testPluginData)// + .setGroupPropertyReportPluginData(groupPropertyReportPluginData); + + // execute the simulation with an output consumer + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .build()// + .execute(); + // show that the report items have the chosen property ids + Map outputItems = testOutputConsumer.getOutputItemMap(ReportItem.class); + assertFalse(outputItems.isEmpty()); + for (ReportItem reportItem : outputItems.keySet()) { + assertEquals(reportLabel, reportItem.getReportLabel()); + } + } + } + + private void testHourlySelectProperties(boolean includeNewProperties) { + + /* + * We will add one agent to move assign property values to groups and + * create and remove groups. Report items from the report will be + * collected in an output consumer and compared to the expected output. + */ + + // add the action plugin + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // have the agent add a new group of type 1 with three people + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(getTime(0, 0, 0), (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + + groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1);// group 0 + groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_2);// group 1 + groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1);// group 2 + groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_2);// group 3 + groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1);// group 4 + groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_2);// group 5 + + })); + + // + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(getTime(0, 1, 10), (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.setGroupPropertyValue(new GroupId(0), TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, true); + groupsDataManager.setGroupPropertyValue(new GroupId(3), TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 45); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(getTime(0, 1, 15), (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupTypeId groupTypeId = TestAuxiliaryGroupTypeId.GROUP_AUX_TYPE_1; + groupsDataManager.addGroupType(groupTypeId); + + /* + * Add three groups before we define the group property so that we + * can show the report initializing values on first encountering the + * new definition because the report skips reporting zero counts + */ + + groupsDataManager.addGroup(groupTypeId); + groupsDataManager.addGroup(groupTypeId); + groupsDataManager.addGroup(groupTypeId); + + GroupPropertyId groupPropertyId = TestAuxiliaryGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK; + PropertyDefinition propertyDefinition = TestAuxiliaryGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK.getPropertyDefinition(); + GroupPropertyDefinitionInitialization groupPropertyDefinitionInitialization = // + GroupPropertyDefinitionInitialization .builder()// + .setGroupTypeId(groupTypeId)// + .setPropertyDefinition(propertyDefinition)// + .setPropertyId(groupPropertyId)// + .build(); + groupsDataManager.defineGroupProperty(groupPropertyDefinitionInitialization); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(getTime(0, 2, 3), (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.setGroupPropertyValue(new GroupId(2), TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, false); + groupsDataManager.setGroupPropertyValue(new GroupId(4), TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, true); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(getTime(0, 5, 0), (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.setGroupPropertyValue(new GroupId(1), TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 123); + groupsDataManager.setGroupPropertyValue(new GroupId(3), TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 123); + groupsDataManager.setGroupPropertyValue(new GroupId(5), TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 123); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(getTime(0, 5, 16), (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.setGroupPropertyValue(new GroupId(0), TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, false); + groupsDataManager.setGroupPropertyValue(new GroupId(5), TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 77); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + TestOutputConsumer expectedOutputConsumer = new TestOutputConsumer(); + + // build the expected output + + expectedOutputConsumer.accept(getReportItem(ReportPeriod.HOURLY, 0, 1, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, false, 3)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.HOURLY, 0, 1, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 0, 3)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.HOURLY, 0, 2, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, false, 2)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.HOURLY, 0, 2, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, true, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.HOURLY, 0, 2, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 0, 2)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.HOURLY, 0, 2, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 45, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.HOURLY, 0, 3, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, false, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.HOURLY, 0, 3, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, true, 2)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.HOURLY, 0, 3, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 0, 2)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.HOURLY, 0, 3, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 45, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.HOURLY, 0, 4, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, false, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.HOURLY, 0, 4, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, true, 2)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.HOURLY, 0, 4, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 0, 2)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.HOURLY, 0, 4, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 45, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.HOURLY, 0, 5, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, false, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.HOURLY, 0, 5, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, true, 2)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.HOURLY, 0, 5, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 123, 3)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.HOURLY, 0, 6, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, false, 2)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.HOURLY, 0, 6, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, true, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.HOURLY, 0, 6, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 123, 2)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.HOURLY, 0, 6, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 77, 1)); + + if (includeNewProperties) { + expectedOutputConsumer.accept( + getReportItem(ReportPeriod.HOURLY, 0, 2, TestAuxiliaryGroupTypeId.GROUP_AUX_TYPE_1, TestAuxiliaryGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, 0, 3)); + expectedOutputConsumer.accept( + getReportItem(ReportPeriod.HOURLY, 0, 3, TestAuxiliaryGroupTypeId.GROUP_AUX_TYPE_1, TestAuxiliaryGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, 0, 3)); + expectedOutputConsumer.accept( + getReportItem(ReportPeriod.HOURLY, 0, 4, TestAuxiliaryGroupTypeId.GROUP_AUX_TYPE_1, TestAuxiliaryGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, 0, 3)); + expectedOutputConsumer.accept( + getReportItem(ReportPeriod.HOURLY, 0, 5, TestAuxiliaryGroupTypeId.GROUP_AUX_TYPE_1, TestAuxiliaryGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, 0, 3)); + expectedOutputConsumer.accept( + getReportItem(ReportPeriod.HOURLY, 0, 6, TestAuxiliaryGroupTypeId.GROUP_AUX_TYPE_1, TestAuxiliaryGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, 0, 3)); + } + + // build the report + GroupPropertyReportPluginData.Builder builder = GroupPropertyReportPluginData.builder(); + builder.setReportLabel(REPORT_LABEL); + builder.setDefaultInclusion(includeNewProperties); + builder.setReportPeriod(ReportPeriod.HOURLY); + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { + builder.excludeGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId); + } + builder.includeGroupProperty(TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK); + builder.includeGroupProperty(TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK); + + GroupPropertyReportPluginData groupPropertyReportPluginData = builder.build(); + + Factory factory = GroupsTestPluginFactory.factory(0, 0, 0, 6092832510476200219L, testPluginData).setGroupPropertyReportPluginData(groupPropertyReportPluginData); + + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .build()// + .execute(); + + Map expectedReportItems = expectedOutputConsumer.getOutputItemMap(ReportItem.class); + Map actualReportItems = testOutputConsumer.getOutputItemMap(ReportItem.class); + assertEquals(expectedReportItems, actualReportItems); + + } + + @Test + @UnitTestMethod(target = GroupPropertyReport.class, name = "init", args = { ReportContext.class }) + public void testInit_Content() { + testDailyAllProperties(); + testHourlySelectProperties(false); + testHourlySelectProperties(true); + } + + private void testDailyAllProperties() { + + /* + * Test for a daily report that includes all property ids. + * + * + * We will add one agent to move, assign property values to groups and + * create and remove groups. Report items from the report will be + * collected in an output consumer and compared to the expected output. + */ + + // add the action plugin + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // have the agent add a new group of type 1 with three people + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + + GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); + assertEquals(0, groupId.getValue()); + + groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_2); + assertEquals(1, groupId.getValue()); + + groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_3); + assertEquals(2, groupId.getValue()); + + groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); + assertEquals(3, groupId.getValue()); + + groupsDataManager.setGroupPropertyValue(new GroupId(3), TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, true); + + groupsDataManager.setGroupPropertyValue(new GroupId(3), TestGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, 45); + + groupsDataManager.setGroupPropertyValue(new GroupId(3), TestGroupPropertyId.GROUP_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK, 16.5); + + })); + + // have the agent add a new group of type 1 with three people + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1.1, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.removeGroup(new GroupId(0)); + groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_2); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2.5, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.setGroupPropertyValue(new GroupId(1), TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 17); + groupsDataManager.setGroupPropertyValue(new GroupId(4), TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK, 800.0); + + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(5.7, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.setGroupPropertyValue(new GroupId(1), TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK, false); + groupsDataManager.setGroupPropertyValue(new GroupId(4), TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 65); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(5.8, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.setGroupPropertyValue(new GroupId(1), TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK, true); + groupsDataManager.setGroupPropertyValue(new GroupId(4), TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 127); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(6.0, (c) -> { + })); + + TestPluginData testPluginData = pluginBuilder.build(); + + // create a container to hold expected results + TestOutputConsumer expectedOutputConsumer = new TestOutputConsumer(); + + // build the expected output + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 1, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, false, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 1, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, 0, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 1, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK, 0.0, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 1, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, true, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 1, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, 45, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 1, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK, 16.5, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 1, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK, false, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 1, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 0, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 1, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK, 0.0, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 1, TestGroupTypeId.GROUP_TYPE_3, TestGroupPropertyId.GROUP_PROPERTY_3_1_BOOLEAN_IMMUTABLE_NO_TRACK, false, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 1, TestGroupTypeId.GROUP_TYPE_3, TestGroupPropertyId.GROUP_PROPERTY_3_2_INTEGER_IMMUTABLE_NO_TRACK, 0, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 1, TestGroupTypeId.GROUP_TYPE_3, TestGroupPropertyId.GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK, 0.0, 1)); + + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 2, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, true, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 2, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, 45, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 2, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK, 16.5, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 2, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK, false, 2)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 2, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 0, 2)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 2, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK, 0.0, 2)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 2, TestGroupTypeId.GROUP_TYPE_3, TestGroupPropertyId.GROUP_PROPERTY_3_1_BOOLEAN_IMMUTABLE_NO_TRACK, false, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 2, TestGroupTypeId.GROUP_TYPE_3, TestGroupPropertyId.GROUP_PROPERTY_3_2_INTEGER_IMMUTABLE_NO_TRACK, 0, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 2, TestGroupTypeId.GROUP_TYPE_3, TestGroupPropertyId.GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK, 0.0, 1)); + + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 3, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, true, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 3, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, 45, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 3, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK, 16.5, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 3, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK, false, 2)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 3, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 0, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 3, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 17, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 3, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK, 0.0, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 3, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK, 800.0, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 3, TestGroupTypeId.GROUP_TYPE_3, TestGroupPropertyId.GROUP_PROPERTY_3_1_BOOLEAN_IMMUTABLE_NO_TRACK, false, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 3, TestGroupTypeId.GROUP_TYPE_3, TestGroupPropertyId.GROUP_PROPERTY_3_2_INTEGER_IMMUTABLE_NO_TRACK, 0, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 3, TestGroupTypeId.GROUP_TYPE_3, TestGroupPropertyId.GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK, 0.0, 1)); + + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 4, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, true, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 4, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, 45, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 4, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK, 16.5, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 4, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK, false, 2)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 4, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 0, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 4, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 17, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 4, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK, 0.0, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 4, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK, 800.0, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 4, TestGroupTypeId.GROUP_TYPE_3, TestGroupPropertyId.GROUP_PROPERTY_3_1_BOOLEAN_IMMUTABLE_NO_TRACK, false, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 4, TestGroupTypeId.GROUP_TYPE_3, TestGroupPropertyId.GROUP_PROPERTY_3_2_INTEGER_IMMUTABLE_NO_TRACK, 0, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 4, TestGroupTypeId.GROUP_TYPE_3, TestGroupPropertyId.GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK, 0.0, 1)); + + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 5, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, true, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 5, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, 45, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 5, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK, 16.5, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 5, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK, false, 2)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 5, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 0, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 5, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 17, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 5, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK, 0.0, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 5, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK, 800.0, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 5, TestGroupTypeId.GROUP_TYPE_3, TestGroupPropertyId.GROUP_PROPERTY_3_1_BOOLEAN_IMMUTABLE_NO_TRACK, false, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 5, TestGroupTypeId.GROUP_TYPE_3, TestGroupPropertyId.GROUP_PROPERTY_3_2_INTEGER_IMMUTABLE_NO_TRACK, 0, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 5, TestGroupTypeId.GROUP_TYPE_3, TestGroupPropertyId.GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK, 0.0, 1)); + + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 6, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, true, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 6, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, 45, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 6, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK, 16.5, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 6, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK, false, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 6, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK, true, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 6, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 17, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 6, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 127, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 6, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK, 0.0, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 6, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK, 800.0, 1));// + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 6, TestGroupTypeId.GROUP_TYPE_3, TestGroupPropertyId.GROUP_PROPERTY_3_1_BOOLEAN_IMMUTABLE_NO_TRACK, false, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 6, TestGroupTypeId.GROUP_TYPE_3, TestGroupPropertyId.GROUP_PROPERTY_3_2_INTEGER_IMMUTABLE_NO_TRACK, 0, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 6, TestGroupTypeId.GROUP_TYPE_3, TestGroupPropertyId.GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK, 0.0, 1)); + + // build the report with all properties selected + GroupPropertyReportPluginData.Builder builder = GroupPropertyReportPluginData.builder(); + builder.setReportLabel(REPORT_LABEL); + builder.setReportPeriod(ReportPeriod.DAILY); + + GroupPropertyReportPluginData groupPropertyReportPluginData = builder.build(); + + Factory factory = GroupsTestPluginFactory.factory(0, 0, 0, 6092832510476200219L, testPluginData).setGroupPropertyReportPluginData(groupPropertyReportPluginData); + + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .build()// + .execute(); + + Map expectedReportItems = expectedOutputConsumer.getOutputItemMap(ReportItem.class); + Map actualReportItems = testOutputConsumer.getOutputItemMap(ReportItem.class); + + assertEquals(expectedReportItems, actualReportItems); + } + + private static ReportItem getReportItem(ReportPeriod reportPeriod, Object... values) { + ReportItem.Builder builder = ReportItem.builder(); + builder.setReportLabel(REPORT_LABEL); + + switch (reportPeriod) { + case DAILY: + builder.setReportHeader(REPORT_DAILY_HEADER); + break; + case HOURLY: + builder.setReportHeader(REPORT_HOURLY_HEADER); + break; + case END_OF_SIMULATION:// fall through + default: + throw new RuntimeException("unhandled case " + reportPeriod); + } + + for (Object value : values) { + builder.addValue(value); + } + return builder.build(); + } + + @Test + @UnitTestMethod(target = GroupPropertyReport.class, name = "init", args = { ReportContext.class }) + public void testInit_ReportHeader() { + + /* + * This test shows that the report produces report items with the + * correct report period time values in the header and body lines of the + * report. + */ + for (ReportPeriod reportPeriod : ReportPeriod.values()) { + + // add a test actor so that we can use the factory + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + })); + TestPluginData testPluginData = pluginDataBuilder.build(); + + /* + * create the report with the report period + */ + GroupPropertyReportPluginData.Builder builder = GroupPropertyReportPluginData.builder(); + builder.setReportLabel(new SimpleReportLabel("report label")); + builder.setReportPeriod(reportPeriod); + GroupPropertyReportPluginData groupPropertyReportPluginData = builder.build(); + + // use the factory to set up the necessary plugins, remembering to + // add + // the report plugin data + GroupsTestPluginFactory.Factory factory = // + GroupsTestPluginFactory// + .factory(30, 3.0, 10.0, 5029722593563249954L, testPluginData)// + .setGroupPropertyReportPluginData(groupPropertyReportPluginData); + + // execute the simulation with an output consumer + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .build()// + .execute(); + + // show that the report items have the chosen property ids + Map outputItems = testOutputConsumer.getOutputItemMap(ReportItem.class); + + for (ReportItem reportItem : outputItems.keySet()) { + ReportHeader reportHeader = reportItem.getReportHeader(); + switch (reportPeriod) { + case DAILY: + assertEquals(REPORT_DAILY_HEADER, reportHeader); + break; + case END_OF_SIMULATION: + assertEquals(REPORT_END_OF_SIMULATION_HEADER, reportHeader); + break; + case HOURLY: + assertEquals(REPORT_HOURLY_HEADER, reportHeader); + break; + default: + throw new RuntimeException("unhandled case " + reportPeriod); + } + } + } + } + + @Test + @UnitTestMethod(target = GroupPropertyReport.class, name = "init", args = { ReportContext.class }) + public void testInit_State() { + // Test with producing simulation + + // add the action plugin + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // have the agent add new groups and set property values + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + + GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); + assertEquals(0, groupId.getValue()); + + groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_2); + assertEquals(1, groupId.getValue()); + + groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_3); + assertEquals(2, groupId.getValue()); + + groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); + assertEquals(3, groupId.getValue()); + + groupsDataManager.setGroupPropertyValue(new GroupId(3), TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, true); + + groupsDataManager.setGroupPropertyValue(new GroupId(3), TestGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, 45); + + groupsDataManager.setGroupPropertyValue(new GroupId(3), TestGroupPropertyId.GROUP_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK, 16.5); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.removeGroup(new GroupId(0)); + groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_2); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2.5, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.setGroupPropertyValue(new GroupId(1), TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 17); + groupsDataManager.setGroupPropertyValue(new GroupId(4), TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK, 800.0); + + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(5.7, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + groupsDataManager.setGroupPropertyValue(new GroupId(1), TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK, false); + groupsDataManager.setGroupPropertyValue(new GroupId(4), TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 65); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + + // build the report with all properties selected + GroupPropertyReportPluginData.Builder builder = GroupPropertyReportPluginData.builder(); + builder.setReportLabel(REPORT_LABEL) + .setReportPeriod(ReportPeriod.DAILY) + .setDefaultInclusion(true) + .excludeGroupProperty(TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK) + .includeGroupProperty(TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK); + + GroupPropertyReportPluginData groupPropertyReportPluginData = builder.build(); + + Factory factory = GroupsTestPluginFactory.factory(0, 0, 0, 6092832510476200219L, testPluginData).setGroupPropertyReportPluginData(groupPropertyReportPluginData); + + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .setProduceSimulationStateOnHalt(true)// + .setSimulationHaltTime(20)// + .build()// + .execute(); + + // show that the plugin data persists after simulation + Map outputItems = testOutputConsumer.getOutputItemMap(GroupPropertyReportPluginData.class); + assertEquals(1, outputItems.size()); + GroupPropertyReportPluginData groupPropertyReportPluginData2 = outputItems.keySet().iterator().next(); + assertEquals(groupPropertyReportPluginData, groupPropertyReportPluginData2); + + // Test without producing simulation + + testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .setProduceSimulationStateOnHalt(false)// + .setSimulationHaltTime(20)// + .build()// + .execute(); + + // show that when the simulation state is not being produced, there is no output plugin data+ + outputItems = testOutputConsumer.getOutputItemMap(GroupPropertyReportPluginData.class); + assertEquals(0, outputItems.size()); + } + + private static final ReportLabel REPORT_LABEL = new SimpleReportLabel("group property report"); + + private static final ReportHeader REPORT_DAILY_HEADER = ReportHeader.builder().add("day").add("group_type").add("property").add("value").add("group_count").build(); + private static final ReportHeader REPORT_HOURLY_HEADER = ReportHeader.builder().add("day").add("hour").add("group_type").add("property").add("value").add("group_count").build(); + private static final ReportHeader REPORT_END_OF_SIMULATION_HEADER = ReportHeader.builder().add("group_type").add("property").add("value").add("group_count").build(); + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/reports/AT_GroupPropertyReportPluginData.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/reports/AT_GroupPropertyReportPluginData.java new file mode 100644 index 000000000..5cc80af9b --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/reports/AT_GroupPropertyReportPluginData.java @@ -0,0 +1,837 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.reports; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupError; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupTypeId; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.TestGroupPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.TestGroupTypeId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportError; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; +import util.wrappers.MultiKey; + +public class AT_GroupPropertyReportPluginData { + + @Test + @UnitTestMethod(target = GroupPropertyReportPluginData.class, name = "builder", args = {}) + public void testBuilder() { + GroupPropertyReportPluginData.Builder builder = GroupPropertyReportPluginData.builder(); + assertNotNull(builder); + } + + @Test + @UnitTestMethod(target = GroupPropertyReportPluginData.Builder.class, name = "build", args = {}) + public void testBuild() { + // the specific capabilities are covered elsewhere + + // precondition test: if the report period is not assigned + ContractException contractException = assertThrows(ContractException.class, () -> // + GroupPropertyReportPluginData.builder()// + .setReportLabel(new SimpleReportLabel(getClass()))// + .build()); + assertEquals(ReportError.NULL_REPORT_PERIOD, contractException.getErrorType()); + + // precondition test: if the report label is not assigned + contractException = assertThrows(ContractException.class, () -> // + GroupPropertyReportPluginData.builder()// + .setReportPeriod(ReportPeriod.DAILY)// + .build()); + assertEquals(ReportError.NULL_REPORT_LABEL, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupPropertyReportPluginData.Builder.class, name = "setReportLabel", args = { + ReportLabel.class }) + public void testSetReportLabel() { + + for (int i = 0; i < 30; i++) { + ReportLabel expectedReportLabel = new SimpleReportLabel(i); + GroupPropertyReportPluginData groupPropertyReportPluginData = // + GroupPropertyReportPluginData.builder()// + .setReportPeriod(ReportPeriod.DAILY)// + .setReportLabel(expectedReportLabel)// + .build(); + + assertEquals(expectedReportLabel, groupPropertyReportPluginData.getReportLabel()); + } + + // precondition: if the report label is null + ContractException contractException = assertThrows(ContractException.class, () -> { + GroupPropertyReportPluginData.builder().setReportLabel(null); + }); + assertEquals(ReportError.NULL_REPORT_LABEL, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupPropertyReportPluginData.Builder.class, name = "setReportPeriod", args = { + ReportPeriod.class }) + public void testSetReportPeriod() { + + ReportLabel reportLabel = new SimpleReportLabel("report label"); + for (ReportPeriod reportPeriod : ReportPeriod.values()) { + + GroupPropertyReportPluginData groupPropertyReportPluginData = // + GroupPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .build(); + + assertEquals(reportPeriod, groupPropertyReportPluginData.getReportPeriod()); + } + + // precondition: if the report period is null + ContractException contractException = assertThrows(ContractException.class, () -> { + GroupPropertyReportPluginData.builder().setReportPeriod(null); + }); + assertEquals(ReportError.NULL_REPORT_PERIOD, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupPropertyReportPluginData.Builder.class, name = "setDefaultInclusion", args = { + boolean.class }) + public void testSetDefaultInclusion() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + ReportPeriod reportPeriod = ReportPeriod.DAILY; + + // show the default value is true + GroupPropertyReportPluginData groupPropertyReportPluginData = // + GroupPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .build(); + assertEquals(true, groupPropertyReportPluginData.getDefaultInclusionPolicy()); + + groupPropertyReportPluginData = // + GroupPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .setDefaultInclusion(true)// + .build(); + assertEquals(true, groupPropertyReportPluginData.getDefaultInclusionPolicy()); + + groupPropertyReportPluginData = // + GroupPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .setDefaultInclusion(false)// + .build(); + assertEquals(false, groupPropertyReportPluginData.getDefaultInclusionPolicy()); + + } + + @Test + @UnitTestMethod(target = GroupPropertyReportPluginData.Builder.class, name = "includeGroupProperty", args = { + GroupTypeId.class, GroupPropertyId.class }) + public void testIncludeGroupProperty() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + ReportPeriod reportPeriod = ReportPeriod.DAILY; + + GroupPropertyReportPluginData groupPropertyReportPluginData = // + GroupPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .build(); + assertTrue(groupPropertyReportPluginData.getIncludedProperties(TestGroupTypeId.GROUP_TYPE_1).isEmpty()); + + // show that inclusion alone works + Set expectedGroupPropertyIds = new LinkedHashSet<>(); + Set expectedMultiKeys = new LinkedHashSet<>(); + + expectedGroupPropertyIds.add(TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK); + expectedGroupPropertyIds.add(TestGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK); + expectedGroupPropertyIds.add(TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK); + + GroupPropertyReportPluginData.Builder builder = GroupPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel);// + + for (TestGroupPropertyId testGroupPropertyId : expectedGroupPropertyIds) { + builder.includeGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId); + expectedMultiKeys.add(new MultiKey(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId)); + } + + Set actualMultiKeys = new LinkedHashSet<>(); + groupPropertyReportPluginData = builder.build(); + for (GroupTypeId testGroupTypeId : groupPropertyReportPluginData.getGroupTypeIds()) { + for (GroupPropertyId testGroupPropertyId : groupPropertyReportPluginData + .getIncludedProperties(testGroupTypeId)) { + actualMultiKeys.add(new MultiKey(testGroupTypeId, testGroupPropertyId)); + } + } + + assertEquals(expectedMultiKeys, actualMultiKeys); + + // show that inclusion will override exclusion + + builder = GroupPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel);// + + for (TestGroupPropertyId testGroupPropertyId : expectedGroupPropertyIds) { + builder.excludeGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId); + builder.includeGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId); + expectedMultiKeys.add(new MultiKey(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId)); + } + + groupPropertyReportPluginData = builder.build(); + + actualMultiKeys = new LinkedHashSet<>(); + groupPropertyReportPluginData = builder.build(); + for (GroupTypeId testGroupTypeId : groupPropertyReportPluginData.getGroupTypeIds()) { + for (GroupPropertyId testGroupPropertyId : groupPropertyReportPluginData + .getIncludedProperties(testGroupTypeId)) { + actualMultiKeys.add(new MultiKey(testGroupTypeId, testGroupPropertyId)); + } + } + + assertEquals(expectedMultiKeys, actualMultiKeys); + + // precondition: if the group type id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + GroupPropertyReportPluginData.builder().includeGroupProperty(null, + TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK); + }); + assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); + + // precondition: if the group property id is null + contractException = assertThrows(ContractException.class, () -> { + GroupPropertyReportPluginData.builder().includeGroupProperty(TestGroupTypeId.GROUP_TYPE_1, null); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupPropertyReportPluginData.Builder.class, name = "excludeGroupProperty", args = { + GroupTypeId.class, GroupPropertyId.class }) + public void testExcludeGroupProperty() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + ReportPeriod reportPeriod = ReportPeriod.DAILY; + + GroupPropertyReportPluginData groupPropertyReportPluginData = // + GroupPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .build(); + assertTrue(groupPropertyReportPluginData.getIncludedProperties(TestGroupTypeId.GROUP_TYPE_1).isEmpty()); + + // show that exclusion alone works + Set expectedGroupPropertyIds = new LinkedHashSet<>(); + Set expectedMultiKeys = new LinkedHashSet<>(); + + expectedGroupPropertyIds.add(TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK); + expectedGroupPropertyIds.add(TestGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK); + expectedGroupPropertyIds.add(TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK); + + GroupPropertyReportPluginData.Builder builder = GroupPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel);// + + for (TestGroupPropertyId testGroupPropertyId : expectedGroupPropertyIds) { + builder.excludeGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId); + expectedMultiKeys.add(new MultiKey(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId)); + } + + Set actualMultiKeys = new LinkedHashSet<>(); + groupPropertyReportPluginData = builder.build(); + for (GroupTypeId testGroupTypeId : groupPropertyReportPluginData.getGroupTypeIds()) { + for (GroupPropertyId testGroupPropertyId : groupPropertyReportPluginData + .getExcludedProperties(testGroupTypeId)) { + actualMultiKeys.add(new MultiKey(testGroupTypeId, testGroupPropertyId)); + } + } + + assertEquals(expectedMultiKeys, actualMultiKeys); + + // show that exclusion will override exclusion + + builder = GroupPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel);// + + for (TestGroupPropertyId testGroupPropertyId : expectedGroupPropertyIds) { + builder.includeGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId); + builder.excludeGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId); + + expectedMultiKeys.add(new MultiKey(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId)); + } + + groupPropertyReportPluginData = builder.build(); + + actualMultiKeys = new LinkedHashSet<>(); + groupPropertyReportPluginData = builder.build(); + for (GroupTypeId testGroupTypeId : groupPropertyReportPluginData.getGroupTypeIds()) { + for (GroupPropertyId testGroupPropertyId : groupPropertyReportPluginData + .getExcludedProperties(testGroupTypeId)) { + actualMultiKeys.add(new MultiKey(testGroupTypeId, testGroupPropertyId)); + } + } + + assertEquals(expectedMultiKeys, actualMultiKeys); + + // precondition: if the group type id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + GroupPropertyReportPluginData.builder().excludeGroupProperty(null, + TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK); + }); + assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); + + // precondition: if the group property id is null + contractException = assertThrows(ContractException.class, () -> { + GroupPropertyReportPluginData.builder().excludeGroupProperty(TestGroupTypeId.GROUP_TYPE_1, null); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupPropertyReportPluginData.class, name = "getReportLabel", args = {}) + public void testGetReportLabel() { + for (int i = 0; i < 30; i++) { + ReportLabel expectedReportLabel = new SimpleReportLabel(i); + GroupPropertyReportPluginData groupPropertyReportPluginData = // + GroupPropertyReportPluginData.builder()// + .setReportPeriod(ReportPeriod.DAILY)// + .setReportLabel(expectedReportLabel)// + .build(); + + assertEquals(expectedReportLabel, groupPropertyReportPluginData.getReportLabel()); + } + + } + + @Test + @UnitTestMethod(target = GroupPropertyReportPluginData.class, name = "getReportPeriod", args = {}) + public void testGetReportPeriod() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + for (ReportPeriod reportPeriod : ReportPeriod.values()) { + + GroupPropertyReportPluginData groupPropertyReportPluginData = // + GroupPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .build(); + + assertEquals(reportPeriod, groupPropertyReportPluginData.getReportPeriod()); + } + } + + @Test + @UnitTestMethod(target = GroupPropertyReportPluginData.class, name = "getIncludedProperties", args = { + GroupTypeId.class }) + + public void testGetIncludedProperties() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + ReportPeriod reportPeriod = ReportPeriod.DAILY; + + GroupPropertyReportPluginData groupPropertyReportPluginData = // + GroupPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .build(); + assertTrue(groupPropertyReportPluginData.getIncludedProperties(TestGroupTypeId.GROUP_TYPE_1).isEmpty()); + + // show that inclusion alone works + Set expectedGroupPropertyIds = new LinkedHashSet<>(); + Set expectedMultiKeys = new LinkedHashSet<>(); + + expectedGroupPropertyIds.add(TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK); + expectedGroupPropertyIds.add(TestGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK); + expectedGroupPropertyIds.add(TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK); + + GroupPropertyReportPluginData.Builder builder = GroupPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel);// + + for (TestGroupPropertyId testGroupPropertyId : expectedGroupPropertyIds) { + builder.includeGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId); + expectedMultiKeys.add(new MultiKey(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId)); + } + + Set actualMultiKeys = new LinkedHashSet<>(); + groupPropertyReportPluginData = builder.build(); + for (GroupTypeId testGroupTypeId : groupPropertyReportPluginData.getGroupTypeIds()) { + for (GroupPropertyId testGroupPropertyId : groupPropertyReportPluginData + .getIncludedProperties(testGroupTypeId)) { + actualMultiKeys.add(new MultiKey(testGroupTypeId, testGroupPropertyId)); + } + } + + assertEquals(expectedMultiKeys, actualMultiKeys); + + // show that inclusion will override exclusion + + builder = GroupPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel);// + + for (TestGroupPropertyId testGroupPropertyId : expectedGroupPropertyIds) { + builder.excludeGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId); + builder.includeGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId); + expectedMultiKeys.add(new MultiKey(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId)); + } + + groupPropertyReportPluginData = builder.build(); + + actualMultiKeys = new LinkedHashSet<>(); + groupPropertyReportPluginData = builder.build(); + for (GroupTypeId testGroupTypeId : groupPropertyReportPluginData.getGroupTypeIds()) { + for (GroupPropertyId testGroupPropertyId : groupPropertyReportPluginData + .getIncludedProperties(testGroupTypeId)) { + actualMultiKeys.add(new MultiKey(testGroupTypeId, testGroupPropertyId)); + } + } + + assertEquals(expectedMultiKeys, actualMultiKeys); + + // precondition: if the group type id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + GroupPropertyReportPluginData groupPropertyReportPluginData2 = GroupPropertyReportPluginData.builder() + .setReportLabel(reportLabel).setReportPeriod(reportPeriod).build(); + groupPropertyReportPluginData2.getIncludedProperties(null); + }); + assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupPropertyReportPluginData.class, name = "getExcludedProperties", args = { + GroupTypeId.class }) + public void testGetExcludedProperties() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + ReportPeriod reportPeriod = ReportPeriod.DAILY; + + GroupPropertyReportPluginData groupPropertyReportPluginData = // + GroupPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .build(); + assertTrue(groupPropertyReportPluginData.getIncludedProperties(TestGroupTypeId.GROUP_TYPE_1).isEmpty()); + + // show that exclusion alone works + Set expectedGroupPropertyIds = new LinkedHashSet<>(); + Set expectedMultiKeys = new LinkedHashSet<>(); + + expectedGroupPropertyIds.add(TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK); + expectedGroupPropertyIds.add(TestGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK); + expectedGroupPropertyIds.add(TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK); + + GroupPropertyReportPluginData.Builder builder = GroupPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel);// + + for (TestGroupPropertyId testGroupPropertyId : expectedGroupPropertyIds) { + builder.excludeGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId); + expectedMultiKeys.add(new MultiKey(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId)); + } + + Set actualMultiKeys = new LinkedHashSet<>(); + groupPropertyReportPluginData = builder.build(); + for (GroupTypeId testGroupTypeId : groupPropertyReportPluginData.getGroupTypeIds()) { + for (GroupPropertyId testGroupPropertyId : groupPropertyReportPluginData + .getExcludedProperties(testGroupTypeId)) { + actualMultiKeys.add(new MultiKey(testGroupTypeId, testGroupPropertyId)); + } + } + + assertEquals(expectedMultiKeys, actualMultiKeys); + + // show that exclusion will override exclusion + + builder = GroupPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel);// + + for (TestGroupPropertyId testGroupPropertyId : expectedGroupPropertyIds) { + builder.includeGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId); + builder.excludeGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId); + + expectedMultiKeys.add(new MultiKey(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId)); + } + + groupPropertyReportPluginData = builder.build(); + + actualMultiKeys = new LinkedHashSet<>(); + groupPropertyReportPluginData = builder.build(); + for (GroupTypeId testGroupTypeId : groupPropertyReportPluginData.getGroupTypeIds()) { + for (GroupPropertyId testGroupPropertyId : groupPropertyReportPluginData + .getExcludedProperties(testGroupTypeId)) { + actualMultiKeys.add(new MultiKey(testGroupTypeId, testGroupPropertyId)); + } + } + + assertEquals(expectedMultiKeys, actualMultiKeys); + + // precondition: if the group type id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + GroupPropertyReportPluginData groupPropertyReportPluginData2 = GroupPropertyReportPluginData.builder() + .setReportLabel(reportLabel).setReportPeriod(reportPeriod).build(); + groupPropertyReportPluginData2.getExcludedProperties(null); + }); + assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupPropertyReportPluginData.class, name = "getDefaultInclusionPolicy", args = {}) + public void testGetDefaultInclusionPolicy() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + ReportPeriod reportPeriod = ReportPeriod.DAILY; + + // show the default value is true + GroupPropertyReportPluginData groupPropertyReportPluginData = // + GroupPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .build(); + assertEquals(true, groupPropertyReportPluginData.getDefaultInclusionPolicy()); + + groupPropertyReportPluginData = // + GroupPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .setDefaultInclusion(true)// + .build(); + assertEquals(true, groupPropertyReportPluginData.getDefaultInclusionPolicy()); + + groupPropertyReportPluginData = // + GroupPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .setDefaultInclusion(false)// + .build(); + assertEquals(false, groupPropertyReportPluginData.getDefaultInclusionPolicy()); + } + + @Test + @UnitTestMethod(target = GroupPropertyReportPluginData.class, name = "getCloneBuilder", args = {}) + public void testGetCloneBuilder() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7759639255438669162L); + + for (int i = 0; i < 10; i++) { + + // build a GroupPropertyReportPluginData from random inputs + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt()); + ReportPeriod reportPeriod = ReportPeriod.values()[randomGenerator.nextInt(ReportPeriod.values().length)]; + + GroupPropertyReportPluginData.Builder builder = // + GroupPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel); + + for (int j = 0; j < 10; j++) { + TestGroupPropertyId testGroupPropertyId = TestGroupPropertyId + .getRandomTestGroupPropertyId(randomGenerator); + if (randomGenerator.nextBoolean()) { + builder.includeGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId); + } else { + builder.excludeGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId); + } + } + + builder.setDefaultInclusion(randomGenerator.nextBoolean()).build(); + + GroupPropertyReportPluginData groupPropertyReportPluginData = builder.build(); + + // create the clone builder and have it build + GroupPropertyReportPluginData cloneGroupPropertyReportPluginData = groupPropertyReportPluginData + .getCloneBuilder().build(); + + // the result should equal the original if the clone builder was + // initialized with the correct state + assertEquals(groupPropertyReportPluginData, cloneGroupPropertyReportPluginData); + + } + } + + @Test + @UnitTestMethod(target = GroupPropertyReportPluginData.class, name = "equals", args = { Object.class }) + public void testEquals() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7759639255438669162L); + for (int i = 0; i < 10; i++) { + // build a GroupPropertyReportPluginData from the same random + // inputs + GroupPropertyReportPluginData.Builder builder1 = GroupPropertyReportPluginData.builder(); + GroupPropertyReportPluginData.Builder builder2 = GroupPropertyReportPluginData.builder(); + + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt(100)); + builder1.setReportLabel(reportLabel); + builder2.setReportLabel(reportLabel); + + ReportPeriod reportPeriod = ReportPeriod.values()[randomGenerator.nextInt(ReportPeriod.values().length)]; + builder1.setReportPeriod(reportPeriod); + builder2.setReportPeriod(reportPeriod); + + for (int j = 0; j < 10; j++) { + TestGroupPropertyId testGroupPropertyId = TestGroupPropertyId + .getRandomTestGroupPropertyId(randomGenerator); + if (randomGenerator.nextBoolean()) { + builder1.includeGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId); + builder2.includeGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId); + } else { + builder1.excludeGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId); + builder2.excludeGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId); + } + } + + boolean defaultInclusion = randomGenerator.nextBoolean(); + builder1.setDefaultInclusion(defaultInclusion).build(); + builder2.setDefaultInclusion(defaultInclusion).build(); + + GroupPropertyReportPluginData groupPropertyReportPluginData1 = builder1.build(); + GroupPropertyReportPluginData groupPropertyReportPluginData2 = builder2.build(); + + assertEquals(groupPropertyReportPluginData1, groupPropertyReportPluginData2); + + // show that plugin datas with different inputs are not equal + + // change the default inclusion + groupPropertyReportPluginData2 = // + groupPropertyReportPluginData1.getCloneBuilder()// + .setDefaultInclusion(!defaultInclusion)// + .build(); + assertNotEquals(groupPropertyReportPluginData2, groupPropertyReportPluginData1); + + // change the report period + int ord = reportPeriod.ordinal() + 1; + ord = ord % ReportPeriod.values().length; + reportPeriod = ReportPeriod.values()[ord]; + groupPropertyReportPluginData2 = // + groupPropertyReportPluginData1.getCloneBuilder()// + .setReportPeriod(reportPeriod)// + .build(); + assertNotEquals(groupPropertyReportPluginData2, groupPropertyReportPluginData1); + + // change the report label + reportLabel = new SimpleReportLabel(1000); + groupPropertyReportPluginData2 = // + groupPropertyReportPluginData1.getCloneBuilder()// + .setReportLabel(reportLabel)// + .build(); + assertNotEquals(groupPropertyReportPluginData2, groupPropertyReportPluginData1); + + // change an included property id + for (GroupTypeId testGroupTypeId : groupPropertyReportPluginData1.getGroupTypeIds()) { + if (!groupPropertyReportPluginData1.getIncludedProperties(testGroupTypeId).isEmpty()) { + GroupPropertyId testGroupPropertyId = groupPropertyReportPluginData1 + .getIncludedProperties(testGroupTypeId).iterator().next(); + groupPropertyReportPluginData2 = // + groupPropertyReportPluginData1.getCloneBuilder()// + .excludeGroupProperty(testGroupTypeId, testGroupPropertyId)// + .build(); + assertNotEquals(groupPropertyReportPluginData2, groupPropertyReportPluginData1); + } + } + // change an excluded property id + for (GroupTypeId testGroupTypeId : groupPropertyReportPluginData1.getGroupTypeIds()) { + if (!groupPropertyReportPluginData1.getExcludedProperties(testGroupTypeId).isEmpty()) { + GroupPropertyId testGroupPropertyId = groupPropertyReportPluginData1 + .getExcludedProperties(testGroupTypeId).iterator().next(); + groupPropertyReportPluginData2 = // + groupPropertyReportPluginData1.getCloneBuilder()// + .includeGroupProperty(testGroupTypeId, testGroupPropertyId)// + .build(); + assertNotEquals(groupPropertyReportPluginData2, groupPropertyReportPluginData1); + } + } + } + + } + + @Test + @UnitTestMethod(target = GroupPropertyReportPluginData.class, name = "hashCode", args = {}) + public void testHashCode() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(9079768427072825406L); + + Set observedHashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 50; i++) { + // build a GroupPropertyReportPluginData from the same random + // inputs + GroupPropertyReportPluginData.Builder builder1 = GroupPropertyReportPluginData.builder(); + GroupPropertyReportPluginData.Builder builder2 = GroupPropertyReportPluginData.builder(); + + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt(100)); + builder1.setReportLabel(reportLabel); + builder2.setReportLabel(reportLabel); + + ReportPeriod reportPeriod = ReportPeriod.values()[randomGenerator.nextInt(ReportPeriod.values().length)]; + builder1.setReportPeriod(reportPeriod); + builder2.setReportPeriod(reportPeriod); + + for (int j = 0; j < 10; j++) { + TestGroupPropertyId testGroupPropertyId = TestGroupPropertyId + .getRandomTestGroupPropertyId(randomGenerator); + if (randomGenerator.nextBoolean()) { + builder1.includeGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId); + builder2.includeGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId); + } else { + builder1.excludeGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId); + builder2.excludeGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId); + } + } + + boolean defaultInclusion = randomGenerator.nextBoolean(); + builder1.setDefaultInclusion(defaultInclusion).build(); + builder2.setDefaultInclusion(defaultInclusion).build(); + + GroupPropertyReportPluginData groupPropertyReportPluginData1 = builder1.build(); + GroupPropertyReportPluginData groupPropertyReportPluginData2 = builder2.build(); + + // show that the hash code is stable + int hashCode = groupPropertyReportPluginData1.hashCode(); + assertEquals(hashCode, groupPropertyReportPluginData1.hashCode()); + assertEquals(hashCode, groupPropertyReportPluginData1.hashCode()); + assertEquals(hashCode, groupPropertyReportPluginData1.hashCode()); + assertEquals(hashCode, groupPropertyReportPluginData1.hashCode()); + + // show that equal objects have equal hash codes + assertEquals(groupPropertyReportPluginData1.hashCode(), groupPropertyReportPluginData2.hashCode()); + + // collect the hashcode + observedHashCodes.add(groupPropertyReportPluginData1.hashCode()); + } + + /* + * The hash codes should be dispersed -- we only show that they are unique + * values -- this is dependent on the random seed + */ + assertEquals(50, observedHashCodes.size()); + + } + + @Test + @UnitTestMethod(target = GroupPropertyReportPluginData.class, name = "getGroupTypeIds", args = {}) + public void testGetGroupTypeIds() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(626906625322362256L); + + for (int i = 0; i < 50; i++) { + GroupPropertyReportPluginData.Builder builder1 = GroupPropertyReportPluginData.builder(); + + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt(100)); + builder1.setReportLabel(reportLabel); + + ReportPeriod reportPeriod = ReportPeriod.values()[randomGenerator.nextInt(ReportPeriod.values().length)]; + builder1.setReportPeriod(reportPeriod); + + Set expectedGroupTypeIds = new LinkedHashSet<>(); + + int propertyCount = randomGenerator.nextInt(3) + 1; + for (int j = 0; j < propertyCount; j++) { + TestGroupPropertyId testGroupPropertyId = TestGroupPropertyId + .getRandomTestGroupPropertyId(randomGenerator); + expectedGroupTypeIds.add(testGroupPropertyId.getTestGroupTypeId()); + if (randomGenerator.nextBoolean()) { + builder1.includeGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId); + } else { + builder1.excludeGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId); + } + } + + boolean defaultInclusion = randomGenerator.nextBoolean(); + builder1.setDefaultInclusion(defaultInclusion).build(); + + GroupPropertyReportPluginData groupPropertyReportPluginData = builder1.build(); + + assertEquals(expectedGroupTypeIds, groupPropertyReportPluginData.getGroupTypeIds()); + } + + } + + @Test + @UnitTestMethod(target = GroupPropertyReportPluginData.class, name = "toString", args = {}) + public void testToString() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2394011517139293620L); + for (int i = 0; i < 10; i++) { + GroupPropertyReportPluginData.Builder builder = GroupPropertyReportPluginData.builder(); + + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt(100)); + builder.setReportLabel(reportLabel); + + ReportPeriod reportPeriod = ReportPeriod.values()[randomGenerator.nextInt(ReportPeriod.values().length)]; + builder.setReportPeriod(reportPeriod); + + Map> includedIds = new LinkedHashMap<>(); + Map> excludedIds = new LinkedHashMap<>(); + + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.getShuffledTestGroupTypeIds(randomGenerator)) { + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId + .getShuffledTestGroupPropertyIds(testGroupTypeId, randomGenerator)) { + if (randomGenerator.nextBoolean()) { + builder.includeGroupProperty(testGroupTypeId, testGroupPropertyId); + Set set = includedIds.get(testGroupTypeId); + if (set == null) { + set = new LinkedHashSet<>(); + includedIds.put(testGroupTypeId, set); + } + set.add(testGroupPropertyId); + set = excludedIds.get(testGroupTypeId); + if (set != null) { + set.remove(testGroupPropertyId); + } + } else { + builder.excludeGroupProperty(testGroupTypeId, testGroupPropertyId); + Set set = excludedIds.get(testGroupTypeId); + if (set == null) { + set = new LinkedHashSet<>(); + excludedIds.put(testGroupTypeId, set); + } + set.add(testGroupPropertyId); + set = includedIds.get(testGroupTypeId); + if (set != null) { + set.remove(testGroupPropertyId); + } + } + } + } + + boolean defaultInclusion = randomGenerator.nextBoolean(); + builder.setDefaultInclusion(defaultInclusion).build(); + + GroupPropertyReportPluginData groupPropertyReportPluginData = builder.build(); + + StringBuilder sb = new StringBuilder(); + sb.append("GroupPropertyReportPluginData [data="); + + StringBuilder superDataBuilder = new StringBuilder(); + superDataBuilder.append("Data [reportLabel="); + superDataBuilder.append(reportLabel); + superDataBuilder.append(", reportPeriod="); + superDataBuilder.append(reportPeriod); + + StringBuilder dataBuilder = new StringBuilder(); + dataBuilder.append(superDataBuilder.toString()); + dataBuilder.append(", includedProperties="); + dataBuilder.append(includedIds); + dataBuilder.append(", excludedProperties="); + dataBuilder.append(excludedIds); + dataBuilder.append(", defaultInclusionPolicy="); + dataBuilder.append(defaultInclusion); + dataBuilder.append("]"); + + sb.append(dataBuilder.toString()); + sb.append("]"); + + assertEquals(sb.toString(), groupPropertyReportPluginData.toString()); + } + } +} diff --git a/gcm3/src/test/java/plugins/groups/support/AT_GroupConstructionInfo.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupConstructionInfo.java similarity index 81% rename from gcm3/src/test/java/plugins/groups/support/AT_GroupConstructionInfo.java rename to gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupConstructionInfo.java index d3a720eb5..49786dc3c 100644 --- a/gcm3/src/test/java/plugins/groups/support/AT_GroupConstructionInfo.java +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupConstructionInfo.java @@ -1,4 +1,4 @@ -package plugins.groups.support; +package gov.hhs.aspr.ms.gcm.plugins.groups.support; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -10,18 +10,17 @@ import org.apache.commons.math3.random.RandomGenerator; import org.junit.jupiter.api.Test; -import plugins.groups.testsupport.TestGroupPropertyId; -import plugins.groups.testsupport.TestGroupTypeId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.TestGroupPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.TestGroupTypeId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestMethod; import util.errors.ContractException; import util.random.RandomGeneratorProvider; -@UnitTest(target = GroupConstructionInfo.class) public final class AT_GroupConstructionInfo { @Test - @UnitTestMethod(name = "getGroupTypeId", args = {}) + @UnitTestMethod(target = GroupConstructionInfo.class, name = "getGroupTypeId", args = {}) public void testGetGroupTypeId() { for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { GroupConstructionInfo groupConstructionInfo = GroupConstructionInfo.builder().setGroupTypeId(testGroupTypeId).build(); @@ -30,7 +29,7 @@ public void testGetGroupTypeId() { } @Test - @UnitTestMethod(name = "getPropertyValues", args = {}) + @UnitTestMethod(target = GroupConstructionInfo.class, name = "getPropertyValues", args = {}) public void testGetPropertyValues() { RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6591155321511911942L); for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { @@ -60,7 +59,7 @@ public void testGetPropertyValues() { } @Test - @UnitTestMethod(name = "builder", args = {}) + @UnitTestMethod(target = GroupConstructionInfo.class, name = "builder", args = {}) public void testBuilder() { assertNotNull(GroupConstructionInfo.builder()); @@ -73,8 +72,8 @@ public void testBuild() { assertNotNull(GroupConstructionInfo.builder().setGroupTypeId(TestGroupTypeId.GROUP_TYPE_1).build()); // precondition tests - - //if the group property value is not set + + // if the group property value is not set ContractException contractException = assertThrows(ContractException.class, () -> GroupConstructionInfo.builder().build()); assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); } @@ -93,8 +92,8 @@ public void testSetGroupTypeId() { assertEquals(expectedGroupTypeId, groupConstructionInfo.getGroupTypeId()); } // precondition tests - - //if the group property value is set to null + + // if the group property value is set to null ContractException contractException = assertThrows(ContractException.class, () -> GroupConstructionInfo.builder().setGroupTypeId(null).build()); assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); @@ -132,25 +131,22 @@ public void testSetGroupPropertyValue() { } } // precondition tests - - //if a group property id is null + + // if a group property id is null ContractException contractException = assertThrows(ContractException.class, () -> { - GroupConstructionInfo.builder()// - .setGroupTypeId(TestGroupTypeId.GROUP_TYPE_1)// - .setGroupPropertyValue(null, 12) - .build();// + GroupConstructionInfo .builder()// + .setGroupTypeId(TestGroupTypeId.GROUP_TYPE_1)// + .setGroupPropertyValue(null, 12).build();// }); - assertEquals(GroupError.NULL_GROUP_PROPERTY_ID, contractException.getErrorType()); - - //if a group property value is null + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // if a group property value is null contractException = assertThrows(ContractException.class, () -> { - GroupConstructionInfo.builder()// - .setGroupTypeId(TestGroupTypeId.GROUP_TYPE_1)// - .setGroupPropertyValue(TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, null) - .build();// + GroupConstructionInfo .builder()// + .setGroupTypeId(TestGroupTypeId.GROUP_TYPE_1)// + .setGroupPropertyValue(TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, null).build();// }); - assertEquals(GroupError.NULL_GROUP_PROPERTY_VALUE, contractException.getErrorType()); - + assertEquals(PropertyError.NULL_PROPERTY_VALUE, contractException.getErrorType()); } diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupError.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupError.java new file mode 100644 index 000000000..be743c196 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupError.java @@ -0,0 +1,28 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.support; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestMethod; + +public class AT_GroupError { + + @Test + @UnitTestMethod(target = GroupError.class, name = "getDescription", args = {}) + public void test() { + // show that each description is a unique, non-null and non-empty string + Set descriptions = new LinkedHashSet<>(); + for (GroupError groupError : GroupError.values()) { + String description = groupError.getDescription(); + assertNotNull(description, "null description for " + groupError); + assertTrue(description.length() > 0, "empty string for " + groupError); + boolean unique = descriptions.add(description); + assertTrue(unique, "description for " + groupError + " is not unique"); + } + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupId.java new file mode 100644 index 000000000..1a756bf4c --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupId.java @@ -0,0 +1,92 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + +public class AT_GroupId { + + + @Test + @UnitTestConstructor(target = GroupId.class,args = { int.class }) + public void testConstructor() { + for (int i = 0; i < 10; i++) { + GroupId GroupId = new GroupId(i); + assertEquals(i, GroupId.getValue()); + } + //precondition test: if the id < 0 + ContractException contractException = assertThrows(ContractException.class, ()->new GroupId(-1)); + assertEquals(GroupError.NEGATIVE_GROUP_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupId.class,name = "compareTo", args = { GroupId.class }) + public void testCompareTo() { + for (int i = 0; i < 10; i++) { + GroupId groupA = new GroupId(i); + for (int j = 0; j < 10; j++) { + GroupId groupB = new GroupId(j); + int comparisonValue = groupA.compareTo(groupB); + if (i < j) { + assertTrue(comparisonValue < 0); + } else if (i > j) { + assertTrue(comparisonValue > 0); + } else { + assertTrue(comparisonValue == 0); + } + } + } + } + + @Test + @UnitTestMethod(target = GroupId.class,name = "equals", args = { Object.class }) + public void testEquals() { + for (int i = 0; i < 10; i++) { + GroupId groupA = new GroupId(i); + for (int j = 0; j < 10; j++) { + GroupId groupB = new GroupId(j); + if (i == j) { + assertEquals(groupA,groupB); + } else { + assertNotEquals(groupA,groupB); + } + } + } + } + + @Test + @UnitTestMethod(target = GroupId.class,name = "getValue", args = {}) + public void testGetValue() { + for (int i = 0; i < 10; i++) { + GroupId group = new GroupId(i); + assertEquals(i, group.getValue()); + } + } + + @Test + @UnitTestMethod(target = GroupId.class,name = "hashCode", args = {}) + public void testHashCode() { + for (int i = 0; i < 10; i++) { + GroupId group = new GroupId(i); + assertEquals(i, group.hashCode()); + } + } + + @Test + @UnitTestMethod(target = GroupId.class,name = "toString", args = {}) + public void testToString() { + for (int i = 0; i < 10; i++) { + GroupId group = new GroupId(i); + assertEquals(Integer.toString(i), group.toString()); + } + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupLabeler.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupLabeler.java new file mode 100644 index 000000000..c0ea1a8fd --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupLabeler.java @@ -0,0 +1,266 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Function; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.groups.datamanagers.GroupsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupMembershipAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupMembershipRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.GroupsTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.GroupsTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.TestGroupTypeId; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.LabelerSensitivity; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.TestPartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + +public final class AT_GroupLabeler { + private static class LocalGroupLabeler extends GroupLabeler { + private final Function labelingFunction; + + public LocalGroupLabeler(Function labelingFunction) { + this.labelingFunction = labelingFunction; + } + + @Override + protected Object getLabelFromGroupTypeCountMap(GroupTypeCountMap groupTypeCountMap) { + return labelingFunction.apply(groupTypeCountMap); + } + + } + + @Test + @UnitTestMethod(target = GroupLabeler.class, name = "getLabelerSensitivities", args = {}) + public void testGetLabelerSensitivities() { + + Set> labelerSensitivities = new LocalGroupLabeler((g) -> null).getLabelerSensitivities(); + + // show that we get back some labeler sensitivities + assertNotNull(labelerSensitivities); + + // we expect exactly two + assertEquals(2, labelerSensitivities.size()); + + boolean groupMembershipAdditionEventSensitivityFound = false; + boolean groupMembershipRemovalEventSensitivityFound = false; + for (LabelerSensitivity labelerSensitivity : labelerSensitivities) { + if (labelerSensitivity.getEventClass() == GroupMembershipAdditionEvent.class) { + groupMembershipAdditionEventSensitivityFound = true; + PersonId personId = new PersonId(45253); + + Optional optional = labelerSensitivity + .getPersonId(new GroupMembershipAdditionEvent(personId, new GroupId(56))); + assertTrue(optional.isPresent()); + PersonId actualPersonId = optional.get(); + assertEquals(personId, actualPersonId); + + } else if (labelerSensitivity.getEventClass() == GroupMembershipRemovalEvent.class) { + groupMembershipRemovalEventSensitivityFound = true; + PersonId personId = new PersonId(45253); + + Optional optional = labelerSensitivity + .getPersonId(new GroupMembershipRemovalEvent(personId, new GroupId(56))); + assertTrue(optional.isPresent()); + PersonId actualPersonId = optional.get(); + assertEquals(personId, actualPersonId); + + } else { + fail("unknown labeler sensitivity"); + } + } + + // show that we found both labeler sensitivities + assertTrue(groupMembershipAdditionEventSensitivityFound); + assertTrue(groupMembershipRemovalEventSensitivityFound); + + } + + @Test + @UnitTestMethod(target = GroupLabeler.class, name = "getCurrentLabel", args = { PartitionsContext.class, + PersonId.class }) + public void testGetCurrentLabel() { + + Consumer consumer = (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + + Function func = (g) -> { + int result = 0; + for (GroupTypeId groupTypeId : g.getGroupTypeIds()) { + TestGroupTypeId testGroupTypeId = (TestGroupTypeId) groupTypeId; + result += (testGroupTypeId.ordinal() + 1) * g.getGroupCount(groupTypeId); + } + return result; + }; + + GroupLabeler groupLabeler = new LocalGroupLabeler(func); + + for (PersonId personId : peopleDataManager.getPeople()) { + GroupTypeCountMap.Builder builder = GroupTypeCountMap.builder(); + for (GroupTypeId groupTypeId : groupsDataManager.getGroupTypeIds()) { + builder.setCount(groupTypeId, + groupsDataManager.getGroupCountForGroupTypeAndPerson(groupTypeId, personId)); + } + GroupTypeCountMap groupTypeCountMap = builder.build(); + Object expectedLabel = func.apply(groupTypeCountMap); + Object actualLabel = groupLabeler.getCurrentLabel(testPartitionsContext, personId); + assertEquals(expectedLabel, actualLabel); + } + + // precondition tests + + // if the person id is null + ContractException contractException = assertThrows(ContractException.class, + () -> groupLabeler.getCurrentLabel(testPartitionsContext, null)); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + // if the person id is unknown + contractException = assertThrows(ContractException.class, + () -> groupLabeler.getCurrentLabel(testPartitionsContext, new PersonId(100000))); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + }; + + Factory factory = GroupsTestPluginFactory.factory(30, 3, 5, 5880749882920317232L, consumer); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = GroupLabeler.class, name = "getId", args = {}) + public void testGetId() { + Function f = (g) -> null; + assertEquals(GroupTypeId.class, new LocalGroupLabeler(f).getId()); + } + + @Test + @UnitTestMethod(target = GroupLabeler.class, name = "getPastLabel", args = { PartitionsContext.class, Event.class }) + public void testGetPastLabel() { + + Consumer consumer = (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + int delta; + Function func = (g) -> { + int result = 0; + for (GroupTypeId groupTypeId : g.getGroupTypeIds()) { + + TestGroupTypeId testGroupTypeId = (TestGroupTypeId) groupTypeId; + result += (testGroupTypeId.ordinal() + 1) * g.getGroupCount(groupTypeId); + } + return result; + }; + + GroupLabeler groupLabeler = new LocalGroupLabeler(func); + + // Addition Events + GroupMembershipAdditionEvent groupMembershipAdditionEvent; + for (PersonId personId : peopleDataManager.getPeople()) { + List groupIdsForPersonId = groupsDataManager.getGroupsForPerson(personId); + int numGroupsForPerson = groupIdsForPersonId.size(); + if (numGroupsForPerson <= 0) + continue; + GroupId groupId = groupIdsForPersonId.get(randomGenerator.nextInt(numGroupsForPerson)); + GroupTypeId expectedGroupTypeId = groupsDataManager.getGroupType(groupId); + + groupMembershipAdditionEvent = new GroupMembershipAdditionEvent(personId, groupId); + delta = -1; + GroupTypeCountMap.Builder builder = GroupTypeCountMap.builder(); + for (GroupTypeId groupTypeId : groupsDataManager.getGroupTypeIds()) { + int count = groupsDataManager.getGroupCountForGroupTypeAndPerson(groupTypeId, personId); + if (groupTypeId.equals(expectedGroupTypeId)) { + count += delta; + } + builder.setCount(groupTypeId, count); + } + GroupTypeCountMap groupTypeCountMap = builder.build(); + Object expectedLabel = func.apply(groupTypeCountMap); + Object actualLabel = groupLabeler.getPastLabel(testPartitionsContext, groupMembershipAdditionEvent); + assertEquals(expectedLabel, actualLabel); + } + + // Removal Events + GroupMembershipRemovalEvent groupMembershipRemovalEvent; + for (PersonId personId : peopleDataManager.getPeople()) { + List groupIdsForPersonId = groupsDataManager.getGroupsForPerson(personId); + int numGroupsForPerson = groupIdsForPersonId.size(); + if (numGroupsForPerson <= 0) + continue; + GroupId groupId = groupIdsForPersonId.get(randomGenerator.nextInt(numGroupsForPerson)); + GroupTypeId expectedGroupTypeId = groupsDataManager.getGroupType(groupId); + groupsDataManager.removePersonFromGroup(personId, groupId); + + groupMembershipRemovalEvent = new GroupMembershipRemovalEvent(personId, groupId); + delta = 1; + GroupTypeCountMap.Builder builder = GroupTypeCountMap.builder(); + for (GroupTypeId groupTypeId : groupsDataManager.getGroupTypeIds()) { + int count = groupsDataManager.getGroupCountForGroupTypeAndPerson(groupTypeId, personId); + if (groupTypeId.equals(expectedGroupTypeId)) { + count += delta; + } + builder.setCount(groupTypeId, count); + } + GroupTypeCountMap groupTypeCountMap = builder.build(); + Object expectedLabel = func.apply(groupTypeCountMap); + Object actualLabel = groupLabeler.getPastLabel(testPartitionsContext, groupMembershipRemovalEvent); + assertEquals(expectedLabel, actualLabel); + } + + GroupId groupId = groupsDataManager.getGroupIds().get(0); + + // precondition: person id is null + ContractException contractException = assertThrows(ContractException.class, () -> groupLabeler + .getPastLabel(testPartitionsContext, new GroupMembershipAdditionEvent(null, groupId))); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + // precondition: person id is unknown + contractException = assertThrows(ContractException.class, + () -> groupLabeler.getPastLabel(testPartitionsContext, + new GroupMembershipAdditionEvent(new PersonId(100000), groupId))); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + }; + + Factory factory = GroupsTestPluginFactory.factory(30, 3, 5, 8478102896119863988L, consumer); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = GroupLabeler.class, name = "toString", args = {}) + public void testToString() { + LocalGroupLabeler localGroupLabeler = new LocalGroupLabeler((g) -> null); + String actualValue = localGroupLabeler.toString(); + String expectedValue = "GroupLabeler []"; + assertEquals(expectedValue, actualValue); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupMemberFilter.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupMemberFilter.java new file mode 100644 index 000000000..6fb46ab5d --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupMemberFilter.java @@ -0,0 +1,217 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.support; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.groups.datamanagers.GroupsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupMembershipAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupMembershipRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.GroupsTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.GroupsTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.TestGroupTypeId; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.FilterSensitivity; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters.Filter; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.TestPartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + +public class AT_GroupMemberFilter { + + @Test + @UnitTestConstructor(target = GroupMemberFilter.class, args = { GroupId.class }) + public void testConstructor() { + Factory factory = GroupsTestPluginFactory.factory(100, 3, 10, 8499169041100865476L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + List groupIds = groupsDataManager.getGroupIds(); + assertFalse(groupIds.isEmpty()); + for (GroupId groupId : groupIds) { + final Filter filter = new GroupMemberFilter(groupId); + assertNotNull(filter); + } + + // precondition tests + ContractException contractException = assertThrows(ContractException.class, + () -> new GroupMemberFilter(null).validate(testPartitionsContext)); + assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = GroupMemberFilter.class, name = "getFilterSensitivities", args = {}) + public void testGetFilterSensitivities() { + + Factory factory = GroupsTestPluginFactory.factory(100, 3, 10, 7283631979607042406L, (c) -> { + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + + GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); + + Filter filter = new GroupMemberFilter(groupId); + + Set> expected = new LinkedHashSet<>(); + expected.add(GroupMembershipAdditionEvent.class); + expected.add(GroupMembershipRemovalEvent.class); + + Set> filterSensitivities = filter.getFilterSensitivities(); + assertNotNull(filterSensitivities); + assertEquals(filterSensitivities.size(), 2); + + Set> actual = new LinkedHashSet<>(); + for (FilterSensitivity filterSensitivity : filterSensitivities) { + Class eventClass = filterSensitivity.getEventClass(); + actual.add(eventClass); + } + assertEquals(expected, actual); + + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = GroupMemberFilter.class, name = "evaluate", args = { PartitionsContext.class, + PersonId.class }) + public void testEvaluate() { + + Factory factory = GroupsTestPluginFactory.factory(100, 3, 10, 6248106595116941770L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_3); + Filter filter = new GroupMemberFilter(groupId); + + for (PersonId personId : peopleDataManager.getPeople()) { + if (randomGenerator.nextBoolean()) { + groupsDataManager.addPersonToGroup(personId, groupId); + } + } + + for (PersonId personId : peopleDataManager.getPeople()) { + boolean expected = groupsDataManager.isPersonInGroup(personId, groupId); + boolean actual = filter.evaluate(testPartitionsContext, personId); + assertEquals(expected, actual); + } + + /* precondition: if the person id is null */ + ContractException contractException = assertThrows(ContractException.class, + () -> filter.evaluate(testPartitionsContext, null)); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + /* precondition: if the person id is unknown */ + contractException = assertThrows(ContractException.class, + () -> filter.evaluate(testPartitionsContext, new PersonId(123412342))); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = GroupMemberFilter.class, name = "validate", args = { PartitionsContext.class }) + public void testValidate() { + + Factory factory = GroupsTestPluginFactory.factory(100, 3, 10, 8525809821136960274L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_3); + Filter filter = new GroupMemberFilter(groupId); + + // show that a properly defined filter validates and does not throw + assertDoesNotThrow(() -> filter.validate(testPartitionsContext)); + + /* precondition: if the groupId is null */ + ContractException contractException = assertThrows(ContractException.class, + () -> new GroupMemberFilter(null).validate(testPartitionsContext)); + assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); + + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = GroupMemberFilter.class, name = "getGroupId", args = {}) + public void testGetGroupId() { + for (int i = 0; i < 10; i++) { + GroupId groupId = new GroupId(i); + GroupMemberFilter groupMemberFilter = new GroupMemberFilter(groupId); + assertEquals(groupId, groupMemberFilter.getGroupId()); + } + } + + @Test + @UnitTestMethod(target = GroupMemberFilter.class, name = "hashCode", args = {}) + public void testHashCode() { + GroupMemberFilter groupMemberFilter1 = new GroupMemberFilter(new GroupId(0)); + GroupMemberFilter groupMemberFilter2 = new GroupMemberFilter(new GroupId(1)); + GroupMemberFilter groupMemberFilter3 = new GroupMemberFilter(new GroupId(0)); + + assertEquals(groupMemberFilter1.hashCode(), groupMemberFilter1.hashCode()); + + assertNotEquals(groupMemberFilter1.hashCode(), groupMemberFilter2.hashCode()); + assertNotEquals(groupMemberFilter2.hashCode(), groupMemberFilter3.hashCode()); + + assertEquals(groupMemberFilter1.hashCode(), groupMemberFilter3.hashCode()); + } + + @Test + @UnitTestMethod(target = GroupMemberFilter.class, name = "equals", args = { Object.class }) + public void testEquals() { + GroupMemberFilter groupMemberFilter1 = new GroupMemberFilter(new GroupId(0)); + GroupMemberFilter groupMemberFilter2 = new GroupMemberFilter(new GroupId(1)); + GroupMemberFilter groupMemberFilter3 = new GroupMemberFilter(new GroupId(0)); + + assertEquals(groupMemberFilter1, groupMemberFilter1); + + assertNotEquals(groupMemberFilter1, null); + + assertNotEquals(groupMemberFilter1, new Object()); + + assertNotEquals(groupMemberFilter1, groupMemberFilter2); + assertNotEquals(groupMemberFilter2, groupMemberFilter3); + + assertEquals(groupMemberFilter1, groupMemberFilter3); + } + + @Test + @UnitTestMethod(target = GroupMemberFilter.class, name = "toString", args = {}) + public void testToString() { + for (int i = 0; i < 10; i++) { + GroupId groupId = new GroupId(i); + GroupMemberFilter groupMemberFilter = new GroupMemberFilter(groupId); + + StringBuilder builder = new StringBuilder(); + builder.append("GroupMemberFilter [groupId=").append(groupId).append("]"); + + assertEquals(builder.toString(), groupMemberFilter.toString()); + } + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupPropertyDefinitionInitialization.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupPropertyDefinitionInitialization.java new file mode 100644 index 000000000..2c8093d96 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupPropertyDefinitionInitialization.java @@ -0,0 +1,275 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.math3.random.RandomGenerator; +import org.apache.commons.math3.util.Pair; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.TestGroupPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.TestGroupTypeId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_GroupPropertyDefinitionInitialization { + + @Test + @UnitTestMethod(target = GroupPropertyDefinitionInitialization.class, name = "builder", args = {}) + public void testBuilder() { + // Show that builder doesn't return null + assertNotNull(GroupPropertyDefinitionInitialization.builder()); + } + + @Test + @UnitTestMethod(target = GroupPropertyDefinitionInitialization.class, name = "getPropertyDefinition", args = {}) + public void testGetPropertyDefinition() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6987473140772497019L); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setDefaultValue(Integer.toString(randomGenerator.nextInt(100))).setType(String.class).build(); + GroupTypeId groupTypeId = TestGroupTypeId.GROUP_TYPE_1; + GroupPropertyId groupPropertyId = TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK; + + GroupPropertyDefinitionInitialization definitionInitialization = GroupPropertyDefinitionInitialization .builder().setGroupTypeId(groupTypeId).setPropertyDefinition(propertyDefinition) + .setPropertyId(groupPropertyId).build(); + + assertNotNull(definitionInitialization); + + assertEquals(propertyDefinition, definitionInitialization.getPropertyDefinition()); + } + + @Test + @UnitTestMethod(target = GroupPropertyDefinitionInitialization.class, name = "getGroupTypeId", args = {}) + public void testGetGroupTypeId() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5813541400218786441L); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setDefaultValue(Integer.toString(randomGenerator.nextInt(100))).setType(String.class).build(); + GroupTypeId groupTypeId = TestGroupTypeId.GROUP_TYPE_1; + GroupPropertyId groupPropertyId = TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK; + + GroupPropertyDefinitionInitialization definitionInitialization = GroupPropertyDefinitionInitialization .builder().setGroupTypeId(groupTypeId).setPropertyDefinition(propertyDefinition) + .setPropertyId(groupPropertyId).build(); + + assertNotNull(definitionInitialization); + + assertEquals(groupTypeId, definitionInitialization.getGroupTypeId()); + } + + @Test + @UnitTestMethod(target = GroupPropertyDefinitionInitialization.class, name = "getPropertyId", args = {}) + public void testGetPropertyId() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1367511048381780862L); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setDefaultValue(Integer.toString(randomGenerator.nextInt(100))).setType(String.class).build(); + GroupTypeId groupTypeId = TestGroupTypeId.GROUP_TYPE_1; + GroupPropertyId groupPropertyId = TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK; + + GroupPropertyDefinitionInitialization definitionInitialization = GroupPropertyDefinitionInitialization .builder().setGroupTypeId(groupTypeId).setPropertyDefinition(propertyDefinition) + .setPropertyId(groupPropertyId).build(); + + assertNotNull(definitionInitialization); + + assertEquals(groupPropertyId, definitionInitialization.getPropertyId()); + } + + @Test + @UnitTestMethod(target = GroupPropertyDefinitionInitialization.class, name = "getPropertyValues", args = {}) + public void testGetPropertyValues() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5838196849857214331L); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setDefaultValue(Integer.toString(randomGenerator.nextInt(100))).setType(String.class).build(); + + + List> expectedListOfPropertyValues = new ArrayList<>(); + + for (int i = 0; i < 10; i++) { + GroupPropertyDefinitionInitialization.Builder definitionInitializationBuilder = GroupPropertyDefinitionInitialization.builder(); + GroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + GroupPropertyId groupPropertyId = TestGroupPropertyId.values()[randomGenerator.nextInt(TestGroupPropertyId.values().length)]; + + definitionInitializationBuilder.setGroupTypeId(groupTypeId).setPropertyDefinition(propertyDefinition).setPropertyId(groupPropertyId); + + GroupId groupId = new GroupId(10000 + i); + for (int j = 0; j < 3; j++) { + String value = Integer.toString(randomGenerator.nextInt(100)); + Pair propertyValue = new Pair<>(groupId, value); + expectedListOfPropertyValues.add(propertyValue); + definitionInitializationBuilder.addPropertyValue(groupId, value); + } + + GroupPropertyDefinitionInitialization definitionInitialization = definitionInitializationBuilder.build(); + assertNotNull(definitionInitialization); + + assertEquals(expectedListOfPropertyValues, definitionInitialization.getPropertyValues()); + expectedListOfPropertyValues.clear(); + } + + } + + @Test + @UnitTestMethod(target = GroupPropertyDefinitionInitialization.Builder.class, name = "build", args = {}) + public void testBuild() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6019590927036411078L); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setDefaultValue(Integer.toString(randomGenerator.nextInt(100))).setType(String.class).build(); + + GroupTypeId groupTypeId = TestGroupTypeId.GROUP_TYPE_1; + GroupPropertyId groupPropertyId = TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK; + GroupId groupId = new GroupId(0); + + + + GroupPropertyDefinitionInitialization definitionInitialization = GroupPropertyDefinitionInitialization .builder().setGroupTypeId(groupTypeId).setPropertyDefinition(propertyDefinition) + .setPropertyId(groupPropertyId).build(); + + assertNotNull(definitionInitialization); + + // precondition: null property definition + ContractException contractException = assertThrows(ContractException.class, () -> GroupPropertyDefinitionInitialization.builder().setGroupTypeId(groupTypeId).setPropertyId(groupPropertyId).build()); + assertEquals(PropertyError.NULL_PROPERTY_DEFINITION, contractException.getErrorType()); + + // precondition: incompatible property definition value + contractException = assertThrows(ContractException.class, + () -> GroupPropertyDefinitionInitialization.builder().setPropertyDefinition(propertyDefinition).setPropertyId(groupPropertyId).setGroupTypeId(groupTypeId).addPropertyValue(groupId, 1).build()); + assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); + + // precondition: null groupTypeId + contractException = assertThrows(ContractException.class, () -> GroupPropertyDefinitionInitialization.builder().setPropertyDefinition(propertyDefinition).setPropertyId(groupPropertyId).build()); + assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); + + // precondition: null propertyId + contractException = assertThrows(ContractException.class, () -> GroupPropertyDefinitionInitialization.builder().setPropertyDefinition(propertyDefinition).setGroupTypeId(groupTypeId).build()); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupPropertyDefinitionInitialization.Builder.class, name = "setPropertyDefinition", args = { PropertyDefinition.class }) + public void testSetPropertyDefinition() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(853904864534105353L); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setDefaultValue(Integer.toString(randomGenerator.nextInt(100))).setType(String.class).build(); + + GroupTypeId groupTypeId = TestGroupTypeId.GROUP_TYPE_1; + GroupPropertyId groupPropertyId = TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK; + + GroupPropertyDefinitionInitialization.Builder builder = GroupPropertyDefinitionInitialization.builder(); + + GroupPropertyDefinitionInitialization definitionInitialization = GroupPropertyDefinitionInitialization .builder().setGroupTypeId(groupTypeId).setPropertyDefinition(propertyDefinition) + .setPropertyId(groupPropertyId).build(); + + assertNotNull(definitionInitialization); + + assertEquals(propertyDefinition, definitionInitialization.getPropertyDefinition()); + + // precondition: null property definition + ContractException contractException = assertThrows(ContractException.class, () -> builder.setPropertyDefinition(null).setGroupTypeId(groupTypeId).setPropertyId(groupPropertyId).build()); + assertEquals(PropertyError.NULL_PROPERTY_DEFINITION, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupPropertyDefinitionInitialization.Builder.class, name = "setGroupTypeId", args = { GroupTypeId.class }) + public void testSetGroupTypeId() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1959708886343213469L); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setDefaultValue(Integer.toString(randomGenerator.nextInt(100))).setType(String.class).build(); + + GroupTypeId groupTypeId = TestGroupTypeId.GROUP_TYPE_1; + GroupPropertyId groupPropertyId = TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK; + + GroupPropertyDefinitionInitialization.Builder builder = GroupPropertyDefinitionInitialization.builder(); + + GroupPropertyDefinitionInitialization definitionInitialization = GroupPropertyDefinitionInitialization .builder().setGroupTypeId(groupTypeId).setPropertyDefinition(propertyDefinition) + .setPropertyId(groupPropertyId).build(); + + assertNotNull(definitionInitialization); + + assertEquals(groupTypeId, definitionInitialization.getGroupTypeId()); + + // precondition: null group type id + ContractException contractException = assertThrows(ContractException.class, + () -> builder.setPropertyDefinition(propertyDefinition).setGroupTypeId(null).setPropertyId(groupPropertyId).build()); + assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupPropertyDefinitionInitialization.Builder.class, name = "setPropertyId", args = { GroupPropertyId.class }) + public void testSetPropertyId() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7319624484285657037L); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setDefaultValue(Integer.toString(randomGenerator.nextInt(100))).setType(String.class).build(); + + GroupTypeId groupTypeId = TestGroupTypeId.GROUP_TYPE_1; + GroupPropertyId groupPropertyId = TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK; + + GroupPropertyDefinitionInitialization.Builder builder = GroupPropertyDefinitionInitialization.builder(); + + GroupPropertyDefinitionInitialization definitionInitialization = GroupPropertyDefinitionInitialization .builder().setGroupTypeId(groupTypeId).setPropertyDefinition(propertyDefinition) + .setPropertyId(groupPropertyId).build(); + + assertNotNull(definitionInitialization); + + assertEquals(groupPropertyId, definitionInitialization.getPropertyId()); + + // precondition: null property id + ContractException contractException = assertThrows(ContractException.class, () -> builder.setPropertyDefinition(propertyDefinition).setGroupTypeId(groupTypeId).setPropertyId(null).build()); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupPropertyDefinitionInitialization.Builder.class, name = "addPropertyValue", args = { GroupId.class, Object.class }) + public void testAddPropertyValue() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2210384535662631024L); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setDefaultValue(Integer.toString(randomGenerator.nextInt(100))).setType(String.class).build(); + + List> expectedListOfPropertyValues = new ArrayList<>(); + + for (int i = 0; i < 10; i++) { + GroupPropertyDefinitionInitialization.Builder definitionInitializationBuilder = GroupPropertyDefinitionInitialization.builder(); + GroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + GroupPropertyId groupPropertyId = TestGroupPropertyId.values()[randomGenerator.nextInt(TestGroupPropertyId.values().length)]; + + definitionInitializationBuilder.setGroupTypeId(groupTypeId).setPropertyDefinition(propertyDefinition).setPropertyId(groupPropertyId); + + GroupId groupId = new GroupId(10000 + i); + for (int j = 0; j < 3; j++) { + String value = Integer.toString(randomGenerator.nextInt(100)); + Pair propertyValue = new Pair<>(groupId, value); + expectedListOfPropertyValues.add(propertyValue); + definitionInitializationBuilder.addPropertyValue(groupId, value); + } + + GroupPropertyDefinitionInitialization definitionInitialization = definitionInitializationBuilder.build(); + assertNotNull(definitionInitialization); + + assertNotNull(definitionInitialization.getPropertyValues()); + assertFalse(definitionInitialization.getPropertyValues().isEmpty()); + assertEquals(expectedListOfPropertyValues, definitionInitialization.getPropertyValues()); + + List> actualListOfPropertyValues = definitionInitialization.getPropertyValues(); + for (int k = 0; k < actualListOfPropertyValues.size(); k++) { + assertEquals(expectedListOfPropertyValues.get(k), actualListOfPropertyValues.get(k)); + assertEquals(String.class, actualListOfPropertyValues.get(k).getSecond().getClass()); + assertEquals(actualListOfPropertyValues.get(k).getSecond(), actualListOfPropertyValues.get(k).getSecond()); + assertEquals(groupId, actualListOfPropertyValues.get(k).getFirst()); + } + expectedListOfPropertyValues.clear(); + } + + GroupTypeId groupTypeId = TestGroupTypeId.GROUP_TYPE_1; + GroupPropertyId groupPropertyId = TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK; + + // precondition: null group id + + + ContractException contractException = assertThrows(ContractException.class, + () -> GroupPropertyDefinitionInitialization.builder() .setPropertyDefinition(propertyDefinition).setGroupTypeId(groupTypeId).setPropertyId(groupPropertyId) + .addPropertyValue(null, Integer.toString(randomGenerator.nextInt(100))).build()); + assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); + + // precondition: null property value + contractException = assertThrows(ContractException.class, () -> GroupPropertyDefinitionInitialization.builder() .setPropertyDefinition(propertyDefinition).setGroupTypeId(groupTypeId) + .setPropertyId(groupPropertyId).addPropertyValue(new GroupId(15000), null).build()); + assertEquals(PropertyError.NULL_PROPERTY_VALUE, contractException.getErrorType()); + + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupPropertyDimension.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupPropertyDimension.java new file mode 100644 index 000000000..072c57d0a --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupPropertyDimension.java @@ -0,0 +1,402 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.DimensionContext; +import gov.hhs.aspr.ms.gcm.plugins.groups.datamanagers.GroupsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.TestGroupPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.TestGroupTypeId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_GroupPropertyDimension { + + @Test + @UnitTestMethod(target = GroupPropertyDimension.Builder.class, name = "addValue", args = { Object.class }) + public void testAddValue() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3468803942988565031L); + + for (int i = 0; i < 50; i++) { + List expectedValues = new ArrayList<>(); + + GroupPropertyDimension.Builder builder = GroupPropertyDimension.builder()// + .setGroupId(new GroupId(i)) + .setGroupPropertyId(TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK); + int n = randomGenerator.nextInt(10); + for (int j = 0; j < n; j++) { + double value = randomGenerator.nextDouble(); + expectedValues.add(value); + builder.addValue(value); + } + GroupPropertyDimension groupPropertyDimension = builder.build(); + + List actualValues = groupPropertyDimension.getValues(); + assertEquals(expectedValues, actualValues); + } + + // precondition test : if the value is null + ContractException contractException = assertThrows(ContractException.class, + () -> GroupPropertyDimension.builder().addValue(null)); + assertEquals(PropertyError.NULL_PROPERTY_VALUE, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupPropertyDimension.Builder.class, name = "build", args = {}) + public void testBuild() { + GroupPropertyDimension groupPropertyDimension = // + GroupPropertyDimension.builder()// + .setGroupId(new GroupId(0)) + .setGroupPropertyId(TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK).build(); + assertNotNull(groupPropertyDimension); + + // precondition test : if the global property id is not assigned + ContractException contractException = assertThrows(ContractException.class, + () -> gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupPropertyDimension.builder().setGroupId(new GroupId(0)).build()); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // if the groupId was not assigned + contractException = assertThrows(ContractException.class, () -> gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupPropertyDimension + .builder().setGroupPropertyId(TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK).build()); + assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupPropertyDimension.Builder.class, name = "setGroupId", args = { GroupId.class }) + public void testSetGroupId() { + for (int i = 0; i < 10; i++) { + GroupId groupId = new GroupId(i); + + GroupPropertyDimension.Builder builder = GroupPropertyDimension.builder()// + .setGroupPropertyId(TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK) + .setGroupId(groupId); + + GroupPropertyDimension groupPropertyDimension = builder.build(); + assertEquals(groupId, groupPropertyDimension.getGroupId()); + } + + // precondition test : if the value is null + ContractException contractException = assertThrows(ContractException.class, + () -> GroupPropertyDimension.builder().setGroupId(null)); + assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupPropertyDimension.Builder.class, name = "setGroupPropertyId", args = { + GroupPropertyId.class }) + public void testSetGroupPropertyId() { + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { + + GroupPropertyDimension.Builder builder = GroupPropertyDimension.builder()// + .setGroupId(new GroupId(0)).setGroupPropertyId(testGroupPropertyId); + + GroupPropertyDimension groupPropertyDimension = builder.build(); + assertEquals(testGroupPropertyId, groupPropertyDimension.getGroupPropertyId()); + } + + // precondition test : if the value is null + ContractException contractException = assertThrows(ContractException.class, + () -> GroupPropertyDimension.builder().setGroupPropertyId(null)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupPropertyDimension.class, name = "builder", args = {}) + public void testBuilder() { + assertNotNull(GroupPropertyDimension.builder()); + } + + @Test + @UnitTestMethod(target = GroupPropertyDimension.class, name = "executeLevel", args = { DimensionContext.class, + int.class }) + public void testExecuteLevel() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2924521933883974690L); + + // run several test cases + for (int i = 0; i < 30; i++) { + + GroupId groupId = new GroupId(0); + // select a random number of levels + int levelCount = randomGenerator.nextInt(10); + // select a random property id + TestGroupPropertyId targetPropertyId = TestGroupPropertyId.getRandomTestGroupPropertyId(randomGenerator); + + // generate random values for the level + List expectedValues = new ArrayList<>(); + for (int j = 0; j < levelCount; j++) { + expectedValues.add(targetPropertyId.getRandomPropertyValue(randomGenerator)); + } + + // create a GroupPropertyDimension with the level values + GroupPropertyDimension.Builder dimBuilder = GroupPropertyDimension.builder()// + .setGroupId(groupId).setGroupPropertyId(targetPropertyId); + + for (Object value : expectedValues) { + dimBuilder.addValue(value); + } + + GroupPropertyDimension groupPropertyDimension = dimBuilder.build(); + + // show that for each level the dimension properly assigns the value + // to a global property data builder + for (int level = 0; level < levelCount; level++) { + /* + * Create a GroupsPluginData, filling it with the test property definitions and + * any values that are required + */ + GroupsPluginData.Builder pluginDataBuilder = GroupsPluginData.builder(); + for (TestGroupPropertyId propertyId : TestGroupPropertyId.values()) { + PropertyDefinition propertyDefinition = propertyId.getPropertyDefinition(); + pluginDataBuilder.defineGroupProperty(TestGroupTypeId.GROUP_TYPE_1, propertyId, propertyDefinition); + + if (propertyDefinition.getDefaultValue().isEmpty()) { + pluginDataBuilder.setGroupPropertyValue(groupId, propertyId, + propertyId.getRandomPropertyValue(randomGenerator)); + } + } + + pluginDataBuilder.addGroupTypeId(TestGroupTypeId.GROUP_TYPE_1).addGroup(groupId, + TestGroupTypeId.GROUP_TYPE_1); + // Create a dimension context that contain the plugin data + // builder + DimensionContext.Builder dimensionContextBuilder = DimensionContext.builder(); + + pluginDataBuilder = (GroupsPluginData.Builder) dimensionContextBuilder.add(pluginDataBuilder.build()); + DimensionContext dimensionContext = dimensionContextBuilder.build(); + + // execute the dimension with the level + groupPropertyDimension.executeLevel(dimensionContext, level); + + /* + * get the GroupsPluginData from the corresponding builder + */ + GroupsPluginData groupsPluginData = pluginDataBuilder.build(); + + /* + * show that the GroupsPluginData has the value we expect for the given level + */ + + List groupPropertyValues = groupsPluginData.getGroupPropertyValues(groupId); + + boolean found = false; + for (GroupPropertyValue groupPropertyValue : groupPropertyValues) { + if (groupPropertyValue.groupPropertyId().equals(targetPropertyId)) { + Object actualValue = groupPropertyValue.value(); + Object expectedValue = expectedValues.get(level); + assertEquals(expectedValue, actualValue); + found = true; + break; + } + } + + assertTrue(found); + } + } + } + + @Test + @UnitTestMethod(target = GroupPropertyDimension.class, name = "getExperimentMetaData", args = {}) + public void testGetExperimentMetaData() { + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { + + List expectedExperimentMetaData = new ArrayList<>(); + expectedExperimentMetaData.add(testGroupPropertyId.toString()); + + GroupPropertyDimension.Builder builder = GroupPropertyDimension.builder()// + .setGroupId(new GroupId(0)).setGroupPropertyId(testGroupPropertyId); + + GroupPropertyDimension groupPropertyDimension = builder.build(); + assertEquals(expectedExperimentMetaData, groupPropertyDimension.getExperimentMetaData()); + } + } + + @Test + @UnitTestMethod(target = GroupPropertyDimension.class, name = "getGroupId", args = {}) + public void testGetGroupId() { + for (int i = 0; i < 10; i++) { + GroupId groupId = new GroupId(i); + GroupPropertyDimension.Builder builder = GroupPropertyDimension.builder()// + .setGroupId(groupId) + .setGroupPropertyId(TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK); + + GroupPropertyDimension groupPropertyDimension = builder.build(); + assertEquals(groupId, groupPropertyDimension.getGroupId()); + } + } + + @Test + @UnitTestMethod(target = GroupPropertyDimension.class, name = "getGroupPropertyId", args = {}) + public void testGetGroupPropertyId() { + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { + + GroupPropertyDimension.Builder builder = GroupPropertyDimension.builder()// + .setGroupId(new GroupId(0)).setGroupPropertyId(testGroupPropertyId); + + GroupPropertyDimension groupPropertyDimension = builder.build(); + assertEquals(testGroupPropertyId, groupPropertyDimension.getGroupPropertyId()); + } + } + + @Test + @UnitTestMethod(target = GroupPropertyDimension.class, name = "getValues", args = {}) + public void testGetValues() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4581428044056639458L); + + for (int i = 0; i < 50; i++) { + List expectedValues = new ArrayList<>(); + + GroupPropertyDimension.Builder builder = GroupPropertyDimension.builder()// + .setGroupId(new GroupId(0)) + .setGroupPropertyId(TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK); + int n = randomGenerator.nextInt(10); + for (int j = 0; j < n; j++) { + double value = randomGenerator.nextDouble(); + expectedValues.add(value); + builder.addValue(value); + } + GroupPropertyDimension groupPropertyDimension = builder.build(); + + List actualValues = groupPropertyDimension.getValues(); + assertEquals(expectedValues, actualValues); + } + } + + @Test + @UnitTestMethod(target = GroupPropertyDimension.class, name = "levelCount", args = {}) + public void testLevelCount() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8913942504408118065L); + + for (int i = 0; i < 50; i++) { + + GroupPropertyDimension.Builder builder = GroupPropertyDimension.builder()// + .setGroupId(new GroupId(0)) + .setGroupPropertyId(TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK); + int n = randomGenerator.nextInt(10); + for (int j = 0; j < n; j++) { + double value = randomGenerator.nextDouble(); + builder.addValue(value); + } + GroupPropertyDimension groupPropertyDimension = builder.build(); + + assertEquals(n, groupPropertyDimension.levelCount()); + } + } + + @Test + @UnitTestMethod(target = GroupPropertyDimension.class, name = "hashCode", args = {}) + public void testHashCode() { + GroupPropertyDimension dimension1 = GroupPropertyDimension.builder() + .setGroupPropertyId(TestGroupPropertyId.GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK) + .setGroupId(new GroupId(0)).build(); + + GroupPropertyDimension dimension2 = GroupPropertyDimension.builder() + .setGroupPropertyId(TestGroupPropertyId.GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK) + .setGroupId(new GroupId(1)).build(); + + GroupPropertyDimension dimension3 = GroupPropertyDimension.builder() + .setGroupPropertyId(TestGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK) + .setGroupId(new GroupId(0)).build(); + + GroupPropertyDimension dimension4 = GroupPropertyDimension.builder() + .setGroupPropertyId(TestGroupPropertyId.GROUP_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK) + .setGroupId(new GroupId(1)).build(); + + GroupPropertyDimension dimension5 = GroupPropertyDimension.builder() + .setGroupPropertyId(TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK) + .setGroupId(new GroupId(1)).build(); + + GroupPropertyDimension dimension6 = GroupPropertyDimension.builder() + .setGroupPropertyId(TestGroupPropertyId.GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK) + .setGroupId(new GroupId(0)).build(); + + assertEquals(dimension1.hashCode(), dimension1.hashCode()); + + assertNotEquals(dimension1.hashCode(), dimension2.hashCode()); + assertNotEquals(dimension1.hashCode(), dimension3.hashCode()); + assertNotEquals(dimension1.hashCode(), dimension4.hashCode()); + assertNotEquals(dimension1.hashCode(), dimension5.hashCode()); + + assertNotEquals(dimension2.hashCode(), dimension3.hashCode()); + assertNotEquals(dimension2.hashCode(), dimension4.hashCode()); + assertNotEquals(dimension2.hashCode(), dimension5.hashCode()); + assertNotEquals(dimension2.hashCode(), dimension6.hashCode()); + + assertNotEquals(dimension3.hashCode(), dimension4.hashCode()); + assertNotEquals(dimension3.hashCode(), dimension5.hashCode()); + assertNotEquals(dimension3.hashCode(), dimension6.hashCode()); + + assertNotEquals(dimension4.hashCode(), dimension5.hashCode()); + assertNotEquals(dimension4.hashCode(), dimension6.hashCode()); + + assertNotEquals(dimension5.hashCode(), dimension6.hashCode()); + + assertEquals(dimension1.hashCode(), dimension6.hashCode()); + } + + @Test + @UnitTestMethod(target = GroupPropertyDimension.class, name = "equals", args = { Object.class }) + public void testEquals() { + GroupPropertyDimension dimension1 = GroupPropertyDimension.builder() + .setGroupPropertyId(TestGroupPropertyId.GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK) + .setGroupId(new GroupId(0)).build(); + + GroupPropertyDimension dimension2 = GroupPropertyDimension.builder() + .setGroupPropertyId(TestGroupPropertyId.GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK) + .setGroupId(new GroupId(1)).build(); + + GroupPropertyDimension dimension3 = GroupPropertyDimension.builder() + .setGroupPropertyId(TestGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK) + .setGroupId(new GroupId(0)).build(); + + GroupPropertyDimension dimension4 = GroupPropertyDimension.builder() + .setGroupPropertyId(TestGroupPropertyId.GROUP_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK) + .setGroupId(new GroupId(1)).build(); + + GroupPropertyDimension dimension5 = GroupPropertyDimension.builder() + .setGroupPropertyId(TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK) + .setGroupId(new GroupId(1)).build(); + + GroupPropertyDimension dimension6 = GroupPropertyDimension.builder() + .setGroupPropertyId(TestGroupPropertyId.GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK) + .setGroupId(new GroupId(0)).build(); + + assertEquals(dimension1, dimension1); + + assertNotEquals(dimension1, null); + assertNotEquals(dimension1, new Object()); + + assertNotEquals(dimension1, dimension2); + assertNotEquals(dimension1, dimension3); + assertNotEquals(dimension1, dimension4); + assertNotEquals(dimension1, dimension5); + + assertNotEquals(dimension2, dimension3); + assertNotEquals(dimension2, dimension4); + assertNotEquals(dimension2, dimension5); + assertNotEquals(dimension2, dimension6); + + assertNotEquals(dimension3, dimension4); + assertNotEquals(dimension3, dimension5); + assertNotEquals(dimension3, dimension6); + + assertNotEquals(dimension4, dimension5); + assertNotEquals(dimension4, dimension6); + + assertNotEquals(dimension5, dimension6); + + assertEquals(dimension1, dimension6); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupPropertyValue.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupPropertyValue.java new file mode 100644 index 000000000..95c2538ed --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupPropertyValue.java @@ -0,0 +1,68 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.TestGroupPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_GroupPropertyValue { + + @Test + @UnitTestConstructor(target = GroupPropertyValue.class, args = { GroupPropertyId.class, Object.class }) + public void testConstructor() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2797741161017158600L); + GroupPropertyId groupPropertyId = TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK; + String value = Integer.toString(randomGenerator.nextInt(100)); + + assertNotNull(new GroupPropertyValue(groupPropertyId, value)); + + // precondition: null group property id + ContractException contractException = assertThrows(ContractException.class, () -> new GroupPropertyValue(null, value)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // precondition: null value + contractException = assertThrows(ContractException.class, () -> new GroupPropertyValue(groupPropertyId, null)); + assertEquals(PropertyError.NULL_PROPERTY_VALUE, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = GroupPropertyValue.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupPropertyValue.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupPropertyValue.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupPropertyValue.class, name = "groupPropertyId", args = {}) + public void testGroupPropertyId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupPropertyValue.class, name = "value", args = {}) + public void testValue() { + // nothing to test + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupSampler.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupSampler.java new file mode 100644 index 000000000..3ee8d7395 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupSampler.java @@ -0,0 +1,149 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.RandomNumberGeneratorId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.testsupport.TestRandomGeneratorId; +import util.annotations.UnitTestMethod; + +public class AT_GroupSampler { + + @Test + @UnitTestMethod(target = GroupSampler.Builder.class, name = "build", args = {}) + public void testBuild() { + // Test covered by other tests in this class + GroupSampler groupSampler = GroupSampler.builder().build(); + assertNotNull(groupSampler); + } + + @Test + @UnitTestMethod(target = GroupSampler.class, name = "builder", args = {}) + public void testBuilder() { + + GroupSampler groupSampler = GroupSampler.builder().build(); + + assertNotNull(groupSampler); + + assertNotNull(groupSampler.getExcludedPerson()); + assertFalse(groupSampler.getExcludedPerson().isPresent()); + + assertNotNull(groupSampler.getWeightingFunction()); + assertFalse(groupSampler.getWeightingFunction().isPresent()); + + assertNotNull(groupSampler.getRandomNumberGeneratorId()); + assertFalse(groupSampler.getRandomNumberGeneratorId().isPresent()); + + } + + @Test + @UnitTestMethod(target = GroupSampler.class, name = "getExcludedPerson", args = {}) + public void testGetExcludedPerson() { + GroupSampler groupSampler = GroupSampler.builder().setExcludedPersonId(new PersonId(67)).build(); + assertNotNull(groupSampler); + assertNotNull(groupSampler.getExcludedPerson()); + assertTrue(groupSampler.getExcludedPerson().isPresent()); + assertEquals(67, groupSampler.getExcludedPerson().get().getValue()); + } + + @Test + @UnitTestMethod(target = GroupSampler.class, name = "getRandomNumberGeneratorId", args = {}) + public void testGetRandomNumberGeneratorId() { + GroupSampler groupSampler = GroupSampler.builder().setRandomNumberGeneratorId(TestRandomGeneratorId.DASHER).setRandomNumberGeneratorId(TestRandomGeneratorId.VIXEN).build(); + + assertNotNull(groupSampler); + assertNotNull(groupSampler.getRandomNumberGeneratorId()); + assertTrue(groupSampler.getRandomNumberGeneratorId().isPresent()); + assertEquals(TestRandomGeneratorId.VIXEN, groupSampler.getRandomNumberGeneratorId().get()); + } + + @Test + @UnitTestMethod(target = GroupSampler.class, name = "getWeightingFunction", args = {}) + public void testGetLabelSetWeightingFunction() { + + double expectedValue = 17.5; + GroupSampler groupSampler = GroupSampler.builder().setGroupWeightingFunction((context, personId, groupId) -> expectedValue).build(); + + assertNotNull(groupSampler); + assertNotNull(groupSampler.getWeightingFunction()); + assertTrue(groupSampler.getWeightingFunction().isPresent()); + GroupWeightingFunction groupWeightingFunction = groupSampler.getWeightingFunction().get(); + assertNotNull(groupWeightingFunction); + + assertEquals(expectedValue, groupWeightingFunction.getWeight(null, null, null), 0); + } + + @Test + @UnitTestMethod(target = GroupSampler.Builder.class, name = "setRandomNumberGeneratorId", args = { RandomNumberGeneratorId.class }) + public void testSetRandomNumberGeneratorId() { + GroupSampler groupSampler = GroupSampler.builder().build(); + + assertNotNull(groupSampler); + // Show that when not set, the RandomNumberGeneratorId is not present. + assertNotNull(groupSampler.getRandomNumberGeneratorId()); + assertFalse(groupSampler.getRandomNumberGeneratorId().isPresent()); + + // Show that when set, the RandomNumberGeneratorId is Present and set to + // what we + // set it to + RandomNumberGeneratorId expectedValue = TestRandomGeneratorId.DASHER; + groupSampler = GroupSampler.builder().setRandomNumberGeneratorId(TestRandomGeneratorId.DASHER).build(); + assertNotNull(groupSampler.getRandomNumberGeneratorId()); + assertTrue(groupSampler.getRandomNumberGeneratorId().isPresent()); + assertEquals(expectedValue, groupSampler.getRandomNumberGeneratorId().get()); + } + + @Test + @UnitTestMethod(target = GroupSampler.Builder.class, name = "setExcludedPersonId", args = { PersonId.class }) + public void testSetExcludedPersonId() { + GroupSampler groupSampler = GroupSampler.builder().build(); + + assertNotNull(groupSampler); + // Show that when not set, the ExcludedPerson is not present. + assertNotNull(groupSampler.getExcludedPerson()); + assertFalse(groupSampler.getExcludedPerson().isPresent()); + + // Show that when set, the ExcludedPerson is Present and set to what we + // set it + // to + PersonId expectedValue = new PersonId(68); + groupSampler = GroupSampler.builder().setExcludedPersonId(expectedValue).build(); + assertNotNull(groupSampler.getExcludedPerson()); + assertTrue(groupSampler.getExcludedPerson().isPresent()); + assertEquals(expectedValue, groupSampler.getExcludedPerson().get()); + } + + @Test + @UnitTestMethod(target = GroupSampler.Builder.class, name = "setGroupWeightingFunction", args = { GroupWeightingFunction.class }) + public void testSetGroupWeightingFunction() { + + GroupSampler groupSampler = GroupSampler.builder().build(); + + assertNotNull(groupSampler); + + // Show that when not set, the weighting function is not present + assertNotNull(groupSampler.getWeightingFunction()); + assertFalse(groupSampler.getWeightingFunction().isPresent()); + + double expectedValue = 12.8; + groupSampler = GroupSampler.builder().setGroupWeightingFunction((context, personId, groupId) -> expectedValue).build(); + + assertNotNull(groupSampler); + + // Show that when set, the weighting function is present and is set to + // the + // expected value + assertNotNull(groupSampler.getWeightingFunction()); + assertTrue(groupSampler.getWeightingFunction().isPresent()); + GroupWeightingFunction groupWeightingFunction = groupSampler.getWeightingFunction().get(); + assertNotNull(groupWeightingFunction); + + assertEquals(expectedValue, groupWeightingFunction.getWeight(null, null, null), 0); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupTypeCountMap.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupTypeCountMap.java new file mode 100644 index 000000000..fd23ef129 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupTypeCountMap.java @@ -0,0 +1,270 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.TestGroupTypeId; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +/** + * Test class for {@link GroupTypeCountMap} + * + * + */ +public class AT_GroupTypeCountMap { + + /** + * Tests {@linkplain GroupTypeCountMap#equals(Object) + */ + @Test + @UnitTestMethod(target = GroupTypeCountMap.class, name = "equals", args = { Object.class }) + public void testEquals() { + // 4832988525233013426L + /* + * Show various cases demonstrating that build order and implied zero + * values do not influence the equals contract + */ + + // order should not matter + GroupTypeCountMap.Builder builder = GroupTypeCountMap.builder(); + builder.setCount(TestGroupTypeId.GROUP_TYPE_1, 5); + builder.setCount(TestGroupTypeId.GROUP_TYPE_2, 7); + GroupTypeCountMap groupTypeCountMap1 = builder.build(); + + builder.setCount(TestGroupTypeId.GROUP_TYPE_2, 7); + builder.setCount(TestGroupTypeId.GROUP_TYPE_1, 5); + GroupTypeCountMap groupTypeCountMap2 = builder.build(); + + assertEquals(groupTypeCountMap1, groupTypeCountMap2); + + // implied zero values should not matter + builder.setCount(TestGroupTypeId.GROUP_TYPE_3, 0); + builder.setCount(TestGroupTypeId.GROUP_TYPE_2, 7); + builder.setCount(TestGroupTypeId.GROUP_TYPE_1, 5); + groupTypeCountMap1 = builder.build(); + + builder.setCount(TestGroupTypeId.GROUP_TYPE_2, 7); + builder.setCount(TestGroupTypeId.GROUP_TYPE_1, 5); + groupTypeCountMap2 = builder.build(); + + assertEquals(groupTypeCountMap1, groupTypeCountMap2); + + // differences in positive counts matter + + builder.setCount(TestGroupTypeId.GROUP_TYPE_1, 5); + builder.setCount(TestGroupTypeId.GROUP_TYPE_2, 7); + groupTypeCountMap1 = builder.build(); + + builder.setCount(TestGroupTypeId.GROUP_TYPE_1, 5); + builder.setCount(TestGroupTypeId.GROUP_TYPE_2, 3); + groupTypeCountMap2 = builder.build(); + + assertNotEquals(groupTypeCountMap1, groupTypeCountMap2); + } + + /** + * Tests {@linkplain GroupTypeCountMap#hashCode() + */ + @Test + @UnitTestMethod(target = GroupTypeCountMap.class, name = "hashCode", args = {}) + public void testHashCode() { + /* + * Equal objects have equal hash codes + */ + + // order should not matter + GroupTypeCountMap.Builder builder = GroupTypeCountMap.builder(); + builder.setCount(TestGroupTypeId.GROUP_TYPE_1, 5); + builder.setCount(TestGroupTypeId.GROUP_TYPE_2, 7); + GroupTypeCountMap groupTypeCountMap1 = builder.build(); + + builder.setCount(TestGroupTypeId.GROUP_TYPE_2, 7); + builder.setCount(TestGroupTypeId.GROUP_TYPE_1, 5); + GroupTypeCountMap groupTypeCountMap2 = builder.build(); + + assertEquals(groupTypeCountMap1.hashCode(), groupTypeCountMap2.hashCode()); + + // implied zero values should not matter + builder.setCount(TestGroupTypeId.GROUP_TYPE_3, 0); + builder.setCount(TestGroupTypeId.GROUP_TYPE_2, 7); + builder.setCount(TestGroupTypeId.GROUP_TYPE_1, 5); + groupTypeCountMap1 = builder.build(); + + builder.setCount(TestGroupTypeId.GROUP_TYPE_2, 7); + builder.setCount(TestGroupTypeId.GROUP_TYPE_1, 5); + groupTypeCountMap2 = builder.build(); + + assertEquals(groupTypeCountMap1.hashCode(), groupTypeCountMap2.hashCode()); + + } + + /** + * Tests {@linkplain GroupTypeCountMap#getGroupCount(GroupTypeId) + */ + @Test + @UnitTestMethod(target = GroupTypeCountMap.class, name = "getGroupCount", args = { GroupTypeId.class }) + public void testGetGroupCount() { + // covered by testBuilder() test method + } + + /** + * Tests {@linkplain GroupTypeCountMap#toString() + */ + @Test + @UnitTestMethod(target = GroupTypeCountMap.class, name = "toString", args = {}) + public void testToString() { + GroupTypeCountMap.Builder builder = GroupTypeCountMap.builder(); + + int count = 1; + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + builder.setCount(testGroupTypeId, count++); + } + GroupTypeCountMap groupTypeCountMap = builder.build(); + + String expectedValue = "GroupTypeCountMap [GROUP_TYPE_1=1, GROUP_TYPE_2=2, GROUP_TYPE_3=3]"; + String actualValue = groupTypeCountMap.toString(); + + assertEquals(expectedValue, actualValue); + } + + /** + * Tests {@linkplain GroupTypeCountMap#builder() + */ + @Test + @UnitTestMethod(target = GroupTypeCountMap.class, name = "builder", args = {}) + public void testBuilder() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1353590720789078598L); + + for (int i = 0; i < 20; i++) { + Map expectedValues = new LinkedHashMap<>(); + GroupTypeCountMap.Builder builder = GroupTypeCountMap.builder(); + + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + expectedValues.put(testGroupTypeId, 0); + if (randomGenerator.nextBoolean()) { + int count = randomGenerator.nextInt(3); + builder.setCount(testGroupTypeId, count); + expectedValues.put(testGroupTypeId, count); + } + } + GroupTypeCountMap groupTypeCountMap = builder.build(); + + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + int expectedValue = expectedValues.get(testGroupTypeId); + int actualValue = groupTypeCountMap.getGroupCount(testGroupTypeId); + assertEquals(expectedValue, actualValue); + } + } + + // precondition checks + ContractException contractException = assertThrows(ContractException.class, () -> GroupTypeCountMap.builder().setCount(null, 10)); + assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, () -> GroupTypeCountMap.builder().setCount(TestGroupTypeId.GROUP_TYPE_1, -1)); + assertEquals(GroupError.NEGATIVE_GROUP_COUNT, contractException.getErrorType()); + + } + + // public java.util.Set gcm.simulation.GroupTypeCountMap.getGroupTypeIds() + /** + * Tests {@linkplain GroupTypeCountMap#getGroupTypeIds() + */ + @Test + @UnitTestMethod(target = GroupTypeCountMap.class, name = "getGroupTypeIds", args = {}) + public void testGetGroupTypeIds() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1310699113269703296L); + + for (int i = 0; i < 20; i++) { + Set expectedGroupTypeIds = new LinkedHashSet<>(); + GroupTypeCountMap.Builder builder = GroupTypeCountMap.builder(); + + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + + if (randomGenerator.nextBoolean()) { + expectedGroupTypeIds.add(testGroupTypeId); + builder.setCount(testGroupTypeId, 1); + } + } + GroupTypeCountMap groupTypeCountMap = builder.build(); + + Set actualGroupTypeIds = groupTypeCountMap.getGroupTypeIds(); + assertEquals(expectedGroupTypeIds, actualGroupTypeIds); + } + + } + + @Test + @UnitTestMethod(target = GroupTypeCountMap.Builder.class, name = "build", args = {}) + public void testBuild() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1446391997583651047L); + + for (int i = 0; i < 20; i++) { + Map expectedValues = new LinkedHashMap<>(); + GroupTypeCountMap.Builder builder = GroupTypeCountMap.builder(); + + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + expectedValues.put(testGroupTypeId, 0); + if (randomGenerator.nextBoolean()) { + int count = randomGenerator.nextInt(3); + builder.setCount(testGroupTypeId, count); + expectedValues.put(testGroupTypeId, count); + } + } + GroupTypeCountMap groupTypeCountMap = builder.build(); + + assertNotNull(groupTypeCountMap); + + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + int expectedValue = expectedValues.get(testGroupTypeId); + int actualValue = groupTypeCountMap.getGroupCount(testGroupTypeId); + assertEquals(expectedValue, actualValue); + } + } + + } + + @Test + @UnitTestMethod(target = GroupTypeCountMap.Builder.class, name = "setCount", args = { GroupTypeId.class, int.class }) + public void testSetCount() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1446391997583651047L); + + for (int i = 0; i < 20; i++) { + Map expectedValues = new LinkedHashMap<>(); + GroupTypeCountMap.Builder builder = GroupTypeCountMap.builder(); + + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + expectedValues.put(testGroupTypeId, 0); + if (randomGenerator.nextBoolean()) { + int count = randomGenerator.nextInt(3); + builder.setCount(testGroupTypeId, count); + expectedValues.put(testGroupTypeId, count); + } + } + GroupTypeCountMap groupTypeCountMap = builder.build(); + + assertNotNull(groupTypeCountMap); + + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + int expectedValue = expectedValues.get(testGroupTypeId); + int actualValue = groupTypeCountMap.getGroupCount(testGroupTypeId); + assertEquals(expectedValue, actualValue); + } + } + + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupTypesForPersonFilter.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupTypesForPersonFilter.java new file mode 100644 index 000000000..7c89f184e --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupTypesForPersonFilter.java @@ -0,0 +1,254 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.groups.datamanagers.GroupsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupMembershipAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupMembershipRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.GroupsTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.GroupsTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.TestGroupTypeId; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.Equality; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.FilterSensitivity; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters.Filter; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.TestPartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_GroupTypesForPersonFilter { + + @Test + @UnitTestConstructor(target = GroupTypesForPersonFilter.class, args = { Equality.class, int.class }) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupTypesForPersonFilter.class, name = "validate", args = { PartitionsContext.class }) + public void testValidate() { + // precondition tests + + // if the equality operator is null + Factory factory = GroupsTestPluginFactory.factory(100, 3, 10, 1499199255771310930L, (c) -> { + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + ContractException contractException = assertThrows(ContractException.class, + () -> new GroupTypesForPersonFilter(null, 5).validate(testPartitionsContext)); + assertEquals(PartitionError.NULL_EQUALITY_OPERATOR, contractException.getErrorType()); + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = GroupTypesForPersonFilter.class, name = "getFilterSensitivities", args = {}) + public void testGetFilterSensitivities() { + + Factory factory = GroupsTestPluginFactory.factory(100, 3, 10, 770617124373530907L, (c) -> { + Filter filter = new GroupTypesForPersonFilter(Equality.EQUAL, 5); + + Set> expected = new LinkedHashSet<>(); + expected.add(GroupMembershipAdditionEvent.class); + expected.add(GroupMembershipRemovalEvent.class); + + Set> filterSensitivities = filter.getFilterSensitivities(); + assertNotNull(filterSensitivities); + assertEquals(filterSensitivities.size(), 2); + + Set> actual = new LinkedHashSet<>(); + for (FilterSensitivity filterSensitivity : filterSensitivities) { + Class eventClass = filterSensitivity.getEventClass(); + actual.add(eventClass); + } + assertEquals(expected, actual); + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = GroupTypesForPersonFilter.class, name = "evaluate", args = { PartitionsContext.class, + PersonId.class }) + public void testEvaluate() { + + Factory factory = GroupsTestPluginFactory.factory(100, 3, 10, 2954287333801626073L, (c) -> { + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + List people = peopleDataManager.getPeople(); + RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); + + GroupId groupId1 = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); + GroupId groupId2 = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_2); + GroupId groupId3 = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_3); + + Filter filter = new GroupTypesForPersonFilter(Equality.EQUAL, 2); + + assertEquals(100, people.size()); + for (PersonId personId : people) { + int typeCount = randomGenerator.nextInt(4); + switch (typeCount) { + case 0: + break; + case 1: + groupsDataManager.addPersonToGroup(personId, groupId1); + break; + case 2: + groupsDataManager.addPersonToGroup(personId, groupId1); + groupsDataManager.addPersonToGroup(personId, groupId2); + break; + default: + groupsDataManager.addPersonToGroup(personId, groupId1); + groupsDataManager.addPersonToGroup(personId, groupId2); + groupsDataManager.addPersonToGroup(personId, groupId3); + break; + } + + } + + for (PersonId personId : people) { + boolean expected = groupsDataManager.getGroupTypeCountForPersonId(personId) == 2; + boolean actual = filter.evaluate(testPartitionsContext, personId); + assertEquals(expected, actual); + } + + /* precondition: if the context is null */ + ContractException contractException = assertThrows(ContractException.class, + () -> filter.evaluate(null, new PersonId(0))); + assertEquals(NucleusError.NULL_SIMULATION_CONTEXT, contractException.getErrorType()); + + /* precondition: if the person id is null */ + contractException = assertThrows(ContractException.class, + () -> filter.evaluate(testPartitionsContext, null)); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + /* precondition: if the person id is unknown */ + contractException = assertThrows(ContractException.class, + () -> filter.evaluate(testPartitionsContext, new PersonId(123412342))); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = GroupTypesForPersonFilter.class, name = "getEquality", args = {}) + public void testGetEquality() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5482382141715795467L); + + for (int i = 0; i < 10; i++) { + Equality equality = Equality.getRandomEquality(randomGenerator); + int groupTypeCount = randomGenerator.nextInt(10); + GroupTypesForPersonFilter filter = new GroupTypesForPersonFilter(equality, groupTypeCount); + + assertEquals(equality, filter.getEquality()); + } + } + + @Test + @UnitTestMethod(target = GroupTypesForPersonFilter.class, name = "getGroupTypeCount", args = {}) + public void testGetGroupTypeCount() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7992636052948538952L); + + for (int i = 0; i < 10; i++) { + Equality equality = Equality.getRandomEquality(randomGenerator); + int groupTypeCount = randomGenerator.nextInt(10); + GroupTypesForPersonFilter filter = new GroupTypesForPersonFilter(equality, groupTypeCount); + + assertEquals(groupTypeCount, filter.getGroupTypeCount()); + } + } + + @Test + @UnitTestMethod(target = GroupTypesForPersonFilter.class, name = "hashCode", args = {}) + public void testHashCode() { + GroupTypesForPersonFilter filter1 = new GroupTypesForPersonFilter(Equality.EQUAL, 2); + GroupTypesForPersonFilter filter2 = new GroupTypesForPersonFilter(Equality.EQUAL, 5); + GroupTypesForPersonFilter filter3 = new GroupTypesForPersonFilter(Equality.NOT_EQUAL, 2); + GroupTypesForPersonFilter filter4 = new GroupTypesForPersonFilter(Equality.NOT_EQUAL, 5); + GroupTypesForPersonFilter filter5 = new GroupTypesForPersonFilter(Equality.EQUAL, 2); + + assertEquals(filter1.hashCode(), filter1.hashCode()); + + assertNotEquals(filter1.hashCode(), filter2.hashCode()); + assertNotEquals(filter1.hashCode(), filter3.hashCode()); + assertNotEquals(filter1.hashCode(), filter4.hashCode()); + + assertNotEquals(filter2.hashCode(), filter3.hashCode()); + assertNotEquals(filter2.hashCode(), filter4.hashCode()); + + assertNotEquals(filter3.hashCode(), filter4.hashCode()); + + assertEquals(filter1.hashCode(), filter5.hashCode()); + } + + @Test + @UnitTestMethod(target = GroupTypesForPersonFilter.class, name = "equals", args = { Object.class }) + public void testEquals() { + GroupTypesForPersonFilter filter1 = new GroupTypesForPersonFilter(Equality.EQUAL, 2); + GroupTypesForPersonFilter filter2 = new GroupTypesForPersonFilter(Equality.EQUAL, 5); + GroupTypesForPersonFilter filter3 = new GroupTypesForPersonFilter(Equality.NOT_EQUAL, 2); + GroupTypesForPersonFilter filter4 = new GroupTypesForPersonFilter(Equality.NOT_EQUAL, 5); + GroupTypesForPersonFilter filter5 = new GroupTypesForPersonFilter(Equality.EQUAL, 2); + + assertEquals(filter1, filter1); + + assertNotEquals(filter1, null); + + assertNotEquals(filter1, new Object()); + + assertNotEquals(filter1, filter2); + assertNotEquals(filter1, filter3); + assertNotEquals(filter1, filter4); + + assertNotEquals(filter2, filter3); + assertNotEquals(filter2, filter4); + + assertNotEquals(filter3, filter4); + + assertEquals(filter1, filter5); + } + + @Test + @UnitTestMethod(target = GroupTypesForPersonFilter.class, name = "toString", args = {}) + public void testToString() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2394011517139293620L); + for (int i = 0; i < 10; i++) { + Equality equality = Equality.getRandomEquality(randomGenerator); + int groupTypeCount = randomGenerator.nextInt(10); + GroupTypesForPersonFilter filter = new GroupTypesForPersonFilter(equality, groupTypeCount); + + StringBuilder builder = new StringBuilder(); + builder.append("GroupTypesForPersonFilter [equality="); + builder.append(equality); + builder.append(", groupTypeCount="); + builder.append(groupTypeCount); + builder.append("]"); + + assertEquals(builder.toString(), filter.toString()); + } + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupsForPersonAndGroupTypeFilter.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupsForPersonAndGroupTypeFilter.java new file mode 100644 index 000000000..d5797a141 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupsForPersonAndGroupTypeFilter.java @@ -0,0 +1,404 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.support; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.groups.datamanagers.GroupsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupMembershipAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupMembershipRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.GroupsTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.GroupsTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.TestGroupTypeId; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.Equality; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.FilterSensitivity; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters.Filter; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.TestPartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_GroupsForPersonAndGroupTypeFilter { + + @Test + @UnitTestConstructor(target = GroupsForPersonAndGroupTypeFilter.class, args = { GroupTypeId.class, Equality.class, + int.class }) + public void testConstructor() { + + Factory factory = GroupsTestPluginFactory.factory(100, 3, 10, 5854778167265102928L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + final Filter filter = new GroupsForPersonAndGroupTypeFilter(TestGroupTypeId.GROUP_TYPE_1, Equality.EQUAL, + 5); + assertNotNull(filter); + + // precondition tests + + // if the group type id is null + ContractException contractException = assertThrows(ContractException.class, + () -> new GroupsForPersonAndGroupTypeFilter(null, Equality.EQUAL, 5) + .validate(testPartitionsContext)); + assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); + + // if the equality operator is null + contractException = assertThrows(ContractException.class, + () -> new GroupsForPersonAndGroupTypeFilter(TestGroupTypeId.GROUP_TYPE_1, null, 5) + .validate(testPartitionsContext)); + assertEquals(PartitionError.NULL_EQUALITY_OPERATOR, contractException.getErrorType()); + + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = GroupsForPersonAndGroupTypeFilter.class, name = "getFilterSensitivities", args = {}) + public void testGetFilterSensitivities() { + + Factory factory = GroupsTestPluginFactory.factory(100, 3, 10, 1469082977858605268L, (c) -> { + Filter filter = new GroupsForPersonAndGroupTypeFilter(TestGroupTypeId.GROUP_TYPE_1, Equality.EQUAL, 5); + + Set> expected = new LinkedHashSet<>(); + expected.add(GroupMembershipAdditionEvent.class); + expected.add(GroupMembershipRemovalEvent.class); + + Set> filterSensitivities = filter.getFilterSensitivities(); + assertNotNull(filterSensitivities); + assertEquals(filterSensitivities.size(), 2); + + Set> actual = new LinkedHashSet<>(); + for (FilterSensitivity filterSensitivity : filterSensitivities) { + Class eventClass = filterSensitivity.getEventClass(); + actual.add(eventClass); + } + assertEquals(expected, actual); + + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = GroupsForPersonAndGroupTypeFilter.class, name = "evaluate", args = { + PartitionsContext.class, PersonId.class }) + public void testEvaluate() { + + Factory factory = GroupsTestPluginFactory.factory(100, 0, 10, 4592268926831796100L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + List people = peopleDataManager.getPeople(); + + GroupId groupId1 = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); + GroupId groupId2 = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); + GroupId groupId3 = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); + + Filter filter = new GroupsForPersonAndGroupTypeFilter(TestGroupTypeId.GROUP_TYPE_1, Equality.EQUAL, 2); + + assertEquals(100, people.size()); + + for (PersonId personId : people) { + int groupCount = randomGenerator.nextInt(4); + switch (groupCount) { + case 0: + break; + case 1: + groupsDataManager.addPersonToGroup(personId, groupId1); + break; + case 2: + groupsDataManager.addPersonToGroup(personId, groupId1); + groupsDataManager.addPersonToGroup(personId, groupId2); + break; + default: + groupsDataManager.addPersonToGroup(personId, groupId1); + groupsDataManager.addPersonToGroup(personId, groupId2); + groupsDataManager.addPersonToGroup(personId, groupId3); + break; + } + + } + + for (PersonId personId : people) { + boolean expected = groupsDataManager.getGroupCountForGroupTypeAndPerson(TestGroupTypeId.GROUP_TYPE_1, + personId) == 2; + boolean actual = filter.evaluate(testPartitionsContext, personId); + assertEquals(expected, actual); + } + + /* precondition: if the person id is null */ + ContractException contractException = assertThrows(ContractException.class, + () -> filter.evaluate(testPartitionsContext, null)); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + /* precondition: if the person id is unknown */ + contractException = assertThrows(ContractException.class, + () -> filter.evaluate(testPartitionsContext, new PersonId(123412342))); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = GroupsForPersonAndGroupTypeFilter.class, name = "validate", args = { + PartitionsContext.class }) + public void testValidate() { + Factory factory = GroupsTestPluginFactory.factory(100, 0, 10, 3710154078488599088L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + + groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); + Filter filter = new GroupsForPersonAndGroupTypeFilter(TestGroupTypeId.GROUP_TYPE_1, Equality.EQUAL, 1); + + // show filter is valid when group type is valid and equality is + // valid + assertDoesNotThrow(() -> filter.validate(testPartitionsContext)); + + // precondition: equality is null + ContractException contractException = assertThrows(ContractException.class, + () -> new GroupsForPersonAndGroupTypeFilter(TestGroupTypeId.GROUP_TYPE_1, null, 2) + .validate(testPartitionsContext)); + assertEquals(PartitionError.NULL_EQUALITY_OPERATOR, contractException.getErrorType()); + + // precondition: group type id is null + contractException = assertThrows(ContractException.class, + () -> new GroupsForPersonAndGroupTypeFilter(null, Equality.EQUAL, 2) + .validate(testPartitionsContext)); + assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); + + // precondition: group type id is unknown + contractException = assertThrows(ContractException.class, + () -> new GroupsForPersonAndGroupTypeFilter(TestGroupTypeId.getUnknownGroupTypeId(), Equality.EQUAL, + 2).validate(testPartitionsContext)); + assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); + + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = GroupsForPersonAndGroupTypeFilter.class, name = "getEquality", args = {}) + public void testGetEquality() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8284890603319105493L); + + for (int i = 0; i < 10; i++) { + Equality equality = Equality.getRandomEquality(randomGenerator); + int groupCount = randomGenerator.nextInt(10); + GroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + GroupsForPersonAndGroupTypeFilter filter = new GroupsForPersonAndGroupTypeFilter(groupTypeId, equality, + groupCount); + + assertEquals(equality, filter.getEquality()); + } + } + + @Test + @UnitTestMethod(target = GroupsForPersonAndGroupTypeFilter.class, name = "getGroupCount", args = {}) + public void testGetGroupCount() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5425834828181361952L); + + for (int i = 0; i < 10; i++) { + Equality equality = Equality.getRandomEquality(randomGenerator); + int groupCount = randomGenerator.nextInt(10); + GroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + GroupsForPersonAndGroupTypeFilter filter = new GroupsForPersonAndGroupTypeFilter(groupTypeId, equality, + groupCount); + + assertEquals(groupCount, filter.getGroupCount()); + } + } + + @Test + @UnitTestMethod(target = GroupsForPersonAndGroupTypeFilter.class, name = "getGroupTypeId", args = {}) + public void testGetGroupTypeId() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6110751755905020120L); + + for (int i = 0; i < 10; i++) { + Equality equality = Equality.getRandomEquality(randomGenerator); + int groupCount = randomGenerator.nextInt(10); + GroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + GroupsForPersonAndGroupTypeFilter filter = new GroupsForPersonAndGroupTypeFilter(groupTypeId, equality, + groupCount); + + assertEquals(groupTypeId, filter.getGroupTypeId()); + } + } + + @Test + @UnitTestMethod(target = GroupsForPersonAndGroupTypeFilter.class, name = "hashCode", args = {}) + public void testHashCode() { + GroupsForPersonAndGroupTypeFilter filter1 = new GroupsForPersonAndGroupTypeFilter(TestGroupTypeId.GROUP_TYPE_1, + Equality.EQUAL, 1); + GroupsForPersonAndGroupTypeFilter filter2 = new GroupsForPersonAndGroupTypeFilter(TestGroupTypeId.GROUP_TYPE_1, + Equality.EQUAL, 0); + GroupsForPersonAndGroupTypeFilter filter3 = new GroupsForPersonAndGroupTypeFilter(TestGroupTypeId.GROUP_TYPE_1, + Equality.NOT_EQUAL, 1); + GroupsForPersonAndGroupTypeFilter filter4 = new GroupsForPersonAndGroupTypeFilter(TestGroupTypeId.GROUP_TYPE_2, + Equality.EQUAL, 1); + GroupsForPersonAndGroupTypeFilter filter5 = new GroupsForPersonAndGroupTypeFilter(TestGroupTypeId.GROUP_TYPE_2, + Equality.NOT_EQUAL, 1); + GroupsForPersonAndGroupTypeFilter filter6 = new GroupsForPersonAndGroupTypeFilter(TestGroupTypeId.GROUP_TYPE_1, + Equality.NOT_EQUAL, 0); + GroupsForPersonAndGroupTypeFilter filter7 = new GroupsForPersonAndGroupTypeFilter(TestGroupTypeId.GROUP_TYPE_2, + Equality.EQUAL, 0); + GroupsForPersonAndGroupTypeFilter filter8 = new GroupsForPersonAndGroupTypeFilter(TestGroupTypeId.GROUP_TYPE_2, + Equality.NOT_EQUAL, 0); + GroupsForPersonAndGroupTypeFilter filter9 = new GroupsForPersonAndGroupTypeFilter(TestGroupTypeId.GROUP_TYPE_1, + Equality.EQUAL, 1); + + assertEquals(filter1.hashCode(), filter1.hashCode()); + + assertNotEquals(filter1.hashCode(), filter2.hashCode()); + assertNotEquals(filter1.hashCode(), filter3.hashCode()); + assertNotEquals(filter1.hashCode(), filter4.hashCode()); + assertNotEquals(filter1.hashCode(), filter5.hashCode()); + assertNotEquals(filter1.hashCode(), filter6.hashCode()); + assertNotEquals(filter1.hashCode(), filter7.hashCode()); + assertNotEquals(filter1.hashCode(), filter8.hashCode()); + + assertNotEquals(filter2.hashCode(), filter3.hashCode()); + assertNotEquals(filter2.hashCode(), filter4.hashCode()); + assertNotEquals(filter2.hashCode(), filter5.hashCode()); + assertNotEquals(filter2.hashCode(), filter6.hashCode()); + assertNotEquals(filter2.hashCode(), filter7.hashCode()); + assertNotEquals(filter2.hashCode(), filter8.hashCode()); + + assertNotEquals(filter3.hashCode(), filter4.hashCode()); + assertNotEquals(filter3.hashCode(), filter5.hashCode()); + assertNotEquals(filter3.hashCode(), filter6.hashCode()); + assertNotEquals(filter3.hashCode(), filter7.hashCode()); + assertNotEquals(filter3.hashCode(), filter8.hashCode()); + + assertNotEquals(filter4.hashCode(), filter5.hashCode()); + assertNotEquals(filter4.hashCode(), filter6.hashCode()); + assertNotEquals(filter4.hashCode(), filter7.hashCode()); + assertNotEquals(filter4.hashCode(), filter8.hashCode()); + + assertNotEquals(filter5.hashCode(), filter6.hashCode()); + assertNotEquals(filter5.hashCode(), filter7.hashCode()); + assertNotEquals(filter5.hashCode(), filter8.hashCode()); + + assertNotEquals(filter6.hashCode(), filter7.hashCode()); + assertNotEquals(filter6.hashCode(), filter8.hashCode()); + + assertNotEquals(filter7.hashCode(), filter8.hashCode()); + + assertEquals(filter1.hashCode(), filter9.hashCode()); + } + + @Test + @UnitTestMethod(target = GroupsForPersonAndGroupTypeFilter.class, name = "equals", args = { Object.class }) + public void testEquals() { + GroupsForPersonAndGroupTypeFilter filter1 = new GroupsForPersonAndGroupTypeFilter(TestGroupTypeId.GROUP_TYPE_1, + Equality.EQUAL, 1); + GroupsForPersonAndGroupTypeFilter filter2 = new GroupsForPersonAndGroupTypeFilter(TestGroupTypeId.GROUP_TYPE_1, + Equality.EQUAL, 0); + GroupsForPersonAndGroupTypeFilter filter3 = new GroupsForPersonAndGroupTypeFilter(TestGroupTypeId.GROUP_TYPE_1, + Equality.NOT_EQUAL, 1); + GroupsForPersonAndGroupTypeFilter filter4 = new GroupsForPersonAndGroupTypeFilter(TestGroupTypeId.GROUP_TYPE_2, + Equality.EQUAL, 1); + GroupsForPersonAndGroupTypeFilter filter5 = new GroupsForPersonAndGroupTypeFilter(TestGroupTypeId.GROUP_TYPE_2, + Equality.NOT_EQUAL, 1); + GroupsForPersonAndGroupTypeFilter filter6 = new GroupsForPersonAndGroupTypeFilter(TestGroupTypeId.GROUP_TYPE_1, + Equality.NOT_EQUAL, 0); + GroupsForPersonAndGroupTypeFilter filter7 = new GroupsForPersonAndGroupTypeFilter(TestGroupTypeId.GROUP_TYPE_2, + Equality.EQUAL, 0); + GroupsForPersonAndGroupTypeFilter filter8 = new GroupsForPersonAndGroupTypeFilter(TestGroupTypeId.GROUP_TYPE_2, + Equality.NOT_EQUAL, 0); + GroupsForPersonAndGroupTypeFilter filter9 = new GroupsForPersonAndGroupTypeFilter(TestGroupTypeId.GROUP_TYPE_1, + Equality.EQUAL, 1); + + assertEquals(filter1, filter1); + + assertNotEquals(filter1, null); + + assertNotEquals(filter1, new Object()); + + assertNotEquals(filter1, filter2); + assertNotEquals(filter1, filter3); + assertNotEquals(filter1, filter4); + assertNotEquals(filter1, filter5); + assertNotEquals(filter1, filter6); + assertNotEquals(filter1, filter7); + assertNotEquals(filter1, filter8); + + assertNotEquals(filter2, filter3); + assertNotEquals(filter2, filter4); + assertNotEquals(filter2, filter5); + assertNotEquals(filter2, filter6); + assertNotEquals(filter2, filter7); + assertNotEquals(filter2, filter8); + + assertNotEquals(filter3, filter4); + assertNotEquals(filter3, filter5); + assertNotEquals(filter3, filter6); + assertNotEquals(filter3, filter7); + assertNotEquals(filter3, filter8); + + assertNotEquals(filter4, filter5); + assertNotEquals(filter4, filter6); + assertNotEquals(filter4, filter7); + assertNotEquals(filter4, filter8); + + assertNotEquals(filter5, filter6); + assertNotEquals(filter5, filter7); + assertNotEquals(filter5, filter8); + + assertNotEquals(filter6, filter7); + assertNotEquals(filter6, filter8); + + assertNotEquals(filter7, filter8); + + assertEquals(filter1, filter9); + } + + @Test + @UnitTestMethod(target = GroupsForPersonAndGroupTypeFilter.class, name = "toString", args = {}) + public void testToString() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2394011517139293620L); + for (int i = 0; i < 10; i++) { + Equality equality = Equality.getRandomEquality(randomGenerator); + int groupCount = randomGenerator.nextInt(10); + GroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + GroupsForPersonAndGroupTypeFilter filter = new GroupsForPersonAndGroupTypeFilter(groupTypeId, equality, + groupCount); + + StringBuilder builder = new StringBuilder(); + builder.append("GroupsForPersonAndGroupTypeFilter [groupTypeId="); + builder.append(groupTypeId); + builder.append(", equality="); + builder.append(equality); + builder.append(", groupCount="); + builder.append(groupCount); + builder.append("]"); + + assertEquals(builder.toString(), filter.toString()); + } + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupsForPersonFilter.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupsForPersonFilter.java new file mode 100644 index 000000000..255fd874f --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/support/AT_GroupsForPersonFilter.java @@ -0,0 +1,252 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.groups.datamanagers.GroupsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupMembershipAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.groups.events.GroupMembershipRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.GroupsTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.GroupsTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.TestGroupTypeId; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.Equality; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.FilterSensitivity; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters.Filter; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.TestPartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_GroupsForPersonFilter { + + @Test + @UnitTestConstructor(target = GroupsForPersonFilter.class, args = { Equality.class, int.class }) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = GroupsForPersonFilter.class, name = "validate", args = { PartitionsContext.class }) + public void testValidate() { + Factory factory = GroupsTestPluginFactory.factory(100, 3, 10, 5329703278551588697L, (c) -> { + // precondition tests + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + // if the equality operator is null + ContractException contractException = assertThrows(ContractException.class, + () -> new GroupsForPersonFilter(null, 5).validate(testPartitionsContext)); + assertEquals(PartitionError.NULL_EQUALITY_OPERATOR, contractException.getErrorType()); + + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = GroupsForPersonFilter.class, name = "getFilterSensitivities", args = {}) + public void testGetFilterSensitivities() { + Factory factory = GroupsTestPluginFactory.factory(100, 3, 10, 8314387061888020596L, (c) -> { + Filter filter = new GroupsForPersonFilter(Equality.EQUAL, 5); + + Set> expected = new LinkedHashSet<>(); + expected.add(GroupMembershipAdditionEvent.class); + expected.add(GroupMembershipRemovalEvent.class); + + Set> filterSensitivities = filter.getFilterSensitivities(); + assertNotNull(filterSensitivities); + assertEquals(filterSensitivities.size(), 2); + + Set> actual = new LinkedHashSet<>(); + for (FilterSensitivity filterSensitivity : filterSensitivities) { + Class eventClass = filterSensitivity.getEventClass(); + actual.add(eventClass); + } + assertEquals(expected, actual); + + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = GroupsForPersonFilter.class, name = "evaluate", args = { PartitionsContext.class, + PersonId.class }) + public void testEvaluate() { + + Factory factory = GroupsTestPluginFactory.factory(100, 0, 10, 6164158277278234559L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); + + List people = peopleDataManager.getPeople(); + + GroupId groupId1 = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); + GroupId groupId2 = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_2); + GroupId groupId3 = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_3); + + Filter filter = new GroupsForPersonFilter(Equality.EQUAL, 2); + + assertEquals(100, people.size()); + + for (PersonId personId : people) { + int typeCount = randomGenerator.nextInt(4); + switch (typeCount) { + case 0: + break; + case 1: + groupsDataManager.addPersonToGroup(personId, groupId1); + break; + case 2: + groupsDataManager.addPersonToGroup(personId, groupId1); + groupsDataManager.addPersonToGroup(personId, groupId2); + break; + default: + groupsDataManager.addPersonToGroup(personId, groupId1); + groupsDataManager.addPersonToGroup(personId, groupId2); + groupsDataManager.addPersonToGroup(personId, groupId3); + break; + } + + } + + for (PersonId personId : people) { + boolean expected = groupsDataManager.getGroupCountForPerson(personId) == 2; + boolean actual = filter.evaluate(testPartitionsContext, personId); + assertEquals(expected, actual); + } + + /* precondition: if the context is null */ + ContractException contractException = assertThrows(ContractException.class, + () -> filter.evaluate(null, new PersonId(0))); + assertEquals(NucleusError.NULL_SIMULATION_CONTEXT, contractException.getErrorType()); + + /* precondition: if the person id is null */ + assertThrows(RuntimeException.class, () -> filter.evaluate(testPartitionsContext, null)); + + /* precondition: if the person id is unknown */ + assertThrows(RuntimeException.class, () -> filter.evaluate(testPartitionsContext, new PersonId(123412342))); + + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = GroupsForPersonFilter.class, name = "getEquality", args = {}) + public void testGetEquality() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8284890603319105493L); + + for (int i = 0; i < 10; i++) { + Equality equality = Equality.getRandomEquality(randomGenerator); + int groupCount = randomGenerator.nextInt(10); + GroupsForPersonFilter filter = new GroupsForPersonFilter(equality, groupCount); + + assertEquals(equality, filter.getEquality()); + } + } + + @Test + @UnitTestMethod(target = GroupsForPersonFilter.class, name = "getGroupCount", args = {}) + public void testGetGroupCount() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5425834828181361952L); + + for (int i = 0; i < 10; i++) { + Equality equality = Equality.getRandomEquality(randomGenerator); + int groupCount = randomGenerator.nextInt(10); + GroupsForPersonFilter filter = new GroupsForPersonFilter(equality, groupCount); + + assertEquals(groupCount, filter.getGroupCount()); + } + } + + @Test + @UnitTestMethod(target = GroupsForPersonFilter.class, name = "hashCode", args = {}) + public void testHashCode() { + GroupsForPersonFilter filter1 = new GroupsForPersonFilter(Equality.EQUAL, 1); + GroupsForPersonFilter filter2 = new GroupsForPersonFilter(Equality.EQUAL, 0); + GroupsForPersonFilter filter3 = new GroupsForPersonFilter(Equality.NOT_EQUAL, 1); + GroupsForPersonFilter filter4 = new GroupsForPersonFilter(Equality.NOT_EQUAL, 0); + GroupsForPersonFilter filter5 = new GroupsForPersonFilter(Equality.EQUAL, 1); + + assertEquals(filter1.hashCode(), filter1.hashCode()); + + assertNotEquals(filter1.hashCode(), filter2.hashCode()); + assertNotEquals(filter1.hashCode(), filter3.hashCode()); + assertNotEquals(filter1.hashCode(), filter4.hashCode()); + + assertNotEquals(filter2.hashCode(), filter3.hashCode()); + assertNotEquals(filter2.hashCode(), filter4.hashCode()); + + assertNotEquals(filter3.hashCode(), filter4.hashCode()); + + assertEquals(filter1.hashCode(), filter5.hashCode()); + } + + @Test + @UnitTestMethod(target = GroupsForPersonFilter.class, name = "equals", args = { Object.class }) + public void testEquals() { + GroupsForPersonFilter filter1 = new GroupsForPersonFilter(Equality.EQUAL, 1); + GroupsForPersonFilter filter2 = new GroupsForPersonFilter(Equality.EQUAL, 0); + GroupsForPersonFilter filter3 = new GroupsForPersonFilter(Equality.NOT_EQUAL, 1); + GroupsForPersonFilter filter4 = new GroupsForPersonFilter(Equality.NOT_EQUAL, 0); + GroupsForPersonFilter filter5 = new GroupsForPersonFilter(Equality.EQUAL, 1); + + assertEquals(filter1, filter1); + + assertNotEquals(filter1, null); + + assertNotEquals(filter1, new Object()); + + assertNotEquals(filter1, filter2); + assertNotEquals(filter1, filter3); + assertNotEquals(filter1, filter4); + + assertNotEquals(filter2, filter3); + assertNotEquals(filter2, filter4); + + assertNotEquals(filter3, filter4); + + assertEquals(filter1, filter5); + } + + @Test + @UnitTestMethod(target = GroupsForPersonFilter.class, name = "toString", args = {}) + public void testToString() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2394011517139293620L); + for (int i = 0; i < 10; i++) { + Equality equality = Equality.getRandomEquality(randomGenerator); + int groupCount = randomGenerator.nextInt(10); + GroupsForPersonFilter filter = new GroupsForPersonFilter(equality, groupCount); + + StringBuilder builder = new StringBuilder(); + builder.append("GroupsForPersonFilter [equality="); + builder.append(equality); + builder.append(", groupCount="); + builder.append(groupCount); + builder.append("]"); + + assertEquals(builder.toString(), filter.toString()); + } + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/testsupport/AT_GroupsTestPluginFactory.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/testsupport/AT_GroupsTestPluginFactory.java new file mode 100644 index 000000000..6fab7509d --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/testsupport/AT_GroupsTestPluginFactory.java @@ -0,0 +1,333 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.testsupport; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.function.Consumer; + +import org.apache.commons.math3.random.RandomGenerator; +import org.apache.commons.math3.util.FastMath; +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.annotation.Testable; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.TestFactoryUtil; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginId; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.groups.GroupsPluginId; +import gov.hhs.aspr.ms.gcm.plugins.groups.datamanagers.GroupsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.groups.reports.GroupPopulationReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.groups.reports.GroupPropertyReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupError; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupId; +import gov.hhs.aspr.ms.gcm.plugins.groups.testsupport.GroupsTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePluginId; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeoplePluginData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonRange; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPluginId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.StochasticsError; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.testsupport.TestRandomGeneratorId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; +import util.wrappers.MultiKey; +import util.wrappers.MutableBoolean; + +@Testable +public class AT_GroupsTestPluginFactory { + + @Test + @UnitTestMethod(target = GroupsTestPluginFactory.class, name = "factory", args = { int.class, double.class, + double.class, long.class, Consumer.class }) + public void testFactory_Consumer() { + MutableBoolean executed = new MutableBoolean(); + Factory factory = GroupsTestPluginFactory.factory(100, 3, 5, 3765548905828391577L, + c -> executed.setValue(true)); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + assertTrue(executed.getValue()); + + // precondition: consumer is null + Consumer nullConsumer = null; + ContractException contractException = assertThrows(ContractException.class, + () -> GroupsTestPluginFactory.factory(0, 0, 0, 0, nullConsumer)); + assertEquals(NucleusError.NULL_ACTOR_CONTEXT_CONSUMER, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupsTestPluginFactory.class, name = "factory", args = { int.class, double.class, + double.class, long.class, TestPluginData.class }) + public void testFactory_TestPluginData() { + MutableBoolean executed = new MutableBoolean(); + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, c -> executed.setValue(true))); + TestPluginData testPluginData = pluginBuilder.build(); + + Factory factory = GroupsTestPluginFactory.factory(100, 3, 5, 1937810385546394605L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + assertTrue(executed.getValue()); + + // precondition: testPluginData is null + TestPluginData nullTestPluginData = null; + ContractException contractException = assertThrows(ContractException.class, + () -> GroupsTestPluginFactory.factory(0, 0, 0, 0, nullTestPluginData)); + assertEquals(NucleusError.NULL_PLUGIN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupsTestPluginFactory.Factory.class, name = "getPlugins", args = {}) + public void testGetPlugins() { + List plugins = GroupsTestPluginFactory.factory(0, 0, 0, 0, t -> { + }).getPlugins(); + assertEquals(4, plugins.size()); + + TestFactoryUtil.checkPluginExists(plugins, GroupsPluginId.PLUGIN_ID); + TestFactoryUtil.checkPluginExists(plugins, PeoplePluginId.PLUGIN_ID); + TestFactoryUtil.checkPluginExists(plugins, StochasticsPluginId.PLUGIN_ID); + TestFactoryUtil.checkPluginExists(plugins, TestPluginId.PLUGIN_ID); + } + + @Test + @UnitTestMethod(target = GroupsTestPluginFactory.Factory.class, name = "setGroupsPluginData", args = { + GroupsPluginData.class }) + public void testSetGroupsPluginData() { + GroupsPluginData.Builder builder = GroupsPluginData.builder(); + + builder.addGroup(new GroupId(0), TestGroupTypeId.GROUP_TYPE_1) + .addGroup(new GroupId(1), TestGroupTypeId.GROUP_TYPE_2) + .addGroup(new GroupId(2), TestGroupTypeId.GROUP_TYPE_1).addGroupTypeId(TestGroupTypeId.GROUP_TYPE_1) + .addGroupTypeId(TestGroupTypeId.GROUP_TYPE_2); + + GroupsPluginData groupsPluginData = builder.build(); + + List plugins = GroupsTestPluginFactory.factory(0, 0, 0, 0, t -> { + }).setGroupsPluginData(groupsPluginData).getPlugins(); + + TestFactoryUtil.checkPluginDataExists(plugins, groupsPluginData, GroupsPluginId.PLUGIN_ID); + + // precondition: groupsPluginData is not null + ContractException contractException = assertThrows(ContractException.class, + () -> GroupsTestPluginFactory.factory(0, 0, 0, 0, t -> { + }).setGroupsPluginData(null)); + assertEquals(GroupError.NULL_GROUP_PLUGIN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupsTestPluginFactory.Factory.class, name = "setPeoplePluginData", args = { + PeoplePluginData.class }) + public void testSetPeoplePluginData() { + PeoplePluginData.Builder builder = PeoplePluginData.builder(); + builder.addPersonRange(new PersonRange(0, 99)); + PeoplePluginData peoplePluginData = builder.build(); + + List plugins = GroupsTestPluginFactory.factory(0, 0, 0, 0, t -> { + }).setPeoplePluginData(peoplePluginData).getPlugins(); + + TestFactoryUtil.checkPluginDataExists(plugins, peoplePluginData, PeoplePluginId.PLUGIN_ID); + + // precondition: peoplePluginData is not null + ContractException contractException = assertThrows(ContractException.class, + () -> GroupsTestPluginFactory.factory(0, 0, 0, 0, t -> { + }).setPeoplePluginData(null)); + assertEquals(PersonError.NULL_PEOPLE_PLUGIN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupsTestPluginFactory.Factory.class, name = "setGroupPropertyReportPluginData", args = { + GroupPropertyReportPluginData.class }) + public void testSetGroupPropertyReportPluginData() { + GroupPropertyReportPluginData.Builder builder = GroupPropertyReportPluginData.builder() + .setReportPeriod(ReportPeriod.DAILY).setReportLabel(new SimpleReportLabel("test")); + + boolean include = false; + for (TestGroupPropertyId groupPropertyId : TestGroupPropertyId.values()) { + if (include) { + builder.includeGroupProperty(TestGroupTypeId.GROUP_TYPE_1, groupPropertyId); + } else { + builder.excludeGroupProperty(TestGroupTypeId.GROUP_TYPE_1, groupPropertyId); + } + + include = !include; + } + + GroupPropertyReportPluginData groupPropertyReportPluginData = builder.build(); + + List plugins = GroupsTestPluginFactory.factory(0, 0, 0, 0, t -> { + }).setGroupPropertyReportPluginData(groupPropertyReportPluginData).getPlugins(); + + TestFactoryUtil.checkPluginDataExists(plugins, groupPropertyReportPluginData, GroupsPluginId.PLUGIN_ID); + + // precondition: peoplePluginData is not null + ContractException contractException = assertThrows(ContractException.class, + () -> GroupsTestPluginFactory.factory(0, 0, 0, 0, t -> { + }).setGroupPropertyReportPluginData(null)); + assertEquals(GroupError.NULL_GROUP_PROPERTY_REPORT_PLUGIN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupsTestPluginFactory.Factory.class, name = "setGroupPopulationReportPluginData", args = { + GroupPopulationReportPluginData.class }) + public void testSetGroupPopulationReportPluginData() { + GroupPopulationReportPluginData.Builder builder = GroupPopulationReportPluginData.builder() + .setReportPeriod(ReportPeriod.DAILY).setReportLabel(new SimpleReportLabel("test")); + + GroupPopulationReportPluginData groupPopulationReportPluginData = builder.build(); + + List plugins = GroupsTestPluginFactory.factory(0, 0, 0, 0, t -> { + }).setGroupPopulationReportPluginData(groupPopulationReportPluginData).getPlugins(); + + TestFactoryUtil.checkPluginDataExists(plugins, groupPopulationReportPluginData, GroupsPluginId.PLUGIN_ID); + + // precondition: peoplePluginData is not null + ContractException contractException = assertThrows(ContractException.class, + () -> GroupsTestPluginFactory.factory(0, 0, 0, 0, t -> { + }).setGroupPopulationReportPluginData(null)); + assertEquals(GroupError.NULL_GROUP_POPULATION_REPORT_PLUGIN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupsTestPluginFactory.Factory.class, name = "setStochasticsPluginData", args = { + StochasticsPluginData.class }) + public void testSetStochasticsPluginData() { + StochasticsPluginData.Builder builder = StochasticsPluginData.builder(); + WellState wellState = WellState.builder().setSeed(8478739978811865148L).build(); + builder.setMainRNGState(wellState); + wellState = WellState.builder().setSeed(1336318114409771694L).build(); + builder.addRNG(TestRandomGeneratorId.BLITZEN, wellState); + + StochasticsPluginData stochasticsPluginData = builder.build(); + + List plugins = GroupsTestPluginFactory.factory(0, 0, 0, 0, t -> { + }).setStochasticsPluginData(stochasticsPluginData).getPlugins(); + + TestFactoryUtil.checkPluginDataExists(plugins, stochasticsPluginData, StochasticsPluginId.PLUGIN_ID); + + // precondition: stochasticsPluginData is not null + ContractException contractException = assertThrows(ContractException.class, + () -> GroupsTestPluginFactory.factory(0, 0, 0, 0, t -> { + }).setStochasticsPluginData(null)); + assertEquals(StochasticsError.NULL_STOCHASTICS_PLUGIN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = GroupsTestPluginFactory.class, name = "getStandardGroupsPluginData", args = { double.class, + double.class, List.class, long.class }) + public void testGetStandardGroupsPluginData() { + + long seed = 6442469165497328184L; + int initialPopulation = 100; + int expectedGroupsPerPerson = 3; + int expectedPeoplePerGroup = 5; + + List people = new ArrayList<>(); + for (int i = 0; i < initialPopulation; i++) { + people.add(new PersonId(i)); + } + int membershipCount = (int) FastMath.round(people.size() * expectedGroupsPerPerson); + int groupCount = (int) FastMath.round(membershipCount / expectedPeoplePerGroup); + membershipCount = FastMath.min(membershipCount, groupCount * people.size()); + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + GroupsPluginData.Builder groupBuilder = GroupsPluginData.builder(); + + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + groupBuilder.addGroupTypeId(testGroupTypeId); + } + + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { + groupBuilder.defineGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId, + testGroupPropertyId.getPropertyDefinition()); + } + + List groups = new ArrayList<>(); + + TestGroupTypeId testGroupTypeId = TestGroupTypeId.GROUP_TYPE_1; + for (int i = 0; i < groupCount; i++) { + GroupId groupId = new GroupId(i); + groups.add(groupId); + groupBuilder.addGroup(groupId, testGroupTypeId); + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId + .getShuffledTestGroupPropertyIds(testGroupTypeId, randomGenerator)) { + PropertyDefinition propertyDefinition = testGroupPropertyId.getPropertyDefinition(); + boolean hasDefaultValue = propertyDefinition.getDefaultValue().isPresent(); + boolean setValue = randomGenerator.nextBoolean(); + + if (!hasDefaultValue || setValue) { + groupBuilder.setGroupPropertyValue(groupId, testGroupPropertyId, + testGroupPropertyId.getRandomPropertyValue(randomGenerator)); + } + } + testGroupTypeId = testGroupTypeId.next(); + } + + List groupMemeberships = new ArrayList<>(); + for (PersonId personId : people) { + for (GroupId groupId : groups) { + groupMemeberships.add(new MultiKey(groupId, personId)); + } + } + Collections.shuffle(groupMemeberships, new Random(randomGenerator.nextLong())); + + for (int i = 0; i < membershipCount; i++) { + MultiKey multiKey = groupMemeberships.get(i); + GroupId groupId = multiKey.getKey(0); + PersonId personId = multiKey.getKey(1); + groupBuilder.associatePersonToGroup(groupId, personId); + } + GroupsPluginData expectedPluginData = groupBuilder.build(); + + GroupsPluginData actualPluginData = GroupsTestPluginFactory.getStandardGroupsPluginData(expectedGroupsPerPerson, + expectedPeoplePerGroup, people, seed); + + assertEquals(expectedPluginData, actualPluginData); + } + + @Test + @UnitTestMethod(target = GroupsTestPluginFactory.class, name = "getStandardPeoplePluginData", args = { int.class }) + public void testGetStandardPeoplePluginData() { + + int initialPopulation = 100; + + PeoplePluginData.Builder peopleBuilder = PeoplePluginData.builder(); + + if (initialPopulation > 0) { + peopleBuilder.addPersonRange(new PersonRange(0, initialPopulation - 1)); + } + + PeoplePluginData expectedPluginData = peopleBuilder.build(); + PeoplePluginData actualPluginData = GroupsTestPluginFactory.getStandardPeoplePluginData(initialPopulation); + + assertEquals(expectedPluginData, actualPluginData); + } + + @Test + @UnitTestMethod(target = GroupsTestPluginFactory.class, name = "getStandardStochasticsPluginData", args = { + long.class }) + public void testGetStandardStochasticsPluginData() { + long seed = 6072871729256538807L; + WellState wellState = WellState.builder().setSeed(seed).build(); + + StochasticsPluginData expectedPluginData = StochasticsPluginData.builder().setMainRNGState(wellState).build(); + StochasticsPluginData actualPluginData = GroupsTestPluginFactory.getStandardStochasticsPluginData(seed); + + assertEquals(expectedPluginData, actualPluginData); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/testsupport/AT_TestAuxiliaryGroupPropertyId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/testsupport/AT_TestAuxiliaryGroupPropertyId.java new file mode 100644 index 000000000..db7ce383a --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/testsupport/AT_TestAuxiliaryGroupPropertyId.java @@ -0,0 +1,100 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.testsupport; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.EnumSet; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import util.annotations.UnitTestMethod; +import util.random.RandomGeneratorProvider; + +public class AT_TestAuxiliaryGroupPropertyId { + + @Test + @UnitTestMethod(target = TestAuxiliaryGroupPropertyId.class, name = "getPropertyDefinition", args = {}) + public void testGetPropertyDefinition() { + for (TestAuxiliaryGroupPropertyId testGroupPropertyId : TestAuxiliaryGroupPropertyId.values()) { + assertNotNull(testGroupPropertyId.getPropertyDefinition()); + } + } + + @Test + @UnitTestMethod(target = TestAuxiliaryGroupPropertyId.class, name = "getRandomPropertyValue", args = { RandomGenerator.class }) + public void testGetRandomPropertyValue() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4599982550528313954L); + + /* + * Show that randomly generated values are compatible with the + * associated property definition. Show that the values are reasonably + * unique + */ + for (TestAuxiliaryGroupPropertyId testGroupPropertyId : TestAuxiliaryGroupPropertyId.values()) { + PropertyDefinition propertyDefinition = testGroupPropertyId.getPropertyDefinition(); + Set values = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + Object propertyValue = testGroupPropertyId.getRandomPropertyValue(randomGenerator); + values.add(propertyValue); + assertTrue(propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())); + } + // show that the values are reasonable unique + if (propertyDefinition.getType() != Boolean.class) { + assertTrue(values.size() > 10); + } else { + assertEquals(2, values.size()); + } + } + } + + @Test + @UnitTestMethod(target = TestAuxiliaryGroupPropertyId.class, name = "getTestGroupTypeId", args = {}) + public void testGetTestGroupTypeId() { + for (TestAuxiliaryGroupPropertyId testGroupPropertyId : TestAuxiliaryGroupPropertyId.values()) { + assertNotNull(testGroupPropertyId.getTestGroupTypeId()); + } + } + + @Test + @UnitTestMethod(target = TestAuxiliaryGroupPropertyId.class, name = "getUnknownGroupPropertyId", args = {}) + public void testGetUnknownGroupPropertyId() { + /* + * Shows that a generated unknown group property id is unique, not null + * and not a member of the enum + */ + Set testProperties = EnumSet.allOf(TestAuxiliaryGroupPropertyId.class); + Set unknownGroupPropertyIds = new LinkedHashSet<>(); + for (int i = 0; i < 30; i++) { + GroupPropertyId unknownGroupPropertyId = TestAuxiliaryGroupPropertyId.getUnknownGroupPropertyId(); + assertNotNull(unknownGroupPropertyId); + boolean unique = unknownGroupPropertyIds.add(unknownGroupPropertyId); + assertTrue(unique); + assertFalse(testProperties.contains(unknownGroupPropertyId)); + } + } + + @Test + @UnitTestMethod(target = TestAuxiliaryGroupPropertyId.class, name = "getTestGroupPropertyIds", args = { TestAuxiliaryGroupTypeId.class }) + public void testGetTestAuxiliaryGroupPropertyIds() { + + for (TestAuxiliaryGroupTypeId testGroupTypeId : TestAuxiliaryGroupTypeId.values()) { + // show that each group type has at least one associated property id + Set testGroupPropertyIds = TestAuxiliaryGroupPropertyId.getTestGroupPropertyIds(testGroupTypeId); + assertNotNull(testGroupPropertyIds); + assertFalse(testGroupPropertyIds.isEmpty()); + + // show that each such property id is associated with that group + // type + for (TestAuxiliaryGroupPropertyId testGroupPropertyId : testGroupPropertyIds) { + assertEquals(testGroupTypeId, testGroupPropertyId.getTestGroupTypeId()); + } + } + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/testsupport/AT_TestAuxiliaryGroupTypeId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/testsupport/AT_TestAuxiliaryGroupTypeId.java new file mode 100644 index 000000000..99648a83b --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/testsupport/AT_TestAuxiliaryGroupTypeId.java @@ -0,0 +1,89 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.testsupport; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupTypeId; +import util.annotations.UnitTestMethod; +import util.random.RandomGeneratorProvider; +import util.wrappers.MutableInteger; + +public class AT_TestAuxiliaryGroupTypeId { + + @Test + @UnitTestMethod(target = TestAuxiliaryGroupTypeId.class, name = "size", args = {}) + public void testSize() { + assertEquals(TestAuxiliaryGroupTypeId.values().length, TestAuxiliaryGroupTypeId.size()); + } + + @Test + @UnitTestMethod(target = TestAuxiliaryGroupTypeId.class, name = "next", args = {}) + public void testNext() { + for (TestAuxiliaryGroupTypeId testGroupTypeId : TestAuxiliaryGroupTypeId.values()) { + int index = (testGroupTypeId.ordinal() + 1) % TestAuxiliaryGroupTypeId.values().length; + TestAuxiliaryGroupTypeId expectedNext = TestAuxiliaryGroupTypeId.values()[index]; + assertEquals(expectedNext, testGroupTypeId.next()); + } + } + + @Test + @UnitTestMethod(target = TestAuxiliaryGroupTypeId.class, name = "getRandomGroupTypeId", args = { RandomGenerator.class }) + public void testGetRandomGroupTypeId() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8706009409831897125L); + + /* + * Show that randomly generated type ids are non-null and reasonably + * distributed + */ + + Map counterMap = new LinkedHashMap<>(); + for (TestAuxiliaryGroupTypeId testGroupTypeId : TestAuxiliaryGroupTypeId.values()) { + counterMap.put(testGroupTypeId, new MutableInteger()); + } + int sampleSize = 1000; + for (int i = 0; i < sampleSize; i++) { + TestAuxiliaryGroupTypeId testGroupTypeId = TestAuxiliaryGroupTypeId.getRandomGroupTypeId(randomGenerator); + assertNotNull(testGroupTypeId); + counterMap.get(testGroupTypeId).increment(); + } + + int expectedSize = sampleSize / TestAuxiliaryGroupTypeId.values().length; + int lowExpectedSize = 4 * expectedSize / 5; + int highExpectedSize = 6 * expectedSize / 5; + for (TestAuxiliaryGroupTypeId testGroupTypeId : TestAuxiliaryGroupTypeId.values()) { + MutableInteger mutableInteger = counterMap.get(testGroupTypeId); + assertTrue(mutableInteger.getValue() > lowExpectedSize); + assertTrue(mutableInteger.getValue() < highExpectedSize); + } + } + + @Test + @UnitTestMethod(target = TestAuxiliaryGroupTypeId.class, name = "getUnknownGroupTypeId", args = {}) + public void testGetUnknownGroupTypeId() { + /* + * Shows that a generated unknown group type id is unique, not null and + * not a member of the enum + */ + Set testGroupTypeIds = EnumSet.allOf(TestAuxiliaryGroupTypeId.class); + Set unknownGroupTypeIds = new LinkedHashSet<>(); + for (int i = 0; i < 30; i++) { + GroupTypeId unknownGroupTypeId = TestAuxiliaryGroupTypeId.getUnknownGroupTypeId(); + assertNotNull(unknownGroupTypeId); + boolean unique = unknownGroupTypeIds.add(unknownGroupTypeId); + assertTrue(unique); + assertFalse(testGroupTypeIds.contains(unknownGroupTypeId)); + } + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/testsupport/AT_TestGroupPropertyId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/testsupport/AT_TestGroupPropertyId.java new file mode 100644 index 000000000..1f115f893 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/testsupport/AT_TestGroupPropertyId.java @@ -0,0 +1,152 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.testsupport; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import util.annotations.UnitTestMethod; +import util.random.RandomGeneratorProvider; + +public class AT_TestGroupPropertyId { + + @Test + @UnitTestMethod(target = TestGroupPropertyId.class, name = "getPropertyDefinition", args = {}) + public void testGetPropertyDefinition() { + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { + assertNotNull(testGroupPropertyId.getPropertyDefinition()); + } + + } + + @Test + @UnitTestMethod(target = TestGroupPropertyId.class, name = "getTestGroupTypeId", args = {}) + public void testGetTestGroupTypeId() { + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { + assertNotNull(testGroupPropertyId.getTestGroupTypeId()); + } + } + + @Test + @UnitTestMethod(target = TestGroupPropertyId.class, name = "getTestGroupPropertyIds", args = { + TestGroupTypeId.class }) + public void testGetTestGroupPropertyIds() { + + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + // show that each group type has at least one associated property id + Set testGroupPropertyIds = TestGroupPropertyId + .getTestGroupPropertyIds(testGroupTypeId); + assertNotNull(testGroupPropertyIds); + assertFalse(testGroupPropertyIds.isEmpty()); + + // show that each such property id is associated with that group + // type + for (TestGroupPropertyId testGroupPropertyId : testGroupPropertyIds) { + assertEquals(testGroupTypeId, testGroupPropertyId.getTestGroupTypeId()); + } + } + } + + @Test + @UnitTestMethod(target = TestGroupPropertyId.class, name = "getUnknownGroupPropertyId", args = {}) + public void testGetUnknownGroupPropertyId() { + /* + * Shows that a generated unknown group property id is unique, not null and not + * a member of the enum + */ + Set testProperties = EnumSet.allOf(TestGroupPropertyId.class); + Set unknownGroupPropertyIds = new LinkedHashSet<>(); + for (int i = 0; i < 30; i++) { + GroupPropertyId unknownGroupPropertyId = TestGroupPropertyId.getUnknownGroupPropertyId(); + assertNotNull(unknownGroupPropertyId); + boolean unique = unknownGroupPropertyIds.add(unknownGroupPropertyId); + assertTrue(unique); + assertFalse(testProperties.contains(unknownGroupPropertyId)); + } + } + + @Test + @UnitTestMethod(target = TestGroupPropertyId.class, name = "getRandomPropertyValue", args = { + RandomGenerator.class }) + public void testGetRandomPropertyValue() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6173923848365818813L); + + /* + * Show that randomly generated values are compatible with the associated + * property definition. Show that the values are reasonably unique + */ + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { + PropertyDefinition propertyDefinition = testGroupPropertyId.getPropertyDefinition(); + Set values = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + Object propertyValue = testGroupPropertyId.getRandomPropertyValue(randomGenerator); + values.add(propertyValue); + assertTrue(propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())); + } + // show that the values are reasonable unique + if (propertyDefinition.getType() != Boolean.class) { + assertTrue(values.size() > 10); + } else { + assertEquals(2, values.size()); + } + } + } + + @Test + @UnitTestMethod(target = TestGroupPropertyId.class, name = "next", args = {}) + public void testNext() { + for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { + int index = (testGroupPropertyId.ordinal() + 1) % TestGroupPropertyId.values().length; + TestGroupPropertyId expectedNext = TestGroupPropertyId.values()[index]; + assertEquals(expectedNext, testGroupPropertyId.next()); + } + + } + + @Test + @UnitTestMethod(target = TestGroupPropertyId.class, name = "getShuffledTestGroupPropertyIds", args = { + TestGroupTypeId.class, RandomGenerator.class }) + public void testGetShuffledTestGroupPropertyIds() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4386761356441288660L); + + List groupPropertyIds = new ArrayList<>( + TestGroupPropertyId.getTestGroupPropertyIds(TestGroupTypeId.GROUP_TYPE_1)); + + Collections.shuffle(groupPropertyIds, new Random(randomGenerator.nextLong())); + + List actuaIds = TestGroupPropertyId.getShuffledTestGroupPropertyIds( + TestGroupTypeId.GROUP_TYPE_1, RandomGeneratorProvider.getRandomGenerator(4386761356441288660L)); + + assertEquals(groupPropertyIds, actuaIds); + } + + @Test + @UnitTestMethod(target = TestGroupPropertyId.class, name = "getRandomTestGroupPropertyId", args = { + RandomGenerator.class }) + public void testGetRandomTestGroupPropertyId() { + RandomGenerator randomGenerator1 = RandomGeneratorProvider.getRandomGenerator(6233266572381278758L); + RandomGenerator randomGenerator2 = RandomGeneratorProvider.getRandomGenerator(6233266572381278758L); + for (int i = 0; i < 10; i++) { + int index = randomGenerator2.nextInt(TestGroupPropertyId.values().length); + + TestGroupPropertyId expectedId = TestGroupPropertyId.values()[index]; + TestGroupPropertyId actualId = TestGroupPropertyId.getRandomTestGroupPropertyId(randomGenerator1); + + assertEquals(expectedId, actualId); + } + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/testsupport/AT_TestGroupTypeId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/testsupport/AT_TestGroupTypeId.java new file mode 100644 index 000000000..ad8bd5bbf --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/groups/testsupport/AT_TestGroupTypeId.java @@ -0,0 +1,114 @@ +package gov.hhs.aspr.ms.gcm.plugins.groups.testsupport; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupTypeId; +import util.annotations.UnitTestMethod; +import util.random.RandomGeneratorProvider; +import util.wrappers.MutableInteger; + +public class AT_TestGroupTypeId { + + @Test + @UnitTestMethod(target = TestGroupTypeId.class, name = "getRandomGroupTypeId", args = { RandomGenerator.class }) + public void testGetRandomGroupTypeId() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2320032453802629402L); + + /* + * Show that randomly generated type ids are non-null and reasonably distributed + */ + + Map counterMap = new LinkedHashMap<>(); + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + counterMap.put(testGroupTypeId, new MutableInteger()); + } + int sampleSize = 1000; + for (int i = 0; i < sampleSize; i++) { + TestGroupTypeId testGroupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); + assertNotNull(testGroupTypeId); + counterMap.get(testGroupTypeId).increment(); + } + + int expectedSize = sampleSize / TestGroupTypeId.values().length; + int lowExpectedSize = 4 * expectedSize / 5; + int highExpectedSize = 6 * expectedSize / 5; + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + MutableInteger mutableInteger = counterMap.get(testGroupTypeId); + assertTrue(mutableInteger.getValue() > lowExpectedSize); + assertTrue(mutableInteger.getValue() < highExpectedSize); + } + + } + + @Test + @UnitTestMethod(target = TestGroupTypeId.class, name = "size", args = {}) + public void testSize() { + assertEquals(TestGroupTypeId.values().length, TestGroupTypeId.size()); + } + + @Test + @UnitTestMethod(target = TestGroupTypeId.class, name = "next", args = {}) + public void testNext() { + for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { + int index = (testGroupTypeId.ordinal() + 1) % TestGroupTypeId.values().length; + TestGroupTypeId expectedNext = TestGroupTypeId.values()[index]; + assertEquals(expectedNext, testGroupTypeId.next()); + } + } + + @Test + @UnitTestMethod(target = TestGroupTypeId.class, name = "getUnknownGroupTypeId", args = {}) + public void testGetUnknownGroupTypeId() { + /* + * Shows that a generated unknown group type id is unique, not null and not a + * member of the enum + */ + Set testGroupTypeIds = EnumSet.allOf(TestGroupTypeId.class); + Set unknownGroupTypeIds = new LinkedHashSet<>(); + for (int i = 0; i < 30; i++) { + GroupTypeId unknownGroupTypeId = TestGroupTypeId.getUnknownGroupTypeId(); + assertNotNull(unknownGroupTypeId); + boolean unique = unknownGroupTypeIds.add(unknownGroupTypeId); + assertTrue(unique); + assertFalse(testGroupTypeIds.contains(unknownGroupTypeId)); + } + } + + @Test + @UnitTestMethod(target = TestGroupTypeId.class, name = "getTestGroupTypeIds", args = {}) + public void testGetTestGroupTypeIds() { + assertEquals(Arrays.asList(TestGroupTypeId.values()), TestGroupTypeId.getTestGroupTypeIds()); + } + + @Test + @UnitTestMethod(target = TestGroupTypeId.class, name = "getShuffledTestGroupTypeIds", args = { + RandomGenerator.class }) + public void testGetShuffledTestGroupTypeIds() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4386761356441288660L); + + List groupPropertyIds = TestGroupTypeId.getTestGroupTypeIds(); + + Collections.shuffle(groupPropertyIds, new Random(randomGenerator.nextLong())); + + List actuaIds = TestGroupTypeId + .getShuffledTestGroupTypeIds(RandomGeneratorProvider.getRandomGenerator(4386761356441288660L)); + + assertEquals(groupPropertyIds, actuaIds); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/AT_MaterialsPlugin.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/AT_MaterialsPlugin.java new file mode 100644 index 000000000..fa4817787 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/AT_MaterialsPlugin.java @@ -0,0 +1,142 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.plugins.materials.datamangers.MaterialsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.materials.reports.BatchStatusReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.materials.reports.MaterialsProducerPropertyReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.materials.reports.MaterialsProducerResourceReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.materials.reports.StageReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsError; +import gov.hhs.aspr.ms.gcm.plugins.regions.RegionsPluginId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.resources.ResourcesPluginId; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + +public class AT_MaterialsPlugin { + + @Test + @UnitTestMethod(target = MaterialsPlugin.class, name = "builder", args = {}) + public void testBuilder() { + assertNotNull(MaterialsPlugin.builder()); + } + + @Test + @UnitTestMethod(target = MaterialsPlugin.Builder.class, name = "getMaterialsPlugin", args = {}) + public void testGetMaterialsPlugin() { + MaterialsPluginData materialsPluginData = MaterialsPluginData.builder().build(); + Plugin materialsPlugin = MaterialsPlugin.builder().setMaterialsPluginData(materialsPluginData) + .getMaterialsPlugin(); + + assertEquals(1, materialsPlugin.getPluginDatas().size()); + assertTrue(materialsPlugin.getPluginDatas().contains(materialsPluginData)); + + assertEquals(MaterialsPluginId.PLUGIN_ID, materialsPlugin.getPluginId()); + + Set expectedDependencies = new LinkedHashSet<>(); + + expectedDependencies.add(RegionsPluginId.PLUGIN_ID); + expectedDependencies.add(ResourcesPluginId.PLUGIN_ID); + assertEquals(expectedDependencies, materialsPlugin.getPluginDependencies()); + + // precondition test: if the materials plugin data is null + ContractException contractException = assertThrows(ContractException.class, + () -> MaterialsPlugin.builder().getMaterialsPlugin()); + assertEquals(MaterialsError.NULL_MATERIALS_PLUGIN_DATA, contractException.getErrorType()); + + } + + + @Test + @UnitTestMethod(target = MaterialsPlugin.Builder.class, name = "setBatchStatusReportPluginData", args = {BatchStatusReportPluginData.class}) + public void testSetBatchStatusReportPluginData() { + MaterialsPluginData materialsPluginData = MaterialsPluginData.builder().build(); + + BatchStatusReportPluginData batchStatusReportPluginData = BatchStatusReportPluginData.builder()// + .setReportLabel(new SimpleReportLabel("BatchStatusReport"))// + .build(); + + Plugin materialsPlugin = MaterialsPlugin.builder()// + .setMaterialsPluginData(materialsPluginData)// + .setBatchStatusReportPluginData(batchStatusReportPluginData)// + .getMaterialsPlugin();// + + assertTrue(materialsPlugin.getPluginDatas().contains(batchStatusReportPluginData)); + } + + @Test + @UnitTestMethod(target = MaterialsPlugin.Builder.class, name = "setMaterialsPluginData", args = {MaterialsPluginData.class}) + public void testSetMaterialsPluginData() { + MaterialsPluginData materialsPluginData = MaterialsPluginData.builder().build(); + + Plugin materialsPlugin = MaterialsPlugin.builder()// + .setMaterialsPluginData(materialsPluginData)// + .getMaterialsPlugin();// + + assertTrue(materialsPlugin.getPluginDatas().contains(materialsPluginData)); + } + + + @Test + @UnitTestMethod(target = MaterialsPlugin.Builder.class, name = "setMaterialsProducerPropertyReportPluginData", args = {MaterialsProducerPropertyReportPluginData.class}) + public void testSetMaterialsProducerPropertyReportPluginData() { + MaterialsPluginData materialsPluginData = MaterialsPluginData.builder().build(); + + MaterialsProducerPropertyReportPluginData materialsProducerPropertyReportPluginData = MaterialsProducerPropertyReportPluginData.builder()// + .setReportLabel(new SimpleReportLabel("MaterialsProducerPropertyReport"))// + .build(); + + Plugin materialsPlugin = MaterialsPlugin.builder()// + .setMaterialsPluginData(materialsPluginData)// + .setMaterialsProducerPropertyReportPluginData(materialsProducerPropertyReportPluginData)// + .getMaterialsPlugin();// + + assertTrue(materialsPlugin.getPluginDatas().contains(materialsProducerPropertyReportPluginData)); + } + + @Test + @UnitTestMethod(target = MaterialsPlugin.Builder.class, name = "setMaterialsProducerResourceReportPluginData", args = {MaterialsProducerResourceReportPluginData.class}) + public void testSetMaterialsProducerResourceReportPluginData() { + MaterialsPluginData materialsPluginData = MaterialsPluginData.builder().build(); + + MaterialsProducerResourceReportPluginData materialsProducerResourceReportPluginData = MaterialsProducerResourceReportPluginData.builder()// + .setReportLabel(new SimpleReportLabel("MaterialsProducerResourceReport"))// + .build(); + + Plugin materialsPlugin = MaterialsPlugin.builder()// + .setMaterialsPluginData(materialsPluginData)// + .setMaterialsProducerResourceReportPluginData(materialsProducerResourceReportPluginData)// + .getMaterialsPlugin();// + + assertTrue(materialsPlugin.getPluginDatas().contains(materialsProducerResourceReportPluginData)); + } + + @Test + @UnitTestMethod(target = MaterialsPlugin.Builder.class, name = "setStageReportPluginData", args = {StageReportPluginData.class}) + public void testSetStageReportPluginData() { + MaterialsPluginData materialsPluginData = MaterialsPluginData.builder().build(); + + StageReportPluginData stageReportPluginData = StageReportPluginData.builder()// + .setReportLabel(new SimpleReportLabel("MaterialsProducerResourceReport"))// + .build(); + + Plugin materialsPlugin = MaterialsPlugin.builder()// + .setMaterialsPluginData(materialsPluginData)// + .setStageReportPluginData(stageReportPluginData)// + .getMaterialsPlugin();// + + assertTrue(materialsPlugin.getPluginDatas().contains(stageReportPluginData)); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/AT_MaterialsPluginId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/AT_MaterialsPluginId.java new file mode 100644 index 000000000..b2279f9f9 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/AT_MaterialsPluginId.java @@ -0,0 +1,16 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestField; + +public class AT_MaterialsPluginId { + + @Test + @UnitTestField(target = MaterialsPluginId.class, name = "PLUGIN_ID") + public void testPluginId() { + assertNotNull(MaterialsPluginId.PLUGIN_ID); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/datamanagers/AT_MaterialsDataManager.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/datamanagers/AT_MaterialsDataManager.java new file mode 100644 index 000000000..f9b245849 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/datamanagers/AT_MaterialsDataManager.java @@ -0,0 +1,7056 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.datamanagers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Random; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.apache.commons.math3.util.Pair; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; +import gov.hhs.aspr.ms.gcm.nucleus.EventFilter; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestOutputConsumer; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.materials.datamangers.MaterialsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.materials.datamangers.MaterialsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.BatchAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.BatchAmountUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.BatchImminentRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.BatchPropertyDefinitionEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.BatchPropertyUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.MaterialIdAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.MaterialsProducerAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.MaterialsProducerPropertyDefinitionEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.MaterialsProducerPropertyUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.MaterialsProducerResourceUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.StageAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.StageImminentRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.StageMaterialsProducerUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.StageMembershipAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.StageMembershipRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.StageOfferUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchConstructionInfo; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchPropertyDefinitionInitialization; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsError; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerConstructionData; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerPropertyDefinitionInitialization; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.StageConversionInfo; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.StageId; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.MaterialsTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.MaterialsTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.TestBatchConstructionInfo; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.TestBatchPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.TestMaterialId; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.TestMaterialsProducerId; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.TestMaterialsProducerPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionError; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.TestRegionId; +import gov.hhs.aspr.ms.gcm.plugins.resources.datamanagers.ResourcesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.resources.events.RegionResourceUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceError; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.TestResourceId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; +import util.wrappers.MultiKey; +import util.wrappers.MutableLong; + +public class AT_MaterialsDataManager { + + @Test + @UnitTestConstructor(target = MaterialsDataManager.class, args = { MaterialsPluginData.class }) + public void testConstructor() { + ContractException contractException = assertThrows(ContractException.class, + () -> new MaterialsDataManager(null)); + assertEquals(MaterialsError.NULL_MATERIALS_PLUGIN_DATA, contractException.getErrorType()); + } + + /** + * Demonstrates that the data manager produces plugin data that reflects its + * final state + */ + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testStateFinalization() { + // fill in a materials plugin data with some nominal content + ////////////////////////////////////////////////////// + + MaterialsPluginData.Builder materialsBuilder = MaterialsPluginData.builder(); + + materialsBuilder.addMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + materialsBuilder.addMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_2); + TestMaterialsProducerPropertyId propId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; + materialsBuilder.defineMaterialsProducerProperty(propId, propId.getPropertyDefinition()); + propId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK; + materialsBuilder.defineMaterialsProducerProperty(propId, propId.getPropertyDefinition()); + materialsBuilder.setMaterialsProducerPropertyValue(TestMaterialsProducerId.MATERIALS_PRODUCER_1, propId, 345); + materialsBuilder.setMaterialsProducerPropertyValue(TestMaterialsProducerId.MATERIALS_PRODUCER_2, propId, 5234); + materialsBuilder.setMaterialsProducerResourceLevel(TestMaterialsProducerId.MATERIALS_PRODUCER_1, + TestResourceId.RESOURCE_1, 123L); + materialsBuilder.setMaterialsProducerResourceLevel(TestMaterialsProducerId.MATERIALS_PRODUCER_2, + TestResourceId.RESOURCE_2, 55L); + materialsBuilder.addMaterial(TestMaterialId.MATERIAL_1); + materialsBuilder.addMaterial(TestMaterialId.MATERIAL_2); + TestBatchPropertyId bprop = TestBatchPropertyId.BATCH_PROPERTY_1_1_BOOLEAN_IMMUTABLE_NO_TRACK; + materialsBuilder.defineBatchProperty(TestMaterialId.MATERIAL_1, bprop, bprop.getPropertyDefinition()); + bprop = TestBatchPropertyId.BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK; + materialsBuilder.defineBatchProperty(TestMaterialId.MATERIAL_1, bprop, bprop.getPropertyDefinition()); + bprop = TestBatchPropertyId.BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK; + materialsBuilder.defineBatchProperty(TestMaterialId.MATERIAL_2, bprop, bprop.getPropertyDefinition()); + materialsBuilder.addBatch(new BatchId(0), TestMaterialId.MATERIAL_1, 10); + materialsBuilder.addBatchToMaterialsProducerInventory(new BatchId(0), + TestMaterialsProducerId.MATERIALS_PRODUCER_1); + materialsBuilder.addBatch(new BatchId(1), TestMaterialId.MATERIAL_1, 11); + materialsBuilder.addBatchToMaterialsProducerInventory(new BatchId(1), + TestMaterialsProducerId.MATERIALS_PRODUCER_1); + materialsBuilder.addBatch(new BatchId(2), TestMaterialId.MATERIAL_2, 12); + materialsBuilder.addBatchToMaterialsProducerInventory(new BatchId(2), + TestMaterialsProducerId.MATERIALS_PRODUCER_2); + materialsBuilder.addBatch(new BatchId(3), TestMaterialId.MATERIAL_2, 13); + materialsBuilder.addBatch(new BatchId(4), TestMaterialId.MATERIAL_2, 14); + materialsBuilder.addStage(new StageId(0), false); + materialsBuilder.addStageToMaterialProducer(new StageId(0), TestMaterialsProducerId.MATERIALS_PRODUCER_2); + materialsBuilder.addBatchToStage(new StageId(0), new BatchId(3)); + materialsBuilder.addBatchToStage(new StageId(0), new BatchId(4)); + materialsBuilder.setBatchPropertyValue(new BatchId(0), + TestBatchPropertyId.BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, 77); + materialsBuilder.setBatchPropertyValue(new BatchId(1), + TestBatchPropertyId.BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, 56); + materialsBuilder.setBatchPropertyValue(new BatchId(4), + TestBatchPropertyId.BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK, 534); + MaterialsPluginData materialsPluginData = materialsBuilder.build(); + + ////////////////////////////////////////////////////// + + MaterialsProducerConstructionData materialsProducerConstructionData = MaterialsProducerConstructionData + .builder().setMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_3) + .setMaterialsProducerPropertyValue( + TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, true) + .setMaterialsProducerPropertyValue( + TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 21) + .build(); + List expectedStageIds = new ArrayList<>(); + + // run the sim with some actors that update a little of everything + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + materialsDataManager.setStageOfferState(stageId, false); + materialsDataManager.moveBatchToStage(new BatchId(0), stageId); + materialsDataManager.removeStage(stageId, false); + materialsDataManager.addMaterialsProducer(materialsProducerConstructionData); + stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_3); + expectedStageIds.add(stageId); + })); + TestPluginData testPluginData = pluginBuilder.build(); + + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 5818867905165255006L, testPluginData); + factory.setMaterialsPluginData(materialsPluginData); + TestOutputConsumer testOutputConsumer = TestSimulation.builder().addPlugins(factory.getPlugins()) + .setProduceSimulationStateOnHalt(true).setSimulationHaltTime(2).build().execute(); + Map outputItems = testOutputConsumer.getOutputItemMap(MaterialsPluginData.class); + assertEquals(1, outputItems.size()); + // build the expected materials plugin data + MaterialsPluginData materialsPluginData2 = outputItems.keySet().iterator().next(); + MaterialsPluginData.Builder expectedBuilder = MaterialsPluginData.builder(); + expectedBuilder.addMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + expectedBuilder.addMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_2); + propId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; + expectedBuilder.defineMaterialsProducerProperty(propId, propId.getPropertyDefinition()); + propId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK; + expectedBuilder.defineMaterialsProducerProperty(propId, propId.getPropertyDefinition()); + expectedBuilder.setMaterialsProducerPropertyValue(TestMaterialsProducerId.MATERIALS_PRODUCER_1, propId, 345); + expectedBuilder.setMaterialsProducerPropertyValue(TestMaterialsProducerId.MATERIALS_PRODUCER_2, propId, 5234); + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + for (TestResourceId testResourceId : TestResourceId.values()) { + expectedBuilder.setMaterialsProducerResourceLevel(testMaterialsProducerId, testResourceId, 0L); + } + } + + expectedBuilder.setMaterialsProducerResourceLevel(TestMaterialsProducerId.MATERIALS_PRODUCER_1, + TestResourceId.RESOURCE_1, 123L); + expectedBuilder.setMaterialsProducerResourceLevel(TestMaterialsProducerId.MATERIALS_PRODUCER_2, + TestResourceId.RESOURCE_2, 55L); + expectedBuilder.addMaterial(TestMaterialId.MATERIAL_1); + expectedBuilder.addMaterial(TestMaterialId.MATERIAL_2); + bprop = TestBatchPropertyId.BATCH_PROPERTY_1_1_BOOLEAN_IMMUTABLE_NO_TRACK; + expectedBuilder.defineBatchProperty(TestMaterialId.MATERIAL_1, bprop, bprop.getPropertyDefinition()); + bprop = TestBatchPropertyId.BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK; + expectedBuilder.defineBatchProperty(TestMaterialId.MATERIAL_1, bprop, bprop.getPropertyDefinition()); + bprop = TestBatchPropertyId.BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK; + expectedBuilder.defineBatchProperty(TestMaterialId.MATERIAL_2, bprop, bprop.getPropertyDefinition()); + expectedBuilder.addBatch(new BatchId(0), TestMaterialId.MATERIAL_1, 10); + expectedBuilder.addBatchToMaterialsProducerInventory(new BatchId(0), + TestMaterialsProducerId.MATERIALS_PRODUCER_1); + expectedBuilder.addBatch(new BatchId(1), TestMaterialId.MATERIAL_1, 11); + expectedBuilder.addBatchToMaterialsProducerInventory(new BatchId(1), + TestMaterialsProducerId.MATERIALS_PRODUCER_1); + expectedBuilder.addBatch(new BatchId(2), TestMaterialId.MATERIAL_2, 12); + expectedBuilder.addBatchToMaterialsProducerInventory(new BatchId(2), + TestMaterialsProducerId.MATERIALS_PRODUCER_2); + expectedBuilder.addBatch(new BatchId(3), TestMaterialId.MATERIAL_2, 13); + expectedBuilder.addBatch(new BatchId(4), TestMaterialId.MATERIAL_2, 14); + expectedBuilder.addStage(new StageId(0), false); + expectedBuilder.addStageToMaterialProducer(new StageId(0), TestMaterialsProducerId.MATERIALS_PRODUCER_2); + expectedBuilder.addStage(expectedStageIds.get(0), false); + expectedBuilder.addStageToMaterialProducer(expectedStageIds.get(0), + TestMaterialsProducerId.MATERIALS_PRODUCER_3); + expectedBuilder.addBatchToStage(new StageId(0), new BatchId(3)); + expectedBuilder.addBatchToStage(new StageId(0), new BatchId(4)); + expectedBuilder.setBatchPropertyValue(new BatchId(0), + TestBatchPropertyId.BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, 77); + expectedBuilder.setBatchPropertyValue(new BatchId(1), + TestBatchPropertyId.BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, 56); + expectedBuilder.setBatchPropertyValue(new BatchId(4), + TestBatchPropertyId.BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK, 534); + expectedBuilder.addMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_3); + expectedBuilder.setMaterialsProducerPropertyValue(TestMaterialsProducerId.MATERIALS_PRODUCER_3, + TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, true); + expectedBuilder.setMaterialsProducerPropertyValue(TestMaterialsProducerId.MATERIALS_PRODUCER_3, + TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 21); + MaterialsPluginData expectedPluginData = expectedBuilder.build(); + + // compare via equals + assertEquals(expectedPluginData, materialsPluginData2); + + // Show that plugin data persists after actions at different times + materialsBuilder = MaterialsPluginData.builder(); + + materialsBuilder.addMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + materialsBuilder.addMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_2); + propId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; + materialsBuilder.defineMaterialsProducerProperty(propId, propId.getPropertyDefinition()); + propId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK; + materialsBuilder.defineMaterialsProducerProperty(propId, propId.getPropertyDefinition()); + materialsBuilder.setMaterialsProducerPropertyValue(TestMaterialsProducerId.MATERIALS_PRODUCER_1, propId, 345); + materialsBuilder.setMaterialsProducerPropertyValue(TestMaterialsProducerId.MATERIALS_PRODUCER_2, propId, 5234); + materialsBuilder.setMaterialsProducerResourceLevel(TestMaterialsProducerId.MATERIALS_PRODUCER_1, + TestResourceId.RESOURCE_1, 123L); + materialsBuilder.setMaterialsProducerResourceLevel(TestMaterialsProducerId.MATERIALS_PRODUCER_2, + TestResourceId.RESOURCE_2, 55L); + materialsBuilder.addMaterial(TestMaterialId.MATERIAL_1); + materialsBuilder.addMaterial(TestMaterialId.MATERIAL_2); + bprop = TestBatchPropertyId.BATCH_PROPERTY_1_1_BOOLEAN_IMMUTABLE_NO_TRACK; + materialsBuilder.defineBatchProperty(TestMaterialId.MATERIAL_1, bprop, bprop.getPropertyDefinition()); + bprop = TestBatchPropertyId.BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK; + materialsBuilder.defineBatchProperty(TestMaterialId.MATERIAL_1, bprop, bprop.getPropertyDefinition()); + bprop = TestBatchPropertyId.BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK; + materialsBuilder.defineBatchProperty(TestMaterialId.MATERIAL_2, bprop, bprop.getPropertyDefinition()); + materialsBuilder.addBatch(new BatchId(0), TestMaterialId.MATERIAL_1, 10); + materialsBuilder.addBatchToMaterialsProducerInventory(new BatchId(0), + TestMaterialsProducerId.MATERIALS_PRODUCER_1); + materialsBuilder.addBatch(new BatchId(1), TestMaterialId.MATERIAL_1, 11); + materialsBuilder.addBatchToMaterialsProducerInventory(new BatchId(1), + TestMaterialsProducerId.MATERIALS_PRODUCER_1); + materialsBuilder.addBatch(new BatchId(2), TestMaterialId.MATERIAL_2, 12); + materialsBuilder.addBatchToMaterialsProducerInventory(new BatchId(2), + TestMaterialsProducerId.MATERIALS_PRODUCER_2); + materialsBuilder.addBatch(new BatchId(3), TestMaterialId.MATERIAL_2, 13); + materialsBuilder.addBatch(new BatchId(4), TestMaterialId.MATERIAL_2, 14); + materialsBuilder.addStage(new StageId(0), false); + materialsBuilder.addStageToMaterialProducer(new StageId(0), TestMaterialsProducerId.MATERIALS_PRODUCER_2); + materialsBuilder.addBatchToStage(new StageId(0), new BatchId(3)); + materialsBuilder.addBatchToStage(new StageId(0), new BatchId(4)); + materialsBuilder.defineBatchProperty(TestMaterialId.MATERIAL_2, + TestBatchPropertyId.BATCH_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK, + TestBatchPropertyId.BATCH_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK.getPropertyDefinition()); + materialsBuilder.setBatchPropertyValue(new BatchId(0), + TestBatchPropertyId.BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, 77); + materialsBuilder.setBatchPropertyValue(new BatchId(1), + TestBatchPropertyId.BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, 56); + materialsBuilder.setBatchPropertyValue(new BatchId(4), + TestBatchPropertyId.BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK, 534); + materialsPluginData = materialsBuilder.build(); + + // run the sim with some actors that update a little of everything + pluginBuilder = TestPluginData.builder(); + + // run the sim with some actors that update a little of everything + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + materialsDataManager.setStageOfferState(stageId, false); + materialsDataManager.moveBatchToStage(new BatchId(0), stageId); + materialsDataManager.removeStage(stageId, false); + materialsDataManager.addMaterialsProducer(materialsProducerConstructionData); + stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_3); + expectedStageIds.add(stageId); + })); + + BatchConstructionInfo batchConstructionInfo = BatchConstructionInfo.builder() + .setMaterialId(TestMaterialId.MATERIAL_2) + .setMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_2) + .setPropertyValue(TestBatchPropertyId.BATCH_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK, true).setAmount(58.9) + .build(); + + List expectedBatchIds = new ArrayList<>(); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + expectedBatchIds.add(batchId); + materialsDataManager.removeBatch(new BatchId(0)); + materialsDataManager.setMaterialsProducerPropertyValue(TestMaterialsProducerId.MATERIALS_PRODUCER_1, + TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 73); + materialsDataManager.setBatchPropertyValue(new BatchId(3), + TestBatchPropertyId.BATCH_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK, true); + })); + + testPluginData = pluginBuilder.build(); + + factory = MaterialsTestPluginFactory.factory(0, 0, 0, 5818867905165255006L, testPluginData); + factory.setMaterialsPluginData(materialsPluginData); + testOutputConsumer = TestSimulation.builder().addPlugins(factory.getPlugins()) + .setProduceSimulationStateOnHalt(true).setSimulationHaltTime(2).build().execute(); + outputItems = testOutputConsumer.getOutputItemMap(MaterialsPluginData.class); + assertEquals(1, outputItems.size()); + // build the expected materials plugin data + materialsPluginData2 = outputItems.keySet().iterator().next(); + expectedBuilder = MaterialsPluginData.builder(); + expectedBuilder.addMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + expectedBuilder.addMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_2); + propId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; + expectedBuilder.defineMaterialsProducerProperty(propId, propId.getPropertyDefinition()); + propId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK; + expectedBuilder.defineMaterialsProducerProperty(propId, propId.getPropertyDefinition()); + expectedBuilder.setMaterialsProducerPropertyValue(TestMaterialsProducerId.MATERIALS_PRODUCER_1, propId, 73); + expectedBuilder.setMaterialsProducerPropertyValue(TestMaterialsProducerId.MATERIALS_PRODUCER_2, propId, 5234); + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + for (TestResourceId testResourceId : TestResourceId.values()) { + expectedBuilder.setMaterialsProducerResourceLevel(testMaterialsProducerId, testResourceId, 0L); + } + } + + expectedBuilder.setMaterialsProducerResourceLevel(TestMaterialsProducerId.MATERIALS_PRODUCER_1, + TestResourceId.RESOURCE_1, 123L); + expectedBuilder.setMaterialsProducerResourceLevel(TestMaterialsProducerId.MATERIALS_PRODUCER_2, + TestResourceId.RESOURCE_2, 55L); + expectedBuilder.addMaterial(TestMaterialId.MATERIAL_1); + expectedBuilder.addMaterial(TestMaterialId.MATERIAL_2); + bprop = TestBatchPropertyId.BATCH_PROPERTY_1_1_BOOLEAN_IMMUTABLE_NO_TRACK; + expectedBuilder.defineBatchProperty(TestMaterialId.MATERIAL_1, bprop, bprop.getPropertyDefinition()); + bprop = TestBatchPropertyId.BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK; + expectedBuilder.defineBatchProperty(TestMaterialId.MATERIAL_1, bprop, bprop.getPropertyDefinition()); + bprop = TestBatchPropertyId.BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK; + expectedBuilder.defineBatchProperty(TestMaterialId.MATERIAL_2, bprop, bprop.getPropertyDefinition()); + bprop = TestBatchPropertyId.BATCH_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK; + expectedBuilder.defineBatchProperty(TestMaterialId.MATERIAL_2, bprop, bprop.getPropertyDefinition()); + expectedBuilder.addBatch(new BatchId(1), TestMaterialId.MATERIAL_1, 11); + expectedBuilder.addBatchToMaterialsProducerInventory(new BatchId(1), + TestMaterialsProducerId.MATERIALS_PRODUCER_1); + expectedBuilder.addBatch(new BatchId(2), TestMaterialId.MATERIAL_2, 12); + expectedBuilder.addBatchToMaterialsProducerInventory(new BatchId(2), + TestMaterialsProducerId.MATERIALS_PRODUCER_2); + expectedBuilder.addBatch(new BatchId(3), TestMaterialId.MATERIAL_2, 13); + expectedBuilder.addBatch(new BatchId(4), TestMaterialId.MATERIAL_2, 14); + expectedBuilder.addBatch(expectedBatchIds.get(0), TestMaterialId.MATERIAL_2, 58.9); + expectedBuilder.addBatchToMaterialsProducerInventory(expectedBatchIds.get(0), + TestMaterialsProducerId.MATERIALS_PRODUCER_2); + expectedBuilder.addStage(new StageId(0), false); + expectedBuilder.addStageToMaterialProducer(new StageId(0), TestMaterialsProducerId.MATERIALS_PRODUCER_2); + + expectedBuilder.addStage(expectedStageIds.get(0), false); + expectedBuilder.addStageToMaterialProducer(expectedStageIds.get(0), + TestMaterialsProducerId.MATERIALS_PRODUCER_3); + + expectedBuilder.addBatchToStage(new StageId(0), new BatchId(3)); + expectedBuilder.addBatchToStage(new StageId(0), new BatchId(4)); + + expectedBuilder.setBatchPropertyValue(new BatchId(1), + TestBatchPropertyId.BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, 56); + expectedBuilder.setBatchPropertyValue(new BatchId(4), + TestBatchPropertyId.BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK, 534); + expectedBuilder.setBatchPropertyValue(new BatchId(3), + TestBatchPropertyId.BATCH_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK, true); + expectedBuilder.setBatchPropertyValue(new BatchId(5), + TestBatchPropertyId.BATCH_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK, true); + expectedBuilder.addMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_3); + expectedBuilder.setMaterialsProducerPropertyValue(TestMaterialsProducerId.MATERIALS_PRODUCER_3, + TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, true); + expectedBuilder.setMaterialsProducerPropertyValue(TestMaterialsProducerId.MATERIALS_PRODUCER_3, + TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 21); + expectedPluginData = expectedBuilder.build(); + + // compare via equals + assertEquals(expectedPluginData, materialsPluginData2); + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "addBatch", args = { BatchConstructionInfo.class }) + public void testAddBatch() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create containers to hold observations + Set expectedBatchObservations = new LinkedHashSet<>(); + Set actualBatchObservations = new LinkedHashSet<>(); + + /* create an observer actor that will observe the batch creations */ + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + c.subscribe(EventFilter.builder(BatchAdditionEvent.class).build(), (c2, e) -> { + actualBatchObservations.add(e.batchId()); + }); + })); + + // create some batches and show that their various features are correct + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + for (int i = 0; i < 20; i++) { + BatchConstructionInfo.Builder builder = BatchConstructionInfo.builder(); + TestMaterialId testMaterialId = TestMaterialId.getRandomMaterialId(randomGenerator); + builder.setMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + builder.setMaterialId(testMaterialId); + double amount = randomGenerator.nextDouble(); + builder.setAmount(amount);// + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId + .getTestBatchPropertyIds(testMaterialId)) { + builder.setPropertyValue(testBatchPropertyId, + testBatchPropertyId.getRandomPropertyValue(randomGenerator)); + } + BatchConstructionInfo batchConstructionInfo = builder.build();// + + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + assertEquals(testMaterialId, materialsDataManager.getBatchMaterial(batchId)); + assertEquals(amount, materialsDataManager.getBatchAmount(batchId)); + + expectedBatchObservations.add(batchId); + } + + })); + + /* + * have the observer show that the observations are properly generated + */ + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(2, (c) -> { + assertTrue(expectedBatchObservations.size() > 0); + assertEquals(expectedBatchObservations, actualBatchObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 5818867905165255006L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the materials producer is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 5818867905165255006L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + BatchConstructionInfo.Builder builder = BatchConstructionInfo.builder(); + builder.setMaterialId(TestMaterialId.MATERIAL_1); + builder.setAmount(0.1234); + BatchConstructionInfo batchConstructionInfo = builder.build(); + materialsDataManager.addBatch(batchConstructionInfo); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + /* precondition test: if the materials producer is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 7126499343584962390L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + BatchConstructionInfo.Builder builder = BatchConstructionInfo.builder(); + builder.setMaterialId(TestMaterialId.MATERIAL_1); + builder.setAmount(0.1234); + builder.setMaterialsProducerId(TestMaterialsProducerId.getUnknownMaterialsProducerId()); + BatchConstructionInfo batchConstructionInfo = builder.build(); + materialsDataManager.addBatch(batchConstructionInfo); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + /* + * precondition test: if the batch construction info in the event is null + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 6921778272119512748L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.addBatch(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_BATCH_CONSTRUCTION_INFO, contractException.getErrorType()); + + /* + * precondition test: if the material id in the batch construction info is null + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 3677913497762052761L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + BatchConstructionInfo.Builder builder = BatchConstructionInfo.builder(); + builder.setAmount(0.1234); + builder.setMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + BatchConstructionInfo batchConstructionInfo = builder.build(); + materialsDataManager.addBatch(batchConstructionInfo); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_MATERIAL_ID, contractException.getErrorType()); + + /* + * precondition test: if the material id in the batch construction info is + * unknown + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 6823349146270705865L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + BatchConstructionInfo.Builder builder = BatchConstructionInfo.builder(); + builder.setMaterialId(TestMaterialId.getUnknownMaterialId()); + builder.setAmount(0.1234); + builder.setMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_3); + BatchConstructionInfo batchConstructionInfo = builder.build(); + materialsDataManager.addBatch(batchConstructionInfo); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_MATERIAL_ID, contractException.getErrorType()); + + /* + * precondition test: if the amount in the batch construction info is not finite + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 1740687746013988916L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + BatchConstructionInfo.Builder builder = BatchConstructionInfo.builder(); + builder.setMaterialId(TestMaterialId.MATERIAL_1); + builder.setAmount(Double.POSITIVE_INFINITY); + builder.setMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + BatchConstructionInfo batchConstructionInfo = builder.build(); + materialsDataManager.addBatch(batchConstructionInfo); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NON_FINITE_MATERIAL_AMOUNT, contractException.getErrorType()); + + /* + * precondition test: if the amount in the batch construction info is negative + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 3552750401177629416L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + BatchConstructionInfo.Builder builder = BatchConstructionInfo.builder(); + builder.setMaterialId(TestMaterialId.MATERIAL_1); + builder.setAmount(-1.0); + builder.setMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_2); + BatchConstructionInfo batchConstructionInfo = builder.build(); + materialsDataManager.addBatch(batchConstructionInfo); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NEGATIVE_MATERIAL_AMOUNT, contractException.getErrorType()); + + /* + * precondition test: if the batch construction info contains a null batch + * property id + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 2067301487300157385L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + BatchConstructionInfo.Builder builder = BatchConstructionInfo.builder(); + builder.setMaterialId(TestMaterialId.MATERIAL_1); + builder.setAmount(0.1234); + builder.setPropertyValue(null, 15); + builder.setMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + BatchConstructionInfo batchConstructionInfo = builder.build(); + materialsDataManager.addBatch(batchConstructionInfo); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + /* + * precondition test: if the batch construction info contains an unknown batch + * property id + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 3866738227501386466L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + BatchConstructionInfo.Builder builder = BatchConstructionInfo.builder(); + builder.setMaterialId(TestMaterialId.MATERIAL_1); + builder.setAmount(0.1234); + builder.setMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + builder.setPropertyValue(TestBatchPropertyId.getUnknownBatchPropertyId(), 15); + BatchConstructionInfo batchConstructionInfo = builder.build(); + materialsDataManager.addBatch(batchConstructionInfo); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + /* + * precondition test: if the batch construction info contains a batch property + * value that is incompatible with the corresponding property def + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 1224987903432629856L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + BatchConstructionInfo.Builder builder = BatchConstructionInfo.builder(); + builder.setMaterialId(TestMaterialId.MATERIAL_1); + builder.setAmount(0.1234); + builder.setMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + builder.setPropertyValue(TestBatchPropertyId.BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, 2.3); + BatchConstructionInfo batchConstructionInfo = builder.build(); + materialsDataManager.addBatch(batchConstructionInfo); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); + + /* + * precondition test: if the batch construction info contains a batch property + * value that is incompatible with the corresponding property def + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 8126846490003696164L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + BatchConstructionInfo.Builder builder = BatchConstructionInfo.builder(); + builder.setMaterialId(TestMaterialId.MATERIAL_1); + builder.setAmount(0.1234); + builder.setMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + builder.setPropertyValue(TestBatchPropertyId.BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, 2.3); + BatchConstructionInfo batchConstructionInfo = builder.build(); + materialsDataManager.addBatch(batchConstructionInfo); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "addStage", args = { MaterialsProducerId.class }) + public void testAddStage() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + double actionTime = 0; + + // create containers to hold observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // have a actor observe stage creations + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + c.subscribe(EventFilter.builder(StageAdditionEvent.class).build(), (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.stageId())); + }); + })); + + // produce stages at various times + for (int i = 0; i < 10; i++) { + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StageId stageId = materialsDataManager.addStage(testMaterialsProducerId); + + // show that the stage exists and belongs to the producer + assertTrue(materialsDataManager.stageExists(stageId)); + assertEquals(testMaterialsProducerId, materialsDataManager.getStageProducer(stageId)); + + // generated expected observations + expectedObservations.add(new MultiKey(c.getTime(), stageId)); + })); + } + } + + // have the observer show that the observations are correct + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + assertEquals(10 * TestMaterialsProducerId.values().length, expectedObservations.size()); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 1344617610771747654L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test if the materials producer is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 2938510662832987631L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.addStage(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + /* precondition test if the materials producer is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 3333157817809403586L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.addStage(TestMaterialsProducerId.getUnknownMaterialsProducerId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "batchExists", args = { BatchId.class }) + public void testBatchExists() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + /* + * Create a data structure to hold batch ids that have been removed and require + * flow of control to leave the actor before removal can be confirmed + */ + Map> removalConfirmationBatches = new LinkedHashMap<>(); + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + removalConfirmationBatches.put(testMaterialsProducerId, new LinkedHashSet<>()); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + Set confimationBatches = removalConfirmationBatches.get(testMaterialsProducerId); + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + double value = randomGenerator.nextDouble() * 100; + // add the batch an show it exists + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo + .getBatchConstructionInfo(testMaterialsProducerId, testMaterialId, value, randomGenerator); + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + assertTrue(materialsDataManager.batchExists(batchId)); + // remove the batch and show that it still exists + materialsDataManager.removeBatch(batchId); + assertTrue(materialsDataManager.batchExists(batchId)); + confimationBatches.add(batchId); + } + // show that null and unknown batch ids return false + assertFalse(materialsDataManager.batchExists(null)); + assertFalse(materialsDataManager.batchExists(new BatchId(10000000))); + + })); + + // show that the batches removed above are now gone + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + Set confimationBatches = removalConfirmationBatches.get(testMaterialsProducerId); + for (BatchId batchId : confimationBatches) { + assertFalse(materialsDataManager.batchExists(batchId)); + } + })); + } + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 3680467733415023569L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "batchPropertyIdExists", args = { MaterialId.class, + BatchPropertyId.class }) + public void testBatchPropertyIdExists() { + + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 4250860228077588132L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.values()) { + MaterialId associatedMaterialId = testBatchPropertyId.getTestMaterialId(); + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + if (testMaterialId.equals(associatedMaterialId)) { + assertTrue(materialsDataManager.batchPropertyIdExists(testMaterialId, testBatchPropertyId)); + } else { + assertFalse(materialsDataManager.batchPropertyIdExists(testMaterialId, testBatchPropertyId)); + } + } + } + + assertFalse(materialsDataManager.batchPropertyIdExists(TestMaterialId.MATERIAL_1, null)); + assertFalse(materialsDataManager.batchPropertyIdExists(null, + TestBatchPropertyId.BATCH_PROPERTY_1_1_BOOLEAN_IMMUTABLE_NO_TRACK)); + assertFalse(materialsDataManager.batchPropertyIdExists(null, null)); + + // precondition tests : none + + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "convertStageToBatch", args = { + StageConversionInfo.class }) + public void testConvertStageToBatch() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + double actionTime = 0; + + // create containers to hold observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // have an actor observe + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + + c.subscribe(EventFilter.builder(BatchImminentRemovalEvent.class).build(), (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.batchId(), "removal")); + }); + + c.subscribe(EventFilter.builder(BatchAdditionEvent.class).build(), (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.batchId(), "creation")); + }); + + c.subscribe(EventFilter.builder(StageImminentRemovalEvent.class).build(), (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.stageId())); + }); + + })); + + // have the producers generate batches via stage conversion + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + + List stagesToConfirm = new ArrayList<>(); + List batchesToConfirm = new ArrayList<>(); + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + for (int i = 0; i < 50; i++) { + StageId stageId = materialsDataManager.addStage(testMaterialsProducerId); + + TestMaterialId materialId; + double amount; + int batchCount = randomGenerator.nextInt(3); + for (int j = 0; j < batchCount; j++) { + materialId = TestMaterialId.getRandomMaterialId(randomGenerator); + amount = randomGenerator.nextDouble() + 0.01; + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo + .getBatchConstructionInfo(testMaterialsProducerId, materialId, amount, randomGenerator); + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + materialsDataManager.moveBatchToStage(batchId, stageId); + } + materialId = TestMaterialId.getRandomMaterialId(randomGenerator); + amount = randomGenerator.nextDouble() + 0.01; + List stageBatches = materialsDataManager.getStageBatches(stageId); + + StageConversionInfo.Builder stageConversionInfoBuilder = // + StageConversionInfo.builder().setStageId(stageId)// + .setMaterialId(materialId)// + .setAmount(amount);// + + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId + .getTestBatchPropertyIds(materialId)) { + if (testBatchPropertyId.getPropertyDefinition().getDefaultValue().isEmpty()) { + Object propertyValue = testBatchPropertyId.getRandomPropertyValue(randomGenerator); + stageConversionInfoBuilder.setPropertyValue(testBatchPropertyId, propertyValue); + } + } + + StageConversionInfo stageConversionInfo = stageConversionInfoBuilder.build(); + BatchId producedBatchId = materialsDataManager.convertStageToBatch(stageConversionInfo); + + // record the stages and batches that should be removed, but + // only after the current actor activation + stagesToConfirm.add(stageId); + batchesToConfirm.addAll(stageBatches); + + // show that the stage was properly converted + assertTrue(materialsDataManager.batchExists(producedBatchId)); + assertEquals(materialId, materialsDataManager.getBatchMaterial(producedBatchId)); + assertEquals(amount, materialsDataManager.getBatchAmount(producedBatchId)); + + // generate the expected observations + for (BatchId batchId : stageBatches) { + expectedObservations.add(new MultiKey(c.getTime(), batchId, "creation")); + expectedObservations.add(new MultiKey(c.getTime(), batchId, "removal")); + } + expectedObservations.add(new MultiKey(c.getTime(), producedBatchId, "creation")); + expectedObservations.add(new MultiKey(c.getTime(), stageId)); + } + })); + + // show that the stages and batches used to generate the new batches + // were in fact removed + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + for (StageId stageId : stagesToConfirm) { + assertFalse(materialsDataManager.stageExists(stageId)); + } + for (BatchId batchId : batchesToConfirm) { + assertFalse(materialsDataManager.batchExists(batchId)); + } + })); + } + + // have the observer show that the correct observations were generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 855059044560726814L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the material id is unknown */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 5132874324434783837L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + double amount = 12.5; + StageConversionInfo stageConversionInfo = StageConversionInfo.builder()// + .setStageId(stageId)// + .setMaterialId(TestMaterialId.getUnknownMaterialId())// + .setAmount(amount)// + .build(); + materialsDataManager.convertStageToBatch(stageConversionInfo); + + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_MATERIAL_ID, contractException.getErrorType()); + + /* precondition test: if stage id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 6716241372071908817L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialId materialId = TestMaterialId.MATERIAL_2; + double amount = 12.5; + StageConversionInfo stageConversionInfo = StageConversionInfo.builder()// + .setStageId(new StageId(10000000))// + .setMaterialId(materialId)// + .setAmount(amount)// + .build(); + materialsDataManager.convertStageToBatch(stageConversionInfo); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + + }); + assertEquals(MaterialsError.UNKNOWN_STAGE_ID, contractException.getErrorType()); + + /* precondition test: if the stage is offered */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 696988531477059866L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + MaterialId materialId = TestMaterialId.MATERIAL_2; + double amount = 12.5; + materialsDataManager.setStageOfferState(stageId, true); + StageConversionInfo stageConversionInfo = StageConversionInfo.builder()// + .setStageId(stageId)// + .setMaterialId(materialId)// + .setAmount(amount)// + .build(); + materialsDataManager.convertStageToBatch(stageConversionInfo); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + + }); + assertEquals(MaterialsError.OFFERED_STAGE_UNALTERABLE, contractException.getErrorType()); + + /* precondition test: if the stage conversion info is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 5132874324434783837L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.convertStageToBatch(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_STAGE_CONVERSION_INFO, contractException.getErrorType()); + + /* + * precondition test: if the batch construction info contains an unknown batch + * property id + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 696988531477059866L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + MaterialId materialId = TestMaterialId.MATERIAL_2; + double amount = 12.5; + StageConversionInfo stageConversionInfo = StageConversionInfo.builder()// + .setStageId(stageId)// + .setMaterialId(materialId)// + .setAmount(amount)// + .setPropertyValue(TestBatchPropertyId.getUnknownBatchPropertyId(), 3)// + .build(); + materialsDataManager.convertStageToBatch(stageConversionInfo); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + /* + * precondition test: if the batch construction info contains a batch property + * value that is incompatible with the corresponding property def + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 696988531477059866L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + MaterialId materialId = TestMaterialId.MATERIAL_2; + double amount = 12.5; + StageConversionInfo stageConversionInfo = StageConversionInfo.builder()// + .setStageId(stageId)// + .setMaterialId(materialId)// + .setAmount(amount)// + .setPropertyValue(TestBatchPropertyId.BATCH_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK, 3)// + .build(); + materialsDataManager.convertStageToBatch(stageConversionInfo); + + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); + + /* + * precondition test: if the batch construction does not contain a batch + * property value assignment for a batch property that does not have a default + * value + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 696988531477059866L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + MaterialId materialId = TestMaterialId.MATERIAL_1; + double amount = 12.5; + StageConversionInfo stageConversionInfo = StageConversionInfo.builder()// + .setStageId(stageId)// + .setMaterialId(materialId)// + .setAmount(amount)// + .build(); + materialsDataManager.convertStageToBatch(stageConversionInfo); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getBatchAmount", args = { BatchId.class }) + public void testGetBatchAmount() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + for (int i = 0; i < 100; i++) { + TestMaterialId testMaterialId = TestMaterialId.getRandomMaterialId(randomGenerator); + double value = randomGenerator.nextDouble() * 100; + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_1, testMaterialId, value, randomGenerator); + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + + // show that the batch matches the inputs + assertEquals(value, materialsDataManager.getBatchAmount(batchId)); + } + + // precondition tests : none + + // if the batch id is null + ContractException contractException = assertThrows(ContractException.class, + () -> materialsDataManager.getBatchAmount(null)); + assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); + + // if the batch id is unknown + contractException = assertThrows(ContractException.class, + () -> materialsDataManager.getBatchAmount(new BatchId(10000000))); + assertEquals(MaterialsError.UNKNOWN_BATCH_ID, contractException.getErrorType()); + + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 1333558356470864456L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getBatchMaterial", args = { BatchId.class }) + public void testGetBatchMaterial() { + + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 2922188778885130752L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + for (int i = 0; i < 100; i++) { + TestMaterialId testMaterialId = TestMaterialId.getRandomMaterialId(randomGenerator); + double value = randomGenerator.nextDouble() * 100; + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_2, testMaterialId, value, randomGenerator); + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + + // show that the batch matches the inputs + assertEquals(testMaterialId, materialsDataManager.getBatchMaterial(batchId)); + } + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the batch id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 8694113802920961598L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.getBatchMaterial(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); + + /* precondition test: if the batch id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 6524569565798029395L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.getBatchMaterial(new BatchId(10000000)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_BATCH_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getBatchProducer", args = { BatchId.class }) + public void testGetBatchProducer() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + for (int i = 0; i < 10; i++) { + + TestMaterialId testMaterialId = TestMaterialId.getRandomMaterialId(randomGenerator); + double value = randomGenerator.nextDouble() * 100; + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo + .getBatchConstructionInfo(testMaterialsProducerId, testMaterialId, value, randomGenerator); + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + + // show that the batch matches the inputs + assertEquals(testMaterialsProducerId, materialsDataManager.getBatchProducer(batchId)); + } + + })); + } + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 8873616248377004295L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test : if the batch id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 1422948417739515067L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.getBatchProducer(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); + + /* precondition test : if the batch id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 6083037726892077495L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.getBatchProducer(new BatchId(10000000)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_BATCH_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getBatchPropertyDefinition", args = { MaterialId.class, + BatchPropertyId.class }) + public void testGetBatchPropertyDefinition() { + + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 4785939121817102392L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + + Set testBatchPropertyIds = TestBatchPropertyId + .getTestBatchPropertyIds(testMaterialId); + for (TestBatchPropertyId testBatchPropertyId : testBatchPropertyIds) { + PropertyDefinition expectedPropertyDefinition = testBatchPropertyId.getPropertyDefinition(); + PropertyDefinition actualPropertyDefinition = materialsDataManager + .getBatchPropertyDefinition(testMaterialId, testBatchPropertyId); + assertEquals(expectedPropertyDefinition, actualPropertyDefinition); + } + } + + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition tests if the material id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 5856664286545303775L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + TestBatchPropertyId testBatchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK; + materialsDataManager.getBatchPropertyDefinition(null, testBatchPropertyId); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_MATERIAL_ID, contractException.getErrorType()); + + /* precondition tests if the material id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 3682262623372578238L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + TestBatchPropertyId testBatchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK; + materialsDataManager.getBatchPropertyDefinition(TestMaterialId.getUnknownMaterialId(), + testBatchPropertyId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_MATERIAL_ID, contractException.getErrorType()); + + /* precondition tests if the batch property id is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 2977320444281387466L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.getBatchPropertyDefinition(TestMaterialId.MATERIAL_1, null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + /* precondition tests if the batch property id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 712791219730643932L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.getBatchPropertyDefinition(TestMaterialId.MATERIAL_1, + TestBatchPropertyId.getUnknownBatchPropertyId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getBatchPropertyIds", args = { MaterialId.class }) + public void testGetBatchPropertyIds() { + + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 8657082858154514151L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + Set expectedBatchPropertyIds = TestBatchPropertyId + .getTestBatchPropertyIds(testMaterialId); + Set actualBatchPropertyIds = materialsDataManager.getBatchPropertyIds(testMaterialId); + assertEquals(expectedBatchPropertyIds, actualBatchPropertyIds); + } + + // precondition tests + + // if the material id is null + ContractException contractException = assertThrows(ContractException.class, + () -> materialsDataManager.getBatchPropertyIds(null)); + assertEquals(MaterialsError.NULL_MATERIAL_ID, contractException.getErrorType()); + + // if the material id is unknown + contractException = assertThrows(ContractException.class, + () -> materialsDataManager.getBatchPropertyIds(TestMaterialId.getUnknownMaterialId())); + assertEquals(MaterialsError.UNKNOWN_MATERIAL_ID, contractException.getErrorType()); + + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the material id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 6822125249787156609L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.getBatchPropertyIds(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_MATERIAL_ID, contractException.getErrorType()); + + /* precondition test: if the material id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 7025275053813907413L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.getBatchPropertyIds(TestMaterialId.getUnknownMaterialId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_MATERIAL_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getBatchPropertyValue", args = { BatchId.class, + BatchPropertyId.class }) + public void testGetBatchPropertyValue() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + /* + * create a data structure to hold the assignments we expect to retrieve. + */ + + Map expectedValues = new LinkedHashMap<>(); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + + /* + * Have the actor add 50 randomized batches and record the values for all + * properties + */ + + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + // create a few batches + for (int i = 0; i < 50; i++) { + + MaterialId materialId = TestMaterialId.getRandomMaterialId(randomGenerator); + double amount = randomGenerator.nextDouble(); + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_2, materialId, amount, randomGenerator); + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + + Set batchPropertyIds = materialsDataManager.getBatchPropertyIds(materialId); + for (TestBatchPropertyId batchPropertyId : batchPropertyIds) { + Object value = materialsDataManager.getBatchPropertyValue(batchId, batchPropertyId); + expectedValues.put(new MultiKey(batchId, batchPropertyId), value); + } + } + + // alter randomly chosen batch properties + + for (int i = 0; i < 200; i++) { + + List inventoryBatches = materialsDataManager + .getInventoryBatches(TestMaterialsProducerId.MATERIALS_PRODUCER_2); + BatchId batchId = inventoryBatches.get(randomGenerator.nextInt(inventoryBatches.size())); + TestMaterialId materialId = materialsDataManager.getBatchMaterial(batchId); + TestBatchPropertyId batchPropertyId = TestBatchPropertyId.getRandomMutableBatchPropertyId(materialId, + randomGenerator); + Object value = batchPropertyId.getRandomPropertyValue(randomGenerator); + materialsDataManager.setBatchPropertyValue(batchId, batchPropertyId, value); + expectedValues.put(new MultiKey(batchId, batchPropertyId), value); + + } + + })); + + // have an actor show that the batches have the expected property values + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + for (MaterialsProducerId materialsProducerId : materialsDataManager.getMaterialsProducerIds()) { + List inventoryBatches = materialsDataManager.getInventoryBatches(materialsProducerId); + for (BatchId batchId : inventoryBatches) { + MaterialId materialId = materialsDataManager.getBatchMaterial(batchId); + Set batchPropertyIds = materialsDataManager.getBatchPropertyIds(materialId); + for (TestBatchPropertyId batchPropertyId : batchPropertyIds) { + Object expectedValue = expectedValues.get(new MultiKey(batchId, batchPropertyId)); + Object actualValue = materialsDataManager.getBatchPropertyValue(batchId, batchPropertyId); + assertEquals(expectedValue, actualValue); + } + } + } + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 1629075115765446254L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the batch id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 7782292483170344303L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + BatchPropertyId batchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_1_1_BOOLEAN_IMMUTABLE_NO_TRACK; + materialsDataManager.getBatchPropertyValue(null, batchPropertyId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); + + /* precondition test: if the batch id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 2235610256211958684L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + BatchPropertyId batchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_1_1_BOOLEAN_IMMUTABLE_NO_TRACK; + materialsDataManager.getBatchPropertyValue(new BatchId(100000), batchPropertyId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_BATCH_ID, contractException.getErrorType()); + + /* precondition test: if the batch property id is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 4276253162944402582L, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_2, 45L, randomGenerator); + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + materialsDataManager.getBatchPropertyValue(batchId, null); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + /* precondition test: if the batch property id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 211483511977100214L, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_2, 45L, randomGenerator); + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + materialsDataManager.getBatchPropertyValue(batchId, TestBatchPropertyId.getUnknownBatchPropertyId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getBatchStageId", args = { BatchId.class }) + public void testGetBatchStageId() { + + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 472707250737446845L, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + // create a stage and a batch + StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_3); + + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_3, TestMaterialId.MATERIAL_2, 4.5, randomGenerator); + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + + // show that the batch does not have a stage + Optional optionalOwningStageId = materialsDataManager.getBatchStageId(batchId); + assertFalse(optionalOwningStageId.isPresent()); + + // put the batch onto the stage + materialsDataManager.moveBatchToStage(batchId, stageId); + + // show that the batch is on the stage + optionalOwningStageId = materialsDataManager.getBatchStageId(batchId); + assertTrue(optionalOwningStageId.isPresent()); + assertEquals(stageId, optionalOwningStageId.get()); + + // put the batch back into inventory + materialsDataManager.moveBatchToInventory(batchId); + + // show that the batch is not on any stage + optionalOwningStageId = materialsDataManager.getBatchStageId(batchId); + assertFalse(optionalOwningStageId.isPresent()); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the batch id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 8182230906627557939L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.getBatchStageId(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); + + /* precondition test: if the batch id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 3682958492574276233L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.getBatchStageId(new BatchId(100000)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_BATCH_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getInventoryBatches", args = { + MaterialsProducerId.class }) + public void testGetInventoryBatches() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create a data structure to hold expectd inventories + Map> expectedInventoryBatchesByProducer = new LinkedHashMap<>(); + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + expectedInventoryBatchesByProducer.put(testMaterialsProducerId, new LinkedHashSet<>()); + } + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + Set expectedInventoryBatches = expectedInventoryBatchesByProducer.get(testMaterialsProducerId); + + // create some batches + for (int i = 0; i < 100; i++) { + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + testMaterialsProducerId, TestMaterialId.getRandomMaterialId(randomGenerator), + randomGenerator.nextInt(100), randomGenerator); + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + expectedInventoryBatches.add(batchId); + } + + // show that the inventory batches are correct + List inventory = materialsDataManager.getInventoryBatches(testMaterialsProducerId); + Set actualInventoryBatches = new LinkedHashSet<>(inventory); + assertEquals(inventory.size(), actualInventoryBatches.size()); + assertEquals(expectedInventoryBatches, actualInventoryBatches); + + // create some stages and put some of the batches onto stages + List batches = new ArrayList<>(expectedInventoryBatches); + Collections.shuffle(batches, new Random(randomGenerator.nextLong())); + int batchIndex = 0; + for (int i = 0; i < 5; i++) { + StageId stageId = materialsDataManager.addStage(testMaterialsProducerId); + for (int j = 0; j < 5; j++) { + BatchId batchId = batches.get(batchIndex++); + materialsDataManager.moveBatchToStage(batchId, stageId); + expectedInventoryBatches.remove(batchId); + } + } + + // destroy a few inventory batches + for (int i = 0; i < 10; i++) { + BatchId batchId = batches.get(batchIndex++); + materialsDataManager.removeBatch(batchId); + expectedInventoryBatches.remove(batchId); + } + + })); + + } + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + Set expectedInventoryBatches = expectedInventoryBatchesByProducer.get(testMaterialsProducerId); + + // show that the inventory batches are correct + List batchList = materialsDataManager.getInventoryBatches(testMaterialsProducerId); + Set actualInventoryBatches = new LinkedHashSet<>(batchList); + assertEquals(batchList.size(), actualInventoryBatches.size()); + assertEquals(expectedInventoryBatches, actualInventoryBatches); + } + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 6343917844917632364L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition tests if the materials producerId id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 6759896268818524420L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.getInventoryBatches(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + /* precondition tests if the materials producerId id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 944257921550728616L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.getInventoryBatches(TestMaterialsProducerId.getUnknownMaterialsProducerId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getInventoryBatchesByMaterialId", args = { + MaterialsProducerId.class, MaterialId.class }) + public void testGetInventoryBatchesByMaterialId() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create a data structure to hold the expected inventories + Map>> expectedInventoryBatchesMap = new LinkedHashMap<>(); + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + Map> materialToBatchesMap = new LinkedHashMap<>(); + expectedInventoryBatchesMap.put(testMaterialsProducerId, materialToBatchesMap); + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + materialToBatchesMap.put(testMaterialId, new LinkedHashSet<>()); + } + } + /* + * Have the various material producers create batches and place them in + * inventory. Add some batches to stages. Destroy some of the batches. + */ + for (int k = 0; k < 10; k++) { + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsProducerId materialsProducerId = TestMaterialsProducerId + .getRandomMaterialsProducerId(randomGenerator); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + Map> materialToBatchesMap = expectedInventoryBatchesMap + .get(materialsProducerId); + + // create some (100) batches + for (int i = 0; i < 100; i++) { + MaterialId materialId = TestMaterialId.getRandomMaterialId(randomGenerator); + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + materialsProducerId, materialId, randomGenerator.nextInt(100), randomGenerator); + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + materialToBatchesMap.get(materialId).add(batchId); + } + + // show that the inventory batches are correct + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + List batchList = materialsDataManager.getInventoryBatchesByMaterialId(materialsProducerId, + testMaterialId); + Set actualInventoryBatches = new LinkedHashSet<>(batchList); + assertEquals(batchList.size(), actualInventoryBatches.size()); + Set expectedInventoryBatches = materialToBatchesMap.get(testMaterialId); + assertEquals(expectedInventoryBatches, actualInventoryBatches); + } + + /* + * Create some stages and put some (25) of the batches onto stages + * + */ + List batches = new ArrayList<>(); + for (Set expectedInventoryBatches : materialToBatchesMap.values()) { + batches.addAll(expectedInventoryBatches); + } + + Collections.shuffle(batches, new Random(randomGenerator.nextLong())); + int batchIndex = 0; + for (int i = 0; i < 5; i++) { + StageId stageId = materialsDataManager.addStage(materialsProducerId); + for (int j = 0; j < 5; j++) { + BatchId batchId = batches.get(batchIndex++); + materialsDataManager.moveBatchToStage(batchId, stageId); + MaterialId batchMaterial = materialsDataManager.getBatchMaterial(batchId); + materialToBatchesMap.get(batchMaterial).remove(batchId); + + } + } + + // destroy a few inventory batches + for (int i = 0; i < 10; i++) { + BatchId batchId = batches.get(batchIndex++); + MaterialId batchMaterial = materialsDataManager.getBatchMaterial(batchId); + materialsDataManager.removeBatch(batchId); + materialToBatchesMap.get(batchMaterial).remove(batchId); + } + + })); + } + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + // show that the inventory batches are correct + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + Map> materialToBatchesMap = expectedInventoryBatchesMap + .get(testMaterialsProducerId); + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + List invBatches = materialsDataManager + .getInventoryBatchesByMaterialId(testMaterialsProducerId, testMaterialId); + Set actualInventoryBatches = new LinkedHashSet<>(invBatches); + assertEquals(invBatches.size(), actualInventoryBatches.size()); + Set expectedInventoryBatches = materialToBatchesMap.get(testMaterialId); + assertEquals(expectedInventoryBatches, actualInventoryBatches); + } + } + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 8436700054410844417L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the materials producerId id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 8066333940937253765L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.getInventoryBatchesByMaterialId(null, TestMaterialId.MATERIAL_1); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + /* precondition test: if the materials producerId id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 3143917391309849287L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.getInventoryBatchesByMaterialId( + TestMaterialsProducerId.getUnknownMaterialsProducerId(), TestMaterialId.MATERIAL_2); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + /* precondition test: if the material id is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 874196115936784556L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.getInventoryBatchesByMaterialId(TestMaterialsProducerId.MATERIALS_PRODUCER_1, + null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_MATERIAL_ID, contractException.getErrorType()); + + /* precondition test: if the material id is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 9112311292467047420L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.getInventoryBatchesByMaterialId(TestMaterialsProducerId.MATERIALS_PRODUCER_1, + TestMaterialId.getUnknownMaterialId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_MATERIAL_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getMaterialIds", args = {}) + public void testGetMaterialIds() { + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 6611654668838622496L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + assertEquals(EnumSet.allOf(TestMaterialId.class), materialsDataManager.getMaterialIds()); + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getMaterialsProducerIds", args = {}) + public void testGetMaterialsProducerIds() { + + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 3824970086302200338L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + assertEquals(EnumSet.allOf(TestMaterialsProducerId.class), materialsDataManager.getMaterialsProducerIds()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "materialIdExists", args = { MaterialId.class }) + public void testMaterialIdExists() { + + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 6918669723394457093L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + assertTrue(materialsDataManager.materialIdExists(testMaterialId)); + } + assertFalse(materialsDataManager.materialIdExists(TestMaterialId.getUnknownMaterialId())); + assertFalse(materialsDataManager.materialIdExists(null)); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getMaterialsProducerPropertyDefinition", args = { + MaterialsProducerPropertyId.class }) + public void testGetMaterialsProducerPropertyDefinition() { + + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 7151961147034751776L, (c) -> { + MaterialsDataManager materialsDaView = c.getDataManager(MaterialsDataManager.class); + + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .values()) { + PropertyDefinition expectedPropertyDefinition = testMaterialsProducerPropertyId.getPropertyDefinition(); + PropertyDefinition actualPropertyDefinition = materialsDaView + .getMaterialsProducerPropertyDefinition(testMaterialsProducerPropertyId); + assertEquals(expectedPropertyDefinition, actualPropertyDefinition); + } + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the materials producer property id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 4030472148503907839L, (c) -> { + MaterialsDataManager materialsDaView = c.getDataManager(MaterialsDataManager.class); + materialsDaView.getMaterialsProducerPropertyDefinition(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + /* + * precondition test: if the materials producer property id is unknown + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 863172317284141879L, (c) -> { + MaterialsDataManager materialsDaView = c.getDataManager(MaterialsDataManager.class); + materialsDaView.getMaterialsProducerPropertyDefinition( + TestMaterialsProducerPropertyId.getUnknownMaterialsProducerPropertyId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getMaterialsProducerPropertyIds", args = {}) + public void testGetMaterialsProducerPropertyIds() { + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 8718225529106870071L, (c) -> { + MaterialsDataManager materialsDaView = c.getDataManager(MaterialsDataManager.class); + assertEquals(EnumSet.allOf(TestMaterialsProducerPropertyId.class), + materialsDaView.getMaterialsProducerPropertyIds()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getMaterialsProducerPropertyValue", args = { + MaterialsProducerId.class, MaterialsProducerPropertyId.class }) + public void testGetMaterialsProducerPropertyValue() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create a structure to hold expected assignment times for materials + // producer property values + Map expectedValuesMap = new LinkedHashMap<>(); + + // initialize the materials data manager + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + MaterialsDataManager materialsDaView = c.getDataManager(MaterialsDataManager.class); + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .values()) { + MultiKey multiKey = new MultiKey(testMaterialsProducerId, testMaterialsProducerPropertyId); + Object value = materialsDaView.getMaterialsProducerPropertyValue(testMaterialsProducerId, + testMaterialsProducerPropertyId); + expectedValuesMap.put(multiKey, value); + } + } + + })); + + // determine a reasonable number of changes per time + int propertyChangeCount = TestMaterialsProducerId.size() * TestMaterialsProducerPropertyId.size() / 10; + + // update several random property values at various times + for (int i = 0; i < 10; i++) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + for (int j = 0; j < propertyChangeCount; j++) { + TestMaterialsProducerId materialsProducerId = TestMaterialsProducerId + .getRandomMaterialsProducerId(randomGenerator); + TestMaterialsProducerPropertyId materialsProducerPropertyId = TestMaterialsProducerPropertyId + .getRandomMutableMaterialsProducerPropertyId(randomGenerator); + Object propertyValue = materialsProducerPropertyId.getRandomPropertyValue(randomGenerator); + materialsDataManager.setMaterialsProducerPropertyValue(materialsProducerId, + materialsProducerPropertyId, propertyValue); + MultiKey multiKey = new MultiKey(materialsProducerId, materialsProducerPropertyId); + expectedValuesMap.put(multiKey, propertyValue); + } + })); + } + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(10, (c) -> { + MaterialsDataManager materialsDaView = c.getDataManager(MaterialsDataManager.class); + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .values()) { + MultiKey multiKey = new MultiKey(testMaterialsProducerId, testMaterialsProducerPropertyId); + Object expectedValue = expectedValuesMap.get(multiKey); + Object actualValue = materialsDaView.getMaterialsProducerPropertyValue(testMaterialsProducerId, + testMaterialsProducerPropertyId); + assertEquals(expectedValue, actualValue); + } + } + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 3587272435527239583L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the materials producerId id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 4247143641356704364L, (c) -> { + MaterialsDataManager materialsDaView = c.getDataManager(MaterialsDataManager.class); + materialsDaView.getMaterialsProducerPropertyValue(null, + TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_4_BOOLEAN_MUTABLE_TRACK); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + /* precondition test: if the materials producerId id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 7731689857034028615L, (c) -> { + MaterialsDataManager materialsDaView = c.getDataManager(MaterialsDataManager.class); + materialsDaView.getMaterialsProducerPropertyValue( + TestMaterialsProducerId.getUnknownMaterialsProducerId(), + TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_4_BOOLEAN_MUTABLE_TRACK); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + /* precondition test: if the materials producerId property id is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 1004792420489047936L, (c) -> { + MaterialsDataManager materialsDaView = c.getDataManager(MaterialsDataManager.class); + materialsDaView.getMaterialsProducerPropertyValue(TestMaterialsProducerId.MATERIALS_PRODUCER_1, null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + /* + * precondition test: if the materials producerId property id is unknown + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 133851619411326116L, (c) -> { + MaterialsDataManager materialsDaView = c.getDataManager(MaterialsDataManager.class); + materialsDaView.getMaterialsProducerPropertyValue(TestMaterialsProducerId.MATERIALS_PRODUCER_1, + TestMaterialsProducerPropertyId.getUnknownMaterialsProducerPropertyId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getMaterialsProducerResourceLevel", args = { + MaterialsProducerId.class, ResourceId.class }) + public void testGetMaterialsProducerResourceLevel() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + long seed = 1633676078121550637L; + + MaterialsPluginData standardPluginData = MaterialsTestPluginFactory.getStandardMaterialsPluginData(0, 0, 0, + seed); + // create a structure to hold expected resource levels for producers + Map expectedLevelsMap = new LinkedHashMap<>(); + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + for (TestResourceId testResourceId : TestResourceId.values()) { + expectedLevelsMap.put(new MultiKey(testMaterialsProducerId, testResourceId), new MutableLong( + standardPluginData.getMaterialsProducerResourceLevel(testMaterialsProducerId, testResourceId))); + } + } + + // determine a reasonable number of changes per time + int resourceLevelChangeCount = TestMaterialsProducerId.size() * TestResourceId.size() / 10; + + // update several random resource levels values at various times + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + for (int j = 0; j < resourceLevelChangeCount; j++) { + + StageId stageId = materialsDataManager.addStage(testMaterialsProducerId); + TestResourceId testResourceId = TestResourceId.getRandomResourceId(randomGenerator); + Long resourceLevel = (long) (randomGenerator.nextInt(100) + 1); + materialsDataManager.convertStageToResource(stageId, testResourceId, resourceLevel); + MultiKey multiKey = new MultiKey(testMaterialsProducerId, testResourceId); + MutableLong mutableLong = expectedLevelsMap.get(multiKey); + mutableLong.increment(resourceLevel); + } + })); + } + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(10, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + for (TestResourceId testResourceId : TestResourceId.values()) { + MultiKey multiKey = new MultiKey(testMaterialsProducerId, testResourceId); + long expectedLevel = expectedLevelsMap.get(multiKey).getValue(); + long actualLevel = materialsDataManager.getMaterialsProducerResourceLevel(testMaterialsProducerId, + testResourceId); + assertEquals(expectedLevel, actualLevel); + } + } + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, seed, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the materials producerId id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 11082575022266925L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.getMaterialsProducerResourceLevel(null, TestResourceId.RESOURCE_1); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + /* precondition test: if the materials producerId id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 6362058221207452078L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.getMaterialsProducerResourceLevel( + TestMaterialsProducerId.getUnknownMaterialsProducerId(), TestResourceId.RESOURCE_1); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + /* precondition test: if the resource id is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 7531288497048301736L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.getMaterialsProducerResourceLevel(TestMaterialsProducerId.MATERIALS_PRODUCER_1, + null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the resource id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 2745862264533327311L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.getMaterialsProducerResourceLevel(TestMaterialsProducerId.MATERIALS_PRODUCER_1, + TestResourceId.getUnknownResourceId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getOfferedStages", args = { + MaterialsProducerId.class }) + public void testGetOfferedStages() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + /* + * create several stages and place about half of them into the offer state + */ + Set expectedStageOffers = new LinkedHashSet<>(); + + for (int i = 0; i < 100; i++) { + StageId stageId = materialsDataManager.addStage(testMaterialsProducerId); + if (randomGenerator.nextBoolean()) { + expectedStageOffers.add(stageId); + materialsDataManager.setStageOfferState(stageId, true); + } + } + + List stages = materialsDataManager.getOfferedStages(testMaterialsProducerId); + Set actualStages = new LinkedHashSet<>(stages); + assertEquals(stages.size(), actualStages.size()); + assertEquals(expectedStageOffers, actualStages); + + })); + } + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 7995017020582510238L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition tests if the materials producerId id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 1728234953072549300L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.getOfferedStages(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + /* precondition tests if the materials producerId id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 809512240800144004L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.getOfferedStages(TestMaterialsProducerId.getUnknownMaterialsProducerId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getStageBatches", args = { StageId.class }) + public void testGetStageBatches() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create a structure to hold expectations + Map> expectedStageBatches = new LinkedHashMap<>(); + + // have each of the materials producers create and stage some batches + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + // create some stages and batches + for (int i = 0; i < 10; i++) { + StageId stageId = materialsDataManager.addStage(testMaterialsProducerId); + int batchCount = randomGenerator.nextInt(3) + 1; + Set batches = new LinkedHashSet<>(); + for (int j = 0; j < batchCount; j++) { + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo + .getBatchConstructionInfo(testMaterialsProducerId, + TestMaterialId.getRandomMaterialId(randomGenerator), + randomGenerator.nextInt(100), randomGenerator); + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + batches.add(batchId); + materialsDataManager.moveBatchToStage(batchId, stageId); + } + expectedStageBatches.put(stageId, batches); + } + })); + } + // have an actor show that the batches are staged as expected + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + Map> actualStageBatches = new LinkedHashMap<>(); + for (MaterialsProducerId materialsProducerId : materialsDataManager.getMaterialsProducerIds()) { + List stageIds = materialsDataManager.getStages(materialsProducerId); + for (StageId stageId : stageIds) { + actualStageBatches.put(stageId, new LinkedHashSet<>(materialsDataManager.getStageBatches(stageId))); + } + } + + assertEquals(expectedStageBatches, actualStageBatches); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 3682458920522952415L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition tests if the stage id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 7434749084817685354L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.getStageBatches(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_STAGE_ID, contractException.getErrorType()); + + /* precondition tests if the stage id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 8988415576850624232L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.getStageBatches(new StageId(10000000)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_STAGE_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getStageBatchesByMaterialId", args = { StageId.class, + MaterialId.class }) + public void testGetStageBatchesByMaterialId() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // build a structure to hold the batches expected per stage and + // material id + Map>> expectedStageBatches = new LinkedHashMap<>(); + + // have each of the materials producers create and stage some batches + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + // create some stages and batches + for (int i = 0; i < 10; i++) { + StageId stageId = materialsDataManager.addStage(testMaterialsProducerId); + int batchCount = randomGenerator.nextInt(10) + 1; + Map> map = new LinkedHashMap<>(); + for (int j = 0; j < batchCount; j++) { + TestMaterialId materialId = TestMaterialId.getRandomMaterialId(randomGenerator); + Set batches = map.get(materialId); + if (batches == null) { + batches = new LinkedHashSet<>(); + map.put(materialId, batches); + } + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo + .getBatchConstructionInfo(testMaterialsProducerId, materialId, + randomGenerator.nextInt(100), randomGenerator); + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + batches.add(batchId); + materialsDataManager.moveBatchToStage(batchId, stageId); + } + expectedStageBatches.put(stageId, map); + } + + })); + } + + // have an actor show that the batches are staged as expected + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + /* + * gather the actual stage batches in a structure identical to the expected + * values + */ + Map>> actualStageBatches = new LinkedHashMap<>(); + for (MaterialsProducerId materialsProducerId : materialsDataManager.getMaterialsProducerIds()) { + List stageIds = materialsDataManager.getStages(materialsProducerId); + + for (StageId stageId : stageIds) { + Map> map = new LinkedHashMap<>(); + actualStageBatches.put(stageId, map); + List stageBatches = materialsDataManager.getStageBatches(stageId); + for (BatchId batchId : stageBatches) { + MaterialId materialId = materialsDataManager.getBatchMaterial(batchId); + Set set = map.get(materialId); + if (set == null) { + set = new LinkedHashSet<>(); + map.put(materialId, set); + } + set.add(batchId); + } + } + } + + // show that the actual batches are retrievable by stage and + // material id correctly + assertEquals(expectedStageBatches, actualStageBatches); + + })); + + // precondition tests + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + // if the stage id is null + ContractException contractException = assertThrows(ContractException.class, + () -> materialsDataManager.getStageBatchesByMaterialId(null, TestMaterialId.MATERIAL_1)); + assertEquals(MaterialsError.NULL_STAGE_ID, contractException.getErrorType()); + + // if the stage id is unknown + contractException = assertThrows(ContractException.class, () -> materialsDataManager + .getStageBatchesByMaterialId(new StageId(10000000), TestMaterialId.MATERIAL_1)); + assertEquals(MaterialsError.UNKNOWN_STAGE_ID, contractException.getErrorType()); + + // if the material id is null + contractException = assertThrows(ContractException.class, + () -> materialsDataManager.getStageBatchesByMaterialId(new StageId(0), null)); + assertEquals(MaterialsError.NULL_MATERIAL_ID, contractException.getErrorType()); + + // if the material id is unknown + contractException = assertThrows(ContractException.class, () -> materialsDataManager + .getStageBatchesByMaterialId(new StageId(0), TestMaterialId.getUnknownMaterialId())); + assertEquals(MaterialsError.UNKNOWN_MATERIAL_ID, contractException.getErrorType()); + + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 2013967899243685546L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getStageProducer", args = { StageId.class }) + public void testGetStageProducer() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StageId stageId = materialsDataManager.addStage(testMaterialsProducerId); + assertEquals(testMaterialsProducerId, materialsDataManager.getStageProducer(stageId)); + })); + } + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 4322374809851867527L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the stage id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 3429442631390139742L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.getStageProducer(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_STAGE_ID, contractException.getErrorType()); + + /* precondition test: if the stage id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 8553021441594074433L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.getStageProducer(new StageId(1000000000)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_STAGE_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getStages", args = { MaterialsProducerId.class }) + public void testGetStages() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + Set expectedStages = new LinkedHashSet<>(); + + int count = randomGenerator.nextInt(10) + 1; + for (int i = 0; i < count; i++) { + StageId stageId = materialsDataManager.addStage(testMaterialsProducerId); + expectedStages.add(stageId); + } + + List stages = materialsDataManager.getStages(testMaterialsProducerId); + + Set actualStageIds = new LinkedHashSet<>(stages); + assertEquals(stages.size(), actualStageIds.size()); + assertEquals(expectedStages, actualStageIds); + })); + } + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 3431193355375533655L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the materials producer id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 8012112350970626114L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.getStages(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + /* precondition test: if the materials producer id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 4013634140214782310L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.getStages(TestMaterialsProducerId.getUnknownMaterialsProducerId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "isStageOffered", args = { StageId.class }) + public void testIsStageOffered() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + for (int i = 0; i < 100; i++) { + StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + assertFalse(materialsDataManager.isStageOffered(stageId)); + materialsDataManager.setStageOfferState(stageId, true); + assertTrue(materialsDataManager.isStageOffered(stageId)); + } + + // precondition tests + + // if the stage id is null + ContractException contractException = assertThrows(ContractException.class, + () -> materialsDataManager.isStageOffered(null)); + assertEquals(MaterialsError.NULL_STAGE_ID, contractException.getErrorType()); + + // if the stage id is unknown + contractException = assertThrows(ContractException.class, + () -> materialsDataManager.isStageOffered(new StageId(1000000))); + assertEquals(MaterialsError.UNKNOWN_STAGE_ID, contractException.getErrorType()); + + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 475901778920012875L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "materialsProducerIdExists", args = { + MaterialsProducerId.class }) + public void testMaterialsProducerIdExists() { + + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 4005535514531641716L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + assertTrue(materialsDataManager.materialsProducerIdExists(testMaterialsProducerId)); + } + assertFalse(materialsDataManager + .materialsProducerIdExists(TestMaterialsProducerId.getUnknownMaterialsProducerId())); + assertFalse(materialsDataManager.materialsProducerIdExists(null)); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "materialsProducerPropertyIdExists", args = { + MaterialsProducerPropertyId.class }) + public void testMaterialsProducerPropertyIdExists() { + + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 8256309156804000329L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .values()) { + assertTrue(materialsDataManager.materialsProducerPropertyIdExists(testMaterialsProducerPropertyId)); + } + assertFalse(materialsDataManager.materialsProducerPropertyIdExists( + TestMaterialsProducerPropertyId.getUnknownMaterialsProducerPropertyId())); + assertFalse(materialsDataManager.materialsProducerPropertyIdExists(null)); + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "stageExists", args = { StageId.class }) + public void testStageExists() { + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 4646356228574091149L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + for (int i = 0; i < 10; i++) { + StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + assertTrue(materialsDataManager.stageExists(stageId)); + } + assertFalse(materialsDataManager.stageExists(null)); + assertFalse(materialsDataManager.stageExists(new StageId(123))); + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "moveBatchToInventory", args = { BatchId.class }) + public void testMoveBatchToInventoryEvent() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + double actionTime = 0; + + // create containers to hold observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // have an observer record batches being removed from stages + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + c.subscribe(EventFilter.builder(StageMembershipRemovalEvent.class).build(), (c2, e) -> { + MultiKey multiKey = new MultiKey(c2.getTime(), e.batchId(), e.stageId()); + actualObservations.add(multiKey); + }); + })); + + /* + * Have the materials producers create batches and place about half of them on + * stages + */ + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + for (int i = 0; i < 40; i++) { + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + testMaterialsProducerId, TestMaterialId.getRandomMaterialId(randomGenerator), + randomGenerator.nextDouble() + 0.01, randomGenerator); + materialsDataManager.addBatch(batchConstructionInfo); + } + + for (int i = 0; i < 5; i++) { + materialsDataManager.addStage(testMaterialsProducerId); + } + + List stages = materialsDataManager.getStages(testMaterialsProducerId); + List batches = materialsDataManager.getInventoryBatches(testMaterialsProducerId); + for (BatchId batchId : batches) { + if (randomGenerator.nextBoolean()) { + StageId stageId = stages.get(randomGenerator.nextInt(stages.size())); + materialsDataManager.moveBatchToStage(batchId, stageId); + } + } + + })); + } + + /* + * Have the materials producers return some about half of the staged batches to + * inventory and show that the batches are now in inventory + */ + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + List stages = materialsDataManager.getStages(testMaterialsProducerId); + for (StageId stageId : stages) { + List batches = materialsDataManager.getStageBatches(stageId); + for (BatchId batchId : batches) { + if (randomGenerator.nextBoolean()) { + materialsDataManager.moveBatchToInventory(batchId); + // show that the batch was returned to inventory + assertFalse(materialsDataManager.getBatchStageId(batchId).isPresent()); + assertTrue(materialsDataManager.getInventoryBatches(testMaterialsProducerId) + .contains(batchId)); + // create the observation + MultiKey multiKey = new MultiKey(c.getTime(), batchId, stageId); + expectedObservations.add(multiKey); + } + } + } + })); + } + + // have the observer show that the correct observations were made + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 5136057466059323708L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the batch id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 9033912130601526542L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.moveBatchToInventory(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); + + /* precondition test: if the batch id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 4357313141781993780L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.moveBatchToInventory(new BatchId(10000000)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_BATCH_ID, contractException.getErrorType()); + + /* precondition test: if the batch is not staged */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 5899034328012868517L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 5.0, randomGenerator); + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + materialsDataManager.moveBatchToInventory(batchId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.BATCH_NOT_STAGED, contractException.getErrorType()); + + /* precondition test: if the stage containing the batch is offered */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 6151702850690578711L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 5.0, randomGenerator); + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + materialsDataManager.moveBatchToStage(batchId, stageId); + materialsDataManager.setStageOfferState(stageId, true); + materialsDataManager.moveBatchToInventory(batchId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.OFFERED_STAGE_UNALTERABLE, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "moveBatchToStage", args = { BatchId.class, + StageId.class }) + public void testMoveBatchToStageEvent() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + double actionTime = 0; + + // create containers to hold observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // have an observer record observations of batches being assigned to + // stages + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + c.subscribe(EventFilter.builder(StageMembershipAdditionEvent.class).build(), (c2, e) -> { + MultiKey multiKey = new MultiKey(c2.getTime(), e.batchId(), e.stageId()); + actualObservations.add(multiKey); + }); + })); + + // have the producers create several batches and stages + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + for (int i = 0; i < 40; i++) { + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + testMaterialsProducerId, TestMaterialId.getRandomMaterialId(randomGenerator), + randomGenerator.nextDouble() + 0.01, randomGenerator); + materialsDataManager.addBatch(batchConstructionInfo); + } + for (int i = 0; i < 5; i++) { + materialsDataManager.addStage(testMaterialsProducerId); + } + })); + } + + // have the producers put about half of their batches onto stages + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + List stages = materialsDataManager.getStages(testMaterialsProducerId); + List batches = materialsDataManager.getInventoryBatches(testMaterialsProducerId); + for (BatchId batchId : batches) { + if (randomGenerator.nextBoolean()) { + StageId stageId = stages.get(randomGenerator.nextInt(stages.size())); + materialsDataManager.moveBatchToStage(batchId, stageId); + + // show that the batch is now on the stage + Optional optionalStageId = materialsDataManager.getBatchStageId(batchId); + assertTrue(optionalStageId.isPresent()); + StageId actualStageId = optionalStageId.get(); + assertEquals(stageId, actualStageId); + + // add the expected observation + MultiKey multiKey = new MultiKey(c.getTime(), batchId, stageId); + expectedObservations.add(multiKey); + } + } + + })); + } + // have the observer show that the correct observations were generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 6845954292451913670L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the batch id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 8929308420703752743L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + materialsDataManager.moveBatchToStage(null, stageId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); + + /* precondition test: if the batch id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 2109267427404198126L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + materialsDataManager.moveBatchToStage(new BatchId(100000000), stageId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_BATCH_ID, contractException.getErrorType()); + + /* precondition test: if the batch is already staged */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 7854805166816872481L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_2, 5.6, randomGenerator); + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + materialsDataManager.moveBatchToStage(batchId, stageId); + materialsDataManager.moveBatchToStage(batchId, stageId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.BATCH_ALREADY_STAGED, contractException.getErrorType()); + + /* precondition test: if the stage id is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 7324432599621147973L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_2, 5.6, randomGenerator); + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + materialsDataManager.moveBatchToStage(batchId, null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_STAGE_ID, contractException.getErrorType()); + + /* precondition test: if the stage id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 1695963867758678736L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_2, 5.6, randomGenerator); + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + materialsDataManager.moveBatchToStage(batchId, new StageId(10000000)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_STAGE_ID, contractException.getErrorType()); + + /* precondition test: if the stage is offered */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 2664182628538225235L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_2, 5.6, randomGenerator); + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + materialsDataManager.setStageOfferState(stageId, true); + materialsDataManager.moveBatchToStage(batchId, stageId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.OFFERED_STAGE_UNALTERABLE, contractException.getErrorType()); + + /* + * precondition test: if batch and stage do not have the same owning materials + * producer + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 424341512515165163L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_2, TestMaterialId.MATERIAL_2, 12, randomGenerator); + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + materialsDataManager.moveBatchToStage(batchId, stageId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.BATCH_STAGED_TO_DIFFERENT_OWNER, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "removeBatch", args = { BatchId.class }) + public void testRemoveBatch() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + double actionTime = 0; + + // construct data structures to hold observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + c.subscribe(EventFilter.builder(BatchImminentRemovalEvent.class).build(), (c2, e) -> { + actualObservations.add(e.batchId()); + }); + })); + + /* + * Add batches -- although we will concentrate on producer 1, we will have the + * other producers generate batches for use in precondition tests + */ + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + for (int i = 0; i < 50; i++) { + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + testMaterialsProducerId, TestMaterialId.getRandomMaterialId(randomGenerator), + randomGenerator.nextDouble(), randomGenerator); + materialsDataManager.addBatch(batchConstructionInfo); + } + })); + } + + // remove some batches + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + List inventoryBatches = materialsDataManager + .getInventoryBatches(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + for (BatchId batchId : inventoryBatches) { + if (randomGenerator.nextBoolean()) { + materialsDataManager.removeBatch(batchId); + expectedObservations.add(batchId); + } + } + })); + + // show that the batches were indeed removed + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + for (BatchId batchId : expectedObservations) { + assertFalse(materialsDataManager.batchExists(batchId)); + } + })); + + // show that the observations were properly generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 2869388661813620663L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the batch id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 6338675888020916426L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.removeBatch(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); + + /* precondition test: if the batch id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 3451796010171778050L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.removeBatch(new BatchId(100000)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_BATCH_ID, contractException.getErrorType()); + + /* precondition test: if the batch is on an offered stage */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 2022222016456137374L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 1.0, randomGenerator); + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + materialsDataManager.moveBatchToStage(batchId, stageId); + materialsDataManager.setStageOfferState(stageId, true); + materialsDataManager.removeBatch(batchId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.OFFERED_STAGE_UNALTERABLE, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "removeStage", args = { StageId.class, boolean.class }) + public void testRemoveStage() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + double actionTime = 0; + + // create containers to hold observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // have a agent observe stage creations + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + + c.subscribe(EventFilter.builder(StageImminentRemovalEvent.class).build(), (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.stageId())); + }); + + c.subscribe(EventFilter.builder(StageMembershipRemovalEvent.class).build(), (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.batchId(), e.stageId())); + }); + + c.subscribe(EventFilter.builder(BatchImminentRemovalEvent.class).build(), (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.batchId())); + }); + })); + + // have the producers create some stages with batches + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + for (int i = 0; i < 50; i++) { + StageId stageId = materialsDataManager.addStage(testMaterialsProducerId); + int batchCount = randomGenerator.nextInt(3); + for (int j = 0; j < batchCount; j++) { + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo + .getBatchConstructionInfo(testMaterialsProducerId, + TestMaterialId.getRandomMaterialId(randomGenerator), + randomGenerator.nextDouble() + 0.01, randomGenerator); + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + materialsDataManager.moveBatchToStage(batchId, stageId); + } + } + })); + } + + // have the producers destroy all their stages, returning about half of + // the batches to inventory + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + + List stagesToConfirm = new ArrayList<>(); + List batchesToConfirm = new ArrayList<>(); + Set expectedInventoryBatches = new LinkedHashSet<>(); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + List stages = materialsDataManager.getStages(testMaterialsProducerId); + for (StageId stageId : stages) { + boolean destroyBatches = randomGenerator.nextBoolean(); + List stageBatches = materialsDataManager.getStageBatches(stageId); + materialsDataManager.removeStage(stageId, destroyBatches); + + /* + * record the batch and stage ids that will be removed after the current agent + * activation so that they can be confirmed later + */ + if (destroyBatches) { + batchesToConfirm.addAll(stageBatches); + } else { + expectedInventoryBatches.addAll(stageBatches); + } + stagesToConfirm.add(stageId); + + // generate the expected observations + if (destroyBatches) { + for (BatchId batchId : stageBatches) { + expectedObservations.add(new MultiKey(c.getTime(), batchId)); + } + } + + expectedObservations.add(new MultiKey(c.getTime(), stageId)); + + } + })); + + // show that the expected batches and stages were removed + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + for (StageId stageId : stagesToConfirm) { + assertFalse(materialsDataManager.stageExists(stageId)); + } + for (BatchId batchId : batchesToConfirm) { + assertFalse(materialsDataManager.batchExists(batchId)); + } + List inventoryBatches = materialsDataManager.getInventoryBatches(testMaterialsProducerId); + Set actualInventoryBatches = new LinkedHashSet<>(inventoryBatches); + assertEquals(expectedInventoryBatches, actualInventoryBatches); + })); + + } + + // have the observer show that the correct observations were generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 886447125697525680L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the stage id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 7585484363151799317L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.removeStage(null, false); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_STAGE_ID, contractException.getErrorType()); + + /* precondition test: if the stage id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 2068986583725856249L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.removeStage(new StageId(1000000), false); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_STAGE_ID, contractException.getErrorType()); + + /* precondition test: if stage is offered */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 4255522387251717178L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + materialsDataManager.setStageOfferState(stageId, true); + materialsDataManager.removeStage(stageId, false); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.OFFERED_STAGE_UNALTERABLE, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "setBatchPropertyValue", args = { BatchId.class, + BatchPropertyId.class, Object.class }) + public void testBatchPropertyValueAssignmentEvent() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + double actionTime = 0; + + // create containers for observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + c.subscribe(EventFilter.builder(BatchPropertyUpdateEvent.class).build(), (c2, e) -> { + MultiKey multiKey = new MultiKey(c2.getTime(), e.batchId(), e.batchPropertyId(), + e.previousPropertyValue(), e.currentPropertyValue()); + actualObservations.add(multiKey); + }); + })); + + // create some batches + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + for (int i = 0; i < 10; i++) { + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + testMaterialsProducerId, TestMaterialId.getRandomMaterialId(randomGenerator), + randomGenerator.nextDouble() + 0.01, randomGenerator); + materialsDataManager.addBatch(batchConstructionInfo); + } + })); + } + + // Alter about half of the mutable properties of batches over 5 + // different times + for (int i = 0; i < 5; i++) { + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + List inventoryBatches = materialsDataManager.getInventoryBatches(testMaterialsProducerId); + for (BatchId batchId : inventoryBatches) { + TestMaterialId batchMaterial = materialsDataManager.getBatchMaterial(batchId); + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId + .getTestBatchPropertyIds(batchMaterial)) { + if (testBatchPropertyId.getPropertyDefinition().propertyValuesAreMutable()) { + if (randomGenerator.nextBoolean()) { + Object newPropertyValue = testBatchPropertyId + .getRandomPropertyValue(randomGenerator); + Object oldPropertyValue = materialsDataManager.getBatchPropertyValue(batchId, + testBatchPropertyId); + expectedObservations.add(new MultiKey(c.getTime(), batchId, testBatchPropertyId, + oldPropertyValue, newPropertyValue)); + materialsDataManager.setBatchPropertyValue(batchId, testBatchPropertyId, + newPropertyValue); + + // show that the new property value is + // present + assertEquals(newPropertyValue, + materialsDataManager.getBatchPropertyValue(batchId, testBatchPropertyId)); + } + } + } + } + })); + } + } + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 6834204103777199004L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the batch id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 7018657860620291081L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + BatchPropertyId batchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK; + Object propertyValue = 56; + materialsDataManager.setBatchPropertyValue(null, batchPropertyId, propertyValue); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); + + /* precondition test: if the batch id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 8208393972487550344L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + BatchPropertyId batchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK; + Object propertyValue = 56; + materialsDataManager.setBatchPropertyValue(new BatchId(100000), batchPropertyId, propertyValue); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_BATCH_ID, contractException.getErrorType()); + + /* precondition test: if the batch property id is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 1469429590277285989L, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialId materialId = TestMaterialId.MATERIAL_1; + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_1, materialId, 1.0, randomGenerator); + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + Object propertyValue = 56; + materialsDataManager.setBatchPropertyValue(batchId, null, propertyValue); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + /* precondition test: if the batch property id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 5183183157525649267L, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialId materialId = TestMaterialId.MATERIAL_1; + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_1, materialId, 1.0, randomGenerator); + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + Object propertyValue = 56; + materialsDataManager.setBatchPropertyValue(batchId, TestBatchPropertyId.getUnknownBatchPropertyId(), + propertyValue); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + /* precondition test: if batch property is not mutable */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 6735391483628478568L, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialId materialId = TestMaterialId.MATERIAL_1; + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_1, materialId, 1.0, randomGenerator); + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + materialsDataManager.setBatchPropertyValue(batchId, + TestBatchPropertyId.BATCH_PROPERTY_1_1_BOOLEAN_IMMUTABLE_NO_TRACK, false); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + + }); + assertEquals(PropertyError.IMMUTABLE_VALUE, contractException.getErrorType()); + + /* precondition test: if the batch property value is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 7110456544138996752L, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialId materialId = TestMaterialId.MATERIAL_1; + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_1, materialId, 1.0, randomGenerator); + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + BatchPropertyId batchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK; + materialsDataManager.setBatchPropertyValue(batchId, batchPropertyId, null); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_VALUE, contractException.getErrorType()); + + /* + * precondition test: if the batch property value is not compatible with the + * corresponding property definition + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 2721004733907727252L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialId materialId = TestMaterialId.MATERIAL_1; + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5751385676704973926L); + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_1, materialId, 1.0, randomGenerator); + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + BatchPropertyId batchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK; + materialsDataManager.setBatchPropertyValue(batchId, batchPropertyId, 12.4); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + + }); + assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); + + /* precondition test: if the batch in on an offered stage */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 8421379216279187305L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialId materialId = TestMaterialId.MATERIAL_1; + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_1, materialId, 1.0, randomGenerator); + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + BatchPropertyId batchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK; + Object propertyValue = 56; + StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + materialsDataManager.moveBatchToStage(batchId, stageId); + materialsDataManager.setStageOfferState(stageId, true); + materialsDataManager.setBatchPropertyValue(batchId, batchPropertyId, propertyValue); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + + }); + assertEquals(MaterialsError.OFFERED_STAGE_UNALTERABLE, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "setMaterialsProducerPropertyValue", args = { + MaterialsProducerId.class, MaterialsProducerPropertyId.class, Object.class }) + public void testSetMaterialsProducerPropertyValue() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + double actionTime = 0; + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // have an agent observe all changes to all producer property values + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .values()) { + EventFilter eventFilter = materialsDataManager + .getEventFilterForMaterialsProducerPropertyUpdateEvent(testMaterialsProducerId, + testMaterialsProducerPropertyId); + c.subscribe(eventFilter, (c2, e) -> { + MultiKey multiKey = new MultiKey(c2.getTime(), e.materialsProducerId(), + e.materialsProducerPropertyId(), e.previousPropertyValue(), e.currentPropertyValue()); + actualObservations.add(multiKey); + }); + } + } + })); + + for (int i = 0; i < 200; i++) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + // pick a random materials producer property and update it to a + // random value + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsProducerId materialsProducerId = TestMaterialsProducerId + .getRandomMaterialsProducerId(randomGenerator); + TestMaterialsProducerPropertyId testMaterialsProducerPropertyId = TestMaterialsProducerPropertyId + .getRandomMutableMaterialsProducerPropertyId(randomGenerator); + Object oldValue = materialsDataManager.getMaterialsProducerPropertyValue(materialsProducerId, + testMaterialsProducerPropertyId); + Object newValue = testMaterialsProducerPropertyId.getRandomPropertyValue(randomGenerator); + materialsDataManager.setMaterialsProducerPropertyValue(materialsProducerId, + testMaterialsProducerPropertyId, newValue); + + // show that the new value is present + assertEquals(newValue, materialsDataManager.getMaterialsProducerPropertyValue(materialsProducerId, + testMaterialsProducerPropertyId)); + + // record the expected observation + MultiKey multiKey = new MultiKey(c.getTime(), materialsProducerId, testMaterialsProducerPropertyId, + oldValue, newValue); + expectedObservations.add(multiKey); + })); + } + + // have the observer show that the proper observations were generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 3888305479377600267L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the materials producer id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 6542684073527908815L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialsProducerPropertyId materialsProducerPropertyId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK; + Object propertyValue = 5; + materialsDataManager.setMaterialsProducerPropertyValue(null, materialsProducerPropertyId, + propertyValue); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + /* precondition test: if the materials producer id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 7681762910631637513L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialsProducerPropertyId materialsProducerPropertyId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK; + Object propertyValue = 5; + materialsDataManager.setMaterialsProducerPropertyValue( + TestMaterialsProducerId.getUnknownMaterialsProducerId(), materialsProducerPropertyId, + propertyValue); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + /* precondition test: if the materials producer property id is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 5021355716878157868L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; + Object propertyValue = 5; + materialsDataManager.setMaterialsProducerPropertyValue(materialsProducerId, null, propertyValue); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + /* + * precondition test: if the materials producer property id is unknown + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 6739449188760503613L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; + Object propertyValue = 5; + materialsDataManager.setMaterialsProducerPropertyValue(materialsProducerId, + TestMaterialsProducerPropertyId.getUnknownMaterialsProducerPropertyId(), propertyValue); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + /* precondition test: if the materials producer property is immutable */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 3015359065220869477L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; + Object propertyValue = 5; + materialsDataManager.setMaterialsProducerPropertyValue(materialsProducerId, + TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_8_INTEGER_IMMUTABLE_NO_TRACK, + propertyValue); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.IMMUTABLE_VALUE, contractException.getErrorType()); + + /* precondition test: if the property value is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 2614476067199172944L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; + MaterialsProducerPropertyId materialsProducerPropertyId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK; + materialsDataManager.setMaterialsProducerPropertyValue(materialsProducerId, materialsProducerPropertyId, + null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_VALUE, contractException.getErrorType()); + + /* + * precondition test: if the property value is incompatible with the + * corresponding property definition + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 5704066697861534404L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; + MaterialsProducerPropertyId materialsProducerPropertyId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK; + materialsDataManager.setMaterialsProducerPropertyValue(materialsProducerId, materialsProducerPropertyId, + 12.5); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "setStageOfferState", args = { StageId.class, + boolean.class }) + public void testSetStageOfferState() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + double actionTime = 0; + + // create containers to hold observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // have a agent observe stage creations + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + c.subscribe(EventFilter.builder(StageOfferUpdateEvent.class).build(), (c2, e) -> { + actualObservations + .add(new MultiKey(c2.getTime(), e.stageId(), e.previousOfferState(), e.currentOfferState())); + }); + })); + + // have the producers create a few stages + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + for (int i = 0; i < 5; i++) { + materialsDataManager.addStage(testMaterialsProducerId); + } + })); + } + + // have the producers make a few random offer state changes + for (int i = 0; i < 10; i++) { + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + List stages = materialsDataManager.getStages(testMaterialsProducerId); + StageId stageId = stages.get(randomGenerator.nextInt(stages.size())); + boolean isOffered = materialsDataManager.isStageOffered(stageId); + materialsDataManager.setStageOfferState(stageId, !isOffered); + + // show that the offer state changed + boolean newOfferState = materialsDataManager.isStageOffered(stageId); + assertNotEquals(isOffered, newOfferState); + + // generate the expected observation + expectedObservations.add(new MultiKey(c.getTime(), stageId, isOffered, !isOffered)); + })); + } + } + + // have the observer show that the correct observations were generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 916177724112971509L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the stage id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 5384463547664747393L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.setStageOfferState(null, true); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_STAGE_ID, contractException.getErrorType()); + + /* precondition test: if the stage id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 319106508275202491L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.setStageOfferState(new StageId(10000000), true); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_STAGE_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "transferMaterialBetweenBatches", args = { + BatchId.class, BatchId.class, double.class }) + public void testBatchContentShiftEvent() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + double actionTime = 0; + + // create data structures to + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // have an agent observe the movement of materials between batches + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + c.subscribe(EventFilter.builder(BatchAmountUpdateEvent.class).build(), (c2, e) -> { + MultiKey multiKey = new MultiKey(e.batchId(), e.previousAmount(), e.currentAmount()); + actualObservations.add(multiKey); + }); + })); + + // Have the materials producers create a few batches, ensuring the + // amount in each batch is positive + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + for (int i = 0; i < 10; i++) { + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo + .getBatchConstructionInfo(testMaterialsProducerId, testMaterialId, + randomGenerator.nextDouble() + 0.1, randomGenerator); + materialsDataManager.addBatch(batchConstructionInfo); + } + } + })); + } + + /* + * We will concentrate on just producer 2 for most of the test but will utilize + * batches in other producers in some tests + */ + MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; + + // have the materials producer swap material amount around + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + List batches = materialsDataManager.getInventoryBatchesByMaterialId(materialsProducerId, + testMaterialId); + int index1 = randomGenerator.nextInt(batches.size()); + int index2 = randomGenerator.nextInt(batches.size()); + if (index2 == index1) { + index2++; + index2 = index2 % batches.size(); + } + BatchId batchId1 = batches.get(index1); + BatchId batchId2 = batches.get(index2); + double batchAmount1 = materialsDataManager.getBatchAmount(batchId1); + double batchAmount2 = materialsDataManager.getBatchAmount(batchId2); + // ensure that we transfer a positive amount, but not the whole + // amount + double portion = randomGenerator.nextDouble() * 0.8 + 0.1; + double transferAmount = batchAmount1 * portion; + materialsDataManager.transferMaterialBetweenBatches(batchId1, batchId2, transferAmount); + + double batchAmount3 = materialsDataManager.getBatchAmount(batchId1); + double batchAmount4 = materialsDataManager.getBatchAmount(batchId2); + + assertEquals(batchAmount1 - transferAmount, batchAmount3, 0.00000000001); + assertEquals(batchAmount2 + transferAmount, batchAmount4, 0.00000000001); + + MultiKey multiKey = new MultiKey(batchId1, batchAmount1, batchAmount3); + expectedObservations.add(multiKey); + + multiKey = new MultiKey(batchId2, batchAmount2, batchAmount4); + expectedObservations.add(multiKey); + } + })); + + // Have the observer show that the observations were generated correctly + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 6185579658134885353L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the source batch id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 4531031694400929670L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 1.0, randomGenerator); + BatchId destinationBatchId = materialsDataManager.addBatch(batchConstructionInfo); + materialsDataManager.transferMaterialBetweenBatches(null, destinationBatchId, 0.1); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + + }); + assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); + + /* precondition test: if the source batch id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 6565712732641056695L, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + BatchId sourceBatchId = new BatchId(100000); + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 1.0, randomGenerator); + BatchId destinationBatchId = materialsDataManager.addBatch(batchConstructionInfo); + materialsDataManager.transferMaterialBetweenBatches(sourceBatchId, destinationBatchId, 0.1); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_BATCH_ID, contractException.getErrorType()); + + /* precondition test: if the destination batch id is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 2331763843587032989L, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 1.0, randomGenerator); + BatchId sourceBatchId = materialsDataManager.addBatch(batchConstructionInfo); + BatchId destinationBatchId = null; + materialsDataManager.transferMaterialBetweenBatches(sourceBatchId, destinationBatchId, 0.1); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); + + /* precondition test: if the destination batch id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 6191035775701595700L, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 1.0, randomGenerator); + BatchId sourceBatchId = materialsDataManager.addBatch(batchConstructionInfo); + BatchId destinationBatchId = new BatchId(10000000); + materialsDataManager.transferMaterialBetweenBatches(sourceBatchId, destinationBatchId, 0.1); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + + }); + assertEquals(MaterialsError.UNKNOWN_BATCH_ID, contractException.getErrorType()); + + /* + * precondition test: if the source and destination batches are the same + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 3554772523918373156L, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 1.0, randomGenerator); + BatchId sourceBatchId = materialsDataManager.addBatch(batchConstructionInfo); + BatchId destinationBatchId = sourceBatchId; + materialsDataManager.transferMaterialBetweenBatches(sourceBatchId, destinationBatchId, 0.1); + + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.REFLEXIVE_BATCH_SHIFT, contractException.getErrorType()); + + /* + * precondition test: if the batches do not have the same material type + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 7162782209932339508L, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 1.0, randomGenerator); + BatchId sourceBatchId = materialsDataManager.addBatch(batchConstructionInfo); + batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_2, 1.0, randomGenerator); + BatchId destinationBatchId = materialsDataManager.addBatch(batchConstructionInfo); + materialsDataManager.transferMaterialBetweenBatches(sourceBatchId, destinationBatchId, 0.1); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + + }); + assertEquals(MaterialsError.MATERIAL_TYPE_MISMATCH, contractException.getErrorType()); + + /* + * precondition test: if the batches have different owning materials producers + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 5391338933894482101L, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 1.0, randomGenerator); + BatchId sourceBatchId = materialsDataManager.addBatch(batchConstructionInfo); + batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_2, TestMaterialId.MATERIAL_1, 1.0, randomGenerator); + BatchId destinationBatchId = materialsDataManager.addBatch(batchConstructionInfo); + materialsDataManager.transferMaterialBetweenBatches(sourceBatchId, destinationBatchId, 0.1); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.BATCH_SHIFT_WITH_MULTIPLE_OWNERS, contractException.getErrorType()); + + /* precondition test: if the source batch is on a stage is offered */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 3272165493215915111L, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 1.0, randomGenerator); + BatchId sourceBatchId = materialsDataManager.addBatch(batchConstructionInfo); + StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + materialsDataManager.moveBatchToStage(sourceBatchId, stageId); + materialsDataManager.setStageOfferState(stageId, true); + batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 1.0, randomGenerator); + BatchId destinationBatchId = materialsDataManager.addBatch(batchConstructionInfo); + materialsDataManager.transferMaterialBetweenBatches(sourceBatchId, destinationBatchId, 0.1); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.OFFERED_STAGE_UNALTERABLE, contractException.getErrorType()); + + /* + * precondition test: if the destination batch is on a stage is offered + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 472094558069917703L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 1.0, randomGenerator); + BatchId sourceBatchId = materialsDataManager.addBatch(batchConstructionInfo); + batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 1.0, randomGenerator); + BatchId destinationBatchId = materialsDataManager.addBatch(batchConstructionInfo); + StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + materialsDataManager.moveBatchToStage(destinationBatchId, stageId); + materialsDataManager.setStageOfferState(stageId, true); + materialsDataManager.transferMaterialBetweenBatches(sourceBatchId, destinationBatchId, 0.1); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.OFFERED_STAGE_UNALTERABLE, contractException.getErrorType()); + + /* precondition test: if the shift amount is not a finite number */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 2032850391793850929L, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 1.0, randomGenerator); + BatchId sourceBatchId = materialsDataManager.addBatch(batchConstructionInfo); + batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 1.0, randomGenerator); + BatchId destinationBatchId = materialsDataManager.addBatch(batchConstructionInfo); + materialsDataManager.transferMaterialBetweenBatches(sourceBatchId, destinationBatchId, + Double.POSITIVE_INFINITY); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + + }); + assertEquals(MaterialsError.NON_FINITE_MATERIAL_AMOUNT, contractException.getErrorType()); + + /* precondition test: if the shift amount is negative */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 5245408361837825062L, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 1.0, randomGenerator); + BatchId sourceBatchId = materialsDataManager.addBatch(batchConstructionInfo); + batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 1.0, randomGenerator); + BatchId destinationBatchId = materialsDataManager.addBatch(batchConstructionInfo); + materialsDataManager.transferMaterialBetweenBatches(sourceBatchId, destinationBatchId, -0.5); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + + }); + assertEquals(MaterialsError.NEGATIVE_MATERIAL_AMOUNT, contractException.getErrorType()); + + /* + * precondition test: if the shift amount exceeds the available material on the + * source batch + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 8724998204446972180L, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 1.0, randomGenerator); + BatchId sourceBatchId = materialsDataManager.addBatch(batchConstructionInfo); + batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 1.0, randomGenerator); + BatchId destinationBatchId = materialsDataManager.addBatch(batchConstructionInfo); + materialsDataManager.transferMaterialBetweenBatches(sourceBatchId, destinationBatchId, 2.0); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + + }); + assertEquals(MaterialsError.INSUFFICIENT_MATERIAL_AVAILABLE, contractException.getErrorType()); + + /* + * precondition test: if the shift amount would cause an overflow on the + * destination batch + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 3007064903100070620L, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, Double.MAX_VALUE, + randomGenerator); + BatchId sourceBatchId = materialsDataManager.addBatch(batchConstructionInfo); + batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, Double.MAX_VALUE, + randomGenerator); + BatchId destinationBatchId = materialsDataManager.addBatch(batchConstructionInfo); + double amount = Double.MAX_VALUE / 2; + materialsDataManager.transferMaterialBetweenBatches(sourceBatchId, destinationBatchId, amount); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + + }); + assertEquals(MaterialsError.MATERIAL_ARITHMETIC_EXCEPTION, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "transferOfferedStage", args = { StageId.class, + MaterialsProducerId.class }) + public void testTransferOfferedStage() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + double actionTime = 0; + + // create containers to hold observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + c.subscribe(EventFilter.builder(StageMaterialsProducerUpdateEvent.class).build(), (c2, e) -> { + MultiKey multiKey = new MultiKey(c.getTime(), e.stageId(), e.previousMaterialsProducerId(), + e.currentMaterialsProducerId()); + actualObservations.add(multiKey); + }); + + c.subscribe(EventFilter.builder(StageOfferUpdateEvent.class).build(), (c2, e) -> { + MultiKey multiKey = new MultiKey(c.getTime(), e.stageId()); + actualObservations.add(multiKey); + + }); + })); + + int stagesPerProducer = 5; + int transferCount = stagesPerProducer * TestMaterialsProducerId.values().length; + + // have the materials producers create a few offered stages + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + for (int i = 0; i < stagesPerProducer; i++) { + StageId stageId = materialsDataManager.addStage(testMaterialsProducerId); + for (int j = 0; j < 3; j++) { + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo + .getBatchConstructionInfo(testMaterialsProducerId, + TestMaterialId.getRandomMaterialId(randomGenerator), + randomGenerator.nextDouble() + 0.01, randomGenerator); + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + materialsDataManager.moveBatchToStage(batchId, stageId); + } + materialsDataManager.setStageOfferState(stageId, true); + MultiKey multiKey = new MultiKey(c.getTime(), stageId); + expectedObservations.add(multiKey); + } + })); + } + + // have an agent transfer offered stages + for (int i = 0; i < transferCount; i++) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + // determine the stages to transfer + + List stagesToTransfer = new ArrayList<>(); + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + List offeredStages = materialsDataManager.getOfferedStages(testMaterialsProducerId); + stagesToTransfer.addAll(offeredStages); + } + StageId stageId = stagesToTransfer.get(randomGenerator.nextInt(stagesToTransfer.size())); + + // select a producer at random to receive the transfered + // stage + + MaterialsProducerId stageProducer = materialsDataManager.getStageProducer(stageId); + List candidateProducers = new ArrayList<>( + materialsDataManager.getMaterialsProducerIds()); + candidateProducers.remove(stageProducer); + MaterialsProducerId altProducer = candidateProducers + .get(randomGenerator.nextInt(candidateProducers.size())); + + // transfer the stage + materialsDataManager.transferOfferedStage(stageId, altProducer); + + // show that the stage was properly transferred + assertEquals(altProducer, materialsDataManager.getStageProducer(stageId)); + + // record expected observations + + MultiKey multiKey = new MultiKey(c.getTime(), stageId, stageProducer, altProducer); + expectedObservations.add(multiKey); + + multiKey = new MultiKey(c.getTime(), stageId); + expectedObservations.add(multiKey); + + })); + } + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 3739485201643969207L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the stage id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 1130010427224075392L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + MaterialsProducerId altProducer = TestMaterialsProducerId.MATERIALS_PRODUCER_2; + materialsDataManager.setStageOfferState(stageId, true); + materialsDataManager.transferOfferedStage(null, altProducer); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_STAGE_ID, contractException.getErrorType()); + + /* precondition test: if the stage id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 3682112595474000731L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + MaterialsProducerId altProducer = TestMaterialsProducerId.MATERIALS_PRODUCER_2; + materialsDataManager.setStageOfferState(stageId, true); + materialsDataManager.transferOfferedStage(new StageId(10000000), altProducer); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_STAGE_ID, contractException.getErrorType()); + + /* precondition test: if the materials producer id is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 2900230239814256887L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + materialsDataManager.setStageOfferState(stageId, true); + materialsDataManager.transferOfferedStage(stageId, null); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + /* precondition test: if the materials producer id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 452781590728467653L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + materialsDataManager.setStageOfferState(stageId, true); + materialsDataManager.transferOfferedStage(stageId, + TestMaterialsProducerId.getUnknownMaterialsProducerId()); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + /* precondition test: if the stage is not offered */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 6778669475043282422L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + MaterialsProducerId altProducer = TestMaterialsProducerId.MATERIALS_PRODUCER_2; + materialsDataManager.setStageOfferState(stageId, true); + materialsDataManager.setStageOfferState(stageId, false); + materialsDataManager.transferOfferedStage(stageId, altProducer); + materialsDataManager.setStageOfferState(stageId, true); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNOFFERED_STAGE_NOT_TRANSFERABLE, contractException.getErrorType()); + + /* + * precondition test: if the source and destination materials producers are the + * same + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 4646205108657064829L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + materialsDataManager.setStageOfferState(stageId, true); + materialsDataManager.transferOfferedStage(stageId, TestMaterialsProducerId.MATERIALS_PRODUCER_1); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.REFLEXIVE_STAGE_TRANSFER, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "transferResourceToRegion", args = { + MaterialsProducerId.class, ResourceId.class, RegionId.class, long.class }) + public void testTransferResourceToRegion() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + double actionTime = 0; + + // create containers to hold observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // have a agent observe resource transfers from producers to regions. + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + /* + * When transferring resources from a producer to a region, a + * RegionResourceAdditionEvent is generated. This is not an observation, but is + * the contracted reaction event that is then processed by the resources + * package. To assess that this event is indeed generated we could add a custom + * resolver, but choose instead to have the observer agent subscribe to the + * resulting RegionResourceUpdateEvent + */ + + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + for (TestRegionId testRegionId : TestRegionId.values()) { + for (TestResourceId testResourceId : TestResourceId.values()) { + EventFilter eventFilter = resourcesDataManager + .getEventFilterForRegionResourceUpdateEvent(testResourceId, testRegionId); + c.subscribe(eventFilter, (c2, e) -> { + MultiKey multiKey = new MultiKey(c.getTime(), e.resourceId(), e.regionId(), + e.previousResourceLevel(), e.currentResourceLevel()); + actualObservations.add(multiKey); + }); + } + } + + for (TestResourceId testResourceId : TestResourceId.values()) { + EventFilter eventFilter = materialsDataManager + .getEventFilterForMaterialsProducerResourceUpdateEvent(testResourceId); + c.subscribe(eventFilter, (c2, e) -> { + MultiKey multiKey = new MultiKey(c.getTime(), e.resourceId(), e.materialsProducerId(), + e.previousResourceLevel(), e.currentResourceLevel()); + actualObservations.add(multiKey); + }); + } + + })); + + // have the producers generate some resources + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + for (TestResourceId testResourceId : TestResourceId.values()) { + if (randomGenerator.nextBoolean()) { + StageId stageId = materialsDataManager.addStage(testMaterialsProducerId); + long existingAmount = materialsDataManager + .getMaterialsProducerResourceLevel(testMaterialsProducerId, testResourceId); + long amount = (randomGenerator.nextInt(1000) + 100); + materialsDataManager.convertStageToResource(stageId, testResourceId, amount); + MultiKey multiKey = new MultiKey(c.getTime(), testResourceId, testMaterialsProducerId, + existingAmount, amount + existingAmount); + expectedObservations.add(multiKey); + } + } + + })); + } + + // have an agent distribute the resources over time + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + for (TestResourceId testResourceId : TestResourceId.values()) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + long materialsProducerResourceLevel = materialsDataManager + .getMaterialsProducerResourceLevel(testMaterialsProducerId, testResourceId); + if (materialsProducerResourceLevel > 0) { + long amountToTransfer = randomGenerator.nextInt((int) materialsProducerResourceLevel) + 1; + TestRegionId regionId = TestRegionId.getRandomRegionId(randomGenerator); + long regionResourceLevel = resourcesDataManager.getRegionResourceLevel(regionId, + testResourceId); + materialsDataManager.transferResourceToRegion(testMaterialsProducerId, testResourceId, regionId, + amountToTransfer); + + // show that the resource was transfered + long currentProducerResourceLevel = materialsDataManager + .getMaterialsProducerResourceLevel(testMaterialsProducerId, testResourceId); + long currentRegionResourceLevel = resourcesDataManager.getRegionResourceLevel(regionId, + testResourceId); + + assertEquals(materialsProducerResourceLevel - amountToTransfer, currentProducerResourceLevel); + assertEquals(regionResourceLevel + amountToTransfer, currentRegionResourceLevel); + + // record the expected observations + + MultiKey multiKey = new MultiKey(c.getTime(), testResourceId, regionId, regionResourceLevel, + currentRegionResourceLevel); + expectedObservations.add(multiKey); + multiKey = new MultiKey(c.getTime(), testResourceId, testMaterialsProducerId, + materialsProducerResourceLevel, currentProducerResourceLevel); + expectedObservations.add(multiKey); + + } + })); + } + } + + // have the observer show that the observations are correct + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 2289322490697828226L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the resource id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 1367796071113751106L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; + ResourceId resourceId = TestResourceId.RESOURCE_3; + RegionId regionId = TestRegionId.REGION_4; + long amountToTransfer = 45L; + StageId stageId = materialsDataManager.addStage(materialsProducerId); + materialsDataManager.convertStageToResource(stageId, resourceId, amountToTransfer); + materialsDataManager.transferResourceToRegion(materialsProducerId, null, regionId, amountToTransfer); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the resource id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 8014590590926533288L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; + ResourceId resourceId = TestResourceId.RESOURCE_3; + RegionId regionId = TestRegionId.REGION_4; + long amountToTransfer = 45L; + StageId stageId = materialsDataManager.addStage(materialsProducerId); + materialsDataManager.convertStageToResource(stageId, resourceId, amountToTransfer); + materialsDataManager.transferResourceToRegion(materialsProducerId, + TestResourceId.getUnknownResourceId(), regionId, amountToTransfer); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the region id is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 4865873025074936636L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; + ResourceId resourceId = TestResourceId.RESOURCE_3; + long amountToTransfer = 45L; + StageId stageId = materialsDataManager.addStage(materialsProducerId); + materialsDataManager.convertStageToResource(stageId, resourceId, amountToTransfer); + materialsDataManager.transferResourceToRegion(materialsProducerId, resourceId, null, amountToTransfer); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); + + /* precondition test: if the region id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 4472671173642659805L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; + ResourceId resourceId = TestResourceId.RESOURCE_3; + long amountToTransfer = 45L; + StageId stageId = materialsDataManager.addStage(materialsProducerId); + materialsDataManager.convertStageToResource(stageId, resourceId, amountToTransfer); + materialsDataManager.transferResourceToRegion(materialsProducerId, resourceId, + TestRegionId.getUnknownRegionId(), amountToTransfer); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); + + /* precondition test: if the materials producer id is null */ + + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 6956131170154399460L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; + ResourceId resourceId = TestResourceId.RESOURCE_3; + RegionId regionId = TestRegionId.REGION_4; + long amountToTransfer = 45L; + StageId stageId = materialsDataManager.addStage(materialsProducerId); + materialsDataManager.convertStageToResource(stageId, resourceId, amountToTransfer); + materialsDataManager.transferResourceToRegion(null, resourceId, regionId, amountToTransfer); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + /* precondition test: if the materials producer id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 1760306489660703762L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; + ResourceId resourceId = TestResourceId.RESOURCE_3; + RegionId regionId = TestRegionId.REGION_4; + long amountToTransfer = 45L; + StageId stageId = materialsDataManager.addStage(materialsProducerId); + materialsDataManager.convertStageToResource(stageId, resourceId, amountToTransfer); + materialsDataManager.transferResourceToRegion(TestMaterialsProducerId.getUnknownMaterialsProducerId(), + resourceId, regionId, amountToTransfer); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + /* precondition test: if the materials amount is negative */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 2214714534579989103L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; + ResourceId resourceId = TestResourceId.RESOURCE_3; + RegionId regionId = TestRegionId.REGION_4; + long amountToTransfer = 45L; + StageId stageId = materialsDataManager.addStage(materialsProducerId); + materialsDataManager.convertStageToResource(stageId, resourceId, amountToTransfer); + materialsDataManager.transferResourceToRegion(materialsProducerId, resourceId, regionId, -1L); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.NEGATIVE_RESOURCE_AMOUNT, contractException.getErrorType()); + + /* + * precondition test: if the materials amount exceeds the resource level of the + * materials producer + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 8260344965557977221L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; + ResourceId resourceId = TestResourceId.RESOURCE_3; + RegionId regionId = TestRegionId.REGION_4; + long amountToTransfer = 45L; + StageId stageId = materialsDataManager.addStage(materialsProducerId); + materialsDataManager.convertStageToResource(stageId, resourceId, amountToTransfer); + materialsDataManager.transferResourceToRegion(materialsProducerId, resourceId, regionId, + amountToTransfer * 2); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.INSUFFICIENT_RESOURCES_AVAILABLE, contractException.getErrorType()); + + /* + * precondition test: if the materials amount would cause an overflow of the + * regions resource level + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 4416313459810970424L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; + ResourceId resourceId = TestResourceId.RESOURCE_3; + RegionId regionId = TestRegionId.REGION_4; + long amountToTransfer = 45L; + StageId stageId = materialsDataManager.addStage(materialsProducerId); + materialsDataManager.convertStageToResource(stageId, resourceId, amountToTransfer); + materialsDataManager.transferResourceToRegion(materialsProducerId, resourceId, regionId, + amountToTransfer); + + /* + * There is currently some of the resource present, so we will add half of the + * max value of long two times in a row. That will cause the region to overflow + * while keeping the producer from doing so + * + */ + long hugeAmount = Long.MAX_VALUE / 2; + stageId = materialsDataManager.addStage(materialsProducerId); + materialsDataManager.convertStageToResource(stageId, resourceId, hugeAmount); + materialsDataManager.transferResourceToRegion(materialsProducerId, resourceId, regionId, hugeAmount); + + stageId = materialsDataManager.addStage(materialsProducerId); + materialsDataManager.convertStageToResource(stageId, resourceId, hugeAmount); + materialsDataManager.transferResourceToRegion(materialsProducerId, resourceId, regionId, hugeAmount); + + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.RESOURCE_ARITHMETIC_EXCEPTION, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testResourceIdAddition() { + /* + * Have the actor add a resource id and show that the materials data manager + * will support the addition + */ + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 7336173642619419311L, (c) -> { + ResourceId newResourceId = TestResourceId.getUnknownResourceId(); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.addResourceId(newResourceId, true); + + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + Set materialsProducerIds = materialsDataManager.getMaterialsProducerIds(); + assertTrue(materialsProducerIds.size() > 0); + for (MaterialsProducerId materialsProducerId : materialsProducerIds) { + long materialsProducerResourceLevel = materialsDataManager + .getMaterialsProducerResourceLevel(materialsProducerId, newResourceId); + assertEquals(0, materialsProducerResourceLevel); + } + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + /** + * Demonstrates that the data manager's initial state reflects its plugin data + */ + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testStateInitialization() { + long seed = 5272599676969321594L; + int numBatches = 50; + int numStages = 10; + int numBatchesToStage = 30; + + MaterialsPluginData materialsPluginData = MaterialsTestPluginFactory.getStandardMaterialsPluginData(numBatches, + numStages, numBatchesToStage, seed); + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create an agent to show that materials initial data was properly + // loaded as reflected in the data view + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + + // show that the correct materials producer ids are present + assertEquals(materialsPluginData.getMaterialsProducerIds(), materialsDataManager.getMaterialsProducerIds()); + + // show that the correct material ids are present + assertEquals(materialsPluginData.getMaterialIds(), materialsDataManager.getMaterialIds()); + + // show that the resource ids used for initial resource levels are + // all contained in the resource plugin + assertTrue(resourcesDataManager.getResourceIds().containsAll(materialsPluginData.getResourceIds())); + + // show that the material property ids are correct + assertEquals(materialsPluginData.getMaterialsProducerPropertyIds(), + materialsDataManager.getMaterialsProducerPropertyIds()); + + // show that the material producer property definitions are correct + for (MaterialsProducerPropertyId materialsProducerPropertyId : materialsPluginData + .getMaterialsProducerPropertyIds()) { + assertEquals(materialsPluginData.getMaterialsProducerPropertyDefinition(materialsProducerPropertyId), + materialsDataManager.getMaterialsProducerPropertyDefinition(materialsProducerPropertyId)); + } + + /* + * Show that the material producer property values are correct. Show that the + * initial resource levels are correct. + */ + for (MaterialsProducerId materialsProducerId : materialsPluginData.getMaterialsProducerIds()) { + for (MaterialsProducerPropertyId materialsProducerPropertyId : materialsPluginData + .getMaterialsProducerPropertyIds()) { + Map materialsProducerPropertyValues = materialsPluginData + .getMaterialsProducerPropertyValues(materialsProducerId); + Object expectedValue = materialsProducerPropertyValues.get(materialsProducerPropertyId); + if (expectedValue == null) { + PropertyDefinition propertyDefinition = materialsPluginData + .getMaterialsProducerPropertyDefinition(materialsProducerPropertyId); + expectedValue = propertyDefinition.getDefaultValue().get(); + } + Object actualValue = materialsDataManager.getMaterialsProducerPropertyValue(materialsProducerId, + materialsProducerPropertyId); + assertEquals(expectedValue, actualValue); + } + for (ResourceId resourceId : resourcesDataManager.getResourceIds()) { + Long expectedResourceLevel = materialsPluginData + .getMaterialsProducerResourceLevel(materialsProducerId, resourceId); + Long actualResourceLevel = materialsDataManager + .getMaterialsProducerResourceLevel(materialsProducerId, resourceId); + assertEquals(expectedResourceLevel, actualResourceLevel); + } + } + /* + * Show that each material is associated with the correct batch property ids. + * Show that each batch property id has the correct definition. + */ + for (MaterialId materialId : materialsPluginData.getMaterialIds()) { + assertEquals(materialsPluginData.getBatchPropertyIds(materialId), + materialsDataManager.getBatchPropertyIds(materialId)); + for (BatchPropertyId batchPropertyId : materialsPluginData.getBatchPropertyIds(materialId)) { + PropertyDefinition expectedPropertyDefinition = materialsPluginData + .getBatchPropertyDefinition(materialId, batchPropertyId); + PropertyDefinition actualPropertyDefinition = materialsDataManager + .getBatchPropertyDefinition(materialId, batchPropertyId); + assertEquals(expectedPropertyDefinition, actualPropertyDefinition); + } + } + + // Show that the correct initial batches are present. + Set expectedBatchIds = new LinkedHashSet<>(); + for (BatchId batchId : materialsPluginData.getBatchIds()) { + expectedBatchIds.add(batchId); + } + + Set actualBatchIds = new LinkedHashSet<>(); + for (MaterialsProducerId materialsProducerId : materialsDataManager.getMaterialsProducerIds()) { + actualBatchIds.addAll(materialsDataManager.getInventoryBatches(materialsProducerId)); + List stages = materialsDataManager.getStages(materialsProducerId); + for (StageId stageId : stages) { + actualBatchIds.addAll(materialsDataManager.getStageBatches(stageId)); + } + } + assertEquals(expectedBatchIds, actualBatchIds); + + // Show that the batches have the correct material id, and amounts + + for (BatchId batchId : materialsPluginData.getBatchIds()) { + + MaterialId expectedMaterialId = materialsPluginData.getBatchMaterial(batchId); + MaterialId actualMaterialId = materialsDataManager.getBatchMaterial(batchId); + assertEquals(expectedMaterialId, actualMaterialId); + + double expectedAmount = materialsPluginData.getBatchAmount(batchId); + double actualAmount = materialsDataManager.getBatchAmount(batchId); + assertEquals(expectedAmount, actualAmount); + + for (BatchPropertyId batchPropertyId : materialsPluginData.getBatchPropertyIds(expectedMaterialId)) { + Object expectedValue = materialsPluginData.getBatchPropertyValues(batchId).get(batchPropertyId); + if (expectedValue == null) { + PropertyDefinition propertyDefinition = materialsPluginData + .getBatchPropertyDefinition(expectedMaterialId, batchPropertyId); + expectedValue = propertyDefinition.getDefaultValue().get(); + } + Object actualValue = materialsDataManager.getBatchPropertyValue(batchId, batchPropertyId); + assertEquals(expectedValue, actualValue); + } + + } + // Show that the inventory batches match + for (MaterialsProducerId materialsProducerId : materialsPluginData.getMaterialsProducerIds()) { + Set expectedBatches = new LinkedHashSet<>( + materialsPluginData.getMaterialsProducerInventoryBatches(materialsProducerId)); + Set actualBatches = new LinkedHashSet<>( + materialsDataManager.getInventoryBatches(materialsProducerId)); + assertEquals(expectedBatches, actualBatches); + } + + // Show that the correct initial stages are present with their + // normalized id values. + Set expectedStageIds = new LinkedHashSet<>(); + for (StageId stageId : materialsPluginData.getStageIds()) { + expectedStageIds.add(stageId); + } + + Set actualStageIds = new LinkedHashSet<>(); + for (MaterialsProducerId materialsProducerId : materialsDataManager.getMaterialsProducerIds()) { + actualStageIds.addAll(materialsDataManager.getStages(materialsProducerId)); + + } + assertEquals(expectedStageIds, actualStageIds); + + for (StageId stageId : materialsPluginData.getStageIds()) { + + boolean expectedOfferState = materialsPluginData.isStageOffered(stageId); + boolean actualOfferStage = materialsDataManager.isStageOffered(stageId); + assertEquals(expectedOfferState, actualOfferStage); + + Set expectedBatches = new LinkedHashSet<>(); + for (BatchId batchId : materialsPluginData.getStageBatches(stageId)) { + expectedBatches.add(batchId); + } + Set actualBatches = new LinkedHashSet<>(materialsDataManager.getStageBatches(stageId)); + assertEquals(expectedBatches, actualBatches); + } + + for (MaterialsProducerId materialsProducerId : materialsPluginData.getMaterialsProducerIds()) { + Set actualStagesForProducer = new LinkedHashSet<>( + materialsDataManager.getStages(materialsProducerId)); + Set expectedStagesForProducer = new LinkedHashSet<>( + materialsPluginData.getMaterialsProducerStages(materialsProducerId)); + assertEquals(expectedStagesForProducer, actualStagesForProducer); + } + + })); + + // add the test plugin + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory + .factory(numBatches, numStages, numBatchesToStage, seed, testPluginData) + .setMaterialsPluginData(materialsPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "addMaterialsProducer", args = { + MaterialsProducerConstructionData.class }) + public void testAddMaterialsProducer() { + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(20, (c) -> { + c.subscribe(EventFilter.builder(MaterialsProducerAdditionEvent.class).build(), (c2, e) -> { + MultiKey multiKey = new MultiKey(c2.getTime(), e.getMaterialsProducerId()); + actualObservations.add(multiKey); + }); + })); + + // show that a new materials producer can be added + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(20, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialsProducerId newMaterialsProducerId = TestMaterialsProducerId.getUnknownMaterialsProducerId(); + assertFalse(materialsDataManager.materialsProducerIdExists(newMaterialsProducerId)); + + MaterialsProducerConstructionData.Builder builder // + = MaterialsProducerConstructionData.builder()// + .setMaterialsProducerId(newMaterialsProducerId);// + + Map expectedPropertyValues = new LinkedHashMap<>(); + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .values()) { + Optional optional = testMaterialsProducerPropertyId.getPropertyDefinition().getDefaultValue(); + if (optional.isPresent()) { + expectedPropertyValues.put(testMaterialsProducerPropertyId, optional.get()); + } else { + Object randomPropertyValue = testMaterialsProducerPropertyId + .getRandomPropertyValue(randomGenerator); + builder.setMaterialsProducerPropertyValue(testMaterialsProducerPropertyId, randomPropertyValue); + expectedPropertyValues.put(testMaterialsProducerPropertyId, randomPropertyValue); + } + } + + MaterialsProducerConstructionData materialsProducerConstructionData = builder.build(); + + materialsDataManager.addMaterialsProducer(materialsProducerConstructionData); + assertTrue(materialsDataManager.materialsProducerIdExists(newMaterialsProducerId)); + + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .values()) { + Object expectedValue = expectedPropertyValues.get(testMaterialsProducerPropertyId); + Object actualValue = materialsDataManager.getMaterialsProducerPropertyValue(newMaterialsProducerId, + testMaterialsProducerPropertyId); + assertEquals(expectedValue, actualValue); + } + + long expectedLevel = 0; + for (TestResourceId testResourceId : TestResourceId.values()) { + long actualLevel = materialsDataManager.getMaterialsProducerResourceLevel(newMaterialsProducerId, + testResourceId); + assertEquals(expectedLevel, actualLevel); + } + + MultiKey multiKey = new MultiKey(c.getTime(), newMaterialsProducerId); + expectedObservations.add(multiKey); + })); + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(21, (c) -> { + assertFalse(expectedObservations.isEmpty()); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 7934044435210594542L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* + * precondition test: if the materials producer id is already present + */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 6261955547781316622L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsProducerConstructionData.Builder builder = // + MaterialsProducerConstructionData.builder(); + builder.setMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .getPropertiesWithoutDefaultValues()) { + Object randomPropertyValue = testMaterialsProducerPropertyId + .getRandomPropertyValue(randomGenerator); + builder.setMaterialsProducerPropertyValue(testMaterialsProducerPropertyId, randomPropertyValue); + } + MaterialsProducerConstructionData materialsProducerConstructionData = builder.build(); + materialsDataManager.addMaterialsProducer(materialsProducerConstructionData); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.DUPLICATE_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + /* + * precondition test: if the materialsProducerConstructionData does not contain + * a property value for any corresponding materials producer property definition + * that lacks a default value + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 1777796798041842032L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialsProducerId newMaterialsProducerId = TestMaterialsProducerId.getUnknownMaterialsProducerId(); + MaterialsProducerConstructionData materialsProducerConstructionData = MaterialsProducerConstructionData + .builder().setMaterialsProducerId(newMaterialsProducerId).build(); + materialsDataManager.addMaterialsProducer(materialsProducerConstructionData); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT, contractException.getErrorType()); + + /* + * precondition test: if the materialsProducerConstructionData contains a + * property value assignment for an unknown materials producer property id. + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 405967616866830371L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsProducerConstructionData.Builder builder = // + MaterialsProducerConstructionData.builder(); + builder.setMaterialsProducerId(TestMaterialsProducerId.getUnknownMaterialsProducerId()); + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .getPropertiesWithoutDefaultValues()) { + Object randomPropertyValue = testMaterialsProducerPropertyId + .getRandomPropertyValue(randomGenerator); + builder.setMaterialsProducerPropertyValue(testMaterialsProducerPropertyId, randomPropertyValue); + } + + builder.setMaterialsProducerPropertyValue( + TestMaterialsProducerPropertyId.getUnknownMaterialsProducerPropertyId(), 10); + + MaterialsProducerConstructionData materialsProducerConstructionData = builder.build(); + + materialsDataManager.addMaterialsProducer(materialsProducerConstructionData); + + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "defineBatchProperty", args = { + BatchPropertyDefinitionInitialization.class }) + public void testDefineBatchProperty() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7985084158958183488L); + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + c.subscribe(EventFilter.builder(BatchPropertyDefinitionEvent.class).build(), (c2, e) -> { + MultiKey multiKey = new MultiKey(c2.getTime(), e.materialId(), e.batchPropertyId()); + actualObservations.add(multiKey); + }); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class) + .setDefaultValue(12).build(); + BatchPropertyId batchPropertyId = TestBatchPropertyId.getUnknownBatchPropertyId(); + MaterialId materialId = TestMaterialId.MATERIAL_1; + BatchPropertyDefinitionInitialization batchPropertyDefinitionInitialization = BatchPropertyDefinitionInitialization + .builder().setMaterialId(materialId).setPropertyId(batchPropertyId) + .setPropertyDefinition(propertyDefinition).build(); + materialsDataManager.defineBatchProperty(batchPropertyDefinitionInitialization); + MultiKey multiKey = new MultiKey(c.getTime(), materialId, batchPropertyId); + expectedObservations.add(multiKey); + assertTrue(materialsDataManager.getBatchPropertyIds(materialId).contains(batchPropertyId)); + PropertyDefinition actualPropertyDefinition = materialsDataManager.getBatchPropertyDefinition(materialId, + batchPropertyId); + assertEquals(propertyDefinition, actualPropertyDefinition); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(String.class) + .setDefaultValue("default").build(); + BatchPropertyId batchPropertyId = TestBatchPropertyId.getUnknownBatchPropertyId(); + MaterialId materialId = TestMaterialId.MATERIAL_2; + BatchPropertyDefinitionInitialization batchPropertyDefinitionInitialization = BatchPropertyDefinitionInitialization + .builder().setMaterialId(materialId).setPropertyId(batchPropertyId) + .setPropertyDefinition(propertyDefinition).build(); + materialsDataManager.defineBatchProperty(batchPropertyDefinitionInitialization); + MultiKey multiKey = new MultiKey(c.getTime(), materialId, batchPropertyId); + expectedObservations.add(multiKey); + assertTrue(materialsDataManager.getBatchPropertyIds(materialId).contains(batchPropertyId)); + PropertyDefinition actualPropertyDefinition = materialsDataManager.getBatchPropertyDefinition(materialId, + batchPropertyId); + assertEquals(propertyDefinition, actualPropertyDefinition); + })); + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(3, (c) -> { + assertFalse(expectedObservations.isEmpty()); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 2434116219643564071L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* + * precondition test: if the material id is unknown + */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 3376758409444036216L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class) + .setDefaultValue(12).build(); + BatchPropertyId batchPropertyId = TestBatchPropertyId.getUnknownBatchPropertyId(); + MaterialId materialId = TestMaterialId.getUnknownMaterialId(); + + BatchPropertyDefinitionInitialization batchPropertyDefinitionInitialization = // + BatchPropertyDefinitionInitialization.builder()// + .setMaterialId(materialId)// + .setPropertyId(batchPropertyId)// + .setPropertyDefinition(propertyDefinition)// + .build(); + materialsDataManager.defineBatchProperty(batchPropertyDefinitionInitialization); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_MATERIAL_ID, contractException.getErrorType()); + + /* + * precondition test: if the batch property id is already present + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 7152319084879177681L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class) + .setDefaultValue(12).build(); + BatchPropertyId batchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK; + MaterialId materialId = TestMaterialId.MATERIAL_1; + + BatchPropertyDefinitionInitialization batchPropertyDefinitionInitialization = // + BatchPropertyDefinitionInitialization.builder()// + .setMaterialId(materialId)// + .setPropertyId(batchPropertyId)// + .setPropertyDefinition(propertyDefinition)// + .build(); + + materialsDataManager.defineBatchProperty(batchPropertyDefinitionInitialization); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.DUPLICATE_PROPERTY_DEFINITION, contractException.getErrorType()); + + // precondition test: if a batch property value assignment has an + // unknown batch id + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 8540977102873288312L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).build(); + BatchPropertyId batchPropertyId = TestBatchPropertyId.getUnknownBatchPropertyId(); + MaterialId materialId = TestMaterialId.MATERIAL_1; + + BatchPropertyDefinitionInitialization batchPropertyDefinitionInitialization = // + BatchPropertyDefinitionInitialization.builder()// + .setMaterialId(materialId)// + .setPropertyId(batchPropertyId)// + .setPropertyDefinition(propertyDefinition)// + .addPropertyValue(new BatchId(765), 88)// + .build(); + + materialsDataManager.defineBatchProperty(batchPropertyDefinitionInitialization); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + + }); + assertEquals(MaterialsError.UNKNOWN_BATCH_ID, contractException.getErrorType()); + + /* + * precondition test: if a batch property value assignment has a batch id + * associated with a different material id type + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 7122546603543728978L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).build(); + BatchPropertyId batchPropertyId = TestBatchPropertyId.getUnknownBatchPropertyId(); + MaterialId materialId = TestMaterialId.MATERIAL_1; + MaterialId altMaterialId = TestMaterialId.MATERIAL_2; + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo( + TestMaterialsProducerId.MATERIALS_PRODUCER_1, altMaterialId, 345.0, randomGenerator); + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + + BatchPropertyDefinitionInitialization batchPropertyDefinitionInitialization = // + BatchPropertyDefinitionInitialization.builder()// + .setMaterialId(materialId)// + .setPropertyId(batchPropertyId)// + .setPropertyDefinition(propertyDefinition)// + .addPropertyValue(batchId, 45)// + .build(); + + materialsDataManager.defineBatchProperty(batchPropertyDefinitionInitialization); + + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + + }); + assertEquals(MaterialsError.MATERIAL_TYPE_MISMATCH, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "addMaterialId", args = { MaterialId.class }) + public void testAddMaterialId() { + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + c.subscribe(EventFilter.builder(MaterialIdAdditionEvent.class).build(), (c2, e) -> { + MultiKey multiKey = new MultiKey(c2.getTime(), e.materialId()); + actualObservations.add(multiKey); + }); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialId newMaterialId = TestMaterialId.getUnknownMaterialId(); + materialsDataManager.addMaterialId(newMaterialId); + MultiKey multiKey = new MultiKey(c.getTime(), newMaterialId); + expectedObservations.add(multiKey); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialId newMaterialId = TestMaterialId.getUnknownMaterialId(); + materialsDataManager.addMaterialId(newMaterialId); + MultiKey multiKey = new MultiKey(c.getTime(), newMaterialId); + expectedObservations.add(multiKey); + })); + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(3, (c) -> { + assertFalse(expectedObservations.isEmpty()); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 2713286843450316570L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* + * precondition test: if the material id is null + */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 801838096204060748L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.addMaterialId(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_MATERIAL_ID, contractException.getErrorType()); + + /* + * precondition test: if the material id is already present + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 4318358212946306160L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.addMaterialId(TestMaterialId.MATERIAL_1); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.DUPLICATE_MATERIAL, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getEventFilterForMaterialsProducerPropertyUpdateEvent", args = { + MaterialsProducerId.class, MaterialsProducerPropertyId.class }) + public void testGetEventFilterForMaterialsProducerPropertyUpdateEvent_Producer_Property() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + double actionTime = 0; + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + Set> selectedPairs = new LinkedHashSet<>(); + selectedPairs.add(new Pair<>(TestMaterialsProducerId.MATERIALS_PRODUCER_1, + TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK)); + selectedPairs.add(new Pair<>(TestMaterialsProducerId.MATERIALS_PRODUCER_1, + TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK)); + selectedPairs.add(new Pair<>(TestMaterialsProducerId.MATERIALS_PRODUCER_1, + TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK)); + selectedPairs.add(new Pair<>(TestMaterialsProducerId.MATERIALS_PRODUCER_2, + TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_4_BOOLEAN_MUTABLE_TRACK)); + selectedPairs.add(new Pair<>(TestMaterialsProducerId.MATERIALS_PRODUCER_2, + TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_5_INTEGER_MUTABLE_TRACK)); + selectedPairs.add(new Pair<>(TestMaterialsProducerId.MATERIALS_PRODUCER_2, + TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_6_DOUBLE_MUTABLE_TRACK)); + + // have an agent observe all changes to the selected producer/property + // pairs + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + for (Pair pair : selectedPairs) { + MaterialsProducerId materialsProducerId = pair.getFirst(); + MaterialsProducerPropertyId materialsProducerPropertyId = pair.getSecond(); + EventFilter eventFilter = materialsDataManager + .getEventFilterForMaterialsProducerPropertyUpdateEvent(materialsProducerId, + materialsProducerPropertyId); + c.subscribe(eventFilter, (c2, e) -> { + MultiKey multiKey = new MultiKey(c2.getTime(), e.materialsProducerId(), + e.materialsProducerPropertyId()); + actualObservations.add(multiKey); + }); + } + })); + + for (int i = 0; i < 200; i++) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + // pick a random materials producer property and update it to a + // random value + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsProducerId materialsProducerId = TestMaterialsProducerId + .getRandomMaterialsProducerId(randomGenerator); + TestMaterialsProducerPropertyId testMaterialsProducerPropertyId = TestMaterialsProducerPropertyId + .getRandomMutableMaterialsProducerPropertyId(randomGenerator); + Object newValue = testMaterialsProducerPropertyId.getRandomPropertyValue(randomGenerator); + materialsDataManager.setMaterialsProducerPropertyValue(materialsProducerId, + testMaterialsProducerPropertyId, newValue); + + Pair pair = new Pair<>(materialsProducerId, + testMaterialsProducerPropertyId); + if (selectedPairs.contains(pair)) { + MultiKey multiKey = new MultiKey(c.getTime(), materialsProducerId, testMaterialsProducerPropertyId); + expectedObservations.add(multiKey); + } + })); + } + + // have the observer show that the proper observations were generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 462390115779917577L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the materials producer id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 4377185528158333370L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialsProducerId materialsProducerId = null; + MaterialsProducerPropertyId materialsProducerPropertyId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK; + materialsDataManager.getEventFilterForMaterialsProducerPropertyUpdateEvent(materialsProducerId, + materialsProducerPropertyId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + /* precondition test: if the materials producer id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 7996800014350194555L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialsProducerPropertyId materialsProducerPropertyId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK; + MaterialsProducerId materialsProducerId = TestMaterialsProducerId.getUnknownMaterialsProducerId(); + materialsDataManager.getEventFilterForMaterialsProducerPropertyUpdateEvent(materialsProducerId, + materialsProducerPropertyId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + /* precondition test: if the materials producer property id is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 3912751053563409579L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialsProducerPropertyId materialsProducerPropertyId = null; + MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; + materialsDataManager.getEventFilterForMaterialsProducerPropertyUpdateEvent(materialsProducerId, + materialsProducerPropertyId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + /* + * precondition test: if the materials producer property id is unknown + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 3228733928828489429L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialsProducerPropertyId materialsProducerPropertyId = TestMaterialsProducerPropertyId + .getUnknownMaterialsProducerPropertyId(); + MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; + materialsDataManager.getEventFilterForMaterialsProducerPropertyUpdateEvent(materialsProducerId, + materialsProducerPropertyId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getEventFilterForMaterialsProducerPropertyUpdateEvent", args = {}) + public void testGetEventFilterForMaterialsProducerPropertyUpdateEvent() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + double actionTime = 0; + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // have an agent observe all changes producer properties + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + EventFilter eventFilter = materialsDataManager + .getEventFilterForMaterialsProducerPropertyUpdateEvent(); + c.subscribe(eventFilter, (c2, e) -> { + MultiKey multiKey = new MultiKey(c2.getTime(), e.materialsProducerId(), + e.materialsProducerPropertyId()); + actualObservations.add(multiKey); + }); + + })); + + for (int i = 0; i < 200; i++) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + // pick a random materials producer property and update it to a + // random value + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsProducerId materialsProducerId = TestMaterialsProducerId + .getRandomMaterialsProducerId(randomGenerator); + TestMaterialsProducerPropertyId testMaterialsProducerPropertyId = TestMaterialsProducerPropertyId + .getRandomMutableMaterialsProducerPropertyId(randomGenerator); + Object newValue = testMaterialsProducerPropertyId.getRandomPropertyValue(randomGenerator); + materialsDataManager.setMaterialsProducerPropertyValue(materialsProducerId, + testMaterialsProducerPropertyId, newValue); + MultiKey multiKey = new MultiKey(c.getTime(), materialsProducerId, testMaterialsProducerPropertyId); + expectedObservations.add(multiKey); + + })); + } + + // have the observer show that the proper observations were generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 6818565317427197123L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getEventFilterForMaterialsProducerResourceUpdateEvent", args = { + MaterialsProducerId.class, ResourceId.class }) + public void testGetEventFilterForMaterialsProducerResourceUpdateEvent_Producer_Resource() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + double actionTime = 0; + + // create containers to hold observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + Set> selectedPairs = new LinkedHashSet<>(); + selectedPairs.add(new Pair<>(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestResourceId.RESOURCE_1)); + selectedPairs.add(new Pair<>(TestMaterialsProducerId.MATERIALS_PRODUCER_2, TestResourceId.RESOURCE_2)); + selectedPairs.add(new Pair<>(TestMaterialsProducerId.MATERIALS_PRODUCER_2, TestResourceId.RESOURCE_3)); + selectedPairs.add(new Pair<>(TestMaterialsProducerId.MATERIALS_PRODUCER_3, TestResourceId.RESOURCE_4)); + selectedPairs.add(new Pair<>(TestMaterialsProducerId.MATERIALS_PRODUCER_3, TestResourceId.RESOURCE_5)); + + // have an actor observe the selected producer/resource updates + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + for (Pair pair : selectedPairs) { + MaterialsProducerId materialsProducerId = pair.getFirst(); + ResourceId resourceId = pair.getSecond(); + EventFilter eventFilter = materialsDataManager + .getEventFilterForMaterialsProducerResourceUpdateEvent(materialsProducerId, resourceId); + + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.materialsProducerId(), e.resourceId())); + }); + } + })); + + // have the producers generate batches via stage conversion + for (int i = 0; i < 10; i++) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsProducerId materialsProducerId = TestMaterialsProducerId + .getRandomMaterialsProducerId(randomGenerator); + ResourceId resourceId = TestResourceId.getRandomResourceId(randomGenerator); + StageId stageId = materialsDataManager.addStage(materialsProducerId); + materialsDataManager.convertStageToResource(stageId, resourceId, 2L); + Pair pair = new Pair<>(materialsProducerId, resourceId); + if (selectedPairs.contains(pair)) { + expectedObservations.add(new MultiKey(c.getTime(), materialsProducerId, resourceId)); + } + })); + } + + // have the observer show that the correct observations were generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 1943593849394263760L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the material producer id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 7226633686166745691L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialsProducerId materialsProducerId = null; + ResourceId resourceId = TestResourceId.RESOURCE_1; + materialsDataManager.getEventFilterForMaterialsProducerResourceUpdateEvent(materialsProducerId, + resourceId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + /* precondition test: if the material producer id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 7355528104369898437L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialsProducerId materialsProducerId = TestMaterialsProducerId.getUnknownMaterialsProducerId(); + ResourceId resourceId = TestResourceId.RESOURCE_1; + materialsDataManager.getEventFilterForMaterialsProducerResourceUpdateEvent(materialsProducerId, + resourceId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + /* precondition test: if the resource id is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 767647396762963328L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; + ResourceId resourceId = null; + materialsDataManager.getEventFilterForMaterialsProducerResourceUpdateEvent(materialsProducerId, + resourceId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if resource id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 6053842863116555591L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; + ResourceId resourceId = TestResourceId.getUnknownResourceId(); + materialsDataManager.getEventFilterForMaterialsProducerResourceUpdateEvent(materialsProducerId, + resourceId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getEventFilterForMaterialsProducerResourceUpdateEvent", args = { + ResourceId.class }) + public void testGetEventFilterForMaterialsProducerResourceUpdateEvent_Resource() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + double actionTime = 0; + + // create containers to hold observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + Set selectedResources = new LinkedHashSet<>(); + selectedResources.add(TestResourceId.RESOURCE_1); + selectedResources.add(TestResourceId.RESOURCE_3); + selectedResources.add(TestResourceId.RESOURCE_5); + + // have an actor observe the selected resource updates + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + for (ResourceId resourceId : selectedResources) { + EventFilter eventFilter = materialsDataManager + .getEventFilterForMaterialsProducerResourceUpdateEvent(resourceId); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.materialsProducerId(), e.resourceId())); + }); + } + })); + + // have the producers generate batches via stage conversion + for (int i = 0; i < 10; i++) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsProducerId materialsProducerId = TestMaterialsProducerId + .getRandomMaterialsProducerId(randomGenerator); + ResourceId resourceId = TestResourceId.getRandomResourceId(randomGenerator); + StageId stageId = materialsDataManager.addStage(materialsProducerId); + materialsDataManager.convertStageToResource(stageId, resourceId, 2L); + if (selectedResources.contains(resourceId)) { + expectedObservations.add(new MultiKey(c.getTime(), materialsProducerId, resourceId)); + } + })); + } + + // have the observer show that the correct observations were generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 4810995292619714100L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the resource id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 7308248516735541073L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + ResourceId resourceId = null; + materialsDataManager.getEventFilterForMaterialsProducerResourceUpdateEvent(resourceId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if resource id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 451212875681013142L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + ResourceId resourceId = TestResourceId.getUnknownResourceId(); + materialsDataManager.getEventFilterForMaterialsProducerResourceUpdateEvent(resourceId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getEventFilterForMaterialsProducerResourceUpdateEvent", args = {}) + public void testGetEventFilterForMaterialsProducerResourceUpdateEvent() { + // return EventFilter + // .builder(MaterialsProducerResourceUpdateEvent.class)// + // .build(); + + /* + * Returns an event filter used to subscribe to {@link + * MaterialsProducerResourceUpdateEvent} events. Matches on all such events. + */ + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + double actionTime = 0; + + // create containers to hold observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // have an actor observe all resource updates + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + EventFilter eventFilter = materialsDataManager + .getEventFilterForMaterialsProducerResourceUpdateEvent(); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.materialsProducerId(), e.resourceId())); + }); + })); + + // have the producers generate batches via stage conversion + for (int i = 0; i < 10; i++) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsProducerId materialsProducerId = TestMaterialsProducerId + .getRandomMaterialsProducerId(randomGenerator); + ResourceId resourceId = TestResourceId.getRandomResourceId(randomGenerator); + StageId stageId = materialsDataManager.addStage(materialsProducerId); + materialsDataManager.convertStageToResource(stageId, resourceId, 2L); + expectedObservations.add(new MultiKey(c.getTime(), materialsProducerId, resourceId)); + })); + } + + // have the observer show that the correct observations were generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 7573518940281736875L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getEventFilterForStageMaterialsProducerUpdateEvent_BySource", args = { + MaterialsProducerId.class }) + public void testGetEventFilterForStageMaterialsProducerUpdateEvent_Source() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + double actionTime = 0; + + // create containers to hold observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + Set selectedProducers = new LinkedHashSet<>(); + selectedProducers.add(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + selectedProducers.add(TestMaterialsProducerId.MATERIALS_PRODUCER_2); + + /* + * Have an actor observe all stage transfers where the source of the stage is + * one of the selected material producers + */ + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + for (MaterialsProducerId materialsProducerId : selectedProducers) { + EventFilter eventFilter = materialsDataManager + .getEventFilterForStageMaterialsProducerUpdateEvent_BySource(materialsProducerId); + c.subscribe(eventFilter, (c2, e) -> { + MultiKey multiKey = new MultiKey(c.getTime(), e.stageId(), e.previousMaterialsProducerId(), + e.currentMaterialsProducerId()); + actualObservations.add(multiKey); + }); + } + })); + + // have an actor transfer stages randomly between producers + for (int i = 0; i < 100; i++) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + TestMaterialsProducerId sourceProducerId = TestMaterialsProducerId + .getRandomMaterialsProducerId(randomGenerator); + TestMaterialsProducerId destinationProducerId; + do { + destinationProducerId = TestMaterialsProducerId.getRandomMaterialsProducerId(randomGenerator); + } while (sourceProducerId.equals(destinationProducerId)); + + StageId stageId = materialsDataManager.addStage(sourceProducerId); + materialsDataManager.setStageOfferState(stageId, true); + materialsDataManager.transferOfferedStage(stageId, destinationProducerId); + if (selectedProducers.contains(sourceProducerId)) { + MultiKey multiKey = new MultiKey(c.getTime(), stageId, sourceProducerId, destinationProducerId); + expectedObservations.add(multiKey); + } + })); + } + + // have the observer show that the observations were as expected + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 4736078884804179967L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the materials producer id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 3035182036041809215L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialsProducerId materialsProducerId = null; + materialsDataManager.getEventFilterForStageMaterialsProducerUpdateEvent_BySource(materialsProducerId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + /* precondition test: if the materials producer id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 5900407295303039835L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialsProducerId materialsProducerId = TestMaterialsProducerId.getUnknownMaterialsProducerId(); + materialsDataManager.getEventFilterForStageMaterialsProducerUpdateEvent_BySource(materialsProducerId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getEventFilterForStageMaterialsProducerUpdateEvent_ByDestination", args = { + MaterialsProducerId.class }) + public void testGetEventFilterForStageMaterialsProducerUpdateEvent_Destination() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + double actionTime = 0; + + // create containers to hold observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + Set selectedProducers = new LinkedHashSet<>(); + selectedProducers.add(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + selectedProducers.add(TestMaterialsProducerId.MATERIALS_PRODUCER_2); + + /* + * Have an actor observe all stage transfers where the source of the stage is + * one of the selected material producers + */ + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + for (MaterialsProducerId materialsProducerId : selectedProducers) { + EventFilter eventFilter = materialsDataManager + .getEventFilterForStageMaterialsProducerUpdateEvent_ByDestination(materialsProducerId); + c.subscribe(eventFilter, (c2, e) -> { + MultiKey multiKey = new MultiKey(c.getTime(), e.stageId(), e.previousMaterialsProducerId(), + e.currentMaterialsProducerId()); + actualObservations.add(multiKey); + }); + } + })); + + // have an actor transfer stages randomly between producers + for (int i = 0; i < 100; i++) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + TestMaterialsProducerId sourceProducerId = TestMaterialsProducerId + .getRandomMaterialsProducerId(randomGenerator); + TestMaterialsProducerId destinationProducerId; + do { + destinationProducerId = TestMaterialsProducerId.getRandomMaterialsProducerId(randomGenerator); + } while (sourceProducerId.equals(destinationProducerId)); + + StageId stageId = materialsDataManager.addStage(sourceProducerId); + materialsDataManager.setStageOfferState(stageId, true); + materialsDataManager.transferOfferedStage(stageId, destinationProducerId); + if (selectedProducers.contains(destinationProducerId)) { + MultiKey multiKey = new MultiKey(c.getTime(), stageId, sourceProducerId, destinationProducerId); + expectedObservations.add(multiKey); + } + })); + } + + // have the observer show that the observations were as expected + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 6640940500286757658L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the materials producer id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 8942138506228493899L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialsProducerId materialsProducerId = null; + materialsDataManager + .getEventFilterForStageMaterialsProducerUpdateEvent_ByDestination(materialsProducerId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + /* precondition test: if the materials producer id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 5949527361688842922L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialsProducerId materialsProducerId = TestMaterialsProducerId.getUnknownMaterialsProducerId(); + materialsDataManager + .getEventFilterForStageMaterialsProducerUpdateEvent_ByDestination(materialsProducerId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getEventFilterForStageMaterialsProducerUpdateEvent", args = { + StageId.class }) + public void testGetEventFilterForStageMaterialsProducerUpdateEvent_Stage() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + double actionTime = 0; + + // create containers to hold observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + Set selectedStages = new LinkedHashSet<>(); + Set nonSelectedStages = new LinkedHashSet<>(); + + // have an actor create multiple stage, some of which will be observed + // in transfer by the observer + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + Set materialsProducerIds = materialsDataManager.getMaterialsProducerIds(); + for (MaterialsProducerId materialsProducerId : materialsProducerIds) { + for (int i = 0; i < 10; i++) { + StageId stageId = materialsDataManager.addStage(materialsProducerId); + materialsDataManager.setStageOfferState(stageId, true); + selectedStages.add(stageId); + stageId = materialsDataManager.addStage(materialsProducerId); + materialsDataManager.setStageOfferState(stageId, true); + nonSelectedStages.add(stageId); + } + } + + })); + + /* + * Have an actor observe all stage transfers where the stage id is one of the + * selected stages + */ + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + for (StageId stageId : selectedStages) { + EventFilter eventFilter = materialsDataManager + .getEventFilterForStageMaterialsProducerUpdateEvent(stageId); + c.subscribe(eventFilter, (c2, e) -> { + MultiKey multiKey = new MultiKey(c.getTime(), e.stageId(), e.previousMaterialsProducerId(), + e.currentMaterialsProducerId()); + actualObservations.add(multiKey); + }); + } + })); + + // have an actor transfer stages randomly between producers + for (int i = 0; i < 100; i++) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + Set materialsProducerIds = materialsDataManager.getMaterialsProducerIds(); + + for (MaterialsProducerId sourceProducerId : materialsProducerIds) { + List stages = materialsDataManager.getStages(sourceProducerId); + TestMaterialsProducerId destinationProducerId; + for (StageId stageId : stages) { + if (materialsDataManager.isStageOffered(stageId)) { + do { + destinationProducerId = TestMaterialsProducerId + .getRandomMaterialsProducerId(randomGenerator); + } while (sourceProducerId.equals(destinationProducerId)); + materialsDataManager.transferOfferedStage(stageId, destinationProducerId); + if (selectedStages.contains(stageId)) { + MultiKey multiKey = new MultiKey(c.getTime(), stageId, sourceProducerId, + destinationProducerId); + expectedObservations.add(multiKey); + } + } + } + } + })); + } + + // have the observer show that the observations were as expected + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 3296787354687433406L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the stage id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 5129361648713614556L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StageId stageId = null; + materialsDataManager.getEventFilterForStageMaterialsProducerUpdateEvent(stageId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_STAGE_ID, contractException.getErrorType()); + + /* precondition test: if the stage id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 99312324736600050L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StageId stageId = new StageId(100000000); + materialsDataManager.getEventFilterForStageMaterialsProducerUpdateEvent(stageId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_STAGE_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getEventFilterForStageMaterialsProducerUpdateEvent", args = {}) + public void testGetEventFilterForStageMaterialsProducerUpdateEvent() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + double actionTime = 0; + + // create containers to hold observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + /* + * Have an actor observe all stage transfers + */ + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + EventFilter eventFilter = materialsDataManager + .getEventFilterForStageMaterialsProducerUpdateEvent(); + c.subscribe(eventFilter, (c2, e) -> { + MultiKey multiKey = new MultiKey(c.getTime(), e.stageId(), e.previousMaterialsProducerId(), + e.currentMaterialsProducerId()); + actualObservations.add(multiKey); + }); + + })); + + // have an actor transfer stages randomly between producers + for (int i = 0; i < 100; i++) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + TestMaterialsProducerId sourceProducerId = TestMaterialsProducerId + .getRandomMaterialsProducerId(randomGenerator); + TestMaterialsProducerId destinationProducerId; + do { + destinationProducerId = TestMaterialsProducerId.getRandomMaterialsProducerId(randomGenerator); + } while (sourceProducerId.equals(destinationProducerId)); + + StageId stageId = materialsDataManager.addStage(sourceProducerId); + materialsDataManager.setStageOfferState(stageId, true); + materialsDataManager.transferOfferedStage(stageId, destinationProducerId); + + MultiKey multiKey = new MultiKey(c.getTime(), stageId, sourceProducerId, destinationProducerId); + expectedObservations.add(multiKey); + + })); + } + + // have the observer show that the observations were as expected + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 6150408500189298357L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getEventFilterForStageOfferUpdateEvent", args = { + StageId.class }) + public void testGetEventFilterForStageOfferUpdateEvent_Stage() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + double actionTime = 0; + + // create containers to hold observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + Set selectedStages = new LinkedHashSet<>(); + + /* + * have an agent create some stages in various offer states and select some of + * them for the observer to observe. + */ + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + Set materialsProducerIds = materialsDataManager.getMaterialsProducerIds(); + for (MaterialsProducerId materialsProducerId : materialsProducerIds) { + for (int i = 0; i < 50; i++) { + StageId stageId = materialsDataManager.addStage(materialsProducerId); + materialsDataManager.setStageOfferState(stageId, randomGenerator.nextBoolean()); + if (randomGenerator.nextBoolean()) { + selectedStages.add(stageId); + } + } + } + })); + + // have a agent observe stage creations + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + for (StageId stageId : selectedStages) { + EventFilter eventFilter = materialsDataManager + .getEventFilterForStageOfferUpdateEvent(stageId); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.stageId())); + }); + } + })); + + // have the actor randomly choose to change some of the offer states + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + Set materialsProducerIds = materialsDataManager.getMaterialsProducerIds(); + for (MaterialsProducerId materialsProducerId : materialsProducerIds) { + List stages = materialsDataManager.getStages(materialsProducerId); + for (StageId stageId : stages) { + if (randomGenerator.nextBoolean()) { + boolean newOfferState = !materialsDataManager.isStageOffered(stageId); + materialsDataManager.setStageOfferState(stageId, newOfferState); + if (selectedStages.contains(stageId)) { + expectedObservations.add(new MultiKey(c.getTime(), stageId)); + } + } + } + } + })); + + // have the observer show that the correct observations were generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 2427005100525993777L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the stage id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 4844028463801822799L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StageId stageId = null; + materialsDataManager.getEventFilterForStageOfferUpdateEvent(stageId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_STAGE_ID, contractException.getErrorType()); + + /* precondition test: if the stage id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 7970389114090461374L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StageId stageId = new StageId(10000000); + materialsDataManager.getEventFilterForStageOfferUpdateEvent(stageId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_STAGE_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getEventFilterForStageOfferUpdateEvent", args = {}) + public void testGetEventFilterForStageOfferUpdateEvent() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + double actionTime = 0; + + // create containers to hold observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + /* + * have an agent create some stages in various offer states and select some of + * them for the observer to observe. + */ + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + Set materialsProducerIds = materialsDataManager.getMaterialsProducerIds(); + for (MaterialsProducerId materialsProducerId : materialsProducerIds) { + for (int i = 0; i < 50; i++) { + StageId stageId = materialsDataManager.addStage(materialsProducerId); + materialsDataManager.setStageOfferState(stageId, randomGenerator.nextBoolean()); + } + } + })); + + // have a agent observe stage creations + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + EventFilter eventFilter = materialsDataManager + .getEventFilterForStageOfferUpdateEvent(); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.stageId())); + }); + + })); + + // have the actor randomly choose to change some of the offer states + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + Set materialsProducerIds = materialsDataManager.getMaterialsProducerIds(); + for (MaterialsProducerId materialsProducerId : materialsProducerIds) { + List stages = materialsDataManager.getStages(materialsProducerId); + for (StageId stageId : stages) { + if (randomGenerator.nextBoolean()) { + boolean newOfferState = !materialsDataManager.isStageOffered(stageId); + materialsDataManager.setStageOfferState(stageId, newOfferState); + expectedObservations.add(new MultiKey(c.getTime(), stageId)); + } + } + } + })); + + // have the observer show that the correct observations were generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 7611854826274953331L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getEventFilterForBatchAdditionEvent", args = {}) + public void testGetEventFilterForBatchAdditionEvent() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + double actionTime = 0; + + // create containers to hold observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // have a agent observe batch creations + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + EventFilter eventFilter = materialsDataManager.getEventFilterForBatchAdditionEvent(); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.batchId())); + }); + + })); + + // have the actor randomly add some batches + for (int i = 0; i < 30; i++) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + MaterialsProducerId materialsProducerId = TestMaterialsProducerId + .getRandomMaterialsProducerId(randomGenerator); + TestMaterialId testMaterialId = TestMaterialId.getRandomMaterialId(randomGenerator); + double amount = randomGenerator.nextDouble(); + BatchConstructionInfo.Builder batchBuilder = BatchConstructionInfo.builder(); + // + batchBuilder.setMaterialsProducerId(materialsProducerId)// + .setMaterialId(testMaterialId)// + .setAmount(amount);// + + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId + .getTestBatchPropertyIds(testMaterialId)) { + Object propertyValue = testBatchPropertyId.getRandomPropertyValue(randomGenerator); + batchBuilder.setPropertyValue(testBatchPropertyId, propertyValue); + } + + BatchConstructionInfo batchConstructionInfo = batchBuilder.build(); + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + + expectedObservations.add(new MultiKey(c.getTime(), batchId)); + })); + } + + // have the observer show that the correct observations were generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 8733374899306819910L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getEventFilterForBatchAmountUpdateEvent", args = {}) + public void testGetEventFilterForBatchAmountUpdateEvent() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + double actionTime = 0; + + // create containers to hold observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // have a agent observe batch amount updates + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + EventFilter eventFilter = materialsDataManager + .getEventFilterForBatchAmountUpdateEvent(); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.batchId())); + }); + + })); + + // have the actor randomly add some batches and then alter the amounts + for (int i = 0; i < 30; i++) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + MaterialsProducerId materialsProducerId = TestMaterialsProducerId + .getRandomMaterialsProducerId(randomGenerator); + TestMaterialId testMaterialId = TestMaterialId.getRandomMaterialId(randomGenerator); + double amount = randomGenerator.nextDouble() + 0.01; + BatchConstructionInfo.Builder batchBuilder = BatchConstructionInfo.builder(); + + batchBuilder.setMaterialsProducerId(materialsProducerId)// + .setMaterialId(testMaterialId)// + .setAmount(amount);// + + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId + .getTestBatchPropertyIds(testMaterialId)) { + Object propertyValue = testBatchPropertyId.getRandomPropertyValue(randomGenerator); + batchBuilder.setPropertyValue(testBatchPropertyId, propertyValue); + } + + BatchConstructionInfo batchConstructionInfo = batchBuilder.build(); + BatchId batchId1 = materialsDataManager.addBatch(batchConstructionInfo); + + batchBuilder.setMaterialsProducerId(materialsProducerId)// + .setMaterialId(testMaterialId)// + .setAmount(amount);// + + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId + .getTestBatchPropertyIds(testMaterialId)) { + Object propertyValue = testBatchPropertyId.getRandomPropertyValue(randomGenerator); + batchBuilder.setPropertyValue(testBatchPropertyId, propertyValue); + } + + batchConstructionInfo = batchBuilder.build(); + BatchId batchId2 = materialsDataManager.addBatch(batchConstructionInfo); + + amount = materialsDataManager.getBatchAmount(batchId1) / 2; + materialsDataManager.transferMaterialBetweenBatches(batchId1, batchId2, amount); + + expectedObservations.add(new MultiKey(c.getTime(), batchId1)); + expectedObservations.add(new MultiKey(c.getTime(), batchId2)); + })); + } + + // have the observer show that the correct observations were generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 1632036988086563905L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getEventFilterForBatchImminentRemovalEvent", args = {}) + public void testGetEventFilterForBatchImminentRemovalEvent() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + double actionTime = 0; + + // create containers to hold observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // have a agent observe batch removals + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + EventFilter eventFilter = materialsDataManager + .getEventFilterForBatchImminentRemovalEvent(); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.batchId())); + }); + + })); + + // have the actor randomly add some batches and then remove them + for (int i = 0; i < 30; i++) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + MaterialsProducerId materialsProducerId = TestMaterialsProducerId + .getRandomMaterialsProducerId(randomGenerator); + TestMaterialId testMaterialId = TestMaterialId.getRandomMaterialId(randomGenerator); + double amount = randomGenerator.nextDouble() + 0.01; + BatchConstructionInfo.Builder batchBuilder = BatchConstructionInfo.builder(); + + batchBuilder.setMaterialsProducerId(materialsProducerId)// + .setMaterialId(testMaterialId)// + .setAmount(amount);// + + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId + .getTestBatchPropertyIds(testMaterialId)) { + Object propertyValue = testBatchPropertyId.getRandomPropertyValue(randomGenerator); + batchBuilder.setPropertyValue(testBatchPropertyId, propertyValue); + } + + BatchConstructionInfo batchConstructionInfo = batchBuilder.build(); + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + materialsDataManager.removeBatch(batchId); + + expectedObservations.add(new MultiKey(c.getTime(), batchId)); + })); + } + + // have the observer show that the correct observations were generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 7418141671964137152L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getEventFilterForBatchPropertyDefinitionEvent", args = {}) + public void testGetEventFilterForBatchPropertyDefinitionEvent() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + double actionTime = 0; + + // create containers to hold observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // have a agent observe batch property definition constructions + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + EventFilter eventFilter = materialsDataManager + .getEventFilterForBatchPropertyDefinitionEvent(); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.batchPropertyId())); + }); + + })); + + // have the actor randomly add some batch properties + for (int i = 0; i < 10; i++) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + TestMaterialId testMaterialId = TestMaterialId.getRandomMaterialId(randomGenerator); + int defaultValue = randomGenerator.nextInt(100); + PropertyDefinition propertyDefinition = PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(defaultValue)// + .build(); + BatchPropertyId batchPropertyId = TestBatchPropertyId.getUnknownBatchPropertyId(); + BatchPropertyDefinitionInitialization batchPropertyDefinitionInitialization = // + BatchPropertyDefinitionInitialization.builder()// + .setMaterialId(testMaterialId).setPropertyDefinition(propertyDefinition)// + .setPropertyId(batchPropertyId)// + .build();// + materialsDataManager.defineBatchProperty(batchPropertyDefinitionInitialization); + + expectedObservations.add(new MultiKey(c.getTime(), batchPropertyId)); + })); + } + + // have the observer show that the correct observations were generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 1659719780457752005L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getEventFilterForBatchPropertyUpdateEvent", args = {}) + public void testGetEventFilterForBatchPropertyUpdateEvent() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + double actionTime = 0; + + // create containers to hold observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // have a agent observe batch property property updates + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + EventFilter eventFilter = materialsDataManager + .getEventFilterForBatchPropertyUpdateEvent(); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.batchId(), e.batchPropertyId())); + }); + + })); + + // have the actor randomly add some batches and change their properties + for (int i = 0; i < 10; i++) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + MaterialsProducerId materialsProducerId = TestMaterialsProducerId + .getRandomMaterialsProducerId(randomGenerator); + TestMaterialId testMaterialId = TestMaterialId.getRandomMaterialId(randomGenerator); + double amount = randomGenerator.nextDouble() + 0.01; + BatchConstructionInfo.Builder batchBuilder = BatchConstructionInfo.builder(); + + batchBuilder.setMaterialsProducerId(materialsProducerId)// + .setMaterialId(testMaterialId)// + .setAmount(amount);// + + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId + .getTestBatchPropertyIds(testMaterialId)) { + Object propertyValue = testBatchPropertyId.getRandomPropertyValue(randomGenerator); + batchBuilder.setPropertyValue(testBatchPropertyId, propertyValue); + } + + BatchConstructionInfo batchConstructionInfo = batchBuilder.build(); + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + + TestBatchPropertyId batchPropertyId = TestBatchPropertyId + .getRandomMutableBatchPropertyId(testMaterialId, randomGenerator); + Object propertyValue = batchPropertyId.getRandomPropertyValue(randomGenerator); + materialsDataManager.setBatchPropertyValue(batchId, batchPropertyId, propertyValue); + + expectedObservations.add(new MultiKey(c.getTime(), batchId, batchPropertyId)); + })); + } + + // have the observer show that the correct observations were generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 2839431361490510612L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getEventFilterForMaterialIdAdditionEvent", args = {}) + public void testGetEventFilterForMaterialIdAdditionEvent() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + double actionTime = 0; + + // create containers to hold observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // have a agent observe the addition of material types + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + EventFilter eventFilter = materialsDataManager + .getEventFilterForMaterialIdAdditionEvent(); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.materialId())); + }); + + })); + + // have the actor add some material ids + for (int i = 0; i < 10; i++) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialId materialId = TestMaterialId.getUnknownMaterialId(); + materialsDataManager.addMaterialId(materialId); + expectedObservations.add(new MultiKey(c.getTime(), materialId)); + })); + } + + // have the observer show that the correct observations were generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 3016777797847869909L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getEventFilterForMaterialsProducerAdditionEvent", args = {}) + public void testGetEventFilterForMaterialsProducerAdditionEvent() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + double actionTime = 0; + + // create containers to hold observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // have an agent observe the addition of material producers + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + EventFilter eventFilter = materialsDataManager + .getEventFilterForMaterialsProducerAdditionEvent(); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.getMaterialsProducerId())); + }); + })); + + // have the actor randomly add some materials producers + for (int i = 0; i < 10; i++) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + MaterialsProducerId materialsProducerId = TestMaterialsProducerId.getUnknownMaterialsProducerId(); + + MaterialsProducerConstructionData.Builder builder = MaterialsProducerConstructionData.builder(); + + builder.setMaterialsProducerId(materialsProducerId);// + + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .values()) { + Object propertyValue = testMaterialsProducerPropertyId.getRandomPropertyValue(randomGenerator); + builder.setMaterialsProducerPropertyValue(testMaterialsProducerPropertyId, propertyValue); + } + + MaterialsProducerConstructionData materialsProducerConstructionData = builder.build(); + materialsDataManager.addMaterialsProducer(materialsProducerConstructionData); + + expectedObservations.add(new MultiKey(c.getTime(), materialsProducerId)); + })); + } + + // have the observer show that the correct observations were generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 9030121507723724675L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getEventFilterForMaterialsProducerPropertyDefinitionEvent", args = {}) + public void testGetEventFilterForMaterialsProducerPropertyDefinitionEvent() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + double actionTime = 0; + + // create containers to hold observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // have an agent observe the definition of material producer properties + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + EventFilter eventFilter = materialsDataManager + .getEventFilterForMaterialsProducerPropertyDefinitionEvent(); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.materialsProducerPropertyId())); + }); + })); + + // have the actor randomly define some materials producer properties + for (int i = 0; i < 10; i++) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + MaterialsProducerPropertyId materialsProducerPropertyId = TestMaterialsProducerPropertyId + .getUnknownMaterialsProducerPropertyId(); + int defaultValue = randomGenerator.nextInt(100); + PropertyDefinition propertyDefinition = PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(defaultValue)// + .build(); + + MaterialsProducerPropertyDefinitionInitialization materialsProducerPropertyDefinitionInitialization = MaterialsProducerPropertyDefinitionInitialization + .builder()// + .setMaterialsProducerPropertyId(materialsProducerPropertyId)// + .setPropertyDefinition(propertyDefinition)// + .build(); + materialsDataManager.defineMaterialsProducerProperty(materialsProducerPropertyDefinitionInitialization); + + expectedObservations.add(new MultiKey(c.getTime(), materialsProducerPropertyId)); + })); + } + + // have the observer show that the correct observations were generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 2555168166874481212L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getEventFilterForStageAdditionEvent", args = {}) + public void testGetEventFilterForStageAdditionEvent() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + double actionTime = 0; + + // create containers to hold observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // have an agent observe the addition of stages + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + EventFilter eventFilter = materialsDataManager.getEventFilterForStageAdditionEvent(); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.stageId())); + }); + })); + + // have the actor add some stages + for (int i = 0; i < 10; i++) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + TestMaterialsProducerId materialsProducerId = TestMaterialsProducerId + .getRandomMaterialsProducerId(randomGenerator); + StageId stageId = materialsDataManager.addStage(materialsProducerId); + + expectedObservations.add(new MultiKey(c.getTime(), stageId)); + })); + } + + // have the observer show that the correct observations were generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 5930670132326679913L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getEventFilterForStageImminentRemovalEvent", args = {}) + public void testGetEventFilterForStageImminentRemovalEvent() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + double actionTime = 0; + + // create containers to hold observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // have an agent observe the imminent removal of stages + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + EventFilter eventFilter = materialsDataManager + .getEventFilterForStageImminentRemovalEvent(); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.stageId())); + }); + })); + + // have the actor add some stages and then remove them + for (int i = 0; i < 10; i++) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + TestMaterialsProducerId materialsProducerId = TestMaterialsProducerId + .getRandomMaterialsProducerId(randomGenerator); + StageId stageId = materialsDataManager.addStage(materialsProducerId); + materialsDataManager.removeStage(stageId, false); + + expectedObservations.add(new MultiKey(c.getTime(), stageId)); + + })); + } + + // have the observer show that the correct observations were generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 4965736606382697699L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getEventFilterForStageMembershipAdditionEvent", args = {}) + public void testGetEventFilterForStageMembershipAdditionEvent() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + double actionTime = 0; + + // create containers to hold observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // have an agent observe the imminent removal of stages + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + EventFilter eventFilter = materialsDataManager + .getEventFilterForStageImminentRemovalEvent(); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.stageId())); + }); + })); + + // have the actor add some stages and then remove them + for (int i = 0; i < 10; i++) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + TestMaterialsProducerId materialsProducerId = TestMaterialsProducerId + .getRandomMaterialsProducerId(randomGenerator); + StageId stageId = materialsDataManager.addStage(materialsProducerId); + materialsDataManager.removeStage(stageId, false); + + expectedObservations.add(new MultiKey(c.getTime(), stageId)); + + })); + } + + // have the observer show that the correct observations were generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 3581801183499812974L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "getEventFilterForStageMembershipRemovalEvent", args = {}) + public void testGetEventFilterForStageMembershipRemovalEvent() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + double actionTime = 0; + + // create containers to hold observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // have an agent observe the removal of batches from stages + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + EventFilter eventFilter = materialsDataManager + .getEventFilterForStageMembershipRemovalEvent(); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.stageId(), e.batchId())); + }); + })); + + // have the actor add some stages and then remove them + for (int i = 0; i < 10; i++) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + TestMaterialsProducerId materialsProducerId = TestMaterialsProducerId + .getRandomMaterialsProducerId(randomGenerator); + double amount = randomGenerator.nextDouble(); + StageId stageId = materialsDataManager.addStage(materialsProducerId); + TestMaterialId materialId = TestMaterialId.getRandomMaterialId(randomGenerator); + BatchConstructionInfo.Builder batchBuilder = BatchConstructionInfo.builder(); + batchBuilder.setMaterialsProducerId(materialsProducerId); + batchBuilder.setAmount(amount); + batchBuilder.setMaterialId(materialId); + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId + .getTestBatchPropertyIds(materialId)) { + Object propertyValue = testBatchPropertyId.getRandomPropertyValue(randomGenerator); + batchBuilder.setPropertyValue(testBatchPropertyId, propertyValue); + } + BatchConstructionInfo batchConstructionInfo = batchBuilder.build(); + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + materialsDataManager.moveBatchToStage(batchId, stageId); + materialsDataManager.moveBatchToInventory(batchId); + + expectedObservations.add(new MultiKey(c.getTime(), stageId, batchId)); + + })); + } + + // have the observer show that the correct observations were generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 6812070525878040557L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "defineMaterialsProducerProperty", args = { + MaterialsProducerPropertyDefinitionInitialization.class }) + public void testDefineMaterialsProducerProperty() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + double actionTime = 0; + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + c.subscribe(EventFilter.builder(MaterialsProducerPropertyDefinitionEvent.class).build(), (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.materialsProducerPropertyId())); + }); + })); + + for (int i = 0; i < 15; i++) { + MaterialsProducerPropertyId materialsProducerPropertyId = TestMaterialsProducerPropertyId + .getUnknownMaterialsProducerPropertyId(); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setDefaultValue(100 * i) + .setPropertyValueMutability(false)// + .setType(Integer.class)// + .build(); + MaterialsProducerPropertyDefinitionInitialization matprodpropdefinit = MaterialsProducerPropertyDefinitionInitialization// + .builder().setMaterialsProducerPropertyId(materialsProducerPropertyId) + .setPropertyDefinition(propertyDefinition)// + .build(); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.defineMaterialsProducerProperty(matprodpropdefinit); + expectedObservations.add(new MultiKey(c.getTime(), materialsProducerPropertyId)); + })); + } + // have the observer show that the correct observations were generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 2721085458686966421L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition: Materials producer property definition init is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 3735323519290927676L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + materialsDataManager.defineMaterialsProducerProperty(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_PROPERTY_DEFINITION_INITIALIZATION, + contractException.getErrorType()); + + // precondition: duplicate Materials producer property id + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 3735323519290927676L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + TestMaterialsProducerPropertyId materialsProducerPropertyId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK; + MaterialsProducerPropertyDefinitionInitialization matprodpropdefinit = // + MaterialsProducerPropertyDefinitionInitialization.builder() + .setMaterialsProducerPropertyId(materialsProducerPropertyId)// + .setPropertyDefinition(materialsProducerPropertyId.getPropertyDefinition())// + .build();// + + materialsDataManager.defineMaterialsProducerProperty(matprodpropdefinit); + + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.DUPLICATE_MATERIALS_PRODUCER_PROPERTY_ID, contractException.getErrorType()); + + // precondition: insufficient property value assignment + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 6282192460518073310L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialsProducerPropertyId materialsProducerPropertyId = TestMaterialsProducerPropertyId + .getUnknownMaterialsProducerPropertyId(); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setPropertyValueMutability(false)// + .setType(Integer.class)// + .build(); + MaterialsProducerPropertyDefinitionInitialization matprodpropdefinit = // + MaterialsProducerPropertyDefinitionInitialization.builder()// + .setMaterialsProducerPropertyId(materialsProducerPropertyId)// + .setPropertyDefinition(propertyDefinition)// + .build(); + materialsDataManager.defineMaterialsProducerProperty(matprodpropdefinit); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "convertStageToResource", args = { StageId.class, + ResourceId.class, long.class }) + public void testConvertStageToResource() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + double actionTime = 0; + + // create containers to hold observations + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // have an actor observe + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + + c.subscribe(EventFilter.builder(BatchImminentRemovalEvent.class).build(), (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.batchId(), "removal")); + }); + + c.subscribe(EventFilter.builder(MaterialsProducerResourceUpdateEvent.class).build(), (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.resourceId(), e.currentResourceLevel(), "update")); + }); + + c.subscribe(EventFilter.builder(StageImminentRemovalEvent.class).build(), (c2, e) -> { + actualObservations.add(new MultiKey(c2.getTime(), e.stageId())); + }); + + })); + + // have the producers generate batches via stage conversion + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + + List stagesToConfirm = new ArrayList<>(); + List batchesToConfirm = new ArrayList<>(); + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + for (int i = 0; i < 50; i++) { + StageId stageId = materialsDataManager.addStage(testMaterialsProducerId); + + MaterialId materialId; + ResourceId resourceId; + double amount; + long resourceAmount; + int batchCount = randomGenerator.nextInt(3); + for (int j = 0; j < batchCount; j++) { + materialId = TestMaterialId.getRandomMaterialId(randomGenerator); + amount = randomGenerator.nextDouble() + 0.01; + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo + .getBatchConstructionInfo(testMaterialsProducerId, materialId, amount, randomGenerator); + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + materialsDataManager.moveBatchToStage(batchId, stageId); + } + resourceId = TestResourceId.getRandomResourceId(randomGenerator); + resourceAmount = 125L; + + long previousResourceAmount = materialsDataManager + .getMaterialsProducerResourceLevel(testMaterialsProducerId, resourceId); + long newResourceAmount = resourceAmount + previousResourceAmount; + + List stageBatches = materialsDataManager.getStageBatches(stageId); + materialsDataManager.convertStageToResource(stageId, resourceId, resourceAmount); + + // record the stages and batches that should be removed, but + // only after the current actor activation + stagesToConfirm.add(stageId); + batchesToConfirm.addAll(stageBatches); + + // show that the stage was properly converted + assertTrue(resourcesDataManager.resourceIdExists(resourceId)); + assertEquals(newResourceAmount, materialsDataManager + .getMaterialsProducerResourceLevel(testMaterialsProducerId, resourceId)); + + // generate the expected observations + for (BatchId batchId : stageBatches) { + expectedObservations.add(new MultiKey(c.getTime(), batchId, "removal")); + } + expectedObservations.add(new MultiKey(c.getTime(), resourceId, newResourceAmount, "update")); + expectedObservations.add(new MultiKey(c.getTime(), stageId)); + } + })); + + // show that the stages and batches used to generate the new batches + // were in fact removed + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + for (StageId stageId : stagesToConfirm) { + assertFalse(materialsDataManager.stageExists(stageId)); + } + for (BatchId batchId : batchesToConfirm) { + assertFalse(materialsDataManager.batchExists(batchId)); + } + })); + } + + // have the observer show that the correct observations were generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 7822140774565669544L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the resource id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 2645688892533853761L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + long amount = 125L; + materialsDataManager.convertStageToResource(stageId, null, amount); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the resource id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 5663564750797913460L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + long amount = 125L; + materialsDataManager.convertStageToResource(stageId, TestResourceId.getUnknownResourceId(), amount); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the stage id is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 4802037379297224622L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + ResourceId resourceId = TestResourceId.RESOURCE_1; + long amount = 125L; + materialsDataManager.convertStageToResource(null, resourceId, amount); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.NULL_STAGE_ID, contractException.getErrorType()); + + /* precondition test: if stage id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 2648372629715030136L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + ResourceId resourceId = TestResourceId.RESOURCE_1; + long amount = 125L; + materialsDataManager.convertStageToResource(new StageId(10000000), resourceId, amount); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.UNKNOWN_STAGE_ID, contractException.getErrorType()); + + /* precondition test: if the stage is offered */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 4249451090321590319L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + ResourceId resourceId = TestResourceId.RESOURCE_1; + long amount = 125L; + materialsDataManager.setStageOfferState(stageId, true); + materialsDataManager.convertStageToResource(stageId, resourceId, amount); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(MaterialsError.OFFERED_STAGE_UNALTERABLE, contractException.getErrorType()); + + /* precondition test: if the resource amount is negative */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 6695497074307172608L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + ResourceId resourceId = TestResourceId.RESOURCE_1; + materialsDataManager.convertStageToResource(stageId, resourceId, -1L); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.NEGATIVE_RESOURCE_AMOUNT, contractException.getErrorType()); + + /* precondition test: if the resource amount is not finite */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = MaterialsTestPluginFactory.factory(0, 0, 0, 4334935928753037959L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + ResourceId resourceId = TestResourceId.RESOURCE_1; + + materialsDataManager.convertStageToResource(stageId, resourceId, 10L); + materialsDataManager.convertStageToResource(stageId, resourceId, Long.MAX_VALUE); + + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.RESOURCE_ARITHMETIC_EXCEPTION, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "toString", args = {}) + public void testToString() { + + Factory factory = MaterialsTestPluginFactory.factory(4, 2, 3, 5192198848691795136L, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + //The expected value is based on the random seed and was manually verified + String expectedValue = "MaterialsDataManager [batchPropertyDefinitions={MATERIAL_1=" + + "{BATCH_PROPERTY_1_1_BOOLEAN_IMMUTABLE_NO_TRACK=PropertyDefinition [type=class java.lang.Boolean," + + " propertyValuesAreMutable=false, defaultValue=false], BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK=" + + "PropertyDefinition [type=class java.lang.Integer, propertyValuesAreMutable=true, defaultValue=null], " + + "BATCH_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK=PropertyDefinition [type=class java.lang.Double, " + + "propertyValuesAreMutable=true, defaultValue=null]}, MATERIAL_2={BATCH_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK" + + "=PropertyDefinition [type=class java.lang.Boolean, propertyValuesAreMutable=true, defaultValue=false]" + + ", BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK=PropertyDefinition [type=class java.lang.Integer, " + + "propertyValuesAreMutable=false, defaultValue=0], BATCH_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK=" + + "PropertyDefinition [type=class java.lang.Double, propertyValuesAreMutable=true, defaultValue=0.0]}, " + + "MATERIAL_3={BATCH_PROPERTY_3_1_BOOLEAN_MUTABLE_NO_TRACK=PropertyDefinition [type=class java.lang.Boolean, " + + "propertyValuesAreMutable=true, defaultValue=false], BATCH_PROPERTY_3_2_INTEGER_MUTABLE_NO_TRACK=" + + "PropertyDefinition [type=class java.lang.Integer, propertyValuesAreMutable=true, defaultValue=0], " + + "BATCH_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK=PropertyDefinition [type=class java.lang.Double, " + + "propertyValuesAreMutable=false, defaultValue=0.0]}}, nextBatchRecordId=12, nextStageRecordId=6, " + + "materialIds=[MATERIAL_1, MATERIAL_2, MATERIAL_3], materialsProducerPropertyDefinitions=" + + "{MATERIALS_PRODUCER_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK=PropertyDefinition [type=class " + + "java.lang.Boolean, propertyValuesAreMutable=true, defaultValue=false], " + + "MATERIALS_PRODUCER_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK=PropertyDefinition " + + "[type=class java.lang.Integer, propertyValuesAreMutable=true, defaultValue=null], " + + "MATERIALS_PRODUCER_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK=PropertyDefinition " + + "[type=class java.lang.Double, propertyValuesAreMutable=true, defaultValue=0.0], " + + "MATERIALS_PRODUCER_PROPERTY_4_BOOLEAN_MUTABLE_TRACK=PropertyDefinition [type=class java.lang.Boolean, " + + "propertyValuesAreMutable=true, defaultValue=false], MATERIALS_PRODUCER_PROPERTY_5_INTEGER_MUTABLE_TRACK=" + + "PropertyDefinition [type=class java.lang.Integer, propertyValuesAreMutable=true, defaultValue=0], " + + "MATERIALS_PRODUCER_PROPERTY_6_DOUBLE_MUTABLE_TRACK=PropertyDefinition [type=class java.lang.Double, " + + "propertyValuesAreMutable=true, defaultValue=0.0], MATERIALS_PRODUCER_PROPERTY_7_BOOLEAN_IMMUTABLE_NO_TRACK=" + + "PropertyDefinition [type=class java.lang.Boolean, propertyValuesAreMutable=false, defaultValue=false], " + + "MATERIALS_PRODUCER_PROPERTY_8_INTEGER_IMMUTABLE_NO_TRACK=PropertyDefinition [type=class java.lang.Integer, " + + "propertyValuesAreMutable=false, defaultValue=0], MATERIALS_PRODUCER_PROPERTY_9_DOUBLE_IMMUTABLE_NO_TRACK=" + + "PropertyDefinition [type=class java.lang.Double, propertyValuesAreMutable=false, defaultValue=0.0]}, " + + "materialsProducerMap={MATERIALS_PRODUCER_1=MaterialsProducerRecord [materialProducerId=MATERIALS_PRODUCER_1, " + + "materialProducerResources={RESOURCE_1=MutableLong [value=5], RESOURCE_2=MutableLong [value=0], RESOURCE_3=" + + "MutableLong [value=1], RESOURCE_4=MutableLong [value=2], RESOURCE_5=MutableLong [value=3]}, stageRecords=" + + "[0, 1], inventory=[3]], MATERIALS_PRODUCER_2=MaterialsProducerRecord [materialProducerId=" + + "MATERIALS_PRODUCER_2, materialProducerResources={RESOURCE_1=MutableLong [value=4], RESOURCE_2=" + + "MutableLong [value=0], RESOURCE_3=MutableLong [value=0], RESOURCE_4=MutableLong [value=0], " + + "RESOURCE_5=MutableLong [value=0]}, stageRecords=[2, 3], inventory=[4]], MATERIALS_PRODUCER_3=" + + "MaterialsProducerRecord [materialProducerId=MATERIALS_PRODUCER_3, materialProducerResources={" + + "RESOURCE_1=MutableLong [value=0], RESOURCE_2=MutableLong [value=8], RESOURCE_3=MutableLong [value=0], " + + "RESOURCE_4=MutableLong [value=0], RESOURCE_5=MutableLong [value=4]}, stageRecords=[4, 5], " + + "inventory=[9]]}, materialsProducerPropertyMap={MATERIALS_PRODUCER_1={" + + "MATERIALS_PRODUCER_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK=1778550126}, MATERIALS_PRODUCER_2=" + + "{MATERIALS_PRODUCER_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK=1924370359}, MATERIALS_PRODUCER_3=" + + "{MATERIALS_PRODUCER_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK=504350891}}, batchPropertyMap={0=" + + "{BATCH_PROPERTY_3_2_INTEGER_MUTABLE_NO_TRACK=-978237422}, 1={BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK=" + + "-1729799849, BATCH_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK=0.9025181743993138}, 2=" + + "{BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK=-484020240, BATCH_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK=" + + "0.3708284558370174}, 3={BATCH_PROPERTY_3_2_INTEGER_MUTABLE_NO_TRACK=1318688999}, 4={" + + "BATCH_PROPERTY_1_1_BOOLEAN_IMMUTABLE_NO_TRACK=true, BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK=" + + "486340417, BATCH_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK=0.8689895623273185}, 5={" + + "BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK=1879150228, BATCH_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK=" + + "0.8454980854466561}, 6={BATCH_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK=true}, 7={" + + "BATCH_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK=true, BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK=1954368533}, " + + "8={BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK=-889595653, BATCH_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK=" + + "0.6979600292821175}, 9={BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK=36780705, " + + "BATCH_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK=0.8682101654568535}, 10={BATCH_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK" + + "=false}, 11={BATCH_PROPERTY_3_2_INTEGER_MUTABLE_NO_TRACK=-2012291536, BATCH_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK" + + "=0.2759903618168962}}, batchRecords={0=BatchRecord [amount=0.6962263327891574, batchId=0, " + + "materialId=MATERIAL_3, materialsProducerRecord=MATERIALS_PRODUCER_1, stageRecord=1], 1=" + + "BatchRecord [amount=0.6519697065916177, batchId=1, materialId=MATERIAL_2, materialsProducerRecord=" + + "MATERIALS_PRODUCER_1, stageRecord=1], 2=BatchRecord [amount=0.9895988019683672, batchId=2, " + + "materialId=MATERIAL_2, materialsProducerRecord=MATERIALS_PRODUCER_1, stageRecord=0], 3=" + + "BatchRecord [amount=0.35814832382492146, batchId=3, materialId=MATERIAL_3, " + + "materialsProducerRecord=MATERIALS_PRODUCER_1, stageRecord=null], 4=BatchRecord " + + "[amount=0.5005769840744261, batchId=4, materialId=MATERIAL_1, materialsProducerRecord" + + "=MATERIALS_PRODUCER_2, stageRecord=null], 5=BatchRecord [amount=0.6584285204523546, " + + "batchId=5, materialId=MATERIAL_2, materialsProducerRecord=MATERIALS_PRODUCER_2, stageRecord=3], " + + "6=BatchRecord [amount=0.625292468692171, batchId=6, materialId=MATERIAL_2, materialsProducerRecord=" + + "MATERIALS_PRODUCER_2, stageRecord=2], 7=BatchRecord [amount=0.5159142705150503, batchId=7, materialId=" + + "MATERIAL_2, materialsProducerRecord=MATERIALS_PRODUCER_2, stageRecord=2], 8=BatchRecord [" + + "amount=0.7099718938862924, batchId=8, materialId=MATERIAL_2, materialsProducerRecord=MATERIALS_PRODUCER_3, " + + "stageRecord=4], 9=BatchRecord [amount=0.5586916328996154, batchId=9, materialId=MATERIAL_2, " + + "materialsProducerRecord=MATERIALS_PRODUCER_3, stageRecord=null], 10=BatchRecord [amount=0.718483880620675, " + + "batchId=10, materialId=MATERIAL_2, materialsProducerRecord=MATERIALS_PRODUCER_3, stageRecord=4], " + + "11=BatchRecord [amount=0.17519194553253437, batchId=11, materialId=MATERIAL_3, materialsProducerRecord=" + + "MATERIALS_PRODUCER_3, stageRecord=5]}, resourceIds=[RESOURCE_1, RESOURCE_2, RESOURCE_3, RESOURCE_4, " + + "RESOURCE_5], stageRecords={0=StageRecord [offered=true, stageId=0, materialsProducerRecord=MATERIALS_PRODUCER_1, " + + "batchRecords=[2]], 1=StageRecord [offered=false, stageId=1, materialsProducerRecord=MATERIALS_PRODUCER_1, " + + "batchRecords=[1, 0]], 2=StageRecord [offered=true, stageId=2, materialsProducerRecord=MATERIALS_PRODUCER_2, " + + "batchRecords=[6, 7]], 3=StageRecord [offered=false, stageId=3, materialsProducerRecord=MATERIALS_PRODUCER_2, " + + "batchRecords=[5]], 4=StageRecord [offered=true, stageId=4, materialsProducerRecord=MATERIALS_PRODUCER_3, " + + "batchRecords=[8, 10]], 5=StageRecord [offered=false, stageId=5, materialsProducerRecord=MATERIALS_PRODUCER_3, " + + "batchRecords=[11]]}]"; + + String actualValue = materialsDataManager.toString(); + + assertEquals(expectedValue, actualValue); + + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/datamanagers/AT_MaterialsDataManager_Continuity.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/datamanagers/AT_MaterialsDataManager_Continuity.java new file mode 100644 index 000000000..e0012edef --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/datamanagers/AT_MaterialsDataManager_Continuity.java @@ -0,0 +1,881 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.datamanagers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Optional; +import java.util.Random; +import java.util.Set; +import java.util.function.Consumer; + +import org.apache.commons.math3.random.RandomGenerator; +import org.apache.commons.math3.util.FastMath; +import org.apache.commons.math3.util.Pair; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.Simulation; +import gov.hhs.aspr.ms.gcm.nucleus.SimulationState; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.runcontinuityplugin.RunContinuityPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.runcontinuityplugin.RunContinuityPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestOutputConsumer; +import gov.hhs.aspr.ms.gcm.plugins.materials.MaterialsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.materials.datamangers.MaterialsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.materials.datamangers.MaterialsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchConstructionInfo; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchPropertyDefinitionInitialization; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerConstructionData; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerPropertyDefinitionInitialization; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.StageConversionInfo; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.StageId; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.TestBatchPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.TestMaterialId; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.TestMaterialsProducerId; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.TestMaterialsProducerPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePlugin; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeoplePluginData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonConstructionData; +import gov.hhs.aspr.ms.gcm.plugins.regions.RegionsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.TestRegionId; +import gov.hhs.aspr.ms.gcm.plugins.resources.ResourcesPlugin; +import gov.hhs.aspr.ms.gcm.plugins.resources.datamanagers.ResourcesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.resources.datamanagers.ResourcesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.TestResourceId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import util.annotations.UnitTestMethod; +import util.random.RandomGeneratorProvider; + +public class AT_MaterialsDataManager_Continuity { + + /** + * Demonstrates that the data manager exhibits run continuity. The state of the + * data manager is not effected by repeatedly starting and stopping the + * simulation. + */ + + @Test + @UnitTestMethod(target = MaterialsDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testStateContinuity() { + + /* + * The returned string is the ordered state of the materials data manager. We + * generate this state at the end of each batch of simulation runs. + */ + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1347391862469572673L); + long seed = randomGenerator.nextLong(); + Set pluginDatas = new LinkedHashSet<>(); + pluginDatas.add(testStateContinuity(1, seed)); + pluginDatas.add(testStateContinuity(5, seed)); + pluginDatas.add(testStateContinuity(10, seed)); + assertEquals(1, pluginDatas.size()); + + } + + /* + * Contains the current plugin data state of the simulation + * + */ + private static class StateData { + + private PeoplePluginData peoplePluginData; + private MaterialsPluginData materialsPluginData; + private RunContinuityPluginData runContinuityPluginData; + private RegionsPluginData regionsPluginData; + private ResourcesPluginData resourcesPluginData; + private StochasticsPluginData stochasticsPluginData; + private SimulationState simulationState; + private double haltTime; + private String output; + } + + private static StateData getInitialState(long seed) { + StateData result = new StateData(); + result.peoplePluginData = getPeoplePluginData(); + result.materialsPluginData = getMaterialsPluginData(); + result.runContinuityPluginData = getRunContinuityPluginData(); + result.regionsPluginData = getRegionsPluginData(); + result.resourcesPluginData = getResourcesPluginData(); + result.stochasticsPluginData = getStochasticsPluginData(seed); + result.simulationState = getSimulationState(); + return result; + } + + private static ResourcesPluginData getResourcesPluginData() { + ResourcesPluginData.Builder resourcesBuilder = ResourcesPluginData.builder(); + for (TestResourceId testResourceId : TestResourceId.values()) { + resourcesBuilder.addResource(testResourceId, 0.0, false); + } + return resourcesBuilder.build(); + } + + private static MaterialsPluginData getMaterialsPluginData() { + return MaterialsPluginData.builder().build(); + } + + private static RegionsPluginData getRegionsPluginData() { + RegionsPluginData.Builder regionsBuilder = RegionsPluginData.builder(); + for (TestRegionId testRegionId : TestRegionId.values()) { + regionsBuilder.addRegion(testRegionId); + } + return regionsBuilder.build(); + } + + private static int BASE_BATCH_COUNT = 50; + private static int BASE_STAGE_COUNT = BASE_BATCH_COUNT / 2; + + private static void addFirstPeople(RunContinuityPluginData.Builder continuityBuilder) { + // add a few people + continuityBuilder.addContextConsumer(0.5, (c) -> { + RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); + + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + for (int i = 0; i < 10; i++) { + TestRegionId regionId = TestRegionId.getRandomRegionId(randomGenerator); + peopleDataManager.addPerson(PersonConstructionData.builder().add(regionId).build()); + } + }); + } + + private static void startMaterialsProduction(RunContinuityPluginData.Builder continuityBuilder) { + /* + * define some of the material ids + * + * add some materials producers + * + * define batch Properties + * + * define materials producer properties + */ + continuityBuilder.addContextConsumer(0.67, (c) -> { + RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + // create all but one of the test materials + List testMaterialIds = new ArrayList<>(); + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + testMaterialIds.add(testMaterialId); + } + Collections.shuffle(testMaterialIds, new Random(randomGenerator.nextInt())); + testMaterialIds.remove(testMaterialIds.size() - 1); + for (TestMaterialId testMaterialId : testMaterialIds) { + materialsDataManager.addMaterialId(testMaterialId); + } + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + + TestResourceId resourceId = TestResourceId.getRandomResourceId(randomGenerator); + long resourceAmount = randomGenerator.nextInt(1000) + 1; + + MaterialsProducerConstructionData materialsProducerConstructionData = MaterialsProducerConstructionData + .builder()// + .setMaterialsProducerId(testMaterialsProducerId)// + .setResourceLevel(resourceId, resourceAmount)// + .build();// + materialsDataManager.addMaterialsProducer(materialsProducerConstructionData); + } + + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .values()) { + + MaterialsProducerPropertyDefinitionInitialization.Builder defbuilder = MaterialsProducerPropertyDefinitionInitialization + .builder(); + + defbuilder.setMaterialsProducerPropertyId(testMaterialsProducerPropertyId); + PropertyDefinition propertyDefinition = testMaterialsProducerPropertyId.getPropertyDefinition(); + defbuilder.setPropertyDefinition(propertyDefinition); + if (propertyDefinition.getDefaultValue().isEmpty()) { + for (MaterialsProducerId materialsProducerId : materialsDataManager.getMaterialsProducerIds()) { + defbuilder.addPropertyValue(materialsProducerId, + testMaterialsProducerPropertyId.getRandomPropertyValue(randomGenerator)); + } + } + MaterialsProducerPropertyDefinitionInitialization materialsProducerPropertyDefinitionInitialization = defbuilder + .build(); + + materialsDataManager.defineMaterialsProducerProperty(materialsProducerPropertyDefinitionInitialization); + } + + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.getBatchPropertyIds()) { + // we need to restrict ourselves to just those properties that were included + // above + if (testMaterialIds.contains(testBatchPropertyId.getTestMaterialId())) { + BatchPropertyDefinitionInitialization batchPropertyDefinitionInitialization = // + BatchPropertyDefinitionInitialization.builder()// + .setPropertyId(testBatchPropertyId)// + .setPropertyDefinition(testBatchPropertyId.getPropertyDefinition())// + .setMaterialId(testBatchPropertyId.getTestMaterialId())// + .build();// + materialsDataManager.defineBatchProperty(batchPropertyDefinitionInitialization); + } + } + + }); + } + + private static void createFirstBatches(RunContinuityPluginData.Builder continuityBuilder) { + // create some batches + continuityBuilder.addContextConsumer(0.95, (c) -> { + RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + List knownMaterialIds = new ArrayList<>(materialsDataManager.getMaterialIds()); + + for (int i = 0; i < BASE_BATCH_COUNT; i++) { + double amount = randomGenerator.nextDouble() * 100; + int index = randomGenerator.nextInt(knownMaterialIds.size()); + TestMaterialId testMaterialId = knownMaterialIds.get(index); + + TestMaterialsProducerId materialsProducerId = TestMaterialsProducerId + .getRandomMaterialsProducerId(randomGenerator); + BatchConstructionInfo.Builder batchBuilder = BatchConstructionInfo.builder();// + batchBuilder.setAmount(amount);// + batchBuilder.setMaterialId(testMaterialId);// + batchBuilder.setMaterialsProducerId(materialsProducerId);// + + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId + .getTestBatchPropertyIds(testMaterialId)) { + if (testBatchPropertyId.getPropertyDefinition().getDefaultValue().isEmpty()) { + Object propertyValue = testBatchPropertyId.getRandomPropertyValue(randomGenerator); + batchBuilder.setPropertyValue(testBatchPropertyId, propertyValue); + } + } + + BatchConstructionInfo batchConstructionInfo = batchBuilder.build(); + + materialsDataManager.addBatch(batchConstructionInfo); + } + + }); + } + + private static void addMoreMaterialsDetails(RunContinuityPluginData.Builder continuityBuilder) { + // define the remaining material ids and batch Properties, adding a few + // more batches + continuityBuilder.addContextConsumer(1.34, (c) -> { + RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + TestMaterialId newTestMaterialId = null; + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + if (!materialsDataManager.materialIdExists(testMaterialId)) { + newTestMaterialId = testMaterialId; + break; + } + } + assertNotNull(newTestMaterialId); + + materialsDataManager.addMaterialId(newTestMaterialId); + + List testBatchPropertyIds = new ArrayList<>( + TestBatchPropertyId.getTestBatchPropertyIds(newTestMaterialId)); + Collections.shuffle(testBatchPropertyIds, new Random(randomGenerator.nextLong())); + + for (TestBatchPropertyId testBatchPropertyId : testBatchPropertyIds) { + BatchPropertyDefinitionInitialization batchPropertyDefinitionInitialization = BatchPropertyDefinitionInitialization// + .builder()// + .setPropertyId(testBatchPropertyId)// + .setPropertyDefinition(testBatchPropertyId.getPropertyDefinition())// + .setMaterialId(testBatchPropertyId.getTestMaterialId())// + .build();// + + materialsDataManager.defineBatchProperty(batchPropertyDefinitionInitialization); + + } + + for (int i = 0; i < BASE_BATCH_COUNT / 2; i++) { + double amount = randomGenerator.nextDouble() * 100; + + TestMaterialsProducerId materialsProducerId = TestMaterialsProducerId + .getRandomMaterialsProducerId(randomGenerator); + BatchConstructionInfo.Builder batchBuilder = BatchConstructionInfo.builder();// + batchBuilder.setAmount(amount);// + batchBuilder.setMaterialId(newTestMaterialId);// + batchBuilder.setMaterialsProducerId(materialsProducerId);// + + List propertyIds = new ArrayList<>( + TestBatchPropertyId.getTestBatchPropertyIds(newTestMaterialId)); + + Collections.shuffle(propertyIds, new Random(randomGenerator.nextLong())); + for (TestBatchPropertyId testBatchPropertyId : propertyIds) { + if (testBatchPropertyId.getPropertyDefinition().getDefaultValue().isEmpty()) { + Object propertyValue = testBatchPropertyId.getRandomPropertyValue(randomGenerator); + batchBuilder.setPropertyValue(testBatchPropertyId, propertyValue); + } + } + + BatchConstructionInfo batchConstructionInfo = batchBuilder.build(); + + materialsDataManager.addBatch(batchConstructionInfo); + } + + }); + } + + private static void createSomeStages(RunContinuityPluginData.Builder continuityBuilder) { + // create some stages + continuityBuilder.addContextConsumer(1.8, (c) -> { + RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + for (int i = 0; i < BASE_STAGE_COUNT; i++) { + TestMaterialsProducerId materialsProducerId = TestMaterialsProducerId + .getRandomMaterialsProducerId(randomGenerator); + materialsDataManager.addStage(materialsProducerId); + } + }); + } + + private static void moveSomeBatchesToStages(RunContinuityPluginData.Builder continuityBuilder) { + // move some batches to stages + continuityBuilder.addContextConsumer(2.1, (c) -> { + RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + List materialsProducerIds = new ArrayList<>( + materialsDataManager.getMaterialsProducerIds()); + Collections.shuffle(materialsProducerIds, new Random(randomGenerator.nextLong())); + + for (MaterialsProducerId materialsProducerId : materialsProducerIds) { + List inventoryBatches = materialsDataManager.getInventoryBatches(materialsProducerId); + List stages = materialsDataManager.getStages(materialsProducerId); + + if (!stages.isEmpty()) { + // move about two thirds of the batches out of inventory and + // onto stages + Collections.shuffle(inventoryBatches, new Random(randomGenerator.nextLong())); + int n = 2 * inventoryBatches.size() / 3; + for (int i = 0; i < n; i++) { + BatchId batchId = inventoryBatches.get(i); + StageId stageId = stages.get(randomGenerator.nextInt(stages.size())); + materialsDataManager.moveBatchToStage(batchId, stageId); + } + } + } + }); + } + + private static void removeSomeStagesAndBatches(RunContinuityPluginData.Builder continuityBuilder) { + // remove some stages and batches + continuityBuilder.addContextConsumer(2.4, (c) -> { + RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + List materialsProducerIds = new ArrayList<>( + materialsDataManager.getMaterialsProducerIds()); + Collections.shuffle(materialsProducerIds, new Random(randomGenerator.nextLong())); + + for (MaterialsProducerId materialsProducerId : materialsProducerIds) { + List inventoryBatches = materialsDataManager.getInventoryBatches(materialsProducerId); + List stages = materialsDataManager.getStages(materialsProducerId); + + Collections.shuffle(inventoryBatches, new Random(randomGenerator.nextLong())); + int n = inventoryBatches.size() / 10; + for (int i = 0; i < n; i++) { + materialsDataManager.removeBatch(inventoryBatches.get(i)); + } + + Collections.shuffle(stages, new Random(randomGenerator.nextLong())); + n = stages.size() / 10; + for (int i = 0; i < n; i++) { + materialsDataManager.removeStage(stages.get(i), randomGenerator.nextBoolean()); + } + } + }); + } + + private static void convertSomeStages(RunContinuityPluginData.Builder continuityBuilder) { + // convert some stages to batches or resources + continuityBuilder.addContextConsumer(2.7, (c) -> { + RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + List materialsProducerIds = new ArrayList<>( + materialsDataManager.getMaterialsProducerIds()); + Collections.shuffle(materialsProducerIds, new Random(randomGenerator.nextLong())); + + for (MaterialsProducerId materialsProducerId : materialsProducerIds) { + + List stages = materialsDataManager.getStages(materialsProducerId); + + Collections.shuffle(stages, new Random(randomGenerator.nextLong())); + int n = stages.size() / 10; + for (int i = 0; i < n; i++) { + if (randomGenerator.nextBoolean()) { + double amount = randomGenerator.nextDouble() * 100; + TestMaterialId materialId = TestMaterialId.getRandomMaterialId(randomGenerator); + StageConversionInfo.Builder builder = StageConversionInfo.builder();// + builder.setAmount(amount);// + builder.setMaterialId(materialId);// + builder.setStageId(stages.get(i));// + + Set batchPropertyIds = materialsDataManager + .getBatchPropertyIds(materialId); + + for (TestBatchPropertyId batchPropertyId : batchPropertyIds) { + PropertyDefinition propertyDefinition = materialsDataManager + .getBatchPropertyDefinition(materialId, batchPropertyId); + if (propertyDefinition.getDefaultValue().isEmpty()) { + Object propertyValue = batchPropertyId.getRandomPropertyValue(randomGenerator); + builder.setPropertyValue(batchPropertyId, propertyValue); + } + } + + StageConversionInfo stageConversionInfo = builder.build(); + materialsDataManager.convertStageToBatch(stageConversionInfo); + } else { + TestResourceId resourceId = TestResourceId.getRandomResourceId(randomGenerator); + long amount = randomGenerator.nextInt(100) + 1; + materialsDataManager.convertStageToResource(stages.get(i), resourceId, amount); + } + } + } + + }); + } + + private static void offerStages(RunContinuityPluginData.Builder continuityBuilder) { + // offer a few stages + continuityBuilder.addContextConsumer(2.9, (c) -> { + RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + List materialsProducerIds = new ArrayList<>( + materialsDataManager.getMaterialsProducerIds()); + Collections.shuffle(materialsProducerIds, new Random(randomGenerator.nextLong())); + + for (MaterialsProducerId materialsProducerId : materialsProducerIds) { + List stages = materialsDataManager.getStages(materialsProducerId); + Collections.shuffle(stages, new Random(randomGenerator.nextLong())); + int n = stages.size() / 6; + for (int i = 0; i < n; i++) { + StageId stageId = stages.get(i); + materialsDataManager.setStageOfferState(stageId, true); + } + } + }); + } + + private static void moveBatchesToInventory(RunContinuityPluginData.Builder continuityBuilder) { + // move some batches to inventory + continuityBuilder.addContextConsumer(3.1, (c) -> { + RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + List materialsProducerIds = new ArrayList<>( + materialsDataManager.getMaterialsProducerIds()); + Collections.shuffle(materialsProducerIds, new Random(randomGenerator.nextLong())); + + for (MaterialsProducerId materialsProducerId : materialsProducerIds) { + List stages = materialsDataManager.getStages(materialsProducerId); + Collections.shuffle(stages, new Random(randomGenerator.nextLong())); + + for (StageId stageId : stages) { + if (!materialsDataManager.isStageOffered(stageId)) { + List batches = materialsDataManager.getStageBatches(stageId); + Collections.shuffle(batches, new Random(randomGenerator.nextLong())); + + for (BatchId batchId : batches) { + if (randomGenerator.nextDouble() < 0.3) { + materialsDataManager.moveBatchToInventory(batchId); + } + } + } + } + } + + }); + } + + private static void setSomeBatchProperties(RunContinuityPluginData.Builder continuityBuilder) { + // set some batch properties + continuityBuilder.addContextConsumer(3.5, (c) -> { + + RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + List materialsProducerIds = new ArrayList<>( + materialsDataManager.getMaterialsProducerIds()); + Collections.shuffle(materialsProducerIds, new Random(randomGenerator.nextLong())); + + for (MaterialsProducerId materialsProducerId : materialsProducerIds) { + List stages = materialsDataManager.getStages(materialsProducerId); + + Collections.shuffle(stages, new Random(randomGenerator.nextLong())); + + for (StageId stageId : stages) { + if (!materialsDataManager.isStageOffered(stageId)) { + List batches = materialsDataManager.getStageBatches(stageId); + + Collections.shuffle(batches, new Random(randomGenerator.nextLong())); + + for (BatchId batchId : batches) { + MaterialId materialId = materialsDataManager.getBatchMaterial(batchId); + List batchPropertyIds = new ArrayList<>( + materialsDataManager.getBatchPropertyIds(materialId)); + + Collections.shuffle(batchPropertyIds, new Random(randomGenerator.nextLong())); + + for (TestBatchPropertyId batchPropertyId : batchPropertyIds) { + PropertyDefinition propertyDefinition = materialsDataManager + .getBatchPropertyDefinition(materialId, batchPropertyId); + if (propertyDefinition.propertyValuesAreMutable()) { + if (randomGenerator.nextDouble() < 0.25) { + Object propertyValue = batchPropertyId.getRandomPropertyValue(randomGenerator); + materialsDataManager.setBatchPropertyValue(batchId, batchPropertyId, + propertyValue); + } + } + } + } + } + } + + List inventoryBatches = materialsDataManager.getInventoryBatches(materialsProducerId); + Collections.shuffle(inventoryBatches, new Random(randomGenerator.nextLong())); + + for (BatchId batchId : inventoryBatches) { + MaterialId materialId = materialsDataManager.getBatchMaterial(batchId); + List batchPropertyIds = new ArrayList<>( + materialsDataManager.getBatchPropertyIds(materialId)); + Collections.shuffle(batchPropertyIds, new Random(randomGenerator.nextLong())); + + for (TestBatchPropertyId batchPropertyId : batchPropertyIds) { + PropertyDefinition propertyDefinition = materialsDataManager + .getBatchPropertyDefinition(materialId, batchPropertyId); + if (propertyDefinition.propertyValuesAreMutable()) { + if (randomGenerator.nextDouble() < 0.25) { + Object propertyValue = batchPropertyId.getRandomPropertyValue(randomGenerator); + materialsDataManager.setBatchPropertyValue(batchId, batchPropertyId, propertyValue); + } + } + } + } + } + }); + } + + private static void setMoreProducerProperties(RunContinuityPluginData.Builder continuityBuilder) { + // set some materials producer properties, add a new resource + continuityBuilder.addContextConsumer(5.5, (c) -> { + RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + List materialsProducerIds = new ArrayList<>( + materialsDataManager.getMaterialsProducerIds()); + Collections.shuffle(materialsProducerIds, new Random(randomGenerator.nextLong())); + + for (MaterialsProducerId materialsProducerId : materialsProducerIds) { + List materialsProducerPropertyIds = new ArrayList<>( + materialsDataManager.getMaterialsProducerPropertyIds()); + + Collections.shuffle(materialsProducerPropertyIds, new Random(randomGenerator.nextLong())); + + for (TestMaterialsProducerPropertyId materialsProducerPropertyId : materialsProducerPropertyIds) { + PropertyDefinition propertyDefinition = materialsDataManager + .getMaterialsProducerPropertyDefinition(materialsProducerPropertyId); + if (propertyDefinition.propertyValuesAreMutable()) { + Object propertyValue = materialsProducerPropertyId.getRandomPropertyValue(randomGenerator); + materialsDataManager.setMaterialsProducerPropertyValue(materialsProducerId, + materialsProducerPropertyId, propertyValue); + } + } + } + ResourceId newResourceId = new ResourceId() { + @Override + public String toString() { + return "RESOURCE_6"; + } + }; + + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.addResourceId(newResourceId, false); + }); + } + + private static void transferMaterialsBetweenBatches(RunContinuityPluginData.Builder continuityBuilder) { + // transfer materials between batches + continuityBuilder.addContextConsumer(5.6, (c) -> { + RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + List materialsProducerIds = new ArrayList<>( + materialsDataManager.getMaterialsProducerIds()); + Collections.shuffle(materialsProducerIds, new Random(randomGenerator.nextLong())); + + for (MaterialsProducerId materialsProducerId : materialsProducerIds) { + List materialIds = new ArrayList<>(materialsDataManager.getMaterialIds()); + Collections.shuffle(materialIds, new Random(randomGenerator.nextLong())); + + for (MaterialId materialId : materialIds) { + List batches = materialsDataManager.getInventoryBatchesByMaterialId(materialsProducerId, + materialId); + if (batches.size() > 1) { + Collections.shuffle(batches, new Random(randomGenerator.nextLong())); + + int n = batches.size(); + + for (int i = 0; i < n; i++) { + BatchId sourceBatchId = batches.get(randomGenerator.nextInt(batches.size())); + BatchId destinationBatchId = sourceBatchId; + while (destinationBatchId.equals(sourceBatchId)) { + destinationBatchId = batches.get(randomGenerator.nextInt(batches.size())); + } + double amount = materialsDataManager.getBatchAmount(sourceBatchId); + amount /= 2; + materialsDataManager.transferMaterialBetweenBatches(sourceBatchId, destinationBatchId, + amount); + } + } + } + } + }); + + } + + private static void transferAnOfferedStage(RunContinuityPluginData.Builder continuityBuilder) { + // transfer an offered stage + continuityBuilder.addContextConsumer(5.9, (c) -> { + RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + + List materialsProducerIds = new ArrayList<>( + materialsDataManager.getMaterialsProducerIds()); + Collections.shuffle(materialsProducerIds, new Random(randomGenerator.nextLong())); + + for (TestMaterialsProducerId materialsProducerId : materialsProducerIds) { + List offeredStages = materialsDataManager.getOfferedStages(materialsProducerId); + + Collections.shuffle(offeredStages, new Random(randomGenerator.nextLong())); + + for (StageId stageId : offeredStages) { + if (randomGenerator.nextBoolean()) { + TestMaterialsProducerId nextMaterialsProducerId = materialsProducerId.next(); + materialsDataManager.transferOfferedStage(stageId, nextMaterialsProducerId); + } + } + } + + }); + } + + private static void transferResourcesToRegions(RunContinuityPluginData.Builder continuityBuilder) { + // transfer resources to regions + continuityBuilder.addContextConsumer(6.34, (c) -> { + RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + List resourceIds = new ArrayList<>(resourcesDataManager.getResourceIds()); + Collections.shuffle(resourceIds, new Random(randomGenerator.nextLong())); + + for (ResourceId resourceId : resourceIds) { + + List materialsProducerIds = new ArrayList<>( + materialsDataManager.getMaterialsProducerIds()); + Collections.shuffle(materialsProducerIds, new Random(randomGenerator.nextLong())); + + for (MaterialsProducerId materialsProducerId : materialsProducerIds) { + TestRegionId regionId = TestRegionId.getRandomRegionId(randomGenerator); + long amount = materialsDataManager.getMaterialsProducerResourceLevel(materialsProducerId, + resourceId); + amount /= 2; + materialsDataManager.transferResourceToRegion(materialsProducerId, resourceId, regionId, amount); + } + } + }); + } + + private static void reportMaterialsManagerState(RunContinuityPluginData.Builder continuityBuilder) { + // transfer resources to regions + continuityBuilder.addContextConsumer(7.0, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + c.releaseOutput(materialsDataManager.toString()); + }); + } + + private static RunContinuityPluginData getRunContinuityPluginData() { + RunContinuityPluginData.Builder continuityBuilder = RunContinuityPluginData.builder(); + + addFirstPeople(continuityBuilder); + startMaterialsProduction(continuityBuilder); + createFirstBatches(continuityBuilder); + addMoreMaterialsDetails(continuityBuilder); + createSomeStages(continuityBuilder); + moveSomeBatchesToStages(continuityBuilder); + removeSomeStagesAndBatches(continuityBuilder); + convertSomeStages(continuityBuilder); + offerStages(continuityBuilder); + moveBatchesToInventory(continuityBuilder); + setSomeBatchProperties(continuityBuilder); + setMoreProducerProperties(continuityBuilder); + transferMaterialsBetweenBatches(continuityBuilder); + transferAnOfferedStage(continuityBuilder); + transferResourcesToRegions(continuityBuilder); + reportMaterialsManagerState(continuityBuilder); + + return continuityBuilder.build(); + + } + + /* + * Returns the default Simulation state -- time starts at zero synchronized to + * the beginning of the epoch. + */ + private static SimulationState getSimulationState() { + return SimulationState.builder().build(); + } + + /* + * Returns an empty people plugin data + */ + private static PeoplePluginData getPeoplePluginData() { + return PeoplePluginData.builder().build(); + } + + /* + * Returns the stochastics plugin data with only the main random generator. + */ + private static StochasticsPluginData getStochasticsPluginData(long seed) { + + WellState wellState = WellState.builder()// + .setSeed(seed)// + .build(); + + return StochasticsPluginData.builder().setMainRNGState(wellState).build(); + } + + /* + * Returns the duration for a single incremented run of the simulation. This is + * determined by finding the last scheduled task in the run continuity plugin + * data and dividing that by the number of increments. + */ + private static double getSimulationTimeIncrement(StateData stateData, int incrementCount) { + double maxTime = Double.NEGATIVE_INFINITY; + for (Pair> pair : stateData.runContinuityPluginData.getConsumers()) { + Double time = pair.getFirst(); + maxTime = FastMath.max(maxTime, time); + } + + return maxTime / incrementCount; + } + + /* + * Returns the ordered state of the Materials Data Manager as a string + */ + private String testStateContinuity(int incrementCount, long seed) { + + /* + * We initialize the various plugin datas needed for the simulation + */ + StateData stateData = getInitialState(seed); + + // We will break up the simulation run into several runs, each lasting a + // fixed duration + double timeIncrement = getSimulationTimeIncrement(stateData, incrementCount); + + while (!stateData.runContinuityPluginData.allPlansComplete()) { + stateData.haltTime += timeIncrement; + runSimulation(stateData); + } + + /* + * When the simulation has finished -- the plans contained in the run continuity + * plugin data have been completed, the string state of the attributes data + * manager is returned + */ + + // show that the groups data manager toString() is returning something + // reasonable + assertNotNull(stateData.output); + assertTrue(stateData.output.length() > 100); + + return stateData.output; + } + + private static void runSimulation(StateData stateData) { + + // build the people plugin + Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(stateData.peoplePluginData); + + // build the materials plugin + Plugin materialsPlugin = MaterialsPlugin.builder().setMaterialsPluginData(stateData.materialsPluginData) + .getMaterialsPlugin(); + + // build the run continuity plugin + Plugin runContinuityPlugin = RunContinuityPlugin.builder()// + .setRunContinuityPluginData(stateData.runContinuityPluginData)// + .build(); + + // build the regions plugin + Plugin regionsPlugin = RegionsPlugin.builder().setRegionsPluginData(stateData.regionsPluginData) + .getRegionsPlugin(); + + // build the resources plugin + Plugin resourcesPlugin = ResourcesPlugin.builder().setResourcesPluginData(stateData.resourcesPluginData) + .getResourcesPlugin(); + + // build the stochastics plugin + Plugin stochasticsPlugin = StochasticsPlugin.getStochasticsPlugin(stateData.stochasticsPluginData); + + TestOutputConsumer outputConsumer = new TestOutputConsumer(); + + // execute the simulation so that it produces a people plugin data + Simulation simulation = Simulation.builder()// + .addPlugin(peoplePlugin)// + .addPlugin(materialsPlugin)// + .addPlugin(runContinuityPlugin)// + .addPlugin(regionsPlugin)// + .addPlugin(resourcesPlugin)// + .addPlugin(stochasticsPlugin)// + .setSimulationHaltTime(stateData.haltTime)// + .setRecordState(true)// + .setOutputConsumer(outputConsumer)// + .setSimulationState(stateData.simulationState)// + .build();// + simulation.execute(); + + // retrieve the people plugin data + stateData.peoplePluginData = outputConsumer.getOutputItem(PeoplePluginData.class).get(); + + // retrieve the materials plugin data + stateData.materialsPluginData = outputConsumer.getOutputItem(MaterialsPluginData.class).get(); + + // retrieve the run continuity plugin data + stateData.runContinuityPluginData = outputConsumer.getOutputItem(RunContinuityPluginData.class).get(); + + // retrieve the regions plugin data + stateData.regionsPluginData = outputConsumer.getOutputItem(RegionsPluginData.class).get(); + + // retrieve the resources plugin data + stateData.resourcesPluginData = outputConsumer.getOutputItem(ResourcesPluginData.class).get(); + + // retrieve the stochastics plugin data + stateData.stochasticsPluginData = outputConsumer.getOutputItem(StochasticsPluginData.class).get(); + + // retrieve the simulation state + stateData.simulationState = outputConsumer.getOutputItem(SimulationState.class).get(); + + Optional optional = outputConsumer.getOutputItem(String.class); + if (optional.isPresent()) { + stateData.output = optional.get(); + // stateData.output = stateData.materialsPluginData.toString(); + } + + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/datamanagers/AT_MaterialsPluginData.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/datamanagers/AT_MaterialsPluginData.java new file mode 100644 index 000000000..b77e43353 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/datamanagers/AT_MaterialsPluginData.java @@ -0,0 +1,2749 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.datamanagers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.PluginDataBuilder; +import gov.hhs.aspr.ms.gcm.plugins.materials.datamangers.MaterialsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsError; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.StageId; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.TestBatchPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.TestMaterialId; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.TestMaterialsProducerId; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.TestMaterialsProducerPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceError; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.TestResourceId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; +import util.wrappers.MultiKey; + +public class AT_MaterialsPluginData { + + @Test + @UnitTestMethod(target = MaterialsPluginData.class, name = "builder", args = {}) + public void testBuilder() { + assertNotNull(MaterialsPluginData.builder()); + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.Builder.class, name = "build", args = {}) + public void testBuild() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8543723876953755503L); + + MaterialsPluginData materialsInitialData = MaterialsPluginData.builder().build(); + + assertNotNull(materialsInitialData); + + assertTrue(materialsInitialData.getBatchIds().isEmpty()); + assertTrue(materialsInitialData.getMaterialIds().isEmpty()); + assertTrue(materialsInitialData.getResourceIds().isEmpty()); + assertTrue(materialsInitialData.getStageIds().isEmpty()); + + /* + * precondition test: if a batch property is associated with a material id that + * was not properly added + */ + ContractException contractException = assertThrows(ContractException.class, () -> { + MaterialId materialId = TestMaterialId.MATERIAL_1; + TestBatchPropertyId propertyId = TestBatchPropertyId.BATCH_PROPERTY_1_1_BOOLEAN_IMMUTABLE_NO_TRACK; + PropertyDefinition propertyDefinition = propertyId.getPropertyDefinition(); + MaterialsPluginData.builder()// + .defineBatchProperty(materialId, propertyId, propertyDefinition)// + .build();// + }); + assertEquals(MaterialsError.UNKNOWN_MATERIAL_ID, contractException.getErrorType()); + + /* + * precondition test: if a batch is added without assigned property values for + * each property definition that lacks a default value + */ + contractException = assertThrows(ContractException.class, () -> { + MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; + MaterialId materialId = TestMaterialId.MATERIAL_1; + TestBatchPropertyId propertyId = TestBatchPropertyId.BATCH_PROPERTY_1_1_BOOLEAN_IMMUTABLE_NO_TRACK; + PropertyDefinition propertyDefinition = PropertyDefinition.builder()// + .setType(Boolean.class)// + .setPropertyValueMutability(false)// + .build();// + + MaterialsPluginData.builder()// + .addMaterial(materialId)// + .addMaterialsProducerId(materialsProducerId)// + .addBatch(new BatchId(12), materialId, 12.3)// + .addBatchToMaterialsProducerInventory(new BatchId(12), materialsProducerId)// + .defineBatchProperty(materialId, propertyId, propertyDefinition).build();// + }); + assertEquals(PropertyError.INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT, contractException.getErrorType()); + + /* + * precondition test: if a materials property value is associated with a + * materials producer id that was not properly added + */ + contractException = assertThrows(ContractException.class, () -> { + MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; + TestMaterialsProducerPropertyId propertyId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; + PropertyDefinition propertyDefinition = propertyId.getPropertyDefinition();// + Object value = propertyId.getRandomPropertyValue(randomGenerator); + + MaterialsPluginData.builder()// + .defineMaterialsProducerProperty(propertyId, propertyDefinition)// + .setMaterialsProducerPropertyValue(materialsProducerId, propertyId, value)// + .build();// + }); + assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + /* + * precondition test: if a materials property value is associated with a + * materials producer property id that was not properly defined + */ + contractException = assertThrows(ContractException.class, () -> { + MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; + TestMaterialsProducerPropertyId propertyId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; + Object value = propertyId.getRandomPropertyValue(randomGenerator); + + MaterialsPluginData.builder()// + .addMaterialsProducerId(materialsProducerId)// + .setMaterialsProducerPropertyValue(materialsProducerId, propertyId, value)// + .build();// + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + /* + * precondition test: if a materials property value is associated with a value + * that is not compatible with the corresponding property definition + * + */ + contractException = assertThrows(ContractException.class, () -> { + MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; + TestMaterialsProducerPropertyId propertyId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; + PropertyDefinition propertyDefinition = propertyId.getPropertyDefinition();// + Object value = 12; + + MaterialsPluginData.builder()// + .addMaterialsProducerId(materialsProducerId)// + .defineMaterialsProducerProperty(propertyId, propertyDefinition)// + .setMaterialsProducerPropertyValue(materialsProducerId, propertyId, value)// + .build();// + }); + assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); + + /* + * precondition test: if a materials property is defined without a default value + * and there is not an assigned property value for each added materials producer + */ + contractException = assertThrows(ContractException.class, () -> { + MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; + TestMaterialsProducerPropertyId propertyId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; + + PropertyDefinition propertyDefinition = PropertyDefinition.builder()// + .setType(Boolean.class)// + .setPropertyValueMutability(true)// + .build(); + + MaterialsPluginData.builder()// + .addMaterialsProducerId(materialsProducerId)// + .defineMaterialsProducerProperty(propertyId, propertyDefinition)// + .build();// + }); + assertEquals(PropertyError.INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT, contractException.getErrorType()); + + /* + * precondition test: if a materials resource level is set for a material + * producer id that was not properly added + */ + + contractException = assertThrows(ContractException.class, () -> { + MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; + TestResourceId testResourceId = TestResourceId.RESOURCE_2; + Long resourceLevel = 10L; + + MaterialsPluginData.builder()// + .setMaterialsProducerResourceLevel(materialsProducerId, testResourceId, resourceLevel) // + .build();// + }); + assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + /* + * precondition test: if a batch is associated with a material that was not + * properly added + */ + contractException = assertThrows(ContractException.class, () -> { + MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; + TestMaterialId testMaterialId = TestMaterialId.MATERIAL_3; + BatchId batchId = new BatchId(67); + double amount = 345.543; + + MaterialsPluginData.builder()// + .addBatch(batchId, testMaterialId, amount)// + .addBatchToMaterialsProducerInventory(batchId, materialsProducerId)// + .addMaterialsProducerId(materialsProducerId)// + .build();// + }); + assertEquals(MaterialsError.UNKNOWN_MATERIAL_ID, contractException.getErrorType()); + + /* + * precondition test: if a batch is associated with at material producer that + * was not properly added + */ + contractException = assertThrows(ContractException.class, () -> { + MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; + TestMaterialId testMaterialId = TestMaterialId.MATERIAL_3; + BatchId batchId = new BatchId(67); + double amount = 345.543; + + MaterialsPluginData.builder()// + .addBatch(batchId, testMaterialId, amount)// + .addBatchToMaterialsProducerInventory(batchId, materialsProducerId)// + .addMaterial(testMaterialId)// + .build();// + }); + assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + /* + * precondition test: if a batch property is associated with batch id that was + * not properly added + */ + contractException = assertThrows(ContractException.class, () -> { + + TestBatchPropertyId testBatchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK; + PropertyDefinition propertyDefinition = testBatchPropertyId.getPropertyDefinition(); + testBatchPropertyId.getRandomPropertyValue(randomGenerator); + TestMaterialId testMaterialId = TestMaterialId.MATERIAL_1; + + BatchId batchId = new BatchId(67); + + MaterialsPluginData.builder()// + .defineBatchProperty(testMaterialId, testBatchPropertyId, propertyDefinition)// + .addMaterial(testMaterialId)// + .setBatchPropertyValue(batchId, testBatchPropertyId, batchId)// + .build();// + }); + assertEquals(MaterialsError.UNKNOWN_BATCH_ID, contractException.getErrorType()); + + /* + * precondition test: if a batch property is associated with batch property id + * that was not properly defined + */ + contractException = assertThrows(ContractException.class, () -> { + TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; + TestBatchPropertyId testBatchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK; + Object value = testBatchPropertyId.getRandomPropertyValue(randomGenerator); + TestMaterialId testMaterialId = TestMaterialId.MATERIAL_1; + + BatchId batchId = new BatchId(67); + double amount = 345.54; + + MaterialsPluginData.builder()// + .addBatch(batchId, testMaterialId, amount)// + .addBatchToMaterialsProducerInventory(batchId, testMaterialsProducerId)// + .addMaterial(testMaterialId)// + .setBatchPropertyValue(batchId, testBatchPropertyId, value)// + .addMaterialsProducerId(testMaterialsProducerId)// + .build();// + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + /* + * precondition test: if a batch property value is incompatible with the + * corresponding property definition + */ + + contractException = assertThrows(ContractException.class, () -> { + TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; + TestBatchPropertyId testBatchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK; + PropertyDefinition propertyDefinition = testBatchPropertyId.getPropertyDefinition(); + Object incompatibleValue = "bad value"; + TestMaterialId testMaterialId = TestMaterialId.MATERIAL_1; + + BatchId batchId = new BatchId(67); + double amount = 345.54; + + MaterialsPluginData.builder()// + .addBatch(batchId, testMaterialId, amount)// + .addBatchToMaterialsProducerInventory(batchId, testMaterialsProducerId)// + .addMaterial(testMaterialId)// + .setBatchPropertyValue(batchId, testBatchPropertyId, incompatibleValue)// + .addMaterialsProducerId(testMaterialsProducerId)// + .defineBatchProperty(testMaterialId, testBatchPropertyId, propertyDefinition)// + .build();// + }); + assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); + + /* + * precondition test: if a stage is associated with a materials producer id that + * was not properly added + */ + contractException = assertThrows(ContractException.class, () -> { + TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; + StageId stageId = new StageId(543); + boolean offered = false; + + MaterialsPluginData.builder()// + .addStage(stageId, offered)// + .addStageToMaterialProducer(stageId, testMaterialsProducerId)// + .build();// + }); + assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + /* + * precondition test: if a batch is associated with a stage id that was not + * properly added + */ + + contractException = assertThrows(ContractException.class, () -> { + TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; + StageId stageId = new StageId(543); + BatchId batchId = new BatchId(55); + double amount = 86.0; + TestMaterialId testMaterialId = TestMaterialId.MATERIAL_3; + + MaterialsPluginData.builder()// + .addBatch(batchId, testMaterialId, amount)// + .addBatchToStage(stageId, batchId)// + .addMaterial(testMaterialId)// + .addMaterialsProducerId(testMaterialsProducerId)// + .build();// + }); + assertEquals(MaterialsError.UNKNOWN_STAGE_ID, contractException.getErrorType()); + + /* + * precondition test: if a stage is associated with a batch id that was not + * properly added + */ + contractException = assertThrows(ContractException.class, () -> { + TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; + StageId stageId = new StageId(543); + BatchId batchId = new BatchId(55); + TestMaterialId testMaterialId = TestMaterialId.MATERIAL_3; + boolean offered = false; + + MaterialsPluginData.builder()// + .addStage(stageId, offered)// + .addStageToMaterialProducer(stageId, testMaterialsProducerId)// + .addBatchToStage(stageId, batchId)// + .addMaterial(testMaterialId)// + .addMaterialsProducerId(testMaterialsProducerId)// + .build();// + }); + assertEquals(MaterialsError.UNKNOWN_BATCH_ID, contractException.getErrorType()); + + /* + * precondition test: if a batch is associated with more than one stage + */ + contractException = assertThrows(ContractException.class, () -> { + TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; + StageId stageId1 = new StageId(543); + StageId stageId2 = new StageId(659); + BatchId batchId = new BatchId(55); + TestMaterialId testMaterialId = TestMaterialId.MATERIAL_3; + boolean offered = false; + double amount = 765.87; + + MaterialsPluginData.builder()// + .addBatch(batchId, testMaterialId, amount)// + .addStage(stageId1, offered)// + .addStageToMaterialProducer(stageId1, testMaterialsProducerId)// + .addStage(stageId2, offered)// + .addStageToMaterialProducer(stageId2, testMaterialsProducerId)// + .addBatchToStage(stageId1, batchId)// + .addBatchToStage(stageId2, batchId)// + .addMaterial(testMaterialId)// + .addMaterialsProducerId(testMaterialsProducerId)// + .build();// + }); + assertEquals(MaterialsError.BATCH_ALREADY_STAGED, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.Builder.class, name = "addBatch", args = { BatchId.class, + MaterialId.class, double.class }) + public void testAddBatch() { + BatchId batchId = new BatchId(456); + MaterialId materialId = TestMaterialId.MATERIAL_1; + double amount = 16.7; + MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_3; + + /* + * adding duplicate data to show that the value persists + */ + MaterialsPluginData materialsInitialData = MaterialsPluginData.builder()// + .addBatch(batchId, materialId, amount)// + .addBatchToMaterialsProducerInventory(batchId, materialsProducerId)// + + .addBatch(batchId, materialId, amount)// + .addBatchToMaterialsProducerInventory(batchId, materialsProducerId)// + .addMaterial(materialId)// + .addMaterialsProducerId(materialsProducerId).build();// + + assertTrue(materialsInitialData.getBatchIds().contains(batchId)); + assertEquals(materialId, materialsInitialData.getBatchMaterial(batchId)); + assertEquals(amount, materialsInitialData.getBatchAmount(batchId)); + assertTrue(materialsInitialData.getMaterialsProducerInventoryBatches(materialsProducerId).contains(batchId)); + + // idempotency tests + + MaterialId materialId2 = TestMaterialId.MATERIAL_2; + double amount2 = 76.1; + + materialsInitialData = MaterialsPluginData.builder()// + .addBatch(batchId, materialId, amount)// + .addBatchToMaterialsProducerInventory(batchId, materialsProducerId)// + // replacing data to show + // that the value persists + .addBatch(batchId, materialId2, amount2)// + .addBatchToMaterialsProducerInventory(batchId, materialsProducerId)// + .addMaterial(materialId2)// + .addMaterialsProducerId(materialsProducerId).build();// + + assertTrue(materialsInitialData.getBatchIds().contains(batchId)); + assertEquals(materialId2, materialsInitialData.getBatchMaterial(batchId)); + assertEquals(amount2, materialsInitialData.getBatchAmount(batchId)); + assertTrue(materialsInitialData.getMaterialsProducerInventoryBatches(materialsProducerId).contains(batchId)); + + // precondition tests + + // if the batch id is null + ContractException contractException = assertThrows(ContractException.class, + () -> MaterialsPluginData.builder().addBatch(null, materialId, amount)); + assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); + + // if the material id is null + contractException = assertThrows(ContractException.class, + () -> MaterialsPluginData.builder().addBatch(batchId, null, amount)); + assertEquals(MaterialsError.NULL_MATERIAL_ID, contractException.getErrorType()); + + // if the material amount is infinite + contractException = assertThrows(ContractException.class, + () -> MaterialsPluginData.builder().addBatch(batchId, materialId, Double.POSITIVE_INFINITY)); + assertEquals(MaterialsError.NON_FINITE_MATERIAL_AMOUNT, contractException.getErrorType()); + + // if the material amount is negative + contractException = assertThrows(ContractException.class, + () -> MaterialsPluginData.builder().addBatch(batchId, materialId, -1)); + assertEquals(MaterialsError.NEGATIVE_MATERIAL_AMOUNT, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.Builder.class, name = "addBatchToMaterialsProducerInventory", args = { + BatchId.class, MaterialsProducerId.class }) + public void testAddBatchToMaterialsProducerInventory() { + + BatchId batchId = new BatchId(456); + MaterialId materialId = TestMaterialId.MATERIAL_1; + MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_3; + double amount = 16.7; + + MaterialsPluginData materialsPluginData = MaterialsPluginData.builder()// + .addBatch(batchId, materialId, amount)// + .addMaterial(materialId)// + .addMaterialsProducerId(materialsProducerId)// + .addBatchToMaterialsProducerInventory(batchId, materialsProducerId).build();// + + assertTrue(materialsPluginData.getMaterialsProducerInventoryBatches(materialsProducerId).contains(batchId)); + + // precondition test: if the batch id is null + ContractException contractException = assertThrows(ContractException.class, + () -> MaterialsPluginData.builder().addBatchToMaterialsProducerInventory(null, materialsProducerId)); + assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); + + // precondition test: if the materials producer id is null + contractException = assertThrows(ContractException.class, + () -> MaterialsPluginData.builder().addBatchToMaterialsProducerInventory(batchId, null)); + assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.Builder.class, name = "addBatchToStage", args = { StageId.class, + BatchId.class }) + public void testAddBatchToStage() { + BatchId batchId = new BatchId(456); + StageId stageId = new StageId(543); + MaterialId materialId = TestMaterialId.MATERIAL_1; + double amount = 16.7; + MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_3; + + /* + * adding duplicate data to show that the value persists + */ + + MaterialsPluginData materialsInitialData = MaterialsPluginData.builder()// + .addBatch(batchId, materialId, amount)// + .addBatchToStage(stageId, batchId)// + .addBatchToStage(stageId, batchId)// + .addStage(stageId, false)// + .addStageToMaterialProducer(stageId, materialsProducerId)// + .addMaterial(materialId)// + .addMaterialsProducerId(materialsProducerId)// + .build();// + + assertTrue(materialsInitialData.getStageBatches(stageId).contains(batchId)); + + // precondition tests + + // if the stage id is null + ContractException contractException = assertThrows(ContractException.class, + () -> MaterialsPluginData.builder().addBatchToStage(null, batchId)); + assertEquals(MaterialsError.NULL_STAGE_ID, contractException.getErrorType()); + + // if the batch id is null + contractException = assertThrows(ContractException.class, + () -> MaterialsPluginData.builder().addBatchToStage(stageId, null)); + assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.Builder.class, name = "addMaterial", args = { MaterialId.class }) + public void testAddMaterial() { + + MaterialId materialId = TestMaterialId.MATERIAL_1; + + MaterialsPluginData materialsInitialData = MaterialsPluginData.builder()// + .addMaterial(materialId) + // adding + // duplicate + // data + // to + // show + // that + // the + // value + // persists + .addMaterial(materialId)// + .build();// + + assertTrue(materialsInitialData.getMaterialIds().contains(materialId)); + + // if the material id is null + ContractException contractException = assertThrows(ContractException.class, + () -> MaterialsPluginData.builder().addMaterial(null)); + assertEquals(MaterialsError.NULL_MATERIAL_ID, contractException.getErrorType()); + + // show that duplicated values persist + Set expectedIds = new LinkedHashSet<>(); + MaterialsPluginData.Builder builder = MaterialsPluginData.builder(); + for (MaterialId matId : TestMaterialId.values()) { + expectedIds.add(matId); + builder.addMaterial(matId).addMaterial(matId); + } + materialsInitialData = builder.build(); + Set actualIds = materialsInitialData.getMaterialIds(); + assertEquals(expectedIds, actualIds); + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.Builder.class, name = "addMaterialsProducerId", args = { + MaterialsProducerId.class }) + public void testAddMaterialsProducerId() { + MaterialsProducerId materialsProducerId1 = TestMaterialsProducerId.MATERIALS_PRODUCER_1; + MaterialsProducerId materialsProducerId2 = TestMaterialsProducerId.MATERIALS_PRODUCER_2; + + MaterialsPluginData materialsInitialData = MaterialsPluginData.builder()// + .addMaterialsProducerId(materialsProducerId1)// + // adding + // duplicate + // data + // to + // show + // that + // the + // value + // persists + .addMaterialsProducerId(materialsProducerId1).addMaterialsProducerId(materialsProducerId2)// + .build();// + + // show that the materials producer ids were added + assertTrue(materialsInitialData.getMaterialsProducerIds().contains(materialsProducerId1)); + + assertTrue(materialsInitialData.getMaterialsProducerIds().contains(materialsProducerId2)); + + // precondition tests + + // if the material id is null + ContractException contractException = assertThrows(ContractException.class, + () -> MaterialsPluginData.builder().addMaterialsProducerId(null)); + assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.Builder.class, name = "addStageToMaterialProducer", args = { + StageId.class, MaterialsProducerId.class }) + public void testAddStageToMaterialProducer() { + StageId stageId = new StageId(456); + MaterialsProducerId materialsProducerId1 = TestMaterialsProducerId.MATERIALS_PRODUCER_1; + MaterialsProducerId materialsProducerId2 = TestMaterialsProducerId.MATERIALS_PRODUCER_2; + MaterialsPluginData materialsPluginData = MaterialsPluginData.builder()// + .addStage(stageId, false)// + .addStageToMaterialProducer(stageId, materialsProducerId1)// + .addStageToMaterialProducer(stageId, materialsProducerId1)// duplicated command + .addMaterialsProducerId(materialsProducerId1)// + .addMaterialsProducerId(materialsProducerId2)// + .build(); + assertTrue(materialsPluginData.getMaterialsProducerStages(materialsProducerId1).contains(stageId)); + assertFalse(materialsPluginData.getMaterialsProducerStages(materialsProducerId2).contains(stageId)); + + // precondition test: if the stage id is null + ContractException contractException = assertThrows(ContractException.class, + () -> MaterialsPluginData.builder().addStageToMaterialProducer(null, materialsProducerId1)); + assertEquals(MaterialsError.NULL_STAGE_ID, contractException.getErrorType()); + + // precondition test: if the materials producer id is null + contractException = assertThrows(ContractException.class, + () -> MaterialsPluginData.builder().addStageToMaterialProducer(stageId, null)); + assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.Builder.class, name = "addStage", args = { StageId.class, + boolean.class }) + public void testAddStage() { + StageId stageId = new StageId(456); + boolean offered = true; + MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_3; + + MaterialsPluginData materialsInitialData = MaterialsPluginData.builder()// + .addStage(stageId, offered)// + .addStageToMaterialProducer(stageId, materialsProducerId)// + .addStage(stageId, offered)// + .addStageToMaterialProducer(stageId, materialsProducerId)// + .addMaterialsProducerId(materialsProducerId)// + .build();// + assertTrue(materialsInitialData.getStageIds().contains(stageId)); + assertEquals(offered, materialsInitialData.isStageOffered(stageId)); + assertTrue(materialsInitialData.getMaterialsProducerStages(materialsProducerId).contains(stageId)); + + offered = false; + materialsInitialData = MaterialsPluginData.builder()// + .addStage(stageId, offered)// + .addStageToMaterialProducer(stageId, materialsProducerId)// + .addStage(stageId, offered)// + .addStageToMaterialProducer(stageId, materialsProducerId)// + .addMaterialsProducerId(materialsProducerId)// + .build();// + + assertTrue(materialsInitialData.getStageIds().contains(stageId)); + assertEquals(offered, materialsInitialData.isStageOffered(stageId)); + assertTrue(materialsInitialData.getMaterialsProducerStages(materialsProducerId).contains(stageId)); + + // idempotency tests + + boolean offered2 = true; + MaterialsProducerId materialsProducerId2 = TestMaterialsProducerId.MATERIALS_PRODUCER_2; + + /* + * replacing data to show that the value persists + * + */ + materialsInitialData = MaterialsPluginData.builder()// + .addStage(stageId, offered)// + .addStageToMaterialProducer(stageId, materialsProducerId2)// + .addStage(stageId, offered2)// + .addStageToMaterialProducer(stageId, materialsProducerId2)// + .addMaterialsProducerId(materialsProducerId2)// + .build();// + assertTrue(materialsInitialData.getStageIds().contains(stageId)); + assertEquals(offered2, materialsInitialData.isStageOffered(stageId)); + assertTrue(materialsInitialData.getMaterialsProducerStages(materialsProducerId2).contains(stageId)); + + offered = true; + offered2 = false; + materialsInitialData = MaterialsPluginData.builder()// + .addStage(stageId, offered) // + .addStageToMaterialProducer(stageId, materialsProducerId2)// + .addStage(stageId, offered2)// + .addStageToMaterialProducer(stageId, materialsProducerId2)// + .addMaterialsProducerId(materialsProducerId2)// + .build();// + + assertTrue(materialsInitialData.getStageIds().contains(stageId)); + assertEquals(offered2, materialsInitialData.isStageOffered(stageId)); + assertTrue(materialsInitialData.getMaterialsProducerStages(materialsProducerId2).contains(stageId)); + + // precondition test: if the stage id is null + ContractException contractException = assertThrows(ContractException.class, + () -> MaterialsPluginData.builder().addStage(null, true)); + assertEquals(MaterialsError.NULL_STAGE_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.Builder.class, name = "defineBatchProperty", args = { MaterialId.class, + BatchPropertyId.class, PropertyDefinition.class }) + public void testDefineBatchProperty() { + + MaterialsPluginData.Builder builder = MaterialsPluginData.builder();// + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + builder.addMaterial(testMaterialId); + } + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.values()) { + // adding duplicate data to show that the value persists + builder.defineBatchProperty(testBatchPropertyId.getTestMaterialId(), testBatchPropertyId, + testBatchPropertyId.getPropertyDefinition()) + .defineBatchProperty(testBatchPropertyId.getTestMaterialId(), testBatchPropertyId, + testBatchPropertyId.getPropertyDefinition()); + } + MaterialsPluginData materialsInitialData = builder.build();// + + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.values()) { + TestMaterialId testMaterialId = testBatchPropertyId.getTestMaterialId(); + assertTrue(materialsInitialData.getBatchPropertyIds(testMaterialId).contains(testBatchPropertyId)); + PropertyDefinition actualPropertyDefinition = materialsInitialData + .getBatchPropertyDefinition(testMaterialId, testBatchPropertyId); + PropertyDefinition expectedPropertyDefinition = testBatchPropertyId.getPropertyDefinition(); + assertEquals(expectedPropertyDefinition, actualPropertyDefinition); + } + // idempotency tests + TestBatchPropertyId testBatchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK; + TestMaterialId testMaterialId = testBatchPropertyId.getTestMaterialId(); + PropertyDefinition propertyDefinition = testBatchPropertyId.getPropertyDefinition(); + + // show that replaced values persist + builder = MaterialsPluginData.builder(); + TestBatchPropertyId testBatchPropertyId2 = TestBatchPropertyId.BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK; + PropertyDefinition propertyDefinition2 = testBatchPropertyId2.getPropertyDefinition(); + builder.addMaterial(testMaterialId); + builder.defineBatchProperty(testMaterialId, testBatchPropertyId, propertyDefinition) + .defineBatchProperty(testMaterialId, testBatchPropertyId2, propertyDefinition2); + materialsInitialData = builder.build(); + assertTrue(materialsInitialData.getBatchPropertyIds(testMaterialId).contains(testBatchPropertyId2)); + PropertyDefinition actualPropertyDefinition2 = materialsInitialData.getBatchPropertyDefinition(testMaterialId, + testBatchPropertyId2); + PropertyDefinition expectedPropertyDefinition2 = testBatchPropertyId2.getPropertyDefinition(); + assertEquals(expectedPropertyDefinition2, actualPropertyDefinition2); + + // precondition test: if the batch property id is null + ContractException contractException = assertThrows(ContractException.class, + () -> MaterialsPluginData.builder().defineBatchProperty(testMaterialId, null, propertyDefinition)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // precondition test: if the material id is null + contractException = assertThrows(ContractException.class, + () -> MaterialsPluginData.builder().defineBatchProperty(null, testBatchPropertyId, propertyDefinition)); + assertEquals(MaterialsError.NULL_MATERIAL_ID, contractException.getErrorType()); + + // precondition test: if the property definition is null + contractException = assertThrows(ContractException.class, + () -> MaterialsPluginData.builder().defineBatchProperty(testMaterialId, testBatchPropertyId, null)); + assertEquals(PropertyError.NULL_PROPERTY_DEFINITION, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.Builder.class, name = "defineMaterialsProducerProperty", args = { + MaterialsProducerPropertyId.class, PropertyDefinition.class }) + public void testDefineMaterialsProducerProperty() { + MaterialsPluginData.Builder builder = MaterialsPluginData.builder();// + + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .values()) { + // adding duplicate data to show that the value persists + builder.defineMaterialsProducerProperty(testMaterialsProducerPropertyId, + testMaterialsProducerPropertyId.getPropertyDefinition()).defineMaterialsProducerProperty( + testMaterialsProducerPropertyId, testMaterialsProducerPropertyId.getPropertyDefinition()); + } + MaterialsPluginData materialsInitialData = builder.build();// + + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .values()) { + assertTrue( + materialsInitialData.getMaterialsProducerPropertyIds().contains(testMaterialsProducerPropertyId)); + PropertyDefinition actualPropertyDefinition = materialsInitialData + .getMaterialsProducerPropertyDefinition(testMaterialsProducerPropertyId); + PropertyDefinition expectedPropertyDefinition = testMaterialsProducerPropertyId.getPropertyDefinition(); + assertEquals(expectedPropertyDefinition, actualPropertyDefinition); + } + + TestMaterialsProducerPropertyId testMaterialsProducerPropertyId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK; + + PropertyDefinition propertyDefinition = testMaterialsProducerPropertyId.getPropertyDefinition(); + // idempotency tests + + // show that replaced values persist + builder = MaterialsPluginData.builder(); + PropertyDefinition propertyDefinition2 = testMaterialsProducerPropertyId.getPropertyDefinition(); + builder.defineMaterialsProducerProperty(testMaterialsProducerPropertyId, propertyDefinition) + .defineMaterialsProducerProperty(testMaterialsProducerPropertyId, propertyDefinition2); + materialsInitialData = builder.build(); + assertTrue(materialsInitialData.getMaterialsProducerPropertyIds().contains(testMaterialsProducerPropertyId)); + PropertyDefinition actualPropertyDefinition2 = materialsInitialData + .getMaterialsProducerPropertyDefinition(testMaterialsProducerPropertyId); + assertEquals(propertyDefinition2, actualPropertyDefinition2); + + // precondition test: if the materials producer property id is null + ContractException contractException = assertThrows(ContractException.class, + () -> MaterialsPluginData.builder().defineMaterialsProducerProperty(null, propertyDefinition)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // precondition test: if the property definition is null + contractException = assertThrows(ContractException.class, () -> MaterialsPluginData.builder() + .defineMaterialsProducerProperty(testMaterialsProducerPropertyId, null)); + assertEquals(PropertyError.NULL_PROPERTY_DEFINITION, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.Builder.class, name = "setBatchPropertyValue", args = { BatchId.class, + BatchPropertyId.class, Object.class }) + public void testSetBatchPropertyValue() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8767111011954878165L); + + /* + * Add 30 batches with about half of the batch properties being set to + * randomized values and the other half set to the default for the property + * definition + */ + MaterialsPluginData.Builder builder = MaterialsPluginData.builder();// + + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + builder.addMaterial(testMaterialId); + } + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.values()) { + builder.defineBatchProperty(testBatchPropertyId.getTestMaterialId(), testBatchPropertyId, + testBatchPropertyId.getPropertyDefinition()); + } + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + builder.addMaterialsProducerId(testMaterialsProducerId); + } + + // create a container to hold expected batch property values + Map expectedBatchPropertyValues = new LinkedHashMap<>(); + + // add the batches + for (int i = 0; i < 30; i++) { + BatchId batchId = new BatchId(i); + TestMaterialId testMaterialId = TestMaterialId.getRandomMaterialId(randomGenerator); + builder.addBatch(batchId, testMaterialId, randomGenerator.nextDouble()); + builder.addBatchToMaterialsProducerInventory(batchId, + TestMaterialsProducerId.getRandomMaterialsProducerId(randomGenerator)); + + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId + .getTestBatchPropertyIds(testMaterialId)) { + MultiKey multiKey = new MultiKey(batchId, testBatchPropertyId); + boolean required = testBatchPropertyId.getPropertyDefinition().getDefaultValue().isEmpty(); + if (required || randomGenerator.nextBoolean()) { + Object propertyValue = testBatchPropertyId.getRandomPropertyValue(randomGenerator); + // adding duplicate data to show that the value persists + builder.setBatchPropertyValue(batchId, testBatchPropertyId, propertyValue)// + .setBatchPropertyValue(batchId, testBatchPropertyId, propertyValue); + expectedBatchPropertyValues.put(multiKey, propertyValue); + } + + } + } + + // build the MaterialsInitialization + MaterialsPluginData materialsInitialData = builder.build();// + + // show that the MaterialsInitialization returns the expected batch + // property values + for (MultiKey multiKey : expectedBatchPropertyValues.keySet()) { + BatchId batchid = multiKey.getKey(0); + BatchPropertyId batchPropertyId = multiKey.getKey(1); + Object expectedValue = expectedBatchPropertyValues.get(multiKey); + Map batchPropertyValues = materialsInitialData.getBatchPropertyValues(batchid); + Object actualValue = batchPropertyValues.get(batchPropertyId); + assertEquals(expectedValue, actualValue); + } + + // idempotency test (replacement) + builder = MaterialsPluginData.builder(); + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + builder.addMaterial(testMaterialId); + } + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.values()) { + builder.defineBatchProperty(testBatchPropertyId.getTestMaterialId(), testBatchPropertyId, + testBatchPropertyId.getPropertyDefinition()); + } + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + builder.addMaterialsProducerId(testMaterialsProducerId); + } + + // reset container to hold expected batch property values + expectedBatchPropertyValues = new LinkedHashMap<>(); + + // add the batches + for (int i = 0; i < 30; i++) { + BatchId batchId = new BatchId(i); + TestMaterialId testMaterialId = TestMaterialId.getRandomMaterialId(randomGenerator); + builder.addBatch(batchId, testMaterialId, randomGenerator.nextDouble()); + builder.addBatchToMaterialsProducerInventory(batchId, + TestMaterialsProducerId.getRandomMaterialsProducerId(randomGenerator)); + + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId + .getTestBatchPropertyIds(testMaterialId)) { + MultiKey multiKey = new MultiKey(batchId, testBatchPropertyId); + boolean required = testBatchPropertyId.getPropertyDefinition().getDefaultValue().isEmpty(); + if (required || randomGenerator.nextBoolean()) { + Object propertyValue = testBatchPropertyId.getRandomPropertyValue(randomGenerator); + // replaced data to show that the value persists + builder.setBatchPropertyValue(batchId, testBatchPropertyId, propertyValue); + propertyValue = testBatchPropertyId.getRandomPropertyValue(randomGenerator); + builder.setBatchPropertyValue(batchId, testBatchPropertyId, propertyValue); + expectedBatchPropertyValues.put(multiKey, propertyValue); + } + + } + } + + // build the MaterialsInitialization + materialsInitialData = builder.build();// + + // show that the MaterialsInitialization returns the expected batch + // property values + for (MultiKey multiKey : expectedBatchPropertyValues.keySet()) { + BatchId batchid = multiKey.getKey(0); + BatchPropertyId batchPropertyId = multiKey.getKey(1); + Object expectedValue = expectedBatchPropertyValues.get(multiKey); + Map batchPropertyValues = materialsInitialData.getBatchPropertyValues(batchid); + Object actualValue = batchPropertyValues.get(batchPropertyId); + assertEquals(expectedValue, actualValue); + } + + // precondition tests + + BatchId batchId = new BatchId(0); + TestBatchPropertyId testBatchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK; + Object propertyValue = 17; + + // if the batch id is null + ContractException contractException = assertThrows(ContractException.class, + () -> MaterialsPluginData.builder().setBatchPropertyValue(null, testBatchPropertyId, propertyValue)); + assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); + + // if the batch property id is null + contractException = assertThrows(ContractException.class, + () -> MaterialsPluginData.builder().setBatchPropertyValue(batchId, null, propertyValue)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // if the batch property value is null + contractException = assertThrows(ContractException.class, + () -> MaterialsPluginData.builder().setBatchPropertyValue(batchId, testBatchPropertyId, null)); + assertEquals(PropertyError.NULL_PROPERTY_VALUE, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.Builder.class, name = "setMaterialsProducerPropertyValue", args = { + MaterialsProducerId.class, MaterialsProducerPropertyId.class, Object.class }) + public void testSetMaterialsProducerPropertyValue() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5680332692938057510L); + MaterialsPluginData.Builder builder = MaterialsPluginData.builder();// + + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .values()) { + builder.defineMaterialsProducerProperty(testMaterialsProducerPropertyId, + testMaterialsProducerPropertyId.getPropertyDefinition()); + } + + Map expectedPropertyValues = new LinkedHashMap<>(); + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + builder.addMaterialsProducerId(testMaterialsProducerId); + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .values()) { + boolean required = testMaterialsProducerPropertyId.getPropertyDefinition().getDefaultValue().isEmpty(); + MultiKey multiKey = new MultiKey(testMaterialsProducerId, testMaterialsProducerPropertyId); + if (required || randomGenerator.nextBoolean()) { + Object propertyValue = testMaterialsProducerPropertyId.getRandomPropertyValue(randomGenerator); + // adding duplicate data to show that the value persists + builder.setMaterialsProducerPropertyValue(testMaterialsProducerId, testMaterialsProducerPropertyId, + propertyValue); + builder.setMaterialsProducerPropertyValue(testMaterialsProducerId, testMaterialsProducerPropertyId, + propertyValue); + expectedPropertyValues.put(multiKey, propertyValue); + } + + } + } + + MaterialsPluginData materialsInitialData = builder.build();// + + for (MultiKey multiKey : expectedPropertyValues.keySet()) { + Object expectedValue = expectedPropertyValues.get(multiKey); + TestMaterialsProducerId testMaterialsProducerId = multiKey.getKey(0); + TestMaterialsProducerPropertyId testMaterialsProducerPropertyId = multiKey.getKey(1); + Map materialsProducerPropertyValues = materialsInitialData + .getMaterialsProducerPropertyValues(testMaterialsProducerId); + Object actualValue = materialsProducerPropertyValues.get(testMaterialsProducerPropertyId); + assertEquals(expectedValue, actualValue); + } + + // idempotency test(replacement) + builder = MaterialsPluginData.builder(); + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .values()) { + builder.defineMaterialsProducerProperty(testMaterialsProducerPropertyId, + testMaterialsProducerPropertyId.getPropertyDefinition()); + } + + expectedPropertyValues = new LinkedHashMap<>(); + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + builder.addMaterialsProducerId(testMaterialsProducerId); + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .values()) { + boolean required = testMaterialsProducerPropertyId.getPropertyDefinition().getDefaultValue().isEmpty(); + MultiKey multiKey = new MultiKey(testMaterialsProducerId, testMaterialsProducerPropertyId); + if (required || randomGenerator.nextBoolean()) { + Object propertyValue = testMaterialsProducerPropertyId.getRandomPropertyValue(randomGenerator); + // replacing data to show that the value persists + builder.setMaterialsProducerPropertyValue(testMaterialsProducerId, testMaterialsProducerPropertyId, + propertyValue); + propertyValue = testMaterialsProducerPropertyId.getRandomPropertyValue(randomGenerator); + builder.setMaterialsProducerPropertyValue(testMaterialsProducerId, testMaterialsProducerPropertyId, + propertyValue); + expectedPropertyValues.put(multiKey, propertyValue); + } + + } + } + + materialsInitialData = builder.build();// + + for (MultiKey multiKey : expectedPropertyValues.keySet()) { + Object expectedValue = expectedPropertyValues.get(multiKey); + TestMaterialsProducerId testMaterialsProducerId = multiKey.getKey(0); + TestMaterialsProducerPropertyId testMaterialsProducerPropertyId = multiKey.getKey(1); + Map materialsProducerPropertyValues = materialsInitialData + .getMaterialsProducerPropertyValues(testMaterialsProducerId); + Object actualValue = materialsProducerPropertyValues.get(testMaterialsProducerPropertyId); + assertEquals(expectedValue, actualValue); + } + + // precondition tests + + TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; + TestMaterialsProducerPropertyId testMaterialsProducerPropertyId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK; + Object propertyValue = 45.6; + + // if the materials producer id is null + ContractException contractException = assertThrows(ContractException.class, () -> MaterialsPluginData.builder() + .setMaterialsProducerPropertyValue(null, testMaterialsProducerPropertyId, propertyValue)); + assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + // if the materials producer property id is null + contractException = assertThrows(ContractException.class, () -> MaterialsPluginData.builder() + .setMaterialsProducerPropertyValue(testMaterialsProducerId, null, propertyValue)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // if the materials producer property value is null + contractException = assertThrows(ContractException.class, () -> MaterialsPluginData.builder() + .setMaterialsProducerPropertyValue(testMaterialsProducerId, testMaterialsProducerPropertyId, null)); + assertEquals(PropertyError.NULL_PROPERTY_VALUE, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.Builder.class, name = "setMaterialsProducerResourceLevel", args = { + MaterialsProducerId.class, ResourceId.class, long.class }) + public void testSetMaterialsProducerResourceLevel() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3277582868385203332L); + + Map expectedResourceLevels = new LinkedHashMap<>(); + + MaterialsPluginData.Builder builder = MaterialsPluginData.builder(); + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + builder.addMaterialsProducerId(testMaterialsProducerId); + for (TestResourceId testResourceId : TestResourceId.values()) { + MultiKey multiKey = new MultiKey(testMaterialsProducerId, testResourceId); + long level = 0; + if (randomGenerator.nextBoolean()) { + level = randomGenerator.nextInt(100); + // adding duplicate data to show that the value persists + builder// + .setMaterialsProducerResourceLevel(testMaterialsProducerId, testResourceId, level)// + .setMaterialsProducerResourceLevel(testMaterialsProducerId, testResourceId, level); + } + expectedResourceLevels.put(multiKey, level); + } + } + + MaterialsPluginData materialsInitialData = builder.build(); + + for (MultiKey multiKey : expectedResourceLevels.keySet()) { + long expectedValue = expectedResourceLevels.get(multiKey); + TestMaterialsProducerId testMaterialsProducerId = multiKey.getKey(0); + TestResourceId testResourceId = multiKey.getKey(1); + Long actualValue = materialsInitialData.getMaterialsProducerResourceLevel(testMaterialsProducerId, + testResourceId); + assertEquals(expectedValue, actualValue); + } + + // idempotency test (replacement) + + expectedResourceLevels = new LinkedHashMap<>(); + builder = MaterialsPluginData.builder(); + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + builder.addMaterialsProducerId(testMaterialsProducerId); + for (TestResourceId testResourceId : TestResourceId.values()) { + MultiKey multiKey = new MultiKey(testMaterialsProducerId, testResourceId); + long level = 0; + if (randomGenerator.nextBoolean()) { + level = randomGenerator.nextInt(100); + // replacing data to show that the value persists + builder.setMaterialsProducerResourceLevel(testMaterialsProducerId, testResourceId, level); + level = 1; + builder.setMaterialsProducerResourceLevel(testMaterialsProducerId, testResourceId, level); + } + expectedResourceLevels.put(multiKey, level); + } + } + + materialsInitialData = builder.build(); + + for (MultiKey multiKey : expectedResourceLevels.keySet()) { + long expectedValue = expectedResourceLevels.get(multiKey); + TestMaterialsProducerId testMaterialsProducerId = multiKey.getKey(0); + TestResourceId testResourceId = multiKey.getKey(1); + Long actualValue = materialsInitialData.getMaterialsProducerResourceLevel(testMaterialsProducerId, + testResourceId); + assertEquals(expectedValue, actualValue); + } + + // precondition tests + + TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; + TestResourceId testResourceId = TestResourceId.RESOURCE_3; + long level = 345; + + // if the materials producer id is null + ContractException contractException = assertThrows(ContractException.class, + () -> MaterialsPluginData.builder().setMaterialsProducerResourceLevel(null, testResourceId, level)); + assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + // if the resource id is null + contractException = assertThrows(ContractException.class, () -> MaterialsPluginData.builder() + .setMaterialsProducerResourceLevel(testMaterialsProducerId, null, level)); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + // if the resource amount is negative + contractException = assertThrows(ContractException.class, () -> MaterialsPluginData.builder() + .setMaterialsProducerResourceLevel(testMaterialsProducerId, testResourceId, -1)); + assertEquals(ResourceError.NEGATIVE_RESOURCE_AMOUNT, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.class, name = "getBatchAmounts", args = {}) + public void testGetBatchAmounts() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6746980823689022132L); + + Map expectedBatchAmounts = new LinkedHashMap<>(); + + MaterialsPluginData.Builder builder = MaterialsPluginData.builder(); + + for (int i = 0; i < 20; i++) { + BatchId batchId = new BatchId(i); + MaterialId materialId = TestMaterialId.getRandomMaterialId(randomGenerator); + double amount = randomGenerator.nextDouble(); + TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId + .getRandomMaterialsProducerId(randomGenerator); + builder.addBatch(batchId, materialId, amount); + builder.addBatchToMaterialsProducerInventory(batchId, testMaterialsProducerId); + expectedBatchAmounts.put(batchId, amount); + } + + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + builder.addMaterial(testMaterialId); + } + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + builder.addMaterialsProducerId(testMaterialsProducerId); + } + + MaterialsPluginData materialsPluginData = builder.build(); + + Map actualBatchAmounts = materialsPluginData.getBatchAmounts(); + + assertEquals(expectedBatchAmounts, actualBatchAmounts); + + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.class, name = "getBatchAmount", args = { BatchId.class }) + public void testGetBatchAmount() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6746980823689022132L); + + Map expectedBatchAmounts = new LinkedHashMap<>(); + + MaterialsPluginData.Builder builder = MaterialsPluginData.builder(); + + for (int i = 0; i < 20; i++) { + BatchId batchId = new BatchId(i); + MaterialId materialId = TestMaterialId.getRandomMaterialId(randomGenerator); + double amount = randomGenerator.nextDouble(); + TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId + .getRandomMaterialsProducerId(randomGenerator); + builder.addBatch(batchId, materialId, amount); + builder.addBatchToMaterialsProducerInventory(batchId, testMaterialsProducerId); + expectedBatchAmounts.put(batchId, amount); + } + + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + builder.addMaterial(testMaterialId); + } + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + builder.addMaterialsProducerId(testMaterialsProducerId); + } + + MaterialsPluginData materialsInitialData = builder.build(); + + for (BatchId batchId : expectedBatchAmounts.keySet()) { + Double expectedValue = expectedBatchAmounts.get(batchId); + Double actualAmount = materialsInitialData.getBatchAmount(batchId); + assertEquals(expectedValue, actualAmount); + } + + // precondition tests + + // if the batch id is null + ContractException contractException = assertThrows(ContractException.class, + () -> materialsInitialData.getBatchAmount(null)); + assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); + + // if the batch id is unknown + contractException = assertThrows(ContractException.class, + () -> materialsInitialData.getBatchAmount(new BatchId(10000000))); + assertEquals(MaterialsError.UNKNOWN_BATCH_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.class, name = "getBatchIds", args = {}) + public void testGetBatchIds() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1361793252807708004L); + + Set expectedBatchIds = new LinkedHashSet<>(); + + MaterialsPluginData.Builder builder = MaterialsPluginData.builder(); + + for (int i = 0; i < 20; i++) { + BatchId batchId = new BatchId(i); + MaterialId materialId = TestMaterialId.getRandomMaterialId(randomGenerator); + double amount = randomGenerator.nextDouble(); + TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId + .getRandomMaterialsProducerId(randomGenerator); + builder.addBatch(batchId, materialId, amount); + builder.addBatchToMaterialsProducerInventory(batchId, testMaterialsProducerId); + expectedBatchIds.add(batchId); + } + + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + builder.addMaterial(testMaterialId); + } + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + builder.addMaterialsProducerId(testMaterialsProducerId); + } + + MaterialsPluginData materialsInitialData = builder.build(); + assertEquals(expectedBatchIds, materialsInitialData.getBatchIds()); + + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.class, name = "getBatchMaterials", args = {}) + public void testGetBatchMaterials() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(116943580559448312L); + + MaterialsPluginData.Builder builder = MaterialsPluginData.builder(); + + Map expectedBatchMaterials = new LinkedHashMap<>(); + + for (int i = 0; i < 20; i++) { + BatchId batchId = new BatchId(i); + MaterialId materialId = TestMaterialId.getRandomMaterialId(randomGenerator); + double amount = randomGenerator.nextDouble(); + TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId + .getRandomMaterialsProducerId(randomGenerator); + builder.addBatch(batchId, materialId, amount); + builder.addBatchToMaterialsProducerInventory(batchId, testMaterialsProducerId); + expectedBatchMaterials.put(batchId, materialId); + } + + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + builder.addMaterial(testMaterialId); + } + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + builder.addMaterialsProducerId(testMaterialsProducerId); + } + + MaterialsPluginData materialsPluginData = builder.build(); + + Map actualBatchMaterials = materialsPluginData.getBatchMaterials(); + assertEquals(expectedBatchMaterials, actualBatchMaterials); + + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.class, name = "getBatchMaterial", args = { BatchId.class }) + public void testGetBatchMaterial() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(116943580559448312L); + + MaterialsPluginData.Builder builder = MaterialsPluginData.builder(); + + Map expectedMaterialIds = new LinkedHashMap<>(); + + for (int i = 0; i < 20; i++) { + BatchId batchId = new BatchId(i); + MaterialId materialId = TestMaterialId.getRandomMaterialId(randomGenerator); + double amount = randomGenerator.nextDouble(); + TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId + .getRandomMaterialsProducerId(randomGenerator); + builder.addBatch(batchId, materialId, amount); + builder.addBatchToMaterialsProducerInventory(batchId, testMaterialsProducerId); + expectedMaterialIds.put(batchId, materialId); + } + + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + builder.addMaterial(testMaterialId); + } + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + builder.addMaterialsProducerId(testMaterialsProducerId); + } + + MaterialsPluginData materialsInitialData = builder.build(); + + for (BatchId batchId : expectedMaterialIds.keySet()) { + MaterialId expectedMaterialId = expectedMaterialIds.get(batchId); + Object actualMaterialId = materialsInitialData.getBatchMaterial(batchId); + assertEquals(expectedMaterialId, actualMaterialId); + } + + // precondition tests + + // if the batch id is null + ContractException contractException = assertThrows(ContractException.class, + () -> materialsInitialData.getBatchMaterial(null)); + assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); + + // if the batch id is unknown + contractException = assertThrows(ContractException.class, + () -> materialsInitialData.getBatchMaterial(new BatchId(10000))); + assertEquals(MaterialsError.UNKNOWN_BATCH_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.class, name = "getMaterialsProducerInventoryBatches", args = {}) + public void testGetMaterialsProducerInventoryBatches() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4201153583410535220L); + + MaterialsPluginData.Builder builder = MaterialsPluginData.builder(); + + Map> expectedInventoryBatches = new LinkedHashMap<>(); + for (int i = 0; i < 20; i++) { + BatchId batchId = new BatchId(i); + MaterialId materialId = TestMaterialId.getRandomMaterialId(randomGenerator); + double amount = randomGenerator.nextDouble(); + TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId + .getRandomMaterialsProducerId(randomGenerator); + builder.addBatch(batchId, materialId, amount); + builder.addBatchToMaterialsProducerInventory(batchId, testMaterialsProducerId); + + Set set = expectedInventoryBatches.get(testMaterialsProducerId); + if (set == null) { + set = new LinkedHashSet<>(); + expectedInventoryBatches.put(testMaterialsProducerId, set); + } + set.add(batchId); + } + + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + builder.addMaterial(testMaterialId); + } + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + builder.addMaterialsProducerId(testMaterialsProducerId); + } + + MaterialsPluginData materialsInitialData = builder.build(); + + Map> actualInventoryBatches = materialsInitialData + .getMaterialsProducerInventoryBatches(); + assertEquals(expectedInventoryBatches, actualInventoryBatches); + + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.class, name = "getMaterialsProducerInventoryBatches", args = { + MaterialsProducerId.class }) + public void testGetMaterialsProducerInventoryBatches_ProducerId() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4201153583410535220L); + + MaterialsPluginData.Builder builder = MaterialsPluginData.builder(); + + Map> expectedBatchMap = new LinkedHashMap<>(); + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + expectedBatchMap.put(testMaterialsProducerId, new LinkedHashSet<>()); + } + + for (int i = 0; i < 20; i++) { + BatchId batchId = new BatchId(i); + MaterialId materialId = TestMaterialId.getRandomMaterialId(randomGenerator); + double amount = randomGenerator.nextDouble(); + TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId + .getRandomMaterialsProducerId(randomGenerator); + builder.addBatch(batchId, materialId, amount); + builder.addBatchToMaterialsProducerInventory(batchId, testMaterialsProducerId); + expectedBatchMap.get(testMaterialsProducerId).add(batchId); + } + + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + builder.addMaterial(testMaterialId); + } + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + builder.addMaterialsProducerId(testMaterialsProducerId); + } + + MaterialsPluginData materialsInitialData = builder.build(); + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + Set expectedBatches = expectedBatchMap.get(testMaterialsProducerId); + Set actualBatches = new LinkedHashSet<>( + materialsInitialData.getMaterialsProducerInventoryBatches(testMaterialsProducerId)); + assertEquals(expectedBatches, actualBatches); + } + + // precondition tests + + // if the materials producer id is null + ContractException contractException = assertThrows(ContractException.class, + () -> materialsInitialData.getMaterialsProducerInventoryBatches(null)); + assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.class, name = "getBatchPropertyDefinitions", args = {}) + public void testGetBatchPropertyDefinitions() { + + Map> expectedBatchPropertyDefinitions = new LinkedHashMap<>(); + + MaterialsPluginData.Builder builder = MaterialsPluginData.builder();// + + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + builder.addMaterial(testMaterialId); + } + + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.values()) { + MaterialId materialId = testBatchPropertyId.getTestMaterialId(); + PropertyDefinition propertyDefinition = testBatchPropertyId.getPropertyDefinition(); + builder.defineBatchProperty(materialId, testBatchPropertyId, propertyDefinition); + + Map map = expectedBatchPropertyDefinitions.get(materialId); + if (map == null) { + map = new LinkedHashMap<>(); + expectedBatchPropertyDefinitions.put(materialId, map); + } + map.put(testBatchPropertyId, propertyDefinition); + + } + + // build the MaterialsInitialization + MaterialsPluginData materialsPluginData = builder.build();// + + Map> actualBatchPropertyDefinitions = materialsPluginData + .getBatchPropertyDefinitions(); + assertEquals(expectedBatchPropertyDefinitions, actualBatchPropertyDefinitions); + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.class, name = "getBatchPropertyDefinition", args = { MaterialId.class, + BatchPropertyId.class }) + public void testGetBatchPropertyDefinition() { + + MaterialsPluginData.Builder builder = MaterialsPluginData.builder();// + + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + builder.addMaterial(testMaterialId); + } + + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.values()) { + builder.defineBatchProperty(testBatchPropertyId.getTestMaterialId(), testBatchPropertyId, + testBatchPropertyId.getPropertyDefinition()); + } + + // build the MaterialsInitialization + MaterialsPluginData materialsInitialData = builder.build();// + + // show that the MaterialsInitialization returns the expected batch + // property definitions + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.values()) { + PropertyDefinition expectedPropertyDefinition = testBatchPropertyId.getPropertyDefinition(); + TestMaterialId testMaterialId = testBatchPropertyId.getTestMaterialId(); + PropertyDefinition actualPropertyDefinition = materialsInitialData + .getBatchPropertyDefinition(testMaterialId, testBatchPropertyId); + assertEquals(expectedPropertyDefinition, actualPropertyDefinition); + } + + // precondition tests + + TestMaterialId testMaterialId = TestMaterialId.MATERIAL_2; + TestBatchPropertyId testBatchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK; + + // if the material id is null + ContractException contractException = assertThrows(ContractException.class, + () -> materialsInitialData.getBatchPropertyDefinition(null, testBatchPropertyId)); + assertEquals(MaterialsError.NULL_MATERIAL_ID, contractException.getErrorType()); + + // if the material id is unknown + contractException = assertThrows(ContractException.class, () -> materialsInitialData + .getBatchPropertyDefinition(TestMaterialId.getUnknownMaterialId(), testBatchPropertyId)); + assertEquals(MaterialsError.UNKNOWN_MATERIAL_ID, contractException.getErrorType()); + + // if the batch property id is null + contractException = assertThrows(ContractException.class, + () -> materialsInitialData.getBatchPropertyDefinition(testMaterialId, null)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // if the batch property id is unknown + contractException = assertThrows(ContractException.class, () -> materialsInitialData + .getBatchPropertyDefinition(testMaterialId, TestBatchPropertyId.getUnknownBatchPropertyId())); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.class, name = "getBatchPropertyIds", args = { MaterialId.class }) + public void testGetBatchPropertyIds() { + + MaterialsPluginData.Builder builder = MaterialsPluginData.builder();// + + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + builder.addMaterial(testMaterialId); + } + + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.values()) { + builder.defineBatchProperty(testBatchPropertyId.getTestMaterialId(), testBatchPropertyId, + testBatchPropertyId.getPropertyDefinition()); + } + + // build the MaterialsInitialization + MaterialsPluginData materialsInitialData = builder.build();// + + // show that the MaterialsInitialization returns the expected batch + // property ids + + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + Set expectedBatchPropertyIds = TestBatchPropertyId + .getTestBatchPropertyIds(testMaterialId); + Set actualBatchPropertyIds = materialsInitialData.getBatchPropertyIds(testMaterialId); + assertEquals(expectedBatchPropertyIds, actualBatchPropertyIds); + } + + // precondition tests + + // if the material id is null + ContractException contractException = assertThrows(ContractException.class, + () -> materialsInitialData.getBatchPropertyIds(null)); + assertEquals(MaterialsError.NULL_MATERIAL_ID, contractException.getErrorType()); + + // if the material id is unknown + contractException = assertThrows(ContractException.class, + () -> materialsInitialData.getBatchPropertyIds(TestMaterialId.getUnknownMaterialId())); + assertEquals(MaterialsError.UNKNOWN_MATERIAL_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.class, name = "getBatchPropertyValues", args = { BatchId.class }) + public void testGetBatchPropertyValues_batchId() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4884114879424388887L); + + /* + * Add 30 batches with about half of the batch properties being set to + * randomized values and the other half set to the default for the property + * definition + */ + MaterialsPluginData.Builder builder = MaterialsPluginData.builder();// + + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + builder.addMaterial(testMaterialId); + } + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.values()) { + builder.defineBatchProperty(testBatchPropertyId.getTestMaterialId(), testBatchPropertyId, + testBatchPropertyId.getPropertyDefinition()); + } + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + builder.addMaterialsProducerId(testMaterialsProducerId); + } + + // create a container to hold expected batch property values + Map expectedBatchPropertyValues = new LinkedHashMap<>(); + + // add the batches + for (int i = 0; i < 30; i++) { + BatchId batchId = new BatchId(i); + TestMaterialId testMaterialId = TestMaterialId.getRandomMaterialId(randomGenerator); + builder.addBatch(batchId, testMaterialId, randomGenerator.nextDouble()); + builder.addBatchToMaterialsProducerInventory(batchId, + TestMaterialsProducerId.getRandomMaterialsProducerId(randomGenerator)); + + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId + .getTestBatchPropertyIds(testMaterialId)) { + MultiKey multiKey = new MultiKey(batchId, testBatchPropertyId); + + boolean required = testBatchPropertyId.getPropertyDefinition().getDefaultValue().isEmpty(); + if (required || randomGenerator.nextBoolean()) { + Object propertyValue = testBatchPropertyId.getRandomPropertyValue(randomGenerator); + builder.setBatchPropertyValue(batchId, testBatchPropertyId, propertyValue); + expectedBatchPropertyValues.put(multiKey, propertyValue); + } + } + } + + // build the MaterialsInitialization + MaterialsPluginData materialsInitialData = builder.build();// + + // show that the MaterialsInitialization returns the expected batch + // property values + for (MultiKey multiKey : expectedBatchPropertyValues.keySet()) { + BatchId batchid = multiKey.getKey(0); + BatchPropertyId batchPropertyId = multiKey.getKey(1); + Object expectedValue = expectedBatchPropertyValues.get(multiKey); + Object actualValue = materialsInitialData.getBatchPropertyValues(batchid).get(batchPropertyId); + assertEquals(expectedValue, actualValue); + } + + // precondition test: if the batch id is null + ContractException contractException = assertThrows(ContractException.class, + () -> materialsInitialData.getBatchPropertyValues(null)); + assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); + + // precondition test: if the batch id is unknown + contractException = assertThrows(ContractException.class, + () -> materialsInitialData.getBatchPropertyValues(new BatchId(10000))); + assertEquals(MaterialsError.UNKNOWN_BATCH_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.class, name = "getMaterialIds", args = {}) + public void testGetMaterialIds() { + + MaterialsPluginData.Builder builder = MaterialsPluginData.builder(); + + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + builder.addMaterial(testMaterialId); + } + MaterialsPluginData materialsInitialData = builder.build();// + assertEquals(EnumSet.allOf(TestMaterialId.class), materialsInitialData.getMaterialIds()); + + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.class, name = "getMaterialsProducerIds", args = {}) + public void testGetMaterialsProducerIds() { + + MaterialsPluginData.Builder builder = MaterialsPluginData.builder();// + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + builder.addMaterialsProducerId(testMaterialsProducerId); + } + MaterialsPluginData materialsInitialData = builder.build();// + + // show that the materials producer ids were added + assertEquals(EnumSet.allOf(TestMaterialsProducerId.class), materialsInitialData.getMaterialsProducerIds()); + + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.class, name = "getMaterialsProducerPropertyDefinitions", args = {}) + public void testGetMaterialsProducerPropertyDefinitions() { + Map expectedPropertyDefinitions = new LinkedHashMap<>(); + MaterialsPluginData.Builder builder = MaterialsPluginData.builder();// + + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .values()) { + PropertyDefinition propertyDefinition = testMaterialsProducerPropertyId.getPropertyDefinition(); + builder.defineMaterialsProducerProperty(testMaterialsProducerPropertyId, propertyDefinition); + expectedPropertyDefinitions.put(testMaterialsProducerPropertyId, propertyDefinition); + } + MaterialsPluginData materialsPluginData = builder.build();// + + Map actualPropertyDefinitions = materialsPluginData + .getMaterialsProducerPropertyDefinitions(); + assertEquals(expectedPropertyDefinitions, actualPropertyDefinitions); + + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.class, name = "getMaterialsProducerPropertyDefinition", args = { + MaterialsProducerPropertyId.class }) + public void testGetMaterialsProducerPropertyDefinition() { + + MaterialsPluginData.Builder builder = MaterialsPluginData.builder();// + + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .values()) { + builder.defineMaterialsProducerProperty(testMaterialsProducerPropertyId, + testMaterialsProducerPropertyId.getPropertyDefinition()); + } + MaterialsPluginData materialsInitialData = builder.build();// + + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .values()) { + assertTrue( + materialsInitialData.getMaterialsProducerPropertyIds().contains(testMaterialsProducerPropertyId)); + PropertyDefinition actualPropertyDefinition = materialsInitialData + .getMaterialsProducerPropertyDefinition(testMaterialsProducerPropertyId); + PropertyDefinition expectedPropertyDefinition = testMaterialsProducerPropertyId.getPropertyDefinition(); + assertEquals(expectedPropertyDefinition, actualPropertyDefinition); + } + + // precondition tests + + // if the materials producer property id is null + ContractException contractException = assertThrows(ContractException.class, + () -> materialsInitialData.getMaterialsProducerPropertyDefinition(null)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // if the materials producer property id is unknown + contractException = assertThrows(ContractException.class, + () -> materialsInitialData.getMaterialsProducerPropertyDefinition( + TestMaterialsProducerPropertyId.getUnknownMaterialsProducerPropertyId())); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.class, name = "getMaterialsProducerPropertyIds", args = {}) + public void testGetMaterialsProducerPropertyIds() { + MaterialsPluginData.Builder builder = MaterialsPluginData.builder();// + + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .values()) { + builder.defineMaterialsProducerProperty(testMaterialsProducerPropertyId, + testMaterialsProducerPropertyId.getPropertyDefinition()); + } + MaterialsPluginData materialsInitialData = builder.build();// + assertEquals(EnumSet.allOf(TestMaterialsProducerPropertyId.class), + materialsInitialData.getMaterialsProducerPropertyIds()); + + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.class, name = "getMaterialsProducerPropertyValues", args = {}) + public void testGetMaterialsProducerPropertyValues() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(175219330466509056L); + MaterialsPluginData.Builder builder = MaterialsPluginData.builder();// + + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .values()) { + builder.defineMaterialsProducerProperty(testMaterialsProducerPropertyId, + testMaterialsProducerPropertyId.getPropertyDefinition()); + } + Map> expectedProducerPropertyValues = new LinkedHashMap<>(); + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + builder.addMaterialsProducerId(testMaterialsProducerId); + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .values()) { + Object propertyValue; + + boolean required = testMaterialsProducerPropertyId.getPropertyDefinition().getDefaultValue().isEmpty(); + + if (required || randomGenerator.nextBoolean()) { + propertyValue = testMaterialsProducerPropertyId.getRandomPropertyValue(randomGenerator); + builder.setMaterialsProducerPropertyValue(testMaterialsProducerId, testMaterialsProducerPropertyId, + propertyValue); + + Map map = expectedProducerPropertyValues + .get(testMaterialsProducerId); + if (map == null) { + map = new LinkedHashMap<>(); + expectedProducerPropertyValues.put(testMaterialsProducerId, map); + } + map.put(testMaterialsProducerPropertyId, propertyValue); + } + } + } + + MaterialsPluginData materialsPluginData = builder.build();// + + Map> actualProducerPropertyValues = materialsPluginData + .getMaterialsProducerPropertyValues(); + + assertEquals(expectedProducerPropertyValues, actualProducerPropertyValues); + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.class, name = "getMaterialsProducerPropertyValues", args = { + MaterialsProducerId.class }) + public void testGetMaterialsProducerPropertyValues_ProducerId() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(175219330466509056L); + MaterialsPluginData.Builder builder = MaterialsPluginData.builder();// + + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .values()) { + builder.defineMaterialsProducerProperty(testMaterialsProducerPropertyId, + testMaterialsProducerPropertyId.getPropertyDefinition()); + } + + Map expectedPropertyValues = new LinkedHashMap<>(); + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + builder.addMaterialsProducerId(testMaterialsProducerId); + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .values()) { + MultiKey multiKey = new MultiKey(testMaterialsProducerId, testMaterialsProducerPropertyId); + Object propertyValue; + + boolean required = testMaterialsProducerPropertyId.getPropertyDefinition().getDefaultValue().isEmpty(); + + if (required || randomGenerator.nextBoolean()) { + propertyValue = testMaterialsProducerPropertyId.getRandomPropertyValue(randomGenerator); + builder.setMaterialsProducerPropertyValue(testMaterialsProducerId, testMaterialsProducerPropertyId, + propertyValue); + expectedPropertyValues.put(multiKey, propertyValue); + } + } + } + + MaterialsPluginData materialsInitialData = builder.build();// + + for (MultiKey multiKey : expectedPropertyValues.keySet()) { + Object expectedValue = expectedPropertyValues.get(multiKey); + TestMaterialsProducerId testMaterialsProducerId = multiKey.getKey(0); + TestMaterialsProducerPropertyId testMaterialsProducerPropertyId = multiKey.getKey(1); + Map materialsProducerPropertyValues = materialsInitialData + .getMaterialsProducerPropertyValues(testMaterialsProducerId); + Object actualValue = materialsProducerPropertyValues.get(testMaterialsProducerPropertyId); + assertEquals(expectedValue, actualValue); + } + + // precondition test: if the materials producer id is null + ContractException contractException = assertThrows(ContractException.class, + () -> materialsInitialData.getMaterialsProducerPropertyValues(null)); + assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + // precondition test: if the materials producer id is unknown + contractException = assertThrows(ContractException.class, () -> materialsInitialData + .getMaterialsProducerPropertyValues(TestMaterialsProducerId.getUnknownMaterialsProducerId())); + assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.class, name = "getMaterialsProducerResourceLevels", args = {}) + public void testGetMaterialsProducerResourceLevels() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4448010834982849838L); + + Map> expectedProducerResourceLevels = new LinkedHashMap<>(); + + MaterialsPluginData.Builder builder = MaterialsPluginData.builder(); + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + builder.addMaterialsProducerId(testMaterialsProducerId); + for (TestResourceId testResourceId : TestResourceId.values()) { + + long level = 0; + if (randomGenerator.nextBoolean()) { + level = randomGenerator.nextInt(100); + builder.setMaterialsProducerResourceLevel(testMaterialsProducerId, testResourceId, level); + + Map map = expectedProducerResourceLevels.get(testMaterialsProducerId); + if (map == null) { + map = new LinkedHashMap<>(); + expectedProducerResourceLevels.put(testMaterialsProducerId, map); + } + map.put(testResourceId, level); + } + } + } + + MaterialsPluginData materialsPluginData = builder.build(); + + Map> actualProducerResourceLevels = materialsPluginData + .getMaterialsProducerResourceLevels(); + assertEquals(expectedProducerResourceLevels, actualProducerResourceLevels); + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.class, name = "getMaterialsProducerResourceLevel", args = { + MaterialsProducerId.class, ResourceId.class }) + public void testGetMaterialsProducerResourceLevel() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4448010834982849838L); + + Map expectedResourceLevels = new LinkedHashMap<>(); + + MaterialsPluginData.Builder builder = MaterialsPluginData.builder(); + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + builder.addMaterialsProducerId(testMaterialsProducerId); + for (TestResourceId testResourceId : TestResourceId.values()) { + MultiKey multiKey = new MultiKey(testMaterialsProducerId, testResourceId); + long level = 0; + if (randomGenerator.nextBoolean()) { + level = randomGenerator.nextInt(100); + builder.setMaterialsProducerResourceLevel(testMaterialsProducerId, testResourceId, level); + } + expectedResourceLevels.put(multiKey, level); + } + } + + MaterialsPluginData materialsInitialData = builder.build(); + + for (MultiKey multiKey : expectedResourceLevels.keySet()) { + long expectedValue = expectedResourceLevels.get(multiKey); + TestMaterialsProducerId testMaterialsProducerId = multiKey.getKey(0); + TestResourceId testResourceId = multiKey.getKey(1); + Long actualValue = materialsInitialData.getMaterialsProducerResourceLevel(testMaterialsProducerId, + testResourceId); + assertEquals(expectedValue, actualValue); + } + + // precondition tests + + TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; + TestResourceId testResourceId = TestResourceId.RESOURCE_3; + + // if the materials producer id is null + ContractException contractException = assertThrows(ContractException.class, + () -> materialsInitialData.getMaterialsProducerResourceLevel(null, testResourceId)); + assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + // if the materials producer id is unknown + contractException = assertThrows(ContractException.class, + () -> materialsInitialData.getMaterialsProducerResourceLevel( + TestMaterialsProducerId.getUnknownMaterialsProducerId(), testResourceId)); + assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + // if the resource id is unknown + contractException = assertThrows(ContractException.class, + () -> materialsInitialData.getMaterialsProducerResourceLevel(testMaterialsProducerId, null)); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.class, name = "getStageBatches", args = {}) + public void testGetStageBatches() { + + MaterialsPluginData.Builder builder = MaterialsPluginData.builder(); + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(273625089589349694L); + + // construct a container to hold the expected stage/batch relationships + + Map> expectedStageBatches = new LinkedHashMap<>(); + + // for each materials producer, add 50 batches, 10 stages and assign 0 + // to 3 batches per stage + int stageIndex = 0; + int batchIndex = 0; + + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + builder.addMaterial(testMaterialId); + } + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + + builder.addMaterialsProducerId(testMaterialsProducerId); + + List stagedBatchIds = new ArrayList<>(); + for (int i = 0; i < 30; i++) { + BatchId batchId = new BatchId(batchIndex++); + stagedBatchIds.add(batchId); + TestMaterialId testMaterialId = TestMaterialId.getRandomMaterialId(randomGenerator); + double amount = randomGenerator.nextDouble(); + builder.addBatch(batchId, testMaterialId, amount); + } + + for (int i = 0; i < 20; i++) { + BatchId batchId = new BatchId(batchIndex++); + TestMaterialId testMaterialId = TestMaterialId.getRandomMaterialId(randomGenerator); + double amount = randomGenerator.nextDouble(); + builder.addBatch(batchId, testMaterialId, amount); + builder.addBatchToMaterialsProducerInventory(batchId, testMaterialsProducerId); + } + + List stageIds = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + StageId stageId = new StageId(stageIndex++); + stageIds.add(stageId); + + boolean offered = randomGenerator.nextBoolean(); + builder.addStage(stageId, offered); + builder.addStageToMaterialProducer(stageId, testMaterialsProducerId); + } + + for (BatchId batchId : stagedBatchIds) { + StageId stageId = stageIds.get(randomGenerator.nextInt(stageIds.size())); + builder.addBatchToStage(stageId, batchId); + Set set = expectedStageBatches.get(stageId); + if (set == null) { + set = new LinkedHashSet<>(); + expectedStageBatches.put(stageId, set); + } + set.add(batchId); + + } + } + + MaterialsPluginData materialsInitialData = builder.build(); + + for (StageId stageId : expectedStageBatches.keySet()) { + Set expectedBatches = expectedStageBatches.get(stageId); + Set actualBatches = materialsInitialData.getStageBatches(stageId); + assertEquals(expectedBatches, actualBatches); + } + + Map> actualStageBatches = materialsInitialData.getStageBatches(); + assertEquals(expectedStageBatches, actualStageBatches); + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.class, name = "getStageBatches", args = { StageId.class }) + public void testGetStageBatches_StageId() { + + MaterialsPluginData.Builder builder = MaterialsPluginData.builder(); + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(273625089589349694L); + + // construct a container to hold the expected stage/batch relationships + + Map> expectedRelationships = new LinkedHashMap<>(); + + // for each materials producer, add 50 batches, 10 stages and assign 0 + // to 3 batches per stage + int stageIndex = 0; + int batchIndex = 0; + + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + builder.addMaterial(testMaterialId); + } + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + + builder.addMaterialsProducerId(testMaterialsProducerId); + + List stagedBatchIds = new ArrayList<>(); + for (int i = 0; i < 30; i++) { + BatchId batchId = new BatchId(batchIndex++); + stagedBatchIds.add(batchId); + TestMaterialId testMaterialId = TestMaterialId.getRandomMaterialId(randomGenerator); + double amount = randomGenerator.nextDouble(); + builder.addBatch(batchId, testMaterialId, amount); + } + + for (int i = 0; i < 20; i++) { + BatchId batchId = new BatchId(batchIndex++); + TestMaterialId testMaterialId = TestMaterialId.getRandomMaterialId(randomGenerator); + double amount = randomGenerator.nextDouble(); + builder.addBatch(batchId, testMaterialId, amount); + builder.addBatchToMaterialsProducerInventory(batchId, testMaterialsProducerId); + } + + List stageIds = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + StageId stageId = new StageId(stageIndex++); + stageIds.add(stageId); + expectedRelationships.put(stageId, new LinkedHashSet<>()); + boolean offered = randomGenerator.nextBoolean(); + builder.addStage(stageId, offered); + builder.addStageToMaterialProducer(stageId, testMaterialsProducerId); + } + + for (BatchId batchId : stagedBatchIds) { + StageId stageId = stageIds.get(randomGenerator.nextInt(stageIds.size())); + expectedRelationships.get(stageId).add(batchId); + builder.addBatchToStage(stageId, batchId); + } + } + + MaterialsPluginData materialsInitialData = builder.build(); + + for (StageId stageId : expectedRelationships.keySet()) { + Set expectedBatches = expectedRelationships.get(stageId); + Set actualBatches = materialsInitialData.getStageBatches(stageId); + assertEquals(expectedBatches, actualBatches); + } + + // precondition tests + + // if the stage id is null + ContractException contractException = assertThrows(ContractException.class, + () -> materialsInitialData.getStageBatches(null)); + assertEquals(MaterialsError.NULL_STAGE_ID, contractException.getErrorType()); + + // if the batch id is null + contractException = assertThrows(ContractException.class, + () -> materialsInitialData.getStageBatches(new StageId(10000000))); + assertEquals(MaterialsError.UNKNOWN_STAGE_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.class, name = "getStageIds", args = {}) + public void testGetStageIds() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3725911532254654669L); + + MaterialsPluginData.Builder builder = MaterialsPluginData.builder(); + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + builder.addMaterialsProducerId(testMaterialsProducerId); + } + Set expectedStageIds = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + StageId stageId = new StageId(i); + expectedStageIds.add(stageId); + boolean offered = randomGenerator.nextBoolean(); + TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId + .getRandomMaterialsProducerId(randomGenerator); + builder.addStage(stageId, offered); + builder.addStageToMaterialProducer(stageId, testMaterialsProducerId); + } + + MaterialsPluginData materialsInitialData = builder.build();// + + assertEquals(expectedStageIds, materialsInitialData.getStageIds()); + + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.class, name = "getMaterialsProducerStages", args = { + MaterialsProducerId.class }) + public void testGetMaterialsProducerStages_materialsProducerId() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4722411464538864709L); + + MaterialsPluginData.Builder builder = MaterialsPluginData.builder(); + + Map> expectedMap = new LinkedHashMap<>(); + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + builder.addMaterialsProducerId(testMaterialsProducerId); + expectedMap.put(testMaterialsProducerId, new ArrayList<>()); + } + + for (int i = 0; i < 100; i++) { + StageId stageId = new StageId(i); + boolean offered = randomGenerator.nextBoolean(); + TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId + .getRandomMaterialsProducerId(randomGenerator); + + builder.addStage(stageId, offered); + builder.addStageToMaterialProducer(stageId, testMaterialsProducerId); + + expectedMap.get(testMaterialsProducerId).add(stageId); + } + + MaterialsPluginData materialsInitialData = builder.build();// + + for (MaterialsProducerId materialsProducerId : expectedMap.keySet()) { + List expectedStages = expectedMap.get(materialsProducerId); + assertEquals(expectedStages, materialsInitialData.getMaterialsProducerStages(materialsProducerId)); + } + + // precondition tests + + // if the stage id is null + ContractException contractException = assertThrows(ContractException.class, + () -> materialsInitialData.getMaterialsProducerStages(null)); + assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + // if the stage id is unknown + contractException = assertThrows(ContractException.class, () -> materialsInitialData + .getMaterialsProducerStages(TestMaterialsProducerId.getUnknownMaterialsProducerId())); + assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.class, name = "getMaterialsProducerStages", args = {}) + public void testGetMaterialsProducerStages() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4722411464538864709L); + + MaterialsPluginData.Builder builder = MaterialsPluginData.builder(); + + Map> expectedMaterialsProducerStages = new LinkedHashMap<>(); + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + builder.addMaterialsProducerId(testMaterialsProducerId); + } + + for (int i = 0; i < 100; i++) { + StageId stageId = new StageId(i); + boolean offered = randomGenerator.nextBoolean(); + TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId + .getRandomMaterialsProducerId(randomGenerator); + + builder.addStage(stageId, offered); + builder.addStageToMaterialProducer(stageId, testMaterialsProducerId); + + Set set = expectedMaterialsProducerStages.get(testMaterialsProducerId); + if (set == null) { + set = new LinkedHashSet<>(); + expectedMaterialsProducerStages.put(testMaterialsProducerId, set); + } + set.add(stageId); + + } + + MaterialsPluginData materialsPluginData = builder.build();// + + Map> acutalMaterialsProducerStages = materialsPluginData + .getMaterialsProducerStages(); + assertEquals(expectedMaterialsProducerStages, acutalMaterialsProducerStages); + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.class, name = "getStageOffers", args = {}) + public void testGetStageOffers() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1042601351499648378L); + + MaterialsPluginData.Builder builder = MaterialsPluginData.builder(); + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + builder.addMaterialsProducerId(testMaterialsProducerId); + } + Map expectedStageOffers = new LinkedHashMap<>(); + + for (int i = 0; i < 100; i++) { + StageId stageId = new StageId(i); + boolean offered = randomGenerator.nextBoolean(); + TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId + .getRandomMaterialsProducerId(randomGenerator); + expectedStageOffers.put(stageId, offered); + builder.addStage(stageId, offered); + builder.addStageToMaterialProducer(stageId, testMaterialsProducerId); + } + + MaterialsPluginData materialsPluginData = builder.build();// + + Map actualStageOffers = materialsPluginData.getStageOffers(); + + assertEquals(expectedStageOffers, actualStageOffers); + + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.class, name = "isStageOffered", args = { StageId.class }) + public void testIsStageOffered() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1042601351499648378L); + + MaterialsPluginData.Builder builder = MaterialsPluginData.builder(); + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + builder.addMaterialsProducerId(testMaterialsProducerId); + } + + Map expectedStageOffers = new LinkedHashMap<>(); + for (int i = 0; i < 100; i++) { + StageId stageId = new StageId(i); + boolean offered = randomGenerator.nextBoolean(); + TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId + .getRandomMaterialsProducerId(randomGenerator); + expectedStageOffers.put(stageId, offered); + builder.addStage(stageId, offered); + builder.addStageToMaterialProducer(stageId, testMaterialsProducerId); + } + + MaterialsPluginData materialsInitialData = builder.build();// + + for (StageId stageId : expectedStageOffers.keySet()) { + Boolean expectedOfferedState = expectedStageOffers.get(stageId); + Boolean actualOfferedState = materialsInitialData.isStageOffered(stageId); + assertEquals(expectedOfferedState, actualOfferedState); + } + + // precondition tests + + // if the stage id is null + ContractException contractException = assertThrows(ContractException.class, + () -> materialsInitialData.isStageOffered(null)); + assertEquals(MaterialsError.NULL_STAGE_ID, contractException.getErrorType()); + + // if the stage id is unknown + contractException = assertThrows(ContractException.class, + () -> materialsInitialData.isStageOffered(new StageId(10000000))); + assertEquals(MaterialsError.UNKNOWN_STAGE_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.class, name = "getResourceIds", args = {}) + public void testGetResourceIds() { + + MaterialsPluginData materialsInitialData = MaterialsPluginData.builder().build();// + + assertTrue(materialsInitialData.getResourceIds().isEmpty()); + + TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; + long amount = 45L; + + MaterialsPluginData.Builder builder = MaterialsPluginData.builder(); + for (TestResourceId testResourceId : TestResourceId.values()) { + builder.setMaterialsProducerResourceLevel(testMaterialsProducerId, testResourceId, amount++); + testMaterialsProducerId = testMaterialsProducerId.next(); + } + + for (TestMaterialsProducerId producerId : TestMaterialsProducerId.values()) { + builder.addMaterialsProducerId(producerId); + } + + materialsInitialData = builder.build(); + + assertEquals(EnumSet.allOf(TestResourceId.class), materialsInitialData.getResourceIds()); + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.class, name = "getCloneBuilder", args = {}) + public void testGetCloneBuilder() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1064212917574117854L); + + MaterialsPluginData.Builder builder = MaterialsPluginData.builder(); + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + builder.addMaterialsProducerId(testMaterialsProducerId); + } + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .values()) { + builder.defineMaterialsProducerProperty(testMaterialsProducerPropertyId, + testMaterialsProducerPropertyId.getPropertyDefinition()); + } + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .values()) { + boolean requiredProperty = testMaterialsProducerPropertyId.getPropertyDefinition().getDefaultValue() + .isEmpty(); + if (requiredProperty || randomGenerator.nextBoolean()) { + builder.setMaterialsProducerPropertyValue(testMaterialsProducerId, testMaterialsProducerPropertyId, + testMaterialsProducerPropertyId.getRandomPropertyValue(randomGenerator)); + } + } + } + Map> stageMap = new LinkedHashMap<>(); + for (int i = 0; i < 30; i++) { + boolean offered = i % 2 == 0; + StageId stageId = new StageId(i); + TestMaterialsProducerId materialsProducerId = TestMaterialsProducerId + .getRandomMaterialsProducerId(randomGenerator); + builder.addStage(stageId, offered); + builder.addStageToMaterialProducer(stageId, materialsProducerId); + List list = stageMap.get(materialsProducerId); + if (list == null) { + list = new ArrayList<>(); + stageMap.put(materialsProducerId, list); + } + list.add(stageId); + } + + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.values()) { + builder.defineBatchProperty(testBatchPropertyId.getTestMaterialId(), testBatchPropertyId, + testBatchPropertyId.getPropertyDefinition()); + } + + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + builder.addMaterial(testMaterialId); + } + + for (int i = 0; i < 150; i++) { + BatchId batchId = new BatchId(i); + TestMaterialsProducerId materialsProducerId = TestMaterialsProducerId + .getRandomMaterialsProducerId(randomGenerator); + TestMaterialId randomMaterialId = TestMaterialId.getRandomMaterialId(randomGenerator); + builder.addBatch(batchId, randomMaterialId, randomGenerator.nextDouble()); + + boolean stageFound = false; + if (randomGenerator.nextBoolean()) { + List stages = stageMap.get(materialsProducerId); + if (!stages.isEmpty()) { + StageId stageId = stages.get(randomGenerator.nextInt(stages.size())); + builder.addBatchToStage(stageId, batchId); + stageFound = true; + } + } + if (!stageFound) { + builder.addBatchToMaterialsProducerInventory(batchId, materialsProducerId); + } + + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId + .getTestBatchPropertyIds(randomMaterialId)) { + boolean required = testBatchPropertyId.getPropertyDefinition().getDefaultValue().isEmpty(); + if (required || randomGenerator.nextBoolean()) { + builder.setBatchPropertyValue(batchId, testBatchPropertyId, + testBatchPropertyId.getRandomPropertyValue(randomGenerator)); + } + } + } + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + for (TestResourceId testResourceId : TestResourceId.values()) { + if (randomGenerator.nextBoolean()) { + int amount = randomGenerator.nextInt(10); + builder.setMaterialsProducerResourceLevel(testMaterialsProducerId, testResourceId, amount); + } + } + } + + MaterialsPluginData materialsPluginData = builder.build(); + // show the clone builder is not null + PluginDataBuilder cloneBuilder = materialsPluginData.getCloneBuilder(); + assertNotNull(cloneBuilder); + // show that the clone plugin data is not null + PluginData pluginData = cloneBuilder.build(); + assertNotNull(pluginData); + + // show that the clone plugin data has the correct type + assertTrue(pluginData instanceof MaterialsPluginData); + + MaterialsPluginData clonePluginData = (MaterialsPluginData) pluginData; + + assertEquals(materialsPluginData, clonePluginData); + + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.Builder.class, name = "setNextBatchRecordId", args = { int.class }) + public void testSetNextBatchRecordId() { + + for (int i = 0; i < 30; i++) { + MaterialsPluginData materialsPluginData = MaterialsPluginData.builder().setNextBatchRecordId(i).build(); + assertEquals(i, materialsPluginData.getNextBatchRecordId()); + } + + // precondition test: if the next batch record id is negative + ContractException contractException = assertThrows(ContractException.class, + () -> MaterialsPluginData.builder().setNextBatchRecordId(-1)); + assertEquals(MaterialsError.NEGATIVE_BATCH_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.class, name = "getNextBatchRecordId", args = {}) + public void testGetNextBatchRecordId() { + + assertEquals(0, MaterialsPluginData.builder().build().getNextBatchRecordId()); + + for (int i = 0; i < 30; i++) { + MaterialsPluginData materialsPluginData = MaterialsPluginData.builder().setNextBatchRecordId(i).build(); + assertEquals(i, materialsPluginData.getNextBatchRecordId()); + } + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.Builder.class, name = "setNextStageRecordId", args = { int.class }) + public void testSetNextStageRecordId() { + + for (int i = 0; i < 30; i++) { + MaterialsPluginData materialsPluginData = MaterialsPluginData.builder().setNextStageRecordId(i).build(); + assertEquals(i, materialsPluginData.getNextStageRecordId()); + } + + // precondition test: if the next batch record id is negative + ContractException contractException = assertThrows(ContractException.class, + () -> MaterialsPluginData.builder().setNextStageRecordId(-1)); + assertEquals(MaterialsError.NEGATIVE_STAGE_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.class, name = "getNextStageRecordId", args = {}) + public void testGetNextStageRecordId() { + + assertEquals(0, MaterialsPluginData.builder().build().getNextStageRecordId()); + + for (int i = 0; i < 30; i++) { + MaterialsPluginData materialsPluginData = MaterialsPluginData.builder().setNextStageRecordId(i).build(); + assertEquals(i, materialsPluginData.getNextStageRecordId()); + } + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.class, name = "getBatchPropertyValues", args = {}) + public void testGetBatchPropertyValues() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4884114879424388887L); + + /* + * Add 30 batches with about half of the batch properties being set to + * randomized values and the other half set to the default for the property + * definition + */ + MaterialsPluginData.Builder builder = MaterialsPluginData.builder();// + + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + builder.addMaterial(testMaterialId); + } + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.values()) { + builder.defineBatchProperty(testBatchPropertyId.getTestMaterialId(), testBatchPropertyId, + testBatchPropertyId.getPropertyDefinition()); + } + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + builder.addMaterialsProducerId(testMaterialsProducerId); + } + + // create a container to hold expected batch property values + Map> expectedBatchPropertyValues = new LinkedHashMap<>(); + + // add the batches + for (int i = 0; i < 30; i++) { + BatchId batchId = new BatchId(i); + Map propMap = new LinkedHashMap<>(); + + TestMaterialId testMaterialId = TestMaterialId.getRandomMaterialId(randomGenerator); + builder.addBatch(batchId, testMaterialId, randomGenerator.nextDouble()); + builder.addBatchToMaterialsProducerInventory(batchId, + TestMaterialsProducerId.getRandomMaterialsProducerId(randomGenerator)); + + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId + .getTestBatchPropertyIds(testMaterialId)) { + boolean required = testBatchPropertyId.getPropertyDefinition().getDefaultValue().isEmpty(); + if (required || randomGenerator.nextBoolean()) { + Object propertyValue = testBatchPropertyId.getRandomPropertyValue(randomGenerator); + builder.setBatchPropertyValue(batchId, testBatchPropertyId, propertyValue); + propMap.put(testBatchPropertyId, propertyValue); + } + } + if (!propMap.isEmpty()) { + expectedBatchPropertyValues.put(batchId, propMap); + } + } + + // build the MaterialsInitialization + MaterialsPluginData materialsPluginData = builder.build();// + + Map> actualBatchPropertyValues = materialsPluginData + .getBatchPropertyValues(); + + // show that the MaterialsInitialization returns the expected batch + // property values + assertEquals(expectedBatchPropertyValues, actualBatchPropertyValues); + } + + private MaterialsPluginData getRandomMaterialsPluginData(long seed) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + + MaterialsPluginData.Builder builder = MaterialsPluginData.builder();// + + List materialIds = new ArrayList<>(); + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + builder.addMaterial(testMaterialId); + materialIds.add(testMaterialId); + if (randomGenerator.nextBoolean()) { + break; + } + } + + List selectedProducerIds = new ArrayList<>(); + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + builder.addMaterialsProducerId(testMaterialsProducerId); + selectedProducerIds.add(testMaterialsProducerId); + if (randomGenerator.nextBoolean()) { + break; + } + } + List stageIds = new ArrayList<>(); + int stageCount = randomGenerator.nextInt(5) + 3; + for (int i = 0; i < stageCount; i++) { + StageId stageId = new StageId(i); + stageIds.add(stageId); + builder.addStage(stageId, randomGenerator.nextBoolean()); + TestMaterialsProducerId testMaterialsProducerId = selectedProducerIds + .get(randomGenerator.nextInt(selectedProducerIds.size())); + builder.addStageToMaterialProducer(stageId, testMaterialsProducerId); + } + + Map batchIds = new LinkedHashMap<>(); + int batchCount = randomGenerator.nextInt(15) + 5; + for (int i = 0; i < batchCount; i++) { + BatchId batchId = new BatchId(i); + MaterialId materialId = materialIds.get(randomGenerator.nextInt(materialIds.size())); + double amount = randomGenerator.nextDouble(); + builder.addBatch(batchId, materialId, amount); + batchIds.put(batchId, materialId); + if (randomGenerator.nextBoolean()) { + TestMaterialsProducerId testMaterialsProducerId = selectedProducerIds + .get(randomGenerator.nextInt(selectedProducerIds.size())); + builder.addBatchToMaterialsProducerInventory(batchId, testMaterialsProducerId); + } else { + StageId stageId = stageIds.get(randomGenerator.nextInt(stageIds.size())); + builder.addBatchToStage(stageId, batchId); + } + } + + builder.setNextBatchRecordId(batchCount + randomGenerator.nextInt(10)); + builder.setNextStageRecordId(stageCount + randomGenerator.nextInt(10)); + + List resourceIds = new ArrayList<>(); + for (TestResourceId testResourceId : TestResourceId.values()) { + resourceIds.add(testResourceId); + if (randomGenerator.nextBoolean()) { + break; + } + } + + for (TestMaterialsProducerId testMaterialsProducerId : selectedProducerIds) { + for (ResourceId resourceId : resourceIds) { + long amount = randomGenerator.nextInt(1000); + builder.setMaterialsProducerResourceLevel(testMaterialsProducerId, resourceId, amount); + } + } + List materialsProducerPropertyIds = new ArrayList<>(); + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .values()) { + PropertyDefinition propertyDefinition = testMaterialsProducerPropertyId.getPropertyDefinition(); + materialsProducerPropertyIds.add(testMaterialsProducerPropertyId); + builder.defineMaterialsProducerProperty(testMaterialsProducerPropertyId, propertyDefinition); + boolean valueRequired = propertyDefinition.getDefaultValue().isEmpty(); + for (TestMaterialsProducerId testMaterialsProducerId : selectedProducerIds) { + if (valueRequired || randomGenerator.nextBoolean()) { + Object propertyValue = testMaterialsProducerPropertyId.getRandomPropertyValue(randomGenerator); + builder.setMaterialsProducerPropertyValue(testMaterialsProducerId, testMaterialsProducerPropertyId, + propertyValue); + } + } + + if (randomGenerator.nextBoolean()) { + break; + } + } + + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.values()) { + MaterialId materialId = testBatchPropertyId.getTestMaterialId(); + if (!materialIds.contains(materialId)) { + continue; + } + PropertyDefinition propertyDefinition = testBatchPropertyId.getPropertyDefinition(); + builder.defineBatchProperty(materialId, testBatchPropertyId, propertyDefinition); + boolean valueRequired = propertyDefinition.getDefaultValue().isEmpty(); + + for (BatchId batchId : batchIds.keySet()) { + MaterialId mat = batchIds.get(batchId); + if (mat.equals(materialId)) { + if (valueRequired || randomGenerator.nextBoolean()) { + Object propertyValue = testBatchPropertyId.getRandomPropertyValue(randomGenerator); + builder.setBatchPropertyValue(batchId, testBatchPropertyId, propertyValue); + } + } + } + + if (randomGenerator.nextDouble() < 0.2) { + break; + } + } + + return builder.build(); + + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.class, name = "equals", args = { Object.class }) + public void testEquals() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(215284709954874816L); + + // is never equal to null + for (int i = 0; i < 10; i++) { + MaterialsPluginData randomMaterialsPluginData = getRandomMaterialsPluginData(randomGenerator.nextLong()); + assertFalse(randomMaterialsPluginData.equals(null)); + } + + // reflexive + for (int i = 0; i < 10; i++) { + MaterialsPluginData randomMaterialsPluginData = getRandomMaterialsPluginData(randomGenerator.nextLong()); + assertTrue(randomMaterialsPluginData.equals(randomMaterialsPluginData)); + } + + // symmetric, transitive , consistent + for (int i = 0; i < 10; i++) { + long seed = randomGenerator.nextLong(); + MaterialsPluginData randomMaterialsPluginData1 = getRandomMaterialsPluginData(seed); + MaterialsPluginData randomMaterialsPluginData2 = getRandomMaterialsPluginData(seed); + for (int j = 0; j < 3; j++) { + assertTrue(randomMaterialsPluginData1.equals(randomMaterialsPluginData2)); + assertTrue(randomMaterialsPluginData2.equals(randomMaterialsPluginData1)); + } + } + + // changes to inputs yield unequal objects -- high probability that they are not + // equal + for (int i = 0; i < 10; i++) { + MaterialsPluginData randomMaterialsPluginData1 = getRandomMaterialsPluginData(randomGenerator.nextLong()); + MaterialsPluginData randomMaterialsPluginData2 = getRandomMaterialsPluginData(randomGenerator.nextLong()); + assertNotEquals(randomMaterialsPluginData1, randomMaterialsPluginData2); + } + + } + + @Test + @UnitTestMethod(target = MaterialsPluginData.class, name = "hashCode", args = {}) + public void testHashCode() { + // equal objects have equal hash codes + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7949697829968462056L); + for (int i = 0; i < 10; i++) { + long seed = randomGenerator.nextLong(); + MaterialsPluginData randomMaterialsPluginData1 = getRandomMaterialsPluginData(seed); + MaterialsPluginData randomMaterialsPluginData2 = getRandomMaterialsPluginData(seed); + assertEquals(randomMaterialsPluginData1, randomMaterialsPluginData2); + assertEquals(randomMaterialsPluginData1.hashCode(), randomMaterialsPluginData2.hashCode()); + } + + // show that hash codes are reasonably distributed + Set hashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + MaterialsPluginData randomMaterialsPluginData = getRandomMaterialsPluginData(randomGenerator.nextLong()); + hashCodes.add(randomMaterialsPluginData.hashCode()); + } + assertTrue(hashCodes.size() > 95); + } + + + @Test + @UnitTestMethod(target = MaterialsPluginData.class, name = "toString", args = {}) + public void testToString() { + MaterialsPluginData randomMaterialsPluginData = getRandomMaterialsPluginData(8064459530862960720L); + + //The expected value was manually verified + String expectedValue = "MaterialsPluginData [data=Data [materialsProducerIds=[MATERIALS_PRODUCER_1], materialIds=[MATERIAL_1, MATERIAL_2], batchPropertyDefinitions={MATERIAL_1={BATCH_PROPERTY_1_1_BOOLEAN_IMMUTABLE_NO_TRACK=PropertyDefinition [type=class java.lang.Boolean, propertyValuesAreMutable=false, defaultValue=false], BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK=PropertyDefinition [type=class java.lang.Integer, propertyValuesAreMutable=true, defaultValue=null], BATCH_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK=PropertyDefinition [type=class java.lang.Double, propertyValuesAreMutable=true, defaultValue=null]}, MATERIAL_2={BATCH_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK=PropertyDefinition [type=class java.lang.Boolean, propertyValuesAreMutable=true, defaultValue=false], BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK=PropertyDefinition [type=class java.lang.Integer, propertyValuesAreMutable=false, defaultValue=0], BATCH_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK=PropertyDefinition [type=class java.lang.Double, propertyValuesAreMutable=true, defaultValue=0.0]}}, materialsProducerPropertyDefinitions={MATERIALS_PRODUCER_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK=PropertyDefinition [type=class java.lang.Boolean, propertyValuesAreMutable=true, defaultValue=false], MATERIALS_PRODUCER_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK=PropertyDefinition [type=class java.lang.Integer, propertyValuesAreMutable=true, defaultValue=null]}, materialsProducerPropertyValues={MATERIALS_PRODUCER_1={MATERIALS_PRODUCER_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK=true, MATERIALS_PRODUCER_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK=-1137930538}}, materialsProducerResourceLevels={MATERIALS_PRODUCER_1={RESOURCE_1=354}}, batchIds=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], batchMaterials={0=MATERIAL_2, 1=MATERIAL_1, 2=MATERIAL_1, 3=MATERIAL_1, 4=MATERIAL_2, 5=MATERIAL_2, 6=MATERIAL_1, 7=MATERIAL_1, 8=MATERIAL_2, 9=MATERIAL_1, 10=MATERIAL_2}, batchAmounts={0=0.37700109658565584, 1=0.6121304249912323, 2=0.2327292009900117, 3=0.5848445304496632, 4=0.3626526310416065, 5=0.8141764231338207, 6=0.7282094630729463, 7=0.2911578167148421, 8=0.3813260302130712, 9=0.038100504160549775, 10=0.294860719409465}, materialsProducerInventoryBatches={MATERIALS_PRODUCER_1=[0, 1, 3, 6, 7, 9, 10]}, batchPropertyValues={1={BATCH_PROPERTY_1_1_BOOLEAN_IMMUTABLE_NO_TRACK=true, BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK=-2092162941, BATCH_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK=0.580252301720191}, 3={BATCH_PROPERTY_1_1_BOOLEAN_IMMUTABLE_NO_TRACK=true, BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK=1273654431, BATCH_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK=0.8140217670183427}, 6={BATCH_PROPERTY_1_1_BOOLEAN_IMMUTABLE_NO_TRACK=true, BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK=1631575277, BATCH_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK=0.8263248505964975}, 9={BATCH_PROPERTY_1_1_BOOLEAN_IMMUTABLE_NO_TRACK=false, BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK=1716954844, BATCH_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK=0.9459982578081394}, 2={BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK=-1465030731, BATCH_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK=0.828428083842786}, 7={BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK=-894472152, BATCH_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK=0.7150821622079484}, 4={BATCH_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK=true, BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK=771315814}, 8={BATCH_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK=true, BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK=-2000257358, BATCH_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK=0.5783721359816127}, 10={BATCH_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK=true}}, stageIds=[0, 1, 2, 3, 4, 5], stageOffers={0=false, 1=true, 2=true, 3=false, 4=true, 5=true}, materialsProducerStages={MATERIALS_PRODUCER_1=[0, 1, 2, 3, 4, 5]}, stageBatches={5=[2], 4=[4], 3=[5], 2=[8]}, nextBatchRecordId=20, nextStageRecordId=14]]"; + + String actualValue = randomMaterialsPluginData.toString(); + + assertEquals(expectedValue, actualValue); + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_BatchAdditionEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_BatchAdditionEvent.java new file mode 100644 index 000000000..64ef500ff --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_BatchAdditionEvent.java @@ -0,0 +1,41 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.events; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_BatchAdditionEvent { + + @Test + @UnitTestConstructor(target = BatchAdditionEvent.class, args = { BatchId.class }) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = BatchAdditionEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = BatchAdditionEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = BatchAdditionEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = BatchAdditionEvent.class, name = "batchId", args = {}) + public void testBatchId() { + // nothing to test + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_BatchAmountUpdateEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_BatchAmountUpdateEvent.java new file mode 100644 index 000000000..f8e2b3ef9 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_BatchAmountUpdateEvent.java @@ -0,0 +1,52 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.events; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_BatchAmountUpdateEvent { + + @Test + @UnitTestConstructor(target = BatchAmountUpdateEvent.class, args = { BatchId.class, double.class, double.class }) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = BatchAmountUpdateEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = BatchAmountUpdateEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = BatchAmountUpdateEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = BatchAmountUpdateEvent.class, name = "batchId", args = {}) + public void testBatchId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = BatchAmountUpdateEvent.class, name = "previousAmount", args = {}) + public void testPreviousAmount() { + // nothing to test + } + + @Test + @UnitTestMethod(target = BatchAmountUpdateEvent.class, name = "currentAmount", args = {}) + public void testCurrentAmount() { + // nothing to test + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_BatchImminentRemovalEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_BatchImminentRemovalEvent.java new file mode 100644 index 000000000..05666d678 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_BatchImminentRemovalEvent.java @@ -0,0 +1,41 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.events; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_BatchImminentRemovalEvent { + + @Test + @UnitTestConstructor(target = BatchImminentRemovalEvent.class, args = { BatchId.class }) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = BatchImminentRemovalEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = BatchImminentRemovalEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = BatchImminentRemovalEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = BatchImminentRemovalEvent.class, name = "batchId", args = {}) + public void testBatchId() { + // nothing to test + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_BatchPropertyDefinitionEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_BatchPropertyDefinitionEvent.java new file mode 100644 index 000000000..3be66180f --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_BatchPropertyDefinitionEvent.java @@ -0,0 +1,64 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.events; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsError; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.TestBatchPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.TestMaterialId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + +public class AT_BatchPropertyDefinitionEvent { + @Test + @UnitTestConstructor(target = BatchPropertyDefinitionEvent.class, args = { MaterialId.class, BatchPropertyId.class }) + public void testConstructor() { + MaterialId materialId = TestMaterialId.MATERIAL_1; + BatchPropertyId batchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK; + + // precondition: null material id + ContractException contractException = assertThrows(ContractException.class, () -> new BatchPropertyDefinitionEvent(null, batchPropertyId)); + assertEquals(MaterialsError.NULL_MATERIAL_ID, contractException.getErrorType()); + + // precondition: null property id + contractException = assertThrows(ContractException.class, () -> new BatchPropertyDefinitionEvent(materialId, null)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = BatchPropertyDefinitionEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = BatchPropertyDefinitionEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = BatchPropertyDefinitionEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = BatchPropertyDefinitionEvent.class, name = "materialId", args = {}) + public void testMaterialId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = BatchPropertyDefinitionEvent.class, name = "batchPropertyId", args = {}) + public void testBatchPropertyId() { + // nothing to test + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_BatchPropertyUpdateEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_BatchPropertyUpdateEvent.java new file mode 100644 index 000000000..7b51fab5e --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_BatchPropertyUpdateEvent.java @@ -0,0 +1,91 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.events; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsError; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.TestBatchPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + +public class AT_BatchPropertyUpdateEvent { + + @Test + @UnitTestConstructor(target = BatchPropertyUpdateEvent.class, args = { BatchId.class, BatchPropertyId.class, Object.class, Object.class }) + public void testConstructor() { + // nothing to test + BatchId batchId = new BatchId(5348); + BatchPropertyId batchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK; + Object previousPropertyValue = 45; + Object currentPropertyValue = 643; + + // test case: null batch id + ContractException batchContractException = assertThrows(ContractException.class, () -> new BatchPropertyUpdateEvent(null, batchPropertyId, previousPropertyValue, currentPropertyValue)); + assertEquals(MaterialsError.NULL_BATCH_ID, batchContractException.getErrorType()); + + // test case: null batch property id + ContractException propContractException = assertThrows(ContractException.class, () -> new BatchPropertyUpdateEvent(batchId, null, previousPropertyValue, currentPropertyValue)); + assertEquals(PropertyError.NULL_PROPERTY_ID, propContractException.getErrorType()); + + // test case: null previous property value + ContractException prevContractException = assertThrows(ContractException.class, () -> new BatchPropertyUpdateEvent(batchId, batchPropertyId, null, currentPropertyValue)); + assertEquals(PropertyError.NULL_PROPERTY_VALUE, prevContractException.getErrorType()); + + // test case: null current property value + ContractException currContractException = assertThrows(ContractException.class, () -> new BatchPropertyUpdateEvent(batchId, batchPropertyId, previousPropertyValue, null)); + assertEquals(PropertyError.NULL_PROPERTY_VALUE, currContractException.getErrorType()); + + // test to assert that the builder's output is not null + assertNotNull(new BatchPropertyUpdateEvent(batchId, batchPropertyId, previousPropertyValue, currentPropertyValue)); + + } + + @Test + @UnitTestMethod(target = BatchPropertyUpdateEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = BatchPropertyUpdateEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = BatchPropertyUpdateEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = BatchPropertyUpdateEvent.class, name = "batchId", args = {}) + public void testBatchId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = BatchPropertyUpdateEvent.class, name = "batchPropertyId", args = {}) + public void testBatchPropertyId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = BatchPropertyUpdateEvent.class, name = "previousPropertyValue", args = {}) + public void testPreviousPropertyValue() { + // nothing to test + } + + @Test + @UnitTestMethod(target = BatchPropertyUpdateEvent.class, name = "currentPropertyValue", args = {}) + public void testCurrentPropertyValue() { + // nothing to test + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_MaterialIdAdditionEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_MaterialIdAdditionEvent.java new file mode 100644 index 000000000..7f43d9f69 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_MaterialIdAdditionEvent.java @@ -0,0 +1,49 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.events; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsError; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + +public class AT_MaterialIdAdditionEvent { + + @Test + @UnitTestConstructor(target = MaterialIdAdditionEvent.class, args = { MaterialId.class }) + public void testConstructor() { + + // precondition: null material id + ContractException contractException = assertThrows(ContractException.class, () -> new MaterialIdAdditionEvent(null)); + assertEquals(MaterialsError.NULL_MATERIAL_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = MaterialIdAdditionEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = MaterialIdAdditionEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = MaterialIdAdditionEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = MaterialIdAdditionEvent.class, name = "materialId", args = {}) + public void testMaterialId() { + // nothing to test + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_MaterialsProducerAdditionEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_MaterialsProducerAdditionEvent.java new file mode 100644 index 000000000..c2337338c --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_MaterialsProducerAdditionEvent.java @@ -0,0 +1,155 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.events; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsError; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerId; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.TestMaterialsProducerId; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_MaterialsProducerAdditionEvent { + + @Test + @UnitTestMethod(target = MaterialsProducerAdditionEvent.class, name = "builder", args = {}) + public void testBuilder() { + MaterialsProducerAdditionEvent.Builder builder = MaterialsProducerAdditionEvent.builder(); + assertNotNull(builder); + } + + @Test + @UnitTestMethod(target = MaterialsProducerAdditionEvent.class, name = "getMaterialsProducerId", args = {}) + public void testGetMaterialsProdcuerId() { + MaterialsProducerAdditionEvent.Builder builder = MaterialsProducerAdditionEvent.builder(); + MaterialsProducerId producerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; + + builder.setMaterialsProducerId(producerId); + + MaterialsProducerAdditionEvent event = builder.build(); + assertNotNull(event); + + assertEquals(producerId, event.getMaterialsProducerId()); + } + + @Test + @UnitTestMethod(target = MaterialsProducerAdditionEvent.class, name = "getValues", args = { Class.class }) + public void testGetValues() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2177130500877054468L); + MaterialsProducerAdditionEvent.Builder builder = MaterialsProducerAdditionEvent.builder(); + MaterialsProducerId producerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; + + builder.setMaterialsProducerId(producerId); + + List expectedIntegerValues = new ArrayList<>(); + List expectedStringValues = new ArrayList<>(); + + for (int i = 0; i < 15; i++) { + int integerVal = randomGenerator.nextInt(100); + String stringVal = Integer.toString(integerVal); + builder.addValue(integerVal); + expectedIntegerValues.add(integerVal); + builder.addValue(stringVal); + expectedStringValues.add(stringVal); + } + + MaterialsProducerAdditionEvent event = builder.build(); + assertNotNull(event); + + List actualIntegerValues = event.getValues(Integer.class); + List actualStringValues = event.getValues(String.class); + + assertEquals(expectedIntegerValues.size(), actualIntegerValues.size()); + assertEquals(expectedIntegerValues, actualIntegerValues); + + assertEquals(expectedStringValues.size(), actualStringValues.size()); + assertEquals(expectedStringValues, actualStringValues); + + assertEquals(0, event.getValues(Double.class).size()); + } + + @Test + @UnitTestMethod(target = MaterialsProducerAdditionEvent.Builder.class, name = "build", args = {}) + public void testBuild() { + MaterialsProducerAdditionEvent.Builder builder = MaterialsProducerAdditionEvent.builder(); + MaterialsProducerId producerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; + + builder.setMaterialsProducerId(producerId); + + MaterialsProducerAdditionEvent event = builder.build(); + assertNotNull(event); + + // precondition: null materials producer id + MaterialsProducerId nProducerId = null; + ContractException contractException = assertThrows(ContractException.class, () -> MaterialsProducerAdditionEvent.builder().setMaterialsProducerId(nProducerId).build()); + assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = MaterialsProducerAdditionEvent.Builder.class, name = "addValue", args = { Object.class }) + public void testAddValue() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5464369416326792932L); + MaterialsProducerAdditionEvent.Builder builder = MaterialsProducerAdditionEvent.builder(); + MaterialsProducerId producerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; + + builder.setMaterialsProducerId(producerId); + + List expectedIntegerValues = new ArrayList<>(); + List expectedStringValues = new ArrayList<>(); + + for (int i = 0; i < 15; i++) { + int integerVal = randomGenerator.nextInt(100); + String stringVal = Integer.toString(integerVal); + builder.addValue(integerVal); + expectedIntegerValues.add(integerVal); + builder.addValue(stringVal); + expectedStringValues.add(stringVal); + } + + MaterialsProducerAdditionEvent event = builder.build(); + assertNotNull(event); + + List actualIntegerValues = event.getValues(Integer.class); + List actualStringValues = event.getValues(String.class); + + assertEquals(expectedIntegerValues.size(), actualIntegerValues.size()); + assertEquals(expectedIntegerValues, actualIntegerValues); + + assertEquals(expectedStringValues.size(), actualStringValues.size()); + assertEquals(expectedStringValues, actualStringValues); + + assertEquals(0, event.getValues(Double.class).size()); + + // precondition: value is null + ContractException contractException = assertThrows(ContractException.class, () -> MaterialsProducerAdditionEvent.builder().addValue(null)); + assertEquals(MaterialsError.NULL_AUXILIARY_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = MaterialsProducerAdditionEvent.Builder.class, name = "setMaterialsProducerId", args = { MaterialsProducerId.class }) + public void testSetMaterialsProducerId() { + MaterialsProducerAdditionEvent.Builder builder = MaterialsProducerAdditionEvent.builder(); + MaterialsProducerId producerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; + + builder.setMaterialsProducerId(producerId); + + MaterialsProducerAdditionEvent event = builder.build(); + assertNotNull(event); + + assertEquals(producerId, event.getMaterialsProducerId()); + + // precondition: null materials producer id + ContractException contractException = assertThrows(ContractException.class, () -> MaterialsProducerAdditionEvent.builder().setMaterialsProducerId(null)); + assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + } + + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_MaterialsProducerPropertyDefinitionEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_MaterialsProducerPropertyDefinitionEvent.java new file mode 100644 index 000000000..67186aeae --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_MaterialsProducerPropertyDefinitionEvent.java @@ -0,0 +1,49 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.events; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + +public class AT_MaterialsProducerPropertyDefinitionEvent { + + @Test + @UnitTestConstructor(target = MaterialsProducerPropertyDefinitionEvent.class, args = { MaterialsProducerPropertyId.class }) + public void testConstructor() { + + // precondition: null producer property id + ContractException contractException = assertThrows(ContractException.class, () -> new MaterialsProducerPropertyDefinitionEvent(null)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = MaterialsProducerPropertyDefinitionEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = MaterialsProducerPropertyDefinitionEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = MaterialsProducerPropertyDefinitionEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = MaterialsProducerPropertyDefinitionEvent.class, name = "materialsProducerPropertyId", args = {}) + public void testMaterialsProducerPropertyId() { + // nothing to test + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_MaterialsProducerPropertyUpdateEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_MaterialsProducerPropertyUpdateEvent.java new file mode 100644 index 000000000..dc3992d31 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_MaterialsProducerPropertyUpdateEvent.java @@ -0,0 +1,60 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.events; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerPropertyId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_MaterialsProducerPropertyUpdateEvent { + + @Test + @UnitTestConstructor(target = MaterialsProducerPropertyUpdateEvent.class, args = { MaterialsProducerId.class, MaterialsProducerPropertyId.class, Object.class, Object.class }) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = MaterialsProducerPropertyUpdateEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = MaterialsProducerPropertyUpdateEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = MaterialsProducerPropertyUpdateEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = MaterialsProducerPropertyUpdateEvent.class, name = "materialsProducerId", args = {}) + public void testMaterialsProducerId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = MaterialsProducerPropertyUpdateEvent.class, name = "materialsProducerPropertyId", args = {}) + public void testMaterialsProducerPropertyId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = MaterialsProducerPropertyUpdateEvent.class, name = "previousPropertyValue", args = {}) + public void testPreviousPropertyValue() { + // nothing to test + } + + @Test + @UnitTestMethod(target = MaterialsProducerPropertyUpdateEvent.class, name = "currentPropertyValue", args = {}) + public void testCurrentPropertyValue() { + // nothing to test + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_MaterialsProducerResourceUpdateEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_MaterialsProducerResourceUpdateEvent.java new file mode 100644 index 000000000..7268ad080 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_MaterialsProducerResourceUpdateEvent.java @@ -0,0 +1,60 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.events; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerId; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_MaterialsProducerResourceUpdateEvent { + + @Test + @UnitTestConstructor(target = MaterialsProducerResourceUpdateEvent.class, args = { MaterialsProducerId.class, ResourceId.class, long.class, long.class }) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = MaterialsProducerResourceUpdateEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = MaterialsProducerResourceUpdateEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = MaterialsProducerResourceUpdateEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = MaterialsProducerResourceUpdateEvent.class, name = "materialsProducerId", args = {}) + public void testMaterialsProducerId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = MaterialsProducerResourceUpdateEvent.class, name = "resourceId", args = {}) + public void testResourceId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = MaterialsProducerResourceUpdateEvent.class, name = "previousResourceLevel", args = {}) + public void testPreviousResourceLevel() { + // nothing to test + } + + @Test + @UnitTestMethod(target = MaterialsProducerResourceUpdateEvent.class, name = "currentResourceLevel", args = {}) + public void testCurrentResourceLevel() { + // nothing to test + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_StageAdditionEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_StageAdditionEvent.java new file mode 100644 index 000000000..0350d8d7e --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_StageAdditionEvent.java @@ -0,0 +1,41 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.events; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.materials.support.StageId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_StageAdditionEvent { + + @Test + @UnitTestConstructor(target = StageAdditionEvent.class, args = { StageId.class }) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = StageAdditionEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = StageAdditionEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = StageAdditionEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = StageAdditionEvent.class, name = "stageId", args = {}) + public void testStageId() { + // nothing to test + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_StageImminentRemovalEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_StageImminentRemovalEvent.java new file mode 100644 index 000000000..1c5c48881 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_StageImminentRemovalEvent.java @@ -0,0 +1,40 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.events; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.materials.support.StageId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_StageImminentRemovalEvent { + + @Test + @UnitTestConstructor(target = StageImminentRemovalEvent.class, args = { StageId.class }) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = StageImminentRemovalEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = StageImminentRemovalEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = StageImminentRemovalEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = StageImminentRemovalEvent.class, name = "stageId", args = {}) + public void testStageId() { + // nothing to test + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_StageMaterialsProducerUpdateEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_StageMaterialsProducerUpdateEvent.java new file mode 100644 index 000000000..79fe23fb1 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_StageMaterialsProducerUpdateEvent.java @@ -0,0 +1,54 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.events; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.StageId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_StageMaterialsProducerUpdateEvent { + + @Test + @UnitTestConstructor(target = StageMaterialsProducerUpdateEvent.class, args = { StageId.class, MaterialsProducerId.class, MaterialsProducerId.class }) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = StageMaterialsProducerUpdateEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = StageMaterialsProducerUpdateEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = StageMaterialsProducerUpdateEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = StageMaterialsProducerUpdateEvent.class, name = "stageId", args = {}) + public void testStageId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = StageMaterialsProducerUpdateEvent.class, name = "previousMaterialsProducerId", args = {}) + public void testPreviousMaterialsProducerId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = StageMaterialsProducerUpdateEvent.class, name = "currentMaterialsProducerId", args = {}) + public void testCurrentMaterialsProducerId() { + // nothing to test + } + +} \ No newline at end of file diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_StageMembershipAdditionEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_StageMembershipAdditionEvent.java new file mode 100644 index 000000000..a67ba0425 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_StageMembershipAdditionEvent.java @@ -0,0 +1,48 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.events; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.StageId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_StageMembershipAdditionEvent { + + @Test + @UnitTestConstructor(target = StageMembershipAdditionEvent.class, args = { BatchId.class, StageId.class }) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = StageMembershipAdditionEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = StageMembershipAdditionEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = StageMembershipAdditionEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = StageMembershipAdditionEvent.class, name = "batchId", args = {}) + public void testBatchId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = StageMembershipAdditionEvent.class, name = "stageId", args = {}) + public void testStageId() { + // nothing to test + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_StageMembershipRemovalEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_StageMembershipRemovalEvent.java new file mode 100644 index 000000000..79c7e997a --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_StageMembershipRemovalEvent.java @@ -0,0 +1,48 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.events; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.StageId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_StageMembershipRemovalEvent { + + @Test + @UnitTestConstructor(target = StageMembershipRemovalEvent.class, args = { BatchId.class, StageId.class }) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = StageMembershipRemovalEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = StageMembershipRemovalEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = StageMembershipRemovalEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = StageMembershipRemovalEvent.class, name = "batchId", args = {}) + public void testBatchId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = StageMembershipRemovalEvent.class, name = "stageId", args = {}) + public void testStageId() { + // nothing to test + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_StageOfferUpdateEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_StageOfferUpdateEvent.java new file mode 100644 index 000000000..bcb653221 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/events/AT_StageOfferUpdateEvent.java @@ -0,0 +1,53 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.events; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.materials.support.StageId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_StageOfferUpdateEvent { + + @Test + @UnitTestConstructor(target = StageOfferUpdateEvent.class, args = { StageId.class, boolean.class, boolean.class }) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = StageOfferUpdateEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = StageOfferUpdateEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = StageOfferUpdateEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = StageOfferUpdateEvent.class, name = "stageId", args = {}) + public void testStageId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = StageOfferUpdateEvent.class, name = "previousOfferState", args = {}) + public void testPreviousOfferState() { + // nothing to test + } + + @Test + @UnitTestMethod(target = StageOfferUpdateEvent.class, name = "currentOfferState", args = {}) + public void testCurrentOfferState() { + // nothing to test + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/AT_BatchStatusReport.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/AT_BatchStatusReport.java new file mode 100644 index 000000000..0bc634499 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/AT_BatchStatusReport.java @@ -0,0 +1,443 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.reports; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Random; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestOutputConsumer; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.materials.datamangers.MaterialsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchConstructionInfo; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.StageId; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.MaterialsTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.MaterialsTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.TestBatchConstructionInfo; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.TestBatchPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.TestMaterialId; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.TestMaterialsProducerId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem.Builder; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import util.annotations.UnitTag; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public final class AT_BatchStatusReport { + + private ReportItem getReportItemFromBatch(ActorContext agentContext, BatchId batchId) { + MaterialsDataManager materialsDataManager = agentContext.getDataManager(MaterialsDataManager.class); + MaterialsProducerId batchProducer = materialsDataManager.getBatchProducer(batchId); + MaterialId batchMaterialId = materialsDataManager.getBatchMaterial(batchId); + double amount = materialsDataManager.getBatchAmount(batchId); + Optional optionalStageId = materialsDataManager.getBatchStageId(batchId); + String stageString = ""; + if (optionalStageId.isPresent()) { + stageString = optionalStageId.get().toString(); + } + + List elements = new ArrayList<>(); + + elements.add(agentContext.getTime()); + elements.add(batchId); + elements.add(batchProducer); + elements.add(stageString); + elements.add(batchMaterialId); + elements.add(amount); + + for (MaterialId materialId : materialsDataManager.getMaterialIds()) { + boolean matchingMaterial = batchMaterialId.equals(materialId); + Set batchPropertyIds = materialsDataManager.getBatchPropertyIds(materialId); + for (BatchPropertyId batchPropertyId : batchPropertyIds) { + if (matchingMaterial) { + elements.add(materialsDataManager.getBatchPropertyValue(batchId, batchPropertyId)); + } else { + elements.add(""); + } + } + } + + ReportItem reportItem = getReportItem(elements); + + return reportItem; + } + + @Test + @UnitTestConstructor(target = BatchStatusReport.class, args = { BatchStatusReportPluginData.class }) + public void testConstructor() { + BatchStatusReport report = new BatchStatusReport(BatchStatusReportPluginData.builder().setReportLabel(REPORT_LABEL).build()); + + assertNotNull(report); + } + + @Test + @UnitTestMethod(target = BatchStatusReport.class, name = "init", args = { ReportContext.class }, tags = {UnitTag.INCOMPLETE }) + public void testInit() { + + Map expectedReportItems = new LinkedHashMap<>(); + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + double actionTime = 0; + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + + // add a few batches + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + for (int i = 0; i < 20; i++) { + TestMaterialId materialId = TestMaterialId.getRandomMaterialId(randomGenerator); + double amount = randomGenerator.nextDouble(); + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo + .getBatchConstructionInfo(testMaterialsProducerId, materialId, amount, randomGenerator); + BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + expectedReportItems.put(getReportItemFromBatch(c, batchId), 1); + } + + })); + + // transfer material between batches + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + List batches = materialsDataManager + .getInventoryBatchesByMaterialId(testMaterialsProducerId, testMaterialId); + + if (batches.size() > 1) { + for (int i = 0; i < batches.size(); i++) { + int index1 = randomGenerator.nextInt(batches.size()); + int index2 = randomGenerator.nextInt(batches.size() - 1); + if (index2 >= index1) { + index2++; + } + BatchId batchId1 = batches.get(index1); + BatchId batchId2 = batches.get(index2); + double portion = randomGenerator.nextDouble(); + double amount = materialsDataManager.getBatchAmount(batchId1); + double transferAmount = amount *= portion; + materialsDataManager.transferMaterialBetweenBatches(batchId1, batchId2, transferAmount); + expectedReportItems.put(getReportItemFromBatch(c, batchId1), 1); + expectedReportItems.put(getReportItemFromBatch(c, batchId2), 1); + } + } + } + })); + + // destroy some batches + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + Random random = new Random(randomGenerator.nextLong()); + List inventoryBatches = materialsDataManager.getInventoryBatches(testMaterialsProducerId); + Collections.shuffle(inventoryBatches, random); + int destructionCount = inventoryBatches.size() / 5; + for (int i = 0; i < destructionCount; i++) { + BatchId batchId = inventoryBatches.get(i); + expectedReportItems.put(getReportItemFromBatch(c, batchId), 1); + materialsDataManager.removeBatch(batchId); + } + })); + + // set some batch property values + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + List inventoryBatches = materialsDataManager.getInventoryBatches(testMaterialsProducerId); + + for (BatchId batchId : inventoryBatches) { + TestMaterialId materialId = materialsDataManager.getBatchMaterial(batchId); + TestBatchPropertyId propertyId = TestBatchPropertyId.getRandomMutableBatchPropertyId(materialId, + randomGenerator); + Object value = propertyId.getRandomPropertyValue(randomGenerator); + materialsDataManager.setBatchPropertyValue(batchId, propertyId, value); + expectedReportItems.put(getReportItemFromBatch(c, batchId), 1); + } + })); + + // put some of the batches on stages + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + List stageIds = new ArrayList<>(); + for (int i = 0; i < 3; i++) { + StageId stageId = materialsDataManager.addStage(testMaterialsProducerId); + stageIds.add(stageId); + } + + List inventoryBatches = materialsDataManager.getInventoryBatches(testMaterialsProducerId); + for (BatchId batchId : inventoryBatches) { + if (randomGenerator.nextBoolean()) { + StageId stageId = stageIds.get(randomGenerator.nextInt(stageIds.size())); + materialsDataManager.moveBatchToStage(batchId, stageId); + expectedReportItems.put(getReportItemFromBatch(c, batchId), 1); + } + } + + })); + + // take some of the batches off of stages + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + List stageIds = materialsDataManager.getStages(testMaterialsProducerId); + + for (StageId stageId : stageIds) { + List batches = materialsDataManager.getStageBatches(stageId); + for (BatchId batchId : batches) { + if (randomGenerator.nextBoolean()) { + materialsDataManager.moveBatchToInventory(batchId); + expectedReportItems.put(getReportItemFromBatch(c, batchId), 1); + } + } + } + })); + + } + + TestPluginData testPluginData = pluginBuilder.build(); + + Factory factory = MaterialsTestPluginFactory// + .factory(0, 0, 0, 2819236410498978100L, testPluginData) + .setBatchStatusReportPluginData(BatchStatusReportPluginData.builder().setReportLabel(REPORT_LABEL).build()); + + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .build()// + .execute(); + + assertEquals(expectedReportItems, testOutputConsumer.getOutputItemMap(ReportItem.class)); + } + + @Test + @UnitTestMethod(target = BatchStatusReport.class, name = "init", args = { ReportContext.class }) + public void testStateFinalization() { + // Test with producing simulation + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + double actionTime = 0; + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + + // add a few batches + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + for (int i = 0; i < 20; i++) { + TestMaterialId materialId = TestMaterialId.getRandomMaterialId(randomGenerator); + double amount = randomGenerator.nextDouble(); + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo + .getBatchConstructionInfo(testMaterialsProducerId, materialId, amount, randomGenerator); + materialsDataManager.addBatch(batchConstructionInfo); + } + })); + + // transfer material between batches + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + List batches = materialsDataManager + .getInventoryBatchesByMaterialId(testMaterialsProducerId, testMaterialId); + + if (batches.size() > 1) { + for (int i = 0; i < batches.size(); i++) { + int index1 = randomGenerator.nextInt(batches.size()); + int index2 = randomGenerator.nextInt(batches.size() - 1); + if (index2 >= index1) { + index2++; + } + BatchId batchId1 = batches.get(index1); + BatchId batchId2 = batches.get(index2); + double portion = randomGenerator.nextDouble(); + double amount = materialsDataManager.getBatchAmount(batchId1); + double transferAmount = amount *= portion; + materialsDataManager.transferMaterialBetweenBatches(batchId1, batchId2, transferAmount); + } + } + } + })); + + // destroy some batches + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + Random random = new Random(randomGenerator.nextLong()); + List inventoryBatches = materialsDataManager.getInventoryBatches(testMaterialsProducerId); + Collections.shuffle(inventoryBatches, random); + int destructionCount = inventoryBatches.size() / 5; + for (int i = 0; i < destructionCount; i++) { + BatchId batchId = inventoryBatches.get(i); + materialsDataManager.removeBatch(batchId); + } + })); + + // set some batch property values + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + List inventoryBatches = materialsDataManager.getInventoryBatches(testMaterialsProducerId); + + for (BatchId batchId : inventoryBatches) { + TestMaterialId materialId = materialsDataManager.getBatchMaterial(batchId); + TestBatchPropertyId propertyId = TestBatchPropertyId.getRandomMutableBatchPropertyId(materialId, + randomGenerator); + Object value = propertyId.getRandomPropertyValue(randomGenerator); + materialsDataManager.setBatchPropertyValue(batchId, propertyId, value); + } + })); + + // put some of the batches on stages + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + List stageIds = new ArrayList<>(); + for (int i = 0; i < 3; i++) { + StageId stageId = materialsDataManager.addStage(testMaterialsProducerId); + stageIds.add(stageId); + } + + List inventoryBatches = materialsDataManager.getInventoryBatches(testMaterialsProducerId); + for (BatchId batchId : inventoryBatches) { + if (randomGenerator.nextBoolean()) { + StageId stageId = stageIds.get(randomGenerator.nextInt(stageIds.size())); + materialsDataManager.moveBatchToStage(batchId, stageId); + } + } + + })); + + // take some of the batches off of stages + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + List stageIds = materialsDataManager.getStages(testMaterialsProducerId); + + for (StageId stageId : stageIds) { + List batches = materialsDataManager.getStageBatches(stageId); + for (BatchId batchId : batches) { + if (randomGenerator.nextBoolean()) { + materialsDataManager.moveBatchToInventory(batchId); + } + } + } + })); + + } + + TestPluginData testPluginData = pluginBuilder.build(); + + BatchStatusReportPluginData batchStatusReportPluginData = BatchStatusReportPluginData.builder() + .setReportLabel(REPORT_LABEL) + .build(); + + Factory factory = MaterialsTestPluginFactory// + .factory(0, 0, 0, 2819236410498978100L, testPluginData) + .setBatchStatusReportPluginData(batchStatusReportPluginData); + + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .setProduceSimulationStateOnHalt(true)// + .setSimulationHaltTime(20)// + .build()// + .execute(); + + // show that the plugin data persists after simulation + Map outputItems = testOutputConsumer.getOutputItemMap(BatchStatusReportPluginData.class); + assertEquals(1, outputItems.size()); + BatchStatusReportPluginData batchStatusReportPluginData2 = outputItems.keySet().iterator().next(); + assertEquals(batchStatusReportPluginData, batchStatusReportPluginData2); + + // Test without producing simulation + testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .setProduceSimulationStateOnHalt(false)// + .setSimulationHaltTime(20)// + .build()// + .execute(); + + outputItems = testOutputConsumer.getOutputItemMap(BatchStatusReportPluginData.class); + assertEquals(0, outputItems.size()); + } + + private static ReportItem getReportItem(List values) { + Builder builder = ReportItem.builder().setReportLabel(REPORT_LABEL).setReportHeader(REPORT_HEADER); + for (Object value : values) { + builder.addValue(value); + } + return builder.build(); + } + + private static final ReportLabel REPORT_LABEL = new SimpleReportLabel("report"); + + private static final ReportHeader REPORT_HEADER = getReportHeader(); + + private static ReportHeader getReportHeader() { + + ReportHeader.Builder builder = ReportHeader.builder()// + .add("time")// + .add("batch")// + .add("materials_producer")// + .add("stage")// + .add("material")// + .add("amount");// + + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId + .getTestBatchPropertyIds(testMaterialId)) { + builder.add(testMaterialId + "." + testBatchPropertyId); + } + } + + return builder.build(); + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/AT_BatchStatusReportPluginData.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/AT_BatchStatusReportPluginData.java new file mode 100644 index 000000000..dd4c4f197 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/AT_BatchStatusReportPluginData.java @@ -0,0 +1,195 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.reports; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportError; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_BatchStatusReportPluginData { + + @Test + @UnitTestMethod(target = BatchStatusReportPluginData.class, name = "builder", args = {}) + public void testBuilder() { + BatchStatusReportPluginData.Builder builder = BatchStatusReportPluginData.builder(); + assertNotNull(builder); + } + + @Test + @UnitTestMethod(target = BatchStatusReportPluginData.Builder.class, name = "build", args = {}) + public void testBuild() { + // the specific capabilities are covered elsewhere + + // precondition test: if the report label is not assigned + ContractException contractException = assertThrows(ContractException.class, () -> // + BatchStatusReportPluginData.builder()// + .build()); + assertEquals(ReportError.NULL_REPORT_LABEL, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = BatchStatusReportPluginData.Builder.class, name = "setReportLabel", args = { + ReportLabel.class }) + public void testSetReportLabel() { + + for (int i = 0; i < 30; i++) { + ReportLabel expectedReportLabel = new SimpleReportLabel(i); + BatchStatusReportPluginData batchStatusReportPluginData = // + BatchStatusReportPluginData.builder()// + .setReportLabel(expectedReportLabel)// + .build(); + + assertEquals(expectedReportLabel, batchStatusReportPluginData.getReportLabel()); + } + + // precondition: if the report label is null + ContractException contractException = assertThrows(ContractException.class, () -> { + BatchStatusReportPluginData.builder().setReportLabel(null); + }); + assertEquals(ReportError.NULL_REPORT_LABEL, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = BatchStatusReportPluginData.class, name = "getReportLabel", args = {}) + public void testGetReportLabel() { + for (int i = 0; i < 30; i++) { + ReportLabel expectedReportLabel = new SimpleReportLabel(i); + BatchStatusReportPluginData batchStatusReportPluginData = // + BatchStatusReportPluginData.builder()// + .setReportLabel(expectedReportLabel)// + .build(); + + assertEquals(expectedReportLabel, batchStatusReportPluginData.getReportLabel()); + } + + } + + @Test + @UnitTestMethod(target = BatchStatusReportPluginData.class, name = "getCloneBuilder", args = {}) + public void testGetCloneBuilder() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8951274294108578550L); + for (int i = 0; i < 10; i++) { + + // build a BatchStatusReportPluginData from random inputs + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt()); + + BatchStatusReportPluginData.Builder builder = // + BatchStatusReportPluginData.builder()// + .setReportLabel(reportLabel); + + BatchStatusReportPluginData batchStatusReportPluginData = builder.build(); + + // create the clone builder and have it build + BatchStatusReportPluginData cloneBatchStatusReportPluginData = batchStatusReportPluginData.getCloneBuilder() + .build(); + + // the result should equal the original if the clone builder was + // initialized with the correct state + assertEquals(batchStatusReportPluginData, cloneBatchStatusReportPluginData); + + } + } + + @Test + @UnitTestMethod(target = BatchStatusReportPluginData.class, name = "equals", args = { Object.class }) + public void testEquals() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7759639255438669162L); + for (int i = 0; i < 10; i++) { + // build a BatchStatusReportPluginData from the same random + // inputs + BatchStatusReportPluginData.Builder builder1 = BatchStatusReportPluginData.builder(); + BatchStatusReportPluginData.Builder builder2 = BatchStatusReportPluginData.builder(); + + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt(100)); + builder1.setReportLabel(reportLabel); + builder2.setReportLabel(reportLabel); + + BatchStatusReportPluginData batchStatusReportPluginData1 = builder1.build(); + BatchStatusReportPluginData batchStatusReportPluginData2 = builder2.build(); + + assertEquals(batchStatusReportPluginData1, batchStatusReportPluginData2); + + // show that plugin datas with different inputs are not equal + + // change the report label + reportLabel = new SimpleReportLabel(1000); + batchStatusReportPluginData2 = // + batchStatusReportPluginData1.getCloneBuilder()// + .setReportLabel(reportLabel)// + .build(); + assertNotEquals(batchStatusReportPluginData2, batchStatusReportPluginData1); + } + + } + + @Test + @UnitTestMethod(target = BatchStatusReportPluginData.class, name = "hashCode", args = {}) + public void testHashCode() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(9079768427072825406L); + + Set observedHashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 50; i++) { + // build a BatchStatusReportPluginData from the same random + // inputs + BatchStatusReportPluginData.Builder builder1 = BatchStatusReportPluginData.builder(); + BatchStatusReportPluginData.Builder builder2 = BatchStatusReportPluginData.builder(); + + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt(100)); + builder1.setReportLabel(reportLabel); + builder2.setReportLabel(reportLabel); + + BatchStatusReportPluginData batchStatusReportPluginData1 = builder1.build(); + BatchStatusReportPluginData batchStatusReportPluginData2 = builder2.build(); + + // show that the hash code is stable + int hashCode = batchStatusReportPluginData1.hashCode(); + assertEquals(hashCode, batchStatusReportPluginData1.hashCode()); + assertEquals(hashCode, batchStatusReportPluginData1.hashCode()); + assertEquals(hashCode, batchStatusReportPluginData1.hashCode()); + assertEquals(hashCode, batchStatusReportPluginData1.hashCode()); + + // show that equal objects have equal hash codes + assertEquals(batchStatusReportPluginData1.hashCode(), batchStatusReportPluginData2.hashCode()); + + // collect the hashcode + observedHashCodes.add(batchStatusReportPluginData1.hashCode()); + } + + /* + * The hash codes should be dispersed -- we only show that they are unique + * values -- this is dependent on the random seed + */ + assertTrue(observedHashCodes.size() > 40); + + } + + @Test + @UnitTestMethod(target = BatchStatusReportPluginData.class, name = "toString", args = {}) + public void testToString() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + BatchStatusReportPluginData batchStatusReportPluginData = // + BatchStatusReportPluginData.builder()// + .setReportLabel(reportLabel)// + .build(); + String expectedValue = "BatchStatusReportPluginData [data=Data [reportLabel=SimpleReportLabel [value=report label], locked=true]]"; + String actualValue = batchStatusReportPluginData.toString(); + assertEquals(expectedValue, actualValue); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/AT_MaterialsProducerPropertyReport.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/AT_MaterialsProducerPropertyReport.java new file mode 100644 index 000000000..5a7bc65a1 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/AT_MaterialsProducerPropertyReport.java @@ -0,0 +1,258 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.reports; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestOutputConsumer; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.materials.datamangers.MaterialsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerConstructionData; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.MaterialsTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.MaterialsTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.TestMaterialsProducerId; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.TestMaterialsProducerPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem.Builder; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import util.annotations.UnitTag; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public final class AT_MaterialsProducerPropertyReport { + + private ReportItem getReportItemFromPropertyId(ActorContext agentContext, MaterialsProducerId materialsProducerId, + MaterialsProducerPropertyId materialsProducerPropertyId) { + + MaterialsDataManager materialsDataManager = agentContext.getDataManager(MaterialsDataManager.class); + Object propertyValue = materialsDataManager.getMaterialsProducerPropertyValue(materialsProducerId, + materialsProducerPropertyId); + + ReportItem reportItem = getReportItem(// + agentContext.getTime(), // + materialsProducerId, // + materialsProducerPropertyId, // + propertyValue // + );// + + return reportItem; + } + + @Test + @UnitTestConstructor(target = MaterialsProducerPropertyReport.class, args = { MaterialsProducerPropertyReportPluginData.class }) + public void testConstructor() { + MaterialsProducerPropertyReport report = new MaterialsProducerPropertyReport(MaterialsProducerPropertyReportPluginData.builder().setReportLabel(REPORT_LABEL).build()); + assertNotNull(report); + } + + @Test + @UnitTestMethod(target = MaterialsProducerPropertyReport.class, name = "init", args = {ReportContext.class }, tags = { UnitTag.INCOMPLETE }) + public void testInit() { + + /* + * The test fails -- only the day value is wrong. + */ + + Map expectedReportItems = new LinkedHashMap<>(); + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + double actionTime = 0; + MaterialsProducerId newMaterialsProducerId = TestMaterialsProducerId.getUnknownMaterialsProducerId(); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .values()) { + expectedReportItems.put( + getReportItemFromPropertyId(c, testMaterialsProducerId, testMaterialsProducerPropertyId), + 1); + } + } + })); + + // add a new materials producer at time 30 + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(30, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsProducerConstructionData.Builder builder = MaterialsProducerConstructionData.builder(); + + builder.setMaterialsProducerId(newMaterialsProducerId); + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .getPropertiesWithoutDefaultValues()) { + Object randomPropertyValue = testMaterialsProducerPropertyId.getRandomPropertyValue(randomGenerator); + builder.setMaterialsProducerPropertyValue(testMaterialsProducerPropertyId, randomPropertyValue); + } + + MaterialsProducerConstructionData materialsProducerConstructionData = builder.build(); + materialsDataManager.addMaterialsProducer(materialsProducerConstructionData); + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .values()) { + expectedReportItems.put( + getReportItemFromPropertyId(c, newMaterialsProducerId, testMaterialsProducerPropertyId), 1); + } + })); + + for (int i = 0; i < 5; i++) { + + // set a property value + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + List materialsProducerIds = new ArrayList<>( + materialsDataManager.getMaterialsProducerIds()); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsProducerId materialsProducerId = materialsProducerIds + .get(randomGenerator.nextInt(materialsProducerIds.size())); + TestMaterialsProducerPropertyId testMaterialsProducerPropertyId = TestMaterialsProducerPropertyId + .getRandomMutableMaterialsProducerPropertyId(randomGenerator); + Object propertyValue = testMaterialsProducerPropertyId.getRandomPropertyValue(randomGenerator); + materialsDataManager.setMaterialsProducerPropertyValue(materialsProducerId, + testMaterialsProducerPropertyId, propertyValue); + expectedReportItems + .put(getReportItemFromPropertyId(c, materialsProducerId, testMaterialsProducerPropertyId), 1); + + })); + + } + + TestPluginData testPluginData = pluginBuilder.build(); + + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 8759226038479000135L, testPluginData); + factory.setMaterialsProducerPropertyReportPluginData(MaterialsProducerPropertyReportPluginData.builder().setReportLabel(REPORT_LABEL).build()); + + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .build()// + .execute(); + + Map acutualReportItems = testOutputConsumer.getOutputItemMap(ReportItem.class); + assertEquals(expectedReportItems, acutualReportItems); + } + + @Test + @UnitTestMethod(target = MaterialsProducerPropertyReport.class, name = "init", args = {ReportContext.class }) + public void testInit_State() { + // Test with producing simulation state + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + double actionTime = 0; + MaterialsProducerId newMaterialsProducerId = TestMaterialsProducerId.getUnknownMaterialsProducerId(); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + })); + + // add a new materials producer at time 30 + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(30, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsProducerConstructionData.Builder builder = MaterialsProducerConstructionData.builder(); + + builder.setMaterialsProducerId(newMaterialsProducerId); + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .getPropertiesWithoutDefaultValues()) { + Object randomPropertyValue = testMaterialsProducerPropertyId.getRandomPropertyValue(randomGenerator); + builder.setMaterialsProducerPropertyValue(testMaterialsProducerPropertyId, randomPropertyValue); + } + + MaterialsProducerConstructionData materialsProducerConstructionData = builder.build(); + materialsDataManager.addMaterialsProducer(materialsProducerConstructionData); + })); + + for (int i = 0; i < 5; i++) { + + // set a property value + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + List materialsProducerIds = new ArrayList<>( + materialsDataManager.getMaterialsProducerIds()); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsProducerId materialsProducerId = materialsProducerIds + .get(randomGenerator.nextInt(materialsProducerIds.size())); + TestMaterialsProducerPropertyId testMaterialsProducerPropertyId = TestMaterialsProducerPropertyId + .getRandomMutableMaterialsProducerPropertyId(randomGenerator); + Object propertyValue = testMaterialsProducerPropertyId.getRandomPropertyValue(randomGenerator); + materialsDataManager.setMaterialsProducerPropertyValue(materialsProducerId, + testMaterialsProducerPropertyId, propertyValue); + })); + + } + + TestPluginData testPluginData = pluginBuilder.build(); + + MaterialsProducerPropertyReportPluginData materialsProducerPropertyReportPluginData = MaterialsProducerPropertyReportPluginData + .builder() + .setReportLabel(REPORT_LABEL) + .build(); + + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 8759226038479000135L, testPluginData); + factory.setMaterialsProducerPropertyReportPluginData(materialsProducerPropertyReportPluginData); + + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .setProduceSimulationStateOnHalt(true)// + .setSimulationHaltTime(50)// + .build()// + .execute(); + + Map outputItems = testOutputConsumer.getOutputItemMap(MaterialsProducerPropertyReportPluginData.class); + assertEquals(1, outputItems.size()); + MaterialsProducerPropertyReportPluginData materialsProducerPropertyReportPluginData2 = outputItems.keySet().iterator().next(); + assertEquals(materialsProducerPropertyReportPluginData, materialsProducerPropertyReportPluginData2); + + // Test without producing simulation state + + testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .setProduceSimulationStateOnHalt(false)// + .setSimulationHaltTime(50)// + .build()// + .execute(); + + outputItems = testOutputConsumer.getOutputItemMap(MaterialsProducerPropertyReportPluginData.class); + assertEquals(0, outputItems.size()); + } + + + private static ReportItem getReportItem(Object... values) { + Builder builder = ReportItem.builder().setReportLabel(REPORT_LABEL).setReportHeader(REPORT_HEADER); + for (Object value : values) { + builder.addValue(value); + } + return builder.build(); + } + + private static final ReportLabel REPORT_LABEL = new SimpleReportLabel("report"); + + private static final ReportHeader REPORT_HEADER = getReportHeader(); + + private static ReportHeader getReportHeader() { + ReportHeader.Builder builder = ReportHeader.builder()// + .add("time")// + .add("materials_producer")// + .add("property")// + .add("value");// + return builder.build(); + } + +} \ No newline at end of file diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/AT_MaterialsProducerPropertyReportPluginData.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/AT_MaterialsProducerPropertyReportPluginData.java new file mode 100644 index 000000000..139bd90ef --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/AT_MaterialsProducerPropertyReportPluginData.java @@ -0,0 +1,202 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.reports; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportError; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_MaterialsProducerPropertyReportPluginData { + + @Test + @UnitTestMethod(target = MaterialsProducerPropertyReportPluginData.class, name = "builder", args = {}) + public void testBuilder() { + MaterialsProducerPropertyReportPluginData.Builder builder = MaterialsProducerPropertyReportPluginData.builder(); + assertNotNull(builder); + } + + @Test + @UnitTestMethod(target = MaterialsProducerPropertyReportPluginData.Builder.class, name = "build", args = {}) + public void testBuild() { + // the specific capabilities are covered elsewhere + + // precondition test: if the report label is not assigned + ContractException contractException = assertThrows(ContractException.class, () -> // + MaterialsProducerPropertyReportPluginData.builder()// + .build()); + assertEquals(ReportError.NULL_REPORT_LABEL, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsProducerPropertyReportPluginData.Builder.class, name = "setReportLabel", args = { + ReportLabel.class }) + public void testSetReportLabel() { + + for (int i = 0; i < 30; i++) { + ReportLabel expectedReportLabel = new SimpleReportLabel(i); + MaterialsProducerPropertyReportPluginData materialsProducerPropertyReportPluginData = // + MaterialsProducerPropertyReportPluginData.builder()// + .setReportLabel(expectedReportLabel)// + .build(); + + assertEquals(expectedReportLabel, materialsProducerPropertyReportPluginData.getReportLabel()); + } + + // precondition: if the report label is null + ContractException contractException = assertThrows(ContractException.class, () -> { + MaterialsProducerPropertyReportPluginData.builder().setReportLabel(null); + }); + assertEquals(ReportError.NULL_REPORT_LABEL, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = MaterialsProducerPropertyReportPluginData.class, name = "getReportLabel", args = {}) + public void testGetReportLabel() { + for (int i = 0; i < 30; i++) { + ReportLabel expectedReportLabel = new SimpleReportLabel(i); + MaterialsProducerPropertyReportPluginData materialsProducerPropertyReportPluginData = // + MaterialsProducerPropertyReportPluginData.builder()// + .setReportLabel(expectedReportLabel)// + .build(); + + assertEquals(expectedReportLabel, materialsProducerPropertyReportPluginData.getReportLabel()); + } + + } + + @Test + @UnitTestMethod(target = MaterialsProducerPropertyReportPluginData.class, name = "getCloneBuilder", args = {}) + public void testGetCloneBuilder() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8951274294108578550L); + for (int i = 0; i < 10; i++) { + + // build a MaterialsProducerPropertyReportPluginData from random + // inputs + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt()); + + MaterialsProducerPropertyReportPluginData.Builder builder = // + MaterialsProducerPropertyReportPluginData.builder()// + .setReportLabel(reportLabel); + + MaterialsProducerPropertyReportPluginData materialsProducerPropertyReportPluginData = builder.build(); + + // create the clone builder and have it build + MaterialsProducerPropertyReportPluginData cloneMaterialsProducerPropertyReportPluginData = materialsProducerPropertyReportPluginData + .getCloneBuilder().build(); + + // the result should equal the original if the clone builder was + // initialized with the correct state + assertEquals(materialsProducerPropertyReportPluginData, cloneMaterialsProducerPropertyReportPluginData); + + } + } + + @Test + @UnitTestMethod(target = MaterialsProducerPropertyReportPluginData.class, name = "equals", args = { Object.class }) + public void testEquals() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7759639255438669162L); + for (int i = 0; i < 10; i++) { + // build a MaterialsProducerPropertyReportPluginData from the same + // random + // inputs + MaterialsProducerPropertyReportPluginData.Builder builder1 = MaterialsProducerPropertyReportPluginData + .builder(); + MaterialsProducerPropertyReportPluginData.Builder builder2 = MaterialsProducerPropertyReportPluginData + .builder(); + + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt(100)); + builder1.setReportLabel(reportLabel); + builder2.setReportLabel(reportLabel); + + MaterialsProducerPropertyReportPluginData materialsProducerPropertyReportPluginData1 = builder1.build(); + MaterialsProducerPropertyReportPluginData materialsProducerPropertyReportPluginData2 = builder2.build(); + + assertEquals(materialsProducerPropertyReportPluginData1, materialsProducerPropertyReportPluginData2); + + // show that plugin datas with different inputs are not equal + + // change the report label + reportLabel = new SimpleReportLabel(1000); + materialsProducerPropertyReportPluginData2 = // + materialsProducerPropertyReportPluginData1.getCloneBuilder()// + .setReportLabel(reportLabel)// + .build(); + assertNotEquals(materialsProducerPropertyReportPluginData2, materialsProducerPropertyReportPluginData1); + } + + } + + @Test + @UnitTestMethod(target = MaterialsProducerPropertyReportPluginData.class, name = "hashCode", args = {}) + public void testHashCode() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(9079768427072825406L); + + Set observedHashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 50; i++) { + // build a MaterialsProducerPropertyReportPluginData from the same + // random + // inputs + MaterialsProducerPropertyReportPluginData.Builder builder1 = MaterialsProducerPropertyReportPluginData + .builder(); + MaterialsProducerPropertyReportPluginData.Builder builder2 = MaterialsProducerPropertyReportPluginData + .builder(); + + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt(100)); + builder1.setReportLabel(reportLabel); + builder2.setReportLabel(reportLabel); + + MaterialsProducerPropertyReportPluginData materialsProducerPropertyReportPluginData1 = builder1.build(); + MaterialsProducerPropertyReportPluginData materialsProducerPropertyReportPluginData2 = builder2.build(); + + // show that the hash code is stable + int hashCode = materialsProducerPropertyReportPluginData1.hashCode(); + assertEquals(hashCode, materialsProducerPropertyReportPluginData1.hashCode()); + assertEquals(hashCode, materialsProducerPropertyReportPluginData1.hashCode()); + assertEquals(hashCode, materialsProducerPropertyReportPluginData1.hashCode()); + assertEquals(hashCode, materialsProducerPropertyReportPluginData1.hashCode()); + + // show that equal objects have equal hash codes + assertEquals(materialsProducerPropertyReportPluginData1.hashCode(), + materialsProducerPropertyReportPluginData2.hashCode()); + + // collect the hashcode + observedHashCodes.add(materialsProducerPropertyReportPluginData1.hashCode()); + } + + /* + * The hash codes should be dispersed -- we only show that they are unique + * values -- this is dependent on the random seed + */ + assertTrue(observedHashCodes.size() > 40); + + } + + @Test + @UnitTestMethod(target = MaterialsProducerPropertyReportPluginData.class, name = "toString", args = {}) + public void testToString() { + MaterialsProducerPropertyReportPluginData materialsProducerPropertyReportPluginData = // + MaterialsProducerPropertyReportPluginData.builder()// + .setReportLabel(new SimpleReportLabel("report label"))// + .build(); + String actualValue = materialsProducerPropertyReportPluginData.toString(); + String expectedValue = "MaterialsProducerPropertyReportPluginData [data=Data [reportLabel=SimpleReportLabel [value=report label], locked=true]]"; + assertEquals(expectedValue, actualValue); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/AT_MaterialsProducerResourceReport.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/AT_MaterialsProducerResourceReport.java new file mode 100644 index 000000000..4440e6694 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/AT_MaterialsProducerResourceReport.java @@ -0,0 +1,308 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.reports; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import org.apache.commons.math3.random.RandomGenerator; +import org.apache.commons.math3.util.FastMath; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestOutputConsumer; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.materials.datamangers.MaterialsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.materials.datamangers.MaterialsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerConstructionData; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.StageId; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.MaterialsTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.TestMaterialsProducerId; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.TestMaterialsProducerPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.TestRegionId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem.Builder; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.resources.datamanagers.ResourcesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.TestResourceId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import util.annotations.UnitTag; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public final class AT_MaterialsProducerResourceReport { + + private ReportItem getReportItemFromResourceId(ActorContext agentContext, MaterialsProducerId materialsProducerId, + ResourceId resourceId, long amount) { + + String actionName; + if (amount < 0) { + actionName = "Removed"; + } else { + actionName = "Added"; + } + + long reportedAmount = FastMath.abs(amount); + + return getReportItem(agentContext.getTime(), resourceId, materialsProducerId, actionName, reportedAmount); + + } + + @Test + @UnitTestConstructor(target = MaterialsProducerResourceReport.class, args = { + MaterialsProducerResourceReportPluginData.class }) + public void testConstructor() { + MaterialsProducerResourceReport report = new MaterialsProducerResourceReport( + MaterialsProducerResourceReportPluginData.builder().setReportLabel(REPORT_LABEL).build()); + assertNotNull(report); + } + + @Test + @UnitTestMethod(target = MaterialsProducerResourceReport.class, name = "init", args = { + ReportContext.class }, tags = { UnitTag.INCOMPLETE }) + public void testInit() { + Map expectedReportItems = new LinkedHashMap<>(); + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + MaterialsProducerId newMaterialsProducerId = TestMaterialsProducerId.getUnknownMaterialsProducerId(); + long seed = 6081341958178733565L; + MaterialsPluginData standardPluginData = MaterialsTestPluginFactory.getStandardMaterialsPluginData(0, 0, 0, + seed); + + double actionTime = 0; + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + for (TestResourceId testResourceId : TestResourceId.values()) { + long existingAmount = standardPluginData.getMaterialsProducerResourceLevel(testMaterialsProducerId, testResourceId); + expectedReportItems.put(getReportItemFromResourceId(c, testMaterialsProducerId, testResourceId, existingAmount), + 1); + } + } + + ResourceId newResourceId = TestResourceId.getUnknownResourceId(); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.addResourceId(newResourceId, true); + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + expectedReportItems.put(getReportItemFromResourceId(c, testMaterialsProducerId, newResourceId, 0L), 1); + } + + })); + + // add a new materials producer just before time 20 + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(19.5, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialsProducerConstructionData.Builder builder = // + MaterialsProducerConstructionData.builder()// + .setMaterialsProducerId(newMaterialsProducerId);// + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .getPropertiesWithoutDefaultValues()) { + Object randomPropertyValue = testMaterialsProducerPropertyId.getRandomPropertyValue(randomGenerator); + builder.setMaterialsProducerPropertyValue(testMaterialsProducerPropertyId, randomPropertyValue); + } + + MaterialsProducerConstructionData materialsProducerConstructionData = builder.build(); + + materialsDataManager.addMaterialsProducer(materialsProducerConstructionData); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + for (ResourceId resourceId : resourcesDataManager.getResourceIds()) { + expectedReportItems.put(getReportItemFromResourceId(c, newMaterialsProducerId, resourceId, 0L), 1); + } + + })); + + for (int i = 0; i < 100; i++) { + + // set a resource value + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + List mats = new ArrayList<>(materialsDataManager.getMaterialsProducerIds()); + + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsProducerId materialsProducerId = mats.get(randomGenerator.nextInt(mats.size())); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + List resourceIds = new ArrayList<>(resourcesDataManager.getResourceIds()); + Random random = new Random(randomGenerator.nextLong()); + + ResourceId resourceId = resourceIds.get(random.nextInt(resourceIds.size())); + + if (randomGenerator.nextBoolean()) { + long amount = randomGenerator.nextInt(100) + 1; + StageId stageId = materialsDataManager.addStage(materialsProducerId); + materialsDataManager.convertStageToResource(stageId, resourceId, amount); + expectedReportItems.put(getReportItemFromResourceId(c, materialsProducerId, resourceId, amount), 1); + } else { + long resourceLevel = materialsDataManager.getMaterialsProducerResourceLevel(materialsProducerId, + resourceId); + if (resourceLevel > 0) { + long amount = randomGenerator.nextInt((int) resourceLevel) + 1; + TestRegionId testRegionId = TestRegionId.getRandomRegionId(randomGenerator); + materialsDataManager.transferResourceToRegion(materialsProducerId, resourceId, testRegionId, + amount); + expectedReportItems + .put(getReportItemFromResourceId(c, materialsProducerId, resourceId, -amount), 1); + } + } + })); + } + + TestPluginData testPluginData = pluginBuilder.build(); + + MaterialsTestPluginFactory.Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, seed, + testPluginData); + MaterialsProducerResourceReportPluginData materialsProducerResourceReportPluginData = MaterialsProducerResourceReportPluginData + .builder().setReportLabel(REPORT_LABEL).build(); + factory.setMaterialsProducerResourceReportPluginData(materialsProducerResourceReportPluginData); + + TestOutputConsumer testOutputConsumer = TestSimulation.builder()// + .addPlugins(factory.getPlugins())// + .build()// + .execute(); + + assertEquals(expectedReportItems, testOutputConsumer.getOutputItemMap(ReportItem.class)); + } + + @Test + @UnitTestMethod(target = MaterialsProducerResourceReport.class, name = "init", args = { ReportContext.class }) + public void testInit_State() { + // Test with producing simulation state + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + MaterialsProducerId newMaterialsProducerId = TestMaterialsProducerId.getUnknownMaterialsProducerId(); + + double actionTime = 0; + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + ResourceId newResourceId = TestResourceId.getUnknownResourceId(); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.addResourceId(newResourceId, true); + })); + + // add a new materials producer just before time 20 + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(19.5, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + MaterialsProducerConstructionData.Builder builder = // + MaterialsProducerConstructionData.builder()// + .setMaterialsProducerId(newMaterialsProducerId);// + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .getPropertiesWithoutDefaultValues()) { + Object randomPropertyValue = testMaterialsProducerPropertyId.getRandomPropertyValue(randomGenerator); + builder.setMaterialsProducerPropertyValue(testMaterialsProducerPropertyId, randomPropertyValue); + } + + MaterialsProducerConstructionData materialsProducerConstructionData = builder.build(); + + materialsDataManager.addMaterialsProducer(materialsProducerConstructionData); + })); + + for (int i = 0; i < 100; i++) { + + // set a resource value + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + List mats = new ArrayList<>(materialsDataManager.getMaterialsProducerIds()); + + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + MaterialsProducerId materialsProducerId = mats.get(randomGenerator.nextInt(mats.size())); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + List resourceIds = new ArrayList<>(resourcesDataManager.getResourceIds()); + Random random = new Random(randomGenerator.nextLong()); + + ResourceId resourceId = resourceIds.get(random.nextInt(resourceIds.size())); + + if (randomGenerator.nextBoolean()) { + long amount = randomGenerator.nextInt(100) + 1; + StageId stageId = materialsDataManager.addStage(materialsProducerId); + materialsDataManager.convertStageToResource(stageId, resourceId, amount); + } else { + long resourceLevel = materialsDataManager.getMaterialsProducerResourceLevel(materialsProducerId, + resourceId); + if (resourceLevel > 0) { + long amount = randomGenerator.nextInt((int) resourceLevel) + 1; + TestRegionId testRegionId = TestRegionId.getRandomRegionId(randomGenerator); + materialsDataManager.transferResourceToRegion(materialsProducerId, resourceId, testRegionId, + amount); + } + } + })); + } + + TestPluginData testPluginData = pluginBuilder.build(); + MaterialsProducerResourceReportPluginData materialsProducerResourceReportPluginData = MaterialsProducerResourceReportPluginData + .builder() + .setReportLabel(REPORT_LABEL) + .build(); + + MaterialsTestPluginFactory.Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 6081341958178733565L, + testPluginData); + factory.setMaterialsProducerResourceReportPluginData(materialsProducerResourceReportPluginData); + + TestOutputConsumer testOutputConsumer = TestSimulation.builder()// + .addPlugins(factory.getPlugins())// + .setProduceSimulationStateOnHalt(true)// + .setSimulationHaltTime(150)// + .build()// + .execute(); + + Map outputItems = testOutputConsumer + .getOutputItemMap(MaterialsProducerResourceReportPluginData.class); + assertEquals(1, outputItems.size()); + MaterialsProducerResourceReportPluginData materialsProducerResourceReportPluginData2 = outputItems.keySet() + .iterator().next(); + assertEquals(materialsProducerResourceReportPluginData, materialsProducerResourceReportPluginData2); + + // Test without producing simulation state + + testOutputConsumer = TestSimulation.builder()// + .addPlugins(factory.getPlugins())// + .setProduceSimulationStateOnHalt(false)// + .setSimulationHaltTime(150)// + .build()// + .execute(); + + outputItems = testOutputConsumer.getOutputItemMap(MaterialsProducerResourceReportPluginData.class); + assertEquals(0, outputItems.size()); + } + + private static ReportItem getReportItem(Object... values) { + Builder builder = ReportItem.builder().setReportLabel(REPORT_LABEL).setReportHeader(REPORT_HEADER); + for (Object value : values) { + builder.addValue(value); + } + return builder.build(); + } + + private static final ReportLabel REPORT_LABEL = new SimpleReportLabel("report"); + + private static final ReportHeader REPORT_HEADER = getReportHeader(); + + private static ReportHeader getReportHeader() { + return ReportHeader.builder()// + .add("time")// + .add("resource")// + .add("materials_producer")// + .add("action")// + .add("amount")// + .build(); + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/AT_MaterialsProducerResourceReportPluginData.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/AT_MaterialsProducerResourceReportPluginData.java new file mode 100644 index 000000000..b8f301f9f --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/AT_MaterialsProducerResourceReportPluginData.java @@ -0,0 +1,194 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.reports; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportError; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_MaterialsProducerResourceReportPluginData { + + @Test + @UnitTestMethod(target = MaterialsProducerResourceReportPluginData.class, name = "builder", args = {}) + public void testBuilder() { + MaterialsProducerResourceReportPluginData.Builder builder = MaterialsProducerResourceReportPluginData.builder(); + assertNotNull(builder); + } + + @Test + @UnitTestMethod(target = MaterialsProducerResourceReportPluginData.Builder.class, name = "build", args = {}) + public void testBuild() { + // the specific capabilities are covered elsewhere + + // precondition test: if the report label is not assigned + ContractException contractException = assertThrows(ContractException.class, () -> // + MaterialsProducerResourceReportPluginData.builder()// + .build()); + assertEquals(ReportError.NULL_REPORT_LABEL, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsProducerResourceReportPluginData.Builder.class, name = "setReportLabel", args = { ReportLabel.class }) + public void testSetReportLabel() { + + for (int i = 0; i < 30; i++) { + ReportLabel expectedReportLabel = new SimpleReportLabel(i); + MaterialsProducerResourceReportPluginData materialsProducerResourceReportPluginData = // + MaterialsProducerResourceReportPluginData.builder()// + .setReportLabel(expectedReportLabel)// + .build(); + + assertEquals(expectedReportLabel, materialsProducerResourceReportPluginData.getReportLabel()); + } + + // precondition: if the report label is null + ContractException contractException = assertThrows(ContractException.class, () -> { + MaterialsProducerResourceReportPluginData.builder().setReportLabel(null); + }); + assertEquals(ReportError.NULL_REPORT_LABEL, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = MaterialsProducerResourceReportPluginData.class, name = "getReportLabel", args = {}) + public void testGetReportLabel() { + for (int i = 0; i < 30; i++) { + ReportLabel expectedReportLabel = new SimpleReportLabel(i); + MaterialsProducerResourceReportPluginData materialsProducerResourceReportPluginData = // + MaterialsProducerResourceReportPluginData.builder()// + .setReportLabel(expectedReportLabel)// + .build(); + + assertEquals(expectedReportLabel, materialsProducerResourceReportPluginData.getReportLabel()); + } + + } + + @Test + @UnitTestMethod(target = MaterialsProducerResourceReportPluginData.class, name = "getCloneBuilder", args = {}) + public void testGetCloneBuilder() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8951274294108578550L); + for (int i = 0; i < 10; i++) { + + // build a MaterialsProducerResourceReportPluginData from random inputs + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt()); + + MaterialsProducerResourceReportPluginData.Builder builder = // + MaterialsProducerResourceReportPluginData.builder()// + .setReportLabel(reportLabel); + + MaterialsProducerResourceReportPluginData materialsProducerResourceReportPluginData = builder.build(); + + // create the clone builder and have it build + MaterialsProducerResourceReportPluginData cloneMaterialsProducerResourceReportPluginData = materialsProducerResourceReportPluginData.getCloneBuilder().build(); + + // the result should equal the original if the clone builder was + // initialized with the correct state + assertEquals(materialsProducerResourceReportPluginData, cloneMaterialsProducerResourceReportPluginData); + + } + } + + @Test + @UnitTestMethod(target = MaterialsProducerResourceReportPluginData.class, name = "equals", args = { Object.class }) + public void testEquals() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7759639255438669162L); + for (int i = 0; i < 10; i++) { + // build a MaterialsProducerResourceReportPluginData from the same random + // inputs + MaterialsProducerResourceReportPluginData.Builder builder1 = MaterialsProducerResourceReportPluginData.builder(); + MaterialsProducerResourceReportPluginData.Builder builder2 = MaterialsProducerResourceReportPluginData.builder(); + + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt(100)); + builder1.setReportLabel(reportLabel); + builder2.setReportLabel(reportLabel); + + MaterialsProducerResourceReportPluginData materialsProducerResourceReportPluginData1 = builder1.build(); + MaterialsProducerResourceReportPluginData materialsProducerResourceReportPluginData2 = builder2.build(); + + assertEquals(materialsProducerResourceReportPluginData1, materialsProducerResourceReportPluginData2); + + // show that plugin datas with different inputs are not equal + + // change the report label + reportLabel = new SimpleReportLabel(1000); + materialsProducerResourceReportPluginData2 = // + materialsProducerResourceReportPluginData1 .getCloneBuilder()// + .setReportLabel(reportLabel)// + .build(); + assertNotEquals(materialsProducerResourceReportPluginData2, materialsProducerResourceReportPluginData1); + } + + } + + @Test + @UnitTestMethod(target = MaterialsProducerResourceReportPluginData.class, name = "hashCode", args = {}) + public void testHashCode() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(9079768427072825406L); + + Set observedHashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 50; i++) { + // build a MaterialsProducerResourceReportPluginData from the same random + // inputs + MaterialsProducerResourceReportPluginData.Builder builder1 = MaterialsProducerResourceReportPluginData.builder(); + MaterialsProducerResourceReportPluginData.Builder builder2 = MaterialsProducerResourceReportPluginData.builder(); + + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt(100)); + builder1.setReportLabel(reportLabel); + builder2.setReportLabel(reportLabel); + + + MaterialsProducerResourceReportPluginData materialsProducerResourceReportPluginData1 = builder1.build(); + MaterialsProducerResourceReportPluginData materialsProducerResourceReportPluginData2 = builder2.build(); + + // show that the hash code is stable + int hashCode = materialsProducerResourceReportPluginData1.hashCode(); + assertEquals(hashCode, materialsProducerResourceReportPluginData1.hashCode()); + assertEquals(hashCode, materialsProducerResourceReportPluginData1.hashCode()); + assertEquals(hashCode, materialsProducerResourceReportPluginData1.hashCode()); + assertEquals(hashCode, materialsProducerResourceReportPluginData1.hashCode()); + + // show that equal objects have equal hash codes + assertEquals(materialsProducerResourceReportPluginData1.hashCode(), materialsProducerResourceReportPluginData2.hashCode()); + + // collect the hashcode + observedHashCodes.add(materialsProducerResourceReportPluginData1.hashCode()); + } + + /* + * The hash codes should be dispersed -- we only show that they are + * unique values -- this is dependent on the random seed + */ + assertTrue(observedHashCodes.size()>40); + + } + + @Test + @UnitTestMethod(target = MaterialsProducerResourceReportPluginData.class, name = "toString", args = {}) + public void testToString() { + MaterialsProducerResourceReportPluginData materialsProducerResourceReportPluginData = // + MaterialsProducerResourceReportPluginData.builder()// + .setReportLabel(new SimpleReportLabel("report label"))// + .build(); + String actualValue = materialsProducerResourceReportPluginData.toString(); + String expectedValue = "MaterialsProducerResourceReportPluginData [data=Data [reportLabel=SimpleReportLabel [value=report label], locked=true]]"; + assertEquals(expectedValue, actualValue); + } + + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/AT_StageReport.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/AT_StageReport.java new file mode 100644 index 000000000..f86810bba --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/AT_StageReport.java @@ -0,0 +1,361 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.reports; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestOutputConsumer; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.materials.datamangers.MaterialsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.StageId; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.MaterialsTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.MaterialsTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.TestMaterialsProducerId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import util.annotations.UnitTag; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.random.RandomGeneratorProvider; + +public final class AT_StageReport { + + private static enum Action { + CREATED("Create"), + + DESTROYED("Destroy"), + + OFFERED("Offer"), + + TRANSFERRED("Transfer"); + + private final String displayName; + + private Action(final String displayName) { + this.displayName = displayName; + } + } + + private ReportItem getReportItem(ActorContext agentContext, StageId stageId, + MaterialsProducerId materialsProducerId, boolean isOffered, Action action) { + + return ReportItem.builder()// + .setReportLabel(REPORT_LABEL)// + .setReportHeader(REPORT_HEADER)// + .addValue(agentContext.getTime())// + .addValue(stageId)// + .addValue(materialsProducerId)// + .addValue(action.displayName)// + .addValue(isOffered)// + .build();// + } + + @Test + @UnitTestConstructor(target = StageReport.class, args = { StageReportPluginData.class }) + public void testConstructor() { + StageReport report = new StageReport(StageReportPluginData.builder().setReportLabel(REPORT_LABEL).build()); + assertNotNull(report); + } + + @Test + @UnitTestMethod(target = StageReport.class, name = "init", args = { ReportContext.class }, tags = {UnitTag.INCOMPLETE }) + public void testInit() { + /* + * Create containers for the expected and actual report items that will + * be generated by various stage related events. + * + */ + Map expectedReportItems = new LinkedHashMap<>(); + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // Generate 500 stage-based actions and record the expected report items + RandomGenerator rg = RandomGeneratorProvider.getRandomGenerator(8635270533185454765L); + + double actionTime = 0; + + // create a list of 500 producer ids at random + List producerIds = new ArrayList<>(); + for (int i = 0; i < 500; i++) { + TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId.getRandomMaterialsProducerId(rg); + producerIds.add(testMaterialsProducerId); + } + + // for each producer id, execute and action and increment time for the + // plan + for (TestMaterialsProducerId testMaterialsProducerId : producerIds) { + + /* + * Have the producer execute one of the four actions: stage + * creation, stage destruction, stage offer status change and stage + * transfer + * + */ + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + List stages = materialsDataManager.getStages(testMaterialsProducerId); + List offeredStages = materialsDataManager.getOfferedStages(testMaterialsProducerId); + + // select one of the four possible actions at random based on + // the availability of the action + + // should we create a stage? -- we can always create a new + // stage, so assume that is what we will do + Action action = Action.CREATED; + int candidateActionCount = 1; + + // should we destroy a non-offered stage? + if (stages.size() > offeredStages.size()) { + candidateActionCount++; + if (randomGenerator.nextDouble() < 1.0 / candidateActionCount) { + action = Action.DESTROYED; + } + } + + // should we transfer an offered stage? + if (offeredStages.size() > 0) { + candidateActionCount++; + if (randomGenerator.nextDouble() < 1.0 / candidateActionCount) { + action = Action.TRANSFERRED; + } + } + + // should we change the offer state of a stage? + if (stages.size() > 0) { + candidateActionCount++; + if (randomGenerator.nextDouble() < 1.0 / candidateActionCount) { + action = Action.OFFERED; + } + } + + StageId stageId; + boolean stageOffered; + switch (action) { + case CREATED: + stageId = materialsDataManager.addStage(testMaterialsProducerId); + stageOffered = materialsDataManager.isStageOffered(stageId); + expectedReportItems + .put(getReportItem(c, stageId, testMaterialsProducerId, stageOffered, action), 1); + break; + case DESTROYED: + stages.removeAll(offeredStages); + stageId = stages.get(randomGenerator.nextInt(stages.size())); + stageOffered = materialsDataManager.isStageOffered(stageId); + materialsDataManager.removeStage(stageId, false); + expectedReportItems + .put(getReportItem(c, stageId, testMaterialsProducerId, stageOffered, action), 1); + break; + case OFFERED: + stageId = stages.get(randomGenerator.nextInt(stages.size())); + stageOffered = materialsDataManager.isStageOffered(stageId); + materialsDataManager.setStageOfferState(stageId, !stageOffered); + expectedReportItems + .put(getReportItem(c, stageId, testMaterialsProducerId, !stageOffered, action), 1); + break; + case TRANSFERRED: + stageId = offeredStages.get(randomGenerator.nextInt(offeredStages.size())); + stageOffered = materialsDataManager.isStageOffered(stageId); + List candidateProducerIds = new ArrayList<>( + materialsDataManager.getMaterialsProducerIds()); + candidateProducerIds.remove(testMaterialsProducerId); + MaterialsProducerId materialsProducerId = candidateProducerIds + .get(randomGenerator.nextInt(candidateProducerIds.size())); + materialsDataManager.transferOfferedStage(stageId, materialsProducerId); + expectedReportItems.put(getReportItem(c, stageId, materialsProducerId, true, action), 1); + expectedReportItems.put(getReportItem(c, stageId, materialsProducerId, false, Action.OFFERED), + 1); + break; + default: + throw new RuntimeException("unhandled action type"); + } + + })); + } + + TestPluginData testPluginData = pluginBuilder.build(); + + + + Factory factory = MaterialsTestPluginFactory// + .factory(0, 0, 0, 542686524159732447L, testPluginData) + .setStageReportPluginData(StageReportPluginData.builder().setReportLabel(REPORT_LABEL).build());// + + + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .build()// + .execute(); + + + assertEquals(expectedReportItems, testOutputConsumer.getOutputItemMap(ReportItem.class)); + } + + @Test + @UnitTestMethod(target = StageReport.class, name = "init", args = { ReportContext.class }) + public void testInit_State() { + // Test with producing simulation state + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // Generate 500 stage-based actions + RandomGenerator rg = RandomGeneratorProvider.getRandomGenerator(8635270533185454765L); + + double actionTime = 0; + + // create a list of 500 producer ids at random + List producerIds = new ArrayList<>(); + for (int i = 0; i < 500; i++) { + TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId.getRandomMaterialsProducerId(rg); + producerIds.add(testMaterialsProducerId); + } + + // for each producer id, execute and action and increment time for the + // plan + for (TestMaterialsProducerId testMaterialsProducerId : producerIds) { + + /* + * Have the producer execute one of the four actions: stage + * creation, stage destruction, stage offer status change and stage + * transfer + * + */ + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { + MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + List stages = materialsDataManager.getStages(testMaterialsProducerId); + List offeredStages = materialsDataManager.getOfferedStages(testMaterialsProducerId); + + // select one of the four possible actions at random based on + // the availability of the action + + // should we create a stage? -- we can always create a new + // stage, so assume that is what we will do + Action action = Action.CREATED; + int candidateActionCount = 1; + + // should we destroy a non-offered stage? + if (stages.size() > offeredStages.size()) { + candidateActionCount++; + if (randomGenerator.nextDouble() < 1.0 / candidateActionCount) { + action = Action.DESTROYED; + } + } + + // should we transfer an offered stage? + if (offeredStages.size() > 0) { + candidateActionCount++; + if (randomGenerator.nextDouble() < 1.0 / candidateActionCount) { + action = Action.TRANSFERRED; + } + } + + // should we change the offer state of a stage? + if (stages.size() > 0) { + candidateActionCount++; + if (randomGenerator.nextDouble() < 1.0 / candidateActionCount) { + action = Action.OFFERED; + } + } + + StageId stageId; + boolean stageOffered; + switch (action) { + case CREATED: + break; + case DESTROYED: + stages.removeAll(offeredStages); + stageId = stages.get(randomGenerator.nextInt(stages.size())); + materialsDataManager.removeStage(stageId, false); + break; + case OFFERED: + stageId = stages.get(randomGenerator.nextInt(stages.size())); + stageOffered = materialsDataManager.isStageOffered(stageId); + materialsDataManager.setStageOfferState(stageId, !stageOffered); + break; + case TRANSFERRED: + stageId = offeredStages.get(randomGenerator.nextInt(offeredStages.size())); + List candidateProducerIds = new ArrayList<>( + materialsDataManager.getMaterialsProducerIds()); + candidateProducerIds.remove(testMaterialsProducerId); + MaterialsProducerId materialsProducerId = candidateProducerIds + .get(randomGenerator.nextInt(candidateProducerIds.size())); + materialsDataManager.transferOfferedStage(stageId, materialsProducerId); + break; + default: + throw new RuntimeException("unhandled action type"); + } + + })); + } + + TestPluginData testPluginData = pluginBuilder.build(); + StageReportPluginData stageReportPluginData = StageReportPluginData.builder() + .setReportLabel(REPORT_LABEL) + .build(); + + Factory factory = MaterialsTestPluginFactory// + .factory(0, 0, 0, 542686524159732447L, testPluginData) + .setStageReportPluginData(stageReportPluginData);// + + + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .setProduceSimulationStateOnHalt(true)// + .setSimulationHaltTime(550)// + .build()// + .execute(); + + Map outputItems = testOutputConsumer.getOutputItemMap(StageReportPluginData.class); + assertEquals(1, outputItems.size()); + StageReportPluginData stageReportPluginData2 = outputItems.keySet().iterator().next(); + assertEquals(stageReportPluginData, stageReportPluginData2); + + // Test without producing simulation state + + testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .setProduceSimulationStateOnHalt(false)// + .setSimulationHaltTime(550)// + .build()// + .execute(); + + outputItems = testOutputConsumer.getOutputItemMap(StageReportPluginData.class); + assertEquals(0, outputItems.size()); + } + + private static final ReportLabel REPORT_LABEL = new SimpleReportLabel("report"); + + private static final ReportHeader REPORT_HEADER = getReportHeader(); + + private static ReportHeader getReportHeader() { + return ReportHeader.builder()// + .add("time")// + .add("stage")// + .add("materials_producer")// + .add("action")// + .add("offered")// + .build(); + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/AT_StageReportPluginData.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/AT_StageReportPluginData.java new file mode 100644 index 000000000..1c6e749de --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/reports/AT_StageReportPluginData.java @@ -0,0 +1,192 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.reports; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportError; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_StageReportPluginData { + + @Test + @UnitTestMethod(target = StageReportPluginData.class, name = "builder", args = {}) + public void testBuilder() { + StageReportPluginData.Builder builder = StageReportPluginData.builder(); + assertNotNull(builder); + } + + @Test + @UnitTestMethod(target = StageReportPluginData.Builder.class, name = "build", args = {}) + public void testBuild() { + // the specific capabilities are covered elsewhere + + // precondition test: if the report label is not assigned + ContractException contractException = assertThrows(ContractException.class, () -> // + StageReportPluginData.builder()// + .build()); + assertEquals(ReportError.NULL_REPORT_LABEL, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = StageReportPluginData.Builder.class, name = "setReportLabel", args = { ReportLabel.class }) + public void testSetReportLabel() { + + for (int i = 0; i < 30; i++) { + ReportLabel expectedReportLabel = new SimpleReportLabel(i); + StageReportPluginData stageReportPluginData = // + StageReportPluginData.builder()// + .setReportLabel(expectedReportLabel)// + .build(); + + assertEquals(expectedReportLabel, stageReportPluginData.getReportLabel()); + } + + // precondition: if the report label is null + ContractException contractException = assertThrows(ContractException.class, () -> { + StageReportPluginData.builder().setReportLabel(null); + }); + assertEquals(ReportError.NULL_REPORT_LABEL, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = StageReportPluginData.class, name = "getReportLabel", args = {}) + public void testGetReportLabel() { + for (int i = 0; i < 30; i++) { + ReportLabel expectedReportLabel = new SimpleReportLabel(i); + StageReportPluginData stageReportPluginData = // + StageReportPluginData.builder()// + .setReportLabel(expectedReportLabel)// + .build(); + + assertEquals(expectedReportLabel, stageReportPluginData.getReportLabel()); + } + + } + + @Test + @UnitTestMethod(target = StageReportPluginData.class, name = "getCloneBuilder", args = {}) + public void testGetCloneBuilder() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8951274294108578550L); + for (int i = 0; i < 10; i++) { + + // build a StageReportPluginData from random inputs + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt()); + + StageReportPluginData.Builder builder = // + StageReportPluginData.builder()// + .setReportLabel(reportLabel); + + StageReportPluginData stageReportPluginData = builder.build(); + + // create the clone builder and have it build + StageReportPluginData cloneStageReportPluginData = stageReportPluginData.getCloneBuilder().build(); + + // the result should equal the original if the clone builder was + // initialized with the correct state + assertEquals(stageReportPluginData, cloneStageReportPluginData); + + } + } + + @Test + @UnitTestMethod(target = StageReportPluginData.class, name = "equals", args = { Object.class }) + public void testEquals() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7759639255438669162L); + for (int i = 0; i < 10; i++) { + // build a StageReportPluginData from the same random + // inputs + StageReportPluginData.Builder builder1 = StageReportPluginData.builder(); + StageReportPluginData.Builder builder2 = StageReportPluginData.builder(); + + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt(100)); + builder1.setReportLabel(reportLabel); + builder2.setReportLabel(reportLabel); + + StageReportPluginData stageReportPluginData1 = builder1.build(); + StageReportPluginData stageReportPluginData2 = builder2.build(); + + assertEquals(stageReportPluginData1, stageReportPluginData2); + + // show that plugin datas with different inputs are not equal + + // change the report label + reportLabel = new SimpleReportLabel(1000); + stageReportPluginData2 = // + stageReportPluginData1.getCloneBuilder()// + .setReportLabel(reportLabel)// + .build(); + assertNotEquals(stageReportPluginData2, stageReportPluginData1); + } + + } + + @Test + @UnitTestMethod(target = StageReportPluginData.class, name = "hashCode", args = {}) + public void testHashCode() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(9079768427072825406L); + + Set observedHashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 50; i++) { + // build a StageReportPluginData from the same random + // inputs + StageReportPluginData.Builder builder1 = StageReportPluginData.builder(); + StageReportPluginData.Builder builder2 = StageReportPluginData.builder(); + + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt(100)); + builder1.setReportLabel(reportLabel); + builder2.setReportLabel(reportLabel); + + StageReportPluginData stageReportPluginData1 = builder1.build(); + StageReportPluginData stageReportPluginData2 = builder2.build(); + + // show that the hash code is stable + int hashCode = stageReportPluginData1.hashCode(); + assertEquals(hashCode, stageReportPluginData1.hashCode()); + assertEquals(hashCode, stageReportPluginData1.hashCode()); + assertEquals(hashCode, stageReportPluginData1.hashCode()); + assertEquals(hashCode, stageReportPluginData1.hashCode()); + + // show that equal objects have equal hash codes + assertEquals(stageReportPluginData1.hashCode(), stageReportPluginData2.hashCode()); + + // collect the hashcode + observedHashCodes.add(stageReportPluginData1.hashCode()); + } + + /* + * The hash codes should be dispersed -- we only show that they are unique + * values -- this is dependent on the random seed + */ + assertTrue(observedHashCodes.size() > 40); + + } + + @Test + @UnitTestMethod(target = StageReportPluginData.class, name = "toString", args = {}) + public void testToString() { + + StageReportPluginData stageReportPluginData = StageReportPluginData.builder().setReportLabel(new SimpleReportLabel("some label")).build(); + + String expectedValue = "StageReportPluginData [data=Data [reportLabel=SimpleReportLabel [value=some label], locked=true]]"; + String actualValue = stageReportPluginData.toString(); + assertEquals(expectedValue, actualValue); + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/AT_BatchConstructionInfo.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/AT_BatchConstructionInfo.java new file mode 100644 index 000000000..a4b8f97ee --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/AT_BatchConstructionInfo.java @@ -0,0 +1,265 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.TestBatchPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.TestMaterialId; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.TestMaterialsProducerId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_BatchConstructionInfo { + + @Test + @UnitTestMethod(target = BatchConstructionInfo.class,name = "builder", args = {}) + public void testBuilder() { + + assertNotNull(BatchConstructionInfo.builder()); + } + + @Test + @UnitTestMethod(target = BatchConstructionInfo.Builder.class, name = "build", args = {}) + public void testBuild() { + + BatchConstructionInfo batchConstructionInfo = // + BatchConstructionInfo.builder()// + .setMaterialId(TestMaterialId.MATERIAL_1)// + .setMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_1)// + .build(); + assertNotNull(batchConstructionInfo); + assertEquals(0, batchConstructionInfo.getAmount()); + assertEquals(TestMaterialId.MATERIAL_1, batchConstructionInfo.getMaterialId()); + assertEquals(TestMaterialsProducerId.MATERIALS_PRODUCER_1, batchConstructionInfo.getMaterialsProducerId()); + assertTrue(batchConstructionInfo.getPropertyValues().isEmpty()); + + // precondition test : if the material id was not set + ContractException contractException = assertThrows(ContractException.class, () -> // + BatchConstructionInfo.builder()// + .setMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_1)// + .build());// + + assertEquals(MaterialsError.NULL_MATERIAL_ID, contractException.getErrorType()); + + // precondition test: if the materials producer id was not set + contractException = assertThrows(ContractException.class, () -> // + BatchConstructionInfo.builder()// + .setMaterialId(TestMaterialId.MATERIAL_1)// + .build());// + + assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + } + + // 2143058467392057240L + // + // 4485728390035031270L + @Test + @UnitTestMethod(target = BatchConstructionInfo.Builder.class, name = "setAmount", args = { double.class }) + public void testSetAmount() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3990739257871475353L); + + for (int i = 0; i < 10; i++) { + double amount = 1000 * i; + BatchConstructionInfo batchConstructionInfo = BatchConstructionInfo.builder()// + .setMaterialsProducerId(TestMaterialsProducerId.getRandomMaterialsProducerId(randomGenerator))// + .setMaterialId(TestMaterialId.getRandomMaterialId(randomGenerator)) // + .setAmount(amount)// + .build();// + assertEquals(amount, batchConstructionInfo.getAmount()); + } + + // precondition test: if the amount is negative + + ContractException contractException = assertThrows(ContractException.class, // + () -> BatchConstructionInfo.builder()// + .setMaterialsProducerId(TestMaterialsProducerId.getRandomMaterialsProducerId(randomGenerator))// + .setMaterialId(TestMaterialId.getRandomMaterialId(randomGenerator)) // + .setAmount(-1.0)// + .build()// + ); + assertEquals(MaterialsError.NEGATIVE_MATERIAL_AMOUNT, contractException.getErrorType()); + + // if the amount is not finite + + contractException = assertThrows(ContractException.class, // + () -> BatchConstructionInfo.builder()// + .setMaterialsProducerId(TestMaterialsProducerId.getRandomMaterialsProducerId(randomGenerator))// + .setMaterialId(TestMaterialId.getRandomMaterialId(randomGenerator)) // + .setAmount(Double.POSITIVE_INFINITY)// + .build()// + ); + assertEquals(MaterialsError.NON_FINITE_MATERIAL_AMOUNT, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, // + () -> BatchConstructionInfo.builder()// + .setMaterialsProducerId(TestMaterialsProducerId.getRandomMaterialsProducerId(randomGenerator))// + .setMaterialId(TestMaterialId.getRandomMaterialId(randomGenerator)) // + .setAmount(Double.NaN)// + .build()// + ); + assertEquals(MaterialsError.NON_FINITE_MATERIAL_AMOUNT, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = BatchConstructionInfo.Builder.class, name = "setMaterialId", args = { MaterialId.class }) + public void testSetMaterialId() { + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + BatchConstructionInfo batchConstructionInfo = BatchConstructionInfo.builder()// + .setMaterialId(testMaterialId)// + .setMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_1).build();// + assertEquals(testMaterialId, batchConstructionInfo.getMaterialId()); + + } + + // precondition test: if the material id is null + ContractException contractException = assertThrows(ContractException.class, () -> // + BatchConstructionInfo.builder()// + .setMaterialId(null)// + .setMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_1).build()// + ); + assertEquals(MaterialsError.NULL_MATERIAL_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = BatchConstructionInfo.class,name = "getMaterialsProducerId", args = {}) + public void testGetMaterialsProdcuerId() { + BatchConstructionInfo.Builder builder = BatchConstructionInfo.builder(); + MaterialId materialId = TestMaterialId.MATERIAL_2; + MaterialsProducerId producerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; + + builder.setMaterialId(materialId).setMaterialsProducerId(producerId); + + BatchConstructionInfo info = builder.build(); + assertNotNull(info); + + assertEquals(producerId, info.getMaterialsProducerId()); + } + + @Test + @UnitTestMethod(target = BatchConstructionInfo.Builder.class, name = "setMaterialsProducerId", args = { + MaterialsProducerId.class }) + public void testSetMaterialsProducerId() { + BatchConstructionInfo.Builder builder = BatchConstructionInfo.builder(); + MaterialId materialId = TestMaterialId.MATERIAL_1; + MaterialsProducerId producerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; + + builder.setMaterialId(materialId).setMaterialsProducerId(producerId); + + BatchConstructionInfo info = builder.build(); + assertNotNull(info); + + assertEquals(producerId, info.getMaterialsProducerId()); + + // precondition: null materials producer id + ContractException contractException = assertThrows(ContractException.class, () -> // + BatchConstructionInfo.builder()// + .setMaterialsProducerId(null)// + ); + assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = BatchConstructionInfo.Builder.class, name = "setPropertyValue", args = { + BatchPropertyId.class, Object.class }) + public void testSetPropertyValue() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1174771995707697849L); + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + BatchConstructionInfo.Builder builder = BatchConstructionInfo.builder();// + builder.setMaterialId(testMaterialId).setMaterialsProducerId(testMaterialsProducerId);// + Map expectedPropertyValues = new LinkedHashMap<>(); + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId + .getTestBatchPropertyIds(testMaterialId)) { + Object value = testBatchPropertyId.getRandomPropertyValue(randomGenerator); + expectedPropertyValues.put(testBatchPropertyId, value); + builder.setPropertyValue(testBatchPropertyId, value);// + } + BatchConstructionInfo batchConstructionInfo = builder.build();// + assertEquals(testMaterialId, batchConstructionInfo.getMaterialId()); + + Map propertyValues = batchConstructionInfo.getPropertyValues(); + assertEquals(expectedPropertyValues, propertyValues); + } + } + + // precondition test: if the property id is null + ContractException contractException = assertThrows(ContractException.class, + () -> BatchConstructionInfo.builder().setPropertyValue(null, 15)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // precondition test: if the property value is null + contractException = assertThrows(ContractException.class, () -> BatchConstructionInfo.builder() + .setPropertyValue(TestBatchPropertyId.BATCH_PROPERTY_1_1_BOOLEAN_IMMUTABLE_NO_TRACK, null)); + assertEquals(PropertyError.NULL_PROPERTY_VALUE, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = BatchConstructionInfo.class,name = "getMaterialId", args = {}) + public void testGetMaterialId() { + + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + BatchConstructionInfo batchConstructionInfo = BatchConstructionInfo.builder()// + .setMaterialId(TestMaterialId.MATERIAL_3)// + .setMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_1)// + .setMaterialId(testMaterialId)// + .build();// + assertEquals(testMaterialId, batchConstructionInfo.getMaterialId()); + } + } + + @Test + @UnitTestMethod(target = BatchConstructionInfo.class,name = "getAmount", args = {}) + public void testGetAmount() { + for (int i = 0; i < 10; i++) { + double amount = 1000 * i; + BatchConstructionInfo batchConstructionInfo = BatchConstructionInfo.builder()// + .setMaterialId(TestMaterialId.MATERIAL_1)// + .setMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_2).setAmount(amount)// + .build();// + assertEquals(amount, batchConstructionInfo.getAmount()); + } + } + + @Test + @UnitTestMethod(target = BatchConstructionInfo.class,name = "getPropertyValues", args = {}) + public void testGetPropertyValues() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1805920219436314340L); + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + BatchConstructionInfo.Builder builder = BatchConstructionInfo.builder();// + builder.setMaterialsProducerId(testMaterialsProducerId).setMaterialId(testMaterialId);// + + Map expectedPropertyValues = new LinkedHashMap<>(); + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId + .getTestBatchPropertyIds(testMaterialId)) { + Object value = testBatchPropertyId.getRandomPropertyValue(randomGenerator); + expectedPropertyValues.put(testBatchPropertyId, value); + builder.setPropertyValue(testBatchPropertyId, value);// + } + BatchConstructionInfo batchConstructionInfo = builder.build();// + assertEquals(testMaterialId, batchConstructionInfo.getMaterialId()); + + Map propertyValues = batchConstructionInfo.getPropertyValues(); + assertEquals(expectedPropertyValues, propertyValues); + } + } + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/AT_BatchId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/AT_BatchId.java new file mode 100644 index 000000000..098817d1c --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/AT_BatchId.java @@ -0,0 +1,85 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_BatchId { + + @Test + @UnitTestConstructor(target = BatchId.class,args = { int.class }) + public void testConstructor() { + for (int i = 0; i < 10; i++) { + BatchId BatchId = new BatchId(i); + assertEquals(i, BatchId.getValue()); + } + + } + + @Test + @UnitTestMethod(target = BatchId.class,name = "compareTo", args = { BatchId.class }) + public void testCompareTo() { + for (int i = 0; i < 10; i++) { + BatchId batchA = new BatchId(i); + for (int j = 0; j < 10; j++) { + BatchId batchB = new BatchId(j); + int comparisonValue = batchA.compareTo(batchB); + if (i < j) { + assertTrue(comparisonValue < 0); + } else if (i > j) { + assertTrue(comparisonValue > 0); + } else { + assertTrue(comparisonValue == 0); + } + } + } + } + + @Test + @UnitTestMethod(target = BatchId.class,name = "equals", args = { Object.class }) + public void testEquals() { + for (int i = 0; i < 10; i++) { + BatchId batchA = new BatchId(i); + for (int j = 0; j < 10; j++) { + BatchId batchB = new BatchId(j); + if (i == j) { + assertEquals(batchA,batchB); + } else { + assertNotEquals(batchA,batchB); + } + } + } + } + + @Test + @UnitTestMethod(target = BatchId.class,name = "getValue", args = {}) + public void testGetValue() { + for (int i = 0; i < 10; i++) { + BatchId batch = new BatchId(i); + assertEquals(i, batch.getValue()); + } + } + + @Test + @UnitTestMethod(target = BatchId.class,name = "hashCode", args = {}) + public void testHashCode() { + for (int i = 0; i < 10; i++) { + BatchId batch = new BatchId(i); + assertEquals(i, batch.hashCode()); + } + } + + @Test + @UnitTestMethod(target = BatchId.class,name = "toString", args = {}) + public void testToString() { + for (int i = 0; i < 10; i++) { + BatchId batch = new BatchId(i); + assertEquals(Integer.toString(i), batch.toString()); + } + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/AT_BatchPropertyDefinitionInitialization.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/AT_BatchPropertyDefinitionInitialization.java new file mode 100644 index 000000000..310f89e8f --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/AT_BatchPropertyDefinitionInitialization.java @@ -0,0 +1,263 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.math3.random.RandomGenerator; +import org.apache.commons.math3.util.Pair; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.TestBatchPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.TestMaterialId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_BatchPropertyDefinitionInitialization { + @Test + @UnitTestMethod(target = BatchPropertyDefinitionInitialization.class, name = "builder", args = {}) + public void testBuilder() { + BatchPropertyDefinitionInitialization.Builder builder = BatchPropertyDefinitionInitialization.builder(); + assertNotNull(builder); + } + + @Test + @UnitTestMethod(target = BatchPropertyDefinitionInitialization.class, name = "getPropertyDefinition", args = {}) + public void testGetPropertyDefinition() { + PropertyDefinition propertyDefinition = PropertyDefinition .builder().setDefaultValue(100).setPropertyValueMutability(false) + .setType(Integer.class).build(); + + BatchPropertyId batchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK; + MaterialId materialId = TestMaterialId.MATERIAL_1; + + BatchPropertyDefinitionInitialization.Builder builder = BatchPropertyDefinitionInitialization .builder().setMaterialId(materialId).setPropertyId(batchPropertyId) + .setPropertyDefinition(propertyDefinition); + + BatchPropertyDefinitionInitialization definitionInitialization = builder.build(); + + assertNotNull(definitionInitialization); + assertEquals(propertyDefinition, definitionInitialization.getPropertyDefinition()); + } + + @Test + @UnitTestMethod(target = BatchPropertyDefinitionInitialization.class, name = "getPropertyId", args = {}) + public void testGetPropertyId() { + PropertyDefinition propertyDefinition = PropertyDefinition .builder().setDefaultValue(100).setPropertyValueMutability(false) + .setType(Integer.class).build(); + + BatchPropertyId batchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK; + MaterialId materialId = TestMaterialId.MATERIAL_1; + + BatchPropertyDefinitionInitialization.Builder builder = BatchPropertyDefinitionInitialization .builder().setMaterialId(materialId).setPropertyId(batchPropertyId) + .setPropertyDefinition(propertyDefinition); + + BatchPropertyDefinitionInitialization definitionInitialization = builder.build(); + + assertNotNull(definitionInitialization); + assertEquals(batchPropertyId, definitionInitialization.getPropertyId()); + } + + @Test + @UnitTestMethod(target = BatchPropertyDefinitionInitialization.class, name = "getPropertyValues", args = {}) + public void testGetPropertyValues() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8487271708488687492L); + PropertyDefinition propertyDefinition = PropertyDefinition .builder().setDefaultValue("100").setPropertyValueMutability(false) + .setType(String.class).build(); + + BatchPropertyId batchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK; + MaterialId materialId = TestMaterialId.MATERIAL_1; + + BatchPropertyDefinitionInitialization.Builder builder = BatchPropertyDefinitionInitialization .builder().setMaterialId(materialId).setPropertyId(batchPropertyId) + .setPropertyDefinition(propertyDefinition); + + List> expectedValues = new ArrayList<>(); + for (int i = 0; i < 15; i++) { + String value = Integer.toString(randomGenerator.nextInt(100)); + BatchId batchId = new BatchId(i); + builder.addPropertyValue(batchId, value); + expectedValues.add(new Pair<>(batchId, value)); + } + + BatchPropertyDefinitionInitialization definitionInitialization = builder.build(); + + assertNotNull(definitionInitialization); + assertEquals(expectedValues.size(), definitionInitialization.getPropertyValues().size()); + assertEquals(expectedValues, definitionInitialization.getPropertyValues()); + + } + + @Test + @UnitTestMethod(target = BatchPropertyDefinitionInitialization.class, name = "getMaterialId", args = {}) + public void testGetMaterialId() { + PropertyDefinition propertyDefinition = PropertyDefinition .builder().setDefaultValue(100).setPropertyValueMutability(false) + .setType(Integer.class).build(); + + BatchPropertyId batchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK; + MaterialId materialId = TestMaterialId.MATERIAL_1; + + BatchPropertyDefinitionInitialization.Builder builder = BatchPropertyDefinitionInitialization .builder().setMaterialId(materialId).setPropertyId(batchPropertyId) + .setPropertyDefinition(propertyDefinition); + + BatchPropertyDefinitionInitialization definitionInitialization = builder.build(); + + assertNotNull(definitionInitialization); + assertEquals(materialId, definitionInitialization.getMaterialId()); + } + + @Test + @UnitTestMethod(target = BatchPropertyDefinitionInitialization.Builder.class, name = "build", args = {}) + public void testBuild() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8487271708488687492L); + PropertyDefinition propertyDefinition = PropertyDefinition .builder().setDefaultValue("100").setPropertyValueMutability(false) + .setType(String.class).build(); + + BatchPropertyId batchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK; + MaterialId materialId = TestMaterialId.MATERIAL_1; + + BatchPropertyDefinitionInitialization.Builder builder = BatchPropertyDefinitionInitialization .builder().setMaterialId(materialId).setPropertyId(batchPropertyId) + .setPropertyDefinition(propertyDefinition); + + List> expectedValues = new ArrayList<>(); + for (int i = 0; i < 15; i++) { + String value = Integer.toString(randomGenerator.nextInt(100)); + BatchId batchId = new BatchId(i); + builder.addPropertyValue(batchId, value); + expectedValues.add(new Pair<>(batchId, value)); + } + + BatchPropertyDefinitionInitialization definitionInitialization = builder.build(); + + assertNotNull(definitionInitialization); + + // precondition: null property definition + PropertyDefinition nPropertyDefinition = null; + ContractException contractException = assertThrows(ContractException.class, + () -> BatchPropertyDefinitionInitialization.builder().setMaterialId(materialId).setPropertyId(batchPropertyId).setPropertyDefinition(nPropertyDefinition).build()); + assertEquals(PropertyError.NULL_PROPERTY_DEFINITION, contractException.getErrorType()); + + // precondition: null property id + BatchPropertyId nBatchPropertyId = null; + contractException = assertThrows(ContractException.class, + () -> BatchPropertyDefinitionInitialization.builder().setMaterialId(materialId).setPropertyId(nBatchPropertyId).setPropertyDefinition(propertyDefinition).build()); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // precondition: null material id + MaterialId nMaterialId = null; + contractException = assertThrows(ContractException.class, + () -> BatchPropertyDefinitionInitialization.builder().setMaterialId(nMaterialId).setPropertyId(batchPropertyId).setPropertyDefinition(propertyDefinition).build()); + assertEquals(MaterialsError.NULL_MATERIAL_ID, contractException.getErrorType()); + + // precondition: incomaptible value + contractException = assertThrows(ContractException.class, + () -> BatchPropertyDefinitionInitialization .builder().setMaterialId(materialId).setPropertyId(batchPropertyId).setPropertyDefinition(propertyDefinition) + .addPropertyValue(new BatchId(0), 100).build()); + assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = BatchPropertyDefinitionInitialization.Builder.class, name = "setPropertyDefinition", args = { PropertyDefinition.class }) + public void testSetPropertyDefinition() { + PropertyDefinition propertyDefinition = PropertyDefinition .builder().setDefaultValue(100).setPropertyValueMutability(false) + .setType(Integer.class).build(); + + BatchPropertyId batchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK; + MaterialId materialId = TestMaterialId.MATERIAL_1; + + BatchPropertyDefinitionInitialization definitionInitialization = BatchPropertyDefinitionInitialization .builder().setMaterialId(materialId).setPropertyId(batchPropertyId) + .setPropertyDefinition(propertyDefinition).build(); + + assertNotNull(definitionInitialization); + assertEquals(propertyDefinition, definitionInitialization.getPropertyDefinition()); + + // precondition: null property definition + ContractException contractException = assertThrows(ContractException.class, () -> BatchPropertyDefinitionInitialization.builder().setPropertyDefinition(null)); + assertEquals(PropertyError.NULL_PROPERTY_DEFINITION, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = BatchPropertyDefinitionInitialization.Builder.class, name = "setPropertyId", args = { BatchPropertyId.class }) + public void testSetPropertyId() { + PropertyDefinition propertyDefinition = PropertyDefinition .builder().setDefaultValue(100).setPropertyValueMutability(false) + .setType(Integer.class).build(); + + BatchPropertyId batchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK; + MaterialId materialId = TestMaterialId.MATERIAL_1; + + BatchPropertyDefinitionInitialization.Builder builder = BatchPropertyDefinitionInitialization .builder().setMaterialId(materialId).setPropertyId(batchPropertyId) + .setPropertyDefinition(propertyDefinition); + + BatchPropertyDefinitionInitialization definitionInitialization = builder.build(); + + assertNotNull(definitionInitialization); + assertEquals(batchPropertyId, definitionInitialization.getPropertyId()); + + // precondition: null property id + ContractException contractException = assertThrows(ContractException.class, () -> BatchPropertyDefinitionInitialization.builder().setPropertyId(null)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = BatchPropertyDefinitionInitialization.Builder.class, name = "addPropertyValue", args = { BatchId.class, Object.class }) + public void testAddPropertyValue() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8487271708488687492L); + PropertyDefinition propertyDefinition = PropertyDefinition .builder().setDefaultValue("100").setPropertyValueMutability(false) + .setType(String.class).build(); + + BatchPropertyId batchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK; + MaterialId materialId = TestMaterialId.MATERIAL_1; + + BatchPropertyDefinitionInitialization.Builder builder = BatchPropertyDefinitionInitialization .builder().setMaterialId(materialId).setPropertyId(batchPropertyId) + .setPropertyDefinition(propertyDefinition); + + List> expectedValues = new ArrayList<>(); + for (int i = 0; i < 15; i++) { + String value = Integer.toString(randomGenerator.nextInt(100)); + BatchId batchId = new BatchId(i); + builder.addPropertyValue(batchId, value); + expectedValues.add(new Pair<>(batchId, value)); + } + + BatchPropertyDefinitionInitialization definitionInitialization = builder.build(); + + assertNotNull(definitionInitialization); + assertEquals(expectedValues.size(), definitionInitialization.getPropertyValues().size()); + assertEquals(expectedValues, definitionInitialization.getPropertyValues()); + + // precondition: null batch id + ContractException contractException = assertThrows(ContractException.class, () -> BatchPropertyDefinitionInitialization.builder().addPropertyValue(null, "100")); + assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); + + // precondition: null value + contractException = assertThrows(ContractException.class, () -> BatchPropertyDefinitionInitialization.builder().addPropertyValue(new BatchId(0), null)); + assertEquals(PropertyError.NULL_PROPERTY_VALUE, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = BatchPropertyDefinitionInitialization.Builder.class, name = "setMaterialId", args = { MaterialId.class }) + public void testSetMaterialId() { + PropertyDefinition propertyDefinition = PropertyDefinition .builder().setDefaultValue(100).setPropertyValueMutability(false) + .setType(Integer.class).build(); + + BatchPropertyId batchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK; + MaterialId materialId = TestMaterialId.MATERIAL_1; + + BatchPropertyDefinitionInitialization.Builder builder = BatchPropertyDefinitionInitialization .builder().setMaterialId(materialId).setPropertyId(batchPropertyId) + .setPropertyDefinition(propertyDefinition); + + BatchPropertyDefinitionInitialization definitionInitialization = builder.build(); + + assertNotNull(definitionInitialization); + assertEquals(materialId, definitionInitialization.getMaterialId()); + + // precondition: null material id + ContractException contractException = assertThrows(ContractException.class, () -> BatchPropertyDefinitionInitialization.builder().setMaterialId(null)); + assertEquals(MaterialsError.NULL_MATERIAL_ID, contractException.getErrorType()); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/AT_MaterialsError.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/AT_MaterialsError.java new file mode 100644 index 000000000..1cd1cade8 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/AT_MaterialsError.java @@ -0,0 +1,28 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.support; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestMethod; + +public class AT_MaterialsError { + + @Test + @UnitTestMethod(target = MaterialsError.class, name = "getDescription", args = {}) + public void test() { + // show that each description is a unique, non-null and non-empty string + Set descriptions = new LinkedHashSet<>(); + for (MaterialsError materialsError : MaterialsError.values()) { + String description = materialsError.getDescription(); + assertNotNull(description, "null description for " + materialsError); + assertTrue(description.length() > 0, "empty string for " + materialsError); + boolean unique = descriptions.add(description); + assertTrue(unique, "description for " + materialsError + " is not unique"); + } + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/AT_MaterialsProducerConstructionData.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/AT_MaterialsProducerConstructionData.java new file mode 100644 index 000000000..1df3f384d --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/AT_MaterialsProducerConstructionData.java @@ -0,0 +1,279 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.TestMaterialsProducerId; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.TestMaterialsProducerPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceError; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.TestResourceId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_MaterialsProducerConstructionData { + + @Test + @UnitTestMethod(target = MaterialsProducerConstructionData.class,name = "builder", args = {}) + public void testBuilder() { + MaterialsProducerConstructionData.Builder builder = MaterialsProducerConstructionData.builder(); + + assertNotNull(builder); + } + + @Test + @UnitTestMethod(target = MaterialsProducerConstructionData.class,name = "getValues", args = { Class.class }) + public void testGetValues() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7180465772129297639L); + MaterialsProducerConstructionData.Builder builder = MaterialsProducerConstructionData.builder(); + builder.setMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + + List expectedIntegers = new ArrayList<>(); + List expectedDoubles = new ArrayList<>(); + for (int i = 0; i < 20; i++) { + if (i % 2 == 0) { + int value = randomGenerator.nextInt(100); + expectedIntegers.add(value); + builder.addValue(value); + } else { + double value = randomGenerator.nextDouble() * 100; + expectedDoubles.add(value); + builder.addValue(value); + } + } + + MaterialsProducerConstructionData constructionData = builder.build(); + assertNotNull(constructionData); + + assertEquals(expectedIntegers, constructionData.getValues(Integer.class)); + assertEquals(expectedDoubles, constructionData.getValues(Double.class)); + + } + + @Test + @UnitTestMethod(target = MaterialsProducerConstructionData.class,name = "getMaterialsProducerId", args = {}) + public void testGetMaterialsProducerId() { + MaterialsProducerConstructionData.Builder builder = MaterialsProducerConstructionData.builder(); + builder.setMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + + MaterialsProducerConstructionData constructionData = builder.build(); + assertNotNull(constructionData); + assertEquals(TestMaterialsProducerId.MATERIALS_PRODUCER_1, constructionData.getMaterialsProducerId()); + } + + @Test + @UnitTestMethod(target = MaterialsProducerConstructionData.class,name = "getMaterialsProducerPropertyValues", args = {}) + public void testGetMaterialsProducerPropertyValues() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6832539036105490849L); + MaterialsProducerConstructionData.Builder builder = MaterialsProducerConstructionData.builder(); + builder.setMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + + Map expectedValues = new LinkedHashMap<>(); + + for (int i = 0; i < 15; i++) { + TestMaterialsProducerPropertyId producerPropertyId = TestMaterialsProducerPropertyId + .getRandomMaterialsProducerPropertyId(randomGenerator); + if (!expectedValues.containsKey(producerPropertyId)) { + Object value = producerPropertyId.getRandomPropertyValue(randomGenerator); + builder.setMaterialsProducerPropertyValue(producerPropertyId, value); + expectedValues.put(producerPropertyId, value); + } + } + + MaterialsProducerConstructionData constructionData = builder.build(); + assertNotNull(constructionData); + assertEquals(expectedValues, constructionData.getMaterialsProducerPropertyValues()); + } + + @Test + @UnitTestMethod(target = MaterialsProducerConstructionData.class,name = "getResourceLevels", args = {}) + public void testGetResourceLevels() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6832539036105490849L); + MaterialsProducerConstructionData.Builder builder = MaterialsProducerConstructionData.builder(); + builder.setMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + + Map expectedValues = new LinkedHashMap<>(); + for (int i = 0; i < 15; i++) { + ResourceId resourceId = TestResourceId.getRandomResourceId(randomGenerator); + if (!expectedValues.containsKey(resourceId)) { + long value = randomGenerator.nextInt(100 * (i + 1)) + 1; + builder.setResourceLevel(resourceId, value); + expectedValues.put(resourceId, value); + } + } + + MaterialsProducerConstructionData constructionData = builder.build(); + assertNotNull(constructionData); + assertEquals(expectedValues, constructionData.getResourceLevels()); + + } + + @Test + @UnitTestMethod(target = MaterialsProducerConstructionData.Builder.class, name = "build", args = {}) + public void testBuild() { + MaterialsProducerConstructionData.Builder builder = MaterialsProducerConstructionData.builder(); + builder.setMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + + MaterialsProducerConstructionData constructionData = builder.build(); + assertNotNull(constructionData); + + // precondition: materials producer id is null + ContractException contractException = assertThrows(ContractException.class, + () -> MaterialsProducerConstructionData.builder().build()); + assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = MaterialsProducerConstructionData.Builder.class, name = "addValue", args = { + Object.class }) + public void testAddValue() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8237136898094031982L); + MaterialsProducerConstructionData.Builder builder = MaterialsProducerConstructionData.builder(); + builder.setMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + + List expectedIntegers = new ArrayList<>(); + List expectedDoubles = new ArrayList<>(); + for (int i = 0; i < 20; i++) { + if (i % 2 == 0) { + int value = randomGenerator.nextInt(100); + expectedIntegers.add(value); + builder.addValue(value); + } else { + double value = randomGenerator.nextDouble() * 100; + expectedDoubles.add(value); + builder.addValue(value); + } + } + + MaterialsProducerConstructionData constructionData = builder.build(); + assertNotNull(constructionData); + + assertEquals(expectedIntegers, constructionData.getValues(Integer.class)); + assertEquals(expectedDoubles, constructionData.getValues(Double.class)); + + // precondition: value is null + ContractException contractException = assertThrows(ContractException.class, + () -> MaterialsProducerConstructionData.builder().addValue(null)); + assertEquals(MaterialsError.NULL_AUXILIARY_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = MaterialsProducerConstructionData.Builder.class, name = "setMaterialsProducerPropertyValue", args = { + MaterialsProducerPropertyId.class, Object.class }) + public void testSetMaterialsProducerPropertyValue() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8800503605965150018L); + MaterialsProducerConstructionData.Builder builder = MaterialsProducerConstructionData.builder(); + builder.setMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + + Map expectedValues = new LinkedHashMap<>(); + + for (int i = 0; i < 15; i++) { + TestMaterialsProducerPropertyId producerPropertyId = TestMaterialsProducerPropertyId + .getRandomMaterialsProducerPropertyId(randomGenerator); + if (!expectedValues.containsKey(producerPropertyId)) { + Object value = producerPropertyId.getRandomPropertyValue(randomGenerator); + builder.setMaterialsProducerPropertyValue(producerPropertyId, value); + expectedValues.put(producerPropertyId, value); + } + } + + MaterialsProducerConstructionData constructionData = builder.build(); + assertNotNull(constructionData); + assertEquals(expectedValues, constructionData.getMaterialsProducerPropertyValues()); + + // precondition: materials producer property id is null + ContractException contractException = assertThrows(ContractException.class, + () -> MaterialsProducerConstructionData.builder() + .setMaterialsProducerPropertyValue(null, "100")); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // precondition: value is null + contractException = assertThrows(ContractException.class, + () -> MaterialsProducerConstructionData.builder() + .setMaterialsProducerPropertyValue( + TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, + null)); + assertEquals(PropertyError.NULL_PROPERTY_VALUE, contractException.getErrorType()); + + // precondition: duplicate producer property id + contractException = assertThrows(ContractException.class, + () -> MaterialsProducerConstructionData.builder() + .setMaterialsProducerPropertyValue( + TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, + false) + .setMaterialsProducerPropertyValue( + TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, + true)); + assertEquals(PropertyError.DUPLICATE_PROPERTY_VALUE_ASSIGNMENT, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = MaterialsProducerConstructionData.Builder.class, name = "setMaterialsProducerId", args = { + MaterialsProducerId.class }) + public void testSetMaterialsProducerId() { + MaterialsProducerConstructionData.Builder builder = MaterialsProducerConstructionData.builder(); + builder.setMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + + MaterialsProducerConstructionData constructionData = builder.build(); + assertNotNull(constructionData); + assertEquals(TestMaterialsProducerId.MATERIALS_PRODUCER_1, constructionData.getMaterialsProducerId()); + + // precondition: materials producer id is null + ContractException contractException = assertThrows(ContractException.class, + () -> MaterialsProducerConstructionData.builder().setMaterialsProducerId(null)); + assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = MaterialsProducerConstructionData.Builder.class, name = "setResourceLevel", args = { + ResourceId.class, long.class }) + public void testSetResourceLevel() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6090230296084756769L); + MaterialsProducerConstructionData.Builder builder = MaterialsProducerConstructionData.builder(); + builder.setMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_1); + + Map expectedValues = new LinkedHashMap<>(); + for (int i = 0; i < 15; i++) { + ResourceId resourceId = TestResourceId.getRandomResourceId(randomGenerator); + if (!expectedValues.containsKey(resourceId)) { + long value = randomGenerator.nextInt(100 * (i + 1)) + 1; + builder.setResourceLevel(resourceId, value); + expectedValues.put(resourceId, value); + } + } + + MaterialsProducerConstructionData constructionData = builder.build(); + assertNotNull(constructionData); + assertEquals(expectedValues, constructionData.getResourceLevels()); + + // precondition: resource id is null + ContractException contractException = assertThrows(ContractException.class, + () -> MaterialsProducerConstructionData.builder() + .setResourceLevel(null, 0)); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + // precondition: level is negative (< 0) + contractException = assertThrows(ContractException.class, + () -> MaterialsProducerConstructionData.builder() + .setResourceLevel(TestResourceId.RESOURCE_1, -100)); + assertEquals(ResourceError.NEGATIVE_RESOURCE_AMOUNT, contractException.getErrorType()); + + // precondition: duplicate resource id + contractException = assertThrows(ContractException.class, + () -> MaterialsProducerConstructionData.builder() + .setResourceLevel(TestResourceId.RESOURCE_1, 100) + .setResourceLevel(TestResourceId.RESOURCE_1, 100)); + assertEquals(ResourceError.DUPLICATE_REGION_RESOURCE_LEVEL_ASSIGNMENT, contractException.getErrorType()); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/AT_MaterialsProducerPropertyDefinitionInitialization.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/AT_MaterialsProducerPropertyDefinitionInitialization.java new file mode 100644 index 000000000..11429b32d --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/AT_MaterialsProducerPropertyDefinitionInitialization.java @@ -0,0 +1,212 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.math3.random.RandomGenerator; +import org.apache.commons.math3.util.Pair; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.TestMaterialsProducerId; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.TestMaterialsProducerPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_MaterialsProducerPropertyDefinitionInitialization { + + @Test + @UnitTestMethod(target = MaterialsProducerPropertyDefinitionInitialization.class, name = "builder", args = {}) + public void testBuilder() { + MaterialsProducerPropertyDefinitionInitialization.Builder builder = MaterialsProducerPropertyDefinitionInitialization.builder(); + assertNotNull(builder); + } + + @Test + @UnitTestMethod(target = MaterialsProducerPropertyDefinitionInitialization.class, name = "getPropertyDefinition", args = {}) + public void testGetPropertyDefinition() { + MaterialsProducerPropertyDefinitionInitialization.Builder builder = MaterialsProducerPropertyDefinitionInitialization.builder(); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setDefaultValue(100).setPropertyValueMutability(false).setType(Integer.class).build(); + MaterialsProducerPropertyId producerPropertyId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_8_INTEGER_IMMUTABLE_NO_TRACK; + + builder.setPropertyDefinition(propertyDefinition).setMaterialsProducerPropertyId(producerPropertyId); + + MaterialsProducerPropertyDefinitionInitialization definitionInitialization = builder.build(); + + assertNotNull(definitionInitialization); + assertEquals(propertyDefinition, definitionInitialization.getPropertyDefinition()); + + } + + @Test + @UnitTestMethod(target = MaterialsProducerPropertyDefinitionInitialization.class, name = "getPropertyValues", args = {}) + public void testGetPropertyValues() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7180465772129297639L); + MaterialsProducerPropertyDefinitionInitialization.Builder builder = MaterialsProducerPropertyDefinitionInitialization.builder(); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setDefaultValue(100).setPropertyValueMutability(false).setType(Integer.class).build(); + MaterialsProducerPropertyId producerPropertyId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_8_INTEGER_IMMUTABLE_NO_TRACK; + + builder.setPropertyDefinition(propertyDefinition).setMaterialsProducerPropertyId(producerPropertyId); + + List> expectedValues = new ArrayList<>(); + for (int i = 0; i < 15; i++) { + int value = randomGenerator.nextInt(100); + MaterialsProducerId producerId = TestMaterialsProducerId.getRandomMaterialsProducerId(randomGenerator); + expectedValues.add(new Pair<>(producerId, value)); + builder.addPropertyValue(producerId, value); + } + + MaterialsProducerPropertyDefinitionInitialization definitionInitialization = builder.build(); + List> actualValues = definitionInitialization.getPropertyValues(); + assertNotNull(definitionInitialization); + assertEquals(expectedValues.size(), actualValues.size()); + assertEquals(expectedValues, actualValues); + } + + @Test + @UnitTestMethod(target = MaterialsProducerPropertyDefinitionInitialization.class, name = "getMaterialsProducerPropertyId", args = {}) + public void testGetMaterialsProducerPropertyId() { + MaterialsProducerPropertyDefinitionInitialization.Builder builder = MaterialsProducerPropertyDefinitionInitialization.builder(); + PropertyDefinition propertyDefinition = PropertyDefinition .builder().setDefaultValue(100)// + .setPropertyValueMutability(false)// + .setType(Integer.class)// + .build(); + MaterialsProducerPropertyId producerPropertyId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_8_INTEGER_IMMUTABLE_NO_TRACK; + + builder.setPropertyDefinition(propertyDefinition).setMaterialsProducerPropertyId(producerPropertyId); + + MaterialsProducerPropertyDefinitionInitialization definitionInitialization = builder.build(); + + assertNotNull(definitionInitialization); + assertEquals(producerPropertyId, definitionInitialization.getMaterialsProducerPropertyId()); + } + + @Test + @UnitTestMethod(target = MaterialsProducerPropertyDefinitionInitialization.Builder.class, name = "build", args = {}) + public void testBuild() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6087174477323266390L); + MaterialsProducerPropertyDefinitionInitialization.Builder builder = MaterialsProducerPropertyDefinitionInitialization.builder(); + PropertyDefinition propertyDefinition = PropertyDefinition .builder()// + .setDefaultValue(100)// + .setPropertyValueMutability(false)// + .setType(Integer.class).build(); + MaterialsProducerPropertyId producerPropertyId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_8_INTEGER_IMMUTABLE_NO_TRACK; + + builder.setPropertyDefinition(propertyDefinition).setMaterialsProducerPropertyId(producerPropertyId); + + List> expectedValues = new ArrayList<>(); + for (int i = 0; i < 15; i++) { + int value = randomGenerator.nextInt(100); + MaterialsProducerId producerId = TestMaterialsProducerId.getRandomMaterialsProducerId(randomGenerator); + expectedValues.add(new Pair<>(producerId, value)); + builder.addPropertyValue(producerId, value); + } + + MaterialsProducerPropertyDefinitionInitialization definitionInitialization = builder.build(); + + assertNotNull(definitionInitialization); + + // precondition: propertyDefinition is null + ContractException contractException = assertThrows(ContractException.class, + () -> MaterialsProducerPropertyDefinitionInitialization.builder().setMaterialsProducerPropertyId(producerPropertyId).build()); + assertEquals(PropertyError.NULL_PROPERTY_DEFINITION, contractException.getErrorType()); + + // precondition: producer property id is null + contractException = assertThrows(ContractException.class, () -> MaterialsProducerPropertyDefinitionInitialization.builder().setPropertyDefinition(propertyDefinition).build()); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // precondition: incompatible value + contractException = assertThrows(ContractException.class, + () -> MaterialsProducerPropertyDefinitionInitialization .builder().setMaterialsProducerPropertyId(producerPropertyId).setPropertyDefinition(propertyDefinition) + .addPropertyValue(TestMaterialsProducerId.getRandomMaterialsProducerId(randomGenerator), "100").build()); + assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = MaterialsProducerPropertyDefinitionInitialization.Builder.class, name = "setPropertyDefinition", args = { PropertyDefinition.class }) + public void testSetPropertyDefinition() { + MaterialsProducerPropertyDefinitionInitialization.Builder builder = MaterialsProducerPropertyDefinitionInitialization.builder(); + PropertyDefinition propertyDefinition = PropertyDefinition .builder()// + .setDefaultValue(100)// + .setPropertyValueMutability(false)// + .setType(Integer.class)// + .build(); + MaterialsProducerPropertyId producerPropertyId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_8_INTEGER_IMMUTABLE_NO_TRACK; + + builder.setPropertyDefinition(propertyDefinition).setMaterialsProducerPropertyId(producerPropertyId); + + MaterialsProducerPropertyDefinitionInitialization definitionInitialization = builder.build(); + + assertNotNull(definitionInitialization); + assertEquals(propertyDefinition, definitionInitialization.getPropertyDefinition()); + + // precondition: propertyDefinition is null + ContractException contractException = assertThrows(ContractException.class, () -> MaterialsProducerPropertyDefinitionInitialization.builder().setPropertyDefinition(null)); + assertEquals(PropertyError.NULL_PROPERTY_DEFINITION, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = MaterialsProducerPropertyDefinitionInitialization.Builder.class, name = "addPropertyValue", args = { MaterialsProducerId.class, Object.class }) + public void testAddPropertyValue() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3968585809330067411L); + MaterialsProducerPropertyDefinitionInitialization.Builder builder = MaterialsProducerPropertyDefinitionInitialization.builder(); + PropertyDefinition propertyDefinition = PropertyDefinition .builder()// + .setDefaultValue(100)// + .setPropertyValueMutability(false)// + .setType(Integer.class).build(); + MaterialsProducerPropertyId producerPropertyId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_8_INTEGER_IMMUTABLE_NO_TRACK; + + builder.setPropertyDefinition(propertyDefinition).setMaterialsProducerPropertyId(producerPropertyId); + + List> expectedValues = new ArrayList<>(); + for (int i = 0; i < 15; i++) { + int value = randomGenerator.nextInt(100); + MaterialsProducerId producerId = TestMaterialsProducerId.getRandomMaterialsProducerId(randomGenerator); + expectedValues.add(new Pair<>(producerId, value)); + builder.addPropertyValue(producerId, value); + } + + MaterialsProducerPropertyDefinitionInitialization definitionInitialization = builder.build(); + List> actualValues = definitionInitialization.getPropertyValues(); + assertNotNull(definitionInitialization); + assertEquals(expectedValues.size(), actualValues.size()); + assertEquals(expectedValues, actualValues); + + // precondition: producer id is null + ContractException contractException = assertThrows(ContractException.class, () -> MaterialsProducerPropertyDefinitionInitialization.builder().addPropertyValue(null, 100)); + assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + // precondition: value is null + contractException = assertThrows(ContractException.class, + () -> MaterialsProducerPropertyDefinitionInitialization.builder().addPropertyValue(TestMaterialsProducerId.MATERIALS_PRODUCER_1, null)); + assertEquals(PropertyError.NULL_PROPERTY_VALUE, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = MaterialsProducerPropertyDefinitionInitialization.Builder.class, name = "setMaterialsProducerPropertyId", args = { MaterialsProducerPropertyId.class }) + public void testSetMaterialsProducerPropertyId() { + MaterialsProducerPropertyDefinitionInitialization.Builder builder = MaterialsProducerPropertyDefinitionInitialization.builder(); + PropertyDefinition propertyDefinition = PropertyDefinition .builder()// + .setDefaultValue(100)// + .setPropertyValueMutability(false)// + .setType(Integer.class).build(); + MaterialsProducerPropertyId producerPropertyId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_8_INTEGER_IMMUTABLE_NO_TRACK; + + builder.setPropertyDefinition(propertyDefinition).setMaterialsProducerPropertyId(producerPropertyId); + + MaterialsProducerPropertyDefinitionInitialization definitionInitialization = builder.build(); + + assertNotNull(definitionInitialization); + assertEquals(producerPropertyId, definitionInitialization.getMaterialsProducerPropertyId()); + + // precondition: producer property id is null + ContractException contractException = assertThrows(ContractException.class, () -> MaterialsProducerPropertyDefinitionInitialization.builder().setMaterialsProducerPropertyId(null)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/AT_StageConversionInfo.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/AT_StageConversionInfo.java new file mode 100644 index 000000000..d11e64301 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/AT_StageConversionInfo.java @@ -0,0 +1,248 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.TestBatchPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.TestMaterialId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_StageConversionInfo { + @Test + @UnitTestMethod(target = StageConversionInfo.class, name = "builder", args = {}) + public void testBuilder() { + assertNotNull(StageConversionInfo.builder()); + } + + @Test + @UnitTestMethod(target = StageConversionInfo.class, name = "getAmount", args = {}) + public void testGetAmount() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1709194760368532797L); + for (int i = 0; i < 30; i++) { + double amount = randomGenerator.nextDouble(); + StageConversionInfo stageConversionInfo = // + StageConversionInfo.builder()// + .setAmount(amount)// + .setMaterialId(TestMaterialId.MATERIAL_1)// + .setStageId(new StageId(6))// + .build(); + + assertEquals(amount, stageConversionInfo.getAmount()); + } + + } + + @Test + @UnitTestMethod(target = StageConversionInfo.class, name = "getMaterialId", args = {}) + public void testGetMaterialId() { + + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + + StageConversionInfo stageConversionInfo = // + StageConversionInfo.builder()// + .setAmount(14.0)// + .setMaterialId(testMaterialId)// + .setStageId(new StageId(6))// + .build(); + + assertEquals(testMaterialId, stageConversionInfo.getMaterialId()); + } + + } + + @Test + @UnitTestMethod(target = StageConversionInfo.class, name = "getPropertyValues", args = {}) + public void testGetPropertyValues() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(685359137261114208L); + + for (int i = 0; i < 30; i++) { + Map expectedPropertyValues = new LinkedHashMap<>(); + + StageConversionInfo.Builder builder = // + StageConversionInfo.builder()// + .setAmount(14.0)// + .setMaterialId(TestMaterialId.MATERIAL_2)// + .setStageId(new StageId(6));// + + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.values()) { + if (randomGenerator.nextBoolean()) { + Object propertyValue = testBatchPropertyId.getRandomPropertyValue(randomGenerator); + builder.setPropertyValue(testBatchPropertyId, propertyValue); + expectedPropertyValues.put(testBatchPropertyId, propertyValue); + } + } + + StageConversionInfo stageConversionInfo = builder.build(); + Map actualPropertyValues = stageConversionInfo.getPropertyValues(); + assertEquals(expectedPropertyValues, actualPropertyValues); + + } + + } + + @Test + @UnitTestMethod(target = StageConversionInfo.class, name = "getStageId", args = {}) + public void testGetStageId() { + + for (int i = 0; i < 30; i++) { + StageId stageId = new StageId(i); + StageConversionInfo stageConversionInfo = // + StageConversionInfo.builder()// + .setAmount(14.0)// + .setMaterialId(TestMaterialId.MATERIAL_2)// + .setStageId(stageId)// + .build(); + + assertEquals(stageId, stageConversionInfo.getStageId()); + } + + } + + @Test + @UnitTestMethod(target = StageConversionInfo.Builder.class, name = "setStageId", args = { StageId.class }) + public void testSetStageId() { + + for (int i = 0; i < 30; i++) { + StageId stageId = new StageId(i); + StageConversionInfo stageConversionInfo = // + StageConversionInfo.builder()// + .setAmount(14.0)// + .setMaterialId(TestMaterialId.MATERIAL_2)// + .setStageId(stageId)// + .build(); + + assertEquals(stageId, stageConversionInfo.getStageId()); + } + + // precondition tests : if the stage id is null + ContractException contractException = assertThrows(ContractException.class, + () -> StageConversionInfo.builder().setStageId(null)); + assertEquals(MaterialsError.NULL_STAGE_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = StageConversionInfo.Builder.class, name = "setMaterialId", args = { MaterialId.class }) + public void testSetMaterialId() { + + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + + StageConversionInfo stageConversionInfo = // + StageConversionInfo.builder()// + .setAmount(14.0)// + .setMaterialId(testMaterialId)// + .setStageId(new StageId(6))// + .build(); + + assertEquals(testMaterialId, stageConversionInfo.getMaterialId()); + } + + // precondition tests : if the materialF id is null + ContractException contractException = assertThrows(ContractException.class, + () -> StageConversionInfo.builder().setMaterialId(null)); + assertEquals(MaterialsError.NULL_MATERIAL_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = StageConversionInfo.Builder.class, name = "setAmount", args = { double.class }) + public void testSetAmount() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1709194760368532797L); + for (int i = 0; i < 30; i++) { + double amount = randomGenerator.nextDouble(); + StageConversionInfo stageConversionInfo = // + StageConversionInfo.builder()// + .setAmount(amount)// + .setMaterialId(TestMaterialId.MATERIAL_1)// + .setStageId(new StageId(6))// + .build(); + + assertEquals(amount, stageConversionInfo.getAmount()); + } + + // precondition tests : if the amount is negative + ContractException contractException = assertThrows(ContractException.class, + () -> StageConversionInfo.builder().setAmount(-234L)); + assertEquals(MaterialsError.NEGATIVE_MATERIAL_AMOUNT, contractException.getErrorType()); + + // precondition tests : if the amount is not finite + contractException = assertThrows(ContractException.class, + () -> StageConversionInfo.builder().setAmount(Double.NaN)); + assertEquals(MaterialsError.NON_FINITE_MATERIAL_AMOUNT, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, + () -> StageConversionInfo.builder().setAmount(Double.POSITIVE_INFINITY)); + assertEquals(MaterialsError.NON_FINITE_MATERIAL_AMOUNT, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, + () -> StageConversionInfo.builder().setAmount(Double.NEGATIVE_INFINITY)); + assertEquals(MaterialsError.NON_FINITE_MATERIAL_AMOUNT, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = StageConversionInfo.Builder.class, name = "setPropertyValue", args = { + BatchPropertyId.class, Object.class }) + public void testSetPropertyValue() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4143317260505344550L); + + for (int i = 0; i < 30; i++) { + Map expectedPropertyValues = new LinkedHashMap<>(); + + StageConversionInfo.Builder builder = // + StageConversionInfo.builder()// + .setAmount(14.0)// + .setMaterialId(TestMaterialId.MATERIAL_2)// + .setStageId(new StageId(6));// + + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.values()) { + if (randomGenerator.nextBoolean()) { + Object propertyValue = testBatchPropertyId.getRandomPropertyValue(randomGenerator); + builder.setPropertyValue(testBatchPropertyId, propertyValue); + expectedPropertyValues.put(testBatchPropertyId, propertyValue); + } + } + + StageConversionInfo stageConversionInfo = builder.build(); + Map actualPropertyValues = stageConversionInfo.getPropertyValues(); + assertEquals(expectedPropertyValues, actualPropertyValues); + + } + + // precondition tests : if the property id is null + ContractException contractException = assertThrows(ContractException.class, + () -> StageConversionInfo.builder().setPropertyValue(null, 3.6)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // precondition tests : if the property id is null + contractException = assertThrows(ContractException.class, () -> StageConversionInfo.builder() + .setPropertyValue(TestBatchPropertyId.BATCH_PROPERTY_1_1_BOOLEAN_IMMUTABLE_NO_TRACK, null)); + assertEquals(PropertyError.NULL_PROPERTY_VALUE, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = StageConversionInfo.Builder.class, name = "build", args = {}) + public void testBuild() { + + // precondition tests : if the stage id is null + ContractException contractException = assertThrows(ContractException.class, + () -> StageConversionInfo.builder().setMaterialId(TestMaterialId.MATERIAL_1).build()); + assertEquals(MaterialsError.NULL_STAGE_ID, contractException.getErrorType()); + + // precondition tests : if the material id is null + contractException = assertThrows(ContractException.class, + () -> StageConversionInfo.builder().setStageId(new StageId(0)).build()); + assertEquals(MaterialsError.NULL_MATERIAL_ID, contractException.getErrorType()); + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/AT_StageId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/AT_StageId.java new file mode 100644 index 000000000..01a2456f5 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/support/AT_StageId.java @@ -0,0 +1,86 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_StageId { + + @Test + @UnitTestConstructor(target = StageId.class, args = { int.class }) + public void testConstructor() { + for (int i = 0; i < 10; i++) { + StageId StageId = new StageId(i); + assertEquals(i, StageId.getValue()); + } + + } + + @Test + @UnitTestMethod(target = StageId.class, name = "compareTo", args = { StageId.class }) + public void testCompareTo() { + for (int i = 0; i < 10; i++) { + StageId stageA = new StageId(i); + for (int j = 0; j < 10; j++) { + StageId stageB = new StageId(j); + int comparisonValue = stageA.compareTo(stageB); + if (i < j) { + assertTrue(comparisonValue < 0); + } else if (i > j) { + assertTrue(comparisonValue > 0); + } else { + assertTrue(comparisonValue == 0); + } + } + } + } + + @Test + @UnitTestMethod(target = StageId.class, name = "equals", args = { Object.class }) + public void testEquals() { + for (int i = 0; i < 10; i++) { + StageId stageA = new StageId(i); + for (int j = 0; j < 10; j++) { + StageId stageB = new StageId(j); + if (i == j) { + assertEquals(stageA, stageB); + } else { + assertNotEquals(stageA, stageB); + } + } + } + } + + @Test + @UnitTestMethod(target = StageId.class, name = "getValue", args = {}) + public void testGetValue() { + for (int i = 0; i < 10; i++) { + StageId stage = new StageId(i); + assertEquals(i, stage.getValue()); + } + } + + @Test + @UnitTestMethod(target = StageId.class, name = "hashCode", args = {}) + public void testHashCode() { + for (int i = 0; i < 10; i++) { + StageId stage = new StageId(i); + assertEquals(i, stage.hashCode()); + } + } + + @Test + @UnitTestMethod(target = StageId.class, name = "toString", args = {}) + public void testToString() { + for (int i = 0; i < 10; i++) { + StageId stage = new StageId(i); + assertEquals(Integer.toString(i), stage.toString()); + } + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/testsupport/AT_MaterialsTestPluginFactory.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/testsupport/AT_MaterialsTestPluginFactory.java new file mode 100644 index 000000000..4f66afeb3 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/testsupport/AT_MaterialsTestPluginFactory.java @@ -0,0 +1,558 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.testsupport; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.Set; +import java.util.function.Consumer; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.TestFactoryUtil; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginId; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.materials.MaterialsPluginId; +import gov.hhs.aspr.ms.gcm.plugins.materials.datamangers.MaterialsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.materials.reports.BatchStatusReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.materials.reports.MaterialsProducerPropertyReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.materials.reports.MaterialsProducerResourceReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.materials.reports.StageReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsError; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.StageId; +import gov.hhs.aspr.ms.gcm.plugins.materials.testsupport.MaterialsTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePluginId; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeoplePluginData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonRange; +import gov.hhs.aspr.ms.gcm.plugins.regions.RegionsPluginId; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionError; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.TestRegionId; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.TestRegionPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.resources.ResourcesPluginId; +import gov.hhs.aspr.ms.gcm.plugins.resources.datamanagers.ResourcesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceError; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.TestResourceId; +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.TestResourcePropertyId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPluginId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.StochasticsError; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.testsupport.TestRandomGeneratorId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; +import util.wrappers.MutableBoolean; + +public class AT_MaterialsTestPluginFactory { + + @Test + @UnitTestMethod(target = MaterialsTestPluginFactory.class, name = "factory", args = { int.class, int.class, + int.class, long.class, Consumer.class }) + public void testFactory_Consumer() { + MutableBoolean executed = new MutableBoolean(); + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 3328026739613106739L, + c -> executed.setValue(true)); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + assertTrue(executed.getValue()); + + // precondition: consumer is null + Consumer nullConsumer = null; + ContractException contractException = assertThrows(ContractException.class, + () -> MaterialsTestPluginFactory.factory(0, 0, 0, 0, nullConsumer)); + assertEquals(NucleusError.NULL_ACTOR_CONTEXT_CONSUMER, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = MaterialsTestPluginFactory.class, name = "factory", args = { int.class, int.class, + int.class, long.class, TestPluginData.class }) + public void testFactory_TestPluginData() { + MutableBoolean executed = new MutableBoolean(); + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, c -> executed.setValue(true))); + TestPluginData testPluginData = pluginBuilder.build(); + + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 7995349318419680542L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + assertTrue(executed.getValue()); + + // precondition: testPluginData is null + TestPluginData nullTestPluginData = null; + ContractException contractException = assertThrows(ContractException.class, + () -> MaterialsTestPluginFactory.factory(0, 0, 0, 0, nullTestPluginData)); + assertEquals(NucleusError.NULL_PLUGIN_DATA, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsTestPluginFactory.Factory.class, name = "getPlugins", args = {}) + public void testGetPlugins() { + List plugins = MaterialsTestPluginFactory.factory(0, 0, 0, 0, t -> { + }).getPlugins(); + assertEquals(6, plugins.size()); + + TestFactoryUtil.checkPluginExists(plugins, MaterialsPluginId.PLUGIN_ID); + TestFactoryUtil.checkPluginExists(plugins, ResourcesPluginId.PLUGIN_ID); + TestFactoryUtil.checkPluginExists(plugins, PeoplePluginId.PLUGIN_ID); + TestFactoryUtil.checkPluginExists(plugins, RegionsPluginId.PLUGIN_ID); + TestFactoryUtil.checkPluginExists(plugins, StochasticsPluginId.PLUGIN_ID); + TestFactoryUtil.checkPluginExists(plugins, TestPluginId.PLUGIN_ID); + } + + @Test + @UnitTestMethod(target = MaterialsTestPluginFactory.Factory.class, name = "setMaterialsPluginData", args = { + MaterialsPluginData.class }) + public void testSetMaterialsPluginData() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4328791012645031581L); + MaterialsPluginData.Builder materialsBuilder = MaterialsPluginData.builder(); + + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + materialsBuilder.addMaterial(testMaterialId); + } + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + materialsBuilder.addMaterialsProducerId(testMaterialsProducerId); + } + + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .values()) { + materialsBuilder.defineMaterialsProducerProperty(testMaterialsProducerPropertyId, + testMaterialsProducerPropertyId.getPropertyDefinition()); + } + + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .getPropertiesWithoutDefaultValues()) { + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + Object randomPropertyValue = testMaterialsProducerPropertyId.getRandomPropertyValue(randomGenerator); + materialsBuilder.setMaterialsProducerPropertyValue(testMaterialsProducerId, + testMaterialsProducerPropertyId, randomPropertyValue); + } + } + + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + Set testBatchPropertyIds = TestBatchPropertyId.getTestBatchPropertyIds(testMaterialId); + for (TestBatchPropertyId testBatchPropertyId : testBatchPropertyIds) { + materialsBuilder.defineBatchProperty(testMaterialId, testBatchPropertyId, + testBatchPropertyId.getPropertyDefinition()); + } + } + + MaterialsPluginData materialsPluginData = materialsBuilder.build(); + + List plugins = MaterialsTestPluginFactory.factory(0, 0, 0, 0, t -> { + }).setMaterialsPluginData(materialsPluginData).getPlugins(); + + TestFactoryUtil.checkPluginDataExists(plugins, materialsPluginData, MaterialsPluginId.PLUGIN_ID); + + // precondition: materialsPluginData is not null + ContractException contractException = assertThrows(ContractException.class, + () -> MaterialsTestPluginFactory.factory(0, 0, 0, 0, t -> { + }).setMaterialsPluginData(null)); + assertEquals(MaterialsError.NULL_MATERIALS_PLUGIN_DATA, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = MaterialsTestPluginFactory.Factory.class, name = "setResourcesPluginData", args = { + ResourcesPluginData.class }) + public void testSetResourcesPluginData() { + ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); + + ResourcesPluginData resourcesPluginData = builder.build(); + + List plugins = MaterialsTestPluginFactory.factory(0, 0, 0, 0, t -> { + }).setResourcesPluginData(resourcesPluginData).getPlugins(); + + TestFactoryUtil.checkPluginDataExists(plugins, resourcesPluginData, ResourcesPluginId.PLUGIN_ID); + + // precondition: resourcesPluginData is not null + ContractException contractException = assertThrows(ContractException.class, + () -> MaterialsTestPluginFactory.factory(0, 0, 0, 0, t -> { + }).setResourcesPluginData(null)); + assertEquals(ResourceError.NULL_RESOURCE_PLUGIN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = MaterialsTestPluginFactory.Factory.class, name = "setRegionsPluginData", args = { + RegionsPluginData.class }) + public void testSetRegionsPluginData() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(968101337385656117L); + int initialPopulation = 30; + List people = new ArrayList<>(); + for (int i = 0; i < initialPopulation; i++) { + people.add(new PersonId(i)); + } + + // add the region plugin + RegionsPluginData.Builder regionPluginBuilder = RegionsPluginData.builder(); + for (TestRegionId regionId : TestRegionId.values()) { + regionPluginBuilder.addRegion(regionId); + } + + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { + regionPluginBuilder.defineRegionProperty(testRegionPropertyId, + testRegionPropertyId.getPropertyDefinition()); + } + + for (TestRegionId regionId : TestRegionId.values()) { + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { + if (testRegionPropertyId.getPropertyDefinition().getDefaultValue().isEmpty() + || randomGenerator.nextBoolean()) { + Object randomPropertyValue = testRegionPropertyId.getRandomPropertyValue(randomGenerator); + regionPluginBuilder.setRegionPropertyValue(regionId, testRegionPropertyId, randomPropertyValue); + } + } + } + TestRegionId testRegionId = TestRegionId.REGION_1; + for (PersonId personId : people) { + regionPluginBuilder.addPerson(personId, testRegionId, 0.0); + testRegionId = testRegionId.next(); + } + + regionPluginBuilder.setPersonRegionArrivalTracking(true); + + RegionsPluginData regionsPluginData = regionPluginBuilder.build(); + + List plugins = MaterialsTestPluginFactory.factory(0, 0, 0, 0, t -> { + }).setRegionsPluginData(regionsPluginData).getPlugins(); + + TestFactoryUtil.checkPluginDataExists(plugins, regionsPluginData, RegionsPluginId.PLUGIN_ID); + + // precondition: regionsPluginData is not null + ContractException contractException = assertThrows(ContractException.class, + () -> MaterialsTestPluginFactory.factory(0, 0, 0, 0, t -> { + }).setRegionsPluginData(null)); + assertEquals(RegionError.NULL_REGION_PLUGIN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = MaterialsTestPluginFactory.Factory.class, name = "setPeoplePluginData", args = { + PeoplePluginData.class }) + public void testSetPeoplePluginData() { + PeoplePluginData.Builder builder = PeoplePluginData.builder(); + builder.addPersonRange(new PersonRange(0, 99)); + PeoplePluginData peoplePluginData = builder.build(); + + List plugins = MaterialsTestPluginFactory.factory(0, 0, 0, 0, t -> { + }).setPeoplePluginData(peoplePluginData).getPlugins(); + + TestFactoryUtil.checkPluginDataExists(plugins, peoplePluginData, PeoplePluginId.PLUGIN_ID); + + // precondition: peoplePluginData is not null + ContractException contractException = assertThrows(ContractException.class, + () -> MaterialsTestPluginFactory.factory(0, 0, 0, 0, t -> { + }).setPeoplePluginData(null)); + assertEquals(PersonError.NULL_PEOPLE_PLUGIN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = MaterialsTestPluginFactory.Factory.class, name = "setStochasticsPluginData", args = { + StochasticsPluginData.class }) + public void testSetStochasticsPluginData() { + StochasticsPluginData.Builder builder = StochasticsPluginData.builder(); + + WellState wellState = WellState.builder().setSeed(2990359774692004249L).build(); + builder.setMainRNGState(wellState); + + wellState = WellState.builder().setSeed(1090094972994322820L).build(); + builder.addRNG(TestRandomGeneratorId.BLITZEN, wellState); + + StochasticsPluginData stochasticsPluginData = builder.build(); + + List plugins = MaterialsTestPluginFactory.factory(0, 0, 0, 0, t -> { + }).setStochasticsPluginData(stochasticsPluginData).getPlugins(); + + TestFactoryUtil.checkPluginDataExists(plugins, stochasticsPluginData, StochasticsPluginId.PLUGIN_ID); + + // precondition: stochasticsPluginData is not null + ContractException contractException = assertThrows(ContractException.class, + () -> MaterialsTestPluginFactory.factory(0, 0, 0, 0, t -> { + }).setStochasticsPluginData(null)); + assertEquals(StochasticsError.NULL_STOCHASTICS_PLUGIN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = MaterialsTestPluginFactory.class, name = "getStandardMaterialsPluginData", args = { + int.class, int.class, int.class, long.class }) + public void testGetStandardMaterialsPluginData() { + int numBatches = 50; + int numStages = 10; + int numBatchesInStage = 30; + long seed = 9029198675932589278L; + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + + MaterialsPluginData.Builder materialsBuilder = MaterialsPluginData.builder(); + + int bId = 0; + int sId = 0; + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + materialsBuilder.addMaterial(testMaterialId); + } + + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + + List batches = new ArrayList<>(); + + for (int i = 0; i < numBatches; i++) { + + TestMaterialId testMaterialId = TestMaterialId.getRandomMaterialId(randomGenerator); + double amount = randomGenerator.nextDouble(); + BatchId batchId = new BatchId(bId++); + materialsBuilder.addBatch(batchId, testMaterialId, amount); + batches.add(batchId); + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId + .getTestBatchPropertyIds(testMaterialId)) { + boolean required = testBatchPropertyId.getPropertyDefinition().getDefaultValue().isEmpty(); + if (required || randomGenerator.nextBoolean()) { + materialsBuilder.setBatchPropertyValue(batchId, testBatchPropertyId, + testBatchPropertyId.getRandomPropertyValue(randomGenerator)); + } + } + + } + + List stages = new ArrayList<>(); + + for (int i = 0; i < numStages; i++) { + StageId stageId = new StageId(sId++); + stages.add(stageId); + boolean offered = i % 2 == 0; + materialsBuilder.addStage(stageId, offered); + materialsBuilder.addStageToMaterialProducer(stageId, testMaterialsProducerId); + } + + Collections.shuffle(batches, new Random(randomGenerator.nextLong())); + for (int i = 0; i < numBatches; i++) { + BatchId batchId = batches.get(i); + if (i < numBatchesInStage) { + StageId stageId = stages.get(randomGenerator.nextInt(stages.size())); + materialsBuilder.addBatchToStage(stageId, batchId); + } else { + materialsBuilder.addBatchToMaterialsProducerInventory(batchId, testMaterialsProducerId); + } + } + materialsBuilder.addMaterialsProducerId(testMaterialsProducerId); + + for (ResourceId resourceId : TestResourceId.values()) { + if (randomGenerator.nextBoolean()) { + materialsBuilder.setMaterialsProducerResourceLevel(testMaterialsProducerId, resourceId, + randomGenerator.nextInt(10)); + } else { + materialsBuilder.setMaterialsProducerResourceLevel(testMaterialsProducerId, resourceId, 0); + } + } + } + + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .values()) { + materialsBuilder.defineMaterialsProducerProperty(testMaterialsProducerPropertyId, + testMaterialsProducerPropertyId.getPropertyDefinition()); + } + + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId + .getPropertiesWithoutDefaultValues()) { + for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { + Object randomPropertyValue = testMaterialsProducerPropertyId.getRandomPropertyValue(randomGenerator); + materialsBuilder.setMaterialsProducerPropertyValue(testMaterialsProducerId, + testMaterialsProducerPropertyId, randomPropertyValue); + } + } + + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + Set testBatchPropertyIds = TestBatchPropertyId.getTestBatchPropertyIds(testMaterialId); + for (TestBatchPropertyId testBatchPropertyId : testBatchPropertyIds) { + materialsBuilder.defineBatchProperty(testMaterialId, testBatchPropertyId, + testBatchPropertyId.getPropertyDefinition()); + } + } + + MaterialsPluginData expectedPluginData = materialsBuilder.build(); + + MaterialsPluginData actualPluginData = MaterialsTestPluginFactory.getStandardMaterialsPluginData(numBatches, + numStages, numBatchesInStage, seed); + + assertEquals(expectedPluginData, actualPluginData); + } + + @Test + @UnitTestMethod(target = MaterialsTestPluginFactory.class, name = "getStandardResourcesPluginData", args = { + long.class }) + public void testGetStandardResourcesPluginData() { + + long seed = 4800551796983227153L; + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + ResourcesPluginData.Builder resourcesBuilder = ResourcesPluginData.builder(); + + for (TestResourceId testResourceId : TestResourceId.values()) { + resourcesBuilder.addResource(testResourceId, 0.0, testResourceId.getTimeTrackingPolicy()); + } + + for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId.values()) { + TestResourceId testResourceId = testResourcePropertyId.getTestResourceId(); + PropertyDefinition propertyDefinition = testResourcePropertyId.getPropertyDefinition(); + Object propertyValue = testResourcePropertyId.getRandomPropertyValue(randomGenerator); + resourcesBuilder.defineResourceProperty(testResourceId, testResourcePropertyId, propertyDefinition); + resourcesBuilder.setResourcePropertyValue(testResourceId, testResourcePropertyId, propertyValue); + } + + ResourcesPluginData expectedPluginData = resourcesBuilder.build(); + ResourcesPluginData actualPluginData = MaterialsTestPluginFactory.getStandardResourcesPluginData(seed); + + assertEquals(expectedPluginData, actualPluginData); + } + + @Test + @UnitTestMethod(target = MaterialsTestPluginFactory.class, name = "getStandardRegionsPluginData", args = {}) + public void testGetStandardRegionsPluginData() { + + RegionsPluginData.Builder regionsBuilder = RegionsPluginData.builder(); + for (TestRegionId testRegionId : TestRegionId.values()) { + regionsBuilder.addRegion(testRegionId); + } + RegionsPluginData expectedPluginData = regionsBuilder.build(); + RegionsPluginData actualPluginData = MaterialsTestPluginFactory.getStandardRegionsPluginData(); + + assertEquals(expectedPluginData, actualPluginData); + } + + @Test + @UnitTestMethod(target = MaterialsTestPluginFactory.class, name = "getStandardPeoplePluginData", args = {}) + public void testGetStandardPeoplePluginData() { + + PeoplePluginData expectedPluginData = PeoplePluginData.builder().build(); + PeoplePluginData actualPluginData = MaterialsTestPluginFactory.getStandardPeoplePluginData(); + assertEquals(expectedPluginData, actualPluginData); + } + + @Test + @UnitTestMethod(target = MaterialsTestPluginFactory.class, name = "getStandardStochasticsPluginData", args = { + long.class }) + public void testGetStandardStochasticsPluginData() { + long seed = 6072871729256538807L; + + WellState wellState = WellState.builder().setSeed(seed).build(); + + StochasticsPluginData expectedPluginData = StochasticsPluginData.builder().setMainRNGState(wellState).build(); + StochasticsPluginData actualPluginData = MaterialsTestPluginFactory.getStandardStochasticsPluginData(seed); + assertEquals(expectedPluginData, actualPluginData); + } + + + + + + @Test + @UnitTestMethod(target = MaterialsTestPluginFactory.Factory.class, name = "setMaterialsProducerResourceReportPluginData", args = { + MaterialsProducerResourceReportPluginData.class }) + public void testSetMaterialsProducerResourceReportPluginData() { + PeoplePluginData.Builder builder = PeoplePluginData.builder(); + builder.addPersonRange(new PersonRange(0, 99)); + PeoplePluginData peoplePluginData = builder.build(); + + MaterialsProducerResourceReportPluginData materialsProducerResourceReportPluginData = MaterialsProducerResourceReportPluginData.builder()// + .setReportLabel(new SimpleReportLabel("MaterialsProducerResourceReport"))// + .build(); + + + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 0, t -> { + }); + factory.setPeoplePluginData(peoplePluginData); + factory.setMaterialsProducerResourceReportPluginData(materialsProducerResourceReportPluginData); + + List plugins = factory.getPlugins(); + + + TestFactoryUtil.checkPluginDataExists(plugins, materialsProducerResourceReportPluginData, MaterialsPluginId.PLUGIN_ID); + } + + @Test + @UnitTestMethod(target = MaterialsTestPluginFactory.Factory.class, name = "setMaterialsProducerPropertyReportPluginData", args = { + MaterialsProducerPropertyReportPluginData.class }) + public void testSetMaterialsProducerPropertyReportPluginData() { + PeoplePluginData.Builder builder = PeoplePluginData.builder(); + builder.addPersonRange(new PersonRange(0, 99)); + PeoplePluginData peoplePluginData = builder.build(); + + MaterialsProducerPropertyReportPluginData materialsProducerPropertyReportPluginData = MaterialsProducerPropertyReportPluginData.builder()// + .setReportLabel(new SimpleReportLabel("MaterialsProducerPropertyReport"))// + .build(); + + + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 0, t -> { + }); + factory.setPeoplePluginData(peoplePluginData); + factory.setMaterialsProducerPropertyReportPluginData(materialsProducerPropertyReportPluginData); + + List plugins = factory.getPlugins(); + + + TestFactoryUtil.checkPluginDataExists(plugins, materialsProducerPropertyReportPluginData, MaterialsPluginId.PLUGIN_ID); + } + @Test + @UnitTestMethod(target = MaterialsTestPluginFactory.Factory.class, name = "setStageReportPluginData", args = { + StageReportPluginData.class }) + public void testSetStageReportPluginData() { + PeoplePluginData.Builder builder = PeoplePluginData.builder(); + builder.addPersonRange(new PersonRange(0, 99)); + PeoplePluginData peoplePluginData = builder.build(); + + StageReportPluginData stageReportPluginData = StageReportPluginData.builder()// + .setReportLabel(new SimpleReportLabel("StageReport"))// + .build(); + + + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 0, t -> { + }); + factory.setPeoplePluginData(peoplePluginData); + factory.setStageReportPluginData(stageReportPluginData); + + List plugins = factory.getPlugins(); + + + TestFactoryUtil.checkPluginDataExists(plugins, stageReportPluginData, MaterialsPluginId.PLUGIN_ID); + } + + @Test + @UnitTestMethod(target = MaterialsTestPluginFactory.Factory.class, name = "setBatchStatusReportPluginData", args = { + BatchStatusReportPluginData.class }) + public void testSetBatchStatusReportPluginData() { + PeoplePluginData.Builder builder = PeoplePluginData.builder(); + builder.addPersonRange(new PersonRange(0, 99)); + PeoplePluginData peoplePluginData = builder.build(); + + BatchStatusReportPluginData batchStatusReportPluginData = BatchStatusReportPluginData.builder()// + .setReportLabel(new SimpleReportLabel("BatchStatusReport"))// + .build(); + + + Factory factory = MaterialsTestPluginFactory.factory(0, 0, 0, 0, t -> { + }); + factory.setPeoplePluginData(peoplePluginData); + factory.setBatchStatusReportPluginData(batchStatusReportPluginData); + + List plugins = factory.getPlugins(); + + + TestFactoryUtil.checkPluginDataExists(plugins, batchStatusReportPluginData, MaterialsPluginId.PLUGIN_ID); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/testsupport/AT_TestBatchConstructionInfo.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/testsupport/AT_TestBatchConstructionInfo.java new file mode 100644 index 000000000..d3d4dd077 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/testsupport/AT_TestBatchConstructionInfo.java @@ -0,0 +1,48 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.testsupport; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchConstructionInfo; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsError; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerId; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_TestBatchConstructionInfo { + + @Test + @UnitTestMethod(target = TestBatchConstructionInfo.class, name = "getBatchConstructionInfo", args = { MaterialsProducerId.class, MaterialId.class, double.class, RandomGenerator.class }) + public void testGetBatchConstructionInfo() { + MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; + MaterialId materialId = TestMaterialId.MATERIAL_1; + double amount = 25.0; + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2531244935109186076L); + + BatchConstructionInfo batchConstructionInfo = TestBatchConstructionInfo.getBatchConstructionInfo(materialsProducerId, materialId, amount, randomGenerator); + + assertNotNull(batchConstructionInfo); + + // precondition: null material id + ContractException contractException = assertThrows(ContractException.class, () -> TestBatchConstructionInfo.getBatchConstructionInfo(materialsProducerId, null, amount, randomGenerator)); + assertEquals(MaterialsError.NULL_MATERIAL_ID, contractException.getErrorType()); + + // precondition: null material producer id + contractException = assertThrows(ContractException.class, () -> TestBatchConstructionInfo.getBatchConstructionInfo(null, materialId, amount, randomGenerator)); + assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); + + // precondition: negative amount + contractException = assertThrows(ContractException.class, () -> TestBatchConstructionInfo.getBatchConstructionInfo(materialsProducerId, materialId, -100.0, randomGenerator)); + assertEquals(MaterialsError.NEGATIVE_MATERIAL_AMOUNT, contractException.getErrorType()); + + // precondition: amount is not finite + contractException = assertThrows(ContractException.class, () -> TestBatchConstructionInfo.getBatchConstructionInfo(materialsProducerId, materialId, Double.POSITIVE_INFINITY, randomGenerator)); + assertEquals(MaterialsError.NON_FINITE_MATERIAL_AMOUNT, contractException.getErrorType()); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/testsupport/AT_TestBatchPropertyId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/testsupport/AT_TestBatchPropertyId.java new file mode 100644 index 000000000..c01d4927c --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/testsupport/AT_TestBatchPropertyId.java @@ -0,0 +1,115 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.testsupport; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import util.annotations.UnitTestMethod; +import util.random.RandomGeneratorProvider; + +public class AT_TestBatchPropertyId { + + @Test + @UnitTestMethod(target = TestBatchPropertyId.class, name = "getPropertyDefinition", args = {}) + public void testGetPropertyDefinition() { + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.values()) { + PropertyDefinition propertyDefinition = testBatchPropertyId.getPropertyDefinition(); + assertNotNull(propertyDefinition); + } + } + + @Test + @UnitTestMethod(target = TestBatchPropertyId.class, name = "getTestMaterialId", args = {}) + public void testGetTestMaterialId() { + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.values()) { + assertNotNull(testBatchPropertyId.getTestMaterialId()); + } + } + + @Test + @UnitTestMethod(target = TestBatchPropertyId.class, name = "getTestBatchPropertyIds", args = { TestMaterialId.class }) + public void testGetTestBatchPropertyIds() { + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + Set expectedBatchPropertyIds = new LinkedHashSet<>(); + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.values()) { + if (testMaterialId.equals(testBatchPropertyId.getTestMaterialId())) { + expectedBatchPropertyIds.add(testBatchPropertyId); + } + } + Set actualBatchPropertyIds = TestBatchPropertyId.getTestBatchPropertyIds(testMaterialId); + assertEquals(expectedBatchPropertyIds, actualBatchPropertyIds); + } + } + + @Test + @UnitTestMethod(target = TestBatchPropertyId.class, name = "getUnknownBatchPropertyId", args = {}) + public void testGetUnknownBatchPropertyId() { + BatchPropertyId unknownBatchPropertyId = TestBatchPropertyId.getUnknownBatchPropertyId(); + assertNotNull(unknownBatchPropertyId); + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.values()) { + assertNotEquals(testBatchPropertyId, unknownBatchPropertyId); + } + BatchPropertyId unknownBatchPropertyId2 = TestBatchPropertyId.getUnknownBatchPropertyId(); + assertNotEquals(unknownBatchPropertyId, unknownBatchPropertyId2); + } + + @Test + @UnitTestMethod(target = TestBatchPropertyId.class, name = "getRandomMutableBatchPropertyId", args = { TestMaterialId.class, RandomGenerator.class }) + public void testGetRandomMutableBatchPropertyId() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7432312917768892660L); + for (TestMaterialId testMaterialId : TestMaterialId.values()) { + TestBatchPropertyId batchPropertyId = TestBatchPropertyId.getRandomMutableBatchPropertyId(testMaterialId, randomGenerator); + assertNotNull(batchPropertyId); + assertEquals(testMaterialId, batchPropertyId.getTestMaterialId()); + } + } + + @Test + @UnitTestMethod(target = TestBatchPropertyId.class, name = "getRandomPropertyValue", args = { RandomGenerator.class }) + public void testGetRandomPropertyValue() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2839187347342327244L); + for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.values()) { + PropertyDefinition propertyDefinition = testBatchPropertyId.getPropertyDefinition(); + for (int i = 0; i < 10; i++) { + Object propertyValue = testBatchPropertyId.getRandomPropertyValue(randomGenerator); + assertNotNull(propertyValue); + assertTrue(propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())); + } + } + } + + + @Test + @UnitTestMethod(target = TestBatchPropertyId.class, name = "getBatchPropertyIds", args = {}) + public void testGetBatchPropertyIds() { + assertEquals(Arrays.asList(TestBatchPropertyId.values()),TestBatchPropertyId.getBatchPropertyIds()); + } + + @Test + @UnitTestMethod(target = TestBatchPropertyId.class, name = "getBatchPropertyIds", args = { RandomGenerator.class }) + public void testGetBatchPropertyIds_RandomGenerator() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2272120777807498806L); + LinkedHashSet expectedSet = new LinkedHashSet<>(Arrays.asList(TestBatchPropertyId.values())); + Set> actualLists = new LinkedHashSet<>(); + for(int i = 0;i<1000;i++) { + List batchPropertyIds = TestBatchPropertyId.getBatchPropertyIds(randomGenerator); + actualLists.add(batchPropertyIds); + //show that the generated list has all the property ids + assertEquals(expectedSet,new LinkedHashSet<>(batchPropertyIds)); + } + //show that the 1000 lists of the 362880 = 9! possible arrangements result in nearly no duplicates + assertTrue(actualLists.size()>990); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/testsupport/AT_TestMaterialId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/testsupport/AT_TestMaterialId.java new file mode 100644 index 000000000..cf7e1a740 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/testsupport/AT_TestMaterialId.java @@ -0,0 +1,49 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.testsupport; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialId; +import util.annotations.UnitTestMethod; +import util.random.RandomGeneratorProvider; + +public class AT_TestMaterialId { + + @Test + @UnitTestMethod(target = TestMaterialId.class, name = "getRandomMaterialId", args = { RandomGenerator.class }) + public void testGetRandomMaterialId() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3905846017447134736L); + for (int i = 0; i < 10; i++) { + assertNotNull(TestMaterialId.getRandomMaterialId(randomGenerator)); + } + } + + @Test + @UnitTestMethod(target = TestMaterialId.class, name = "size", args = {}) + public void testSize() { + assertEquals(TestMaterialId.values().length, TestMaterialId.size()); + } + + @Test + @UnitTestMethod(target = TestMaterialId.class, name = "next", args = {}) + public void testNext() { + TestMaterialId[] values = TestMaterialId.values(); + for (int i = 0; i < values.length; i++) { + assertEquals(values[(i + 1) % values.length], values[i].next()); + } + } + + @Test + @UnitTestMethod(target = TestMaterialId.class, name = "getUnknownMaterialId", args = {}) + public void testGetUnknownMaterialId() { + MaterialId unknownMaterialId = TestMaterialId.getUnknownMaterialId(); + assertNotNull(unknownMaterialId); + MaterialId unknownMaterialId2 = TestMaterialId.getUnknownMaterialId(); + assertNotEquals(unknownMaterialId, unknownMaterialId2); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/testsupport/AT_TestMaterialsProducerId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/testsupport/AT_TestMaterialsProducerId.java new file mode 100644 index 000000000..5f04d8c92 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/testsupport/AT_TestMaterialsProducerId.java @@ -0,0 +1,48 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.testsupport; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerId; +import util.annotations.UnitTestMethod; +import util.random.RandomGeneratorProvider; + +public class AT_TestMaterialsProducerId { + + @Test + @UnitTestMethod(target = TestMaterialsProducerId.class,name = "getRandomMaterialsProducerId", args = { RandomGenerator.class }) + public void testGetRandomMaterialsProducerId() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2141886758156469650L); + for (int i = 0; i < 10; i++) { + assertNotNull(TestMaterialsProducerId.getRandomMaterialsProducerId(randomGenerator)); + } + } + + @Test + @UnitTestMethod(target = TestMaterialsProducerId.class,name = "size", args = {}) + public void testSize() { + assertEquals(TestMaterialsProducerId.values().length, TestMaterialId.size()); + } + + @Test + @UnitTestMethod(target = TestMaterialsProducerId.class,name = "next", args = {}) + public void testNext() { + TestMaterialsProducerId[] values = TestMaterialsProducerId.values(); + for (int i = 0; i < values.length; i++) { + assertEquals(values[(i + 1) % values.length], values[i].next()); + } + } + + @Test + @UnitTestMethod(target = TestMaterialsProducerId.class,name = "getUnknownMaterialsProducerId", args = {}) + public void testGetUnknownMaterialsProducerId() { + MaterialsProducerId unknownMaterialsProducerId = TestMaterialsProducerId.getUnknownMaterialsProducerId(); + assertNotNull(unknownMaterialsProducerId); + MaterialsProducerId unknownMaterialsProducerId2 = TestMaterialsProducerId.getUnknownMaterialsProducerId(); + assertNotEquals(unknownMaterialsProducerId, unknownMaterialsProducerId2); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/testsupport/AT_TestMaterialsProducerPropertyId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/testsupport/AT_TestMaterialsProducerPropertyId.java new file mode 100644 index 000000000..e1e67135d --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/materials/testsupport/AT_TestMaterialsProducerPropertyId.java @@ -0,0 +1,136 @@ +package gov.hhs.aspr.ms.gcm.plugins.materials.testsupport; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import util.annotations.UnitTestMethod; +import util.random.RandomGeneratorProvider; + +public class AT_TestMaterialsProducerPropertyId { + + @Test + @UnitTestMethod(target = TestMaterialsProducerPropertyId.class, name = "getPropertyDefinition", args = {}) + public void testGetPropertyDefinition() { + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId.values()) { + PropertyDefinition propertyDefinition = testMaterialsProducerPropertyId.getPropertyDefinition(); + assertNotNull(propertyDefinition); + } + } + + @Test + @UnitTestMethod(target = TestMaterialsProducerPropertyId.class, name = "getUnknownMaterialsProducerPropertyId", args = {}) + public void testGetUnknownMaterialsProducerPropertyId() { + MaterialsProducerPropertyId unknownMaterialsProducerPropertyId = TestMaterialsProducerPropertyId.getUnknownMaterialsProducerPropertyId(); + assertNotNull(unknownMaterialsProducerPropertyId); + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId.values()) { + assertNotEquals(testMaterialsProducerPropertyId, unknownMaterialsProducerPropertyId); + } + MaterialsProducerPropertyId unknownMaterialsProducerPropertyId2 = TestMaterialsProducerPropertyId.getUnknownMaterialsProducerPropertyId(); + assertNotEquals(unknownMaterialsProducerPropertyId, unknownMaterialsProducerPropertyId2); + + } + + @Test + @UnitTestMethod(target = TestMaterialsProducerPropertyId.class, name = "getRandomPropertyValue", args = { RandomGenerator.class }) + public void testGetRandomPropertyValue() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7973900878959109442L); + + for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId.values()) { + PropertyDefinition propertyDefinition = testMaterialsProducerPropertyId.getPropertyDefinition(); + for (int i = 0; i < 10; i++) { + Object value = testMaterialsProducerPropertyId.getRandomPropertyValue(randomGenerator); + assertNotNull(value); + assertTrue(propertyDefinition.getType().isAssignableFrom(value.getClass())); + } + } + + } + + @Test + @UnitTestMethod(target = TestMaterialsProducerPropertyId.class, name = "getRandomMaterialsProducerPropertyId", args = { RandomGenerator.class }) + public void testGetRandomMaterialsProducerPropertyId() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5963531689679394818L); + + for (int i = 0; i < 10; i++) { + TestMaterialsProducerPropertyId producerPropertyId = TestMaterialsProducerPropertyId.getRandomMaterialsProducerPropertyId(randomGenerator); + assertNotNull(producerPropertyId); + } + + } + + @Test + @UnitTestMethod(target = TestMaterialsProducerPropertyId.class, name = "getRandomMutableMaterialsProducerPropertyId", args = { RandomGenerator.class }) + public void testGetRandomMutableMaterialsProducerPropertyId() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8476649750185982818L); + for (int i = 0; i < 10; i++) { + TestMaterialsProducerPropertyId producerPropertyId = TestMaterialsProducerPropertyId.getRandomMutableMaterialsProducerPropertyId(randomGenerator); + assertNotNull(producerPropertyId); + assertTrue(producerPropertyId.getPropertyDefinition().propertyValuesAreMutable()); + } + + } + + @Test + @UnitTestMethod(target = TestMaterialsProducerPropertyId.class, name = "size", args = {}) + public void testSize() { + assertEquals(TestMaterialsProducerPropertyId.values().length, TestMaterialsProducerPropertyId.size()); + } + + @Test + @UnitTestMethod(target = TestMaterialsProducerPropertyId.class, name = "getPropertiesWithDefaultValues", args = {}) + public void testGetPropertesWithDefaultValues() { + List expectedValues = new ArrayList<>(); + + for (TestMaterialsProducerPropertyId id : TestMaterialsProducerPropertyId.values()) { + if (id.getPropertyDefinition().getDefaultValue().isPresent()) { + expectedValues.add(id); + } + } + + List actualValues = TestMaterialsProducerPropertyId.getPropertiesWithDefaultValues(); + + assertNotNull(actualValues); + assertEquals(expectedValues.size(), actualValues.size()); + Set setOfExpectedValues = new LinkedHashSet<>(expectedValues); + Set setOfActualValues = new LinkedHashSet<>(actualValues); + assertEquals(setOfExpectedValues, setOfActualValues); + assertEquals(expectedValues.size(), setOfExpectedValues.size()); + assertEquals(actualValues.size(), setOfActualValues.size()); + } + + @Test + @UnitTestMethod(target = TestMaterialsProducerPropertyId.class, name = "getPropertiesWithoutDefaultValues", args = {}) + public void testGetPropertesWithoutDefaultValues() { + List expectedValues = new ArrayList<>(); + + for (TestMaterialsProducerPropertyId id : TestMaterialsProducerPropertyId.values()) { + if (id.getPropertyDefinition().getDefaultValue().isEmpty()) { + expectedValues.add(id); + } + } + + List actualValues = TestMaterialsProducerPropertyId.getPropertiesWithoutDefaultValues(); + + assertNotNull(actualValues); + assertEquals(expectedValues.size(), actualValues.size()); + Set setOfExpectedValues = new LinkedHashSet<>(expectedValues); + Set setOfActualValues = new LinkedHashSet<>(actualValues); + assertEquals(setOfExpectedValues, setOfActualValues); + assertEquals(expectedValues.size(), setOfExpectedValues.size()); + assertEquals(actualValues.size(), setOfActualValues.size()); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/AT_PartitionsPlugin.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/AT_PartitionsPlugin.java new file mode 100644 index 000000000..51498929b --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/AT_PartitionsPlugin.java @@ -0,0 +1,123 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.plugins.partitions.datamanagers.PartitionsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionError; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePluginId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPluginId; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + +public final class AT_PartitionsPlugin { + + @Test + @UnitTestMethod(target = PartitionsPlugin.Builder.class, name = "setPartitionsPluginData", args = { PartitionsPluginData.class }) + public void testSetPartitionsPluginData() { + + PartitionsPluginData partitionsPluginData1 = PartitionsPluginData.builder().build(); + PartitionsPluginData partitionsPluginData2 = PartitionsPluginData.builder().build(); + PartitionsPluginData partitionsPluginData3 = PartitionsPluginData.builder().build(); + + Plugin partitionsPlugin = PartitionsPlugin.builder()// + .setPartitionsPluginData(partitionsPluginData1)// + .setPartitionsPluginData(partitionsPluginData2)// + .setPartitionsPluginData(partitionsPluginData3)// + .getPartitionsPlugin(); + + List pluginDatas = partitionsPlugin.getPluginDatas(); + assertNotNull(pluginDatas); + assertEquals(1,pluginDatas.size()); + PluginData pluginData = pluginDatas.get(0); + assertEquals(partitionsPluginData3, pluginData); + + } + + @Test + @UnitTestMethod(target = PartitionsPlugin.Builder.class, name = "addPluginDependency", args = { PluginId.class }) + public void testAddPluginDependency() { + + PluginId somePluginId = new PluginId() { + }; + + Plugin partitionsPlugin = PartitionsPlugin.builder()// + .addPluginDependency(somePluginId)// + .setPartitionsPluginData(PartitionsPluginData.builder().build())// + .getPartitionsPlugin(); + + Set expectedDependencies = new LinkedHashSet<>(); + expectedDependencies.add(PeoplePluginId.PLUGIN_ID); + expectedDependencies.add(StochasticsPluginId.PLUGIN_ID); + expectedDependencies.add(somePluginId); + + assertEquals(expectedDependencies, partitionsPlugin.getPluginDependencies()); + + } + + @Test + @UnitTestMethod(target = PartitionsPlugin.class, name = "builder", args = {}) + public void testBuilder() { + assertNotNull(PartitionsPlugin.builder()); + } + + @Test + @UnitTestMethod(target = PartitionsPlugin.Builder.class, name = "getPartitionsPlugin", args = {}) + public void testGetPartitionsPlugin() { + + PartitionsPluginData partitionsPluginData = PartitionsPluginData.builder().build(); + + Plugin partitionsPlugin = PartitionsPlugin.builder()// + .setPartitionsPluginData(partitionsPluginData)// + .getPartitionsPlugin(); + + assertEquals(1, partitionsPlugin.getPluginDatas().size()); + assertTrue(partitionsPlugin.getPluginDatas().contains(partitionsPluginData)); + + assertEquals(PartitionsPluginId.PLUGIN_ID, partitionsPlugin.getPluginId()); + + Set expectedDependencies = new LinkedHashSet<>(); + expectedDependencies.add(PeoplePluginId.PLUGIN_ID); + expectedDependencies.add(StochasticsPluginId.PLUGIN_ID); + assertEquals(expectedDependencies, partitionsPlugin.getPluginDependencies()); + + //precondition test: if a plugin dependency is null + assertThrows(ContractException.class, ()->{ + PartitionsPlugin.builder()// + .setPartitionsPluginData(PartitionsPluginData.builder().build())// + .addPluginDependency(null) + .getPartitionsPlugin(); + }); + + //precondition test: if a plugin dependency is null + ContractException contractException = assertThrows(ContractException.class, ()->{ + PartitionsPlugin.builder()// + .setPartitionsPluginData(PartitionsPluginData.builder().build())// + .addPluginDependency(null) + .getPartitionsPlugin(); + }); + assertEquals(NucleusError.NULL_PLUGIN_ID,contractException.getErrorType()); + + //precondition test: if a plugin dependency is null + contractException = assertThrows(ContractException.class, ()->{ + PartitionsPlugin.builder()// + .setPartitionsPluginData(null)// + .getPartitionsPlugin(); + }); + assertEquals(PartitionError.NULL_PARTITION_PLUGIN_DATA,contractException.getErrorType()); + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/AT_PartitionsPluginId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/AT_PartitionsPluginId.java new file mode 100644 index 000000000..bd34668b0 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/AT_PartitionsPluginId.java @@ -0,0 +1,16 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestField; + +public class AT_PartitionsPluginId { + + @Test + @UnitTestField(target = PartitionsPluginId.class, name = "PLUGIN_ID") + public void testPluginId() { + assertNotNull(PartitionsPluginId.PLUGIN_ID); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/datamanagers/AT_PartitionsDataManager.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/datamanagers/AT_PartitionsDataManager.java new file mode 100644 index 000000000..795fcc45f --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/datamanagers/AT_PartitionsDataManager.java @@ -0,0 +1,1948 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.datamanagers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Random; +import java.util.Set; +import java.util.function.Function; + +import org.apache.commons.math3.random.RandomGenerator; +import org.apache.commons.math3.util.FastMath; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; +import gov.hhs.aspr.ms.gcm.nucleus.EventFilter; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestOutputConsumer; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.partitions.PartitionsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.Equality; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.LabelSet; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.LabelSetFunction; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.LabelSetWeightingFunction; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.Partition; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionSampler; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters.Filter; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters.TrueFilter; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.FunctionalAttributeLabeler; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.PartitionsTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.PartitionsTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.AttributesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support.AttributeFilter; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support.TestAttributeId; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.events.PersonImminentRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonConstructionData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; +import util.wrappers.MutableInteger; + +public final class AT_PartitionsDataManager { + + @Test + @UnitTestConstructor(target = PartitionsDataManager.class, args = {PartitionsPluginData.class}) + public void testConstructor() { + PartitionsDataManager dataManager = new PartitionsDataManager(PartitionsPluginData.builder().build()); + assertNotNull(dataManager); + } + + /* + * Assigns randomized values for all attributes to all people. Values are + * assigned to be consistent with the static labeling functions. + */ + private static void assignRandomAttributes(final ActorContext c) { + final PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + final StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + final RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + for (final PersonId personId : peopleDataManager.getPeople()) { + + boolean b = randomGenerator.nextBoolean(); + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, b); + + b = randomGenerator.nextBoolean(); + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_1, b); + + int i = randomGenerator.nextInt(100); + attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_0, i); + + i = randomGenerator.nextInt(100); + attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_1, i); + + double d = randomGenerator.nextDouble() * 100; + attributesDataManager.setAttributeValue(personId, TestAttributeId.DOUBLE_0, d); + + d = randomGenerator.nextDouble() * 100; + attributesDataManager.setAttributeValue(personId, TestAttributeId.DOUBLE_1, d); + + } + } + + private static Function INT_0_LABELFUNCTION = (value) -> { + final int v = (Integer) value; + return v % 3; + }; + + private static Function INT_1_LABELFUNCTION = (value) -> { + final int v = (Integer) value; + if (v < 40) { + return true; + } + return false; + }; + + private static Function DOUBLE_0_LABELFUNCTION = (value) -> { + final double v = (Double) value; + if (v < 33) { + return "A"; + } + if (v < 67) { + return "B"; + } + return "C"; + }; + + private static Function DOUBLE_1_LABELFUNCTION = (value) -> { + final double v = (Double) value; + return v < 90; + }; + + /* + * Creates a map from LabelSet to PersonId that covers all people who have + * an attribute value of true for BOOLEAN_0 and false for BOOLEAN_1, to be + * consistent with the filter used in the partition addition test. Label + * sets consist of labels for INT_0, INT_1, DOUBLE_0 and DOUBLE_1. + */ + private static Map> getExpectedStructure(final ActorContext c) { + final PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + final AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + final Map> expectedPeople = new LinkedHashMap<>(); + for (final PersonId personId : peopleDataManager.getPeople()) { + + final Boolean b0 = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); + final Boolean b1 = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_1); + if (b0 && !b1) { + + final Integer i0 = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_0); + final Object label_i0 = INT_0_LABELFUNCTION.apply(i0); + + final Integer i1 = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_1); + final Object label_i1 = INT_1_LABELFUNCTION.apply(i1); + + final Double d0 = attributesDataManager.getAttributeValue(personId, TestAttributeId.DOUBLE_0); + final Object label_d0 = DOUBLE_0_LABELFUNCTION.apply(d0); + + final Double d1 = attributesDataManager.getAttributeValue(personId, TestAttributeId.DOUBLE_1); + final Object label_d1 = DOUBLE_1_LABELFUNCTION.apply(d1); + + final LabelSet labelSet = LabelSet .builder()// + .setLabel(TestAttributeId.INT_0, label_i0)// + .setLabel(TestAttributeId.INT_1, label_i1)// + .setLabel(TestAttributeId.DOUBLE_0, label_d0)// + .setLabel(TestAttributeId.DOUBLE_1, label_d1)// + .build();// + + Set people = expectedPeople.get(labelSet); + if (people == null) { + people = new LinkedHashSet<>(); + expectedPeople.put(labelSet, people); + } + people.add(personId); + } + } + return expectedPeople; + + } + + /* + * Compares the expected alignment of people to label sets to the population + * partition's content via assertions. + */ + private static void showPartitionIsCorrect(final ActorContext c, final Map> expectedPartitionStructure, final Object key) { + + final PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); + + // derive the number of people in the expected partition structure + int expectedPersonCount = 0; + for (final LabelSet labelSet : expectedPartitionStructure.keySet()) { + final Set expectedPeople = expectedPartitionStructure.get(labelSet); + expectedPersonCount += expectedPeople.size(); + } + + // Show that the number of people in the partition matches the expected + // count of people. + final int actualPersonCount = partitionsDataManager.getPersonCount(key); + assertEquals(expectedPersonCount, actualPersonCount); + + /* + * Show that each label set in the expected structure is associated with + * the same people in the population partition. + * + * Since we know that the expected partition structure and the + * population partition have the same number of people and that no + * person can be associated with two label sets, we know there are no + * uncounted people in the population partition and thus the two data + * structures match. + */ + for (final LabelSet labelSet : expectedPartitionStructure.keySet()) { + final Set expectedPeople = expectedPartitionStructure.get(labelSet); + final List actualpeople = partitionsDataManager.getPeople(key, labelSet); + assertEquals(expectedPeople.size(), actualpeople.size()); + assertEquals(expectedPeople, new LinkedHashSet<>(actualpeople)); + } + } + + /* + * Removes the given number of people from the simulation, chosen at random. + * Person count may exceed the current population size. + */ + private static void removePeople(final ActorContext c, final int numberOfPeopleToRemove) { + final PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + final StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + final long seed = stochasticsDataManager.getRandomGenerator().nextLong(); + final Random random = new Random(seed); + final List people = peopleDataManager.getPeople(); + Collections.shuffle(people, random); + final int correctedNumberOfPeopleToRemove = FastMath.min(numberOfPeopleToRemove, people.size()); + for (int i = 0; i < correctedNumberOfPeopleToRemove; i++) { + peopleDataManager.removePerson(people.get(i)); + } + } + + /* + * Adds the given number of people to the simulation + */ + private static void addPeople(final ActorContext c, final int numberOfPeopleToAdd) { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + for (int i = 0; i < numberOfPeopleToAdd; i++) { + peopleDataManager.addPerson(PersonConstructionData.builder().build()); + } + } + + @Test + @UnitTestMethod(target = PartitionsDataManager.class, name = "addPartition", args = { Partition.class, Object.class }) + public void testAddPartition() { + + // Have the simulation initialized with 1000 people. Have an agent + // execute a partition addition and multiple changes to the population + // and their attributes to show that the partition resolver maintains + // the partition. + + Factory factory = PartitionsTestPluginFactory.factory(1000, 5127268948453841557L, (c) -> { + // get the partition data view + final PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); + + // initialize people's attributes + assignRandomAttributes(c); + + // create a key to use for a new partition + final Object key = new Object(); + + // show that the population partition does not yet exist + assertFalse(partitionsDataManager.partitionExists(key)); + + /* + * Add the partition. We will filter to select people who have + * BOOLEAN_0 as true and BOOLEAN_1 as false. The remaining + * attributes will be used to define the cells of the partition via + * the four static labeling functions defined in this class. + */ + final Filter filter0 = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); + final Filter filter1 = new AttributeFilter(TestAttributeId.BOOLEAN_1, Equality.EQUAL, false); + final Filter filter = filter0.and(filter1); + final Partition partition = Partition .builder()// + .setFilter(filter)// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.INT_0, INT_0_LABELFUNCTION))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.INT_1, INT_1_LABELFUNCTION))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.DOUBLE_0, DOUBLE_0_LABELFUNCTION))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.DOUBLE_1, DOUBLE_1_LABELFUNCTION))// + .build();// + + partitionsDataManager.addPartition(partition, key); + + // show that the partition was added + assertTrue(partitionsDataManager.partitionExists(key)); + + /* + * Get the expected structure by examining each person and grouping + * them by the label sets that we expect to find in the partition + */ + Map> expectedPartitionStructure = getExpectedStructure(c); + + /* + * Show that the expected structure matches the actual structure of + * the partition + */ + showPartitionIsCorrect(c, expectedPartitionStructure, key); + + /* + * Perform various changes to the people and their attributes. + */ + removePeople(c, 100); + addPeople(c, 120); + assignRandomAttributes(c); + + /* + * Get the expected structure again now that there have been changes + * to people's attributes + */ + expectedPartitionStructure = getExpectedStructure(c); + + /* + * Show that the expected structure matches the actual structure of + * the partition and thus the partition resolver must be maintaining + * the partition as stated in the contract. + */ + showPartitionIsCorrect(c, expectedPartitionStructure, key); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // if the key is already allocated to another population partition + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = PartitionsTestPluginFactory.factory(0, 1137046131619466337L, (c) -> { + PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); + Object key = new Object(); + partitionsDataManager.addPartition(Partition.builder().build(), key); + partitionsDataManager.addPartition(Partition.builder().build(), key); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PartitionError.DUPLICATE_PARTITION, contractException.getErrorType()); + + // precondition: if the partition is null + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = PartitionsTestPluginFactory.factory(0, 7407325994321033161L, (c) -> { + PartitionsPluginData partitionsPluginData = PartitionsPluginData.builder().build(); + PartitionsDataManager partitionsDataManager = new PartitionsDataManager(partitionsPluginData); + Object key = new Object(); + partitionsDataManager.addPartition(null, key); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PartitionError.NULL_PARTITION, contractException.getErrorType()); + + // precondition: if the key is null + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = PartitionsTestPluginFactory.factory(0, 530075900162852558L, (c) -> { + PartitionsPluginData partitionsPluginData = PartitionsPluginData.builder().build(); + PartitionsDataManager partitionsDataManager = new PartitionsDataManager(partitionsPluginData); + partitionsDataManager.addPartition(Partition.builder().build(), null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PartitionError.NULL_PARTITION_KEY, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = PartitionsDataManager.class, name = "removePartition", args = { Object.class }) + public void testRemovePartition() { + + Factory factory = PartitionsTestPluginFactory.factory(0, 5767679585616452606L, (c) -> { + + PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); + Object key = new Object(); + + // show that the partition does not yet exist + assertFalse(partitionsDataManager.partitionExists(key)); + + Partition partition = Partition.builder().build(); + partitionsDataManager.addPartition(partition, key); + + // show that the partition was added + assertTrue(partitionsDataManager.partitionExists(key)); + + // show that partition is removed + partitionsDataManager.removePartition(key); + assertFalse(partitionsDataManager.partitionExists(key)); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = PartitionsDataManager.class, name = "partitionExists", args = { Object.class }) + public void testPartitionExists() { + + Factory factory = PartitionsTestPluginFactory.factory(0, 1968926333881399732L, (c) -> { + PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); + + // create containers to hold known and unknown keys + Set knownKeys = new LinkedHashSet<>(); + for (int i = 0; i < 10; i++) { + Object key = "key" + i; + knownKeys.add(key); + } + + Set unknownKeys = new LinkedHashSet<>(); + for (int i = 10; i < 20; i++) { + Object key = "key" + i; + unknownKeys.add(key); + } + + // add a partition for each key + for (Object key : knownKeys) { + Partition partition = Partition.builder().build(); + partitionsDataManager.addPartition(partition, key); + } + + // show that the known keys will have a partition + for (Object key : knownKeys) { + assertTrue(partitionsDataManager.partitionExists(key)); + } + + // show that the unknown keys will not have a partition + for (Object key : unknownKeys) { + assertFalse(partitionsDataManager.partitionExists(key)); + } + + // show that the null key has no partition + assertFalse(partitionsDataManager.partitionExists(null)); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = PartitionsDataManager.class, name = "contains", args = { PersonId.class, Object.class }) + public void testContains() { + + Factory factory = PartitionsTestPluginFactory.factory(100, 607630153604184177L, (c) -> { + PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + + // create a partition where half the population is in the partition + Object key = new Object(); + Partition partition = Partition .builder()// + .setFilter(new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.INT_0, (v) -> 3)).build();// + + partitionsDataManager.addPartition(partition, key); + + // change the BOOLEAN_0 randomly for every person + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + for (PersonId personId : peopleDataManager.getPeople()) { + boolean newValue = randomGenerator.nextBoolean(); + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, newValue); + } + + // show that there is at least one person in the partition and one + // person outside the partition + int personCountInPartition = partitionsDataManager.getPersonCount(key); + assertTrue(personCountInPartition < peopleDataManager.getPopulationCount()); + assertTrue(personCountInPartition > 0); + + // show that a person is in the partition if and only if their + // BOOLEAN_0 attribute is true + for (PersonId personId : peopleDataManager.getPeople()) { + boolean expectPersonInPartition = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); + boolean actualPersonInPartition = partitionsDataManager.contains(personId, key); + assertEquals(expectPersonInPartition, actualPersonInPartition); + } + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = PartitionsDataManager.class, name = "contains", args = { PersonId.class, LabelSet.class, Object.class }) + public void testContains_LabelSet() { + + Factory factory = PartitionsTestPluginFactory.factory(100, 7338572401998066291L, (c) -> { + PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + + /* + * Define functions that will convert attribute values into labels + * for attributes INT_0, INT_1, DOUBLE_0, and DOUBLE_1. We will use + * these in the partition's labeling + */ + Function int_0_labelFunction = (value) -> { + int v = (Integer) value; + return v % 3; + }; + + Function int_1_labelFunction = (value) -> { + int v = (Integer) value; + if (v < 40) { + return true; + } + return false; + }; + + Function double_0_labelFunction = (value) -> { + double v = (Double) value; + if (v < 33) { + return "A"; + } + if (v < 67) { + return "B"; + } + return "C"; + }; + + Function double_1_labelFunction = (value) -> { + double v = (Double) value; + return v < 90; + }; + + // create a partition where half the population is in the partition + // with labeling + Object key = new Object(); + Partition partition = Partition .builder()// + .setFilter(new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.INT_0, int_0_labelFunction))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.INT_1, int_1_labelFunction)) + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.DOUBLE_0, double_0_labelFunction)) + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.DOUBLE_1, double_1_labelFunction)).build();// + + partitionsDataManager.addPartition(partition, key); + + // alter people's attributes randomly + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + for (PersonId personId : peopleDataManager.getPeople()) { + int intValue = (int) (randomGenerator.nextDouble() * 100); + attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_0, intValue); + + intValue = (int) (randomGenerator.nextDouble() * 100); + attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_1, intValue); + + double doubleValue = randomGenerator.nextDouble() * 100; + attributesDataManager.setAttributeValue(personId, TestAttributeId.DOUBLE_0, doubleValue); + + doubleValue = randomGenerator.nextDouble() * 100; + attributesDataManager.setAttributeValue(personId, TestAttributeId.DOUBLE_1, doubleValue); + + boolean booleanValue = randomGenerator.nextBoolean(); + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, booleanValue); + + booleanValue = randomGenerator.nextBoolean(); + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_1, booleanValue); + + } + + // Create a label set to use in the contains query + LabelSet queryLabelSet = LabelSet .builder()// + .setLabel(TestAttributeId.INT_0, 0)// + .setLabel(TestAttributeId.DOUBLE_0, "A")// + .build();// + + /* + * Show that a person is in the partition under the query label if + * and only if their BOOLEAN_0 attribute is true and their INT_0, + * and DOUBLE_0 labels are 0 and A + */ + for (PersonId personId : peopleDataManager.getPeople()) { + boolean contained = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); + + Integer int0Value = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_0); + Integer int0Label = (Integer) int_0_labelFunction.apply(int0Value); + + Double double0Value = attributesDataManager.getAttributeValue(personId, TestAttributeId.DOUBLE_0); + String double0Label = (String) double_0_labelFunction.apply(double0Value); + + boolean expectPersonInPartitionUnderLabel = contained && int0Label.equals(0) && double0Label.equals("A"); + + boolean actualPersonInPartitionUnderLabel = partitionsDataManager.contains(personId, queryLabelSet, key); + assertEquals(expectPersonInPartitionUnderLabel, actualPersonInPartitionUnderLabel); + } + + // precondition tests + PersonId personId = new PersonId(0); + assertTrue(peopleDataManager.personExists(personId)); + + PersonId unknownPersonId = new PersonId(10000000); + assertFalse(peopleDataManager.personExists(unknownPersonId)); + + Object unknownKey = new Object(); + + LabelSet badLabelSet = LabelSet.builder().setLabel(TestAttributeId.BOOLEAN_1, 0).build(); + + // if the person id is null + ContractException contractException = assertThrows(ContractException.class, () -> partitionsDataManager.contains(null, queryLabelSet, key)); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + // if the person id is unknown + contractException = assertThrows(ContractException.class, () -> partitionsDataManager.contains(unknownPersonId, queryLabelSet, key)); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + // if the key is null + contractException = assertThrows(ContractException.class, () -> partitionsDataManager.contains(personId, queryLabelSet, null)); + assertEquals(PartitionError.NULL_PARTITION_KEY, contractException.getErrorType()); + + // if the key is unknown + contractException = assertThrows(ContractException.class, () -> partitionsDataManager.contains(personId, queryLabelSet, unknownKey)); + assertEquals(PartitionError.UNKNOWN_POPULATION_PARTITION_KEY, contractException.getErrorType()); + + // if the label set is null + contractException = assertThrows(ContractException.class, () -> partitionsDataManager.contains(personId, null, key)); + assertEquals(PartitionError.NULL_LABEL_SET, contractException.getErrorType()); + + // if the label contains a dimension not present in the partition + contractException = assertThrows(ContractException.class, () -> partitionsDataManager.contains(personId, badLabelSet, key)); + assertEquals(PartitionError.INCOMPATIBLE_LABEL_SET, contractException.getErrorType()); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = PartitionsDataManager.class, name = "getPeople", args = { Object.class }) + public void testGetPeople() { + + // initialized with 100 people + Factory factory = PartitionsTestPluginFactory.factory(100, 6033209037401060593L, (c) -> { + + // establish data views + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + + // create a container to hold the people we expect to find in the + // partition + Set expectedPeople = new LinkedHashSet<>(); + + // alter people's attributes randomly + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + for (PersonId personId : peopleDataManager.getPeople()) { + int intValue = (int) (randomGenerator.nextDouble() * 100); + attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_0, intValue); + + boolean booleanValue = randomGenerator.nextBoolean(); + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, booleanValue); + + if (booleanValue) { + expectedPeople.add(personId); + } + } + + // create a partition that will contain about half of the population + Object key = new Object(); + Partition partition = Partition .builder()// + .setFilter(new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.INT_0, (value) -> { + int v = (Integer) value; + return v / 10; + }))// + .build(); + partitionsDataManager.addPartition(partition, key); + + // get the people in the partition + List peopleInPartition = partitionsDataManager.getPeople(key); + Set actualPeople = new LinkedHashSet<>(peopleInPartition); + // show that the list of people contained no duplicates + assertEquals(peopleInPartition.size(), actualPeople.size()); + + // show that there were the expected people + assertEquals(expectedPeople, actualPeople); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = PartitionsDataManager.class, name = "getPeople", args = { Object.class, LabelSet.class }) + public void testGetPeople_LabelSet() { + // initialized with 100 people + Factory factory = PartitionsTestPluginFactory.factory(100, 7761046492495930843L, (c) -> { + + // establish data views + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + + // define a function that will convert an integer into another + // integer that will be used as a labeling function for the + // partition + Function attributeValueLabelingFunction = (value) -> { + int v = (Integer) value; + return v / 10; + }; + + // alter people's INT_0 and BOOLEAN_0 attributes randomly + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + for (PersonId personId : peopleDataManager.getPeople()) { + int intValue = (int) (randomGenerator.nextDouble() * 100); + attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_0, intValue); + + boolean booleanValue = randomGenerator.nextBoolean(); + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, booleanValue); + + } + + // create a partition that will contain about half of the population + Object key = new Object(); + Partition partition = Partition .builder()// + .setFilter(new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.INT_0, attributeValueLabelingFunction))// + .build(); + + partitionsDataManager.addPartition(partition, key); + + // create a container to hold the people we expect to find in the + // partition associated with labels + Map> expectedLabelToPeopleMap = new LinkedHashMap<>(); + + // fill the expectedLabelToPeopleMap + for (PersonId personId : peopleDataManager.getPeople()) { + // will the person pass the filter? + + Boolean booleanValue = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); + if (booleanValue) { + // determine the label that should be associated with the + // person + Integer intValue = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_0); + Object labelValue = attributeValueLabelingFunction.apply(intValue); + + // place the person in the expectedLabelToPeopleMap + Set expectedPeople = expectedLabelToPeopleMap.get(labelValue); + if (expectedPeople == null) { + expectedPeople = new LinkedHashSet<>(); + expectedLabelToPeopleMap.put(labelValue, expectedPeople); + } + expectedPeople.add(personId); + } + } + + // for each label in the expectedLabelToPeopleMap, get the people in + // the partition who match that label value + for (Object labelValue : expectedLabelToPeopleMap.keySet()) { + + // get the people we expect to find + Set expectedPeople = expectedLabelToPeopleMap.get(labelValue); + + // get the people that the partition associates with the label + LabelSet labelSet = LabelSet.builder().setLabel(TestAttributeId.INT_0, labelValue).build(); + List peopleInPartition = partitionsDataManager.getPeople(key, labelSet); + Set actualPeople = new LinkedHashSet<>(peopleInPartition); + + /* + * Show that the list of people returned from the population + * partition contains no duplicates + */ + assertEquals(peopleInPartition.size(), actualPeople.size()); + + // show that expected and actual people are equal + assertEquals(expectedPeople, actualPeople); + } + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = PartitionsDataManager.class, name = "getPeopleCountMap", args = { Object.class, LabelSet.class }) + public void testGetPeopleCountMap() { + // initialized with 1000 people + Factory factory = PartitionsTestPluginFactory.factory(1000, 3993911184725585603L, (c) -> { + + // establish data views + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + + /* + * Define functions that will convert attribute values into labels + * for attributes INT_0, INT_1, DOUBLE_0, and DOUBLE_1. We will use + * these in the partition's labeling + */ + Function int_0_labelFunction = (value) -> { + int v = (Integer) value; + return v % 3; + }; + + Function int_1_labelFunction = (value) -> { + int v = (Integer) value; + if (v < 40) { + return true; + } + return false; + }; + + Function double_0_labelFunction = (value) -> { + double v = (Double) value; + if (v < 33) { + return "A"; + } + if (v < 67) { + return "B"; + } + return "C"; + }; + + Function double_1_labelFunction = (value) -> { + double v = (Double) value; + return v < 90; + }; + + // alter people's attributes randomly + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + for (PersonId personId : peopleDataManager.getPeople()) { + int intValue = (int) (randomGenerator.nextDouble() * 100); + attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_0, intValue); + + intValue = (int) (randomGenerator.nextDouble() * 100); + attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_1, intValue); + + double doubleValue = randomGenerator.nextDouble() * 100; + attributesDataManager.setAttributeValue(personId, TestAttributeId.DOUBLE_0, doubleValue); + + doubleValue = randomGenerator.nextDouble() * 100; + attributesDataManager.setAttributeValue(personId, TestAttributeId.DOUBLE_1, doubleValue); + + boolean booleanValue = randomGenerator.nextBoolean(); + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, booleanValue); + + booleanValue = randomGenerator.nextBoolean(); + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_1, booleanValue); + + } + + /* + * Create a partition that will contain about half of the population + * by filtering on BOOLEAN_0. We will partition on INT_0, INT_1, + * DOUBLE_0 and DOUBLE_1. Note that we do not use BOOLEAN_1 as part + * of the partition. + */ + Object key = new Object(); + Partition partition = Partition .builder()// + .setFilter(new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.INT_0, int_0_labelFunction))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.INT_1, int_1_labelFunction))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.DOUBLE_0, double_0_labelFunction))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.DOUBLE_1, double_1_labelFunction))// + .build(); + + partitionsDataManager.addPartition(partition, key); + + /* + * Create a container to hold the number of people we expect to find + * in the partition for every label set that is associated with at + * least one person. + */ + Map expectedPartitionContentMap = new LinkedHashMap<>(); + + // fill the expectedLabelToPeopleMap + for (PersonId personId : peopleDataManager.getPeople()) { + // will the person pass the filter? + Boolean booleanValue = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); + if (booleanValue) { + + // construct the label set for this person we expect to + // exist in the partition + LabelSet.Builder labelSetBuilder = LabelSet.builder(); + + Integer intValue = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_0); + Object labelValue = int_0_labelFunction.apply(intValue); + labelSetBuilder.setLabel(TestAttributeId.INT_0, labelValue); + + intValue = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_1); + labelValue = int_1_labelFunction.apply(intValue); + labelSetBuilder.setLabel(TestAttributeId.INT_1, labelValue); + + Double doubleValue = attributesDataManager.getAttributeValue(personId, TestAttributeId.DOUBLE_0); + labelValue = double_0_labelFunction.apply(doubleValue); + labelSetBuilder.setLabel(TestAttributeId.DOUBLE_0, labelValue); + + doubleValue = attributesDataManager.getAttributeValue(personId, TestAttributeId.DOUBLE_1); + labelValue = double_1_labelFunction.apply(doubleValue); + labelSetBuilder.setLabel(TestAttributeId.DOUBLE_1, labelValue); + + LabelSet labelSet = labelSetBuilder.build(); + + // place the person in the expectedLabelToPeopleMap + MutableInteger mutableInteger = expectedPartitionContentMap.get(labelSet); + if (mutableInteger == null) { + mutableInteger = new MutableInteger(); + expectedPartitionContentMap.put(labelSet, mutableInteger); + } + mutableInteger.increment(); + } + } + + /* + * We will form our query using two of the four partition dimensions + * so that the maps returned by the queries will contain multiple + * members. + * + * We want to test create queries using INT_0 and DOUBLE_0 across + * all their known label values, but also want include some label + * values we known will not be present in the partition. + */ + + Set int_0_label_values = new LinkedHashSet<>(); + int_0_label_values.add(0); + int_0_label_values.add(1); + int_0_label_values.add(2); + int_0_label_values.add(3);// will not match any person + + Set double_0_label_values = new LinkedHashSet<>(); + double_0_label_values.add("A"); + double_0_label_values.add("B"); + double_0_label_values.add("C"); + double_0_label_values.add("D");// will not match any person + + for (Integer int_0_label_value : int_0_label_values) { + for (String double_0_label_value : double_0_label_values) { + + /* + * Create a label set for the query that does not contain + * all the attribute labels and has legitimate values for + * each dimension. + */ + LabelSet.Builder labelSetBuilder = LabelSet.builder(); + labelSetBuilder.setLabel(TestAttributeId.INT_0, int_0_label_value); + labelSetBuilder.setLabel(TestAttributeId.DOUBLE_0, double_0_label_value); + LabelSet queryLabelSet = labelSetBuilder.build(); + + /* + * We are only interested in those parts of the + * expectedPartitionContentMap that match the query's label + * set. + */ + Map expectedCountMap = new LinkedHashMap<>(); + for (LabelSet labelSet : expectedPartitionContentMap.keySet()) { + boolean isMatchingLabelSet = true; + for (Object dimension : queryLabelSet.getDimensions()) { + Optional queryLabel = queryLabelSet.getLabel(dimension); + Optional label = labelSet.getLabel(dimension); + if (!queryLabel.equals(label)) { + isMatchingLabelSet = false; + break; + } + } + if (isMatchingLabelSet) { + MutableInteger mutableInteger = expectedPartitionContentMap.get(labelSet); + expectedCountMap.put(labelSet, mutableInteger.getValue()); + } + } + + /* + * Show that the count map we receive from the partition + * matches our expectation + */ + Map actualCountMap = partitionsDataManager.getPeopleCountMap(key, queryLabelSet); + assertEquals(expectedCountMap, actualCountMap); + } + } + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = PartitionsDataManager.class, name = "getPersonCount", args = { Object.class }) + public void getPersonCount() { + + // initialized with 100 people + Factory factory = PartitionsTestPluginFactory.factory(100, 1559429415782871174L, (c) -> { + + // establish data views + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + + // create a couter to hold the number people we expect to find in + // the + // partition + int expectedPeopleCount = 0; + + // alter people's attributes randomly + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + for (PersonId personId : peopleDataManager.getPeople()) { + int intValue = (int) (randomGenerator.nextDouble() * 100); + attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_0, intValue); + + boolean booleanValue = randomGenerator.nextBoolean(); + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, booleanValue); + if (booleanValue) { + expectedPeopleCount++; + } + } + + // create a partition that will contain about half of the population + Object key = new Object(); + Partition partition = Partition .builder()// + .setFilter(new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.INT_0, (value) -> { + int v = (Integer) value; + return v / 10; + }))// + .build(); + partitionsDataManager.addPartition(partition, key); + + // get the people in the partition + int actualCount = partitionsDataManager.getPersonCount(key); + + // show that there were the expected people + assertEquals(expectedPeopleCount, actualCount); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = PartitionsDataManager.class, name = "getPersonCount", args = { Object.class, LabelSet.class }) + public void testGetPersonCount_LabelSet() { + // initialized with 100 people + Factory factory = PartitionsTestPluginFactory.factory(100, 3217787540697556531L, (c) -> { + + // establish data views + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + + // define a function that will convert an integer into another + // integer that will be used as a labeling function for the + // partition + Function attributeValueLabelingFunction = (value) -> { + int v = (Integer) value; + return v % 10; + }; + + // alter people's INT_0 and BOOLEAN_0 attributes randomly + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + for (PersonId personId : peopleDataManager.getPeople()) { + int intValue = (int) (randomGenerator.nextDouble() * 100); + attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_0, intValue); + + boolean booleanValue = randomGenerator.nextBoolean(); + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, booleanValue); + + } + + // create a partition that will contain about half of the population + Object key = new Object(); + Partition partition = Partition .builder()// + .setFilter(new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.INT_0, attributeValueLabelingFunction))// + .build(); + + partitionsDataManager.addPartition(partition, key); + + // create a container to hold the people we expect to find in the + // partition associated with labels + Map expectedPeopleCountMap = new LinkedHashMap<>(); + + // fill the expectedLabelToPeopleMap + for (PersonId personId : peopleDataManager.getPeople()) { + // will the person pass the filter? + Boolean booleanValue = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); + if (booleanValue) { + // determine the label that should be associated with the + // person + Integer intValue = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_0); + Object labelValue = attributeValueLabelingFunction.apply(intValue); + + // place the person in the expectedLabelToPeopleMap + MutableInteger mutableInteger = expectedPeopleCountMap.get(labelValue); + if (mutableInteger == null) { + mutableInteger = new MutableInteger(); + expectedPeopleCountMap.put(labelValue, mutableInteger); + } + mutableInteger.increment(); + } + } + + // for each label in the expectedLabelToPeopleMap, get the people in + // the partition who match that label value + for (Object labelValue : expectedPeopleCountMap.keySet()) { + + // get the people we expect to find + MutableInteger expectedPeopleCount = expectedPeopleCountMap.get(labelValue); + + // get the people that the partition associates with the label + LabelSet labelSet = LabelSet.builder().setLabel(TestAttributeId.INT_0, labelValue).build(); + int actualPersonCount = partitionsDataManager.getPersonCount(key, labelSet); + + // show that expected and actual people counts are equal + assertEquals(expectedPeopleCount.getValue(), actualPersonCount); + } + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + private static enum ExcludedPersonType { + NULL, MATCHING_MEMBER, NON_MATCHING_MEMBER, NON_MEMBER; + } + + @Test + @UnitTestMethod(target = PartitionsDataManager.class, name = "samplePartition", args = { Object.class, PartitionSampler.class }) + public void testSamplePartition_General() { + + /* + * Tests the sample mechanism under a variety of partition samplers. + * + * The partition is formed from 4 labeling functions over the attributes + * + * INT_0-> 0, 1, 2 + * + * INT_1 -> TRUE, FALSE + * + * DOUBLE_0 -> A, B, C + * + * DOUBLE_1 -> TRUE, FALSE + * + * Filtering for the partition is either on or off. The filter passes + * when the attribute BOOLEAN_0 is true. + * + * The partition sampler will optionally set its excluded person to + * null, a person not in the partition, a person in the partition who is + * not expected to match the sampler's label set and a person who does + * match the sampler's label set. + * + * The partition sampler will optionally use a label set. The label set + * will be composed of combinations of labels over INT_0 and DOUBLE_0, + * using label values that are associated with people and some that are + * not. + * + * The partition sampler will optionally use a weighting function. The + * weighting function will return 1 for any person having a label of + * TRUE for INT_1 and 0 otherwise. + * + * This test does not demonstrate precondition checks, proper use of + * random number generator ids, or the proper distribution of results + * aligned to the weighting function other that the simple binary + * alignment for the weighting function described above. + * + * Each combination is run with a randomly generated seed value. + * + */ + + Set int_0_label_values = new LinkedHashSet<>(); + int_0_label_values.add(0); + int_0_label_values.add(1); + int_0_label_values.add(2); + int_0_label_values.add(3);// will not match any person + + Set double_0_label_values = new LinkedHashSet<>(); + double_0_label_values.add("A"); + double_0_label_values.add("B"); + double_0_label_values.add("C"); + double_0_label_values.add("D");// will not match any person + + Set weightingFunctionValues = new LinkedHashSet<>(); + weightingFunctionValues.add(false); + weightingFunctionValues.add(true); + + Set useFilterValues = new LinkedHashSet<>(); + useFilterValues.add(false); + useFilterValues.add(true); + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7729976665156925181L); + + for (ExcludedPersonType excludedPersonType : ExcludedPersonType.values()) { + for (Boolean useWeightingFunction : weightingFunctionValues) { + for (Boolean useFilter : useFilterValues) { + long seed = randomGenerator.nextLong(); + executeSamplingTest(seed, useFilter, excludedPersonType, useWeightingFunction, null, null); + for (Integer int_0_label_value : int_0_label_values) { + for (String double_0_label_value : double_0_label_values) { + seed = randomGenerator.nextLong(); + executeSamplingTest(seed, useFilter, excludedPersonType, useWeightingFunction, int_0_label_value, double_0_label_value); + } + } + } + } + } + + } + + private void executeSamplingTest(long seed, Boolean useFilter, ExcludedPersonType excludedPersonType, Boolean useWeightingFunction, Integer int_0_label_value, String double_0_label_value) { + + Factory factory = PartitionsTestPluginFactory.factory(1000, seed, (c) -> { + + // remember to test with general and COMET to show they get + // different results? + + // establish data views + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + + /* + * Define functions that will convert attribute values into labels + * for attributes INT_0, INT_1, DOUBLE_0, and DOUBLE_1. We will use + * these in the partition's labeling + */ + Function int_0_labelFunction = (value) -> { + int v = (Integer) value; + return v % 3; + }; + + Function int_1_labelFunction = (value) -> { + int v = (Integer) value; + if (v < 40) { + return true; + } + return false; + }; + + Function double_0_labelFunction = (value) -> { + double v = (Double) value; + if (v < 33) { + return "A"; + } + if (v < 67) { + return "B"; + } + return "C"; + }; + + Function double_1_labelFunction = (value) -> { + double v = (Double) value; + return v < 90; + }; + + // determine the people in the world + List peopleInTheWorld = peopleDataManager.getPeople(); + + // alter people's attributes randomly + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + for (PersonId personId : peopleInTheWorld) { + int intValue = (int) (randomGenerator.nextDouble() * 100); + attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_0, intValue); + + intValue = (int) (randomGenerator.nextDouble() * 100); + attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_1, intValue); + + double doubleValue = randomGenerator.nextDouble() * 100; + attributesDataManager.setAttributeValue(personId, TestAttributeId.DOUBLE_0, doubleValue); + + doubleValue = randomGenerator.nextDouble() * 100; + attributesDataManager.setAttributeValue(personId, TestAttributeId.DOUBLE_1, doubleValue); + + boolean booleanValue = randomGenerator.nextBoolean(); + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, booleanValue); + + booleanValue = randomGenerator.nextBoolean(); + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_1, booleanValue); + + } + + /* + * Create a partition that may filter about half of the population + * on BOOLEAN_0. We will partition on INT_0, INT_1, DOUBLE_0 and + * DOUBLE_1. Note that we do not use BOOLEAN_1 as part of the + * partition. + */ + Object key = new Object(); + Partition.Builder partitionBuilder = Partition.builder(); + if (useFilter) { + partitionBuilder// + .setFilter(new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true));// + } + partitionBuilder// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.INT_0, int_0_labelFunction))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.INT_1, int_1_labelFunction))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.DOUBLE_0, double_0_labelFunction))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.DOUBLE_1, double_1_labelFunction)); + + Partition partition = partitionBuilder.build(); + + partitionsDataManager.addPartition(partition, key); + + /* + * Create a label set for the query that does not contain all the + * attribute labels and has legitimate values for each dimension. + */ + LabelSet queryLabelSet = null; + if (int_0_label_value != null && double_0_label_value != null) { + LabelSet.Builder labelSetBuilder = LabelSet.builder(); + labelSetBuilder.setLabel(TestAttributeId.INT_0, int_0_label_value); + labelSetBuilder.setLabel(TestAttributeId.DOUBLE_0, double_0_label_value); + queryLabelSet = labelSetBuilder.build(); + } + + // determine the people in the partition + Set expectedPeopleInPartition = new LinkedHashSet<>(); + + for (PersonId personId : peopleInTheWorld) { + + if (useFilter) { + Boolean personInPartition = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); + if (personInPartition) { + expectedPeopleInPartition.add(personId); + } + } else { + expectedPeopleInPartition.add(personId); + } + + } + + // determine the people who will match the query label set + Set expectedPeopleMatchingQueryLabelSet = new LinkedHashSet<>(); + if (queryLabelSet != null) { + for (PersonId personId : expectedPeopleInPartition) { + // will the person pass the filter? + + Integer intValue = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_0); + Object labelValue = int_0_labelFunction.apply(intValue); + if (labelValue.equals(int_0_label_value)) { + Double doubleValue = attributesDataManager.getAttributeValue(personId, TestAttributeId.DOUBLE_0); + labelValue = double_0_labelFunction.apply(doubleValue); + if (labelValue.equals(double_0_label_value)) { + expectedPeopleMatchingQueryLabelSet.add(personId); + } + + } + + } + } else { + expectedPeopleMatchingQueryLabelSet.addAll(expectedPeopleInPartition); + } + + PartitionSampler.Builder partitionSamplerBuilder = PartitionSampler.builder(); + partitionSamplerBuilder.setLabelSet(queryLabelSet);// + + // Select the excluded person + PersonId excludedPersonId = null; + switch (excludedPersonType) { + case MATCHING_MEMBER: + for (PersonId personId : expectedPeopleMatchingQueryLabelSet) { + excludedPersonId = personId; + break; + } + + break; + case NON_MATCHING_MEMBER: + for (PersonId personId : expectedPeopleInPartition) { + if (!expectedPeopleMatchingQueryLabelSet.contains(personId)) { + excludedPersonId = personId; + break; + } + } + break; + + case NON_MEMBER: + if (useFilter) { + for (PersonId personId : peopleInTheWorld) { + if (!expectedPeopleInPartition.contains(personId)) { + excludedPersonId = personId; + break; + } + } + } + break; + + case NULL: + // do nothing + break; + default: + throw new RuntimeException("unhandled case: " + excludedPersonType); + } + partitionSamplerBuilder.setExcludedPerson(excludedPersonId); + + Set expectedPeopleMatchingPartitionSampler = new LinkedHashSet<>(expectedPeopleMatchingQueryLabelSet); + expectedPeopleMatchingPartitionSampler.remove(excludedPersonId); + + if (useWeightingFunction) { + Iterator iterator = expectedPeopleMatchingPartitionSampler.iterator(); + while (iterator.hasNext()) { + PersonId personId = iterator.next(); + Integer int_1_attributeValue = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_1); + Boolean passed = (Boolean) int_1_labelFunction.apply(int_1_attributeValue); + if (!passed) { + iterator.remove(); + } + } + + LabelSetWeightingFunction labelSetWeightingFunction = (c2, labelSet) -> { + Boolean value = (Boolean) labelSet.getLabel(TestAttributeId.INT_1).get(); + if (value) { + return 1; + } else { + return 0; + } + }; + partitionSamplerBuilder.setLabelSetWeightingFunction(labelSetWeightingFunction); + } + + PartitionSampler partitionSampler = partitionSamplerBuilder.build(); + + int samplingCount = FastMath.min(expectedPeopleMatchingQueryLabelSet.size() + 1, 10); + + for (int i = 0; i < samplingCount; i++) { + Optional optional = partitionsDataManager.samplePartition(key, partitionSampler); + if (optional.isPresent()) { + PersonId selectedPerson = optional.get(); + assertTrue(expectedPeopleMatchingPartitionSampler.contains(selectedPerson)); + } else { + assertTrue(expectedPeopleMatchingPartitionSampler.isEmpty()); + } + } + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = PartitionsDataManager.class, name = "samplePartition", args = { Object.class, PartitionSampler.class }) + public void testSamplePartition_PreconditionChecks() { + // precondition: if the key is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory = PartitionsTestPluginFactory.factory(10, 8368182028203057994L, (c) -> { + PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); + Object key = new Object(); + Partition partition = Partition.builder().setFilter(new TrueFilter()).build(); + partitionsDataManager.addPartition(partition, key); + PartitionSampler partitionSampler = PartitionSampler.builder().build(); + // first we show that the values we will be using are valid + assertNotNull(partitionsDataManager.samplePartition(key, partitionSampler)); + partitionsDataManager.samplePartition(null, partitionSampler); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + }); + assertEquals(PartitionError.NULL_PARTITION_KEY, contractException.getErrorType()); + + // precondition: if the key is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory = PartitionsTestPluginFactory.factory(10, 2301450217287059237L, (c) -> { + PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); + Object key = new Object(); + Partition partition = Partition.builder().setFilter(new TrueFilter()).build(); + partitionsDataManager.addPartition(partition, key); + PartitionSampler partitionSampler = PartitionSampler.builder().build(); + Object unknownKey = new Object(); + // first we show that the values we will be using are valid + assertNotNull(partitionsDataManager.samplePartition(key, partitionSampler)); + partitionsDataManager.samplePartition(unknownKey, partitionSampler); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + }); + assertEquals(PartitionError.UNKNOWN_POPULATION_PARTITION_KEY, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, () -> { + Factory factory = PartitionsTestPluginFactory.factory(10, 8837909864261179707L, (c) -> { + PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); + Object key = new Object(); + Partition partition = Partition.builder().setFilter(new TrueFilter()).build(); + partitionsDataManager.addPartition(partition, key); + PartitionSampler partitionSampler = PartitionSampler.builder().build(); + // first we show that the values we will be using are valid + assertNotNull(partitionsDataManager.samplePartition(key, partitionSampler)); + // if the partition sampler is null + partitionsDataManager.samplePartition(key, null); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + }); + assertEquals(PartitionError.NULL_PARTITION_SAMPLER, contractException.getErrorType()); + + /* + * precondition: if the partition sampler has a label set containing + * dimensions not present in the population partition + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory = PartitionsTestPluginFactory.factory(10, 1697817005173536231L, (c) -> { + PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); + Object key = new Object(); + Partition partition = Partition.builder().setFilter(new TrueFilter()).build(); + partitionsDataManager.addPartition(partition, key); + PartitionSampler partitionSampler = PartitionSampler.builder().build(); + LabelSet labelSet = LabelSet.builder().setLabel(TestAttributeId.INT_0, 15).build(); + PartitionSampler partitionSamplerWithBadDimension = PartitionSampler.builder().setLabelSet(labelSet).build(); + // first we show that the values we will be using are valid + assertNotNull(partitionsDataManager.samplePartition(key, partitionSampler)); + partitionsDataManager.samplePartition(key, partitionSamplerWithBadDimension); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + }); + assertEquals(PartitionError.INCOMPATIBLE_LABEL_SET, contractException.getErrorType()); + + /* + * precondition: if the partition sampler has an excluded person that + * does not exist + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory = PartitionsTestPluginFactory.factory(10, 624346712512051803L, (c) -> { + PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); + Object key = new Object(); + Partition partition = Partition.builder().setFilter(new TrueFilter()).build(); + partitionsDataManager.addPartition(partition, key); + PartitionSampler partitionSampler = PartitionSampler.builder().build(); + PartitionSampler partitionSamplerWithUnknownExcludedPerson = PartitionSampler.builder().setExcludedPerson(new PersonId(10000)).build(); + // first we show that the values we will be using are valid + assertNotNull(partitionsDataManager.samplePartition(key, partitionSampler)); + partitionsDataManager.samplePartition(key, partitionSamplerWithUnknownExcludedPerson); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + }); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PartitionsDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testPartitionDataManagerInitialization() { + Factory factory = PartitionsTestPluginFactory.factory(0, 2954766214498605129L, (c) -> { + PartitionsDataManager dataManager = c.getDataManager(PartitionsDataManager.class); + assertNotNull(dataManager); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = PartitionsDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testPersonAdditionEvent() { + Factory factory = PartitionsTestPluginFactory.factory(100, 6964380012813498875L, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); + + /* + * Create keys for the two population partitions. One that accepts + * people with attribute BOOLEAN_0 = true and the other with + * BOOLEAN_0 = false. + */ + Object key1 = new Object(); + Object key2 = new Object(); + + // add the partitions + Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); + Partition partition1 = Partition.builder().setFilter(filter).build(); + partitionsDataManager.addPartition(partition1, key1); + + filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, false); + Partition partition2 = Partition.builder().setFilter(filter).build(); + partitionsDataManager.addPartition(partition2, key2); + + // add a new person, by default they will have BOOLEAN_0 = false + // determine the person id of the person just added + PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); + + // show that the person is not a member of partition 1 + assertFalse(partitionsDataManager.contains(personId, key1)); + + // show that the person is a member of partition 2 + assertTrue(partitionsDataManager.contains(personId, key2)); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = PartitionsDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testPersonRemovalEvent() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + /* + * Create a key for a partition of interest that will contain a person + * we are about to delete + */ + Object key = new Object(); + + /* + * Add an agent that will create a partition that will contain 10 people + * of interest who will be removed later. + */ + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + + // select 10 people + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); + List people = peopleDataManager.getPeople(); + List peopleOfInterest = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + peopleOfInterest.add(people.get(i)); + } + + /* + * Give these people an attribute BOOLEAN_0 a value of true so they + * will be included in the partition + */ + for (PersonId personId : peopleOfInterest) { + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, true); + } + + /* + * Create a partition that will include the people of interest + */ + Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); + Partition partition = Partition.builder().setFilter(filter).build(); + partitionsDataManager.addPartition(partition, key); + + // show that the partition does contain the people of interest + List actualPeople = partitionsDataManager.getPeople(key); + assertEquals(peopleOfInterest.size(), actualPeople.size()); + assertEquals(new LinkedHashSet<>(peopleOfInterest), new LinkedHashSet<>(actualPeople)); + + })); + + /* + * Create an observer that subscribes to the PersonImminentRemovalEvent. + * This will be used to show that a report or any other observer can + * still see a person and their membership in a partition even though + * the removal of the person is already underway. + * + * The report will record the ids of the people who were in the removal + * process + */ + List peopleVerifiedByReport = new ArrayList<>(); + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + c.subscribe(EventFilter.builder(PersonImminentRemovalEvent.class).build(), (c2, e) -> { + + PersonId personId = e.personId(); + + // show that the person is still in the partition + PartitionsDataManager partitionsDataManager = c2.getDataManager(PartitionsDataManager.class); + assertTrue(partitionsDataManager.contains(personId, key)); + + // add the person to the verified list for later use + peopleVerifiedByReport.add(personId); + + }); + })); + + /* + * Have the agent remove the people who are in the partition from the + * simulation. The people will temporarily remain in the simulation and + * will only leave when the planning system moves to the next plan. + */ + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + + // Remove from the simulation the people who are in the partition + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); + List people = partitionsDataManager.getPeople(key); + for (PersonId personId : people) { + peopleDataManager.removePerson(personId); + } + + // show that the people still exist + + for (PersonId personId : people) { + assertTrue(peopleDataManager.personExists(personId)); + } + List peopleImmediatelyAfterRemoval = partitionsDataManager.getPeople(key); + assertEquals(people, peopleImmediatelyAfterRemoval); + + })); + + /* + * Have the agent verify that the people are gone and that the partition + * no longer contains them. Note that this plan is for the same time as + * the plan above but is guaranteed to execute after that plan. + */ + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + /* + * Show that the report, as an observer of the removals, was able to + * observe each removal and still perceived each person as being a + * member of the partition. + */ + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + assertEquals(10, peopleVerifiedByReport.size()); + + // show that each of these people is no longer in the simulation + for (PersonId personId : peopleVerifiedByReport) { + assertFalse(peopleDataManager.personExists(personId)); + } + + // show that the partition is empty + PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); + assertEquals(0, partitionsDataManager.getPersonCount(key)); + + })); + + // build and add the action plugin to the engine + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = PartitionsTestPluginFactory.factory(100, 6406306513403641718L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + /** + * Demonstrates that the data manager's initial state reflects its plugin + * data + */ + @Test + @UnitTestMethod(target = PartitionsDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testStateInitialization() { + /* + * Nothing can be demonstrated since the state of the plugin data does + * not have an observable influence on the data manager + */ + } + + /** + * Demonstrates that the data manager produces plugin data that reflects its + * final state + */ + @Test + @UnitTestMethod(target = PartitionsDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testStateFinalization() { + + for (boolean supportRunContinuity : new boolean[] { true, false }) { + + // build a plugin factory and replace the partitions plugin with one + // that has run continuity set to the expected state + Factory factory = PartitionsTestPluginFactory.factory(100, 607630153604184177L, (c) -> { + }); + PartitionsPluginData inputPartitionsPluginData = PartitionsPluginData .builder()// + .setRunContinuitySupport(supportRunContinuity)// + .build(); + Plugin partitionsPlugin = PartitionsPlugin .builder()// + .setPartitionsPluginData(inputPartitionsPluginData)// + .getPartitionsPlugin(); + factory.setPartitionsPlugin(partitionsPlugin); + + // run the simulation and tell it to produce plugin data on halt + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .setSimulationHaltTime(100)// + .setProduceSimulationStateOnHalt(true)// + .build()// + .execute(); + + // retrieve the PartitionsPluginData released by the + // PartitionsDataManager + Optional optional = testOutputConsumer.getOutputItem(PartitionsPluginData.class); + assertTrue(optional.isPresent()); + PartitionsPluginData outputPartitionsPluginData = optional.get(); + + // show that the output plugin data is equal to the input plugin + // data + assertEquals(inputPartitionsPluginData, outputPartitionsPluginData); + } + } + + @Test + @UnitTestMethod(target = PartitionsDataManager.class, name = "getPersonValue", args = {Object.class, LabelSetFunction.class, PersonId.class }) + public void testGetPersonValue() { + RandomGenerator rng = RandomGeneratorProvider.getRandomGenerator(1889608169419896318L); + long seed = rng.nextLong(); + + String key = "key"; + + /* + * Define functions that will convert attribute values into labels for + * attributes INT_0, INT_1, DOUBLE_0, and DOUBLE_1. We will use these in + * the partition's labeling + */ + Function int_0_labelFunction = (value) -> { + int v = (Integer) value; + return v % 3; + }; + + Function int_1_labelFunction = (value) -> { + int v = (Integer) value; + if (v < 40) { + return true; + } + return false; + }; + + Function double_0_labelFunction = (value) -> { + double v = (Double) value; + if (v < 33) { + return "A"; + } + if (v < 67) { + return "B"; + } + return "C"; + }; + + Function double_1_labelFunction = (value) -> { + double v = (Double) value; + return v < 90; + }; + + TestPluginData.Builder testPluginDataBuilder = TestPluginData.builder(); + + /* + * Have the actor set the attribute values for each person to random + * values + */ + testPluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + // determine the people in the world + List peopleInTheWorld = peopleDataManager.getPeople(); + + // alter people's attributes randomly + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + for (PersonId personId : peopleInTheWorld) { + int intValue = (int) (randomGenerator.nextDouble() * 100); + attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_0, intValue); + + intValue = (int) (randomGenerator.nextDouble() * 100); + attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_1, intValue); + + double doubleValue = randomGenerator.nextDouble() * 100; + attributesDataManager.setAttributeValue(personId, TestAttributeId.DOUBLE_0, doubleValue); + + doubleValue = randomGenerator.nextDouble() * 100; + attributesDataManager.setAttributeValue(personId, TestAttributeId.DOUBLE_1, doubleValue); + + boolean booleanValue = randomGenerator.nextBoolean(); + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, booleanValue); + + booleanValue = randomGenerator.nextBoolean(); + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_1, booleanValue); + } + })); + + /* + * Have the actor add a partition under the key that has four labelers + * corresponding to the functions above and a simple filter based on + * TestAttributeId.BOOLEAN_0. + */ + testPluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); + + /* + * Create a partition that may filter about half of the population + * on BOOLEAN_0. We will partition on INT_0, INT_1, DOUBLE_0 and + * DOUBLE_1. Note that we do not use BOOLEAN_1 as part of the + * partition. + */ + Partition.Builder partitionBuilder = Partition.builder(); + + partitionBuilder.setFilter(new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true));// + + partitionBuilder// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.INT_0, int_0_labelFunction))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.INT_1, int_1_labelFunction))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.DOUBLE_0, double_0_labelFunction))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.DOUBLE_1, double_1_labelFunction)); + + Partition partition = partitionBuilder.build(); + partitionsDataManager.addPartition(partition, key); + })); + + // Have the actor get person values using a labeling function and show + // that the result of the function matches expectations. + testPluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + + // establish data views + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); + + for (PersonId personId : peopleDataManager.getPeople()) { + + + + LabelSetFunction f = (pc, labelset) -> { + Integer i = (Integer) labelset.getLabel(TestAttributeId.INT_0).get(); + Boolean b1 = (Boolean) labelset.getLabel(TestAttributeId.INT_1).get(); + String s = (String) labelset.getLabel(TestAttributeId.DOUBLE_0).get(); + Boolean b2 = (Boolean) labelset.getLabel(TestAttributeId.DOUBLE_1).get(); + int result = i; + if (b1) { + result += 20; + } + switch (s) { + case "A": + result *= 2; + break; + case "B": + result *= 3; + break; + default: + result *= 4; + break; + } + if (b2) { + result += 17; + } + return result; + }; + + + Optional optional = partitionsDataManager.getPersonValue(key, f, personId); + + // the person should be in the partition if and only if the + // optional is present + Boolean expectedInclusion = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); + assertEquals(expectedInclusion, optional.isPresent()); + + if (optional.isPresent()) { + // determine the expected value of the function + + //first, get the attribute values of the person + int i0 = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_0); + int i1 = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_1); + double d0 = attributesDataManager.getAttributeValue(personId, TestAttributeId.DOUBLE_0); + double d1 = attributesDataManager.getAttributeValue(personId, TestAttributeId.DOUBLE_1); + + //now determine what the labelers will do with those values + int i = (Integer)int_0_labelFunction.apply(i0); + boolean b1 = (Boolean)int_1_labelFunction.apply(i1); + String s = (String)double_0_labelFunction.apply(d0); + boolean b2 = (Boolean)double_1_labelFunction.apply(d1); + + //finally calculate what the label function will do with the label values + int expectedValue = i; + if (b1) { + expectedValue += 20; + } + switch (s) { + case "A": + expectedValue *= 2; + break; + case "B": + expectedValue *= 3; + break; + default: + expectedValue *= 4; + break; + } + if (b2) { + expectedValue += 17; + } + + // show the values are equal + assertEquals(expectedValue, optional.get().intValue()); + } + + } + })); + + TestPluginData testPluginData = testPluginDataBuilder.build(); + + Factory factory = PartitionsTestPluginFactory.factory(1000, seed, testPluginData); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + +} \ No newline at end of file diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/datamanagers/AT_PartitionsDataManager_Continuity.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/datamanagers/AT_PartitionsDataManager_Continuity.java new file mode 100644 index 000000000..4bbd691e3 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/datamanagers/AT_PartitionsDataManager_Continuity.java @@ -0,0 +1,468 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.datamanagers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Consumer; + +import org.apache.commons.math3.random.RandomGenerator; +import org.apache.commons.math3.util.FastMath; +import org.apache.commons.math3.util.Pair; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.Simulation; +import gov.hhs.aspr.ms.gcm.nucleus.SimulationState; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.runcontinuityplugin.RunContinuityPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.runcontinuityplugin.RunContinuityPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestOutputConsumer; +import gov.hhs.aspr.ms.gcm.plugins.partitions.PartitionsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.Equality; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.LabelSet; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.Partition; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionSampler; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.AttributesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.AttributesPlugin; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.AttributesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.AttributesPluginId; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support.AttributeFilter; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support.AttributeLabeler; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support.TestAttributeId; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePlugin; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeoplePluginData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonConstructionData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import util.annotations.UnitTestMethod; +import util.random.RandomGeneratorProvider; + +public final class AT_PartitionsDataManager_Continuity { + + // create a key value for the partition + private final static Object key = "key"; + + private static enum B1_Label { + A, B + } + + private static enum I0_Label { + YES, NO + } + + private static enum I1_Label { + LOW, MEDIUM, HIGH + } + + /** + * Demonstrates that the data manager exhibits run continuity. The state of + * the data manager is not effected by repeatedly starting and stopping the + * simulation. + * + * This test is somewhat indirect. The partitions data manager does + * serialize its partitions at the end of the simulation and so we use the + * state of the Attributes data manager as a proxy to show that repeatedly + * starting and stopping a simulation that is actively using the partition + * sampling mechanism will result in a run-continuous simulation.* + */ + @Test + @UnitTestMethod(target = PartitionsDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testStateContinuity() { + + // We will run a simulation that sample from a partition to select + // people to mutate. + + Set attributeStates = new LinkedHashSet<>(); + attributeStates.add(testStateContinuity(1)); + attributeStates.add(testStateContinuity(5)); + attributeStates.add(testStateContinuity(10)); + + assertEquals(1, attributeStates.size()); + + String state = attributeStates.iterator().next(); + assertNotNull(state); + assertTrue(state.length() > 0); + + } + + /* + * Partitions are not explicitly serialized. We have to recreate the + * partition each time the simulation executes on the first use of the + * partition in each simulation instance. + * + * The partition consists of: + * + * A filter on TestAttributeId.BOOLEAN_0 + * + * A labeler TestAttributeId.BOOLEAN_1 that labels people with B1_Label.A or + * B1_Label.B + * + * A labeler TestAttributeId.INT_0 that labels people with I0_Label.YES or + * I0_Label.NO + * + * A labeler TestAttributeId.INT_1 that labels people with I1_Label.LOW, + * I1_Label.MEDIUM, or I1_Label.HIGH + */ + private static void makePartition(ActorContext c) { + PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); + + AttributeFilter attributeFilter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); + + AttributeLabeler attributeLabeler1 = new AttributeLabeler(TestAttributeId.BOOLEAN_1) { + protected Object getLabelFromValue(Object value) { + Boolean b = (Boolean) value; + if (b) { + return B1_Label.A; + } else { + return B1_Label.B; + } + } + }; + + AttributeLabeler attributeLabeler2 = new AttributeLabeler(TestAttributeId.INT_0) { + protected Object getLabelFromValue(Object value) { + Integer i = (Integer) value; + if (i < 3) { + return I0_Label.YES; + } + return I0_Label.NO; + } + }; + + AttributeLabeler attributeLabeler3 = new AttributeLabeler(TestAttributeId.INT_1) { + protected Object getLabelFromValue(Object value) { + Integer i = (Integer) value; + if (i < 30) { + return I1_Label.LOW; + } else if (i < 70) { + return I1_Label.MEDIUM; + } else { + return I1_Label.HIGH; + } + + } + }; + + Partition partition = Partition .builder()// + .setFilter(attributeFilter)// + .addLabeler(attributeLabeler1)// + .addLabeler(attributeLabeler2)// + .addLabeler(attributeLabeler3)// + .build(); + + partitionsDataManager.addPartition(partition, key); + + } + + /* + * Returns the run continuity plugin data. This consists of: + * + * 1)An initial task at time zero to create 1000 people and assign them + * randomized attribute values + * + * 2)100 tasks to A) select a person via partition sampling and randomly + * mutate their attributes, and B) select a person at random from the + * general population and randomly mutate their attributes + * + * 3)A final task to release the attribute data manager's string + * representation to output. This ouput will be collected and compared for + * each incrementing of the simulation. + */ + private static RunContinuityPluginData getRunContinuityPluginData() { + /* + * Build the RunContinuityPluginData + */ + RunContinuityPluginData.Builder continuityBuilder = RunContinuityPluginData.builder(); + double actionTime = 0; + + /* + * + * create some people with their attributes + */ + continuityBuilder.addContextConsumer(actionTime++, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + + for (int i = 0; i < 1000; i++) { + PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, randomGenerator.nextBoolean()); + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_1, randomGenerator.nextBoolean()); + attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_0, randomGenerator.nextInt(5)); + attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_1, randomGenerator.nextInt(100)); + } + + }); + + /* + * Use the partition to select people to mutate, also select people at + * random to mutate + */ + for (int i = 0; i < 100; i++) { + continuityBuilder.addContextConsumer(actionTime++, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + + if (!partitionsDataManager.partitionExists(key)) { + makePartition(c); + } + + // Build a random label set + LabelSet labelSet = LabelSet.builder().setLabel(TestAttributeId.BOOLEAN_1, B1_Label.values()[randomGenerator.nextInt(2)])// + .setLabel(TestAttributeId.INT_0, I0_Label.values()[randomGenerator.nextInt(2)])// + .setLabel(TestAttributeId.INT_1, I1_Label.values()[randomGenerator.nextInt(3)])// + .build(); + + // find a person in the corresponding partition cell + + PartitionSampler partitionSampler = PartitionSampler.builder().setLabelSet(labelSet).build(); + Optional optional = partitionsDataManager.samplePartition(key, partitionSampler); + if (optional.isPresent()) { + PersonId personId = optional.get(); + // mutate this person + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, randomGenerator.nextBoolean()); + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_1, randomGenerator.nextBoolean()); + attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_0, randomGenerator.nextInt(5)); + attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_1, randomGenerator.nextInt(100)); + } + + /* + * The logic above will tend to reduce the number of people in + * the partition, so we will also select random people from the + * whole population and mutate them as well + */ + + List people = peopleDataManager.getPeople(); + if (!people.isEmpty()) { + PersonId personId = people.get(randomGenerator.nextInt(people.size())); + // mutate this person + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, randomGenerator.nextBoolean()); + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_1, randomGenerator.nextBoolean()); + attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_0, randomGenerator.nextInt(5)); + attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_1, randomGenerator.nextInt(100)); + } + + }); + } + + /* + * release the attributes data manager as a string + */ + continuityBuilder.addContextConsumer(actionTime++, (c) -> { + + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + c.releaseOutput(attributesDataManager.toString()); + }); + return continuityBuilder.build(); + } + + /* + * Returns the starting AttributesPluginData containing just the fixed + * TestAttributeId associated attribute definitions. + */ + private static AttributesPluginData getAttributesPluginData() { + AttributesPluginData.Builder attributeBuilder = AttributesPluginData.builder(); + for (TestAttributeId testAttributeId : TestAttributeId.values()) { + attributeBuilder.defineAttribute(testAttributeId, testAttributeId.getAttributeDefinition()); + } + return attributeBuilder.build(); + } + + /* + * Returns an empty people plugin data + */ + private static PeoplePluginData getPeoplePluginData() { + return PeoplePluginData.builder().build(); + } + + /* + * Returns the stochastics plugin data with only the main random generator. + */ + private static StochasticsPluginData getStochasticsPluginData(RandomGenerator randomGenerator) { + + WellState wellState = WellState .builder()// + .setSeed(randomGenerator.nextLong())// + .build(); + + return StochasticsPluginData.builder().setMainRNGState(wellState).build(); + } + + /* + * Returns a partitions plugin data that has been set to support run + * continuity. + */ + private static PartitionsPluginData getPartitionsPluginData() { + return PartitionsPluginData .builder()// + .setRunContinuitySupport(true)// + .build(); + } + + /* + * Returns the default Simulation state -- time starts at zero synchronized + * to the beginning of the epoch. + */ + private static SimulationState getSimulationState() { + return SimulationState.builder().build(); + } + + private static class StateData { + private RunContinuityPluginData runContinuityPluginData; + private AttributesPluginData attributesPluginData; + private PeoplePluginData peoplePluginData; + private StochasticsPluginData stochasticsPluginData; + private PartitionsPluginData partitionsPluginData; + private SimulationState simulationState; + private double haltTime; + private String output; + } + + /* + * Returns the initial state of the StateData + */ + private static StateData getInitialState(RandomGenerator randomGenerator) { + StateData result = new StateData(); + + result.runContinuityPluginData = getRunContinuityPluginData(); + result.attributesPluginData = getAttributesPluginData(); + result.peoplePluginData = getPeoplePluginData(); + result.stochasticsPluginData = getStochasticsPluginData(randomGenerator); + result.partitionsPluginData = getPartitionsPluginData(); + result.simulationState = getSimulationState(); + + return result; + } + + /* + * Executes the simulation using the state data, scheduling the simulation + * to halt at a prescribed time and gathers plugin data information from the + * simulation. This information is used to update the state data. + */ + private static void runSimulation(StateData stateData) { + // build the run continuity plugin + Plugin runContinuityPlugin = RunContinuityPlugin.builder()// + .setRunContinuityPluginData(stateData.runContinuityPluginData)// + .build(); + + // build the attributes plugin + Plugin attributesPlugin = AttributesPlugin.getAttributesPlugin(stateData.attributesPluginData); + + // build the people plugin + Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(stateData.peoplePluginData); + + // build the stochastics plugin + Plugin stochasticsPlugin = StochasticsPlugin.getStochasticsPlugin(stateData.stochasticsPluginData); + + // build the partitions plugin + Plugin partitionsPlugin = PartitionsPlugin .builder()// + .addPluginDependency(AttributesPluginId.PLUGIN_ID)// + .setPartitionsPluginData(stateData.partitionsPluginData)// + .getPartitionsPlugin(); + + TestOutputConsumer outputConsumer = new TestOutputConsumer(); + + // execute the simulation so that it produces a people plugin data + Simulation simulation = Simulation .builder()// + .addPlugin(runContinuityPlugin)// + .addPlugin(attributesPlugin)// + .addPlugin(peoplePlugin)// + .addPlugin(stochasticsPlugin)// + .addPlugin(partitionsPlugin)// + .setSimulationHaltTime(stateData.haltTime)// + .setRecordState(true)// + .setOutputConsumer(outputConsumer)// + .setSimulationState(stateData.simulationState)// + .build();// + simulation.execute(); + + // retrieve the run continuity plugin data + stateData.runContinuityPluginData = outputConsumer.getOutputItem(RunContinuityPluginData.class).get(); + + // retrieve the attributes plugin data + stateData.attributesPluginData = outputConsumer.getOutputItem(AttributesPluginData.class).get(); + + // retrieve the people plugin data + stateData.peoplePluginData = outputConsumer.getOutputItem(PeoplePluginData.class).get(); + + // retrieve the stochastics plugin data + stateData.stochasticsPluginData = outputConsumer.getOutputItem(StochasticsPluginData.class).get(); + + // retrieve the partitions plugin data + stateData.partitionsPluginData = outputConsumer.getOutputItem(PartitionsPluginData.class).get(); + + // retrieve the simulation state + stateData.simulationState = outputConsumer.getOutputItem(SimulationState.class).get(); + + Optional optional = outputConsumer.getOutputItem(String.class); + if (optional.isPresent()) { + stateData.output = optional.get(); + } + + } + + /* + * Returns the duration for a single incremented run of the simulation. This + * is determined by finding the last scheduled task in the run continuity + * plugin data and dividing that by the number of increments. + */ + private static double getSimulationTimeIncrement(StateData stateData, int incrementCount) { + double maxTime = Double.NEGATIVE_INFINITY; + for (Pair> pair : stateData.runContinuityPluginData.getConsumers()) { + Double time = pair.getFirst(); + maxTime = FastMath.max(maxTime, time); + } + + return maxTime / incrementCount; + } + + /* + * Returns the string representation of the attributes data manager after + * breaking up the simulation's execution into several sub-executions. + */ + private String testStateContinuity(int incrementCount) { + + /* + * Each simulation will start at time zero. Seed initialization for the + * stochastics data manager starts with a seed derived from this fixed + * random generator. + */ + RandomGenerator rng = RandomGeneratorProvider.getRandomGenerator(6684803818366629112L); + + /* + * We initialize the various plugin datas needed for the simulation + */ + StateData stateData = getInitialState(rng); + + // We will break up the simulation run into several runs, each lasting a + // fixed duration + double timeIncrement = getSimulationTimeIncrement(stateData, incrementCount); + + while (!stateData.runContinuityPluginData.allPlansComplete()) { + stateData.haltTime += timeIncrement; + runSimulation(stateData); + } + + /* + * When the simulation has finished -- the plans contained in the run + * continuity plugin data have been completed, the string state of the + * attributes data manager is returned + */ + return stateData.output; + } + + + +} \ No newline at end of file diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/datamanagers/AT_PartitionsPluginData.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/datamanagers/AT_PartitionsPluginData.java new file mode 100644 index 000000000..a220665b8 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/datamanagers/AT_PartitionsPluginData.java @@ -0,0 +1,153 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.datamanagers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import util.annotations.UnitTestMethod; + +public class AT_PartitionsPluginData { + + @Test + @UnitTestMethod(target = PartitionsPluginData.class, name = "builder", args = {}) + public void testBuilder() { + assertNotNull(PartitionsPluginData.builder()); + } + + @Test + @UnitTestMethod(target = PartitionsPluginData.class, name = "getCloneBuilder", args = {}) + public void testGetCloneBuilder() { + PartitionsPluginData p1 = PartitionsPluginData.builder().setRunContinuitySupport(true).build(); + PluginData p2 = p1.getCloneBuilder().build(); + assertEquals(p1, p2); + + p1 = PartitionsPluginData.builder().setRunContinuitySupport(false).build(); + p2 = p1.getCloneBuilder().build(); + assertEquals(p1, p2); + } + + @Test + @UnitTestMethod(target = PartitionsPluginData.class, name = "hashCode", args = {}) + public void testHashCode() { + PartitionsPluginData p1 = PartitionsPluginData.builder().build(); + PartitionsPluginData p2 = PartitionsPluginData.builder().setRunContinuitySupport(true).build(); + PartitionsPluginData p3 = PartitionsPluginData.builder().setRunContinuitySupport(true).build(); + PartitionsPluginData p4 = PartitionsPluginData.builder().setRunContinuitySupport(false).build(); + PartitionsPluginData p5 = PartitionsPluginData.builder().setRunContinuitySupport(false).build(); + + // equal objects have equal hash codes + assertEquals(p1.hashCode(), p4.hashCode()); + assertEquals(p1.hashCode(), p5.hashCode()); + assertEquals(p2.hashCode(), p3.hashCode()); + + // hash codes are reasonably distributed + assertNotEquals(p1.hashCode(), p2.hashCode()); + + } + + @Test + @UnitTestMethod(target = PartitionsPluginData.class, name = "equals", args = { Object.class }) + public void testEquals() { + + PartitionsPluginData p1 = PartitionsPluginData.builder().build(); + PartitionsPluginData p2 = PartitionsPluginData.builder().setRunContinuitySupport(true).build(); + PartitionsPluginData p3 = PartitionsPluginData.builder().setRunContinuitySupport(true).build(); + PartitionsPluginData p4 = PartitionsPluginData.builder().setRunContinuitySupport(false).build(); + PartitionsPluginData p5 = PartitionsPluginData.builder().setRunContinuitySupport(false).build(); + + // null + assertNotEquals(p1, null); + assertNotEquals(p2, null); + assertNotEquals(p3, null); + assertNotEquals(p4, null); + assertNotEquals(p5, null); + + // reflexive + assertEquals(p1, p1); + assertEquals(p2, p2); + assertEquals(p3, p3); + assertEquals(p4, p4); + assertEquals(p5, p5); + + // symmetry and transitivity + assertEquals(p1, p4); + assertEquals(p4, p1); + + assertEquals(p1, p5); + assertEquals(p5, p1); + + assertEquals(p4, p5); + assertEquals(p5, p4); + + assertEquals(p2, p3); + assertEquals(p3, p2); + + // non-equality + assertNotEquals(p1, p2); + assertNotEquals(p1, p3); + assertNotEquals(p4, p2); + assertNotEquals(p4, p3); + + } + + @Test + @UnitTestMethod(target = PartitionsPluginData.Builder.class, name = "build", args = {}) + public void testBuild() { + + PartitionsPluginData partitionsPluginData = PartitionsPluginData.builder().build(); + assertNotNull(partitionsPluginData); + + partitionsPluginData = PartitionsPluginData.builder().setRunContinuitySupport(true).build(); + assertNotNull(partitionsPluginData); + + partitionsPluginData = PartitionsPluginData.builder().setRunContinuitySupport(false).build(); + assertNotNull(partitionsPluginData); + + } + + @Test + @UnitTestMethod(target = PartitionsPluginData.class, name = "supportsRunContinuity", args = {}) + public void testSupportsRunContinuity() { + PartitionsPluginData p = PartitionsPluginData.builder().setRunContinuitySupport(true).build(); + assertTrue(p.supportsRunContinuity()); + + p = PartitionsPluginData.builder().setRunContinuitySupport(false).build(); + assertFalse(p.supportsRunContinuity()); + + p = PartitionsPluginData.builder().build(); + assertFalse(p.supportsRunContinuity()); + + } + + @Test + @UnitTestMethod(target = PartitionsPluginData.Builder.class, name = "setRunContinuitySupport", args = { boolean.class }) + public void testSetRunContinuitySupport() { + + PartitionsPluginData p = PartitionsPluginData.builder().setRunContinuitySupport(true).build(); + assertTrue(p.supportsRunContinuity()); + + p = PartitionsPluginData.builder().setRunContinuitySupport(false).build(); + assertFalse(p.supportsRunContinuity()); + } + + @Test + @UnitTestMethod(target = PartitionsPluginData.class, name = "toString", args = {}) + public void testToString() { + PartitionsPluginData p = PartitionsPluginData.builder().build(); + assertEquals("PartitionsPluginData [data=Data [supportRunContinuity=false]]",p.toString()); + + p = PartitionsPluginData.builder().setRunContinuitySupport(false).build(); + assertEquals("PartitionsPluginData [data=Data [supportRunContinuity=false]]",p.toString()); + + p = PartitionsPluginData.builder().setRunContinuitySupport(true).build(); + assertEquals("PartitionsPluginData [data=Data [supportRunContinuity=true]]",p.toString()); + + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/AT_DegeneratePopulationPartitionImpl.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/AT_DegeneratePopulationPartitionImpl.java new file mode 100644 index 000000000..6657ec4c4 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/AT_DegeneratePopulationPartitionImpl.java @@ -0,0 +1,917 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; + +import org.apache.commons.math3.random.RandomGenerator; +import org.apache.commons.math3.util.FastMath; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters.Filter; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.FunctionalAttributeLabeler; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.PartitionsTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.PartitionsTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.TestPartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.AttributesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.events.AttributeUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support.AttributeFilter; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support.TestAttributeId; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonConstructionData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_DegeneratePopulationPartitionImpl { + + @Test + @UnitTestConstructor(target = DegeneratePopulationPartitionImpl.class, args = { PartitionsContext.class, Partition.class , boolean.class}) + public void testConstructor() { + + Factory factory = PartitionsTestPluginFactory.factory(100, 3760806761100897313L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + // establish data view + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + // select about half of the people + Set expectedPeople = new LinkedHashSet<>(); + for (PersonId personId : peopleDataManager.getPeople()) { + if (randomGenerator.nextBoolean()) { + expectedPeople.add(personId); + } + } + + // set attribute BOOLEAN_0 to true for those people + for (PersonId personId : expectedPeople) { + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, true); + } + + // create the population partition + Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); + Partition partition = Partition.builder().setFilter(filter).build(); + PopulationPartition populationPartition = new DegeneratePopulationPartitionImpl(testPartitionsContext, partition,false); + + // show that the population partition contains the expected people + List actualPeople = populationPartition.getPeople(); + assertEquals(expectedPeople.size(), actualPeople.size()); + assertEquals(expectedPeople, new LinkedHashSet<>(actualPeople)); + + // precondition tests + // if the context is null + assertThrows(RuntimeException.class, () -> new DegeneratePopulationPartitionImpl(null, partition,false)); + + // if the partition is null + assertThrows(RuntimeException.class, () -> new DegeneratePopulationPartitionImpl(testPartitionsContext, null,false)); + + // if the partition is not degenerate + ContractException contractException = assertThrows(ContractException.class, + () -> new DegeneratePopulationPartitionImpl(testPartitionsContext, + Partition.builder().addLabeler(new FunctionalAttributeLabeler(TestAttributeId.BOOLEAN_0, (v) -> v)).build(),false)); + assertEquals(PartitionError.NON_DEGENERATE_PARTITION, contractException.getErrorType()); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = DegeneratePopulationPartitionImpl.class, name = "attemptPersonAddition", args = { PersonId.class }) + public void testAttemptPersonAddition() { + + Factory factory = PartitionsTestPluginFactory.factory(100, 2545018253500191849L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + // establish data views + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + + /* + * Create the population partition filtering on attribute BOOLEAN_0 + * = true + */ + Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); + Partition partition = Partition.builder().setFilter(filter).build(); + PopulationPartition populationPartition = new DegeneratePopulationPartitionImpl(testPartitionsContext, partition,false); + + // precondition test: + assertThrows(RuntimeException.class, () -> populationPartition.attemptPersonAddition(null)); + + /* + * Add new people, setting the attribute to alternating values of + * true and false + */ + for (int i = 0; i < 20; i++) { + + PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); + + boolean attributeValue = i % 2 == 0; + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, attributeValue); + + populationPartition.attemptPersonAddition(personId); + + /* + * Show that the person is in the population partition if and + * only if their attribute value was set to true + */ + assertEquals(attributeValue, populationPartition.contains(personId)); + } + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = DegeneratePopulationPartitionImpl.class, name = "attemptPersonRemoval", args = { PersonId.class }) + public void testAttemptPersonRemoval() { + Factory factory = PartitionsTestPluginFactory.factory(100, 1924419629240381672L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + // establish data views + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + /* + * Create a container for the people we expect to be contained in + * the population partition. + */ + Set expectedPeople = new LinkedHashSet<>(); + + // select about half of the people to have attribute BOOLEAN_0 value + // of true + for (PersonId personId : peopleDataManager.getPeople()) { + if (randomGenerator.nextBoolean()) { + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, true); + expectedPeople.add(personId); + } + } + + /* + * Create the population partition filtering on attribute BOOLEAN_0 + * = true + */ + Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); + Partition partition = Partition.builder().setFilter(filter).build(); + PopulationPartition populationPartition = new DegeneratePopulationPartitionImpl(testPartitionsContext, partition,false); + + // show that the expected people are in the population partition + List actualPeople = populationPartition.getPeople(); + assertEquals(expectedPeople.size(), actualPeople.size()); + assertEquals(expectedPeople, new LinkedHashSet<>(actualPeople)); + + /* + * Remove people and show that they are no longer in the partition + */ + for (PersonId personId : expectedPeople) { + peopleDataManager.removePerson(personId); + populationPartition.attemptPersonRemoval(personId); + // show that the person was removed + assertFalse(populationPartition.contains(personId)); + } + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = DegeneratePopulationPartitionImpl.class, name = "handleEvent", args = { Event.class }) + public void testHandleEvent() { + + Factory factory = PartitionsTestPluginFactory.factory(100, 5331854470768144150L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + // establish data views + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + + for (PersonId personId : peopleDataManager.getPeople()) { + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, randomGenerator.nextBoolean()); + } + + /* + * Create the population partition filtering on attribute BOOLEAN_0 + * = true + */ + Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); + Partition partition = Partition.builder().setFilter(filter).build(); + PopulationPartition populationPartition = new DegeneratePopulationPartitionImpl(testPartitionsContext, partition,false); + + for (PersonId personId : peopleDataManager.getPeople()) { + Boolean b0 = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); + + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, !b0); + populationPartition.handleEvent(new AttributeUpdateEvent(personId, TestAttributeId.BOOLEAN_0, b0, !b0)); + + assertEquals(!b0, populationPartition.contains(personId)); + + } + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = DegeneratePopulationPartitionImpl.class, name = "validateLabelSetInfo", args = { LabelSet.class }) + public void testValidateLabelSetInfo() { + Factory factory = PartitionsTestPluginFactory.factory(100, 7896267308674363012L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + /* + * Create the population partition filtering on attribute BOOLEAN_0 + * = true + */ + Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); + Partition partition = Partition.builder().setFilter(filter).build(); + PopulationPartition populationPartition = new DegeneratePopulationPartitionImpl(testPartitionsContext, partition,false); + + LabelSet labelSet = LabelSet.builder().setLabel(TestAttributeId.BOOLEAN_1, 2).build(); + assertFalse(populationPartition.validateLabelSetInfo(labelSet)); + + assertTrue(populationPartition.validateLabelSetInfo(LabelSet.builder().build())); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = DegeneratePopulationPartitionImpl.class, name = "getPeopleCount", args = {}) + public void testGetPeopleCount() { + + Factory factory = PartitionsTestPluginFactory.factory(100, 2295886123984917407L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + // establish data views + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + /* + * Create a container for the people we expect to be contained in + * the population partition. + */ + Set expectedPeople = new LinkedHashSet<>(); + + // select about half of the people to have attribute BOOLEAN_0 value + // of true + for (PersonId personId : peopleDataManager.getPeople()) { + if (randomGenerator.nextBoolean()) { + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, true); + expectedPeople.add(personId); + } + } + + /* + * Create the population partition filtering on attribute BOOLEAN_0 + * = true + */ + Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); + Partition partition = Partition.builder().setFilter(filter).build(); + PopulationPartition populationPartition = new DegeneratePopulationPartitionImpl(testPartitionsContext, partition,false); + + // show that the people count matches expectations + assertEquals(expectedPeople.size(), populationPartition.getPeopleCount()); + + /* + * Change the attributes for the expected people and show that the + * expected count is correct + */ + int expectedPeopleCount = expectedPeople.size(); + for (PersonId personId : expectedPeople) { + expectedPeopleCount--; + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, false); + populationPartition.handleEvent(new AttributeUpdateEvent(personId, TestAttributeId.BOOLEAN_0, true, false)); + assertEquals(expectedPeopleCount, populationPartition.getPeopleCount()); + } + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = DegeneratePopulationPartitionImpl.class, name = "getPeopleCount", args = { LabelSet.class }) + public void testGetPeopleCount_LabelSet() { + Factory factory = PartitionsTestPluginFactory.factory(1000, 1957059921486084637L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + + // Randomize the attribute values for all people + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + for (PersonId personId : peopleDataManager.getPeople()) { + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, randomGenerator.nextBoolean()); + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_1, randomGenerator.nextBoolean()); + } + + // build a container to hold the expected relationship from label + // sets to people + Set expectedPeople = new LinkedHashSet<>(); + for (PersonId personId : peopleDataManager.getPeople()) { + Boolean b0 = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); + Boolean b1 = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_1); + if (b0 && !b1) { + expectedPeople.add(personId); + } + } + + /* + * Create the population partition filtering on attribute BOOLEAN_0 + * = true + */ + Filter filter_0 = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); + Filter filter_1 = new AttributeFilter(TestAttributeId.BOOLEAN_1, Equality.EQUAL, false); + Filter filter = filter_0.and(filter_1); + Partition partition = Partition.builder().setFilter(filter).build(); + + PopulationPartition populationPartition = new DegeneratePopulationPartitionImpl(testPartitionsContext, partition,false); + + // show that the people count matches expectations + List actualPeople = populationPartition.getPeople(LabelSet.builder().build()); + assertEquals(expectedPeople.size(), actualPeople.size()); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = DegeneratePopulationPartitionImpl.class, name = "getPeopleCountMap", args = { LabelSet.class }) + public void testGetPeopleCountMap() { + + Factory factory = PartitionsTestPluginFactory.factory(1000, 5254073186909000918L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + // randomize BOOLEAN_0 attribute values + for (PersonId personId : peopleDataManager.getPeople()) { + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, randomGenerator.nextBoolean()); + } + // build the population partition with the BOOLEAN_0 + Partition partition = Partition.builder().setFilter(new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true)).build(); + DegeneratePopulationPartitionImpl populationPartition = new DegeneratePopulationPartitionImpl(testPartitionsContext, partition,false); + + Map peopleCountMap = populationPartition.getPeopleCountMap(LabelSet.builder().build()); + + assertEquals(1, peopleCountMap.size()); + LabelSet keyLabelSet = peopleCountMap.keySet().iterator().next(); + assertTrue(keyLabelSet.isEmpty()); + Integer count = peopleCountMap.get(keyLabelSet); + assertEquals(populationPartition.getPeopleCount(), count); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = DegeneratePopulationPartitionImpl.class, name = "contains", args = { PersonId.class }) + public void testContains() { + Factory factory = PartitionsTestPluginFactory.factory(100, 2907418341194860848L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + // establish data views + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + /* + * Create a container for the people we expect to be contained in + * the population partition. + */ + Set expectedPeople = new LinkedHashSet<>(); + + // select about half of the people to have attribute BOOLEAN_0 value + // of true + for (PersonId personId : peopleDataManager.getPeople()) { + if (randomGenerator.nextBoolean()) { + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, true); + expectedPeople.add(personId); + } + } + + /* + * Create the population partition filtering on attribute BOOLEAN_0 + * = true + */ + Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); + Partition partition = Partition.builder().setFilter(filter).build(); + PopulationPartition populationPartition = new DegeneratePopulationPartitionImpl(testPartitionsContext, partition,false); + + // show that the person data view contains the people we expect + assertEquals(expectedPeople.size(), populationPartition.getPeople().size()); + for (PersonId personId : peopleDataManager.getPeople()) { + assertEquals(expectedPeople.contains(personId), populationPartition.contains(personId)); + } + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = DegeneratePopulationPartitionImpl.class, name = "contains", args = { PersonId.class, LabelSet.class }) + public void testContains_LabelSet() { + Factory factory = PartitionsTestPluginFactory.factory(100, 2888054511830289156L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + // establish data views + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + /* + * Create a container for the people we expect to be contained in + * the population partition. + */ + Set expectedPeople = new LinkedHashSet<>(); + + // select about half of the people to have attribute BOOLEAN_0 value + // of true + for (PersonId personId : peopleDataManager.getPeople()) { + if (randomGenerator.nextBoolean()) { + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, true); + expectedPeople.add(personId); + } + } + + /* + * Create the population partition filtering on attribute BOOLEAN_0 + * = true + */ + Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); + Partition partition = Partition.builder().setFilter(filter).build(); + PopulationPartition populationPartition = new DegeneratePopulationPartitionImpl(testPartitionsContext, partition,false); + + LabelSet labelSet = LabelSet.builder().build(); + // show that the person data view contains the people we expect + assertEquals(expectedPeople.size(), populationPartition.getPeople().size()); + for (PersonId personId : peopleDataManager.getPeople()) { + assertEquals(expectedPeople.contains(personId), populationPartition.contains(personId, labelSet)); + } + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = DegeneratePopulationPartitionImpl.class, name = "getPeople", args = { LabelSet.class }) + public void testGetPeople_LabelSet() { + Factory factory = PartitionsTestPluginFactory.factory(1000, 8577028018353363458L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + + // Randomize the attribute values for all people + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + for (PersonId personId : peopleDataManager.getPeople()) { + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, randomGenerator.nextBoolean()); + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_1, randomGenerator.nextBoolean()); + } + + // build a container to hold the expected relationship from label + // sets to people + Set expectedPeople = new LinkedHashSet<>(); + for (PersonId personId : peopleDataManager.getPeople()) { + Boolean b0 = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); + Boolean b1 = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_1); + if (b0 && !b1) { + expectedPeople.add(personId); + } + } + + /* + * Create the population partition filtering on attribute BOOLEAN_0 + * = true + */ + Filter filter_0 = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); + Filter filter_1 = new AttributeFilter(TestAttributeId.BOOLEAN_1, Equality.EQUAL, false); + Filter filter = filter_0.and(filter_1); + Partition partition = Partition.builder().setFilter(filter).build(); + + PopulationPartition populationPartition = new DegeneratePopulationPartitionImpl(testPartitionsContext, partition,false); + + // show that the people count matches expectations + List actualPeople = populationPartition.getPeople(LabelSet.builder().build()); + assertEquals(expectedPeople.size(), actualPeople.size()); + assertEquals(expectedPeople, new LinkedHashSet<>(actualPeople)); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = DegeneratePopulationPartitionImpl.class, name = "getPeople", args = {}) + public void testGetPeople() { + Factory factory = PartitionsTestPluginFactory.factory(100, 3706541397073246652L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + // establish data views + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + /* + * Create a container for the people we expect to be contained in + * the population partition. + */ + Set expectedPeople = new LinkedHashSet<>(); + + // select about half of the people to have attribute BOOLEAN_0 value + // of true + for (PersonId personId : peopleDataManager.getPeople()) { + if (randomGenerator.nextBoolean()) { + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, true); + expectedPeople.add(personId); + } + } + + /* + * Create the population partition filtering on attribute BOOLEAN_0 + * = true + */ + Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); + Partition partition = Partition.builder().setFilter(filter).build(); + PopulationPartition populationPartition = new DegeneratePopulationPartitionImpl(testPartitionsContext, partition,false); + + // show that the person data view contains the people we expect + assertEquals(expectedPeople.size(), populationPartition.getPeople().size()); + assertEquals(expectedPeople, new LinkedHashSet<>(populationPartition.getPeople())); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + private static enum ExcludedPersonType { + NULL, MATCHING_MEMBER, NON_MATCHING_MEMBER, NON_MEMBER; + } + + @Test + @UnitTestMethod(target = DegeneratePopulationPartitionImpl.class, name = "samplePartition", args = { PartitionSampler.class }) + public void testSamplePartition() { + + /* + * Tests the sample mechanism under a variety of partition samplers. + * + * The partition is formed from 4 labeling functions over the attributes + * + * INT_0-> 0, 1, 2 + * + * INT_1 -> TRUE, FALSE + * + * DOUBLE_0 -> A, B, C + * + * DOUBLE_1 -> TRUE, FALSE + * + * Filtering for the partition is either on or off. The filter passes + * when the attribute BOOLEAN_0 is true. + * + * The partition sampler will optionally set its excluded person to + * null, a person not in the partition, a person in the partition who is + * not expected to match the sampler's label set and a person who does + * match the sampler's label set. + * + * The partition sampler will optionally use a label set. The label set + * will either be null or empty since degenerate population partitions + * do no contain labelers. + * + * The partition sampler will optionally use a weighting function. The + * weighting function will return 1 for any person having a label of + * TRUE for INT_1 and 0 otherwise. + * + * This test does not demonstrate precondition checks, proper use of + * random number generator ids, or the proper distribution of results + * aligned to the weighting function other that the simple binary + * alignment for the weighting function described above. + * + * Each combination is run with a randomly generated seed value. + * + */ + + Set int_0_label_values = new LinkedHashSet<>(); + int_0_label_values.add(0); + int_0_label_values.add(1); + int_0_label_values.add(2); + int_0_label_values.add(3);// will not match any person + + Set double_0_label_values = new LinkedHashSet<>(); + double_0_label_values.add("A"); + double_0_label_values.add("B"); + double_0_label_values.add("C"); + double_0_label_values.add("D");// will not match any person + + Set weightingFunctionValues = new LinkedHashSet<>(); + weightingFunctionValues.add(false); + weightingFunctionValues.add(true); + + Set useFilterValues = new LinkedHashSet<>(); + useFilterValues.add(false); + useFilterValues.add(true); + + Set useLabelSetValues = new LinkedHashSet<>(); + useLabelSetValues.add(false); + useLabelSetValues.add(true); + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8925918754735468568L); + + for (ExcludedPersonType excludedPersonType : ExcludedPersonType.values()) { + for (Boolean useWeightingFunction : weightingFunctionValues) { + for (Boolean useFilter : useFilterValues) { + long seed = randomGenerator.nextLong(); + for (Boolean useLabelSet : useLabelSetValues) { + seed = randomGenerator.nextLong(); + executeSamplingTest(seed, useFilter, excludedPersonType, useWeightingFunction, useLabelSet); + } + } + } + } + } + + private void executeSamplingTest(long seed, Boolean useFilter, ExcludedPersonType excludedPersonType, Boolean useWeightingFunction, boolean useLabelSet) { + + Factory factory = PartitionsTestPluginFactory.factory(1000, seed, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + // remember to test with general and COMET to show they get + // different results? + + // establish data views + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + + /* + * Define functions that will convert attribute values into labels + * for attributes INT_0, INT_1, DOUBLE_0, and DOUBLE_1. We will use + * these in the partition's labeling + */ + Function int_0_labelFunction = (value) -> { + int v = (Integer) value; + return v % 3; + }; + + Function int_1_labelFunction = (value) -> { + int v = (Integer) value; + if (v < 40) { + return true; + } + return false; + }; + + Function double_0_labelFunction = (value) -> { + double v = (Double) value; + if (v < 33) { + return "A"; + } + if (v < 67) { + return "B"; + } + return "C"; + }; + + Function double_1_labelFunction = (value) -> { + double v = (Double) value; + return v < 90; + }; + + // determine the people in the world + List peopleInTheWorld = peopleDataManager.getPeople(); + + // alter people's attributes randomly + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + for (PersonId personId : peopleInTheWorld) { + int intValue = (int) (randomGenerator.nextDouble() * 100); + attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_0, intValue); + + intValue = (int) (randomGenerator.nextDouble() * 100); + attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_1, intValue); + + double doubleValue = randomGenerator.nextDouble() * 100; + attributesDataManager.setAttributeValue(personId, TestAttributeId.DOUBLE_0, doubleValue); + + doubleValue = randomGenerator.nextDouble() * 100; + attributesDataManager.setAttributeValue(personId, TestAttributeId.DOUBLE_1, doubleValue); + + boolean booleanValue = randomGenerator.nextBoolean(); + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, booleanValue); + + booleanValue = randomGenerator.nextBoolean(); + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_1, booleanValue); + + } + + /* + * Create a partition that may filter about half of the population + * on BOOLEAN_0. We will partition on INT_0, INT_1, DOUBLE_0 and + * DOUBLE_1. Note that we do not use BOOLEAN_1 as part of the + * partition. + */ + Partition.Builder partitionBuilder = Partition.builder(); + if (useFilter) { + partitionBuilder// + .setFilter(new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true));// + } + partitionBuilder// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.INT_0, int_0_labelFunction))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.INT_1, int_1_labelFunction))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.DOUBLE_0, double_0_labelFunction))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.DOUBLE_1, double_1_labelFunction)); + + Partition partition = partitionBuilder.build(); + + PopulationPartitionImpl populationPartition = new PopulationPartitionImpl(testPartitionsContext, partition,false); + + /* + * Create a label set for the query. + */ + LabelSet queryLabelSet = null; + if (useLabelSet) { + queryLabelSet = LabelSet.builder().build(); + } + + // determine the people in the partition + Set expectedPeopleInPartition = new LinkedHashSet<>(); + + for (PersonId personId : peopleInTheWorld) { + + if (useFilter) { + Boolean personInPartition = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); + if (personInPartition) { + expectedPeopleInPartition.add(personId); + } + } else { + expectedPeopleInPartition.add(personId); + } + + } + + // determine the people who will match the query label set + Set expectedPeopleMatchingQueryLabelSet = new LinkedHashSet<>(); + expectedPeopleMatchingQueryLabelSet.addAll(expectedPeopleInPartition); + + PartitionSampler.Builder partitionSamplerBuilder = PartitionSampler.builder(); + partitionSamplerBuilder.setLabelSet(queryLabelSet);// + + // Select the excluded person + PersonId excludedPersonId = null; + switch (excludedPersonType) { + case MATCHING_MEMBER: + for (PersonId personId : expectedPeopleMatchingQueryLabelSet) { + excludedPersonId = personId; + break; + } + + break; + case NON_MATCHING_MEMBER: + for (PersonId personId : expectedPeopleInPartition) { + if (!expectedPeopleMatchingQueryLabelSet.contains(personId)) { + excludedPersonId = personId; + break; + } + } + break; + + case NON_MEMBER: + if (useFilter) { + for (PersonId personId : peopleInTheWorld) { + if (!expectedPeopleInPartition.contains(personId)) { + excludedPersonId = personId; + break; + } + } + } + break; + + case NULL: + // do nothing + break; + default: + throw new RuntimeException("unhandled case: " + excludedPersonType); + } + partitionSamplerBuilder.setExcludedPerson(excludedPersonId); + + Set expectedPeopleMatchingPartitionSampler = new LinkedHashSet<>(expectedPeopleMatchingQueryLabelSet); + expectedPeopleMatchingPartitionSampler.remove(excludedPersonId); + + if (useWeightingFunction) { + Iterator iterator = expectedPeopleMatchingPartitionSampler.iterator(); + while (iterator.hasNext()) { + PersonId personId = iterator.next(); + Integer int_1_attributeValue = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_1); + Boolean passed = (Boolean) int_1_labelFunction.apply(int_1_attributeValue); + if (!passed) { + iterator.remove(); + } + } + + LabelSetWeightingFunction labelSetWeightingFunction = (c2, labelSet) -> { + Boolean value = (Boolean) labelSet.getLabel(TestAttributeId.INT_1).get(); + if (value) { + return 1; + } else { + return 0; + } + }; + partitionSamplerBuilder.setLabelSetWeightingFunction(labelSetWeightingFunction); + } + + PartitionSampler partitionSampler = partitionSamplerBuilder.build(); + + int samplingCount = FastMath.min(expectedPeopleMatchingQueryLabelSet.size() + 1, 10); + + for (int i = 0; i < samplingCount; i++) { + Optional optional = populationPartition.samplePartition(partitionSampler); + if (optional.isPresent()) { + PersonId selectedPerson = optional.get(); + assertTrue(expectedPeopleMatchingPartitionSampler.contains(selectedPerson)); + } else { + assertTrue(expectedPeopleMatchingPartitionSampler.isEmpty()); + } + } + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = DegeneratePopulationPartitionImpl.class, name = "getPersonValue", args = { LabelSetFunction.class, PersonId.class }) + public void testGetPersonValue() { + Factory factory = PartitionsTestPluginFactory.factory(100, 3706541397073246652L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + // establish data views + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + + + // select about half of the people to have attribute BOOLEAN_0 value + // of true + for (PersonId personId : peopleDataManager.getPeople()) { + if (randomGenerator.nextBoolean()) { + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, true); + } + } + + /* + * Create the population partition filtering on attribute BOOLEAN_0 + * = true + */ + Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); + Partition partition = Partition.builder().setFilter(filter).build(); + PopulationPartition populationPartition = new DegeneratePopulationPartitionImpl(testPartitionsContext, partition,false); + + LabelSetFunction f = (pc, ls) -> 5; + + for (PersonId personId : peopleDataManager.getPeople()) { + Optional optional = populationPartition.getPersonValue(f, personId); + assertTrue(optional.isEmpty()); + } + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/AT_Equality.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/AT_Equality.java new file mode 100644 index 000000000..f0a229790 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/AT_Equality.java @@ -0,0 +1,96 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestMethod; +import util.random.RandomGeneratorProvider; +import util.wrappers.MutableInteger; + +public class AT_Equality { + + /** + * Tests {@link Equality#isCompatibleComparisonValue(int)} + */ + @Test + @UnitTestMethod(target = Equality.class, name = "isCompatibleComparisonValue", args = { int.class }) + public void testIsCompatibleComparisonValue() { + + assertEquals(6, Equality.values().length); + + /* + * Show that the six Equality members return the proper compatibility + * with integer comparison values. + */ + + for (int i = 1; i <= 10; i++) { + assertFalse(Equality.EQUAL.isCompatibleComparisonValue(-i)); + assertTrue(Equality.EQUAL.isCompatibleComparisonValue(0)); + assertFalse(Equality.EQUAL.isCompatibleComparisonValue(i)); + + assertTrue(Equality.NOT_EQUAL.isCompatibleComparisonValue(-i)); + assertFalse(Equality.NOT_EQUAL.isCompatibleComparisonValue(0)); + assertTrue(Equality.NOT_EQUAL.isCompatibleComparisonValue(i)); + + assertTrue(Equality.LESS_THAN.isCompatibleComparisonValue(-i)); + assertFalse(Equality.LESS_THAN.isCompatibleComparisonValue(0)); + assertFalse(Equality.LESS_THAN.isCompatibleComparisonValue(i)); + + assertTrue(Equality.LESS_THAN_EQUAL.isCompatibleComparisonValue(-i)); + assertTrue(Equality.LESS_THAN_EQUAL.isCompatibleComparisonValue(0)); + assertFalse(Equality.LESS_THAN_EQUAL.isCompatibleComparisonValue(i)); + + assertFalse(Equality.GREATER_THAN.isCompatibleComparisonValue(-i)); + assertFalse(Equality.GREATER_THAN.isCompatibleComparisonValue(0)); + assertTrue(Equality.GREATER_THAN.isCompatibleComparisonValue(i)); + + assertFalse(Equality.GREATER_THAN_EQUAL.isCompatibleComparisonValue(-i)); + assertTrue(Equality.GREATER_THAN_EQUAL.isCompatibleComparisonValue(0)); + assertTrue(Equality.GREATER_THAN_EQUAL.isCompatibleComparisonValue(i)); + } + } + + + @Test + @UnitTestMethod(target = Equality.class, name = "getNegation", args = { Equality.class }) + public void testGetNegation() { + assertEquals(6, Equality.values().length); + assertEquals(Equality.NOT_EQUAL, Equality.getNegation(Equality.EQUAL)); + assertEquals(Equality.EQUAL, Equality.getNegation(Equality.NOT_EQUAL)); + assertEquals(Equality.LESS_THAN_EQUAL, Equality.getNegation(Equality.GREATER_THAN)); + assertEquals(Equality.LESS_THAN, Equality.getNegation(Equality.GREATER_THAN_EQUAL)); + assertEquals(Equality.GREATER_THAN_EQUAL, Equality.getNegation(Equality.LESS_THAN)); + assertEquals(Equality.GREATER_THAN, Equality.getNegation(Equality.LESS_THAN_EQUAL)); + } + + + @Test + @UnitTestMethod(target = Equality.class, name = "getRandomEquality", args = { RandomGenerator.class }) + public void testGetRandomEquality() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(81893850178162700L); + Map map = new LinkedHashMap<>(); + for(Equality equality : Equality.values()) { + map.put(equality, new MutableInteger()); + } + + for(int i =0;i<6000;i++) { + Equality equality = Equality.getRandomEquality(randomGenerator); + map.get(equality).increment(); + } + for(Equality equality : Equality.values()) { + int count = map.get(equality).getValue(); + assertTrue(count>900); + assertTrue(count<1100); + } + + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/AT_FilterSensitivity.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/AT_FilterSensitivity.java new file mode 100644 index 000000000..d3cde01d4 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/AT_FilterSensitivity.java @@ -0,0 +1,74 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Optional; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.PartitionsTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.PartitionsTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.TestPartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_FilterSensitivity { + + @Test + @UnitTestConstructor(target = FilterSensitivity.class,args = { Class.class, EventPredicate.class }) + public void testConstructor() { + // nothing to test + } + + private static class Event2 implements Event{} + private static class Event3 implements Event{} + + @Test + @UnitTestMethod(target = FilterSensitivity.class,name = "getEventClass", args = {}) + public void testGetEventClass() { + + FilterSensitivity filterSensitivity1 = new FilterSensitivity<>(Event.class, (c, e) -> Optional.empty()); + assertEquals(Event.class, filterSensitivity1.getEventClass()); + + /* + * Note that we are using two event types here just to show that it + * works. These events do not carry person information and normally a + * FilterSensitivity is only used with such events. + */ + FilterSensitivity filterSensitivity2 = new FilterSensitivity<>(Event2.class, (c, e) -> Optional.empty()); + assertEquals(Event2.class, filterSensitivity2.getEventClass()); + + FilterSensitivity filterSensitivity3 = new FilterSensitivity<>(Event3.class, (c, e) -> Optional.empty()); + assertEquals(Event3.class, filterSensitivity3.getEventClass()); + + } + + @Test + @UnitTestMethod(target = FilterSensitivity.class,name = "requiresRefresh", args = { PartitionsContext.class, Event.class }) + public void testRequiresRefresh() { + Factory factory = PartitionsTestPluginFactory.factory(10, 8678712526990350206L, (context)->{ + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(context); + + FilterSensitivity filterSensitivity = new FilterSensitivity<>(Event.class, (c, e) -> Optional.empty()); + Optional optional = filterSensitivity.requiresRefresh(testPartitionsContext, new Event() { + }); + assertFalse(optional.isPresent()); + + PersonId personId = new PersonId(0); + filterSensitivity = new FilterSensitivity<>(Event.class, (c, e) -> Optional.of(personId)); + optional = filterSensitivity.requiresRefresh(testPartitionsContext, new Event() { + }); + assertTrue(optional.isPresent()); + assertEquals(personId, optional.get()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + +} \ No newline at end of file diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/AT_LabelSet.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/AT_LabelSet.java new file mode 100644 index 000000000..005748f49 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/AT_LabelSet.java @@ -0,0 +1,160 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + + +public class AT_LabelSet { + + private static enum Dimension { + DIM_1, DIM_2, DIM_3, DIM_4, DIM_5; + } + + /** + * Tests {@linkplain LabelSet#builder() + */ + @Test + @UnitTestMethod(target = LabelSet.class, name = "builder", args = {}) + public void testBuilder() { + assertNotNull(LabelSet.builder()); + } + + /** + * Tests {@linkplain LabelSet#isEmpty() + */ + @Test + @UnitTestMethod(target = LabelSet.class, name = "isEmpty", args = {}) + public void testIsEmpty() { + + LabelSet labelSet = LabelSet.builder().build(); + assertTrue(labelSet.isEmpty()); + + labelSet = LabelSet.builder().setLabel(Dimension.DIM_1, "compartment label").build(); + assertFalse(labelSet.isEmpty()); + + labelSet = LabelSet.builder().setLabel(Dimension.DIM_2, "group label").build(); + assertFalse(labelSet.isEmpty()); + + labelSet = LabelSet.builder().setLabel(Dimension.DIM_3, "region label").build(); + assertFalse(labelSet.isEmpty()); + + labelSet = LabelSet.builder().setLabel(Dimension.DIM_4, "resource label").build(); + assertFalse(labelSet.isEmpty()); + + labelSet = LabelSet.builder().setLabel(Dimension.DIM_5, "property label").build(); + assertFalse(labelSet.isEmpty()); + + } + + @Test + @UnitTestMethod(target = LabelSet.class, name = "getLabel", args = { Object.class }) + public void testGetLabel() { + Object expectedCompartmentLabel = "Compartment Label"; + LabelSet labelSet = LabelSet.builder().setLabel(Dimension.DIM_1, expectedCompartmentLabel).build(); + Optional optionalLabel = labelSet.getLabel(Dimension.DIM_1); + assertTrue(optionalLabel.isPresent()); + Object actualCompartmentLabel = optionalLabel.get(); + assertEquals(expectedCompartmentLabel, actualCompartmentLabel); + + } + + /** + * Tests {@linkplain LabelSet#getDimensions() + */ + @Test + @UnitTestMethod(target = LabelSet.class, name = "getDimensions", args = {}) + public void testGetDimensions() { + // getDimensions() + LabelSet.Builder builder = LabelSet.builder(); + Set expectedDimensions = new LinkedHashSet<>(); + for (int i = 0; i < 10; i++) { + expectedDimensions.add(i); + builder.setLabel(i, Integer.toString(i)); + } + LabelSet labelSet = builder.build(); + Set actualDimensions = labelSet.getDimensions(); + assertEquals(expectedDimensions, actualDimensions); + } + + /** + * Tests {@linkplain LabelSet#equals(Object) + */ + @Test + @UnitTestMethod(target = LabelSet.class, name = "equals", args = { Object.class }) + public void testEquals() { + LabelSet labelSet1 = LabelSet.builder().setLabel(Dimension.DIM_1, "compartment label").build(); + LabelSet labelSet2 = LabelSet.builder().setLabel(Dimension.DIM_1, "compartment label").build(); + LabelSet labelSet3 = LabelSet.builder().setLabel(Dimension.DIM_1, "compartment label2").build(); + + assertFalse(labelSet1 == labelSet2); + assertTrue(labelSet1.equals(labelSet1)); + assertTrue(labelSet1.equals(labelSet2)); + assertTrue(labelSet2.equals(labelSet1)); + assertFalse(labelSet1.equals(labelSet3)); + + } + + /** + * Tests {@linkplain LabelSet#hashCode() + */ + @Test + @UnitTestMethod(target = LabelSet.class, name = "hashCode", args = {}) + public void testHashCode() { + + LabelSet labelSet1 = LabelSet.builder().setLabel(Dimension.DIM_1, "compartment label").build(); + LabelSet labelSet2 = LabelSet.builder().setLabel(Dimension.DIM_1, "compartment label").build(); + + assertFalse(labelSet1 == labelSet2); + assertEquals(labelSet1, labelSet2); + assertEquals(labelSet1.hashCode(), labelSet2.hashCode()); + } + + @Test + @UnitTestMethod(target = LabelSet.class, name = "toString", args = {}) + public void testToString() { + LabelSet labelSet = LabelSet.builder().setLabel(Dimension.DIM_1, "compartment label").build(); + + String expectedString = "LabelSet [labels={DIM_1=compartment label}]"; + assertNotNull(labelSet); + assertEquals(expectedString, labelSet.toString()); + } + + @Test + @UnitTestMethod(target = LabelSet.Builder.class, name = "build", args = {}) + public void testBuild() { + LabelSet labelSet = LabelSet.builder().setLabel(Dimension.DIM_1, "compartment label").build(); + assertNotNull(labelSet); + } + + @Test + @UnitTestMethod(target = LabelSet.Builder.class, name = "setLabel", args = { Object.class, Object.class }) + public void testSetLabel() { + String expectedLabel1 = "expected label 1"; + String expectedLabel2 = "expected label 2"; + + LabelSet labelSet = LabelSet.builder().setLabel(Dimension.DIM_1, expectedLabel1).setLabel(Dimension.DIM_2, expectedLabel2).build(); + assertEquals(expectedLabel1, labelSet.getLabel(Dimension.DIM_1).get()); + assertEquals(expectedLabel2, labelSet.getLabel(Dimension.DIM_2).get()); + + // precondition test: if the label is null + ContractException contractException = assertThrows(ContractException.class, () -> LabelSet.builder().setLabel(null, expectedLabel1)); + assertEquals(PartitionError.NULL_PARTITION_LABEL_DIMENSION, contractException.getErrorType()); + + // precondition test: if the dimension is null + contractException = assertThrows(ContractException.class, () -> LabelSet.builder().setLabel(Dimension.DIM_1, null)); + assertEquals(PartitionError.NULL_PARTITION_LABEL, contractException.getErrorType()); + + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/AT_LabelerSensitivity.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/AT_LabelerSensitivity.java new file mode 100644 index 000000000..e5fa92777 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/AT_LabelerSensitivity.java @@ -0,0 +1,68 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Optional; +import java.util.function.Function; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public final class AT_LabelerSensitivity { + + @Test + @UnitTestConstructor(target = LabelerSensitivity.class, args = { Class.class, Function.class }) + public void testConstructor() { + // nothing to test + } + + private static class Event2 implements Event { + } + + private static class Event3 implements Event { + } + + @Test + @UnitTestMethod(target = LabelerSensitivity.class, name = "getEventClass", args = {}) + public void testGetEventClass() { + LabelerSensitivity labelerSensitivity1 = new LabelerSensitivity<>(Event.class, (e) -> Optional.ofNullable(null)); + assertEquals(Event.class, labelerSensitivity1.getEventClass()); + + /* + * Note that we are using two event types here just to show that it + * works. These events do not carry person information and normally a + * LabelerSensitivity is only used with such events. + */ + + LabelerSensitivity labelerSensitivity2 = new LabelerSensitivity<>(Event2.class, (e) -> Optional.ofNullable(null)); + assertEquals(Event2.class, labelerSensitivity2.getEventClass()); + + LabelerSensitivity labelerSensitivity3 = new LabelerSensitivity<>(Event3.class, (e) -> Optional.ofNullable(null)); + assertEquals(Event3.class, labelerSensitivity3.getEventClass()); + + } + + @Test + @UnitTestMethod(target = LabelerSensitivity.class, name = "getPersonId", args = { Event.class }) + public void testGetPersonId() { + + LabelerSensitivity labelerSensitivity = new LabelerSensitivity<>(Event.class, (e) -> Optional.empty()); + Optional optional = labelerSensitivity.getPersonId(new Event() { + }); + assertFalse(optional.isPresent()); + + PersonId personId = new PersonId(0); + labelerSensitivity = new LabelerSensitivity<>(Event.class, (e) -> Optional.of(personId)); + optional = labelerSensitivity.getPersonId(new Event() { + }); + assertTrue(optional.isPresent()); + assertEquals(personId, optional.get()); + } + +} \ No newline at end of file diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/AT_Partition.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/AT_Partition.java new file mode 100644 index 000000000..4eeecb854 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/AT_Partition.java @@ -0,0 +1,291 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters.Filter; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters.TrueFilter; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.FunctionalAttributeLabeler; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support.AttributeFilter; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support.TestAttributeId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import util.annotations.UnitTestMethod; +import util.random.RandomGeneratorProvider; + +public class AT_Partition { + + /** + * Tests {@linkplain Partition#builder() + */ + @Test + @UnitTestMethod(target = Partition.class, name = "builder", args = {}) + public void testBuilder() { + Partition partition = Partition.builder().build(); + assertNotNull(partition); + assertFalse(partition.getFilter().isPresent()); + assertTrue(partition.getLabelers().isEmpty()); + assertTrue(partition.isDegenerate()); + } + + @Test + @UnitTestMethod(target = Partition.class, name = "getLabelers", args = {}) + public void testGetLabelers() { + + Set expectedLabelers = new LinkedHashSet<>(); + expectedLabelers.add(new FunctionalAttributeLabeler(TestAttributeId.BOOLEAN_0, (v) -> new Object())); + expectedLabelers.add(new FunctionalAttributeLabeler(TestAttributeId.BOOLEAN_1, (v) -> new Object())); + expectedLabelers.add(new FunctionalAttributeLabeler(TestAttributeId.DOUBLE_0, (v) -> new Object())); + + Partition.Builder builder = Partition.builder(); + for (Labeler labeler : expectedLabelers) { + builder.addLabeler(labeler); + } + + Partition partition = builder.build(); + + Set actualLabelers = partition.getLabelers(); + + assertEquals(expectedLabelers, actualLabelers); + + } + + /** + * Tests {@linkplain Partition#getFilter() + */ + @Test + @UnitTestMethod(target = Partition.class, name = "getFilter", args = {}) + public void testGetFilter() { + + Partition partition = Partition.builder().build();// + assertFalse(partition.getFilter().isPresent()); + + partition = Partition.builder().setFilter(new TrueFilter()).build();// + assertTrue(partition.getFilter().isPresent()); + + } + + /** + * Tests {@linkplain Partition#isDegenerate() + */ + @Test + @UnitTestMethod(target = Partition.class, name = "isDegenerate", args = {}) + public void testIsDegenerate() { + + Partition partition = Partition.builder().build();// + assertTrue(partition.isDegenerate()); + + partition = Partition.builder() + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.BOOLEAN_0, (v) -> new Object())).build(); + assertFalse(partition.isDegenerate()); + } + + @Test + @UnitTestMethod(target = Partition.class, name = "retainPersonKeys", args = {}) + public void testRetainPersonKeys() { + Partition retainKeys = Partition.builder().setRetainPersonKeys(true).build(); + Partition dontRetainKeys = Partition.builder().setRetainPersonKeys(false).build(); + + assertTrue(retainKeys.retainPersonKeys()); + assertFalse(dontRetainKeys.retainPersonKeys()); + } + + @Test + @UnitTestMethod(target = Partition.Builder.class, name = "build", args = {}) + public void testBuild() { + Partition partition = Partition.builder().build(); + assertNotNull(partition); + } + + @Test + @UnitTestMethod(target = Partition.Builder.class, name = "setFilter", args = { Filter.class }) + public void testSetFilter() { + Partition.Builder builder = Partition.builder(); + Filter filter = new TrueFilter(); + builder.setFilter(filter); + + Partition partition = builder.build(); + assertNotNull(partition); + assertEquals(filter, partition.getFilter().get()); + assertTrue(!Partition.builder().build().getFilter().isPresent()); + } + + @Test + @UnitTestMethod(target = Partition.Builder.class, name = "setRetainPersonKeys", args = { boolean.class }) + public void testSetRetainPersonKeys() { + Partition retainKeys = Partition.builder().setRetainPersonKeys(true).build(); + Partition dontRetainKeys = Partition.builder().setRetainPersonKeys(false).build(); + + assertTrue(retainKeys.retainPersonKeys()); + assertFalse(dontRetainKeys.retainPersonKeys()); + } + + @Test + @UnitTestMethod(target = Partition.Builder.class, name = "addLabeler", args = { Labeler.class }) + public void testAddlabeler() { + Set expectedLabelers = new LinkedHashSet<>(); + expectedLabelers.add(new FunctionalAttributeLabeler(TestAttributeId.BOOLEAN_0, (v) -> new Object())); + expectedLabelers.add(new FunctionalAttributeLabeler(TestAttributeId.BOOLEAN_1, (v) -> new Object())); + expectedLabelers.add(new FunctionalAttributeLabeler(TestAttributeId.DOUBLE_0, (v) -> new Object())); + + Partition.Builder builder = Partition.builder(); + for (Labeler labeler : expectedLabelers) { + builder.addLabeler(labeler); + } + + Partition partition = builder.build(); + + Set actualLabelers = partition.getLabelers(); + + assertEquals(expectedLabelers, actualLabelers); + } + + private static class LocalLabeler implements Labeler { + + private final int value; + + public LocalLabeler(int value) { + this.value = value; + } + + @Override + public Set> getLabelerSensitivities() { + return null; + } + + @Override + public Object getCurrentLabel(PartitionsContext partitionsContext, PersonId personId) { + return null; + } + + @Override + public Object getPastLabel(PartitionsContext partitionsContext, Event event) { + return null; + } + + @Override + public Object getId() { + return value; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + value; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof LocalLabeler)) { + return false; + } + LocalLabeler other = (LocalLabeler) obj; + if (value != other.value) { + return false; + } + return true; + } + + } + + private Partition getRandomPartition(long seed) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + Partition.Builder builder = Partition.builder(); + builder.setRetainPersonKeys(randomGenerator.nextBoolean()); + Labeler labeler = new LocalLabeler(randomGenerator.nextInt()); + builder.setFilter(new AttributeFilter(TestAttributeId.getRandomAttributeId(randomGenerator), + Equality.getRandomEquality(randomGenerator), randomGenerator.nextInt())); + builder.addLabeler(labeler); + return builder.build(); + } + + @Test + @UnitTestMethod(target = Partition.class, name = "equals", args = { Object.class }) + public void testEquals() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2832165952351188895L); + + // never equal null + for (int i = 0; i < 30; i++) { + Partition partition = getRandomPartition(randomGenerator.nextLong());// + assertFalse(partition.equals(null)); + } + + // reflexive + for (int i = 0; i < 30; i++) { + Partition partition = getRandomPartition(randomGenerator.nextLong());// + assertTrue(partition.equals(partition)); + } + + // symmetric, transitive, consistent + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + Partition partition1 = getRandomPartition(seed);// + Partition partition2 = getRandomPartition(seed);// + for (int j = 0; j < 5; j++) { + assertTrue(partition1.equals(partition2)); + assertTrue(partition2.equals(partition1)); + } + } + + // different inputs yield non-equal objects + Set partitions = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + + Partition partition = getRandomPartition(randomGenerator.nextLong());// + partitions.add(partition); + + } + assertEquals(100, partitions.size()); + + } + + @Test + @UnitTestMethod(target = Partition.class, name = "hashCode", args = {}) + public void testHashCode() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2170049186562286346L); + + // equal objects have equal hash codes + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + Partition partition1 = getRandomPartition(seed);// + Partition partition2 = getRandomPartition(seed);// + assertEquals(partition1, partition2); + assertEquals(partition1.hashCode(), partition2.hashCode()); + } + + + //hash codes are reasonably distributed + Set hashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + + Partition partition = getRandomPartition(randomGenerator.nextLong());// + hashCodes.add(partition.hashCode()); + + } + assertEquals(100, hashCodes.size()); + } + + + @Test + @UnitTestMethod(target = Partition.class, name = "toString", args = {}) + public void testToString() { + Partition randomPartition = getRandomPartition(5250756946904578664L); + String actualValue = randomPartition.toString(); + + String expectedValue = "Partition [data=Data [filter=AttributeFilter [attributeId=BOOLEAN_1, value=2146794287, equality=LESS_THAN, attributesDataManager=null], labelers={1157575879=gov.hhs.aspr.ms.gcm.plugins.partitions.support.AT_Partition$LocalLabeler@44ff34e6}, retainPersonKeys=false]]"; + assertEquals(expectedValue, actualValue); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/AT_PartitionError.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/AT_PartitionError.java new file mode 100644 index 000000000..9169b4738 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/AT_PartitionError.java @@ -0,0 +1,28 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestMethod; + +public class AT_PartitionError { + @Test + @UnitTestMethod(target = PartitionError.class, name = "getDescription", args = {}) + public void test() { + // show that each description is a unique, non-null and non-empty string + Set descriptions = new LinkedHashSet<>(); + for (PartitionError partitionError : PartitionError.values()) { + String description = partitionError.getDescription(); + assertNotNull(description, "null description for " + partitionError); + assertTrue(description.length() > 0, "empty string for " + partitionError); + boolean unique = descriptions.add(description); + assertTrue(unique, "description for " + partitionError + " is not unique"); + } + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/AT_PartitionSampler.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/AT_PartitionSampler.java new file mode 100644 index 000000000..d3a41b37a --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/AT_PartitionSampler.java @@ -0,0 +1,164 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.RandomNumberGeneratorId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.testsupport.TestRandomGeneratorId; +import util.annotations.UnitTestMethod; + +public class AT_PartitionSampler { + + /** + * Tests {@linkplain PartitionSampler#builder() + */ + @Test + @UnitTestMethod(target = PartitionSampler.class, name = "builder", args = {}) + public void testBuilder() { + assertNotNull(PartitionSampler.builder()); + } + + /** + * Tests {@linkplain PartitionSampler#getExcludedPerson() + */ + @Test + @UnitTestMethod(target = PartitionSampler.class, name = "getExcludedPerson", args = {}) + public void testGetExcludedPerson() { + PartitionSampler partitionSampler = PartitionSampler.builder().setExcludedPerson(new PersonId(67)).build(); + assertNotNull(partitionSampler); + assertNotNull(partitionSampler.getExcludedPerson()); + assertTrue(partitionSampler.getExcludedPerson().isPresent()); + assertEquals(67, partitionSampler.getExcludedPerson().get().getValue()); + } + + /** + * Tests {@linkplain PartitionSampler#getRandomNumberGeneratorId() + */ + @Test + @UnitTestMethod(target = PartitionSampler.class, name = "getRandomNumberGeneratorId", args = {}) + public void testGetRandomNumberGeneratorId() { + PartitionSampler partitionSampler = PartitionSampler.builder().setRandomNumberGeneratorId(TestRandomGeneratorId.DASHER).setRandomNumberGeneratorId(TestRandomGeneratorId.VIXEN).build(); + + assertNotNull(partitionSampler); + assertNotNull(partitionSampler.getRandomNumberGeneratorId()); + assertTrue(partitionSampler.getRandomNumberGeneratorId().isPresent()); + assertEquals(TestRandomGeneratorId.VIXEN, partitionSampler.getRandomNumberGeneratorId().get()); + } + + private static enum Dimensions { + DIM_1, DIM_2; + } + + /** + * Tests {@linkplain PartitionSampler#getLabelSet() + */ + @Test + @UnitTestMethod(target = PartitionSampler.class, name = "getLabelSet", args = {}) + public void testGetLabelSet() { + PartitionSampler partitionSampler = PartitionSampler.builder().setLabelSet(LabelSet .builder()// + .setLabel(Dimensions.DIM_1, "compartmentLabel")// + .setLabel(Dimensions.DIM_2, "regionLabel").build())// + .build(); + + assertNotNull(partitionSampler); + assertNotNull(partitionSampler.getLabelSet()); + assertTrue(partitionSampler.getLabelSet().isPresent()); + LabelSet labelSet = partitionSampler.getLabelSet().get(); + + assertTrue(labelSet.getLabel(Dimensions.DIM_1).isPresent()); + assertEquals("compartmentLabel", labelSet.getLabel(Dimensions.DIM_1).get()); + assertTrue(labelSet.getLabel(Dimensions.DIM_2).isPresent()); + assertEquals("regionLabel", labelSet.getLabel(Dimensions.DIM_2).get()); + } + + /** + * Tests {@linkplain PartitionSampler#getLabelSetWeightingFunction() + */ + @Test + @UnitTestMethod(target = PartitionSampler.class, name = "getLabelSetWeightingFunction", args = {}) + public void testGetLabelSetWeightingFunction() { + + double expectedValue = 17.5; + + PartitionSampler partitionSampler = PartitionSampler.builder().setLabelSetWeightingFunction((context, labelSet) -> expectedValue).build(); + + assertNotNull(partitionSampler); + assertNotNull(partitionSampler.getLabelSetWeightingFunction()); + assertTrue(partitionSampler.getLabelSetWeightingFunction().isPresent()); + LabelSetWeightingFunction labelSetWeightingFunction = partitionSampler.getLabelSetWeightingFunction().get(); + assertNotNull(labelSetWeightingFunction); + + assertEquals(expectedValue, labelSetWeightingFunction.getWeight(null, null), 0); + } + + @Test + @UnitTestMethod(target = PartitionSampler.Builder.class, name = "build", args = {}) + public void testBuild() { + PartitionSampler.Builder builder = PartitionSampler.builder(); + + PartitionSampler partitionSampler = builder.build(); + assertNotNull(partitionSampler); + + } + + @Test + @UnitTestMethod(target = PartitionSampler.Builder.class, name = "setRandomNumberGeneratorId", args = { RandomNumberGeneratorId.class }) + public void testSetRandomNumberGeneratorId() { + PartitionSampler partitionSampler = PartitionSampler.builder().setRandomNumberGeneratorId(TestRandomGeneratorId.DASHER).setRandomNumberGeneratorId(TestRandomGeneratorId.VIXEN).build(); + + assertNotNull(partitionSampler); + assertNotNull(partitionSampler.getRandomNumberGeneratorId()); + assertTrue(partitionSampler.getRandomNumberGeneratorId().isPresent()); + assertEquals(TestRandomGeneratorId.VIXEN, partitionSampler.getRandomNumberGeneratorId().get()); + } + + @Test + @UnitTestMethod(target = PartitionSampler.Builder.class, name = "setLabelSetWeightingFunction", args = { LabelSetWeightingFunction.class }) + public void testSetLabelSetWeightingFunction() { + double expectedValue = 20.5; + + PartitionSampler partitionSampler = PartitionSampler.builder().setLabelSetWeightingFunction((context, labelSet) -> expectedValue).build(); + + assertNotNull(partitionSampler); + assertNotNull(partitionSampler.getLabelSetWeightingFunction()); + assertTrue(partitionSampler.getLabelSetWeightingFunction().isPresent()); + LabelSetWeightingFunction labelSetWeightingFunction = partitionSampler.getLabelSetWeightingFunction().get(); + assertNotNull(labelSetWeightingFunction); + + assertEquals(expectedValue, labelSetWeightingFunction.getWeight(null, null), 0); + } + + @Test + @UnitTestMethod(target = PartitionSampler.Builder.class, name = "setExcludedPerson", args = { PersonId.class }) + public void testSetExcludedPerson() { + PartitionSampler partitionSampler = PartitionSampler.builder().setExcludedPerson(new PersonId(67)).build(); + assertNotNull(partitionSampler); + assertNotNull(partitionSampler.getExcludedPerson()); + assertTrue(partitionSampler.getExcludedPerson().isPresent()); + assertEquals(67, partitionSampler.getExcludedPerson().get().getValue()); + } + + @Test + @UnitTestMethod(target = PartitionSampler.Builder.class, name = "setLabelSet", args = { LabelSet.class }) + public void testSetLabelSet() { + PartitionSampler partitionSampler = PartitionSampler.builder().setLabelSet(LabelSet .builder()// + .setLabel(Dimensions.DIM_1, "compartmentLabel")// + .setLabel(Dimensions.DIM_2, "regionLabel").build())// + .build(); + + assertNotNull(partitionSampler); + assertNotNull(partitionSampler.getLabelSet()); + assertTrue(partitionSampler.getLabelSet().isPresent()); + LabelSet labelSet = partitionSampler.getLabelSet().get(); + + assertTrue(labelSet.getLabel(Dimensions.DIM_1).isPresent()); + assertEquals("compartmentLabel", labelSet.getLabel(Dimensions.DIM_1).get()); + assertTrue(labelSet.getLabel(Dimensions.DIM_2).isPresent()); + assertEquals("regionLabel", labelSet.getLabel(Dimensions.DIM_2).get()); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/AT_PopulationPartitionImpl.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/AT_PopulationPartitionImpl.java new file mode 100644 index 000000000..84774ae38 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/AT_PopulationPartitionImpl.java @@ -0,0 +1,1285 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; + +import org.apache.commons.math3.random.RandomGenerator; +import org.apache.commons.math3.util.FastMath; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.partitions.datamanagers.PartitionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters.Filter; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.FunctionalAttributeLabeler; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.PartitionsTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.PartitionsTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.TestPartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.AttributesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.events.AttributeUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support.AttributeFilter; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support.TestAttributeId; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonConstructionData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.random.RandomGeneratorProvider; + +public class AT_PopulationPartitionImpl { + + @Test + @UnitTestConstructor(target = PopulationPartitionImpl.class, args = { PartitionsContext.class, Partition.class,boolean.class }) + public void testConstructor() { + Factory factory = PartitionsTestPluginFactory.factory(100, 2997202170895856110L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + // establish data view + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + // select about half of the people + Set expectedPeople = new LinkedHashSet<>(); + for (PersonId personId : peopleDataManager.getPeople()) { + if (randomGenerator.nextBoolean()) { + expectedPeople.add(personId); + } + } + + // set attribute BOOLEAN_0 to true for those people + for (PersonId personId : expectedPeople) { + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, true); + } + + // create the population partition + Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); + Partition partition = Partition.builder().setFilter(filter).build(); + PopulationPartition populationPartition = new PopulationPartitionImpl(testPartitionsContext, partition,false); + + // show that the population partition contains the expected people + List actualPeople = populationPartition.getPeople(); + assertEquals(expectedPeople.size(), actualPeople.size()); + assertEquals(expectedPeople, new LinkedHashSet<>(actualPeople)); + + // precondition tests + // if the context is null + assertThrows(RuntimeException.class, () -> new PopulationPartitionImpl(null, partition,false)); + + // if the partition is null + assertThrows(RuntimeException.class, () -> new PopulationPartitionImpl(testPartitionsContext, null,false)); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = PopulationPartitionImpl.class, name = "attemptPersonAddition", args = { PersonId.class }) + public void testAttemptPersonAddition() { + + Factory factory = PartitionsTestPluginFactory.factory(100, 3063819509780972206L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + // establish data views + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + + /* + * Create the population partition filtering on attribute BOOLEAN_0 + * = true + */ + Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); + Partition partition = Partition.builder().setFilter(filter).build(); + PopulationPartition populationPartition = new PopulationPartitionImpl(testPartitionsContext, partition,false); + + // precondition test: + assertThrows(RuntimeException.class, () -> populationPartition.attemptPersonAddition(null)); + + /* + * Add new people, setting the attribute to alternating values of + * true and false + */ + for (int i = 0; i < 20; i++) { + PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); + boolean attributeValue = i % 2 == 0; + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, attributeValue); + populationPartition.attemptPersonAddition(personId); + + /* + * Show that the person is in the population partition if and + * only if their attribute value was set to true + */ + assertEquals(attributeValue, populationPartition.contains(personId)); + } + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = PopulationPartitionImpl.class, name = "attemptPersonRemoval", args = { PersonId.class }) + public void testAttemptPersonRemoval() { + + Factory factory = PartitionsTestPluginFactory.factory(100, 4856457716960397685L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + // establish data views + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + /* + * Create a container for the people we expect to be contained in + * the population partition. + */ + Set expectedPeople = new LinkedHashSet<>(); + + // select about half of the people to have attribute BOOLEAN_0 value + // of true + for (PersonId personId : peopleDataManager.getPeople()) { + if (randomGenerator.nextBoolean()) { + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, true); + expectedPeople.add(personId); + } + } + + /* + * Create the population partition filtering on attribute BOOLEAN_0 + * = true + */ + Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); + Partition partition = Partition.builder().setFilter(filter).build(); + PopulationPartition populationPartition = new PopulationPartitionImpl(testPartitionsContext, partition,false); + + // show that the expected people are in the population partition + List actualPeople = populationPartition.getPeople(); + assertEquals(expectedPeople.size(), actualPeople.size()); + assertEquals(expectedPeople, new LinkedHashSet<>(actualPeople)); + + /* + * Remove people and show that they are no longer in the partition + */ + for (PersonId personId : expectedPeople) { + peopleDataManager.removePerson(personId); + populationPartition.attemptPersonRemoval(personId); + // show that the person was removed + assertFalse(populationPartition.contains(personId)); + } + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = PopulationPartitionImpl.class, name = "handleEvent", args = { Event.class }) + public void testHandleEvent() { + Factory factory = PartitionsTestPluginFactory.factory(100, 8982209428616460818L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + // establish data views + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + + for (PersonId personId : peopleDataManager.getPeople()) { + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, randomGenerator.nextBoolean()); + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_1, randomGenerator.nextBoolean()); + } + + /* + * Create the population partition filtering on attribute BOOLEAN_0 + * = true + */ + + Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); + Partition partition = Partition.builder().addLabeler(new FunctionalAttributeLabeler(TestAttributeId.BOOLEAN_1, (v) -> v)).setFilter(filter).build(); + PopulationPartition populationPartition = new PopulationPartitionImpl(testPartitionsContext, partition,false); + + for (PersonId personId : peopleDataManager.getPeople()) { + Boolean b0 = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); + Boolean b1 = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_1); + + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, !b0); + populationPartition.handleEvent(new AttributeUpdateEvent(personId, TestAttributeId.BOOLEAN_0, b0, !b0)); + + assertEquals(!b0, populationPartition.contains(personId)); + + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_1, !b1); + populationPartition.handleEvent(new AttributeUpdateEvent(personId, TestAttributeId.BOOLEAN_1, b1, !b1)); + + if (!b0) { + LabelSet labelSet = LabelSet.builder().setLabel(TestAttributeId.BOOLEAN_1, !b1).build(); + assertTrue(populationPartition.contains(personId, labelSet)); + + labelSet = LabelSet.builder().setLabel(TestAttributeId.BOOLEAN_1, b1).build(); + assertFalse(populationPartition.contains(personId, labelSet)); + } + } + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = PopulationPartitionImpl.class, name = "validateLabelSetInfo", args = { LabelSet.class }) + public void testValidateLabelSetInfo() { + + Factory factory = PartitionsTestPluginFactory.factory(100, 4662203440339012044L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + /* + * Create the population partition filtering on attribute BOOLEAN_0 + * = true + */ + Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); + Partition partition = Partition .builder().setFilter(filter).addLabeler(new FunctionalAttributeLabeler(TestAttributeId.BOOLEAN_1, (v) -> 1)) + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.INT_0, (i) -> "value")).build(); + PopulationPartition populationPartition = new PopulationPartitionImpl(testPartitionsContext, partition,false); + + LabelSet labelSet = LabelSet.builder().setLabel(TestAttributeId.BOOLEAN_1, 2).build(); + assertTrue(populationPartition.validateLabelSetInfo(labelSet)); + + labelSet = LabelSet.builder().setLabel(TestAttributeId.INT_0, 2).build(); + assertTrue(populationPartition.validateLabelSetInfo(labelSet)); + + labelSet = LabelSet.builder().setLabel(TestAttributeId.INT_1, 2).build(); + assertFalse(populationPartition.validateLabelSetInfo(labelSet)); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = PopulationPartitionImpl.class, name = "getPeopleCount", args = {}) + public void testGetPeopleCount() { + Factory factory = PartitionsTestPluginFactory.factory(100, 9050139615348413060L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + // establish data views + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + /* + * Create a container for the people we expect to be contained in + * the population partition. + */ + Set expectedPeople = new LinkedHashSet<>(); + + // select about half of the people to have attribute BOOLEAN_0 value + // of true + for (PersonId personId : peopleDataManager.getPeople()) { + if (randomGenerator.nextBoolean()) { + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, true); + expectedPeople.add(personId); + } + } + + /* + * Create the population partition filtering on attribute BOOLEAN_0 + * = true + */ + Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); + Partition partition = Partition.builder().setFilter(filter).build(); + PopulationPartition populationPartition = new PopulationPartitionImpl(testPartitionsContext, partition,false); + + // show that the people count matches expectations + assertEquals(expectedPeople.size(), populationPartition.getPeopleCount()); + + /* + * Change the attributes for the expected people and show that the + * expected count is correct + */ + int expectedPeopleCount = expectedPeople.size(); + for (PersonId personId : expectedPeople) { + expectedPeopleCount--; + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, false); + populationPartition.handleEvent(new AttributeUpdateEvent(personId, TestAttributeId.BOOLEAN_0, true, false)); + assertEquals(expectedPeopleCount, populationPartition.getPeopleCount()); + } + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + private static Function INT_0_LABELFUNCTION = (value) -> { + final int v = (Integer) value; + return v % 3; + }; + + private static Function INT_1_LABELFUNCTION = (value) -> { + final int v = (Integer) value; + if (v < 40) { + return true; + } + return false; + }; + + private static Function DOUBLE_0_LABELFUNCTION = (value) -> { + final double v = (Double) value; + if (v < 33) { + return "A"; + } + if (v < 67) { + return "B"; + } + return "C"; + }; + + private static Function DOUBLE_1_LABELFUNCTION = (value) -> { + final double v = (Double) value; + return v < 90; + }; + + /* + * Assigns randomized values for all attributes to all people. Values are + * assigned to be consistent with the static labeling functions. + */ + private static void assignRandomAttributes(final ActorContext c) { + final PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + final StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + final RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + for (final PersonId personId : peopleDataManager.getPeople()) { + boolean b = randomGenerator.nextBoolean(); + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, b); + + b = randomGenerator.nextBoolean(); + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_1, b); + + int i = randomGenerator.nextInt(100); + attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_0, i); + + i = randomGenerator.nextInt(100); + attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_1, i); + + double d = randomGenerator.nextDouble() * 100; + attributesDataManager.setAttributeValue(personId, TestAttributeId.DOUBLE_0, d); + + d = randomGenerator.nextDouble() * 100; + attributesDataManager.setAttributeValue(personId, TestAttributeId.DOUBLE_1, d); + } + } + + /* + * Creates a map from LabelSet to PersonId that covers all people who have + * an attribute value of true for BOOLEAN_0 and false for BOOLEAN_1, to be + * consistent with the filter used in the partition addition test. Label + * sets consist of labels for INT_0, INT_1, DOUBLE_0 and DOUBLE_1. + */ + private static Map> getExpectedStructure(final ActorContext c) { + final PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + final AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + final Map> expectedPeople = new LinkedHashMap<>(); + for (final PersonId personId : peopleDataManager.getPeople()) { + + final Boolean b0 = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); + final Boolean b1 = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_1); + if (b0 && !b1) { + + final Integer i0 = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_0); + final Object label_i0 = INT_0_LABELFUNCTION.apply(i0); + + final Integer i1 = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_1); + final Object label_i1 = INT_1_LABELFUNCTION.apply(i1); + + final Double d0 = attributesDataManager.getAttributeValue(personId, TestAttributeId.DOUBLE_0); + final Object label_d0 = DOUBLE_0_LABELFUNCTION.apply(d0); + + final Double d1 = attributesDataManager.getAttributeValue(personId, TestAttributeId.DOUBLE_1); + final Object label_d1 = DOUBLE_1_LABELFUNCTION.apply(d1); + + final LabelSet labelSet = LabelSet .builder()// + .setLabel(TestAttributeId.INT_0, label_i0)// + .setLabel(TestAttributeId.INT_1, label_i1)// + .setLabel(TestAttributeId.DOUBLE_0, label_d0)// + .setLabel(TestAttributeId.DOUBLE_1, label_d1)// + .build();// + + Set people = expectedPeople.get(labelSet); + if (people == null) { + people = new LinkedHashSet<>(); + expectedPeople.put(labelSet, people); + } + people.add(personId); + } + } + return expectedPeople; + + } + + @Test + @UnitTestMethod(target = PopulationPartitionImpl.class, name = "getPeopleCount", args = { LabelSet.class }) + public void testGetPeopleCount_LabelSet() { + Factory factory = PartitionsTestPluginFactory.factory(1000, 8522399796145249846L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + // Randomize the attribute values for all people + assignRandomAttributes(c); + + // build a container to hold the expected relationship from label + // sets to people + Map> expectedStructure = getExpectedStructure(c); + + /* + * Create the population partition filtering on attribute BOOLEAN_0 + * = true + */ + Filter filter_0 = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); + Filter filter_1 = new AttributeFilter(TestAttributeId.BOOLEAN_1, Equality.EQUAL, false); + Filter filter = filter_0.and(filter_1); + Partition partition = Partition .builder()// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.INT_0, INT_0_LABELFUNCTION))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.INT_1, INT_1_LABELFUNCTION))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.DOUBLE_0, DOUBLE_0_LABELFUNCTION))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.DOUBLE_1, DOUBLE_1_LABELFUNCTION))// + .setFilter(filter)// + .build();// + + PopulationPartition populationPartition = new PopulationPartitionImpl(testPartitionsContext, partition,false); + + // show that the people count matches expectations + int expectedCount = 0; + for (LabelSet labelSet : expectedStructure.keySet()) { + expectedCount += expectedStructure.get(labelSet).size(); + } + assertEquals(expectedCount, populationPartition.getPeopleCount()); + + for (LabelSet labelSet : expectedStructure.keySet()) { + Set expectedPeople = expectedStructure.get(labelSet); + List actualPeople = populationPartition.getPeople(labelSet); + assertEquals(expectedPeople.size(), actualPeople.size()); + } + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = PopulationPartitionImpl.class, name = "getPeopleCountMap", args = { LabelSet.class }) + public void testGetPeopleCountMap() { + Factory factory = PartitionsTestPluginFactory.factory(1000, 4793886153660135719L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + // Randomize the attribute values for all people + assignRandomAttributes(c); + + /* + * Create the population partition filtering on attribute BOOLEAN_0 + * = true and BOOLEAN_1 = false + */ + Filter filter_0 = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); + Filter filter_1 = new AttributeFilter(TestAttributeId.BOOLEAN_1, Equality.EQUAL, false); + Filter filter = filter_0.and(filter_1); + Partition partition = Partition .builder().addLabeler(new FunctionalAttributeLabeler(TestAttributeId.INT_0, INT_0_LABELFUNCTION))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.INT_1, INT_1_LABELFUNCTION))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.DOUBLE_0, DOUBLE_0_LABELFUNCTION))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.DOUBLE_1, DOUBLE_1_LABELFUNCTION))// + .setFilter(filter)// + .build();// + + PopulationPartition populationPartition = new PopulationPartitionImpl(testPartitionsContext, partition,false); + + List int_0_labelValues = new ArrayList<>(); + int_0_labelValues.add(0); + int_0_labelValues.add(1); + int_0_labelValues.add(2); + int_0_labelValues.add(3); + + List double_0_labelValues = new ArrayList<>(); + double_0_labelValues.add("A"); + double_0_labelValues.add("B"); + double_0_labelValues.add("C"); + double_0_labelValues.add("D"); + + // build a container to hold the expected relationship from label + // sets to people + Map> expectedStructure = new LinkedHashMap<>(); + + // build the keys of the expected structure + + for (Integer int_0_labelValue : int_0_labelValues) { + for (String double_0_labelValue : double_0_labelValues) { + LabelSet.Builder labelSetBuilder = LabelSet.builder(); + LabelSet labelSet = labelSetBuilder.setLabel(TestAttributeId.INT_0, int_0_labelValue).setLabel(TestAttributeId.DOUBLE_0, double_0_labelValue).build(); + expectedStructure.put(labelSet, new LinkedHashMap<>()); + } + } + + // build the values of the expected structure + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + for (PersonId personId : peopleDataManager.getPeople()) { + Boolean b0 = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); + Boolean b1 = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_1); + if (b0 && !b1) { + Integer i0 = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_0); + Object int_0_label_value = INT_0_LABELFUNCTION.apply(i0); + Double d0 = attributesDataManager.getAttributeValue(personId, TestAttributeId.DOUBLE_0); + Object double_0_label_value = DOUBLE_0_LABELFUNCTION.apply(d0); + Integer i1 = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_1); + Object int_1_label_value = INT_1_LABELFUNCTION.apply(i1); + Double d1 = attributesDataManager.getAttributeValue(personId, TestAttributeId.DOUBLE_1); + Object double_1_label_value = DOUBLE_1_LABELFUNCTION.apply(d1); + + LabelSet labelSet = LabelSet.builder().setLabel(TestAttributeId.INT_0, int_0_label_value).setLabel(TestAttributeId.DOUBLE_0, double_0_label_value).build(); + Map map = expectedStructure.get(labelSet); + labelSet = LabelSet .builder().setLabel(TestAttributeId.INT_0, int_0_label_value).setLabel(TestAttributeId.DOUBLE_0, double_0_label_value) + .setLabel(TestAttributeId.INT_1, int_1_label_value).setLabel(TestAttributeId.DOUBLE_1, double_1_label_value).build(); + Integer count = map.get(labelSet); + if (count == null) { + count = 0; + } + count = count + 1; + map.put(labelSet, count); + } + } + + // show that every expected people count map corresponds to an + // identical people count map from the population partition + for (LabelSet labelSet : expectedStructure.keySet()) { + Map expectedPeopleCountMap = expectedStructure.get(labelSet); + Map actualPeopleCountMap = populationPartition.getPeopleCountMap(labelSet); + assertEquals(expectedPeopleCountMap, actualPeopleCountMap); + } + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = PopulationPartitionImpl.class, name = "contains", args = { PersonId.class }) + public void testContains() { + Factory factory = PartitionsTestPluginFactory.factory(100, 2652052463264971998L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + // establish data views + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + + /* + * Create a container for the people we expect to be contained in + * the population partition. + */ + Set expectedPeople = new LinkedHashSet<>(); + + // select about half of the people to have attribute BOOLEAN_0 value + // of true + for (PersonId personId : peopleDataManager.getPeople()) { + if (randomGenerator.nextBoolean()) { + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, true); + expectedPeople.add(personId); + + } + } + + /* + * Create the population partition filtering on attribute BOOLEAN_0 + * = true + */ + Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); + Partition partition = Partition.builder().setFilter(filter).build(); + PopulationPartition populationPartition = new PopulationPartitionImpl(testPartitionsContext, partition,false); + + // show that the person data view contains the people we expect + assertEquals(expectedPeople.size(), populationPartition.getPeople().size()); + for (PersonId personId : peopleDataManager.getPeople()) { + + assertEquals(expectedPeople.contains(personId), populationPartition.contains(personId)); + } + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = PopulationPartitionImpl.class, name = "contains", args = { PersonId.class, LabelSet.class }) + public void testContains_LabelSet() { + Factory factory = PartitionsTestPluginFactory.factory(1000, 827063967966581841L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + // Randomize the attribute values for all people + assignRandomAttributes(c); + + // build a container to hold the expected relationship from label + // sets to people + Map> expectedStructure = getExpectedStructure(c); + + /* + * Create the population partition filtering on attribute BOOLEAN_0 + * = true + */ + Filter filter_0 = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); + Filter filter_1 = new AttributeFilter(TestAttributeId.BOOLEAN_1, Equality.EQUAL, false); + Filter filter = filter_0.and(filter_1); + Partition partition = Partition .builder().addLabeler(new FunctionalAttributeLabeler(TestAttributeId.INT_0, INT_0_LABELFUNCTION))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.INT_1, INT_1_LABELFUNCTION))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.DOUBLE_0, DOUBLE_0_LABELFUNCTION))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.DOUBLE_1, DOUBLE_1_LABELFUNCTION))// + .setFilter(filter)// + .build();// + + PopulationPartition populationPartition = new PopulationPartitionImpl(testPartitionsContext, partition,false); + + // show that the people count matches expectations + int expectedCount = 0; + for (LabelSet labelSet : expectedStructure.keySet()) { + expectedCount += expectedStructure.get(labelSet).size(); + } + assertEquals(expectedCount, populationPartition.getPeopleCount()); + + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + List allPeople = peopleDataManager.getPeople(); + + // show that each label set contains the people expected + for (LabelSet labelSet : expectedStructure.keySet()) { + Set expectedPeople = expectedStructure.get(labelSet); + for (PersonId personId : allPeople) { + assertEquals(expectedPeople.contains(personId), populationPartition.contains(personId, labelSet)); + } + } + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = PopulationPartitionImpl.class, name = "getPeople", args = { LabelSet.class }) + public void testGetPeople_LabelSet() { + Factory factory = PartitionsTestPluginFactory.factory(1000, 1040083420377037302L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + // Randomize the attribute values for all people + assignRandomAttributes(c); + + // build a container to hold the expected relationship from label + // sets to people + Map> expectedStructure = getExpectedStructure(c); + + /* + * Create the population partition filtering on attribute BOOLEAN_0 + * = true + */ + Filter filter_0 = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); + Filter filter_1 = new AttributeFilter(TestAttributeId.BOOLEAN_1, Equality.EQUAL, false); + Filter filter = filter_0.and(filter_1); + Partition partition = Partition .builder().addLabeler(new FunctionalAttributeLabeler(TestAttributeId.INT_0, INT_0_LABELFUNCTION))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.INT_1, INT_1_LABELFUNCTION))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.DOUBLE_0, DOUBLE_0_LABELFUNCTION))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.DOUBLE_1, DOUBLE_1_LABELFUNCTION))// + .setFilter(filter)// + .build();// + + PopulationPartition populationPartition = new PopulationPartitionImpl(testPartitionsContext, partition,false); + + // show that the people count matches expectations + int expectedCount = 0; + for (LabelSet labelSet : expectedStructure.keySet()) { + expectedCount += expectedStructure.get(labelSet).size(); + } + assertEquals(expectedCount, populationPartition.getPeopleCount()); + + for (LabelSet labelSet : expectedStructure.keySet()) { + Set expectedPeople = expectedStructure.get(labelSet); + List actualPeople = populationPartition.getPeople(labelSet); + assertEquals(expectedPeople.size(), actualPeople.size()); + assertEquals(expectedPeople, new LinkedHashSet<>(actualPeople)); + } + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = PopulationPartitionImpl.class, name = "getPeople", args = {}) + public void testGetPeople() { + Factory factory = PartitionsTestPluginFactory.factory(100, 4597503339659285165L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + // establish data views + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + + /* + * Create a container for the people we expect to be contained in + * the population partition. + */ + Set expectedPeople = new LinkedHashSet<>(); + + // select about half of the people to have attribute BOOLEAN_0 value + // of true + for (PersonId personId : peopleDataManager.getPeople()) { + if (randomGenerator.nextBoolean()) { + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, true); + expectedPeople.add(personId); + } + } + + /* + * Create the population partition filtering on attribute BOOLEAN_0 + * = true + */ + Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); + Partition partition = Partition.builder().addLabeler(new FunctionalAttributeLabeler(TestAttributeId.BOOLEAN_1, (v) -> v)).setFilter(filter).build(); + PopulationPartition populationPartition = new PopulationPartitionImpl(testPartitionsContext, partition,false); + + // show that the person data view contains the people we expect + assertEquals(expectedPeople.size(), populationPartition.getPeople().size()); + assertEquals(expectedPeople, new LinkedHashSet<>(populationPartition.getPeople())); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + private static enum ExcludedPersonType { + NULL, MATCHING_MEMBER, NON_MATCHING_MEMBER, NON_MEMBER; + } + + @Test + @UnitTestMethod(target = PopulationPartitionImpl.class, name = "samplePartition", args = { PartitionSampler.class }) + public void testSamplePartition() { + /* + * Tests the sample mechanism under a variety of partition samplers. + * + * The partition is formed from 4 labeling functions over the attributes + * + * INT_0-> 0, 1, 2 + * + * INT_1 -> TRUE, FALSE + * + * DOUBLE_0 -> A, B, C + * + * DOUBLE_1 -> TRUE, FALSE + * + * Filtering for the partition is either on or off. The filter passes + * when the attribute BOOLEAN_0 is true. + * + * The partition sampler will optionally set its excluded person to + * null, a person not in the partition, a person in the partition who is + * not expected to match the sampler's label set and a person who does + * match the sampler's label set. + * + * The partition sampler will optionally use a label set. The label set + * will be composed of combinations of labels over INT_0 and DOUBLE_0, + * using label values that are associated with people and some that are + * not. + * + * The partition sampler will optionally use a weighting function. The + * weighting function will return 1 for any person having a label of + * TRUE for INT_1 and 0 otherwise. + * + * This test does not demonstrate precondition checks, proper use of + * random number generator ids, or the proper distribution of results + * aligned to the weighting function other that the simple binary + * alignment for the weighting function described above. + * + * Each combination is run with a randomly generated seed value. + * + */ + + Set int_0_label_values = new LinkedHashSet<>(); + int_0_label_values.add(0); + int_0_label_values.add(1); + int_0_label_values.add(2); + int_0_label_values.add(3);// will not match any person + + Set double_0_label_values = new LinkedHashSet<>(); + double_0_label_values.add("A"); + double_0_label_values.add("B"); + double_0_label_values.add("C"); + double_0_label_values.add("D");// will not match any person + + Set weightingFunctionValues = new LinkedHashSet<>(); + weightingFunctionValues.add(false); + weightingFunctionValues.add(true); + + Set useFilterValues = new LinkedHashSet<>(); + useFilterValues.add(false); + useFilterValues.add(true); + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6166310781583500795L); + + for (ExcludedPersonType excludedPersonType : ExcludedPersonType.values()) { + for (Boolean useWeightingFunction : weightingFunctionValues) { + for (Boolean useFilter : useFilterValues) { + long seed = randomGenerator.nextLong(); + executeSamplingTest(seed, useFilter, excludedPersonType, useWeightingFunction, null, null); + for (Integer int_0_label_value : int_0_label_values) { + for (String double_0_label_value : double_0_label_values) { + seed = randomGenerator.nextLong(); + executeSamplingTest(seed, useFilter, excludedPersonType, useWeightingFunction, int_0_label_value, double_0_label_value); + } + } + } + } + } + + } + + private void executeSamplingTest(long seed, Boolean useFilter, ExcludedPersonType excludedPersonType, Boolean useWeightingFunction, Integer int_0_label_value, String double_0_label_value) { + + Factory factory = PartitionsTestPluginFactory.factory(1000, seed, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + // remember to test with general and COMET to show they get + // different results? + + // establish data views + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + + /* + * Define functions that will convert attribute values into labels + * for attributes INT_0, INT_1, DOUBLE_0, and DOUBLE_1. We will use + * these in the partition's labeling + */ + Function int_0_labelFunction = (value) -> { + int v = (Integer) value; + return v % 3; + }; + + Function int_1_labelFunction = (value) -> { + int v = (Integer) value; + if (v < 40) { + return true; + } + return false; + }; + + Function double_0_labelFunction = (value) -> { + double v = (Double) value; + if (v < 33) { + return "A"; + } + if (v < 67) { + return "B"; + } + return "C"; + }; + + Function double_1_labelFunction = (value) -> { + double v = (Double) value; + return v < 90; + }; + + // determine the people in the world + List peopleInTheWorld = peopleDataManager.getPeople(); + + // alter people's attributes randomly + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + for (PersonId personId : peopleInTheWorld) { + int intValue = (int) (randomGenerator.nextDouble() * 100); + attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_0, intValue); + + intValue = (int) (randomGenerator.nextDouble() * 100); + attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_1, intValue); + + double doubleValue = randomGenerator.nextDouble() * 100; + attributesDataManager.setAttributeValue(personId, TestAttributeId.DOUBLE_0, doubleValue); + + doubleValue = randomGenerator.nextDouble() * 100; + attributesDataManager.setAttributeValue(personId, TestAttributeId.DOUBLE_1, doubleValue); + + boolean booleanValue = randomGenerator.nextBoolean(); + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, booleanValue); + + booleanValue = randomGenerator.nextBoolean(); + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_1, booleanValue); + + } + + /* + * Create a partition that may filter about half of the population + * on BOOLEAN_0. We will partition on INT_0, INT_1, DOUBLE_0 and + * DOUBLE_1. Note that we do not use BOOLEAN_1 as part of the + * partition. + */ + Partition.Builder partitionBuilder = Partition.builder(); + if (useFilter) { + partitionBuilder// + .setFilter(new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true));// + } + partitionBuilder// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.INT_0, int_0_labelFunction))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.INT_1, int_1_labelFunction))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.DOUBLE_0, double_0_labelFunction))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.DOUBLE_1, double_1_labelFunction)); + + Partition partition = partitionBuilder.build(); + + PopulationPartitionImpl populationPartition = new PopulationPartitionImpl(testPartitionsContext, partition,false); + + /* + * Create a label set for the query that does not contain all the + * attribute labels and has legitimate values for each dimension. + */ + LabelSet queryLabelSet = null; + if (int_0_label_value != null && double_0_label_value != null) { + LabelSet.Builder labelSetBuilder = LabelSet.builder(); + labelSetBuilder.setLabel(TestAttributeId.INT_0, int_0_label_value); + labelSetBuilder.setLabel(TestAttributeId.DOUBLE_0, double_0_label_value); + queryLabelSet = labelSetBuilder.build(); + } + + // determine the people in the partition + Set expectedPeopleInPartition = new LinkedHashSet<>(); + + for (PersonId personId : peopleInTheWorld) { + + if (useFilter) { + Boolean personInPartition = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); + if (personInPartition) { + expectedPeopleInPartition.add(personId); + } + } else { + expectedPeopleInPartition.add(personId); + } + + } + + // determine the people who will match the query label set + Set expectedPeopleMatchingQueryLabelSet = new LinkedHashSet<>(); + if (queryLabelSet != null) { + for (PersonId personId : expectedPeopleInPartition) { + // will the person pass the filter? + + Integer intValue = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_0); + Object labelValue = int_0_labelFunction.apply(intValue); + if (labelValue.equals(int_0_label_value)) { + Double doubleValue = attributesDataManager.getAttributeValue(personId, TestAttributeId.DOUBLE_0); + labelValue = double_0_labelFunction.apply(doubleValue); + if (labelValue.equals(double_0_label_value)) { + expectedPeopleMatchingQueryLabelSet.add(personId); + } + + } + + } + } else { + expectedPeopleMatchingQueryLabelSet.addAll(expectedPeopleInPartition); + } + + PartitionSampler.Builder partitionSamplerBuilder = PartitionSampler.builder(); + partitionSamplerBuilder.setLabelSet(queryLabelSet);// + + // Select the excluded person + PersonId excludedPersonId = null; + switch (excludedPersonType) { + case MATCHING_MEMBER: + for (PersonId personId : expectedPeopleMatchingQueryLabelSet) { + excludedPersonId = personId; + break; + } + + break; + case NON_MATCHING_MEMBER: + for (PersonId personId : expectedPeopleInPartition) { + if (!expectedPeopleMatchingQueryLabelSet.contains(personId)) { + excludedPersonId = personId; + break; + } + } + break; + + case NON_MEMBER: + if (useFilter) { + for (PersonId personId : peopleInTheWorld) { + if (!expectedPeopleInPartition.contains(personId)) { + excludedPersonId = personId; + break; + } + } + } + break; + + case NULL: + // do nothing + break; + default: + throw new RuntimeException("unhandled case: " + excludedPersonType); + } + partitionSamplerBuilder.setExcludedPerson(excludedPersonId); + + Set expectedPeopleMatchingPartitionSampler = new LinkedHashSet<>(expectedPeopleMatchingQueryLabelSet); + expectedPeopleMatchingPartitionSampler.remove(excludedPersonId); + + if (useWeightingFunction) { + Iterator iterator = expectedPeopleMatchingPartitionSampler.iterator(); + while (iterator.hasNext()) { + PersonId personId = iterator.next(); + Integer int_1_attributeValue = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_1); + Boolean passed = (Boolean) int_1_labelFunction.apply(int_1_attributeValue); + if (!passed) { + iterator.remove(); + } + } + + LabelSetWeightingFunction labelSetWeightingFunction = (c2, labelSet) -> { + Boolean value = (Boolean) labelSet.getLabel(TestAttributeId.INT_1).get(); + if (value) { + return 1; + } else { + return 0; + } + }; + partitionSamplerBuilder.setLabelSetWeightingFunction(labelSetWeightingFunction); + } + + PartitionSampler partitionSampler = partitionSamplerBuilder.build(); + + int samplingCount = FastMath.min(expectedPeopleMatchingQueryLabelSet.size() + 1, 10); + + for (int i = 0; i < samplingCount; i++) { + Optional optional = populationPartition.samplePartition(partitionSampler); + if (optional.isPresent()) { + PersonId selectedPerson = optional.get(); + assertTrue(expectedPeopleMatchingPartitionSampler.contains(selectedPerson)); + } else { + assertTrue(expectedPeopleMatchingPartitionSampler.isEmpty()); + } + } + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = PopulationPartitionImpl.class, name = "getPersonValue", args = { LabelSetFunction.class, PersonId.class }) + public void testGetPersonValue() { + RandomGenerator rng = RandomGeneratorProvider.getRandomGenerator(1889608169419896318L); + long seed = rng.nextLong(); + + String key = "key"; + + /* + * Define functions that will convert attribute values into labels for + * attributes INT_0, INT_1, DOUBLE_0, and DOUBLE_1. We will use these in + * the partition's labeling + */ + Function int_0_labelFunction = (value) -> { + int v = (Integer) value; + return v % 3; + }; + + Function int_1_labelFunction = (value) -> { + int v = (Integer) value; + if (v < 40) { + return true; + } + return false; + }; + + Function double_0_labelFunction = (value) -> { + double v = (Double) value; + if (v < 33) { + return "A"; + } + if (v < 67) { + return "B"; + } + return "C"; + }; + + Function double_1_labelFunction = (value) -> { + double v = (Double) value; + return v < 90; + }; + + TestPluginData.Builder testPluginDataBuilder = TestPluginData.builder(); + + /* + * Have the actor set the attribute values for each person to random + * values + */ + testPluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + // determine the people in the world + List peopleInTheWorld = peopleDataManager.getPeople(); + + // alter people's attributes randomly + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + for (PersonId personId : peopleInTheWorld) { + int intValue = (int) (randomGenerator.nextDouble() * 100); + attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_0, intValue); + + intValue = (int) (randomGenerator.nextDouble() * 100); + attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_1, intValue); + + double doubleValue = randomGenerator.nextDouble() * 100; + attributesDataManager.setAttributeValue(personId, TestAttributeId.DOUBLE_0, doubleValue); + + doubleValue = randomGenerator.nextDouble() * 100; + attributesDataManager.setAttributeValue(personId, TestAttributeId.DOUBLE_1, doubleValue); + + boolean booleanValue = randomGenerator.nextBoolean(); + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, booleanValue); + + booleanValue = randomGenerator.nextBoolean(); + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_1, booleanValue); + } + })); + + /* + * Have the actor add a partition under the key that has four labelers + * corresponding to the functions above and a simple filter based on + * TestAttributeId.BOOLEAN_0. + */ + testPluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); + + /* + * Create a partition that may filter about half of the population + * on BOOLEAN_0. We will partition on INT_0, INT_1, DOUBLE_0 and + * DOUBLE_1. Note that we do not use BOOLEAN_1 as part of the + * partition. + */ + Partition.Builder partitionBuilder = Partition.builder(); + + partitionBuilder.setFilter(new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true));// + + partitionBuilder// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.INT_0, int_0_labelFunction))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.INT_1, int_1_labelFunction))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.DOUBLE_0, double_0_labelFunction))// + .addLabeler(new FunctionalAttributeLabeler(TestAttributeId.DOUBLE_1, double_1_labelFunction)); + + Partition partition = partitionBuilder.build(); + partitionsDataManager.addPartition(partition, key); + })); + + // Have the actor get person values using a labeling function and show + // that the result of the function matches expectations. + testPluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + + // establish data views + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); + + for (PersonId personId : peopleDataManager.getPeople()) { + + + + LabelSetFunction f = (pc, labelset) -> { + Integer i = (Integer) labelset.getLabel(TestAttributeId.INT_0).get(); + Boolean b1 = (Boolean) labelset.getLabel(TestAttributeId.INT_1).get(); + String s = (String) labelset.getLabel(TestAttributeId.DOUBLE_0).get(); + Boolean b2 = (Boolean) labelset.getLabel(TestAttributeId.DOUBLE_1).get(); + int result = i; + if (b1) { + result += 20; + } + switch (s) { + case "A": + result *= 2; + break; + case "B": + result *= 3; + break; + default: + result *= 4; + break; + } + if (b2) { + result += 17; + } + return result; + }; + + + Optional optional = partitionsDataManager.getPersonValue(key, f, personId); + + // the person should be in the partition if and only if the + // optional is present + Boolean expectedInclusion = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); + assertEquals(expectedInclusion, optional.isPresent()); + + if (optional.isPresent()) { + // determine the expected value of the function + + //first, get the attribute values of the person + int i0 = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_0); + int i1 = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_1); + double d0 = attributesDataManager.getAttributeValue(personId, TestAttributeId.DOUBLE_0); + double d1 = attributesDataManager.getAttributeValue(personId, TestAttributeId.DOUBLE_1); + + //now determine what the labelers will do with those values + int i = (Integer)int_0_labelFunction.apply(i0); + boolean b1 = (Boolean)int_1_labelFunction.apply(i1); + String s = (String)double_0_labelFunction.apply(d0); + boolean b2 = (Boolean)double_1_labelFunction.apply(d1); + + //finally calculate what the label function will do with the label values + int expectedValue = i; + if (b1) { + expectedValue += 20; + } + switch (s) { + case "A": + expectedValue *= 2; + break; + case "B": + expectedValue *= 3; + break; + default: + expectedValue *= 4; + break; + } + if (b2) { + expectedValue += 17; + } + + // show the values are equal + assertEquals(expectedValue, optional.get().intValue()); + } + + } + })); + + TestPluginData testPluginData = testPluginDataBuilder.build(); + + Factory factory = PartitionsTestPluginFactory.factory(1000, seed, testPluginData); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/AT_Tuplator.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/AT_Tuplator.java new file mode 100644 index 000000000..e37c27fb0 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/AT_Tuplator.java @@ -0,0 +1,157 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.Tuplator.Builder; +import util.annotations.UnitTestMethod; +import util.random.RandomGeneratorProvider; + +public class AT_Tuplator { + + /** + * Tests {@link Tuplator#size()} + */ + @Test + @UnitTestMethod(target = Tuplator.class, name = "size", args = {}) + public void testSize() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7820715406750309229L); + for (int i = 0; i < 100; i++) { + Builder builder = Tuplator.builder(); + int dimensionCount = randomGenerator.nextInt(4) + 1; + int expectedSize = 1; + for (int j = 0; j < dimensionCount; j++) { + int dimSize = randomGenerator.nextInt(10) + 1; + expectedSize *= dimSize; + builder.addDimension(dimSize); + } + int actualSize = builder.build().size(); + assertEquals(expectedSize, actualSize); + } + } + + /** + * Tests {@link Tuplator#builder()} + */ + @Test + @UnitTestMethod(target = Tuplator.class, name = "builder", args = {}) + public void testBuilder() { + // covered by other tests + } + + /** + * Tests {@link Tuplator#dimensions()} + */ + @Test + @UnitTestMethod(target = Tuplator.class, name = "dimensions", args = {}) + public void testDimensions() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7661626069466374878L); + for (int i = 0; i < 100; i++) { + Builder builder = Tuplator.builder(); + int dimensionCount = randomGenerator.nextInt(4) + 1; + for (int j = 0; j < dimensionCount; j++) { + int dimSize = randomGenerator.nextInt(10) + 1; + builder.addDimension(dimSize); + } + int actualDimensionCount = builder.build().dimensions(); + assertEquals(dimensionCount, actualDimensionCount); + } + } + + /** + * Tests {@link Tuplator#fillTuple(int, int[])} + */ + @Test + @UnitTestMethod(target = Tuplator.class, name = "fillTuple", args = { int.class, int[].class }) + public void testFillTuple() { + + Tuplator tuplator = Tuplator.builder().addDimension(2).addDimension(3).addDimension(5).build(); + + int[] tuple = new int[tuplator.dimensions()]; + + List expectedArrays = new ArrayList<>(); + + expectedArrays.add(new int[] { 0, 0, 0 }); + expectedArrays.add(new int[] { 1, 0, 0 }); + expectedArrays.add(new int[] { 0, 1, 0 }); + expectedArrays.add(new int[] { 1, 1, 0 }); + expectedArrays.add(new int[] { 0, 2, 0 }); + expectedArrays.add(new int[] { 1, 2, 0 }); + expectedArrays.add(new int[] { 0, 0, 1 }); + expectedArrays.add(new int[] { 1, 0, 1 }); + expectedArrays.add(new int[] { 0, 1, 1 }); + expectedArrays.add(new int[] { 1, 1, 1 }); + expectedArrays.add(new int[] { 0, 2, 1 }); + expectedArrays.add(new int[] { 1, 2, 1 }); + expectedArrays.add(new int[] { 0, 0, 2 }); + expectedArrays.add(new int[] { 1, 0, 2 }); + expectedArrays.add(new int[] { 0, 1, 2 }); + expectedArrays.add(new int[] { 1, 1, 2 }); + expectedArrays.add(new int[] { 0, 2, 2 }); + expectedArrays.add(new int[] { 1, 2, 2 }); + expectedArrays.add(new int[] { 0, 0, 3 }); + expectedArrays.add(new int[] { 1, 0, 3 }); + expectedArrays.add(new int[] { 0, 1, 3 }); + expectedArrays.add(new int[] { 1, 1, 3 }); + expectedArrays.add(new int[] { 0, 2, 3 }); + expectedArrays.add(new int[] { 1, 2, 3 }); + expectedArrays.add(new int[] { 0, 0, 4 }); + expectedArrays.add(new int[] { 1, 0, 4 }); + expectedArrays.add(new int[] { 0, 1, 4 }); + expectedArrays.add(new int[] { 1, 1, 4 }); + expectedArrays.add(new int[] { 0, 2, 4 }); + expectedArrays.add(new int[] { 1, 2, 4 }); + + for (int i = 0; i < tuplator.size(); i++) { + tuplator.fillTuple(i, tuple); + assertTrue(Arrays.equals(expectedArrays.get(i), tuple)); + } + /** + * precondition tests + */ + assertThrows(IndexOutOfBoundsException.class, () -> tuplator.fillTuple(-2, tuple)); + assertThrows(IndexOutOfBoundsException.class, () -> tuplator.fillTuple(-1, tuple)); + assertThrows(IndexOutOfBoundsException.class, () -> tuplator.fillTuple(tuplator.size(), tuple)); + assertThrows(IndexOutOfBoundsException.class, () -> tuplator.fillTuple(tuplator.size() + 1, tuple)); + assertThrows(IllegalArgumentException.class, () -> tuplator.fillTuple(0, null)); + assertThrows(IllegalArgumentException.class, () -> tuplator.fillTuple(0, new int[tuplator.dimensions() - 1])); + assertThrows(IllegalArgumentException.class, () -> tuplator.fillTuple(0, new int[tuplator.dimensions() + 1])); + + } + + @Test + @UnitTestMethod(target = Tuplator.Builder.class, name = "build", args = {}) + public void testBuild() { + Tuplator.Builder builder = Tuplator.builder(); + Tuplator tuplator = builder.build(); + + assertNotNull(tuplator); + } + + @Test + @UnitTestMethod(target = Tuplator.Builder.class, name = "addDimension", args = { int.class }) + public void testAddDimension() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1967914502607382607L); + for (int i = 0; i < 100; i++) { + Builder builder = Tuplator.builder(); + int dimensionCount = randomGenerator.nextInt(4) + 1; + for (int j = 0; j < dimensionCount; j++) { + int dimSize = randomGenerator.nextInt(10) + 1; + builder.addDimension(dimSize); + } + int actualDimensionCount = builder.build().dimensions(); + assertEquals(dimensionCount, actualDimensionCount); + } + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/containers/AT_BasePeopleContainer.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/containers/AT_BasePeopleContainer.java new file mode 100644 index 000000000..6b38344bf --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/containers/AT_BasePeopleContainer.java @@ -0,0 +1,65 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support.containers; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_BasePeopleContainer { + + private PeopleContainer getPeopleContainer(PartitionsContext partitionsContext) { + return new BasePeopleContainer(partitionsContext,false); + } + + @Test + @UnitTestConstructor(target = BasePeopleContainer.class, args = { PartitionsContext.class, boolean.class}) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = BasePeopleContainer.class, name = "getPeople", args = {}) + public void testGetPeople() { + PeopleContainerTester.testGetPeople(this::getPeopleContainer, 473484353360778267L); + } + + @Test + @UnitTestMethod(target = BasePeopleContainer.class, name = "safeAdd", args = { PersonId.class }) + public void testSafeAdd() { + PeopleContainerTester.testSafeAdd(this::getPeopleContainer, 1067260810269284703L); + } + + @Test + @UnitTestMethod(target = BasePeopleContainer.class, name = "unsafeAdd", args = { PersonId.class }) + public void testUnsafeAdd() { + PeopleContainerTester.testUnsafeAdd(this::getPeopleContainer, 2640497434656632684L); + } + + @Test + @UnitTestMethod(target = BasePeopleContainer.class, name = "remove", args = { PersonId.class }) + public void testRemove() { + PeopleContainerTester.testRemove(this::getPeopleContainer, 1461315035567239819L); + } + + @Test + @UnitTestMethod(target = BasePeopleContainer.class, name = "size", args = {}) + public void testSize() { + PeopleContainerTester.testSize(this::getPeopleContainer, 5880341220076297803L); + } + + @Test + @UnitTestMethod(target = BasePeopleContainer.class, name = "contains", args = { PersonId.class }) + public void testContains() { + PeopleContainerTester.testContains(this::getPeopleContainer, 6865277196728541573L); + } + + @Test + @UnitTestMethod(target = BasePeopleContainer.class, name = "getRandomPersonId", args = { RandomGenerator.class }) + public void testGetRandomPersonId() { + PeopleContainerTester.testGetRandomPersonId(this::getPeopleContainer, 1976658500916036734L); + } + +} \ No newline at end of file diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/containers/AT_IntSetPeopleContainer.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/containers/AT_IntSetPeopleContainer.java new file mode 100644 index 000000000..3840edb93 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/containers/AT_IntSetPeopleContainer.java @@ -0,0 +1,75 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support.containers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestField; +import util.annotations.UnitTestMethod; + +public class AT_IntSetPeopleContainer { + + @Test + @UnitTestField(target = IntSetPeopleContainer.class,name = "DEFAULT_TARGET_DEPTH") + public void testDefaultTargetDepth() { + assertEquals(80, IntSetPeopleContainer.DEFAULT_TARGET_DEPTH); + } + + private PeopleContainer getPeopleContainer(PartitionsContext context) { + return new IntSetPeopleContainer(); + } + + @Test + @UnitTestConstructor(target = IntSetPeopleContainer.class, args = {}) + public void testConstructor() { + assertNotNull(new IntSetPeopleContainer()); + } + + @Test + @UnitTestMethod(target = IntSetPeopleContainer.class, name = "getPeople", args = {}) + public void testGetPeople() { + PeopleContainerTester.testGetPeople(this::getPeopleContainer, 2057487963804869808L); + } + + @Test + @UnitTestMethod(target = IntSetPeopleContainer.class, name = "safeAdd", args = { PersonId.class }) + public void testSafeAdd() { + PeopleContainerTester.testSafeAdd(this::getPeopleContainer, 8929313356256516845L); + } + + @Test + @UnitTestMethod(target = IntSetPeopleContainer.class, name = "unsafeAdd", args = { PersonId.class }) + public void testUnsafeAdd() { + PeopleContainerTester.testUnsafeAdd(this::getPeopleContainer, 7154495188896202332L); + } + + @Test + @UnitTestMethod(target = IntSetPeopleContainer.class, name = "remove", args = { PersonId.class }) + public void testRemove() { + PeopleContainerTester.testRemove(this::getPeopleContainer, 3751615599692356108L); + } + + @Test + @UnitTestMethod(target = IntSetPeopleContainer.class, name = "size", args = {}) + public void testSize() { + PeopleContainerTester.testSize(this::getPeopleContainer, 7891778848164062625L); + } + + @Test + @UnitTestMethod(target = IntSetPeopleContainer.class, name = "contains", args = { PersonId.class }) + public void testContains() { + PeopleContainerTester.testContains(this::getPeopleContainer, 604075687669743780L); + } + + @Test + @UnitTestMethod(target = IntSetPeopleContainer.class, name = "getRandomPersonId", args = { RandomGenerator.class }) + public void testGetRandomPersonId() { + PeopleContainerTester.testGetRandomPersonId(this::getPeopleContainer, 2124533986436224378L); + } + +} \ No newline at end of file diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/containers/AT_TreeBitSetPeopleContainer.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/containers/AT_TreeBitSetPeopleContainer.java new file mode 100644 index 000000000..1f22c14d2 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/containers/AT_TreeBitSetPeopleContainer.java @@ -0,0 +1,75 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support.containers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + + +public class AT_TreeBitSetPeopleContainer { + + + private PeopleContainer getPeopleContainer(PartitionsContext context) { + return new TreeBitSetPeopleContainer(context.getDataManager(PeopleDataManager.class)); + } + + @Test + @UnitTestConstructor(target = TreeBitSetPeopleContainer.class,args = {PeopleDataManager.class}) + public void testConstructor() { + ContractException contractException = assertThrows(ContractException.class,()->new TreeBitSetPeopleContainer(null)); + assertEquals(PartitionError.NULL_PERSON_DATA_VIEW, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = TreeBitSetPeopleContainer.class,name = "getPeople", args= {}) + public void testGetPeople(){ + PeopleContainerTester.testGetPeople(this::getPeopleContainer,647365023411331008L); + } + + @Test + @UnitTestMethod(target = TreeBitSetPeopleContainer.class,name = "safeAdd", args= {PersonId.class}) + public void testSafeAdd(){ + PeopleContainerTester.testSafeAdd(this::getPeopleContainer,5811617482064210099L); + } + + + @Test + @UnitTestMethod(target = TreeBitSetPeopleContainer.class,name = "unsafeAdd", args= {PersonId.class}) + public void testUnsafeAdd(){ + PeopleContainerTester.testUnsafeAdd(this::getPeopleContainer,3895584565521026498L); + } + + @Test + @UnitTestMethod(target = TreeBitSetPeopleContainer.class,name = "remove", args= {PersonId.class}) + public void testRemove(){ + PeopleContainerTester.testRemove(this::getPeopleContainer,6552795405656249759L); + } + + @Test + @UnitTestMethod(target = TreeBitSetPeopleContainer.class,name = "size", args= {}) + public void testSize(){ + PeopleContainerTester.testSize(this::getPeopleContainer,4996900201269028809L); + } + + @Test + @UnitTestMethod(target = TreeBitSetPeopleContainer.class,name = "contains", args= {PersonId.class}) + public void testContains(){ + PeopleContainerTester.testContains(this::getPeopleContainer,3310387654745518349L); + } + + @Test + @UnitTestMethod(target = TreeBitSetPeopleContainer.class,name = "getRandomPersonId", args= {RandomGenerator.class}) + public void testGetRandomPersonId(){ + PeopleContainerTester.testGetRandomPersonId(this::getPeopleContainer,4940840474171134819L); + } + +} \ No newline at end of file diff --git a/gcm3/src/test/java/plugins/partitions/support/containers/MT_ArrayIntSet.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/containers/MT_ArrayIntSet.java similarity index 98% rename from gcm3/src/test/java/plugins/partitions/support/containers/MT_ArrayIntSet.java rename to gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/containers/MT_ArrayIntSet.java index 205931370..4b4141e8a 100644 --- a/gcm3/src/test/java/plugins/partitions/support/containers/MT_ArrayIntSet.java +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/containers/MT_ArrayIntSet.java @@ -1,4 +1,4 @@ -package plugins.partitions.support.containers; +package gov.hhs.aspr.ms.gcm.plugins.partitions.support.containers; /* * Manual performance tuning test turned off until adoption of JOL or another memory assessment tool for post-8 java @@ -26,7 +26,6 @@ * 2,500,000 people. * * - * @author Shawn Hatch * */ public class MT_ArrayIntSet { @@ -220,7 +219,7 @@ public class MT_ArrayIntSet { // sb.append(intSetPerformance.memorySizeAtPeak); // sb.append("\t"); // sb.append(intSetPerformance.memorySizeAtEnd); -// System.out.println(sb); +// // } // // private MT_ArrayIntSet() { diff --git a/gcm3/src/test/java/plugins/partitions/support/containers/PeopleContainerTester.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/containers/PeopleContainerTester.java similarity index 78% rename from gcm3/src/test/java/plugins/partitions/support/containers/PeopleContainerTester.java rename to gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/containers/PeopleContainerTester.java index bc8141197..5443a2d50 100644 --- a/gcm3/src/test/java/plugins/partitions/support/containers/PeopleContainerTester.java +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/containers/PeopleContainerTester.java @@ -1,4 +1,4 @@ -package plugins.partitions.support.containers; +package gov.hhs.aspr.ms.gcm.plugins.partitions.support.containers; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -16,11 +16,14 @@ import org.apache.commons.math3.random.RandomGenerator; import org.apache.commons.math3.util.FastMath; -import nucleus.SimulationContext; -import plugins.partitions.testsupport.PartitionsActionSupport; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.PersonId; -import plugins.stochastics.StochasticsDataManager; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.PartitionsTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.PartitionsTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.TestPartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; /* * Static support class for testing PopulationContainer implementer classes @@ -30,11 +33,11 @@ public class PeopleContainerTester { - public static void testGetPeople(Function provider, long seed) { - PartitionsActionSupport.testConsumer(100, seed, (c) -> { - + public static void testGetPeople(Function provider, long seed) { + Factory factory = PartitionsTestPluginFactory.factory(100, seed, (c) -> { + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); // get the people container to test - PeopleContainer peopleContainer = provider.apply(c); + PeopleContainer peopleContainer = provider.apply(testPartitionsContext); // get some data views that will be needed below PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); @@ -79,14 +82,17 @@ public static void testGetPeople(Function pr assertEquals(new LinkedHashSet<>(expectedPeople), new LinkedHashSet<>(peopleList)); }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); } - public static void testSafeAdd(Function provider, long seed) { - PartitionsActionSupport.testConsumer(100, seed, (c) -> { + public static void testSafeAdd(Function provider, long seed) { + Factory factory = PartitionsTestPluginFactory.factory(100, seed, (c) -> { + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + // get the people container to test - PeopleContainer peopleContainer = provider.apply(c); + PeopleContainer peopleContainer = provider.apply(testPartitionsContext); // get some data views that will be needed below PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); @@ -123,13 +129,14 @@ public static void testSafeAdd(Function prov assertEquals(new LinkedHashSet<>(expectedPeople), new LinkedHashSet<>(peopleList)); }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); } - public static void testUnsafeAdd(Function provider, long seed) { - PartitionsActionSupport.testConsumer(100, seed, (c) -> { - + public static void testUnsafeAdd(Function provider, long seed) { + Factory factory = PartitionsTestPluginFactory.factory(100, seed, (c) -> { + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); // get the people container to test - PeopleContainer peopleContainer = provider.apply(c); + PeopleContainer peopleContainer = provider.apply(testPartitionsContext); // get some data views that will be needed below PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); @@ -160,13 +167,16 @@ public static void testUnsafeAdd(Function pr } }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); } - public static void testRemove(Function provider, long seed) { - PartitionsActionSupport.testConsumer(100, seed, (c) -> { + public static void testRemove(Function provider, long seed) { + Factory factory = PartitionsTestPluginFactory.factory(100, seed, (c) -> { + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + // get the people container to test - PeopleContainer peopleContainer = provider.apply(c); + PeopleContainer peopleContainer = provider.apply(testPartitionsContext); // get some data views that will be needed below PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); @@ -204,13 +214,14 @@ public static void testRemove(Function provi } }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); } - public static void testSize(Function provider, long seed) { - PartitionsActionSupport.testConsumer(100, seed, (c) -> { - + public static void testSize(Function provider, long seed) { + Factory factory = PartitionsTestPluginFactory.factory(100, seed, (c) -> { + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); // get the people container to test - PeopleContainer peopleContainer = provider.apply(c); + PeopleContainer peopleContainer = provider.apply(testPartitionsContext); // get some data views that will be needed below PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); @@ -247,13 +258,14 @@ public static void testSize(Function provide } }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); } - public static void testContains(Function provider, long seed) { - PartitionsActionSupport.testConsumer(100, seed, (c) -> { - + public static void testContains(Function provider, long seed) { + Factory factory = PartitionsTestPluginFactory.factory(100, seed, (c) -> { + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); // get the people container to test - PeopleContainer peopleContainer = provider.apply(c); + PeopleContainer peopleContainer = provider.apply(testPartitionsContext); // get some data views that will be needed below PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); @@ -295,13 +307,14 @@ public static void testContains(Function pro } }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); } - public static void testGetRandomPersonId(Function provider, long seed) { - PartitionsActionSupport.testConsumer(100, seed, (c) -> { - + public static void testGetRandomPersonId(Function provider, long seed) { + Factory factory = PartitionsTestPluginFactory.factory(100, seed, (c) -> { + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); // get the people container to test - PeopleContainer peopleContainer = provider.apply(c); + PeopleContainer peopleContainer = provider.apply(testPartitionsContext); // get some data views that will be needed below PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); @@ -360,5 +373,6 @@ public static void testGetRandomPersonId(Function FILTER_SENSITIVITY_2 = new FilterSensitivity<>(Type2Event.class, + (c, e) -> { + if (e.getPersonId().getValue() % 2 == 0) { + return Optional.of(e.getPersonId()); + } + return Optional.empty(); + }); + + private final static FilterSensitivity FILTER_SENSITIVITY_3 = new FilterSensitivity<>(Type3Event.class, + (c, e) -> { + if (e.getPersonId().getValue() % 2 == 0) { + return Optional.of(e.getPersonId()); + } + return Optional.empty(); + }); + + private final static FilterSensitivity FILTER_SENSITIVITY_5 = new FilterSensitivity<>(Type5Event.class, + (c, e) -> { + if (e.getPersonId().getValue() % 2 == 0) { + return Optional.of(e.getPersonId()); + } + return Optional.empty(); + }); + + private static abstract class Type0Event implements Event { + private final PersonId personid; + + public PersonId getPersonId() { + return personid; + } + + public Type0Event(PersonId personid) { + this.personid = personid; + } + } + + private final static class Type2Event extends Type0Event { + + public Type2Event(PersonId personid) { + super(personid); + } + } + + private final static class Type3Event extends Type0Event { + + public Type3Event(PersonId personid) { + super(personid); + } + } + + private final static class Type5Event extends Type0Event { + + public Type5Event(PersonId personid) { + super(personid); + } + } + + @Test + @UnitTestConstructor(target = AndFilter.class, args = { Filter.class, Filter.class }) + public void testAndFilter() { + + // precondition test: if either child filter is null + ContractException contractException = assertThrows(ContractException.class, () -> { + new AndFilter(new TrueFilter(), null); + }); + assertEquals(PartitionError.NULL_FILTER, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, () -> { + new AndFilter(null, new TrueFilter()); + }); + assertEquals(PartitionError.NULL_FILTER, contractException.getErrorType()); + + } + + private AndFilter getRandomAndFilter(long seed) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + Filter a = new TestFilter(randomGenerator.nextInt()); + Filter b = new TestFilter(randomGenerator.nextInt()); + return new AndFilter(a, b); + } + + @Test + @UnitTestMethod(target = AndFilter.class, name = "equals", args = { Object.class }) + public void testEquals() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5725831217415880484L); + + // is never equal to null + for (int i = 0; i < 30; i++) { + assertFalse(getRandomAndFilter(randomGenerator.nextLong()).equals(null)); + } + + // reflexive + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + AndFilter f = getRandomAndFilter(seed); + assertTrue(f.equals(f)); + } + + // symmetric, transitive, consistent + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + AndFilter f1 = getRandomAndFilter(seed); + AndFilter f2 = getRandomAndFilter(seed); + for (int j = 0; j < 10; j++) { + assertTrue(f1.equals(f2)); + assertTrue(f2.equals(f1)); + } + } + + AndFilter f1 = new AndFilter(new TestFilter(3), new TestFilter(5)); + AndFilter f2 = new AndFilter(new TestFilter(5), new TestFilter(3)); + AndFilter f3 = new AndFilter(new TestFilter(3), new TestFilter(4)); + + // return true when arguments are equal in some order + assertEquals(f1, f2); + + // returns false when filter inputs are different in any order + assertNotEquals(f1, f3); + assertNotEquals(f2, f3); + + } + + private final static class TestFilter extends Filter { + private final int index; + + public TestFilter(int index) { + this.index = index; + } + + @Override + public boolean evaluate(PartitionsContext partitionsContext, PersonId personId) { + return index % 2 == 0; + } + + @Override + public void validate(PartitionsContext partitionsContext) { + if (index < 0) { + throw new RuntimeException(); + } + } + + @Override + public Set> getFilterSensitivities() { + Set> result = new LinkedHashSet<>(); + + if (index % 2 == 0) { + result.add(FILTER_SENSITIVITY_2); + } + if (index % 3 == 0) { + result.add(FILTER_SENSITIVITY_3); + } + if (index % 5 == 0) { + result.add(FILTER_SENSITIVITY_5); + } + + return result; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + index; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof TestFilter)) { + return false; + } + TestFilter other = (TestFilter) obj; + if (index != other.index) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("TestFilter [index="); + builder.append(index); + builder.append("]"); + return builder.toString(); + } + + } + + @Test + @UnitTestMethod(target = AndFilter.class, name = "hashCode", args = {}) + public void testHashCode() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(9151412325951204846L); + + // equal objects have equal hash codes, note that argument order does not matter + for (int i = 0; i < 30; i++) { + int seed1 = randomGenerator.nextInt(); + int seed2 = randomGenerator.nextInt(); + AndFilter f12 = new AndFilter(new TestFilter(seed1), new TestFilter(seed2)); + AndFilter f21 = new AndFilter(new TestFilter(seed2), new TestFilter(seed1)); + assertEquals(f12, f21); + assertEquals(f12.hashCode(), f21.hashCode()); + } + + // hash codes are reasonably distributed + Set hashCodes = new LinkedHashSet<>(); + + for (int i = 0; i < 100; i++) { + TestFilter f1 = new TestFilter(randomGenerator.nextInt()); + TestFilter f2 = new TestFilter(randomGenerator.nextInt()); + AndFilter andFilter = new AndFilter(f1, f2); + hashCodes.add(andFilter.hashCode()); + } + + assertTrue(hashCodes.size() > 95); + + } + + private AndFilter getAndFilter(int a, int b) { + return new AndFilter(new TestFilter(a), new TestFilter(b)); + } + + @Test + @UnitTestMethod(target = AndFilter.class, name = "evaluate", args = { PartitionsContext.class, PersonId.class }) + public void testEvaluate() { + PartitionsContext partitionsContext = null; + PersonId personId = new PersonId(56); + + // using AND over TestFilters, evaluate should return true if and only if both + // arguments are even + assertFalse(getAndFilter(20, 5).evaluate(partitionsContext, personId)); + assertTrue(getAndFilter(20, 10).evaluate(partitionsContext, personId)); + assertFalse(getAndFilter(3, 2).evaluate(partitionsContext, personId)); + assertFalse(getAndFilter(3, 5).evaluate(partitionsContext, personId)); + assertTrue(getAndFilter(0, 4).evaluate(partitionsContext, personId)); + } + + @Test + @UnitTestMethod(target = AndFilter.class, name = "getFilterSensitivities", args = {}) + public void testGetFilterSensitivities() { + // Filter sensitivities for the TestFilter class are based on index modulo 2, 3 + // and 5 + + for (int i = 0; i < 30; i++) { + TestFilter testFilter1 = new TestFilter(i); + for (int j = 0; j < 30; j++) { + TestFilter testFilter2 = new TestFilter(j); + AndFilter andFilter = new AndFilter(testFilter1, testFilter2); + Set> actualFilterSensitivities = andFilter.getFilterSensitivities(); + + Set> expectedFilterSensitivities = new LinkedHashSet<>(); + if (i % 2 == 0 || j % 2 == 0) { + expectedFilterSensitivities.add(FILTER_SENSITIVITY_2); + } + if (i % 3 == 0 || j % 3 == 0) { + expectedFilterSensitivities.add(FILTER_SENSITIVITY_3); + } + if (i % 5 == 0 || j % 5 == 0) { + expectedFilterSensitivities.add(FILTER_SENSITIVITY_5); + } + assertEquals(expectedFilterSensitivities, actualFilterSensitivities); + } + } + } + + @Test + @UnitTestMethod(target = AndFilter.class, name = "getFirstFilter", args = {}) + public void testGetFirstFilter() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7492542318245868773L); + + for (int i = 0; i < 30; i++) { + TestFilter testFilter1 = new TestFilter(randomGenerator.nextInt()); + TestFilter testFilter2 = new TestFilter(randomGenerator.nextInt()); + + AndFilter andFilter = new AndFilter(testFilter1, testFilter2); + assertEquals(testFilter1, andFilter.getFirstFilter()); + } + } + + @Test + @UnitTestMethod(target = AndFilter.class, name = "getSecondFilter", args = {}) + public void testGetSecondFilter() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(710120811220020778L); + + for (int i = 0; i < 30; i++) { + TestFilter testFilter1 = new TestFilter(randomGenerator.nextInt()); + TestFilter testFilter2 = new TestFilter(randomGenerator.nextInt()); + + AndFilter andFilter = new AndFilter(testFilter1, testFilter2); + assertEquals(testFilter2, andFilter.getSecondFilter()); + } + + } + + @Test + @UnitTestMethod(target = AndFilter.class, name = "toString", args = {}) + public void testToString() { + TestFilter testFilter1 = new TestFilter(32); + TestFilter testFilter2 = new TestFilter(17); + AndFilter andFilter = new AndFilter(testFilter1, testFilter2); + String actualValue = andFilter.toString(); + String expectedValue = "AndFilter [a=TestFilter [index=32], b=TestFilter [index=17]]"; + assertEquals(expectedValue, actualValue); + } + + @Test + @UnitTestMethod(target = AndFilter.class, name = "validate", args = { PartitionsContext.class }) + public void testValidate() { + PartitionsContext partitionsContext = null; + + /* + * TestFilters throw an exception when validate is invoked when their index is + * negative. We show here that the AndFilter is invoking the validate for both + * of its child filters. + * + */ + + assertDoesNotThrow(() -> { + AndFilter andFilter = new AndFilter(new TestFilter(0), new TestFilter(3)); + andFilter.validate(partitionsContext); + }); + + assertThrows(RuntimeException.class, () -> { + AndFilter andFilter = new AndFilter(new TestFilter(-1), new TestFilter(3)); + andFilter.validate(partitionsContext); + }); + + assertThrows(RuntimeException.class, () -> { + AndFilter andFilter = new AndFilter(new TestFilter(0), new TestFilter(-1)); + andFilter.validate(partitionsContext); + }); + + assertThrows(RuntimeException.class, () -> { + AndFilter andFilter = new AndFilter(new TestFilter(-1), new TestFilter(-1)); + andFilter.validate(partitionsContext); + }); + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/filters/AT_FalseFilter.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/filters/AT_FalseFilter.java new file mode 100644 index 000000000..11102cdae --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/filters/AT_FalseFilter.java @@ -0,0 +1,96 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_FalseFilter { + + @Test + @UnitTestConstructor(target = FalseFilter.class, args = {}) + public void testTrueFilter() { + // nothing to test + } + + @Test + @UnitTestMethod(target = FalseFilter.class, name = "equals", args = { Object.class }) + public void testEquals() { + FalseFilter falseFilter = new FalseFilter(); + + // is never equal to null + assertFalse(falseFilter.equals(null)); + + // reflexive + assertTrue(falseFilter.equals(falseFilter)); + + // symmetric, transitive, consistent + for (int i = 0; i < 30; i++) { + FalseFilter f1 = new FalseFilter(); + FalseFilter f2 = new FalseFilter(); + for (int j = 0; j < 10; j++) { + assertTrue(f1.equals(f2)); + assertTrue(f2.equals(f1)); + } + } + } + + @Test + @UnitTestMethod(target = FalseFilter.class, name = "hashCode", args = {}) + public void testHashCode() { + assertEquals(0, new FalseFilter().hashCode()); + } + + @Test + @UnitTestMethod(target = FalseFilter.class, name = "evaluate", args = { PartitionsContext.class, PersonId.class }) + public void testEvaluate() { + PartitionsContext partitionsContext = null; + PersonId personId = new PersonId(56); + assertFalse(new FalseFilter().evaluate(partitionsContext, personId)); + } + + @Test + @UnitTestMethod(target = FalseFilter.class, name = "getFilterSensitivities", args = {}) + public void testGetFilterSensitivities() { + FalseFilter falseFilter = new FalseFilter(); + assertTrue(falseFilter.getFilterSensitivities().isEmpty()); + } + + @Test + @UnitTestMethod(target = FalseFilter.class, name = "toString", args = {}) + public void testToString() { + FalseFilter falseFilter = new FalseFilter(); + String actualValue = falseFilter.toString(); + String expectedValue = "FalseFilter []"; + assertEquals(expectedValue, actualValue); + } + + @Test + @UnitTestMethod(target = FalseFilter.class, name = "validate", args = { PartitionsContext.class }) + public void testValidate() { + PartitionsContext partitionsContext = null; + + /* + * TestFilters throw an exception when validate is invoked when their index is + * negative. We show here that the FalseFilter is invoking the validate for both + * of its child filters. + * + */ + + assertDoesNotThrow(() -> { + FalseFilter falseFilter = new FalseFilter(); + falseFilter.validate(partitionsContext); + }); + + + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/filters/AT_Filter.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/filters/AT_Filter.java new file mode 100644 index 000000000..2e80ff91b --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/filters/AT_Filter.java @@ -0,0 +1,247 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.FilterSensitivity; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.PartitionsTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.PartitionsTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.TestPartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + +public class AT_Filter { + + private static class LocalFilter extends Filter { + + private final Set> filterSensitivities = new LinkedHashSet<>(); + + @SafeVarargs + public LocalFilter(FilterSensitivity... filterSensitivities) { + for (FilterSensitivity filterSensitivity : filterSensitivities) { + this.filterSensitivities.add(filterSensitivity); + } + } + + @Override + public boolean evaluate(PartitionsContext partitionsContext, PersonId personId) { + return false; + } + + @Override + public Set> getFilterSensitivities() { + return new LinkedHashSet<>(filterSensitivities); + } + + @Override + public void validate(PartitionsContext partitionsContext) { + // do nothing + + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((filterSensitivities == null) ? 0 : filterSensitivities.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof LocalFilter)) { + return false; + } + LocalFilter other = (LocalFilter) obj; + if (filterSensitivities == null) { + if (other.filterSensitivities != null) { + return false; + } + } else if (!filterSensitivities.equals(other.filterSensitivities)) { + return false; + } + return true; + } + + @Override + public String toString() { + return "LocalFilter[]"; + } + } + + private static Optional eventPredicate(PartitionsContext partitionsContext, Event event) { + return Optional.of(new PersonId(4)); + } + + /** + * Tests {@link Filter#and(Filter)} + */ + @Test + @UnitTestMethod(target = Filter.class, name = "and", args = { Filter.class }) + public void testAnd() { + Factory factory = PartitionsTestPluginFactory.factory(100, 254308828477050611L, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + /* + * Show that there are enough people in the simulation to make a + * valid test + */ + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + assertEquals(100, peopleDataManager.getPopulationCount()); + + // create the filters + Filter filter = new TrueFilter().and(new TrueFilter()); + for (PersonId personId : peopleDataManager.getPeople()) { + assertTrue(filter.evaluate(testPartitionsContext, personId)); + } + + filter = new TrueFilter().and(new FalseFilter()); + for (PersonId personId : peopleDataManager.getPeople()) { + assertFalse(filter.evaluate(testPartitionsContext, personId)); + } + + filter = new FalseFilter().and(new TrueFilter()); + for (PersonId personId : peopleDataManager.getPeople()) { + assertFalse(filter.evaluate(testPartitionsContext, personId)); + } + + filter = new FalseFilter().and(new FalseFilter()); + for (PersonId personId : peopleDataManager.getPeople()) { + assertFalse(filter.evaluate(testPartitionsContext, personId)); + } + + FilterSensitivity fsA = new FilterSensitivity<>(Event.class, AT_Filter::eventPredicate); + FilterSensitivity fsB = new FilterSensitivity<>(Event.class, AT_Filter::eventPredicate); + FilterSensitivity fsC = new FilterSensitivity<>(Event.class, AT_Filter::eventPredicate); + FilterSensitivity fsD = new FilterSensitivity<>(Event.class, AT_Filter::eventPredicate); + filter = new LocalFilter(fsA, fsB, fsC).and(new LocalFilter(fsA, fsD)); + + Set> expectedFilterSensitivities = new LinkedHashSet<>(); + expectedFilterSensitivities.add(fsA); + expectedFilterSensitivities.add(fsB); + expectedFilterSensitivities.add(fsC); + expectedFilterSensitivities.add(fsD); + Set> actualFilterSensitivities = filter.getFilterSensitivities(); + assertEquals(expectedFilterSensitivities, actualFilterSensitivities); + + // precondition tests + + // if the filter is null + ContractException contractException = assertThrows(ContractException.class, () -> new TrueFilter().and(null)); + assertEquals(PartitionError.NULL_FILTER, contractException.getErrorType()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + /** + * Tests {@link Filter#or(Filter)} + */ + @Test + @UnitTestMethod(target = Filter.class, name = "or", args = { Filter.class }) + public void testOr() { + Factory factory = PartitionsTestPluginFactory.factory(100, 921279696119043098L, (c) -> { + + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + /* + * Show that there are enough people in the simulation to make a + * valid test + */ + assertEquals(100, peopleDataManager.getPopulationCount()); + + // create the filters + Filter filter = new TrueFilter().or(new TrueFilter()); + for (PersonId personId : peopleDataManager.getPeople()) { + assertTrue(filter.evaluate(testPartitionsContext, personId)); + } + + filter = new TrueFilter().or(new FalseFilter()); + for (PersonId personId : peopleDataManager.getPeople()) { + assertTrue(filter.evaluate(testPartitionsContext, personId)); + } + + filter = new FalseFilter().or(new TrueFilter()); + for (PersonId personId : peopleDataManager.getPeople()) { + assertTrue(filter.evaluate(testPartitionsContext, personId)); + } + + filter = new FalseFilter().or(new FalseFilter()); + for (PersonId personId : peopleDataManager.getPeople()) { + assertFalse(filter.evaluate(testPartitionsContext, personId)); + } + + FilterSensitivity fsA = new FilterSensitivity<>(Event.class, AT_Filter::eventPredicate); + FilterSensitivity fsB = new FilterSensitivity<>(Event.class, AT_Filter::eventPredicate); + FilterSensitivity fsC = new FilterSensitivity<>(Event.class, AT_Filter::eventPredicate); + FilterSensitivity fsD = new FilterSensitivity<>(Event.class, AT_Filter::eventPredicate); + filter = new LocalFilter(fsA, fsB, fsC).or(new LocalFilter(fsA, fsD)); + + Set> expectedFilterSensitivities = new LinkedHashSet<>(); + expectedFilterSensitivities.add(fsA); + expectedFilterSensitivities.add(fsB); + expectedFilterSensitivities.add(fsC); + expectedFilterSensitivities.add(fsD); + Set> actualFilterSensitivities = filter.getFilterSensitivities(); + assertEquals(expectedFilterSensitivities, actualFilterSensitivities); + + // precondition test + + // if the filter is null + + ContractException contractException = assertThrows(ContractException.class, () -> new TrueFilter().or(null)); + assertEquals(PartitionError.NULL_FILTER, contractException.getErrorType()); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + /** + * Tests {@link Filter#not()} + */ + @Test + @UnitTestMethod(target = Filter.class, name = "not", args = {}) + public void testNot() { + Factory factory = PartitionsTestPluginFactory.factory(100, 4038710674336002107L, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + /* + * Show that there are enough people in the simulation to make a + * valid test + */ + assertEquals(100, peopleDataManager.getPopulationCount()); + + Filter filter = new TrueFilter().not(); + + for (PersonId personId : peopleDataManager.getPeople()) { + assertFalse(filter.evaluate(testPartitionsContext, personId)); + } + + filter = new FalseFilter().not(); + for (PersonId personId : peopleDataManager.getPeople()) { + assertTrue(filter.evaluate(testPartitionsContext, personId)); + } + + assertEquals(filter.getFilterSensitivities().size(), 0); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/filters/AT_NotFilter.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/filters/AT_NotFilter.java new file mode 100644 index 000000000..2a9cf2bfd --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/filters/AT_NotFilter.java @@ -0,0 +1,321 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.FilterSensitivity; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_NotFilter { + + private final static FilterSensitivity FILTER_SENSITIVITY_2 = new FilterSensitivity<>(Type2Event.class, + (c, e) -> { + if (e.getPersonId().getValue() % 2 == 0) { + return Optional.of(e.getPersonId()); + } + return Optional.empty(); + }); + + private final static FilterSensitivity FILTER_SENSITIVITY_3 = new FilterSensitivity<>(Type3Event.class, + (c, e) -> { + if (e.getPersonId().getValue() % 2 == 0) { + return Optional.of(e.getPersonId()); + } + return Optional.empty(); + }); + + private final static FilterSensitivity FILTER_SENSITIVITY_5 = new FilterSensitivity<>(Type5Event.class, + (c, e) -> { + if (e.getPersonId().getValue() % 2 == 0) { + return Optional.of(e.getPersonId()); + } + return Optional.empty(); + }); + + private static abstract class Type0Event implements Event { + private final PersonId personid; + + public PersonId getPersonId() { + return personid; + } + + public Type0Event(PersonId personid) { + this.personid = personid; + } + } + + private final static class Type2Event extends Type0Event { + + public Type2Event(PersonId personid) { + super(personid); + } + } + + private final static class Type3Event extends Type0Event { + + public Type3Event(PersonId personid) { + super(personid); + } + } + + private final static class Type5Event extends Type0Event { + + public Type5Event(PersonId personid) { + super(personid); + } + } + + @Test + @UnitTestConstructor(target = NotFilter.class, args = { Filter.class}) + public void testNotFilter() { + + // precondition test: if the child filter is null + ContractException contractException = assertThrows(ContractException.class, () -> { + new NotFilter(null); + }); + assertEquals(PartitionError.NULL_FILTER, contractException.getErrorType()); + } + + private NotFilter getRandomNotFilter(long seed) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + Filter a = new TestFilter(randomGenerator.nextInt()); + return new NotFilter(a); + } + + @Test + @UnitTestMethod(target = NotFilter.class, name = "equals", args = { Object.class }) + public void testEquals() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5725831217415880484L); + + // is never equal to null + for (int i = 0; i < 30; i++) { + assertFalse(getRandomNotFilter(randomGenerator.nextLong()).equals(null)); + } + + // reflexive + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + NotFilter f = getRandomNotFilter(seed); + assertTrue(f.equals(f)); + } + + // symmetric, transitive, consistent + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + NotFilter f1 = getRandomNotFilter(seed); + NotFilter f2 = getRandomNotFilter(seed); + for (int j = 0; j < 10; j++) { + assertTrue(f1.equals(f2)); + assertTrue(f2.equals(f1)); + } + } + + } + + private final static class TestFilter extends Filter { + private final int index; + + public TestFilter(int index) { + this.index = index; + } + + @Override + public boolean evaluate(PartitionsContext partitionsContext, PersonId personId) { + return index % 2 == 0; + } + + @Override + public void validate(PartitionsContext partitionsContext) { + if (index < 0) { + throw new RuntimeException(); + } + } + + @Override + public Set> getFilterSensitivities() { + Set> result = new LinkedHashSet<>(); + + if (index % 2 == 0) { + result.add(FILTER_SENSITIVITY_2); + } + if (index % 3 == 0) { + result.add(FILTER_SENSITIVITY_3); + } + if (index % 5 == 0) { + result.add(FILTER_SENSITIVITY_5); + } + + return result; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + index; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof TestFilter)) { + return false; + } + TestFilter other = (TestFilter) obj; + if (index != other.index) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("TestFilter [index="); + builder.append(index); + builder.append("]"); + return builder.toString(); + } + + } + + @Test + @UnitTestMethod(target = NotFilter.class, name = "hashCode", args = {}) + public void testHashCode() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(9151412325951204846L); + + // equal objects have equal hash codes, note that argument order does not matter + for (int i = 0; i < 30; i++) { + int seed = randomGenerator.nextInt(); + NotFilter f1 = new NotFilter(new TestFilter(seed)); + NotFilter f2 = new NotFilter(new TestFilter(seed)); + assertEquals(f1, f2); + assertEquals(f1.hashCode(), f2.hashCode()); + } + + // hash codes are reasonably distributed + Set hashCodes = new LinkedHashSet<>(); + + for (int i = 0; i < 100; i++) { + TestFilter f = new TestFilter(randomGenerator.nextInt()); + NotFilter notFilter = new NotFilter(f); + hashCodes.add(notFilter.hashCode()); + } + + assertTrue(hashCodes.size() > 95); + + } + + private NotFilter getNotFilter(int a) { + return new NotFilter(new TestFilter(a)); + } + + @Test + @UnitTestMethod(target = NotFilter.class, name = "evaluate", args = { PartitionsContext.class, PersonId.class }) + public void testEvaluate() { + PartitionsContext partitionsContext = null; + PersonId personId = new PersonId(56); + + // using NOT over TestFilters, evaluate should return the opposite of the test + // filter + assertFalse(getNotFilter(20).evaluate(partitionsContext, personId)); + assertTrue(getNotFilter(17).evaluate(partitionsContext, personId)); + assertTrue(getNotFilter(3).evaluate(partitionsContext, personId)); + assertFalse(getNotFilter(8).evaluate(partitionsContext, personId)); + assertFalse(getNotFilter(0).evaluate(partitionsContext, personId)); + } + + @Test + @UnitTestMethod(target = NotFilter.class, name = "getFilterSensitivities", args = {}) + public void testGetFilterSensitivities() { + // Filter sensitivities for the TestFilter class are based on index modulo 2, 3 + // and 5 + + for (int i = 0; i < 30; i++) { + TestFilter testFilter = new TestFilter(i); + NotFilter notFilter = new NotFilter(testFilter); + Set> actualFilterSensitivities = notFilter.getFilterSensitivities(); + + Set> expectedFilterSensitivities = new LinkedHashSet<>(); + if (i % 2 == 0) { + expectedFilterSensitivities.add(FILTER_SENSITIVITY_2); + } + if (i % 3 == 0) { + expectedFilterSensitivities.add(FILTER_SENSITIVITY_3); + } + if (i % 5 == 0) { + expectedFilterSensitivities.add(FILTER_SENSITIVITY_5); + } + assertEquals(expectedFilterSensitivities, actualFilterSensitivities); + + } + } + + @Test + @UnitTestMethod(target = NotFilter.class, name = "getSubFilter", args = {}) + public void testGetSubFilter() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7492542318245868773L); + + for (int i = 0; i < 30; i++) { + TestFilter testFilter = new TestFilter(randomGenerator.nextInt()); + NotFilter notFilter = new NotFilter(testFilter); + assertEquals(testFilter, notFilter.getSubFilter()); + } + } + + @Test + @UnitTestMethod(target = NotFilter.class, name = "toString", args = {}) + public void testToString() { + TestFilter testFilter = new TestFilter(32); + NotFilter notFilter = new NotFilter(testFilter); + String actualValue = notFilter.toString(); + String expectedValue = "NotFilter [a=TestFilter [index=32]]"; + assertEquals(expectedValue, actualValue); + } + + @Test + @UnitTestMethod(target = NotFilter.class, name = "validate", args = { PartitionsContext.class }) + public void testValidate() { + PartitionsContext partitionsContext = null; + + /* + * TestFilters throw an exception when validate is invoked when their index is + * negative. We show here that the NotFilter is invoking the validate for its + * child filter. + * + */ + + assertDoesNotThrow(() -> { + NotFilter notFilter = new NotFilter(new TestFilter(0)); + notFilter.validate(partitionsContext); + }); + + assertThrows(RuntimeException.class, () -> { + NotFilter notFilter = new NotFilter(new TestFilter(-1)); + notFilter.validate(partitionsContext); + }); + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/filters/AT_OrFilter.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/filters/AT_OrFilter.java new file mode 100644 index 000000000..9fab290be --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/filters/AT_OrFilter.java @@ -0,0 +1,372 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.FilterSensitivity; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_OrFilter { + + private final static FilterSensitivity FILTER_SENSITIVITY_2 = new FilterSensitivity<>(Type2Event.class, + (c, e) -> { + if (e.getPersonId().getValue() % 2 == 0) { + return Optional.of(e.getPersonId()); + } + return Optional.empty(); + }); + + private final static FilterSensitivity FILTER_SENSITIVITY_3 = new FilterSensitivity<>(Type3Event.class, + (c, e) -> { + if (e.getPersonId().getValue() % 2 == 0) { + return Optional.of(e.getPersonId()); + } + return Optional.empty(); + }); + + private final static FilterSensitivity FILTER_SENSITIVITY_5 = new FilterSensitivity<>(Type5Event.class, + (c, e) -> { + if (e.getPersonId().getValue() % 2 == 0) { + return Optional.of(e.getPersonId()); + } + return Optional.empty(); + }); + + private static abstract class Type0Event implements Event { + private final PersonId personid; + + public PersonId getPersonId() { + return personid; + } + + public Type0Event(PersonId personid) { + this.personid = personid; + } + } + + private final static class Type2Event extends Type0Event { + + public Type2Event(PersonId personid) { + super(personid); + } + } + + private final static class Type3Event extends Type0Event { + + public Type3Event(PersonId personid) { + super(personid); + } + } + + private final static class Type5Event extends Type0Event { + + public Type5Event(PersonId personid) { + super(personid); + } + } + + @Test + @UnitTestConstructor(target = OrFilter.class, args = { Filter.class, Filter.class }) + public void testOrFilter() { + + // precondition test: if either child filter is null + ContractException contractException = assertThrows(ContractException.class, () -> { + new OrFilter(new TrueFilter(), null); + }); + assertEquals(PartitionError.NULL_FILTER, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, () -> { + new OrFilter(null, new TrueFilter()); + }); + assertEquals(PartitionError.NULL_FILTER, contractException.getErrorType()); + + } + + private OrFilter getRandomAndFilter(long seed) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + Filter a = new TestFilter(randomGenerator.nextInt()); + Filter b = new TestFilter(randomGenerator.nextInt()); + return new OrFilter(a, b); + } + + @Test + @UnitTestMethod(target = OrFilter.class, name = "equals", args = { Object.class }) + public void testEquals() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5725831217415880484L); + + // is never equal to null + for (int i = 0; i < 30; i++) { + assertFalse(getRandomAndFilter(randomGenerator.nextLong()).equals(null)); + } + + // reflexive + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + OrFilter f = getRandomAndFilter(seed); + assertTrue(f.equals(f)); + } + + // symmetric, transitive, consistent + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + OrFilter f1 = getRandomAndFilter(seed); + OrFilter f2 = getRandomAndFilter(seed); + for (int j = 0; j < 10; j++) { + assertTrue(f1.equals(f2)); + assertTrue(f2.equals(f1)); + } + } + + OrFilter f1 = new OrFilter(new TestFilter(3), new TestFilter(5)); + OrFilter f2 = new OrFilter(new TestFilter(5), new TestFilter(3)); + OrFilter f3 = new OrFilter(new TestFilter(3), new TestFilter(4)); + + // return true when arguments are equal in some order + assertEquals(f1, f2); + + // returns false when filter inputs are different in any order + assertNotEquals(f1, f3); + assertNotEquals(f2, f3); + + } + + private final static class TestFilter extends Filter { + private final int index; + + public TestFilter(int index) { + this.index = index; + } + + @Override + public boolean evaluate(PartitionsContext partitionsContext, PersonId personId) { + return index % 2 == 0; + } + + @Override + public void validate(PartitionsContext partitionsContext) { + if (index < 0) { + throw new RuntimeException(); + } + } + + @Override + public Set> getFilterSensitivities() { + Set> result = new LinkedHashSet<>(); + + if (index % 2 == 0) { + result.add(FILTER_SENSITIVITY_2); + } + if (index % 3 == 0) { + result.add(FILTER_SENSITIVITY_3); + } + if (index % 5 == 0) { + result.add(FILTER_SENSITIVITY_5); + } + + return result; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + index; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof TestFilter)) { + return false; + } + TestFilter other = (TestFilter) obj; + if (index != other.index) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("TestFilter [index="); + builder.append(index); + builder.append("]"); + return builder.toString(); + } + + } + + @Test + @UnitTestMethod(target = OrFilter.class, name = "hashCode", args = {}) + public void testHashCode() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(9151412325951204846L); + + // equal objects have equal hash codes, note that argument order does not matter + for (int i = 0; i < 30; i++) { + int seed1 = randomGenerator.nextInt(); + int seed2 = randomGenerator.nextInt(); + OrFilter f12 = new OrFilter(new TestFilter(seed1), new TestFilter(seed2)); + OrFilter f21 = new OrFilter(new TestFilter(seed2), new TestFilter(seed1)); + assertEquals(f12, f21); + assertEquals(f12.hashCode(), f21.hashCode()); + } + + // hash codes are reasonably distributed + Set hashCodes = new LinkedHashSet<>(); + + for (int i = 0; i < 100; i++) { + TestFilter f1 = new TestFilter(randomGenerator.nextInt()); + TestFilter f2 = new TestFilter(randomGenerator.nextInt()); + OrFilter orFilter = new OrFilter(f1, f2); + hashCodes.add(orFilter.hashCode()); + } + + assertTrue(hashCodes.size() > 95); + + } + + private OrFilter getAndFilter(int a, int b) { + return new OrFilter(new TestFilter(a), new TestFilter(b)); + } + + @Test + @UnitTestMethod(target = OrFilter.class, name = "evaluate", args = { PartitionsContext.class, PersonId.class }) + public void testEvaluate() { + PartitionsContext partitionsContext = null; + PersonId personId = new PersonId(56); + + // using OR over TestFilters, evaluate should return true if either + // argument is even + assertTrue(getAndFilter(20, 5).evaluate(partitionsContext, personId)); + assertTrue(getAndFilter(20, 10).evaluate(partitionsContext, personId)); + assertTrue(getAndFilter(3, 2).evaluate(partitionsContext, personId)); + assertFalse(getAndFilter(3, 5).evaluate(partitionsContext, personId)); + assertFalse(getAndFilter(7, 11).evaluate(partitionsContext, personId)); + } + + @Test + @UnitTestMethod(target = OrFilter.class, name = "getFilterSensitivities", args = {}) + public void testGetFilterSensitivities() { + // Filter sensitivities for the TestFilter class are based on index modulo 2, 3 + // and 5 + + for (int i = 0; i < 30; i++) { + TestFilter testFilter1 = new TestFilter(i); + for (int j = 0; j < 30; j++) { + TestFilter testFilter2 = new TestFilter(j); + OrFilter orFilter = new OrFilter(testFilter1, testFilter2); + Set> actualFilterSensitivities = orFilter.getFilterSensitivities(); + + Set> expectedFilterSensitivities = new LinkedHashSet<>(); + if (i % 2 == 0 || j % 2 == 0) { + expectedFilterSensitivities.add(FILTER_SENSITIVITY_2); + } + if (i % 3 == 0 || j % 3 == 0) { + expectedFilterSensitivities.add(FILTER_SENSITIVITY_3); + } + if (i % 5 == 0 || j % 5 == 0) { + expectedFilterSensitivities.add(FILTER_SENSITIVITY_5); + } + assertEquals(expectedFilterSensitivities, actualFilterSensitivities); + } + } + } + + @Test + @UnitTestMethod(target = OrFilter.class, name = "getFirstFilter", args = {}) + public void testGetFirstFilter() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7492542318245868773L); + + for (int i = 0; i < 30; i++) { + TestFilter testFilter1 = new TestFilter(randomGenerator.nextInt()); + TestFilter testFilter2 = new TestFilter(randomGenerator.nextInt()); + + OrFilter orFilter = new OrFilter(testFilter1, testFilter2); + assertEquals(testFilter1, orFilter.getFirstFilter()); + } + } + + @Test + @UnitTestMethod(target = OrFilter.class, name = "getSecondFilter", args = {}) + public void testGetSecondFilter() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(710120811220020778L); + + for (int i = 0; i < 30; i++) { + TestFilter testFilter1 = new TestFilter(randomGenerator.nextInt()); + TestFilter testFilter2 = new TestFilter(randomGenerator.nextInt()); + + OrFilter orFilter = new OrFilter(testFilter1, testFilter2); + assertEquals(testFilter2, orFilter.getSecondFilter()); + } + + } + + @Test + @UnitTestMethod(target = OrFilter.class, name = "toString", args = {}) + public void testToString() { + TestFilter testFilter1 = new TestFilter(32); + TestFilter testFilter2 = new TestFilter(17); + OrFilter orFilter = new OrFilter(testFilter1, testFilter2); + String actualValue = orFilter.toString(); + String expectedValue = "OrFilter [a=TestFilter [index=32], b=TestFilter [index=17]]"; + assertEquals(expectedValue, actualValue); + } + + @Test + @UnitTestMethod(target = OrFilter.class, name = "validate", args = { PartitionsContext.class }) + public void testValidate() { + PartitionsContext partitionsContext = null; + + /* + * TestFilters throw an exception when validate is invoked when their index is + * negative. We show here that the OrFilter is invoking the validate for both + * of its child filters. + * + */ + + assertDoesNotThrow(() -> { + OrFilter orFilter = new OrFilter(new TestFilter(0), new TestFilter(3)); + orFilter.validate(partitionsContext); + }); + + assertThrows(RuntimeException.class, () -> { + OrFilter orFilter = new OrFilter(new TestFilter(-1), new TestFilter(3)); + orFilter.validate(partitionsContext); + }); + + assertThrows(RuntimeException.class, () -> { + OrFilter orFilter = new OrFilter(new TestFilter(0), new TestFilter(-1)); + orFilter.validate(partitionsContext); + }); + + assertThrows(RuntimeException.class, () -> { + OrFilter orFilter = new OrFilter(new TestFilter(-1), new TestFilter(-1)); + orFilter.validate(partitionsContext); + }); + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/filters/AT_TrueFilter.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/filters/AT_TrueFilter.java new file mode 100644 index 000000000..942bef6a1 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/support/filters/AT_TrueFilter.java @@ -0,0 +1,96 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_TrueFilter { + + @Test + @UnitTestConstructor(target = TrueFilter.class, args = {}) + public void testTrueFilter() { + // nothing to test + } + + @Test + @UnitTestMethod(target = TrueFilter.class, name = "equals", args = { Object.class }) + public void testEquals() { + TrueFilter trueFilter = new TrueFilter(); + + // is never equal to null + assertFalse(trueFilter.equals(null)); + + // reflexive + assertTrue(trueFilter.equals(trueFilter)); + + // symmetric, transitive, consistent + for (int i = 0; i < 30; i++) { + TrueFilter f1 = new TrueFilter(); + TrueFilter f2 = new TrueFilter(); + for (int j = 0; j < 10; j++) { + assertTrue(f1.equals(f2)); + assertTrue(f2.equals(f1)); + } + } + } + + @Test + @UnitTestMethod(target = TrueFilter.class, name = "hashCode", args = {}) + public void testHashCode() { + assertEquals(1, new TrueFilter().hashCode()); + } + + @Test + @UnitTestMethod(target = TrueFilter.class, name = "evaluate", args = { PartitionsContext.class, PersonId.class }) + public void testEvaluate() { + PartitionsContext partitionsContext = null; + PersonId personId = new PersonId(56); + assertTrue(new TrueFilter().evaluate(partitionsContext, personId)); + } + + @Test + @UnitTestMethod(target = TrueFilter.class, name = "getFilterSensitivities", args = {}) + public void testGetFilterSensitivities() { + TrueFilter trueFilter = new TrueFilter(); + assertTrue(trueFilter.getFilterSensitivities().isEmpty()); + } + + @Test + @UnitTestMethod(target = TrueFilter.class, name = "toString", args = {}) + public void testToString() { + TrueFilter trueFilter = new TrueFilter(); + String actualValue = trueFilter.toString(); + String expectedValue = "TrueFilter []"; + assertEquals(expectedValue, actualValue); + } + + @Test + @UnitTestMethod(target = TrueFilter.class, name = "validate", args = { PartitionsContext.class }) + public void testValidate() { + PartitionsContext partitionsContext = null; + + /* + * TestFilters throw an exception when validate is invoked when their index is + * negative. We show here that the TrueFilter is invoking the validate for both + * of its child filters. + * + */ + + assertDoesNotThrow(() -> { + TrueFilter trueFilter = new TrueFilter(); + trueFilter.validate(partitionsContext); + }); + + + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/AT_FunctionalAttributeLabeler.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/AT_FunctionalAttributeLabeler.java new file mode 100644 index 000000000..0c0b005d2 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/AT_FunctionalAttributeLabeler.java @@ -0,0 +1,27 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.function.Function; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support.AttributeId; +import util.annotations.UnitTestConstructor; + +public class AT_FunctionalAttributeLabeler { + private static enum LocalAttributeId implements AttributeId { + LOCAL_ID + } + + @Test + @UnitTestConstructor(target = FunctionalAttributeLabeler.class, args = { AttributeId.class, Function.class }) + public void testFunctionalAttributeLabeler() { + FunctionalAttributeLabeler functionalAttributeLabeler = new FunctionalAttributeLabeler( + LocalAttributeId.LOCAL_ID, (a) -> "valid value"); + + assertEquals(LocalAttributeId.LOCAL_ID, functionalAttributeLabeler.getId()); + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/AT_PartitionsTestPluginFactory.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/AT_PartitionsTestPluginFactory.java new file mode 100644 index 000000000..d78a9e621 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/AT_PartitionsTestPluginFactory.java @@ -0,0 +1,241 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.function.Consumer; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.TestFactoryUtil; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginId; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.partitions.PartitionsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.partitions.PartitionsPluginId; +import gov.hhs.aspr.ms.gcm.plugins.partitions.datamanagers.PartitionsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.PartitionsTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.AttributesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.AttributesPluginId; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support.AttributeError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support.TestAttributeId; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePluginId; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeoplePluginData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonRange; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPluginId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.StochasticsError; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.testsupport.TestRandomGeneratorId; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.wrappers.MutableBoolean; + +public class AT_PartitionsTestPluginFactory { + + @Test + @UnitTestMethod(target = PartitionsTestPluginFactory.class, name = "factory", args = { int.class, long.class, + Consumer.class }) + public void testFtestFactory_Consumeractory1() { + MutableBoolean executed = new MutableBoolean(); + Factory factory = PartitionsTestPluginFactory.factory(100, 9029198675932589278L, c -> executed.setValue(true)); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + assertTrue(executed.getValue()); + + // precondition: consumer is null + Consumer nullConsumer = null; + ContractException contractException = assertThrows(ContractException.class, + () -> PartitionsTestPluginFactory.factory(0, 0, nullConsumer)); + assertEquals(NucleusError.NULL_ACTOR_CONTEXT_CONSUMER, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PartitionsTestPluginFactory.class, name = "factory", args = { int.class, long.class, + TestPluginData.class }) + public void testFactory_TestPluginData() { + MutableBoolean executed = new MutableBoolean(); + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, c -> executed.setValue(true))); + TestPluginData testPluginData = pluginBuilder.build(); + + Factory factory = PartitionsTestPluginFactory.factory(100, 2990359774692004249L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + assertTrue(executed.getValue()); + + // precondition: testPluginData is null + TestPluginData nullTestPluginData = null; + ContractException contractException = assertThrows(ContractException.class, + () -> PartitionsTestPluginFactory.factory(0, 0, nullTestPluginData)); + assertEquals(NucleusError.NULL_PLUGIN_DATA, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = PartitionsTestPluginFactory.Factory.class, name = "getPlugins", args = {}) + public void testGetPlugins() { + List plugins = PartitionsTestPluginFactory.factory(0, 0, t -> { + }).getPlugins(); + assertEquals(5, plugins.size()); + + TestFactoryUtil.checkPluginExists(plugins, AttributesPluginId.PLUGIN_ID); + TestFactoryUtil.checkPluginExists(plugins, PartitionsPluginId.PLUGIN_ID); + TestFactoryUtil.checkPluginExists(plugins, PeoplePluginId.PLUGIN_ID); + TestFactoryUtil.checkPluginExists(plugins, StochasticsPluginId.PLUGIN_ID); + TestFactoryUtil.checkPluginExists(plugins, TestPluginId.PLUGIN_ID); + } + + @Test + @UnitTestMethod(target = PartitionsTestPluginFactory.Factory.class, name = "setAttributesPluginData", args = { + AttributesPluginData.class }) + public void testSetAttributesPluginData() { + AttributesPluginData.Builder attributesBuilder = AttributesPluginData.builder(); + for (TestAttributeId testAttributeId : TestAttributeId.values()) { + attributesBuilder.defineAttribute(testAttributeId, testAttributeId.getAttributeDefinition()); + } + + AttributesPluginData attributesPluginData = attributesBuilder.build(); + + List plugins = PartitionsTestPluginFactory.factory(0, 0, t -> { + }).setAttributesPluginData(attributesPluginData).getPlugins(); + + TestFactoryUtil.checkPluginDataExists(plugins, attributesPluginData, AttributesPluginId.PLUGIN_ID); + + // precondition: attributesPluginData is not null + ContractException contractException = assertThrows(ContractException.class, + () -> PartitionsTestPluginFactory.factory(0, 0, t -> { + }).setAttributesPluginData(null)); + assertEquals(AttributeError.NULL_ATTRIBUTES_PLUGIN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PartitionsTestPluginFactory.Factory.class, name = "setPartitionsPlugin", args = { + Plugin.class }) + public void testSetPartitionsPlugin() { + Plugin partitionsPlugin = PartitionsPlugin.builder()// + .setPartitionsPluginData(PartitionsPluginData.builder().build())// + .addPluginDependency(AttributesPluginId.PLUGIN_ID)// + .getPartitionsPlugin(); + + List plugins = PartitionsTestPluginFactory.factory(0, 0, t -> { + }).setPartitionsPlugin(partitionsPlugin).getPlugins(); + + Plugin actualPlugin = TestFactoryUtil.checkPluginExists(plugins, PartitionsPluginId.PLUGIN_ID); + assertTrue(partitionsPlugin == actualPlugin); + + // precondition: partitionsPlugin is not null + ContractException contractException = assertThrows(ContractException.class, + () -> PartitionsTestPluginFactory.factory(0, 0, t -> { + }).setPartitionsPlugin(null)); + assertEquals(PartitionError.NULL_PARTITION_PLUGIN, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PartitionsTestPluginFactory.Factory.class, name = "setPeoplePluginData", args = { + PeoplePluginData.class }) + public void testSetPeoplePluginData() { + PeoplePluginData.Builder builder = PeoplePluginData.builder(); + builder.addPersonRange(new PersonRange(0, 99)); + + PeoplePluginData peoplePluginData = builder.build(); + + List plugins = PartitionsTestPluginFactory.factory(0, 0, t -> { + }).setPeoplePluginData(peoplePluginData).getPlugins(); + + TestFactoryUtil.checkPluginDataExists(plugins, peoplePluginData, PeoplePluginId.PLUGIN_ID); + + // precondition: peoplePluginData is not null + ContractException contractException = assertThrows(ContractException.class, + () -> PartitionsTestPluginFactory.factory(0, 0, t -> { + }).setPeoplePluginData(null)); + assertEquals(PersonError.NULL_PEOPLE_PLUGIN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PartitionsTestPluginFactory.Factory.class, name = "setStochasticsPluginData", args = { + StochasticsPluginData.class }) + public void testSetStochasticsPluginData() { + StochasticsPluginData.Builder builder = StochasticsPluginData.builder(); + + WellState wellState = WellState.builder().setSeed(2990359774692004249L).build(); + builder.setMainRNGState(wellState); + + wellState = WellState.builder().setSeed(450787180090162111L).build(); + builder.addRNG(TestRandomGeneratorId.BLITZEN, wellState); + + StochasticsPluginData stochasticsPluginData = builder.build(); + + List plugins = PartitionsTestPluginFactory.factory(0, 0, t -> { + }).setStochasticsPluginData(stochasticsPluginData).getPlugins(); + + TestFactoryUtil.checkPluginDataExists(plugins, stochasticsPluginData, StochasticsPluginId.PLUGIN_ID); + + // precondition: stochasticsPluginData is not null + ContractException contractException = assertThrows(ContractException.class, + () -> PartitionsTestPluginFactory.factory(0, 0, t -> { + }).setStochasticsPluginData(null)); + assertEquals(StochasticsError.NULL_STOCHASTICS_PLUGIN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PartitionsTestPluginFactory.class, name = "getStandardAttributesPluginData", args = {}) + public void testGetStandardAttributesPluginData() { + + AttributesPluginData.Builder attributesBuilder = AttributesPluginData.builder(); + for (TestAttributeId testAttributeId : TestAttributeId.values()) { + attributesBuilder.defineAttribute(testAttributeId, testAttributeId.getAttributeDefinition()); + } + + AttributesPluginData expectedPluginData = attributesBuilder.build(); + AttributesPluginData actualPluginData = PartitionsTestPluginFactory.getStandardAttributesPluginData(); + + assertEquals(expectedPluginData, actualPluginData); + } + + @Test + @UnitTestMethod(target = PartitionsTestPluginFactory.class, name = "getStandardPartitionsPlugin", args = {}) + public void testGetStandardPartitionsPlugin() { + + Plugin partitionsPlugin = PartitionsTestPluginFactory.getStandardPartitionsPlugin(); + assertTrue(partitionsPlugin.getPluginDependencies().contains(AttributesPluginId.PLUGIN_ID)); + } + + @Test + @UnitTestMethod(target = PartitionsTestPluginFactory.class, name = "getStandardPeoplePluginData", args = { + int.class }) + public void testGetStandardPeoplePluginData() { + + int initialPopulation = 100; + + PeoplePluginData.Builder peopleBuilder = PeoplePluginData.builder(); + if (initialPopulation > 0) { + peopleBuilder.addPersonRange(new PersonRange(0, initialPopulation - 1)); + } + + PeoplePluginData expectedPluginData = peopleBuilder.build(); + PeoplePluginData actualPluginData = PartitionsTestPluginFactory.getStandardPeoplePluginData(initialPopulation); + assertEquals(expectedPluginData, actualPluginData); + } + + @Test + @UnitTestMethod(target = PartitionsTestPluginFactory.class, name = "getStandardStochasticsPluginData", args = { + long.class }) + public void testGetStandardStochasticsPluginData() { + long seed = 7995349318419680542L; + + WellState wellState = WellState.builder().setSeed(seed).build(); + + StochasticsPluginData expectedPluginData = StochasticsPluginData.builder().setMainRNGState(wellState).build(); + StochasticsPluginData actualPluginData = PartitionsTestPluginFactory.getStandardStochasticsPluginData(seed); + + assertEquals(expectedPluginData, actualPluginData); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/AT_TestPartitionsContext.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/AT_TestPartitionsContext.java new file mode 100644 index 000000000..5884ebddd --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/AT_TestPartitionsContext.java @@ -0,0 +1,75 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.stream.IntStream; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.partitions.datamanagers.PartitionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.PartitionsTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.AttributesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_TestPartitionsContext { + + @Test + @UnitTestConstructor(target = TestPartitionsContext.class, args = { ActorContext.class }) + public void testTestPartitionsContext() { + // nothing to test + } + + @Test + @UnitTestMethod(target = TestPartitionsContext.class, name = "getDataManager", args = { Class.class }) + public void testGetDataManager() { + + Factory factory = PartitionsTestPluginFactory.factory(0, 9139618710983560173L, (c) -> { + + // establish data managers + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + + // create a TestPartitionsContext from the ActorContext + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + // show that the partitions context is a pass through to the actor context + assertEquals(attributesDataManager, testPartitionsContext.getDataManager(AttributesDataManager.class)); + assertEquals(peopleDataManager, testPartitionsContext.getDataManager(PeopleDataManager.class)); + assertEquals(partitionsDataManager, testPartitionsContext.getDataManager(PartitionsDataManager.class)); + assertEquals(stochasticsDataManager, testPartitionsContext.getDataManager(StochasticsDataManager.class)); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = TestPartitionsContext.class, name = "getTime", args = {}) + public void testGetTime() { + TestPluginData.Builder testPluginDataBuilder = TestPluginData.builder(); + + //show that the TestPartitionsContext returns the correct time + IntStream.range(0, 5).forEach((i) -> { + testPluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(i, (c) -> { + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + assertEquals(c.getTime(), testPartitionsContext.getTime()); + + })); + }); + + TestPluginData testPluginData = testPluginDataBuilder.build(); + + + Factory factory = PartitionsTestPluginFactory.factory(0, 2094974625949946320L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/AT_AttributesDataManager.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/AT_AttributesDataManager.java new file mode 100644 index 000000000..a79e1c890 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/AT_AttributesDataManager.java @@ -0,0 +1,500 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; +import gov.hhs.aspr.ms.gcm.nucleus.EventFilter; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.partitions.PartitionsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.partitions.datamanagers.PartitionsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.PartitionsTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.PartitionsTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.events.AttributeUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support.AttributeDefinition; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support.AttributeError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support.AttributeId; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support.TestAttributeId; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePlugin; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeoplePluginData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonRange; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; +import util.wrappers.MultiKey; + +public class AT_AttributesDataManager { + + @Test + @UnitTestMethod(target = AttributesDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testAttributesDataViewInitialization() { + Factory factory = PartitionsTestPluginFactory.factory(0, 5241628071704306523L, (c) -> { + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + + assertEquals(EnumSet.allOf(TestAttributeId.class), attributesDataManager.getAttributeIds()); + + for (TestAttributeId testAttributeId : TestAttributeId.values()) { + assertEquals(testAttributeId.getAttributeDefinition(), + attributesDataManager.getAttributeDefinition(testAttributeId)); + } + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = AttributesDataManager.class, name = "setAttributeValue", args = { PersonId.class, + AttributeId.class, Object.class }) + + public void testSetAttributeValue() { + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // add an agent that will observe attribute changes + Set peopleObserved = new LinkedHashSet<>(); + Set expectedPersonIds = new LinkedHashSet<>(); + for (int i = 0; i < 10; i++) { + expectedPersonIds.add(new PersonId(i)); + } + + pluginDataBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + EventFilter eventFilter = attributesDataManager + .getEventFilterForAttributeUpdateEvent(TestAttributeId.BOOLEAN_0); + c.subscribe(eventFilter, (c2, e) -> { + peopleObserved.add(e.personId()); + }); + })); + + // add an agent that will show that the AttributesDataView is properly + // initialized + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + + for (PersonId personId : peopleDataManager.getPeople()) { + Boolean value = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); + + assertFalse(value); + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, true); + + value = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); + assertTrue(value); + } + + })); + + TestPluginData testPluginData = pluginDataBuilder.build(); + + Factory factory = PartitionsTestPluginFactory.factory(expectedPersonIds.size(), 4599936503626031739L, + testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // show that the correct observations were made; + assertEquals(expectedPersonIds, peopleObserved); + + } + + @Test + @UnitTestConstructor(target = AttributesDataManager.class, args = { AttributesPluginData.class }) + public void testConstructor() { + ContractException contractException = assertThrows(ContractException.class, + () -> new AttributesDataManager(null)); + assertEquals(AttributeError.NULL_ATTRIBUTE_INITIAL_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = AttributesDataManager.class, name = "attributeExists", args = { AttributeId.class }) + public void testAttributeExists() { + Factory factory = PartitionsTestPluginFactory.factory(100, 6136319242032948471L, (c) -> { + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + + for (TestAttributeId testAttributeId : TestAttributeId.values()) { + assertTrue(attributesDataManager.attributeExists(testAttributeId)); + } + + assertFalse(attributesDataManager.attributeExists(TestAttributeId.getUnknownAttributeId())); + + assertFalse(attributesDataManager.attributeExists(null)); + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = AttributesDataManager.class, name = "getAttributeDefinition", args = { AttributeId.class }) + public void testGetAttributeDefinition() { + + Factory factory = PartitionsTestPluginFactory.factory(100, 7056826773207732206L, (c) -> { + + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + + for (TestAttributeId testAttributeId : TestAttributeId.values()) { + AttributeDefinition expectedAttributeDefinition = testAttributeId.getAttributeDefinition(); + AttributeDefinition actualAttributeDefinition = attributesDataManager + .getAttributeDefinition(testAttributeId); + assertEquals(expectedAttributeDefinition, actualAttributeDefinition); + } + + // precondition tests: + + // if the attribute id is null + ContractException contractException = assertThrows(ContractException.class, + () -> attributesDataManager.getAttributeDefinition(null)); + assertEquals(AttributeError.NULL_ATTRIBUTE_ID, contractException.getErrorType()); + + // if the attribute id unknown + contractException = assertThrows(ContractException.class, + () -> attributesDataManager.getAttributeDefinition(TestAttributeId.getUnknownAttributeId())); + assertEquals(AttributeError.UNKNOWN_ATTRIBUTE_ID, contractException.getErrorType()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = AttributesDataManager.class, name = "getAttributeIds", args = {}) + public void testGetAttributeIds() { + Factory factory = PartitionsTestPluginFactory.factory(100, 3922883805893258744L, (c) -> { + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + assertEquals(EnumSet.allOf(TestAttributeId.class), attributesDataManager.getAttributeIds()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = AttributesDataManager.class, name = "getAttributeValue", args = { PersonId.class, + AttributeId.class }) + public void testGetAttributeValue() { + + Factory factory = PartitionsTestPluginFactory.factory(100, 6311231823303553715L, (c) -> { + + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + + // show that there are people in the test + assertEquals(100, peopleDataManager.getPopulationCount()); + + // set random attribute values on people + for (PersonId personId : peopleDataManager.getPeople()) { + + Boolean b0_expected = randomGenerator.nextBoolean(); + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, b0_expected); + Boolean b0_actual = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); + assertEquals(b0_expected, b0_actual); + + Boolean b1_expected = randomGenerator.nextBoolean(); + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_1, b1_expected); + Boolean b1_actual = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_1); + assertEquals(b1_expected, b1_actual); + + Integer i0_expected = randomGenerator.nextInt(); + attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_0, i0_expected); + Integer i0_actual = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_0); + assertEquals(i0_expected, i0_actual); + + Integer i1_expected = randomGenerator.nextInt(); + attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_1, i1_expected); + Integer i1_actual = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_1); + assertEquals(i1_expected, i1_actual); + + Double d0_expected = randomGenerator.nextDouble(); + attributesDataManager.setAttributeValue(personId, TestAttributeId.DOUBLE_0, d0_expected); + Double d0_actual = attributesDataManager.getAttributeValue(personId, TestAttributeId.DOUBLE_0); + assertEquals(d0_expected, d0_actual); + + Double d1_expected = randomGenerator.nextDouble(); + attributesDataManager.setAttributeValue(personId, TestAttributeId.DOUBLE_1, d1_expected); + Double d1_actual = attributesDataManager.getAttributeValue(personId, TestAttributeId.DOUBLE_1); + assertEquals(d1_expected, d1_actual); + } + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // if the attribute id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = PartitionsTestPluginFactory.factory(100, 1745090937470588460L, (c) -> { + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + PersonId goodPersonId = new PersonId(0); + attributesDataManager.getAttributeValue(goodPersonId, null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(AttributeError.NULL_ATTRIBUTE_ID, contractException.getErrorType()); + + // if the attribute id unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = PartitionsTestPluginFactory.factory(100, 6116294452862148843L, (c) -> { + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + PersonId goodPersonId = new PersonId(0); + AttributeId badAttributeId = TestAttributeId.getUnknownAttributeId(); + attributesDataManager.getAttributeValue(goodPersonId, badAttributeId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(AttributeError.UNKNOWN_ATTRIBUTE_ID, contractException.getErrorType()); + + // if the person id is null + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = PartitionsTestPluginFactory.factory(100, 5272912205692835718L, (c) -> { + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + AttributeId goodAttributeId = TestAttributeId.BOOLEAN_0; + attributesDataManager.getAttributeValue(null, goodAttributeId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + // if the person id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = PartitionsTestPluginFactory.factory(100, 4650301398849685719L, (c) -> { + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + PersonId badPersonId = new PersonId(10000000); + AttributeId goodAttributeId = TestAttributeId.BOOLEAN_0; + attributesDataManager.getAttributeValue(badPersonId, goodAttributeId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = AttributesDataManager.class, name = "getEventFilterForAttributeUpdateEvent", args = { + AttributeId.class }) + public void testGetEventFilterForAttributeUpdateEvent_attribute() { + + // select a proper subset of the attribute ids for filtering + Set selectedTestAttributeIds = new LinkedHashSet<>(); + selectedTestAttributeIds.add(TestAttributeId.BOOLEAN_0); + selectedTestAttributeIds.add(TestAttributeId.DOUBLE_1); + selectedTestAttributeIds.add(TestAttributeId.INT_1); + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // add an agent that will observe attribute changes + Set actualObservations = new LinkedHashSet<>(); + Set expectedObservations = new LinkedHashSet<>(); + + pluginDataBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + + for (TestAttributeId testAttributeId : selectedTestAttributeIds) { + EventFilter eventFilter = attributesDataManager + .getEventFilterForAttributeUpdateEvent(testAttributeId); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c.getTime(), e.attributeId(), e.personId())); + }); + } + })); + + // add an actor that will alter the people attributes over time + for (int i = 1; i < 5; i++) { + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(i, (c) -> { + + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + for (PersonId personId : peopleDataManager.getPeople()) { + for (TestAttributeId testAttributeId : TestAttributeId.values()) { + if (randomGenerator.nextBoolean()) { + Object propertyValue = testAttributeId.getRandomPropertyValue(randomGenerator); + attributesDataManager.setAttributeValue(personId, testAttributeId, propertyValue); + if (selectedTestAttributeIds.contains(testAttributeId)) { + expectedObservations.add(new MultiKey(c.getTime(), testAttributeId, personId)); + } + } + } + } + })); + } + + TestPluginData testPluginData = pluginDataBuilder.build(); + + Factory factory = PartitionsTestPluginFactory.factory(10, 6745924247452865860L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // show that the correct observations were made; + assertEquals(expectedObservations, actualObservations); + + // precondition test: if the attribute id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = PartitionsTestPluginFactory.factory(10, 947664382516123630L, (c) -> { + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + attributesDataManager.getEventFilterForAttributeUpdateEvent(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(AttributeError.NULL_ATTRIBUTE_ID, contractException.getErrorType()); + + // precondition test: if the attribute id is not known + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = PartitionsTestPluginFactory.factory(10, 1819497399235641135L, (c) -> { + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + attributesDataManager.getEventFilterForAttributeUpdateEvent(TestAttributeId.getUnknownAttributeId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(AttributeError.UNKNOWN_ATTRIBUTE_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = AttributesDataManager.class, name = "getEventFilterForAttributeUpdateEvent", args = {}) + public void testGetEventFilterForAttributeUpdateEvent() { + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // add an agent that will observe all attribute changes + Set actualObservations = new LinkedHashSet<>(); + Set expectedObservations = new LinkedHashSet<>(); + + pluginDataBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + + EventFilter eventFilter = attributesDataManager + .getEventFilterForAttributeUpdateEvent(); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c.getTime(), e.attributeId(), e.personId())); + }); + + })); + + // add an actor that will alter the people attributes over time + for (int i = 1; i < 5; i++) { + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(i, (c) -> { + + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + for (PersonId personId : peopleDataManager.getPeople()) { + for (TestAttributeId testAttributeId : TestAttributeId.values()) { + if (randomGenerator.nextBoolean()) { + Object propertyValue = testAttributeId.getRandomPropertyValue(randomGenerator); + attributesDataManager.setAttributeValue(personId, testAttributeId, propertyValue); + expectedObservations.add(new MultiKey(c.getTime(), testAttributeId, personId)); + } + } + } + })); + } + + TestPluginData testPluginData = pluginDataBuilder.build(); + + Factory factory = PartitionsTestPluginFactory.factory(10, 6920537014398191296L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // show that the correct observations were made; + assertEquals(expectedObservations, actualObservations); + + } + + private AttributesPluginData getRandomAttributesPluginData(long seed, List people) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + AttributesPluginData.Builder builder = AttributesPluginData.builder(); + + for (TestAttributeId testAttributeId : TestAttributeId.values()) { + AttributeDefinition attributeDefinition = testAttributeId.getAttributeDefinition(); + builder.defineAttribute(testAttributeId, attributeDefinition); + } + + for (PersonId personId : people) { + for (TestAttributeId testAttributeId : TestAttributeId.values()) { + Object propertyValue = testAttributeId.getRandomPropertyValue(randomGenerator); + builder.setPersonAttributeValue(personId, testAttributeId, propertyValue); + } + } + + return builder.build(); + } + + @Test + @UnitTestMethod(target = AttributesDataManager.class, name = "toString", args = {}) + public void testToString() { + List people = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + people.add(new PersonId(i)); + } + + AttributesPluginData randomAttributesPluginData = getRandomAttributesPluginData(146602962355699453L, people); + Plugin attributesPlugin = AttributesPlugin.getAttributesPlugin(randomAttributesPluginData); + + PeoplePluginData peoplePluginData = PeoplePluginData.builder().addPersonRange(new PersonRange(0, 4)).build(); + Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(peoplePluginData); + + PartitionsPluginData partitionsPluginData = PartitionsPluginData.builder().build(); + Plugin partitionsPlugin = PartitionsPlugin.builder().setPartitionsPluginData(partitionsPluginData) + .getPartitionsPlugin(); + + StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder() + .setMainRNGState(WellState.builder().setSeed(6440829571736239633L).build()).build(); + Plugin stochasticsPlugin = StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData); + + TestPluginData testPluginData = TestPluginData.builder().addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + String actualValue = attributesDataManager.toString(); + + //expected value validated via inspection + String expectedValue = "AttributesDataManager [" + + "attributeDefinitions={" + + "INT_0=AttributeDefinition [type=class java.lang.Integer, defaultValue=0], " + + "INT_1=AttributeDefinition [type=class java.lang.Integer, defaultValue=1], " + + "DOUBLE_0=AttributeDefinition [type=class java.lang.Double, defaultValue=0.0], " + + "DOUBLE_1=AttributeDefinition [type=class java.lang.Double, defaultValue=1.0], " + + "BOOLEAN_0=AttributeDefinition [type=class java.lang.Boolean, defaultValue=false], " + + "BOOLEAN_1=AttributeDefinition [type=class java.lang.Boolean, defaultValue=true]}, " + + "attributeValues={" + + "INT_0={0=-1853985413, 1=267548881, 2=2046503620, 3=781984606, 4=-1339150308}, " + + "INT_1={0=907447393, 1=-821246733, 2=1097040035, 3=242609926, 4=1108077451}, " + + "DOUBLE_0={0=0.17544748424416778, 1=0.3430229866948431, 2=0.7111106859824385, 3=0.4231972579610015, 4=0.8313225649559359}, " + + "DOUBLE_1={0=0.40215185526664254, 1=0.9508065326364401, 2=0.4860658436547638, 3=0.732728221359974, 4=0.268176327893356}, " + + "BOOLEAN_0={0=false, 1=true, 2=true, 3=true, 4=false}, " + + "BOOLEAN_1={0=false, 1=false, 2=true, 3=false, 4=true}}]"; + + assertEquals(expectedValue, actualValue); + + })).build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + TestSimulation.builder()// + .addPlugin(attributesPlugin)// + .addPlugin(partitionsPlugin)// + .addPlugin(peoplePlugin)// + .addPlugin(stochasticsPlugin)// + .addPlugin(testPlugin)// + .build()// + .execute(); + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/AT_AttributesPlugin.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/AT_AttributesPlugin.java new file mode 100644 index 000000000..8282be864 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/AT_AttributesPlugin.java @@ -0,0 +1,90 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData.Builder; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.partitions.PartitionsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.partitions.datamanagers.PartitionsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePlugin; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePluginId; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeoplePluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import util.annotations.UnitTestMethod; + +public class AT_AttributesPlugin { + + @Test + @UnitTestMethod(target = AttributesPlugin.class, name = "getAttributesPlugin", args = { AttributesPluginData.class }) + public void testGetAttributesPlugin() { + + /* + * Create an attributes plugin + */ + AttributesPluginData attributesPluginData = AttributesPluginData.builder().build(); + Plugin attributesPlugin = AttributesPlugin.getAttributesPlugin(attributesPluginData); + + Plugin partitionsPlugin = PartitionsPlugin.builder()// + .setPartitionsPluginData(PartitionsPluginData.builder().build())// + //.addPluginDependency(AttributesPluginId.PLUGIN_ID)// + .getPartitionsPlugin(); + + + // show that the plugin data is present + List pluginDatas = attributesPlugin.getPluginDatas(); + assertNotNull(pluginDatas); + assertEquals(1, pluginDatas.size()); + assertTrue(pluginDatas.contains(attributesPluginData)); + + // show that the plugin has the expected id + assertEquals(AttributesPluginId.PLUGIN_ID, attributesPlugin.getPluginId()); + + // show that the plugin has the correct dependencies + Set expectedDependencies = new LinkedHashSet<>(); + expectedDependencies.add(PeoplePluginId.PLUGIN_ID); + Set actualDependencies = attributesPlugin.getPluginDependencies(); + assertEquals(expectedDependencies, actualDependencies); + + // show that the plugin contributed the AttributesDataManager to the + // simulation + Builder testPluginDataBuilder = TestPluginData.builder(); + + testPluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + AttributesDataManager dataManager = c.getDataManager(AttributesDataManager.class); + assertNotNull(dataManager); + })); + + TestPluginData testPluginData = testPluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + List plugins = new ArrayList<>(); + plugins.add(testPlugin); + WellState wellState = WellState.builder().setSeed(435346454564566L).build(); + plugins.add(StochasticsPlugin.getStochasticsPlugin(StochasticsPluginData.builder().setMainRNGState(wellState).build())); + plugins.add(PeoplePlugin.getPeoplePlugin(PeoplePluginData.builder().build())); + plugins.add(partitionsPlugin); + plugins.add(attributesPlugin); + + TestSimulation.builder().addPlugins(plugins).build().execute(); + + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/AT_AttributesPluginData.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/AT_AttributesPluginData.java new file mode 100644 index 000000000..5c824da99 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/AT_AttributesPluginData.java @@ -0,0 +1,462 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.PluginDataBuilder; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support.AttributeDefinition; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support.AttributeError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support.AttributeId; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support.TestAttributeId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; +import util.wrappers.MultiKey; + +public class AT_AttributesPluginData { + + @Test + @UnitTestMethod(target = AttributesPluginData.class, name = "builder", args = {}) + public void testBuilder() { + assertNotNull(AttributesPluginData.builder()); + } + + @Test + @UnitTestMethod(target = AttributesPluginData.Builder.class, name = "build", args = {}) + public void testBuild() { + assertNotNull(AttributesPluginData.builder().build()); + } + + @Test + @UnitTestMethod(target = AttributesPluginData.Builder.class, name = "defineAttribute", args = { AttributeId.class, + AttributeDefinition.class }) + public void testDefineAttribute() { + AttributesPluginData.Builder builder = AttributesPluginData.builder(); + for (TestAttributeId testAttributeId : TestAttributeId.values()) { + builder.defineAttribute(testAttributeId, testAttributeId.getAttributeDefinition()); + } + AttributesPluginData attributesPluginData = builder.build(); + assertEquals(EnumSet.allOf(TestAttributeId.class), attributesPluginData.getAttributeIds()); + for (TestAttributeId testAttributeId : TestAttributeId.values()) { + assertEquals(testAttributeId.getAttributeDefinition(), + attributesPluginData.getAttributeDefinition(testAttributeId)); + } + + // precondition tests + + // if the attribute id is null + ContractException contractException = assertThrows(ContractException.class, () -> AttributesPluginData.builder() + .defineAttribute(null, TestAttributeId.BOOLEAN_0.getAttributeDefinition())); + assertEquals(AttributeError.NULL_ATTRIBUTE_ID, contractException.getErrorType()); + + // if the attribute definition is null + contractException = assertThrows(ContractException.class, + () -> AttributesPluginData.builder().defineAttribute(TestAttributeId.BOOLEAN_0, null)); + assertEquals(AttributeError.NULL_ATTRIBUTE_DEFINITION, contractException.getErrorType()); + + // if the attribute id was previously added + contractException = assertThrows(ContractException.class, () -> { + AttributesPluginData.builder()// + .defineAttribute(TestAttributeId.BOOLEAN_0, TestAttributeId.BOOLEAN_0.getAttributeDefinition()) + .defineAttribute(TestAttributeId.BOOLEAN_0, TestAttributeId.BOOLEAN_0.getAttributeDefinition()); + }); + assertEquals(AttributeError.DUPLICATE_ATTRIBUTE_DEFINITION, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = AttributesPluginData.class, name = "getAttributeDefinition", args = { AttributeId.class }) + public void testGetAttributeDefinition() { + AttributesPluginData.Builder builder = AttributesPluginData.builder(); + for (TestAttributeId testAttributeId : TestAttributeId.values()) { + builder.defineAttribute(testAttributeId, testAttributeId.getAttributeDefinition()); + } + AttributesPluginData attributesPluginData = builder.build(); + assertEquals(EnumSet.allOf(TestAttributeId.class), attributesPluginData.getAttributeIds()); + for (TestAttributeId testAttributeId : TestAttributeId.values()) { + assertEquals(testAttributeId.getAttributeDefinition(), + attributesPluginData.getAttributeDefinition(testAttributeId)); + } + + // precondition tests + + // if the attribute id is null + ContractException contractException = assertThrows(ContractException.class, + () -> attributesPluginData.getAttributeDefinition(null)); + assertEquals(AttributeError.NULL_ATTRIBUTE_ID, contractException.getErrorType()); + + // if the attribute id is unknown + contractException = assertThrows(ContractException.class, + () -> attributesPluginData.getAttributeDefinition(TestAttributeId.getUnknownAttributeId())); + assertEquals(AttributeError.UNKNOWN_ATTRIBUTE_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = AttributesPluginData.class, name = "getAttributeIds", args = {}) + public void testGetAttributeIds() { + AttributesPluginData.Builder builder = AttributesPluginData.builder(); + for (TestAttributeId testAttributeId : TestAttributeId.values()) { + builder.defineAttribute(testAttributeId, testAttributeId.getAttributeDefinition()); + } + AttributesPluginData attributesPluginData = builder.build(); + assertEquals(EnumSet.allOf(TestAttributeId.class), attributesPluginData.getAttributeIds()); + } + + @Test + @UnitTestMethod(target = AttributesPluginData.class, name = "getCloneBuilder", args = {}) + public void testGetCloneBuilder() { + AttributesPluginData.Builder builder = AttributesPluginData.builder(); + for (TestAttributeId testAttributeId : TestAttributeId.values()) { + builder.defineAttribute(testAttributeId, testAttributeId.getAttributeDefinition()); + } + + AttributesPluginData attributesPluginData = builder.build(); + PluginDataBuilder cloneBuilder = attributesPluginData.getCloneBuilder(); + assertNotNull(cloneBuilder); + + PluginData pluginData = cloneBuilder.build(); + + assertTrue(pluginData instanceof AttributesPluginData); + + AttributesPluginData clonePluginData = (AttributesPluginData) pluginData; + assertEquals(attributesPluginData.getAttributeIds(), clonePluginData.getAttributeIds()); + for (TestAttributeId testAttributeId : TestAttributeId.values()) { + assertEquals(attributesPluginData.getAttributeDefinition(testAttributeId), + clonePluginData.getAttributeDefinition(testAttributeId)); + } + } + + @Test + @UnitTestMethod(target = AttributesPluginData.class, name = "getAttributeValues", args = { + AttributeId.class }) + public void testGetAttributeValues_AttributeId() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3876639732263152250L); + + AttributesPluginData.Builder builder = AttributesPluginData.builder(); + for (TestAttributeId testAttributeId : TestAttributeId.values()) { + builder.defineAttribute(testAttributeId, testAttributeId.getAttributeDefinition()); + } + + Map expectedValues = new LinkedHashMap<>(); + + List people = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + PersonId personId = new PersonId(2 * i + 1); + people.add(personId); + } + + for (PersonId personId : people) { + for (TestAttributeId testAttributeId : TestAttributeId.values()) { + Object propertyValue = testAttributeId.getRandomPropertyValue(randomGenerator); + builder.setPersonAttributeValue(personId, testAttributeId, propertyValue); + expectedValues.put(new MultiKey(personId, testAttributeId), propertyValue); + } + } + + AttributesPluginData attributesPluginData = builder.build(); + + Map actualValues = new LinkedHashMap<>(); + + for (AttributeId attributeId : attributesPluginData.getAttributeIds()) { + List list = attributesPluginData.getAttributeValues(attributeId); + for (int i = 0; i < list.size(); i++) { + Object propertyValue = list.get(i); + if (propertyValue != null) { + Object replacedValue = actualValues.put(new MultiKey(new PersonId(i), attributeId), propertyValue); + assertNull(replacedValue); + } + } + } + + assertEquals(expectedValues, actualValues); + } + + @Test + @UnitTestMethod(target = AttributesPluginData.class, name = "getAttributeValues", args = {}) + public void testGetAttributeValues() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3876639732263152250L); + + AttributesPluginData.Builder builder = AttributesPluginData.builder(); + for (TestAttributeId testAttributeId : TestAttributeId.values()) { + builder.defineAttribute(testAttributeId, testAttributeId.getAttributeDefinition()); + } + + Map expectedValues = new LinkedHashMap<>(); + + List people = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + PersonId personId = new PersonId(2 * i + 1); + people.add(personId); + } + + for (PersonId personId : people) { + for (TestAttributeId testAttributeId : TestAttributeId.values()) { + Object propertyValue = testAttributeId.getRandomPropertyValue(randomGenerator); + builder.setPersonAttributeValue(personId, testAttributeId, propertyValue); + expectedValues.put(new MultiKey(personId, testAttributeId), propertyValue); + } + } + + AttributesPluginData attributesPluginData = builder.build(); + + Map actualValues = new LinkedHashMap<>(); + + Map> attributeValues = attributesPluginData.getAttributeValues(); + for (AttributeId attributeId : attributeValues.keySet()) { + List list = attributeValues.get(attributeId); + for (int i = 0; i < list.size(); i++) { + Object propertyValue = list.get(i); + if (propertyValue != null) { + Object replacedValue = actualValues.put(new MultiKey(new PersonId(i), attributeId), propertyValue); + assertNull(replacedValue); + } + } + } + + assertEquals(expectedValues, actualValues); + } + + @Test + @UnitTestMethod(target = AttributesPluginData.Builder.class, name = "setPersonAttributeValue", args = { + PersonId.class, AttributeId.class, Object.class }) + public void testSetPersonAttributeValue() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5976036831935756004L); + + AttributesPluginData.Builder builder = AttributesPluginData.builder(); + for (TestAttributeId testAttributeId : TestAttributeId.values()) { + builder.defineAttribute(testAttributeId, testAttributeId.getAttributeDefinition()); + } + + Map expectedValues = new LinkedHashMap<>(); + + List people = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + PersonId personId = new PersonId(2 * i + 1); + people.add(personId); + } + + for (PersonId personId : people) { + for (TestAttributeId testAttributeId : TestAttributeId.values()) { + Object propertyValue = testAttributeId.getRandomPropertyValue(randomGenerator); + builder.setPersonAttributeValue(personId, testAttributeId, propertyValue); + expectedValues.put(new MultiKey(personId, testAttributeId), propertyValue); + } + } + + AttributesPluginData attributesPluginData = builder.build(); + + Map actualValues = new LinkedHashMap<>(); + + Map> attributeValues = attributesPluginData.getAttributeValues(); + for (AttributeId attributeId : attributeValues.keySet()) { + List list = attributeValues.get(attributeId); + for (int i = 0; i < list.size(); i++) { + Object propertyValue = list.get(i); + if (propertyValue != null) { + Object replacedValue = actualValues.put(new MultiKey(new PersonId(i), attributeId), propertyValue); + assertNull(replacedValue); + } + } + } + + assertEquals(expectedValues, actualValues); + + // if the person id is null + ContractException contractException = assertThrows(ContractException.class, + () -> AttributesPluginData.builder().setPersonAttributeValue(null, TestAttributeId.BOOLEAN_0, false)); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + // if the attribute property id is null + contractException = assertThrows(ContractException.class, + () -> AttributesPluginData.builder().setPersonAttributeValue(new PersonId(0), null, false)); + assertEquals(AttributeError.NULL_ATTRIBUTE_ID, contractException.getErrorType()); + + // if the attribute property value is null + contractException = assertThrows(ContractException.class, () -> { + AttributesPluginData.builder().setPersonAttributeValue(new PersonId(0), TestAttributeId.BOOLEAN_0, null); + + }); + assertEquals(AttributeError.NULL_ATTRIBUTE_VALUE, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = AttributesPluginData.class, name = "getAttributeDefinitions", args = {}) + public void testGetAttributeDefinitions() { + + Map expectedAttributeDefinitions = new LinkedHashMap<>(); + + AttributesPluginData.Builder builder = AttributesPluginData.builder(); + for (TestAttributeId testAttributeId : TestAttributeId.values()) { + AttributeDefinition attributeDefinition = testAttributeId.getAttributeDefinition(); + builder.defineAttribute(testAttributeId, attributeDefinition); + expectedAttributeDefinitions.put(testAttributeId, attributeDefinition); + } + + AttributesPluginData attributesPluginData = builder.build(); + + Map actualAttributeDefinitions = attributesPluginData + .getAttributeDefinitions(); + assertEquals(expectedAttributeDefinitions, actualAttributeDefinitions); + + } + + private AttributesPluginData getRandomAttributesPluginData(long seed) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + Random random = new Random(randomGenerator.nextLong()); + + AttributesPluginData.Builder builder = AttributesPluginData.builder(); + List list = Arrays.asList(TestAttributeId.values()); + Collections.shuffle(list, random); + int count = Math.max(1, randomGenerator.nextInt(list.size())); + List selectedAttributeIds = new ArrayList<>(); + for (int i = 0; i < count; i++) { + selectedAttributeIds.add(list.get(i)); + } + + for (TestAttributeId testAttributeId : selectedAttributeIds) { + AttributeDefinition attributeDefinition = testAttributeId.getAttributeDefinition(); + builder.defineAttribute(testAttributeId, attributeDefinition); + } + count = randomGenerator.nextInt(5); + int id = 0; + for (int i = 0; i < count; i++) { + id += randomGenerator.nextInt(3); + PersonId personId = new PersonId(id); + for (TestAttributeId testAttributeId : selectedAttributeIds) { + Object propertyValue = testAttributeId.getRandomPropertyValue(randomGenerator); + builder.setPersonAttributeValue(personId, testAttributeId, propertyValue); + } + } + + return builder.build(); + } + + @Test + @UnitTestMethod(target = AttributesPluginData.class, name = "hashCode", args = {}) + public void testHashCode() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8530033813336044717L); + + // equal objects have equal hash codes + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + AttributesPluginData attributesPluginData1 = getRandomAttributesPluginData(seed); + AttributesPluginData attributesPluginData2 = getRandomAttributesPluginData(seed); + assertEquals(attributesPluginData1, attributesPluginData2); + assertEquals(attributesPluginData1.hashCode(), attributesPluginData2.hashCode()); + } + + // hash codes are reasonably distributed + Set set = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + AttributesPluginData attributesPluginData = getRandomAttributesPluginData(randomGenerator.nextLong()); + set.add(attributesPluginData.hashCode()); + } + + assertTrue(set.size() > 95); + + } + + @Test + @UnitTestMethod(target = AttributesPluginData.class, name = "equals", args = { Object.class }) + public void testEquals() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8530033813336044717L); + + // never equals null + for (int i = 0; i < 30; i++) { + AttributesPluginData attributesPluginData = getRandomAttributesPluginData(randomGenerator.nextLong()); + assertFalse(attributesPluginData.equals(null)); + } + + // reflexive + for (int i = 0; i < 30; i++) { + AttributesPluginData attributesPluginData = getRandomAttributesPluginData(randomGenerator.nextLong()); + assertTrue(attributesPluginData.equals(attributesPluginData)); + } + + // symmetric, transitive and consistent + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + AttributesPluginData attributesPluginData1 = getRandomAttributesPluginData(seed); + AttributesPluginData attributesPluginData2 = getRandomAttributesPluginData(seed); + for (int j = 0; j < 5; j++) { + assertTrue(attributesPluginData1.equals(attributesPluginData2)); + assertTrue(attributesPluginData2.equals(attributesPluginData1)); + } + } + + // changes to inputs cause non-equality + Set set = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + AttributesPluginData attributesPluginData = getRandomAttributesPluginData(randomGenerator.nextLong()); + set.add(attributesPluginData); + } + + assertTrue(set.size() > 95); + + } + + @Test + @UnitTestMethod(target = AttributesPluginData.class, name = "toString", args = {}) + public void testToString() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8530033813336044717L); + + AttributesPluginData.Builder builder = AttributesPluginData.builder(); + for (TestAttributeId testAttributeId : TestAttributeId.values()) { + AttributeDefinition attributeDefinition = testAttributeId.getAttributeDefinition(); + builder.defineAttribute(testAttributeId, attributeDefinition); + } + + for (int i = 0; i < 3; i++) { + PersonId personId = new PersonId(2 * i + 1); + for (TestAttributeId testAttributeId : TestAttributeId.values()) { + Object propertyValue = testAttributeId.getRandomPropertyValue(randomGenerator); + builder.setPersonAttributeValue(personId, testAttributeId, propertyValue); + } + } + + AttributesPluginData attributesPluginData = builder.build(); + + // expected value validated by inspection + String expectedValue = "AttributesPluginData [data=Data [attributeDefinitions={" + + "INT_0=AttributeDefinition [type=class java.lang.Integer, defaultValue=0], INT_1=AttributeDefinition " + + "[type=class java.lang.Integer, defaultValue=1], DOUBLE_0=AttributeDefinition " + + "[type=class java.lang.Double, defaultValue=0.0], DOUBLE_1=AttributeDefinition " + + "[type=class java.lang.Double, defaultValue=1.0], BOOLEAN_0=AttributeDefinition " + + "[type=class java.lang.Boolean, defaultValue=false], BOOLEAN_1=AttributeDefinition " + + "[type=class java.lang.Boolean, defaultValue=true]}, " + "personAttributeValues={" + + "INT_0=[null, 329463590, null, -1510862987, null, -1603186760], " + + "INT_1=[null, -1442300487, null, -2051859947, null, 1530603228], " + + "DOUBLE_0=[null, 0.17274128088453056, null, 0.2724502050653399, null, 0.8295695456845824], " + + "DOUBLE_1=[null, 0.317216253156253, null, 0.3024007880133237, null, 0.24225942651802534], " + + "BOOLEAN_0=[null, false, null, true, null, false], " + + "BOOLEAN_1=[null, true, null, true, null, true]}]]"; + String actualValue = attributesPluginData.toString(); + + assertEquals(expectedValue, actualValue); + + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/AT_AttributesPluginId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/AT_AttributesPluginId.java new file mode 100644 index 000000000..c4be291b4 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/AT_AttributesPluginId.java @@ -0,0 +1,16 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestField; + +public class AT_AttributesPluginId { + + @Test + @UnitTestField(target = AttributesPluginId.class, name = "PLUGIN_ID") + public void testPluginId() { + assertNotNull(AttributesPluginId.PLUGIN_ID); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/events/AT_AttributeUpdateEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/events/AT_AttributeUpdateEvent.java new file mode 100644 index 000000000..fe883fef6 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/events/AT_AttributeUpdateEvent.java @@ -0,0 +1,91 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.events; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support.AttributeId; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support.TestAttributeId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_AttributeUpdateEvent { + + @Test + @UnitTestConstructor(target = AttributeUpdateEvent.class, args = { PersonId.class, AttributeId.class, Object.class, Object.class }) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = AttributeUpdateEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = AttributeUpdateEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = AttributeUpdateEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = AttributeUpdateEvent.class, name = "personId", args = {}) + public void testPersonId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = AttributeUpdateEvent.class, name = "attributeId", args = {}) + public void testAttributeId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = AttributeUpdateEvent.class, name = "previousValue", args = {}) + public void testPreviousValue() { + // nothing to test + } + + @Test + @UnitTestMethod(target = AttributeUpdateEvent.class, name = "currentValue", args = {}) + public void testCurrentValue() { + // nothing to test + } + + @Test + @UnitTestMethod(target = AttributeUpdateEvent.class, name = "getPreviousValue", args = {}) + public void testGetPreviousValue() { + + for (int i = 0; i < 20; i++) { + PersonId personId = new PersonId(345); + AttributeId attributeId = TestAttributeId.INT_0; + Integer previousValue = i; + Integer currentValue = 45; + AttributeUpdateEvent attributeUpdateEvent = new AttributeUpdateEvent(personId, attributeId, previousValue, currentValue); + Integer actualValue = attributeUpdateEvent.getPreviousValue(); + assertEquals(previousValue, actualValue); + } + } + + @Test + @UnitTestMethod(target = AttributeUpdateEvent.class, name = "getCurrentValue", args = {}) + public void testGetCurrentValue() { + for (int i = 0; i < 20; i++) { + PersonId personId = new PersonId(345); + AttributeId attributeId = TestAttributeId.INT_0; + Integer previousValue = 12; + Integer currentValue = i; + AttributeUpdateEvent attributeUpdateEvent = new AttributeUpdateEvent(personId, attributeId, previousValue, currentValue); + Integer actualValue = attributeUpdateEvent.getCurrentValue(); + assertEquals(currentValue, actualValue); + } + } +} diff --git a/gcm3/src/test/java/plugins/partitions/testsupport/attributes/support/AT_AttributeDefinition.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/AT_AttributeDefinition.java similarity index 89% rename from gcm3/src/test/java/plugins/partitions/testsupport/attributes/support/AT_AttributeDefinition.java rename to gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/AT_AttributeDefinition.java index aca45c187..7e00b9284 100644 --- a/gcm3/src/test/java/plugins/partitions/testsupport/attributes/support/AT_AttributeDefinition.java +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/AT_AttributeDefinition.java @@ -1,4 +1,4 @@ -package plugins.partitions.testsupport.attributes.support; +package gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; @@ -7,15 +7,13 @@ import org.junit.jupiter.api.Test; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; +import util.annotations.UnitTestMethod; import util.errors.ContractException; -@UnitTest(target = AttributeDefinition.class) public final class AT_AttributeDefinition { @Test - @UnitTestMethod(name = "builder", args = {}) + @UnitTestMethod(target = AttributeDefinition.class,name = "builder", args = {}) public void testBuilder() { assertNotNull(AttributeDefinition.builder()); } @@ -61,7 +59,7 @@ public void testSetDefaultValue() { } @Test - @UnitTestMethod(name = "getDefaultValue", args = {}) + @UnitTestMethod(target = AttributeDefinition.class,name = "getDefaultValue", args = {}) public void testGetDefaultValue() { AttributeDefinition attributeDefinition = AttributeDefinition.builder().setDefaultValue(12).setType(Integer.class).build(); assertEquals(12, attributeDefinition.getDefaultValue()); @@ -71,7 +69,7 @@ public void testGetDefaultValue() { } @Test - @UnitTestMethod(name = "getType", args = {}) + @UnitTestMethod(target = AttributeDefinition.class,name = "getType", args = {}) public void testGetType() { AttributeDefinition attributeDefinition = AttributeDefinition.builder().setDefaultValue(12).setType(Integer.class).build(); assertEquals(Integer.class, attributeDefinition.getType()); @@ -82,7 +80,7 @@ public void testGetType() { } @Test - @UnitTestMethod(name = "hashCode", args = {}) + @UnitTestMethod(target = AttributeDefinition.class,name = "hashCode", args = {}) public void testHashCode() { @@ -94,7 +92,7 @@ public void testHashCode() { } @Test - @UnitTestMethod(name = "equals", args = { Object.class }) + @UnitTestMethod(target = AttributeDefinition.class,name = "equals", args = { Object.class }) public void testEquals() { AttributeDefinition attributeDefinition1 = AttributeDefinition.builder().setDefaultValue(12).setType(Integer.class).build(); AttributeDefinition attributeDefinition2 = AttributeDefinition.builder().setDefaultValue(13).setType(Integer.class).build(); @@ -115,7 +113,7 @@ public void testEquals() { } @Test - @UnitTestMethod(name = "toString", args = {}) + @UnitTestMethod(target = AttributeDefinition.class,name = "toString", args = {}) public void testToString() { AttributeDefinition attributeDefinition = AttributeDefinition.builder().setDefaultValue(12).setType(Integer.class).build(); String expectedValue = "AttributeDefinition [type=class java.lang.Integer, defaultValue=12]"; diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/AT_AttributeError.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/AT_AttributeError.java new file mode 100644 index 000000000..c9fdb2ae3 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/AT_AttributeError.java @@ -0,0 +1,28 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestMethod; + +public class AT_AttributeError { + @Test + @UnitTestMethod(target = AttributeError.class, name = "getDescription", args = {}) + public void test() { + // show that each description is a unique, non-null and non-empty string + Set descriptions = new LinkedHashSet<>(); + for (AttributeError attributeError : AttributeError.values()) { + String description = attributeError.getDescription(); + assertNotNull(description, "null description for " + attributeError); + assertTrue(description.length() > 0, "empty string for " + attributeError); + boolean unique = descriptions.add(description); + assertTrue(unique, "description for " + attributeError + " is not unique"); + } + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/AT_AttributeFilter.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/AT_AttributeFilter.java new file mode 100644 index 000000000..c5c1bd590 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/AT_AttributeFilter.java @@ -0,0 +1,394 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.partitions.PartitionsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.partitions.datamanagers.PartitionsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.Equality; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.FilterSensitivity; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters.Filter; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.PartitionsTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.PartitionsTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.TestPartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.AttributesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.AttributesPlugin; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.AttributesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.events.AttributeUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePlugin; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeoplePluginData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonRange; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; +import util.wrappers.MultiKey; + +public final class AT_AttributeFilter { + + @Test + @UnitTestConstructor(target = AttributeFilter.class, args = { AttributeId.class, Equality.class, Object.class }) + public void testConstructor() { + // nothing to test + } + + private static enum LocalAttributeId implements AttributeId { + DATA_ID + } + + private static class Data { + private final int value; + + public Data(int value) { + this.value = value; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + value; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + if (value != other.value) { + return false; + } + return true; + } + } + + @Test + @UnitTestMethod(target = AttributeFilter.class, name = "validate", args = { PartitionsContext.class }) + public void testValidate() { + int initialPopulation = 100; + + List plugins = new ArrayList<>(); + // define some person attributes + final AttributesPluginData.Builder attributesBuilder = AttributesPluginData.builder(); + for (final TestAttributeId testAttributeId : TestAttributeId.values()) { + attributesBuilder.defineAttribute(testAttributeId, testAttributeId.getAttributeDefinition()); + } + AttributeDefinition attributeDefinition = AttributeDefinition.builder().setDefaultValue(new Data(7)) + .setType(Data.class).build(); + attributesBuilder.defineAttribute(LocalAttributeId.DATA_ID, attributeDefinition); + + Plugin attributesPlugin = AttributesPlugin.getAttributesPlugin(attributesBuilder.build()); + + Plugin partitionsPlugin = PartitionsPlugin.builder()// + .setPartitionsPluginData(PartitionsPluginData.builder().build())// + // .addPluginDependency(AttributesPluginId.PLUGIN_ID)// + .getPartitionsPlugin(); + + plugins.add(attributesPlugin); + + final PeoplePluginData.Builder peopleBuilder = PeoplePluginData.builder(); + peopleBuilder.addPersonRange(new PersonRange(0, initialPopulation - 1)); + + PeoplePluginData peoplePluginData = peopleBuilder.build(); + Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(peoplePluginData); + plugins.add(peoplePlugin); + + WellState wellState = WellState.builder().setSeed(7698506335486677498L).build(); + plugins.add(StochasticsPlugin + .getStochasticsPlugin(StochasticsPluginData.builder().setMainRNGState(wellState).build())); + + plugins.add(partitionsPlugin); + + // and add the action plugin to the engine + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + // if the filter's attribute id is null + ContractException contractException = assertThrows(ContractException.class, + () -> new AttributeFilter(null, Equality.EQUAL, false).validate(testPartitionsContext)); + assertEquals(AttributeError.NULL_ATTRIBUTE_ID, contractException.getErrorType()); + + // if the filter's equality operator is null + contractException = assertThrows(ContractException.class, + () -> new AttributeFilter(TestAttributeId.BOOLEAN_0, null, false).validate(testPartitionsContext)); + assertEquals(PartitionError.NULL_EQUALITY_OPERATOR, contractException.getErrorType()); + + // if the filter's value is null + contractException = assertThrows(ContractException.class, + () -> new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, null) + .validate(testPartitionsContext)); + assertEquals(AttributeError.NULL_ATTRIBUTE_VALUE, contractException.getErrorType()); + + // if the filter's value is incompatible with the attribute + // definition associated with the filter's attribute id. + contractException = assertThrows(ContractException.class, + () -> new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, 5) + .validate(testPartitionsContext)); + assertEquals(AttributeError.INCOMPATIBLE_VALUE, contractException.getErrorType()); + + // if the filter's value is not a COMPARABLE when the filter's + // equality operator is not EQUALS or NOT_EQUALS. + + contractException = assertThrows(ContractException.class, + () -> new AttributeFilter(LocalAttributeId.DATA_ID, Equality.GREATER_THAN, new Data(12)) + .validate(testPartitionsContext)); + assertEquals(PartitionError.NON_COMPARABLE_ATTRIBUTE, contractException.getErrorType()); + + })); + + TestPluginData testPluginData = pluginBuilder.build(); + plugins.add(TestPlugin.getTestPlugin(testPluginData)); + + TestSimulation.builder().addPlugins(plugins).build().execute(); + + } + + @Test + @UnitTestMethod(target = AttributeFilter.class, name = "evaluate", args = { PartitionsContext.class, + PersonId.class }) + public void testEvaluate() { + Factory factory = PartitionsTestPluginFactory.factory(100, 2853953940626718331L, (c) -> { + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + for (PersonId personId : peopleDataManager.getPeople()) { + boolean value = randomGenerator.nextBoolean(); + attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, value); + boolean expected = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); + boolean actual = filter.evaluate(testPartitionsContext, personId); + assertEquals(expected, actual); + } + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition: if the context is null */ + assertThrows(RuntimeException.class, () -> { + Factory factory2 = PartitionsTestPluginFactory.factory(100, 1011872226453537614L, (c) -> { + Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); + filter.evaluate(null, new PersonId(0)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + + /* precondition: if the person id is null */ + assertThrows(RuntimeException.class, () -> { + + Factory factory2 = PartitionsTestPluginFactory.factory(100, 6858667758520667469L, (c) -> { + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); + filter.evaluate(testPartitionsContext, null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + + /* precondition: if the person id is unknown */ + assertThrows(RuntimeException.class, () -> { + Factory factory2 = PartitionsTestPluginFactory.factory(100, 9106972672436024633L, (c) -> { + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); + filter.evaluate(testPartitionsContext, new PersonId(123412342)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + + } + + @Test + @UnitTestMethod(target = AttributeFilter.class, name = "getFilterSensitivities", args = {}) + public void testGetFilterSensitivities() { + + Factory factory = PartitionsTestPluginFactory.factory(100, 3455263917994200075L, (c) -> { + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + // create an attribute filter + Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, false); + + /* + * show the filter has a single sensitivity + */ + Set> filterSensitivities = filter.getFilterSensitivities(); + assertNotNull(filterSensitivities); + assertEquals(filterSensitivities.size(), 1); + + /* + * show that this sensitivity is associated with AttributeUpdateEvent events. + */ + FilterSensitivity filterSensitivity = filterSensitivities.iterator().next(); + assertEquals(AttributeUpdateEvent.class, filterSensitivity.getEventClass()); + + /* + * Show that the sensitivity requires refresh for AttributeUpdateEvent events if + * and only if the attribute ids are equal and the event has different previous + * and current values. + */ + PersonId personId = new PersonId(0); + + AttributeUpdateEvent attributeUpdateEvent = new AttributeUpdateEvent(personId, TestAttributeId.BOOLEAN_0, + false, true); + + assertTrue(filterSensitivity.requiresRefresh(testPartitionsContext, attributeUpdateEvent).isPresent()); + + attributeUpdateEvent = new AttributeUpdateEvent(personId, TestAttributeId.BOOLEAN_0, false, false); + + assertFalse(filterSensitivity.requiresRefresh(testPartitionsContext, attributeUpdateEvent).isPresent()); + + attributeUpdateEvent = new AttributeUpdateEvent(personId, TestAttributeId.BOOLEAN_1, false, true); + + assertFalse(filterSensitivity.requiresRefresh(testPartitionsContext, attributeUpdateEvent).isPresent()); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = AttributeFilter.class, name = "getValue", args = {}) + public void testGetValue() { + for (int i = 0; i < 10; i++) { + AttributeFilter attributeFilter = new AttributeFilter(TestAttributeId.INT_0, Equality.EQUAL, i); + assertEquals(i, attributeFilter.getValue()); + } + } + + @Test + @UnitTestMethod(target = AttributeFilter.class, name = "getEquality", args = {}) + public void testGetEquality() { + for (Equality equality : Equality.values()) { + AttributeFilter attributeFilter = new AttributeFilter(TestAttributeId.INT_0, equality, 25); + assertEquals(equality, attributeFilter.getEquality()); + } + } + + @Test + @UnitTestMethod(target = AttributeFilter.class, name = "getAttributeId", args = {}) + public void testGetAttributeId() { + for (TestAttributeId testAttributeId : TestAttributeId.values()) { + AttributeFilter attributeFilter = new AttributeFilter(testAttributeId, Equality.EQUAL, 25); + assertEquals(testAttributeId, attributeFilter.getAttributeId()); + } + } + + private AttributeFilter getRandomAttributeFilter(long seed) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + TestAttributeId testAttributeId = TestAttributeId.getRandomAttributeId(randomGenerator); + Object propertyValue = testAttributeId.getRandomPropertyValue(randomGenerator); + Equality randomEquality = Equality.getRandomEquality(randomGenerator); + return new AttributeFilter(testAttributeId, randomEquality, propertyValue); + } + + @Test + @UnitTestMethod(target = AttributeFilter.class, name = "equals", args = { Object.class }) + public void testEquals() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6412971079328158580L); + + // never equal to null + for (int i = 0; i < 30; i++) { + AttributeFilter attributeFilter = getRandomAttributeFilter(randomGenerator.nextLong()); + assertFalse(attributeFilter.equals(null)); + } + + // reflexive + for (int i = 0; i < 30; i++) { + AttributeFilter attributeFilter = getRandomAttributeFilter(randomGenerator.nextLong()); + assertTrue(attributeFilter.equals(attributeFilter)); + } + + // symmetric, transitive, consistent + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + AttributeFilter attributeFilter1 = getRandomAttributeFilter(seed); + AttributeFilter attributeFilter2 = getRandomAttributeFilter(seed); + for (int j = 0; j < 5; j++) { + assertTrue(attributeFilter1.equals(attributeFilter2)); + assertTrue(attributeFilter2.equals(attributeFilter1)); + } + } + + // different inputs yield non-equal objects + Set attributeFilters = new LinkedHashSet<>(); + Set multiKeys = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + AttributeFilter attributeFilter = getRandomAttributeFilter(randomGenerator.nextLong()); + attributeFilters.add(attributeFilter); + MultiKey multiKey = new MultiKey(attributeFilter.getAttributeId(), attributeFilter.getEquality(), + attributeFilter.getValue()); + multiKeys.add(multiKey); + } + + assertEquals(multiKeys.size(), attributeFilters.size()); + + } + + @Test + @UnitTestMethod(target = AttributeFilter.class, name = "hashCode", args = {}) + public void testHashCode() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5562725555946491304L); + + // equal objects have equal hash codes + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + AttributeFilter attributeFilter1 = getRandomAttributeFilter(seed); + AttributeFilter attributeFilter2 = getRandomAttributeFilter(seed); + assertEquals(attributeFilter1, attributeFilter2); + assertEquals(attributeFilter1.hashCode(), attributeFilter2.hashCode()); + } + + // hash codes are reasonably distributed + Set attributeFilters = new LinkedHashSet<>(); + for (int i = 0; i < 1000; i++) { + AttributeFilter attributeFilter = getRandomAttributeFilter(randomGenerator.nextLong()); + attributeFilters.add(attributeFilter); + } + + // There will be a fairly high collision rate since 1/3 of the attribute + // properties are Booleans + assertTrue(attributeFilters.size() > 675); + + } + + @Test + @UnitTestMethod(target = AttributeFilter.class, name = "toString", args = {}) + public void testToString() { + AttributeFilter attributeFilter = new AttributeFilter(TestAttributeId.INT_0, Equality.EQUAL, 25); + String actualValue = attributeFilter.toString(); + String expectedValue = "AttributeFilter [attributeId=INT_0, value=25, equality=EQUAL, attributesDataManager=null]"; + assertEquals(expectedValue, actualValue); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/AT_AttributeLabeler.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/AT_AttributeLabeler.java new file mode 100644 index 000000000..0b4cc02b0 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/AT_AttributeLabeler.java @@ -0,0 +1,210 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.LabelerSensitivity; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.FunctionalAttributeLabeler; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.PartitionsTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.PartitionsTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.TestPartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.AttributesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.events.AttributeUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public final class AT_AttributeLabeler { + + @Test + @UnitTestConstructor(target = AttributeLabeler.class, args = { AttributeId.class }) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = AttributeLabeler.class, name = "getLabelerSensitivities", args = {}) + public void testGetLabelerSensitivities() { + /* + * Get the labeler sensitivities and show that they are consistent with their + * documented behaviors. + */ + + AttributeLabeler attributeLabeler = new FunctionalAttributeLabeler(TestAttributeId.BOOLEAN_0, (c) -> null); + + Set> labelerSensitivities = attributeLabeler.getLabelerSensitivities(); + + // show that there is exactly one sensitivity + assertEquals(1, labelerSensitivities.size()); + + // show that the sensitivity is associated with + // AttributeUpdateEvent + LabelerSensitivity labelerSensitivity = labelerSensitivities.iterator().next(); + assertEquals(AttributeUpdateEvent.class, labelerSensitivity.getEventClass()); + + // show that the sensitivity will return the person id from a + // AttributeUpdateEvent + PersonId personId = new PersonId(56); + AttributeUpdateEvent attributeUpdateEvent = new AttributeUpdateEvent(personId, TestAttributeId.BOOLEAN_0, false, + true); + Optional optional = labelerSensitivity.getPersonId(attributeUpdateEvent); + assertTrue(optional.isPresent()); + assertEquals(personId, optional.get()); + + } + + @Test + @UnitTestMethod(target = AttributeLabeler.class, name = "getCurrentLabel", args = { PartitionsContext.class, + PersonId.class }) + public void testGetCurrentLabel() { + // build an attribute function + Function function = (c) -> { + Boolean value = (Boolean) c; + if (value) { + return "A"; + } + return "B"; + }; + + AttributeLabeler attributeLabeler = new FunctionalAttributeLabeler(TestAttributeId.BOOLEAN_0, function); + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + /* + * + */ + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + List people = peopleDataManager.getPeople(); + for (PersonId personId : people) { + + // get the person's attribute value and apply the function + // directly + Boolean b0 = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); + Object expectedLabel = function.apply(b0); + + // get the label from the person id + Object actualLabel = attributeLabeler.getCurrentLabel(testPartitionsContext, personId); + + // show that the two labels are equal + assertEquals(expectedLabel, actualLabel); + + } + })); + + // test preconditions + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + // if the person does not exist + ContractException contractException = assertThrows(ContractException.class, + () -> attributeLabeler.getCurrentLabel(testPartitionsContext, new PersonId(100000))); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + // if the person id is null + contractException = assertThrows(ContractException.class, + () -> attributeLabeler.getCurrentLabel(testPartitionsContext, null)); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + })); + + TestPluginData testPluginData = pluginBuilder.build(); + + Factory factory = PartitionsTestPluginFactory.factory(10, 4676319446289433016L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = AttributeLabeler.class, name = "getPastLabel", args = { PartitionsContext.class, + Event.class }) + public void testGetPastLabel() { + Function function = (c) -> { + return c; + }; + + AttributeLabeler attributeLabeler = new FunctionalAttributeLabeler(TestAttributeId.INT_0, function); + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8705700576614764378L); + + /* + * + */ + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); + List people = peopleDataManager.getPeople(); + + AttributeUpdateEvent event; + for (PersonId personId : people) { + + // get the person's attribute value and apply the function + // directly + int prev = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_0); + int next = randomGenerator.nextInt(100); + attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_0, next); + event = new AttributeUpdateEvent(personId, TestAttributeId.INT_0, prev, next); + Object expectedLabel = function.apply(prev); + + // get the label from the person id + Object actualLabel = attributeLabeler.getPastLabel(testPartitionsContext, event); + + // show that the two labels are equal + assertEquals(expectedLabel, actualLabel); + + } + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = PartitionsTestPluginFactory.factory(10, 4161680035971681080L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = AttributeLabeler.class, name = "getId", args = {}) + public void testGetId() { + for (TestAttributeId testAttributeId : TestAttributeId.values()) { + assertEquals(testAttributeId, new FunctionalAttributeLabeler(testAttributeId, (c) -> null).getId()); + } + } + + @Test + @UnitTestMethod(target = AttributeLabeler.class, name = "toString", args = {}) + public void testToString() { + FunctionalAttributeLabeler functionalAttributeLabeler = new FunctionalAttributeLabeler( + TestAttributeId.BOOLEAN_0, (c) -> null); + String actualValue = functionalAttributeLabeler.toString(); + + String expectedValue = "AttributeLabeler [attributeId=BOOLEAN_0]"; + + assertEquals(expectedValue, actualValue); + } + +} diff --git a/gcm3/src/test/java/plugins/partitions/testsupport/attributes/support/AT_SimpleAttributeId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/AT_SimpleAttributeId.java similarity index 86% rename from gcm3/src/test/java/plugins/partitions/testsupport/attributes/support/AT_SimpleAttributeId.java rename to gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/AT_SimpleAttributeId.java index f5bb60fb7..edbde4118 100644 --- a/gcm3/src/test/java/plugins/partitions/testsupport/attributes/support/AT_SimpleAttributeId.java +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/AT_SimpleAttributeId.java @@ -1,4 +1,4 @@ -package plugins.partitions.testsupport.attributes.support; +package gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; @@ -11,15 +11,13 @@ import org.junit.jupiter.api.Test; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; -@UnitTest(target = AttributeId.class) public class AT_SimpleAttributeId { @Test - @UnitTestConstructor(args = { Object.class }) + @UnitTestConstructor(target = SimpleAttributeId.class, args = { Object.class }) public void testConstructor() { assertNotNull(new SimpleAttributeId(5)); @@ -27,7 +25,7 @@ public void testConstructor() { } @Test - @UnitTestMethod(name = "toString", args = {}) + @UnitTestMethod(target = SimpleAttributeId.class, name = "toString", args = {}) public void testToString() { /* * Show that the toString of the SimpleAttributeId equals its input's @@ -41,7 +39,7 @@ public void testToString() { } @Test - @UnitTestMethod(name = "toString", args = {}) + @UnitTestMethod(target = SimpleAttributeId.class, name = "equals", args = { Object.class }) public void testEquals() { SimpleAttributeId id_1 = new SimpleAttributeId(2); SimpleAttributeId id_2 = new SimpleAttributeId(5); @@ -109,7 +107,7 @@ public void testEquals() { } @Test - @UnitTestMethod(name = "hashCode", args = {}) + @UnitTestMethod(target = SimpleAttributeId.class, name = "hashCode", args = {}) public void testHashCode() { // equal objects have equal hash codes @@ -118,7 +116,7 @@ public void testHashCode() { SimpleAttributeId s2 = new SimpleAttributeId(i); assertEquals(s1.hashCode(), s2.hashCode()); } - + Set hashCodes = new LinkedHashSet<>(); for (int i = 0; i < 30; i++) { boolean unique = hashCodes.add(new SimpleAttributeId(i).hashCode()); diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/AT_TestAttributeId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/AT_TestAttributeId.java new file mode 100644 index 000000000..cef0e7b6e --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/partitions/testsupport/attributes/support/AT_TestAttributeId.java @@ -0,0 +1,100 @@ +package gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.attributes.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestMethod; +import util.random.RandomGeneratorProvider; +import util.wrappers.MutableInteger; + +public class AT_TestAttributeId { + + @Test + @UnitTestMethod(target = TestAttributeId.class,name = "getUnknownAttributeId", args = {}) + public void testGetUnknownAttributeId() { + /* + * Show that a generated unknown attribute id is not null and not a + * member of the enum + */ + Set attributeIds = new LinkedHashSet<>(); + for (int i = 0; i < 30; i++) { + AttributeId unknownAttributeId = TestAttributeId.getUnknownAttributeId(); + assertNotNull(unknownAttributeId); + boolean unique = attributeIds.add(unknownAttributeId); + assertTrue(unique); + for (TestAttributeId testAttributeId : TestAttributeId.values()) { + assertNotEquals(testAttributeId, unknownAttributeId); + } + } + } + + @Test + @UnitTestMethod(target = TestAttributeId.class,name = "getRandomAttributeId", args = {RandomGenerator.class}) + public void testGetRandomAttributeId() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8197974538085999024L); + Map counters = new LinkedHashMap<>(); + for(TestAttributeId testAttributeId : TestAttributeId.values()) { + counters.put(testAttributeId, new MutableInteger()); + } + + for (int i = 0; i < 6000; i++) { + AttributeId attributeId = TestAttributeId.getRandomAttributeId(randomGenerator); + counters.get(attributeId).increment(); + } + + for(TestAttributeId testAttributeId : TestAttributeId.values()) { + int count = counters.get(testAttributeId).getValue(); + assertTrue(count>900); + assertTrue(count<1100); + } + + } + + + @Test + @UnitTestMethod(target = TestAttributeId.class,name = "getAttributeDefinition", args = {}) + public void testGetAttributeDefinition() { + // show that each member has an attribute definition + for (TestAttributeId testAttributeId : TestAttributeId.values()) { + assertNotNull(testAttributeId.getAttributeDefinition()); + } + } + + @Test + @UnitTestMethod(target = TestAttributeId.class,name = "getRandomPropertyValue", args = { RandomGenerator.class }) + public void testGetRandomPropertyValue() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(675234329644922354L); + + /* + * Show that randomly generated values are compatible with the + * associated property definition. Show that the values are reasonably + * unique + */ + for (TestAttributeId testAttributeId : TestAttributeId.values()) { + AttributeDefinition attributeDefinition = testAttributeId.getAttributeDefinition(); + Set values = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + Object propertyValue = testAttributeId.getRandomPropertyValue(randomGenerator); + values.add(propertyValue); + assertTrue(attributeDefinition.getType().isAssignableFrom(propertyValue.getClass())); + } + // show that the values are reasonable unique + if (attributeDefinition.getType() != Boolean.class) { + assertTrue(values.size() > 10); + } else { + assertEquals(2, values.size()); + } + } + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/AT_PeoplePlugin.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/AT_PeoplePlugin.java new file mode 100644 index 000000000..19598b54a --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/AT_PeoplePlugin.java @@ -0,0 +1,30 @@ +package gov.hhs.aspr.ms.gcm.plugins.people; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeoplePluginData; +import util.annotations.UnitTestMethod; + +public class AT_PeoplePlugin { + + + @Test + @UnitTestMethod(target = PeoplePlugin.class,name = "getPeoplePlugin", args = { PeoplePluginData.class }) + public void testGetPlugin() { + + + PeoplePluginData peoplePluginData = PeoplePluginData.builder().build(); + Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(peoplePluginData); + + assertTrue(peoplePlugin.getPluginDatas().contains(peoplePluginData)); + assertEquals(PeoplePluginId.PLUGIN_ID, peoplePlugin.getPluginId()); + + assertTrue(peoplePlugin.getPluginDependencies().isEmpty()); + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/AT_PeoplePluginId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/AT_PeoplePluginId.java new file mode 100644 index 000000000..74869de32 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/AT_PeoplePluginId.java @@ -0,0 +1,16 @@ +package gov.hhs.aspr.ms.gcm.plugins.people; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestField; + +public class AT_PeoplePluginId { + + @Test + @UnitTestField(target = PeoplePluginId.class, name = "PLUGIN_ID") + public void testPluginId() { + assertNotNull(PeoplePluginId.PLUGIN_ID); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/datamanagers/AT_PeopleDataManager.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/datamanagers/AT_PeopleDataManager.java new file mode 100644 index 000000000..9ad23ad1f --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/datamanagers/AT_PeopleDataManager.java @@ -0,0 +1,862 @@ +package gov.hhs.aspr.ms.gcm.plugins.people.datamanagers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Consumer; + +import org.apache.commons.math3.util.FastMath; +import org.apache.commons.math3.util.Pair; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; +import gov.hhs.aspr.ms.gcm.nucleus.EventFilter; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.Simulation; +import gov.hhs.aspr.ms.gcm.nucleus.SimulationState; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.runcontinuityplugin.RunContinuityPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.runcontinuityplugin.RunContinuityPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestOutputConsumer; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePlugin; +import gov.hhs.aspr.ms.gcm.plugins.people.events.PersonAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.events.PersonImminentAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.events.PersonImminentRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.events.PersonRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonConstructionData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonRange; +import gov.hhs.aspr.ms.gcm.plugins.people.testsupport.PeopleTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.people.testsupport.PeopleTestPluginFactory.Factory; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + +public final class AT_PeopleDataManager { + /** + * Demonstrates that the data manager's initial state reflects its plugin data + */ + @Test + @UnitTestMethod(target = PeopleDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testStateInitialization() { + + // add a few people with gaps between id values + int numberOfPeople = 5; + PeoplePluginData.Builder peoplePluginDataBuilder = PeoplePluginData.builder(); + Set expectedPersonIds = new LinkedHashSet<>(); + for (int i = 0; i < numberOfPeople; i++) { + PersonId personId = new PersonId(i * 3 + 10); + peoplePluginDataBuilder.addPersonRange(new PersonRange(personId.getValue(), personId.getValue())); + expectedPersonIds.add(personId); + } + + PeoplePluginData peoplePluginData = peoplePluginDataBuilder.build(); + + // add an actor to test the people were properly loaded into the person + // data manager + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + assertEquals(numberOfPeople, peopleDataManager.getPopulationCount()); + Set actualPersonIds = new LinkedHashSet<>(peopleDataManager.getPeople()); + assertEquals(expectedPersonIds, actualPersonIds); + + // show that the person id limit has the correct value + int maxPersonIndex = Integer.MIN_VALUE; + for (PersonId personId : expectedPersonIds) { + maxPersonIndex = FastMath.max(maxPersonIndex, personId.getValue()); + } + assertEquals(maxPersonIndex + 1, peopleDataManager.getPersonIdLimit()); + + // show that the people who exist meet expectations + Set peopleByExistence = new LinkedHashSet<>(); + for (int i = 0; i < maxPersonIndex + 10; i++) { + PersonId personId = new PersonId(i); + if (peopleDataManager.personExists(personId)) { + peopleByExistence.add(personId); + } + } + assertEquals(expectedPersonIds, peopleByExistence); + + })); + TestPluginData testPluginData = pluginBuilder.build(); + + Factory factory = PeopleTestPluginFactory// + .factory(6970812715559334185L, testPluginData)// + .setPeoplePluginData(peoplePluginData); + + TestSimulation.builder()// + .addPlugins(factory.getPlugins())// + .build()// + .execute(); + + /** + * precondition test: if the plugin data person assignment time exceeds the + * start time of the simulation + */ + + ContractException contractException = assertThrows(ContractException.class, () -> { + PeoplePluginData peoplePluginData2 = PeoplePluginData.builder()// + .setAssignmentTime(2.0)// + .build(); + + Factory factory2 = PeopleTestPluginFactory.factory(1054042752863257441L, testPluginData)// + .setPeoplePluginData(peoplePluginData2); + + SimulationState simulationState = SimulationState.builder()// + .setStartTime(1.0)// + .build(); + + TestSimulation.builder()// + .addPlugins(factory2.getPlugins())// + .setSimulationState(simulationState)// + .build()// + .execute(); + }); + assertEquals(PersonError.PERSON_ASSIGNMENT_TIME_IN_FUTURE, contractException.getErrorType()); + + } + + /** + * Demonstrates that the data manager produces plugin data that reflects its + * final state + */ + @Test + @UnitTestMethod(target = PeopleDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testStateFinalization() { + + PeoplePluginData.Builder peoplePluginDataBuilder = PeoplePluginData.builder(); + PeoplePluginData peoplePluginData = peoplePluginDataBuilder.build(); + Set expectedPersonIds = new LinkedHashSet<>(); + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // add 10 people to the data manager + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PersonConstructionData personConstructionData = PersonConstructionData.builder().build(); + for (int i = 0; i < 10; i++) { + PersonId personId = peopleDataManager.addPerson(personConstructionData); + expectedPersonIds.add(personId); + } + })); + + // show that the plugin data contains what we defined + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = PeopleTestPluginFactory// + .factory(6970812715559334185L, testPluginData)// + .setPeoplePluginData(peoplePluginData); + + TestOutputConsumer testOutputConsumer = TestSimulation.builder()// + .addPlugins(factory.getPlugins())// + .setProduceSimulationStateOnHalt(true)// + .setSimulationHaltTime(2)// + .build()// + .execute(); + + Map outputItems = testOutputConsumer.getOutputItemMap(PeoplePluginData.class); + assertEquals(1, outputItems.size()); + PeoplePluginData actualPluginData = outputItems.keySet().iterator().next(); + + PeoplePluginData.Builder expectedBuilder = PeoplePluginData.builder(); + for (PersonId personId : expectedPersonIds) { + expectedBuilder.addPersonRange(new PersonRange(personId.getValue(), personId.getValue())); + } + PeoplePluginData expectedPluginData = expectedBuilder.build(); + + assertEquals(expectedPluginData, actualPluginData); + + // show that the plugin data persists after multiple actions + Set expectedPersonIds2 = new LinkedHashSet<>(); + + pluginBuilder = TestPluginData.builder(); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PersonConstructionData personConstructionData = PersonConstructionData.builder().build(); + for (int i = 0; i < 10; i++) { + PersonId personId = peopleDataManager.addPerson(personConstructionData); + expectedPersonIds2.add(personId); + } + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + List people = peopleDataManager.getPeople(); + for (int i = 0; i < 9; i += 2) { + PersonId personId = people.get(i); + peopleDataManager.removePerson(personId); + expectedPersonIds2.remove(personId); + } + })); + + testPluginData = pluginBuilder.build(); + + factory = PeopleTestPluginFactory// + .factory(6970812715559334185L, testPluginData)// + .setPeoplePluginData(peoplePluginData); + + testOutputConsumer = TestSimulation.builder()// + .addPlugins(factory.getPlugins())// + .setProduceSimulationStateOnHalt(true)// + .setSimulationHaltTime(2).build()// + .execute(); + + outputItems = testOutputConsumer.getOutputItemMap(PeoplePluginData.class); + assertEquals(1, outputItems.size()); + actualPluginData = outputItems.keySet().iterator().next(); + + expectedBuilder = PeoplePluginData.builder(); + for (PersonId personId : expectedPersonIds2) { + expectedBuilder.addPersonRange(new PersonRange(personId.getValue(), personId.getValue())); + } + expectedBuilder.setAssignmentTime(1.0); + expectedPluginData = expectedBuilder.build(); + + assertEquals(expectedPluginData, actualPluginData); + } + + @Test + @UnitTestMethod(target = PeopleDataManager.class, name = "personIndexExists", args = { int.class }) + public void testPersonIndexExists() { + + Factory factory = PeopleTestPluginFactory.factory(3328026739613106739L, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + + // initially there are no people despite the initial size + assertFalse(peopleDataManager.personIndexExists(-1)); + assertFalse(peopleDataManager.personIndexExists(0)); + assertFalse(peopleDataManager.personIndexExists(1)); + + // show that we can add a few people and for each the manager will + // indicate that they exist + for (int i = 0; i < 10; i++) { + PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); + assertTrue(peopleDataManager.personIndexExists(personId.getValue())); + } + + // show that people who should not exist, actually don't exist + assertFalse(peopleDataManager.personIndexExists(-1)); + for (int i = 10; i < 20; i++) { + assertFalse(peopleDataManager.personIndexExists(i)); + } + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = PeopleDataManager.class, name = "getPersonIdLimit", args = {}) + public void testGetPersonIdLimit() { + Factory factory = PeopleTestPluginFactory.factory(2489565009155477444L, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + /* + * Initially there are no people despite the initial size, so we expect the + * limit to be zero. + */ + assertEquals(0, peopleDataManager.getPersonIdLimit()); + + // show that the limit increments as PersonId values are added + for (int i = 0; i < 10; i++) { + PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); + assertEquals(personId.getValue() + 1, peopleDataManager.getPersonIdLimit()); + } + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = PeopleDataManager.class, name = "getBoxedPersonId", args = { int.class }) + public void testGetBoxedPersonId() { + Factory factory = PeopleTestPluginFactory.factory(7973222351020835580L, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + // show that the boxed person id is correct + for (int i = 0; i < 10; i++) { + Optional optional = peopleDataManager.getBoxedPersonId(i); + assertFalse(optional.isPresent()); + + PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); + optional = peopleDataManager.getBoxedPersonId(i); + assertTrue(optional.isPresent()); + + PersonId boxedPersonId = optional.get(); + assertEquals(personId, boxedPersonId); + } + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = PeopleDataManager.class, name = "addPerson", args = { PersonConstructionData.class }) + public void testAddPerson() { + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // create containers to hold observations + Set observedPersonIds = new LinkedHashSet<>(); + Set observedImminentPersonIds = new LinkedHashSet<>(); + Set expectedPersonIds = new LinkedHashSet<>(); + + for (int i = 0; i < 10; i++) { + PersonId personId = new PersonId(i); + expectedPersonIds.add(personId); + } + + pluginDataBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + c.subscribe(EventFilter.builder(PersonImminentAdditionEvent.class).build(), + (c2, e) -> observedImminentPersonIds.add(e.personId())); + c.subscribe(EventFilter.builder(PersonAdditionEvent.class).build(), + (c2, e) -> observedPersonIds.add(e.personId())); + })); + + // have the agent add a few people and show they were added + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + for (PersonId expectedPersonId : expectedPersonIds) { + PersonId actualPersonId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); + assertEquals(expectedPersonId, actualPersonId); + assertTrue(peopleDataManager.personExists(actualPersonId)); + } + })); + + // precondition tests + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + ContractException contractException = assertThrows(ContractException.class, + () -> peopleDataManager.addPerson(null)); + assertEquals(PersonError.NULL_PERSON_CONSTRUCTION_DATA, contractException.getErrorType()); + })); + + TestPluginData testPluginData = pluginDataBuilder.build(); + + Factory factory = PeopleTestPluginFactory.factory(3010391631885520624L, testPluginData); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + // show that the expected and actual observations match + assertEquals(expectedPersonIds, observedPersonIds); + assertEquals(expectedPersonIds, observedImminentPersonIds); + } + + @Test + @UnitTestMethod(target = PeopleDataManager.class, name = "personExists", args = { PersonId.class }) + public void testPersonExists() { + + Factory factory = PeopleTestPluginFactory.factory(8692409871861590014L, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + + for (int i = 0; i < 10; i++) { + peopleDataManager.addPerson(PersonConstructionData.builder().build()); + } + + assertFalse(peopleDataManager.personExists(new PersonId(100000))); + + for (int i = 0; i < 10; i++) { + assertTrue(peopleDataManager.personExists(new PersonId(i))); + } + + for (int i = 10; i < 20; i++) { + assertFalse(peopleDataManager.personExists(new PersonId(i))); + } + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = PeopleDataManager.class, name = "getPeople", args = {}) + public void testGetPeople() { + + List expectedPeople = new ArrayList<>(); + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + + // add some people + + for (int i = 0; i < 10; i++) { + PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); + expectedPeople.add(personId); + } + + List actualPeople = peopleDataManager.getPeople(); + assertEquals(expectedPeople.size(), actualPeople.size()); + assertEquals(new LinkedHashSet<>(expectedPeople), new LinkedHashSet<>(actualPeople)); + + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + + // remove a few people + for (int i = 0; i < 5; i++) { + PersonId personId = new PersonId(i); + peopleDataManager.removePerson(personId); + expectedPeople.remove(personId); + } + + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + // show that the removals resulted in the correct people + List actualPeople = peopleDataManager.getPeople(); + assertEquals(expectedPeople.size(), actualPeople.size()); + assertEquals(new LinkedHashSet<>(expectedPeople), new LinkedHashSet<>(actualPeople)); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + + Factory factory = PeopleTestPluginFactory.factory(6955438283727605404L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = PeopleDataManager.class, name = "removePerson", args = { PersonId.class }) + public void testRemovePerson() { + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // add a container to collect the observed removals + Set observedRemovals = new LinkedHashSet<>(); + Set observedImminentRemovals = new LinkedHashSet<>(); + Set expectedRemovals = new LinkedHashSet<>(); + for (int i = 0; i < 5; i++) { + expectedRemovals.add(new PersonId(i)); + } + + // have the observer subscribe to the removals and record them onto the + // observed removals + pluginDataBuilder.addTestActorPlan("observer", new TestActorPlan(1, (c) -> { + c.subscribe(EventFilter.builder(PersonRemovalEvent.class).build(), + (c2, e) -> observedRemovals.add(e.personId())); + c.subscribe(EventFilter.builder(PersonImminentRemovalEvent.class).build(), + (c2, e) -> observedImminentRemovals.add(e.personId())); + })); + + // have the agent add a few people + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + for (int i = 0; i < 10; i++) { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + peopleDataManager.addPerson(PersonConstructionData.builder().build()); + } + })); + + // have the agent remove some people + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + for (int i = 0; i < 5; i++) { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + peopleDataManager.removePerson(new PersonId(i)); + } + })); + + // precondition tests + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(3, (c) -> { + + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + + ContractException contractException = assertThrows(ContractException.class, + () -> peopleDataManager.removePerson(null)); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, + () -> peopleDataManager.removePerson(new PersonId(1000))); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + })); + TestPluginData testPluginData = pluginDataBuilder.build(); + + Factory factory = PeopleTestPluginFactory.factory(8330481544200634026L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + // show that the observed removals match the expected removals + assertEquals(expectedRemovals, observedRemovals); + assertEquals(expectedRemovals, observedImminentRemovals); + } + + @Test + @UnitTestConstructor(target = PeopleDataManager.class, args = { PeoplePluginData.class }) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PeopleDataManager.class, name = "getPopulationCount", args = {}) + public void testGetPopulationCount() { + Factory factory = PeopleTestPluginFactory.factory(8471595108422434117L, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + // show the population count grows as we add people + for (int i = 0; i < 10; i++) { + assertEquals(i + 24, peopleDataManager.getPopulationCount()); + peopleDataManager.addPerson(PersonConstructionData.builder().build()); + } + }); + + // start with a 24 people + PeoplePluginData peoplePluginData = PeoplePluginData.builder().addPersonRange(new PersonRange(12, 35)).build(); + factory.setPeoplePluginData(peoplePluginData); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = PeopleDataManager.class, name = "getPopulationTime", args = {}) + public void testGetPopulationTime() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + + assertEquals(0.0, peopleDataManager.getPopulationTime()); + + // add some people + for (int i = 0; i < 10; i++) { + peopleDataManager.addPerson(PersonConstructionData.builder().build()); + assertEquals(c.getTime(), peopleDataManager.getPopulationTime()); + } + + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + peopleDataManager.removePerson(new PersonId(0)); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + assertEquals(c.getTime(), peopleDataManager.getPopulationTime()); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + peopleDataManager.removePerson(new PersonId(1)); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + assertEquals(c.getTime(), peopleDataManager.getPopulationTime()); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + + Factory factory = PeopleTestPluginFactory.factory(544849633773456332L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = PeopleDataManager.class, name = "getEventFilterForPersonAdditionEvent", args = {}) + public void testGetEventFilterForPersonAdditionEvent() { + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // create containers to hold observations + Set observedPersonIds = new LinkedHashSet<>(); + Set expectedPersonIds = new LinkedHashSet<>(); + + pluginDataBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + EventFilter eventFilter = peopleDataManager.getEventFilterForPersonAdditionEvent(); + c.subscribe(eventFilter, (c2, e) -> observedPersonIds.add(e.personId())); + })); + + // have the agent add a few people and show they were added + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + for (int i = 0; i < 10; i++) { + PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); + expectedPersonIds.add(personId); + } + })); + + // have the observer show that the expected and actual observations + // match + pluginDataBuilder.addTestActorPlan("observer", new TestActorPlan(2, (c) -> { + assertEquals(expectedPersonIds, observedPersonIds); + })); + + TestPluginData testPluginData = pluginDataBuilder.build(); + + Factory factory = PeopleTestPluginFactory.factory(1359354206586648087L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = PeopleDataManager.class, name = "getEventFilterForPersonImminentRemovalEvent", args = {}) + public void testGetEventFilterForPersonImminentRemovalEvent() { + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // add a container to collect the observed removals + Set observedRemovals = new LinkedHashSet<>(); + Set expectedRemovals = new LinkedHashSet<>(); + + // have the observer subscribe to the removals and record them onto the + // observed removals + pluginDataBuilder.addTestActorPlan("observer", new TestActorPlan(1, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + EventFilter eventFilter = peopleDataManager + .getEventFilterForPersonImminentRemovalEvent(); + c.subscribe(eventFilter, (c2, e) -> observedRemovals.add(e.personId())); + })); + + // have the actor add a few people + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + for (int i = 0; i < 10; i++) { + peopleDataManager.addPerson(PersonConstructionData.builder().build()); + } + })); + + // have the actor remove some people + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(3, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + for (int i = 0; i < 5; i++) { + PersonId personId = new PersonId(i); + peopleDataManager.removePerson(personId); + expectedRemovals.add(personId); + } + })); + + // have the observer show the expected and observed events are equal + pluginDataBuilder.addTestActorPlan("observer", new TestActorPlan(4, (c) -> { + assertFalse(expectedRemovals.isEmpty()); + assertEquals(expectedRemovals, observedRemovals); + })); + + TestPluginData testPluginData = pluginDataBuilder.build(); + + Factory factory = PeopleTestPluginFactory.factory(3387041999627132151L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + /** + * Demonstrates that the data manager exhibits run continuity. The state of the + * data manager is not effected by repeatedly starting and stopping the + * simulation. + */ + @Test + @UnitTestMethod(target = PeopleDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testStateContinuity() { + + /* + * Note that we are not testing the content of the plugin datas -- that is + * covered by the other state tests. We show here only that the resulting plugin + * data state is the same without regard to how we break up the run. + */ + + Set pluginDatas = new LinkedHashSet<>(); + pluginDatas.add(testStateContinuity(1)); + pluginDatas.add(testStateContinuity(5)); + pluginDatas.add(testStateContinuity(10)); + + assertEquals(1, pluginDatas.size()); + + } + + /* + * Returns the people plugin data resulting from several people events over + * several days. Attempts to stop and start the simulation by the given number + * of increments. + */ + private String testStateContinuity(int incrementCount) { + String result = null; + + /* + * Build the RunContinuityPluginData with five context consumers that will add + * and remove people over several days + */ + RunContinuityPluginData.Builder continuityBuilder = RunContinuityPluginData.builder(); + + continuityBuilder.addContextConsumer(0.5, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + for (int i = 0; i < 3; i++) { + peopleDataManager.addPerson(PersonConstructionData.builder().build()); + } + }); + + continuityBuilder.addContextConsumer(1.2, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + peopleDataManager.removePerson(new PersonId(0)); + for (int i = 0; i < 3; i++) { + peopleDataManager.addPerson(PersonConstructionData.builder().build()); + } + }); + + continuityBuilder.addContextConsumer(1.8, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + peopleDataManager.removePerson(new PersonId(3)); + peopleDataManager.removePerson(new PersonId(4)); + for (int i = 0; i < 5; i++) { + peopleDataManager.addPerson(PersonConstructionData.builder().build()); + } + }); + + continuityBuilder.addContextConsumer(2.05, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + peopleDataManager.removePerson(new PersonId(1)); + peopleDataManager.removePerson(new PersonId(6)); + peopleDataManager.removePerson(new PersonId(10)); + + for (int i = 0; i < 3; i++) { + peopleDataManager.addPerson(PersonConstructionData.builder().build()); + } + }); + + continuityBuilder.addContextConsumer(4.2, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + + for (int i = 0; i < 3; i++) { + peopleDataManager.addPerson(PersonConstructionData.builder().build()); + } + + c.releaseOutput(peopleDataManager.toString()); + }); + + RunContinuityPluginData runContinuityPluginData = continuityBuilder.build(); + + // Build an empty people plugin data for time zero + PeoplePluginData peoplePluginData = PeoplePluginData.builder().build(); + + // build the initial simulation state data -- time starts at zero + SimulationState simulationState = SimulationState.builder().build(); + + /* + * Run the simulation in one day increments until all the plans in the run + * continuity plugin data have been executed + */ + double haltTime = 0; + double maxTime = Double.NEGATIVE_INFINITY; + for (Pair> pair : runContinuityPluginData.getConsumers()) { + Double time = pair.getFirst(); + maxTime = FastMath.max(maxTime, time); + } + double timeIncrement = maxTime / incrementCount; + while (!runContinuityPluginData.allPlansComplete()) { + haltTime += timeIncrement; + + // build the run continuity plugin + Plugin runContinuityPlugin = RunContinuityPlugin.builder()// + .setRunContinuityPluginData(runContinuityPluginData)// + .build(); + + // build the people plugin + Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(peoplePluginData); + + TestOutputConsumer outputConsumer = new TestOutputConsumer(); + + // execute the simulation so that it produces a people plugin data + Simulation simulation = Simulation.builder()// + .addPlugin(peoplePlugin)// + .addPlugin(runContinuityPlugin)// + .setSimulationHaltTime(haltTime)// + .setRecordState(true)// + .setOutputConsumer(outputConsumer)// + .setSimulationState(simulationState)// + .build();// + simulation.execute(); + + // retrieve the people plugin data + peoplePluginData = outputConsumer.getOutputItem(PeoplePluginData.class).get(); + + // retrieve the simulation state + simulationState = outputConsumer.getOutputItem(SimulationState.class).get(); + + // retrieve the run continuity plugin data + runContinuityPluginData = outputConsumer.getOutputItem(RunContinuityPluginData.class).get(); + + Optional optional = outputConsumer.getOutputItem(String.class); + if (optional.isPresent()) { + result = optional.get(); + } + + } + + assertNotNull(result); + return result; + + } + + @Test + @UnitTestMethod(target = PeopleDataManager.class, name = "getPersonIndexIterator", args = {}) + public void testGetPersonIndexIterator() { + Factory factory = PeopleTestPluginFactory.factory(8471595108422434117L, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + + //show that the iterator yields the list of existing people in the usual order + List expectedPeople = peopleDataManager.getPeople(); + List actualPeople = new ArrayList<>(); + Iterator personIndexIterator = peopleDataManager.getPersonIndexIterator(); + while(personIndexIterator.hasNext()) { + Integer id = personIndexIterator.next(); + Optional optional = peopleDataManager.getBoxedPersonId(id); + assertTrue(optional.isPresent()); + actualPeople.add(optional.get()); + } + assertEquals(expectedPeople, actualPeople); + + }); + + // start with a few people that are not in a contiguous group + PeoplePluginData peoplePluginData = PeoplePluginData.builder()// + .addPersonRange(new PersonRange(12, 35))// + .addPersonRange(new PersonRange(67, 90))// + .build(); + factory.setPeoplePluginData(peoplePluginData); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + @Test + @UnitTestMethod(target = PeopleDataManager.class, name = "toString", args = {}) + public void testToString() { + + TestPluginData.Builder testPluginDataBuilder = TestPluginData.builder(); + + testPluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1,(c)->{ + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + peopleDataManager.addPerson(PersonConstructionData.builder().build()); + })); + testPluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(5,(c)->{ + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + String actualValue = peopleDataManager.toString(); + String expectedValue = "PeopleDataManager [" + + "personIds=[null, null, null, null, null, null, null, null, null, null, null, null, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91], " + + "globalPopulationRecord=PopulationRecord [populationCount=49, assignmentTime=1.0]]"; + assertEquals(expectedValue, actualValue); + })); + + TestPluginData testPluginData = testPluginDataBuilder.build(); + + Factory factory = PeopleTestPluginFactory.factory(7149069907708379711L, testPluginData); + + // start with a few people that are not in a contiguous group + PeoplePluginData peoplePluginData = PeoplePluginData.builder()// + .addPersonRange(new PersonRange(12, 35))// + .addPersonRange(new PersonRange(67, 90))// + .build(); + factory.setPeoplePluginData(peoplePluginData); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + + + + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/datamanagers/AT_PeoplePluginData.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/datamanagers/AT_PeoplePluginData.java new file mode 100644 index 000000000..85350bf88 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/datamanagers/AT_PeoplePluginData.java @@ -0,0 +1,396 @@ +package gov.hhs.aspr.ms.gcm.plugins.people.datamanagers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonRange; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public final class AT_PeoplePluginData { + @Test + @UnitTestMethod(target = PeoplePluginData.class, name = "builder", args = {}) + public void testBuilder() { + assertNotNull(PeoplePluginData.builder()); + } + + @Test + @UnitTestMethod(target = PeoplePluginData.Builder.class, name = "build", args = {}) + public void testBuild() { + + // RandomGenerator randomGenerator = + // RandomGeneratorProvider.getRandomGenerator(1713830743266777795L); + + // show that an empty builder returns an empty set of people + PeoplePluginData peoplePluginData = PeoplePluginData.builder().build(); + assertEquals(0, peoplePluginData.getPersonCount()); + assertTrue(peoplePluginData.getPersonIds().isEmpty()); + assertTrue(peoplePluginData.getPersonRanges().isEmpty()); + + // precondition test: if an invalid person count is set + ContractException contractException = assertThrows(ContractException.class, () -> { + PeoplePluginData.builder().addPersonRange(new PersonRange(5, 19)).setPersonCount(10).build(); + }); + assertEquals(PersonError.INVALID_PERSON_COUNT, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PeoplePluginData.Builder.class, name = "addPersonRange", args = { PersonRange.class }) + public void testAddPersonRange() { + + List expectedPersonRanges = new ArrayList<>(); + expectedPersonRanges.add(new PersonRange(3, 8)); + expectedPersonRanges.add(new PersonRange(12, 18)); + expectedPersonRanges.add(new PersonRange(20, 22)); + + List actualPersonRanges = // + PeoplePluginData.builder()// + .addPersonRange(new PersonRange(12, 15))// + .addPersonRange(new PersonRange(3, 8))// + .addPersonRange(new PersonRange(13, 18))// + .addPersonRange(new PersonRange(20, 22))// + .build()// + .getPersonRanges(); + + assertEquals(expectedPersonRanges, actualPersonRanges); + + expectedPersonRanges = new ArrayList<>(); + expectedPersonRanges.add(new PersonRange(1, 13)); + + actualPersonRanges = PeoplePluginData.builder()// + .addPersonRange(new PersonRange(3, 5))// + .addPersonRange(new PersonRange(1, 5))// + .addPersonRange(new PersonRange(4, 8)).addPersonRange(new PersonRange(9, 13))// + .build()// + .getPersonRanges(); + + assertEquals(expectedPersonRanges, actualPersonRanges); + + // precondition test : if a person range is null + ContractException contractException = assertThrows(ContractException.class, + () -> PeoplePluginData.builder().addPersonRange(null)); + assertEquals(PersonError.NULL_PERSON_RANGE, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = PeoplePluginData.class, name = "getPersonIds", args = {}) + public void testGetPersonIds() { + + List expectedPersonIds = new ArrayList<>(); + for (int i = 3; i <= 8; i++) { + expectedPersonIds.add(new PersonId(i)); + } + for (int i = 12; i <= 18; i++) { + expectedPersonIds.add(new PersonId(i)); + } + for (int i = 20; i <= 22; i++) { + expectedPersonIds.add(new PersonId(i)); + } + + List actualPersonIds = // + PeoplePluginData.builder()// + .addPersonRange(new PersonRange(12, 15))// + .addPersonRange(new PersonRange(3, 8))// + .addPersonRange(new PersonRange(13, 18))// + .addPersonRange(new PersonRange(20, 22))// + .build()// + .getPersonIds(); + + assertEquals(expectedPersonIds, actualPersonIds); + + expectedPersonIds = new ArrayList<>(); + for (int i = 1; i <= 13; i++) { + expectedPersonIds.add(new PersonId(i)); + } + + actualPersonIds = PeoplePluginData.builder()// + .addPersonRange(new PersonRange(3, 5))// + .addPersonRange(new PersonRange(1, 5))// + .addPersonRange(new PersonRange(4, 8)).addPersonRange(new PersonRange(9, 13))// + .build()// + .getPersonIds(); + + assertEquals(expectedPersonIds, actualPersonIds); + + } + + @Test + @UnitTestMethod(target = PeoplePluginData.class, name = "getPersonRanges", args = {}) + public void testGetPersonRanges() { + List expectedPersonRanges = new ArrayList<>(); + expectedPersonRanges.add(new PersonRange(3, 8)); + expectedPersonRanges.add(new PersonRange(12, 18)); + expectedPersonRanges.add(new PersonRange(20, 22)); + + List actualPersonRanges = // + PeoplePluginData.builder()// + .addPersonRange(new PersonRange(12, 15))// + .addPersonRange(new PersonRange(3, 8))// + .addPersonRange(new PersonRange(13, 18))// + .addPersonRange(new PersonRange(20, 22))// + .build()// + .getPersonRanges(); + + assertEquals(expectedPersonRanges, actualPersonRanges); + + expectedPersonRanges = new ArrayList<>(); + expectedPersonRanges.add(new PersonRange(1, 13)); + + actualPersonRanges = PeoplePluginData.builder()// + .addPersonRange(new PersonRange(3, 5))// + .addPersonRange(new PersonRange(1, 5))// + .addPersonRange(new PersonRange(4, 8)).addPersonRange(new PersonRange(9, 13))// + .build()// + .getPersonRanges(); + + assertEquals(expectedPersonRanges, actualPersonRanges); + } + + @Test + @UnitTestMethod(target = PeoplePluginData.Builder.class, name = "setPersonCount", args = { int.class }) + public void testSetPersonCount() { + + // if the builder is empty + assertEquals(0, PeoplePluginData.builder().build().getPersonCount()); + + // if we explicitly set the person count on an empty builder + assertEquals(5, PeoplePluginData.builder().setPersonCount(5).build().getPersonCount()); + + // if we do not explicitly set the person count + int actualPersonCount = PeoplePluginData.builder()// + .addPersonRange(new PersonRange(12, 15))// + .addPersonRange(new PersonRange(3, 8))// + .addPersonRange(new PersonRange(13, 18))// + .addPersonRange(new PersonRange(20, 22))// + .build()// + .getPersonCount(); + assertEquals(23, actualPersonCount); + + // if we explicitly set the person count + actualPersonCount = PeoplePluginData.builder()// + .addPersonRange(new PersonRange(12, 15))// + .addPersonRange(new PersonRange(3, 8))// + .addPersonRange(new PersonRange(13, 18))// + .addPersonRange(new PersonRange(20, 22))// + .setPersonCount(45).build()// + .getPersonCount(); + assertEquals(45, actualPersonCount); + + /* + * precondition : if the person count is explicitly set to a value less than or + * equal to the highest value in an included range + */ + + ContractException contractException = assertThrows(ContractException.class, () -> { + PeoplePluginData.builder()// + .addPersonRange(new PersonRange(12, 15))// + .setPersonCount(15).build(); + });// + assertEquals(PersonError.INVALID_PERSON_COUNT, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = PeoplePluginData.class, name = "getCloneBuilder", args = {}) + public void testGetCloneBuilder() { + + PeoplePluginData pluginData = PeoplePluginData.builder()// + .addPersonRange(new PersonRange(3, 9)).addPersonRange(new PersonRange(8, 12)) + .addPersonRange(new PersonRange(15, 19)).build(); + + PluginData pluginData2 = pluginData.getCloneBuilder().build(); + + assertEquals(pluginData, pluginData2); + } + + @Test + @UnitTestMethod(target = PeoplePluginData.Builder.class, name = "setAssignmentTime", args = { double.class }) + public void testSetAssignmentTime() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2239063975495496234L); + + for (int i = 0; i < 30; i++) { + double expectedAssignmentTime = randomGenerator.nextDouble(); + double actualAssignmentTime = // + PeoplePluginData.builder()// + .addPersonRange(new PersonRange(0, 15))// + .setAssignmentTime(expectedAssignmentTime)// + .build()// + .getAssignmentTime(); + assertEquals(expectedAssignmentTime, actualAssignmentTime); + } + + } + + @Test + @UnitTestMethod(target = PeoplePluginData.class, name = "getPersonCount", args = {}) + public void testGetPersonCount() { + + // if the builder is empty + assertEquals(0, PeoplePluginData.builder().build().getPersonCount()); + + // if we explicitly set the person count on an empty builder + assertEquals(5, PeoplePluginData.builder().setPersonCount(5).build().getPersonCount()); + + // if we do not explicitly set the person count + int actualPersonCount = PeoplePluginData.builder()// + .addPersonRange(new PersonRange(12, 15))// + .addPersonRange(new PersonRange(3, 8))// + .addPersonRange(new PersonRange(13, 18))// + .addPersonRange(new PersonRange(20, 22))// + .build()// + .getPersonCount(); + assertEquals(23, actualPersonCount); + + // if we explicitly set the person count + actualPersonCount = PeoplePluginData.builder()// + .addPersonRange(new PersonRange(12, 15))// + .addPersonRange(new PersonRange(3, 8))// + .addPersonRange(new PersonRange(13, 18))// + .addPersonRange(new PersonRange(20, 22))// + .setPersonCount(45).build()// + .getPersonCount(); + assertEquals(45, actualPersonCount); + + } + + @Test + @UnitTestMethod(target = PeoplePluginData.class, name = "getAssignmentTime", args = {}) + public void testGetAssignmentTime() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2239063975495496234L); + + for (int i = 0; i < 30; i++) { + double expectedAssignmentTime = randomGenerator.nextDouble(); + double actualAssignmentTime = // + PeoplePluginData.builder()// + .addPersonRange(new PersonRange(0, 15))// + .setAssignmentTime(expectedAssignmentTime)// + .build()// + .getAssignmentTime(); + assertEquals(expectedAssignmentTime, actualAssignmentTime); + } + + } + + private PeoplePluginData getRandomPeoplePluginData(long seed) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + PeoplePluginData.Builder builder = PeoplePluginData.builder(); + int low = 1; + int high = 1; + int rangeCount = randomGenerator.nextInt(3) + 1; + + for (int i = 0; i < rangeCount; i++) { + low += randomGenerator.nextInt(10); + high = low + randomGenerator.nextInt(10) + 1; + builder.addPersonRange(new PersonRange(low, high)); + low = high+1; + + } + + builder.setAssignmentTime(randomGenerator.nextDouble() * 100 - 50); + int personCount = high+randomGenerator.nextInt(5)+1; + builder.setPersonCount(personCount); + return builder.build(); + + } + + @Test + @UnitTestMethod(target = PeoplePluginData.class, name = "equals", args = { Object.class }) + public void testEquals() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8980821493557306870L); + + // never equal to null + for (int i = 0; i < 30; i++) { + PeoplePluginData pluginData = getRandomPeoplePluginData(randomGenerator.nextLong()); + assertFalse(pluginData.equals(null)); + } + + // reflexive + for (int i = 0; i < 30; i++) { + PeoplePluginData pluginData = getRandomPeoplePluginData(randomGenerator.nextLong()); + assertTrue(pluginData.equals(pluginData)); + } + + // symmetric, transitive, consistent + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + PeoplePluginData pluginData1 = getRandomPeoplePluginData(seed); + PeoplePluginData pluginData2 = getRandomPeoplePluginData(seed); + for (int j = 0; j < 5; j++) { + assertTrue(pluginData1.equals(pluginData2)); + assertTrue(pluginData2.equals(pluginData1)); + } + } + + // different inputs yield unequal plugin datas + Set set = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + PeoplePluginData pluginData = getRandomPeoplePluginData(randomGenerator.nextLong()); + set.add(pluginData); + } + assertEquals(100, set.size()); + } + + @Test + @UnitTestMethod(target = PeoplePluginData.class, name = "hashCode", args = {}) + public void testHashCode() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6496930019491275913L); + + //equal objects have equal hash codes + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + PeoplePluginData pluginData1 = getRandomPeoplePluginData(seed); + PeoplePluginData pluginData2 = getRandomPeoplePluginData(seed); + + assertEquals(pluginData1 ,pluginData2); + assertEquals(pluginData1.hashCode() ,pluginData2.hashCode()); + + } + + //hash codes are reasonably distributed + Set hashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + PeoplePluginData pluginData = getRandomPeoplePluginData(randomGenerator.nextLong()); + hashCodes.add(pluginData.hashCode()); + } + assertTrue(hashCodes.size()>95); + } + + @Test + @UnitTestMethod(target = PeoplePluginData.class, name = "toString", args = {}) + public void testToString() { + PeoplePluginData pluginData = getRandomPeoplePluginData(8839731936101813165L); + + String actualValue = pluginData.toString(); + + //Expected value validated by inspection + String expectedValue = "PeoplePluginData [data=Data [" + + "personCount=34, " + + "personRanges=[" + + "PersonRange [lowPersonId=4, highPersonId=13], " + + "PersonRange [lowPersonId=19, highPersonId=20], " + + "PersonRange [lowPersonId=27, highPersonId=30]], " + + "assignmentTime=49.458417619948875, " + + "locked=true]]"; + assertEquals(expectedValue, actualValue); + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/events/AT_PersonAdditionEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/events/AT_PersonAdditionEvent.java new file mode 100644 index 000000000..50d4a24ff --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/events/AT_PersonAdditionEvent.java @@ -0,0 +1,47 @@ +package gov.hhs.aspr.ms.gcm.plugins.people.events; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + +public class AT_PersonAdditionEvent { + + @Test + @UnitTestConstructor(target = PersonAdditionEvent.class, args = { PersonId.class }) + public void testConstructor() { + ContractException contractException = assertThrows(ContractException.class, () -> new PersonAdditionEvent(null)); + assertEquals(contractException.getErrorType(), PersonError.NULL_PERSON_ID); + } + + @Test + @UnitTestMethod(target = PersonAdditionEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonAdditionEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonAdditionEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonAdditionEvent.class, name = "personId", args = {}) + public void testPersonId() { + // nothing to test + } + +} \ No newline at end of file diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/events/AT_PersonImminentAdditionEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/events/AT_PersonImminentAdditionEvent.java new file mode 100644 index 000000000..de5db8742 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/events/AT_PersonImminentAdditionEvent.java @@ -0,0 +1,60 @@ +package gov.hhs.aspr.ms.gcm.plugins.people.events; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonConstructionData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + +public class AT_PersonImminentAdditionEvent { + + @Test + @UnitTestConstructor(target = PersonImminentAdditionEvent.class, args = { PersonId.class, PersonConstructionData.class }) + public void testConstructor() { + PersonId personId = new PersonId(0); + PersonConstructionData personConstructionData = PersonConstructionData.builder().build(); + + ContractException contractException = assertThrows(ContractException.class, () -> new PersonImminentAdditionEvent(null, personConstructionData)); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, () -> new PersonImminentAdditionEvent(personId, null)); + assertEquals(PersonError.NULL_PERSON_CONSTRUCTION_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PersonImminentAdditionEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonImminentAdditionEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonImminentAdditionEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonImminentAdditionEvent.class, name = "personId", args = {}) + public void testPersonId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonImminentAdditionEvent.class, name = "personConstructionData", args = {}) + public void testPersonConstructionData() { + // nothing to test + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/events/AT_PersonImminentRemovalEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/events/AT_PersonImminentRemovalEvent.java new file mode 100644 index 000000000..e12a3ffb4 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/events/AT_PersonImminentRemovalEvent.java @@ -0,0 +1,50 @@ +package gov.hhs.aspr.ms.gcm.plugins.people.events; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + +public class AT_PersonImminentRemovalEvent { + + @Test + @UnitTestConstructor(target = PersonImminentRemovalEvent.class, args = { PersonId.class }) + public void testConstructor() { + + // precondition tests + ContractException contractException = assertThrows(ContractException.class, () -> new PersonImminentRemovalEvent(null)); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = PersonImminentRemovalEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonImminentRemovalEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonImminentRemovalEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonImminentRemovalEvent.class, name = "personId", args = {}) + public void testPersonId() { + // nothing to test + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/events/AT_PersonRemovalEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/events/AT_PersonRemovalEvent.java new file mode 100644 index 000000000..346c5befe --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/events/AT_PersonRemovalEvent.java @@ -0,0 +1,48 @@ +package gov.hhs.aspr.ms.gcm.plugins.people.events; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + +public class AT_PersonRemovalEvent { + + @Test + @UnitTestConstructor(target = PersonRemovalEvent.class, args = { PersonId.class }) + public void testConstructor() { + ContractException contractException = assertThrows(ContractException.class, () -> new PersonRemovalEvent(null)); + assertEquals(contractException.getErrorType(), PersonError.NULL_PERSON_ID); + } + + @Test + @UnitTestMethod(target = PersonRemovalEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonRemovalEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonRemovalEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonRemovalEvent.class, name = "personId", args = {}) + public void testPersonId() { + // nothing to test + } + + +} \ No newline at end of file diff --git a/gcm3/src/test/java/plugins/people/support/AT_PersonConstructionData.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/support/AT_PersonConstructionData.java similarity index 90% rename from gcm3/src/test/java/plugins/people/support/AT_PersonConstructionData.java rename to gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/support/AT_PersonConstructionData.java index 3a8c3867b..9714c365d 100644 --- a/gcm3/src/test/java/plugins/people/support/AT_PersonConstructionData.java +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/support/AT_PersonConstructionData.java @@ -1,4 +1,4 @@ -package plugins.people.support; +package gov.hhs.aspr.ms.gcm.plugins.people.support; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -13,14 +13,12 @@ import org.junit.jupiter.api.Test; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; +import util.annotations.UnitTestMethod; -@UnitTest(target = PersonConstructionData.class) public final class AT_PersonConstructionData { @Test - @UnitTestMethod(name = "builder", args = {}) + @UnitTestMethod(target = PersonConstructionData.class,name = "builder", args = {}) public void testBuilder() { assertNotNull(PersonConstructionData.builder()); } @@ -76,7 +74,7 @@ public void testAdd() { } @Test - @UnitTestMethod(name = "getValue", args = {Class.class}) + @UnitTestMethod(target = PersonConstructionData.class,name = "getValue", args = {Class.class}) public void testGetValue() { Map, List> expectedValues = new LinkedHashMap<>(); @@ -104,7 +102,7 @@ public void testGetValue() { } /* - * Show that the bulk construction data returns the first auxiliary data by type + * Show that the person construction data returns the first auxiliary data by type * */ PersonConstructionData personConstructionData = builder.build(); @@ -124,7 +122,7 @@ public void testGetValue() { @Test - @UnitTestMethod(name = "getValues", args = {Class.class}) + @UnitTestMethod(target = PersonConstructionData.class,name = "getValues", args = {Class.class}) public void testGetValues() { Map, List> expectedValues = new LinkedHashMap<>(); diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/support/AT_PersonError.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/support/AT_PersonError.java new file mode 100644 index 000000000..9233e3fa9 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/support/AT_PersonError.java @@ -0,0 +1,28 @@ +package gov.hhs.aspr.ms.gcm.plugins.people.support; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestMethod; + +public class AT_PersonError { + + @Test + @UnitTestMethod(target = PersonError.class, name = "getDescription", args = {}) + public void test() { + // show that each description is a unique, non-null and non-empty string + Set descriptions = new LinkedHashSet<>(); + for (PersonError personError : PersonError.values()) { + String description = personError.getDescription(); + assertNotNull(description, "null description for " + personError); + assertTrue(description.length() > 0, "empty string for " + personError); + boolean unique = descriptions.add(description); + assertTrue(unique, "description for " + personError + " is not unique"); + } + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/support/AT_PersonId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/support/AT_PersonId.java new file mode 100644 index 000000000..bef4e280a --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/support/AT_PersonId.java @@ -0,0 +1,94 @@ +package gov.hhs.aspr.ms.gcm.plugins.people.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + + +public final class AT_PersonId { + + @Test + @UnitTestConstructor(target = PersonId.class,args = { int.class }) + public void testConstructor() { + for (int i = 0; i < 10; i++) { + PersonId personId = new PersonId(i); + assertEquals(i, personId.getValue()); + } + + //precondition test: if the id < 0 + ContractException contractException = assertThrows(ContractException.class, ()->new PersonId(-1)); + assertEquals(PersonError.NEGATIVE_PERSON_ID, contractException.getErrorType()); + + + } + + @Test + @UnitTestMethod(target = PersonId.class,name = "compareTo", args = { PersonId.class }) + public void testCompareTo() { + for (int i = 0; i < 10; i++) { + PersonId personA = new PersonId(i); + for (int j = 0; j < 10; j++) { + PersonId personB = new PersonId(j); + int comparisonValue = personA.compareTo(personB); + if (i < j) { + assertTrue(comparisonValue < 0); + } else if (i > j) { + assertTrue(comparisonValue > 0); + } else { + assertTrue(comparisonValue == 0); + } + } + } + } + + @Test + @UnitTestMethod(target = PersonId.class,name = "equals", args = { Object.class }) + public void testEquals() { + for (int i = 0; i < 10; i++) { + PersonId personA = new PersonId(i); + for (int j = 0; j < 10; j++) { + PersonId personB = new PersonId(j); + if (i == j) { + assertEquals(personA,personB); + } else { + assertNotEquals(personA,personB); + } + } + } + } + + @Test + @UnitTestMethod(target = PersonId.class,name = "getValue", args = {}) + public void testGetValue() { + for (int i = 0; i < 10; i++) { + PersonId person = new PersonId(i); + assertEquals(i, person.getValue()); + } + } + + @Test + @UnitTestMethod(target = PersonId.class,name = "hashCode", args = {}) + public void testHashCode() { + for (int i = 0; i < 10; i++) { + PersonId person = new PersonId(i); + assertEquals(i, person.hashCode()); + } + } + + @Test + @UnitTestMethod(target = PersonId.class,name = "toString", args = {}) + public void testToString() { + for (int i = 0; i < 10; i++) { + PersonId person = new PersonId(i); + assertEquals(Integer.toString(i), person.toString()); + } + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/support/AT_PersonRange.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/support/AT_PersonRange.java new file mode 100644 index 000000000..b3d9b9066 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/support/AT_PersonRange.java @@ -0,0 +1,177 @@ +package gov.hhs.aspr.ms.gcm.plugins.people.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_PersonRange { + + @Test + @UnitTestConstructor(target = PersonRange.class, args = {int.class, int.class}) + public void testPersonRange() { + + // precondition test: illegal person range + ContractException illegalContractException = assertThrows(ContractException.class, () -> new PersonRange(10, 7)); + assertEquals(PersonError.ILLEGAL_PERSON_RANGE, illegalContractException.getErrorType()); + + // precondition test: negative person if + ContractException negativeContractException = assertThrows(ContractException.class, () -> new PersonRange(-5, 20)); + assertEquals(PersonError.NEGATIVE_PERSON_ID, negativeContractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PersonRange.class, name = "getLowPersonId", args = {}) + public void testGetLowPersonId() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7817347436220081509L); + for (int i = 0; i < 10; i++) { + int lowId = randomGenerator.nextInt(50); + int highId = lowId + 1 + randomGenerator.nextInt(50); + PersonRange personRange = new PersonRange(lowId, highId); + int actualId = personRange.getLowPersonId(); + assertEquals(lowId, actualId); + } + } + + @Test + @UnitTestMethod(target = PersonRange.class, name = "getHighPersonId", args = {}) + public void testGetHighId() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2964674930895304415L); + for (int i = 0; i < 10; i++) { + int lowId = randomGenerator.nextInt(50); + int highId = lowId + 1 + randomGenerator.nextInt(50); + PersonRange personRange = new PersonRange(lowId, highId); + int actualId = personRange.getHighPersonId(); + assertEquals(highId, actualId); + } + } + + @Test + @UnitTestMethod(target = PersonRange.class, name = "compareTo", args = {PersonRange.class}) + public void testCompareTo() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2964674930895304415L); + for (int i = 0; i < 30; i++) { + int lowId = randomGenerator.nextInt(5); + int highId = lowId + 1 + randomGenerator.nextInt(50); + int lowId2 = randomGenerator.nextInt(5); + int highId2 = lowId + 1 + randomGenerator.nextInt(50); + PersonRange personRange = new PersonRange(lowId, highId); + PersonRange personRange2 = new PersonRange(lowId2, highId2); + if (lowId == lowId2) { + if (highId == highId2) { + assertEquals(0, personRange.compareTo(personRange2)); + } else if (highId < highId2) { + assertTrue(personRange.compareTo(personRange2) < 0); + } else { + assertTrue(personRange.compareTo(personRange2) > 0); + } + } else if (lowId < lowId2) { + assertTrue(personRange.compareTo(personRange2) < 0); + } else { + assertTrue(personRange.compareTo(personRange2) > 0); + } + } + } + + @Test + @UnitTestMethod(target = PersonRange.class, name = "hashCode", args = {}) + public void testHashCode() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8720935725310593369L); + // show that equal person ranges have equal hashcodes + for (int i = 0; i < 10; i++) { + int lowId = randomGenerator.nextInt(50); + int highId = lowId + 1 + randomGenerator.nextInt(50); + PersonRange personRange = new PersonRange(lowId, highId); + PersonRange duplicatePersonRange = new PersonRange(lowId, highId); + assertEquals(personRange, duplicatePersonRange); + assertEquals(personRange.hashCode(), duplicatePersonRange.hashCode()); + } + + // show that hash codes are reasonably distributed + Set hashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + int lowId = randomGenerator.nextInt(50); + int highId = lowId + 1 + randomGenerator.nextInt(50); + PersonRange personRange = new PersonRange(lowId, highId); + hashCodes.add(personRange.hashCode()); + } + assertTrue(hashCodes.size() >= 90); + } + + @Test + @UnitTestMethod(target = PersonRange.class, name = "equals", args = {Object.class}) + public void testEquals() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4375612914105986895L); + // show that no object is null + for (int i = 0; i < 10; i++) { + int lowId = randomGenerator.nextInt(50); + int highId = lowId + 1 + randomGenerator.nextInt(50); + PersonRange personRange = new PersonRange(lowId, highId); + assertFalse(personRange.equals(null)); + } + + // stability + for (int i = 0; i < 10; i++) { + int lowId = randomGenerator.nextInt(50); + int highId = lowId + 1 + randomGenerator.nextInt(50); + PersonRange personRange = new PersonRange(lowId, highId); + PersonRange duplicatePersonRange = new PersonRange(lowId, highId); + for (int j = 0; j < 10; j++) { + assertTrue(personRange.equals(duplicatePersonRange)); + assertTrue(personRange.equals(duplicatePersonRange)); + assertTrue(personRange.equals(duplicatePersonRange)); + assertTrue(personRange.equals(duplicatePersonRange)); + } + } + + // symmetric + for (int i = 0; i < 10; i++) { + int lowId = randomGenerator.nextInt(50); + int highId = lowId + 1 + randomGenerator.nextInt(50); + PersonRange personRange = new PersonRange(lowId, highId); + PersonRange duplicatePersonRange = new PersonRange(lowId, highId); + assertTrue(personRange.equals(duplicatePersonRange)); + assertTrue(duplicatePersonRange.equals(personRange)); + } + + // reflexive + for (int i = 0; i < 10; i++) { + int lowId = randomGenerator.nextInt(50); + int highId = lowId + 1 + randomGenerator.nextInt(50); + PersonRange personRange = new PersonRange(lowId, highId); + assertTrue(personRange.equals(personRange)); + } + + // transitive + for (int i = 0; i < 10; i++) { + int lowId = randomGenerator.nextInt(50); + int highId = lowId + 1 + randomGenerator.nextInt(50); + PersonRange personRange = new PersonRange(lowId, highId); + PersonRange personRange2 = new PersonRange(lowId, highId); + PersonRange personRange3 = new PersonRange(lowId, highId); + assertTrue(personRange.equals(personRange2)); + assertTrue(personRange2.equals(personRange3)); + assertTrue(personRange3.equals(personRange)); + } + } + + @Test + @UnitTestMethod(target = PersonRange.class, name = "toString", args = {}) + public void testToString() { + assertEquals(new PersonRange(2, 100).toString(), "PersonRange [lowPersonId=2, highPersonId=100]"); + assertEquals(new PersonRange(4, 10).toString(), "PersonRange [lowPersonId=4, highPersonId=10]"); + assertEquals(new PersonRange(5, 5).toString(), "PersonRange [lowPersonId=5, highPersonId=5]"); + assertEquals(new PersonRange(12, 19).toString(), "PersonRange [lowPersonId=12, highPersonId=19]"); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/testsupport/AT_PeopleTestPluginFactory.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/testsupport/AT_PeopleTestPluginFactory.java new file mode 100644 index 000000000..9389165da --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/people/testsupport/AT_PeopleTestPluginFactory.java @@ -0,0 +1,150 @@ +package gov.hhs.aspr.ms.gcm.plugins.people.testsupport; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.function.Consumer; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.TestFactoryUtil; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginId; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePluginId; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeoplePluginData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonRange; +import gov.hhs.aspr.ms.gcm.plugins.people.testsupport.PeopleTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPluginId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.StochasticsError; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.wrappers.MutableBoolean; + +public class AT_PeopleTestPluginFactory { + + @Test + @UnitTestMethod(target = PeopleTestPluginFactory.class, name = "factory", args = { long.class, Consumer.class }) + public void testFactory_Consumer() { + MutableBoolean executed = new MutableBoolean(); + Factory factory = PeopleTestPluginFactory.factory(6489240163414718858L, c -> executed.setValue(true)); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + assertTrue(executed.getValue()); + + // precondition: consumer is null + Consumer nullConsumer = null; + ContractException contractException = assertThrows(ContractException.class, + () -> PeopleTestPluginFactory.factory(0, nullConsumer)); + assertEquals(NucleusError.NULL_ACTOR_CONTEXT_CONSUMER, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PeopleTestPluginFactory.class, name = "factory", args = { long.class, + TestPluginData.class }) + public void testFactory_TestPluginData() { + MutableBoolean executed = new MutableBoolean(); + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, c -> executed.setValue(true))); + TestPluginData testPluginData = pluginBuilder.build(); + + Factory factory = PeopleTestPluginFactory.factory(3745668053390022091L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + assertTrue(executed.getValue()); + + // precondition: testPluginData is null + TestPluginData nullTestPluginData = null; + ContractException contractException = assertThrows(ContractException.class, + () -> PeopleTestPluginFactory.factory(0, nullTestPluginData)); + assertEquals(NucleusError.NULL_PLUGIN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PeopleTestPluginFactory.Factory.class, name = "getPlugins", args = {}) + public void testGetPlugins() { + List plugins = PeopleTestPluginFactory.factory(0, t -> { + }).getPlugins(); + + assertEquals(3, plugins.size()); + + TestFactoryUtil.checkPluginExists(plugins, PeoplePluginId.PLUGIN_ID); + TestFactoryUtil.checkPluginExists(plugins, StochasticsPluginId.PLUGIN_ID); + TestFactoryUtil.checkPluginExists(plugins, TestPluginId.PLUGIN_ID); + + } + + @Test + @UnitTestMethod(target = PeopleTestPluginFactory.Factory.class, name = "setPeoplePluginData", args = { + PeoplePluginData.class }) + public void testSetPeoplePluginData() { + PeoplePluginData.Builder builder = PeoplePluginData.builder(); + builder.addPersonRange(new PersonRange(0, 99)); + + PeoplePluginData peoplePluginData = builder.build(); + + List plugins = PeopleTestPluginFactory.factory(0, t -> { + }).setPeoplePluginData(peoplePluginData).getPlugins(); + + TestFactoryUtil.checkPluginDataExists(plugins, peoplePluginData, PeoplePluginId.PLUGIN_ID); + + // precondition: peoplePluginData is not null + ContractException contractException = assertThrows(ContractException.class, + () -> PeopleTestPluginFactory.factory(0, t -> { + }).setPeoplePluginData(null)); + assertEquals(PersonError.NULL_PEOPLE_PLUGIN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PeopleTestPluginFactory.Factory.class, name = "setStochasticsPluginData", args = { + StochasticsPluginData.class }) + public void testSetStochasticsPluginData() { + StochasticsPluginData.Builder builder = StochasticsPluginData.builder(); + + WellState wellState = WellState.builder().setSeed(2758378374654665699L).build(); + builder.setMainRNGState(wellState); + + StochasticsPluginData stochasticsPluginData = builder.build(); + + List plugins = PeopleTestPluginFactory.factory(0, t -> { + }).setStochasticsPluginData(stochasticsPluginData).getPlugins(); + + TestFactoryUtil.checkPluginDataExists(plugins, stochasticsPluginData, StochasticsPluginId.PLUGIN_ID); + + // precondition: stochasticsPluginData is not null + ContractException contractException = assertThrows(ContractException.class, + () -> PeopleTestPluginFactory.factory(0, t -> { + }).setStochasticsPluginData(null)); + assertEquals(StochasticsError.NULL_STOCHASTICS_PLUGIN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PeopleTestPluginFactory.class, name = "getStandardPeoplePluginData", args = {}) + public void testGetStandardPeoplePluginData() { + + PeoplePluginData expectedPluginData = PeoplePluginData.builder().build(); + PeoplePluginData actualPluginData = PeopleTestPluginFactory.getStandardPeoplePluginData(); + assertEquals(expectedPluginData, actualPluginData); + } + + @Test + @UnitTestMethod(target = PeopleTestPluginFactory.class, name = "getStandardStochasticsPluginData", args = { + long.class }) + public void testGetStandardStochasticsPluginData() { + long seed = 6072871729256538807L; + + WellState wellState = WellState.builder().setSeed(seed).build(); + + StochasticsPluginData expectedPluginData = StochasticsPluginData.builder().setMainRNGState(wellState).build(); + StochasticsPluginData actualPluginData = PeopleTestPluginFactory.getStandardStochasticsPluginData(seed); + assertEquals(expectedPluginData, actualPluginData); + } + +} \ No newline at end of file diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/AT_PersonPropertiesPlugin.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/AT_PersonPropertiesPlugin.java new file mode 100644 index 000000000..398973bfd --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/AT_PersonPropertiesPlugin.java @@ -0,0 +1,96 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePluginId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.reports.PersonPropertyInteractionReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.reports.PersonPropertyReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.regions.RegionsPluginId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import util.annotations.UnitTestMethod; + +public class AT_PersonPropertiesPlugin { + + @Test + @UnitTestMethod(target = PersonPropertiesPlugin.Builder.class, name = "getPersonPropertyPlugin", args = {}) + public void testGetPersonPropertyPlugin() { + PersonPropertiesPluginData personPropertiesPluginData = PersonPropertiesPluginData.builder().build(); + Plugin personPropertiesPlugin = PersonPropertiesPlugin.builder() + .setPersonPropertiesPluginData(personPropertiesPluginData).getPersonPropertyPlugin(); + + assertEquals(1, personPropertiesPlugin.getPluginDatas().size()); + assertTrue(personPropertiesPlugin.getPluginDatas().contains(personPropertiesPluginData)); + + assertEquals(PersonPropertiesPluginId.PLUGIN_ID, personPropertiesPlugin.getPluginId()); + + Set expectedDependencies = new LinkedHashSet<>(); + expectedDependencies.add(PeoplePluginId.PLUGIN_ID); + expectedDependencies.add(RegionsPluginId.PLUGIN_ID); + + assertEquals(expectedDependencies, personPropertiesPlugin.getPluginDependencies()); + + } + + @Test + @UnitTestMethod(target = PersonPropertiesPlugin.class, name = "builder", args = {}) + public void testBuilder() { + assertNotNull(PersonPropertiesPlugin.builder()); + } + + @Test + @UnitTestMethod(target = PersonPropertiesPlugin.Builder.class, name = "setPersonPropertyInteractionReportPluginData", args = { + PersonPropertyInteractionReportPluginData.class }) + public void testSetGroupPropertyReportPluginData() { + PersonPropertiesPluginData personPropertiesPluginData = PersonPropertiesPluginData.builder().build(); + PersonPropertyInteractionReportPluginData personPropertyInteractionReportPluginData = PersonPropertyInteractionReportPluginData.builder() + .setReportLabel(new SimpleReportLabel("test")).setReportPeriod(ReportPeriod.DAILY).build(); + + Plugin personPropertiesPlugin = PersonPropertiesPlugin.builder()// + .setPersonPropertiesPluginData(personPropertiesPluginData) + .setPersonPropertyInteractionReportPluginData(personPropertyInteractionReportPluginData) + .getPersonPropertyPlugin(); + + assertTrue(personPropertiesPlugin.getPluginDatas().contains(personPropertyInteractionReportPluginData)); + } + + @Test + @UnitTestMethod(target = PersonPropertiesPlugin.Builder.class, name = "setPersonPropertyReportPluginData", args = { + PersonPropertyReportPluginData.class }) + public void testSetPersonPropertyReportPluginData() { + PersonPropertiesPluginData personPropertiesPluginData = PersonPropertiesPluginData.builder().build(); + PersonPropertyReportPluginData personPropertyReportPluginData = PersonPropertyReportPluginData.builder() + .setReportLabel(new SimpleReportLabel("test")).setReportPeriod(ReportPeriod.DAILY).build(); + + Plugin personPropertiesPlugin = PersonPropertiesPlugin.builder()// + .setPersonPropertiesPluginData(personPropertiesPluginData) + .setPersonPropertyReportPluginData(personPropertyReportPluginData) + .getPersonPropertyPlugin(); + + assertTrue(personPropertiesPlugin.getPluginDatas().contains(personPropertyReportPluginData)); + } + + @Test + @UnitTestMethod(target = PersonPropertiesPlugin.Builder.class, name = "setPersonPropertiesPluginData", args = { + PersonPropertiesPluginData.class }) + public void testSetPersonPropertiesPluginData() { + PersonPropertiesPluginData personPropertiesPluginData = PersonPropertiesPluginData.builder().build(); + + Plugin personPropertiesPlugin = PersonPropertiesPlugin.builder()// + .setPersonPropertiesPluginData(personPropertiesPluginData) + .getPersonPropertyPlugin(); + + assertTrue(personPropertiesPlugin.getPluginDatas().contains(personPropertiesPluginData)); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/AT_PersonPropertiesPluginId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/AT_PersonPropertiesPluginId.java new file mode 100644 index 000000000..0302335ff --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/AT_PersonPropertiesPluginId.java @@ -0,0 +1,16 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestField; + +public class AT_PersonPropertiesPluginId { + + @Test + @UnitTestField(target = PersonPropertiesPluginId.class, name = "PLUGIN_ID") + public void testPluginId() { + assertNotNull(PersonPropertiesPluginId.PLUGIN_ID); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/datamanagers/AT_PersonPropertyDataManager.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/datamanagers/AT_PersonPropertyDataManager.java new file mode 100644 index 000000000..51bb34d24 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/datamanagers/AT_PersonPropertyDataManager.java @@ -0,0 +1,2540 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Random; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.IntStream; + +import org.apache.commons.math3.random.RandomGenerator; +import org.apache.commons.math3.util.FastMath; +import org.apache.commons.math3.util.Pair; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; +import gov.hhs.aspr.ms.gcm.nucleus.EventFilter; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.Simulation; +import gov.hhs.aspr.ms.gcm.nucleus.SimulationState; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.runcontinuityplugin.RunContinuityPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.runcontinuityplugin.RunContinuityPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestOutputConsumer; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePlugin; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeoplePluginData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonConstructionData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonRange; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.PersonPropertiesPlugin; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.events.PersonPropertyDefinitionEvent; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.events.PersonPropertyUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyDefinitionInitialization; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyError; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyValueInitialization; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.testsupport.PersonPropertiesTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.testsupport.PersonPropertiesTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.testsupport.TestAuxiliaryPersonPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.testsupport.TestPersonPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.regions.RegionsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionError; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.TestRegionId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; +import util.wrappers.MultiKey; +import util.wrappers.MutableBoolean; +import util.wrappers.MutableInteger; + +public final class AT_PersonPropertyDataManager { + + /** + * Demonstrates that the data manager produces plugin data that reflects its + * final state + */ + @Test + @UnitTestMethod(target = PersonPropertiesDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testStateFinalization() { + /* + * Plan for test: + * + * we will start with three people and two properties, one with a default and + * one without + * + * 1) generate the necessary plugins + * + * 2) generate the initial people properties plugin data state + * + * 3)have an actor perform various operations that change the person property + * management state + * + * 4)get the people properties plugin data that results from the simulation + * + * 5) build the expected plugin data + * + * 6)compare the two versions for equality + */ + + // add the three people + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7333676542014748090L); + PeoplePluginData peoplePluginData = PeoplePluginData.builder().addPersonRange(new PersonRange(1, 3)).build(); + Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(peoplePluginData); + + // create three regions + RegionsPluginData regionsPluginData = RegionsPluginData.builder()// + .addRegion(TestRegionId.REGION_1)// + .addRegion(TestRegionId.REGION_2)// + .addRegion(TestRegionId.REGION_3)// + .addPerson(new PersonId(1), TestRegionId.REGION_1)// + .addPerson(new PersonId(2), TestRegionId.REGION_2)// + .addPerson(new PersonId(3), TestRegionId.REGION_2)// + .build(); + Plugin regionsPlugin = RegionsPlugin.builder()// + .setRegionsPluginData(regionsPluginData)// + .getRegionsPlugin(); + + // create the stochastics plugin + WellState wellState = WellState.builder()// + .setSeed(randomGenerator.nextLong())// + .build(); + StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder()// + .setMainRNGState(wellState)// + .build(); + Plugin stochasticsPlugin = StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData); + + /* + * Create the person properties plugin. There will be three properties. The + * first two exist in the initial plugin data and third will be added later. + * Time tracking will be turned on for prop1 and prop3 + */ + PersonPropertyId prop1 = new LocalPersonPropertyId(1) { + }; + PersonPropertyId prop2 = new LocalPersonPropertyId(2) { + }; + PersonPropertyId prop3 = new LocalPersonPropertyId(3) { + }; + + PropertyDefinition def1 = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(17).build(); + PropertyDefinition def2 = PropertyDefinition.builder().setType(Double.class).setDefaultValue(12.3).build(); + PropertyDefinition def3 = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(true).build(); + + PersonPropertiesPluginData.Builder propBuilder = PersonPropertiesPluginData.builder(); + propBuilder.definePersonProperty(prop1, def1, 1.2, true); + propBuilder.definePersonProperty(prop2, def2, 0, false); + propBuilder.setPersonPropertyValue(new PersonId(1), prop1, 18); + propBuilder.setPersonPropertyTime(new PersonId(1), prop1, 1.5); + propBuilder.setPersonPropertyTime(new PersonId(2), prop1, 1.3); + propBuilder.setPersonPropertyValue(new PersonId(3), prop1, 99); + propBuilder.setPersonPropertyTime(new PersonId(3), prop1, 2.3); + propBuilder.setPersonPropertyValue(new PersonId(1), prop2, 34.4); + propBuilder.setPersonPropertyValue(new PersonId(2), prop2, 88.7); + + PersonPropertiesPluginData personPropertiesPluginData = propBuilder.build(); + Plugin personPropertyPlugin = PersonPropertiesPlugin.builder()// + .setPersonPropertiesPluginData(personPropertiesPluginData)// + .getPersonPropertyPlugin(); + + TestPluginData.Builder testPluginBuilder = TestPluginData.builder(); + testPluginBuilder.addTestActorPlan("actor", new TestActorPlan(2.6, (c) -> { + // remove a person + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + peopleDataManager.removePerson(new PersonId(1)); + // update some values + + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(2), prop1, 66); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(3), prop2, 100.5); + })); + + testPluginBuilder.addTestActorPlan("actor", new TestActorPlan(3.4, (c) -> { + // introduce a new property + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + PersonPropertyDefinitionInitialization personPropertyDefinitionInitialization = // + PersonPropertyDefinitionInitialization.builder()// + .setPersonPropertyId(prop3)// + .setPropertyDefinition(def3)// + .setTrackTimes(true)// + .build(); + personPropertiesDataManager.definePersonProperty(personPropertyDefinitionInitialization); + // add a person + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PersonConstructionData personConstructionData = // + PersonConstructionData.builder()// + .add(TestRegionId.REGION_3)// + .add(new PersonPropertyValueInitialization(prop2, 456.6))// + .build(); + peopleDataManager.addPerson(personConstructionData); + + // update some values + personPropertiesDataManager.setPersonPropertyValue(new PersonId(4), prop3, false); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(4), prop1, 13); + + })); + + testPluginBuilder.addTestActorPlan("actor", new TestActorPlan(6.7, (c) -> { + // update some values + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + + personPropertiesDataManager.setPersonPropertyValue(new PersonId(2), prop1, 17); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(2), prop3, false); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(3), prop2, 123.31); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(3), prop3, true); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(4), prop1, 88); + + })); + + TestPluginData testPluginData = testPluginBuilder.build(); + + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + /* + * Run the simulation starting on day 2.5 and get the actual person properties + * plugin data that results from the data manager mutations + */ + SimulationState simulationState = SimulationState.builder().setStartTime(2.5).build(); + + TestOutputConsumer testOutputConsumer = TestSimulation.builder()// + .addPlugin(peoplePlugin)// + .addPlugin(regionsPlugin)// + .addPlugin(stochasticsPlugin)// + .addPlugin(personPropertyPlugin)// + .addPlugin(testPlugin)// + .setProduceSimulationStateOnHalt(true)// + .setSimulationState(simulationState)// + .setSimulationHaltTime(10.0)// + .build()// + .execute(); + + Map outputItems = testOutputConsumer + .getOutputItemMap(PersonPropertiesPluginData.class); + assertTrue(outputItems.size() == 1); + PersonPropertiesPluginData actualPersonPropertiesPluginData = outputItems.keySet().iterator().next(); + + /* + * Generate the expected person properties plugin data + */ + propBuilder = PersonPropertiesPluginData.builder(); + propBuilder.definePersonProperty(prop1, def1, 1.2, true); + propBuilder.definePersonProperty(prop2, def2, 0, false); + propBuilder.definePersonProperty(prop3, def3, 3.4, true); + propBuilder.setPersonPropertyTime(new PersonId(2), prop1, 6.7); + propBuilder.setPersonPropertyValue(new PersonId(2), prop1, 17); + propBuilder.setPersonPropertyValue(new PersonId(2), prop2, 88.7); + propBuilder.setPersonPropertyValue(new PersonId(2), prop3, false); + propBuilder.setPersonPropertyTime(new PersonId(2), prop3, 6.7); + propBuilder.setPersonPropertyValue(new PersonId(3), prop1, 99); + propBuilder.setPersonPropertyTime(new PersonId(3), prop1, 2.3); + propBuilder.setPersonPropertyValue(new PersonId(3), prop2, 123.31); + propBuilder.setPersonPropertyValue(new PersonId(3), prop3, true); + propBuilder.setPersonPropertyTime(new PersonId(3), prop3, 6.7); + propBuilder.setPersonPropertyValue(new PersonId(4), prop1, 88); + propBuilder.setPersonPropertyTime(new PersonId(4), prop1, 6.7); + propBuilder.setPersonPropertyValue(new PersonId(4), prop2, 456.6); + propBuilder.setPersonPropertyValue(new PersonId(4), prop3, false); + // the following property time is extraneous, but should not effect + // equality + propBuilder.setPersonPropertyTime(new PersonId(4), prop3, 3.4); + PersonPropertiesPluginData expectedPersonPropertiesPluginData = propBuilder.build(); + + // compare the expected and actual plugin datas + + assertEquals(expectedPersonPropertiesPluginData, actualPersonPropertiesPluginData); + + } + + @Test + @UnitTestMethod(target = PersonPropertiesDataManager.class, name = "getPeopleWithPropertyValue", args = { + PersonPropertyId.class, Object.class }) + public void testGetPeopleWithPropertyValue() { + + Factory factory = PersonPropertiesTestPluginFactory.factory(100, 7917315534360369845L, (c) -> { + + // establish data views + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + /* + * Assign random values of 1, 2 or 3 for property 2 to all people. Build a + * structure to hold expected results. + */ + List people = peopleDataManager.getPeople(); + Map> expectedValuesToPeople = new LinkedHashMap<>(); + for (int i = 0; i < 3; i++) { + expectedValuesToPeople.put(i, new LinkedHashSet<>()); + } + + for (PersonId personId : people) { + int value = randomGenerator.nextInt(3); + personPropertiesDataManager.setPersonPropertyValue(personId, + TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, value); + expectedValuesToPeople.get(value).add(personId); + } + + // show that the proper people are returned for each value + for (Integer value : expectedValuesToPeople.keySet()) { + List actualPeople = personPropertiesDataManager.getPeopleWithPropertyValue( + TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, value); + Set expectedPeople = expectedValuesToPeople.get(value); + assertEquals(expectedPeople.size(), actualPeople.size()); + assertEquals(expectedPeople, new LinkedHashSet<>(actualPeople)); + } + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = PersonPropertiesDataManager.class, name = "getPersonCountForPropertyValue", args = { + PersonPropertyId.class, Object.class }) + public void testGetPersonCountForPropertyValue() { + + Factory factory = PersonPropertiesTestPluginFactory.factory(100, 686456599634987511L, (c) -> { + + // establish data views + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + /* + * Assign random values of 1, 2 or 3 for property 2 to all people. Build a + * structure to hold expected results. + */ + List people = peopleDataManager.getPeople(); + Map expectedValuesToPeople = new LinkedHashMap<>(); + for (int i = 0; i < 3; i++) { + expectedValuesToPeople.put(i, new MutableInteger()); + } + + for (PersonId personId : people) { + int value = randomGenerator.nextInt(3); + personPropertiesDataManager.setPersonPropertyValue(personId, + TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, value); + expectedValuesToPeople.get(value).increment(); + } + + // show that the proper counts are returned for each value + for (Integer value : expectedValuesToPeople.keySet()) { + int actualCount = personPropertiesDataManager.getPersonCountForPropertyValue( + TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, value); + MutableInteger mutableInteger = expectedValuesToPeople.get(value); + assertEquals(mutableInteger.getValue(), actualCount); + } + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = PersonPropertiesDataManager.class, name = "getPersonPropertyDefinition", args = { + PersonPropertyId.class }) + public void testGetPersonPropertyDefinition() { + + Factory factory = PersonPropertiesTestPluginFactory.factory(0, 138806179316502662L, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + + // show that the person property definitions match expectations + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + PropertyDefinition expectedPropertyDefinition = testPersonPropertyId.getPropertyDefinition(); + PropertyDefinition actualPropertyDefinition = personPropertiesDataManager + .getPersonPropertyDefinition(testPersonPropertyId); + assertEquals(expectedPropertyDefinition, actualPropertyDefinition); + } + + // precondition tests + + // if the person property id is null + ContractException contractException = assertThrows(ContractException.class, + () -> personPropertiesDataManager.getPersonPropertyDefinition(null)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // if the person property id is unknown + contractException = assertThrows(ContractException.class, () -> personPropertiesDataManager + .getPersonPropertyDefinition(TestPersonPropertyId.getUnknownPersonPropertyId())); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = PersonPropertiesDataManager.class, name = "getPersonPropertyIds", args = {}) + public void testGetPersonPropertyIds() { + Factory factory = PersonPropertiesTestPluginFactory.factory(0, 8485097765777963229L, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + EnumSet expectedPropertyIds = EnumSet.allOf(TestPersonPropertyId.class); + Set actualPropertyIds = personPropertiesDataManager.getPersonPropertyIds(); + assertEquals(expectedPropertyIds, actualPropertyIds); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = PersonPropertiesDataManager.class, name = "getPersonPropertyTime", args = { PersonId.class, + PersonPropertyId.class }) + public void testGetPersonPropertyTime() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // show that all person property times are 0 for the time-tracked + // properties + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + List people = peopleDataManager.getPeople(); + for (PersonId personId : people) { + double personPropertyTime = personPropertiesDataManager.getPersonPropertyTime(personId, + TestPersonPropertyId.PERSON_PROPERTY_4_BOOLEAN_MUTABLE_TRACK); + assertEquals(0.0, personPropertyTime); + personPropertyTime = personPropertiesDataManager.getPersonPropertyTime(personId, + TestPersonPropertyId.PERSON_PROPERTY_5_INTEGER_MUTABLE_TRACK); + assertEquals(0.0, personPropertyTime); + personPropertyTime = personPropertiesDataManager.getPersonPropertyTime(personId, + TestPersonPropertyId.PERSON_PROPERTY_6_DOUBLE_MUTABLE_TRACK); + assertEquals(0.0, personPropertyTime); + } + })); + + // Set property 5 for all people at time 1 + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + List people = peopleDataManager.getPeople(); + RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); + for (PersonId personId : people) { + personPropertiesDataManager.setPersonPropertyValue(personId, + TestPersonPropertyId.PERSON_PROPERTY_5_INTEGER_MUTABLE_TRACK, randomGenerator.nextInt()); + } + })); + + // Set property 6 for all people at time 2 + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + List people = peopleDataManager.getPeople(); + RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); + for (PersonId personId : people) { + personPropertiesDataManager.setPersonPropertyValue(personId, + TestPersonPropertyId.PERSON_PROPERTY_6_DOUBLE_MUTABLE_TRACK, randomGenerator.nextDouble()); + } + })); + + // show that the person property times agree with the times above + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(3, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + List people = peopleDataManager.getPeople(); + for (PersonId personId : people) { + double personPropertyTime = personPropertiesDataManager.getPersonPropertyTime(personId, + TestPersonPropertyId.PERSON_PROPERTY_4_BOOLEAN_MUTABLE_TRACK); + assertEquals(0.0, personPropertyTime); + personPropertyTime = personPropertiesDataManager.getPersonPropertyTime(personId, + TestPersonPropertyId.PERSON_PROPERTY_5_INTEGER_MUTABLE_TRACK); + assertEquals(1.0, personPropertyTime); + personPropertyTime = personPropertiesDataManager.getPersonPropertyTime(personId, + TestPersonPropertyId.PERSON_PROPERTY_6_DOUBLE_MUTABLE_TRACK); + assertEquals(2.0, personPropertyTime); + } + })); + + // precondition tests + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(4, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + + PersonId personId = new PersonId(0); + PersonId unknownPersonId = new PersonId(100000); + PersonPropertyId personPropertyId = TestPersonPropertyId.PERSON_PROPERTY_5_INTEGER_MUTABLE_TRACK; + PersonPropertyId unknownPersonPropertyId = TestPersonPropertyId.getUnknownPersonPropertyId(); + PersonPropertyId untrackedPersonPropertyId = TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; + + // if the person id is null + ContractException contractException = assertThrows(ContractException.class, + () -> personPropertiesDataManager.getPersonPropertyTime(null, personPropertyId)); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + // if the person id is unknown + contractException = assertThrows(ContractException.class, + () -> personPropertiesDataManager.getPersonPropertyTime(unknownPersonId, personPropertyId)); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + // if the person property id is null + contractException = assertThrows(ContractException.class, + () -> personPropertiesDataManager.getPersonPropertyTime(personId, null)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // if the person property id is unknown + contractException = assertThrows(ContractException.class, + () -> personPropertiesDataManager.getPersonPropertyTime(personId, unknownPersonPropertyId)); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + // if the person property does not have time tracking turned on in + // the associated property definition + contractException = assertThrows(ContractException.class, + () -> personPropertiesDataManager.getPersonPropertyTime(personId, untrackedPersonPropertyId)); + assertEquals(PersonPropertyError.PROPERTY_ASSIGNMENT_TIME_NOT_TRACKED, contractException.getErrorType()); + + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = PersonPropertiesTestPluginFactory.factory(10, 6980289425630085602L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = PersonPropertiesDataManager.class, name = "getPersonPropertyValue", args = { + PersonId.class, PersonPropertyId.class }) + public void testGetPersonPropertyValue() { + + Factory factory = PersonPropertiesTestPluginFactory.factory(10, 816143115345188642L, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + // create a container to hold expectations + Map expectedValues = new LinkedHashMap<>(); + + // assign random values for property 2 for all the people + List people = peopleDataManager.getPeople(); + for (PersonId personId : people) { + int value = randomGenerator.nextInt(); + personPropertiesDataManager.setPersonPropertyValue(personId, + TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, value); + expectedValues.put(personId, value); + } + + // show that the values retrieved match expectations + for (PersonId personId : people) { + Integer expectedValue = expectedValues.get(personId); + Integer actualValue = personPropertiesDataManager.getPersonPropertyValue(personId, + TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK); + assertEquals(expectedValue, actualValue); + } + + // precondition tests + PersonId personId = new PersonId(0); + PersonId unknownPersonId = new PersonId(100000); + PersonPropertyId personPropertyId = TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK; + PersonPropertyId unknownPersonPropertyId = TestPersonPropertyId.getUnknownPersonPropertyId(); + + // if the person id is null + ContractException contractException = assertThrows(ContractException.class, + () -> personPropertiesDataManager.getPersonPropertyValue(null, personPropertyId)); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + // if the person id is unknown + contractException = assertThrows(ContractException.class, + () -> personPropertiesDataManager.getPersonPropertyValue(unknownPersonId, personPropertyId)); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + // if the person property id is null + contractException = assertThrows(ContractException.class, + () -> personPropertiesDataManager.getPersonPropertyValue(personId, null)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // if the person property id is unknown + contractException = assertThrows(ContractException.class, + () -> personPropertiesDataManager.getPersonPropertyValue(personId, unknownPersonPropertyId)); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestConstructor(target = PersonPropertiesDataManager.class, args = { PersonPropertiesPluginData.class }) + public void testConstructor() { + ContractException contractException = assertThrows(ContractException.class, + () -> new PersonPropertiesDataManager(null)); + assertEquals(PersonPropertyError.NULL_PERSON_PROPERTY_PLUGN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PersonPropertiesDataManager.class, name = "expandCapacity", args = { int.class }) + public void testExpandCapacity() { + Factory factory = PersonPropertiesTestPluginFactory.factory(20, 7153865371557964932L, (c) -> { + // show that a negative growth causes an exception + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + ContractException contractException = assertThrows(ContractException.class, + () -> personPropertiesDataManager.expandCapacity(-1)); + assertEquals(PersonError.NEGATIVE_GROWTH_PROJECTION, contractException.getErrorType()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + // use manual tests for non-negative growth + } + + @Test + @UnitTestMethod(target = PersonPropertiesDataManager.class, name = "personPropertyIdExists", args = { + PersonPropertyId.class }) + public void testPersonPropertyIdExists() { + + Factory factory = PersonPropertiesTestPluginFactory.factory(0, 4797443283568888200L, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + assertTrue(personPropertiesDataManager.personPropertyIdExists(testPersonPropertyId)); + } + assertFalse(personPropertiesDataManager + .personPropertyIdExists(TestPersonPropertyId.getUnknownPersonPropertyId())); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = PersonPropertiesDataManager.class, name = "setPersonPropertyValue", args = { + PersonId.class, PersonPropertyId.class, Object.class }) + public void testSetPersonPropertyValue() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create some containers to hold the expected and actual observations + // for later comparison + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // add an agent that will observe changes to all person properties + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + + EventFilter eventFilter = personPropertiesDataManager + .getEventFilterForPersonPropertyUpdateEvent(); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(e.personId(), e.personPropertyId(), e.previousPropertyValue(), + e.currentPropertyValue())); + }); + + })); + + /* + * Add an agent that will alter person property values and record the + * corresponding expected observations. + */ + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + + // establish data views + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + // select all the property ids that are mutable + Set mutableProperties = new LinkedHashSet<>(); + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + boolean mutable = testPersonPropertyId.getPropertyDefinition().propertyValuesAreMutable(); + if (mutable) { + mutableProperties.add(testPersonPropertyId); + } + } + + // get the people + List people = peopleDataManager.getPeople(); + + // set all their mutable property values, recording the expected + // observations + for (PersonId personId : people) { + for (TestPersonPropertyId testPersonPropertyId : mutableProperties) { + + // determine the new and current values + Object newValue = testPersonPropertyId.getRandomPropertyValue(randomGenerator); + Object currentValue = personPropertiesDataManager.getPersonPropertyValue(personId, + testPersonPropertyId); + + // record the expected observation + expectedObservations.add(new MultiKey(personId, testPersonPropertyId, currentValue, newValue)); + + // update the person property + personPropertiesDataManager.setPersonPropertyValue(personId, testPersonPropertyId, newValue); + + // show that the value changed + Object actualValue = personPropertiesDataManager.getPersonPropertyValue(personId, + testPersonPropertyId); + assertEquals(newValue, actualValue); + } + } + })); + + // have the agent perform precondition checks + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + PersonId personId = new PersonId(0); + PersonPropertyId personPropertyId = TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + PersonPropertyId immutablePersonPropertyId = TestPersonPropertyId.PERSON_PROPERTY_7_BOOLEAN_IMMUTABLE_NO_TRACK; + Object value = true; + + PersonId unknownPersonId = new PersonId(100000); + PersonPropertyId unknownPersonPropertyId = TestPersonPropertyId.getUnknownPersonPropertyId(); + Object incompatibleValue = 12; + + // if the person id is null + ContractException contractException = assertThrows(ContractException.class, + () -> personPropertiesDataManager.setPersonPropertyValue(null, personPropertyId, value)); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + // if the person id is unknown + contractException = assertThrows(ContractException.class, + () -> personPropertiesDataManager.setPersonPropertyValue(unknownPersonId, personPropertyId, value)); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + // if the person property id is null + contractException = assertThrows(ContractException.class, + () -> personPropertiesDataManager.setPersonPropertyValue(personId, null, value)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // if the person property id is unknown + contractException = assertThrows(ContractException.class, + () -> personPropertiesDataManager.setPersonPropertyValue(personId, unknownPersonPropertyId, value)); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + // if the property value is null + contractException = assertThrows(ContractException.class, + () -> personPropertiesDataManager.setPersonPropertyValue(personId, personPropertyId, null)); + assertEquals(PropertyError.NULL_PROPERTY_VALUE, contractException.getErrorType()); + + // if the property value is not compatible with the corresponding + // property definition + contractException = assertThrows(ContractException.class, () -> personPropertiesDataManager + .setPersonPropertyValue(personId, personPropertyId, incompatibleValue)); + assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); + + // if the corresponding property definition marks the property as + // immutable + contractException = assertThrows(ContractException.class, () -> personPropertiesDataManager + .setPersonPropertyValue(personId, immutablePersonPropertyId, value)); + assertEquals(PropertyError.IMMUTABLE_VALUE, contractException.getErrorType()); + + })); + + // have the observer show that the expected observations were actually + // observed + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(3, (c) -> { + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = PersonPropertiesTestPluginFactory.factory(10, 2321272063791878719L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + /** + * Demonstrates that the data manager's initial state reflects its plugin data + */ + @Test + @UnitTestMethod(target = PersonPropertiesDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testStateInitialization() { + + int totalPeople = 10; + + List people = new ArrayList<>(); + for (int i = 0; i < totalPeople; i++) { + people.add(new PersonId(i)); + } + long seed = 2693836950854697940L; + PersonPropertiesPluginData personPropertiesPluginData = PersonPropertiesTestPluginFactory + .getStandardPersonPropertiesPluginData(people, seed); + // add the action plugin + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + /* + * Add an agent that will show that the person property data view is properly + * initialized from the person property initial data + */ + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c2) -> { + // get the person property data view + PersonPropertiesDataManager personPropertiesDataManager = c2 + .getDataManager(PersonPropertiesDataManager.class); + PeopleDataManager peopleDataManager = c2.getDataManager(PeopleDataManager.class); + + // show that the property ids are correct + assertEquals(personPropertiesPluginData.getPersonPropertyIds(), + personPropertiesDataManager.getPersonPropertyIds()); + + // show that the property definitions are correct + for (PersonPropertyId personPropertyId : personPropertiesPluginData.getPersonPropertyIds()) { + PropertyDefinition expectedPropertyDefinition = personPropertiesPluginData + .getPersonPropertyDefinition(personPropertyId); + PropertyDefinition actualPropertyDefinition = personPropertiesDataManager + .getPersonPropertyDefinition(personPropertyId); + assertEquals(expectedPropertyDefinition, actualPropertyDefinition); + } + + // show that the person property values are set to the default + // values for those properties that have default values + List personIds = peopleDataManager.getPeople(); + assertTrue(personIds.size() > 0); + + for (PersonPropertyId personPropertyId : personPropertiesPluginData.getPersonPropertyIds()) { + PropertyDefinition propertyDefinition = personPropertiesPluginData + .getPersonPropertyDefinition(personPropertyId); + Optional optional = propertyDefinition.getDefaultValue(); + Object defaultValue = null; + if (optional.isPresent()) { + defaultValue = optional.get(); + } + List propertyValues = personPropertiesPluginData.getPropertyValues(personPropertyId); + + for (PersonId personId : people) { + + Object expectedValue = null; + if (personId.getValue() < propertyValues.size()) { + expectedValue = propertyValues.get(personId.getValue()); + } + if (expectedValue == null) { + expectedValue = defaultValue; + } + + Object actualValue = personPropertiesDataManager.getPersonPropertyValue(personId, personPropertyId); + assertEquals(expectedValue, actualValue); + } + } + })); + + TestPluginData testPluginData = pluginBuilder.build(); + + Factory factory = PersonPropertiesTestPluginFactory.factory(totalPeople, seed, testPluginData)// + .setPersonPropertiesPluginData(personPropertiesPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = PersonPropertiesDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testPersonAdditionEvent() { + + Factory factory = PersonPropertiesTestPluginFactory.factory(100, 4771130331997762252L, (c) -> { + // establish data views + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + + // get the random generator for use later + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + // add a person with some person property auxiliary data + PersonConstructionData.Builder personBuilder = PersonConstructionData.builder(); + + // create a container to hold expectations + Map expectedPropertyValues = new LinkedHashMap<>(); + + // set the expectation to the default values of all the properties, + // for those that have defaults + Set personPropertyIds = personPropertiesDataManager.getPersonPropertyIds(); + for (PersonPropertyId personPropertyId : personPropertyIds) { + + PropertyDefinition personPropertyDefinition = personPropertiesDataManager + .getPersonPropertyDefinition(personPropertyId); + if (personPropertyDefinition.getDefaultValue().isPresent()) { + Object value = personPropertyDefinition.getDefaultValue().get(); + expectedPropertyValues.put(personPropertyId, value); + } + } + + // set two properties to random values and record them in the + // expected data + int iValue = randomGenerator.nextInt(); + personBuilder.add(new PersonPropertyValueInitialization( + TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, iValue)); + expectedPropertyValues.put(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, iValue); + + double dValue = randomGenerator.nextDouble(); + personBuilder.add(new PersonPropertyValueInitialization( + TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK, dValue)); + expectedPropertyValues.put(TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK, dValue); + + // ensure that non-defaulted properties get a value assignment + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.getPropertiesWithoutDefaultValues()) { + Object value = testPersonPropertyId.getRandomPropertyValue(randomGenerator); + personBuilder.add(new PersonPropertyValueInitialization(testPersonPropertyId, value)); + expectedPropertyValues.put(testPersonPropertyId, value); + + } + + personBuilder.add(TestRegionId.REGION_1); + PersonConstructionData personConstructionData = personBuilder.build(); + + // add the person and get its person id + PersonId personId = peopleDataManager.addPerson(personConstructionData); + + // show that the person exists + assertTrue(peopleDataManager.personExists(personId)); + + // show that the person has the correct property values + for (PersonPropertyId personPropertyId : personPropertyIds) { + Object expectedValue = expectedPropertyValues.get(personPropertyId); + Object actualValue = personPropertiesDataManager.getPersonPropertyValue(personId, personPropertyId); + assertEquals(expectedValue, actualValue); + } + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* + * precondition test: if the event contains a PersonPropertyInitialization that + * has a person property value that is not compatible with the corresponding + * property definition + */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = PersonPropertiesTestPluginFactory.factory(100, 5194635938533128930L, (c) -> { + // add a person with some person property auxiliary data + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + PersonConstructionData.Builder personBuilder = PersonConstructionData.builder(); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + personBuilder.add(TestRegionId.getRandomRegionId(randomGenerator)); + personBuilder.add(new PersonPropertyValueInitialization( + TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, 45)); + PersonConstructionData constructionData = personBuilder.build(); + peopleDataManager.addPerson(constructionData); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); + + /* + * precondition test: if the event contains a PersonPropertyInitialization that + * has a null person property value + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = PersonPropertiesTestPluginFactory.factory(100, 4349734439660163798L, (c) -> { + // add a person with some person property auxiliary data + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + PersonConstructionData.Builder personBuilder = PersonConstructionData.builder(); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + personBuilder.add(TestRegionId.getRandomRegionId(randomGenerator)); + personBuilder.add(new PersonPropertyValueInitialization( + TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, null)); + PersonConstructionData constructionData = personBuilder.build(); + peopleDataManager.addPerson(constructionData); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_VALUE, contractException.getErrorType()); + + /* + * precondition test: if the event contains a PersonPropertyInitialization that + * has an unknown person property id + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = PersonPropertiesTestPluginFactory.factory(100, 2152152824636786936L, (c) -> { + // add a person with some person property auxiliary data + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + PersonConstructionData.Builder personBuilder = PersonConstructionData.builder(); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + personBuilder.add(TestRegionId.getRandomRegionId(randomGenerator)); + personBuilder.add(new PersonPropertyValueInitialization( + TestPersonPropertyId.getUnknownPersonPropertyId(), false)); + PersonConstructionData constructionData = personBuilder.build(); + peopleDataManager.addPerson(constructionData); + + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + /* + * precondition test: if the event contains a PersonPropertyInitialization that + * has a null person property id + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = PersonPropertiesTestPluginFactory.factory(100, 8379070211267955743L, (c) -> { + // add a person with some person property auxiliary data + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + PersonConstructionData.Builder personBuilder = PersonConstructionData.builder(); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + // if the event contains a PersonPropertyInitialization that has + // a + // null person property id + personBuilder.add(TestRegionId.getRandomRegionId(randomGenerator)); + personBuilder.add(new PersonPropertyValueInitialization(null, false)); + PersonConstructionData constructionData = personBuilder.build(); + peopleDataManager.addPerson(constructionData); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = PersonPropertiesDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testPersonRemovalEvent() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + /* + * Have the actor remove a person and show that their properties remain during + * the current span of this agent's activation + */ + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + PersonId personId = new PersonId(0); + + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + + assertTrue(peopleDataManager.personExists(personId)); + + PersonPropertyId personPropertyId = TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK; + + // Set the property value to a non-default value. + Integer expectedPropertyValue = 999; + personPropertiesDataManager.setPersonPropertyValue(personId, personPropertyId, expectedPropertyValue); + + // remove the person + peopleDataManager.removePerson(personId); + + // show that the property value is still present + Object actualPropertyValue = personPropertiesDataManager.getPersonPropertyValue(personId, personPropertyId); + assertEquals(expectedPropertyValue, actualPropertyValue); + + })); + + // Have the actor now show that these person properties are no longer + // available + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + + PersonId personId = new PersonId(0); + + // show that the person does not exist + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + assertFalse(peopleDataManager.personExists(personId)); + + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + + PersonPropertyId personPropertyId = TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK; + + ContractException contractException = assertThrows(ContractException.class, + () -> personPropertiesDataManager.getPersonPropertyValue(personId, personPropertyId)); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = PersonPropertiesTestPluginFactory.factory(10, 2020442537537236753L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = PersonPropertiesDataManager.class, name = "definePersonProperty", args = { + PersonPropertyDefinitionInitialization.class }) + public void testDefinePersonProperty() { + + /* + * Show that the PropertyDefinitionInitialization is handled correctly when + * default values EXIST on the property definition + */ + Factory factory = PersonPropertiesTestPluginFactory.factory(100, 3100440347097616280L, (c) -> { + double planTime = 1; + MutableBoolean trackTimes = new MutableBoolean(); + for (TestAuxiliaryPersonPropertyId auxPropertyId : TestAuxiliaryPersonPropertyId.values()) { + + c.addPlan((c2) -> { + PeopleDataManager peopleDataManager = c2.getDataManager(PeopleDataManager.class); + PersonPropertiesDataManager personPropertiesDataManager = c2 + .getDataManager(PersonPropertiesDataManager.class); + PropertyDefinition expectedPropertyDefinition = auxPropertyId.getPropertyDefinition(); + PersonPropertyDefinitionInitialization propertyDefinitionInitialization = // + PersonPropertyDefinitionInitialization.builder()// + .setPersonPropertyId(auxPropertyId)// + .setPropertyDefinition(expectedPropertyDefinition)// + .setTrackTimes(trackTimes.getValue()).build(); + + personPropertiesDataManager.definePersonProperty(propertyDefinitionInitialization); + + // show that the definition was added + PropertyDefinition actualPropertyDefinition = personPropertiesDataManager + .getPersonPropertyDefinition(auxPropertyId); + assertEquals(expectedPropertyDefinition, actualPropertyDefinition); + + // show that the property has the correct initial value + // show that the property has the correct initial time + Object expectedValue = expectedPropertyDefinition.getDefaultValue().get(); + double expectedTime = c2.getTime(); + for (PersonId personId : peopleDataManager.getPeople()) { + + Object actualValue = personPropertiesDataManager.getPersonPropertyValue(personId, + auxPropertyId); + assertEquals(expectedValue, actualValue); + + if (trackTimes.getValue()) { + double actualTime = personPropertiesDataManager.getPersonPropertyTime(personId, + auxPropertyId); + assertEquals(expectedTime, actualTime); + } + } + trackTimes.setValue(!trackTimes.getValue()); + + }, planTime++); + } + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* + * Show that the PropertyDefinitionInitialization is handled correctly when + * default values DO NOT EXIST on the property definition + * + */ + + factory = PersonPropertiesTestPluginFactory.factory(10, 3969826324474876300L, (c) -> { + double planTime = 1; + + MutableBoolean trackTimes = new MutableBoolean(); + for (TestAuxiliaryPersonPropertyId auxPropertyId : TestAuxiliaryPersonPropertyId.values()) { + + c.addPlan((c2) -> { + StochasticsDataManager stochasticsDataManager = c2.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + PeopleDataManager peopleDataManager = c2.getDataManager(PeopleDataManager.class); + PersonPropertiesDataManager personPropertiesDataManager = c2 + .getDataManager(PersonPropertiesDataManager.class); + PropertyDefinition expectedPropertyDefinition = auxPropertyId.getPropertyDefinition(); + /* + * All of the TestAuxiliaryPersonPropertyId associated property definitions have + * default values. We will copy the property definition, but leave the default + * out. + */ + expectedPropertyDefinition = PropertyDefinition.builder()// + .setDefaultValue(expectedPropertyDefinition.getDefaultValue().get())// + .setPropertyValueMutability(expectedPropertyDefinition.propertyValuesAreMutable())// + .setType(expectedPropertyDefinition.getType())// + .build(); + + Map expectedPropertyValues = new LinkedHashMap<>(); + + PersonPropertyDefinitionInitialization.Builder defBuilder = // + PersonPropertyDefinitionInitialization.builder()// + .setPersonPropertyId(auxPropertyId)// + .setPropertyDefinition(expectedPropertyDefinition) + .setTrackTimes(trackTimes.getValue()); + + // + expectedPropertyDefinition.getType(); + for (PersonId personId : peopleDataManager.getPeople()) { + Object randomPropertyValue = auxPropertyId.getRandomPropertyValue(randomGenerator); + defBuilder.addPropertyValue(personId, randomPropertyValue); + expectedPropertyValues.put(personId, randomPropertyValue); + } + + PersonPropertyDefinitionInitialization propertyDefinitionInitialization = defBuilder.build(); + + personPropertiesDataManager.definePersonProperty(propertyDefinitionInitialization); + + // show that the definition was added + PropertyDefinition actualPropertyDefinition = personPropertiesDataManager + .getPersonPropertyDefinition(auxPropertyId); + assertEquals(expectedPropertyDefinition, actualPropertyDefinition); + + // show that the property has the correct initial value + // show that the property has the correct initial time + + double expectedTime = c2.getTime(); + for (PersonId personId : peopleDataManager.getPeople()) { + Object expectedValue = expectedPropertyValues.get(personId); + + Object actualValue = personPropertiesDataManager.getPersonPropertyValue(personId, + auxPropertyId); + assertEquals(expectedValue, actualValue); + + if (trackTimes.getValue()) { + double actualTime = personPropertiesDataManager.getPersonPropertyTime(personId, + auxPropertyId); + assertEquals(expectedTime, actualTime); + } + } + trackTimes.setValue(!trackTimes.getValue()); + }, planTime++); + + } + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition test: if the person property id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = PersonPropertiesTestPluginFactory.factory(0, 4627357002700907595L, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + personPropertiesDataManager.definePersonProperty(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_DEFINITION_INITIALIZATION, contractException.getErrorType()); + + // if the person property already exists + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = PersonPropertiesTestPluginFactory.factory(0, 8802528032031272978L, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + PersonPropertyId personPropertyId = TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; + PropertyDefinition propertyDefinition = TestAuxiliaryPersonPropertyId.PERSON_AUX_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK + .getPropertyDefinition(); + PersonPropertyDefinitionInitialization propertyDefinitionInitialization = // + PersonPropertyDefinitionInitialization.builder()// + .setPersonPropertyId(personPropertyId)// + .setPropertyDefinition(propertyDefinition)// + .build(); + personPropertiesDataManager.definePersonProperty(propertyDefinitionInitialization); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.DUPLICATE_PROPERTY_DEFINITION, contractException.getErrorType()); + + /* + * if the property definition has no default value and there is no included + * value assignment for some extant person + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = PersonPropertiesTestPluginFactory.factory(0, 1498052576475289605L, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + PersonPropertyId personPropertyId = TestAuxiliaryPersonPropertyId.PERSON_AUX_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).build(); + + // get the minimum set of properties that we will need to + // initialize + // for each new person + List requiredPropertyIds = new ArrayList<>(); + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + if (testPersonPropertyId.getPropertyDefinition().getDefaultValue().isEmpty()) { + requiredPropertyIds.add(testPersonPropertyId); + } + } + + PersonConstructionData.Builder personBuilder = PersonConstructionData.builder(); + + // add a first person + personBuilder.add(TestRegionId.REGION_1); + for (TestPersonPropertyId testPersonPropertyId : requiredPropertyIds) { + Object value = testPersonPropertyId.getRandomPropertyValue(randomGenerator); + PersonPropertyValueInitialization personPropertyValueInitialization = new PersonPropertyValueInitialization( + testPersonPropertyId, value); + personBuilder.add(personPropertyValueInitialization); + } + PersonConstructionData personConstructionData = personBuilder.build(); + PersonId personId1 = peopleDataManager.addPerson(personConstructionData); + + // add a second person + personBuilder.add(TestRegionId.REGION_2); + for (TestPersonPropertyId testPersonPropertyId : requiredPropertyIds) { + Object value = testPersonPropertyId.getRandomPropertyValue(randomGenerator); + PersonPropertyValueInitialization personPropertyValueInitialization = new PersonPropertyValueInitialization( + testPersonPropertyId, value); + personBuilder.add(personPropertyValueInitialization); + } + personConstructionData = personBuilder.build(); + peopleDataManager.addPerson(personConstructionData); + + /* + * define a new property without a default value and only set the value for one + * of the two people in the population. + * + * only assign a value to one person + */ + PersonPropertyDefinitionInitialization propertyDefinitionInitialization = // + PersonPropertyDefinitionInitialization.builder()// + .setPersonPropertyId(personPropertyId)// + .setPropertyDefinition(propertyDefinition)// + + .addPropertyValue(personId1, 12)// + .build(); + + personPropertiesDataManager.definePersonProperty(propertyDefinitionInitialization); + + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + + }); + assertEquals(PropertyError.INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = PersonPropertiesDataManager.class, name = "getEventFilterForPersonPropertyUpdateEvent", args = { + PersonPropertyId.class }) + public void testGetEventFilterForPersonPropertyUpdateEvent_property() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + /* + * have an observer subscribe to every person property id + */ + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + for (TestPersonPropertyId propertyId : TestPersonPropertyId.values()) { + EventFilter eventFilter = personPropertiesDataManager + .getEventFilterForPersonPropertyUpdateEvent(propertyId); + assertNotNull(eventFilter); + c.subscribe(eventFilter, (c2, e) -> { + MultiKey multiKey = new MultiKey(c.getTime(), e.personId(), e.personPropertyId(), + e.currentPropertyValue()); + actualObservations.add(multiKey); + }); + } + })); + + /* + * have an actor change every person's properties for all those properties that + * can be changed over three distinct times + */ + for (int i = 1; i < 2; i++) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(i, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + for (TestPersonPropertyId propertyId : TestPersonPropertyId.values()) { + if (propertyId.getPropertyDefinition().propertyValuesAreMutable()) { + for (PersonId personId : peopleDataManager.getPeople()) { + Object randomPropertyValue = propertyId.getRandomPropertyValue(randomGenerator); + personPropertiesDataManager.setPersonPropertyValue(personId, propertyId, + randomPropertyValue); + MultiKey multiKey = new MultiKey(c.getTime(), personId, propertyId, randomPropertyValue); + expectedObservations.add(multiKey); + } + } + } + })); + } + + /* + * have the observer show that the expected and actual observations are equal + */ + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(4, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = PersonPropertiesTestPluginFactory.factory(10, 5585766374187295381L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition test: if the person property id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = PersonPropertiesTestPluginFactory.factory(0, 6844554554783464142L, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + personPropertiesDataManager.getEventFilterForPersonPropertyUpdateEvent(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // precondition test: if the person property id is not known + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = PersonPropertiesTestPluginFactory.factory(0, 334179992057034848L, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + personPropertiesDataManager + .getEventFilterForPersonPropertyUpdateEvent(TestPersonPropertyId.getUnknownPersonPropertyId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PersonPropertiesDataManager.class, name = "getEventFilterForPersonPropertyUpdateEvent", args = { + PersonId.class, PersonPropertyId.class }) + public void testGetEventFilterForPersonPropertyUpdateEvent_person_property() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + Set> selectedPairs = new LinkedHashSet<>(); + + /* + * have an actor determine which people and property pairs will be observed + */ + pluginBuilder.addTestActorPlan("selector", new TestActorPlan(0, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + List people = peopleDataManager.getPeople(); + for (PersonId personId : people) { + if (randomGenerator.nextDouble() < 0.6) { + continue; + } + for (TestPersonPropertyId propertyId : TestPersonPropertyId.values()) { + if (!propertyId.getPropertyDefinition().propertyValuesAreMutable()) { + continue; + } + selectedPairs.add(new Pair<>(personId, propertyId)); + } + } + })); + + /* + * have an observer subscribe to the selected (person, property) pairs + */ + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + for (Pair pair : selectedPairs) { + PersonId personId = pair.getFirst(); + TestPersonPropertyId propertyId = pair.getSecond(); + + EventFilter eventFilter = personPropertiesDataManager + .getEventFilterForPersonPropertyUpdateEvent(personId, propertyId); + assertNotNull(eventFilter); + c.subscribe(eventFilter, (c2, e) -> { + MultiKey multiKey = new MultiKey(c.getTime(), e.personId(), e.personPropertyId(), + e.currentPropertyValue()); + actualObservations.add(multiKey); + }); + } + })); + + /* + * Have an actor change every person's properties for all those properties that + * can be changed over three distinct times. + */ + for (int i = 1; i < 2; i++) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(i, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + for (TestPersonPropertyId propertyId : TestPersonPropertyId.values()) { + if (propertyId.getPropertyDefinition().propertyValuesAreMutable()) { + for (PersonId personId : peopleDataManager.getPeople()) { + Object randomPropertyValue = propertyId.getRandomPropertyValue(randomGenerator); + personPropertiesDataManager.setPersonPropertyValue(personId, propertyId, + randomPropertyValue); + Pair pair = new Pair<>(personId, propertyId); + if (selectedPairs.contains(pair)) { + MultiKey multiKey = new MultiKey(c.getTime(), personId, propertyId, + randomPropertyValue); + expectedObservations.add(multiKey); + } + } + } + } + })); + } + + /* + * have the observer show that the expected and actual observations are equal + */ + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(4, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = PersonPropertiesTestPluginFactory.factory(50, 752337695044384521L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition test: if the person property id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = PersonPropertiesTestPluginFactory.factory(10, 7436809263151926252L, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + List people = peopleDataManager.getPeople(); + assertTrue(people.size() > 0); + PersonId personId = people.get(0); + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + personPropertiesDataManager.getEventFilterForPersonPropertyUpdateEvent(personId, null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // precondition test: if the person property id is not known + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = PersonPropertiesTestPluginFactory.factory(10, 5042142105400574982L, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + List people = peopleDataManager.getPeople(); + assertTrue(people.size() > 0); + PersonId personId = people.get(0); + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + personPropertiesDataManager.getEventFilterForPersonPropertyUpdateEvent(personId, + TestPersonPropertyId.getUnknownPersonPropertyId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + // precondition test: if the person id is null + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = PersonPropertiesTestPluginFactory.factory(10, 2414428612890791850L, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + PersonId nullPersonId = null; + personPropertiesDataManager.getEventFilterForPersonPropertyUpdateEvent(nullPersonId, + TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + // precondition test: if the person id is not known + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = PersonPropertiesTestPluginFactory.factory(10, 6438595550119080771L, (c) -> { + PersonId personId = new PersonId(1000000); + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + personPropertiesDataManager.getEventFilterForPersonPropertyUpdateEvent(personId, + TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + } + + private static class LocalPersonPropertyId implements PersonPropertyId { + private final int id; + + public LocalPersonPropertyId(int id) { + this.id = id; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + id; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof LocalPersonPropertyId)) { + return false; + } + LocalPersonPropertyId other = (LocalPersonPropertyId) obj; + if (id != other.id) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("LocalPersonPropertyId [id="); + builder.append(id); + builder.append("]"); + return builder.toString(); + } + + } + + private void testPropertyUpdateEvent_previous(TestPersonPropertyId testPersonPropertyId, List chosenValues, + List sourceValues, long seed) { + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + + int planTime = 0; + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(planTime++, (c) -> { + // set a bunch of random values + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + for (PersonId personId : peopleDataManager.getPeople()) { + int index = randomGenerator.nextInt(sourceValues.size()); + Object value = sourceValues.get(index); + personPropertiesDataManager.setPersonPropertyValue(personId, testPersonPropertyId, value); + } + })); + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(planTime++, (c) -> { + // subscribe to every chosen value + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + for (int i = 0; i < chosenValues.size(); i++) { + EventFilter eventFilter = personPropertiesDataManager + .getEventFilterForPersonPropertyUpdateEvent(testPersonPropertyId, chosenValues.get(i), false); + c.subscribe(eventFilter, (c2, e) -> { + MultiKey multiKey = new MultiKey(c.getTime(), e.personId(), e.personPropertyId(), + e.getPreviousPropertyValue()); + actualObservations.add(multiKey); + }); + } + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(planTime++, (c) -> { + // set a bunch of random values + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + for (PersonId personId : peopleDataManager.getPeople()) { + int index = randomGenerator.nextInt(sourceValues.size()); + Object value = sourceValues.get(index); + Object previousValue = personPropertiesDataManager.getPersonPropertyValue(personId, + testPersonPropertyId); + personPropertiesDataManager.setPersonPropertyValue(personId, testPersonPropertyId, value); + if (chosenValues.contains(previousValue)) { + MultiKey multiKey = new MultiKey(c.getTime(), personId, testPersonPropertyId, previousValue); + expectedObservations.add(multiKey); + } + } + })); + + // show that we only get the subscribed events + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(planTime++, (c) -> { + assertTrue(expectedObservations.size() >= sourceValues.size() / 4); + assertEquals(expectedObservations, actualObservations); + })); + + // run the sim with 50 people + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = PersonPropertiesTestPluginFactory.factory(50, seed, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + private void testPropertyUpdateEvent_current(TestPersonPropertyId testPersonPropertyId, List chosenValues, + List sourceValues, long seed) { + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + + int planTime = 0; + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(planTime++, (c) -> { + // subscribe to every chosen value + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + for (int i = 0; i < chosenValues.size(); i++) { + EventFilter eventFilter = personPropertiesDataManager + .getEventFilterForPersonPropertyUpdateEvent(testPersonPropertyId, chosenValues.get(i), true); + c.subscribe(eventFilter, (c2, e) -> { + MultiKey multiKey = new MultiKey(c.getTime(), e.personId(), e.personPropertyId(), + e.getCurrentPropertyValue()); + actualObservations.add(multiKey); + }); + } + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(planTime++, (c) -> { + // set a bunch of random values + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + for (PersonId personId : peopleDataManager.getPeople()) { + int index = randomGenerator.nextInt(sourceValues.size()); + Object value = sourceValues.get(index); + personPropertiesDataManager.setPersonPropertyValue(personId, testPersonPropertyId, value); + if (chosenValues.contains(value)) { + MultiKey multiKey = new MultiKey(c.getTime(), personId, testPersonPropertyId, value); + expectedObservations.add(multiKey); + } + } + })); + + // show that we only get the subscribed events + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(planTime++, (c) -> { + assertTrue(expectedObservations.size() >= sourceValues.size() / 4); + assertEquals(expectedObservations, actualObservations); + })); + + // run the sim with 50 people + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = PersonPropertiesTestPluginFactory.factory(50, seed, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = PersonPropertiesDataManager.class, name = "getEventFilterForPersonPropertyUpdateEvent", args = { + PersonPropertyId.class, Object.class, boolean.class }) + public void testGetEventFilterForPersonPropertyUpdateEvent_propertyId_object() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8796864982253772625L); + + // get testPropertyIds to use + List testPersonPropertyIds = new ArrayList<>(); + + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + if (testPersonPropertyId.getPropertyDefinition().propertyValuesAreMutable()) { + testPersonPropertyIds.add(testPersonPropertyId); + } + } + + // set and subscribe to test actor plans for each testPropertyId + for (TestPersonPropertyId testPersonPropertyId : testPersonPropertyIds) { + + // generate 50 random values + List sourceValues = new ArrayList<>(); + for (int i = 0; i < 50; i++) { + Object value = testPersonPropertyId.getRandomPropertyValue(randomGenerator); + if (!sourceValues.contains(value)) { + sourceValues.add(value); + } + } + + // pick out unique values to subscribe to + List chosenValues = new ArrayList<>(); + for (int i = 0; i < sourceValues.size(); i++) { + Object value = sourceValues.get(i); + if (i % 2 == 0) { + chosenValues.add(value); + } + } + + testPropertyUpdateEvent_current(testPersonPropertyId, chosenValues, sourceValues, + randomGenerator.nextLong()); + testPropertyUpdateEvent_previous(testPersonPropertyId, chosenValues, sourceValues, + randomGenerator.nextLong()); + } + + // precondition tests + + // precondition test: if the person property id is null + + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory = PersonPropertiesTestPluginFactory.factory(50, 7212207259440375049L, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + personPropertiesDataManager.getEventFilterForPersonPropertyUpdateEvent(null, 1, true); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // precondition test: if the person property id is not known + contractException = assertThrows(ContractException.class, () -> { + Factory factory = PersonPropertiesTestPluginFactory.factory(50, 7580223995144844140L, (c) -> { + PersonPropertyId unknownPropertyId = TestPersonPropertyId.getUnknownPersonPropertyId(); + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + personPropertiesDataManager.getEventFilterForPersonPropertyUpdateEvent(unknownPropertyId, 1, true); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + // precondition test: if the property value is null + contractException = assertThrows(ContractException.class, () -> { + Factory factory = PersonPropertiesTestPluginFactory.factory(50, 451632169807459388L, (c) -> { + TestPersonPropertyId testPersonPropertyId = TestPersonPropertyId.PERSON_PROPERTY_5_INTEGER_MUTABLE_TRACK; + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + personPropertiesDataManager.getEventFilterForPersonPropertyUpdateEvent(testPersonPropertyId, null, + true); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_VALUE, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = PersonPropertiesDataManager.class, name = "getEventFilterForPersonPropertyDefinitionEvent", args = {}) + public void testGetEventFilterForPersonPropertyDefinitionEvent() { + // + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + /* + * have an observer subscribe to person property definition events + */ + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + EventFilter eventFilter = personPropertiesDataManager + .getEventFilterForPersonPropertyDefinitionEvent(); + assertNotNull(eventFilter); + c.subscribe(eventFilter, (c2, e) -> { + MultiKey multiKey = new MultiKey(c.getTime(), e.personPropertyId()); + actualObservations.add(multiKey); + }); + + })); + + /* + * Have an actor add several new person property definitions at various times. + */ + + PropertyDefinition propertyDefinition = PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(0)// + .build(); + IntStream.range(1, 4).forEach((i) -> { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(i, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + PersonPropertyId personPropertyId = new LocalPersonPropertyId(i); + + PersonPropertyDefinitionInitialization personPropertyDefinitionInitialization = // + + PersonPropertyDefinitionInitialization.builder()// + .setPersonPropertyId(personPropertyId)// + .setPropertyDefinition(propertyDefinition)// + .build(); + personPropertiesDataManager.definePersonProperty(personPropertyDefinitionInitialization); + expectedObservations.add(new MultiKey((double) i, personPropertyId)); + + })); + }); + + /* + * have the observer show that the expected and actual observations are equal + */ + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(4, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = PersonPropertiesTestPluginFactory.factory(100, 6462842714052608355L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = PersonPropertiesDataManager.class, name = "getEventFilterForPersonPropertyUpdateEvent", args = { + RegionId.class, PersonPropertyId.class }) + public void testGetEventFilterForPersonPropertyUpdateEvent_region_property() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + Set> selectedPairs = new LinkedHashSet<>(); + + /* + * have an actor determine which region and property pairs will be observed + */ + pluginBuilder.addTestActorPlan("selector", new TestActorPlan(0, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + Set regionIds = regionsDataManager.getRegionIds(); + for (RegionId regionId : regionIds) { + if (randomGenerator.nextDouble() < 0.6) { + continue; + } + for (TestPersonPropertyId propertyId : TestPersonPropertyId.values()) { + if (!propertyId.getPropertyDefinition().propertyValuesAreMutable()) { + continue; + } + selectedPairs.add(new Pair<>(regionId, propertyId)); + } + } + })); + + /* + * have an observer subscribe to the selected (person, property) pairs + */ + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + for (Pair pair : selectedPairs) { + RegionId regionId = pair.getFirst(); + TestPersonPropertyId propertyId = pair.getSecond(); + EventFilter eventFilter = personPropertiesDataManager + .getEventFilterForPersonPropertyUpdateEvent(regionId, propertyId); + assertNotNull(eventFilter); + c.subscribe(eventFilter, (c2, e) -> { + MultiKey multiKey = new MultiKey(c.getTime(), e.personId(), e.personPropertyId(), + e.currentPropertyValue()); + actualObservations.add(multiKey); + }); + } + })); + + /* + * Have an actor change every person's properties for all those properties that + * can be changed over three distinct times. + */ + for (int i = 1; i < 2; i++) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(i, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + for (TestPersonPropertyId propertyId : TestPersonPropertyId.values()) { + if (propertyId.getPropertyDefinition().propertyValuesAreMutable()) { + for (PersonId personId : peopleDataManager.getPeople()) { + Object randomPropertyValue = propertyId.getRandomPropertyValue(randomGenerator); + personPropertiesDataManager.setPersonPropertyValue(personId, propertyId, + randomPropertyValue); + RegionId regionId = regionsDataManager.getPersonRegion(personId); + Pair pair = new Pair<>(regionId, propertyId); + + if (selectedPairs.contains(pair)) { + MultiKey multiKey = new MultiKey(c.getTime(), personId, propertyId, + randomPropertyValue); + expectedObservations.add(multiKey); + } + } + } + } + })); + } + + /* + * have the observer show that the expected and actual observations are equal + */ + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(4, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = PersonPropertiesTestPluginFactory.factory(100, 2659336653501353916L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition test: if the person property id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = PersonPropertiesTestPluginFactory.factory(10, 6900159997685687591L, (c) -> { + RegionId regionId = TestRegionId.REGION_1; + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + personPropertiesDataManager.getEventFilterForPersonPropertyUpdateEvent(regionId, null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // precondition test: if the person property id is not known + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = PersonPropertiesTestPluginFactory.factory(10, 7580223995144844140L, (c) -> { + RegionId regionId = TestRegionId.REGION_1; + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + personPropertiesDataManager.getEventFilterForPersonPropertyUpdateEvent(regionId, + TestPersonPropertyId.getUnknownPersonPropertyId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + // precondition test: if the region id is null + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = PersonPropertiesTestPluginFactory.factory(10, 451632169807459388L, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + RegionId nullRegionId = null; + personPropertiesDataManager.getEventFilterForPersonPropertyUpdateEvent(nullRegionId, + TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); + + // precondition test: if the person id is not known + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = PersonPropertiesTestPluginFactory.factory(10, 558207030058239684L, (c) -> { + RegionId regionId = TestRegionId.getUnknownRegionId(); + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + personPropertiesDataManager.getEventFilterForPersonPropertyUpdateEvent(regionId, + TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = PersonPropertiesDataManager.class, name = "getEventFilterForPersonPropertyUpdateEvent", args = {}) + public void testGetEventFilterForPersonPropertyUpdateEvent() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + /* + * have an observer subscribe to every person property id + */ + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + + EventFilter eventFilter = personPropertiesDataManager + .getEventFilterForPersonPropertyUpdateEvent(); + assertNotNull(eventFilter); + c.subscribe(eventFilter, (c2, e) -> { + MultiKey multiKey = new MultiKey(c.getTime(), e.personId(), e.personPropertyId(), + e.currentPropertyValue()); + actualObservations.add(multiKey); + }); + + })); + + /* + * have an actor change every person's properties for all those properties that + * can be changed over three distinct times + */ + for (int i = 1; i < 2; i++) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(i, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + for (TestPersonPropertyId propertyId : TestPersonPropertyId.values()) { + if (propertyId.getPropertyDefinition().propertyValuesAreMutable()) { + for (PersonId personId : peopleDataManager.getPeople()) { + Object randomPropertyValue = propertyId.getRandomPropertyValue(randomGenerator); + personPropertiesDataManager.setPersonPropertyValue(personId, propertyId, + randomPropertyValue); + MultiKey multiKey = new MultiKey(c.getTime(), personId, propertyId, randomPropertyValue); + expectedObservations.add(multiKey); + } + } + } + })); + } + + /* + * have the observer show that the expected and actual observations are equal + */ + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(4, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = PersonPropertiesTestPluginFactory.factory(10, 3804034702019855460L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + /** + * Demonstrates that the data manager exhibits run continuity. The state of the + * data manager is not effected by repeatedly starting and stopping the + * simulation. + */ + @Test + @UnitTestMethod(target = PersonPropertiesDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testStateContinuity() { + + /* + * Note that we are not testing the content of the plugin datas -- that is + * covered by the other state tests. We show here only that the resulting plugin + * data state is the same without regard to how we break up the run. + */ + + Set pluginDatas = new LinkedHashSet<>(); + pluginDatas.add(testStateContinuity(1)); + pluginDatas.add(testStateContinuity(5)); + pluginDatas.add(testStateContinuity(10)); + + assertEquals(1, pluginDatas.size()); + + } + + /* + * Returns the person properties plugin data resulting from several person + * property events over several days. Attempts to stop and start the simulation + * by the given number of increments. + */ + private String testStateContinuity(int incrementCount) { + String result = null; + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2767991670068250768L); + + /* + * Build the RunContinuityPluginData with several context consumers that will + * add people, person properties and set some person properties + */ + RunContinuityPluginData.Builder continuityBuilder = RunContinuityPluginData.builder(); + + // Add a few people + continuityBuilder.addContextConsumer(0.5, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + + peopleDataManager.addPerson(PersonConstructionData.builder().add(TestRegionId.REGION_1).build()); + peopleDataManager.addPerson(PersonConstructionData.builder().add(TestRegionId.REGION_2).build()); + peopleDataManager.addPerson(PersonConstructionData.builder().add(TestRegionId.REGION_3).build()); + peopleDataManager.addPerson(PersonConstructionData.builder().add(TestRegionId.REGION_4).build()); + peopleDataManager.addPerson(PersonConstructionData.builder().add(TestRegionId.REGION_3).build()); + peopleDataManager.addPerson(PersonConstructionData.builder().add(TestRegionId.REGION_2).build()); + peopleDataManager.addPerson(PersonConstructionData.builder().add(TestRegionId.REGION_1).build()); + + }); + + // define a few person properties + continuityBuilder.addContextConsumer(1.2, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + TestPersonPropertyId testPersonPropertyId = TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; + + PersonPropertyDefinitionInitialization personPropertyDefinitionInitialization = // + PersonPropertyDefinitionInitialization.builder()// + .setPersonPropertyId(testPersonPropertyId)// + .setPropertyDefinition(testPersonPropertyId.getPropertyDefinition())// + .setTrackTimes(true)// + .build(); + personPropertiesDataManager.definePersonProperty(personPropertyDefinitionInitialization); + + testPersonPropertyId = TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK; + + personPropertyDefinitionInitialization = // + PersonPropertyDefinitionInitialization.builder()// + .setPersonPropertyId(testPersonPropertyId)// + .setPropertyDefinition(testPersonPropertyId.getPropertyDefinition())// + .setTrackTimes(true)// + .build(); + personPropertiesDataManager.definePersonProperty(personPropertyDefinitionInitialization); + + }); + + // set some person properties + continuityBuilder.addContextConsumer(1.8, (c) -> { + + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + TestPersonPropertyId testPersonPropertyId; + + testPersonPropertyId = TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK; + personPropertiesDataManager.setPersonPropertyValue(new PersonId(4), testPersonPropertyId, 14); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(6), testPersonPropertyId, 88); + + testPersonPropertyId = TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; + personPropertiesDataManager.setPersonPropertyValue(new PersonId(0), testPersonPropertyId, true); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(3), testPersonPropertyId, true); + + }); + + // define another property without a default, add a few more people + continuityBuilder.addContextConsumer(2.05, (c) -> { + + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + + TestPersonPropertyId testPersonPropertyId = TestPersonPropertyId.PERSON_PROPERTY_9_DOUBLE_IMMUTABLE_NO_TRACK; + PersonPropertyDefinitionInitialization.Builder builder = PersonPropertyDefinitionInitialization.builder();// + builder.setPersonPropertyId(testPersonPropertyId);// + builder.setPropertyDefinition(testPersonPropertyId.getPropertyDefinition());// + builder.setTrackTimes(false);// + List people = peopleDataManager.getPeople(); + Collections.shuffle(people, new Random(randomGenerator.nextLong())); + for (PersonId personId : people) { + builder.addPropertyValue(personId, testPersonPropertyId.getRandomPropertyValue(randomGenerator)); + } + + PersonPropertyDefinitionInitialization personPropertyDefinitionInitialization = builder.build(); + personPropertiesDataManager.definePersonProperty(personPropertyDefinitionInitialization); + + PersonConstructionData personConstructionData = PersonConstructionData.builder()// + .add(TestRegionId.REGION_1)// + .add(new PersonPropertyValueInitialization(testPersonPropertyId, 2.7))// + .build(); + + peopleDataManager.addPerson(personConstructionData); + + personConstructionData = PersonConstructionData.builder()// + .add(TestRegionId.REGION_2)// + .add(new PersonPropertyValueInitialization(testPersonPropertyId, 4.7))// + .build(); + + peopleDataManager.addPerson(personConstructionData); + + personConstructionData = PersonConstructionData.builder()// + .add(TestRegionId.REGION_3)// + .add(new PersonPropertyValueInitialization(testPersonPropertyId, 8.9))// + .build(); + + peopleDataManager.addPerson(personConstructionData); + + }); + + // set some more properties + continuityBuilder.addContextConsumer(4.2, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + + TestPersonPropertyId testPersonPropertyId = TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; + personPropertiesDataManager.setPersonPropertyValue(new PersonId(5), testPersonPropertyId, false); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(9), testPersonPropertyId, true); + + testPersonPropertyId = TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK; + personPropertiesDataManager.setPersonPropertyValue(new PersonId(2), testPersonPropertyId, 124); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(9), testPersonPropertyId, 555); + + c.releaseOutput(personPropertiesDataManager.toString()); + }); + + RunContinuityPluginData runContinuityPluginData = continuityBuilder.build(); + + // Build an empty people plugin data for time zero + PeoplePluginData peoplePluginData = PeoplePluginData.builder().build(); + + // Build a regions plugin data with just a few regions + RegionsPluginData.Builder regionsBuilder = RegionsPluginData.builder(); + for (TestRegionId testRegionId : TestRegionId.values()) { + regionsBuilder.addRegion(testRegionId); + } + RegionsPluginData regionsPluginData = regionsBuilder.build(); + + // build an empty person properties plugin data + PersonPropertiesPluginData personPropertiesPluginData = PersonPropertiesPluginData.builder().build(); + + // build the initial simulation state data -- time starts at zero + SimulationState simulationState = SimulationState.builder().build(); + + /* + * Run the simulation in one day increments until all the plans in the run + * continuity plugin data have been executed + */ + double haltTime = 0; + double maxTime = Double.NEGATIVE_INFINITY; + for (Pair> pair : runContinuityPluginData.getConsumers()) { + Double time = pair.getFirst(); + maxTime = FastMath.max(maxTime, time); + } + double timeIncrement = maxTime / incrementCount; + while (!runContinuityPluginData.allPlansComplete()) { + haltTime += timeIncrement; + + // build the run continuity plugin + Plugin runContinuityPlugin = RunContinuityPlugin.builder()// + .setRunContinuityPluginData(runContinuityPluginData)// + .build(); + + // build the people plugin + Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(peoplePluginData); + + // build the regions plugin + Plugin regionsPlugin = RegionsPlugin.builder().setRegionsPluginData(regionsPluginData).getRegionsPlugin(); + + // build the person properties plugin + Plugin personPropertyPlugin = PersonPropertiesPlugin.builder() + .setPersonPropertiesPluginData(personPropertiesPluginData).getPersonPropertyPlugin(); + + TestOutputConsumer outputConsumer = new TestOutputConsumer(); + + // execute the simulation so that it produces a people plugin data + Simulation simulation = Simulation.builder()// + .addPlugin(peoplePlugin)// + .addPlugin(runContinuityPlugin)// + .addPlugin(regionsPlugin)// + .addPlugin(personPropertyPlugin)// + .setSimulationHaltTime(haltTime)// + .setRecordState(true)// + .setOutputConsumer(outputConsumer)// + .setSimulationState(simulationState)// + .build();// + simulation.execute(); + + // retrieve the people plugin data + peoplePluginData = outputConsumer.getOutputItem(PeoplePluginData.class).get(); + + // retrieve the simulation state + simulationState = outputConsumer.getOutputItem(SimulationState.class).get(); + + // retrieve the region plugin data + regionsPluginData = outputConsumer.getOutputItem(RegionsPluginData.class).get(); + + // retrieve the person properties plugin data + personPropertiesPluginData = outputConsumer.getOutputItem(PersonPropertiesPluginData.class).get(); + + // retrieve the run continuity plugin data + runContinuityPluginData = outputConsumer.getOutputItem(RunContinuityPluginData.class).get(); + + Optional optional = outputConsumer.getOutputItem(String.class); + if (optional.isPresent()) { + result = optional.get(); + } + + } + + // show that the resulting string is relatively large + assertNotNull(result); + assertTrue(result.length() > 100); + + return result; + + } + + @Test + @UnitTestMethod(target = PersonPropertiesDataManager.class, name = "isPropertyTimeTracked", args = { + PersonPropertyId.class }) + public void testIsPropertyTimeTracked() { + + Factory factory = PersonPropertiesTestPluginFactory.factory(0, 2209705385008769618L, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + assertEquals(testPersonPropertyId.isTimeTracked(), + personPropertiesDataManager.isPropertyTimeTracked(testPersonPropertyId)); + } + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = PersonPropertiesDataManager.class, name = "getPropertyDefinitionTime", args = { + PersonPropertyId.class }) + public void testGetPropertyDefinitionTime() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2518312427007099543L); + // create a map to hold the expected property definition times + Map expectedDefinitionTimes = new LinkedHashMap<>(); + + /* + * We will have to build the person properties plugin data since the factory + * sets the definition times to zero. Note that the definition times will all be + * in the first day. + */ + PersonPropertiesPluginData.Builder personPropertyBuilder = PersonPropertiesPluginData.builder(); + + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.getPersonPropertyIds()) { + double defaultTime = randomGenerator.nextDouble(); + personPropertyBuilder.definePersonProperty(testPersonPropertyId, + testPersonPropertyId.getPropertyDefinition(), defaultTime, testPersonPropertyId.isTimeTracked()); + expectedDefinitionTimes.put(testPersonPropertyId, defaultTime); + } + + PersonPropertiesPluginData personPropertiesPluginData = personPropertyBuilder.build(); + + /* + * We will also have to build the TestPlugin data since we are assigning the + * actor plan to execute at time = 2 + */ + TestPluginData.Builder testPluginDataBuilder = TestPluginData.builder(); + testPluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + assertEquals(expectedDefinitionTimes.get(testPersonPropertyId), + personPropertiesDataManager.getPropertyDefinitionTime(testPersonPropertyId)); + } + })); + TestPluginData testPluginData = testPluginDataBuilder.build(); + + Factory factory = PersonPropertiesTestPluginFactory.factory(0, 2209705385008769618L, testPluginData); + + factory.setPersonPropertiesPluginData(personPropertiesPluginData); + + // We will have to start the simulation after the property definition times and + // before the actor's task, so we choose time = 1 + SimulationState simulationState = SimulationState.builder().setStartTime(1).build(); + + TestSimulation.builder().addPlugins(factory.getPlugins()).setSimulationState(simulationState).build().execute(); + } + + @Test + @UnitTestMethod(target = PersonPropertiesDataManager.class, name = "toString", args = {}) + public void testToString() { + + /* + * We are trying to force in some features and complexity into the person + * properties data manager, so we will not use the standard factory pattern and + * instead create all of the plugins from scratch. + */ + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8755233431852791770L); + + // Generate the people plugin + PeoplePluginData.Builder peoplePluginDataBuilder = PeoplePluginData.builder(); + + for (int i = 0; i < 10; i++) { + int id = 2 * i + 5; + peoplePluginDataBuilder.addPersonRange(new PersonRange(id, id)); + } + PeoplePluginData peoplePluginData = peoplePluginDataBuilder.build(); + List people = peoplePluginData.getPersonIds(); + Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(peoplePluginData); + + // Generate the person properties plugin + PersonPropertiesPluginData.Builder personPropertiesPluginDataBuilder = PersonPropertiesPluginData.builder(); + double defTime = 0; + boolean track = false; + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + personPropertiesPluginDataBuilder.definePersonProperty(testPersonPropertyId, + testPersonPropertyId.getPropertyDefinition(), defTime, track); + + for (PersonId personId : people) { + Object propertyValue = testPersonPropertyId.getRandomPropertyValue(randomGenerator); + personPropertiesPluginDataBuilder.setPersonPropertyValue(personId, testPersonPropertyId, propertyValue); + if (track) { + personPropertiesPluginDataBuilder.setPersonPropertyTime(personId, testPersonPropertyId, + defTime + randomGenerator.nextDouble() * 10); + } + } + defTime += 10; + track = !track; + } + + PersonPropertiesPluginData personPropertiesPluginData = personPropertiesPluginDataBuilder.build(); + Plugin personPropertyPlugin = PersonPropertiesPlugin.builder() + .setPersonPropertiesPluginData(personPropertiesPluginData).getPersonPropertyPlugin(); + + // Generate the regions plugin + RegionsPluginData.Builder regionsPluginDataBuilder = RegionsPluginData.builder(); + for (TestRegionId testRegionId : TestRegionId.values()) { + regionsPluginDataBuilder.addRegion(testRegionId); + } + for (PersonId personId : people) { + regionsPluginDataBuilder.addPerson(personId, TestRegionId.getRandomRegionId(randomGenerator)); + } + RegionsPluginData regionsPluginData = regionsPluginDataBuilder.build(); + Plugin regionsPlugin = RegionsPlugin.builder().setRegionsPluginData(regionsPluginData).getRegionsPlugin(); + + // Generate the stochastics plugin + StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder() + .setMainRNGState(WellState.builder().setSeed(randomGenerator.nextLong()).build()).build(); + Plugin stochasticsPlugin = StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData); + + /* + * Generate the test plugin. We will assign the actor plan to execute at time = 2 days after the + * last definition time + */ + TestPluginData.Builder testPluginDataBuilder = TestPluginData.builder(); + testPluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(defTime + 2, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + String actualValue = personPropertiesDataManager.toString(); + + + //Expected value verified by inspection + String expectedValue = "PersonPropertiesDataManager [" + + "propertyDefinitions={" + + "PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK=PropertyDefinition [type=class java.lang.Boolean, propertyValuesAreMutable=true, defaultValue=false], " + + "PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK=PropertyDefinition [type=class java.lang.Integer, propertyValuesAreMutable=true, defaultValue=0], " + + "PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK=PropertyDefinition [type=class java.lang.Double, propertyValuesAreMutable=true, defaultValue=0.0], " + + "PERSON_PROPERTY_4_BOOLEAN_MUTABLE_TRACK=PropertyDefinition [type=class java.lang.Boolean, propertyValuesAreMutable=true, defaultValue=false], " + + "PERSON_PROPERTY_5_INTEGER_MUTABLE_TRACK=PropertyDefinition [type=class java.lang.Integer, propertyValuesAreMutable=true, defaultValue=0], " + + "PERSON_PROPERTY_6_DOUBLE_MUTABLE_TRACK=PropertyDefinition [type=class java.lang.Double, propertyValuesAreMutable=true, defaultValue=0.0], " + + "PERSON_PROPERTY_7_BOOLEAN_IMMUTABLE_NO_TRACK=PropertyDefinition [type=class java.lang.Boolean, propertyValuesAreMutable=false, defaultValue=false], " + + "PERSON_PROPERTY_8_INTEGER_IMMUTABLE_NO_TRACK=PropertyDefinition [type=class java.lang.Integer, propertyValuesAreMutable=false, defaultValue=0], " + + "PERSON_PROPERTY_9_DOUBLE_IMMUTABLE_NO_TRACK=PropertyDefinition [type=class java.lang.Double, propertyValuesAreMutable=false, defaultValue=null]}, " + + + + "propertyDefinitionTimes={" + + "PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK=0.0, " + + "PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK=10.0, " + + "PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK=20.0, " + + "PERSON_PROPERTY_4_BOOLEAN_MUTABLE_TRACK=30.0, " + + "PERSON_PROPERTY_5_INTEGER_MUTABLE_TRACK=40.0, " + + "PERSON_PROPERTY_6_DOUBLE_MUTABLE_TRACK=50.0, " + + "PERSON_PROPERTY_7_BOOLEAN_IMMUTABLE_NO_TRACK=60.0, " + + "PERSON_PROPERTY_8_INTEGER_IMMUTABLE_NO_TRACK=70.0, " + + "PERSON_PROPERTY_9_DOUBLE_IMMUTABLE_NO_TRACK=80.0}, " + + + + "propertyValues={" + + "PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK=BooleanPropertyManager [boolContainer=BooleanContainer [defaultValue=false, bitSet=[5=false, 7=true, 9=false, 11=false, 13=true, 15=false, 17=false, 19=false, 21=false, 23=true]]], " + + "PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK=IntPropertyManager [intValueContainer=IntValueContainer [subTypeArray=IntArray [values=[5=-1784993732, 7=-109471333, 9=-641697795, 11=-853847212, 13=1855748319, 15=1827577953, 17=177514276, 19=-1799284826, 21=-626016377, 23=-1823346824], defaultValue=0]], intValueType=INT], " + + "PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK=DoublePropertyManager [doubleValueContainer=DoubleValueContainer [values=[5=0.5824618866151392, 7=0.32445713185273983, 9=0.03265419470130482, 11=0.8071539615798824, 13=0.8762355391369716, 15=0.033260956343352355, 17=0.7391026590158609, 19=0.9140171970828741, 21=0.15690598584870608, 23=0.17768940550077428], defaultValue=0.0]], " + + "PERSON_PROPERTY_4_BOOLEAN_MUTABLE_TRACK=BooleanPropertyManager [boolContainer=BooleanContainer [defaultValue=false, bitSet=[5=true, 7=false, 9=false, 11=true, 13=true, 15=false, 17=false, 19=false, 21=false, 23=false]]], " + + "PERSON_PROPERTY_5_INTEGER_MUTABLE_TRACK=IntPropertyManager [intValueContainer=IntValueContainer [subTypeArray=IntArray [values=[5=1537669152, 7=-1212722720, 9=-1321990665, 11=1164663499, 13=1659788389, 15=-2049928903, 17=-1574793757, 19=937176171, 21=-2026579870, 23=1502045915], defaultValue=0]], intValueType=INT], " + + "PERSON_PROPERTY_6_DOUBLE_MUTABLE_TRACK=DoublePropertyManager [doubleValueContainer=DoubleValueContainer [values=[5=0.6435571764491883, 7=0.15180051697199248, 9=0.8270405563499779, 11=0.5170625153214135, 13=0.7625551132258688, 15=0.8593578035035705, 17=0.27039127435029386, 19=0.04695698817506089, 21=0.7412996624131245, 23=0.514107728782623], defaultValue=0.0]], " + + "PERSON_PROPERTY_7_BOOLEAN_IMMUTABLE_NO_TRACK=BooleanPropertyManager [boolContainer=BooleanContainer [defaultValue=false, bitSet=[5=false, 7=false, 9=true, 11=false, 13=false, 15=true, 17=false, 19=true, 21=true, 23=true]]], " + + "PERSON_PROPERTY_8_INTEGER_IMMUTABLE_NO_TRACK=IntPropertyManager [intValueContainer=IntValueContainer [subTypeArray=IntArray [values=[5=1695573863, 7=-1825255758, 9=-699523552, 11=415571646, 13=-1764338201, 15=1365442318, 17=-1168885721, 19=-217000921, 21=-1935999918, 23=1816541785], defaultValue=0]], intValueType=INT], " + + "PERSON_PROPERTY_9_DOUBLE_IMMUTABLE_NO_TRACK=DoublePropertyManager [doubleValueContainer=DoubleValueContainer [values=[5=0.9464229716200492, 7=0.8780260194201455, 9=0.9049661618368101, 11=0.5378088867498643, 13=0.8768640339684239, 15=0.5485275606992501, 17=0.13054650062513473, 19=0.05010982715562107, 21=0.007152764713832971, 23=0.9936814752334928], defaultValue=0.0]]}, propertyTrackingPolicies={PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK=false, PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK=true, PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK=false, PERSON_PROPERTY_4_BOOLEAN_MUTABLE_TRACK=true, PERSON_PROPERTY_5_INTEGER_MUTABLE_TRACK=false, PERSON_PROPERTY_6_DOUBLE_MUTABLE_TRACK=true, PERSON_PROPERTY_7_BOOLEAN_IMMUTABLE_NO_TRACK=false, PERSON_PROPERTY_8_INTEGER_IMMUTABLE_NO_TRACK=true, PERSON_PROPERTY_9_DOUBLE_IMMUTABLE_NO_TRACK=false}, " + + + + "propertyTimes={" + + "PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK=DoubleValueContainer [values=[5=15.08171142451204, 7=15.411062205893236, 9=16.542833291164307, 11=16.253824882614325, 13=11.303675742845412, 15=16.871251981539427, 17=14.77701648273313, 19=12.925171654145231, 21=16.51055558262879, 23=12.211953000397838], defaultValue=10.0], " + + "PERSON_PROPERTY_4_BOOLEAN_MUTABLE_TRACK=DoubleValueContainer [values=[5=36.989398748334274, 7=32.63875498464337, 9=39.665805066056954, 11=37.97975065250025, 13=34.12721167419171, 15=38.78454108049003, 17=38.1994261250516, 19=39.056507171734374, 21=36.24325158296854, 23=34.51135513069816], defaultValue=30.0], " + + "PERSON_PROPERTY_6_DOUBLE_MUTABLE_TRACK=DoubleValueContainer [values=[5=52.56714915599213, 7=57.138725245642775, 9=56.84920656152565, 11=57.252923547109155, 13=54.44141841857151, 15=56.536139013953296, 17=58.95502858796634, 19=55.16555180553936, 21=51.29647283837218, 23=55.85289756382876], defaultValue=50.0], " + + "PERSON_PROPERTY_8_INTEGER_IMMUTABLE_NO_TRACK=DoubleValueContainer [values=[5=71.29830149062961, 7=71.9966285794095, 9=79.56528585286935, 11=78.67607305409997, 13=71.7323823816627, 15=71.42041658535238, 17=79.30789515021706, 19=73.03586146032546, 21=79.23203253002579, 23=70.76649742399243], defaultValue=70.0]}]"; + + + assertEquals(expectedValue, actualValue); + })); + TestPluginData testPluginData = testPluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // We will have to start the simulation after the property definition times and + // before the actor's task, so we choose time = 1 day after the last definition + // time + SimulationState simulationState = SimulationState.builder().setStartTime(defTime + 1).build(); + + + //Execute the simulation + TestSimulation.builder()// + .addPlugin(peoplePlugin)// + .addPlugin(testPlugin)// + .addPlugin(personPropertyPlugin)// + .addPlugin(regionsPlugin)// + .addPlugin(stochasticsPlugin)// + .setSimulationState(simulationState).build().execute(); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/datamanagers/AT_PersonPropertyPluginData.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/datamanagers/AT_PersonPropertyPluginData.java new file mode 100644 index 000000000..b6ae8b176 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/datamanagers/AT_PersonPropertyPluginData.java @@ -0,0 +1,1283 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.PluginDataBuilder; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyError; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.testsupport.TestPersonPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTag; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; +import util.wrappers.MultiKey; + +public class AT_PersonPropertyPluginData { + + @Test + @UnitTestMethod(target = PersonPropertiesPluginData.class, name = "builder", args = {}) + public void testBuilder() { + assertNotNull(PersonPropertiesPluginData.builder()); + } + + @Test + @UnitTestMethod(target = PersonPropertiesPluginData.Builder.class, name = "build", args = {}) + public void testBuild() { + TestPersonPropertyId propertyId = TestPersonPropertyId.PERSON_PROPERTY_5_INTEGER_MUTABLE_TRACK; + PropertyDefinition def = propertyId.getPropertyDefinition(); + PersonPropertiesPluginData.Builder builder = PersonPropertiesPluginData.builder(); + + // build a plugin data + PersonPropertiesPluginData pluginData1 = builder// + .definePersonProperty(propertyId, def, 1.2, true)// + .setPersonPropertyValue(new PersonId(5), propertyId, 7)// + .setPersonPropertyTime(new PersonId(5), propertyId, 2.5)// + .build(); + + // show that it was not null + assertNotNull(pluginData1); + + // show that the builder returns an identical plugin data if build is + // invoked again + PersonPropertiesPluginData pluginData2 = builder.build(); + assertEquals(pluginData1, pluginData2); + + /* + * precondition test: if a person is assigned a property value for a property + * that was not defined + */ + ContractException contractException = assertThrows(ContractException.class, // + () -> PersonPropertiesPluginData.builder()// + .setPersonPropertyValue(new PersonId(0), + TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, true)// + .build());// + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + /* + * precondition test: if a person is assigned a property assignment time for a + * property that was not defined + */ + contractException = assertThrows(ContractException.class, // + () -> PersonPropertiesPluginData.builder()// + .setPersonPropertyTime(new PersonId(0), + TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, 2.3)// + .build());// + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + /* + * precondition test: if a person is assigned a property value that is + * incompatible with the associated property definition + */ + contractException = assertThrows(ContractException.class, // + () -> {// + TestPersonPropertyId testPersonPropertyId = TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; + PropertyDefinition propertyDefinition = testPersonPropertyId.getPropertyDefinition(); + PersonPropertiesPluginData.builder()// + .definePersonProperty(testPersonPropertyId, propertyDefinition, 0.0, false)// + .setPersonPropertyValue(new PersonId(0), + TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, 45)// + .build();// + });// + assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); + + /* + * precondition test: if a person is assigned a property assignment time, but + * the corresponding property is not marked for time tracking + */ + contractException = assertThrows(ContractException.class, // + () -> {// + + TestPersonPropertyId prop1 = TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; + PropertyDefinition def1 = prop1.getPropertyDefinition(); + + PersonPropertiesPluginData.builder()// + .definePersonProperty(prop1, def1, 0.0, false)// + .setPersonPropertyTime(new PersonId(0), prop1, 3.2)// + .build();// + });// + assertEquals(PropertyError.TIME_TRACKING_OFF, contractException.getErrorType()); + + /* + * precondition test: if a person is assigned a property assignment time, but + * that value precedes default tracking time for the corresponding property id + */ + + contractException = assertThrows(ContractException.class, // + () -> {// + + TestPersonPropertyId prop1 = TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; + PropertyDefinition def1 = prop1.getPropertyDefinition(); + + PersonPropertiesPluginData.builder()// + .definePersonProperty(prop1, def1, 4.2, true)// + .setPersonPropertyTime(new PersonId(0), prop1, 3.2)// + .build();// + });// + assertEquals(PersonPropertyError.PROPERTY_TIME_PRECEDES_DEFAULT, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PersonPropertiesPluginData.Builder.class, name = "definePersonProperty", args = { + PersonPropertyId.class, PropertyDefinition.class, double.class, boolean.class }) + public void testDefinePersonProperty() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7695820040353096191L); + + // create a builder + PersonPropertiesPluginData.Builder personPropertyBuilder = PersonPropertiesPluginData.builder(); + + Set expectedValues = new LinkedHashSet<>(); + + // fill the builder with property definitions + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + TestPersonPropertyId testPersonPropertyId2 = testPersonPropertyId.next(); + double time = randomGenerator.nextDouble(); + boolean trackTime = randomGenerator.nextBoolean(); + personPropertyBuilder.definePersonProperty(testPersonPropertyId, + testPersonPropertyId2.getPropertyDefinition(), time, trackTime); + // replacing data to show that the value persists + time = randomGenerator.nextDouble(); + trackTime = randomGenerator.nextBoolean(); + personPropertyBuilder.definePersonProperty(testPersonPropertyId, + testPersonPropertyId.getPropertyDefinition(), time, trackTime); + // adding duplicate data to show that the value persists + time = randomGenerator.nextDouble(); + trackTime = randomGenerator.nextBoolean(); + personPropertyBuilder.definePersonProperty(testPersonPropertyId, + testPersonPropertyId.getPropertyDefinition(), time, trackTime); + expectedValues.add( + new MultiKey(testPersonPropertyId, testPersonPropertyId.getPropertyDefinition(), time, trackTime)); + } + + // build the person property plugin data + PersonPropertiesPluginData personPropertiesPluginData = personPropertyBuilder.build(); + + /* + * Show that the definitions returned by the initial data match the expected + * definitions. + */ + Set actualValues = new LinkedHashSet<>(); + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + PropertyDefinition propertyDefinition = personPropertiesPluginData + .getPersonPropertyDefinition(testPersonPropertyId); + double time = personPropertiesPluginData.getPropertyDefinitionTime(testPersonPropertyId); + boolean tracked = personPropertiesPluginData.propertyAssignmentTimesTracked(testPersonPropertyId); + actualValues.add(new MultiKey(testPersonPropertyId, propertyDefinition, time, tracked)); + } + + assertEquals(expectedValues, actualValues); + // precondition tests + + // if the person property id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + PersonPropertiesPluginData.Builder builder = PersonPropertiesPluginData.builder(); + TestPersonPropertyId testPersonPropertyId = TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; + builder.definePersonProperty(null, testPersonPropertyId.getPropertyDefinition(), 0, false); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // if the person property definition value is null + contractException = assertThrows(ContractException.class, () -> { + PersonPropertiesPluginData.Builder builder = PersonPropertiesPluginData.builder(); + TestPersonPropertyId testPersonPropertyId = TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; + builder.definePersonProperty(testPersonPropertyId, null, 0, false); + }); + assertEquals(PropertyError.NULL_PROPERTY_DEFINITION, contractException.getErrorType()); + + // if the person property definition value is null + contractException = assertThrows(ContractException.class, () -> { + PersonPropertiesPluginData.Builder builder = PersonPropertiesPluginData.builder(); + TestPersonPropertyId testPersonPropertyId = TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; + builder.definePersonProperty(testPersonPropertyId, testPersonPropertyId.getPropertyDefinition(), Double.NaN, + false); + }); + assertEquals(PersonPropertyError.NON_FINITE_TIME, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, () -> { + PersonPropertiesPluginData.Builder builder = PersonPropertiesPluginData.builder(); + TestPersonPropertyId testPersonPropertyId = TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; + builder.definePersonProperty(testPersonPropertyId, testPersonPropertyId.getPropertyDefinition(), + Double.POSITIVE_INFINITY, false); + }); + assertEquals(PersonPropertyError.NON_FINITE_TIME, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, () -> { + PersonPropertiesPluginData.Builder builder = PersonPropertiesPluginData.builder(); + TestPersonPropertyId testPersonPropertyId = TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; + builder.definePersonProperty(testPersonPropertyId, testPersonPropertyId.getPropertyDefinition(), + Double.NEGATIVE_INFINITY, false); + }); + assertEquals(PersonPropertyError.NON_FINITE_TIME, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PersonPropertiesPluginData.class, name = "getPropertyDefinitions", args = {}, tags = {}) + public void testGetPropertyDefinitions() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5091700094092190515L); + + // create a builder + PersonPropertiesPluginData.Builder personPropertyBuilder = PersonPropertiesPluginData.builder(); + + Map expectedPropertyDefinitions = new LinkedHashMap<>(); + + // fill the builder with property definitions + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + double time = randomGenerator.nextDouble(); + boolean trackTime = randomGenerator.nextBoolean(); + personPropertyBuilder.definePersonProperty(testPersonPropertyId, + testPersonPropertyId.getPropertyDefinition(), time, trackTime); + expectedPropertyDefinitions.put(testPersonPropertyId, testPersonPropertyId.getPropertyDefinition()); + } + + // build the person property plugin data + PersonPropertiesPluginData personPropertiesPluginData = personPropertyBuilder.build(); + + Map actualPropertyDefinitions = personPropertiesPluginData + .getPropertyDefinitions(); + + assertEquals(expectedPropertyDefinitions, actualPropertyDefinitions); + + } + + @Test + @UnitTestMethod(target = PersonPropertiesPluginData.class, name = "getPersonPropertyDefinition", args = { + PersonPropertyId.class }, tags = { UnitTag.LOCAL_PROXY }) + public void testGetPersonPropertyDefinition() { + + // the test: testDefinePersonProperty() covers all the postcondition + // tests + + // precondition tests + + // if the person property id is null + + ContractException contractException = assertThrows(ContractException.class, + () -> PersonPropertiesPluginData.builder().build().getPersonPropertyDefinition(null)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // if the person property id is unknown + contractException = assertThrows(ContractException.class, () -> PersonPropertiesPluginData.builder().build() + .getPersonPropertyDefinition(TestPersonPropertyId.getUnknownPersonPropertyId())); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = PersonPropertiesPluginData.class, name = "getPropertyDefinitionTimes", args = {}, tags = { + UnitTag.LOCAL_PROXY }) + public void testGetPropertyDefinitionTimes() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5091700094092190515L); + + // create a builder + PersonPropertiesPluginData.Builder personPropertyBuilder = PersonPropertiesPluginData.builder(); + + Map expectedPropertyDefinitionTimes = new LinkedHashMap<>(); + + // fill the builder with property definitions + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + double time = randomGenerator.nextDouble(); + boolean trackTime = randomGenerator.nextBoolean(); + personPropertyBuilder.definePersonProperty(testPersonPropertyId, + testPersonPropertyId.getPropertyDefinition(), time, trackTime); + expectedPropertyDefinitionTimes.put(testPersonPropertyId, time); + } + + // build the person property plugin data + PersonPropertiesPluginData personPropertiesPluginData = personPropertyBuilder.build(); + + Map actualPropertyDefinitionTimes = personPropertiesPluginData + .getPropertyDefinitionTimes(); + + assertEquals(expectedPropertyDefinitionTimes, actualPropertyDefinitionTimes); + + } + + @Test + @UnitTestMethod(target = PersonPropertiesPluginData.class, name = "getPropertyDefinitionTime", args = { + PersonPropertyId.class }, tags = { UnitTag.LOCAL_PROXY }) + public void testGetPropertyDefinitionTime() { + + // the test: testDefinePersonProperty() covers all the postcondition + // tests + + // precondition tests + + // if the person property id is null + ContractException contractException = assertThrows(ContractException.class, + () -> PersonPropertiesPluginData.builder().build().getPropertyDefinitionTime(null)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // if the person property id is unknown + contractException = assertThrows(ContractException.class, () -> PersonPropertiesPluginData.builder().build() + .getPropertyDefinitionTime(TestPersonPropertyId.getUnknownPersonPropertyId())); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = PersonPropertiesPluginData.class, name = "getPropertyTrackingPolicies", args = {}, tags = { + UnitTag.LOCAL_PROXY }) + public void testGetPropertyTrackingPolicies() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7695820040353096191L); + Map expectedPropertyTrackingPolicies = new LinkedHashMap<>(); + // create a builder + PersonPropertiesPluginData.Builder personPropertyBuilder = PersonPropertiesPluginData.builder(); + + // fill the builder with property definitions + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + TestPersonPropertyId testPersonPropertyId2 = testPersonPropertyId.next(); + double time = randomGenerator.nextDouble(); + boolean trackTime = randomGenerator.nextBoolean(); + personPropertyBuilder.definePersonProperty(testPersonPropertyId, + testPersonPropertyId2.getPropertyDefinition(), time, trackTime); + expectedPropertyTrackingPolicies.put(testPersonPropertyId, trackTime); + } + + // build the person property plugin data + PersonPropertiesPluginData personPropertiesPluginData = personPropertyBuilder.build(); + + /* + * Show that the definitions returned by the initial data match the expected + * definitions. + */ + + Map actualPropertyTrackingPolicies = personPropertiesPluginData + .getPropertyTrackingPolicies(); + assertEquals(expectedPropertyTrackingPolicies, actualPropertyTrackingPolicies); + + } + + @Test + @UnitTestMethod(target = PersonPropertiesPluginData.class, name = "propertyAssignmentTimesTracked", args = { + PersonPropertyId.class }, tags = { UnitTag.LOCAL_PROXY }) + public void testPropertyAssignmentTimesTracked() { + + // the test: testDefinePersonProperty() covers all the postcondition + // tests + + // precondition tests + + // if the person property id is null + ContractException contractException = assertThrows(ContractException.class, + () -> PersonPropertiesPluginData.builder().build().propertyAssignmentTimesTracked(null)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // if the person property id is unknown + contractException = assertThrows(ContractException.class, () -> PersonPropertiesPluginData.builder().build() + .propertyAssignmentTimesTracked(TestPersonPropertyId.getUnknownPersonPropertyId())); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = PersonPropertiesPluginData.class, name = "getPersonPropertyIds", args = {}) + public void testGetPersonPropertyIds() { + + // create a builder + PersonPropertiesPluginData.Builder personPropertyBuilder = PersonPropertiesPluginData.builder(); + + // fill the builder with property definitions + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + personPropertyBuilder.definePersonProperty(testPersonPropertyId, + testPersonPropertyId.getPropertyDefinition(), 0, false); + } + + // build the person property initial data + PersonPropertiesPluginData personPropertyInitialData = personPropertyBuilder.build(); + + /* + * Show that the person proproperty ids match expectations + */ + assertEquals(EnumSet.allOf(TestPersonPropertyId.class), personPropertyInitialData.getPersonPropertyIds()); + + } + + @Test + @UnitTestMethod(target = PersonPropertiesPluginData.class, name = "getCloneBuilder", args = {}) + public void testGetCloneBuilder() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3666595741799189966L); + PersonPropertiesPluginData.Builder pluginBuilder = PersonPropertiesPluginData.builder(); + + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + pluginBuilder.definePersonProperty(testPersonPropertyId, testPersonPropertyId.getPropertyDefinition(), + randomGenerator.nextDouble(), randomGenerator.nextBoolean()); + } + int personCount = 100; + for (int i = 0; i < personCount; i++) { + PersonId personId = new PersonId(i); + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + boolean hasDefault = testPersonPropertyId.getPropertyDefinition().getDefaultValue().isPresent(); + if (!hasDefault || randomGenerator.nextBoolean()) { + Object randomPropertyValue = testPersonPropertyId.getRandomPropertyValue(randomGenerator); + pluginBuilder.setPersonPropertyValue(personId, testPersonPropertyId, randomPropertyValue); + } + } + } + + PersonPropertiesPluginData expectedPluginData = pluginBuilder.build(); + + PluginDataBuilder cloneBuilder = expectedPluginData.getCloneBuilder(); + assertNotNull(cloneBuilder); + PluginData actualPluginData = cloneBuilder.build(); + + // show that the two plugin datas are equal + assertEquals(expectedPluginData, actualPluginData); + + } + + @Test + @UnitTestMethod(target = PersonPropertiesPluginData.Builder.class, name = "setPersonPropertyValue", args = { + PersonId.class, PersonPropertyId.class, Object.class }) + public void testSetPersonPropertyValue() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6340277988168121078L); + + // create a builder + PersonPropertiesPluginData.Builder personPropertyBuilder = PersonPropertiesPluginData.builder(); + + // fill the builder with property definitions + List propertiesWithoutDefaultValues = new ArrayList<>(); + List propertiesWithDefaultValues = new ArrayList<>(); + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + PropertyDefinition propertyDefinition = testPersonPropertyId.getPropertyDefinition(); + if (propertyDefinition.getDefaultValue().isPresent()) { + propertiesWithDefaultValues.add(testPersonPropertyId); + } else { + propertiesWithoutDefaultValues.add(testPersonPropertyId); + } + personPropertyBuilder.definePersonProperty(testPersonPropertyId, propertyDefinition, 0, false); + } + + Set expectedPersonPropertyValues = new LinkedHashSet<>(); + + int personCount = 150; + for (int i = 0; i < personCount; i++) { + PersonId personId = new PersonId(i); + Set propertiesToUse = new LinkedHashSet<>(propertiesWithoutDefaultValues); + for (TestPersonPropertyId testPersonPropertyId : propertiesWithDefaultValues) { + if (randomGenerator.nextBoolean()) { + propertiesToUse.add(testPersonPropertyId); + } + } + for (TestPersonPropertyId testPersonPropertyId : propertiesToUse) { + Object value = testPersonPropertyId.getRandomPropertyValue(randomGenerator); + Object value2 = testPersonPropertyId.getRandomPropertyValue(randomGenerator); + if (value instanceof Boolean) { + value2 = !(Boolean) value; + } + personPropertyBuilder.setPersonPropertyValue(personId, testPersonPropertyId, value2); + // replacing data to show that the value persists + personPropertyBuilder.setPersonPropertyValue(personId, testPersonPropertyId, value); + // adding duplicate data to show that the value persists + personPropertyBuilder.setPersonPropertyValue(personId, testPersonPropertyId, value); + + expectedPersonPropertyValues.add(new MultiKey(personId, testPersonPropertyId, value)); + } + + } + + // build the person property initial data + PersonPropertiesPluginData personPropertiesPluginData = personPropertyBuilder.build(); + + /* + * Show that property values match expectations + */ + Set acutalPersonPropertyValues = new LinkedHashSet<>(); + + for (PersonPropertyId personPropertyId : personPropertiesPluginData.getPersonPropertyIds()) { + List propertyValues = personPropertiesPluginData.getPropertyValues(personPropertyId); + for (int i = 0; i < propertyValues.size(); i++) { + Object value = propertyValues.get(i); + if (value != null) { + acutalPersonPropertyValues.add(new MultiKey(new PersonId(i), personPropertyId, value)); + } + } + } + assertEquals(expectedPersonPropertyValues, acutalPersonPropertyValues); + + // precondition test: if the person id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + PersonPropertiesPluginData.Builder builder = PersonPropertiesPluginData.builder(); + TestPersonPropertyId testPersonPropertyId = TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; + builder.setPersonPropertyValue(null, testPersonPropertyId, true); + }); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + // precondition test: if the person property value is null + contractException = assertThrows(ContractException.class, () -> { + PersonPropertiesPluginData.Builder builder = PersonPropertiesPluginData.builder(); + TestPersonPropertyId testPersonPropertyId = TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; + builder.setPersonPropertyValue(new PersonId(0), testPersonPropertyId, null); + }); + assertEquals(PropertyError.NULL_PROPERTY_VALUE, contractException.getErrorType()); + + // precondition test: if the person property id is null + contractException = assertThrows(ContractException.class, () -> { + PersonPropertiesPluginData.Builder builder = PersonPropertiesPluginData.builder(); + builder.setPersonPropertyValue(new PersonId(0), null, true); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + } +// PersonPropertiesPluginData public java.util.Map plugins.personproperties.datamanagers.PersonPropertiesPluginData.getPropertyValues() + + @Test + @UnitTestMethod(target = PersonPropertiesPluginData.class, name = "getPropertyValues", args = {}) + public void testGetPropertyValues() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6340277988168121078L); + + // create a builder + PersonPropertiesPluginData.Builder personPropertyBuilder = PersonPropertiesPluginData.builder(); + + // fill the builder with property definitions + List propertiesWithoutDefaultValues = new ArrayList<>(); + List propertiesWithDefaultValues = new ArrayList<>(); + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + PropertyDefinition propertyDefinition = testPersonPropertyId.getPropertyDefinition(); + if (propertyDefinition.getDefaultValue().isPresent()) { + propertiesWithDefaultValues.add(testPersonPropertyId); + } else { + propertiesWithoutDefaultValues.add(testPersonPropertyId); + } + personPropertyBuilder.definePersonProperty(testPersonPropertyId, propertyDefinition, 0, false); + } + + Map> expectedPropertyValues = new LinkedHashMap<>(); + + int personCount = 150; + for (int i = 0; i < personCount; i++) { + PersonId personId = new PersonId(i); + + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + Object value = null; + if (randomGenerator.nextBoolean()) { + value = testPersonPropertyId.getRandomPropertyValue(randomGenerator); + personPropertyBuilder.setPersonPropertyValue(personId, testPersonPropertyId, value); + } + List list = expectedPropertyValues.get(testPersonPropertyId); + if (list == null) { + list = new ArrayList<>(); + expectedPropertyValues.put(testPersonPropertyId, list); + } + if (value != null) { + int n = personId.getValue(); + while (list.size() < n) { + list.add(null); + } + list.add(value); + } + } + } + + // build the person property initial data + PersonPropertiesPluginData personPropertiesPluginData = personPropertyBuilder.build(); + + Map> actualPropertyValues = personPropertiesPluginData.getPropertyValues(); + + assertEquals(expectedPropertyValues, actualPropertyValues); + + } + + @Test + @UnitTestMethod(target = PersonPropertiesPluginData.class, name = "getPropertyValues", args = { + PersonPropertyId.class }) + public void testGetPropertyValues_PersonPropertyId() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6340277988168121078L); + + // create a builder + PersonPropertiesPluginData.Builder personPropertyBuilder = PersonPropertiesPluginData.builder(); + + // fill the builder with property definitions + List propertiesWithoutDefaultValues = new ArrayList<>(); + List propertiesWithDefaultValues = new ArrayList<>(); + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + PropertyDefinition propertyDefinition = testPersonPropertyId.getPropertyDefinition(); + if (propertyDefinition.getDefaultValue().isPresent()) { + propertiesWithDefaultValues.add(testPersonPropertyId); + } else { + propertiesWithoutDefaultValues.add(testPersonPropertyId); + } + personPropertyBuilder.definePersonProperty(testPersonPropertyId, propertyDefinition, 0, false); + } + + Set expectedPersonPropertyValues = new LinkedHashSet<>(); + + int personCount = 150; + for (int i = 0; i < personCount; i++) { + PersonId personId = new PersonId(i); + Set propertiesToUse = new LinkedHashSet<>(propertiesWithoutDefaultValues); + for (TestPersonPropertyId testPersonPropertyId : propertiesWithDefaultValues) { + if (randomGenerator.nextBoolean()) { + propertiesToUse.add(testPersonPropertyId); + } + } + for (TestPersonPropertyId testPersonPropertyId : propertiesToUse) { + Object value = testPersonPropertyId.getRandomPropertyValue(randomGenerator); + Object value2 = testPersonPropertyId.getRandomPropertyValue(randomGenerator); + if (value instanceof Boolean) { + value2 = !(Boolean) value; + } + personPropertyBuilder.setPersonPropertyValue(personId, testPersonPropertyId, value2); + // replacing data to show that the value persists + personPropertyBuilder.setPersonPropertyValue(personId, testPersonPropertyId, value); + // adding duplicate data to show that the value persists + personPropertyBuilder.setPersonPropertyValue(personId, testPersonPropertyId, value); + + expectedPersonPropertyValues.add(new MultiKey(personId, testPersonPropertyId, value)); + } + + } + + // build the person property initial data + PersonPropertiesPluginData personPropertiesPluginData = personPropertyBuilder.build(); + + /* + * Show that property values match expectations + */ + Set acutalPersonPropertyValues = new LinkedHashSet<>(); + + for (PersonPropertyId personPropertyId : personPropertiesPluginData.getPersonPropertyIds()) { + List propertyValues = personPropertiesPluginData.getPropertyValues(personPropertyId); + for (int i = 0; i < propertyValues.size(); i++) { + Object value = propertyValues.get(i); + if (value != null) { + acutalPersonPropertyValues.add(new MultiKey(new PersonId(i), personPropertyId, value)); + } + } + } + assertEquals(expectedPersonPropertyValues, acutalPersonPropertyValues); + + // precondition test: if the person property id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + PersonPropertiesPluginData.builder().build().getPropertyValues(null); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // precondition test: if the person property id is unknown + contractException = assertThrows(ContractException.class, () -> { + PersonPropertiesPluginData.builder().build() + .getPropertyValues(TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = PersonPropertiesPluginData.class, name = "getPropertyTimes", args = {}) + public void testGetPropertyTimes() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7969263718268675163L); + + // create a builder + PersonPropertiesPluginData.Builder personPropertyBuilder = PersonPropertiesPluginData.builder(); + + Map baseTimes = new LinkedHashMap<>(); + + // fill the builder with property definitions + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + PropertyDefinition propertyDefinition = testPersonPropertyId.getPropertyDefinition(); + double time = randomGenerator.nextInt() * 10; + personPropertyBuilder.definePersonProperty(testPersonPropertyId, propertyDefinition, time, true); + baseTimes.put(testPersonPropertyId, time); + } + + Set expectedPersonPropertyTimes = new LinkedHashSet<>(); + + int personCount = 150; + for (int i = 0; i < personCount; i++) { + PersonId personId = new PersonId(i); + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + Double time = randomGenerator.nextDouble() + baseTimes.get(testPersonPropertyId); + Double time2 = randomGenerator.nextDouble() + baseTimes.get(testPersonPropertyId); + personPropertyBuilder.setPersonPropertyTime(personId, testPersonPropertyId, time2); + // replacing data to show that the value persists + personPropertyBuilder.setPersonPropertyTime(personId, testPersonPropertyId, time); + // adding duplicate data to show that the value persists + personPropertyBuilder.setPersonPropertyTime(personId, testPersonPropertyId, time); + + expectedPersonPropertyTimes.add(new MultiKey(personId, testPersonPropertyId, time)); + } + + } + + // build the person property initial data + PersonPropertiesPluginData personPropertiesPluginData = personPropertyBuilder.build(); + + /* + * Show that property times match expectations + */ + Set acutalPersonPropertyTimes = new LinkedHashSet<>(); + + for (PersonPropertyId personPropertyId : personPropertiesPluginData.getPersonPropertyIds()) { + List propertyTimes = personPropertiesPluginData.getPropertyTimes(personPropertyId); + for (int i = 0; i < propertyTimes.size(); i++) { + Double time = propertyTimes.get(i); + if (time != null) { + acutalPersonPropertyTimes.add(new MultiKey(new PersonId(i), personPropertyId, time)); + } + } + } + assertEquals(expectedPersonPropertyTimes, acutalPersonPropertyTimes); + + } + + @Test + @UnitTestMethod(target = PersonPropertiesPluginData.class, name = "getPropertyTimes", args = { + PersonPropertyId.class }) + public void testGetPropertyTimes_PersonPropertyId() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7969263718268675163L); + + // create a builder + PersonPropertiesPluginData.Builder personPropertyBuilder = PersonPropertiesPluginData.builder(); + + Map baseTimes = new LinkedHashMap<>(); + + // fill the builder with property definitions + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + PropertyDefinition propertyDefinition = testPersonPropertyId.getPropertyDefinition(); + double time = randomGenerator.nextInt() * 10; + personPropertyBuilder.definePersonProperty(testPersonPropertyId, propertyDefinition, time, true); + baseTimes.put(testPersonPropertyId, time); + } + + Set expectedPersonPropertyTimes = new LinkedHashSet<>(); + + int personCount = 150; + for (int i = 0; i < personCount; i++) { + PersonId personId = new PersonId(i); + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + Double time = randomGenerator.nextDouble() + baseTimes.get(testPersonPropertyId); + Double time2 = randomGenerator.nextDouble() + baseTimes.get(testPersonPropertyId); + personPropertyBuilder.setPersonPropertyTime(personId, testPersonPropertyId, time2); + // replacing data to show that the value persists + personPropertyBuilder.setPersonPropertyTime(personId, testPersonPropertyId, time); + // adding duplicate data to show that the value persists + personPropertyBuilder.setPersonPropertyTime(personId, testPersonPropertyId, time); + + expectedPersonPropertyTimes.add(new MultiKey(personId, testPersonPropertyId, time)); + } + + } + + // build the person property initial data + PersonPropertiesPluginData personPropertiesPluginData = personPropertyBuilder.build(); + + /* + * Show that property times match expectations + */ + Set acutalPersonPropertyTimes = new LinkedHashSet<>(); + + for (PersonPropertyId personPropertyId : personPropertiesPluginData.getPersonPropertyIds()) { + List propertyTimes = personPropertiesPluginData.getPropertyTimes(personPropertyId); + for (int i = 0; i < propertyTimes.size(); i++) { + Double time = propertyTimes.get(i); + if (time != null) { + acutalPersonPropertyTimes.add(new MultiKey(new PersonId(i), personPropertyId, time)); + } + } + } + assertEquals(expectedPersonPropertyTimes, acutalPersonPropertyTimes); + + // precondition test: if the person property id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + PersonPropertiesPluginData.builder().build().getPropertyTimes(null); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // precondition test: if the person property id is unknown + contractException = assertThrows(ContractException.class, () -> { + PersonPropertiesPluginData.builder().build() + .getPropertyTimes(TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PersonPropertiesPluginData.Builder.class, name = "setPersonPropertyTime", args = { + PersonId.class, PersonPropertyId.class, Double.class }) + public void testSetPersonPropertyTime() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7969263718268675163L); + + // create a builder + PersonPropertiesPluginData.Builder personPropertyBuilder = PersonPropertiesPluginData.builder(); + + Map baseTimes = new LinkedHashMap<>(); + + // fill the builder with property definitions + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + PropertyDefinition propertyDefinition = testPersonPropertyId.getPropertyDefinition(); + double time = randomGenerator.nextInt() * 10; + personPropertyBuilder.definePersonProperty(testPersonPropertyId, propertyDefinition, time, true); + baseTimes.put(testPersonPropertyId, time); + } + + Set expectedPersonPropertyTimes = new LinkedHashSet<>(); + + int personCount = 150; + for (int i = 0; i < personCount; i++) { + PersonId personId = new PersonId(i); + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + Double time = randomGenerator.nextDouble() + baseTimes.get(testPersonPropertyId); + Double time2 = randomGenerator.nextDouble() + baseTimes.get(testPersonPropertyId); + personPropertyBuilder.setPersonPropertyTime(personId, testPersonPropertyId, time2); + // replacing data to show that the value persists + personPropertyBuilder.setPersonPropertyTime(personId, testPersonPropertyId, time); + // adding duplicate data to show that the value persists + personPropertyBuilder.setPersonPropertyTime(personId, testPersonPropertyId, time); + + expectedPersonPropertyTimes.add(new MultiKey(personId, testPersonPropertyId, time)); + } + + } + + // build the person property initial data + PersonPropertiesPluginData personPropertiesPluginData = personPropertyBuilder.build(); + + /* + * Show that property times match expectations + */ + Set acutalPersonPropertyTimes = new LinkedHashSet<>(); + + for (PersonPropertyId personPropertyId : personPropertiesPluginData.getPersonPropertyIds()) { + List propertyTimes = personPropertiesPluginData.getPropertyTimes(personPropertyId); + for (int i = 0; i < propertyTimes.size(); i++) { + Double time = propertyTimes.get(i); + if (time != null) { + acutalPersonPropertyTimes.add(new MultiKey(new PersonId(i), personPropertyId, time)); + } + } + } + assertEquals(expectedPersonPropertyTimes, acutalPersonPropertyTimes); + + // precondition test: if the person id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + PersonPropertiesPluginData.builder().setPersonPropertyTime(null, + TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, 2.3); + }); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + // precondition test: if the person property id is null + contractException = assertThrows(ContractException.class, () -> { + PersonPropertiesPluginData.builder().setPersonPropertyTime(new PersonId(4), null, 2.3); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // precondition test: if the person property time is null + contractException = assertThrows(ContractException.class, () -> { + PersonPropertiesPluginData.builder().setPersonPropertyTime(new PersonId(4), + TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK, null); + }); + assertEquals(PersonPropertyError.NULL_TIME, contractException.getErrorType()); + + // precondition test: if the person property time is not finite + contractException = assertThrows(ContractException.class, () -> { + PersonPropertiesPluginData.builder().setPersonPropertyTime(new PersonId(4), + TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK, Double.NaN); + }); + assertEquals(PersonPropertyError.NON_FINITE_TIME, contractException.getErrorType()); + + // precondition test: if the person property time is not finite + contractException = assertThrows(ContractException.class, () -> { + PersonPropertiesPluginData.builder().setPersonPropertyTime(new PersonId(4), + TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK, Double.POSITIVE_INFINITY); + }); + assertEquals(PersonPropertyError.NON_FINITE_TIME, contractException.getErrorType()); + + // precondition test: if the person property time is not finite + contractException = assertThrows(ContractException.class, () -> { + PersonPropertiesPluginData.builder().setPersonPropertyTime(new PersonId(4), + TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK, Double.NEGATIVE_INFINITY); + }); + assertEquals(PersonPropertyError.NON_FINITE_TIME, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PersonPropertiesPluginData.class, name = "equals", args = { Object.class }) + public void testEquals() { + // equality on property definitions + PersonPropertyId propId1 = new PersonPropertyId() { + }; + PropertyDefinition def1 = PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(5)// + .setPropertyValueMutability(true)// + .build(); + + PersonPropertyId propId2 = new PersonPropertyId() { + }; + PropertyDefinition def2 = PropertyDefinition.builder()// + .setType(Double.class)// + .setDefaultValue(3.0)// + .setPropertyValueMutability(true)// + .build(); + + PersonPropertiesPluginData pluginData1 = PersonPropertiesPluginData.builder().build(); + PersonPropertiesPluginData pluginData2 = PersonPropertiesPluginData.builder()// + .definePersonProperty(propId1, def1, 2.0, true)// + .build(); + PersonPropertiesPluginData pluginData3 = PersonPropertiesPluginData.builder()// + .definePersonProperty(propId1, def1, 2.0, true)// + .build(); + PersonPropertiesPluginData pluginData4 = PersonPropertiesPluginData.builder()// + .definePersonProperty(propId2, def1, 0, true)// + .build(); + PersonPropertiesPluginData pluginData5 = PersonPropertiesPluginData.builder()// + .definePersonProperty(propId2, def2, 0, true)// + .build(); + PersonPropertiesPluginData pluginData6 = PersonPropertiesPluginData.builder()// + .definePersonProperty(propId1, def1, 0, true)// + .definePersonProperty(propId2, def2, 0, true)// + .build(); + + // reflexive + assertEquals(pluginData1, pluginData1); + assertEquals(pluginData2, pluginData2); + // symmetric + assertEquals(pluginData2, pluginData3); + assertEquals(pluginData3, pluginData2); + + // transitive implicitly covered + + // not equals + assertNotEquals(pluginData1, pluginData2); + assertNotEquals(pluginData2, pluginData4); + assertNotEquals(pluginData4, pluginData5); + assertNotEquals(pluginData2, pluginData5); + assertNotEquals(pluginData5, pluginData6); + + // equality on time tracking + pluginData1 = PersonPropertiesPluginData.builder()// + .definePersonProperty(propId1, def1, 2.3, true)// + .definePersonProperty(propId2, def2, 0, false)// + .build(); + + // same as 1 + pluginData2 = PersonPropertiesPluginData.builder()// + .definePersonProperty(propId1, def1, 2.3, true)// + .definePersonProperty(propId2, def2, 0, false)// + .build(); + + // use a different property id + pluginData3 = PersonPropertiesPluginData.builder()// + .definePersonProperty(propId1, def1, 0, false)// + .definePersonProperty(propId2, def2, 2.3, true)// + .build(); + + // changed the default time + pluginData4 = PersonPropertiesPluginData.builder()// + .definePersonProperty(propId1, def1, 5.3, true)// + .definePersonProperty(propId2, def2, 0, false)// + .build(); + + // now has two default times + pluginData5 = PersonPropertiesPluginData.builder()// + .definePersonProperty(propId1, def1, 2.3, true)// + .definePersonProperty(propId2, def2, 4.7, true)// + .build(); + + assertEquals(pluginData1, pluginData1); + assertEquals(pluginData1, pluginData2); + assertNotEquals(pluginData1, pluginData3); + assertNotEquals(pluginData1, pluginData4); + assertNotEquals(pluginData1, pluginData5); + + // equality on person property values + pluginData1 = PersonPropertiesPluginData.builder()// + .definePersonProperty(propId1, def1, 6, true)// + .definePersonProperty(propId2, def2, 5.4, true)// + .build(); + + pluginData2 = PersonPropertiesPluginData.builder()// + .definePersonProperty(propId1, def1, 6, true)// + .definePersonProperty(propId2, def2, 5.4, true)// + .build(); + + // change a person + pluginData3 = PersonPropertiesPluginData.builder()// + .definePersonProperty(propId1, def1, 6, true)// + .definePersonProperty(propId2, def2, 5.4, false)// + .build(); + + // change a value + pluginData4 = PersonPropertiesPluginData.builder()// + .definePersonProperty(propId1, def1, 6, true)// + .definePersonProperty(propId2, def2, 5.5, true)// + .build(); + + // add a person + pluginData5 = PersonPropertiesPluginData.builder()// + .definePersonProperty(propId1, def1, 0, false)// + .definePersonProperty(propId2, def2, 0, false)// + .setPersonPropertyValue(new PersonId(2), propId1, 6)// + .setPersonPropertyValue(new PersonId(5), propId2, 5.4)// + .setPersonPropertyValue(new PersonId(8), propId2, 8.4)// + .build(); + + assertEquals(pluginData1, pluginData1); + assertEquals(pluginData1, pluginData2); + assertNotEquals(pluginData1, pluginData3); + assertNotEquals(pluginData1, pluginData4); + assertNotEquals(pluginData1, pluginData5); + + // equality on person property times + pluginData1 = PersonPropertiesPluginData.builder()// + .definePersonProperty(propId1, def1, 2, true)// + .definePersonProperty(propId2, def2, 3, true)// + .setPersonPropertyTime(new PersonId(2), propId1, 6.0)// + .setPersonPropertyTime(new PersonId(5), propId2, 5.4)// + .build(); + + pluginData2 = PersonPropertiesPluginData.builder()// + .definePersonProperty(propId1, def1, 2, true)// + .definePersonProperty(propId2, def2, 3, true)// + .setPersonPropertyTime(new PersonId(2), propId1, 6.0)// + .setPersonPropertyTime(new PersonId(5), propId2, 5.4)// + .build(); + + // change a person + pluginData3 = PersonPropertiesPluginData.builder()// + .definePersonProperty(propId1, def1, 2, true)// + .definePersonProperty(propId2, def2, 3, true)// + .setPersonPropertyTime(new PersonId(1), propId1, 6.0)// + .setPersonPropertyTime(new PersonId(5), propId2, 5.4)// + .build(); + + // change a time + pluginData4 = PersonPropertiesPluginData.builder()// + .definePersonProperty(propId1, def1, 2, true)// + .definePersonProperty(propId2, def2, 3, true)// + .setPersonPropertyTime(new PersonId(2), propId1, 6.0)// + .setPersonPropertyTime(new PersonId(5), propId2, 5.5)// + .build(); + + // add a person + pluginData5 = PersonPropertiesPluginData.builder()// + .definePersonProperty(propId1, def1, 2, true)// + .definePersonProperty(propId2, def2, 3, true)// + .setPersonPropertyTime(new PersonId(2), propId1, 6.0)// + .setPersonPropertyTime(new PersonId(5), propId2, 5.5)// + .setPersonPropertyTime(new PersonId(8), propId2, 8.4)// + .build(); + + assertEquals(pluginData1, pluginData1); + assertEquals(pluginData1, pluginData2); + assertNotEquals(pluginData1, pluginData3); + assertNotEquals(pluginData1, pluginData4); + assertNotEquals(pluginData1, pluginData5); + + } + + @Test + @UnitTestMethod(target = PersonPropertiesPluginData.class, name = "hashCode", args = {}) + public void testHashCode() { + + // some setup first + PersonPropertyId propId1 = new PersonPropertyId() { + }; + PropertyDefinition def1 = PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(5)// + .setPropertyValueMutability(true)// + .build(); + + PersonPropertyId propId2 = new PersonPropertyId() { + }; + PropertyDefinition def2 = PropertyDefinition.builder()// + .setType(Double.class)// + .setDefaultValue(3.0)// + .setPropertyValueMutability(true)// + .build(); + + PersonPropertiesPluginData pluginData1 = PersonPropertiesPluginData.builder()// + .definePersonProperty(propId1, def1, 2, true)// + .definePersonProperty(propId2, def2, 3, true)// + + .setPersonPropertyValue(new PersonId(2), propId1, 5)// + .setPersonPropertyTime(new PersonId(2), propId1, 6.0)// + + .setPersonPropertyValue(new PersonId(5), propId2, 12.5)// + .setPersonPropertyTime(new PersonId(5), propId2, 3.0)// + + .setPersonPropertyTime(new PersonId(8), propId2, 8.4)// + .setPersonPropertyTime(new PersonId(8), propId2, 12.7)// + .build(); + + PersonPropertiesPluginData pluginData2 = PersonPropertiesPluginData.builder()// + .definePersonProperty(propId1, def1, 2, true)// + .definePersonProperty(propId2, def2, 3, true)// + + .setPersonPropertyValue(new PersonId(2), propId1, 5)// + .setPersonPropertyTime(new PersonId(2), propId1, 6.0)// + + .setPersonPropertyValue(new PersonId(5), propId2, 12.5)// + .setPersonPropertyTime(new PersonId(5), propId2, 3.0)// + + .setPersonPropertyTime(new PersonId(8), propId2, 8.4)// + .setPersonPropertyTime(new PersonId(8), propId2, 12.7)// + .build(); + + // equal objects have equal hash codes + assertEquals(pluginData1, pluginData2); + + assertEquals(pluginData1.hashCode(), pluginData2.hashCode()); + + // show that hash codes are reasonably distributed + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3839519625960869013L); + + int n = 100; + Set hashCodes = new LinkedHashSet<>(); + for (int i = 0; i < n; i++) { + PersonPropertiesPluginData.Builder builder = PersonPropertiesPluginData.builder(); + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + if (randomGenerator.nextBoolean()) { + boolean trackTimes = randomGenerator.nextBoolean(); + double baseTime = randomGenerator.nextDouble(); + PropertyDefinition propertyDefinition = testPersonPropertyId.getPropertyDefinition(); + builder.definePersonProperty(testPersonPropertyId, propertyDefinition, baseTime, trackTimes);// + for (int j = 0; j < 5; j++) { + if (randomGenerator.nextBoolean()) { + builder.setPersonPropertyValue(new PersonId(j), testPersonPropertyId, + testPersonPropertyId.getRandomPropertyValue(randomGenerator)); + } + } + if (trackTimes) { + for (int j = 0; j < 5; j++) { + if (randomGenerator.nextBoolean()) { + builder.setPersonPropertyTime(new PersonId(j), testPersonPropertyId, + baseTime + randomGenerator.nextDouble()); + } + } + } + } + } + PersonPropertiesPluginData personPropertiesPluginData = builder.build(); + hashCodes.add(personPropertiesPluginData.hashCode()); + } + int expectedCount = (9 * n) / 10; + assertTrue(hashCodes.size() > expectedCount); + } + + @Test + @UnitTestMethod(target = PersonPropertiesPluginData.class, name = "toString", args = {}) + public void testToString() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3694836073644636049L); + + List people = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + people.add(new PersonId(2 * i + 5)); + } + + PersonPropertiesPluginData.Builder builder = PersonPropertiesPluginData.builder(); + double defTime = 0; + boolean track = false; + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + builder.definePersonProperty(testPersonPropertyId, testPersonPropertyId.getPropertyDefinition(), defTime, + track); + + for (PersonId personId : people) { + Object propertyValue = testPersonPropertyId.getRandomPropertyValue(randomGenerator); + builder.setPersonPropertyValue(personId, testPersonPropertyId, propertyValue); + if (track) { + builder.setPersonPropertyTime(personId, testPersonPropertyId, + defTime + randomGenerator.nextDouble() * 10); + } + } + defTime += 10; + track = !track; + } + + PersonPropertiesPluginData personPropertiesPluginData = builder.build(); + + String actualValue = personPropertiesPluginData.toString(); + String expectedValue = "PersonPropertiesPluginData [data=Data [personPropertyDefinitions=" + + "{PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK=PropertyDefinition " + + "[type=class java.lang.Boolean, propertyValuesAreMutable=true, defaultValue=false], " + + "PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK=PropertyDefinition [type=class java.lang.Integer, " + + "propertyValuesAreMutable=true, defaultValue=0], PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK=PropertyDefinition " + + "[type=class java.lang.Double, propertyValuesAreMutable=true, defaultValue=0.0], " + + "PERSON_PROPERTY_4_BOOLEAN_MUTABLE_TRACK=PropertyDefinition [type=class java.lang.Boolean, " + + "propertyValuesAreMutable=true, defaultValue=false], PERSON_PROPERTY_5_INTEGER_MUTABLE_TRACK=" + + "PropertyDefinition [type=class java.lang.Integer, propertyValuesAreMutable=true, defaultValue=0], " + + "PERSON_PROPERTY_6_DOUBLE_MUTABLE_TRACK=PropertyDefinition [type=class java.lang.Double, " + + "propertyValuesAreMutable=true, defaultValue=0.0], PERSON_PROPERTY_7_BOOLEAN_IMMUTABLE_NO_TRACK=" + + "PropertyDefinition [type=class java.lang.Boolean, propertyValuesAreMutable=false, " + + "defaultValue=false], PERSON_PROPERTY_8_INTEGER_IMMUTABLE_NO_TRACK=PropertyDefinition " + + "[type=class java.lang.Integer, propertyValuesAreMutable=false, defaultValue=0], " + + "PERSON_PROPERTY_9_DOUBLE_IMMUTABLE_NO_TRACK=PropertyDefinition [type=class java.lang.Double, " + + "propertyValuesAreMutable=false, defaultValue=null]}, propertyDefinitionTimes=" + + "{PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK=0.0, PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK=10.0, " + + "PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK=20.0, PERSON_PROPERTY_4_BOOLEAN_MUTABLE_TRACK=30.0, " + + "PERSON_PROPERTY_5_INTEGER_MUTABLE_TRACK=40.0, PERSON_PROPERTY_6_DOUBLE_MUTABLE_TRACK=50.0, " + + "PERSON_PROPERTY_7_BOOLEAN_IMMUTABLE_NO_TRACK=60.0, PERSON_PROPERTY_8_INTEGER_IMMUTABLE_NO_TRACK=" + + "70.0, PERSON_PROPERTY_9_DOUBLE_IMMUTABLE_NO_TRACK=80.0}, propertyTrackingPolicies={" + + "PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK=false, PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK=true, " + + "PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK=false, PERSON_PROPERTY_4_BOOLEAN_MUTABLE_TRACK=true, " + + "PERSON_PROPERTY_5_INTEGER_MUTABLE_TRACK=false, PERSON_PROPERTY_6_DOUBLE_MUTABLE_TRACK=true, " + + "PERSON_PROPERTY_7_BOOLEAN_IMMUTABLE_NO_TRACK=false, PERSON_PROPERTY_8_INTEGER_IMMUTABLE_NO_TRACK=true, " + + "PERSON_PROPERTY_9_DOUBLE_IMMUTABLE_NO_TRACK=false}, personPropertyValues={" + + "PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK=[null, null, null, null, null, " + + "false, null, true, null, true, null, true, null, true, null, true, null, " + + "false, null, false, null, true, null, false], PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK=" + + "[null, null, null, null, null, -621108106, null, 2100066974, null, -2018035576, null, " + + "1968625103, null, 1374125534, null, 1531713167, null, 1582611905, null, -1007215429, null, " + + "-1746683312, null, 1270680106], PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK=[null, " + + "null, null, null, null, 0.768935287898453, null, 0.8364646544958321, null, " + + "0.2558395651219538, null, 0.5191462165704828, null, 0.07007453695242538, null, " + + "0.01464388215554413, null, 0.6468884427978394, null, 0.14067662402164638, null, " + + "0.9462070884899854, null, 0.3387682391115401], PERSON_PROPERTY_4_BOOLEAN_MUTABLE_TRACK=[" + + "null, null, null, null, null, false, null, false, null, false, null, true, null, true, " + + "null, true, null, true, null, true, null, true, null, false], PERSON_PROPERTY_5_INTEGER_MUTABLE_TRACK=[" + + "null, null, null, null, null, -1568693474, null, 186939033, null, 1168801853, null, -2105311462, " + + "null, -1179232898, null, 1301961038, null, 1500224149, null, 1046340576, null, 1464613321, " + + "null, 313138634], PERSON_PROPERTY_6_DOUBLE_MUTABLE_TRACK=[null, null, null, null, null, " + + "0.09874678269064252, null, 0.8068629284994859, null, 0.34718027838481214, null, 0.6468842925484919, " + + "null, 0.12348080023922603, null, 0.3445025787207139, null, 0.6441936016018901, null, " + + "0.22605544325326998, null, 0.6149961179850165, null, 0.4370425154054456], " + + "PERSON_PROPERTY_7_BOOLEAN_IMMUTABLE_NO_TRACK=[null, null, null, null, null, true, null, true, " + + "null, true, null, false, null, false, null, true, null, false, null, true, null, false, null, " + + "false], PERSON_PROPERTY_8_INTEGER_IMMUTABLE_NO_TRACK=[null, null, null, null, null, " + + "-1061266563, null, 248923101, null, -1034787615, null, -2105458344, null, -2053555776, " + + "null, 734233623, null, -1788427753, null, -2147042288, null, 795856471, null, -1107524672], " + + "PERSON_PROPERTY_9_DOUBLE_IMMUTABLE_NO_TRACK=[null, null, null, null, null, 0.04473211964365187," + + " null, 0.35677440405445027, null, 0.7094328573866935, null, 0.4262998541569638, null, " + + "0.6721812305884087, null, 0.06599533517359268, null, 0.5898411326259143, null, 0.2688893818375615, " + + "null, 0.7498770794369971, null, 0.3698063555641413]}, personPropertyTimes={PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK=" + + "[null, null, null, null, null, 15.01053068281431, null, 19.94981623136485, null, 14.153192884863667, null, " + + "12.6440676490902, null, 15.598933330056575, null, 13.252151935445784, null, 16.63154860967876, null, " + + "10.493942026810101, null, 10.443786590357679, null, 18.660720681068277], " + + "PERSON_PROPERTY_4_BOOLEAN_MUTABLE_TRACK=[null, null, null, null, null, 32.79706714473025, null, " + + "30.28243209635568, null, 39.401217348796465, null, 35.97150480992329, null, 36.26522497015469, " + + "null, 33.879624529240154, null, 36.925982440804454, null, 32.807367349614246, null, 32.398150127097296, " + + "null, 39.290644182925476], PERSON_PROPERTY_6_DOUBLE_MUTABLE_TRACK=[null, null, null, null, " + + "null, 55.08342661900296, null, 57.745554002438034, null, 56.506973180579166, null, 54.30056662223466, " + + "null, 59.04310482253601, null, 52.95886544676756, null, 54.049056592024826, null, 54.30100804888913, " + + "null, 52.27313116665432, null, 52.72849938412798], PERSON_PROPERTY_8_INTEGER_IMMUTABLE_NO_TRACK=[" + + "null, null, null, null, null, 79.52059465811621, null, 72.35392133239016, null, 75.13846509635269, " + + "null, 75.4774777727181, null, 77.77386975625731, null, 76.46205411237824, null, 70.54554388234095, " + + "null, 72.92650170630496, null, 78.75239401434217, null, 77.34177298897009]}]]"; + + assertEquals(expectedValue, actualValue); + + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/events/AT_PersonPropertyDefinitionEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/events/AT_PersonPropertyDefinitionEvent.java new file mode 100644 index 000000000..a6e6d768e --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/events/AT_PersonPropertyDefinitionEvent.java @@ -0,0 +1,49 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.events; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + +public class AT_PersonPropertyDefinitionEvent { + + @Test + @UnitTestConstructor(target = PersonPropertyDefinitionEvent.class, args = { PersonPropertyId.class }) + public void testConstructor() { + + // precondition: person property id is null + ContractException contractException = assertThrows(ContractException.class, () -> new PersonPropertyDefinitionEvent(null)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PersonPropertyDefinitionEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonPropertyDefinitionEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonPropertyDefinitionEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonPropertyDefinitionEvent.class, name = "personPropertyId", args = {}) + public void testPersonPropertyId() { + // nothing to test + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/events/AT_PersonPropertyUpdateEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/events/AT_PersonPropertyUpdateEvent.java new file mode 100644 index 000000000..7980e2c4e --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/events/AT_PersonPropertyUpdateEvent.java @@ -0,0 +1,94 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.events; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.testsupport.TestPersonPropertyId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_PersonPropertyUpdateEvent { + + @Test + @UnitTestConstructor(target = PersonPropertyUpdateEvent.class, args = { PersonId.class, PersonPropertyId.class, Object.class, Object.class }) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonPropertyUpdateEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonPropertyUpdateEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonPropertyUpdateEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonPropertyUpdateEvent.class, name = "personId", args = {}) + public void testPersonId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonPropertyUpdateEvent.class, name = "personPropertyId", args = {}) + public void testPersonPropertyId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonPropertyUpdateEvent.class, name = "previousPropertyValue", args = {}) + public void testPreviousPropertyValue() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonPropertyUpdateEvent.class, name = "currentPropertyValue", args = {}) + public void testCurrentPropertyValue() { + // nothing to test + } + + + @Test + @UnitTestMethod(target = PersonPropertyUpdateEvent.class, name = "getPreviousPropertyValue", args = {}) + public void testGetPreviousValue() { + + for (int i = 0; i < 20; i++) { + PersonId personId = new PersonId(345); + PersonPropertyId personPropertyId = TestPersonPropertyId.PERSON_PROPERTY_7_BOOLEAN_IMMUTABLE_NO_TRACK; + Integer previousPropertyValue = i; + Integer currentPropertyValue = 45; + + PersonPropertyUpdateEvent personPropertyUpdateEvent = new PersonPropertyUpdateEvent(personId, personPropertyId, previousPropertyValue, currentPropertyValue); + Integer actualValue = personPropertyUpdateEvent.getPreviousPropertyValue(); + assertEquals(previousPropertyValue, actualValue); + } + } + + @Test + @UnitTestMethod(target = PersonPropertyUpdateEvent.class, name = "getCurrentPropertyValue", args = {}) + public void testGetCurrentValue() { + for (int i = 0; i < 20; i++) { + PersonId personId = new PersonId(345); + PersonPropertyId personPropertyId = TestPersonPropertyId.PERSON_PROPERTY_7_BOOLEAN_IMMUTABLE_NO_TRACK; + Integer previousPropertyValue = 37; + Integer currentPropertyValue = i; + + PersonPropertyUpdateEvent personPropertyUpdateEvent = new PersonPropertyUpdateEvent(personId, personPropertyId, previousPropertyValue, currentPropertyValue); + Integer actualValue = personPropertyUpdateEvent.getCurrentPropertyValue(); + assertEquals(currentPropertyValue, actualValue); + } + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/reports/AT_PersonPropertyInteractionReport.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/reports/AT_PersonPropertyInteractionReport.java new file mode 100644 index 000000000..7e9b98df0 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/reports/AT_PersonPropertyInteractionReport.java @@ -0,0 +1,240 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.reports; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestOutputConsumer; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePlugin; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeoplePluginData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonRange; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.PersonPropertiesPlugin; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyDefinitionInitialization; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.testsupport.TestPersonPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.regions.RegionsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.TestRegionId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportError; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import util.annotations.UnitTag; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + +public class AT_PersonPropertyInteractionReport { + + @Test + @UnitTestConstructor(target = PersonPropertyInteractionReport.class, args = { PersonPropertyInteractionReportPluginData.class}) + public void testConstructor() { + + // precondition: report label is null + ContractException contractException = assertThrows(ContractException.class, () -> { + PersonPropertyInteractionReportPluginData pluginData = PersonPropertyInteractionReportPluginData.builder().setReportPeriod(ReportPeriod.DAILY).build(); + new PersonPropertyInteractionReport(pluginData); + }); + assertEquals(ReportError.NULL_REPORT_LABEL, contractException.getErrorType()); + + // precondition: report period is null + contractException = assertThrows(ContractException.class, () -> { + PersonPropertyInteractionReportPluginData pluginData = PersonPropertyInteractionReportPluginData.builder().setReportLabel(new SimpleReportLabel("label")).build(); + new PersonPropertyInteractionReport(pluginData); + }); + assertEquals(ReportError.NULL_REPORT_PERIOD, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PersonPropertyInteractionReport.class, name = "init", args = { ReportContext.class }, tags = { UnitTag.INCOMPLETE }) + public void testInit() { + // TBD + } + + @Test + @UnitTestMethod(target = PersonPropertyInteractionReport.class, name = "init", args = { ReportContext.class }) + public void testInit_State() { + // Test with producing simulation output + + List plugins = new ArrayList<>(); + PersonPropertyId unknownIdToExclude = new PersonPropertyId() { + @Override + public String toString() { + return "unknownIdToExclude"; + } + }; + PersonPropertyId unknownIdToInclude = new PersonPropertyId() { + @Override + public String toString() { + return "unknownIdToInclude"; + } + }; + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + // pluginDataBuilder.addPluginDependency(PersonPropertiesPluginId.PLUGIN_ID); + + // have the actor add a new person property definitions + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(1).build(); + + PersonPropertyDefinitionInitialization personPropertyDefinitionInitialization = PersonPropertyDefinitionInitialization .builder().setPersonPropertyId(unknownIdToExclude) + .setPropertyDefinition(propertyDefinition).build(); + personPropertiesDataManager.definePersonProperty(personPropertyDefinitionInitialization); + + propertyDefinition = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(false).build(); + PersonPropertyDefinitionInitialization personPropertyDefinitionInitialization2 = PersonPropertyDefinitionInitialization .builder().setPersonPropertyId(unknownIdToInclude) + .setPropertyDefinition(propertyDefinition).build(); + personPropertiesDataManager.definePersonProperty(personPropertyDefinitionInitialization2); + + })); + + // have the actor set a few person property values + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0.4, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(7), TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 35); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(7), TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 40); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(8), TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK, 2.75); + })); + + // have the actor set a few person property values + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0.7, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(7), TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 35); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(7), TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 40); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(8), TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK, 2.75); + })); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1.2, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(8), unknownIdToInclude, true); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(17), unknownIdToExclude, 134); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(9), TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 17); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(10), TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, true); + })); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1.8, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(9), TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 59); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(10), TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, false); + })); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(9), TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 30); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(10), TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, true); + })); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(2.5, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(4), TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 12); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(6), TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 12); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(6), unknownIdToInclude, true); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(6), unknownIdToExclude, 99); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(11), unknownIdToInclude, true); + })); + + ReportLabel reportLabel = new SimpleReportLabel("report label"); + ReportPeriod hourlyReportPeriod = ReportPeriod.DAILY; + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + plugins.add(testPlugin); + + int populationSize = 30; + + PeoplePluginData.Builder peopleBuilder = PeoplePluginData.builder(); + peopleBuilder.addPersonRange(new PersonRange(0, populationSize-1)); + + PeoplePluginData peoplePluginData = peopleBuilder.build(); + Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(peoplePluginData); + plugins.add(peoplePlugin); + + // add the first three TestPersonPropertyId values and their definitions + PersonPropertiesPluginData.Builder personPropertyBuilder = PersonPropertiesPluginData.builder(); + Set basePropertyIds = new LinkedHashSet<>(); + basePropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK); + basePropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK); + basePropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK); + for (TestPersonPropertyId testPersonPropertyId : basePropertyIds) { + personPropertyBuilder.definePersonProperty(testPersonPropertyId, testPersonPropertyId.getPropertyDefinition(),0,false); + } + PersonPropertiesPluginData personPropertiesPluginData = personPropertyBuilder.build(); + + PersonPropertyInteractionReportPluginData personPropertyInteractionReportPluginData = // + PersonPropertyInteractionReportPluginData .builder()// + .setReportLabel(reportLabel).setReportPeriod(hourlyReportPeriod)// + .addPersonPropertyId(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK)// + .removePersonPropertyId(TestPersonPropertyId.PERSON_PROPERTY_6_DOUBLE_MUTABLE_TRACK)// + .build(); + + Plugin personPropertyPlugin = PersonPropertiesPlugin.builder()// + .setPersonPropertiesPluginData(personPropertiesPluginData)// + .setPersonPropertyInteractionReportPluginData(personPropertyInteractionReportPluginData)// + .getPersonPropertyPlugin(); + + plugins.add(personPropertyPlugin); + + /* + * Add two region - even people in region 1 and odd people in region 2 + */ + RegionsPluginData.Builder regionBuilder = RegionsPluginData.builder(); + + regionBuilder.addRegion(TestRegionId.REGION_1); + regionBuilder.addRegion(TestRegionId.REGION_2); + for (int i = 0; i < populationSize; i++) { + PersonId personId = peoplePluginData.getPersonIds().get(i); + if (i % 2 == 0) { + regionBuilder.addPerson(personId, TestRegionId.REGION_1); + } else { + regionBuilder.addPerson(personId, TestRegionId.REGION_2); + } + } + RegionsPluginData regionsPluginData = regionBuilder.build(); + Plugin regionsPlugin = RegionsPlugin.builder().setRegionsPluginData(regionsPluginData).getRegionsPlugin(); + plugins.add(regionsPlugin); + + // execute the simulation and gather the report items + + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(plugins)// + .setProduceSimulationStateOnHalt(true)// + .setSimulationHaltTime(50)// + .build()// + .execute(); + + Map outputItems = testOutputConsumer.getOutputItemMap(PersonPropertyInteractionReportPluginData.class); + assertEquals(1, outputItems.size()); + PersonPropertyInteractionReportPluginData personPropertyInteractionReportPluginData2 = outputItems.keySet().iterator().next(); + assertEquals(personPropertyInteractionReportPluginData, personPropertyInteractionReportPluginData2); + + // Test without producing simulation state + + testOutputConsumer = TestSimulation .builder()// + .addPlugins(plugins)// + .setProduceSimulationStateOnHalt(false)// + .setSimulationHaltTime(20)// + .build()// + .execute(); + + outputItems = testOutputConsumer.getOutputItemMap(PersonPropertyInteractionReportPluginData.class); + assertEquals(0, outputItems.size()); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/reports/AT_PersonPropertyInteractionReportPluginData.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/reports/AT_PersonPropertyInteractionReportPluginData.java new file mode 100644 index 000000000..09099600f --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/reports/AT_PersonPropertyInteractionReportPluginData.java @@ -0,0 +1,314 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.reports; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.testsupport.TestPersonPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportError; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_PersonPropertyInteractionReportPluginData { + + @Test + @UnitTestMethod(target = PersonPropertyInteractionReportPluginData.class, name = "builder", args = {}) + public void testBuilder() { + PersonPropertyInteractionReportPluginData.Builder builder = PersonPropertyInteractionReportPluginData.builder(); + assertNotNull(builder); + } + + @Test + @UnitTestMethod(target = PersonPropertyInteractionReportPluginData.Builder.class, name = "setReportLabel", args = { + ReportLabel.class }) + public void testSetReportLabel() { + + for (int i = 0; i < 30; i++) { + ReportLabel expectedReportLabel = new SimpleReportLabel(i); + PersonPropertyInteractionReportPluginData personPropertyInteractionReportPluginData = // + PersonPropertyInteractionReportPluginData.builder()// + .setReportPeriod(ReportPeriod.DAILY)// + .setReportLabel(expectedReportLabel)// + .build(); + + assertEquals(expectedReportLabel, personPropertyInteractionReportPluginData.getReportLabel()); + } + + // precondition: if the report label is null + ContractException contractException = assertThrows(ContractException.class, () -> { + PersonPropertyInteractionReportPluginData.builder().setReportLabel(null); + }); + assertEquals(ReportError.NULL_REPORT_LABEL, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PersonPropertyInteractionReportPluginData.Builder.class, name = "setReportPeriod", args = { + ReportPeriod.class }) + public void testSetReportPeriod() { + + ReportLabel reportLabel = new SimpleReportLabel("report label"); + for (ReportPeriod reportPeriod : ReportPeriod.values()) { + + PersonPropertyInteractionReportPluginData personPropertyInteractionReportPluginData = // + PersonPropertyInteractionReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .build(); + + assertEquals(reportPeriod, personPropertyInteractionReportPluginData.getReportPeriod()); + } + + // precondition: if the report period is null + ContractException contractException = assertThrows(ContractException.class, () -> { + PersonPropertyInteractionReportPluginData.builder().setReportPeriod(null); + }); + assertEquals(ReportError.NULL_REPORT_PERIOD, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PersonPropertyInteractionReportPluginData.Builder.class, name = "removePersonPropertyId", args = { + PersonPropertyId.class }) + public void testRemovePersonPropertyId() { + + ReportLabel reportLabel = new SimpleReportLabel("report label"); + + Set expectedPropertyIds = new LinkedHashSet<>(); + expectedPropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK); + expectedPropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK); + + PersonPropertyInteractionReportPluginData personPropertyInteractionReportPluginData = // + PersonPropertyInteractionReportPluginData.builder()// + .setReportPeriod(ReportPeriod.DAILY)// + .setReportLabel(reportLabel)// + .addPersonPropertyId(TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK) + .addPersonPropertyId(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK) + .addPersonPropertyId(TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK) + .removePersonPropertyId(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK) + .build(); + + assertEquals(expectedPropertyIds, personPropertyInteractionReportPluginData.getPersonPropertyIds()); + } + + @Test + @UnitTestMethod(target = PersonPropertyInteractionReportPluginData.Builder.class, name = "addPersonPropertyId", args = { + PersonPropertyId.class }) + public void testAddPersonPropertyId() { + + ReportLabel reportLabel = new SimpleReportLabel("report label"); + + Set expectedPropertyIds = new LinkedHashSet<>(); + expectedPropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK); + expectedPropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK); + expectedPropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK); + + PersonPropertyInteractionReportPluginData personPropertyInteractionReportPluginData = // + PersonPropertyInteractionReportPluginData.builder()// + .setReportPeriod(ReportPeriod.DAILY)// + .setReportLabel(reportLabel)// + .addPersonPropertyId(TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK) + .addPersonPropertyId(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK) + .addPersonPropertyId(TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK) + .addPersonPropertyId(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK) + .addPersonPropertyId(TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK).build(); + + assertEquals(expectedPropertyIds, personPropertyInteractionReportPluginData.getPersonPropertyIds()); + } + + @Test + @UnitTestMethod(target = PersonPropertyInteractionReportPluginData.class, name = "getCloneBuilder", args = {}) + public void testGetCloneBuilder() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6316245099247321601L); + for (int i = 0; i < 10; i++) { + + // build a PersonPropertyReportPluginData from random inputs + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt()); + ReportPeriod reportPeriod = ReportPeriod.values()[randomGenerator.nextInt(ReportPeriod.values().length)]; + + PersonPropertyInteractionReportPluginData.Builder builder = // + PersonPropertyInteractionReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel); + + for (int j = 0; j < 10; j++) { + TestPersonPropertyId testPersonPropertyId = TestPersonPropertyId + .getRandomPersonPropertyId(randomGenerator); + if (randomGenerator.nextBoolean()) { + builder.addPersonPropertyId(testPersonPropertyId); + } + } + + PersonPropertyInteractionReportPluginData personPropertyInteractionReportPluginData = builder.build(); + + // create the clone builder and have it build + PersonPropertyInteractionReportPluginData clonePersonPropertyInteractionReportPluginData = personPropertyInteractionReportPluginData + .getCloneBuilder().build(); + + // the result should equal the original if the clone builder was + // initialized with the correct state + assertEquals(personPropertyInteractionReportPluginData, clonePersonPropertyInteractionReportPluginData); + + } + } + + @Test + @UnitTestMethod(target = PersonPropertyInteractionReportPluginData.Builder.class, name = "build", args = {}) + public void testBuild() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonPropertyInteractionReportPluginData.class, name = "getPersonPropertyIds", args = {}) + public void testGetPersonPropertyIds() { + + ReportLabel reportLabel = new SimpleReportLabel("report label"); + + Set expectedPropertyIds = new LinkedHashSet<>(); + expectedPropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK); + expectedPropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK); + + PersonPropertyInteractionReportPluginData personPropertyInteractionReportPluginData = // + PersonPropertyInteractionReportPluginData.builder()// + .setReportPeriod(ReportPeriod.DAILY)// + .setReportLabel(reportLabel)// + .addPersonPropertyId(TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK) + .addPersonPropertyId(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK) + .addPersonPropertyId(TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK) + .removePersonPropertyId(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK) + .addPersonPropertyId(TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK).build(); + + assertEquals(expectedPropertyIds, personPropertyInteractionReportPluginData.getPersonPropertyIds()); + } + + private PersonPropertyInteractionReportPluginData getRandomPersonPropertyInteractionReportPluginData(long seed) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + + PersonPropertyInteractionReportPluginData.Builder builder = PersonPropertyInteractionReportPluginData.builder(); + ReportLabel reportLabel = new SimpleReportLabel("report label" + randomGenerator.nextInt(5)); + builder.setReportLabel(reportLabel); + int count = ReportPeriod.values().length; + int index = randomGenerator.nextInt(count); + ReportPeriod reportPeriod = ReportPeriod.values()[index]; + builder.setReportPeriod(reportPeriod); + + boolean first = true; + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId + .getShuffledPersonPropertyIds(randomGenerator)) { + if (first) { + builder.addPersonPropertyId(testPersonPropertyId); + first = false; + } else { + if (randomGenerator.nextBoolean()) { + builder.addPersonPropertyId(testPersonPropertyId); + } + } + } + + return builder.build(); + } + + @Test + @UnitTestMethod(target = PersonPropertyInteractionReportPluginData.class, name = "equals", args = { Object.class }) + public void testEquals() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(9157025935584862941L); + + // never equal to null + for (int i = 0; i < 30; i++) { + PersonPropertyInteractionReportPluginData reportPluginData = getRandomPersonPropertyInteractionReportPluginData( + randomGenerator.nextLong()); + assertFalse(reportPluginData.equals(null)); + } + + // reflexive + for (int i = 0; i < 30; i++) { + PersonPropertyInteractionReportPluginData reportPluginData = getRandomPersonPropertyInteractionReportPluginData( + randomGenerator.nextLong()); + assertTrue(reportPluginData.equals(reportPluginData)); + } + + // symmetric, transitive, consistent + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + PersonPropertyInteractionReportPluginData reportPluginData1 = getRandomPersonPropertyInteractionReportPluginData( + seed); + PersonPropertyInteractionReportPluginData reportPluginData2 = getRandomPersonPropertyInteractionReportPluginData( + seed); + + for (int j = 0; j < 5; j++) { + assertTrue(reportPluginData1.equals(reportPluginData2)); + assertTrue(reportPluginData2.equals(reportPluginData1)); + } + } + + // different inputs yield unequal objects. There are 7680 possible random plugin + // datas and a low probability for collision + Set peronInteractionReportPluginDatas = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + PersonPropertyInteractionReportPluginData pluginData = getRandomPersonPropertyInteractionReportPluginData( + randomGenerator.nextLong()); + peronInteractionReportPluginDatas.add(pluginData); + } + + assertTrue(peronInteractionReportPluginDatas.size() > 90); + + } + + @Test + @UnitTestMethod(target = PersonPropertyInteractionReportPluginData.class, name = "hashCode", args = {}) + public void testHashCode() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4074270846326821416L); + + // equal objects have equal hash codes + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + PersonPropertyInteractionReportPluginData reportPluginData1 = getRandomPersonPropertyInteractionReportPluginData( + seed); + PersonPropertyInteractionReportPluginData reportPluginData2 = getRandomPersonPropertyInteractionReportPluginData( + seed); + assertEquals(reportPluginData1, reportPluginData2); + assertEquals(reportPluginData1.hashCode(), reportPluginData2.hashCode()); + } + + // hash codes are reasonably distributed + Set hashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + PersonPropertyInteractionReportPluginData pluginData = getRandomPersonPropertyInteractionReportPluginData( + randomGenerator.nextLong()); + hashCodes.add(pluginData.hashCode()); + } + assertTrue(hashCodes.size() > 90); + } + + @Test + @UnitTestMethod(target = PersonPropertyInteractionReportPluginData.class, name = "toString", args = {}) + public void testToString() { + PersonPropertyInteractionReportPluginData reportPluginData = getRandomPersonPropertyInteractionReportPluginData(7710973343170558582L); + + String actualValue = reportPluginData.toString(); + + String expectedValue = "PersonPropertyInteractionReportPluginData [data=Data [" + + "reportLabel=SimpleReportLabel [value=report label0], " + + "reportPeriod=HOURLY, " + + "personPropertyIds=[" + + "PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, " + + "PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, " + + "PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK, " + + "PERSON_PROPERTY_5_INTEGER_MUTABLE_TRACK]]]"; + assertEquals(expectedValue, actualValue); + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/reports/AT_PersonPropertyReport.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/reports/AT_PersonPropertyReport.java new file mode 100644 index 000000000..a399eb6ba --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/reports/AT_PersonPropertyReport.java @@ -0,0 +1,1092 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.reports; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestOutputConsumer; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePlugin; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeoplePluginData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonRange; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.PersonPropertiesPlugin; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyDefinitionInitialization; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.testsupport.PersonPropertiesTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.testsupport.TestPersonPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.regions.RegionsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.TestRegionId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import util.annotations.UnitTag; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_PersonPropertyReport { + @Test + @UnitTestConstructor(target = PersonPropertyReport.class, args = { PersonPropertyReportPluginData.class }, tags = {}) + public void testConstructor() { + // construction is covered by the other tests + + /* + * Due to this being a periodic report with a super constructor, it is + * not possible for the report to throw a Contract exception when given + * a null PersonPropertyReportPluginData. + */ + + // precondition test: if the PersonPropertyReportPluginData is null + assertThrows(NullPointerException.class, () -> new PersonPropertyReport(null)); + } + + @Test + @UnitTestMethod(target = PersonPropertyReport.class, name = "init", args = { ReportContext.class }, tags = {}) + public void testInit_Content() { + + /* + * This test covers only the general content of the report by exercising + * a variety of person property changes over time. + */ + + List plugins = new ArrayList<>(); + PersonPropertyId unknownIdToExclude = new PersonPropertyId() { + @Override + public String toString() { + return "unknownIdToExclude"; + } + }; + PersonPropertyId unknownIdToInclude = new PersonPropertyId() { + @Override + public String toString() { + return "unknownIdToInclude"; + } + }; + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // have the actor add a new person property definitions + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(1).build(); + + PersonPropertyDefinitionInitialization personPropertyDefinitionInitialization = PersonPropertyDefinitionInitialization .builder().setPersonPropertyId(unknownIdToExclude) + .setPropertyDefinition(propertyDefinition).build(); + personPropertiesDataManager.definePersonProperty(personPropertyDefinitionInitialization); + + propertyDefinition = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(false).build(); + PersonPropertyDefinitionInitialization personPropertyDefinitionInitialization2 = PersonPropertyDefinitionInitialization .builder().setPersonPropertyId(unknownIdToInclude) + .setPropertyDefinition(propertyDefinition).build(); + personPropertiesDataManager.definePersonProperty(personPropertyDefinitionInitialization2); + + })); + + // have the actor set a few person property values + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0.4, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(7), TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 35); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(7), TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 40); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(8), TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK, 2.75); + })); + + // have the actor set a few person property values + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0.7, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(7), TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 35); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(7), TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 40); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(8), TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK, 2.75); + })); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1.2, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(8), unknownIdToInclude, true); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(17), unknownIdToExclude, 134); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(9), TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 17); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(10), TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, true); + })); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1.8, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(9), TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 59); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(10), TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, false); + })); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(9), TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 30); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(10), TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, true); + })); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(2.5, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(4), TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 12); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(6), TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 12); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(6), unknownIdToInclude, true); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(6), unknownIdToExclude, 99); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(11), unknownIdToInclude, true); + })); + + ReportLabel reportLabel = new SimpleReportLabel("report label"); + ReportPeriod hourlyReportPeriod = ReportPeriod.DAILY; + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + plugins.add(testPlugin); + + int populationSize = 30; + + PeoplePluginData.Builder peopleBuilder = PeoplePluginData.builder(); + peopleBuilder.addPersonRange(new PersonRange(0,populationSize-1)); + + PeoplePluginData peoplePluginData = peopleBuilder.build(); + Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(peoplePluginData); + plugins.add(peoplePlugin); + + // add the first three TestPersonPropertyId values and their definitions + PersonPropertiesPluginData.Builder personPropertyBuilder = PersonPropertiesPluginData.builder(); + Set basePropertyIds = new LinkedHashSet<>(); + basePropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK); + basePropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK); + basePropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK); + for (TestPersonPropertyId testPersonPropertyId : basePropertyIds) { + personPropertyBuilder.definePersonProperty(testPersonPropertyId, testPersonPropertyId.getPropertyDefinition(),0,false); + } + PersonPropertiesPluginData personPropertiesPluginData = personPropertyBuilder.build(); + + PersonPropertyReportPluginData.Builder builder = PersonPropertyReportPluginData.builder(); + builder.setReportLabel(reportLabel); + builder.setReportPeriod(hourlyReportPeriod); + builder.setDefaultInclusion(true); + builder.excludePersonProperty(TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK); + builder.excludePersonProperty(unknownIdToExclude); + PersonPropertyReportPluginData personPropertyReportPluginData = builder.build(); + + Plugin personPropertyPlugin = PersonPropertiesPlugin.builder()// + .setPersonPropertiesPluginData(personPropertiesPluginData)// + .setPersonPropertyReportPluginData(personPropertyReportPluginData)// + .getPersonPropertyPlugin(); + + plugins.add(personPropertyPlugin); + + /* + * Add two region - even people in region 1 and odd people in region 2 + */ + RegionsPluginData.Builder regionBuilder = RegionsPluginData.builder(); + + regionBuilder.addRegion(TestRegionId.REGION_1); + regionBuilder.addRegion(TestRegionId.REGION_2); + for (int i = 0; i < populationSize; i++) { + PersonId personId = peoplePluginData.getPersonIds().get(i); + if (i % 2 == 0) { + regionBuilder.addPerson(personId, TestRegionId.REGION_1); + } else { + regionBuilder.addPerson(personId, TestRegionId.REGION_2); + } + } + RegionsPluginData regionsPluginData = regionBuilder.build(); + Plugin regionsPlugin = RegionsPlugin.builder().setRegionsPluginData(regionsPluginData).getRegionsPlugin(); + plugins.add(regionsPlugin); + + // execute the simulation and gather the report items + + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(plugins)// + .build()// + .execute(); + + // build the expected report items + TestOutputConsumer expectedOutputConsumer = new TestOutputConsumer(); + + // expected report at time 0 + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 0, TestRegionId.REGION_1, TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, false, 15)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 0, TestRegionId.REGION_1, TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 0, 15)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 0, TestRegionId.REGION_2, TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, false, 15)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 0, TestRegionId.REGION_2, TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 0, 15)); + + // expected report at time 1 + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 1, TestRegionId.REGION_1, TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, false, 15)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 1, TestRegionId.REGION_1, TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 0, 15)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 1, TestRegionId.REGION_1, unknownIdToInclude, false, 15)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 1, TestRegionId.REGION_2, TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, false, 15)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 1, TestRegionId.REGION_2, TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 0, 14)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 1, TestRegionId.REGION_2, TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 40, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 1, TestRegionId.REGION_2, unknownIdToInclude, false, 15)); + + // expected report at time 2 + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 2, TestRegionId.REGION_1, TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, false, 14)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 2, TestRegionId.REGION_1, TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, true, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 2, TestRegionId.REGION_1, TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 0, 15)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 2, TestRegionId.REGION_1, unknownIdToInclude, false, 14)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 2, TestRegionId.REGION_1, unknownIdToInclude, true, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 2, TestRegionId.REGION_2, TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, false, 15)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 2, TestRegionId.REGION_2, TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 0, 13)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 2, TestRegionId.REGION_2, TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 40, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 2, TestRegionId.REGION_2, TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 30, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 2, TestRegionId.REGION_2, unknownIdToInclude, false, 15)); + + // expected report at time 3 + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 3, TestRegionId.REGION_1, TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, false, 14)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 3, TestRegionId.REGION_1, TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, true, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 3, TestRegionId.REGION_1, TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 0, 13)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 3, TestRegionId.REGION_1, TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 12, 2)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 3, TestRegionId.REGION_1, unknownIdToInclude, false, 13)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 3, TestRegionId.REGION_1, unknownIdToInclude, true, 2)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 3, TestRegionId.REGION_2, TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, false, 15)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 3, TestRegionId.REGION_2, TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 0, 13)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 3, TestRegionId.REGION_2, TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 40, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 3, TestRegionId.REGION_2, TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 30, 1)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 3, TestRegionId.REGION_2, unknownIdToInclude, false, 14)); + expectedOutputConsumer.accept(getReportItem(ReportPeriod.DAILY, 3, TestRegionId.REGION_2, unknownIdToInclude, true, 1)); + + // compare the expected and actual report items + Map expectedReportItems = expectedOutputConsumer.getOutputItemMap(ReportItem.class); + Map actualReportItems = testOutputConsumer.getOutputItemMap(ReportItem.class); + assertEquals(expectedReportItems, actualReportItems); + } + + @Test + @UnitTestMethod(target = PersonPropertyReport.class, name = "init", args = { ReportContext.class }, tags = { UnitTag.INCOMPLETE }) + public void testInit_ReportLabel() { + /* + * This test shows that the report produces report items with the + * correct report labels. + */ + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + // provides an empty task so that the TestSimulation.execute does + // not throw an exception + })); + + ReportLabel reportLabel = new SimpleReportLabel(1000); + + PersonPropertyReportPluginData.Builder builder = PersonPropertyReportPluginData.builder(); + builder.setReportLabel(reportLabel); + builder.setReportPeriod(ReportPeriod.DAILY); + PersonPropertyReportPluginData personPropertyReportPluginData = builder.build(); + + TestPluginData testPluginData = pluginDataBuilder.build(); + PersonPropertiesTestPluginFactory.Factory factory = PersonPropertiesTestPluginFactory// + .factory(30, 1174198461656549476L, testPluginData)// + .setPersonPropertyReportPluginData(personPropertyReportPluginData);// + + List plugins = factory.getPlugins(); + + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(plugins)// + .build()// + .execute(); + // show that the report labels are what we expect for each report item + Map outputItems = testOutputConsumer.getOutputItemMap(ReportItem.class); + assertFalse(outputItems.isEmpty()); + for (ReportItem reportItem : outputItems.keySet()) { + assertEquals(reportItem.getReportLabel(), reportLabel); + } + + } + + @Test + @UnitTestMethod(target = PersonPropertyReport.class, name = "init", args = { ReportContext.class }, tags = { UnitTag.INCOMPLETE }) + public void testInit_ReportHeader() { + for(ReportPeriod reportPeriod : ReportPeriod.values()) { + testInit_ReportHeader(reportPeriod); + } + } + + private void testInit_ReportHeader(ReportPeriod reportPeriod) { + /* + * This test shows that the report produces report items with the + * correct report headers. + */ + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + // provides an empty task so that the TestSimulation.execute does + // not throw an exception + })); + + ReportLabel reportLabel = new SimpleReportLabel(1000); + + PersonPropertyReportPluginData.Builder builder = PersonPropertyReportPluginData.builder(); + builder.setReportLabel(reportLabel); + builder.setReportPeriod(reportPeriod); + PersonPropertyReportPluginData personPropertyReportPluginData = builder.build(); + + TestPluginData testPluginData = pluginDataBuilder.build(); + PersonPropertiesTestPluginFactory.Factory factory = // + PersonPropertiesTestPluginFactory// + .factory(30, 1174198461656549476L, testPluginData)// + .setPersonPropertyReportPluginData(personPropertyReportPluginData); + List plugins = factory.getPlugins(); + + + + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(plugins)// + .build()// + .execute(); + + // show that the report labels are what we expect for each report item + Map outputItems = testOutputConsumer.getOutputItemMap(ReportItem.class); + assertFalse(outputItems.isEmpty()); + + ReportHeader expectedReportHeader; + switch(reportPeriod) { + case DAILY: + expectedReportHeader = REPORT_DAILY_HEADER; + break; + case END_OF_SIMULATION: + expectedReportHeader = REPORT_END_OF_SIMULATION_HEADER; + break; + case HOURLY: + expectedReportHeader = REPORT_HOURLY_HEADER; + break; + default: + throw new RuntimeException("unhandled case "+reportPeriod); + + } + + for (ReportItem reportItem : outputItems.keySet()) { + assertEquals(expectedReportHeader,reportItem.getReportHeader()); + } + + } + + @Test + @UnitTestMethod(target = PersonPropertyReport.class, name = "init", args = { ReportContext.class }, tags = { UnitTag.INCOMPLETE }) + public void testInit_ReportPeriod() { + /* + * This test shows that the report produces report items with the + * correct report periods. + */ + + // post conditional testing for Hourly, Daily, and End Of Simulation + // report periods + testSetReportPeriod_Hourly(); + testSetReportPeriod_Daily(); + testSetReportPeriod_EndOfSim(); + } + + private void testSetReportPeriod_Hourly() { + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + // provides an empty task so that the TestSimulation.execute does + // not throw an exception + })); + + ReportLabel reportLabel = new SimpleReportLabel(1000); + ReportPeriod hourlyReportPeriod = ReportPeriod.HOURLY; + + PersonPropertyReportPluginData.Builder builder = PersonPropertyReportPluginData.builder(); + builder.setReportLabel(reportLabel); + builder.setReportPeriod(hourlyReportPeriod); + PersonPropertyReportPluginData personPropertyReportPluginData = builder.build(); + + TestPluginData testPluginData = pluginDataBuilder.build(); + PersonPropertiesTestPluginFactory.Factory factory = // + PersonPropertiesTestPluginFactory// + .factory(30, 1174198461656549476L, testPluginData) + .setPersonPropertyReportPluginData(personPropertyReportPluginData); + + + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .build()// + .execute(); + + // show that the report periods are what we expect for each report item + Map outputItems = testOutputConsumer.getOutputItemMap(ReportItem.class); + assertFalse(outputItems.isEmpty()); + for (ReportItem reportItem : outputItems.keySet()) { + assertEquals(reportItem.getReportHeader().getHeaderStrings().get(0), "day"); + assertEquals(reportItem.getReportHeader().getHeaderStrings().get(1), "hour"); + } + } + + private void testSetReportPeriod_Daily() { + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + // provides an empty task so that the TestSimulation.execute does + // not throw an exception + })); + + ReportLabel reportLabel = new SimpleReportLabel(1000); + ReportPeriod hourlyReportPeriod = ReportPeriod.DAILY; + + PersonPropertyReportPluginData.Builder builder = PersonPropertyReportPluginData.builder(); + builder.setReportLabel(reportLabel); + builder.setReportPeriod(hourlyReportPeriod); + PersonPropertyReportPluginData personPropertyReportPluginData = builder.build(); + + TestPluginData testPluginData = pluginDataBuilder.build(); + PersonPropertiesTestPluginFactory.Factory factory = // + PersonPropertiesTestPluginFactory// + .factory(30, 1174198461656549476L, testPluginData) + .setPersonPropertyReportPluginData(personPropertyReportPluginData); + + + + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .build()// + .execute(); + + // show that the report periods are what we expect for each report item + Map outputItems = testOutputConsumer.getOutputItemMap(ReportItem.class); + assertFalse(outputItems.isEmpty()); + for (ReportItem reportItem : outputItems.keySet()) { + assertEquals(reportItem.getReportHeader().getHeaderStrings().get(0), "day"); + assertFalse(reportItem.getReportHeader().getHeaderStrings().contains("hour")); + } + } + + private void testSetReportPeriod_EndOfSim() { + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + // provides an empty task so that the TestSimulation.execute does + // not throw an exception + })); + + ReportLabel reportLabel = new SimpleReportLabel(1000); + ReportPeriod hourlyReportPeriod = ReportPeriod.END_OF_SIMULATION; + + PersonPropertyReportPluginData.Builder builder = PersonPropertyReportPluginData.builder(); + builder.setReportLabel(reportLabel); + builder.setReportPeriod(hourlyReportPeriod); + PersonPropertyReportPluginData personPropertyReportPluginData = builder.build(); + + TestPluginData testPluginData = pluginDataBuilder.build(); + PersonPropertiesTestPluginFactory.Factory factory = // + PersonPropertiesTestPluginFactory// + .factory(30, 1174198461656549476L, testPluginData) + .setPersonPropertyReportPluginData(personPropertyReportPluginData); + + + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .build()// + .execute(); + + // show that the report periods are what we expect for each report item + Map outputItems = testOutputConsumer.getOutputItemMap(ReportItem.class); + assertFalse(outputItems.isEmpty()); + for (ReportItem reportItem : outputItems.keySet()) { + assertFalse(reportItem.getReportHeader().getHeaderStrings().contains("day")); + assertFalse(reportItem.getReportHeader().getHeaderStrings().contains("hour")); + } + } + + @Test + @UnitTestMethod(target = PersonPropertyReport.class, name = "init", args = { ReportContext.class }, tags = {}) + public void testInit_DefaultInclusion() { + /* + * This test shows that the report produces report items with the + * correct selected properties as a function of the default property + * inclusion policy + */ + + testSetDefaultInclusion_False(); + testSetDefaultInclusion_True(); + testSetDefaultInclusion_Unset(); + } + + private void testSetDefaultInclusion_False() { + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // create a test actor plan where we set several person property values + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + for (PersonId personId : peopleDataManager.getPeople()) { + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + if (testPersonPropertyId.getPropertyDefinition().propertyValuesAreMutable()) { + Object personProperty = testPersonPropertyId.getRandomPropertyValue(randomGenerator); + personPropertiesDataManager.setPersonPropertyValue(personId, testPersonPropertyId, personProperty); + } + } + } + })); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); + + PersonPropertyId unknownPersonPropertyId = TestPersonPropertyId.getUnknownPersonPropertyId(); + + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(1).build(); + PersonPropertyDefinitionInitialization personPropertyDefinitionInitialization = PersonPropertyDefinitionInitialization .builder().setPersonPropertyId(unknownPersonPropertyId) + .setPropertyDefinition(propertyDefinition).build(); + + personPropertiesDataManager.definePersonProperty(personPropertyDefinitionInitialization); + })); + + ReportLabel reportLabel = new SimpleReportLabel(1000); + ReportPeriod hourlyReportPeriod = ReportPeriod.HOURLY; + + PersonPropertyReportPluginData.Builder builder = PersonPropertyReportPluginData.builder(); + builder.setReportLabel(reportLabel); + builder.setReportPeriod(hourlyReportPeriod); + builder.setDefaultInclusion(false); + PersonPropertyReportPluginData personPropertyReportPluginData = builder.build(); + + TestPluginData testPluginData = pluginDataBuilder.build(); + PersonPropertiesTestPluginFactory.Factory factory = // + PersonPropertiesTestPluginFactory// + .factory(30, 1174198461656549476L, testPluginData)// + .setPersonPropertyReportPluginData(personPropertyReportPluginData); + + List plugins = factory.getPlugins(); + + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(plugins)// + .build()// + .execute(); + Map outputItems = testOutputConsumer.getOutputItemMap(ReportItem.class); + assertTrue(outputItems.isEmpty()); + } + + private void testSetDefaultInclusion_True() { + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // create a test actor plan where we set several person property values + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + for (PersonId personId : peopleDataManager.getPeople()) { + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + if (testPersonPropertyId.getPropertyDefinition().propertyValuesAreMutable()) { + Object personProperty = testPersonPropertyId.getRandomPropertyValue(randomGenerator); + personPropertiesDataManager.setPersonPropertyValue(personId, testPersonPropertyId, personProperty); + } + } + } + })); + + // create an unknown property id + PersonPropertyId unknownPersonPropertyId = TestPersonPropertyId.getUnknownPersonPropertyId(); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(1).build(); + + PersonPropertyDefinitionInitialization personPropertyDefinitionInitialization = PersonPropertyDefinitionInitialization .builder().setPersonPropertyId(unknownPersonPropertyId) + .setPropertyDefinition(propertyDefinition).build(); + personPropertiesDataManager.definePersonProperty(personPropertyDefinitionInitialization); + + PersonId personId = peopleDataManager.getPeople().get(0); + personPropertiesDataManager.setPersonPropertyValue(personId, unknownPersonPropertyId, 2); + })); + + ReportLabel reportLabel = new SimpleReportLabel(1000); + ReportPeriod hourlyReportPeriod = ReportPeriod.HOURLY; + + PersonPropertyReportPluginData.Builder builder = PersonPropertyReportPluginData.builder(); + builder.setReportLabel(reportLabel); + builder.setReportPeriod(hourlyReportPeriod); + builder.setDefaultInclusion(true); + PersonPropertyReportPluginData personPropertyReportPluginData = builder.build(); + + + TestPluginData testPluginData = pluginDataBuilder.build(); + PersonPropertiesTestPluginFactory.Factory factory = // + PersonPropertiesTestPluginFactory// + .factory(30, 1174198461656549476L, testPluginData) + .setPersonPropertyReportPluginData(personPropertyReportPluginData); + + List plugins = factory.getPlugins(); + + // set the default inclusion to false + + + + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(plugins)// + .build()// + .execute(); + + // show that whe find the expected person property ids + Map outputItems = testOutputConsumer.getOutputItemMap(ReportItem.class); + assertFalse(outputItems.isEmpty()); + Set outputPropertyStrings = new LinkedHashSet<>(); + for (ReportItem reportItem : outputItems.keySet()) { + outputPropertyStrings.add(reportItem.getValue(3)); + } + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + assertTrue(outputPropertyStrings.contains(testPersonPropertyId.toString())); + } + assertTrue(outputPropertyStrings.contains(unknownPersonPropertyId.toString())); + } + + private void testSetDefaultInclusion_Unset() { + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // create a test actor plan where we set several person property values + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + for (PersonId personId : peopleDataManager.getPeople()) { + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + if (testPersonPropertyId.getPropertyDefinition().propertyValuesAreMutable()) { + Object personProperty = testPersonPropertyId.getRandomPropertyValue(randomGenerator); + personPropertiesDataManager.setPersonPropertyValue(personId, testPersonPropertyId, personProperty); + } + } + } + })); + + // create an unknown property id + PersonPropertyId unknownPersonPropertyId = TestPersonPropertyId.getUnknownPersonPropertyId(); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(1).build(); + + PersonPropertyDefinitionInitialization personPropertyDefinitionInitialization = PersonPropertyDefinitionInitialization .builder().setPersonPropertyId(unknownPersonPropertyId) + .setPropertyDefinition(propertyDefinition).build(); + personPropertiesDataManager.definePersonProperty(personPropertyDefinitionInitialization); + PersonId personId = peopleDataManager.getPeople().get(0); + personPropertiesDataManager.setPersonPropertyValue(personId, unknownPersonPropertyId, 2); + })); + + ReportLabel reportLabel = new SimpleReportLabel(1000); + ReportPeriod hourlyReportPeriod = ReportPeriod.HOURLY; + + PersonPropertyReportPluginData.Builder builder = PersonPropertyReportPluginData.builder(); + builder.setReportLabel(reportLabel); + builder.setReportPeriod(hourlyReportPeriod); + PersonPropertyReportPluginData personPropertyReportPluginData = builder.build(); + + TestPluginData testPluginData = pluginDataBuilder.build(); + PersonPropertiesTestPluginFactory.Factory factory = // + PersonPropertiesTestPluginFactory// + .factory(30, 1174198461656549476L, testPluginData) + .setPersonPropertyReportPluginData(personPropertyReportPluginData); + + List plugins = factory.getPlugins(); + + // set the default inclusion to false + + + + + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(plugins)// + .build()// + .execute(); + + // show that when the default inclusion is unset, it defaults to true + Map outputItems = testOutputConsumer.getOutputItemMap(ReportItem.class); + assertFalse(outputItems.isEmpty()); + Set outputPropertyStrings = new LinkedHashSet<>(); + for (ReportItem reportItem : outputItems.keySet()) { + outputPropertyStrings.add(reportItem.getValue(3)); + } + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + assertTrue(outputPropertyStrings.contains(testPersonPropertyId.toString())); + } + assertTrue(outputPropertyStrings.contains(unknownPersonPropertyId.toString())); + } + + @Test + @UnitTestMethod(target = PersonPropertyReport.class, name = "init", args = { ReportContext.class }, tags = {}) + public void testInit_IncludePersonProperty() { + + /* + * This test shows that the report produces report items with the + * correct selected properties as a function of the explicitly included + * properties + */ + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // create a test actor plan where we set several person property values + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + for (PersonId personId : peopleDataManager.getPeople()) { + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + if (testPersonPropertyId.getPropertyDefinition().propertyValuesAreMutable()) { + Object personProperty = testPersonPropertyId.getRandomPropertyValue(randomGenerator); + personPropertiesDataManager.setPersonPropertyValue(personId, testPersonPropertyId, personProperty); + } + } + } + })); + + PersonPropertyId unknownPersonPropertyId = TestPersonPropertyId.getUnknownPersonPropertyId(); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(1).build(); + + PersonPropertyDefinitionInitialization personPropertyDefinitionInitialization = PersonPropertyDefinitionInitialization .builder().setPersonPropertyId(unknownPersonPropertyId) + .setPropertyDefinition(propertyDefinition).build(); + personPropertiesDataManager.definePersonProperty(personPropertyDefinitionInitialization); + + PersonId personId = peopleDataManager.getPeople().get(0); + personPropertiesDataManager.setPersonPropertyValue(personId, unknownPersonPropertyId, 2); + })); + + ReportLabel reportLabel = new SimpleReportLabel(1000); + ReportPeriod hourlyReportPeriod = ReportPeriod.HOURLY; + TestPersonPropertyId testPersonPropertyId = TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; + + PersonPropertyReportPluginData.Builder builder = PersonPropertyReportPluginData.builder(); + builder.setReportLabel(reportLabel); + builder.setReportPeriod(hourlyReportPeriod); + builder.setDefaultInclusion(false); + builder.includePersonProperty(testPersonPropertyId); + builder.includePersonProperty(unknownPersonPropertyId); + PersonPropertyReportPluginData personPropertyReportPluginData = builder.build(); + + + TestPluginData testPluginData = pluginDataBuilder.build(); + PersonPropertiesTestPluginFactory.Factory factory = // + PersonPropertiesTestPluginFactory// + .factory(30, 1174198461656549476L, testPluginData) + .setPersonPropertyReportPluginData(personPropertyReportPluginData); + + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .build()// + .execute(); + + // show that our report items include the chosen property id + Map outputItems = testOutputConsumer.getOutputItemMap(ReportItem.class); + assertFalse(outputItems.isEmpty()); + Set outputPropertyStrings = new LinkedHashSet<>(); + for (ReportItem reportItem : outputItems.keySet()) { + outputPropertyStrings.add(reportItem.getValue(3)); + } + assertTrue(outputPropertyStrings.contains(testPersonPropertyId.toString())); + assertTrue(outputPropertyStrings.contains(unknownPersonPropertyId.toString())); + + } + + @Test + @UnitTestMethod(target = PersonPropertyReport.class, name = "init", args = { ReportContext.class }, tags = {}) + public void testInit_ExcludePersonProperty() { + /* + * This test shows that the report produces report items with the + * correct selected properties as a function of the explicitly excluded + * properties + */ + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // create a test actor plan where we set several person property values + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + for (PersonId personId : peopleDataManager.getPeople()) { + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + if (testPersonPropertyId.getPropertyDefinition().propertyValuesAreMutable()) { + Object personProperty = testPersonPropertyId.getRandomPropertyValue(randomGenerator); + personPropertiesDataManager.setPersonPropertyValue(personId, testPersonPropertyId, personProperty); + } + } + } + })); + + PersonPropertyId unknownPersonPropertyId = TestPersonPropertyId.getUnknownPersonPropertyId(); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(1).build(); + + PersonPropertyDefinitionInitialization personPropertyDefinitionInitialization = PersonPropertyDefinitionInitialization .builder().setPersonPropertyId(unknownPersonPropertyId) + .setPropertyDefinition(propertyDefinition).build(); + personPropertiesDataManager.definePersonProperty(personPropertyDefinitionInitialization); + + PersonId personId = peopleDataManager.getPeople().get(0); + personPropertiesDataManager.setPersonPropertyValue(personId, unknownPersonPropertyId, 2); + })); + + ReportLabel reportLabel = new SimpleReportLabel(1000); + ReportPeriod hourlyReportPeriod = ReportPeriod.HOURLY; + TestPersonPropertyId testPersonPropertyId = TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; + + PersonPropertyReportPluginData.Builder builder = PersonPropertyReportPluginData.builder(); + builder.setReportLabel(reportLabel); + builder.setReportPeriod(hourlyReportPeriod); + builder.excludePersonProperty(testPersonPropertyId); + PersonPropertyReportPluginData personPropertyReportPluginData = builder.build(); + + TestPluginData testPluginData = pluginDataBuilder.build(); + PersonPropertiesTestPluginFactory.Factory factory = // + PersonPropertiesTestPluginFactory// + .factory(30, 1174198461656549476L, testPluginData) + .setPersonPropertyReportPluginData(personPropertyReportPluginData); + + List plugins = factory.getPlugins(); + + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(plugins)// + .build()// + .execute(); + + + // show that our report items do not include the chosen property id + Map outputItems = testOutputConsumer.getOutputItemMap(ReportItem.class); + assertFalse(outputItems.isEmpty()); + Set outputPropertyStrings = new LinkedHashSet<>(); + for (ReportItem reportItem : outputItems.keySet()) { + outputPropertyStrings.add(reportItem.getValue(3)); + } + assertFalse(outputPropertyStrings.contains(testPersonPropertyId.toString())); + assertTrue(outputPropertyStrings.contains(unknownPersonPropertyId.toString())); + } + + private static ReportItem getReportItem(ReportPeriod reportPeriod, Object... values) { + ReportItem.Builder builder = ReportItem.builder(); + builder.setReportLabel(REPORT_LABEL); + + switch (reportPeriod) { + case DAILY: + builder.setReportHeader(REPORT_DAILY_HEADER); + break; + case HOURLY: + builder.setReportHeader(REPORT_HOURLY_HEADER); + break; + case END_OF_SIMULATION:// fall through + default: + throw new RuntimeException("unhandled case " + reportPeriod); + + } + + for (Object value : values) { + builder.addValue(value); + } + return builder.build(); + } + + @Test + @UnitTestMethod(target = PersonPropertyReport.class, name = "init", args = { ReportContext.class }) + public void testInit_State() { + // Test with producing simulation output + + List plugins = new ArrayList<>(); + PersonPropertyId unknownIdToExclude = new PersonPropertyId() { + @Override + public String toString() { + return "unknownIdToExclude"; + } + }; + PersonPropertyId unknownIdToInclude = new PersonPropertyId() { + @Override + public String toString() { + return "unknownIdToInclude"; + } + }; + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // have the actor add two new person property definitions + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(1).build(); + + PersonPropertyDefinitionInitialization personPropertyDefinitionInitialization = PersonPropertyDefinitionInitialization .builder()// + .setPersonPropertyId(unknownIdToExclude) + .setPropertyDefinition(propertyDefinition)// + .build(); + + personPropertiesDataManager.definePersonProperty(personPropertyDefinitionInitialization); + + propertyDefinition = PropertyDefinition.builder()// + .setType(Boolean.class).setDefaultValue(false)// + .build(); + + PersonPropertyDefinitionInitialization personPropertyDefinitionInitialization2 = // + PersonPropertyDefinitionInitialization .builder()// + .setPersonPropertyId(unknownIdToInclude) + .setPropertyDefinition(propertyDefinition)// + .build(); + + personPropertiesDataManager.definePersonProperty(personPropertyDefinitionInitialization2); + + })); + + // have the actor set a few person property values + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0.4, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(7), TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 35); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(7), TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 40); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(8), TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK, 2.75); + })); + + // have the actor set a few person property values + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0.7, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(7), TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 35); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(7), TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 40); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(8), TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK, 2.75); + })); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1.2, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(8), unknownIdToInclude, true); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(17), unknownIdToExclude, 134); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(9), TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 17); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(10), TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, true); + })); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1.8, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(9), TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 59); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(10), TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, false); + })); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(9), TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 30); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(10), TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, true); + })); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(2.5, (c) -> { + PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(4), TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 12); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(6), TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 12); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(6), unknownIdToInclude, true); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(6), unknownIdToExclude, 99); + personPropertiesDataManager.setPersonPropertyValue(new PersonId(11), unknownIdToInclude, true); + })); + + ReportLabel reportLabel = new SimpleReportLabel("report label"); + ReportPeriod hourlyReportPeriod = ReportPeriod.DAILY; + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + plugins.add(testPlugin); + + int populationSize = 30; + + PeoplePluginData.Builder peopleBuilder = PeoplePluginData.builder(); + peopleBuilder.addPersonRange(new PersonRange(0,populationSize-1)); + + PeoplePluginData peoplePluginData = peopleBuilder.build(); + Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(peoplePluginData); + plugins.add(peoplePlugin); + + // add the first three TestPersonPropertyId values and their definitions + PersonPropertiesPluginData.Builder personPropertyBuilder = PersonPropertiesPluginData.builder(); + Set basePropertyIds = new LinkedHashSet<>(); + basePropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK); + basePropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK); + basePropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK); + for (TestPersonPropertyId testPersonPropertyId : basePropertyIds) { + personPropertyBuilder.definePersonProperty(testPersonPropertyId, testPersonPropertyId.getPropertyDefinition(),0,false); + } + PersonPropertiesPluginData personPropertiesPluginData = personPropertyBuilder.build(); + + PersonPropertyReportPluginData.Builder builder = PersonPropertyReportPluginData.builder(); + builder.setReportLabel(reportLabel); + builder.setReportPeriod(hourlyReportPeriod); + builder.setDefaultInclusion(true); + builder.excludePersonProperty(TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK); + builder.excludePersonProperty(unknownIdToExclude); + PersonPropertyReportPluginData personPropertyReportPluginData = builder.build(); + + Plugin personPropertyPlugin = PersonPropertiesPlugin.builder()// + .setPersonPropertiesPluginData(personPropertiesPluginData)// + .setPersonPropertyReportPluginData(personPropertyReportPluginData)// + .getPersonPropertyPlugin(); + + plugins.add(personPropertyPlugin); + + /* + * Add two region - even people in region 1 and odd people in region 2 + */ + RegionsPluginData.Builder regionBuilder = RegionsPluginData.builder(); + + regionBuilder.addRegion(TestRegionId.REGION_1); + regionBuilder.addRegion(TestRegionId.REGION_2); + for (int i = 0; i < populationSize; i++) { + PersonId personId = peoplePluginData.getPersonIds().get(i); + if (i % 2 == 0) { + regionBuilder.addPerson(personId, TestRegionId.REGION_1); + } else { + regionBuilder.addPerson(personId, TestRegionId.REGION_2); + } + } + RegionsPluginData regionsPluginData = regionBuilder.build(); + Plugin regionsPlugin = RegionsPlugin.builder().setRegionsPluginData(regionsPluginData).getRegionsPlugin(); + plugins.add(regionsPlugin); + + // execute the simulation and gather the report items + + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(plugins)// + .setProduceSimulationStateOnHalt(true)// + .setSimulationHaltTime(20)// + .build()// + .execute(); + + Map outputItems = testOutputConsumer.getOutputItemMap(PersonPropertyReportPluginData.class); + assertEquals(1, outputItems.size()); + PersonPropertyReportPluginData personPropertyReportPluginData2 = outputItems.keySet().iterator().next(); + assertEquals(personPropertyReportPluginData, personPropertyReportPluginData2); + + // Test without producing simulation state + + testOutputConsumer = TestSimulation .builder()// + .addPlugins(plugins)// + .setProduceSimulationStateOnHalt(false)// + .setSimulationHaltTime(20)// + .build()// + .execute(); + + outputItems = testOutputConsumer.getOutputItemMap(PersonPropertyReportPluginData.class); + assertEquals(0, outputItems.size()); + } + + private static final ReportLabel REPORT_LABEL = new SimpleReportLabel("report label"); + + private static final ReportHeader REPORT_HOURLY_HEADER = ReportHeader.builder().add("day").add("hour").add("region").add("property").add("value").add("person_count").build(); + private static final ReportHeader REPORT_DAILY_HEADER = ReportHeader.builder().add("day").add("region").add("property").add("value").add("person_count").build(); + private static final ReportHeader REPORT_END_OF_SIMULATION_HEADER = ReportHeader.builder().add("region").add("property").add("value").add("person_count").build(); +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/reports/AT_PersonPropertyReportPluginData.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/reports/AT_PersonPropertyReportPluginData.java new file mode 100644 index 000000000..8ecb5f4da --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/reports/AT_PersonPropertyReportPluginData.java @@ -0,0 +1,641 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.reports; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.testsupport.TestPersonPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportError; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_PersonPropertyReportPluginData { + + @Test + @UnitTestMethod(target = PersonPropertyReportPluginData.class, name = "builder", args = {}) + public void testBuilder() { + PersonPropertyReportPluginData.Builder builder = PersonPropertyReportPluginData.builder(); + assertNotNull(builder); + } + + @Test + @UnitTestMethod(target = PersonPropertyReportPluginData.Builder.class, name = "build", args = {}) + public void testBuild() { + // the specific capabilities are covered elsewhere + + // precondition test: if the report period is not assigned + ContractException contractException = assertThrows(ContractException.class, () -> // + PersonPropertyReportPluginData.builder()// + .setReportLabel(new SimpleReportLabel(getClass()))// + .build()); + assertEquals(ReportError.NULL_REPORT_PERIOD, contractException.getErrorType()); + + // precondition test: if the report label is not assigned + contractException = assertThrows(ContractException.class, () -> // + PersonPropertyReportPluginData.builder()// + .setReportPeriod(ReportPeriod.DAILY)// + .build()); + assertEquals(ReportError.NULL_REPORT_LABEL, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = PersonPropertyReportPluginData.Builder.class, name = "setReportLabel", args = { + ReportLabel.class }) + public void testSetReportLabel() { + + for (int i = 0; i < 30; i++) { + ReportLabel expectedReportLabel = new SimpleReportLabel(i); + PersonPropertyReportPluginData personPropertyReportPluginData = // + PersonPropertyReportPluginData.builder()// + .setReportPeriod(ReportPeriod.DAILY)// + .setReportLabel(expectedReportLabel)// + .build(); + + assertEquals(expectedReportLabel, personPropertyReportPluginData.getReportLabel()); + } + + // precondition: if the report label is null + ContractException contractException = assertThrows(ContractException.class, () -> { + PersonPropertyReportPluginData.builder().setReportLabel(null); + }); + assertEquals(ReportError.NULL_REPORT_LABEL, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PersonPropertyReportPluginData.Builder.class, name = "setReportPeriod", args = { + ReportPeriod.class }) + public void testSetReportPeriod() { + + ReportLabel reportLabel = new SimpleReportLabel("report label"); + for (ReportPeriod reportPeriod : ReportPeriod.values()) { + + PersonPropertyReportPluginData personPropertyReportPluginData = // + PersonPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .build(); + + assertEquals(reportPeriod, personPropertyReportPluginData.getReportPeriod()); + } + + // precondition: if the report period is null + ContractException contractException = assertThrows(ContractException.class, () -> { + PersonPropertyReportPluginData.builder().setReportPeriod(null); + }); + assertEquals(ReportError.NULL_REPORT_PERIOD, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PersonPropertyReportPluginData.Builder.class, name = "setDefaultInclusion", args = { + boolean.class }) + public void testSetDefaultInclusion() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + ReportPeriod reportPeriod = ReportPeriod.DAILY; + + // show the default value is true + PersonPropertyReportPluginData personPropertyReportPluginData = // + PersonPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .build(); + assertEquals(true, personPropertyReportPluginData.getDefaultInclusionPolicy()); + + personPropertyReportPluginData = // + PersonPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .setDefaultInclusion(true)// + .build(); + assertEquals(true, personPropertyReportPluginData.getDefaultInclusionPolicy()); + + personPropertyReportPluginData = // + PersonPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .setDefaultInclusion(false)// + .build(); + assertEquals(false, personPropertyReportPluginData.getDefaultInclusionPolicy()); + + } + + @Test + @UnitTestMethod(target = PersonPropertyReportPluginData.Builder.class, name = "includePersonProperty", args = { + PersonPropertyId.class }) + public void testIncludePersonProperty() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + ReportPeriod reportPeriod = ReportPeriod.DAILY; + + PersonPropertyReportPluginData personPropertyReportPluginData = // + PersonPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .build(); + assertTrue(personPropertyReportPluginData.getIncludedProperties().isEmpty()); + + // show that inclusion alone works + Set expectedPersonPropertyIds = new LinkedHashSet<>(); + + expectedPersonPropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK); + expectedPersonPropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_4_BOOLEAN_MUTABLE_TRACK); + expectedPersonPropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK); + + PersonPropertyReportPluginData.Builder builder = PersonPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel);// + + for (PersonPropertyId personPropertyId : expectedPersonPropertyIds) { + builder.includePersonProperty(personPropertyId); + } + + personPropertyReportPluginData = builder.build(); + assertEquals(expectedPersonPropertyIds, personPropertyReportPluginData.getIncludedProperties()); + + // show that inclusion will override exclusion + expectedPersonPropertyIds.clear(); + + expectedPersonPropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK); + expectedPersonPropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_4_BOOLEAN_MUTABLE_TRACK); + expectedPersonPropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK); + + builder = PersonPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel);// + + for (PersonPropertyId personPropertyId : expectedPersonPropertyIds) { + builder.excludePersonProperty(personPropertyId); + builder.includePersonProperty(personPropertyId); + } + + personPropertyReportPluginData = builder.build(); + assertEquals(expectedPersonPropertyIds, personPropertyReportPluginData.getIncludedProperties()); + + // precondition: if the person property id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + PersonPropertyReportPluginData.builder().includePersonProperty(null); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PersonPropertyReportPluginData.Builder.class, name = "excludePersonProperty", args = { + PersonPropertyId.class }) + public void testExcludePersonProperty() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + ReportPeriod reportPeriod = ReportPeriod.DAILY; + + // show the default is non-exclusion + PersonPropertyReportPluginData personPropertyReportPluginData = // + PersonPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .build(); + assertTrue(personPropertyReportPluginData.getExcludedProperties().isEmpty()); + + // show that exclusion alone works + Set expectedPersonPropertyIds = new LinkedHashSet<>(); + + expectedPersonPropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK); + expectedPersonPropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_4_BOOLEAN_MUTABLE_TRACK); + expectedPersonPropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK); + + PersonPropertyReportPluginData.Builder builder = PersonPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel);// + + for (PersonPropertyId personPropertyId : expectedPersonPropertyIds) { + builder.excludePersonProperty(personPropertyId); + } + + personPropertyReportPluginData = builder.build(); + assertEquals(expectedPersonPropertyIds, personPropertyReportPluginData.getExcludedProperties()); + + // show that exclusion will override inclusion + expectedPersonPropertyIds.clear(); + + expectedPersonPropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK); + expectedPersonPropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_4_BOOLEAN_MUTABLE_TRACK); + expectedPersonPropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK); + + builder = PersonPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel);// + + for (PersonPropertyId personPropertyId : expectedPersonPropertyIds) { + builder.includePersonProperty(personPropertyId); + builder.excludePersonProperty(personPropertyId); + } + + personPropertyReportPluginData = builder.build(); + assertEquals(expectedPersonPropertyIds, personPropertyReportPluginData.getExcludedProperties()); + + // precondition: if the person property id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + PersonPropertyReportPluginData.builder().excludePersonProperty(null); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PersonPropertyReportPluginData.class, name = "getReportLabel", args = {}) + public void testGetReportLabel() { + for (int i = 0; i < 30; i++) { + ReportLabel expectedReportLabel = new SimpleReportLabel(i); + PersonPropertyReportPluginData personPropertyReportPluginData = // + PersonPropertyReportPluginData.builder()// + .setReportPeriod(ReportPeriod.DAILY)// + .setReportLabel(expectedReportLabel)// + .build(); + + assertEquals(expectedReportLabel, personPropertyReportPluginData.getReportLabel()); + } + + } + + @Test + @UnitTestMethod(target = PersonPropertyReportPluginData.class, name = "getReportPeriod", args = {}) + public void testGetReportPeriod() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + for (ReportPeriod reportPeriod : ReportPeriod.values()) { + + PersonPropertyReportPluginData personPropertyReportPluginData = // + PersonPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .build(); + + assertEquals(reportPeriod, personPropertyReportPluginData.getReportPeriod()); + } + } + + @Test + @UnitTestMethod(target = PersonPropertyReportPluginData.class, name = "getIncludedProperties", args = {}) + public void testGetIncludedProperties() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + ReportPeriod reportPeriod = ReportPeriod.DAILY; + + // show the default is non-inclusion + PersonPropertyReportPluginData personPropertyReportPluginData = // + PersonPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .build(); + assertTrue(personPropertyReportPluginData.getIncludedProperties().isEmpty()); + + // show that inclusion alone works + Set expectedPersonPropertyIds = new LinkedHashSet<>(); + + expectedPersonPropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK); + expectedPersonPropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_4_BOOLEAN_MUTABLE_TRACK); + expectedPersonPropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK); + + PersonPropertyReportPluginData.Builder builder = PersonPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel);// + + for (PersonPropertyId personPropertyId : expectedPersonPropertyIds) { + builder.includePersonProperty(personPropertyId); + } + + personPropertyReportPluginData = builder.build(); + assertEquals(expectedPersonPropertyIds, personPropertyReportPluginData.getIncludedProperties()); + + // show that inclusion will override exclusion + expectedPersonPropertyIds.clear(); + + expectedPersonPropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK); + expectedPersonPropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_4_BOOLEAN_MUTABLE_TRACK); + expectedPersonPropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK); + + builder = PersonPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel);// + + for (PersonPropertyId personPropertyId : expectedPersonPropertyIds) { + builder.excludePersonProperty(personPropertyId); + builder.includePersonProperty(personPropertyId); + } + + personPropertyReportPluginData = builder.build(); + assertEquals(expectedPersonPropertyIds, personPropertyReportPluginData.getIncludedProperties()); + + } + + @Test + @UnitTestMethod(target = PersonPropertyReportPluginData.class, name = "getExcludedProperties", args = {}) + public void testGetExcludedProperties() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + ReportPeriod reportPeriod = ReportPeriod.DAILY; + + // show the default is non-exclusion + PersonPropertyReportPluginData personPropertyReportPluginData = // + PersonPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .build(); + assertTrue(personPropertyReportPluginData.getExcludedProperties().isEmpty()); + + // show that exclusion alone works + Set expectedPersonPropertyIds = new LinkedHashSet<>(); + + expectedPersonPropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK); + expectedPersonPropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_4_BOOLEAN_MUTABLE_TRACK); + expectedPersonPropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK); + + PersonPropertyReportPluginData.Builder builder = PersonPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel);// + + for (PersonPropertyId personPropertyId : expectedPersonPropertyIds) { + builder.excludePersonProperty(personPropertyId); + } + + personPropertyReportPluginData = builder.build(); + assertEquals(expectedPersonPropertyIds, personPropertyReportPluginData.getExcludedProperties()); + + // show that exclusion will override inclusion + expectedPersonPropertyIds.clear(); + + expectedPersonPropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK); + expectedPersonPropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_4_BOOLEAN_MUTABLE_TRACK); + expectedPersonPropertyIds.add(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK); + + builder = PersonPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel);// + + for (PersonPropertyId personPropertyId : expectedPersonPropertyIds) { + builder.includePersonProperty(personPropertyId); + builder.excludePersonProperty(personPropertyId); + } + + personPropertyReportPluginData = builder.build(); + assertEquals(expectedPersonPropertyIds, personPropertyReportPluginData.getExcludedProperties()); + + } + + @Test + @UnitTestMethod(target = PersonPropertyReportPluginData.class, name = "getDefaultInclusionPolicy", args = {}) + public void testGetDefaultInclusionPolicy() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + ReportPeriod reportPeriod = ReportPeriod.DAILY; + + // show the default value is true + PersonPropertyReportPluginData personPropertyReportPluginData = // + PersonPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .build(); + assertEquals(true, personPropertyReportPluginData.getDefaultInclusionPolicy()); + + personPropertyReportPluginData = // + PersonPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .setDefaultInclusion(true)// + .build(); + assertEquals(true, personPropertyReportPluginData.getDefaultInclusionPolicy()); + + personPropertyReportPluginData = // + PersonPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .setDefaultInclusion(false)// + .build(); + assertEquals(false, personPropertyReportPluginData.getDefaultInclusionPolicy()); + } + + @Test + @UnitTestMethod(target = PersonPropertyReportPluginData.class, name = "getCloneBuilder", args = {}) + public void testGetCloneBuilder() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7759639255438669162L); + for (int i = 0; i < 10; i++) { + + // build a PersonPropertyReportPluginData from random inputs + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt()); + ReportPeriod reportPeriod = ReportPeriod.values()[randomGenerator.nextInt(ReportPeriod.values().length)]; + + PersonPropertyReportPluginData.Builder builder = // + PersonPropertyReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel); + + for (int j = 0; j < 10; j++) { + TestPersonPropertyId testPersonPropertyId = TestPersonPropertyId + .getRandomPersonPropertyId(randomGenerator); + if (randomGenerator.nextBoolean()) { + builder.includePersonProperty(testPersonPropertyId); + } else { + builder.excludePersonProperty(testPersonPropertyId); + } + } + + builder.setDefaultInclusion(randomGenerator.nextBoolean()).build(); + + PersonPropertyReportPluginData personPropertyReportPluginData = builder.build(); + + // create the clone builder and have it build + PersonPropertyReportPluginData clonePersonPropertyReportPluginData = personPropertyReportPluginData + .getCloneBuilder().build(); + + // the result should equal the original if the clone builder was + // initialized with the correct state + assertEquals(personPropertyReportPluginData, clonePersonPropertyReportPluginData); + + } + } + + @Test + @UnitTestMethod(target = PersonPropertyReportPluginData.class, name = "equals", args = { Object.class }) + public void testEquals() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7759639255438669162L); + for (int i = 0; i < 10; i++) { + // build a PersonPropertyReportPluginData from the same random + // inputs + PersonPropertyReportPluginData.Builder builder1 = PersonPropertyReportPluginData.builder(); + PersonPropertyReportPluginData.Builder builder2 = PersonPropertyReportPluginData.builder(); + + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt(100)); + builder1.setReportLabel(reportLabel); + builder2.setReportLabel(reportLabel); + + ReportPeriod reportPeriod = ReportPeriod.values()[randomGenerator.nextInt(ReportPeriod.values().length)]; + builder1.setReportPeriod(reportPeriod); + builder2.setReportPeriod(reportPeriod); + + for (int j = 0; j < 10; j++) { + TestPersonPropertyId testPersonPropertyId = TestPersonPropertyId + .getRandomPersonPropertyId(randomGenerator); + if (randomGenerator.nextBoolean()) { + builder1.includePersonProperty(testPersonPropertyId); + builder2.includePersonProperty(testPersonPropertyId); + } else { + builder1.excludePersonProperty(testPersonPropertyId); + builder2.excludePersonProperty(testPersonPropertyId); + } + } + + boolean defaultInclusion = randomGenerator.nextBoolean(); + builder1.setDefaultInclusion(defaultInclusion).build(); + builder2.setDefaultInclusion(defaultInclusion).build(); + + PersonPropertyReportPluginData personPropertyReportPluginData1 = builder1.build(); + PersonPropertyReportPluginData personPropertyReportPluginData2 = builder2.build(); + + assertEquals(personPropertyReportPluginData1, personPropertyReportPluginData2); + + // show that plugin datas with different inputs are not equal + + // change the default inclusion + personPropertyReportPluginData2 = // + personPropertyReportPluginData1.getCloneBuilder()// + .setDefaultInclusion(!defaultInclusion)// + .build(); + assertNotEquals(personPropertyReportPluginData2, personPropertyReportPluginData1); + + // change the report period + int ord = reportPeriod.ordinal() + 1; + ord = ord % ReportPeriod.values().length; + reportPeriod = ReportPeriod.values()[ord]; + personPropertyReportPluginData2 = // + personPropertyReportPluginData1.getCloneBuilder()// + .setReportPeriod(reportPeriod)// + .build(); + assertNotEquals(personPropertyReportPluginData2, personPropertyReportPluginData1); + + // change the report label + reportLabel = new SimpleReportLabel(1000); + personPropertyReportPluginData2 = // + personPropertyReportPluginData1.getCloneBuilder()// + .setReportLabel(reportLabel)// + .build(); + assertNotEquals(personPropertyReportPluginData2, personPropertyReportPluginData1); + + // change an included property id + if (!personPropertyReportPluginData1.getIncludedProperties().isEmpty()) { + PersonPropertyId personPropertyId = personPropertyReportPluginData1.getIncludedProperties().iterator() + .next(); + personPropertyReportPluginData2 = // + personPropertyReportPluginData1.getCloneBuilder()// + .excludePersonProperty(personPropertyId)// + .build(); + assertNotEquals(personPropertyReportPluginData2, personPropertyReportPluginData1); + } + // change an excluded property id + if (!personPropertyReportPluginData1.getExcludedProperties().isEmpty()) { + PersonPropertyId personPropertyId = personPropertyReportPluginData1.getExcludedProperties().iterator() + .next(); + personPropertyReportPluginData2 = // + personPropertyReportPluginData1.getCloneBuilder()// + .includePersonProperty(personPropertyId)// + .build(); + assertNotEquals(personPropertyReportPluginData2, personPropertyReportPluginData1); + } + + } + + } + + @Test + @UnitTestMethod(target = PersonPropertyReportPluginData.class, name = "hashCode", args = {}) + public void testHashCode() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(9079768427072825406L); + + Set observedHashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 50; i++) { + // build a PersonPropertyReportPluginData from the same random + // inputs + PersonPropertyReportPluginData.Builder builder1 = PersonPropertyReportPluginData.builder(); + PersonPropertyReportPluginData.Builder builder2 = PersonPropertyReportPluginData.builder(); + + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt(100)); + builder1.setReportLabel(reportLabel); + builder2.setReportLabel(reportLabel); + + ReportPeriod reportPeriod = ReportPeriod.values()[randomGenerator.nextInt(ReportPeriod.values().length)]; + builder1.setReportPeriod(reportPeriod); + builder2.setReportPeriod(reportPeriod); + + for (int j = 0; j < 10; j++) { + TestPersonPropertyId testPersonPropertyId = TestPersonPropertyId + .getRandomPersonPropertyId(randomGenerator); + if (randomGenerator.nextBoolean()) { + builder1.includePersonProperty(testPersonPropertyId); + builder2.includePersonProperty(testPersonPropertyId); + } else { + builder1.excludePersonProperty(testPersonPropertyId); + builder2.excludePersonProperty(testPersonPropertyId); + } + } + + boolean defaultInclusion = randomGenerator.nextBoolean(); + builder1.setDefaultInclusion(defaultInclusion).build(); + builder2.setDefaultInclusion(defaultInclusion).build(); + + PersonPropertyReportPluginData personPropertyReportPluginData1 = builder1.build(); + PersonPropertyReportPluginData personPropertyReportPluginData2 = builder2.build(); + + // show that the hash code is stable + int hashCode = personPropertyReportPluginData1.hashCode(); + assertEquals(hashCode, personPropertyReportPluginData1.hashCode()); + assertEquals(hashCode, personPropertyReportPluginData1.hashCode()); + assertEquals(hashCode, personPropertyReportPluginData1.hashCode()); + assertEquals(hashCode, personPropertyReportPluginData1.hashCode()); + + // show that equal objects have equal hash codes + assertEquals(personPropertyReportPluginData1.hashCode(), personPropertyReportPluginData2.hashCode()); + + // collect the hashcode + observedHashCodes.add(personPropertyReportPluginData1.hashCode()); + } + + /* + * The hash codes should be dispersed -- we only show that they are unique + * values -- this is dependent on the random seed + */ + assertEquals(50, observedHashCodes.size()); + + } + + @Test + @UnitTestMethod(target = PersonPropertyReportPluginData.class, name = "toString", args = {}) + public void testToString() { + PersonPropertyReportPluginData personPropertyReportPluginData = PersonPropertyReportPluginData.builder()// + .setDefaultInclusion(true)// + .setReportLabel(new SimpleReportLabel("report label"))// + .setReportPeriod(ReportPeriod.DAILY)// + .includePersonProperty(TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK)// + .includePersonProperty(TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK)// + .excludePersonProperty(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK)// + .excludePersonProperty(TestPersonPropertyId.PERSON_PROPERTY_4_BOOLEAN_MUTABLE_TRACK).build();// + + String actualValue = personPropertyReportPluginData.toString(); + + String expectedValue = "PersonPropertyReportPluginData [" + + "data=Data [" + + "reportLabel=SimpleReportLabel [value=report label], " + + "reportPeriod=DAILY, " + + "includedProperties=[PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK], " + + "excludedProperties=[PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, PERSON_PROPERTY_4_BOOLEAN_MUTABLE_TRACK], " + + "defaultInclusionPolicy=true]]"; + + assertEquals(expectedValue, actualValue); + + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/AT_PersonPropertyDefinitionInitialization.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/AT_PersonPropertyDefinitionInitialization.java new file mode 100644 index 000000000..966c0e142 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/AT_PersonPropertyDefinitionInitialization.java @@ -0,0 +1,265 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.math3.random.RandomGenerator; +import org.apache.commons.math3.util.Pair; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.testsupport.TestPersonPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_PersonPropertyDefinitionInitialization { + + @Test + @UnitTestMethod(target = PersonPropertyDefinitionInitialization.class, name = "builder", args = {}) + public void testBuilder() { + PersonPropertyDefinitionInitialization.Builder builder = PersonPropertyDefinitionInitialization.builder(); + + assertNotNull(builder); + } + + @Test + @UnitTestMethod(target = PersonPropertyDefinitionInitialization.class, name = "getPropertyDefinition", args = {}) + public void testGetPropertyDefinition() { + PersonPropertyDefinitionInitialization.Builder builder = PersonPropertyDefinitionInitialization.builder(); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(100) + .setPropertyValueMutability(true).build(); + + builder.setPropertyDefinition(propertyDefinition); + builder.setPersonPropertyId(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK); + + PersonPropertyDefinitionInitialization propertyDefinitionInitialization = builder.build(); + + assertNotNull(propertyDefinitionInitialization); + assertEquals(propertyDefinition, propertyDefinitionInitialization.getPropertyDefinition()); + } + + @Test + @UnitTestMethod(target = PersonPropertyDefinitionInitialization.class, name = "getPropertyValues", args = {}) + public void testGetPropertyValues() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2754843240208076356L); + + PersonPropertyDefinitionInitialization.Builder builder = PersonPropertyDefinitionInitialization.builder(); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(100) + .setPropertyValueMutability(true).build(); + + builder.setPropertyDefinition(propertyDefinition); + builder.setPersonPropertyId(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK); + + List> expectedValues = new ArrayList<>(); + + for (int i = 0; i < 20; i++) { + int value = randomGenerator.nextInt(100); + PersonId personId = new PersonId(i * 2); + builder.addPropertyValue(personId, value); + expectedValues.add(new Pair<>(personId, value)); + } + + PersonPropertyDefinitionInitialization propertyDefinitionInitialization = builder.build(); + + assertNotNull(propertyDefinitionInitialization); + + List> actualValues = propertyDefinitionInitialization.getPropertyValues(); + assertNotNull(actualValues); + assertFalse(actualValues.isEmpty()); + assertEquals(expectedValues, actualValues); + } + + @Test + @UnitTestMethod(target = PersonPropertyDefinitionInitialization.class, name = "getPersonPropertyId", args = {}) + public void testGetPersonPropertyId() { + PersonPropertyDefinitionInitialization.Builder builder = PersonPropertyDefinitionInitialization.builder(); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(100) + .setPropertyValueMutability(true).build(); + PersonPropertyId personPropertyId = TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK; + builder.setPropertyDefinition(propertyDefinition); + builder.setPersonPropertyId(personPropertyId); + PersonPropertyDefinitionInitialization propertyDefinitionInitialization = builder.build(); + + assertNotNull(propertyDefinitionInitialization); + assertEquals(personPropertyId, propertyDefinitionInitialization.getPersonPropertyId()); + } + + @Test + @UnitTestMethod(target = PersonPropertyDefinitionInitialization.Builder.class, name = "build", args = {}) + public void testBuild() { + PersonPropertyDefinitionInitialization.Builder builder = PersonPropertyDefinitionInitialization.builder(); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(100) + .setPropertyValueMutability(true).build(); + PersonPropertyId personPropertyId = TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK; + builder.setPropertyDefinition(propertyDefinition); + builder.setPersonPropertyId(personPropertyId); + PersonPropertyDefinitionInitialization propertyDefinitionInitialization = builder.build(); + + assertNotNull(propertyDefinitionInitialization); + + // precondition: null propertyDefinition + ContractException contractException = assertThrows(ContractException.class, () -> { + PersonPropertyDefinitionInitialization.builder().setPersonPropertyId(personPropertyId).build(); + }); + assertEquals(PropertyError.NULL_PROPERTY_DEFINITION, contractException.getErrorType()); + + // precondition: person property id is null + contractException = assertThrows(ContractException.class, () -> { + PersonPropertyDefinitionInitialization.builder().setPropertyDefinition(propertyDefinition).build(); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // precondition: incomaptible value + contractException = assertThrows(ContractException.class, () -> { + PersonPropertyDefinitionInitialization.builder().setPropertyDefinition(propertyDefinition) + .setPersonPropertyId(personPropertyId).addPropertyValue(new PersonId(1000), "100").build(); + }); + assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PersonPropertyDefinitionInitialization.Builder.class, name = "setPropertyDefinition", args = { + PropertyDefinition.class }) + public void testSetPropertyDefinition() { + PersonPropertyDefinitionInitialization.Builder builder = PersonPropertyDefinitionInitialization.builder(); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Double.class) + .setDefaultValue(100.0).setPropertyValueMutability(true).build(); + PersonPropertyId personPropertyId = TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK; + builder.setPropertyDefinition(propertyDefinition); + builder.setPersonPropertyId(personPropertyId); + PersonPropertyDefinitionInitialization propertyDefinitionInitialization = builder.build(); + + assertNotNull(propertyDefinitionInitialization); + assertEquals(personPropertyId, propertyDefinitionInitialization.getPersonPropertyId()); + + // precondition: property definition is null + ContractException contractException = assertThrows(ContractException.class, () -> { + PersonPropertyDefinitionInitialization.builder().setPropertyDefinition(null); + }); + assertEquals(PropertyError.NULL_PROPERTY_DEFINITION, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PersonPropertyDefinitionInitialization.Builder.class, name = "addPropertyValue", args = { + PersonId.class, Object.class }) + public void testAddPropertyValue() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2816191091329528844L); + + PersonPropertyDefinitionInitialization.Builder builder = PersonPropertyDefinitionInitialization.builder(); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Double.class) + .setDefaultValue(100.0).setPropertyValueMutability(true).build(); + + builder.setPropertyDefinition(propertyDefinition); + builder.setPersonPropertyId(TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK); + + List> expectedValues = new ArrayList<>(); + for (int i = 0; i < 20; i++) { + double value = randomGenerator.nextDouble() * 100; + PersonId personId = new PersonId(i * 2); + builder.addPropertyValue(personId, value); + expectedValues.add(new Pair<>(personId, value)); + } + + PersonPropertyDefinitionInitialization propertyDefinitionInitialization = builder.build(); + List> actualValues = propertyDefinitionInitialization.getPropertyValues(); + assertNotNull(actualValues); + assertFalse(actualValues.isEmpty()); + assertEquals(expectedValues, actualValues); + + // precondition: null person id + ContractException contractException = assertThrows(ContractException.class, () -> { + PersonPropertyDefinitionInitialization.builder().addPropertyValue(null, 100.0); + }); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + // precondition: value is null + contractException = assertThrows(ContractException.class, () -> { + PersonPropertyDefinitionInitialization.builder().addPropertyValue(new PersonId(1000), null); + }); + assertEquals(PropertyError.NULL_PROPERTY_VALUE, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PersonPropertyDefinitionInitialization.Builder.class, name = "setPersonPropertyId", args = { + PersonPropertyId.class }) + public void testSetPersonPropertyId() { + PersonPropertyDefinitionInitialization.Builder builder = PersonPropertyDefinitionInitialization.builder(); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Double.class) + .setDefaultValue(100.0).setPropertyValueMutability(true).build(); + PersonPropertyId personPropertyId = TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK; + builder.setPropertyDefinition(propertyDefinition); + builder.setPersonPropertyId(personPropertyId); + PersonPropertyDefinitionInitialization propertyDefinitionInitialization = builder.build(); + + assertNotNull(propertyDefinitionInitialization); + assertEquals(personPropertyId, propertyDefinitionInitialization.getPersonPropertyId()); + + // precondition: property id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + PersonPropertyDefinitionInitialization.builder().setPersonPropertyId(null); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PersonPropertyDefinitionInitialization.class, name = "trackTimes", args = {}) + public void testTrackTimes() { + + for (boolean trackTimes : new boolean[] { true, false }) { + PropertyDefinition propertyDefinition = PropertyDefinition.builder()// + .setType(Double.class)// + .setDefaultValue(100.0)// + .setPropertyValueMutability(true)// + .build();// + PersonPropertyId personPropertyId = TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK; + + PersonPropertyDefinitionInitialization.Builder builder = PersonPropertyDefinitionInitialization.builder(); + builder.setPropertyDefinition(propertyDefinition); + builder.setPersonPropertyId(personPropertyId); + builder.setTrackTimes(trackTimes); + PersonPropertyDefinitionInitialization propertyDefinitionInitialization = builder.build(); + + assertNotNull(propertyDefinitionInitialization); + assertEquals(trackTimes, propertyDefinitionInitialization.trackTimes()); + + } + + + } + + @Test + @UnitTestMethod(target = PersonPropertyDefinitionInitialization.Builder.class, name = "setTrackTimes", args = { + boolean.class }) + public void testSetTrackTimes() { + + for (boolean trackTimes : new boolean[] { true, false }) { + PropertyDefinition propertyDefinition = PropertyDefinition.builder()// + .setType(Double.class)// + .setDefaultValue(100.0)// + .setPropertyValueMutability(true)// + .build();// + PersonPropertyId personPropertyId = TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK; + + PersonPropertyDefinitionInitialization.Builder builder = PersonPropertyDefinitionInitialization.builder(); + builder.setPropertyDefinition(propertyDefinition); + builder.setPersonPropertyId(personPropertyId); + builder.setTrackTimes(trackTimes); + PersonPropertyDefinitionInitialization propertyDefinitionInitialization = builder.build(); + + assertNotNull(propertyDefinitionInitialization); + assertEquals(trackTimes, propertyDefinitionInitialization.trackTimes()); + + } + + + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/AT_PersonPropertyDimension.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/AT_PersonPropertyDimension.java new file mode 100644 index 000000000..49c457651 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/AT_PersonPropertyDimension.java @@ -0,0 +1,357 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.DimensionContext; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.testsupport.TestPersonPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_PersonPropertyDimension { + + @Test + @UnitTestMethod(target = PersonPropertyDimension.Builder.class, name = "addValue", args = { Object.class }) + public void testAddValue() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3468803942988565031L); + + for (int i = 0; i < 50; i++) { + List expectedValues = new ArrayList<>(); + + PersonPropertyDimension.Builder builder = PersonPropertyDimension.builder()// + .setPersonPropertyId(TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK); + int n = randomGenerator.nextInt(10); + for (int j = 0; j < n; j++) { + double value = randomGenerator.nextDouble(); + expectedValues.add(value); + builder.addValue(value); + } + PersonPropertyDimension personPropertyDimension = builder.build(); + + List actualValues = personPropertyDimension.getValues(); + assertEquals(expectedValues, actualValues); + } + + // precondition test : if the value is null + ContractException contractException = assertThrows(ContractException.class, + () -> PersonPropertyDimension.builder().addValue(null)); + assertEquals(PropertyError.NULL_PROPERTY_VALUE, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PersonPropertyDimension.Builder.class, name = "build", args = {}) + public void testBuild() { + PersonPropertyDimension personPropertyDimension = // + PersonPropertyDimension.builder()// + .setPersonPropertyId(TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK).build(); + assertNotNull(personPropertyDimension); + + // precondition test : if the global property id is not assigned + ContractException contractException = assertThrows(ContractException.class, + () -> PersonPropertyDimension.builder().build()); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PersonPropertyDimension.Builder.class, name = "setTrackTimes", args = { boolean.class }) + public void testSetTrackTimes() { + for (int i = 0; i < 10; i++) { + boolean trackTimes = i % 2 == 0; + PersonPropertyDimension.Builder builder = PersonPropertyDimension.builder()// + .setPersonPropertyId(TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK) + .setTrackTimes(trackTimes); + + PersonPropertyDimension personPropertyDimension = builder.build(); + assertEquals(trackTimes, personPropertyDimension.getTrackTimes()); + } + } + + @Test + @UnitTestMethod(target = PersonPropertyDimension.Builder.class, name = "setPersonPropertyId", args = { + PersonPropertyId.class }) + public void testSetPersonPropertyId() { + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + + PersonPropertyDimension.Builder builder = PersonPropertyDimension.builder()// + .setPersonPropertyId(testPersonPropertyId); + + PersonPropertyDimension personPropertyDimension = builder.build(); + assertEquals(testPersonPropertyId, personPropertyDimension.getPersonPropertyId()); + } + + // precondition test : if the value is null + ContractException contractException = assertThrows(ContractException.class, + () -> PersonPropertyDimension.builder().setPersonPropertyId(null)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PersonPropertyDimension.class, name = "builder", args = {}) + public void testBuilder() { + assertNotNull(PersonPropertyDimension.builder()); + } + + @Test + @UnitTestMethod(target = PersonPropertyDimension.class, name = "executeLevel", args = { DimensionContext.class, + int.class }) + public void testExecuteLevel() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2924521933883974690L); + + // run several test cases + for (int i = 0; i < 30; i++) { + + // select a random number of levels + int levelCount = randomGenerator.nextInt(10); + // select a random property id + TestPersonPropertyId targetPropertyId = TestPersonPropertyId.getRandomPersonPropertyId(randomGenerator); + + // generate random values for the level + List expectedValues = new ArrayList<>(); + for (int j = 0; j < levelCount; j++) { + expectedValues.add(targetPropertyId.getRandomPropertyValue(randomGenerator)); + } + + // create a PersonPropertyDimension with the level values + PersonPropertyDimension.Builder dimBuilder = PersonPropertyDimension.builder()// + .setPersonPropertyId(targetPropertyId); + + for (Object value : expectedValues) { + dimBuilder.addValue(value); + } + + PersonPropertyDimension personPropertyDimension = dimBuilder.build(); + + // show that for each level the dimension properly assigns the value + // to a global property data builder + for (int level = 0; level < levelCount; level++) { + /* + * Create a PersonPropertiesPluginData, filling it with the test property + * definitions and any values that are required + */ + PersonPropertiesPluginData.Builder pluginDataBuilder = PersonPropertiesPluginData.builder(); + for (TestPersonPropertyId propertyId : TestPersonPropertyId.values()) { + PropertyDefinition propertyDefinition = propertyId.getPropertyDefinition(); + pluginDataBuilder.definePersonProperty(propertyId, propertyDefinition, 0, false); + } + + // Create a dimension context that contain the plugin data + // builder + DimensionContext.Builder dimensionContextBuilder = DimensionContext.builder(); + + pluginDataBuilder = (PersonPropertiesPluginData.Builder) dimensionContextBuilder + .add(pluginDataBuilder.build()); + DimensionContext dimensionContext = dimensionContextBuilder.build(); + + // execute the dimension with the level + personPropertyDimension.executeLevel(dimensionContext, level); + + /* + * get the PersonPropertiesPluginData from the corresponding builder + */ + PersonPropertiesPluginData personsPluginData = pluginDataBuilder.build(); + + /* + * show that the PersonPropertiesPluginData has the value we expect for the + * given level + */ + + PropertyDefinition actualDefinition = personsPluginData.getPersonPropertyDefinition(targetPropertyId); + + assertTrue(actualDefinition.getDefaultValue().isPresent()); + Object actualValue = actualDefinition.getDefaultValue().get(); + Object expectedValue = expectedValues.get(level); + assertEquals(expectedValue, actualValue); + } + } + } + + @Test + @UnitTestMethod(target = PersonPropertyDimension.class, name = "getExperimentMetaData", args = {}) + public void testGetExperimentMetaData() { + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + + List expectedExperimentMetaData = new ArrayList<>(); + expectedExperimentMetaData.add(testPersonPropertyId.toString()); + + PersonPropertyDimension.Builder builder = PersonPropertyDimension.builder()// + .setPersonPropertyId(testPersonPropertyId); + + PersonPropertyDimension personPropertyDimension = builder.build(); + assertEquals(expectedExperimentMetaData, personPropertyDimension.getExperimentMetaData()); + } + } + + @Test + @UnitTestMethod(target = PersonPropertyDimension.class, name = "getTrackTimes", args = {}) + public void testGetTrackTimes() { + for (int i = 0; i < 10; i++) { + boolean trackTimes = i % 2 == 0; + PersonPropertyDimension.Builder builder = PersonPropertyDimension.builder()// + .setPersonPropertyId(TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK) + .setTrackTimes(trackTimes); + + PersonPropertyDimension personPropertyDimension = builder.build(); + assertEquals(trackTimes, personPropertyDimension.getTrackTimes()); + } + } + + @Test + @UnitTestMethod(target = PersonPropertyDimension.class, name = "getPersonPropertyId", args = {}) + public void testGetPersonPropertyId() { + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + + PersonPropertyDimension.Builder builder = PersonPropertyDimension.builder()// + .setPersonPropertyId(testPersonPropertyId); + + PersonPropertyDimension personPropertyDimension = builder.build(); + assertEquals(testPersonPropertyId, personPropertyDimension.getPersonPropertyId()); + } + } + + @Test + @UnitTestMethod(target = PersonPropertyDimension.class, name = "getValues", args = {}) + public void testGetValues() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4581428044056639458L); + + for (int i = 0; i < 50; i++) { + List expectedValues = new ArrayList<>(); + + PersonPropertyDimension.Builder builder = PersonPropertyDimension.builder()// + .setPersonPropertyId(TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK); + int n = randomGenerator.nextInt(10); + for (int j = 0; j < n; j++) { + double value = randomGenerator.nextDouble(); + expectedValues.add(value); + builder.addValue(value); + } + PersonPropertyDimension personPropertyDimension = builder.build(); + + List actualValues = personPropertyDimension.getValues(); + assertEquals(expectedValues, actualValues); + } + } + + @Test + @UnitTestMethod(target = PersonPropertyDimension.class, name = "levelCount", args = {}) + public void testLevelCount() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8913942504408118065L); + + for (int i = 0; i < 50; i++) { + + PersonPropertyDimension.Builder builder = PersonPropertyDimension.builder()// + .setPersonPropertyId(TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK); + int n = randomGenerator.nextInt(10); + for (int j = 0; j < n; j++) { + double value = randomGenerator.nextDouble(); + builder.addValue(value); + } + PersonPropertyDimension personPropertyDimension = builder.build(); + + assertEquals(n, personPropertyDimension.levelCount()); + } + } + + /* + * Generates a random PersonPropertyDimension with 2790 possible outcomes + */ + private PersonPropertyDimension getRandomPersonPropertyDimension(long seed) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + + PersonPropertyDimension.Builder builder = PersonPropertyDimension.builder(); + builder.setPersonPropertyId(TestPersonPropertyId.getRandomPersonPropertyId(randomGenerator)); + builder.setTrackTimes(randomGenerator.nextBoolean()); + int count = randomGenerator.nextInt(3) + 1; + for (int i = 0; i < count; i++) { + builder.addValue(randomGenerator.nextInt(5)); + } + return builder.build(); + } + + @Test + @UnitTestMethod(target = PersonPropertyDimension.class, name = "equals", args = { Object.class }) + public void testEquals() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5592194423075711575L); + + // is never equal to null; + for (int i = 0; i < 30; i++) { + PersonPropertyDimension dimension = getRandomPersonPropertyDimension(randomGenerator.nextLong()); + assertFalse(dimension.equals(null)); + } + + // reflexive + for (int i = 0; i < 30; i++) { + PersonPropertyDimension dimension = getRandomPersonPropertyDimension(randomGenerator.nextLong()); + assertTrue(dimension.equals(dimension)); + } + + // symmetric, transitive, consistent + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + PersonPropertyDimension dimension1 = getRandomPersonPropertyDimension(seed); + PersonPropertyDimension dimension2 = getRandomPersonPropertyDimension(seed); + for (int j = 0; j < 5; j++) { + assertTrue(dimension1.equals(dimension2)); + assertTrue(dimension2.equals(dimension1)); + } + } + + // different inputs yield non-equal objects. There are 2790 possible generated + // values, so the collision probability is very low + Set personPropertyDimensions = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + + PersonPropertyDimension dimension = getRandomPersonPropertyDimension(randomGenerator.nextLong()); + personPropertyDimensions.add(dimension); + + } + + assertTrue(personPropertyDimensions.size() > 90); + + } + +// PersonPropertyDimension public int plugins.personproperties.support.PersonPropertyDimension.hashCode() + @Test + @UnitTestMethod(target = PersonPropertyDimension.class, name = "hashCode", args = {}) + public void testHashCode() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6008834354417928206L); + + // equal objects have equal hash codes + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + PersonPropertyDimension dimension1 = getRandomPersonPropertyDimension(seed); + PersonPropertyDimension dimension2 = getRandomPersonPropertyDimension(seed); + assertEquals(dimension1, dimension2); + assertEquals(dimension1.hashCode(), dimension2.hashCode()); + } + + // hash codes are reasonably distributed. There are 2790 possible generated + // values, so the collision probability is very low + Set hashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + + PersonPropertyDimension dimension = getRandomPersonPropertyDimension(randomGenerator.nextLong()); + hashCodes.add(dimension.hashCode()); + + } + + assertTrue(hashCodes.size() > 90); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/AT_PersonPropertyError.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/AT_PersonPropertyError.java new file mode 100644 index 000000000..7add226d3 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/AT_PersonPropertyError.java @@ -0,0 +1,28 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.support; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestMethod; + +public class AT_PersonPropertyError { + + @Test + @UnitTestMethod(target = PersonPropertyError.class, name = "getDescription", args = {}) + public void test() { + // show that each description is a unique, non-null and non-empty string + Set descriptions = new LinkedHashSet<>(); + for (PersonPropertyError personPropertyError : PersonPropertyError.values()) { + String description = personPropertyError.getDescription(); + assertNotNull(description, "null description for " + personPropertyError); + assertTrue(description.length() > 0, "empty string for " + personPropertyError); + boolean unique = descriptions.add(description); + assertTrue(unique, "description for " + personPropertyError + " is not unique"); + } + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/AT_PersonPropertyFilter.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/AT_PersonPropertyFilter.java new file mode 100644 index 000000000..8cfbdc203 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/AT_PersonPropertyFilter.java @@ -0,0 +1,255 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.Equality; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.FilterSensitivity; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters.Filter; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.TestPartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.events.PersonPropertyUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.testsupport.PersonPropertiesTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.testsupport.PersonPropertiesTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.testsupport.TestPersonPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_PersonPropertyFilter { + + @Test + @UnitTestConstructor(target = PersonPropertyFilter.class, args = { PersonPropertyId.class, Equality.class, + Object.class }) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonPropertyFilter.class, name = "validate", args = { PartitionsContext.class }) + public void testValidate() { + + Factory factory = PersonPropertiesTestPluginFactory.factory(100, 7889475921077680704L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + final Filter filter = new PersonPropertyFilter( + TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, Equality.EQUAL, 12); + assertNotNull(filter); + + ContractException contractException = assertThrows(ContractException.class, + () -> new PersonPropertyFilter(TestPersonPropertyId.getUnknownPersonPropertyId(), Equality.EQUAL, + 12).validate(testPartitionsContext)); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, + () -> new PersonPropertyFilter(null, Equality.EQUAL, 12L).validate(testPartitionsContext)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, + () -> new PersonPropertyFilter(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, + null, 12).validate(testPartitionsContext)); + assertEquals(PartitionError.NULL_EQUALITY_OPERATOR, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, + () -> new PersonPropertyFilter(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, + Equality.EQUAL, "bad value").validate(testPartitionsContext)); + assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = PersonPropertyFilter.class, name = "getFilterSensitivities", args = {}) + public void testGetFilterSensitivities() { + + Filter filter = new PersonPropertyFilter(TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, + Equality.EQUAL, 12); + + Set> filterSensitivities = filter.getFilterSensitivities(); + assertNotNull(filterSensitivities); + assertEquals(filterSensitivities.size(), 1); + + FilterSensitivity filterSensitivity = filterSensitivities.iterator().next(); + assertEquals(PersonPropertyUpdateEvent.class, filterSensitivity.getEventClass()); + } + + @Test + @UnitTestMethod(target = PersonPropertyFilter.class, name = "evaluate", args = { PartitionsContext.class, + PersonId.class }) + public void testEvaluate() { + + Factory factory = PersonPropertiesTestPluginFactory.factory(100, 9037413907425227057L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PersonPropertiesDataManager personPropertiesDataManager = c + .getDataManager(PersonPropertiesDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + TestPersonPropertyId testPersonPropertyId = TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK; + + Filter filter = new PersonPropertyFilter(testPersonPropertyId, Equality.GREATER_THAN, 12); + + for (PersonId personId : peopleDataManager.getPeople()) { + int value = randomGenerator.nextInt(10) + 7; + personPropertiesDataManager.setPersonPropertyValue(personId, testPersonPropertyId, value); + } + + for (PersonId personId : peopleDataManager.getPeople()) { + Integer value = personPropertiesDataManager.getPersonPropertyValue(personId, testPersonPropertyId); + boolean expected = value > 12; + boolean actual = filter.evaluate(testPartitionsContext, personId); + assertEquals(expected, actual); + } + + /* precondition: if the context is null */ + ContractException contractException = assertThrows(ContractException.class, + () -> filter.evaluate(null, new PersonId(0))); + assertEquals(NucleusError.NULL_SIMULATION_CONTEXT, contractException.getErrorType()); + + /* precondition: if the person id is null */ + contractException = assertThrows(ContractException.class, + () -> filter.evaluate(testPartitionsContext, null)); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + /* precondition: if the person id is unknown */ + contractException = assertThrows(ContractException.class, + () -> filter.evaluate(testPartitionsContext, new PersonId(123412342))); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = PersonPropertyFilter.class, name = "toString", args = {}) + public void testToString() { + Filter filter = new PersonPropertyFilter(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, + Equality.GREATER_THAN, 12); + String expectedString = "PropertyFilter [personPropertyId=PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, personPropertyValue=12, equality=GREATER_THAN]"; + + assertEquals(expectedString, filter.toString()); + + } + + @Test + @UnitTestMethod(target = PersonPropertyFilter.class, name = "getEquality", args = {}) + public void testGetEquality() { + for (Equality equality : Equality.values()) { + PersonPropertyFilter personPropertyFilter = new PersonPropertyFilter( + TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, equality, 12); + assertEquals(equality, personPropertyFilter.getEquality()); + } + } + + @Test + @UnitTestMethod(target = PersonPropertyFilter.class, name = "getPersonPropertyId", args = {}) + public void testGetPersonPropertyId() { + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + PersonPropertyFilter personPropertyFilter = new PersonPropertyFilter(testPersonPropertyId, Equality.EQUAL, + 12); + assertEquals(testPersonPropertyId, personPropertyFilter.getPersonPropertyId()); + } + } + + @Test + @UnitTestMethod(target = PersonPropertyFilter.class, name = "getPersonPropertyValue", args = {}) + public void testGetPersonPropertyValue() { + for (int i = 0; i < 10; i++) { + PersonPropertyFilter personPropertyFilter = new PersonPropertyFilter( + TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, Equality.EQUAL, i); + assertEquals(i, personPropertyFilter.getPersonPropertyValue()); + } + } + + private PersonPropertyFilter getRandomPersonPropertyFilter(long seed) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + TestPersonPropertyId testPersonPropertyId = TestPersonPropertyId.getRandomPersonPropertyId(randomGenerator); + Object propertyValue = testPersonPropertyId.getRandomPropertyValue(randomGenerator); + Equality equality = Equality.getRandomEquality(randomGenerator); + + return new PersonPropertyFilter(testPersonPropertyId, equality, propertyValue); + + } + + @Test + @UnitTestMethod(target = PersonPropertyFilter.class, name = "equals", args = { Object.class }) + public void testEquals() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3804944746539493450L); + // never equals null + for (int i = 0; i < 30; i++) { + PersonPropertyFilter filter = getRandomPersonPropertyFilter(randomGenerator.nextLong()); + assertFalse(filter.equals(null)); + } + + // reflexive + for (int i = 0; i < 30; i++) { + PersonPropertyFilter filter = getRandomPersonPropertyFilter(randomGenerator.nextLong()); + assertTrue(filter.equals(filter)); + } + + // symmetric, transitive, consistent + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + PersonPropertyFilter filter1 = getRandomPersonPropertyFilter(seed); + PersonPropertyFilter filter2 = getRandomPersonPropertyFilter(seed); + for (int j = 0; j < 5; j++) { + assertTrue(filter1.equals(filter2)); + assertTrue(filter2.equals(filter1)); + } + } + + // non-equal inputs yield non-equal objects + Set personPropertyFilters = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + PersonPropertyFilter filter = getRandomPersonPropertyFilter(randomGenerator.nextLong()); + personPropertyFilters.add(filter); + } + + // we choose 80 since the probability of collision is high due to Boolean + // property values + assertTrue(personPropertyFilters.size() > 80); + + } + + @Test + @UnitTestMethod(target = PersonPropertyFilter.class, name = "hashCode", args = {}) + public void testHashCode() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4705334626551418173L); + + // equal objects have equal hash codes + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + PersonPropertyFilter filter1 = getRandomPersonPropertyFilter(seed); + PersonPropertyFilter filter2 = getRandomPersonPropertyFilter(seed); + assertEquals(filter1, filter2); + assertEquals(filter1.hashCode(), filter2.hashCode()); + } + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/AT_PersonPropertyInitialization.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/AT_PersonPropertyInitialization.java new file mode 100644 index 000000000..af15751c9 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/AT_PersonPropertyInitialization.java @@ -0,0 +1,156 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.personproperties.testsupport.TestPersonPropertyId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_PersonPropertyInitialization { + + @Test + @UnitTestConstructor(target = PersonPropertyValueInitialization.class, args = { PersonPropertyId.class, Object.class }) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonPropertyValueInitialization.class, name = "getPersonPropertyId", args = {}) + public void testGetPersonPropertyId() { + PersonPropertyId personPropertyId = TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK; + Double value = 2.7; + PersonPropertyValueInitialization personPropertyValueInitialization = new PersonPropertyValueInitialization(personPropertyId, value); + assertEquals(personPropertyId, personPropertyValueInitialization.getPersonPropertyId()); + } + + @Test + @UnitTestMethod(target = PersonPropertyValueInitialization.class, name = "getValue", args = {}) + public void testGetValue() { + PersonPropertyId personPropertyId = TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK; + Double value = 2.7; + PersonPropertyValueInitialization personPropertyValueInitialization = new PersonPropertyValueInitialization(personPropertyId, value); + assertEquals(value, personPropertyValueInitialization.getValue()); + } + + @Test + @UnitTestMethod(target = PersonPropertyValueInitialization.class, name = "equals", args = { Object.class }) + public void testEquals() { + PersonPropertyValueInitialization personProp1 = new PersonPropertyValueInitialization(TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK, 2.7); + PersonPropertyValueInitialization personProp2 = new PersonPropertyValueInitialization(TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, false); + PersonPropertyValueInitialization personProp3 = new PersonPropertyValueInitialization(TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, true); + PersonPropertyValueInitialization personProp4 = new PersonPropertyValueInitialization(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 20); + PersonPropertyValueInitialization personProp5 = new PersonPropertyValueInitialization(TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK, 2.7); + PersonPropertyValueInitialization personProp6 = new PersonPropertyValueInitialization(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 20); + + // reflexive + assertEquals(personProp1, personProp1); + assertEquals(personProp2, personProp2); + assertEquals(personProp3, personProp3); + assertEquals(personProp4, personProp4); + assertEquals(personProp5, personProp5); + assertEquals(personProp6, personProp6); + + // symmetric + assertEquals(personProp1, personProp5); + assertEquals(personProp5, personProp1); + assertEquals(personProp4, personProp6); + assertEquals(personProp6, personProp4); + + assertNotEquals(personProp1, personProp2); + assertNotEquals(personProp1, personProp3); + assertNotEquals(personProp1, personProp4); + assertNotEquals(personProp1, personProp6); + + assertNotEquals(personProp2, personProp1); + assertNotEquals(personProp2, personProp3); + assertNotEquals(personProp2, personProp4); + assertNotEquals(personProp2, personProp5); + assertNotEquals(personProp2, personProp6); + + assertNotEquals(personProp3, personProp1); + assertNotEquals(personProp3, personProp2); + assertNotEquals(personProp3, personProp4); + assertNotEquals(personProp3, personProp5); + assertNotEquals(personProp3, personProp6); + + assertNotEquals(personProp4, personProp1); + assertNotEquals(personProp4, personProp2); + assertNotEquals(personProp4, personProp3); + assertNotEquals(personProp4, personProp5); + + assertNotEquals(personProp5, personProp2); + assertNotEquals(personProp5, personProp3); + assertNotEquals(personProp5, personProp4); + assertNotEquals(personProp5, personProp6); + + assertNotEquals(personProp6, personProp1); + assertNotEquals(personProp6, personProp2); + assertNotEquals(personProp6, personProp3); + assertNotEquals(personProp6, personProp5); + + assertNotEquals(personProp1, null); + assertNotEquals(personProp2, null); + assertNotEquals(personProp3, null); + assertNotEquals(personProp4, null); + assertNotEquals(personProp5, null); + assertNotEquals(personProp6, null); + } + + @Test + @UnitTestMethod(target = PersonPropertyValueInitialization.class, name = "toString", args = {}) + public void testToString() { + PersonPropertyId personPropertyId = TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK; + Double value = 2.7; + PersonPropertyValueInitialization personPropertyValueInitialization = new PersonPropertyValueInitialization(personPropertyId, value); + String expectedString = "PersonPropertyAssignment [personPropertyId=PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK, value=2.7]"; + + assertEquals(expectedString, personPropertyValueInitialization.toString()); + } + + @Test + @UnitTestMethod(target = PersonPropertyValueInitialization.class, name = "hashCode", args = {}) + public void testHashCode() { + PersonPropertyValueInitialization personProp1 = new PersonPropertyValueInitialization(TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK, 2.7); + PersonPropertyValueInitialization personProp2 = new PersonPropertyValueInitialization(TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, false); + PersonPropertyValueInitialization personProp3 = new PersonPropertyValueInitialization(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 20); + PersonPropertyValueInitialization personProp4 = new PersonPropertyValueInitialization(TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK, 2.7); + PersonPropertyValueInitialization personProp5 = new PersonPropertyValueInitialization(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, 20); + + // reflexive + assertEquals(personProp1.hashCode(), personProp1.hashCode()); + assertEquals(personProp2.hashCode(), personProp2.hashCode()); + assertEquals(personProp3.hashCode(), personProp3.hashCode()); + assertEquals(personProp4.hashCode(), personProp4.hashCode()); + assertEquals(personProp5.hashCode(), personProp5.hashCode()); + + // symmetric + assertEquals(personProp1.hashCode(), personProp4.hashCode()); + assertEquals(personProp4.hashCode(), personProp1.hashCode()); + assertEquals(personProp3.hashCode(), personProp5.hashCode()); + assertEquals(personProp5.hashCode(), personProp3.hashCode()); + + assertNotEquals(personProp1.hashCode(), personProp2.hashCode()); + assertNotEquals(personProp1.hashCode(), personProp3.hashCode()); + assertNotEquals(personProp1.hashCode(), personProp5.hashCode()); + + assertNotEquals(personProp2.hashCode(), personProp1.hashCode()); + assertNotEquals(personProp2.hashCode(), personProp3.hashCode()); + assertNotEquals(personProp2.hashCode(), personProp4.hashCode()); + assertNotEquals(personProp2.hashCode(), personProp5.hashCode()); + + assertNotEquals(personProp3.hashCode(), personProp1.hashCode()); + assertNotEquals(personProp3.hashCode(), personProp2.hashCode()); + assertNotEquals(personProp3.hashCode(), personProp4.hashCode()); + + assertNotEquals(personProp4.hashCode(), personProp2.hashCode()); + assertNotEquals(personProp4.hashCode(), personProp3.hashCode()); + assertNotEquals(personProp4.hashCode(), personProp5.hashCode()); + + assertNotEquals(personProp5.hashCode(), personProp1.hashCode()); + assertNotEquals(personProp5.hashCode(), personProp2.hashCode()); + assertNotEquals(personProp5.hashCode(), personProp4.hashCode()); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/AT_PersonPropertyLabeler.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/AT_PersonPropertyLabeler.java new file mode 100644 index 000000000..82584e34b --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/support/AT_PersonPropertyLabeler.java @@ -0,0 +1,271 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.LabelerSensitivity; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.TestPartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.events.PersonPropertyUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.testsupport.PersonPropertiesTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.testsupport.PersonPropertiesTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.testsupport.TestPersonPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + +public class AT_PersonPropertyLabeler { + + private static class LocalPersonPropertyLabeler extends PersonPropertyLabeler { + private final Function labelingFunction; + + public LocalPersonPropertyLabeler(PersonPropertyId personPropertyId, Function labelingFunction) { + super(personPropertyId); + this.labelingFunction = labelingFunction; + } + + @Override + protected Object getLabelFromValue(Object value) { + return labelingFunction.apply(value); + } + } + + @Test + @UnitTestConstructor(target = PersonPropertyLabeler.class, args = { PersonPropertyId.class}) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonPropertyLabeler.class, name = "getLabelerSensitivities", args = {}) + public void testGetLabelerSensitivities() { + + /* + * Get the labeler sensitivities and show that they are consistent with + * their documented behaviors. + */ + + PersonPropertyId personPropertyId = TestPersonPropertyId.PERSON_PROPERTY_4_BOOLEAN_MUTABLE_TRACK; + PersonPropertyLabeler personPropertyLabeler = new LocalPersonPropertyLabeler(personPropertyId, (c) -> null); + + Set> labelerSensitivities = personPropertyLabeler.getLabelerSensitivities(); + + // show that there is exactly one sensitivity + assertEquals(1, labelerSensitivities.size()); + + // show that the sensitivity is associated with + // PersonPropertyUpdateEvent + LabelerSensitivity labelerSensitivity = labelerSensitivities.iterator().next(); + assertEquals(PersonPropertyUpdateEvent.class, labelerSensitivity.getEventClass()); + + /* + * Show that the sensitivity will return the person id from a + * PersonPropertyUpdateEvent if the event matches the person property + * id. + */ + PersonId personId = new PersonId(56); + PersonPropertyUpdateEvent personPropertyUpdateEvent = new PersonPropertyUpdateEvent(personId, personPropertyId, false, true); + Optional optional = labelerSensitivity.getPersonId(personPropertyUpdateEvent); + assertTrue(optional.isPresent()); + assertEquals(personId, optional.get()); + + /* + * Show that the sensitivity will return an empty optional of person id + * from a PersonPropertyUpdateEvent if the event does not match the + * person property id. + */ + + personPropertyId = TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; + personPropertyUpdateEvent = new PersonPropertyUpdateEvent(personId, personPropertyId, false, true); + optional = labelerSensitivity.getPersonId(personPropertyUpdateEvent); + assertFalse(optional.isPresent()); + + } + + @Test + @UnitTestMethod(target = PersonPropertyLabeler.class, name = "getCurrentLabel", args = { PartitionsContext.class, PersonId.class }) + public void testGetCurrentLabel() { + /* + * Have the agent show that the person property labeler produces a label + * for each person that is consistent with the function passed to the + * person property labeler. + */ + Factory factory = PersonPropertiesTestPluginFactory.factory(10, 6445109933336671672L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + // establish data views + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + // select a property to work with + PersonPropertyId personPropertyId = TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; + + /* + * Assign random values to the people so that we can get some + * variety in the labels + */ + List people = peopleDataManager.getPeople(); + for (PersonId personId : people) { + personPropertiesDataManager.setPersonPropertyValue(personId, personPropertyId, randomGenerator.nextBoolean()); + } + + /* + * build a person property labeler with a function that can be + * tested + */ + Function function = (input) -> { + Boolean value = (Boolean) input; + if (value) { + return "A"; + } + return "B"; + }; + + PersonPropertyLabeler personPropertyLabeler = new LocalPersonPropertyLabeler(personPropertyId, function); + + /* + * Apply the labeler to each person and compare it to the more + * direct use of the labeler's function + */ + for (PersonId personId : people) { + + // get the person's property value and apply the function + // directly + Boolean value = personPropertiesDataManager.getPersonPropertyValue(personId, personPropertyId); + Object expectedLabel = function.apply(value); + + // get the label from the person id + Object actualLabel = personPropertyLabeler.getCurrentLabel(testPartitionsContext, personId); + + // show that the two labels are equal + assertEquals(expectedLabel, actualLabel); + + } + + // precondition tests + + // if the person does not exist + ContractException contractException = assertThrows(ContractException.class, () -> personPropertyLabeler.getCurrentLabel(testPartitionsContext, new PersonId(100000))); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + // if the person id is null + contractException = assertThrows(ContractException.class, () -> personPropertyLabeler.getCurrentLabel(testPartitionsContext, null)); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = PersonPropertyLabeler.class, name = "getId", args = {}) + public void testGetId() { + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + assertEquals(testPersonPropertyId, new LocalPersonPropertyLabeler(testPersonPropertyId, (c) -> null).getId()); + } + } + + @Test + @UnitTestMethod(target = PersonPropertyLabeler.class, name = "getPersonPropertyId", args = {}) + public void testGetPersonPropertyId() { + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + assertEquals(testPersonPropertyId, new LocalPersonPropertyLabeler(testPersonPropertyId, (c) -> null).getPersonPropertyId()); + } + } + + @Test + @UnitTestMethod(target = PersonPropertyLabeler.class, name = "getPastLabel", args = { PartitionsContext.class, Event.class }) + public void testGetPastLabel() { + Factory factory = PersonPropertiesTestPluginFactory.factory(10, 770141763380713425L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + // establish data views + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + // select a property to work with + PersonPropertyId personPropertyId = TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK; + + /* + * Assign random values to the people so that we can get some + * variety in the labels + */ + List people = peopleDataManager.getPeople(); + for (PersonId personId : people) { + personPropertiesDataManager.setPersonPropertyValue(personId, personPropertyId, randomGenerator.nextInt(100)); + } + + /* + * build a person property labeler with a function that can be + * tested + */ + Function function = (g) -> { + Integer integer = (Integer) g; + return integer; + }; + + PersonPropertyLabeler personPropertyLabeler = new LocalPersonPropertyLabeler(personPropertyId, function); + + /* + * Apply the labeler to each person and compare it to the more + * direct use of the labeler's function + */ + for (PersonId personId : people) { + int newValue = randomGenerator.nextInt(1000); + int oldValue = personPropertiesDataManager.getPersonPropertyValue(personId, personPropertyId); + + if (newValue == oldValue) { + newValue += 1; + } + + personPropertiesDataManager.setPersonPropertyValue(personId, personPropertyId, newValue); + Object expectedLabel = function.apply(oldValue); + + // get the label + Object actualLabel = personPropertyLabeler.getPastLabel(testPartitionsContext, new PersonPropertyUpdateEvent(personId, personPropertyId, oldValue, newValue)); + + // show that the two labels are equal + assertEquals(expectedLabel, actualLabel); + + } + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + + @Test + @UnitTestMethod(target = PersonPropertyLabeler.class, name = "toString", args = {}) + public void testToString() { + + for(TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + LocalPersonPropertyLabeler localPersonPropertyLabeler = new LocalPersonPropertyLabeler(testPersonPropertyId,(value)->null); + String actualValue = localPersonPropertyLabeler.toString(); + String expectedValue ="PersonPropertyLabeler [personPropertyId="+testPersonPropertyId+"]"; + assertEquals(expectedValue, actualValue); + } + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/testsupport/AT_PersonPropertiesTestPluginFactory.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/testsupport/AT_PersonPropertiesTestPluginFactory.java new file mode 100644 index 000000000..1a138a953 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/testsupport/AT_PersonPropertiesTestPluginFactory.java @@ -0,0 +1,400 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.testsupport; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.TestFactoryUtil; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginId; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePluginId; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeoplePluginData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonRange; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.PersonPropertiesPluginId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.reports.PersonPropertyInteractionReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.reports.PersonPropertyReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyError; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.testsupport.PersonPropertiesTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.regions.RegionsPluginId; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionError; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.TestRegionId; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.TestRegionPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPluginId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.StochasticsError; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; +import util.wrappers.MutableBoolean; + +public class AT_PersonPropertiesTestPluginFactory { + + @Test + @UnitTestMethod(target = PersonPropertiesTestPluginFactory.class, name = "factory", args = { int.class, long.class, + Consumer.class }) + public void testFactory_Consumer() { + MutableBoolean executed = new MutableBoolean(); + Factory factory = PersonPropertiesTestPluginFactory.factory(100, 4135374341935235561L, + c -> executed.setValue(true)); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + assertTrue(executed.getValue()); + + // precondition: consumer is null + Consumer nullConsumer = null; + ContractException contractException = assertThrows(ContractException.class, + () -> PersonPropertiesTestPluginFactory.factory(0, 0, nullConsumer)); + assertEquals(NucleusError.NULL_ACTOR_CONTEXT_CONSUMER, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PersonPropertiesTestPluginFactory.class, name = "factory", args = { int.class, long.class, + TestPluginData.class }) + public void testFactory_TestPluginData() { + MutableBoolean executed = new MutableBoolean(); + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, c -> executed.setValue(true))); + TestPluginData testPluginData = pluginBuilder.build(); + + Factory factory = PersonPropertiesTestPluginFactory.factory(100, 92376779979686632L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + assertTrue(executed.getValue()); + + // precondition: testPluginData is null + TestPluginData nullTestPluginData = null; + ContractException contractException = assertThrows(ContractException.class, + () -> PersonPropertiesTestPluginFactory.factory(0, 0, nullTestPluginData)); + assertEquals(NucleusError.NULL_PLUGIN_DATA, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = PersonPropertiesTestPluginFactory.Factory.class, name = "getPlugins", args = {}) + public void testGetPlugins() { + List plugins = PersonPropertiesTestPluginFactory.factory(0, 0, t -> { + }).getPlugins(); + assertEquals(5, plugins.size()); + + TestFactoryUtil.checkPluginExists(plugins, PersonPropertiesPluginId.PLUGIN_ID); + TestFactoryUtil.checkPluginExists(plugins, RegionsPluginId.PLUGIN_ID); + TestFactoryUtil.checkPluginExists(plugins, PeoplePluginId.PLUGIN_ID); + TestFactoryUtil.checkPluginExists(plugins, StochasticsPluginId.PLUGIN_ID); + TestFactoryUtil.checkPluginExists(plugins, TestPluginId.PLUGIN_ID); + } + + @Test + @UnitTestMethod(target = PersonPropertiesTestPluginFactory.Factory.class, name = "setPersonPropertiesPluginData", args = { + PersonPropertiesPluginData.class }) + public void testSetPersonPropertiesPluginData() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2764277826547948301L); + PersonPropertiesPluginData.Builder personPropertyBuilder = PersonPropertiesPluginData.builder(); + + List people = new ArrayList<>(); + for (int i = 0; i < 100; i++) { + people.add(new PersonId(i)); + } + + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + personPropertyBuilder.definePersonProperty(testPersonPropertyId, + testPersonPropertyId.getPropertyDefinition(), 0.0, testPersonPropertyId.isTimeTracked()); + } + for (PersonId personId : people) { + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + boolean doesNotHaveDefaultValue = testPersonPropertyId.getPropertyDefinition().getDefaultValue() + .isEmpty(); + if (doesNotHaveDefaultValue || randomGenerator.nextBoolean()) { + Object randomPropertyValue = testPersonPropertyId.getRandomPropertyValue(randomGenerator); + personPropertyBuilder.setPersonPropertyValue(personId, testPersonPropertyId, randomPropertyValue); + } + } + } + + PersonPropertiesPluginData personPropertiesPluginData = personPropertyBuilder.build(); + + List plugins = PersonPropertiesTestPluginFactory.factory(0, 0, t -> { + }).setPersonPropertiesPluginData(personPropertiesPluginData).getPlugins(); + + TestFactoryUtil.checkPluginDataExists(plugins, personPropertiesPluginData, PersonPropertiesPluginId.PLUGIN_ID); + + // precondition: personPropertiesPluginData is not null + ContractException contractException = assertThrows(ContractException.class, + () -> PersonPropertiesTestPluginFactory.factory(0, 0, t -> { + }).setPersonPropertiesPluginData(null)); + assertEquals(PersonPropertyError.NULL_PERSON_PROPERTY_PLUGN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PersonPropertiesTestPluginFactory.Factory.class, name = "setPeoplePluginData", args = { + PeoplePluginData.class }) + public void testSetPeoplePluginData() { + PeoplePluginData.Builder builder = PeoplePluginData.builder(); + builder.addPersonRange(new PersonRange(0, 99)); + + PeoplePluginData peoplePluginData = builder.build(); + + List plugins = PersonPropertiesTestPluginFactory.factory(0, 0, t -> { + }).setPeoplePluginData(peoplePluginData).getPlugins(); + + TestFactoryUtil.checkPluginDataExists(plugins, peoplePluginData, PeoplePluginId.PLUGIN_ID); + + // precondition: peoplePluginData is not null + ContractException contractException = assertThrows(ContractException.class, + () -> PersonPropertiesTestPluginFactory.factory(0, 0, t -> { + }).setPeoplePluginData(null)); + assertEquals(PersonError.NULL_PEOPLE_PLUGIN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PersonPropertiesTestPluginFactory.Factory.class, name = "setRegionsPluginData", args = { + RegionsPluginData.class }) + public void testSetRegionsPluginData() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3277720775100432241L); + int initialPopulation = 30; + List people = new ArrayList<>(); + for (int i = 0; i < initialPopulation; i++) { + people.add(new PersonId(i)); + } + + // add the region plugin + RegionsPluginData.Builder regionPluginBuilder = RegionsPluginData.builder(); + for (TestRegionId regionId : TestRegionId.values()) { + regionPluginBuilder.addRegion(regionId); + } + + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { + regionPluginBuilder.defineRegionProperty(testRegionPropertyId, + testRegionPropertyId.getPropertyDefinition()); + } + + for (TestRegionId regionId : TestRegionId.values()) { + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { + if (testRegionPropertyId.getPropertyDefinition().getDefaultValue().isEmpty() + || randomGenerator.nextBoolean()) { + Object randomPropertyValue = testRegionPropertyId.getRandomPropertyValue(randomGenerator); + regionPluginBuilder.setRegionPropertyValue(regionId, testRegionPropertyId, randomPropertyValue); + } + } + } + + TestRegionId testRegionId = TestRegionId.REGION_1; + for (PersonId personId : people) { + regionPluginBuilder.addPerson(personId, testRegionId, 0.0); + testRegionId = testRegionId.next(); + } + + regionPluginBuilder.setPersonRegionArrivalTracking(true); + + RegionsPluginData regionsPluginData = regionPluginBuilder.build(); + + List plugins = PersonPropertiesTestPluginFactory.factory(0, 0, t -> { + }).setRegionsPluginData(regionsPluginData).getPlugins(); + + TestFactoryUtil.checkPluginDataExists(plugins, regionsPluginData, RegionsPluginId.PLUGIN_ID); + + // precondition: regionsPluginData is not null + ContractException contractException = assertThrows(ContractException.class, + () -> PersonPropertiesTestPluginFactory.factory(0, 0, t -> { + }).setRegionsPluginData(null)); + assertEquals(RegionError.NULL_REGION_PLUGIN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PersonPropertiesTestPluginFactory.Factory.class, name = "setStochasticsPluginData", args = { + StochasticsPluginData.class }) + public void testSetStochasticsPluginData() { + StochasticsPluginData.Builder builder = StochasticsPluginData.builder(); + + WellState wellState = WellState.builder().setSeed(2758378374654665699L).build(); + builder.setMainRNGState(wellState); + + StochasticsPluginData stochasticsPluginData = builder.build(); + + List plugins = PersonPropertiesTestPluginFactory.factory(0, 0, t -> { + }).setStochasticsPluginData(stochasticsPluginData).getPlugins(); + + TestFactoryUtil.checkPluginDataExists(plugins, stochasticsPluginData, StochasticsPluginId.PLUGIN_ID); + + // precondition: stochasticsPluginData is not null + ContractException contractException = assertThrows(ContractException.class, + () -> PersonPropertiesTestPluginFactory.factory(0, 0, t -> { + }).setStochasticsPluginData(null)); + assertEquals(StochasticsError.NULL_STOCHASTICS_PLUGIN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PersonPropertiesTestPluginFactory.class, name = "getStandardPersonPropertiesPluginData", args = { + List.class, long.class, double[].class }) + public void testGetStandardPersonPropertiesPluginData() { + + long seed = 4684903523797799712L; + double[] propertyTime = new double[0]; + List people = new ArrayList<>(); + for (int i = 0; i < 100; i++) { + people.add(new PersonId(i)); + } + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + + double actualPropertyTime = 0; + if (propertyTime.length > 0) { + actualPropertyTime = propertyTime[0]; + } + + PersonPropertiesPluginData.Builder personPropertyBuilder = PersonPropertiesPluginData.builder(); + + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + personPropertyBuilder.definePersonProperty(testPersonPropertyId, + testPersonPropertyId.getPropertyDefinition(), 0.0, testPersonPropertyId.isTimeTracked()); + } + + for (PersonId personId : people) { + + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId + .getShuffledPersonPropertyIds(randomGenerator)) { + + boolean hasDefault = testPersonPropertyId.getPropertyDefinition().getDefaultValue().isPresent(); + boolean setValue = randomGenerator.nextBoolean(); + + if (!hasDefault || setValue) { + Object randomPropertyValue = testPersonPropertyId.getRandomPropertyValue(randomGenerator); + personPropertyBuilder.setPersonPropertyValue(personId, testPersonPropertyId, randomPropertyValue); + } else if (hasDefault && personId.getValue() % 5 == 0) { + personPropertyBuilder.setPersonPropertyValue(personId, testPersonPropertyId, + testPersonPropertyId.getPropertyDefinition().getDefaultValue().get()); + } + + if (testPersonPropertyId.isTimeTracked() && personId.getValue() % 5 == 0) { + personPropertyBuilder.setPersonPropertyTime(personId, testPersonPropertyId, actualPropertyTime); + } + } + } + + PersonPropertiesPluginData expectedPluginData = personPropertyBuilder.build(); + PersonPropertiesPluginData actualPluginData = PersonPropertiesTestPluginFactory + .getStandardPersonPropertiesPluginData(people, seed); + + assertEquals(expectedPluginData, actualPluginData); + } + + @Test + @UnitTestMethod(target = PersonPropertiesTestPluginFactory.class, name = "getStandardPeoplePluginData", args = { + int.class }) + public void testGetStandardPeoplePluginData() { + + int initialPopulation = 100; + + PeoplePluginData.Builder peopleBuilder = PeoplePluginData.builder(); + if (initialPopulation > 0) { + peopleBuilder.addPersonRange(new PersonRange(0, initialPopulation - 1)); + } + + PeoplePluginData expectedPluginData = peopleBuilder.build(); + PeoplePluginData actualPluginData = PersonPropertiesTestPluginFactory + .getStandardPeoplePluginData(initialPopulation); + + assertEquals(expectedPluginData, actualPluginData); + } + + @Test + @UnitTestMethod(target = PersonPropertiesTestPluginFactory.class, name = "getStandardRegionsPluginData", args = { + List.class, long.class }) + public void testGetStandardRegionsPluginData() { + + long seed = 2729857981015931439L; + int initialPopulation = 100; + List people = new ArrayList<>(); + for (int i = 0; i < initialPopulation; i++) { + people.add(new PersonId(i)); + } + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + + RegionsPluginData.Builder regionBuilder = RegionsPluginData.builder(); + // add the regions + for (TestRegionId testRegionId : TestRegionId.values()) { + regionBuilder.addRegion(testRegionId); + } + for (PersonId personId : people) { + TestRegionId randomRegionId = TestRegionId.getRandomRegionId(randomGenerator); + regionBuilder.addPerson(personId, randomRegionId); + } + + RegionsPluginData expectedPluginData = regionBuilder.build(); + RegionsPluginData actualPluginData = PersonPropertiesTestPluginFactory.getStandardRegionsPluginData(people, + seed); + + assertEquals(expectedPluginData, actualPluginData); + } + + @Test + @UnitTestMethod(target = PersonPropertiesTestPluginFactory.class, name = "getStandardStochasticsPluginData", args = { + long.class }) + public void testGetStandardStochasticsPluginData() { + long seed = 6072871729256538807L; + + WellState wellState = WellState.builder().setSeed(seed).build(); + + StochasticsPluginData expectedPluginData = StochasticsPluginData.builder().setMainRNGState(wellState).build(); + StochasticsPluginData actualPluginData = PersonPropertiesTestPluginFactory + .getStandardStochasticsPluginData(seed); + + assertEquals(expectedPluginData, actualPluginData); + } + + + @Test + @UnitTestMethod(target = PersonPropertiesTestPluginFactory.Factory.class, name = "setPersonPropertyReportPluginData", args = { + PersonPropertyReportPluginData.class }) + public void testSetsetPersonPropertyReportPluginData() { + PersonPropertyReportPluginData personPropertyReportPluginData = PersonPropertyReportPluginData.builder()// + .setReportLabel(new SimpleReportLabel("report label"))// + .setReportPeriod(ReportPeriod.DAILY)// + .build(); + + List plugins = PersonPropertiesTestPluginFactory.factory(0, 0, t -> { + }).setPersonPropertyReportPluginData(personPropertyReportPluginData).getPlugins(); + + TestFactoryUtil.checkPluginDataExists(plugins, personPropertyReportPluginData, PersonPropertiesPluginId.PLUGIN_ID); + } + + + @Test + @UnitTestMethod(target = PersonPropertiesTestPluginFactory.Factory.class, name = "setPersonPropertyInteractionReportPluginData", args = { + PersonPropertyInteractionReportPluginData.class }) + public void testSetPersonPropertyInteractionReportPluginData() { + PersonPropertyInteractionReportPluginData personPropertyInteractionReportPluginData = PersonPropertyInteractionReportPluginData.builder()// + .addPersonPropertyId(TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK)// + .addPersonPropertyId(TestPersonPropertyId.PERSON_PROPERTY_5_INTEGER_MUTABLE_TRACK)// + .setReportLabel(new SimpleReportLabel("report label"))// + .setReportPeriod(ReportPeriod.DAILY)// + .build(); + + List plugins = PersonPropertiesTestPluginFactory.factory(0, 0, t -> { + }).setPersonPropertyInteractionReportPluginData(personPropertyInteractionReportPluginData).getPlugins(); + + TestFactoryUtil.checkPluginDataExists(plugins, personPropertyInteractionReportPluginData, PersonPropertiesPluginId.PLUGIN_ID); + } + + + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/testsupport/AT_TestAuxiliaryPersonPropertyId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/testsupport/AT_TestAuxiliaryPersonPropertyId.java new file mode 100644 index 000000000..bf5320b36 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/testsupport/AT_TestAuxiliaryPersonPropertyId.java @@ -0,0 +1,100 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.testsupport; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import util.annotations.UnitTestMethod; +import util.random.RandomGeneratorProvider; +import util.wrappers.MutableInteger; + +public class AT_TestAuxiliaryPersonPropertyId { + @Test + @UnitTestMethod(target = TestAuxiliaryPersonPropertyId.class, name = "getRandomPersonPropertyId", args = { RandomGenerator.class }) + public void testGetRandomPersonPropertyId() { + Map countMap = new LinkedHashMap<>(); + for (TestAuxiliaryPersonPropertyId testPersonPropertyId : TestAuxiliaryPersonPropertyId.values()) { + countMap.put(testPersonPropertyId, new MutableInteger()); + } + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2864629454580072362L); + int sampleCount = 1000; + for (int i = 0; i < sampleCount; i++) { + TestAuxiliaryPersonPropertyId randomPersonPropertyId = TestAuxiliaryPersonPropertyId.getRandomPersonPropertyId(randomGenerator); + assertNotNull(randomPersonPropertyId); + countMap.get(randomPersonPropertyId).increment(); + } + + int minCount = sampleCount / TestAuxiliaryPersonPropertyId.values().length; + minCount *= 4; + minCount /= 5; + for (TestAuxiliaryPersonPropertyId testPersonPropertyId : TestAuxiliaryPersonPropertyId.values()) { + assertTrue(countMap.get(testPersonPropertyId).getValue() > minCount); + } + } + + @Test + @UnitTestMethod(target = TestAuxiliaryPersonPropertyId.class, name = "getRandomPropertyValue", args = { RandomGenerator.class }) + public void testGetRandomPropertyValue() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(890505028463718572L); + + /* + * Show that randomly generated values are compatible with the + * associated property definition. Show that the values are reasonably + * unique + */ + for (TestAuxiliaryPersonPropertyId testPersonPropertyId : TestAuxiliaryPersonPropertyId.values()) { + PropertyDefinition propertyDefinition = testPersonPropertyId.getPropertyDefinition(); + Set values = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + Object propertyValue = testPersonPropertyId.getRandomPropertyValue(randomGenerator); + values.add(propertyValue); + assertTrue(propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())); + } + // show that the values are reasonable unique + if (propertyDefinition.getType() != Boolean.class) { + assertTrue(values.size() > 10); + } else { + assertEquals(2, values.size()); + } + } + } + + @Test + @UnitTestMethod(target = TestAuxiliaryPersonPropertyId.class, name = "getPropertyDefinition", args = {}) + public void testGetPropertyDefinition() { + for (TestAuxiliaryPersonPropertyId testPersonPropertyId : TestAuxiliaryPersonPropertyId.values()) { + PropertyDefinition propertyDefinition = testPersonPropertyId.getPropertyDefinition(); + assertNotNull(propertyDefinition); + } + } + + @Test + @UnitTestMethod(target = TestAuxiliaryPersonPropertyId.class, name = "getUnknownPersonPropertyId", args = {}) + public void testGetUnknownPersonPropertyId() { + /* + * Shows that a generated unknown person property id is unique, not null + * and not a member of the enum + */ + Set testProperties = EnumSet.allOf(TestAuxiliaryPersonPropertyId.class); + Set unknownPersonPropertyIds = new LinkedHashSet<>(); + for (int i = 0; i < 30; i++) { + PersonPropertyId unknownPersonPropertyId = TestAuxiliaryPersonPropertyId.getUnknownPersonPropertyId(); + assertNotNull(unknownPersonPropertyId); + boolean unique = unknownPersonPropertyIds.add(unknownPersonPropertyId); + assertTrue(unique); + assertFalse(testProperties.contains(unknownPersonPropertyId)); + } + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/testsupport/AT_TestPersonPropertyId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/testsupport/AT_TestPersonPropertyId.java new file mode 100644 index 000000000..7b8bddfd7 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/testsupport/AT_TestPersonPropertyId.java @@ -0,0 +1,209 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.testsupport; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import util.annotations.UnitTestMethod; +import util.random.RandomGeneratorProvider; +import util.wrappers.MutableInteger; + +public class AT_TestPersonPropertyId implements PersonPropertyId { + + @Test + @UnitTestMethod(target = TestPersonPropertyId.class, name = "getRandomPersonPropertyId", args = { + RandomGenerator.class }) + public void testGetRandomPersonPropertyId() { + Map countMap = new LinkedHashMap<>(); + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + countMap.put(testPersonPropertyId, new MutableInteger()); + } + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5471359242434395756L); + int sampleCount = 1000; + for (int i = 0; i < sampleCount; i++) { + TestPersonPropertyId randomPersonPropertyId = TestPersonPropertyId + .getRandomPersonPropertyId(randomGenerator); + assertNotNull(randomPersonPropertyId); + countMap.get(randomPersonPropertyId).increment(); + } + + int minCount = sampleCount / TestPersonPropertyId.values().length; + minCount *= 4; + minCount /= 5; + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + assertTrue(countMap.get(testPersonPropertyId).getValue() > minCount); + } + } + + @Test + @UnitTestMethod(target = TestPersonPropertyId.class, name = "getRandomPropertyValue", args = { + RandomGenerator.class }) + public void testGetRandomPropertyValue() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8507625070108242089L); + + /* + * Show that randomly generated values are compatible with the associated + * property definition. Show that the values are reasonably unique + */ + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + PropertyDefinition propertyDefinition = testPersonPropertyId.getPropertyDefinition(); + Set values = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + Object propertyValue = testPersonPropertyId.getRandomPropertyValue(randomGenerator); + values.add(propertyValue); + assertTrue(propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())); + } + // show that the values are reasonable unique + if (propertyDefinition.getType() != Boolean.class) { + assertTrue(values.size() > 10); + } else { + assertEquals(2, values.size()); + } + } + } + + @Test + @UnitTestMethod(target = TestPersonPropertyId.class, name = "getPropertyDefinition", args = {}) + public void testGetPropertyDefinition() { + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + PropertyDefinition propertyDefinition = testPersonPropertyId.getPropertyDefinition(); + assertNotNull(propertyDefinition); + } + } + + @Test + @UnitTestMethod(target = TestPersonPropertyId.class, name = "getUnknownPersonPropertyId", args = {}) + public void testGetUnknownPersonPropertyId() { + /* + * Shows that a generated unknown person property id is unique, not null and not + * a member of the enum + */ + Set testProperties = EnumSet.allOf(TestPersonPropertyId.class); + Set unknownPersonPropertyIds = new LinkedHashSet<>(); + for (int i = 0; i < 30; i++) { + PersonPropertyId unknownPersonPropertyId = TestPersonPropertyId.getUnknownPersonPropertyId(); + assertNotNull(unknownPersonPropertyId); + boolean unique = unknownPersonPropertyIds.add(unknownPersonPropertyId); + assertTrue(unique); + assertFalse(testProperties.contains(unknownPersonPropertyId)); + } + } + + @Test + @UnitTestMethod(target = TestPersonPropertyId.class, name = "getPropertiesWithDefaultValues", args = {}) + public void testGetPropertiesWithDefaultValues() { + List expectedValues = new ArrayList<>(); + + for (TestPersonPropertyId id : TestPersonPropertyId.values()) { + if (id.getPropertyDefinition().getDefaultValue().isPresent()) { + expectedValues.add(id); + } + } + + List actualValues = TestPersonPropertyId.getPropertiesWithDefaultValues(); + + assertNotNull(actualValues); + assertEquals(expectedValues.size(), actualValues.size()); + Set setOfExpectedValues = new LinkedHashSet<>(expectedValues); + Set setOfActualValues = new LinkedHashSet<>(actualValues); + assertEquals(setOfExpectedValues, setOfActualValues); + assertEquals(expectedValues.size(), setOfExpectedValues.size()); + assertEquals(actualValues.size(), setOfActualValues.size()); + } + + @Test + @UnitTestMethod(target = TestPersonPropertyId.class, name = "getPropertiesWithoutDefaultValues", args = {}) + public void testGetPropertiesWithoutDefaultValues() { + List expectedValues = new ArrayList<>(); + + for (TestPersonPropertyId id : TestPersonPropertyId.values()) { + if (id.getPropertyDefinition().getDefaultValue().isEmpty()) { + expectedValues.add(id); + } + } + + List actualValues = TestPersonPropertyId.getPropertiesWithoutDefaultValues(); + + assertNotNull(actualValues); + assertEquals(expectedValues.size(), actualValues.size()); + Set setOfExpectedValues = new LinkedHashSet<>(expectedValues); + Set setOfActualValues = new LinkedHashSet<>(actualValues); + assertEquals(setOfExpectedValues, setOfActualValues); + assertEquals(expectedValues.size(), setOfExpectedValues.size()); + assertEquals(actualValues.size(), setOfActualValues.size()); + } + + @Test + @UnitTestMethod(target = TestPersonPropertyId.class, name = "next", args = {}) + public void testNext() { + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + int index = testPersonPropertyId.ordinal(); + index += 1; + index %= TestPersonPropertyId.values().length; + TestPersonPropertyId expectedNextTestPersonPropertyId = TestPersonPropertyId.values()[index]; + assertEquals(expectedNextTestPersonPropertyId, testPersonPropertyId.next()); + } + } + + @Test + @UnitTestMethod(target = TestPersonPropertyId.class, name = "getPersonPropertyIds", args = {}) + public void testGetPersonPropertyIds() { + assertEquals(Arrays.asList(TestPersonPropertyId.values()), TestPersonPropertyId.getPersonPropertyIds()); + } + + @Test + @UnitTestMethod(target = TestPersonPropertyId.class, name = "isTimeTracked", args = {}) + public void testIsTimeTracked() { + assertEquals(9, TestPersonPropertyId.values().length); + assertFalse(TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK.isTimeTracked()); + assertFalse(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK.isTimeTracked()); + assertFalse(TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK.isTimeTracked()); + assertTrue(TestPersonPropertyId.PERSON_PROPERTY_4_BOOLEAN_MUTABLE_TRACK.isTimeTracked()); + assertTrue(TestPersonPropertyId.PERSON_PROPERTY_5_INTEGER_MUTABLE_TRACK.isTimeTracked()); + assertTrue(TestPersonPropertyId.PERSON_PROPERTY_6_DOUBLE_MUTABLE_TRACK.isTimeTracked()); + assertFalse(TestPersonPropertyId.PERSON_PROPERTY_7_BOOLEAN_IMMUTABLE_NO_TRACK.isTimeTracked()); + assertFalse(TestPersonPropertyId.PERSON_PROPERTY_8_INTEGER_IMMUTABLE_NO_TRACK.isTimeTracked()); + assertFalse(TestPersonPropertyId.PERSON_PROPERTY_9_DOUBLE_IMMUTABLE_NO_TRACK.isTimeTracked()); + } + + @Test + @UnitTestMethod(target = TestPersonPropertyId.class, name = "getShuffledPersonPropertyIds", args = { + RandomGenerator.class }) + public void testGetShuffledPersonPropertyIds() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(687867202081341049L); + + Set baseSet = new LinkedHashSet<>(); + for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { + baseSet.add(testPersonPropertyId); + } + //There are 9!=362880 possible lists. The probability of collision is very low. + Set> lists = new LinkedHashSet<>(); + for (int i = 0; i < 1000; i++) { + List shuffledPersonPropertyIds = TestPersonPropertyId + .getShuffledPersonPropertyIds(randomGenerator); + + lists.add(shuffledPersonPropertyIds); + + Set set = new LinkedHashSet<>(shuffledPersonPropertyIds); + assertEquals(baseSet, set); + } + + assertEquals(1000,lists.size()); + } + +} \ No newline at end of file diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/testsupport/AT_TestPersonPropertyLabeler.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/testsupport/AT_TestPersonPropertyLabeler.java new file mode 100644 index 000000000..dedff81a5 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/personproperties/testsupport/AT_TestPersonPropertyLabeler.java @@ -0,0 +1,55 @@ +package gov.hhs.aspr.ms.gcm.plugins.personproperties.testsupport; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.Equality; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_TestPersonPropertyLabeler { + + + @Test + @UnitTestConstructor(target = TestPersonPropertyLabeler.class, args = { PersonPropertyId.class, Equality.class, + int.class }) + public void testTestPersonPropertyLabeler() { + // nothing to test + } + + @Test + @UnitTestMethod(target = TestPersonPropertyLabeler.class, name = "getEquality", args = {}) + public void testGetEquality() { + for (Equality equality : Equality.values()) { + TestPersonPropertyLabeler testPersonPropertyLabeler = new TestPersonPropertyLabeler( + TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, equality, 5); + assertEquals(equality, testPersonPropertyLabeler.getEquality()); + } + } + + @Test + @UnitTestMethod(target = TestPersonPropertyLabeler.class, name = "getValue", args = {}) + public void testGetValue() { + for (int i = 0; i < 10; i++) { + TestPersonPropertyLabeler testPersonPropertyLabeler = new TestPersonPropertyLabeler( + TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, Equality.EQUAL, i); + assertEquals(i, testPersonPropertyLabeler.getValue()); + } + } + + @Test + @UnitTestMethod(target = TestPersonPropertyLabeler.class, name = "toString", args = {}) + public void testToString() { + TestPersonPropertyLabeler testPersonPropertyLabeler = new TestPersonPropertyLabeler( + TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, Equality.EQUAL, 15); + + String actualValue = testPersonPropertyLabeler.toString(); + + String expectedValue = "TestPersonPropertyLabeler [equality=EQUAL, value=15, personPropertyId=PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK]"; + + assertEquals(expectedValue, actualValue); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/AT_RegionPlugin.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/AT_RegionPlugin.java new file mode 100644 index 000000000..c5cb1e91f --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/AT_RegionPlugin.java @@ -0,0 +1,103 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePluginId; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.regions.reports.RegionPropertyReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.regions.reports.RegionTransferReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import util.annotations.UnitTestMethod; + +public class AT_RegionPlugin { + + + + @Test + @UnitTestMethod(target = RegionsPlugin.Builder.class,name = "getRegionsPlugin", args = {}) + public void testGetRegionPlugin() { + RegionsPluginData regionsPluginData = RegionsPluginData.builder().build(); + Plugin regionPlugin = RegionsPlugin.builder().setRegionsPluginData(regionsPluginData).getRegionsPlugin(); + + assertEquals(1,regionPlugin.getPluginDatas().size()); + assertTrue(regionPlugin.getPluginDatas().contains(regionsPluginData)); + + assertEquals(RegionsPluginId.PLUGIN_ID, regionPlugin.getPluginId()); + + Set expectedDependencies = new LinkedHashSet<>(); + expectedDependencies.add(PeoplePluginId.PLUGIN_ID); + + assertEquals(expectedDependencies, regionPlugin.getPluginDependencies()); + + } + +// RegionsPlugin public static plugins.regions.RegionsPlugin$Builder plugins.regions.RegionsPlugin.builder() + @Test + @UnitTestMethod(target = RegionsPlugin.class,name = "builder", args = {}) + public void testBuilder() { + assertNotNull(RegionsPlugin.builder()); + } + + + + @Test + @UnitTestMethod(target = RegionsPlugin.Builder.class, name = "setRegionsPluginData", args = { + RegionsPluginData.class }) + public void testSetRegionsPluginData() { + RegionsPluginData regionsPluginData = RegionsPluginData.builder().build(); + + + Plugin regionsPlugin = RegionsPlugin.builder() + .setRegionsPluginData(regionsPluginData) + .getRegionsPlugin(); + + assertTrue(regionsPlugin.getPluginDatas().contains(regionsPluginData)); + } + + + @Test + @UnitTestMethod(target = RegionsPlugin.Builder.class, name = "setRegionPropertyReportPluginData", args = { + RegionPropertyReportPluginData.class }) + public void testSetRegionPropertyReportPluginData() { + RegionsPluginData regionsPluginData = RegionsPluginData.builder().build(); + RegionPropertyReportPluginData regionPropertyReportPluginData = RegionPropertyReportPluginData.builder().setReportLabel(new SimpleReportLabel("RegionPropertyReport")).build(); + + Plugin regionsPlugin = RegionsPlugin.builder()// + .setRegionsPluginData(regionsPluginData)// + .setRegionPropertyReportPluginData(regionPropertyReportPluginData)// + .getRegionsPlugin(); + + assertTrue(regionsPlugin.getPluginDatas().contains(regionPropertyReportPluginData)); + } + + @Test + @UnitTestMethod(target = RegionsPlugin.Builder.class, name = "setRegionTransferReportPluginData", args = { + RegionTransferReportPluginData.class }) + public void setRegionTransferReportPluginData() { + RegionsPluginData regionsPluginData = RegionsPluginData.builder().build(); + RegionTransferReportPluginData regionTransferReportPluginData = RegionTransferReportPluginData.builder()// + .setReportLabel(new SimpleReportLabel("RegionPropertyReport"))// + .setReportPeriod(ReportPeriod.DAILY)// + .build(); + + Plugin regionsPlugin = RegionsPlugin.builder()// + .setRegionsPluginData(regionsPluginData)// + .setRegionTransferReportPluginData(regionTransferReportPluginData)// + .getRegionsPlugin(); + + assertTrue(regionsPlugin.getPluginDatas().contains(regionTransferReportPluginData)); + } + + + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/AT_RegionPluginId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/AT_RegionPluginId.java new file mode 100644 index 000000000..9f9020fca --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/AT_RegionPluginId.java @@ -0,0 +1,16 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestField; + +public class AT_RegionPluginId { + + @Test + @UnitTestField(target = RegionsPluginId.class, name = "PLUGIN_ID") + public void testPluginId() { + assertNotNull(RegionsPluginId.PLUGIN_ID); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/datamanagers/AT_RegionsDataManager.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/datamanagers/AT_RegionsDataManager.java new file mode 100644 index 000000000..34c3bcfb4 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/datamanagers/AT_RegionsDataManager.java @@ -0,0 +1,2988 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Random; +import java.util.Set; +import java.util.function.Consumer; + +import org.apache.commons.math3.random.RandomGenerator; +import org.apache.commons.math3.util.FastMath; +import org.apache.commons.math3.util.Pair; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.nucleus.EventFilter; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.Simulation; +import gov.hhs.aspr.ms.gcm.nucleus.SimulationState; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.runcontinuityplugin.RunContinuityPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.runcontinuityplugin.RunContinuityPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestDataManager; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestDataManagerPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestOutputConsumer; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePlugin; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePluginId; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeoplePluginData; +import gov.hhs.aspr.ms.gcm.plugins.people.events.PersonImminentAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.events.PersonRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonConstructionData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonRange; +import gov.hhs.aspr.ms.gcm.plugins.regions.RegionsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.regions.events.PersonRegionUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.regions.events.RegionAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.regions.events.RegionPropertyDefinitionEvent; +import gov.hhs.aspr.ms.gcm.plugins.regions.events.RegionPropertyUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionConstructionData; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionError; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionPropertyDefinitionInitialization; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.RegionsTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.RegionsTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.TestRegionId; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.TestRegionPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; +import util.wrappers.MultiKey; +import util.wrappers.MutableDouble; +import util.wrappers.MutableInteger; + +public class AT_RegionsDataManager { + + @Test + @UnitTestMethod(target = RegionsDataManager.class, name = "expandCapacity", args = { int.class }) + public void testExpandCapacity() { + Factory factory = RegionsTestPluginFactory.factory(20, 3161087621160007875L, true, (c) -> { + // show that a negative growth causes an exception + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + ContractException contractException = assertThrows(ContractException.class, + () -> regionsDataManager.expandCapacity(-1)); + assertEquals(PersonError.NEGATIVE_GROWTH_PROJECTION, contractException.getErrorType()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // use manual tests for non-negative growth + } + + @Test + @UnitTestConstructor(target = RegionsDataManager.class, args = { RegionsPluginData.class }) + public void testConstructor() { + // this test is covered by the remaining tests + ContractException contractException = assertThrows(ContractException.class, () -> new RegionsDataManager(null)); + assertEquals(RegionError.NULL_REGION_PLUGIN_DATA, contractException.getErrorType()); + } + + /** + * Demonstrates that the data manager produces plugin data that reflects its + * final state + */ + @Test + @UnitTestMethod(target = RegionsDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testStateFinalization() { + + RegionsPluginData regionsPluginData = RegionsPluginData.builder().build(); + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + RegionPropertyDefinitionInitialization regionPropertyDefinitionInitialization = RegionPropertyDefinitionInitialization + .builder().setRegionPropertyId(TestRegionPropertyId.REGION_PROPERTY_1_BOOLEAN_MUTABLE) + .setPropertyDefinition(TestRegionPropertyId.REGION_PROPERTY_1_BOOLEAN_MUTABLE.getPropertyDefinition()) + .build(); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + regionsDataManager.defineRegionProperty(regionPropertyDefinitionInitialization); + RegionId regionId = TestRegionId.REGION_1; + RegionConstructionData regionConstructionData = RegionConstructionData.builder() + .setRegionPropertyValue(regionPropertyDefinitionInitialization.getRegionPropertyId(), true) + .setRegionId(regionId).build(); + regionsDataManager.addRegion(regionConstructionData); + regionsDataManager.setRegionPropertyValue(regionId, + regionPropertyDefinitionInitialization.getRegionPropertyId(), true); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = RegionsTestPluginFactory.factory(0, 3347423560010833899L, true, testPluginData)// + .setRegionsPluginData(regionsPluginData); + + TestOutputConsumer testOutputConsumer = TestSimulation.builder()// + .addPlugins(factory.getPlugins())// + .setProduceSimulationStateOnHalt(true)// + .setSimulationHaltTime(2).build()// + .execute(); + Map outputItems = testOutputConsumer.getOutputItemMap(RegionsPluginData.class); + assertEquals(1, outputItems.size()); + + RegionsPluginData expectedPluginData = RegionsPluginData.builder().addRegion(TestRegionId.REGION_1) + .defineRegionProperty(regionPropertyDefinitionInitialization.getRegionPropertyId(), + regionPropertyDefinitionInitialization.getPropertyDefinition()) + .setRegionPropertyValue(TestRegionId.REGION_1, + regionPropertyDefinitionInitialization.getRegionPropertyId(), true) + .build(); + RegionsPluginData actualPluginData = outputItems.keySet().iterator().next(); + assertEquals(expectedPluginData, actualPluginData); + + // + regionsPluginData = RegionsPluginData.builder().build(); + pluginBuilder = TestPluginData.builder(); + RegionPropertyDefinitionInitialization regionPropertyDefinitionInitialization2 = RegionPropertyDefinitionInitialization + .builder().setRegionPropertyId(TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE) + .setPropertyDefinition(TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE.getPropertyDefinition()) + .build(); + RegionPropertyDefinitionInitialization regionPropertyDefinitionInitialization3 = RegionPropertyDefinitionInitialization + .builder().setRegionPropertyId(TestRegionPropertyId.REGION_PROPERTY_3_DOUBLE_MUTABLE) + .setPropertyDefinition(TestRegionPropertyId.REGION_PROPERTY_3_DOUBLE_MUTABLE.getPropertyDefinition()) + .build(); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + regionsDataManager.defineRegionProperty(regionPropertyDefinitionInitialization2); + regionsDataManager.defineRegionProperty(regionPropertyDefinitionInitialization3); + RegionId regionId2 = TestRegionId.REGION_2; + RegionId regionId3 = TestRegionId.REGION_3; + RegionConstructionData regionConstructionData2 = RegionConstructionData.builder() + .setRegionPropertyValue(regionPropertyDefinitionInitialization2.getRegionPropertyId(), 15) + .setRegionId(regionId2).build(); + RegionConstructionData regionConstructionData3 = RegionConstructionData.builder() + .setRegionPropertyValue(regionPropertyDefinitionInitialization2.getRegionPropertyId(), 67) + .setRegionId(regionId3).build(); + regionsDataManager.addRegion(regionConstructionData2); + regionsDataManager.addRegion(regionConstructionData3); + regionsDataManager.setRegionPropertyValue(regionId2, + regionPropertyDefinitionInitialization2.getRegionPropertyId(), 15); + regionsDataManager.setRegionPropertyValue(regionId3, + regionPropertyDefinitionInitialization3.getRegionPropertyId(), 67.9); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + RegionId regionId2 = TestRegionId.REGION_2; + RegionId regionId3 = TestRegionId.REGION_3; + regionsDataManager.setRegionPropertyValue(regionId2, + regionPropertyDefinitionInitialization2.getRegionPropertyId(), 92); + regionsDataManager.setRegionPropertyValue(regionId2, + regionPropertyDefinitionInitialization2.getRegionPropertyId(), 5); + regionsDataManager.setRegionPropertyValue(regionId3, + regionPropertyDefinitionInitialization3.getRegionPropertyId(), 82.5); + regionsDataManager.setRegionPropertyValue(regionId3, + regionPropertyDefinitionInitialization3.getRegionPropertyId(), 123.5); + })); + + testPluginData = pluginBuilder.build(); + factory = RegionsTestPluginFactory.factory(0, 3347423560010833899L, true, testPluginData) + .setRegionsPluginData(regionsPluginData); + testOutputConsumer = TestSimulation.builder().addPlugins(factory.getPlugins()) + .setProduceSimulationStateOnHalt(true).setSimulationHaltTime(2).build().execute(); + outputItems = testOutputConsumer.getOutputItemMap(RegionsPluginData.class); + assertEquals(1, outputItems.size()); + expectedPluginData = RegionsPluginData.builder()// + + .addRegion(TestRegionId.REGION_3).addRegion(TestRegionId.REGION_2)// + .defineRegionProperty(regionPropertyDefinitionInitialization2.getRegionPropertyId(), + regionPropertyDefinitionInitialization2.getPropertyDefinition()) + .defineRegionProperty(regionPropertyDefinitionInitialization3.getRegionPropertyId(), + regionPropertyDefinitionInitialization3.getPropertyDefinition()) + .setRegionPropertyValue(TestRegionId.REGION_2, + regionPropertyDefinitionInitialization2.getRegionPropertyId(), 5) + .setRegionPropertyValue(TestRegionId.REGION_3, + regionPropertyDefinitionInitialization2.getRegionPropertyId(), 67) + .setRegionPropertyValue(TestRegionId.REGION_3, + regionPropertyDefinitionInitialization3.getRegionPropertyId(), 123.5) + .build(); + actualPluginData = outputItems.keySet().iterator().next(); + assertEquals(expectedPluginData, actualPluginData); + } + + @Test + @UnitTestMethod(target = RegionsDataManager.class, name = "getPeopleInRegion", args = { RegionId.class }) + public void testGetPeopleInRegion() { + + // create a container to hold expectations + Map> expectedPeopelInRegions = new LinkedHashMap<>(); + for (TestRegionId testRegionId : TestRegionId.values()) { + expectedPeopelInRegions.put(testRegionId, new LinkedHashSet<>()); + } + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // show that each region is empty at time zero + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + for (TestRegionId testRegionId : TestRegionId.values()) { + assertEquals(0, regionsDataManager.getPeopleInRegion(testRegionId).size()); + } + })); + + // add some people + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + for (int i = 0; i < 100; i++) { + TestRegionId regionId = TestRegionId.getRandomRegionId(stochasticsDataManager.getRandomGenerator()); + PersonConstructionData personConstructionData = PersonConstructionData.builder().add(regionId).build(); + PersonId personId = peopleDataManager.addPerson(personConstructionData); + expectedPeopelInRegions.get(regionId).add(personId); + } + })); + + // show that the people in the regions match expectations + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + for (TestRegionId testRegionId : TestRegionId.values()) { + Set expectedPeople = expectedPeopelInRegions.get(testRegionId); + LinkedHashSet actualPeople = new LinkedHashSet<>( + regionsDataManager.getPeopleInRegion(testRegionId)); + assertEquals(expectedPeople, actualPeople); + } + })); + + // build and add the action plugin + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = RegionsTestPluginFactory.factory(0, 3347423560010833899L, true, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition test: if the region id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(0, 9052181434511982170L, true, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + regionsDataManager.getPeopleInRegion(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); + + // precondition test: if the region id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(0, 1410190102298165957L, true, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + // if the region id is unknown + regionsDataManager.getPeopleInRegion(TestRegionId.getUnknownRegionId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = RegionsDataManager.class, name = "getPersonRegion", args = { PersonId.class }) + public void testGetPersonRegion() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create a container to hold expectations + Map expectedPersonRegions = new LinkedHashMap<>(); + + int numberOfPeople = 100; + + /* + * Add some people and show that their regions are correctly assigned. + */ + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + for (int i = 0; i < numberOfPeople; i++) { + // select a region at random + TestRegionId regionId = TestRegionId.getRandomRegionId(stochasticsDataManager.getRandomGenerator()); + // create the person + PersonConstructionData personConstructionData = PersonConstructionData.builder().add(regionId).build(); + PersonId personId = peopleDataManager.addPerson(personConstructionData); + // show that the person has the correct region + assertEquals(regionId, regionsDataManager.getPersonRegion(personId)); + // add the person to the expectations + expectedPersonRegions.put(personId, regionId); + } + })); + + // move people over time and show that each time they are moved the + // correct region is reported + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + double planTime = 0; + for (PersonId personId : peopleDataManager.getPeople()) { + c.addPlan((c2) -> { + // determine the person's current region + RegionsDataManager regionsDataManager = c2.getDataManager(RegionsDataManager.class); + TestRegionId regionId = regionsDataManager.getPersonRegion(personId); + // select the next region for the person + regionId = regionId.next(); + // move the person + regionsDataManager.setPersonRegion(personId, regionId); + /* + * show that the region arrival time for the person is the current time in the + * simulation + */ + assertEquals(regionId, regionsDataManager.getPersonRegion(personId)); + // update the expectations + expectedPersonRegions.put(personId, regionId); + }, planTime); + planTime++; + } + })); + + double postPersonMovementTime = numberOfPeople; + + /* + * Show that the people region arrival times are maintained over time + */ + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(postPersonMovementTime, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + for (PersonId personId : peopleDataManager.getPeople()) { + RegionId expectedRegionId = expectedPersonRegions.get(personId); + RegionId actualRegionId = regionsDataManager.getPersonRegion(personId); + assertEquals(expectedRegionId, actualRegionId); + } + })); + // build and add the action plugin + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = RegionsTestPluginFactory.factory(0, 5151111920517015649L, true, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition test: if the person id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(0, 1490027040692903854L, true, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + regionsDataManager.getPersonRegion(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + // precondition test: if the person id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(0, 2144445839100475443L, true, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + regionsDataManager.getPersonRegion(new PersonId(100000)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = RegionsDataManager.class, name = "getPersonRegionArrivalTime", args = { PersonId.class }) + public void testGetPersonRegionArrivalTime() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create a container to hold expectations + Map expectedPersonRegionArrivalTimes = new LinkedHashMap<>(); + + int numberOfPeople = 100; + + /* + * Add some people and show that their region arrival times are zero. + */ + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + for (int i = 0; i < numberOfPeople; i++) { + // select a region at random + TestRegionId regionId = TestRegionId.getRandomRegionId(stochasticsDataManager.getRandomGenerator()); + // create the person + PersonConstructionData personConstructionData = PersonConstructionData.builder().add(regionId).build(); + PersonId personId = peopleDataManager.addPerson(personConstructionData); + + // show that the person has a region arrival time of zero + assertEquals(0.0, regionsDataManager.getPersonRegionArrivalTime(personId)); + + // add the person to the expectations + expectedPersonRegionArrivalTimes.put(personId, new MutableDouble()); + } + })); + + // move people over time and show that each time they are moved the + // their arrival time is correct + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + double planTime = 0; + for (PersonId personId : peopleDataManager.getPeople()) { + c.addPlan((c2) -> { + // determine the person's current region + RegionsDataManager regionsDataManager = c2.getDataManager(RegionsDataManager.class); + TestRegionId regionId = regionsDataManager.getPersonRegion(personId); + // select the next region for the person + regionId = regionId.next(); + // move the person + regionsDataManager.setPersonRegion(personId, regionId); + /* + * show that the region arrival time for the person is the current time in the + * simulation + */ + assertEquals(c2.getTime(), regionsDataManager.getPersonRegionArrivalTime(personId)); + // update the expectations + expectedPersonRegionArrivalTimes.get(personId).setValue(c2.getTime()); + }, planTime); + planTime++; + } + })); + + double postPersonMovementTime = numberOfPeople; + + /* + * Show that the people region arrival times are maintained over time + */ + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(postPersonMovementTime, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + for (PersonId personId : peopleDataManager.getPeople()) { + double expectedArrivalTime = expectedPersonRegionArrivalTimes.get(personId).getValue(); + double actualArrivalTime = regionsDataManager.getPersonRegionArrivalTime(personId); + assertEquals(expectedArrivalTime, actualArrivalTime); + } + })); + + // build and add the action plugin + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = RegionsTestPluginFactory.factory(0, 2278422620232176214L, true, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition test: if region arrival times are not being tracked + pluginBuilder = TestPluginData.builder(); + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + for (int i = 0; i < numberOfPeople; i++) { + // select a region at random + TestRegionId regionId = TestRegionId.getRandomRegionId(stochasticsDataManager.getRandomGenerator()); + // create the person + PersonConstructionData personConstructionData = PersonConstructionData.builder().add(regionId).build(); + peopleDataManager.addPerson(personConstructionData); + } + })); + + // build and add the action plugin + testPluginData = pluginBuilder.build(); + factory = RegionsTestPluginFactory.factory(0, 9214210856215652451L, true, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition test: if region arrival times are not being tracked + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(10, 1906010286127446114L, false, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + // if region arrival times are not being tracked + regionsDataManager.getPersonRegionArrivalTime(new PersonId(0)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(RegionError.REGION_ARRIVAL_TIMES_NOT_TRACKED, contractException.getErrorType()); + + // precondition test: if the person id is null + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(0, 2922597221561284586L, true, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + regionsDataManager.getPersonRegionArrivalTime(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + // precondition test: if the person id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(0, 9132391945335483479L, true, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + regionsDataManager.getPersonRegionArrivalTime(new PersonId(100000)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = RegionsDataManager.class, name = "regionArrivalsAreTracked", args = {}) + public void testRegionArrivalsAreTracked() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7220786446142555493L); + for (boolean trackTime : new boolean[] { true, false }) { + Factory factory = RegionsTestPluginFactory.factory(0, randomGenerator.nextLong(), trackTime, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + assertEquals(trackTime, regionsDataManager.regionArrivalsAreTracked()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + } + + @Test + @UnitTestMethod(target = RegionsDataManager.class, name = "getRegionIds", args = {}) + public void testGetRegionIds() { + Factory factory = RegionsTestPluginFactory.factory(0, 87615823520161580L, true, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + + Set expectedRegionIds = new LinkedHashSet<>(); + for (TestRegionId testRegionId : TestRegionId.values()) { + expectedRegionIds.add(testRegionId); + } + assertEquals(expectedRegionIds, regionsDataManager.getRegionIds()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = RegionsDataManager.class, name = "getRegionPopulationCount", args = { RegionId.class }) + public void testGetRegionPopulationCount() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // show that each region has no people + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + for (TestRegionId testRegionId : TestRegionId.values()) { + assertEquals(0, regionsDataManager.getRegionPopulationCount(testRegionId)); + } + })); + + // show that adding people results in the correct population counts + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + int n = TestRegionId.values().length; + for (int i = 0; i < 3 * n; i++) { + TestRegionId regionId = TestRegionId.values()[i % n]; + PersonConstructionData personConstructionData = PersonConstructionData.builder().add(regionId).build(); + peopleDataManager.addPerson(personConstructionData); + } + + for (TestRegionId testRegionId : TestRegionId.values()) { + assertEquals(3, regionsDataManager.getRegionPopulationCount(testRegionId)); + } + + })); + + // precondition tests + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + + // if the region id is null + ContractException contractException = assertThrows(ContractException.class, + () -> regionsDataManager.getRegionPopulationCount(null)); + assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); + + // if the region id is unknown + contractException = assertThrows(ContractException.class, + () -> regionsDataManager.getRegionPopulationCount(TestRegionId.getUnknownRegionId())); + assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); + + })); + + // build and add the action plugin + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = RegionsTestPluginFactory.factory(0, 1525815460460902517L, true, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = RegionsDataManager.class, name = "getRegionPropertyDefinition", args = { + RegionPropertyId.class }) + public void testGetRegionPropertyDefinition() { + Factory factory = RegionsTestPluginFactory.factory(0, 8915683065425449883L, true, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + Set regionPropertyIds = regionsDataManager.getRegionPropertyIds(); + assertEquals(TestRegionPropertyId.size(), regionPropertyIds.size()); + for (TestRegionPropertyId testRegionPropertyId : regionPropertyIds) { + PropertyDefinition expectedPropertyDefinition = testRegionPropertyId.getPropertyDefinition(); + PropertyDefinition actualPropertyDefinition = regionsDataManager + .getRegionPropertyDefinition(testRegionPropertyId); + assertEquals(expectedPropertyDefinition, actualPropertyDefinition); + } + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition check: if the region property id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(0, 4217775232224320101L, true, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + regionsDataManager.getRegionPropertyDefinition(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // precondition check: if the region property id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(0, 1425794836864585647L, true, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + regionsDataManager.getRegionPropertyDefinition(TestRegionPropertyId.getUnknownRegionPropertyId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = RegionsDataManager.class, name = "getRegionPropertyIds", args = {}) + public void testGetRegionPropertyIds() { + Factory factory = RegionsTestPluginFactory.factory(0, 2658585233315606268L, true, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + Set expectedRegionPropertyIds = new LinkedHashSet<>(); + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { + expectedRegionPropertyIds.add(testRegionPropertyId); + } + assertEquals(expectedRegionPropertyIds, regionsDataManager.getRegionPropertyIds()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + // no precondition tests + + } + + @Test + @UnitTestMethod(target = RegionsDataManager.class, name = "getRegionPropertyValue", args = { RegionId.class, + RegionPropertyId.class }) + public void testGetRegionPropertyValue() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + Map expectedPropertyValues = new LinkedHashMap<>(); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + + for (TestRegionId testRegionId : TestRegionId.values()) { + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { + + Object regionPropertyValue = regionsDataManager.getRegionPropertyValue(testRegionId, + testRegionPropertyId); + MultiKey multiKey = new MultiKey(testRegionId, testRegionPropertyId); + expectedPropertyValues.put(multiKey, regionPropertyValue); + } + } + })); + + // show that changes to the property values properly reflect the + // previous values + + for (int i = 1; i < 300; i++) { + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(i, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + TestRegionId testRegionId = TestRegionId.getRandomRegionId(randomGenerator); + TestRegionPropertyId testRegionPropertyId = TestRegionPropertyId + .getRandomMutableRegionPropertyId(randomGenerator); + + // show that the property has the correct value + MultiKey multiKey = new MultiKey(testRegionId, testRegionPropertyId); + Object expectedPropertyValue = expectedPropertyValues.get(multiKey); + + Object actualPropertyValue = regionsDataManager.getRegionPropertyValue(testRegionId, + testRegionPropertyId); + assertEquals(expectedPropertyValue, actualPropertyValue); + + Object newPropertyValue = testRegionPropertyId.getRandomPropertyValue(randomGenerator); + regionsDataManager.setRegionPropertyValue(testRegionId, testRegionPropertyId, newPropertyValue); + expectedPropertyValues.put(multiKey, newPropertyValue); + + })); + } + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = RegionsTestPluginFactory.factory(0, 8784099691519492811L, true, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition check: if the region id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(0, 468427930601885944L, true, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + RegionPropertyId knownRegionPropertyId = TestRegionPropertyId.REGION_PROPERTY_1_BOOLEAN_MUTABLE; + regionsDataManager.getRegionPropertyValue(null, knownRegionPropertyId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); + + // precondition check: if the region id is not known + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(0, 6075787443228538245L, true, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + RegionId unknownRegionId = TestRegionId.getUnknownRegionId(); + RegionPropertyId knownRegionPropertyId = TestRegionPropertyId.REGION_PROPERTY_1_BOOLEAN_MUTABLE; + regionsDataManager.getRegionPropertyValue(unknownRegionId, knownRegionPropertyId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); + + // precondition check: if the region property id is null + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(0, 2997323471294141386L, true, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + RegionId knownRegionId = TestRegionId.REGION_1; + regionsDataManager.getRegionPropertyValue(knownRegionId, null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // precondition check: if the region property id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(0, 7980671049474262492L, true, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + RegionId knownRegionId = TestRegionId.REGION_1; + RegionPropertyId unknownRegionPropertyId = TestRegionPropertyId.getUnknownRegionPropertyId(); + regionsDataManager.getRegionPropertyValue(knownRegionId, unknownRegionPropertyId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = RegionsDataManager.class, name = "regionIdExists", args = { RegionId.class }) + public void testRegionIdExists() { + Factory factory = RegionsTestPluginFactory.factory(0, 8636579794186794067L, true, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + + // show that null region ids do not exist + assertFalse(regionsDataManager.regionIdExists(null)); + + // show that the region ids added do exist + for (TestRegionId testRegionId : TestRegionId.values()) { + assertTrue(regionsDataManager.regionIdExists(testRegionId)); + } + + // show that an unknown region id does not exist + assertFalse(regionsDataManager.regionIdExists(TestRegionId.getUnknownRegionId())); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = RegionsDataManager.class, name = "regionPropertyIdExists", args = { + RegionPropertyId.class }) + public void testRegionPropertyIdExists() { + Factory factory = RegionsTestPluginFactory.factory(0, 3797498566412748237L, true, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + + // show that the property ids exist + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { + assertTrue(regionsDataManager.regionPropertyIdExists(testRegionPropertyId)); + } + + // show that null references return false + assertFalse(regionsDataManager.regionPropertyIdExists(null)); + + // show that unknown region property ids return false + assertFalse(regionsDataManager.regionPropertyIdExists(TestRegionPropertyId.getUnknownRegionPropertyId())); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = RegionsDataManager.class, name = "setPersonRegion", args = { PersonId.class, + RegionId.class }) + public void testSetPersonRegion() { + int numberOfPeople = 30; + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create some containers for movement observations + List recievedObservations = new ArrayList<>(); + List expectedObservations = new ArrayList<>(); + + /* + * Have the observer agent observe all movements and record those observations + */ + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + for (TestRegionId testRegionId : TestRegionId.values()) { + EventFilter eventFilter = regionsDataManager + .getEventFilterForPersonRegionUpdateEvent_ByArrivalRegion(testRegionId); + c.subscribe(eventFilter, (c2, e) -> { + recievedObservations + .add(new MultiKey(e.previousRegionId(), e.currentRegionId(), e.personId(), c2.getTime())); + }); + } + })); + + /* + * Have the mover agent move every person over time and show that each person is + * where we expect them to be + */ + pluginBuilder.addTestActorPlan("mover", new TestActorPlan(1, (c) -> { + + /* + * Make sure that there are actually people in the simulation so that test is + * actually testing something + */ + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + + List people = peopleDataManager.getPeople(); + assertTrue(people.size() > 0); + + // schedule a time for each person to be moved to a new region + double planTime = 10; + for (final PersonId personId : people) { + c.addPlan((c2) -> { + TestRegionId regionId = regionsDataManager.getPersonRegion(personId); + TestRegionId nextRegionId = regionId.next(); + regionsDataManager.setPersonRegion(personId, nextRegionId); + + // show that the person's region is updated + assertEquals(nextRegionId, regionsDataManager.getPersonRegion(personId)); + expectedObservations.add(new MultiKey(regionId, nextRegionId, personId, c2.getTime())); + + // show that the person's region arrival time is + // updated + assertEquals(c2.getTime(), regionsDataManager.getPersonRegionArrivalTime(personId)); + + }, planTime); + planTime += 5; + } + })); + + // build the plugin + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = RegionsTestPluginFactory.factory(numberOfPeople, 5655227215512656797L, true, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // show that the observations were correct + assertEquals(expectedObservations.size(), recievedObservations.size()); + assertEquals(new LinkedHashSet<>(expectedObservations), new LinkedHashSet<>(recievedObservations)); + + /* + * precondition test: if the person id is null + */ + factory = RegionsTestPluginFactory.factory(numberOfPeople, 9048586333860290178L, true, (c) -> { + // Select a person at random from the simulation and create a person + // id outside of the simulation + + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + List people = peopleDataManager.getPeople(); + PersonId personId = people.get(randomGenerator.nextInt(people.size())); + + // establish the person's current region and next region + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + TestRegionId currentRegionId = regionsDataManager.getPersonRegion(personId); + TestRegionId nextRegionId = currentRegionId.next(); + + ContractException contractException = assertThrows(ContractException.class, + () -> regionsDataManager.setPersonRegion(null, nextRegionId)); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* + * precondition test: if the person id is unknown + */ + factory = RegionsTestPluginFactory.factory(numberOfPeople, 6693022571477538917L, true, (c) -> { + // Select a person at random from the simulation and create a person + // id outside of the simulation + + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + List people = peopleDataManager.getPeople(); + PersonId personId = people.get(randomGenerator.nextInt(people.size())); + PersonId badPersonId = new PersonId(people.size()); + + // establish the person's current region and next region + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + TestRegionId currentRegionId = regionsDataManager.getPersonRegion(personId); + TestRegionId nextRegionId = currentRegionId.next(); + + ContractException contractException = assertThrows(ContractException.class, + () -> regionsDataManager.setPersonRegion(badPersonId, nextRegionId)); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* + * precondition test: if the region id is null + */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(numberOfPeople, 1385204599279421266L, true, (c) -> { + // Select a person at random from the simulation and create a + // person + // id outside of the simulation + + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + List people = peopleDataManager.getPeople(); + PersonId personId = people.get(randomGenerator.nextInt(people.size())); + + // establish the person's current region and next region + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + + // + regionsDataManager.setPersonRegion(personId, null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); + + /* + * precondition test: if the region id is unknown + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(numberOfPeople, 6025662871362676118L, true, (c) -> { + // Select a person at random from the simulation and create a + // person + // id outside of the simulation + + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + List people = peopleDataManager.getPeople(); + PersonId personId = people.get(randomGenerator.nextInt(people.size())); + + // establish the person's current region and next region + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + + // create a non-existent region id + RegionId unknownRegionId = TestRegionId.getUnknownRegionId(); + + regionsDataManager.setPersonRegion(personId, unknownRegionId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = RegionsDataManager.class, name = "setRegionPropertyValue", args = { RegionId.class, + RegionPropertyId.class, Object.class }) + public void testSetRegionPropertyValue() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create containers to hold actual and expected observations + List actualObservations = new ArrayList<>(); + List expectedObservations = new ArrayList<>(); + + // Have the observer agent start observations record them + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + EventFilter eventFilter = regionsDataManager + .getEventFilterForRegionPropertyUpdateEvent(TestRegionId.REGION_1, + TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations + .add(new MultiKey(c2.getTime(), e.regionId(), e.regionPropertyId(), e.currentPropertyValue())); + }); + + eventFilter = regionsDataManager.getEventFilterForRegionPropertyUpdateEvent(TestRegionId.REGION_2, + TestRegionPropertyId.REGION_PROPERTY_3_DOUBLE_MUTABLE); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations + .add(new MultiKey(c2.getTime(), e.regionId(), e.regionPropertyId(), e.currentPropertyValue())); + }); + + })); + + // Have the update agent make various region property updates over + // time + pluginBuilder.addTestActorPlan("update", new TestActorPlan(0, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + for (int i = 0; i < 10; i++) { + c.addPlan((c2) -> { + StochasticsDataManager stochasticsDataManager2 = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator2 = stochasticsDataManager2.getRandomGenerator(); + RegionsDataManager regionsDataManager = c2.getDataManager(RegionsDataManager.class); + Integer newValue = randomGenerator2.nextInt(); + regionsDataManager.setRegionPropertyValue(TestRegionId.REGION_1, + TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE, newValue); + Integer actualValue = regionsDataManager.getRegionPropertyValue(TestRegionId.REGION_1, + TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE); + assertEquals(newValue, actualValue); + expectedObservations.add(new MultiKey(c2.getTime(), TestRegionId.REGION_1, + TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE, newValue)); + }, randomGenerator.nextDouble() * 1000); + } + + for (int i = 0; i < 10; i++) { + c.addPlan((c2) -> { + StochasticsDataManager stochasticsDataManager2 = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator2 = stochasticsDataManager2.getRandomGenerator(); + RegionsDataManager regionsDataManager = c2.getDataManager(RegionsDataManager.class); + Double newValue = randomGenerator2.nextDouble(); + regionsDataManager.setRegionPropertyValue(TestRegionId.REGION_2, + TestRegionPropertyId.REGION_PROPERTY_3_DOUBLE_MUTABLE, newValue); + Double actualValue = regionsDataManager.getRegionPropertyValue(TestRegionId.REGION_2, + TestRegionPropertyId.REGION_PROPERTY_3_DOUBLE_MUTABLE); + assertEquals(newValue, actualValue); + expectedObservations.add(new MultiKey(c2.getTime(), TestRegionId.REGION_2, + TestRegionPropertyId.REGION_PROPERTY_3_DOUBLE_MUTABLE, newValue)); + }, randomGenerator.nextDouble() * 1000); + } + + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = RegionsTestPluginFactory.factory(0, 4630021532130673951L, true, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // show that the observed changes match expectations + assertEquals(expectedObservations.size(), actualObservations.size()); + + assertEquals(new LinkedHashSet<>(expectedObservations), new LinkedHashSet<>(actualObservations)); + + // precondition check: if the region id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(0, 7347707922069273812L, true, (c) -> { + RegionPropertyId regionPropertyId = TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE; + Object propertyValue = 67; + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + regionsDataManager.setRegionPropertyValue(null, regionPropertyId, propertyValue); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); + + // precondition check: if the region id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(0, 3075330757105736185L, true, (c) -> { + RegionId unknownRegionId = TestRegionId.getUnknownRegionId(); + RegionPropertyId regionPropertyId = TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE; + Object propertyValue = 67; + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + regionsDataManager.setRegionPropertyValue(unknownRegionId, regionPropertyId, propertyValue); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); + + // precondition check: if the region property id is null + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(0, 4169934733913962790L, true, (c) -> { + RegionId regionId = TestRegionId.REGION_1; + Object propertyValue = 67; + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + regionsDataManager.setRegionPropertyValue(regionId, null, propertyValue); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // precondition check: if the region property id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(0, 5578070775436119166L, true, (c) -> { + RegionId regionId = TestRegionId.REGION_1; + RegionPropertyId unknownRegionPropertyId = TestRegionPropertyId.getUnknownRegionPropertyId(); + Object propertyValue = 67; + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + regionsDataManager.setRegionPropertyValue(regionId, unknownRegionPropertyId, propertyValue); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + // precondition check: if the region property value is null + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(0, 217279748753596418L, true, (c) -> { + RegionId regionId = TestRegionId.REGION_1; + RegionPropertyId regionPropertyId = TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE; + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + regionsDataManager.setRegionPropertyValue(regionId, regionPropertyId, null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_VALUE, contractException.getErrorType()); + + // precondition check: if the region property value is incompatible with + // the defined type for the property + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(0, 7043526072670323223L, true, (c) -> { + RegionId regionId = TestRegionId.REGION_1; + RegionPropertyId regionPropertyId = TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE; + Object incompatiblePropertyValue = "incompatible value"; + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + regionsDataManager.setRegionPropertyValue(regionId, regionPropertyId, incompatiblePropertyValue); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); + + // precondition check: if the region id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(0, 8501593854721316109L, true, (c) -> { + RegionId unknownRegionId = TestRegionId.getUnknownRegionId(); + RegionPropertyId regionPropertyId = TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE; + Object propertyValue = 67; + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + regionsDataManager.setRegionPropertyValue(unknownRegionId, regionPropertyId, propertyValue); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); + + // precondition check: if the region property value is null + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(0, 6977487076968608944L, true, (c) -> { + RegionId regionId = TestRegionId.REGION_1; + RegionPropertyId immutableRegionPropertyId = TestRegionPropertyId.REGION_PROPERTY_5_INTEGER_IMMUTABLE; + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + regionsDataManager.setRegionPropertyValue(regionId, immutableRegionPropertyId, null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_VALUE, contractException.getErrorType()); + + } + + /* + * Returns a region plugin data with 1) a randomized tracking policy, 2) a + * randomized set of region properties, 3) randomized set of regions and 4) a + * randomized set of person region assignments + */ + private static RegionsPluginData getRandomizedRegionsPluginData(int populationSize, + RandomGenerator randomGenerator) { + + RegionsPluginData.Builder regionPluginBuilder = RegionsPluginData.builder(); + + // pick about half of the test regions, with at least one selected + boolean firstRegionAdded = false; + List selectedRegions = new ArrayList<>(); + for (TestRegionId regionId : TestRegionId.values()) { + if (firstRegionAdded) { + if (randomGenerator.nextBoolean()) { + selectedRegions.add(regionId); + + } + } else { + firstRegionAdded = true; + selectedRegions.add(regionId); + } + } + for (TestRegionId regionId : selectedRegions) { + regionPluginBuilder.addRegion(regionId); + } + + // pick about half of the properties and assign non-default values to + // about half of the regions + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { + if (randomGenerator.nextBoolean()) { + PropertyDefinition propertyDefinition = testRegionPropertyId.getPropertyDefinition(); + regionPluginBuilder.defineRegionProperty(testRegionPropertyId, propertyDefinition); + boolean noDefaultValuePresent = propertyDefinition.getDefaultValue().isEmpty(); + for (TestRegionId regionId : selectedRegions) { + if (noDefaultValuePresent || randomGenerator.nextBoolean()) { + regionPluginBuilder.setRegionPropertyValue(regionId, testRegionPropertyId, + testRegionPropertyId.getRandomPropertyValue(randomGenerator)); + } + } + } + } + boolean trackTimes = randomGenerator.nextBoolean(); + regionPluginBuilder.setPersonRegionArrivalTracking(trackTimes); + + for (int i = 0; i < populationSize; i++) { + PersonId personId = new PersonId(i); + RegionId regionId = selectedRegions.get(randomGenerator.nextInt(selectedRegions.size())); + if (trackTimes) { + regionPluginBuilder.addPerson(personId, regionId, 0.0); + } else { + regionPluginBuilder.addPerson(personId, regionId); + } + } + + return regionPluginBuilder.build(); + } + + /** + * Demonstrates that the data manager's initial state reflects its plugin data + */ + @Test + @UnitTestMethod(target = RegionsDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testStateInitialization() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4454658950052475227L); + int populationSize = 30; + + /* + * Run 10 iterations showing that randomly generated region plugin data + * instances are properly reflected in the initial state of the regions data + * manager + */ + for (int i = 0; i < 10; i++) { + + // Build the people plugin with the starting population + PeoplePluginData peoplePluginData = PeoplePluginData.builder()// + .addPersonRange(new PersonRange(0, populationSize - 1))// + .build(); + Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(peoplePluginData); + + // Build a region plugin with randomized regions plugin data + RegionsPluginData regionsPluginData = getRandomizedRegionsPluginData(populationSize, randomGenerator); + Plugin regionsPlugin = RegionsPlugin.builder().setRegionsPluginData(regionsPluginData).getRegionsPlugin(); + + // Build the test plugin + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + /* + * Add a single actor that will demonstrate that every aspect of the regions + * plugin data is reflected in the state of the regions data manager + */ + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + // show that the initial state of the region data manager + // matches + // the state of the region plugin data + + assertEquals(regionsPluginData.getRegionIds(), regionsDataManager.getRegionIds()); + assertEquals(regionsPluginData.getPersonRegionArrivalTrackingPolicy(), + regionsDataManager.regionArrivalsAreTracked()); + assertEquals(regionsPluginData.getRegionPropertyIds(), regionsDataManager.getRegionPropertyIds()); + for (RegionPropertyId regionPropertyId : regionsPluginData.getRegionPropertyIds()) { + PropertyDefinition expectedPropertyDefinition = regionsPluginData + .getRegionPropertyDefinition(regionPropertyId); + PropertyDefinition actualPropertyDefinition = regionsDataManager + .getRegionPropertyDefinition(regionPropertyId); + assertEquals(expectedPropertyDefinition, actualPropertyDefinition); + } + + for (RegionId regionId : regionsPluginData.getRegionIds()) { + Map expectedPropertyValues = regionsPluginData + .getRegionPropertyValues(regionId); + for (RegionPropertyId regionPropertyId : regionsPluginData.getRegionPropertyIds()) { + Object expectedValue = expectedPropertyValues.get(regionPropertyId); + if (expectedValue == null) { + expectedValue = regionsPluginData.getRegionPropertyDefinition(regionPropertyId) + .getDefaultValue().get(); + } + Object actualValue = regionsDataManager.getRegionPropertyValue(regionId, regionPropertyId); + assertEquals(expectedValue, actualValue); + } + } + + List people = peopleDataManager.getPeople(); + for (PersonId personId : people) { + RegionId expectedRegionId = regionsPluginData.getPersonRegion(personId).get(); + RegionId actualRegionId = regionsDataManager.getPersonRegion(personId); + assertEquals(expectedRegionId, actualRegionId); + + Optional optional = regionsPluginData.getPersonRegionArrivalTime(personId); + if (optional.isPresent()) { + Double expectedTime = optional.get(); + double actualTime = regionsDataManager.getPersonRegionArrivalTime(personId); + assertEquals(expectedTime, actualTime); + } + } + + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + // Run the simulation + TestSimulation.builder()// + .addPlugin(testPlugin)// + .addPlugin(peoplePlugin)// + .addPlugin(regionsPlugin)// + .build()// + .execute(); + } + + /* + * precondition test: if a person in the people plugin does not have an assigned + * region id in the region plugin data + */ + ContractException contractException = assertThrows(ContractException.class, () -> { + PeoplePluginData peoplePluginData = PeoplePluginData.builder()// + .addPersonRange(new PersonRange(0, 1))// + .build(); + Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(peoplePluginData); + + RegionsPluginData regionsPluginData = RegionsPluginData.builder()// + .addRegion(TestRegionId.REGION_1)// + .addPerson(new PersonId(0), TestRegionId.REGION_1)// + .build(); + Plugin regionsPlugin = RegionsPlugin.builder().setRegionsPluginData(regionsPluginData).getRegionsPlugin(); + + Simulation.builder().addPlugin(peoplePlugin).addPlugin(regionsPlugin).build().execute(); + + }); + assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); + + /* + * precondition test: if a person's region arrival time exceeds the current + * simulation time + */ + + contractException = assertThrows(ContractException.class, () -> { + PeoplePluginData peoplePluginData = PeoplePluginData.builder()// + .addPersonRange(new PersonRange(0, 1))// + .build(); + Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(peoplePluginData); + + RegionsPluginData regionsPluginData = RegionsPluginData.builder()// + .addRegion(TestRegionId.REGION_1)// + .setPersonRegionArrivalTracking(true)// + .addPerson(new PersonId(0), TestRegionId.REGION_1, 7.7)// + .build(); + Plugin regionsPlugin = RegionsPlugin.builder().setRegionsPluginData(regionsPluginData).getRegionsPlugin(); + + SimulationState simulationState = SimulationState.builder()// + .setStartTime(2.4)// + .build(); + + Simulation.builder()// + .setSimulationState(simulationState)// + .addPlugin(peoplePlugin)// + .addPlugin(regionsPlugin)// + .build()// + .execute(); + + }); + assertEquals(RegionError.REGION_ARRIVAL_TIME_EXCEEDS_SIM_TIME, contractException.getErrorType()); + + /* + * precondition test: if the regions plugin data contains information for an + * unknown person id + */ + contractException = assertThrows(ContractException.class, () -> { + PeoplePluginData peoplePluginData = PeoplePluginData.builder().build(); + Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(peoplePluginData); + + RegionsPluginData regionsPluginData = RegionsPluginData.builder()// + .addRegion(TestRegionId.REGION_1)// + .addPerson(new PersonId(0), TestRegionId.REGION_1)// + .build(); + Plugin regionsPlugin = RegionsPlugin.builder()// + .setRegionsPluginData(regionsPluginData)// + .getRegionsPlugin(); + + Simulation.builder()// + .addPlugin(peoplePlugin)// + .addPlugin(regionsPlugin)// + .build()// + .execute(); + + }); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = RegionsDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testPersonImmimentAdditionEvent() { + /* + * Have the agent create some people over time and show that each person is in + * the correct region at the correct time + */ + Factory factory = RegionsTestPluginFactory.factory(0, 8294774271110836859L, true, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + for (int i = 0; i < 100; i++) { + c.addPlan((c2) -> { + StochasticsDataManager stochasticsDataManager2 = c2.getDataManager(StochasticsDataManager.class); + RegionsDataManager regionsDataManager = c2.getDataManager(RegionsDataManager.class); + PeopleDataManager peopleDataManager = c2.getDataManager(PeopleDataManager.class); + + /* + * Generate a random region to for the new person and add the person + */ + TestRegionId randomRegionId = TestRegionId + .getRandomRegionId(stochasticsDataManager2.getRandomGenerator()); + PersonConstructionData personConstructionData = PersonConstructionData.builder().add(randomRegionId) + .build(); + PersonId personId = peopleDataManager.addPerson(personConstructionData); + + /* + * Show that the person is in the correct region with the correct region arrival + * time + */ + RegionId personRegionId = regionsDataManager.getPersonRegion(personId); + assertEquals(randomRegionId, personRegionId); + assertEquals(c2.getTime(), regionsDataManager.getPersonRegionArrivalTime(personId)); + + }, randomGenerator.nextDouble() * 1000); + } + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(0, 7737810808059858455L, true, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PersonConstructionData personConstructionData = PersonConstructionData.builder().build(); + peopleDataManager.addPerson(personConstructionData); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + + }); + assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); + + // precondition check: if the region in the event is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(0, 2879410509293373914L, true, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PersonConstructionData personConstructionData = PersonConstructionData.builder() + .add(TestRegionId.getUnknownRegionId()).build(); + peopleDataManager.addPerson(personConstructionData); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); + // precondition check: if the person id does not exist + /* + * Note : it is not possible to force the PersonDataManager to release such an + * event, so we release it from a test data manager + */ + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + pluginBuilder.addTestDataManager("dm", () -> new PassThroughDataManager()); + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + PersonConstructionData personConstructionData = PersonConstructionData.builder().add(TestRegionId.REGION_1) + .build(); + PersonImminentAdditionEvent personImminentAdditionEvent = new PersonImminentAdditionEvent( + new PersonId(10000), personConstructionData); + PassThroughDataManager passThroughDataManager = c.getDataManager(PassThroughDataManager.class); + passThroughDataManager.passThrough(personImminentAdditionEvent); + })); + + TestPluginData testPluginData1 = pluginBuilder.build(); + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(0, 5311711819224332248L, true, testPluginData1); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + // precondition check: if the person was previously added + /* + * Note : it is not possible to force the PersonDataManager to release such an + * event, so we release it from a test data manager + */ + pluginBuilder = TestPluginData.builder(); + pluginBuilder.addTestDataManager("dm", () -> new PassThroughDataManager()); + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PersonConstructionData personConstructionData = PersonConstructionData.builder().add(TestRegionId.REGION_1) + .build(); + PersonId personId = peopleDataManager.addPerson(personConstructionData); + + PersonImminentAdditionEvent personImminentAdditionEvent = new PersonImminentAdditionEvent(personId, + personConstructionData); + PassThroughDataManager passThroughDataManager = c.getDataManager(PassThroughDataManager.class); + passThroughDataManager.passThrough(personImminentAdditionEvent); + + })); + + pluginBuilder.addPluginDependency(PeoplePluginId.PLUGIN_ID); + TestPluginData testPluginData2 = pluginBuilder.build(); + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(0, 5824136557013438265L, true, testPluginData2); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(RegionError.DUPLICATE_PERSON_ADDITION, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = RegionsDataManager.class, name = "defineRegionProperty", args = { + RegionPropertyDefinitionInitialization.class }) + public void testDefineRegionProperty() { + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + RegionPropertyId regionPropertyId_1 = TestRegionPropertyId.getUnknownRegionPropertyId(); + RegionPropertyId regionPropertyId_2 = TestRegionPropertyId.getUnknownRegionPropertyId(); + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // add an observer + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + c.subscribe(EventFilter.builder(RegionPropertyDefinitionEvent.class).build(), (c2, e) -> { + MultiKey multiKey = new MultiKey(c2.getTime(), e.regionPropertyId()); + actualObservations.add(multiKey); + }); + })); + + // have an actor define property 1 + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + assertFalse(regionsDataManager.regionPropertyIdExists(regionPropertyId_1)); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class) + .setDefaultValue(55).build(); + RegionPropertyDefinitionInitialization.Builder propertyBuilder = RegionPropertyDefinitionInitialization + .builder(); + propertyBuilder.setRegionPropertyId(regionPropertyId_1).setPropertyDefinition(propertyDefinition); + Set regionIds = regionsDataManager.getRegionIds(); + assertFalse(regionIds.isEmpty()); + int value = 0; + Map expectedValues = new LinkedHashMap<>(); + for (RegionId regionId : regionIds) { + propertyBuilder.addPropertyValue(regionId, value); + expectedValues.put(regionId, value); + value++; + } + + RegionPropertyDefinitionInitialization regionPropertyDefinitionInitialization = propertyBuilder.build(); + + regionsDataManager.defineRegionProperty(regionPropertyDefinitionInitialization); + assertTrue(regionsDataManager.regionPropertyIdExists(regionPropertyId_1)); + assertTrue(regionsDataManager.getRegionPropertyIds().contains(regionPropertyId_1)); + PropertyDefinition actualPropertyDefinition = regionsDataManager + .getRegionPropertyDefinition(regionPropertyId_1); + assertEquals(propertyDefinition, actualPropertyDefinition); + MultiKey multiKey = new MultiKey(c.getTime(), regionPropertyId_1); + expectedObservations.add(multiKey); + + for (RegionId regionId : regionIds) { + Integer expectedValue = expectedValues.get(regionId); + Integer actualValue = regionsDataManager.getRegionPropertyValue(regionId, regionPropertyId_1); + assertEquals(expectedValue, actualValue); + } + + })); + + // have an actor define property 2 having no default property + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + Set regionIds = regionsDataManager.getRegionIds(); + assertFalse(regionIds.isEmpty()); + + assertFalse(regionsDataManager.regionPropertyIdExists(regionPropertyId_2)); + String defaultValue = "default value"; + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(String.class) + .setDefaultValue(defaultValue).build(); + RegionPropertyDefinitionInitialization regionPropertyDefinitionInitialization = RegionPropertyDefinitionInitialization + .builder().setRegionPropertyId(regionPropertyId_2).setPropertyDefinition(propertyDefinition) + .build(); + regionsDataManager.defineRegionProperty(regionPropertyDefinitionInitialization); + assertTrue(regionsDataManager.regionPropertyIdExists(regionPropertyId_2)); + assertTrue(regionsDataManager.getRegionPropertyIds().contains(regionPropertyId_2)); + PropertyDefinition actualPropertyDefinition = regionsDataManager + .getRegionPropertyDefinition(regionPropertyId_2); + assertEquals(propertyDefinition, actualPropertyDefinition); + MultiKey multiKey = new MultiKey(c.getTime(), regionPropertyId_2); + expectedObservations.add(multiKey); + for (RegionId regionId : regionIds) { + String actualValue = regionsDataManager.getRegionPropertyValue(regionId, regionPropertyId_2); + assertEquals(defaultValue, actualValue); + } + + })); + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(3, (c) -> { + assertFalse(expectedObservations.isEmpty()); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = RegionsTestPluginFactory.factory(0, 6410427420030580842L, true, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* + * precondition test: if the region property definition initialization is null + */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(0, 755408328420621219L, true, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + regionsDataManager.defineRegionProperty(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(RegionError.NULL_REGION_PROPERTY_DEFINITION_INITIALIZATION, contractException.getErrorType()); + + /* + * precondition test: if the region property is already defined + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(0, 1524991526094322535L, true, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + PropertyDefinition propertyDefinition = PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(7)// + .build(); + + RegionPropertyDefinitionInitialization regionPropertyDefinitionInitialization = // + RegionPropertyDefinitionInitialization.builder()// + .setPropertyDefinition(propertyDefinition)// + .setRegionPropertyId(TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE)// + .build(); + regionsDataManager.defineRegionProperty(regionPropertyDefinitionInitialization); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.DUPLICATE_PROPERTY_DEFINITION, contractException.getErrorType()); + + /* + * precondition test: if the region property definition has no default and a + * property value for some region is missing from the + * RegionPropertyDefinitionInitialization + * + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(0, 737227361871382193L, true, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + PropertyDefinition propertyDefinition = PropertyDefinition.builder()// + .setType(Integer.class)// + .build(); + + RegionPropertyDefinitionInitialization regionPropertyDefinitionInitialization = // + RegionPropertyDefinitionInitialization.builder()// + .setPropertyDefinition(propertyDefinition)// + .setRegionPropertyId(TestRegionPropertyId.getUnknownRegionPropertyId())// + .build(); + + regionsDataManager.defineRegionProperty(regionPropertyDefinitionInitialization); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = RegionsDataManager.class, name = "addRegion", args = { RegionConstructionData.class }) + public void testAddRegion() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + c.subscribe(EventFilter.builder(RegionAdditionEvent.class).build(), (c2, e) -> { + MultiKey multiKey = new MultiKey(c2.getTime(), e.getRegionId()); + actualObservations.add(multiKey); + }); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + RegionId newRegionId = TestRegionId.getUnknownRegionId(); + RegionConstructionData.Builder builder = RegionConstructionData.builder().setRegionId(newRegionId);// + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.getPropertiesWithoutDefaultValues()) { + builder.setRegionPropertyValue(testRegionPropertyId, + testRegionPropertyId.getRandomPropertyValue(randomGenerator)); + } + RegionConstructionData regionConstructionData = builder.build(); + regionsDataManager.addRegion(regionConstructionData); + MultiKey multiKey = new MultiKey(c.getTime(), newRegionId); + expectedObservations.add(multiKey); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + + RegionId newRegionId = TestRegionId.getUnknownRegionId(); + RegionConstructionData.Builder builder = RegionConstructionData.builder().setRegionId(newRegionId);// + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.getPropertiesWithoutDefaultValues()) { + builder.setRegionPropertyValue(testRegionPropertyId, + testRegionPropertyId.getRandomPropertyValue(randomGenerator)); + } + RegionConstructionData regionConstructionData = builder.build(); + regionsDataManager.addRegion(regionConstructionData); + MultiKey multiKey = new MultiKey(c.getTime(), newRegionId); + expectedObservations.add(multiKey); + })); + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(3, (c) -> { + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = RegionsTestPluginFactory.factory(0, 4801681059718243112L, true, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* + * precondition test: if the region construction data is null + */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(0, 1930072318129921567L, true, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + regionsDataManager.addRegion(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(RegionError.NULL_REGION_CONSTRUCTION_DATA, contractException.getErrorType()); + + /* + * precondition test: if the region is already present + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(0, 4107332213003089045L, true, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + RegionConstructionData regionConstructionData = RegionConstructionData.builder() + .setRegionId(TestRegionId.REGION_1).build(); + regionsDataManager.addRegion(regionConstructionData); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(RegionError.DUPLICATE_REGION_ID, contractException.getErrorType()); + + /* + * precondition test: if not all region properties have default values + */ + contractException = assertThrows(ContractException.class, () -> { + testConsumerWithNoDefaultRegionProperties(6895625301110154531L, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + RegionConstructionData regionConstructionData = RegionConstructionData.builder() + .setRegionId(TestRegionId.getUnknownRegionId()).build(); + regionsDataManager.addRegion(regionConstructionData); + }); + }); + assertEquals(PropertyError.INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT, contractException.getErrorType()); + + } + + /* + * Executes the simulation with each of the region properties defined without + * default values + */ + private static void testConsumerWithNoDefaultRegionProperties(long seed, Consumer consumer) { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, consumer)); + TestPluginData testPluginData = pluginBuilder.build(); + + // add the region plugin + RegionsPluginData.Builder regionPluginBuilder = RegionsPluginData.builder(); + for (TestRegionId regionId : TestRegionId.values()) { + regionPluginBuilder.addRegion(regionId); + } + + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { + // create a property definition with no default + PropertyDefinition propertyDefinition = testRegionPropertyId.getPropertyDefinition(); + propertyDefinition = PropertyDefinition.builder().setType(propertyDefinition.getType()).build(); + regionPluginBuilder.defineRegionProperty(testRegionPropertyId, propertyDefinition); + for (TestRegionId regionId : TestRegionId.values()) { + regionPluginBuilder.setRegionPropertyValue(regionId, testRegionPropertyId, + testRegionPropertyId.getRandomPropertyValue(randomGenerator)); + } + } + + RegionsPluginData regionsPluginData = regionPluginBuilder.build(); + + Factory factory = RegionsTestPluginFactory.factory(0, seed, true, testPluginData)// + .setRegionsPluginData(regionsPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = RegionsDataManager.class, name = "getEventFilterForPersonRegionUpdateEvent_ByArrivalRegion", args = { + RegionId.class }) + public void testGetEventFilterForPersonRegionUpdateEvent_ByArrivalRegion() { + + int numberOfPeople = 30; + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create some containers for movement observations + List recievedObservations = new ArrayList<>(); + List expectedObservations = new ArrayList<>(); + + Set selectedRegions = new LinkedHashSet<>(); + selectedRegions.add(TestRegionId.REGION_1); + selectedRegions.add(TestRegionId.REGION_4); + selectedRegions.add(TestRegionId.REGION_6); + + /* + * Have the observer agent observe all movements into the selected regions + * observations + */ + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + + for (TestRegionId testRegionId : selectedRegions) { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + EventFilter eventFilter = regionsDataManager + .getEventFilterForPersonRegionUpdateEvent_ByArrivalRegion(testRegionId); + c.subscribe(eventFilter, (c2, e) -> { + recievedObservations + .add(new MultiKey(e.previousRegionId(), e.currentRegionId(), e.personId(), c2.getTime())); + }); + } + })); + + /* + * Have the mover agent move every person over time + */ + pluginBuilder.addTestActorPlan("mover", new TestActorPlan(1, (c) -> { + + /* + * Make sure that there are actually people in the simulation so that test is + * actually testing something + */ + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + + List people = peopleDataManager.getPeople(); + assertTrue(people.size() > 0); + + // schedule a time for each person to be moved to a new region + double planTime = 10; + for (final PersonId personId : people) { + c.addPlan((c2) -> { + TestRegionId regionId = regionsDataManager.getPersonRegion(personId); + TestRegionId nextRegionId = regionId.next(); + regionsDataManager.setPersonRegion(personId, nextRegionId); + + if (selectedRegions.contains(nextRegionId)) { + expectedObservations.add(new MultiKey(regionId, nextRegionId, personId, c2.getTime())); + } + }, planTime); + planTime += 5; + } + })); + + // build the plugin + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = RegionsTestPluginFactory.factory(numberOfPeople, 6280260397394362229L, true, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // show that the observations were correct + assertEquals(expectedObservations.size(), recievedObservations.size()); + assertEquals(new LinkedHashSet<>(expectedObservations), new LinkedHashSet<>(recievedObservations)); + + /* + * precondition test: if the region id is null + */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(numberOfPeople, 8703868236194395945L, true, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + regionsDataManager.getEventFilterForPersonRegionUpdateEvent_ByArrivalRegion(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); + /* + * precondition test: if the region id is not known + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(numberOfPeople, 1521124301443522213L, true, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + regionsDataManager + .getEventFilterForPersonRegionUpdateEvent_ByArrivalRegion(TestRegionId.getUnknownRegionId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = RegionsDataManager.class, name = "getEventFilterForPersonRegionUpdateEvent_ByDepartureRegion", args = { + RegionId.class }) + public void testGetEventFilterForPersonRegionUpdateEvent_ByDepartureRegion() { + + int numberOfPeople = 30; + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create some containers for movement observations + List recievedObservations = new ArrayList<>(); + List expectedObservations = new ArrayList<>(); + + Set selectedRegions = new LinkedHashSet<>(); + selectedRegions.add(TestRegionId.REGION_1); + selectedRegions.add(TestRegionId.REGION_2); + selectedRegions.add(TestRegionId.REGION_3); + selectedRegions.add(TestRegionId.REGION_6); + + /* + * Have the observer agent observe all movements out of the selected regions + * observations + */ + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + + for (TestRegionId testRegionId : selectedRegions) { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + EventFilter eventFilter = regionsDataManager + .getEventFilterForPersonRegionUpdateEvent_ByDepartureRegion(testRegionId); + c.subscribe(eventFilter, (c2, e) -> { + recievedObservations + .add(new MultiKey(e.previousRegionId(), e.currentRegionId(), e.personId(), c2.getTime())); + }); + } + })); + + /* + * Have the mover agent move every person over time + */ + pluginBuilder.addTestActorPlan("mover", new TestActorPlan(1, (c) -> { + + /* + * Make sure that there are actually people in the simulation so that test is + * actually testing something + */ + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + + List people = peopleDataManager.getPeople(); + assertTrue(people.size() > 0); + + // schedule a time for each person to be moved to a new region + double planTime = 10; + for (final PersonId personId : people) { + c.addPlan((c2) -> { + TestRegionId regionId = regionsDataManager.getPersonRegion(personId); + TestRegionId nextRegionId = regionId.next(); + regionsDataManager.setPersonRegion(personId, nextRegionId); + + if (selectedRegions.contains(regionId)) { + expectedObservations.add(new MultiKey(regionId, nextRegionId, personId, c2.getTime())); + } + }, planTime); + planTime += 5; + } + })); + + // build the plugin + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = RegionsTestPluginFactory.factory(numberOfPeople, 5906547765098032882L, true, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // show that the observations were correct + assertEquals(expectedObservations.size(), recievedObservations.size()); + assertEquals(new LinkedHashSet<>(expectedObservations), new LinkedHashSet<>(recievedObservations)); + + /* + * precondition test: if the region id is null + */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(numberOfPeople, 5941332064278474841L, true, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + regionsDataManager.getEventFilterForPersonRegionUpdateEvent_ByDepartureRegion(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); + /* + * precondition test: if the region id is not known + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(numberOfPeople, 5981948058533294963L, true, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + regionsDataManager + .getEventFilterForPersonRegionUpdateEvent_ByDepartureRegion(TestRegionId.getUnknownRegionId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = RegionsDataManager.class, name = "getEventFilterForPersonRegionUpdateEvent", args = { + PersonId.class }) + public void testGetEventFilterForPersonRegionUpdateEvent_Person() { + int numberOfPeople = 30; + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create some containers for movement observations + List recievedObservations = new ArrayList<>(); + List expectedObservations = new ArrayList<>(); + + Set selectedPeople = new LinkedHashSet<>(); + + /* + * Have the observer agent observe all movements out of the selected regions + * observations + */ + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + List people = peopleDataManager.getPeople(); + for (PersonId personId : people) { + if (randomGenerator.nextBoolean()) { + selectedPeople.add(personId); + EventFilter eventFilter = regionsDataManager + .getEventFilterForPersonRegionUpdateEvent(personId); + c.subscribe(eventFilter, (c2, e) -> { + recievedObservations.add( + new MultiKey(e.previousRegionId(), e.currentRegionId(), e.personId(), c2.getTime())); + }); + } + } + })); + + /* + * Have the mover agent move every person over time + */ + pluginBuilder.addTestActorPlan("mover", new TestActorPlan(1, (c) -> { + + /* + * Make sure that there are actually people in the simulation so that test is + * actually testing something + */ + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + + List people = peopleDataManager.getPeople(); + assertTrue(people.size() > 0); + + // schedule a time for each person to be moved to a new region + double planTime = 10; + for (final PersonId personId : people) { + c.addPlan((c2) -> { + TestRegionId regionId = regionsDataManager.getPersonRegion(personId); + TestRegionId nextRegionId = regionId.next(); + regionsDataManager.setPersonRegion(personId, nextRegionId); + + if (selectedPeople.contains(personId)) { + expectedObservations.add(new MultiKey(regionId, nextRegionId, personId, c2.getTime())); + } + }, planTime); + planTime += 5; + } + })); + + // build the plugin + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = RegionsTestPluginFactory.factory(numberOfPeople, 3786801901191355144L, true, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // show that the observations were correct + assertEquals(expectedObservations.size(), recievedObservations.size()); + assertEquals(new LinkedHashSet<>(expectedObservations), new LinkedHashSet<>(recievedObservations)); + + /* + * precondition test: if the person id is null + */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(numberOfPeople, 4504604454474342921L, true, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + PersonId nullPersonId = null; + regionsDataManager.getEventFilterForPersonRegionUpdateEvent(nullPersonId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + /* + * precondition test: if the person id is not known + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(numberOfPeople, 1166492228021587827L, true, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + regionsDataManager.getEventFilterForPersonRegionUpdateEvent(new PersonId(1000000)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = RegionsDataManager.class, name = "getEventFilterForPersonRegionUpdateEvent", args = {}) + public void testGetEventFilterForPersonRegionUpdateEvent() { + + int numberOfPeople = 30; + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create some containers for movement observations + List recievedObservations = new ArrayList<>(); + List expectedObservations = new ArrayList<>(); + + /* + * Have the observer agent observe all movements out of the selected regions + * observations + */ + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + + List people = peopleDataManager.getPeople(); + for (PersonId personId : people) { + EventFilter eventFilter = regionsDataManager + .getEventFilterForPersonRegionUpdateEvent(personId); + c.subscribe(eventFilter, (c2, e) -> { + recievedObservations + .add(new MultiKey(e.previousRegionId(), e.currentRegionId(), e.personId(), c2.getTime())); + }); + } + })); + + /* + * Have the mover agent move every person over time + */ + pluginBuilder.addTestActorPlan("mover", new TestActorPlan(1, (c) -> { + + /* + * Make sure that there are actually people in the simulation so that test is + * actually testing something + */ + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + + List people = peopleDataManager.getPeople(); + assertTrue(people.size() > 0); + + // schedule a time for each person to be moved to a new region + double planTime = 10; + for (final PersonId personId : people) { + c.addPlan((c2) -> { + TestRegionId regionId = regionsDataManager.getPersonRegion(personId); + TestRegionId nextRegionId = regionId.next(); + regionsDataManager.setPersonRegion(personId, nextRegionId); + expectedObservations.add(new MultiKey(regionId, nextRegionId, personId, c2.getTime())); + }, planTime); + planTime += 5; + } + })); + + // build the plugin + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = RegionsTestPluginFactory.factory(numberOfPeople, 8773677547139261431L, true, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // show that the observations were correct + assertEquals(expectedObservations.size(), recievedObservations.size()); + assertEquals(new LinkedHashSet<>(expectedObservations), new LinkedHashSet<>(recievedObservations)); + + } + + @Test + @UnitTestMethod(target = RegionsDataManager.class, name = "getEventFilterForRegionPropertyUpdateEvent", args = { + RegionPropertyId.class }) + public void testGetEventFilterForRegionPropertyUpdateEvent_Region() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create containers to hold actual and expected observations + Set actualObservations = new LinkedHashSet<>(); + Set expectedObservations = new LinkedHashSet<>(); + + Set selectedPropertyIds = new LinkedHashSet<>(); + selectedPropertyIds.add(TestRegionPropertyId.REGION_PROPERTY_1_BOOLEAN_MUTABLE); + selectedPropertyIds.add(TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE); + + // Have the observer agent observe updates to the selected properties + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + for (TestRegionPropertyId testRegionPropertyId : selectedPropertyIds) { + EventFilter eventFilter = regionsDataManager + .getEventFilterForRegionPropertyUpdateEvent(testRegionPropertyId); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add( + new MultiKey(c2.getTime(), e.regionId(), e.regionPropertyId(), e.currentPropertyValue())); + }); + } + })); + + int comparisonDay = 100; + + // Have the update agent make various region property updates over + // time + pluginBuilder.addTestActorPlan("update", new TestActorPlan(0, (c) -> { + + for (int i = 1; i < comparisonDay; i++) { + c.addPlan((c2) -> { + StochasticsDataManager stochasticsDataManager2 = c2.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager2.getRandomGenerator(); + RegionsDataManager regionsDataManager = c2.getDataManager(RegionsDataManager.class); + + TestRegionId regionId = TestRegionId.getRandomRegionId(randomGenerator); + TestRegionPropertyId regionPropertyId = TestRegionPropertyId + .getRandomMutableRegionPropertyId(randomGenerator); + Object propertyValue = regionPropertyId.getRandomPropertyValue(randomGenerator); + + regionsDataManager.setRegionPropertyValue(regionId, regionPropertyId, propertyValue); + + if (selectedPropertyIds.contains(regionPropertyId)) { + expectedObservations.add(new MultiKey(c2.getTime(), regionId, regionPropertyId, propertyValue)); + } + + }, i); + } + })); + + // Have the observer agent observe show observed changes match + // expectations + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(comparisonDay, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = RegionsTestPluginFactory.factory(0, 1827237237983764002L, true, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition check: if the region property id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(0, 2294490256521547918L, true, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + regionsDataManager.getEventFilterForRegionPropertyUpdateEvent(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // precondition check: if the region property id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(0, 4878569785353296577L, true, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + regionsDataManager + .getEventFilterForRegionPropertyUpdateEvent(TestRegionPropertyId.getUnknownRegionPropertyId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = RegionsDataManager.class, name = "getEventFilterForRegionPropertyUpdateEvent", args = { + RegionId.class, RegionPropertyId.class }) + public void getEventFilterForRegionPropertyUpdateEvent_Region_Property() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create containers to hold actual and expected observations + Set actualObservations = new LinkedHashSet<>(); + Set expectedObservations = new LinkedHashSet<>(); + + Set> selectedRegionPropertyPairs = new LinkedHashSet<>(); + selectedRegionPropertyPairs + .add(new Pair<>(TestRegionId.REGION_1, TestRegionPropertyId.REGION_PROPERTY_1_BOOLEAN_MUTABLE)); + selectedRegionPropertyPairs + .add(new Pair<>(TestRegionId.REGION_2, TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE)); + selectedRegionPropertyPairs + .add(new Pair<>(TestRegionId.REGION_3, TestRegionPropertyId.REGION_PROPERTY_3_DOUBLE_MUTABLE)); + selectedRegionPropertyPairs + .add(new Pair<>(TestRegionId.REGION_4, TestRegionPropertyId.REGION_PROPERTY_1_BOOLEAN_MUTABLE)); + selectedRegionPropertyPairs + .add(new Pair<>(TestRegionId.REGION_5, TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE)); + selectedRegionPropertyPairs + .add(new Pair<>(TestRegionId.REGION_6, TestRegionPropertyId.REGION_PROPERTY_3_DOUBLE_MUTABLE)); + + // Have the observer agent observe updates to the selected + // region/property pairs + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + for (Pair pair : selectedRegionPropertyPairs) { + RegionId regionId = pair.getFirst(); + RegionPropertyId regionPropertyId = pair.getSecond(); + EventFilter eventFilter = regionsDataManager + .getEventFilterForRegionPropertyUpdateEvent(regionId, regionPropertyId); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add( + new MultiKey(c2.getTime(), e.regionId(), e.regionPropertyId(), e.currentPropertyValue())); + }); + } + })); + + int comparisonDay = 100; + + // Have the update agent make various region property updates over + // time + pluginBuilder.addTestActorPlan("update", new TestActorPlan(0, (c) -> { + + for (int i = 1; i < comparisonDay; i++) { + c.addPlan((c2) -> { + StochasticsDataManager stochasticsDataManager2 = c2.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager2.getRandomGenerator(); + RegionsDataManager regionsDataManager = c2.getDataManager(RegionsDataManager.class); + + TestRegionId regionId = TestRegionId.getRandomRegionId(randomGenerator); + TestRegionPropertyId regionPropertyId = TestRegionPropertyId + .getRandomMutableRegionPropertyId(randomGenerator); + Object propertyValue = regionPropertyId.getRandomPropertyValue(randomGenerator); + + regionsDataManager.setRegionPropertyValue(regionId, regionPropertyId, propertyValue); + + Pair pair = new Pair<>(regionId, regionPropertyId); + if (selectedRegionPropertyPairs.contains(pair)) { + expectedObservations.add(new MultiKey(c2.getTime(), regionId, regionPropertyId, propertyValue)); + } + + }, i); + } + })); + + // Have the observer agent observe show observed changes match + // expectations + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(comparisonDay, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = RegionsTestPluginFactory.factory(0, 7132294759338470890L, true, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition check: if the region property id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(0, 5168071523034596869L, true, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + regionsDataManager.getEventFilterForRegionPropertyUpdateEvent(TestRegionId.REGION_1, null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // precondition check: if the region property id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(0, 5851898172389262566L, true, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + regionsDataManager.getEventFilterForRegionPropertyUpdateEvent(TestRegionId.REGION_1, + TestRegionPropertyId.getUnknownRegionPropertyId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + // precondition check: if the region id is null + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(0, 3683702073309702135L, true, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + regionsDataManager.getEventFilterForRegionPropertyUpdateEvent(null, + TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); + + // precondition check: if the region id is unknown + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(0, 6706349084351695058L, true, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + regionsDataManager.getEventFilterForRegionPropertyUpdateEvent(TestRegionId.getUnknownRegionId(), + TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = RegionsDataManager.class, name = "getEventFilterForRegionPropertyUpdateEvent", args = {}) + public void testGetEventFilterForRegionPropertyUpdateEvent() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create containers to hold actual and expected observations + Set actualObservations = new LinkedHashSet<>(); + Set expectedObservations = new LinkedHashSet<>(); + + // Have the observer agent observe updates to the selected + // region/property pairs + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + EventFilter eventFilter = regionsDataManager + .getEventFilterForRegionPropertyUpdateEvent(); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations + .add(new MultiKey(c2.getTime(), e.regionId(), e.regionPropertyId(), e.currentPropertyValue())); + }); + + })); + + int comparisonDay = 100; + + // Have the update agent make various region property updates over + // time + pluginBuilder.addTestActorPlan("update", new TestActorPlan(0, (c) -> { + + for (int i = 1; i < comparisonDay; i++) { + c.addPlan((c2) -> { + StochasticsDataManager stochasticsDataManager2 = c2.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager2.getRandomGenerator(); + RegionsDataManager regionsDataManager = c2.getDataManager(RegionsDataManager.class); + + TestRegionId regionId = TestRegionId.getRandomRegionId(randomGenerator); + TestRegionPropertyId regionPropertyId = TestRegionPropertyId + .getRandomMutableRegionPropertyId(randomGenerator); + Object propertyValue = regionPropertyId.getRandomPropertyValue(randomGenerator); + + regionsDataManager.setRegionPropertyValue(regionId, regionPropertyId, propertyValue); + + expectedObservations.add(new MultiKey(c2.getTime(), regionId, regionPropertyId, propertyValue)); + + }, i); + } + })); + + // Have the observer agent observe show observed changes match + // expectations + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(comparisonDay, (c) -> { + assertTrue(expectedObservations.size() > 0); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = RegionsTestPluginFactory.factory(0, 6300334142182919392L, true, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = RegionsDataManager.class, name = "getEventFilterForRegionAdditionEvent", args = {}) + public void testGetEventFilterForRegionAdditionEvent() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + EventFilter eventFilter = regionsDataManager.getEventFilterForRegionAdditionEvent(); + c.subscribe(eventFilter, (c2, e) -> { + MultiKey multiKey = new MultiKey(c2.getTime(), e.getRegionId()); + actualObservations.add(multiKey); + }); + })); + + int comparisonDay = 10; + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + + for (int i = 1; i < comparisonDay; i++) { + c.addPlan((c2) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + RegionId newRegionId = TestRegionId.getUnknownRegionId(); + RegionConstructionData.Builder builder = RegionConstructionData.builder().setRegionId(newRegionId);// + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId + .getPropertiesWithoutDefaultValues()) { + builder.setRegionPropertyValue(testRegionPropertyId, + testRegionPropertyId.getRandomPropertyValue(randomGenerator)); + } + RegionConstructionData regionConstructionData = builder.build(); + regionsDataManager.addRegion(regionConstructionData); + MultiKey multiKey = new MultiKey(c.getTime(), newRegionId); + expectedObservations.add(multiKey); + }, i); + } + + })); + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(comparisonDay, (c) -> { + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = RegionsTestPluginFactory.factory(0, 6272247954838684078L, true, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = RegionsDataManager.class, name = "getEventFilterForRegionPropertyDefinitionEvent", args = {}) + public void testGetEventFilterForRegionPropertyDefinitionEvent() { + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // add an observer + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + EventFilter eventFilter = regionsDataManager + .getEventFilterForRegionPropertyDefinitionEvent(); + c.subscribe(eventFilter, (c2, e) -> { + MultiKey multiKey = new MultiKey(c2.getTime(), e.regionPropertyId()); + actualObservations.add(multiKey); + }); + })); + + int comparisonDay = 10; + + // have an actor define property 1 + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + + for (int i = 1; i < comparisonDay; i++) { + c.addPlan((c2) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class) + .setDefaultValue(55).build(); + RegionPropertyDefinitionInitialization.Builder propertyBuilder = RegionPropertyDefinitionInitialization + .builder(); + RegionPropertyId regionPropertyId = TestRegionPropertyId.getUnknownRegionPropertyId(); + propertyBuilder.setRegionPropertyId(regionPropertyId).setPropertyDefinition(propertyDefinition); + Set regionIds = regionsDataManager.getRegionIds(); + assertFalse(regionIds.isEmpty()); + int value = 0; + Map expectedValues = new LinkedHashMap<>(); + for (RegionId regionId : regionIds) { + propertyBuilder.addPropertyValue(regionId, value); + expectedValues.put(regionId, value); + value++; + } + RegionPropertyDefinitionInitialization regionPropertyDefinitionInitialization = propertyBuilder + .build(); + + regionsDataManager.defineRegionProperty(regionPropertyDefinitionInitialization); + + MultiKey multiKey = new MultiKey(c.getTime(), regionPropertyId); + expectedObservations.add(multiKey); + + }, i); + } + + })); + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(3, (c) -> { + assertFalse(expectedObservations.isEmpty()); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = RegionsTestPluginFactory.factory(0, 1033803161227361793L, true, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + private static class PassThroughDataManager extends TestDataManager { + private DataManagerContext dataManagerContext; + + public void init(DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + this.dataManagerContext = dataManagerContext; + } + + public void passThrough(Event event) { + dataManagerContext.releaseMutationEvent(event); + } + } + + @Test + @UnitTestMethod(target = RegionsDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testPersonRemovalEvent() { + + MutableInteger pId = new MutableInteger(); + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + /* + * Have the actor add a person and then remove it. There will be a delay of 0 + * time for the person to be removed. + */ + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + + PersonConstructionData personConstructionData = PersonConstructionData.builder().add(TestRegionId.REGION_1) + .build(); + PersonId personId = peopleDataManager.addPerson(personConstructionData); + pId.setValue(personId.getValue()); + + int regionPopulationCount = regionsDataManager.getRegionPopulationCount(TestRegionId.REGION_1); + assertEquals(1, regionPopulationCount); + assertEquals(TestRegionId.REGION_1, regionsDataManager.getPersonRegion(personId)); + peopleDataManager.removePerson(personId); + + })); + + /* + * Have the actor show that the person is no longer present + * + */ + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(3, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + PersonId personId = new PersonId(pId.getValue()); + + int regionPopulationCount = regionsDataManager.getRegionPopulationCount(TestRegionId.REGION_1); + assertEquals(0, regionPopulationCount); + ContractException contractException = assertThrows(ContractException.class, + () -> regionsDataManager.getPersonRegion(personId)); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + })); + + // build action plugin + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = RegionsTestPluginFactory.factory(0, 163202760371564041L, true, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition test: if the person id is unknown + /* + * Note : it is not possible to force the PersonDataManager to release such an + * event, so we release it from a data manager + */ + pluginBuilder.addTestDataManager("dm", () -> new PassThroughDataManager()); + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + PersonRemovalEvent personRemovalEvent = new PersonRemovalEvent(new PersonId(1000)); + PassThroughDataManager passThroughDataManager = c.getDataManager(PassThroughDataManager.class); + ContractException contractException = assertThrows(ContractException.class, + () -> passThroughDataManager.passThrough(personRemovalEvent)); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + })); + testPluginData = pluginBuilder.build(); + factory = RegionsTestPluginFactory.factory(0, 2314376445339268382L, true, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* + * Precondition test: if the person was previously removed. The exception will + * be thrown after the consumer fully executes and will bubble up and out of the + * simulation instance being executed and thus must be captured outside of the + * static test methods. Note : it is not possible to force the PersonDataManager + * to release such an event, so we release it from an actor + */ + pluginBuilder.addTestDataManager("dm", () -> new TestDataManager()); + pluginBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(0, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + PersonId personId = peopleDataManager + .addPerson(PersonConstructionData.builder().add(TestRegionId.REGION_1).build()); + peopleDataManager.removePerson(personId); + PersonRemovalEvent personRemovalEvent = new PersonRemovalEvent(personId); + c.releaseObservationEvent(personRemovalEvent); + + })); + pluginBuilder.addPluginDependency(PeoplePluginId.PLUGIN_ID); + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = RegionsTestPluginFactory.factory(0, 3490172254703369545L, true, pluginBuilder.build()); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + } + + /** + * Demonstrates that the data manager exhibits run continuity. The state of the + * data manager is not effected by repeatedly starting and stopping the + * simulation. + */ + @Test + @UnitTestMethod(target = RegionsDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testStateContinuity() { + + /* + * Note that we are not testing the content of the plugin datas -- that is + * covered by the other state tests. We show here only that the resulting plugin + * data state is the same without regard to how we break up the run. + */ + + Set pluginDatas = new LinkedHashSet<>(); + pluginDatas.add(testStateContinuity(1)); + pluginDatas.add(testStateContinuity(5)); + pluginDatas.add(testStateContinuity(10)); + + assertEquals(1, pluginDatas.size()); + + } + + /* + * Returns the regions plugin data resulting from several region related events + * over several days. Attempts to stop and start the simulation by the given + * number of increments. + */ + private String testStateContinuity(int incrementCount) { + String result = null; + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4401967199145357368L); + + /* + * Build the RunContinuityPluginData with several context consumers that will + * add regions and people, define region properties, etc; + */ + RunContinuityPluginData.Builder continuityBuilder = RunContinuityPluginData.builder(); + + // add two regions + continuityBuilder.addContextConsumer(0.5, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + + RegionConstructionData regionConstructionData = RegionConstructionData.builder()// + .setRegionId(TestRegionId.REGION_1)// + .build(); + + regionsDataManager.addRegion(regionConstructionData); + + regionConstructionData = RegionConstructionData.builder()// + .setRegionId(TestRegionId.REGION_2)// + .build(); + regionsDataManager.addRegion(regionConstructionData); + }); + + // add a few people + continuityBuilder.addContextConsumer(1.2, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + + PersonConstructionData personConstructionData = PersonConstructionData.builder()// + .add(TestRegionId.REGION_2)// + .build(); + peopleDataManager.addPerson(personConstructionData); + peopleDataManager.addPerson(personConstructionData); + peopleDataManager.addPerson(personConstructionData); + + personConstructionData = PersonConstructionData.builder()// + .add(TestRegionId.REGION_1)// + .build(); + peopleDataManager.addPerson(personConstructionData); + peopleDataManager.addPerson(personConstructionData); + + }); + + // define a few region properties + continuityBuilder.addContextConsumer(4.7, (c) -> { + + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + + TestRegionPropertyId testRegionPropertyId = TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE; + RegionPropertyDefinitionInitialization regionPropertyDefinitionInitialization = // + RegionPropertyDefinitionInitialization.builder()// + .setPropertyDefinition(testRegionPropertyId.getPropertyDefinition())// + .setRegionPropertyId(testRegionPropertyId)// + .addPropertyValue(TestRegionId.REGION_2, + testRegionPropertyId.getRandomPropertyValue(randomGenerator)) + .addPropertyValue(TestRegionId.REGION_1, + testRegionPropertyId.getRandomPropertyValue(randomGenerator))// + .build(); + + regionsDataManager.defineRegionProperty(regionPropertyDefinitionInitialization); + + testRegionPropertyId = TestRegionPropertyId.REGION_PROPERTY_3_DOUBLE_MUTABLE; + regionPropertyDefinitionInitialization = // + RegionPropertyDefinitionInitialization.builder()// + .setPropertyDefinition(testRegionPropertyId.getPropertyDefinition())// + .setRegionPropertyId(testRegionPropertyId)// + .build(); + + regionsDataManager.defineRegionProperty(regionPropertyDefinitionInitialization); + + }); + + // move some people + continuityBuilder.addContextConsumer(5.5, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + regionsDataManager.setPersonRegion(new PersonId(0), TestRegionId.REGION_2); + regionsDataManager.setPersonRegion(new PersonId(2), TestRegionId.REGION_2); + regionsDataManager.setPersonRegion(new PersonId(3), TestRegionId.REGION_1); + }); + + // update region properties + continuityBuilder.addContextConsumer(5.8, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + + TestRegionPropertyId testRegionPropertyId = TestRegionPropertyId.REGION_PROPERTY_3_DOUBLE_MUTABLE; + regionsDataManager.setRegionPropertyValue(TestRegionId.REGION_1, testRegionPropertyId, + testRegionPropertyId.getRandomPropertyValue(randomGenerator)); + regionsDataManager.setRegionPropertyValue(TestRegionId.REGION_2, testRegionPropertyId, + testRegionPropertyId.getRandomPropertyValue(randomGenerator)); + + testRegionPropertyId = TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE; + regionsDataManager.setRegionPropertyValue(TestRegionId.REGION_1, testRegionPropertyId, + testRegionPropertyId.getRandomPropertyValue(randomGenerator)); + regionsDataManager.setRegionPropertyValue(TestRegionId.REGION_2, testRegionPropertyId, + testRegionPropertyId.getRandomPropertyValue(randomGenerator)); + + c.releaseOutput(regionsDataManager.toString()); + + }); + + RunContinuityPluginData runContinuityPluginData = continuityBuilder.build(); + + // Build an empty people plugin data for time zero + PeoplePluginData peoplePluginData = PeoplePluginData.builder().build(); + + /* + * Build an empty regions plugin data with region arrival tracking turned on + */ + RegionsPluginData regionsPluginData = RegionsPluginData.builder()// + .setPersonRegionArrivalTracking(true)// + .build(); + + // build the initial simulation state data -- time starts at zero + SimulationState simulationState = SimulationState.builder().build(); + + /* + * Run the simulation in increments until all the plans in the run continuity + * plugin data have been executed + */ + double haltTime = 0; + double maxTime = Double.NEGATIVE_INFINITY; + for (Pair> pair : runContinuityPluginData.getConsumers()) { + Double time = pair.getFirst(); + maxTime = FastMath.max(maxTime, time); + } + double timeIncrement = maxTime / incrementCount; + while (!runContinuityPluginData.allPlansComplete()) { + haltTime += timeIncrement; + + // build the run continuity plugin + Plugin runContinuityPlugin = RunContinuityPlugin.builder()// + .setRunContinuityPluginData(runContinuityPluginData)// + .build(); + + // build the people plugin + Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(peoplePluginData); + + // build the regions plugin + Plugin regionsPlugin = RegionsPlugin.builder()// + .setRegionsPluginData(regionsPluginData)// + .getRegionsPlugin(); + + TestOutputConsumer outputConsumer = new TestOutputConsumer(); + + // execute the simulation so that it produces a people plugin data + Simulation simulation = Simulation.builder()// + .addPlugin(peoplePlugin)// + .addPlugin(regionsPlugin)// + .addPlugin(runContinuityPlugin)// + .setSimulationHaltTime(haltTime)// + .setRecordState(true)// + .setOutputConsumer(outputConsumer)// + .setSimulationState(simulationState)// + .build();// + simulation.execute(); + + // retrieve the people plugin data + peoplePluginData = outputConsumer.getOutputItem(PeoplePluginData.class).get(); + + // retrieve the regions plugin data + regionsPluginData = outputConsumer.getOutputItem(RegionsPluginData.class).get(); + + // retrieve the simulation state + simulationState = outputConsumer.getOutputItem(SimulationState.class).get(); + + // retrieve the run continuity plugin data + runContinuityPluginData = outputConsumer.getOutputItem(RunContinuityPluginData.class).get(); + + Optional optional = outputConsumer.getOutputItem(String.class); + if (optional.isPresent()) { + result = optional.get(); + } + + } + + assertNotNull(result); + + return result; + + } + + @Test + @UnitTestMethod(target = RegionsDataManager.class, name = "toString", args = {}) + public void testToString() { + + //we will build a custom, slightly randomized regions plugin data with 10 people, tracking times and person assignments to regions up to day 12. + int populationSize = 10; + double maxTime =12.0; + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(941941179564965001L); + Random random = new Random(randomGenerator.nextLong()); + + + RegionsPluginData.Builder regionPluginBuilder = RegionsPluginData.builder(); + + // pick about half of the test regions, with at least one selected + boolean firstRegionAdded = false; + List selectedRegions = new ArrayList<>(); + for (TestRegionId regionId : TestRegionId.values()) { + if (firstRegionAdded) { + if (randomGenerator.nextBoolean()) { + selectedRegions.add(regionId); + + } + } else { + firstRegionAdded = true; + selectedRegions.add(regionId); + } + } + for (TestRegionId regionId : selectedRegions) { + regionPluginBuilder.addRegion(regionId); + } + + Collections.shuffle(selectedRegions, random); + + // pick about half of the properties and assign non-default values to + // about half of the regions + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { + PropertyDefinition propertyDefinition = testRegionPropertyId.getPropertyDefinition(); + regionPluginBuilder.defineRegionProperty(testRegionPropertyId, propertyDefinition); + boolean noDefaultValuePresent = propertyDefinition.getDefaultValue().isEmpty(); + for (TestRegionId regionId : selectedRegions) { + if (noDefaultValuePresent || randomGenerator.nextBoolean()) { + regionPluginBuilder.setRegionPropertyValue(regionId, testRegionPropertyId, + testRegionPropertyId.getRandomPropertyValue(randomGenerator)); + } + } + } + + regionPluginBuilder.setPersonRegionArrivalTracking(true); + + for (int i = 0; i < populationSize; i++) { + PersonId personId = new PersonId(i); + RegionId regionId = selectedRegions.get(randomGenerator.nextInt(selectedRegions.size())); + regionPluginBuilder.addPerson(personId, regionId, randomGenerator.nextDouble()*maxTime); + } + + RegionsPluginData regionsPluginData = regionPluginBuilder.build(); + Plugin regionsPlugin = RegionsPlugin.builder().setRegionsPluginData(regionsPluginData).getRegionsPlugin(); + + + //build the people plugin + PeoplePluginData peoplePluginData = PeoplePluginData.builder() + .addPersonRange(new PersonRange(0, populationSize - 1)).build(); + Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(peoplePluginData); + + //build the test plugin + TestPluginData testPluginData = TestPluginData.builder().addTestActorPlan("actor", new TestActorPlan(16.0, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + String actualValue = regionsDataManager.toString(); + + //expected value verified by inspection + String expectedValue = "RegionsDataManager [regionPropertyMap={REGION_1={REGION_PROPERTY_2_INTEGER_MUTABLE=-430271448, REGION_PROPERTY_4_BOOLEAN_IMMUTABLE=true, REGION_PROPERTY_5_INTEGER_IMMUTABLE=1638462445}, REGION_5={REGION_PROPERTY_2_INTEGER_MUTABLE=-2096012126, REGION_PROPERTY_4_BOOLEAN_IMMUTABLE=false}}, regionPropertyIds=[REGION_PROPERTY_1_BOOLEAN_MUTABLE, REGION_PROPERTY_2_INTEGER_MUTABLE, REGION_PROPERTY_3_DOUBLE_MUTABLE, REGION_PROPERTY_4_BOOLEAN_IMMUTABLE, REGION_PROPERTY_5_INTEGER_IMMUTABLE, REGION_PROPERTY_6_DOUBLE_IMMUTABLE], regionPropertyDefinitions={REGION_PROPERTY_1_BOOLEAN_MUTABLE=PropertyDefinition [type=class java.lang.Boolean, propertyValuesAreMutable=true, defaultValue=false], REGION_PROPERTY_2_INTEGER_MUTABLE=PropertyDefinition [type=class java.lang.Integer, propertyValuesAreMutable=true, defaultValue=null], REGION_PROPERTY_3_DOUBLE_MUTABLE=PropertyDefinition [type=class java.lang.Double, propertyValuesAreMutable=true, defaultValue=0.0], REGION_PROPERTY_4_BOOLEAN_IMMUTABLE=PropertyDefinition [type=class java.lang.Boolean, propertyValuesAreMutable=false, defaultValue=false], REGION_PROPERTY_5_INTEGER_IMMUTABLE=PropertyDefinition [type=class java.lang.Integer, propertyValuesAreMutable=false, defaultValue=0], REGION_PROPERTY_6_DOUBLE_IMMUTABLE=PropertyDefinition [type=class java.lang.Double, propertyValuesAreMutable=false, defaultValue=0.0]}, regionPopulationRecordMap={REGION_1=MutableInteger [value=7], REGION_5=MutableInteger [value=3]}, regionToIndexMap={REGION_1=1, REGION_5=2}, indexToRegionMap=[null, REGION_1, REGION_5], regionValues=IntValueContainer [subTypeArray=ByteArray [values=[0=1, 1=1, 2=1, 3=2, 4=2, 5=1, 6=1, 7=2, 8=1, 9=1], defaultValue=0]], regionArrivalTimes=DoubleValueContainer [values=[0=2.9821860216608798, 1=5.560111134433853, 2=0.3295343049347226, 3=2.169800585537338, 4=7.7567431186187985, 5=10.575349160340982, 6=2.2839626031304086, 7=11.011496468201365, 8=11.009859589871265, 9=9.25188153533573], defaultValue=0.0]]"; + assertEquals(expectedValue, actualValue); + + })).build(); + + + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + //have the simulation start after the last person arrived in their region, but before the test actor plan + SimulationState simulationState = SimulationState.builder().setStartTime(15.0).build(); + + TestSimulation.builder()// + .addPlugin(regionsPlugin)// + .addPlugin(peoplePlugin)// + .addPlugin(testPlugin)// + .setSimulationState(simulationState)// + .build()// + .execute(); + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/datamanagers/AT_RegionsPluginData.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/datamanagers/AT_RegionsPluginData.java new file mode 100644 index 000000000..ed48a12e8 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/datamanagers/AT_RegionsPluginData.java @@ -0,0 +1,957 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Random; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.apache.commons.math3.util.FastMath; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionError; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.TestRegionId; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.TestRegionPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTag; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +/** + * Test unit for {@linkplain RegionsPluginData}. Tests for + * RegionPluginData.Builder are limited to precondition tests and are otherwise + * covered via the class level tests. Note that the builder does not impose any + * ordering on the invocation of its methods and many validation tests are + * deferred to the build invocation. + * + * + */ +public class AT_RegionsPluginData { + + @Test + @UnitTestMethod(target = RegionsPluginData.class, name = "builder", args = {}) + public void testBuilder() { + // show that we can create a builder + assertNotNull(RegionsPluginData.builder()); + } + + @Test + @UnitTestMethod(target = RegionsPluginData.class, name = "getRegionIds", args = {}) + public void testGetRegionIds() { + // use the test region ids + Set expectedRegionIds = new LinkedHashSet<>(); + for (TestRegionId testRegionId : TestRegionId.values()) { + expectedRegionIds.add(testRegionId); + } + + // show that the regions added are present + RegionsPluginData.Builder builder = RegionsPluginData.builder(); + for (TestRegionId testRegionId : TestRegionId.values()) { + builder.addRegion(testRegionId); + } + RegionsPluginData regionsPluginData = builder.build(); + + Set actualRegionIds = regionsPluginData.getRegionIds(); + assertEquals(expectedRegionIds, actualRegionIds); + } + + @Test + @UnitTestMethod(target = RegionsPluginData.class, name = "getRegionPropertyDefinition", args = { + RegionPropertyId.class }) + public void testGetRegionPropertyDefinition() { + RegionsPluginData.Builder builder = RegionsPluginData.builder(); + /* + * Place the various properties defined in TestRegionPropertyId into the builder + * and associate them with distinct property definitions. Each property + * definition will differ by its initial value. + */ + for (TestRegionId testRegionId : TestRegionId.values()) { + builder.addRegion(testRegionId); + + } + + int defaultValue = 0; + Map expectedDefinitions = new LinkedHashMap<>(); + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class) + .setDefaultValue(defaultValue++).build(); + expectedDefinitions.put(testRegionPropertyId, propertyDefinition); + builder.defineRegionProperty(testRegionPropertyId, propertyDefinition); + } + + // build the region initial data + RegionsPluginData regionsPluginData = builder.build(); + + /* + * Retrieve all of the property definitions in the region initial data and place + * them in a map for comparison. + */ + Map actualDefinitions = new LinkedHashMap<>(); + + Set regionPropertyIds = regionsPluginData.getRegionPropertyIds(); + for (RegionPropertyId regionPropertyId : regionPropertyIds) { + PropertyDefinition propertyDefinition = regionsPluginData.getRegionPropertyDefinition(regionPropertyId); + actualDefinitions.put(regionPropertyId, propertyDefinition); + } + + // show that the two maps are equal + assertEquals(expectedDefinitions, actualDefinitions); + + // precondition tests + + // if the region property id is null + ContractException contractException = assertThrows(ContractException.class, + () -> regionsPluginData.getRegionPropertyDefinition(null)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // if the region property id is unknown + contractException = assertThrows(ContractException.class, + () -> regionsPluginData.getRegionPropertyDefinition(TestRegionPropertyId.getUnknownRegionPropertyId())); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = RegionsPluginData.class, name = "getRegionPropertyIds", args = {}) + public void testGetRegionPropertyIds() { + RegionsPluginData.Builder builder = RegionsPluginData.builder(); + /* + * Place the various region/property pairs defined in TestRegionId into the + * builder and associate them with distinct property definitions. + */ + + Set expectedPropertyIds = new LinkedHashSet<>(); + for (TestRegionId testRegionId : TestRegionId.values()) { + builder.addRegion(testRegionId); + + } + + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class) + .setDefaultValue(0).build(); + builder.defineRegionProperty(testRegionPropertyId, propertyDefinition); + expectedPropertyIds.add(testRegionPropertyId); + } + + // build the region initial data + RegionsPluginData regionsPluginData = builder.build(); + + /* + * Retrieve all of the property defintions in the region inital data and place + * them in a map for comparison. + */ + Set actualPropertyIds = regionsPluginData.getRegionPropertyIds(); + + // show that the two sets are equal + assertEquals(expectedPropertyIds, actualPropertyIds); + + // no precondition tests + + } + + @Test + @UnitTestMethod(target = RegionsPluginData.class, name = "getRegionPropertyValues", args = {}) + public void testGetRegionPropertyValues() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1348577218631439602L); + Map> expectedValues = new LinkedHashMap<>(); + RegionsPluginData.Builder builder = RegionsPluginData.builder(); + + for (TestRegionId testRegionId : TestRegionId.values()) { + builder.addRegion(testRegionId); + } + + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { + PropertyDefinition propertyDefinition = testRegionPropertyId.getPropertyDefinition(); + builder.defineRegionProperty(testRegionPropertyId, propertyDefinition); + } + + /* + * set about half of the properties for each region for those properties that + * have a default value + */ + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.getPropertiesWithDefaultValues()) { + for (TestRegionId testRegionId : TestRegionId.values()) { + if (randomGenerator.nextBoolean()) { + Object value = testRegionPropertyId.getRandomPropertyValue(randomGenerator); + builder.setRegionPropertyValue(testRegionId, testRegionPropertyId, value); + Map map = expectedValues.get(testRegionId); + if (map == null) { + map = new LinkedHashMap<>(); + expectedValues.put(testRegionId, map); + } + map.put(testRegionPropertyId, value); + + } + } + } + + /* + * set all of the properties for each region for those properties that do not + * have a default value + */ + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.getPropertiesWithoutDefaultValues()) { + for (TestRegionId testRegionId : TestRegionId.values()) { + Object value = testRegionPropertyId.getRandomPropertyValue(randomGenerator); + builder.setRegionPropertyValue(testRegionId, testRegionPropertyId, value); + Map map = expectedValues.get(testRegionId); + if (map == null) { + map = new LinkedHashMap<>(); + expectedValues.put(testRegionId, map); + } + map.put(testRegionPropertyId, value); + + } + } + + // build the region initial data + RegionsPluginData regionsPluginData = builder.build(); + + Map> actualValues = regionsPluginData.getRegionPropertyValues(); + assertEquals(expectedValues, actualValues); + + } + + @Test + @UnitTestMethod(target = RegionsPluginData.class, name = "getRegionPropertyValues", args = { RegionId.class }) + public void testGetRegionPropertyValues_RegionId() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(887285678478260177L); + + RegionsPluginData.Builder builder = RegionsPluginData.builder(); + + for (TestRegionId testRegionId : TestRegionId.values()) { + builder.addRegion(testRegionId); + } + + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { + PropertyDefinition propertyDefinition = testRegionPropertyId.getPropertyDefinition(); + builder.defineRegionProperty(testRegionPropertyId, propertyDefinition); + } + + Map> expectedPropertyValues = new LinkedHashMap<>(); + for (TestRegionId testRegionId : TestRegionId.values()) { + expectedPropertyValues.put(testRegionId, new LinkedHashMap<>()); + } + + /* + * set about half of the properties for each region for those properties that + * have a default value + */ + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.getPropertiesWithDefaultValues()) { + for (TestRegionId testRegionId : TestRegionId.values()) { + if (randomGenerator.nextBoolean()) { + Object value = testRegionPropertyId.getRandomPropertyValue(randomGenerator); + builder.setRegionPropertyValue(testRegionId, testRegionPropertyId, value); + expectedPropertyValues.get(testRegionId).put(testRegionPropertyId, value); + } + } + } + + /* + * set all of the properties for each region for those properties that do not + * have a default value + */ + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.getPropertiesWithoutDefaultValues()) { + for (TestRegionId testRegionId : TestRegionId.values()) { + Object value = testRegionPropertyId.getRandomPropertyValue(randomGenerator); + builder.setRegionPropertyValue(testRegionId, testRegionPropertyId, value); + expectedPropertyValues.get(testRegionId).put(testRegionPropertyId, value); + } + } + + // build the region initial data + RegionsPluginData regionsPluginData = builder.build(); + + /* + * Retrieve all of the property values in the region inital data and place them + * in a map for comparison. + */ + for (RegionId regionId : regionsPluginData.getRegionIds()) { + Map expectedMap = expectedPropertyValues.get(regionId); + Map actualMap = regionsPluginData.getRegionPropertyValues(regionId); + // show that the two maps are equal + assertEquals(expectedMap, actualMap); + } + + // precondition test: if the region id is null + ContractException contractException = assertThrows(ContractException.class, + () -> regionsPluginData.getRegionPropertyValues(null)); + assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); + + // precondition test: if the region id is unknown + contractException = assertThrows(ContractException.class, + () -> regionsPluginData.getRegionPropertyValues(TestRegionId.getUnknownRegionId())); + assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = RegionsPluginData.class, name = "getPersonRegionArrivalTrackingPolicy", args = {}) + public void testGetPersonRegionArrivalTrackingPolicy() { + + RegionsPluginData regionsPluginData = RegionsPluginData.builder()// + .setPersonRegionArrivalTracking(true)// + .build();// + assertEquals(true, regionsPluginData.getPersonRegionArrivalTrackingPolicy()); + + regionsPluginData = RegionsPluginData.builder()// + .setPersonRegionArrivalTracking(false)// + .build();// + assertEquals(false, regionsPluginData.getPersonRegionArrivalTrackingPolicy()); + + } + + @Test + @UnitTestMethod(target = RegionsPluginData.Builder.class, name = "build", args = {}) + public void testBuild() { + + // show the builder does not return null + assertNotNull(RegionsPluginData.builder().build()); + + // precondition tests + + /* + * if a region property value was associated with a region id that was not + * properly added with an initial agent behavior. + */ + RegionPropertyId regionPropertyId = TestRegionPropertyId.REGION_PROPERTY_1_BOOLEAN_MUTABLE; + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setDefaultValue(0).setType(Integer.class) + .build(); + ContractException contractException = assertThrows(ContractException.class, () -> { + RegionsPluginData.builder().defineRegionProperty(regionPropertyId, propertyDefinition)// + .setRegionPropertyValue(TestRegionId.REGION_1, regionPropertyId, 5)// + .build(); + }); + assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); + + /* + * if a region property value was associated with a region property id that was + * not defined + */ + contractException = assertThrows(ContractException.class, () -> { + RegionsPluginData.builder()// + .addRegion(TestRegionId.REGION_1)// + .setRegionPropertyValue(TestRegionId.REGION_1, regionPropertyId, 5)// + .build(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + /* + * if a region property value was associated with a region and region property + * id that is incompatible with the corresponding property definition. + */ + contractException = assertThrows(ContractException.class, () -> { + RegionsPluginData.builder()// + .addRegion(TestRegionId.REGION_1)// + .defineRegionProperty(regionPropertyId, propertyDefinition)// + .setRegionPropertyValue(TestRegionId.REGION_1, regionPropertyId, "invalid value")// + .build();// + }); + assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); + + /* + * if a region property definition does not have a default value and there are + * no property values added to replace that default. + */ + + contractException = assertThrows(ContractException.class, () -> { + PropertyDefinition def = PropertyDefinition.builder().setType(Double.class).build(); + + RegionsPluginData.builder()// + .addRegion(TestRegionId.REGION_1)// + .defineRegionProperty(regionPropertyId, def)// + .build();// + }); + assertEquals(PropertyError.INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT, contractException.getErrorType()); + + /* + * if a person region arrival data was collected, but the policy for region + * arrival tracking is false + */ + contractException = assertThrows(ContractException.class, () -> { + RegionsPluginData.builder()// + .addRegion(TestRegionId.REGION_1)// + .setPersonRegionArrivalTracking(false)// + .addPerson(new PersonId(0), TestRegionId.REGION_1, 0.0)// + .build();// + }); + assertEquals(RegionError.PERSON_ARRIVAL_DATA_PRESENT, contractException.getErrorType()); + + /* + * if the policy for region arrival tracking is set to true, but region arrival + * times are missing + */ + + contractException = assertThrows(ContractException.class, () -> { + RegionsPluginData.builder()// + .addRegion(TestRegionId.REGION_1)// + .setPersonRegionArrivalTracking(true)// + .addPerson(new PersonId(0), TestRegionId.REGION_1)// + .build(); + }); + assertEquals(RegionError.MISSING_PERSON_ARRIVAL_DATA, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = RegionsPluginData.Builder.class, name = "defineRegionProperty", args = { + RegionPropertyId.class, PropertyDefinition.class }) + public void testDefineRegionProperty() { + RegionsPluginData.Builder builder = RegionsPluginData.builder(); + + RegionPropertyId regionPropertyId = TestRegionPropertyId.REGION_PROPERTY_1_BOOLEAN_MUTABLE; + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setDefaultValue(9).setType(Integer.class) + .build(); + + // if the region property id is null + ContractException contractException = assertThrows(ContractException.class, + () -> builder.defineRegionProperty(null, propertyDefinition)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // if the property definition is null + contractException = assertThrows(ContractException.class, + () -> builder.defineRegionProperty(regionPropertyId, null)); + assertEquals(PropertyError.NULL_PROPERTY_DEFINITION, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = RegionsPluginData.Builder.class, name = "addRegion", args = { RegionId.class }) + public void testAddRegion() { + RegionsPluginData.Builder builder = RegionsPluginData.builder(); + + // if the region id is null + ContractException contractException = assertThrows(ContractException.class, () -> builder.addRegion(null)); + assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = RegionsPluginData.class, name = "getPersonRegionArrivalTime", args = { PersonId.class }) + public void testGetPersonRegionArrivalTime() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8722606929396924838L); + + RegionsPluginData.Builder builder = RegionsPluginData.builder(); + builder.setPersonRegionArrivalTracking(true); + for (TestRegionId testRegionId : TestRegionId.values()) { + builder.addRegion(testRegionId); + } + + Map expectedRegionArrivalTimes = new LinkedHashMap<>(); + int maxId = Integer.MIN_VALUE; + for (int i = 0; i < 20; i++) { + PersonId personId = new PersonId(3 * i + 5); + maxId = FastMath.max(maxId, personId.getValue()); + RegionId regionId = TestRegionId.getRandomRegionId(randomGenerator); + double time = randomGenerator.nextDouble(); + builder.addPerson(personId, regionId, time); + expectedRegionArrivalTimes.put(personId, time); + } + maxId++; + + RegionsPluginData regionsPluginData = builder.build(); + + for (int i = 0; i < maxId; i++) { + PersonId personId = new PersonId(i); + Optional optional = regionsPluginData.getPersonRegionArrivalTime(personId); + Double expectedTime = expectedRegionArrivalTimes.get(personId); + if (expectedTime != null) { + assertTrue(optional.isPresent()); + assertEquals(expectedTime, optional.get()); + } else { + assertTrue(optional.isEmpty()); + } + } + + // precondition test: if the person id is null + ContractException contractException = assertThrows(ContractException.class, + () -> RegionsPluginData.builder().build().getPersonRegionArrivalTime(null)); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = RegionsPluginData.class, name = "getPersonRegion", args = { PersonId.class }) + public void testGetPersonRegion() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8722606929396924838L); + + RegionsPluginData.Builder builder = RegionsPluginData.builder(); + for (TestRegionId testRegionId : TestRegionId.values()) { + builder.addRegion(testRegionId); + } + + Map expectedRegionAssignments = new LinkedHashMap<>(); + int maxId = Integer.MIN_VALUE; + for (int i = 0; i < 20; i++) { + PersonId personId = new PersonId(3 * i + 5); + maxId = FastMath.max(maxId, personId.getValue()); + RegionId regionId = TestRegionId.getRandomRegionId(randomGenerator); + builder.addPerson(personId, regionId); + expectedRegionAssignments.put(personId, regionId); + } + maxId++; + + RegionsPluginData regionsPluginData = builder.build(); + + for (int i = 0; i < maxId; i++) { + PersonId personId = new PersonId(i); + Optional optional = regionsPluginData.getPersonRegion(personId); + RegionId regionId = expectedRegionAssignments.get(personId); + if (regionId != null) { + assertTrue(optional.isPresent()); + assertEquals(regionId, optional.get()); + } else { + assertTrue(optional.isEmpty()); + } + } + + // precondition test: if the person id is null + ContractException contractException = assertThrows(ContractException.class, + () -> RegionsPluginData.builder().build().getPersonRegion(null)); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = RegionsPluginData.Builder.class, name = "setRegionPropertyValue", args = { RegionId.class, + RegionPropertyId.class, Object.class }) + public void testSetRegionPropertyValue() { + RegionsPluginData.Builder builder = RegionsPluginData.builder(); + + RegionId regionId = TestRegionId.REGION_1; + + RegionPropertyId regionPropertyId = TestRegionPropertyId.REGION_PROPERTY_1_BOOLEAN_MUTABLE; + Object validValue = 5; + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setDefaultValue(0).setType(Integer.class) + .build(); + + builder.addRegion(regionId); + builder.defineRegionProperty(regionPropertyId, propertyDefinition); + + // non-precondition tests covered by testGetRegionPropertyValue + + // if the region id is null + ContractException contractException = assertThrows(ContractException.class, + () -> builder.setRegionPropertyValue(null, regionPropertyId, validValue)); + assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); + + // if the region property id is null + contractException = assertThrows(ContractException.class, + () -> builder.setRegionPropertyValue(regionId, null, validValue)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // Note: Invalid values will not throw an exception and are caught + // during the build invocation. + } + + @Test + @UnitTestMethod(target = RegionsPluginData.Builder.class, name = "addPerson", args = { PersonId.class, + RegionId.class }, tags = { UnitTag.LOCAL_PROXY }) + public void testAddPerson_Region() { + RegionsPluginData.Builder builder = RegionsPluginData.builder(); + + PersonId personId = new PersonId(45); + RegionId regionId = TestRegionId.REGION_1; + + // non-precondition tests covered by testGetPersonRegion + + // precondition test: if the person id is null + ContractException contractException = assertThrows(ContractException.class, + () -> builder.addPerson(null, regionId)); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + // precondition test: if the region id is null + contractException = assertThrows(ContractException.class, () -> builder.addPerson(personId, null)); + assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); + + // precondition test: if other people have been added using region + // arrival times + contractException = assertThrows(ContractException.class, () -> { + builder.addPerson(personId, regionId, 2.6); + builder.addPerson(personId, regionId); + }); + assertEquals(RegionError.REGION_ARRIVAL_TIMES_MISMATCHED, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = RegionsPluginData.Builder.class, name = "addPerson", args = { PersonId.class, + RegionId.class, Double.class }, tags = { UnitTag.LOCAL_PROXY }) + public void testAddPerson_Region_Time() { + RegionsPluginData.Builder builder = RegionsPluginData.builder(); + + PersonId personId = new PersonId(45); + RegionId regionId = TestRegionId.REGION_1; + Double time = 2.6; + + // non-precondition tests covered by testGetPersonRegion + + // precondition test: if the person id is null + ContractException contractException = assertThrows(ContractException.class, + () -> builder.addPerson(null, regionId, time)); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + // precondition test: if the region id is null + contractException = assertThrows(ContractException.class, () -> builder.addPerson(personId, null, time)); + assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); + + // precondition test: if the time is null + contractException = assertThrows(ContractException.class, () -> builder.addPerson(personId, regionId, null)); + assertEquals(RegionError.NULL_TIME, contractException.getErrorType()); + + // precondition test: if the time is not finite + contractException = assertThrows(ContractException.class, + () -> builder.addPerson(personId, regionId, Double.NaN)); + assertEquals(RegionError.NON_FINITE_TIME, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, + () -> builder.addPerson(personId, regionId, Double.POSITIVE_INFINITY)); + assertEquals(RegionError.NON_FINITE_TIME, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, + () -> builder.addPerson(personId, regionId, Double.NEGATIVE_INFINITY)); + assertEquals(RegionError.NON_FINITE_TIME, contractException.getErrorType()); + + // precondition test: if other people have been added without using + // region + // arrival times + contractException = assertThrows(ContractException.class, () -> { + builder.addPerson(personId, regionId); + builder.addPerson(personId, regionId, 2.6); + }); + assertEquals(RegionError.REGION_ARRIVAL_TIMES_MISMATCHED, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = RegionsPluginData.Builder.class, name = "setPersonRegionArrivalTracking", args = { + boolean.class }) + public void testSetPersonRegionArrivalTracking() { + assertTrue(RegionsPluginData.builder().setPersonRegionArrivalTracking(true).build() + .getPersonRegionArrivalTrackingPolicy()); + assertFalse(RegionsPluginData.builder().setPersonRegionArrivalTracking(false).build() + .getPersonRegionArrivalTrackingPolicy()); + } + + @Test + @UnitTestMethod(target = RegionsPluginData.class, name = "getCloneBuilder", args = {}) + public void testGetCloneBuilder() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6712645837048772782L); + RegionsPluginData.Builder regionPluginDataBuilder = RegionsPluginData.builder(); + regionPluginDataBuilder.setPersonRegionArrivalTracking(true); + for (TestRegionId testRegionId : TestRegionId.values()) { + regionPluginDataBuilder.addRegion(testRegionId); + } + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { + regionPluginDataBuilder.defineRegionProperty(testRegionPropertyId, + testRegionPropertyId.getPropertyDefinition()); + } + for (TestRegionId testRegionId : TestRegionId.values()) { + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { + if (testRegionPropertyId.getPropertyDefinition().getDefaultValue().isEmpty() + || randomGenerator.nextBoolean()) { + Object randomPropertyValue = testRegionPropertyId.getRandomPropertyValue(randomGenerator); + regionPluginDataBuilder.setRegionPropertyValue(testRegionId, testRegionPropertyId, + randomPropertyValue); + } + } + } + int personCount = 100; + for (int i = 0; i < personCount; i++) { + PersonId personId = new PersonId(i * 2 + 5); + TestRegionId randomRegionId = TestRegionId.getRandomRegionId(randomGenerator); + regionPluginDataBuilder.addPerson(personId, randomRegionId, 0.0); + } + + RegionsPluginData regionsPluginData = regionPluginDataBuilder.build(); + + PluginData pluginData = regionsPluginData.getCloneBuilder().build(); + + // show that the clone plugin data has the correct type + assertTrue(pluginData instanceof RegionsPluginData); + RegionsPluginData cloneRegionPluginData = (RegionsPluginData) pluginData; + + // show that the two plugin datas have the same arrival tracking policy + assertEquals(regionsPluginData.getPersonRegionArrivalTrackingPolicy(), + cloneRegionPluginData.getPersonRegionArrivalTrackingPolicy()); + + // show that the two plugin datas have the same region ids + assertEquals(regionsPluginData.getRegionIds(), cloneRegionPluginData.getRegionIds()); + + // show that the two plugin datas have the same region property ids + assertEquals(regionsPluginData.getRegionPropertyIds(), cloneRegionPluginData.getRegionPropertyIds()); + + // show that the two plugin datas have the same region property + // definitions + for (RegionPropertyId regionPropertyId : regionsPluginData.getRegionPropertyIds()) { + PropertyDefinition expectedPropertyDefinition = regionsPluginData + .getRegionPropertyDefinition(regionPropertyId); + PropertyDefinition actualPropertyDefinition = cloneRegionPluginData + .getRegionPropertyDefinition(regionPropertyId); + assertEquals(expectedPropertyDefinition, actualPropertyDefinition); + } + + // show that the two plugin datas have the same region property values + for (RegionId regionId : regionsPluginData.getRegionIds()) { + Map expectedRegionPropertyValues = regionsPluginData + .getRegionPropertyValues(regionId); + Map actualRegionPropertyValues = cloneRegionPluginData + .getRegionPropertyValues(regionId); + assertEquals(expectedRegionPropertyValues, actualRegionPropertyValues); + } + + // show that the two plugin datas have the same people and region + // assignments + + int pluginPersonCount = regionsPluginData.getPersonCount(); + int clonePluginPersonCount = cloneRegionPluginData.getPersonCount(); + assertEquals(pluginPersonCount, clonePluginPersonCount); + + for (int i = 0; i < pluginPersonCount; i++) { + PersonId personId = new PersonId(i); + boolean isPresentInPluginData = regionsPluginData.getPersonRegion(personId).isPresent(); + boolean isPresentInClonePluginData = cloneRegionPluginData.getPersonRegion(personId).isPresent(); + assertEquals(isPresentInPluginData, isPresentInClonePluginData); + if (isPresentInPluginData) { + RegionId expectedRegionId = regionsPluginData.getPersonRegion(personId).get(); + RegionId actualRegionId = cloneRegionPluginData.getPersonRegion(personId).get(); + assertEquals(expectedRegionId, actualRegionId); + } + } + + } + + @Test + @UnitTestMethod(target = RegionsPluginData.class, name = "getPersonCount", args = {}) + public void testGetPersonCount() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(101704379866671191L); + for (int j = 0; j < 10; j++) { + RegionsPluginData.Builder regionPluginDataBuilder = RegionsPluginData.builder(); + regionPluginDataBuilder.setPersonRegionArrivalTracking(true); + for (TestRegionId testRegionId : TestRegionId.values()) { + regionPluginDataBuilder.addRegion(testRegionId); + } + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { + regionPluginDataBuilder.defineRegionProperty(testRegionPropertyId, + testRegionPropertyId.getPropertyDefinition()); + } + for (TestRegionId testRegionId : TestRegionId.values()) { + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { + if (testRegionPropertyId.getPropertyDefinition().getDefaultValue().isEmpty() + || randomGenerator.nextBoolean()) { + Object randomPropertyValue = testRegionPropertyId.getRandomPropertyValue(randomGenerator); + regionPluginDataBuilder.setRegionPropertyValue(testRegionId, testRegionPropertyId, + randomPropertyValue); + } + } + } + int personCount = randomGenerator.nextInt(100); + int offset = j; + for (int i = 0; i < personCount; i++) { + /* + * the offset matters in this case because the setPersonRegion method skips all + * indexes of people based on the PersonId So if you had PersonId 1 and PersonId + * 3 The internal logic will place a blank value in index 2 (where PersonId 2 + * would have been) So to acurately test this functionality, the offset must be + * added here and subtracted in the assert clause Because the offset is tied to + * the value of j, the offset will increase from 0 to 9 The effect of this is + * that the internal list will start at the offset index value instead of index + * 0 + */ + PersonId personId = new PersonId(i + offset); + TestRegionId randomRegionId = TestRegionId.getRandomRegionId(randomGenerator); + regionPluginDataBuilder.addPerson(personId, randomRegionId, 0.0); + } + + RegionsPluginData regionsPluginData = regionPluginDataBuilder.build(); + assertNotNull(regionsPluginData); + assertEquals(personCount, regionsPluginData.getPersonCount() - offset); + + } + } + + private RegionsPluginData getRandomRegionsPluginData(long seed) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + Random random = new Random(randomGenerator.nextLong()); + RegionsPluginData.Builder builder = RegionsPluginData.builder(); + + // define some region properties + List regionPropertyIds = Arrays.asList(TestRegionPropertyId.values()); + List selectedRegionPropertyIds = new ArrayList<>(); + Collections.shuffle(regionPropertyIds, random); + for (TestRegionPropertyId testRegionPropertyId : regionPropertyIds) { + selectedRegionPropertyIds.add(testRegionPropertyId); + builder.defineRegionProperty(testRegionPropertyId, testRegionPropertyId.getPropertyDefinition()); + if (random.nextDouble() < 0.25) { + break; + } + } + + // add a few regions + List regionIds = Arrays.asList(TestRegionId.values()); + List selectedRegionIds = new ArrayList<>(); + Collections.shuffle(regionIds, random); + for (TestRegionId testRegionId : regionIds) { + selectedRegionIds.add(testRegionId); + builder.addRegion(testRegionId); + if (random.nextDouble() < 0.25) { + break; + } + } + + Collections.shuffle(selectedRegionPropertyIds, random); + + // set some region property values + for (TestRegionPropertyId testRegionPropertyId : selectedRegionPropertyIds) { + Collections.shuffle(selectedRegionIds, random); + for (TestRegionId testRegionId : selectedRegionIds) { + PropertyDefinition propertyDefinition = testRegionPropertyId.getPropertyDefinition(); + Optional optional = propertyDefinition.getDefaultValue(); + if (optional.isEmpty() || randomGenerator.nextBoolean()) { + Object propertyValue = testRegionPropertyId.getRandomPropertyValue(randomGenerator); + builder.setRegionPropertyValue(testRegionId, testRegionPropertyId, propertyValue); + } + } + } + + List people = new ArrayList<>(); + int personCount = random.nextInt(10); + for (int i = 0; i < personCount; i++) { + people.add(new PersonId(2 * i + 1)); + } + boolean track = randomGenerator.nextBoolean(); + builder.setPersonRegionArrivalTracking(track); + + if (track) { + for (PersonId personId : people) { + TestRegionId testRegionId = selectedRegionIds.get(randomGenerator.nextInt(selectedRegionIds.size())); + builder.addPerson(personId, testRegionId, randomGenerator.nextDouble()); + } + } else { + for (PersonId personId : people) { + TestRegionId testRegionId = selectedRegionIds.get(randomGenerator.nextInt(selectedRegionIds.size())); + builder.addPerson(personId, testRegionId); + } + } + + return builder.build(); + } + + @Test + @UnitTestMethod(target = RegionsPluginData.class, name = "equals", args = { Object.class }) + public void testEquals() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(685858669518256073L); + + // is never equal to null + for (int i = 0; i < 30; i++) { + RegionsPluginData regionsPluginData = getRandomRegionsPluginData(randomGenerator.nextLong()); + assertFalse(regionsPluginData.equals(null)); + } + + // reflexive + for (int i = 0; i < 30; i++) { + RegionsPluginData regionsPluginData = getRandomRegionsPluginData(randomGenerator.nextLong()); + assertTrue(regionsPluginData.equals(regionsPluginData)); + } + + // symmetric, transitive, consistent + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + RegionsPluginData regionsPluginData1 = getRandomRegionsPluginData(seed); + RegionsPluginData regionsPluginData2 = getRandomRegionsPluginData(seed); + + for (int j = 0; j < 5; j++) { + assertTrue(regionsPluginData1.equals(regionsPluginData2)); + assertTrue(regionsPluginData2.equals(regionsPluginData1)); + } + } + + // different inputs yield unequal objects + Set regionsPluginDatas = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + RegionsPluginData regionsPluginData = getRandomRegionsPluginData(randomGenerator.nextLong()); + regionsPluginDatas.add(regionsPluginData); + } + assertEquals(100, regionsPluginDatas.size()); + } + + @Test + @UnitTestMethod(target = RegionsPluginData.class, name = "hashCode", args = {}) + public void testHashCode() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(586211957860853353L); + + // equal objects have equal hash codes + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + RegionsPluginData regionsPluginData1 = getRandomRegionsPluginData(seed); + RegionsPluginData regionsPluginData2 = getRandomRegionsPluginData(seed); + assertEquals(regionsPluginData1,regionsPluginData2); + assertEquals(regionsPluginData1.hashCode(),regionsPluginData2.hashCode()); + } + + //hash codes are reasonably distributed + Set hashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + RegionsPluginData regionsPluginData = getRandomRegionsPluginData(randomGenerator.nextLong()); + hashCodes.add(regionsPluginData.hashCode()); + } + assertEquals(100, hashCodes.size()); + + } + + + +// RegionsPluginData public java.lang.String plugins.regions.datamanagers.RegionsPluginData.toString() + + @Test + @UnitTestMethod(target = RegionsPluginData.class, name = "toString", args = {}) + public void testToString() { + RegionsPluginData regionsPluginData = getRandomRegionsPluginData(6728844980805060979L); + + String actualValue = regionsPluginData.toString(); + + + //expected value manually verified + String expectedValue = "RegionsPluginData [data=Data [" + + "regionPropertyDefinitions={" + + "REGION_PROPERTY_3_DOUBLE_MUTABLE=PropertyDefinition [type=class java.lang.Double, propertyValuesAreMutable=true, defaultValue=0.0], " + + "REGION_PROPERTY_5_INTEGER_IMMUTABLE=PropertyDefinition [type=class java.lang.Integer, propertyValuesAreMutable=false, defaultValue=0]}, " + + + "regionIds=[REGION_3, REGION_1, REGION_4], " + + + "trackRegionArrivalTimes=true, " + + + "regionPropertyValues={" + + "REGION_1={REGION_PROPERTY_5_INTEGER_IMMUTABLE=1769994519}, " + + "REGION_3={REGION_PROPERTY_5_INTEGER_IMMUTABLE=706454702}}, " + + + "personRegions=[null, REGION_4, null, REGION_3, null, REGION_1, null, REGION_3, null, REGION_1], " + + + + "personArrivalTimes=[null, 0.008078132587675535, null, 0.7027533190641975, null, 0.38173962849044774, null, 0.7955082969867588, null, 0.9457602126490658], " + + + + "locked=true]]"; + + assertEquals(expectedValue, actualValue); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/events/AT_PersonRegionUpdateEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/events/AT_PersonRegionUpdateEvent.java new file mode 100644 index 000000000..0d899f176 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/events/AT_PersonRegionUpdateEvent.java @@ -0,0 +1,54 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.events; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_PersonRegionUpdateEvent { + + @Test + @UnitTestConstructor(target = PersonRegionUpdateEvent.class, args = { PersonId.class, RegionId.class, RegionId.class }) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonRegionUpdateEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonRegionUpdateEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonRegionUpdateEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonRegionUpdateEvent.class, name = "personId", args = {}) + public void testPersonId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonRegionUpdateEvent.class, name = "previousRegionId", args = {}) + public void testPreviousRegionId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonRegionUpdateEvent.class, name = "currentRegionId", args = {}) + public void testCurrentRegionId() { + // nothing to test + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/events/AT_RegionAdditionEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/events/AT_RegionAdditionEvent.java new file mode 100644 index 000000000..717ba0117 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/events/AT_RegionAdditionEvent.java @@ -0,0 +1,137 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.events; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionError; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.SimpleRegionId; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_RegionAdditionEvent { + + @Test + @UnitTestMethod(target = RegionAdditionEvent.class, name = "builder", args = {}) + public void testBuilder() { + RegionAdditionEvent.Builder builder = RegionAdditionEvent.builder(); + + assertNotNull(builder); + } + + @Test + @UnitTestMethod(target = RegionAdditionEvent.class, name = "getValues", args = { Class.class }) + public void testGetValues() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2586891640909005860L); + RegionAdditionEvent.Builder builder = RegionAdditionEvent.builder(); + RegionId regionId = new SimpleRegionId(1000); + + List expectedStringValues = new ArrayList<>(); + List expectedIntValues = new ArrayList<>(); + + for (int i = 0; i < 15; i++) { + int intValue = randomGenerator.nextInt(100); + String value = Integer.toString(randomGenerator.nextInt(100)); + expectedStringValues.add(value); + expectedIntValues.add(intValue); + builder.addValue(value).addValue(intValue); + } + builder.setRegionId(regionId); + + RegionAdditionEvent regionAdditionEvent = builder.build(); + + assertNotNull(regionAdditionEvent); + assertEquals(expectedStringValues, regionAdditionEvent.getValues(String.class)); + assertEquals(expectedIntValues, regionAdditionEvent.getValues(Integer.class)); + } + + @Test + @UnitTestMethod(target = RegionAdditionEvent.class, name = "getRegionId", args = {}) + public void testGetRegionId() { + RegionAdditionEvent.Builder builder = RegionAdditionEvent.builder(); + RegionId regionId = new SimpleRegionId(1000); + + builder.setRegionId(regionId); + + RegionAdditionEvent regionAdditionEvent = builder.build(); + + assertNotNull(regionAdditionEvent); + assertEquals(regionId, regionAdditionEvent.getRegionId()); + } + + @Test + @UnitTestMethod(target = RegionAdditionEvent.Builder.class, name = "build", args = {}) + public void testBuild() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5095911653055460787L); + RegionAdditionEvent.Builder builder = RegionAdditionEvent.builder(); + RegionId regionId = new SimpleRegionId(1000); + + builder.setRegionId(regionId); + + RegionAdditionEvent regionAdditionEvent = builder.build(); + + assertNotNull(regionAdditionEvent); + // builder.addValue() and builder.setRegionId are covered by other tests + + // precondition: null region id + ContractException contractException = assertThrows(ContractException.class, () -> RegionAdditionEvent.builder().addValue(Integer.toString(randomGenerator.nextInt(100))).build()); + assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = RegionAdditionEvent.Builder.class, name = "addValue", args = { Object.class }) + public void testAddValue() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3583185907954183553L); + RegionAdditionEvent.Builder builder = RegionAdditionEvent.builder(); + RegionId regionId = new SimpleRegionId(1000); + + List expectedStringValues = new ArrayList<>(); + List expectedIntValues = new ArrayList<>(); + + for (int i = 0; i < 15; i++) { + int intValue = randomGenerator.nextInt(100); + String value = Integer.toString(randomGenerator.nextInt(100)); + expectedStringValues.add(value); + expectedIntValues.add(intValue); + builder.addValue(value).addValue(intValue); + } + builder.setRegionId(regionId); + + RegionAdditionEvent regionAdditionEvent = builder.build(); + + assertNotNull(regionAdditionEvent); + assertEquals(expectedStringValues, regionAdditionEvent.getValues(String.class)); + assertEquals(expectedIntValues, regionAdditionEvent.getValues(Integer.class)); + + // precondition: value is null + ContractException contractException = assertThrows(ContractException.class, () -> RegionAdditionEvent.builder().addValue(null).setRegionId(regionId).build()); + assertEquals(RegionError.NULL_AUXILIARY_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = RegionAdditionEvent.Builder.class, name = "setRegionId", args = { RegionId.class }) + public void testSetRegiodId() { + RegionAdditionEvent.Builder builder = RegionAdditionEvent.builder(); + RegionId regionId = new SimpleRegionId(1000); + + builder.setRegionId(regionId); + + RegionAdditionEvent regionAdditionEvent = builder.build(); + + assertNotNull(regionAdditionEvent); + assertEquals(regionId, regionAdditionEvent.getRegionId()); + + // precondition: region id is null + ContractException contractException = assertThrows(ContractException.class, () -> RegionAdditionEvent.builder().setRegionId(null).build()); + assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/events/AT_RegionPropertyDefinitionEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/events/AT_RegionPropertyDefinitionEvent.java new file mode 100644 index 000000000..c0934e8f1 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/events/AT_RegionPropertyDefinitionEvent.java @@ -0,0 +1,49 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.events; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + +public class AT_RegionPropertyDefinitionEvent { + + @Test + @UnitTestConstructor(target = RegionPropertyDefinitionEvent.class, args = { RegionPropertyId.class }) + public void testConstructor() { + + // precondition: region property id is null + ContractException contractException = assertThrows(ContractException.class, () -> new RegionPropertyDefinitionEvent(null)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = RegionPropertyDefinitionEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = RegionPropertyDefinitionEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = RegionPropertyDefinitionEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = RegionPropertyDefinitionEvent.class, name = "regionPropertyId", args = {}) + public void testRegionPropertyId() { + // nothing to test + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/events/AT_RegionPropertyUpdateEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/events/AT_RegionPropertyUpdateEvent.java new file mode 100644 index 000000000..9457e1990 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/events/AT_RegionPropertyUpdateEvent.java @@ -0,0 +1,60 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.events; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionPropertyId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_RegionPropertyUpdateEvent { + + @Test + @UnitTestConstructor(target = RegionPropertyUpdateEvent.class, args = { RegionId.class, RegionPropertyId.class, Object.class, Object.class }) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = RegionPropertyUpdateEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = RegionPropertyUpdateEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = RegionPropertyUpdateEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = RegionPropertyUpdateEvent.class, name = "regionId", args = {}) + public void testRegionId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = RegionPropertyUpdateEvent.class, name = "regionPropertyId", args = {}) + public void testRegionPropertyId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = RegionPropertyUpdateEvent.class, name = "previousPropertyValue", args = {}) + public void testPreviousPropertyValue() { + // nothing to test + } + + @Test + @UnitTestMethod(target = RegionPropertyUpdateEvent.class, name = "currentPropertyValue", args = {}) + public void testCurrentPropertyValue() { + // nothing to test + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/reports/AT_RegionPropertyReport.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/reports/AT_RegionPropertyReport.java new file mode 100644 index 000000000..fdd62fcc1 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/reports/AT_RegionPropertyReport.java @@ -0,0 +1,649 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.reports; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestOutputConsumer; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionError; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionPropertyDefinitionInitialization; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.SimpleRegionId; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.SimpleRegionPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.RegionsTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.RegionsTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.TestRegionId; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.TestRegionPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + +public class AT_RegionPropertyReport { + + @Test + @UnitTestConstructor(target = RegionPropertyReport.class, args = { RegionPropertyReportPluginData.class }, tags = {}) + public void testConstructor() { + // construction is covered by the other tests + + // precondition test: if the RegionPropertyReportPluginData is null + ContractException contractException = assertThrows(ContractException.class, () -> new RegionPropertyReport(null)); + assertEquals(RegionError.NULL_REGION_PROPERTY_REPORT_PLUGIN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = RegionPropertyReport.class, name = "init", args = { ReportContext.class }) + public void testInit_Content() { + + /* + * We will add one actor and the region property report to the engine. + * We will define a few region properties and the actor will alter + * various region properties over time. Report items from the report + * will be collected in an output consumer. The expected report items + * will be collected in a separate consumer and the consumers will be + * compared for equality. + */ + + RegionPropertyReportPluginData regionPropertyReportPluginData = // + RegionPropertyReportPluginData .builder()// + .setReportLabel(REPORT_LABEL)// + .setDefaultInclusion(true)// + .build(); + + // add the global property definitions + + RegionsPluginData.Builder initialDatabuilder = RegionsPluginData.builder(); + + RegionId regionA = new SimpleRegionId("Region_A"); + initialDatabuilder.addRegion(regionA); + RegionId regionB = new SimpleRegionId("Region_B"); + initialDatabuilder.addRegion(regionB); + RegionId regionC = new SimpleRegionId("Region_C"); + initialDatabuilder.addRegion(regionC); + + RegionPropertyId regionPropertyId_1 = new SimpleRegionPropertyId("id_1"); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(3).build(); + initialDatabuilder.defineRegionProperty(regionPropertyId_1, propertyDefinition); + + RegionPropertyId regionPropertyId_2 = new SimpleRegionPropertyId("id_2"); + propertyDefinition = PropertyDefinition.builder().setType(Double.class).setDefaultValue(6.78).build(); + initialDatabuilder.defineRegionProperty(regionPropertyId_2, propertyDefinition); + + RegionPropertyId regionPropertyId_3 = new SimpleRegionPropertyId("id_3"); + propertyDefinition = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(true).build(); + initialDatabuilder.defineRegionProperty(regionPropertyId_3, propertyDefinition); + + RegionsPluginData regionsPluginData = initialDatabuilder.build(); + + /* + * Define two more properties that are not included in the plugin data + * and will be added by an actor + */ + RegionPropertyId regionPropertyId_4 = new SimpleRegionPropertyId("id_4"); + PropertyDefinition propertyDefinition_4 = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(true).build(); + + RegionPropertyId regionPropertyId_5 = new SimpleRegionPropertyId("id_5"); + PropertyDefinition propertyDefinition_5 = PropertyDefinition.builder().setType(Double.class).setDefaultValue(199.16).build(); + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create an agent and have it assign various region properties at + // various times + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0.0, (c) -> { + /* + * note that this is time 0 and should show that property initial + * values are still reported correctly + */ + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + regionsDataManager.setRegionPropertyValue(regionA, regionPropertyId_1, 67); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1.0, (c) -> { + // two settings of the same property + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + regionsDataManager.setRegionPropertyValue(regionB, regionPropertyId_2, 88.88); + regionsDataManager.setRegionPropertyValue(regionC, regionPropertyId_3, false); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2.0, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + regionsDataManager.setRegionPropertyValue(regionA, regionPropertyId_1, 100); + regionsDataManager.setRegionPropertyValue(regionB, regionPropertyId_2, 3.45); + regionsDataManager.setRegionPropertyValue(regionC, regionPropertyId_3, true); + RegionPropertyDefinitionInitialization regionPropertyDefinitionInitialization = RegionPropertyDefinitionInitialization .builder().setRegionPropertyId(regionPropertyId_4) + .setPropertyDefinition(propertyDefinition_4).build(); + regionsDataManager.defineRegionProperty(regionPropertyDefinitionInitialization); + + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(3.0, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + + regionsDataManager.setRegionPropertyValue(regionC, regionPropertyId_3, false); + // note the duplicated value + regionsDataManager.setRegionPropertyValue(regionB, regionPropertyId_2, 99.7); + regionsDataManager.setRegionPropertyValue(regionB, regionPropertyId_2, 99.7); + // and now a third setting of the same property to a new value + regionsDataManager.setRegionPropertyValue(regionB, regionPropertyId_2, 100.0); + regionsDataManager.setRegionPropertyValue(regionC, regionPropertyId_3, true); + RegionPropertyDefinitionInitialization regionPropertyDefinitionInitialization = RegionPropertyDefinitionInitialization .builder().setRegionPropertyId(regionPropertyId_5) + .setPropertyDefinition(propertyDefinition_5).build(); + regionsDataManager.defineRegionProperty(regionPropertyDefinitionInitialization); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + + /* + * Collect the expected report items. Note that order does not matter. * + */ + TestOutputConsumer expectedOutputConsumer = new TestOutputConsumer(); + + expectedOutputConsumer.accept(getReportItem(0.0, regionA, regionPropertyId_1, 3)); + expectedOutputConsumer.accept(getReportItem(0.0, regionA, regionPropertyId_2, 6.78)); + expectedOutputConsumer.accept(getReportItem(0.0, regionA, regionPropertyId_3, true)); + expectedOutputConsumer.accept(getReportItem(0.0, regionB, regionPropertyId_1, 3)); + expectedOutputConsumer.accept(getReportItem(0.0, regionB, regionPropertyId_2, 6.78)); + expectedOutputConsumer.accept(getReportItem(0.0, regionB, regionPropertyId_3, true)); + expectedOutputConsumer.accept(getReportItem(0.0, regionC, regionPropertyId_1, 3)); + expectedOutputConsumer.accept(getReportItem(0.0, regionC, regionPropertyId_2, 6.78)); + expectedOutputConsumer.accept(getReportItem(0.0, regionC, regionPropertyId_3, true)); + expectedOutputConsumer.accept(getReportItem(0.0, regionA, regionPropertyId_1, 67)); + expectedOutputConsumer.accept(getReportItem(1.0, regionB, regionPropertyId_2, 88.88)); + expectedOutputConsumer.accept(getReportItem(1.0, regionC, regionPropertyId_3, false)); + expectedOutputConsumer.accept(getReportItem(2.0, regionA, regionPropertyId_1, 100)); + expectedOutputConsumer.accept(getReportItem(2.0, regionB, regionPropertyId_2, 3.45)); + expectedOutputConsumer.accept(getReportItem(2.0, regionC, regionPropertyId_3, true)); + expectedOutputConsumer.accept(getReportItem(2.0, regionA, regionPropertyId_4, true)); + expectedOutputConsumer.accept(getReportItem(2.0, regionB, regionPropertyId_4, true)); + expectedOutputConsumer.accept(getReportItem(2.0, regionC, regionPropertyId_4, true)); + expectedOutputConsumer.accept(getReportItem(3.0, regionC, regionPropertyId_3, false)); + expectedOutputConsumer.accept(getReportItem(3.0, regionB, regionPropertyId_2, 99.7)); + expectedOutputConsumer.accept(getReportItem(3.0, regionB, regionPropertyId_2, 99.7)); + expectedOutputConsumer.accept(getReportItem(3.0, regionB, regionPropertyId_2, 100.0)); + expectedOutputConsumer.accept(getReportItem(3.0, regionC, regionPropertyId_3, true)); + expectedOutputConsumer.accept(getReportItem(3.0, regionA, regionPropertyId_5, 199.16)); + expectedOutputConsumer.accept(getReportItem(3.0, regionB, regionPropertyId_5, 199.16)); + expectedOutputConsumer.accept(getReportItem(3.0, regionC, regionPropertyId_5, 199.16)); + + Factory factory = RegionsTestPluginFactory .factory(0, 3558607823596502222L, true, testPluginData)// + .setRegionsPluginData(regionsPluginData)// + .setRegionPropertyReportPluginData(regionPropertyReportPluginData);// + + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .build()// + .execute(); + + Map expectedReportItems = expectedOutputConsumer.getOutputItemMap(ReportItem.class); + Map actualReportItems = testOutputConsumer.getOutputItemMap(ReportItem.class); + + assertEquals(expectedReportItems, actualReportItems); + + } + + private static ReportItem getReportItem(Object... values) { + ReportItem.Builder builder = ReportItem.builder(); + builder.setReportLabel(REPORT_LABEL); + builder.setReportHeader(REPORT_HEADER); + for (Object value : values) { + builder.addValue(value); + } + return builder.build(); + } + + @Test + @UnitTestMethod(target = RegionPropertyReport.class, name = "init", args = { ReportContext.class }) + public void testInit_IncludeProperty() { + /* + * This test shows that the report produces report items with the + * correct selected properties as a function of the explicitly included + * properties + */ + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // create a test actor plan where we set several region property values + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { + if (testRegionPropertyId.getPropertyDefinition().propertyValuesAreMutable()) { + Object regionPropertyValue = testRegionPropertyId.getRandomPropertyValue(randomGenerator); + regionsDataManager.setRegionPropertyValue(TestRegionId.REGION_1, testRegionPropertyId, regionPropertyValue); + } + } + })); + + RegionPropertyId unknownRegionPropertyId = TestRegionPropertyId.getUnknownRegionPropertyId(); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(1).build(); + + RegionPropertyDefinitionInitialization regionPropertyDefinitionInitialization = RegionPropertyDefinitionInitialization// + .builder()// + .setRegionPropertyId(unknownRegionPropertyId)// + .setPropertyDefinition(propertyDefinition).build(); + regionsDataManager.defineRegionProperty(regionPropertyDefinitionInitialization); + + regionsDataManager.setRegionPropertyValue(TestRegionId.REGION_1, unknownRegionPropertyId, 2); + })); + + ReportLabel reportLabel = new SimpleReportLabel("report label"); + TestRegionPropertyId testRegionPropertyId = TestRegionPropertyId.REGION_PROPERTY_1_BOOLEAN_MUTABLE; + + RegionPropertyReportPluginData.Builder builder = RegionPropertyReportPluginData.builder(); + builder.setReportLabel(reportLabel); + builder.setDefaultInclusion(false); + builder.includeRegionProperty(testRegionPropertyId); + builder.includeRegionProperty(unknownRegionPropertyId); + RegionPropertyReportPluginData regionPropertyReportPluginData = builder.build(); + + TestPluginData testPluginData = pluginDataBuilder.build(); + + Factory factory = RegionsTestPluginFactory// + .factory(0, 424190079221645034L, true, testPluginData)// + .setRegionPropertyReportPluginData(regionPropertyReportPluginData);// + + // tell the builder to include a specific region property id + + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .build()// + .execute(); + + // show that our report items include the chosen property id + Map outputItems = testOutputConsumer.getOutputItemMap(ReportItem.class); + assertFalse(outputItems.isEmpty()); + + Set outputPropertyStrings = new LinkedHashSet<>(); + for (ReportItem reportItem : outputItems.keySet()) { + outputPropertyStrings.add(reportItem.getValue(2)); + } + assertTrue(outputPropertyStrings.contains(testRegionPropertyId.toString())); + assertTrue(outputPropertyStrings.contains(unknownRegionPropertyId.toString())); + } + + @Test + @UnitTestMethod(target = RegionPropertyReport.class, name = "init", args = { ReportContext.class }) + public void testInit_DefaultInclusion() { + + // group the properties into explicitly included, explicitly excluded, + // and those that are not specified + RegionPropertyId includedPropertyId = TestRegionPropertyId.REGION_PROPERTY_1_BOOLEAN_MUTABLE; + RegionPropertyId excludedPropertyId = TestRegionPropertyId.REGION_PROPERTY_6_DOUBLE_IMMUTABLE; + Set middlePropertyIds = new LinkedHashSet<>(); + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { + middlePropertyIds.add(testRegionPropertyId); + } + middlePropertyIds.remove(includedPropertyId); + middlePropertyIds.remove(excludedPropertyId); + + // create an enum to represent setting the default inclusion policy + enum DefaultInclusionPolicy { + TRUE, FALSE, UNSPECIFIED + } + ; + + // loop over the three policies + for (DefaultInclusionPolicy defaultInclusionPolicy : DefaultInclusionPolicy.values()) { + + // build the report plugin data + RegionPropertyReportPluginData.Builder reportBuilder = RegionPropertyReportPluginData.builder(); + reportBuilder.setReportLabel(new SimpleReportLabel("report label")); + switch (defaultInclusionPolicy) { + case FALSE: + reportBuilder.setDefaultInclusion(false); + break; + case TRUE: + reportBuilder.setDefaultInclusion(true); + break; + default: + // do nothing + } + + reportBuilder.includeRegionProperty(includedPropertyId); + reportBuilder.excludeRegionProperty(excludedPropertyId); + RegionPropertyReportPluginData regionPropertyReportPluginData = reportBuilder.build(); + + // build the region plugin using the report plugin data and the + // standard region plugin data build + Factory factory = RegionsTestPluginFactory// + .factory(0, 6198510650051666838L, true, (c) -> { + })// + .setRegionPropertyReportPluginData(regionPropertyReportPluginData);// + + // execute the simulation + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .build()// + .execute(); + + // gather from the report items the property ids that were actually + // included in the report + Set actualPropertyIds = new LinkedHashSet<>(); + Map outputItems = testOutputConsumer.getOutputItemMap(ReportItem.class); + for (ReportItem reportItem : outputItems.keySet()) { + Integer count = outputItems.get(reportItem); + assertEquals(1, count); + TestRegionPropertyId testRegionPropertyId = TestRegionPropertyId.valueOf(reportItem.getValue(2)); + actualPropertyIds.add(testRegionPropertyId); + } + + // build the expected property ids based on the policy + Set expectedPropertyIds = new LinkedHashSet<>(); + expectedPropertyIds.add(includedPropertyId); + + switch (defaultInclusionPolicy) { + case FALSE: + // only the single included property + break; + default: + expectedPropertyIds.addAll(middlePropertyIds); + break; + } + + // show that the property id sets are equals + assertEquals(expectedPropertyIds, actualPropertyIds); + + } + + } + + @Test + @UnitTestMethod(target = RegionPropertyReport.class, name = "init", args = { ReportContext.class }) + public void testInit_ReportHeader() { + /* + * This test shows that the report produces report items with the + * correct header + */ + + RegionPropertyReportPluginData regionPropertyReportPluginData = // + RegionPropertyReportPluginData .builder()// + .setReportLabel(new SimpleReportLabel("report label"))// + .build(); + + Factory factory = RegionsTestPluginFactory// + .factory(0, 2608710368019720039L, true, (c) -> { + })// + .setRegionPropertyReportPluginData(regionPropertyReportPluginData);// + + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .build()// + .execute(); + + // show that the report labels are what we expect for each report item + Map outputItems = testOutputConsumer.getOutputItemMap(ReportItem.class); + assertFalse(outputItems.isEmpty()); + + for (ReportItem reportItem : outputItems.keySet()) { + assertEquals(REPORT_HEADER, reportItem.getReportHeader()); + } + } + + @Test + @UnitTestMethod(target = RegionPropertyReport.class, name = "init", args = { ReportContext.class }) + public void testInit_ExcludeProperty() { + /* + * This test shows that the report produces report items with the + * correct selected properties as a function of the explicitly included + * properties + */ + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + // create a test actor plan where we set several region property values + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { + if (testRegionPropertyId.getPropertyDefinition().propertyValuesAreMutable()) { + Object regionPropertyValue = testRegionPropertyId.getRandomPropertyValue(randomGenerator); + regionsDataManager.setRegionPropertyValue(TestRegionId.REGION_1, testRegionPropertyId, regionPropertyValue); + } + } + + })); + + RegionPropertyId unknownRegionPropertyId = TestRegionPropertyId.getUnknownRegionPropertyId(); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(1).build(); + + RegionPropertyDefinitionInitialization regionPropertyDefinitionInitialization = RegionPropertyDefinitionInitialization// + .builder()// + .setRegionPropertyId(unknownRegionPropertyId)// + .setPropertyDefinition(propertyDefinition).build(); + regionsDataManager.defineRegionProperty(regionPropertyDefinitionInitialization); + + regionsDataManager.setRegionPropertyValue(TestRegionId.REGION_1, unknownRegionPropertyId, 2); + })); + + ReportLabel reportLabel = new SimpleReportLabel("report label"); + TestRegionPropertyId testRegionPropertyId = TestRegionPropertyId.REGION_PROPERTY_1_BOOLEAN_MUTABLE; + + RegionPropertyReportPluginData.Builder builder = RegionPropertyReportPluginData.builder(); + builder.setReportLabel(reportLabel); + builder.setDefaultInclusion(true); + builder.excludeRegionProperty(testRegionPropertyId); + builder.excludeRegionProperty(unknownRegionPropertyId); + RegionPropertyReportPluginData regionPropertyReportPluginData = builder.build(); + + TestPluginData testPluginData = pluginDataBuilder.build(); + + Factory factory = RegionsTestPluginFactory// + .factory(0, 4660368916694613140L, true, testPluginData)// + .setRegionPropertyReportPluginData(regionPropertyReportPluginData);// + + // tell the builder to include a specific region property id + + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .build()// + .execute(); + + // show that our report items exclude the chosen property id + Map outputItems = testOutputConsumer.getOutputItemMap(ReportItem.class); + assertFalse(outputItems.isEmpty()); + + Set outputPropertyStrings = new LinkedHashSet<>(); + for (ReportItem reportItem : outputItems.keySet()) { + outputPropertyStrings.add(reportItem.getValue(1)); + } + assertFalse(outputPropertyStrings.contains(testRegionPropertyId.toString())); + assertFalse(outputPropertyStrings.contains(unknownRegionPropertyId.toString())); + + } + + @Test + @UnitTestMethod(target = RegionPropertyReport.class, name = "init", args = { ReportContext.class }) + public void testInit_ReportLabel() { + /* + * This test shows that the report produces report items with the + * correct header + */ + + ReportLabel reportLabel = new SimpleReportLabel("report label"); + + RegionPropertyReportPluginData regionPropertyReportPluginData = // + RegionPropertyReportPluginData .builder()// + .setReportLabel(reportLabel)// + .build(); + + Factory factory = RegionsTestPluginFactory// + .factory(0, 6305425169353361697L, true, (c) -> { + })// + .setRegionPropertyReportPluginData(regionPropertyReportPluginData);// + + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .build()// + .execute(); + + // show that the report labels are what we expect for each report item + Map outputItems = testOutputConsumer.getOutputItemMap(ReportItem.class); + assertFalse(outputItems.isEmpty()); + + for (ReportItem reportItem : outputItems.keySet()) { + assertEquals(reportLabel, reportItem.getReportLabel()); + } + } + + @Test + @UnitTestMethod(target = RegionPropertyReport.class, name = "init", args = { ReportContext.class }) + public void testInit_State() { + // Test with producing simulation state + + RegionPropertyReportPluginData regionPropertyReportPluginData = // + RegionPropertyReportPluginData .builder()// + .setReportLabel(REPORT_LABEL)// + .setDefaultInclusion(true)// + .excludeRegionProperty(TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE) + .build(); + + // add the global property definitions + + RegionsPluginData.Builder initialDatabuilder = RegionsPluginData.builder(); + + RegionId regionA = new SimpleRegionId("Region_A"); + initialDatabuilder.addRegion(regionA); + RegionId regionB = new SimpleRegionId("Region_B"); + initialDatabuilder.addRegion(regionB); + RegionId regionC = new SimpleRegionId("Region_C"); + initialDatabuilder.addRegion(regionC); + + RegionPropertyId regionPropertyId_1 = new SimpleRegionPropertyId("id_1"); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(3).build(); + initialDatabuilder.defineRegionProperty(regionPropertyId_1, propertyDefinition); + + RegionPropertyId regionPropertyId_2 = new SimpleRegionPropertyId("id_2"); + propertyDefinition = PropertyDefinition.builder().setType(Double.class).setDefaultValue(6.78).build(); + initialDatabuilder.defineRegionProperty(regionPropertyId_2, propertyDefinition); + + RegionPropertyId regionPropertyId_3 = new SimpleRegionPropertyId("id_3"); + propertyDefinition = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(true).build(); + initialDatabuilder.defineRegionProperty(regionPropertyId_3, propertyDefinition); + + RegionsPluginData regionsPluginData = initialDatabuilder.build(); + + /* + * Define two more properties that are not included in the plugin data + * and will be added by an actor + */ + RegionPropertyId regionPropertyId_4 = new SimpleRegionPropertyId("id_4"); + PropertyDefinition propertyDefinition_4 = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(true).build(); + + RegionPropertyId regionPropertyId_5 = new SimpleRegionPropertyId("id_5"); + PropertyDefinition propertyDefinition_5 = PropertyDefinition.builder().setType(Double.class).setDefaultValue(199.16).build(); + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create an agent and have it assign various region properties at + // various times + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0.0, (c) -> { + /* + * note that this is time 0 and should show that property initial + * values are still reported correctly + */ + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + regionsDataManager.setRegionPropertyValue(regionA, regionPropertyId_1, 67); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1.0, (c) -> { + // two settings of the same property + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + regionsDataManager.setRegionPropertyValue(regionB, regionPropertyId_2, 88.88); + regionsDataManager.setRegionPropertyValue(regionC, regionPropertyId_3, false); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2.0, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + regionsDataManager.setRegionPropertyValue(regionA, regionPropertyId_1, 100); + regionsDataManager.setRegionPropertyValue(regionB, regionPropertyId_2, 3.45); + regionsDataManager.setRegionPropertyValue(regionC, regionPropertyId_3, true); + RegionPropertyDefinitionInitialization regionPropertyDefinitionInitialization = RegionPropertyDefinitionInitialization .builder().setRegionPropertyId(regionPropertyId_4) + .setPropertyDefinition(propertyDefinition_4).build(); + regionsDataManager.defineRegionProperty(regionPropertyDefinitionInitialization); + + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(3.0, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + + regionsDataManager.setRegionPropertyValue(regionC, regionPropertyId_3, false); + // note the duplicated value + regionsDataManager.setRegionPropertyValue(regionB, regionPropertyId_2, 99.7); + regionsDataManager.setRegionPropertyValue(regionB, regionPropertyId_2, 99.7); + // and now a third setting of the same property to a new value + regionsDataManager.setRegionPropertyValue(regionB, regionPropertyId_2, 100.0); + regionsDataManager.setRegionPropertyValue(regionC, regionPropertyId_3, true); + RegionPropertyDefinitionInitialization regionPropertyDefinitionInitialization = RegionPropertyDefinitionInitialization .builder().setRegionPropertyId(regionPropertyId_5) + .setPropertyDefinition(propertyDefinition_5).build(); + regionsDataManager.defineRegionProperty(regionPropertyDefinitionInitialization); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + + Factory factory = RegionsTestPluginFactory .factory(0, 3558607823596502222L, true, testPluginData)// + .setRegionsPluginData(regionsPluginData)// + .setRegionPropertyReportPluginData(regionPropertyReportPluginData);// + + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .setProduceSimulationStateOnHalt(true)// + .setSimulationHaltTime(20) + .build()// + .execute(); + + Map outputItems = testOutputConsumer.getOutputItemMap(RegionPropertyReportPluginData.class); + assertEquals(1, outputItems.size()); + RegionPropertyReportPluginData regionPropertyReportPluginData2 = outputItems.keySet().iterator().next(); + assertEquals(regionPropertyReportPluginData, regionPropertyReportPluginData2); + + // Test without producing simulation output + + testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .setProduceSimulationStateOnHalt(false)// + .setSimulationHaltTime(20) + .build()// + .execute(); + + outputItems = testOutputConsumer.getOutputItemMap(RegionPropertyReportPluginData.class); + assertEquals(0, outputItems.size()); + } + + private static final ReportLabel REPORT_LABEL = new SimpleReportLabel("region property report"); + + private static final ReportHeader REPORT_HEADER = ReportHeader.builder().add("Time").add("Region").add("Property").add("Value").build(); +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/reports/AT_RegionPropertyReportPluginData.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/reports/AT_RegionPropertyReportPluginData.java new file mode 100644 index 000000000..9268f5539 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/reports/AT_RegionPropertyReportPluginData.java @@ -0,0 +1,547 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.reports; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.TestRegionPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportError; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_RegionPropertyReportPluginData { + + @Test + @UnitTestMethod(target = RegionPropertyReportPluginData.class, name = "builder", args = {}) + public void testBuilder() { + RegionPropertyReportPluginData.Builder builder = RegionPropertyReportPluginData.builder(); + assertNotNull(builder); + } + + @Test + @UnitTestMethod(target = RegionPropertyReportPluginData.Builder.class, name = "build", args = {}) + public void testBuild() { + // the specific capabilities are covered elsewhere + + // precondition test: if the report period is not assigned + ContractException contractException = assertThrows(ContractException.class, () -> // + RegionPropertyReportPluginData.builder()// + .build()); + assertEquals(ReportError.NULL_REPORT_LABEL, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = RegionPropertyReportPluginData.Builder.class, name = "setReportLabel", args = { + ReportLabel.class }) + public void testSetReportLabel() { + + for (int i = 0; i < 30; i++) { + ReportLabel expectedReportLabel = new SimpleReportLabel(i); + RegionPropertyReportPluginData regionPropertyReportPluginData = // + RegionPropertyReportPluginData.builder()// + .setReportLabel(expectedReportLabel)// + .build(); + + assertEquals(expectedReportLabel, regionPropertyReportPluginData.getReportLabel()); + } + + // precondition: if the report label is null + ContractException contractException = assertThrows(ContractException.class, () -> { + RegionPropertyReportPluginData.builder().setReportLabel(null); + }); + assertEquals(ReportError.NULL_REPORT_LABEL, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = RegionPropertyReportPluginData.Builder.class, name = "setDefaultInclusion", args = { + boolean.class }) + public void testSetDefaultInclusion() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + + // show the default value is true + RegionPropertyReportPluginData regionPropertyReportPluginData = // + RegionPropertyReportPluginData.builder()// + .setReportLabel(reportLabel)// + .build(); + assertEquals(true, regionPropertyReportPluginData.getDefaultInclusionPolicy()); + + regionPropertyReportPluginData = // + RegionPropertyReportPluginData.builder()// + .setReportLabel(reportLabel)// + .setDefaultInclusion(true)// + .build(); + assertEquals(true, regionPropertyReportPluginData.getDefaultInclusionPolicy()); + + regionPropertyReportPluginData = // + RegionPropertyReportPluginData.builder()// . + .setReportLabel(reportLabel)// + .setDefaultInclusion(false)// + .build(); + assertEquals(false, regionPropertyReportPluginData.getDefaultInclusionPolicy()); + + } + + @Test + @UnitTestMethod(target = RegionPropertyReportPluginData.Builder.class, name = "includeRegionProperty", args = { + RegionPropertyId.class }) + public void testIncludeRegionProperty() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + + // show the default is non-inclusion + RegionPropertyReportPluginData regionPropertyReportPluginData = // + RegionPropertyReportPluginData.builder()// + .setReportLabel(reportLabel)// + .build(); + assertTrue(regionPropertyReportPluginData.getIncludedProperties().isEmpty()); + + // show that inclusion alone works + Set expectedRegionPropertyIds = new LinkedHashSet<>(); + + expectedRegionPropertyIds.add(TestRegionPropertyId.REGION_PROPERTY_1_BOOLEAN_MUTABLE); + expectedRegionPropertyIds.add(TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE); + expectedRegionPropertyIds.add(TestRegionPropertyId.REGION_PROPERTY_3_DOUBLE_MUTABLE); + + RegionPropertyReportPluginData.Builder builder = RegionPropertyReportPluginData.builder()// + .setReportLabel(reportLabel);// + + for (RegionPropertyId regionPropertyId : expectedRegionPropertyIds) { + builder.includeRegionProperty(regionPropertyId); + } + + regionPropertyReportPluginData = builder.build(); + assertEquals(expectedRegionPropertyIds, regionPropertyReportPluginData.getIncludedProperties()); + + // show that inclusion will override exclusion + expectedRegionPropertyIds.clear(); + + expectedRegionPropertyIds.add(TestRegionPropertyId.REGION_PROPERTY_1_BOOLEAN_MUTABLE); + expectedRegionPropertyIds.add(TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE); + expectedRegionPropertyIds.add(TestRegionPropertyId.REGION_PROPERTY_3_DOUBLE_MUTABLE); + + builder = RegionPropertyReportPluginData.builder()// + .setReportLabel(reportLabel);// + + for (RegionPropertyId regionPropertyId : expectedRegionPropertyIds) { + builder.excludeRegionProperty(regionPropertyId); + builder.includeRegionProperty(regionPropertyId); + } + + regionPropertyReportPluginData = builder.build(); + assertEquals(expectedRegionPropertyIds, regionPropertyReportPluginData.getIncludedProperties()); + + // precondition: if the region property id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + RegionPropertyReportPluginData.builder().includeRegionProperty(null); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = RegionPropertyReportPluginData.Builder.class, name = "excludeRegionProperty", args = { + RegionPropertyId.class }) + public void testExcludeRegionProperty() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + + // show the default is non-exclusion + RegionPropertyReportPluginData regionPropertyReportPluginData = // + RegionPropertyReportPluginData.builder()// + .setReportLabel(reportLabel)// + .build(); + assertTrue(regionPropertyReportPluginData.getExcludedProperties().isEmpty()); + + // show that exclusion alone works + Set expectedRegionPropertyIds = new LinkedHashSet<>(); + + expectedRegionPropertyIds.add(TestRegionPropertyId.REGION_PROPERTY_1_BOOLEAN_MUTABLE); + expectedRegionPropertyIds.add(TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE); + expectedRegionPropertyIds.add(TestRegionPropertyId.REGION_PROPERTY_3_DOUBLE_MUTABLE); + + RegionPropertyReportPluginData.Builder builder = RegionPropertyReportPluginData.builder()// + .setReportLabel(reportLabel);// + + for (RegionPropertyId regionPropertyId : expectedRegionPropertyIds) { + builder.excludeRegionProperty(regionPropertyId); + } + + regionPropertyReportPluginData = builder.build(); + assertEquals(expectedRegionPropertyIds, regionPropertyReportPluginData.getExcludedProperties()); + + // show that exclusion will override inclusion + expectedRegionPropertyIds.clear(); + + expectedRegionPropertyIds.add(TestRegionPropertyId.REGION_PROPERTY_1_BOOLEAN_MUTABLE); + expectedRegionPropertyIds.add(TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE); + expectedRegionPropertyIds.add(TestRegionPropertyId.REGION_PROPERTY_3_DOUBLE_MUTABLE); + + builder = RegionPropertyReportPluginData.builder()// + .setReportLabel(reportLabel);// + + for (RegionPropertyId regionPropertyId : expectedRegionPropertyIds) { + builder.includeRegionProperty(regionPropertyId); + builder.excludeRegionProperty(regionPropertyId); + } + + regionPropertyReportPluginData = builder.build(); + assertEquals(expectedRegionPropertyIds, regionPropertyReportPluginData.getExcludedProperties()); + + // precondition: if the region property id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + RegionPropertyReportPluginData.builder().excludeRegionProperty(null); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = RegionPropertyReportPluginData.class, name = "getReportLabel", args = {}) + public void testGetReportLabel() { + for (int i = 0; i < 30; i++) { + ReportLabel expectedReportLabel = new SimpleReportLabel(i); + RegionPropertyReportPluginData regionPropertyReportPluginData = // + RegionPropertyReportPluginData.builder()// + .setReportLabel(expectedReportLabel)// + .build(); + + assertEquals(expectedReportLabel, regionPropertyReportPluginData.getReportLabel()); + } + + } + + @Test + @UnitTestMethod(target = RegionPropertyReportPluginData.class, name = "getIncludedProperties", args = {}) + public void testGetIncludedProperties() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + + // show the default is non-inclusion + RegionPropertyReportPluginData regionPropertyReportPluginData = // + RegionPropertyReportPluginData.builder()// + + .setReportLabel(reportLabel)// + .build(); + assertTrue(regionPropertyReportPluginData.getIncludedProperties().isEmpty()); + + // show that inclusion alone works + Set expectedRegionPropertyIds = new LinkedHashSet<>(); + + expectedRegionPropertyIds.add(TestRegionPropertyId.REGION_PROPERTY_1_BOOLEAN_MUTABLE); + expectedRegionPropertyIds.add(TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE); + expectedRegionPropertyIds.add(TestRegionPropertyId.REGION_PROPERTY_3_DOUBLE_MUTABLE); + + RegionPropertyReportPluginData.Builder builder = RegionPropertyReportPluginData.builder()// + .setReportLabel(reportLabel);// + + for (RegionPropertyId regionPropertyId : expectedRegionPropertyIds) { + builder.includeRegionProperty(regionPropertyId); + } + + regionPropertyReportPluginData = builder.build(); + assertEquals(expectedRegionPropertyIds, regionPropertyReportPluginData.getIncludedProperties()); + + // show that inclusion will override exclusion + expectedRegionPropertyIds.clear(); + + expectedRegionPropertyIds.add(TestRegionPropertyId.REGION_PROPERTY_1_BOOLEAN_MUTABLE); + expectedRegionPropertyIds.add(TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE); + expectedRegionPropertyIds.add(TestRegionPropertyId.REGION_PROPERTY_3_DOUBLE_MUTABLE); + + builder = RegionPropertyReportPluginData.builder()// + .setReportLabel(reportLabel);// + + for (RegionPropertyId regionPropertyId : expectedRegionPropertyIds) { + builder.excludeRegionProperty(regionPropertyId); + builder.includeRegionProperty(regionPropertyId); + } + + regionPropertyReportPluginData = builder.build(); + assertEquals(expectedRegionPropertyIds, regionPropertyReportPluginData.getIncludedProperties()); + + } + + @Test + @UnitTestMethod(target = RegionPropertyReportPluginData.class, name = "getExcludedProperties", args = {}) + public void testGetExcludedProperties() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + + // show the default is non-exclusion + RegionPropertyReportPluginData regionPropertyReportPluginData = // + RegionPropertyReportPluginData.builder()// + .setReportLabel(reportLabel)// + .build(); + assertTrue(regionPropertyReportPluginData.getExcludedProperties().isEmpty()); + + // show that exclusion alone works + Set expectedRegionPropertyIds = new LinkedHashSet<>(); + + expectedRegionPropertyIds.add(TestRegionPropertyId.REGION_PROPERTY_1_BOOLEAN_MUTABLE); + expectedRegionPropertyIds.add(TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE); + expectedRegionPropertyIds.add(TestRegionPropertyId.REGION_PROPERTY_3_DOUBLE_MUTABLE); + + RegionPropertyReportPluginData.Builder builder = RegionPropertyReportPluginData.builder()// + .setReportLabel(reportLabel);// + + for (RegionPropertyId regionPropertyId : expectedRegionPropertyIds) { + builder.excludeRegionProperty(regionPropertyId); + } + + regionPropertyReportPluginData = builder.build(); + assertEquals(expectedRegionPropertyIds, regionPropertyReportPluginData.getExcludedProperties()); + + // show that exclusion will override inclusion + expectedRegionPropertyIds.clear(); + + expectedRegionPropertyIds.add(TestRegionPropertyId.REGION_PROPERTY_1_BOOLEAN_MUTABLE); + expectedRegionPropertyIds.add(TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE); + expectedRegionPropertyIds.add(TestRegionPropertyId.REGION_PROPERTY_3_DOUBLE_MUTABLE); + + builder = RegionPropertyReportPluginData.builder()// + .setReportLabel(reportLabel);// + + for (RegionPropertyId regionPropertyId : expectedRegionPropertyIds) { + builder.includeRegionProperty(regionPropertyId); + builder.excludeRegionProperty(regionPropertyId); + } + + regionPropertyReportPluginData = builder.build(); + assertEquals(expectedRegionPropertyIds, regionPropertyReportPluginData.getExcludedProperties()); + + } + + @Test + @UnitTestMethod(target = RegionPropertyReportPluginData.class, name = "getDefaultInclusionPolicy", args = {}) + public void testGetDefaultInclusionPolicy() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + + // show the default value is true + RegionPropertyReportPluginData regionPropertyReportPluginData = // + RegionPropertyReportPluginData.builder()// + .setReportLabel(reportLabel)// + .build(); + assertEquals(true, regionPropertyReportPluginData.getDefaultInclusionPolicy()); + + regionPropertyReportPluginData = // + RegionPropertyReportPluginData.builder()// + .setReportLabel(reportLabel)// + .setDefaultInclusion(true)// + .build(); + assertEquals(true, regionPropertyReportPluginData.getDefaultInclusionPolicy()); + + regionPropertyReportPluginData = // + RegionPropertyReportPluginData.builder()// + .setReportLabel(reportLabel)// + .setDefaultInclusion(false)// + .build(); + assertEquals(false, regionPropertyReportPluginData.getDefaultInclusionPolicy()); + } + + @Test + @UnitTestMethod(target = RegionPropertyReportPluginData.class, name = "getCloneBuilder", args = {}) + public void testGetCloneBuilder() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7759639255438669162L); + for (int i = 0; i < 10; i++) { + + // build a RegionPropertyReportPluginData from random inputs + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt()); + + RegionPropertyReportPluginData.Builder builder = // + RegionPropertyReportPluginData.builder()// + .setReportLabel(reportLabel); + + for (int j = 0; j < 10; j++) { + TestRegionPropertyId testRegionPropertyId = TestRegionPropertyId + .getRandomRegionPropertyId(randomGenerator); + if (randomGenerator.nextBoolean()) { + builder.includeRegionProperty(testRegionPropertyId); + } else { + builder.excludeRegionProperty(testRegionPropertyId); + } + } + + builder.setDefaultInclusion(randomGenerator.nextBoolean()).build(); + + RegionPropertyReportPluginData regionPropertyReportPluginData = builder.build(); + + // create the clone builder and have it build + RegionPropertyReportPluginData cloneRegionPropertyReportPluginData = regionPropertyReportPluginData + .getCloneBuilder().build(); + + // the result should equal the original if the clone builder was + // initialized with the correct state + assertEquals(regionPropertyReportPluginData, cloneRegionPropertyReportPluginData); + + } + } + + @Test + @UnitTestMethod(target = RegionPropertyReportPluginData.class, name = "equals", args = { Object.class }) + public void testEquals() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7376599865331451959L); + for (int i = 0; i < 10; i++) { + // build a RegionPropertyReportPluginData from the same random + // inputs + RegionPropertyReportPluginData.Builder builder1 = RegionPropertyReportPluginData.builder(); + RegionPropertyReportPluginData.Builder builder2 = RegionPropertyReportPluginData.builder(); + + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt(100)); + builder1.setReportLabel(reportLabel); + builder2.setReportLabel(reportLabel); + + for (int j = 0; j < 10; j++) { + TestRegionPropertyId testRegionPropertyId = TestRegionPropertyId + .getRandomRegionPropertyId(randomGenerator); + if (randomGenerator.nextBoolean()) { + builder1.includeRegionProperty(testRegionPropertyId); + builder2.includeRegionProperty(testRegionPropertyId); + } else { + builder1.excludeRegionProperty(testRegionPropertyId); + builder2.excludeRegionProperty(testRegionPropertyId); + } + } + + boolean defaultInclusion = randomGenerator.nextBoolean(); + builder1.setDefaultInclusion(defaultInclusion).build(); + builder2.setDefaultInclusion(defaultInclusion).build(); + + RegionPropertyReportPluginData regionPropertyReportPluginData1 = builder1.build(); + RegionPropertyReportPluginData regionPropertyReportPluginData2 = builder2.build(); + + assertEquals(regionPropertyReportPluginData1, regionPropertyReportPluginData2); + + // show that plugin datas with different inputs are not equal + + // change the default inclusion + regionPropertyReportPluginData2 = // + regionPropertyReportPluginData1.getCloneBuilder()// + .setDefaultInclusion(!defaultInclusion)// + .build(); + assertNotEquals(regionPropertyReportPluginData2, regionPropertyReportPluginData1); + + // change the report label + reportLabel = new SimpleReportLabel(1000); + regionPropertyReportPluginData2 = // + regionPropertyReportPluginData1.getCloneBuilder()// + .setReportLabel(reportLabel)// + .build(); + assertNotEquals(regionPropertyReportPluginData2, regionPropertyReportPluginData1); + + // change an included property id + if (!regionPropertyReportPluginData1.getIncludedProperties().isEmpty()) { + RegionPropertyId regionPropertyId = regionPropertyReportPluginData1.getIncludedProperties().iterator() + .next(); + regionPropertyReportPluginData2 = // + regionPropertyReportPluginData1.getCloneBuilder()// + .excludeRegionProperty(regionPropertyId)// + .build(); + assertNotEquals(regionPropertyReportPluginData2, regionPropertyReportPluginData1); + } + // change an excluded property id + if (!regionPropertyReportPluginData1.getExcludedProperties().isEmpty()) { + RegionPropertyId regionPropertyId = regionPropertyReportPluginData1.getExcludedProperties().iterator() + .next(); + regionPropertyReportPluginData2 = // + regionPropertyReportPluginData1.getCloneBuilder()// + .includeRegionProperty(regionPropertyId)// + .build(); + assertNotEquals(regionPropertyReportPluginData2, regionPropertyReportPluginData1); + } + + } + + } + + @Test + @UnitTestMethod(target = RegionPropertyReportPluginData.class, name = "hashCode", args = {}) + public void testHashCode() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8483458328908100435L); + + Set observedHashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 50; i++) { + // build a RegionPropertyReportPluginData from the same random + // inputs + RegionPropertyReportPluginData.Builder builder1 = RegionPropertyReportPluginData.builder(); + RegionPropertyReportPluginData.Builder builder2 = RegionPropertyReportPluginData.builder(); + + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt(100)); + builder1.setReportLabel(reportLabel); + builder2.setReportLabel(reportLabel); + + for (int j = 0; j < 10; j++) { + TestRegionPropertyId testRegionPropertyId = TestRegionPropertyId + .getRandomRegionPropertyId(randomGenerator); + if (randomGenerator.nextBoolean()) { + builder1.includeRegionProperty(testRegionPropertyId); + builder2.includeRegionProperty(testRegionPropertyId); + } else { + builder1.excludeRegionProperty(testRegionPropertyId); + builder2.excludeRegionProperty(testRegionPropertyId); + } + } + + boolean defaultInclusion = randomGenerator.nextBoolean(); + builder1.setDefaultInclusion(defaultInclusion).build(); + builder2.setDefaultInclusion(defaultInclusion).build(); + + RegionPropertyReportPluginData regionPropertyReportPluginData1 = builder1.build(); + RegionPropertyReportPluginData regionPropertyReportPluginData2 = builder2.build(); + + // show that the hash code is stable + int hashCode = regionPropertyReportPluginData1.hashCode(); + assertEquals(hashCode, regionPropertyReportPluginData1.hashCode()); + assertEquals(hashCode, regionPropertyReportPluginData1.hashCode()); + assertEquals(hashCode, regionPropertyReportPluginData1.hashCode()); + assertEquals(hashCode, regionPropertyReportPluginData1.hashCode()); + + // show that equal objects have equal hash codes + assertEquals(regionPropertyReportPluginData1.hashCode(), regionPropertyReportPluginData2.hashCode()); + + // collect the hashcode + observedHashCodes.add(regionPropertyReportPluginData1.hashCode()); + } + + /* + * The hash codes should be dispersed -- we only show that they are unique + * values -- this is dependent on the random seed + */ + assertEquals(50, observedHashCodes.size()); + + } + + @Test + @UnitTestMethod(target = RegionPropertyReportPluginData.class, name = "toString", args = {}) + public void testToString() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + + RegionPropertyReportPluginData.Builder builder = // + RegionPropertyReportPluginData.builder();// + builder.setReportLabel(reportLabel);// + builder.setDefaultInclusion(true); + builder.excludeRegionProperty(TestRegionPropertyId.REGION_PROPERTY_1_BOOLEAN_MUTABLE); + builder.excludeRegionProperty(TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE); + builder.includeRegionProperty(TestRegionPropertyId.REGION_PROPERTY_3_DOUBLE_MUTABLE); + builder.includeRegionProperty(TestRegionPropertyId.REGION_PROPERTY_4_BOOLEAN_IMMUTABLE); + RegionPropertyReportPluginData regionPropertyReportPluginData = builder.build(); + String actualValue = regionPropertyReportPluginData.toString(); + + String expectedValue = "RegionPropertyReportPluginData [data=Data [" + + "reportLabel=SimpleReportLabel [value=report label], " + + "includedProperties=[REGION_PROPERTY_3_DOUBLE_MUTABLE, REGION_PROPERTY_4_BOOLEAN_IMMUTABLE], " + + "excludedProperties=[REGION_PROPERTY_1_BOOLEAN_MUTABLE, REGION_PROPERTY_2_INTEGER_MUTABLE], " + + "defaultInclusionPolicy=true]]"; + + assertEquals(expectedValue, actualValue); + + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/reports/AT_RegionTransferReport.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/reports/AT_RegionTransferReport.java new file mode 100644 index 000000000..ac5ec427e --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/reports/AT_RegionTransferReport.java @@ -0,0 +1,309 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.reports; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.math3.random.RandomGenerator; +import org.apache.commons.math3.util.Pair; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestOutputConsumer; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonConstructionData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.SimpleRegionId; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.SimpleRegionPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.RegionsTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.RegionsTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportError; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import util.annotations.UnitTag; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + +public class AT_RegionTransferReport { + + @Test + @UnitTestConstructor(target = RegionTransferReport.class, args = { RegionTransferReportPluginData.class}) + public void testConstructor() { + RegionTransferReport regionTransferReport = new RegionTransferReport(RegionTransferReportPluginData.builder().setReportLabel(REPORT_LABEL).setReportPeriod(ReportPeriod.DAILY).build()); + + // Show not null + assertNotNull(regionTransferReport); + + // precondition: null report period + assertThrows(NullPointerException.class, () -> new RegionTransferReport(null)); + } + + @Test + @UnitTestMethod(target = RegionTransferReport.class, name = "init", args = { ReportContext.class }, tags = { UnitTag.INCOMPLETE }) + public void testInit() { + TestOutputConsumer expectedConsumer = new TestOutputConsumer(); + /* + * TEST IS INCOMPLETE + * + * This test has to force times to be non integer values and include a + * last task to for proper flushing in the report + */ + + RegionsPluginData.Builder regionBuilder = RegionsPluginData.builder(); + + // add regions A, B, C and D + RegionId regionA = new SimpleRegionId("Region_A"); + regionBuilder.addRegion(regionA); + RegionId regionB = new SimpleRegionId("Region_B"); + regionBuilder.addRegion(regionB); + RegionId regionC = new SimpleRegionId("Region_C"); + regionBuilder.addRegion(regionC); + RegionId regionD = new SimpleRegionId("Region_D"); + regionBuilder.addRegion(regionD); + + // add the region properties + RegionPropertyId prop_age = new SimpleRegionPropertyId("prop_age"); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setDefaultValue(3).setType(Integer.class).build(); + regionBuilder.defineRegionProperty(prop_age, propertyDefinition); + + RegionsPluginData regionsPluginData = regionBuilder.build(); + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + /* + * Collect the expected report items. Note that order does not matter. * + */ + + final int numPeople = 100; + // create an actor to add people to the simulation + // This will test adding people to a region, which on the report will + // say that + // the person transfer to and from the same region + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0.0, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + RegionId[] regionIds = { regionA, regionB, regionC, regionD }; + for (int i = 0; i < numPeople; i++) { + PersonConstructionData personConstructionData = PersonConstructionData.builder().add(regionIds[i % 4]).build(); + peopleDataManager.addPerson(personConstructionData); + } + + expectedConsumer.accept(getReportItem(1, regionA, regionA, 25)); + expectedConsumer.accept(getReportItem(1, regionB, regionB, 25)); + expectedConsumer.accept(getReportItem(1, regionC, regionC, 25)); + expectedConsumer.accept(getReportItem(1, regionD, regionD, 25)); + })); + + // create an actor to move people from one region to another + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1.1, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); + + List personIds = peopleDataManager.getPeople(); + // randomly move 25 people each to region a, region b, region c and + // region d + RegionId[] regionIds = { regionA, regionB, regionC, regionD }; + Map, Integer> transfermap = new LinkedHashMap<>(); + + for (int i = 0; i < numPeople; i++) { + int person = randomGenerator.nextInt(personIds.size()); + PersonId personId = personIds.remove(person); + RegionId prevRegionId = regionsDataManager.getPersonRegion(personId); + RegionId nextRegionId = regionIds[i % 4]; + Pair transfer = new Pair<>(prevRegionId, nextRegionId); + int numTransfers = 1; + if (transfermap.containsKey(transfer)) { + numTransfers += transfermap.get(transfer); + } + transfermap.put(transfer, numTransfers); + + regionsDataManager.setPersonRegion(personId, nextRegionId); + } + + for (Pair regionTransfer : transfermap.keySet()) { + RegionId sourceRegionId = regionTransfer.getFirst(); + RegionId destRegionId = regionTransfer.getSecond(); + int numTransfers = transfermap.get(regionTransfer); + + expectedConsumer.accept(getReportItem(2, sourceRegionId, destRegionId, numTransfers)); + } + })); + + /* + * To get the report for day 1, must do 'something' on day 2 otherwise + * the report for the previous day won't print + */ + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2.0, (c) -> { + })); + + TestPluginData testPluginData = pluginBuilder.build(); + + RegionTransferReportPluginData regionTransferReportPluginData = RegionTransferReportPluginData.builder().setReportLabel(REPORT_LABEL).setReportPeriod(ReportPeriod.DAILY).build(); + + Factory factory = RegionsTestPluginFactory// + .factory(0, 3054641152904904632L, true, testPluginData) + .setRegionsPluginData(regionsPluginData)// + .setRegionTransferReportPluginData(regionTransferReportPluginData); + + TestOutputConsumer actualConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .build()// + .execute(); + + Map expectedReportItems = expectedConsumer.getOutputItemMap(ReportItem.class); + Map actualReportItems = actualConsumer.getOutputItemMap(ReportItem.class); + + assertEquals(expectedReportItems, actualReportItems); + + // precondition: Actor context is null + ContractException contractException = assertThrows(ContractException.class, () -> { + new RegionTransferReport(RegionTransferReportPluginData.builder().setReportLabel(REPORT_LABEL).setReportPeriod(ReportPeriod.DAILY).build()).init(null); + }); + assertEquals(ReportError.NULL_CONTEXT, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = RegionTransferReport.class, name = "init", args = { ReportContext.class }) + public void testInit_State() { + // Test with producing simulation state + + RegionsPluginData.Builder regionBuilder = RegionsPluginData.builder(); + + // add regions A, B, C and D + RegionId regionA = new SimpleRegionId("Region_A"); + regionBuilder.addRegion(regionA); + RegionId regionB = new SimpleRegionId("Region_B"); + regionBuilder.addRegion(regionB); + RegionId regionC = new SimpleRegionId("Region_C"); + regionBuilder.addRegion(regionC); + RegionId regionD = new SimpleRegionId("Region_D"); + regionBuilder.addRegion(regionD); + + // add the region properties + RegionPropertyId prop_age = new SimpleRegionPropertyId("prop_age"); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setDefaultValue(3).setType(Integer.class).build(); + regionBuilder.defineRegionProperty(prop_age, propertyDefinition); + + RegionsPluginData regionsPluginData = regionBuilder.build(); + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + /* + * Collect the expected report items. Note that order does not matter. * + */ + + final int numPeople = 100; + // create an actor to add people to the simulation + // This will test adding people to a region, which on the report will + // say that + // the person transfer to and from the same region + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0.0, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + RegionId[] regionIds = { regionA, regionB, regionC, regionD }; + for (int i = 0; i < numPeople; i++) { + PersonConstructionData personConstructionData = PersonConstructionData.builder().add(regionIds[i % 4]).build(); + peopleDataManager.addPerson(personConstructionData); + } + })); + + // create an actor to move people from one region to another + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1.1, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); + + List personIds = peopleDataManager.getPeople(); + // randomly move 25 people each to region a, region b, region c and + // region d + RegionId[] regionIds = { regionA, regionB, regionC, regionD }; + Map, Integer> transfermap = new LinkedHashMap<>(); + + for (int i = 0; i < numPeople; i++) { + int person = randomGenerator.nextInt(personIds.size()); + PersonId personId = personIds.remove(person); + RegionId prevRegionId = regionsDataManager.getPersonRegion(personId); + RegionId nextRegionId = regionIds[i % 4]; + Pair transfer = new Pair<>(prevRegionId, nextRegionId); + int numTransfers = 1; + if (transfermap.containsKey(transfer)) { + numTransfers += transfermap.get(transfer); + } + transfermap.put(transfer, numTransfers); + + regionsDataManager.setPersonRegion(personId, nextRegionId); + } + })); + + /* + * To get the report for day 1, must do 'something' on day 2 otherwise + * the report for the previous day won't print + */ + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2.0, (c) -> { + })); + + TestPluginData testPluginData = pluginBuilder.build(); + + RegionTransferReportPluginData regionTransferReportPluginData = RegionTransferReportPluginData.builder() + .setReportLabel(REPORT_LABEL) + .setReportPeriod(ReportPeriod.DAILY) + .build(); + + Factory factory = RegionsTestPluginFactory// + .factory(0, 3054641152904904632L, true, testPluginData).setRegionsPluginData(regionsPluginData)// + .setRegionTransferReportPluginData(regionTransferReportPluginData); + + TestOutputConsumer actualConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .setProduceSimulationStateOnHalt(true)// + .setSimulationHaltTime(20)// + .build()// + .execute(); + + Map outputItems = actualConsumer.getOutputItemMap(RegionTransferReportPluginData.class); + assertEquals(1, outputItems.size()); + RegionTransferReportPluginData regionTransferReportPluginData2 = outputItems.keySet().iterator().next(); + assertEquals(regionTransferReportPluginData, regionTransferReportPluginData2); + + // Test without producing simulation state + + actualConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .setProduceSimulationStateOnHalt(false)// + .setSimulationHaltTime(20)// + .build()// + .execute(); + + outputItems = actualConsumer.getOutputItemMap(RegionTransferReportPluginData.class); + assertEquals(0, outputItems.size()); + } + + + private static ReportItem getReportItem(Object... values) { + ReportItem.Builder builder = ReportItem.builder(); + builder.setReportLabel(REPORT_LABEL); + builder.setReportHeader(REPORT_HEADER); + for (Object value : values) { + builder.addValue(value); + } + return builder.build(); + } + + private static final ReportLabel REPORT_LABEL = new SimpleReportLabel("region transfer report"); + + private static final ReportHeader REPORT_HEADER = ReportHeader.builder().add("day").add("source_region").add("destination_region").add("transfers").build(); +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/reports/AT_RegionTransferReportPluginData.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/reports/AT_RegionTransferReportPluginData.java new file mode 100644 index 000000000..1dbcca9e0 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/reports/AT_RegionTransferReportPluginData.java @@ -0,0 +1,271 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.reports; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportError; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_RegionTransferReportPluginData { + + @Test + @UnitTestMethod(target = RegionTransferReportPluginData.class, name = "builder", args = {}) + public void testBuilder() { + RegionTransferReportPluginData.Builder builder = RegionTransferReportPluginData.builder(); + assertNotNull(builder); + } + + @Test + @UnitTestMethod(target = RegionTransferReportPluginData.Builder.class, name = "build", args = {}) + public void testBuild() { + // the specific capabilities are covered elsewhere + + // precondition test: if the report period is not assigned + ContractException contractException = assertThrows(ContractException.class, () -> // + RegionTransferReportPluginData.builder()// + .setReportLabel(new SimpleReportLabel(getClass()))// + .build()); + assertEquals(ReportError.NULL_REPORT_PERIOD, contractException.getErrorType()); + + // precondition test: if the report label is not assigned + contractException = assertThrows(ContractException.class, () -> // + RegionTransferReportPluginData.builder()// + .setReportPeriod(ReportPeriod.DAILY)// + .build()); + assertEquals(ReportError.NULL_REPORT_LABEL, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = RegionTransferReportPluginData.Builder.class, name = "setReportLabel", args = { + ReportLabel.class }) + public void testSetReportLabel() { + + for (int i = 0; i < 30; i++) { + ReportLabel expectedReportLabel = new SimpleReportLabel(i); + RegionTransferReportPluginData regionTransferReportPluginData = // + RegionTransferReportPluginData.builder()// + .setReportPeriod(ReportPeriod.DAILY)// + .setReportLabel(expectedReportLabel)// + .build(); + + assertEquals(expectedReportLabel, regionTransferReportPluginData.getReportLabel()); + } + + // precondition: if the report label is null + ContractException contractException = assertThrows(ContractException.class, () -> { + RegionTransferReportPluginData.builder().setReportLabel(null); + }); + assertEquals(ReportError.NULL_REPORT_LABEL, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = RegionTransferReportPluginData.Builder.class, name = "setReportPeriod", args = { + ReportPeriod.class }) + public void testSetReportPeriod() { + + ReportLabel reportLabel = new SimpleReportLabel("report label"); + for (ReportPeriod reportPeriod : ReportPeriod.values()) { + + RegionTransferReportPluginData regionTransferReportPluginData = // + RegionTransferReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .build(); + + assertEquals(reportPeriod, regionTransferReportPluginData.getReportPeriod()); + } + + // precondition: if the report period is null + ContractException contractException = assertThrows(ContractException.class, () -> { + RegionTransferReportPluginData.builder().setReportPeriod(null); + }); + assertEquals(ReportError.NULL_REPORT_PERIOD, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = RegionTransferReportPluginData.class, name = "getReportLabel", args = {}) + public void testGetReportLabel() { + for (int i = 0; i < 30; i++) { + ReportLabel expectedReportLabel = new SimpleReportLabel(i); + RegionTransferReportPluginData regionTransferReportPluginData = // + RegionTransferReportPluginData.builder()// + .setReportPeriod(ReportPeriod.DAILY)// + .setReportLabel(expectedReportLabel)// + .build(); + + assertEquals(expectedReportLabel, regionTransferReportPluginData.getReportLabel()); + } + + } + + @Test + @UnitTestMethod(target = RegionTransferReportPluginData.class, name = "getReportPeriod", args = {}) + public void testGetReportPeriod() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + for (ReportPeriod reportPeriod : ReportPeriod.values()) { + + RegionTransferReportPluginData regionTransferReportPluginData = // + RegionTransferReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .build(); + + assertEquals(reportPeriod, regionTransferReportPluginData.getReportPeriod()); + } + } + + @Test + @UnitTestMethod(target = RegionTransferReportPluginData.class, name = "getCloneBuilder", args = {}) + public void testGetCloneBuilder() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7759639255438669162L); + for (int i = 0; i < 10; i++) { + + // build a RegionTransferReportPluginData from random inputs + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt()); + ReportPeriod reportPeriod = ReportPeriod.values()[randomGenerator.nextInt(ReportPeriod.values().length)]; + + RegionTransferReportPluginData.Builder builder = // + RegionTransferReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel); + + RegionTransferReportPluginData regionTransferReportPluginData = builder.build(); + + // create the clone builder and have it build + RegionTransferReportPluginData cloneRegionTransferReportPluginData = regionTransferReportPluginData + .getCloneBuilder().build(); + + // the result should equal the original if the clone builder was + // initialized with the correct state + assertEquals(regionTransferReportPluginData, cloneRegionTransferReportPluginData); + + } + } + + @Test + @UnitTestMethod(target = RegionTransferReportPluginData.class, name = "equals", args = { Object.class }) + public void testEquals() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7759639255438669162L); + for (int i = 0; i < 10; i++) { + // build a RegionTransferReportPluginData from the same random + // inputs + RegionTransferReportPluginData.Builder builder1 = RegionTransferReportPluginData.builder(); + RegionTransferReportPluginData.Builder builder2 = RegionTransferReportPluginData.builder(); + + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt(100)); + builder1.setReportLabel(reportLabel); + builder2.setReportLabel(reportLabel); + + ReportPeriod reportPeriod = ReportPeriod.values()[randomGenerator.nextInt(ReportPeriod.values().length)]; + builder1.setReportPeriod(reportPeriod); + builder2.setReportPeriod(reportPeriod); + + RegionTransferReportPluginData regionTransferReportPluginData1 = builder1.build(); + RegionTransferReportPluginData regionTransferReportPluginData2 = builder2.build(); + + assertEquals(regionTransferReportPluginData1, regionTransferReportPluginData2); + + // show that plugin datas with different inputs are not equal + + // change the report period + int ord = reportPeriod.ordinal() + 1; + ord = ord % ReportPeriod.values().length; + reportPeriod = ReportPeriod.values()[ord]; + regionTransferReportPluginData2 = // + regionTransferReportPluginData1.getCloneBuilder()// + .setReportPeriod(reportPeriod)// + .build(); + assertNotEquals(regionTransferReportPluginData2, regionTransferReportPluginData1); + + // change the report label + reportLabel = new SimpleReportLabel(1000); + regionTransferReportPluginData2 = // + regionTransferReportPluginData1.getCloneBuilder()// + .setReportLabel(reportLabel)// + .build(); + assertNotEquals(regionTransferReportPluginData2, regionTransferReportPluginData1); + + } + + } + + @Test + @UnitTestMethod(target = RegionTransferReportPluginData.class, name = "hashCode", args = {}) + public void testHashCode() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(9079768427072825406L); + + Set observedHashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 50; i++) { + // build a RegionTransferReportPluginData from the same random + // inputs + RegionTransferReportPluginData.Builder builder1 = RegionTransferReportPluginData.builder(); + RegionTransferReportPluginData.Builder builder2 = RegionTransferReportPluginData.builder(); + + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt(100)); + builder1.setReportLabel(reportLabel); + builder2.setReportLabel(reportLabel); + + ReportPeriod reportPeriod = ReportPeriod.values()[randomGenerator.nextInt(ReportPeriod.values().length)]; + builder1.setReportPeriod(reportPeriod); + builder2.setReportPeriod(reportPeriod); + + RegionTransferReportPluginData regionTransferReportPluginData1 = builder1.build(); + RegionTransferReportPluginData regionTransferReportPluginData2 = builder2.build(); + + // show that the hash code is stable + int hashCode = regionTransferReportPluginData1.hashCode(); + assertEquals(hashCode, regionTransferReportPluginData1.hashCode()); + assertEquals(hashCode, regionTransferReportPluginData1.hashCode()); + assertEquals(hashCode, regionTransferReportPluginData1.hashCode()); + assertEquals(hashCode, regionTransferReportPluginData1.hashCode()); + + // show that equal objects have equal hash codes + assertEquals(regionTransferReportPluginData1.hashCode(), regionTransferReportPluginData2.hashCode()); + + // collect the hashcode + observedHashCodes.add(regionTransferReportPluginData1.hashCode()); + } + + /* + * The hash codes should be dispersed -- we only show that they are unique + * values -- this is dependent on the random seed + */ + assertTrue(observedHashCodes.size() > 45); + + } + + @Test + @UnitTestMethod(target = RegionTransferReportPluginData.class, name = "toString", args = {}) + public void testToString() { + RegionTransferReportPluginData regionTransferReportPluginData = RegionTransferReportPluginData.builder()// + .setReportLabel(new SimpleReportLabel("RegionTransferReport"))// + .setReportPeriod(ReportPeriod.DAILY)// + .build(); + + String actualValue = regionTransferReportPluginData.toString(); + + String expectedValue = "RegionTransferReportPluginData [data=Data [" + + "reportLabel=SimpleReportLabel [value=RegionTransferReport], " + + "reportPeriod=DAILY]]"; + assertEquals(expectedValue, actualValue); + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/AT_RegionConstructionData.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/AT_RegionConstructionData.java new file mode 100644 index 000000000..68f37ee1c --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/AT_RegionConstructionData.java @@ -0,0 +1,201 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_RegionConstructionData { + @Test + @UnitTestMethod(target = RegionConstructionData.class, name = "builder", args = {}) + public void testBuilder() { + RegionConstructionData.Builder builder = RegionConstructionData.builder(); + + assertNotNull(builder); + } + + @Test + @UnitTestMethod(target = RegionConstructionData.class, name = "getValues", args = { Class.class }) + public void testGetValues() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3711237688912261992L); + RegionConstructionData.Builder builder = RegionConstructionData.builder(); + + RegionId regionId = new SimpleRegionId(1000); + + List expectedStringValues = new ArrayList<>(); + List expectedIntValues = new ArrayList<>(); + + for (int i = 0; i < 15; i++) { + int integerValue = randomGenerator.nextInt(100); + String stringValue = Integer.toString(randomGenerator.nextInt(100)); + + expectedIntValues.add(integerValue); + expectedStringValues.add(stringValue); + builder.addValue(stringValue).addValue(integerValue); + } + + builder.setRegionId(regionId); + + RegionConstructionData regionConstructionData = builder.build(); + + assertNotNull(regionConstructionData); + assertEquals(expectedIntValues, regionConstructionData.getValues(Integer.class)); + assertEquals(expectedStringValues, regionConstructionData.getValues(String.class)); + } + + @Test + @UnitTestMethod(target = RegionConstructionData.class, name = "getRegionPropertyValues", args = {}) + public void testGetRegionPropertyValues() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1088787935993630091L); + RegionConstructionData.Builder builder = RegionConstructionData.builder(); + + RegionId regionId = new SimpleRegionId(1000); + + Map expectedValues = new LinkedHashMap<>(); + + for (int i = 0; i < 15; i++) { + RegionPropertyId regionPropertyId = new SimpleRegionPropertyId(i * 2 + 5); + int integerValue = randomGenerator.nextInt(100); + + expectedValues.put(regionPropertyId, integerValue); + builder.setRegionPropertyValue(regionPropertyId, integerValue); + } + + builder.setRegionId(regionId); + + RegionConstructionData regionConstructionData = builder.build(); + + assertNotNull(regionConstructionData); + assertEquals(expectedValues, regionConstructionData.getRegionPropertyValues()); + } + + @Test + @UnitTestMethod(target = RegionConstructionData.class, name = "getRegionId", args = {}) + public void testGetRegionId() { + RegionConstructionData.Builder builder = RegionConstructionData.builder(); + + RegionId regionId = new SimpleRegionId(1000); + builder.setRegionId(regionId); + + RegionConstructionData regionConstructionData = builder.build(); + + assertNotNull(regionConstructionData); + assertEquals(regionId, regionConstructionData.getRegionId()); + } + + @Test + @UnitTestMethod(target = RegionConstructionData.Builder.class, name = "build", args = {}) + public void testBuild() { + RegionConstructionData.Builder builder = RegionConstructionData.builder(); + + RegionId regionId = new SimpleRegionId(1000); + builder.setRegionId(regionId); + + RegionConstructionData regionConstructionData = builder.build(); + + assertNotNull(regionConstructionData); + + // precondition: null region id + ContractException contractException = assertThrows(ContractException.class, () -> RegionConstructionData.builder().build()); + assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = RegionConstructionData.Builder.class, name = "setRegionPropertyValue", args = { RegionPropertyId.class, Object.class }) + public void testSetRegionPropertyValue() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5396117443174616496L); + RegionConstructionData.Builder builder = RegionConstructionData.builder(); + + RegionId regionId = new SimpleRegionId(1000); + + Map expectedValues = new LinkedHashMap<>(); + + for (int i = 0; i < 15; i++) { + RegionPropertyId regionPropertyId = new SimpleRegionPropertyId(i * 2 + 5); + int integerValue = randomGenerator.nextInt(100); + + expectedValues.put(regionPropertyId, integerValue); + builder.setRegionPropertyValue(regionPropertyId, integerValue); + } + + builder.setRegionId(regionId); + + RegionConstructionData regionConstructionData = builder.build(); + + assertNotNull(regionConstructionData); + assertEquals(expectedValues, regionConstructionData.getRegionPropertyValues()); + + // precondition: null property id + ContractException contractException = assertThrows(ContractException.class, () -> RegionConstructionData.builder().setRegionId(regionId).setRegionPropertyValue(null, 100).build()); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // precondition: null property value + contractException = assertThrows(ContractException.class, () -> RegionConstructionData.builder().setRegionId(regionId).setRegionPropertyValue(new SimpleRegionPropertyId(100), null).build()); + assertEquals(PropertyError.NULL_PROPERTY_VALUE, contractException.getErrorType()); + + // precondition: duplicate property value + RegionPropertyId regionPropertyId = new SimpleRegionPropertyId(100); + contractException = assertThrows(ContractException.class, + () -> RegionConstructionData.builder().setRegionId(regionId).setRegionPropertyValue(regionPropertyId, 100).setRegionPropertyValue(regionPropertyId, 150).build()); + assertEquals(PropertyError.DUPLICATE_PROPERTY_VALUE_ASSIGNMENT, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = RegionConstructionData.Builder.class, name = "addValue", args = { Object.class }) + public void testAddValue() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4498242625038387679L); + RegionConstructionData.Builder builder = RegionConstructionData.builder(); + + RegionId regionId = new SimpleRegionId(1000); + + List expectedStringValues = new ArrayList<>(); + List expectedIntValues = new ArrayList<>(); + + for (int i = 0; i < 15; i++) { + int integerValue = randomGenerator.nextInt(100); + String stringValue = Integer.toString(randomGenerator.nextInt(100)); + + expectedIntValues.add(integerValue); + expectedStringValues.add(stringValue); + builder.addValue(stringValue).addValue(integerValue); + } + + builder.setRegionId(regionId); + + RegionConstructionData regionConstructionData = builder.build(); + + assertNotNull(regionConstructionData); + assertEquals(expectedIntValues, regionConstructionData.getValues(Integer.class)); + assertEquals(expectedStringValues, regionConstructionData.getValues(String.class)); + } + + @Test + @UnitTestMethod(target = RegionConstructionData.Builder.class, name = "setRegionId", args = { RegionId.class }) + public void testSetRegionId() { + RegionConstructionData.Builder builder = RegionConstructionData.builder(); + + RegionId regionId = new SimpleRegionId(1000); + builder.setRegionId(regionId); + + RegionConstructionData regionConstructionData = builder.build(); + + assertNotNull(regionConstructionData); + assertEquals(regionId, regionConstructionData.getRegionId()); + + // precondition: null region id + ContractException contractException = assertThrows(ContractException.class, () -> RegionConstructionData.builder().build()); + assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/AT_RegionError.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/AT_RegionError.java new file mode 100644 index 000000000..aefc9da73 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/AT_RegionError.java @@ -0,0 +1,28 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.support; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestMethod; + +public class AT_RegionError { + + @Test + @UnitTestMethod(target = RegionError.class, name = "getDescription", args = {}) + public void test() { + // show that each description is a unique, non-null and non-empty string + Set descriptions = new LinkedHashSet<>(); + for (RegionError regionError : RegionError.values()) { + String description = regionError.getDescription(); + assertNotNull(description, "null description for " + regionError); + assertTrue(description.length() > 0, "empty string for " + regionError); + boolean unique = descriptions.add(description); + assertTrue(unique, "description for " + regionError + " is not unique"); + } + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/AT_RegionFilter.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/AT_RegionFilter.java new file mode 100644 index 000000000..d648eddb1 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/AT_RegionFilter.java @@ -0,0 +1,291 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.support; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.FilterSensitivity; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters.Filter; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.TestPartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.regions.events.PersonRegionUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.RegionsTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.RegionsTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.TestRegionId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_RegionFilter { + + @Test + @UnitTestConstructor(target = RegionFilter.class, args = { RegionId[].class }) + public void testConstructorWithArray() { + Factory factory = RegionsTestPluginFactory.factory(100, 4602637405159227338L, false, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + /* precondition: if the set is null */ + Set regionIds = null; + + assertThrows(RuntimeException.class, () -> new RegionFilter(regionIds)); + + /* precondition: if the region is unknown */ + ContractException contractException = assertThrows(ContractException.class, + () -> new RegionFilter(TestRegionId.getUnknownRegionId()).validate(testPartitionsContext)); + assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); + + // precondition: null region id + contractException = assertThrows(ContractException.class, + () -> new RegionFilter(null, TestRegionId.REGION_1).validate(testPartitionsContext)); + assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestConstructor(target = RegionFilter.class, args = { Set.class }) + public void testConstructorWithSet() { + Factory factory = RegionsTestPluginFactory.factory(100, 4602637405159227338L, false, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + /* precondition: if the set is null */ + Set regionIds = null; + + assertThrows(RuntimeException.class, () -> new RegionFilter(regionIds)); + + /* precondition: if the region is unknown */ + ContractException contractException = assertThrows(ContractException.class, + () -> new RegionFilter(TestRegionId.getUnknownRegionId()).validate(testPartitionsContext)); + assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); + + // precondition: null region id + contractException = assertThrows(ContractException.class, + () -> new RegionFilter(null, TestRegionId.REGION_1).validate(testPartitionsContext)); + assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = RegionFilter.class, name = "getFilterSensitivities", args = {}) + public void testGetFilterSensitivities() { + Factory factory = RegionsTestPluginFactory.factory(100, 2916119612012950359L, false, (c) -> { + + Filter filter = new RegionFilter(TestRegionId.REGION_1); + + Set> filterSensitivities = filter.getFilterSensitivities(); + assertNotNull(filterSensitivities); + assertEquals(filterSensitivities.size(), 1); + + FilterSensitivity filterSensitivity = filterSensitivities.iterator().next(); + assertEquals(PersonRegionUpdateEvent.class, filterSensitivity.getEventClass()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = RegionFilter.class, name = "evaluate", args = { PartitionsContext.class, PersonId.class }) + public void testEvaluate() { + Factory factory = RegionsTestPluginFactory.factory(100, 28072097989345652L, false, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + + Filter filter = new RegionFilter(TestRegionId.REGION_1, TestRegionId.REGION_2); + + for (PersonId personId : peopleDataManager.getPeople()) { + boolean expected = regionsDataManager.getPersonRegion(personId).equals(TestRegionId.REGION_1) + || regionsDataManager.getPersonRegion(personId).equals(TestRegionId.REGION_2); + boolean actual = filter.evaluate(testPartitionsContext, personId); + assertEquals(expected, actual); + } + + /* precondition: if the context is null */ + assertThrows(RuntimeException.class, () -> filter.evaluate(null, new PersonId(0))); + + /* precondition: if the person id is null */ + assertThrows(RuntimeException.class, () -> filter.evaluate(testPartitionsContext, null)); + + /* precondition: if the person id is unknown */ + assertThrows(RuntimeException.class, () -> filter.evaluate(testPartitionsContext, new PersonId(123412342))); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = RegionFilter.class, name = "toString", args = {}) + public void testToString() { + Filter filter = new RegionFilter(TestRegionId.REGION_1, TestRegionId.REGION_2); + + String expectedString = "RegionFilter [regionIds=[REGION_1, REGION_2]]"; + + assertEquals(expectedString, filter.toString()); + } + + @Test + @UnitTestMethod(target = RegionFilter.class, name = "validate", args = { PartitionsContext.class }) + public void testValidate() { + Factory factory = RegionsTestPluginFactory.factory(100, 28072097989345652L, false, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + Filter filter = new RegionFilter(TestRegionId.REGION_1, TestRegionId.REGION_2); + + assertDoesNotThrow(() -> filter.validate(testPartitionsContext)); + + // precondition: null simulation context + ContractException contractException = assertThrows(ContractException.class, () -> filter.validate(null)); + assertEquals(NucleusError.NULL_SIMULATION_CONTEXT, contractException.getErrorType()); + + RegionId badRegion = null; + // precondition: region id is null + Filter badFilter1 = new RegionFilter(badRegion); + contractException = assertThrows(ContractException.class, () -> badFilter1.validate(testPartitionsContext)); + assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); + + // precondition: region id is unknown + Filter badFilter2 = new RegionFilter(TestRegionId.getUnknownRegionId()); + contractException = assertThrows(ContractException.class, () -> badFilter2.validate(testPartitionsContext)); + assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = RegionFilter.class, name = "getRegionIds", args = {}) + public void testGetRegionIds() { + + // test against both constructors + RegionFilter regionFilter = new RegionFilter(TestRegionId.REGION_1, TestRegionId.REGION_3, + TestRegionId.REGION_6); + Set expectedRegionIds = new LinkedHashSet<>(); + expectedRegionIds.add(TestRegionId.REGION_1); + expectedRegionIds.add(TestRegionId.REGION_3); + expectedRegionIds.add(TestRegionId.REGION_6); + Set actualRegionIds = regionFilter.getRegionIds(); + assertEquals(expectedRegionIds, actualRegionIds); + + regionFilter = new RegionFilter(expectedRegionIds); + actualRegionIds = regionFilter.getRegionIds(); + assertEquals(expectedRegionIds, actualRegionIds); + } + + private RegionFilter getRandomRegionFilter(long seed) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + Random random = new Random(randomGenerator.nextLong()); + List list = Arrays.asList(TestRegionId.values()); + Collections.shuffle(list, random); + int count = randomGenerator.nextInt(list.size()) + 1; + Set selectedRegions = new LinkedHashSet<>(); + for (int i = 0; i < count; i++) { + selectedRegions.add(list.get(i)); + } + return new RegionFilter(selectedRegions); + } + + @Test + @UnitTestMethod(target = RegionFilter.class, name = "equals", args = { Object.class }) + public void testEquals() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8665861319201143941L); + + // never equal to null + for (int i = 0; i < 30; i++) { + RegionFilter regionFilter = getRandomRegionFilter(randomGenerator.nextLong()); + assertFalse(regionFilter.equals(null)); + } + + // reflexive + for (int i = 0; i < 30; i++) { + RegionFilter regionFilter = getRandomRegionFilter(randomGenerator.nextLong()); + assertTrue(regionFilter.equals(regionFilter)); + } + + // symmetric, transitive, consistent + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + RegionFilter regionFilter1 = getRandomRegionFilter(seed); + RegionFilter regionFilter2 = getRandomRegionFilter(seed); + for (int j = 0; j < 5; j++) { + assertTrue(regionFilter1.equals(regionFilter2)); + assertTrue(regionFilter2.equals(regionFilter1)); + } + } + + // different inputs yield non-equal objects + for (int i = 0; i < 100; i++) { + RegionFilter regionFilter1 = getRandomRegionFilter(randomGenerator.nextLong()); + RegionFilter regionFilter2 = getRandomRegionFilter(randomGenerator.nextLong()); + + if (!regionFilter1.getRegionIds().equals(regionFilter2.getRegionIds())) { + assertNotEquals(regionFilter1, regionFilter2); + } + } + + } + + @Test + @UnitTestMethod(target = RegionFilter.class, name = "hashCode", args = {}) + public void testHashCode() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7083246786855409948L); + + // equal objects have equal hash codes + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + RegionFilter regionFilter1 = getRandomRegionFilter(seed); + RegionFilter regionFilter2 = getRandomRegionFilter(seed); + + assertEquals(regionFilter1, regionFilter2); + assertEquals(regionFilter1.hashCode(), regionFilter2.hashCode()); + + } + + // hash codes are reasonably distributed -- there are only 64 possible values + // returned by the random function, so our approach is to eliminate collisions. + Set regionFilters = new LinkedHashSet<>(); + for (int i = 0; i < 300; i++) { + RegionFilter regionFilter = getRandomRegionFilter(randomGenerator.nextLong()); + regionFilters.add(regionFilter); + } + + assertTrue(regionFilters.size()>50); + + Set hashCodes = new LinkedHashSet<>(); + for(RegionFilter regionFilter : regionFilters) { + hashCodes.add(regionFilter.hashCode()); + } + assertEquals(hashCodes.size(), regionFilters.size()); + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/AT_RegionLabeler.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/AT_RegionLabeler.java new file mode 100644 index 000000000..c722bec5c --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/AT_RegionLabeler.java @@ -0,0 +1,228 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.function.Function; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.LabelerSensitivity; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.TestPartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonConstructionData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.regions.events.PersonRegionUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.RegionsTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.RegionsTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.TestRegionId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + +public class AT_RegionLabeler { + private static class LocalRegionLabeler extends RegionLabeler { + private final Function regionLabelingFunction; + + public LocalRegionLabeler(Function regionLabelingFunction) { + this.regionLabelingFunction = regionLabelingFunction; + } + + @Override + protected Object getLabelFromRegionId(RegionId regionId) { + return regionLabelingFunction.apply(regionId); + } + } + + @Test + @UnitTestConstructor(target = RegionLabeler.class, args = {}) + public void testConstructor() { + assertNotNull(new LocalRegionLabeler((c) -> null)); + } + + @Test + @UnitTestMethod(target = RegionLabeler.class, name = "getId", args = {}) + public void testGetId() { + assertEquals(RegionId.class, new LocalRegionLabeler((c) -> null).getId()); + } + + @Test + @UnitTestMethod(target = RegionLabeler.class, name = "getCurrentLabel", args = { PartitionsContext.class, + PersonId.class }) + public void testGetLabel() { + + /* + * Create a region labeler from a function. Have an agent apply the function + * directly to a person's region to get a label for that person. Get the label + * from the region labeler from the person id alone. Compare the two labels for + * equality. + */ + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // build a region labeler with a function that can be tested + Function function = (c) -> { + TestRegionId testRegionId = (TestRegionId) c; + return testRegionId.ordinal(); + }; + + RegionLabeler regionLabeler = new LocalRegionLabeler(function); + + // add a few people to the simulation spread across the various + // regions + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + int numberOfPeople = 2 * TestRegionId.size(); + + // show that there will be people + assertTrue(numberOfPeople > 0); + + for (int i = 0; i < numberOfPeople; i++) { + RegionId regionId = TestRegionId.values()[i % TestRegionId.size()]; + PersonConstructionData personConstructionData = PersonConstructionData.builder().add(regionId).build(); + peopleDataManager.addPerson(personConstructionData); + } + })); + + /* + * Have the agent show that the region labeler created above produces a label + * for each person that is consistent with the function passed to the region + * labeler. + */ + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + + List people = peopleDataManager.getPeople(); + for (PersonId personId : people) { + + // get the person's region and apply the function directly + RegionId regionId = regionsDataManager.getPersonRegion(personId); + Object expectedLabel = function.apply(regionId); + + // get the label from the person id + Object actualLabel = regionLabeler.getCurrentLabel(testPartitionsContext, personId); + + // show that the two labels are equal + assertEquals(expectedLabel, actualLabel); + + } + })); + + // test preconditions + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + // if the person does not exist + ContractException contractException = assertThrows(ContractException.class, + () -> regionLabeler.getCurrentLabel(testPartitionsContext, new PersonId(100000))); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + // if the person id is null + contractException = assertThrows(ContractException.class, + () -> regionLabeler.getCurrentLabel(testPartitionsContext, null)); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = RegionsTestPluginFactory.factory(0, 4893773537497436066L, false, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = RegionLabeler.class, name = "getLabelerSensitivities", args = {}) + public void testGetLabelerSensitivities() { + + /* + * Get the labeler sensitivities and show that they are consistent with their + * documented behaviors. + */ + + RegionLabeler regionLabeler = new LocalRegionLabeler((c) -> null); + + Set> labelerSensitivities = regionLabeler.getLabelerSensitivities(); + + // show that there is exactly one sensitivity + assertEquals(1, labelerSensitivities.size()); + + // show that the sensitivity is associated with + // PersonRegionUpdateEvent + LabelerSensitivity labelerSensitivity = labelerSensitivities.iterator().next(); + assertEquals(PersonRegionUpdateEvent.class, labelerSensitivity.getEventClass()); + + // show that the sensitivity will return the person id from a + // PersonRegionUpdateEvent + PersonId personId = new PersonId(56); + PersonRegionUpdateEvent personRegionUpdateEvent = new PersonRegionUpdateEvent(personId, TestRegionId.REGION_1, + TestRegionId.REGION_2); + labelerSensitivity.getPersonId(personRegionUpdateEvent); + + } + + @Test + @UnitTestMethod(target = RegionLabeler.class, name = "getPastLabel", args = { PartitionsContext.class, + Event.class }) + public void testGetPastLabel() { + + Factory factory = RegionsTestPluginFactory.factory(30, 349819763474394472L, true, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + + Function func = (g) -> { + TestRegionId testRegionId = (TestRegionId) g; + return testRegionId.ordinal(); + }; + + RegionLabeler regionLabeler = new LocalRegionLabeler(func); + List regions = new ArrayList<>(regionsDataManager.getRegionIds()); + + // Person region update event + for (PersonId personId : peopleDataManager.getPeople()) { + RegionId personRegion = regionsDataManager.getPersonRegion(personId); + RegionId nextRegion = regions.get(randomGenerator.nextInt(regions.size())); + regionsDataManager.setPersonRegion(personId, nextRegion); + PersonRegionUpdateEvent personRegionUpdateEvent = new PersonRegionUpdateEvent(personId, personRegion, + nextRegion); + Object expectedLabel = func.apply(personRegion); + Object actualLabel = regionLabeler.getPastLabel(testPartitionsContext, personRegionUpdateEvent); + assertEquals(expectedLabel, actualLabel); + } + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = RegionLabeler.class, name = "toString", args = {}) + public void testToString() { + RegionLabeler regionLabeler = new LocalRegionLabeler((r) -> null); + String actualValue = regionLabeler.toString(); + String expectedValue = "RegionLabeler []"; + assertEquals(expectedValue, actualValue); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/AT_RegionPropertyDefinitionInitialization.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/AT_RegionPropertyDefinitionInitialization.java new file mode 100644 index 000000000..aa61b273d --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/AT_RegionPropertyDefinitionInitialization.java @@ -0,0 +1,187 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.math3.random.RandomGenerator; +import org.apache.commons.math3.util.Pair; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_RegionPropertyDefinitionInitialization { + + @Test + @UnitTestMethod(target = RegionPropertyDefinitionInitialization.class, name = "builder", args = {}) + public void testBuilder() { + RegionPropertyDefinitionInitialization.Builder builder = RegionPropertyDefinitionInitialization.builder(); + + assertNotNull(builder); + } + + @Test + @UnitTestMethod(target = RegionPropertyDefinitionInitialization.class, name = "getPropertyDefinition", args = {}) + public void testGetPropertyDefinition() { + RegionPropertyDefinitionInitialization.Builder builder = RegionPropertyDefinitionInitialization.builder(); + RegionPropertyId regionPropertyId = new SimpleRegionPropertyId(1000); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setDefaultValue(3).setType(Integer.class).build(); + builder.setPropertyDefinition(propertyDefinition).setRegionPropertyId(regionPropertyId); + + RegionPropertyDefinitionInitialization propertyDefinitionInitialization = builder.build(); + + assertNotNull(propertyDefinitionInitialization); + assertEquals(propertyDefinition, propertyDefinitionInitialization.getPropertyDefinition()); + } + + @Test + @UnitTestMethod(target = RegionPropertyDefinitionInitialization.class, name = "getPropertyValues", args = {}) + public void testGetPropertyValues() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1981422884617107939L); + RegionPropertyDefinitionInitialization.Builder builder = RegionPropertyDefinitionInitialization.builder(); + RegionPropertyId regionPropertyId = new SimpleRegionPropertyId(1000); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setDefaultValue(3).setType(Integer.class).build(); + builder.setPropertyDefinition(propertyDefinition).setRegionPropertyId(regionPropertyId); + + List> expectedValues = new ArrayList<>(); + + for (int i = 0; i < 15; i++) { + int value = randomGenerator.nextInt(100); + RegionId regionId = new SimpleRegionId(i * 2 + 5); + builder.addPropertyValue(regionId, value); + expectedValues.add(new Pair<>(regionId, value)); + } + + RegionPropertyDefinitionInitialization propertyDefinitionInitialization = builder.build(); + + assertNotNull(propertyDefinitionInitialization); + assertEquals(expectedValues, propertyDefinitionInitialization.getPropertyValues()); + } + + @Test + @UnitTestMethod(target = RegionPropertyDefinitionInitialization.class, name = "getRegionPropertyId", args = {}) + public void testGetRegionPropertyId() { + RegionPropertyDefinitionInitialization.Builder builder = RegionPropertyDefinitionInitialization.builder(); + RegionPropertyId regionPropertyId = new SimpleRegionPropertyId(1000); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setDefaultValue(3).setType(Integer.class).build(); + builder.setPropertyDefinition(propertyDefinition).setRegionPropertyId(regionPropertyId); + + RegionPropertyDefinitionInitialization propertyDefinitionInitialization = builder.build(); + + assertNotNull(propertyDefinitionInitialization); + assertEquals(regionPropertyId, propertyDefinitionInitialization.getRegionPropertyId()); + } + + @Test + @UnitTestMethod(target = RegionPropertyDefinitionInitialization.Builder.class, name = "build", args = {}) + public void testBuild() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1981422884617107939L); + RegionPropertyDefinitionInitialization.Builder builder = RegionPropertyDefinitionInitialization.builder(); + RegionPropertyId regionPropertyId = new SimpleRegionPropertyId(1000); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setDefaultValue(3).setType(Integer.class).build(); + builder.setPropertyDefinition(propertyDefinition).setRegionPropertyId(regionPropertyId); + + for (int i = 0; i < 15; i++) { + int value = randomGenerator.nextInt(100); + RegionId regionId = new SimpleRegionId(i * 2 + 5); + builder.addPropertyValue(regionId, value); + } + + RegionPropertyDefinitionInitialization propertyDefinitionInitialization = builder.build(); + + assertNotNull(propertyDefinitionInitialization); + + // precondition: property definition is null + PropertyDefinition nullPropertyDefinition = null; + ContractException contractException = assertThrows(ContractException.class, () -> builder.setPropertyDefinition(nullPropertyDefinition).setRegionPropertyId(regionPropertyId).build()); + assertEquals(PropertyError.NULL_PROPERTY_DEFINITION, contractException.getErrorType()); + + // precondition: region property id is null + RegionPropertyId nullRegionPropertyId = null; + contractException = assertThrows(ContractException.class, () -> builder.setPropertyDefinition(propertyDefinition).setRegionPropertyId(nullRegionPropertyId).build()); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // precondition: incompatible property value + PropertyDefinition badPropertyDefinition = PropertyDefinition.builder().setDefaultValue(false).setType(Boolean.class).build(); + contractException = assertThrows(ContractException.class, + () -> builder.setPropertyDefinition(badPropertyDefinition).setRegionPropertyId(regionPropertyId).addPropertyValue(new SimpleRegionId(1000), 100).build()); + assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = RegionPropertyDefinitionInitialization.Builder.class, name = "setPropertyDefinition", args = { PropertyDefinition.class }) + public void testSetPropertyDefinition() { + RegionPropertyDefinitionInitialization.Builder builder = RegionPropertyDefinitionInitialization.builder(); + RegionPropertyId regionPropertyId = new SimpleRegionPropertyId(1000); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setDefaultValue(3).setType(Integer.class).build(); + builder.setPropertyDefinition(propertyDefinition).setRegionPropertyId(regionPropertyId); + + RegionPropertyDefinitionInitialization propertyDefinitionInitialization = builder.build(); + + assertNotNull(propertyDefinitionInitialization); + assertEquals(propertyDefinition, propertyDefinitionInitialization.getPropertyDefinition()); + + // precondition: property definition is null + ContractException contractException = assertThrows(ContractException.class, () -> builder.setPropertyDefinition(null)); + assertEquals(PropertyError.NULL_PROPERTY_DEFINITION, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = RegionPropertyDefinitionInitialization.Builder.class, name = "addPropertyValue", args = { RegionId.class, Object.class }) + public void testAddPropertyValue() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(9052754083757003238L); + RegionPropertyDefinitionInitialization.Builder builder = RegionPropertyDefinitionInitialization.builder(); + RegionPropertyId regionPropertyId = new SimpleRegionPropertyId(1000); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setDefaultValue(3).setType(Integer.class).build(); + builder.setPropertyDefinition(propertyDefinition).setRegionPropertyId(regionPropertyId); + + List> expectedValues = new ArrayList<>(); + + for (int i = 0; i < 15; i++) { + int value = randomGenerator.nextInt(100); + RegionId regionId = new SimpleRegionId(i * 2 + 5); + builder.addPropertyValue(regionId, value); + expectedValues.add(new Pair<>(regionId, value)); + } + + RegionPropertyDefinitionInitialization propertyDefinitionInitialization = builder.build(); + + assertNotNull(propertyDefinitionInitialization); + assertEquals(expectedValues, propertyDefinitionInitialization.getPropertyValues()); + + // precondition: region id is null + + ContractException contractException = assertThrows(ContractException.class, () -> RegionPropertyDefinitionInitialization.builder().addPropertyValue(null, 1000)); + assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); + + // precondition: value is null + contractException = assertThrows(ContractException.class, () -> RegionPropertyDefinitionInitialization.builder().addPropertyValue(new SimpleRegionId(1000), null)); + assertEquals(PropertyError.NULL_PROPERTY_VALUE, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = RegionPropertyDefinitionInitialization.Builder.class, name = "setRegionPropertyId", args = { RegionPropertyId.class }) + public void testSetRegionPropertyId() { + RegionPropertyDefinitionInitialization.Builder builder = RegionPropertyDefinitionInitialization.builder(); + RegionPropertyId regionPropertyId = new SimpleRegionPropertyId(1000); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setDefaultValue(3).setType(Integer.class).build(); + builder.setPropertyDefinition(propertyDefinition).setRegionPropertyId(regionPropertyId); + + RegionPropertyDefinitionInitialization propertyDefinitionInitialization = builder.build(); + + assertNotNull(propertyDefinitionInitialization); + assertEquals(regionPropertyId, propertyDefinitionInitialization.getRegionPropertyId()); + + // precondition: region property id is null + ContractException contractException = assertThrows(ContractException.class, () -> builder.setRegionPropertyId(null)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/AT_RegionPropertyDimension.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/AT_RegionPropertyDimension.java new file mode 100644 index 000000000..057e4b152 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/AT_RegionPropertyDimension.java @@ -0,0 +1,379 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.DimensionContext; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.TestRegionId; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.TestRegionPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_RegionPropertyDimension { + @Test + @UnitTestMethod(target = RegionPropertyDimension.Builder.class, name = "addValue", args = { Object.class }) + public void testAddValue() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3468803942988565031L); + + for (int i = 0; i < 50; i++) { + List expectedValues = new ArrayList<>(); + + RegionPropertyDimension.Builder builder = RegionPropertyDimension.builder()// + .setRegionId(new RegionId() { + }).setRegionPropertyId(TestRegionPropertyId.REGION_PROPERTY_3_DOUBLE_MUTABLE); + int n = randomGenerator.nextInt(10); + for (int j = 0; j < n; j++) { + double value = randomGenerator.nextDouble(); + expectedValues.add(value); + builder.addValue(value); + } + RegionPropertyDimension regionPropertyDimension = builder.build(); + + List actualValues = regionPropertyDimension.getValues(); + assertEquals(expectedValues, actualValues); + } + + // precondition test : if the value is null + ContractException contractException = assertThrows(ContractException.class, + () -> RegionPropertyDimension.builder().addValue(null)); + assertEquals(PropertyError.NULL_PROPERTY_VALUE, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = RegionPropertyDimension.Builder.class, name = "build", args = {}) + public void testBuild() { + RegionPropertyDimension regionPropertyDimension = // + RegionPropertyDimension.builder()// + .setRegionId(new RegionId() { + }).setRegionPropertyId(TestRegionPropertyId.REGION_PROPERTY_3_DOUBLE_MUTABLE).build(); + assertNotNull(regionPropertyDimension); + + // precondition test : if the global property id is not assigned + ContractException contractException = assertThrows(ContractException.class, + () -> RegionPropertyDimension.builder().setRegionId(new RegionId() { + }).build()); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // if the regionId was not assigned + contractException = assertThrows(ContractException.class, () -> RegionPropertyDimension.builder() + .setRegionPropertyId(TestRegionPropertyId.REGION_PROPERTY_3_DOUBLE_MUTABLE).build()); + assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = RegionPropertyDimension.Builder.class, name = "setRegionId", args = { RegionId.class }) + public void testSetRegionId() { + for (int i = 0; i < 10; i++) { + RegionId regionId = new RegionId() { + }; + + RegionPropertyDimension.Builder builder = RegionPropertyDimension.builder()// + .setRegionPropertyId(TestRegionPropertyId.REGION_PROPERTY_3_DOUBLE_MUTABLE).setRegionId(regionId); + + RegionPropertyDimension regionPropertyDimension = builder.build(); + assertEquals(regionId, regionPropertyDimension.getRegionId()); + } + + // precondition test : if the value is null + ContractException contractException = assertThrows(ContractException.class, + () -> RegionPropertyDimension.builder().setRegionId(null)); + assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = RegionPropertyDimension.Builder.class, name = "setRegionPropertyId", args = { + RegionPropertyId.class }) + public void testSetRegionPropertyId() { + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { + + RegionPropertyDimension.Builder builder = RegionPropertyDimension.builder()// + .setRegionId(new RegionId() { + }).setRegionPropertyId(testRegionPropertyId); + + RegionPropertyDimension regionPropertyDimension = builder.build(); + assertEquals(testRegionPropertyId, regionPropertyDimension.getRegionPropertyId()); + } + + // precondition test : if the value is null + ContractException contractException = assertThrows(ContractException.class, + () -> RegionPropertyDimension.builder().setRegionPropertyId(null)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = RegionPropertyDimension.class, name = "builder", args = {}) + public void testBuilder() { + assertNotNull(RegionPropertyDimension.builder()); + } + + @Test + @UnitTestMethod(target = RegionPropertyDimension.class, name = "executeLevel", args = { DimensionContext.class, + int.class }) + public void testExecuteLevel() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2924521933883974690L); + + // run several test cases + for (int i = 0; i < 30; i++) { + + RegionId regionId = new RegionId() { + }; + // select a random number of levels + int levelCount = randomGenerator.nextInt(10); + // select a random property id + TestRegionPropertyId targetPropertyId = TestRegionPropertyId + .getRandomMutableRegionPropertyId(randomGenerator); + + // generate random values for the level + List expectedValues = new ArrayList<>(); + for (int j = 0; j < levelCount; j++) { + expectedValues.add(targetPropertyId.getRandomPropertyValue(randomGenerator)); + } + + // create a RegionPropertyDimension with the level values + RegionPropertyDimension.Builder dimBuilder = RegionPropertyDimension.builder()// + .setRegionId(regionId).setRegionPropertyId(targetPropertyId); + + for (Object value : expectedValues) { + dimBuilder.addValue(value); + } + + RegionPropertyDimension regionPropertyDimension = dimBuilder.build(); + + // show that for each level the dimension properly assigns the value + // to a global property data builder + for (int level = 0; level < levelCount; level++) { + /* + * Create a RegionsPluginData, filling it with the test property definitions and + * any values that are required + */ + RegionsPluginData.Builder pluginDataBuilder = RegionsPluginData.builder(); + for (TestRegionPropertyId propertyId : TestRegionPropertyId.values()) { + PropertyDefinition propertyDefinition = propertyId.getPropertyDefinition(); + pluginDataBuilder.defineRegionProperty(propertyId, propertyDefinition); + + if (propertyDefinition.getDefaultValue().isEmpty()) { + pluginDataBuilder.setRegionPropertyValue(regionId, propertyId, + propertyId.getRandomPropertyValue(randomGenerator)); + } + } + + pluginDataBuilder.addRegion(regionId); + // Create a dimension context that contain the plugin data + // builder + DimensionContext.Builder dimensionContextBuilder = DimensionContext.builder(); + + pluginDataBuilder = (RegionsPluginData.Builder) dimensionContextBuilder.add(pluginDataBuilder.build()); + DimensionContext dimensionContext = dimensionContextBuilder.build(); + + // execute the dimension with the level + regionPropertyDimension.executeLevel(dimensionContext, level); + + /* + * get the RegionsPluginData from the corresponding builder + */ + RegionsPluginData regionsPluginData = pluginDataBuilder.build(); + + /* + * show that the RegionsPluginData has the value we expect for the given level + */ + + Map regionPropertyValues = regionsPluginData + .getRegionPropertyValues(regionId); + + assertTrue(regionPropertyValues.containsKey(targetPropertyId)); + + Object actualValue = regionPropertyValues.get(targetPropertyId); + Object expectedValue = expectedValues.get(level); + assertEquals(expectedValue, actualValue); + } + } + } + + @Test + @UnitTestMethod(target = RegionPropertyDimension.class, name = "getExperimentMetaData", args = {}) + public void testGetExperimentMetaData() { + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { + + List expectedExperimentMetaData = new ArrayList<>(); + expectedExperimentMetaData.add(testRegionPropertyId.toString()); + + RegionPropertyDimension.Builder builder = RegionPropertyDimension.builder()// + .setRegionId(new RegionId() { + }).setRegionPropertyId(testRegionPropertyId); + + RegionPropertyDimension regionPropertyDimension = builder.build(); + assertEquals(expectedExperimentMetaData, regionPropertyDimension.getExperimentMetaData()); + } + } + + @Test + @UnitTestMethod(target = RegionPropertyDimension.class, name = "getRegionId", args = {}) + public void testGetRegionId() { + for (int i = 0; i < 10; i++) { + RegionId regionId = new RegionId() { + }; + RegionPropertyDimension.Builder builder = RegionPropertyDimension.builder()// + .setRegionId(regionId).setRegionPropertyId(TestRegionPropertyId.REGION_PROPERTY_3_DOUBLE_MUTABLE); + + RegionPropertyDimension regionPropertyDimension = builder.build(); + assertEquals(regionId, regionPropertyDimension.getRegionId()); + } + } + + @Test + @UnitTestMethod(target = RegionPropertyDimension.class, name = "getRegionPropertyId", args = {}) + public void testGetRegionPropertyId() { + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { + + RegionPropertyDimension.Builder builder = RegionPropertyDimension.builder()// + .setRegionId(new RegionId() { + }).setRegionPropertyId(testRegionPropertyId); + + RegionPropertyDimension regionPropertyDimension = builder.build(); + assertEquals(testRegionPropertyId, regionPropertyDimension.getRegionPropertyId()); + } + } + + @Test + @UnitTestMethod(target = RegionPropertyDimension.class, name = "getValues", args = {}) + public void testGetValues() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4581428044056639458L); + + for (int i = 0; i < 50; i++) { + List expectedValues = new ArrayList<>(); + + RegionPropertyDimension.Builder builder = RegionPropertyDimension.builder()// + .setRegionId(new RegionId() { + }).setRegionPropertyId(TestRegionPropertyId.REGION_PROPERTY_3_DOUBLE_MUTABLE); + int n = randomGenerator.nextInt(10); + for (int j = 0; j < n; j++) { + double value = randomGenerator.nextDouble(); + expectedValues.add(value); + builder.addValue(value); + } + RegionPropertyDimension regionPropertyDimension = builder.build(); + + List actualValues = regionPropertyDimension.getValues(); + assertEquals(expectedValues, actualValues); + } + } + + @Test + @UnitTestMethod(target = RegionPropertyDimension.class, name = "levelCount", args = {}) + public void testLevelCount() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8913942504408118065L); + + for (int i = 0; i < 50; i++) { + + RegionPropertyDimension.Builder builder = RegionPropertyDimension.builder()// + .setRegionId(new RegionId() { + }).setRegionPropertyId(TestRegionPropertyId.REGION_PROPERTY_3_DOUBLE_MUTABLE); + int n = randomGenerator.nextInt(10); + for (int j = 0; j < n; j++) { + double value = randomGenerator.nextDouble(); + builder.addValue(value); + } + RegionPropertyDimension regionPropertyDimension = builder.build(); + + assertEquals(n, regionPropertyDimension.levelCount()); + } + } + + private RegionPropertyDimension getRandomRegionPropertyDimension(long seed) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + RegionPropertyDimension.Builder builder = RegionPropertyDimension.builder(); + builder.setRegionId(TestRegionId.getRandomRegionId(randomGenerator)); + TestRegionPropertyId regionPropertyId = TestRegionPropertyId.getRandomRegionPropertyId(randomGenerator); + builder.setRegionPropertyId(TestRegionPropertyId.getRandomRegionPropertyId(randomGenerator)); + int count = randomGenerator.nextInt(3) + 1; + for (int i = 0; i < count; i++) { + Object propertyValue = regionPropertyId.getRandomPropertyValue(randomGenerator); + builder.addValue(propertyValue); + } + return builder.build(); + } + + @Test + @UnitTestMethod(target = RegionPropertyDimension.class, name = "equals", args = { Object.class }) + public void testEquals() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6276127520796404855L); + + // never equal to null + for (int i = 0; i < 30; i++) { + RegionPropertyDimension regionPropertyDimension = getRandomRegionPropertyDimension( + randomGenerator.nextLong()); + assertFalse(regionPropertyDimension.equals(null)); + } + + // reflexive + for (int i = 0; i < 30; i++) { + RegionPropertyDimension regionPropertyDimension = getRandomRegionPropertyDimension( + randomGenerator.nextLong()); + assertTrue(regionPropertyDimension.equals(regionPropertyDimension)); + } + + // symmetric, transitive and consistent + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + RegionPropertyDimension regionPropertyDimension1 = getRandomRegionPropertyDimension(seed); + RegionPropertyDimension regionPropertyDimension2 = getRandomRegionPropertyDimension(seed); + + assertTrue(regionPropertyDimension1.equals(regionPropertyDimension2)); + assertTrue(regionPropertyDimension2.equals(regionPropertyDimension1)); + } + + // Different inputs yield unequal values + Set regionPropertyDimensions = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + RegionPropertyDimension regionPropertyDimension = getRandomRegionPropertyDimension( + randomGenerator.nextLong()); + regionPropertyDimensions.add(regionPropertyDimension); + } + assertTrue(regionPropertyDimensions.size() > 95); + + } + + @Test + @UnitTestMethod(target = RegionPropertyDimension.class, name = "hashCode", args = {}) + public void testHashCode() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(331499833066074706L); + + // equal objects have equal hash codes + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + RegionPropertyDimension regionPropertyDimension1 = getRandomRegionPropertyDimension(seed); + RegionPropertyDimension regionPropertyDimension2 = getRandomRegionPropertyDimension(seed); + + assertEquals(regionPropertyDimension1, regionPropertyDimension2); + assertEquals(regionPropertyDimension1.hashCode(), regionPropertyDimension2.hashCode()); + } + + //hash codes are reasonably distributed + Set hashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + RegionPropertyDimension regionPropertyDimension = getRandomRegionPropertyDimension( + randomGenerator.nextLong()); + hashCodes.add(regionPropertyDimension.hashCode()); + } + assertTrue(hashCodes.size() > 95); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/AT_SimpleRegionId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/AT_SimpleRegionId.java new file mode 100644 index 000000000..59688c9eb --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/AT_SimpleRegionId.java @@ -0,0 +1,147 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_SimpleRegionId { + + @Test + @UnitTestConstructor(target = SimpleRegionId.class, args = { Object.class }) + public void testConstructor() { + assertNotNull(new SimpleRegionId(5)); + + assertThrows(NullPointerException.class, () -> new SimpleRegionId(null)); + } + + @Test + @UnitTestMethod(target = SimpleRegionId.class, name = "getValue", args = {}) + public void testGetValue() { + + Object value = "some value"; + SimpleRegionId simpleRegionId = new SimpleRegionId(value); + assertEquals(value, simpleRegionId.getValue()); + + value = 678; + simpleRegionId = new SimpleRegionId(value); + assertEquals(value, simpleRegionId.getValue()); + + + value = false; + simpleRegionId = new SimpleRegionId(value); + assertEquals(value, simpleRegionId.getValue()); + + value = 2.98; + simpleRegionId = new SimpleRegionId(value); + assertEquals(value, simpleRegionId.getValue()); + + + } + @Test + @UnitTestMethod(target = SimpleRegionId.class, name = "toString", args = {}) + public void testToString() { + /* + * Show that the toString of the SimpleRegionId equals its input's + * toString + */ + + assertEquals(Integer.toString(5), new SimpleRegionId(5).toString()); + assertEquals("table", new SimpleRegionId("table").toString()); + assertEquals(Double.toString(2345.5345), new SimpleRegionId(2345.5345).toString()); + + } + + @Test + @UnitTestMethod(target = SimpleRegionId.class, name = "equals", args = { Object.class }) + public void testEquals() { + SimpleRegionId id_1 = new SimpleRegionId(2); + SimpleRegionId id_2 = new SimpleRegionId(5); + SimpleRegionId id_3 = new SimpleRegionId(2); + SimpleRegionId id_4 = new SimpleRegionId("A"); + SimpleRegionId id_5 = new SimpleRegionId("A"); + SimpleRegionId id_6 = new SimpleRegionId("B"); + + // reflexive + assertEquals(id_1, id_1); + assertEquals(id_2, id_2); + assertEquals(id_3, id_3); + assertEquals(id_4, id_4); + assertEquals(id_5, id_5); + assertEquals(id_6, id_6); + + // symmetric + assertEquals(id_1, id_3); + assertEquals(id_3, id_1); + assertEquals(id_4, id_5); + assertEquals(id_5, id_4); + + assertNotEquals(id_1, id_2); + assertNotEquals(id_1, id_4); + assertNotEquals(id_1, id_5); + assertNotEquals(id_1, id_6); + + assertNotEquals(id_2, id_1); + assertNotEquals(id_2, id_3); + assertNotEquals(id_2, id_4); + assertNotEquals(id_2, id_5); + assertNotEquals(id_2, id_6); + + assertNotEquals(id_3, id_2); + assertNotEquals(id_3, id_4); + assertNotEquals(id_3, id_5); + assertNotEquals(id_3, id_6); + + assertNotEquals(id_4, id_1); + assertNotEquals(id_4, id_2); + assertNotEquals(id_4, id_3); + assertNotEquals(id_4, id_6); + + assertNotEquals(id_5, id_1); + assertNotEquals(id_5, id_2); + assertNotEquals(id_5, id_3); + assertNotEquals(id_5, id_6); + + assertNotEquals(id_6, id_1); + assertNotEquals(id_6, id_2); + assertNotEquals(id_6, id_3); + assertNotEquals(id_6, id_4); + assertNotEquals(id_6, id_5); + + assertNotEquals(id_1, null); + assertNotEquals(id_2, null); + assertNotEquals(id_3, null); + assertNotEquals(id_4, null); + assertNotEquals(id_5, null); + assertNotEquals(id_6, null); + + } + + @Test + @UnitTestMethod(target = SimpleRegionId.class, name = "hashCode", args = {}) + public void testHashCode() { + + // equal objects have equal hash codes + for (int i = 0; i < 30; i++) { + SimpleRegionId s1 = new SimpleRegionId(i); + SimpleRegionId s2 = new SimpleRegionId(i); + assertEquals(s1.hashCode(), s2.hashCode()); + } + + Set hashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 30; i++) { + boolean unique = hashCodes.add(new SimpleRegionId(i).hashCode()); + assertTrue(unique); + } + + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/AT_SimpleRegionPropertyId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/AT_SimpleRegionPropertyId.java new file mode 100644 index 000000000..d81e49f0b --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/support/AT_SimpleRegionPropertyId.java @@ -0,0 +1,146 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_SimpleRegionPropertyId { + @Test + @UnitTestMethod(target = SimpleRegionPropertyId.class, name = "getValue", args = {}) + public void testGetValue() { + + Object value = "some value"; + SimpleRegionPropertyId simpleRegionPropertyId = new SimpleRegionPropertyId(value); + assertEquals(value, simpleRegionPropertyId.getValue()); + + value = 678; + simpleRegionPropertyId = new SimpleRegionPropertyId(value); + assertEquals(value, simpleRegionPropertyId.getValue()); + + + value = false; + simpleRegionPropertyId = new SimpleRegionPropertyId(value); + assertEquals(value, simpleRegionPropertyId.getValue()); + + value = 2.98; + simpleRegionPropertyId = new SimpleRegionPropertyId(value); + assertEquals(value, simpleRegionPropertyId.getValue()); + + + } + @Test + @UnitTestConstructor(target = SimpleRegionPropertyId.class, args = { Object.class }) + public void testConstructor() { + assertNotNull(new SimpleRegionPropertyId(5)); + + assertThrows(NullPointerException.class, () -> new SimpleRegionPropertyId(null)); + } + + @Test + @UnitTestMethod(target = SimpleRegionPropertyId.class, name = "toString", args = {}) + public void testToString() { + /* + * Show that the toString of the SimpleRegionPropertyId equals its + * input's toString + */ + + assertEquals(Integer.toString(5), new SimpleRegionPropertyId(5).toString()); + assertEquals("table", new SimpleRegionPropertyId("table").toString()); + assertEquals(Double.toString(2345.5345), new SimpleRegionPropertyId(2345.5345).toString()); + + } + + @Test + @UnitTestMethod(target = SimpleRegionPropertyId.class, name = "equals", args = { Object.class }) + public void testEquals() { + SimpleRegionId id_1 = new SimpleRegionId(2); + SimpleRegionId id_2 = new SimpleRegionId(5); + SimpleRegionId id_3 = new SimpleRegionId(2); + SimpleRegionId id_4 = new SimpleRegionId("A"); + SimpleRegionId id_5 = new SimpleRegionId("A"); + SimpleRegionId id_6 = new SimpleRegionId("B"); + + // reflexive + assertEquals(id_1, id_1); + assertEquals(id_2, id_2); + assertEquals(id_3, id_3); + assertEquals(id_4, id_4); + assertEquals(id_5, id_5); + assertEquals(id_6, id_6); + + // symmetric + assertEquals(id_1, id_3); + assertEquals(id_3, id_1); + assertEquals(id_4, id_5); + assertEquals(id_5, id_4); + + assertNotEquals(id_1, id_2); + assertNotEquals(id_1, id_4); + assertNotEquals(id_1, id_5); + assertNotEquals(id_1, id_6); + + assertNotEquals(id_2, id_1); + assertNotEquals(id_2, id_3); + assertNotEquals(id_2, id_4); + assertNotEquals(id_2, id_5); + assertNotEquals(id_2, id_6); + + assertNotEquals(id_3, id_2); + assertNotEquals(id_3, id_4); + assertNotEquals(id_3, id_5); + assertNotEquals(id_3, id_6); + + assertNotEquals(id_4, id_1); + assertNotEquals(id_4, id_2); + assertNotEquals(id_4, id_3); + assertNotEquals(id_4, id_6); + + assertNotEquals(id_5, id_1); + assertNotEquals(id_5, id_2); + assertNotEquals(id_5, id_3); + assertNotEquals(id_5, id_6); + + assertNotEquals(id_6, id_1); + assertNotEquals(id_6, id_2); + assertNotEquals(id_6, id_3); + assertNotEquals(id_6, id_4); + assertNotEquals(id_6, id_5); + + assertNotEquals(id_1, null); + assertNotEquals(id_2, null); + assertNotEquals(id_3, null); + assertNotEquals(id_4, null); + assertNotEquals(id_5, null); + assertNotEquals(id_6, null); + + } + + @Test + @UnitTestMethod(target = SimpleRegionPropertyId.class, name = "hashCode", args = {}) + public void testHashCode() { + + // equal objects have equal hash codes + for (int i = 0; i < 30; i++) { + SimpleRegionPropertyId s1 = new SimpleRegionPropertyId(i); + SimpleRegionPropertyId s2 = new SimpleRegionPropertyId(i); + assertEquals(s1.hashCode(), s2.hashCode()); + } + + Set hashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 30; i++) { + boolean unique = hashCodes.add(new SimpleRegionPropertyId(i).hashCode()); + assertTrue(unique); + } + + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/testsupport/AT_RegionsTestPluginFactory.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/testsupport/AT_RegionsTestPluginFactory.java new file mode 100644 index 000000000..07319c883 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/testsupport/AT_RegionsTestPluginFactory.java @@ -0,0 +1,333 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.testsupport; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.TestFactoryUtil; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginId; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePluginId; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeoplePluginData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonRange; +import gov.hhs.aspr.ms.gcm.plugins.regions.RegionsPluginId; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.regions.reports.RegionPropertyReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.regions.reports.RegionTransferReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionError; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.RegionsTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPluginId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.StochasticsError; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; +import util.wrappers.MutableBoolean; + +public class AT_RegionsTestPluginFactory { + + @Test + @UnitTestMethod(target = RegionsTestPluginFactory.class, name = "factory", args = { int.class, long.class, + boolean.class, Consumer.class }) + public void testFactory_Consumer() { + MutableBoolean executed = new MutableBoolean(); + Factory factory = RegionsTestPluginFactory.factory(100, 5785172948650781925L, true, + c -> executed.setValue(true)); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + assertTrue(executed.getValue()); + + // precondition: consumer is null + Consumer nullConsumer = null; + ContractException contractException = assertThrows(ContractException.class, + () -> RegionsTestPluginFactory.factory(0, 0, true, nullConsumer)); + assertEquals(NucleusError.NULL_ACTOR_CONTEXT_CONSUMER, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = RegionsTestPluginFactory.class, name = "factory", args = { int.class, long.class, + boolean.class, TestPluginData.class }) + public void testFactory_TestPluginData() { + MutableBoolean executed = new MutableBoolean(); + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, c -> executed.setValue(true))); + TestPluginData testPluginData = pluginBuilder.build(); + + Factory factory = RegionsTestPluginFactory.factory(100, 5166994853007999229L, true, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + assertTrue(executed.getValue()); + // precondition: testPluginData is null + TestPluginData nullTestPluginData = null; + ContractException contractException = assertThrows(ContractException.class, + () -> RegionsTestPluginFactory.factory(0, 0, true, nullTestPluginData)); + assertEquals(NucleusError.NULL_PLUGIN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = RegionsTestPluginFactory.Factory.class, name = "getPlugins", args = {}) + public void testGetPlugins() { + List plugins = RegionsTestPluginFactory.factory(0, 0, true, t -> { + }).getPlugins(); + assertEquals(4, plugins.size()); + + TestFactoryUtil.checkPluginExists(plugins, RegionsPluginId.PLUGIN_ID); + TestFactoryUtil.checkPluginExists(plugins, PeoplePluginId.PLUGIN_ID); + TestFactoryUtil.checkPluginExists(plugins, StochasticsPluginId.PLUGIN_ID); + TestFactoryUtil.checkPluginExists(plugins, TestPluginId.PLUGIN_ID); + } + + @Test + @UnitTestMethod(target = RegionsTestPluginFactory.Factory.class, name = "setPeoplePluginData", args = { + PeoplePluginData.class }) + public void testSetPeoplePluginData() { + PeoplePluginData.Builder builder = PeoplePluginData.builder(); + builder.addPersonRange(new PersonRange(0, 99)); + PeoplePluginData peoplePluginData = builder.build(); + + List plugins = RegionsTestPluginFactory.factory(0, 0, true, t -> { + }).setPeoplePluginData(peoplePluginData).getPlugins(); + + TestFactoryUtil.checkPluginDataExists(plugins, peoplePluginData, PeoplePluginId.PLUGIN_ID); + + // precondition: peoplePluginData is not null + ContractException contractException = assertThrows(ContractException.class, + () -> RegionsTestPluginFactory.factory(0, 0, true, t -> { + }).setPeoplePluginData(null)); + assertEquals(PersonError.NULL_PEOPLE_PLUGIN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = RegionsTestPluginFactory.Factory.class, name = "setRegionsPluginData", args = { + RegionsPluginData.class }) + public void testSetRegionsPluginData() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3111731594673998076L); + int initialPopulation = 30; + List people = new ArrayList<>(); + for (int i = 0; i < initialPopulation; i++) { + people.add(new PersonId(i)); + } + + // add the region plugin + RegionsPluginData.Builder regionPluginBuilder = RegionsPluginData.builder(); + for (TestRegionId regionId : TestRegionId.values()) { + regionPluginBuilder.addRegion(regionId); + } + + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { + regionPluginBuilder.defineRegionProperty(testRegionPropertyId, + testRegionPropertyId.getPropertyDefinition()); + } + + for (TestRegionId regionId : TestRegionId.values()) { + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { + if (testRegionPropertyId.getPropertyDefinition().getDefaultValue().isEmpty() + || randomGenerator.nextBoolean()) { + Object randomPropertyValue = testRegionPropertyId.getRandomPropertyValue(randomGenerator); + regionPluginBuilder.setRegionPropertyValue(regionId, testRegionPropertyId, randomPropertyValue); + } + } + } + + TestRegionId testRegionId = TestRegionId.REGION_1; + for (PersonId personId : people) { + regionPluginBuilder.addPerson(personId, testRegionId, 0.0); + testRegionId = testRegionId.next(); + } + + regionPluginBuilder.setPersonRegionArrivalTracking(true); + + RegionsPluginData regionsPluginData = regionPluginBuilder.build(); + + List plugins = RegionsTestPluginFactory.factory(0, 0, true, t -> { + }).setRegionsPluginData(regionsPluginData).getPlugins(); + + TestFactoryUtil.checkPluginDataExists(plugins, regionsPluginData, RegionsPluginId.PLUGIN_ID); + + // precondition: regionsPluginData is not null + ContractException contractException = assertThrows(ContractException.class, + () -> RegionsTestPluginFactory.factory(0, 0, true, t -> { + }).setRegionsPluginData(null)); + assertEquals(RegionError.NULL_REGION_PLUGIN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = RegionsTestPluginFactory.Factory.class, name = "setStochasticsPluginData", args = { + StochasticsPluginData.class }) + public void testSetStochasticsPluginData() { + StochasticsPluginData.Builder builder = StochasticsPluginData.builder(); + + WellState wellState = WellState.builder().setSeed(1286485118818778304L).build(); + builder.setMainRNGState(wellState); + + StochasticsPluginData stochasticsPluginData = builder.build(); + + List plugins = RegionsTestPluginFactory.factory(0, 0, true, t -> { + }).setStochasticsPluginData(stochasticsPluginData).getPlugins(); + + TestFactoryUtil.checkPluginDataExists(plugins, stochasticsPluginData, StochasticsPluginId.PLUGIN_ID); + + // precondition: stochasticsPluginData is not null + ContractException contractException = assertThrows(ContractException.class, + () -> RegionsTestPluginFactory.factory(0, 0, true, t -> { + }).setStochasticsPluginData(null)); + assertEquals(StochasticsError.NULL_STOCHASTICS_PLUGIN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = RegionsTestPluginFactory.class, name = "getStandardPeoplePluginData", args = { int.class }) + public void testGetStandardPeoplePluginData() { + + int initialPopulation = 100; + + PeoplePluginData.Builder peopleBuilder = PeoplePluginData.builder(); + if (initialPopulation > 0) { + peopleBuilder.addPersonRange(new PersonRange(0, initialPopulation - 1)); + } + + PeoplePluginData expectedPluginData = peopleBuilder.build(); + PeoplePluginData actualPluginData = RegionsTestPluginFactory.getStandardPeoplePluginData(initialPopulation); + + assertEquals(expectedPluginData, actualPluginData); + } + + @Test + @UnitTestMethod(target = RegionsTestPluginFactory.class, name = "getStandardRegionsPluginData", args = { List.class, + boolean.class, long.class }) + public void testGetStandardRegionsPluginData() { + + long seed = 6178540698301704248L; + int initialPopulation = 100; + boolean trackTimes = true; + List people = new ArrayList<>(); + for (int i = 0; i < initialPopulation; i++) { + people.add(new PersonId(i)); + } + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + + RegionsPluginData.Builder regionPluginBuilder = RegionsPluginData.builder(); + for (TestRegionId regionId : TestRegionId.values()) { + regionPluginBuilder.addRegion(regionId); + } + + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.getTestRegionPropertyIds()) { + PropertyDefinition propertyDefinition = testRegionPropertyId.getPropertyDefinition(); + regionPluginBuilder.defineRegionProperty(testRegionPropertyId, propertyDefinition); + boolean hasDeaultValue = propertyDefinition.getDefaultValue().isPresent(); + + if (!hasDeaultValue) { + for (TestRegionId regionId : TestRegionId.values()) { + regionPluginBuilder.setRegionPropertyValue(regionId, testRegionPropertyId, + testRegionPropertyId.getRandomPropertyValue(randomGenerator)); + } + } + } + + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId + .getTestShuffledRegionPropertyIds(randomGenerator)) { + PropertyDefinition propertyDefinition = testRegionPropertyId.getPropertyDefinition(); + boolean hasDeaultValue = propertyDefinition.getDefaultValue().isPresent(); + boolean setValue = randomGenerator.nextBoolean(); + + if (hasDeaultValue && setValue) { + for (TestRegionId regionId : TestRegionId.values()) { + regionPluginBuilder.setRegionPropertyValue(regionId, testRegionPropertyId, + propertyDefinition.getDefaultValue().get()); + } + } else if (setValue) { + for (TestRegionId regionId : TestRegionId.values()) { + regionPluginBuilder.setRegionPropertyValue(regionId, testRegionPropertyId, + testRegionPropertyId.getRandomPropertyValue(randomGenerator)); + } + } + } + + regionPluginBuilder.setPersonRegionArrivalTracking(trackTimes); + TestRegionId testRegionId = TestRegionId.REGION_1; + if (trackTimes) { + for (PersonId personId : people) { + regionPluginBuilder.addPerson(personId, testRegionId, 0.0); + testRegionId = testRegionId.next(); + } + } else { + for (PersonId personId : people) { + regionPluginBuilder.addPerson(personId, testRegionId); + testRegionId = testRegionId.next(); + } + } + + RegionsPluginData expectedPluginData = regionPluginBuilder.build(); + RegionsPluginData actualPluginData = RegionsTestPluginFactory.getStandardRegionsPluginData(people, trackTimes, + seed); + + assertEquals(expectedPluginData, actualPluginData); + } + + @Test + @UnitTestMethod(target = RegionsTestPluginFactory.class, name = "getStandardStochasticsPluginData", args = { + long.class }) + public void testGetStandardStochasticsPluginData() { + long seed = 8184805053177550601L; + + WellState wellState = WellState.builder().setSeed(seed).build(); + + StochasticsPluginData expectedPluginData = StochasticsPluginData.builder().setMainRNGState(wellState).build(); + StochasticsPluginData actualPluginData = RegionsTestPluginFactory.getStandardStochasticsPluginData(seed); + + assertEquals(expectedPluginData, actualPluginData); + } + + @Test + @UnitTestMethod(target = RegionsTestPluginFactory.Factory.class, name = "setRegionPropertyReportPluginData", args = { + RegionPropertyReportPluginData.class }) + public void testSetRegionPropertyReportPluginData() { + RegionPropertyReportPluginData regionPropertyReportPluginData = RegionPropertyReportPluginData.builder()// + .setReportLabel(new SimpleReportLabel("RegionPropertyReport"))// + .setDefaultInclusion(true)// + .includeRegionProperty(TestRegionPropertyId.REGION_PROPERTY_1_BOOLEAN_MUTABLE)// + .excludeRegionProperty(TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE)// + .build();// + + Factory factory = RegionsTestPluginFactory.factory(0, 0, true, t -> { }); + factory.setRegionPropertyReportPluginData(regionPropertyReportPluginData); + List plugins = factory.getPlugins(); + + TestFactoryUtil.checkPluginDataExists(plugins, regionPropertyReportPluginData, RegionsPluginId.PLUGIN_ID); + } + + @Test + @UnitTestMethod(target = RegionsTestPluginFactory.Factory.class, name = "setRegionTransferReportPluginData", args = { + RegionTransferReportPluginData.class }) + public void testSetRegionTransferReportPluginData() { + RegionTransferReportPluginData regionTransferReportPluginData = RegionTransferReportPluginData.builder()// + .setReportLabel(new SimpleReportLabel("RegionTransferReport")) + .setReportPeriod(ReportPeriod.DAILY) + .build(); + + Factory factory = RegionsTestPluginFactory.factory(0, 0, true, t -> { }); + factory.setRegionTransferReportPluginData(regionTransferReportPluginData); + List plugins = factory.getPlugins(); + + TestFactoryUtil.checkPluginDataExists(plugins, regionTransferReportPluginData, RegionsPluginId.PLUGIN_ID); + + + } +} diff --git a/gcm3/src/test/java/plugins/regions/testsupport/AT_TestRegionId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/testsupport/AT_TestRegionId.java similarity index 80% rename from gcm3/src/test/java/plugins/regions/testsupport/AT_TestRegionId.java rename to gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/testsupport/AT_TestRegionId.java index 1bd2ca625..63eff8a8e 100644 --- a/gcm3/src/test/java/plugins/regions/testsupport/AT_TestRegionId.java +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/testsupport/AT_TestRegionId.java @@ -1,4 +1,4 @@ -package plugins.regions.testsupport; +package gov.hhs.aspr.ms.gcm.plugins.regions.testsupport; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; @@ -13,21 +13,19 @@ import org.apache.commons.math3.random.RandomGenerator; import org.junit.jupiter.api.Test; -import plugins.regions.support.RegionId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import util.annotations.UnitTestMethod; import util.random.RandomGeneratorProvider; import util.wrappers.MutableInteger; -@UnitTest(target = TestRegionId.class) public class AT_TestRegionId { /** - * Shows that generated random test regions are members of the - * TestRegionId enum and are reasonably random + * Shows that generated random test regions are members of the TestRegionId + * enum and are reasonably random */ @Test - @UnitTestMethod(name = "getRandomRegionId", args = { RandomGenerator.class }) + @UnitTestMethod(target = TestRegionId.class, name = "getRandomRegionId", args = { RandomGenerator.class }) public void testGetRandomRegionId() { Map countMap = new LinkedHashMap<>(); @@ -51,11 +49,11 @@ public void testGetRandomRegionId() { } /** - * Shows that a generated unknown region is not null and not a member - * of the enum + * Shows that a generated unknown region is not null and not a member of the + * enum */ @Test - @UnitTestMethod(name = "getUnknownRegionId", args = {}) + @UnitTestMethod(target = TestRegionId.class, name = "getUnknownRegionId", args = {}) public void testGetUnknownRegionId() { Set unknownRegionIds = new LinkedHashSet<>(); for (int i = 0; i < 30; i++) { @@ -69,13 +67,11 @@ public void testGetUnknownRegionId() { } } - /** - * Shows that size() returns the number of members in the TestRegionId - * enum + * Shows that size() returns the number of members in the TestRegionId enum */ @Test - @UnitTestMethod(name = "size", args = {}) + @UnitTestMethod(target = TestRegionId.class, name = "size", args = {}) public void testSize() { assertEquals(TestRegionId.values().length, TestRegionId.size()); } @@ -84,17 +80,17 @@ public void testSize() { * Shows the next value of each member matches expectations */ @Test - @UnitTestMethod(name = "next", args = {}) + @UnitTestMethod(target = TestRegionId.class, name = "next", args = {}) public void test() { assertEquals(6, TestRegionId.values().length); - + assertEquals(TestRegionId.REGION_2, TestRegionId.REGION_1.next()); assertEquals(TestRegionId.REGION_3, TestRegionId.REGION_2.next()); assertEquals(TestRegionId.REGION_4, TestRegionId.REGION_3.next()); assertEquals(TestRegionId.REGION_5, TestRegionId.REGION_4.next()); assertEquals(TestRegionId.REGION_6, TestRegionId.REGION_5.next()); assertEquals(TestRegionId.REGION_1, TestRegionId.REGION_6.next()); - + } } diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/testsupport/AT_TestRegionPropertyId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/testsupport/AT_TestRegionPropertyId.java new file mode 100644 index 000000000..1e8dbed2d --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/regions/testsupport/AT_TestRegionPropertyId.java @@ -0,0 +1,222 @@ +package gov.hhs.aspr.ms.gcm.plugins.regions.testsupport; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import util.annotations.UnitTestMethod; +import util.random.RandomGeneratorProvider; +import util.wrappers.MutableInteger; + +public class AT_TestRegionPropertyId { + + /** + * Shows that a generated unknown region property id is not null and not a + * member of the enum and is unique. + */ + @Test + @UnitTestMethod(target = TestRegionPropertyId.class, name = "getUnknownRegionPropertyId", args = {}) + public void testGetUnknownRegionPropertyId() { + RegionPropertyId unknownRegionPropertyId = TestRegionPropertyId.getUnknownRegionPropertyId(); + assertNotNull(unknownRegionPropertyId); + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { + assertNotEquals(testRegionPropertyId, unknownRegionPropertyId); + } + + Set unknownRegionPropertyIds = new LinkedHashSet<>(); + for (int i = 0; i < 10; i++) { + unknownRegionPropertyId = TestRegionPropertyId.getUnknownRegionPropertyId(); + assertNotNull(unknownRegionPropertyId); + boolean unique = unknownRegionPropertyIds.add(unknownRegionPropertyId); + assertTrue(unique); + } + } + + /** + * Shows that size() returns the number of members in the TestRegionId enum + */ + @Test + @UnitTestMethod(target = TestRegionPropertyId.class, name = "size", args = {}) + public void testSize() { + assertEquals(TestRegionPropertyId.values().length, TestRegionPropertyId.size()); + } + + @Test + @UnitTestMethod(target = TestRegionPropertyId.class, name = "getPropertyDefinition", args = {}) + public void testGetPropertyDefinition() { + for (TestRegionPropertyId propertyId : TestRegionPropertyId.values()) { + PropertyDefinition propertyDefinition = propertyId.getPropertyDefinition(); + assertNotNull(propertyDefinition); + } + } + + @Test + @UnitTestMethod(target = TestRegionPropertyId.class, name = "getRandomPropertyValue", args = { + RandomGenerator.class }) + public void testGetRandomPropertyValue() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(9052754083757003238L); + + /* + * Show that randomly generated values are compatible with the associated + * property definition. Show that the values are reasonably unique + */ + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { + PropertyDefinition propertyDefinition = testRegionPropertyId.getPropertyDefinition(); + Set values = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + Object propertyValue = testRegionPropertyId.getRandomPropertyValue(randomGenerator); + values.add(propertyValue); + assertTrue(propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())); + } + // show that the values are reasonable unique + if (propertyDefinition.getType() != Boolean.class) { + assertTrue(values.size() > 10); + } else { + assertEquals(2, values.size()); + } + } + } + + @Test + @UnitTestMethod(target = TestRegionPropertyId.class, name = "getPropertiesWithDefaultValues", args = {}) + public void testGetPropertesWithDefaultValues() { + List expectedValues = new ArrayList<>(); + + for (TestRegionPropertyId id : TestRegionPropertyId.values()) { + if (id.getPropertyDefinition().getDefaultValue().isPresent()) { + expectedValues.add(id); + } + } + + List actualValues = TestRegionPropertyId.getPropertiesWithDefaultValues(); + + assertNotNull(actualValues); + assertEquals(expectedValues.size(), actualValues.size()); + Set setOfExpectedValues = new LinkedHashSet<>(expectedValues); + Set setOfActualValues = new LinkedHashSet<>(actualValues); + assertEquals(setOfExpectedValues, setOfActualValues); + assertEquals(expectedValues.size(), setOfExpectedValues.size()); + assertEquals(actualValues.size(), setOfActualValues.size()); + } + + @Test + @UnitTestMethod(target = TestRegionPropertyId.class, name = "getPropertiesWithoutDefaultValues", args = {}) + public void testGetPropertesWithoutDefaultValues() { + List expectedValues = new ArrayList<>(); + + for (TestRegionPropertyId id : TestRegionPropertyId.values()) { + if (id.getPropertyDefinition().getDefaultValue().isEmpty()) { + expectedValues.add(id); + } + } + + List actualValues = TestRegionPropertyId.getPropertiesWithoutDefaultValues(); + + assertNotNull(actualValues); + assertEquals(expectedValues.size(), actualValues.size()); + Set setOfExpectedValues = new LinkedHashSet<>(expectedValues); + Set setOfActualValues = new LinkedHashSet<>(actualValues); + assertEquals(setOfExpectedValues, setOfActualValues); + assertEquals(expectedValues.size(), setOfExpectedValues.size()); + assertEquals(actualValues.size(), setOfActualValues.size()); + } + + @Test + @UnitTestMethod(target = TestRegionPropertyId.class, name = "getRandomMutableRegionPropertyId", args = { + RandomGenerator.class }) + public void testGetRandomMutableRegionPropertyId() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4772298816496492540L); + + Set applicableValues = Set.of(TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE, + TestRegionPropertyId.REGION_PROPERTY_3_DOUBLE_MUTABLE, + TestRegionPropertyId.REGION_PROPERTY_1_BOOLEAN_MUTABLE); + + for (int i = 0; i < 15; i++) { + TestRegionPropertyId actualValue = TestRegionPropertyId.getRandomMutableRegionPropertyId(randomGenerator); + + assertNotNull(actualValue); + assertTrue(applicableValues.contains(actualValue)); + } + } + + @Test + @UnitTestMethod(target = TestRegionPropertyId.class, name = "getRandomRegionPropertyId", args = { + RandomGenerator.class }) + public void testGetRandomRegionPropertyId() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8246696863539332004L); + + Set applicableValues = EnumSet.allOf(TestRegionPropertyId.class); + Map valueCounter = new LinkedHashMap<>(); + + for (TestRegionPropertyId actualValue : TestRegionPropertyId.values()) { + valueCounter.put(actualValue, new MutableInteger()); + } + + for (int i = 0; i < 100; i++) { + for (int j = 0; j < applicableValues.size(); j++) { + TestRegionPropertyId actualValue = TestRegionPropertyId.getRandomRegionPropertyId(randomGenerator); + + assertNotNull(actualValue); + assertTrue(applicableValues.contains(actualValue)); + valueCounter.get(actualValue).increment(); + } + } + for (TestRegionPropertyId propertyId : valueCounter.keySet()) { + int numTimes = valueCounter.get(propertyId).getValue(); + assertTrue(numTimes >= 90); + assertTrue(numTimes < 150); + } + } + + @Test + @UnitTestMethod(target = TestRegionPropertyId.class, name = "getTestRegionPropertyIds", args = {}) + public void testGetTestRegionPropertyIds() { + assertEquals(Arrays.asList(TestRegionPropertyId.values()), TestRegionPropertyId.getTestRegionPropertyIds()); + } + + @Test + @UnitTestMethod(target = TestRegionPropertyId.class, name = "getTestShuffledRegionPropertyIds", args = {RandomGenerator.class}) + public void testGetTestShuffledRegionPropertyIds() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8246696863539332004L); + + Set baseSet = new LinkedHashSet<>(); + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { + baseSet.add(testRegionPropertyId); + } + + Set> lists = new LinkedHashSet<>(); + + /* + * Generate a few thousand random lists and show that each list contains all the + * expected region property ids + * + */ + for (int i = 0; i < 3000; i++) { + List list = TestRegionPropertyId.getTestShuffledRegionPropertyIds(randomGenerator); + lists.add(list); + assertEquals(baseSet, new LinkedHashSet<>(list)); + + } + + //show that the results of the method hit most of the 720(=6!) possible lists + assertTrue(lists.size() > 700); + assertTrue(lists.size() <= 720); + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/reports/AT_ReportPlugin.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/reports/AT_ReportPlugin.java new file mode 100644 index 000000000..f14d38f0b --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/reports/AT_ReportPlugin.java @@ -0,0 +1,23 @@ +package gov.hhs.aspr.ms.gcm.plugins.reports; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import util.annotations.UnitTestMethod; + +public class AT_ReportPlugin { + + @Test + @UnitTestMethod(target = ReportsPlugin.class, name = "getReportsPlugin", args = {}) + public void testGetReportPlugin() { + + Plugin reportPlugin = ReportsPlugin.getReportsPlugin(); + + // show that the plugin has the correct idea + assertEquals(ReportsPluginId.PLUGIN_ID, reportPlugin.getPluginId()); + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/reports/AT_ReportsPluginId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/reports/AT_ReportsPluginId.java new file mode 100644 index 000000000..b658ed461 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/reports/AT_ReportsPluginId.java @@ -0,0 +1,16 @@ +package gov.hhs.aspr.ms.gcm.plugins.reports; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestField; + +public class AT_ReportsPluginId { + + @Test + @UnitTestField(target = ReportsPluginId.class, name = "PLUGIN_ID") + public void testPluginId() { + assertNotNull(ReportsPluginId.PLUGIN_ID); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/AT_ExperimentLineWriter.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/AT_ExperimentLineWriter.java new file mode 100644 index 000000000..2f5313985 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/AT_ExperimentLineWriter.java @@ -0,0 +1,38 @@ +package gov.hhs.aspr.ms.gcm.plugins.reports.support; + +import java.nio.file.Path; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ExperimentContext; +import util.annotations.UnitTag; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_ExperimentLineWriter { + + @Test + @UnitTestConstructor(target = ExperimentLineWriter.class, args = { ExperimentContext.class, Path.class, String.class}, tags = { UnitTag.MANUAL , UnitTag.CLASS_PROXY}) + public void testConstructor() { + // covered by manual test of NIOReportItemHandler + } + + @Test + @UnitTestMethod(target = ExperimentLineWriter.class, name = "write", args = { ExperimentContext.class, int.class}, tags = { UnitTag.MANUAL , UnitTag.CLASS_PROXY}) + public void testWrite() { + // covered by manual test of NIOReportItemHandler + } + + @Test + @UnitTestMethod(target = ExperimentLineWriter.class, name = "flush", args = {}, tags = { UnitTag.MANUAL , UnitTag.CLASS_PROXY}) + public void testFLush() { + // covered by manual test of NIOReportItemHandler + } + + @Test + @UnitTestMethod(target = ExperimentLineWriter.class, name = "close", args = {}, tags = { UnitTag.MANUAL , UnitTag.CLASS_PROXY}) + public void testClose() { + // covered by manual test of NIOReportItemHandler + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/AT_LineWriter.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/AT_LineWriter.java new file mode 100644 index 000000000..ad2376654 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/AT_LineWriter.java @@ -0,0 +1,38 @@ +package gov.hhs.aspr.ms.gcm.plugins.reports.support; + +import java.nio.file.Path; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ExperimentContext; +import util.annotations.UnitTag; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_LineWriter { + + @Test + @UnitTestConstructor(target = LineWriter.class, args = { ExperimentContext.class, Path.class, boolean.class,String.class}, tags = { UnitTag.MANUAL , UnitTag.CLASS_PROXY}) + public void testConstructor() { + // covered by manual test of NIOReportItemHandler + } + + @Test + @UnitTestMethod(target = LineWriter.class, name = "write", args = { ExperimentContext.class, int.class, ReportItem.class }, tags = { UnitTag.MANUAL , UnitTag.CLASS_PROXY}) + public void testWrite() { + // covered by manual test of NIOReportItemHandler + } + + @Test + @UnitTestMethod(target = LineWriter.class, name = "flush", args = {}, tags = { UnitTag.MANUAL , UnitTag.CLASS_PROXY}) + public void testFLush() { + // covered by manual test of NIOReportItemHandler + } + + @Test + @UnitTestMethod(target = LineWriter.class, name = "close", args = {}, tags = { UnitTag.MANUAL , UnitTag.CLASS_PROXY}) + public void testClose() { + // covered by manual test of NIOReportItemHandler + } + +} \ No newline at end of file diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/AT_NIOReportItemHandler.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/AT_NIOReportItemHandler.java new file mode 100644 index 000000000..bbe0224dd --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/AT_NIOReportItemHandler.java @@ -0,0 +1,97 @@ +package gov.hhs.aspr.ms.gcm.plugins.reports.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.nio.file.Path; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ExperimentContext; +import util.annotations.UnitTag; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + +public class AT_NIOReportItemHandler { + + @Test + @UnitTestMethod(target = NIOReportItemHandler.Builder.class, name = "build", args = {}) + public void testBuild() { + NIOReportItemHandler.Builder builder = NIOReportItemHandler.builder(); + ReportLabel reportLabel1 = new SimpleReportLabel("testReportLabel1"); + ReportLabel reportLabel2 = new SimpleReportLabel("testReportLabel2"); + final Path path1 = Path.of("example_path1"); + final Path path2 = Path.of("example_path2"); + + // show that a path collision error happens when 2 reports have the same + // path + builder.addReport(reportLabel1, path1); + builder.addReport(reportLabel2, path1); + + ContractException contractException = assertThrows(ContractException.class, () -> builder.build()); + assertEquals(contractException.getErrorType(), ReportError.PATH_COLLISION); + + // show that what is built is not null + builder.addReport(reportLabel1, path1); + builder.addReport(reportLabel2, path2); + assertNotNull(builder.build()); + } + + @Test + @UnitTestMethod(target = NIOReportItemHandler.Builder.class, name = "addReport", args = { ReportLabel.class, Path.class }, tags = { UnitTag.MANUAL }) + public void testAddReport() { + NIOReportItemHandler.Builder builder = NIOReportItemHandler.builder(); + ReportLabel reportLabel = new SimpleReportLabel("testReportLabel"); + final Path path1 = Path.of("example_path3"); + + // null report label check + ContractException pathContractException = assertThrows(ContractException.class, () -> builder.addReport(null, path1)); + assertEquals(pathContractException.getErrorType(), ReportError.NULL_REPORT_LABEL); + + // null report path check + ContractException idContractException = assertThrows(ContractException.class, () -> builder.addReport(reportLabel, null)); + assertEquals(idContractException.getErrorType(), ReportError.NULL_REPORT_PATH); + + // the existence of the report file is covered by a manual test + } + + @Test + @UnitTestMethod(target = NIOReportItemHandler.Builder.class, name = "addExperimentReport", args = { Path.class }, tags = { UnitTag.MANUAL }) + public void testAddExperimentReport() { + NIOReportItemHandler.Builder builder = NIOReportItemHandler.builder(); + + // null report path check + ContractException idContractException = assertThrows(ContractException.class, () -> builder.addExperimentReport(null)); + assertEquals(idContractException.getErrorType(), ReportError.NULL_REPORT_PATH); + + // the existence of the report file is covered by a manual test + } + + @Test + @UnitTestMethod(target = NIOReportItemHandler.Builder.class, name = "setDelimiter", args = {String.class}, tags = UnitTag.MANUAL) + public void testSetDelimiter() { + // covered by test 6 in manual test + } + + @Test + @UnitTestMethod(target = NIOReportItemHandler.Builder.class, name = "setDisplayExperimentColumnsInReports", args = { boolean.class }, tags = { UnitTag.MANUAL }) + public void testSetDisplayExperimentColumnsInReports() { + NIOReportItemHandler.Builder builder = NIOReportItemHandler.builder(); + final boolean displayExperimentColumnsInReports = true; + assertNotNull(builder.setDisplayExperimentColumnsInReports(displayExperimentColumnsInReports)); + // the existence of the columns in the reports is covered by manual test + } + + @Test + @UnitTestMethod(target = NIOReportItemHandler.class, name = "builder", args = {}) + public void testBuilder() { + assertNotNull(NIOReportItemHandler.builder()); + } + + @Test + @UnitTestMethod(target = NIOReportItemHandler.class, name = "accept", args = { ExperimentContext.class }, tags = { UnitTag.MANUAL }) + public void testAccept() { + // covered by test for accept in manual test + } +} \ No newline at end of file diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/AT_PeriodicReport.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/AT_PeriodicReport.java new file mode 100644 index 000000000..c9d34c438 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/AT_PeriodicReport.java @@ -0,0 +1,464 @@ +package gov.hhs.aspr.ms.gcm.plugins.reports.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.math3.util.FastMath; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.nucleus.SimplePluginId; +import gov.hhs.aspr.ms.gcm.nucleus.Simulation; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestOutputConsumer; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.wrappers.MutableDouble; +import util.wrappers.MutableInteger; + +public class AT_PeriodicReport { + + private static class TestReport extends PeriodicReport { + + public TestReport(ReportLabel reportLabel, ReportPeriod reportPeriod) { + super(reportLabel, reportPeriod); + } + + @Override + protected void flush(ReportContext reportContext) { + } + + } + + /* + * Used to test the fillTimeFields() method when the period is + * ReportPeriod.DAILY + */ + private static class DailyTestReport extends PeriodicReport { + + private MutableInteger testCounter = new MutableInteger(); + + public DailyTestReport(ReportLabel reportLabel, ReportPeriod reportPeriod) { + super(reportLabel, reportPeriod); + if (reportPeriod != ReportPeriod.DAILY) { + throw new RuntimeException("must be a daily report"); + } + } + + @Override + protected void flush(ReportContext reportContext) { + testCounter.increment(); + ReportHeader.Builder reportHeaderBuilder = ReportHeader.builder(); + this.addTimeFieldHeaders(reportHeaderBuilder); + ReportHeader reportHeader = reportHeaderBuilder.build(); + ReportItem.Builder reportItemBuilder = ReportItem.builder(); + + fillTimeFields(reportItemBuilder); + + ReportItem reportItem = reportItemBuilder.setReportLabel(getReportLabel()).setReportHeader(reportHeader).build(); + + int dayValue = (int) FastMath.ceil(reportContext.getTime()); + + String expectedTimeString = Integer.toString(dayValue); + + String actualTimeString = reportItem.getValue(0); + + assertEquals(expectedTimeString, actualTimeString); + reportContext.releaseOutput(reportItem); + + } + + } + + /* + * Used to test the fillTimeFields() method when the period is + * ReportPeriod.HOURLY + */ + private static class HourlyTestReport extends PeriodicReport { + + private MutableInteger testCounter = new MutableInteger(); + + public HourlyTestReport(ReportLabel reportLabel, ReportPeriod reportPeriod) { + super(reportLabel, reportPeriod); + if (reportPeriod != ReportPeriod.HOURLY) { + throw new RuntimeException("must be an hourly report"); + } + + } + + @Override + protected void flush(ReportContext reportContext) { + testCounter.increment(); + ReportHeader.Builder reportHeaderBuilder = ReportHeader.builder(); + this.addTimeFieldHeaders(reportHeaderBuilder); + ReportHeader reportHeader = reportHeaderBuilder.build(); + ReportItem.Builder reportItemBuilder = ReportItem.builder(); + + fillTimeFields(reportItemBuilder); + + ReportItem reportItem = reportItemBuilder.setReportLabel(getReportLabel()).setReportHeader(reportHeader).build(); + double time = reportContext.getTime(); + + int hour = (int) FastMath.ceil(time * 24); + int expectedDay = hour / 24; + int expectedHour = hour % 24; + String expectedHourTimeString = Integer.toString(expectedHour); + String expectedDayTimeString = Integer.toString(expectedDay); + String actualDayTimeString = reportItem.getValue(0); + String actualHourTimeString = reportItem.getValue(1); + assertEquals(expectedDayTimeString, actualDayTimeString); + assertEquals(expectedHourTimeString, actualHourTimeString); + + reportContext.releaseOutput(reportItem); + } + + } + + /* + * Used to test the fillTimeFields() method when the period is + * ReportPeriod.END_OF_SIMULATION + */ + private static class EndOfSimulationTestReport extends PeriodicReport { + + private MutableInteger flushCount = new MutableInteger(); + private MutableDouble flushTime = new MutableDouble(); + + public EndOfSimulationTestReport(ReportLabel reportLabel, ReportPeriod reportPeriod) { + super(reportLabel, reportPeriod); + if (reportPeriod != ReportPeriod.END_OF_SIMULATION) { + throw new RuntimeException("must be an end of simulation report"); + } + } + + @Override + protected void flush(ReportContext reportContext) { + flushTime.setValue(reportContext.getTime()); + flushCount.increment(); + + ReportHeader.Builder reportHeaderBuilder = ReportHeader.builder(); + addTimeFieldHeaders(reportHeaderBuilder); + ReportHeader reportHeader = reportHeaderBuilder.build(); + ReportItem.Builder reportItemBuilder = ReportItem.builder(); + fillTimeFields(reportItemBuilder); + + ReportItem reportItem = reportItemBuilder.setReportLabel(getReportLabel()).setReportHeader(reportHeader).build(); + + assertEquals(0, reportItem.size()); + } + + } + + private static class InitTestReport extends PeriodicReport { + private List flushTimes = new ArrayList<>(); + + public InitTestReport(ReportLabel reportLabel, ReportPeriod reportPeriod) { + super(reportLabel, reportPeriod); + } + + @Override + protected void flush(ReportContext reportContext) { + flushTimes.add(reportContext.getTime()); + } + + } + + @Test + @UnitTestConstructor(target = PeriodicReport.class, args = { ReportLabel.class, ReportPeriod.class }) + public void testConstructor() { + ContractException contractException = assertThrows(ContractException.class, () -> new TestReport(null, ReportPeriod.DAILY)); + assertEquals(ReportError.NULL_REPORT_LABEL, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, () -> new TestReport(new SimpleReportLabel("report"), null)); + assertEquals(ReportError.NULL_REPORT_PERIOD, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = PeriodicReport.class, name = "init", args = { ReportContext.class }) + public void testAddTimeFieldHeaders() { + + ReportHeader.Builder reportHeaderBuilder = ReportHeader.builder(); + + ReportLabel reportLabel = new SimpleReportLabel("report"); + + TestReport testReport = new TestReport(reportLabel, ReportPeriod.HOURLY); + testReport.addTimeFieldHeaders(reportHeaderBuilder); + ReportHeader reportHeader = reportHeaderBuilder.build(); + List headerStrings = reportHeader.getHeaderStrings(); + assertEquals(2, headerStrings.size()); + assertEquals("day", headerStrings.get(0)); + assertEquals("hour", headerStrings.get(1)); + + reportHeaderBuilder = ReportHeader.builder(); + testReport = new TestReport(reportLabel, ReportPeriod.DAILY); + testReport.addTimeFieldHeaders(reportHeaderBuilder); + reportHeader = reportHeaderBuilder.build(); + headerStrings = reportHeader.getHeaderStrings(); + assertEquals(1, headerStrings.size()); + assertEquals("day", headerStrings.get(0)); + + reportHeaderBuilder = ReportHeader.builder(); + testReport = new TestReport(reportLabel, ReportPeriod.END_OF_SIMULATION); + testReport.addTimeFieldHeaders(reportHeaderBuilder); + reportHeader = reportHeaderBuilder.build(); + headerStrings = reportHeader.getHeaderStrings(); + assertEquals(0, headerStrings.size()); + + } + + @Test + @UnitTestMethod(target = PeriodicReport.class, name = "init", args = { ReportContext.class }) + public void testFillTimeFields_Daily() { + double simulationEndTime = 10.6; + + List plugins = new ArrayList<>(); + + ReportLabel reportLabel = new SimpleReportLabel("report"); + DailyTestReport dailyTestReport = new DailyTestReport(reportLabel, ReportPeriod.DAILY); + + plugins.add(Plugin .builder()// + .setPluginId(new SimplePluginId("anonymous plugin"))// + .setInitializer((pc) -> { + pc.addReport(dailyTestReport::init); + })// + .build()); + + // add the reports plugin + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create an agent that will make the engine run for a few days + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(simulationEndTime, (c) -> { + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + plugins.add(testPlugin); + + // build and execute the simulation + + + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(plugins)// + .build()// + .execute(); + + int maxDay = (int) FastMath.ceil(simulationEndTime); + Set expectedDays = new LinkedHashSet<>(); + for (int i = 0; i <= maxDay; i++) { + expectedDays.add(i); + } + + Set actualDays = new LinkedHashSet<>(); + Map outputItems = testOutputConsumer.getOutputItemMap(ReportItem.class); + for (ReportItem reportItem : outputItems.keySet()) { + assertEquals(1, outputItems.get(reportItem)); + actualDays.add(Integer.parseInt(reportItem.getValue(0))); + } + assertEquals(expectedDays, actualDays); + + } + + @Test + @UnitTestMethod(target = PeriodicReport.class, name = "init", args = { ReportContext.class }) + public void testFillTimeFields_Hourly() { + double simulationEndTime = 3.6; + + List plugins = new ArrayList<>(); + + ReportLabel reportLabel = new SimpleReportLabel("report"); + HourlyTestReport hourlyTestReport = new HourlyTestReport(reportLabel, ReportPeriod.HOURLY); + + plugins.add(Plugin .builder()// + .setPluginId(new SimplePluginId("anonymous plugin"))// + .setInitializer((pc) -> { + pc.addReport(hourlyTestReport::init); + })// + .build()); + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create an agent that will make the engine run for a few days + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(simulationEndTime, (c) -> { + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + plugins.add(testPlugin); + + // build and execute the engine + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(plugins)// + .build()// + .execute(); + + // hours 0 through 3d 15h inclusive + Set expectedHours = new LinkedHashSet<>(); + for (int i = 0; i < 88; i++) { + expectedHours.add(i); + } + + Set actualHours = new LinkedHashSet<>(); + Map outputItems = testOutputConsumer.getOutputItemMap(ReportItem.class); + for (ReportItem reportItem : outputItems.keySet()) { + Integer count = outputItems.get(reportItem); + assertEquals(1, count); + int hour = Integer.parseInt(reportItem.getValue(0)); + hour *= 24; + hour += Integer.parseInt(reportItem.getValue(1)); + actualHours.add(hour); + } + + assertEquals(expectedHours, actualHours); + + } + + @Test + @UnitTestMethod(target = PeriodicReport.class, name = "init", args = { ReportContext.class }) + public void testFillTimeFields_EndOfSimulation() { + double simulationEndTime = 3.6; + + Simulation.Builder builder = Simulation.builder(); + + ReportLabel reportLabel = new SimpleReportLabel("report"); + EndOfSimulationTestReport endOfSimulationTestReport = new EndOfSimulationTestReport(reportLabel, ReportPeriod.END_OF_SIMULATION); + builder.addPlugin(Plugin .builder()// + .setPluginId(new SimplePluginId("anonymous plugin"))// + .setInitializer((pc) -> { + pc.addReport(endOfSimulationTestReport::init); + })// + .build()); + + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create an agent that will make the engine run for a few days + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(simulationEndTime, (c) -> { + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + builder.addPlugin(testPlugin); + + // build and execute the engine + builder.build().execute(); + + /* + * Show that the end of simulation test report executed its test exactly + * once at the end of the simulation + */ + + assertEquals(1, endOfSimulationTestReport.flushCount.getValue()); + assertEquals(simulationEndTime, endOfSimulationTestReport.flushTime.getValue()); + + } + + @Test + @UnitTestMethod(target = PeriodicReport.class, name = "init", args = { ReportContext.class }) + public void testInit() { + + for (ReportPeriod reportPeriod : ReportPeriod.values()) { + + double simulationEndTime = 10.6; + + Simulation.Builder builder = Simulation.builder(); + + ReportLabel reportLabel = new SimpleReportLabel("report"); + InitTestReport initTestReport = new InitTestReport(reportLabel, reportPeriod); + + builder.addPlugin(Plugin .builder()// + .setPluginId(new SimplePluginId("anonymous plugin"))// + .setInitializer((pc) -> { + pc.addReport(initTestReport::init); + })// + .build()); + + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // create an agent that will make the engine run for a few days + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(simulationEndTime, (c) -> { + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + builder.addPlugin(testPlugin); + + // build and execute the engine + builder.build().execute(); + + // show that the report is flushed at the end of the simulation + int lastIndex = initTestReport.flushTimes.size() - 1; + assertTrue(lastIndex >= 0); + double lastFlushTime = initTestReport.flushTimes.get(lastIndex); + assertEquals(simulationEndTime, lastFlushTime); + + // show that the report is flushed on the given reporting period + + // calculate the expected flushing times as a function of the report + // period + List expectedTimes = new ArrayList<>(); + switch (reportPeriod) { + case DAILY: + int lastDay = (int) simulationEndTime; + for (int i = 0; i <= lastDay; i++) { + double time = i; + expectedTimes.add(time); + } + expectedTimes.add(simulationEndTime); + break; + case END_OF_SIMULATION: + expectedTimes.add(simulationEndTime); + // expectedTimes.addAll(initTestReport.flushTimes); + break; + case HOURLY: + + int lastHour = (int) (simulationEndTime * 24); + for (int i = 0; i <= lastHour; i++) { + double time = i; + time /= 24; + expectedTimes.add(time); + } + expectedTimes.add(simulationEndTime); + + break; + default: + throw new RuntimeException("unhandled report period " + reportPeriod); + } + + // show that the expected and actual times are reasonably equal + assertEquals(expectedTimes.size(), initTestReport.flushTimes.size()); + + for (int i = 0; i < expectedTimes.size(); i++) { + assertEquals(expectedTimes.get(i), initTestReport.flushTimes.get(i), 0.00001); + } + + // precondition tests + + TestReport testReport = new TestReport(reportLabel, ReportPeriod.DAILY); + ContractException contractException = assertThrows(ContractException.class, () -> testReport.init(null)); + assertEquals(ReportError.NULL_CONTEXT, contractException.getErrorType()); + } + + // precondition tests + TestReport testReport = new TestReport(new SimpleReportLabel("report"), ReportPeriod.DAILY); + ContractException contractException = assertThrows(ContractException.class, () -> testReport.init(null)); + assertEquals(ReportError.NULL_CONTEXT, contractException.getErrorType()); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/AT_PeriodicReportPluginData.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/AT_PeriodicReportPluginData.java new file mode 100644 index 000000000..2c9de9a3c --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/AT_PeriodicReportPluginData.java @@ -0,0 +1,118 @@ +package gov.hhs.aspr.ms.gcm.plugins.reports.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestMethod; + +public class AT_PeriodicReportPluginData { + + private static class LocalPeriodicReportPluginData extends PeriodicReportPluginData { + + private final Data data; + + private LocalPeriodicReportPluginData(Data data) { + super(data); + this.data = data; + } + + private static class Data extends PeriodicReportPluginData.Data { + + private Data() { + super(); + } + + private Data(Data data) { + super(data); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(super.toString()); + builder.append("]"); + return builder.toString(); + } + } + + public final static class Builder extends PeriodicReportPluginData.Builder { + + private Data data; + + private Builder(Data data) { + super(data); + this.data = data; + } + + @Override + public LocalPeriodicReportPluginData build() { + return new LocalPeriodicReportPluginData(data); + } + + @Override + public Builder setReportLabel(ReportLabel reportLabel) { + super.setReportLabel(reportLabel); + return this; + } + + @Override + public Builder setReportPeriod(ReportPeriod reportPeriod) { + super.setReportPeriod(reportPeriod); + return this; + } + } + + public static Builder builder() { + return new Builder(new Data()); + } + + @Override + public Builder getCloneBuilder() { + return new Builder(new Data(data)); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("GroupPopulationReportPluginData [data="); + builder.append(data); + builder.append("]"); + return builder.toString(); + } + } + + @Test + @UnitTestMethod(target = PeriodicReportPluginData.Builder.class, name = "setReportLabel", args = { + ReportLabel.class }) + public void testSetReportLabel() { + + for (int i = 0; i < 10; i++) { + SimpleReportLabel simpleReportLabel = new SimpleReportLabel("report label " + i); + + LocalPeriodicReportPluginData localPeriodicReportPluginData = // + LocalPeriodicReportPluginData.builder()// + .setReportLabel(simpleReportLabel)// + .setReportPeriod(ReportPeriod.DAILY)// + .build(); + assertEquals(simpleReportLabel, localPeriodicReportPluginData.getReportLabel()); + } + } + + @Test + @UnitTestMethod(target = PeriodicReportPluginData.Builder.class, name = "setReportPeriod", args = { + ReportPeriod.class }) + public void testSetReportPeriod() { + for (ReportPeriod reportPeriod : ReportPeriod.values()) { + SimpleReportLabel simpleReportLabel = new SimpleReportLabel("report label"); + + LocalPeriodicReportPluginData localPeriodicReportPluginData = // + LocalPeriodicReportPluginData.builder()// + .setReportLabel(simpleReportLabel)// + .setReportPeriod(reportPeriod)// + .build(); + assertEquals(reportPeriod, localPeriodicReportPluginData.getReportPeriod()); + } + } + +} diff --git a/gcm3/src/test/java/plugins/reports/support/AT_ReportError.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/AT_ReportError.java similarity index 79% rename from gcm3/src/test/java/plugins/reports/support/AT_ReportError.java rename to gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/AT_ReportError.java index 2f80225b8..69d3c4006 100644 --- a/gcm3/src/test/java/plugins/reports/support/AT_ReportError.java +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/AT_ReportError.java @@ -1,4 +1,4 @@ -package plugins.reports.support; +package gov.hhs.aspr.ms.gcm.plugins.reports.support; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -8,14 +8,12 @@ import org.junit.jupiter.api.Test; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; +import util.annotations.UnitTestMethod; -@UnitTest(target = ReportError.class) public class AT_ReportError { @Test - @UnitTestMethod(name = "getDescription", args = {}) + @UnitTestMethod(target = ReportError.class,name = "getDescription", args = {}) public void test() { //show that each description is a unique, non-null and non-empty string Set descriptions = new LinkedHashSet<>(); diff --git a/gcm3/src/test/java/plugins/reports/support/AT_ReportHeader.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/AT_ReportHeader.java similarity index 88% rename from gcm3/src/test/java/plugins/reports/support/AT_ReportHeader.java rename to gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/AT_ReportHeader.java index 06646cdf9..7a5d6df50 100644 --- a/gcm3/src/test/java/plugins/reports/support/AT_ReportHeader.java +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/AT_ReportHeader.java @@ -1,4 +1,4 @@ -package plugins.reports.support; +package gov.hhs.aspr.ms.gcm.plugins.reports.support; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; @@ -13,16 +13,14 @@ import org.apache.commons.math3.random.RandomGenerator; import org.junit.jupiter.api.Test; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; +import util.annotations.UnitTestMethod; import util.errors.ContractException; import util.random.RandomGeneratorProvider; -@UnitTest(target = ReportHeader.class) public class AT_ReportHeader { @Test - @UnitTestMethod(name = "builder", args = {}) + @UnitTestMethod(target = ReportHeader.class, name = "builder", args = {}) public void testBuilder() { assertNotNull(ReportHeader.builder()); } @@ -85,7 +83,7 @@ public void testBuild() { } @Test - @UnitTestMethod(name = "getHeaderStrings", args = {}) + @UnitTestMethod(target = ReportHeader.class, name = "getHeaderStrings", args = {}) public void testGetHeaderStrings() { /* * Show that when no strings are added, the resulting header is empty @@ -125,7 +123,7 @@ public void testGetHeaderStrings() { } @Test - @UnitTestMethod(name = "toString", args = {}) + @UnitTestMethod(target = ReportHeader.class, name = "toString", args = {}) public void testToString() { ReportHeader reportHeader = ReportHeader.builder().build(); @@ -147,8 +145,8 @@ public void testToString() { } private static Character generateRandomCharacter(RandomGenerator randomGenerator) { - int i = randomGenerator.nextInt(26)+97; - return (char)i; + int i = randomGenerator.nextInt(26) + 97; + return (char) i; } private static String generateRandomString(RandomGenerator randomGenerator, int length) { @@ -160,7 +158,7 @@ private static String generateRandomString(RandomGenerator randomGenerator, int } @Test - @UnitTestMethod(name = "hashCode", args = {}) + @UnitTestMethod(target = ReportHeader.class, name = "hashCode", args = {}) public void testHashCode() { RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2142808365770946523L); @@ -205,15 +203,15 @@ public void testHashCode() { } @Test - @UnitTestMethod(name = "equals", args = {}) + @UnitTestMethod(target = ReportHeader.class, name = "equals", args = { Object.class }) public void testEquals() { - ReportHeader.Builder builder = ReportHeader.builder(); - ReportHeader AB1 = builder.add("A").add("B").build(); - ReportHeader BA = builder.add("B").add("A").build(); - ReportHeader ABC1 = builder.add("A").add("B").add("C").build(); - ReportHeader AB2 = builder.add("A").add("B").build(); - ReportHeader ABC2 = builder.add("A").add("B").add("C").build(); - ReportHeader ABC3 = builder.add("A").add("B").add("C").build(); + + ReportHeader AB1 = ReportHeader.builder().add("A").add("B").build(); + ReportHeader BA = ReportHeader.builder().add("B").add("A").build(); + ReportHeader ABC1 = ReportHeader.builder().add("A").add("B").add("C").build(); + ReportHeader AB2 = ReportHeader.builder().add("A").add("B").build(); + ReportHeader ABC2 = ReportHeader.builder().add("A").add("B").add("C").build(); + ReportHeader ABC3 = ReportHeader.builder().add("A").add("B").add("C").build(); // Reflexive assertEquals(AB1, AB1); diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/AT_ReportItem.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/AT_ReportItem.java new file mode 100644 index 000000000..06648f95f --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/AT_ReportItem.java @@ -0,0 +1,297 @@ +package gov.hhs.aspr.ms.gcm.plugins.reports.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public final class AT_ReportItem { + + @Test + @UnitTestMethod(target = ReportItem.class,name = "builder", args = {}) + public void testBuilder() { + assertNotNull(ReportItem.builder()); + } + + @Test + @UnitTestMethod(target = ReportItem.Builder.class, name = "addValue", args = { Object.class }) + public void testAddValue() { + + for (int i = 0; i < 10; i++) { + ReportItem reportItem = ReportItem .builder()// + .setReportLabel(new SimpleReportLabel("report"))// + .setReportHeader(ReportHeader.builder().build())// + .addValue(i)// + .addValue(i - 1)// + .build();// + + assertEquals(Integer.toString(i), reportItem.getValue(0)); + assertEquals(Integer.toString(i - 1), reportItem.getValue(1)); + } + + // precondition tests + ContractException contractException = assertThrows(ContractException.class, () -> ReportItem.builder().addValue(null)); + assertEquals(ReportError.NULL_REPORT_ITEM_ENTRY, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ReportItem.Builder.class, name = "build", args = {}) + public void testBuild() { + + // precondition tests + + ContractException contractException = assertThrows(ContractException.class, () -> { + ReportItem.builder().setReportLabel(new SimpleReportLabel("report")).build(); + }); + assertEquals(ReportError.NULL_REPORT_HEADER, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, () -> { + ReportItem.builder().setReportHeader(ReportHeader.builder().build()).build(); + }); + assertEquals(ReportError.NULL_REPORT_LABEL, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ReportItem.Builder.class, name = "setReportHeader", args = { ReportHeader.class }) + public void testSetReportHeader() { + ReportHeader reportHeader = ReportHeader.builder().add("A").add("B").build(); + + ReportItem reportItem = ReportItem.builder().setReportHeader(reportHeader).setReportLabel(new SimpleReportLabel("report")).build(); + + assertEquals(reportHeader, reportItem.getReportHeader()); + + // precondition tests + ContractException contractException = assertThrows(ContractException.class, () -> ReportItem.builder().setReportHeader(null)); + assertEquals(ReportError.NULL_REPORT_HEADER, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ReportItem.Builder.class, name = "setReportLabel", args = { ReportLabel.class }) + public void testSetReportLabel() { + + SimpleReportLabel reportLabel = new SimpleReportLabel("report"); + + ReportItem reportItem = ReportItem.builder().setReportHeader(ReportHeader.builder().build()).setReportLabel(reportLabel).build(); + + assertEquals(reportLabel, reportItem.getReportLabel()); + + // precondition tests + ContractException contractException = assertThrows(ContractException.class, () -> ReportItem.builder().setReportLabel(null)); + assertEquals(ReportError.NULL_REPORT_LABEL, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ReportItem.class,name = "getReportLabel", args = {}) + public void testGetReportLabel() { + SimpleReportLabel reportLabel = new SimpleReportLabel("report"); + + ReportItem reportItem = ReportItem.builder().setReportHeader(ReportHeader.builder().build()).setReportLabel(reportLabel).build(); + + assertEquals(reportLabel, reportItem.getReportLabel()); + } + + @Test + @UnitTestMethod(target = ReportItem.class,name = "getReportHeader", args = {}) + + public void testGetReportHeader() { + ReportHeader reportHeader = ReportHeader.builder().add("A").add("B").build(); + + ReportItem reportItem = ReportItem.builder().setReportHeader(reportHeader).setReportLabel(new SimpleReportLabel("report")).build(); + + assertEquals(reportHeader, reportItem.getReportHeader()); + } + + @Test + @UnitTestMethod(target = ReportItem.class,name = "getValue", args = { int.class }) + + public void testGetValue() { + ReportHeader reportHeader = ReportHeader.builder().add("A").add("B").add("C").add("D").build(); + ReportItem reportItem = ReportItem .builder()// + .setReportHeader(reportHeader)// + .setReportLabel(new SimpleReportLabel("report"))// + .addValue("alpha")// + .addValue(12)// + .addValue(false)// + .addValue(123.42)// + .build();// + + assertEquals("alpha", reportItem.getValue(0)); + assertEquals(Integer.toString(12), reportItem.getValue(1)); + assertEquals(Boolean.toString(false), reportItem.getValue(2)); + assertEquals(Double.toString(123.42), reportItem.getValue(3)); + + } + + @Test + @UnitTestMethod(target = ReportItem.class,name = "size", args = {}) + + public void testSize() { + ReportHeader reportHeader = ReportHeader.builder().build(); + SimpleReportLabel reportLabel = new SimpleReportLabel("report"); + + for (int i = 0; i < 10; i++) { + ReportItem.Builder builder = ReportItem.builder().setReportHeader(reportHeader).setReportLabel(reportLabel); + for (int j = 0; j < i; j++) { + builder.addValue(j); + } + ReportItem reportItem = builder.build(); + assertEquals(i, reportItem.size()); + } + + } + + @Test + @UnitTestMethod(target = ReportItem.class,name = "toString", args = {}) + public void testToString() { + ReportHeader reportHeader = ReportHeader.builder().build(); + SimpleReportLabel reportLabel = new SimpleReportLabel("report"); + + ReportItem reportItem = ReportItem.builder().setReportHeader(reportHeader).setReportLabel(reportLabel).addValue("A").addValue("B").build(); + + String expectedValue = "ReportItem [reportLabel=SimpleReportLabel [value=report], reportHeader=ReportHeader [headerStrings=[]], values=[A, B]]"; + String actualValue = reportItem.toString(); + assertEquals(expectedValue, actualValue); + } + + @Test + @UnitTestMethod(target = ReportItem.class,name = "toValueString", args = {}) + public void testToValueString() { + ReportHeader reportHeader = ReportHeader.builder().build(); + SimpleReportLabel reportLabel = new SimpleReportLabel("report"); + + ReportItem reportItem = ReportItem.builder().setReportHeader(reportHeader).setReportLabel(reportLabel).addValue("A").addValue("B").build(); + + String expectedValue = "[A, B]"; + String actualValue = reportItem.toValueString(); + + assertEquals(expectedValue, actualValue); + } + + @Test + @UnitTestMethod(target = ReportItem.class,name = "hashCode", args = {}) + public void testHashCode() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7481311225319288863L); + /* + * Show equal report items have equal hash codes. We will focus on the + * values part since other tests should cover the report label and header. + */ + + ReportHeader reportHeader = ReportHeader.builder().build(); + SimpleReportLabel reportLabel = new SimpleReportLabel("report"); + + ReportItem reportItem1 = ReportItem.builder().setReportHeader(reportHeader).setReportLabel(reportLabel).build(); + ReportItem reportItem2 = ReportItem.builder().setReportHeader(reportHeader).setReportLabel(reportLabel).build(); + assertEquals(reportItem1.hashCode(), reportItem2.hashCode()); + + reportItem1 = ReportItem.builder().setReportHeader(reportHeader).setReportLabel(reportLabel).addValue("A").addValue("B").build(); + reportItem2 = ReportItem.builder().setReportHeader(reportHeader).setReportLabel(reportLabel).addValue("A").addValue("B").build(); + assertEquals(reportItem1.hashCode(), reportItem2.hashCode()); + + /* + * Show that the hash codes are reasonable dispersed + * + */ + Set hashCodes = new LinkedHashSet<>(); + ReportItem.Builder builder = ReportItem.builder(); + + int sampleCount = 1000; + for (int i = 0; i < sampleCount; i++) { + builder.setReportHeader(reportHeader).setReportLabel(reportLabel); + int fieldCount = randomGenerator.nextInt(3) + 1; + for(int j = 0;j4*sampleCount/5); + + } + + private static Character generateRandomCharacter(RandomGenerator randomGenerator) { + int i = randomGenerator.nextInt(26) + 97; + return (char) i; + } + + private static String generateRandomString(RandomGenerator randomGenerator, int length) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; i++) { + sb.append(generateRandomCharacter(randomGenerator)); + } + return sb.toString(); + } + + @Test + @UnitTestMethod(target = ReportItem.class,name = "equals", args = { Object.class }) + + public void testEquals() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7530977954336798039L); + + ReportHeader reportHeader = ReportHeader.builder().build(); + SimpleReportLabel reportLabel = new SimpleReportLabel("report"); + + + /* + * Show that equal objects are equal + * + */ + ReportItem.Builder builder1 = ReportItem.builder(); + ReportItem.Builder builder2 = ReportItem.builder(); + + int sampleCount = 100; + for (int i = 0; i < sampleCount; i++) { + builder1.setReportHeader(reportHeader).setReportLabel(reportLabel); + builder2.setReportHeader(reportHeader).setReportLabel(reportLabel); + int fieldCount = randomGenerator.nextInt(3) + 1; + for(int j = 0;j new SimpleReportLabel(null)); + assertEquals(contractException.getErrorType(), ReportError.NULL_REPORT_LABEL); + + } + + @Test + @UnitTestMethod(target = SimpleReportLabel.class, name = "toString", args = {}) + public void testToString() { + Object value = 325; + SimpleReportLabel simpleReportLabel = new SimpleReportLabel(value); + String expectedString = "SimpleReportLabel [value=" + value + "]"; + String actualString = simpleReportLabel.toString(); + + assertEquals(expectedString, actualString); + } + + @Test + @UnitTestMethod(target = SimpleReportLabel.class, name = "hashCode", args = {}) + public void testHashCode() { + // equal objects have equal hash codes + for (int i = 0; i < 30; i++) { + SimpleReportLabel s1 = new SimpleReportLabel(i); + SimpleReportLabel s2 = new SimpleReportLabel(i); + assertEquals(s1.hashCode(), s2.hashCode()); + } + + Set hashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 30; i++) { + boolean unique = hashCodes.add(new SimpleReportLabel(i).hashCode()); + assertTrue(unique); + } + } + + @Test + @UnitTestMethod(target = SimpleReportLabel.class, name = "equals", args = { Object.class }, tags = UnitTag.INCOMPLETE) + public void testEquals() { + + SimpleReportLabel id_1 = new SimpleReportLabel(2); + SimpleReportLabel id_2 = new SimpleReportLabel(5); + SimpleReportLabel id_3 = new SimpleReportLabel(2); + SimpleReportLabel id_4 = new SimpleReportLabel("A"); + SimpleReportLabel id_5 = new SimpleReportLabel("A"); + SimpleReportLabel id_6 = new SimpleReportLabel("B"); + SimpleReportLabel id_7 = new SimpleReportLabel("A"); + MutableInteger simpleGlobalPropertyId = new MutableInteger(2); + + // should return false if the object is not a SimpleReportLabel + assertNotEquals(id_1, simpleGlobalPropertyId); + + assertEquals(id_1, id_1); // testing reflexive property + assertNotEquals(id_1, id_2); + assertEquals(id_1, id_3); // part of reflective property test + assertNotEquals(id_1, id_4); + assertNotEquals(id_1, id_5); + assertNotEquals(id_1, id_6); + + assertNotEquals(id_2, id_1); + assertEquals(id_2, id_2); + assertNotEquals(id_2, id_3); + assertNotEquals(id_2, id_4); + assertNotEquals(id_2, id_5); + assertNotEquals(id_2, id_6); + + assertEquals(id_3, id_1); // part of reflective property test + assertNotEquals(id_3, id_2); + assertEquals(id_3, id_3); + assertNotEquals(id_3, id_4); + assertNotEquals(id_3, id_5); + assertNotEquals(id_3, id_6); + + assertNotEquals(id_4, id_1); + assertNotEquals(id_4, id_2); + assertNotEquals(id_4, id_3); + assertEquals(id_4, id_4); + assertEquals(id_4, id_5); // part of transitive property test + assertNotEquals(id_4, id_6); + assertEquals(id_4, id_7); // part of transitive property test + + assertNotEquals(id_5, id_1); + assertNotEquals(id_5, id_2); + assertNotEquals(id_5, id_3); + assertEquals(id_5, id_4); + assertEquals(id_5, id_5); + assertNotEquals(id_5, id_6); + assertEquals(id_5, id_7); // part of transitive property test + + assertNotEquals(id_6, id_1); + assertNotEquals(id_6, id_2); + assertNotEquals(id_6, id_3); + assertNotEquals(id_6, id_4); + assertNotEquals(id_6, id_5); + assertEquals(id_6, id_6); + + // null tests + assertNotEquals(id_1, null); + assertNotEquals(id_2, null); + assertNotEquals(id_3, null); + assertNotEquals(id_4, null); + assertNotEquals(id_5, null); + assertNotEquals(id_6, null); + } + + @Test + @UnitTestMethod(target = SimpleReportLabel.class, name = "getValue", args = {}) + public void testGetValue() { + Object value = "some value"; + SimpleReportLabel simpleReportLabel = new SimpleReportLabel(value); + assertEquals(value, simpleReportLabel.getValue()); + + value = 678; + simpleReportLabel = new SimpleReportLabel(value); + assertEquals(value, simpleReportLabel.getValue()); + + + value = false; + simpleReportLabel = new SimpleReportLabel(value); + assertEquals(value, simpleReportLabel.getValue()); + + value = 2.98; + simpleReportLabel = new SimpleReportLabel(value); + assertEquals(value, simpleReportLabel.getValue()); + + } + +} \ No newline at end of file diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/MT_NIOReportItemHandler.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/MT_NIOReportItemHandler.java new file mode 100644 index 000000000..8dbb018c0 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/reports/support/MT_NIOReportItemHandler.java @@ -0,0 +1,997 @@ +package gov.hhs.aspr.ms.gcm.plugins.reports.support; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import gov.hhs.aspr.ms.gcm.nucleus.Dimension; +import gov.hhs.aspr.ms.gcm.nucleus.Experiment; +import gov.hhs.aspr.ms.gcm.nucleus.ExperimentParameterData; +import gov.hhs.aspr.ms.gcm.nucleus.ExperimentStatusConsole; +import gov.hhs.aspr.ms.gcm.nucleus.FunctionalDimension; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; + +public final class MT_NIOReportItemHandler { + + private static enum Command { + COMMENT("-c"), // + HELP("-help"), // + DIRECTORY("-d"), // + TEST("-t"), // + UNKNOWN("-")// + ; + + private String commandString; + + private Command(String commandString) { + this.commandString = commandString; + } + + public static Command getCommand(String value) { + Command result = null; + + for (Command command : Command.values()) { + if (command != UNKNOWN) { + if (command.commandString.equals(value)) { + result = command; + } + } + } + + if (result == null) { + if (value.startsWith("-")) { + result = UNKNOWN; + result.commandString = value; + } + } + + return result; + } + } + + private static void printInstructions() { + + StringBuilder sb = new StringBuilder(); + + sb.append("Usage: " + "\n"); + sb.append("\t" + "Any order of the following commands are legal:" + "\n"); + sb.append("\t" + "\t" + "-c followed by any number arguments to ignore" + "\n"); + sb.append("\t" + "\t" + "-d followed by a directory name" + "\n"); + sb.append("\t" + "\t" + "-t followed by a test case number" + "\n"); + sb.append("\t" + "\t" + "-help for instructions" + "\n"); + sb.append("\t" + "Exactly one directory name and exactly one test case number are required."); + sb.append("Example: " + "\n"); + sb.append("\t" + "-d c:\\temp\\src\\main\\java c:\\temp\\src\\test\\java" + "\n"); + sb.append("\t" + "-t 1" + "\n"); + sb.append("\t" + "-c testing" + "\n"); + sb.append("Test Cases: " + "\n"); + sb.append("\t" + "Test 1:" + "\n"); + sb.append("\t" + "\t" + "No progress log will be written, no progress log will be read, and " + "\n"); + sb.append("\t" + "\t" + "the experiment columns will be used. For this test, a custom delimiter is also set." + "\n"); + sb.append("\t" + "Test 2:" + "\n"); + sb.append("\t" + "\t" + "No progress log will be written, no progress log will be read, and " + "\n"); + sb.append("\t" + "\t" + "no experiment columns will be used." + "\n"); + sb.append("\t" + "Test 3:" + "\n"); + sb.append("\t" + "\t" + "A progress log will be written, the progress log won't be read, and " + "\n"); + sb.append("\t" + "\t" + "no experiment columns will be used" + "\n"); + sb.append("\t" + "Test 4:" + "\n"); + sb.append("\t" + "\t" + "A progress log will be written, the progress log will be read, and " + "\n"); + sb.append("\t" + "\t" + "the experiment columns will be used" + "\n"); + sb.append("\t" + "Test 5:" + "\n"); + sb.append("\t" + "\t" + "No progress log will be written, an attempt at reading a non-existent progress log " + "\n"); + sb.append("\t" + "\t" + "will be made, and the experiment columns will be used" + "\n"); + sb.append("\t" + "Test 6:" + "\n"); + sb.append("\t" + "\t" + "No progress log will be written, no progress log will be read, and " + "\n"); + sb.append("\t" + "\t" + "the experiment columns will be used. For this test, a custom delimiter is also set." + "\n"); + sb.append("\t" + "\t" + "The process for this test will be executed twice." + "\n"); + sb.append("\t" + "Test 7:" + "\n"); + sb.append("\t" + "\t" + "No progress log will be written, no progress log will be read, and " + "\n"); + sb.append("\t" + "\t" + "the experiment columns will be used. For this test, a custom delimiter is also set." + "\n"); + sb.append("\t" + "\t" + "An experiment report will be written." + "\n"); + System.out.println(sb); + } + + private static class CommandBlock { + private final Command command; + private final List arguments = new ArrayList<>(); + + public CommandBlock(Command command) { + this.command = command; + } + + } + + private final Path basePath; + private Integer testToRun; + + private MT_NIOReportItemHandler(Path dirPath, Integer testToRun) { + this.basePath = dirPath; + this.testToRun = testToRun; + } + + public static void main(String[] args) throws IOException { + + Map> commandBlocks = new LinkedHashMap<>(); + for (Command command : Command.values()) { + commandBlocks.put(command, new ArrayList<>()); + } + CommandBlock currentCommandBlock = new CommandBlock(Command.UNKNOWN); + commandBlocks.get(currentCommandBlock.command).add(currentCommandBlock); + + for (String arg : args) { + Command command = Command.getCommand(arg); + if (command != null) { + currentCommandBlock = new CommandBlock(command); + commandBlocks.get(command).add(currentCommandBlock); + } else { + currentCommandBlock.arguments.add(arg); + } + } + + Path basePath = null; + int testIndex = 0; + + // HELP + int directoryCount = commandBlocks.get(Command.DIRECTORY).size(); + int testCount = commandBlocks.get(Command.TEST).size(); + + if (directoryCount == 0 && testCount == 0) { + printInstructions(); + return; + } + + List blocks = commandBlocks.get(Command.HELP); + if (!blocks.isEmpty()) { + printInstructions(); + return; + } + + // DIRECTORY + blocks = commandBlocks.get(Command.DIRECTORY); + if (blocks.isEmpty()) { + throw new RuntimeException("requires a directory command -d"); + } + + if (blocks.size() > 1) { + throw new RuntimeException("too many directory commands -d"); + } + + CommandBlock commandBlock = blocks.get(0); + if (commandBlock.arguments.isEmpty()) { + throw new RuntimeException("requires exactly one directory for -d"); + } + + if (commandBlock.arguments.size() > 1) { + throw new RuntimeException("too many directories listed for -d"); + } + + String directoryName = commandBlock.arguments.get(0); + basePath = Paths.get(directoryName); + if (!basePath.toFile().exists()) { + throw new RuntimeException("base directory does not exist"); + } + if (!basePath.toFile().isDirectory()) { + throw new RuntimeException("base directory is not a directory"); + } + + // TEST + + blocks = commandBlocks.get(Command.TEST); + if (blocks.isEmpty()) { + throw new RuntimeException("requires a test command -t"); + } + + if (blocks.size() > 1) { + throw new RuntimeException("too many test commands -t"); + } + + commandBlock = blocks.get(0); + if (commandBlock.arguments.isEmpty()) { + throw new RuntimeException("requires exactly one test number for -t"); + } + + if (commandBlock.arguments.size() > 1) { + throw new RuntimeException("too many test numbers listed for -t"); + } + try { + testIndex = Integer.parseInt(commandBlock.arguments.get(0)); + } catch (NumberFormatException e) { + throw new RuntimeException("test index needs to be a integer", e); + } + + if (testIndex < 1 || testIndex > 7) { + throw new RuntimeException("test index out of bounds"); + } + + // UNKNOWN + blocks = commandBlocks.get(Command.UNKNOWN); + + if (blocks.size() > 1) { + StringBuilder sb = new StringBuilder(); + String unknownCommand = commandBlocks.get(Command.UNKNOWN).get(0).command.commandString; + if (!currentCommandBlock.arguments.isEmpty()) { + String unknownCommandArgs = currentCommandBlock.arguments.get(0); + sb.append("encountered an unknown command: "); + sb.append(unknownCommand); + sb.append(" "); + sb.append(unknownCommandArgs); + } else { + sb.append("encountered an unknown command: "); + sb.append(unknownCommand); + } + throw new RuntimeException(sb.toString()); + } + + new MT_NIOReportItemHandler(basePath, testIndex).execute(); + } + + private void recursiveDelete(File file) throws IOException { + if (file.exists()) { + if (file.isDirectory()) { + File[] entries = file.listFiles(); + if (entries != null) { + for (File entry : entries) { + recursiveDelete(entry); + } + } + } + if (!file.delete()) { + throw new IOException("Failed to delete " + file); + } + } + } + + private void createDirectory(Path path) throws IOException { + recursiveDelete(path.toFile()); + Files.createDirectory(path); + + } + + private void execute() throws IOException { + switch (testToRun) { + case 1: + Path subPath = basePath.resolve("test1"); + createDirectory(subPath); + printExpected(1); + test1(subPath); + break; + case 2: + subPath = basePath.resolve("test2"); + createDirectory(subPath); + printExpected(2); + test2(subPath); + break; + case 3: + subPath = basePath.resolve("test3"); + createDirectory(subPath); + printExpected(3); + test3(subPath); + break; + case 4: + subPath = basePath.resolve("test4"); + createDirectory(subPath); + printExpected(4); + test4(subPath); + break; + case 5: + subPath = basePath.resolve("test5"); + createDirectory(subPath); + printExpected(5); + test5(subPath); + break; + case 6: + subPath = basePath.resolve("test6"); + createDirectory(subPath); + printExpected(6); + test1(subPath); + test1(subPath); + break; + case 7: + subPath = basePath.resolve("test7"); + createDirectory(subPath); + printExpected(7); + test7(subPath); + break; + default: + throw new RuntimeException("unknown test number: " + testToRun); + } + } + + /* + * no progress log written + * + * no progress log read + * + * use experiment columns + * + * delimiter set + * + * write three reports + */ + private void test1(Path subPath) { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + + ReportHeader.Builder reportHeaderBuilder = ReportHeader.builder(); + reportHeaderBuilder.add("alpha"); + reportHeaderBuilder.add("beta"); + ReportHeader reportHeader = reportHeaderBuilder.build(); + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + + for (int i = 0; i < 10; i++) { + ReportItem.Builder reportItemBuilder = ReportItem.builder(); + reportItemBuilder.setReportHeader(reportHeader); + reportItemBuilder.setReportLabel(reportLabel); + reportItemBuilder.addValue(i); + reportItemBuilder.addValue("value " + i); + ReportItem reportItem = reportItemBuilder.build(); + c.releaseOutput(reportItem); + } + })); + + Dimension dimension1 = FunctionalDimension.builder()// + .addMetaDatum("xxx")// + .addLevel((c) -> { + List result = new ArrayList<>(); + result.add("a"); + return result; + }).addLevel((c) -> { + List result = new ArrayList<>(); + result.add("b"); + return result; + }).build(); + + Dimension dimension2 = FunctionalDimension.builder()// + .addMetaDatum("xyz").addLevel((c) -> { + List result = new ArrayList<>(); + result.add("x"); + return result; + }).addLevel((c) -> { + List result = new ArrayList<>(); + result.add("y"); + return result; + }).addLevel((c) -> { + List result = new ArrayList<>(); + result.add("z"); + return result; + }).build(); + + NIOReportItemHandler nioReportItemHandler = // + NIOReportItemHandler.builder()// + .addReport(reportLabel, subPath.resolve("report1.txt"))// + .setDelimiter(",").build(); + + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + ExperimentStatusConsole experimentStatusConsole = ExperimentStatusConsole.builder().build(); + + Experiment .builder()// + .addPlugin(testPlugin)// + .addDimension(dimension1)// + .addDimension(dimension2)// + .addExperimentContextConsumer(nioReportItemHandler)// + .addExperimentContextConsumer(experimentStatusConsole)// + .build()// + .execute(); + + } + + /* + * no progress log written + * + * no progress log read + * + * no experiment columns + * + * write three reports + */ + private void test2(Path subPath) { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + + ReportHeader.Builder reportHeaderBuilder = ReportHeader.builder(); + reportHeaderBuilder.add("alpha"); + reportHeaderBuilder.add("beta"); + ReportHeader reportHeader = reportHeaderBuilder.build(); + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + + for (int i = 0; i < 10; i++) { + ReportItem.Builder reportItemBuilder = ReportItem.builder(); + reportItemBuilder.setReportHeader(reportHeader); + reportItemBuilder.setReportLabel(reportLabel); + reportItemBuilder.addValue(i); + reportItemBuilder.addValue("value " + i); + ReportItem reportItem = reportItemBuilder.build(); + c.releaseOutput(reportItem); + } + })); + + FunctionalDimension dimension1 = FunctionalDimension.builder()// + .addMetaDatum("xxx")// + .addLevel((c) -> { + List result = new ArrayList<>(); + result.add("a"); + return result; + })// + .addLevel((c) -> { + List result = new ArrayList<>(); + result.add("b"); + return result; + })// + .build(); + + Dimension dimension2 = FunctionalDimension.builder()// + .addMetaDatum("xyz")// + .addLevel((c) -> { + List result = new ArrayList<>(); + result.add("x"); + return result; + })// + .addLevel((c) -> { + List result = new ArrayList<>(); + result.add("y"); + return result; + })// + .addLevel((c) -> { + List result = new ArrayList<>(); + result.add("z"); + return result; + })// + .build();// + + NIOReportItemHandler nioReportItemHandler = // + NIOReportItemHandler.builder()// + .addReport(reportLabel, subPath.resolve("report1.txt"))// + .setDisplayExperimentColumnsInReports(false)// + .build(); + + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + ExperimentStatusConsole experimentStatusConsole = ExperimentStatusConsole.builder().build(); + + Experiment .builder()// + .addPlugin(testPlugin)// + .addDimension(dimension1)// + .addDimension(dimension2)// + .addExperimentContextConsumer(nioReportItemHandler)// + .addExperimentContextConsumer(experimentStatusConsole)// + .build()// + .execute(); + } + + /* + * write progress log + * + * do not read progress log + * + * write three reports + */ + private void test3(Path subPath) { + + ReportLabel reportLabel = new SimpleReportLabel("report label"); + + ReportHeader.Builder reportHeaderBuilder = ReportHeader.builder(); + reportHeaderBuilder.add("alpha"); + reportHeaderBuilder.add("beta"); + ReportHeader reportHeader = reportHeaderBuilder.build(); + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + + for (int i = 0; i < 10; i++) { + ReportItem.Builder reportItemBuilder = ReportItem.builder(); + reportItemBuilder.setReportHeader(reportHeader); + reportItemBuilder.setReportLabel(reportLabel); + reportItemBuilder.addValue(i); + reportItemBuilder.addValue("value " + i); + ReportItem reportItem = reportItemBuilder.build(); + c.releaseOutput(reportItem); + } + })); + + Dimension dimension1 = FunctionalDimension.builder()// + .addMetaDatum("xxx")// + .addLevel((c) -> { + List result = new ArrayList<>(); + result.add("a"); + return result; + })// + .addLevel((c) -> { + List result = new ArrayList<>(); + result.add("b"); + return result; + })// + .build(); + + Dimension dimension2 = FunctionalDimension.builder()// + .addMetaDatum("xyz")// + .addLevel((c) -> { + List result = new ArrayList<>(); + result.add("x"); + return result; + })// + .addLevel((c) -> { + List result = new ArrayList<>(); + result.add("y"); + return result; + })// + .addLevel((c) -> { + List result = new ArrayList<>(); + result.add("z"); + return result; + })// + .build(); + + NIOReportItemHandler nioReportItemHandler = // + NIOReportItemHandler.builder()// + .addReport(reportLabel, subPath.resolve("report1.txt"))// + .build(); + + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + ExperimentStatusConsole experimentStatusConsole = ExperimentStatusConsole.builder().build(); + + ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()// + .setExperimentProgressLog(subPath.resolve("progresslog.txt"))// + .setContinueFromProgressLog(false)// + .build(); + + Experiment .builder()// + .addPlugin(testPlugin)// + .addDimension(dimension1)// + .addDimension(dimension2)// + .addExperimentContextConsumer(nioReportItemHandler)// + .addExperimentContextConsumer(experimentStatusConsole)// + .setExperimentParameterData(experimentParameterData)// + .build()// + .execute(); + } + + /* + * write progress log + * + * read progress log + * + * write three reports + * + */ + private void test4(Path subPath) throws IOException { + + CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder(); + OutputStream out = Files.newOutputStream(subPath.resolve("progresslog.txt"), StandardOpenOption.CREATE); + BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, encoder)); + + writer.write("scenario"); + writer.write("\t"); + writer.write("xxx"); + writer.write("\t"); + writer.write("xyz"); + writer.newLine(); + + writer.write("0"); + writer.write("\t"); + writer.write("a"); + writer.write("\t"); + writer.write("x"); + writer.newLine(); + + writer.write("1"); + writer.write("\t"); + writer.write("b"); + writer.write("\t"); + writer.write("x"); + writer.newLine(); + + writer.close(); + + ReportLabel reportLabel = new SimpleReportLabel("report label"); + + ReportHeader.Builder reportHeaderBuilder = ReportHeader.builder(); + reportHeaderBuilder.add("alpha"); + reportHeaderBuilder.add("beta"); + ReportHeader reportHeader = reportHeaderBuilder.build(); + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + + for (int i = 0; i < 10; i++) { + ReportItem.Builder reportItemBuilder = ReportItem.builder(); + reportItemBuilder.setReportHeader(reportHeader); + reportItemBuilder.setReportLabel(reportLabel); + reportItemBuilder.addValue(i); + reportItemBuilder.addValue("value " + i); + ReportItem reportItem = reportItemBuilder.build(); + c.releaseOutput(reportItem); + } + })); + + Dimension dimension1 = FunctionalDimension.builder()// + .addMetaDatum("xxx")// + .addLevel((c) -> { + List result = new ArrayList<>(); + result.add("a"); + return result; + })// + .addLevel((c) -> { + List result = new ArrayList<>(); + result.add("b"); + return result; + })// + .build();// + + Dimension dimension2 = FunctionalDimension.builder()// + .addMetaDatum("xyz").addLevel((c) -> { + List result = new ArrayList<>(); + result.add("x"); + return result; + }).addLevel((c) -> { + List result = new ArrayList<>(); + result.add("y"); + return result; + }).addLevel((c) -> { + List result = new ArrayList<>(); + result.add("z"); + return result; + }).build(); + + NIOReportItemHandler nioReportItemHandler = // + NIOReportItemHandler.builder()// + .addReport(reportLabel, subPath.resolve("report1.txt"))// + .build(); + + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + ExperimentStatusConsole experimentStatusConsole = ExperimentStatusConsole.builder().build(); + + ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()// + .setExperimentProgressLog(subPath.resolve("progresslog.txt"))// + .setContinueFromProgressLog(true)// + .build(); + + Experiment .builder()// + .addPlugin(testPlugin)// + .addDimension(dimension1)// + .addDimension(dimension2)// + .addExperimentContextConsumer(nioReportItemHandler)// + .addExperimentContextConsumer(experimentStatusConsole)// + .setExperimentParameterData(experimentParameterData)// + .build()// + .execute(); + + } + + /* + * no progress log written + * + * progress log read + * + * write three reports + * + * use experiment columns + * + */ + private void test5(Path subPath) { + + ReportLabel reportLabel = new SimpleReportLabel("report label"); + + ReportHeader.Builder reportHeaderBuilder = ReportHeader.builder(); + reportHeaderBuilder.add("alpha"); + reportHeaderBuilder.add("beta"); + ReportHeader reportHeader = reportHeaderBuilder.build(); + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + + for (int i = 0; i < 10; i++) { + ReportItem.Builder reportItemBuilder = ReportItem.builder(); + reportItemBuilder.setReportHeader(reportHeader); + reportItemBuilder.setReportLabel(reportLabel); + reportItemBuilder.addValue(i); + reportItemBuilder.addValue("value " + i); + ReportItem reportItem = reportItemBuilder.build(); + c.releaseOutput(reportItem); + } + })); + + Dimension dimension1 = FunctionalDimension.builder()// + .addMetaDatum("xxx")// + .addLevel((c) -> { + List result = new ArrayList<>(); + result.add("a"); + return result; + })// + .addLevel((c) -> { + List result = new ArrayList<>(); + result.add("b"); + return result; + })// + .build(); + + Dimension dimension2 = FunctionalDimension.builder()// + .addMetaDatum("xyz") + .addLevel((c) -> { + List result = new ArrayList<>(); + result.add("x"); + return result; + }) + .addLevel((c) -> { + List result = new ArrayList<>(); + result.add("y"); + return result; + }) + .addLevel((c) -> { + List result = new ArrayList<>(); + result.add("z"); + return result; + }) + .build(); + + NIOReportItemHandler nioReportItemHandler = // + NIOReportItemHandler.builder()// + .addReport(reportLabel, subPath.resolve("report1.txt"))// + .build(); + + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + ExperimentStatusConsole experimentStatusConsole = ExperimentStatusConsole.builder().build(); + + ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()// + .setExperimentProgressLog(subPath.resolve("progresslog.txt"))// + .setContinueFromProgressLog(true)// + .build(); + + Experiment .builder()// + .addPlugin(testPlugin)// + .addDimension(dimension1)// + .addDimension(dimension2)// + .addExperimentContextConsumer(nioReportItemHandler)// + .addExperimentContextConsumer(experimentStatusConsole)// + .setExperimentParameterData(experimentParameterData)// + .build()// + .execute(); + + } + + /* + * no progress log written + * + * no progress log read + * + * use experiment columns + * + * delimiter set + * + * write four reports + * + * write one experiment report + */ + private void test7(Path subPath) { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + + ReportHeader.Builder reportHeaderBuilder = ReportHeader.builder(); + reportHeaderBuilder.add("alpha"); + reportHeaderBuilder.add("beta"); + ReportHeader reportHeader = reportHeaderBuilder.build(); + + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + + for (int i = 0; i < 10; i++) { + ReportItem.Builder reportItemBuilder = ReportItem.builder(); + reportItemBuilder.setReportHeader(reportHeader); + reportItemBuilder.setReportLabel(reportLabel); + reportItemBuilder.addValue(i); + reportItemBuilder.addValue("value " + i); + ReportItem reportItem = reportItemBuilder.build(); + c.releaseOutput(reportItem); + } + })); + + Dimension dimension1 = FunctionalDimension.builder()// + .addMetaDatum("xxx")// + .addLevel((c) -> { + List result = new ArrayList<>(); + result.add("a"); + return result; + }).addLevel((c) -> { + List result = new ArrayList<>(); + result.add("b"); + return result; + }).build(); + + Dimension dimension2 = FunctionalDimension.builder()// + .addMetaDatum("xyz").addLevel((c) -> { + List result = new ArrayList<>(); + result.add("x"); + return result; + }).addLevel((c) -> { + List result = new ArrayList<>(); + result.add("y"); + return result; + }).addLevel((c) -> { + List result = new ArrayList<>(); + result.add("z"); + return result; + }).build(); + + NIOReportItemHandler nioReportItemHandler = // + NIOReportItemHandler.builder()// + .addReport(reportLabel, subPath.resolve("report1.csv"))// + .addExperimentReport(subPath.resolve("experiment_report.csv")) + .setDelimiter(",").build(); + + TestPluginData testPluginData = pluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + ExperimentStatusConsole experimentStatusConsole = ExperimentStatusConsole.builder().build(); + + Experiment .builder()// + .addPlugin(testPlugin)// + .addDimension(dimension1)// + .addDimension(dimension2)// + .addExperimentContextConsumer(nioReportItemHandler)// + .addExperimentContextConsumer(experimentStatusConsole)// + .build()// + .execute(); + } + + private void printExpected(Integer testNum) { + + StringBuilder sb = new StringBuilder(); + + switch (testNum) { + case 1: + sb.append("This test is meant to prove that when we run a simulation, we can generate a basic report with experiment columns." + "\n"); + sb.append("Expected Observations: " + "\n"); + sb.append("\t" + "After all 6 scenarios are completed, the console should show 1" + "\n"); + sb.append("\t" + "value. You should observe a SUCCEEDED value of 6." + "\n"); + sb.append("\t" + "A folder named 'test1' should appear in the specified directory." + "\n"); + sb.append("\t" + "A file named 'report1.txt' should be in the 'test1' folder." + "\n"); + sb.append("\t" + "The file's data should be comma separated." + "\n"); + sb.append("\t" + "The header of the text file should have the following columns: " + "\n"); + sb.append("\t" + "\t" + "scenario" + "\n"); + sb.append("\t" + "\t" + "xxx" + "\n"); + sb.append("\t" + "\t" + "xyz" + "\n"); + sb.append("\t" + "\t" + "alpha" + "\n"); + sb.append("\t" + "\t" + "beta" + "\n"); + sb.append("------------------------------ CONSOLE OUTPUT ------------------------------" + "\n"); + break; + case 2: + sb.append("This test is meant to prove that when we run a simulation, we can generate a basic report without experiment columns." + "\n"); + sb.append("Expected Observations: " + "\n"); + sb.append("\t" + "After all 6 scenarios are completed, the console should show 1" + "\n"); + sb.append("\t" + "value. You should observe a SUCCEEDED value of 6." + "\n"); + sb.append("\t" + "A folder named 'test2' should appear in the specified directory." + "\n"); + sb.append("\t" + "A file named 'report1.txt' should be in the 'test2' folder." + "\n"); + sb.append("\t" + "The file's data should be tab separated." + "\n"); + sb.append("\t" + "The header of the text file should have the following columns: " + "\n"); + sb.append("\t" + "\t" + "scenario" + "\n"); + sb.append("\t" + "\t" + "alpha" + "\n"); + sb.append("\t" + "\t" + "beta" + "\n"); + sb.append("------------------------------ CONSOLE OUTPUT ------------------------------" + "\n"); + break; + case 3: + sb.append("This test is meant to prove that when we run a simulation, we can generate a basic report as well as a progress log." + "\n"); + sb.append("Expected observations: " + "\n"); + sb.append("\t" + "After all 6 scenarios are completed, the console should show 1" + "\n"); + sb.append("\t" + "value. You should observe a SUCCEEDED value of 6." + "\n"); + sb.append("\t" + "A folder named 'test3' should appear in the specified directory." + "\n"); + sb.append("\t" + "A file named 'report1.txt' should be in the 'test3' folder." + "\n"); + sb.append("\t" + "The file's data should be tab separated." + "\n"); + sb.append("\t" + "The header of the text file should have the following columns.: " + "\n"); + sb.append("\t" + "\t" + "scenario" + "\n"); + sb.append("\t" + "\t" + "xxx" + "\n"); + sb.append("\t" + "\t" + "xyz" + "\n"); + sb.append("\t" + "\t" + "alpha" + "\n"); + sb.append("\t" + "\t" + "beta" + "\n"); + sb.append("\t" + "Another file named 'progresslog.txt' should be in the folder." + "\n"); + sb.append("\t" + "The header of the progress log file should have the following columns: " + "\n"); + sb.append("\t" + "\t" + "scenario" + "\n"); + sb.append("\t" + "\t" + "xxx" + "\n"); + sb.append("\t" + "\t" + "xyz" + "\n"); + sb.append("------------------------------ CONSOLE OUTPUT ------------------------------" + "\n"); + break; + case 4: + sb.append("This test is meant to prove that when a simulation run is interrupted, we can complete the simulation using the progress log." + "\n"); + sb.append("Expected observations: " + "\n"); + sb.append("\t" + "After all 6 scenarios are completed, the console should show 2" + "\n"); + sb.append("\t" + "values. You should observe PREVIOUSLY_SUCCEEDED and SUCCEEDED values" + "\n"); + sb.append("\t" + "whose sum should total up to 6." + "\n"); + sb.append("\t" + "A folder named 'test4' should appear in the specified directory." + "\n"); + sb.append("\t" + "A file named 'report1.txt' should be in the 'test4' folder." + "\n"); + sb.append("\t" + "The file's data should be tab separated." + "\n"); + sb.append("\t" + "The header of the text file should have the following columns: " + "\n"); + sb.append("\t" + "\t" + "scenario" + "\n"); + sb.append("\t" + "\t" + "xxx" + "\n"); + sb.append("\t" + "\t" + "xyz" + "\n"); + sb.append("\t" + "\t" + "alpha" + "\n"); + sb.append("\t" + "\t" + "beta" + "\n"); + sb.append("\t" + "Another file named 'progresslog.txt' should be in the folder." + "\n"); + sb.append("\t" + "The header of the progress log file should have the following columns: " + "\n"); + sb.append("\t" + "\t" + "scenario" + "\n"); + sb.append("\t" + "\t" + "xxx" + "\n"); + sb.append("\t" + "\t" + "xyz" + "\n"); + sb.append("------------------------------ CONSOLE OUTPUT ------------------------------" + "\n"); + break; + case 5: + sb.append("This test is meant to prove that when attempting to complete a simulation using a non existing progress log, " + "\n"); + sb.append("that the proper contract exception is thrown." + "\n"); + sb.append("Expected observations: " + "\n"); + sb.append("\t" + "After running test 5, you should receive an exception with the following message: " + "\n"); + sb.append("\t" + "Exception in thread \"main\" util.errors.ContractException: The scenario progress file does not exist," + "\n"); + sb.append("\t" + "but is required when continuation from progress file is chosen" + "\n"); + sb.append("------------------------------ CONSOLE OUTPUT ------------------------------" + "\n"); + break; + case 6: + sb.append("This test is meant to prove that when we can run a simulation multiple times without encountering an exception." + "\n"); + sb.append("Expected Observations: " + "\n"); + sb.append("\t" + "After the first 6 scenarios are completed, the console should show 1" + "\n"); + sb.append("\t" + "value. You should observe a SUCCEEDED value of 6." + "\n"); + sb.append("\t" + "The 6 scenarios should run a second time and the same SUCCEEDED value should appear." + "\n"); + sb.append("\t" + "A folder named 'test6' should appear in the specified directory." + "\n"); + sb.append("\t" + "A file named 'report1.txt' should be in the 'test6' folder." + "\n"); + sb.append("\t" + "The file's data should be comma separated." + "\n"); + sb.append("\t" + "The header of the text file should have the following columns: " + "\n"); + sb.append("\t" + "\t" + "scenario" + "\n"); + sb.append("\t" + "\t" + "xxx" + "\n"); + sb.append("\t" + "\t" + "xyz" + "\n"); + sb.append("\t" + "\t" + "alpha" + "\n"); + sb.append("\t" + "\t" + "beta" + "\n"); + sb.append("------------------------------ CONSOLE OUTPUT ------------------------------" + "\n"); + break; + case 7: + sb.append("This test is meant to prove that when we run a simulation, we can generate an experiment report " + "\n"); + sb.append(" alongside our basic reports." + "\n"); + sb.append("Expected Observations: " + "\n"); + sb.append("\t" + "After all 6 scenarios are completed, the console should show 1" + "\n"); + sb.append("\t" + "value. You should observe a SUCCEEDED value of 6." + "\n"); + sb.append("\t" + "A folder named 'test7' should appear in the specified directory." + "\n"); + sb.append("\t" + "A file named 'report1.csv' should be in the 'test1' folder." + "\n"); + sb.append("\t" + "The file's data should be comma separated." + "\n"); + sb.append("\t" + "The header of the text file should have the following columns: " + "\n"); + sb.append("\t" + "\t" + "scenario" + "\n"); + sb.append("\t" + "\t" + "xxx" + "\n"); + sb.append("\t" + "\t" + "xyz" + "\n"); + sb.append("\t" + "\t" + "alpha" + "\n"); + sb.append("\t" + "\t" + "beta" + "\n"); + sb.append("\t" + "Another file named 'experiment_report' should also appear in the folder."); + sb.append("\t" + "The file's data should be comma separated." + "\n"); + sb.append("\t" + "The header of the text file should have the following columns: " + "\n"); + sb.append("\t" + "\t" + "scenario" + "\n"); + sb.append("\t" + "\t" + "max_family_size" + "\n"); + sb.append("------------------------------ CONSOLE OUTPUT ------------------------------" + "\n"); + break; + } + + System.out.println(sb); + } + +} \ No newline at end of file diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/AT_ResourcesPlugin.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/AT_ResourcesPlugin.java new file mode 100644 index 000000000..cca21150c --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/AT_ResourcesPlugin.java @@ -0,0 +1,117 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePluginId; +import gov.hhs.aspr.ms.gcm.plugins.regions.RegionsPluginId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.resources.datamanagers.ResourcesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.resources.reports.PersonResourceReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.resources.reports.ResourcePropertyReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.resources.reports.ResourceReportPluginData; +import util.annotations.UnitTestMethod; + +public class AT_ResourcesPlugin { + + @Test + @UnitTestMethod(target = ResourcesPlugin.Builder.class, name = "getResourcesPlugin", args = { }) + public void testGetResourcesPlugin() { + + ResourcesPluginData resourcesPluginData = ResourcesPluginData.builder().build(); + Plugin resourcesPlugin = ResourcesPlugin.builder().setResourcesPluginData(resourcesPluginData).getResourcesPlugin(); + + assertEquals(1, resourcesPlugin.getPluginDatas().size()); + assertTrue(resourcesPlugin.getPluginDatas().contains(resourcesPluginData)); + + assertEquals(ResourcesPluginId.PLUGIN_ID, resourcesPlugin.getPluginId()); + + Set expectedDependencies = new LinkedHashSet<>(); + expectedDependencies.add(PeoplePluginId.PLUGIN_ID); + expectedDependencies.add(RegionsPluginId.PLUGIN_ID); + + assertEquals(expectedDependencies, resourcesPlugin.getPluginDependencies()); + + } + + @Test + @UnitTestMethod(target = ResourcesPlugin.class, name = "builder", args= {}) + public void testBuilder() { + assertNotNull(ResourcesPlugin.builder()); + } + + @Test + @UnitTestMethod(target = ResourcesPlugin.Builder.class, name = "setPersonResourceReportPluginData", args = {PersonResourceReportPluginData.class}) + public void testSetPersonResourceReportPluginData() { + ResourcesPluginData resourcesPluginData = ResourcesPluginData.builder().build(); + + PersonResourceReportPluginData personResourceReportPluginData = PersonResourceReportPluginData.builder()// + .setReportLabel(new SimpleReportLabel("PersonResourceReport"))// + .setReportPeriod(ReportPeriod.DAILY)// + .build();// + + Plugin resourcePlugin = ResourcesPlugin.builder()// + .setPersonResourceReportPluginData(personResourceReportPluginData)// + .setResourcesPluginData(resourcesPluginData)// + .getResourcesPlugin();// + + assertTrue(resourcePlugin.getPluginDatas().contains(personResourceReportPluginData)); + } + + @Test + @UnitTestMethod(target = ResourcesPlugin.Builder.class, name = "setResourcePropertyReportPluginData", args = {ResourcePropertyReportPluginData.class}) + public void testSetResourcePropertyReportPluginData() { + ResourcesPluginData resourcesPluginData = ResourcesPluginData.builder().build(); + + ResourcePropertyReportPluginData resourcePropertyReportPluginData = ResourcePropertyReportPluginData.builder()// + .setReportLabel(new SimpleReportLabel("ResourcePropertyReport"))// + .build();// + + Plugin resourcePlugin = ResourcesPlugin.builder()// + .setResourcePropertyReportPluginData(resourcePropertyReportPluginData)// + .setResourcesPluginData(resourcesPluginData)// + .getResourcesPlugin();// + + assertTrue(resourcePlugin.getPluginDatas().contains(resourcePropertyReportPluginData)); + } + + @Test + @UnitTestMethod(target = ResourcesPlugin.Builder.class, name = "setResourceReportPluginData", args = {ResourceReportPluginData.class}) + public void testSetResourceReportPluginData() { + ResourcesPluginData resourcesPluginData = ResourcesPluginData.builder().build(); + + ResourceReportPluginData resourceReportPluginData = ResourceReportPluginData.builder()// + .setReportLabel(new SimpleReportLabel("ResourceReport"))// + .setReportPeriod(ReportPeriod.DAILY)// + .build();// + + Plugin resourcePlugin = ResourcesPlugin.builder()// + .setResourceReportPluginData(resourceReportPluginData)// + .setResourcesPluginData(resourcesPluginData)// + .getResourcesPlugin();// + + assertTrue(resourcePlugin.getPluginDatas().contains(resourceReportPluginData)); + } + + @Test + @UnitTestMethod(target = ResourcesPlugin.Builder.class, name = "setResourcesPluginData", args = {ResourcesPluginData.class}) + public void testSetResourcesPluginData() { + ResourcesPluginData resourcesPluginData = ResourcesPluginData.builder().build(); + + Plugin resourcePlugin = ResourcesPlugin.builder()// + .setResourcesPluginData(resourcesPluginData)// + .getResourcesPlugin();// + + assertTrue(resourcePlugin.getPluginDatas().contains(resourcesPluginData)); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/AT_ResourcesPluginId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/AT_ResourcesPluginId.java new file mode 100644 index 000000000..b93a482bd --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/AT_ResourcesPluginId.java @@ -0,0 +1,16 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestField; + +public class AT_ResourcesPluginId { + + @Test + @UnitTestField(target = ResourcesPluginId.class, name = "PLUGIN_ID") + public void testPluginId() { + assertNotNull(ResourcesPluginId.PLUGIN_ID); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/datamanagers/AT_ResourcesDataManager.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/datamanagers/AT_ResourcesDataManager.java new file mode 100644 index 000000000..c3efb6f3e --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/datamanagers/AT_ResourcesDataManager.java @@ -0,0 +1,4499 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.datamanagers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Random; +import java.util.Set; +import java.util.function.Consumer; + +import org.apache.commons.math3.random.RandomGenerator; +import org.apache.commons.math3.util.FastMath; +import org.apache.commons.math3.util.Pair; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; +import gov.hhs.aspr.ms.gcm.nucleus.EventFilter; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.Simulation; +import gov.hhs.aspr.ms.gcm.nucleus.SimulationState; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.runcontinuityplugin.RunContinuityPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.runcontinuityplugin.RunContinuityPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestOutputConsumer; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePlugin; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeoplePluginData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonConstructionData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonRange; +import gov.hhs.aspr.ms.gcm.plugins.regions.RegionsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionConstructionData; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionError; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.TestRegionId; +import gov.hhs.aspr.ms.gcm.plugins.resources.ResourcesPlugin; +import gov.hhs.aspr.ms.gcm.plugins.resources.events.PersonResourceUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.resources.events.RegionResourceUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.resources.events.ResourceIdAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.resources.events.ResourcePropertyDefinitionEvent; +import gov.hhs.aspr.ms.gcm.plugins.resources.events.ResourcePropertyUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceError; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceInitialization; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourcePropertyId; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourcePropertyInitialization; +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.ResourcesTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.ResourcesTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.TestResourceId; +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.TestResourcePropertyId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; +import util.wrappers.MultiKey; +import util.wrappers.MutableDouble; +import util.wrappers.MutableInteger; +import util.wrappers.MutableObject; + +public final class AT_ResourcesDataManager { + + /** + * Demonstrates that the data manager produces plugin data that reflects its + * final state + */ + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testStateFinalization() { + + // show that the plugin data persists after multiple actions + List expectedRegionIds = new ArrayList<>(); + + ResourcesPluginData resourcesPluginData2 = ResourcesPluginData.builder() + .defineResourceProperty(TestResourceId.RESOURCE_1, + TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE, + TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE.getPropertyDefinition()) + .defineResourceProperty(TestResourceId.RESOURCE_2, + TestResourcePropertyId.ResourceProperty_1_1_BOOLEAN_MUTABLE, + TestResourcePropertyId.ResourceProperty_1_1_BOOLEAN_MUTABLE.getPropertyDefinition()) + .addResource(TestResourceId.RESOURCE_1, 0.0, false)// + .addResource(TestResourceId.RESOURCE_2, 0.0, true)// + .setResourcePropertyValue(TestResourceId.RESOURCE_1, + TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE, 45)// + .build(); + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + + resourcesDataManager.addResourceToRegion(TestResourceId.RESOURCE_1, TestRegionId.REGION_1, 55); + + RegionId personRegion = regionsDataManager.getPersonRegion(new PersonId(0)); + expectedRegionIds.add(personRegion); + resourcesDataManager.addResourceToRegion(TestResourceId.RESOURCE_2, personRegion, 33); + resourcesDataManager.transferResourceToPersonFromRegion(TestResourceId.RESOURCE_2, new PersonId(0), 30); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + + resourcesDataManager.addResourceId(TestResourceId.RESOURCE_3, false); + resourcesDataManager.addResourceToRegion(TestResourceId.RESOURCE_3, TestRegionId.REGION_2, 73); + resourcesDataManager.transferResourceFromPersonToRegion(TestResourceId.RESOURCE_2, new PersonId(0), 10); + resourcesDataManager.transferResourceBetweenRegions(TestResourceId.RESOURCE_2, TestRegionId.REGION_1, + TestRegionId.REGION_2, 5); + resourcesDataManager.expandCapacity(5); + + })); + + TestPluginData testPluginData2 = pluginBuilder.build(); + Factory factory2 = ResourcesTestPluginFactory.factory(2, 7939130943360648501L, testPluginData2)// + .setResourcesPluginData(resourcesPluginData2); + TestOutputConsumer testOutputConsumer2 = TestSimulation.builder()// + .addPlugins(factory2.getPlugins())// + .setProduceSimulationStateOnHalt(true)// + .setSimulationHaltTime(2)// + .build()// + .execute(); + Map outputItems2 = testOutputConsumer2 + .getOutputItemMap(ResourcesPluginData.class); + assertEquals(1, outputItems2.size()); + ResourcesPluginData actualPluginData = outputItems2.keySet().iterator().next(); + ResourcesPluginData expectedPluginData = ResourcesPluginData.builder() + .defineResourceProperty(TestResourceId.RESOURCE_1, + TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE, + TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE.getPropertyDefinition()) + .defineResourceProperty(TestResourceId.RESOURCE_2, + TestResourcePropertyId.ResourceProperty_1_1_BOOLEAN_MUTABLE, + TestResourcePropertyId.ResourceProperty_1_1_BOOLEAN_MUTABLE.getPropertyDefinition()) + .addResource(TestResourceId.RESOURCE_1, 0.0, false)// + .addResource(TestResourceId.RESOURCE_2, 0.0, true)// + .addResource(TestResourceId.RESOURCE_3, 1.0, false)// + .setResourcePropertyValue(TestResourceId.RESOURCE_1, + TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE, 45) + .setRegionResourceLevel(TestRegionId.REGION_1, TestResourceId.RESOURCE_1, 55) + .setRegionResourceLevel(expectedRegionIds.get(0), TestResourceId.RESOURCE_2, 8) + .setRegionResourceLevel(TestRegionId.REGION_2, TestResourceId.RESOURCE_2, 5) + .setRegionResourceLevel(TestRegionId.REGION_2, TestResourceId.RESOURCE_3, 73) + .setPersonResourceLevel(new PersonId(0), TestResourceId.RESOURCE_2, 20L)// + .setPersonResourceTime(new PersonId(0), TestResourceId.RESOURCE_2, 1.0).build(); + + assertEquals(expectedPluginData, actualPluginData); + } + + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testPersonRemovalEvent() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // Have an actor add a person with resources and then remove that person + + MutableObject mutablePersonId = new MutableObject<>(); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + + // create a person and set their resources + PersonConstructionData personConstructionData = PersonConstructionData.builder().add(TestRegionId.REGION_1) + .build(); + PersonId personId = peopleDataManager.addPerson(personConstructionData); + mutablePersonId.setValue(personId); + + for (TestResourceId testResourceId : TestResourceId.values()) { + resourcesDataManager.addResourceToRegion(testResourceId, TestRegionId.REGION_1, 100); + resourcesDataManager.transferResourceToPersonFromRegion(testResourceId, personId, 1); + } + + peopleDataManager.removePerson(personId); + + // show that the person still exists + assertTrue(peopleDataManager.personExists(personId)); + })); + + // Have the actor show that the person does not exist and there are no + // resources for that person. This is done at the same time as the + // person removal, but due to ordering will executeSimulation after the + // person is + // fully eliminated + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + PersonId personId = mutablePersonId.getValue(); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + + assertFalse(peopleDataManager.personExists(personId)); + + for (TestResourceId testResourceId : TestResourceId.values()) { + assertThrows(ContractException.class, + () -> resourcesDataManager.getPersonResourceLevel(testResourceId, personId)); + } + + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = ResourcesTestPluginFactory.factory(0, 5231820238498733928L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "getPeopleWithoutResource", args = { ResourceId.class }) + public void testGetPeopleWithoutResource() { + + Factory factory = ResourcesTestPluginFactory.factory(100, 3641510187112920884L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + Set expectedPeople = new LinkedHashSet<>(); + // give about half of the people the resource + for (PersonId personId : peopleDataManager.getPeople()) { + long resourceLevel = resourcesDataManager.getPersonResourceLevel(TestResourceId.RESOURCE_5, personId); + if (randomGenerator.nextBoolean() || resourceLevel > 0) { + RegionId regionId = regionsDataManager.getPersonRegion(personId); + resourcesDataManager.addResourceToRegion(TestResourceId.RESOURCE_5, regionId, 5); + resourcesDataManager.transferResourceToPersonFromRegion(TestResourceId.RESOURCE_5, personId, 5); + } else { + expectedPeople.add(personId); + } + } + // show that those who did not get the resource are returned + List actualPeople = resourcesDataManager.getPeopleWithoutResource(TestResourceId.RESOURCE_5); + assertEquals(expectedPeople.size(), actualPeople.size()); + assertEquals(expectedPeople, new LinkedHashSet<>(actualPeople)); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the resource id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(100, 3473450607674582992L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.getPeopleWithoutResource(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the resource id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(100, 1143781261828756924L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.getPeopleWithoutResource(TestResourceId.getUnknownResourceId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestConstructor(target = ResourcesDataManager.class, args = { ResourcesPluginData.class }) + public void testConstructor() { + ContractException contractException = assertThrows(ContractException.class, + () -> new ResourcesDataManager(null)); + assertEquals(ResourceError.NULL_RESOURCE_PLUGIN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "expandCapacity", args = { int.class }) + public void testExpandCapacity() { + Factory factory = ResourcesTestPluginFactory.factory(100, 9107703044214388523L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + ContractException contractException = assertThrows(ContractException.class, + () -> resourcesDataManager.expandCapacity(-1)); + assertEquals(PersonError.NEGATIVE_GROWTH_PROJECTION, contractException.getErrorType()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "getPeopleWithResource", args = { ResourceId.class }) + public void testGetPeopleWithResource() { + + Factory factory = ResourcesTestPluginFactory.factory(100, 1030108367649001208L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + Set expectedPeople = new LinkedHashSet<>(); + // give about half of the people the resource + for (PersonId personId : peopleDataManager.getPeople()) { + long resourceLevel = resourcesDataManager.getPersonResourceLevel(TestResourceId.RESOURCE_5, personId); + if (randomGenerator.nextBoolean() || resourceLevel > 0) { + RegionId regionId = regionsDataManager.getPersonRegion(personId); + resourcesDataManager.addResourceToRegion(TestResourceId.RESOURCE_5, regionId, 5); + resourcesDataManager.transferResourceToPersonFromRegion(TestResourceId.RESOURCE_5, personId, 5); + expectedPeople.add(personId); + } + } + // show that those who did not get the resource are returned + List actualPeople = resourcesDataManager.getPeopleWithResource(TestResourceId.RESOURCE_5); + assertEquals(expectedPeople.size(), actualPeople.size()); + assertEquals(expectedPeople, new LinkedHashSet<>(actualPeople)); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the resource id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(100, 319392144027980087L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.getPeopleWithoutResource(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the resource id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(100, 8576038889544967878L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.getPeopleWithoutResource(TestResourceId.getUnknownResourceId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "getPersonResourceLevel", args = { ResourceId.class, + PersonId.class }) + public void testGetPersonResourceLevel() { + + Factory factory = ResourcesTestPluginFactory.factory(20, 110987310555566746L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + + List people = peopleDataManager.getPeople(); + + // give random amounts of resource to random people + for (int i = 0; i < 1000; i++) { + PersonId personId = people.get(randomGenerator.nextInt(people.size())); + ResourceId resourceId = TestResourceId.getRandomResourceId(randomGenerator); + int amount = randomGenerator.nextInt(5); + long expectedLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); + expectedLevel += amount; + + RegionId regionId = regionsDataManager.getPersonRegion(personId); + resourcesDataManager.addResourceToRegion(resourceId, regionId, amount); + resourcesDataManager.transferResourceToPersonFromRegion(resourceId, personId, amount); + long actualLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); + assertEquals(expectedLevel, actualLevel); + } + + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the resource id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(20, 5173387308794126450L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.getPersonResourceLevel(null, new PersonId(0)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the resource id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(20, 5756572221517144312L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.getPersonResourceLevel(TestResourceId.getUnknownResourceId(), new PersonId(0)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the person id null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(20, 1392115005391991861L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.getPersonResourceLevel(TestResourceId.RESOURCE_1, null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "getPersonResourceTime", args = { ResourceId.class, + PersonId.class }) + public void testGetPersonResourceTime() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + Map expectedTimes = new LinkedHashMap<>(); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + // establish data views + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + + // establish the people and resources + Set resourceIds = resourcesDataManager.getResourceIds(); + List people = peopleDataManager.getPeople(); + + // initialize the expected times + for (PersonId personId : people) { + for (ResourceId resourceId : resourceIds) { + expectedTimes.put(new MultiKey(personId, resourceId), new MutableDouble()); + } + } + + // show that there are at least two resources that are being time + // tracked + int trackedResourceCount = 0; + for (ResourceId resourceId : resourceIds) { + boolean personResourceTimeTrackingPolicy = resourcesDataManager + .getPersonResourceTimeTrackingPolicy(resourceId); + if (personResourceTimeTrackingPolicy) { + trackedResourceCount++; + } + } + assertTrue(trackedResourceCount > 1); + + // give random amounts of resource to random people + for (int i = 0; i < people.size(); i++) { + PersonId personId = people.get(randomGenerator.nextInt(people.size())); + ResourceId resourceId = TestResourceId.getRandomResourceId(randomGenerator); + int amount = randomGenerator.nextInt(5) + 1; + + RegionId regionId = regionsDataManager.getPersonRegion(personId); + resourcesDataManager.addResourceToRegion(resourceId, regionId, amount); + resourcesDataManager.transferResourceToPersonFromRegion(resourceId, personId, amount); + + expectedTimes.get(new MultiKey(personId, resourceId)).setValue(c.getTime()); + } + + })); + + // make more resource updates at time 1 + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + // establish data views + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + + // establish the people and resources + List people = peopleDataManager.getPeople(); + + // give random amounts of resource to random people + for (int i = 0; i < people.size(); i++) { + PersonId personId = people.get(randomGenerator.nextInt(people.size())); + ResourceId resourceId = TestResourceId.getRandomResourceId(randomGenerator); + int amount = randomGenerator.nextInt(5) + 1; + + RegionId regionId = regionsDataManager.getPersonRegion(personId); + resourcesDataManager.addResourceToRegion(resourceId, regionId, amount); + resourcesDataManager.transferResourceToPersonFromRegion(resourceId, personId, amount); + + expectedTimes.get(new MultiKey(personId, resourceId)).setValue(c.getTime()); + } + + })); + + // make more resource updates at time 2 + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + // establish data views + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + + // establish the people and resources + List people = peopleDataManager.getPeople(); + + // give random amounts of resource to random people + + for (int i = 0; i < people.size(); i++) { + PersonId personId = people.get(randomGenerator.nextInt(people.size())); + ResourceId resourceId = TestResourceId.getRandomResourceId(randomGenerator); + int amount = randomGenerator.nextInt(5) + 1; + RegionId regionId = regionsDataManager.getPersonRegion(personId); + resourcesDataManager.addResourceToRegion(resourceId, regionId, amount); + resourcesDataManager.transferResourceToPersonFromRegion(resourceId, personId, amount); + expectedTimes.get(new MultiKey(personId, resourceId)).setValue(c.getTime()); + } + + })); + + // test the person resource times + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(3, (c) -> { + // establish data views + + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + + // show that the person resource times match expectations + int actualAssertionsCount = 0; + for (MultiKey multiKey : expectedTimes.keySet()) { + PersonId personId = multiKey.getKey(0); + ResourceId resourceId = multiKey.getKey(1); + boolean trackTimes = resourcesDataManager.getPersonResourceTimeTrackingPolicy(resourceId); + if (trackTimes) { + double expectedTime = expectedTimes.get(multiKey).getValue(); + double actualTime = resourcesDataManager.getPersonResourceTime(resourceId, personId); + assertEquals(expectedTime, actualTime); + actualAssertionsCount++; + } + } + /* + * Show that the number of time values that were tested is equal to the size of + * the population times the number of time-tracked resources + */ + + int trackedResourceCount = 0; + for (ResourceId resourceId : resourcesDataManager.getResourceIds()) { + boolean trackTimes = resourcesDataManager.getPersonResourceTimeTrackingPolicy(resourceId); + if (trackTimes) { + trackedResourceCount++; + } + } + + int expectedAssertionsCount = peopleDataManager.getPopulationCount() * trackedResourceCount; + assertEquals(expectedAssertionsCount, actualAssertionsCount); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = ResourcesTestPluginFactory.factory(30, 3274189520478045515L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + /* + * precondition test: if the assignment times for the resource are not tracked + */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(30, 4631279382559646912L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.getPersonResourceTime(TestResourceId.RESOURCE_2, new PersonId(0)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.RESOURCE_ASSIGNMENT_TIME_NOT_TRACKED, contractException.getErrorType()); + + /* precondition test: if the resource id is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(30, 2409228447197751995L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.getPersonResourceTime(null, new PersonId(0)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the resource id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(30, 6640524810334992305L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.getPersonResourceTime(TestResourceId.getUnknownResourceId(), new PersonId(0)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the person id null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(30, 6775179388362303664L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.getPersonResourceTime(TestResourceId.RESOURCE_1, null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "getPersonResourceTimeTrackingPolicy", args = { + ResourceId.class }) + public void testGetPersonResourceTimeTrackingPolicy() { + + Factory factory = ResourcesTestPluginFactory.factory(5, 757175164544632409L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + for (TestResourceId testResourceId : TestResourceId.values()) { + boolean actualPolicy = resourcesDataManager.getPersonResourceTimeTrackingPolicy(testResourceId); + boolean expectedPolicy = testResourceId.getTimeTrackingPolicy(); + assertEquals(expectedPolicy, actualPolicy); + } + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the resource id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(5, 1761534115327431429L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.getPersonResourceTimeTrackingPolicy(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the resource id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(5, 7202590650313787556L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.getPersonResourceTimeTrackingPolicy(TestResourceId.getUnknownResourceId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "getRegionResourceLevel", args = { RegionId.class, + ResourceId.class }) + public void testGetRegionResourceLevel() { + + Factory factory = ResourcesTestPluginFactory.factory(20, 6606932435911201728L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + + List regionIds = new ArrayList<>(regionsDataManager.getRegionIds()); + + // give random amounts of resource to random regions + for (int i = 0; i < 1000; i++) { + RegionId regionId = regionIds.get(randomGenerator.nextInt(regionIds.size())); + ResourceId resourceId = TestResourceId.getRandomResourceId(randomGenerator); + int amount = randomGenerator.nextInt(5); + long expectedLevel = resourcesDataManager.getRegionResourceLevel(regionId, resourceId); + expectedLevel += amount; + resourcesDataManager.addResourceToRegion(resourceId, regionId, amount); + long actualLevel = resourcesDataManager.getRegionResourceLevel(regionId, resourceId); + assertEquals(expectedLevel, actualLevel); + } + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the resource id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(20, 1436454351032688103L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.getRegionResourceLevel(TestRegionId.REGION_1, null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the resource id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(20, 7954290176104108412L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.getRegionResourceLevel(TestRegionId.REGION_1, + TestResourceId.getUnknownResourceId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the region id null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(20, 936653403265146113L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.getRegionResourceLevel(null, TestResourceId.RESOURCE_1); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); + + /* precondition test: if the region id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(20, 8256630838791330328L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.getRegionResourceLevel(TestRegionId.getUnknownRegionId(), + TestResourceId.RESOURCE_1); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "getResourceIds", args = {}) + public void testGetResourceIds() { + + Factory factory = ResourcesTestPluginFactory.factory(5, 2601236547109660988L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + + // show that the resource ids are the test resource ids + Set expectedResourceIds = new LinkedHashSet<>(); + for (TestResourceId testResourceId : TestResourceId.values()) { + expectedResourceIds.add(testResourceId); + } + assertEquals(expectedResourceIds, resourcesDataManager.getResourceIds()); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "getResourcePropertyDefinition", args = { + ResourceId.class, ResourcePropertyId.class }) + public void testGetResourcePropertyDefinition() { + + Factory factory = ResourcesTestPluginFactory.factory(5, 7619546908709928867L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + // show that each of the resource property definitions from the test + // resource property enum are present + for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId.values()) { + PropertyDefinition expectedPropertyDefinition = testResourcePropertyId.getPropertyDefinition(); + PropertyDefinition actualPropertyDefinition = resourcesDataManager.getResourcePropertyDefinition( + testResourcePropertyId.getTestResourceId(), testResourcePropertyId); + assertEquals(expectedPropertyDefinition, actualPropertyDefinition); + } + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "getResourcePropertyIds", args = { ResourceId.class }) + public void testGetResourcePropertyIds() { + + Factory factory = ResourcesTestPluginFactory.factory(5, 1203402714876510055L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + // show that the resource property ids are the test resource + // property ids + for (TestResourceId testResourceId : TestResourceId.values()) { + Set expectedPropertyIds = TestResourcePropertyId + .getTestResourcePropertyIds(testResourceId); + Set actualPropertyIds = resourcesDataManager.getResourcePropertyIds(testResourceId); + assertEquals(expectedPropertyIds, actualPropertyIds); + } + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition tests if the resource id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(5, 3551512082879672269L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.getResourcePropertyIds(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + /* precondition tests if the resource id unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(5, 7372199991315732905L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.getResourcePropertyIds(TestResourceId.getUnknownResourceId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "getResourcePropertyValue", args = { ResourceId.class, + ResourcePropertyId.class }) + public void testGetResourcePropertyValue() { + + Factory factory = ResourcesTestPluginFactory.factory(10, 8757871520559824784L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + // establish the expected values of all resource properties + Map expectedValues = new LinkedHashMap<>(); + for (TestResourceId testResourceId : TestResourceId.values()) { + for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId + .getTestResourcePropertyIds(testResourceId)) { + Object propertyValue = resourcesDataManager.getResourcePropertyValue(testResourceId, + testResourcePropertyId); + expectedValues.put(new MultiKey(testResourceId, testResourcePropertyId), propertyValue); + } + } + + // make a few random resource property updates + int updateCount = 0; + for (int i = 0; i < 1000; i++) { + TestResourceId testResourceId = TestResourceId.getRandomResourceId(randomGenerator); + TestResourcePropertyId testResourcePropertyId = TestResourcePropertyId + .getRandomResourcePropertyId(testResourceId, randomGenerator); + PropertyDefinition resourcePropertyDefinition = resourcesDataManager + .getResourcePropertyDefinition(testResourceId, testResourcePropertyId); + if (resourcePropertyDefinition.propertyValuesAreMutable()) { + Object propertyValue = testResourcePropertyId.getRandomPropertyValue(randomGenerator); + resourcesDataManager.setResourcePropertyValue(testResourceId, testResourcePropertyId, + propertyValue); + expectedValues.put(new MultiKey(testResourceId, testResourcePropertyId), propertyValue); + updateCount++; + } + } + + /* + * Show that the number of updates was reasonable - some of the properties are + * not mutable so it will be <1000 + */ + assertTrue(updateCount > 500); + + // show that the values of the resource properties are correct + for (MultiKey multiKey : expectedValues.keySet()) { + TestResourceId testResourceId = multiKey.getKey(0); + TestResourcePropertyId testResourcePropertyId = multiKey.getKey(1); + Object expectedValue = expectedValues.get(multiKey); + Object actualValue = resourcesDataManager.getResourcePropertyValue(testResourceId, + testResourcePropertyId); + assertEquals(expectedValue, actualValue); + } + + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the resource id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(10, 5856579804289926491L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.getResourcePropertyValue(null, + TestResourcePropertyId.ResourceProperty_1_1_BOOLEAN_MUTABLE); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the resource id unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(10, 1735955680485266104L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.getResourcePropertyValue(TestResourceId.getUnknownResourceId(), + TestResourcePropertyId.ResourceProperty_1_1_BOOLEAN_MUTABLE); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the resource property id is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(10, 5544999164968796966L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.getResourcePropertyValue(TestResourceId.RESOURCE_1, null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + /* precondition test: if the resource property id unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(10, 3394498124288646142L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.getResourcePropertyValue(TestResourceId.RESOURCE_1, + TestResourcePropertyId.ResourceProperty_2_1_BOOLEAN_MUTABLE); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + /* precondition test: if the resource property id unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(10, 2505584646755789288L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.getResourcePropertyValue(TestResourceId.RESOURCE_1, + TestResourcePropertyId.getUnknownResourcePropertyId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "resourceIdExists", args = { ResourceId.class }) + public void testResourceIdExists() { + + Factory factory = ResourcesTestPluginFactory.factory(5, 4964974931601945506L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + + // show that the resource ids that exist are the test resource ids + + for (TestResourceId testResourceId : TestResourceId.values()) { + assertTrue(resourcesDataManager.resourceIdExists(testResourceId)); + } + assertFalse(resourcesDataManager.resourceIdExists(TestResourceId.getUnknownResourceId())); + assertFalse(resourcesDataManager.resourceIdExists(null)); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "resourcePropertyIdExists", args = { ResourceId.class, + ResourcePropertyId.class }) + public void testResourcePropertyIdExists() { + + Factory factory = ResourcesTestPluginFactory.factory(5, 8074706630609416041L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + + // show that the resource property ids that exist are the test + // resource property ids + + for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId.values()) { + assertTrue(resourcesDataManager.resourcePropertyIdExists(testResourcePropertyId.getTestResourceId(), + testResourcePropertyId)); + } + + assertFalse(resourcesDataManager.resourcePropertyIdExists(TestResourceId.RESOURCE_1, + TestResourcePropertyId.ResourceProperty_2_1_BOOLEAN_MUTABLE)); + assertFalse(resourcesDataManager.resourcePropertyIdExists(null, + TestResourcePropertyId.ResourceProperty_2_1_BOOLEAN_MUTABLE)); + assertFalse(resourcesDataManager.resourcePropertyIdExists(TestResourceId.RESOURCE_1, null)); + assertFalse(resourcesDataManager.resourcePropertyIdExists(TestResourceId.RESOURCE_1, + TestResourcePropertyId.getUnknownResourcePropertyId())); + assertFalse(resourcesDataManager.resourcePropertyIdExists(TestResourceId.getUnknownResourceId(), + TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE)); + assertFalse(resourcesDataManager.resourcePropertyIdExists(TestResourceId.getUnknownResourceId(), + TestResourcePropertyId.getUnknownResourcePropertyId())); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "defineResourceProperty", args = { + ResourcePropertyInitialization.class }) + + public void testDefineResourceProperty() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // have an actor observe the ResourcePropertyAdditionEvent events + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + c.subscribe(EventFilter.builder(ResourcePropertyDefinitionEvent.class).build(), (c2, e) -> { + MultiKey multiKey = new MultiKey(c2.getTime(), e.resourceId(), e.resourcePropertyId(), + e.resourcePropertyValue()); + actualObservations.add(multiKey); + }); + })); + + // have an actor define a new resource property + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + ResourcePropertyId newResourcePropertyId = TestResourcePropertyId.getUnknownResourcePropertyId(); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Double.class) + .setDefaultValue(34.6).build(); + ResourcePropertyInitialization resourcePropertyInitialization = // + ResourcePropertyInitialization.builder()// + .setPropertyDefinition(propertyDefinition)// + .setResourceId(TestResourceId.RESOURCE_1)// + .setResourcePropertyId(newResourcePropertyId)// + .build(); + resourcesDataManager.defineResourceProperty(resourcePropertyInitialization); + assertTrue(resourcesDataManager.resourcePropertyIdExists(TestResourceId.RESOURCE_1, newResourcePropertyId)); + PropertyDefinition actualDefinition = resourcesDataManager + .getResourcePropertyDefinition(TestResourceId.RESOURCE_1, newResourcePropertyId); + assertEquals(propertyDefinition, actualDefinition); + MultiKey multiKey = new MultiKey(c.getTime(), TestResourceId.RESOURCE_1, newResourcePropertyId, + propertyDefinition.getDefaultValue().get()); + expectedObservations.add(multiKey); + })); + + // have an actor define a new resource property + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + ResourcePropertyId newResourcePropertyId = TestResourcePropertyId.getUnknownResourcePropertyId(); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(String.class) + .setDefaultValue("default").build(); + + ResourcePropertyInitialization resourcePropertyInitialization = // + ResourcePropertyInitialization.builder()// + .setPropertyDefinition(propertyDefinition)// + .setResourceId(TestResourceId.RESOURCE_2)// + .setResourcePropertyId(newResourcePropertyId)// + .build(); + resourcesDataManager.defineResourceProperty(resourcePropertyInitialization); + + assertTrue(resourcesDataManager.resourcePropertyIdExists(TestResourceId.RESOURCE_2, newResourcePropertyId)); + PropertyDefinition actualDefinition = resourcesDataManager + .getResourcePropertyDefinition(TestResourceId.RESOURCE_2, newResourcePropertyId); + assertEquals(propertyDefinition, actualDefinition); + MultiKey multiKey = new MultiKey(c.getTime(), TestResourceId.RESOURCE_2, newResourcePropertyId, + propertyDefinition.getDefaultValue().get()); + expectedObservations.add(multiKey); + })); + + // have the observer verify the observations were correct + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(3, (c) -> { + assertEquals(2, expectedObservations.size()); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = ResourcesTestPluginFactory.factory(5, 4535415202634885293L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the resource id is unknown */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(5, 6361316703720629700L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class) + .setDefaultValue(1).build(); + ResourcePropertyInitialization resourcePropertyInitialization = // + ResourcePropertyInitialization.builder()// + .setPropertyDefinition(propertyDefinition)// + .setResourceId(TestResourceId.getUnknownResourceId())// + .setResourcePropertyId(TestResourcePropertyId.getUnknownResourcePropertyId())// + .build(); + resourcesDataManager.defineResourceProperty(resourcePropertyInitialization); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the resource property is already defined */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(5, 3114198987897928160L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class) + .setDefaultValue(1).build(); + ResourcePropertyInitialization resourcePropertyInitialization = // + ResourcePropertyInitialization.builder()// + .setPropertyDefinition(propertyDefinition)// + .setResourceId(TestResourceId.RESOURCE_1)// + .setResourcePropertyId(TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE)// + .build(); + resourcesDataManager.defineResourceProperty(resourcePropertyInitialization); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.DUPLICATE_PROPERTY_DEFINITION, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "addResourceId", args = { ResourceId.class, + boolean.class }) + public void testAddResourceId() { + + ResourceId newResourceId1 = TestResourceId.getUnknownResourceId(); + ResourceId newResourceId2 = TestResourceId.getUnknownResourceId(); + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + c.subscribe(EventFilter.builder(ResourceIdAdditionEvent.class).build(), (c2, e) -> { + MultiKey multiKey = new MultiKey(c2.getTime(), e.resourceId(), e.timeTrackingPolicy()); + actualObservations.add(multiKey); + }); + + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + boolean timeTrackingPolicy = false; + assertFalse(resourcesDataManager.resourceIdExists(newResourceId1)); + resourcesDataManager.addResourceId(newResourceId1, timeTrackingPolicy); + assertTrue(resourcesDataManager.resourceIdExists(newResourceId1)); + MultiKey multiKey = new MultiKey(c.getTime(), newResourceId1, false); + expectedObservations.add(multiKey); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + boolean timeTrackingPolicy = true; + assertFalse(resourcesDataManager.resourceIdExists(newResourceId2)); + resourcesDataManager.addResourceId(newResourceId2, timeTrackingPolicy); + assertTrue(resourcesDataManager.resourceIdExists(newResourceId2)); + MultiKey multiKey = new MultiKey(c.getTime(), newResourceId2, true); + expectedObservations.add(multiKey); + })); + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(3, (c) -> { + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = ResourcesTestPluginFactory.factory(5, 3128266603988900429L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition test: if the resource id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(5, 3016555021220987436L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.addResourceId(null, false); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + // precondition test: if the resource type is already present + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(5, 9097839209339012193L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.addResourceId(TestResourceId.RESOURCE_1, false); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.DUPLICATE_RESOURCE_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "setResourcePropertyValue", args = { ResourceId.class, + ResourcePropertyId.class, Object.class }) + public void testSetResourcePropertyValue() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // Have an actor observe the resource property changes + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + for (TestResourceId testResourceId : TestResourceId.values()) { + Set testResourcePropertyIds = TestResourcePropertyId + .getTestResourcePropertyIds(testResourceId); + for (TestResourcePropertyId testResourcePropertyId : testResourcePropertyIds) { + EventFilter eventFilter = resourcesDataManager + .getEventFilterForResourcePropertyUpdateEvent(testResourceId, testResourcePropertyId); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(e.resourceId(), e.resourcePropertyId(), + e.previousPropertyValue(), e.currentPropertyValue())); + }); + } + } + })); + + // Have an actor assign resource properties + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + + for (TestResourceId testResourceId : TestResourceId.values()) { + Set testResourcePropertyIds = TestResourcePropertyId + .getTestResourcePropertyIds(testResourceId); + for (TestResourcePropertyId testResourcePropertyId : testResourcePropertyIds) { + PropertyDefinition propertyDefinition = resourcesDataManager + .getResourcePropertyDefinition(testResourceId, testResourcePropertyId); + if (propertyDefinition.propertyValuesAreMutable()) { + // update the property value + Object resourcePropertyValue = resourcesDataManager.getResourcePropertyValue(testResourceId, + testResourcePropertyId); + Object expectedValue = testResourcePropertyId.getRandomPropertyValue(randomGenerator); + resourcesDataManager.setResourcePropertyValue(testResourceId, testResourcePropertyId, + expectedValue); + // show that the property value was changed + Object actualValue = resourcesDataManager.getResourcePropertyValue(testResourceId, + testResourcePropertyId); + assertEquals(expectedValue, actualValue); + + expectedObservations.add(new MultiKey(testResourceId, testResourcePropertyId, + resourcePropertyValue, expectedValue)); + } + } + } + + })); + + // Have the observer show the the observations were properly generated + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = ResourcesTestPluginFactory.factory(0, 8240654442453940072L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the resource id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 8603231391482244436L, (c) -> { + ResourcePropertyId resourcePropertyId = TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE; + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + Object value = 10; + resourcesDataManager.setResourcePropertyValue(null, resourcePropertyId, value); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the resource id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 4345368701918830681L, (c) -> { + ResourcePropertyId resourcePropertyId = TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE; + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + Object value = 10; + resourcesDataManager.setResourcePropertyValue(TestResourceId.getUnknownResourceId(), resourcePropertyId, + value); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the resource property id is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 697099694521127247L, (c) -> { + ResourceId resourceId = TestResourceId.RESOURCE_1; + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + Object value = 10; + resourcesDataManager.setResourcePropertyValue(resourceId, null, value); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + /* precondition test: if the resource property id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 5208483875882077960L, (c) -> { + ResourceId resourceId = TestResourceId.RESOURCE_1; + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + Object value = 10; + resourcesDataManager.setResourcePropertyValue(resourceId, + TestResourcePropertyId.getUnknownResourcePropertyId(), value); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + /* precondition test: if the resource property value is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 1862818482356534123L, (c) -> { + ResourceId resourceId = TestResourceId.RESOURCE_1; + ResourcePropertyId resourcePropertyId = TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE; + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.setResourcePropertyValue(resourceId, resourcePropertyId, null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_VALUE, contractException.getErrorType()); + + /* + * precondition test: if the resource property value is incompatible with the + * corresponding property definition + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 8731358919842250070L, (c) -> { + ResourceId resourceId = TestResourceId.RESOURCE_1; + ResourcePropertyId resourcePropertyId = TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE; + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.setResourcePropertyValue(resourceId, resourcePropertyId, 23.4); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); + + /* precondition test: if the property has been defined as immutable */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 2773568485593496806L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + Object value = 10; + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_5, + TestResourcePropertyId.ResourceProperty_5_1_INTEGER_IMMUTABLE, value); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.IMMUTABLE_VALUE, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "removeResourceFromPerson", args = { ResourceId.class, + PersonId.class, long.class }) + public void testPersonResourceRemovalEvent() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // Have and actor give resources to people + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + + List people = peopleDataManager.getPeople(); + // add resources to all the people + for (PersonId personId : people) { + for (TestResourceId testResourceId : TestResourceId.values()) { + RegionId regionId = regionsDataManager.getPersonRegion(personId); + resourcesDataManager.addResourceToRegion(testResourceId, regionId, 100L); + resourcesDataManager.transferResourceToPersonFromRegion(testResourceId, personId, 100L); + } + } + })); + + // have an actor observe the changes to person resources + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(1, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + for (TestResourceId testResourceId : TestResourceId.values()) { + EventFilter eventFilter = resourcesDataManager + .getEventFilterForPersonResourceUpdateEvent(testResourceId); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(e.personId(), e.resourceId(), e.previousResourceLevel(), + e.currentResourceLevel())); + }); + } + })); + + // Have the actor remove resources from people + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + + List people = peopleDataManager.getPeople(); + + // remove random amounts of resources from people + int transfercount = 0; + for (int i = 0; i < 40; i++) { + // select a random person and resource + PersonId personId = people.get(randomGenerator.nextInt(people.size())); + TestResourceId resourceId = TestResourceId.getRandomResourceId(randomGenerator); + + long personResourceLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); + // ensure that the person has a positive amount of the resource + if (personResourceLevel > 0) { + + // select an amount to remove + long amount = randomGenerator.nextInt((int) personResourceLevel) + 1; + resourcesDataManager.removeResourceFromPerson(resourceId, personId, amount); + transfercount++; + + // show that the amount was transferred + long expectedPersonResourceLevel = personResourceLevel - amount; + long actualPersonResourceLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); + assertEquals(expectedPersonResourceLevel, actualPersonResourceLevel); + + expectedObservations + .add(new MultiKey(personId, resourceId, personResourceLevel, expectedPersonResourceLevel)); + + } + + } + + // show that enough transfers occurred to make a valid test + assertTrue(transfercount > 10); + + })); + + // Have the observer show that the generated observations were correct + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(3, (c) -> { + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = ResourcesTestPluginFactory.factory(50, 6476360369877622233L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the person id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(50, 368123167921446410L, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + ResourceId resourceId = TestResourceId.RESOURCE_1; + PersonId personId = new PersonId(0); + long amount = 10; + // add resource to the person to ensure the precondition tests + // will + // work + RegionId regionId = regionsDataManager.getPersonRegion(personId); + resourcesDataManager.addResourceToRegion(resourceId, regionId, 100L); + resourcesDataManager.transferResourceToPersonFromRegion(resourceId, personId, 100L); + resourcesDataManager.removeResourceFromPerson(resourceId, null, amount); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + /* precondition test: if the person does not exist */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(50, 463919801005664846L, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + ResourceId resourceId = TestResourceId.RESOURCE_1; + PersonId personId = new PersonId(0); + long amount = 10; + // add resource to the person to ensure the precondition tests + // will + // work + RegionId regionId = regionsDataManager.getPersonRegion(personId); + resourcesDataManager.addResourceToRegion(resourceId, regionId, 100L); + resourcesDataManager.transferResourceToPersonFromRegion(resourceId, personId, 100L); + resourcesDataManager.removeResourceFromPerson(resourceId, new PersonId(1000), amount); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + /* precondition test: if the resource id is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(50, 5201087860428100698L, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + ResourceId resourceId = TestResourceId.RESOURCE_1; + PersonId personId = new PersonId(0); + long amount = 10; + // add resource to the person to ensure the precondition tests + // will + // work + RegionId regionId = regionsDataManager.getPersonRegion(personId); + resourcesDataManager.addResourceToRegion(resourceId, regionId, 100L); + resourcesDataManager.transferResourceToPersonFromRegion(resourceId, personId, 100L); + resourcesDataManager.removeResourceFromPerson(null, personId, amount); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the resource id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(50, 805801782412801541L, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + ResourceId resourceId = TestResourceId.RESOURCE_1; + PersonId personId = new PersonId(0); + long amount = 10; + // add resource to the person to ensure the precondition tests + // will + // work + RegionId regionId = regionsDataManager.getPersonRegion(personId); + resourcesDataManager.addResourceToRegion(resourceId, regionId, 100L); + resourcesDataManager.transferResourceToPersonFromRegion(resourceId, personId, 100L); + resourcesDataManager.removeResourceFromPerson(TestResourceId.getUnknownResourceId(), personId, amount); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the amount is negative */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(50, 6748548509217290999L, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + ResourceId resourceId = TestResourceId.RESOURCE_1; + PersonId personId = new PersonId(0); + // add resource to the person to ensure the precondition tests + // will + // work + RegionId regionId = regionsDataManager.getPersonRegion(personId); + resourcesDataManager.addResourceToRegion(resourceId, regionId, 100L); + resourcesDataManager.transferResourceToPersonFromRegion(resourceId, personId, 100L); + resourcesDataManager.removeResourceFromPerson(resourceId, personId, -1); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.NEGATIVE_RESOURCE_AMOUNT, contractException.getErrorType()); + + /* + * precondition test: if the person does not have the required amount of the + * resource + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(50, 6668079690803354725L, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + ResourceId resourceId = TestResourceId.RESOURCE_1; + PersonId personId = new PersonId(0); + // add resource to the person to ensure the precondition tests + // will + // work + RegionId regionId = regionsDataManager.getPersonRegion(personId); + resourcesDataManager.addResourceToRegion(resourceId, regionId, 100L); + resourcesDataManager.transferResourceToPersonFromRegion(resourceId, personId, 100L); + resourcesDataManager.removeResourceFromPerson(resourceId, personId, 10000); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.INSUFFICIENT_RESOURCES_AVAILABLE, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "removeResourceFromRegion", args = { ResourceId.class, + RegionId.class, long.class }) + public void testRegionResourceRemovalEvent() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // Have the actor add resources to the regions + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + // add resources to the regions + for (TestRegionId testRegionId : TestRegionId.values()) { + for (TestResourceId testResourceId : TestResourceId.values()) { + resourcesDataManager.addResourceToRegion(testResourceId, testRegionId, 100L); + } + } + + })); + + // Have an actor observe the resource being removed from regions + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(1, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + for (TestRegionId testRegionId : TestRegionId.values()) { + for (TestResourceId testResourceId : TestResourceId.values()) { + EventFilter eventFilter = resourcesDataManager + .getEventFilterForRegionResourceUpdateEvent(testResourceId, testRegionId); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(e.regionId(), e.resourceId(), e.previousResourceLevel(), + e.currentResourceLevel())); + }); + } + } + + })); + + // Have the actor remove resources from regions + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + + // remove random amounts of resources from regions + int transfercount = 0; + for (int i = 0; i < 40; i++) { + // select random regions and resources + TestRegionId regionId = TestRegionId.getRandomRegionId(randomGenerator); + TestResourceId resourceId = TestResourceId.getRandomResourceId(randomGenerator); + long regionLevel = resourcesDataManager.getRegionResourceLevel(regionId, resourceId); + + if (regionLevel > 0) { + // select an amount to add + long amount = randomGenerator.nextInt((int) regionLevel) + 1; + resourcesDataManager.removeResourceFromRegion(resourceId, regionId, amount); + transfercount++; + + // show that the amount was added + long expectedRegionLevel = regionLevel - amount; + long actualRegionLevel = resourcesDataManager.getRegionResourceLevel(regionId, resourceId); + assertEquals(expectedRegionLevel, actualRegionLevel); + + expectedObservations.add(new MultiKey(regionId, resourceId, regionLevel, expectedRegionLevel)); + } + } + + // show that enough removals occurred to make a valid test + assertTrue(transfercount > 10); + + })); + + // Have the observer show that the observations were correctly generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(3, (c) -> { + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = ResourcesTestPluginFactory.factory(0, 3784957617927969790L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the region id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 5886805948424471010L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + ResourceId resourceId = TestResourceId.RESOURCE_1; + long amount = 10; + resourcesDataManager.removeResourceFromRegion(resourceId, null, amount); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); + + /* precondition test: if the region id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 1916159097321882678L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + ResourceId resourceId = TestResourceId.RESOURCE_1; + long amount = 10; + resourcesDataManager.removeResourceFromRegion(resourceId, TestRegionId.getUnknownRegionId(), amount); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); + + /* precondition test: if the resource id is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 6766634049148364532L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + RegionId regionId = TestRegionId.REGION_1; + long amount = 10; + resourcesDataManager.removeResourceFromRegion(null, regionId, amount); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the resource id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 3589045787461097821L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + RegionId regionId = TestRegionId.REGION_1; + long amount = 10; + resourcesDataManager.removeResourceFromRegion(TestResourceId.getUnknownResourceId(), regionId, amount); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the amount is negative */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 4784578124305542584L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + ResourceId resourceId = TestResourceId.RESOURCE_1; + RegionId regionId = TestRegionId.REGION_1; + resourcesDataManager.removeResourceFromRegion(resourceId, regionId, -1L); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.NEGATIVE_RESOURCE_AMOUNT, contractException.getErrorType()); + + /* + * precondition test: if the region does not have the required amount of the + * resource + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 4875324598998641428L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + ResourceId resourceId = TestResourceId.RESOURCE_1; + RegionId regionId = TestRegionId.REGION_1; + resourcesDataManager.removeResourceFromRegion(resourceId, regionId, 10000000L); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.INSUFFICIENT_RESOURCES_AVAILABLE, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "transferResourceBetweenRegions", args = { + ResourceId.class, RegionId.class, RegionId.class, long.class }) + public void testTransferResourceBetweenRegions() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + Set actualObservations = new LinkedHashSet<>(); + Set expectedObservations = new LinkedHashSet<>(); + + // create an actor to observe resource transfers + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + for (TestRegionId testRegionId : TestRegionId.values()) { + for (TestResourceId testResourceId : TestResourceId.values()) { + EventFilter eventFilter = resourcesDataManager + .getEventFilterForRegionResourceUpdateEvent(testResourceId, testRegionId); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(e.regionId(), e.resourceId(), e.previousResourceLevel(), + e.currentResourceLevel())); + }); + } + } + })); + + // create an actor that will transfer resources between regions + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + + // add resources to all the regions + for (TestRegionId testRegionId : TestRegionId.values()) { + for (TestResourceId testResourceId : TestResourceId.values()) { + long resourceLevel = resourcesDataManager.getRegionResourceLevel(testRegionId, testResourceId); + resourcesDataManager.addResourceToRegion(testResourceId, testRegionId, 100L); + expectedObservations + .add(new MultiKey(testRegionId, testResourceId, resourceLevel, 100L + resourceLevel)); + } + } + + // transfer random amounts of resources between regions + int transfercount = 0; + for (int i = 0; i < 40; i++) { + // select random regions and resource + TestRegionId regionId1 = TestRegionId.getRandomRegionId(randomGenerator); + TestRegionId regionId2 = TestRegionId.getRandomRegionId(randomGenerator); + TestResourceId resourceId = TestResourceId.getRandomResourceId(randomGenerator); + // ensure the regions are different + if (!regionId1.equals(regionId2)) { + long region1Level = resourcesDataManager.getRegionResourceLevel(regionId1, resourceId); + // ensure that the first region has a positive amount of the + // resource + if (region1Level > 0) { + // establish the current level of the second region + long region2Level = resourcesDataManager.getRegionResourceLevel(regionId2, resourceId); + // select an amount to transfer + long amount = randomGenerator.nextInt((int) region1Level) + 1; + resourcesDataManager.transferResourceBetweenRegions(resourceId, regionId1, regionId2, amount); + transfercount++; + + // show that the amount was transferred + long expectedRegion1Level = region1Level - amount; + long expectedRegion2Level = region2Level + amount; + + long actualRegion1Level = resourcesDataManager.getRegionResourceLevel(regionId1, resourceId); + long actualRegion2Level = resourcesDataManager.getRegionResourceLevel(regionId2, resourceId); + assertEquals(expectedRegion1Level, actualRegion1Level); + assertEquals(expectedRegion2Level, actualRegion2Level); + + expectedObservations + .add(new MultiKey(regionId1, resourceId, region1Level, expectedRegion1Level)); + expectedObservations + .add(new MultiKey(regionId2, resourceId, region2Level, expectedRegion2Level)); + + } + } + } + + // show that enough transfers occurred to make a valid test + assertTrue(transfercount > 10); + + })); + + // have the observer show that the correct observations were made + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(2, (c) -> { + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = ResourcesTestPluginFactory.factory(0, 7976375269741360076L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the source region is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 2545276913032843668L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + ResourceId resourceId = TestResourceId.RESOURCE_1; + RegionId regionId2 = TestRegionId.REGION_2; + long amount = 10; + + // add resources to all the regions to ensure the precondition + // tests + // will work + for (TestRegionId testRegionId : TestRegionId.values()) { + for (TestResourceId testResourceId : TestResourceId.values()) { + resourcesDataManager.addResourceToRegion(testResourceId, testRegionId, 100L); + } + } + resourcesDataManager.transferResourceBetweenRegions(resourceId, null, regionId2, amount); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); + + /* precondition test: if the source region is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 1182536948902380826L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + ResourceId resourceId = TestResourceId.RESOURCE_1; + RegionId regionId2 = TestRegionId.REGION_2; + long amount = 10; + // add resources to all the regions to ensure the precondition + // tests + // will work + for (TestRegionId testRegionId : TestRegionId.values()) { + for (TestResourceId testResourceId : TestResourceId.values()) { + resourcesDataManager.addResourceToRegion(testResourceId, testRegionId, 100L); + } + } + resourcesDataManager.transferResourceBetweenRegions(resourceId, TestRegionId.getUnknownRegionId(), + regionId2, amount); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); + + /* precondition test: if the destination region is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 3358578155263941L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + ResourceId resourceId = TestResourceId.RESOURCE_1; + RegionId regionId1 = TestRegionId.REGION_1; + long amount = 10; + // add resources to all the regions to ensure the precondition + // tests + // will work + for (TestRegionId testRegionId : TestRegionId.values()) { + for (TestResourceId testResourceId : TestResourceId.values()) { + resourcesDataManager.addResourceToRegion(testResourceId, testRegionId, 100L); + } + } + resourcesDataManager.transferResourceBetweenRegions(resourceId, regionId1, null, amount); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); + + /* precondition test: if the destination region is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 289436879730670757L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + ResourceId resourceId = TestResourceId.RESOURCE_1; + RegionId regionId1 = TestRegionId.REGION_1; + long amount = 10; + // add resources to all the regions to ensure the precondition + // tests + // will work + for (TestRegionId testRegionId : TestRegionId.values()) { + for (TestResourceId testResourceId : TestResourceId.values()) { + resourcesDataManager.addResourceToRegion(testResourceId, testRegionId, 100L); + } + } + resourcesDataManager.transferResourceBetweenRegions(resourceId, regionId1, + TestRegionId.getUnknownRegionId(), amount); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); + + /* precondition test: if the resource id is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 3690172166437098600L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + RegionId regionId1 = TestRegionId.REGION_1; + RegionId regionId2 = TestRegionId.REGION_2; + long amount = 10; + // add resources to all the regions to ensure the precondition + // tests + // will work + for (TestRegionId testRegionId : TestRegionId.values()) { + for (TestResourceId testResourceId : TestResourceId.values()) { + resourcesDataManager.addResourceToRegion(testResourceId, testRegionId, 100L); + } + } + resourcesDataManager.transferResourceBetweenRegions(null, regionId1, regionId2, amount); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the resource id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 7636787584894783093L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + RegionId regionId1 = TestRegionId.REGION_1; + RegionId regionId2 = TestRegionId.REGION_2; + long amount = 10; + // add resources to all the regions to ensure the precondition + // tests + // will work + for (TestRegionId testRegionId : TestRegionId.values()) { + for (TestResourceId testResourceId : TestResourceId.values()) { + resourcesDataManager.addResourceToRegion(testResourceId, testRegionId, 100L); + } + } + resourcesDataManager.transferResourceBetweenRegions(TestResourceId.getUnknownResourceId(), regionId1, + regionId2, amount); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the resource amount is negative */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 1320571074133841280L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + ResourceId resourceId = TestResourceId.RESOURCE_1; + RegionId regionId1 = TestRegionId.REGION_1; + RegionId regionId2 = TestRegionId.REGION_2; + // add resources to all the regions to ensure the precondition + // tests + // will work + for (TestRegionId testRegionId : TestRegionId.values()) { + for (TestResourceId testResourceId : TestResourceId.values()) { + resourcesDataManager.addResourceToRegion(testResourceId, testRegionId, 100L); + } + } + resourcesDataManager.transferResourceBetweenRegions(resourceId, regionId1, regionId2, -1); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.NEGATIVE_RESOURCE_AMOUNT, contractException.getErrorType()); + + /* precondition test: if the source and destination region are equal */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 2402299633191289724L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + ResourceId resourceId = TestResourceId.RESOURCE_1; + RegionId regionId1 = TestRegionId.REGION_1; + long amount = 10; + // add resources to all the regions to ensure the precondition + // tests + // will work + for (TestRegionId testRegionId : TestRegionId.values()) { + for (TestResourceId testResourceId : TestResourceId.values()) { + resourcesDataManager.addResourceToRegion(testResourceId, testRegionId, 100L); + } + } + resourcesDataManager.transferResourceBetweenRegions(resourceId, regionId1, regionId1, amount); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.REFLEXIVE_RESOURCE_TRANSFER, contractException.getErrorType()); + + /* + * precondition test: if the source region does not have sufficient resources to + * support the transfer + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 9136536902267748610L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + ResourceId resourceId = TestResourceId.RESOURCE_1; + RegionId regionId1 = TestRegionId.REGION_1; + RegionId regionId2 = TestRegionId.REGION_2; + // add resources to all the regions to ensure the precondition + // tests + // will work + for (TestRegionId testRegionId : TestRegionId.values()) { + for (TestResourceId testResourceId : TestResourceId.values()) { + resourcesDataManager.addResourceToRegion(testResourceId, testRegionId, 100L); + } + } + resourcesDataManager.transferResourceBetweenRegions(resourceId, regionId1, regionId2, 100000L); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.INSUFFICIENT_RESOURCES_AVAILABLE, contractException.getErrorType()); + + /* + * precondition test: if the transfer will cause a numeric overflow in the + * destination region + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 342832088592207841L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + ResourceId resourceId = TestResourceId.RESOURCE_1; + RegionId regionId1 = TestRegionId.REGION_1; + RegionId regionId2 = TestRegionId.REGION_2; + long amount = 10; + // add resources to all the regions to ensure the precondition + // tests + // will work + for (TestRegionId testRegionId : TestRegionId.values()) { + for (TestResourceId testResourceId : TestResourceId.values()) { + resourcesDataManager.addResourceToRegion(testResourceId, testRegionId, 100L); + } + } + // fill region 2 to the max long value + long fillAmount = Long.MAX_VALUE - resourcesDataManager.getRegionResourceLevel(regionId2, resourceId); + resourcesDataManager.addResourceToRegion(resourceId, regionId2, fillAmount); + resourcesDataManager.transferResourceBetweenRegions(resourceId, regionId1, regionId2, amount); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.RESOURCE_ARITHMETIC_EXCEPTION, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "transferResourceFromPersonToRegion", args = { + ResourceId.class, PersonId.class, long.class }) + public void testResourceTransferFromPersonEvent() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // Have an actor give people resources + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + + List people = peopleDataManager.getPeople(); + + // add resources to all people + for (PersonId personId : people) { + RegionId regionId = regionsDataManager.getPersonRegion(personId); + for (TestResourceId testResourceId : TestResourceId.values()) { + resourcesDataManager.addResourceToRegion(testResourceId, regionId, 100L); + resourcesDataManager.transferResourceToPersonFromRegion(testResourceId, personId, 100L); + } + } + + })); + + // Have an actor observe the resource changes + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(1, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + for (TestRegionId testRegionId : TestRegionId.values()) { + for (TestResourceId testResourceId : TestResourceId.values()) { + EventFilter eventFilter = resourcesDataManager + .getEventFilterForRegionResourceUpdateEvent(testResourceId, testRegionId); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(e.regionId(), e.resourceId(), e.previousResourceLevel(), + e.currentResourceLevel())); + }); + } + } + for (TestResourceId testResourceId : TestResourceId.values()) { + EventFilter eventFilter = resourcesDataManager + .getEventFilterForPersonResourceUpdateEvent(testResourceId); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(e.personId(), e.resourceId(), e.previousResourceLevel(), + e.currentResourceLevel())); + }); + } + + })); + + // Have an actor return resources from people back to their regions + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + + List people = peopleDataManager.getPeople(); + + int transferCount = 0; + // transfer resources back to the regions from the people + for (int i = 0; i < 100; i++) { + PersonId personId = people.get(randomGenerator.nextInt(people.size())); + ResourceId resourceId = TestResourceId.getRandomResourceId(randomGenerator); + RegionId regionId = regionsDataManager.getPersonRegion(personId); + long personResourceLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); + long regionResourceLevel = resourcesDataManager.getRegionResourceLevel(regionId, resourceId); + + if (personResourceLevel > 0) { + long amount = randomGenerator.nextInt((int) personResourceLevel); + long expectedPersonResourceLevel = personResourceLevel - amount; + long expectedRegionResourceLevel = regionResourceLevel + amount; + resourcesDataManager.transferResourceFromPersonToRegion(resourceId, personId, amount); + transferCount++; + long actualPersonResourceLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); + long actualRegionResourceLevel = resourcesDataManager.getRegionResourceLevel(regionId, resourceId); + assertEquals(expectedPersonResourceLevel, actualPersonResourceLevel); + assertEquals(expectedRegionResourceLevel, actualRegionResourceLevel); + + expectedObservations + .add(new MultiKey(regionId, resourceId, regionResourceLevel, expectedRegionResourceLevel)); + expectedObservations + .add(new MultiKey(personId, resourceId, personResourceLevel, expectedPersonResourceLevel)); + + } + } + + // show that a reasonable number of transfers occurred + assertTrue(transferCount > 20); + + })); + + // Have the observer show that the observations were properly generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(3, (c) -> { + assertEquals(expectedObservations, actualObservations); + })); + + // Have an actor test preconditions + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(4, (c) -> { + + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + // precondition tests + PersonId personId = new PersonId(0); + ResourceId resourceId = TestResourceId.RESOURCE_4; + long amount = 10; + + // add resources to the person to support the precondition tests + + RegionId regionId = regionsDataManager.getPersonRegion(personId); + for (TestResourceId testResourceId : TestResourceId.values()) { + resourcesDataManager.addResourceToRegion(testResourceId, regionId, 100L); + resourcesDataManager.transferResourceToPersonFromRegion(testResourceId, personId, 100L); + } + + // if the person id is null + + ContractException contractException = assertThrows(ContractException.class, + () -> resourcesDataManager.transferResourceFromPersonToRegion(resourceId, null, amount)); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + // if the person does not exist + contractException = assertThrows(ContractException.class, () -> resourcesDataManager + .transferResourceFromPersonToRegion(resourceId, new PersonId(3434), amount)); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + // if the resource id is null + contractException = assertThrows(ContractException.class, + () -> resourcesDataManager.transferResourceFromPersonToRegion(null, personId, amount)); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + // if the resource id is unknown + contractException = assertThrows(ContractException.class, () -> resourcesDataManager + .transferResourceFromPersonToRegion(TestResourceId.getUnknownResourceId(), personId, amount)); + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + + // if the amount is negative + contractException = assertThrows(ContractException.class, + () -> resourcesDataManager.transferResourceFromPersonToRegion(resourceId, personId, -1L)); + assertEquals(ResourceError.NEGATIVE_RESOURCE_AMOUNT, contractException.getErrorType()); + + // if the person does not have the required amount of the resource + contractException = assertThrows(ContractException.class, + () -> resourcesDataManager.transferResourceFromPersonToRegion(resourceId, personId, 1000000)); + assertEquals(ResourceError.INSUFFICIENT_RESOURCES_AVAILABLE, contractException.getErrorType()); + + // if the transfer results in an overflow of the region's resource + // level + + // fill the region + long regionResourceLevel = resourcesDataManager.getRegionResourceLevel(regionId, resourceId); + resourcesDataManager.removeResourceFromRegion(resourceId, regionId, regionResourceLevel); + resourcesDataManager.addResourceToRegion(resourceId, regionId, Long.MAX_VALUE); + + // empty the person + long personResourceLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); + resourcesDataManager.removeResourceFromPerson(resourceId, personId, personResourceLevel); + + // transfer the region's inventory to the person + resourcesDataManager.transferResourceToPersonFromRegion(resourceId, personId, Long.MAX_VALUE); + + // add one more unit to the region + resourcesDataManager.addResourceToRegion(resourceId, regionId, 1L); + + // attempt to transfer the person's inventory back to the region + + contractException = assertThrows(ContractException.class, () -> resourcesDataManager + .transferResourceFromPersonToRegion(resourceId, personId, Long.MAX_VALUE)); + assertEquals(ResourceError.RESOURCE_ARITHMETIC_EXCEPTION, contractException.getErrorType()); + + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = ResourcesTestPluginFactory.factory(30, 3166011813977431605L, testPluginData); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "transferResourceToPersonFromRegion", args = { + ResourceId.class, PersonId.class, long.class }) + public void testResourceTransferToPersonEvent() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // Have an actor add resources to the regions + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + RegionsDataManager regionLocationDataManager = c.getDataManager(RegionsDataManager.class); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + + List people = peopleDataManager.getPeople(); + + // add resources to all regions + for (PersonId personId : people) { + RegionId regionId = regionLocationDataManager.getPersonRegion(personId); + for (TestResourceId testResourceId : TestResourceId.values()) { + resourcesDataManager.addResourceToRegion(testResourceId, regionId, 1000L); + } + } + + })); + + // Have an actor observe the resource transfers + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(1, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + + for (TestRegionId testRegionId : TestRegionId.values()) { + for (TestResourceId testResourceId : TestResourceId.values()) { + EventFilter eventFilter = resourcesDataManager + .getEventFilterForRegionResourceUpdateEvent(testResourceId, testRegionId); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(e.regionId(), e.resourceId(), e.previousResourceLevel(), + e.currentResourceLevel())); + }); + } + } + + for (TestResourceId testResourceId : TestResourceId.values()) { + EventFilter eventFilter = resourcesDataManager + .getEventFilterForPersonResourceUpdateEvent(testResourceId); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(e.personId(), e.resourceId(), e.previousResourceLevel(), + e.currentResourceLevel())); + + }); + } + + })); + + // Have an actor transfer the resources to people + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + RegionsDataManager regionLocationDataManager = c.getDataManager(RegionsDataManager.class); + + List people = peopleDataManager.getPeople(); + + int transferCount = 0; + // transfer resources back to the people + for (int i = 0; i < 100; i++) { + PersonId personId = people.get(randomGenerator.nextInt(people.size())); + ResourceId resourceId = TestResourceId.getRandomResourceId(randomGenerator); + RegionId regionId = regionLocationDataManager.getPersonRegion(personId); + long personResourceLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); + long regionResourceLevel = resourcesDataManager.getRegionResourceLevel(regionId, resourceId); + + if (regionResourceLevel > 0) { + long amount = randomGenerator.nextInt((int) regionResourceLevel); + long expectedPersonResourceLevel = personResourceLevel + amount; + long expectedRegionResourceLevel = regionResourceLevel - amount; + resourcesDataManager.transferResourceToPersonFromRegion(resourceId, personId, amount); + transferCount++; + long actualPersonResourceLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); + long actualRegionResourceLevel = resourcesDataManager.getRegionResourceLevel(regionId, resourceId); + assertEquals(expectedPersonResourceLevel, actualPersonResourceLevel); + assertEquals(expectedRegionResourceLevel, actualRegionResourceLevel); + + expectedObservations + .add(new MultiKey(regionId, resourceId, regionResourceLevel, expectedRegionResourceLevel)); + expectedObservations + .add(new MultiKey(personId, resourceId, personResourceLevel, expectedPersonResourceLevel)); + + } + } + + // show that a reasonable number of transfers occurred + assertTrue(transferCount > 20); + + })); + + // Have an actor show that the proper observations were generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(3, (c) -> { + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = ResourcesTestPluginFactory.factory(30, 3808042869854225459L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the person id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(30, 2628501738627419743L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + PersonId personId = new PersonId(0); + ResourceId resourceId = TestResourceId.RESOURCE_4; + long amount = 10; + // add resources to the region to support the precondition tests + RegionId regionId = regionsDataManager.getPersonRegion(personId); + for (TestResourceId testResourceId : TestResourceId.values()) { + resourcesDataManager.addResourceToRegion(testResourceId, regionId, 100L); + } + resourcesDataManager.transferResourceToPersonFromRegion(resourceId, null, amount); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + /* precondition test: if the person does not exist */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(30, 4172586983768511485L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + PersonId personId = new PersonId(0); + ResourceId resourceId = TestResourceId.RESOURCE_4; + long amount = 10; + // add resources to the region to support the precondition tests + RegionId regionId = regionsDataManager.getPersonRegion(personId); + for (TestResourceId testResourceId : TestResourceId.values()) { + resourcesDataManager.addResourceToRegion(testResourceId, regionId, 100L); + } + resourcesDataManager.transferResourceToPersonFromRegion(resourceId, new PersonId(3434), amount); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + /* precondition test: if the resource id is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(30, 6256935891787853979L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + PersonId personId = new PersonId(0); + long amount = 10; + // add resources to the region to support the precondition tests + RegionId regionId = regionsDataManager.getPersonRegion(personId); + for (TestResourceId testResourceId : TestResourceId.values()) { + resourcesDataManager.addResourceToRegion(testResourceId, regionId, 100L); + } + resourcesDataManager.transferResourceToPersonFromRegion(null, personId, amount); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the resource id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(30, 6949348067383487020L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + PersonId personId = new PersonId(0); + long amount = 10; + // add resources to the region to support the precondition tests + RegionId regionId = regionsDataManager.getPersonRegion(personId); + for (TestResourceId testResourceId : TestResourceId.values()) { + resourcesDataManager.addResourceToRegion(testResourceId, regionId, 100L); + } + resourcesDataManager.transferResourceToPersonFromRegion(TestResourceId.getUnknownResourceId(), personId, + amount); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the amount is negative */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(30, 6911979438110217773L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + PersonId personId = new PersonId(0); + ResourceId resourceId = TestResourceId.RESOURCE_4; + // add resources to the region to support the precondition tests + RegionId regionId = regionsDataManager.getPersonRegion(personId); + for (TestResourceId testResourceId : TestResourceId.values()) { + resourcesDataManager.addResourceToRegion(testResourceId, regionId, 100L); + } + resourcesDataManager.transferResourceToPersonFromRegion(resourceId, personId, -1L); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.NEGATIVE_RESOURCE_AMOUNT, contractException.getErrorType()); + + /* + * precondition test: if the region does not have the required amount of the + * resource + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(30, 1022333582572896703L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + PersonId personId = new PersonId(0); + ResourceId resourceId = TestResourceId.RESOURCE_4; + // add resources to the region to support the precondition tests + RegionId regionId = regionsDataManager.getPersonRegion(personId); + for (TestResourceId testResourceId : TestResourceId.values()) { + resourcesDataManager.addResourceToRegion(testResourceId, regionId, 100L); + } + resourcesDataManager.transferResourceToPersonFromRegion(resourceId, personId, 1000000); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.INSUFFICIENT_RESOURCES_AVAILABLE, contractException.getErrorType()); + + /* + * precondition test: if the transfer results in an overflow of the person's + * resource level + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(30, 1989550065510462161L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + PersonId personId = new PersonId(0); + ResourceId resourceId = TestResourceId.RESOURCE_4; + // add resources to the region to support the precondition tests + RegionId regionId = regionsDataManager.getPersonRegion(personId); + for (TestResourceId testResourceId : TestResourceId.values()) { + resourcesDataManager.addResourceToRegion(testResourceId, regionId, 100L); + } + // fill the region + long regionResourceLevel = resourcesDataManager.getRegionResourceLevel(regionId, resourceId); + resourcesDataManager.removeResourceFromRegion(resourceId, regionId, regionResourceLevel); + resourcesDataManager.addResourceToRegion(resourceId, regionId, Long.MAX_VALUE); + + // empty the person + long personResourceLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); + resourcesDataManager.removeResourceFromPerson(resourceId, personId, personResourceLevel); + + // transfer the region's inventory to the person + resourcesDataManager.transferResourceToPersonFromRegion(resourceId, personId, Long.MAX_VALUE); + + // add one more unit to the region + resourcesDataManager.addResourceToRegion(resourceId, regionId, 1L); + + // attempt to transfer the on unit to the person + + resourcesDataManager.transferResourceToPersonFromRegion(resourceId, personId, 1L); + }); + + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + + }); + assertEquals(ResourceError.RESOURCE_ARITHMETIC_EXCEPTION, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "addResourceToRegion", args = { ResourceId.class, + RegionId.class, long.class }) + public void testAddResourceToRegion() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // Have an actor to observe the resource changes + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + for (TestRegionId testRegionId : TestRegionId.values()) { + for (TestResourceId testResourceId : TestResourceId.values()) { + EventFilter eventFilter = resourcesDataManager + .getEventFilterForRegionResourceUpdateEvent(testResourceId, testRegionId); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(e.regionId(), e.resourceId(), e.previousResourceLevel(), + e.currentResourceLevel())); + }); + } + } + })); + + // Have an actor add resources to regions + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + + // add random amounts of resources to regions + int transfercount = 0; + for (int i = 0; i < 40; i++) { + // select random regions and resources + TestRegionId regionId = TestRegionId.getRandomRegionId(randomGenerator); + TestResourceId resourceId = TestResourceId.getRandomResourceId(randomGenerator); + long regionLevel = resourcesDataManager.getRegionResourceLevel(regionId, resourceId); + + // select an amount to add + long amount = randomGenerator.nextInt(100) + 1; + resourcesDataManager.addResourceToRegion(resourceId, regionId, amount); + transfercount++; + + // show that the amount was added + long expectedRegionLevel = regionLevel + amount; + long actualRegionLevel = resourcesDataManager.getRegionResourceLevel(regionId, resourceId); + assertEquals(expectedRegionLevel, actualRegionLevel); + + expectedObservations.add(new MultiKey(regionId, resourceId, regionLevel, expectedRegionLevel)); + + } + + // show that enough additions occurred to make a valid test + assertTrue(transfercount > 10); + + })); + + // have the observer show that the correct observations were generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(2, (c) -> { + assertEquals(expectedObservations, actualObservations); + + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = ResourcesTestPluginFactory.factory(0, 2273638431976256278L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the region id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 6097938300290796293L, (c) -> { + ResourceId resourceId = TestResourceId.RESOURCE_1; + long amount = 10; + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.addResourceToRegion(resourceId, null, amount); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); + + /* precondition test: if the region id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 1284607529543124944L, (c) -> { + ResourceId resourceId = TestResourceId.RESOURCE_1; + long amount = 10; + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.addResourceToRegion(resourceId, TestRegionId.getUnknownRegionId(), amount); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); + + /* precondition test: if the resource id is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 5929063621703486118L, (c) -> { + RegionId regionId = TestRegionId.REGION_1; + long amount = 10; + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.addResourceToRegion(null, regionId, amount); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the resource id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 1240045272882068003L, (c) -> { + RegionId regionId = TestRegionId.REGION_1; + long amount = 10; + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.addResourceToRegion(TestResourceId.getUnknownResourceId(), regionId, amount); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the amount is negative */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 2192023733930104434L, (c) -> { + ResourceId resourceId = TestResourceId.RESOURCE_1; + RegionId regionId = TestRegionId.REGION_1; + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.addResourceToRegion(resourceId, regionId, -1); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.NEGATIVE_RESOURCE_AMOUNT, contractException.getErrorType()); + + /* precondition test: if the addition results in an overflow */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 4518775448744653729L, (c) -> { + ResourceId resourceId = TestResourceId.RESOURCE_1; + RegionId regionId = TestRegionId.REGION_1; + long amount = 10; + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.addResourceToRegion(resourceId, regionId, amount); + resourcesDataManager.addResourceToRegion(resourceId, regionId, Long.MAX_VALUE); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.RESOURCE_ARITHMETIC_EXCEPTION, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testPersonAdditionEvent() { + + // Have an actor create a few people with random resource levels + Factory factory = ResourcesTestPluginFactory.factory(0, 5441878385875188805L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + // create 30 people, testing each in turn for their resource levels + for (int i = 0; i < 30; i++) { + PersonConstructionData.Builder builder = PersonConstructionData.builder(); + // give the person and region + builder.add(TestRegionId.getRandomRegionId(randomGenerator)); + + // give the person a positive resource level for about half of + // the resources + Map expectedResources = new LinkedHashMap<>(); + for (TestResourceId testResourceId : TestResourceId.values()) { + MutableInteger mutableInteger = new MutableInteger(); + expectedResources.put(testResourceId, mutableInteger); + if (randomGenerator.nextBoolean()) { + int amount = randomGenerator.nextInt(30) + 1; + mutableInteger.setValue(amount); + ResourceInitialization resourceInitialization = new ResourceInitialization(testResourceId, + (long) amount); + builder.add(resourceInitialization); + } + } + + // create the person which will in turn generate the + // PersonAdditionEvent + PersonId personId = peopleDataManager.addPerson(builder.build()); + + // show that the person has the correct resource levels + for (TestResourceId testResourceId : TestResourceId.values()) { + int actualPersonResourceLevel = (int) resourcesDataManager.getPersonResourceLevel(testResourceId, + personId); + int expectedPersonResourceLevel = expectedResources.get(testResourceId).getValue(); + assertEquals(expectedPersonResourceLevel, actualPersonResourceLevel); + } + } + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 3508334533286675130L, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + /* + * Precondition tests for the validity of the person id are shadowed by other + * plugins and cannot be easily tested + */ + + /* + * if the auxiliary data contains a ResourceInitialization that has a null + * resource id + */ + peopleDataManager.addPerson(PersonConstructionData.builder().add(TestRegionId.REGION_1) + .add(new ResourceInitialization(null, 15L)).build()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 7458875943724352968L, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + + /* + * Precondition tests for the validity of the person id are shadowed by other + * plugins and cannot be easily tested + */ + + /* + * if the auxiliary data contains a ResourceInitialization that has an unknown + * resource id + */ + + peopleDataManager.addPerson(PersonConstructionData.builder()// + .add(TestRegionId.REGION_2)// + .add(new ResourceInitialization(TestResourceId.getUnknownResourceId(), 15L))// + .build()); + + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 3702960689314847457L, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + + /* + * Precondition tests for the validity of the person id are shadowed by other + * plugins and cannot be easily tested + */ + + /* + * if the auxiliary data contains a ResourceInitialization that has a negative + * resource level + */ + + peopleDataManager.addPerson(PersonConstructionData.builder()// + .add(TestRegionId.REGION_3)// + .add(new ResourceInitialization(TestResourceId.RESOURCE_1, -15L))// + .build());// + + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.NEGATIVE_RESOURCE_AMOUNT, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testRegionAdditionEvent() { + + /* + * show that a newly added region will cause the resource data manager to return + * the expected levels from the event. + */ + + Factory factory = ResourcesTestPluginFactory.factory(0, 7471968091128250788L, (c) -> { + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + RegionId newRegionId = TestRegionId.getUnknownRegionId(); + + Map expectedValues = new LinkedHashMap<>(); + for (TestResourceId testResourceId : TestResourceId.values()) { + expectedValues.put(testResourceId, 0L); + } + expectedValues.put(TestResourceId.RESOURCE_1, 75L); + expectedValues.put(TestResourceId.RESOURCE_2, 432L); + + RegionConstructionData.Builder regionConstructionDataBuilder = // + RegionConstructionData.builder()// + .setRegionId(newRegionId); + + for (TestResourceId testResourceId : TestResourceId.values()) { + Long amount = expectedValues.get(testResourceId); + if (amount != 0L) { + regionConstructionDataBuilder.addValue(new ResourceInitialization(testResourceId, amount));// + } + } + + RegionConstructionData regionConstructionData = regionConstructionDataBuilder.build(); + regionsDataManager.addRegion(regionConstructionData); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + + for (TestResourceId testResourceId : TestResourceId.values()) { + long expectedResourceLevel = expectedValues.get(testResourceId); + long actualResourceLevel = resourcesDataManager.getRegionResourceLevel(newRegionId, testResourceId); + assertEquals(expectedResourceLevel, actualResourceLevel); + } + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* + * show that an unknown region will cause the resource data manager to throw an + * exception when retrieving a resource level for that region + */ + assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 4192802703078518338L, (c) -> { + RegionId newRegionId = TestRegionId.getUnknownRegionId(); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.getRegionResourceLevel(newRegionId, TestResourceId.RESOURCE_1); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + } + + /** + * Demonstrates that the data manager's initial state reflects its plugin data + */ + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testStateInitialization() { + + int initialPopulation = 10; + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1828556358289827784L); + + // create a list of people + List people = new ArrayList<>(); + for (int i = 0; i < initialPopulation; i++) { + people.add(new PersonId(i)); + } + + // add the resources plugin + ResourcesPluginData.Builder resourcesBuilder = ResourcesPluginData.builder(); + + for (int i = 0; i < initialPopulation; i++) { + PersonId personId = new PersonId(i); + for (TestResourceId testResourceId : TestResourceId.values()) { + resourcesBuilder.setPersonResourceLevel(personId, testResourceId, randomGenerator.nextInt(5)); + } + } + + for (TestRegionId testRegionId : TestRegionId.values()) { + for (TestResourceId testResourceId : TestResourceId.values()) { + resourcesBuilder.setRegionResourceLevel(testRegionId, testResourceId, randomGenerator.nextInt(5)); + } + } + + for (TestResourceId testResourceId : TestResourceId.values()) { + resourcesBuilder.addResource(testResourceId, 0.0, testResourceId.getTimeTrackingPolicy()); + } + + for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId.values()) { + TestResourceId testResourceId = testResourcePropertyId.getTestResourceId(); + PropertyDefinition propertyDefinition = testResourcePropertyId.getPropertyDefinition(); + Object propertyValue = testResourcePropertyId.getRandomPropertyValue(randomGenerator); + resourcesBuilder.defineResourceProperty(testResourceId, testResourcePropertyId, propertyDefinition); + resourcesBuilder.setResourcePropertyValue(testResourceId, testResourcePropertyId, propertyValue); + } + + ResourcesPluginData resourcesPluginData = resourcesBuilder.build(); + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + + List personIds = peopleDataManager.getPeople(); + + Set expectedRegionIds = regionsDataManager.getRegionIds(); + Set actualRegionIds = resourcesPluginData.getRegionIds(); + assertEquals(expectedRegionIds, actualRegionIds); + + for (RegionId regionId : resourcesPluginData.getRegionIds()) { + Map expectedAmounts = new LinkedHashMap<>(); + for (ResourceId resourceId : resourcesPluginData.getResourceIds()) { + expectedAmounts.put(resourceId, 0L); + } + for (ResourceId resourceId : resourcesPluginData.getResourceIds()) { + Optional optional = resourcesPluginData.getRegionResourceLevel(regionId, resourceId); + if (optional.isPresent()) { + expectedAmounts.put(resourceId, optional.get()); + } + } + for (ResourceId resourceId : resourcesPluginData.getResourceIds()) { + long expectedRegionResourceLevel = expectedAmounts.get(resourceId); + long actualRegionResourceLevel = resourcesDataManager.getRegionResourceLevel(regionId, resourceId); + assertEquals(expectedRegionResourceLevel, actualRegionResourceLevel); + } + } + + for (ResourceId resourceId : resourcesPluginData.getResourceIds()) { + boolean expectedPolicy = resourcesPluginData.getResourceTimeTrackingPolicy(resourceId); + boolean actualPolicy = resourcesDataManager.getPersonResourceTimeTrackingPolicy(resourceId); + assertEquals(expectedPolicy, actualPolicy); + } + + assertEquals(resourcesPluginData.getResourceIds(), resourcesDataManager.getResourceIds()); + + for (ResourceId resourceId : resourcesDataManager.getResourceIds()) { + List expectedResourceLevels = resourcesPluginData.getPersonResourceLevels(resourceId); + for (PersonId personId : personIds) { + long expectedLevel = 0L; + int personIndex = personId.getValue(); + if (personIndex < expectedResourceLevels.size()) { + Long value = expectedResourceLevels.get(personIndex); + if (value != null) { + expectedLevel = value; + } + } + long actualLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); + assertEquals(expectedLevel, actualLevel); + } + } + + for (ResourceId resourceId : resourcesDataManager.getResourceIds()) { + boolean trackTimes = resourcesPluginData.getResourceTimeTrackingPolicy(resourceId); + if (trackTimes) { + List expectedResourceTimes = resourcesPluginData.getPersonResourceTimes(resourceId); + Double resourceDefaultTime = resourcesPluginData.getResourceDefaultTime(resourceId); + for (PersonId personId : personIds) { + double expectedTime = resourceDefaultTime; + int personIndex = personId.getValue(); + if (personIndex < expectedResourceTimes.size()) { + Double time = expectedResourceTimes.get(personIndex); + if (time != null) { + expectedTime = time; + } + } + double actualTime = resourcesDataManager.getPersonResourceTime(resourceId, personId); + assertEquals(expectedTime, actualTime); + } + } + } + + for (ResourceId resourceId : resourcesPluginData.getResourceIds()) { + Set expectedResourcePropertyIds = resourcesPluginData + .getResourcePropertyIds(resourceId); + Set actualResourcePropertyIds = resourcesDataManager + .getResourcePropertyIds(resourceId); + assertEquals(expectedResourcePropertyIds, actualResourcePropertyIds); + + for (ResourcePropertyId resourcePropertyId : expectedResourcePropertyIds) { + PropertyDefinition expectedDefinition = resourcesPluginData + .getResourcePropertyDefinition(resourceId, resourcePropertyId); + PropertyDefinition actualDefinition = resourcesDataManager.getResourcePropertyDefinition(resourceId, + resourcePropertyId); + assertEquals(expectedDefinition, actualDefinition); + + Optional optional = resourcesPluginData.getResourcePropertyValue(resourceId, + resourcePropertyId); + assertTrue(optional.isPresent()); + Object expectedValue = optional.get(); + Object actualValue = resourcesDataManager.getResourcePropertyValue(resourceId, resourcePropertyId); + assertEquals(expectedValue, actualValue); + + } + } + + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = ResourcesTestPluginFactory.factory(initialPopulation, initialPopulation, testPluginData) + .setResourcesPluginData(resourcesPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "getEventFilterForPersonResourceUpdateEvent", args = { + ResourceId.class }) + public void testGetEventFilterForPersonResourceUpdateEvent_Resource() { + + Set selectedResources = new LinkedHashSet<>(); + selectedResources.add(TestResourceId.RESOURCE_1); + selectedResources.add(TestResourceId.RESOURCE_3); + selectedResources.add(TestResourceId.RESOURCE_5); + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // Have an actor add resources to the regions + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + RegionsDataManager regionLocationDataManager = c.getDataManager(RegionsDataManager.class); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + + List people = peopleDataManager.getPeople(); + + // add resources to all regions + for (PersonId personId : people) { + RegionId regionId = regionLocationDataManager.getPersonRegion(personId); + for (TestResourceId testResourceId : TestResourceId.values()) { + resourcesDataManager.addResourceToRegion(testResourceId, regionId, 1L); + } + } + + })); + + // Have an actor observe the resource transfers + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(1, (c) -> { + + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + + for (TestResourceId testResourceId : selectedResources) { + EventFilter eventFilter = resourcesDataManager + .getEventFilterForPersonResourceUpdateEvent(testResourceId); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c.getTime(), e.personId(), e.resourceId())); + }); + } + + })); + + int comparisonDay = 100; + + // Have an actor transfer the resources to people + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + List people = peopleDataManager.getPeople(); + + for (int i = 2; i < comparisonDay; i++) { + c.addPlan((c2) -> { + TestResourceId testResourceId = TestResourceId.getRandomResourceId(randomGenerator); + PersonId personId = people.get(randomGenerator.nextInt(people.size())); + RegionId regionId = regionsDataManager.getPersonRegion(personId); + long resourceLevel = resourcesDataManager.getRegionResourceLevel(regionId, testResourceId); + if (resourceLevel > 0) { + resourcesDataManager.transferResourceToPersonFromRegion(testResourceId, personId, 1L); + if (selectedResources.contains(testResourceId)) { + expectedObservations.add(new MultiKey(c2.getTime(), personId, testResourceId)); + } + } + }, i); + } + + })); + + // Have an actor show that the proper observations were generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(comparisonDay, (c) -> { + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = ResourcesTestPluginFactory.factory(30, 4043641365602447479L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the resource id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(30, 5107085853667531414L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.getEventFilterForPersonResourceUpdateEvent(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the resource id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(30, 5551635264070855342L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.getEventFilterForPersonResourceUpdateEvent(TestResourceId.getUnknownResourceId()); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "getEventFilterForPersonResourceUpdateEvent", args = { + ResourceId.class, PersonId.class }) + public void testGetEventFilterForPersonResourceUpdateEvent_Resource_Person() { + + Set selectedResources = new LinkedHashSet<>(); + selectedResources.add(TestResourceId.RESOURCE_1); + selectedResources.add(TestResourceId.RESOURCE_3); + selectedResources.add(TestResourceId.RESOURCE_5); + + Set selectedPeople = new LinkedHashSet<>(); + selectedPeople.add(new PersonId(22)); + selectedPeople.add(new PersonId(8)); + selectedPeople.add(new PersonId(5)); + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // Have an actor add resources to the regions + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + RegionsDataManager regionLocationDataManager = c.getDataManager(RegionsDataManager.class); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + + List people = peopleDataManager.getPeople(); + + // add resources to all regions + for (PersonId personId : people) { + RegionId regionId = regionLocationDataManager.getPersonRegion(personId); + for (TestResourceId testResourceId : TestResourceId.values()) { + resourcesDataManager.addResourceToRegion(testResourceId, regionId, 1L); + } + } + + })); + + // Have an actor observe the resource transfers + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(1, (c) -> { + + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + + for (TestResourceId testResourceId : selectedResources) { + for (PersonId personId : selectedPeople) { + EventFilter eventFilter = resourcesDataManager + .getEventFilterForPersonResourceUpdateEvent(testResourceId, personId); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c.getTime(), e.personId(), e.resourceId())); + }); + } + } + + })); + + int comparisonDay = 100; + + // Have an actor transfer the resources to people + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + List people = peopleDataManager.getPeople(); + + for (int i = 2; i < comparisonDay; i++) { + c.addPlan((c2) -> { + TestResourceId testResourceId = TestResourceId.getRandomResourceId(randomGenerator); + PersonId personId = people.get(randomGenerator.nextInt(people.size())); + RegionId regionId = regionsDataManager.getPersonRegion(personId); + long resourceLevel = resourcesDataManager.getRegionResourceLevel(regionId, testResourceId); + if (resourceLevel > 0) { + resourcesDataManager.transferResourceToPersonFromRegion(testResourceId, personId, 1L); + if (selectedResources.contains(testResourceId) && selectedPeople.contains(personId)) { + expectedObservations.add(new MultiKey(c2.getTime(), personId, testResourceId)); + } + } + }, i); + } + + })); + + // Have an actor show that the proper observations were generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(comparisonDay, (c) -> { + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = ResourcesTestPluginFactory.factory(30, 3776094770483573425L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the resource id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(30, 8909938597230752836L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.getEventFilterForPersonResourceUpdateEvent(null, new PersonId(0)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the resource id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(30, 4146350189128134907L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.getEventFilterForPersonResourceUpdateEvent(TestResourceId.getUnknownResourceId(), + new PersonId(0)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the person id is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(30, 8356399638914398643L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + PersonId nullPersonId = null; + resourcesDataManager.getEventFilterForPersonResourceUpdateEvent(TestResourceId.RESOURCE_1, + nullPersonId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + /* precondition test: if the person id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(30, 3890936504108305392L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + PersonId unknownPersonId = new PersonId(100000); + resourcesDataManager.getEventFilterForPersonResourceUpdateEvent(TestResourceId.RESOURCE_1, + unknownPersonId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "getEventFilterForPersonResourceUpdateEvent", args = { + ResourceId.class, RegionId.class }) + public void testGetEventFilterForPersonResourceUpdateEvent_Resource_Region() { + Set selectedResources = new LinkedHashSet<>(); + selectedResources.add(TestResourceId.RESOURCE_1); + selectedResources.add(TestResourceId.RESOURCE_3); + selectedResources.add(TestResourceId.RESOURCE_5); + + Set selectedRegions = new LinkedHashSet<>(); + selectedRegions.add(TestRegionId.REGION_1); + selectedRegions.add(TestRegionId.REGION_5); + selectedRegions.add(TestRegionId.REGION_6); + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // Have an actor add resources to the regions + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + RegionsDataManager regionLocationDataManager = c.getDataManager(RegionsDataManager.class); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + + List people = peopleDataManager.getPeople(); + + // add resources to all regions + for (PersonId personId : people) { + RegionId regionId = regionLocationDataManager.getPersonRegion(personId); + for (TestResourceId testResourceId : TestResourceId.values()) { + resourcesDataManager.addResourceToRegion(testResourceId, regionId, 1L); + } + } + + })); + + // Have an actor observe the resource transfers + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(1, (c) -> { + + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + + for (TestResourceId testResourceId : selectedResources) { + for (RegionId regionId : selectedRegions) { + EventFilter eventFilter = resourcesDataManager + .getEventFilterForPersonResourceUpdateEvent(testResourceId, regionId); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c.getTime(), e.personId(), e.resourceId())); + }); + } + } + + })); + + int comparisonDay = 100; + + // Have an actor transfer the resources to people + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + List people = peopleDataManager.getPeople(); + + for (int i = 2; i < comparisonDay; i++) { + c.addPlan((c2) -> { + TestResourceId testResourceId = TestResourceId.getRandomResourceId(randomGenerator); + PersonId personId = people.get(randomGenerator.nextInt(people.size())); + RegionId regionId = regionsDataManager.getPersonRegion(personId); + long resourceLevel = resourcesDataManager.getRegionResourceLevel(regionId, testResourceId); + if (resourceLevel > 0) { + resourcesDataManager.transferResourceToPersonFromRegion(testResourceId, personId, 1L); + if (selectedResources.contains(testResourceId) && selectedRegions.contains(regionId)) { + expectedObservations.add(new MultiKey(c2.getTime(), personId, testResourceId)); + } + } + }, i); + } + + })); + + // Have an actor show that the proper observations were generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(comparisonDay, (c) -> { + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = ResourcesTestPluginFactory.factory(30, 1727074366899837142L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the resource id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(30, 7693743966390586978L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.getEventFilterForPersonResourceUpdateEvent(null, new PersonId(0)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the resource id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(30, 693173450564289263L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.getEventFilterForPersonResourceUpdateEvent(TestResourceId.getUnknownResourceId(), + new PersonId(0)); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the region id is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(30, 9201364062172125070L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + RegionId nullRegionId = null; + resourcesDataManager.getEventFilterForPersonResourceUpdateEvent(TestResourceId.RESOURCE_1, + nullRegionId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); + + /* precondition test: if the region id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(30, 5569918148190340272L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + RegionId unknownRegionId = TestRegionId.getUnknownRegionId(); + resourcesDataManager.getEventFilterForPersonResourceUpdateEvent(TestResourceId.RESOURCE_1, + unknownRegionId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "getEventFilterForPersonResourceUpdateEvent", args = {}) + public void testGetEventFilterForPersonResourceUpdateEvent() { + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // Have an actor add resources to the regions + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + RegionsDataManager regionLocationDataManager = c.getDataManager(RegionsDataManager.class); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + + List people = peopleDataManager.getPeople(); + + // add resources to all regions + for (PersonId personId : people) { + RegionId regionId = regionLocationDataManager.getPersonRegion(personId); + for (TestResourceId testResourceId : TestResourceId.values()) { + resourcesDataManager.addResourceToRegion(testResourceId, regionId, 1L); + } + } + + })); + + // Have an actor observe the resource transfers + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(1, (c) -> { + + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + + EventFilter eventFilter = resourcesDataManager + .getEventFilterForPersonResourceUpdateEvent(); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c.getTime(), e.personId(), e.resourceId())); + }); + + })); + + int comparisonDay = 100; + + // Have an actor transfer the resources to people + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + List people = peopleDataManager.getPeople(); + + for (int i = 2; i < comparisonDay; i++) { + c.addPlan((c2) -> { + TestResourceId testResourceId = TestResourceId.getRandomResourceId(randomGenerator); + PersonId personId = people.get(randomGenerator.nextInt(people.size())); + RegionId regionId = regionsDataManager.getPersonRegion(personId); + long resourceLevel = resourcesDataManager.getRegionResourceLevel(regionId, testResourceId); + if (resourceLevel > 0) { + resourcesDataManager.transferResourceToPersonFromRegion(testResourceId, personId, 1L); + expectedObservations.add(new MultiKey(c2.getTime(), personId, testResourceId)); + } + }, i); + } + + })); + + // Have an actor show that the proper observations were generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(comparisonDay, (c) -> { + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = ResourcesTestPluginFactory.factory(30, 1345117947886682832L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "getEventFilterForRegionResourceUpdateEvent", args = { + ResourceId.class }) + public void testGetEventFilterForRegionResourceUpdateEvent_Resource() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + Set selectedResources = new LinkedHashSet<>(); + selectedResources.add(TestResourceId.RESOURCE_1); + selectedResources.add(TestResourceId.RESOURCE_3); + selectedResources.add(TestResourceId.RESOURCE_4); + + // Have an actor to observe the selected resource changes + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + for (TestResourceId testResourceId : selectedResources) { + EventFilter eventFilter = resourcesDataManager + .getEventFilterForRegionResourceUpdateEvent(testResourceId); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(e.regionId(), e.resourceId(), e.previousResourceLevel(), + e.currentResourceLevel())); + }); + } + })); + + int comparisonDay = 100; + + // Have an actor add resources to regions + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + + // add random amounts of resources to regions + + for (int i = 2; i < comparisonDay; i++) { + c.addPlan((c2) -> { + // select random regions and resources + TestRegionId regionId = TestRegionId.getRandomRegionId(randomGenerator); + TestResourceId resourceId = TestResourceId.getRandomResourceId(randomGenerator); + long regionLevel = resourcesDataManager.getRegionResourceLevel(regionId, resourceId); + + // select an amount to add + long amount = randomGenerator.nextInt(100) + 1; + resourcesDataManager.addResourceToRegion(resourceId, regionId, amount); + + if (selectedResources.contains(resourceId)) { + expectedObservations.add(new MultiKey(regionId, resourceId, regionLevel, regionLevel + amount)); + } + + }, i); + } + + })); + + // have the observer show that the correct observations were generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(comparisonDay, (c) -> { + assertFalse(expectedObservations.isEmpty()); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = ResourcesTestPluginFactory.factory(0, 2870952108296201475L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the resource id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 9101711257710159283L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + ResourceId nullResourceId = null; + resourcesDataManager.getEventFilterForRegionResourceUpdateEvent(nullResourceId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the resource id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 4216397684435821705L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + ResourceId unknownResourceId = TestResourceId.getUnknownResourceId(); + resourcesDataManager.getEventFilterForRegionResourceUpdateEvent(unknownResourceId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "getEventFilterForRegionResourceUpdateEvent", args = { + ResourceId.class, RegionId.class }) + public void testGetEventFilterForRegionResourceUpdateEvent_Resource_Region() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + Set> selectedRegionResourcePairs = new LinkedHashSet<>(); + selectedRegionResourcePairs.add(new Pair<>(TestRegionId.REGION_2, TestResourceId.RESOURCE_1)); + selectedRegionResourcePairs.add(new Pair<>(TestRegionId.REGION_5, TestResourceId.RESOURCE_2)); + selectedRegionResourcePairs.add(new Pair<>(TestRegionId.REGION_2, TestResourceId.RESOURCE_3)); + selectedRegionResourcePairs.add(new Pair<>(TestRegionId.REGION_2, TestResourceId.RESOURCE_4)); + selectedRegionResourcePairs.add(new Pair<>(TestRegionId.REGION_4, TestResourceId.RESOURCE_5)); + selectedRegionResourcePairs.add(new Pair<>(TestRegionId.REGION_4, TestResourceId.RESOURCE_3)); + + // Have an actor to observe the selected resource changes + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + + for (Pair pair : selectedRegionResourcePairs) { + TestRegionId regionId = pair.getFirst(); + TestResourceId resourceId = pair.getSecond(); + EventFilter eventFilter = resourcesDataManager + .getEventFilterForRegionResourceUpdateEvent(resourceId, regionId); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(e.regionId(), e.resourceId(), e.previousResourceLevel(), + e.currentResourceLevel())); + }); + } + })); + + int comparisonDay = 100; + + // Have an actor add resources to regions + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + + // add random amounts of resources to regions + + for (int i = 2; i < comparisonDay; i++) { + c.addPlan((c2) -> { + // select random regions and resources + TestRegionId regionId = TestRegionId.getRandomRegionId(randomGenerator); + TestResourceId resourceId = TestResourceId.getRandomResourceId(randomGenerator); + long regionLevel = resourcesDataManager.getRegionResourceLevel(regionId, resourceId); + + // select an amount to add + long amount = randomGenerator.nextInt(100) + 1; + resourcesDataManager.addResourceToRegion(resourceId, regionId, amount); + Pair pair = new Pair<>(regionId, resourceId); + if (selectedRegionResourcePairs.contains(pair)) { + expectedObservations.add(new MultiKey(regionId, resourceId, regionLevel, regionLevel + amount)); + } + + }, i); + } + + })); + + // have the observer show that the correct observations were generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(comparisonDay, (c) -> { + assertFalse(expectedObservations.isEmpty()); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = ResourcesTestPluginFactory.factory(0, 9022862258230350395L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the resource id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 217976606974469406L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + ResourceId nullResourceId = null; + resourcesDataManager.getEventFilterForRegionResourceUpdateEvent(nullResourceId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the resource id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 8125399461811894989L, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + ResourceId unknownResourceId = TestResourceId.getUnknownResourceId(); + resourcesDataManager.getEventFilterForRegionResourceUpdateEvent(unknownResourceId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "getEventFilterForRegionResourceUpdateEvent", args = {}) + public void testGetEventFilterForRegionResourceUpdateEvent() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // Have an actor to observe the selected resource changes + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + + EventFilter eventFilter = resourcesDataManager + .getEventFilterForRegionResourceUpdateEvent(); + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(e.regionId(), e.resourceId(), e.previousResourceLevel(), + e.currentResourceLevel())); + }); + + })); + + int comparisonDay = 100; + + // Have an actor add resources to regions + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + + // add random amounts of resources to regions + + for (int i = 2; i < comparisonDay; i++) { + c.addPlan((c2) -> { + // select random regions and resources + TestRegionId regionId = TestRegionId.getRandomRegionId(randomGenerator); + TestResourceId resourceId = TestResourceId.getRandomResourceId(randomGenerator); + long regionLevel = resourcesDataManager.getRegionResourceLevel(regionId, resourceId); + + // select an amount to add + long amount = randomGenerator.nextInt(100) + 1; + resourcesDataManager.addResourceToRegion(resourceId, regionId, amount); + expectedObservations.add(new MultiKey(regionId, resourceId, regionLevel, regionLevel + amount)); + }, i); + } + + })); + + // have the observer show that the correct observations were generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(comparisonDay, (c) -> { + assertFalse(expectedObservations.isEmpty()); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = ResourcesTestPluginFactory.factory(0, 4130610902285408287L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "getEventFilterForResourcePropertyUpdateEvent", args = { + ResourceId.class, ResourcePropertyId.class }) + public void testGetEventFilterForResourcePropertyUpdateEvent_Resource_Property() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + Set> selectedResourcePropertyPairs = new LinkedHashSet<>(); + selectedResourcePropertyPairs + .add(new Pair<>(TestResourceId.RESOURCE_1, TestResourcePropertyId.ResourceProperty_1_3_DOUBLE_MUTABLE)); + selectedResourcePropertyPairs.add( + new Pair<>(TestResourceId.RESOURCE_1, TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE)); + selectedResourcePropertyPairs.add( + new Pair<>(TestResourceId.RESOURCE_2, TestResourcePropertyId.ResourceProperty_2_2_INTEGER_MUTABLE)); + selectedResourcePropertyPairs + .add(new Pair<>(TestResourceId.RESOURCE_3, TestResourcePropertyId.ResourceProperty_3_2_STRING_MUTABLE)); + selectedResourcePropertyPairs.add( + new Pair<>(TestResourceId.RESOURCE_3, TestResourcePropertyId.ResourceProperty_3_1_BOOLEAN_MUTABLE)); + selectedResourcePropertyPairs.add( + new Pair<>(TestResourceId.RESOURCE_5, TestResourcePropertyId.ResourceProperty_5_2_DOUBLE_IMMUTABLE)); + + // Have an actor observe the selected resource property changes + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + for (Pair pair : selectedResourcePropertyPairs) { + TestResourceId testResourceId = pair.getFirst(); + TestResourcePropertyId testResourcePropertyId = pair.getSecond(); + EventFilter eventFilter = resourcesDataManager + .getEventFilterForResourcePropertyUpdateEvent(testResourceId, testResourcePropertyId); + + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c.getTime(), e.resourceId(), e.resourcePropertyId(), + e.previousPropertyValue(), e.currentPropertyValue())); + }); + + } + })); + + int comparisonDay = 100; + + // Have an actor assign resource properties + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + + TestResourceId testResourceId = TestResourceId.getRandomResourceId(randomGenerator); + TestResourcePropertyId testResourcePropertyId = TestResourcePropertyId + .getRandomResourcePropertyId(testResourceId, randomGenerator); + + for (int i = 1; i < comparisonDay; i++) { + c.addPlan((c2) -> { + PropertyDefinition propertyDefinition = resourcesDataManager + .getResourcePropertyDefinition(testResourceId, testResourcePropertyId); + if (propertyDefinition.propertyValuesAreMutable()) { + // update the property value + Object resourcePropertyValue = resourcesDataManager.getResourcePropertyValue(testResourceId, + testResourcePropertyId); + Object expectedValue = testResourcePropertyId.getRandomPropertyValue(randomGenerator); + resourcesDataManager.setResourcePropertyValue(testResourceId, testResourcePropertyId, + expectedValue); + + Pair pair = new Pair<>(testResourceId, + testResourcePropertyId); + if (selectedResourcePropertyPairs.contains(pair)) { + expectedObservations.add(new MultiKey(c2.getTime(), testResourceId, testResourcePropertyId, + resourcePropertyValue, expectedValue)); + } + } + }, i); + } + + })); + + // Have the observer show that the observations were properly generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(comparisonDay, (c) -> { + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = ResourcesTestPluginFactory.factory(0, 4039871222190675923L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + /* precondition test: if the resource id is null */ + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 7664472869248061620L, (c) -> { + ResourceId resourceId = null; + ResourcePropertyId resourcePropertyId = TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE; + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.getEventFilterForResourcePropertyUpdateEvent(resourceId, resourcePropertyId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the resource id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 2475328515664171695L, (c) -> { + ResourceId resourceId = TestResourceId.getUnknownResourceId(); + ResourcePropertyId resourcePropertyId = TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE; + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.getEventFilterForResourcePropertyUpdateEvent(resourceId, resourcePropertyId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + + /* precondition test: if the resource property id is null */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 7416000716392694948L, (c) -> { + ResourceId resourceId = TestResourceId.RESOURCE_1; + ResourcePropertyId resourcePropertyId = null; + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.getEventFilterForResourcePropertyUpdateEvent(resourceId, resourcePropertyId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + /* precondition test: if the resource property id is unknown */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 697790634696788239L, (c) -> { + ResourceId resourceId = TestResourceId.RESOURCE_1; + ResourcePropertyId resourcePropertyId = TestResourcePropertyId.getUnknownResourcePropertyId(); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.getEventFilterForResourcePropertyUpdateEvent(resourceId, resourcePropertyId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + /* + * precondition test: if the resource property id is unknown -- in this case it + * is linked to a different resource + */ + contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = ResourcesTestPluginFactory.factory(0, 107265130769422979L, (c) -> { + ResourceId resourceId = TestResourceId.RESOURCE_1; + ResourcePropertyId resourcePropertyId = TestResourcePropertyId.ResourceProperty_2_1_BOOLEAN_MUTABLE; + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.getEventFilterForResourcePropertyUpdateEvent(resourceId, resourcePropertyId); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "getEventFilterForResourcePropertyUpdateEvent", args = {}) + public void testGetEventFilterForResourcePropertyUpdateEvent() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // Have an actor observe the selected resource property changes + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + + EventFilter eventFilter = resourcesDataManager + .getEventFilterForResourcePropertyUpdateEvent(); + + c.subscribe(eventFilter, (c2, e) -> { + actualObservations.add(new MultiKey(c.getTime(), e.resourceId(), e.resourcePropertyId(), + e.previousPropertyValue(), e.currentPropertyValue())); + }); + + })); + + int comparisonDay = 100; + + // Have an actor assign resource properties + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + + TestResourceId testResourceId = TestResourceId.getRandomResourceId(randomGenerator); + TestResourcePropertyId testResourcePropertyId = TestResourcePropertyId + .getRandomResourcePropertyId(testResourceId, randomGenerator); + + for (int i = 1; i < comparisonDay; i++) { + c.addPlan((c2) -> { + PropertyDefinition propertyDefinition = resourcesDataManager + .getResourcePropertyDefinition(testResourceId, testResourcePropertyId); + if (propertyDefinition.propertyValuesAreMutable()) { + // update the property value + Object resourcePropertyValue = resourcesDataManager.getResourcePropertyValue(testResourceId, + testResourcePropertyId); + Object expectedValue = testResourcePropertyId.getRandomPropertyValue(randomGenerator); + resourcesDataManager.setResourcePropertyValue(testResourceId, testResourcePropertyId, + expectedValue); + expectedObservations.add(new MultiKey(c2.getTime(), testResourceId, testResourcePropertyId, + resourcePropertyValue, expectedValue)); + } + }, i); + } + + })); + + // Have the observer show that the observations were properly generated + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(comparisonDay, (c) -> { + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = ResourcesTestPluginFactory.factory(0, 4428711217570070234L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "getEventFilterForResourceIdAdditionEvent", args = {}) + public void testGetEventFilterForResourceIdAdditionEvent() { + ResourceId newResourceId1 = TestResourceId.getUnknownResourceId(); + ResourceId newResourceId2 = TestResourceId.getUnknownResourceId(); + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + EventFilter eventFilter = resourcesDataManager + .getEventFilterForResourceIdAdditionEvent(); + c.subscribe(eventFilter, (c2, e) -> { + MultiKey multiKey = new MultiKey(c2.getTime(), e.resourceId(), e.timeTrackingPolicy()); + actualObservations.add(multiKey); + }); + + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + boolean timeTrackingPolicy = false; + assertFalse(resourcesDataManager.resourceIdExists(newResourceId1)); + resourcesDataManager.addResourceId(newResourceId1, timeTrackingPolicy); + MultiKey multiKey = new MultiKey(c.getTime(), newResourceId1, false); + expectedObservations.add(multiKey); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + boolean timeTrackingPolicy = true; + assertFalse(resourcesDataManager.resourceIdExists(newResourceId2)); + resourcesDataManager.addResourceId(newResourceId2, timeTrackingPolicy); + MultiKey multiKey = new MultiKey(c.getTime(), newResourceId2, true); + expectedObservations.add(multiKey); + })); + + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(3, (c) -> { + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = ResourcesTestPluginFactory.factory(5, 6169797168816977272L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "getEventFilterForResourcePropertyDefinitionEvent", args = {}) + public void testGetEventFilterForResourcePropertyDefinitionEvent() { + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + Set expectedObservations = new LinkedHashSet<>(); + Set actualObservations = new LinkedHashSet<>(); + + // have an actor observe the resource property definition events + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + EventFilter eventFilter = resourcesDataManager + .getEventFilterForResourcePropertyDefinitionEvent(); + c.subscribe(eventFilter, (c2, e) -> { + MultiKey multiKey = new MultiKey(c2.getTime(), e.resourceId(), e.resourcePropertyId()); + actualObservations.add(multiKey); + }); + })); + + // have an actor define a new resource property + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + ResourcePropertyId newResourcePropertyId = TestResourcePropertyId.getUnknownResourcePropertyId(); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Double.class) + .setDefaultValue(34.6).build(); + ResourcePropertyInitialization resourcePropertyInitialization = // + ResourcePropertyInitialization.builder()// + .setPropertyDefinition(propertyDefinition)// + .setResourceId(TestResourceId.RESOURCE_1)// + .setResourcePropertyId(newResourcePropertyId)// + .build(); + resourcesDataManager.defineResourceProperty(resourcePropertyInitialization); + MultiKey multiKey = new MultiKey(c.getTime(), TestResourceId.RESOURCE_1, newResourcePropertyId); + expectedObservations.add(multiKey); + })); + + // have an actor define a new resource property + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + ResourcePropertyId newResourcePropertyId = TestResourcePropertyId.getUnknownResourcePropertyId(); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(String.class) + .setDefaultValue("default").build(); + + ResourcePropertyInitialization resourcePropertyInitialization = // + ResourcePropertyInitialization.builder()// + .setPropertyDefinition(propertyDefinition)// + .setResourceId(TestResourceId.RESOURCE_2)// + .setResourcePropertyId(newResourcePropertyId)// + .build(); + resourcesDataManager.defineResourceProperty(resourcePropertyInitialization); + + MultiKey multiKey = new MultiKey(c.getTime(), TestResourceId.RESOURCE_2, newResourcePropertyId); + expectedObservations.add(multiKey); + })); + + // have the observer verify the observations were correct + pluginBuilder.addTestActorPlan("observer", new TestActorPlan(3, (c) -> { + assertEquals(2, expectedObservations.size()); + assertEquals(expectedObservations, actualObservations); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = ResourcesTestPluginFactory.factory(5, 1942435631952524244L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + /** + * Demonstrates that the data manager exhibits run continuity. The state of the + * data manager is not effected by repeatedly starting and stopping the + * simulation. + */ + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testStateContinuity() { + + /* + * Note that we are not testing the content of the plugin datas -- that is + * covered by the other state tests. We show here only that the resulting plugin + * data state is the same without regard to how we break up the run. + */ + + Set pluginDatas = new LinkedHashSet<>(); + pluginDatas.add(testStateContinuity(1)); + pluginDatas.add(testStateContinuity(5)); + pluginDatas.add(testStateContinuity(10)); + + assertEquals(1, pluginDatas.size()); + } + + /* + * Returns the resources plugin data resulting from various resource related + * events over several days. Attempts to stop and start the simulation by the + * given number of increments. + */ + private String testStateContinuity(int incrementCount) { + String result = null; + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(177404262666515111L); + + /* + * Build the RunContinuityPluginData with five context consumers that will add + * and remove people over several days + */ + RunContinuityPluginData.Builder continuityBuilder = RunContinuityPluginData.builder(); + + /* + * Add some resource ids. Add some people with one of the resources added to the + * people. + */ + continuityBuilder.addContextConsumer(0.5, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + + boolean trackResources = false; + for (TestResourceId testResourceId : TestResourceId.values()) { + resourcesDataManager.addResourceId(testResourceId, trackResources); + trackResources = !trackResources; + } + + for (int i = 0; i < 10; i++) { + TestRegionId regionId = TestRegionId.getRandomRegionId(randomGenerator); + ResourceId resourceId = TestResourceId.RESOURCE_3; + long resourceLevel = randomGenerator.nextInt(5); + ResourceInitialization resourceInitialization = new ResourceInitialization(resourceId, resourceLevel); + PersonConstructionData personConstructionData = PersonConstructionData.builder()// + .add(regionId)// + .add(resourceInitialization)// + .build();// + peopleDataManager.addPerson(personConstructionData); + } + + }); + + /* + * Add 300 to 1300 units of each resource to each region + */ + continuityBuilder.addContextConsumer(1.2, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + + List list = new ArrayList<>(); + Random random = new Random(randomGenerator.nextLong()); + + for (TestRegionId testRegionId : TestRegionId.values()) { + for (TestResourceId testResourceId : TestResourceId.values()) { + list.add(new MultiKey(testRegionId, testResourceId)); + } + } + Collections.shuffle(list, random); + + for (MultiKey multiKey : list) { + TestRegionId testRegionId = multiKey.getKey(0); + TestResourceId testResourceId = multiKey.getKey(1); + long amount = randomGenerator.nextInt(10) * 100 + 300; + resourcesDataManager.addResourceToRegion(testResourceId, testRegionId, amount); + } + }); + + // add some more resources to some regions + continuityBuilder.addContextConsumer(1.5, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + for (int i = 0; i < 30; i++) { + TestRegionId testRegionId = TestRegionId.getRandomRegionId(randomGenerator); + TestResourceId testResourceId = TestResourceId.getRandomResourceId(randomGenerator); + long amount = randomGenerator.nextInt(1000) + 1; + resourcesDataManager.addResourceToRegion(testResourceId, testRegionId, amount); + } + }); + + /* + * define some resource properties + */ + continuityBuilder.addContextConsumer(1.6, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId.values()) { + ResourcePropertyInitialization resourcePropertyInitialization = // + ResourcePropertyInitialization.builder()// + .setPropertyDefinition(testResourcePropertyId.getPropertyDefinition())// + .setResourceId(testResourcePropertyId.getTestResourceId()) + .setResourcePropertyId(testResourcePropertyId).build(); + resourcesDataManager.defineResourceProperty(resourcePropertyInitialization); + } + + }); + + // set some resource properties + continuityBuilder.addContextConsumer(2.2, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + + List testResourcePropertyIds = Arrays.asList(TestResourcePropertyId.values()); + Collections.reverse(testResourcePropertyIds); + + for (TestResourcePropertyId testResourcePropertyId : testResourcePropertyIds) { + + PropertyDefinition propertyDefinition = resourcesDataManager.getResourcePropertyDefinition( + testResourcePropertyId.getTestResourceId(), testResourcePropertyId); + if (propertyDefinition.propertyValuesAreMutable()) { + resourcesDataManager.setResourcePropertyValue(testResourcePropertyId.getTestResourceId(), + testResourcePropertyId, testResourcePropertyId.getRandomPropertyValue(randomGenerator)); + } + } + + }); + + // transfer resources between regions + continuityBuilder.addContextConsumer(2.5, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + for (int i = 0; i < 50; i++) { + TestResourceId resourceId = TestResourceId.getRandomResourceId(randomGenerator); + TestRegionId sourceRegionId = TestRegionId.getRandomRegionId(randomGenerator); + TestRegionId destinationRegionId = TestRegionId.getRandomRegionId(randomGenerator); + + if (sourceRegionId != destinationRegionId) { + + long regionResourceLevel = resourcesDataManager.getRegionResourceLevel(sourceRegionId, resourceId); + + long amountToTransfer = regionResourceLevel / 10; + + if (amountToTransfer > 0) { + resourcesDataManager.transferResourceBetweenRegions(resourceId, sourceRegionId, + destinationRegionId, amountToTransfer); + } + } + } + + }); + + // transfer resource from regions to people + continuityBuilder.addContextConsumer(4.6, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + + List people = peopleDataManager.getPeople(); + + for (int i = 0; i < people.size(); i++) { + PersonId personId = people.get(randomGenerator.nextInt(people.size())); + TestResourceId testResourceId = TestResourceId.getRandomResourceId(randomGenerator); + RegionId regionId = regionsDataManager.getPersonRegion(personId); + long avaialableAmount = resourcesDataManager.getRegionResourceLevel(regionId, testResourceId); + long amount = randomGenerator.nextInt(15); + amount = FastMath.min(amount, avaialableAmount); + if (amount > 0) { + resourcesDataManager.transferResourceToPersonFromRegion(testResourceId, personId, amount); + } + } + + }); + + // transfer resources from people to regions + continuityBuilder.addContextConsumer(4.7, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + List people = peopleDataManager.getPeople(); + for (int i = 0; i < people.size(); i++) { + PersonId personId = people.get(randomGenerator.nextInt(people.size())); + TestResourceId testResourceId = TestResourceId.getRandomResourceId(randomGenerator); + long avaialableAmount = resourcesDataManager.getPersonResourceLevel(testResourceId, personId); + long amount = randomGenerator.nextInt(15); + amount = FastMath.min(amount, avaialableAmount); + if (amount > 0) { + resourcesDataManager.transferResourceFromPersonToRegion(testResourceId, personId, amount); + } + } + + }); + + // remove resource from people + continuityBuilder.addContextConsumer(5.3, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + List people = peopleDataManager.getPeople(); + for (int i = 0; i < people.size(); i++) { + PersonId personId = people.get(randomGenerator.nextInt(people.size())); + TestResourceId testResourceId = TestResourceId.getRandomResourceId(randomGenerator); + long avaialableAmount = resourcesDataManager.getPersonResourceLevel(testResourceId, personId); + long amount = randomGenerator.nextInt(15); + amount = FastMath.min(amount, avaialableAmount); + if (amount > 0) { + resourcesDataManager.removeResourceFromPerson(testResourceId, personId, amount); + } + } + + }); + + // remove resources from regions + continuityBuilder.addContextConsumer(5.5, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + + List regionIds = new ArrayList<>(regionsDataManager.getRegionIds()); + + for (int i = 0; i < regionIds.size(); i++) { + RegionId regionId = regionIds.get(randomGenerator.nextInt(regionIds.size())); + TestResourceId testResourceId = TestResourceId.getRandomResourceId(randomGenerator); + long avaialableAmount = resourcesDataManager.getRegionResourceLevel(regionId, testResourceId); + long amount = randomGenerator.nextInt(15); + amount = FastMath.min(amount, avaialableAmount); + if (amount > 0) { + resourcesDataManager.removeResourceFromRegion(testResourceId, regionId, amount); + } + } + + }); + + continuityBuilder.addContextConsumer(6.0, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + c.releaseOutput(resourcesDataManager.toString()); + }); + + RunContinuityPluginData runContinuityPluginData = continuityBuilder.build(); + + // Build an empty people plugin data for time zero + PeoplePluginData peoplePluginData = PeoplePluginData.builder().build(); + + // Build a regions plugin data with the test regions + RegionsPluginData.Builder regionsBuilder = RegionsPluginData.builder(); + for (TestRegionId testRegionId : TestRegionId.values()) { + regionsBuilder.addRegion(testRegionId); + } + RegionsPluginData regionsPluginData = regionsBuilder.build(); + + // Build an empty resources plugin data + ResourcesPluginData resourcesPluginData = ResourcesPluginData.builder().build(); + + // Build the initial simulation state data -- time starts at zero + SimulationState simulationState = SimulationState.builder().build(); + + /* + * Run the simulation in one day increments until all the plans in the run + * continuity plugin data have been executed + */ + double haltTime = 0; + double maxTime = Double.NEGATIVE_INFINITY; + for (Pair> pair : runContinuityPluginData.getConsumers()) { + Double time = pair.getFirst(); + maxTime = FastMath.max(maxTime, time); + } + double timeIncrement = maxTime / incrementCount; + while (!runContinuityPluginData.allPlansComplete()) { + haltTime += timeIncrement; + + // build the run continuity plugin + Plugin runContinuityPlugin = RunContinuityPlugin.builder()// + .setRunContinuityPluginData(runContinuityPluginData)// + .build(); + + // build the people plugin + Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(peoplePluginData); + + // build the regions plugin + Plugin regionsPlugin = RegionsPlugin.builder().setRegionsPluginData(regionsPluginData).getRegionsPlugin(); + + // build the resources plugin + Plugin resourcesPlugin = ResourcesPlugin.builder().setResourcesPluginData(resourcesPluginData) + .getResourcesPlugin(); + + TestOutputConsumer outputConsumer = new TestOutputConsumer(); + + // execute the simulation so that it produces a people plugin data + Simulation simulation = Simulation.builder()// + .addPlugin(peoplePlugin)// + .addPlugin(regionsPlugin)// + .addPlugin(runContinuityPlugin)// + .addPlugin(resourcesPlugin)// + .setSimulationHaltTime(haltTime)// + .setRecordState(true)// + .setOutputConsumer(outputConsumer)// + .setSimulationState(simulationState)// + .build();// + simulation.execute(); + + // retrieve the people plugin data + peoplePluginData = outputConsumer.getOutputItem(PeoplePluginData.class).get(); + + // retrieve the regions plugin data + regionsPluginData = outputConsumer.getOutputItem(RegionsPluginData.class).get(); + + // retrieve the resources plugin data + resourcesPluginData = outputConsumer.getOutputItem(ResourcesPluginData.class).get(); + + // retrieve the simulation state + simulationState = outputConsumer.getOutputItem(SimulationState.class).get(); + + // retrieve the run continuity plugin data + runContinuityPluginData = outputConsumer.getOutputItem(RunContinuityPluginData.class).get(); + + Optional optional = outputConsumer.getOutputItem(String.class); + if (optional.isPresent()) { + result = optional.get(); + } + + } + + // show that the result is a reasonably long string + assertNotNull(result); + assertTrue(result.length() > 100); + + return result; + + } + + @Test + @UnitTestMethod(target = ResourcesDataManager.class, name = "toString", args = {}) + public void testToString() { + + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7634125044092781695L); + Random random = new Random(randomGenerator.nextLong()); + + //build the people plugin + + PeoplePluginData.Builder peoplePluginDataBuilder = PeoplePluginData.builder(); + for(int i = 0;i<10;i++) { + int id = 2*i+1; + peoplePluginDataBuilder.addPersonRange(new PersonRange(id, id)); + } + PeoplePluginData peoplePluginData = peoplePluginDataBuilder.build(); + Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(peoplePluginData); + + List people = peoplePluginData.getPersonIds(); + + //build the resources plugin + ResourcesPluginData.Builder resourcesPluginDataBuilder = ResourcesPluginData.builder(); + + List selectedTestResourceIds = new ArrayList<>(); + + for (TestResourceId testResourceId : TestResourceId.values()) { + selectedTestResourceIds.add(testResourceId); + } + Collections.shuffle(selectedTestResourceIds, random); + + Set timeTrackedResourceIds = new LinkedHashSet<>(); + + for (TestResourceId testResourceId : selectedTestResourceIds) { + double time = randomGenerator.nextDouble(); + boolean timeTrackingPolicy = randomGenerator.nextBoolean(); + if (timeTrackingPolicy) { + timeTrackedResourceIds.add(testResourceId); + } + resourcesPluginDataBuilder.addResource(testResourceId, time, timeTrackingPolicy); + } + + List selectedTestResourcePropertyIds = new ArrayList<>(); + + for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId.values()) { + selectedTestResourcePropertyIds.add(testResourcePropertyId); + } + + Collections.shuffle(selectedTestResourcePropertyIds, random); + + for (TestResourcePropertyId testResourcePropertyId : selectedTestResourcePropertyIds) { + resourcesPluginDataBuilder.defineResourceProperty(testResourcePropertyId.getTestResourceId(), + testResourcePropertyId, testResourcePropertyId.getPropertyDefinition()); + } + + for (TestResourcePropertyId testResourcePropertyId : selectedTestResourcePropertyIds) { + boolean required = testResourcePropertyId.getPropertyDefinition().getDefaultValue().isEmpty(); + if (required || randomGenerator.nextBoolean()) { + resourcesPluginDataBuilder.setResourcePropertyValue(testResourcePropertyId.getTestResourceId(), + testResourcePropertyId, testResourcePropertyId.getRandomPropertyValue(randomGenerator)); + } + } + + List selectedRegionIds = new ArrayList<>(); + + for (TestRegionId testRegionId : TestRegionId.values()) { + selectedRegionIds.add(testRegionId); + } + Collections.shuffle(selectedRegionIds,random); + + for (TestRegionId testRegionId : selectedRegionIds) { + Collections.shuffle(selectedTestResourceIds,random); + for (TestResourceId testResourceId : selectedTestResourceIds) { + if (randomGenerator.nextBoolean()) { + long value = randomGenerator.nextInt(1000); + resourcesPluginDataBuilder.setRegionResourceLevel(testRegionId, testResourceId, value); + } + } + } + + + + + for (PersonId personId: people) { + Collections.shuffle(selectedTestResourceIds,random); + for (TestResourceId testResourceId : selectedTestResourceIds) { + if (randomGenerator.nextBoolean()) { + long value = randomGenerator.nextInt(5); + resourcesPluginDataBuilder.setPersonResourceLevel(personId, testResourceId, value); + } + if (timeTrackedResourceIds.contains(testResourceId) && randomGenerator.nextBoolean()) { + double time = randomGenerator.nextDouble() + 1.0; + resourcesPluginDataBuilder.setPersonResourceTime(personId, testResourceId, time); + } + } + } + ResourcesPluginData resourcesPluginData = resourcesPluginDataBuilder.build(); + Plugin resourcesPlugin = ResourcesPlugin.builder().setResourcesPluginData(resourcesPluginData) + .getResourcesPlugin(); + + //build the regions plugin + + RegionsPluginData.Builder regionsPluginDataBuilder = RegionsPluginData.builder(); + for(TestRegionId testRegionId : TestRegionId.values()) { + regionsPluginDataBuilder.addRegion(testRegionId); + } + + for(PersonId personId : people) { + regionsPluginDataBuilder.addPerson(personId, TestRegionId.getRandomRegionId(randomGenerator)); + } + + RegionsPluginData regionsPluginData = regionsPluginDataBuilder.build(); + Plugin regionsPlugin = RegionsPlugin.builder().setRegionsPluginData(regionsPluginData).getRegionsPlugin(); + + //generate the stochastics plugin + StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder().setMainRNGState(WellState.builder().setSeed(randomGenerator.nextLong()).build()).build(); + Plugin stochasticsPlugin = StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData); + + //generate the test plugin + TestPluginData.Builder testPluginDataBuilder = TestPluginData.builder(); + testPluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(3,(c)->{ + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + String actualValue = resourcesDataManager.toString(); + //Expected value vaidated by inspection + String expectedValue = "ResourcesDataManager [" + + "resourceDefaultTimes={" + + "RESOURCE_4=0.217564152372538, " + + "RESOURCE_3=0.8830727840178094, " + + "RESOURCE_1=0.33699001981033283, " + + "RESOURCE_2=0.6084446874416862, " + + "RESOURCE_5=0.2584192617810743}, " + + + "resourcePropertyValues={" + + "RESOURCE_2={ResourceProperty_2_2_INTEGER_MUTABLE=-1872626629, ResourceProperty_2_1_BOOLEAN_MUTABLE=true}, " + + "RESOURCE_3={ResourceProperty_3_1_BOOLEAN_MUTABLE=true}, " + + "RESOURCE_5={ResourceProperty_5_1_INTEGER_IMMUTABLE=1952440069}}, " + + + "personResourceLevels={" + + "RESOURCE_1=IntValueContainer [subTypeArray=ByteArray [values=[1=0, 3=0, 5=2, 7=0, 9=0, 11=2, 13=4, 15=0, 17=1, 19=0], defaultValue=0]], " + + "RESOURCE_3=IntValueContainer [subTypeArray=ByteArray [values=[1=1, 3=0, 5=2, 7=4, 9=0, 11=0, 13=4, 15=0, 17=0, 19=0], defaultValue=0]], " + + "RESOURCE_2=IntValueContainer [subTypeArray=ByteArray [values=[1=0, 3=0, 5=3, 7=3, 9=0, 11=0, 13=0, 15=0, 17=0, 19=0], defaultValue=0]], " + + "RESOURCE_5=IntValueContainer [subTypeArray=ByteArray [values=[1=0, 3=0, 5=0, 7=2, 9=0, 11=0, 13=3, 15=0, 17=0, 19=0], defaultValue=0]], " + + "RESOURCE_4=IntValueContainer [subTypeArray=ByteArray [values=[1=0, 3=0, 5=0, 7=0, 9=0, 11=0, 13=2, 15=0, 17=0, 19=1], defaultValue=0]]}, " + + + "resourcePropertyDefinitions={" + + "RESOURCE_2={ResourceProperty_2_2_INTEGER_MUTABLE=PropertyDefinition [type=class java.lang.Integer, propertyValuesAreMutable=true, defaultValue=5], ResourceProperty_2_1_BOOLEAN_MUTABLE=PropertyDefinition [type=class java.lang.Boolean, propertyValuesAreMutable=true, defaultValue=true]}, " + + "RESOURCE_1={ResourceProperty_1_3_DOUBLE_MUTABLE=PropertyDefinition [type=class java.lang.Double, propertyValuesAreMutable=true, defaultValue=0.0], ResourceProperty_1_1_BOOLEAN_MUTABLE=PropertyDefinition [type=class java.lang.Boolean, propertyValuesAreMutable=true, defaultValue=false], ResourceProperty_1_2_INTEGER_MUTABLE=PropertyDefinition [type=class java.lang.Integer, propertyValuesAreMutable=true, defaultValue=0]}, " + + "RESOURCE_3={ResourceProperty_3_1_BOOLEAN_MUTABLE=PropertyDefinition [type=class java.lang.Boolean, propertyValuesAreMutable=true, defaultValue=false], ResourceProperty_3_2_STRING_MUTABLE=PropertyDefinition [type=class java.lang.String, propertyValuesAreMutable=true, defaultValue=]}, " + + "RESOURCE_5={ResourceProperty_5_1_INTEGER_IMMUTABLE=PropertyDefinition [type=class java.lang.Integer, propertyValuesAreMutable=false, defaultValue=7], ResourceProperty_5_2_DOUBLE_IMMUTABLE=PropertyDefinition [type=class java.lang.Double, propertyValuesAreMutable=false, defaultValue=2.7]}, " + + "RESOURCE_4={ResourceProperty_4_1_BOOLEAN_MUTABLE=PropertyDefinition [type=class java.lang.Boolean, propertyValuesAreMutable=true, defaultValue=true]}}, " + + + "resourceTimeTrackingPolicies={" + + "RESOURCE_4=false, " + + "RESOURCE_3=true, " + + "RESOURCE_1=true, " + + "RESOURCE_2=true, " + + "RESOURCE_5=true}, " + + + "personResourceTimes={" + + "RESOURCE_1=DoubleValueContainer [values=[1=1.4101040879057936, 3=0.33699001981033283, 5=0.33699001981033283, 7=1.671063001388121, 9=1.8031941854939413, 11=1.17923883714124, 13=0.33699001981033283, 15=0.33699001981033283, 17=0.33699001981033283, 19=0.33699001981033283], defaultValue=0.33699001981033283], " + + "RESOURCE_3=DoubleValueContainer [values=[1=1.1385628625362318, 3=0.8830727840178094, 5=1.991978622923995, 7=0.8830727840178094, 9=1.3511645237538967, 11=0.8830727840178094, 13=1.4140228511404762, 15=1.1216048964167127, 17=0.8830727840178094, 19=0.8830727840178094], defaultValue=0.8830727840178094], " + + "RESOURCE_5=DoubleValueContainer [values=[1=0.2584192617810743, 3=1.200541444517943, 5=0.2584192617810743, 7=0.2584192617810743, 9=1.543391403912596, 11=0.2584192617810743, 13=0.2584192617810743, 15=1.9944181030137489, 17=0.2584192617810743, 19=1.542836102920688], defaultValue=0.2584192617810743], " + + "RESOURCE_2=DoubleValueContainer [values=[1=0.6084446874416862, 3=0.6084446874416862, 5=1.413709876061411, 7=1.6431364543995068, 9=1.4748673882603254, 11=0.6084446874416862, 13=1.9264298147324626, 15=1.3054658380301465, 17=0.6084446874416862, 19=0.6084446874416862], defaultValue=0.6084446874416862]}, " + + + "regionResources={" + + "REGION_3={RESOURCE_4=MutableLong [value=973], RESOURCE_1=MutableLong [value=10], RESOURCE_3=MutableLong [value=216], RESOURCE_2=MutableLong [value=267]}, " + + "REGION_4={RESOURCE_3=MutableLong [value=22], RESOURCE_5=MutableLong [value=720], RESOURCE_1=MutableLong [value=705], RESOURCE_2=MutableLong [value=877]}, " + + "REGION_2={RESOURCE_1=MutableLong [value=121], RESOURCE_4=MutableLong [value=216], RESOURCE_2=MutableLong [value=244], RESOURCE_3=MutableLong [value=502]}, " + + "REGION_1={RESOURCE_4=MutableLong [value=0], RESOURCE_5=MutableLong [value=719]}, " + + "REGION_5={RESOURCE_4=MutableLong [value=954], RESOURCE_2=MutableLong [value=598], RESOURCE_5=MutableLong [value=347], RESOURCE_1=MutableLong [value=865]}, " + + "REGION_6={RESOURCE_2=MutableLong [value=868], RESOURCE_3=MutableLong [value=831], RESOURCE_5=MutableLong [value=643]}}]"; + + assertEquals(expectedValue, actualValue); + + })); + TestPluginData testPluginData = testPluginDataBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + + SimulationState simulationState = SimulationState.builder().setStartTime(2).build(); + + TestSimulation.builder()// + .addPlugin(resourcesPlugin)// + .addPlugin(peoplePlugin)// + .addPlugin(regionsPlugin)// + .addPlugin(stochasticsPlugin)// + .addPlugin(testPlugin)// + .setSimulationState(simulationState)// + .build()// + .execute(); + + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/datamanagers/AT_ResourcesPluginData.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/datamanagers/AT_ResourcesPluginData.java new file mode 100644 index 000000000..8fc6064a7 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/datamanagers/AT_ResourcesPluginData.java @@ -0,0 +1,1482 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.datamanagers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionError; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.TestRegionId; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceError; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourcePropertyId; +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.TestResourceId; +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.TestResourcePropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; +import util.wrappers.MultiKey; + +public final class AT_ResourcesPluginData { + + @Test + @UnitTestMethod(target = ResourcesPluginData.class, name = "getPersonResourceTimes", args = {}) + public void testGetPersonResourceTimes() { + Map> expectedResourceTimes = new LinkedHashMap<>(); + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2121375123528875466L); + + // add the resources + ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); + for (TestResourceId testResourceId : TestResourceId.values()) { + builder.addResource(testResourceId, 0.0, true); + } + + // add up to 30 people + Set people = new LinkedHashSet<>(); + int id = 0; + for (int i = 0; i < 30; i++) { + id += randomGenerator.nextInt(3) + 1; + people.add(new PersonId(id)); + } + assertTrue(people.size() > 20); + for (PersonId personId : people) { + for (TestResourceId testResourceId : TestResourceId.values()) { + if (randomGenerator.nextBoolean()) { + long amount = randomGenerator.nextInt(10); + double time = randomGenerator.nextDouble(); + builder.setPersonResourceLevel(personId, testResourceId, amount); + builder.setPersonResourceTime(personId, testResourceId, time); + List list = expectedResourceTimes.get(testResourceId); + if (list == null) { + list = new ArrayList<>(); + expectedResourceTimes.put(testResourceId, list); + } + while (list.size() < personId.getValue()) { + list.add(null); + } + list.add(time); + } + } + } + + ResourcesPluginData resourcesPluginData = builder.build(); + + Map> actualResourceTimes = resourcesPluginData.getPersonResourceTimes(); + + assertEquals(expectedResourceTimes, actualResourceTimes); + + } + + @Test + @UnitTestMethod(target = ResourcesPluginData.class, name = "getPersonResourceTimes", args = { ResourceId.class }) + public void testGetPersonResourceTimes_ResourceId() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2121375123528875466L); + + // add the resources + ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); + for (TestResourceId testResourceId : TestResourceId.values()) { + builder.addResource(testResourceId, 0.0, true); + } + + Set expectedValues = new LinkedHashSet<>(); + + // add up to 30 people + Set people = new LinkedHashSet<>(); + int id = 0; + for (int i = 0; i < 30; i++) { + id += randomGenerator.nextInt(3) + 1; + people.add(new PersonId(id)); + } + assertTrue(people.size() > 20); + for (PersonId personId : people) { + for (TestResourceId testResourceId : TestResourceId.values()) { + if (randomGenerator.nextBoolean()) { + long amount = randomGenerator.nextInt(10); + double time = randomGenerator.nextDouble(); + builder.setPersonResourceLevel(personId, testResourceId, amount); + builder.setPersonResourceTime(personId, testResourceId, time); + MultiKey multiKey = new MultiKey(personId, testResourceId, time); + expectedValues.add(multiKey); + } + } + } + + ResourcesPluginData resourceInitialData = builder.build(); + + Set actualValues = new LinkedHashSet<>(); + for (TestResourceId testResourceId : TestResourceId.values()) { + List personResourceTimes = resourceInitialData.getPersonResourceTimes(testResourceId); + for (int i = 0; i < personResourceTimes.size(); i++) { + Double time = personResourceTimes.get(i); + if (time != null) { + MultiKey multiKey = new MultiKey(new PersonId(i), testResourceId, time); + actualValues.add(multiKey); + } + } + } + + assertEquals(expectedValues, actualValues); + + // precondition test: if the person id is null + ContractException contractException = assertThrows(ContractException.class, + () -> resourceInitialData.getPersonResourceTimes(null)); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = ResourcesPluginData.class, name = "getResourceDefaultTimes", args = {}) + public void testGetResourceDefaultTimes() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(9133618222677631125L); + + Map expectedResourceDefaultTimes = new LinkedHashMap<>(); + + // add the resources + ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); + for (TestResourceId testResourceId : TestResourceId.values()) { + double time = randomGenerator.nextDouble(); + builder.addResource(testResourceId, time, false); + expectedResourceDefaultTimes.put(testResourceId, time); + } + + ResourcesPluginData resourceInitialData = builder.build(); + + Set actualValues = new LinkedHashSet<>(); + for (ResourceId resourceId : resourceInitialData.getResourceIds()) { + Double time = resourceInitialData.getResourceDefaultTime(resourceId); + actualValues.add(new MultiKey(resourceId, time)); + } + + Map actualResourceDefaultTimes = resourceInitialData.getResourceDefaultTimes(); + assertEquals(expectedResourceDefaultTimes, actualResourceDefaultTimes); + } + + @Test + @UnitTestMethod(target = ResourcesPluginData.class, name = "getResourceDefaultTime", args = { ResourceId.class }) + public void testGetResourceDefaultTime() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(9133618222677631125L); + Set expectedValues = new LinkedHashSet<>(); + // add the resources + ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); + for (TestResourceId testResourceId : TestResourceId.values()) { + double time = randomGenerator.nextDouble(); + builder.addResource(testResourceId, time, false); + expectedValues.add(new MultiKey(testResourceId, time)); + } + + ResourcesPluginData resourceInitialData = builder.build(); + + Set actualValues = new LinkedHashSet<>(); + for (ResourceId resourceId : resourceInitialData.getResourceIds()) { + Double time = resourceInitialData.getResourceDefaultTime(resourceId); + actualValues.add(new MultiKey(resourceId, time)); + } + + assertEquals(expectedValues, actualValues); + + // precondition test: if the person id is null + ContractException contractException = assertThrows(ContractException.class, + () -> resourceInitialData.getResourceDefaultTime(null)); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ResourcesPluginData.class, name = "hashCode", args = {}) + public void testHashCode() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7567353570953948981L); + + // equal objects have equal hash codes + for (int i = 0; i < 10; i++) { + long seed = randomGenerator.nextLong(); + ResourcesPluginData rpd1 = getRandomResourcesPluginData(seed); + ResourcesPluginData rpd2 = getRandomResourcesPluginData(seed); + + assertEquals(rpd1, rpd2); + assertEquals(rpd1.hashCode(), rpd2.hashCode()); + } + int count = 100; + Set hashCodes = new LinkedHashSet<>(); + for (int i = 0; i < count; i++) { + long seed = randomGenerator.nextLong(); + ResourcesPluginData rpd = getRandomResourcesPluginData(seed); + hashCodes.add(rpd.hashCode()); + } + int minimumCount = count * 9 / 10; + assertTrue(hashCodes.size() > minimumCount); + } + + /* + * Returns a randomly generated resources plugin data + */ + private ResourcesPluginData getRandomResourcesPluginData(long seed) { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + ResourcesPluginData.Builder pluginDataBuilder = ResourcesPluginData.builder(); + + Set selectedTestResourceIds = new LinkedHashSet<>(); + + for (TestResourceId testResourceId : TestResourceId.values()) { + if (randomGenerator.nextDouble() < 0.8) { + selectedTestResourceIds.add(testResourceId); + } + } + + Set timeTrackedResourceIds = new LinkedHashSet<>(); + + for (TestResourceId testResourceId : selectedTestResourceIds) { + double time = randomGenerator.nextDouble(); + boolean timeTrackingPolicy = randomGenerator.nextBoolean(); + if (timeTrackingPolicy) { + timeTrackedResourceIds.add(testResourceId); + } + pluginDataBuilder.addResource(testResourceId, time, timeTrackingPolicy); + } + + Set selectedTestResourcePropertyIds = new LinkedHashSet<>(); + + for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId.values()) { + TestResourceId testResourceId = testResourcePropertyId.getTestResourceId(); + if (selectedTestResourceIds.contains(testResourceId)) { + if (randomGenerator.nextDouble() < 0.8) { + selectedTestResourcePropertyIds.add(testResourcePropertyId); + } + } + } + + for (TestResourcePropertyId testResourcePropertyId : selectedTestResourcePropertyIds) { + pluginDataBuilder.defineResourceProperty(testResourcePropertyId.getTestResourceId(), testResourcePropertyId, + testResourcePropertyId.getPropertyDefinition()); + } + + for (TestResourcePropertyId testResourcePropertyId : selectedTestResourcePropertyIds) { + if (randomGenerator.nextBoolean()) { + pluginDataBuilder.setResourcePropertyValue(testResourcePropertyId.getTestResourceId(), + testResourcePropertyId, testResourcePropertyId.getRandomPropertyValue(randomGenerator)); + } + } + + Set selectedRegionIds = new LinkedHashSet<>(); + + for (TestRegionId testRegionId : TestRegionId.values()) { + if (randomGenerator.nextDouble() < 0.8) { + selectedRegionIds.add(testRegionId); + } + } + + for (TestRegionId testRegionId : selectedRegionIds) { + for (TestResourceId testResourceId : selectedTestResourceIds) { + if (randomGenerator.nextBoolean()) { + long value = randomGenerator.nextInt(1000); + pluginDataBuilder.setRegionResourceLevel(testRegionId, testResourceId, value); + } + } + } + + int personCount = randomGenerator.nextInt(5) + 5; + + for (int i = 0; i < personCount; i++) { + PersonId personId = new PersonId(i * i); + for (TestResourceId testResourceId : selectedTestResourceIds) { + if (randomGenerator.nextBoolean()) { + long value = randomGenerator.nextInt(5); + pluginDataBuilder.setPersonResourceLevel(personId, testResourceId, value); + } + if (timeTrackedResourceIds.contains(testResourceId) && randomGenerator.nextBoolean()) { + double time = randomGenerator.nextDouble() + 1.0; + pluginDataBuilder.setPersonResourceTime(personId, testResourceId, time); + } + } + } + + return pluginDataBuilder.build(); + } + + @Test + @UnitTestMethod(target = ResourcesPluginData.class, name = "equals", args = { Object.class }) + public void testEquals() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1110078478105073449L); + + // null equality + for (int i = 0; i < 10; i++) { + long seed = randomGenerator.nextLong(); + ResourcesPluginData rpd = getRandomResourcesPluginData(seed); + assertFalse(rpd.equals(null)); + } + + // reflexivity + for (int i = 0; i < 10; i++) { + long seed = randomGenerator.nextLong(); + ResourcesPluginData rpd = getRandomResourcesPluginData(seed); + assertEquals(rpd, rpd); + } + + // symmetry + for (int i = 0; i < 10; i++) { + long seed = randomGenerator.nextLong(); + ResourcesPluginData rpd1 = getRandomResourcesPluginData(seed); + ResourcesPluginData rpd2 = getRandomResourcesPluginData(seed); + assertEquals(rpd1, rpd2); + assertEquals(rpd2, rpd1); + } + + // transitivity -- implied by symmetry + + } + + @Test + @UnitTestMethod(target = ResourcesPluginData.Builder.class, name = "setPersonResourceTime", args = { PersonId.class, + ResourceId.class, Double.class }) + public void testSetPersonResourceTime() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2121375123528875466L); + + // add the resources + ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); + for (TestResourceId testResourceId : TestResourceId.values()) { + builder.addResource(testResourceId, 0.0, true); + } + + Set expectedValues = new LinkedHashSet<>(); + + // add up to 30 people + Set people = new LinkedHashSet<>(); + int id = 0; + for (int i = 0; i < 30; i++) { + id += randomGenerator.nextInt(3) + 1; + people.add(new PersonId(id)); + } + assertTrue(people.size() > 20); + for (PersonId personId : people) { + for (TestResourceId testResourceId : TestResourceId.values()) { + if (randomGenerator.nextBoolean()) { + long amount = randomGenerator.nextInt(10); + double time = randomGenerator.nextDouble(); + builder.setPersonResourceLevel(personId, testResourceId, amount); + builder.setPersonResourceTime(personId, testResourceId, time); + MultiKey multiKey = new MultiKey(personId, testResourceId, time); + expectedValues.add(multiKey); + } + } + } + + ResourcesPluginData resourceInitialData = builder.build(); + + Set actualValues = new LinkedHashSet<>(); + for (TestResourceId testResourceId : TestResourceId.values()) { + List personResourceTimes = resourceInitialData.getPersonResourceTimes(testResourceId); + for (int i = 0; i < personResourceTimes.size(); i++) { + Double time = personResourceTimes.get(i); + if (time != null) { + MultiKey multiKey = new MultiKey(new PersonId(i), testResourceId, time); + actualValues.add(multiKey); + } + } + } + + assertEquals(expectedValues, actualValues); + + // precondition test: if the person id is null + ContractException contractException = assertThrows(ContractException.class, + () -> ResourcesPluginData.builder().setPersonResourceTime(null, TestResourceId.RESOURCE_1, 0.0)); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + // precondition test: if the resource id is null + contractException = assertThrows(ContractException.class, + () -> ResourcesPluginData.builder().setPersonResourceTime(new PersonId(0), null, 0.0)); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + // precondition test: if the time is null + contractException = assertThrows(ContractException.class, () -> ResourcesPluginData.builder() + .setPersonResourceTime(new PersonId(0), TestResourceId.RESOURCE_1, null)); + assertEquals(ResourceError.NULL_TIME, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = ResourcesPluginData.class, name = "builder", args = {}) + public void testBuilder() { + assertNotNull(ResourcesPluginData.builder()); + } + + @Test + @UnitTestMethod(target = ResourcesPluginData.Builder.class, name = "build", args = {}) + public void testBuild() { + + assertNotNull(ResourcesPluginData.builder().build()); + + // precondition tests + + /* + * if a resource property definition was collected for a resource that was not + * added + */ + ContractException contractException = assertThrows(ContractException.class, () -> { + ResourceId resourceId = TestResourceId.RESOURCE_1; + ResourcePropertyId resourcePropertyId = TestResourcePropertyId.ResourceProperty_1_1_BOOLEAN_MUTABLE; + PropertyDefinition propertyDefinition = TestResourcePropertyId.ResourceProperty_1_1_BOOLEAN_MUTABLE + .getPropertyDefinition(); + ResourcesPluginData.builder().defineResourceProperty(resourceId, resourcePropertyId, propertyDefinition) + .build(); + });// + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + + /* + * if a resource property value was collected for a resource that was not added + */ + contractException = assertThrows(ContractException.class, () -> { + ResourceId resourceId = TestResourceId.RESOURCE_1; + ResourcePropertyId resourcePropertyId = TestResourcePropertyId.ResourceProperty_1_1_BOOLEAN_MUTABLE; + Boolean value = false; + ResourcesPluginData.builder().setResourcePropertyValue(resourceId, resourcePropertyId, value).build(); + });// + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + + /* + * if a resource property value was collected for a resource property that is + * not associated with the given resource id + */ + contractException = assertThrows(ContractException.class, () -> { + ResourceId resourceId = TestResourceId.RESOURCE_1; + ResourcePropertyId resourcePropertyId = TestResourcePropertyId.ResourceProperty_1_1_BOOLEAN_MUTABLE; + Boolean value = false; + ResourcesPluginData.builder()// + .addResource(resourceId, 0.0, false)// + .setResourcePropertyValue(resourceId, resourcePropertyId, value)// + .build();// + });// + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + /* + * if a resource property value was collected for a resource property that is + * not compatible with the associated resource property definition + */ + contractException = assertThrows(ContractException.class, () -> { + ResourceId resourceId = TestResourceId.RESOURCE_1; + ResourcePropertyId resourcePropertyId = TestResourcePropertyId.ResourceProperty_1_1_BOOLEAN_MUTABLE; + PropertyDefinition propertyDefinition = TestResourcePropertyId.ResourceProperty_1_1_BOOLEAN_MUTABLE + .getPropertyDefinition(); + Integer value = 5; + ResourcesPluginData.builder()// + .addResource(resourceId, 0.0, false)// + .defineResourceProperty(resourceId, resourcePropertyId, propertyDefinition)// + .setResourcePropertyValue(resourceId, resourcePropertyId, value)// + .build(); + });// + assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); + + /* + * if a resource property definition has a null default value and there is no + * assigned resource property value for that resource + */ + contractException = assertThrows(ContractException.class, () -> { + ResourceId resourceId = TestResourceId.RESOURCE_1; + ResourcePropertyId resourcePropertyId = TestResourcePropertyId.ResourceProperty_1_1_BOOLEAN_MUTABLE; + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Boolean.class).build(); + ResourcesPluginData.builder()// + .addResource(resourceId, 0.0, false)// + .defineResourceProperty(resourceId, resourcePropertyId, propertyDefinition)// + .build(); + });// + assertEquals(PropertyError.INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT, contractException.getErrorType()); + /* + * if a resource level was collected for a person that is an unknown resource id + */ + contractException = assertThrows(ContractException.class, () -> { + ResourceId resourceId = TestResourceId.RESOURCE_1; + Long value = 566L; + ResourcesPluginData.builder().setPersonResourceLevel(new PersonId(0), resourceId, value).build(); + });// + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + + /* + * if a resource level was collected for a region that is an unknown resource id + */ + contractException = assertThrows(ContractException.class, () -> { + ResourceId resourceId = TestResourceId.RESOURCE_1; + Long value = 566L; + ResourcesPluginData.builder().setRegionResourceLevel(TestRegionId.REGION_1, resourceId, value).build(); + });// + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ResourcesPluginData.Builder.class, name = "addResource", args = { ResourceId.class, + Double.class, boolean.class }) + public void testAddResource() { + + ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); + Set expectedResourceIds = new LinkedHashSet<>(); + for (TestResourceId testResourceId : TestResourceId.values()) { + + // replacing data to show that the value persists + builder.addResource(testResourceId, 0.0, false); + // adding duplicate data to show that the value persists + builder.addResource(testResourceId, 0.0, false); + expectedResourceIds.add(testResourceId); + } + ResourcesPluginData resourceInitialData = builder.build(); + assertEquals(expectedResourceIds, resourceInitialData.getResourceIds()); + + // precondition tests + + // if the resource id is null + ContractException contractException = assertThrows(ContractException.class, + () -> ResourcesPluginData.builder().addResource(null, 0.0, false)); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ResourcesPluginData.Builder.class, name = "defineResourceProperty", args = { + ResourceId.class, ResourcePropertyId.class, PropertyDefinition.class }) + public void testDefineResourceProperty() { + + ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); + for (TestResourceId testResourceId : TestResourceId.values()) { + builder.addResource(testResourceId, 0.0, false); + Set testResourcePropertyIds = TestResourcePropertyId + .getTestResourcePropertyIds(testResourceId); + for (TestResourcePropertyId testResourcePropertyId : testResourcePropertyIds) { + PropertyDefinition propertyDefinition = testResourcePropertyId.getPropertyDefinition(); + PropertyDefinition propertyDefinition2 = testResourcePropertyId.next().getPropertyDefinition(); + builder.defineResourceProperty(testResourceId, testResourcePropertyId, propertyDefinition2); + // replacing data to show that the value persists + builder.defineResourceProperty(testResourceId, testResourcePropertyId, propertyDefinition); + // adding duplicate data to show that values persist + builder.defineResourceProperty(testResourceId, testResourcePropertyId, propertyDefinition); + } + } + + ResourcesPluginData resourceInitialData = builder.build(); + + for (TestResourceId testResourceId : TestResourceId.values()) { + Set testResourcePropertyIds = TestResourcePropertyId + .getTestResourcePropertyIds(testResourceId); + for (TestResourcePropertyId testResourcePropertyId : testResourcePropertyIds) { + PropertyDefinition expectedPropertyDefinition = testResourcePropertyId.getPropertyDefinition(); + PropertyDefinition actualPropertyDefinition = resourceInitialData + .getResourcePropertyDefinition(testResourceId, testResourcePropertyId); + assertEquals(expectedPropertyDefinition, actualPropertyDefinition); + } + } + + // precondition tests + + ResourceId resourceId = TestResourceId.RESOURCE_2; + ResourcePropertyId resourcePropertyId = TestResourcePropertyId.ResourceProperty_2_2_INTEGER_MUTABLE; + PropertyDefinition propertyDefinition = TestResourcePropertyId.ResourceProperty_2_2_INTEGER_MUTABLE + .getPropertyDefinition(); + + // if the resource id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + ResourcesPluginData.builder().defineResourceProperty(null, resourcePropertyId, propertyDefinition); + }); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + // if the resource property id is null + contractException = assertThrows(ContractException.class, () -> { + ResourcesPluginData.builder().defineResourceProperty(resourceId, null, propertyDefinition); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // if the property definition is null + contractException = assertThrows(ContractException.class, () -> { + ResourcesPluginData.builder().defineResourceProperty(resourceId, resourcePropertyId, null); + }); + assertEquals(PropertyError.NULL_PROPERTY_DEFINITION, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = ResourcesPluginData.Builder.class, name = "setPersonResourceLevel", args = { + PersonId.class, ResourceId.class, long.class }) + public void testSetPersonResourceLevel() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6539895160899665826L); + + // add the resources + ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); + for (TestResourceId testResourceId : TestResourceId.values()) { + builder.addResource(testResourceId, 0.0, false); + } + + Set expectedValues = new LinkedHashSet<>(); + + // add up to 30 people + int id = 0; + Set people = new LinkedHashSet<>(); + + for (int i = 0; i < 30; i++) { + id += randomGenerator.nextInt(3) + 1; + people.add(new PersonId(id)); + } + assertTrue(people.size() > 20); + for (PersonId personId : people) { + for (TestResourceId testResourceId : TestResourceId.values()) { + if (randomGenerator.nextBoolean()) { + long amount = randomGenerator.nextInt(10); + builder.setPersonResourceLevel(personId, testResourceId, amount + 1); + // replacing data to show that the value persists + builder.setPersonResourceLevel(personId, testResourceId, amount); + // adding duplicate data to show that the value persists + builder.setPersonResourceLevel(personId, testResourceId, amount); + MultiKey multiKey = new MultiKey(personId, testResourceId, amount); + expectedValues.add(multiKey); + } + } + } + + ResourcesPluginData resourceInitialData = builder.build(); + + Set actualValues = new LinkedHashSet<>(); + for (TestResourceId testResourceId : TestResourceId.values()) { + List personResourceLevels = resourceInitialData.getPersonResourceLevels(testResourceId); + for (int i = 0; i < personResourceLevels.size(); i++) { + Long amount = personResourceLevels.get(i); + if (amount != null) { + MultiKey multiKey = new MultiKey(new PersonId(i), testResourceId, amount); + actualValues.add(multiKey); + } + } + } + + assertEquals(expectedValues, actualValues); + + // precondition tests + PersonId personId = new PersonId(0); + ResourceId resourceId = TestResourceId.RESOURCE_5; + long amount = 678; + + // if the person id is null + ContractException contractException = assertThrows(ContractException.class, + () -> builder.setPersonResourceLevel(null, resourceId, amount)); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + // if the resource id is null + contractException = assertThrows(ContractException.class, + () -> builder.setPersonResourceLevel(personId, null, amount)); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + // if the resource amount is negative + contractException = assertThrows(ContractException.class, + () -> builder.setPersonResourceLevel(personId, resourceId, -5L)); + assertEquals(ResourceError.NEGATIVE_RESOURCE_AMOUNT, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = ResourcesPluginData.Builder.class, name = "setRegionResourceLevel", args = { + RegionId.class, ResourceId.class, long.class }) + public void testSetRegionResourceLevel() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8877834706249831995L); + + // add the resources + ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); + for (TestResourceId testResourceId : TestResourceId.values()) { + builder.addResource(testResourceId, 0.0, false); + } + + Map expectedValues = new LinkedHashMap<>(); + + for (TestRegionId testRegionId : TestRegionId.values()) { + for (TestResourceId testResourceId : TestResourceId.values()) { + if (randomGenerator.nextBoolean()) { + long amount = randomGenerator.nextInt(10); + builder.setRegionResourceLevel(testRegionId, testResourceId, amount); + MultiKey multiKey = new MultiKey(testRegionId, testResourceId); + expectedValues.put(multiKey, amount); + } + } + } + + Map actualValues = new LinkedHashMap<>(); + + ResourcesPluginData resourceInitialData = builder.build(); + + for (TestRegionId testRegionId : TestRegionId.values()) { + + for (TestResourceId testResourceId : TestResourceId.values()) { + Optional optional = resourceInitialData.getRegionResourceLevel(testRegionId, testResourceId); + if (optional.isPresent()) { + Long amount = optional.get(); + MultiKey multiKey = new MultiKey(testRegionId, testResourceId); + actualValues.put(multiKey, amount); + } + } + + } + + assertEquals(expectedValues, actualValues); + + // precondition tests + RegionId regionId = TestRegionId.REGION_3; + ResourceId resourceId = TestResourceId.RESOURCE_5; + long amount = 678; + + // if the region id is null + ContractException contractException = assertThrows(ContractException.class, + () -> builder.setRegionResourceLevel(null, resourceId, amount)); + assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); + + // if the resource id is null + contractException = assertThrows(ContractException.class, + () -> builder.setRegionResourceLevel(regionId, null, amount)); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + // if the resource amount is negative + contractException = assertThrows(ContractException.class, + () -> builder.setRegionResourceLevel(regionId, resourceId, -5L)); + assertEquals(ResourceError.NEGATIVE_RESOURCE_AMOUNT, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = ResourcesPluginData.Builder.class, name = "setResourcePropertyValue", args = { + ResourceId.class, ResourcePropertyId.class, Object.class }) + public void testSetResourcePropertyValue() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7516798209205913252L); + + Map expectedValues = new LinkedHashMap<>(); + + ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); + for (TestResourceId testResourceId : TestResourceId.values()) { + builder.addResource(testResourceId, 0.0, false); + Set testResourcePropertyIds = TestResourcePropertyId + .getTestResourcePropertyIds(testResourceId); + for (TestResourcePropertyId testResourcePropertyId : testResourcePropertyIds) { + MultiKey multiKey = new MultiKey(testResourceId, testResourcePropertyId); + PropertyDefinition propertyDefinition = testResourcePropertyId.getPropertyDefinition(); + builder.defineResourceProperty(testResourceId, testResourcePropertyId, propertyDefinition); + if (randomGenerator.nextBoolean()) { + Object value = testResourcePropertyId.getRandomPropertyValue(randomGenerator); + Object value2 = testResourcePropertyId.getRandomPropertyValue(randomGenerator); + if (value instanceof Boolean) { + value2 = !(Boolean) value; + } + builder.setResourcePropertyValue(testResourceId, testResourcePropertyId, value2); + // replacing data to show that the value persists + builder.setResourcePropertyValue(testResourceId, testResourcePropertyId, value); + // adding duplicate data to show that the value persists + builder.setResourcePropertyValue(testResourceId, testResourcePropertyId, value); + expectedValues.put(multiKey, value); + } + } + } + + ResourcesPluginData resourceInitialData = builder.build(); + + for (TestResourceId testResourceId : TestResourceId.values()) { + Set testResourcePropertyIds = TestResourcePropertyId + .getTestResourcePropertyIds(testResourceId); + for (TestResourcePropertyId testResourcePropertyId : testResourcePropertyIds) { + MultiKey multiKey = new MultiKey(testResourceId, testResourcePropertyId); + Object expectedValue = expectedValues.get(multiKey); + Optional optional = resourceInitialData.getResourcePropertyValue(testResourceId, + testResourcePropertyId); + if (expectedValue == null) { + assertTrue(optional.isEmpty()); + } else { + assertEquals(expectedValue, optional.get()); + } + + } + } + + // precondition tests + ResourceId resourceId = TestResourceId.RESOURCE_3; + ResourcePropertyId resourcePropertyId = TestResourcePropertyId.ResourceProperty_2_2_INTEGER_MUTABLE; + + // if the resource id is null + ContractException contractException = assertThrows(ContractException.class, + () -> builder.setResourcePropertyValue(null, resourcePropertyId, 5)); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + // if the resource property id is null + contractException = assertThrows(ContractException.class, + () -> builder.setResourcePropertyValue(resourceId, null, 5)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // if the resource property value is null + contractException = assertThrows(ContractException.class, + () -> builder.setResourcePropertyValue(resourceId, null, 5)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = ResourcesPluginData.class, name = "getResourcePropertyDefinitions", args = {}) + public void testGetResourcePropertyDefinitions() { + + Map> expectedDefinitions = new LinkedHashMap<>(); + + ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); + for (TestResourceId testResourceId : TestResourceId.values()) { + builder.addResource(testResourceId, 0.0, false); + Set testResourcePropertyIds = TestResourcePropertyId + .getTestResourcePropertyIds(testResourceId); + for (TestResourcePropertyId testResourcePropertyId : testResourcePropertyIds) { + PropertyDefinition propertyDefinition = testResourcePropertyId.getPropertyDefinition(); + builder.defineResourceProperty(testResourceId, testResourcePropertyId, propertyDefinition); + Map map = expectedDefinitions.get(testResourceId); + if (map == null) { + map = new LinkedHashMap<>(); + expectedDefinitions.put(testResourceId, map); + } + map.put(testResourcePropertyId, propertyDefinition); + } + } + ResourcesPluginData resourcesPluginData = builder.build(); + + Map> actualDefinitions = resourcesPluginData + .getResourcePropertyDefinitions(); + + assertEquals(expectedDefinitions, actualDefinitions); + } + + @Test + @UnitTestMethod(target = ResourcesPluginData.class, name = "getResourcePropertyDefinition", args = { + ResourceId.class, ResourcePropertyId.class }) + public void testGetResourcePropertyDefinition() { + + ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); + for (TestResourceId testResourceId : TestResourceId.values()) { + builder.addResource(testResourceId, 0.0, false); + Set testResourcePropertyIds = TestResourcePropertyId + .getTestResourcePropertyIds(testResourceId); + for (TestResourcePropertyId testResourcePropertyId : testResourcePropertyIds) { + PropertyDefinition propertyDefinition = testResourcePropertyId.getPropertyDefinition(); + builder.defineResourceProperty(testResourceId, testResourcePropertyId, propertyDefinition); + } + } + + ResourcesPluginData resourceInitialData = builder.build(); + + for (TestResourceId testResourceId : TestResourceId.values()) { + Set testResourcePropertyIds = TestResourcePropertyId + .getTestResourcePropertyIds(testResourceId); + for (TestResourcePropertyId testResourcePropertyId : testResourcePropertyIds) { + PropertyDefinition expectedPropertyDefinition = testResourcePropertyId.getPropertyDefinition(); + PropertyDefinition actualPropertyDefinition = resourceInitialData + .getResourcePropertyDefinition(testResourceId, testResourcePropertyId); + assertEquals(expectedPropertyDefinition, actualPropertyDefinition); + } + } + + // precondition tests + + // if the resource id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + resourceInitialData.getResourcePropertyDefinition(null, + TestResourcePropertyId.ResourceProperty_1_1_BOOLEAN_MUTABLE); + }); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + // if the resource id is unknown + contractException = assertThrows(ContractException.class, () -> { + resourceInitialData.getResourcePropertyDefinition(TestResourceId.getUnknownResourceId(), + TestResourcePropertyId.ResourceProperty_1_1_BOOLEAN_MUTABLE); + }); + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + + // if the resource property id is null + contractException = assertThrows(ContractException.class, () -> { + resourceInitialData.getResourcePropertyDefinition(TestResourceId.RESOURCE_1, null); + }); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // if the resource property id is unknown + contractException = assertThrows(ContractException.class, () -> { + resourceInitialData.getResourcePropertyDefinition(TestResourceId.RESOURCE_1, + TestResourcePropertyId.getUnknownResourcePropertyId()); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, () -> { + resourceInitialData.getResourcePropertyDefinition(TestResourceId.RESOURCE_1, + TestResourcePropertyId.ResourceProperty_2_2_INTEGER_MUTABLE); + }); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ResourcesPluginData.class, name = "getResourcePropertyIds", args = { ResourceId.class }) + public void testGetResourcePropertyIds() { + // 7475098698397765251L + ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); + for (TestResourceId testResourceId : TestResourceId.values()) { + builder.addResource(testResourceId, 0.0, false); + Set testResourcePropertyIds = TestResourcePropertyId + .getTestResourcePropertyIds(testResourceId); + for (TestResourcePropertyId testResourcePropertyId : testResourcePropertyIds) { + PropertyDefinition propertyDefinition = testResourcePropertyId.getPropertyDefinition(); + builder.defineResourceProperty(testResourceId, testResourcePropertyId, propertyDefinition); + } + } + + ResourcesPluginData resourceInitialData = builder.build(); + + for (TestResourceId testResourceId : TestResourceId.values()) { + + Set expectedResourcePropertyIds = TestResourcePropertyId + .getTestResourcePropertyIds(testResourceId); + Set actualResourcePropertyIds = resourceInitialData + .getResourcePropertyIds(testResourceId); + assertEquals(expectedResourcePropertyIds, actualResourcePropertyIds); + } + + // precondition tests + + // if the resource id is null + ContractException contractException = assertThrows(ContractException.class, + () -> resourceInitialData.getResourcePropertyIds(null)); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + // if the resource id is unknown + contractException = assertThrows(ContractException.class, + () -> resourceInitialData.getResourcePropertyIds(TestResourceId.getUnknownResourceId())); + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ResourcesPluginData.class, name = "getResourcePropertyValues", args = {}) + public void testGetResourcePropertyValues() { + + Map> expectedPropertyValues = new LinkedHashMap<>(); + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6566464377962300906L); + + ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); + for (TestResourceId testResourceId : TestResourceId.values()) { + builder.addResource(testResourceId, 0.0, false); + Set testResourcePropertyIds = TestResourcePropertyId + .getTestResourcePropertyIds(testResourceId); + for (TestResourcePropertyId testResourcePropertyId : testResourcePropertyIds) { + PropertyDefinition propertyDefinition = testResourcePropertyId.getPropertyDefinition(); + builder.defineResourceProperty(testResourceId, testResourcePropertyId, propertyDefinition); + if (randomGenerator.nextBoolean()) { + Object value = testResourcePropertyId.getRandomPropertyValue(randomGenerator); + builder.setResourcePropertyValue(testResourceId, testResourcePropertyId, value); + Map map = expectedPropertyValues.get(testResourceId); + if (map == null) { + map = new LinkedHashMap<>(); + expectedPropertyValues.put(testResourceId, map); + } + map.put(testResourcePropertyId, value); + } + } + } + + ResourcesPluginData resourcesPluginData = builder.build(); + Map> actualPropertyValues = resourcesPluginData + .getResourcePropertyValues(); + + assertEquals(expectedPropertyValues, actualPropertyValues); + } + + @Test + @UnitTestMethod(target = ResourcesPluginData.class, name = "getResourcePropertyValue", args = { ResourceId.class, + ResourcePropertyId.class }) + public void testGetResourcePropertyValue() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1876340540126853882L); + + Map expectedValues = new LinkedHashMap<>(); + + ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); + for (TestResourceId testResourceId : TestResourceId.values()) { + builder.addResource(testResourceId, 0.0, false); + Set testResourcePropertyIds = TestResourcePropertyId + .getTestResourcePropertyIds(testResourceId); + for (TestResourcePropertyId testResourcePropertyId : testResourcePropertyIds) { + MultiKey multiKey = new MultiKey(testResourceId, testResourcePropertyId); + PropertyDefinition propertyDefinition = testResourcePropertyId.getPropertyDefinition(); + builder.defineResourceProperty(testResourceId, testResourcePropertyId, propertyDefinition); + if (randomGenerator.nextBoolean()) { + Object value = testResourcePropertyId.getRandomPropertyValue(randomGenerator); + builder.setResourcePropertyValue(testResourceId, testResourcePropertyId, value); + expectedValues.put(multiKey, value); + } + } + } + + ResourcesPluginData resourceInitialData = builder.build(); + + for (TestResourceId testResourceId : TestResourceId.values()) { + Set testResourcePropertyIds = TestResourcePropertyId + .getTestResourcePropertyIds(testResourceId); + for (TestResourcePropertyId testResourcePropertyId : testResourcePropertyIds) { + MultiKey multiKey = new MultiKey(testResourceId, testResourcePropertyId); + Object expectedValue = expectedValues.get(multiKey); + Optional optional = resourceInitialData.getResourcePropertyValue(testResourceId, + testResourcePropertyId); + if (expectedValue == null) { + assertTrue(optional.isEmpty()); + } else { + assertEquals(expectedValue, optional.get()); + } + } + } + + // precondition tests + + ResourceId resourceId = TestResourceId.RESOURCE_3; + ResourcePropertyId resourcePropertyId = TestResourcePropertyId.ResourceProperty_2_2_INTEGER_MUTABLE; + + // if the resource id is null + ContractException contractException = assertThrows(ContractException.class, + () -> resourceInitialData.getResourcePropertyValue(null, resourcePropertyId)); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + // if the resource id is unknown + contractException = assertThrows(ContractException.class, () -> resourceInitialData + .getResourcePropertyValue(TestResourceId.getUnknownResourceId(), resourcePropertyId)); + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + + // if the resource property id is null + contractException = assertThrows(ContractException.class, + () -> resourceInitialData.getResourcePropertyValue(resourceId, null)); + assertEquals(PropertyError.NULL_PROPERTY_ID, contractException.getErrorType()); + + // if the resource property id is unknown + contractException = assertThrows(ContractException.class, () -> resourceInitialData + .getResourcePropertyValue(resourceId, TestResourcePropertyId.getUnknownResourcePropertyId())); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, () -> resourceInitialData + .getResourcePropertyValue(resourceId, TestResourcePropertyId.ResourceProperty_5_1_INTEGER_IMMUTABLE)); + assertEquals(PropertyError.UNKNOWN_PROPERTY_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ResourcesPluginData.class, name = "getPersonResourceLevels", args = {}) + public void testGetPersonResourceLevels() { + Map> expectedResourceLevels = new LinkedHashMap<>(); + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(579338638644505479L); + + // add the resources + ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); + for (TestResourceId testResourceId : TestResourceId.values()) { + builder.addResource(testResourceId, 0.0, false); + } + + // add up to 30 people + Set people = new LinkedHashSet<>(); + int id = 0; + for (int i = 0; i < 30; i++) { + id += randomGenerator.nextInt(3) + 1; + people.add(new PersonId(id)); + } + assertTrue(people.size() > 20); + for (PersonId personId : people) { + for (TestResourceId testResourceId : TestResourceId.values()) { + + if (randomGenerator.nextBoolean()) { + long amount = randomGenerator.nextInt(10); + builder.setPersonResourceLevel(personId, testResourceId, amount); + List list = expectedResourceLevels.get(testResourceId); + if (list == null) { + list = new ArrayList<>(); + expectedResourceLevels.put(testResourceId, list); + } + while (list.size() < personId.getValue()) { + list.add(null); + } + list.add(amount); + } + } + } + + ResourcesPluginData resourcesPluginData = builder.build(); + + Map> actualResourceLevels = resourcesPluginData.getPersonResourceLevels(); + assertEquals(expectedResourceLevels, actualResourceLevels); + } + + @Test + @UnitTestMethod(target = ResourcesPluginData.class, name = "getPersonResourceLevels", args = { ResourceId.class }) + public void testGetPersonResourceLevels_ResourceId() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2902745806851600371L); + + // add the resources + ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); + for (TestResourceId testResourceId : TestResourceId.values()) { + builder.addResource(testResourceId, 0.0, false); + } + + Set expectedValues = new LinkedHashSet<>(); + + // add up to 30 people + Set people = new LinkedHashSet<>(); + int id = 0; + for (int i = 0; i < 30; i++) { + id += randomGenerator.nextInt(3) + 1; + people.add(new PersonId(id)); + } + assertTrue(people.size() > 20); + for (PersonId personId : people) { + for (TestResourceId testResourceId : TestResourceId.values()) { + + if (randomGenerator.nextBoolean()) { + long amount = randomGenerator.nextInt(10); + builder.setPersonResourceLevel(personId, testResourceId, amount); + MultiKey multiKey = new MultiKey(personId, testResourceId, amount); + expectedValues.add(multiKey); + } + } + } + + ResourcesPluginData resourceInitialData = builder.build(); + + Set actualValues = new LinkedHashSet<>(); + for (TestResourceId testResourceId : TestResourceId.values()) { + List personResourceLevels = resourceInitialData.getPersonResourceLevels(testResourceId); + for (int i = 0; i < personResourceLevels.size(); i++) { + Long amount = personResourceLevels.get(i); + if (amount != null) { + MultiKey multiKey = new MultiKey(new PersonId(i), testResourceId, amount); + actualValues.add(multiKey); + } + } + } + + assertEquals(expectedValues, actualValues); + + // precondition test: if the person id is null + ContractException contractException = assertThrows(ContractException.class, + () -> resourceInitialData.getPersonResourceLevels(null)); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ResourcesPluginData.class, name = "getResourceIds", args = {}) + public void testGetResourceIds() { + + ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); + Set expectedResourceIds = new LinkedHashSet<>(); + for (TestResourceId testResourceId : TestResourceId.values()) { + builder.addResource(testResourceId, 0.0, false); + expectedResourceIds.add(testResourceId); + } + ResourcesPluginData resourceInitialData = builder.build(); + assertEquals(expectedResourceIds, resourceInitialData.getResourceIds()); + + } + + @Test + @UnitTestMethod(target = ResourcesPluginData.class, name = "getRegionResourceLevels", args = {}) + public void testGetRegionResourceLevels() { + Map> expectedResourceLevels = new LinkedHashMap<>(); + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6794457915874374469L); + + // add the resources + ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); + for (TestResourceId testResourceId : TestResourceId.values()) { + builder.addResource(testResourceId, 0.0, false); + } + + for (TestRegionId testRegionId : TestRegionId.values()) { + for (TestResourceId testResourceId : TestResourceId.values()) { + if (randomGenerator.nextBoolean()) { + long amount = randomGenerator.nextInt(10); + builder.setRegionResourceLevel(testRegionId, testResourceId, amount); + + Map map = expectedResourceLevels.get(testRegionId); + if (map == null) { + map = new LinkedHashMap<>(); + expectedResourceLevels.put(testRegionId, map); + } + map.put(testResourceId, amount); + } + } + } + + ResourcesPluginData resourceInitialData = builder.build(); + + Map> actualResourceLevels = resourceInitialData.getRegionResourceLevels(); + + assertEquals(expectedResourceLevels, actualResourceLevels); + } + + @Test + @UnitTestMethod(target = ResourcesPluginData.class, name = "getRegionResourceLevel", args = { RegionId.class, + ResourceId.class }) + public void testGetRegionResourceLevel() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6794457915874374469L); + + // add the resources + ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); + for (TestResourceId testResourceId : TestResourceId.values()) { + builder.addResource(testResourceId, 0.0, false); + } + + Map expectedValues = new LinkedHashMap<>(); + + for (TestRegionId testRegionId : TestRegionId.values()) { + for (TestResourceId testResourceId : TestResourceId.values()) { + if (randomGenerator.nextBoolean()) { + long amount = randomGenerator.nextInt(10); + builder.setRegionResourceLevel(testRegionId, testResourceId, amount); + MultiKey multiKey = new MultiKey(testRegionId, testResourceId); + expectedValues.put(multiKey, amount); + } + } + } + + Map actualValues = new LinkedHashMap<>(); + + ResourcesPluginData resourceInitialData = builder.build(); + + for (TestRegionId testRegionId : TestRegionId.values()) { + + for (TestResourceId testResourceId : TestResourceId.values()) { + Optional optional = resourceInitialData.getRegionResourceLevel(testRegionId, testResourceId); + if (optional.isPresent()) { + Long amount = optional.get(); + MultiKey multiKey = new MultiKey(testRegionId, testResourceId); + actualValues.put(multiKey, amount); + } + } + + } + + assertEquals(expectedValues, actualValues); + + // precondition test: if the region id is null + ContractException contractException = assertThrows(ContractException.class, + () -> resourceInitialData.getRegionResourceLevel(null, TestResourceId.RESOURCE_1)); + assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); + + // precondition test: if the resource id is null + contractException = assertThrows(ContractException.class, + () -> resourceInitialData.getRegionResourceLevel(TestRegionId.REGION_1, null)); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + // precondition test: if the resource id is unknown + contractException = assertThrows(ContractException.class, () -> resourceInitialData + .getRegionResourceLevel(TestRegionId.REGION_1, TestResourceId.getUnknownResourceId())); + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = ResourcesPluginData.class, name = "getResourceTimeTrackingPolicies", args = {}) + public void testGetResourceTimeTrackingPolicies() { + + Map expectedPolicies = new LinkedHashMap<>(); + + boolean track = true; + ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); + for (TestResourceId testResourceId : TestResourceId.values()) { + builder.addResource(testResourceId, 0.0, track); + expectedPolicies.put(testResourceId, track); + track = !track; + } + + ResourcesPluginData resourcesPluginData = builder.build(); + + Map actualPolicies = resourcesPluginData.getResourceTimeTrackingPolicies(); + assertEquals(expectedPolicies, actualPolicies); + + } + + @Test + @UnitTestMethod(target = ResourcesPluginData.class, name = "getResourceTimeTrackingPolicy", args = { + ResourceId.class }) + public void testGetResourceTimeTrackingPolicy() { + + Map expectedValues = new LinkedHashMap<>(); + + boolean track = true; + ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); + for (TestResourceId testResourceId : TestResourceId.values()) { + builder.addResource(testResourceId, 0.0, track); + expectedValues.put(testResourceId, track); + track = !track; + } + + ResourcesPluginData resourceInitialData = builder.build(); + + for (TestResourceId testResourceId : TestResourceId.values()) { + boolean expectectedValue = expectedValues.get(testResourceId); + boolean actualValue = resourceInitialData.getResourceTimeTrackingPolicy(testResourceId); + assertEquals(expectectedValue, actualValue); + + } + + // precondition test: if the resource id is null + ContractException contractException = assertThrows(ContractException.class, + () -> resourceInitialData.getResourceTimeTrackingPolicy(null)); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + // precondition test: if the resource id is unknown + contractException = assertThrows(ContractException.class, + () -> resourceInitialData.getResourceTimeTrackingPolicy(TestResourceId.getUnknownResourceId())); + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ResourcesPluginData.class, name = "getRegionIds", args = {}) + public void testGetRegionIds() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7912521358724932418L); + + // add the resources + ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); + for (TestResourceId testResourceId : TestResourceId.values()) { + builder.addResource(testResourceId, 0.0, false); + } + + Set expectedRegionIds = new LinkedHashSet<>(); + for (TestRegionId testRegionId : TestRegionId.values()) { + + for (TestResourceId testResourceId : TestResourceId.values()) { + if (randomGenerator.nextBoolean()) { + int amount = randomGenerator.nextInt(10); + builder.setRegionResourceLevel(testRegionId, testResourceId, amount); + expectedRegionIds.add(testRegionId); + } + } + } + + ResourcesPluginData resourceInitialData = builder.build(); + + assertEquals(expectedRegionIds, resourceInitialData.getRegionIds()); + } + + @Test + @UnitTestMethod(target = ResourcesPluginData.class, name = "getCloneBuilder", args = {}) + public void testGetCloneBuilder() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7644775230297230691L); + ResourcesPluginData.Builder pluginDataBuilder = ResourcesPluginData.builder(); + + for (TestResourceId testResourceId : TestResourceId.values()) { + double time = randomGenerator.nextDouble(); + boolean timeTrackingPolicy = testResourceId.getTimeTrackingPolicy(); + pluginDataBuilder.addResource(testResourceId, time, timeTrackingPolicy); + } + + for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId.values()) { + pluginDataBuilder.defineResourceProperty(testResourcePropertyId.getTestResourceId(), testResourcePropertyId, + testResourcePropertyId.getPropertyDefinition()); + } + + for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId.values()) { + if (randomGenerator.nextBoolean()) { + pluginDataBuilder.setResourcePropertyValue(testResourcePropertyId.getTestResourceId(), + testResourcePropertyId, testResourcePropertyId.getRandomPropertyValue(randomGenerator)); + } + } + + for (TestRegionId testRegionId : TestRegionId.values()) { + for (TestResourceId testResourceId : TestResourceId.values()) { + if (randomGenerator.nextBoolean()) { + long value = randomGenerator.nextInt(1000); + pluginDataBuilder.setRegionResourceLevel(testRegionId, testResourceId, value); + } + } + } + + for (int i = 0; i < 10; i++) { + PersonId personId = new PersonId(i * i); + for (TestResourceId testResourceId : TestResourceId.values()) { + if (randomGenerator.nextBoolean()) { + long value = randomGenerator.nextInt(5); + double time = randomGenerator.nextDouble() + 1.0; + pluginDataBuilder.setPersonResourceLevel(personId, testResourceId, value); + if (testResourceId.getTimeTrackingPolicy()) { + pluginDataBuilder.setPersonResourceTime(personId, testResourceId, time); + } + } + } + } + + ResourcesPluginData resourcesPluginData = pluginDataBuilder.build(); + + PluginData pluginData = resourcesPluginData.getCloneBuilder().build(); + + // show that the plugin data is of the expected type + assertTrue(pluginData instanceof ResourcesPluginData); + + ResourcesPluginData cloneResourcesPluginData = (ResourcesPluginData) pluginData; + + assertEquals(resourcesPluginData, cloneResourcesPluginData); + + } + + + @Test + @UnitTestMethod(target = ResourcesPluginData.class, name = "toString", args = {}) + public void testToString() { + + ResourcesPluginData randomResourcesPluginData = getRandomResourcesPluginData(3613301633594044660L); + + String actualValue = randomResourcesPluginData.toString(); + + //The expected value was manually verified + String expectedValue = "ResourcesPluginData [data=Data [" + + "resourceDefaultTimes={" + + "RESOURCE_1=0.12069246251184063, " + + "RESOURCE_3=0.5181842946303716, " + + "RESOURCE_4=0.9749466517405754, " + + "RESOURCE_5=0.7470658222016602}, " + + "resourceTimeTrackingPolicies={" + + "RESOURCE_1=false, " + + "RESOURCE_3=false, " + + "RESOURCE_4=true, " + + "RESOURCE_5=false}, " + + "resourcePropertyDefinitions={" + + "RESOURCE_1={" + + "ResourceProperty_1_1_BOOLEAN_MUTABLE=PropertyDefinition [type=class java.lang.Boolean, propertyValuesAreMutable=true, defaultValue=false], " + + "ResourceProperty_1_2_INTEGER_MUTABLE=PropertyDefinition [type=class java.lang.Integer, propertyValuesAreMutable=true, defaultValue=0]}, " + + "RESOURCE_3={" + + "ResourceProperty_3_1_BOOLEAN_MUTABLE=PropertyDefinition [type=class java.lang.Boolean, propertyValuesAreMutable=true, defaultValue=false]}, " + + "RESOURCE_4={" + + "ResourceProperty_4_1_BOOLEAN_MUTABLE=PropertyDefinition [type=class java.lang.Boolean, propertyValuesAreMutable=true, defaultValue=true]}, " + + "RESOURCE_5={" + + "ResourceProperty_5_1_INTEGER_IMMUTABLE=PropertyDefinition [type=class java.lang.Integer, propertyValuesAreMutable=false, defaultValue=7], " + + "ResourceProperty_5_2_DOUBLE_IMMUTABLE=PropertyDefinition [type=class java.lang.Double, propertyValuesAreMutable=false, defaultValue=2.7]}}, " + + "resourcePropertyValues={" + + "RESOURCE_3={ResourceProperty_3_1_BOOLEAN_MUTABLE=false}, " + + "RESOURCE_5={ResourceProperty_5_1_INTEGER_IMMUTABLE=-1328557948}}, " + + "personResourceLevels={" + + "RESOURCE_4=[1, 2, null, null, 0, null, null, null, null, 3, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, 0, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, 1], " + + "RESOURCE_5=[4, 0, null, null, null, null, null, null, null, 2, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, 0], " + + "RESOURCE_1=[null, 0, null, null, 4, null, null, null, null, 2, null, null, null, null, null, null, 4, null, null, null, null, null, null, null, null, 4], " + + "RESOURCE_3=[null, null, null, null, null, null, null, null, null, 1, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, 2]}, " + + "personResourceTimes={" + + "RESOURCE_4=[1.6021497784639966, null, null, null, 1.769887399263266, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, 1.8351982268522948, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, 1.913996434647729]}, " + + "regionResourceLevels={" + + "REGION_2={RESOURCE_3=7}, " + + "REGION_3={RESOURCE_3=404, RESOURCE_5=247}, " + + "REGION_4={RESOURCE_1=971, RESOURCE_5=565}, " + + "REGION_5={RESOURCE_1=930, RESOURCE_3=653}, " + + "REGION_6={RESOURCE_4=402}}]]"; + + assertEquals(expectedValue, actualValue); + + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/events/AT_PersonResourceUpdateEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/events/AT_PersonResourceUpdateEvent.java new file mode 100644 index 000000000..f8d095fbb --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/events/AT_PersonResourceUpdateEvent.java @@ -0,0 +1,60 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.events; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_PersonResourceUpdateEvent { + + @Test + @UnitTestConstructor(target = PersonResourceUpdateEvent.class, args = { PersonId.class, ResourceId.class, long.class, long.class }) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonResourceUpdateEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonResourceUpdateEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonResourceUpdateEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonResourceUpdateEvent.class, name = "personId", args = {}) + public void testPersonId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonResourceUpdateEvent.class, name = "resourceId", args = {}) + public void testResourceId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonResourceUpdateEvent.class, name = "previousResourceLevel", args = {}) + public void testPreviousResourceLevel() { + // nothing to test + } + + @Test + @UnitTestMethod(target = PersonResourceUpdateEvent.class, name = "currentResourceLevel", args = {}) + public void testCurrentResourceLevel() { + // nothing to test + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/events/AT_RegionResourceUpdateEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/events/AT_RegionResourceUpdateEvent.java new file mode 100644 index 000000000..19be0a41f --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/events/AT_RegionResourceUpdateEvent.java @@ -0,0 +1,60 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.events; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_RegionResourceUpdateEvent { + + @Test + @UnitTestConstructor(target = RegionResourceUpdateEvent.class, args = { RegionId.class, ResourceId.class, long.class, long.class }) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = RegionResourceUpdateEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = RegionResourceUpdateEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = RegionResourceUpdateEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = RegionResourceUpdateEvent.class, name = "regionId", args = {}) + public void testRegionId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = RegionResourceUpdateEvent.class, name = "resourceId", args = {}) + public void testResourceId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = RegionResourceUpdateEvent.class, name = "previousResourceLevel", args = {}) + public void testPreviousResourceLevel() { + // nothing to test + } + + @Test + @UnitTestMethod(target = RegionResourceUpdateEvent.class, name = "currentResourceLevel", args = {}) + public void testCurrentResourceLevel() { + // nothing to test + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/events/AT_ResourceIdAdditionEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/events/AT_ResourceIdAdditionEvent.java new file mode 100644 index 000000000..d5bcebc2c --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/events/AT_ResourceIdAdditionEvent.java @@ -0,0 +1,55 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.events; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceError; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + +public class AT_ResourceIdAdditionEvent { + + @Test + @UnitTestConstructor(target = ResourceIdAdditionEvent.class, args = { ResourceId.class, boolean.class }) + public void testConstructor() { + + // test case: null resource id + ContractException resourceIdContractException = assertThrows(ContractException.class, () -> new ResourceIdAdditionEvent(null, true)); + assertEquals(resourceIdContractException.getErrorType(), ResourceError.NULL_RESOURCE_ID); + } + + @Test + @UnitTestMethod(target = ResourceIdAdditionEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = ResourceIdAdditionEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = ResourceIdAdditionEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = ResourceIdAdditionEvent.class, name = "resourceId", args = {}) + public void testResourceId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = ResourceIdAdditionEvent.class, name = "timeTrackingPolicy", args = {}) + public void testTimeTrackingPolicy() { + // nothing to test + } + +} \ No newline at end of file diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/events/AT_ResourcePropertyDefinitionEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/events/AT_ResourcePropertyDefinitionEvent.java new file mode 100644 index 000000000..169f4fb27 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/events/AT_ResourcePropertyDefinitionEvent.java @@ -0,0 +1,77 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.events; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceError; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourcePropertyId; +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.TestResourceId; +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.TestResourcePropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + +public class AT_ResourcePropertyDefinitionEvent { + + @Test + @UnitTestConstructor(target = ResourcePropertyDefinitionEvent.class, args = { ResourceId.class, ResourcePropertyId.class, Object.class }) + public void testConstructor() { + TestResourcePropertyId testResourcePropertyId = TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE; + TestResourceId testResourceId = TestResourceId.RESOURCE_1; + Integer propertyValue = 7; + + // test case: null resource id + ContractException contractException = assertThrows(ContractException.class, () -> new ResourcePropertyDefinitionEvent(null, testResourcePropertyId, propertyValue)); + assertEquals(contractException.getErrorType(), ResourceError.NULL_RESOURCE_ID); + + // test case: null property id + contractException = assertThrows(ContractException.class, () -> new ResourcePropertyDefinitionEvent(testResourceId, null, propertyValue)); + assertEquals(contractException.getErrorType(), PropertyError.NULL_PROPERTY_ID); + + // test case: null property value + contractException = assertThrows(ContractException.class, () -> new ResourcePropertyDefinitionEvent(testResourceId, testResourcePropertyId, null)); + assertEquals(contractException.getErrorType(), PropertyError.NULL_PROPERTY_VALUE); + } + + @Test + @UnitTestMethod(target = ResourcePropertyDefinitionEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = ResourcePropertyDefinitionEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = ResourcePropertyDefinitionEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = ResourcePropertyDefinitionEvent.class, name = "resourceId", args = {}) + public void testResourceId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = ResourcePropertyDefinitionEvent.class, name = "resourcePropertyId", args = {}) + public void testResourcePropertyId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = ResourcePropertyDefinitionEvent.class, name = "resourcePropertyValue", args = {}) + public void testResourcePropertyValue() { + // nothing to test + } + + +} \ No newline at end of file diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/events/AT_ResourcePropertyUpdateEvent.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/events/AT_ResourcePropertyUpdateEvent.java new file mode 100644 index 000000000..5d8ac983e --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/events/AT_ResourcePropertyUpdateEvent.java @@ -0,0 +1,60 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.events; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourcePropertyId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_ResourcePropertyUpdateEvent { + + @Test + @UnitTestConstructor(target = ResourcePropertyUpdateEvent.class, args = { ResourceId.class, ResourcePropertyId.class, Object.class, Object.class }) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = ResourcePropertyUpdateEvent.class, name = "equals", args = { Object.class }) + public void testEquals() { + // nothing to test + } + + @Test + @UnitTestMethod(target = ResourcePropertyUpdateEvent.class, name = "toString", args = {}) + public void testToString() { + // nothing to test + } + + @Test + @UnitTestMethod(target = ResourcePropertyUpdateEvent.class, name = "hashCode", args = {}) + public void testHashCode() { + // nothing to test + } + + @Test + @UnitTestMethod(target = ResourcePropertyUpdateEvent.class, name = "resourceId", args = {}) + public void testResourceId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = ResourcePropertyUpdateEvent.class, name = "resourcePropertyId", args = {}) + public void testResourcePropertyId() { + // nothing to test + } + + @Test + @UnitTestMethod(target = ResourcePropertyUpdateEvent.class, name = "previousPropertyValue", args = {}) + public void testPreviousPropertyValue() { + // nothing to test + } + + @Test + @UnitTestMethod(target = ResourcePropertyUpdateEvent.class, name = "currentPropertyValue", args = {}) + public void testCurrentPropertyValue() { + // nothing to test + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/reports/AT_PersonResourceReport.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/reports/AT_PersonResourceReport.java new file mode 100644 index 000000000..9d69e3dfe --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/reports/AT_PersonResourceReport.java @@ -0,0 +1,175 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.reports; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestOutputConsumer; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.resources.datamanagers.ResourcesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourcePropertyInitialization; +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.ResourcesTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.TestResourceId; +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.TestResourcePropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import util.annotations.UnitTag; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_PersonResourceReport { + + @Test + @UnitTestConstructor(target = PersonResourceReport.class, args = { PersonResourceReportPluginData.class }) + public void testConstructor() { + /* + * Nothing to test. The PersonResourceReportPluginData guarantees that + * the report lable and report period will not be null. The super + * constructor of the PersonResourceReport prevents the use of a + * ContractException for null PersonResourceReportPluginData instances. + */ + assertThrows(NullPointerException.class, () -> new PersonResourceReport(null)); + + } + + @Test + @UnitTestMethod(target = PersonResourceReport.class, name = "init", args = { ReportContext.class }, tags = UnitTag.INCOMPLETE) + public void testInit() { + TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); + pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0,(c)->{})); + TestPluginData testPluginData = pluginDataBuilder.build(); + + List plugins = ResourcesTestPluginFactory// + .factory(5, 5884216992159063226L, testPluginData)// + .getPlugins(); + + TestSimulation.builder().addPlugins(plugins).build().execute(); + } + + @Test + @UnitTestMethod(target = PersonResourceReport.class, name = "init", args = { ReportContext.class }) + public void testInit_State() { + // Test with producing simulation state + + int initialPopulation = 20; + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + /* + * create an agent and have it assign various resource properties at + * various times + */ + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0.0, (c) -> { + /* + * note that this is time 0 and should show that property initial + * values are still reported correctly + */ + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_3, + TestResourcePropertyId.ResourceProperty_3_2_STRING_MUTABLE, "A"); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1.0, (c) -> { + // two settings of the same property + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_2, + TestResourcePropertyId.ResourceProperty_2_2_INTEGER_MUTABLE, 45); + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_1, + TestResourcePropertyId.ResourceProperty_1_3_DOUBLE_MUTABLE, 36.7); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2.0, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_4, + TestResourcePropertyId.ResourceProperty_4_1_BOOLEAN_MUTABLE, true); + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_2, + TestResourcePropertyId.ResourceProperty_2_1_BOOLEAN_MUTABLE, false); + + // add new property definitions + for (AT_ResourcePropertyReport.TestAuxiliaryResourcePropertyId propertyId : AT_ResourcePropertyReport.TestAuxiliaryResourcePropertyId.values()) { + TestResourceId testResourceId = propertyId.getTestResourceId(); + PropertyDefinition propertyDefinition = propertyId.getPropertyDefinition(); + ResourcePropertyInitialization resourcePropertyInitialization = ResourcePropertyInitialization.builder() + .setResourceId(testResourceId).setResourcePropertyId(propertyId) + .setPropertyDefinition(propertyDefinition).build(); + resourcesDataManager.defineResourceProperty(resourcePropertyInitialization); + } + + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(3.0, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_4, + TestResourcePropertyId.ResourceProperty_4_1_BOOLEAN_MUTABLE, true); + + // note the duplicated value + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_1, + TestResourcePropertyId.ResourceProperty_1_3_DOUBLE_MUTABLE, 2.5); + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_1, + TestResourcePropertyId.ResourceProperty_1_3_DOUBLE_MUTABLE, 2.5); + + // and now a third setting of the same property to a new value + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_1, + TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE, 100); + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_1, + TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE, 60); + + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_1, + AT_ResourcePropertyReport.TestAuxiliaryResourcePropertyId.AUX_RESOURCE_PROPERTY_1_BOOLEAN_MUTABLE, true); + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_2, + AT_ResourcePropertyReport.TestAuxiliaryResourcePropertyId.AUX_RESOURCE_PROPERTY_2_INTEGER_MUTABLE, 137); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + + ResourcesTestPluginFactory.Factory factory = ResourcesTestPluginFactory.factory(initialPopulation, 8914112012010329946L, testPluginData); + ResourcePropertyReportPluginData resourcePropertyReportPluginData = ResourcePropertyReportPluginData.builder() + .setReportLabel(REPORT_LABEL) + .build(); + factory.setResourcePropertyReportPluginData(resourcePropertyReportPluginData); + PersonResourceReportPluginData personResourceReportPluginData = PersonResourceReportPluginData.builder() + .setReportPeriod(ReportPeriod.DAILY) + .setReportLabel(REPORT_LABEL) + .excludeResource(TestResourceId.RESOURCE_1) + .setDefaultInclusion(true) + .build(); + factory.setPersonResourceReportPluginData(personResourceReportPluginData); + + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .setProduceSimulationStateOnHalt(true)// + .setSimulationHaltTime(20)// + .build()// + .execute(); + + Map outputItems = testOutputConsumer.getOutputItemMap(PersonResourceReportPluginData.class); + assertEquals(1, outputItems.size()); + PersonResourceReportPluginData personResourceReportPluginData2 = outputItems.keySet().iterator().next(); + assertEquals(personResourceReportPluginData, personResourceReportPluginData2); + + // Test without producing simulation state + + testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .setProduceSimulationStateOnHalt(false)// + .setSimulationHaltTime(20)// + .build()// + .execute(); + + outputItems = testOutputConsumer.getOutputItemMap(PersonResourceReportPluginData.class); + assertEquals(0, outputItems.size()); + } + + private static final ReportLabel REPORT_LABEL = new SimpleReportLabel("resource property report"); +} \ No newline at end of file diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/reports/AT_PersonResourceReportPluginData.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/reports/AT_PersonResourceReportPluginData.java new file mode 100644 index 000000000..0803b7bbd --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/reports/AT_PersonResourceReportPluginData.java @@ -0,0 +1,634 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.reports; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportError; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceError; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.TestResourceId; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_PersonResourceReportPluginData { + + @Test + @UnitTestMethod(target = PersonResourceReportPluginData.class, name = "builder", args = {}) + public void testBuilder() { + PersonResourceReportPluginData.Builder builder = PersonResourceReportPluginData.builder(); + assertNotNull(builder); + } + + @Test + @UnitTestMethod(target = PersonResourceReportPluginData.Builder.class, name = "build", args = {}) + public void testBuild() { + // the specific capabilities are covered elsewhere + + // precondition test: if the report period is not assigned + ContractException contractException = assertThrows(ContractException.class, () -> // + PersonResourceReportPluginData.builder()// + .setReportLabel(new SimpleReportLabel(getClass()))// + .build()); + assertEquals(ReportError.NULL_REPORT_PERIOD, contractException.getErrorType()); + + // precondition test: if the report label is not assigned + contractException = assertThrows(ContractException.class, () -> // + PersonResourceReportPluginData.builder()// + .setReportPeriod(ReportPeriod.DAILY)// + .build()); + assertEquals(ReportError.NULL_REPORT_LABEL, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = PersonResourceReportPluginData.Builder.class, name = "setReportLabel", args = { + ReportLabel.class }) + public void testSetReportLabel() { + + for (int i = 0; i < 30; i++) { + ReportLabel expectedReportLabel = new SimpleReportLabel(i); + PersonResourceReportPluginData personResourceReportPluginData = // + PersonResourceReportPluginData.builder()// + .setReportPeriod(ReportPeriod.DAILY)// + .setReportLabel(expectedReportLabel)// + .build(); + + assertEquals(expectedReportLabel, personResourceReportPluginData.getReportLabel()); + } + + // precondition: if the report label is null + ContractException contractException = assertThrows(ContractException.class, () -> { + PersonResourceReportPluginData.builder().setReportLabel(null); + }); + assertEquals(ReportError.NULL_REPORT_LABEL, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PersonResourceReportPluginData.Builder.class, name = "setReportPeriod", args = { + ReportPeriod.class }) + public void testSetReportPeriod() { + + ReportLabel reportLabel = new SimpleReportLabel("report label"); + for (ReportPeriod reportPeriod : ReportPeriod.values()) { + + PersonResourceReportPluginData personResourceReportPluginData = // + PersonResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .build(); + + assertEquals(reportPeriod, personResourceReportPluginData.getReportPeriod()); + } + + // precondition: if the report period is null + ContractException contractException = assertThrows(ContractException.class, () -> { + PersonResourceReportPluginData.builder().setReportPeriod(null); + }); + assertEquals(ReportError.NULL_REPORT_PERIOD, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PersonResourceReportPluginData.Builder.class, name = "setDefaultInclusion", args = { + boolean.class }) + public void testSetDefaultInclusion() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + ReportPeriod reportPeriod = ReportPeriod.DAILY; + + // show the default value is true + PersonResourceReportPluginData personResourceReportPluginData = // + PersonResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .build(); + assertEquals(true, personResourceReportPluginData.getDefaultInclusionPolicy()); + + personResourceReportPluginData = // + PersonResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .setDefaultInclusion(true)// + .build(); + assertEquals(true, personResourceReportPluginData.getDefaultInclusionPolicy()); + + personResourceReportPluginData = // + PersonResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .setDefaultInclusion(false)// + .build(); + assertEquals(false, personResourceReportPluginData.getDefaultInclusionPolicy()); + + } + + @Test + @UnitTestMethod(target = PersonResourceReportPluginData.Builder.class, name = "includeResource", args = { + ResourceId.class }) + public void testIncludeResource() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + ReportPeriod reportPeriod = ReportPeriod.DAILY; + + PersonResourceReportPluginData personResourceReportPluginData = // + PersonResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .build(); + assertTrue(personResourceReportPluginData.getIncludedResourceIds().isEmpty()); + + // show that inclusion alone works + Set expectedResourceIds = new LinkedHashSet<>(); + + expectedResourceIds.add(TestResourceId.RESOURCE_1); + expectedResourceIds.add(TestResourceId.RESOURCE_4); + expectedResourceIds.add(TestResourceId.RESOURCE_2); + + PersonResourceReportPluginData.Builder builder = PersonResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel);// + + for (ResourceId resourceId : expectedResourceIds) { + builder.includeResource(resourceId); + } + + personResourceReportPluginData = builder.build(); + assertEquals(expectedResourceIds, personResourceReportPluginData.getIncludedResourceIds()); + + // show that inclusion will override exclusion + expectedResourceIds.clear(); + + expectedResourceIds.add(TestResourceId.RESOURCE_1); + expectedResourceIds.add(TestResourceId.RESOURCE_4); + expectedResourceIds.add(TestResourceId.RESOURCE_2); + + builder = PersonResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel);// + + for (ResourceId resourceId : expectedResourceIds) { + builder.excludeResource(resourceId); + builder.includeResource(resourceId); + } + + personResourceReportPluginData = builder.build(); + assertEquals(expectedResourceIds, personResourceReportPluginData.getIncludedResourceIds()); + + // precondition: if the person property id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + PersonResourceReportPluginData.builder().includeResource(null); + }); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PersonResourceReportPluginData.Builder.class, name = "excludeResource", args = { + ResourceId.class }) + public void testExcludeResource() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + ReportPeriod reportPeriod = ReportPeriod.DAILY; + + // show the default is non-exclusion + PersonResourceReportPluginData personResourceReportPluginData = // + PersonResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .build(); + assertTrue(personResourceReportPluginData.getExcludedResourceIds().isEmpty()); + + // show that exclusion alone works + Set expectedResourceIds = new LinkedHashSet<>(); + + expectedResourceIds.add(TestResourceId.RESOURCE_1); + expectedResourceIds.add(TestResourceId.RESOURCE_4); + expectedResourceIds.add(TestResourceId.RESOURCE_2); + + PersonResourceReportPluginData.Builder builder = PersonResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel);// + + for (ResourceId resourceId : expectedResourceIds) { + builder.excludeResource(resourceId); + } + + personResourceReportPluginData = builder.build(); + assertEquals(expectedResourceIds, personResourceReportPluginData.getExcludedResourceIds()); + + // show that exclusion will override inclusion + expectedResourceIds.clear(); + + expectedResourceIds.add(TestResourceId.RESOURCE_1); + expectedResourceIds.add(TestResourceId.RESOURCE_4); + expectedResourceIds.add(TestResourceId.RESOURCE_2); + + builder = PersonResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel);// + + for (ResourceId resourceId : expectedResourceIds) { + builder.includeResource(resourceId); + builder.excludeResource(resourceId); + } + + personResourceReportPluginData = builder.build(); + assertEquals(expectedResourceIds, personResourceReportPluginData.getExcludedResourceIds()); + + // precondition: if the person property id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + PersonResourceReportPluginData.builder().excludeResource(null); + }); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = PersonResourceReportPluginData.class, name = "getReportLabel", args = {}) + public void testGetReportLabel() { + for (int i = 0; i < 30; i++) { + ReportLabel expectedReportLabel = new SimpleReportLabel(i); + PersonResourceReportPluginData personResourceReportPluginData = // + PersonResourceReportPluginData.builder()// + .setReportPeriod(ReportPeriod.DAILY)// + .setReportLabel(expectedReportLabel)// + .build(); + + assertEquals(expectedReportLabel, personResourceReportPluginData.getReportLabel()); + } + + } + + @Test + @UnitTestMethod(target = PersonResourceReportPluginData.class, name = "getReportPeriod", args = {}) + public void testGetReportPeriod() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + for (ReportPeriod reportPeriod : ReportPeriod.values()) { + + PersonResourceReportPluginData personResourceReportPluginData = // + PersonResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .build(); + + assertEquals(reportPeriod, personResourceReportPluginData.getReportPeriod()); + } + } + + @Test + @UnitTestMethod(target = PersonResourceReportPluginData.class, name = "getIncludedResourceIds", args = {}) + public void testGetIncludedResourceIds() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + ReportPeriod reportPeriod = ReportPeriod.DAILY; + + // show the default is non-inclusion + PersonResourceReportPluginData personResourceReportPluginData = // + PersonResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .build(); + assertTrue(personResourceReportPluginData.getIncludedResourceIds().isEmpty()); + + // show that inclusion alone works + Set expectedResourceIds = new LinkedHashSet<>(); + + expectedResourceIds.add(TestResourceId.RESOURCE_1); + expectedResourceIds.add(TestResourceId.RESOURCE_4); + expectedResourceIds.add(TestResourceId.RESOURCE_2); + + PersonResourceReportPluginData.Builder builder = PersonResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel);// + + for (ResourceId resourceId : expectedResourceIds) { + builder.includeResource(resourceId); + } + + personResourceReportPluginData = builder.build(); + assertEquals(expectedResourceIds, personResourceReportPluginData.getIncludedResourceIds()); + + // show that inclusion will override exclusion + expectedResourceIds.clear(); + + expectedResourceIds.add(TestResourceId.RESOURCE_1); + expectedResourceIds.add(TestResourceId.RESOURCE_4); + expectedResourceIds.add(TestResourceId.RESOURCE_2); + + builder = PersonResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel);// + + for (ResourceId resourceId : expectedResourceIds) { + builder.excludeResource(resourceId); + builder.includeResource(resourceId); + } + + personResourceReportPluginData = builder.build(); + assertEquals(expectedResourceIds, personResourceReportPluginData.getIncludedResourceIds()); + + } + + @Test + @UnitTestMethod(target = PersonResourceReportPluginData.class, name = "getExcludedResourceIds", args = {}) + public void testGetExcludedResourceIds() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + ReportPeriod reportPeriod = ReportPeriod.DAILY; + + // show the default is non-exclusion + PersonResourceReportPluginData personResourceReportPluginData = // + PersonResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .build(); + assertTrue(personResourceReportPluginData.getExcludedResourceIds().isEmpty()); + + // show that exclusion alone works + Set expectedResourceIds = new LinkedHashSet<>(); + + expectedResourceIds.add(TestResourceId.RESOURCE_1); + expectedResourceIds.add(TestResourceId.RESOURCE_4); + expectedResourceIds.add(TestResourceId.RESOURCE_2); + + PersonResourceReportPluginData.Builder builder = PersonResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel);// + + for (ResourceId resourceId : expectedResourceIds) { + builder.excludeResource(resourceId); + } + + personResourceReportPluginData = builder.build(); + assertEquals(expectedResourceIds, personResourceReportPluginData.getExcludedResourceIds()); + + // show that exclusion will override inclusion + expectedResourceIds.clear(); + + expectedResourceIds.add(TestResourceId.RESOURCE_1); + expectedResourceIds.add(TestResourceId.RESOURCE_4); + expectedResourceIds.add(TestResourceId.RESOURCE_2); + + builder = PersonResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel);// + + for (ResourceId resourceId : expectedResourceIds) { + builder.includeResource(resourceId); + builder.excludeResource(resourceId); + } + + personResourceReportPluginData = builder.build(); + assertEquals(expectedResourceIds, personResourceReportPluginData.getExcludedResourceIds()); + + } + + @Test + @UnitTestMethod(target = PersonResourceReportPluginData.class, name = "getDefaultInclusionPolicy", args = {}) + public void testGetDefaultInclusionPolicy() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + ReportPeriod reportPeriod = ReportPeriod.DAILY; + + // show the default value is true + PersonResourceReportPluginData personResourceReportPluginData = // + PersonResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .build(); + assertEquals(true, personResourceReportPluginData.getDefaultInclusionPolicy()); + + personResourceReportPluginData = // + PersonResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .setDefaultInclusion(true)// + .build(); + assertEquals(true, personResourceReportPluginData.getDefaultInclusionPolicy()); + + personResourceReportPluginData = // + PersonResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .setDefaultInclusion(false)// + .build(); + assertEquals(false, personResourceReportPluginData.getDefaultInclusionPolicy()); + } + + @Test + @UnitTestMethod(target = PersonResourceReportPluginData.class, name = "getCloneBuilder", args = {}) + public void testGetCloneBuilder() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8951274294108578550L); + for (int i = 0; i < 10; i++) { + + // build a PersonResourceReportPluginData from random inputs + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt()); + ReportPeriod reportPeriod = ReportPeriod.values()[randomGenerator.nextInt(ReportPeriod.values().length)]; + + PersonResourceReportPluginData.Builder builder = // + PersonResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel); + + for (int j = 0; j < 10; j++) { + TestResourceId testResourceId = TestResourceId.getRandomResourceId(randomGenerator); + if (randomGenerator.nextBoolean()) { + builder.includeResource(testResourceId); + } else { + builder.excludeResource(testResourceId); + } + } + + builder.setDefaultInclusion(randomGenerator.nextBoolean()).build(); + + PersonResourceReportPluginData personResourceReportPluginData = builder.build(); + + // create the clone builder and have it build + PersonResourceReportPluginData clonePersonResourceReportPluginData = personResourceReportPluginData + .getCloneBuilder().build(); + + // the result should equal the original if the clone builder was + // initialized with the correct state + assertEquals(personResourceReportPluginData, clonePersonResourceReportPluginData); + + } + } + + @Test + @UnitTestMethod(target = PersonResourceReportPluginData.class, name = "equals", args = { Object.class }) + public void testEquals() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7759639255438669162L); + for (int i = 0; i < 10; i++) { + // build a PersonResourceReportPluginData from the same random + // inputs + PersonResourceReportPluginData.Builder builder1 = PersonResourceReportPluginData.builder(); + PersonResourceReportPluginData.Builder builder2 = PersonResourceReportPluginData.builder(); + + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt(100)); + builder1.setReportLabel(reportLabel); + builder2.setReportLabel(reportLabel); + + ReportPeriod reportPeriod = ReportPeriod.values()[randomGenerator.nextInt(ReportPeriod.values().length)]; + builder1.setReportPeriod(reportPeriod); + builder2.setReportPeriod(reportPeriod); + + for (int j = 0; j < 10; j++) { + TestResourceId testResourceId = TestResourceId.getRandomResourceId(randomGenerator); + if (randomGenerator.nextBoolean()) { + builder1.includeResource(testResourceId); + builder2.includeResource(testResourceId); + } else { + builder1.excludeResource(testResourceId); + builder2.excludeResource(testResourceId); + } + } + + boolean defaultInclusion = randomGenerator.nextBoolean(); + builder1.setDefaultInclusion(defaultInclusion).build(); + builder2.setDefaultInclusion(defaultInclusion).build(); + + PersonResourceReportPluginData personResourceReportPluginData1 = builder1.build(); + PersonResourceReportPluginData personResourceReportPluginData2 = builder2.build(); + + assertEquals(personResourceReportPluginData1, personResourceReportPluginData2); + + // show that plugin datas with different inputs are not equal + + // change the default inclusion + personResourceReportPluginData2 = // + personResourceReportPluginData1.getCloneBuilder()// + .setDefaultInclusion(!defaultInclusion)// + .build(); + assertNotEquals(personResourceReportPluginData2, personResourceReportPluginData1); + + // change the report period + int ord = reportPeriod.ordinal() + 1; + ord = ord % ReportPeriod.values().length; + reportPeriod = ReportPeriod.values()[ord]; + personResourceReportPluginData2 = // + personResourceReportPluginData1.getCloneBuilder()// + .setReportPeriod(reportPeriod)// + .build(); + assertNotEquals(personResourceReportPluginData2, personResourceReportPluginData1); + + // change the report label + reportLabel = new SimpleReportLabel(1000); + personResourceReportPluginData2 = // + personResourceReportPluginData1.getCloneBuilder()// + .setReportLabel(reportLabel)// + .build(); + assertNotEquals(personResourceReportPluginData2, personResourceReportPluginData1); + + // change an included property id + if (!personResourceReportPluginData1.getIncludedResourceIds().isEmpty()) { + ResourceId resourceId = personResourceReportPluginData1.getIncludedResourceIds().iterator().next(); + personResourceReportPluginData2 = // + personResourceReportPluginData1.getCloneBuilder()// + .excludeResource(resourceId)// + .build(); + assertNotEquals(personResourceReportPluginData2, personResourceReportPluginData1); + } + // change an excluded property id + if (!personResourceReportPluginData1.getExcludedResourceIds().isEmpty()) { + ResourceId resourceId = personResourceReportPluginData1.getExcludedResourceIds().iterator().next(); + personResourceReportPluginData2 = // + personResourceReportPluginData1.getCloneBuilder()// + .includeResource(resourceId)// + .build(); + assertNotEquals(personResourceReportPluginData2, personResourceReportPluginData1); + } + + } + + } + + @Test + @UnitTestMethod(target = PersonResourceReportPluginData.class, name = "hashCode", args = {}) + public void testHashCode() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(9079768427072825406L); + + Set observedHashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 50; i++) { + // build a PersonResourceReportPluginData from the same random + // inputs + PersonResourceReportPluginData.Builder builder1 = PersonResourceReportPluginData.builder(); + PersonResourceReportPluginData.Builder builder2 = PersonResourceReportPluginData.builder(); + + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt(100)); + builder1.setReportLabel(reportLabel); + builder2.setReportLabel(reportLabel); + + ReportPeriod reportPeriod = ReportPeriod.values()[randomGenerator.nextInt(ReportPeriod.values().length)]; + builder1.setReportPeriod(reportPeriod); + builder2.setReportPeriod(reportPeriod); + + for (int j = 0; j < 10; j++) { + TestResourceId testResourceId = TestResourceId.getRandomResourceId(randomGenerator); + if (randomGenerator.nextBoolean()) { + builder1.includeResource(testResourceId); + builder2.includeResource(testResourceId); + } else { + builder1.excludeResource(testResourceId); + builder2.excludeResource(testResourceId); + } + } + + boolean defaultInclusion = randomGenerator.nextBoolean(); + builder1.setDefaultInclusion(defaultInclusion).build(); + builder2.setDefaultInclusion(defaultInclusion).build(); + + PersonResourceReportPluginData personResourceReportPluginData1 = builder1.build(); + PersonResourceReportPluginData personResourceReportPluginData2 = builder2.build(); + + // show that the hash code is stable + int hashCode = personResourceReportPluginData1.hashCode(); + assertEquals(hashCode, personResourceReportPluginData1.hashCode()); + assertEquals(hashCode, personResourceReportPluginData1.hashCode()); + assertEquals(hashCode, personResourceReportPluginData1.hashCode()); + assertEquals(hashCode, personResourceReportPluginData1.hashCode()); + + // show that equal objects have equal hash codes + assertEquals(personResourceReportPluginData1.hashCode(), personResourceReportPluginData2.hashCode()); + + // collect the hashcode + observedHashCodes.add(personResourceReportPluginData1.hashCode()); + } + + /* + * The hash codes should be dispersed -- we only show that they are + * unique values -- this is dependent on the random seed + */ + assertEquals(50, observedHashCodes.size()); + + } + + @Test + @UnitTestMethod(target = PersonResourceReportPluginData.class, name = "toString", args = {}) + public void testToString() { + PersonResourceReportPluginData personResourceReportPluginData = PersonResourceReportPluginData.builder()// + .setReportLabel(new SimpleReportLabel("report label"))// + .setDefaultInclusion(true)// + .setReportPeriod(ReportPeriod.DAILY)// + .includeResource(TestResourceId.RESOURCE_1)// + .includeResource(TestResourceId.RESOURCE_3)// + .excludeResource(TestResourceId.RESOURCE_2)// + .excludeResource(TestResourceId.RESOURCE_4)// + .build();// + + String actualValue = + personResourceReportPluginData.toString(); + + String expectedValue = "PersonResourceReportPluginData [data=Data [reportLabel=SimpleReportLabel [value=report label]," + + " reportPeriod=DAILY, includedResourceIds=[RESOURCE_1, RESOURCE_3], excludedResourceIds=[RESOURCE_2, RESOURCE_4]," + + " defaultInclusionPolicy=true]]"; + + assertEquals(actualValue, expectedValue); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/reports/AT_ResourcePropertyReport.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/reports/AT_ResourcePropertyReport.java new file mode 100644 index 000000000..81116bd43 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/reports/AT_ResourcePropertyReport.java @@ -0,0 +1,343 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.reports; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestOutputConsumer; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.resources.datamanagers.ResourcesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.resources.datamanagers.ResourcesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourcePropertyId; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourcePropertyInitialization; +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.ResourcesTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.ResourcesTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.TestResourceId; +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.TestResourcePropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_ResourcePropertyReport { + + public enum TestAuxiliaryResourcePropertyId implements ResourcePropertyId { + + AUX_RESOURCE_PROPERTY_1_BOOLEAN_MUTABLE(TestResourceId.RESOURCE_1, + PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(false).build()), + AUX_RESOURCE_PROPERTY_2_INTEGER_MUTABLE(TestResourceId.RESOURCE_2, + PropertyDefinition.builder().setType(Integer.class).setDefaultValue(0).build()); + + private final TestResourceId testResourceId; + private final PropertyDefinition propertyDefinition; + + public PropertyDefinition getPropertyDefinition() { + return propertyDefinition; + } + + private TestAuxiliaryResourcePropertyId(TestResourceId testResourceId, PropertyDefinition propertyDefinition) { + this.testResourceId = testResourceId; + this.propertyDefinition = propertyDefinition; + } + + public TestResourceId getTestResourceId() { + return testResourceId; + } + + } + + @Test + @UnitTestConstructor(target = ResourcePropertyReport.class, args = { ResourcePropertyReportPluginData.class }) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = ResourcePropertyReport.class, name = "init", args = { ReportContext.class }) + public void testInit() { + int initialPopulation = 20; + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + /* + * create an agent and have it assign various resource properties at + * various times + */ + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0.0, (c) -> { + /* + * note that this is time 0 and should show that property initial + * values are still reported correctly + */ + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_3, + TestResourcePropertyId.ResourceProperty_3_2_STRING_MUTABLE, "A"); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1.0, (c) -> { + // two settings of the same property + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_2, + TestResourcePropertyId.ResourceProperty_2_2_INTEGER_MUTABLE, 45); + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_1, + TestResourcePropertyId.ResourceProperty_1_3_DOUBLE_MUTABLE, 36.7); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2.0, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_4, + TestResourcePropertyId.ResourceProperty_4_1_BOOLEAN_MUTABLE, true); + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_2, + TestResourcePropertyId.ResourceProperty_2_1_BOOLEAN_MUTABLE, false); + + // add new property definitions + for (TestAuxiliaryResourcePropertyId propertyId : TestAuxiliaryResourcePropertyId.values()) { + TestResourceId testResourceId = propertyId.getTestResourceId(); + PropertyDefinition propertyDefinition = propertyId.getPropertyDefinition(); + ResourcePropertyInitialization resourcePropertyInitialization = ResourcePropertyInitialization.builder() + .setResourceId(testResourceId).setResourcePropertyId(propertyId) + .setPropertyDefinition(propertyDefinition).build(); + resourcesDataManager.defineResourceProperty(resourcePropertyInitialization); + } + + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(3.0, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_4, + TestResourcePropertyId.ResourceProperty_4_1_BOOLEAN_MUTABLE, true); + + // note the duplicated value + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_1, + TestResourcePropertyId.ResourceProperty_1_3_DOUBLE_MUTABLE, 2.5); + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_1, + TestResourcePropertyId.ResourceProperty_1_3_DOUBLE_MUTABLE, 2.5); + + // and now a third setting of the same property to a new value + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_1, + TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE, 100); + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_1, + TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE, 60); + + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_1, + TestAuxiliaryResourcePropertyId.AUX_RESOURCE_PROPERTY_1_BOOLEAN_MUTABLE, true); + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_2, + TestAuxiliaryResourcePropertyId.AUX_RESOURCE_PROPERTY_2_INTEGER_MUTABLE, 137); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + + /* + * Collect the expected report items. Note that order does not matter. * + */ + Map expectedReportItems = new LinkedHashMap<>(); + expectedReportItems.put(getReportItem(0.0, TestResourceId.RESOURCE_1, + TestResourcePropertyId.ResourceProperty_1_1_BOOLEAN_MUTABLE, false), 1); + expectedReportItems.put(getReportItem(0.0, TestResourceId.RESOURCE_1, + TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE, 0), 1); + expectedReportItems.put(getReportItem(0.0, TestResourceId.RESOURCE_1, + TestResourcePropertyId.ResourceProperty_1_3_DOUBLE_MUTABLE, 0.0), 1); + expectedReportItems.put(getReportItem(0.0, TestResourceId.RESOURCE_2, + TestResourcePropertyId.ResourceProperty_2_1_BOOLEAN_MUTABLE, true), 1); + expectedReportItems.put(getReportItem(0.0, TestResourceId.RESOURCE_2, + TestResourcePropertyId.ResourceProperty_2_2_INTEGER_MUTABLE, 5), 1); + expectedReportItems.put(getReportItem(0.0, TestResourceId.RESOURCE_3, + TestResourcePropertyId.ResourceProperty_3_1_BOOLEAN_MUTABLE, false), 1); + expectedReportItems.put(getReportItem(0.0, TestResourceId.RESOURCE_3, + TestResourcePropertyId.ResourceProperty_3_2_STRING_MUTABLE, ""), 1); + expectedReportItems.put(getReportItem(0.0, TestResourceId.RESOURCE_4, + TestResourcePropertyId.ResourceProperty_4_1_BOOLEAN_MUTABLE, true), 1); + expectedReportItems.put(getReportItem(0.0, TestResourceId.RESOURCE_5, + TestResourcePropertyId.ResourceProperty_5_1_INTEGER_IMMUTABLE, 7), 1); + expectedReportItems.put(getReportItem(0.0, TestResourceId.RESOURCE_5, + TestResourcePropertyId.ResourceProperty_5_2_DOUBLE_IMMUTABLE, 2.7), 1); + expectedReportItems.put(getReportItem(0.0, TestResourceId.RESOURCE_3, + TestResourcePropertyId.ResourceProperty_3_2_STRING_MUTABLE, "A"), 1); + expectedReportItems.put(getReportItem(1.0, TestResourceId.RESOURCE_2, + TestResourcePropertyId.ResourceProperty_2_2_INTEGER_MUTABLE, 45), 1); + expectedReportItems.put(getReportItem(1.0, TestResourceId.RESOURCE_1, + TestResourcePropertyId.ResourceProperty_1_3_DOUBLE_MUTABLE, 36.7), 1); + expectedReportItems.put(getReportItem(2.0, TestResourceId.RESOURCE_4, + TestResourcePropertyId.ResourceProperty_4_1_BOOLEAN_MUTABLE, true), 1); + expectedReportItems.put(getReportItem(2.0, TestResourceId.RESOURCE_2, + TestResourcePropertyId.ResourceProperty_2_1_BOOLEAN_MUTABLE, false), 1); + expectedReportItems.put(getReportItem(3.0, TestResourceId.RESOURCE_4, + TestResourcePropertyId.ResourceProperty_4_1_BOOLEAN_MUTABLE, true), 1); + expectedReportItems.put(getReportItem(3.0, TestResourceId.RESOURCE_1, + TestResourcePropertyId.ResourceProperty_1_3_DOUBLE_MUTABLE, 2.5), 2); + expectedReportItems.put(getReportItem(3.0, TestResourceId.RESOURCE_1, + TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE, 100), 1); + expectedReportItems.put(getReportItem(3.0, TestResourceId.RESOURCE_1, + TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE, 60), 1); + expectedReportItems.put(getReportItem(2.0, TestResourceId.RESOURCE_1, + TestAuxiliaryResourcePropertyId.AUX_RESOURCE_PROPERTY_1_BOOLEAN_MUTABLE, false), 1); + expectedReportItems.put(getReportItem(2.0, TestResourceId.RESOURCE_2, + TestAuxiliaryResourcePropertyId.AUX_RESOURCE_PROPERTY_2_INTEGER_MUTABLE, 0), 1); + expectedReportItems.put(getReportItem(3.0, TestResourceId.RESOURCE_1, + TestAuxiliaryResourcePropertyId.AUX_RESOURCE_PROPERTY_1_BOOLEAN_MUTABLE, true), 1); + expectedReportItems.put(getReportItem(3.0, TestResourceId.RESOURCE_2, + TestAuxiliaryResourcePropertyId.AUX_RESOURCE_PROPERTY_2_INTEGER_MUTABLE, 137), 1); + + + ResourcesPluginData.Builder resourcesBuilder = ResourcesPluginData.builder(); + + for (TestResourceId testResourceId : TestResourceId.values()) { + resourcesBuilder.addResource(testResourceId,0.0, testResourceId.getTimeTrackingPolicy()); + } + + for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId.values()) { + TestResourceId testResourceId = testResourcePropertyId.getTestResourceId(); + PropertyDefinition propertyDefinition = testResourcePropertyId.getPropertyDefinition(); + resourcesBuilder.defineResourceProperty(testResourceId, testResourcePropertyId, propertyDefinition); + } + + ResourcesPluginData resourcesPluginData = resourcesBuilder.build(); + + + Factory factory = ResourcesTestPluginFactory.factory(initialPopulation, 8914112012010329946L, testPluginData); + factory.setResourcesPluginData(resourcesPluginData); + ResourcePropertyReportPluginData resourcePropertyReportPluginData = ResourcePropertyReportPluginData.builder().setReportLabel(REPORT_LABEL).build(); + factory.setResourcePropertyReportPluginData(resourcePropertyReportPluginData); + + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .build()// + .execute(); + + Map actualReportItems = testOutputConsumer.getOutputItemMap(ReportItem.class); + + assertEquals(expectedReportItems, actualReportItems); + } + + @Test + @UnitTestMethod(target = ResourcePropertyReport.class, name = "init", args = { ReportContext.class }) + public void testInit_State() { + // Test with producing simulation state + + int initialPopulation = 20; + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + /* + * create an agent and have it assign various resource properties at + * various times + */ + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0.0, (c) -> { + /* + * note that this is time 0 and should show that property initial + * values are still reported correctly + */ + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_3, + TestResourcePropertyId.ResourceProperty_3_2_STRING_MUTABLE, "A"); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1.0, (c) -> { + // two settings of the same property + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_2, + TestResourcePropertyId.ResourceProperty_2_2_INTEGER_MUTABLE, 45); + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_1, + TestResourcePropertyId.ResourceProperty_1_3_DOUBLE_MUTABLE, 36.7); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2.0, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_4, + TestResourcePropertyId.ResourceProperty_4_1_BOOLEAN_MUTABLE, true); + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_2, + TestResourcePropertyId.ResourceProperty_2_1_BOOLEAN_MUTABLE, false); + + // add new property definitions + for (TestAuxiliaryResourcePropertyId propertyId : TestAuxiliaryResourcePropertyId.values()) { + TestResourceId testResourceId = propertyId.getTestResourceId(); + PropertyDefinition propertyDefinition = propertyId.getPropertyDefinition(); + ResourcePropertyInitialization resourcePropertyInitialization = ResourcePropertyInitialization.builder() + .setResourceId(testResourceId).setResourcePropertyId(propertyId) + .setPropertyDefinition(propertyDefinition).build(); + resourcesDataManager.defineResourceProperty(resourcePropertyInitialization); + } + + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(3.0, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_4, + TestResourcePropertyId.ResourceProperty_4_1_BOOLEAN_MUTABLE, true); + + // note the duplicated value + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_1, + TestResourcePropertyId.ResourceProperty_1_3_DOUBLE_MUTABLE, 2.5); + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_1, + TestResourcePropertyId.ResourceProperty_1_3_DOUBLE_MUTABLE, 2.5); + + // and now a third setting of the same property to a new value + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_1, + TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE, 100); + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_1, + TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE, 60); + + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_1, + TestAuxiliaryResourcePropertyId.AUX_RESOURCE_PROPERTY_1_BOOLEAN_MUTABLE, true); + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_2, + TestAuxiliaryResourcePropertyId.AUX_RESOURCE_PROPERTY_2_INTEGER_MUTABLE, 137); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + + Factory factory = ResourcesTestPluginFactory.factory(initialPopulation, 8914112012010329946L, testPluginData); + ResourcePropertyReportPluginData resourcePropertyReportPluginData = ResourcePropertyReportPluginData.builder() + .setReportLabel(REPORT_LABEL) + .build(); + factory.setResourcePropertyReportPluginData(resourcePropertyReportPluginData); + + TestOutputConsumer testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .setProduceSimulationStateOnHalt(true)// + .setSimulationHaltTime(20)// + .build()// + .execute(); + + Map outputItems = testOutputConsumer.getOutputItemMap(ResourcePropertyReportPluginData.class); + assertEquals(1, outputItems.size()); + ResourcePropertyReportPluginData resourcePropertyReportPluginData2 = outputItems.keySet().iterator().next(); + assertEquals(resourcePropertyReportPluginData, resourcePropertyReportPluginData2); + + // Test without producing simulation state + + testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .setProduceSimulationStateOnHalt(false)// + .setSimulationHaltTime(20)// + .build()// + .execute(); + + outputItems = testOutputConsumer.getOutputItemMap(ResourcePropertyReportPluginData.class); + assertEquals(0, outputItems.size()); + } + + private static ReportItem getReportItem(Object... values) { + ReportItem.Builder builder = ReportItem.builder(); + builder.setReportLabel(REPORT_LABEL); + builder.setReportHeader(REPORT_HEADER); + for (Object value : values) { + builder.addValue(value); + } + return builder.build(); + } + + private static final ReportLabel REPORT_LABEL = new SimpleReportLabel("resource property report"); + + private static final ReportHeader REPORT_HEADER = ReportHeader.builder().add("time").add("resource").add("property") + .add("value").build(); +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/reports/AT_ResourcePropertyReportPluginData.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/reports/AT_ResourcePropertyReportPluginData.java new file mode 100644 index 000000000..f287fbb17 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/reports/AT_ResourcePropertyReportPluginData.java @@ -0,0 +1,200 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.reports; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportError; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_ResourcePropertyReportPluginData { + + @Test + @UnitTestMethod(target = ResourcePropertyReportPluginData.class, name = "builder", args = {}) + public void testBuilder() { + ResourcePropertyReportPluginData.Builder builder = ResourcePropertyReportPluginData.builder(); + assertNotNull(builder); + } + + @Test + @UnitTestMethod(target = ResourcePropertyReportPluginData.Builder.class, name = "build", args = {}) + public void testBuild() { + // the specific capabilities are covered elsewhere + + // precondition test: if the report label is not assigned + ContractException contractException = assertThrows(ContractException.class, () -> // + ResourcePropertyReportPluginData.builder()// + .build()); + assertEquals(ReportError.NULL_REPORT_LABEL, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ResourcePropertyReportPluginData.Builder.class, name = "setReportLabel", args = { ReportLabel.class }) + public void testSetReportLabel() { + + for (int i = 0; i < 30; i++) { + ReportLabel expectedReportLabel = new SimpleReportLabel(i); + ResourcePropertyReportPluginData resourcePropertyReportPluginData = // + ResourcePropertyReportPluginData.builder()// + .setReportLabel(expectedReportLabel)// + .build(); + + assertEquals(expectedReportLabel, resourcePropertyReportPluginData.getReportLabel()); + } + + // precondition: if the report label is null + ContractException contractException = assertThrows(ContractException.class, () -> { + ResourcePropertyReportPluginData.builder().setReportLabel(null); + }); + assertEquals(ReportError.NULL_REPORT_LABEL, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = ResourcePropertyReportPluginData.class, name = "getReportLabel", args = {}) + public void testGetReportLabel() { + for (int i = 0; i < 30; i++) { + ReportLabel expectedReportLabel = new SimpleReportLabel(i); + ResourcePropertyReportPluginData resourcePropertyReportPluginData = // + ResourcePropertyReportPluginData.builder()// + .setReportLabel(expectedReportLabel)// + .build(); + + assertEquals(expectedReportLabel, resourcePropertyReportPluginData.getReportLabel()); + } + + } + + @Test + @UnitTestMethod(target = ResourcePropertyReportPluginData.class, name = "getCloneBuilder", args = {}) + public void testGetCloneBuilder() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8951274294108578550L); + for (int i = 0; i < 10; i++) { + + // build a ResourcePropertyReportPluginData from random inputs + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt()); + + ResourcePropertyReportPluginData.Builder builder = // + ResourcePropertyReportPluginData.builder()// + .setReportLabel(reportLabel); + + ResourcePropertyReportPluginData resourcePropertyReportPluginData = builder.build(); + + // create the clone builder and have it build + ResourcePropertyReportPluginData cloneResourcePropertyReportPluginData = resourcePropertyReportPluginData.getCloneBuilder().build(); + + // the result should equal the original if the clone builder was + // initialized with the correct state + assertEquals(resourcePropertyReportPluginData, cloneResourcePropertyReportPluginData); + + } + } + + @Test + @UnitTestMethod(target = ResourcePropertyReportPluginData.class, name = "equals", args = { Object.class }) + public void testEquals() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7759639255438669162L); + for (int i = 0; i < 10; i++) { + // build a ResourcePropertyReportPluginData from the same random + // inputs + ResourcePropertyReportPluginData.Builder builder1 = ResourcePropertyReportPluginData.builder(); + ResourcePropertyReportPluginData.Builder builder2 = ResourcePropertyReportPluginData.builder(); + + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt(100)); + builder1.setReportLabel(reportLabel); + builder2.setReportLabel(reportLabel); + + ResourcePropertyReportPluginData resourcePropertyReportPluginData1 = builder1.build(); + ResourcePropertyReportPluginData resourcePropertyReportPluginData2 = builder2.build(); + + assertEquals(resourcePropertyReportPluginData1, resourcePropertyReportPluginData2); + + // show that plugin datas with different inputs are not equal + + // change the report label + reportLabel = new SimpleReportLabel(1000); + resourcePropertyReportPluginData2 = // + resourcePropertyReportPluginData1 .getCloneBuilder()// + .setReportLabel(reportLabel)// + .build(); + assertNotEquals(resourcePropertyReportPluginData2, resourcePropertyReportPluginData1); + } + + } + + @Test + @UnitTestMethod(target = ResourcePropertyReportPluginData.class, name = "hashCode", args = {}) + public void testHashCode() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(9079768427072825406L); + + Set observedHashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 50; i++) { + // build a ResourcePropertyReportPluginData from the same random + // inputs + ResourcePropertyReportPluginData.Builder builder1 = ResourcePropertyReportPluginData.builder(); + ResourcePropertyReportPluginData.Builder builder2 = ResourcePropertyReportPluginData.builder(); + + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt(100)); + builder1.setReportLabel(reportLabel); + builder2.setReportLabel(reportLabel); + + + ResourcePropertyReportPluginData resourcePropertyReportPluginData1 = builder1.build(); + ResourcePropertyReportPluginData resourcePropertyReportPluginData2 = builder2.build(); + + // show that the hash code is stable + int hashCode = resourcePropertyReportPluginData1.hashCode(); + assertEquals(hashCode, resourcePropertyReportPluginData1.hashCode()); + assertEquals(hashCode, resourcePropertyReportPluginData1.hashCode()); + assertEquals(hashCode, resourcePropertyReportPluginData1.hashCode()); + assertEquals(hashCode, resourcePropertyReportPluginData1.hashCode()); + + // show that equal objects have equal hash codes + assertEquals(resourcePropertyReportPluginData1.hashCode(), resourcePropertyReportPluginData2.hashCode()); + + // collect the hashcode + observedHashCodes.add(resourcePropertyReportPluginData1.hashCode()); + } + + /* + * The hash codes should be dispersed -- we only show that they are + * unique values -- this is dependent on the random seed + */ + assertTrue(observedHashCodes.size()>40); + + } + + + + @Test + @UnitTestMethod(target = ResourcePropertyReportPluginData.class, name = "toString", args = {}) + public void testToString() { + for (int i = 0; i < 30; i++) { + ReportLabel expectedReportLabel = new SimpleReportLabel(i); + ResourcePropertyReportPluginData resourcePropertyReportPluginData = // + ResourcePropertyReportPluginData.builder()// + .setReportLabel(expectedReportLabel)// + .build(); + + String actualValue = resourcePropertyReportPluginData.toString(); + String expectedValue = "ResourcePropertyReportPluginData [data=Data [reportLabel=SimpleReportLabel [value="+i+"], locked=true]]"; + + assertEquals(expectedValue, actualValue); + } + + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/reports/AT_ResourceReport.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/reports/AT_ResourceReport.java new file mode 100644 index 000000000..96f195c59 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/reports/AT_ResourceReport.java @@ -0,0 +1,154 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.reports; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestOutputConsumer; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.resources.datamanagers.ResourcesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourcePropertyInitialization; +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.ResourcesTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.TestResourceId; +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.TestResourcePropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import util.annotations.UnitTag; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_ResourceReport { + + @Test + @UnitTestConstructor(target = ResourceReport.class, args = { ResourceReportPluginData.class}) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = ResourceReport.class, name = "init", args = { ReportContext.class }, tags = UnitTag.INCOMPLETE) + public void testInit() { + // incomplete test + } + + @Test + @UnitTestMethod(target = ResourceReport.class, name = "init", args = { ReportContext.class }, tags = UnitTag.INCOMPLETE) + public void testInit_State() { + // Test with producing simulation state + + int initialPopulation = 20; + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + /* + * create an agent and have it assign various resource properties at + * various times + */ + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0.0, (c) -> { + /* + * note that this is time 0 and should show that property initial + * values are still reported correctly + */ + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_3, + TestResourcePropertyId.ResourceProperty_3_2_STRING_MUTABLE, "A"); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1.0, (c) -> { + // two settings of the same property + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_2, + TestResourcePropertyId.ResourceProperty_2_2_INTEGER_MUTABLE, 45); + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_1, + TestResourcePropertyId.ResourceProperty_1_3_DOUBLE_MUTABLE, 36.7); + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2.0, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_4, + TestResourcePropertyId.ResourceProperty_4_1_BOOLEAN_MUTABLE, true); + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_2, + TestResourcePropertyId.ResourceProperty_2_1_BOOLEAN_MUTABLE, false); + + // add new property definitions + for (AT_ResourcePropertyReport.TestAuxiliaryResourcePropertyId propertyId : AT_ResourcePropertyReport.TestAuxiliaryResourcePropertyId.values()) { + TestResourceId testResourceId = propertyId.getTestResourceId(); + PropertyDefinition propertyDefinition = propertyId.getPropertyDefinition(); + ResourcePropertyInitialization resourcePropertyInitialization = ResourcePropertyInitialization.builder() + .setResourceId(testResourceId).setResourcePropertyId(propertyId) + .setPropertyDefinition(propertyDefinition).build(); + resourcesDataManager.defineResourceProperty(resourcePropertyInitialization); + } + + })); + + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(3.0, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_4, + TestResourcePropertyId.ResourceProperty_4_1_BOOLEAN_MUTABLE, true); + + // note the duplicated value + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_1, + TestResourcePropertyId.ResourceProperty_1_3_DOUBLE_MUTABLE, 2.5); + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_1, + TestResourcePropertyId.ResourceProperty_1_3_DOUBLE_MUTABLE, 2.5); + + // and now a third setting of the same property to a new value + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_1, + TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE, 100); + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_1, + TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE, 60); + + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_1, + AT_ResourcePropertyReport.TestAuxiliaryResourcePropertyId.AUX_RESOURCE_PROPERTY_1_BOOLEAN_MUTABLE, true); + resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_2, + AT_ResourcePropertyReport.TestAuxiliaryResourcePropertyId.AUX_RESOURCE_PROPERTY_2_INTEGER_MUTABLE, 137); + })); + + TestPluginData testPluginData = pluginBuilder.build(); + + ResourcesTestPluginFactory.Factory factory = ResourcesTestPluginFactory.factory(initialPopulation, 8914112012010329946L, testPluginData); + ResourceReportPluginData resourceReportPluginData = ResourceReportPluginData.builder() + .setReportLabel(REPORT_LABEL) + .setReportPeriod(ReportPeriod.DAILY) + .setDefaultInclusion(true) + .excludeResource(TestResourceId.RESOURCE_2) + .build(); + factory.setResourceReportPluginData(resourceReportPluginData); + + TestOutputConsumer testOutputConsumer = TestSimulation.builder()// + .addPlugins(factory.getPlugins())// + .setProduceSimulationStateOnHalt(true)// + .setSimulationHaltTime(20)// + .build()// + .execute(); + + Map outputItems = testOutputConsumer.getOutputItemMap(ResourceReportPluginData.class); + assertEquals(1, outputItems.size()); + ResourceReportPluginData resourceReportPluginData2 = outputItems.keySet().iterator().next(); + assertEquals(resourceReportPluginData, resourceReportPluginData2); + + // Test without producing simulation state + + testOutputConsumer = TestSimulation .builder()// + .addPlugins(factory.getPlugins())// + .setProduceSimulationStateOnHalt(false)// + .setSimulationHaltTime(20)// + .build()// + .execute(); + + outputItems = testOutputConsumer.getOutputItemMap(ResourceReportPluginData.class); + assertEquals(0, outputItems.size()); + } + + private static final ReportLabel REPORT_LABEL = new SimpleReportLabel("resource property report"); + +} \ No newline at end of file diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/reports/AT_ResourceReportPluginData.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/reports/AT_ResourceReportPluginData.java new file mode 100644 index 000000000..45936dda2 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/reports/AT_ResourceReportPluginData.java @@ -0,0 +1,637 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.reports; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportError; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceError; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.TestResourceId; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_ResourceReportPluginData { + + @Test + @UnitTestMethod(target = ResourceReportPluginData.class, name = "builder", args = {}) + public void testBuilder() { + ResourceReportPluginData.Builder builder = ResourceReportPluginData.builder(); + assertNotNull(builder); + } + + @Test + @UnitTestMethod(target = ResourceReportPluginData.Builder.class, name = "build", args = {}) + public void testBuild() { + // the specific capabilities are covered elsewhere + + // precondition test: if the report period is not assigned + ContractException contractException = assertThrows(ContractException.class, () -> // + ResourceReportPluginData.builder()// + .setReportLabel(new SimpleReportLabel(getClass()))// + .build()); + assertEquals(ReportError.NULL_REPORT_PERIOD, contractException.getErrorType()); + + // precondition test: if the report label is not assigned + contractException = assertThrows(ContractException.class, () -> // + ResourceReportPluginData.builder()// + .setReportPeriod(ReportPeriod.DAILY)// + .build()); + assertEquals(ReportError.NULL_REPORT_LABEL, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ResourceReportPluginData.Builder.class, name = "setReportLabel", args = { + ReportLabel.class }) + public void testSetReportLabel() { + + for (int i = 0; i < 30; i++) { + ReportLabel expectedReportLabel = new SimpleReportLabel(i); + ResourceReportPluginData resourceReportPluginData = // + ResourceReportPluginData.builder()// + .setReportPeriod(ReportPeriod.DAILY)// + .setReportLabel(expectedReportLabel)// + .build(); + + assertEquals(expectedReportLabel, resourceReportPluginData.getReportLabel()); + } + + // precondition: if the report label is null + ContractException contractException = assertThrows(ContractException.class, () -> { + ResourceReportPluginData.builder().setReportLabel(null); + }); + assertEquals(ReportError.NULL_REPORT_LABEL, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = ResourceReportPluginData.Builder.class, name = "setReportPeriod", args = { + ReportPeriod.class }) + public void testSetReportPeriod() { + + ReportLabel reportLabel = new SimpleReportLabel("report label"); + for (ReportPeriod reportPeriod : ReportPeriod.values()) { + + ResourceReportPluginData resourceReportPluginData = // + ResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .build(); + + assertEquals(reportPeriod, resourceReportPluginData.getReportPeriod()); + } + + // precondition: if the report period is null + ContractException contractException = assertThrows(ContractException.class, () -> { + ResourceReportPluginData.builder().setReportPeriod(null); + }); + assertEquals(ReportError.NULL_REPORT_PERIOD, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = ResourceReportPluginData.Builder.class, name = "setDefaultInclusion", args = { + boolean.class }) + public void testSetDefaultInclusion() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + ReportPeriod reportPeriod = ReportPeriod.DAILY; + + // show the default value is true + ResourceReportPluginData resourceReportPluginData = // + ResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .build(); + assertEquals(true, resourceReportPluginData.getDefaultInclusionPolicy()); + + resourceReportPluginData = // + ResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .setDefaultInclusion(true)// + .build(); + assertEquals(true, resourceReportPluginData.getDefaultInclusionPolicy()); + + resourceReportPluginData = // + ResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .setDefaultInclusion(false)// + .build(); + assertEquals(false, resourceReportPluginData.getDefaultInclusionPolicy()); + + } + + @Test + @UnitTestMethod(target = ResourceReportPluginData.Builder.class, name = "includeResource", args = { + ResourceId.class }) + public void testIncludeResource() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + ReportPeriod reportPeriod = ReportPeriod.DAILY; + + ResourceReportPluginData resourceReportPluginData = // + ResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .build(); + assertTrue(resourceReportPluginData.getIncludedResourceIds().isEmpty()); + + // show that inclusion alone works + Set expectedResourceIds = new LinkedHashSet<>(); + + expectedResourceIds.add(TestResourceId.RESOURCE_1); + expectedResourceIds.add(TestResourceId.RESOURCE_4); + expectedResourceIds.add(TestResourceId.RESOURCE_2); + + ResourceReportPluginData.Builder builder = ResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel);// + + for (ResourceId resourceId : expectedResourceIds) { + builder.includeResource(resourceId); + } + + resourceReportPluginData = builder.build(); + assertEquals(expectedResourceIds, resourceReportPluginData.getIncludedResourceIds()); + + // show that inclusion will override exclusion + expectedResourceIds.clear(); + + expectedResourceIds.add(TestResourceId.RESOURCE_1); + expectedResourceIds.add(TestResourceId.RESOURCE_4); + expectedResourceIds.add(TestResourceId.RESOURCE_2); + + builder = ResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel);// + + for (ResourceId resourceId : expectedResourceIds) { + builder.excludeResource(resourceId); + builder.includeResource(resourceId); + } + + resourceReportPluginData = builder.build(); + assertEquals(expectedResourceIds, resourceReportPluginData.getIncludedResourceIds()); + + // precondition: if the person property id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + ResourceReportPluginData.builder().includeResource(null); + }); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = ResourceReportPluginData.Builder.class, name = "excludeResource", args = { + ResourceId.class }) + public void testExcludeResource() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + ReportPeriod reportPeriod = ReportPeriod.DAILY; + + // show the default is non-exclusion + ResourceReportPluginData resourceReportPluginData = // + ResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .build(); + assertTrue(resourceReportPluginData.getExcludedResourceIds().isEmpty()); + + // show that exclusion alone works + Set expectedResourceIds = new LinkedHashSet<>(); + + expectedResourceIds.add(TestResourceId.RESOURCE_1); + expectedResourceIds.add(TestResourceId.RESOURCE_4); + expectedResourceIds.add(TestResourceId.RESOURCE_2); + + ResourceReportPluginData.Builder builder = ResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel);// + + for (ResourceId resourceId : expectedResourceIds) { + builder.excludeResource(resourceId); + } + + resourceReportPluginData = builder.build(); + assertEquals(expectedResourceIds, resourceReportPluginData.getExcludedResourceIds()); + + // show that exclusion will override inclusion + expectedResourceIds.clear(); + + expectedResourceIds.add(TestResourceId.RESOURCE_1); + expectedResourceIds.add(TestResourceId.RESOURCE_4); + expectedResourceIds.add(TestResourceId.RESOURCE_2); + + builder = ResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel);// + + for (ResourceId resourceId : expectedResourceIds) { + builder.includeResource(resourceId); + builder.excludeResource(resourceId); + } + + resourceReportPluginData = builder.build(); + assertEquals(expectedResourceIds, resourceReportPluginData.getExcludedResourceIds()); + + // precondition: if the person property id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + ResourceReportPluginData.builder().excludeResource(null); + }); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = ResourceReportPluginData.class, name = "getReportLabel", args = {}) + public void testGetReportLabel() { + for (int i = 0; i < 30; i++) { + ReportLabel expectedReportLabel = new SimpleReportLabel(i); + ResourceReportPluginData resourceReportPluginData = // + ResourceReportPluginData.builder()// + .setReportPeriod(ReportPeriod.DAILY)// + .setReportLabel(expectedReportLabel)// + .build(); + + assertEquals(expectedReportLabel, resourceReportPluginData.getReportLabel()); + } + + } + + @Test + @UnitTestMethod(target = ResourceReportPluginData.class, name = "getReportPeriod", args = {}) + public void testGetReportPeriod() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + for (ReportPeriod reportPeriod : ReportPeriod.values()) { + + ResourceReportPluginData resourceReportPluginData = // + ResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .build(); + + assertEquals(reportPeriod, resourceReportPluginData.getReportPeriod()); + } + } + + @Test + @UnitTestMethod(target = ResourceReportPluginData.class, name = "getIncludedResourceIds", args = {}) + public void testGetIncludedResourceIds() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + ReportPeriod reportPeriod = ReportPeriod.DAILY; + + // show the default is non-inclusion + ResourceReportPluginData resourceReportPluginData = // + ResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .build(); + assertTrue(resourceReportPluginData.getIncludedResourceIds().isEmpty()); + + // show that inclusion alone works + Set expectedResourceIds = new LinkedHashSet<>(); + + expectedResourceIds.add(TestResourceId.RESOURCE_1); + expectedResourceIds.add(TestResourceId.RESOURCE_4); + expectedResourceIds.add(TestResourceId.RESOURCE_2); + + ResourceReportPluginData.Builder builder = ResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel);// + + for (ResourceId resourceId : expectedResourceIds) { + builder.includeResource(resourceId); + } + + resourceReportPluginData = builder.build(); + assertEquals(expectedResourceIds, resourceReportPluginData.getIncludedResourceIds()); + + // show that inclusion will override exclusion + expectedResourceIds.clear(); + + expectedResourceIds.add(TestResourceId.RESOURCE_1); + expectedResourceIds.add(TestResourceId.RESOURCE_4); + expectedResourceIds.add(TestResourceId.RESOURCE_2); + + builder = ResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel);// + + for (ResourceId resourceId : expectedResourceIds) { + builder.excludeResource(resourceId); + builder.includeResource(resourceId); + } + + resourceReportPluginData = builder.build(); + assertEquals(expectedResourceIds, resourceReportPluginData.getIncludedResourceIds()); + + } + + @Test + @UnitTestMethod(target = ResourceReportPluginData.class, name = "getExcludedResourceIds", args = {}) + public void testGetExcludedResourceIds() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + ReportPeriod reportPeriod = ReportPeriod.DAILY; + + // show the default is non-exclusion + ResourceReportPluginData resourceReportPluginData = // + ResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .build(); + assertTrue(resourceReportPluginData.getExcludedResourceIds().isEmpty()); + + // show that exclusion alone works + Set expectedResourceIds = new LinkedHashSet<>(); + + expectedResourceIds.add(TestResourceId.RESOURCE_1); + expectedResourceIds.add(TestResourceId.RESOURCE_4); + expectedResourceIds.add(TestResourceId.RESOURCE_2); + + ResourceReportPluginData.Builder builder = ResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel);// + + for (ResourceId resourceId : expectedResourceIds) { + builder.excludeResource(resourceId); + } + + resourceReportPluginData = builder.build(); + assertEquals(expectedResourceIds, resourceReportPluginData.getExcludedResourceIds()); + + // show that exclusion will override inclusion + expectedResourceIds.clear(); + + expectedResourceIds.add(TestResourceId.RESOURCE_1); + expectedResourceIds.add(TestResourceId.RESOURCE_4); + expectedResourceIds.add(TestResourceId.RESOURCE_2); + + builder = ResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel);// + + for (ResourceId resourceId : expectedResourceIds) { + builder.includeResource(resourceId); + builder.excludeResource(resourceId); + } + + resourceReportPluginData = builder.build(); + assertEquals(expectedResourceIds, resourceReportPluginData.getExcludedResourceIds()); + + } + + @Test + @UnitTestMethod(target = ResourceReportPluginData.class, name = "getDefaultInclusionPolicy", args = {}) + public void testGetDefaultInclusionPolicy() { + ReportLabel reportLabel = new SimpleReportLabel("report label"); + ReportPeriod reportPeriod = ReportPeriod.DAILY; + + // show the default value is true + ResourceReportPluginData resourceReportPluginData = // + ResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .build(); + assertEquals(true, resourceReportPluginData.getDefaultInclusionPolicy()); + + resourceReportPluginData = // + ResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .setDefaultInclusion(true)// + .build(); + assertEquals(true, resourceReportPluginData.getDefaultInclusionPolicy()); + + resourceReportPluginData = // + ResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel)// + .setDefaultInclusion(false)// + .build(); + assertEquals(false, resourceReportPluginData.getDefaultInclusionPolicy()); + } + + @Test + @UnitTestMethod(target = ResourceReportPluginData.class, name = "getCloneBuilder", args = {}) + public void testGetCloneBuilder() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8951274294108578550L); + for (int i = 0; i < 10; i++) { + + // build a ResourceReportPluginData from random inputs + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt()); + ReportPeriod reportPeriod = ReportPeriod.values()[randomGenerator.nextInt(ReportPeriod.values().length)]; + + ResourceReportPluginData.Builder builder = // + ResourceReportPluginData.builder()// + .setReportPeriod(reportPeriod)// + .setReportLabel(reportLabel); + + for (int j = 0; j < 10; j++) { + TestResourceId testResourceId = TestResourceId.getRandomResourceId(randomGenerator); + if (randomGenerator.nextBoolean()) { + builder.includeResource(testResourceId); + } else { + builder.excludeResource(testResourceId); + } + } + + builder.setDefaultInclusion(randomGenerator.nextBoolean()).build(); + + ResourceReportPluginData resourceReportPluginData = builder.build(); + + // create the clone builder and have it build + ResourceReportPluginData cloneResourceReportPluginData = resourceReportPluginData.getCloneBuilder().build(); + + // the result should equal the original if the clone builder was + // initialized with the correct state + assertEquals(resourceReportPluginData, cloneResourceReportPluginData); + + } + } + + @Test + @UnitTestMethod(target = ResourceReportPluginData.class, name = "equals", args = { Object.class }) + public void testEquals() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7759639255438669162L); + for (int i = 0; i < 10; i++) { + // build a ResourceReportPluginData from the same random + // inputs + ResourceReportPluginData.Builder builder1 = ResourceReportPluginData.builder(); + ResourceReportPluginData.Builder builder2 = ResourceReportPluginData.builder(); + + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt(100)); + builder1.setReportLabel(reportLabel); + builder2.setReportLabel(reportLabel); + + ReportPeriod reportPeriod = ReportPeriod.values()[randomGenerator.nextInt(ReportPeriod.values().length)]; + builder1.setReportPeriod(reportPeriod); + builder2.setReportPeriod(reportPeriod); + + for (int j = 0; j < 10; j++) { + TestResourceId testResourceId = TestResourceId.getRandomResourceId(randomGenerator); + if (randomGenerator.nextBoolean()) { + builder1.includeResource(testResourceId); + builder2.includeResource(testResourceId); + } else { + builder1.excludeResource(testResourceId); + builder2.excludeResource(testResourceId); + } + } + + boolean defaultInclusion = randomGenerator.nextBoolean(); + builder1.setDefaultInclusion(defaultInclusion).build(); + builder2.setDefaultInclusion(defaultInclusion).build(); + + ResourceReportPluginData resourceReportPluginData1 = builder1.build(); + ResourceReportPluginData resourceReportPluginData2 = builder2.build(); + + assertEquals(resourceReportPluginData1, resourceReportPluginData2); + + // show that plugin datas with different inputs are not equal + + // change the default inclusion + resourceReportPluginData2 = // + resourceReportPluginData1.getCloneBuilder()// + .setDefaultInclusion(!defaultInclusion)// + .build(); + assertNotEquals(resourceReportPluginData2, resourceReportPluginData1); + + // change the report period + int ord = reportPeriod.ordinal() + 1; + ord = ord % ReportPeriod.values().length; + reportPeriod = ReportPeriod.values()[ord]; + resourceReportPluginData2 = // + resourceReportPluginData1.getCloneBuilder()// + .setReportPeriod(reportPeriod)// + .build(); + assertNotEquals(resourceReportPluginData2, resourceReportPluginData1); + + // change the report label + reportLabel = new SimpleReportLabel(1000); + resourceReportPluginData2 = // + resourceReportPluginData1.getCloneBuilder()// + .setReportLabel(reportLabel)// + .build(); + assertNotEquals(resourceReportPluginData2, resourceReportPluginData1); + + // change an included property id + if (!resourceReportPluginData1.getIncludedResourceIds().isEmpty()) { + ResourceId resourceId = resourceReportPluginData1.getIncludedResourceIds().iterator().next(); + resourceReportPluginData2 = // + resourceReportPluginData1.getCloneBuilder()// + .excludeResource(resourceId)// + .build(); + assertNotEquals(resourceReportPluginData2, resourceReportPluginData1); + } + // change an excluded property id + if (!resourceReportPluginData1.getExcludedResourceIds().isEmpty()) { + ResourceId resourceId = resourceReportPluginData1.getExcludedResourceIds().iterator().next(); + resourceReportPluginData2 = // + resourceReportPluginData1.getCloneBuilder()// + .includeResource(resourceId)// + .build(); + assertNotEquals(resourceReportPluginData2, resourceReportPluginData1); + } + + } + + } + + @Test + @UnitTestMethod(target = ResourceReportPluginData.class, name = "hashCode", args = {}) + public void testHashCode() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(9079768427072825406L); + + Set observedHashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 50; i++) { + // build a ResourceReportPluginData from the same random + // inputs + ResourceReportPluginData.Builder builder1 = ResourceReportPluginData.builder(); + ResourceReportPluginData.Builder builder2 = ResourceReportPluginData.builder(); + + ReportLabel reportLabel = new SimpleReportLabel(randomGenerator.nextInt(100)); + builder1.setReportLabel(reportLabel); + builder2.setReportLabel(reportLabel); + + ReportPeriod reportPeriod = ReportPeriod.values()[randomGenerator.nextInt(ReportPeriod.values().length)]; + builder1.setReportPeriod(reportPeriod); + builder2.setReportPeriod(reportPeriod); + + for (int j = 0; j < 10; j++) { + TestResourceId testResourceId = TestResourceId.getRandomResourceId(randomGenerator); + if (randomGenerator.nextBoolean()) { + builder1.includeResource(testResourceId); + builder2.includeResource(testResourceId); + } else { + builder1.excludeResource(testResourceId); + builder2.excludeResource(testResourceId); + } + } + + boolean defaultInclusion = randomGenerator.nextBoolean(); + builder1.setDefaultInclusion(defaultInclusion).build(); + builder2.setDefaultInclusion(defaultInclusion).build(); + + ResourceReportPluginData resourceReportPluginData1 = builder1.build(); + ResourceReportPluginData resourceReportPluginData2 = builder2.build(); + + // show that the hash code is stable + int hashCode = resourceReportPluginData1.hashCode(); + assertEquals(hashCode, resourceReportPluginData1.hashCode()); + assertEquals(hashCode, resourceReportPluginData1.hashCode()); + assertEquals(hashCode, resourceReportPluginData1.hashCode()); + assertEquals(hashCode, resourceReportPluginData1.hashCode()); + + // show that equal objects have equal hash codes + assertEquals(resourceReportPluginData1.hashCode(), resourceReportPluginData2.hashCode()); + + // collect the hashcode + observedHashCodes.add(resourceReportPluginData1.hashCode()); + } + + /* + * The hash codes should be dispersed -- we only show that they are unique + * values -- this is dependent on the random seed + */ + assertEquals(50, observedHashCodes.size()); + + } + + @Test + @UnitTestMethod(target = ResourceReportPluginData.class, name = "toString", args = {}) + public void testToString() { + ResourceReportPluginData.Builder builder = ResourceReportPluginData.builder(); + builder.setReportLabel(new SimpleReportLabel("report label")); + builder.setReportPeriod(ReportPeriod.DAILY); + + boolean include = false; + for (TestResourceId testResourceId : TestResourceId.values()) { + + if (include) { + builder.includeResource(testResourceId); + } else { + builder.excludeResource(testResourceId); + } + include = !include; + } + + builder.setDefaultInclusion(true).build(); + + ResourceReportPluginData resourceReportPluginData = builder.build(); + String actualValue = resourceReportPluginData.toString(); + String expectedValue = "ResourceReportPluginData [data=Data [reportLabel=SimpleReportLabel [value=report label], reportPeriod=DAILY, includedResourceIds=[RESOURCE_2, RESOURCE_4], excludedResourceIds=[RESOURCE_1, RESOURCE_3, RESOURCE_5], defaultInclusionPolicy=true]]"; + assertEquals(expectedValue, actualValue); + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/support/AT_ResourceError.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/support/AT_ResourceError.java new file mode 100644 index 000000000..c0d4ab981 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/support/AT_ResourceError.java @@ -0,0 +1,28 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.support; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestMethod; + +public class AT_ResourceError { + + @Test + @UnitTestMethod(target = ResourceError.class, name = "getDescription", args = {}) + public void test() { + // show that each description is a unique, non-null and non-empty string + Set descriptions = new LinkedHashSet<>(); + for (ResourceError resourceError : ResourceError.values()) { + String description = resourceError.getDescription(); + assertNotNull(description, "null description for " + resourceError); + assertTrue(description.length() > 0, "empty string for " + resourceError); + boolean unique = descriptions.add(description); + assertTrue(unique, "description for " + resourceError + " is not unique"); + } + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/support/AT_ResourceFilter.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/support/AT_ResourceFilter.java new file mode 100644 index 000000000..c2cfd84ec --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/support/AT_ResourceFilter.java @@ -0,0 +1,250 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.Equality; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.FilterSensitivity; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionError; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters.Filter; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.TestPartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.resources.datamanagers.ResourcesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.resources.events.PersonResourceUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.ResourcesTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.ResourcesTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.TestResourceId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_ResourceFilter { + @Test + @UnitTestMethod(target = ResourceFilter.class, name = "getFilterSensitivities", args = {}) + public void testGetFilterSensitivities() { + + Factory factory = ResourcesTestPluginFactory.factory(12, 5802033011343021047L, (c) -> { + Filter filter = new ResourceFilter(TestResourceId.RESOURCE_1, Equality.EQUAL, 12L); + + Set> filterSensitivities = filter.getFilterSensitivities(); + assertNotNull(filterSensitivities); + assertEquals(filterSensitivities.size(), 1); + + FilterSensitivity filterSensitivity = filterSensitivities.iterator().next(); + assertEquals(PersonResourceUpdateEvent.class, filterSensitivity.getEventClass()); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = ResourceFilter.class, name = "validate", args = { PartitionsContext.class }) + public void testValidate() { + + Factory factory = ResourcesTestPluginFactory.factory(12, 6989281647149803633L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + // if the equality operator is null + ContractException contractException = assertThrows(ContractException.class, + () -> new ResourceFilter(TestResourceId.RESOURCE_1, null, 12L).validate(testPartitionsContext)); + assertEquals(PartitionError.NULL_EQUALITY_OPERATOR, contractException.getErrorType()); + + // ResourceError.NULL_RESOURCE_ID + contractException = assertThrows(ContractException.class, + () -> new ResourceFilter(null, Equality.GREATER_THAN, 12L).validate(testPartitionsContext)); + assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); + + // NucleusError.NULL_CONTEXT + contractException = assertThrows(ContractException.class, + () -> new ResourceFilter(TestResourceId.RESOURCE_1, Equality.GREATER_THAN, 12L).validate(null)); + assertEquals(NucleusError.NULL_SIMULATION_CONTEXT, contractException.getErrorType()); + + // ResourceError.UNKNOWN_RESOURCE_ID + contractException = assertThrows(ContractException.class, + () -> new ResourceFilter(TestResourceId.getUnknownResourceId(), Equality.GREATER_THAN, 12L) + .validate(testPartitionsContext)); + assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = ResourceFilter.class, name = "evaluate", args = { PartitionsContext.class, + PersonId.class }) + public void testEvaluate() { + + Factory factory = ResourcesTestPluginFactory.factory(100, 5313696152098995059L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + Filter filter = new ResourceFilter(TestResourceId.RESOURCE_1, Equality.GREATER_THAN, 12L); + + for (PersonId personId : peopleDataManager.getPeople()) { + long amount = randomGenerator.nextInt(10) + 7; + RegionId regionId = regionsDataManager.getPersonRegion(personId); + resourcesDataManager.addResourceToRegion(TestResourceId.RESOURCE_1, regionId, amount); + resourcesDataManager.transferResourceToPersonFromRegion(TestResourceId.RESOURCE_1, personId, amount); + } + + for (PersonId personId : peopleDataManager.getPeople()) { + long personResourceLevel = resourcesDataManager.getPersonResourceLevel(TestResourceId.RESOURCE_1, + personId); + boolean expected = personResourceLevel > 12L; + boolean actual = filter.evaluate(testPartitionsContext, personId); + assertEquals(expected, actual); + } + + /* precondition: if the context is null */ + assertThrows(RuntimeException.class, () -> filter.evaluate(null, new PersonId(0))); + + /* precondition: if the person id is null */ + assertThrows(RuntimeException.class, () -> filter.evaluate(testPartitionsContext, null)); + + /* precondition: if the person id is unknown */ + assertThrows(RuntimeException.class, () -> filter.evaluate(testPartitionsContext, new PersonId(123412342))); + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestConstructor(target = ResourceFilter.class, args = { ResourceId.class, Equality.class, long.class }) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = ResourceFilter.class, name = "getResourceValue", args = {}) + public void testGetResourceValue() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6760936828235979053L); + + for (int i = 0; i < 30; i++) { + long value = randomGenerator.nextLong(); + ResourceFilter resourceFilter = new ResourceFilter(TestResourceId.RESOURCE_1, Equality.EQUAL, value); + assertEquals(value, resourceFilter.getResourceValue()); + } + } + + @Test + @UnitTestMethod(target = ResourceFilter.class, name = "getResourceId", args = {}) + public void testGetResourceId() { + for (TestResourceId testResourceId : TestResourceId.values()) { + ResourceFilter resourceFilter = new ResourceFilter(testResourceId, Equality.EQUAL, 12L); + assertEquals(testResourceId, resourceFilter.getResourceId()); + } + } + + @Test + @UnitTestMethod(target = ResourceFilter.class, name = "getEquality", args = {}) + public void testGetEquality() { + for (Equality equality : Equality.values()) { + ResourceFilter resourceFilter = new ResourceFilter(TestResourceId.RESOURCE_1, equality, 12L); + assertEquals(equality, resourceFilter.getEquality()); + } + } + + private ResourceFilter getRandomResourceFilter(long seed) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + TestResourceId randomResourceId = TestResourceId.getRandomResourceId(randomGenerator); + Equality randomEquality = Equality.getRandomEquality(randomGenerator); + long value = randomGenerator.nextLong(); + ResourceFilter result = new ResourceFilter(randomResourceId, randomEquality, value); + return result; + } + + @Test + @UnitTestMethod(target = ResourceFilter.class, name = "equals", args = { Object.class }) + public void testEquals() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7631979699053748572L); + + // never equal to null + for (int i = 0; i < 30; i++) { + ResourceFilter resourceFilter = getRandomResourceFilter(randomGenerator.nextLong()); + assertFalse(resourceFilter.equals(null)); + } + + // reflexive + for (int i = 0; i < 30; i++) { + ResourceFilter resourceFilter = getRandomResourceFilter(randomGenerator.nextLong()); + assertTrue(resourceFilter.equals(resourceFilter)); + } + + // symmetric, transitive and consistent + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + ResourceFilter resourceFilter1 = getRandomResourceFilter(seed); + ResourceFilter resourceFilter2 = getRandomResourceFilter(seed); + assertTrue(resourceFilter1.equals(resourceFilter2)); + assertTrue(resourceFilter2.equals(resourceFilter1)); + } + + //different inputs yield non-equal objects + for (int i = 0; i < 30; i++) { + ResourceFilter resourceFilter1 = getRandomResourceFilter(randomGenerator.nextLong()); + ResourceFilter resourceFilter2 = getRandomResourceFilter(randomGenerator.nextLong()); + assertNotEquals(resourceFilter1, resourceFilter2); + } + + } + + + @Test + @UnitTestMethod(target = ResourceFilter.class, name = "hashCode", args = {}) + public void testHashCode() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(428701786790006362L); + + //equal objects have equal hash codes + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + ResourceFilter resourceFilter1 = getRandomResourceFilter(seed); + ResourceFilter resourceFilter2 = getRandomResourceFilter(seed); + assertEquals(resourceFilter1,resourceFilter2); + assertEquals(resourceFilter1.hashCode(),resourceFilter2.hashCode()); + } + + //hash codes are reasonably distributed + Set hashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + ResourceFilter resourceFilter = getRandomResourceFilter(randomGenerator.nextLong()); + hashCodes.add(resourceFilter.hashCode()); + } + + assertTrue(hashCodes.size()>90); + } + + @Test + @UnitTestMethod(target = ResourceFilter.class, name = "toString", args = {}) + public void testToString() { + ResourceFilter resourceFilter = new ResourceFilter(TestResourceId.RESOURCE_1, Equality.EQUAL, 15L); + String expectedValue = "ResourceFilter [resourceId=RESOURCE_1, resourceValue=15, equality=EQUAL]"; + String actualValue = resourceFilter.toString(); + assertEquals(expectedValue, actualValue); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/support/AT_ResourceInitialization.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/support/AT_ResourceInitialization.java new file mode 100644 index 000000000..0c2804c7f --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/support/AT_ResourceInitialization.java @@ -0,0 +1,124 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.TestResourceId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.random.RandomGeneratorProvider; + +public class AT_ResourceInitialization { + + @Test + @UnitTestConstructor(target = ResourceInitialization.class, args = { ResourceId.class, Long.class }) + public void testConstructor() { + // nothing to test + } + + @Test + @UnitTestMethod(target = ResourceInitialization.class, name = "getResourceId", args = {}) + public void testGetResourceId() { + for (TestResourceId testResourceId : TestResourceId.values()) { + ResourceInitialization resourceInitialization = new ResourceInitialization(testResourceId, 123L); + assertEquals(testResourceId, resourceInitialization.getResourceId()); + } + } + + @Test + @UnitTestMethod(target = ResourceInitialization.class, name = "getAmount", args = {}) + public void testGetAmount() { + for (long value = 0; value < 10; value++) { + ResourceInitialization resourceInitialization = new ResourceInitialization(TestResourceId.RESOURCE_3, + value); + assertEquals(value, resourceInitialization.getAmount().longValue()); + } + } + + @Test + @UnitTestMethod(target = ResourceInitialization.class, name = "toString", args = {}) + public void testToString() { + ResourceInitialization resourceInitialization = new ResourceInitialization(TestResourceId.RESOURCE_3, 15L); + assertEquals("ResourceAssignment [resourceId=RESOURCE_3, amount=15]", resourceInitialization.toString()); + } + + private ResourceInitialization getRandomResourceInitialization(long seed) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + return new ResourceInitialization(TestResourceId.getRandomResourceId(randomGenerator), + (long) randomGenerator.nextInt(100000)); + } + + @Test + @UnitTestMethod(target = ResourceInitialization.class, name = "equals", args = { Object.class }) + public void testEquals() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8342493324811391268L); + + // never equal to null + for (int i = 0; i < 30; i++) { + ResourceInitialization resourceInitialization = getRandomResourceInitialization(randomGenerator.nextLong()); + assertFalse(resourceInitialization.equals(null)); + } + + // reflexive + for (int i = 0; i < 30; i++) { + ResourceInitialization resourceInitialization = getRandomResourceInitialization(randomGenerator.nextLong()); + assertTrue(resourceInitialization.equals(resourceInitialization)); + } + + // symmetric, transitive, consistent + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + ResourceInitialization resourceInitialization1 = getRandomResourceInitialization(seed); + ResourceInitialization resourceInitialization2 = getRandomResourceInitialization(seed); + for (int j = 0; j < 5; j++) { + assertTrue(resourceInitialization1.equals(resourceInitialization2)); + assertTrue(resourceInitialization1.equals(resourceInitialization2)); + } + } + + // Different inputs yield unequal objects. There is a low probability with + // 500,000 possible values + Set resourceInitializations = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + ResourceInitialization resourceInitialization = getRandomResourceInitialization(randomGenerator.nextLong()); + resourceInitializations.add(resourceInitialization); + + } + + assertEquals(100, resourceInitializations.size()); + + } + + @Test + @UnitTestMethod(target = ResourceInitialization.class, name = "hashCode", args = {}) + public void testHashCode() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5406419164101868160L); + + // equal objects have equal hash codes + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + ResourceInitialization resourceInitialization1 = getRandomResourceInitialization(seed); + ResourceInitialization resourceInitialization2 = getRandomResourceInitialization(seed); + assertEquals(resourceInitialization1,resourceInitialization2); + assertEquals(resourceInitialization1.hashCode(),resourceInitialization2.hashCode()); + } + + //hash codes are reasonably distributed + Set hashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + ResourceInitialization resourceInitialization = getRandomResourceInitialization(randomGenerator.nextLong()); + hashCodes.add(resourceInitialization.hashCode()); + + } + + assertEquals(100, hashCodes.size()); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/support/AT_ResourceLabeler.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/support/AT_ResourceLabeler.java new file mode 100644 index 000000000..589eb344d --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/support/AT_ResourceLabeler.java @@ -0,0 +1,247 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.LabelerSensitivity; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.partitions.testsupport.TestPartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.resources.datamanagers.ResourcesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.resources.events.PersonResourceUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.ResourcesTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.ResourcesTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.TestResourceId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + +public final class AT_ResourceLabeler { + private static class LocalResourceLabeler extends ResourceLabeler { + private final Function resourceLabelingFunction; + + public LocalResourceLabeler(ResourceId resourceId, Function resourceLabelingFunction) { + super(resourceId); + this.resourceLabelingFunction = resourceLabelingFunction; + } + + @Override + protected Object getLabelFromAmount(long amount) { + return resourceLabelingFunction.apply(amount); + } + } + + @Test + @UnitTestConstructor(target = ResourceLabeler.class, args = { ResourceId.class }) + public void testConstructor() { + assertNotNull(new LocalResourceLabeler(TestResourceId.RESOURCE_3, (v) -> null)); + } + + @Test + @UnitTestMethod(target = ResourceLabeler.class, name = "getLabelerSensitivities", args = {}) + public void testGetLabelerSensitivities() { + /* + * Get the labeler sensitivities and show that they are consistent with their + * documented behaviors. + */ + ResourceLabeler resourceLabeler = new LocalResourceLabeler(TestResourceId.RESOURCE_1, (c) -> null); + + Set> labelerSensitivities = resourceLabeler.getLabelerSensitivities(); + + // show that there is exactly one sensitivity + assertEquals(1, labelerSensitivities.size()); + + // show that the sensitivity is associated with + // PersonResourceUpdateEvent + LabelerSensitivity labelerSensitivity = labelerSensitivities.iterator().next(); + assertEquals(PersonResourceUpdateEvent.class, labelerSensitivity.getEventClass()); + + // show that the sensitivity will return the person id from a + // PersonResourceUpdateEvent + PersonId personId = new PersonId(56); + + // show that an event that does not match the resource type will not + // return a person id + PersonResourceUpdateEvent personResourceUpdateEvent = new PersonResourceUpdateEvent(personId, + TestResourceId.RESOURCE_4, 25L, 17L); + Optional optional = labelerSensitivity.getPersonId(personResourceUpdateEvent); + assertFalse(optional.isPresent()); + + // show that an event that matches the resource type will return the + // correct person id + personResourceUpdateEvent = new PersonResourceUpdateEvent(personId, TestResourceId.RESOURCE_1, 25L, 17L); + optional = labelerSensitivity.getPersonId(personResourceUpdateEvent); + assertTrue(optional.isPresent()); + + assertEquals(personId, optional.get()); + } + + @Test + @UnitTestMethod(target = ResourceLabeler.class, name = "getCurrentLabel", args = { PartitionsContext.class, + PersonId.class }) + public void testGetCurrentLabel() { + /* + * Create a resource labeler from a function. Have an agent apply the function + * directly to a person's resource to get a label for that person. Get the label + * from the resource labeler from the person id alone. Compare the two labels + * for equality. + */ + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // build a resource labeler with a function that can be tested + Function function = (v) -> { + return v % 2; + }; + + ResourceLabeler resourceLabeler = new LocalResourceLabeler(TestResourceId.RESOURCE_1, function); + + // distribute random resources across people + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + for (PersonId personId : peopleDataManager.getPeople()) { + RegionId regionId = regionsDataManager.getPersonRegion(personId); + for (TestResourceId testResourceId : TestResourceId.values()) { + long amount = randomGenerator.nextInt(100) + 1; + resourcesDataManager.addResourceToRegion(testResourceId, regionId, amount); + resourcesDataManager.transferResourceToPersonFromRegion(testResourceId, personId, amount); + } + } + })); + + /* + * Have the agent show that the resource labeler created above produces a label + * for each person that is consistent with the function passed to the resource + * labeler. + */ + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); + List people = peopleDataManager.getPeople(); + for (PersonId personId : people) { + + // get the person's resource and apply the function directly + long personResourceLevel = resourcesDataManager.getPersonResourceLevel(TestResourceId.RESOURCE_1, + personId); + Object expectedLabel = function.apply(personResourceLevel); + + // get the label from the person id + Object actualLabel = resourceLabeler.getCurrentLabel(testPartitionsContext, personId); + + // show that the two labels are equal + assertEquals(expectedLabel, actualLabel); + + } + })); + + // test preconditions + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + // if the person does not exist + ContractException contractException = assertThrows(ContractException.class, + () -> resourceLabeler.getCurrentLabel(testPartitionsContext, new PersonId(10000))); + assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); + + // if the person id is null + contractException = assertThrows(ContractException.class, + () -> resourceLabeler.getCurrentLabel(testPartitionsContext, null)); + assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); + + })); + + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = ResourcesTestPluginFactory.factory(10, 7394122902151816457L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = ResourceLabeler.class, name = "getId", args = {}) + public void testGetId() { + for (TestResourceId testResourceId : TestResourceId.values()) { + assertEquals(testResourceId, new LocalResourceLabeler(testResourceId, (c) -> null).getId()); + } + } + + @Test + @UnitTestMethod(target = ResourceLabeler.class, name = "getPastLabel", args = { PartitionsContext.class, + Event.class }) + public void testGetPastLabel() { + Factory factory = ResourcesTestPluginFactory.factory(10, 6601261985382450295L, (c) -> { + + TestPartitionsContext testPartitionsContext = new TestPartitionsContext(c); + + final PersonId personId = new PersonId(45); + final ResourceId resourceId = TestResourceId.RESOURCE_4; + long previousResourceLevel = 14L; + final long currentResourceLevel = 99L; + + Function function = (v) -> { + return v % 2; + }; + + ResourceLabeler resourceLabeler = new LocalResourceLabeler(TestResourceId.RESOURCE_1, function); + + for (int i = 0; i < 10; i++) { + previousResourceLevel = i; + Object expectedLabel = function.apply(previousResourceLevel); + PersonResourceUpdateEvent personResourceUpdateEvent = new PersonResourceUpdateEvent(personId, + resourceId, previousResourceLevel, currentResourceLevel); + Object actualLabel = resourceLabeler.getPastLabel(testPartitionsContext, personResourceUpdateEvent); + assertEquals(expectedLabel, actualLabel); + } + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = ResourceLabeler.class, name = "toString", args = {}) + public void testToString() { + for (TestResourceId testResourceId : TestResourceId.values()) { + LocalResourceLabeler localResourceLabeler = new LocalResourceLabeler(testResourceId, (c) -> null); + String actualValue = localResourceLabeler.toString(); + String expectedValue = "ResourceLabeler [resourceId=" + testResourceId + "]"; + assertEquals(expectedValue, actualValue); + } + } + + @Test + @UnitTestMethod(target = ResourceLabeler.class, name = "getResourceId", args = {}) + public void testGetResourceId() { + for (TestResourceId testResourceId : TestResourceId.values()) { + ResourceLabeler resourceLabeler = new LocalResourceLabeler(testResourceId, (value) -> null); + assertEquals(testResourceId, resourceLabeler.getResourceId()); + } + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/support/AT_ResourcePropertyInitialization.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/support/AT_ResourcePropertyInitialization.java new file mode 100644 index 000000000..28638266b --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/support/AT_ResourcePropertyInitialization.java @@ -0,0 +1,248 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.TestResourceId; +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.TestResourcePropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyError; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + +public class AT_ResourcePropertyInitialization { + + @Test + @UnitTestMethod(target = ResourcePropertyInitialization.class, name = "builder", args = {}) + public void testBuilder() { + assertNotNull(ResourcePropertyInitialization.builder()); + } + + @Test + @UnitTestMethod(target = ResourcePropertyInitialization.Builder.class, name = "build", args = {}) + public void testBuild() { + ResourcePropertyInitialization.Builder builder = ResourcePropertyInitialization.builder(); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).build(); + TestResourcePropertyId testResourcePropertyId = TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE; + TestResourceId testResourceId = TestResourceId.RESOURCE_1; + + builder.setPropertyDefinition(propertyDefinition); + builder.setValue(1); + builder.setResourceId(testResourceId); + builder.setResourcePropertyId(testResourcePropertyId); + ResourcePropertyInitialization resourcePropertyInitialization = builder.build(); + assertNotNull(resourcePropertyInitialization); + assertEquals(resourcePropertyInitialization.getValue().get(), 1); + assertEquals(resourcePropertyInitialization.getResourcePropertyId(), testResourcePropertyId); + assertEquals(resourcePropertyInitialization.getResourceId(), testResourceId); + assertEquals(resourcePropertyInitialization.getPropertyDefinition(), propertyDefinition); + + // precondition test: if property definition is not set + + ContractException definitionContractException = assertThrows(ContractException.class, () -> { + ResourcePropertyInitialization .builder()// + .setResourceId(testResourceId)// + .setValue(2)// + .setResourcePropertyId(testResourcePropertyId)// + .build();// + }); + assertEquals(definitionContractException.getErrorType(), PropertyError.NULL_PROPERTY_DEFINITION); + + // precondition test: if property id is not set + ContractException propertyIdContractException = assertThrows(ContractException.class, () -> { + ResourcePropertyInitialization .builder()// + .setPropertyDefinition(propertyDefinition)// + .setValue(3)// + .setResourceId(testResourceId)// + .build();// + }); + assertEquals(propertyIdContractException.getErrorType(), PropertyError.NULL_PROPERTY_ID); + + // precondition test: if resource id is not set + + ContractException resourceIdContractException = assertThrows(ContractException.class, () -> { + ResourcePropertyInitialization .builder()// + .setPropertyDefinition(propertyDefinition)// + .setValue(4)// + .setResourcePropertyId(testResourcePropertyId)// + .build();// + }); + assertEquals(resourceIdContractException.getErrorType(), ResourceError.NULL_RESOURCE_ID); + + // precondition test: if the value is not set + + ContractException insufficientValueContractException = assertThrows(ContractException.class, () -> { + ResourcePropertyInitialization .builder()// + .setPropertyDefinition(propertyDefinition)// + .setResourcePropertyId(testResourcePropertyId)// + .setResourceId(testResourceId)// + .build(); + }); + assertEquals(insufficientValueContractException.getErrorType(), PropertyError.INSUFFICIENT_PROPERTY_VALUE_ASSIGNMENT); + + // precondition test: if the value in incompatible with the property + // definition + + ContractException incompatibleContractException = assertThrows(ContractException.class, () -> { + ResourcePropertyInitialization .builder()// + .setPropertyDefinition(propertyDefinition)// + .setValue("this is a string")// + .setResourcePropertyId(testResourcePropertyId)// + .setResourceId(testResourceId)// + .build(); + }); + assertEquals(incompatibleContractException.getErrorType(), PropertyError.INCOMPATIBLE_VALUE); + } + + @Test + @UnitTestMethod(target = ResourcePropertyInitialization.Builder.class, name = "setResourceId", args = { ResourceId.class }) + public void testSetResourceId() { + ResourcePropertyInitialization.Builder builder = ResourcePropertyInitialization.builder(); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).build(); + TestResourcePropertyId testResourcePropertyId = TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE; + TestResourceId testResourceId = TestResourceId.RESOURCE_1; + + // precondition test: if resource id is null + ContractException resourceIdContractException = assertThrows(ContractException.class, + () -> builder.setPropertyDefinition(propertyDefinition).setValue(6).setResourcePropertyId(testResourcePropertyId).setResourceId(null)); + assertEquals(resourceIdContractException.getErrorType(), ResourceError.NULL_RESOURCE_ID); + + ResourcePropertyInitialization resourcePropertyInitialization = builder .setPropertyDefinition(propertyDefinition).setValue(6).setPropertyDefinition(propertyDefinition) + .setResourcePropertyId(testResourcePropertyId).setResourceId(testResourceId).build(); + + assertEquals(resourcePropertyInitialization.getResourceId(), testResourceId); + } + + @Test + @UnitTestMethod(target = ResourcePropertyInitialization.Builder.class, name = "setResourcePropertyId", args = { ResourcePropertyId.class }) + public void testSetResourcePropertyId() { + ResourcePropertyInitialization.Builder builder = ResourcePropertyInitialization.builder(); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).build(); + TestResourcePropertyId testResourcePropertyId = TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE; + TestResourceId testResourceId = TestResourceId.RESOURCE_1; + + // precondition test: if resource property id is null + ContractException resourceIdContractException = assertThrows(ContractException.class, + () -> builder.setPropertyDefinition(propertyDefinition).setValue(7).setResourceId(testResourceId).setResourcePropertyId(null)); + assertEquals(resourceIdContractException.getErrorType(), PropertyError.NULL_PROPERTY_ID); + + ResourcePropertyInitialization resourcePropertyInitialization = builder .setPropertyDefinition(propertyDefinition).setValue(6).setPropertyDefinition(propertyDefinition) + .setResourcePropertyId(testResourcePropertyId).setResourceId(testResourceId).build(); + + assertEquals(resourcePropertyInitialization.getResourcePropertyId(), testResourcePropertyId); + } + + @Test + @UnitTestMethod(target = ResourcePropertyInitialization.Builder.class, name = "setPropertyDefinition", args = { PropertyDefinition.class }) + public void testSetPropertyDefinition() { + ResourcePropertyInitialization.Builder builder = ResourcePropertyInitialization.builder(); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).build(); + TestResourcePropertyId testResourcePropertyId = TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE; + TestResourceId testResourceId = TestResourceId.RESOURCE_1; + + // precondition test: if the property definition is null + ContractException resourceIdContractException = assertThrows(ContractException.class, + () -> builder.setValue(8).setResourceId(testResourceId).setResourcePropertyId(testResourcePropertyId).setPropertyDefinition(null)); + assertEquals(resourceIdContractException.getErrorType(), PropertyError.NULL_PROPERTY_DEFINITION); + + ResourcePropertyInitialization resourcePropertyInitialization = builder .setPropertyDefinition(propertyDefinition).setValue(6).setPropertyDefinition(propertyDefinition) + .setResourcePropertyId(testResourcePropertyId).setResourceId(testResourceId).build(); + + assertEquals(resourcePropertyInitialization.getPropertyDefinition(), propertyDefinition); + } + + @Test + @UnitTestMethod(target = ResourcePropertyInitialization.Builder.class, name = "setValue", args = { Object.class }) + public void testSetValue() { + ResourcePropertyInitialization.Builder builder = ResourcePropertyInitialization.builder(); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).build(); + TestResourcePropertyId testResourcePropertyId = TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE; + TestResourceId testResourceId = TestResourceId.RESOURCE_1; + + // precondition test: if the value is null + ContractException resourceIdContractException = assertThrows(ContractException.class, + () -> builder.setResourceId(testResourceId).setResourcePropertyId(testResourcePropertyId).setPropertyDefinition(propertyDefinition).setValue(null)); + assertEquals(resourceIdContractException.getErrorType(), PropertyError.NULL_PROPERTY_VALUE); + + ResourcePropertyInitialization resourcePropertyInitialization = builder .setPropertyDefinition(propertyDefinition).setValue(6).setPropertyDefinition(propertyDefinition) + .setResourcePropertyId(testResourcePropertyId).setResourceId(testResourceId).build(); + + assertEquals(resourcePropertyInitialization.getValue().get(), 6); + } + + @Test + @UnitTestMethod(target = ResourcePropertyInitialization.class, name = "getResourcePropertyId", args = {}) + public void testGetResourcePropertyId() { + ResourcePropertyInitialization.Builder builder = ResourcePropertyInitialization.builder(); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).build(); + TestResourcePropertyId testResourcePropertyId = TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE; + TestResourceId testResourceId = TestResourceId.RESOURCE_1; + + builder.setPropertyDefinition(propertyDefinition); + builder.setResourceId(testResourceId); + builder.setResourcePropertyId(testResourcePropertyId); + builder.setValue(9); + ResourcePropertyInitialization resourcePropertyInitialization = builder.build(); + + assertNotNull(resourcePropertyInitialization.getResourcePropertyId()); + assertEquals(resourcePropertyInitialization.getResourcePropertyId(), testResourcePropertyId); + } + + @Test + @UnitTestMethod(target = ResourcePropertyInitialization.class, name = "getResourceId", args = {}) + public void testGetResourceId() { + ResourcePropertyInitialization.Builder builder = ResourcePropertyInitialization.builder(); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).build(); + TestResourcePropertyId testResourcePropertyId = TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE; + TestResourceId testResourceId = TestResourceId.RESOURCE_1; + + builder.setPropertyDefinition(propertyDefinition); + builder.setResourceId(testResourceId); + builder.setResourcePropertyId(testResourcePropertyId); + builder.setValue(10); + ResourcePropertyInitialization resourcePropertyInitialization = builder.build(); + + assertNotNull(resourcePropertyInitialization.getResourceId()); + assertEquals(resourcePropertyInitialization.getResourceId(), testResourceId); + } + + @Test + @UnitTestMethod(target = ResourcePropertyInitialization.class, name = "getPropertyDefinition", args = {}) + public void testGetPropertyDefinition() { + ResourcePropertyInitialization.Builder builder = ResourcePropertyInitialization.builder(); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).build(); + TestResourcePropertyId testResourcePropertyId = TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE; + TestResourceId testResourceId = TestResourceId.RESOURCE_1; + + builder.setPropertyDefinition(propertyDefinition); + builder.setResourceId(testResourceId); + builder.setResourcePropertyId(testResourcePropertyId); + builder.setValue(11); + ResourcePropertyInitialization resourcePropertyInitialization = builder.build(); + + assertNotNull(resourcePropertyInitialization.getPropertyDefinition()); + assertEquals(resourcePropertyInitialization.getPropertyDefinition(), propertyDefinition); + } + + @Test + @UnitTestMethod(target = ResourcePropertyInitialization.class, name = "getValue", args = {}) + public void testGetValue() { + ResourcePropertyInitialization.Builder builder = ResourcePropertyInitialization.builder(); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).build(); + TestResourcePropertyId testResourcePropertyId = TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE; + TestResourceId testResourceId = TestResourceId.RESOURCE_1; + + builder.setPropertyDefinition(propertyDefinition); + builder.setResourceId(testResourceId); + builder.setResourcePropertyId(testResourcePropertyId); + builder.setValue(12); + ResourcePropertyInitialization resourcePropertyInitialization = builder.build(); + + assertNotNull(resourcePropertyInitialization.getValue()); + assertEquals(resourcePropertyInitialization.getValue().get(), 12); + } + +} \ No newline at end of file diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/testsupport/AT_ResourcesTestPluginFactory.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/testsupport/AT_ResourcesTestPluginFactory.java new file mode 100644 index 000000000..7f22d5316 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/testsupport/AT_ResourcesTestPluginFactory.java @@ -0,0 +1,403 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.testsupport; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.TestFactoryUtil; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginId; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePluginId; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeoplePluginData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonRange; +import gov.hhs.aspr.ms.gcm.plugins.regions.RegionsPluginId; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionError; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.TestRegionId; +import gov.hhs.aspr.ms.gcm.plugins.regions.testsupport.TestRegionPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.SimpleReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.resources.ResourcesPluginId; +import gov.hhs.aspr.ms.gcm.plugins.resources.datamanagers.ResourcesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.resources.reports.PersonResourceReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.resources.reports.ResourcePropertyReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.resources.reports.ResourceReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceError; +import gov.hhs.aspr.ms.gcm.plugins.resources.testsupport.ResourcesTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPluginId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.StochasticsError; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; +import util.wrappers.MutableBoolean; + +public class AT_ResourcesTestPluginFactory { + + @Test + @UnitTestMethod(target = ResourcesTestPluginFactory.class, name = "factory", args = { int.class, long.class, + Consumer.class }) + public void testFactory_Consumer() { + MutableBoolean executed = new MutableBoolean(); + Factory factory = ResourcesTestPluginFactory.factory(100, 5785172948650781925L, c -> executed.setValue(true)); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + assertTrue(executed.getValue()); + + // precondition: consumer is null + Consumer nullConsumer = null; + ContractException contractException = assertThrows(ContractException.class, + () -> ResourcesTestPluginFactory.factory(0, 0, nullConsumer)); + assertEquals(NucleusError.NULL_ACTOR_CONTEXT_CONSUMER, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = ResourcesTestPluginFactory.class, name = "factory", args = { int.class, long.class, + TestPluginData.class }) + public void testFactory_TestPluginData() { + MutableBoolean executed = new MutableBoolean(); + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, c -> executed.setValue(true))); + TestPluginData testPluginData = pluginBuilder.build(); + + Factory factory = ResourcesTestPluginFactory.factory(100, 5785172948650781925L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + assertTrue(executed.getValue()); + + // precondition: testPluginData is null + TestPluginData nullTestPluginData = null; + ContractException contractException = assertThrows(ContractException.class, + () -> ResourcesTestPluginFactory.factory(0, 0, nullTestPluginData)); + assertEquals(NucleusError.NULL_PLUGIN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = ResourcesTestPluginFactory.Factory.class, name = "getPlugins", args = {}) + public void testGetPlugins() { + List plugins = ResourcesTestPluginFactory.factory(0, 0, t -> { + }).getPlugins(); + assertEquals(5, plugins.size()); + + TestFactoryUtil.checkPluginExists(plugins, ResourcesPluginId.PLUGIN_ID); + TestFactoryUtil.checkPluginExists(plugins, PeoplePluginId.PLUGIN_ID); + TestFactoryUtil.checkPluginExists(plugins, RegionsPluginId.PLUGIN_ID); + TestFactoryUtil.checkPluginExists(plugins, StochasticsPluginId.PLUGIN_ID); + TestFactoryUtil.checkPluginExists(plugins, TestPluginId.PLUGIN_ID); + } + + @Test + @UnitTestMethod(target = ResourcesTestPluginFactory.Factory.class, name = "setPeoplePluginData", args = { + PeoplePluginData.class }) + public void testSetPeoplePluginData() { + PeoplePluginData.Builder builder = PeoplePluginData.builder(); + builder.addPersonRange(new PersonRange(0, 99)); + + PeoplePluginData peoplePluginData = builder.build(); + + List plugins = ResourcesTestPluginFactory.factory(0, 0, t -> { + }).setPeoplePluginData(peoplePluginData).getPlugins(); + + TestFactoryUtil.checkPluginDataExists(plugins, peoplePluginData, PeoplePluginId.PLUGIN_ID); + + // precondition: peoplePluginData is not null + ContractException contractException = assertThrows(ContractException.class, + () -> ResourcesTestPluginFactory.factory(0, 0, t -> { + }).setPeoplePluginData(null)); + assertEquals(PersonError.NULL_PEOPLE_PLUGIN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = ResourcesTestPluginFactory.Factory.class, name = "setRegionsPluginData", args = { + RegionsPluginData.class }) + public void testSetRegionsPluginData() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3415883014218424925L); + int initialPopulation = 30; + List people = new ArrayList<>(); + for (int i = 0; i < initialPopulation; i++) { + people.add(new PersonId(i)); + } + + // add the region plugin + RegionsPluginData.Builder regionPluginBuilder = RegionsPluginData.builder(); + for (TestRegionId regionId : TestRegionId.values()) { + regionPluginBuilder.addRegion(regionId); + } + + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { + regionPluginBuilder.defineRegionProperty(testRegionPropertyId, + testRegionPropertyId.getPropertyDefinition()); + } + + for (TestRegionId regionId : TestRegionId.values()) { + for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { + if (testRegionPropertyId.getPropertyDefinition().getDefaultValue().isEmpty() + || randomGenerator.nextBoolean()) { + Object randomPropertyValue = testRegionPropertyId.getRandomPropertyValue(randomGenerator); + regionPluginBuilder.setRegionPropertyValue(regionId, testRegionPropertyId, randomPropertyValue); + } + } + } + + TestRegionId testRegionId = TestRegionId.REGION_1; + for (PersonId personId : people) { + regionPluginBuilder.addPerson(personId, testRegionId, 0.0); + testRegionId = testRegionId.next(); + } + + regionPluginBuilder.setPersonRegionArrivalTracking(true); + + RegionsPluginData regionsPluginData = regionPluginBuilder.build(); + + List plugins = ResourcesTestPluginFactory.factory(0, 0, t -> { + }).setRegionsPluginData(regionsPluginData).getPlugins(); + + TestFactoryUtil.checkPluginDataExists(plugins, regionsPluginData, RegionsPluginId.PLUGIN_ID); + + // precondition: regionsPluginData is not null + ContractException contractException = assertThrows(ContractException.class, + () -> ResourcesTestPluginFactory.factory(0, 0, t -> { + }).setRegionsPluginData(null)); + assertEquals(RegionError.NULL_REGION_PLUGIN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = ResourcesTestPluginFactory.Factory.class, name = "setResourcesPluginData", args = { + ResourcesPluginData.class }) + public void testSetResourcesPluginData() { + ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); + + ResourcesPluginData resourcesPluginData = builder.build(); + + List plugins = ResourcesTestPluginFactory.factory(0, 0, t -> { + }).setResourcesPluginData(resourcesPluginData).getPlugins(); + + TestFactoryUtil.checkPluginDataExists(plugins, resourcesPluginData, ResourcesPluginId.PLUGIN_ID); + + // precondition: resourcesPluginData is not null + ContractException contractException = assertThrows(ContractException.class, + () -> ResourcesTestPluginFactory.factory(0, 0, t -> { + }).setResourcesPluginData(null)); + assertEquals(ResourceError.NULL_RESOURCE_PLUGIN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = ResourcesTestPluginFactory.Factory.class, name = "setStochasticsPluginData", args = { + StochasticsPluginData.class }) + public void testSetStochasticsPluginData() { + StochasticsPluginData.Builder builder = StochasticsPluginData.builder(); + + WellState wellState = WellState.builder().setSeed(2990359774692004249L).build(); + builder.setMainRNGState(wellState); + + StochasticsPluginData stochasticsPluginData = builder.build(); + + List plugins = ResourcesTestPluginFactory.factory(0, 0, t -> { + }).setStochasticsPluginData(stochasticsPluginData).getPlugins(); + + TestFactoryUtil.checkPluginDataExists(plugins, stochasticsPluginData, StochasticsPluginId.PLUGIN_ID); + + // precondition: stochasticsPluginData is not null + ContractException contractException = assertThrows(ContractException.class, + () -> ResourcesTestPluginFactory.factory(0, 0, t -> { + }).setStochasticsPluginData(null)); + assertEquals(StochasticsError.NULL_STOCHASTICS_PLUGIN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = ResourcesTestPluginFactory.class, name = "getStandardRegionsPluginData", args = { + List.class, long.class }) + public void testGetStandardRegionsPluginData() { + + List people = new ArrayList<>(); + for (int i = 0; i < 100; i++) { + people.add(new PersonId(i)); + } + long seed = 4570318399157617579L; + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + + RegionsPluginData.Builder regionBuilder = RegionsPluginData.builder(); + // add the regions + for (TestRegionId testRegionId : TestRegionId.values()) { + regionBuilder.addRegion(testRegionId); + } + for (PersonId personId : people) { + TestRegionId randomRegionId = TestRegionId.getRandomRegionId(randomGenerator); + regionBuilder.addPerson(personId, randomRegionId); + } + + RegionsPluginData expectedPluginData = regionBuilder.build(); + RegionsPluginData actualPluginData = ResourcesTestPluginFactory.getStandardRegionsPluginData(people, seed); + + assertEquals(expectedPluginData, actualPluginData); + } + + @Test + @UnitTestMethod(target = ResourcesTestPluginFactory.class, name = "getStandardPeoplePluginData", args = { + int.class }) + public void testGetStandardPeoplePluginData() { + + int initialPopulation = 100; + + PeoplePluginData.Builder peopleBuilder = PeoplePluginData.builder(); + if (initialPopulation > 0) { + peopleBuilder.addPersonRange(new PersonRange(0, initialPopulation - 1)); + } + + PeoplePluginData expectedPluginData = peopleBuilder.build(); + PeoplePluginData actualPluginData = ResourcesTestPluginFactory.getStandardPeoplePluginData(initialPopulation); + + assertEquals(expectedPluginData, actualPluginData); + + } + + @Test + @UnitTestMethod(target = ResourcesTestPluginFactory.class, name = "getStandardResourcesPluginData", args = { + List.class, long.class }) + public void testGetStandardResourcesPluginData() { + + long seed = 4800551796983227153L; + List people = new ArrayList<>(); + for (int i = 0; i < 100; i++) { + people.add(new PersonId(i)); + } + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + + ResourcesPluginData.Builder resourcesBuilder = ResourcesPluginData.builder(); + + for (TestResourceId testResourceId : TestResourceId.values()) { + resourcesBuilder.addResource(testResourceId, 0.0, testResourceId.getTimeTrackingPolicy()); + + for (PersonId personId : people) { + if (randomGenerator.nextBoolean()) { + resourcesBuilder.setPersonResourceLevel(personId, testResourceId, randomGenerator.nextInt(10)); + } + boolean trackTimes = testResourceId.getTimeTrackingPolicy(); + if (trackTimes && randomGenerator.nextBoolean()) { + resourcesBuilder.setPersonResourceTime(personId, testResourceId, 0.0); + } + } + + for (RegionId regionId : TestRegionId.values()) { + if (randomGenerator.nextBoolean()) { + resourcesBuilder.setRegionResourceLevel(regionId, testResourceId, randomGenerator.nextInt(10)); + } else { + resourcesBuilder.setRegionResourceLevel(regionId, testResourceId, 0); + } + } + } + + for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId.getTestResourcePropertyIds()) { + TestResourceId testResourceId = testResourcePropertyId.getTestResourceId(); + PropertyDefinition propertyDefinition = testResourcePropertyId.getPropertyDefinition(); + boolean hasDeaultValue = propertyDefinition.getDefaultValue().isPresent(); + + resourcesBuilder.defineResourceProperty(testResourceId, testResourcePropertyId, propertyDefinition); + + if (!hasDeaultValue) { + Object propertyValue = testResourcePropertyId.getRandomPropertyValue(randomGenerator); + resourcesBuilder.setResourcePropertyValue(testResourceId, testResourcePropertyId, propertyValue); + } + } + + for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId + .getShuffledTestResourcePropertyIds(randomGenerator)) { + TestResourceId testResourceId = testResourcePropertyId.getTestResourceId(); + PropertyDefinition propertyDefinition = testResourcePropertyId.getPropertyDefinition(); + boolean hasDefault = propertyDefinition.getDefaultValue().isPresent(); + boolean setValue = randomGenerator.nextBoolean(); + if (hasDefault && setValue) { + resourcesBuilder.setResourcePropertyValue(testResourceId, testResourcePropertyId, + propertyDefinition.getDefaultValue().get()); + } else if (setValue) { + Object propertyValue = testResourcePropertyId.getRandomPropertyValue(randomGenerator); + resourcesBuilder.setResourcePropertyValue(testResourceId, testResourcePropertyId, propertyValue); + } + } + + ResourcesPluginData expectedPluginData = resourcesBuilder.build(); + ResourcesPluginData actualPluginData = ResourcesTestPluginFactory.getStandardResourcesPluginData(people, seed); + + assertEquals(expectedPluginData, actualPluginData); + } + + @Test + @UnitTestMethod(target = ResourcesTestPluginFactory.class, name = "getStandardStochasticsPluginData", args = { + long.class }) + public void testGetStandardStochasticsPluginData() { + long seed = 6072871729256538807L; + + WellState wellState = WellState.builder().setSeed(seed).build(); + + StochasticsPluginData expectedPluginData = StochasticsPluginData.builder().setMainRNGState(wellState).build(); + StochasticsPluginData actualPluginData = ResourcesTestPluginFactory.getStandardStochasticsPluginData(seed); + + assertEquals(expectedPluginData, actualPluginData); + } + + + //testSetPersonResourceReportPluginData() plugins.resources.testsupport.ResourcesTestPluginFactory$Factory.setPersonResourceReportPluginData(plugins.resources.reports.ResourceReportPluginData) + @Test + @UnitTestMethod(target = ResourcesTestPluginFactory.Factory.class, name = "setPersonResourceReportPluginData", args = { + PersonResourceReportPluginData.class }) + public void testSetPersonResourceReportPluginData() { + PersonResourceReportPluginData personResourceReportPluginData = PersonResourceReportPluginData.builder() + .setReportLabel(new SimpleReportLabel("report label")) + .setDefaultInclusion(true) + .setReportPeriod(ReportPeriod.DAILY) + .build(); + + List plugins = ResourcesTestPluginFactory.factory(0, 0, t -> { + }).setPersonResourceReportPluginData(personResourceReportPluginData).getPlugins(); + + TestFactoryUtil.checkPluginDataExists(plugins, personResourceReportPluginData, ResourcesPluginId.PLUGIN_ID); + } + + @Test + @UnitTestMethod(target = ResourcesTestPluginFactory.Factory.class, name = "setResourcePropertyReportPluginData", args = { + ResourcePropertyReportPluginData.class }) + public void testSetResourcePropertyReportPluginData() { + ResourcePropertyReportPluginData resourcePropertyReportPluginData = ResourcePropertyReportPluginData.builder() + .setReportLabel(new SimpleReportLabel("report label")) + .build(); + + List plugins = ResourcesTestPluginFactory.factory(0, 0, t -> { + }).setResourcePropertyReportPluginData(resourcePropertyReportPluginData).getPlugins(); + + TestFactoryUtil.checkPluginDataExists(plugins, resourcePropertyReportPluginData, ResourcesPluginId.PLUGIN_ID); + } + + @Test + @UnitTestMethod(target = ResourcesTestPluginFactory.Factory.class, name = "setResourceReportPluginData", args = { + ResourceReportPluginData.class }) + public void testSetResourceReportPluginData() { + ResourceReportPluginData resourceReportPluginData = ResourceReportPluginData.builder() + .setReportLabel(new SimpleReportLabel("report label")) + .setDefaultInclusion(true) + .setReportPeriod(ReportPeriod.DAILY) + .build(); + + List plugins = ResourcesTestPluginFactory.factory(0, 0, t -> { + }).setResourceReportPluginData(resourceReportPluginData).getPlugins(); + + TestFactoryUtil.checkPluginDataExists(plugins, resourceReportPluginData, ResourcesPluginId.PLUGIN_ID); + } + +} \ No newline at end of file diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/testsupport/AT_TestResourceId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/testsupport/AT_TestResourceId.java new file mode 100644 index 000000000..028746c94 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/testsupport/AT_TestResourceId.java @@ -0,0 +1,86 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.testsupport; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; +import util.annotations.UnitTestMethod; +import util.random.RandomGeneratorProvider; +import util.wrappers.MutableInteger; + +public class AT_TestResourceId { + + @Test + @UnitTestMethod(target = TestResourceId.class, name = "getTimeTrackingPolicy", args = {}) + public void testGetTimeTrackingPolicy() { + for (TestResourceId testResourceId : TestResourceId.values()) { + assertNotNull(testResourceId.getTimeTrackingPolicy()); + } + } + + @Test + @UnitTestMethod(target = TestResourceId.class, name = "getRandomResourceId", args = { RandomGenerator.class }) + public void testGetRandomResourceId() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5357990509395444631L); + Map idCounter = new LinkedHashMap<>(); + + for (TestResourceId testResourceId : TestResourceId.values()) { + idCounter.put(testResourceId, new MutableInteger()); + } + + for (int i = 0; i < 50; i++) { + TestResourceId testResourceId = TestResourceId.getRandomResourceId(randomGenerator); + idCounter.get(testResourceId).increment(); + } + + for (TestResourceId testResourceId : idCounter.keySet()) { + assertTrue(idCounter.get(testResourceId).getValue() >= 5 && idCounter.get(testResourceId).getValue() <= 30); + } + } + + @Test + @UnitTestMethod(target = TestResourceId.class, name = "getUnknownResourceId", args = {}) + public void testGetUnknownResourceId() { + Set oldIds = EnumSet.allOf(TestResourceId.class); + Set unknownIds = new LinkedHashSet<>(); + + // show that each unknown id is not null and unique + for (int i = 0; i < 50; i++) { + ResourceId unknownResourceId = TestResourceId.getUnknownResourceId(); + assertNotNull(unknownResourceId); + boolean unique = unknownIds.add(unknownResourceId); + assertTrue(unique); + assertFalse(oldIds.contains(unknownResourceId)); + } + } + + @Test + @UnitTestMethod(target = TestResourceId.class, name = "size", args = {}) + public void testSize() { + assertNotNull(TestResourceId.size()); + assertEquals(TestResourceId.size(), 5); + } + + @Test + @UnitTestMethod(target = TestResourceId.class, name = "next", args = {}) + public void testNext() { + for (TestResourceId testResourceId : TestResourceId.values()) { + int index = (testResourceId.ordinal() + 1) % TestResourceId.values().length; + TestResourceId expectedNext = TestResourceId.values()[index]; + assertNotNull(testResourceId.next()); + assertEquals(expectedNext, testResourceId.next()); + } + } + +} \ No newline at end of file diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/testsupport/AT_TestResourcePropertyId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/testsupport/AT_TestResourcePropertyId.java new file mode 100644 index 000000000..45a014249 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/resources/testsupport/AT_TestResourcePropertyId.java @@ -0,0 +1,201 @@ +package gov.hhs.aspr.ms.gcm.plugins.resources.testsupport; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourcePropertyId; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import util.annotations.UnitTestMethod; +import util.random.RandomGeneratorProvider; +import util.wrappers.MutableInteger; + +public class AT_TestResourcePropertyId { + + @Test + @UnitTestMethod(target = TestResourcePropertyId.class, name = "getPropertyDefinition", args = {}) + public void testGetPropertyDefinition() { + for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId.values()) { + assertNotNull(testResourcePropertyId.getPropertyDefinition()); + } + } + + @Test + @UnitTestMethod(target = TestResourcePropertyId.class, name = "getTestResourceId", args = {}) + public void testGetTestResourceId() { + for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId.values()) { + assertNotNull(testResourcePropertyId.getTestResourceId()); + } + } + + @Test + @UnitTestMethod(target = TestResourcePropertyId.class, name = "getUnknownResourcePropertyId", args = {}) + public void testGetUnknownResourcePropertyId() { + Set oldIds = EnumSet.allOf(TestResourcePropertyId.class); + Set unknownIds = new LinkedHashSet<>(); + + // show that each unknown id is not null and unique + for (int i = 1; i < 100; i++) { + ResourcePropertyId unknownResourcePropertyId = TestResourcePropertyId.getUnknownResourcePropertyId(); + assertNotNull(unknownResourcePropertyId); + boolean unique = unknownIds.add(unknownResourcePropertyId); + assertTrue(unique); + assertFalse(oldIds.contains(unknownResourcePropertyId)); + } + } + + @Test + @UnitTestMethod(target = TestResourcePropertyId.class, name = "getTestResourcePropertyIds", args = {}) + public void testGetTestResourcePropertyIds() { + assertEquals(Arrays.asList(TestResourcePropertyId.values()), + TestResourcePropertyId.getTestResourcePropertyIds()); + + } + + @Test + @UnitTestMethod(target = TestResourcePropertyId.class, name = "getTestResourcePropertyIds", args = { + TestResourceId.class }) + public void testGetTestResourcePropertyIds_ResourceId() { + + for (TestResourceId testResourceId : TestResourceId.values()) { + Set expectedIds = new LinkedHashSet<>(); + for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId.values()) { + if (testResourceId.equals(testResourcePropertyId.getTestResourceId())) { + expectedIds.add(testResourcePropertyId); + } + } + Set actualIds = TestResourcePropertyId.getTestResourcePropertyIds(testResourceId); + assertEquals(actualIds, expectedIds); + } + } + + @Test + @UnitTestMethod(target = TestResourcePropertyId.class, name = "getRandomResourcePropertyId", args = { + TestResourceId.class, RandomGenerator.class }) + public void testGetRandomResourcePropertyId() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7615402310345074403L); + + for (TestResourceId testResourceId : TestResourceId.values()) { + + // gather the expected test resource property id values for each + // given test resource + Set expectedTestResourcePropertyIds = new LinkedHashSet<>(); + for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId.values()) { + if (testResourcePropertyId.getTestResourceId().equals(testResourceId)) { + expectedTestResourcePropertyIds.add(testResourcePropertyId); + } + } + + // initialize the property id counter to zeros + Map propertyIdCounter = new LinkedHashMap<>(); + for (TestResourcePropertyId testResourcePropertyId : expectedTestResourcePropertyIds) { + propertyIdCounter.put(testResourcePropertyId, new MutableInteger()); + } + + // sample a reasonable number of invocations + int sampleCount = 10 * expectedTestResourcePropertyIds.size(); + for (int i = 0; i < sampleCount; i++) { + TestResourcePropertyId testResourcePropertyId = TestResourcePropertyId + .getRandomResourcePropertyId(testResourceId, randomGenerator); + assertTrue(expectedTestResourcePropertyIds.contains(testResourcePropertyId)); + propertyIdCounter.get(testResourcePropertyId).increment(); + } + + // show that we get a reasonable number of matches to each resource + // property id + for (TestResourcePropertyId testResourcePropertyId : propertyIdCounter.keySet()) { + MutableInteger mutableInteger = propertyIdCounter.get(testResourcePropertyId); + int value = mutableInteger.getValue(); + assertTrue(value >= 5 && value <= 20); + } + + } + } + + @Test + @UnitTestMethod(target = TestResourcePropertyId.class, name = "getRandomPropertyValue", args = { + RandomGenerator.class }) + public void testGetRandomPropertyValue() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7615402310345074403L); + /* + * Show that randomly generated values are compatible with the associated + * property definition. Show that the values are reasonably unique + */ + for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId.values()) { + PropertyDefinition propertyDefinition = testResourcePropertyId.getPropertyDefinition(); + Set values = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + Object propertyValue = testResourcePropertyId.getRandomPropertyValue(randomGenerator); + values.add(propertyValue); + assertTrue(propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())); + } + + // show that the values are reasonable unique + if (propertyDefinition.getType() != Boolean.class) { + assertTrue(values.size() > 10); + } else { + assertEquals(2, values.size()); + } + } + } + + @Test + @UnitTestMethod(target = TestResourcePropertyId.class, name = "next", args = {}) + public void testNext() { + for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId.values()) { + int index = testResourcePropertyId.ordinal(); + index += 1; + index %= TestResourcePropertyId.values().length; + TestResourcePropertyId expectedNextTestResourcePropertyId = TestResourcePropertyId.values()[index]; + assertEquals(expectedNextTestResourcePropertyId, testResourcePropertyId.next()); + } + } + +// TestResourcePropertyId public static java.util.List plugins.resources.testsupport.TestResourcePropertyId.getShuffledTestResourcePropertyIds(org.apache.commons.math3.random.RandomGenerator) + + @Test + @UnitTestMethod(target = TestResourcePropertyId.class, name = "getShuffledTestResourcePropertyIds", args = {RandomGenerator.class}) + public void testGetTestShuffledRegionPropertyIds() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8246696863539332004L); + + Set baseSet = new LinkedHashSet<>(); + for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId.values()) { + baseSet.add(testResourcePropertyId); + } + + Set> lists = new LinkedHashSet<>(); + + /* + * Generate a few thousand random lists and show that each list contains all the + * expected region property ids + * + */ + for (int i = 0; i < 3000; i++) { + List list = TestResourcePropertyId.getShuffledTestResourcePropertyIds(randomGenerator); + lists.add(list); + assertEquals(baseSet, new LinkedHashSet<>(list)); + + } + + + + //There are 10! possible lists, so we don't expect many collsions + assertTrue(lists.size() > 2900); + + + } + + +} \ No newline at end of file diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/AT_StochasticsDataManager.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/AT_StochasticsDataManager.java new file mode 100644 index 000000000..a6bdee1a6 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/AT_StochasticsDataManager.java @@ -0,0 +1,524 @@ +package gov.hhs.aspr.ms.gcm.plugins.stochastics; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.IntStream; + +import org.apache.commons.math3.random.RandomGenerator; +import org.apache.commons.math3.util.FastMath; +import org.apache.commons.math3.util.Pair; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.Simulation; +import gov.hhs.aspr.ms.gcm.nucleus.SimulationState; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.runcontinuityplugin.RunContinuityPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.runcontinuityplugin.RunContinuityPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestOutputConsumer; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.RandomNumberGeneratorId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.StochasticsError; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.Well; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.testsupport.StochasticsTestPluginFactory; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.testsupport.StochasticsTestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.testsupport.TestRandomGeneratorId; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_StochasticsDataManager { + + /** + * Demonstrates that the data manager exhibits run continuity. The state of the + * data manager is not effected by repeatedly starting and stopping the + * simulation. + */ + @Test + @UnitTestMethod(target = StochasticsDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testStateContinuity() { + + /* + * Note that we are not testing the content of the plugin datas -- that is + * covered by the other state tests. We show here only that the resulting plugin + * data state is the same without regard to how we break up the run. + */ + + Set pluginDatas = new LinkedHashSet<>(); + pluginDatas.add(testStateContinuity(1)); + pluginDatas.add(testStateContinuity(6)); + pluginDatas.add(testStateContinuity(15)); + + assertEquals(1, pluginDatas.size()); + + } + + /* + * Returns the StochasticsPluginData resulting from several random draws over + * several days. Attempt to stop and start the simulation by the given number of + * increments. + */ + private String testStateContinuity(int incrementCount) { + + String result = null; + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4693559432563807708L); + /* + * Build the RunContinuityPluginData with five context consumers that will add + * and remove people over several days + */ + RunContinuityPluginData.Builder continuityBuilder = RunContinuityPluginData.builder(); + + int n = 15; + + IntStream.range(0, n).forEach((i) -> { + double time = randomGenerator.nextDouble() * 10; + continuityBuilder.addContextConsumer(time, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + + // attempt to add a new rng + List candidates = new ArrayList<>(); + for (TestRandomGeneratorId id : TestRandomGeneratorId.values()) { + if (!stochasticsDataManager.randomNumberGeneratorIdExists(id)) { + candidates.add(id); + } + } + if (!candidates.isEmpty()) { + TestRandomGeneratorId testRandomGeneratorId = candidates + .get(randomGenerator.nextInt(candidates.size())); + stochasticsDataManager.addRandomNumberGenerator(testRandomGeneratorId, + getRandomWellState(randomGenerator)); + } + + // randomly stimulate the existing rngs + RandomGenerator rng = stochasticsDataManager.getRandomGenerator(); + if (randomGenerator.nextBoolean()) { + rng.nextDouble(); + } + for (RandomNumberGeneratorId id : stochasticsDataManager.getRandomNumberGeneratorIds()) { + if (randomGenerator.nextBoolean()) { + rng = stochasticsDataManager.getRandomGeneratorFromId(id); + rng.nextDouble(); + } + } + + if (i == (n - 1)) { + c.releaseOutput(stochasticsDataManager.toString()); + } + + }); + }); + + RunContinuityPluginData runContinuityPluginData = continuityBuilder.build(); + + StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder()// + .setMainRNGState(getRandomWellState(randomGenerator))// + .build(); + + // build the initial simulation state data -- time starts at zero + SimulationState simulationState = SimulationState.builder().build(); + + /* + * Run the simulation in one day increments until all the plans in the run + * continuity plugin data have been executed + */ + double haltTime = 0; + double maxTime = Double.NEGATIVE_INFINITY; + for (Pair> pair : runContinuityPluginData.getConsumers()) { + Double time = pair.getFirst(); + maxTime = FastMath.max(maxTime, time); + } + double timeIncrement = maxTime / incrementCount; + while (!runContinuityPluginData.allPlansComplete()) { + haltTime += timeIncrement; + + // build the run continuity plugin + Plugin runContinuityPlugin = RunContinuityPlugin.builder()// + .setRunContinuityPluginData(runContinuityPluginData)// + .build(); + + Plugin stochasticsPlugin = StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData); + + TestOutputConsumer outputConsumer = new TestOutputConsumer(); + + // execute the simulation so that it produces a people plugin data + Simulation simulation = Simulation.builder()// + .addPlugin(runContinuityPlugin)// + .addPlugin(stochasticsPlugin)// + .setSimulationHaltTime(haltTime)// + .setRecordState(true)// + .setOutputConsumer(outputConsumer)// + .setSimulationState(simulationState)// + .build();// + simulation.execute(); + + // retrieve the people plugin data + stochasticsPluginData = outputConsumer.getOutputItem(StochasticsPluginData.class).get(); + + // retrieve the simulation state + simulationState = outputConsumer.getOutputItem(SimulationState.class).get(); + + // retrieve the run continuity plugin data + runContinuityPluginData = outputConsumer.getOutputItem(RunContinuityPluginData.class).get(); + + Optional optional = outputConsumer.getOutputItem(String.class); + if (optional.isPresent()) { + result = optional.get(); + } + } + assertNotNull(result); + + return result; + + } + + /** + * Demonstrates that the data manager's initial state reflects its plugin data + */ + @Test + @UnitTestMethod(target = StochasticsDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testStateInitialization() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2051947783068799322L); + + // Generate a few random well states + WellState wellState_MAIN = getRandomWellState(randomGenerator); + WellState wellState_BLITZEN = getRandomWellState(randomGenerator); + WellState wellState_CUPID = getRandomWellState(randomGenerator); + WellState wellState_DANCER = getRandomWellState(randomGenerator); + + // create the initial plugin data with only BLITZEN + StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder()// + .setMainRNGState(wellState_MAIN)// + .addRNG(TestRandomGeneratorId.BLITZEN, wellState_BLITZEN)// + .addRNG(TestRandomGeneratorId.CUPID, wellState_CUPID)// + .addRNG(TestRandomGeneratorId.DANCER, wellState_DANCER)// + .build(); + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // Have an actor add CUPID and use the various random generators + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + + // show that the rng ids are as expected + Set expectedIds = new LinkedHashSet<>(); + expectedIds.add(TestRandomGeneratorId.BLITZEN); + expectedIds.add(TestRandomGeneratorId.CUPID); + expectedIds.add(TestRandomGeneratorId.DANCER); + Set actualIds = stochasticsDataManager.getRandomNumberGeneratorIds(); + assertEquals(expectedIds, actualIds); + + // show that the state of the rngs are correct + Well well = (Well) stochasticsDataManager.getRandomGenerator(); + assertEquals(wellState_MAIN, well.getWellState()); + + well = (Well) stochasticsDataManager.getRandomGeneratorFromId(TestRandomGeneratorId.BLITZEN); + assertEquals(wellState_BLITZEN, well.getWellState()); + + well = (Well) stochasticsDataManager.getRandomGeneratorFromId(TestRandomGeneratorId.CUPID); + assertEquals(wellState_CUPID, well.getWellState()); + + well = (Well) stochasticsDataManager.getRandomGeneratorFromId(TestRandomGeneratorId.DANCER); + assertEquals(wellState_DANCER, well.getWellState()); + + })); + + // run the simulation + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = StochasticsTestPluginFactory.factory(2244108072445615171L, testPluginData)// + .setStochasticsPluginData(stochasticsPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins())// + .setProduceSimulationStateOnHalt(true)// + .setSimulationHaltTime(2)// + .build()// + .execute(); + + } + + /** + * Demonstrates that the data manager produces plugin data that reflects its + * final state + */ + @Test + @UnitTestMethod(target = StochasticsDataManager.class, name = "init", args = { DataManagerContext.class }) + public void testStateFinalization() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2051947783068799322L); + + // Generate a few random well states + WellState wellState_MAIN = getRandomWellState(randomGenerator); + WellState wellState_BLITZEN = getRandomWellState(randomGenerator); + WellState wellState_CUPID = getRandomWellState(randomGenerator); + WellState wellState_DANCER = getRandomWellState(randomGenerator); + + // create the initial plugin data with only BLITZEN + StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder()// + .setMainRNGState(wellState_MAIN)// + .addRNG(TestRandomGeneratorId.BLITZEN, wellState_BLITZEN)// + .build(); + + TestPluginData.Builder pluginBuilder = TestPluginData.builder(); + + // Have an actor add CUPID and use the various random generators + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + stochasticsDataManager.addRandomNumberGenerator(TestRandomGeneratorId.CUPID, wellState_CUPID); + RandomGenerator rng = stochasticsDataManager.getRandomGenerator(); + rng.nextBoolean(); + rng.nextInt(); + rng = stochasticsDataManager.getRandomGeneratorFromId(TestRandomGeneratorId.CUPID); + rng.nextDouble(); + rng.nextDouble(); + rng.nextDouble(); + })); + + // Have an actor add DANCER and use the various random generators + pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + stochasticsDataManager.addRandomNumberGenerator(TestRandomGeneratorId.DANCER, wellState_DANCER); + RandomGenerator rng = stochasticsDataManager.getRandomGenerator(); + rng.nextInt(); + rng.nextLong(); + rng = stochasticsDataManager.getRandomGeneratorFromId(TestRandomGeneratorId.DANCER); + rng.nextBoolean(); + rng.nextBoolean(); + rng.nextFloat(); + rng = stochasticsDataManager.getRandomGeneratorFromId(TestRandomGeneratorId.CUPID); + rng.nextLong(); + rng.nextDouble(); + rng.nextDouble(); + + })); + + // run the simulation + TestPluginData testPluginData = pluginBuilder.build(); + Factory factory = StochasticsTestPluginFactory.factory(3078336459131759089L, testPluginData)// + .setStochasticsPluginData(stochasticsPluginData); + TestOutputConsumer testOutputConsumer = TestSimulation.builder().addPlugins(factory.getPlugins())// + .setProduceSimulationStateOnHalt(true)// + .setSimulationHaltTime(2)// + .build()// + .execute(); + // Get the resulting StochasticsPluginData + Map outputItems = testOutputConsumer + .getOutputItemMap(StochasticsPluginData.class); + assertEquals(1, outputItems.size()); + StochasticsPluginData actualPluginData = outputItems.keySet().iterator().next(); + + /* + * Build the expected StochasticsPluginData by repeating the actions of the + * actors for each well + */ + StochasticsPluginData.Builder builder = StochasticsPluginData.builder();// + Well well = new Well(wellState_MAIN); + well.nextBoolean(); + well.nextInt(); + well.nextInt(); + well.nextLong(); + builder.setMainRNGState(well.getWellState()); + + well = new Well(wellState_BLITZEN); + builder.addRNG(TestRandomGeneratorId.BLITZEN, well.getWellState()); + + well = new Well(wellState_CUPID); + well.nextDouble(); + well.nextDouble(); + well.nextDouble(); + well.nextLong(); + well.nextDouble(); + well.nextDouble(); + builder.addRNG(TestRandomGeneratorId.CUPID, well.getWellState()); + + well = new Well(wellState_DANCER); + well.nextBoolean(); + well.nextBoolean(); + well.nextFloat(); + builder.addRNG(TestRandomGeneratorId.DANCER, well.getWellState()); + + StochasticsPluginData expectedPluginData = builder.build(); + + // show the resulting plugin data are equal + assertEquals(expectedPluginData, actualPluginData); + } + + @Test + @UnitTestMethod(target = StochasticsDataManager.class, name = "getRandomNumberGeneratorIds", args = {}) + public void testGetRandomNumberGeneratorIds() { + + Factory factory = StochasticsTestPluginFactory.factory(1244273915891145733L, (c) -> { + + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + + Set actualRandomNumberGeneratorIds = stochasticsDataManager + .getRandomNumberGeneratorIds(); + + Set expectedRandomGeneratorIds = new LinkedHashSet<>(); + for (TestRandomGeneratorId testRandomGeneratorId : TestRandomGeneratorId.values()) { + expectedRandomGeneratorIds.add(testRandomGeneratorId); + } + assertEquals(expectedRandomGeneratorIds, actualRandomNumberGeneratorIds); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = StochasticsDataManager.class, name = "randomNumberGeneratorIdExists", args = { + RandomNumberGeneratorId.class }) + public void testRandomNumberGeneratorIdExists() { + Factory factory = StochasticsTestPluginFactory.factory(1244273915891145733L, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomNumberGeneratorId unknownRandomGeneratorId = TestRandomGeneratorId + .getUnknownRandomNumberGeneratorId(); + Set randomNumberGeneratorIds = stochasticsDataManager + .getRandomNumberGeneratorIds(); + for (TestRandomGeneratorId testRandomGeneratorId : TestRandomGeneratorId.values()) { + assertTrue(randomNumberGeneratorIds.contains(testRandomGeneratorId)); + } + assertFalse(randomNumberGeneratorIds.contains(unknownRandomGeneratorId)); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = StochasticsDataManager.class, name = "getRandomGeneratorFromId", args = { + RandomNumberGeneratorId.class }) + public void testGetRandomGeneratorFromId() { + + // show that random generators can be retrieved by ids. + Factory factory = StochasticsTestPluginFactory.factory(5489824520767978373L, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + for (TestRandomGeneratorId testRandomGeneratorId : TestRandomGeneratorId.values()) { + RandomGenerator randomGeneratorFromId = stochasticsDataManager + .getRandomGeneratorFromId(testRandomGeneratorId); + assertNotNull(randomGeneratorFromId); + } + }); + + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + // precondition test : if the random number generator is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Factory factory2 = StochasticsTestPluginFactory.factory(1893848105389404535L, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + stochasticsDataManager.getRandomGeneratorFromId(null); + }); + TestSimulation.builder().addPlugins(factory2.getPlugins()).build().execute(); + }); + assertEquals(StochasticsError.NULL_RANDOM_NUMBER_GENERATOR_ID, contractException.getErrorType()); + + // precondition test : if the random number generator is unknown + ContractException contractException2 = assertThrows(ContractException.class, () -> { + Factory factory3 = StochasticsTestPluginFactory.factory(6057300273321098424L, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + stochasticsDataManager + .getRandomGeneratorFromId(TestRandomGeneratorId.getUnknownRandomNumberGeneratorId()); + }); + TestSimulation.builder().addPlugins(factory3.getPlugins()).build().execute(); + }); + assertEquals(StochasticsError.UNKNOWN_RANDOM_NUMBER_GENERATOR_ID, contractException2.getErrorType()); + } + + @Test + @UnitTestMethod(target = StochasticsDataManager.class, name = "getRandomGenerator", args = {}) + public void testGetRandomGenerator() { + // show that random generators can be retrieved by ids + Factory factory = StochasticsTestPluginFactory.factory(683597885444214892L, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGeneratorFromId = stochasticsDataManager.getRandomGenerator(); + assertNotNull(randomGeneratorFromId); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = StochasticsDataManager.class, name = "addRandomNumberGenerator", args = { + RandomNumberGeneratorId.class, WellState.class }) + public void testAddRandomNumberGenerator() { + Factory factory = StochasticsTestPluginFactory.factory(1244273915891145733L, (c) -> { + + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + RandomNumberGeneratorId numberGeneratorIdToAdd = TestRandomGeneratorId.getUnknownRandomNumberGeneratorId(); + WellState wellState = WellState.builder().build(); + stochasticsDataManager.addRandomNumberGenerator(numberGeneratorIdToAdd, wellState); + + Set actualRandomNumberGeneratorIds = stochasticsDataManager + .getRandomNumberGeneratorIds(); + + Set expectedRandomGeneratorIds = new LinkedHashSet<>(); + for (RandomNumberGeneratorId testRandomGeneratorId : TestRandomGeneratorId.values()) { + expectedRandomGeneratorIds.add(testRandomGeneratorId); + } + expectedRandomGeneratorIds.add(numberGeneratorIdToAdd); + assertEquals(expectedRandomGeneratorIds, actualRandomNumberGeneratorIds); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestConstructor(target = StochasticsDataManager.class, args = { StochasticsPluginData.class }) + public void testConstructor() { + // test of constructor is covered by the method tests + } + + private WellState getRandomWellState(RandomGenerator randomGenerator) { + int stateIndex = randomGenerator.nextInt(1390); + int[] vArray = new int[1391]; + + for (int i = 0; i < 1391; i++) { + vArray[i] = randomGenerator.nextInt(); + } + WellState wellState = WellState.builder()// + .setInternals(stateIndex, vArray)// + .setSeed(randomGenerator.nextLong())// + .build(); + return wellState; + } + + @Test + @UnitTestMethod(target = StochasticsDataManager.class, name = "toString", args = {}) + public void testToString() { + Factory factory = StochasticsTestPluginFactory.factory(4394401734465184701L, (c) -> { + StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); + String actualValue = stochasticsDataManager.toString(); + + RandomGenerator mainRandomGenerator = stochasticsDataManager.getRandomGenerator(); + Map randomGeneratorMap = new LinkedHashMap<>(); + for (RandomNumberGeneratorId randomNumberGeneratorId : stochasticsDataManager + .getRandomNumberGeneratorIds()) { + Well randomGeneratorFromId = stochasticsDataManager.getRandomGeneratorFromId(randomNumberGeneratorId); + randomGeneratorMap.put(randomNumberGeneratorId, randomGeneratorFromId); + } + + StringBuilder builder = new StringBuilder(); + builder.append("StochasticsDataManager [randomGeneratorMap="); + builder.append(randomGeneratorMap); + builder.append(", randomGenerator="); + builder.append(mainRandomGenerator); + builder.append("]"); + String expectedValue = builder.toString(); + + assertEquals(expectedValue, actualValue); + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/AT_StochasticsPlugin.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/AT_StochasticsPlugin.java new file mode 100644 index 000000000..836fd319c --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/AT_StochasticsPlugin.java @@ -0,0 +1,142 @@ +package gov.hhs.aspr.ms.gcm.plugins.stochastics; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.Simulation; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPlugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import util.annotations.UnitTestMethod; + +public class AT_StochasticsPlugin { + + @Test + @UnitTestMethod(target = StochasticsPlugin.class, name = "getStochasticsPlugin", args = { StochasticsPluginData.class }) + public void testGetPlugin() { + + WellState wellState = WellState.builder().setSeed(7501293495427253479L).build(); + StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder().setMainRNGState(wellState).build(); + Plugin stochasticsPlugin = StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData); + + // show the plugin is not null + assertNotNull(stochasticsPlugin); + + // show that the plugin has no dependencies + assertTrue(stochasticsPlugin.getPluginDependencies().isEmpty()); + + // show that the plugin has the correct id + assertEquals(StochasticsPluginId.PLUGIN_ID, stochasticsPlugin.getPluginId()); + + /* + * Show that the plugin establishes the StochasticsDataManager + */ + TestPluginData.Builder testPluginBuilder = TestPluginData.builder(); + + TestPluginData testPluginData = testPluginBuilder.build(); + Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); + + Simulation .builder()// + .addPlugin(testPlugin)// + .addPlugin(stochasticsPlugin)// + .build()// + .execute();// + + } + + // the code below should show that the state of the data manager properly + // reflects the plugin data + + // @Test + // @UnitTestMethod(name = "init", args = { ResolverContext.class }) + // public void testStochasticsDataViewInitialzation() { + // long seed = 745645785689L; + // + // // show that the stochastics data view is published and has the correct + // // state + // + // // show that we are contributing random generator ids + // assertTrue(TestRandomGeneratorId.values().length > 0); + // + // // build the initial data + // Set expectedRandomGeneratorIds = new + // LinkedHashSet<>(); + // StochasticsPlugin.Builder builder = StochasticsPlugin.builder(); + // for (TestRandomGeneratorId testRandomGeneratorId : + // TestRandomGeneratorId.values()) { + // expectedRandomGeneratorIds.add(testRandomGeneratorId); + // builder.addRandomGeneratorId(testRandomGeneratorId); + // } + // builder.setSeed(seed); + // StochasticsPlugin stochasticsPlugin = builder.build(); + // + // List publishedDataViews = new ArrayList<>(); + // + // // build the manager + // MockResolverContext mockResolverContext = + // MockResolverContext.builder().setPublishDataViewConsumer((d) -> + // publishedDataViews.add(d)).build(); + // StochasticsResolver stochasticsResolver = new + // StochasticsResolver(stochasticsPlugin); + // stochasticsResolver.init(mockResolverContext); + // + // // show that only one data view was published + // assertEquals(1, publishedDataViews.size()); + // + // // show that the published data view is not null + // DataView dataView = publishedDataViews.get(0); + // assertNotNull(dataView); + // + // // show that the published data view is a StochasticsDataView + // assertEquals(StochasticsDataView.class, dataView.getClass()); + // + // StochasticsDataView stochasticsDataView = (StochasticsDataView) dataView; + // + // // show that the data view returns the correct random generator + // RandomGenerator randomGenerator = + // stochasticsDataView.getRandomGenerator(); + // // show that the random generator is not null + // assertNotNull(randomGenerator); + // // show that the random generator is the expected implementor + // assertEquals(Well44497b.class, randomGenerator.getClass()); + // + // // show that the random generator is likely to have been seeded + // // correctly + // Well44497b well44497b = new Well44497b(seed); + // for (int i = 0; i < 100; i++) { + // assertEquals(well44497b.nextLong(), randomGenerator.nextLong()); + // } + // + // // show that the data view returns the correct random generator ids + // Set actualRandomNumberGeneratorIds = + // stochasticsDataView.getRandomNumberGeneratorIds(); + // assertEquals(expectedRandomGeneratorIds, actualRandomNumberGeneratorIds); + // + // // show that the random generators associated with id values are correct + // for (TestRandomGeneratorId testRandomGeneratorId : + // TestRandomGeneratorId.values()) { + // // show that the data view returns the correct random generator + // randomGenerator = + // stochasticsDataView.getRandomGeneratorFromId(testRandomGeneratorId); + // // show that the random generator is not null + // assertNotNull(randomGenerator); + // // show that the random generator is the expected implementor + // assertEquals(Well44497b.class, randomGenerator.getClass()); + // + // // show that the random generator is likely to have been seeded + // // correctly + // well44497b = new Well44497b(seed + + // testRandomGeneratorId.toString().hashCode()); + // for (int i = 0; i < 100; i++) { + // assertEquals(well44497b.nextLong(), randomGenerator.nextLong()); + // } + // } + // + // } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/AT_StochasticsPluginData.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/AT_StochasticsPluginData.java new file mode 100644 index 000000000..2af08391e --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/AT_StochasticsPluginData.java @@ -0,0 +1,277 @@ +package gov.hhs.aspr.ms.gcm.plugins.stochastics; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginDataBuilder; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.RandomNumberGeneratorId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.StochasticsError; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.testsupport.TestRandomGeneratorId; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_StochasticsPluginData { + + @Test + @UnitTestMethod(target = StochasticsPluginData.class, name = "getCloneBuilder", args = {}) + public void testGetCloneBuilder() { + + WellState wellState = WellState.builder().setSeed(4970625656919510170L).build(); + + StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder()// + .setMainRNGState(wellState)// + .addRNG(TestRandomGeneratorId.BLITZEN, wellState)// + .addRNG(TestRandomGeneratorId.COMET, wellState)// + .build();// + + // show that the clone builder is not null + PluginDataBuilder cloneBuilder = stochasticsPluginData.getCloneBuilder(); + assertNotNull(cloneBuilder); + StochasticsPluginData cloneData = (StochasticsPluginData) cloneBuilder.build(); + + // show that the clone builder is properly initialized + assertEquals(cloneData, stochasticsPluginData); + + } + + @Test + @UnitTestMethod(target = StochasticsPluginData.class, name = "getWellState", args = {}) + public void testGetWellState() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4970625656919510170L); + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + WellState wellState = WellState.builder().setSeed(seed).build(); + StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder().setMainRNGState(wellState) + .build(); + assertEquals(wellState, stochasticsPluginData.getWellState()); + } + } + + @Test + @UnitTestMethod(target = StochasticsPluginData.class, name = "getRandomNumberGeneratorIds", args = {}) + public void testGetRandomNumberGeneratorIds() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1644320989680923741L); + Map expectedRandomNumberGeneratorIds = new LinkedHashMap<>(); + StochasticsPluginData.Builder builder = StochasticsPluginData.builder(); + for (TestRandomGeneratorId testRandomGeneratorId : TestRandomGeneratorId.values()) { + WellState wellState = WellState.builder().setSeed(randomGenerator.nextLong()).build(); + expectedRandomNumberGeneratorIds.put(testRandomGeneratorId, wellState); + builder.addRNG(testRandomGeneratorId, wellState); + } + WellState wellState = WellState.builder().setSeed(randomGenerator.nextLong()).build(); + builder.setMainRNGState(wellState); + StochasticsPluginData stochasticsPluginData = builder.build(); + + Map actualRandomNumberGeneratorIds = new LinkedHashMap<>(); + for (RandomNumberGeneratorId randomNumberGeneratorId : stochasticsPluginData.getRandomNumberGeneratorIds()) { + wellState = stochasticsPluginData.getWellState(randomNumberGeneratorId); + actualRandomNumberGeneratorIds.put(randomNumberGeneratorId, wellState); + } + assertEquals(expectedRandomNumberGeneratorIds, actualRandomNumberGeneratorIds); + } + + @Test + @UnitTestMethod(target = StochasticsPluginData.class, name = "getWellState", args = { + RandomNumberGeneratorId.class }) + public void testGetWellState_randomNumberGeneratorId() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1644320989680923741L); + Map expectedRandomNumberGeneratorIds = new LinkedHashMap<>(); + StochasticsPluginData.Builder builder = StochasticsPluginData.builder(); + for (TestRandomGeneratorId testRandomGeneratorId : TestRandomGeneratorId.values()) { + WellState wellState = WellState.builder().setSeed(randomGenerator.nextLong()).build(); + expectedRandomNumberGeneratorIds.put(testRandomGeneratorId, wellState); + builder.addRNG(testRandomGeneratorId, wellState); + } + WellState wellState = WellState.builder().setSeed(randomGenerator.nextLong()).build(); + builder.setMainRNGState(wellState); + StochasticsPluginData stochasticsPluginData = builder.build(); + + Map actualRandomNumberGeneratorIds = new LinkedHashMap<>(); + for (RandomNumberGeneratorId randomNumberGeneratorId : stochasticsPluginData.getRandomNumberGeneratorIds()) { + wellState = stochasticsPluginData.getWellState(randomNumberGeneratorId); + actualRandomNumberGeneratorIds.put(randomNumberGeneratorId, wellState); + } + assertEquals(expectedRandomNumberGeneratorIds, actualRandomNumberGeneratorIds); + } + + @Test + @UnitTestMethod(target = StochasticsPluginData.class, name = "builder", args = {}) + public void testBuilder() { + // show that the builder returns a non-null instance of + // StochasticsPluginData.Builder + assertNotNull(StochasticsPluginData.builder()); + } + + @Test + @UnitTestMethod(target = StochasticsPluginData.Builder.class, name = "build", args = {}) + public void testBuild() { + ContractException contractException = assertThrows(ContractException.class, + () -> StochasticsPluginData.builder().build()); + assertEquals(StochasticsError.NULL_SEED, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = StochasticsPluginData.Builder.class, name = "setMainRNGState", args = { WellState.class }) + public void testSetMainRNGState() { + long seed = 235234623445234756L; + WellState wellState = WellState.builder().setSeed(seed).build(); + StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder()// + .setMainRNGState(wellState)// + .build(); + assertEquals(seed, stochasticsPluginData.getWellState().getSeed()); + } + + @Test + @UnitTestMethod(target = StochasticsPluginData.Builder.class, name = "addRNG", args = { + RandomNumberGeneratorId.class, WellState.class }) + public void testAddRNG() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4300202782621809065L); + + Map expectedGenerators = new LinkedHashMap<>(); + + StochasticsPluginData.Builder builder = StochasticsPluginData.builder();// + builder.setMainRNGState(WellState.builder().setSeed(randomGenerator.nextLong()).build()); + for (TestRandomGeneratorId testRandomGeneratorId : TestRandomGeneratorId.values()) { + WellState wellState = WellState.builder().setSeed(randomGenerator.nextLong()).build(); + builder.addRNG(testRandomGeneratorId, wellState); + expectedGenerators.put(testRandomGeneratorId, wellState); + } + StochasticsPluginData stochasticsPluginData = builder.build(); + Map actualGenerators = new LinkedHashMap<>(); + for (RandomNumberGeneratorId randomNumberGeneratorId : stochasticsPluginData.getRandomNumberGeneratorIds()) { + WellState wellState = stochasticsPluginData.getWellState(randomNumberGeneratorId); + actualGenerators.put(randomNumberGeneratorId, wellState); + } + assertEquals(expectedGenerators, actualGenerators); + + // precondition test: if the random number generator id is null + ContractException contractException = assertThrows(ContractException.class, () -> { + WellState wellState = WellState.builder().setSeed(1130627593613916615L).build(); + + StochasticsPluginData.builder()// + .addRNG(null, wellState); + }); + assertEquals(StochasticsError.NULL_RANDOM_NUMBER_GENERATOR_ID, contractException.getErrorType()); + + // precondition test: if the well state is null + contractException = assertThrows(ContractException.class, () -> { + StochasticsPluginData.builder()// + .addRNG(TestRandomGeneratorId.DANCER, null); + }); + assertEquals(StochasticsError.NULL_WELL_STATE, contractException.getErrorType()); + + } + + private StochasticsPluginData getRandomStochasticsPluginData(long seed) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + Random random = new Random(randomGenerator.nextLong()); + List randomGeneratorIds = Arrays.asList(TestRandomGeneratorId.values()); + Collections.shuffle(randomGeneratorIds, random); + StochasticsPluginData.Builder builder = StochasticsPluginData.builder(); + builder.setMainRNGState(WellState.builder().setSeed(randomGenerator.nextLong()).build()); + for (TestRandomGeneratorId testRandomGeneratorId : randomGeneratorIds) { + builder.addRNG(testRandomGeneratorId, WellState.builder().setSeed(randomGenerator.nextLong()).build()); + if (randomGenerator.nextDouble() < 0.25) { + break; + } + } + return builder.build(); + } + + @Test + @UnitTestMethod(target = StochasticsPluginData.class, name = "equals", args = { Object.class }) + public void testEquals() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4427478130102505257L); + // never equal to null + for (int i = 0; i < 30; i++) { + StochasticsPluginData stochasticsPluginData = getRandomStochasticsPluginData(randomGenerator.nextLong()); + assertFalse(stochasticsPluginData.equals(null)); + } + + // reflexive + for (int i = 0; i < 30; i++) { + StochasticsPluginData stochasticsPluginData = getRandomStochasticsPluginData(randomGenerator.nextLong()); + assertTrue(stochasticsPluginData.equals(stochasticsPluginData)); + } + + // symmetric, transitive, consistent + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + StochasticsPluginData stochasticsPluginData1 = getRandomStochasticsPluginData(seed); + StochasticsPluginData stochasticsPluginData2 = getRandomStochasticsPluginData(seed); + for (int j = 0; j < 5; j++) { + assertTrue(stochasticsPluginData1.equals(stochasticsPluginData2)); + assertTrue(stochasticsPluginData2.equals(stochasticsPluginData1)); + } + } + + // different inputs yield non-equal objects + Set stochasticsPluginDatas = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + StochasticsPluginData stochasticsPluginData = getRandomStochasticsPluginData(randomGenerator.nextLong()); + stochasticsPluginDatas.add(stochasticsPluginData); + } + assertEquals(100, stochasticsPluginDatas.size()); + + } + + @Test + @UnitTestMethod(target = StochasticsPluginData.class, name = "hashCode", args = {}) + public void testHashCode() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8867946191732013544L); + + // equal objects have equal hash codes + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + StochasticsPluginData stochasticsPluginData1 = getRandomStochasticsPluginData(seed); + StochasticsPluginData stochasticsPluginData2 = getRandomStochasticsPluginData(seed); + assertEquals(stochasticsPluginData1, stochasticsPluginData2); + assertEquals(stochasticsPluginData1.hashCode(), stochasticsPluginData2.hashCode()); + } + + // hash codes are reasonably distributed + Set hashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + StochasticsPluginData stochasticsPluginData = getRandomStochasticsPluginData(randomGenerator.nextLong()); + hashCodes.add(stochasticsPluginData.hashCode()); + } + assertEquals(100, hashCodes.size()); + } + + @Test + @UnitTestMethod(target = StochasticsPluginData.class, name = "toString", args = {}) + public void testToString() { + StochasticsPluginData stochasticsPluginData = getRandomStochasticsPluginData(3688475113239640194L); + String actualValue = stochasticsPluginData.toString(); + + /* + * Expected value manually verified. It is impractical to use the full string for verification, so we will assert that certain critical substrings are contained as expected. + */ + assertTrue(actualValue.contains("StochasticsPluginData [data=Data [wellState=WellState [data=Data [seed=-3890456017103968429")); + assertTrue(actualValue.contains("VIXEN=WellState [data=Data [seed=-9202547125755605402")); + assertTrue(actualValue.contains("DONNER=WellState [data=Data [seed=-4994162167240462248")); + assertTrue(actualValue.contains("PRANCER=WellState [data=Data [seed=2580414198424993374")); + assertTrue(actualValue.contains("BLITZEN=WellState [data=Data [seed=-3565866373405448731")); + assertTrue(actualValue.contains("DANCER=WellState [data=Data [seed=3656710085871564729")); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/AT_StochasticsPluginId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/AT_StochasticsPluginId.java new file mode 100644 index 000000000..9805e8191 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/AT_StochasticsPluginId.java @@ -0,0 +1,17 @@ +package gov.hhs.aspr.ms.gcm.plugins.stochastics; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestField; + +public class AT_StochasticsPluginId { + + @Test + @UnitTestField(target = StochasticsPluginId.class, name = "PLUGIN_ID") + public void testPluginId() { + assertNotNull(StochasticsPluginId.PLUGIN_ID); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/support/AT_SimpleRandomNumberGeneratorId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/support/AT_SimpleRandomNumberGeneratorId.java new file mode 100644 index 000000000..dbcd5ff23 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/support/AT_SimpleRandomNumberGeneratorId.java @@ -0,0 +1,101 @@ +package gov.hhs.aspr.ms.gcm.plugins.stochastics.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; + +public class AT_SimpleRandomNumberGeneratorId { + @Test + @UnitTestConstructor(target = SimpleRandomNumberGeneratorId.class, args = { Object.class }) + public void testSimpleRandomNumberGeneratorId() { + + // precondition test: if the value is null + ContractException contractException = assertThrows(ContractException.class, + () -> new SimpleRandomNumberGeneratorId(null)); + assertEquals(StochasticsError.NULL_RANDOM_NUMBER_GENERATOR_ID, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = SimpleRandomNumberGeneratorId.class, name = "equals", args = { Object.class }) + public void testEquals() { + // not equal to null + assertFalse(new SimpleRandomNumberGeneratorId("A").equals(null)); + + // reflexive + for (int i = 0; i < 30; i++) { + SimpleRandomNumberGeneratorId simpleRandomNumberGeneratorId = new SimpleRandomNumberGeneratorId(i); + assertTrue(simpleRandomNumberGeneratorId.equals(simpleRandomNumberGeneratorId)); + } + + // symmetric, transitive and consistent + for (int i = 0; i < 30; i++) { + SimpleRandomNumberGeneratorId a = new SimpleRandomNumberGeneratorId(i); + SimpleRandomNumberGeneratorId b = new SimpleRandomNumberGeneratorId(i); + for (int j = 0; j < 10; j++) { + assertTrue(a.equals(b)); + assertTrue(b.equals(a)); + } + } + + // different inputs yield unequal SimpleRandomNumberGeneratorIds + for (int i = 0; i < 30; i++) { + SimpleRandomNumberGeneratorId a = new SimpleRandomNumberGeneratorId(i); + SimpleRandomNumberGeneratorId b = new SimpleRandomNumberGeneratorId(i + 1); + assertNotEquals(a, b); + } + } + + @Test + @UnitTestMethod(target = SimpleRandomNumberGeneratorId.class, name = "getValue", args = {}) + public void testGetValue() { + for (int i = 0; i < 30; i++) { + Integer input = i; + SimpleRandomNumberGeneratorId simpleRandomNumberGeneratorId = new SimpleRandomNumberGeneratorId(input); + assertEquals(input, simpleRandomNumberGeneratorId.getValue()); + } + } + + @Test + @UnitTestMethod(target = SimpleRandomNumberGeneratorId.class, name = "hashCode", args = {}) + public void testHashCode() { + // equal objects have equal hash codes + for (int i = 0; i < 30; i++) { + SimpleRandomNumberGeneratorId a = new SimpleRandomNumberGeneratorId(i); + SimpleRandomNumberGeneratorId b = new SimpleRandomNumberGeneratorId(i); + assertEquals(a, b); + assertEquals(a.hashCode(), b.hashCode()); + } + + //hash codes are reasonably distributed + Set hashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + SimpleRandomNumberGeneratorId a = new SimpleRandomNumberGeneratorId(i); + hashCodes.add(a.hashCode()); + } + assertEquals(100, hashCodes.size()); + + + } + + @Test + @UnitTestMethod(target = SimpleRandomNumberGeneratorId.class, name = "toString", args = {}) + public void testToString() { + SimpleRandomNumberGeneratorId simpleRandomNumberGeneratorId = new SimpleRandomNumberGeneratorId("Value"); + + String actualValue = simpleRandomNumberGeneratorId.toString(); + String expectedValue = "Value"; + + assertEquals(expectedValue, actualValue); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/support/AT_StochasticsError.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/support/AT_StochasticsError.java new file mode 100644 index 000000000..130c281df --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/support/AT_StochasticsError.java @@ -0,0 +1,29 @@ +package gov.hhs.aspr.ms.gcm.plugins.stochastics.support; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestMethod; + +public class AT_StochasticsError { + + @Test + @UnitTestMethod(target = StochasticsError.class, name = "getDescription", args = {}) + public void test() { + + // show that each description is a unique, non-null and non-empty string + Set descriptions = new LinkedHashSet<>(); + for (StochasticsError stochasticsError : StochasticsError.values()) { + String description = stochasticsError.getDescription(); + assertNotNull(description, "null description for " + stochasticsError); + assertTrue(description.length() > 0, "empty string for " + stochasticsError); + boolean unique = descriptions.add(description); + assertTrue(unique, "description for " + stochasticsError + " is not unique"); + } + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/support/AT_Well.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/support/AT_Well.java new file mode 100644 index 000000000..f55dc4526 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/support/AT_Well.java @@ -0,0 +1,208 @@ +package gov.hhs.aspr.ms.gcm.plugins.stochastics.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.random.RandomGeneratorProvider; + +public class AT_Well { + + @Test + @UnitTestConstructor(target = Well.class, args = {WellState.class}) + public void testWell() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3201733256995562119L); + for (int i = 0; i < 30; i++) { + Long seed = randomGenerator.nextLong(); + WellState wellState = createWellState(seed); + Well well = new Well(wellState); + WellState actualWellState = well.getWellState(); + assertNotNull(well); + assertEquals(wellState, actualWellState); + } + } + + @Test + @UnitTestMethod(target = Well.class, name = "getWellState", args = {}) + public void testGetWellState() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3201733256995562119L); + for (int i = 0; i < 30; i++) { + Long seed = randomGenerator.nextLong(); + WellState wellState = createWellState(seed); + Well well = new Well(wellState); + WellState actualWellState = well.getWellState(); + assertEquals(wellState, actualWellState); + } + } + + @Test + @UnitTestMethod(target = Well.class, name = "hashCode", args = {}) + public void testHashCode() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3103545448276048549L); + // show that equal well states have equal hash codes + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + WellState wellState1 = createWellState(seed); + WellState wellState2 = createWellState(seed); + assertEquals(wellState1, wellState2); + assertEquals(wellState1.hashCode(), wellState2.hashCode()); + } + + // show that hash codes are reasonably distributed + Set hashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + Long stateSeed = randomGenerator.nextLong(); + WellState wellState = createWellState(stateSeed); + Well well = new Well(wellState); + hashCodes.add(well.hashCode()); + } + assertTrue(hashCodes.size() >= 90); + } + + @Test + @UnitTestMethod(target = Well.class, name = "equals", args = {Object.class}) + public void testEquals() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7242512295369848202L); + // no object equals null + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + WellState wellState = createWellState(seed); + Well well = new Well(wellState); + assertFalse(well.equals(null)); + } + + // reflexive + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + WellState wellState = createWellState(seed); + Well well = new Well(wellState); + assertTrue(well.equals(well)); + } + + // symmetric + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + WellState wellState1 = createWellState(seed); + Well well1 = new Well(wellState1); + WellState wellState2 = createWellState(seed); + Well well2 = new Well(wellState2); + assertTrue(well1.equals(well2)); + assertTrue(well2.equals(well1)); + } + + // transitive + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + WellState wellState1 = createWellState(seed); + Well well1 = new Well(wellState1); + WellState wellState2 = createWellState(seed); + Well well2 = new Well(wellState2); + WellState wellState3 = createWellState(seed); + Well well3 = new Well(wellState3); + assertTrue(well1.equals(well2)); + assertTrue(well2.equals(well3)); + assertTrue(well1.equals(well3)); + } + } + + private WellState createWellState(Long seed) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + int stateIndex = randomGenerator.nextInt(1390); + int[] vArray = new int[1391]; + + for (int i = 0; i < 1391; i++) { + vArray[i] = randomGenerator.nextInt(); + } + WellState wellState = WellState.builder().setInternals(stateIndex, vArray).setSeed(seed).build(); + return wellState; + } + + @Test + @UnitTestMethod(target = Well.class, name = "toString", args = {}) + public void testToString() { + + Long seed = 169009019879709398L; + int[] vArray = new int[1391]; + for (int j = 0; j < 1391; j++) { + vArray[j] = j; + } + WellState wellState = WellState.builder().setInternals(45, vArray).setSeed(seed).build(); + Well well = new Well(wellState); + + String actualValue = well.toString(); + + String expectedValue = "Well [seed=169009019879709398, index=45, " + + "v=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, " + + "29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, " + + "58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, " + + "87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, " + + "113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, " + + "136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, " + + "160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, " + + "184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, " + + "208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, " + + "232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, " + + "256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, " + + "280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, " + + "304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, " + + "328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, " + + "352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, " + + "376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, " + + "400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, " + + "424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, " + + "448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, " + + "472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, " + + "496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, " + + "520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, " + + "544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, " + + "568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, " + + "592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, " + + "616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, " + + "640, 641, 642, 643, 644, 645, 646, 647, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, " + + "664, 665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, 686, 687, " + + "688, 689, 690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 700, 701, 702, 703, 704, 705, 706, 707, 708, 709, 710, 711, " + + "712, 713, 714, 715, 716, 717, 718, 719, 720, 721, 722, 723, 724, 725, 726, 727, 728, 729, 730, 731, 732, 733, 734, 735, " + + "736, 737, 738, 739, 740, 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, " + + "760, 761, 762, 763, 764, 765, 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778, 779, 780, 781, 782, 783, " + + "784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, 798, 799, 800, 801, 802, 803, 804, 805, 806, 807, " + + "808, 809, 810, 811, 812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 830, 831, " + + "832, 833, 834, 835, 836, 837, 838, 839, 840, 841, 842, 843, 844, 845, 846, 847, 848, 849, 850, 851, 852, 853, 854, 855, " + + "856, 857, 858, 859, 860, 861, 862, 863, 864, 865, 866, 867, 868, 869, 870, 871, 872, 873, 874, 875, 876, 877, 878, 879, " + + "880, 881, 882, 883, 884, 885, 886, 887, 888, 889, 890, 891, 892, 893, 894, 895, 896, 897, 898, 899, 900, 901, 902, 903, " + + "904, 905, 906, 907, 908, 909, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, " + + "928, 929, 930, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, " + + "952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 975, " + + "976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 989, 990, 991, 992, 993, 994, 995, 996, 997, 998, 999, " + + "1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019, " + + "1020, 1021, 1022, 1023, 1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, " + + "1040, 1041, 1042, 1043, 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, " + + "1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, 1070, 1071, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, " + + "1080, 1081, 1082, 1083, 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095, 1096, 1097, 1098, 1099, " + + "1100, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, 1109, 1110, 1111, 1112, 1113, 1114, 1115, 1116, 1117, 1118, 1119, " + + "1120, 1121, 1122, 1123, 1124, 1125, 1126, 1127, 1128, 1129, 1130, 1131, 1132, 1133, 1134, 1135, 1136, 1137, 1138, 1139, " + + "1140, 1141, 1142, 1143, 1144, 1145, 1146, 1147, 1148, 1149, 1150, 1151, 1152, 1153, 1154, 1155, 1156, 1157, 1158, 1159, " + + "1160, 1161, 1162, 1163, 1164, 1165, 1166, 1167, 1168, 1169, 1170, 1171, 1172, 1173, 1174, 1175, 1176, 1177, 1178, 1179, " + + "1180, 1181, 1182, 1183, 1184, 1185, 1186, 1187, 1188, 1189, 1190, 1191, 1192, 1193, 1194, 1195, 1196, 1197, 1198, 1199, " + + "1200, 1201, 1202, 1203, 1204, 1205, 1206, 1207, 1208, 1209, 1210, 1211, 1212, 1213, 1214, 1215, 1216, 1217, 1218, 1219, " + + "1220, 1221, 1222, 1223, 1224, 1225, 1226, 1227, 1228, 1229, 1230, 1231, 1232, 1233, 1234, 1235, 1236, 1237, 1238, 1239, " + + "1240, 1241, 1242, 1243, 1244, 1245, 1246, 1247, 1248, 1249, 1250, 1251, 1252, 1253, 1254, 1255, 1256, 1257, 1258, 1259, " + + "1260, 1261, 1262, 1263, 1264, 1265, 1266, 1267, 1268, 1269, 1270, 1271, 1272, 1273, 1274, 1275, 1276, 1277, 1278, 1279, " + + "1280, 1281, 1282, 1283, 1284, 1285, 1286, 1287, 1288, 1289, 1290, 1291, 1292, 1293, 1294, 1295, 1296, 1297, 1298, 1299, " + + "1300, 1301, 1302, 1303, 1304, 1305, 1306, 1307, 1308, 1309, 1310, 1311, 1312, 1313, 1314, 1315, 1316, 1317, 1318, 1319, " + + "1320, 1321, 1322, 1323, 1324, 1325, 1326, 1327, 1328, 1329, 1330, 1331, 1332, 1333, 1334, 1335, 1336, 1337, 1338, 1339, " + + "1340, 1341, 1342, 1343, 1344, 1345, 1346, 1347, 1348, 1349, 1350, 1351, 1352, 1353, 1354, 1355, 1356, 1357, 1358, 1359, " + + "1360, 1361, 1362, 1363, 1364, 1365, 1366, 1367, 1368, 1369, 1370, 1371, 1372, 1373, 1374, 1375, 1376, 1377, 1378, 1379, " + + "1380, 1381, 1382, 1383, 1384, 1385, 1386, 1387, 1388, 1389, 1390]]"; + + assertEquals(expectedValue, actualValue); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/support/AT_WellState.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/support/AT_WellState.java new file mode 100644 index 000000000..14331e497 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/support/AT_WellState.java @@ -0,0 +1,326 @@ +package gov.hhs.aspr.ms.gcm.plugins.stochastics.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTag; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +public class AT_WellState { + + @Test + @UnitTestMethod(target = WellState.Builder.class, name = "build", args = {}, tags = UnitTag.INCOMPLETE) + public void testBuild() { + WellState wellState = WellState.builder().build(); + assertNotNull(wellState); + } + + @Test + @UnitTestMethod(target = WellState.Builder.class, name = "setSeed", args = { + long.class }, tags = UnitTag.INCOMPLETE) + public void testSetSeed() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6503031989275285502L); + + for (int i = 0; i < 30; i++) { + long expectedSeed = randomGenerator.nextLong(); + WellState wellState = WellState.builder().setSeed(expectedSeed).build(); + Long actualSeed = wellState.getSeed(); + assertEquals(expectedSeed, actualSeed); + } + + } + + @Test + @UnitTestMethod(target = WellState.class, name = "builder", args = {}) + public void testBuilder() { + assertNotNull(WellState.builder()); + } + + @Test + @UnitTestMethod(target = WellState.class, name = "getSeed", args = {}) + public void testGetSeed() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4710925545909840517L); + for (int i = 0; i < 30; i++) { + Long seed = randomGenerator.nextLong(); + WellState wellState = createWellState(seed); + Long actualSeed = wellState.getSeed(); + assertEquals(seed, actualSeed); + } + } + + @Test + @UnitTestMethod(target = WellState.class, name = "getIndex", args = {}) + public void testGetIndex() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3907591903339469675L); + for (int i = 0; i < 30; i++) { + Long seed = randomGenerator.nextLong(); + Integer index = randomGenerator.nextInt(1390); + WellState wellState = createWellState(seed, index); + assertEquals(index, wellState.getIndex()); + } + } + + @Test + @UnitTestMethod(target = WellState.class, name = "getVArray", args = {}) + public void testGetVArray() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7474457766232034059L); + for (int i = 0; i < 30; i++) { + Long seed = randomGenerator.nextLong(); + int[] vArray = new int[1391]; + for (int j = 0; j < 1391; j++) { + vArray[j] = randomGenerator.nextInt(); + } + WellState wellState = createWellState(seed, vArray); + assertTrue(Arrays.equals(vArray, wellState.getVArray())); + } + } + + @Test + @UnitTestMethod(target = WellState.class, name = "hashCode", args = {}) + public void testHashCode() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3103545448276048549L); + + // show that equal well states have equal hash codes + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + WellState wellState1 = createWellState(seed); + WellState wellState2 = createWellState(seed); + assertEquals(wellState1, wellState2); + assertEquals(wellState1.hashCode(), wellState2.hashCode()); + } + + // show that hash codes are reasonably distributed + Set hashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + hashCodes.add(createWellState(randomGenerator.nextLong()).hashCode()); + } + assertTrue(hashCodes.size() >= 90); + } + + @Test + @UnitTestMethod(target = WellState.Builder.class, name = "setInternals", args = { int.class, + int[].class }, tags = UnitTag.INCOMPLETE) + public void testSetInternals() { + WellState.Builder builder = WellState.builder(); + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5209481424678049863L); + + for (int i = 0; i < 30; i++) { + Long seed = randomGenerator.nextLong(); + int index = randomGenerator.nextInt(1391); + int[] vArray = new int[1391]; + for (int j = 0; j < 1391; j++) { + vArray[j] = randomGenerator.nextInt(); + } + WellState wellState = builder.setInternals(index, vArray).setSeed(seed).build(); + assertEquals(index, wellState.getIndex()); + assertTrue(Arrays.equals(vArray, wellState.getVArray())); + } + + int index = 15; + int[] vArray = new int[1391]; + // precondition test: null vArray + ContractException contractException = assertThrows(ContractException.class, + () -> builder.setInternals(index, null)); + assertEquals(StochasticsError.ILLEGAL_SEED_ININITIAL_STATE, contractException.getErrorType()); + + // precondition test: improper vArray size + ContractException contractException2 = assertThrows(ContractException.class, + () -> builder.setInternals(index, new int[15])); + assertEquals(StochasticsError.ILLEGAL_SEED_ININITIAL_STATE, contractException2.getErrorType()); + + // precondition test: index out of allowed range + ContractException contractException3 = assertThrows(ContractException.class, + () -> builder.setInternals(-1, vArray)); + assertEquals(StochasticsError.ILLEGAL_SEED_ININITIAL_STATE, contractException3.getErrorType()); + + ContractException contractException4 = assertThrows(ContractException.class, + () -> builder.setInternals(1391, vArray)); + assertEquals(StochasticsError.ILLEGAL_SEED_ININITIAL_STATE, contractException4.getErrorType()); + } + + @Test + @UnitTestMethod(target = WellState.class, name = "equals", args = { Object.class }, tags = UnitTag.INCOMPLETE) + public void testEquals() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7242512295369848202L); + + // no object equals null + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + WellState wellState = createWellState(seed); + assertFalse(wellState.equals(null)); + } + + // stability + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + WellState wellState1 = createWellState(seed); + WellState wellState2 = createWellState(seed); + for (int j = 0; j < 10; j++) { + assertTrue(wellState1.equals(wellState2)); + assertTrue(wellState2.equals(wellState1)); + } + } + + // reflexive + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + WellState wellState = createWellState(seed); + assertTrue(wellState.equals(wellState)); + } + + // symmetric + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + WellState wellState1 = createWellState(seed); + WellState wellState2 = createWellState(seed); + assertTrue(wellState1.equals(wellState2)); + assertTrue(wellState2.equals(wellState1)); + } + + // transitive + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + WellState wellState1 = createWellState(seed); + WellState wellState2 = createWellState(seed); + WellState wellState3 = createWellState(seed); + assertTrue(wellState1.equals(wellState2)); + assertTrue(wellState2.equals(wellState3)); + assertTrue(wellState1.equals(wellState3)); + } + + // show that different inputs lead to non-equality -- assumed + for (int i = 0; i < 30; i++) { + long seed1 = randomGenerator.nextLong(); + long seed2 = randomGenerator.nextLong(); + if (seed1 != seed2) { + WellState wellState1 = createWellState(seed1); + WellState wellState2 = createWellState(seed2); + assertNotEquals(wellState1, wellState2); + } + } + } + + private WellState createWellState(Long seed) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + int stateIndex = randomGenerator.nextInt(1390); + int[] vArray = new int[1391]; + + for (int i = 0; i < 1391; i++) { + vArray[i] = randomGenerator.nextInt(); + } + WellState wellState = WellState.builder().setInternals(stateIndex, vArray).setSeed(seed).build(); + return wellState; + } + + private WellState createWellState(Long seed, Integer index) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + int stateIndex = index; + int[] vArray = new int[1391]; + + for (int i = 0; i < 1391; i++) { + vArray[i] = randomGenerator.nextInt(); + } + WellState wellState = WellState.builder().setInternals(stateIndex, vArray).setSeed(seed).build(); + return wellState; + } + + private WellState createWellState(Long seed, int[] vArray) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + int stateIndex = randomGenerator.nextInt(1390); + WellState wellState = WellState.builder().setInternals(stateIndex, vArray).setSeed(seed).build(); + return wellState; + } + + @Test + @UnitTestMethod(target = WellState.class, name = "toString", args = {}) + public void testToString() { + + Long seed = 169009019879709398L; + int[] vArray = new int[1391]; + for (int j = 0; j < 1391; j++) { + vArray[j] = j; + } + WellState wellState = WellState.builder().setInternals(45, vArray).setSeed(seed).build(); + + String actualValue = wellState.toString(); + + String expectedValue = "WellState [data=Data [seed=169009019879709398, index=45, " + + "vArray=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, " + + "29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, " + + "58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, " + + "87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, " + + "113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, " + + "136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, " + + "160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, " + + "184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, " + + "208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, " + + "232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, " + + "256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, " + + "280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, " + + "304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, " + + "328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, " + + "352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, " + + "376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, " + + "400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, " + + "424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, " + + "448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, " + + "472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, " + + "496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, " + + "520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, " + + "544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, " + + "568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, " + + "592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, " + + "616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, " + + "640, 641, 642, 643, 644, 645, 646, 647, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, " + + "664, 665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, 686, 687, " + + "688, 689, 690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 700, 701, 702, 703, 704, 705, 706, 707, 708, 709, 710, 711, " + + "712, 713, 714, 715, 716, 717, 718, 719, 720, 721, 722, 723, 724, 725, 726, 727, 728, 729, 730, 731, 732, 733, 734, 735, " + + "736, 737, 738, 739, 740, 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, " + + "760, 761, 762, 763, 764, 765, 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778, 779, 780, 781, 782, 783, " + + "784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, 798, 799, 800, 801, 802, 803, 804, 805, 806, 807, " + + "808, 809, 810, 811, 812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 830, 831, " + + "832, 833, 834, 835, 836, 837, 838, 839, 840, 841, 842, 843, 844, 845, 846, 847, 848, 849, 850, 851, 852, 853, 854, 855, " + + "856, 857, 858, 859, 860, 861, 862, 863, 864, 865, 866, 867, 868, 869, 870, 871, 872, 873, 874, 875, 876, 877, 878, 879, " + + "880, 881, 882, 883, 884, 885, 886, 887, 888, 889, 890, 891, 892, 893, 894, 895, 896, 897, 898, 899, 900, 901, 902, 903, " + + "904, 905, 906, 907, 908, 909, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, " + + "928, 929, 930, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, " + + "952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 975, " + + "976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 989, 990, 991, 992, 993, 994, 995, 996, 997, 998, 999, " + + "1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019, " + + "1020, 1021, 1022, 1023, 1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, " + + "1040, 1041, 1042, 1043, 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, " + + "1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, 1070, 1071, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, " + + "1080, 1081, 1082, 1083, 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095, 1096, 1097, 1098, 1099, " + + "1100, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, 1109, 1110, 1111, 1112, 1113, 1114, 1115, 1116, 1117, 1118, 1119, " + + "1120, 1121, 1122, 1123, 1124, 1125, 1126, 1127, 1128, 1129, 1130, 1131, 1132, 1133, 1134, 1135, 1136, 1137, 1138, 1139, " + + "1140, 1141, 1142, 1143, 1144, 1145, 1146, 1147, 1148, 1149, 1150, 1151, 1152, 1153, 1154, 1155, 1156, 1157, 1158, 1159, " + + "1160, 1161, 1162, 1163, 1164, 1165, 1166, 1167, 1168, 1169, 1170, 1171, 1172, 1173, 1174, 1175, 1176, 1177, 1178, 1179, " + + "1180, 1181, 1182, 1183, 1184, 1185, 1186, 1187, 1188, 1189, 1190, 1191, 1192, 1193, 1194, 1195, 1196, 1197, 1198, 1199, " + + "1200, 1201, 1202, 1203, 1204, 1205, 1206, 1207, 1208, 1209, 1210, 1211, 1212, 1213, 1214, 1215, 1216, 1217, 1218, 1219, " + + "1220, 1221, 1222, 1223, 1224, 1225, 1226, 1227, 1228, 1229, 1230, 1231, 1232, 1233, 1234, 1235, 1236, 1237, 1238, 1239, " + + "1240, 1241, 1242, 1243, 1244, 1245, 1246, 1247, 1248, 1249, 1250, 1251, 1252, 1253, 1254, 1255, 1256, 1257, 1258, 1259, " + + "1260, 1261, 1262, 1263, 1264, 1265, 1266, 1267, 1268, 1269, 1270, 1271, 1272, 1273, 1274, 1275, 1276, 1277, 1278, 1279, " + + "1280, 1281, 1282, 1283, 1284, 1285, 1286, 1287, 1288, 1289, 1290, 1291, 1292, 1293, 1294, 1295, 1296, 1297, 1298, 1299, " + + "1300, 1301, 1302, 1303, 1304, 1305, 1306, 1307, 1308, 1309, 1310, 1311, 1312, 1313, 1314, 1315, 1316, 1317, 1318, 1319, " + + "1320, 1321, 1322, 1323, 1324, 1325, 1326, 1327, 1328, 1329, 1330, 1331, 1332, 1333, 1334, 1335, 1336, 1337, 1338, 1339, " + + "1340, 1341, 1342, 1343, 1344, 1345, 1346, 1347, 1348, 1349, 1350, 1351, 1352, 1353, 1354, 1355, 1356, 1357, 1358, 1359, " + + "1360, 1361, 1362, 1363, 1364, 1365, 1366, 1367, 1368, 1369, 1370, 1371, 1372, 1373, 1374, 1375, 1376, 1377, 1378, 1379, " + + "1380, 1381, 1382, 1383, 1384, 1385, 1386, 1387, 1388, 1389, 1390]]]"; + + assertEquals(expectedValue, actualValue); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/testsupport/AT_StochasticsTestPluginFactory.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/testsupport/AT_StochasticsTestPluginFactory.java new file mode 100644 index 000000000..d33ca87c5 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/testsupport/AT_StochasticsTestPluginFactory.java @@ -0,0 +1,126 @@ +package gov.hhs.aspr.ms.gcm.plugins.stochastics.testsupport; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.function.Consumer; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.NucleusError; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.TestFactoryUtil; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestActorPlan; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginId; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPluginId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.StochasticsError; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.testsupport.StochasticsTestPluginFactory.Factory; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; +import util.wrappers.MutableBoolean; + +public class AT_StochasticsTestPluginFactory { + + @Test + @UnitTestMethod(target = StochasticsTestPluginFactory.class, name = "factory", args = { long.class, + Consumer.class }) + public void testFactory_Consumer() { + MutableBoolean executed = new MutableBoolean(); + + Factory factory = StochasticsTestPluginFactory.factory(576570479777898470L, c -> executed.setValue(true)); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + assertTrue(executed.getValue()); + + // precondition: consumer is null + Consumer nullConsumer = null; + ContractException contractException = assertThrows(ContractException.class, + () -> StochasticsTestPluginFactory.factory(0, nullConsumer)); + assertEquals(NucleusError.NULL_ACTOR_CONTEXT_CONSUMER, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = StochasticsTestPluginFactory.class, name = "factory", args = { long.class, + TestPluginData.class }) + public void testFactory_TestPluginData() { + MutableBoolean executed = new MutableBoolean(); + + TestPluginData.Builder builder = TestPluginData.builder(); + builder.addTestActorPlan("actor", new TestActorPlan(0, c -> executed.setValue(true))); + TestPluginData testPluginData = builder.build(); + + Factory factory = StochasticsTestPluginFactory.factory(45235233432345378L, testPluginData); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + assertTrue(executed.getValue()); + + // precondition: testPluginData is null + TestPluginData nullTestPluginData = null; + ContractException contractException = assertThrows(ContractException.class, + () -> StochasticsTestPluginFactory.factory(0, nullTestPluginData)); + assertEquals(NucleusError.NULL_PLUGIN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = StochasticsTestPluginFactory.Factory.class, name = "getPlugins", args = {}) + public void testGetPlugins() { + + List plugins = StochasticsTestPluginFactory.factory(3626443405517810332L, t -> { + }).getPlugins(); + assertEquals(2, plugins.size()); + + TestFactoryUtil.checkPluginExists(plugins, StochasticsPluginId.PLUGIN_ID); + TestFactoryUtil.checkPluginExists(plugins, TestPluginId.PLUGIN_ID); + } + + @Test + @UnitTestMethod(target = StochasticsTestPluginFactory.Factory.class, name = "setStochasticsPluginData", args = { + StochasticsPluginData.class }) + public void testSetStochasticsPluginData() { + StochasticsPluginData.Builder builder = StochasticsPluginData.builder(); + + WellState wellState = WellState.builder().setSeed(2990359774692004249L).build(); + builder.setMainRNGState(wellState); + + StochasticsPluginData stochasticsPluginData = builder.build(); + + List plugins = StochasticsTestPluginFactory.factory(5433603767451466687L, t -> { + }).setStochasticsPluginData(stochasticsPluginData).getPlugins(); + + TestFactoryUtil.checkPluginDataExists(plugins, stochasticsPluginData, StochasticsPluginId.PLUGIN_ID); + + // precondition: stochasticsPluginData is not null + ContractException contractException = assertThrows(ContractException.class, + () -> StochasticsTestPluginFactory.factory(5433603767451466687L, t -> { + }).setStochasticsPluginData(null)); + assertEquals(StochasticsError.NULL_STOCHASTICS_PLUGIN_DATA, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = StochasticsTestPluginFactory.class, name = "getStandardStochasticsPluginData", args = { + long.class }) + public void testGetStandardStochasticsPluginData() { + long seed = 6072871729256538807L; + + StochasticsPluginData.Builder builder = StochasticsPluginData.builder(); + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + WellState wellState = WellState.builder().setSeed(randomGenerator.nextLong()).build(); + builder.setMainRNGState(wellState); + for (TestRandomGeneratorId testRandomGeneratorId : TestRandomGeneratorId.values()) { + wellState = WellState.builder().setSeed(randomGenerator.nextLong()).build(); + builder.addRNG(testRandomGeneratorId, wellState); + } + + StochasticsPluginData expectedPluginData = builder.build(); + StochasticsPluginData actualPluginData = StochasticsTestPluginFactory.getStandardStochasticsPluginData(seed); + + assertEquals(expectedPluginData, actualPluginData); + } +} diff --git a/gcm3/src/test/java/plugins/stochastics/testsupport/AT_TestRandomGeneratorId.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/testsupport/AT_TestRandomGeneratorId.java similarity index 75% rename from gcm3/src/test/java/plugins/stochastics/testsupport/AT_TestRandomGeneratorId.java rename to gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/testsupport/AT_TestRandomGeneratorId.java index 606c8ab5a..525fed2ab 100644 --- a/gcm3/src/test/java/plugins/stochastics/testsupport/AT_TestRandomGeneratorId.java +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/stochastics/testsupport/AT_TestRandomGeneratorId.java @@ -1,4 +1,4 @@ -package plugins.stochastics.testsupport; +package gov.hhs.aspr.ms.gcm.plugins.stochastics.testsupport; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -9,19 +9,17 @@ import org.junit.jupiter.api.Test; -import plugins.stochastics.support.RandomNumberGeneratorId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.RandomNumberGeneratorId; +import util.annotations.UnitTestMethod; -@UnitTest(target = TestRandomGeneratorId.class) public class AT_TestRandomGeneratorId { - + /** - * Shows that a generated unknown RandomGeneratorId is not null and not a member - * of the enum + * Shows that a generated unknown RandomGeneratorId is not null and not a + * member of the enum */ @Test - @UnitTestMethod(name = "getUnknownRandomNumberGeneratorId", args = {}) + @UnitTestMethod(target = TestRandomGeneratorId.class, name = "getUnknownRandomNumberGeneratorId", args = {}) public void testGetUnknownRandomNumberGeneratorId() { Set randomNumberGeneratorIds = new LinkedHashSet<>(); for (int i = 0; i < 30; i++) { @@ -34,6 +32,5 @@ public void testGetUnknownRandomNumberGeneratorId() { } } } - - + } diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/AT_BooleanPropertyManager.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/AT_BooleanPropertyManager.java new file mode 100644 index 000000000..6453dc76c --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/AT_BooleanPropertyManager.java @@ -0,0 +1,273 @@ +package gov.hhs.aspr.ms.gcm.plugins.util.properties; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginFactory; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +/** + * Common interface to all person property managers. A person property manager + * manages all the property values for people for a particular person property + * identifier. + * + * + */ + +public class AT_BooleanPropertyManager { + + private Iterator getEmptyIndexIterator() { + return Collections.emptyIterator(); + } + + @Test + @UnitTestMethod(target = BooleanPropertyManager.class, name = "getPropertyValue", args = { int.class }) + public void testGetPropertyValue() { + Factory factory = TestPluginFactory.factory((c) -> { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4879223247393954289L); + + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Boolean.class) + .setDefaultValue(false).build(); + + BooleanPropertyManager booleanPropertyManager = new BooleanPropertyManager(propertyDefinition, + this::getEmptyIndexIterator); + + /* + * We will set the first 300 values multiple times at random + */ + Map expectedValues = new LinkedHashMap<>(); + + for (int i = 0; i < 1000; i++) { + int id = randomGenerator.nextInt(300); + boolean value = randomGenerator.nextBoolean(); + expectedValues.put(id, value); + booleanPropertyManager.setPropertyValue(id, value); + } + + /* + * if the value was set above, then it should equal the last value place in the + * expected values, otherwise it will have the default value. + */ + for (int i = 0; i < 300; i++) { + if (expectedValues.containsKey(i)) { + assertEquals(expectedValues.get(i), booleanPropertyManager.getPropertyValue(i)); + + } else { + assertFalse((Boolean) booleanPropertyManager.getPropertyValue(i)); + + } + } + + // precondition tests + ContractException contractException = assertThrows(ContractException.class, + () -> booleanPropertyManager.getPropertyValue(-1)); + assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + + @Test + @UnitTestMethod(target = BooleanPropertyManager.class, name = "setPropertyValue", args = { int.class, + Object.class }) + public void testSetPropertyValue() { + Factory factory = TestPluginFactory.factory((c) -> { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4827517950755837724L); + + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Boolean.class) + .setDefaultValue(false).build(); + + BooleanPropertyManager booleanPropertyManager = new BooleanPropertyManager(propertyDefinition, + this::getEmptyIndexIterator); + + /* + * We will set the first 300 values multiple times at random + */ + Map expectedValues = new LinkedHashMap<>(); + + for (int i = 0; i < 1000; i++) { + int id = randomGenerator.nextInt(300); + boolean value = randomGenerator.nextBoolean(); + expectedValues.put(id, value); + booleanPropertyManager.setPropertyValue(id, value); + } + + /* + * if the value was set above, then it should equal the last value place in the + * expected values, otherwise it will have the default value. + */ + for (int i = 0; i < 300; i++) { + if (expectedValues.containsKey(i)) { + assertEquals(expectedValues.get(i), booleanPropertyManager.getPropertyValue(i)); + + } else { + assertFalse((Boolean) booleanPropertyManager.getPropertyValue(i)); + + } + } + + // precondition tests + ContractException contractException = assertThrows(ContractException.class, + () -> booleanPropertyManager.setPropertyValue(-1, false)); + assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = BooleanPropertyManager.class, name = "removeId", args = { int.class }) + public void testRemoveId() { + Factory factory = TestPluginFactory.factory((c) -> { + + // we will first test the manager with an initial value of false + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Boolean.class) + .setDefaultValue(false).build(); + + BooleanPropertyManager booleanPropertyManager = new BooleanPropertyManager(propertyDefinition, + this::getEmptyIndexIterator); + + // initially, the value should be the default value for the manager + assertFalse((Boolean) booleanPropertyManager.getPropertyValue(5)); + + // after setting the value we should be able to retrieve a true + // value + booleanPropertyManager.setPropertyValue(5, true); + assertTrue((Boolean) booleanPropertyManager.getPropertyValue(5)); + + // removing the id from the manager should have no effect, since we + // do + // not waste time setting the value back to the default + booleanPropertyManager.removeId(5); + + assertTrue((Boolean) booleanPropertyManager.getPropertyValue(5)); + + // we will next test the manager with an initial value of true + propertyDefinition = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(true).build(); + + booleanPropertyManager = new BooleanPropertyManager(propertyDefinition, this::getEmptyIndexIterator); + + // initially, the value should be the default value for the manager + assertTrue((Boolean) booleanPropertyManager.getPropertyValue(5)); + + // after setting the value we should be able to retrieve a true + // value + booleanPropertyManager.setPropertyValue(5, false); + assertFalse((Boolean) booleanPropertyManager.getPropertyValue(5)); + + // removing the id from the manager should have no effect, since we + // do + // not waste time setting the value back to the default + booleanPropertyManager.removeId(5); + + assertFalse((Boolean) booleanPropertyManager.getPropertyValue(5)); + + // precondition tests + PropertyDefinition def = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(true).build(); + BooleanPropertyManager bpm = new BooleanPropertyManager(def, this::getEmptyIndexIterator); + + ContractException contractException = assertThrows(ContractException.class, () -> bpm.removeId(-1)); + assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestConstructor(target = BooleanPropertyManager.class, args = { PropertyDefinition.class, Supplier.class }) + public void testConstructor() { + Factory factory = TestPluginFactory.factory((c) -> { + + PropertyDefinition goodPropertyDefinition = PropertyDefinition.builder().setType(Boolean.class) + .setDefaultValue(false).build(); + PropertyDefinition badPropertyDefinition = PropertyDefinition.builder().setType(Double.class) + .setDefaultValue(2.3).build(); + + // precondition tests + + // if the property definition is null + ContractException contractException = assertThrows(ContractException.class, + () -> new BooleanPropertyManager(null, this::getEmptyIndexIterator)); + assertEquals(PropertyError.NULL_PROPERTY_DEFINITION, contractException.getErrorType()); + + // if the property definition does not have a type of Boolean.class + contractException = assertThrows(ContractException.class, + () -> new BooleanPropertyManager(badPropertyDefinition, this::getEmptyIndexIterator)); + assertEquals(PropertyError.PROPERTY_DEFINITION_IMPROPER_TYPE, contractException.getErrorType()); + + BooleanPropertyManager booleanPropertyManager = new BooleanPropertyManager(goodPropertyDefinition, + this::getEmptyIndexIterator); + assertNotNull(booleanPropertyManager); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = BooleanPropertyManager.class, name = "incrementCapacity", args = { int.class }) + public void testIncrementCapacity() { + Factory factory = TestPluginFactory.factory((c) -> { + + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Boolean.class) + .setDefaultValue(false).build(); + + BooleanPropertyManager booleanPropertyManager = new BooleanPropertyManager(propertyDefinition, + this::getEmptyIndexIterator); + + // precondition tests + ContractException contractException = assertThrows(ContractException.class, + () -> booleanPropertyManager.incrementCapacity(-1)); + assertEquals(PropertyError.NEGATIVE_CAPACITY_INCREMENT, contractException.getErrorType()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = BooleanPropertyManager.class, name = "toString", args = {}) + public void testToString() { + Factory factory = TestPluginFactory.factory((c) -> { + + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Boolean.class) + .setDefaultValue(false).build(); + + List list = new ArrayList<>(); + list.add(1); + list.add(2); + list.add(5); + list.add(6); + list.add(7); + + BooleanPropertyManager booleanPropertyManager = new BooleanPropertyManager(propertyDefinition, + () -> list.iterator()); + + booleanPropertyManager.setPropertyValue(5, true); + booleanPropertyManager.setPropertyValue(7, true); + booleanPropertyManager.setPropertyValue(1, true); + booleanPropertyManager.setPropertyValue(8, true); + + String actualValue = booleanPropertyManager.toString(); + + String expectedValue = "BooleanPropertyManager [boolContainer=BooleanContainer [defaultValue=false, bitSet=[1=true, 2=false, 5=true, 6=false, 7=true]]]"; + assertEquals(expectedValue, actualValue); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/AT_DoublePropertyManager.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/AT_DoublePropertyManager.java new file mode 100644 index 000000000..d49704cc6 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/AT_DoublePropertyManager.java @@ -0,0 +1,268 @@ +package gov.hhs.aspr.ms.gcm.plugins.util.properties; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginFactory; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +/** + * Common interface to all person property managers. A person property manager + * manages all the property values for people for a particular person property + * identifier. + * + * + */ + +public class AT_DoublePropertyManager { + + private Iterator getEmptyIndexIterator() { + return Collections.emptyIterator(); + } + + + @Test + @UnitTestMethod(target = DoublePropertyManager.class,name = "getPropertyValue", args = { int.class }) + public void testGetPropertyValue() { + Factory factory = TestPluginFactory.factory((c) -> { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3799865640223574835L); + + double defaultValue = 423.645; + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Double.class).setDefaultValue(defaultValue).build(); + + DoublePropertyManager doublePropertyManager = new DoublePropertyManager(propertyDefinition,this::getEmptyIndexIterator); + + /* + * We will set the first 300 values multiple times at random + */ + Map expectedValues = new LinkedHashMap<>(); + + for (int i = 0; i < 1000; i++) { + int id = randomGenerator.nextInt(300); + double value = randomGenerator.nextDouble(); + expectedValues.put(id, value); + doublePropertyManager.setPropertyValue(id, value); + } + + /* + * if the value was set above, then it should equal the last value + * place in the expected values, otherwise it will have the default + * value. + */ + for (int i = 0; i < 300; i++) { + if (expectedValues.containsKey(i)) { + assertEquals(expectedValues.get(i), doublePropertyManager.getPropertyValue(i)); + + } else { + assertEquals(defaultValue, (Double) doublePropertyManager.getPropertyValue(i)); + + } + } + + // precondition tests + ContractException contractException = assertThrows(ContractException.class, () -> doublePropertyManager.getPropertyValue(-1)); + assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + + @Test + @UnitTestMethod(target = DoublePropertyManager.class,name = "setPropertyValue", args = { int.class, Object.class }) + public void testSetPropertyValue() { + Factory factory = TestPluginFactory.factory((c) -> { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1599837792379294459L); + + double defaultValue = 423.645; + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Double.class).setDefaultValue(defaultValue).build(); + + DoublePropertyManager doublePropertyManager = new DoublePropertyManager(propertyDefinition,this::getEmptyIndexIterator); + + /* + * We will set the first 300 values multiple times at random + */ + Map expectedValues = new LinkedHashMap<>(); + + for (int i = 0; i < 1000; i++) { + int id = randomGenerator.nextInt(300); + double value = randomGenerator.nextDouble(); + expectedValues.put(id, value); + doublePropertyManager.setPropertyValue(id, value); + } + + /* + * if the value was set above, then it should equal the last value + * place in the expected values, otherwise it will have the default + * value. + */ + for (int i = 0; i < 300; i++) { + if (expectedValues.containsKey(i)) { + assertEquals(expectedValues.get(i), doublePropertyManager.getPropertyValue(i)); + + } else { + assertEquals(defaultValue, (Double) doublePropertyManager.getPropertyValue(i)); + + } + } + + // precondition tests + + ContractException contractException = assertThrows(ContractException.class, () -> doublePropertyManager.setPropertyValue(-1, 23.4)); + assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = DoublePropertyManager.class,name = "removeId", args = { int.class }) + public void testRemoveId() { + Factory factory = TestPluginFactory.factory((c) -> { + /* + * Should have no effect on the value that is stored for the sake of + * efficiency. + */ + + // we will first test the manager with an initial value of false + double defaultValue = 6.2345345; + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Double.class).setDefaultValue(defaultValue).build(); + + DoublePropertyManager doublePropertyManager = new DoublePropertyManager(propertyDefinition,this::getEmptyIndexIterator); + + // initially, the value should be the default value for the manager + assertEquals(defaultValue, (Double) doublePropertyManager.getPropertyValue(5), 0); + + // after setting the value we should be able to retrieve a new value + double newValue = 34534.4; + doublePropertyManager.setPropertyValue(5, newValue); + assertEquals(newValue, (Double) doublePropertyManager.getPropertyValue(5), 0); + + // removing the id from the manager should have no effect, since we + // do + // not waste time setting the value back to the default + doublePropertyManager.removeId(5); + + assertEquals(newValue, (Double) doublePropertyManager.getPropertyValue(5), 0); + + // we will next test the manager with an initial value of true + propertyDefinition = PropertyDefinition.builder().setType(Double.class).setDefaultValue(defaultValue).build(); + + doublePropertyManager = new DoublePropertyManager(propertyDefinition,this::getEmptyIndexIterator); + + // initially, the value should be the default value for the manager + assertEquals(defaultValue, (Double) doublePropertyManager.getPropertyValue(5), 0); + + // after setting the value we should be able to retrieve the new + // value + doublePropertyManager.setPropertyValue(5, newValue); + assertEquals(newValue, (Double) doublePropertyManager.getPropertyValue(5), 0); + + // removing the id from the manager should have no effect, since we + // do + // not waste time setting the value back to the default + doublePropertyManager.removeId(5); + + assertEquals(newValue, (Double) doublePropertyManager.getPropertyValue(5), 0); + + // precondition tests + ContractException contractException = assertThrows(ContractException.class, () -> { + PropertyDefinition def = PropertyDefinition.builder().setType(Double.class).setDefaultValue(4534.4).build(); + DoublePropertyManager dpm = new DoublePropertyManager(def,this::getEmptyIndexIterator); + dpm.removeId(-1); + }); + assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestConstructor(target = DoublePropertyManager.class,args = {PropertyDefinition.class, Supplier.class }) + public void testConstructor() { + Factory factory = TestPluginFactory.factory((c) -> { + + PropertyDefinition goodPropertyDefinition = PropertyDefinition.builder().setType(Double.class).setDefaultValue(2.3).build(); + PropertyDefinition badPropertyDefinition = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(false).build(); + + // precondition tests + + // if the property definition is null + ContractException contractException = assertThrows(ContractException.class, () -> new DoublePropertyManager(null,this::getEmptyIndexIterator)); + assertEquals(PropertyError.NULL_PROPERTY_DEFINITION, contractException.getErrorType()); + + // if the property definition does not have a type of Double.class + contractException = assertThrows(ContractException.class, () -> new DoublePropertyManager(badPropertyDefinition,this::getEmptyIndexIterator)); + assertEquals(PropertyError.PROPERTY_DEFINITION_IMPROPER_TYPE, contractException.getErrorType()); + + + DoublePropertyManager doublePropertyManager = new DoublePropertyManager(goodPropertyDefinition,this::getEmptyIndexIterator); + assertNotNull(doublePropertyManager); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = DoublePropertyManager.class,name = "incrementCapacity", args = { int.class }) + public void testIncrementCapacity() { + Factory factory = TestPluginFactory.factory((c) -> { + + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Double.class).setDefaultValue(2.42).build(); + + DoublePropertyManager doublePropertyManager = new DoublePropertyManager(propertyDefinition,this::getEmptyIndexIterator); + + // precondition tests + ContractException contractException = assertThrows(ContractException.class, () -> doublePropertyManager.incrementCapacity(-1)); + assertEquals(PropertyError.NEGATIVE_CAPACITY_INCREMENT, contractException.getErrorType()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + + @Test + @UnitTestMethod(target = DoublePropertyManager.class, name = "toString", args = {}) + public void testToString() { + Factory factory = TestPluginFactory.factory((c) -> { + + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Double.class) + .setDefaultValue(0.0).build(); + + List list = new ArrayList<>(); + list.add(1); + list.add(2); + list.add(5); + list.add(6); + list.add(7); + + DoublePropertyManager doublePropertyManager = new DoublePropertyManager(propertyDefinition, + () -> list.iterator()); + + doublePropertyManager.setPropertyValue(5, 2.5); + doublePropertyManager.setPropertyValue(7, 3.5); + doublePropertyManager.setPropertyValue(1, 0.5); + doublePropertyManager.setPropertyValue(8, 4.0); + + String actualValue = doublePropertyManager.toString(); + + String expectedValue = "DoublePropertyManager [doubleValueContainer=DoubleValueContainer [values=[1=0.5, 2=0.0, 5=2.5, 6=0.0, 7=3.5], defaultValue=0.0]]"; + assertEquals(expectedValue, actualValue); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/AT_EnumPropertyManager.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/AT_EnumPropertyManager.java new file mode 100644 index 000000000..8f94bff1b --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/AT_EnumPropertyManager.java @@ -0,0 +1,271 @@ +package gov.hhs.aspr.ms.gcm.plugins.util.properties; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginFactory; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +/** + * Common interface to all person property managers. A person property manager + * manages all the property values for people for a particular person property + * identifier. + * + * + */ + +public class AT_EnumPropertyManager { + + private Iterator getEmptyIndexIterator() { + return Collections.emptyIterator(); + } + + @Test + @UnitTestMethod(target = EnumPropertyManager.class, name = "getPropertyValue", args = { int.class }) + public void testGetPropertyValue() { + Factory factory = TestPluginFactory.factory((c) -> { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5102684240650614254L); + + Color defaultValue = Color.YELLOW; + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Color.class).setDefaultValue(defaultValue).build(); + + EnumPropertyManager enumPropertyManager = new EnumPropertyManager(propertyDefinition, this::getEmptyIndexIterator); + + /* + * We will set the first 300 values multiple times at random + */ + Map expectedValues = new LinkedHashMap<>(); + + for (int i = 0; i < 1000; i++) { + int id = randomGenerator.nextInt(300); + Color value = Color.values()[randomGenerator.nextInt(Color.values().length)]; + expectedValues.put(id, value); + enumPropertyManager.setPropertyValue(id, value); + } + + /* + * if the value was set above, then it should equal the last value + * place in the expected values, otherwise it will have the default + * value. + */ + for (int i = 0; i < 300; i++) { + if (expectedValues.containsKey(i)) { + assertEquals(expectedValues.get(i), enumPropertyManager.getPropertyValue(i)); + + } else { + assertEquals(defaultValue, (Color) enumPropertyManager.getPropertyValue(i)); + + } + } + + // precondition tests + ContractException contractException = assertThrows(ContractException.class, () -> enumPropertyManager.getPropertyValue(-1)); + assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + + @Test + @UnitTestMethod(target = EnumPropertyManager.class, name = "setPropertyValue", args = { int.class, Object.class }) + public void testSetPropertyValue() { + Factory factory = TestPluginFactory.factory((c) -> { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6716984272666831621L); + + Color defaultValue = Color.YELLOW; + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Color.class).setDefaultValue(defaultValue).build(); + + EnumPropertyManager enumPropertyManager = new EnumPropertyManager(propertyDefinition, this::getEmptyIndexIterator); + + /* + * We will set the first 300 values multiple times at random + */ + Map expectedValues = new LinkedHashMap<>(); + + for (int i = 0; i < 1000; i++) { + int id = randomGenerator.nextInt(300); + Color value = Color.values()[randomGenerator.nextInt(Color.values().length)]; + expectedValues.put(id, value); + enumPropertyManager.setPropertyValue(id, value); + } + + /* + * if the value was set above, then it should equal the last value + * place in the expected values, otherwise it will have the default + * value. + */ + for (int i = 0; i < 300; i++) { + if (expectedValues.containsKey(i)) { + assertEquals(expectedValues.get(i), enumPropertyManager.getPropertyValue(i)); + + } else { + assertEquals(defaultValue, (Color) enumPropertyManager.getPropertyValue(i)); + + } + } + + // precondition tests + ContractException contractException = assertThrows(ContractException.class, () -> enumPropertyManager.setPropertyValue(-1, Color.BLUE)); + assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = EnumPropertyManager.class, name = "removeId", args = { int.class }) + public void testRemoveId() { + Factory factory = TestPluginFactory.factory((c) -> { + /* + * Should have no effect on the value that is stored for the sake of + * efficiency. + */ + + // we will first test the manager with an initial value of false + Color defaultValue = Color.RED; + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Color.class).setDefaultValue(defaultValue).build(); + + EnumPropertyManager enumPropertyManager = new EnumPropertyManager(propertyDefinition, this::getEmptyIndexIterator); + + // initially, the value should be the default value for the manager + assertEquals(defaultValue, (Color) enumPropertyManager.getPropertyValue(5)); + + // after setting the value we should be able to retrieve a new value + Color newValue = Color.BLUE; + enumPropertyManager.setPropertyValue(5, newValue); + assertEquals(newValue, (Color) enumPropertyManager.getPropertyValue(5)); + + // removing the id from the manager should have no effect, since we + // do + // not waste time setting the value back to the default + enumPropertyManager.removeId(5); + + assertEquals(newValue, (Color) enumPropertyManager.getPropertyValue(5)); + + // we will next test the manager with an initial value of true + propertyDefinition = PropertyDefinition.builder().setType(Color.class).setDefaultValue(defaultValue).build(); + + enumPropertyManager = new EnumPropertyManager(propertyDefinition, this::getEmptyIndexIterator); + + // initially, the value should be the default value for the manager + assertEquals(defaultValue, (Color) enumPropertyManager.getPropertyValue(5)); + + // after setting the value we should be able to retrieve the new + // value + enumPropertyManager.setPropertyValue(5, newValue); + assertEquals(newValue, (Color) enumPropertyManager.getPropertyValue(5)); + + // removing the id from the manager should have no effect, since we + // do + // not waste time setting the value back to the default + enumPropertyManager.removeId(5); + + assertEquals(newValue, (Color) enumPropertyManager.getPropertyValue(5)); + + // precondition tests + // precondition tests + PropertyDefinition def = PropertyDefinition.builder().setType(Color.class).setDefaultValue(Color.YELLOW).build(); + EnumPropertyManager epm = new EnumPropertyManager(def, this::getEmptyIndexIterator); + + ContractException contractException = assertThrows(ContractException.class, () -> epm.removeId(-1)); + + assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + // Helper enum + private static enum Color { + RED, YELLOW, BLUE; + } + + @Test + @UnitTestConstructor(target = EnumPropertyManager.class, args = {PropertyDefinition.class, Supplier.class }) + public void testConstructor() { + Factory factory = TestPluginFactory.factory((c) -> { + PropertyDefinition goodPropertyDefinition = PropertyDefinition.builder().setType(Color.class).setDefaultValue(Color.BLUE).build(); + PropertyDefinition badPropertyDefinition = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(false).build(); + + EnumPropertyManager enumPropertyManager = new EnumPropertyManager(goodPropertyDefinition, this::getEmptyIndexIterator); + assertNotNull(enumPropertyManager); + + // precondition test: if the property definition is null + ContractException contractException = assertThrows(ContractException.class, () -> new EnumPropertyManager(null, this::getEmptyIndexIterator)); + assertEquals(PropertyError.NULL_PROPERTY_DEFINITION, contractException.getErrorType()); + + // precondition test: if the property definition does not have a type of Enum.class + contractException = assertThrows(ContractException.class, () -> new EnumPropertyManager(badPropertyDefinition, this::getEmptyIndexIterator)); + assertEquals(PropertyError.PROPERTY_DEFINITION_IMPROPER_TYPE, contractException.getErrorType()); + + + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = EnumPropertyManager.class, name = "incrementCapacity", args = { int.class }) + public void testIncrementCapacity() { + Factory factory = TestPluginFactory.factory((c) -> { + + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Color.class).setDefaultValue(Color.RED).build(); + + EnumPropertyManager enumPropertyManager = new EnumPropertyManager(propertyDefinition, this::getEmptyIndexIterator); + + // precondition tests + ContractException contractException = assertThrows(ContractException.class, () -> enumPropertyManager.incrementCapacity(-1)); + assertEquals(PropertyError.NEGATIVE_CAPACITY_INCREMENT, contractException.getErrorType()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = EnumPropertyManager.class, name = "toString", args = {}) + public void testToString() { + Factory factory = TestPluginFactory.factory((c) -> { + + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Color.class) + .setDefaultValue(Color.BLUE).build(); + + List list = new ArrayList<>(); + list.add(1); + list.add(2); + list.add(5); + list.add(6); + list.add(7); + + EnumPropertyManager enumPropertyManager = new EnumPropertyManager(propertyDefinition, + () -> list.iterator()); + + enumPropertyManager.setPropertyValue(5, Color.RED); + enumPropertyManager.setPropertyValue(7, Color.YELLOW); + enumPropertyManager.setPropertyValue(1, Color.RED); + enumPropertyManager.setPropertyValue(8, Color.BLUE); + + String actualValue = enumPropertyManager.toString(); + + + String expectedValue = "EnumPropertyManager [enumContainer=EnumContainer [values=[1=RED, 2=BLUE, 5=RED, 6=BLUE, 7=YELLOW], enumClass=class gov.hhs.aspr.ms.gcm.plugins.util.properties.AT_EnumPropertyManager$Color]]"; + + assertEquals(expectedValue, actualValue); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/AT_FloatPropertyManager.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/AT_FloatPropertyManager.java new file mode 100644 index 000000000..6f52b247c --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/AT_FloatPropertyManager.java @@ -0,0 +1,277 @@ +package gov.hhs.aspr.ms.gcm.plugins.util.properties; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginFactory; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +/** + * Common interface to all person property managers. A person property manager + * manages all the property values for people for a particular person property + * identifier. + * + * + */ + +public class AT_FloatPropertyManager { + + private Iterator getEmptyIndexIterator() { + return Collections.emptyIterator(); + } + + @Test + @UnitTestMethod(target = FloatPropertyManager.class, name = "getPropertyValue", args = { int.class }) + public void testGetPropertyValue() { + + Factory factory = TestPluginFactory.factory((c) -> { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8486538414190886901L); + + float defaultValue = 423.645F; + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Float.class) + .setDefaultValue(defaultValue).build(); + + FloatPropertyManager floatPropertyManager = new FloatPropertyManager(propertyDefinition, + this::getEmptyIndexIterator); + + /* + * We will set the first 300 values multiple times at random + */ + Map expectedValues = new LinkedHashMap<>(); + + for (int i = 0; i < 1000; i++) { + int id = randomGenerator.nextInt(300); + float value = randomGenerator.nextFloat(); + expectedValues.put(id, value); + floatPropertyManager.setPropertyValue(id, value); + } + + /* + * if the value was set above, then it should equal the last value place in the + * expected values, otherwise it will have the default value. + */ + for (int i = 0; i < 300; i++) { + if (expectedValues.containsKey(i)) { + assertEquals(expectedValues.get(i), floatPropertyManager.getPropertyValue(i)); + + } else { + assertEquals(defaultValue, (Float) floatPropertyManager.getPropertyValue(i)); + + } + } + + // precondition tests + ContractException contractException = assertThrows(ContractException.class, + () -> floatPropertyManager.getPropertyValue(-1)); + assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = FloatPropertyManager.class, name = "setPropertyValue", args = { int.class, Object.class }) + public void testSetPropertyValue() { + Factory factory = TestPluginFactory.factory((c) -> { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6087185710247012204L); + + float defaultValue = 423.645F; + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Float.class) + .setDefaultValue(defaultValue).build(); + + FloatPropertyManager floatPropertyManager = new FloatPropertyManager(propertyDefinition, + this::getEmptyIndexIterator); + + /* + * We will set the first 300 values multiple times at random + */ + Map expectedValues = new LinkedHashMap<>(); + + for (int i = 0; i < 1000; i++) { + int id = randomGenerator.nextInt(300); + float value = randomGenerator.nextFloat(); + expectedValues.put(id, value); + floatPropertyManager.setPropertyValue(id, value); + } + + /* + * if the value was set above, then it should equal the last value place in the + * expected values, otherwise it will have the default value. + */ + for (int i = 0; i < 300; i++) { + if (expectedValues.containsKey(i)) { + assertEquals(expectedValues.get(i), floatPropertyManager.getPropertyValue(i)); + + } else { + assertEquals(defaultValue, (Float) floatPropertyManager.getPropertyValue(i)); + + } + } + + // precondition tests + ContractException contractException = assertThrows(ContractException.class, + () -> floatPropertyManager.setPropertyValue(-1, 3.4F)); + assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = FloatPropertyManager.class, name = "removeId", args = { int.class }) + public void testRemoveId() { + + Factory factory = TestPluginFactory.factory((c) -> { + /* + * Should have no effect on the value that is stored for the sake of efficiency. + */ + + // we will first test the manager with an initial value of false + float defaultValue = 6.2345345F; + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Float.class) + .setDefaultValue(defaultValue).build(); + + FloatPropertyManager floatPropertyManager = new FloatPropertyManager(propertyDefinition, + this::getEmptyIndexIterator); + + // initially, the value should be the default value for the manager + assertEquals(defaultValue, (Float) floatPropertyManager.getPropertyValue(5), 0); + + // after setting the value we should be able to retrieve a new value + float newValue = 34534.4F; + floatPropertyManager.setPropertyValue(5, newValue); + assertEquals(newValue, (Float) floatPropertyManager.getPropertyValue(5), 0); + + // removing the id from the manager should have no effect, since we + // do + // not waste time setting the value back to the default + floatPropertyManager.removeId(5); + + assertEquals(newValue, (Float) floatPropertyManager.getPropertyValue(5), 0); + + // we will next test the manager with an initial value of true + propertyDefinition = PropertyDefinition.builder().setType(Float.class).setDefaultValue(defaultValue) + .build(); + + floatPropertyManager = new FloatPropertyManager(propertyDefinition, this::getEmptyIndexIterator); + + // initially, the value should be the default value for the manager + assertEquals(defaultValue, (Float) floatPropertyManager.getPropertyValue(5), 0); + + // after setting the value we should be able to retrieve the new + // value + floatPropertyManager.setPropertyValue(5, newValue); + assertEquals(newValue, (Float) floatPropertyManager.getPropertyValue(5), 0); + + // removing the id from the manager should have no effect, since we + // do + // not waste time setting the value back to the default + floatPropertyManager.removeId(5); + + assertEquals(newValue, (Float) floatPropertyManager.getPropertyValue(5), 0); + + // precondition tests + PropertyDefinition def = PropertyDefinition.builder().setType(Float.class).setDefaultValue(4.5F).build(); + FloatPropertyManager fpm = new FloatPropertyManager(def, this::getEmptyIndexIterator); + + ContractException contractException = assertThrows(ContractException.class, () -> fpm.removeId(-1)); + assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestConstructor(target = FloatPropertyManager.class, args = { PropertyDefinition.class, Supplier.class }) + public void testConstructor() { + Factory factory = TestPluginFactory.factory((c) -> { + + PropertyDefinition goodPropertyDefinition = PropertyDefinition.builder().setType(Float.class) + .setDefaultValue(2.3F).build(); + PropertyDefinition badPropertyDefinition = PropertyDefinition.builder().setType(Boolean.class) + .setDefaultValue(false).build(); + + // if the property definition is null + ContractException contractException = assertThrows(ContractException.class, + () -> new FloatPropertyManager(null, this::getEmptyIndexIterator)); + assertEquals(PropertyError.NULL_PROPERTY_DEFINITION, contractException.getErrorType()); + + // if the property definition does not have a type of Float.class + contractException = assertThrows(ContractException.class, + () -> new FloatPropertyManager(badPropertyDefinition, this::getEmptyIndexIterator)); + assertEquals(PropertyError.PROPERTY_DEFINITION_IMPROPER_TYPE, contractException.getErrorType()); + + FloatPropertyManager doublePropertyManager = new FloatPropertyManager(goodPropertyDefinition, + this::getEmptyIndexIterator); + assertNotNull(doublePropertyManager); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = FloatPropertyManager.class, name = "incrementCapacity", args = { int.class }) + public void testIncrementCapacity() { + Factory factory = TestPluginFactory.factory((c) -> { + + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Float.class) + .setDefaultValue(234.42F).build(); + + FloatPropertyManager floatPropertyManager = new FloatPropertyManager(propertyDefinition, + this::getEmptyIndexIterator); + + // precondition tests + ContractException contractException = assertThrows(ContractException.class, + () -> floatPropertyManager.incrementCapacity(-1)); + assertEquals(PropertyError.NEGATIVE_CAPACITY_INCREMENT, contractException.getErrorType()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = FloatPropertyManager.class, name = "toString", args = {}) + public void testToString() { + Factory factory = TestPluginFactory.factory((c) -> { + + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Float.class) + .setDefaultValue(0.0F).build(); + + List list = new ArrayList<>(); + list.add(1); + list.add(2); + list.add(5); + list.add(6); + list.add(7); + + FloatPropertyManager floatPropertyManager = new FloatPropertyManager(propertyDefinition, + () -> list.iterator()); + + floatPropertyManager.setPropertyValue(5, 2.5F); + floatPropertyManager.setPropertyValue(7, 3.5F); + floatPropertyManager.setPropertyValue(1, 0.5F); + floatPropertyManager.setPropertyValue(8, 4.0F); + + String actualValue = floatPropertyManager.toString(); + + String expectedValue = "FloatPropertyManager [floatValueContainer=FloatValueContainer [values=[1=0.5, 2=0.0, 5=2.5, 6=0.0, 7=3.5], defaultValue=0.0]]"; + + assertEquals(expectedValue, actualValue); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/AT_IntPropertyManager.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/AT_IntPropertyManager.java new file mode 100644 index 000000000..5c312b33e --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/AT_IntPropertyManager.java @@ -0,0 +1,293 @@ +package gov.hhs.aspr.ms.gcm.plugins.util.properties; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestDataManager; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginFactory; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +/** + * Common interface to all person property managers. A person property manager + * manages all the property values for people for a particular person property + * identifier. + * + * + */ +public class AT_IntPropertyManager { + + private Iterator getEmptyIndexIterator() { + return Collections.emptyIterator(); + } + + @Test + @UnitTestMethod(target = IntPropertyManager.class, name = "getPropertyValue", args = { int.class }) + public void testGetPropertyValue() { + Factory factory = TestPluginFactory.factory((c) -> { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7951361060252638380L); + + int defaultValue = 423; + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class) + .setDefaultValue(defaultValue).build(); + + IntPropertyManager intPropertyManager = new IntPropertyManager(propertyDefinition, + this::getEmptyIndexIterator); + + /* + * We will set the first 300 values multiple times at random + */ + Map expectedValues = new LinkedHashMap<>(); + + for (int i = 0; i < 1000; i++) { + int id = randomGenerator.nextInt(300); + int value = randomGenerator.nextInt(); + expectedValues.put(id, value); + intPropertyManager.setPropertyValue(id, value); + } + + /* + * if the value was set above, then it should equal the last value place in the + * expected values, otherwise it will have the default value. + */ + for (int i = 0; i < 300; i++) { + if (expectedValues.containsKey(i)) { + assertEquals(expectedValues.get(i), intPropertyManager.getPropertyValue(i)); + + } else { + assertEquals(defaultValue, ((Integer) intPropertyManager.getPropertyValue(i)).intValue()); + } + } + + // precondition tests + ContractException contractException = assertThrows(ContractException.class, + () -> intPropertyManager.getPropertyValue(-1)); + assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + /* + * Local data manager used to properly initialize an ObjectPropertyManager for + * use in time sensitive tests + */ + public static class LocalDM extends TestDataManager { + public IntPropertyManager intPropertyManager; + + @Override + public void init(DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class) + .setDefaultValue(342).build(); + + Supplier> supplier = ()->Collections.emptyIterator(); + + intPropertyManager = new IntPropertyManager(propertyDefinition, supplier); + } + } + + @Test + @UnitTestMethod(target = IntPropertyManager.class, name = "setPropertyValue", args = { int.class, Object.class }) + public void testSetPropertyValue() { + Factory factory = TestPluginFactory.factory((c) -> { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5297426971018191882L); + + int defaultValue = 423; + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class) + .setDefaultValue(defaultValue).build(); + + IntPropertyManager intPropertyManager = new IntPropertyManager(propertyDefinition, + this::getEmptyIndexIterator); + + /* + * We will set the first 300 values multiple times at random + */ + Map expectedValues = new LinkedHashMap<>(); + + for (int i = 0; i < 1000; i++) { + int id = randomGenerator.nextInt(300); + int value = randomGenerator.nextInt(); + expectedValues.put(id, value); + intPropertyManager.setPropertyValue(id, value); + } + + /* + * if the value was set above, then it should equal the last value place in the + * expected values, otherwise it will have the default value. + */ + for (int i = 0; i < 300; i++) { + if (expectedValues.containsKey(i)) { + assertEquals(expectedValues.get(i), intPropertyManager.getPropertyValue(i)); + + } else { + assertEquals(defaultValue, ((Integer) intPropertyManager.getPropertyValue(i)).intValue()); + } + } + + // precondition tests + ContractException contractException = assertThrows(ContractException.class, + () -> intPropertyManager.setPropertyValue(-1, 23)); + assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = IntPropertyManager.class, name = "removeId", args = { int.class }) + public void testRemoveId() { + Factory factory = TestPluginFactory.factory((c) -> { + /* + * Should have no effect on the value that is stored for the sake of efficiency. + */ + + // we will first test the manager with an initial value of 6 + int defaultValue = 6; + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class) + .setDefaultValue(defaultValue).build(); + + IntPropertyManager intPropertyManager = new IntPropertyManager(propertyDefinition, + this::getEmptyIndexIterator); + + // initially, the value should be the default value for the manager + assertEquals(defaultValue, ((Integer) intPropertyManager.getPropertyValue(5)).intValue()); + + // after setting the value we should be able to retrieve a new value + int newValue = 34534; + intPropertyManager.setPropertyValue(5, newValue); + assertEquals(newValue, ((Integer) intPropertyManager.getPropertyValue(5)).intValue()); + + // removing the id from the manager should have no effect, since we + // do + // not waste time setting the value back to the default + intPropertyManager.removeId(5); + + assertEquals(newValue, ((Integer) intPropertyManager.getPropertyValue(5)).intValue()); + + // we will next test the manager with an initial value of true + propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(defaultValue) + .build(); + + intPropertyManager = new IntPropertyManager(propertyDefinition, this::getEmptyIndexIterator); + + // initially, the value should be the default value for the manager + assertEquals(defaultValue, ((Integer) intPropertyManager.getPropertyValue(5)).intValue()); + + // after setting the value we should be able to retrieve the new + // value + intPropertyManager.setPropertyValue(5, newValue); + assertEquals(newValue, ((Integer) intPropertyManager.getPropertyValue(5)).intValue(), 0); + + // removing the id from the manager should have no effect, since we + // do + // not waste time setting the value back to the default + intPropertyManager.removeId(5); + + assertEquals(newValue, ((Integer) intPropertyManager.getPropertyValue(5)).intValue(), 0); + + // precondition tests + PropertyDefinition def = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(3).build(); + IntPropertyManager ipm = new IntPropertyManager(def, this::getEmptyIndexIterator); + + ContractException contractException = assertThrows(ContractException.class, () -> ipm.removeId(-1)); + assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestConstructor(target = IntPropertyManager.class, args = { PropertyDefinition.class, Supplier.class }) + public void testConstructor() { + Factory factory = TestPluginFactory.factory((c) -> { + + PropertyDefinition goodPropertyDefinition = PropertyDefinition.builder().setType(Integer.class) + .setDefaultValue(2).build(); + PropertyDefinition badPropertyDefinition = PropertyDefinition.builder().setType(Boolean.class) + .setDefaultValue(false).build(); + + // if the property definition is null + ContractException contractException = assertThrows(ContractException.class, + () -> new IntPropertyManager(null, this::getEmptyIndexIterator)); + assertEquals(PropertyError.NULL_PROPERTY_DEFINITION, contractException.getErrorType()); + + // if the property definition does not have a type of Double.class + contractException = assertThrows(ContractException.class, + () -> new IntPropertyManager(badPropertyDefinition, this::getEmptyIndexIterator)); + assertEquals(PropertyError.PROPERTY_DEFINITION_IMPROPER_TYPE, contractException.getErrorType()); + + IntPropertyManager doublePropertyManager = new IntPropertyManager(goodPropertyDefinition, + this::getEmptyIndexIterator); + assertNotNull(doublePropertyManager); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = IntPropertyManager.class, name = "incrementCapacity", args = { int.class }) + public void testIncrementCapacity() { + Factory factory = TestPluginFactory.factory((c) -> { + + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class) + .setDefaultValue(234).build(); + + IntPropertyManager intPropertyManager = new IntPropertyManager(propertyDefinition, + this::getEmptyIndexIterator); + + // precondition tests + ContractException contractException = assertThrows(ContractException.class, + () -> intPropertyManager.incrementCapacity(-1)); + assertEquals(PropertyError.NEGATIVE_CAPACITY_INCREMENT, contractException.getErrorType()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + + @Test + @UnitTestMethod(target = IntPropertyManager.class, name = "toString", args = {}) + public void testToString() { + Factory factory = TestPluginFactory.factory((c) -> { + + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class) + .setDefaultValue(0).build(); + + List list = new ArrayList<>(); + list.add(1); + list.add(2); + list.add(5); + list.add(6); + list.add(7); + + IntPropertyManager intPropertyManager = new IntPropertyManager(propertyDefinition, + () -> list.iterator()); + + intPropertyManager.setPropertyValue(5, 2); + intPropertyManager.setPropertyValue(7, 3); + intPropertyManager.setPropertyValue(1, 0); + intPropertyManager.setPropertyValue(8, 4); + + String actualValue = intPropertyManager.toString(); + String expectedValue = "IntPropertyManager [intValueContainer=IntValueContainer [subTypeArray=ByteArray [values=[1=0, 2=0, 5=2, 6=0, 7=3], defaultValue=0]], intValueType=INT]"; + + assertEquals(expectedValue, actualValue); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/AT_ObjectPropertyManager.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/AT_ObjectPropertyManager.java new file mode 100644 index 000000000..86ff242d1 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/AT_ObjectPropertyManager.java @@ -0,0 +1,265 @@ +package gov.hhs.aspr.ms.gcm.plugins.util.properties; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginFactory; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestPluginFactory.Factory; +import gov.hhs.aspr.ms.gcm.nucleus.testsupport.testplugin.TestSimulation; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; +import util.errors.ContractException; +import util.random.RandomGeneratorProvider; + +/** + * Common interface to all person property managers. A person property manager + * manages all the property values for people for a particular person property + * identifier. + * + * + */ + +public class AT_ObjectPropertyManager { + + + private Iterator getEmptyIndexIterator() { + return Collections.emptyIterator(); + } + + @Test + @UnitTestMethod(target = ObjectPropertyManager.class, name = "getPropertyValue", args = { int.class }) + public void testGetPropertyValue() { + Factory factory = TestPluginFactory.factory((c) -> { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3707927404057976793L); + + String defaultValue = "YELLOW"; + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(String.class).setDefaultValue(defaultValue).build(); + + ObjectPropertyManager objectPropertyManager = new ObjectPropertyManager(propertyDefinition, this::getEmptyIndexIterator); + + /* + * We will set the first 300 values multiple times at random + */ + Map expectedValues = new LinkedHashMap<>(); + + for (int i = 0; i < 1000; i++) { + int id = randomGenerator.nextInt(300); + String value = getRandomString(randomGenerator); + expectedValues.put(id, value); + objectPropertyManager.setPropertyValue(id, value); + } + + /* + * if the value was set above, then it should equal the last value + * place in the expected values, otherwise it will have the default + * value. + */ + for (int i = 0; i < 300; i++) { + if (expectedValues.containsKey(i)) { + assertEquals(expectedValues.get(i), objectPropertyManager.getPropertyValue(i)); + + } else { + assertEquals(defaultValue, (String) objectPropertyManager.getPropertyValue(i)); + + } + } + + // precondition tests + ContractException contractException = assertThrows(ContractException.class, () -> objectPropertyManager.getPropertyValue(-1)); + assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + private static String getRandomString(RandomGenerator randomGenerator) { + switch (randomGenerator.nextInt(3)) { + case 0: + return "RED"; + case 1: + return "YELLOW"; + default: + return "BLUE"; + } + } + + @Test + @UnitTestMethod(target = ObjectPropertyManager.class, name = "setPropertyValue", args = { int.class, Object.class }) + public void testSetPropertyValue() { + Factory factory = TestPluginFactory.factory((c) -> { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6268125375257441705L); + + String defaultValue = "YELLOW"; + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(String.class).setDefaultValue(defaultValue).build(); + + ObjectPropertyManager objectPropertyManager = new ObjectPropertyManager( propertyDefinition, this::getEmptyIndexIterator); + + /* + * We will set the first 300 values multiple times at random + */ + Map expectedValues = new LinkedHashMap<>(); + + for (int i = 0; i < 1000; i++) { + int id = randomGenerator.nextInt(300); + String value = getRandomString(randomGenerator); + expectedValues.put(id, value); + objectPropertyManager.setPropertyValue(id, value); + } + + /* + * if the value was set above, then it should equal the last value + * place in the expected values, otherwise it will have the default + * value. + */ + for (int i = 0; i < 300; i++) { + if (expectedValues.containsKey(i)) { + assertEquals(expectedValues.get(i), objectPropertyManager.getPropertyValue(i)); + + } else { + assertEquals(defaultValue, (String) objectPropertyManager.getPropertyValue(i)); + + } + } + + // precondition tests + ContractException contractException = assertThrows(ContractException.class, () -> objectPropertyManager.setPropertyValue(-1, "value")); + assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = ObjectPropertyManager.class, name = "removeId", args = { int.class }) + public void testRemoveId() { + + Factory factory = TestPluginFactory.factory((c) -> { + /* + * Should have no effect on the value that is stored for the sake of + * efficiency. + */ + + // we will first test the manager with an initial value of false + String defaultValue = "RED"; + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(String.class).setDefaultValue(defaultValue).build(); + + ObjectPropertyManager objectPropertyManager = new ObjectPropertyManager(propertyDefinition, this::getEmptyIndexIterator); + + // initially, the value should be the default value for the manager + assertEquals(defaultValue, (String) objectPropertyManager.getPropertyValue(5)); + + // after setting the value we should be able to retrieve a new value + String newValue = "BLUE"; + objectPropertyManager.setPropertyValue(5, newValue); + assertEquals(newValue, (String) objectPropertyManager.getPropertyValue(5)); + + // removing the id from the manager should return the value to the + // deafault + objectPropertyManager.removeId(5); + + assertEquals(defaultValue, (String) objectPropertyManager.getPropertyValue(5)); + + // we will next test the manager with an initial value of true + propertyDefinition = PropertyDefinition.builder().setType(String.class).setDefaultValue(defaultValue).build(); + + objectPropertyManager = new ObjectPropertyManager(propertyDefinition, this::getEmptyIndexIterator); + + // initially, the value should be the default value for the manager + assertEquals(defaultValue, (String) objectPropertyManager.getPropertyValue(5)); + + // after setting the value we should be able to retrieve the new + // value + objectPropertyManager.setPropertyValue(5, newValue); + assertEquals(newValue, (String) objectPropertyManager.getPropertyValue(5)); + + // removing the id from the manager should return the value to the + // default + objectPropertyManager.removeId(5); + + assertEquals(defaultValue, (String) objectPropertyManager.getPropertyValue(5)); + + // precondition tests + PropertyDefinition def = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(true).build(); + ObjectPropertyManager opm = new ObjectPropertyManager( def, this::getEmptyIndexIterator); + + ContractException contractException = assertThrows(ContractException.class, () -> opm.removeId(-1)); + assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestConstructor(target = ObjectPropertyManager.class, args = { PropertyDefinition.class, Supplier.class }) + public void testConstructor() { + Factory factory = TestPluginFactory.factory((c) -> { + + PropertyDefinition goodPropertyDefinition = PropertyDefinition.builder().setType(Object.class).setDefaultValue("BLUE").build(); + + // if the property definition is null + ContractException contractException = assertThrows(ContractException.class, () -> new ObjectPropertyManager(null, this::getEmptyIndexIterator)); + assertEquals(PropertyError.NULL_PROPERTY_DEFINITION, contractException.getErrorType()); + + + ObjectPropertyManager objectPropertyManager = new ObjectPropertyManager(goodPropertyDefinition, this::getEmptyIndexIterator); + assertNotNull(objectPropertyManager); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + + @Test + @UnitTestMethod(target = ObjectPropertyManager.class, name = "incrementCapacity", args = { int.class }) + public void testIncrementCapacity() { + Factory factory = TestPluginFactory.factory((c) -> { + + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(234).build(); + + ObjectPropertyManager objectPropertyManager = new ObjectPropertyManager(propertyDefinition, this::getEmptyIndexIterator); + + // precondition tests + ContractException contractException = assertThrows(ContractException.class, () -> objectPropertyManager.incrementCapacity(-1)); + assertEquals(PropertyError.NEGATIVE_CAPACITY_INCREMENT, contractException.getErrorType()); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + } + @Test + @UnitTestMethod(target = ObjectPropertyManager.class, name = "toString", args = {}) + public void testToString() { + Factory factory = TestPluginFactory.factory((c) -> { + + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Object.class) + .setDefaultValue("C").build(); + + List list = new ArrayList<>(); + list.add(1); + list.add(2); + list.add(5); + list.add(6); + list.add(7); + + ObjectPropertyManager objectPropertyManager = new ObjectPropertyManager(propertyDefinition, + () -> list.iterator()); + + objectPropertyManager.setPropertyValue(5, 2); + objectPropertyManager.setPropertyValue(7, "A"); + objectPropertyManager.setPropertyValue(1, 3.4); + objectPropertyManager.setPropertyValue(8, "B"); + + String actualValue = objectPropertyManager.toString(); + String expectedValue = "ObjectPropertyManager [objectValueContainer=ObjectValueContainer [elements=[1=3.4, 2=C, 5=2, 6=C, 7=A], defaultValue=C], defaultValue=C]"; + + assertEquals(expectedValue, actualValue); + }); + TestSimulation.builder().addPlugins(factory.getPlugins()).build().execute(); + + } +} diff --git a/gcm3/src/test/java/plugins/util/properties/AT_PropertyDefinition.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/AT_PropertyDefinition.java similarity index 81% rename from gcm3/src/test/java/plugins/util/properties/AT_PropertyDefinition.java rename to gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/AT_PropertyDefinition.java index 03072eb79..60325c68d 100644 --- a/gcm3/src/test/java/plugins/util/properties/AT_PropertyDefinition.java +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/AT_PropertyDefinition.java @@ -1,4 +1,4 @@ -package plugins.util.properties; +package gov.hhs.aspr.ms.gcm.plugins.util.properties; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -12,13 +12,11 @@ import org.apache.commons.math3.random.RandomGenerator; import org.junit.jupiter.api.Test; -import plugins.util.properties.PropertyDefinition.Builder; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition.Builder; +import util.annotations.UnitTestMethod; import util.errors.ContractException; import util.random.RandomGeneratorProvider; -@UnitTest(target = PropertyDefinition.class) public class AT_PropertyDefinition { private static enum BooleanType { TRUE(Boolean.TRUE), FALSE(Boolean.FALSE); @@ -67,13 +65,11 @@ private PropertyDefinition generateRandomPropertyDefinition(RandomGenerator rand } boolean propertyValuesAreMutability = randomGenerator.nextBoolean(); - TimeTrackingPolicy timeTrackingPolicy = TimeTrackingPolicy.values()[randomGenerator.nextInt(TimeTrackingPolicy.values().length)]; return PropertyDefinition .builder()// .setType(type)// .setDefaultValue(defaultValue)// - .setPropertyValueMutability(propertyValuesAreMutability)// - .setTimeTrackingPolicy(timeTrackingPolicy)// + .setPropertyValueMutability(propertyValuesAreMutability)// .build();// } @@ -92,8 +88,7 @@ private PropertyDefinition generateNonMatchingRandomPropertyDefinition(PropertyD if (propertyDefinition.getDefaultValue().isPresent() && result.getDefaultValue().isPresent()) { different |= !result.getDefaultValue().get().equals(propertyDefinition.getDefaultValue().get()); } - - different |= !result.getTimeTrackingPolicy().equals(propertyDefinition.getTimeTrackingPolicy()); + different |= !result.getType().equals(propertyDefinition.getType()); if (different) { return result; @@ -112,13 +107,12 @@ private PropertyDefinition generateMatchingPropertyDefinition(PropertyDefinition } return builder .setType(propertyDefinition.getType())// - .setPropertyValueMutability(propertyDefinition.propertyValuesAreMutable())// - .setTimeTrackingPolicy(propertyDefinition.getTimeTrackingPolicy())// + .setPropertyValueMutability(propertyDefinition.propertyValuesAreMutable())// .build();// } @Test - @UnitTestMethod(name = "toString", args = {}) + @UnitTestMethod(target = PropertyDefinition.class, name = "toString", args = {}) public void testToString() { RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2790643065916150473L); @@ -135,7 +129,7 @@ public void testToString() { } @Test - @UnitTestMethod(name = "getDefaultValue", args = {}) + @UnitTestMethod(target = PropertyDefinition.class, name = "getDefaultValue", args = {}) public void testGetDefaultValue() { RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5344086660090478893L); @@ -187,27 +181,9 @@ public void testGetDefaultValue() { } - @Test - @UnitTestMethod(name = "getTimeTrackingPolicy", args = {}) - public void testGetTimeTrackingPolicy() { - - /* - * Show that the TimeTrackingPolicy value used to form the property - * definition is returned by the property definition - */ - for (TimeTrackingPolicy timeTrackingPolicy : TimeTrackingPolicy.values()) { - PropertyDefinition propertyDefinition = PropertyDefinition .builder()// - .setType(String.class)// - .setDefaultValue("defaultValue")// - .setTimeTrackingPolicy(timeTrackingPolicy)// - .build();// - assertEquals(timeTrackingPolicy, propertyDefinition.getTimeTrackingPolicy()); - } - - } @Test - @UnitTestMethod(name = "getType", args = {}) + @UnitTestMethod(target = PropertyDefinition.class, name = "getType", args = {}) public void testGetType() { /* @@ -248,7 +224,7 @@ public void testGetType() { } @Test - @UnitTestMethod(name = "propertyValuesAreMutable", args = {}) + @UnitTestMethod(target = PropertyDefinition.class, name = "propertyValuesAreMutable", args = {}) public void testPropertyValuesAreMutable() { /* @@ -297,48 +273,42 @@ public void testPropertyValuesAreMutable() { } @Test - @UnitTestMethod(name = "equals", args = { Object.class }) + @UnitTestMethod(target = PropertyDefinition.class, name = "equals", args = { Object.class }) public void testEquals() { PropertyDefinition propertyDefinition1 = PropertyDefinition .builder()// .setType(String.class)// .setDefaultValue("asdf")// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.DO_NOT_TRACK_TIME)// + .setPropertyValueMutability(true)// .build();// PropertyDefinition propertyDefinition2 = PropertyDefinition .builder()// .setType(String.class)// .setDefaultValue("asdf")// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.DO_NOT_TRACK_TIME)// + .setPropertyValueMutability(true)// .build();// PropertyDefinition propertyDefinition3 = PropertyDefinition .builder()// .setType(String.class)// .setDefaultValue("xxx")// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.DO_NOT_TRACK_TIME)// + .setPropertyValueMutability(true)// .build();// PropertyDefinition propertyDefinition4 = PropertyDefinition .builder()// .setType(String.class)// .setDefaultValue("asdf")// - .setPropertyValueMutability(false)// - .setTimeTrackingPolicy(TimeTrackingPolicy.DO_NOT_TRACK_TIME)// + .setPropertyValueMutability(false)// .build();// PropertyDefinition propertyDefinition5 = PropertyDefinition .builder()// .setType(String.class)// .setDefaultValue("asdf")// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME)// + .setPropertyValueMutability(true)// .build();// PropertyDefinition propertyDefinition6 = PropertyDefinition .builder()// .setType(Integer.class)// .setDefaultValue(45)// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.DO_NOT_TRACK_TIME)// + .setPropertyValueMutability(true)// .build();// assertEquals(propertyDefinition1, propertyDefinition1); @@ -347,7 +317,7 @@ public void testEquals() { assertNotEquals(propertyDefinition1, propertyDefinition3); assertNotEquals(propertyDefinition1, propertyDefinition4); - assertNotEquals(propertyDefinition1, propertyDefinition5); + assertEquals(propertyDefinition1, propertyDefinition5); assertNotEquals(propertyDefinition1, propertyDefinition6); RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3951851825163960855L); @@ -401,27 +371,25 @@ public void testEquals() { } @Test - @UnitTestMethod(name = "hashCode", args = {}) + @UnitTestMethod(target = PropertyDefinition.class, name = "hashCode", args = {}) public void testHashCode() { PropertyDefinition propertyDefinition1 = PropertyDefinition .builder()// .setType(String.class)// .setDefaultValue("asdf")// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.DO_NOT_TRACK_TIME)// + .setPropertyValueMutability(true)// .build();// PropertyDefinition propertyDefinition2 = PropertyDefinition .builder()// .setType(String.class)// .setDefaultValue("asdf")// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.DO_NOT_TRACK_TIME)// + .setPropertyValueMutability(true)// .build();// assertEquals(propertyDefinition1.hashCode(), propertyDefinition2.hashCode()); } @Test - @UnitTestMethod(name = "builder", args = {}) + @UnitTestMethod(target = PropertyDefinition.class, name = "builder", args = {}) public void testBuilder() { assertNotNull(PropertyDefinition.builder()); } @@ -484,12 +452,12 @@ public void testSetDefaultValue() { // show that not setting the default yields an empty optional PropertyDefinition propertyDefinition = PropertyDefinition .builder()// - .setType(Integer.class)// + .setType(Integer.class)// .build();// Optional optional = propertyDefinition.getDefaultValue(); assertFalse(optional.isPresent()); - + } @Test @@ -522,37 +490,6 @@ public void testSetPropertyValueMutability() { } - @Test - @UnitTestMethod(target = PropertyDefinition.Builder.class, name = "setTimeTrackingPolicy", args = { TimeTrackingPolicy.class }) - public void testSetTimeTrackingPolicy() { - - PropertyDefinition propertyDefinition = PropertyDefinition .builder()// - .setType(Integer.class)// - .setDefaultValue(10)// - .setTimeTrackingPolicy(TimeTrackingPolicy.DO_NOT_TRACK_TIME)// - .build();// - - assertEquals(TimeTrackingPolicy.DO_NOT_TRACK_TIME, propertyDefinition.getTimeTrackingPolicy()); - - propertyDefinition = PropertyDefinition .builder()// - .setType(Integer.class)// - .setDefaultValue(10)// - .setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME)// - .build();// - - assertEquals(TimeTrackingPolicy.TRACK_TIME, propertyDefinition.getTimeTrackingPolicy()); - - // show that the default is DO_NOT_TRACK_TIME - propertyDefinition = PropertyDefinition .builder()// - .setType(Integer.class)// - .setDefaultValue(10)// - // .setTimeTrackingPolicy(TimeTrackingPolicy.DO_NOT_TRACK_TIME)// - .build();// - - assertEquals(TimeTrackingPolicy.DO_NOT_TRACK_TIME, propertyDefinition.getTimeTrackingPolicy()); - - } - @Test @UnitTestMethod(target = PropertyDefinition.Builder.class, name = "setType", args = { Class.class }) public void testSetType() { diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/AT_PropertyError.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/AT_PropertyError.java new file mode 100644 index 000000000..f428b3bd9 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/AT_PropertyError.java @@ -0,0 +1,28 @@ +package gov.hhs.aspr.ms.gcm.plugins.util.properties; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestMethod; + +public class AT_PropertyError { + + @Test + @UnitTestMethod(target = PropertyError.class, name = "getDescription", args = {}) + public void test() { + // show that each description is a unique, non-null and non-empty string + Set descriptions = new LinkedHashSet<>(); + for (PropertyError propertyError : PropertyError.values()) { + String description = propertyError.getDescription(); + assertNotNull(description, "null description for " + propertyError); + assertTrue(description.length() > 0, "empty string for " + propertyError); + boolean unique = descriptions.add(description); + assertTrue(unique, "description for " + propertyError + " is not unique"); + } + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/AT_BooleanContainer.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/AT_BooleanContainer.java new file mode 100644 index 000000000..77c7d6f98 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/AT_BooleanContainer.java @@ -0,0 +1,113 @@ +package gov.hhs.aspr.ms.gcm.plugins.util.properties.arraycontainers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Random; +import java.util.function.Supplier; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTag; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_BooleanContainer { + + private Iterator getEmptyIndexIterator() { + return Collections.emptyIterator(); + } + + @Test + @UnitTestConstructor(target = BooleanContainer.class, args = { boolean.class, Supplier.class }) + public void testBooleanContainer() { + BooleanContainer booleanContainer = new BooleanContainer(true, this::getEmptyIndexIterator); + + for (int i = 0; i < 10; i++) { + assertTrue(booleanContainer.get(i)); + } + + booleanContainer = new BooleanContainer(false, this::getEmptyIndexIterator); + + for (int i = 0; i < 10; i++) { + assertFalse(booleanContainer.get(i)); + } + + } + + @Test + @UnitTestMethod(target = BooleanContainer.class, name = "get", args = { int.class }) + public void testGet() { + Random random = new Random(53463457457456456L); + int n = 1000; + boolean[] array = new boolean[n]; + for (int i = 0; i < n; i++) { + array[i] = random.nextBoolean(); + } + + BooleanContainer booleanContainer = new BooleanContainer(true, this::getEmptyIndexIterator); + + for (int i = 0; i < n; i++) { + booleanContainer.set(i, array[i]); + } + + for (int i = 0; i < n; i++) { + assertEquals(array[i], booleanContainer.get(i)); + } + + booleanContainer = new BooleanContainer(true, this::getEmptyIndexIterator); + + for (int i = 0; i < n; i++) { + booleanContainer.set(i, array[i]); + } + + for (int i = 0; i < n; i++) { + assertEquals(array[i], booleanContainer.get(i)); + } + + } + + /** + * Test {@link BooleanContainer#set(int, boolean)} + */ + @Test + @UnitTestMethod(target = BooleanContainer.class, name = "set", args = { int.class, boolean.class }) + public void testSet() { + // proxy via testGet() + } + + @Test + @UnitTestMethod(target = BooleanContainer.class, name = "expandCapacity", args = { int.class }, tags = { + UnitTag.INCOMPLETE }) + public void testExpandCapacity() { + // requires a manual performance test + } + + @Test + @UnitTestMethod(target = BooleanContainer.class, name = "toString", args = {}) + public void testToString() { + + List list = new ArrayList<>(); + list.add(1); + list.add(2); + list.add(5); + list.add(6); + list.add(7); + + BooleanContainer booleanContainer = new BooleanContainer(false, () -> list.iterator()); + booleanContainer.set(5, true); + booleanContainer.set(7, true); + booleanContainer.set(1, true); + booleanContainer.set(8, true); + String actualValue = booleanContainer.toString(); + + String expectedValue = "BooleanContainer [defaultValue=false, bitSet=[1=true, 2=false, 5=true, 6=false, 7=true]]"; + assertEquals(expectedValue, actualValue); + + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/AT_DoubleValueContainer.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/AT_DoubleValueContainer.java new file mode 100644 index 000000000..d37471812 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/AT_DoubleValueContainer.java @@ -0,0 +1,187 @@ +package gov.hhs.aspr.ms.gcm.plugins.util.properties.arraycontainers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.function.Supplier; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_DoubleValueContainer { + + /** + * Tests {@link DoubleValueContainer#DoubleValueContainer(double)} + */ + @Test + @UnitTestConstructor(target = DoubleValueContainer.class, args = { double.class, Supplier.class }) + public void testConstructor_Double() { + DoubleValueContainer doubleValueContainer = new DoubleValueContainer(0,this::getEmptyIndexIterator); + assertNotNull(doubleValueContainer); + } + + + private Iterator getEmptyIndexIterator() { + return Collections.emptyIterator(); + } + + + + /** + * Tests {@link DoubleValueContainer#getCapacity()} + */ + @Test + @UnitTestMethod(target = DoubleValueContainer.class, name = "getCapacity", args = {}) + public void testGetCapacity() { + DoubleValueContainer doubleValueContainer = new DoubleValueContainer(0,this::getEmptyIndexIterator); + + assertTrue(doubleValueContainer.getCapacity() >= 0); + + doubleValueContainer.setValue(1, 123.4); + assertTrue(doubleValueContainer.getCapacity() >= 1); + + doubleValueContainer.setValue(34, 36.4); + assertTrue(doubleValueContainer.getCapacity() >= 34); + + doubleValueContainer.setValue(10, 15.4); + assertTrue(doubleValueContainer.getCapacity() >= 20); + + doubleValueContainer.setValue(137, 25.26); + assertTrue(doubleValueContainer.getCapacity() >= 137); + + doubleValueContainer.setValue(1000, 123.6345); + assertTrue(doubleValueContainer.getCapacity() >= 1000); + } + + /** + * Tests {@link DoubleValueContainer#getDefaultValue()} + */ + @Test + @UnitTestMethod(target = DoubleValueContainer.class, name = "getDefaultValue", args = {}) + public void testGetDefaultValue() { + double defaultValue = 0; + DoubleValueContainer doubleValueContainer = new DoubleValueContainer(defaultValue,this::getEmptyIndexIterator); + assertEquals(defaultValue, doubleValueContainer.getDefaultValue(), 0); + + defaultValue = -10; + doubleValueContainer = new DoubleValueContainer(defaultValue,this::getEmptyIndexIterator); + assertEquals(defaultValue, doubleValueContainer.getDefaultValue(), 0); + + defaultValue = 10; + doubleValueContainer = new DoubleValueContainer(defaultValue,this::getEmptyIndexIterator); + assertEquals(defaultValue, doubleValueContainer.getDefaultValue(), 0); + + } + + /** + * Tests {@link DoubleValueContainer#getValue(int)} + */ + @Test + @UnitTestMethod(target = DoubleValueContainer.class, name = "getValue", args = { int.class }) + public void testGetValue() { + double defaultValue = -345.34; + DoubleValueContainer doubleValueContainer = new DoubleValueContainer(defaultValue,this::getEmptyIndexIterator); + int highIndex = 1000; + double delta = 2.3452346; + + double[] doubles = new double[highIndex]; + for (int i = 0; i < doubles.length; i++) { + doubles[i] = delta + i; + } + for (int i = 0; i < doubles.length; i++) { + doubleValueContainer.setValue(i, doubles[i]); + } + + for (int i = 0; i < doubles.length; i++) { + assertEquals(doubles[i], doubleValueContainer.getValue(i), 0); + } + + // show that the default value is returned for indices that have not yet + // had value assignments + for (int i = 0; i < 5; i++) { + assertEquals(doubleValueContainer.getValue(i + highIndex), defaultValue, 0); + } + + // pre-condition tests + + // if index < 0 + assertThrows(RuntimeException.class, () -> doubleValueContainer.getValue(-1)); + + } + + /** + * Tests {@link DoubleValueContainer#setCapacity(int)} + */ + @Test + @UnitTestMethod(target = DoubleValueContainer.class, name = "setCapacity", args = { int.class }) + public void testSetCapacity() { + DoubleValueContainer doubleValueContainer = new DoubleValueContainer(0,this::getEmptyIndexIterator); + + int expectedCapacity = 5; + doubleValueContainer.setCapacity(expectedCapacity); + assertTrue(doubleValueContainer.getCapacity() >= expectedCapacity); + + expectedCapacity = 15; + doubleValueContainer.setCapacity(expectedCapacity); + assertTrue(doubleValueContainer.getCapacity() >= expectedCapacity); + + expectedCapacity = 50; + doubleValueContainer.setCapacity(expectedCapacity); + assertTrue(doubleValueContainer.getCapacity() >= expectedCapacity); + + expectedCapacity = 1000; + doubleValueContainer.setCapacity(expectedCapacity); + assertTrue(doubleValueContainer.getCapacity() >= expectedCapacity); + } + + /** + * Tests {@link DoubleValueContainer#setValue(int, double)} + */ + @Test + @UnitTestMethod(target = DoubleValueContainer.class, name = "setValue", args = { int.class, double.class }) + public void testSetValue() { + DoubleValueContainer doubleValueContainer = new DoubleValueContainer(0,this::getEmptyIndexIterator); + + // long value + double value = 12123.234; + doubleValueContainer.setValue(0, value); + assertEquals(value, doubleValueContainer.getValue(0), 0); + + // pre-condition tests + assertThrows(RuntimeException.class, () -> doubleValueContainer.setValue(-1, 234.63)); + + } + + @Test + @UnitTestMethod(target = DoubleValueContainer.class, name = "toString", args = {}) + public void testToString() { + + List list = new ArrayList<>(); + list.add(1); + list.add(2); + list.add(5); + list.add(6); + list.add(7); + + DoubleValueContainer doubleValueContainer = new DoubleValueContainer(0.0, () -> list.iterator()); + doubleValueContainer.setValue(5, 2.5); + doubleValueContainer.setValue(7, 3.5); + doubleValueContainer.setValue(1, 0.5); + doubleValueContainer.setValue(8, 4); + String actualValue = doubleValueContainer.toString(); + + + String expectedValue = "DoubleValueContainer [values=[1=0.5, 2=0.0, 5=2.5, 6=0.0, 7=3.5], defaultValue=0.0]"; + assertEquals(expectedValue, actualValue); + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/AT_EnumContainer.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/AT_EnumContainer.java new file mode 100644 index 000000000..48b8c4177 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/AT_EnumContainer.java @@ -0,0 +1,171 @@ +package gov.hhs.aspr.ms.gcm.plugins.util.properties.arraycontainers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.function.Supplier; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTag; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + + +public class AT_EnumContainer { + + private Iterator getEmptyIndexIterator() { + return Collections.emptyIterator(); + } + + public enum Animal { + CAT, PIG, SHEEP, DOG, HORSE; + } + + /** + * Tests {@link EnumContainer#EnumContainer(Class, Object)} + */ + @Test + @UnitTestConstructor(target = EnumContainer.class, args = { Class.class, Object.class, Supplier.class}) + public void testConstructor_ClassObject() { + assertNotNull(new EnumContainer(Animal.class, Animal.DOG,this::getEmptyIndexIterator)); + + // Test preconditions + + // if the class is null + assertThrows(IllegalArgumentException.class, () -> new EnumContainer(null, Animal.DOG,this::getEmptyIndexIterator)); + + // if the class is not an enumeration + assertThrows(IllegalArgumentException.class, () -> new EnumContainer(Integer.class, Animal.DOG,this::getEmptyIndexIterator)); + + // if the default is not a member of the enum + assertThrows(IllegalArgumentException.class, () -> new EnumContainer(Animal.class, 234,this::getEmptyIndexIterator)); + + } + + + + /** + * Tests {@link EnumContainer#getValue(int)} + */ + @Test + @UnitTestMethod(target = EnumContainer.class, name = "getValue", args = { int.class }) + public void testGetValue() { + EnumContainer enumContainer = new EnumContainer(Animal.class, Animal.DOG,this::getEmptyIndexIterator); + enumContainer.setValue(3, Animal.CAT); + enumContainer.setValue(5, Animal.CAT); + enumContainer.setValue(2, Animal.SHEEP); + enumContainer.setValue(0, Animal.HORSE); + enumContainer.setValue(2, Animal.PIG); + + assertEquals(Animal.HORSE, enumContainer.getValue(0)); + assertEquals(Animal.DOG, enumContainer.getValue(1)); + assertEquals(Animal.PIG, enumContainer.getValue(2)); + assertEquals(Animal.CAT, enumContainer.getValue(3)); + assertEquals(Animal.DOG, enumContainer.getValue(4)); + assertEquals(Animal.CAT, enumContainer.getValue(5)); + assertEquals(Animal.DOG, enumContainer.getValue(6)); + + enumContainer = new EnumContainer(Animal.class, Animal.DOG, this::getEmptyIndexIterator); + enumContainer.setValue(3, Animal.CAT); + enumContainer.setValue(5, Animal.CAT); + enumContainer.setValue(2, Animal.SHEEP); + enumContainer.setValue(0, Animal.HORSE); + enumContainer.setValue(2, Animal.PIG); + + assertEquals(Animal.HORSE, enumContainer.getValue(0)); + assertEquals(Animal.DOG, enumContainer.getValue(1)); + assertEquals(Animal.PIG, enumContainer.getValue(2)); + assertEquals(Animal.CAT, enumContainer.getValue(3)); + assertEquals(Animal.DOG, enumContainer.getValue(4)); + assertEquals(Animal.CAT, enumContainer.getValue(5)); + assertEquals(Animal.DOG, enumContainer.getValue(6)); + + } + + /** + * Test {@link EnumContainer#setValue(int, Object)} + */ + @Test + @UnitTestMethod(target = EnumContainer.class, name = "setValue", args = { int.class, Object.class }) + public void testSetValue() { + + Map animalMap = new LinkedHashMap<>(); + + EnumContainer enumContainer = new EnumContainer(Animal.class, Animal.DOG,this::getEmptyIndexIterator); + Random random = new Random(4545456567994423L); + for (int i = 0; i < 1000; i++) { + int index = random.nextInt(6); + int ord = random.nextInt(Animal.values().length); + Animal animal = Animal.values()[ord]; + animalMap.put(index, animal); + enumContainer.setValue(index, animal); + assertEquals(animal, enumContainer.getValue(index)); + } + + enumContainer = new EnumContainer(Animal.class, Animal.DOG, this::getEmptyIndexIterator); + for (int i = 0; i < 1000; i++) { + int index = random.nextInt(6); + int ord = random.nextInt(Animal.values().length); + Animal animal = Animal.values()[ord]; + animalMap.put(index, animal); + enumContainer.setValue(index, animal); + assertEquals(animal, enumContainer.getValue(index)); + } + + // Test pre-conditions + EnumContainer preConditionEnumContainer = enumContainer; + // if the index is negative + assertThrows(IllegalArgumentException.class, () -> preConditionEnumContainer.setValue(-1, Animal.HORSE)); + + // if the value is null + assertThrows(IllegalArgumentException.class, () -> preConditionEnumContainer.setValue(1, null)); + + // if the value is not a member of the enumeration + assertThrows(IllegalArgumentException.class, () -> preConditionEnumContainer.setValue(1, 45)); + } + + @Test + @UnitTestMethod(target = EnumContainer.class, name = "getCapacity", args = {}, tags = { UnitTag.INCOMPLETE }) + public void testGetCapacity() { + // requires a manual performance test + } + + @Test + @UnitTestMethod(target = EnumContainer.class, name = "setCapacity", args = { int.class }, tags = { UnitTag.INCOMPLETE }) + public void testSetCapacity() { + // requires a manual performance test + } + + + @Test + @UnitTestMethod(target = EnumContainer.class, name = "toString", args = {}) + public void testToString() { + + List list = new ArrayList<>(); + list.add(1); + list.add(2); + list.add(5); + list.add(6); + list.add(7); + + EnumContainer enumContainer = new EnumContainer(Animal.class,Animal.HORSE, () -> list.iterator()); + enumContainer.setValue(5, Animal.DOG); + enumContainer.setValue(7, Animal.CAT); + enumContainer.setValue(1, Animal.PIG); + enumContainer.setValue(8, Animal.SHEEP); + String actualValue = enumContainer.toString(); + + String expectedValue = "EnumContainer [values=[1=PIG, 2=HORSE, 5=DOG, 6=HORSE, 7=CAT], enumClass=class gov.hhs.aspr.ms.gcm.plugins.util.properties.arraycontainers.AT_EnumContainer$Animal]"; + assertEquals(expectedValue, actualValue); + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/AT_FloatValueContainer.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/AT_FloatValueContainer.java new file mode 100644 index 000000000..b0141a580 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/AT_FloatValueContainer.java @@ -0,0 +1,183 @@ +package gov.hhs.aspr.ms.gcm.plugins.util.properties.arraycontainers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.function.Supplier; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_FloatValueContainer { + + private Iterator getEmptyIndexIterator() { + return Collections.emptyIterator(); + } + + /** + * Tests {@link FloatValueContainer#FloatValueContainer(float)} + */ + @Test + @UnitTestConstructor(target = FloatValueContainer.class, args = { float.class, Supplier.class }) + public void testConstructor_Float() { + FloatValueContainer floatValueContainer = new FloatValueContainer(0,this::getEmptyIndexIterator); + assertNotNull(floatValueContainer); + } + + /** + * Tests {@link FloatValueContainer#getCapacity()} + */ + @Test + @UnitTestMethod(target = FloatValueContainer.class, name = "getCapacity", args = {}) + public void testGetCapacity() { + FloatValueContainer floatValueContainer = new FloatValueContainer(0,this::getEmptyIndexIterator); + + assertTrue(floatValueContainer.getCapacity() >= 0); + + floatValueContainer.setValue(1, 123.4f); + assertTrue(floatValueContainer.getCapacity() >= 1); + + floatValueContainer.setValue(34, 36.4f); + assertTrue(floatValueContainer.getCapacity() >= 34); + + floatValueContainer.setValue(10, 15.4f); + assertTrue(floatValueContainer.getCapacity() >= 10); + + floatValueContainer.setValue(137, 25.26f); + assertTrue(floatValueContainer.getCapacity() >= 137); + + floatValueContainer.setValue(1000, 123.6345f); + assertTrue(floatValueContainer.getCapacity() >= 1000); + } + + /** + * Tests {@link FloatValueContainer#getDefaultValue()} + */ + @Test + @UnitTestMethod(target = FloatValueContainer.class, name = "getDefaultValue", args = {}) + public void testGetDefaultValue() { + float defaultValue = 0; + FloatValueContainer floatValueContainer = new FloatValueContainer(defaultValue,this::getEmptyIndexIterator); + assertEquals(defaultValue, floatValueContainer.getDefaultValue(), 0); + + defaultValue = -10; + floatValueContainer = new FloatValueContainer(defaultValue,this::getEmptyIndexIterator); + assertEquals(defaultValue, floatValueContainer.getDefaultValue(), 0); + + defaultValue = 10; + floatValueContainer = new FloatValueContainer(defaultValue,this::getEmptyIndexIterator); + assertEquals(defaultValue, floatValueContainer.getDefaultValue(), 0); + + } + + /** + * Test {@link FloatValueContainer#getValue(int)} + */ + @Test + @UnitTestMethod(target = FloatValueContainer.class, name = "getValue", args = { int.class }) + public void testGetValue() { + float defaultValue = -345.34f; + FloatValueContainer floatValueContainer = new FloatValueContainer(defaultValue,this::getEmptyIndexIterator); + int highIndex = 1000; + float delta = 2.3452346f; + + float[] floats = new float[highIndex]; + for (int i = 0; i < floats.length; i++) { + floats[i] = delta + i; + } + for (int i = 0; i < floats.length; i++) { + floatValueContainer.setValue(i, floats[i]); + } + + for (int i = 0; i < floats.length; i++) { + assertEquals(floats[i], floatValueContainer.getValue(i), 0); + } + + // show that the default value is returned for indices that have not yet + // had value assignments + for (int i = 0; i < 5; i++) { + assertEquals(floatValueContainer.getValue(i + highIndex), defaultValue, 0); + } + + // pre-condition tests + + // if index < 0 + assertThrows(RuntimeException.class, () -> floatValueContainer.getValue(-1)); + + } + + /** + * Tests {@link FloatValueContainer#setCapacity(int)} + */ + @Test + @UnitTestMethod(target = FloatValueContainer.class, name = "setCapacity", args = { int.class }) + public void testSetCapacity() { + FloatValueContainer floatValueContainer = new FloatValueContainer(0,this::getEmptyIndexIterator); + + int expectedCapacity = 5; + floatValueContainer.setCapacity(expectedCapacity); + assertTrue(floatValueContainer.getCapacity() >= expectedCapacity); + + expectedCapacity = 15; + floatValueContainer.setCapacity(expectedCapacity); + assertTrue(floatValueContainer.getCapacity() >= expectedCapacity); + + expectedCapacity = 50; + floatValueContainer.setCapacity(expectedCapacity); + assertTrue(floatValueContainer.getCapacity() >= expectedCapacity); + + expectedCapacity = 1000; + floatValueContainer.setCapacity(expectedCapacity); + assertTrue(floatValueContainer.getCapacity() >= expectedCapacity); + } + + /** + * Test {@link FloatValueContainer#setValue(int, float)} + */ + @Test + @UnitTestMethod(target = FloatValueContainer.class, name = "setValue", args = { int.class, float.class }) + public void testSetValue() { + FloatValueContainer floatValueContainer = new FloatValueContainer(0,this::getEmptyIndexIterator); + + // long value + float value = 12123.234f; + floatValueContainer.setValue(0, value); + assertEquals(value, floatValueContainer.getValue(0), 0); + + // pre-condition tests + assertThrows(RuntimeException.class, () -> floatValueContainer.setValue(-1, 234.63f)); + + } + + @Test + @UnitTestMethod(target = FloatValueContainer.class, name = "toString", args = {}) + public void testToString() { + + List list = new ArrayList<>(); + list.add(1); + list.add(2); + list.add(5); + list.add(6); + list.add(7); + + FloatValueContainer floatValueContainer = new FloatValueContainer(0.0F, () -> list.iterator()); + floatValueContainer.setValue(5, 2.5F); + floatValueContainer.setValue(7, 3.5F); + floatValueContainer.setValue(1, 0.5F); + floatValueContainer.setValue(8, 4.0F); + String actualValue = floatValueContainer.toString(); + + String expectedValue = "FloatValueContainer [values=[1=0.5, 2=0.0, 5=2.5, 6=0.0, 7=3.5], defaultValue=0.0]"; + assertEquals(expectedValue, actualValue); + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/AT_IntValueContainer.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/AT_IntValueContainer.java new file mode 100644 index 000000000..d24138383 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/AT_IntValueContainer.java @@ -0,0 +1,649 @@ +package gov.hhs.aspr.ms.gcm.plugins.util.properties.arraycontainers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.function.Supplier; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.gcm.plugins.util.properties.arraycontainers.IntValueContainer.IntValueType; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_IntValueContainer { + + private Iterator getEmptyIndexIterator() { + return Collections.emptyIterator(); + } + + + /** + * Test for {@link IntValueContainer#getValueAsByte(int)} + */ + @Test + @UnitTestMethod(target = IntValueContainer.class, name = "getValueAsByte", args = { int.class }) + public void testGetValueAsByte() { + long defaultValue = 123; + IntValueContainer intValueContainer = new IntValueContainer(defaultValue,this::getEmptyIndexIterator); + int highIndex = 1000; + + byte[] bytes = new byte[highIndex]; + for (int i = 0; i < bytes.length; i++) { + byte b = (byte) (i % 256 - 128); + bytes[i] = b; + } + for (int i = 0; i < bytes.length; i++) { + intValueContainer.setByteValue(i, bytes[i]); + } + + for (int i = 0; i < bytes.length; i++) { + assertEquals(intValueContainer.getValueAsByte(i), bytes[i]); + } + + // show that the default value is returned for indices that have not yet + // had value assignments + for (int i = 0; i < 5; i++) { + assertEquals(intValueContainer.getValueAsByte(i + highIndex), defaultValue); + } + + // pre-condition tests + + // if index < 0 + assertThrows(RuntimeException.class, () -> intValueContainer.getValueAsByte(-1)); + + // if the value to return is not compatible with byte + intValueContainer.setIntValue(highIndex, 240); + assertThrows(RuntimeException.class, () -> intValueContainer.getValueAsByte(highIndex)); + + } + + /** + * Test for {@link IntValueContainer#getValueAsInt(int)} + */ + @Test + @UnitTestMethod(target = IntValueContainer.class, name = "getValueAsInt", args = { int.class }) + public void testGetValueAsInt() { + long defaultValue = 9546754; + IntValueContainer intValueContainer = new IntValueContainer(defaultValue,this::getEmptyIndexIterator); + int highIndex = 1000; + + int[] ints = new int[highIndex]; + for (int i = 0; i < ints.length; i++) { + ints[i] = i; + } + for (int i = 0; i < ints.length; i++) { + intValueContainer.setIntValue(i, ints[i]); + } + + for (int i = 0; i < ints.length; i++) { + assertEquals(intValueContainer.getValueAsInt(i), ints[i]); + } + + // show that the default value is returned for indices that have not yet + // had value assignments + for (int i = 0; i < 5; i++) { + assertEquals(intValueContainer.getValueAsInt(i + highIndex), defaultValue); + } + + // pre-condition tests + + // if index < 0 + assertThrows(RuntimeException.class, () -> intValueContainer.getValueAsInt(-1)); + + // if the value to return is not compatible with int + intValueContainer.setLongValue(highIndex, 535445345543L); + assertThrows(RuntimeException.class, () -> intValueContainer.getValueAsInt(highIndex)); + } + + /** + * Test for {@link IntValueContainer#getValueAsLong(int)} + */ + @Test + @UnitTestMethod(target = IntValueContainer.class, name = "getValueAsLong", args = { int.class }) + public void testGetValueAsLong() { + long defaultValue = 9546754; + IntValueContainer intValueContainer = new IntValueContainer(defaultValue,this::getEmptyIndexIterator); + int highIndex = 1000; + + long[] longs = new long[highIndex]; + for (int i = 0; i < longs.length; i++) { + longs[i] = 745644534457456L + i; + } + for (int i = 0; i < longs.length; i++) { + intValueContainer.setLongValue(i, longs[i]); + } + + for (int i = 0; i < longs.length; i++) { + assertEquals(intValueContainer.getValueAsLong(i), longs[i]); + } + + // show that the default value is returned for indices that have not yet + // had value assignments + for (int i = 0; i < 5; i++) { + assertEquals(intValueContainer.getValueAsLong(i + highIndex), defaultValue); + } + + // pre-condition tests + + // if index < 0 + assertThrows(RuntimeException.class, () -> intValueContainer.getValueAsLong(-1)); + + // if the value to return is not compatible with long -- can't fail + // since all values are compatible with long. + + } + + /** + * Test for {@link IntValueContainer#getValueAsShort(int)} + */ + @Test + @UnitTestMethod(target = IntValueContainer.class, name = "getValueAsShort", args = { int.class }) + public void testGetValueAsShort() { + short defaultValue = 30467; + IntValueContainer intValueContainer = new IntValueContainer(defaultValue,this::getEmptyIndexIterator); + int highIndex = 1000; + + short[] shorts = new short[highIndex]; + for (int i = 0; i < shorts.length; i++) { + short s = (short) (i % (256 * 256) - 128 * 256); + shorts[i] = s; + } + for (int i = 0; i < shorts.length; i++) { + intValueContainer.setShortValue(i, shorts[i]); + } + + for (int i = 0; i < shorts.length; i++) { + assertEquals(intValueContainer.getValueAsShort(i), shorts[i]); + } + + // show that the default value is returned for indices that have not yet + // had value assignments + for (int i = 0; i < 5; i++) { + assertEquals(intValueContainer.getValueAsShort(i + highIndex), defaultValue); + } + + // pre-condition tests + + // if index < 0 + assertThrows(RuntimeException.class, () -> intValueContainer.getValueAsShort(-1)); + + // if the value to return is not compatible with short + intValueContainer.setIntValue(highIndex, 40000); + assertThrows(RuntimeException.class, () -> intValueContainer.getValueAsShort(highIndex)); + + } + + /** + * Test for {@link IntValueContainer#setCapacity(int)} + */ + @Test + @UnitTestMethod(target = IntValueContainer.class, name = "setCapacity", args = { int.class }) + public void testSetCapacity() { + IntValueContainer intValueContainer = new IntValueContainer(0,this::getEmptyIndexIterator); + + int expectedCapacity = 5; + intValueContainer.setCapacity(expectedCapacity); + assertTrue(intValueContainer.getCapacity() >= expectedCapacity); + + expectedCapacity = 15; + intValueContainer.setCapacity(expectedCapacity); + assertTrue(intValueContainer.getCapacity() >= expectedCapacity); + + expectedCapacity = 50; + intValueContainer.setCapacity(expectedCapacity); + assertTrue(intValueContainer.getCapacity() >= expectedCapacity); + + expectedCapacity = 1000; + intValueContainer.setCapacity(expectedCapacity); + assertTrue(intValueContainer.getCapacity() >= expectedCapacity); + + } + + /** + * Test for {@link IntValueContainer#getCapacity()} + */ + @Test + @UnitTestMethod(target = IntValueContainer.class, name = "getCapacity", args = {}) + public void testGetCapacity() { + + IntValueContainer intValueContainer = new IntValueContainer(0,this::getEmptyIndexIterator); + + assertTrue(intValueContainer.getCapacity() >= 0); + + intValueContainer.setIntValue(1, 1234); + assertTrue(intValueContainer.getCapacity() >= 1); + + intValueContainer.setIntValue(34, 364); + assertTrue(intValueContainer.getCapacity() >= 34); + + intValueContainer.setIntValue(10, 154); + assertTrue(intValueContainer.getCapacity() >= 10); + + intValueContainer.setIntValue(137, 2526); + assertTrue(intValueContainer.getCapacity() >= 137); + + intValueContainer.setLongValue(1000, 1234534234234234234L); + assertTrue(intValueContainer.getCapacity() >= 1000); + + } + + /** + * Test for {@link IntValueContainer#setLongValue(int, long)} + */ + @Test + @UnitTestMethod(target = IntValueContainer.class, name = "setLongValue", args = { int.class, long.class }) + public void testSetLongValue() { + IntValueContainer intValueContainer = new IntValueContainer(0,this::getEmptyIndexIterator); + + // long value + long l = 523423463534562345L; + intValueContainer.setLongValue(0, l); + assertEquals(l, intValueContainer.getValueAsLong(0)); + + // pre-condition tests + long l2 = 1; + assertThrows(RuntimeException.class, () -> intValueContainer.setLongValue(-1, l2)); + + } + + /** + * Test for {@link IntValueContainer#setIntValue(int, int)} + */ + @Test + @UnitTestMethod(target = IntValueContainer.class, name = "setIntValue", args = { int.class, int.class }) + public void testSetIntValue() { + IntValueContainer intValueContainer = new IntValueContainer(0,this::getEmptyIndexIterator); + + // int value + int i = 70000; + intValueContainer.setIntValue(0, i); + assertEquals(i, intValueContainer.getValueAsInt(0)); + + // pre-condition tests + int i2 = 1; + assertThrows(RuntimeException.class, () -> intValueContainer.setIntValue(-1, i2)); + } + + /** + * Test for {@link IntValueContainer#setShortValue(int, short)} + */ + + @Test + @UnitTestMethod(target = IntValueContainer.class, name = "setShortValue", args = { int.class, short.class }) + public void testSetShortValue() { + IntValueContainer intValueContainer = new IntValueContainer(0,this::getEmptyIndexIterator); + + // short value + short s = 300; + intValueContainer.setShortValue(0, s); + assertEquals(s, intValueContainer.getValueAsShort(0)); + + // pre-condition tests + short s2 = 1; + assertThrows(RuntimeException.class, () -> intValueContainer.setShortValue(-1, s2)); + + } + + /** + * Test for {@link IntValueContainer#setByteValue(int, byte)} + */ + + @Test + @UnitTestMethod(target = IntValueContainer.class, name = "setByteValue", args = { int.class, byte.class }) + public void testSetByteValue() { + IntValueContainer intValueContainer = new IntValueContainer(0,this::getEmptyIndexIterator); + + // byte value + byte b = 5; + intValueContainer.setByteValue(0, b); + assertEquals(b, intValueContainer.getValueAsByte(0)); + + // pre-condition tests + byte b2 = 1; + assertThrows(RuntimeException.class, () -> intValueContainer.setByteValue(-1, b2)); + + } + + /** + * Test for {@link IntValueContainer#incrementIntValue(int, int)} + */ + + @Test + @UnitTestMethod(target = IntValueContainer.class, name = "incrementIntValue", args = { int.class, int.class }) + public void testIncrementIntValue() { + IntValueContainer intValueContainer = new IntValueContainer(0,this::getEmptyIndexIterator); + + // int value + int i1 = 70000; + intValueContainer.setIntValue(0, i1); + int i2 = 2000; + intValueContainer.incrementIntValue(0, i2); + assertEquals(i1 + i2, intValueContainer.getValueAsInt(0)); + + // pre-condition tests + int i3 = 1; + assertThrows(RuntimeException.class, () -> intValueContainer.incrementIntValue(-1, i3)); + intValueContainer.setLongValue(0, Long.MAX_VALUE); + + assertThrows(ArithmeticException.class, () -> intValueContainer.incrementIntValue(0, i3)); + } + + /** + * Test for {@link IntValueContainer#incrementLongValue(int, long)} + */ + @Test + @UnitTestMethod(target = IntValueContainer.class, name = "incrementLongValue", args = { int.class, long.class }) + public void testIncrementLongValue() { + IntValueContainer intValueContainer = new IntValueContainer(0,this::getEmptyIndexIterator); + + // long value + long l1 = 523423463534562345L; + intValueContainer.setLongValue(0, l1); + long l2 = 66457456456456456L; + intValueContainer.incrementLongValue(0, l2); + assertEquals(l1 + l2, intValueContainer.getValueAsLong(0)); + + // pre-condition tests + long l3 = 1; + assertThrows(RuntimeException.class, () -> intValueContainer.incrementLongValue(-1, l3)); + intValueContainer.setLongValue(0, Long.MAX_VALUE); + + assertThrows(ArithmeticException.class, () -> intValueContainer.incrementLongValue(0, l3)); + } + + /** + * Test for {@link IntValueContainer#incrementShortValue(int, short)} + */ + @Test + @UnitTestMethod(target = IntValueContainer.class, name = "incrementShortValue", args = { int.class, short.class }) + public void testIncrementShortValue() { + IntValueContainer intValueContainer = new IntValueContainer(0,this::getEmptyIndexIterator); + + // short value + short s1 = 300; + intValueContainer.setShortValue(0, s1); + short s2 = 100; + intValueContainer.incrementShortValue(0, s2); + assertEquals(s1 + s2, intValueContainer.getValueAsShort(0)); + + // pre-condition tests + short s3 = 1; + assertThrows(RuntimeException.class, () -> intValueContainer.incrementShortValue(-1, s3)); + intValueContainer.setLongValue(0, Long.MAX_VALUE); + + assertThrows(ArithmeticException.class, () -> intValueContainer.incrementShortValue(0, s3)); + } + + /** + * Test for {@link IntValueContainer#incrementByteValue(int, byte)} + */ + @Test + @UnitTestMethod(target = IntValueContainer.class, name = "incrementByteValue", args = { int.class, byte.class }) + public void testIncrementByteValue() { + IntValueContainer intValueContainer = new IntValueContainer(0,this::getEmptyIndexIterator); + + // byte value + byte b1 = 5; + intValueContainer.setByteValue(0, b1); + byte b2 = 12; + intValueContainer.incrementByteValue(0, b2); + assertEquals(b1 + b2, intValueContainer.getValueAsByte(0)); + + // pre-condition tests + byte b3 = 1; + assertThrows(RuntimeException.class, () -> intValueContainer.incrementByteValue(-1, b3)); + intValueContainer.setLongValue(0, Long.MAX_VALUE); + + assertThrows(ArithmeticException.class, () -> intValueContainer.incrementByteValue(0, b3)); + } + + /** + * Test for {@link IntValueContainer#decrementByteValue(int, byte)} + */ + @Test + @UnitTestMethod(target = IntValueContainer.class, name = "decrementByteValue", args = { int.class, byte.class }) + public void testDecrementByteValue() { + IntValueContainer intValueContainer = new IntValueContainer(0,this::getEmptyIndexIterator); + + // byte value + byte b1 = 5; + intValueContainer.setByteValue(0, b1); + + byte b2 = 12; + intValueContainer.decrementByteValue(0, b2); + assertEquals(b1 - b2, intValueContainer.getValueAsByte(0)); + + // pre-condition tests + byte b3 = 1; + assertThrows(RuntimeException.class, () -> intValueContainer.decrementByteValue(-1, b3)); + intValueContainer.setLongValue(0, Long.MIN_VALUE); + + assertThrows(ArithmeticException.class, () -> intValueContainer.decrementByteValue(0, b3)); + } + + /** + * Test for {@link IntValueContainer#decrementShortValue(int, short)} + */ + @Test + @UnitTestMethod(target = IntValueContainer.class, name = "decrementShortValue", args = { int.class, short.class }) + public void testDecrementShortValue() { + IntValueContainer intValueContainer = new IntValueContainer(0,this::getEmptyIndexIterator); + + // short value + short s1 = 300; + intValueContainer.setShortValue(0, s1); + short s2 = 100; + intValueContainer.decrementShortValue(0, s2); + assertEquals(s1 - s2, intValueContainer.getValueAsShort(0)); + + // pre-condition tests + short s3 = 1; + assertThrows(RuntimeException.class, () -> intValueContainer.decrementShortValue(-1, s3)); + intValueContainer.setLongValue(0, Long.MIN_VALUE); + + assertThrows(ArithmeticException.class, () -> intValueContainer.decrementShortValue(0, s3)); + } + + /** + * Test for {@link IntValueContainer#decrementIntValue(int, int)} + */ + @Test + @UnitTestMethod(target = IntValueContainer.class, name = "decrementIntValue", args = { int.class, int.class }) + public void testDecrementIntValue() { + IntValueContainer intValueContainer = new IntValueContainer(0,this::getEmptyIndexIterator); + + // int value + int i1 = 70000; + intValueContainer.setIntValue(0, i1); + int i2 = 2000; + intValueContainer.decrementIntValue(0, i2); + assertEquals(i1 - i2, intValueContainer.getValueAsInt(0)); + + // pre-condition tests + int i3 = 1; + assertThrows(RuntimeException.class, () -> intValueContainer.decrementIntValue(-1, i3)); + intValueContainer.setLongValue(0, Long.MIN_VALUE); + + assertThrows(ArithmeticException.class, () -> intValueContainer.decrementIntValue(0, i3)); + } + + /** + * Test for {@link IntValueContainer#decrementLongValue(int, long)} + */ + @Test + @UnitTestMethod(target = IntValueContainer.class, name = "decrementLongValue", args = { int.class, long.class }) + public void testDecrementLongValue() { + IntValueContainer intValueContainer = new IntValueContainer(0,this::getEmptyIndexIterator); + + // long value + long l1 = 523423463534562345L; + intValueContainer.setLongValue(0, l1); + long l2 = 66457456456456456L; + intValueContainer.decrementLongValue(0, l2); + assertEquals(l1 - l2, intValueContainer.getValueAsLong(0)); + + // pre-condition tests + long l3 = 1; + assertThrows(RuntimeException.class, () -> intValueContainer.decrementLongValue(-1, l3)); + intValueContainer.setLongValue(0, Long.MIN_VALUE); + + assertThrows(ArithmeticException.class, () -> intValueContainer.decrementLongValue(0, l3)); + } + + /** + * Test for {@link IntValueContainer#IntValueContainer(long)} + */ + @Test + @UnitTestConstructor(target = IntValueContainer.class, args = { long.class, Supplier.class }) + public void testConstructor() { + IntValueContainer intValueContainer = new IntValueContainer(12,this::getEmptyIndexIterator); + assertNotNull(intValueContainer); + } + + + + /** + * Test for {@link IntValueContainer#getDefaultValueAsByte()} + */ + @Test + @UnitTestMethod(target = IntValueContainer.class, name = "getDefaultValueAsByte", args = {}) + public void testGetDefaultValueAsByte() { + byte expected = 120; + IntValueContainer intValueContainer = new IntValueContainer(expected,this::getEmptyIndexIterator); + byte actual = intValueContainer.getDefaultValueAsByte(); + assertEquals(expected, actual); + + // pre-condition tests + + // default short + assertThrows(RuntimeException.class, () -> new IntValueContainer(30000,this::getEmptyIndexIterator).getDefaultValueAsByte()); + + // default int + assertThrows(RuntimeException.class, () -> new IntValueContainer(120000,this::getEmptyIndexIterator).getDefaultValueAsByte()); + + // default long + assertThrows(RuntimeException.class, () -> new IntValueContainer(123124235123234234L,this::getEmptyIndexIterator).getDefaultValueAsByte()); + + } + + /** + * Test for {@link IntValueContainer#getDefaultValueAsShort()} + */ + @Test + @UnitTestMethod(target = IntValueContainer.class, name = "getDefaultValueAsShort", args = {}) + public void testGetDefaultValueAsShort() { + short expected = 32000; + IntValueContainer intValueContainer = new IntValueContainer(expected,this::getEmptyIndexIterator); + short actual = intValueContainer.getDefaultValueAsShort(); + assertEquals(expected, actual); + + // pre-condition tests + + // default int + assertThrows(RuntimeException.class, () -> new IntValueContainer(120000,this::getEmptyIndexIterator).getDefaultValueAsShort()); + + // default long + assertThrows(RuntimeException.class, () -> new IntValueContainer(123124235123234234L,this::getEmptyIndexIterator).getDefaultValueAsShort()); + } + + /** + * Test for {@link IntValueContainer#getDefaultValueAsInt()} + */ + @Test + @UnitTestMethod(target = IntValueContainer.class, name = "getDefaultValueAsInt", args = {}) + public void testGetDefaultValueAsInt() { + int expected = 52000; + IntValueContainer intValueContainer = new IntValueContainer(expected,this::getEmptyIndexIterator); + int actual = intValueContainer.getDefaultValueAsInt(); + assertEquals(expected, actual); + + // pre-condition tests + + // default long + assertThrows(RuntimeException.class, () -> new IntValueContainer(123124235123234234L,this::getEmptyIndexIterator).getDefaultValueAsInt()); + } + + /** + * Test for {@link IntValueContainer#getDefaultValueAsLong()} + */ + @Test + @UnitTestMethod(target = IntValueContainer.class, name = "getDefaultValueAsLong", args = {}) + public void testGetDefaultValueAsLong() { + long expected = 364534534534534345L; + IntValueContainer intValueContainer = new IntValueContainer(expected,this::getEmptyIndexIterator); + long actual = intValueContainer.getDefaultValueAsLong(); + assertEquals(expected, actual); + + // pre-condition tests -- none + } + + /** + * Test for {@link IntValueContainer#getIntValueType()} + */ + @Test + @UnitTestMethod(target = IntValueContainer.class, name = "getIntValueType", args = {}) + public void testGetIntValueType() { + IntValueContainer intValueContainer = new IntValueContainer(0,this::getEmptyIndexIterator); + assertEquals(IntValueType.BYTE, intValueContainer.getIntValueType()); + + intValueContainer.setIntValue(0, 1); + assertEquals(IntValueType.BYTE, intValueContainer.getIntValueType()); + + intValueContainer.setIntValue(1, 130); + assertEquals(IntValueType.SHORT, intValueContainer.getIntValueType()); + + intValueContainer.setIntValue(2, 70000); + assertEquals(IntValueType.INT, intValueContainer.getIntValueType()); + + intValueContainer.setLongValue(3, 123123123123123123L); + assertEquals(IntValueType.LONG, intValueContainer.getIntValueType()); + + intValueContainer.setIntValue(4, 1); + assertEquals(IntValueType.LONG, intValueContainer.getIntValueType()); + + } + + @Test + @UnitTestMethod(target = IntValueContainer.class, name = "toString", args = {}) + public void testToString() { + + List list = new ArrayList<>(); + list.add(1); + list.add(2); + list.add(5); + list.add(6); + list.add(7); + + IntValueContainer intValueContainer = new IntValueContainer(0, () -> list.iterator()); + intValueContainer.setIntValue(5, 2); + intValueContainer.setIntValue(7, 3); + intValueContainer.setIntValue(1, 1); + intValueContainer.setIntValue(8, 4); + String actualValue = intValueContainer.toString(); + + + String expectedValue = "IntValueContainer [subTypeArray=ByteArray [values=[1=1, 2=0, 5=2, 6=0, 7=3], defaultValue=0]]"; + assertEquals(expectedValue, actualValue); + + + //demonstration with larger values + intValueContainer = new IntValueContainer(0, () -> list.iterator()); + intValueContainer.setIntValue(5, 2000); + intValueContainer.setIntValue(7, 3000); + intValueContainer.setIntValue(1, 1); + intValueContainer.setIntValue(8, 4); + actualValue = intValueContainer.toString(); + + expectedValue = "IntValueContainer [subTypeArray=ShortArray [values=[1=1, 2=0, 5=2000, 6=0, 7=3000], defaultValue=0]]"; + assertEquals(expectedValue, actualValue); + + } + +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/AT_ObjectValueContainer.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/AT_ObjectValueContainer.java new file mode 100644 index 000000000..9a03205d1 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/AT_ObjectValueContainer.java @@ -0,0 +1,136 @@ +package gov.hhs.aspr.ms.gcm.plugins.util.properties.arraycontainers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.function.Supplier; + +import org.junit.jupiter.api.Test; + +import util.annotations.UnitTag; +import util.annotations.UnitTestConstructor; +import util.annotations.UnitTestMethod; + +public class AT_ObjectValueContainer { + private Iterator getEmptyIndexIterator() { + return Collections.emptyIterator(); + } + /** + * Tests {@link ObjectValueContainer#ObjectValueContainer(Object, int)} + */ + @Test + @UnitTestConstructor(target = ObjectValueContainer.class,args = { Object.class, Supplier.class }) + public void testConstructor() { + String defaultValue = "default"; + ObjectValueContainer objectValueContainer = new ObjectValueContainer(defaultValue, this::getEmptyIndexIterator); + assertNotNull(objectValueContainer); + + objectValueContainer = new ObjectValueContainer(null, this::getEmptyIndexIterator); + assertNotNull(objectValueContainer); + + + } + + /** + * Tests {@link ObjectValueContainer#setValue(int, Object)} + */ + @Test + @UnitTestMethod(target = ObjectValueContainer.class,name = "setValue", args = { int.class, Object.class }) + public void testSetValue() { + String defaultValue = "default"; + ObjectValueContainer objectValueContainer = new ObjectValueContainer(defaultValue,this::getEmptyIndexIterator); + objectValueContainer.setValue(3, "dog"); + objectValueContainer.setValue(1, "cat"); + objectValueContainer.setValue(4, "pig"); + objectValueContainer.setValue(7, "cow"); + objectValueContainer.setValue(3, "bat"); + objectValueContainer.setValue(5, null); + + assertEquals(defaultValue, objectValueContainer.getValue(0)); + assertEquals("cat", objectValueContainer.getValue(1)); + assertEquals(defaultValue, objectValueContainer.getValue(2)); + assertEquals("bat", objectValueContainer.getValue(3)); + assertEquals("pig", objectValueContainer.getValue(4)); + assertNull(objectValueContainer.getValue(5)); + assertEquals(defaultValue, objectValueContainer.getValue(6)); + assertEquals("cow", objectValueContainer.getValue(7)); + assertEquals(defaultValue, objectValueContainer.getValue(8)); + assertEquals(defaultValue, objectValueContainer.getValue(9)); + + // test pre-conditions + assertThrows(IllegalArgumentException.class, () -> objectValueContainer.setValue(-1, "frog")); + } + + /** + * Tests {@link ObjectValueContainer#getValue(int)} + */ + @Test + @UnitTestMethod(target = ObjectValueContainer.class,name = "getValue", args = { int.class }) + public void testGetValue() { + + String defaultValue = "default"; + ObjectValueContainer objectValueContainer = new ObjectValueContainer(defaultValue, this::getEmptyIndexIterator); + objectValueContainer.setValue(3, "dog"); + objectValueContainer.setValue(1, "cat"); + objectValueContainer.setValue(4, "pig"); + objectValueContainer.setValue(7, "cow"); + objectValueContainer.setValue(3, "bat"); + objectValueContainer.setValue(5, null); + + assertEquals(defaultValue, objectValueContainer.getValue(0)); + assertEquals("cat", objectValueContainer.getValue(1)); + assertEquals(defaultValue, objectValueContainer.getValue(2)); + assertEquals("bat", objectValueContainer.getValue(3)); + assertEquals("pig", objectValueContainer.getValue(4)); + assertNull(objectValueContainer.getValue(5)); + assertEquals(defaultValue, objectValueContainer.getValue(6)); + assertEquals("cow", objectValueContainer.getValue(7)); + assertEquals(defaultValue, objectValueContainer.getValue(8)); + assertEquals(defaultValue, objectValueContainer.getValue(9)); + + // test pre-conditions + assertThrows(IllegalArgumentException.class, () -> objectValueContainer.getValue(-1)); + } + + @Test + @UnitTestMethod(target = ObjectValueContainer.class,name = "setCapacity", args = {int.class}, tags = {UnitTag.INCOMPLETE}) + public void testSetCapacity() { + // requires a manual performance test + } + + @Test + @UnitTestMethod(target = ObjectValueContainer.class,name = "getCapacity", args = {}, tags = {UnitTag.INCOMPLETE}) + public void testGetCapacity() { + // requires a manual performance test + } + + @Test + @UnitTestMethod(target = ObjectValueContainer.class, name = "toString", args = {}) + public void testToString() { + + List list = new ArrayList<>(); + list.add(1); + list.add(2); + list.add(5); + list.add(6); + list.add(7); + + ObjectValueContainer objectValueContainer = new ObjectValueContainer(0, () -> list.iterator()); + objectValueContainer.setValue(5, 2); + objectValueContainer.setValue(7, "A"); + objectValueContainer.setValue(1, 3.8); + objectValueContainer.setValue(8, "B"); + String actualValue = objectValueContainer.toString(); + + + String expectedValue = "ObjectValueContainer [elements=[1=3.8, 2=0, 5=2, 6=0, 7=A], defaultValue=0]"; + assertEquals(expectedValue, actualValue); + } + +} diff --git a/gcm3/src/test/java/plugins/util/properties/arraycontainers/MT_BooleanContainer.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/MT_BooleanContainer.java similarity index 98% rename from gcm3/src/test/java/plugins/util/properties/arraycontainers/MT_BooleanContainer.java rename to gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/MT_BooleanContainer.java index 2a7f2a011..60d62a442 100644 --- a/gcm3/src/test/java/plugins/util/properties/arraycontainers/MT_BooleanContainer.java +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/plugins/util/properties/arraycontainers/MT_BooleanContainer.java @@ -1,4 +1,4 @@ -package plugins.util.properties.arraycontainers; +package gov.hhs.aspr.ms.gcm.plugins.util.properties.arraycontainers; /* * Manual performance and tuning test turned off until adoption of JOL or some other appropriate library for assessing memory use in post-8 java. @@ -22,7 +22,6 @@ * using the LinkedHashSet and those have more than 1% should be stored using a * BooleanContainer. * - * @author Shawn Hatch * */ public class MT_BooleanContainer { diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/tools/BitSetTest.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/tools/BitSetTest.java new file mode 100644 index 000000000..3997231a2 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/tools/BitSetTest.java @@ -0,0 +1,13 @@ +package gov.hhs.aspr.ms.gcm.tools; + +import java.util.BitSet; + +public class BitSetTest { + public static void main(String[] args) { + BitSet b = new BitSet(); + b.set(107,false); + + System.out.println(b.length()); + + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/tools/DocCheck.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/tools/DocCheck.java new file mode 100644 index 000000000..b5b206395 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/tools/DocCheck.java @@ -0,0 +1,138 @@ +package gov.hhs.aspr.ms.gcm.tools; + +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.List; + +public final class DocCheck { + private final List directories; + + private DocCheck(List directories) { + this.directories = new ArrayList<>(directories); + } + + private void execute() { + for (Path dir : directories) { + loadSourceClasses(dir); + } + } + + private static boolean isJavaFile(Path file) { + return Files.isRegularFile(file) && file.toString().endsWith(".java"); + } + + private static enum ItemType { + START, END + } + + private static class Item { + private ItemType itemType; + private String line; + private int lineNumber; + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Item [itemType="); + builder.append(itemType); + builder.append(", line="); + builder.append(line); + builder.append(", lineNumber="); + builder.append(lineNumber); + builder.append("]"); + return builder.toString(); + } + + } + + private List gatherItems(final Path file) throws IOException { + List result = new ArrayList<>(); + List lines = Files.readAllLines(file); + + for (int i = 0; i < lines.size(); i++) { + String line = lines.get(i); + if (line.toLowerCase().contains("start code_ref")) { + Item item = new Item(); + item.itemType = ItemType.START; + item.line = line; + item.lineNumber = i + 1; + result.add(item); + } else if (line.toLowerCase().contains("/* end */")) { + Item item = new Item(); + item.itemType = ItemType.END; + item.line = line; + item.lineNumber = i + 1; + result.add(item); + } + } + + return result; + } + + private void probeFile(final Path file) throws IOException { + + List items = gatherItems(file); + if(items.size()==0) { + return; + } + + boolean mismatchFound = false; + if (items.size() % 2 != 0) { + mismatchFound = true; + } + for (int i = 0; i < items.size(); i++) { + Item item = items.get(i); + if (i % 2 == 0) { + if (item.itemType != ItemType.START) { + mismatchFound = true; + } + } else { + if (item.itemType != ItemType.END) { + mismatchFound = true; + } + } + } + if (mismatchFound) { + System.out.println(file); + for (Item item : items) { + System.out.println(item); + } + } + } + + private final class SourceFileVisitor extends SimpleFileVisitor { + @Override + public FileVisitResult visitFile(final Path file, final BasicFileAttributes attr) throws IOException { + if (isJavaFile(file)) { + probeFile(file); + } + return FileVisitResult.CONTINUE; + } + } + + private void loadSourceClasses(Path path) { + + final SourceFileVisitor sourceFileVisitor = new SourceFileVisitor(); + try { + Files.walkFileTree(path, sourceFileVisitor); + } catch (final IOException e) { + throw new RuntimeException(e); + } + + } + + public static void main(String[] args) { + List directories = new ArrayList<>(); + for (String arg : args) { + Path path = Paths.get(arg); + directories.add(path); + } + new DocCheck(directories).execute(); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/tools/MetaInfoReportRunner.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/tools/MetaInfoReportRunner.java new file mode 100644 index 000000000..690240d91 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/tools/MetaInfoReportRunner.java @@ -0,0 +1,9 @@ +package gov.hhs.aspr.ms.gcm.tools; + +import util.meta.unittestcoverage.reports.MetaInfoReport; + +public class MetaInfoReportRunner { + public static void main(String[] args) { + MetaInfoReport.run(args); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/tools/MissingTestsReportRunner.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/tools/MissingTestsReportRunner.java new file mode 100644 index 000000000..4e5f488d8 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/tools/MissingTestsReportRunner.java @@ -0,0 +1,14 @@ +package gov.hhs.aspr.ms.gcm.tools; + +import util.meta.unittestcoverage.reports.MissingTestsReport; + +public class MissingTestsReportRunner { + public static void main(String[] args) { + String[] args2 = new String[3]; + args2[0] = args[0]; + args2[1] = args[1]; + // args2[2] = "global"; + + MissingTestsReport.run(args2); + } +} diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/tools/SeedGenerator.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/tools/SeedGenerator.java new file mode 100644 index 000000000..d081bd8f9 --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/tools/SeedGenerator.java @@ -0,0 +1,25 @@ +package gov.hhs.aspr.ms.gcm.tools; + +import java.util.Random; + +import org.apache.commons.math3.util.FastMath; + +/** + * Produces 20 non-negative random longs. Negatives present copy/paste issues. + * + * + */ +public final class SeedGenerator { + + private SeedGenerator() { + + } + + public static void main(String[] args) { + Random random = new Random(); + for (int i = 0; i < 20; i++) { + System.out.println(FastMath.abs(random.nextLong()) + "L"); + } + } + +} diff --git a/gcm3/src/test/java/tools/codestats/CodeCounts.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/tools/codestats/CodeCounts.java similarity index 79% rename from gcm3/src/test/java/tools/codestats/CodeCounts.java rename to gcm/src/test/java/gov/hhs/aspr/ms/gcm/tools/codestats/CodeCounts.java index c94590a15..1181da499 100644 --- a/gcm3/src/test/java/tools/codestats/CodeCounts.java +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/tools/codestats/CodeCounts.java @@ -1,4 +1,6 @@ -package tools.codestats; +package gov.hhs.aspr.ms.gcm.tools.codestats; + +import util.meta.codecount.CodeCountReport; public class CodeCounts { public static void main(String[] args) { @@ -7,8 +9,6 @@ public static void main(String[] args) { codeCountReportBuilder.addDirectory(arg); } CodeCountReport codeCountReport = codeCountReportBuilder.build(); - System.out.println(codeCountReport.getDetailsReport()); - } } diff --git a/gcm/src/test/java/gov/hhs/aspr/ms/gcm/tools/meta/plugindependency/PluginDependencyInfoGenerator.java b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/tools/meta/plugindependency/PluginDependencyInfoGenerator.java new file mode 100644 index 000000000..f7814770f --- /dev/null +++ b/gcm/src/test/java/gov/hhs/aspr/ms/gcm/tools/meta/plugindependency/PluginDependencyInfoGenerator.java @@ -0,0 +1,470 @@ +package gov.hhs.aspr.ms.gcm.tools.meta.plugindependency; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.GlobalPropertiesPlugin; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.GlobalPropertiesPluginId; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.groups.GroupsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.groups.GroupsPluginId; +import gov.hhs.aspr.ms.gcm.plugins.groups.datamanagers.GroupsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.materials.MaterialsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.materials.MaterialsPluginId; +import gov.hhs.aspr.ms.gcm.plugins.materials.datamangers.MaterialsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.partitions.PartitionsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.partitions.PartitionsPluginId; +import gov.hhs.aspr.ms.gcm.plugins.partitions.datamanagers.PartitionsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePlugin; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePluginId; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeoplePluginData; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.PersonPropertiesPlugin; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.PersonPropertiesPluginId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.regions.RegionsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.regions.RegionsPluginId; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.reports.ReportsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.reports.ReportsPluginId; +import gov.hhs.aspr.ms.gcm.plugins.resources.ResourcesPlugin; +import gov.hhs.aspr.ms.gcm.plugins.resources.ResourcesPluginId; +import gov.hhs.aspr.ms.gcm.plugins.resources.datamanagers.ResourcesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPluginId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import util.graph.Graph; +import util.graph.GraphDepthEvaluator; +import util.graph.Graphs; +import util.graph.MutableGraph; +import util.meta.packagedependency.PackageDependencyData; +import util.path.MapPathSolver; + +/** + * + * + */ +public class PluginDependencyInfoGenerator { + + private static boolean isJavaFile(Path file) { + return Files.isRegularFile(file) && file.toString().endsWith(".java"); + } + + private static String getClassName(Path sourcePath, Path file) { + return file.toString().substring(sourcePath.toString().length() + 1, file.toString().length() - 5).replace(File.separator, "."); + } + + /** + * Assumes that the source path and file are consistent + */ + private static Class getClassFromFile(Path sourcePath, Path file) { + try { + String className = getClassName(sourcePath, file); + return Class.forName(className); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + private static class Edge { + Set> classes = new LinkedHashSet<>(); + } + + private MutableGraph packageDependencyGraph = new MutableGraph<>(); + + private PackageDependencyData.Builder warningContainerBuilder = PackageDependencyData.builder(); + + private Node getImportNode(String line) { + + for (Node node : packageDependencyGraph.getNodes()) { + if (line.startsWith(node.name)) { + return node; + } + } + return null; + } + + private void probeFile(Path file) throws IOException { + + Class c = getClassFromFile(data.sourcePath, file); + List lines = Files.readAllLines(file); + + Node originNode = null; + Set destinationNodes = new LinkedHashSet<>(); + + boolean packageRead = false; + for (String line : lines) { + if (!packageRead) { + String trim = line.trim(); + if (trim.contains("package")) { + trim = line.substring(7, trim.length()).trim(); + Node node = getImportNode(trim); + if (node != null) { + originNode = node; + packageRead = true; + } + } + } else { + String trim = line.trim(); + if (trim.startsWith("import ")) { + trim = line.substring(6, trim.length()).trim(); + Node node = getImportNode(trim); + if (node != null) { + destinationNodes.add(node); + } + } + } + } + + if (originNode == null) { + throw new RuntimeException("cannot find origin node for " + c.getCanonicalName()); + } + + destinationNodes.remove(originNode); + + for (Node node : destinationNodes) { + List edges = packageDependencyGraph.getEdges(originNode, node); + Edge edge; + if (edges.isEmpty()) { + edge = new Edge(); + packageDependencyGraph.addEdge(edge, originNode, node); + } else { + edge = edges.get(0); + } + edge.classes.add(c); + } + + } + + private static class Node { + private String name; + + public Node(String name) { + this.name = name; + } + + @Override + public String toString() { + return name; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Node)) { + return false; + } + Node other = (Node) obj; + if (name == null) { + if (other.name != null) { + return false; + } + } else if (!name.equals(other.name)) { + return false; + } + return true; + } + + } + + private final class SourceFileVisitor extends SimpleFileVisitor { + @Override + public FileVisitResult visitFile(final Path file, final BasicFileAttributes attr) throws IOException { + if (isJavaFile(file)) { + probeFile(file); + } + return FileVisitResult.CONTINUE; + } + } + + private final Data data; + + private static class Data { + private Path sourcePath; + + private Path testPath; + + public Data() {} + public Data(Data data) { + sourcePath = data.sourcePath; + testPath = data.testPath; + } + } + + public final static Builder builder() { + return new Builder(); + } + + public final static class Builder { + private Builder() { + } + + private Data data = new Data(); + + public PluginDependencyInfoGenerator build() { + validate(); + return new PluginDependencyInfoGenerator(new Data(data)); + } + + private void validate() { + + } + + public Builder setSourcePath(Path sourcePath) { + data.sourcePath = sourcePath; + return this; + } + + public Builder setTestPath(Path testPath) { + data.testPath = testPath; + return this; + } + + } + + private PluginDependencyInfoGenerator(Data data) { + this.data = data; + packageDependencyGraph.addNode(new Node("util")); + packageDependencyGraph.addNode(new Node("nucleus")); + packageDependencyGraph.addNode(new Node("plugins.globalproperties")); + packageDependencyGraph.addNode(new Node("plugins.groups")); + packageDependencyGraph.addNode(new Node("plugins.materials")); + packageDependencyGraph.addNode(new Node("plugins.partitions")); + packageDependencyGraph.addNode(new Node("plugins.people")); + packageDependencyGraph.addNode(new Node("plugins.personproperties")); + packageDependencyGraph.addNode(new Node("plugins.regions")); + packageDependencyGraph.addNode(new Node("plugins.reports")); + packageDependencyGraph.addNode(new Node("plugins.resources")); + packageDependencyGraph.addNode(new Node("plugins.stochastics")); + packageDependencyGraph.addNode(new Node("plugins.util")); + packageDependencyGraph.addNode(new Node("tools")); + } + + private void loadSourceClasses() { + + final SourceFileVisitor sourceFileVisitor = new SourceFileVisitor(); + try { + Files.walkFileTree(data.sourcePath, sourceFileVisitor); + Files.walkFileTree(data.testPath, sourceFileVisitor); + } catch (final IOException e) { + throw new RuntimeException(e); + } + + } + + private void addToGraph(MutableGraph mutableGraph, Plugin plugin) { + + PluginId origin = plugin.getPluginId(); + mutableGraph.addNode(origin); + for (PluginId destination : plugin.getPluginDependencies()) { + mutableGraph.addEdge(new Object(), origin, destination); + } + } + + private Graph getPluginDependencyGraph() { + // create a graph of the plugin dependencies + MutableGraph pluginDependencyGraph = new MutableGraph<>(); + + + + addToGraph(pluginDependencyGraph, GlobalPropertiesPlugin.builder().setGlobalPropertiesPluginData(GlobalPropertiesPluginData.builder().build()).getGlobalPropertiesPlugin()); + addToGraph(pluginDependencyGraph, GroupsPlugin.builder().setGroupsPluginData(GroupsPluginData.builder().build()).getGroupsPlugin()); + addToGraph(pluginDependencyGraph, MaterialsPlugin.builder().setMaterialsPluginData(MaterialsPluginData.builder().build()).getMaterialsPlugin()); + addToGraph(pluginDependencyGraph, PartitionsPlugin.builder().setPartitionsPluginData(PartitionsPluginData.builder().build()).getPartitionsPlugin()); + addToGraph(pluginDependencyGraph, PeoplePlugin.getPeoplePlugin(PeoplePluginData.builder().build())); + addToGraph(pluginDependencyGraph, PersonPropertiesPlugin.builder().setPersonPropertiesPluginData(PersonPropertiesPluginData.builder().build()).getPersonPropertyPlugin()); + + addToGraph(pluginDependencyGraph, RegionsPlugin.builder().setRegionsPluginData(RegionsPluginData.builder().build()).getRegionsPlugin()); + addToGraph(pluginDependencyGraph, ReportsPlugin.getReportsPlugin()); + addToGraph(pluginDependencyGraph, ResourcesPlugin.builder().setResourcesPluginData(ResourcesPluginData.builder().build()).getResourcesPlugin()); + WellState wellState = WellState.builder().setSeed(0L).build(); + addToGraph(pluginDependencyGraph, StochasticsPlugin.getStochasticsPlugin(StochasticsPluginData.builder().setMainRNGState(wellState).build())); + + // build a map to help convert the map above into the type of graph we + // need + Map map = new LinkedHashMap<>(); + map.put(GlobalPropertiesPluginId.PLUGIN_ID, new Node("plugins.globalproperties")); + map.put(GroupsPluginId.PLUGIN_ID, new Node("plugins.groups")); + map.put(MaterialsPluginId.PLUGIN_ID, new Node("plugins.materials")); + map.put(PartitionsPluginId.PLUGIN_ID, new Node("plugins.partitions")); + map.put(PeoplePluginId.PLUGIN_ID, new Node("plugins.people")); + map.put(PersonPropertiesPluginId.PLUGIN_ID, new Node("plugins.personproperties")); + map.put(RegionsPluginId.PLUGIN_ID, new Node("plugins.regions")); + map.put(ReportsPluginId.PLUGIN_ID, new Node("plugins.reports")); + map.put(ResourcesPluginId.PLUGIN_ID, new Node("plugins.resources")); + map.put(StochasticsPluginId.PLUGIN_ID, new Node("plugins.stochastics")); + + /* + * Make sure that the map above maps each plugin to a node that is + * contained in the package dependency map. Note that this should be a + * subset of those nodes. + */ + for (PluginId pluginId : map.keySet()) { + Node node = map.get(pluginId); + if (!packageDependencyGraph.containsNode(node)) { + throw new RuntimeException("map contains unknown node for " + pluginId); + } + } + + MutableGraph mutableResult = new MutableGraph<>(); + + for (Object o : pluginDependencyGraph.getEdges()) { + PluginId originPluginId = pluginDependencyGraph.getOriginNode(o); + PluginId destinationPluginId = pluginDependencyGraph.getDestinationNode(o); + + Node originNode = map.get(originPluginId); + Node destinationNode = map.get(destinationPluginId); + + mutableResult.addEdge(new Edge(), originNode, destinationNode); + } + + Graph result = mutableResult.toGraph(); + Optional> optional = GraphDepthEvaluator.getGraphDepthEvaluator(result); + if (!optional.isPresent()) { + throw new RuntimeException("the plugin dependencies are circular"); + } + return result; + } + + public PackageDependencyData execute() { + + loadSourceClasses(); + + for (Edge edge : packageDependencyGraph.getEdges()) { + Node originNode = packageDependencyGraph.getOriginNode(edge); + Node destinationNode = packageDependencyGraph.getDestinationNode(edge); + System.out.println(originNode.name + "->" + destinationNode.name); + for (Class c : edge.classes) { + System.out.println("\t" + c.getSimpleName()); + } + } + + System.out.println("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); + + Optional> optional = GraphDepthEvaluator.getGraphDepthEvaluator(packageDependencyGraph.toGraph()); + + if (!optional.isPresent()) { + /* + * Explain in detail why there is a circular dependency + */ + + Graph g = packageDependencyGraph.toGraph(); + + g = Graphs.getSourceSinkReducedGraph(g); + g = Graphs.getEdgeReducedGraph(g); + g = Graphs.getSourceSinkReducedGraph(g); + + List> cutGraphs = Graphs.cutGraph(g); + StringBuilder sb = new StringBuilder(); + String lineSeparator = System.getProperty("line.separator"); + sb.append(lineSeparator); + boolean firstCutGraph = true; + + for (Graph cutGraph : cutGraphs) { + // cutGraph = Graphs.getSourceSinkReducedGraph(cutGraph); + if (firstCutGraph) { + firstCutGraph = false; + } else { + sb.append(lineSeparator); + } + sb.append("Dependency group: "); + sb.append(lineSeparator); + Set nodes = cutGraph.getNodes().stream().collect(Collectors.toCollection(LinkedHashSet::new)); + + for (Node node : nodes) { + sb.append("\t"); + sb.append(node); + sb.append(" requires:"); + sb.append(lineSeparator); + for (Edge edge : cutGraph.getOutboundEdges(node)) { + Node dependencyNode = cutGraph.getDestinationNode(edge); + if (nodes.contains(dependencyNode)) { + sb.append("\t"); + sb.append("\t"); + sb.append(dependencyNode); + sb.append(lineSeparator); + } + } + } + } + + System.out.println(sb); + } + + System.out.println("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); + + Graph pluginDependencyGraph = getPluginDependencyGraph(); + + for (Edge edge : pluginDependencyGraph.getEdges()) { + Node originNode = pluginDependencyGraph.getOriginNode(edge); + Node destinationNode = pluginDependencyGraph.getDestinationNode(edge); + System.out.println(originNode + "---->" + destinationNode); + } + + MapPathSolver mapPathSolver = new MapPathSolver<>(pluginDependencyGraph, (e) -> 1, (a, b) -> 0); + + for (Edge edge : packageDependencyGraph.getEdges()) { + Node originNode = packageDependencyGraph.getOriginNode(edge); + Node destinationNode = packageDependencyGraph.getDestinationNode(edge); + + // if (originNode.name.equals("plugins.partitions")) { + // if (destinationNode.name.equals("plugins.materials")) { + // System.out.println("weeeeeeeeeeeeeeee"); + // + // for (Node node : pluginDependencyGraph.getNodes()) { + // if (node.name.equals(originNode.name)) { + // System.out.println("found the origin node " + + // pluginDependencyGraph.containsNode(originNode)); + // + // } + // if (node.name.equals(destinationNode.name)) { + // System.out.println("found the destination node " + + // pluginDependencyGraph.containsNode(destinationNode)); + // + // } + // } + // System.out.println(pluginDependencyGraph.containsNode(originNode)); + // System.out.println(pluginDependencyGraph.containsNode(originNode)); + // System.out.println(pluginDependencyGraph.containsNode(originNode) + // && pluginDependencyGraph.containsEdge(destinationNode)); + // + // } + // } + + if (pluginDependencyGraph.containsNode(originNode) && pluginDependencyGraph.containsNode(destinationNode)) { + Optional> optionalPath = mapPathSolver.getPath(originNode, destinationNode); + if (optionalPath.isEmpty()) { + System.out.println("The package dependency of " + originNode + "->" + destinationNode + " violates the plugin dependency graph"); + } + } + } + + return warningContainerBuilder.build(); + + } + +} diff --git a/gcm/src/test/resources/nucleus/progress_log.txt b/gcm/src/test/resources/nucleus/progress_log.txt new file mode 100644 index 000000000..e69de29bb diff --git a/gcm3/.gitignore b/gcm3/.gitignore deleted file mode 100644 index 3863064f6..000000000 --- a/gcm3/.gitignore +++ /dev/null @@ -1,324 +0,0 @@ -################################### -# START Custom -################################### - -.classpath -.project -.idea - -# Specifically for intelliJ. Duplicative with part of the intelliJ section, but not a full replacement -.idea/ -*.iml - -################################### -# END Custom -################################### - -################################### -# START Java -# https://github.com/github/gitignore/blob/master/Java.gitignore -################################### - -# Compiled class file -*.class - -# Log file -*.log - -# BlueJ files -*.ctxt - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.jar -*.war -*.ear -*.zip -*.tar.gz -*.rar - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* - -################################### -# END Java -################################### - -################################### -# START R -# https://github.com/github/gitignore/blob/master/R.gitignore -################################### - -# History files -.Rhistory -.Rapp.history - -# Session Data files -.RData - -# Example code in package build process -*-Ex.R - -# Output files from R CMD build -/*.tar.gz - -# Output files from R CMD check -/*.Rcheck/ - -# RStudio files -.Rproj.user/ - -# produced vignettes -vignettes/*.html -vignettes/*.pdf - -# OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3 -.httr-oauth - -# knitr and R markdown default cache directories -/*_cache/ -/cache/ - -# Temporary files created by R markdown -*.utf8.md -*.knit.md - -################################### -# END R -################################### - -################################### -# START Eclipse -# https://github.com/github/gitignore/blob/master/Global/Eclipse.gitignore -################################### - -.metadata -bin/ -tmp/ -*.tmp -*.bak -*.swp -*~.nib -local.properties -.settings/ -.loadpath -.recommenders - -# External tool builders -.externalToolBuilders/ - -# Locally stored "Eclipse launch configurations" -*.launch - -# PyDev specific (Python IDE for Eclipse) -*.pydevproject - -# CDT-specific (C/C++ Development Tooling) -.cproject - -# Java annotation processor (APT) -.factorypath - -# PDT-specific (PHP Development Tools) -.buildpath - -# sbteclipse plugin -.target - -# Tern plugin -.tern-project - -# TeXlipse plugin -.texlipse - -# STS (Spring Tool Suite) -.springBeans - -# Code Recommenders -.recommenders/ - -# Scala IDE specific (Scala & Java development for Eclipse) -.cache-main -.scala_dependencies -.worksheet - -################################### -# END Eclipse -################################### - -################################### -# START JetBrains -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm -# https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 -# https://github.com/github/gitignore/blob/master/Global/JetBrains.gitignore -################################### - -# User-specific stuff: -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/dictionaries - -# Sensitive or high-churn files: -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.xml -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml - -# Gradle: -.idea/**/gradle.xml -.idea/**/libraries - -# CMake -cmake-build-debug/ - -# Mongo Explorer plugin: -.idea/**/mongoSettings.xml - -## File-based project format: -*.iws - -## Plugin-specific files: - -# IntelliJ -out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Cursive Clojure plugin -.idea/replstate.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -################################### -# END JetBrains -################################### - -################################### -# START Maven -# https://github.com/github/gitignore/blob/master/Maven.gitignore -################################### - -target/ -pom.xml.tag -pom.xml.releaseBackup -pom.xml.versionsBackup -pom.xml.next -release.properties -dependency-reduced-pom.xml -buildNumber.properties -.mvn/timing.properties - -# Avoid ignoring Maven wrapper jar file (.jar files are usually ignored) -!/.mvn/wrapper/maven-wrapper.jar - -################################### -# END Maven -################################### - -################################### -# START Windows -# https://github.com/github/gitignore/blob/master/Global/Windows.gitignore -################################### - -# Windows thumbnail cache files -Thumbs.db -ehthumbs.db -ehthumbs_vista.db - -# Dump file -*.stackdump - -# Folder config file -Desktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Windows Installer files -*.cab -*.msi -*.msm -*.msp - -# Windows shortcuts -*.lnk - -################################### -# END Windows -################################### - -################################### -# START MS Office -# https://github.com/github/gitignore/blob/master/Global/MicrosoftOffice.gitignore -################################### - -*.tmp - -# Word temporary -~$*.doc* - -# Excel temporary -~$*.xls* - -# Excel Backup File -*.xlk - -# PowerPoint temporary -~$*.ppt* - -# Visio autosave temporary files -*.~vsd* - -################################### -# END MS Office -################################### - -################################### -# START MacOS -# https://github.com/github/gitignore/blob/master/Global/macOS.gitignore -################################### - -# General -.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - -################################### -# END MacOS -################################### \ No newline at end of file diff --git a/gcm3/doc/modeling guide/2020.03.24 GCM 3.0 Modeling Guide.docx b/gcm3/doc/modeling guide/2020.03.24 GCM 3.0 Modeling Guide.docx deleted file mode 100644 index a5b43c0d3..000000000 Binary files a/gcm3/doc/modeling guide/2020.03.24 GCM 3.0 Modeling Guide.docx and /dev/null differ diff --git a/gcm3/doc/modeling guide/2020.12.24 GCM 3.1 Modeling Guide.docx b/gcm3/doc/modeling guide/2020.12.24 GCM 3.1 Modeling Guide.docx deleted file mode 100644 index 26adc8c29..000000000 Binary files a/gcm3/doc/modeling guide/2020.12.24 GCM 3.1 Modeling Guide.docx and /dev/null differ diff --git a/gcm3/doc/modeling guide/PersonLifeReport.java b/gcm3/doc/modeling guide/PersonLifeReport.java deleted file mode 100644 index 16a1f17b5..000000000 --- a/gcm3/doc/modeling guide/PersonLifeReport.java +++ /dev/null @@ -1,198 +0,0 @@ -package gcm.test.manual.demo.report; - -import java.util.LinkedHashSet; -import java.util.Set; - -import gcm.output.reports.AbstractReport; -import gcm.output.reports.PersonInfo; -import gcm.output.reports.ReportHeader; -import gcm.output.reports.ReportHeader.ReportHeaderBuilder; -import gcm.output.reports.ReportItem.ReportItemBuilder; -import gcm.output.reports.StateChange; -import gcm.scenario.CompartmentId; -import gcm.scenario.GroupId; -import gcm.scenario.PersonId; -import gcm.scenario.PersonPropertyId; -import gcm.scenario.RegionId; -import gcm.scenario.ResourceId; -import gcm.simulation.ObservableEnvironment; - -public class PersonLifeReport extends AbstractReport { - private ReportHeader reportHeader; - - private PersonId personId; - - private ReportHeader getReportHeader() { - if (reportHeader == null) { - /* - * Only the columns that this report is adding are put into the - * header. The header portions for the scenario, the replication and - * the experiment columns are automatically generated. - */ - ReportHeaderBuilder reportHeaderBuilder = new ReportHeaderBuilder(); - reportHeaderBuilder.add("Person"); - reportHeaderBuilder.add("Time"); - reportHeaderBuilder.add("Activity"); - reportHeaderBuilder.add("Details"); - reportHeader = reportHeaderBuilder.build(); - } - return reportHeader; - } - - @Override - public Set getListenedStateChanges() { - final Set result = new LinkedHashSet<>(); - /* - * Add only the state changes that are needed for this report. For each - * such state change there will need to be one corresponding handle() - * method added to this class. Both the StateChange and Report classes - * contain documentation on this relationship. - */ - result.add(StateChange.COMPARTMENT_ASSIGNMENT); - result.add(StateChange.GROUP_MEMBERSHIP_ADDITION); - result.add(StateChange.GROUP_MEMBERSHIP_REMOVAL); - result.add(StateChange.PERSON_ADDITION); - result.add(StateChange.PERSON_REMOVAL); - result.add(StateChange.PERSON_PROPERTY_VALUE_ASSIGNMENT); - result.add(StateChange.PERSON_RESOURCE_ADDITION); - result.add(StateChange.PERSON_RESOURCE_REMOVAL); - result.add(StateChange.PERSON_RESOURCE_TRANSFER_TO_REGION); - result.add(StateChange.REGION_RESOURCE_TRANSFER_TO_PERSON); - result.add(StateChange.REGION_ASSIGNMENT); - - return result; - } - - @Override - public void init(final ObservableEnvironment observableEnvironment, Set initialData) { - super.init(observableEnvironment, initialData); - /* - * This report is focused on the activities of a single person. The - * person id should be contained in the initial data. - */ - for (Object initialDatum : initialData) { - if (initialDatum instanceof PersonId) { - personId = (PersonId) initialDatum; - } - } - /* - * If we can't find a personId, assume it is person 0. - */ - if (personId == null) { - personId = new PersonId(0); - } - } - - private void writeReportItem(ObservableEnvironment observableEnvironment, Object activity, Object details) { - final ReportItemBuilder reportItemBuilder = new ReportItemBuilder(); - /* - * First add the standard parts of every report item: header, the report - * class reference, the scenario id and the replication id - */ - reportItemBuilder.setReportHeader(getReportHeader()); - reportItemBuilder.setReportType(getClass()); - reportItemBuilder.setScenarioId(observableEnvironment.getScenarioId()); - reportItemBuilder.setReplicationId(observableEnvironment.getReplicationId()); - - /* - * Now write the data that matches the header - */ - reportItemBuilder.addValue(personId); - reportItemBuilder.addValue(observableEnvironment.getTime()); - reportItemBuilder.addValue(activity); - reportItemBuilder.addValue(details); - - /* - * Finally, release the report item to the environment. The report item - * will be sent to NIOReportItemHandler and written to the appropriate - * file. - */ - observableEnvironment.releaseOutputItem(reportItemBuilder.build()); - } - - private boolean personIsEligible(PersonId personId) { - return this.personId.equals(personId); - } - - @Override - public void handleCompartmentAssignment(ObservableEnvironment observableEnvironment, final PersonId personId, final CompartmentId sourceCompartmentId) { - if (personIsEligible(personId)) { - CompartmentId personCompartmentId = observableEnvironment.getPersonCompartment(personId); - writeReportItem(observableEnvironment, "Compart Assignment", sourceCompartmentId + " --> " + personCompartmentId); - } - } - - @Override - public void handleRegionAssignment(ObservableEnvironment observableEnvironment, final PersonId personId, final RegionId sourceRegionId) { - if (personIsEligible(personId)) { - RegionId personRegionId = observableEnvironment.getPersonRegion(personId); - writeReportItem(observableEnvironment, "Region Assignment", sourceRegionId + " --> " + personRegionId); - } - } - - @Override - public void handleGroupMembershipAddition(ObservableEnvironment observableEnvironment, GroupId groupId, PersonId personId) { - if (personIsEligible(personId)) { - writeReportItem(observableEnvironment, "Joins Group", groupId); - } - } - - @Override - public void handleGroupMembershipRemoval(ObservableEnvironment observableEnvironment, GroupId groupId, PersonId personId) { - if (personIsEligible(personId)) { - writeReportItem(observableEnvironment, "Leaves Group", groupId); - } - } - - @Override - public void handlePersonAddition(ObservableEnvironment observableEnvironment, final PersonId personId) { - if (personIsEligible(personId)) { - writeReportItem(observableEnvironment, "Added to Simulation", personId); - } - } - - @Override - public void handlePersonRemoval(ObservableEnvironment observableEnvironment, PersonInfo personInfo) { - if (personIsEligible(personInfo.getPersonId())) { - writeReportItem(observableEnvironment, "Removed from Simulation", personInfo.getPersonId()); - } - } - - @Override - public void handlePersonPropertyValueAssignment(ObservableEnvironment observableEnvironment, final PersonId personId, final PersonPropertyId personPropertyId, - final Object oldPersonPropertyValue) { - if (personIsEligible(personId)) { - Object personPropertyValue = observableEnvironment.getPersonPropertyValue(personId, personPropertyId); - writeReportItem(observableEnvironment, "Property Value Update", personPropertyId + ": " + oldPersonPropertyValue + "-->" + personPropertyValue); - } - } - - @Override - public void handlePersonResourceAddition(ObservableEnvironment observableEnvironment, final PersonId personId, final ResourceId resourceId, final long amount) { - if (personIsEligible(personId)) { - writeReportItem(observableEnvironment, "Resource addition", resourceId + ": " + amount); - } - } - - @Override - public void handlePersonResourceRemoval(ObservableEnvironment observableEnvironment, final PersonId personId, final ResourceId resourceId, final long amount) { - if (personIsEligible(personId)) { - writeReportItem(observableEnvironment, "Resource removal", resourceId + ": " + amount); - } - } - - @Override - public void handleRegionResourceTransferToPerson(ObservableEnvironment observableEnvironment, final PersonId personId, final ResourceId resourceId, final long amount) { - if (personIsEligible(personId)) { - writeReportItem(observableEnvironment, "Resource transfer to person", resourceId + ": " + amount); - } - } - - @Override - public void handlePersonResourceTransferToRegion(ObservableEnvironment observableEnvironment, final PersonId personId, final ResourceId resourceId, final long amount) { - if (personIsEligible(personId)) { - writeReportItem(observableEnvironment, "Resource transfer from person", resourceId + ": " + amount); - } - } - -} diff --git a/gcm3/doc/presentations/2020-05-06 GCM Executive Summary.pptx b/gcm3/doc/presentations/2020-05-06 GCM Executive Summary.pptx deleted file mode 100644 index 8765c5d7b..000000000 Binary files a/gcm3/doc/presentations/2020-05-06 GCM Executive Summary.pptx and /dev/null differ diff --git a/gcm3/doc/presentations/2020-05-06 GCM Model Overview.pptx b/gcm3/doc/presentations/2020-05-06 GCM Model Overview.pptx deleted file mode 100644 index eed94bbb3..000000000 Binary files a/gcm3/doc/presentations/2020-05-06 GCM Model Overview.pptx and /dev/null differ diff --git a/gcm3/doc/presentations/2020-05-06 GCM Test Plan.pptx b/gcm3/doc/presentations/2020-05-06 GCM Test Plan.pptx deleted file mode 100644 index 6f6a09e01..000000000 Binary files a/gcm3/doc/presentations/2020-05-06 GCM Test Plan.pptx and /dev/null differ diff --git a/gcm3/pom.xml b/gcm3/pom.xml deleted file mode 100644 index 797454295..000000000 --- a/gcm3/pom.xml +++ /dev/null @@ -1,117 +0,0 @@ - - - - - 4.0.0 - - UTF-8 - - - - - ASPR - https://www.phe.gov - - - - gov.hhs.aspr - gcm3 - General Compartmental Model - jar - 4.0.0-SNAPSHOT - - - - - org.mockito - mockito-core - 3.11.2 - - - - org.apache.commons - commons-math3 - 3.6.1 - - - net.jcip - jcip-annotations - 1.0 - - - org.junit.jupiter - junit-jupiter-api - 5.7.0 - test - - - org.junit.jupiter - junit-jupiter-engine - 5.7.0 - test - - - - - - - Shawn Hatch - Leidos - https://www.leidos.com - - - Chris Ludka - Leidos - https://www.leidos.com - - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.8.1 - - 1.8 - 1.8 - - - - org.apache.maven.plugins - maven-source-plugin - 3.2.1 - - - attach-sources - verify - - jar-no-fork - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 3.0.0-M5 - - - - *.java - - - - - - - manual - - - - - - \ No newline at end of file diff --git a/gcm3/src/main/java/nucleus/ActorContext.java b/gcm3/src/main/java/nucleus/ActorContext.java deleted file mode 100644 index b5dcf380c..000000000 --- a/gcm3/src/main/java/nucleus/ActorContext.java +++ /dev/null @@ -1,208 +0,0 @@ -package nucleus; - -import java.util.List; -import java.util.Optional; -import java.util.function.BiConsumer; -import java.util.function.Consumer; - -import util.errors.ContractException; - -/** - * An actor context provides access to the nucleus engine and published data - * managers to actors. It is supplied by the engine each time it interacts with - * an actor. Actors are defined by this context. If this context is passed to a - * method invocation, then that method is an actor method. - * - * @author Shawn Hatch - * - */ - -public interface ActorContext extends SimulationContext { - - /** - * Schedules a plan that will be executed at the given time. - * - * @throws ContractException - *
  • {@link NucleusError#NULL_PLAN} if the plan is null - *
  • {@link NucleusError#PAST_PLANNING_TIME} if the plan is - * scheduled for a time in the past - * - * - * - * - */ - public void addPlan(Consumer plan, double planTime); - - /** - * Schedules a plan that will be executed at the given time. The plan is - * associated with the given key and can be canceled or retrieved via this - * key. Keys must be unique to the actor doing the planning, but can be - * repeated across actors and other planning entities. Use of keys with - * plans should be avoided unless retrieval or cancellation is needed. - * - * - * @throws ContractException - *
  • {@link NucleusError#NULL_PLAN} if the plan is null - *
  • {@link NucleusError#NULL_PLAN_KEY} if the plan key is - * null - *
  • {@link NucleusError#DUPLICATE_PLAN_KEY} if the key is - * already in use by an existing plan - *
  • {@link NucleusError#PAST_PLANNING_TIME} if the plan is - * scheduled for a time in the past - * - */ - public void addKeyedPlan(Consumer plan, double planTime, Object key); - - /** - * Schedules a plan that will be executed at the given time. Passive plans - * are not required to execute and the simulation will terminate if only - * passive plans remain on the planning schedule. - * - * @throws ContractException - *
  • {@link NucleusError#NULL_PLAN} if the plan is null - *
  • {@link NucleusError#PAST_PLANNING_TIME} if the plan is - * scheduled for a time in the past - * - * - * - * - */ - public void addPassivePlan(Consumer plan, double planTime); - - /** - * Schedules a plan that will be executed at the given time. The plan is - * associated with the given key and can be canceled or retrieved via this - * key. Keys must be unique to the actor doing the planning, but can be - * repeated across actors and other planning entities. Use of keys with - * plans should be avoided unless retrieval or cancellation is needed. - * Passive plans are not required to execute and the simulation will - * terminate if only passive plans remain on the planning schedule. - * - * - * @throws ContractException - *
  • {@link NucleusError#NULL_PLAN} if the plan is null - *
  • {@link NucleusError#NULL_PLAN_KEY} if the plan key is - * null - *
  • {@link NucleusError#DUPLICATE_PLAN_KEY} if the key is - * already in use by an existing plan - *
  • {@link NucleusError#PAST_PLANNING_TIME} if the plan is - * scheduled for a time in the past - * - */ - public void addPassiveKeyedPlan(Consumer plan, double planTime, Object key); - - /** - * Retrieves a plan stored for the given key. - * - * @throws ContractException - *
  • {@link NucleusError#NULL_PLAN_KEY} if the plan key is - * null - */ - public > Optional getPlan(final Object key); - - /** - * Returns the scheduled execution time for the plan associated with the - * given key - * - * @throws ContractException - *
  • {@link NucleusError#NULL_PLAN_KEY} if the plan key is - * null - */ - public Optional getPlanTime(final Object key); - - /** - * Removes and returns the plan associated with the given key. - * - * @throws ContractException - *
  • {@link NucleusError#NULL_PLAN_KEY} if the plan key is - * null - */ - public Optional removePlan(final Object key); - - /** - * Returns a list of the current plan keys associated with the current actor - * - */ - public List getPlanKeys(); - - /** - * Returns the ActorId of the current actor - */ - public ActorId getActorId(); - - /** - * Subscribes the current actor to the given event label. Events of the type - * T that are matched to the event label. If a match is found, then the - * event will be consumed by the supplied event consumer. - * - * - * @throws ContractException - *
  • {@link NucleusError#NULL_EVENT_LABEL} if the EventLabel - * is null - *
  • {@link NucleusError#NULL_EVENT_CONSUMER} if the - * ActorEventConsumer is null - *
  • {@link NucleusError#UNKNOWN_EVENT_LABELER} if the event - * labeler id in the event label cannot be resolved to a - * registered event labeler - * - * - */ - public void subscribe(EventLabel eventLabel, BiConsumer eventConsumer); - - /** - * Subscribes the current actor to the given event. - * - * - * @throws ContractException - *
  • {@link NucleusError#NULL_EVENT_CLASS} if the event class - * is null - *
  • {@link NucleusError#NULL_EVENT_CONSUMER} if the event - * consumer is null - * - */ - public void subscribe(Class eventClass, BiConsumer eventConsumer); - - /** - * Unsubscribes the current actor from the given event label. - * - * @throws ContractException - *
  • {@link NucleusError#NULL_EVENT_LABEL} if the EventLabel - * is null - *
  • {@link NucleusError#NULL_EVENT_CLASS_IN_EVENT_LABELER} if - * the event class in the event label is null - *
  • {@link NucleusError#NULL_LABELER_ID_IN_EVENT_LABEL} if - * the event labeler id in the event label is null - *
  • {@link NucleusError#UNKNOWN_EVENT_LABELER} if the event - * labeler id in the event label cannot be resolved to a - * registered event labeler - *
  • {@link NucleusError#NULL_PRIMARY_KEY_VALUE} if the event - * label has a null primary key - */ - public void unsubscribe(EventLabel eventLabel); - - /** - * Unsubscribes the actor from events of the given type. - * - * @throws ContractException - *
  • {@link NucleusError#NULL_EVENT_CLASS} if the event class - * is null
  • - */ - public void unsubscribe(Class eventClass); - - /** - * Registers the given consumer to be executed at the end of the simulation. - * Activity associated with the consumer should be limited to querying data - * state and releasing output. - * - * @throws ContractException - *
  • {@link NucleusError#NULL_ACTOR_CONTEXT_CONSUMER} if the - * consumer is null
  • - */ - public void subscribeToSimulationClose(Consumer consumer); - - /** - * Returns true if and only if there are actor or data managers subscribed - * to the given event type. - */ - public boolean subscribersExist(Class eventClass); -} diff --git a/gcm3/src/main/java/nucleus/DataManager.java b/gcm3/src/main/java/nucleus/DataManager.java deleted file mode 100644 index 90626e2dc..000000000 --- a/gcm3/src/main/java/nucleus/DataManager.java +++ /dev/null @@ -1,40 +0,0 @@ -package nucleus; - -import util.errors.ContractException; - -/** - * Base class for all data managers. - * - * - * @author Shawn Hatch - * - */ -public class DataManager { - /** - * Package access used by the simulation to help ensure that init() is - * invoked exactly once. - */ - boolean isInitialized() { - return initialized; - } - - private boolean initialized; - - /** - * Initializes the data manager. This method should only be invoked by the - * simulation. All data manager descendant classes that override this method - * must invoke the super. - * - * @throws ContractException - *
  • {@linkplain NucleusError#DATA_MANAGER_DUPLICATE_INITIALIZATION} - * if init() is invoked more than once
  • - * - */ - public void init(DataManagerContext dataManagerContext) { - if (initialized) { - throw new ContractException(NucleusError.DATA_MANAGER_DUPLICATE_INITIALIZATION); - } - initialized = true; - } - -} diff --git a/gcm3/src/main/java/nucleus/DataManagerContext.java b/gcm3/src/main/java/nucleus/DataManagerContext.java deleted file mode 100644 index 426b56187..000000000 --- a/gcm3/src/main/java/nucleus/DataManagerContext.java +++ /dev/null @@ -1,178 +0,0 @@ -package nucleus; - -import java.util.List; -import java.util.Optional; -import java.util.function.BiConsumer; -import java.util.function.Consumer; - -import util.errors.ContractException; - -/** - * A data manager context provides access to the nucleus engine and other data - * managers for data managers. It is supplied by the engine each time it - * interacts with a data manager. - * - * @author Shawn Hatch - * - */ - -public interface DataManagerContext extends SimulationContext { - - /** - * Schedules a plan that will be executed at the given time. - * - * @throws ContractException - *
  • {@link NucleusError#NULL_PLAN} if the plan is null - *
  • {@link NucleusError#PAST_PLANNING_TIME} if the plan is - * scheduled for a time in the past - * - * - */ - public void addPlan(Consumer plan, double planTime); - - /** - * Schedules a plan that will be executed at the given time. The plan is - * associated with the given key and can be canceled or retrieved via this - * key. Keys must be unique to the actor doing the planning, but can be - * repeated across actors and other planning entities. Use of keys with - * plans should be avoided unless retrieval or cancellation is needed. - * - * - * @throws ContractException - *
  • {@link NucleusError#NULL_PLAN} if the plan is null - *
  • {@link NucleusError#DUPLICATE_PLAN_KEY} if the key is - * already in use by an existing plan - *
  • {@link NucleusError#PAST_PLANNING_TIME} if the plan is - * scheduled for a time in the past - */ - public void addKeyedPlan(Consumer plan, double planTime, Object key); - - /** - * Schedules a plan that will be executed at the given time. Passive plans - * are not required to execute and the simulation will terminate if only - * passive plans remain on the planning schedule. - * - * @throws ContractException - *
  • {@link NucleusError#NULL_PLAN} if the plan is null - *
  • {@link NucleusError#PAST_PLANNING_TIME} if the plan is - * scheduled for a time in the past - * - * - */ - public void addPassivePlan(Consumer plan, double planTime); - - /** - * Schedules a plan that will be executed at the given time. The plan is - * associated with the given key and can be canceled or retrieved via this - * key. Keys must be unique to the actor doing the planning, but can be - * repeated across actors and other planning entities. Use of keys with - * plans should be avoided unless retrieval or cancellation is - * needed.Passive plans are not required to execute and the simulation will - * terminate if only passive plans remain on the planning schedule. - * - * - * @throws ContractException - *
  • {@link NucleusError#NULL_PLAN} if the plan is null - *
  • {@link NucleusError#DUPLICATE_PLAN_KEY} if the key is - * already in use by an existing plan - *
  • {@link NucleusError#PAST_PLANNING_TIME} if the plan is - * scheduled for a time in the past - */ - public void addPassiveKeyedPlan(Consumer plan, double planTime, Object key); - - /** - * Retrieves a plan for the given key. - * - * @throws ContractException - *
  • {@link NucleusError#NULL_PLAN_KEY} if the plan key is - * null - */ - public > Optional getPlan(final Object key); - - /** - * Returns the scheduled execution time for the plan associated with the - * given key - * - * @throws ContractException - *
  • {@link NucleusError#NULL_PLAN_KEY} if the plan key is - * null - */ - public Optional getPlanTime(final Object key); - - /** - * Removes and returns the plan associated with the given key. - * - * @throws ContractException - *
  • {@link NucleusError#NULL_PLAN_KEY} if the plan key is - * null - */ - public Optional removePlan(final Object key); - - /** - * Returns a list of the current plan keys associated with the current data - * manager - */ - public List getPlanKeys(); - - /** - * Subscribes the data manager to events of the given type for the purpose - * of execution of data changes. - * - * @throws ContractException - *
  • {@link NucleusError#NULL_EVENT_CLASS} if the event class - * is null - *
  • {@link NucleusError#NULL_EVENT_CONSUMER} if the event - * consumer is null - */ - public void subscribe(Class eventClass, BiConsumer eventConsumer); - - /** - * Subscribes the data manager to events of the given type for handling - * after the execution phase is complete. This should only be used for - * special cases where order of resolution is difficult to determine and the - * data manager needs to take action after all other data managers. - * Dependency ordering in this phase is indeterminate and thus should only - * be used when the data manager will not generate observation events that - * might be consumed by other data managers. - * - * @throws ContractException - *
  • {@link NucleusError#NULL_EVENT_CLASS} if the event class - * is null - *
  • {@link NucleusError#NULL_EVENT_CONSUMER} if the event - * consumer is null - */ - public void subscribePostOrder(Class eventClass, BiConsumer eventConsumer); - - /** - * Unsubscribes the data manager from events of the given type for all - * phases of event handling. - * - * @throws ContractException - *
  • {@link NucleusError#NULL_EVENT_CLASS} if the event class - * is null - */ - public void unSubscribe(Class eventClass); - - /** - * Returns true if and only if there are actor or data managers subscribed - * to the given event type. - */ - public boolean subscribersExist(Class eventClass); - - /** - * Returns the DataManagerId associated with this DataManagerContext - */ - public DataManagerId getDataManagerId(); - - /** - * Registers the given consumer to be executed at the end of the simulation. - * Activity associated with the consumer should be limited to querying data - * state and releasing output. - * - * @throws ContractException - *
  • {@link NucleusError#NULL_DATA_MANAGER_CONTEXT_CONSUMER} if the - * consumer is null
  • - */ - public void subscribeToSimulationClose(Consumer consumer); - -} diff --git a/gcm3/src/main/java/nucleus/Dimension.java b/gcm3/src/main/java/nucleus/Dimension.java deleted file mode 100644 index 3ef6bf36d..000000000 --- a/gcm3/src/main/java/nucleus/Dimension.java +++ /dev/null @@ -1,106 +0,0 @@ -package nucleus; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Function; - -/** - * A Dimension represents a single independent dimension of an experiment. - * Dimensions are composed of a finite number of levels. Each level in a - * dimension is a function that 1) consumes a type map of plugin data builders, - * 2) updates those builders to create alternate inputs for each scenario and 3) - * returns a list of strings representing the variant values in the scenario. - * - * @author Shawn Hatch - * - */ - -public final class Dimension { - - private static class Data { - List metaData = new ArrayList<>(); - List>> levels = new ArrayList<>(); - } - - /** - * Returns a builder class for Dimension - */ - public static Builder builder() { - return new Builder(); - } - - /** - * A builder class for Dimension - */ - - public static class Builder { - - private Data data = new Data(); - - private Builder() { - } - - /** - * Returns a Dimension from the collected data - */ - public Dimension build() { - try { - return new Dimension(data); - } finally { - data = new Data(); - } - } - - /** - * Adds a level function to the dimension. Each such function consumes a - * PluginDataBuilderContext of PluginDataBuilders and returns a list of scenario-level - * meta data that describes the changes performed on the - * PluginDataBuilders. The list of meta data is aligned to the - * experiment level meta data contained in the dimension and must - * contain the same number of elements. - */ - public Builder addLevel(Function> memberGenerator) { - data.levels.add(memberGenerator); - return this; - } - - /** - * Adds an experiment-level string meta datum value that describes the - * corresponding scenario-level meta data returned by the individual - * levels of this dimension. - */ - public Builder addMetaDatum(String idValue) { - data.metaData.add(idValue); - return this; - } - } - - private Dimension(Data data) { - this.data = data; - } - - private final Data data; - - /** - * Returns the meta data for the experiment that describes the - * scenario-level meta data. - */ - public List getMetaData() { - return new ArrayList<>(data.metaData); - } - - /** - * Returns the number of levels in this dimension - */ - public int size() { - return data.levels.size(); - } - - /** - * Returns the function(level) for the given index. Valid indexes are zero through size()-1 inclusive. - */ - public Function> getLevel(int index) { - return data.levels.get(index); - } - -} diff --git a/gcm3/src/main/java/nucleus/Event.java b/gcm3/src/main/java/nucleus/Event.java deleted file mode 100644 index 327897b21..000000000 --- a/gcm3/src/main/java/nucleus/Event.java +++ /dev/null @@ -1,24 +0,0 @@ -package nucleus; - -/** - * - * Represents an observable data change. Events can be produced by either actors - * or data managers. Both actors and data mangers can subscribe to events using - * various means. Data managers receive events immediately each time an event is - * released to a simulation context. Actors receive events only after the - * current actor and data managers have completed their current processing and - * before the next plan is processed from the simulation's planning queue. - * - * @author Shawn Hatch - * - */ -public interface Event { - /** - * Returns the non-null primary key value for the event. This value is used - * to reduce the run-time associated with the distribution of events. The - * default value is the class of this event. - */ - public default Object getPrimaryKeyValue() { - return getClass(); - } -} diff --git a/gcm3/src/main/java/nucleus/EventLabel.java b/gcm3/src/main/java/nucleus/EventLabel.java deleted file mode 100644 index 4391e50c8..000000000 --- a/gcm3/src/main/java/nucleus/EventLabel.java +++ /dev/null @@ -1,212 +0,0 @@ -package nucleus; - -import java.util.ArrayList; -import java.util.List; - -import net.jcip.annotations.NotThreadSafe; -import util.errors.ContractException; -import util.wrappers.MultiKey; - -/** - * - * A generics-based class that is used to filter event observations. - * - * When an actor subscribes to observe a particular type of event, it may need - * to filter such events. - * - * For example, suppose a fox(actor) has subscribed to actor movement events. - * The fox is only interested in rabbits that are close by and is unconcerned - * with distant rabbits or other animals. When the fox subscribes to movement - * event observation, it uses an event label to describe this filtering. When - * any animal moves, a movement observation event is generated by some plugin. - * Nucleus will then generate an event label from the event using a registered - * event-labeler. If this generated event label matches, via equality, the event - * label used by the fox, then the fox will receive the movement observation - * event. - * - * Event labels are paired with event labelers. A data manager registers an - * event labeler in anticipation of actors needing to use the corresponding - * event labels. Event labels remain active until the actor unsubscribes them. - * - * - * @author Shawn Hatch - * - * @param - */ -@NotThreadSafe -public final class EventLabel { - - private static class Data { - - private Class eventClass; - - private EventLabelerId labelerId; - - private final List keys = new ArrayList<>(); - } - - private final MultiKey multiKey; - - private final Class eventClass; - - private final EventLabelerId labelerId; - - private final Object primaryKeyValue; - - /** - * Returns a new instance of the Builder class - * - * @throws ContractException - * - *
  • {@linkplain NucleusError#NULL_EVENT_CLASS } if the class - * reference is null
  • - */ - public static Builder builder(Class classReference) { - if (classReference == null) { - throw new ContractException(NucleusError.NULL_EVENT_CLASS); - } - return new Builder(classReference); - } - - public static class Builder { - - private Data data = new Data<>(); - - private final Class eventClass; - - private Builder(Class classReference) { - this.eventClass = classReference; - } - - private void validate() { - if (data.keys.isEmpty()) { - throw new ContractException(NucleusError.NULL_PRIMARY_KEY_VALUE); - } - - if (data.labelerId == null) { - throw new ContractException(NucleusError.NULL_LABELER_ID_IN_EVENT_LABEL); - } - } - - /** - * Constructs a new EventLabel. - * - * @throws ContractException - *
  • {@linkplain NucleusError#NULL_PRIMARY_KEY_VALUE} if - * no keys were added
  • - *
  • {@linkplain NucleusError#NULL_LABELER_ID_IN_EVENT_LABEL} if no - * event labeler was set
  • - */ - public EventLabel build() { - try { - validate(); - data.eventClass = this.eventClass; - return new EventLabel<>(data); - } finally { - data = new Data<>(); - } - } - - /** - * Adds a key to the event label - * - * @throws ContractException - *
  • {@linkplain NucleusError#NULL_EVENT_LABEL_KEY} if the - * key is null
  • - */ - public Builder addKey(Object key) { - if (key == null) { - throw new ContractException(NucleusError.NULL_EVENT_LABEL_KEY); - } - data.keys.add(key); - return this; - } - - /** - * Sets the event labeler for the event label - * - * @throws ContractException - *
  • {@linkplain NucleusError#NULL_EVENT_LABELER_ID} if - * the labeler is null
  • - */ - public Builder setEventLabelerId(EventLabelerId eventLabelerId) { - if (eventLabelerId == null) { - throw new ContractException(NucleusError.NULL_EVENT_LABELER_ID); - } - data.labelerId = eventLabelerId; - return this; - } - - } - - private EventLabel(Data data) { - this.primaryKeyValue = data.keys.get(0); - this.eventClass = data.eventClass; - this.labelerId = data.labelerId; - MultiKey.Builder builder = MultiKey.builder(); - for (Object key : data.keys) { - builder.addKey(key); - } - multiKey = builder.build(); - } - - /** - * WARNING, NON-STANDARD EQUALS CONTRACT: Nucleus only checks for equality - * between event labels when those labels have the same primary keys, event - * class types and labeler ids. Thus, within the confines of nucleus, the - * equality contract can ignore these values to gain efficiency. - */ - @Override - public int hashCode() { - /* - * Justify the use of a non-standard approach to equals: this was done - * to gain efficiency, but should we use a correct implementation or - * force this to be the only implementation class of the event label? - */ - return multiKey.hashCode(); - } - - /** - * NOTE: Nucleus only checks for equality between event labels when those - * labels have the same primary keys, event class types and labeler ids. - */ - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof EventLabel)) { - return false; - } - @SuppressWarnings("rawtypes") - EventLabel other = (EventLabel) obj; - return multiKey.equals(other.multiKey); - } - - /** - * Returns the event subclass that this label applies to. - */ - public Class getEventClass() { - return eventClass; - } - - /** - * Returns the labeler id associated with this label. Labels can only be - * compared to other labels that share the same labeler id. - */ - public EventLabelerId getLabelerId() { - return labelerId; - } - - /** - * Returns the primary key value of any event that this label matches. This - * provides efficiency to the publication/subscription process and does not - * replace the equality comparison between labels. This label will only be - * matched to events that have the same primary key. - */ - public Object getPrimaryKeyValue() { - return primaryKeyValue; - } - -} diff --git a/gcm3/src/main/java/nucleus/EventLabeler.java b/gcm3/src/main/java/nucleus/EventLabeler.java deleted file mode 100644 index 71c33ed85..000000000 --- a/gcm3/src/main/java/nucleus/EventLabeler.java +++ /dev/null @@ -1,137 +0,0 @@ -package nucleus; - -import java.util.function.BiFunction; - -import util.errors.ContractException; - -/** - * A generics-based class that is used to filter event observations. - * - * See {@linkplain EventLabelX} for details. - * - * @author Shawn Hatch - * - * @param - */ -public final class EventLabeler { - - private static class Data { - private Class eventClass; - private BiFunction> labelFunction; - private EventLabelerId eventLabelerId; - } - - /** - * Returns an instance of the builder class that will build EventLabelers of - * the type specified by the class reference. - * - * @throws ContractException - *
  • {@linkplain NucleusError#NULL_EVENT_CLASS} if the class reference is null
  • - */ - public static Builder builder(Class classReference) { - if (classReference == null) { - throw new ContractException(NucleusError.NULL_EVENT_CLASS); - } - return new Builder(classReference); - } - - public static class Builder { - private final Class classReference; - - private Builder(Class classReference) { - this.classReference = classReference; - } - - private Data data = new Data<>(); - - private void validate() { - if (data.eventLabelerId == null) { - throw new ContractException(NucleusError.NULL_LABELER_ID_IN_EVENT_LABELER); - } - if (data.labelFunction == null) { - throw new ContractException(NucleusError.NULL_EVENT_LABEL_FUNCTION); - } - } - - /** - * Builds the event labeler from the given input - * - * @throws ContractException - *
  • {@linkplain NucleusError#NULL_LABELER_ID_IN_EVENT_LABELER} if the id is not set
  • - *
  • {@linkplain NucleusError#NULL_EVENT_LABEL_FUNCTION} if the label function
  • - * - */ - public EventLabeler build() { - try { - validate(); - data.eventClass = classReference; - return new EventLabeler<>(data); - } finally { - data = new Data<>(); - } - } - - /** - * Sets the label function - * - * @throws ContractException - *
  • {@linkplain NucleusError#NULL_EVENT_LABEL_FUNCTION} id the label function is null
  • - */ - public Builder setLabelFunction(BiFunction> labelFunction) { - if (labelFunction == null) { - throw new ContractException(NucleusError.NULL_EVENT_LABEL_FUNCTION); - } - data.labelFunction = labelFunction; - return this; - } - - /** - * Sets the event labeler id - * - * @throws ContractException - *
  • {@linkplain NucleusError#NULL_EVENT_LABELER_ID} if the event labeler id is null
  • - */ - public Builder setEventLabelerId(EventLabelerId eventLabelerId) { - if (eventLabelerId == null) { - throw new ContractException(NucleusError.NULL_EVENT_LABELER_ID); - } - data.eventLabelerId = eventLabelerId; - return this; - } - - } - - /** - * Constructs the event labeler from the given labeler id, event class and - * function for producing a label from an event. - */ - private final Data data; - - private EventLabeler(Data data) { - this.data = data; - } - - /** - * Returns the event class of T - */ - public final Class getEventClass() { - return data.eventClass; - } - - /** - * Returns an event label from a given event. This event label must 1)have - * the same labeler id as this labeler 2) have the same primary key as the - * event and 3) have the same event class as this labeler. - */ - public EventLabel getEventLabel(SimulationContext simulationContext, T event) { - return data.labelFunction.apply(simulationContext, event); - } - - /** - * Returns the unique id of this labeler - */ - public EventLabelerId getEventLabelerId() { - return data.eventLabelerId; - } - -} diff --git a/gcm3/src/main/java/nucleus/EventLabelerId.java b/gcm3/src/main/java/nucleus/EventLabelerId.java deleted file mode 100644 index b87ab458b..000000000 --- a/gcm3/src/main/java/nucleus/EventLabelerId.java +++ /dev/null @@ -1,11 +0,0 @@ -package nucleus; - -/** - * Marker interface for event labeler identification - * - * @author Shawn Hatch - * - */ -public interface EventLabelerId { - -} diff --git a/gcm3/src/main/java/nucleus/Experiment.java b/gcm3/src/main/java/nucleus/Experiment.java deleted file mode 100644 index 695609c90..000000000 --- a/gcm3/src/main/java/nucleus/Experiment.java +++ /dev/null @@ -1,548 +0,0 @@ -package nucleus; - -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.Callable; -import java.util.concurrent.CompletionService; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorCompletionService; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.function.Consumer; -import java.util.function.Function; - -import net.jcip.annotations.Immutable; -import util.errors.ContractException; - -/** - * An experiment provides a means for executing the simulation over variants of - * plugin data. Each such variant is referred to as a scenario. The scenarios - * correspond to the cross product of a finite number of dimensions, with each - * dimension having a finite number of variant levels. - * - * For example: An experiment contains several plugins and correspondingly - * several plugin data objects. The experiment has two dimensions. The first - * dimension varies one of the plugin data objects with 5 new values. The second - * dimension varies two values in two separate plugin data objects with 3 new - * values each. There will be 15 resulting scenarios numbered 0 to 14 - * corresponding to each combination of altered inputs. - * - * The experiment then executes the scenarios concurrently based on the number - * of threads chosen for the execution. - * - * @author Shawn Hatch - * - */ - -public final class Experiment { - - public static class Builder { - private Data data = new Data(); - - private Builder() { - } - - /** - * Adds a dimension to the experiment - */ - public Builder addDimension(final Dimension dimension) { - data.dimensions.add(dimension); - return this; - } - - /** - * Adds the output item handler to the experiment. Consumers of - * experiment context must be thread safe. - * - * @throws ContractException - *
  • {@linkplain NucleusError#NULL_OUTPUT_HANDLER} if the - * output item handler is null
  • - */ - public Builder addOutputHandler(final Consumer experimentContextConsumer) { - if (experimentContextConsumer == null) { - throw new ContractException(NucleusError.NULL_OUTPUT_HANDLER); - } - data.experimentContextConsumers.add(experimentContextConsumer); - return this; - } - - /** - * Adds a plugin to the experiment. - */ - public Builder addPlugin(final Plugin plugin) { - data.plugins.add(plugin); - return this; - } - - /** - * Builds an experiment from the collected plugins, dimensions and - * output handlers. - */ - public Experiment build() { - try { - return new Experiment(data); - } finally { - data = new Data(); - } - } - - /** - * Turns on or off the logging of experiment progress to standard out. - * Default value is true. - * - */ - public Builder reportProgressToConsole(final boolean reportProgressToConsole) { - data.reportProgressToConsole = reportProgressToConsole; - return this; - } - - /** - * Sets the path for experiment progress log. A null path turns off - * logging and run resumption. Default value is null. - */ - public Builder setExperimentProgressLog(final Path path) { - data.experimentProgressLogPath = path; - return this; - } - - /** - * Instructs the experiment to continue experiment progress from the - * experiment progress log. Defaults to false; - * - */ - public Builder setContinueFromProgressLog(boolean continueFromProgressLog) { - data.continueFromProgressLog = continueFromProgressLog; - return this; - } - - /** - * Sets the number of scenarios that may run concurrently. Generally - * this should be set to one less than the number of virtual processors - * on the machine that is running the experiment. Setting the thread - * count to zero causes the simulations to execute in the main thread. - * - * @throws ContractException - *
  • {@linkplain NucleusError#NEGATIVE_THREAD_COUNT} if - * the thread count is negative
  • - * - * - */ - public Builder setThreadCount(final int threadCount) { - if (threadCount < 0) { - throw new ContractException(NucleusError.NEGATIVE_THREAD_COUNT); - } - data.threadCount = threadCount; - return this; - } - - /** - * Sets the policy on reporting scenario failures. Defaults to true. - */ - public Builder reportFailuresToConsole(final boolean reportFailuresToConsole) { - data.reportFailuresToConsole = reportFailuresToConsole; - return this; - } - - } - - /* - * A data class for holding the inputs to this builder from its client. - */ - private static class Data { - private final List dimensions = new ArrayList<>(); - private final List plugins = new ArrayList<>(); - private final List> experimentContextConsumers = new ArrayList<>(); - private int threadCount; - private boolean reportFailuresToConsole = true; - private boolean reportProgressToConsole = true; - private Path experimentProgressLogPath; - private boolean continueFromProgressLog; - - } - - /* - * The result of the execution of a SimulationCallable - */ - @Immutable - private static class SimResult { - private final boolean success; - private final int scenarioId; - private final Exception failureCause; - - public SimResult(final int scenarioId, final boolean success, Exception failureCause) { - this.scenarioId = scenarioId; - this.success = success; - this.failureCause = failureCause; - } - } - - /* - * A Callable implementor that runs the simulation in a thread from a - * completion service. - */ - private static class SimulationCallable implements Callable { - private final ExperimentStateManager experimentStateManager; - private final List plugins; - private final Integer scenarioId; - private final boolean reportScenarioFailureToConsole; - - /* - * All construction arguments are thread safe implementations. - */ - private SimulationCallable(final Integer scenarioId, final ExperimentStateManager experimentStateManager, final List plugins, final boolean reportScenarioFailureToConsole) { - this.scenarioId = scenarioId; - this.experimentStateManager = experimentStateManager; - this.plugins = new ArrayList<>(plugins); - this.reportScenarioFailureToConsole = reportScenarioFailureToConsole; - } - - /** - * Executes the simulation for a scenario. Returns a SimResult - * indicating success/failure. If the simulation throws an exception it - * is handled by printing a stack trace and reports a failure for the - * scenario. - */ - @Override - public SimResult call() throws Exception { - - final Simulation.Builder simBuilder = Simulation.builder(); - - // Load the plugins into the simulation builder - for (final Plugin plugin : plugins) { - simBuilder.addPlugin(plugin); - } - - // direct output from the simulation to the subscribed consumers - simBuilder.setOutputConsumer(experimentStateManager.getOutputConsumer(scenarioId)); - - // build the simulation - final Simulation simulation = simBuilder.build(); - - // run the simulation - boolean success = false; - Exception failureCause = null; - try { - simulation.execute(); - success = true; - } catch (final Exception e) { - failureCause = e; - if (reportScenarioFailureToConsole) { - System.err.println("Simulation failure for scenario " + scenarioId); - e.printStackTrace(); - } - } - return new SimResult(scenarioId, success, failureCause); - } - - } - - /** - * Returns a builder for Experiment - */ - public static Builder builder() { - return new Builder(); - } - - private final Data data; - - private ExperimentStateManager experimentStateManager; - - private Experiment(final Data data) { - this.data = data; - } - - /** - * Executes the experiment using the information collected by the builder. - * - */ - public void execute() { - - int scenarioCount = 1; - for (final Dimension dimension : data.dimensions) { - scenarioCount *= dimension.size(); - } - - ExperimentStateManager.Builder builder = ExperimentStateManager.builder(); - builder.setScenarioCount(scenarioCount); - builder.setContinueFromProgressLog(data.continueFromProgressLog); - builder.setScenarioProgressLogFile(data.experimentProgressLogPath); - - final List experimentMetaData = new ArrayList<>(); - for (final Dimension dimension : data.dimensions) { - experimentMetaData.addAll(dimension.getMetaData()); - } - - builder.setExperimentMetaData(experimentMetaData); - - if (data.reportProgressToConsole) { - data.experimentContextConsumers.add(new ExperimentStatusConsole()::init); - } - - // initialize the experiment context consumers so that they can - // subscribe to experiment level events - for (final Consumer consumer : data.experimentContextConsumers) { - builder.addExperimentContextConsumer(consumer); - } - - experimentStateManager = builder.build(); - // announce to consumers that the experiment is starting - experimentStateManager.openExperiment(); - - if (data.threadCount > 0) { - executeMultiThreaded(); - } else { - executeSingleThreaded(); - } - - // announce to consumers that the experiment has ended - experimentStateManager.closeExperiment(); - - } - - /* - * Executes the experiment utilizing multiple threads. If the simulation - * throws an exception it is caught and handled by reporting to standard - * error that the failure occured as well as printing a stack trace. - */ - private void executeMultiThreaded() { - - // Determine the scenarios - final List jobs = new ArrayList<>(); - - final int scenarioCount = experimentStateManager.getScenarioCount(); - - for (int scenarioId = 0; scenarioId < scenarioCount; scenarioId++) { - final ScenarioStatus scenarioStatus = experimentStateManager.getScenarioStatus(scenarioId).get(); - if (scenarioStatus == ScenarioStatus.READY) { - jobs.add(scenarioId); - } - } - - /* - * If there is nothing to do, then do not engage. - */ - if (jobs.isEmpty()) { - return; - } - - int jobIndex = 0; - - // Create the Completion Service using the suggested thread - // count - final ExecutorService executorService = Executors.newFixedThreadPool(data.threadCount); - final CompletionService completionService = new ExecutorCompletionService<>(executorService); - - /* - * Start the initial threads. Don't exceed the thread count or the job - * count. Each time a thread is cleared, a new simulation will be - * processed through the CompletionService until we run out of - * simulations to run. - */ - while (jobIndex < (Math.min(data.threadCount, jobs.size()) - 1)) { - final Integer scenarioId = jobs.get(jobIndex); - List plugins = preSimActions(scenarioId); - completionService.submit(new SimulationCallable(scenarioId, experimentStateManager, plugins, data.reportFailuresToConsole)); - jobIndex++; - } - - /* - * While there are still jobs to be assigned to a thread, or jobs that - * have not yet completed processing, we check to see if a new job needs - * processing and see if a previous job has completed. - */ - int jobCompletionCount = 0; - while (jobCompletionCount < jobs.size()) { - if (jobIndex < jobs.size()) { - final Integer scenarioId = jobs.get(jobIndex); - List plugins = preSimActions(scenarioId); - completionService.submit(new SimulationCallable(scenarioId, experimentStateManager, plugins, data.reportFailuresToConsole)); - jobIndex++; - } - - /* - * This call is blocking and waits for a job to complete and a - * thread to clear. - */ - try { - final SimResult simResult = completionService.take().get(); - if (simResult.success) { - experimentStateManager.closeScenarioAsSuccess(simResult.scenarioId); - } else { - experimentStateManager.closeScenarioAsFailure(simResult.scenarioId, simResult.failureCause); - } - } catch (final InterruptedException | ExecutionException e) { - // Note that this is the completion service failing and - // not the simulation - - //re-thrown as runtime exception - throw new RuntimeException(e); - } - - /* - * Once the blocking call returns, we increment the - * jobCompletionCount - */ - jobCompletionCount++; - } - - /* - * Since all jobs are done, the CompletionService is no longer needed so - * we shut down the executorService that backs it. - */ - executorService.shutdown(); - - } - - /* - * Executes the experiment using the main thread. If the simulation throws - * an exception it is caught and handled by reporting to standard error that - * the failure occurred as well as printing a stack trace. - */ - private void executeSingleThreaded() { - - // Execute each scenario - final int scenarioCount = experimentStateManager.getScenarioCount(); - final Simulation.Builder simBuilder = Simulation.builder(); - for (int scenarioId = 0; scenarioId < scenarioCount; scenarioId++) { - final ScenarioStatus scenarioStatus = experimentStateManager.getScenarioStatus(scenarioId).get(); - if (scenarioStatus != ScenarioStatus.READY) { - continue; - } - - // generate the plugins that will form the simulation for the given - // scenario id - final List plugins = preSimActions(scenarioId); - - // Load the plugin behaviors into the simulation builder - for (final Plugin plugin : plugins) { - simBuilder.addPlugin(plugin); - } - - // direct output from the simulation to the subscribed consumers - simBuilder.setOutputConsumer(experimentStateManager.getOutputConsumer(scenarioId)); - - // build the simulation - final Simulation simulation = simBuilder.build(); - - // run the simulation - boolean success = false; - Exception failureCause = null; - try { - simulation.execute(); - success = true; - } catch (final Exception e) { - failureCause = e; - if (data.reportFailuresToConsole) { - System.err.println("Simulation failure for scenario " + scenarioId); - e.printStackTrace(); - } - } - - if (success) { - experimentStateManager.closeScenarioAsSuccess(scenarioId); - }else { - experimentStateManager.closeScenarioAsFailure(scenarioId, failureCause); - } - - } - } - - private List preSimActions(final int scenarioId) { - /* - * Build the type map of the clone plugin data builders from the plugins - * supplied to the dimensions of the experiment - */ - - final PluginDataBuilderContext.Builder contextBuilder = PluginDataBuilderContext.builder(); - - /* - * Set up a map that will allow us to associate each data builder with - * the plugin that should own that data - */ - Map humptyMap = new LinkedHashMap<>(); - - for (final Plugin plugin : data.plugins) { - for (final PluginData pluginData : plugin.getPluginDatas()) { - PluginDataBuilder pluginDataBuilder = pluginData.getCloneBuilder(); - humptyMap.put(pluginDataBuilder, plugin.getPluginId()); - contextBuilder.add(pluginDataBuilder); - } - } - final PluginDataBuilderContext pluginDataBuilderContext = contextBuilder.build(); - - // initialize the scenario meta data - final List scenarioMetaData = new ArrayList<>(); - - /* - * From the scenario id select the functions from each dimension. Have - * the functions mutate the plugin builders and return meta data. - */ - int modulus = 1; - for (final Dimension dimension : data.dimensions) { - /* - * Determine for the dimension the index within the dimension that - * corresponds to the scenario id - */ - final int index = (scenarioId / modulus) % dimension.size(); - modulus *= dimension.size(); - - // get the function from the dimension - final Function> memberGenerator = dimension.getLevel(index); - - // apply the function that will update the plugin builders and - // return the meta data for this function - scenarioMetaData.addAll(memberGenerator.apply(pluginDataBuilderContext)); - - } - - // update the experiment state manager with the meta data for the - // scenario - experimentStateManager.openScenario(scenarioId, scenarioMetaData); - - /* - * Rebuild the plugins. - */ - - // First, copy each plugin, excluding the plugin data items. - Map dumptyMap = new LinkedHashMap<>(); - for (final Plugin plugin : data.plugins) { - Plugin.Builder pluginBuilder = Plugin.builder(); - dumptyMap.put(plugin.getPluginId(), pluginBuilder); - pluginBuilder.setPluginId(plugin.getPluginId()); - for (PluginId pluginId : plugin.getPluginDependencies()) { - pluginBuilder.addPluginDependency(pluginId); - } - Optional> optionalInitializer = plugin.getInitializer(); - if (optionalInitializer.isPresent()) { - pluginBuilder.setInitializer(optionalInitializer.get()); - } - } - - // Get the plugin data builders and create the new plugin datas, - // associating each with the correct plugin. The plugin datas should be - // added in the order that they were in in the original plugins - for (final PluginDataBuilder pluginDataBuilder : pluginDataBuilderContext.getContents()) { - final PluginData pluginData = pluginDataBuilder.build(); - PluginId pluginId = humptyMap.get(pluginDataBuilder); - Plugin.Builder pluginBuilder = dumptyMap.get(pluginId); - pluginBuilder.addPluginData(pluginData); - } - - /* - * Construct the new plugins from the plugin builders - */ - final List result = new ArrayList<>(); - - for (Plugin.Builder plugingBuilder : dumptyMap.values()) { - result.add(plugingBuilder.build()); - } - - return result; - - } - -} diff --git a/gcm3/src/main/java/nucleus/ExperimentContext.java b/gcm3/src/main/java/nucleus/ExperimentContext.java deleted file mode 100644 index 7ffc5b568..000000000 --- a/gcm3/src/main/java/nucleus/ExperimentContext.java +++ /dev/null @@ -1,128 +0,0 @@ -package nucleus; - -import java.util.List; - -import java.util.Optional; -import java.util.function.BiConsumer; -import java.util.function.Consumer; - -import net.jcip.annotations.ThreadSafe; -import nucleus.util.TriConsumer; -import util.errors.ContractException; - -/** - * Interface for the thread safe access of experiment/scenario state and meta - * data. This context provides subscription for the five key events that occur - * outside of the simulation; 1) Experiment Open, 2) Scenario Open, 3) Scenario - * Output, 4) Scenario Close and 5) Experiment Close - */ - -@ThreadSafe -public interface ExperimentContext { - - /** - * Subscribes to the open of simulations - * - * @throws ContractException - * - *
  • {@link NucleusError.NULL_EXPERIMENT_CONTEXT_CONSUMER} if - * the consumer is null
  • - */ - public void subscribeToSimulationOpen(BiConsumer consumer); - - /** - * Subscribes to the close of simulations - * - * @throws ContractException - * - *
  • {@link NucleusError.NULL_EXPERIMENT_CONTEXT_CONSUMER} if - * the consumer is null
  • - */ - - public void subscribeToSimulationClose(BiConsumer consumer); - - /** - * Subscribes to the open of the experiment - * - * @throws ContractException - * - *
  • {@link NucleusError.NULL_EXPERIMENT_CONTEXT_CONSUMER} if - * the consumer is null
  • - */ - public void subscribeToExperimentOpen(Consumer consumer); - - /** - * Subscribes to the close of the experiment - * - * @throws ContractException - * - *
  • {@link NucleusError.NULL_EXPERIMENT_CONTEXT_CONSUMER} if - * the consumer is null
  • - */ - public void subscribeToExperimentClose(Consumer consumer); - - /** - * Subscribes the output handler to output of the given type. - * - * @throws ContractException - * - * - *
  • {@link NucleusError#NULL_EXPERIMENT_CONTEXT_CONSUMER} if - * the consumer is null
  • - *
  • {@link NucleusError#NULL_EXPERIMENT_CONTEXT_CONSUMER} if - * the consumer is null
  • - * - * - * - */ - public void subscribeToOutput(Class outputClass, TriConsumer consumer); - - /** - * - * Returns the current status for the given scenario id if the scenario - * exists. - * - */ - public Optional getScenarioStatus(int scenarioId); - - /** - * - * Returns the current number of scenarios with the given status - * - */ - public int getStatusCount(ScenarioStatus scenarioStatus); - - /** - * Returns the number of seconds that have elapsed since the start of the - * experimnet - */ - public double getElapsedSeconds(); - - /** - * Returns the list of meta data for the given scenario if it is available. - */ - public Optional> getScenarioMetaData(int scenarioId); - - /** - * Returns the list of meta data for the experiment. These meta data are - * descriptors of the scenario meta data produced by each execution of the - * simulation. - */ - public List getExperimentMetaData(); - - /** - * Returns the number of scenarios in the experiment - */ - public int getScenarioCount(); - - /** - * Returns the current list of scenario ids for the given scenario status - */ - public List getScenarios(ScenarioStatus scenarioStatus); - - /** - * Returns the exception associated with a failed sceario - */ - public Optional getScenarioFailureCause(int scenarioId); - -} diff --git a/gcm3/src/main/java/nucleus/ExperimentStateManager.java b/gcm3/src/main/java/nucleus/ExperimentStateManager.java deleted file mode 100644 index 04ffa00eb..000000000 --- a/gcm3/src/main/java/nucleus/ExperimentStateManager.java +++ /dev/null @@ -1,810 +0,0 @@ -package nucleus; - -import java.io.BufferedWriter; -import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.nio.charset.CharsetEncoder; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.function.BiConsumer; -import java.util.function.Consumer; - -import net.jcip.annotations.NotThreadSafe; -import net.jcip.annotations.ThreadSafe; -import nucleus.util.TriConsumer; -import util.errors.ContractException; -import util.time.TimeElapser; - -/** - * A utility class used by the experiment to manage experiment context consumers - * and ensure thread safe access to scenario related state. - * - * @author Shawn Hatch - * - */ -@ThreadSafe -public final class ExperimentStateManager { - - private ExperimentStatus experimentStatus = ExperimentStatus.READY; - - private static enum ExperimentStatus { - READY, OPENED, CLOSED; - } - - private ExperimentContext experimentContext; - - private class ExperimentContextImpl implements ExperimentContext { - - @Override - public void subscribeToSimulationOpen(BiConsumer consumer) { - ExperimentStateManager.this.subscribeToSimulationOpen(consumer); - - } - - @Override - public void subscribeToSimulationClose(BiConsumer consumer) { - ExperimentStateManager.this.subscribeToSimulationClose(consumer); - } - - @Override - public void subscribeToExperimentOpen(Consumer consumer) { - ExperimentStateManager.this.subscribeToExperimentOpen(consumer); - } - - @Override - public void subscribeToExperimentClose(Consumer consumer) { - ExperimentStateManager.this.subscribeToExperimentClose(consumer); - } - - @Override - public void subscribeToOutput(Class outputClass, TriConsumer consumer) { - ExperimentStateManager.this.subscribeToOutput(outputClass, consumer); - } - - @Override - public Optional> getScenarioMetaData(int scenarioId) { - return ExperimentStateManager.this.getScenarioMetaData(scenarioId); - } - - @Override - public List getExperimentMetaData() { - return ExperimentStateManager.this.getExperimentMetaData(); - } - - @Override - public int getScenarioCount() { - return ExperimentStateManager.this.getScenarioCount(); - } - - @Override - public Optional getScenarioStatus(int scenarioId) { - return ExperimentStateManager.this.getScenarioStatus(scenarioId); - } - - @Override - public int getStatusCount(ScenarioStatus scenarioStatus) { - return ExperimentStateManager.this.getStatusCount(scenarioStatus); - } - - @Override - public double getElapsedSeconds() { - return ExperimentStateManager.this.getElapsedSeconds(); - } - - @Override - public List getScenarios(ScenarioStatus scenarioStatus) { - return ExperimentStateManager.this.getScenarios(scenarioStatus); - } - - @Override - public Optional getScenarioFailureCause(int scenarioId) { - return ExperimentStateManager.this.getScenarioFailureCause(scenarioId); - } - - } - - private TimeElapser timeElapser = new TimeElapser(); - - private Map scenarioRecords = new LinkedHashMap<>(); - - /** - * Updates the scenario's status. Announces the opening of the scenario to - * subscribed experiment context consumers. - * - * @throws ContractException - *
  • {@linkplain NucleusError#NULL_SCENARIO_ID}if the scenario - * id is null
  • - *
  • {@linkplain NucleusError#NULL_META_DATA}if the meta data - * is null
  • - *
  • {@linkplain NucleusError#NULL_META_DATA}if the meta data - * contains a null datum
  • - *
  • {@linkplain NucleusError#UNKNOWN_SCENARIO_ID}if the - * scenario is not known
  • - *
  • {@linkplain NucleusError#SCENARIO_CANNOT_BE_EXECUTED}if - * the scenario's current status is not - * {@linkplain Scenario#READY}
  • - */ - public synchronized void openScenario(Integer scenarioId, List metaData) { - - ScenarioRecord scenarioRecord = scenarioRecords.get(scenarioId); - if (scenarioRecord == null) { - throw new ContractException(NucleusError.UNKNOWN_SCENARIO_ID, scenarioId); - } - - if (scenarioRecord.scenarioStatus != ScenarioStatus.READY) { - throw new ContractException(NucleusError.SCENARIO_CANNOT_BE_EXECUTED, "scenario " + scenarioId + " " + scenarioRecord.scenarioStatus); - } - - if (metaData == null) { - throw new ContractException(NucleusError.NULL_META_DATA); - } - for (String metaDatum : metaData) { - if (metaDatum == null) { - throw new ContractException(NucleusError.NULL_META_DATA); - } - } - - scenarioRecord.scenarioStatus = ScenarioStatus.RUNNING; - scenarioRecord.metaData.addAll(metaData); - - for (BiConsumer consumer : simOpenConsumers) { - consumer.accept(experimentContext, scenarioId); - } - } - - private static final String LINE_SEPARATOR = System.getProperty("line.separator"); - - /** - * Announces the closure of the scenario to subscribed experiment context - * consumers. Records the scenario in the scenario progress file if the file - * is active and the scenario was successful(i.e. the simulation executed - * without throwing an exception). - * - * @throws ContractException - *
  • {@linkplain NucleusError#NULL_SCENARIO_ID} if the - * scenario id is null
  • - *
  • {@linkplain NucleusError#UNKNOWN_SCENARIO_ID} if the - * scenario id is not in the range [0,scenario count)
  • - */ - public synchronized void closeScenarioAsSuccess(Integer scenarioId) { - - if (scenarioId == null) { - throw new ContractException(NucleusError.NULL_SCENARIO_ID); - } - - ScenarioRecord scenarioRecord = scenarioRecords.get(scenarioId); - - if (scenarioRecord == null) { - throw new ContractException(NucleusError.UNKNOWN_SCENARIO_ID); - } - - scenarioRecord.scenarioStatus = ScenarioStatus.SUCCEDED; - - for (BiConsumer consumer : simCloseConsumers) { - consumer.accept(experimentContext, scenarioId); - } - - if (writer != null) { - try { - writer.write(scenarioId.toString()); - for (String metaDatum : scenarioRecord.metaData) { - writer.write("\t"); - writer.write(metaDatum); - } - writer.write(LINE_SEPARATOR); - writer.flush(); - } catch (final IOException e) { - // re-thrown as runtime exception - throw new RuntimeException(e); - } - } - } - - /** - * Announces the closure of the scenario to subscribed experiment context - * consumers. Records the scenario in the scenario progress file if the file - * is active and the scenario was successful(i.e. the simulation executed - * without throwing an exception). - * - * @throws ContractException - *
  • {@linkplain NucleusError#NULL_SCENARIO_ID} if the - * scenario id is null
  • - *
  • {@linkplain NucleusError#UNKNOWN_SCENARIO_ID} if the - * scenario id is not in the range [0,scenario count)
  • - */ - public synchronized void closeScenarioAsFailure(Integer scenarioId, Exception exception) { - - if (scenarioId == null) { - throw new ContractException(NucleusError.NULL_SCENARIO_ID); - } - - ScenarioRecord scenarioRecord = scenarioRecords.get(scenarioId); - - if (scenarioRecord == null) { - throw new ContractException(NucleusError.UNKNOWN_SCENARIO_ID); - } - - scenarioRecord.scenarioStatus = ScenarioStatus.FAILED; - scenarioRecord.failureCause = exception; - - for (BiConsumer consumer : simCloseConsumers) { - consumer.accept(experimentContext, scenarioId); - } - - } - - /** - * - * Returns the current status for the given scenario id if the scenario - * exists. - * - */ - public synchronized Optional getScenarioStatus(int scenarioId) { - ScenarioRecord scenarioRecord = scenarioRecords.get(scenarioId); - ScenarioStatus result = null; - if (scenarioRecord != null) { - result = scenarioRecord.scenarioStatus; - } - return Optional.ofNullable(result); - } - - public synchronized Optional getScenarioFailureCause(int scenarioId) { - ScenarioRecord scenarioRecord = scenarioRecords.get(scenarioId); - Exception result = null; - if (scenarioRecord != null) { - result = scenarioRecord.failureCause; - } - return Optional.ofNullable(result); - } - - /** - * - * Returns the current number of scenarios with the given status - * - */ - public synchronized int getStatusCount(ScenarioStatus scenarioStatus) { - int result = 0; - for (Integer scenarioId : scenarioRecords.keySet()) { - ScenarioRecord scenarioRecord = scenarioRecords.get(scenarioId); - if (scenarioRecord.scenarioStatus == scenarioStatus) { - result++; - } - } - return result; - } - - /** - * Returns the list of meta data for the experiment. These meta data are - * descriptors of the scenario meta data produced by each execution of the - * simulation. - */ - public synchronized List getExperimentMetaData() { - return new ArrayList<>(data.experimentMetaData); - } - - /** - * Returns the list of meta data for the given scenario if it is available. - */ - public synchronized Optional> getScenarioMetaData(Integer scenarioId) { - ScenarioRecord scenarioRecord = scenarioRecords.get(scenarioId); - List result = null; - if (scenarioRecord != null) { - result = new ArrayList<>(scenarioRecord.metaData); - } - return Optional.ofNullable(result); - } - - /** - * Returns the current list of scenario ids for the given scenario status - */ - public synchronized List getScenarios(ScenarioStatus scenarioStatus) { - List result = new ArrayList<>(); - for (Integer scenarioId : scenarioRecords.keySet()) { - ScenarioRecord scenarioRecord = scenarioRecords.get(scenarioId); - if (scenarioRecord.scenarioStatus == scenarioStatus) { - result.add(scenarioId); - } - } - return result; - } - - private List> simOpenConsumers = new ArrayList<>(); - - private List> simCloseConsumers = new ArrayList<>(); - - private List> experimentOpenConsumers = new ArrayList<>(); - - private List> experimentCloseConsumers = new ArrayList<>(); - - private Map, Set>> outputConsumerMap = new LinkedHashMap<>(); - - private static class MetaOutputConsumer { - private MetaOutputConsumer(TriConsumer consumer) { - this.consumer = consumer; - } - - private TriConsumer consumer; - - @SuppressWarnings("unchecked") - public void accept(ExperimentContext experimentContext, Integer scenarioId, Object object) { - consumer.accept(experimentContext, scenarioId, (T) object); - } - } - - /** - * Returns the number of seconds that have elapsed since the start of the - * experiment - */ - public synchronized double getElapsedSeconds() { - return timeElapser.getElapsedSeconds(); - } - - /** - * Returns the number of scenarios in the experiment - */ - public synchronized int getScenarioCount() { - return data.scenarioCount; - } - - private static class ScenarioRecord { - private ScenarioStatus scenarioStatus; - private List metaData = new ArrayList<>(); - private Exception failureCause; - } - - private static class Data { - private Integer scenarioCount; - private Path progressLogFile; - private List experimentMetaData = new ArrayList<>(); - private List> contextConsumers = new ArrayList<>(); - private boolean continueFromProgressLog; - } - - /** - * Returns a new Builder instance for the ExperimentStateManager - */ - public static Builder builder() { - return new Builder(); - } - - /** - * Builder class for ExperimentStateManager - * - * @author Shawn Hatch - * - */ - public static class Builder { - private Data data = new Data(); - - private Builder() { - - } - - public ExperimentStateManager build() { - try { - return new ExperimentStateManager(data); - } finally { - data = new Data(); - } - } - - /** - * Sets the scenario count as calculated from the dimensions. - * - */ - public Builder setScenarioCount(Integer scenarioCount) { - data.scenarioCount = scenarioCount; - return this; - } - - /** - * Sets the file that is used to report scenario progress. - */ - public Builder setScenarioProgressLogFile(Path progressLogFile) { - data.progressLogFile = progressLogFile; - return this; - } - - /** - * Sets the list of string meta data for the experiment. These meta data - * are descriptors of the scenario meta data produced by each execution - * of the simulation. - * - * @throws ContractException - *
  • {@linkplain NucleusError#NULL_META_DATA} if the - * experiment meta data is null
  • - * - *
  • {@linkplain NucleusError#NULL_META_DATA} if the - * experiment meta data is null
  • - * - */ - public Builder setExperimentMetaData(List experimentMetaData) { - if (experimentMetaData == null) { - throw new ContractException(NucleusError.NULL_META_DATA); - } - for (String metaDatum : experimentMetaData) { - if (metaDatum == null) { - throw new ContractException(NucleusError.NULL_META_DATA); - } - } - data.experimentMetaData = new ArrayList<>(experimentMetaData); - return this; - } - - /** - * Adds a experiment context consumer that will be initialized at the - * start of the experiment. - * - * @throws ContractException - *
  • {@linkplain NucleusError#NULL_EXPERIMENT_CONTEXT_CONSUMER} - * if the context consumer is null
  • - */ - public Builder addExperimentContextConsumer(Consumer contextConsumer) { - if (contextConsumer == null) { - throw new ContractException(NucleusError.NULL_EXPERIMENT_CONTEXT_CONSUMER); - } - data.contextConsumers.add(contextConsumer); - return this; - } - - /** - * Sets the option to continue the current experiment from the progress. - */ - public Builder setContinueFromProgressLog(boolean continueFromProgressLog) { - data.continueFromProgressLog = continueFromProgressLog; - return this; - } - } - - private Data data; - - private BufferedWriter writer; - - private ExperimentStateManager(Data data) { - this.data = data; - experimentContext = new ExperimentContextImpl(); - - for (int i = 0; i < data.scenarioCount; i++) { - ScenarioRecord scenarioRecord = new ScenarioRecord(); - scenarioRecord.scenarioStatus = ScenarioStatus.READY; - scenarioRecords.put(i, scenarioRecord); - } - - } - - private void subscribeToSimulationOpen(BiConsumer consumer) { - simOpenConsumers.add(consumer); - } - - private void subscribeToSimulationClose(BiConsumer consumer) { - if (consumer == null) { - throw new ContractException(NucleusError.NULL_EXPERIMENT_CONTEXT_CONSUMER); - } - simCloseConsumers.add(consumer); - } - - private void subscribeToExperimentOpen(Consumer consumer) { - experimentOpenConsumers.add(consumer); - } - - private void subscribeToExperimentClose(Consumer consumer) { - experimentCloseConsumers.add(consumer); - } - - private void subscribeToOutput(Class outputClass, TriConsumer consumer) { - Set> outputConsumers = outputConsumerMap.get(outputClass); - if (outputConsumers == null) { - outputConsumers = new LinkedHashSet<>(); - outputConsumerMap.put(outputClass, outputConsumers); - } - outputConsumers.add(new MetaOutputConsumer<>(consumer)::accept); - } - - private final static String SCENARIO_LABEL = "scenario"; - - private void readProgressFile() { - - /* - * if the client does not want to continue from the progress log, we - * return. - */ - if (!data.continueFromProgressLog) { - return; - } - - /* - * If the progress file is null, then we can't proceed. - */ - if (data.progressLogFile == null) { - throw new ContractException(NucleusError.NULL_SCENARIO_PROGRESS_FILE); - } - - if (!Files.exists(data.progressLogFile)) { - throw new ContractException(NucleusError.NON_EXISTANT_SCEANARIO_PROGRESS); - } - - if (!Files.isRegularFile(data.progressLogFile)) { - throw new ContractException(NucleusError.UNREADABLE_SCEANARIO_PROGRESS, "not a regular file"); - } - - /* - * Try to read in the tab delimited lines. - */ - List lines = new ArrayList<>(); - - try { - lines = Files.readAllLines(data.progressLogFile); - } catch (IOException e) { - e.printStackTrace(); - throw new ContractException(NucleusError.UNREADABLE_SCEANARIO_PROGRESS, " failed to load lines"); - } - - /* - * Corruption of lines in this file is expected since it may not have - * been closed properly. We will default to not loading input - * gracefully. - */ - if (lines.isEmpty()) { - return; - } - - /* - * If the header line does not match the current meta data, then we will - * throw an exception since the client may not want the existing file to - * be overwritten - */ - - List expectedHeader = new ArrayList<>(); - expectedHeader.add(SCENARIO_LABEL); - expectedHeader.addAll(data.experimentMetaData); - - List actualHeader = Arrays.asList(lines.get(0).split("\t")); - if (!expectedHeader.equals(actualHeader)) { - throw new ContractException(NucleusError.INCOMPATIBLE_SCEANARIO_PROGRESS, "wrong file header"); - } - - // Load the remaining lines - int n = lines.size(); - for (int i = 1; i < n; i++) { - List entries = Arrays.asList(lines.get(i).split("\t")); - if (entries.size() != expectedHeader.size()) { - /* - * Potential ungraceful termination of previous experiment - */ - break; - } - - // try to get the scenario id - int scenarioId = -1; - try { - scenarioId = Integer.parseInt(entries.get(0)); - } catch (Exception e) { - /* - * Potential ungraceful termination of previous experiment - */ - break; - } - - ScenarioRecord scenarioRecord = scenarioRecords.get(scenarioId); - - /* - * If the scenario is not recognized, then we throw an exception so - * that the file is not overwritten - */ - if (scenarioRecord == null) { - throw new ContractException(NucleusError.INCOMPATIBLE_SCEANARIO_PROGRESS, "scenario " + scenarioId + " is out of bounds"); - } - - // record the scenario status and meta data - - scenarioRecord.metaData = new ArrayList<>(); - for (int j = 1; j < entries.size(); j++) { - scenarioRecord.scenarioStatus = ScenarioStatus.PREVIOUSLY_SUCCEEDED; - scenarioRecord.metaData.add(entries.get(j)); - } - scenarioRecords.put(scenarioId, scenarioRecord); - } - - } - - private void writeProgressFile() { - if (data.progressLogFile == null) { - return; - } - /* - * We will clear out the old content from the file and replace it with - * the items in the experiment progress log. - */ - final CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder(); - OutputStream out; - try { - out = Files.newOutputStream(data.progressLogFile, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); - } catch (final IOException e) { - // re-thrown as runtime exception - throw new RuntimeException(e); - } - writer = new BufferedWriter(new OutputStreamWriter(out, encoder)); - try { - writer.write("scenario"); - for (String metaDatum : data.experimentMetaData) { - writer.write("\t"); - writer.write(metaDatum); - } - writer.write(LINE_SEPARATOR); - - for (Integer scenarioId : scenarioRecords.keySet()) { - ScenarioRecord scenarioRecord = scenarioRecords.get(scenarioId); - if (scenarioRecord.scenarioStatus == ScenarioStatus.PREVIOUSLY_SUCCEEDED) { - - writer.write(scenarioId.toString()); - for (String metaDatum : scenarioRecord.metaData) { - writer.write("\t"); - writer.write(metaDatum); - } - writer.write(LINE_SEPARATOR); - } - } - writer.flush(); - } catch (final IOException e) { - // re-thrown as runtime exception - throw new RuntimeException(e); - } - } - - /** - * Announces the opening of the experiment to subscribed experiment context - * consumers. Opens the experiment progress file if it is being used. Can - * only be called once. - * - * @throws ContractException - *
  • {@linkplain NucleusError#NULL_SCENARIO_PROGRESS_FILE} if - * continue from progress file was chosen, but the path to the - * file is null
  • - *
  • {@linkplain NucleusError#NON_EXISTANT_SCEANARIO_PROGRESS} - * if continue from progress file was chosen, but the path to - * the file does not exist
  • - *
  • {@linkplain NucleusError#UNREADABLE_SCEANARIO_PROGRESS} - * if continue from progress file was chosen, but the path lead - * to a non-file
  • - *
  • {@linkplain NucleusError#UNREADABLE_SCEANARIO_PROGRESS} - * if the lines of the file cannot be loaded
  • - *
  • {@linkplain NucleusError#INCOMPATIBLE_SCEANARIO_PROGRESS} - * if the header line of the file does not match the expected - * header line for the current experiment
  • - *
  • {@linkplain NucleusError#INCOMPATIBLE_SCEANARIO_PROGRESS} - * if a scenario id is encountered that is not valid for the the - * current experiment
  • - * - * @throws ContractException - *
  • {@linkplain NucleusError#DUPLICATE_EXPERIMENT_OPEN} if - * the experiment status manager is not currently in the ready - * state
  • - */ - public synchronized void openExperiment() { - if (experimentStatus != ExperimentStatus.READY) { - throw new ContractException(NucleusError.DUPLICATE_EXPERIMENT_OPEN); - } - experimentStatus = ExperimentStatus.OPENED; - - if (data.continueFromProgressLog) { - readProgressFile(); - } - writeProgressFile(); - - // handshake with the consumers - for (Consumer consumer : data.contextConsumers) { - consumer.accept(experimentContext); - } - - // announce the opening of the experiment - for (Consumer consumer : experimentOpenConsumers) { - consumer.accept(experimentContext); - } - } - - /** - * Announces the closure of the experiment to subscribed experiment context - * consumers. Closes the experiment progress file if it is being used. - * - * @throws ContractException - *
  • {@linkplain NucleusError#UNCLOSABLE_EXPERIMENT} if the - * experiment status manager is not currently in the open - * state
  • - */ - public synchronized void closeExperiment() { - - if (experimentStatus != ExperimentStatus.OPENED) { - throw new ContractException(NucleusError.UNCLOSABLE_EXPERIMENT); - } - experimentStatus = ExperimentStatus.CLOSED; - - for (Consumer consumer : experimentCloseConsumers) { - consumer.accept(experimentContext); - } - - try { - if (writer != null) { - writer.close(); - } - } catch (final IOException e) { - // deception - throw new RuntimeException(e); - } - } - - @NotThreadSafe - private static class OutputItemConsumerManager { - - private final Map, Set>> baseMap; - private final Map, Set>> workingMap = new LinkedHashMap<>(); - private final Integer scenarioId; - private ExperimentContext experimentContext; - - public OutputItemConsumerManager(ExperimentContext experimentContext, Integer scenarioId, Map, Set>> consumerMap) { - this.experimentContext = experimentContext; - this.scenarioId = scenarioId; - this.baseMap = new LinkedHashMap<>(consumerMap); - } - - public void handleOutput(Object output) { - if (output == null) { - throw new ContractException(NucleusError.NULL_OUTPUT_ITEM); - } - - Set> consumers = workingMap.get(output.getClass()); - if (consumers == null) { - consumers = new LinkedHashSet<>(); - Class outputClass = output.getClass(); - for (Class c : baseMap.keySet()) { - if (c.isAssignableFrom(outputClass)) { - consumers.addAll(baseMap.get(c)); - } - } - workingMap.put(outputClass, consumers); - } - - for (TriConsumer triConsumer : consumers) { - triConsumer.accept(experimentContext, scenarioId, output); - } - } - } - - /** - * Returns a non-threadsafe consumer of output that will distribute output - * objects to the appropriate class-mapped output consumers. Each simulation - * instance running a scenario should have its own instance of this consumer - * that is confined to the thread running the simulation. This limits the - * thread collisions to the specific output consumer end points. - * - * @throws ContractException - *
  • {@linkplain NucleusError#NULL_SCENARIO_ID} if the - * scenario id is null
  • - *
  • {@linkplain NucleusError#UNKNOWN_SCENARIO_ID} if the - * scenario id is not in the range [0,scenario count)
  • - */ - - public synchronized Consumer getOutputConsumer(Integer scenarioId) { - if (scenarioId == null) { - throw new ContractException(NucleusError.NULL_SCENARIO_ID); - } - ScenarioRecord scenarioRecord = scenarioRecords.get(scenarioId); - - if (scenarioRecord == null) { - throw new ContractException(NucleusError.UNKNOWN_SCENARIO_ID); - } - - return new OutputItemConsumerManager(experimentContext, scenarioId, outputConsumerMap)::handleOutput; - } - -} \ No newline at end of file diff --git a/gcm3/src/main/java/nucleus/ExperimentStatusConsole.java b/gcm3/src/main/java/nucleus/ExperimentStatusConsole.java deleted file mode 100644 index 57b6e69e5..000000000 --- a/gcm3/src/main/java/nucleus/ExperimentStatusConsole.java +++ /dev/null @@ -1,122 +0,0 @@ -package nucleus; - -import java.util.List; - -import net.jcip.annotations.ThreadSafe; - -/** - * Utility class used by the Experiment for reporting the execution progress of - * an experiment to the console. - * - * @author Shawn Hatch - * - */ - -@ThreadSafe -public final class ExperimentStatusConsole { - - /* - * The last reported percentage completion value that includes credit from - * previous executions of the experiment. Guarded by this. - */ - private int lastReportPercentage = -1; - - /* - * Returns the string representation of the value with a prepended "0" for - * numbers less than 10 - */ - private static String getBase60String(int value) { - if (value < 10) { - return "0" + Integer.toString(value); - } - return Integer.toString(value); - } - - /* - * Returns a colon delimited string representation for the number of seconds - * in the form HH:MM:SS - */ - private static String getTimeExpression(double seconds) { - int n = (int) Math.round(seconds); - int h = n / 3600; - n = n % 3600; - int m = n / 60; - int s = n % 60; - - return h + ":" + getBase60String(m) + ":" + getBase60String(s); - } - - private void handleExperimentClose(ExperimentContext experimentContext) { - String timeExpression = getTimeExpression(experimentContext.getElapsedSeconds()); - - int previousProgressCount = experimentContext.getStatusCount(ScenarioStatus.PREVIOUSLY_SUCCEEDED); - int totalSuccessCount = previousProgressCount + experimentContext.getStatusCount(ScenarioStatus.SUCCEDED); - int experimentCount = experimentContext.getScenarioCount(); - int failCount = experimentContext.getStatusCount(ScenarioStatus.FAILED); - String experimentCompletionMessage; - if (experimentContext.getStatusCount(ScenarioStatus.PREVIOUSLY_SUCCEEDED) == 0) { - experimentCompletionMessage = "Experiment finished with " + totalSuccessCount + " of " + experimentCount + " scenario replications successfully completed in " + timeExpression; - } else { - experimentCompletionMessage = "Experiment finished with " + totalSuccessCount + "(" + previousProgressCount + " from previous run(s))" + " of " + experimentCount - + " scenario replications successfully completed in " + timeExpression; - } - if (totalSuccessCount != experimentCount) { - System.err.println(experimentCompletionMessage); - } else { - System.out.println(experimentCompletionMessage); - } - if (failCount > 0) { - System.err.println("Failed simulations"); - - List scenarios = experimentContext.getScenarios(ScenarioStatus.FAILED); - int count = Math.min(100, scenarios.size()); - for (int i = 0; i < count; i++) { - System.err.println(scenarios.get(i)); - } - if (failCount > 100) { - System.err.println("\t..."); - } - } - } - - private void handleSimulationClose(ExperimentContext experimentContext, int scenarioId) { - - int completionCount = experimentContext.getStatusCount(ScenarioStatus.SUCCEDED) + experimentContext.getStatusCount(ScenarioStatus.PREVIOUSLY_SUCCEEDED) - + experimentContext.getStatusCount(ScenarioStatus.FAILED); - - double completionProportion = completionCount; - completionProportion /= experimentContext.getScenarioCount(); - completionProportion *= 100; - - int percentComplete = (int) completionProportion; - - boolean reportToConsole = false; - synchronized (this) { - if (percentComplete > lastReportPercentage) { - lastReportPercentage = percentComplete; - reportToConsole = true; - } - } - - if (reportToConsole) { - int executedCountForThisRun = experimentContext.getStatusCount(ScenarioStatus.SUCCEDED) + experimentContext.getStatusCount(ScenarioStatus.FAILED); - double averageTimePerExecution = experimentContext.getElapsedSeconds() / executedCountForThisRun; - int remainingExecutions = experimentContext.getScenarioCount() - completionCount; - double expectedRemainingTime = Math.round(averageTimePerExecution * remainingExecutions); - String timeExpression = getTimeExpression(expectedRemainingTime); - System.out.println( - completionCount + " of " + experimentContext.getScenarioCount() + " scenario replications, " + percentComplete + "% complete. Expected experiment completion in " + timeExpression); - } - - } - - /** - * Initializes this ExperimentStatusConsole, which registers for simulation - * and experiment close events. - */ - public void init(ExperimentContext experimentContext) { - experimentContext.subscribeToSimulationClose(this::handleSimulationClose); - experimentContext.subscribeToExperimentClose(this::handleExperimentClose); - } - -} diff --git a/gcm3/src/main/java/nucleus/NucleusError.java b/gcm3/src/main/java/nucleus/NucleusError.java deleted file mode 100644 index 04ddb4701..000000000 --- a/gcm3/src/main/java/nucleus/NucleusError.java +++ /dev/null @@ -1,90 +0,0 @@ -package nucleus; - -import util.errors.ContractError; -import util.errors.ContractException; - -/** - * An enumeration supporting {@link ContractException} that acts as a general - * description of the exception. - * - * @author Shawn Hatch - * - */ -public enum NucleusError implements ContractError { - ACCESS_VIOLATION("A contributed behavior is accessing locked state during a state change"), - AMBIGUOUS_DATA_MANAGER_CLASS("Multiple data manager matches found"), - AMBIGUOUS_PLUGIN_DATA_BUILDER_CLASS("Multiple plugin data builder matches found"), - AMBIGUOUS_PLUGIN_DATA_CLASS("Multiple plugin data object matches found"), - CIRCULAR_PLUGIN_DEPENDENCIES("Circular plugin dependencies were found"), - DATA_MANAGER_DUPLICATE_INITIALIZATION("Data manager was already initialized"), - DATA_MANAGER_INITIALIZATION_FAILURE("Data manager base class was not properly initialized, be sure to call super()"), - DUPLICATE_DATA_MANAGER_TYPE("Duplicate data manager type"), - DUPLICATE_EXPERIMENT_OPEN("Duplicate opening of experiment"), - DUPLICATE_LABELER_ID_IN_EVENT_LABELER("Duplicate labeler id in labeler"), - DUPLICATE_PLAN_KEY("There is an existing plan currently scheduled with the same key"), - DUPLICATE_PLUGIN("There are two or more plugins with the same id"), - INCOMPATIBLE_SCEANARIO_PROGRESS("The scenario progress file is incompatible with the current experiment"), - LABLER_GENERATED_LABEL_WITH_INCORRECT_EVENT_CLASS("Event labler generated a label with an incorrect event class"), - LABLER_GENERATED_LABEL_WITH_INCORRECT_ID("Event labler generated a label with an incorrect event labeler id"), - LABLER_GENERATED_LABEL_WITH_INCORRECT_PRIMARY_KEY("Event labler generated a label with an incorrect primary key"), - MISSING_PLUGIN("A plugin is missing"), - NEGATIVE_THREAD_COUNT("Negative thread count"), - NON_EXISTANT_SCEANARIO_PROGRESS("The scenario progress file does not exist, but is required when continuation from progress file is chosen"), - NULL_ACTOR_CONTEXT_CONSUMER("Null actor context consumer"), - NULL_ACTOR_ID("Null actor id"), - NULL_DATA_MANAGER("Null data manager"), - NULL_DATA_MANAGER_CLASS("Null data manager class"), - NULL_DATA_MANAGER_CONTEXT_CONSUMER("Null data manager context consumer"), - NULL_EVENT("Event is null"), - NULL_EVENT_CLASS("Null event class"), - NULL_EVENT_CONSUMER("Null event consumer"), - NULL_EVENT_LABEL("Null event label"), - NULL_EVENT_LABEL_FUNCTION("Null event label function"), - NULL_EVENT_LABEL_KEY("Null event label key"), - NULL_EVENT_LABELER("Null event labeler"), - NULL_EVENT_LABELER_ID("Null event labeler id"), - NULL_EXPERIMENT_CONTEXT_CONSUMER("Null experiment context consumer"), - NULL_LABELER_ID_IN_EVENT_LABEL("Event label returns a null event labeler id"), - NULL_LABELER_ID_IN_EVENT_LABELER("Event labeler returns a null id"), - NULL_META_DATA("Null meta data"), - NULL_OUTPUT_HANDLER("Null output item handler"), - NULL_OUTPUT_ITEM("Null output"), - NULL_PLAN("Null plan"), - NULL_PLAN_KEY("Null planning key"), - NULL_PLUGIN("Null plugin"), - NULL_PLUGIN_CONTEXT("Null plugin context"), - NULL_PLUGIN_DATA("Null plugin data"), - NULL_PLUGIN_DATA_BUILDER("A null plugin data builder instance was added to the context"), - NULL_PLUGIN_DATA_CLASS("Null plugin data class"), - NULL_PLUGIN_ID("Null plugin id"), - NULL_PLUGIN_INITIALIZER("Null plugin initializer"), - NULL_PRIMARY_KEY_VALUE("Null primary key value"), - NULL_SCENARIO_ID("Null scenario id"), - NULL_SCENARIO_PROGRESS_FILE("Null scenario progress file"), - NULL_SIMULATION_CONTEXT("Null simulation context"), - PAST_PLANNING_TIME("Plan execution time is in the past"), - PLUGIN_INITIALIZATION_CLOSED("Plugin context is no longer valid"), - REPEATED_EXECUTION("Attempted repeat execution of simulation engine"), - SCENARIO_CANNOT_BE_EXECUTED("Scenario cannot be executed"), - UNCLOSABLE_EXPERIMENT("Cannot close an experiment not in the open state"), - UNKNOWN_ACTOR_ID("Actor id does not correspond to a known actor"), - UNKNOWN_DATA_MANAGER("Unknown data manager"), - UNKNOWN_EVENT_LABELER("The labeler id an event label does not match a registered event labeler"), - UNKNOWN_PLUGIN_DATA_BUILDER_CLASS("The plugin data builder class was not found"), - UNKNOWN_PLUGIN_DATA_CLASS("Unknown plugin data class"), - UNKNOWN_SCENARIO_ID("Unknown scenario id"), - UNREADABLE_SCEANARIO_PROGRESS("The scenario progress file is unreadable"), - - ; - - private final String description; - - private NucleusError(final String description) { - this.description = description; - } - - @Override - public String getDescription() { - return description; - } -} diff --git a/gcm3/src/main/java/nucleus/Plugin.java b/gcm3/src/main/java/nucleus/Plugin.java deleted file mode 100644 index 255a39dec..000000000 --- a/gcm3/src/main/java/nucleus/Plugin.java +++ /dev/null @@ -1,185 +0,0 @@ -package nucleus; - -import java.util.LinkedHashSet; -import java.util.Optional; -import java.util.Set; -import java.util.function.Consumer; - -import net.jcip.annotations.ThreadSafe; -import util.errors.ContractException; - -/** - * A plugin is the main compositional element of an experiment. Plugins contain - * the initial data state for simulations and add actors and data managers to - * each simulation at the startup. - * - * - * @author Shawn Hatch - * - */ -@ThreadSafe -public final class Plugin { - - private static class Data { - private PluginId pluginId; - private Set pluginDependencies = new LinkedHashSet<>(); - private Set pluginDatas = new LinkedHashSet<>(); - private Consumer initializer; - } - - /** - * Returns an new instance of the Builder - */ - public static Builder builder() { - return new Builder(); - } - - /** - * A builder class for Plugin - * - * @author Shawn Hatch - * - */ - public static class Builder { - private Builder() { - } - - private void validate() { - if (data.pluginId == null) { - throw new ContractException(NucleusError.NULL_PLUGIN_ID); - } - } - - private Data data = new Data(); - - /** - * Returns the plugin formed by the inputs collected by this builder. - * - * @throws ContractException - * - *
  • {@linkplain NucleusError#NULL_PLUGIN_ID} if the - * plugin id was not set or set to null
  • - */ - public Plugin build() { - try { - validate(); - return new Plugin(data); - } finally { - data = new Data(); - } - } - - public Builder setPluginId(PluginId pluginId) { - if(pluginId == null) { - throw new ContractException(NucleusError.NULL_PLUGIN_ID); - } - data.pluginId = pluginId; - return this; - } - - /** - * Establishes that the plugin using this plugin context depends upon - * the given plugin. - * - * Plugin dependencies are gathered by nucleus and used to determine - * that the simulation is well formed. Nucleus requires that: 1) there - * are no duplicate plugins, 2)there are no null plugins, 3)there are no - * missing plugins, and 4) the plugin dependencies form an acyclic, - * directed graph. - * - * Nucleus will initialize each plugin primarily in the order dictated - * by this graph and secondarily in the order each plugin was - * contributed to a simulation or experiment. - * - * @throws ContractException - * - *
  • {@link NucleusError#NULL_PLUGIN_ID} if the plugin id - * is null - */ - public Builder addPluginDependency(PluginId pluginId) { - if(pluginId == null) { - throw new ContractException(NucleusError.NULL_PLUGIN_ID); - } - data.pluginDependencies.add(pluginId); - return this; - } - - /** - * Adds a plugin data object. Plugin data object must be thread-safe. It - * is best practice for a plugin data to be properly immutable: 1) its - * state cannot be altered after construction, 2) all its member fields - * are declared final and 3) it does not pass any reference to itself - * during its construction. - * - * @throws ContractException - *
  • {@linkplain NucleusError#NULL_PLUGIN_DATA} if the - * plugin data is null
  • - * - */ - public Builder addPluginData(PluginData pluginData) { - if (pluginData == null) { - throw new ContractException(NucleusError.NULL_PLUGIN_DATA); - } - data.pluginDatas.add(pluginData); - return this; - } - - /** - * Sets the consumer of plugin context that interacts with the - * simulation by adding actors and data mangers to the simulation on the - * simulation's startup. The initializer must be thread-safe. It is best - * practice for the initializer to be stateless. - * - * @throws ContractException - *
  • {@linkplain NucleusError#NULL_PLUGIN_INITIALIZER} if the initializer is null
  • - * - */ - public Builder setInitializer(Consumer initializer) { - if(initializer == null) { - throw new ContractException(NucleusError.NULL_PLUGIN_INITIALIZER); - } - data.initializer = initializer; - return this; - } - - } - - private final Data data; - - private Plugin(Data data) { - this.data = data; - } - - /** - * Returns the plugin id of this plugin. - */ - public final PluginId getPluginId() { - return data.pluginId; - } - - /** - * Returns the plugin id values of the other plugins that this plugin - * depends on to function correctly. These dependencies for a directed - * acyclic graph and determine the initialization order of plugins. - */ - public final Set getPluginDependencies() { - return new LinkedHashSet<>(data.pluginDependencies); - } - - /** - * Returns the set thread-safe plugin data objects collected by this - * plugin's builder. - */ - public final Set getPluginDatas() { - return new LinkedHashSet<>(data.pluginDatas); - } - - /** - * Returns a thread-safe consumer of plugin context. The initializer - * interacts with the simulation by adding actors and data mangers to the - * simulation on the simulation's startup. - */ - public final Optional> getInitializer() { - return Optional.ofNullable(data.initializer); - } -} diff --git a/gcm3/src/main/java/nucleus/PluginContext.java b/gcm3/src/main/java/nucleus/PluginContext.java deleted file mode 100644 index ef5417a05..000000000 --- a/gcm3/src/main/java/nucleus/PluginContext.java +++ /dev/null @@ -1,59 +0,0 @@ -package nucleus; - -import java.util.function.Consumer; - -import util.errors.ContractException; - -/** - * A plugin context provides plugin's the ability to add actors and data - * managers to the initialization of each simulation instance(scenario) in an - * experiment. It provides the set of plugin data objects gathered from the - * plugins that compose the experiment. - * - * @author Shawn Hatch - * - */ -public interface PluginContext { - - /** - * - * Adds a data manager to the simulation. - * - * - * @throws ContractException - *
  • {@link NucleusError#PLUGIN_INITIALIZATION_CLOSED} if - * plugin initialization is over
  • - * - */ - public void addDataManager(DataManager dataManager); - - /** - * - * Adds an actor to the simulation. - * - * - * @throws ContractException - *
  • {@link NucleusError#PLUGIN_INITIALIZATION_CLOSED} if - * plugin initialization is over
  • - * - */ - public ActorId addActor(Consumer init); - - /** - * Returns the plugin data object associated with the given class reference - * - * @throws ContractException - * - *
  • {@linkplain NucleusError#NULL_PLUGIN_DATA_CLASS} if - * the class reference is null
  • - - *
  • {@linkplain NucleusError#AMBIGUOUS_PLUGIN_DATA_CLASS} if - * more than one plugin data object matches the class - * reference
  • - * - *
  • {@linkplain NucleusError#UNKNOWN_PLUGIN_DATA_CLASS} if no - * plugin data object matches the class reference
  • - * - */ - public T getPluginData(Class pluginDataClass); -} diff --git a/gcm3/src/main/java/nucleus/PluginData.java b/gcm3/src/main/java/nucleus/PluginData.java deleted file mode 100644 index 5cecb5cd0..000000000 --- a/gcm3/src/main/java/nucleus/PluginData.java +++ /dev/null @@ -1,32 +0,0 @@ -package nucleus; - -import net.jcip.annotations.ThreadSafe; - -/** - * Plugin data objects are thread-safe containers of initialization state for - * the scenarios of a simulation. Plugin data are contributed to plugins which - * are in turn added to the experiment. The experiment generates multiple - * scenarios by having the experiment dimensions alter copies of the plugin - * datas and then distributing those alternate copies to multiple simulation - * instances. It is best practice for a plugin data to be properly immutable: 1) - * its state cannot be altered after construction, 2) all its member fields are - * declared final and 3) it does not pass any reference to itself during its - * construction. - * - * Plugin data classes require a corresponding PluginDataBuilder that builds the - * plugin. - * - * @author Shawn Hatch - * - */ -@ThreadSafe -public interface PluginData { - - /** - * Returns a PluginDataBuilder that can build the plugin data. The returned - * builder should be initialized with this plugin data object's internal - * state such that invocation of pluginData.getCloneBuilder().build() will - * generate a copy of the current plugin. - */ - public PluginDataBuilder getCloneBuilder(); -} diff --git a/gcm3/src/main/java/nucleus/PluginDataBuilder.java b/gcm3/src/main/java/nucleus/PluginDataBuilder.java deleted file mode 100644 index 9d7cb5f8d..000000000 --- a/gcm3/src/main/java/nucleus/PluginDataBuilder.java +++ /dev/null @@ -1,17 +0,0 @@ -package nucleus; - -/** - * PlugingDataBuilder is an interface for the builders of plugins. - * - * @author Shawn Hatch - * - */ - -public interface PluginDataBuilder { - - /** - * Returns a plugin data - */ - public PluginData build(); - -} diff --git a/gcm3/src/main/java/nucleus/PluginDataBuilderContext.java b/gcm3/src/main/java/nucleus/PluginDataBuilderContext.java deleted file mode 100644 index 39bb82a40..000000000 --- a/gcm3/src/main/java/nucleus/PluginDataBuilderContext.java +++ /dev/null @@ -1,126 +0,0 @@ -package nucleus; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import util.errors.ContractException; - -/** - * - * A context containing PluginDataBuilder instances that are used to build a - * particular scenario within an experiment. - * - * @author Shawn Hatch - * - * - */ -public final class PluginDataBuilderContext { - - private PluginDataBuilderContext() { - } - - private Map, PluginDataBuilder> baseMap = new LinkedHashMap<>(); - - private Map, PluginDataBuilder> workingMap = new LinkedHashMap<>(); - - /** - * Returns the stored item matching the given class reference. - * - * @throws ContractException - *
  • {@linkplain NucleusError#AMBIGUOUS_PLUGIN_DATA_BUILDER_CLASS} - * if more than one plugin data builder matches the given class - * reference
  • - * - *
  • {@linkplain NucleusError#UNKNOWN_PLUGIN_DATA_BUILDER_CLASS} - * if no plugin data builder matches the given class - * reference
  • - */ - @SuppressWarnings("unchecked") - public T get(Class classRef) { - - PluginDataBuilder pluginDataBuilder = workingMap.get(classRef); - if (pluginDataBuilder == null) { - List> candidates = new ArrayList<>(); - for (Class c : baseMap.keySet()) { - if (classRef.isAssignableFrom(c)) { - candidates.add(c); - } - } - if (candidates.size() > 1) { - throw new ContractException(NucleusError.AMBIGUOUS_PLUGIN_DATA_BUILDER_CLASS); - } - if (candidates.size() == 1) { - pluginDataBuilder = baseMap.get(candidates.get(0)); - workingMap.put(classRef, pluginDataBuilder); - } - } - if (pluginDataBuilder == null) { - throw new ContractException(NucleusError.UNKNOWN_PLUGIN_DATA_BUILDER_CLASS); - } - return (T)pluginDataBuilder; - } - - /** - * Returns a typed Builder instance for PluginDataBuilderContext - */ - public static Builder builder() { - return new Builder(); - } - - /** - * A builder class for PluginDataBuilderContext - * - * @author Shawn Hatch - * - */ - public static class Builder { - private Builder() { - } - - private Map, PluginDataBuilder> map = new LinkedHashMap<>(); - - /** - * Returns the PluginDataBuilderContext instance composed from the - * inputs to this builder. - */ - public PluginDataBuilderContext build() { - try { - PluginDataBuilderContext result = new PluginDataBuilderContext(); - result.baseMap = map; - return result; - } finally { - map = new LinkedHashMap<>(); - } - } - - /** - * @throws ContractException - *
  • {@linkplain NucleusError#NULL_PLUGIN_DATA_BUILDER} if - * the plugin data builder is null
  • - * - */ - public Builder add(T t) { - if (t == null) { - throw new ContractException(NucleusError.NULL_PLUGIN_DATA_BUILDER); - } - map.put(t.getClass(), t); - return this; - } - } - - /** - * Returns the set of items stored in this container. - */ - public Set getContents() { - Set result = new LinkedHashSet<>(); - for (PluginDataBuilder pluginDataBuilder : baseMap.values()) { - result.add(pluginDataBuilder); - } - return result; - } - -} diff --git a/gcm3/src/main/java/nucleus/PluginId.java b/gcm3/src/main/java/nucleus/PluginId.java deleted file mode 100644 index b64f3dca4..000000000 --- a/gcm3/src/main/java/nucleus/PluginId.java +++ /dev/null @@ -1,15 +0,0 @@ -package nucleus; - -import net.jcip.annotations.ThreadSafe; - -/** - * Marker interface for plugin identification. Plugins added to an experiment - * should have unique ids. - * - * @author Shawn Hatch - * - */ -@ThreadSafe -public interface PluginId { - -} diff --git a/gcm3/src/main/java/nucleus/ScenarioStatus.java b/gcm3/src/main/java/nucleus/ScenarioStatus.java deleted file mode 100644 index b95e44db4..000000000 --- a/gcm3/src/main/java/nucleus/ScenarioStatus.java +++ /dev/null @@ -1,19 +0,0 @@ -package nucleus; - -/** - * The status of a scenario from the perspective of an experiment. If an - * experiment is executed as a continuation of a previous execution, then the - * scenarios fully recorded in the scenario progress file are marked as - * PREVIOUSLY_SUCCEEDED. All other scenarios put into READY status. As the - * experiment executes it updates the scenario status first to RUNNING until the - * corresponding simulation completes. If the simulation runs without throwing - * an exception, the scenario is updated to SUCCEDED. Otherwise, it is updated - * to FAILED. - * - * @author Shawn Hatch - * - */ - -public enum ScenarioStatus { - READY, RUNNING, PREVIOUSLY_SUCCEEDED, SUCCEDED, FAILED; -} \ No newline at end of file diff --git a/gcm3/src/main/java/nucleus/Simulation.java b/gcm3/src/main/java/nucleus/Simulation.java deleted file mode 100644 index cfad896c1..000000000 --- a/gcm3/src/main/java/nucleus/Simulation.java +++ /dev/null @@ -1,1610 +0,0 @@ -package nucleus; - -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.Deque; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.PriorityQueue; -import java.util.Set; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.stream.Collectors; - -import org.apache.commons.math3.util.FastMath; - -import net.jcip.annotations.NotThreadSafe; -import util.errors.ContractException; -import util.graph.Graph; -import util.graph.GraphDepthEvaluator; -import util.graph.Graphs; -import util.graph.MutableGraph; - -/** - * An instance of the Simulation orchestrates the execution of a scenario from a - * set of contributed plugins. - * - * Plugins are loaded primarily based on the directed acyclic graph implied by - * their dependencies and then secondarily on the order in which the plugins - * were added to the experiment or simulation. - * - * Each plugin contributes an initialization behavior that adds actors and data - * managers to the simulation at simulation startup. The data managers are - * initialized in the order they are added to the simulation. Actor - * initialization then follows in a similar order. - * - * After initialization is over, time flows based on the execution of planning. - * Plans are collected from both actors and data managers. When no more plans - * remain, the simulation halts. - * - * - * @author Shawn Hatch - * - */ -@NotThreadSafe -public class Simulation { - - private class PluginContextImpl implements PluginContext { - - @Override - public void addDataManager(DataManager dataManager) { - - if (focalPluginId == null) { - throw new ContractException(NucleusError.PLUGIN_INITIALIZATION_CLOSED); - } - - if (dataManager == null) { - throw new ContractException(NucleusError.NULL_DATA_MANAGER); - } - - if (baseClassToDataManagerMap.containsKey(dataManager.getClass())) { - throw new ContractException(NucleusError.DUPLICATE_DATA_MANAGER_TYPE, dataManager.getClass()); - } - - DataManagerId dataManagerId = new DataManagerId(masterDataManagerIndex++); - DataManagerContext dataManagerContext = new DataManagerContextImpl(Simulation.this, dataManagerId); - dataManagerIdToContextMap.put(dataManagerId, dataManagerContext); - baseClassToDataManagerMap.put(dataManager.getClass(), dataManager); - dataManagerIdToDataManagerMap.put(dataManagerId, dataManager); - - } - - @Override - public ActorId addActor(Consumer consumer) { - - if (consumer == null) { - throw new ContractException(NucleusError.NULL_ACTOR_CONTEXT_CONSUMER); - } - - if (focalPluginId == null) { - throw new ContractException(NucleusError.PLUGIN_INITIALIZATION_CLOSED); - } - - // assign an actorId - ActorId actorId = new ActorId(actorIds.size()); - actorIds.add(actorId); - - // add the actor's initialization to the actor queue - final ActorContentRec actorContentRec = new ActorContentRec(); - actorContentRec.actorId = actorId; - actorContentRec.plan = consumer; - actorQueue.add(actorContentRec); - - return actorId; - - } - - @SuppressWarnings("unchecked") - @Override - - public T getPluginData(Class pluginDataClass) { - if (pluginDataClass == null) { - throw new ContractException(NucleusError.NULL_PLUGIN_DATA_CLASS); - } - - PluginData pluginData = workingPluginDataMap.get(pluginDataClass); - if (pluginData == null) { - List> candidates = new ArrayList<>(); - for (Class c : basePluginDataMap.keySet()) { - if (pluginDataClass.isAssignableFrom(c)) { - candidates.add(c); - } - } - if (candidates.size() > 1) { - throw new ContractException(NucleusError.AMBIGUOUS_PLUGIN_DATA_CLASS); - } - if (candidates.size() == 1) { - pluginData = basePluginDataMap.get(candidates.get(0)); - workingPluginDataMap.put(pluginDataClass, pluginData); - } - } - if (pluginData == null) { - throw new ContractException(NucleusError.UNKNOWN_PLUGIN_DATA_CLASS); - } - - return (T) pluginData; - } - - } - - private class ActorContextImpl implements ActorContext { - - @Override - public void addPlan(final Consumer plan, final double planTime) { - addActorPlan(plan, planTime, true, null); - } - - @Override - public void addKeyedPlan(final Consumer plan, final double planTime, final Object key) { - validatePlanKeyNotNull(key); - validateActorPlanKeyNotDuplicate(key); - addActorPlan(plan, planTime, true, key); - } - - @Override - public void addPassivePlan(final Consumer plan, final double planTime) { - addActorPlan(plan, planTime, false, null); - } - - @Override - public void addPassiveKeyedPlan(final Consumer plan, final double planTime, final Object key) { - validatePlanKeyNotNull(key); - validateActorPlanKeyNotDuplicate(key); - addActorPlan(plan, planTime, false, key); - } - - @Override - public boolean actorExists(final ActorId actorId) { - return Simulation.this.actorExists(actorId); - } - - @Override - public ActorId getActorId() { - return focalActorId; - } - - @Override - public T getDataManager(Class dataManagerClass) { - return Simulation.this.getDataManager(dataManagerClass); - } - - @SuppressWarnings("unchecked") - @Override - public > Optional getPlan(final Object key) { - return (Optional) Simulation.this.getActorPlan(key); - } - - @Override - public List getPlanKeys() { - return Simulation.this.getActorPlanKeys(); - } - - @Override - public Optional getPlanTime(final Object key) { - return Simulation.this.getActorPlanTime(key); - } - - @Override - public double getTime() { - return time; - } - - @Override - public void halt() { - Simulation.this.halt(); - } - - @Override - public Optional removePlan(final Object key) { - return Simulation.this.removeActorPlan(key); - } - - @Override - public void releaseOutput(Object output) { - Simulation.this.releaseOutput(output); - - } - - @Override - public void releaseEvent(final Event event) { - Simulation.this.releaseEvent(event); - - } - - @Override - public void subscribe(EventLabel eventLabel, BiConsumer eventConsumer) { - Simulation.this.subscribeActorToEventByLabel(eventLabel, eventConsumer); - } - - @Override - public void subscribe(Class eventClass, BiConsumer eventConsumer) { - Simulation.this.subscribeActorToEventByClass(eventClass, eventConsumer); - } - - @Override - public void unsubscribe(EventLabel eventLabel) { - Simulation.this.unsubscribeActorFromEventByLabel(eventLabel); - } - - @Override - public void addEventLabeler(EventLabeler eventLabeler) { - Simulation.this.addEventLabeler(eventLabeler); - } - - @Override - public void subscribeToSimulationClose(Consumer consumer) { - subscribeActorToSimulationClose(consumer); - } - - @Override - public boolean subscribersExist(Class eventClass) { - return Simulation.this.subscribersExistForEvent(eventClass); - } - - @Override - public ActorId addActor(Consumer consumer) { - return Simulation.this.addActor(consumer); - } - - @Override - public void removeActor(ActorId actorId) { - Simulation.this.removeActor(actorId); - } - - @Override - public void unsubscribe(Class eventClass) { - unSubscribeActorFromEventByClass(eventClass); - } - - } - - private static final String LINE_SEPARATOR = System.getProperty("line.separator"); - - private static class PlanRec { - - private Planner planner; - private double time; - private long arrivalId; - private boolean isActive; - - private Consumer actorPlan; - private ActorId actorId; - - private Consumer dataManagerPlan; - private DataManagerId dataManagerId; - - private Object key; - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - - builder.append("time = "); - builder.append(time); - builder.append(LINE_SEPARATOR); - - builder.append("arrivalId = "); - builder.append(arrivalId); - builder.append(LINE_SEPARATOR); - - builder.append("key = "); - builder.append(key); - builder.append(LINE_SEPARATOR); - - builder.append(planner); - builder.append(" = "); - switch (planner) { - case ACTOR: - builder.append(actorId); - break; - case DATA_MANAGER: - builder.append(dataManagerId); - break; - default: - throw new RuntimeException("unhandled planner case"); - - } - builder.append(LINE_SEPARATOR); - builder.append(LINE_SEPARATOR); - - return builder.toString(); - } - } - - public static class Builder { - - private Data data = new Data(); - - private Builder() { - - } - - /** - * Sets the output consumer for the simulation. Tolerates null. - */ - public Builder setOutputConsumer(Consumer outputConsumer) { - data.outputConsumer = outputConsumer; - return this; - } - - /** - * Adds a plugin initializer to this builder for inclusion in the - * simulation - * - * @throws ContractException - *
  • {@link NucleusError#NULL_PLUGIN_INITIALIZER} if the - * plugin intializer is null - * - */ - - public Builder addPlugin(Plugin plugin) { - if (plugin == null) { - throw new ContractException(NucleusError.NULL_PLUGIN); - } - data.plugins.add(plugin); - return this; - } - - /** - * Returns an Engine instance that is initialized with the plugins and - * output consumer collected by this builder. - */ - public Simulation build() { - try { - return new Simulation(data); - } finally { - data = new Data(); - } - } - } - - /* - * Defines the type of actor that has created a plan. THE ORDER OF THIS ENUM - * IS CRITICAL TO THE FUNCTION OF THE SIMULATION! - */ - private static enum Planner { - DATA_MANAGER, ACTOR - } - - private static class DataManagerContextImpl implements DataManagerContext { - private final DataManagerId dataManagerId; - private final Simulation simulation; - - private DataManagerContextImpl(Simulation simulation, DataManagerId dataManagerId) { - this.simulation = simulation; - this.dataManagerId = dataManagerId; - } - - @Override - public ActorId addActor(Consumer consumer) { - return simulation.addActor(consumer); - } - - @Override - public void addPlan(final Consumer plan, final double planTime) { - simulation.addDataManagerPlan(dataManagerId, plan, planTime, true, null); - } - - @Override - public void addKeyedPlan(final Consumer plan, final double planTime, final Object key) { - simulation.validatePlanKeyNotNull(key); - simulation.validateDataManagerPlanKeyNotDuplicate(dataManagerId, key); - simulation.addDataManagerPlan(dataManagerId, plan, planTime, true, key); - } - - @Override - public void addPassivePlan(final Consumer plan, final double planTime) { - simulation.addDataManagerPlan(dataManagerId, plan, planTime, false, null); - } - - @Override - public void addPassiveKeyedPlan(final Consumer plan, final double planTime, final Object key) { - simulation.validatePlanKeyNotNull(key); - simulation.validateDataManagerPlanKeyNotDuplicate(dataManagerId, key); - simulation.addDataManagerPlan(dataManagerId, plan, planTime, false, key); - } - - @Override - public boolean actorExists(final ActorId actorId) { - return simulation.actorExists(actorId); - } - - @Override - public T getDataManager(Class dataManagerClass) { - return simulation.getDataManager(dataManagerClass); - } - - @SuppressWarnings("unchecked") - @Override - public > Optional getPlan(final Object key) { - return (Optional) simulation.getDataManagerPlan(dataManagerId, key); - } - - @Override - public List getPlanKeys() { - return simulation.getDataManagerPlanKeys(dataManagerId); - } - - @Override - public Optional getPlanTime(final Object key) { - return simulation.getDataManagerPlanTime(dataManagerId, key); - } - - @Override - public double getTime() { - return simulation.time; - } - - @Override - public void halt() { - simulation.halt(); - } - - @Override - public void releaseEvent(final Event event) { - simulation.releaseEvent(event); - - } - - @Override - public void removeActor(final ActorId actorId) { - simulation.removeActor(actorId); - } - - @Override - public Optional removePlan(final Object key) { - return simulation.removeDataManagerPlan(dataManagerId, key); - } - - @Override - public void releaseOutput(Object output) { - simulation.releaseOutput(output); - } - - @Override - public void unSubscribe(Class eventClass) { - simulation.unSubscribeDataManagerFromEvent(dataManagerId, eventClass); - } - - @Override - public void addEventLabeler(EventLabeler eventLabeler) { - simulation.addEventLabeler(eventLabeler); - } - - @Override - public boolean subscribersExist(Class eventClass) { - return simulation.subscribersExistForEvent(eventClass); - } - - @Override - public void subscribePostOrder(Class eventClass, BiConsumer eventConsumer) { - simulation.subscribeDataManagerToEventPostPhase(dataManagerId, eventClass, eventConsumer); - } - - @Override - public void subscribe(Class eventClass, BiConsumer eventConsumer) { - simulation.subscribeDataManagerToEventExecutionPhase(dataManagerId, eventClass, eventConsumer); - } - - @Override - public DataManagerId getDataManagerId() { - return dataManagerId; - } - - @Override - public void subscribeToSimulationClose(Consumer consumer) { - simulation.subscribeDataManagerToSimulationClose(dataManagerId, consumer); - } - - } - - private static class Data { - private List plugins = new ArrayList<>(); - private Consumer outputConsumer; - } - - /** - * Returns a reusable EngineBuilder instance - */ - public static Builder builder() { - return new Builder(); - } - - private final Comparator futureComparable = new Comparator() { - - @Override - public int compare(final PlanRec plannedEvent1, final PlanRec plannedEvent2) { - int result = Double.compare(plannedEvent1.time, plannedEvent2.time); - if (result == 0) { - result = plannedEvent1.planner.compareTo(plannedEvent2.planner); - if (result == 0) { - result = Long.compare(plannedEvent1.arrivalId, plannedEvent2.arrivalId); - } - } - return result; - } - }; - - // planning - private long masterPlanningArrivalId; - private double time; - private boolean processEvents = true; - private int activePlanCount; - private final PriorityQueue planningQueue = new PriorityQueue<>(futureComparable); - - // actors - - private final Map> simulationCloseActorCallbacks = new LinkedHashMap<>(); - - private final Map> simulationCloseDataManagerCallbacks = new LinkedHashMap<>(); - - private boolean started; - - private final PluginContext pluginContext = new PluginContextImpl(); - - private PluginId focalPluginId; - - private final Map, PluginData> basePluginDataMap = new LinkedHashMap<>(); - private final Map, PluginData> workingPluginDataMap = new LinkedHashMap<>(); - - private final Data data; - - private Simulation(Data data) { - this.data = data; - } - - private void validateActorPlan(final Consumer plan) { - if (plan == null) { - throw new ContractException(NucleusError.NULL_PLAN); - } - } - - private void validateDataManagerPlan(final Consumer plan) { - if (plan == null) { - throw new ContractException(NucleusError.NULL_PLAN); - } - } - - private void addActorPlan(final Consumer plan, final double time, final boolean isActivePlan, final Object key) { - - validatePlanTime(time); - validateActorPlan(plan); - - final PlanRec planRec = new PlanRec(); - planRec.isActive = isActivePlan; - planRec.arrivalId = masterPlanningArrivalId++; - planRec.planner = Planner.ACTOR; - planRec.time = FastMath.max(time, this.time); - planRec.actorPlan = plan; - planRec.key = key; - - Map map; - - planRec.actorId = focalActorId; - - if (key != null) { - map = actorPlanMap.get(focalActorId); - if (map == null) { - map = new LinkedHashMap<>(); - actorPlanMap.put(focalActorId, map); - } - map.put(key, planRec); - } - - if (isActivePlan) { - activePlanCount++; - } - planningQueue.add(planRec); - - } - - private void addDataManagerPlan(final DataManagerId dataManagerId, final Consumer plan, final double time, final boolean isActivePlan, final Object key) { - - validateDataManagerPlan(plan); - validatePlanTime(time); - - final PlanRec planRec = new PlanRec(); - planRec.isActive = isActivePlan; - planRec.arrivalId = masterPlanningArrivalId++; - planRec.planner = Planner.DATA_MANAGER; - planRec.time = FastMath.max(time, this.time); - planRec.dataManagerPlan = plan; - planRec.key = key; - - Map map; - - planRec.dataManagerId = dataManagerId; - if (key != null) { - map = dataManagerPlanMap.get(dataManagerId); - if (map == null) { - map = new LinkedHashMap<>(); - dataManagerPlanMap.put(dataManagerId, map); - } - map.put(key, planRec); - } - - if (isActivePlan) { - activePlanCount++; - } - planningQueue.add(planRec); - } - - private void validateDataManagerPlanKeyNotDuplicate(DataManagerId dataManagerId, final Object key) { - if (getDataManagerPlan(dataManagerId, key).isPresent()) { - throw new ContractException(NucleusError.DUPLICATE_PLAN_KEY); - } - } - - private void validateActorPlanKeyNotDuplicate(final Object key) { - if (getActorPlan(key).isPresent()) { - throw new ContractException(NucleusError.DUPLICATE_PLAN_KEY); - } - } - - @SuppressWarnings("unchecked") - private Optional removeActorPlan(final Object key) { - validatePlanKeyNotNull(key); - - Map map = actorPlanMap.get(focalActorId); - - T result = null; - if (map != null) { - final PlanRec planRecord = map.remove(key); - if (planRecord != null) { - result = (T) planRecord.actorPlan; - planRecord.actorPlan = null; - } - } - return Optional.ofNullable(result); - - } - - private List getOrderedPlugins() { - - MutableGraph pluginDependencyGraph = new MutableGraph<>(); - - Map pluginMap = new LinkedHashMap<>(); - - /* - * Add the nodes to the graph, check for duplicate ids, build the - * mapping from plugin id back to plugin - */ - for (Plugin plugin : data.plugins) { - focalPluginId = plugin.getPluginId(); - pluginMap.put(focalPluginId, plugin); - // ensure that there are no duplicate plugins - if (pluginDependencyGraph.containsNode(focalPluginId)) { - throw new ContractException(NucleusError.DUPLICATE_PLUGIN, focalPluginId); - } - pluginDependencyGraph.addNode(focalPluginId); - focalPluginId = null; - } - - // Add the edges to the graph - for (Plugin plugin : data.plugins) { - focalPluginId = plugin.getPluginId(); - for (PluginId pluginId : plugin.getPluginDependencies()) { - pluginDependencyGraph.addEdge(new Object(), focalPluginId, pluginId); - } - focalPluginId = null; - } - - /* - * Check for missing plugins from the plugin dependencies that were - * collected from the known plugins. - */ - for (PluginId pluginId : pluginDependencyGraph.getNodes()) { - if (!pluginMap.containsKey(pluginId)) { - List inboundEdges = pluginDependencyGraph.getInboundEdges(pluginId); - StringBuilder sb = new StringBuilder(); - sb.append("cannot locate instance of "); - sb.append(pluginId); - sb.append(" needed for "); - boolean first = true; - for (Object edge : inboundEdges) { - if (first) { - first = false; - } else { - sb.append(", "); - } - PluginId dependentPluginId = pluginDependencyGraph.getOriginNode(edge); - sb.append(dependentPluginId); - } - throw new ContractException(NucleusError.MISSING_PLUGIN, sb.toString()); - } - } - - /* - * Determine whether the graph is acyclic and generate a graph depth - * evaluator for the graph so that we can determine the order of - * initialization. - */ - Optional> optional = GraphDepthEvaluator.getGraphDepthEvaluator(pluginDependencyGraph.toGraph()); - - if (!optional.isPresent()) { - /* - * Explain in detail why there is a circular dependency - */ - - Graph g = pluginDependencyGraph.toGraph(); - g = Graphs.getSourceSinkReducedGraph(g); - List> cutGraphs = Graphs.cutGraph(g); - StringBuilder sb = new StringBuilder(); - String lineSeparator = System.getProperty("line.separator"); - sb.append(lineSeparator); - boolean firstCutGraph = true; - - for (Graph cutGraph : cutGraphs) { - if (firstCutGraph) { - firstCutGraph = false; - } else { - sb.append(lineSeparator); - } - sb.append("Dependency group: "); - sb.append(lineSeparator); - Set nodes = cutGraph.getNodes().stream().collect(Collectors.toCollection(LinkedHashSet::new)); - - for (PluginId node : nodes) { - sb.append("\t"); - sb.append(node); - sb.append(" requires:"); - sb.append(lineSeparator); - for (Object edge : cutGraph.getInboundEdges(node)) { - PluginId dependencyNode = cutGraph.getOriginNode(edge); - if (nodes.contains(dependencyNode)) { - sb.append("\t"); - sb.append("\t"); - sb.append(dependencyNode); - sb.append(lineSeparator); - } - } - } - } - throw new ContractException(NucleusError.CIRCULAR_PLUGIN_DEPENDENCIES, sb.toString()); - } - - // the graph is acyclic, so the depth evaluator is present - GraphDepthEvaluator graphDepthEvaluator = optional.get(); - - List orderedPluginIds = graphDepthEvaluator.getNodesInRankOrder(); - - List orderedPlugins = new ArrayList<>(); - for (PluginId pluginId : orderedPluginIds) { - orderedPlugins.add(pluginMap.get(pluginId)); - } - return orderedPlugins; - } - - /** - * Executes this Simulation instance. Contributed plugin initializers are - * accessed in the order of their addition to the builder. Actors and data - * managers are organized based on their plugin dependency ordering. Time - * starts at zero and flows based on planning. When all plans are executed, - * time stops and the simulation halts. - * - * @throws ContractException - *
  • {@link NucleusError#REPEATED_EXECUTION} if execute is - * invoked more than once - * - *
  • {@link NucleusError#MISSING_PLUGIN} if the contributed - * plugins contain dependencies on plugins that have not been - * added to the simulation - * - *
  • {@link NucleusError#MISSING_PLUGIN} if the contributed - * plugins contain duplicate plugin ids - * - *
  • {@link NucleusError#CIRCULAR_PLUGIN_DEPENDENCIES} if the - * contributed plugins form a circular chain of dependencies - *
  • {@link NucleusError#DATA_MANAGER_INITIALIZATION_FAILURE} - * if a data manager does not invoke - * {@linkplain DataManager#init(DataManagerContext)} in its - * override of init(). - * - * - */ - public void execute() { - - // start the simulation - if (started) { - throw new ContractException(NucleusError.REPEATED_EXECUTION); - } - started = true; - - // set the output consumer - outputConsumer = data.outputConsumer; - - /* - * Get the plugins listed in dependency order - */ - List orderedPlugins = getOrderedPlugins(); - - // Make the plugin data available - for (Plugin plugin : orderedPlugins) { - focalPluginId = plugin.getPluginId(); - for (PluginData pluginData : plugin.getPluginDatas()) { - basePluginDataMap.put(pluginData.getClass(), pluginData); - } - } - - // Have each plugin contribute data managers and actors - for (Plugin plugin : orderedPlugins) { - focalPluginId = plugin.getPluginId(); - Optional> optionalInitializer = plugin.getInitializer(); - if (optionalInitializer.isPresent()) { - optionalInitializer.get().accept(pluginContext); - } - focalPluginId = null; - } - - // initialize the data managers - for (DataManagerId dataManagerId : dataManagerIdToDataManagerMap.keySet()) { - DataManager dataManager = dataManagerIdToDataManagerMap.get(dataManagerId); - DataManagerContext dataManagerContext = dataManagerIdToContextMap.get(dataManagerId); - dataManager.init(dataManagerContext); - if (!dataManager.isInitialized()) { - throw new ContractException(NucleusError.DATA_MANAGER_INITIALIZATION_FAILURE, dataManager.getClass().getSimpleName()); - } - } - - // initialize the actors by flushing the actor queue - executeActorQueue(); - - // start the planning-based portion of the simulation where time flows - while (processEvents && (activePlanCount > 0)) { - final PlanRec planRec = planningQueue.poll(); - time = planRec.time; - if (planRec.isActive) { - activePlanCount--; - } - switch (planRec.planner) { - case ACTOR: - - if (planRec.actorPlan != null) { - if (planRec.key != null) { - actorPlanMap.get(planRec.actorId).remove(planRec.key); - } - ActorContentRec actorContentRec = new ActorContentRec(); - actorContentRec.actorId = planRec.actorId; - actorContentRec.plan = planRec.actorPlan; - actorQueue.add(actorContentRec); - executeActorQueue(); - } - break; - case DATA_MANAGER: - if (planRec.dataManagerPlan != null) { - if (planRec.key != null) { - dataManagerPlanMap.get(planRec.dataManagerId).remove(planRec.key); - } - DataManagerContext dataManagerContext = dataManagerIdToContextMap.get(planRec.dataManagerId); - planRec.dataManagerPlan.accept(dataManagerContext); - executeActorQueue(); - } - break; - default: - throw new RuntimeException("unhandled planner type " + planRec.planner); - } - } - - // signal to the data managers that the simulation is closing - for (DataManagerId dataManagerId : simulationCloseDataManagerCallbacks.keySet()) { - Consumer dataManagerCloseCallback = simulationCloseDataManagerCallbacks.get(dataManagerId); - DataManagerContext dataManagerContext = dataManagerIdToContextMap.get(dataManagerId); - dataManagerCloseCallback.accept(dataManagerContext); - } - - // signal to the actors that the simulation is closing - for (ActorId actorId : simulationCloseActorCallbacks.keySet()) { - if (actorIds.get(actorId.getValue()) != null) { - focalActorId = actorId; - Consumer simulationCloseCallback = simulationCloseActorCallbacks.get(actorId); - simulationCloseCallback.accept(actorContext); - focalActorId = null; - } - } - - } - - private void executeActorQueue() { - while (!actorQueue.isEmpty()) { - final ActorContentRec actorContentRec = actorQueue.pollFirst(); - - if (containsDeletedActors) { - /* - * we know that the actor id was valid at some point and that - * the actorMap never shrinks, so we do not have to range check - * the actor id - */ - if (actorIds.get(actorContentRec.actorId.getValue()) == null) { - continue; - } - } - - focalActorId = actorContentRec.actorId; - if (actorContentRec.event != null) { - actorContentRec.metaActorEventConsumer.handleEvent(actorContentRec.event); - } else { - actorContentRec.plan.accept(actorContext); - } - focalActorId = null; - } - - } - - private Optional> getActorPlan(final Object key) { - validatePlanKeyNotNull(key); - Map map = actorPlanMap.get(focalActorId); - Consumer result = null; - if (map != null) { - final PlanRec planRecord = map.get(key); - if (planRecord != null) { - result = planRecord.actorPlan; - } - } - return Optional.ofNullable(result); - } - - private Optional> getDataManagerPlan(DataManagerId dataManagerId, final Object key) { - validatePlanKeyNotNull(key); - Map map = dataManagerPlanMap.get(dataManagerId); - Consumer result = null; - if (map != null) { - final PlanRec planRecord = map.get(key); - if (planRecord != null) { - result = planRecord.dataManagerPlan; - } - } - return Optional.ofNullable(result); - } - - private List getActorPlanKeys() { - Map map = actorPlanMap.get(focalActorId); - if (map != null) { - return new ArrayList<>(map.keySet()); - } - return new ArrayList<>(); - } - - private List getDataManagerPlanKeys(DataManagerId dataManagerId) { - Map map = dataManagerPlanMap.get(dataManagerId); - if (map != null) { - return new ArrayList<>(map.keySet()); - } - return new ArrayList<>(); - } - - private Optional getActorPlanTime(final Object key) { - validatePlanKeyNotNull(key); - Map map = actorPlanMap.get(focalActorId); - Double result = null; - if (map != null) { - final PlanRec planRecord = map.get(key); - if (planRecord != null) { - result = planRecord.time; - } - } - return Optional.ofNullable(result); - } - - private Optional getDataManagerPlanTime(final DataManagerId dataManagerId, final Object key) { - validatePlanKeyNotNull(key); - Map map = dataManagerPlanMap.get(dataManagerId); - Double result = null; - if (map != null) { - final PlanRec planRecord = map.get(key); - if (planRecord != null) { - result = planRecord.time; - } - } - return Optional.ofNullable(result); - } - - private void halt() { - if (processEvents) { - processEvents = false; - } - } - - private Consumer outputConsumer; - - /* - * We drop the plan out of the plan map and thus have no way to reference - * the plan directly. However, we do not remove the plan from the planQueue - * and instead simply mark the plan record as cancelled. When the cancelled - * plan record reaches the top of the queue, it is popped off and ignored. - * This avoids the inefficiency of walking the queue and removing the plan. - * - * Note that we are allowing components to delete plans that do not exist. - * This was done to ease any bookkeeping burdens on the component and seems - * generally harmless. - * - * - */ - - @SuppressWarnings("unchecked") - private Optional removeDataManagerPlan(DataManagerId dataManagerId, final Object key) { - validatePlanKeyNotNull(key); - - Map map = dataManagerPlanMap.get(dataManagerId); - T result = null; - if (map != null) { - final PlanRec planRecord = map.remove(key); - if (planRecord != null) { - result = (T) planRecord.dataManagerPlan; - planRecord.dataManagerPlan = null; - } - } - return Optional.ofNullable(result); - } - - private void validatePlanKeyNotNull(final Object key) { - if (key == null) { - throw new ContractException(NucleusError.NULL_PLAN_KEY, ""); - } - } - - private void validatePlanTime(final double planTime) { - if (planTime < time) { - throw new ContractException(NucleusError.PAST_PLANNING_TIME); - } - } - - private void subscribeActorToEventByClass(Class eventClass, BiConsumer eventConsumer) { - if (eventClass == null) { - throw new ContractException(NucleusError.NULL_EVENT_CLASS); - } - - if (eventConsumer == null) { - throw new ContractException(NucleusError.NULL_EVENT_CONSUMER); - } - - Map> map = actorEventMap.get(eventClass); - if (map == null) { - map = new LinkedHashMap<>(); - actorEventMap.put(eventClass, map); - } - MetaActorEventConsumer metaActorEventConsumer = new MetaActorEventConsumer<>(actorContext, eventConsumer); - map.put(focalActorId, metaActorEventConsumer); - } - - private void releaseOutput(Object output) { - if (outputConsumer != null) { - outputConsumer.accept(output); - } - } - - private static class MetaEventLabeler { - - private final EventLabeler eventLabeler; - private final Class eventClass; - private final EventLabelerId id; - - public MetaEventLabeler(EventLabeler eventLabeler, Class eventClass) { - this.id = eventLabeler.getEventLabelerId(); - this.eventClass = eventClass; - this.eventLabeler = eventLabeler; - - } - - @SuppressWarnings("unchecked") - public EventLabel getEventLabel(SimulationContext simulationContext, Event event) { - EventLabel eventLabel = eventLabeler.getEventLabel(simulationContext, (T) event); - if (eventLabel == null) { - throw new ContractException(NucleusError.NULL_EVENT_LABEL, - "event labeler for class = " + eventLabeler.getEventClass().getSimpleName() + " and label id =" + eventLabeler.getEventLabelerId() + " returned a null event label"); - } - if (!eventClass.equals(eventLabel.getEventClass())) { - throw new ContractException(NucleusError.LABLER_GENERATED_LABEL_WITH_INCORRECT_EVENT_CLASS); - } - if (!id.equals(eventLabel.getLabelerId())) { - throw new ContractException(NucleusError.LABLER_GENERATED_LABEL_WITH_INCORRECT_ID); - } - if (!event.getPrimaryKeyValue().equals(eventLabel.getPrimaryKeyValue())) { - throw new ContractException(NucleusError.LABLER_GENERATED_LABEL_WITH_INCORRECT_PRIMARY_KEY); - } - return eventLabel; - } - - public EventLabelerId getId() { - return id; - } - - } - - private static class MetaActorEventConsumer { - - private final BiConsumer eventConsumer; - - private final ActorContext context; - - public MetaActorEventConsumer(ActorContext context, BiConsumer eventConsumer) { - this.eventConsumer = eventConsumer; - this.context = context; - } - - @SuppressWarnings("unchecked") - public void handleEvent(Event event) { - - try { - eventConsumer.accept(context, (T) event); - } catch (ClassCastException e) { - throw new RuntimeException("Class cast exception likely due to improperly formed event label", e); - } - - } - } - - private boolean actorExists(final ActorId actorId) { - if (actorId == null) { - return false; - } - int index = actorId.getValue(); - if (index < 0) { - return false; - } - if (index >= actorIds.size()) { - return false; - } - - return actorIds.get(index) != null; - } - - private void subscribeActorToSimulationClose(Consumer consumer) { - if (consumer == null) { - throw new ContractException(NucleusError.NULL_ACTOR_CONTEXT_CONSUMER); - } - simulationCloseActorCallbacks.put(focalActorId, consumer); - } - - private void subscribeDataManagerToSimulationClose(DataManagerId dataManagerId, Consumer consumer) { - if (consumer == null) { - throw new ContractException(NucleusError.NULL_DATA_MANAGER_CONTEXT_CONSUMER); - } - simulationCloseDataManagerCallbacks.put(dataManagerId, consumer); - } - - private static enum EventPhase { - EXECUTION, POST_EXECUTION - } - - private void subscribeDataManagerToEventExecutionPhase(DataManagerId dataManagerId, Class eventClass, BiConsumer eventConsumer) { - if (eventClass == null) { - throw new ContractException(NucleusError.NULL_EVENT_CLASS); - } - if (eventConsumer == null) { - throw new ContractException(NucleusError.NULL_EVENT_CONSUMER); - } - - List> list = dataManagerEventMap.get(eventClass); - if (list == null) { - list = new ArrayList<>(); - dataManagerEventMap.put(eventClass, list); - } - DataManagerContext dataManagerContext = dataManagerIdToContextMap.get(dataManagerId); - MetaDataManagerEventConsumer metaDataManagerEventConsumer = new MetaDataManagerEventConsumer<>(dataManagerContext, dataManagerId, eventConsumer, EventPhase.EXECUTION); - - int insertionIndex = -1; - - for (int i = 0; i < list.size(); i++) { - MetaDataManagerEventConsumer m = list.get(i); - if (m.eventPhase == EventPhase.POST_EXECUTION) { - insertionIndex = i; - break; - } - } - - if (insertionIndex < 0) { - list.add(metaDataManagerEventConsumer); - } else { - list.add(insertionIndex, metaDataManagerEventConsumer); - } - } - - private void subscribeDataManagerToEventPostPhase(DataManagerId dataManagerId, Class eventClass, BiConsumer eventConsumer) { - if (eventClass == null) { - throw new ContractException(NucleusError.NULL_EVENT_CLASS); - } - if (eventConsumer == null) { - throw new ContractException(NucleusError.NULL_EVENT_CONSUMER); - } - - List> list = dataManagerEventMap.get(eventClass); - if (list == null) { - list = new ArrayList<>(); - dataManagerEventMap.put(eventClass, list); - } - DataManagerContext dataManagerContext = dataManagerIdToContextMap.get(dataManagerId); - MetaDataManagerEventConsumer metaDataManagerEventConsumer = new MetaDataManagerEventConsumer<>(dataManagerContext, dataManagerId, eventConsumer, EventPhase.POST_EXECUTION); - - list.add(metaDataManagerEventConsumer); - - } - - private void unSubscribeDataManagerFromEvent(DataManagerId dataManagerId, Class eventClass) { - if (eventClass == null) { - throw new ContractException(NucleusError.NULL_EVENT_CLASS); - } - - List> list = dataManagerEventMap.get(eventClass); - - if (list != null) { - Iterator> iterator = list.iterator(); - while (iterator.hasNext()) { - MetaDataManagerEventConsumer metaDataManagerEventConsumer = iterator.next(); - if (metaDataManagerEventConsumer.dataManagerId.equals(dataManagerId)) { - iterator.remove(); - } - } - - if (list.isEmpty()) { - dataManagerEventMap.remove(eventClass); - } - } - } - - private void unSubscribeActorFromEventByClass(Class eventClass) { - if (eventClass == null) { - throw new ContractException(NucleusError.NULL_EVENT_CLASS); - } - Map> map = actorEventMap.get(eventClass); - map.remove(focalActorId); - } - - private void releaseEvent(final Event event) { - - if (event == null) { - throw new ContractException(NucleusError.NULL_EVENT); - } - - Map> consumerMap = actorEventMap.get(event.getClass()); - if (consumerMap != null) { - for (final ActorId actorId : consumerMap.keySet()) { - MetaActorEventConsumer metaActorEventConsumer = consumerMap.get(actorId); - final ActorContentRec contentRec = new ActorContentRec(); - contentRec.actorId = actorId; - contentRec.event = event; - contentRec.metaActorEventConsumer = metaActorEventConsumer; - actorQueue.add(contentRec); - } - } - - broadcastEventToActorSubscribers(event); - - List> list = dataManagerEventMap.get(event.getClass()); - if (list != null) { - for (MetaDataManagerEventConsumer metaDataManagerEventConsumer : list) { - metaDataManagerEventConsumer.handleEvent(event); - } - } - } - - private ActorId addActor(Consumer consumer) { - - if (consumer == null) { - throw new ContractException(NucleusError.NULL_ACTOR_CONTEXT_CONSUMER); - } - - ActorId result = new ActorId(actorIds.size()); - actorIds.add(result); - - final ActorContentRec actorContentRec = new ActorContentRec(); - actorContentRec.actorId = result; - actorContentRec.plan = consumer; - actorQueue.add(actorContentRec); - return result; - } - - private void removeActor(final ActorId actorId) { - if (actorId == null) { - throw new ContractException(NucleusError.NULL_ACTOR_ID); - } - - int actorIndex = actorId.getValue(); - - if (actorIndex < 0) { - throw new ContractException(NucleusError.UNKNOWN_ACTOR_ID); - } - - if (actorIndex >= actorIds.size()) { - throw new ContractException(NucleusError.UNKNOWN_ACTOR_ID); - } - - ActorId existingActorId = actorIds.get(actorIndex); - - if (existingActorId == null) { - throw new ContractException(NucleusError.UNKNOWN_ACTOR_ID); - } - - actorIds.set(actorIndex, null); - - containsDeletedActors = true; - } - - @SuppressWarnings("unchecked") - private T getDataManager(Class dataManagerClass) { - - if (dataManagerClass == null) { - throw new ContractException(NucleusError.NULL_DATA_MANAGER_CLASS); - } - - DataManager dataManager = workingClassToDataManagerMap.get(dataManagerClass); - /* - * If the working map does not contain the data manager, try to find a - * single match from the base map that was collected from the plugins. - * - * If two or more matches are found, then throw an exception. - * - * If exactly one match is found, update the working map. - * - * If no matches are found, nothing is done, but we are vulnerable to - * somewhat slower performance if the data manager is sought repeatedly. - */ - if (dataManager == null) { - List> candidates = new ArrayList<>(); - for (Class c : baseClassToDataManagerMap.keySet()) { - if (dataManagerClass.isAssignableFrom(c)) { - candidates.add(c); - } - } - if (candidates.size() > 1) { - throw new ContractException(NucleusError.AMBIGUOUS_DATA_MANAGER_CLASS); - } - if (candidates.size() == 1) { - dataManager = baseClassToDataManagerMap.get(candidates.get(0)); - workingClassToDataManagerMap.put(dataManagerClass, dataManager); - } - } - - if (dataManager == null) { - throw new ContractException(NucleusError.UNKNOWN_DATA_MANAGER, " : " + dataManagerClass.getSimpleName()); - } - return (T) dataManager; - } - - ///////////////////////////////// - // data manager support - ///////////////////////////////// - - private int masterDataManagerIndex; - - private static class MetaDataManagerEventConsumer { - - private final BiConsumer dataManagerEventConsumer; - - private final DataManagerContext context; - - private final DataManagerId dataManagerId; - - private final EventPhase eventPhase; - - public MetaDataManagerEventConsumer(DataManagerContext context, DataManagerId dataManagerId, BiConsumer eventConsumer, EventPhase eventPhase) { - this.dataManagerEventConsumer = eventConsumer; - this.context = context; - this.dataManagerId = dataManagerId; - this.eventPhase = eventPhase; - } - - @SuppressWarnings("unchecked") - public void handleEvent(Event event) { - - try { - dataManagerEventConsumer.accept(context, (T) event); - } catch (ClassCastException e) { - throw new RuntimeException("Class cast exception likely due to improperly formed event label", e); - } - - } - } - - // used for subscriptions - private final Map, List>> dataManagerEventMap = new LinkedHashMap<>(); - - // used for retrieving and canceling plans owned by data managers - private final Map> dataManagerPlanMap = new LinkedHashMap<>(); - - // used to locate data managers by class type - private Map, DataManager> baseClassToDataManagerMap = new LinkedHashMap<>(); - private Map, DataManager> workingClassToDataManagerMap = new LinkedHashMap<>(); - - // map of contexts for each data manager - private Map dataManagerIdToContextMap = new LinkedHashMap<>(); - - // map of data manager id to data manager instances - private Map dataManagerIdToDataManagerMap = new LinkedHashMap<>(); - - ////////////////////////////// - // actor support - ////////////////////////////// - - private final Map, Map>> actorEventMap = new LinkedHashMap<>(); - - private final ActorContext actorContext = new ActorContextImpl(); - - private final List actorIds = new ArrayList<>(); - - private boolean containsDeletedActors; - - private final Map> actorPlanMap = new LinkedHashMap<>(); - - private final Deque actorQueue = new ArrayDeque<>(); - - private ActorId focalActorId; - - private static class ActorContentRec { - - private Event event; - - private MetaActorEventConsumer metaActorEventConsumer; - - private Consumer plan; - - private ActorId actorId; - - } - - ///////////////////////////////////////////////////////////////////////// - - private void addEventLabeler(EventLabeler eventLabeler) { - if (eventLabeler == null) { - throw new ContractException(NucleusError.NULL_EVENT_LABELER); - } - - Class eventClass = eventLabeler.getEventClass(); - - EventLabelerId id = eventLabeler.getEventLabelerId(); - - if (id_Labeler_Map.containsKey(id)) { - throw new ContractException(NucleusError.DUPLICATE_LABELER_ID_IN_EVENT_LABELER); - } - - MetaEventLabeler metaEventLabeler = new MetaEventLabeler<>(eventLabeler, eventClass); - - id_Labeler_Map.put(metaEventLabeler.getId(), metaEventLabeler); - } - - private void broadcastEventToActorSubscribers(final Event event) { - Map, Map>>>> map1 = actorPubSub.get(event.getClass()); - if (map1 == null) { - return; - } - Object primaryKeyValue = event.getPrimaryKeyValue(); - Map, Map>>> map2 = map1.get(primaryKeyValue); - if (map2 == null) { - return; - } - - for (EventLabelerId eventLabelerId : map2.keySet()) { - MetaEventLabeler metaEventLabeler = id_Labeler_Map.get(eventLabelerId); - EventLabel eventLabel = metaEventLabeler.getEventLabel(actorContext, event); - Map, Map>> map3 = map2.get(eventLabelerId); - Map> map4 = map3.get(eventLabel); - if (map4 != null) { - for (ActorId actorId : map4.keySet()) { - MetaActorEventConsumer metaConsumer = map4.get(actorId); - final ActorContentRec actorContentRec = new ActorContentRec(); - actorContentRec.event = event; - actorContentRec.actorId = actorId; - actorContentRec.metaActorEventConsumer = metaConsumer; - actorQueue.add(actorContentRec); - - } - } - } - } - - private void subscribeActorToEventByLabel(EventLabel eventLabel, BiConsumer eventConsumer) { - - if (eventLabel == null) { - throw new ContractException(NucleusError.NULL_EVENT_LABEL); - } - - if (eventConsumer == null) { - throw new ContractException(NucleusError.NULL_EVENT_CONSUMER); - } - Class eventClass = eventLabel.getEventClass(); - EventLabelerId eventLabelerId = eventLabel.getLabelerId(); - MetaEventLabeler metaEventLabeler = id_Labeler_Map.get(eventLabelerId); - - if (metaEventLabeler == null) { - throw new ContractException(NucleusError.UNKNOWN_EVENT_LABELER); - } - - Object primaryKeyValue = eventLabel.getPrimaryKeyValue(); - - Map, Map>>>> map1 = actorPubSub.get(eventLabel.getEventClass()); - if (map1 == null) { - map1 = new LinkedHashMap<>(); - actorPubSub.put(eventClass, map1); - } - - Map, Map>>> map2 = map1.get(primaryKeyValue); - if (map2 == null) { - map2 = new LinkedHashMap<>(); - map1.put(primaryKeyValue, map2); - } - - Map, Map>> map3 = map2.get(eventLabelerId); - if (map3 == null) { - map3 = new LinkedHashMap<>(); - map2.put(eventLabelerId, map3); - } - - Map> map4 = map3.get(eventLabel); - if (map4 == null) { - map4 = new LinkedHashMap<>(); - map3.put(eventLabel, map4); - } - - MetaActorEventConsumer metaEventConsumer = new MetaActorEventConsumer<>(actorContext, eventConsumer); - map4.put(focalActorId, metaEventConsumer); - - } - - private void unsubscribeActorFromEventByLabel(EventLabel eventLabel) { - - if (eventLabel == null) { - throw new ContractException(NucleusError.NULL_EVENT_LABEL); - } - - Class eventClass = eventLabel.getEventClass(); - EventLabelerId eventLabelerId = eventLabel.getLabelerId(); - MetaEventLabeler metaEventLabeler = id_Labeler_Map.get(eventLabelerId); - if (metaEventLabeler == null) { - throw new ContractException(NucleusError.UNKNOWN_EVENT_LABELER, eventLabelerId); - } - - Object primaryKeyValue = eventLabel.getPrimaryKeyValue(); - - Map, Map>>>> map1 = actorPubSub.get(eventClass); - - if (map1 == null) { - return; - } - - Map, Map>>> map2 = map1.get(primaryKeyValue); - - if (map2 == null) { - return; - } - - Map, Map>> map3 = map2.get(eventLabelerId); - - if (map3 == null) { - return; - } - - Map> map4 = map3.get(eventLabel); - - if (map4 == null) { - return; - } - - map4.remove(focalActorId); - - if (map4.isEmpty()) { - map3.remove(eventLabel); - if (map3.isEmpty()) { - map2.remove(eventLabelerId); - if (map2.isEmpty()) { - map1.remove(primaryKeyValue); - if (map1.isEmpty()) { - actorPubSub.remove(eventClass); - } - } - } - } - } - - private boolean subscribersExistForEvent(Class eventClass) { - return (dataManagerEventMap.containsKey(eventClass) || actorPubSub.containsKey(eventClass) || actorEventMap.containsKey(eventClass)); - } - - private Map> id_Labeler_Map = new LinkedHashMap<>(); - - private Map, Map, Map>>>>> actorPubSub = new LinkedHashMap<>(); - -} \ No newline at end of file diff --git a/gcm3/src/main/java/nucleus/SimulationContext.java b/gcm3/src/main/java/nucleus/SimulationContext.java deleted file mode 100644 index 50576f13f..000000000 --- a/gcm3/src/main/java/nucleus/SimulationContext.java +++ /dev/null @@ -1,95 +0,0 @@ -package nucleus; - -import java.util.function.Consumer; - -import util.errors.ContractException; - -/** - * A context provides basic access to the nucleus simulation and the data - * managers contributed by the plugins that formed the experiment. - * - * @author Shawn Hatch - * - */ -public interface SimulationContext { - - /** - * Sends output to whatever consumer of output is registered with nucleus, - * if any - */ - public void releaseOutput(Object output); - - /** - * Broadcasts the given event to all subscribers. Data manager subscribers - * receive event immediately. Actors receive events after the current actor - * or data manager has completed its current actions. - * - * @throws ContractException - *
  • {@link NucleusError#NULL_EVENT} if the event is null - */ - public void releaseEvent(final Event event); - - /** - * - * @throws ContractException - *
  • {@linkplain NucleusError#NULL_DATA_MANAGER_CLASS} if data - * manager class is null
  • - *
  • {@linkplain NucleusError#AMBIGUOUS_DATA_MANAGER_CLASS} if - * more than one data manager matches the given class
  • - * - */ - public T getDataManager(Class dataManagerClass); - - /** - * Returns the current time in the simulation - */ - public double getTime(); - - /** - * Terminates the simulation after the current plan is fully executed. - */ - public void halt(); - - /** - * Adds an event labeler to nucleus. - * - * @throws ContractException - *
  • {@link NucleusError#NULL_EVENT_LABELER} if the event - * labeler is null - *
  • {@link NucleusError#DUPLICATE_LABELER_ID_IN_EVENT_LABELER} - * if the event labeler contains a labeler id that is the id of - * a previously added event labeler - */ - public void addEventLabeler(EventLabeler eventLabeler); - - /** - * Returns true if an only if an actor is associated with the given id. - * Tolerates null values. - */ - public boolean actorExists(ActorId actorId); - - /** - * Adds an actor to the simulation. The actor is added immediately, but the - * consumer of ActorContext is invoked after event resolution is finished - * and before time progresses. - * - * @throws ContractException - * - *
  • {@link NucleusError#NULL_ACTOR_CONTEXT_CONSUMER} if the - * actor context consumer is null - * - */ - public ActorId addActor(Consumer consumer); - - /** - * Removes the given actor from the simulation. - * - * @throws ContractException - *
  • {@link NucleusError#NULL_ACTOR_ID} if the actorId is null - *
  • {@link NucleusError#NEGATIVE_ACTOR_ID} if the actor id is - * negative - *
  • {@link NucleusError#UNKNOWN_ACTOR_ID} if the actor id - * does not correspond to a known actor - */ - public void removeActor(ActorId actorId); -} diff --git a/gcm3/src/main/java/nucleus/testsupport/testplugin/ExperimentPlanCompletionObserver.java b/gcm3/src/main/java/nucleus/testsupport/testplugin/ExperimentPlanCompletionObserver.java deleted file mode 100644 index 595abfb89..000000000 --- a/gcm3/src/main/java/nucleus/testsupport/testplugin/ExperimentPlanCompletionObserver.java +++ /dev/null @@ -1,43 +0,0 @@ -package nucleus.testsupport.testplugin; - -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Optional; - -import net.jcip.annotations.ThreadSafe; -import nucleus.ExperimentContext; - -/** - * A threadsafe output handler that subscribes to TestScenarioReport. A single - * TestScenarioReport is produced at the end of of each scenario by the - * TestPlanDataManager contributed by the TestPlugin. Each report is kept and - * can be accessed by scenario id. - * - * @author Shawn Hatch - * - */ - -@ThreadSafe -public final class ExperimentPlanCompletionObserver { - - private Map testScenarioReports = new LinkedHashMap<>(); - - /** - * Initializes this observer by subscribing to TestScenarioReport - */ - public synchronized void init(ExperimentContext experimentContext) { - experimentContext.subscribeToOutput(TestScenarioReport.class, this::handleActionCompletionReport); - } - - private synchronized void handleActionCompletionReport(ExperimentContext experimentContext, Integer scenarioId, TestScenarioReport testScenarioReport) { - testScenarioReports.put(scenarioId, testScenarioReport); - } - - /** - * Returns the TestScenarioReport collected for the given scenario id. - */ - public synchronized Optional getActionCompletionReport(Integer scenarioId) { - return Optional.ofNullable(testScenarioReports.get(scenarioId)); - } - -} diff --git a/gcm3/src/main/java/nucleus/testsupport/testplugin/ScenarioPlanCompletionObserver.java b/gcm3/src/main/java/nucleus/testsupport/testplugin/ScenarioPlanCompletionObserver.java deleted file mode 100644 index f17ad48ef..000000000 --- a/gcm3/src/main/java/nucleus/testsupport/testplugin/ScenarioPlanCompletionObserver.java +++ /dev/null @@ -1,50 +0,0 @@ -package nucleus.testsupport.testplugin; - -import net.jcip.annotations.ThreadSafe; -import util.errors.ContractException; - -/** - * A threadsafe output handler that handles all output from a simulation. A - * single TestScenarioReport is produced at the end of of each scenario by the - * TestPlanDataManager contributed by the TestPlugin. This utility expects - * exactly one TestScenarioReport and will retain the success state of that - * report. Used to determine if 1)the test plugin contained at least one plan - * and 2) all such plans were executed. - * - * @author Shawn Hatch - * - */ -@ThreadSafe -public final class ScenarioPlanCompletionObserver { - private Boolean executed; - - /** - * Handles all output from a simulation, but processes only - * TestScenarioReport items. - * - * @throws ContractException - *
  • {@linkplain TestError#DUPLICATE_TEST_SCENARIO_REPORTS} if - * multiple TestScenarioReport items are received
  • - */ - public synchronized void handleOutput(Object output) { - if (output instanceof TestScenarioReport) { - if (executed != null) { - throw new ContractException(TestError.DUPLICATE_TEST_SCENARIO_REPORTS); - } - TestScenarioReport testScenarioReport = (TestScenarioReport) output; - executed = testScenarioReport.isComplete(); - } - } - - /** - * Returns true if and only if the TestScenarioReport was received and plans - * were completed. - */ - public synchronized boolean allPlansExecuted() { - if (executed == null) { - return false; - } - return executed; - } - -} diff --git a/gcm3/src/main/java/nucleus/testsupport/testplugin/TestActor.java b/gcm3/src/main/java/nucleus/testsupport/testplugin/TestActor.java deleted file mode 100644 index 16522c847..000000000 --- a/gcm3/src/main/java/nucleus/testsupport/testplugin/TestActor.java +++ /dev/null @@ -1,49 +0,0 @@ -package nucleus.testsupport.testplugin; - -import java.util.List; - -import nucleus.ActorContext; - -/** - * Test Support actor implementation designed to execute test-defined behaviors - * from within the actor. The actor first registers its ActorId with its alias - * by registering it with the TestPlanDataManager. It then schedules the - * ActorActionPlans that were stored in the TestPluginData that were associated - * with its alias. - * - * Alias identification exists for the convenience of the test implementor so - * that tests can name actors and are not bound to the forced ordering pattern - * implied by ActorId values. - * - * @author Shawn Hatch - * - */ -public final class TestActor { - private final Object alias; - - /** - * Creates the test actor with its alias - * - */ - public TestActor(Object alias) { - this.alias = alias; - } - - /** - * Associates its ActorId. Schedules the ActorActionPlans that were stored - * in the ActionDataView that were associated with its alias. - */ - public void init(ActorContext actorContext) { - TestPlanDataManager testPlanDataManager = actorContext.getDataManager(TestPlanDataManager.class); - - List testActorPlans = testPlanDataManager.getTestActorPlans(alias); - for (final TestActorPlan testActorPlan : testActorPlans) { - if (testActorPlan.getKey() != null) { - actorContext.addKeyedPlan(testActorPlan::executeAction, testActorPlan.getScheduledTime(), testActorPlan.getKey()); - } else { - actorContext.addPlan(testActorPlan::executeAction, testActorPlan.getScheduledTime()); - } - } - } - -} diff --git a/gcm3/src/main/java/nucleus/testsupport/testplugin/TestActorPlan.java b/gcm3/src/main/java/nucleus/testsupport/testplugin/TestActorPlan.java deleted file mode 100644 index ee000a234..000000000 --- a/gcm3/src/main/java/nucleus/testsupport/testplugin/TestActorPlan.java +++ /dev/null @@ -1,163 +0,0 @@ -package nucleus.testsupport.testplugin; - -import java.util.Optional; -import java.util.function.Consumer; - -import nucleus.ActorContext; -import util.errors.ContractException; - -/** - * Test Support class that describes an action for an actor as a scheduled plan - * with an optional key. - */ -public class TestActorPlan { - - /* - * Key value generator for plans - */ - private static int masterKey; - - private static synchronized int getNextKey() { - return masterKey++; - } - - private final double scheduledTime; - - private final Object key; - - private final boolean releaseKey; - - private boolean executed; - - private final Consumer plan; - - /** - * Constructs an actor action plan. If assignKey is false, then this actor - * action plan will return an empty optional key. - * - * @throws ContractException - *
  • {@linkplain TestError#NEGATIVE_PLANNING_TIME} if the scheduled plan time is negative
  • - *
  • {@linkplain TestError#NULL_PLAN} if the plan is null
  • - * - */ - public TestActorPlan(final double scheduledTime, Consumer plan, boolean assignKey) { - if (scheduledTime < 0) { - throw new ContractException(TestError.NEGATIVE_PLANNING_TIME); - } - - if (plan == null) { - throw new ContractException(TestError.NULL_PLAN); - } - - this.scheduledTime = scheduledTime; - - this.key = getNextKey(); - - releaseKey = assignKey; - - this.plan = plan; - } - - /** - * Constructs an actor action plan. A key value will be generated. - */ - public TestActorPlan(final double scheduledTime, Consumer action) { - this(scheduledTime, action, true); - } - - /** - * Boilerplate implementation of hashCode consistent with equals() - */ - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + (executed ? 1231 : 1237); - result = prime * result + ((key == null) ? 0 : key.hashCode()); - result = prime * result + (releaseKey ? 1231 : 1237); - long temp; - temp = Double.doubleToLongBits(scheduledTime); - result = prime * result + (int) (temp ^ (temp >>> 32)); - return result; - } - - /** - * Boilerplate implementation of equals. TestActorPlans are equal if and - * only if all fields are equal. - */ - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof TestActorPlan)) { - return false; - } - TestActorPlan other = (TestActorPlan) obj; - if (executed != other.executed) { - return false; - } - if (key == null) { - if (other.key != null) { - return false; - } - } else if (!key.equals(other.key)) { - return false; - } - if (releaseKey != other.releaseKey) { - return false; - } - if (Double.doubleToLongBits(scheduledTime) != Double.doubleToLongBits(other.scheduledTime)) { - return false; - } - return true; - } - - /** - * Constructs an test actor plan from another test actor plan. - */ - public TestActorPlan(TestActorPlan testActorPlan) { - scheduledTime = testActorPlan.scheduledTime; - key = testActorPlan.key; - releaseKey = testActorPlan.releaseKey; - executed = testActorPlan.executed; - plan = testActorPlan.plan; - } - - /** - * Returns true if an only if this actor action plan was executed - */ - public boolean executed() { - return executed; - } - - /** - * Package access. Executes the embedded action and marks this action plan - * as executed. - */ - void executeAction(final ActorContext actorContext) { - try { - plan.accept(actorContext); - } finally { - executed = true; - } - } - - /** - * Returns the key, possibly empty, associated with this action plan - */ - public Optional getKey() { - if (releaseKey) { - return Optional.of(key); - } - return Optional.empty(); - } - - /** - * Returns the scheduled time for action execution - */ - public double getScheduledTime() { - return scheduledTime; - } - -} diff --git a/gcm3/src/main/java/nucleus/testsupport/testplugin/TestDataManager.java b/gcm3/src/main/java/nucleus/testsupport/testplugin/TestDataManager.java deleted file mode 100644 index 65ab4837a..000000000 --- a/gcm3/src/main/java/nucleus/testsupport/testplugin/TestDataManager.java +++ /dev/null @@ -1,52 +0,0 @@ -package nucleus.testsupport.testplugin; - -import java.util.List; - -import nucleus.DataManager; -import nucleus.DataManagerContext; - -/** - * Test Support data manager implementation designed to execute test-defined - * behaviors from within the data manager. The data manager schedules the - * TestDataManagerPlans that were stored in the TestPluginData that were - * associated with its alias. - * - * Alias identification exists for the convenience of the test implementor so - * that tests can name data managers. - * - * @author Shawn Hatch - * - */ - -public class TestDataManager extends DataManager { - private Object alias; - - /* - * Package level access for setting the alias of the data manager. This is - * done immediately after construction, before any integration into the - * experiment or simulation. - */ - void setAlias(Object alias) { - this.alias = alias; - } - - /** - * Associates its ActorId. Schedules the ActorActionPlans that were stored - * in the ActionDataView that were associated with its alias. - */ - @Override - public void init(DataManagerContext dataManagerContext) { - super.init(dataManagerContext); - TestPlanDataManager testPlanDataManager = dataManagerContext.getDataManager(TestPlanDataManager.class); - - List testDataManagerPlans = testPlanDataManager.getTestDataManagerPlans(alias); - for (final TestDataManagerPlan testDataManagerPlan : testDataManagerPlans) { - if (testDataManagerPlan.getKey() != null) { - dataManagerContext.addKeyedPlan(testDataManagerPlan::executeAction, testDataManagerPlan.getScheduledTime(), testDataManagerPlan.getKey()); - } else { - dataManagerContext.addPlan(testDataManagerPlan::executeAction, testDataManagerPlan.getScheduledTime()); - } - } - } - -} diff --git a/gcm3/src/main/java/nucleus/testsupport/testplugin/TestDataManagerPlan.java b/gcm3/src/main/java/nucleus/testsupport/testplugin/TestDataManagerPlan.java deleted file mode 100644 index 0e61dd9d7..000000000 --- a/gcm3/src/main/java/nucleus/testsupport/testplugin/TestDataManagerPlan.java +++ /dev/null @@ -1,159 +0,0 @@ -package nucleus.testsupport.testplugin; - -import java.util.Optional; -import java.util.function.Consumer; - -import nucleus.DataManagerContext; -import util.errors.ContractException; - -/** - * Test Support class that describes an action for a data manager as a scheduled - * plan with an optional key. - */ -public class TestDataManagerPlan { - - /* - * Key value generator for plans - */ - private static int masterKey; - - private static synchronized int getNextKey() { - return masterKey++; - } - - private final double scheduledTime; - - private final Object key; - - private final boolean releaseKey; - - private boolean executed; - - private final Consumer plan; - - /** - * Constructs an test actor plan from another test actor plan. - */ - public TestDataManagerPlan(TestDataManagerPlan testDataManagerPlan) { - scheduledTime = testDataManagerPlan.scheduledTime; - key = testDataManagerPlan.key; - releaseKey = testDataManagerPlan.releaseKey; - executed = testDataManagerPlan.executed; - plan = testDataManagerPlan.plan; - } - - /** - * Constructs an data manager action plan. If assignKey is false, then this - * actor action plan will return an empty optional key. - * - * @throws ContractException - *
  • {@linkplain TestError#NEGATIVE_PLANNING_TIME} if the - * scheduled plan time is negative
  • - *
  • {@linkplain TestError#NULL_PLAN} if the plan is null
  • - */ - public TestDataManagerPlan(final double scheduledTime, Consumer plan, boolean assignKey) { - if (scheduledTime < 0) { - throw new ContractException(TestError.NEGATIVE_PLANNING_TIME); - } - - if (plan == null) { - throw new ContractException(TestError.NULL_PLAN); - } - this.scheduledTime = scheduledTime; - this.key = getNextKey(); - this.releaseKey = assignKey; - this.plan = plan; - } - - /** - * Constructs an data manager action plan. A key value will be generated. - */ - public TestDataManagerPlan(final double scheduledTime, Consumer action) { - this(scheduledTime, action, true); - } - - /** - * Returns true if an only if this data manager action plan was executed - */ - public boolean executed() { - return executed; - } - - /** - * Package access. Executes the embedded action and marks this action plan - * as executed. - */ - void executeAction(final DataManagerContext dataManagerContext) { - try { - plan.accept(dataManagerContext); - } finally { - executed = true; - } - } - - /** - * Boilerplate implementation of hashCode consistent with equals() - */ - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + (executed ? 1231 : 1237); - result = prime * result + ((key == null) ? 0 : key.hashCode()); - result = prime * result + (releaseKey ? 1231 : 1237); - long temp; - temp = Double.doubleToLongBits(scheduledTime); - result = prime * result + (int) (temp ^ (temp >>> 32)); - return result; - } - - /** - * Boilerplate implementation of equals. TestDataMangerPlans are equal if - * and only if all fields are equal. - */ - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof TestDataManagerPlan)) { - return false; - } - TestDataManagerPlan other = (TestDataManagerPlan) obj; - if (executed != other.executed) { - return false; - } - if (key == null) { - if (other.key != null) { - return false; - } - } else if (!key.equals(other.key)) { - return false; - } - if (releaseKey != other.releaseKey) { - return false; - } - if (Double.doubleToLongBits(scheduledTime) != Double.doubleToLongBits(other.scheduledTime)) { - return false; - } - return true; - } - - /** - * Returns the key, possibly null, associated with this action plan - */ - public Optional getKey() { - if (releaseKey) { - return Optional.of(key); - } - return Optional.empty(); - } - - /** - * Returns the scheduled time for action execution - */ - public double getScheduledTime() { - return scheduledTime; - } - -} diff --git a/gcm3/src/main/java/nucleus/testsupport/testplugin/TestError.java b/gcm3/src/main/java/nucleus/testsupport/testplugin/TestError.java deleted file mode 100644 index db93dd2a2..000000000 --- a/gcm3/src/main/java/nucleus/testsupport/testplugin/TestError.java +++ /dev/null @@ -1,37 +0,0 @@ -package nucleus.testsupport.testplugin; - -import util.errors.ContractError; -import util.errors.ContractException; - -/** - * An enumeration supporting {@link ContractException} that acts as a general - * description of the exception. - * - * @author Shawn Hatch - * - */ -public enum TestError implements ContractError { - DUPLICATE_TEST_SCENARIO_REPORTS("Duplicate test scenario reports"), - NULL_ALIAS("Null alias value"), - NULL_DATA_MANAGER_SUPPLIER("Null data manager supplier"), - NEGATIVE_PLANNING_TIME("Negative test planning time"), - NULL_PLAN("Null plan"), - TEST_EXECUTION_FAILURE("Not all action plans were executed or no action plans were added to the action plugin"), - UNKNOWN_DATA_MANAGER_ALIAS("A data manager test plan was submitted under an alias that does not have a test data manager class type"), - - ; - - private final String description; - - private TestError(final String description) { - this.description = description; - } - - /** - * Returns the text description of the error - */ - @Override - public String getDescription() { - return description; - } -} diff --git a/gcm3/src/main/java/nucleus/testsupport/testplugin/TestPlanDataManager.java b/gcm3/src/main/java/nucleus/testsupport/testplugin/TestPlanDataManager.java deleted file mode 100644 index 878a0dd55..000000000 --- a/gcm3/src/main/java/nucleus/testsupport/testplugin/TestPlanDataManager.java +++ /dev/null @@ -1,112 +0,0 @@ -package nucleus.testsupport.testplugin; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import nucleus.DataManager; -import nucleus.DataManagerContext; - -/** - * A data manager used by test actors and test data managers to retrieve plans - * by alias ids. - * - * @author Shawn Hatch - * - */ - -public class TestPlanDataManager extends DataManager { - - private final Map> actorActionPlanMap = new LinkedHashMap<>(); - - private final Map> dataManagerActionPlanMap = new LinkedHashMap<>(); - - /** - * Constructs this data manager from the given test plugin data - */ - public TestPlanDataManager(TestPluginData testPluginData) { - - for (Object alias : testPluginData.getTestActorAliases()) { - List testActorPlans = testPluginData.getTestActorPlans(alias); - actorActionPlanMap.put(alias, testActorPlans); - } - - for (Object alias : testPluginData.getTestDataManagerAliases()) { - List testDataManagerPlans = testPluginData.getTestDataManagerPlans(alias); - dataManagerActionPlanMap.put(alias, testDataManagerPlans); - } - } - - /** - * Initializes this data manager by subscribing to simulation close. On - * close it releases a single TestScenarioReport that indicates success if - * there was at least one plan and all plans were executed. - */ - @Override - public void init(DataManagerContext dataManagerContext) { - super.init(dataManagerContext); - dataManagerContext.subscribeToSimulationClose(this::sendActionCompletionReport); - } - - /** - * Returns all plans associated with the given actor alias - */ - public List getTestActorPlans(Object alias) { - List result = new ArrayList<>(); - List list = actorActionPlanMap.get(alias); - if (list != null) { - result.addAll(list); - } - return result; - } - - /** - * Returns all plans associated with the given data manager alias - */ - public List getTestDataManagerPlans(Object alias) { - List result = new ArrayList<>(); - - List list = dataManagerActionPlanMap.get(alias); - if (list != null) { - result.addAll(list); - } - return result; - } - - /** - * Return true if and only if all actions that are stored in this action - * data view have been executed. Indicates that all injected behaviors in a - * unit test were actually executed. RETURNS FALSE IF THERE WERE NO ACTIONS - * STORED. - */ - - private void sendActionCompletionReport(DataManagerContext context) { - context.releaseOutput(new TestScenarioReport(allActionsExecuted())); - } - - private boolean allActionsExecuted() { - int planCount = 0; - for (Object alias : actorActionPlanMap.keySet()) { - for (TestActorPlan testActorPlan : actorActionPlanMap.get(alias)) { - planCount++; - if (!testActorPlan.executed()) { - return false; - } - } - } - - for (Object alias : dataManagerActionPlanMap.keySet()) { - for (TestDataManagerPlan testDataManagerPlan : dataManagerActionPlanMap.get(alias)) { - planCount++; - if (!testDataManagerPlan.executed()) { - return false; - } - } - } - - return planCount > 0; - - } - -} diff --git a/gcm3/src/main/java/nucleus/testsupport/testplugin/TestPlugin.java b/gcm3/src/main/java/nucleus/testsupport/testplugin/TestPlugin.java deleted file mode 100644 index b5b2bc388..000000000 --- a/gcm3/src/main/java/nucleus/testsupport/testplugin/TestPlugin.java +++ /dev/null @@ -1,67 +0,0 @@ -package nucleus.testsupport.testplugin; - -import java.util.List; -import java.util.Optional; - -import nucleus.NucleusError; -import nucleus.Plugin; -import nucleus.PluginContext; -import util.errors.ContractException; - -/** - * Static test support plugin that is designed to work with a unit testing - * framework. It provides for the injection of behavior into actors and data - * managers to test various simulation behaviors in a function/system setting. - * - * @author Shawn Hatch - * - */ -public class TestPlugin { - - private TestPlugin() { - } - - /* - * Initializes a simulation via the given context. Using a TestPluginData - * retrieved from the context, this initializer adds test actor and test - * data manager instances that are used in testing. It also creates an - * TestPlanDataManager that is used internally to this plugin to help manage - * plan distribution for the aforementioned actors and data managers. - * - * @throws ContractException - *
  • {@linkplain NucleusError#NULL_PLUGIN_CONTEXT} if the - * pluginContext is null
  • - */ - private static void init(PluginContext pluginContext) { - if (pluginContext == null) { - throw new ContractException(NucleusError.NULL_PLUGIN_CONTEXT); - } - - TestPluginData testPluginData = pluginContext.getPluginData(TestPluginData.class); - - TestPlanDataManager testPlanDataManager = new TestPlanDataManager(testPluginData); - pluginContext.addDataManager(testPlanDataManager); - - List dataManagerAliases = testPluginData.getTestDataManagerAliases(); - for (Object alias : dataManagerAliases) { - Optional optional = testPluginData.getTestDataManager(alias); - if (optional.isPresent()) { - pluginContext.addDataManager(optional.get()); - } - } - - for (Object alias : testPluginData.getTestActorAliases()) { - pluginContext.addActor(new TestActor(alias)::init); - } - - } - - public static Plugin getTestPlugin(TestPluginData testPluginData) { - return Plugin .builder()// - .setInitializer(TestPlugin::init)// - .addPluginData(testPluginData)// - .setPluginId(TestPluginId.PLUGIN_ID)// - .build();// - } - -} diff --git a/gcm3/src/main/java/nucleus/testsupport/testplugin/TestPluginData.java b/gcm3/src/main/java/nucleus/testsupport/testplugin/TestPluginData.java deleted file mode 100644 index a8deaf68e..000000000 --- a/gcm3/src/main/java/nucleus/testsupport/testplugin/TestPluginData.java +++ /dev/null @@ -1,326 +0,0 @@ -package nucleus.testsupport.testplugin; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.function.Supplier; - -import net.jcip.annotations.ThreadSafe; -import nucleus.PluginData; -import nucleus.PluginDataBuilder; -import util.errors.ContractException; - -/** - * Thread safe plugin data container for associating plans with actor and data - * manager aliases. - * - * @author Shawn Hatch - * - */ -@ThreadSafe -public class TestPluginData implements PluginData { - - private static class Data { - - private Data() { - } - - private Data(Data data) { - - for (Object alias : data.testActorPlanMap.keySet()) { - List oldPlans = data.testActorPlanMap.get(alias); - List newPlans = new ArrayList<>(); - testActorPlanMap.put(alias, newPlans); - for (TestActorPlan oldPlan : oldPlans) { - TestActorPlan newPlan = new TestActorPlan(oldPlan); - newPlans.add(newPlan); - } - } - - testDataManagerSuppliers.putAll(data.testDataManagerSuppliers); - - for (Object alias : data.testDataManagerPlanMap.keySet()) { - List oldPlans = data.testDataManagerPlanMap.get(alias); - List newPlans = new ArrayList<>(); - testDataManagerPlanMap.put(alias, newPlans); - for (TestDataManagerPlan oldPlan : oldPlans) { - TestDataManagerPlan newPlan = new TestDataManagerPlan(oldPlan); - newPlans.add(newPlan); - } - } - - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((testActorPlanMap == null) ? 0 : testActorPlanMap.hashCode()); - result = prime * result + ((testDataManagerPlanMap == null) ? 0 : testDataManagerPlanMap.hashCode()); - result = prime * result + ((testDataManagerSuppliers == null) ? 0 : testDataManagerSuppliers.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof Data)) { - return false; - } - Data other = (Data) obj; - if (testActorPlanMap == null) { - if (other.testActorPlanMap != null) { - return false; - } - } else if (!testActorPlanMap.equals(other.testActorPlanMap)) { - return false; - } - if (testDataManagerPlanMap == null) { - if (other.testDataManagerPlanMap != null) { - return false; - } - } else if (!testDataManagerPlanMap.equals(other.testDataManagerPlanMap)) { - return false; - } - if (testDataManagerSuppliers == null) { - if (other.testDataManagerSuppliers != null) { - return false; - } - } else if (!testDataManagerSuppliers.equals(other.testDataManagerSuppliers)) { - return false; - } - return true; - } - - /* - * Map of action plans key by actor aliases - */ - private final Map> testActorPlanMap = new LinkedHashMap<>(); - - private Map> testDataManagerSuppliers = new LinkedHashMap<>(); - - private final Map> testDataManagerPlanMap = new LinkedHashMap<>(); - - } - - private TestPluginData(Data data) { - this.data = data; - - } - - /** - * Returns a builder for TestPluginData - */ - public static Builder builder() { - return new Builder(new Data()); - } - - public static class Builder implements PluginDataBuilder { - private Data data; - - private Builder(Data data) { - this.data = data; - } - - @Override - public TestPluginData build() { - try { - validate(); - return new TestPluginData(data); - } finally { - data = new Data(); - } - } - - private void validate() { - - for (Object alias : data.testDataManagerPlanMap.keySet()) { - if (!data.testDataManagerSuppliers.containsKey(alias)) { - throw new ContractException(TestError.UNKNOWN_DATA_MANAGER_ALIAS, alias); - } - } - } - - /** - * Adds an actor action plan associated with the alias - * - * @throws ContractException - *
  • {@linkplain TestError#NULL_ALIAS} if the alias is - * null
  • - *
  • {@linkplain TestError#NULL_PLAN}if the actor action - * plan is null
  • - */ - public Builder addTestActorPlan(final Object alias, TestActorPlan testActorPlan) { - if (alias == null) { - throw new ContractException(TestError.NULL_ALIAS); - } - - if (testActorPlan == null) { - throw new ContractException(TestError.NULL_PLAN); - } - - List list = data.testActorPlanMap.get(alias); - - if (list == null) { - list = new ArrayList<>(); - data.testActorPlanMap.put(alias, list); - } - - list.add(testActorPlan); - - return this; - - } - - /** - * Adds a test data manager to the test plugin via a supplier of - * TestDataManager. The supplier must be threadsafe. - * - * @throws ContractException - * - */ - public Builder addTestDataManager(Object alias, Supplier supplier) { - if (alias == null) { - throw new ContractException(TestError.NULL_ALIAS); - } - if (supplier == null) { - throw new ContractException(TestError.NULL_DATA_MANAGER_SUPPLIER); - } - data.testDataManagerSuppliers.put(alias, supplier); - return this; - } - - /** - * Adds an data manager action plan associated with the alias - * - * @throws ContractException - *
  • {@linkplain TestError#NULL_ALIAS} if the alias is - * null
  • - *
  • {@linkplain TestError#NULL_PLAN}if the actor action - * plan is null
  • - */ - public Builder addTestDataManagerPlan(final Object alias, TestDataManagerPlan testDataManagerPlan) { - - if (alias == null) { - throw new ContractException(TestError.NULL_ALIAS); - } - if (testDataManagerPlan == null) { - throw new ContractException(TestError.NULL_PLAN); - } - List list = data.testDataManagerPlanMap.get(alias); - - if (list == null) { - list = new ArrayList<>(); - data.testDataManagerPlanMap.put(alias, list); - } - - list.add(testDataManagerPlan); - - return this; - - } - - } - - /** - * Returns a Builder that is initialized to contain the plans and suppliers - * of data managers contained in this TestPluginData. - */ - @Override - public Builder getCloneBuilder() { - return new Builder(new Data(data)); - } - - private final Data data; - - /** - * Returns a list of the test actor aliases - */ - public List getTestActorAliases() { - return new ArrayList<>(data.testActorPlanMap.keySet()); - } - - /** - * Returns the test actor plans associated with the actor alias - */ - public List getTestActorPlans(Object alias) { - List result = new ArrayList<>(); - List list = data.testActorPlanMap.get(alias); - if (list != null) { - result.addAll(list); - } - return result; - } - - /** - * Returns a test data manager instance from the given alias. - */ - @SuppressWarnings("unchecked") - public Optional getTestDataManager(Object alias) { - TestDataManager result = null; - Supplier supplier = data.testDataManagerSuppliers.get(alias); - if (supplier != null) { - result = supplier.get(); - result.setAlias(alias); - } - return Optional.ofNullable((T) result); - } - - /** - * Returns the test data manager plans associated with the actor alias - */ - public List getTestDataManagerPlans(Object alias) { - List list = data.testDataManagerPlanMap.get(alias); - List result = new ArrayList<>(); - if (list != null) { - result.addAll(list); - } - return result; - } - - /** - * Returns a list of the test data manager aliases - */ - public List getTestDataManagerAliases() { - return new ArrayList<>(data.testDataManagerSuppliers.keySet()); - } - - /** - * Hash code implementation consistent with equals() - */ - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((data == null) ? 0 : data.hashCode()); - return result; - } - - /** - * TestPluginData instances are equal if and only if they contain identical - * plans and suppliers of test data managers. - */ - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof TestPluginData)) { - return false; - } - TestPluginData other = (TestPluginData) obj; - if (data == null) { - if (other.data != null) { - return false; - } - } else if (!data.equals(other.data)) { - return false; - } - return true; - } - -} diff --git a/gcm3/src/main/java/nucleus/testsupport/testplugin/TestPluginId.java b/gcm3/src/main/java/nucleus/testsupport/testplugin/TestPluginId.java deleted file mode 100644 index 45457f306..000000000 --- a/gcm3/src/main/java/nucleus/testsupport/testplugin/TestPluginId.java +++ /dev/null @@ -1,14 +0,0 @@ -package nucleus.testsupport.testplugin; - -import nucleus.PluginId; -import nucleus.SimplePluginId; -/** - * Static plugin id implementation for the TestPlugin - * - * @author Shawn Hatch - * - */ -public class TestPluginId { - private TestPluginId() {} - public final static PluginId PLUGIN_ID = new SimplePluginId(TestPluginId.class); -} diff --git a/gcm3/src/main/java/nucleus/util/TriConsumer.java b/gcm3/src/main/java/nucleus/util/TriConsumer.java deleted file mode 100644 index 68069fe14..000000000 --- a/gcm3/src/main/java/nucleus/util/TriConsumer.java +++ /dev/null @@ -1,62 +0,0 @@ -package nucleus.util; - -import java.util.Objects; - -/** - * Represents an operation that accepts three input arguments and returns no - * result. This is the three-arity specialization of {@link Consumer}. Unlike - * most other functional interfaces, {@code TriConsumer} is expected to operate - * via side-effects. - * - *

    - * This is a functional interface whose - * functional method is {@link #accept(Object, Object, Object)}. - * - * @param - * the type of the first argument to the operation - * @param - * the type of the second argument to the operation - * @param - * the type of the third argument to the operation - * - * @see Consumer - * @since 1.8 - */ -@FunctionalInterface -public interface TriConsumer { - - /** - * Performs this operation on the given arguments. - * - * @param t - * the first input argument - * @param u - * the second input argument - * @param u - * the third input argument - */ - void accept(T t, U u, V v); - - /** - * Returns a composed {@code TriConsumer} that performs, in sequence, this - * operation followed by the {@code after} operation. If performing either - * operation throws an exception, it is relayed to the caller of the - * composed operation. If performing this operation throws an exception, the - * {@code after} operation will not be performed. - * - * @param after - * the operation to perform after this operation - * @return a composed {@code TriConsumer} that performs in sequence this - * operation followed by the {@code after} operation - * @throws NullPointerException - * if {@code after} is null - */ - default TriConsumer andThen(TriConsumer after) { - Objects.requireNonNull(after); - - return (l, r, q) -> { - accept(l, r, q); - after.accept(l, r, q); - }; - } -} diff --git a/gcm3/src/main/java/plugins/globalproperties/GlobalPropertiesPlugin.java b/gcm3/src/main/java/plugins/globalproperties/GlobalPropertiesPlugin.java deleted file mode 100644 index eef45a530..000000000 --- a/gcm3/src/main/java/plugins/globalproperties/GlobalPropertiesPlugin.java +++ /dev/null @@ -1,58 +0,0 @@ -package plugins.globalproperties; - -import nucleus.Plugin; -import plugins.globalproperties.actors.GlobalPropertyReport; -import plugins.globalproperties.datamanagers.GlobalPropertiesDataManager; - -/** - * A plugin providing a global property data manager to the simulation. - * - * @author Shawn Hatch - * - */ -public final class GlobalPropertiesPlugin { - - private GlobalPropertiesPlugin() { - } - - /** - * Returns the global plugin. - * - *

    - * Uses GlobalsPluginId.PLUGIN_ID as its id - *

    - * - *

    - * Depends on plugins: - *

      - *
    • Report Plugin
    • - *
    - *

    - * - *

    - * Provides data mangers: - *

      - *
    • {@linkplain GlobalPropertiesDataManager}
    • - *
    - *

    - * - *

    - * Provides actors: - *

      - *
    • {@linkplain GlobalPropertyReport}
    • - *
    - *

    - * - */ - public static Plugin getPlugin(GlobalPropertiesPluginData globalPropertiesPluginData) { - return Plugin .builder()// - .addPluginData(globalPropertiesPluginData)// - .setInitializer((c) -> { - GlobalPropertiesPluginData data = c.getPluginData(GlobalPropertiesPluginData.class); - c.addDataManager(new GlobalPropertiesDataManager(data)); - })// - .setPluginId(GlobalPropertiesPluginId.PLUGIN_ID)// - .build(); - } - -} diff --git a/gcm3/src/main/java/plugins/globalproperties/GlobalPropertiesPluginData.java b/gcm3/src/main/java/plugins/globalproperties/GlobalPropertiesPluginData.java deleted file mode 100644 index b544f6548..000000000 --- a/gcm3/src/main/java/plugins/globalproperties/GlobalPropertiesPluginData.java +++ /dev/null @@ -1,281 +0,0 @@ -package plugins.globalproperties; - -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -import net.jcip.annotations.Immutable; -import nucleus.PluginData; -import nucleus.PluginDataBuilder; -import plugins.globalproperties.support.GlobalPropertiesError; -import plugins.globalproperties.support.GlobalPropertyId; -import plugins.util.properties.PropertyDefinition; -import util.errors.ContractException; - -/** - * An immutable container of the initial state of global components and global - * properties. It contains:
    - *
      - *
    • global property definitions
    • - *
    • global property values
    • - *
    - * - * @author Shawn Hatch - * - */ -@Immutable -public final class GlobalPropertiesPluginData implements PluginData { - - private static void validateGlobalPropertyIsNotDefined(final Data data, final GlobalPropertyId globalPropertyId) { - final PropertyDefinition propertyDefinition = data.globalPropertyDefinitions.get(globalPropertyId); - if (propertyDefinition != null) { - throw new ContractException(GlobalPropertiesError.DUPLICATE_GLOBAL_PROPERTY_DEFINITION, globalPropertyId); - } - } - - private static void validateGlobalPropertyValueNotAssigned(final Data data, final GlobalPropertyId globalPropertyId) { - if (data.globalPropertyValues.containsKey(globalPropertyId)) { - throw new ContractException(GlobalPropertiesError.DUPLICATE_GLOBAL_PROPERTY_VALUE_ASSIGNMENT, globalPropertyId); - } - } - - /** - * Builder class for GloblaInitialData - * - * @author Shawn Hatch - * - */ - public static class Builder implements PluginDataBuilder { - private Data data; - - private Builder(Data data) { - this.data = data; - } - - /** - * Returns the {@link GlobalInitialData} from the collected information - * supplied to this builder. Clears the builder's state. - * - * @throws ContractException - *
  • {@linkplain GlobalPropertiesError.UNKNOWN_GLOBAL_PROPERTY_ID}
  • - * if a global property value was associated with a global - * property id that was not defined - * - *
  • {@linkplain GlobalPropertiesError#INCOMPATIBLE_VALUE}
  • if a - * global property value was associated with a global - * property id that is incompatible with the corresponding - * property definition. - * - *
  • {@linkplain GlobalPropertiesError#INSUFFICIENT_GLOBAL_PROPERTY_VALUE_ASSIGNMENT}
  • - * if a global property definition does not have a default - * value and there are no property values added to replace - * that default. - */ - public GlobalPropertiesPluginData build() { - try { - validateData(data); - return new GlobalPropertiesPluginData(data); - } finally { - data = new Data(); - } - } - - /** - * Defines a global property - * - * @throws ContractException - * - * - *
  • {@linkplain GlobalPropertiesError#NULL_GLOBAL_PROPERTY_ID}
  • - * if the global property id is null - * - *
  • {@linkplain GlobalPropertiesError#NULL_GLOBAL_PROPERTY_DEFINITION} - *
  • if the property definition is null - * - *
  • {@linkplain GlobalPropertiesError#DUPLICATE_GLOBAL_PROPERTY_DEFINITION} - *
  • if a property definition for the given global - * property id was previously defined. - * - */ - public Builder defineGlobalProperty(final GlobalPropertyId globalPropertyId, final PropertyDefinition propertyDefinition) { - validateGlobalPropertyIdNotNull(globalPropertyId); - validateGlobalPropertyDefinitionNotNull(propertyDefinition); - validateGlobalPropertyIsNotDefined(data, globalPropertyId); - data.globalPropertyDefinitions.put(globalPropertyId, propertyDefinition); - return this; - } - - /** - * Sets the global property value that overrides the default value of - * the corresponding property definition - * - * @throws ContractException - * - *
  • {@linkplain GlobalPropertiesError#NULL_GLOBAL_PROPERTY_ID} - *
  • if the global property id is null - * - *
  • {@linkplain GlobalPropertiesError#NULL_GLOBAL_PROPERTY_VALUE} - *
  • if the global property value is null - * - *
  • {@linkplain GlobalPropertiesError#DUPLICATE_GLOBAL_PROPERTY_VALUE_ASSIGNMENT} - *
  • if the global property value was previously defined - * for the given global property id - * - */ - public Builder setGlobalPropertyValue(final GlobalPropertyId globalPropertyId, final Object propertyValue) { - validateGlobalPropertyIdNotNull(globalPropertyId); - validateGlobalPropertyValueNotNull(propertyValue); - validateGlobalPropertyValueNotAssigned(data, globalPropertyId); - data.globalPropertyValues.put(globalPropertyId, propertyValue); - return this; - } - - } - - private static class Data { - - private final Map globalPropertyDefinitions = new LinkedHashMap<>(); - - private final Map globalPropertyValues = new LinkedHashMap<>(); - - private Data() { - } - - private Data(Data data) { - globalPropertyDefinitions.putAll(data.globalPropertyDefinitions); - globalPropertyValues.putAll(globalPropertyValues); - } - } - - /** - * Returns a Builder instance - */ - public static Builder builder() { - return new Builder(new Data()); - } - - private static void validateData(final Data data) { - for (final GlobalPropertyId globalPropertyId : data.globalPropertyValues.keySet()) { - if (!data.globalPropertyDefinitions.containsKey(globalPropertyId)) { - throw new ContractException(GlobalPropertiesError.UNKNOWN_GLOBAL_PROPERTY_ID, globalPropertyId); - } - } - for (final GlobalPropertyId globalPropertyId : data.globalPropertyValues.keySet()) { - final Object propertyValue = data.globalPropertyValues.get(globalPropertyId); - final PropertyDefinition propertyDefinition = data.globalPropertyDefinitions.get(globalPropertyId); - if (!propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())) { - throw new ContractException(GlobalPropertiesError.INCOMPATIBLE_VALUE, globalPropertyId + " = " + propertyValue); - } - } - - /* - * For every global property definition that has a null default value, - * ensure that there is a corresponding global property value assignment - * and put that initial assignment on the property definition and repair - * the definition. - */ - for (GlobalPropertyId globalPropertyId : data.globalPropertyDefinitions.keySet()) { - PropertyDefinition propertyDefinition = data.globalPropertyDefinitions.get(globalPropertyId); - if (!propertyDefinition.getDefaultValue().isPresent()) { - Object propertyValue = data.globalPropertyValues.get(globalPropertyId); - if (propertyValue == null) { - throw new ContractException(GlobalPropertiesError.INSUFFICIENT_GLOBAL_PROPERTY_VALUE_ASSIGNMENT, globalPropertyId); - } - propertyDefinition = // - PropertyDefinition .builder()// - .setPropertyValueMutability(propertyDefinition.propertyValuesAreMutable())// - .setDefaultValue(propertyValue)// - .setTimeTrackingPolicy(propertyDefinition.getTimeTrackingPolicy())// - .setType(propertyDefinition.getType())// - .build();// - data.globalPropertyDefinitions.put(globalPropertyId, propertyDefinition); - } - } - } - - private static void validateGlobalPropertyDefinitionNotNull(final PropertyDefinition propertyDefinition) { - if (propertyDefinition == null) { - throw new ContractException(GlobalPropertiesError.NULL_GLOBAL_PROPERTY_DEFINITION); - } - } - - private static void validateGlobalPropertyIdNotNull(final GlobalPropertyId globalPropertyId) { - if (globalPropertyId == null) { - throw new ContractException(GlobalPropertiesError.NULL_GLOBAL_PROPERTY_ID); - } - } - - private static void validateGlobalPropertyValueNotNull(final Object propertyValue) { - if (propertyValue == null) { - throw new ContractException(GlobalPropertiesError.NULL_GLOBAL_PROPERTY_VALUE); - } - } - - private final Data data; - - private GlobalPropertiesPluginData(final Data data) { - this.data = data; - } - - /** - * Returns the {@link PropertyDefinition} for the given - * {@link GlobalPropertyId}. - * - * @throws ContractException - * - *
  • {@linkplain GlobalPropertiesError#NULL_GLOBAL_PROPERTY_ID}
  • if - * the global property id is null - *
  • {@linkplain GlobalPropertiesError#UNKNOWN_GLOBAL_PROPERTY_ID}
  • - * if the global property id is known - */ - public PropertyDefinition getGlobalPropertyDefinition(final GlobalPropertyId globalPropertyId) { - validategGlobalPropertyIdExists(data, globalPropertyId); - return data.globalPropertyDefinitions.get(globalPropertyId); - } - - /** - * Returns the set of {@link GlobalPropertyId} - * - */ - @SuppressWarnings("unchecked") - public Set getGlobalPropertyIds() { - final Set result = new LinkedHashSet<>(); - for (final GlobalPropertyId globalPropertyId : data.globalPropertyDefinitions.keySet()) { - result.add((T) globalPropertyId); - } - return result; - } - - /** - * Returns the property value for the given {@link GlobalPropertyId}. - * - * @throws ContractException - * - * - *
  • {@linkplain GlobalPropertiesError#NULL_GLOBAL_PROPERTY_ID}
  • if - * the global property id is null - *
  • {@linkplain GlobalPropertiesError#UNKNOWN_GLOBAL_PROPERTY_ID}
  • - * if the global property id is known - */ - @SuppressWarnings("unchecked") - public T getGlobalPropertyValue(final GlobalPropertyId globalPropertyId) { - validategGlobalPropertyIdExists(data, globalPropertyId); - return (T) data.globalPropertyValues.get(globalPropertyId); - } - - private static void validategGlobalPropertyIdExists(final Data data, final GlobalPropertyId globalPropertyId) { - if (globalPropertyId == null) { - throw new ContractException(GlobalPropertiesError.NULL_GLOBAL_PROPERTY_ID); - } - if (!data.globalPropertyDefinitions.containsKey(globalPropertyId)) { - throw new ContractException(GlobalPropertiesError.UNKNOWN_GLOBAL_PROPERTY_ID, globalPropertyId); - } - } - - @Override - public PluginDataBuilder getCloneBuilder() { - return new Builder(new Data(data)); - } - -} diff --git a/gcm3/src/main/java/plugins/globalproperties/GlobalPropertiesPluginId.java b/gcm3/src/main/java/plugins/globalproperties/GlobalPropertiesPluginId.java deleted file mode 100644 index fa7ca2002..000000000 --- a/gcm3/src/main/java/plugins/globalproperties/GlobalPropertiesPluginId.java +++ /dev/null @@ -1,14 +0,0 @@ -package plugins.globalproperties; - -import nucleus.PluginId; -/** - * Static plugin id implementation for the GlobalsPlugin - * - * @author Shawn Hatch - * - */ - -public final class GlobalPropertiesPluginId implements PluginId { - public final static PluginId PLUGIN_ID = new GlobalPropertiesPluginId(); - private GlobalPropertiesPluginId() {}; -} diff --git a/gcm3/src/main/java/plugins/globalproperties/actors/GlobalPropertyReport.java b/gcm3/src/main/java/plugins/globalproperties/actors/GlobalPropertyReport.java deleted file mode 100644 index 6f550b461..000000000 --- a/gcm3/src/main/java/plugins/globalproperties/actors/GlobalPropertyReport.java +++ /dev/null @@ -1,119 +0,0 @@ -package plugins.globalproperties.actors; - -import java.util.LinkedHashSet; -import java.util.Set; - -import nucleus.ActorContext; -import nucleus.EventLabel; -import plugins.globalproperties.datamanagers.GlobalPropertiesDataManager; -import plugins.globalproperties.events.GlobalPropertyUpdateEvent; -import plugins.globalproperties.support.GlobalPropertiesError; -import plugins.globalproperties.support.GlobalPropertyId; -import plugins.reports.support.ReportHeader; -import plugins.reports.support.ReportId; -import plugins.reports.support.ReportItem; -import util.errors.ContractException; - -/** - * A Report that displays assigned global property values over time. - * - * - * Fields - * - * Time -- the time in days when the global property was set - * - * Property -- the global property identifier - * - * Value -- the value of the global property - * - * @author Shawn Hatch - * - */ -public final class GlobalPropertyReport { - - private ReportHeader reportHeader; - - /* - * The constrained set of person properties that will be used in this - * report. They are set during init() - */ - private final Set globalPropertyIds = new LinkedHashSet<>(); - - private ReportHeader getReportHeader() { - if (reportHeader == null) { - reportHeader = ReportHeader .builder()// - .add("time")// - .add("property")// - .add("value")// - .build();// - } - return reportHeader; - } - - private void handleGlobalPropertyUpdateEvent(ActorContext actorContext, GlobalPropertyUpdateEvent globalPropertyUpdateEvent) { - GlobalPropertyId globalPropertyId = globalPropertyUpdateEvent.getGlobalPropertyId(); - if (globalPropertyIds.contains(globalPropertyId)) { - writeProperty(actorContext, globalPropertyId,globalPropertyUpdateEvent.getCurrentPropertyValue()); - } - } - - - private final ReportId reportId; - - public GlobalPropertyReport(ReportId reportId ,GlobalPropertyId... globalPropertyIds) { - this.reportId = reportId; - for (GlobalPropertyId globalPropertyId : globalPropertyIds) { - this.globalPropertyIds.add(globalPropertyId); - } - } - - /** - * Initialization of the report. Subscribes to GlobalPropertyUpdateEvent. - */ - public void init(final ActorContext actorContext) { - - GlobalPropertiesDataManager globalPropertiesDataManager = actorContext.getDataManager(GlobalPropertiesDataManager.class); - - /* - * If no global properties were specified, then assume all are wanted - */ - if (globalPropertyIds.size() == 0) { - globalPropertyIds.addAll(globalPropertiesDataManager.getGlobalPropertyIds()); - } - - /* - * Ensure that every client supplied property identifier is valid - */ - final Set validPropertyIds = globalPropertiesDataManager.getGlobalPropertyIds(); - for (final GlobalPropertyId globalPropertyId : globalPropertyIds) { - if (!validPropertyIds.contains(globalPropertyId)) { - throw new ContractException(GlobalPropertiesError.UNKNOWN_GLOBAL_PROPERTY_ID, globalPropertyId); - } - } - - if (globalPropertyIds.equals(globalPropertiesDataManager.getGlobalPropertyIds())) { - actorContext.subscribe(GlobalPropertyUpdateEvent.class, this::handleGlobalPropertyUpdateEvent); - } else { - for (GlobalPropertyId globalPropertyId : globalPropertyIds) { - EventLabel eventLabel = GlobalPropertyUpdateEvent.getEventLabel(actorContext, globalPropertyId); - actorContext.subscribe(eventLabel, this::handleGlobalPropertyUpdateEvent); - } - } - - for (final GlobalPropertyId globalPropertyId : globalPropertyIds) { - Object globalPropertyValue = globalPropertiesDataManager.getGlobalPropertyValue(globalPropertyId); - writeProperty(actorContext, globalPropertyId,globalPropertyValue); - } - } - - private void writeProperty(ActorContext actorContext, final GlobalPropertyId globalPropertyId,Object globalPropertyValue) { - final ReportItem.Builder reportItemBuilder = ReportItem.builder(); - reportItemBuilder.setReportHeader(getReportHeader()); - reportItemBuilder.setReportId(reportId); - reportItemBuilder.addValue(actorContext.getTime()); - reportItemBuilder.addValue(globalPropertyId.toString()); - reportItemBuilder.addValue(globalPropertyValue); - actorContext.releaseOutput(reportItemBuilder.build()); - } - -} \ No newline at end of file diff --git a/gcm3/src/main/java/plugins/globalproperties/datamanagers/GlobalPropertiesDataManager.java b/gcm3/src/main/java/plugins/globalproperties/datamanagers/GlobalPropertiesDataManager.java deleted file mode 100644 index 5ad0c56f3..000000000 --- a/gcm3/src/main/java/plugins/globalproperties/datamanagers/GlobalPropertiesDataManager.java +++ /dev/null @@ -1,243 +0,0 @@ -package plugins.globalproperties.datamanagers; - -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -import nucleus.DataManager; -import nucleus.DataManagerContext; -import plugins.globalproperties.GlobalPropertiesPluginData; -import plugins.globalproperties.events.GlobalPropertyUpdateEvent; -import plugins.globalproperties.support.GlobalPropertiesError; -import plugins.globalproperties.support.GlobalPropertyId; -import plugins.util.properties.PropertyDefinition; -import plugins.util.properties.PropertyError; -import plugins.util.properties.PropertyValueRecord; -import util.errors.ContractException; - -/** - * A mutable data manager for global properties. - * - * @author Shawn Hatch - * - */ - -public final class GlobalPropertiesDataManager extends DataManager { - - private DataManagerContext dataManagerContext; - private Map globalPropertyMap = new LinkedHashMap<>(); - private Map globalPropertyDefinitions = new LinkedHashMap<>(); - - /** - * Adds the global property definition. - * - * - * @throws ContractException - * - * - *
  • {@linkplain GlobalPropertiesError#NULL_GLOBAL_PROPERTY_ID} if the - * global property id is null
  • - *
  • {@linkplain GlobalPropertiesError#NULL_GLOBAL_PROPERTY_DEFINITION} - * if the property definition is null
  • - *
  • {@linkplain GlobalPropertiesError#DUPLICATE_GLOBAL_PROPERTY_DEFINITION} - * if the global property was previously added
  • - * - * - */ - private void addGlobalPropertyDefinition(GlobalPropertyId globalPropertyId, PropertyDefinition propertyDefinition) { - validateGlobalPropertyAddition(globalPropertyId, propertyDefinition); - - PropertyValueRecord propertyValueRecord = new PropertyValueRecord(dataManagerContext); - propertyValueRecord.setPropertyValue(propertyDefinition.getDefaultValue().get()); - globalPropertyMap.put(globalPropertyId, propertyValueRecord); - globalPropertyDefinitions.put(globalPropertyId, propertyDefinition); - } - - private void validateGlobalPropertyAddition(GlobalPropertyId globalPropertyId, PropertyDefinition propertyDefinition) { - - if (globalPropertyId == null) { - throw new ContractException(GlobalPropertiesError.NULL_GLOBAL_PROPERTY_ID); - } - - if (propertyDefinition == null) { - throw new ContractException(GlobalPropertiesError.NULL_GLOBAL_PROPERTY_DEFINITION); - } - - if (globalPropertyDefinitions.containsKey(globalPropertyId)) { - throw new ContractException(GlobalPropertiesError.DUPLICATE_GLOBAL_PROPERTY_DEFINITION); - } - - } - - private final GlobalPropertiesPluginData globalPropertiesPluginData; - - /** - * Constructs the data manager - * - * @throws ContractException - *
  • {@linkplain GlobalPropertiesError#NULL_GLOBAL_PLUGIN_DATA} if the - * global plugin data is null
  • - */ - public GlobalPropertiesDataManager(GlobalPropertiesPluginData globalPropertiesPluginData) { - if (globalPropertiesPluginData == null) { - throw new ContractException(GlobalPropertiesError.NULL_GLOBAL_PLUGIN_DATA); - } - this.globalPropertiesPluginData = globalPropertiesPluginData; - - } - - /** - * Returns the property definition for the given {@link GlobalPropertyId} - * - * @throws ContractException - *
  • {@linkplain GlobalPropertiesError#NULL_GLOBAL_PROPERTY_ID} if the - * global property id is null
  • - *
  • {@linkplain GlobalPropertiesError#UNKNOWN_GLOBAL_PROPERTY_ID} if - * the global property id is unknown
  • - * - */ - - public PropertyDefinition getGlobalPropertyDefinition(final GlobalPropertyId globalPropertyId) { - validateGlobalPropertyId(globalPropertyId); - return globalPropertyDefinitions.get(globalPropertyId); - } - - /** - * Returns the set of global property ids - */ - @SuppressWarnings("unchecked") - public Set getGlobalPropertyIds() { - Set result = new LinkedHashSet<>(globalPropertyDefinitions.keySet().size()); - for (GlobalPropertyId globalPropertyId : globalPropertyDefinitions.keySet()) { - result.add((T) globalPropertyId); - } - return result; - } - - /** - * Returns the value of the global property. - * - * @throws ContractException - *
  • {@linkplain GlobalPropertiesError#NULL_GLOBAL_PROPERTY_ID} if the - * global property id is null
  • - *
  • {@linkplain GlobalPropertiesError#UNKNOWN_GLOBAL_PROPERTY_ID} if - * the global property id is unknown
  • - * - */ - @SuppressWarnings("unchecked") - public T getGlobalPropertyValue(GlobalPropertyId globalPropertyId) { - validateGlobalPropertyId(globalPropertyId); - return (T) globalPropertyMap.get(globalPropertyId).getValue(); - } - - /** - * Returns the time when the of the global property was last assigned. - * - * @throws ContractException - *
  • {@linkplain GlobalPropertiesError#NULL_GLOBAL_PROPERTY_ID} if the - * global property id is null
  • - *
  • {@linkplain GlobalPropertiesError#UNKNOWN_GLOBAL_PROPERTY_ID} if - * the global property id is unknown
  • - */ - - public double getGlobalPropertyTime(GlobalPropertyId globalPropertyId) { - validateGlobalPropertyId(globalPropertyId); - return globalPropertyMap.get(globalPropertyId).getAssignmentTime(); - } - - /** - * Set the value of the global property and updates the property assignment - * time. - * - * @throw {@link ContractException} - *
  • {@link GlobalPropertiesError.NULL_GLOBAL_PROPERTY_ID} if the global - * property id is null - *
  • {@link GlobalPropertiesError.UNKNOWN_GLOBAL_PROPERTY_ID} if the global - * property id is unknown - *
  • {@link GlobalPropertiesError.NULL_GLOBAL_PROPERTY_VALUE} if the property - * value is null - *
  • {@link PropertyError.IMMUTABLE_VALUE} if the global property - * definition indicates the property is not mutable - *
  • {@link PropertyError.INCOMPATIBLE_VALUE} if the property value - * is incompatible with the property definition
  • - */ - public void setGlobalPropertyValue(GlobalPropertyId globalPropertyId, Object globalPropertyValue) { - validateGlobalPropertyId(dataManagerContext, globalPropertyId); - validateGlobalPropertyValueNotNull(dataManagerContext, globalPropertyValue); - final PropertyDefinition propertyDefinition = getGlobalPropertyDefinition(globalPropertyId); - validatePropertyMutability(dataManagerContext, propertyDefinition); - validateValueCompatibility(dataManagerContext, globalPropertyId, propertyDefinition, globalPropertyValue); - final Object oldPropertyValue = getGlobalPropertyValue(globalPropertyId); - globalPropertyMap.get(globalPropertyId).setPropertyValue(globalPropertyValue); - dataManagerContext.releaseEvent(new GlobalPropertyUpdateEvent(globalPropertyId, oldPropertyValue, globalPropertyValue)); - } - - /** - * Returns true if and only if the global property id exists. Returns false - * for null input. - */ - public boolean globalPropertyIdExists(final GlobalPropertyId globalPropertyId) { - return globalPropertyMap.containsKey(globalPropertyId); - } - - @Override - public void init(DataManagerContext dataManagerContext) { - super.init(dataManagerContext); - this.dataManagerContext = dataManagerContext; - - dataManagerContext.addEventLabeler(GlobalPropertyUpdateEvent.getEventLabeler()); - - for (GlobalPropertyId globalPropertyId : globalPropertiesPluginData.getGlobalPropertyIds()) { - PropertyDefinition globalPropertyDefinition = globalPropertiesPluginData.getGlobalPropertyDefinition(globalPropertyId); - addGlobalPropertyDefinition(globalPropertyId, globalPropertyDefinition); - final Object globalPropertyValue = globalPropertiesPluginData.getGlobalPropertyValue(globalPropertyId); - if (globalPropertyValue != null) { - final PropertyDefinition propertyDefinition = globalPropertiesPluginData.getGlobalPropertyDefinition(globalPropertyId); - validateValueCompatibility(dataManagerContext, globalPropertyId, propertyDefinition, globalPropertyValue); - setGlobalPropertyValue(globalPropertyId, globalPropertyValue); - } - } - - } - - private void validateGlobalPropertyId(final DataManagerContext dataManagerContext, final GlobalPropertyId globalPropertyId) { - if (globalPropertyId == null) { - throw new ContractException(GlobalPropertiesError.NULL_GLOBAL_PROPERTY_ID); - } - - if (!globalPropertyIdExists(globalPropertyId)) { - throw new ContractException(GlobalPropertiesError.UNKNOWN_GLOBAL_PROPERTY_ID, globalPropertyId); - } - } - - private void validateGlobalPropertyValueNotNull(final DataManagerContext dataManagerContext, final Object propertyValue) { - if (propertyValue == null) { - throw new ContractException(GlobalPropertiesError.NULL_GLOBAL_PROPERTY_VALUE); - } - } - - private void validatePropertyMutability(final DataManagerContext dataManagerContext, final PropertyDefinition propertyDefinition) { - if (!propertyDefinition.propertyValuesAreMutable()) { - throw new ContractException(PropertyError.IMMUTABLE_VALUE); - } - } - - private void validateValueCompatibility(final DataManagerContext dataManagerContext, final Object propertyId, final PropertyDefinition propertyDefinition, final Object propertyValue) { - if (!propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())) { - throw new ContractException(PropertyError.INCOMPATIBLE_VALUE, - "Property value " + propertyValue + " is not of type " + propertyDefinition.getType().getName() + " and does not match definition of " + propertyId); - } - } - - private void validateGlobalPropertyId(final GlobalPropertyId globalPropertyId) { - if (globalPropertyId == null) { - throw new ContractException(GlobalPropertiesError.NULL_GLOBAL_PROPERTY_ID); - } - - if (!globalPropertyIdExists(globalPropertyId)) { - throw new ContractException(GlobalPropertiesError.UNKNOWN_GLOBAL_PROPERTY_ID, globalPropertyId); - } - } - -} diff --git a/gcm3/src/main/java/plugins/globalproperties/events/GlobalPropertyUpdateEvent.java b/gcm3/src/main/java/plugins/globalproperties/events/GlobalPropertyUpdateEvent.java deleted file mode 100644 index 9bde85055..000000000 --- a/gcm3/src/main/java/plugins/globalproperties/events/GlobalPropertyUpdateEvent.java +++ /dev/null @@ -1,123 +0,0 @@ -package plugins.globalproperties.events; - -import net.jcip.annotations.Immutable; -import nucleus.Event; -import nucleus.EventLabel; -import nucleus.EventLabeler; -import nucleus.EventLabelerId; -import nucleus.SimulationContext; -import plugins.globalproperties.datamanagers.GlobalPropertiesDataManager; -import plugins.globalproperties.support.GlobalPropertiesError; -import plugins.globalproperties.support.GlobalPropertyId; -import util.errors.ContractException; - -/** - * - * An event released by the global data manager whenever a global property is - * changed. - * - * @author Shawn Hatch - * - */ - -@Immutable -public class GlobalPropertyUpdateEvent implements Event { - private final GlobalPropertyId globalPropertyId; - private final Object previousPropertyValue; - private final Object currentPropertyValue; - - /** - * Constructs the event. - * - */ - public GlobalPropertyUpdateEvent(GlobalPropertyId globalPropertyId, Object previousPropertyValue, Object currentPropertyValue) { - super(); - this.globalPropertyId = globalPropertyId; - this.previousPropertyValue = previousPropertyValue; - this.currentPropertyValue = currentPropertyValue; - } - - /** - * Returns the global property id - */ - public GlobalPropertyId getGlobalPropertyId() { - return globalPropertyId; - } - - /** - * Returns the previous property value - */ - public Object getPreviousPropertyValue() { - return previousPropertyValue; - } - - /** - * Returns the current property value - */ - public Object getCurrentPropertyValue() { - return currentPropertyValue; - } - - /** - * Standard string implementation of the form - * - * GlobalPropertyUpdateEvent [globalPropertyId=" + globalPropertyId + ", - * previousPropertyValue=" + previousPropertyValue + ", - * currentPropertyValue=" + currentPropertyValue + "] - */ - @Override - public String toString() { - return "GlobalPropertyUpdateEvent [globalPropertyId=" + globalPropertyId + ", previousPropertyValue=" + previousPropertyValue + ", currentPropertyValue=" + currentPropertyValue + "]"; - } - - private static enum LabelerId implements EventLabelerId { - PROPERTY - } - - /** - * Returns an event label used to subscribe to - * {@link GlobalPropertyUpdateEvent} events. Matches on global property id. - * - * - * @throws ContractException - * - *
  • {@linkplain GlobalPropertiesError#NULL_GLOBAL_PROPERTY_ID} if the - * global property id is null
  • - *
  • {@linkplain GlobalPropertiesError#UNKNOWN_GLOBAL_PROPERTY_ID} if - * the global property id is unknown
  • - */ - public static EventLabel getEventLabel(SimulationContext simulationContext, GlobalPropertyId globalPropertyId) { - validateGlobalProperty(simulationContext, globalPropertyId); - return _getEventLabel(globalPropertyId); - } - - private static EventLabel _getEventLabel(GlobalPropertyId globalPropertyId) { - return EventLabel .builder(GlobalPropertyUpdateEvent.class)// - .setEventLabelerId(LabelerId.PROPERTY)// - .addKey(globalPropertyId)// - .build();// - } - - /** - * Returns an event labeler for {@link GlobalPropertyUpdateEvent} events - * that the global property id. - */ - public static EventLabeler getEventLabeler() { - return EventLabeler .builder(GlobalPropertyUpdateEvent.class)// - .setEventLabelerId(LabelerId.PROPERTY)// - .setLabelFunction((context, event) -> _getEventLabel(event.getGlobalPropertyId()))// - .build(); - } - - private static void validateGlobalProperty(SimulationContext simulationContext, GlobalPropertyId globalPropertyId) { - simulationContext.getDataManager(GlobalPropertiesDataManager.class).getGlobalPropertyDefinition(globalPropertyId); - } - - /** - * Returns the global property id used to create this event - */ - @Override - public Object getPrimaryKeyValue() { - return globalPropertyId; - } -} diff --git a/gcm3/src/main/java/plugins/globalproperties/support/GlobalPropertiesError.java b/gcm3/src/main/java/plugins/globalproperties/support/GlobalPropertiesError.java deleted file mode 100644 index ef1a75f9e..000000000 --- a/gcm3/src/main/java/plugins/globalproperties/support/GlobalPropertiesError.java +++ /dev/null @@ -1,48 +0,0 @@ -package plugins.globalproperties.support; - -import util.errors.ContractError; -import util.errors.ContractException; - -/** - * An enumeration supporting {@link ContractException} that acts as a general - * description of the exception. - * - * @author Shawn Hatch - * - */ -public enum GlobalPropertiesError implements ContractError { - - NULL_GLOBAL_COMPONENT_INITIAL_BEHVAVIOR_SUPPLIER("Null global component initial behvavior supplier"), - - NULL_GLOBAL_DATA_MANGER("Null global data manager"), - - NULL_GLOBAL_PLUGIN_DATA("Null global plugin data"), - - NULL_GLOBAL_PROPERTY_DEFINITION("Null global property definition"), - - NULL_GLOBAL_PROPERTY_ID("Null global property id"), - - NULL_GLOBAL_PROPERTY_VALUE("Null global property value"), - - UNKNOWN_GLOBAL_PROPERTY_ID("Unknown global property id"), - - INCOMPATIBLE_VALUE("Property value is incompatible with the global property definition"), - - DUPLICATE_GLOBAL_PROPERTY_DEFINITION("Duplicate global property definition"), - - DUPLICATE_GLOBAL_PROPERTY_VALUE_ASSIGNMENT("Duplicate global property value assignment"), - - INSUFFICIENT_GLOBAL_PROPERTY_VALUE_ASSIGNMENT("A global property definition default value is null and not replaced with sufficient property value assignments") - ; - - private final String description; - - private GlobalPropertiesError(final String description) { - this.description = description; - } - - @Override - public String getDescription() { - return description; - } -} diff --git a/gcm3/src/main/java/plugins/globalproperties/support/GlobalPropertyId.java b/gcm3/src/main/java/plugins/globalproperties/support/GlobalPropertyId.java deleted file mode 100644 index 6e9821bed..000000000 --- a/gcm3/src/main/java/plugins/globalproperties/support/GlobalPropertyId.java +++ /dev/null @@ -1,14 +0,0 @@ -package plugins.globalproperties.support; - -import net.jcip.annotations.ThreadSafe; - -/** - * Marker interface for global property identifiers - * - * @author Shawn Hatch - * - */ -@ThreadSafe -public interface GlobalPropertyId { - -} diff --git a/gcm3/src/main/java/plugins/globalproperties/support/SimpleGlobalPropertyId.java b/gcm3/src/main/java/plugins/globalproperties/support/SimpleGlobalPropertyId.java deleted file mode 100644 index a77dacf57..000000000 --- a/gcm3/src/main/java/plugins/globalproperties/support/SimpleGlobalPropertyId.java +++ /dev/null @@ -1,66 +0,0 @@ -package plugins.globalproperties.support; - -import util.errors.ContractException; -/** - * A simple implementor of {@link GlobalPropertyId} that wraps a value. - * @author Shawn Hatch - * - */ -public final class SimpleGlobalPropertyId implements GlobalPropertyId { - - private final Object value; - - /** - * Creates a global property id from the given value. The value must implement a proper equals contract. - * - * @throws ContractException - *
  • {@linkplain GlobalPropertiesError#NULL_GLOBAL_PROPERTY_VALUE} if - * the value is null
  • - */ - public SimpleGlobalPropertyId(Object value) { - if (value == null) { - throw new ContractException(GlobalPropertiesError.NULL_GLOBAL_PROPERTY_VALUE); - } - this.value = value; - } - - /** - * Standard implementation - */ - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((value == null) ? 0 : value.hashCode()); - return result; - } - - /** - * Two {@link SimpleGlobalPropertyId} instances are equal if and only if - * their inputs are equal. - */ - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof SimpleGlobalPropertyId)) { - return false; - } - SimpleGlobalPropertyId other = (SimpleGlobalPropertyId) obj; - if (value == null) { - if (other.value != null) { - return false; - } - } else if (!value.equals(other.value)) { - return false; - } - return true; - } - - @Override - public String toString() { - return value.toString(); - } - -} diff --git a/gcm3/src/main/java/plugins/globalproperties/testsupport/GlobalsPropertiesActionSupport.java b/gcm3/src/main/java/plugins/globalproperties/testsupport/GlobalsPropertiesActionSupport.java deleted file mode 100644 index c7512c337..000000000 --- a/gcm3/src/main/java/plugins/globalproperties/testsupport/GlobalsPropertiesActionSupport.java +++ /dev/null @@ -1,71 +0,0 @@ -package plugins.globalproperties.testsupport; - -import java.util.function.Consumer; - -import nucleus.ActorContext; -import nucleus.Plugin; -import nucleus.Simulation; -import nucleus.testsupport.testplugin.ScenarioPlanCompletionObserver; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestError; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import plugins.globalproperties.GlobalPropertiesPlugin; -import plugins.globalproperties.GlobalPropertiesPluginData; -import util.errors.ContractException; - -/** - * A static test support class for the globals plugin. Provides convenience - * methods for integrating a test plugin into a global-properties simulation - * test harness. - * - * - * @author Shawn Hatch - * - */ - -public class GlobalsPropertiesActionSupport { - - /** - * Creates the test plugin containing a test actor initialized by the given - * consumer. Executes the simulation via the - * {@linkplain GlobalsPropertiesActionSupport#testConsumers(Plugin)} method - * - */ - - public static void testConsumer(Consumer consumer) { - - TestPluginData testPluginData = TestPluginData .builder()// - .addTestActorPlan("actor", new TestActorPlan(0, consumer))// - .build(); - - Plugin plugin = TestPlugin.getTestPlugin(testPluginData); - testConsumers(plugin); - } - - /** - * Executes a simulation composed of the given test plugin and the global - * plugin initialized with the {@linkplain TestGlobalPropertyId} properties. - */ - public static void testConsumers(Plugin testPlugin) { - - GlobalPropertiesPluginData.Builder globalsPluginBuilder = GlobalPropertiesPluginData.builder(); - for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { - globalsPluginBuilder.defineGlobalProperty(testGlobalPropertyId, testGlobalPropertyId.getPropertyDefinition()); - } - GlobalPropertiesPluginData globalPropertiesPluginData = globalsPluginBuilder.build(); - Plugin globalsPlugin = GlobalPropertiesPlugin.getPlugin(globalPropertiesPluginData); - - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - Simulation .builder()// - .addPlugin(globalsPlugin)// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput).addPlugin(testPlugin)// - .build()// - .execute();// - - // show that all actions were executed - if (!scenarioPlanCompletionObserver.allPlansExecuted()) { - throw new ContractException(TestError.TEST_EXECUTION_FAILURE); - } - } -} diff --git a/gcm3/src/main/java/plugins/globalproperties/testsupport/TestGlobalPropertyId.java b/gcm3/src/main/java/plugins/globalproperties/testsupport/TestGlobalPropertyId.java deleted file mode 100644 index 542b134d9..000000000 --- a/gcm3/src/main/java/plugins/globalproperties/testsupport/TestGlobalPropertyId.java +++ /dev/null @@ -1,120 +0,0 @@ -package plugins.globalproperties.testsupport; - -import org.apache.commons.math3.random.RandomGenerator; - -import plugins.globalproperties.support.GlobalPropertyId; -import plugins.util.properties.PropertyDefinition; -import plugins.util.properties.TimeTrackingPolicy; - -/** - * Enumeration that provides a variety of global property definitions for testing purposes - */ -public enum TestGlobalPropertyId implements GlobalPropertyId { - - GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE( - PropertyDefinition .builder()// - .setType(Boolean.class)// - .setDefaultValue(false)// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME)// - .build() // - ), // - GLOBAL_PROPERTY_2_INTEGER_MUTABLE( - PropertyDefinition .builder()// - .setType(Integer.class)// - .setDefaultValue(0)// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME)// - .build() // - ), // - GLOBAL_PROPERTY_3_DOUBLE_MUTABLE( - PropertyDefinition .builder()// - .setType(Double.class)// - .setDefaultValue(0.0)// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME)// - .build() // - ), // - GLOBAL_PROPERTY_4_BOOLEAN_IMMUTABLE( - PropertyDefinition .builder()// - .setType(Boolean.class)// - .setDefaultValue(false)// - .setPropertyValueMutability(false)// - .setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME)// - .build() // - ), // - GLOBAL_PROPERTY_5_INTEGER_IMMUTABLE( - PropertyDefinition .builder()// - .setType(Integer.class)// - .setDefaultValue(0)// - .setPropertyValueMutability(false)// - .setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME)// - .build() // - ), // - GLOBAL_PROPERTY_6_DOUBLE_IMMUTABLE( - PropertyDefinition .builder()// - .setType(Double.class)// - .setDefaultValue(0.0)// - .setPropertyValueMutability(false)// - .setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME)// - .build() // - );// - - /** - * Returns a randomly selected member of this enumeration - */ - public static TestGlobalPropertyId getRandomGlobalPropertyId(final RandomGenerator randomGenerator) { - return TestGlobalPropertyId.values()[randomGenerator.nextInt(TestGlobalPropertyId.values().length)]; - } - - /** - * Returns a randomly selected value that is compatible with this member's - * associated property definition. - * - */ - @SuppressWarnings("unchecked") - public T getRandomPropertyValue(final RandomGenerator randomGenerator) { - switch (this) { - case GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE: - Boolean b1 = randomGenerator.nextBoolean(); - return (T) b1; - case GLOBAL_PROPERTY_2_INTEGER_MUTABLE: - Integer i2 = randomGenerator.nextInt(); - return (T) i2; - case GLOBAL_PROPERTY_3_DOUBLE_MUTABLE: - Double d3 = randomGenerator.nextDouble(); - return (T) d3; - case GLOBAL_PROPERTY_4_BOOLEAN_IMMUTABLE: - Boolean b4 = randomGenerator.nextBoolean(); - return (T) b4; - case GLOBAL_PROPERTY_5_INTEGER_IMMUTABLE: - Integer i5 = randomGenerator.nextInt(); - return (T) i5; - case GLOBAL_PROPERTY_6_DOUBLE_IMMUTABLE: - Double d6 = randomGenerator.nextDouble(); - return (T) d6; - default: - throw new RuntimeException("unhandled case: " + this); - - } - } - - private final PropertyDefinition propertyDefinition; - - private TestGlobalPropertyId(PropertyDefinition propertyDefinition) { - this.propertyDefinition = propertyDefinition; - } - - public PropertyDefinition getPropertyDefinition() { - return propertyDefinition; - } - - /** - * Returns a new {@link GlobalPropertyId} instance. - */ - public static GlobalPropertyId getUnknownGlobalPropertyId() { - return new GlobalPropertyId() { - }; - } - -} diff --git a/gcm3/src/main/java/plugins/groups/GroupsPlugin.java b/gcm3/src/main/java/plugins/groups/GroupsPlugin.java deleted file mode 100644 index d8fbf4e97..000000000 --- a/gcm3/src/main/java/plugins/groups/GroupsPlugin.java +++ /dev/null @@ -1,32 +0,0 @@ -package plugins.groups; - -import nucleus.Plugin; -import plugins.groups.datamanagers.GroupsDataManager; -import plugins.people.PeoplePluginId; -import plugins.stochastics.StochasticsPluginId; - -/** - * A plugin providing a group data manager to the simulation. - * - * @author Shawn Hatch - * - */ -public final class GroupsPlugin { - private GroupsPlugin() { - } - - public static Plugin getGroupPlugin(GroupsPluginData groupsPluginData) { - - return Plugin .builder()// - .setPluginId(GroupsPluginId.PLUGIN_ID)// - .addPluginData(groupsPluginData)// - .addPluginDependency(PeoplePluginId.PLUGIN_ID)// - .addPluginDependency(StochasticsPluginId.PLUGIN_ID)// - .setInitializer((c) -> { - GroupsPluginData pluginData = c.getPluginData(GroupsPluginData.class); - c.addDataManager(new GroupsDataManager(pluginData)); - })// - .build(); - } - -} diff --git a/gcm3/src/main/java/plugins/groups/GroupsPluginData.java b/gcm3/src/main/java/plugins/groups/GroupsPluginData.java deleted file mode 100644 index 20cb7202d..000000000 --- a/gcm3/src/main/java/plugins/groups/GroupsPluginData.java +++ /dev/null @@ -1,610 +0,0 @@ -package plugins.groups; - -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -import net.jcip.annotations.Immutable; -import nucleus.PluginData; -import nucleus.PluginDataBuilder; -import plugins.groups.support.GroupError; -import plugins.groups.support.GroupId; -import plugins.groups.support.GroupPropertyId; -import plugins.groups.support.GroupTypeId; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import plugins.util.properties.PropertyDefinition; -import plugins.util.properties.PropertyError; -import util.errors.ContractException; - -/** - * An immutable container of the initial state of groups. It contains:
    - *
      - *
    • group type ids
    • - *
    • group ids
    • - *
    • group property definitions: each group type has its own set of properties - * and all property definitions have default values
    • - *
    • group property values
    • - *
    • person group assignments
    • - *
    - * - * @author Shawn Hatch - * - */ -@Immutable -public final class GroupsPluginData implements PluginData { - - private static class Data { - - private final Map> groupPropertyDefinitions; - - private final Map> groupPropertyValues; - - private final Set groupTypeIds; - - private final Set groupIds; - - private final Map groupTypes; - - private final Map> groupMemberships; - - - public Data() { - groupPropertyDefinitions = new LinkedHashMap<>(); - groupPropertyValues = new LinkedHashMap<>(); - groupTypeIds = new LinkedHashSet<>(); - groupIds = new LinkedHashSet<>(); - groupTypes = new LinkedHashMap<>(); - groupMemberships = new LinkedHashMap<>(); - } - - public Data(Data data) { - groupPropertyDefinitions = new LinkedHashMap<>(); - for(GroupTypeId groupTypeId : data.groupPropertyDefinitions.keySet()) { - Map map = data.groupPropertyDefinitions.get(groupTypeId); - Map newMap = new LinkedHashMap<>(); - newMap.putAll(map); - groupPropertyDefinitions.put(groupTypeId, newMap); - } - groupPropertyValues = new LinkedHashMap<>(); - for(GroupId groupId : data.groupPropertyValues.keySet()) { - Map map = data.groupPropertyValues.get(groupId); - Map newMap = new LinkedHashMap<>(); - newMap.putAll(map); - groupPropertyValues.put(groupId, newMap); - } - groupTypeIds = new LinkedHashSet<>(data.groupTypeIds); - groupIds = new LinkedHashSet<>(data.groupIds); - groupTypes = new LinkedHashMap<>(data.groupTypes); - - groupMemberships = new LinkedHashMap<>(); - for(GroupId groupId : data.groupMemberships.keySet()) { - Set set = data.groupMemberships.get(groupId); - Set newSet = new LinkedHashSet<>(set); - groupMemberships.put(groupId, newSet); - } - - } - - } - - private final Data data; - - private GroupsPluginData(Data data) { - this.data = data; - } - - /** - * Returns a new builder instance - */ - public static Builder builder() { - return new Builder(new Data()); - } - - private static void validateGroupPropertyIsNotDefined(Data data, GroupTypeId groupTypeId, GroupPropertyId groupPropertyId) { - Map map = data.groupPropertyDefinitions.get(groupTypeId); - if (map != null) { - if (map.containsKey(groupPropertyId)) { - throw new ContractException(GroupError.DUPLICATE_GROUP_PROPERTY_DEFINITION, groupTypeId + ": " + groupPropertyId); - } - } - } - - private static void validatePropertyDefinitionNotNull(PropertyDefinition propertyDefinition) { - if (propertyDefinition == null) { - throw new ContractException(GroupError.NULL_PROPERTY_DEFINITION); - } - } - - private static void validateGroupPropertyIdNotNull(GroupPropertyId groupPropertyId) { - if (groupPropertyId == null) { - throw new ContractException(GroupError.NULL_GROUP_PROPERTY_ID); - } - } - - private static void validateGroupTypeIdNotNull(GroupTypeId groupTypeId) { - if (groupTypeId == null) { - throw new ContractException(GroupError.NULL_GROUP_TYPE_ID); - } - } - - private static void validateGroupTypeDoesNotExist(final Data data, final GroupTypeId groupTypeId) { - - if (data.groupTypeIds.contains(groupTypeId)) { - throw new ContractException(GroupError.DUPLICATE_GROUP_TYPE, groupTypeId); - } - } - - private static void validateGroupPropertyValueNotSet(Data data, GroupId groupId, GroupPropertyId groupPropertyId) { - Map map = data.groupPropertyValues.get(groupId); - if (map != null) { - if (map.containsKey(groupPropertyId)) { - throw new ContractException(GroupError.DUPLICATE_GROUP_PROPERTY_VALUE_ASSIGNMENT, groupId + ": " + groupPropertyId); - } - } - - } - - private static void validateGroupPropertyValueNotNull(Object groupPropertyValue) { - if (groupPropertyValue == null) { - throw new ContractException(GroupError.NULL_GROUP_PROPERTY_VALUE); - } - } - - private static void validateGroupIdNotNull(GroupId groupId) { - if (groupId == null) { - throw new ContractException(GroupError.NULL_GROUP_ID); - } - } - - private static void validateGroupDoesNotExist(final Data data, final GroupId groupId) { - if (data.groupIds.contains(groupId)) { - throw new ContractException(GroupError.DUPLICATE_GROUP_ID, groupId); - } - } - - /** - * Builder class for GroupInitialData - * - * @author Shawn Hatch - * - */ - public static class Builder implements PluginDataBuilder{ - - private Data data; - - private Builder(Data data) { - this.data = data; - - } - - /** - * Return the GroupInitialData from the data collected by this builder. - * - * @throws ContractException - * - * - * - *
  • {@linkplain PersonError#UNKNOWN_GROUP_TYPE_ID}
  • - * if a group was added with a group type id that was not - * defined - * - *
  • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID}
  • if - * a group property definition was defined for a group type - * id that was not defined. - * - *
  • {@linkplain GroupError#UNKNOWN_GROUP_ID}
  • if a - * group property value was set for a group id that was not - * defined. - * - *
  • {@linkplain GroupError#UNKNOWN_GROUP_PROPERTY_ID}
  • - * if a group property value is added for a group property - * id that is not associated with the group. - * - *
  • {@linkplain GroupError#INCOMPATIBLE_VALUE}
  • if a - * group property value is added that is incompatible with - * the corresponding property definition - * - *
  • {@linkplain GroupError#PROPERTY_DEFINITION_REQUIRES_DEFAULT}
  • - * if a group property definition does not contain a default - * value - * - */ - public GroupsPluginData build() { - try { - validate(data); - return new GroupsPluginData(data); - } finally { - data = new Data(); - } - } - - /** - * Adds a person to a group - * - * @throws ContractException - * - *
  • {@linkplain GroupError#NULL_GROUP_ID}
  • if the - * group id is null - * - *
  • {@linkplain PersonError#NULL_PERSON_ID}
  • if the - * person id is null - * - *
  • {@linkplain GroupError#DUPLICATE_GROUP_MEMBERSHIP}
  • - * if the person is already a member of the group - * - */ - public Builder addPersonToGroup(final GroupId groupId, final PersonId personId) { - validateGroupIdNotNull(groupId); - validatePersonIdNotNull(personId); - validatePersonNotInGroup(data, groupId, personId); - Set people = data.groupMemberships.get(groupId); - if (people == null) { - people = new LinkedHashSet<>(); - data.groupMemberships.put(groupId, people); - } - people.add(personId); - return this; - } - - - - /** - * Adds a group type id - * - * @throws ContractException - * - *
  • {@linkplain GroupError#NULL_GROUP_TYPE_ID}
  • if - * the group type id is null - * - *
  • {@linkplain GroupError#DUPLICATE_GROUP_TYPE}
  • if - * the group type was already added - * - */ - public Builder addGroupTypeId(final GroupTypeId groupTypeId) { - validateGroupTypeIdNotNull(groupTypeId); - validateGroupTypeDoesNotExist(data, groupTypeId); - data.groupTypeIds.add(groupTypeId); - return this; - } - - /** - * Adds a group with the given group type - * - * @throws ContractException - *
  • {@linkplain GroupError#NULL_GROUP_ID}
  • if the - * group id is null - * - *
  • {@linkplain GroupError#NULL_GROUP_TYPE_ID}
  • if - * the group type id is null - * - *
  • {@linkplain GroupError#DUPLICATE_GROUP_ID}
  • if - * the group was already added - * - */ - public Builder addGroup(final GroupId groupId, final GroupTypeId groupTypeId) { - validateGroupIdNotNull(groupId); - validateGroupTypeIdNotNull(groupTypeId); - validateGroupDoesNotExist(data, groupId); - data.groupIds.add(groupId); - data.groupTypes.put(groupId, groupTypeId); - return this; - } - - /** - * Defines a group property - * - * @throws ContractException - *
  • {@linkplain GroupError#NULL_GROUP_TYPE_ID}
  • if - * the group type id is null - * - *
  • {@linkplain GroupError#NULL_GROUP_PROPERTY_ID}
  • - * if the group property id is null - * - *
  • {@linkplain GroupError#NULL_PROPERTY_DEFINITION}
  • - * if the property definition is null - * - *
  • {@linkplain GroupError#DUPLICATE_GROUP_PROPERTY_DEFINITION} - *
  • if a property definition for the given group type - * id and property id was previously defined. - * - */ - public Builder defineGroupProperty(final GroupTypeId groupTypeId, final GroupPropertyId groupPropertyId, final PropertyDefinition propertyDefinition) { - validateGroupTypeIdNotNull(groupTypeId); - validateGroupPropertyIdNotNull(groupPropertyId); - validatePropertyDefinitionNotNull(propertyDefinition); - validateGroupPropertyIsNotDefined(data, groupTypeId, groupPropertyId); - Map propertyDefinitionsMap = data.groupPropertyDefinitions.get(groupTypeId); - if (propertyDefinitionsMap == null) { - propertyDefinitionsMap = new LinkedHashMap<>(); - data.groupPropertyDefinitions.put(groupTypeId, propertyDefinitionsMap); - } - propertyDefinitionsMap.put(groupPropertyId, propertyDefinition); - return this; - } - - /** - * Sets the group property value that overrides the default value of the - * corresponding property definition - * - * @throws ContractException - * - *
  • {@linkplain GroupError#NULL_GROUP_ID}
  • if the - * group id is null - * - *
  • {@linkplain GroupError#NULL_GROUP_PROPERTY_ID}
  • if - * the group property id is null - * - *
  • {@linkplain GroupError#NULL_GROUP_PROPERTY_VALUE} - *
  • if the group property value is null - * - *
  • {@linkplain GroupError#DUPLICATE_GROUP_PROPERTY_VALUE_ASSIGNMENT} - *
  • if the group property value was previously assigned - * - */ - public Builder setGroupPropertyValue(final GroupId groupId, final GroupPropertyId groupPropertyId, final Object groupPropertyValue) { - validateGroupIdNotNull(groupId); - validateGroupPropertyIdNotNull(groupPropertyId); - validateGroupPropertyValueNotNull(groupPropertyValue); - validateGroupPropertyValueNotSet(data, groupId, groupPropertyId); - Map propertyMap = data.groupPropertyValues.get(groupId); - if (propertyMap == null) { - propertyMap = new LinkedHashMap<>(); - data.groupPropertyValues.put(groupId, propertyMap); - } - propertyMap.put(groupPropertyId, groupPropertyValue); - return this; - } - } - - private static void validate(Data data) { - - for (GroupId groupId : data.groupMemberships.keySet()) { - if (!data.groupIds.contains(groupId)) { - throw new ContractException(GroupError.UNKNOWN_GROUP_ID, "A group membership contains the unknown group " + groupId); - } - } - - for (GroupId groupId : data.groupTypes.keySet()) { - GroupTypeId groupTypeId = data.groupTypes.get(groupId); - if (!data.groupTypeIds.contains(groupTypeId)) { - throw new ContractException(GroupError.UNKNOWN_GROUP_TYPE_ID, groupId + " has unknown group type " + groupTypeId); - } - } - - for (GroupTypeId groupTypeId : data.groupPropertyDefinitions.keySet()) { - if (!data.groupTypeIds.contains(groupTypeId)) { - throw new ContractException(GroupError.UNKNOWN_GROUP_TYPE_ID, "group property definitions have unknown group type " + groupTypeId); - } - } - - for (GroupId groupId : data.groupPropertyValues.keySet()) { - if (!data.groupIds.contains(groupId)) { - throw new ContractException(GroupError.UNKNOWN_GROUP_ID, "A group property value contains the unknown group " + groupId); - } - GroupTypeId groupTypeId = data.groupTypes.get(groupId); - - Map propDefMap = data.groupPropertyDefinitions.get(groupTypeId); - Map valueMap = data.groupPropertyValues.get(groupId); - for (GroupPropertyId groupPropertyId : valueMap.keySet()) { - PropertyDefinition propertyDefinition = propDefMap.get(groupPropertyId); - if (propertyDefinition == null) { - throw new ContractException(GroupError.UNKNOWN_GROUP_PROPERTY_ID, groupPropertyId + " under group type " + groupTypeId); - } - Object propertyValue = valueMap.get(groupPropertyId); - if (!propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())) { - throw new ContractException(PropertyError.INCOMPATIBLE_VALUE, groupId + ": " + groupPropertyId + ": " + propertyValue); - } - } - - } - - /* - * All group property definitions must have default values since groups - * may be created dynamically in the simulation - */ - for (GroupTypeId groupTypeId : data.groupTypeIds) { - Map propertyDefinitionMap = data.groupPropertyDefinitions.get(groupTypeId); - if (propertyDefinitionMap != null) { - for (GroupPropertyId groupPropertyId : propertyDefinitionMap.keySet()) { - PropertyDefinition propertyDefinition = propertyDefinitionMap.get(groupPropertyId); - if (!propertyDefinition.getDefaultValue().isPresent()) { - throw new ContractException(GroupError.PROPERTY_DEFINITION_REQUIRES_DEFAULT, groupTypeId + ": " + groupPropertyId); - } - } - } - } - } - - private static void validateGroupTypeExists(final Data data, final GroupTypeId groupTypeId) { - if (groupTypeId == null) { - throw new ContractException(GroupError.NULL_GROUP_TYPE_ID); - } - if (!data.groupTypeIds.contains(groupTypeId)) { - throw new ContractException(GroupError.UNKNOWN_GROUP_TYPE_ID, groupTypeId); - } - } - - private static void validateGroupPropertyIsDefined(final Data data, final GroupTypeId groupTypeId, final GroupPropertyId groupPropertyId) { - final Map map = data.groupPropertyDefinitions.get(groupTypeId); - if (map == null) { - throw new ContractException(GroupError.UNKNOWN_GROUP_PROPERTY_ID, groupPropertyId); - } - final PropertyDefinition propertyDefinition = map.get(groupPropertyId); - if (propertyDefinition == null) { - throw new ContractException(GroupError.UNKNOWN_GROUP_PROPERTY_ID, groupPropertyId); - } - } - - /** - * Returns the property definition for the given group type id and group - * property id - * - * @throws ContractException - *
  • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if the group - * type id is null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} if the - * group type id is unknown
  • - *
  • {@linkplain GroupError#NULL_GROUP_PROPERTY_ID} if the - * group property id is null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_PROPERTY_ID} if the - * group property id is not associated with the group type id - * via a property definition
  • - */ - public PropertyDefinition getGroupPropertyDefinition(final GroupTypeId groupTypeId, final GroupPropertyId groupPropertyId) { - validateGroupTypeExists(data, groupTypeId); - validateGroupPropertyIdNotNull(groupPropertyId); - validateGroupPropertyIsDefined(data, groupTypeId, groupPropertyId); - final Map map = data.groupPropertyDefinitions.get(groupTypeId); - final PropertyDefinition propertyDefinition = map.get(groupPropertyId); - return propertyDefinition; - } - - /** - * Returns the set of group property ids for the given group type id - * - * @throws ContractException - *
  • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if the group - * type id is null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} if the - * group type id is unknown
  • - */ - @SuppressWarnings("unchecked") - public Set getGroupPropertyIds(final GroupTypeId groupTypeId) { - validateGroupTypeExists(data, groupTypeId); - final Set result = new LinkedHashSet<>(); - final Map map = data.groupPropertyDefinitions.get(groupTypeId); - if (map != null) { - for (GroupPropertyId groupPropertyId : map.keySet()) { - result.add((T) groupPropertyId); - } - } - return result; - } - - private static void validateGroupExists(final Data data, final GroupId groupId) { - if (groupId == null) { - throw new ContractException(GroupError.NULL_GROUP_ID); - } - if (!data.groupIds.contains(groupId)) { - throw new ContractException(GroupError.UNKNOWN_GROUP_ID, groupId); - } - } - - /** - * Returns the property value associated with the given group id and group - * property id - * - * @throws ContractException - *
  • {@linkplain GroupError#NULL_GROUP_ID} if the group id is - * null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_ID} if the group id - * is unknown
  • - *
  • {@linkplain GroupError#NULL_GROUP_PROPERTY_ID} if the - * group property id is null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_PROPERTY_ID} if the - * group property id is not associated with the group type id - * via a property definition
  • - * - */ - @SuppressWarnings("unchecked") - public T getGroupPropertyValue(final GroupId groupId, final GroupPropertyId groupPropertyId) { - validateGroupExists(data, groupId); - validateGroupPropertyIdNotNull(groupPropertyId); - - final GroupTypeId groupTypeId = data.groupTypes.get(groupId); - validateGroupPropertyIsDefined(data, groupTypeId, groupPropertyId); - - Object result = null; - final Map map = data.groupPropertyValues.get(groupId); - if (map != null) { - result = map.get(groupPropertyId); - } - if (result == null) { - final Map defMap = data.groupPropertyDefinitions.get(groupTypeId); - final PropertyDefinition propertyDefinition = defMap.get(groupPropertyId); - result = propertyDefinition.getDefaultValue().get(); - } - return (T) result; - - } - - /** - * Returns the set of group type ids - * - */ - @SuppressWarnings("unchecked") - public Set getGroupTypeIds() { - Set result = new LinkedHashSet<>(data.groupTypeIds.size()); - for (GroupTypeId groupTypeId : data.groupTypeIds) { - result.add((T) groupTypeId); - } - return result; - } - - /** - * Returns the collected group idd - */ - public Set getGroupIds() { - return new LinkedHashSet<>(data.groupIds); - } - - /** - * Returns the group type id associated with the given group id - * - * @throws ContractException - *
  • {@linkplain GroupError#NULL_GROUP_ID} if the group id is - * null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_ID} if the group id - * is unknown
  • - */ - @SuppressWarnings("unchecked") - public T getGroupTypeId(final GroupId groupId) { - validateGroupExists(data, groupId); - final GroupTypeId result = data.groupTypes.get(groupId); - return (T) result; - } - - @Override - public PluginDataBuilder getCloneBuilder() { - - return new Builder(new Data(data)); - } - /** - * Returns the set of people associated with the group id - * - * @throws ContractException - * - *
  • {@linkplain GroupError#NULL_GROUP_ID} if the group id - * is null
  • - * - *
  • {@linkplain GroupError#UNKNOWN_GROUP_ID} if the group id - * is unknown
  • - */ - public Set getGroupMembers(final GroupId groupId) { - validateGroupExists(data, groupId); - final Set result = new LinkedHashSet<>(); - final Set set = data.groupMemberships.get(groupId); - if (set != null) { - result.addAll(set); - } - return result; - } - - private static void validatePersonNotInGroup(Data data, GroupId groupId, PersonId personId) { - Set set = data.groupMemberships.get(groupId); - if (set != null) { - if (set.contains(personId)) { - throw new ContractException(GroupError.DUPLICATE_PERSON_GROUP_ASSIGNMENT, personId + ": " + groupId); - } - } - } - - private static void validatePersonIdNotNull(PersonId personId) { - if (personId == null) { - throw new ContractException(PersonError.NULL_PERSON_ID); - } - } - -} diff --git a/gcm3/src/main/java/plugins/groups/GroupsPluginId.java b/gcm3/src/main/java/plugins/groups/GroupsPluginId.java deleted file mode 100644 index 62472c4ae..000000000 --- a/gcm3/src/main/java/plugins/groups/GroupsPluginId.java +++ /dev/null @@ -1,14 +0,0 @@ -package plugins.groups; - -import nucleus.PluginId; -/** - * Static plugin id implementation for the GroupPlugin - * - * @author Shawn Hatch - * - */ - -public final class GroupsPluginId implements PluginId { - public final static PluginId PLUGIN_ID = new GroupsPluginId(); - private GroupsPluginId() {}; -} diff --git a/gcm3/src/main/java/plugins/groups/actors/GroupPopulationReport.java b/gcm3/src/main/java/plugins/groups/actors/GroupPopulationReport.java deleted file mode 100644 index c7b79458b..000000000 --- a/gcm3/src/main/java/plugins/groups/actors/GroupPopulationReport.java +++ /dev/null @@ -1,116 +0,0 @@ -package plugins.groups.actors; - -import java.util.LinkedHashMap; -import java.util.Map; - -import nucleus.ActorContext; -import plugins.groups.datamanagers.GroupsDataManager; -import plugins.groups.support.GroupId; -import plugins.groups.support.GroupTypeId; -import plugins.reports.support.PeriodicReport; -import plugins.reports.support.ReportHeader; -import plugins.reports.support.ReportId; -import plugins.reports.support.ReportItem; -import plugins.reports.support.ReportPeriod; - -/** - * A periodic Report that displays the number of groups having a particular - * number of people for a given group type. - * - * Fields - * - * GroupType -- the group type of group - * - * PersonCount -- the number of people in each group - * - * GroupCount -- the number of groups having the person count - * - * @author Shawn Hatch - * - */ -public final class GroupPopulationReport extends PeriodicReport { - - public GroupPopulationReport(ReportId reportId,ReportPeriod reportPeriod) { - super(reportId,reportPeriod); - } - - /* - * - * Count of the number of groups having a particular person count for a - * particular group type - * - */ - private static class Counter { - int count; - } - - private ReportHeader reportHeader; - - private ReportHeader getReportHeader() { - if (reportHeader == null) { - ReportHeader.Builder reportHeaderBuilder = ReportHeader.builder(); - reportHeader = addTimeFieldHeaders(reportHeaderBuilder)// - .add("group_type")// - .add("person_count")// - .add("group_count")// - .build();// - } - return reportHeader; - } - - @Override - protected void flush(ActorContext actorContext) { - - final ReportItem.Builder reportItemBuilder = ReportItem.builder(); - - /* - * Count the number of groups of each size that exist for each group - * type - */ - Map> groupTypePopulationMap = new LinkedHashMap<>(); - for (GroupTypeId groupTypeId : groupsDataManager.getGroupTypeIds()) { - Map groupSizeMap = new LinkedHashMap<>(); - groupTypePopulationMap.put(groupTypeId, groupSizeMap); - for (GroupId groupId : groupsDataManager.getGroupsForGroupType(groupTypeId)) { - Integer personCountForGroup = groupsDataManager.getPersonCountForGroup(groupId); - Counter counter = groupSizeMap.get(personCountForGroup); - if (counter == null) { - counter = new Counter(); - groupSizeMap.put(personCountForGroup, counter); - } - counter.count++; - } - } - - /* - * Report the collected group counters - */ - for (final GroupTypeId groupTypeId : groupTypePopulationMap.keySet()) { - Map groupSizeMap = groupTypePopulationMap.get(groupTypeId); - for (final Integer personCount : groupSizeMap.keySet()) { - Counter counter = groupSizeMap.get(personCount); - - final int groupCount = counter.count; - reportItemBuilder.setReportHeader(getReportHeader()); - reportItemBuilder.setReportId(getReportId()); - fillTimeFields(reportItemBuilder); - reportItemBuilder.addValue(groupTypeId.toString()); - reportItemBuilder.addValue(personCount); - reportItemBuilder.addValue(groupCount); - ReportItem reportItem = reportItemBuilder.build(); - actorContext.releaseOutput(reportItem); - - } - } - - } - - private GroupsDataManager groupsDataManager; - - @Override - public void init(ActorContext actorContext) { - super.init(actorContext); - groupsDataManager = actorContext.getDataManager(GroupsDataManager.class); - } - -} \ No newline at end of file diff --git a/gcm3/src/main/java/plugins/groups/actors/GroupPropertyReport.java b/gcm3/src/main/java/plugins/groups/actors/GroupPropertyReport.java deleted file mode 100644 index fdfde9cf8..000000000 --- a/gcm3/src/main/java/plugins/groups/actors/GroupPropertyReport.java +++ /dev/null @@ -1,395 +0,0 @@ -package plugins.groups.actors; - -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -import nucleus.ActorContext; -import nucleus.EventLabel; -import plugins.groups.datamanagers.GroupsDataManager; -import plugins.groups.events.GroupAdditionEvent; -import plugins.groups.events.GroupImminentRemovalEvent; -import plugins.groups.events.GroupPropertyUpdateEvent; -import plugins.groups.support.GroupError; -import plugins.groups.support.GroupId; -import plugins.groups.support.GroupPropertyId; -import plugins.groups.support.GroupTypeId; -import plugins.reports.support.PeriodicReport; -import plugins.reports.support.ReportError; -import plugins.reports.support.ReportHeader; -import plugins.reports.support.ReportId; -import plugins.reports.support.ReportItem; -import plugins.reports.support.ReportPeriod; -import util.errors.ContractException; - -/** - * A periodic Report that displays the number of groups having particular values - * for each group property for a given group type. Only non-zero person counts - * are reported. The report is further limited to the - * (GroupType,GroupPropertyId) pairs contained in the - * GroupPropertyReportSettings instance used to initialize this report. - * - * - * - * Fields - * - * GroupType -- the group type of group - * - * Property -- the group property identifier - * - * Value -- the value of the property - * - * GroupCount -- the number of groups having the property value for the given - * group type - * - * @author Shawn Hatch - * - */ -public final class GroupPropertyReport extends PeriodicReport { - - private static class Scaffold { - private ReportPeriod reportPeriod = ReportPeriod.DAILY; - private final Map> clientPropertyMap = new LinkedHashMap<>(); - private final Set allProperties = new LinkedHashSet<>(); - private ReportId reportId; - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - private Scaffold scaffold = new Scaffold(); - - private Builder() { - } - - /** - * Returns the GroupPropertyReport instance - */ - public GroupPropertyReport build() { - try { - - return new GroupPropertyReport(scaffold); - } finally { - scaffold = new Scaffold(); - } - } - - /** - * Sets the report period for this report - * - * @throws ContractException - *
  • {@linkplain ReportError#NULL_REPORT_PERIOD} if the report period is null
  • - *
  • if the report period END_OF_SIMULATION
  • - */ - public Builder setReportPeriod(ReportPeriod reportPeriod) { - if (reportPeriod == null) { - throw new ContractException(ReportError.NULL_REPORT_PERIOD); - } - - if (reportPeriod == ReportPeriod.END_OF_SIMULATION) { - throw new ContractException(ReportError.UNSUPPORTED_REPORT_PERIOD, ReportPeriod.END_OF_SIMULATION); - } - scaffold.reportPeriod = reportPeriod; - return this; - } - - /** - * Sets the report period for this report - * - * @throws ContractException - *
  • {@linkplain ReportError#NULL_REPORT_ID} if the report period is null
  • - * - */ - public Builder setReportId(ReportId reportId) { - if (reportId == null) { - throw new ContractException(ReportError.NULL_REPORT_ID); - } - - scaffold.reportId = reportId; - return this; - } - - /** - * Adds all properties for the given group type id - * - * @throws ContractException - *
  • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if the group type id is null
  • - */ - public Builder addAllProperties(GroupTypeId groupTypeId) { - if (groupTypeId == null) { - throw new ContractException(GroupError.NULL_GROUP_TYPE_ID); - } - scaffold.allProperties.add(groupTypeId); - return this; - } - - /** - * Adds all properties for the given group type id - * - * @throws ContractException - *
  • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if the group type id is null
  • - *
  • {@linkplain GroupError#NULL_GROUP_PROPERTY_ID} if the group property id is null
  • - */ - public Builder addProperty(GroupTypeId groupTypeId, GroupPropertyId groupPropertyId) { - if (groupTypeId == null) { - throw new ContractException(GroupError.NULL_GROUP_TYPE_ID); - } - if (groupPropertyId == null) { - throw new ContractException(GroupError.NULL_GROUP_PROPERTY_ID); - } - Set set = scaffold.clientPropertyMap.get(groupTypeId); - if (set == null) { - set = new LinkedHashSet<>(); - scaffold.clientPropertyMap.put(groupTypeId, set); - } - set.add(groupPropertyId); - return this; - } - - } - - private final Scaffold scaffold; - - private GroupPropertyReport(Scaffold scaffold) { - super(scaffold.reportId,scaffold.reportPeriod); - this.scaffold = scaffold; - } - - private static class Counter { - int count; - } - - /* - * The set of (GroupTypeId,GroupPropertyId) pairs collected from the - * GroupPropertyReportSettings supplied during initialization - */ - private final Map> clientPropertyMap = new LinkedHashMap<>(); - - /* - * For each (GroupTypeId,GroupPropertyId,property value) triplet, count the - * number of groups having that triplet - */ - private final Map>> groupTypeMap = new LinkedHashMap<>(); - - private ReportHeader reportHeader; - - private ReportHeader getReportHeader() { - if (reportHeader == null) { - ReportHeader.Builder reportHeaderBuilder = ReportHeader.builder(); - reportHeader = addTimeFieldHeaders(reportHeaderBuilder)// - .add("group_type")// - .add("property")// - .add("value")// - .add("group_count")// - .build();// - } - return reportHeader; - } - - /* - * Decrement the number of groups for the given - * (GroupTypeId,GroupPropertyId,property value) triplet - */ - private void decrement(final GroupTypeId groupTypeId, final GroupPropertyId groupPropertyId, final Object groupPropertyValue) { - getCounter(groupTypeId, groupPropertyId, groupPropertyValue).count--; - } - - @Override - protected void flush(ActorContext actorContext) { - - final ReportItem.Builder reportItemBuilder = ReportItem.builder(); - - for (final GroupTypeId groupTypeId : groupTypeMap.keySet()) { - final Map> propertyIdMap = groupTypeMap.get(groupTypeId); - for (final GroupPropertyId groupPropertyId : propertyIdMap.keySet()) { - final Map groupPropertyValueMap = propertyIdMap.get(groupPropertyId); - for (final Object groupPropertyValue : groupPropertyValueMap.keySet()) { - final Counter counter = groupPropertyValueMap.get(groupPropertyValue); - if (counter.count > 0) { - final int personCount = counter.count; - reportItemBuilder.setReportHeader(getReportHeader()); - reportItemBuilder.setReportId(getReportId()); - - fillTimeFields(reportItemBuilder); - reportItemBuilder.addValue(groupTypeId.toString()); - reportItemBuilder.addValue(groupPropertyId.toString()); - reportItemBuilder.addValue(groupPropertyValue); - reportItemBuilder.addValue(personCount); - - actorContext.releaseOutput(reportItemBuilder.build()); - } - } - } - } - } - - private Counter getCounter(final GroupTypeId groupTypeId, final GroupPropertyId groupPropertyId, final Object groupPropertyValue) { - final Map propertyValueMap = groupTypeMap.get(groupTypeId).get(groupPropertyId); - Counter counter = propertyValueMap.get(groupPropertyValue); - if (counter == null) { - counter = new Counter(); - propertyValueMap.put(groupPropertyValue, counter); - } - return counter; - } - - /* - * Increment the number of groups for the given - * (GroupTypeId,GroupPropertyId,property value) triplet - */ - private void increment(final GroupTypeId groupTypeId, final GroupPropertyId groupPropertyId, final Object groupPropertyValue) { - getCounter(groupTypeId, groupPropertyId, groupPropertyValue).count++; - } - - private GroupsDataManager groupsDataManager; - - @Override - public void init(final ActorContext actorContext) { - super.init(actorContext); - - groupsDataManager = actorContext.getDataManager(GroupsDataManager.class); - - // transfer all VALID property selections from the scaffold - Set groupTypeIds = groupsDataManager.getGroupTypeIds(); - for (GroupTypeId groupTypeId : groupTypeIds) { - Set groupPropertyIds = new LinkedHashSet<>(); - if (scaffold.allProperties.contains(groupTypeId)) { - groupPropertyIds.addAll(groupsDataManager.getGroupPropertyIds(groupTypeId)); - } else { - Set selectedPropertyIds = scaffold.clientPropertyMap.get(groupTypeId); - if (selectedPropertyIds != null) { - Set allPropertyIds = groupsDataManager.getGroupPropertyIds(groupTypeId); - for (GroupPropertyId groupPropertyId : allPropertyIds) { - if (selectedPropertyIds.contains(groupPropertyId)) { - groupPropertyIds.add(groupPropertyId); - } - } - } - } - clientPropertyMap.put(groupTypeId, groupPropertyIds); - } - - // determine the subscriptions for group creation - if (clientPropertyMap.keySet().equals(groupsDataManager.getGroupTypeIds())) { - - actorContext.subscribe(GroupAdditionEvent.class, getFlushingConsumer(this::handleGroupAdditionEvent)); - } else { - for (GroupTypeId groupTypeId : clientPropertyMap.keySet()) { - EventLabel eventLabelByGroupType = GroupAdditionEvent.getEventLabelByGroupType(actorContext, groupTypeId); - actorContext.subscribe(eventLabelByGroupType, getFlushingConsumer(this::handleGroupAdditionEvent)); - } - } - - //determine the subscriptions for group removal observations - if (clientPropertyMap.keySet().equals(groupsDataManager.getGroupTypeIds())) { - actorContext.subscribe(GroupImminentRemovalEvent.class, getFlushingConsumer(this::handleGroupImminentRemovalEvent)); - } else { - for (GroupTypeId groupTypeId : clientPropertyMap.keySet()) { - EventLabel eventLabelByGroupType = GroupImminentRemovalEvent.getEventLabelByGroupType(actorContext, groupTypeId); - actorContext.subscribe(eventLabelByGroupType, getFlushingConsumer(this::handleGroupImminentRemovalEvent)); - } - } - - //determine the subscriptions for group property changes - boolean allPropertiesRequired = false; - if (clientPropertyMap.keySet().equals(groupsDataManager.getGroupTypeIds())) { - allPropertiesRequired = true; - for (GroupTypeId groupTypeId : clientPropertyMap.keySet()) { - if(!clientPropertyMap.get(groupTypeId).equals(groupsDataManager.getGroupPropertyIds(groupTypeId))) { - allPropertiesRequired = false; - break; - } - } - } - - if(allPropertiesRequired) { - actorContext.subscribe(GroupPropertyUpdateEvent.class, getFlushingConsumer(this::handleGroupPropertyUpdateEvent)); - }else { - for (GroupTypeId groupTypeId : clientPropertyMap.keySet()) { - if(clientPropertyMap.get(groupTypeId).equals(groupsDataManager.getGroupPropertyIds(groupTypeId))) { - EventLabel eventLabelByGroupType = GroupPropertyUpdateEvent.getEventLabelByGroupType(actorContext, groupTypeId); - actorContext.subscribe(eventLabelByGroupType, getFlushingConsumer(this::handleGroupPropertyUpdateEvent)); - }else { - for(GroupPropertyId groupPropertyId : clientPropertyMap.get(groupTypeId)) { - EventLabel eventLabelByGroupTypeAndProperty = GroupPropertyUpdateEvent.getEventLabelByGroupTypeAndProperty(actorContext, groupTypeId, groupPropertyId); - actorContext.subscribe(eventLabelByGroupTypeAndProperty, getFlushingConsumer(this::handleGroupPropertyUpdateEvent)); - } - } - } - } - - /* - * Fill the top layers of the groupTypeMap. We do not yet know the set - * of property values, so we leave that layer empty. - * - */ - - for (GroupTypeId groupTypeId : clientPropertyMap.keySet()) { - final Map> propertyIdMap = new LinkedHashMap<>(); - groupTypeMap.put(groupTypeId, propertyIdMap); - Set groupPropertyIds = clientPropertyMap.get(groupTypeId); - for (final GroupPropertyId groupPropertyId : groupPropertyIds) { - final Map propertyValueMap = new LinkedHashMap<>(); - propertyIdMap.put(groupPropertyId, propertyValueMap); - } - } - - // group addition - for (GroupId groupId : groupsDataManager.getGroupIds()) { - final GroupTypeId groupTypeId = groupsDataManager.getGroupType(groupId); - if (clientPropertyMap.containsKey(groupTypeId)) { - for (final GroupPropertyId groupPropertyId : clientPropertyMap.get(groupTypeId)) { - final Object groupPropertyValue = groupsDataManager.getGroupPropertyValue(groupId, groupPropertyId); - increment(groupTypeId, groupPropertyId, groupPropertyValue); - } - } - } - } - - private void handleGroupPropertyUpdateEvent(ActorContext actorContext, GroupPropertyUpdateEvent groupPropertyUpdateEvent) { - GroupId groupId = groupPropertyUpdateEvent.getGroupId(); - - final GroupTypeId groupTypeId = groupsDataManager.getGroupType(groupId); - if (clientPropertyMap.containsKey(groupTypeId)) { - - GroupPropertyId groupPropertyId = groupPropertyUpdateEvent.getGroupPropertyId(); - Object previousPropertyValue = groupPropertyUpdateEvent.getPreviousPropertyValue(); - Object currentPropertyValue = groupPropertyUpdateEvent.getCurrentPropertyValue(); - - if (clientPropertyMap.get(groupTypeId).contains(groupPropertyId)) { - - increment(groupTypeId, groupPropertyId, currentPropertyValue); - decrement(groupTypeId, groupPropertyId, previousPropertyValue); - } - } - - } - - private void handleGroupAdditionEvent(ActorContext actorContext, GroupAdditionEvent groupAdditionEvent) { - GroupId groupId = groupAdditionEvent.getGroupId(); - final GroupTypeId groupTypeId = groupsDataManager.getGroupType(groupId); - if (clientPropertyMap.containsKey(groupTypeId)) { - for (final GroupPropertyId groupPropertyId : clientPropertyMap.get(groupTypeId)) { - final Object groupPropertyValue = groupsDataManager.getGroupPropertyValue(groupId, groupPropertyId); - increment(groupTypeId, groupPropertyId, groupPropertyValue); - } - } - } - - private void handleGroupImminentRemovalEvent(ActorContext actorContext, GroupImminentRemovalEvent groupImminentRemovalEvent) { - GroupId groupId = groupImminentRemovalEvent.getGroupId(); - final GroupTypeId groupTypeId = groupsDataManager.getGroupType(groupId); - if (clientPropertyMap.containsKey(groupTypeId)) { - Set groupPropertyIds = groupsDataManager.getGroupPropertyIds(groupTypeId); - for (final GroupPropertyId groupPropertyId : groupPropertyIds) { - if (clientPropertyMap.get(groupTypeId).contains(groupPropertyId)) { - final Object groupPropertyValue = groupsDataManager.getGroupPropertyValue(groupId, groupPropertyId); - decrement(groupTypeId, groupPropertyId, groupPropertyValue); - } - } - } - } -} \ No newline at end of file diff --git a/gcm3/src/main/java/plugins/groups/datamanagers/GroupsDataManager.java b/gcm3/src/main/java/plugins/groups/datamanagers/GroupsDataManager.java deleted file mode 100644 index 342481452..000000000 --- a/gcm3/src/main/java/plugins/groups/datamanagers/GroupsDataManager.java +++ /dev/null @@ -1,1491 +0,0 @@ -package plugins.groups.datamanagers; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; - -import org.apache.commons.math3.random.RandomGenerator; -import org.apache.commons.math3.util.FastMath; - -import net.jcip.annotations.GuardedBy; -import nucleus.DataManager; -import nucleus.DataManagerContext; -import nucleus.NucleusError; -import nucleus.SimulationContext; -import plugins.groups.GroupsPluginData; -import plugins.groups.events.GroupAdditionEvent; -import plugins.groups.events.GroupImminentRemovalEvent; -import plugins.groups.events.GroupMembershipAdditionEvent; -import plugins.groups.events.GroupMembershipRemovalEvent; -import plugins.groups.events.GroupPropertyUpdateEvent; -import plugins.groups.support.BulkGroupMembershipData; -import plugins.groups.support.GroupConstructionInfo; -import plugins.groups.support.GroupError; -import plugins.groups.support.GroupId; -import plugins.groups.support.GroupPropertyId; -import plugins.groups.support.GroupSampler; -import plugins.groups.support.GroupTypeId; -import plugins.groups.support.GroupWeightingFunction; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.events.BulkPersonAdditionEvent; -import plugins.people.events.PersonImminentRemovalEvent; -import plugins.people.support.BulkPersonConstructionData; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import plugins.stochastics.StochasticsDataManager; -import plugins.stochastics.support.RandomNumberGeneratorId; -import plugins.stochastics.support.StochasticsError; -import plugins.util.properties.BooleanPropertyManager; -import plugins.util.properties.DoublePropertyManager; -import plugins.util.properties.EnumPropertyManager; -import plugins.util.properties.FloatPropertyManager; -import plugins.util.properties.IndexedPropertyManager; -import plugins.util.properties.IntPropertyManager; -import plugins.util.properties.ObjectPropertyManager; -import plugins.util.properties.PropertyDefinition; -import plugins.util.properties.PropertyError; -import plugins.util.properties.arraycontainers.IntValueContainer; -import plugins.util.properties.arraycontainers.ObjectValueContainer; -import util.errors.ContractException; - -/** - *

    - * Mutable data manager that backs the {@linkplain PersonGroupDataView}. - *

    - * - * Implementation Notes - * - *

    - * Group membership is managed through four mappings: 1)People to Groups, - * 2)Groups to People, 3)Groups to GroupTypes and 4)GroupTypes to Groups. Except - * for groups to types, all are implemented as ObjectValueContainers over - * ArrayLists of Integers rather than as straight-forward maps. This design was - * chosen to help minimize the memory requirements for storing grouping data for - * millions of people. - * - * The principle assumptions that have driven this design are: - * - * 1) The number of people per group is usually fairly small and rarely exceeds - * 100 - * - * 2) The number of groups per person is usually very small and rarely exceeds - * 10 - * - * 3) The number of group types is fairly small and rarely exceeds 20 - * - * The mapping from groups to types is accomplished with an IntValueContainer - * since the number of group types is small and we can treat the group type ids - * as Bytes or Shorts. The typesToIndexesMap and indexesToTypesMap serve to help - * convert group-type Object references to and from integers. - *

    - * - * @author Shawn Hatch - */ - -public final class GroupsDataManager extends DataManager { - - /* - * Used to generate new group id values - */ - private int masterGroupId; - - // container for group property values - private final Map> groupPropertyManagerMap = new LinkedHashMap<>(); - - // Guard for both weights array and weightedPersonIds array - private boolean samplingIsLocked; - - @GuardedBy("samplingIsLocked") - // weights array that is reused during sampling - private double[] weights; - - @GuardedBy("samplingIsLocked") - // person array that is reused during sampling - private PersonId[] weightedPersonIds; - - private final ObjectValueContainer typesToGroupsMap = new ObjectValueContainer(null, 0); - - private final ObjectValueContainer groupsToPeopleMap = new ObjectValueContainer(null, 0); - - private final ObjectValueContainer peopleToGroupsMap = new ObjectValueContainer(null, 0); - - private final IntValueContainer groupsToTypesMap = new IntValueContainer(-1); - - private final Map typesToIndexesMap = new LinkedHashMap<>(); - - private final Map> groupPropertyDefinitions = new LinkedHashMap<>(); - - private final List indexesToTypesMap = new ArrayList<>(); - - private StochasticsDataManager stochasticsDataManager; - - private DataManagerContext dataManagerContext; - - private final GroupsPluginData groupsPluginData; - - /** - * Constructs this person group data manager - * - * @throws ContractException - *
  • {@linkplain NucleusError#NULL_CONTEXT}
  • - */ - public GroupsDataManager(GroupsPluginData groupsPluginData) { - if (groupsPluginData == null) { - throw new ContractException(GroupError.NULL_GROUP_INITIALIZATION_DATA); - } - this.groupsPluginData = groupsPluginData; - } - - private PeopleDataManager peopleDataManager; - - /** - * Initial behavior - * - *
  • Adds all event labelers defined by the following events
  • - *
      - *
    • {@linkplain GroupMembershipAdditionEvent}
    • - *
    • {@linkplain GroupMembershipRemovalEvent}
    • - *
    • {@linkplain GroupAdditionEvent}
    • - *
    • {@linkplain GroupImminentRemovalEvent}
    • - *
    • {@linkplain GroupPropertyUpdateEvent}
    • - *
    - * - *
  • Adds groups, group memberships, group properties from the - * {@linkplain GroupsPluginData}
  • - * - * - *
  • Subscribes to the following events: - *
      - * - * {@linkplain BulkPersonAdditionEvent} Assigns the newly created people - * into newly created groups on the basis of auxiliary data carried in the - * event as a BulkGroupMembershipData. Publishes the groups to the person - * group data view. Generates the corresponding - * {@linkplain GroupAdditionEvent} events.
      - *
      - * Throws {@link ContractException} - *
        - *
      • {@link PersonError#UNKNOWN_PERSON_ID} if the event contains an - * unknown person id
      • - *
      • {@link PersonError#UNKNOWN_PERSON_ID} if the BulkMembership data - * exists and contains an unknown person id, i.e. it uses a person - * index
      • - *
      • {@link GroupError#UNKNOWN_GROUP_TYPE_ID} if the BulkMembership data - * exists and contains an unknown group type id
      • - *
      • {@link GroupError#UNKNOWN_GROUP_PROPERTY_ID} if the BulkMembership - * data exists and contains an unknown group property id
      • - *
      • {@link PropertyError#INCOMPATIBLE_VALUE} if the BulkMembership data - * exists and contains an incompatible group property value
      • - *
      - * - * {@linkplain PersonImminentRemovalEvent} Removes the person from all - * groups by scheduling the removal for the current time. This allows - * references and group memberships to remain long enough for resolvers, - * agents and reports to have final reference to the person while still - * associated with any relevant groups. - * - * - *
      - *
      - * Throws {@linkplain ContractException} - *
        - *
      • {@linkplain PersonError#NULL_PERSON_ID} if the person id is null
      • - *
      • {@linkplain PersonError#UNKNOWN_PERSON_ID} if the person id is - * unknown
      • - *
      - * - * - *
    - * - * - */ - @Override - public void init(DataManagerContext dataManagerContext) { - super.init(dataManagerContext); - if (dataManagerContext == null) { - throw new ContractException(NucleusError.NULL_SIMULATION_CONTEXT); - } - this.dataManagerContext = dataManagerContext; - stochasticsDataManager = dataManagerContext.getDataManager(StochasticsDataManager.class); - peopleDataManager = dataManagerContext.getDataManager(PeopleDataManager.class); - - /* - * Add the event labelers associated with the various observation events - * contained in the groups plugin. - */ - - dataManagerContext.addEventLabeler(GroupMembershipAdditionEvent.getEventLabelerForGroup()); - dataManagerContext.addEventLabeler(GroupMembershipAdditionEvent.getEventLabelerForGroupAndPerson()); - dataManagerContext.addEventLabeler(GroupMembershipAdditionEvent.getEventLabelerForGroupType(this)); - dataManagerContext.addEventLabeler(GroupMembershipAdditionEvent.getEventLabelerForGroupTypeAndPerson(this)); - dataManagerContext.addEventLabeler(GroupMembershipAdditionEvent.getEventLabelerForPerson()); - - dataManagerContext.addEventLabeler(GroupMembershipRemovalEvent.getEventLabelerForGroup()); - dataManagerContext.addEventLabeler(GroupMembershipRemovalEvent.getEventLabelerForGroupAndPerson()); - dataManagerContext.addEventLabeler(GroupMembershipRemovalEvent.getEventLabelerForGroupType(this)); - dataManagerContext.addEventLabeler(GroupMembershipRemovalEvent.getEventLabelerForGroupTypeAndPerson(this)); - dataManagerContext.addEventLabeler(GroupMembershipRemovalEvent.getEventLabelerForPerson()); - - dataManagerContext.addEventLabeler(GroupAdditionEvent.getEventLabelerForGroupType(this)); - - dataManagerContext.addEventLabeler(GroupImminentRemovalEvent.getEventLabelerForGroup()); - dataManagerContext.addEventLabeler(GroupImminentRemovalEvent.getEventLabelerForGroupType(this)); - - dataManagerContext.addEventLabeler(GroupPropertyUpdateEvent.getEventLabelerForGroup()); - dataManagerContext.addEventLabeler(GroupPropertyUpdateEvent.getEventLabelerForGroupAndProperty()); - dataManagerContext.addEventLabeler(GroupPropertyUpdateEvent.getEventLabelerForGroupType(this)); - dataManagerContext.addEventLabeler(GroupPropertyUpdateEvent.getEventLabelerForGroupTypeAndProperty(this)); - - loadGroupTypes(); - loadGroupPropertyDefinitions(); - loadGroups(); - loadGroupMembership(); - loadGroupPropertyValues(); - - dataManagerContext.subscribe(BulkPersonAdditionEvent.class, this::handleBulkPersonAdditionEvent); - dataManagerContext.subscribe(PersonImminentRemovalEvent.class, this::handlePersonImminentRemovalEvent); - - } - - private void loadGroupPropertyDefinitions() { - for (final GroupTypeId groupTypeId : groupsPluginData.getGroupTypeIds()) { - final Set propertyIds = groupsPluginData.getGroupPropertyIds(groupTypeId); - for (final GroupPropertyId groupPropertyId : propertyIds) { - final PropertyDefinition propertyDefinition = groupsPluginData.getGroupPropertyDefinition(groupTypeId, groupPropertyId); - Map managerMap = groupPropertyManagerMap.get(groupTypeId); - Map map = groupPropertyDefinitions.get(groupTypeId); - final IndexedPropertyManager indexedPropertyManager = getIndexedPropertyManager(dataManagerContext, propertyDefinition, 0); - managerMap.put(groupPropertyId, indexedPropertyManager); - map.put(groupPropertyId, propertyDefinition); - } - } - } - - private void loadGroupTypes() { - for (final GroupTypeId groupTypeId : groupsPluginData.getGroupTypeIds()) { - final int index = typesToIndexesMap.size(); - typesToIndexesMap.put(groupTypeId, index); - indexesToTypesMap.add(groupTypeId); - groupPropertyManagerMap.put(groupTypeId, new LinkedHashMap<>()); - groupPropertyDefinitions.put(groupTypeId, new LinkedHashMap<>()); - } - } - - private void loadGroupMembership() { - for (final GroupId groupId : groupsPluginData.getGroupIds()) { - for (final PersonId personId : groupsPluginData.getGroupMembers(groupId)) { - - List people = groupsToPeopleMap.getValue(groupId.getValue()); - if (people == null) { - people = new ArrayList<>(); - groupsToPeopleMap.setValue(groupId.getValue(), people); - } - people.add(personId); - - List groups = peopleToGroupsMap.getValue(personId.getValue()); - if (groups == null) { - groups = new ArrayList<>(1); - peopleToGroupsMap.setValue(personId.getValue(), groups); - } - groups.add(groupId); - } - } - } - - /** - * Adds a person to a group. Generates the corresponding - * {@linkplain GroupMembershipAdditionEvent} - * - * @throws ContractException - * - *
  • {@link PersonError#NULL_PERSON_ID} if the person id is - * null
  • - *
  • {@link PersonError#UNKNOWN_PERSON_ID} if the person id is - * unknown
  • - *
  • {@link GroupError#NULL_GROUP_ID} if the group id is - * null
  • - *
  • {@link GroupError#UNKNOWN_GROUP_ID} if the group id is - * unknown
  • - *
  • {@link GroupError#DUPLICATE_GROUP_MEMBERSHIP} if the - * person is already a member of the group
  • - * - * - */ - public void addPersonToGroup(final PersonId personId, final GroupId groupId) { - - validatePersonExists(personId); - validateGroupExists(groupId); - validatePersonNotInGroup(personId, groupId); - - List people = groupsToPeopleMap.getValue(groupId.getValue()); - if (people == null) { - people = new ArrayList<>(); - groupsToPeopleMap.setValue(groupId.getValue(), people); - } - people.add(personId); - - List groups = peopleToGroupsMap.getValue(personId.getValue()); - if (groups == null) { - groups = new ArrayList<>(1); - peopleToGroupsMap.setValue(personId.getValue(), groups); - } - groups.add(groupId); - - dataManagerContext.releaseEvent(new GroupMembershipAdditionEvent(personId, groupId)); - } - - /* - * Preconditions : the person and group exist - */ - private void validatePersonNotInGroup(final PersonId personId, final GroupId groupId) { - final List groups = peopleToGroupsMap.getValue(personId.getValue()); - if (groups != null && groups.contains(groupId)) { - throw new ContractException(GroupError.DUPLICATE_GROUP_MEMBERSHIP, "Person " + personId + " is already a member of group " + groupId); - } - } - - private void loadGroupPropertyValues() { - for (final GroupId groupId : groupsPluginData.getGroupIds()) { - final GroupTypeId groupTypeId = groupsPluginData.getGroupTypeId(groupId); - for (final GroupPropertyId groupPropertyId : groupsPluginData.getGroupPropertyIds(groupTypeId)) { - final Object groupPropertyValue = groupsPluginData.getGroupPropertyValue(groupId, groupPropertyId); - final PropertyDefinition propertyDefinition = groupPropertyDefinitions.get(groupTypeId).get(groupPropertyId); - Object defaultValue = propertyDefinition.getDefaultValue(); - if (!groupPropertyValue.equals(defaultValue)) { - final Map map = groupPropertyManagerMap.get(groupTypeId); - final IndexedPropertyManager indexedPropertyManager = map.get(groupPropertyId); - indexedPropertyManager.setPropertyValue(groupId.getValue(), groupPropertyValue); - } - } - } - } - - /** - * Sets a property value for a group. Generates the corresponding - * {@linkplain GroupPropertyUpdateEvent} event. - * - * @throws ContractException - *
  • {@linkplain GroupError.NULL_GROUP_ID } if the group id is - * null
  • - *
  • {@linkplain GroupError.UNKNOWN_GROUP_ID } if the group id - * is unknown
  • - *
  • {@linkplain GroupError.NULL_GROUP_PROPERTY_ID } if the - * group property id is null
  • - *
  • {@linkplain GroupError.UNKNOWN_GROUP_PROPERTY_ID } if the - * group property id is unknown
  • - *
  • {@linkplain PropertyError.IMMUTABLE_VALUE } if the - * corresponding property definition defines the property as - * immutable
  • - *
  • {@linkplain GroupError.NULL_GROUP_PROPERTY_VALUE } if the - * property value is null
  • - *
  • {@linkplain PropertyError.INCOMPATIBLE_VALUE } if - * property value is incompatible with the corresponding - * property definition
  • - * - */ - public void setGroupPropertyValue(final GroupId groupId, final GroupPropertyId groupPropertyId, final Object groupPropertyValue) { - validateGroupExists(groupId); - final GroupTypeId groupTypeId = indexesToTypesMap.get(groupsToTypesMap.getValueAsInt(groupId.getValue())); - validateGroupPropertyId(groupTypeId, groupPropertyId); - final PropertyDefinition propertyDefinition = groupPropertyDefinitions.get(groupTypeId).get(groupPropertyId); - validatePropertyMutability(propertyDefinition); - validateGroupPropertyValueNotNull(groupPropertyValue); - validateValueCompatibility(groupPropertyId, propertyDefinition, groupPropertyValue); - final Map map = groupPropertyManagerMap.get(groupTypeId); - final IndexedPropertyManager indexedPropertyManager = map.get(groupPropertyId); - Object oldValue = indexedPropertyManager.getPropertyValue(groupId.getValue()); - indexedPropertyManager.setPropertyValue(groupId.getValue(), groupPropertyValue); - dataManagerContext.releaseEvent(new GroupPropertyUpdateEvent(groupId, groupPropertyId, oldValue, groupPropertyValue)); - } - - private void validatePropertyMutability(final PropertyDefinition propertyDefinition) { - if (!propertyDefinition.propertyValuesAreMutable()) { - throw new ContractException(PropertyError.IMMUTABLE_VALUE); - } - } - - private void loadGroups() { - masterGroupId = -1; - for (final GroupId groupId : groupsPluginData.getGroupIds()) { - final GroupTypeId groupTypeId = groupsPluginData.getGroupTypeId(groupId); - final Integer typeIndex = typesToIndexesMap.get(groupTypeId); - List groups = typesToGroupsMap.getValue(typeIndex); - if (groups == null) { - groups = new ArrayList<>(); - typesToGroupsMap.setValue(typeIndex, groups); - } - groups.add(groupId); - masterGroupId = FastMath.max(masterGroupId, groupId.getValue()); - groupsToTypesMap.setIntValue(groupId.getValue(), typeIndex); - } - masterGroupId++; - } - - /** - * Adds groups with any group property initialization that is contained in - * the events's auxiliary data. Generates the corresponding - * {@linkplain GroupAdditionEvent} event. Returns the id of the first group - * added. - * - * - * @throws ContractException - * - *
  • {@linkplain GroupError#NULL_GROUP_CONSTRUCTION_INFO} if - * the group construction info is null
  • - * - *
  • {@link GroupError#NULL_GROUP_TYPE_ID} if the group type - * id contained in the group construction info is null
  • - * - *
  • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} if the - * group type id contained in the group construction info is - * unknown
  • - * - *
  • {@linkplain GroupError#UNKNOWN_GROUP_PROPERTY_ID} if a - * group property id contained in the group construction info is - * unknown
  • - * - *
  • {@linkplain PropertyError#INCOMPATIBLE_VALUE} if a group - * property value contained in the group construction info is - * incompatible with the corresponding property definition.
  • - * - * - */ - public GroupId addGroup(GroupConstructionInfo groupConstructionInfo) { - validateGroupConstructionInfoNotNull(groupConstructionInfo); - final GroupTypeId groupTypeId = groupConstructionInfo.getGroupTypeId(); - validateGroupTypeId(groupConstructionInfo.getGroupTypeId()); - - final Map propertyValues = groupConstructionInfo.getPropertyValues(); - for (final GroupPropertyId groupPropertyId : propertyValues.keySet()) { - validateGroupPropertyId(groupTypeId, groupPropertyId); - final PropertyDefinition propertyDefinition = groupPropertyDefinitions.get(groupTypeId).get(groupPropertyId); - final Object groupPropertyValue = propertyValues.get(groupPropertyId); - validateGroupPropertyValueNotNull(groupPropertyValue); - validateValueCompatibility(groupPropertyId, propertyDefinition, groupPropertyValue); - } - - final Integer typeIndex = typesToIndexesMap.get(groupTypeId); - List groups = typesToGroupsMap.getValue(typeIndex); - if (groups == null) { - groups = new ArrayList<>(); - typesToGroupsMap.setValue(typeIndex, groups); - } - final GroupId groupId = new GroupId(masterGroupId++); - - groups.add(groupId); - groupsToTypesMap.setIntValue(groupId.getValue(), typeIndex); - - for (final GroupPropertyId groupPropertyId : propertyValues.keySet()) { - final Object groupPropertyValue = propertyValues.get(groupPropertyId); - final Map map = groupPropertyManagerMap.get(groupTypeId); - final IndexedPropertyManager indexedPropertyManager = map.get(groupPropertyId); - indexedPropertyManager.setPropertyValue(groupId.getValue(), groupPropertyValue); - } - dataManagerContext.releaseEvent(new GroupAdditionEvent(groupId)); - return groupId; - } - - private void validateValueCompatibility(final Object propertyId, final PropertyDefinition propertyDefinition, final Object propertyValue) { - if (!propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())) { - throw new ContractException(PropertyError.INCOMPATIBLE_VALUE, - "Property value " + propertyValue + " is not of type " + propertyDefinition.getType().getName() + " and does not match definition of " + propertyId); - } - } - - private void validateGroupPropertyValueNotNull(final Object propertyValue) { - if (propertyValue == null) { - throw new ContractException(GroupError.NULL_GROUP_PROPERTY_VALUE); - } - } - - /* - * Validates the group type id - * - * @throws ContractException - * - *
  • {@link NucleusError#NULL_GROUP_CONSTRUCTION_INFO} if the group - * construction info is null - */ - private void validateGroupConstructionInfoNotNull(final GroupConstructionInfo groupConstructionInfo) { - if (groupConstructionInfo == null) { - throw new ContractException(GroupError.NULL_GROUP_CONSTRUCTION_INFO); - } - - } - - /** - * Adds a group. Generates the corresponding {@linkplain GroupAdditionEvent} - * event. Returns the id of the new group. - * - * @throws {@link - * ContractException} - * - *
  • {@link GroupError#NULL_GROUP_TYPE_ID} if the group type - * id is null
  • - * - *
  • {@link GroupError#UNKNOWN_GROUP_TYPE_ID} if the group - * type id is unknown
  • - * - */ - public GroupId addGroup(final GroupTypeId groupTypeId) { - validateGroupTypeId(groupTypeId); - - final Integer typeIndex = typesToIndexesMap.get(groupTypeId); - List groups = typesToGroupsMap.getValue(typeIndex); - if (groups == null) { - groups = new ArrayList<>(); - typesToGroupsMap.setValue(typeIndex, groups); - } - final GroupId result = new GroupId(masterGroupId++); - - groups.add(result); - groupsToTypesMap.setIntValue(result.getValue(), typeIndex); - - dataManagerContext.releaseEvent(new GroupAdditionEvent(result)); - - return result; - } - - /* - * Allocates the weights array to the given size or 50% larger than the - * current size, whichever is largest. Size must be non-negative - */ - private void allocateWeights(final int size) { - if (weights == null) { - weights = new double[size]; - weightedPersonIds = new PersonId[size]; - } - if (weights.length < size) { - final int newSize = Math.max(size, weights.length + (weights.length / 2)); - weights = new double[newSize]; - weightedPersonIds = new PersonId[newSize]; - } - } - - /* - * Attempts to acquire a lock on the sampling data structures. - * - * @throws ContractException
  • {@linkplain NucleusError#ACCESS_VIOLATION} - * If another sampling is ongoing
  • - */ - private void aquireSamplingLock() { - if (samplingIsLocked) { - throw new ContractException(NucleusError.ACCESS_VIOLATION, "cannot access weighted sampling during the execution of a previous weighted sampling"); - } - samplingIsLocked = true; - } - - /* - * Returns the index in the weights array that is the first to meet or - * exceed the target value. Assumes a strictly increasing set of values for - * indices 0 through peopleCount. Decreasing values are strictly prohibited. - * Consecutive equal values may return an ambiguous result. The target value - * must not exceed weights[peopleCount]. - * - */ - private int findTargetIndex(final double targetValue, final int peopleCount) { - int low = 0; - int high = peopleCount - 1; - - while (low <= high) { - final int mid = (low + high) >>> 1; - final double midVal = weights[mid]; - if (midVal < targetValue) { - low = mid + 1; - } else if (midVal > targetValue) { - high = mid - 1; - } else { - return mid; - } - } - return low; - } - - /** - * Returns the number of groups there are for a particular group type. - * - * @throws ContractException - *
  • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if the group - * type id is null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} if the - * group type id is unknown
  • - */ - public int getGroupCountForGroupType(final GroupTypeId groupTypeId) { - - validateGroupTypeId(groupTypeId); - - final Integer typeIndex = typesToIndexesMap.get(groupTypeId); - final List groups = typesToGroupsMap.getValue(typeIndex); - if (groups != null) { - return groups.size(); - } - return 0; - } - - private void validateGroupTypeId(final GroupTypeId groupTypeId) { - - if (groupTypeId == null) { - throw new ContractException(GroupError.NULL_GROUP_TYPE_ID); - } - - if (!typesToIndexesMap.keySet().contains(groupTypeId)) { - throw new ContractException(GroupError.UNKNOWN_GROUP_TYPE_ID, groupTypeId); - } - - } - - /** - * Returns the number of groups associated with the given person where each - * group has the given group type. - * - * @throws ContractException - * - *
  • {@linkplain PersonError#NULL_PERSON_ID} if the person id - * is null
  • - *
  • {@linkplain PersonError#UNKNOWN_PERSON_ID} if the person - * id is unknown
  • - *
  • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if the group - * type id is null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} if the - * group type id is unknown
  • - */ - public int getGroupCountForGroupTypeAndPerson(final GroupTypeId groupTypeId, final PersonId personId) { - validatePersonExists(personId); - validateGroupTypeId(groupTypeId); - - int result = 0; - final List groups = peopleToGroupsMap.getValue(personId.getValue()); - if (groups != null) { - for (final GroupId groupId : groups) { - final GroupTypeId groupType = getGroupType(groupId); - if (groupType.equals(groupTypeId)) { - result++; - } - } - } - return result; - } - - private void validatePersonExists(final PersonId personId) { - if (personId == null) { - throw new ContractException(PersonError.NULL_PERSON_ID); - } - if (!peopleDataManager.personExists(personId)) { - throw new ContractException(PersonError.UNKNOWN_PERSON_ID); - } - } - - /** - * Returns the number of groups associated with the given person. The person - * id must be non-null and non-negative. - * - * @throws ContractException - *
  • {@linkplain PersonError#NULL_PERSON_ID} if the person id - * is null
  • - *
  • {@linkplain PersonError#UNKNOWN_PERSON_ID} if the person - * id is unknown
  • - */ - public int getGroupCountForPerson(final PersonId personId) { - validatePersonExists(personId); - final List groups = peopleToGroupsMap.getValue(personId.getValue()); - if (groups != null) { - return groups.size(); - } - return 0; - } - - /** - * Returns the set of group ids as a list. - */ - public List getGroupIds() { - final List result = new ArrayList<>(); - for (final GroupTypeId groupTypeId : typesToIndexesMap.keySet()) { - result.addAll(getGroupsForGroupType(groupTypeId)); - } - return result; - } - - /** - * Returns the property definition for the given group type id and group - * property id - * - * @throws ContractException - *
  • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if the group - * type id is null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} if the - * group type id is unknown
  • - *
  • {@linkplain GroupError#NULL_GROUP_PROPERTY_ID} if the - * group property id is null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_PROPERTY_ID} if the - * group property id is unknown
  • - */ - public PropertyDefinition getGroupPropertyDefinition(GroupTypeId groupTypeId, GroupPropertyId groupPropertyId) { - validateGroupTypeId(groupTypeId); - validateGroupPropertyId(groupTypeId, groupPropertyId); - final Map map = groupPropertyDefinitions.get(groupTypeId); - return map.get(groupPropertyId); - } - - private void validateGroupPropertyId(final GroupTypeId groupTypeId, final GroupPropertyId groupPropertyId) { - if (groupPropertyId == null) { - throw new ContractException(GroupError.NULL_GROUP_PROPERTY_ID); - } - - final Map map = groupPropertyManagerMap.get(groupTypeId); - if (map == null || !map.containsKey(groupPropertyId)) { - throw new ContractException(GroupError.UNKNOWN_GROUP_PROPERTY_ID); - } - - } - - /** - * Returns true if and only if there is a property definition associated - * with the given group type id and group property id. Accepts all values. - */ - public boolean getGroupPropertyExists(final GroupTypeId groupTypeId, final GroupPropertyId groupPropertyId) { - final Map map = groupPropertyManagerMap.get(groupTypeId); - if (map == null) { - return false; - } - return map.containsKey(groupPropertyId); - } - - /** - * Returns the set of group property ids for the given group type id - * - * @throws ContractException - *
  • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if the group - * type id is null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} if the - * group type id is unknown
  • - * - */ - @SuppressWarnings("unchecked") - public Set getGroupPropertyIds(GroupTypeId groupTypeId) { - validateGroupTypeId(groupTypeId); - final Map map = groupPropertyDefinitions.get(groupTypeId); - final Set result = new LinkedHashSet<>(map.keySet().size()); - for (final GroupPropertyId groupPropertyId : map.keySet()) { - result.add((T) groupPropertyId); - } - return result; - } - - /** - * Returns the value of the group property. - * - * @throws ContractException - *
  • {@linkplain GroupError#NULL_GROUP_ID} if the group id is - * null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_ID} if the group id - * is unknown
  • - *
  • {@linkplain GroupError#NULL_GROUP_PROPERTY_ID} if the - * group property id is null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_PROPERTY_ID} if the - * group property id is unknown
  • - * - */ - public double getGroupPropertyTime(final GroupId groupId, GroupPropertyId groupPropertyId) { - validateGroupExists(groupId); - final GroupTypeId groupTypeId = getGroupType(groupId); - validateGroupPropertyId(groupTypeId, groupPropertyId); - final Map map = groupPropertyManagerMap.get(groupTypeId); - final IndexedPropertyManager indexedPropertyManager = map.get(groupPropertyId); - return indexedPropertyManager.getPropertyTime(groupId.getValue()); - } - - private void validateGroupExists(final GroupId groupId) { - if (groupId == null) { - throw new ContractException(GroupError.NULL_GROUP_ID); - } - - if (groupId.getValue() < 0 || groupsToTypesMap.getValueAsLong(groupId.getValue()) < 0) { - throw new ContractException(GroupError.UNKNOWN_GROUP_ID); - } - } - - /** - * Returns the value of the group property. - * - * @throws ContractException - *
  • {@linkplain GroupError#NULL_GROUP_ID} if the group id is - * null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_ID} if the group id - * is unknown
  • - *
  • {@linkplain GroupError#NULL_GROUP_PROPERTY_ID} if the - * group property id is null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_PROPERTY_ID} if the - * group property id is unknown
  • - * - */ - - public T getGroupPropertyValue(final GroupId groupId, GroupPropertyId groupPropertyId) { - validateGroupExists(groupId); - final GroupTypeId groupTypeId = getGroupType(groupId); - validateGroupPropertyId(groupTypeId, groupPropertyId); - final Map map = groupPropertyManagerMap.get(groupTypeId); - final IndexedPropertyManager indexedPropertyManager = map.get(groupPropertyId); - return indexedPropertyManager.getPropertyValue(groupId.getValue()); - } - - /** - * Returns the set groupIds associated with the given group type id as a - * list - * - * @throws ContractException - *
  • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if the group - * type id is null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} if the - * group type id is unknown
  • - * - */ - public List getGroupsForGroupType(final GroupTypeId groupTypeId) { - validateGroupTypeId(groupTypeId); - final Integer typeIndex = typesToIndexesMap.get(groupTypeId); - final List groups = typesToGroupsMap.getValue(typeIndex); - if (groups != null) { - return new ArrayList<>(groups); - } - return new ArrayList<>(); - } - - /** - * Returns the set of group ids associated with the given person and group - * type as a list. - * - * @throws ContractException - *
  • {@linkplain PersonError#NULL_PERSON_ID} if the person id - * is null
  • - *
  • {@linkplain PersonError#UNKNOWN_PERSON_ID} if the person - * id is unknown
  • - *
  • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if the group - * type id is null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} if the - * group type id is unknown
  • - * - */ - public List getGroupsForGroupTypeAndPerson(final GroupTypeId groupTypeId, final PersonId personId) { - validatePersonExists(personId); - validateGroupTypeId(groupTypeId); - final List result = new ArrayList<>(); - final List groups = peopleToGroupsMap.getValue(personId.getValue()); - if (groups != null) { - for (final GroupId groupId : groups) { - if (getGroupType(groupId).equals(groupTypeId)) { - result.add(groupId); - } - } - } - return result; - } - - /** - * Returns the set group ids associated the the given person as a list. - * - * @throws ContractException - *
  • {@linkplain PersonError#NULL_PERSON_ID} if the person id - * is null
  • - *
  • {@linkplain PersonError#UNKNOWN_PERSON_ID} if the person - * id is unknown
  • - * - */ - public List getGroupsForPerson(final PersonId personId) { - validatePersonExists(personId); - final List groups = peopleToGroupsMap.getValue(personId.getValue()); - if (groups != null) { - return new ArrayList<>(groups); - } - return new ArrayList<>(); - } - - /** - * Returns the group type for the given group. - * - * @throws ContractException - *
  • {@linkplain GroupError#NULL_GROUP_ID} if the group id is - * null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_ID} if the group id - * is unknown
  • - */ - @SuppressWarnings("unchecked") - public T getGroupType(final GroupId groupId) { - validateGroupExists(groupId); - return (T) indexesToTypesMap.get(groupsToTypesMap.getValueAsInt(groupId.getValue())); - } - - /** - * Return the number of group types associated with the person via their - * group memberships. - * - * @throws ContractException - *
  • {@linkplain PersonError#NULL_PERSON_ID} if the person id - * is null
  • - *
  • {@linkplain PersonError#UNKNOWN_PERSON_ID} if the person - * id is unknown
  • - */ - public int getGroupTypeCountForPersonId(final PersonId personId) { - validatePersonExists(personId); - final Set types = new LinkedHashSet<>(); - final List groups = peopleToGroupsMap.getValue(personId.getValue()); - if (groups != null) { - for (final GroupId groupId : groups) { - types.add(getGroupType(groupId)); - } - } - return types.size(); - } - - /** - * Returns the group type ids - */ - @SuppressWarnings("unchecked") - public Set getGroupTypeIds() { - final Set result = new LinkedHashSet<>(typesToIndexesMap.keySet().size()); - for (final GroupTypeId groupTypeId : typesToIndexesMap.keySet()) { - result.add((T) groupTypeId); - } - return result; - } - - /** - * Returns the set group types associated with the person's groups as a - * list. - * - * @throws ContractException - *
  • {@linkplain PersonError#NULL_PERSON_ID} if the person id - * is null
  • - *
  • {@linkplain PersonError#UNKNOWN_PERSON_ID} if the person - * id is unknown
  • - */ - public List getGroupTypesForPerson(final PersonId personId) { - validatePersonExists(personId); - final Set types = new LinkedHashSet<>(); - final List groups = peopleToGroupsMap.getValue(personId.getValue()); - if (groups != null) { - for (final GroupId groupId : groups) { - types.add(getGroupType(groupId)); - } - } - return new ArrayList<>(types); - } - - private IndexedPropertyManager getIndexedPropertyManager(final SimulationContext simulationContext, final PropertyDefinition propertyDefinition, final int intialSize) { - - IndexedPropertyManager indexedPropertyManager; - if (propertyDefinition.getType() == Boolean.class) { - indexedPropertyManager = new BooleanPropertyManager(simulationContext, propertyDefinition, intialSize); - } else if (propertyDefinition.getType() == Float.class) { - indexedPropertyManager = new FloatPropertyManager(simulationContext, propertyDefinition, intialSize); - } else if (propertyDefinition.getType() == Double.class) { - indexedPropertyManager = new DoublePropertyManager(simulationContext, propertyDefinition, intialSize); - } else if (propertyDefinition.getType() == Byte.class) { - indexedPropertyManager = new IntPropertyManager(simulationContext, propertyDefinition, intialSize); - } else if (propertyDefinition.getType() == Short.class) { - indexedPropertyManager = new IntPropertyManager(simulationContext, propertyDefinition, intialSize); - } else if (propertyDefinition.getType() == Integer.class) { - indexedPropertyManager = new IntPropertyManager(simulationContext, propertyDefinition, intialSize); - } else if (propertyDefinition.getType() == Long.class) { - indexedPropertyManager = new IntPropertyManager(simulationContext, propertyDefinition, intialSize); - } else if (Enum.class.isAssignableFrom(propertyDefinition.getType())) { - indexedPropertyManager = new EnumPropertyManager(simulationContext, propertyDefinition, intialSize); - } else { - indexedPropertyManager = new ObjectPropertyManager(simulationContext, propertyDefinition, intialSize); - } - return indexedPropertyManager; - } - - /** - * Returns the set of people who are in the given group as a list. - * - * @throws ContractException - *
  • {@linkplain GroupError#NULL_GROUP_ID} if the group id is - * null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_ID} if the group id - * is unknown
  • - */ - public List getPeopleForGroup(final GroupId groupId) { - validateGroupExists(groupId); - final List people = groupsToPeopleMap.getValue(groupId.getValue()); - if (people != null) { - return new ArrayList<>(people); - } - return new ArrayList<>(); - } - - /** - * Returns a list of unique person ids for the given group type(i.e. all - * people in groups having that type). Group type id must be valid. - * - * - * @throws ContractException - *
  • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if the group - * type id is null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} if the - * group type id is unknown
  • - */ - public List getPeopleForGroupType(final GroupTypeId groupTypeId) { - validateGroupTypeId(groupTypeId); - final Set allPeople = new LinkedHashSet<>(); - final Integer typeIndex = typesToIndexesMap.get(groupTypeId); - final List groups = typesToGroupsMap.getValue(typeIndex); - if (groups != null) { - for (final GroupId groupId : groups) { - final List people = groupsToPeopleMap.getValue(groupId.getValue()); - if (people != null) { - allPeople.addAll(people); - } - } - } - return new ArrayList<>(allPeople); - } - - /** - * Returns the number of people in the given group. - * - * @throws ContractException - *
  • {@linkplain GroupError#NULL_GROUP_ID} if the group id is - * null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_ID} if the group id - * is unknown
  • - */ - public int getPersonCountForGroup(final GroupId groupId) { - validateGroupExists(groupId); - final List people = groupsToPeopleMap.getValue(groupId.getValue()); - if (people != null) { - return people.size(); - } - return 0; - } - - /** - * Returns the number of people who are associated with groups having the - * given group type. - * - * @throws ContractException - *
  • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if the group - * type id is null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} if the - * group type id is unknown
  • - */ - public int getPersonCountForGroupType(final GroupTypeId groupTypeId) { - validateGroupTypeId(groupTypeId); - final Set allPeople = new LinkedHashSet<>(); - final Integer typeIndex = typesToIndexesMap.get(groupTypeId); - final List groups = typesToGroupsMap.getValue(typeIndex); - if (groups != null) { - for (final GroupId groupId : groups) { - final List people = groupsToPeopleMap.getValue(groupId.getValue()); - if (people != null) { - allPeople.addAll(people); - } - } - } - return allPeople.size(); - } - - /** - * Returns true if and only if the group exists. Null tolerant. - */ - public boolean groupExists(final GroupId groupId) { - if ((groupId == null) || (groupId.getValue() < 0)) { - return false; - } - return groupsToTypesMap.getValueAsLong(groupId.getValue()) >= 0; - } - - /** - * Returns true if and only if the group type exists. Null tolerant. - */ - public boolean groupTypeIdExists(final GroupTypeId groupTypeId) { - return typesToIndexesMap.keySet().contains(groupTypeId); - } - - /** - * Returns true if and only if the person is in the group. - * - * @throws ContractException - *
  • {@linkplain GroupError#NULL_GROUP_ID} if the group id is - * null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_ID} if the group id - * is unknown
  • - *
  • {@linkplain PersonError#NULL_PERSON_ID} if the person id - * is null
  • - *
  • {@linkplain PersonError#UNKNOWN_PERSON_ID} if the person - * id is unknown
  • - */ - public boolean isPersonInGroup(final PersonId personId, final GroupId groupId) { - validatePersonExists(personId); - validateGroupExists(groupId); - final List groups = peopleToGroupsMap.getValue(personId.getValue()); - if (groups != null) { - return groups.contains(groupId); - } - return false; - } - - private void releaseSamplingLock() { - if (!samplingIsLocked) { - throw new RuntimeException("cannot release sample locking when lock not present"); - } - samplingIsLocked = false; - } - - /** - * Removes the group. Generates the corresponding - * {@linkplain GroupImminentRemovalEvent} event. - * - * @throws ContractException - *
  • {@linkplain GroupError#NULL_GROUP_ID} if the group id is - * null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_ID} if the group id - * is unknown
  • - * - */ - public void removeGroup(final GroupId groupId) { - - validateGroupExists(groupId); - - dataManagerContext.addPlan((context) -> { - - final GroupTypeId groupTypeId = getGroupType(groupId); - - final Map map = groupPropertyManagerMap.get(groupTypeId); - for (final GroupPropertyId groupPropertyId : map.keySet()) { - final IndexedPropertyManager indexedPropertyManager = map.get(groupPropertyId); - indexedPropertyManager.removeId(groupId.getValue()); - } - - groupsToTypesMap.setIntValue(groupId.getValue(), -1); - final Integer typeIndex = typesToIndexesMap.get(groupTypeId); - List groups = typesToGroupsMap.getValue(typeIndex); - groups.remove(groupId); - if (groups.size() == 0) { - typesToGroupsMap.setValue(typeIndex, null); - } - final List people = groupsToPeopleMap.getValue(groupId.getValue()); - groupsToPeopleMap.setValue(groupId.getValue(), null); - if (people != null) { - for (final PersonId personId : people) { - groups = peopleToGroupsMap.getValue(personId.getValue()); - groups.remove(groupId); - } - } - - }, dataManagerContext.getTime()); - - dataManagerContext.releaseEvent(new GroupImminentRemovalEvent(groupId)); - } - - /** - * Removes the person from the group. Generates the corresponding - * {@linkplain GroupMembershipRemovalEvent} event. - * - * @throws ContractException - * - *
  • {@link PersonError#NULL_PERSON_ID} if the person id is - * null
  • - *
  • {@link PersonError#UNKNOWN_PERSON_ID} if the person id is - * unknown
  • - *
  • {@link GroupError#NULL_GROUP_ID} if the group id is - * null
  • - *
  • {@link GroupError#UNKNOWN_GROUP_ID} if the group id is - * unknown
  • - *
  • {@link GroupError#NON_GROUP_MEMBERSHIP} if the person is - * not a member of the group
  • - * - * - */ - public void removePersonFromGroup(final PersonId personId, final GroupId groupId) { - - validatePersonExists(personId); - validateGroupExists(groupId); - validatePersonInGroup(personId, groupId); - - final List people = groupsToPeopleMap.getValue(groupId.getValue()); - if (people != null) { - people.remove(personId); - if (people.size() == 0) { - groupsToPeopleMap.setValue(groupId.getValue(), null); - } - } - final List groups = peopleToGroupsMap.getValue(personId.getValue()); - if (groups != null) { - groups.remove(groupId); - } - - dataManagerContext.releaseEvent(new GroupMembershipRemovalEvent(personId, groupId)); - } - - /* - * Preconditions : the person and group exist - */ - private void validatePersonInGroup(final PersonId personId, final GroupId groupId) { - final List groups = peopleToGroupsMap.getValue(personId.getValue()); - if (groups == null || !groups.contains(groupId)) { - throw new ContractException(GroupError.NON_GROUP_MEMBERSHIP, "Person " + personId + " is not a member of group " + groupId); - } - } - - /* - * Precondition : group sampler info is not null - */ - private void validateGroupSampler(final GroupSampler groupSampler) { - if (groupSampler == null) { - throw new ContractException(GroupError.NULL_GROUP_SAMPLER); - } - if (groupSampler.getExcludedPerson().isPresent()) { - final PersonId excludedPersonId = groupSampler.getExcludedPerson().get(); - validatePersonExists(excludedPersonId); - } - } - - /** - * Returns a randomly selected person from the group using the group sampler - * to determine the probability for each person's selection. Returns an - * empty optional if no person meets the requirements of the group sampler. - * - * - * @throws ContractException - *
  • {@linkplain GroupError#NULL_GROUP_ID} if the group id is - * null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_ID} if the group id - * is unknown
  • - *
  • {@linkplain GroupError#NULL_GROUP_SAMPLER} if the group - * sampler is null
  • - *
  • {@linkplain PersonError#UNKNOWN_PERSON_ID} if the - * excluded person contained in the sampler is not null and is - * unknown
  • - *
  • {@linkplain StochasticsError#UNKNOWN_RANDOM_NUMBER_GENERATOR_ID} - * if the sampler contains an unknown random number generator - * id
  • - */ - public Optional sampleGroup(final GroupId groupId, final GroupSampler groupSampler) { - validateGroupExists(groupId); - validateGroupSampler(groupSampler); - - RandomGenerator randomGenerator; - if (groupSampler.getRandomNumberGeneratorId().isPresent()) { - final RandomNumberGeneratorId randomNumberGeneratorId = groupSampler.getRandomNumberGeneratorId().get(); - randomGenerator = stochasticsDataManager.getRandomGeneratorFromId(randomNumberGeneratorId); - } else { - randomGenerator = stochasticsDataManager.getRandomGenerator(); - } - final GroupWeightingFunction groupWeightingFunction = groupSampler.getWeightingFunction().orElse(null); - final PersonId excludedPersonId = groupSampler.getExcludedPerson().orElse(null); - - final boolean exclude = (excludedPersonId != null) && isPersonInGroup(excludedPersonId, groupId); - PersonId selectedPersonId = null; - final List people = groupsToPeopleMap.getValue(groupId.getValue()); - - if (groupWeightingFunction != null) { - - int candidateCount = people.size(); - if (exclude) { - candidateCount--; - } - if ((people != null) && (candidateCount > 0)) { - - aquireSamplingLock(); - try { - allocateWeights(people.size()); - /* - * Initialize the sum of the weights to zero and set the - * index in the weights and weightedPersonId to zero. - */ - double sum = 0; - int weightsLength = 0; - /* - * Collect a weight for each person in the group - */ - for (final PersonId personId : people) { - if (personId.equals(excludedPersonId)) { - continue; - } - /* - * Determine the weight of the person. Any weight that - * is negative , infinite or NAN is cause to return - * immediately since no person may be legitimately - * selected. - */ - final double weight = groupWeightingFunction.getWeight(dataManagerContext, personId, groupId); - if (!Double.isFinite(weight) || (weight < 0)) { - throw new ContractException(GroupError.MALFORMED_GROUP_SAMPLE_WEIGHTING_FUNCTION); - - } - /* - * People having a zero weight are rejected for - * selection - */ - if (weight > 0) { - sum += weight; - weights[weightsLength] = sum; - weightedPersonIds[weightsLength] = personId; - weightsLength++; - } - } - - /* - * If at least one person was accepted for selection, then - * we attempt a random selection. - */ - if (weightsLength > 0) { - /* - * Although the individual weights may have been finite, - * if the sum of those weights is not finite no - * legitimate selection can be made - */ - if (!Double.isFinite(sum)) { - throw new ContractException(GroupError.MALFORMED_GROUP_SAMPLE_WEIGHTING_FUNCTION); - } - - final double targetValue = randomGenerator.nextDouble() * sum; - final int targetIndex = findTargetIndex(targetValue, weightsLength); - selectedPersonId = weightedPersonIds[targetIndex]; - } - - } finally { - releaseSamplingLock(); - } - } - } else { - if (exclude) { - if ((people != null) && (people.size() > 1)) { - while (true) { - final int selectedIndex = randomGenerator.nextInt(people.size()); - final PersonId personId = people.get(selectedIndex); - if (!personId.equals(excludedPersonId)) { - selectedPersonId = personId; - break; - } - } - } - } else { - if ((people != null) && (people.size() > 0)) { - final int selectedIndex = randomGenerator.nextInt(people.size()); - selectedPersonId = people.get(selectedIndex); - } - } - } - return Optional.ofNullable(selectedPersonId); - - } - - private void validatePersonIndexExists(final int personIndex) { - if (!peopleDataManager.personIndexExists(personIndex)) { - throw new ContractException(PersonError.UNKNOWN_PERSON_ID); - } - } - - private void handleBulkPersonAdditionEvent(final DataManagerContext dataManagerContext, final BulkPersonAdditionEvent bulkPersonAdditionEvent) { - BulkPersonConstructionData bulkPersonConstructionData = bulkPersonAdditionEvent.getBulkPersonConstructionData(); - Optional optional = bulkPersonConstructionData.getValue(BulkGroupMembershipData.class); - if (optional.isPresent()) { - int personCount = bulkPersonConstructionData.getPersonConstructionDatas().size(); - int basePersonIndex = bulkPersonAdditionEvent.getPersonId().getValue(); - - for (int i = 0; i < personCount; i++) { - validatePersonIndexExists(i + basePersonIndex); - } - BulkGroupMembershipData bulkGroupMembershipData = optional.get(); - int groupCount = bulkGroupMembershipData.getGroupCount(); - for (int i = 0; i < groupCount; i++) { - GroupTypeId groupTypeId = bulkGroupMembershipData.getGroupTypeId(i); - validateGroupTypeId(groupTypeId); - } - for (Integer personIndex : bulkGroupMembershipData.getPersonIndices()) { - validatePersonIndexExists(personIndex + basePersonIndex); - } - - boolean groupCreationSubscribersExist = dataManagerContext.subscribersExist(GroupAdditionEvent.class); - - List newGroups = new ArrayList<>(); - - for (int i = 0; i < groupCount; i++) { - GroupTypeId groupTypeId = bulkGroupMembershipData.getGroupTypeId(i); - - final Integer typeIndex = typesToIndexesMap.get(groupTypeId); - List groups = typesToGroupsMap.getValue(typeIndex); - if (groups == null) { - groups = new ArrayList<>(); - typesToGroupsMap.setValue(typeIndex, groups); - } - GroupId groupId = new GroupId(masterGroupId++); - - groups.add(groupId); - groupsToTypesMap.setIntValue(groupId.getValue(), typeIndex); - - newGroups.add(groupId); - - for (GroupPropertyId groupPropertyId : bulkGroupMembershipData.getGroupPropertyIds(i)) { - Object groupPropertyValue = bulkGroupMembershipData.getGroupPropertyValue(i, groupPropertyId).get(); - validateGroupPropertyId(groupTypeId, groupPropertyId); - final PropertyDefinition propertyDefinition = groupPropertyDefinitions.get(groupTypeId).get(groupPropertyId); - validateGroupPropertyValueNotNull(groupPropertyValue); - validateValueCompatibility(groupPropertyId, propertyDefinition, groupPropertyValue); - final Map map = groupPropertyManagerMap.get(groupTypeId); - final IndexedPropertyManager indexedPropertyManager = map.get(groupPropertyId); - indexedPropertyManager.setPropertyValue(groupId.getValue(), groupPropertyValue); - } - - if (groupCreationSubscribersExist) { - dataManagerContext.releaseEvent(new GroupAdditionEvent(groupId)); - } - } - - for (Integer personIndex : bulkGroupMembershipData.getPersonIndices()) { - PersonId boxedPersonId = peopleDataManager.getBoxedPersonId(personIndex + basePersonIndex).get(); - List groupIndices = bulkGroupMembershipData.getGroupIndicesForPersonIndex(personIndex); - for (Integer groupIndex : groupIndices) { - GroupId groupId = newGroups.get(groupIndex); - validatePersonNotInGroup(boxedPersonId, groupId); - - List people = groupsToPeopleMap.getValue(groupId.getValue()); - if (people == null) { - people = new ArrayList<>(); - groupsToPeopleMap.setValue(groupId.getValue(), people); - } - people.add(boxedPersonId); - - List groups = peopleToGroupsMap.getValue(boxedPersonId.getValue()); - if (groups == null) { - groups = new ArrayList<>(1); - peopleToGroupsMap.setValue(boxedPersonId.getValue(), groups); - } - groups.add(groupId); - } - } - - } - } - - private void handlePersonImminentRemovalEvent(final DataManagerContext dataManagerContext, PersonImminentRemovalEvent personImminentRemovalEvent) { - validatePersonExists(personImminentRemovalEvent.getPersonId()); - dataManagerContext.addPlan((context) -> { - removePerson(personImminentRemovalEvent.getPersonId()); - }, dataManagerContext.getTime()); - } - - /** - * Removes the person from all group tracking. - * - */ - private void removePerson(final PersonId personId) { - final List groups = peopleToGroupsMap.getValue(personId.getValue()); - peopleToGroupsMap.setValue(personId.getValue(), null); - if (groups != null) { - for (final GroupId groupId : groups) { - final List people = groupsToPeopleMap.getValue(groupId.getValue()); - if (people != null) { - people.remove(personId); - if (people.size() == 0) { - groupsToPeopleMap.setValue(groupId.getValue(), null); - } - } - } - } - } -} diff --git a/gcm3/src/main/java/plugins/groups/events/GroupAdditionEvent.java b/gcm3/src/main/java/plugins/groups/events/GroupAdditionEvent.java deleted file mode 100644 index 338f85b39..000000000 --- a/gcm3/src/main/java/plugins/groups/events/GroupAdditionEvent.java +++ /dev/null @@ -1,93 +0,0 @@ -package plugins.groups.events; - -import net.jcip.annotations.Immutable; -import nucleus.Event; -import nucleus.EventLabel; -import nucleus.EventLabeler; -import nucleus.EventLabelerId; -import nucleus.SimulationContext; -import plugins.groups.datamanagers.GroupsDataManager; -import plugins.groups.support.GroupError; -import plugins.groups.support.GroupId; -import plugins.groups.support.GroupTypeId; -import util.errors.ContractException; - -/** - * An event indicating that a group has been created - * - * @author Shawn Hatch - * - */ -@Immutable -public class GroupAdditionEvent implements Event { - - private final GroupId groupId; - - /** - * Constructs this event from the group id - * - */ - public GroupAdditionEvent(final GroupId groupId) { - this.groupId = groupId; - } - - /** - * Returns the group id used to create this event - */ - public GroupId getGroupId() { - return groupId; - } - - private static enum LabelerId implements EventLabelerId { - TYPE - } - - /** - * Returns an event label used to subscribe to {@link GroupAdditionEvent} - * events. Matches on group type id. - * - * Preconditions : The context cannot be null - * - * @throws ContractException - * - *
  • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if the group - * type id is null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} if the - * group type id is not known
  • - * - */ - public static EventLabel getEventLabelByGroupType(SimulationContext simulationContext, GroupTypeId groupTypeId) { - if (groupTypeId == null) { - throw new ContractException(GroupError.NULL_GROUP_TYPE_ID); - } - GroupsDataManager groupsDataManager = simulationContext.getDataManager(GroupsDataManager.class); - if (!groupsDataManager.groupTypeIdExists(groupTypeId)) { - throw new ContractException(GroupError.UNKNOWN_GROUP_TYPE_ID); - } - return _getEventLabelByGroupType(groupTypeId); - } - - private static EventLabel _getEventLabelByGroupType(GroupTypeId groupTypeId) { - - return EventLabel .builder(GroupAdditionEvent.class)// - .setEventLabelerId(LabelerId.TYPE)// - .addKey(GroupAdditionEvent.class)// - .addKey(groupTypeId)// - .build();// - } - - /** - * Returns an event labeler for {@link GroupAdditionEvent} events that uses - * group type id. Automatically added at initialization. - */ - public static EventLabeler getEventLabelerForGroupType(GroupsDataManager groupsDataManager) { - return EventLabeler .builder(GroupAdditionEvent.class)// - .setEventLabelerId(LabelerId.TYPE)// - .setLabelFunction((context, event) -> { - GroupTypeId groupTypeId = groupsDataManager.getGroupType(event.getGroupId()); - return _getEventLabelByGroupType(groupTypeId); - })// - .build(); - } - -} diff --git a/gcm3/src/main/java/plugins/groups/events/GroupImminentRemovalEvent.java b/gcm3/src/main/java/plugins/groups/events/GroupImminentRemovalEvent.java deleted file mode 100644 index 76602aba5..000000000 --- a/gcm3/src/main/java/plugins/groups/events/GroupImminentRemovalEvent.java +++ /dev/null @@ -1,148 +0,0 @@ -package plugins.groups.events; - -import net.jcip.annotations.Immutable; -import nucleus.Event; -import nucleus.EventLabel; -import nucleus.EventLabeler; -import nucleus.EventLabelerId; -import nucleus.SimulationContext; -import plugins.groups.datamanagers.GroupsDataManager; -import plugins.groups.support.GroupError; -import plugins.groups.support.GroupId; -import plugins.groups.support.GroupTypeId; -import util.errors.ContractException; - -/** - * Event to signal the imminent removal of a group from the simulation - * - * @author Shawn Hatch - * - */ - -@Immutable -public class GroupImminentRemovalEvent implements Event { - private final GroupId groupId; - - /** - * Constructs this event from the group id - * - */ - public GroupImminentRemovalEvent(final GroupId groupId) { - super(); - this.groupId = groupId; - } - - /** - * Returns the group id used to create this event - */ - public GroupId getGroupId() { - return groupId; - } - - private static void validateGroupId(SimulationContext simulationContext, GroupId groupId) { - if (groupId == null) { - throw new ContractException(GroupError.NULL_GROUP_ID); - } - GroupsDataManager groupsDataManager = simulationContext.getDataManager(GroupsDataManager.class); - if (!groupsDataManager.groupExists(groupId)) { - throw new ContractException(GroupError.UNKNOWN_GROUP_ID, groupId); - } - } - - private static void validateGroupTypeId(SimulationContext simulationContext, GroupTypeId groupTypeId) { - if (groupTypeId == null) { - throw new ContractException(GroupError.NULL_GROUP_TYPE_ID); - } - GroupsDataManager groupsDataManager = simulationContext.getDataManager(GroupsDataManager.class); - if (!groupsDataManager.groupTypeIdExists(groupTypeId)) { - throw new ContractException(GroupError.UNKNOWN_GROUP_TYPE_ID, groupTypeId); - } - } - - private static enum LabelerId implements EventLabelerId { - GROUP, GROUPTYPE - } - - /** - * Returns an event label used to subscribe to - * {@link GroupImminentRemovalEvent} events. Matches on group id. - * - * Preconditions : The context cannot be null - * - * @throws ContractException - * - *
  • {@linkplain GroupError#NULL_GROUP_ID} if the group id is - * null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_ID} if the group id - * is not known
  • - * - */ - public static EventLabel getEventLabelByGroup(SimulationContext simulationContext, GroupId groupId) { - validateGroupId(simulationContext, groupId); - return _getEventLabelByGroup(groupId); - } - - private static EventLabel _getEventLabelByGroup(GroupId groupId) { - - return EventLabel .builder(GroupImminentRemovalEvent.class)// - .setEventLabelerId(LabelerId.GROUP)// - .addKey(GroupImminentRemovalEvent.class)// - .addKey(groupId)// - .build(); - } - - /** - * Returns an event labeler for {@link GroupImminentRemovalEvent} events - * that uses group id. Automatically added at initialization. - */ - public static EventLabeler getEventLabelerForGroup() { - return EventLabeler .builder(GroupImminentRemovalEvent.class)// - .setEventLabelerId(LabelerId.GROUP).setLabelFunction((context, event) -> _getEventLabelByGroup(event.getGroupId()))// - .build(); - } - - /** - * Returns an event label used to subscribe to - * {@link GroupImminentRemovalEvent} events. Matches on group type id. - * - * Preconditions : The context cannot be null - * - * @throws ContractException - * - *
  • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if the group - * type id is null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} if the - * group type id is not known
  • - * - */ - public static EventLabel getEventLabelByGroupType(SimulationContext simulationContext, GroupTypeId groupTypeId) { - validateGroupTypeId(simulationContext, groupTypeId); - return _getEventLabelByGroupType(groupTypeId);// - } - - - private static EventLabel _getEventLabelByGroupType(GroupTypeId groupTypeId) { - - return EventLabel .builder(GroupImminentRemovalEvent.class)// - .setEventLabelerId(LabelerId.GROUPTYPE)// - .addKey(GroupImminentRemovalEvent.class)// - .addKey(groupTypeId)// - .build();// - } - - /** - * Returns an event labeler for {@link GroupImminentRemovalEvent} events - * that uses group type id. Automatically added at initialization. - */ - public static EventLabeler getEventLabelerForGroupType(GroupsDataManager groupsDataManager) { - return EventLabeler .builder(GroupImminentRemovalEvent.class).setEventLabelerId(LabelerId.GROUPTYPE)// - .setLabelFunction((context, event) -> { - GroupTypeId groupTypeId = groupsDataManager.getGroupType(event.getGroupId()); - return _getEventLabelByGroupType(groupTypeId); - })// - .build(); - } - - - -} diff --git a/gcm3/src/main/java/plugins/groups/events/GroupMembershipAdditionEvent.java b/gcm3/src/main/java/plugins/groups/events/GroupMembershipAdditionEvent.java deleted file mode 100644 index f499f410c..000000000 --- a/gcm3/src/main/java/plugins/groups/events/GroupMembershipAdditionEvent.java +++ /dev/null @@ -1,307 +0,0 @@ -package plugins.groups.events; - -import net.jcip.annotations.Immutable; -import nucleus.Event; -import nucleus.EventLabel; -import nucleus.EventLabeler; -import nucleus.EventLabelerId; -import nucleus.SimulationContext; -import plugins.groups.datamanagers.GroupsDataManager; -import plugins.groups.support.GroupError; -import plugins.groups.support.GroupId; -import plugins.groups.support.GroupTypeId; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import util.errors.ContractException; - -/** - * Event to indicating that person was added to a group - * - * @author Shawn Hatch - * - */ - -@Immutable -public class GroupMembershipAdditionEvent implements Event { - private final PersonId personId; - private final GroupId groupId; - - /** - * Constructs this event from the given person id and group id - * - */ - public GroupMembershipAdditionEvent(final PersonId personId, final GroupId groupId) { - super(); - this.personId = personId; - this.groupId = groupId; - } - - /** - * Returns the group id used to create this event - */ - public GroupId getGroupId() { - return groupId; - } - - /** - * Returns the person id used to create this event - */ - public PersonId getPersonId() { - return personId; - } - - private static enum LabelerId implements EventLabelerId { - GROUP_PERSON, GROUP, PERSON, TYPE_PERSON, TYPE - } - - private static void validateGroupId(SimulationContext simulationContext, GroupId groupId) { - if (groupId == null) { - throw new ContractException(GroupError.NULL_GROUP_ID); - } - GroupsDataManager groupsDataManager = simulationContext.getDataManager(GroupsDataManager.class); - - if (!groupsDataManager.groupExists(groupId)) { - throw new ContractException(GroupError.UNKNOWN_GROUP_ID, groupId); - } - } - - private static void validatePersonId(SimulationContext simulationContext, PersonId personId) { - if (personId == null) { - throw new ContractException(PersonError.NULL_PERSON_ID); - } - PeopleDataManager peopleDataManager = simulationContext.getDataManager(PeopleDataManager.class); - if (!peopleDataManager.personExists(personId)) { - throw new ContractException(PersonError.UNKNOWN_PERSON_ID); - } - } - - private static void validateGroupTypeId(SimulationContext simulationContext, GroupTypeId groupTypeId) { - if (groupTypeId == null) { - throw new ContractException(GroupError.NULL_GROUP_TYPE_ID); - } - GroupsDataManager groupsDataManager = simulationContext.getDataManager(GroupsDataManager.class); - if (!groupsDataManager.groupTypeIdExists(groupTypeId)) { - throw new ContractException(GroupError.UNKNOWN_GROUP_TYPE_ID); - } - } - - /** - * Returns an event label used to subscribe to - * {@link GroupMembershipAdditionEvent} events. Matches on group id and - * person id. - * - * Preconditions : The context cannot be null - * - * @throws ContractException - * - *
  • {@linkplain GroupError#NULL_GROUP_ID} if the group id is - * null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_ID} if the group id - * is not known
  • - *
  • {@linkplain PersonError#NULL_PERSON_ID} if the person id - * is null
  • - *
  • {@linkplain PersonError#UNKNOWN_PERSON_ID} if the person - * id is not known
  • - * - */ - public static EventLabel getEventLabelByGroupAndPerson(SimulationContext simulationContext, GroupId groupId, PersonId personId) { - validateGroupId(simulationContext, groupId); - validatePersonId(simulationContext, personId); - return _getEventLabelByGroupAndPerson(groupId, personId);// - } - - private static EventLabel _getEventLabelByGroupAndPerson(GroupId groupId, PersonId personId) { - - return EventLabel .builder(GroupMembershipAdditionEvent.class)// - .setEventLabelerId(LabelerId.GROUP_PERSON)// - .addKey(GroupMembershipAdditionEvent.class)// - .addKey(groupId)// - .addKey(personId)// - .build();// - } - - /** - * Returns an event labeler for {@link GroupMembershipAdditionEvent} events - * that uses group id and person id. Automatically added at initialization. - */ - public static EventLabeler getEventLabelerForGroupAndPerson() { - return EventLabeler .builder(GroupMembershipAdditionEvent.class)// - .setEventLabelerId(LabelerId.GROUP_PERSON)// - .setLabelFunction((context, event) -> _getEventLabelByGroupAndPerson(event.getGroupId(), event.getPersonId()))// - .build();// - } - - /** - * Returns an event label used to subscribe to - * {@link GroupMembershipAdditionEvent} events. Matches on group id. - * - * Preconditions : The context cannot be null - * - * @throws ContractException - * - *
  • {@linkplain GroupError#NULL_GROUP_ID} if the group id is - * null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_ID} if the group id - * is not known
  • - * - */ - public static EventLabel getEventLabelByGroup(SimulationContext simulationContext, GroupId groupId) { - validateGroupId(simulationContext, groupId); - return _getEventLabelByGroup(groupId);// - } - - - private static EventLabel _getEventLabelByGroup(GroupId groupId) { - - return EventLabel .builder(GroupMembershipAdditionEvent.class)// - .setEventLabelerId(LabelerId.GROUP)// - .addKey(GroupMembershipAdditionEvent.class)// - .addKey(groupId)// - .build();// - } - - /** - * Returns an event labeler for {@link GroupMembershipAdditionEvent} events - * that uses group id. Automatically added at initialization. - */ - public static EventLabeler getEventLabelerForGroup() { - return EventLabeler .builder(GroupMembershipAdditionEvent.class).setEventLabelerId(LabelerId.GROUP)// - .setLabelFunction((context, event) -> _getEventLabelByGroup(event.getGroupId()))// - .build(); - } - - /** - * Returns an event label used to subscribe to - * {@link GroupMembershipAdditionEvent} events. Matches on person id. - * - * Preconditions : The context cannot be null - * - * @throws ContractException - * - *
  • {@linkplain PersonError#NULL_PERSON_ID} if the person id - * is null
  • - *
  • {@linkplain PersonError#UNKNOWN_PERSON_ID} if the person - * id is not known
  • - * - */ - public static EventLabel getEventLabelByPerson(SimulationContext simulationContext, PersonId personId) { - validatePersonId(simulationContext, personId); - return _getEventLabelByPerson(personId); - - } - - private static EventLabel _getEventLabelByPerson(PersonId personId) { - - return EventLabel .builder(GroupMembershipAdditionEvent.class)// - .setEventLabelerId(LabelerId.PERSON)// - .addKey(GroupMembershipAdditionEvent.class)// - .addKey(personId)// - .build(); - - } - - /** - * Returns an event labeler for {@link GroupMembershipAdditionEvent} events - * that uses person id. Automatically added at initialization. - */ - public static EventLabeler getEventLabelerForPerson() { - return EventLabeler .builder(GroupMembershipAdditionEvent.class)// - .setEventLabelerId(LabelerId.PERSON)// - .setLabelFunction((context, event) -> _getEventLabelByPerson(event.getPersonId()))// - .build(); - } - - /** - * Returns an event label used to subscribe to - * {@link GroupMembershipAdditionEvent} events. Matches on person id and - * group type id. - * - * Preconditions : The context cannot be null - * - * @throws ContractException - * - *
  • {@linkplain PersonError#NULL_PERSON_ID} if the person id - * is null
  • - *
  • {@linkplain PersonError#UNKNOWN_PERSON_ID} if the person - * id is not known
  • - *
  • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if the group - * type id is null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} if the - * group type id is not known
  • - */ - public static EventLabel getEventLabelByGroupTypeAndPerson(SimulationContext simulationContext, GroupTypeId groupTypeId, PersonId personId) { - validateGroupTypeId(simulationContext, groupTypeId); - validatePersonId(simulationContext, personId); - return _getEventLabelByGroupTypeAndPerson(groupTypeId, personId);// - } - - private static EventLabel _getEventLabelByGroupTypeAndPerson(GroupTypeId groupTypeId, PersonId personId) { - - return EventLabel .builder(GroupMembershipAdditionEvent.class)// - .setEventLabelerId(LabelerId.TYPE_PERSON)// - .addKey(GroupMembershipAdditionEvent.class)// - .addKey(groupTypeId)// - .addKey(personId)// - .build();// - } - - /** - * Returns an event labeler for {@link GroupMembershipAdditionEvent} events - * that uses group type id and person id. Automatically added at - * initialization. - */ - public static EventLabeler getEventLabelerForGroupTypeAndPerson(GroupsDataManager groupsDataManager) { - return EventLabeler .builder(GroupMembershipAdditionEvent.class)// - .setEventLabelerId(LabelerId.TYPE_PERSON)// - .setLabelFunction((context, event) -> { - GroupId groupId = event.getGroupId(); - GroupTypeId groupTypeId = groupsDataManager.getGroupType(groupId); - return _getEventLabelByGroupTypeAndPerson(groupTypeId, event.getPersonId()); - })// - .build(); - } - - /** - * Returns an event label used to subscribe to - * {@link GroupMembershipAdditionEvent} events. Matches on group type id. - * - * Preconditions : The context cannot be null - * - * @throws ContractException - * - *
  • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if the group - * type id is null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} if the - * group type id is not known
  • - */ - public static EventLabel getEventLabelByGroupType(SimulationContext simulationContext, GroupTypeId groupTypeId) { - validateGroupTypeId(simulationContext, groupTypeId); - return _getEventLabelByGroupType(groupTypeId);// - } - - private static EventLabel _getEventLabelByGroupType(GroupTypeId groupTypeId) { - - return EventLabel .builder(GroupMembershipAdditionEvent.class)// - .setEventLabelerId(LabelerId.TYPE)// - .addKey(GroupMembershipAdditionEvent.class)// - .addKey(groupTypeId)// - .build();// - } - - /** - * Returns an event labeler for {@link GroupMembershipAdditionEvent} Matches - * on group type id. Automatically added at initialization. - */ - public static EventLabeler getEventLabelerForGroupType(GroupsDataManager groupsDataManager) { - return EventLabeler .builder(GroupMembershipAdditionEvent.class)// - .setEventLabelerId(LabelerId.TYPE)// - .setLabelFunction((context, event) -> { - GroupId groupId = event.getGroupId(); - GroupTypeId groupTypeId = groupsDataManager.getGroupType(groupId); - return _getEventLabelByGroupType(groupTypeId); - })// - .build(); - } - -} diff --git a/gcm3/src/main/java/plugins/groups/events/GroupMembershipRemovalEvent.java b/gcm3/src/main/java/plugins/groups/events/GroupMembershipRemovalEvent.java deleted file mode 100644 index d2b8132a1..000000000 --- a/gcm3/src/main/java/plugins/groups/events/GroupMembershipRemovalEvent.java +++ /dev/null @@ -1,305 +0,0 @@ -package plugins.groups.events; - -import net.jcip.annotations.Immutable; -import nucleus.Event; -import nucleus.EventLabel; -import nucleus.EventLabeler; -import nucleus.EventLabelerId; -import nucleus.SimulationContext; -import plugins.groups.datamanagers.GroupsDataManager; -import plugins.groups.support.GroupError; -import plugins.groups.support.GroupId; -import plugins.groups.support.GroupTypeId; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import util.errors.ContractException; - -/** - * Event to indicating that person was removed from a group - * - * @author Shawn Hatch - * - */ -@Immutable -public class GroupMembershipRemovalEvent implements Event { - private final PersonId personId; - private final GroupId groupId; - - /** - * Constructs this event from the given person id and group id - * - */ - public GroupMembershipRemovalEvent(final PersonId personId, final GroupId groupId) { - super(); - this.personId = personId; - this.groupId = groupId; - } - - /** - * Returns the group id used to create this event - */ - public GroupId getGroupId() { - return groupId; - } - - /** - * Returns the person id used to create this event - */ - public PersonId getPersonId() { - return personId; - } - - private static void validateGroupId(SimulationContext simulationContext, GroupId groupId) { - if (groupId == null) { - throw new ContractException(GroupError.NULL_GROUP_ID); - } - GroupsDataManager personGroupDataView = simulationContext.getDataManager(GroupsDataManager.class); - - if (!personGroupDataView.groupExists(groupId)) { - throw new ContractException(GroupError.UNKNOWN_GROUP_ID, groupId); - } - } - - private static void validatePersonId(SimulationContext simulationContext, PersonId personId) { - if (personId == null) { - throw new ContractException(PersonError.NULL_PERSON_ID); - } - PeopleDataManager peopleDataManager = simulationContext.getDataManager(PeopleDataManager.class); - if (!peopleDataManager.personExists(personId)) { - throw new ContractException(PersonError.UNKNOWN_PERSON_ID); - } - } - - private static void validateGroupTypeId(SimulationContext simulationContext, GroupTypeId groupTypeId) { - if (groupTypeId == null) { - throw new ContractException(GroupError.NULL_GROUP_TYPE_ID); - } - GroupsDataManager groupsDataManager = simulationContext.getDataManager(GroupsDataManager.class); - if (!groupsDataManager.groupTypeIdExists(groupTypeId)) { - throw new ContractException(GroupError.UNKNOWN_GROUP_TYPE_ID); - } - } - - /** - * Returns an event label used to subscribe to - * {@link GroupMembershipRemovalEvent} events. Matches on group id and - * person id. - * - * Preconditions : The context cannot be null - * - * @throws ContractException - * - *
  • {@linkplain GroupError#NULL_GROUP_ID} if the group id is - * null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_ID} if the group id - * is not known
  • - *
  • {@linkplain PersonError#NULL_PERSON_ID} if the person id - * is null
  • - *
  • {@linkplain PersonError#UNKNOWN_PERSON_ID} if the person - * id is not known
  • - * - */ - public static EventLabel getEventLabelByGroupAndPerson(SimulationContext simulationContext, GroupId groupId, PersonId personId) { - validateGroupId(simulationContext, groupId); - validatePersonId(simulationContext, personId); - return _getEventLabelByGroupAndPerson(groupId, personId);// - } - - private static EventLabel _getEventLabelByGroupAndPerson(GroupId groupId, PersonId personId) { - - return EventLabel .builder(GroupMembershipRemovalEvent.class)// - .setEventLabelerId(LabelerId.GROUP_PERSON)// - .addKey(GroupMembershipRemovalEvent.class)// - .addKey(groupId)// - .addKey(personId)// - .build();// - } - - - /** - * Returns an event labeler for {@link GroupMembershipRemovalEvent} events - * that uses group id and person id. Automatically added at initialization. - */ - public static EventLabeler getEventLabelerForGroupAndPerson() { - return EventLabeler .builder(GroupMembershipRemovalEvent.class)// - .setEventLabelerId(LabelerId.GROUP_PERSON)// - .setLabelFunction((context, event) -> _getEventLabelByGroupAndPerson(event.getGroupId(), event.getPersonId()))// - .build(); - } - - /** - * Returns an event label used to subscribe to - * {@link GroupMembershipRemovalEvent} events. Matches on group id. - * - * Preconditions : The context cannot be null - * - * @throws ContractException - * - *
  • {@linkplain GroupError#NULL_GROUP_ID} if the group id is - * null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_ID} if the group id - * is not known
  • - * - */ - public static EventLabel getEventLabelByGroup(SimulationContext simulationContext, GroupId groupId) { - validateGroupId(simulationContext, groupId); - return _getEventLabelByGroup(groupId);// - } - - private static EventLabel _getEventLabelByGroup(GroupId groupId) { - - return EventLabel .builder(GroupMembershipRemovalEvent.class)// - .setEventLabelerId(LabelerId.GROUP)// - .addKey(GroupMembershipRemovalEvent.class)// - .addKey(groupId)// - .build();// - } - - /** - * Returns an event labeler for {@link GroupMembershipRemovalEvent} events - * that uses group id. Automatically added at initialization. - */ - public static EventLabeler getEventLabelerForGroup() { - return EventLabeler .builder(GroupMembershipRemovalEvent.class)// - .setEventLabelerId(LabelerId.GROUP)// - .setLabelFunction((context, event) -> _getEventLabelByGroup(event.getGroupId()))// - .build(); - } - - /** - * Returns an event label used to subscribe to - * {@link GroupMembershipRemovalEvent} events. Matches on person id. - * - * Preconditions : The context cannot be null - * - * @throws ContractException - * - *
  • {@linkplain PersonError#NULL_PERSON_ID} if the person id - * is null
  • - *
  • {@linkplain PersonError#UNKNOWN_PERSON_ID} if the person - * id is not known
  • - * - */ - public static EventLabel getEventLabelByPerson(SimulationContext simulationContext, PersonId personId) { - validatePersonId(simulationContext, personId); - return _getEventLabelByPerson(personId); - } - - private static EventLabel _getEventLabelByPerson(PersonId personId) { - - return EventLabel .builder(GroupMembershipRemovalEvent.class)// - .setEventLabelerId(LabelerId.PERSON)// - .addKey(GroupMembershipRemovalEvent.class)// - .addKey(personId)// - .build(); - } - - /** - * Returns an event labeler for {@link GroupMembershipRemovalEvent} events - * that uses person id. Automatically added at initialization. - */ - public static EventLabeler getEventLabelerForPerson() { - - return EventLabeler .builder(GroupMembershipRemovalEvent.class)// - .setEventLabelerId(LabelerId.PERSON)// - .setLabelFunction((context, event) -> _getEventLabelByPerson(event.getPersonId()))// - .build(); - } - - /** - * Returns an event label used to subscribe to - * {@link GroupMembershipRemovalEvent} events. Matches on person id and - * group type id. - * - * Preconditions : The context cannot be null - * - * @throws ContractException - * - *
  • {@linkplain PersonError#NULL_PERSON_ID} if the person id - * is null
  • - *
  • {@linkplain PersonError#UNKNOWN_PERSON_ID} if the person - * id is not known
  • - *
  • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if the group - * type id is null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} if the - * group type id is not known
  • - */ - public static EventLabel getEventLabelByGroupTypeAndPerson(SimulationContext simulationContext, GroupTypeId groupTypeId, PersonId personId) { - validateGroupTypeId(simulationContext, groupTypeId); - validatePersonId(simulationContext, personId); - return _getEventLabelByGroupTypeAndPerson(groupTypeId, personId);// - } - - private static EventLabel _getEventLabelByGroupTypeAndPerson(GroupTypeId groupTypeId, PersonId personId) { - - return EventLabel .builder(GroupMembershipRemovalEvent.class).setEventLabelerId(LabelerId.TYPE_PERSON)// - .addKey(GroupMembershipRemovalEvent.class)// - .addKey(groupTypeId)// - .addKey(personId)// - .build();// - } - - /** - * Returns an event labeler for {@link GroupMembershipRemovalEvent} events - * that uses group type id and person id. Automatically added at - * initialization. - */ - public static EventLabeler getEventLabelerForGroupTypeAndPerson(GroupsDataManager groupsDataManager) { - return EventLabeler .builder(GroupMembershipRemovalEvent.class)// - .setEventLabelerId(LabelerId.TYPE_PERSON)// - .setLabelFunction((context, event) -> { - GroupId groupId = event.getGroupId(); - GroupTypeId groupTypeId = groupsDataManager.getGroupType(groupId); - return _getEventLabelByGroupTypeAndPerson(groupTypeId, event.getPersonId()); - })// - .build(); - - } - - private static enum LabelerId implements EventLabelerId { - GROUP_PERSON, GROUP, PERSON, TYPE_PERSON, TYPE - } - - /** - * Returns an event label used to subscribe to - * {@link GroupMembershipRemovalEvent} events. Matches on group type id. - * - * Preconditions : The context cannot be null - * - * @throws ContractException - * - *
  • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if the group - * type id is null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} if the - * group type id is not known
  • - */ - public static EventLabel getEventLabelByGroupType(SimulationContext simulationContext, GroupTypeId groupTypeId) { - validateGroupTypeId(simulationContext, groupTypeId); - return _getEventLabelByGroupType(groupTypeId);// - } - - private static EventLabel _getEventLabelByGroupType(GroupTypeId groupTypeId) { - - return EventLabel .builder(GroupMembershipRemovalEvent.class)// - .setEventLabelerId(LabelerId.TYPE)// - .addKey(GroupMembershipRemovalEvent.class)// - .addKey(groupTypeId)// - .build();// - } - - /** - * Returns an event labeler for {@link GroupMembershipRemovalEvent} Matches - * on group type id. Automatically added at initialization. - */ - public static EventLabeler getEventLabelerForGroupType(GroupsDataManager groupsDataManager) { - return EventLabeler .builder(GroupMembershipRemovalEvent.class).setEventLabelerId(LabelerId.TYPE)// - .setLabelFunction((context, event) -> { - GroupId groupId = event.getGroupId(); - GroupTypeId groupTypeId = groupsDataManager.getGroupType(groupId); - return _getEventLabelByGroupType(groupTypeId); - })// - .build(); - } - -} diff --git a/gcm3/src/main/java/plugins/groups/events/GroupPropertyUpdateEvent.java b/gcm3/src/main/java/plugins/groups/events/GroupPropertyUpdateEvent.java deleted file mode 100644 index 3952a4faf..000000000 --- a/gcm3/src/main/java/plugins/groups/events/GroupPropertyUpdateEvent.java +++ /dev/null @@ -1,296 +0,0 @@ -package plugins.groups.events; - -import net.jcip.annotations.Immutable; -import nucleus.Event; -import nucleus.EventLabel; -import nucleus.EventLabeler; -import nucleus.EventLabelerId; -import nucleus.SimulationContext; -import plugins.groups.datamanagers.GroupsDataManager; -import plugins.groups.support.GroupError; -import plugins.groups.support.GroupId; -import plugins.groups.support.GroupPropertyId; -import plugins.groups.support.GroupTypeId; -import util.errors.ContractException; - -/** - * Event to indicating that a group had a property value change - * - * @author Shawn Hatch - * - */ -@Immutable -public class GroupPropertyUpdateEvent implements Event { - private final GroupId groupId; - private final GroupPropertyId groupPropertyId; - private final Object previousPropertyValue; - private final Object currentPropertyValue; - - /** - * Constructs this event from the given group id, group property id , - * previous property value and current property value. - * - */ - public GroupPropertyUpdateEvent(final GroupId groupId, final GroupPropertyId groupPropertyId, final Object previousPropertyValue, final Object currentPropertyValue) { - super(); - this.groupId = groupId; - this.groupPropertyId = groupPropertyId; - this.previousPropertyValue = previousPropertyValue; - this.currentPropertyValue = currentPropertyValue; - } - - /** - * Returns the current property value id used to create this event - */ - public Object getCurrentPropertyValue() { - return currentPropertyValue; - } - - /** - * Returns the group id used to create this event - */ - public GroupId getGroupId() { - return groupId; - } - - /** - * Returns the group property id used to create this event - */ - public GroupPropertyId getGroupPropertyId() { - return groupPropertyId; - } - - /** - * Returns the previous property value id used to create this event - */ - public Object getPreviousPropertyValue() { - return previousPropertyValue; - } - - private static enum LabelerId implements EventLabelerId { - GROUP_PROPERTY, GROUP, TYPE_PROPERTY, TYPE - } - - private static void validateGroupId(SimulationContext simulationContext, GroupId groupId) { - if (groupId == null) { - throw new ContractException(GroupError.NULL_GROUP_ID); - } - GroupsDataManager groupsDataManager = simulationContext.getDataManager(GroupsDataManager.class); - if (!groupsDataManager.groupExists(groupId)) { - throw new ContractException(GroupError.UNKNOWN_GROUP_ID, groupId); - } - } - - private static void validateGroupTypeId(SimulationContext simulationContext, GroupTypeId groupTypeId) { - if (groupTypeId == null) { - throw new ContractException(GroupError.NULL_GROUP_TYPE_ID); - } - GroupsDataManager groupsDataManager = simulationContext.getDataManager(GroupsDataManager.class); - if (!groupsDataManager.groupTypeIdExists(groupTypeId)) { - throw new ContractException(GroupError.UNKNOWN_GROUP_TYPE_ID, groupTypeId); - } - } - - private static void validateGroupPropertyId(SimulationContext simulationContext, GroupId groupId, GroupPropertyId groupPropertyId) { - if (groupPropertyId == null) { - throw new ContractException(GroupError.NULL_GROUP_PROPERTY_ID); - } - GroupsDataManager groupsDataManager = simulationContext.getDataManager(GroupsDataManager.class); - GroupTypeId groupTypeId = groupsDataManager.getGroupType(groupId); - if (!groupsDataManager.getGroupPropertyExists(groupTypeId, groupPropertyId)) { - throw new ContractException(GroupError.UNKNOWN_GROUP_PROPERTY_ID, groupTypeId + ": " + groupPropertyId); - } - } - - private static void validateGroupPropertyId(SimulationContext simulationContext, GroupTypeId groupTypeId, GroupPropertyId groupPropertyId) { - if (groupPropertyId == null) { - throw new ContractException(GroupError.NULL_GROUP_PROPERTY_ID); - } - GroupsDataManager groupsDataManager = simulationContext.getDataManager(GroupsDataManager.class); - - if (!groupsDataManager.getGroupPropertyExists(groupTypeId, groupPropertyId)) { - throw new ContractException(GroupError.UNKNOWN_GROUP_PROPERTY_ID, groupTypeId + ": " + groupPropertyId); - } - } - - /** - * Returns an event label used to subscribe to - * {@link GroupPropertyUpdateEvent} events. Matches on group id and group - * property id. - * - * Preconditions : The context cannot be null - * - * @throws ContractException - * - *
  • {@linkplain GroupError#NULL_GROUP_ID} if the group id is - * null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_ID} if the group id - * is not known
  • - *
  • {@linkplain GroupError#NULL_GROUP_PROPERTY_ID} if the - * group property id is null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_PROPERTY_ID} if the - * group property id is not known
  • - * - */ - public static EventLabel getEventLabelByGroupAndProperty(SimulationContext simulationContext, GroupId groupId, GroupPropertyId groupPropertyId) { - validateGroupId(simulationContext, groupId); - validateGroupPropertyId(simulationContext, groupId, groupPropertyId); - return _getEventLabelByGroupAndProperty(groupId, groupPropertyId);// - } - - private static EventLabel _getEventLabelByGroupAndProperty(GroupId groupId, GroupPropertyId groupPropertyId) { - - return EventLabel .builder(GroupPropertyUpdateEvent.class)// - .setEventLabelerId(LabelerId.GROUP_PROPERTY)// - .addKey(GroupPropertyUpdateEvent.class)// - .addKey(groupId)// - .addKey(groupPropertyId)// - .build();// - } - - /** - * Returns an event labeler for {@link GroupPropertyUpdateEvent} events that - * uses group id and group property id. Automatically added at - * initialization. - */ - public static EventLabeler getEventLabelerForGroupAndProperty() { - return EventLabeler .builder(GroupPropertyUpdateEvent.class)// - .setEventLabelerId(LabelerId.GROUP_PROPERTY)// - .setLabelFunction((context, event) -> _getEventLabelByGroupAndProperty(event.getGroupId(), event.getGroupPropertyId()))// - .build(); - } - - /** - * Returns an event label used to subscribe to - * {@link GroupPropertyUpdateEvent} events. Matches on group id. - * - * Preconditions : The context cannot be null - * - * @throws ContractException - * - *
  • {@linkplain GroupError#NULL_GROUP_ID} if the group id is - * null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_ID} if the group id - * is not known
  • - * - */ - public static EventLabel getEventLabelByGroup(SimulationContext simulationContext, GroupId groupId) { - validateGroupId(simulationContext, groupId); - return _getEventLabelByGroup(groupId);// - } - - private static EventLabel _getEventLabelByGroup(GroupId groupId) { - - return EventLabel .builder(GroupPropertyUpdateEvent.class)// - .setEventLabelerId(LabelerId.GROUP)// - .addKey(GroupPropertyUpdateEvent.class)// - .addKey(groupId)// - .build();// - } - - /** - * Returns an event labeler for {@link GroupPropertyUpdateEvent} events that - * uses group id. Automatically added at initialization. - */ - public static EventLabeler getEventLabelerForGroup() { - return EventLabeler .builder(GroupPropertyUpdateEvent.class)// - .setEventLabelerId(LabelerId.GROUP)// - .setLabelFunction((context, event) -> _getEventLabelByGroup(event.getGroupId()))// - .build(); - } - - - - /** - * Returns an event label used to subscribe to - * {@link GroupPropertyUpdateEvent} events. Matches on group type id and - * group property id. - * - * Preconditions : The context cannot be null - * - * @throws ContractException - * - *
  • {@linkplain GroupError#NULL_GROUP_PROPERTY_ID} if the - * group property id is null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_PROPERTY_ID} if the - * group property id is not known
  • - *
  • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if the group - * type id is null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} if the - * group type id is not known
  • - * - */ - public static EventLabel getEventLabelByGroupTypeAndProperty(SimulationContext simulationContext, GroupTypeId groupTypeId, GroupPropertyId groupPropertyId) { - validateGroupTypeId(simulationContext, groupTypeId); - validateGroupPropertyId(simulationContext, groupTypeId, groupPropertyId); - return _getEventLabelByGroupTypeAndProperty(groupTypeId, groupPropertyId); - } - - private static EventLabel _getEventLabelByGroupTypeAndProperty(GroupTypeId groupTypeId, GroupPropertyId groupPropertyId) { - - return EventLabel .builder(GroupPropertyUpdateEvent.class)// - .setEventLabelerId(LabelerId.TYPE_PROPERTY)// - .addKey(GroupPropertyUpdateEvent.class)// - .addKey(groupTypeId)// - .addKey(groupPropertyId)// - .build(); - } - - /** - * Returns an event labeler for {@link GroupPropertyUpdateEvent} events that - * uses group id and group type id. Automatically added at initialization. - */ - public static EventLabeler getEventLabelerForGroupTypeAndProperty(GroupsDataManager groupsDataManager) { - return EventLabeler .builder(GroupPropertyUpdateEvent.class)// - .setEventLabelerId(LabelerId.TYPE_PROPERTY)// - .setLabelFunction((context, event) -> { - GroupTypeId groupTypeId = groupsDataManager.getGroupType(event.getGroupId()); - return _getEventLabelByGroupTypeAndProperty(groupTypeId, event.getGroupPropertyId()); - })// - .build(); - } - - /** - * Returns an event label used to subscribe to - * {@link GroupPropertyUpdateEvent} events. Matches on group type id. - * - * Preconditions : The context cannot be null - * - * @throws ContractException - * - * - *
  • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if the group - * type id is null
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_TYPE_ID} if the - * group type id is not known
  • - * - */ - public static EventLabel getEventLabelByGroupType(SimulationContext simulationContext, GroupTypeId groupTypeId) { - validateGroupTypeId(simulationContext, groupTypeId); - return _getEventLabelByGroupType(groupTypeId);// - } - - private static EventLabel _getEventLabelByGroupType(GroupTypeId groupTypeId) { - - return EventLabel .builder(GroupPropertyUpdateEvent.class)// - .setEventLabelerId(LabelerId.TYPE)// - .addKey(GroupPropertyUpdateEvent.class)// - .addKey(groupTypeId)// - .build();// - } - - /** - * Returns an event labeler for {@link GroupPropertyUpdateEvent} events that - * uses group type id. Automatically added at initialization. - */ - public static EventLabeler getEventLabelerForGroupType(GroupsDataManager groupsDataManager) { - return EventLabeler .builder(GroupPropertyUpdateEvent.class)// - .setEventLabelerId(LabelerId.TYPE)// - .setLabelFunction((context, event) -> { - GroupTypeId groupTypeId = groupsDataManager.getGroupType(event.getGroupId()); - return _getEventLabelByGroupType(groupTypeId); - })// - .build(); - } - -} diff --git a/gcm3/src/main/java/plugins/groups/support/BulkGroupMembershipData.java b/gcm3/src/main/java/plugins/groups/support/BulkGroupMembershipData.java deleted file mode 100644 index 6ca5d496b..000000000 --- a/gcm3/src/main/java/plugins/groups/support/BulkGroupMembershipData.java +++ /dev/null @@ -1,260 +0,0 @@ -package plugins.groups.support; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; - -import plugins.people.support.PersonError; -import util.errors.ContractException; - -public class BulkGroupMembershipData { - - private static class Data { - - /* - * Map from int(group id) -> Group type id - */ - private List groupTypes = new ArrayList<>(); - - /* - * Integer(PersonId)->List(GroupId) - */ - private Map> groupMemberships = new LinkedHashMap<>(); - - /* - * An empty list of Group id values used as the groups for a person when - * that person was not added to the group memberships. - */ - private final List emptyGroupIndicesList = Collections.unmodifiableList(new ArrayList<>()); - - private final Set emptyGroupPropertySet = Collections.unmodifiableSet(new LinkedHashSet<>()); - - private Map> groupPropertyValues = new LinkedHashMap<>(); - } - - private final Data data; - - private BulkGroupMembershipData(Data data) { - this.data = data; - } - - /** - * Returns a builder for {@link BulkGroupMembershipData} - */ - public static Builder builder() { - return new Builder(); - } - - /** - * Builder class for {@linkplain BulkGroupMembershipData} - * - * @author Shawn Hatch - * - */ - public static class Builder { - - private Builder() { - - } - - private Data data = new Data(); - - /** - * Builds the {@link BulkGroupMembershipData} from the collected data - * - * @throws ContractException - *
  • {@linkplain GroupError#UNKNOWN_GROUP_ID} if a group - * membership was a negative group index
  • - * - *
  • {@linkplain GroupError#UNKNOWN_GROUP_ID} if a group - * membership was added for a group index that was not added - * as a group
  • - */ - public BulkGroupMembershipData build() { - try { - validate(); - return new BulkGroupMembershipData(data); - } finally { - data = new Data(); - } - } - - private void validate() { - // show that the group indexes for each person are in bounds. - // Negative indexes are rejected when entered. - int maxGroupIndex = data.groupTypes.size(); - for (Integer personIndex : data.groupMemberships.keySet()) { - List groupIndices = data.groupMemberships.get(personIndex); - for (Integer groupIndex : groupIndices) { - if (groupIndex >= maxGroupIndex) { - throw new ContractException(GroupError.UNKNOWN_GROUP_ID); - } - } - } - - // show that the collected group property values are associated with - // valid group id values - for (Integer groupIndex : data.groupPropertyValues.keySet()) { - if (groupIndex >= maxGroupIndex) { - throw new ContractException(GroupError.UNKNOWN_GROUP_ID); - } - } - - } - - /** - * Add a group with the given group type id. - * - * @throws ContractException - *
  • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if a group - * type id is null
  • - * - * - */ - public Builder addGroup(GroupTypeId groupTypeId) { - if (groupTypeId == null) { - throw new ContractException(GroupError.NULL_GROUP_TYPE_ID); - } - data.groupTypes.add(groupTypeId); - return this; - } - - /** - * Set a group property value - * - * @throws ContractException - *
  • {@linkplain GroupError#UNKNOWN_GROUP_ID} if the group - * index is negative
  • - *
  • {@linkplain GroupError#NULL_GROUP_PROPERTY_ID} if the - * group property id is null
  • - *
  • {@linkplain GroupError#NULL_GROUP_PROPERTY_VALUE} if - * the property value is null
  • - * - * - */ - public Builder setGroupPropertyValue(int groupIndex, GroupPropertyId groupPropertyId, Object propertyValue) { - if (groupIndex < 0) { - throw new ContractException(GroupError.UNKNOWN_GROUP_ID); - } - if (groupPropertyId == null) { - throw new ContractException(GroupError.NULL_GROUP_PROPERTY_ID); - } - if (propertyValue == null) { - throw new ContractException(GroupError.NULL_GROUP_PROPERTY_VALUE); - } - Map map = data.groupPropertyValues.get(groupIndex); - if (map == null) { - map = new LinkedHashMap<>(); - data.groupPropertyValues.put(groupIndex, map); - } - map.put(groupPropertyId, propertyValue); - return this; - } - - /** - * Add a person to a group - * - * @throws ContractException - *
  • {@linkplain PersonError#UNKNOWN_PERSON_ID} if the - * person index is negative
  • - *
  • {@linkplain GroupError#UNKNOWN_GROUP_ID} if the group - * index is negative
  • - *
  • {@linkplain GroupError#DUPLICATE_GROUP_MEMBERSHIP} if - * the person index is already associated with the group - * index
  • - * - * - */ - public Builder addPersonToGroup(int personIndex, int groupIndex) { - if (personIndex < 0) { - throw new ContractException(PersonError.UNKNOWN_PERSON_ID); - } - - if (groupIndex < 0) { - throw new ContractException(GroupError.UNKNOWN_GROUP_ID); - } - - List list = data.groupMemberships.get(personIndex); - if (list == null) { - list = new ArrayList<>(); - data.groupMemberships.put(personIndex, list); - } - if (list.contains(groupIndex)) { - throw new ContractException(GroupError.DUPLICATE_GROUP_MEMBERSHIP); - } - list.add(groupIndex); - return this; - } - } - - /** - * Returns the number of groups contained in this - * {@link BulkGroupMembershipData} - */ - public int getGroupCount() { - return data.groupTypes.size(); - } - - /** - * Returns the group type id for the given group index. - * - * @throws ContractException - *
  • {@linkplain GroupError.UNKNOWN_GROUP_ID} if the index is - * < 0
  • - *
  • {@linkplain GroupError.UNKNOWN_GROUP_ID} if the index is - * >= getGroupCount()
  • - */ - public GroupTypeId getGroupTypeId(int groupIndex) { - if (groupIndex < 0) { - throw new ContractException(GroupError.UNKNOWN_GROUP_ID); - } - if (groupIndex >= data.groupTypes.size()) { - throw new ContractException(GroupError.UNKNOWN_GROUP_ID); - } - return data.groupTypes.get(groupIndex); - } - - /** - * Returns the list of group indices associated with the given person index. - * Returns an empty list if the person is unknown - * - */ - public List getGroupIndicesForPersonIndex(int personIndex) { - List list = data.groupMemberships.get(personIndex); - if (list != null) { - return Collections.unmodifiableList(list); - } - return data.emptyGroupIndicesList; - } - - /** - * Returns the people associated with this {@link BulkGroupMembershipData} - */ - public List getPersonIndices() { - return new ArrayList<>(data.groupMemberships.keySet()); - } - - public Set getGroupPropertyIds(int groupIndex) { - Map map = data.groupPropertyValues.get(groupIndex); - if (map != null) { - return Collections.unmodifiableSet(map.keySet()); - } - return data.emptyGroupPropertySet; - } - - @SuppressWarnings("unchecked") - public Optional getGroupPropertyValue(int groupIndex, GroupPropertyId groupPropertyId) { - Map map = data.groupPropertyValues.get(groupIndex); - Object propertyValue = null; - if (map != null) { - propertyValue = map.get(groupPropertyId); - } - return Optional.ofNullable((T) propertyValue); - } - -} diff --git a/gcm3/src/main/java/plugins/groups/support/GroupConstructionInfo.java b/gcm3/src/main/java/plugins/groups/support/GroupConstructionInfo.java deleted file mode 100644 index cf8348d5f..000000000 --- a/gcm3/src/main/java/plugins/groups/support/GroupConstructionInfo.java +++ /dev/null @@ -1,117 +0,0 @@ -package plugins.groups.support; - -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; - -import net.jcip.annotations.Immutable; -import net.jcip.annotations.NotThreadSafe; -import util.errors.ContractException; - -/** - * Represents the information to add a group, but not its relationship to - * people. - * - * @author Shawn Hatch - * - */ -@Immutable -public final class GroupConstructionInfo { - private final Scaffold scaffold; - - private static class Scaffold { - private GroupTypeId groupTypeId; - private Map propertyValues = new LinkedHashMap<>(); - } - - /** - * Returns the group Type of the group - */ - @SuppressWarnings("unchecked") - public T getGroupTypeId() { - return (T) scaffold.groupTypeId; - } - - /** - * Returns a map of the group property values for the group - */ - public Map getPropertyValues() { - return Collections.unmodifiableMap(scaffold.propertyValues); - } - - /* - * Hidden constructor - */ - private GroupConstructionInfo(Scaffold scaffold) { - this.scaffold = scaffold; - } - - public static Builder builder() { - return new Builder(); - } - - @NotThreadSafe - public static class Builder { - - private Builder() { - } - - private Scaffold scaffold = new Scaffold(); - - private void validate() { - if (scaffold.groupTypeId == null) { - throw new ContractException(GroupError.NULL_GROUP_TYPE_ID); - } - } - - /** - * Builds the {@link GroupConstructionInfo} from the collected data - * - * @throws ContractException; - *
  • {@linkplain GroupError#NULL_GROUP_TYPE_ID} if no - * group type id was collected
  • - */ - public GroupConstructionInfo build() { - try { - validate(); - return new GroupConstructionInfo(scaffold); - } finally { - scaffold = new Scaffold(); - } - } - - /** - * Sets the group type id - * - * @throws ContractException - * if the group type id is null - */ - public Builder setGroupTypeId(GroupTypeId groupTypeId) { - if (groupTypeId == null) { - throw new ContractException(GroupError.NULL_GROUP_TYPE_ID); - } - scaffold.groupTypeId = groupTypeId; - return this; - } - - /** - * Sets the group property value. - * - * @throws ContractException - *
  • {@linkplain GroupError#NULL_GROUP_PROPERTY_ID} if the group property id is null
  • - *
  • {@linkplain GroupError#NULL_GROUP_PROPERTY_VALUE} if the group property value is null
  • - */ - public Builder setGroupPropertyValue(GroupPropertyId groupPropertyId, Object groupPropertyValue) { - if (groupPropertyId == null) { - throw new ContractException(GroupError.NULL_GROUP_PROPERTY_ID); - } - if (groupPropertyValue == null) { - throw new ContractException(GroupError.NULL_GROUP_PROPERTY_VALUE); - } - scaffold.propertyValues.put(groupPropertyId, groupPropertyValue); - return this; - } - - } - -} diff --git a/gcm3/src/main/java/plugins/groups/support/GroupError.java b/gcm3/src/main/java/plugins/groups/support/GroupError.java deleted file mode 100644 index 2af227aec..000000000 --- a/gcm3/src/main/java/plugins/groups/support/GroupError.java +++ /dev/null @@ -1,50 +0,0 @@ -package plugins.groups.support; - -import util.errors.ContractError; -import util.errors.ContractException; - -/** - * An enumeration supporting {@link ContractException} that acts as a general - * description of the exception. - * - * @author Shawn Hatch - * - */ -public enum GroupError implements ContractError { - - NEGATIVE_GROUP_COUNT("group count is negative"), - NULL_GROUP_INITIALIZATION_DATA("Null group initialization data"), - NULL_GROUP_DATA_MANAGER("Null group data manager"), - DUPLICATE_GROUP_MEMBERSHIP("Person was previously assigned to group"), - DUPLICATE_GROUP_TYPE("Duplicate group type"), - DUPLICATE_GROUP_PROPERTY_VALUE_ASSIGNMENT("Duplicate group property value assignment"), - DUPLICATE_GROUP_PROPERTY_DEFINITION("Duplicate group property definition"), - DUPLICATE_GROUP_ID("Duplicate group id"), - DUPLICATE_PERSON_GROUP_ASSIGNMENT("Duplicate person group assignment"), - MALFORMED_GROUP_SAMPLE_WEIGHTING_FUNCTION("Data used to form an enumerated distribution for group sampling was malformed"), - NON_GROUP_MEMBERSHIP("Person is not currently assigned to group"), - NULL_GROUP_CONSTRUCTION_INFO("Null group construction info"), - NULL_GROUP_ID("Null group id"), - NULL_GROUP_PROPERTY_ID("Null group property id"), - NULL_GROUP_PROPERTY_VALUE("Null group property value"), - NULL_PROPERTY_DEFINITION("Null property defintion"), - NULL_GROUP_SAMPLER("Null group sampler"), - NULL_GROUP_TYPE_ID("Null group type id"), - UNKNOWN_GROUP_ID("Unknown group id"), - UNKNOWN_GROUP_PROPERTY_ID("Unknown group property id"), - UNKNOWN_GROUP_TYPE_ID("Unknown group type id"), - PROPERTY_DEFINITION_REQUIRES_DEFAULT("Group property definition does not have an assigned default value"), - INSUFFICIENT_GROUP_PROPERTY_VALUE_ASSIGNMENT("A group property definition default value is null and not replaced with sufficient property value assignments"); - ; - - private final String description; - - private GroupError(final String description) { - this.description = description; - } - - @Override - public String getDescription() { - return description; - } -} diff --git a/gcm3/src/main/java/plugins/groups/support/GroupId.java b/gcm3/src/main/java/plugins/groups/support/GroupId.java deleted file mode 100644 index 02924c0a2..000000000 --- a/gcm3/src/main/java/plugins/groups/support/GroupId.java +++ /dev/null @@ -1,55 +0,0 @@ -package plugins.groups.support; - -import net.jcip.annotations.Immutable; - - -/** - * Identifier for all groups - * - * @author Shawn Hatch - * - */ -@Immutable -public final class GroupId implements Comparable{ - - private final int id; - - public GroupId(int id) { - this.id = id; - } - - public int getValue() { - return id; - } - - @Override - public int compareTo(GroupId groupId) { - return Integer.compare(id,groupId.id); - } - - @Override - public int hashCode() { - return id; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof GroupId)) { - return false; - } - GroupId other = (GroupId) obj; - if (id != other.id) { - return false; - } - return true; - } - - @Override - public String toString() { - return Integer.toString(id); - } - -} diff --git a/gcm3/src/main/java/plugins/groups/support/GroupLabeler.java b/gcm3/src/main/java/plugins/groups/support/GroupLabeler.java deleted file mode 100644 index a0ca7a512..000000000 --- a/gcm3/src/main/java/plugins/groups/support/GroupLabeler.java +++ /dev/null @@ -1,129 +0,0 @@ -package plugins.groups.support; - -import java.util.LinkedHashSet; -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; - -import nucleus.Event; -import nucleus.SimulationContext; -import plugins.groups.datamanagers.GroupsDataManager; -import plugins.groups.events.GroupMembershipAdditionEvent; -import plugins.groups.events.GroupMembershipRemovalEvent; -import plugins.partitions.support.Labeler; -import plugins.partitions.support.LabelerSensitivity; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; - -/** - * A labeler for groups. The dimension of the labeler is - * {@linkplain GroupTypeId}, the events that stimulates a label update are - * {@linkplain GroupMembershipAdditionEvent} and - * {@linkplain GroupMembershipRemovalEvent} and the labeling function - * is composed from the given Function. - * - * @author Shawn Hatch - * - */ -public final class GroupLabeler implements Labeler { - - private final Function groupTypeCountLabelingFunction; - private GroupsDataManager groupsDataManager; - - /** - * Creates the Group labeler from the given labeling function - */ - public GroupLabeler(Function groupTypeCountLabelingFunction) { - this.groupTypeCountLabelingFunction = groupTypeCountLabelingFunction; - } - - private Optional getPersonId(GroupMembershipAdditionEvent groupMembershipAdditionEvent) { - return Optional.of(groupMembershipAdditionEvent.getPersonId()); - } - - private Optional getPersonId(GroupMembershipRemovalEvent groupMembershipRemovalEvent) { - return Optional.of(groupMembershipRemovalEvent.getPersonId()); - } - - /** - * Returns a set of labeler sensitivitites for - * GroupMembershipAdditionEvent and - * GroupMembershipRemovalEvent. All group changes will effect the - * partition. - */ - @Override - public Set> getLabelerSensitivities() { - Set> result = new LinkedHashSet<>(); - result.add(new LabelerSensitivity(GroupMembershipAdditionEvent.class, this::getPersonId)); - result.add(new LabelerSensitivity(GroupMembershipRemovalEvent.class, this::getPersonId)); - return result; - } - - /** - * Returns the label for the given person id - * - * @throwsContractException - *
  • {@linkplain PersonError#NULL_PERSON_ID} if - * the person id is null - *
  • {@linkplain PersonError#UNKNOWN_PERSON_ID} - * if the person id is unknown - */ - @Override - public Object getLabel(SimulationContext simulationContext, PersonId personId) { - if (groupsDataManager == null) { - groupsDataManager = simulationContext.getDataManager(GroupsDataManager.class); - } - - GroupTypeCountMap.Builder groupTypeCountMapBuilder = GroupTypeCountMap.builder(); - for (GroupTypeId groupTypeId : groupsDataManager.getGroupTypeIds()) { - int count = groupsDataManager.getGroupCountForGroupTypeAndPerson(groupTypeId, personId); - groupTypeCountMapBuilder.setCount(groupTypeId, count); - } - GroupTypeCountMap groupTypeCountMap = groupTypeCountMapBuilder.build(); - return groupTypeCountLabelingFunction.apply(groupTypeCountMap); - } - - /** - * Returns {@link GroupTypeId} class as the dimension. - */ - @Override - public Object getDimension() { - return GroupTypeId.class; - } - - @Override - public Object getPastLabel(SimulationContext simulationContext, Event event) { - if (groupsDataManager == null) { - groupsDataManager = simulationContext.getDataManager(GroupsDataManager.class); - } - - PersonId personId; - GroupTypeId eventGroupTypeId; - int delta; - if (event instanceof GroupMembershipAdditionEvent) { - GroupMembershipAdditionEvent groupMembershipAdditionEvent = (GroupMembershipAdditionEvent) event; - personId = groupMembershipAdditionEvent.getPersonId(); - GroupId groupId = groupMembershipAdditionEvent.getGroupId(); - eventGroupTypeId = groupsDataManager.getGroupType(groupId); - delta = -1; - } else { - GroupMembershipRemovalEvent groupMembershipRemovalEvent = (GroupMembershipRemovalEvent) event; - personId = groupMembershipRemovalEvent.getPersonId(); - GroupId groupId = groupMembershipRemovalEvent.getGroupId(); - eventGroupTypeId = groupsDataManager.getGroupType(groupId); - delta = +1; - } - - GroupTypeCountMap.Builder groupTypeCountMapBuilder = GroupTypeCountMap.builder(); - for (GroupTypeId groupTypeId : groupsDataManager.getGroupTypeIds()) { - int count = groupsDataManager.getGroupCountForGroupTypeAndPerson(groupTypeId, personId); - if(groupTypeId.equals(eventGroupTypeId)) { - count += delta; - } - groupTypeCountMapBuilder.setCount(groupTypeId, count); - } - GroupTypeCountMap groupTypeCountMap = groupTypeCountMapBuilder.build(); - return groupTypeCountLabelingFunction.apply(groupTypeCountMap); - } - -} diff --git a/gcm3/src/main/java/plugins/groups/support/GroupMemberFilter.java b/gcm3/src/main/java/plugins/groups/support/GroupMemberFilter.java deleted file mode 100644 index 4eaa2a9e3..000000000 --- a/gcm3/src/main/java/plugins/groups/support/GroupMemberFilter.java +++ /dev/null @@ -1,71 +0,0 @@ -package plugins.groups.support; - -import java.util.LinkedHashSet; -import java.util.Optional; -import java.util.Set; - -import nucleus.NucleusError; -import nucleus.SimulationContext; -import plugins.groups.datamanagers.GroupsDataManager; -import plugins.groups.events.GroupMembershipAdditionEvent; -import plugins.groups.events.GroupMembershipRemovalEvent; -import plugins.partitions.support.Filter; -import plugins.partitions.support.FilterSensitivity; -import plugins.people.support.PersonId; -import util.errors.ContractException; - -public class GroupMemberFilter extends Filter { - final GroupId groupId; - private GroupsDataManager groupsDataManager; - - private void validateGroupIdNotNull(SimulationContext simulationContext, final GroupId groupId) { - if (groupId == null) { - throw new ContractException(GroupError.NULL_GROUP_ID); - } - } - - public GroupMemberFilter(final GroupId groupId) { - this.groupId = groupId; - } - - @Override - public void validate(SimulationContext simulationContext) { - validateGroupIdNotNull(simulationContext, groupId); - } - - private Optional additionRequiresRefresh(SimulationContext simulationContext, GroupMembershipAdditionEvent event) { - if (event.getGroupId().equals(groupId)) { - return Optional.of(event.getPersonId()); - } - return Optional.empty(); - } - - private Optional removalRequiresRefresh(SimulationContext simulationContext, GroupMembershipRemovalEvent event) { - if (event.getGroupId().equals(groupId)) { - return Optional.of(event.getPersonId()); - } - return Optional.empty(); - - } - - @Override - public Set> getFilterSensitivities() { - Set> result = new LinkedHashSet<>(); - result.add(new FilterSensitivity(GroupMembershipAdditionEvent.class, this::additionRequiresRefresh)); - result.add(new FilterSensitivity(GroupMembershipRemovalEvent.class, this::removalRequiresRefresh)); - - return result; - } - - @Override - public boolean evaluate(SimulationContext simulationContext, PersonId personId) { - if(simulationContext == null) { - throw new ContractException(NucleusError.NULL_SIMULATION_CONTEXT); - } - if (groupsDataManager == null) { - groupsDataManager = simulationContext.getDataManager(GroupsDataManager.class); - } - return groupsDataManager.isPersonInGroup(personId,groupId); - } - -} diff --git a/gcm3/src/main/java/plugins/groups/support/GroupPropertyId.java b/gcm3/src/main/java/plugins/groups/support/GroupPropertyId.java deleted file mode 100644 index 3a40c21fb..000000000 --- a/gcm3/src/main/java/plugins/groups/support/GroupPropertyId.java +++ /dev/null @@ -1,14 +0,0 @@ -package plugins.groups.support; - -import net.jcip.annotations.ThreadSafe; - -/** - * Marker interface for group property identifiers - * - * @author Shawn Hatch - * - */ -@ThreadSafe -public interface GroupPropertyId { - -} diff --git a/gcm3/src/main/java/plugins/groups/support/GroupSampler.java b/gcm3/src/main/java/plugins/groups/support/GroupSampler.java deleted file mode 100644 index 683e78d69..000000000 --- a/gcm3/src/main/java/plugins/groups/support/GroupSampler.java +++ /dev/null @@ -1,78 +0,0 @@ -package plugins.groups.support; - -import java.util.Optional; - -import plugins.people.support.PersonId; -import plugins.stochastics.support.RandomNumberGeneratorId; - -public final class GroupSampler { - private final PersonId excludedPerson; - - private final RandomNumberGeneratorId randomNumberGeneratorId; - - private final GroupWeightingFunction weightingFunction; - - private GroupSampler(Scaffold scaffold) { - this.excludedPerson = scaffold.excludedPerson; - this.weightingFunction = scaffold.weightingFunction; - this.randomNumberGeneratorId = scaffold.randomNumberGeneratorId; - } - - private static class Scaffold { - - private PersonId excludedPerson; - - private RandomNumberGeneratorId randomNumberGeneratorId; - - private GroupWeightingFunction weightingFunction; - } - - public static Builder builder() { - return new Builder(); - } - - public final static class Builder { - Scaffold scaffold = new Scaffold(); - - private Builder() { - - } - - public GroupSampler build() { - try { - return new GroupSampler(scaffold); - } finally { - scaffold = new Scaffold(); - } - } - - public Builder setExcludedPersonId(PersonId personId) { - scaffold.excludedPerson = personId; - return this; - } - - public Builder setRandomNumberGeneratorId(RandomNumberGeneratorId randomNumberGeneratorId) { - scaffold.randomNumberGeneratorId = randomNumberGeneratorId; - return this; - } - - public Builder setGroupWeightingFunction(GroupWeightingFunction weightingFunction) { - scaffold.weightingFunction = weightingFunction; - return this; - } - - } - - public Optional getExcludedPerson() { - return Optional.ofNullable(excludedPerson); - } - - public Optional getRandomNumberGeneratorId() { - return Optional.ofNullable(randomNumberGeneratorId); - } - - public Optional getWeightingFunction() { - return Optional.ofNullable(weightingFunction); - } - -} diff --git a/gcm3/src/main/java/plugins/groups/support/GroupTypeId.java b/gcm3/src/main/java/plugins/groups/support/GroupTypeId.java deleted file mode 100644 index d3368d03e..000000000 --- a/gcm3/src/main/java/plugins/groups/support/GroupTypeId.java +++ /dev/null @@ -1,14 +0,0 @@ -package plugins.groups.support; - -import net.jcip.annotations.ThreadSafe; - -/** - * Marker interface for group type identifiers - * - * @author Shawn Hatch - * - */ -@ThreadSafe -public interface GroupTypeId { - -} diff --git a/gcm3/src/main/java/plugins/groups/support/GroupTypesForPersonFilter.java b/gcm3/src/main/java/plugins/groups/support/GroupTypesForPersonFilter.java deleted file mode 100644 index 1a5167335..000000000 --- a/gcm3/src/main/java/plugins/groups/support/GroupTypesForPersonFilter.java +++ /dev/null @@ -1,70 +0,0 @@ -package plugins.groups.support; - -import java.util.LinkedHashSet; -import java.util.Optional; -import java.util.Set; - -import nucleus.NucleusError; -import nucleus.SimulationContext; -import plugins.groups.datamanagers.GroupsDataManager; -import plugins.groups.events.GroupMembershipAdditionEvent; -import plugins.groups.events.GroupMembershipRemovalEvent; -import plugins.partitions.support.Equality; -import plugins.partitions.support.Filter; -import plugins.partitions.support.FilterSensitivity; -import plugins.partitions.support.PartitionError; -import plugins.people.support.PersonId; -import util.errors.ContractException; - -public final class GroupTypesForPersonFilter extends Filter { - - private final Equality equality; - private final int groupTypeCount; - private GroupsDataManager groupsDataManager; - - private void validateEquality(final SimulationContext simulationContext, final Equality equality) { - if (equality == null) { - throw new ContractException(PartitionError.NULL_EQUALITY_OPERATOR); - } - } - - public GroupTypesForPersonFilter(final Equality equality, final int groupTypeCount) { - this.equality = equality; - this.groupTypeCount = groupTypeCount; - } - - @Override - public void validate(SimulationContext simulationContext) { - validateEquality(simulationContext, equality); - } - - @Override - public boolean evaluate(SimulationContext simulationContext, PersonId personId) { - if(simulationContext == null) { - throw new ContractException(NucleusError.NULL_SIMULATION_CONTEXT); - } - if (groupsDataManager == null) { - groupsDataManager = simulationContext.getDataManager(GroupsDataManager.class); - } - final int count = groupsDataManager.getGroupTypeCountForPersonId(personId); - return equality.isCompatibleComparisonValue(Integer.compare(count, groupTypeCount)); - } - - private Optional additionRequiresRefresh(SimulationContext simulationContext, GroupMembershipAdditionEvent event) { - return Optional.of(event.getPersonId()); - } - - private Optional removalRequiresRefresh(SimulationContext simulationContext, GroupMembershipRemovalEvent event) { - return Optional.of(event.getPersonId()); - } - - @Override - public Set> getFilterSensitivities() { - Set> result = new LinkedHashSet<>(); - result.add(new FilterSensitivity(GroupMembershipAdditionEvent.class, this::additionRequiresRefresh)); - result.add(new FilterSensitivity(GroupMembershipRemovalEvent.class, this::removalRequiresRefresh)); - - return result; - } - -} \ No newline at end of file diff --git a/gcm3/src/main/java/plugins/groups/support/GroupWeightingFunction.java b/gcm3/src/main/java/plugins/groups/support/GroupWeightingFunction.java deleted file mode 100644 index 0d6c8eaeb..000000000 --- a/gcm3/src/main/java/plugins/groups/support/GroupWeightingFunction.java +++ /dev/null @@ -1,21 +0,0 @@ -package plugins.groups.support; - -import nucleus.SimulationContext; -import plugins.people.support.PersonId; - -/** - * A functional interface for selecting people from a group based on assigning a - * weighting value to a person. - * - * @author Shawn Hatch - * - */ -public interface GroupWeightingFunction { - /** - * Returns a non-negative, finite and stable value for the given inputs. - * Repeated invocations with the same arguments should return the same value - * while no mutations to simulation state have taken place. The person will - * be a member of the group. - */ - public double getWeight(SimulationContext simulationContext, PersonId personId, GroupId groupId); -} diff --git a/gcm3/src/main/java/plugins/groups/support/GroupsForPersonAndGroupTypeFilter.java b/gcm3/src/main/java/plugins/groups/support/GroupsForPersonAndGroupTypeFilter.java deleted file mode 100644 index c044a971b..000000000 --- a/gcm3/src/main/java/plugins/groups/support/GroupsForPersonAndGroupTypeFilter.java +++ /dev/null @@ -1,98 +0,0 @@ -package plugins.groups.support; - -import java.util.LinkedHashSet; -import java.util.Optional; -import java.util.Set; - -import nucleus.SimulationContext; -import plugins.groups.datamanagers.GroupsDataManager; -import plugins.groups.events.GroupMembershipAdditionEvent; -import plugins.groups.events.GroupMembershipRemovalEvent; -import plugins.partitions.support.Equality; -import plugins.partitions.support.Filter; -import plugins.partitions.support.FilterSensitivity; -import plugins.partitions.support.PartitionError; -import plugins.people.support.PersonId; -import util.errors.ContractException; - -public final class GroupsForPersonAndGroupTypeFilter extends Filter { - private final GroupTypeId groupTypeId; - private final Equality equality; - private final int groupCount; - private GroupsDataManager groupsDataManager; - - private void validateEquality(final SimulationContext simulationContext, final Equality equality) { - if (equality == null) { - throw new ContractException(PartitionError.NULL_EQUALITY_OPERATOR); - } - } - - private void validateGroupTypeId(final SimulationContext simulationContext, final GroupTypeId groupTypeId) { - if (groupTypeId == null) { - throw new ContractException(GroupError.NULL_GROUP_TYPE_ID); - } - - if (groupsDataManager == null) { - groupsDataManager = simulationContext.getDataManager(GroupsDataManager.class); - } - - if (!groupsDataManager.groupTypeIdExists(groupTypeId)) { - throw new ContractException(GroupError.UNKNOWN_GROUP_TYPE_ID, groupTypeId); - } - } - - public GroupsForPersonAndGroupTypeFilter(final GroupTypeId groupTypeId, final Equality equality, final int groupCount) { - this.equality = equality; - this.groupCount = groupCount; - this.groupTypeId = groupTypeId; - } - - @Override - public void validate(SimulationContext simulationContext) { - validateEquality(simulationContext, equality); - validateGroupTypeId(simulationContext, groupTypeId); - } - - @Override - public boolean evaluate(SimulationContext simulationContext, PersonId personId) { - if (groupsDataManager == null) { - groupsDataManager = simulationContext.getDataManager(GroupsDataManager.class); - } - final int count = groupsDataManager.getGroupCountForGroupTypeAndPerson(groupTypeId, personId); - return evaluate(count); - } - - private boolean evaluate(int count) { - return equality.isCompatibleComparisonValue(Integer.compare(count, groupCount)); - } - - private Optional additionRequiresRefresh(SimulationContext simulationContext, GroupMembershipAdditionEvent event) { - if (groupsDataManager == null) { - groupsDataManager = simulationContext.getDataManager(GroupsDataManager.class); - } - if (groupsDataManager.getGroupType(event.getGroupId()).equals(groupTypeId)) { - return Optional.of(event.getPersonId()); - } - return Optional.empty(); - } - - private Optional removalRequiresRefresh(SimulationContext simulationContext, GroupMembershipRemovalEvent event) { - if (groupsDataManager == null) { - groupsDataManager = simulationContext.getDataManager(GroupsDataManager.class); - } - if (groupsDataManager.getGroupType(event.getGroupId()).equals(groupTypeId)) { - return Optional.of(event.getPersonId()); - } - return Optional.empty(); - } - - @Override - public Set> getFilterSensitivities() { - Set> result = new LinkedHashSet<>(); - result.add(new FilterSensitivity(GroupMembershipAdditionEvent.class, this::additionRequiresRefresh)); - result.add(new FilterSensitivity(GroupMembershipRemovalEvent.class, this::removalRequiresRefresh)); - - return result; - } - -} \ No newline at end of file diff --git a/gcm3/src/main/java/plugins/groups/support/GroupsForPersonFilter.java b/gcm3/src/main/java/plugins/groups/support/GroupsForPersonFilter.java deleted file mode 100644 index 36b8f4e27..000000000 --- a/gcm3/src/main/java/plugins/groups/support/GroupsForPersonFilter.java +++ /dev/null @@ -1,70 +0,0 @@ -package plugins.groups.support; - -import java.util.LinkedHashSet; -import java.util.Optional; -import java.util.Set; - -import nucleus.NucleusError; -import nucleus.SimulationContext; -import plugins.groups.datamanagers.GroupsDataManager; -import plugins.groups.events.GroupMembershipAdditionEvent; -import plugins.groups.events.GroupMembershipRemovalEvent; -import plugins.partitions.support.Equality; -import plugins.partitions.support.Filter; -import plugins.partitions.support.FilterSensitivity; -import plugins.partitions.support.PartitionError; -import plugins.people.support.PersonId; -import util.errors.ContractException; - -public class GroupsForPersonFilter extends Filter { - - private final Equality equality; - private final int groupCount; - private GroupsDataManager groupsDataManager; - - private void validateEquality(final SimulationContext simulationContext, final Equality equality) { - if (equality == null) { - throw new ContractException(PartitionError.NULL_EQUALITY_OPERATOR); - } - } - - public GroupsForPersonFilter(final Equality equality, final int groupCount) { - this.equality = equality; - this.groupCount = groupCount; - } - - @Override - public void validate(SimulationContext simulationContext) { - validateEquality(simulationContext, equality); - } - - @Override - public boolean evaluate(SimulationContext simulationContext, PersonId personId) { - if(simulationContext == null) { - throw new ContractException(NucleusError.NULL_SIMULATION_CONTEXT); - } - if (groupsDataManager == null) { - groupsDataManager = simulationContext.getDataManager(GroupsDataManager.class); - } - final int count = groupsDataManager.getGroupCountForPerson(personId); - return equality.isCompatibleComparisonValue(Integer.compare(count, groupCount)); - } - - private Optional additionRequiresRefresh(SimulationContext simulationContext, GroupMembershipAdditionEvent event) { - return Optional.of(event.getPersonId()); - } - - private Optional removalRequiresRefresh(SimulationContext simulationContext, GroupMembershipRemovalEvent event) { - return Optional.of(event.getPersonId()); - } - - @Override - public Set> getFilterSensitivities() { - Set> result = new LinkedHashSet<>(); - result.add(new FilterSensitivity(GroupMembershipAdditionEvent.class, this::additionRequiresRefresh)); - result.add(new FilterSensitivity(GroupMembershipRemovalEvent.class, this::removalRequiresRefresh)); - - return result; - } - -} diff --git a/gcm3/src/main/java/plugins/groups/testsupport/GroupsActionSupport.java b/gcm3/src/main/java/plugins/groups/testsupport/GroupsActionSupport.java deleted file mode 100644 index ed9383d84..000000000 --- a/gcm3/src/main/java/plugins/groups/testsupport/GroupsActionSupport.java +++ /dev/null @@ -1,158 +0,0 @@ -package plugins.groups.testsupport; - -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.function.Consumer; - -import org.apache.commons.math3.random.RandomGenerator; -import org.apache.commons.math3.util.FastMath; - -import nucleus.ActorContext; -import nucleus.Plugin; -import nucleus.Simulation; -import nucleus.Simulation.Builder; -import nucleus.testsupport.testplugin.ScenarioPlanCompletionObserver; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestError; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import plugins.groups.GroupsPlugin; -import plugins.groups.GroupsPluginData; -import plugins.groups.support.GroupId; -import plugins.people.PeoplePlugin; -import plugins.people.PeoplePluginData; -import plugins.people.support.PersonId; -import plugins.stochastics.StochasticsPlugin; -import plugins.stochastics.StochasticsPluginData; -import util.errors.ContractException; -import util.random.RandomGeneratorProvider; -import util.wrappers.MultiKey; - -/** - * A static test support class for the groups plugin. Provides convenience - * methods for integrating an action plugin into a groups-based simulation test - * harness. - * - * - * @author Shawn Hatch - * - */ -public class GroupsActionSupport { - - /** - * Creates an action plugin with an agent that will execute the given - * consumer at time 0. The action plugin and the remaining arguments are - * passed to an invocation of the testConsumers() method. - */ - public static void testConsumer(int initialPopulation, double expectedGroupsPerPerson, double expectedPeoplePerGroup, long seed, Consumer consumer) { - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, consumer)); - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - testConsumers(initialPopulation, expectedGroupsPerPerson, expectedPeoplePerGroup, seed, testPlugin); - } - - /** - * Executes a simulation instance that supports group plugin testing. - * - * The initial population is added in the initial data. The expected groups - * per person and expected people per group are used to determine the number - * of groups from the number of initial people. People are randomly - * allocated to these groups in a way that approximates the expected rates. - * Groups are allocated uniformly to the TestGroupId enumeration members. - * - * The seed is used to produce randomized initial group types and group - * memberships. - * - * The action plugin is integrated into the simulation run and must contain - * at least one action plan. This helps to ensure that a test that does not - * run completely does not lead to a false positive test evaluation. - * - * @throws ContractException - *
  • {@linkplain ActionError#ACTION_EXECUTION_FAILURE} if not - * all action plans execute or if there are no action plans - * contained in the action plugin
  • - */ - public static void testConsumers(int initialPopulation, double expectedGroupsPerPerson, double expectedPeoplePerGroup, long seed, Plugin testPlugin) { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); - - // create a list of people - List people = new ArrayList<>(); - for (int i = 0; i < initialPopulation; i++) { - people.add(new PersonId(i)); - } - - int membershipCount = (int) FastMath.round(initialPopulation * expectedGroupsPerPerson); - int groupCount = (int) FastMath.round(membershipCount / expectedPeoplePerGroup); - - Builder builder = Simulation.builder(); - - // add the group plugin - GroupsPluginData.Builder groupBuilder = GroupsPluginData.builder(); - // add group types - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - groupBuilder.addGroupTypeId(testGroupTypeId); - } - // define group properties - for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { - groupBuilder.defineGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId, testGroupPropertyId.getPropertyDefinition()); - } - - // add the groups - List groups = new ArrayList<>(); - for (int i = 0; i < groupCount; i++) { - GroupId groupId = new GroupId(i); - groups.add(groupId); - groupBuilder.addGroup(groupId, TestGroupTypeId.getRandomGroupTypeId(randomGenerator)); - } - - // add the group memberships - Set groupMemeberships = new LinkedHashSet<>(); - while (groupMemeberships.size() < membershipCount) { - PersonId personId = people.get(randomGenerator.nextInt(people.size())); - GroupId groupId = groups.get(randomGenerator.nextInt(groups.size())); - groupMemeberships.add(new MultiKey(groupId, personId)); - } - - for (MultiKey multiKey : groupMemeberships) { - GroupId groupId = multiKey.getKey(0); - PersonId personId = multiKey.getKey(1); - groupBuilder.addPersonToGroup(groupId, personId); - } - GroupsPluginData groupsPluginData = groupBuilder.build(); - Plugin groupPlugin = GroupsPlugin.getGroupPlugin(groupsPluginData); - builder.addPlugin(groupPlugin); - - // add the people plugin - PeoplePluginData.Builder peopleBuilder = PeoplePluginData.builder(); - for (PersonId personId : people) { - peopleBuilder.addPersonId(personId); - } - PeoplePluginData peoplePluginData = peopleBuilder.build(); - Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(peoplePluginData); - builder.addPlugin(peoplePlugin); - - - - // add the stochastics plugin - StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder().setSeed(randomGenerator.nextLong()).build(); - Plugin stochasticPlugin = StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData); - builder.addPlugin(stochasticPlugin); - - // add the action plugin - builder.addPlugin(testPlugin); - - // build and execute the engine - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - builder.setOutputConsumer(scenarioPlanCompletionObserver::handleOutput).build().execute(); - - // show that all actions were executed - if (!scenarioPlanCompletionObserver.allPlansExecuted()) { - throw new ContractException(TestError.TEST_EXECUTION_FAILURE); - } - } - -} diff --git a/gcm3/src/main/java/plugins/groups/testsupport/TestGroupPropertyId.java b/gcm3/src/main/java/plugins/groups/testsupport/TestGroupPropertyId.java deleted file mode 100644 index cb45c5344..000000000 --- a/gcm3/src/main/java/plugins/groups/testsupport/TestGroupPropertyId.java +++ /dev/null @@ -1,170 +0,0 @@ -package plugins.groups.testsupport; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.apache.commons.math3.random.RandomGenerator; - -import plugins.groups.support.GroupPropertyId; -import plugins.util.properties.PropertyDefinition; -import plugins.util.properties.TimeTrackingPolicy; - -public enum TestGroupPropertyId implements GroupPropertyId { - - GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK( - TestGroupTypeId.GROUP_TYPE_1, // - PropertyDefinition .builder()// - .setType(Boolean.class)// - .setDefaultValue(false)// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.DO_NOT_TRACK_TIME)// - .build()), // - GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK( - TestGroupTypeId.GROUP_TYPE_1, // - PropertyDefinition .builder()// - .setType(Integer.class)// - .setDefaultValue(0)// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.DO_NOT_TRACK_TIME)// - .build() // - ), // - GROUP_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK( - TestGroupTypeId.GROUP_TYPE_1, // - PropertyDefinition .builder()// - .setType(Double.class)// - .setDefaultValue(0.0)// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.DO_NOT_TRACK_TIME)// - .build() // - ), // - GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK( - TestGroupTypeId.GROUP_TYPE_2, // - PropertyDefinition .builder()// - .setType(Boolean.class)// - .setDefaultValue(false)// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME)// - .build() // - ), // - GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK( - TestGroupTypeId.GROUP_TYPE_2, // - PropertyDefinition .builder()// - .setType(Integer.class)// - .setDefaultValue(0)// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME)// - .build() // - ), // - GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK( - TestGroupTypeId.GROUP_TYPE_2, // - PropertyDefinition .builder()// - .setType(Double.class)// - .setDefaultValue(0.0)// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME)// - .build() // - ), // - GROUP_PROPERTY_3_1_BOOLEAN_IMMUTABLE_NO_TRACK( - TestGroupTypeId.GROUP_TYPE_3, // - PropertyDefinition .builder()// - .setType(Boolean.class)// - .setDefaultValue(false)// - .setPropertyValueMutability(false)// - .setTimeTrackingPolicy(TimeTrackingPolicy.DO_NOT_TRACK_TIME)// - .build() // - ), // - GROUP_PROPERTY_3_2_INTEGER_IMMUTABLE_NO_TRACK( - TestGroupTypeId.GROUP_TYPE_3, // - PropertyDefinition .builder()// - .setType(Integer.class)// - .setDefaultValue(0)// - .setPropertyValueMutability(false)// - .setTimeTrackingPolicy(TimeTrackingPolicy.DO_NOT_TRACK_TIME)// - .build() // - ), // - GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK( - TestGroupTypeId.GROUP_TYPE_3, // - PropertyDefinition .builder()// - .setType(Double.class)// - .setDefaultValue(0.0)// - .setPropertyValueMutability(false)// - .setTimeTrackingPolicy(TimeTrackingPolicy.DO_NOT_TRACK_TIME)// - .build() // - );// - - private final PropertyDefinition propertyDefinition; - private final TestGroupTypeId testGroupTypeId; - - /** - * Returns the property definition associated with this member - */ - public PropertyDefinition getPropertyDefinition() { - return propertyDefinition; - } - - private TestGroupPropertyId(TestGroupTypeId testGroupTypeId, PropertyDefinition propertyDefinition) { - this.testGroupTypeId = testGroupTypeId; - this.propertyDefinition = propertyDefinition; - } - - /** - * Returns the TestGroupTypeId that should be the type associated with the - * property - */ - public TestGroupTypeId getTestGroupTypeId() { - return testGroupTypeId; - } - - /** - * Returns the TestGroupPropertyId associated with the given TestGroupTypeId - * - * Preconditions: The TestGroupTypeId should not be null - */ - public static Set getTestGroupPropertyIds(TestGroupTypeId testGroupTypeId){ - Set result = new LinkedHashSet<>(); - for(TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { - if(testGroupPropertyId.testGroupTypeId==testGroupTypeId) { - result.add(testGroupPropertyId); - } - } - return result; - } - - /** - * Returns a unique GroupPropertyId instance that is not a member of this enumeration - */ - public static GroupPropertyId getUnknownGroupPropertyId() { - return new GroupPropertyId() {}; - } - - /** - * Returns a randomly selected value that is compatible with this member's - * associated property definition. - * - */ - public Object getRandomPropertyValue(final RandomGenerator randomGenerator) { - switch (this) { - case GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK: - return randomGenerator.nextBoolean(); - case GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK: - return randomGenerator.nextInt(); - case GROUP_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK: - return randomGenerator.nextDouble(); - case GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK: - return randomGenerator.nextBoolean(); - case GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK: - return randomGenerator.nextInt(); - case GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK: - return randomGenerator.nextDouble(); - case GROUP_PROPERTY_3_1_BOOLEAN_IMMUTABLE_NO_TRACK: - return randomGenerator.nextBoolean(); - case GROUP_PROPERTY_3_2_INTEGER_IMMUTABLE_NO_TRACK: - return randomGenerator.nextInt(); - case GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK: - return randomGenerator.nextDouble(); - default: - throw new RuntimeException("unhandled case: " + this); - - } - } -} diff --git a/gcm3/src/main/java/plugins/groups/testsupport/TestGroupTypeId.java b/gcm3/src/main/java/plugins/groups/testsupport/TestGroupTypeId.java deleted file mode 100644 index 11af4bbee..000000000 --- a/gcm3/src/main/java/plugins/groups/testsupport/TestGroupTypeId.java +++ /dev/null @@ -1,46 +0,0 @@ -package plugins.groups.testsupport; - -import org.apache.commons.math3.random.RandomGenerator; - -import plugins.groups.support.GroupTypeId; - -/** - * Enumeration of GroupTypeId to support unit testing - */ -public enum TestGroupTypeId implements GroupTypeId { - GROUP_TYPE_1, GROUP_TYPE_2, GROUP_TYPE_3; - - /** - * Returns a randomly selected member of this enumeration. - * - * Precondition: The random generator must not be null - */ - public static TestGroupTypeId getRandomGroupTypeId(final RandomGenerator randomGenerator) { - return TestGroupTypeId.values()[randomGenerator.nextInt(TestGroupTypeId.values().length)]; - } - - public static int size() { - return values().length; - } - - private TestGroupTypeId next; - - /** - * Returns the next member of this enumeration - */ - public TestGroupTypeId next() { - if (next == null) { - next = TestGroupTypeId.values()[(ordinal() + 1) % TestGroupTypeId.values().length]; - } - return next; - } - - /** - * Returns a new {@link GroupTypeId} instance. - */ - public static GroupTypeId getUnknownGroupTypeId() { - return new GroupTypeId() { - }; - } - -} diff --git a/gcm3/src/main/java/plugins/materials/MaterialsPlugin.java b/gcm3/src/main/java/plugins/materials/MaterialsPlugin.java deleted file mode 100644 index 222d0f7ae..000000000 --- a/gcm3/src/main/java/plugins/materials/MaterialsPlugin.java +++ /dev/null @@ -1,32 +0,0 @@ -package plugins.materials; - -import nucleus.Plugin; -import plugins.materials.datamangers.MaterialsDataManager; -import plugins.regions.RegionsPluginId; -import plugins.resources.ResourcesPluginId; - -/** - * A plugin providing a materials data manager to the simulation. - * - * @author Shawn Hatch - * - */ -public final class MaterialsPlugin { - private MaterialsPlugin() { - } - - public static Plugin getMaterialsPlugin(MaterialsPluginData materialsPluginData) { - - return Plugin .builder()// - .setPluginId(MaterialsPluginId.PLUGIN_ID)// - .addPluginData(materialsPluginData)// - .addPluginDependency(RegionsPluginId.PLUGIN_ID)// - .addPluginDependency(ResourcesPluginId.PLUGIN_ID)// - .setInitializer((c) -> { - MaterialsPluginData pluginData = c.getPluginData(MaterialsPluginData.class); - c.addDataManager(new MaterialsDataManager(pluginData)); - }).build(); - - } - -} diff --git a/gcm3/src/main/java/plugins/materials/MaterialsPluginData.java b/gcm3/src/main/java/plugins/materials/MaterialsPluginData.java deleted file mode 100644 index 099bd7e45..000000000 --- a/gcm3/src/main/java/plugins/materials/MaterialsPluginData.java +++ /dev/null @@ -1,1185 +0,0 @@ -package plugins.materials; - -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -import net.jcip.annotations.Immutable; -import nucleus.PluginData; -import nucleus.PluginDataBuilder; -import plugins.materials.support.BatchId; -import plugins.materials.support.BatchPropertyId; -import plugins.materials.support.MaterialId; -import plugins.materials.support.MaterialsError; -import plugins.materials.support.MaterialsProducerId; -import plugins.materials.support.MaterialsProducerPropertyId; -import plugins.materials.support.StageId; -import plugins.resources.support.ResourceError; -import plugins.resources.support.ResourceId; -import plugins.util.properties.PropertyDefinition; -import plugins.util.properties.PropertyError; -import util.errors.ContractException; - -/** - * An immutable container of the initial state materials producers. It contains: - *
    - *
      - *
    • material producer ids
    • - *
    • materials producer property definitions
    • - *
    • materials producer property values
    • - *
    • materials producer resource levels
    • - *
    • stage ids
    • - *
    • batch ids
    • - *
    • batch property definitions
    • - *
    • batch property values
    • - *
    • batch stage assignments
    • - *
    - * - * Construction is conducted via the contained builder class. Builder methods - * can be invoked in any order and relational validation is delayed until - * build() is invoked. - * - * @author Shawn Hatch - * - */ - -@Immutable -public final class MaterialsPluginData implements PluginData { - - /** - * Builder class for MaterialsInitialization - * - * @author Shawn Hatch - * - */ - public static class Builder implements PluginDataBuilder { - private Data data ; - - private Builder(Data data) { - this.data = data; - } - - /** - * Adds the batch. - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_BATCH_ID} if the - * batch id is null
  • - *
  • {@linkplain MaterialsError#NULL_MATERIAL_ID} if the - * material id is null
  • - *
  • {@linkplain MaterialsError#NON_FINITE_MATERIAL_AMOUNT} - * if the material amount is infinite
  • - *
  • {@linkplain MaterialsError#NEGATIVE_MATERIAL_AMOUNT} - * if the material amount is negative
  • - *
  • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} - * if the materials producer id is null
  • - */ - public Builder addBatch(final BatchId batchId, final MaterialId materialId, final double amount, final MaterialsProducerId materialsProducerId) { - validateBatchIdNotNull(batchId); - validateBatchDoesNotExist(data, batchId); - validateMaterialIdNotNull(materialId); - validateBatchAmount(amount); - validateMaterialsProducerIdNotNull(materialsProducerId); - data.batchIds.add(batchId); - data.batchMaterials.put(batchId, materialId); - data.batchMaterialsProducers.put(batchId, materialsProducerId); - data.batchAmounts.put(batchId, amount); - return this; - } - - /** - * Adds a batch to stage. - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_BATCH_ID} if the - * batch id is null
  • - *
  • {@linkplain MaterialsError#NULL_STAGE_ID} if the - * stage id is null
  • - */ - public Builder addBatchToStage(final StageId stageId, final BatchId batchId) { - validateStageIdNotNull(stageId); - validateBatchIdNotNull(batchId); - - Set batches = data.stageBatches.get(stageId); - if (batches == null) { - batches = new LinkedHashSet<>(); - data.stageBatches.put(stageId, batches); - } - batches.add(batchId); - data.batchStages.put(batchId, stageId); - - return this; - } - - /** - * Adds a batch to stage. - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_MATERIAL_ID} if the - * material id is null
  • - *
  • {@linkplain MaterialsError#DUPLICATE_MATERIAL} if the - * material was previously added
  • - */ - public Builder addMaterial(final MaterialId materialId) { - validateMaterialIdNotNull(materialId); - validateMaterialDoesNotExist(data, materialId); - data.materialIds.add(materialId); - return this; - } - - /** - * Adds a batch to stage. - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} - * if the material producer id is null
  • - * - */ - public Builder addMaterialsProducerId(final MaterialsProducerId materialsProducerId) { - validateMaterialsProducerIdNotNull(materialsProducerId); - data.materialsProducerIds.add(materialsProducerId); - return this; - } - - /** - * Adds a batch to stage. - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_STAGE_ID} if the - * stage id is null
  • - *
  • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} - * if the materials producer id is null
  • - */ - public Builder addStage(final StageId stageId, final boolean offered, final MaterialsProducerId materialsProducerId) { - validateStageIdNotNull(stageId); - validateMaterialsProducerIdNotNull(materialsProducerId); - data.stageIds.add(stageId); - data.stageMaterialsProducers.put(stageId, materialsProducerId); - data.stageOffers.put(stageId, offered); - return this; - } - - /** - * Set the materials producer resource value. - * - * @throws ContractException - *
  • {@linkplain MaterialsError#UNKNOWN_MATERIAL_ID} if a - * batch property is associated with a material id that was - * not properly added
  • - *
  • {@linkplain MaterialsError#PROPERTY_DEFINITION_REQUIRES_DEFAULT} - * if a batch property is defined without a default - * value
  • - *
  • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} - * if a materials property value is associated with a - * materials producer id that was not properly added
  • - *
  • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_PROPERTY_ID} - * if a materials property value is associated with a - * materials producer property id that was not properly - * defined
  • - *
  • {@linkplain MaterialsError#INCOMPATIBLE_MATERIALS_PRODUCER_PROPERTY_VALUE} - * if a materials property value is associated with a value - * that is not compatible with the corresponding property - * definition
  • - *
  • {@linkplain MaterialsError#INSUFFICIENT_MATERIALS_PRODUCER_PROPERTY_VALUE_ASSIGNMENT} - * if a materials property is defined without a default - * value and there is not an assigned property value for - * each added materials producer
  • - *
  • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} - * if a materials resource level is set for a material - * producer id that was not properly added
  • - *
  • {@linkplain MaterialsError#UNKNOWN_MATERIAL_ID} if a - * batch is associated with at material that was not - * properly added
  • - *
  • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} - * if a batch is associated with at material producer that - * was not properly added
  • - *
  • {@linkplain MaterialsError#UNKNOWN_BATCH_ID} if a - * batch property is associated with batch id that was not - * properly added
  • - *
  • {@linkplain MaterialsError#UNKNOWN_BATCH_PROPERTY_ID} - * if a batch property is associated with batch property id - * that was not properly defined
  • - *
  • {@linkplain MaterialsError#INCOMPATIBLE_BATCH_PROPERTY_VALUE} - * if a batch property value is incompatible with the - * corresponding property definition
  • - *
  • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} - * if a stage is associated with a materials producer id - * that was not properly added
  • - *
  • {@linkplain MaterialsError#UNKNOWN_STAGE_ID} if a - * batch is associated with a stage id that was not properly - * added
  • - *
  • {@linkplain MaterialsError#UNKNOWN_BATCH_ID} if a - * stage is associated with a batch id that was not properly - * added
  • - *
  • {@linkplain MaterialsError#BATCH_ALREADY_STAGED} if a - * batch is associated with more than one stage
  • - *
  • {@linkplain MaterialsError#BATCH_STAGED_TO_DIFFERENT_OWNER} if a - * batch is associated with a stage that is not owned by the - * same materials producer as the batch
  • - */ - - public MaterialsPluginData build() { - try { - validateData(data); - return new MaterialsPluginData(data); - } finally { - data = new Data(); - } - } - - /** - * Adds a batch to stage. - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_BATCH_PROPERTY_ID} if - * the batch property id is null
  • - *
  • {@linkplain MaterialsError#NULL_MATERIAL_ID} if the - * material id is null
  • - *
  • {@linkplain PropertyError#NULL_PROPERTY_DEFINITION} - * if the property definition is null
  • - *
  • {@linkplain MaterialsError#DUPLICATE_BATCH_PROPERTY_DEFINITION} - * if the property definition was previously defined
  • - * - */ - public Builder defineBatchProperty(final MaterialId materialId, final BatchPropertyId batchPropertyId, final PropertyDefinition propertyDefinition) { - validateBatchPropertyIdNotNull(batchPropertyId); - validateMaterialIdNotNull(materialId); - validatePropertyDefinitionNotNull(propertyDefinition); - validateBatchPropertyIsNotDefined(data, materialId, batchPropertyId); - Map propertyDefinitionsMap = data.batchPropertyDefinitions.get(materialId); - if (propertyDefinitionsMap == null) { - propertyDefinitionsMap = new LinkedHashMap<>(); - data.batchPropertyDefinitions.put(materialId, propertyDefinitionsMap); - } - propertyDefinitionsMap.put(batchPropertyId, propertyDefinition); - return this; - } - - /** - * Adds a batch to stage. - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_PROPERTY_ID} - * if the materials producer property id is null
  • - *
  • {@linkplain PropertyError#NULL_PROPERTY_DEFINITION} - * if the property definition is null
  • - *
  • {@linkplain MaterialsError#DUPLICATE_MATERIALS_PRODUCER_PROPERTY_DEFINITION} - * if the materials producer property was previously - * defined
  • - */ - public Builder defineMaterialsProducerProperty(final MaterialsProducerPropertyId materialsProducerPropertyId, final PropertyDefinition propertyDefinition) { - validateMaterialsProducerPropertyIdNotNull(materialsProducerPropertyId); - validatePropertyDefinitionNotNull(propertyDefinition); - validateMaterialsProducerPropertyIsNotDefined(data, materialsProducerPropertyId); - data.materialsProducerPropertyDefinitions.put(materialsProducerPropertyId, propertyDefinition); - return this; - } - - /** - * Set the batch property value. - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_BATCH_ID} if the - * batch id is null
  • - *
  • {@linkplain MaterialsError#NULL_BATCH_PROPERTY_ID} if - * the batch property id is null
  • - *
  • {@linkplain MaterialsError#NULL_BATCH_PROPERTY_VALUE} - * if the batch property value is null
  • - *
  • {@linkplain MaterialsError#DUPLICATE_BATCH_PROPERTY_VALUE_ASSIGNMENT} - * if the batch property value was previously set
  • - */ - public Builder setBatchPropertyValue(final BatchId batchId, final BatchPropertyId batchPropertyId, final Object batchPropertyValue) { - validateBatchIdNotNull(batchId); - validateBatchPropertyIdNotNull(batchPropertyId); - validateBatchPropertyValueNotNull(batchPropertyValue); - validateBatchPropertyValueNotSet(data, batchId, batchPropertyId); - Map propertyMap = data.batchPropertyValues.get(batchId); - if (propertyMap == null) { - propertyMap = new LinkedHashMap<>(); - data.batchPropertyValues.put(batchId, propertyMap); - } - propertyMap.put(batchPropertyId, batchPropertyValue); - return this; - } - - /** - * Set the materials producer property value. - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} - * if the materials producer id is null
  • - *
  • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_PROPERTY_ID} - * if the materials producer property id is null
  • - *
  • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_PROPERTY_VALUE} - * if the materials producer property value is null
  • - *
  • {@linkplain MaterialsError#DUPLICATE_MATERIALS_PRODUCER_PROPERTY_VALUE_ASSIGNMENT} - * if the materials producer property value was previously - * set
  • - */ - public Builder setMaterialsProducerPropertyValue(final MaterialsProducerId materialsProducerId, final MaterialsProducerPropertyId materialsProducerPropertyId, - final Object materialsProducerPropertyValue) { - validateMaterialsProducerIdNotNull(materialsProducerId); - validateMaterialsProducerPropertyIdNotNull(materialsProducerPropertyId); - validateMaterialsProducerPropertyValueNotNull(materialsProducerPropertyValue); - validateMaterialsProducerPropertyNotSet(data, materialsProducerId, materialsProducerPropertyId); - Map propertyMap = data.materialsProducerPropertyValues.get(materialsProducerId); - if (propertyMap == null) { - propertyMap = new LinkedHashMap<>(); - data.materialsProducerPropertyValues.put(materialsProducerId, propertyMap); - } - propertyMap.put(materialsProducerPropertyId, materialsProducerPropertyValue); - return this; - } - - /** - * Set the materials producer resource value. - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} - * if the materials producer id is null
  • - *
  • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
  • - *
  • {@linkplain ResourceError#NEGATIVE_RESOURCE_AMOUNT} - * if the resource amount is negative
  • - *
  • {@linkplain MaterialsError#DUPLICATE_MATERIALS_PRODUCER_RESOURCE_ASSIGNMENT} - * if the materials producer resource level was previously - * set
  • - */ - public Builder setMaterialsProducerResourceLevel(final MaterialsProducerId materialsProducerId, final ResourceId resourceId, final long amount) { - validateMaterialsProducerIdNotNull(materialsProducerId); - validateResourceIdNotNull(resourceId); - validateResourceAmount(amount); - validateMaterialsProducerResourceLevelNotSet(data, materialsProducerId, resourceId); - - Map resourceLevelMap = data.materialsProducerResourceLevels.get(materialsProducerId); - if (resourceLevelMap == null) { - resourceLevelMap = new LinkedHashMap<>(); - data.materialsProducerResourceLevels.put(materialsProducerId, resourceLevelMap); - } - resourceLevelMap.put(resourceId, amount); - return this; - } - - } - - private static class Data { - /* - * The following members are arranged in dependency order to make - * reviewing a bit easier - */ - private final Set materialsProducerIds; - - private final Set materialIds; - - private final Map> batchPropertyDefinitions; - - private final Map materialsProducerPropertyDefinitions; - - private final Map> materialsProducerPropertyValues; - - private final Map> materialsProducerResourceLevels; - - private final Set batchIds; - - private final Map batchMaterials; - - private final Map batchAmounts; - - private final Map batchMaterialsProducers; - - private final Map> batchPropertyValues; - - private final Set stageIds; - - private final Map stageOffers; - - private final Map stageMaterialsProducers; - - private final Map> stageBatches; - - private final Map batchStages; - - - public Data() { - materialsProducerIds = new LinkedHashSet<>(); - - materialIds = new LinkedHashSet<>(); - - batchPropertyDefinitions = new LinkedHashMap<>(); - - materialsProducerPropertyDefinitions = new LinkedHashMap<>(); - - materialsProducerPropertyValues = new LinkedHashMap<>(); - - materialsProducerResourceLevels = new LinkedHashMap<>(); - - batchIds = new LinkedHashSet<>(); - - batchMaterials = new LinkedHashMap<>(); - - batchAmounts = new LinkedHashMap<>(); - - batchMaterialsProducers = new LinkedHashMap<>(); - - batchPropertyValues = new LinkedHashMap<>(); - - stageIds = new LinkedHashSet<>(); - - stageOffers = new LinkedHashMap<>(); - - stageMaterialsProducers = new LinkedHashMap<>(); - - stageBatches = new LinkedHashMap<>(); - - batchStages = new LinkedHashMap<>(); - - } - public Data (Data data) { - - materialsProducerIds = new LinkedHashSet<>(data.materialsProducerIds); - - materialIds = new LinkedHashSet<>(data.materialIds); - - batchPropertyDefinitions = new LinkedHashMap<>(); - for(MaterialId materialId : data.batchPropertyDefinitions.keySet()) { - Map map = data.batchPropertyDefinitions.get(materialId); - Map newMap = new LinkedHashMap<>(map); - batchPropertyDefinitions.put(materialId, newMap); - } - - materialsProducerPropertyDefinitions = new LinkedHashMap<>(data.materialsProducerPropertyDefinitions); - - materialsProducerPropertyValues = new LinkedHashMap<>(); - for( MaterialsProducerId materialsProducerId : data.materialsProducerPropertyValues.keySet()) { - Map map = data.materialsProducerPropertyValues.get(materialsProducerId); - Map newMap = new LinkedHashMap<>(map); - materialsProducerPropertyValues.put(materialsProducerId, newMap); - } - - materialsProducerResourceLevels = new LinkedHashMap<>(); - for(MaterialsProducerId materialsProducerId : data.materialsProducerResourceLevels.keySet()) { - Map map = data.materialsProducerResourceLevels.get(materialsProducerId); - Map newMap = new LinkedHashMap<>(map); - materialsProducerResourceLevels.put(materialsProducerId, newMap); - } - - batchIds = new LinkedHashSet<>(data.batchIds); - - batchMaterials = new LinkedHashMap<>(data.batchMaterials); - - batchAmounts = new LinkedHashMap<>(data.batchAmounts); - - batchMaterialsProducers = new LinkedHashMap<>(data.batchMaterialsProducers); - - batchPropertyValues = new LinkedHashMap<>(); - for(BatchId batchId : data.batchPropertyValues.keySet()) { - Map map = data.batchPropertyValues.get(batchId); - Map newMap = new LinkedHashMap<>(map); - batchPropertyValues.put(batchId, newMap); - } - - stageIds = new LinkedHashSet<>(data.stageIds); - - stageOffers = new LinkedHashMap<>(data.stageOffers); - - stageMaterialsProducers = new LinkedHashMap<>(data.stageMaterialsProducers); - - stageBatches = new LinkedHashMap<>(); - for(StageId stageId : data.stageBatches.keySet()) { - Set set = data.stageBatches.get(stageId); - Set newSet = new LinkedHashSet<>(set); - stageBatches.put(stageId, newSet); - } - - - batchStages = new LinkedHashMap<>(data.batchStages); - - } - - } - - /** - * Returns a new builder instance - */ - public static Builder builder() { - return new Builder(new Data()); - } - - private static void validateBatchAmount(final double amount) { - if (!Double.isFinite(amount)) { - throw new ContractException(MaterialsError.NON_FINITE_MATERIAL_AMOUNT, amount); - } - if (amount < 0) { - throw new ContractException(MaterialsError.NEGATIVE_MATERIAL_AMOUNT, amount); - } - - } - - - private static void validateBatchDoesNotExist(final Data data, final Object batchId) { - - if (data.batchIds.contains(batchId)) { - throw new ContractException(MaterialsError.DUPLICATE_BATCH_ID, batchId); - } - } - - private static void validateBatchExists(final Data data, final Object batchId) { - - if (!data.batchIds.contains(batchId)) { - throw new ContractException(MaterialsError.UNKNOWN_BATCH_ID, batchId); - } - } - - private static void validateBatchIdNotNull(final BatchId batchId) { - if (batchId == null) { - throw new ContractException(MaterialsError.NULL_BATCH_ID); - } - } - - private static void validateBatchPropertyIdNotNull(final BatchPropertyId batchPropertyId) { - if (batchPropertyId == null) { - throw new ContractException(MaterialsError.NULL_BATCH_PROPERTY_ID); - } - } - - private static void validateBatchPropertyIsDefined(final Data data, final MaterialId materialId, final BatchPropertyId batchPropertyId) { - validateBatchPropertyIdNotNull(batchPropertyId); - final Map map = data.batchPropertyDefinitions.get(materialId); - if (map == null) { - throw new ContractException(MaterialsError.UNKNOWN_BATCH_PROPERTY_ID, batchPropertyId); - } - final PropertyDefinition propertyDefinition = map.get(batchPropertyId); - if (propertyDefinition == null) { - throw new ContractException(MaterialsError.UNKNOWN_BATCH_PROPERTY_ID, batchPropertyId); - } - } - - private static void validateBatchPropertyIsNotDefined(final Data data, final MaterialId materialId, final BatchPropertyId batchPropertyId) { - final Map map = data.batchPropertyDefinitions.get(materialId); - if (map != null) { - final PropertyDefinition propertyDefinition = map.get(batchPropertyId); - if (propertyDefinition != null) { - throw new ContractException(MaterialsError.DUPLICATE_BATCH_PROPERTY_DEFINITION, batchPropertyId); - } - } - } - - private static void validateBatchPropertyValueNotNull(final Object batchPropertyValue) { - if (batchPropertyValue == null) { - throw new ContractException(MaterialsError.NULL_BATCH_PROPERTY_VALUE); - } - } - - private static void validateBatchPropertyValueNotSet(final Data data, final BatchId batchId, final BatchPropertyId batchPropertyId) { - final Map propertyMap = data.batchPropertyValues.get(batchId); - if (propertyMap != null) { - if (propertyMap.containsKey(batchPropertyId)) { - throw new ContractException(MaterialsError.DUPLICATE_BATCH_PROPERTY_VALUE_ASSIGNMENT, batchId + ": " + batchPropertyId); - } - } - } - - - private static void validateData(final Data data) { - - for (final MaterialId materialId : data.batchPropertyDefinitions.keySet()) { - if (!data.materialIds.contains(materialId)) { - throw new ContractException(MaterialsError.UNKNOWN_MATERIAL_ID, materialId + " in batch property definitions"); - } - } - - /* - * All batch property definitions must have default values since batches - * may be created dynamically in the simulation - */ - - for (final MaterialId materialId : data.materialIds) { - final Map propertyDefinitionMap = data.batchPropertyDefinitions.get(materialId); - if (propertyDefinitionMap != null) { - for (final BatchPropertyId batchPropertyId : propertyDefinitionMap.keySet()) { - final PropertyDefinition propertyDefinition = propertyDefinitionMap.get(batchPropertyId); - if (!propertyDefinition.getDefaultValue().isPresent()) { - throw new ContractException(MaterialsError.PROPERTY_DEFINITION_REQUIRES_DEFAULT, batchPropertyId); - } - } - } - } - - for (final MaterialsProducerId materialsProducerId : data.materialsProducerPropertyValues.keySet()) { - if (!data.materialsProducerIds.contains(materialsProducerId)) { - throw new ContractException(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, materialsProducerId + " in materials producer property values"); - } - final Map propMap = data.materialsProducerPropertyValues.get(materialsProducerId); - for (final MaterialsProducerPropertyId materialsProducerPropertyId : propMap.keySet()) { - final PropertyDefinition propertyDefinition = data.materialsProducerPropertyDefinitions.get(materialsProducerPropertyId); - if (propertyDefinition == null) { - throw new ContractException(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_PROPERTY_ID, materialsProducerId + " in materials producer property values"); - } - final Object propertyValue = propMap.get(materialsProducerPropertyId); - if (!propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())) { - throw new ContractException(MaterialsError.INCOMPATIBLE_MATERIALS_PRODUCER_PROPERTY_VALUE, materialsProducerId + ": " + materialsProducerPropertyId + ": " + propertyValue); - } - } - } - - /* - * For every materials producer property definition that has a null - * default value, ensure that all corresponding materials producer - * property values are not null and repair the definition. - */ - - for (final MaterialsProducerPropertyId materialsProducerPropertyId : data.materialsProducerPropertyDefinitions.keySet()) { - final PropertyDefinition propertyDefinition = data.materialsProducerPropertyDefinitions.get(materialsProducerPropertyId); - if (!propertyDefinition.getDefaultValue().isPresent()) { - for (final MaterialsProducerId materialsProducerId : data.materialsProducerIds) { - Object propertyValue = null; - final Map propertyValueMap = data.materialsProducerPropertyValues.get(materialsProducerId); - if (propertyValueMap != null) { - propertyValue = propertyValueMap.get(materialsProducerPropertyId); - - } - if (propertyValue == null) { - throw new ContractException(MaterialsError.INSUFFICIENT_MATERIALS_PRODUCER_PROPERTY_VALUE_ASSIGNMENT, materialsProducerPropertyId); - } - } - } - } - - for (final MaterialsProducerId materialsProducerId : data.materialsProducerResourceLevels.keySet()) { - if (!data.materialsProducerIds.contains(materialsProducerId)) { - throw new ContractException(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, materialsProducerId + " in materials producer resource levels"); - } - } - - for (final BatchId batchId : data.batchMaterials.keySet()) { - final MaterialId materialId = data.batchMaterials.get(batchId); - if (!data.materialIds.contains(materialId)) { - throw new ContractException(MaterialsError.UNKNOWN_MATERIAL_ID, materialId + " in batch addition of " + batchId); - } - } - - for (final BatchId batchId : data.batchMaterialsProducers.keySet()) { - final MaterialsProducerId materialsProducerId = data.batchMaterialsProducers.get(batchId); - if (!data.materialsProducerIds.contains(materialsProducerId)) { - throw new ContractException(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, materialsProducerId + " in batch addition of " + batchId); - } - } - - for (final BatchId batchId : data.batchPropertyValues.keySet()) { - if (!data.batchIds.contains(batchId)) { - throw new ContractException(MaterialsError.UNKNOWN_BATCH_ID, batchId + " in batch property values"); - } - - final MaterialId materialId = data.batchMaterials.get(batchId); - final Map defMap = data.batchPropertyDefinitions.get(materialId); - - final Map propMap = data.batchPropertyValues.get(batchId); - for (final BatchPropertyId batchPropertyId : propMap.keySet()) { - if(defMap == null) { - throw new ContractException(MaterialsError.UNKNOWN_BATCH_PROPERTY_ID, batchPropertyId + " in batch property values"); - } - final PropertyDefinition propertyDefinition = defMap.get(batchPropertyId); - if (propertyDefinition == null) { - throw new ContractException(MaterialsError.UNKNOWN_BATCH_PROPERTY_ID, batchPropertyId + " in batch property values"); - } - final Object propertyValue = propMap.get(batchPropertyId); - if (!propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())) { - throw new ContractException(MaterialsError.INCOMPATIBLE_BATCH_PROPERTY_VALUE, batchId + ": " + batchPropertyId + ": " + propertyValue); - } - } - } - - for (final StageId stageId : data.stageMaterialsProducers.keySet()) { - final MaterialsProducerId materialsProducerId = data.stageMaterialsProducers.get(stageId); - if (!data.materialsProducerIds.contains(materialsProducerId)) { - throw new ContractException(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, stageId + " in stage additions"); - } - } - - for (final StageId stageId : data.stageBatches.keySet()) { - if (!data.stageIds.contains(stageId)) { - throw new ContractException(MaterialsError.UNKNOWN_STAGE_ID, stageId + " in batch additions to stages"); - } - final Set batches = data.stageBatches.get(stageId); - for (final BatchId batchId : batches) { - if (!data.batchIds.contains(batchId)) { - throw new ContractException(MaterialsError.UNKNOWN_BATCH_ID, stageId + ": " + batchId + " in batch additions to stages"); - } - } - } - - for (final BatchId batchId : data.batchStages.keySet()) { - if (!data.batchIds.contains(batchId)) { - throw new ContractException(MaterialsError.UNKNOWN_BATCH_ID, batchId + " in batch additions to stages"); - } - final StageId stageId = data.batchStages.get(batchId); - if (!data.stageIds.contains(stageId)) { - throw new ContractException(MaterialsError.UNKNOWN_STAGE_ID, stageId + " in batch additions to stages"); - } - } - - for (final StageId stageId : data.stageBatches.keySet()) { - final Set batches = data.stageBatches.get(stageId); - for (final BatchId batchId : batches) { - final StageId linkedStageId = data.batchStages.get(batchId); - if (!linkedStageId.equals(stageId)) { - throw new ContractException(MaterialsError.BATCH_ALREADY_STAGED, batchId + " has been assigned to multiple stages"); - } - } - } - for (final BatchId batchId : data.batchStages.keySet()) { - final StageId stageId = data.batchStages.get(batchId); - final MaterialsProducerId batchMaterialsProducerId = data.batchMaterialsProducers.get(batchId); - final MaterialsProducerId stageMaterialsProducerId = data.stageMaterialsProducers.get(stageId); - if (!batchMaterialsProducerId.equals(stageMaterialsProducerId)) { - throw new ContractException(MaterialsError.BATCH_STAGED_TO_DIFFERENT_OWNER, stageId + ": " + batchId); - } - } - - } - - private static void validateMaterialDoesNotExist(final Data data, final MaterialId materialId) { - if (data.materialIds.contains(materialId)) { - throw new ContractException(MaterialsError.DUPLICATE_MATERIAL, materialId); - } - } - - private static void validateMaterialExists(final Data data, final MaterialId materialId) { - if (materialId == null) { - throw new ContractException(MaterialsError.NULL_MATERIAL_ID); - } - if (!data.materialIds.contains(materialId)) { - throw new ContractException(MaterialsError.UNKNOWN_MATERIAL_ID, materialId); - } - } - - private static void validateMaterialIdNotNull(final MaterialId materialId) { - if (materialId == null) { - throw new ContractException(MaterialsError.NULL_MATERIAL_ID); - } - } - - private static void validateMaterialsProducerExists(final Data data, final MaterialsProducerId materialsProducerId) { - if (materialsProducerId == null) { - throw new ContractException(MaterialsError.NULL_MATERIALS_PRODUCER_ID); - } - if (!data.materialsProducerIds.contains(materialsProducerId)) { - throw new ContractException(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, materialsProducerId); - } - } - - private static void validateMaterialsProducerIdNotNull(final MaterialsProducerId materialsProducerId) { - if (materialsProducerId == null) { - throw new ContractException(MaterialsError.NULL_MATERIALS_PRODUCER_ID); - } - } - - private static void validateMaterialsProducerPropertyIdNotNull(final MaterialsProducerPropertyId materialsProducerPropertyId) { - if (materialsProducerPropertyId == null) { - throw new ContractException(MaterialsError.NULL_MATERIALS_PRODUCER_PROPERTY_ID); - } - } - - private static void validateMaterialsProducerPropertyIsDefined(final Data data, final MaterialsProducerPropertyId materialsProducerPropertyId) { - validateMaterialsProducerPropertyIdNotNull(materialsProducerPropertyId); - final PropertyDefinition propertyDefinition = data.materialsProducerPropertyDefinitions.get(materialsProducerPropertyId); - if (propertyDefinition == null) { - throw new ContractException(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_PROPERTY_ID, materialsProducerPropertyId); - } - } - - private static void validateMaterialsProducerPropertyIsNotDefined(final Data data, final MaterialsProducerPropertyId materialsProducerPropertyId) { - if (data.materialsProducerPropertyDefinitions.containsKey(materialsProducerPropertyId)) { - throw new ContractException(MaterialsError.DUPLICATE_MATERIALS_PRODUCER_PROPERTY_DEFINITION, materialsProducerPropertyId); - } - } - - private static void validateMaterialsProducerPropertyNotSet(final Data data, final MaterialsProducerId materialsProducerId, final MaterialsProducerPropertyId materialsProducerPropertyId) { - final Map propertyMap = data.materialsProducerPropertyValues.get(materialsProducerId); - if (propertyMap != null) { - if (propertyMap.containsKey(materialsProducerPropertyId)) { - throw new ContractException(MaterialsError.DUPLICATE_MATERIALS_PRODUCER_PROPERTY_VALUE_ASSIGNMENT, materialsProducerId + ": " + materialsProducerPropertyId); - } - } - } - - private static void validateMaterialsProducerPropertyValueNotNull(final Object materialsProducerPropertyValue) { - if (materialsProducerPropertyValue == null) { - throw new ContractException(MaterialsError.NULL_MATERIALS_PRODUCER_PROPERTY_VALUE); - } - } - - private static void validateMaterialsProducerResourceLevelNotSet(final Data data, final MaterialsProducerId materialsProducerId, final ResourceId resourceId) { - final Map resourceLevelMap = data.materialsProducerResourceLevels.get(materialsProducerId); - if (resourceLevelMap != null) { - if (resourceLevelMap.containsKey(resourceId)) { - throw new ContractException(MaterialsError.DUPLICATE_MATERIALS_PRODUCER_RESOURCE_ASSIGNMENT, materialsProducerId + ": " + resourceId); - } - } - } - - private static void validatePropertyDefinitionNotNull(final PropertyDefinition propertyDefinition) { - if (propertyDefinition == null) { - throw new ContractException(PropertyError.NULL_PROPERTY_DEFINITION); - } - } - - private static void validateResourceAmount(final long amount) { - if (amount < 0) { - throw new ContractException(ResourceError.NEGATIVE_RESOURCE_AMOUNT); - } - } - - private static void validateResourceIdNotNull(final ResourceId resourceId) { - if (resourceId == null) { - throw new ContractException(ResourceError.NULL_RESOURCE_ID); - } - } - - private static void validateStageExists(final Data data, final StageId stageId) { - validateStageIdNotNull(stageId); - if (!data.stageIds.contains(stageId)) { - throw new ContractException(MaterialsError.UNKNOWN_STAGE_ID, stageId); - } - } - - private static void validateStageIdNotNull(final StageId stageId) { - if (stageId == null) { - throw new ContractException(MaterialsError.NULL_STAGE_ID); - } - } - - private final Data data; - - private MaterialsPluginData(final Data data) { - this.data = data; - } - - /** - * Returns the material amount for the given batch. - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_BATCH_ID} if the batch id - * is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_BATCH_ID} if the batch - * id is unknown
  • - */ - public Double getBatchAmount(final BatchId batchId) { - validateBatchIdNotNull(batchId); - validateBatchExists(data, batchId); - return data.batchAmounts.get(batchId); - } - - /** - * Returns the collected batch ids. - * - */ - public Set getBatchIds() { - return new LinkedHashSet<>(data.batchIds); - } - - /** - * Returns the material type for the given batch id. - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_BATCH_ID} if the batch id - * is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_BATCH_ID} if the batch - * id is unknown
  • - */ - @SuppressWarnings("unchecked") - public T getBatchMaterial(final BatchId batchId) { - validateBatchIdNotNull(batchId); - validateBatchExists(data, batchId); - return (T) data.batchMaterials.get(batchId); - } - - /** - * Returns the materials producer id for the given batch id. - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_BATCH_ID} if the batch id - * is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_BATCH_ID} if the batch - * id is unknown
  • - */ - @SuppressWarnings("unchecked") - public T getBatchMaterialsProducer(final BatchId batchId) { - validateBatchIdNotNull(batchId); - validateBatchExists(data, batchId); - return (T) data.batchMaterialsProducers.get(batchId); - } - - /** - * Returns the property definition for the given batch property id and - * material id - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_MATERIAL_ID} if the - * material id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_MATERIAL_ID} if the - * material id is unknown
  • - *
  • {@linkplain MaterialsError#NULL_BATCH_PROPERTY_ID} if the - * batch property id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_BATCH_PROPERTY_ID} if - * the batch property id is unknown
  • - */ - public PropertyDefinition getBatchPropertyDefinition(final MaterialId materialId, final BatchPropertyId batchPropertyId) { - validateMaterialExists(data, materialId); - validateBatchPropertyIsDefined(data, materialId, batchPropertyId); - - final Map map = data.batchPropertyDefinitions.get(materialId); - final PropertyDefinition propertyDefinition = map.get(batchPropertyId); - return propertyDefinition; - } - - /** - * Returns the property ids associated with the given material id - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_MATERIAL_ID} if the - * material id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_MATERIAL_ID} if the - * material id is unknown
  • - */ - @SuppressWarnings("unchecked") - public Set getBatchPropertyIds(final MaterialId materialId) { - validateMaterialExists(data, materialId); - final Set result = new LinkedHashSet<>(); - final Map map = data.batchPropertyDefinitions.get(materialId); - if (map != null) { - final Set batchPropertyIds = map.keySet(); - for (final BatchPropertyId batchPropertyId : batchPropertyIds) { - result.add((T) batchPropertyId); - } - } - return result; - } - - /** - * Returns the property value associated with the given batch id and batch - * property id - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_BATCH_ID} if the batch id - * is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_BATCH_ID} if the batch - * id is unknown
  • - *
  • {@linkplain MaterialsError#NULL_BATCH_PROPERTY_ID} if the - * batch property id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_BATCH_PROPERTY_ID} if - * the batch property id is unknown
  • - */ - @SuppressWarnings("unchecked") - public T getBatchPropertyValue(final BatchId batchId, final BatchPropertyId batchPropertyId) { - validateBatchIdNotNull(batchId); - validateBatchExists(data, batchId); - - final MaterialId materialId = data.batchMaterials.get(batchId); - validateBatchPropertyIsDefined(data, materialId, batchPropertyId); - - Object result = null; - final Map map = data.batchPropertyValues.get(batchId); - if (map != null) { - result = map.get(batchPropertyId); - } - if (result == null) { - final Map defMap = data.batchPropertyDefinitions.get(materialId); - final PropertyDefinition propertyDefinition = defMap.get(batchPropertyId); - if (propertyDefinition.getDefaultValue().isPresent()) { - result = propertyDefinition.getDefaultValue().get(); - } - } - return (T) result; - } - - /** - * Returns the collected material ids - * - */ - @SuppressWarnings("unchecked") - public Set getMaterialIds() { - final Set result = new LinkedHashSet<>(data.materialIds.size()); - for (final MaterialId materialId : data.materialIds) { - result.add((T) materialId); - } - return result; - } - - /** - * Returns the collected material producer ids - */ - @SuppressWarnings("unchecked") - public Set getMaterialsProducerIds() { - final Set result = new LinkedHashSet<>(data.materialsProducerIds.size()); - for (final MaterialsProducerId materialsProducerId : data.materialsProducerIds) { - result.add((T) materialsProducerId); - } - return result; - } - - /** - * Returns the property definition associated with the given materials - * producer property id - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_PROPERTY_ID} - * if the materials producer property id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_PROPERTY_ID} - * if the materials producer property id is unknown
  • - */ - public PropertyDefinition getMaterialsProducerPropertyDefinition(final MaterialsProducerPropertyId materialsProducerPropertyId) { - validateMaterialsProducerPropertyIdNotNull(materialsProducerPropertyId); - - final PropertyDefinition propertyDefinition = data.materialsProducerPropertyDefinitions.get(materialsProducerPropertyId); - if (propertyDefinition == null) { - throw new ContractException(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_PROPERTY_ID, materialsProducerPropertyId); - } - return propertyDefinition; - } - - /** - * Returns the materials producer property ids - * - */ - @SuppressWarnings("unchecked") - public Set getMaterialsProducerPropertyIds() { - final Set result = new LinkedHashSet<>(); - for (final MaterialsProducerPropertyId materialsProducerPropertyId : data.materialsProducerPropertyDefinitions.keySet()) { - result.add((T) materialsProducerPropertyId); - } - return result; - } - - /** - * Returns the property value for the given materials producer id and - * materials producer property id - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} if - * the materials producer id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} - * if the materials producer id is unknown
  • - *
  • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_PROPERTY_ID} - * if the materials producer property id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_PROPERTY_ID} - * if the materials producer property id is unknown
  • - */ - @SuppressWarnings("unchecked") - public T getMaterialsProducerPropertyValue(final MaterialsProducerId materialsProducerId, final MaterialsProducerPropertyId materialsProducerPropertyId) { - validateMaterialsProducerExists(data, materialsProducerId); - validateMaterialsProducerPropertyIsDefined(data, materialsProducerPropertyId); - Object result = null; - final Map map = data.materialsProducerPropertyValues.get(materialsProducerId); - if (map != null) { - result = map.get(materialsProducerPropertyId); - } - if (result == null) { - final PropertyDefinition propertyDefinition = data.materialsProducerPropertyDefinitions.get(materialsProducerPropertyId); - result = propertyDefinition.getDefaultValue().get(); - } - return (T) result; - } - - /** - * Returns the resource level the given materials producer id and resource. - * - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} if - * the materials producer id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} - * if the materials producer id is unknown
  • - *
  • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is unknown
  • - */ - public Long getMaterialsProducerResourceLevel(final MaterialsProducerId materialsProducerId, final ResourceId resourceId) { - validateMaterialsProducerExists(data, materialsProducerId); - validateResourceIdNotNull(resourceId); - Long result = null; - final Map map = data.materialsProducerResourceLevels.get(materialsProducerId); - if (map != null) { - result = map.get(resourceId); - } - if (result == null) { - result = 0L; - } - return result; - } - - /** - * Returns the collected resource ids - */ - public Set getResourceIds() { - final Set result = new LinkedHashSet<>(); - for (final MaterialsProducerId materialsProducerId : data.materialsProducerResourceLevels.keySet()) { - final Map map = data.materialsProducerResourceLevels.get(materialsProducerId); - result.addAll(map.keySet()); - } - return result; - } - - /** - * Returns the batch ids that are assigned to the given stage id. - * - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_STAGE_ID} if the stage id - * is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_STAGE_ID} if the stage - * id is unknown
  • - */ - public Set getStageBatches(final StageId stageId) { - validateStageExists(data, stageId); - final Set result = new LinkedHashSet<>(); - final Set set = data.stageBatches.get(stageId); - if (set != null) { - result.addAll(set); - } - return result; - } - - /** - * Returns the collected stage ids - */ - public Set getStageIds() { - return new LinkedHashSet<>(data.stageIds); - } - - /** - * Returns the materials producer id associated with the given stage id. - * - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_STAGE_ID} if the stage id - * is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_STAGE_ID} if the stage - * id is unknown
  • - */ - @SuppressWarnings("unchecked") - public T getStageMaterialsProducer(final StageId stageId) { - validateStageExists(data, stageId); - return (T) data.stageMaterialsProducers.get(stageId); - } - - /** - * Returns the offer state of the given stage id. - * - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_STAGE_ID} if the stage id - * is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_STAGE_ID} if the stage - * id is unknown
  • - */ - public Boolean isStageOffered(final StageId stageId) { - validateStageExists(data, stageId); - return data.stageOffers.get(stageId); - } - - @Override - public PluginDataBuilder getCloneBuilder() { - return new Builder(new Data(data)); - } -} diff --git a/gcm3/src/main/java/plugins/materials/MaterialsPluginId.java b/gcm3/src/main/java/plugins/materials/MaterialsPluginId.java deleted file mode 100644 index c3a73ee35..000000000 --- a/gcm3/src/main/java/plugins/materials/MaterialsPluginId.java +++ /dev/null @@ -1,14 +0,0 @@ -package plugins.materials; - -import nucleus.PluginId; -/** - * Static plugin id implementation for the materials plugin - * - * @author Shawn Hatch - * - */ - -public final class MaterialsPluginId implements PluginId { - public final static PluginId PLUGIN_ID = new MaterialsPluginId(); - private MaterialsPluginId() {}; -} diff --git a/gcm3/src/main/java/plugins/materials/actors/BatchStatusReport.java b/gcm3/src/main/java/plugins/materials/actors/BatchStatusReport.java deleted file mode 100644 index a30457895..000000000 --- a/gcm3/src/main/java/plugins/materials/actors/BatchStatusReport.java +++ /dev/null @@ -1,235 +0,0 @@ -package plugins.materials.actors; - -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Optional; -import java.util.Set; - -import nucleus.ActorContext; -import plugins.materials.datamangers.MaterialsDataManager; -import plugins.materials.events.BatchAmountUpdateEvent; -import plugins.materials.events.BatchAdditionEvent; -import plugins.materials.events.BatchImminentRemovalEvent; -import plugins.materials.events.BatchPropertyUpdateEvent; -import plugins.materials.events.StageMembershipAdditionEvent; -import plugins.materials.events.StageMembershipRemovalEvent; -import plugins.materials.support.BatchId; -import plugins.materials.support.BatchPropertyId; -import plugins.materials.support.MaterialId; -import plugins.materials.support.MaterialsProducerId; -import plugins.materials.support.StageId; -import plugins.reports.support.ReportHeader; -import plugins.reports.support.ReportId; -import plugins.reports.support.ReportItem; - -/** - * A Report that displays the state of batches over time. - * - * - * Fields - * - * Time -- the time in days when batch state was updated - * - * Batch -- the batch identifier - * - * Stage -- the stage associated with the batch - * - * MaterialsProducer -- the materials producer of the owner of the batch - * - * Offered -- the offered state of the batch - * - * Material -- the material of the batch - * - * Amount -- the amount of material in the batch - * - * Material.PropertyId -- multiple columns for the batch properties selected for - * the report - * - * @author Shawn Hatch - * - */ -public final class BatchStatusReport { - - private static class BatchRecord { - private double time; - private BatchId batchId; - private MaterialsProducerId materialsProducerId; - private StageId stageId; - private MaterialId materialId; - private double amount; - private Map propertyValues = new LinkedHashMap<>(); - - } - private final ReportId reportId; - public BatchStatusReport(ReportId reportId) { - this.reportId = reportId; - } - - private Map batchRecords = new LinkedHashMap<>(); - - private Map> batchPropertyMap = new LinkedHashMap<>(); - - /* - * Releases a report item for each updated batch that still exists - */ - private void reportBatch(ActorContext actorContext, BatchRecord batchRecord) { - - // report the batch - make sure batch exists - - final ReportItem.Builder reportItemBuilder = ReportItem.builder(); - reportItemBuilder.setReportHeader(getReportHeader()); - reportItemBuilder.setReportId(reportId); - reportItemBuilder.addValue(batchRecord.time); - reportItemBuilder.addValue(batchRecord.batchId); - reportItemBuilder.addValue(batchRecord.materialsProducerId); - - if (batchRecord.stageId != null) { - reportItemBuilder.addValue(batchRecord.stageId); - } else { - reportItemBuilder.addValue(""); - } - - reportItemBuilder.addValue(batchRecord.materialId); - reportItemBuilder.addValue(batchRecord.amount); - - for (MaterialId materialId : batchPropertyMap.keySet()) { - boolean matchingMaterial = batchRecord.materialId.equals(materialId); - Set batchPropertyIds = batchPropertyMap.get(materialId); - for (BatchPropertyId batchPropertyId : batchPropertyIds) { - if (matchingMaterial) { - reportItemBuilder.addValue(batchRecord.propertyValues.get(batchPropertyId)); - } else { - reportItemBuilder.addValue(""); - } - } - } - actorContext.releaseOutput(reportItemBuilder.build()); - - } - - private ReportHeader reportHeader; - - /* - * Returns the ReportHeader based on the batch properties selected by the - * client. - */ - private ReportHeader getReportHeader() { - if (reportHeader == null) { - ReportHeader.Builder builder = ReportHeader .builder()// - .add("time")// - .add("batch")// - .add("materials_producer")// - .add("stage")// - .add("material")// - .add("amount");// - Set materialIds = materialsDataManager.getMaterialIds(); - for (MaterialId materialId : materialIds) { - Set batchPropertyIds = materialsDataManager.getBatchPropertyIds(materialId); - for (BatchPropertyId batchPropertyId : batchPropertyIds) { - builder.add(materialId + "." + batchPropertyId); - } - } - reportHeader = builder.build(); - } - return reportHeader; - - } - - private BatchRecord createBatchRecord(ActorContext actorContext, BatchId batchId) { - - BatchRecord batchRecord = new BatchRecord(); - - batchRecord.time = actorContext.getTime(); - batchRecord.batchId = batchId; - batchRecord.materialsProducerId = materialsDataManager.getBatchProducer(batchId); - Optional optionalStageId = materialsDataManager.getBatchStageId(batchId); - if (optionalStageId.isPresent()) { - batchRecord.stageId = optionalStageId.get(); - } else { - batchRecord.stageId = null; - } - batchRecord.materialId = materialsDataManager.getBatchMaterial(batchId); - batchRecord.amount = materialsDataManager.getBatchAmount(batchId); - - Set batchPropertyIds = materialsDataManager.getBatchPropertyIds(batchRecord.materialId); - for (BatchPropertyId batchPropertyId : batchPropertyIds) { - Object batchPropertyValue = materialsDataManager.getBatchPropertyValue(batchId, batchPropertyId); - batchRecord.propertyValues.put(batchPropertyId, batchPropertyValue); - } - batchRecords.put(batchId, batchRecord); - return batchRecord; - } - - private void handleBatchAdditionEvent(ActorContext actorContext, BatchAdditionEvent batchAdditionEvent) { - BatchId batchId = batchAdditionEvent.getBatchId(); - BatchRecord batchRecord = createBatchRecord(actorContext, batchId); - reportBatch(actorContext, batchRecord); - } - - private void handleBatchImminentRemovalEvent(ActorContext actorContext, BatchImminentRemovalEvent batchImminentRemovalEvent) { - BatchId batchId = batchImminentRemovalEvent.getBatchId(); - BatchRecord batchRecord = batchRecords.remove(batchId); - batchRecord.time = actorContext.getTime(); - reportBatch(actorContext, batchRecord); - } - - private void handleBatchAmountUpdateEvent(ActorContext actorContext, BatchAmountUpdateEvent batchAmountUpdateEvent) { - BatchId batchId = batchAmountUpdateEvent.getBatchId(); - BatchRecord batchRecord = batchRecords.get(batchId); - batchRecord.amount = batchAmountUpdateEvent.getCurrentAmount(); - batchRecord.time = actorContext.getTime(); - reportBatch(actorContext, batchRecord); - } - - private void handleStageMembershipAdditionEvent(ActorContext actorContext, StageMembershipAdditionEvent stageMembershipAdditionEvent) { - BatchId batchId = stageMembershipAdditionEvent.getBatchId(); - BatchRecord batchRecord = batchRecords.get(batchId); - batchRecord.stageId = stageMembershipAdditionEvent.getStageId(); - batchRecord.time = actorContext.getTime(); - reportBatch(actorContext, batchRecord); - } - - private void handleStageMembershipRemovalEvent(ActorContext actorContext, StageMembershipRemovalEvent stageMembershipRemovalEvent) { - BatchId batchId = stageMembershipRemovalEvent.getBatchId(); - BatchRecord batchRecord = batchRecords.get(batchId); - batchRecord.stageId = null; - batchRecord.time = actorContext.getTime(); - reportBatch(actorContext, batchRecord); - } - - private void handleBatchPropertyUpdateEvent(ActorContext actorContext, BatchPropertyUpdateEvent batchPropertyUpdateEvent) { - BatchId batchId = batchPropertyUpdateEvent.getBatchId(); - BatchRecord batchRecord = batchRecords.get(batchId); - batchRecord.propertyValues.put(batchPropertyUpdateEvent.getBatchPropertyId(), batchPropertyUpdateEvent.getCurrentPropertyValue()); - batchRecord.time = actorContext.getTime(); - reportBatch(actorContext, batchRecord); - } - - private MaterialsDataManager materialsDataManager; - - public void init(final ActorContext actorContext) { - - actorContext.subscribe(BatchAdditionEvent.class, this::handleBatchAdditionEvent); - actorContext.subscribe(BatchImminentRemovalEvent.class, this::handleBatchImminentRemovalEvent); - actorContext.subscribe(BatchAmountUpdateEvent.class, this::handleBatchAmountUpdateEvent); - actorContext.subscribe(BatchPropertyUpdateEvent.class, this::handleBatchPropertyUpdateEvent); - actorContext.subscribe(StageMembershipAdditionEvent.class, this::handleStageMembershipAdditionEvent); - actorContext.subscribe(StageMembershipRemovalEvent.class, this::handleStageMembershipRemovalEvent); - - materialsDataManager = actorContext.getDataManager(MaterialsDataManager.class); - - for (MaterialsProducerId materialsProducerId : materialsDataManager.getMaterialsProducerIds()) { - for (BatchId inventoryBatchId : materialsDataManager.getInventoryBatches(materialsProducerId)) { - BatchRecord batchRecord = createBatchRecord(actorContext, inventoryBatchId); - reportBatch(actorContext, batchRecord); - } - for (StageId stageId : materialsDataManager.getStages(materialsProducerId)) { - for (BatchId stageBatchId : materialsDataManager.getStageBatches(stageId)) { - BatchRecord batchRecord = createBatchRecord(actorContext, stageBatchId); - reportBatch(actorContext, batchRecord); - } - } - } - } - -} diff --git a/gcm3/src/main/java/plugins/materials/actors/MaterialsProducerPropertyReport.java b/gcm3/src/main/java/plugins/materials/actors/MaterialsProducerPropertyReport.java deleted file mode 100644 index 0bb22a6a3..000000000 --- a/gcm3/src/main/java/plugins/materials/actors/MaterialsProducerPropertyReport.java +++ /dev/null @@ -1,84 +0,0 @@ -package plugins.materials.actors; - -import nucleus.ActorContext; -import plugins.materials.datamangers.MaterialsDataManager; -import plugins.materials.events.MaterialsProducerPropertyUpdateEvent; -import plugins.materials.support.MaterialsProducerId; -import plugins.materials.support.MaterialsProducerPropertyId; -import plugins.reports.support.ReportHeader; -import plugins.reports.support.ReportId; -import plugins.reports.support.ReportItem; - -/** - * A Report that displays assigned materials producer property values over time. - * - * - * Fields - * - * Time -- the time in days when the materials producer property was set - * - * MaterialsProducer -- the materials producer identifier - * - * Property -- the region property identifier - * - * Value -- the value of the region property - * - * @author Shawn Hatch - * - */ -public final class MaterialsProducerPropertyReport { - - private final ReportId reportId; - - public MaterialsProducerPropertyReport(ReportId reportId) { - this.reportId = reportId; - } - - private ReportHeader reportHeader; - - private ReportHeader getReportHeader() { - if (reportHeader == null) { - reportHeader = ReportHeader .builder()// - .add("time")// - .add("materials_producer")// - .add("property")// - .add("value")// - .build();// - } - return reportHeader; - } - - private void handleMaterialsProducerPropertyUpdateEvent(ActorContext actorContext, MaterialsProducerPropertyUpdateEvent materialsProducerPropertyUpdateEvent) { - MaterialsProducerId materialsProducerId = materialsProducerPropertyUpdateEvent.getMaterialsProducerId(); - MaterialsProducerPropertyId materialsProducerPropertyId = materialsProducerPropertyUpdateEvent.getMaterialsProducerPropertyId(); - Object currentPropertyValue = materialsProducerPropertyUpdateEvent.getCurrentPropertyValue(); - writeProperty(actorContext, materialsProducerId, materialsProducerPropertyId, currentPropertyValue); - } - - public void init(final ActorContext actorContext) { - - actorContext.subscribe(MaterialsProducerPropertyUpdateEvent.class, this::handleMaterialsProducerPropertyUpdateEvent); - - MaterialsDataManager materialsDataManager = actorContext.getDataManager(MaterialsDataManager.class); - - for (final MaterialsProducerId materialsProducerId : materialsDataManager.getMaterialsProducerIds()) { - for (final MaterialsProducerPropertyId materialsProducerPropertyId : materialsDataManager.getMaterialsProducerPropertyIds()) { - final Object materialsProducerPropertyValue = materialsDataManager.getMaterialsProducerPropertyValue(materialsProducerId, materialsProducerPropertyId); - writeProperty(actorContext, materialsProducerId, materialsProducerPropertyId, materialsProducerPropertyValue); - } - } - } - - private void writeProperty(ActorContext actorContext, final MaterialsProducerId materialsProducerId, final MaterialsProducerPropertyId materialsProducerPropertyId, - Object materialsProducerPropertyValue) { - final ReportItem.Builder reportItemBuilder = ReportItem.builder(); - reportItemBuilder.setReportHeader(getReportHeader()); - reportItemBuilder.setReportId(reportId); - reportItemBuilder.addValue(actorContext.getTime()); - reportItemBuilder.addValue(materialsProducerId.toString()); - reportItemBuilder.addValue(materialsProducerPropertyId.toString()); - reportItemBuilder.addValue(materialsProducerPropertyValue); - actorContext.releaseOutput(reportItemBuilder.build()); - } - -} \ No newline at end of file diff --git a/gcm3/src/main/java/plugins/materials/actors/MaterialsProducerResourceReport.java b/gcm3/src/main/java/plugins/materials/actors/MaterialsProducerResourceReport.java deleted file mode 100644 index 7d0aaaf89..000000000 --- a/gcm3/src/main/java/plugins/materials/actors/MaterialsProducerResourceReport.java +++ /dev/null @@ -1,114 +0,0 @@ -package plugins.materials.actors; - -import nucleus.ActorContext; -import plugins.materials.datamangers.MaterialsDataManager; -import plugins.materials.events.MaterialsProducerResourceUpdateEvent; -import plugins.materials.support.MaterialsProducerId; -import plugins.reports.support.ReportHeader; -import plugins.reports.support.ReportId; -import plugins.reports.support.ReportItem; -import plugins.resources.datamanagers.ResourcesDataManager; -import plugins.resources.support.ResourceId; - -/** - * A Report that displays materials producer resource changes over time. - * - * - * Fields - * - * Time -- the time in days when the materials producer resource level was set - * - * Resource -- the resource identifier - * - * MaterialsProducer -- the materials producer identifier - * - * Action -- the action taken on the resource - * - * Amount -- the amount of resource - * - * @author Shawn Hatch - * - */ -public final class MaterialsProducerResourceReport { - private final ReportId reportId; - public MaterialsProducerResourceReport(ReportId reportId) { - this.reportId = reportId; - } - - private static enum Action { - /* - * Used when a resource is directly added to a materials producer which - * only happens when the materials producer is being initialized from - * the scenario - */ - ADDED("Added"), - - /* - * Used when a materials producer transfers resource to a region - */ - - REMOVED("Removed"); - - private final String displayName; - - private Action(final String displayName) { - this.displayName = displayName; - } - } - - private ReportHeader reportHeader; - - private ReportHeader getReportHeader() { - if (reportHeader == null) { - reportHeader = ReportHeader .builder()// - .add("time")// - .add("resource")// - .add("materials_producer")// - .add("action")// - .add("amount")// - .build();// - } - return reportHeader; - } - - private void handleMaterialsProducerResourceUpdateEvent(ActorContext actorContext,MaterialsProducerResourceUpdateEvent materialsProducerResourceUpdateEvent) { - long currentResourceLevel = materialsProducerResourceUpdateEvent.getCurrentResourceLevel(); - long previousResourceLevel = materialsProducerResourceUpdateEvent.getPreviousResourceLevel(); - long amount = currentResourceLevel - previousResourceLevel; - ResourceId resourceId = materialsProducerResourceUpdateEvent.getResourceId(); - MaterialsProducerId materialsProducerId = materialsProducerResourceUpdateEvent.getMaterialsProducerId(); - if (amount > 0) { - writeReportItem(actorContext,resourceId, materialsProducerId, Action.ADDED, amount); - } else { - amount = -amount; - writeReportItem(actorContext,resourceId, materialsProducerId, Action.REMOVED, amount); - } - } - - private void writeReportItem(ActorContext actorContext,final ResourceId resourceId, final MaterialsProducerId materialsProducerId, final Action action, final long amount) { - final ReportItem.Builder reportItemBuilder = ReportItem.builder(); - reportItemBuilder.setReportHeader(getReportHeader()); - reportItemBuilder.setReportId(reportId); - reportItemBuilder.addValue(actorContext.getTime()); - reportItemBuilder.addValue(resourceId.toString()); - reportItemBuilder.addValue(materialsProducerId.toString()); - reportItemBuilder.addValue(action.displayName); - reportItemBuilder.addValue(amount); - actorContext.releaseOutput(reportItemBuilder.build()); - } - - public void init(final ActorContext actorContext) { - - actorContext.subscribe(MaterialsProducerResourceUpdateEvent.class,this::handleMaterialsProducerResourceUpdateEvent); - - ResourcesDataManager resourcesDataManager = actorContext.getDataManager(ResourcesDataManager.class); - MaterialsDataManager materialsDataManager = actorContext.getDataManager(MaterialsDataManager.class); - for (MaterialsProducerId materialsProducerId : materialsDataManager.getMaterialsProducerIds()) { - for (ResourceId resourceId : resourcesDataManager.getResourceIds()) { - long materialsProducerResourceLevel = materialsDataManager.getMaterialsProducerResourceLevel(materialsProducerId, resourceId); - writeReportItem(actorContext,resourceId, materialsProducerId, Action.ADDED, materialsProducerResourceLevel); - } - } - } - -} diff --git a/gcm3/src/main/java/plugins/materials/actors/StageReport.java b/gcm3/src/main/java/plugins/materials/actors/StageReport.java deleted file mode 100644 index 231790637..000000000 --- a/gcm3/src/main/java/plugins/materials/actors/StageReport.java +++ /dev/null @@ -1,168 +0,0 @@ -package plugins.materials.actors; - -import java.util.LinkedHashMap; -import java.util.Map; - -import nucleus.ActorContext; -import plugins.materials.datamangers.MaterialsDataManager; -import plugins.materials.events.StageAdditionEvent; -import plugins.materials.events.StageImminentRemovalEvent; -import plugins.materials.events.StageMaterialsProducerUpdateEvent; -import plugins.materials.events.StageOfferUpdateEvent; -import plugins.materials.support.MaterialsProducerId; -import plugins.materials.support.StageId; -import plugins.reports.support.ReportHeader; -import plugins.reports.support.ReportId; -import plugins.reports.support.ReportItem; - -/** - * A Report that displays the creation, destruction, offering, batch conversion, - * resource conversion and transfer of stages. - * - * - * Fields - * - * Time -- the time in days when the global resource was set - * - * Stage -- the stage identifier - * - * MaterialsProducer -- the acting materials producer - * - * Action -- One of Create, Destroy, Offer, BatchConversion, ResourceConversion, - * Transfer - * - * Offered -- the offered state of the stage - * - * ResourceMaterial - * - * Amount - * - * @author Shawn Hatch - * - */ -public final class StageReport { - private final ReportId reportId; - - public StageReport(ReportId reportId) { - this.reportId = reportId; - } - - private static class StageRecord { - StageId stageId; - MaterialsProducerId materialsProducerId; - boolean isOffered; - Action lastAction; - } - - private Map stageRecords = new LinkedHashMap<>(); - - /* - * An enumeration mirroring the cause of a change to a stage - */ - private static enum Action { - CREATED("Create"), - - DESTROYED("Destroy"), - - OFFERED("Offer"), - - TRANSFERRED("Transfer"); - - private final String displayName; - - private Action(final String displayName) { - this.displayName = displayName; - } - } - - /* - * The derived header for this report - */ - private ReportHeader reportHeader; - - private ReportHeader getReportHeader() { - if (reportHeader == null) { - reportHeader = ReportHeader .builder()// - .add("time")// - .add("stage")// - .add("materials_producer")// - .add("action")// - .add("offered")// - .build();// - } - return reportHeader; - } - - private void handleStageAdditionEvent(ActorContext actorContext, StageAdditionEvent stageAdditionEvent) { - - StageRecord stageRecord = new StageRecord(); - stageRecord.stageId = stageAdditionEvent.getStageId(); - stageRecord.isOffered = materialsDataManager.isStageOffered(stageRecord.stageId); - stageRecord.materialsProducerId = materialsDataManager.getStageProducer(stageRecord.stageId); - stageRecord.lastAction = Action.CREATED; - stageRecords.put(stageRecord.stageId, stageRecord); - writeReportItem(actorContext, stageRecord); - } - - private void handleStageImminentRemovalEvent(ActorContext actorContext, StageImminentRemovalEvent stageImminentRemovalEvent) { - StageId stageId = stageImminentRemovalEvent.getStageId(); - StageRecord stageRecord = stageRecords.remove(stageId); - stageRecord.lastAction = Action.DESTROYED; - writeReportItem(actorContext, stageRecord); - } - - private void handleStageOfferUpdateEvent(ActorContext actorContext, StageOfferUpdateEvent stageOfferUpdateEvent) { - StageId stageId = stageOfferUpdateEvent.getStageId(); - StageRecord stageRecord = stageRecords.get(stageId); - stageRecord.isOffered = stageOfferUpdateEvent.isCurrentOfferState(); - stageRecord.lastAction = Action.OFFERED; - writeReportItem(actorContext, stageRecord); - } - - private void handleStageMaterialsProducerUpdateEvent(ActorContext actorContext, StageMaterialsProducerUpdateEvent stageMaterialsProducerUpdateEvent) { - StageId stageId = stageMaterialsProducerUpdateEvent.getStageId(); - StageRecord stageRecord = stageRecords.get(stageId); - stageRecord.materialsProducerId = stageMaterialsProducerUpdateEvent.getCurrentMaterialsProducerId(); - stageRecord.lastAction = Action.TRANSFERRED; - writeReportItem(actorContext, stageRecord); - } - - private void writeReportItem(ActorContext actorContext, final StageRecord stageRecord) { - final ReportItem.Builder reportItemBuilder = ReportItem.builder(); - reportItemBuilder.setReportHeader(getReportHeader()); - reportItemBuilder.setReportId(reportId); - reportItemBuilder.addValue(actorContext.getTime()); - reportItemBuilder.addValue(stageRecord.stageId); - reportItemBuilder.addValue(stageRecord.materialsProducerId.toString()); - reportItemBuilder.addValue(stageRecord.lastAction.displayName); - reportItemBuilder.addValue(stageRecord.isOffered); - actorContext.releaseOutput(reportItemBuilder.build()); - } - - private MaterialsDataManager materialsDataManager; - - public void init(final ActorContext actorContext) { - - actorContext.subscribe(StageOfferUpdateEvent.class, this::handleStageOfferUpdateEvent); - actorContext.subscribe(StageAdditionEvent.class, this::handleStageAdditionEvent); - actorContext.subscribe(StageImminentRemovalEvent.class, this::handleStageImminentRemovalEvent); - actorContext.subscribe(StageMaterialsProducerUpdateEvent.class, this::handleStageMaterialsProducerUpdateEvent); - - materialsDataManager = actorContext.getDataManager(MaterialsDataManager.class); - - for (MaterialsProducerId materialsProducerId : materialsDataManager.getMaterialsProducerIds()) { - for (StageId stageId : materialsDataManager.getStages(materialsProducerId)) { - - StageRecord stageRecord = new StageRecord(); - stageRecord.stageId = stageId; - stageRecord.isOffered = materialsDataManager.isStageOffered(stageId); - stageRecord.materialsProducerId = materialsProducerId; - stageRecord.lastAction = Action.CREATED; - stageRecords.put(stageRecord.stageId, stageRecord); - - writeReportItem(actorContext, stageRecord); - } - } - } - -} diff --git a/gcm3/src/main/java/plugins/materials/datamangers/MaterialsDataManager.java b/gcm3/src/main/java/plugins/materials/datamangers/MaterialsDataManager.java deleted file mode 100644 index 6cd79d245..000000000 --- a/gcm3/src/main/java/plugins/materials/datamangers/MaterialsDataManager.java +++ /dev/null @@ -1,1914 +0,0 @@ -package plugins.materials.datamangers; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; - -import org.apache.commons.math3.util.FastMath; - -import nucleus.DataManager; -import nucleus.DataManagerContext; -import nucleus.NucleusError; -import nucleus.SimulationContext; -import plugins.materials.MaterialsPluginData; -import plugins.materials.events.BatchAmountUpdateEvent; -import plugins.materials.events.BatchAdditionEvent; -import plugins.materials.events.BatchImminentRemovalEvent; -import plugins.materials.events.BatchPropertyUpdateEvent; -import plugins.materials.events.MaterialsProducerPropertyUpdateEvent; -import plugins.materials.events.MaterialsProducerResourceUpdateEvent; -import plugins.materials.events.StageAdditionEvent; -import plugins.materials.events.StageImminentRemovalEvent; -import plugins.materials.events.StageMaterialsProducerUpdateEvent; -import plugins.materials.events.StageMembershipAdditionEvent; -import plugins.materials.events.StageMembershipRemovalEvent; -import plugins.materials.events.StageOfferUpdateEvent; -import plugins.materials.support.BatchConstructionInfo; -import plugins.materials.support.BatchId; -import plugins.materials.support.BatchPropertyId; -import plugins.materials.support.MaterialId; -import plugins.materials.support.MaterialsError; -import plugins.materials.support.MaterialsProducerId; -import plugins.materials.support.MaterialsProducerPropertyId; -import plugins.materials.support.StageId; -import plugins.regions.datamanagers.RegionsDataManager; -import plugins.regions.support.RegionError; -import plugins.regions.support.RegionId; -import plugins.resources.datamanagers.ResourcesDataManager; -import plugins.resources.support.ResourceError; -import plugins.resources.support.ResourceId; -import plugins.util.properties.PropertyDefinition; -import plugins.util.properties.PropertyError; -import plugins.util.properties.PropertyValueRecord; -import util.errors.ContractException; - -/** - * General manager for all material activities. - * - * @author Shawn Hatch - * - */ -public final class MaterialsDataManager extends DataManager { - - /* - * Represents the batch - */ - private static class BatchRecord { - - private final BatchId batchId; - /* - * The stage on which this batch is staged -- may be null - */ - private StageRecord stageRecord; - /* - * The non-negative amount of this batch - */ - private double amount; - /* - * The time when this batch was created - */ - private double creationTime; - /* - * The non-null material for this batch - */ - private MaterialId materialId; - /* - * The owning material producer - */ - private MaterialsProducerRecord materialsProducerRecord; - - private BatchRecord(final int index) { - batchId = new BatchId(index); - } - } - - private static class ComponentResourceRecord { - private final SimulationContext simulationContext; - - private long amount; - - private double assignmentTime; - - public ComponentResourceRecord(final SimulationContext simulationContext) { - this.simulationContext = simulationContext; - } - - public void decrementAmount(final long amount) { - if (amount < 0) { - throw new RuntimeException("negative amount"); - } - - if (this.amount < amount) { - throw new RuntimeException("cannot decrement to a negative level"); - } - this.amount = Math.subtractExact(this.amount, amount); - assignmentTime = simulationContext.getTime(); - } - - public long getAmount() { - return amount; - } - - public double getAssignmentTime() { - return assignmentTime; - } - - public void incrementAmount(final long amount) { - if (amount < 0) { - throw new RuntimeException("negative amount"); - } - this.amount = Math.addExact(this.amount, amount); - assignmentTime = simulationContext.getTime(); - } - - } - - /* - * Represents the materials producer - */ - private static class MaterialsProducerRecord { - private final Map materialProducerResources = new LinkedHashMap<>(); - /* - * Identifier for the materials producer - */ - private MaterialsProducerId materialProducerId; - - /* - * Those batches owned by this materials producer that are not staged - */ - private final Set inventory = new LinkedHashSet<>(); - - /* - * Those batches owned by this materials producer that are staged - */ - private final Set stageRecords = new LinkedHashSet<>(); - - } - - /* - * Represents the stage - */ - private static class StageRecord { - /* - * The owning material producer - */ - private MaterialsProducerRecord materialsProducerRecord; - - private final StageId stageId; - /* - * Flag marking that the stage has been offered up to other components. - * While true, this stage and its batches are immutable. - */ - private boolean offered; - /* - * The set of batches that are staged on this stage - */ - private final Set batchRecords = new LinkedHashSet<>(); - - private StageRecord(final int index) { - stageId = new StageId(index); - } - } - - private final Set materialsProducerPropertyIds = new LinkedHashSet<>(); - - private final Map> materialsProducerPropertyMap = new LinkedHashMap<>(); - - private final Map> batchPropertyMap = new LinkedHashMap<>(); - - private final Map> batchPropertyIdMap = new LinkedHashMap<>(); - - /* - * The identifier for the next created batch - */ - private int nextBatchRecordId; - - /* - * The identifier for the next created stage - */ - private int nextStageRecordId; - - private final Map batchRecords = new LinkedHashMap<>(); - - /* - * - */ - private final Map stageRecords = new LinkedHashMap<>(); - - /* - * - */ - private final Map materialsProducerMap = new LinkedHashMap<>(); - - private final Map materialsProducerPropertyDefinitions = new LinkedHashMap<>(); - - private final Map> batchPropertyDefinitions = new LinkedHashMap<>(); - - private final Set materialIds = new LinkedHashSet<>(); - - private final Set resourceIds = new LinkedHashSet<>(); - - private DataManagerContext dataManagerContext; - - /** - * Constructs the data manager. - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_MATERIALS_PLUGIN_DATA} if - * the material plugin data is null
  • - */ - public MaterialsDataManager(MaterialsPluginData materialsPluginData) { - if (materialsPluginData == null) { - throw new ContractException(MaterialsError.NULL_MATERIALS_PLUGIN_DATA); - } - this.materialsPluginData = materialsPluginData; - } - - private final MaterialsPluginData materialsPluginData; - private ResourcesDataManager resourcesDataManager; - private RegionsDataManager regionsDataManager; - - @Override - public void init(DataManagerContext dataManagerContext) { - super.init(dataManagerContext); - if (dataManagerContext == null) { - throw new ContractException(NucleusError.NULL_SIMULATION_CONTEXT); - } - resourcesDataManager = dataManagerContext.getDataManager(ResourcesDataManager.class); - regionsDataManager = dataManagerContext.getDataManager(RegionsDataManager.class); - - this.dataManagerContext = dataManagerContext; - - dataManagerContext.addEventLabeler(StageOfferUpdateEvent.getEventLabelerForStage()); - dataManagerContext.addEventLabeler(StageMaterialsProducerUpdateEvent.getEventLabelerForDestination()); - dataManagerContext.addEventLabeler(StageMaterialsProducerUpdateEvent.getEventLabelerForSource()); - dataManagerContext.addEventLabeler(StageMaterialsProducerUpdateEvent.getEventLabelerForStage()); - dataManagerContext.addEventLabeler(MaterialsProducerPropertyUpdateEvent.getEventLabelerForMaterialsProducerAndProperty()); - dataManagerContext.addEventLabeler(MaterialsProducerResourceUpdateEvent.getEventLabelerForMaterialsProducerAndResource()); - dataManagerContext.addEventLabeler(MaterialsProducerResourceUpdateEvent.getEventLabelerForResource()); - - for (final MaterialId materialId : materialsPluginData.getMaterialIds()) { - materialIds.add(materialId); - batchPropertyIdMap.put(materialId, new LinkedHashSet<>()); - batchPropertyDefinitions.put(materialId, new LinkedHashMap<>()); - for (final BatchPropertyId batchPropertyId : materialsPluginData.getBatchPropertyIds(materialId)) { - final PropertyDefinition propertyDefinition = materialsPluginData.getBatchPropertyDefinition(materialId, batchPropertyId); - batchPropertyIdMap.get(materialId).add(batchPropertyId); - final Map map = batchPropertyDefinitions.get(materialId); - map.put(batchPropertyId, propertyDefinition); - } - } - - for (ResourceId resourceId : resourcesDataManager.getResourceIds()) { - resourceIds.add(resourceId); - } - for (ResourceId resourceId : materialsPluginData.getResourceIds()) { - if (!resourceIds.contains(resourceId)) { - throw new ContractException(ResourceError.UNKNOWN_RESOURCE_ID, resourceId + " in resource levels of materials producers"); - } - } - - for (final MaterialsProducerId materialsProducerId : materialsPluginData.getMaterialsProducerIds()) { - final MaterialsProducerRecord materialsProducerRecord = new MaterialsProducerRecord(); - materialsProducerRecord.materialProducerId = materialsProducerId; - for (final ResourceId resourceId : resourceIds) { - materialsProducerRecord.materialProducerResources.put(resourceId, new ComponentResourceRecord(dataManagerContext)); - } - materialsProducerMap.put(materialsProducerId, materialsProducerRecord); - materialsProducerPropertyMap.put(materialsProducerId, new LinkedHashMap<>()); - } - - for (final MaterialsProducerPropertyId materialsProducerPropertyId : materialsPluginData.getMaterialsProducerPropertyIds()) { - PropertyDefinition propertyDefinition = materialsPluginData.getMaterialsProducerPropertyDefinition(materialsProducerPropertyId); - materialsProducerPropertyIds.add(materialsProducerPropertyId); - for (final MaterialsProducerId materialsProducerId : materialsProducerMap.keySet()) { - final Map map = materialsProducerPropertyMap.get(materialsProducerId); - final PropertyValueRecord propertyValueRecord = new PropertyValueRecord(dataManagerContext); - map.put(materialsProducerPropertyId, propertyValueRecord); - } - materialsProducerPropertyDefinitions.put(materialsProducerPropertyId, propertyDefinition); - } - - /* - * Load the remaining data from the scenario that generally corresponds - * to mutations available to components so that reporting will properly - * reflect these data. The adding of resources directly to people and - * material producers is covered here but do not correspond to mutations - * allowed to components. - */ - - for (final MaterialsProducerId materialsProducerId : materialsPluginData.getMaterialsProducerIds()) { - for (final MaterialsProducerPropertyId materialsProducerPropertyId : materialsPluginData.getMaterialsProducerPropertyIds()) { - final Object materialsProducerPropertyValue = materialsPluginData.getMaterialsProducerPropertyValue(materialsProducerId, materialsProducerPropertyId); - materialsProducerPropertyMap.get(materialsProducerId).get(materialsProducerPropertyId).setPropertyValue(materialsProducerPropertyValue); - } - } - - nextStageRecordId = -1; - for (final StageId stageId : materialsPluginData.getStageIds()) { - final MaterialsProducerId materialsProducerId = materialsPluginData.getStageMaterialsProducer(stageId); - final MaterialsProducerRecord materialsProducerRecord = materialsProducerMap.get(materialsProducerId); - final StageRecord stageRecord = new StageRecord(stageId.getValue()); - nextStageRecordId = FastMath.max(nextStageRecordId,stageRecord.stageId.getValue()); - stageRecord.materialsProducerRecord = materialsProducerRecord; - stageRecord.offered = materialsPluginData.isStageOffered(stageId); - materialsProducerRecord.stageRecords.add(stageRecord); - stageRecords.put(stageRecord.stageId, stageRecord); - } - nextStageRecordId++; - - nextBatchRecordId = -1; - for (final BatchId batchId : materialsPluginData.getBatchIds()) { - final MaterialsProducerId materialsProducerId = materialsPluginData.getBatchMaterialsProducer(batchId); - final MaterialId materialId = materialsPluginData.getBatchMaterial(batchId); - final double amount = materialsPluginData.getBatchAmount(batchId); - final MaterialsProducerRecord materialsProducerRecord = materialsProducerMap.get(materialsProducerId); - final BatchRecord batchRecord = new BatchRecord(batchId.getValue()); - nextBatchRecordId = FastMath.max(nextBatchRecordId, batchRecord.batchId.getValue()); - batchRecord.amount = amount; - batchRecord.creationTime = dataManagerContext.getTime(); - batchRecord.materialId = materialId; - batchRecord.materialsProducerRecord = materialsProducerRecord; - materialsProducerRecord.inventory.add(batchRecord); - batchRecords.put(batchRecord.batchId, batchRecord); - final Map map = new LinkedHashMap<>(); - final Set batchPropertyIds = batchPropertyIdMap.get(materialId); - for (final BatchPropertyId batchPropertyId : batchPropertyIds) { - final PropertyDefinition propertyDefinition = getBatchPropertyDefinition(materialId, batchPropertyId); - final PropertyValueRecord propertyValueRecord = new PropertyValueRecord(dataManagerContext); - propertyValueRecord.setPropertyValue(propertyDefinition.getDefaultValue()); - map.put(batchPropertyId, propertyValueRecord); - } - batchPropertyMap.put(batchRecord.batchId, map); - } - nextBatchRecordId++; - - for (final StageId stageId : materialsPluginData.getStageIds()) { - final Set batches = materialsPluginData.getStageBatches(stageId); - for (final BatchId batchId : batches) { - final BatchRecord batchRecord = batchRecords.get(batchId); - if (batchRecord.stageRecord != null) { - throw new ContractException(MaterialsError.BATCH_ALREADY_STAGED); - } - final StageRecord stageRecord = stageRecords.get(stageId); - - batchRecord.stageRecord = stageRecord; - stageRecord.batchRecords.add(batchRecord); - batchRecord.materialsProducerRecord.inventory.remove(batchRecord); - } - } - - for (final BatchId batchId : materialsPluginData.getBatchIds()) { - final MaterialId materialId = materialsPluginData.getBatchMaterial(batchId); - final Set batchPropertyIds = materialsPluginData.getBatchPropertyIds(materialId); - for (final BatchPropertyId batchPropertyId : batchPropertyIds) { - final Object batchPropertyValue = materialsPluginData.getBatchPropertyValue(batchId, batchPropertyId); - final Map map = batchPropertyMap.get(batchId); - PropertyValueRecord propertyValueRecord = map.get(batchPropertyId); - if (propertyValueRecord == null) { - propertyValueRecord = new PropertyValueRecord(dataManagerContext); - map.put(batchPropertyId, propertyValueRecord); - } - propertyValueRecord.setPropertyValue(batchPropertyValue); - } - } - - for (final MaterialsProducerId materialsProducerId : materialsPluginData.getMaterialsProducerIds()) { - for (final ResourceId resourceId : resourceIds) { - final Long amount = materialsPluginData.getMaterialsProducerResourceLevel(materialsProducerId, resourceId); - if (amount != null) { - final MaterialsProducerRecord materialsProducerRecord = materialsProducerMap.get(materialsProducerId); - final ComponentResourceRecord componentResourceRecord = materialsProducerRecord.materialProducerResources.get(resourceId); - componentResourceRecord.incrementAmount(amount); - } - } - } - } - - /** - * Returns true if and only if the batch exists. Null tolerant. - */ - public boolean batchExists(final BatchId batchId) { - return batchRecords.containsKey(batchId); - } - - /** - * Returns true if and only if the batch property exists. Null tolerant. - * - */ - public boolean batchPropertyIdExists(final MaterialId materialId, final BatchPropertyId batchPropertyId) { - final Map map = batchPropertyDefinitions.get(materialId); - if (map == null) { - return false; - } - return map.containsKey(batchPropertyId); - } - - - /** - * Returns the amount of material in the batch. - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_BATCH_ID} if the batch id - * is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_BATCH_ID} if the batch - * id is unknown
  • - */ - public double getBatchAmount(final BatchId batchId) { - validateBatchId(batchId); - final BatchRecord batchRecord = batchRecords.get(batchId); - return batchRecord.amount; - } - - private void validateBatchId(final BatchId batchId) { - - if (batchId == null) { - throw new ContractException(MaterialsError.NULL_BATCH_ID); - } - - if (!batchExists(batchId)) { - throw new ContractException(MaterialsError.UNKNOWN_BATCH_ID, batchId); - } - } - - /** - * Returns the type of material in the batch. - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_BATCH_ID} if the batch id - * is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_BATCH_ID} if the batch - * id is unknown
  • - * - */ - @SuppressWarnings("unchecked") - public T getBatchMaterial(final BatchId batchId) { - validateBatchId(batchId); - return (T) batchRecords.get(batchId).materialId; - } - - /** - * Returns the materials producer identifier of the batch - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_BATCH_ID} if the batch id - * is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_BATCH_ID} if the batch - * id is unknown
  • - * - */ - @SuppressWarnings("unchecked") - public T getBatchProducer(final BatchId batchId) { - validateBatchId(batchId); - return (T) batchRecords.get(batchId).materialsProducerRecord.materialProducerId; - } - - /** - * Returns the property definition for the given {@link MaterialId} and - * {@link BatchPropertyId} - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_MATERIAL_ID} if the - * material id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_MATERIAL_ID} if the - * material id is unknown
  • - *
  • {@linkplain MaterialsError#NULL_BATCH_PROPERTY_ID} if the - * batch property id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_BATCH_PROPERTY_ID} if - * the batch property id is unknown
  • - */ - public PropertyDefinition getBatchPropertyDefinition(final MaterialId materialId, final BatchPropertyId batchPropertyId) { - validateMaterialId(materialId); - validateBatchPropertyId(materialId, batchPropertyId); - final Map map = batchPropertyDefinitions.get(materialId); - return map.get(batchPropertyId); - } - - private void validateMaterialId(final MaterialId materialId) { - if (materialId == null) { - throw new ContractException(MaterialsError.NULL_MATERIAL_ID); - } - - if (!materialIds.contains(materialId)) { - throw new ContractException(MaterialsError.UNKNOWN_MATERIAL_ID, materialId); - } - } - - private void validateBatchPropertyId(final MaterialId materialId, final BatchPropertyId batchPropertyId) { - if (batchPropertyId == null) { - throw new ContractException(MaterialsError.NULL_BATCH_PROPERTY_ID); - } - - final Map map = batchPropertyDefinitions.get(materialId); - if (map == null || !map.containsKey(batchPropertyId)) { - throw new ContractException(MaterialsError.UNKNOWN_BATCH_PROPERTY_ID); - } - - } - - /** - * Returns the {@link BatchPropertyId} values for the given - * {@link MaterialId} - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_MATERIAL_ID} if the - * materials id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_MATERIAL_ID - * S_PRODUCER_ID} if the materials id is unknown
  • - */ - @SuppressWarnings("unchecked") - public Set getBatchPropertyIds(final MaterialId materialId) { - validateMaterialId(materialId); - final Map map = batchPropertyDefinitions.get(materialId); - final Set result = new LinkedHashSet<>(map.keySet().size()); - for (final BatchPropertyId batchPropertyId : map.keySet()) { - result.add((T) batchPropertyId); - } - return result; - } - - /** - * Returns the time when the of the batch property was last assigned. It is - * the caller's responsibility to validate the inputs. - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_BATCH_ID} if the batch id - * is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_BATCH_ID} if the batch - * id is unknown
  • - *
  • {@linkplain MaterialsError#NULL_BATCH_PROPERTY_ID} if the - * batch property id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_BATCH_PROPERTY_ID} if - * the batch property id is unknown
  • - */ - public double getBatchPropertyTime(BatchId batchId, BatchPropertyId batchPropertyId) { - validateBatchId(batchId); - final MaterialId materialId = getBatchMaterial(batchId); - validateBatchPropertyId(materialId, batchPropertyId); - final Map map = batchPropertyMap.get(batchId); - final PropertyValueRecord propertyValueRecord = map.get(batchPropertyId); - return propertyValueRecord.getAssignmentTime(); - } - - /** - * Returns the value of the batch property. It is the caller's - * responsibility to validate the inputs. - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_BATCH_ID} if the batch id - * is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_BATCH_ID} if the batch - * id is unknown
  • - *
  • {@linkplain MaterialsError#NULL_BATCH_PROPERTY_ID} if the - * batch property id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_BATCH_PROPERTY_ID} if - * the batch property id is unknown
  • - */ - - @SuppressWarnings("unchecked") - public T getBatchPropertyValue(BatchId batchId, BatchPropertyId batchPropertyId) { - validateBatchId(batchId); - final MaterialId batchMaterial = batchRecords.get(batchId).materialId; - validateBatchPropertyId(batchMaterial, batchPropertyId); - final Map map = batchPropertyMap.get(batchId); - final PropertyValueRecord propertyValueRecord = map.get(batchPropertyId); - return (T) propertyValueRecord.getValue(); - } - - /** - * Returns the stage id the batch. Returns null if the batch is not in a - * stage. - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_BATCH_ID} if the batch id - * is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_BATCH_ID} if the batch - * id is unknown
  • - */ - public Optional getBatchStageId(final BatchId batchId) { - validateBatchId(batchId); - final BatchRecord batchRecord = batchRecords.get(batchId); - StageId stageId; - if (batchRecord.stageRecord == null) { - stageId = null; - } else { - stageId = batchRecord.stageRecord.stageId; - } - return Optional.ofNullable(stageId); - } - - /** - * Returns the creation time for the batch. - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_BATCH_ID} if the batch id - * is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_BATCH_ID} if the batch - * id is unknown
  • - * - */ - public double getBatchTime(final BatchId batchId) { - validateBatchId(batchId); - final BatchRecord batchRecord = batchRecords.get(batchId); - return batchRecord.creationTime; - } - - /** - * Returns as a list the set of batch ids matching the materials producer - * where the batches are not staged. - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} if - * the materials producer id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} - * if the materials producer id is unknown
  • - */ - public List getInventoryBatches(final MaterialsProducerId materialsProducerId) { - validateMaterialsProducerId(materialsProducerId); - final MaterialsProducerRecord materialsProducerRecord = materialsProducerMap.get(materialsProducerId); - final List result = new ArrayList<>(materialsProducerRecord.inventory.size()); - for (final BatchRecord batchRecord : materialsProducerRecord.inventory) { - result.add(batchRecord.batchId); - } - return result; - } - - private void validateMaterialsProducerId(final MaterialsProducerId materialsProducerId) { - if (materialsProducerId == null) { - throw new ContractException(MaterialsError.NULL_MATERIALS_PRODUCER_ID); - } - - if (!materialsProducerMap.containsKey(materialsProducerId)) { - throw new ContractException(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, materialsProducerId); - } - } - - /** - * Returns as a list the set of batch ids matching the materials producer - * and material id where the batches are not staged. - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} if - * the materials producer id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} - * if the materials producer id is unknown
  • - *
  • {@linkplain MaterialsError#NULL_MATERIAL_ID} if the - * material id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_MATERIAL_ID} if the - * material id is unknown
  • - */ - public List getInventoryBatchesByMaterialId(final MaterialsProducerId materialsProducerId, final MaterialId materialId) { - validateMaterialsProducerId(materialsProducerId); - validateMaterialId(materialId); - final MaterialsProducerRecord materialsProducerRecord = materialsProducerMap.get(materialsProducerId); - final List result = new ArrayList<>(); - for (final BatchRecord batchRecord : materialsProducerRecord.inventory) { - if (batchRecord.materialId.equals(materialId)) { - result.add(batchRecord.batchId); - } - } - return result; - } - - /** - * Returns the material id values for the simulation - */ - @SuppressWarnings("unchecked") - public Set getMaterialIds() { - final Set result = new LinkedHashSet<>(materialIds.size()); - for (final MaterialId materialId : materialIds) { - result.add((T) materialId); - } - return result; - } - - /** - * Returns the set of material producer ids - */ - @SuppressWarnings("unchecked") - public Set getMaterialsProducerIds() { - final Set result = new LinkedHashSet<>(materialsProducerMap.size()); - for (final MaterialsProducerId materialsProducerId : materialsProducerMap.keySet()) { - result.add((T) materialsProducerId); - } - return result; - } - - /** - * Returns the property definition for the given - * {@link MaterialsProducerPropertyId} - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_PROPERTY_ID} - * if the materials producer property id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_PROPERTY_ID} - * if the materials producer property id is unknown
  • - */ - public PropertyDefinition getMaterialsProducerPropertyDefinition(final MaterialsProducerPropertyId materialsProducerPropertyId) { - validateMaterialsProducerPropertyId(materialsProducerPropertyId); - return materialsProducerPropertyDefinitions.get(materialsProducerPropertyId); - } - - private void validateMaterialsProducerPropertyId(final MaterialsProducerPropertyId materialsProducerPropertyId) { - if (materialsProducerPropertyId == null) { - throw new ContractException(MaterialsError.NULL_MATERIALS_PRODUCER_PROPERTY_ID); - } - - if (!materialsProducerPropertyIds.contains(materialsProducerPropertyId)) { - throw new ContractException(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_PROPERTY_ID, materialsProducerPropertyId); - } - } - - /** - * Returns the set materials producer property ids - */ - @SuppressWarnings("unchecked") - public Set getMaterialsProducerPropertyIds() { - final Set result = new LinkedHashSet<>(materialsProducerPropertyDefinitions.keySet().size()); - for (final MaterialsProducerPropertyId materialsProducerPropertyId : materialsProducerPropertyDefinitions.keySet()) { - result.add((T) materialsProducerPropertyId); - } - return result; - } - - /** - * Returns the time when the of the materials producer property was last - * assigned. - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} if - * the materials producer id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} - * if the materials producer id is unknown
  • - *
  • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_PROPERTY_ID} - * if the materials producer property id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_PROPERTY_ID} - * if the materials producer property id is unknown
  • - */ - - public double getMaterialsProducerPropertyTime(MaterialsProducerId materialsProducerId, MaterialsProducerPropertyId materialsProducerPropertyId) { - validateMaterialsProducerId(materialsProducerId); - validateMaterialsProducerPropertyId(materialsProducerPropertyId); - return materialsProducerPropertyMap.get(materialsProducerId).get(materialsProducerPropertyId).getAssignmentTime(); - } - - /** - * Returns the value of the materials producer property. - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} if - * the materials producer id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} - * if the materials producer id is unknown
  • - *
  • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_PROPERTY_ID} - * if the materials producer property id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_PROPERTY_ID} - * if the materials producer property id is unknown
  • - */ - @SuppressWarnings("unchecked") - public T getMaterialsProducerPropertyValue(MaterialsProducerId materialsProducerId, MaterialsProducerPropertyId materialsProducerPropertyId) { - validateMaterialsProducerId(materialsProducerId); - validateMaterialsProducerPropertyId(materialsProducerPropertyId); - return (T) materialsProducerPropertyMap.get(materialsProducerId).get(materialsProducerPropertyId).getValue(); - } - - /** - * Returns the materials producer resource level. - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} if - * the materials producer id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} - * if the materials producer id is unknown
  • - *
  • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
  • - *
  • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if the - * resource id is unknown
  • - */ - public long getMaterialsProducerResourceLevel(MaterialsProducerId materialsProducerId, ResourceId resourceId) { - validateMaterialsProducerId(materialsProducerId); - validateResourceId(resourceId); - final MaterialsProducerRecord materialsProducerRecord = materialsProducerMap.get(materialsProducerId); - final ComponentResourceRecord componentResourceRecord = materialsProducerRecord.materialProducerResources.get(resourceId); - return componentResourceRecord.getAmount(); - } - - private void validateResourceId(final ResourceId resourceId) { - if (resourceId == null) { - throw new ContractException(ResourceError.NULL_RESOURCE_ID); - } - - if (!resourcesDataManager.resourceIdExists(resourceId)) { - throw new ContractException(ResourceError.UNKNOWN_RESOURCE_ID, resourceId); - } - } - - /** - * Returns the simulation time when the materials producer resource level - * was last assigned. - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} if - * the materials producer id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} - * if the materials producer id is unknown
  • - *
  • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
  • - *
  • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if the - * resource id is unknown
  • - */ - public double getMaterialsProducerResourceTime(MaterialsProducerId materialsProducerId, ResourceId resourceId) { - validateMaterialsProducerId(materialsProducerId); - validateResourceId(resourceId); - final MaterialsProducerRecord materialsProducerRecord = materialsProducerMap.get(materialsProducerId); - final ComponentResourceRecord componentResourceRecord = materialsProducerRecord.materialProducerResources.get(resourceId); - return componentResourceRecord.getAssignmentTime(); - } - - /** - * Returns as a list the set of stage ids owned by the material producer - * where the stage is being offered. - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} if - * the materials producer id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} - * if the materials producer id is unknown
  • - */ - public List getOfferedStages(final MaterialsProducerId materialsProducerId) { - validateMaterialsProducerId(materialsProducerId); - final List result = new ArrayList<>(); - final MaterialsProducerRecord materialsProducerRecord = materialsProducerMap.get(materialsProducerId); - for (final StageRecord stageRecord : materialsProducerRecord.stageRecords) { - if (stageRecord.offered) { - result.add(stageRecord.stageId); - } - } - return result; - } - - /** - * Returns as a list the set of batch ids matching the stage . - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_STAGE_ID} if the stage id - * is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_STAGE_ID} if the stage - * id is unknown
  • - */ - public List getStageBatches(final StageId stageId) { - validateStageId(stageId); - final StageRecord stageRecord = stageRecords.get(stageId); - final List result = new ArrayList<>(stageRecord.batchRecords.size()); - for (final BatchRecord batchRecord : stageRecord.batchRecords) { - result.add(batchRecord.batchId); - } - return result; - } - - private void validateStageId(final StageId stageId) { - if (stageId == null) { - throw new ContractException(MaterialsError.NULL_STAGE_ID); - } - - if (!stageRecords.containsKey(stageId)) { - throw new ContractException(MaterialsError.UNKNOWN_STAGE_ID, stageId); - } - } - - /** - * Returns as a list the set of batch ids matching the stage and material - * type. - * - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_STAGE_ID} if the stage id - * is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_STAGE_ID} if the stage - * id is unknown
  • - *
  • {@linkplain MaterialsError#NULL_MATERIAL_ID} if the - * material id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_MATERIAL_ID} if the - * material id is unknown
  • - */ - public List getStageBatchesByMaterialId(final StageId stageId, final MaterialId materialId) { - validateStageId(stageId); - validateMaterialId(materialId); - final StageRecord stageRecord = stageRecords.get(stageId); - final List result = new ArrayList<>(); - for (final BatchRecord batchRecord : stageRecord.batchRecords) { - if (batchRecord.materialId.equals(materialId)) { - result.add(batchRecord.batchId); - } - } - return result; - } - - /** - * Returns the materials producer id for the stage. - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_STAGE_ID} if the stage id - * is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_STAGE_ID} if the stage - * id is unknown
  • - * - */ - @SuppressWarnings("unchecked") - public T getStageProducer(final StageId stageId) { - validateStageId(stageId); - return (T) stageRecords.get(stageId).materialsProducerRecord.materialProducerId; - } - - /** - * Returns as a list the set of stage ids owned by the material producer. - * - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} if - * the materials producer id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} - * if the materials producer id is unknown
  • - */ - public List getStages(final MaterialsProducerId materialsProducerId) { - validateMaterialsProducerId(materialsProducerId); - final MaterialsProducerRecord materialsProducerRecord = materialsProducerMap.get(materialsProducerId); - final List result = new ArrayList<>(materialsProducerRecord.stageRecords.size()); - for (final StageRecord stageRecord : materialsProducerRecord.stageRecords) { - result.add(stageRecord.stageId); - } - return result; - } - - - /** - * Returns true if and only if the stage is offered. - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_STAGE_ID} if the stage id - * is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_STAGE_ID} if the stage - * id is unknown
  • - */ - public boolean isStageOffered(final StageId stageId) { - validateStageId(stageId); - final StageRecord stageRecord = stageRecords.get(stageId); - return stageRecord.offered; - } - - /** - * Returns true if and only if the material exists. Tolerates null. - */ - public boolean materialIdExists(final MaterialId materialId) { - return (materialIds.contains(materialId)); - } - - /** - * Returns true if and only if the material producer id exists. Null - * tolerant. - */ - public boolean materialsProducerIdExists(final MaterialsProducerId materialsProducerId) { - return materialsProducerMap.containsKey(materialsProducerId); - } - - /** - * Returns true if and only if the material producer property id exists. - * Null tolerant. - */ - public boolean materialsProducerPropertyIdExists(final MaterialsProducerPropertyId materialsProducerPropertyId) { - return materialsProducerPropertyIds.contains(materialsProducerPropertyId); - } - - - - /** - * Returns true if and only if the stage exists. Null tolerant. - */ - public boolean stageExists(final StageId stageId) { - return stageRecords.containsKey(stageId); - } - - /** - * Creates a batch from the {@linkplain BatchConstructionInfo} contained in - * the event. Sets batch properties found in the batch construction info. - * Generates a corresponding {@linkplain BatchAdditionEvent} - * event - * - * @throws ContractException - * - * - *
  • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} if - * the materials producer id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} - * if the materials producer id is unknown
  • - * - *
  • {@linkplain MaterialsError#NULL_BATCH_CONSTRUCTION_INFO} - * if the batch construction info in the event is null
  • - *
  • {@linkplain MaterialsError#NULL_MATERIAL_ID} if the - * material id in the batch construction info is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_MATERIAL_ID} if the - * material id in the batch construction info is unknown
  • - *
  • {@linkplain MaterialsError#NON_FINITE_MATERIAL_AMOUNT} if - * the amount in the batch construction info is not finite
  • - *
  • {@linkplain MaterialsError#NEGATIVE_MATERIAL_AMOUNT} if - * the amount in the batch construction info is negative
  • - *
  • {@linkplain MaterialsError#NULL_BATCH_PROPERTY_ID} if the - * batch construction info contains a null batch property - * id
  • - *
  • {@linkplain MaterialsError#UNKNOWN_BATCH_PROPERTY_ID} if - * the batch construction info contains an unknown batch - * property id
  • - *
  • {@linkplain MaterialsError#NULL_BATCH_PROPERTY_VALUE} if - * the batch construction info contains a null batch property - * value
  • - *
  • {@linkplain PropertyError#INCOMPATIBLE_VALUE} if the - * batch construction info contains a batch property value that - * is incompatible with the corresponding property def
  • - * - * - */ - public BatchId addBatch(BatchConstructionInfo batchConstructionInfo) { - validateBatchConstructionInfoNotNull(batchConstructionInfo); - final MaterialId materialId = batchConstructionInfo.getMaterialId(); - MaterialsProducerId materialsProducerId = batchConstructionInfo.getMaterialsProducerId(); - validateMaterialsProducerId(materialsProducerId); - validateMaterialId(materialId); - final double amount = batchConstructionInfo.getAmount(); - validateNonnegativeFiniteMaterialAmount(amount); - final Map propertyValues = batchConstructionInfo.getPropertyValues(); - for (final BatchPropertyId batchPropertyId : propertyValues.keySet()) { - validateBatchPropertyId(materialId, batchPropertyId); - final Object batchPropertyValue = propertyValues.get(batchPropertyId); - final PropertyDefinition propertyDefinition = batchPropertyDefinitions.get(materialId).get(batchPropertyId); - validateBatchPropertyValueNotNull(batchPropertyValue); - validateValueCompatibility(batchPropertyId, propertyDefinition, batchPropertyValue); - } - final MaterialsProducerRecord materialsProducerRecord = materialsProducerMap.get(materialsProducerId); - final BatchRecord batchRecord = new BatchRecord(nextBatchRecordId++); - batchRecord.amount = amount; - batchRecord.creationTime = dataManagerContext.getTime(); - batchRecord.materialId = materialId; - batchRecord.materialsProducerRecord = materialsProducerRecord; - materialsProducerRecord.inventory.add(batchRecord); - batchRecords.put(batchRecord.batchId, batchRecord); - - final Map map = new LinkedHashMap<>(); - final Set batchPropertyIds = batchPropertyIdMap.get(materialId); - for (final BatchPropertyId batchPropertyId : batchPropertyIds) { - final PropertyDefinition propertyDefinition = getBatchPropertyDefinition(materialId, batchPropertyId); - final PropertyValueRecord propertyValueRecord = new PropertyValueRecord(dataManagerContext); - Object batchPropertyValue = propertyValues.get(batchPropertyId); - if (batchPropertyValue == null) { - batchPropertyValue = propertyDefinition.getDefaultValue(); - } - propertyValueRecord.setPropertyValue(batchPropertyValue); - map.put(batchPropertyId, propertyValueRecord); - } - batchPropertyMap.put(batchRecord.batchId, map); - BatchId result = batchRecord.batchId; - dataManagerContext.releaseEvent(new BatchAdditionEvent(batchRecord.batchId)); - return result; - } - - private void validateValueCompatibility(final Object propertyId, final PropertyDefinition propertyDefinition, final Object propertyValue) { - if (!propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())) { - throw new ContractException(PropertyError.INCOMPATIBLE_VALUE, - "Property value " + propertyValue + " is not of type " + propertyDefinition.getType().getName() + " and does not match definition of " + propertyId); - } - } - - private void validateBatchPropertyValueNotNull(final Object propertyValue) { - if (propertyValue == null) { - throw new ContractException(MaterialsError.NULL_BATCH_PROPERTY_VALUE); - } - } - - private void validateNonnegativeFiniteMaterialAmount(final double amount) { - if (!Double.isFinite(amount)) { - throw new ContractException(MaterialsError.NON_FINITE_MATERIAL_AMOUNT); - } - if (amount < 0) { - throw new ContractException(MaterialsError.NEGATIVE_MATERIAL_AMOUNT); - } - } - - private void validateBatchConstructionInfoNotNull(final BatchConstructionInfo batchConstructionInfo) { - if (batchConstructionInfo == null) { - throw new ContractException(MaterialsError.NULL_BATCH_CONSTRUCTION_INFO); - } - } - - /** - * Creates a batch. Generates a corresponding - * {@linkplain BatchAdditionEvent} event - * - * @throws ContractException - * - *
  • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} if - * the materials producer id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} - * if the materials producer id is unknown
  • - *
  • {@linkplain MaterialsError#NULL_MATERIAL_ID} if the - * material id in the batch construction info is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_MATERIAL_ID} if the - * material id in the batch construction info is unknown
  • - *
  • {@linkplain MaterialsError#NON_FINITE_MATERIAL_AMOUNT} if - * the amount in the batch construction info is not finite
  • - *
  • {@linkplain MaterialsError#NEGATIVE_MATERIAL_AMOUNT} if - * the amount in the batch construction info is negative
  • - * - */ - public BatchId addBatch(MaterialsProducerId materialsProducerId, MaterialId materialId, double amount) { - validateMaterialsProducerId(materialsProducerId); - validateMaterialId(materialId); - validateNonnegativeFiniteMaterialAmount(amount); - - final MaterialsProducerRecord materialsProducerRecord = materialsProducerMap.get(materialsProducerId); - final BatchRecord batchRecord = new BatchRecord(nextBatchRecordId++); - batchRecord.amount = amount; - batchRecord.creationTime = dataManagerContext.getTime(); - batchRecord.materialId = materialId; - batchRecord.materialsProducerRecord = materialsProducerRecord; - materialsProducerRecord.inventory.add(batchRecord); - batchRecords.put(batchRecord.batchId, batchRecord); - - final Map map = new LinkedHashMap<>(); - final Set batchPropertyIds = batchPropertyIdMap.get(materialId); - for (final BatchPropertyId batchPropertyId : batchPropertyIds) { - final PropertyDefinition propertyDefinition = getBatchPropertyDefinition(materialId, batchPropertyId); - final PropertyValueRecord propertyValueRecord = new PropertyValueRecord(dataManagerContext); - propertyValueRecord.setPropertyValue(propertyDefinition.getDefaultValue()); - map.put(batchPropertyId, propertyValueRecord); - } - batchPropertyMap.put(batchRecord.batchId, map); - - BatchId batchId = batchRecord.batchId; - dataManagerContext.releaseEvent(new BatchAdditionEvent(batchId)); - return batchId; - } - - /** - * Transfers an amount of material from one batch to another batch of the - * same material type and material producer. Generates a corresponding - * {@linkplain BatchAmountUpdateEvent} for each batch. - * - * @throws ContractException - * - * - *
  • {@linkplain MaterialsError#NULL_BATCH_ID} if the source - * batch id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_BATCH_ID} if the - * source batch id is unknown
  • - *
  • {@linkplain MaterialsError#NULL_BATCH_ID} if the - * destination batch id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_BATCH_ID} if the - * destination batch id is unknown
  • - *
  • {@linkplain MaterialsError#REFLEXIVE_BATCH_SHIFT} if the - * source and destination batches are the same
  • - *
  • {@linkplain MaterialsError#MATERIAL_TYPE_MISMATCH} if the - * batches do not have the same material type
  • - *
  • {@linkplain MaterialsError#BATCH_SHIFT_WITH_MULTIPLE_OWNERS} - * if the batches have different owning materials producers
  • - *
  • {@linkplain MaterialsError#OFFERED_STAGE_UNALTERABLE} if - * the source batch is on a stage is offered
  • - *
  • {@linkplain MaterialsError#OFFERED_STAGE_UNALTERABLE} if - * the destination batch is on a stage is offered
  • - *
  • {@linkplain MaterialsError#NON_FINITE_MATERIAL_AMOUNT} if - * the shift amount is not a finite number
  • - *
  • {@linkplain MaterialsError#NEGATIVE_MATERIAL_AMOUNT} if - * the shift amount is negative
  • - *
  • {@linkplain MaterialsError#INSUFFICIENT_MATERIAL_AVAILABLE} - * if the shift amount exceeds the available material on the - * source batch
  • - *
  • {@linkplain MaterialsError#MATERIAL_ARITHMETIC_EXCEPTION} - * if the shift amount would cause an overflow on the - * destination batch
  • - * - * - * - * - */ - - public void transferMaterialBetweenBatches(BatchId sourceBatchId, BatchId destinationBatchId, double amount) { - validateBatchId(sourceBatchId); - validateBatchId(destinationBatchId); - validateDifferentBatchesForShift(sourceBatchId, destinationBatchId); - validateMaterialsMatchForShift(sourceBatchId, destinationBatchId); - validateProducersMatchForShift(sourceBatchId, destinationBatchId); - validateBatchIsNotOnOfferedStage(sourceBatchId); - validateBatchIsNotOnOfferedStage(destinationBatchId); - validateNonnegativeFiniteMaterialAmount(amount); - validateBatchHasSufficientMaterial(sourceBatchId, amount); - validateBatchHasSufficientUllage(destinationBatchId, amount); - - BatchRecord sourceBatchRecord = batchRecords.get(sourceBatchId); - BatchRecord destinationBatchRecord = batchRecords.get(destinationBatchId); - - double previousSourceAmount = sourceBatchRecord.amount; - double previousDestinationAmount = destinationBatchRecord.amount; - - sourceBatchRecord.amount -= amount; - destinationBatchRecord.amount += amount; - - double currentSourceAmount = sourceBatchRecord.amount; - double currentDestinationAmount = destinationBatchRecord.amount; - - dataManagerContext.releaseEvent(new BatchAmountUpdateEvent(sourceBatchId, previousSourceAmount, currentSourceAmount)); - dataManagerContext.releaseEvent(new BatchAmountUpdateEvent(destinationBatchId, previousDestinationAmount, currentDestinationAmount)); - } - - private void validateBatchHasSufficientUllage(BatchId batchId, double amount) { - if (!Double.isFinite(batchRecords.get(batchId).amount + amount)) { - throw new ContractException(MaterialsError.MATERIAL_ARITHMETIC_EXCEPTION); - } - } - - /* - * Precondition : batch must exist - */ - private void validateBatchHasSufficientMaterial(final BatchId batchId, final double amount) { - if (batchRecords.get(batchId).amount < amount) { - throw new ContractException(MaterialsError.INSUFFICIENT_MATERIAL_AVAILABLE); - } - } - - /* - * Preconditions : batch must exist - */ - private void validateBatchIsNotOnOfferedStage(final BatchId batchId) { - final BatchRecord batchRecord = batchRecords.get(batchId); - if (batchRecord.stageRecord != null) { - if (batchRecord.stageRecord.offered) { - throw new ContractException(MaterialsError.OFFERED_STAGE_UNALTERABLE); - } - } - } - - /* - * Preconditions : the batches must exist - */ - private void validateProducersMatchForShift(final BatchId sourceBatchId, final BatchId destinationBatchId) { - final MaterialsProducerId sourceMaterialsProducerId = batchRecords.get(sourceBatchId).materialsProducerRecord.materialProducerId; - final MaterialsProducerId destinationMaterialsProducerId = batchRecords.get(destinationBatchId).materialsProducerRecord.materialProducerId; - - if (!sourceMaterialsProducerId.equals(destinationMaterialsProducerId)) { - throw new ContractException(MaterialsError.BATCH_SHIFT_WITH_MULTIPLE_OWNERS); - } - - } - - /* - * Preconditions: The batches must exist - */ - private void validateMaterialsMatchForShift(final BatchId sourceBatchId, final BatchId destinationBatchId) { - final MaterialId sourceMaterialId = batchRecords.get(sourceBatchId).materialId; - final MaterialId destinationMaterialId = batchRecords.get(destinationBatchId).materialId; - - if (!sourceMaterialId.equals(destinationMaterialId)) { - throw new ContractException(MaterialsError.MATERIAL_TYPE_MISMATCH); - } - - } - - private void validateDifferentBatchesForShift(final BatchId sourceBatchId, final BatchId destinationBatchId) { - if (sourceBatchId.equals(destinationBatchId)) { - throw new ContractException(MaterialsError.REFLEXIVE_BATCH_SHIFT); - } - } - - /** - * Removes the given batch. Generates a corresponding - * {@linkplain BatchImminentRemovalEvent} - * - * @throws ContractException - * - *
  • {@linkplain MaterialsError#NULL_BATCH_ID} if the batch id - * is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_BATCH_ID} if the batch - * id is unknown
  • - *
  • {@linkplain MaterialsError#OFFERED_STAGE_UNALTERABLE} if - * the batch is on an offered stage
  • - * - * - * - */ - public void removeBatch(BatchId batchId) { - validateBatchId(batchId); - validateBatchIsNotOnOfferedStage(batchId); - dataManagerContext.addPlan((context) -> destroyBatch(batchId), dataManagerContext.getTime()); - dataManagerContext.releaseEvent(new BatchImminentRemovalEvent(batchId)); - } - - private void destroyBatch(final BatchId batchId) { - final BatchRecord batchRecord = batchRecords.get(batchId); - batchPropertyMap.remove(batchId); - /* - * Remove the batch from its stage and the master batch tracking map - */ - if (batchRecord.stageRecord != null) { - batchRecord.stageRecord.batchRecords.remove(batchRecord); - } - batchRecord.materialsProducerRecord.inventory.remove(batchRecord); - batchRecords.remove(batchId); - } - - /** - * - * Set a batch's property value. Generates a corresponding - * {@linkplain BatchPropertyUpdateEvent} - * - * @throws ContractException - * - *
  • {@linkplain MaterialsError#NULL_BATCH_ID} if the batch id - * is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_BATCH_ID} if the batch - * id is unknown
  • - *
  • {@linkplain MaterialsError#NULL_BATCH_PROPERTY_ID} if the - * batch property id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_BATCH_PROPERTY_ID} if - * the batch property id is unknown
  • - *
  • {@linkplain PropertyError#IMMUTABLE_VALUE} if batch - * property is not mutable
  • - *
  • {@linkplain MaterialsError#NULL_BATCH_PROPERTY_VALUE} if - * the batch property value is null
  • - *
  • {@linkplain PropertyError#INCOMPATIBLE_VALUE} if the - * batch property value is not compatible with the corresponding - * property definition
  • - *
  • {@linkplain MaterialsError#OFFERED_STAGE_UNALTERABLE} if - * the batch in on an offered stage
  • - *
  • {@linkplain MaterialsError#MATERIALS_OWNERSHIP} if the - * requesting agent is not the owning materials producer
  • - * - * - * - */ - public void setBatchPropertyValue(BatchId batchId, BatchPropertyId batchPropertyId, Object batchPropertyValue) { - validateBatchId(batchId); - BatchRecord batchRecord = batchRecords.get(batchId); - final MaterialId materialId = batchRecord.materialId; - validateBatchPropertyId(materialId, batchPropertyId); - final PropertyDefinition propertyDefinition = batchPropertyDefinitions.get(materialId).get(batchPropertyId); - validatePropertyMutability(propertyDefinition); - validateBatchPropertyValueNotNull(batchPropertyValue); - validateValueCompatibility(batchPropertyId, propertyDefinition, batchPropertyValue); - validateBatchIsNotOnOfferedStage(batchId); - final Map map = batchPropertyMap.get(batchId); - final PropertyValueRecord propertyValueRecord = map.get(batchPropertyId); - Object previousPropertyValue = propertyValueRecord.getValue(); - propertyValueRecord.setPropertyValue(batchPropertyValue); - dataManagerContext.releaseEvent(new BatchPropertyUpdateEvent(batchId, batchPropertyId, previousPropertyValue, batchPropertyValue)); - } - - private void validatePropertyMutability(final PropertyDefinition propertyDefinition) { - if (!propertyDefinition.propertyValuesAreMutable()) { - throw new ContractException(PropertyError.IMMUTABLE_VALUE); - } - } - - /** - * Assigns a property value to a materials producer. Generates a - * corresponding - * {@linkplain MaterialsProducerPropertyUpdateEvent} - * - * @throws ContractException - * - * - *
  • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} if - * the materials producer id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} - * if the materials producer id is unknown
  • - *
  • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_PROPERTY_ID} - * if the materials producer property id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_PROPERTY_ID} - * if the materials producer property id is unknown
  • - *
  • {@linkplain PropertyError#IMMUTABLE_VALUE} if the - * materials producer property is immutable
  • - *
  • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_PROPERTY_VALUE} - * if the property value is null
  • - * {@linkplain PropertyError#INCOMPATIBLE_VALUE} if the property - * value is incompatible with the corresponding property - * definition - * - * - */ - public void setMaterialsProducerPropertyValue(MaterialsProducerId materialsProducerId, MaterialsProducerPropertyId materialsProducerPropertyId, Object materialsProducerPropertyValue) { - validateMaterialsProducerId(materialsProducerId); - validateMaterialsProducerPropertyId(materialsProducerPropertyId); - final PropertyDefinition propertyDefinition = materialsProducerPropertyDefinitions.get(materialsProducerPropertyId); - validatePropertyMutability(propertyDefinition); - validateMaterialProducerPropertyValueNotNull(materialsProducerPropertyValue); - validateValueCompatibility(materialsProducerPropertyId, propertyDefinition, materialsProducerPropertyValue); - PropertyValueRecord propertyValueRecord = materialsProducerPropertyMap.get(materialsProducerId).get(materialsProducerPropertyId); - Object oldPropertyValue = propertyValueRecord.getValue(); - propertyValueRecord.setPropertyValue(materialsProducerPropertyValue); - dataManagerContext.releaseEvent(new MaterialsProducerPropertyUpdateEvent(materialsProducerId, materialsProducerPropertyId, oldPropertyValue, materialsProducerPropertyValue)); - } - - private void validateMaterialProducerPropertyValueNotNull(final Object propertyValue) { - if (propertyValue == null) { - throw new ContractException(MaterialsError.NULL_MATERIALS_PRODUCER_PROPERTY_VALUE); - } - } - - /** - *
  • Removes a batch from a non-offered stage, placing it into the - * materials producer's inventory. Generates a corresponding - * {@linkplain StageMembershipRemovalEvent} - * - * @throws ContractException - * - *
  • {@linkplain MaterialsError#NULL_BATCH_ID} if the batch id - * is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_BATCH_ID} if the batch - * id is unknown
  • - *
  • {@linkplain MaterialsError#MATERIALS_OWNERSHIP} if the - * requesting agent is not the owning materials producer
  • - *
  • {@linkplain MaterialsError#BATCH_NOT_STAGED} if the batch - * is not staged
  • - *
  • {@linkplain MaterialsError#OFFERED_STAGE_UNALTERABLE } if - * the stage containing the batch is offered
  • - * - */ - public void moveBatchToInventory(BatchId batchId) { - validateBatchId(batchId); - BatchRecord batchRecord = batchRecords.get(batchId); - validateBatchIsStaged(batchId); - final StageId stageId = batchRecord.stageRecord.stageId; - validateStageIsNotOffered(stageId); - batchRecord.stageRecord.batchRecords.remove(batchRecord); - batchRecord.stageRecord = null; - batchRecord.materialsProducerRecord.inventory.add(batchRecord); - dataManagerContext.releaseEvent(new StageMembershipRemovalEvent(batchId, stageId)); - } - - private void validateStageIsNotOffered(final StageId stageId) { - - final StageRecord stageRecord = stageRecords.get(stageId); - - if (stageRecord.offered) { - throw new ContractException(MaterialsError.OFFERED_STAGE_UNALTERABLE); - } - } - - private void validateBatchIsStaged(final BatchId batchId) { - final BatchRecord batchRecord = batchRecords.get(batchId); - if (batchRecord.stageRecord == null) { - throw new ContractException(MaterialsError.BATCH_NOT_STAGED); - } - } - - /** - * Removes a batch frominventory, placing it on a stage. Generates a - * corresponding {@linkplain StageMembershipAdditionEvent} - * - * @throws ContractException - * - * - *
  • {@linkplain MaterialsError#NULL_BATCH_ID} if the batch id - * is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_BATCH_ID} if the batch - * id is unknown
  • - *
  • {@linkplain MaterialsError#BATCH_ALREADY_STAGED} if the - * batch is already staged
  • - *
  • {@linkplain MaterialsError#NULL_STAGE_ID} if the stage id - * is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_STAGE_ID} if the stage - * id is unknown
  • - *
  • {@linkplain MaterialsError#OFFERED_STAGE_UNALTERABLE} if - * the stage is offered
  • - *
  • {@linkplain MaterialsError#BATCH_STAGED_TO_DIFFERENT_OWNER} - * if batch and stage do not have the same owning materials - * producer
  • - *
  • {@linkplain MaterialsError#MATERIALS_OWNERSHIP} if the - * requesting agent is not the owning material producer
  • - * - * - */ - public void moveBatchToStage(BatchId batchId, StageId stageId) { - - validateBatchId(batchId); - validateBatchIsNotStaged(batchId); - validateStageId(stageId); - validateStageIsNotOffered(stageId); - validateBatchAndStageOwnersMatch(batchId, stageId); - - final BatchRecord batchRecord = batchRecords.get(batchId); - final StageRecord stageRecord = stageRecords.get(stageId); - batchRecord.stageRecord = stageRecord; - stageRecord.batchRecords.add(batchRecord); - batchRecord.materialsProducerRecord.inventory.remove(batchRecord); - dataManagerContext.releaseEvent(new StageMembershipAdditionEvent(batchId, stageId)); - } - - private void validateBatchAndStageOwnersMatch(final BatchId batchId, final StageId stageId) { - final MaterialsProducerId batchProducerId = batchRecords.get(batchId).materialsProducerRecord.materialProducerId; - final MaterialsProducerId stageProducerId = stageRecords.get(stageId).materialsProducerRecord.materialProducerId; - if (!batchProducerId.equals(stageProducerId)) { - throw new ContractException(MaterialsError.BATCH_STAGED_TO_DIFFERENT_OWNER); - } - } - - private void validateBatchIsNotStaged(final BatchId batchId) { - final BatchRecord batchRecord = batchRecords.get(batchId); - if (batchRecord.stageRecord != null) { - throw new ContractException(MaterialsError.BATCH_ALREADY_STAGED); - } - } - - /** - * - * Transfers an offered stage from the current owning materials producer to - * another and marks the stage as not offered. Generates the corresponding - * {@linkplain StageMaterialsProducerUpdateEvent} - * {@linkplain StageOfferUpdateEvent} - * - * @throws ContractException - * - * - *
  • {@linkplain MaterialsError#NULL_STAGE_ID } if the stage - * id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_STAGE_ID } if the - * stage id is unknown
  • - *
  • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID } - * if the materials producer id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID } - * if the materials producer id is unknown
  • - *
  • {@linkplain MaterialsError#UNOFFERED_STAGE_NOT_TRANSFERABLE } - * if the stage is not offered
  • - *
  • {@linkplain MaterialsError#REFLEXIVE_STAGE_TRANSFER } if - * the source and destination materials producers are the - * same
  • - * - * - */ - public void transferOfferedStage(StageId stageId, MaterialsProducerId materialsProducerId) { - validateStageId(stageId); - validateMaterialsProducerId(materialsProducerId); - validateStageIsOffered(stageId); - validateStageNotOwnedByReceivingMaterialsProducer(stageId, materialsProducerId); - final StageRecord stageRecord = stageRecords.get(stageId); - final MaterialsProducerRecord currentMaterialsProducerRecord = stageRecord.materialsProducerRecord; - MaterialsProducerId stageProducer = currentMaterialsProducerRecord.materialProducerId; - final MaterialsProducerRecord newMaterialsProducerRecord = materialsProducerMap.get(materialsProducerId); - currentMaterialsProducerRecord.stageRecords.remove(stageRecord); - newMaterialsProducerRecord.stageRecords.add(stageRecord); - stageRecord.materialsProducerRecord = newMaterialsProducerRecord; - for (final BatchRecord batchRecord : stageRecord.batchRecords) { - batchRecord.materialsProducerRecord = newMaterialsProducerRecord; - } - stageRecord.offered = false; - dataManagerContext.releaseEvent(new StageMaterialsProducerUpdateEvent(stageId, stageProducer, materialsProducerId)); - dataManagerContext.releaseEvent(new StageOfferUpdateEvent(stageId, true, false)); - } - - private void validateStageNotOwnedByReceivingMaterialsProducer(final StageId stageId, final MaterialsProducerId materialsProducerId) { - final MaterialsProducerId stageProducer = stageRecords.get(stageId).materialsProducerRecord.materialProducerId; - if (materialsProducerId.equals(stageProducer)) { - throw new ContractException(MaterialsError.REFLEXIVE_STAGE_TRANSFER); - } - } - - private void validateStageIsOffered(final StageId stageId) { - StageRecord stageRecord = stageRecords.get(stageId); - if (!stageRecord.offered) { - throw new ContractException(MaterialsError.UNOFFERED_STAGE_NOT_TRANSFERABLE); - } - } - - /** - * - * - * Transfers a resource amount from a materials producer to a region. - * Generates a corresponding {@linkplain RegionResourceAdditionEvent} and - * {@linkplain MaterialsProducerResourceUpdateEvent} - * - * @throws ContractException - * * - * - *
  • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
  • - *
  • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if the - * resource id is unknown
  • - *
  • {@linkplain RegionError#NULL_REGION_ID} if the region id - * is null
  • - *
  • {@linkplain RegionError#UNKNOWN_REGION_ID} if the region - * id is unknown
  • - *
  • {@linkplain MaterialsError#NULL_MATERIALS_PRODUCER_ID} if - * the materials producer id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_MATERIALS_PRODUCER_ID} - * if the materials producer id is unknown
  • - *
  • {@linkplain ResourceError#NEGATIVE_RESOURCE_AMOUNT} if - * the materials amount is negative
  • - *
  • {@linkplain ResourceError#INSUFFICIENT_RESOURCES_AVAILABLE} - * if the materials amount exceeds the resource level of the - * materials producer
  • - *
  • {@linkplain ResourceError#RESOURCE_ARITHMETIC_EXCEPTION} - * if the materials amount would cause an overflow of the - * regions resource level
  • - * - * - * - */ - public void transferResourceToRegion(MaterialsProducerId materialsProducerId, ResourceId resourceId, RegionId regionId, long amount) { - - validateResourceId(resourceId); - validateRegionId(regionId); - validateMaterialsProducerId(materialsProducerId); - validateNonnegativeResourceAmount(amount); - validateMaterialsProducerHasSufficientResource(materialsProducerId, resourceId, amount); - final long currentResourceLevel = resourcesDataManager.getRegionResourceLevel(regionId, resourceId); - validateResourceAdditionValue(currentResourceLevel, amount); - - final MaterialsProducerRecord materialsProducerRecord = materialsProducerMap.get(materialsProducerId); - final ComponentResourceRecord componentResourceRecord = materialsProducerRecord.materialProducerResources.get(resourceId); - long previousMaterialsProducerResourceLevel = componentResourceRecord.getAmount(); - componentResourceRecord.decrementAmount(amount); - long currentMaterialsProducerResourceLevel = componentResourceRecord.getAmount(); - resourcesDataManager.addResourceToRegion(resourceId, regionId, amount); - dataManagerContext.releaseEvent( - new MaterialsProducerResourceUpdateEvent(materialsProducerId, resourceId, previousMaterialsProducerResourceLevel, currentMaterialsProducerResourceLevel)); - } - - private void validateResourceAdditionValue(final long currentResourceLevel, final long amount) { - try { - Math.addExact(currentResourceLevel, amount); - } catch (final ArithmeticException e) { - throw new ContractException(ResourceError.RESOURCE_ARITHMETIC_EXCEPTION); - } - } - - private void validateMaterialsProducerHasSufficientResource(final MaterialsProducerId materialsProducerId, final ResourceId resourceId, final long amount) { - final MaterialsProducerRecord materialsProducerRecord = materialsProducerMap.get(materialsProducerId); - final ComponentResourceRecord componentResourceRecord = materialsProducerRecord.materialProducerResources.get(resourceId); - if (componentResourceRecord.getAmount() < amount) { - throw new ContractException(ResourceError.INSUFFICIENT_RESOURCES_AVAILABLE); - } - } - - private void validateNonnegativeResourceAmount(final long amount) { - if (amount < 0) { - throw new ContractException(ResourceError.NEGATIVE_RESOURCE_AMOUNT); - } - } - - private void validateRegionId(final RegionId regionId) { - - if (regionId == null) { - throw new ContractException(RegionError.NULL_REGION_ID); - } - - if (!regionsDataManager.regionIdExists(regionId)) { - throw new ContractException(RegionError.UNKNOWN_REGION_ID, regionId); - } - } - - /** - * Creates a stage. Generates a corresponding - * {@linkplain StageAdditionEvent} - * - * @throws ContractException - * - *
  • {@linkplain MaterialsError.NULL_MATERIALS_PRODUCER_ID} if - * the materials producer id is unknown
  • - * - *
  • {@linkplain MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID} - * if the materials producer id is unknown
  • - * - */ - public StageId addStage(final MaterialsProducerId materialsProducerId) { - validateMaterialsProducerId(materialsProducerId); - final MaterialsProducerRecord materialsProducerRecord = materialsProducerMap.get(materialsProducerId); - final StageRecord stageRecord = new StageRecord(nextStageRecordId++); - stageRecord.materialsProducerRecord = materialsProducerRecord; - materialsProducerRecord.stageRecords.add(stageRecord); - stageRecords.put(stageRecord.stageId, stageRecord); - dataManagerContext.releaseEvent(new StageAdditionEvent(stageRecord.stageId)); - return stageRecord.stageId; - } - - /** - * - * Destroys a non-offered stage and optionally destroys any associated - * batches. Generates the corresponding - * {@linkplain BatchImminentRemovalEvent} and - * {@linkplain StageImminentRemovalEvent} events. - * - * @throws ContractException - *
  • {@linkplain MaterialsError#NULL_STAGE_ID} if the stage id - * is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_STAGE_ID} if the stage - * id is unknown
  • - *
  • {@linkplain MaterialsError#OFFERED_STAGE_UNALTERABLE} if - * stage is offered
  • - */ - public void removeStage(StageId stageId, boolean destroyBatches) { - validateStageId(stageId); - validateStageIsNotOffered(stageId); - StageRecord stageRecord = stageRecords.get(stageId); - - if (destroyBatches) { - // plan for the removal of the stage and the batches - dataManagerContext.addPlan((context) -> { - for (final BatchRecord batchRecord : stageRecord.batchRecords) { - batchPropertyMap.remove(batchRecord.batchId); - batchRecords.remove(batchRecord.batchId); - } - stageRecord.materialsProducerRecord.stageRecords.remove(stageRecord); - stageRecords.remove(stageId); - }, dataManagerContext.getTime()); - - for (BatchRecord batchRecord : stageRecord.batchRecords) { - dataManagerContext.releaseEvent(new BatchImminentRemovalEvent(batchRecord.batchId)); - } - dataManagerContext.releaseEvent(new StageImminentRemovalEvent(stageId)); - } else { - // plan for the removal of the stage and return of the batches to - // inventory - dataManagerContext.addPlan((context) -> { - for (final BatchRecord batchRecord : stageRecord.batchRecords) { - batchRecord.stageRecord = null; - batchRecord.materialsProducerRecord.inventory.add(batchRecord); - } - stageRecord.materialsProducerRecord.stageRecords.remove(stageRecord); - stageRecords.remove(stageId); - }, dataManagerContext.getTime()); - - dataManagerContext.releaseEvent(new StageImminentRemovalEvent(stageId)); - } - } - - /** - * Sets the offer state of a stage. Generates a corresponding - * {@linkplain StageOfferUpdateEvent} - * - * @throws ContractException - * - * - *
  • {@linkplain MaterialsError#NULL_STAGE_ID } if the stage - * id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_STAGE_ID } if the - * stage id is unknown
  • - * - */ - public void setStageOfferState(StageId stageId, boolean offer) { - validateStageId(stageId); - final StageRecord stageRecord = stageRecords.get(stageId); - boolean previousState = stageRecord.offered; - stageRecord.offered = offer; - dataManagerContext.releaseEvent(new StageOfferUpdateEvent(stageId, previousState, offer)); - } - - /** - * - * Converts a non-offered stage, including its associated batches, into a - * new batch of the given material. The new batch is placed into inventory. - * Generates a corresponding - * {@linkplain BatchImminentRemovalEvent}, - * {@linkplain BatchAdditionEvent} and - * {@linkplain StageImminentRemovalEvent} events - * - * @throws ContractException - * - * - * - *
  • {@linkplain MaterialsError#NULL_MATERIAL_ID} if the - * material id is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_MATERIAL_ID} if the - * material id is unknown
  • - *
  • {@linkplain MaterialsError#NULL_STAGE_ID} if the stage id - * is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_STAGE_ID} if stage id - * is unknown
  • - *
  • {@linkplain MaterialsError#MATERIALS_OWNERSHIP} if the - * requesting agent is not the owning materials producer
  • - *
  • {@linkplain MaterialsError#OFFERED_STAGE_UNALTERABLE} if - * the stage is offered
  • - *
  • {@linkplain MaterialsError#NON_FINITE_MATERIAL_AMOUNT} if - * the material amount is not finite
  • - *
  • {@linkplain MaterialsError#NEGATIVE_MATERIAL_AMOUNT} if - * the material amount is negative
  • - * - * - */ - public BatchId convertStageToBatch(StageId stageId, MaterialId materialId, double amount) { - - validateMaterialId(materialId); - validateStageId(stageId); - StageRecord stageRecord = stageRecords.get(stageId); - final MaterialsProducerId materialsProducerId = stageRecord.materialsProducerRecord.materialProducerId; - validateStageIsNotOffered(stageId); - validateNonnegativeFiniteMaterialAmount(amount); - - // add the new batch - final MaterialsProducerRecord materialsProducerRecord = materialsProducerMap.get(materialsProducerId); - final BatchRecord newBatchRecord = new BatchRecord(nextBatchRecordId++); - newBatchRecord.amount = amount; - newBatchRecord.creationTime = dataManagerContext.getTime(); - newBatchRecord.materialId = materialId; - newBatchRecord.materialsProducerRecord = materialsProducerRecord; - materialsProducerRecord.inventory.add(newBatchRecord); - batchRecords.put(newBatchRecord.batchId, newBatchRecord); - - final Map map = new LinkedHashMap<>(); - final Set batchPropertyIds = batchPropertyIdMap.get(materialId); - for (final BatchPropertyId batchPropertyId : batchPropertyIds) { - final PropertyDefinition propertyDefinition = getBatchPropertyDefinition(materialId, batchPropertyId); - final PropertyValueRecord propertyValueRecord = new PropertyValueRecord(dataManagerContext); - propertyValueRecord.setPropertyValue(propertyDefinition.getDefaultValue()); - map.put(batchPropertyId, propertyValueRecord); - } - batchPropertyMap.put(newBatchRecord.batchId, map); - - BatchId batchId = newBatchRecord.batchId; - - dataManagerContext.addPlan((context) -> { - for (final BatchRecord batchRecord : stageRecord.batchRecords) { - batchPropertyMap.remove(batchRecord.batchId); - batchRecords.remove(batchRecord.batchId); - } - stageRecord.materialsProducerRecord.stageRecords.remove(stageRecord); - stageRecords.remove(stageId); - - }, dataManagerContext.getTime()); - - dataManagerContext.releaseEvent(new BatchAdditionEvent(batchId)); - for (BatchRecord batchRec : stageRecord.batchRecords) { - dataManagerContext.releaseEvent(new BatchImminentRemovalEvent(batchRec.batchId)); - } - dataManagerContext.releaseEvent(new StageImminentRemovalEvent(stageId)); - return batchId; - } - - /** - * - * Converts a non-offered stage, including its associated batches, into an - * amount of resource. The resulting resource is owned by the associated - * material producer. Generates the corresponding - * {@linkplain BatchImminentRemovalEvent}, - * {@linkplain MaterialsProducerResourceUpdateEvent} and - * {@linkplain StageImminentRemovalEvent} events. - * - * - * @throws ContractException - * - * - *
  • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
  • - *
  • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if the - * resource id is unknown
  • - *
  • {@linkplain MaterialsError#NULL_STAGE_ID} if the stage id - * is null
  • - *
  • {@linkplain MaterialsError#UNKNOWN_STAGE_ID} if the stage - * id is unknown
  • - *
  • {@linkplain MaterialsError#OFFERED_STAGE_UNALTERABLE} if - * the stage is offered
  • - *
  • {@linkplain MaterialsError#MATERIALS_OWNERSHIP} if the - * requesting agent is not the owning materials producer
  • - *
  • {@linkplain ResourceError#NEGATIVE_RESOURCE_AMOUNT} if - * the the resource amount is negative
  • - *
  • {@linkplain ResourceError#RESOURCE_ARITHMETIC_EXCEPTION} - * if the resource amount would cause an overflow of the - * materials producer's resource level
  • - * - * - */ - public void convertStageToResource(StageId stageId, ResourceId resourceId, long amount) { - - validateResourceId( resourceId); - validateStageId( stageId); - validateStageIsNotOffered(stageId); - StageRecord stageRecord = stageRecords.get(stageId); - final MaterialsProducerId materialsProducerId = stageRecord.materialsProducerRecord.materialProducerId; - validateNonnegativeResourceAmount( amount); - final MaterialsProducerRecord materialsProducerRecord = materialsProducerMap.get(materialsProducerId); - final ComponentResourceRecord componentResourceRecord = materialsProducerRecord.materialProducerResources.get(resourceId); - long previousResourceLevel = componentResourceRecord.getAmount(); - validateResourceAdditionValue( previousResourceLevel, amount); - - dataManagerContext.addPlan((context) -> { - for (final BatchRecord batchRecord : stageRecord.batchRecords) { - batchPropertyMap.remove(batchRecord.batchId); - batchRecords.remove(batchRecord.batchId); - } - stageRecord.materialsProducerRecord.stageRecords.remove(stageRecord); - stageRecords.remove(stageId); - - }, dataManagerContext.getTime()); - componentResourceRecord.incrementAmount(amount); - - long currentResourceLevel = componentResourceRecord.getAmount(); - dataManagerContext.releaseEvent(new MaterialsProducerResourceUpdateEvent(materialsProducerId, resourceId, previousResourceLevel, currentResourceLevel)); - - for (BatchRecord batchRecord : stageRecord.batchRecords) { - dataManagerContext.releaseEvent(new BatchImminentRemovalEvent(batchRecord.batchId)); - } - dataManagerContext.releaseEvent(new StageImminentRemovalEvent(stageId)); - - - } - -} diff --git a/gcm3/src/main/java/plugins/materials/events/BatchAdditionEvent.java b/gcm3/src/main/java/plugins/materials/events/BatchAdditionEvent.java deleted file mode 100644 index 6d9840ef0..000000000 --- a/gcm3/src/main/java/plugins/materials/events/BatchAdditionEvent.java +++ /dev/null @@ -1,25 +0,0 @@ -package plugins.materials.events; - -import net.jcip.annotations.Immutable; -import nucleus.Event; -import plugins.materials.support.BatchId; - -@Immutable -public class BatchAdditionEvent implements Event { - private final BatchId batchId; - - public BatchAdditionEvent(final BatchId batchId) { - super(); - this.batchId = batchId; - } - - public BatchId getBatchId() { - return batchId; - } - - @Override - public String toString() { - return "BatchAdditionEvent [batchId=" + batchId + "]"; - } - -} diff --git a/gcm3/src/main/java/plugins/materials/events/BatchAmountUpdateEvent.java b/gcm3/src/main/java/plugins/materials/events/BatchAmountUpdateEvent.java deleted file mode 100644 index 98cc6ee07..000000000 --- a/gcm3/src/main/java/plugins/materials/events/BatchAmountUpdateEvent.java +++ /dev/null @@ -1,37 +0,0 @@ -package plugins.materials.events; - -import net.jcip.annotations.Immutable; -import nucleus.Event; -import plugins.materials.support.BatchId; - -@Immutable -public class BatchAmountUpdateEvent implements Event { - private final BatchId batchId; - private final double previousAmount; - private final double currentAmount; - - public BatchAmountUpdateEvent(BatchId batchId, double previousAmount, double currentAmount) { - super(); - this.batchId = batchId; - this.previousAmount = previousAmount; - this.currentAmount = currentAmount; - } - - public BatchId getBatchId() { - return batchId; - } - - public double getPreviousAmount() { - return previousAmount; - } - - public double getCurrentAmount() { - return currentAmount; - } - - @Override - public String toString() { - return "BatchAmountUpdateEvent [batchId=" + batchId + ", previousAmount=" + previousAmount + ", currentAmount=" + currentAmount + "]"; - } - -} diff --git a/gcm3/src/main/java/plugins/materials/events/BatchImminentRemovalEvent.java b/gcm3/src/main/java/plugins/materials/events/BatchImminentRemovalEvent.java deleted file mode 100644 index f581a3f40..000000000 --- a/gcm3/src/main/java/plugins/materials/events/BatchImminentRemovalEvent.java +++ /dev/null @@ -1,29 +0,0 @@ -package plugins.materials.events; - -import net.jcip.annotations.Immutable; -import nucleus.Event; -import plugins.materials.support.BatchId; - -@Immutable -public class BatchImminentRemovalEvent implements Event { - private final BatchId batchId; - - public BatchImminentRemovalEvent(final BatchId batchId) { - super(); - this.batchId = batchId; - } - - public BatchId getBatchId() { - return batchId; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("BatchImminentRemovalEvent [batchId="); - builder.append(batchId); - builder.append("]"); - return builder.toString(); - } - -} diff --git a/gcm3/src/main/java/plugins/materials/events/BatchPropertyUpdateEvent.java b/gcm3/src/main/java/plugins/materials/events/BatchPropertyUpdateEvent.java deleted file mode 100644 index c20b7ab11..000000000 --- a/gcm3/src/main/java/plugins/materials/events/BatchPropertyUpdateEvent.java +++ /dev/null @@ -1,46 +0,0 @@ -package plugins.materials.events; - -import net.jcip.annotations.Immutable; -import nucleus.Event; -import plugins.materials.support.BatchId; -import plugins.materials.support.BatchPropertyId; - -@Immutable -public class BatchPropertyUpdateEvent implements Event { - private final BatchId batchId; - private final BatchPropertyId batchPropertyId; - private final Object previousPropertyValue; - private final Object currentPropertyValue; - - public BatchPropertyUpdateEvent(BatchId batchId, BatchPropertyId batchPropertyId, Object previousPropertyValue, Object currentPropertyValue) { - super(); - this.batchId = batchId; - this.batchPropertyId = batchPropertyId; - this.previousPropertyValue = previousPropertyValue; - this.currentPropertyValue = currentPropertyValue; - } - - public BatchId getBatchId() { - return batchId; - } - - public BatchPropertyId getBatchPropertyId() { - return batchPropertyId; - } - - public Object getPreviousPropertyValue() { - return previousPropertyValue; - } - - public Object getCurrentPropertyValue() { - return currentPropertyValue; - } - - @Override - public String toString() { - return "BatchPropertyUpdateEvent [batchId=" + batchId + ", batchPropertyId=" + batchPropertyId + ", previousPropertyValue=" + previousPropertyValue + ", currentPropertyValue=" - + currentPropertyValue + "]"; - } - - -} diff --git a/gcm3/src/main/java/plugins/materials/events/MaterialsProducerPropertyUpdateEvent.java b/gcm3/src/main/java/plugins/materials/events/MaterialsProducerPropertyUpdateEvent.java deleted file mode 100644 index b1003dea0..000000000 --- a/gcm3/src/main/java/plugins/materials/events/MaterialsProducerPropertyUpdateEvent.java +++ /dev/null @@ -1,106 +0,0 @@ -package plugins.materials.events; - -import net.jcip.annotations.Immutable; -import nucleus.Event; -import nucleus.EventLabel; -import nucleus.EventLabeler; -import nucleus.EventLabelerId; -import nucleus.SimulationContext; -import plugins.materials.datamangers.MaterialsDataManager; -import plugins.materials.support.MaterialsError; -import plugins.materials.support.MaterialsProducerId; -import plugins.materials.support.MaterialsProducerPropertyId; -import util.errors.ContractException; - -@Immutable -public class MaterialsProducerPropertyUpdateEvent implements Event { - private final MaterialsProducerId materialsProducerId; - private final MaterialsProducerPropertyId materialsProducerPropertyId; - private final Object previousPropertyValue; - private final Object currentPropertyValue; - - public MaterialsProducerPropertyUpdateEvent(MaterialsProducerId materialsProducerId, MaterialsProducerPropertyId materialsProducerPropertyId, Object previousPropertyValue, - Object currentPropertyValue) { - super(); - this.materialsProducerId = materialsProducerId; - this.materialsProducerPropertyId = materialsProducerPropertyId; - this.previousPropertyValue = previousPropertyValue; - this.currentPropertyValue = currentPropertyValue; - } - - public MaterialsProducerId getMaterialsProducerId() { - return materialsProducerId; - } - - public MaterialsProducerPropertyId getMaterialsProducerPropertyId() { - return materialsProducerPropertyId; - } - - public Object getPreviousPropertyValue() { - return previousPropertyValue; - } - - public Object getCurrentPropertyValue() { - return currentPropertyValue; - } - - @Override - public String toString() { - return "MaterialsProducerPropertyUpdateEvent [materialsProducerId=" + materialsProducerId + ", materialsProducerPropertyId=" + materialsProducerPropertyId + ", previousPropertyValue=" - + previousPropertyValue + ", currentPropertyValue=" + currentPropertyValue + "]"; - } - - private static enum LabelerId implements EventLabelerId { - PRODUCER_PROPERTY - } - - private static void validateMaterialsProducerId(SimulationContext simulationContext, MaterialsProducerId materialsProducerId) { - if (materialsProducerId == null) { - throw new ContractException(MaterialsError.NULL_MATERIALS_PRODUCER_ID); - } - MaterialsDataManager materialsDataManager = simulationContext.getDataManager(MaterialsDataManager.class); - if (!materialsDataManager.materialsProducerIdExists(materialsProducerId)) { - throw new ContractException(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID); - } - } - - private static void validateMaterialsProducerPropertyId(SimulationContext simulationContext, MaterialsProducerPropertyId materialsProducerPropertyId) { - if (materialsProducerPropertyId == null) { - throw new ContractException(MaterialsError.NULL_MATERIALS_PRODUCER_PROPERTY_ID); - } - MaterialsDataManager materialsDataManager = simulationContext.getDataManager(MaterialsDataManager.class); - if (!materialsDataManager.materialsProducerPropertyIdExists(materialsProducerPropertyId)) { - throw new ContractException(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_PROPERTY_ID); - } - } - - public static EventLabel getEventLabelByMaterialsProducerAndProperty(SimulationContext simulationContext, MaterialsProducerId materialsProducerId, - MaterialsProducerPropertyId materialsProducerPropertyId) { - validateMaterialsProducerId(simulationContext, materialsProducerId); - validateMaterialsProducerPropertyId(simulationContext, materialsProducerPropertyId); - return _getEventLabelByMaterialsProducerAndProperty(materialsProducerId, materialsProducerPropertyId);// - } - - private static EventLabel _getEventLabelByMaterialsProducerAndProperty(MaterialsProducerId materialsProducerId, - MaterialsProducerPropertyId materialsProducerPropertyId) { - - return EventLabel.builder(MaterialsProducerPropertyUpdateEvent.class)// - .setEventLabelerId(LabelerId.PRODUCER_PROPERTY)// - .addKey(materialsProducerPropertyId)// - .addKey(materialsProducerId)// - .build();// - } - - public static EventLabeler getEventLabelerForMaterialsProducerAndProperty() { - return EventLabeler .builder(MaterialsProducerPropertyUpdateEvent.class)// - .setEventLabelerId(LabelerId.PRODUCER_PROPERTY)// - .setLabelFunction((context, event) -> _getEventLabelByMaterialsProducerAndProperty(event.getMaterialsProducerId(), event.getMaterialsProducerPropertyId()))// - .build(); - } - - @Override - public Object getPrimaryKeyValue() { - return this.materialsProducerPropertyId; - } - -} diff --git a/gcm3/src/main/java/plugins/materials/events/MaterialsProducerResourceUpdateEvent.java b/gcm3/src/main/java/plugins/materials/events/MaterialsProducerResourceUpdateEvent.java deleted file mode 100644 index d0488f5e5..000000000 --- a/gcm3/src/main/java/plugins/materials/events/MaterialsProducerResourceUpdateEvent.java +++ /dev/null @@ -1,129 +0,0 @@ -package plugins.materials.events; - -import net.jcip.annotations.Immutable; -import nucleus.Event; -import nucleus.EventLabel; -import nucleus.EventLabeler; -import nucleus.EventLabelerId; -import nucleus.SimulationContext; -import plugins.materials.datamangers.MaterialsDataManager; -import plugins.materials.support.MaterialsError; -import plugins.materials.support.MaterialsProducerId; -import plugins.resources.datamanagers.ResourcesDataManager; -import plugins.resources.support.ResourceError; -import plugins.resources.support.ResourceId; -import util.errors.ContractException; - -@Immutable -public class MaterialsProducerResourceUpdateEvent implements Event { - private final MaterialsProducerId materialsProducerId; - private final ResourceId resourceId; - private final long previousResourceLevel; - private final long currentResourceLevel; - - public MaterialsProducerResourceUpdateEvent(MaterialsProducerId materialsProducerId, ResourceId resourceId, long previousResourceLevel, long currentResourceLevel) { - super(); - this.materialsProducerId = materialsProducerId; - this.resourceId = resourceId; - this.previousResourceLevel = previousResourceLevel; - this.currentResourceLevel = currentResourceLevel; - } - - public MaterialsProducerId getMaterialsProducerId() { - return materialsProducerId; - } - - public ResourceId getResourceId() { - return resourceId; - } - - public long getPreviousResourceLevel() { - return previousResourceLevel; - } - - public long getCurrentResourceLevel() { - return currentResourceLevel; - } - - @Override - public String toString() { - return "MaterialsProducerResourceUpdateEvent [materialsProducerId=" + materialsProducerId + ", resourceId=" + resourceId + ", previousResourceLevel=" + previousResourceLevel - + ", currentResourceLevel=" + currentResourceLevel + "]"; - } - - private static enum LabelerId implements EventLabelerId { - PRODUCER_RESOURCE, RESOURCE - } - - private static void validateMaterialProducerId(SimulationContext simulationContext, MaterialsProducerId materialsProducerId) { - if (materialsProducerId == null) { - throw new ContractException(MaterialsError.NULL_MATERIALS_PRODUCER_ID); - } - MaterialsDataManager materialsDataManager = simulationContext.getDataManager(MaterialsDataManager.class); - if (!materialsDataManager.materialsProducerIdExists(materialsProducerId)) { - throw new ContractException(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID); - } - } - - private static void validateResourceId(SimulationContext simulationContext, ResourceId resourceId) { - if (resourceId == null) { - throw new ContractException(ResourceError.NULL_RESOURCE_ID); - } - ResourcesDataManager resourcesDataManager = simulationContext.getDataManager(ResourcesDataManager.class); - if (!resourcesDataManager.resourceIdExists(resourceId)) { - throw new ContractException(ResourceError.UNKNOWN_RESOURCE_ID); - } - } - - public static EventLabel getEventLabelByMaterialsProducerAndResource(SimulationContext simulationContext, MaterialsProducerId materialsProducerId, - ResourceId resourceId) { - validateMaterialProducerId(simulationContext, materialsProducerId); - validateResourceId(simulationContext, resourceId); - return _getEventLabelByMaterialsProducerAndResource(materialsProducerId, resourceId);// - - } - - private static EventLabel _getEventLabelByMaterialsProducerAndResource(MaterialsProducerId materialsProducerId, - ResourceId resourceId) { - return EventLabel .builder(MaterialsProducerResourceUpdateEvent.class)// - .setEventLabelerId(LabelerId.PRODUCER_RESOURCE)// - .addKey(resourceId)// - .addKey(materialsProducerId)// - .build();// - - } - - - public static EventLabeler getEventLabelerForMaterialsProducerAndResource() { - return EventLabeler .builder(MaterialsProducerResourceUpdateEvent.class)// - .setEventLabelerId(LabelerId.PRODUCER_RESOURCE)// - .setLabelFunction((context, event) -> _getEventLabelByMaterialsProducerAndResource(event.getMaterialsProducerId(), event.getResourceId()))// - .build(); - } - - public static EventLabel getEventLabelByResource(SimulationContext simulationContext, ResourceId resourceId) { - validateResourceId(simulationContext, resourceId); - return _getEventLabelByResource(resourceId);// - } - - private static EventLabel _getEventLabelByResource(ResourceId resourceId) { - - return EventLabel .builder(MaterialsProducerResourceUpdateEvent.class)// - .setEventLabelerId(LabelerId.RESOURCE)// - .addKey(resourceId)// - .build();// - } - - public static EventLabeler getEventLabelerForResource() { - return EventLabeler .builder(MaterialsProducerResourceUpdateEvent.class)// - .setEventLabelerId(LabelerId.RESOURCE)// - .setLabelFunction((context, event) -> _getEventLabelByResource(event.getResourceId()))// - .build(); - } - - @Override - public Object getPrimaryKeyValue() { - return resourceId; - } - -} diff --git a/gcm3/src/main/java/plugins/materials/events/StageAdditionEvent.java b/gcm3/src/main/java/plugins/materials/events/StageAdditionEvent.java deleted file mode 100644 index dd51c95b5..000000000 --- a/gcm3/src/main/java/plugins/materials/events/StageAdditionEvent.java +++ /dev/null @@ -1,26 +0,0 @@ -package plugins.materials.events; - -import net.jcip.annotations.Immutable; -import nucleus.Event; -import plugins.materials.support.StageId; - -@Immutable -public class StageAdditionEvent implements Event { - private final StageId stageId; - - public StageAdditionEvent(StageId stageId) { - super(); - this.stageId = stageId; - } - - public StageId getStageId() { - return stageId; - } - - @Override - public String toString() { - return "StageCreation [stageId=" + stageId + "]"; - } - - -} diff --git a/gcm3/src/main/java/plugins/materials/events/StageImminentRemovalEvent.java b/gcm3/src/main/java/plugins/materials/events/StageImminentRemovalEvent.java deleted file mode 100644 index fc027940a..000000000 --- a/gcm3/src/main/java/plugins/materials/events/StageImminentRemovalEvent.java +++ /dev/null @@ -1,31 +0,0 @@ -package plugins.materials.events; - -import net.jcip.annotations.Immutable; -import nucleus.Event; -import plugins.materials.support.StageId; - -@Immutable - -public class StageImminentRemovalEvent implements Event { - private final StageId stageId; - - public StageImminentRemovalEvent(StageId stageId) { - super(); - this.stageId = stageId; - } - - public StageId getStageId() { - return stageId; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("StageImminentRemovalEvent [stageId="); - builder.append(stageId); - builder.append("]"); - return builder.toString(); - } - - -} diff --git a/gcm3/src/main/java/plugins/materials/events/StageMaterialsProducerUpdateEvent.java b/gcm3/src/main/java/plugins/materials/events/StageMaterialsProducerUpdateEvent.java deleted file mode 100644 index 85b44b982..000000000 --- a/gcm3/src/main/java/plugins/materials/events/StageMaterialsProducerUpdateEvent.java +++ /dev/null @@ -1,127 +0,0 @@ -package plugins.materials.events; - -import net.jcip.annotations.Immutable; -import nucleus.Event; -import nucleus.EventLabel; -import nucleus.EventLabeler; -import nucleus.EventLabelerId; -import nucleus.SimulationContext; -import plugins.materials.datamangers.MaterialsDataManager; -import plugins.materials.support.MaterialsError; -import plugins.materials.support.MaterialsProducerId; -import plugins.materials.support.StageId; -import util.errors.ContractException; - -@Immutable -public class StageMaterialsProducerUpdateEvent implements Event { - private final StageId stageId; - private final MaterialsProducerId previousMaterialsProducerId; - private final MaterialsProducerId currentMaterialsProducerId; - - public StageMaterialsProducerUpdateEvent(StageId stageId, MaterialsProducerId previousMaterialsProducerId, MaterialsProducerId currentMaterialsProducerId) { - super(); - this.stageId = stageId; - this.previousMaterialsProducerId = previousMaterialsProducerId; - this.currentMaterialsProducerId = currentMaterialsProducerId; - } - - public StageId getStageId() { - return stageId; - } - - public MaterialsProducerId getPreviousMaterialsProducerId() { - return previousMaterialsProducerId; - } - - public MaterialsProducerId getCurrentMaterialsProducerId() { - return currentMaterialsProducerId; - } - - @Override - public String toString() { - return "StageMaterialsProducerUpdateEvent [stageId=" + stageId + ", previousMaterialsProducerId=" + previousMaterialsProducerId + ", currentMaterialsProducerId=" + currentMaterialsProducerId - + "]"; - } - - private static enum LabelerId implements EventLabelerId { - SOURCE, DESTINATION, STAGE - } - - private static void validateStageId(SimulationContext simulationContext, StageId stageId) { - if (stageId == null) { - throw new ContractException(MaterialsError.NULL_STAGE_ID); - } - MaterialsDataManager materialsDataManager = simulationContext.getDataManager(MaterialsDataManager.class); - if (!materialsDataManager.stageExists(stageId)) { - throw new ContractException(MaterialsError.UNKNOWN_STAGE_ID); - } - } - - private static void validateMaterialsProducerId(SimulationContext simulationContext, MaterialsProducerId materialsProducerId) { - if (materialsProducerId == null) { - throw new ContractException(MaterialsError.NULL_MATERIALS_PRODUCER_ID); - } - MaterialsDataManager materialsDataManager = simulationContext.getDataManager(MaterialsDataManager.class); - if (!materialsDataManager.materialsProducerIdExists(materialsProducerId)) { - throw new ContractException(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID); - } - } - - public static EventLabel getEventLabelByDestination(SimulationContext simulationContext, MaterialsProducerId destinationMaterialsProducerId) { - validateMaterialsProducerId(simulationContext, destinationMaterialsProducerId); - return _getEventLabelByDestination(destinationMaterialsProducerId); - } - - private static EventLabel _getEventLabelByDestination(MaterialsProducerId destinationMaterialsProducerId) { - return EventLabel .builder(StageMaterialsProducerUpdateEvent.class)// - .setEventLabelerId(LabelerId.DESTINATION)// - .addKey(StageMaterialsProducerUpdateEvent.class).addKey(destinationMaterialsProducerId).build(); - } - - public static EventLabeler getEventLabelerForDestination() { - return EventLabeler .builder(StageMaterialsProducerUpdateEvent.class)// - .setEventLabelerId(LabelerId.DESTINATION)// - .setLabelFunction((context, event) -> _getEventLabelByDestination(event.getCurrentMaterialsProducerId())).build(); - } - - public static EventLabel getEventLabelBySource(SimulationContext simulationContext, MaterialsProducerId sourceMaterialsProducerId) { - validateMaterialsProducerId(simulationContext, sourceMaterialsProducerId); - return _getEventLabelBySource(sourceMaterialsProducerId);// - } - - private static EventLabel _getEventLabelBySource(MaterialsProducerId sourceMaterialsProducerId) { - return EventLabel .builder(StageMaterialsProducerUpdateEvent.class)// - .setEventLabelerId(LabelerId.SOURCE)// - .addKey(StageMaterialsProducerUpdateEvent.class)// - .addKey(sourceMaterialsProducerId)// - .build();// - } - - public static EventLabeler getEventLabelerForSource() { - return EventLabeler .builder(StageMaterialsProducerUpdateEvent.class).setEventLabelerId(LabelerId.SOURCE)// - .setLabelFunction((context, event) -> _getEventLabelBySource(event.getPreviousMaterialsProducerId()))// - .build(); - } - - public static EventLabel getEventLabelByStage(SimulationContext simulationContext, StageId stageId) { - validateStageId(simulationContext, stageId); - return _getEventLabelByStage(stageId); - } - - private static EventLabel _getEventLabelByStage(StageId stageId) { - - return EventLabel .builder(StageMaterialsProducerUpdateEvent.class)// - .setEventLabelerId(LabelerId.STAGE)// - .addKey(StageMaterialsProducerUpdateEvent.class)// - .addKey(stageId)// - .build(); - } - - public static EventLabeler getEventLabelerForStage() { - return EventLabeler .builder(StageMaterialsProducerUpdateEvent.class)// - .setEventLabelerId(LabelerId.STAGE)// - .setLabelFunction((context, event) -> _getEventLabelByStage(event.getStageId()))// - .build(); - } - -} diff --git a/gcm3/src/main/java/plugins/materials/events/StageMembershipAdditionEvent.java b/gcm3/src/main/java/plugins/materials/events/StageMembershipAdditionEvent.java deleted file mode 100644 index 2e8f6792b..000000000 --- a/gcm3/src/main/java/plugins/materials/events/StageMembershipAdditionEvent.java +++ /dev/null @@ -1,32 +0,0 @@ -package plugins.materials.events; - -import net.jcip.annotations.Immutable; -import nucleus.Event; -import plugins.materials.support.BatchId; -import plugins.materials.support.StageId; - -@Immutable -public class StageMembershipAdditionEvent implements Event { - private final BatchId batchId; - private final StageId stageId; - - public StageMembershipAdditionEvent(BatchId batchId, StageId stageId) { - super(); - this.batchId = batchId; - this.stageId = stageId; - } - - public BatchId getBatchId() { - return batchId; - } - - public StageId getStageId() { - return stageId; - } - - @Override - public String toString() { - return "StageMembershipAdditionEvent [batchId=" + batchId + ", stageId=" + stageId + "]"; - } - -} diff --git a/gcm3/src/main/java/plugins/materials/events/StageMembershipRemovalEvent.java b/gcm3/src/main/java/plugins/materials/events/StageMembershipRemovalEvent.java deleted file mode 100644 index f9e8aa610..000000000 --- a/gcm3/src/main/java/plugins/materials/events/StageMembershipRemovalEvent.java +++ /dev/null @@ -1,32 +0,0 @@ -package plugins.materials.events; - -import net.jcip.annotations.Immutable; -import nucleus.Event; -import plugins.materials.support.BatchId; -import plugins.materials.support.StageId; - -@Immutable -public class StageMembershipRemovalEvent implements Event { - private final BatchId batchId; - private final StageId stageId; - - public StageMembershipRemovalEvent(BatchId batchId, StageId stageId) { - super(); - this.batchId = batchId; - this.stageId = stageId; - } - - public BatchId getBatchId() { - return batchId; - } - - public StageId getStageId() { - return stageId; - } - - @Override - public String toString() { - return "StageMembershipRemovalEvent [batchId=" + batchId + ", stageId=" + stageId + "]"; - } - -} diff --git a/gcm3/src/main/java/plugins/materials/events/StageOfferUpdateEvent.java b/gcm3/src/main/java/plugins/materials/events/StageOfferUpdateEvent.java deleted file mode 100644 index d2e5948d0..000000000 --- a/gcm3/src/main/java/plugins/materials/events/StageOfferUpdateEvent.java +++ /dev/null @@ -1,78 +0,0 @@ -package plugins.materials.events; - -import net.jcip.annotations.Immutable; -import nucleus.Event; -import nucleus.EventLabel; -import nucleus.EventLabeler; -import nucleus.EventLabelerId; -import nucleus.SimulationContext; -import plugins.materials.datamangers.MaterialsDataManager; -import plugins.materials.support.MaterialsError; -import plugins.materials.support.StageId; -import util.errors.ContractException; - -@Immutable -public class StageOfferUpdateEvent implements Event { - private final StageId stageId; - private final boolean previousOfferState; - private final boolean currentOfferState; - - public StageOfferUpdateEvent(StageId stageId, boolean previousOfferState, boolean currentOfferState) { - super(); - this.stageId = stageId; - this.previousOfferState = previousOfferState; - this.currentOfferState = currentOfferState; - } - - public StageId getStageId() { - return stageId; - } - - public boolean isPreviousOfferState() { - return previousOfferState; - } - - public boolean isCurrentOfferState() { - return currentOfferState; - } - - @Override - public String toString() { - return "StageOfferUpdateEvent [stageId=" + stageId + ", previousOfferState=" + previousOfferState + ", currentOfferState=" + currentOfferState + "]"; - } - - private static enum LabelerId implements EventLabelerId { - STAGE - } - - private static void validateStageId(SimulationContext simulationContext, StageId stageId) { - if (stageId == null) { - throw new ContractException(MaterialsError.NULL_STAGE_ID); - } - MaterialsDataManager materialsDataManager = simulationContext.getDataManager(MaterialsDataManager.class); - if (!materialsDataManager.stageExists(stageId)) { - throw new ContractException(MaterialsError.UNKNOWN_STAGE_ID, stageId); - } - } - - public static EventLabel getEventLabelByStage(SimulationContext simulationContext, StageId stageId) { - validateStageId(simulationContext, stageId); - return _getEventLabelByStage(stageId);// - } - - private static EventLabel _getEventLabelByStage(StageId stageId) { - - return EventLabel .builder(StageOfferUpdateEvent.class)// - .setEventLabelerId(LabelerId.STAGE)// - .addKey(StageOfferUpdateEvent.class)// - .addKey(stageId)// - .build();// - } - - public static EventLabeler getEventLabelerForStage() { - return EventLabeler .builder(StageOfferUpdateEvent.class)// - .setEventLabelerId(LabelerId.STAGE).setLabelFunction((context, event) -> _getEventLabelByStage(event.getStageId()))// - .build(); - } - -} diff --git a/gcm3/src/main/java/plugins/materials/support/BatchConstructionInfo.java b/gcm3/src/main/java/plugins/materials/support/BatchConstructionInfo.java deleted file mode 100644 index d21011800..000000000 --- a/gcm3/src/main/java/plugins/materials/support/BatchConstructionInfo.java +++ /dev/null @@ -1,122 +0,0 @@ -package plugins.materials.support; - -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; - -import net.jcip.annotations.Immutable; - -/** - * Represents the information to fully specify a batch, but not its relationship - * to stages - * - * @author Shawn Hatch - * - */ -@Immutable -public class BatchConstructionInfo { - - private BatchConstructionInfo(Scaffold scaffold) { - this.scaffold = scaffold; - } - - private final Scaffold scaffold; - - private static class Scaffold { - private MaterialsProducerId materialsProducerId; - private MaterialId materialId; - private double amount; - private Map propertyValues = new LinkedHashMap<>(); - } - - /** - * Returns a builder instance - */ - public static Builder builder() { - return new Builder(); - } - - /** - * A builder class for BatchConstructionInfo - */ - public static class Builder { - private Scaffold scaffold = new Scaffold(); - - private Builder() { - } - - /** - * Builds the BatchConstructionInfo from the collected data - */ - public BatchConstructionInfo build() { - try { - return new BatchConstructionInfo(scaffold); - } finally { - scaffold = new Scaffold(); - } - } - - /** - * Sets the amount. Defaulted to zero. - * - */ - public Builder setAmount(double amount) { - scaffold.amount = amount; - return this; - } - - /** - * Sets the material id. Defaulted to null. - */ - public Builder setMaterialId(MaterialId materialId) { - scaffold.materialId = materialId; - return this; - } - - /** - * Sets the materials producer id. Defaulted to null. - */ - public Builder setMaterialsProducerId(MaterialsProducerId materialsProducerId) { - scaffold.materialsProducerId = materialsProducerId; - return this; - } - - /** - * Sets a property value; - * - */ - public Builder setPropertyValue(BatchPropertyId batchPropertyId, Object propertyValue) { - scaffold.propertyValues.put(batchPropertyId, propertyValue); - return this; - } - } - - /** - * Return the material id of the new batch - */ - public MaterialId getMaterialId() { - return scaffold.materialId; - } - - /** - * Return the materials producer id of the new batch - */ - public MaterialsProducerId getMaterialsProducerId() { - return scaffold.materialsProducerId; - } - - /** - * Returns the amount of the new batch - */ - public double getAmount() { - return scaffold.amount; - } - - /** - * Returns a map of the batch property values of the new batch - */ - public Map getPropertyValues() { - return Collections.unmodifiableMap(scaffold.propertyValues); - } - -} diff --git a/gcm3/src/main/java/plugins/materials/support/BatchPropertyId.java b/gcm3/src/main/java/plugins/materials/support/BatchPropertyId.java deleted file mode 100644 index 1b52347e4..000000000 --- a/gcm3/src/main/java/plugins/materials/support/BatchPropertyId.java +++ /dev/null @@ -1,14 +0,0 @@ -package plugins.materials.support; - -import net.jcip.annotations.ThreadSafe; - -/** - * Marker interface for batch property identifiers - * - * @author Shawn Hatch - * - */ -@ThreadSafe -public interface BatchPropertyId { - -} diff --git a/gcm3/src/main/java/plugins/materials/support/MaterialId.java b/gcm3/src/main/java/plugins/materials/support/MaterialId.java deleted file mode 100644 index 8e99534c8..000000000 --- a/gcm3/src/main/java/plugins/materials/support/MaterialId.java +++ /dev/null @@ -1,14 +0,0 @@ -package plugins.materials.support; - -import net.jcip.annotations.ThreadSafe; - -/** - * Marker interface for material identifiers - * - * @author Shawn Hatch - * - */ -@ThreadSafe -public interface MaterialId { - -} diff --git a/gcm3/src/main/java/plugins/materials/support/MaterialsError.java b/gcm3/src/main/java/plugins/materials/support/MaterialsError.java deleted file mode 100644 index db34f89ba..000000000 --- a/gcm3/src/main/java/plugins/materials/support/MaterialsError.java +++ /dev/null @@ -1,69 +0,0 @@ -package plugins.materials.support; - -import util.errors.ContractError; -import util.errors.ContractException; - -/** - * An enumeration supporting {@link ContractException} that acts as a general - * description of the exception. - * - * @author Shawn Hatch - * - */ -public enum MaterialsError implements ContractError { - - DUPLICATE_BATCH_PROPERTY_VALUE_ASSIGNMENT("Duplicate batch property value assignment"), - DUPLICATE_MATERIALS_PRODUCER_PROPERTY_VALUE_ASSIGNMENT("Duplicate materials producer property value assignment"), - DUPLICATE_MATERIALS_PRODUCER_RESOURCE_ASSIGNMENT("Duplicate materials producer resource assignment"), - DUPLICATE_MATERIALS_PRODUCER_ID("Duplicate materials producer id"), - RESOURCE_LOADING_ORDER("Resources must be added before materials producers are added"), - MATERIALS_PRODUCER_PROPERTY_LOADING_ORDER("Material procuders must be added before materials producer properties are added"), - BATCH_ALREADY_STAGED("Batch is already staged"), - DUPLICATE_MATERIAL("Duplicate material addition"), - BATCH_NOT_STAGED("Batch is not currently staged"), - BATCH_SHIFT_WITH_MULTIPLE_OWNERS("Cannot shift material from a batch to another batch not owned by the same materials producer"), - BATCH_STAGED_TO_DIFFERENT_OWNER("Cannot stage a batch onto a stage not owned by the same materials producer"), - INSUFFICIENT_MATERIAL_AVAILABLE("Material level is insufficient for transaction amount"), - MATERIAL_ARITHMETIC_EXCEPTION("Material arithmetic error due to non finite sum"), - MATERIAL_TYPE_MISMATCH("Material identifiers do not match"), - NEGATIVE_MATERIAL_AMOUNT("Material amount is negative"), - NON_FINITE_MATERIAL_AMOUNT("Material amount is not finite"), - NULL_BATCH_CONSTRUCTION_INFO("Null batch construction info"), - NULL_BATCH_ID("Null batch id"), - NULL_BATCH_PROPERTY_ID("Null batch property id"), - NULL_BATCH_PROPERTY_VALUE("Null batch property value"), - NULL_MATERIAL_ID("Null material id"), - NULL_MATERIALS_PLUGIN_DATA("Null materials initial data"), - NULL_MATERIALS_PRODUCER_ID("Null materials producer id"), - NULL_MATERIALS_PRODUCER_PROPERTY_ID("Null materials producer property id"), - NULL_MATERIALS_PRODUCER_PROPERTY_VALUE("Null materials producer property value"), - NULL_STAGE_ID("Null stage id"), - OFFERED_STAGE_UNALTERABLE("An offered stage and its batches cannot be altered"), - REFLEXIVE_BATCH_SHIFT("Cannot shift material from a batch to itself"), - REFLEXIVE_STAGE_TRANSFER("Producer cannot transfer a stage to itself"), - UNKNOWN_BATCH_ID("Unknown batch id"), - UNKNOWN_BATCH_PROPERTY_ID("Unknown batch property id"), - UNKNOWN_MATERIAL_ID("Unknown material id"), - UNKNOWN_MATERIALS_PRODUCER_ID("Unknown materials producer id"), - UNKNOWN_MATERIALS_PRODUCER_PROPERTY_ID("Unknown material producer property id"), - UNKNOWN_STAGE_ID("Unknown stage id"), - UNOFFERED_STAGE_NOT_TRANSFERABLE("Unoffered stages are not transferable"), - INSUFFICIENT_MATERIALS_PRODUCER_PROPERTY_VALUE_ASSIGNMENT("A materials producer property definition default value is null and not replaced with sufficient property value assignments"), - DUPLICATE_BATCH_PROPERTY_DEFINITION("Duplicate batch property definition"), - DUPLICATE_BATCH_ID("Duplicate batch id"), - DUPLICATE_MATERIALS_PRODUCER_PROPERTY_DEFINITION("Duplicate materials producer property definition"), - PROPERTY_DEFINITION_REQUIRES_DEFAULT("Batch property definition does not have an assigned default value"), - INCOMPATIBLE_MATERIALS_PRODUCER_PROPERTY_VALUE("Materials producer property value is incompatible with the property definition"), - INCOMPATIBLE_BATCH_PROPERTY_VALUE("Batch property value is incompatible with the property definition"),; - - private final String description; - - private MaterialsError(final String description) { - this.description = description; - } - - @Override - public String getDescription() { - return description; - } -} diff --git a/gcm3/src/main/java/plugins/materials/support/MaterialsProducerId.java b/gcm3/src/main/java/plugins/materials/support/MaterialsProducerId.java deleted file mode 100644 index 34528e217..000000000 --- a/gcm3/src/main/java/plugins/materials/support/MaterialsProducerId.java +++ /dev/null @@ -1,14 +0,0 @@ -package plugins.materials.support; - -import net.jcip.annotations.ThreadSafe; - -/** - * Marker interface for materials producer identifiers - * - * @author Shawn Hatch - * - */ -@ThreadSafe -public interface MaterialsProducerId { - -} diff --git a/gcm3/src/main/java/plugins/materials/support/MaterialsProducerPropertyId.java b/gcm3/src/main/java/plugins/materials/support/MaterialsProducerPropertyId.java deleted file mode 100644 index f3a054d7f..000000000 --- a/gcm3/src/main/java/plugins/materials/support/MaterialsProducerPropertyId.java +++ /dev/null @@ -1,14 +0,0 @@ -package plugins.materials.support; - -import net.jcip.annotations.ThreadSafe; - -/** - * Marker interface for materials producer property identifiers - * - * @author Shawn Hatch - * - */ -@ThreadSafe -public interface MaterialsProducerPropertyId { - -} diff --git a/gcm3/src/main/java/plugins/materials/testsupport/MaterialsActionSupport.java b/gcm3/src/main/java/plugins/materials/testsupport/MaterialsActionSupport.java deleted file mode 100644 index cb17b37c1..000000000 --- a/gcm3/src/main/java/plugins/materials/testsupport/MaterialsActionSupport.java +++ /dev/null @@ -1,204 +0,0 @@ -package plugins.materials.testsupport; - - -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; -import java.util.function.Consumer; - -import org.apache.commons.math3.random.RandomGenerator; - -import nucleus.ActorContext; -import nucleus.Experiment; -import nucleus.Plugin; -import nucleus.testsupport.testplugin.ExperimentPlanCompletionObserver; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestError; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import plugins.materials.MaterialsPlugin; -import plugins.materials.MaterialsPluginData; -import plugins.people.PeoplePlugin; -import plugins.people.PeoplePluginData; -import plugins.regions.RegionsPlugin; -import plugins.regions.RegionsPluginData; -import plugins.regions.testsupport.TestRegionId; -import plugins.reports.ReportsPlugin; -import plugins.reports.ReportsPluginData; -import plugins.reports.support.ReportItem; -import plugins.reports.testsupport.TestReportItemOutputConsumer; -import plugins.resources.ResourcesPlugin; -import plugins.resources.ResourcesPluginData; -import plugins.resources.testsupport.TestResourceId; -import plugins.resources.testsupport.TestResourcePropertyId; -import plugins.stochastics.StochasticsPlugin; -import plugins.stochastics.StochasticsPluginData; -import plugins.util.properties.PropertyDefinition; -import util.errors.ContractException; -import util.random.RandomGeneratorProvider; - -/** - * A static test support class for the materials plugin. Provides convenience - * methods for integrating an action plugin into a materials-based simulation - * test harness. - * - * - * @author Shawn Hatch - * - */ -public class MaterialsActionSupport { - - /** - * Creates an action plugin with an agent that will execute the given - * consumer at time 0. The action plugin and the remaining arguments are - * passed to an invocation of the testConsumers() method. - */ - public static Set testConsumer(long seed, Consumer consumer) { - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, consumer)); - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - return testConsumers(seed, testPlugin, null); - } - - - - public static Set testConsumers(long seed, Plugin testPlugin) { - return testConsumers(seed, testPlugin, null); - } - /** - * Executes a simulation instance that supports materials plugin testing. - * - * The initial population is added in the initial data. - * - * Materials, Materials Producers and their associated properties are added. - * No batches or stages are created. Materials producer resource levels are - * zero. - * - * Resources and their property definitions and initial values are added. No - * resources are allocated to regions or people. - * - * The seed is used to produce randomized initial group types and group - * memberships. - * - * The test plugin is integrated into the simulation run and must contain - * at least one action plan. This helps to ensure that a test that does not - * run completely does not lead to a false positive test evaluation. - * - * @throws ContractException - *
  • {@linkplain TestError#TEST_EXECUTION_FAILURE} if not - * all action plans execute or if there are no action plans - * contained in the action plugin
  • - */ - public static Set testConsumers(long seed, Plugin testPlugin, - - Consumer report) { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); - - Experiment.Builder builder = Experiment.builder(); - - MaterialsPluginData.Builder materialsBuilder = MaterialsPluginData.builder(); - - for (TestMaterialId testMaterialId : TestMaterialId.values()) { - materialsBuilder.addMaterial(testMaterialId); - } - - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - materialsBuilder.addMaterialsProducerId(testMaterialsProducerId); - } - - for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId.values()) { - materialsBuilder.defineMaterialsProducerProperty(testMaterialsProducerPropertyId, testMaterialsProducerPropertyId.getPropertyDefinition()); - } - - for (TestMaterialId testMaterialId : TestMaterialId.values()) { - Set testBatchPropertyIds = TestBatchPropertyId.getTestBatchPropertyIds(testMaterialId); - for (TestBatchPropertyId testBatchPropertyId : testBatchPropertyIds) { - materialsBuilder.defineBatchProperty(testMaterialId, testBatchPropertyId, testBatchPropertyId.getPropertyDefinition()); - } - } - MaterialsPluginData materialsPluginData = materialsBuilder.build(); - Plugin materialsPlugin = MaterialsPlugin.getMaterialsPlugin(materialsPluginData); - builder.addPlugin(materialsPlugin); - - // add the resources plugin - ResourcesPluginData.Builder resourcesBuilder = ResourcesPluginData.builder(); - - for (TestResourceId testResourceId : TestResourceId.values()) { - resourcesBuilder.addResource(testResourceId); - resourcesBuilder.setResourceTimeTracking(testResourceId, testResourceId.getTimeTrackingPolicy()); - } - - for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId.values()) { - TestResourceId testResourceId = testResourcePropertyId.getTestResourceId(); - PropertyDefinition propertyDefinition = testResourcePropertyId.getPropertyDefinition(); - Object propertyValue = testResourcePropertyId.getRandomPropertyValue(randomGenerator); - resourcesBuilder.defineResourceProperty(testResourceId, testResourcePropertyId, propertyDefinition); - resourcesBuilder.setResourcePropertyValue(testResourceId, testResourcePropertyId, propertyValue); - } - - - ResourcesPluginData resourcesPluginData = resourcesBuilder.build(); - Plugin resourcesPlugin = ResourcesPlugin.getResourcesPlugin(resourcesPluginData); - builder.addPlugin(resourcesPlugin); - - // add the people plugin - - PeoplePluginData.Builder peopleBuilder = PeoplePluginData.builder(); - PeoplePluginData peoplePluginData = peopleBuilder.build(); - Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(peoplePluginData); - builder.addPlugin(peoplePlugin); - - // add the regions plugin - RegionsPluginData.Builder regionsBuilder = RegionsPluginData.builder(); - for (TestRegionId testRegionId : TestRegionId.values()) { - regionsBuilder.addRegion(testRegionId); - } - RegionsPluginData regionsPluginData = regionsBuilder.build(); - Plugin regionPlugin = RegionsPlugin.getRegionsPlugin(regionsPluginData); - builder.addPlugin(regionPlugin); - - // add the report plugin - - ReportsPluginData.Builder reportsBuilder = ReportsPluginData.builder(); - if (report != null) { - reportsBuilder.addReport(() -> report); - } - ReportsPluginData reportsPluginData = reportsBuilder.build(); - Plugin reportPlugin = ReportsPlugin.getReportPlugin(reportsPluginData); - builder.addPlugin(reportPlugin); - - StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder().setSeed(randomGenerator.nextLong()).build(); - Plugin stochasticsPlugin = StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData); - // add the stochastics plugin - builder.addPlugin(stochasticsPlugin); - - // add the action plugin - builder.addPlugin(testPlugin); - - // set the output consumer - TestReportItemOutputConsumer testReportItemOutputConsumer = new TestReportItemOutputConsumer(); - builder.addOutputHandler(testReportItemOutputConsumer::init); - ExperimentPlanCompletionObserver experimentPlanCompletionObserver = new ExperimentPlanCompletionObserver(); - builder.addOutputHandler(experimentPlanCompletionObserver::init); - builder.reportProgressToConsole(false); - - // build and execute the engine - - builder.build().execute(); - Map reportItems = testReportItemOutputConsumer.getReportItems().get(0); - Set result = new LinkedHashSet<>(); - if(reportItems != null) { - result.addAll(reportItems.keySet()); - } - - // show that all actions were executed - boolean complete = experimentPlanCompletionObserver.getActionCompletionReport(0).get().isComplete(); - if(!complete) { - throw new ContractException(TestError.TEST_EXECUTION_FAILURE); - } - return result; - } - -} diff --git a/gcm3/src/main/java/plugins/materials/testsupport/TestBatchPropertyId.java b/gcm3/src/main/java/plugins/materials/testsupport/TestBatchPropertyId.java deleted file mode 100644 index 01a0c3de9..000000000 --- a/gcm3/src/main/java/plugins/materials/testsupport/TestBatchPropertyId.java +++ /dev/null @@ -1,201 +0,0 @@ -package plugins.materials.testsupport; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.apache.commons.math3.random.RandomGenerator; - -import plugins.materials.support.BatchPropertyId; -import plugins.util.properties.PropertyDefinition; -import plugins.util.properties.TimeTrackingPolicy; - -/** - * A test support enumeration that contains a variety of batch property id - * values with corresponding property definitions and associations to Test - * Material Ids. Supports random selection, random property value generation and - * generation of unique, unknown batch property ids. - * - * @author Shawn Hatch - * - */ -public enum TestBatchPropertyId implements BatchPropertyId { - - BATCH_PROPERTY_1_1_BOOLEAN_IMMUTABLE_NO_TRACK( - TestMaterialId.MATERIAL_1, // - PropertyDefinition .builder()// - .setType(Boolean.class)// - .setDefaultValue(false)// - .setPropertyValueMutability(false)// - .setTimeTrackingPolicy(TimeTrackingPolicy.DO_NOT_TRACK_TIME)// - .build()), // - BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK( - TestMaterialId.MATERIAL_1, // - PropertyDefinition .builder()// - .setType(Integer.class)// - .setDefaultValue(0)// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.DO_NOT_TRACK_TIME)// - .build() // - ), // - BATCH_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK( - TestMaterialId.MATERIAL_1, // - PropertyDefinition .builder()// - .setType(Double.class)// - .setDefaultValue(0.0)// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.DO_NOT_TRACK_TIME)// - .build() // - ), // - BATCH_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK( - TestMaterialId.MATERIAL_2, // - PropertyDefinition .builder()// - .setType(Boolean.class)// - .setDefaultValue(false)// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME)// - .build() // - ), // - BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK( - TestMaterialId.MATERIAL_2, // - PropertyDefinition .builder()// - .setType(Integer.class)// - .setDefaultValue(0)// - .setPropertyValueMutability(false)// - .setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME)// - .build() // - ), // - BATCH_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK( - TestMaterialId.MATERIAL_2, // - PropertyDefinition .builder()// - .setType(Double.class)// - .setDefaultValue(0.0)// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME)// - .build() // - ), // - BATCH_PROPERTY_3_1_BOOLEAN_MUTABLE_NO_TRACK( - TestMaterialId.MATERIAL_3, // - PropertyDefinition .builder()// - .setType(Boolean.class)// - .setDefaultValue(false)// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.DO_NOT_TRACK_TIME)// - .build() // - ), // - BATCH_PROPERTY_3_2_INTEGER_MUTABLE_NO_TRACK( - TestMaterialId.MATERIAL_3, // - PropertyDefinition .builder()// - .setType(Integer.class)// - .setDefaultValue(0)// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.DO_NOT_TRACK_TIME)// - .build() // - ), // - BATCH_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK( - TestMaterialId.MATERIAL_3, // - PropertyDefinition .builder()// - .setType(Double.class)// - .setDefaultValue(0.0)// - .setPropertyValueMutability(false)// - .setTimeTrackingPolicy(TimeTrackingPolicy.DO_NOT_TRACK_TIME)// - .build() // - );// - - private final PropertyDefinition propertyDefinition; - private final TestMaterialId testMaterialId; - - /** - * Returns the property definition associated with this member - */ - public PropertyDefinition getPropertyDefinition() { - return propertyDefinition; - } - - private TestBatchPropertyId(TestMaterialId testMaterialId, PropertyDefinition propertyDefinition) { - this.testMaterialId = testMaterialId; - this.propertyDefinition = propertyDefinition; - } - - /** - * Returns the TestMaterialId that should be associated with the property - */ - public TestMaterialId getTestMaterialId() { - return testMaterialId; - } - - /** - * Returns the TestBatchPropertyIds associated with the given TestMaterialId - * - * Preconditions: The TestMaterialId should not be null - */ - public static Set getTestBatchPropertyIds(TestMaterialId testMaterialId) { - Set result = new LinkedHashSet<>(); - for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.values()) { - if (testBatchPropertyId.testMaterialId == testMaterialId) { - result.add(testBatchPropertyId); - } - } - return result; - } - - /** - * Returns a unique BatchPropertyId instance that is not a member of this - * enumeration - */ - public static BatchPropertyId getUnknownBatchPropertyId() { - return new BatchPropertyId() { - }; - - } - - /** - * Returns a TestBatchPropertyId that is associated with the given material - * id and whose associated property definition is marked as mutable. - */ - public static TestBatchPropertyId getRandomMutableBatchPropertyId(final TestMaterialId testMaterialId, final RandomGenerator randomGenerator) { - Set set = getTestBatchPropertyIds(testMaterialId); - int count = 1; - TestBatchPropertyId selectedTestBatchPropertyId = null; - for (TestBatchPropertyId testBatchPropertyId : set) { - if (testBatchPropertyId.propertyDefinition.propertyValuesAreMutable()) { - if (randomGenerator.nextDouble() < (1.0 / count)) { - selectedTestBatchPropertyId = testBatchPropertyId; - } - } - } - return selectedTestBatchPropertyId; - - } - - /** - * Returns a randomly selected value that is compatible with this member's - * associated property definition. - * - */ - public Object getRandomPropertyValue(final RandomGenerator randomGenerator) { - switch (this) { - case BATCH_PROPERTY_1_1_BOOLEAN_IMMUTABLE_NO_TRACK: - return randomGenerator.nextBoolean(); - case BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK: - return randomGenerator.nextInt(); - case BATCH_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK: - return randomGenerator.nextDouble(); - case BATCH_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK: - return randomGenerator.nextBoolean(); - case BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK: - return randomGenerator.nextInt(); - case BATCH_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK: - return randomGenerator.nextDouble(); - case BATCH_PROPERTY_3_1_BOOLEAN_MUTABLE_NO_TRACK: - return randomGenerator.nextBoolean(); - case BATCH_PROPERTY_3_2_INTEGER_MUTABLE_NO_TRACK: - return randomGenerator.nextInt(); - case BATCH_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK: - return randomGenerator.nextDouble(); - default: - - throw new RuntimeException("unhandled case: " + this); - - } - } -} diff --git a/gcm3/src/main/java/plugins/materials/testsupport/TestMaterialsProducerPropertyId.java b/gcm3/src/main/java/plugins/materials/testsupport/TestMaterialsProducerPropertyId.java deleted file mode 100644 index 4cf4e2dee..000000000 --- a/gcm3/src/main/java/plugins/materials/testsupport/TestMaterialsProducerPropertyId.java +++ /dev/null @@ -1,177 +0,0 @@ -package plugins.materials.testsupport; - -import org.apache.commons.math3.random.RandomGenerator; - -import plugins.materials.support.MaterialsProducerPropertyId; -import plugins.util.properties.PropertyDefinition; -import plugins.util.properties.TimeTrackingPolicy; - -/** - * A test support enumeration that contains a variety of materials producer - * property id values with corresponding property definitions. Supports random - * selection, random property value generation and generation of unique, unknown - * batch property ids. - * - * @author Shawn Hatch - * - */ -public enum TestMaterialsProducerPropertyId implements MaterialsProducerPropertyId { - - MATERIALS_PRODUCER_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK( - - PropertyDefinition .builder()// - .setType(Boolean.class)// - .setDefaultValue(false)// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.DO_NOT_TRACK_TIME)// - .build()), // - MATERIALS_PRODUCER_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK( - - PropertyDefinition .builder()// - .setType(Integer.class)// - .setDefaultValue(0)// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.DO_NOT_TRACK_TIME)// - .build() // - ), // - MATERIALS_PRODUCER_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK( - - PropertyDefinition .builder()// - .setType(Double.class)// - .setDefaultValue(0.0)// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.DO_NOT_TRACK_TIME)// - .build() // - ), // - MATERIALS_PRODUCER_PROPERTY_4_BOOLEAN_MUTABLE_TRACK( - - PropertyDefinition .builder()// - .setType(Boolean.class)// - .setDefaultValue(false)// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME)// - .build() // - ), // - MATERIALS_PRODUCER_PROPERTY_5_INTEGER_MUTABLE_TRACK( - - PropertyDefinition .builder()// - .setType(Integer.class)// - .setDefaultValue(0)// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME)// - .build() // - ), // - MATERIALS_PRODUCER_PROPERTY_6_DOUBLE_MUTABLE_TRACK( - - PropertyDefinition .builder()// - .setType(Double.class)// - .setDefaultValue(0.0)// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME)// - .build() // - ), // - MATERIALS_PRODUCER_PROPERTY_7_BOOLEAN_IMMUTABLE_NO_TRACK( - - PropertyDefinition .builder()// - .setType(Boolean.class)// - .setDefaultValue(false)// - .setPropertyValueMutability(false)// - .setTimeTrackingPolicy(TimeTrackingPolicy.DO_NOT_TRACK_TIME)// - .build() // - ), // - MATERIALS_PRODUCER_PROPERTY_8_INTEGER_IMMUTABLE_NO_TRACK( - - PropertyDefinition .builder()// - .setType(Integer.class)// - .setDefaultValue(0)// - .setPropertyValueMutability(false)// - .setTimeTrackingPolicy(TimeTrackingPolicy.DO_NOT_TRACK_TIME)// - .build() // - ), // - MATERIALS_PRODUCER_PROPERTY_9_DOUBLE_IMMUTABLE_NO_TRACK( - - PropertyDefinition .builder()// - .setType(Double.class)// - .setDefaultValue(0.0)// - .setPropertyValueMutability(false)// - .setTimeTrackingPolicy(TimeTrackingPolicy.DO_NOT_TRACK_TIME)// - .build() // - );// - - private final PropertyDefinition propertyDefinition; - - /** - * Returns the property definition associated with this member. The property - * definition will contain a default value. - */ - public PropertyDefinition getPropertyDefinition() { - return propertyDefinition; - } - - private TestMaterialsProducerPropertyId(PropertyDefinition propertyDefinition) { - this.propertyDefinition = propertyDefinition; - } - - /** - * Returns a unique MaterialsProducerPropertyId instance that is not a - * member of this enumeration - */ - public static MaterialsProducerPropertyId getUnknownMaterialsProducerPropertyId() { - return new MaterialsProducerPropertyId() { - }; - } - - /** - * Returns a randomly selected value that is compatible with this member's - * associated property definition. - * - */ - public Object getRandomPropertyValue(final RandomGenerator randomGenerator) { - switch (this) { - case MATERIALS_PRODUCER_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK: - return randomGenerator.nextBoolean(); - case MATERIALS_PRODUCER_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK: - return randomGenerator.nextInt(); - case MATERIALS_PRODUCER_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK: - return randomGenerator.nextDouble(); - case MATERIALS_PRODUCER_PROPERTY_4_BOOLEAN_MUTABLE_TRACK: - return randomGenerator.nextBoolean(); - case MATERIALS_PRODUCER_PROPERTY_5_INTEGER_MUTABLE_TRACK: - return randomGenerator.nextInt(); - case MATERIALS_PRODUCER_PROPERTY_6_DOUBLE_MUTABLE_TRACK: - return randomGenerator.nextDouble(); - case MATERIALS_PRODUCER_PROPERTY_7_BOOLEAN_IMMUTABLE_NO_TRACK: - return randomGenerator.nextBoolean(); - case MATERIALS_PRODUCER_PROPERTY_8_INTEGER_IMMUTABLE_NO_TRACK: - return randomGenerator.nextInt(); - case MATERIALS_PRODUCER_PROPERTY_9_DOUBLE_IMMUTABLE_NO_TRACK: - return randomGenerator.nextDouble(); - default: - - throw new RuntimeException("unhandled case: " + this); - - } - } - - /** - * Returns a randomly selected member of this enumeration. - */ - public static TestMaterialsProducerPropertyId getRandomMaterialsProducerPropertyId(final RandomGenerator randomGenerator) { - return TestMaterialsProducerPropertyId.values()[randomGenerator.nextInt(TestMaterialsProducerPropertyId.values().length)]; - } - - /** - * Returns a randomly selected member of this enumeration whose associated - * property definition is marked as mutable. - */ - public static TestMaterialsProducerPropertyId getRandomMutableMaterialsProducerPropertyId(final RandomGenerator randomGenerator) { - return TestMaterialsProducerPropertyId.values()[randomGenerator.nextInt(6)]; - } - - /** - * Returns the number of members in this enumeration. - */ - public static int size() { - return TestMaterialsProducerPropertyId.values().length; - } -} diff --git a/gcm3/src/main/java/plugins/partitions/PartitionsPlugin.java b/gcm3/src/main/java/plugins/partitions/PartitionsPlugin.java deleted file mode 100644 index 379c15aa4..000000000 --- a/gcm3/src/main/java/plugins/partitions/PartitionsPlugin.java +++ /dev/null @@ -1,57 +0,0 @@ -package plugins.partitions; - -import nucleus.Plugin; -import plugins.partitions.datamanagers.PartitionsDataManager; -import plugins.people.PeoplePluginId; -import plugins.stochastics.StochasticsPluginId; - -/** - * - * A nucleus plugin for the management of population partitions. A population - * partition represents a filtered and partitioned subset of the people in the - * simulation. - */ -public final class PartitionsPlugin { - private PartitionsPlugin() { - } - - /** - * Returns the partitions plugin. - * - *

    - * Uses PartitionsPluginId.PLUGIN_ID as its id - *

    - * - *

    - * Depends on plugins: - *

      - *
    • Stochastics Plugin
    • - *
    • People Plugin
    • - *
    - *

    - * - *

    - * Provides data mangers: - *

      - *
    • {@linkplain PartitionsDataManager}
    • - *
    - *

    - * - *

    - * Provides not actors: - *

    - * - */ - public static Plugin getPartitionsPlugin() { - - return Plugin .builder()// - .setPluginId(PartitionsPluginId.PLUGIN_ID).// - setInitializer((c) -> { - c.addDataManager(new PartitionsDataManager()); - })// - .addPluginDependency(PeoplePluginId.PLUGIN_ID)// - .addPluginDependency(StochasticsPluginId.PLUGIN_ID)// - .build(); - } - -} diff --git a/gcm3/src/main/java/plugins/partitions/PartitionsPluginId.java b/gcm3/src/main/java/plugins/partitions/PartitionsPluginId.java deleted file mode 100644 index 1a932754b..000000000 --- a/gcm3/src/main/java/plugins/partitions/PartitionsPluginId.java +++ /dev/null @@ -1,14 +0,0 @@ -package plugins.partitions; - -import nucleus.PluginId; -/** - * Static plugin id implementation for the GlobalsPlugin - * - * @author Shawn Hatch - * - */ - -public final class PartitionsPluginId implements PluginId { - public final static PluginId PLUGIN_ID = new PartitionsPluginId(); - private PartitionsPluginId() {}; -} diff --git a/gcm3/src/main/java/plugins/partitions/datamanagers/PartitionsDataManager.java b/gcm3/src/main/java/plugins/partitions/datamanagers/PartitionsDataManager.java deleted file mode 100644 index 9ce7ac2bb..000000000 --- a/gcm3/src/main/java/plugins/partitions/datamanagers/PartitionsDataManager.java +++ /dev/null @@ -1,586 +0,0 @@ -package plugins.partitions.datamanagers; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; - -import nucleus.DataManager; -import nucleus.DataManagerContext; -import nucleus.Event; -import plugins.partitions.support.DegeneratePopulationPartitionImpl; -import plugins.partitions.support.Filter; -import plugins.partitions.support.FilterSensitivity; -import plugins.partitions.support.LabelSet; -import plugins.partitions.support.Labeler; -import plugins.partitions.support.LabelerSensitivity; -import plugins.partitions.support.Partition; -import plugins.partitions.support.PartitionError; -import plugins.partitions.support.PartitionSampler; -import plugins.partitions.support.PopulationPartition; -import plugins.partitions.support.PopulationPartitionImpl; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.events.BulkPersonAdditionEvent; -import plugins.people.events.PersonAdditionEvent; -import plugins.people.events.PersonImminentRemovalEvent; -import plugins.people.support.BulkPersonConstructionData; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import plugins.stochastics.support.StochasticsError; -import util.errors.ContractException; - -/** - * Mutable data manager for managing population partitions that are maintained - * as data changes are made to people. - * - * - *

    - * Subscribes to the following events for all partitions: - *

    - *
      - *
    • {@linkplain PersonAdditionEvent}
      Adds the person to all - * relevant population partitions after event validation and execution phases - * are complete.
    • - * - * - *
    • {@linkplain BulkPersonAdditionEvent}
      Adds the people to the - * relevant population partitions after event validation and execution phases - * are complete.
    • - * - * - *
    • {@linkplain PersonImminentRemovalEvent}
      Removes the person - * from all population partitions by scheduling the removal for the current - * time. This allows references and partition memberships to remain long enough - * for resolvers, agents and reports to have final reference to the person while - * still associated with any relevant partitions.
    • - *
    - * - *

    - * Subscribes to other events as needed to support the population partition - * maintenance - *

    - * - * - * @author Shawn Hatch - * - */ - -public final class PartitionsDataManager extends DataManager { - - private final Map>> keyToEventClassesMap = new LinkedHashMap<>(); - - private final Set> reservedEventClasses = new LinkedHashSet<>(); - - private final Map, Set> eventClassToKeyMap = new LinkedHashMap<>(); - - private DataManagerContext dataManagerContext; - private PeopleDataManager peopleDataManager; - - private final Map keyToPopulationPartitionMap = new LinkedHashMap<>(); - private final Set safePartitionKeys = Collections.unmodifiableSet(keyToPopulationPartitionMap.keySet()); - - /* - * Returns the set of keys for population partitions - */ - private Set getKeys() { - return safePartitionKeys; - } - - /** - * Returns the list of person identifiers in the population partition for - * the given key. - * - * @throws ContractException - * - *
  • {@link PartitionError.NULL_PARTITION_KEY} if the key is - * null
  • - * - *
  • {@link PartitionError.UNKNOWN_POPULATION_PARTITION_KEY} - * if the key is unknown
  • - */ - public List getPeople(final Object key) { - validateKeyExists(key); - return getPopulationPartition(key).getPeople(); - } - - /** - * Returns the list of person identifiers in the population partition for - * the given key. A person is included if every label in the label set is - * equal to the corresponding label for the person. - * - * @throws ContractException - * - *
  • {@link PartitionError.NULL_PARTITION_KEY} if the key is - * null
  • - * - *
  • {@link PartitionError.UNKNOWN_POPULATION_PARTITION_KEY} - * if the key is unknown
  • - * - *
  • {@link PartitionError.NULL_LABEL_SET} if the label set is - * null
  • - * - *
  • {@link PartitionError.INCOMPATIBLE_LABEL_SET} if the - * label set contains dimensions not contained in the population - * partition
  • - */ - public List getPeople(final Object key, LabelSet labelSet) { - validateKeyExists(key); - PopulationPartition populationPartition = getPopulationPartition(key); - validateLabelSet(populationPartition, labelSet); - return populationPartition.getPeople(labelSet); - } - - /** - * Returns the number of people in the population partition for the given - * key. - * - *
  • {@link PartitionError.NULL_PARTITION_KEY} if the key is null
  • - * - *
  • {@link PartitionError.UNKNOWN_POPULATION_PARTITION_KEY} if the key is - * unknown
  • - */ - public int getPersonCount(final Object key) { - validateKeyExists(key); - PopulationPartition populationPartition = getPopulationPartition(key); - return populationPartition.getPeopleCount(); - } - - /** - * Returns the number of people in the population partition for the given - * key under the given label set. A person is counted if every label in the - * label set is equal to the corresponding label for the person. - * - * @throws ContractException - * - *
  • {@link PartitionError.NULL_PARTITION_KEY} if the key is - * null
  • - * - *
  • {@link PartitionError.UNKNOWN_POPULATION_PARTITION_KEY} - * if the key is unknown
  • - * - *
  • {@link PartitionError.NULL_LABEL_SET} if the label set is - * null
  • - * - *
  • {@link PartitionError.INCOMPATIBLE_LABEL_SET} if the - * label set contains dimensions not contained in the population - * partition
  • - * - */ - public int getPersonCount(final Object key, LabelSet labelSet) { - validateKeyExists(key); - PopulationPartition populationPartition = getPopulationPartition(key); - validateLabelSet(populationPartition, labelSet); - return populationPartition.getPeopleCount(labelSet); - } - - /** - * Returns a mapping from LabelSet to Integer whose keys are all the label - * sets in the population partition that match the given label set. The - * values are the number of people matching that label set. A person is - * counted if every label in the label set is equal to the corresponding - * label for the person. All values will be positive. - * - * @throws ContractException - * - *
  • {@link PartitionError.NULL_PARTITION_KEY} if the key is - * null
  • - * - *
  • {@link PartitionError.UNKNOWN_POPULATION_PARTITION_KEY} - * if the key is unknown
  • - * - *
  • {@link PartitionError.NULL_LABEL_SET} if the label set is - * null
  • - * - *
  • {@link PartitionError.INCOMPATIBLE_LABEL_SET} if the - * label set contains dimensions not contained in the population - * partition
  • - * - */ - public Map getPeopleCountMap(final Object key, LabelSet labelSet) { - validateKeyExists(key); - PopulationPartition populationPartition = getPopulationPartition(key); - validateLabelSet(populationPartition, labelSet); - return populationPartition.getPeopleCountMap(labelSet); - } - - /** - * Returns a randomly selected person from the given population partition - * using the given PartitionSampler. - * - * @throws ContractException - * - *
  • {@link PartitionError.NULL_PARTITION_KEY} if the key is - * null
  • - * - *
  • {@link PartitionError.UNKNOWN_POPULATION_PARTITION_KEY} - * if the key is unknown
  • - * - *
  • {@link PartitionError.NULL_PARTITION_SAMPLER} if the - * partition sampler is null
  • - * - *
  • {@link PartitionError.INCOMPATIBLE_LABEL_SET} if the - * partition sampler has a label set containing dimensions not - * present in the population partition
  • - * - *
  • {@link PersonError.UNKNOWN_PERSON_ID} if the partition - * sampler has an excluded person that does not exist
  • - * - *
  • {@link StochasticsError.UNKNOWN_RANDOM_NUMBER_GENERATOR_ID} - * if the partition sampler has a random number generator id - * that is unknown
  • - * - */ - public Optional samplePartition(Object key, PartitionSampler partitionSampler) { - validateKeyExists(key); - PopulationPartition populationPartition = getPopulationPartition(key); - validatePartitionSampler(populationPartition, partitionSampler); - return populationPartition.samplePartition(partitionSampler); - } - - /** - * Returns true if and only if the person is contained in the population - * partition corresponding to the key. - * - * @throws ContractException - * - *
  • {@link PersonError.NULL_PERSON_ID} if the person id is - * null
  • - * - *
  • {@link PersonError.UNKNOWN_PERSON_ID} if the person id is - * null
  • - * - *
  • {@link PartitionError.NULL_PARTITION_KEY} if the key is - * null
  • - * - *
  • {@link PartitionError.UNKNOWN_POPULATION_PARTITION_KEY} - * if the key is unknown
  • - */ - - public boolean contains(final PersonId personId, Object key) { - validatePersonNotNull(personId); - validatePersonExists(personId); - validateKeyExists(key); - PopulationPartition populationPartition = getPopulationPartition(key); - return populationPartition.contains(personId); - } - - /** - * Returns true if and only if the person is contained in the population - * corresponding to the key. A person is counted if every label in the label - * set is equal to the corresponding label for the person. - * - * @throws ContractException - * - *
  • {@link PersonError.NULL_PERSON_ID} if the person id is - * null
  • - * - *
  • {@link PersonError.UNKNOWN_PERSON_ID} if the person id is - * null
  • - * - *
  • {@link PartitionError.NULL_PARTITION_KEY} if the key is - * null
  • - * - *
  • {@link PartitionError.UNKNOWN_POPULATION_PARTITION_KEY} - * if the key is unknown
  • - * - *
  • {@link PartitionError.NULL_LABEL_SET} if the label set is - * null
  • - * - *
  • {@link PartitionError.INCOMPATIBLE_LABEL_SET} if the - * label contains a dimension not present in the partition
  • - * - * - * - */ - - public boolean contains(PersonId personId, LabelSet labelSet, Object key) { - validatePersonNotNull(personId); - validatePersonExists(personId); - validateKeyExists(key); - PopulationPartition populationPartition = getPopulationPartition(key); - validateLabelSet(populationPartition, labelSet); - - return populationPartition.contains(personId, labelSet); - } - - /** - * Returns true if and only if a partition is associated with the given key. - * Null tolerant. - */ - public boolean partitionExists(final Object key) { - return keyToPopulationPartitionMap.containsKey(key); - } - - private void validateLabelSet(final PopulationPartition populationPartition, final LabelSet labelSet) { - if (labelSet == null) { - throw new ContractException(PartitionError.NULL_LABEL_SET); - } - - if (!populationPartition.validateLabelSetInfo(labelSet)) { - throw new ContractException(PartitionError.INCOMPATIBLE_LABEL_SET); - } - } - - private void validatePartitionSampler(PopulationPartition populationPartition, final PartitionSampler partitionSampler) { - if (partitionSampler == null) { - throw new ContractException(PartitionError.NULL_PARTITION_SAMPLER); - } - - if (partitionSampler.getLabelSet().isPresent()) { - final LabelSet labelSet = partitionSampler.getLabelSet().get(); - if (!populationPartition.validateLabelSetInfo(labelSet)) { - throw new ContractException(PartitionError.INCOMPATIBLE_LABEL_SET); - } - } - - if (partitionSampler.getExcludedPerson().isPresent()) { - final PersonId excludedPersonId = partitionSampler.getExcludedPerson().get(); - validatePersonExists(excludedPersonId); - } - - } - - /* - * Precondition : person id is not null - */ - private void validatePersonExists(final PersonId personId) { - if (!peopleDataManager.personExists(personId)) { - throw new ContractException(PersonError.UNKNOWN_PERSON_ID); - } - } - - private void validatePersonNotNull(final PersonId personId) { - if (personId == null) { - throw new ContractException(PersonError.NULL_PERSON_ID); - } - } - - private void validateKeyExists(final Object key) { - if (key == null) { - throw new ContractException(PartitionError.NULL_PARTITION_KEY); - } - - if (!keyToPopulationPartitionMap.containsKey(key)) { - throw new ContractException(PartitionError.UNKNOWN_POPULATION_PARTITION_KEY, key); - } - } - - @Override - public void init(DataManagerContext dataManagerContext) { - super.init(dataManagerContext); - this.dataManagerContext = dataManagerContext; - peopleDataManager = dataManagerContext.getDataManager(PeopleDataManager.class); - - dataManagerContext.subscribePostOrder(PersonAdditionEvent.class, this::handlePersonAdditionEvent); - - dataManagerContext.subscribePostOrder(BulkPersonAdditionEvent.class, this::handleBulkPersonAdditionEvent); - - dataManagerContext.subscribePostOrder(PersonImminentRemovalEvent.class, this::handlePersonImminentRemovalEvent); - - } - - /* - * Returns the population partition associated with the given key. Returns - * null if the key is not recognized. - */ - private PopulationPartition getPopulationPartition(final Object key) { - return keyToPopulationPartitionMap.get(key); - } - - private void validatePopulationPartitionDoesNotExist(final Object key) { - if (keyToPopulationPartitionMap.containsKey(key)) { - throw new ContractException(PartitionError.DUPLICATE_PARTITION, key); - } - } - - /* - * Precondition : the key is not null - */ - private void validatePopulationPartitionExists(final Object key) { - if (!keyToPopulationPartitionMap.containsKey(key)) { - throw new ContractException(PartitionError.UNKNOWN_POPULATION_PARTITION_KEY, key); - } - } - - private void validatePopulationPartitionKeyNotNull(final Object key) { - if (key == null) { - throw new ContractException(PartitionError.NULL_PARTITION_KEY); - } - } - - private void validatePopulationPartitionNotNull(final Partition partition) { - if (partition == null) { - throw new ContractException(PartitionError.NULL_PARTITION); - } - } - - private void handlePersonAdditionEvent(final DataManagerContext dataManagerContext, final PersonAdditionEvent personAdditionEvent) { - final PersonId personId = personAdditionEvent.getPersonId(); - for (final Object key : getKeys()) { - final PopulationPartition populationPartition = getPopulationPartition(key); - populationPartition.attemptPersonAddition(personId); - } - } - - /* - * Returns true if and only if there are not population partitions contained - * in this manager. - */ - private boolean isEmpty() { - return keyToPopulationPartitionMap.isEmpty(); - } - - private void handleBulkPersonAdditionEvent(final DataManagerContext dataManagerContext, final BulkPersonAdditionEvent bulkPersonAdditionEvent) { - if (isEmpty()) { - return; - } - final PersonId basePersonId = bulkPersonAdditionEvent.getPersonId(); - final int lowId = basePersonId.getValue(); - final BulkPersonConstructionData bulkPersonConstructionData = bulkPersonAdditionEvent.getBulkPersonConstructionData(); - int highId = bulkPersonConstructionData.getPersonConstructionDatas().size(); - highId += lowId; - final List personIds = new ArrayList<>(); - for (int id = lowId; id < highId; id++) { - final PersonId boxedPersonId = peopleDataManager.getBoxedPersonId(id).get(); - personIds.add(boxedPersonId); - } - final Set partitionIds = getKeys(); - for (final Object key : partitionIds) { - final PopulationPartition populationPartition = getPopulationPartition(key); - for (final PersonId personId : personIds) { - populationPartition.attemptPersonAddition(personId); - } - } - - } - - private void handlePersonImminentRemovalEvent(final DataManagerContext dataManagerContext, final PersonImminentRemovalEvent personImminentRemovalEvent) { - dataManagerContext.addPlan((context) -> { - for (final Object key : getKeys()) { - final PopulationPartition populationPartition = getPopulationPartition(key); - populationPartition.attemptPersonRemoval(personImminentRemovalEvent.getPersonId()); - } - }, dataManagerContext.getTime()); - } - - /** - * Adds a population partition for the given key and component id. The key - * must not duplicate an existing key. - * - * @throws ContractException - * - *
  • {@linkplain PartitionError#NULL_PARTITION} if the - * partition is null
  • - *
  • {@linkplain PartitionError#NULL_PARTITION_KEY} if the key - * is null
  • - *
  • {@linkplain PartitionError#DUPLICATE_PARTITION} if a - * partition is currently associated with the key
  • - * - */ - - public void addPartition(final Partition partition, final Object key) { - - validatePopulationPartitionNotNull(partition); - validatePopulationPartitionKeyNotNull(key); - validatePopulationPartitionDoesNotExist(key); - - /* - * determine the event classes that will trigger refreshes on the - * partition - */ - final Set> eventClasses = new LinkedHashSet<>(); - - final Filter filter = partition.getFilter().orElse(Filter.allPeople()); - filter.validate(dataManagerContext); - for (final FilterSensitivity filterSensitivity : filter.getFilterSensitivities()) { - eventClasses.add(filterSensitivity.getEventClass()); - } - - for (final Labeler labeler : partition.getLabelers()) { - final Set> labelerSensitivities = labeler.getLabelerSensitivities(); - for (final LabelerSensitivity labelerSensitivity : labelerSensitivities) { - eventClasses.add(labelerSensitivity.getEventClass()); - } - } - - // show that these classes do not include the events that this resolver - // processes - for (final Class reservedEventClass : reservedEventClasses) { - if (eventClasses.contains(reservedEventClass)) { - throw new ContractException(PartitionError.RESERVED_PARTITION_TRIGGER, reservedEventClass); - } - } - - keyToEventClassesMap.put(key, eventClasses); - - /* - * Integrate this into the subscription management maps and subscribe - * for the event class if needed - */ - for (final Class eventClass : eventClasses) { - Set keys = eventClassToKeyMap.get(eventClass); - if (keys == null) { - keys = new LinkedHashSet<>(); - eventClassToKeyMap.put(eventClass, keys); - dataManagerContext.subscribePostOrder(eventClass, this::handleEvent); - } - keys.add(key); - } - - // pass the partition to the partition manager - - PopulationPartition populationPartition; - if (partition.isDegenerate()) { - populationPartition = new DegeneratePopulationPartitionImpl(dataManagerContext, partition); - } else { - populationPartition = new PopulationPartitionImpl(dataManagerContext, partition); - } - keyToPopulationPartitionMap.put(key, populationPartition); - - } - - private void handleEvent(final DataManagerContext dataManagerContext, final Event event) { - final Set keys = eventClassToKeyMap.get(event.getClass()); - if (keys != null) { - for (final Object key : keys) { - final PopulationPartition populationPartition = getPopulationPartition(key); - populationPartition.handleEvent(event); - } - } else { - throw new RuntimeException("received unhandled event " + event); - } - } - - /** - * Removes the population index for the given key if that key is present. - * Returns true if and only if the partition was removed. - * - *
  • {@linkplain PartitionError#NULL_PARTITION_KEY} if the key is - * null
  • - *
  • {@linkplain PartitionError#UNKNOWN_POPULATION_PARTITION_KEY} if the - * key is unknown
  • - * - * - */ - public void removePartition(final Object key) { - - validatePopulationPartitionKeyNotNull(key); - validatePopulationPartitionExists(key); - keyToPopulationPartitionMap.remove(key); - - final Set> eventClasses = keyToEventClassesMap.get(key); - for (final Class eventClass : eventClasses) { - final Set keys = eventClassToKeyMap.get(eventClass); - keys.remove(key); - if (keys.isEmpty()) { - eventClassToKeyMap.remove(eventClass); - dataManagerContext.unSubscribe(eventClass); - } - } - } - -} \ No newline at end of file diff --git a/gcm3/src/main/java/plugins/partitions/support/DegeneratePopulationPartitionImpl.java b/gcm3/src/main/java/plugins/partitions/support/DegeneratePopulationPartitionImpl.java deleted file mode 100644 index 46bf78d75..000000000 --- a/gcm3/src/main/java/plugins/partitions/support/DegeneratePopulationPartitionImpl.java +++ /dev/null @@ -1,215 +0,0 @@ -package plugins.partitions.support; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import org.apache.commons.math3.random.RandomGenerator; - -import nucleus.Event; -import nucleus.SimulationContext; -import plugins.partitions.support.containers.BasePeopleContainer; -import plugins.partitions.support.containers.PeopleContainer; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.PersonId; -import plugins.stochastics.StochasticsDataManager; -import plugins.stochastics.support.RandomNumberGeneratorId; -import util.errors.ContractException; - -/** - * Implementation of PopulationPartition for degenerate partitions having only a - * filter and a single cell in its partition space, i.e. the partition was - * specified with no labeling functions. - */ -public class DegeneratePopulationPartitionImpl implements PopulationPartition { - - private final StochasticsDataManager stochasticsDataManager; - - private final PeopleContainer peopleContainer; - - private final SimulationContext simulationContext; - - private final Filter filter; - - private final Map, List>> eventClassToFilterSensitivityMap = new LinkedHashMap<>(); - - /** - * Constructs an DegeneratePopulationPartitionImpl - * - * @throws ContractException - *
  • {@linkplain PartitionError#NON_DEGENERATE_PARTITION} if - * the partition contains labelers
  • - * - * @throws RuntimeException - *
  • if context is null
  • - *
  • if partition is null
  • - *
  • if the partition contains labelers
  • - */ - public DegeneratePopulationPartitionImpl(final SimulationContext simulationContext, final Partition partition) { - - this.simulationContext = simulationContext; - stochasticsDataManager = simulationContext.getDataManager(StochasticsDataManager.class); - filter = partition.getFilter().orElse(Filter.allPeople()); - - if (!partition.isDegenerate()) { - throw new ContractException(PartitionError.NON_DEGENERATE_PARTITION); - } - - for (final FilterSensitivity filterSensitivity : filter.getFilterSensitivities()) { - List> list = eventClassToFilterSensitivityMap.get(filterSensitivity.getEventClass()); - if (list == null) { - list = new ArrayList<>(); - eventClassToFilterSensitivityMap.put(filterSensitivity.getEventClass(), list); - } - list.add(filterSensitivity); - } - - peopleContainer = new BasePeopleContainer(simulationContext); - - final PeopleDataManager peopleDataManager = simulationContext.getDataManager(PeopleDataManager.class); - final int personIdLimit = peopleDataManager.getPersonIdLimit(); - for (int i = 0; i < personIdLimit; i++) { - if (peopleDataManager.personIndexExists(i)) { - final PersonId personId = peopleDataManager.getBoxedPersonId(i).get(); - if (filter.evaluate(simulationContext, personId)) { - /* - * Using unsafe add since this is in the constructor, we are - * sure that the person is not already contained - */ - peopleContainer.unsafeAdd(personId); - } - } - } - - } - - @Override - public void attemptPersonAddition(final PersonId personId) { - if (filter.evaluate(simulationContext, personId)) { - /* - * By contract, this method is only invoked with person ids that are - * new to the simulation or new to this population partition and - * thus cannot already in members of this population partition. - */ - peopleContainer.unsafeAdd(personId); - } - } - - @Override - public void attemptPersonRemoval(final PersonId personId) { - peopleContainer.remove(personId); - } - - @Override - public boolean contains(final PersonId personId) { - return peopleContainer.contains(personId); - } - - @Override - public boolean contains(final PersonId personId, final LabelSet labelSet) { - return peopleContainer.contains(personId); - } - - /** - * Returns the people identifiers of this index - */ - @Override - public List getPeople() { - return peopleContainer.getPeople(); - } - - @Override - public List getPeople(final LabelSet labelSet) { - return peopleContainer.getPeople(); - } - - @Override - public int getPeopleCount() { - return peopleContainer.size(); - } - - @Override - public int getPeopleCount(final LabelSet labelSet) { - return peopleContainer.size(); - } - - /** - * Returns a map whose single key is an empty label set and whose single - * value is the number of people in the population partition. - * - * Precondition: the label set should be empty or null. - */ - @Override - public Map getPeopleCountMap(LabelSet labelSet) { - Map result = new LinkedHashMap<>(); - result.put(LabelSet.builder().build(), peopleContainer.size()); - return result; - } - - @Override - public void handleEvent(final Event event) { - PersonId personId = null; - final List> filterSensitivities = eventClassToFilterSensitivityMap.get(event.getClass()); - if (filterSensitivities != null) { - for (final FilterSensitivity filterSensitivity : filterSensitivities) { - final Optional optionalPersonId = filterSensitivity.requiresRefresh(simulationContext, event); - if (optionalPersonId.isPresent()) { - personId = optionalPersonId.get(); - break; - } - } - } - - if (personId != null) { - if (filter.evaluate(simulationContext, personId)) { - peopleContainer.safeAdd(personId); - } else { - peopleContainer.remove(personId); - } - } - - } - - /** - * Forces the index to evaluate a person's membership in this index. - */ - - @Override - public Optional samplePartition(final PartitionSampler partitionSampler) { - RandomGenerator randomGenerator; - final RandomNumberGeneratorId randomNumberGeneratorId = partitionSampler.getRandomNumberGeneratorId().orElse(null); - if (randomNumberGeneratorId != null) { - randomGenerator = stochasticsDataManager.getRandomGeneratorFromId(randomNumberGeneratorId); - } else { - randomGenerator = stochasticsDataManager.getRandomGenerator(); - } - - final PersonId excludedPersonId = partitionSampler.getExcludedPerson().orElse(null); - - int candidateCount = peopleContainer.size(); - if (excludedPersonId != null) { - if (peopleContainer.contains(excludedPersonId)) { - candidateCount--; - } - } - PersonId result = null; - if (candidateCount > 0) { - while (true) { - result = peopleContainer.getRandomPersonId(randomGenerator); - if (!result.equals(excludedPersonId)) { - break; - } - } - } - - return Optional.ofNullable(result); - } - - @Override - public boolean validateLabelSetInfo(final LabelSet labelSet) { - return labelSet.isEmpty(); - } - -} diff --git a/gcm3/src/main/java/plugins/partitions/support/EventPredicate.java b/gcm3/src/main/java/plugins/partitions/support/EventPredicate.java deleted file mode 100644 index 5c1e3b38b..000000000 --- a/gcm3/src/main/java/plugins/partitions/support/EventPredicate.java +++ /dev/null @@ -1,26 +0,0 @@ -package plugins.partitions.support; - -import java.util.Optional; - -import nucleus.Event; -import nucleus.SimulationContext; -import plugins.people.support.PersonId; - -/** - * A generics based function that returns an optional {@link PersonId} from a - * {@link SimulationContext} and {@link Event}. Used by {@link FilterSensitivity} to - * ascertain the person id from an event and whether that event would effect the - * filter that owns the filter sensitivity. - * - * - * @author Shawn Hatch - * - * - */ -public interface EventPredicate { - /** - * Returns an optional of the person id associated with the event if that - * event would effect that filter. Returns an empty optional otherwise. - */ - public Optional requiresRefresh(SimulationContext simulationContext, T event); -} \ No newline at end of file diff --git a/gcm3/src/main/java/plugins/partitions/support/Filter.java b/gcm3/src/main/java/plugins/partitions/support/Filter.java deleted file mode 100644 index 79016c583..000000000 --- a/gcm3/src/main/java/plugins/partitions/support/Filter.java +++ /dev/null @@ -1,247 +0,0 @@ -package plugins.partitions.support; - -import java.util.LinkedHashSet; -import java.util.Set; - -import nucleus.NucleusError; -import nucleus.SimulationContext; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import util.errors.ContractException; - -public abstract class Filter { - - private static class AndFilter extends Filter { - final Filter a; - final Filter b; - - public AndFilter(Filter a, Filter b) { - this.a = a; - this.b = b; - } - - @Override - public boolean evaluate(SimulationContext simulationContext, PersonId personId) { - return a.evaluate(simulationContext, personId) && b.evaluate(simulationContext, personId); - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("AndFilter [a="); - builder.append(a); - builder.append(", b="); - builder.append(b); - builder.append("]"); - return builder.toString(); - } - - @Override - public Set> getFilterSensitivities() { - Set> result = a.getFilterSensitivities(); - result.addAll(b.getFilterSensitivities()); - return result; - } - - @Override - public void validate(SimulationContext simulationContext) { - a.validate(simulationContext); - b.validate(simulationContext); - } - - } - - private static class OrFilter extends Filter { - final Filter a; - final Filter b; - - public OrFilter(Filter a, Filter b) { - this.a = a; - this.b = b; - } - - @Override - public boolean evaluate(SimulationContext simulationContext, PersonId personId) { - return a.evaluate(simulationContext, personId) || b.evaluate(simulationContext, personId); - } - - @Override - public Set> getFilterSensitivities() { - Set> result = a.getFilterSensitivities(); - result.addAll(b.getFilterSensitivities()); - return result; - } - - @Override - public void validate(SimulationContext simulationContext) { - a.validate(simulationContext); - b.validate(simulationContext); - } - } - - private static class NotFilter extends Filter { - final Filter a; - - public NotFilter(Filter a) { - this.a = a; - } - - @Override - public void validate(SimulationContext simulationContext) { - a.validate(simulationContext); - } - - @Override - public boolean evaluate(SimulationContext simulationContext, PersonId personId) { - return !a.evaluate(simulationContext, personId); - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("NotFilter [a="); - builder.append(a); - builder.append("]"); - return builder.toString(); - } - - @Override - public Set> getFilterSensitivities() { - return a.getFilterSensitivities(); - } - - } - - /** - * Returns a filter that is the conjunction of this and the given filter. - * - * @throws ContractException - *
  • {@linkplain PartitionError#NULL_FILTER} if the filter is - * null
  • - */ - public final Filter and(Filter filter) { - if (filter == null) { - throw new ContractException(PartitionError.NULL_FILTER); - } - return new AndFilter(this, filter); - } - - /** - * Returns a filter that is the disjunction of this and the given filter. - * - * @throws ContractException - *
  • {@linkplain PartitionError#NULL_FILTER} if the filter is - * null
  • - */ - public final Filter or(Filter filter) { - if (filter == null) { - throw new ContractException(PartitionError.NULL_FILTER); - } - return new OrFilter(this, filter); - } - - /** - * Returns a filter that is the negation of this filter. - */ - public final Filter negate() { - return new NotFilter(this); - } - - private final static class NoPeopleFilter extends Filter { - - @Override - public boolean evaluate(SimulationContext simulationContext, PersonId personId) { - return false; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("NoPeopleFilter []"); - return builder.toString(); - } - - @Override - public Set> getFilterSensitivities() { - return new LinkedHashSet<>(); - } - - @Override - public void validate(SimulationContext simulationContext) { - - } - - } - - private final static class AllPeopleFilter extends Filter { - - @Override - public boolean evaluate(SimulationContext simulationContext, PersonId personId) { - return true; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("AllPeopleFilter []"); - return builder.toString(); - } - - @Override - public Set> getFilterSensitivities() { - return new LinkedHashSet<>(); - } - - @Override - public void validate(SimulationContext simulationContext) { - - } - } - - /** - * Evaluates the person against the filter. - * - * Preconditions : - * - * @throws ContractException - *
  • {@linkplain NucleusError#NULL_SIMULATION_CONTEXT} if the context is not null
  • - * - * - *
  • {@linkplain PersonError#NULL_PERSON_ID} if the person id is not null
  • - * - *
  • {@linkplain PersonError#UNKNOWN_PERSON_ID} if the person id is known
  • - */ - public abstract boolean evaluate(SimulationContext simulationContext, PersonId personId); - - /** - * Validates the filter from the given context. - * - * Preconditions: - * - *
  • the context is not null
  • - */ - public abstract void validate(SimulationContext simulationContext); - - /** - * Returns the filter sensitivities - */ - public abstract Set> getFilterSensitivities(); - - /** - * Returns a filter that passes no people. Used for concatenating filters in - * an OR loop. - */ - public static Filter noPeople() { - return new NoPeopleFilter(); - } - - /** - * Returns a filter that passes all people. Used for concatenating filters - * in an AND loop. - */ - public static Filter allPeople() { - return new AllPeopleFilter(); - - } - -} diff --git a/gcm3/src/main/java/plugins/partitions/support/FilterSensitivity.java b/gcm3/src/main/java/plugins/partitions/support/FilterSensitivity.java deleted file mode 100644 index a804d74e8..000000000 --- a/gcm3/src/main/java/plugins/partitions/support/FilterSensitivity.java +++ /dev/null @@ -1,73 +0,0 @@ -package plugins.partitions.support; - -import java.util.Optional; - -import net.jcip.annotations.Immutable; -import nucleus.Event; -import nucleus.SimulationContext; -import plugins.people.support.PersonId; - -/** - * Partitions are maintained as events relating to people are resolved. To do so - * efficiently, we need to determine whether a particular event will trigger a - * change to either a person's membership in the partition or their position - * within the partition space. - * - * Partition filters describe their sensitivity to events via this generics-base - * class. - * - * @author Shawn Hatch - * - * - */ -@Immutable -public final class FilterSensitivity { - - private class MetaPredicate { - - private final EventPredicate eventPredicate; - - public MetaPredicate(EventPredicate eventPredicate) { - this.eventPredicate = eventPredicate; - } - - @SuppressWarnings("unchecked") - public Optional requiresRefresh(SimulationContext simulationContext, Event event) { - return eventPredicate.requiresRefresh(simulationContext, (K) event); - } - } - - private final Class eventClass; - - private final MetaPredicate metaPredicate; - - /** - * Creates this FilterSensitivity with the generic event type and given - * event predicate. - */ - public FilterSensitivity(Class eventClass, EventPredicate eventPredicate) { - super(); - this.eventClass = eventClass; - this.metaPredicate = new MetaPredicate(eventPredicate); - } - - /** - * Returns the event class for this filter sensitivity - */ - public Class getEventClass() { - return eventClass; - } - - /** - * Returns a PersonId if and only if the event would require a refresh from - * the owning filter. The partition resolver is not able to determine from - * an event the person id of an event that possibly changes the membership - * of a person in a partition. The person id returned is the person that - * will trigger the refresh and is thus the person id associated with the - * event. - */ - public Optional requiresRefresh(SimulationContext simulationContext, Event event) { - return metaPredicate.requiresRefresh(simulationContext, event); - } - -} \ No newline at end of file diff --git a/gcm3/src/main/java/plugins/partitions/support/LabelFunction.java b/gcm3/src/main/java/plugins/partitions/support/LabelFunction.java deleted file mode 100644 index df60539f0..000000000 --- a/gcm3/src/main/java/plugins/partitions/support/LabelFunction.java +++ /dev/null @@ -1,8 +0,0 @@ -package plugins.partitions.support; - -import nucleus.SimulationContext; -import plugins.people.support.PersonId; - -public interface LabelFunction { - public Object getLabel(SimulationContext simulationContext, PersonId personId); -} \ No newline at end of file diff --git a/gcm3/src/main/java/plugins/partitions/support/LabelSet.java b/gcm3/src/main/java/plugins/partitions/support/LabelSet.java deleted file mode 100644 index ba81cc043..000000000 --- a/gcm3/src/main/java/plugins/partitions/support/LabelSet.java +++ /dev/null @@ -1,153 +0,0 @@ -package plugins.partitions.support; - -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Optional; -import java.util.Set; - -import util.errors.ContractException; - -/** - * A {@linkplain LabelSet} is a set of labels that are used to specify a sub-set - * of the cell space of a partition during sampling. - * - * Partitions are composed of cells that are associated with combinations of - * labels associated with the various attributes of people. The label set - * specifies a subset of that space by value. - * - * For example: Suppose a partition is formed by the regions and two person - * properties. The regions are grouped together under state labels. The first - * property is the Integer AGE and is grouped by PRESCHOOL, SCHOOL and ADULT. - * The second property is the Integer VACCINE_DOSES_RECEIVED and ranges from 0 - * to 3 inclusive. - * - * The {@link LabelSet} [REGION = VIRGINIA, AGE=PRESHOOL, - * VACCINE_DOSES_RECEIVED=2] will match the single partition cell that - * represents Virginia preschoolers who have received 2 doses of vaccine. The - * {@link LabelSet} [REGION = VIRGINIA, AGE=PRESHOOL] will match all partition - * cells that represent Virginia preschoolers, without regard to doses of - * vaccine received. - * - * - * Label Sets are built by the modeler via the supplied Builder class. - * - * @author Shawn Hatch - * - */ - -public final class LabelSet { - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((labels == null) ? 0 : labels.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - LabelSet other = (LabelSet) obj; - if (labels == null) { - if (other.labels != null) - return false; - } else if (!labels.equals(other.labels)) - return false; - return true; - } - - /** - * Returns a new Builder instance - */ - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - - private Builder() { - - } - - private Scaffold scaffold = new Scaffold(); - - public LabelSet build() { - try { - return new LabelSet(scaffold); - } finally { - scaffold = new Scaffold(); - } - } - - /** - * Sets the dimension label - * - * @throws ContractException - *
  • {@linkplain PartitionError#NULL_PARTITION_LABEL_DIMENSION} - * if the dimension is null
  • - *
  • {@linkplain PartitionError#NULL_PARTITION_LABEL} if - * the label is null
  • - */ - public Builder setLabel(Object dimension, Object label) { - if (dimension == null) { - throw new ContractException(PartitionError.NULL_PARTITION_LABEL_DIMENSION); - } - if (label == null) { - throw new ContractException(PartitionError.NULL_PARTITION_LABEL); - } - scaffold.labels.put(dimension, label); - return this; - } - - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("LabelSet [labels="); - builder.append(labels); - builder.append("]"); - return builder.toString(); - } - - private static class Scaffold { - private Map labels = new LinkedHashMap<>(); - } - - /** - * Returns the dimension label for this {@link LabelSet} - */ - public Optional getLabel(Object dimension) { - return Optional.ofNullable(this.labels.get(dimension)); - } - - /** - * Returns an unmodifiable list of dimensions - */ - public Set getDimensions() { - return dimensions; - } - - /** - * Returns true if and only if this {@link LabelSet} has no label values - */ - public boolean isEmpty() { - return labels.isEmpty(); - } - - private LabelSet(Scaffold scaffold) { - this.labels = scaffold.labels; - this.dimensions = Collections.unmodifiableSet(new LinkedHashSet<>(labels.keySet())); - } - - private final Map labels; - private final Set dimensions; -} diff --git a/gcm3/src/main/java/plugins/partitions/support/LabelSetWeightingFunction.java b/gcm3/src/main/java/plugins/partitions/support/LabelSetWeightingFunction.java deleted file mode 100644 index dce8a57e0..000000000 --- a/gcm3/src/main/java/plugins/partitions/support/LabelSetWeightingFunction.java +++ /dev/null @@ -1,20 +0,0 @@ -package plugins.partitions.support; - -import nucleus.SimulationContext; - -/** - * A functional interface for selecting people from a {@link Partition} based on - * assigning a weighting value to a {@link LabelSet}. - * - * @author Shawn Hatch - * - */ -public interface LabelSetWeightingFunction { - /** - * Returns a non-negative, finite and stable value for the given inputs. - * This function should be stable: repeated invocations with the same - * arguments should return the same value during the span of a single sample - * of a {@link Partition}. - */ - public double getWeight(SimulationContext simulationContext, LabelSet labelSet); -} diff --git a/gcm3/src/main/java/plugins/partitions/support/Labeler.java b/gcm3/src/main/java/plugins/partitions/support/Labeler.java deleted file mode 100644 index 9659fa7ac..000000000 --- a/gcm3/src/main/java/plugins/partitions/support/Labeler.java +++ /dev/null @@ -1,39 +0,0 @@ -package plugins.partitions.support; - -import java.util.Set; - -import nucleus.Event; -import nucleus.SimulationContext; -import plugins.people.support.PersonId; - -/** - * A partition labeler creates an object label for a person. The labeler has an - * object dimension which is a dimension within a partition and the label - * occupies a point in that dimension. - * - * @author Shawn Hatch - * - */ - -public interface Labeler { - - /** - * Returns the labeler sensitivities associated with this label - */ - public Set> getLabelerSensitivities(); - - /** - * Returns the label for the person - */ - public Object getLabel(SimulationContext simulationContext, PersonId personId); - - /** - * Returns the label for the person based on the previous value recored in the event - */ - public Object getPastLabel(SimulationContext simulationContext, Event event); - - /** - * Returns the dimension for this labeler. - */ - public Object getDimension(); -} diff --git a/gcm3/src/main/java/plugins/partitions/support/LabelerSensitivity.java b/gcm3/src/main/java/plugins/partitions/support/LabelerSensitivity.java deleted file mode 100644 index 737415a89..000000000 --- a/gcm3/src/main/java/plugins/partitions/support/LabelerSensitivity.java +++ /dev/null @@ -1,78 +0,0 @@ -package plugins.partitions.support; - -import java.util.Optional; -import java.util.function.Function; - -import net.jcip.annotations.Immutable; -import nucleus.Event; -import plugins.people.support.PersonId; - -/** - * Partitions are maintained as events relating to people are resolved. To do so - * efficiently, we need to determine whether a particular event will trigger a - * change to either a person's membership in the partition or their position - * within the partition space. - * - * Partition labelers describe their sensitivity to events via this - * generics-base class. - * - * @author Shawn Hatch - * - * - */ -@Immutable -public final class LabelerSensitivity { - - /* - * Private wrapper class over the person function that properly casts events - */ - private class MetaFunction { - - private final Function> personFunction; - - public MetaFunction(Function> personFunction) { - this.personFunction = personFunction; - } - - @SuppressWarnings("unchecked") - public Optional requiresRefresh(Event event) { - return personFunction.apply((K) event); - } - } - - private final Class eventClass; - - private final MetaFunction metaFunction; - - /** - * Creates the labeler sensitivity from the event type and person function. - * The person function should return and empty optional if an event will not - * effect a person's label and an optional of the person id otherwise. - */ - public LabelerSensitivity(Class eventClass, Function> personFunction) { - super(); - this.eventClass = eventClass; - this.metaFunction = new MetaFunction<>(personFunction); - } - - /** - * Returns the event class of this labeler sensitivity. This will be same - * type and the generic type of the class. - */ - public Class getEventClass() { - return eventClass; - } - - /** - * Returns the person id from the event if the event will effect that - * person's label value and an empty optional otherwise. The partition resolver is not able to determine from - * an event the person id of an event that possibly changes the label cell - * of a person in a partition. The person id returned is the person that - * will trigger the refresh and is thus the person id associated with the - * event. - */ - public Optional getPersonId(Event event) { - return metaFunction.requiresRefresh(event); - } - -} \ No newline at end of file diff --git a/gcm3/src/main/java/plugins/partitions/support/Partition.java b/gcm3/src/main/java/plugins/partitions/support/Partition.java deleted file mode 100644 index 8b3f593fb..000000000 --- a/gcm3/src/main/java/plugins/partitions/support/Partition.java +++ /dev/null @@ -1,179 +0,0 @@ -package plugins.partitions.support; - -import java.util.LinkedHashSet; -import java.util.Optional; -import java.util.Set; - -import net.jcip.annotations.Immutable; -import net.jcip.annotations.NotThreadSafe; - -/** - * A {@linkplain Partition} is the general description of a partitioning of the - * people contained in the simulation. It is composed of a filter and various - * functions that map values associated with each person to labels. The space - * formed by the labels forms the formal partition. Partitions significantly - * reduce the runtime required to sample/select people from the simulation who - * meet some set of criteria. - * - * This class functions as a description of a population partition and is - * immutable. However, the implementation of the partition within the simulation - * is dynamic and the contents(people) of the partition remain consistent with - * the filter and label mapping functions as people change their properties, - * group associations, resources and regions. - * - * Partitions are built by the modeler via the supplied Builder class. - * Partitions may be added and removed from the simulation and are identified by - * a unique identifier. Only the Component that adds a partition may remove that - * partition. - * - * The filter: The role of the filter supplied to the partition is simply to - * determine which people will be included in the partition's cells. If no - * filter is supplied, the resulting partition will include all people. For - * example, suppose there is a Boolean person property IS_VACCINTATED. If the - * filter is [IS_VACCINATED EQUALS == FALSE] then only people who had not been - * vaccinated would be included in the cells of the partition. - * - * The labeling functions: The labeling functions serve to group people into the - * cells of the partition. For example, suppose their are numerous regions in - * the simulation with each representing a single census tract. The modeler may - * wish to group these regions by state and would thus provide a function that - * accepts a region and returns a state name as the label. Labels are not - * required to be of any particular type or even to be of the same type for any - * particular function. In this example, the modeler could create an enumeration - * composed of values for each of the states and territories(ALABAMA, ALASKA, - * ...) and would thus return the associated enumeration member as the label - * value. - * - * The inclusion of multiple labeling functions serves to refine the cells of - * the partition. For example, suppose that there is a person property for the - * (Integer) age of each person. The modeler may want to group people under age - * 30 as "YOUNG" and those older as "OLD". Combined with the previous region - * labeling function, the partition will have cells such as [UTAH,OLD], - * [OHIO,YOUNG], etc. - * - * Thus to retrieve or randomly sample from the simulation those people who are - * unvaccinated, live in Maryland, and are below the age of 30 the modeler - * queries the environment with the the id of the partition and the label values - * [MARYLAND,YOUNG]. - * - * When implementing a supplied labeling function, the modeler must take some - * care to only consider the inputs of the function and to guarantee the - * stability of the return value. For example, in the age labeling function - * above, the function will receive an Integer (such as 35) and must always - * return the same label(OLD) in every invocation of the function. Without this - * the partition will not function correctly. - * - * @author Shawn Hatch - * - */ -@Immutable -public final class Partition { - - /** - * Returns a new Builder instance - */ - public static Builder builder() { - return new Builder(); - } - - @NotThreadSafe - /** - * Standard builder class for partitions. All inputs are optional. - * - * @author Shawn Hatch - * - */ - public static class Builder { - - private Scaffold scaffold = new Scaffold(); - - private Builder() { - } - - /** - * Returns the {@linkplain Partition} formed from the inputs collected - * by this builder and resets the state of the builder to empty. - */ - public Partition build() { - try { - return new Partition(scaffold); - } finally { - scaffold = new Scaffold(); - } - } - - /** - * Adds a labeler. - */ - public Builder addLabeler(Labeler labeler) { - scaffold.labelers.add(labeler); - return this; - } - - /** - * Sets the filter for the {@linkplain Partition}. If no filter is - * provided, a default filter that accepts all people is used instead. - */ - public Builder setFilter(Filter filter) { - scaffold.filter = filter; - return this; - } - - /** - * Set the retention policy for derived partition cell keys for people - */ - public Builder setRetainPersonKeys(boolean retainPersonKeys) { - scaffold.retainPersonKeys = retainPersonKeys; - return this; - } - - } - - private Partition(Scaffold scaffold) { - this.labelers = scaffold.labelers; - this.filter = scaffold.filter; - this.retainPersonKeys = scaffold.retainPersonKeys; - } - - /** - * Returns true if and only if the {@linkplain Partition} contains no - * labeling functions. - */ - public boolean isDegenerate() { - return labelers.isEmpty(); - } - - /** - * Returns true if and only if partition cell keys should be retained by the - * partition for faster performance at the cost of higher memory usage. - */ - public boolean retainPersonKeys() { - return retainPersonKeys; - } - - private static class Scaffold { - private boolean retainPersonKeys = true; - - private Set labelers = new LinkedHashSet<>(); - - private Filter filter; - } - - private final Set labelers; - - private final Filter filter; - - private final boolean retainPersonKeys; - - /** - * Returns the filter contained in this {@linkplain Partition} - */ - public Optional getFilter() { - return Optional.ofNullable(filter); - } - - public Set getLabelers() { - return new LinkedHashSet<>(labelers); - } - -} \ No newline at end of file diff --git a/gcm3/src/main/java/plugins/partitions/support/PartitionError.java b/gcm3/src/main/java/plugins/partitions/support/PartitionError.java deleted file mode 100644 index 46ca062f8..000000000 --- a/gcm3/src/main/java/plugins/partitions/support/PartitionError.java +++ /dev/null @@ -1,45 +0,0 @@ -package plugins.partitions.support; - -import util.errors.ContractError; -import util.errors.ContractException; - -/** - * An enumeration supporting {@link ContractException} that acts as a general - * description of the exception. - * - * @author Shawn Hatch - * - */ -public enum PartitionError implements ContractError { - - DUPLICATE_PARTITION("Duplicate partition key"), - NON_DEGENERATE_PARTITION("Requires a degenerate partition"), - INCOMPATIBLE_LABEL_SET("The label set is incompatible with the selected population partition definition"), - MALFORMED_PARTITION_SAMPLE_WEIGHTING_FUNCTION("Data used to form an enumerated distribution for partition sampling was malformed"), - RESERVED_PARTITION_TRIGGER("An event class is being used to trigger refreshes as part of a partition that is reserved for the partition resolver"), - NON_COMPARABLE_ATTRIBUTE("The attribute definition is not compatible with innequality comparisons"), - NULL_EQUALITY_OPERATOR("Null equality operator"), - NULL_LABEL_SET("Null label set"), - NULL_PARTITION("Null partition"), - NULL_PARTITION_LABEL("Null partition label"), - NULL_PARTITION_LABEL_DIMENSION("Null partition label dimension"), - NULL_FILTER("Null filter"), - NULL_PARTITION_KEY("Null population partition key"), - NULL_POPULATION_PARTITION("Null population partition"), - NULL_PARTITION_DATA_MANAGER("Null partition data manager"), - NULL_PARTITION_SAMPLER("Null partition sampler"), - - UNKNOWN_POPULATION_PARTITION_KEY("No population partition found"), - NULL_PERSON_DATA_VIEW("Null person data view"); - - private final String description; - - private PartitionError(final String description) { - this.description = description; - } - - @Override - public String getDescription() { - return description; - } -} diff --git a/gcm3/src/main/java/plugins/partitions/support/PartitionSampler.java b/gcm3/src/main/java/plugins/partitions/support/PartitionSampler.java deleted file mode 100644 index 64e5309d3..000000000 --- a/gcm3/src/main/java/plugins/partitions/support/PartitionSampler.java +++ /dev/null @@ -1,144 +0,0 @@ -package plugins.partitions.support; - -import java.util.Optional; - -import plugins.people.support.PersonId; -import plugins.stochastics.support.RandomNumberGeneratorId; - -/** - * - * A {@link PartitionSampler} represents the details of a sample query for a - * {@link Partition}. All inputs to the {@link PartitionSampler} are optional. - * - * - * @author Shawn Hatch - * - */ -public final class PartitionSampler { - - private final Scaffold scaffold; - - private static class Scaffold { - - private PersonId excludedPersonId; - - private RandomNumberGeneratorId randomNumberGeneratorId; - - private LabelSet labelSet; - - private LabelSetWeightingFunction labelSetWeightingFunction; - } - - /** - * Returns a new Builder instance - */ - public static Builder builder() { - return new Builder(); - } - - /** - * Standard builder class for partition samplers. All inputs are optional. - * - * @author Shawn Hatch - * - */ - public static class Builder { - private Scaffold scaffold = new Scaffold(); - - private Builder() { - } - - /** - * Returns the {@linkplain PartitionSampler} formed from the inputs - * collected by this builder and resets the state of the builder to - * empty. - */ - public PartitionSampler build() { - try { - return new PartitionSampler(scaffold); - } finally { - scaffold = new Scaffold(); - } - } - - /** - * Sets the {@link PersonId} to be excluded as a sample result. - */ - public Builder setExcludedPerson(PersonId excludedPersonId) { - scaffold.excludedPersonId = excludedPersonId; - return this; - } - - /** - * Sets the {@link RandomNumberGeneratorId} to be used when sampling - * from a {@link Partition}. If no {@link RandomNumberGeneratorId} is - * provided, the default random number generator for the simulation is - * used. - */ - public Builder setRandomNumberGeneratorId(RandomNumberGeneratorId randomNumberGeneratorId) { - scaffold.randomNumberGeneratorId = randomNumberGeneratorId; - return this; - } - - /** - * Sets the {@link LabelSet} for this {@link PartitionSampler}. If no - * {@link LabelSet} is provided, all cells of the {@link Partition} - * participate in the sampling. - */ - public Builder setLabelSet(LabelSet labelSet) { - scaffold.labelSet = labelSet; - return this; - } - - /** - * Sets the {@link LabelSetWeightingFunction} for this - * {@link PartitionSampler}. If no {@link LabelSetWeightingFunction} is - * provided, all cells of the {@link Partition} are weighted uniformly. - */ - public Builder setLabelSetWeightingFunction(LabelSetWeightingFunction labelSetWeightingFunction) { - scaffold.labelSetWeightingFunction = labelSetWeightingFunction; - return this; - } - - } - - /** - * Returns the PersonId to be excluded as a result of sampling a - * {@link Partition} - */ - public Optional getExcludedPerson() { - return Optional.ofNullable(scaffold.excludedPersonId); - } - - /** - * Returns the {@link RandomNumberGeneratorId} to be used when generating - * the random sample. If no {@link RandomNumberGeneratorId} is provided, the - * default random number generator for the simulation is used. - */ - public Optional getRandomNumberGeneratorId() { - return Optional.ofNullable(scaffold.randomNumberGeneratorId); - } - - /** - * Returns the {@link LabelSet} for this {@link PartitionSampler}. If no - * {@link LabelSet} is provided, all cells of the {@link Partition} - * participate in the sampling. - */ - public Optional getLabelSet() { - return Optional.ofNullable(scaffold.labelSet); - } - - /** - * Returns the {@link LabelSetWeightingFunction} for this - * {@link PartitionSampler}. If no {@link LabelSetWeightingFunction} is - * provided, all cells of the {@link Partition} are weighted uniformly. - */ - public Optional getLabelSetWeightingFunction() { - return Optional.ofNullable(scaffold.labelSetWeightingFunction); - } - - private PartitionSampler(Scaffold scaffold) { - this.scaffold = scaffold; - } - -} diff --git a/gcm3/src/main/java/plugins/partitions/support/PopulationPartition.java b/gcm3/src/main/java/plugins/partitions/support/PopulationPartition.java deleted file mode 100644 index 425e26ce2..000000000 --- a/gcm3/src/main/java/plugins/partitions/support/PopulationPartition.java +++ /dev/null @@ -1,117 +0,0 @@ -package plugins.partitions.support; - -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import nucleus.Event; -import plugins.people.support.PersonId; - -/** - * - * A {@link PopulationPartition} is the interface for implementors of - * partitions, maintaining the people associated with the partition's cells by - * handling individual mutation events. - * - * - * - * @author Shawn Hatch - * - */ -public interface PopulationPartition { - - /** - * Handles the addition of a person to the simulation. - * - * Preconditions : Should only be used to add people who have just been - * created and thus cannot already be members. - * - * @throws RuntimeException - *
  • if the person id is null
  • - * - * - */ - public void attemptPersonAddition(PersonId personId); - - /** - * Handles the removal of a person from the simulation - * - * Precondition: Person must exist - * - */ - public void attemptPersonRemoval(PersonId personId); - - /** - * Handles the relevant data change to a person - * - * preconditions: the event must not be null - */ - public void handleEvent(Event event); - - /** - * Returns true if and only if the given {@link LabelSet} is compatible with - * this {@link PopulationPartition}. To be consistent, the {@link LabelSet} - * must not contain label values for label dimensions not contained in this - * partition. - * - * Precondition: label set may not be null - */ - public boolean validateLabelSetInfo(LabelSet labelSet); - - /** - * Returns the number of people contained in this - * {@link PopulationPartition} - */ - - public int getPeopleCount(); - - /** - * Returns the number of people contained in this - * {@link PopulationPartition} that are contained in cells that match the - * given {@link LabelSet} - */ - public int getPeopleCount(LabelSet labelSet); - - /** - * Returns the number of people contained in this - * {@link PopulationPartition} that are contained in cells that match the - * given {@link LabelSet}, mapped to each cell. - */ - public Map getPeopleCountMap(LabelSet labelSet); - - /** - * Returns true if and only if the person is contained in this - * {@link PopulationPartition} - */ - public boolean contains(PersonId personId); - - /** - * Returns true if and only if the person is contained in this - * {@link PopulationPartition} under the cells consistent with the given - * {@link LabelSet} - */ - public boolean contains(PersonId personId, LabelSet labelSet); - - /** - * Returns the people contained in this {@link PopulationPartition} that are - * contained in cells that match the given {@link LabelSet} - * - * Precondition : the label set must be non-null and contain only compatible - * dimensions with this population partition. - */ - public List getPeople(LabelSet labelSet); - - /** - * Returns the people contained in this {@link PopulationPartition} - */ - public List getPeople(); - - /** - * Returns a randomly chosen person identifier from the partition consistent - * with the partition sampler info. Note that the sampler must be consistent - * with the partition definition used to create this population partition. - * No precondition tests will be performed. - */ - public Optional samplePartition(final PartitionSampler partitionSampler); - -} diff --git a/gcm3/src/main/java/plugins/partitions/support/Tuplator.java b/gcm3/src/main/java/plugins/partitions/support/Tuplator.java deleted file mode 100644 index 5a22a3042..000000000 --- a/gcm3/src/main/java/plugins/partitions/support/Tuplator.java +++ /dev/null @@ -1,104 +0,0 @@ -package plugins.partitions.support; - -import java.util.ArrayList; -import java.util.List; - -/** - * Utility class for generating tuples from a fixed set of finite ranges of the - * form [0,n). Uses a standard builder pattern for construction. - * - * - * - * @author Shawn Hatch - * - */ -public final class Tuplator { - private final int[] moduli; - private final int[] dimensions; - private final int size; - - private Tuplator(List dimensionSizes) { - dimensions = new int[dimensionSizes.size()]; - moduli = new int[dimensionSizes.size()]; - - int count = 1; - for (int i = 0; i < dimensionSizes.size(); i++) { - int dimSize = dimensionSizes.get(i); - dimensions[i] = dimSize; - moduli[i] = count; - count *= dimSize; - } - size = count; - } - - /** - * Returns the number of dimensions in this {@link Tuplator}. - */ - public int dimensions() { - return dimensions.length; - } - - /** - * Returns the number of tuples that can be generated by this - * {@link Tuplator} - */ - public int size() { - return size; - } - - /** - * Fills the given tuple with the values that correspond to the given index. - * - * @throws IndexOutOfBoundsException - *
  • if index < 0 - *
  • if index >= size() - * @throws IllegalArgumentException - *
  • if the tuple is null - *
  • if the tuple's length is not equal to dimensions() - * - */ - public void fillTuple(int index, int[] tuple) { - if ((index < 0) || (index >= size)) { - throw new IndexOutOfBoundsException("index out of bounds"); - } - - if (tuple == null) { - throw new IllegalArgumentException("null array"); - } - - if (tuple.length != dimensions.length) { - throw new IllegalArgumentException("wrong number of dimensions"); - } - - for (int i = 0; i < dimensions.length; i++) { - tuple[i] = (index / moduli[i]) % dimensions[i]; - } - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - private List dimensionSizes = new ArrayList<>(); - - private Builder() { - } - - public Tuplator build() { - try { - return new Tuplator(dimensionSizes); - } finally { - dimensionSizes = new ArrayList<>(); - } - } - - public Builder addDimension(int dimensionSize) { - if (dimensionSize <= 0) { - throw new IllegalArgumentException("Non positive dimension size"); - } - dimensionSizes.add(dimensionSize); - return this; - } - } -} diff --git a/gcm3/src/main/java/plugins/partitions/support/containers/BasePeopleContainer.java b/gcm3/src/main/java/plugins/partitions/support/containers/BasePeopleContainer.java deleted file mode 100644 index 260e486ee..000000000 --- a/gcm3/src/main/java/plugins/partitions/support/containers/BasePeopleContainer.java +++ /dev/null @@ -1,134 +0,0 @@ -package plugins.partitions.support.containers; - -import java.util.List; - -import org.apache.commons.math3.random.RandomGenerator; - -import nucleus.SimulationContext; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.PersonId; - -/** - * Implementor of PeopleContainer that acts as a dynamic switching mechanism - * between the two lower-level PeopleContainer implementors - * - * @author Shawn Hatch - */ -public class BasePeopleContainer implements PeopleContainer { - /* - * Enumeration for the two ways that people are stored in a partition - */ - private static enum PeopleContainerMode { - INTSET, TREE_BIT_SET_SLOW; - } - - private static final int TREE_BIT_SET_SLOW_THRESHOLD = 28; - - private static final int INT_SET_THRESHOLD = 33; - - private PeopleContainerMode mode; - - private final PeopleDataManager peopleDataManager; - - private PeopleContainer internalPeopleContainer; - - public BasePeopleContainer(SimulationContext simulationContext) { - this.peopleDataManager = simulationContext.getDataManager(PeopleDataManager.class); - mode = PeopleContainerMode.INTSET; - internalPeopleContainer = new IntSetPeopleContainer(); - - } - - /* - * Switches the internal container between BooleanPeopleContainer and - * SetPeopleContainer as needed whenever the appropriate threshold has been - * crossed. If the size of the container is less than 0.5% of the total - * world population, then the SetPeopleContainer should be chosen. If the - * size of the container is greater than 1% of the total world population, - * then the BooleanPeopleContainer should be chosen. By setting two separate - * thresholds, we avoid modality thrash. - */ - private void determineMode(int size) { - - switch (mode) { - - case TREE_BIT_SET_SLOW: - if (size <= peopleDataManager.getProjectedPopulationCount() / INT_SET_THRESHOLD) { - mode = PeopleContainerMode.INTSET; - List people = internalPeopleContainer.getPeople(); - internalPeopleContainer = new IntSetPeopleContainer(); - for (PersonId personId : people) { - /* - * We use unsafe add as it faster and we know that the - * person id cannot already be contained - */ - internalPeopleContainer.unsafeAdd(personId); - } - } - break; - case INTSET: - if (size >= peopleDataManager.getProjectedPopulationCount() / TREE_BIT_SET_SLOW_THRESHOLD) { - mode = PeopleContainerMode.TREE_BIT_SET_SLOW; - List people = internalPeopleContainer.getPeople(); - internalPeopleContainer = new TreeBitSetPeopleContainer(peopleDataManager); - for (PersonId personId : people) { - // we use the safe add as it is faster - internalPeopleContainer.safeAdd(personId); - } - } - break; - default: - throw new RuntimeException("unhandled mode " + mode); - } - } - - @Override - public List getPeople() { - return internalPeopleContainer.getPeople(); - } - - @Override - public boolean safeAdd(PersonId personId) { - boolean result = internalPeopleContainer.safeAdd(personId); - if (result) { - determineMode(size()); - } - return result; - } - - @Override - public boolean unsafeAdd(PersonId personId) { - boolean result = internalPeopleContainer.unsafeAdd(personId); - if (result) { - determineMode(size()); - } - return result; - } - - @Override - public boolean remove(PersonId personId) { - boolean result = internalPeopleContainer.remove(personId); - determineMode(size()); - return result; - } - - @Override - public int size() { - return internalPeopleContainer.size(); - } - - @Override - public boolean contains(PersonId personId) { - return internalPeopleContainer.contains(personId); - } - - /* - * Returns a randomly selected person if this container has any people. - * Returns null otherwise. - */ - @Override - public PersonId getRandomPersonId(RandomGenerator randomGenerator) { - return internalPeopleContainer.getRandomPersonId(randomGenerator); - } - -} \ No newline at end of file diff --git a/gcm3/src/main/java/plugins/partitions/support/containers/PeopleContainer.java b/gcm3/src/main/java/plugins/partitions/support/containers/PeopleContainer.java deleted file mode 100644 index d7d3a42d6..000000000 --- a/gcm3/src/main/java/plugins/partitions/support/containers/PeopleContainer.java +++ /dev/null @@ -1,61 +0,0 @@ -package plugins.partitions.support.containers; - -import java.util.List; - -import org.apache.commons.math3.random.RandomGenerator; - -import plugins.people.support.PersonId; - -/** - * Interface for abstracting the details of how people ids are stored as either - * a Set or a Boolean container. - * - * @author Shawn Hatch - */ -public interface PeopleContainer { - - /** - * Returns a list of the people in the set with no duplicates - */ - public List getPeople(); - - /** - * Returns true if and only if the person was successfully added. - */ - public boolean safeAdd(PersonId personId); - - /** - * Returns true if and only if the person was successfully added. To use - * unsafe adding, the caller MUST guarantee that the person id being added - * does not already exist in this people container. Depending on the - * implementor, this can reduce the time for addition significantly. - */ - public boolean unsafeAdd(PersonId personId); - - /** - * Returns true if and only if the person was successfully removed. - * - * Precondition: The person cannot be null. - * - * - */ - public boolean remove(PersonId personId); - - /** - * Returns the number of people in this container - */ - public int size(); - - /** - * Returns true if and only if the person is contained. - */ - public boolean contains(PersonId personId); - - /** - * Returns a randomly selected person if this container has any people. - * Returns null otherwise. - * - * Precondition : random generator cannot be null - */ - public PersonId getRandomPersonId(RandomGenerator randomGenerator); -} \ No newline at end of file diff --git a/gcm3/src/main/java/plugins/partitions/testsupport/PartitionsActionSupport.java b/gcm3/src/main/java/plugins/partitions/testsupport/PartitionsActionSupport.java deleted file mode 100644 index 8a5f221b0..000000000 --- a/gcm3/src/main/java/plugins/partitions/testsupport/PartitionsActionSupport.java +++ /dev/null @@ -1,86 +0,0 @@ -package plugins.partitions.testsupport; - -import java.util.function.Consumer; - -import nucleus.ActorContext; -import nucleus.Plugin; -import nucleus.Simulation; -import nucleus.Simulation.Builder; -import nucleus.testsupport.testplugin.ScenarioPlanCompletionObserver; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestError; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import plugins.partitions.PartitionsPlugin; -import plugins.partitions.testsupport.attributes.AttributesPlugin; -import plugins.partitions.testsupport.attributes.AttributesPluginData; -import plugins.partitions.testsupport.attributes.support.TestAttributeId; -import plugins.people.PeoplePlugin; -import plugins.people.PeoplePluginData; -import plugins.people.support.PersonId; -import plugins.stochastics.StochasticsPlugin; -import plugins.stochastics.StochasticsPluginData; -import util.errors.ContractException; - -public class PartitionsActionSupport { - - public static void testConsumer(int initialPopulation, long seed, Consumer consumer) { - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, consumer)); - - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - testConsumers(initialPopulation, seed, testPlugin); - } - - public static void testConsumers(int initialPopulation, long seed, Plugin testPlugin) { - - final Builder builder = Simulation.builder(); - builder.addPlugin(testPlugin); - - // define some person attributes - final AttributesPluginData.Builder attributesBuilder = AttributesPluginData.builder(); - for (final TestAttributeId testAttributeId : TestAttributeId.values()) { - attributesBuilder.defineAttribute(testAttributeId, testAttributeId.getAttributeDefinition()); - } - AttributesPluginData attributesPluginData = attributesBuilder.build(); - Plugin attributesPlugin = AttributesPlugin.getAttributesPlugin(attributesPluginData); - builder.addPlugin(attributesPlugin); - - - - //add the people plugin - - final PeoplePluginData.Builder peopleBuilder = PeoplePluginData.builder(); - for (int i = 0; i < initialPopulation; i++) { - peopleBuilder.addPersonId(new PersonId(i)); - } - - PeoplePluginData peoplePluginData = peopleBuilder.build(); - Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(peoplePluginData); - builder.addPlugin(peoplePlugin); - - //add the stochastics plugin - Plugin stochasticsPlugin = StochasticsPlugin.getStochasticsPlugin(StochasticsPluginData.builder().setSeed(seed).build()); - builder.addPlugin(stochasticsPlugin); - - //add the partitions plugin - Plugin partitionsPlugin = PartitionsPlugin.getPartitionsPlugin(); - builder.addPlugin(partitionsPlugin); - - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - - // build and execute the engine - builder .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput)// - .build()// - .execute(); - - // show that all actions were executed - if (!scenarioPlanCompletionObserver.allPlansExecuted()) { - throw new ContractException(TestError.TEST_EXECUTION_FAILURE); - } - } - -} diff --git a/gcm3/src/main/java/plugins/partitions/testsupport/attributes/AttributesDataManager.java b/gcm3/src/main/java/plugins/partitions/testsupport/attributes/AttributesDataManager.java deleted file mode 100644 index e4e508738..000000000 --- a/gcm3/src/main/java/plugins/partitions/testsupport/attributes/AttributesDataManager.java +++ /dev/null @@ -1,246 +0,0 @@ -package plugins.partitions.testsupport.attributes; - -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -import nucleus.DataManager; -import nucleus.DataManagerContext; -import plugins.partitions.testsupport.attributes.events.AttributeUpdateEvent; -import plugins.partitions.testsupport.attributes.support.AttributeDefinition; -import plugins.partitions.testsupport.attributes.support.AttributeError; -import plugins.partitions.testsupport.attributes.support.AttributeId; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.events.PersonImminentRemovalEvent; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import util.errors.ContractException; - -/** - * Published data view that provides attribute information. - * - * @author Shawn Hatch - * - */ - -public final class AttributesDataManager extends DataManager { - - private DataManagerContext dataManagerContext; - - private final Map attributeDefinitions = new LinkedHashMap<>(); - - private final Map> attributeValues = new LinkedHashMap<>(); - - /** - * Returns the attribute definition associated with the given attribute id - * without validation. - * - * @throws ContractException - *
  • {@linkplain AttributeError#NULL_ATTRIBUTE_ID} if the - * attribute id is null
  • - *
  • {@linkplain AttributeError#UNKNOWN_ATTRIBUTE_ID} if the - * attribute id unknown
  • - */ - public AttributeDefinition getAttributeDefinition(final AttributeId attributeId) { - validateAttributeId(attributeId); - return attributeDefinitions.get(attributeId); - } - - private void validateAttributeId(AttributeId attributeId) { - if (attributeId == null) { - throw new ContractException(AttributeError.NULL_ATTRIBUTE_ID); - } - - if (!attributeExists(attributeId)) { - throw new ContractException(AttributeError.UNKNOWN_ATTRIBUTE_ID); - } - - } - - /** - * Returns the attribute ids - */ - @SuppressWarnings("unchecked") - public Set getAttributeIds() { - final Set result = new LinkedHashSet<>(attributeDefinitions.keySet().size()); - for (final AttributeId attributeId : attributeDefinitions.keySet()) { - result.add((T) attributeId); - } - return result; - } - - /** - * Returns the attribute value associated with the given attribute id - * - * - * @throws ContractException - *
  • {@linkplain AttributeError#NULL_ATTRIBUTE_ID} if the - * attribute id is null
  • - *
  • {@linkplain AttributeError#UNKNOWN_ATTRIBUTE_ID} if the - * attribute id unknown
  • - *
  • {@linkplain PersonError#NULL_PERSON_ID} if the person id - * is null
  • - *
  • {@linkplain PersonError#UNKNOWN_PERSON_ID} if the person - * id unknown
  • - */ - @SuppressWarnings("unchecked") - public T getAttributeValue(final PersonId personId, final AttributeId attributeId) { - validateAttributeId(attributeId); - validatePersonId(personId); - Object value = attributeValues.get(attributeId).get(personId); - if (value == null) { - value = attributeDefinitions.get(attributeId).getDefaultValue(); - } - return (T) value; - } - - private void validatePersonId(PersonId personId) { - if (personId == null) { - throw new ContractException(PersonError.NULL_PERSON_ID); - } - if (!peopleDataManager.personExists(personId)) { - throw new ContractException(PersonError.UNKNOWN_PERSON_ID); - } - } - - private PeopleDataManager peopleDataManager; - - /** - * Returns true if and only if the attribute is contained - */ - public boolean attributeExists(final AttributeId attributeId) { - return attributeDefinitions.containsKey(attributeId); - } - - private final AttributesPluginData attributesPluginData; - - /** - * Constructs this data manager from the given context - * - */ - public AttributesDataManager(AttributesPluginData attributesPluginData) { - if (attributesPluginData == null) { - throw new ContractException(AttributeError.NULL_ATTRIBUTE_INITIAL_DATA); - } - this.attributesPluginData = attributesPluginData; - } - - @Override - public void init(DataManagerContext dataManagerContext) { - super.init(dataManagerContext); - this.dataManagerContext = dataManagerContext; - peopleDataManager = dataManagerContext.getDataManager(PeopleDataManager.class); - - dataManagerContext.addEventLabeler(AttributeUpdateEvent.getEventLabeler()); - - for (AttributeId attributeId : attributesPluginData.getAttributeIds()) { - AttributeDefinition attributeDefinition = attributesPluginData.getAttributeDefinition(attributeId); - addAttribute(attributeId, attributeDefinition); - } - - dataManagerContext.subscribe(PersonImminentRemovalEvent.class, this::handlePersonImminentRemovalEvent); - - } - - private void handlePersonImminentRemovalEvent(final DataManagerContext dataManagerContext, final PersonImminentRemovalEvent personImminentRemovalEvent) { - validatePersonExists(dataManagerContext, personImminentRemovalEvent.getPersonId()); - dataManagerContext.addPlan((c) -> { - for (AttributeId attributeId : attributeValues.keySet()) { - attributeValues.get(attributeId).remove(personImminentRemovalEvent.getPersonId()); - } - }, dataManagerContext.getTime()); - } - - /* - * Adds an attribute definition - * - * @throws ContractException - * - *
  • {@linkplain AttributeError#NULL_ATTRIBUTE_ID} if the attribute id is - * null
  • {@linkplain AttributeError#NULL_ATTRIBUTE_DEFINITION} if - * the attribute definition is null
  • {@linkplain - * AttributeError#DUPLICATE_ATTRIBUTE_DEFINITION} if the attribute - * definition was previously added
  • - */ - private void addAttribute(AttributeId attributeId, AttributeDefinition attributeDefinition) { - if (attributeId == null) { - throw new ContractException(AttributeError.NULL_ATTRIBUTE_ID); - } - - if (attributeDefinition == null) { - throw new ContractException(AttributeError.NULL_ATTRIBUTE_DEFINITION); - } - - if (attributeDefinitions.containsKey(attributeId)) { - throw new ContractException(AttributeError.DUPLICATE_ATTRIBUTE_DEFINITION); - } - - attributeDefinitions.put(attributeId, attributeDefinition); - attributeValues.put(attributeId, new LinkedHashMap<>()); - } - - /** - * Updates the person's current attribute value. Generates a corresponding - * {@linkplain AttributeUpdateEvent} - * - * - * Throws {@link ContractException} - * - *
      - *
    • {@link PersonError#NULL_PERSON_ID} if the person id is null
    • - *
    • {@link PersonError#UNKNOWN_PERSON_ID} if the person id is - * unknown
    • - *
    • {@link AttributeError#NULL_ATTRIBUTE_ID} if the attribute id is - * null
    • - *
    • {@link AttributeError#UNKNOWN_ATTRIBUTE_ID} if the attribute id is - * unknown
    • - *
    • {@link AttributeError#NULL_ATTRIBUTE_VALUE} if the attribute value is - * null
    • - *
    • {@link AttributeError#INCOMPATIBLE_VALUE} if the attribute value is - * incompatible with the associated attribute definition
    • - *
    - */ - public void setAttributeValue(final PersonId personId, final AttributeId attributeId, final Object value) { - validatePersonExists(dataManagerContext, personId); - validateAttributeId(dataManagerContext, attributeId); - validateValueNotNull(dataManagerContext, value); - final AttributeDefinition attributeDefinition = getAttributeDefinition(attributeId); - validateValueCompatibility(dataManagerContext, attributeId, attributeDefinition, value); - Object previousValue = getAttributeValue(personId, attributeId); - attributeValues.get(attributeId).put(personId, value); - dataManagerContext.releaseEvent(new AttributeUpdateEvent(personId, attributeId, previousValue, value)); - } - - private void validatePersonExists(final DataManagerContext dataManagerContext, final PersonId personId) { - if (personId == null) { - throw new ContractException(PersonError.NULL_PERSON_ID); - } - if (!peopleDataManager.personExists(personId)) { - throw new ContractException(PersonError.UNKNOWN_PERSON_ID); - } - } - - private static void validateValueNotNull(final DataManagerContext dataManagerContext, final Object value) { - if (value == null) { - throw new ContractException(AttributeError.NULL_ATTRIBUTE_VALUE); - } - } - - private static void validateValueCompatibility(final DataManagerContext dataManagerContext, final AttributeId attributeId, final AttributeDefinition attributeDefinition, final Object value) { - if (!attributeDefinition.getType().isAssignableFrom(value.getClass())) { - throw new ContractException(AttributeError.INCOMPATIBLE_VALUE, - "Attribute value " + value + " is not of type " + attributeDefinition.getType().getName() + " and does not match definition of " + attributeId); - } - } - - private void validateAttributeId(DataManagerContext dataManagerContext, final AttributeId attributeId) { - if (attributeId == null) { - throw new ContractException(AttributeError.NULL_ATTRIBUTE_ID); - } - if (!attributeExists(attributeId)) { - throw new ContractException(AttributeError.UNKNOWN_ATTRIBUTE_ID, attributeId); - } - } - -} diff --git a/gcm3/src/main/java/plugins/partitions/testsupport/attributes/AttributesPlugin.java b/gcm3/src/main/java/plugins/partitions/testsupport/attributes/AttributesPlugin.java deleted file mode 100644 index 3a195d0c0..000000000 --- a/gcm3/src/main/java/plugins/partitions/testsupport/attributes/AttributesPlugin.java +++ /dev/null @@ -1,119 +0,0 @@ -package plugins.partitions.testsupport.attributes; - -import nucleus.Plugin; -import plugins.partitions.PartitionsPluginId; -import plugins.partitions.testsupport.attributes.support.AttributeError; -import plugins.people.PeoplePluginId; -import util.errors.ContractError; -import util.errors.ContractException; - -/** - * - *

    - * Summary A nucleus test support plugin for testing the partitions - * plugin. Introduces the concept of attributes that can be assigned to people. - *

    - * - *

    - * Events See each event class for details. - *

      - *
    • AttributeValueAssignmentEvent: Sets an attribute value for a - * person
    • - * - *
    • AttributeUpdateEvent: Notifies subscribed oberservers - * of a person attribute value change
    • - * - *
    - *

    - * - *

    - * Resolvers - *

      - *
    • AttributesEventResolver: Uses initializing data to create and - * publish data view. Handles all plugin-defined events. - *
    - *

    - * - *

    - * Data Views The attributes plugin supplies one data view. - *

      - *
    • Attributes Data View: Supplies attribute values, ids and - * definitions.
    • - *
    - *

    - * - *

    - * Reports The plugin defines no reports. - *

    - * - *

    - * Agents: This plugin defines no agent implementations. - *

    - * - *

    - * Initializing data: An immutable container of the attribute - * definitions. - * - * - * - *

    - * - *

    - * Support classes - *

      - *
    • AttributeError:
    • Enumeration implementing - * {@linkplain ContractError} for this plugin. - *
    • AttributeDefinition:
    • Class for defining the type and default - * value of an attribute - *
    • AttributeFilter:
    • Defines a filter used in partitions. - *
    • AttributeId:
    • Marker interface that defines attribute id - * values. - *
    • AttributeLabeler:
    • Provides dimension labeling for person - * attributes in partitions. - *
    - *

    - * - *

    - * Required Plugins - *

      - *
    • PartitionsPlugin: Uses support classes in the partitions plugin to - * define filtering and labeling behaviors. - *
    • PeoplePlugin: Used throughout the plugin since the plugin is - * focused on person attributes - *
    - *

    - * - * @author Shawn Hatch - * - */ -public final class AttributesPlugin { - - private AttributesPlugin() {} - - /** - * Constructs this plugin - * - * @throws ContractException - *
  • {@linkplain AttributeError#NULL_ATTRIBUTE_INITIAL_DATA} - * if the initial data is null
  • - * - */ - public static Plugin getAttributesPlugin(AttributesPluginData attributesPluginData) { - if (attributesPluginData == null) { - throw new ContractException(AttributeError.NULL_ATTRIBUTE_INITIAL_DATA); - } - - return Plugin .builder()// - .setPluginId(AttributesPluginId.PLUGIN_ID)// - .addPluginDependency(PeoplePluginId.PLUGIN_ID)// - .addPluginDependency(PartitionsPluginId.PLUGIN_ID)// - .setInitializer((c) -> { - AttributesPluginData pluginData = c.getPluginData(AttributesPluginData.class); - c.addDataManager(new AttributesDataManager(pluginData)); - })// - .addPluginData(attributesPluginData)// - .build(); - - } - -} diff --git a/gcm3/src/main/java/plugins/partitions/testsupport/attributes/AttributesPluginData.java b/gcm3/src/main/java/plugins/partitions/testsupport/attributes/AttributesPluginData.java deleted file mode 100644 index 8d4c32621..000000000 --- a/gcm3/src/main/java/plugins/partitions/testsupport/attributes/AttributesPluginData.java +++ /dev/null @@ -1,129 +0,0 @@ -package plugins.partitions.testsupport.attributes; - -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -import net.jcip.annotations.Immutable; -import nucleus.PluginData; -import nucleus.PluginDataBuilder; -import plugins.partitions.testsupport.attributes.support.AttributeDefinition; -import plugins.partitions.testsupport.attributes.support.AttributeError; -import plugins.partitions.testsupport.attributes.support.AttributeId; -import util.errors.ContractException; - -@Immutable -public class AttributesPluginData implements PluginData { - - private static class Data { - private Map attributeDefinitions = new LinkedHashMap<>(); - - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - private Data data = new Data(); - - private Builder() { - - } - - /** - * Returns the {@linkplain AttributesPluginData} from the collected data - */ - public AttributesPluginData build() { - try { - return new AttributesPluginData(data); - } finally { - data = new Data(); - } - } - - /** - * Adds an attribute definition. - * - * @throws ContractException - *
  • {@linkplain AttributeError#NULL_ATTRIBUTE_ID} if the - * attribute id is null
  • - *
  • {@linkplain AttributeError#NULL_ATTRIBUTE_DEFINITION} - * if the attribute definition is null
  • - *
  • {@linkplain AttributeError#DUPLICATE_ATTRIBUTE_DEFINITION} - * if the attribute id was previously added
  • - */ - public Builder defineAttribute(final AttributeId attributeId, final AttributeDefinition attributeDefinition) { - validateAttributeIdNotNull(attributeId); - validateAttributeDefinitionNotNull(attributeDefinition); - validateAttributeIsNotDefined(data, attributeId); - data.attributeDefinitions.put(attributeId, attributeDefinition); - return this; - } - - } - - private static void validateAttributeIsNotDefined(final Data data, final AttributeId attributeId) { - AttributeDefinition attributeDefinition = data.attributeDefinitions.get(attributeId); - if (attributeDefinition != null) { - throw new ContractException(AttributeError.DUPLICATE_ATTRIBUTE_DEFINITION, attributeId); - } - } - - private static void validateAttributeDefinitionNotNull(AttributeDefinition attributeDefinition) { - if (attributeDefinition == null) { - throw new ContractException(AttributeError.NULL_ATTRIBUTE_DEFINITION); - } - } - - private static void validateAttributeIdNotNull(AttributeId attributeId) { - if (attributeId == null) { - throw new ContractException(AttributeError.NULL_ATTRIBUTE_ID); - } - } - - private final Data data; - - private AttributesPluginData(Data data) { - this.data = data; - } - - /** - * Returns the attribute definition for the given attribute id - * - * @throws ContractException - *
  • {@linkplain AttributeError#NULL_ATTRIBUTE_ID} if the - * attribute id is null
  • - *
  • {@linkplain AttributeError#UNKNOWN_ATTRIBUTE_ID} if the - * attribute id is unknown
  • - */ - public AttributeDefinition getAttributeDefinition(final AttributeId attributeId) { - validateAttributeIdNotNull(attributeId); - AttributeDefinition attributeDefinition = data.attributeDefinitions.get(attributeId); - if (attributeDefinition == null) { - throw new ContractException(AttributeError.UNKNOWN_ATTRIBUTE_ID, attributeId); - } - return attributeDefinition; - } - - /** - * Returns the attribute ids - * - */ - @SuppressWarnings("unchecked") - public Set getAttributeIds() { - Set result = new LinkedHashSet<>(); - for (AttributeId attributeId : data.attributeDefinitions.keySet()) { - result.add((T) attributeId); - } - return result; - } - - @Override - public PluginDataBuilder getCloneBuilder() { - // TODO Auto-generated method stub - return null; - } - -} diff --git a/gcm3/src/main/java/plugins/partitions/testsupport/attributes/AttributesPluginId.java b/gcm3/src/main/java/plugins/partitions/testsupport/attributes/AttributesPluginId.java deleted file mode 100644 index 87428495a..000000000 --- a/gcm3/src/main/java/plugins/partitions/testsupport/attributes/AttributesPluginId.java +++ /dev/null @@ -1,14 +0,0 @@ -package plugins.partitions.testsupport.attributes; - -import nucleus.PluginId; -/** - * Static plugin id implementation for the Attributes Plugin - * - * @author Shawn Hatch - * - */ - -public final class AttributesPluginId implements PluginId { - public final static PluginId PLUGIN_ID = new AttributesPluginId(); - private AttributesPluginId() {}; -} diff --git a/gcm3/src/main/java/plugins/partitions/testsupport/attributes/events/AttributeUpdateEvent.java b/gcm3/src/main/java/plugins/partitions/testsupport/attributes/events/AttributeUpdateEvent.java deleted file mode 100644 index c0b4bf7c1..000000000 --- a/gcm3/src/main/java/plugins/partitions/testsupport/attributes/events/AttributeUpdateEvent.java +++ /dev/null @@ -1,103 +0,0 @@ -package plugins.partitions.testsupport.attributes.events; - -import net.jcip.annotations.Immutable; -import nucleus.Event; -import nucleus.EventLabel; -import nucleus.EventLabeler; -import nucleus.EventLabelerId; -import nucleus.SimulationContext; -import plugins.partitions.testsupport.attributes.AttributesDataManager; -import plugins.partitions.testsupport.attributes.support.AttributeError; -import plugins.partitions.testsupport.attributes.support.AttributeId; -import plugins.people.support.PersonId; -import util.errors.ContractException; - -@Immutable -public class AttributeUpdateEvent implements Event { - private final PersonId personId; - private final AttributeId attributeId; - private final Object previousValue; - private final Object currentValue; - - public AttributeUpdateEvent(final PersonId personId, final AttributeId attributeId, final Object previousValue, final Object currentValue) { - super(); - this.personId = personId; - this.attributeId = attributeId; - this.previousValue = previousValue; - this.currentValue = currentValue; - } - - public Object getCurrentValue() { - return currentValue; - } - - public AttributeId getAttributeId() { - return attributeId; - } - - public PersonId getPersonId() { - return personId; - } - - public Object getPreviousValue() { - return previousValue; - } - - @Override - public Object getPrimaryKeyValue() { - return attributeId; - } - - private static enum LabelerId implements EventLabelerId { - ATTRIBUTE - } - - /** - * Returns an event label used to subscribe to {@link AttributeUpdateEvent} - * events. Matches on attribute id. - * - * - * @throws ContractException - * - *
  • {@linkplain AttributeError#NULL_ATTRIBUTE_ID} if the - * attribute id is null
  • - *
  • {@linkplain AttributeError#UNKNOWN_ATTRIBUTE_ID} if the - * attribute id is not known
  • - */ - public static EventLabel getEventLabel(final SimulationContext simulationContext, final AttributeId attributeId) { - validateAttributed(simulationContext, attributeId); - return EventLabel.builder(AttributeUpdateEvent.class)// - .setEventLabelerId(LabelerId.ATTRIBUTE) - .addKey(attributeId) - .build(); - } - - private static EventLabel _getEventLabel(final AttributeId attributeId) { - - return EventLabel.builder(AttributeUpdateEvent.class)// - .setEventLabelerId(LabelerId.ATTRIBUTE) - .addKey(attributeId) - .build(); - } - - /** - * Returns an event labeler for {@link AttributeUpdateEvent} events that - * uses only the attribute id. Automatically added at initialization. - */ - public static EventLabeler getEventLabeler() { - return EventLabeler .builder(AttributeUpdateEvent.class)// - .setEventLabelerId(LabelerId.ATTRIBUTE)// - .setLabelFunction((context, event) -> _getEventLabel(event.getAttributeId()))// - .build(); - } - - private static void validateAttributed(final SimulationContext simulationContext, final AttributeId attributeId) { - if (attributeId == null) { - throw new ContractException(AttributeError.NULL_ATTRIBUTE_ID); - } - final AttributesDataManager attributesDataManager = simulationContext.getDataManager(AttributesDataManager.class); - if (!attributesDataManager.attributeExists(attributeId)) { - throw new ContractException(AttributeError.UNKNOWN_ATTRIBUTE_ID); - } - } -} diff --git a/gcm3/src/main/java/plugins/partitions/testsupport/attributes/support/AttributeDefinition.java b/gcm3/src/main/java/plugins/partitions/testsupport/attributes/support/AttributeDefinition.java deleted file mode 100644 index 1ce58e992..000000000 --- a/gcm3/src/main/java/plugins/partitions/testsupport/attributes/support/AttributeDefinition.java +++ /dev/null @@ -1,179 +0,0 @@ -package plugins.partitions.testsupport.attributes.support; - -import net.jcip.annotations.ThreadSafe; -import util.errors.ContractException; - - -/** - * A thread-safe, immutable class that defines an attribute, but does not - * indicate the role that attribute is playing or the identifier of the - * attribute. - * - * @author Shawn Hatch - * - */ -@ThreadSafe -public final class AttributeDefinition { - - public static Builder builder() { - return new Builder(); - } - - private static class Scaffold { - - private Class type = null; - - private Object defaultValue = null; - - } - - /** - * Builder class for {@linkplain AttributeDefinition} - * - * @author Shawn Hatch - * - */ - public static class Builder { - - private Scaffold scaffold = new Scaffold(); - - private Builder() { - } - - /** - * Builds the attribute definition - * - * @throws ContractException - *
  • {@linkplain AttributeError#NULL_ATTRIBUTE_TYPE} if - * the class type of the definition is not assigned or - * null
  • - * - *
  • {@linkplain AttributeError#NULL_DEFAULT_VALUE}if the - * default value null
  • - * - *
  • {@linkplain AttributeError#INCOMPATIBLE_DEFAULT_VALUE}if - * the class type is not a super-type of the default - * value
  • - * - */ - public AttributeDefinition build() { - try { - return new AttributeDefinition(scaffold); - } finally { - scaffold = new Scaffold(); - } - } - - /** - * Sets the class type. Value must be set by client. - */ - public Builder setType(final Class type) { - scaffold.type = type; - return this; - } - - /** - * Sets the default value for the attribute. - */ - public Builder setDefaultValue(Object defaultValue) { - scaffold.defaultValue = defaultValue; - return this; - } - - } - - private final Class type; - - private final Object defaultValue; - - private AttributeDefinition(Scaffold scaffold) { - - if (scaffold.type == null) { - throw new ContractException(AttributeError.NULL_ATTRIBUTE_TYPE); - } - - if (scaffold.defaultValue == null) { - throw new ContractException(AttributeError.NULL_DEFAULT_VALUE); - } - - if (!scaffold.type.isInstance(scaffold.defaultValue)) { - throw new ContractException(AttributeError.INCOMPATIBLE_DEFAULT_VALUE); - } - - this.type = scaffold.type; - - this.defaultValue = scaffold.defaultValue; - - } - - /** - * Returns the default value. - */ - public Object getDefaultValue() { - return defaultValue; - } - - /** - * Returns that class type of this definition. - * - */ - public Class getType() { - return type; - } - - /** - * Boilerplate implementation that uses all fields. - */ - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((defaultValue == null) ? 0 : defaultValue.hashCode()); - result = prime * result + ((type == null) ? 0 : type.hashCode()); - return result; - } - - /** - * Attribute definitions are equal if they have the same type and default - * value. - */ - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - AttributeDefinition other = (AttributeDefinition) obj; - - if (defaultValue == null) { - if (other.defaultValue != null) - return false; - } else if (!defaultValue.equals(other.defaultValue)) - return false; - - if (type == null) { - return other.type == null; - } else - return type.equals(other.type); - } - - /** - * Standard string representation in the form: - * - * AttributeDefinition - * [type=someType,mapOption=mapOption,defaultValue=someValue] - */ - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("AttributeDefinition [type="); - builder.append(type); - builder.append(", defaultValue="); - builder.append(defaultValue); - builder.append("]"); - return builder.toString(); - } - -} \ No newline at end of file diff --git a/gcm3/src/main/java/plugins/partitions/testsupport/attributes/support/AttributeError.java b/gcm3/src/main/java/plugins/partitions/testsupport/attributes/support/AttributeError.java deleted file mode 100644 index e3d678674..000000000 --- a/gcm3/src/main/java/plugins/partitions/testsupport/attributes/support/AttributeError.java +++ /dev/null @@ -1,38 +0,0 @@ -package plugins.partitions.testsupport.attributes.support; - -import util.errors.ContractError; -import util.errors.ContractException; - -/** - * An enumeration supporting {@link ContractException} that acts as a general - * description of the exception. - * - * @author Shawn Hatch - * - */ -public enum AttributeError implements ContractError { - - - NULL_ATTRIBUTE_INITIAL_DATA("Null attribute initial data"),// - NULL_ATTRIBUTE_DATA_MANAGER("Null attributes data manager"),// - NULL_ATTRIBUTE_ID("Null attribute id"),// - NULL_ATTRIBUTE_DEFINITION("Null attribute definition"),// - NULL_ATTRIBUTE_VALUE("Null attribute value"),// - NULL_ATTRIBUTE_TYPE("Null attribute type"),// - INCOMPATIBLE_DEFAULT_VALUE("Incompatible default value"), - NULL_DEFAULT_VALUE("Null default value"), - UNKNOWN_ATTRIBUTE_ID("Unknown attribute id"),// - INCOMPATIBLE_VALUE("Atrribute value is incompatible with the attribute definition"),// - DUPLICATE_ATTRIBUTE_DEFINITION("Duplicate attribute definition");// - - private final String description; - - private AttributeError(final String description) { - this.description = description; - } - - @Override - public String getDescription() { - return description; - } -} diff --git a/gcm3/src/main/java/plugins/partitions/testsupport/attributes/support/AttributeFilter.java b/gcm3/src/main/java/plugins/partitions/testsupport/attributes/support/AttributeFilter.java deleted file mode 100644 index 6623bad9a..000000000 --- a/gcm3/src/main/java/plugins/partitions/testsupport/attributes/support/AttributeFilter.java +++ /dev/null @@ -1,169 +0,0 @@ -package plugins.partitions.testsupport.attributes.support; - -import java.util.LinkedHashSet; -import java.util.Optional; -import java.util.Set; - -import nucleus.NucleusError; -import nucleus.SimulationContext; -import plugins.partitions.support.Equality; -import plugins.partitions.support.Filter; -import plugins.partitions.support.FilterSensitivity; -import plugins.partitions.support.PartitionError; -import plugins.partitions.testsupport.attributes.AttributesDataManager; -import plugins.partitions.testsupport.attributes.events.AttributeUpdateEvent; -import plugins.people.support.PersonId; -import util.errors.ContractException; - -public final class AttributeFilter extends Filter { - - private final AttributeId attributeId; - private final Object value; - private final Equality equality; - private AttributesDataManager attributesDataManager; - - private void validateAttributeId(SimulationContext simulationContext, final AttributeId attributeId) { - if (attributeId == null) { - throw new ContractException(AttributeError.NULL_ATTRIBUTE_ID); - } - if (attributesDataManager == null) { - attributesDataManager = simulationContext.getDataManager(AttributesDataManager.class); - } - - if (!attributesDataManager.attributeExists(attributeId)) { - throw new ContractException(AttributeError.UNKNOWN_ATTRIBUTE_ID, attributeId); - } - } - - private void validateEquality(SimulationContext simulationContext, final Equality equality) { - if (equality == null) { - throw new ContractException(PartitionError.NULL_EQUALITY_OPERATOR); - } - } - - private void validateValueNotNull(SimulationContext simulationContext, final Object value) { - if (value == null) { - throw new ContractException(AttributeError.NULL_ATTRIBUTE_VALUE); - } - } - - private void validateValueCompatibility(SimulationContext simulationContext, final AttributeId attributeId, final AttributeDefinition attributeDefinition, final Object value) { - if (!attributeDefinition.getType().isAssignableFrom(value.getClass())) { - throw new ContractException(AttributeError.INCOMPATIBLE_VALUE, - "Attribute value " + value + " is not of type " + attributeDefinition.getType().getName() + " and does not match definition of " + attributeId); - } - } - - private void validateEqualityCompatibility(SimulationContext simulationContext, final AttributeId attributeId, final AttributeDefinition attributeDefinition, final Equality equality) { - - if (equality == Equality.EQUAL) { - return; - } - if (equality == Equality.NOT_EQUAL) { - return; - } - - if (!Comparable.class.isAssignableFrom(attributeDefinition.getType())) { - throw new ContractException(PartitionError.NON_COMPARABLE_ATTRIBUTE, "Values for " + attributeId + " are not comparable via " + equality); - } - } - - public AttributeFilter(final AttributeId attributeId, final Equality equality, final Object value) { - this.attributeId = attributeId; - this.value = value; - this.equality = equality; - } - - /** - * Validates this attribute filter - * - * @throws ContractException - *
  • {@linkplain AttributeError#NULL_ATTRIBUTE_ID}
  • if the - * filter's attribute id is null - * - *
  • {@linkplain AttributeError#UNKNOWN_ATTRIBUTE_ID}
  • if - * the filter's attribute id is unknown - * - *
  • {@linkplain PartitionError.NULL_EQUALITY_OPERATOR}
  • if - * the filter's equality operator is null - * - *
  • {@linkplain AttributeError.NULL_ATTRIBUTE_VALUE}
  • if - * the filter's value is null - * - *
  • {@linkplain AttributeError.INCOMPATIBLE_VALUE}
  • if the - * filter's value is incompatible with the attribute definition - * associated with the filter's attribute id. - * - *
  • {@linkplain PartitionError.NON_COMPARABLE_ATTRIBUTE}
  • if - * the filter's value is not a COMPARABLE when the filter's - * equality operator is not EQUALS or NOT_EQUALS. - * - */ - @Override - public void validate(SimulationContext simulationContext) { - validateAttributeId(simulationContext, attributeId); - validateEquality(simulationContext, equality); - validateValueNotNull(simulationContext, value); - if (attributesDataManager == null) { - attributesDataManager = simulationContext.getDataManager(AttributesDataManager.class); - } - final AttributeDefinition attributeDefinition = attributesDataManager.getAttributeDefinition(attributeId); - validateValueCompatibility(simulationContext, attributeId, attributeDefinition, value); - validateEqualityCompatibility(simulationContext, attributeId, attributeDefinition, equality); - } - - @Override - public boolean evaluate(SimulationContext simulationContext, PersonId personId) { - - if(simulationContext == null) { - throw new ContractException(NucleusError.NULL_SIMULATION_CONTEXT); - } - - if (attributesDataManager == null) { - attributesDataManager = simulationContext.getDataManager(AttributesDataManager.class); - } - - // we do not assume that the returned attribute value is - // comparable unless we are forced to. - final Object attValue = attributesDataManager.getAttributeValue(personId, attributeId); - - return evaluate(attValue); - - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - private boolean evaluate(Object propVal) { - if (equality.equals(Equality.EQUAL)) { - return propVal.equals(value); - } else if (equality.equals(Equality.NOT_EQUAL)) { - return !propVal.equals(value); - } else { - Comparable comparableAttributeValue = (Comparable) propVal; - int evaluation = comparableAttributeValue.compareTo(value); - return equality.isCompatibleComparisonValue(evaluation); - } - } - - private Optional requiresRefresh(SimulationContext simulationContext, AttributeUpdateEvent event) { - if (event.getAttributeId().equals(attributeId)) { - if (evaluate(event.getPreviousValue()) != evaluate(event.getCurrentValue())) { - return Optional.of(event.getPersonId()); - } - } - return Optional.empty(); - } - - /** - * Returns a single filter sensitivity for AttributeUpdateEvent - * events. This sensitivity will require refreshes for events with the same - * attribute id and where the event where the event has different previous - * and current values. - */ - @Override - public Set> getFilterSensitivities() { - Set> result = new LinkedHashSet<>(); - result.add(new FilterSensitivity(AttributeUpdateEvent.class, this::requiresRefresh)); - return result; - } - -} diff --git a/gcm3/src/main/java/plugins/partitions/testsupport/attributes/support/AttributeId.java b/gcm3/src/main/java/plugins/partitions/testsupport/attributes/support/AttributeId.java deleted file mode 100644 index 14e81cf92..000000000 --- a/gcm3/src/main/java/plugins/partitions/testsupport/attributes/support/AttributeId.java +++ /dev/null @@ -1,14 +0,0 @@ -package plugins.partitions.testsupport.attributes.support; - -import net.jcip.annotations.ThreadSafe; - -/** - * Marker interface for attribute identifiers - * - * @author Shawn Hatch - * - */ -@ThreadSafe -public interface AttributeId { - -} diff --git a/gcm3/src/main/java/plugins/partitions/testsupport/attributes/support/AttributeLabeler.java b/gcm3/src/main/java/plugins/partitions/testsupport/attributes/support/AttributeLabeler.java deleted file mode 100644 index a9875cec7..000000000 --- a/gcm3/src/main/java/plugins/partitions/testsupport/attributes/support/AttributeLabeler.java +++ /dev/null @@ -1,89 +0,0 @@ -package plugins.partitions.testsupport.attributes.support; - -import java.util.LinkedHashSet; -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; - -import nucleus.Event; -import nucleus.SimulationContext; -import plugins.partitions.support.Labeler; -import plugins.partitions.support.LabelerSensitivity; -import plugins.partitions.testsupport.attributes.AttributesDataManager; -import plugins.partitions.testsupport.attributes.events.AttributeUpdateEvent; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; - -/** - * A labeler for attributes. The dimension of the labeler is the given - * {@linkplain AttributeId}, the event that stimulates a label update is - * {@linkplain AttributeUpdateEvent} and the labeling function is - * composed from the given Function. - * - * @author Shawn Hatch - * - */ -public final class AttributeLabeler implements Labeler { - - private final AttributeId attributeId; - private final Function attributeValueLabelingFunction; - private AttributesDataManager attributesDataManager; - - public AttributeLabeler(AttributeId attributeId, Function attributeValueLabelingFunction) { - this.attributeId = attributeId; - this.attributeValueLabelingFunction = attributeValueLabelingFunction; - } - - private Optional getPersonId(AttributeUpdateEvent attributeUpdateEvent) { - PersonId result = null; - if (attributeUpdateEvent.getAttributeId().equals(attributeId)) { - result = attributeUpdateEvent.getPersonId(); - } - return Optional.ofNullable(result); - } - - /** - * Returns one LabelerSensitivity of AttributeUpdateEvent - */ - @Override - public Set> getLabelerSensitivities() { - Set> result = new LinkedHashSet<>(); - result.add(new LabelerSensitivity(AttributeUpdateEvent.class, this::getPersonId)); - return result; - } - - /** - * Returns the label for the person - * - * precondition: the context should not be null - * - * @throwsContractException - *
  • {@linkplain PersonError#NULL_PERSON_ID} if - * the person id is null - *
  • {@linkplain PersonError#UNKNOWN_PERSON_ID} - * if the person id is unknown - */ - @Override - public Object getLabel(SimulationContext simulationContext, PersonId personId) { - if (attributesDataManager == null) { - attributesDataManager = simulationContext.getDataManager(AttributesDataManager.class); - } - Object value = attributesDataManager.getAttributeValue(personId, attributeId); - return attributeValueLabelingFunction.apply(value); - } - - /** - * Returns the attribute id as the dimension - */ - @Override - public Object getDimension() { - return attributeId; - } - - @Override - public Object getPastLabel(SimulationContext simulationContext, Event event) { - AttributeUpdateEvent attributeUpdateEvent = (AttributeUpdateEvent)event; - return attributeValueLabelingFunction.apply(attributeUpdateEvent.getPreviousValue()); - } - -} diff --git a/gcm3/src/main/java/plugins/partitions/testsupport/attributes/support/TestAttributeId.java b/gcm3/src/main/java/plugins/partitions/testsupport/attributes/support/TestAttributeId.java deleted file mode 100644 index 3f59a127a..000000000 --- a/gcm3/src/main/java/plugins/partitions/testsupport/attributes/support/TestAttributeId.java +++ /dev/null @@ -1,30 +0,0 @@ -package plugins.partitions.testsupport.attributes.support; - -/** - * A test support enumeration of attribute id value with associated attribute - * definitions. - */ -public enum TestAttributeId implements AttributeId { - - INT_0(AttributeDefinition.builder().setType(Integer.class).setDefaultValue(0).build()), - INT_1(AttributeDefinition.builder().setType(Integer.class).setDefaultValue(1).build()), - DOUBLE_0(AttributeDefinition.builder().setType(Double.class).setDefaultValue(0.0).build()), - DOUBLE_1(AttributeDefinition.builder().setType(Double.class).setDefaultValue(1.0).build()), - BOOLEAN_0(AttributeDefinition.builder().setType(Boolean.class).setDefaultValue(false).build()), - BOOLEAN_1(AttributeDefinition.builder().setType(Boolean.class).setDefaultValue(true).build()); - - private final AttributeDefinition attributeDefinition; - - public AttributeDefinition getAttributeDefinition() { - return attributeDefinition; - } - - private TestAttributeId(AttributeDefinition attributeDefinition) { - this.attributeDefinition = attributeDefinition; - } - - public static AttributeId getUnknownAttributeId() { - return new AttributeId() {}; - } - -} diff --git a/gcm3/src/main/java/plugins/people/PeoplePlugin.java b/gcm3/src/main/java/plugins/people/PeoplePlugin.java deleted file mode 100644 index 32986364e..000000000 --- a/gcm3/src/main/java/plugins/people/PeoplePlugin.java +++ /dev/null @@ -1,58 +0,0 @@ -package plugins.people; - -import nucleus.Plugin; -import plugins.people.datamanagers.PeopleDataManager; - - -/** - * A nucleus plugin for representing people, dealing only with their existence. - * - * @author Shawn Hatch - * - */ - -public final class PeoplePlugin { - - private PeoplePlugin() { - - } - - /** - * Returns the people plugin. - * - *

    - * Uses PeoplePluginId.PLUGIN_ID as its id - *

    - * - *

    - * Depends on plugins: none - * - *

    - * Provides data mangers: - *

      - *
    • {@linkplain PeopleDataManager}
    • - *
    - *

    - * - *

    - * Provides actors: - *

      - *
    • {@linkplain PeopleLoader} for loading people from the - * PeoplePluginData
    • - *
    - *

    - * - */ - public static Plugin getPeoplePlugin(PeoplePluginData peoplePluginData) { - - return Plugin .builder()// - .addPluginData(peoplePluginData)// - .setPluginId(PeoplePluginId.PLUGIN_ID)// - .setInitializer((c) -> { - PeoplePluginData pluginData = c.getPluginData(PeoplePluginData.class); - c.addDataManager(new PeopleDataManager(pluginData)); - })// - .build(); - } - -} diff --git a/gcm3/src/main/java/plugins/people/PeoplePluginData.java b/gcm3/src/main/java/plugins/people/PeoplePluginData.java deleted file mode 100644 index 10eaecfc0..000000000 --- a/gcm3/src/main/java/plugins/people/PeoplePluginData.java +++ /dev/null @@ -1,112 +0,0 @@ -package plugins.people; - -import java.util.LinkedHashSet; -import java.util.Set; - -import net.jcip.annotations.Immutable; -import nucleus.PluginData; -import nucleus.PluginDataBuilder; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import util.errors.ContractException; - -/** - * An immutable container of the initial state of people containing person ids. - * All other person initialization data is provided by other plugins. - * - * @author Shawn Hatch - * - */ -@Immutable -public final class PeoplePluginData implements PluginData { - private static class Data { - private Set personIds = new LinkedHashSet<>(); - - - public Data() { - } - - public Data(Data data) { - this.personIds.addAll(data.personIds); - } - } - - private final Data data; - - /** - * Returns a new builder instance for this class - */ - public static Builder builder() { - return new Builder(new Data()); - } - - /** - * Builder class for PeoplePluginData - */ - public static class Builder implements PluginDataBuilder { - private Data data; - - private Builder(Data data) { - this.data = data; - - } - - /** - * Returns the PeopleInitialData resulting from the person ids collected - * by this builder. - */ - public PeoplePluginData build() { - try { - return new PeoplePluginData(data); - } finally { - data = new Data(); - } - } - - /** - * Adds a person. - * - * @throws ContractException - *
  • {@linkplain PersonError#NULL_PERSON_ID} if the person - * id is null
  • - *
  • {@linkplain PersonError#DUPLICATE_PERSON_ID} if the - * person id is already contained
  • - * - */ - public Builder addPersonId(PersonId personId) { - validatePersonIdNotNull(personId); - validatePersonDoesNotExist(data, personId); - data.personIds.add(personId); - return this; - } - } - - - private PeoplePluginData(Data data) { - this.data = data; - } - - /** - * Returns the set of person ids stored in this container - */ - public Set getPersonIds() { - return new LinkedHashSet<>(data.personIds); - } - - @Override - public PluginDataBuilder getCloneBuilder() { - return new Builder(new Data(data)); - } - - private static void validatePersonDoesNotExist(final Data data, final PersonId personId) { - if (data.personIds.contains(personId)) { - throw new ContractException(PersonError.DUPLICATE_PERSON_ID, personId); - } - } - - private static void validatePersonIdNotNull(PersonId personId) { - if (personId == null) { - throw new ContractException(PersonError.NULL_PERSON_ID); - } - } -} diff --git a/gcm3/src/main/java/plugins/people/PeoplePluginId.java b/gcm3/src/main/java/plugins/people/PeoplePluginId.java deleted file mode 100644 index 5d76317b6..000000000 --- a/gcm3/src/main/java/plugins/people/PeoplePluginId.java +++ /dev/null @@ -1,14 +0,0 @@ -package plugins.people; - -import nucleus.PluginId; -/** - * Static plugin id implementation for the GlobalsPlugin - * - * @author Shawn Hatch - * - */ - -public final class PeoplePluginId implements PluginId { - public final static PluginId PLUGIN_ID = new PeoplePluginId(); - private PeoplePluginId() {}; -} diff --git a/gcm3/src/main/java/plugins/people/datamanagers/PeopleDataManager.java b/gcm3/src/main/java/plugins/people/datamanagers/PeopleDataManager.java deleted file mode 100644 index 7b2b9c6b5..000000000 --- a/gcm3/src/main/java/plugins/people/datamanagers/PeopleDataManager.java +++ /dev/null @@ -1,291 +0,0 @@ -package plugins.people.datamanagers; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import nucleus.DataManager; -import nucleus.DataManagerContext; -import nucleus.NucleusError; -import plugins.people.PeoplePluginData; -import plugins.people.events.BulkPersonAdditionEvent; -import plugins.people.events.PersonAdditionEvent; -import plugins.people.events.PersonImminentRemovalEvent; -import plugins.people.support.BulkPersonConstructionData; -import plugins.people.support.PersonConstructionData; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import util.errors.ContractException; - -/** - * Mutable data manager for people. - * - * @author Shawn Hatch - * - */ -public final class PeopleDataManager extends DataManager { - - private final PeoplePluginData peoplePluginData; - - public PeopleDataManager(PeoplePluginData peoplePluginData) { - this.peoplePluginData = peoplePluginData; - - } - - private static class PopulationRecord { - private int projectedPopulationCount; - private int populationCount; - private double assignmentTime; - } - - /* - * We keep the person records in a list rather than a map so that we can - * retrieve a person record by index (personId). - */ - private List personIds = new ArrayList<>(); - - private DataManagerContext dataManagerContext; - - private final PopulationRecord globalPopulationRecord = new PopulationRecord(); - - /** - * Returns a new person id that has been added to the simulation. The - * returned PersonId is unique and will wrap the int value returned by - * getPersonIdLimit() just prior to invoking this method. - * - * @throws ContractException - *
  • {@linkplain PersonError#NULL_PERSON_CONSTRUCTION_DATA} if - * the person construction data is null
  • - * - */ - public Optional addBulkPeople(final BulkPersonConstructionData bulkPersonConstructionData) { - validateBulkPersonConstructionData(bulkPersonConstructionData); - - final List personConstructionDatas = bulkPersonConstructionData.getPersonConstructionDatas(); - PersonId result = null; - final int count = personConstructionDatas.size(); - for (int i = 0; i < count; i++) { - final PersonId personId = addPersonId(); - if (result == null) { - result = personId; - } - } - - if (result != null) { - final BulkPersonAdditionEvent bulkPersonAdditionEvent = new BulkPersonAdditionEvent(result, bulkPersonConstructionData); - dataManagerContext.releaseEvent(bulkPersonAdditionEvent); - } - - return Optional.ofNullable(result); - } - - /** - * Returns a new person id that has been added to the simulation. The - * returned PersonId is unique and will wrap the int value returned by - * getPersonIdLimit() just prior to invoking this method. - * - * @throws ContractException - *
  • {@linkplain PersonError#NULL_PERSON_CONSTRUCTION_DATA} if - * the person construction data is null
  • - * - */ - public PersonId addPerson(final PersonConstructionData personConstructionData) { - validatePersonConstructionDataNotNull(personConstructionData); - - final PersonId personId = addPersonId(); - - final PersonAdditionEvent personAdditionEvent = new PersonAdditionEvent(personId, personConstructionData); - dataManagerContext.releaseEvent(personAdditionEvent); - - return personId; - } - - private PersonId addPersonId() { - final PersonId personId = new PersonId(personIds.size()); - - personIds.add(personId); - if (globalPopulationRecord.projectedPopulationCount < personIds.size()) { - globalPopulationRecord.projectedPopulationCount = personIds.size(); - } - globalPopulationRecord.populationCount++; - globalPopulationRecord.assignmentTime = dataManagerContext.getTime(); - return personId; - } - - /** - * Expands the capacity of data structures to hold people by the given - * count. Used to more efficiently prepare for bulk population additions. - * - * @throws ContractException - *
  • {@linkplain PersonError#NEGATIVE_GROWTH_PROJECTION} if - * the count is negative
  • - */ - public void expandCapacity(final int count) { - if (count < 0) { - throw new ContractException(PersonError.NEGATIVE_GROWTH_PROJECTION); - } - if (count > 0) { - globalPopulationRecord.projectedPopulationCount += count; - final List newPersonIds = new ArrayList<>(personIds.size() + count); - newPersonIds.addAll(personIds); - personIds = newPersonIds; - } - } - - /** - * Returns the PersonId that corresponds to the given int value. - */ - public Optional getBoxedPersonId(final int personId) { - PersonId result = null; - if ((personId >= 0) && (personIds.size() > personId)) { - result = personIds.get(personId); - } - return Optional.ofNullable(result); - } - - /** - * Returns a list of PersonId for each person that exists. - */ - public List getPeople() { - - int count = 0; - for (final PersonId boxedPersonId : personIds) { - if (boxedPersonId != null) { - count++; - } - } - final List result = new ArrayList<>(count); - - for (final PersonId boxedPersonId : personIds) { - if (boxedPersonId != null) { - result.add(boxedPersonId); - } - } - - return result; - } - - /** - * Returns the lowest int id that has yet to be associated with a person. - * Lower values will correspond to existing, removed or unused id values. - */ - public int getPersonIdLimit() { - return personIds.size(); - } - - /** - * Returns the number of existing people - */ - public int getPopulationCount() { - return globalPopulationRecord.populationCount; - } - - /** - * Returns the time of the last added or removed person. Returns zero if no - * people have been added. - */ - public double getPopulationTime() { - return globalPopulationRecord.assignmentTime; - } - - /** - * Returns the projected population count that reflects the effect of the - * current population count and any capacity expansions. - */ - public int getProjectedPopulationCount() { - return globalPopulationRecord.projectedPopulationCount; - } - - /** - * Initializes the data manager. This method should only be invoked by the - * simulation. All data manager descendant classes that override this method - * must invoke the super. - * - * @throws ContractException - *
  • {@linkplain NucleusError#DATA_MANAGER_DUPLICATE_INITIALIZATION} - * if init() is invoked more than once
  • - * - */ - @Override - public void init(final DataManagerContext dataManagerContext) { - super.init(dataManagerContext); - this.dataManagerContext = dataManagerContext; - - for (PersonId personId : peoplePluginData.getPersonIds()) { - personIds.add(personId); - } - globalPopulationRecord.projectedPopulationCount = personIds.size(); - globalPopulationRecord.populationCount = personIds.size(); - globalPopulationRecord.assignmentTime = dataManagerContext.getTime(); - } - - /** - * Returns true if and only if the person exists in the simulation. - */ - public boolean personExists(final PersonId personId) { - if ((personId != null) && (personId.getValue() >= 0) && (personId.getValue() < personIds.size())) { - return personIds.get(personId.getValue()) != null; - } - return false; - } - - /** - * Returns true if and only if there is an existing person associated with - * the given index. The PersonId is a wrapper around an int index. - */ - public boolean personIndexExists(final int personId) { - boolean result = false; - if ((personId >= 0) && (personId < personIds.size())) { - result = personIds.get(personId) != null; - } - return result; - - } - - /** - * Removes the person from the simulation. - * - * @throws ContractException - *
  • {@linkplain PersonError#NULL_PERSON_ID} if the person id - * is null - *
  • - *
  • {@linkplain PersonError#UNKNOWN_PERSON_ID} if the person - * does not exist - *
  • - * - * - */ - public void removePerson(final PersonId personId) { - validatePersonExists(personId); - - dataManagerContext.addPlan((context) -> { - globalPopulationRecord.populationCount--; - globalPopulationRecord.assignmentTime = dataManagerContext.getTime(); - personIds.set(personId.getValue(), null); - }, dataManagerContext.getTime()); - - dataManagerContext.releaseEvent(new PersonImminentRemovalEvent(personId)); - - } - - private void validateBulkPersonConstructionData(final BulkPersonConstructionData bulkPersonConstructionData) { - if (bulkPersonConstructionData == null) { - throw new ContractException(PersonError.NULL_BULK_PERSON_CONSTRUCTION_DATA); - } - } - - private void validatePersonConstructionDataNotNull(final PersonConstructionData personConstructionData) { - if (personConstructionData == null) { - throw new ContractException(PersonError.NULL_PERSON_CONSTRUCTION_DATA); - } - } - - private void validatePersonExists(final PersonId personId) { - if (personId == null) { - throw new ContractException(PersonError.NULL_PERSON_ID); - } - if (!personExists(personId)) { - throw new ContractException(PersonError.UNKNOWN_PERSON_ID); - } - } -} diff --git a/gcm3/src/main/java/plugins/people/events/BulkPersonAdditionEvent.java b/gcm3/src/main/java/plugins/people/events/BulkPersonAdditionEvent.java deleted file mode 100644 index fc3f7dad7..000000000 --- a/gcm3/src/main/java/plugins/people/events/BulkPersonAdditionEvent.java +++ /dev/null @@ -1,56 +0,0 @@ -package plugins.people.events; - -import net.jcip.annotations.Immutable; -import nucleus.Event; -import plugins.people.support.BulkPersonConstructionData; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import util.errors.ContractException; - -/** - * An event for observing the construction multiple people from a - * {@linkplain BulkPersonCreationEvent} event. - * - * @author Shawn Hatch - * - */ -@Immutable -public final class BulkPersonAdditionEvent implements Event { - private final PersonId personId; - private final BulkPersonConstructionData bulkPersonConstructionData; - - /** - * Constructs the event from the given person and bulk person construction - * data. The person id will correspond to the first person created from the - * BulkPersonConstructionData. - * - */ - public BulkPersonAdditionEvent(final PersonId personId, BulkPersonConstructionData bulkPersonConstructionData) { - if (personId == null) { - throw new ContractException(PersonError.NULL_PERSON_ID); - } - if (bulkPersonConstructionData == null) { - throw new ContractException(PersonError.NULL_BULK_PERSON_CONSTRUCTION_DATA); - } - - this.personId = personId; - this.bulkPersonConstructionData = bulkPersonConstructionData; - } - - /** - * Returns the person id for the first person that was created from the - * BulkPersonConstructionData. People are constructed contiguously in the - * order contained in the BulkPersonConstructionData. - */ - public PersonId getPersonId() { - return personId; - } - - /** - * Returns the BulkPersonConstructionData used to create this event. - */ - public BulkPersonConstructionData getBulkPersonConstructionData() { - return bulkPersonConstructionData; - } - -} diff --git a/gcm3/src/main/java/plugins/people/events/PersonAdditionEvent.java b/gcm3/src/main/java/plugins/people/events/PersonAdditionEvent.java deleted file mode 100644 index b48b9c08e..000000000 --- a/gcm3/src/main/java/plugins/people/events/PersonAdditionEvent.java +++ /dev/null @@ -1,52 +0,0 @@ -package plugins.people.events; - -import net.jcip.annotations.Immutable; -import nucleus.Event; -import plugins.people.support.PersonConstructionData; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import util.errors.ContractException; - -@Immutable -public final class PersonAdditionEvent implements Event { - private final PersonId personId; - private final PersonConstructionData personConstructionData; - - /** - * Constructs the event from the given person id and person construction - * data - * - * @throws ContractException - *
  • {@linkplain PersonError#NULL_PERSON_ID} if the person id - * is null
  • - *
  • {@linkplain PersonError#NULL_PERSON_CONSTRUCTION_DATA} if - * the person construction data is null
  • - */ - public PersonAdditionEvent(final PersonId personId, PersonConstructionData personConstructionData) { - - if (personId == null) { - throw new ContractException(PersonError.NULL_PERSON_ID); - } - if (personConstructionData == null) { - throw new ContractException(PersonError.NULL_PERSON_CONSTRUCTION_DATA); - } - this.personId = personId; - this.personConstructionData = personConstructionData; - } - - /** - * Returns the person id used to create this event - */ - public PersonId getPersonId() { - return personId; - } - - /** - * Returns the person construction data used to create this event - */ - public PersonConstructionData getPersonConstructionData() { - return personConstructionData; - } - - -} diff --git a/gcm3/src/main/java/plugins/people/events/PersonImminentRemovalEvent.java b/gcm3/src/main/java/plugins/people/events/PersonImminentRemovalEvent.java deleted file mode 100644 index bdc7db1a3..000000000 --- a/gcm3/src/main/java/plugins/people/events/PersonImminentRemovalEvent.java +++ /dev/null @@ -1,57 +0,0 @@ -package plugins.people.events; - -import net.jcip.annotations.Immutable; -import nucleus.Event; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import util.errors.ContractException; - -/** - * Indicates that the given person will be removed from the simulation - * imminently, but all references to the person will still function at the time - * this event is received. No further events or plans should be generated that - * reference the person. - * - * @author Shawn Hatch - * - */ -@Immutable -public final class PersonImminentRemovalEvent implements Event { - private final PersonId personId; - - /** - * Constructs the event from the give person id - * - * @throws ContractException - *
  • {@linkplain PersonError#NULL_PERSON_ID}
  • - */ - public PersonImminentRemovalEvent(PersonId personId) { - if (personId == null) { - throw new ContractException(PersonError.NULL_PERSON_ID); - } - this.personId = personId; - - } - - /** - * Returns the person id used to create this event - */ - public PersonId getPersonId() { - return personId; - } - - /** - * Returns this event as a string in the form: - * - * PersonImminentRemovalEvent [personId="+i+"] - */ - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("PersonImminentRemovalEvent [personId="); - builder.append(personId); - builder.append("]"); - return builder.toString(); - } - -} diff --git a/gcm3/src/main/java/plugins/people/support/BulkPersonConstructionData.java b/gcm3/src/main/java/plugins/people/support/BulkPersonConstructionData.java deleted file mode 100644 index d3d041dc2..000000000 --- a/gcm3/src/main/java/plugins/people/support/BulkPersonConstructionData.java +++ /dev/null @@ -1,133 +0,0 @@ -package plugins.people.support; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -import net.jcip.annotations.Immutable; -import util.errors.ContractException; - - -/** - * A collection of {@link PersonConstructionData} that represents multiple person - * additions combined with a collection of unspecified data types that can be - * used as auxiliary data about those people. - * - */ -@Immutable -public final class BulkPersonConstructionData { - - private static class Data { - private List personConstructionDatas = new ArrayList<>(); - private List values = new ArrayList<>(); - } - - private final Data data; - - private BulkPersonConstructionData(Data data) { - this.data = data; - } - - public static Builder builder() { - return new Builder(); - } - - /** - * Builder class for {@link BulkPersonConstructionData} - */ - public static class Builder { - private Data data = new Data(); - - private Builder() { - - } - - /** - * Returns a {@link BulkPersonConstructionData} formed from the collected - * data. Clears the state of the builder. - */ - public BulkPersonConstructionData build() { - try { - return new BulkPersonConstructionData(data); - } finally { - data = new Data(); - } - } - - /** - * Adds the construction data for a single person - * - * @throws ContractException - *
  • {@linkplain PersonError#NULL_BULK_PERSON_CONSTRUCTION_DATA} - * if the person construction data is null
  • - * - */ - public Builder add(PersonConstructionData personConstructionData) { - if (personConstructionData == null) { - throw new ContractException(PersonError.NULL_PERSON_CONSTRUCTION_DATA); - } - data.personConstructionDatas.add(personConstructionData); - return this; - } - - /** - * Adds data that relates to the entire group of people being - * constructed. - * - * @throws ContractException - *
  • {@linkplain PersonError#NULL_AUXILIARY_DATA} if the - * auxiliary data is null
  • - */ - public Builder addAuxiliaryData(Object auxiliaryData) { - if (auxiliaryData == null) { - throw new ContractException(PersonError.NULL_AUXILIARY_DATA); - } - data.values.add(auxiliaryData); - return this; - } - - } - - /** - * Returns the PersonConstructionData in the order of their addition to the - * builder - */ - public List getPersonConstructionDatas() { - return Collections.unmodifiableList(data.personConstructionDatas); - } - - /** - * Returns the first auxiliary object that is an instance of the given - * class. Should be used only getValues() is expected to have at most one - * member. - */ - @SuppressWarnings("unchecked") - public Optional getValue(Class c) { - T result = null; - for (Object value : data.values) { - if (c.isAssignableFrom(value.getClass())) { - result = (T) value; - break; - } - } - return Optional.ofNullable(result); - - } - - /** - * Returns the auxiliary objects that are instances of the given class in - * the order of their addition to the builder. - */ - @SuppressWarnings("unchecked") - public List getValues(Class c) { - List result = new ArrayList<>(); - for (Object value : data.values) { - if (c.isAssignableFrom(value.getClass())) { - result.add((T) value); - } - } - return result; - - } -} diff --git a/gcm3/src/main/java/plugins/people/support/PersonConstructionData.java b/gcm3/src/main/java/plugins/people/support/PersonConstructionData.java deleted file mode 100644 index 059c9f0b4..000000000 --- a/gcm3/src/main/java/plugins/people/support/PersonConstructionData.java +++ /dev/null @@ -1,104 +0,0 @@ -package plugins.people.support; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import net.jcip.annotations.Immutable; -import util.errors.ContractException; - -/** - * Container for values used to in the construction of an agent. Values are - * retrievable by class type and are ordered. - * - */ -@Immutable -public final class PersonConstructionData { - - private final List values; - - private PersonConstructionData(List auxiliaryData) { - this.values = auxiliaryData; - } - - public static Builder builder() { - return new Builder(); - } - - /** - * Builder class for {@link PersonConstructionData} - * - * @author Shawn Hatch - * - */ - public static class Builder { - private List values = new ArrayList<>(); - - private Builder() { - - } - - /** - * Returns the {@link PersonConstructionData} formed from the inputs to - * this builder. - */ - public PersonConstructionData build() { - try { - return new PersonConstructionData(values); - } finally { - values = new ArrayList<>(); - } - } - - /** - * Adds a value to the builder. - * - * @throws ContractException - *
  • {@linkplain PersonError#NULL_AUXILIARY_DATA} if the - * value is null
  • - */ - public Builder add(Object value) { - if (value == null) { - throw new ContractException(PersonError.NULL_AUXILIARY_DATA); - } - values.add(value); - return this; - } - - } - - /** - * Returns the first auxiliary object that is an instance of the given - * class. Should be used only getValues() is expected to have at most one - * member. - */ - @SuppressWarnings("unchecked") - public Optional getValue(Class c) { - T result = null; - for (Object value : values) { - if (c.isAssignableFrom(value.getClass())) { - result = (T) value; - break; - } - } - return Optional.ofNullable(result); - - } - - /** - * Returns the auxiliary objects that are instances of the given class in - * the order of their addition to the builder. - */ - @SuppressWarnings("unchecked") - public List getValues(Class c) { - List result = new ArrayList<>(); - for (Object value : values) { - if (c.isAssignableFrom(value.getClass())) { - result.add((T) value); - } - } - return result; - - } - -} diff --git a/gcm3/src/main/java/plugins/people/support/PersonError.java b/gcm3/src/main/java/plugins/people/support/PersonError.java deleted file mode 100644 index 3dd8c6d05..000000000 --- a/gcm3/src/main/java/plugins/people/support/PersonError.java +++ /dev/null @@ -1,41 +0,0 @@ -package plugins.people.support; - -import util.errors.ContractError; -import util.errors.ContractException; - -/** - * An enumeration supporting {@link ContractException} that acts as a general - * description of the exception. - * - * @author Shawn Hatch - * - */ -public enum PersonError implements ContractError { - // person - NULL_PERSON_DATA_MANAGER("Null person data manager"), - NULL_AUXILIARY_DATA("Null auxiliary data"), - NULL_BULK_PERSON_CONSTRUCTION_DATA("Null bulk person construction data"), - NULL_PERSON_CONSTRUCTION_DATA("Null person construction data"), - NULL_PERSON_ID("Null person id"), - UNKNOWN_PERSON_ID("Unknown person id"), - DUPLICATE_PERSON_ID("Duplicate person addition"), - NON_ONE_TO_ONE_MAPPING("Mapping of initial data person ids to simulation person is not one-to-one"), - - NULL_SUGGESTED_POPULATION_SIZE("Scenario identifier is null"), - NEGATIVE_SUGGGESTED_POPULATION("Suggested population size is negative"), - NEGATIVE_GROWTH_PROJECTION("Growth projection count is negative"), - DUPLICATE_SUGGESTED_POPULATION_SIZE_ASSIGNMENT("Duplicate assignment of suggested population size"); - - - - private final String description; - - private PersonError(final String description) { - this.description = description; - } - - @Override - public String getDescription() { - return description; - } -} diff --git a/gcm3/src/main/java/plugins/people/support/PersonId.java b/gcm3/src/main/java/plugins/people/support/PersonId.java deleted file mode 100644 index 50a23537f..000000000 --- a/gcm3/src/main/java/plugins/people/support/PersonId.java +++ /dev/null @@ -1,53 +0,0 @@ -package plugins.people.support; - -import net.jcip.annotations.Immutable; - -/** - * Identifier for all people - * - * @author Shawn Hatch - * - */ -@Immutable -public final class PersonId implements Comparable{ - - private final int id; - - public PersonId(int id) { - this.id = id; - } - - public int getValue() { - return id; - } - - @Override - public int compareTo(PersonId personId) { - return Integer.compare(id,personId.id); - } - - @Override - public int hashCode() { - return id; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof PersonId)) { - return false; - } - PersonId other = (PersonId) obj; - if (id != other.id) { - return false; - } - return true; - } - - @Override - public String toString() { - return Integer.toString(id); - } -} diff --git a/gcm3/src/main/java/plugins/people/testsupport/PeopleActionSupport.java b/gcm3/src/main/java/plugins/people/testsupport/PeopleActionSupport.java deleted file mode 100644 index 40dc4187f..000000000 --- a/gcm3/src/main/java/plugins/people/testsupport/PeopleActionSupport.java +++ /dev/null @@ -1,66 +0,0 @@ -package plugins.people.testsupport; - -import java.util.function.Consumer; - -import nucleus.ActorContext; -import nucleus.Plugin; -import nucleus.Simulation; -import nucleus.testsupport.testplugin.ScenarioPlanCompletionObserver; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestError; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import plugins.people.PeoplePlugin; -import plugins.people.PeoplePluginData; -import util.errors.ContractException; - -/** - * A static test support class for the globals plugin. Provides convenience - * methods for integrating a test plugin into a people plugin simulation test - * harness. - * - * - * @author Shawn Hatch - * - */ - -public class PeopleActionSupport { - - /** - * Creates the test plugin containing a test actor initialized by the given - * consumer. Executes the simulation via the - * {@linkplain PeopleActionSupport#testConsumers(Plugin)} method - * - */ - - public static void testConsumer(int initialPopulationSize, Consumer consumer) { - - TestPluginData testPluginData = TestPluginData .builder()// - .addTestActorPlan("actor", new TestActorPlan(0, consumer))// - .build(); - - Plugin plugin = TestPlugin.getTestPlugin(testPluginData); - testConsumers(initialPopulationSize,plugin); - } - - /** - * Executes a simulation composed of the given test plugin. - */ - public static void testConsumers(int initialPopulationSize,Plugin testPlugin) { - - Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(PeoplePluginData.builder().build()); - - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - Simulation .builder()// - .addPlugin(peoplePlugin)// - .addPlugin(testPlugin)// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput)// - .build()// - .execute();// - - // show that all actions were executed - if (!scenarioPlanCompletionObserver.allPlansExecuted()) { - throw new ContractException(TestError.TEST_EXECUTION_FAILURE); - } - } -} diff --git a/gcm3/src/main/java/plugins/personproperties/PersonPropertiesPlugin.java b/gcm3/src/main/java/plugins/personproperties/PersonPropertiesPlugin.java deleted file mode 100644 index 1f0073ce2..000000000 --- a/gcm3/src/main/java/plugins/personproperties/PersonPropertiesPlugin.java +++ /dev/null @@ -1,35 +0,0 @@ -package plugins.personproperties; - -import nucleus.Plugin; -import plugins.people.PeoplePluginId; -import plugins.personproperties.datamanagers.PersonPropertiesDataManager; -import plugins.regions.RegionsPluginId; - -/** - * A plugin providing a person property management to the simulation. - * - * @author Shawn Hatch - * - */ - -public final class PersonPropertiesPlugin { - - private PersonPropertiesPlugin() { - - } - - public static Plugin getPersonPropertyPlugin(PersonPropertiesPluginData personPropertiesPluginData) { - - return Plugin .builder()// - .setPluginId(PersonPropertiesPluginId.PLUGIN_ID)// - .addPluginData(personPropertiesPluginData)// - .addPluginDependency(PeoplePluginId.PLUGIN_ID)// - .addPluginDependency(RegionsPluginId.PLUGIN_ID)// - .setInitializer((c) -> { - PersonPropertiesPluginData pluginData = c.getPluginData(PersonPropertiesPluginData.class); - c.addDataManager(new PersonPropertiesDataManager(pluginData)); - }).build(); - - } - -} diff --git a/gcm3/src/main/java/plugins/personproperties/PersonPropertiesPluginData.java b/gcm3/src/main/java/plugins/personproperties/PersonPropertiesPluginData.java deleted file mode 100644 index bb7dc90dd..000000000 --- a/gcm3/src/main/java/plugins/personproperties/PersonPropertiesPluginData.java +++ /dev/null @@ -1,290 +0,0 @@ -package plugins.personproperties; - -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -import net.jcip.annotations.Immutable; -import nucleus.PluginData; -import nucleus.PluginDataBuilder; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import plugins.personproperties.support.PersonPropertyError; -import plugins.personproperties.support.PersonPropertyId; -import plugins.util.properties.PropertyDefinition; -import plugins.util.properties.PropertyError; -import util.errors.ContractException; - -/** - * An immutable container of the initial state of person properties. Contains: - *
    - *
      - *
    • person property ids
    • - *
    • person property definitions
    • - *
    - * - * @author Shawn Hatch - * - */ -@Immutable -public class PersonPropertiesPluginData implements PluginData { - - private static class Data { - private Map personPropertyDefinitions = new LinkedHashMap<>(); - - private Map> personPropertyValues = new LinkedHashMap<>(); - - private Data() { - } - - private Data(Data data) { - personPropertyDefinitions.putAll(data.personPropertyDefinitions); - for(PersonId personId : data.personPropertyValues.keySet()) { - Map map = data.personPropertyValues.get(personId); - Map newMap = new LinkedHashMap<>(map); - personPropertyValues.put(personId, newMap); - } - } - } - - /** - * Returns a new builder instance - */ - public static Builder builder() { - return new Builder(new Data()); - } - - /** - * Builder class for PersonPropertyInitialData - * - * @author Shawn Hatch - * - */ - public static class Builder implements PluginDataBuilder { - private Data data; - - private Builder(Data data) { - this.data = data; - } - - /** - * Builds the {@linkplain PersonPropertiesPluginData} from the collected - * data. - * - */ - public PersonPropertiesPluginData build() { - try { - validateData(data); - return new PersonPropertiesPluginData(data); - } finally { - data = new Data(); - } - } - - /** - * Defines a person property definition - * - * @throws ContractException - *
  • {@linkplain PersonPropertyError#NULL_PERSON_PROPERTY_ID} - * if the person property id is null
  • - *
  • {@linkplain PersonPropertyError#NULL_PERSON_PROPERTY_DEFINITION} - * if the person property definition value is null
  • - *
  • {@linkplain PersonPropertyError#DUPLICATE_PERSON_PROPERTY_DEFINITION} - * if the person property definition is already added
  • - *
  • {@linkplain PersonPropertyError#PROPERTY_DEFINITION_REQUIRES_DEFAULT} - * if the person property definition does not have a default - * value
  • - * - * - */ - public Builder definePersonProperty(final PersonPropertyId personPropertyId, final PropertyDefinition propertyDefinition) { - validatePersonPropertyIdNotNull(personPropertyId); - validatePersonPropertyDefinitionNotNull(propertyDefinition); - validatePersonPropertyIsNotDefined(data, personPropertyId); - validatePersonPropertyDefinitionHasDefault(propertyDefinition); - data.personPropertyDefinitions.put(personPropertyId, propertyDefinition); - return this; - } - /** - * Sets the person's property value - * - * @throws ContractException - *
  • {@linkplain PersonError#NULL_PERSON_ID} if the person - * id is null
  • - *
  • {@linkplain PersonPropertyError#NULL_PERSON_PROPERTY_ID} - * if the person property id is null
  • - *
  • {@linkplain PersonPropertyError#NULL_PERSON_PROPERTY_VALUE} - * if the person property value is null
  • - *
  • {@linkplain PersonPropertyError#DUPLICATE_PERSON_PROPERTY_VALUE_ASSIGNMENT} - * if the person property value is already assigned
  • - */ - public Builder setPersonPropertyValue(final PersonId personId, final PersonPropertyId personPropertyId, final Object personPropertyValue) { - validatePersonIdNotNull(personId); - validatePersonPropertyIdNotNull(personPropertyId); - validatePersonPropertyValueNotNull(personPropertyValue); - validatePersonPropertyNotAssigned(data, personId, personPropertyId); - Map map = data.personPropertyValues.get(personId); - if (map == null) { - map = new LinkedHashMap<>(); - data.personPropertyValues.put(personId, map); - } - map.put(personPropertyId, personPropertyValue); - return this; - } - } - - private static void validatePersonPropertyIsNotDefined(final Data data, final PersonPropertyId personPropertyId) { - final PropertyDefinition propertyDefinition = data.personPropertyDefinitions.get(personPropertyId); - if (propertyDefinition != null) { - throw new ContractException(PersonPropertyError.DUPLICATE_PERSON_PROPERTY_DEFINITION, personPropertyId); - } - } - - private static void validatePersonPropertyDefinitionNotNull(PropertyDefinition propertyDefinition) { - if (propertyDefinition == null) { - throw new ContractException(PersonPropertyError.NULL_PERSON_PROPERTY_DEFINITION); - } - } - - private static void validatePersonPropertyDefinitionHasDefault(PropertyDefinition propertyDefinition) { - if (!propertyDefinition.getDefaultValue().isPresent()) { - throw new ContractException(PersonPropertyError.PROPERTY_DEFINITION_REQUIRES_DEFAULT); - } - } - - private static void validatePersonPropertyIdNotNull(PersonPropertyId personPropertyId) { - if (personPropertyId == null) { - throw new ContractException(PersonPropertyError.NULL_PERSON_PROPERTY_ID); - } - } - - private final Data data; - - private PersonPropertiesPluginData(Data data) { - this.data = data; - } - - /** - * Returns the {@link PropertyDefinition} for the given - * {@link PersonPropertyId} - * - * @throws ContractException - * - *
  • {@linkplain PersonPropertyError#NULL_PERSON_PROPERTY_ID} - * if the person property id is null
  • - *
  • {@linkplain PersonPropertyError#UNKNOWN_PERSON_PROPERTY_ID} - * if the person property id is unknown
  • - * - */ - public PropertyDefinition getPersonPropertyDefinition(final PersonPropertyId personPropertyId) { - validatePersonPropertyIdNotNull(personPropertyId); - final PropertyDefinition propertyDefinition = data.personPropertyDefinitions.get(personPropertyId); - if (propertyDefinition == null) { - throw new ContractException(PersonPropertyError.UNKNOWN_PERSON_PROPERTY_ID, personPropertyId); - } - return propertyDefinition; - } - - /** - * Returns the set of {@link PersonPropertyId} ids - * - */ - @SuppressWarnings("unchecked") - public Set getPersonPropertyIds() { - Set result = new LinkedHashSet<>(); - for (PersonPropertyId personPropertyId : data.personPropertyDefinitions.keySet()) { - result.add((T) personPropertyId); - } - return result; - } - - @Override - public PluginDataBuilder getCloneBuilder() { - - return new Builder(new Data(data)); - } - - private static void validatePersonIdNotNull(PersonId personId) { - if (personId == null) { - throw new ContractException(PersonError.NULL_PERSON_ID); - } - } - - private static void validatePersonPropertyValueNotNull(Object personPropertyValue) { - if (personPropertyValue == null) { - throw new ContractException(PersonPropertyError.NULL_PERSON_PROPERTY_VALUE); - } - } - - private static void validatePersonPropertyNotAssigned(final Data data, final PersonId personId, final PersonPropertyId personPropertyId) { - final Map propertyMap = data.personPropertyValues.get(personId); - if (propertyMap != null) { - if (propertyMap.containsKey(personPropertyId)) { - throw new ContractException(PersonPropertyError.DUPLICATE_PERSON_PROPERTY_VALUE_ASSIGNMENT, personPropertyId + " = " + personId); - } - } - } - - /** - * Returns the set of {@link PersonId} ids collected by the builder - */ - public Set getPersonIds() { - return new LinkedHashSet<>(data.personPropertyValues.keySet()); - } - - /** - * Returns the property value for the given {@link PersonId} and - * {@link PersonPropertyId}. - * - * @throws ContractException - * - *
  • {@linkplain PersonError#NULL_PERSON_ID}
  • if the - * person id is null - *
  • {@linkplain PersonPropertyError#NULL_PERSON_PROPERTY_ID} - *
  • if the person property id is null - *
  • {@linkplain PersonPropertyError#UNKNOWN_PERSON_PROPERTY_ID} - *
  • if the person property id is known - */ - @SuppressWarnings("unchecked") - public T getPersonPropertyValue(final PersonId personId, final PersonPropertyId personPropertyId) { - validatePersonIdNotNull(personId); - validatePersonPropertyIdNotNull(personPropertyId); - validatePersonPropertyDefinitionIsDefined(data,personPropertyId); - Object result = null; - final Map map = data.personPropertyValues.get(personId); - if (map != null) { - result = map.get(personPropertyId); - } - if (result == null) { - final PropertyDefinition propertyDefinition = data.personPropertyDefinitions.get(personPropertyId); - result = propertyDefinition.getDefaultValue().get(); - } - return (T) result; - } - - private static void validatePersonPropertyDefinitionIsDefined(Data data, PersonPropertyId personPropertyId) { - if (!data.personPropertyDefinitions.containsKey(personPropertyId)) { - throw new ContractException(PersonPropertyError.UNKNOWN_PERSON_PROPERTY_ID, personPropertyId); - } - } - - private static void validateData(Data data) { - - for (PersonId personId : data.personPropertyValues.keySet()) { - Map map = data.personPropertyValues.get(personId); - for (PersonPropertyId personPropertyId : map.keySet()) { - PropertyDefinition propertyDefinition = data.personPropertyDefinitions.get(personPropertyId); - if (propertyDefinition == null) { - throw new ContractException(PersonPropertyError.UNKNOWN_PERSON_PROPERTY_ID, personPropertyId); - } - Object propertyValue = map.get(personPropertyId); - if (!propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())) { - throw new ContractException(PropertyError.INCOMPATIBLE_VALUE, personPropertyId + " = " + propertyValue); - } - } - } - - } - -} diff --git a/gcm3/src/main/java/plugins/personproperties/PersonPropertiesPluginId.java b/gcm3/src/main/java/plugins/personproperties/PersonPropertiesPluginId.java deleted file mode 100644 index 46ebcf4bb..000000000 --- a/gcm3/src/main/java/plugins/personproperties/PersonPropertiesPluginId.java +++ /dev/null @@ -1,17 +0,0 @@ -package plugins.personproperties; - -import nucleus.PluginId; - -/** - * Static plugin id implementation for the person properties plugin - * - * @author Shawn Hatch - * - */ - -public final class PersonPropertiesPluginId implements PluginId { - public final static PluginId PLUGIN_ID = new PersonPropertiesPluginId(); - - private PersonPropertiesPluginId() { - }; -} diff --git a/gcm3/src/main/java/plugins/personproperties/actors/PersonPropertyInteractionReport.java b/gcm3/src/main/java/plugins/personproperties/actors/PersonPropertyInteractionReport.java deleted file mode 100644 index 8c06a48cf..000000000 --- a/gcm3/src/main/java/plugins/personproperties/actors/PersonPropertyInteractionReport.java +++ /dev/null @@ -1,320 +0,0 @@ -package plugins.personproperties.actors; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -import nucleus.ActorContext; -import nucleus.EventLabel; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.events.PersonAdditionEvent; -import plugins.people.events.PersonImminentRemovalEvent; -import plugins.people.support.PersonId; -import plugins.personproperties.datamanagers.PersonPropertiesDataManager; -import plugins.personproperties.events.PersonPropertyUpdateEvent; -import plugins.personproperties.support.PersonPropertyId; -import plugins.regions.datamanagers.RegionsDataManager; -import plugins.regions.events.PersonRegionUpdateEvent; -import plugins.regions.support.RegionId; -import plugins.reports.support.PeriodicReport; -import plugins.reports.support.ReportHeader; -import plugins.reports.support.ReportId; -import plugins.reports.support.ReportItem; -import plugins.reports.support.ReportPeriod; - -/** - * A periodic Report that displays the number of people exhibiting a tuple of - * person property values for a given region. Only non-zero person counts are - * reported. - * - * - * Fields - * - * region -- the region identifier - * - * [propertyIds] -- the tuple of property field values - * - * person count -- the number of people having the tuple of property values - * within the region pair - * - * @author Shawn Hatch - * - */ -public final class PersonPropertyInteractionReport extends PeriodicReport { - - public PersonPropertyInteractionReport(ReportId reportId, ReportPeriod reportPeriod, PersonPropertyId... personPropertyIds) { - super(reportId, reportPeriod); - for (PersonPropertyId personPropertyId : personPropertyIds) { - propertyIds.add(personPropertyId); - } - } - - /* - * Represents a count of people in a particular region and having a - * particular tuple of property values. - */ - private static class Counter { - int count; - } - - private final List propertyIds = new ArrayList<>(); - - /* - * Map of ...>> - * - * A map of map of map... that starts with regions, each property id in - * order and ends with Counter - */ - - private final Map regionMap = new LinkedHashMap<>(); - - private ReportHeader reportHeader; - - /* - * Returns the report header for this report having columns for the selected - * property id values - */ - private ReportHeader getReportHeader() { - if (reportHeader == null) { - ReportHeader.Builder reportHeaderBuilder = ReportHeader.builder(); - addTimeFieldHeaders(reportHeaderBuilder).add("region"); - for (final PersonPropertyId personPropertyId : propertyIds) { - reportHeaderBuilder.add(personPropertyId.toString().toLowerCase()); - } - reportHeaderBuilder.add("person_Count"); - reportHeader = reportHeaderBuilder.build(); - } - return reportHeader; - } - - /* - * Decrements the Counter for the given region and person property values - * associated with the person - */ - private void decrement(final Object regionId, final PersonId personId) { - getCounter(regionId, personId, null, null).count--; - } - - /* - * Decrements the Counter for the given region and person property values - * associated with the person with the old property value being used instead - * of the current property value. - */ - private void decrementOldPropertyValue(final Object regionId, final PersonId personId, final Object oldPropertyId, final Object oldPropertyValue) { - getCounter(regionId, personId, oldPropertyId, oldPropertyValue).count--; - } - - @Override - protected void flush(ActorContext actorContext) { - - /* - * For each region pair, execute the recursive propertyFlush - */ - final Object[] propertyValues = new Object[propertyIds.size()]; - for (final Object regionId : regionMap.keySet()) { - - @SuppressWarnings("unchecked") - final Map map = (Map) regionMap.get(regionId); - propertyFlush(actorContext, regionId, map, propertyValues, 0); - - } - } - - /* - * Selects the counter that is accounting for the people in the region who - * have the same tuple of property values that the given person currently - * has. If the selectedPropertyId is not null, then the formerPropertyValue - * is used instead for forming the tuple. This is done to select the counter - * for the previous property value so that the counter may decremented. - */ - private Counter getCounter(final Object regionId, final PersonId personId, final Object selectedPropertyId, final Object formerPropertyValue) { - - /* - * First, push through the region map with the region to - * arrive at a nested map of maps for the properties - */ - - @SuppressWarnings("unchecked") - Map propertyValueMap = (Map) regionMap.get(regionId); - if (propertyValueMap == null) { - propertyValueMap = new LinkedHashMap<>(); - regionMap.put(regionId, propertyValueMap); - } - - /* - * Push downward through the mapping layers until all property values - * have been used. The last layer will have Counters as its values. - */ - final int n = propertyIds.size(); - for (int i = 0; i < n; i++) { - final PersonPropertyId personPropertyId = propertyIds.get(i); - Object personPropertyValue; - /* - * When this method is being used to decrement a counter for a - * previous value of a property, we select the former property value - * instead of the current property value. - */ - if (personPropertyId.equals(selectedPropertyId)) { - personPropertyValue = formerPropertyValue; - } else { - personPropertyValue = personPropertiesDataManager.getPersonPropertyValue(personId, personPropertyId); - } - - /* - * The last map level has Counters as its values. All other levels - * will have maps as their values. - */ - if (i == (n - 1)) { - Counter counter = (Counter) propertyValueMap.get(personPropertyValue); - if (counter == null) { - counter = new Counter(); - propertyValueMap.put(personPropertyValue, counter); - } - return counter; - } - @SuppressWarnings("unchecked") - Map subMap = (Map) propertyValueMap.get(personPropertyValue); - if (subMap == null) { - subMap = new LinkedHashMap<>(); - propertyValueMap.put(personPropertyValue, subMap); - } - propertyValueMap = subMap; - } - return null; - } - - private void handlePersonAdditionEvent(ActorContext actorContext, PersonAdditionEvent personAdditionEvent) { - PersonId personId = personAdditionEvent.getPersonId(); - final Object regionId = regionsDataManager.getPersonRegion(personId); - increment(regionId, personId); - } - - private void handlePersonPropertyUpdateEvent(ActorContext actorContext, PersonPropertyUpdateEvent personPropertyUpdateEvent) { - PersonPropertyId personPropertyId = personPropertyUpdateEvent.getPersonPropertyId(); - if (propertyIds.contains(personPropertyId)) { - PersonId personId = personPropertyUpdateEvent.getPersonId(); - Object previousPropertyValue = personPropertyUpdateEvent.getPreviousPropertyValue(); - final Object regionId = regionsDataManager.getPersonRegion(personId); - increment(regionId, personId); - decrementOldPropertyValue(regionId, personId, personPropertyId, previousPropertyValue); - } - } - - private void handlePersonImminentRemovalEvent(ActorContext actorContext, PersonImminentRemovalEvent personImminentRemovalEvent) { - PersonId personId = personImminentRemovalEvent.getPersonId(); - RegionId regionId = regionsDataManager.getPersonRegion(personId); - decrement(regionId, personId); - } - - private void handlePersonRegionUpdateEvent(ActorContext actorContext, PersonRegionUpdateEvent personRegionUpdateEvent) { - PersonId personId = personRegionUpdateEvent.getPersonId(); - RegionId sourceRegionId = personRegionUpdateEvent.getPreviousRegionId(); - final Object regionId = personRegionUpdateEvent.getCurrentRegionId(); - increment(regionId, personId); - decrement(sourceRegionId, personId); - } - - /* - * Increments the Counter for the given region and person - * property values associated with the person - */ - private void increment(final Object regionId, final PersonId personId) { - getCounter(regionId, personId, null, null).count++; - } - - private RegionsDataManager regionsDataManager; - private PeopleDataManager peopleDataManager; - private PersonPropertiesDataManager personPropertiesDataManager; - - @Override - public void init(final ActorContext actorContext) { - super.init(actorContext); - - actorContext.subscribe(PersonAdditionEvent.class, this::handlePersonAdditionEvent); - actorContext.subscribe(PersonImminentRemovalEvent.class, this::handlePersonImminentRemovalEvent); - actorContext.subscribe(PersonRegionUpdateEvent.class, this::handlePersonRegionUpdateEvent); - - personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class); - peopleDataManager = actorContext.getDataManager(PeopleDataManager.class); - regionsDataManager = actorContext.getDataManager(RegionsDataManager.class); - - /* - * if the client did not choose any properties, then we assume that all - * properties are selected - */ - if (propertyIds.size() == 0) { - propertyIds.addAll(personPropertiesDataManager.getPersonPropertyIds()); - } - - /* - * Validate the client's property ids and ignore any that are not known - * to the environment - */ - final Set validPersonPropertyIds = personPropertiesDataManager.getPersonPropertyIds(); - - final Iterator iterator = propertyIds.iterator(); - while (iterator.hasNext()) { - if (!validPersonPropertyIds.contains(iterator.next())) { - iterator.remove(); - } - } - - // If all person properties are included, then subscribe to the event - // class, otherwise subscribe to the individual property values - if (propertyIds.stream().collect(Collectors.toSet()).equals(personPropertiesDataManager.getPersonPropertyIds())) { - actorContext.subscribe(PersonPropertyUpdateEvent.class, this::handlePersonPropertyUpdateEvent); - } else { - for (PersonPropertyId personPropertyId : propertyIds) { - EventLabel eventLabelByProperty = PersonPropertyUpdateEvent.getEventLabelByProperty(actorContext, personPropertyId); - actorContext.subscribe(eventLabelByProperty, this::handlePersonPropertyUpdateEvent); - } - } - - for (PersonId personId : peopleDataManager.getPeople()) { - final Object regionId = regionsDataManager.getPersonRegion(personId); - increment(regionId, personId); - } - } - - /* - * Flushes the positive counters recursively. - */ - private void propertyFlush(ActorContext actorContext, final Object regionId, final Map map, final Object[] personPropertyValues, final int level) { - - for (final Object personPropertyValue : map.keySet()) { - personPropertyValues[level] = personPropertyValue; - if (level < (propertyIds.size() - 1)) { - @SuppressWarnings("unchecked") - final Map subMap = (Map) map.get(personPropertyValue); - propertyFlush(actorContext, regionId, subMap, personPropertyValues, level + 1); - } else { - final Counter counter = (Counter) map.get(personPropertyValue); - - if (counter.count > 0) { - final Map propertyIdsAndValues = new LinkedHashMap<>(); - for (int i = 0; i < propertyIds.size(); i++) { - propertyIdsAndValues.put(propertyIds.get(i).toString(), personPropertyValues[i]); - } - final ReportItem.Builder reportItemBuilder = ReportItem.builder(); - reportItemBuilder.setReportHeader(getReportHeader()); - reportItemBuilder.setReportId(getReportId()); - - fillTimeFields(reportItemBuilder); - reportItemBuilder.addValue(regionId.toString()); - - for (int i = 0; i < propertyIds.size(); i++) { - reportItemBuilder.addValue(personPropertyValues[i]); - } - reportItemBuilder.addValue(counter.count); - - actorContext.releaseOutput(reportItemBuilder.build()); - } - } - } - - } -} diff --git a/gcm3/src/main/java/plugins/personproperties/actors/PersonPropertyReport.java b/gcm3/src/main/java/plugins/personproperties/actors/PersonPropertyReport.java deleted file mode 100644 index 8739a070d..000000000 --- a/gcm3/src/main/java/plugins/personproperties/actors/PersonPropertyReport.java +++ /dev/null @@ -1,272 +0,0 @@ -package plugins.personproperties.actors; - -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -import nucleus.ActorContext; -import nucleus.EventLabel; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.events.PersonAdditionEvent; -import plugins.people.events.PersonImminentRemovalEvent; -import plugins.people.support.PersonId; -import plugins.personproperties.datamanagers.PersonPropertiesDataManager; -import plugins.personproperties.events.PersonPropertyUpdateEvent; -import plugins.personproperties.support.PersonPropertyError; -import plugins.personproperties.support.PersonPropertyId; -import plugins.regions.datamanagers.RegionsDataManager; -import plugins.regions.events.PersonRegionUpdateEvent; -import plugins.regions.support.RegionId; -import plugins.reports.support.PeriodicReport; -import plugins.reports.support.ReportHeader; -import plugins.reports.support.ReportId; -import plugins.reports.support.ReportItem; -import plugins.reports.support.ReportPeriod; -import util.errors.ContractException; - -/** - * A periodic Report that displays the number of people exhibiting a particular - * value for each person property for a given region pair. Only non-zero person - * counts are reported. - * - * - * Fields - * - * region -- the region identifier - * - * property -- the person property identifier - * - * value -- the value of the property - * - * person_count -- the number of people having the property value within the - * region - * - * @author Shawn Hatch - * - */ -public final class PersonPropertyReport extends PeriodicReport { - - public PersonPropertyReport(ReportId reportId, ReportPeriod reportPeriod, PersonPropertyId... personPropertyIds) { - super(reportId, reportPeriod); - for (PersonPropertyId personPropertyId : personPropertyIds) { - this.personPropertyIds.add(personPropertyId); - } - } - - /* - * A counter for people having the tuple (Region, Person Property, Property - * Value) - */ - private final static class Counter { - int count; - } - - /* - * The constrained set of person properties that will be used in this - * report. They are set during init() - */ - private final Set personPropertyIds = new LinkedHashSet<>(); - - /* - * The tuple mapping to person counts that is maintained via handling of - * events. - */ - private final Map>> tupleMap = new LinkedHashMap<>(); - - private ReportHeader reportHeader; - - private ReportHeader getReportHeader() { - if (reportHeader == null) { - ReportHeader.Builder reportHeaderBuilder = ReportHeader.builder(); - reportHeader = addTimeFieldHeaders(reportHeaderBuilder)// - .add("region")// - .add("property")// - .add("value")// - .add("person_count")// - .build();// - } - return reportHeader; - } - - /* - * Decrements the population for the given tuple - */ - private void decrement(final RegionId regionId, final PersonPropertyId personPropertyId, final Object personPropertyValue) { - getCounter(regionId, personPropertyId, personPropertyValue).count--; - } - - @Override - protected void flush(ActorContext actorContext) { - - final ReportItem.Builder reportItemBuilder = ReportItem.builder(); - - /* - * For each tuple having a positive population, report the tuple - */ - for (final RegionId regionId : tupleMap.keySet()) { - final Map> propertyIdMap = tupleMap.get(regionId); - for (final PersonPropertyId personPropertyId : propertyIdMap.keySet()) { - final Map personPropertyValueMap = propertyIdMap.get(personPropertyId); - for (final Object personPropertyValue : personPropertyValueMap.keySet()) { - final Counter counter = personPropertyValueMap.get(personPropertyValue); - if (counter.count > 0) { - final int personCount = counter.count; - reportItemBuilder.setReportHeader(getReportHeader()); - reportItemBuilder.setReportId(getReportId()); - - fillTimeFields(reportItemBuilder); - reportItemBuilder.addValue(regionId.toString()); - reportItemBuilder.addValue(personPropertyId.toString()); - reportItemBuilder.addValue(personPropertyValue); - reportItemBuilder.addValue(personCount); - - actorContext.releaseOutput(reportItemBuilder.build()); - } - } - } - - } - } - - /* - * Returns the counter for the give tuple. Creates the counter if it does - * not already exist. - */ - private Counter getCounter(final RegionId regionId, final PersonPropertyId personPropertyId, final Object personPropertyValue) { - final Map propertyValueMap = tupleMap.get(regionId).get(personPropertyId); - Counter counter = propertyValueMap.get(personPropertyValue); - if (counter == null) { - counter = new Counter(); - propertyValueMap.put(personPropertyValue, counter); - } - return counter; - - } - - private void handlePersonAdditionEvent(ActorContext context, PersonAdditionEvent personAdditionEvent) { - PersonId personId = personAdditionEvent.getPersonId(); - final RegionId regionId = regionsDataManager.getPersonRegion(personId); - for (final PersonPropertyId personPropertyId : personPropertyIds) { - final Object personPropertyValue = personPropertiesDataManager.getPersonPropertyValue(personId, personPropertyId); - increment(regionId, personPropertyId, personPropertyValue); - } - } - - private void handlePersonPropertyUpdateEvent(ActorContext context, PersonPropertyUpdateEvent personPropertyUpdateEvent) { - PersonPropertyId personPropertyId = personPropertyUpdateEvent.getPersonPropertyId(); - if (personPropertyIds.contains(personPropertyId)) { - PersonId personId = personPropertyUpdateEvent.getPersonId(); - Object previousPropertyValue = personPropertyUpdateEvent.getPreviousPropertyValue(); - final RegionId regionId = regionsDataManager.getPersonRegion(personId); - final Object currentValue = personPropertiesDataManager.getPersonPropertyValue(personId, personPropertyId); - increment(regionId, personPropertyId, currentValue); - decrement(regionId, personPropertyId, previousPropertyValue); - } - } - - private void handlePersonImminentRemovalEvent(ActorContext context, PersonImminentRemovalEvent personImminentRemovalEvent) { - PersonId personId = personImminentRemovalEvent.getPersonId(); - RegionId regionId = regionsDataManager.getPersonRegion(personId); - for (PersonPropertyId personPropertyId : personPropertyIds) { - final Object personPropertyValue = personPropertiesDataManager.getPersonPropertyValue(personId, personPropertyId); - decrement(regionId, personPropertyId, personPropertyValue); - } - } - - private void handlePersonRegionUpdateEvent(ActorContext context, PersonRegionUpdateEvent personRegionUpdateEvent) { - PersonId personId = personRegionUpdateEvent.getPersonId(); - RegionId previousRegionId = personRegionUpdateEvent.getPreviousRegionId(); - RegionId regionId = personRegionUpdateEvent.getCurrentRegionId(); - for (final PersonPropertyId personPropertyId : personPropertyIds) { - final Object personPropertyValue = personPropertiesDataManager.getPersonPropertyValue(personId, personPropertyId); - increment(regionId, personPropertyId, personPropertyValue); - decrement(previousRegionId, personPropertyId, personPropertyValue); - } - } - - /* - * Increments the population for the given tuple - */ - private void increment(final RegionId regionId, final PersonPropertyId personPropertyId, final Object personPropertyValue) { - getCounter(regionId, personPropertyId, personPropertyValue).count++; - } - - private PersonPropertiesDataManager personPropertiesDataManager; - - private RegionsDataManager regionsDataManager; - - /** - * @throws ContractException - * - *
  • {@linkplain PersonPropertyError.UNKNOWN_PERSON_PROPERTY_ID} - * if a person property specified in construction is unknown
  • - */ - @Override - public void init(final ActorContext actorContext) { - super.init(actorContext); - - actorContext.subscribe(PersonAdditionEvent.class, this::handlePersonAdditionEvent); - actorContext.subscribe(PersonImminentRemovalEvent.class, this::handlePersonImminentRemovalEvent); - actorContext.subscribe(PersonRegionUpdateEvent.class, this::handlePersonRegionUpdateEvent); - - regionsDataManager = actorContext.getDataManager(RegionsDataManager.class); - personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class); - PeopleDataManager peopleDataManager = actorContext.getDataManager(PeopleDataManager.class); - - /* - * If no person properties were specified, then assume all are wanted - */ - if (personPropertyIds.size() == 0) { - personPropertyIds.addAll(personPropertiesDataManager.getPersonPropertyIds()); - } - - /* - * Ensure that every client supplied property identifier is valid - */ - final Set validPropertyIds = personPropertiesDataManager.getPersonPropertyIds(); - for (final PersonPropertyId personPropertyId : personPropertyIds) { - if (!validPropertyIds.contains(personPropertyId)) { - throw new ContractException(PersonPropertyError.UNKNOWN_PERSON_PROPERTY_ID, personPropertyId); - } - } - - // If all person properties are included, then subscribe to the event - // class, otherwise subscribe to the individual property values - if (personPropertyIds.equals(personPropertiesDataManager.getPersonPropertyIds())) { - actorContext.subscribe(PersonPropertyUpdateEvent.class, this::handlePersonPropertyUpdateEvent); - } else { - for (PersonPropertyId personPropertyId : personPropertyIds) { - EventLabel eventLabelByProperty = PersonPropertyUpdateEvent.getEventLabelByProperty(actorContext, personPropertyId); - actorContext.subscribe(eventLabelByProperty, this::handlePersonPropertyUpdateEvent); - } - } - - /* - * Fill the top layers of the regionMap. We do not yet know the set of - * property values, so we leave that layer empty. - * - */ - - // Map>> - final Set regionIds = actorContext.getDataManager(RegionsDataManager.class).getRegionIds(); - for (final RegionId regionId : regionIds) { - final Map> propertyIdMap = new LinkedHashMap<>(); - tupleMap.put(regionId, propertyIdMap); - for (final PersonPropertyId personPropertyId : personPropertyIds) { - final Map propertyValueMap = new LinkedHashMap<>(); - propertyIdMap.put(personPropertyId, propertyValueMap); - } - } - - for (PersonId personId : peopleDataManager.getPeople()) { - final RegionId regionId = regionsDataManager.getPersonRegion(personId); - for (final PersonPropertyId personPropertyId : personPropertyIds) { - final Object personPropertyValue = personPropertiesDataManager.getPersonPropertyValue(personId, personPropertyId); - increment(regionId, personPropertyId, personPropertyValue); - } - } - - } - -} \ No newline at end of file diff --git a/gcm3/src/main/java/plugins/personproperties/datamanagers/PersonPropertiesDataManager.java b/gcm3/src/main/java/plugins/personproperties/datamanagers/PersonPropertiesDataManager.java deleted file mode 100644 index e05e53e8b..000000000 --- a/gcm3/src/main/java/plugins/personproperties/datamanagers/PersonPropertiesDataManager.java +++ /dev/null @@ -1,482 +0,0 @@ -package plugins.personproperties.datamanagers; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import nucleus.DataManager; -import nucleus.DataManagerContext; -import nucleus.SimulationContext; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.events.BulkPersonAdditionEvent; -import plugins.people.events.PersonAdditionEvent; -import plugins.people.events.PersonImminentRemovalEvent; -import plugins.people.support.BulkPersonConstructionData; -import plugins.people.support.PersonConstructionData; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import plugins.personproperties.PersonPropertiesPluginData; -import plugins.personproperties.events.PersonPropertyUpdateEvent; -import plugins.personproperties.support.PersonPropertyError; -import plugins.personproperties.support.PersonPropertyId; -import plugins.personproperties.support.PersonPropertyInitialization; -import plugins.regions.datamanagers.RegionsDataManager; -import plugins.util.properties.BooleanPropertyManager; -import plugins.util.properties.DoublePropertyManager; -import plugins.util.properties.EnumPropertyManager; -import plugins.util.properties.FloatPropertyManager; -import plugins.util.properties.IndexedPropertyManager; -import plugins.util.properties.IntPropertyManager; -import plugins.util.properties.ObjectPropertyManager; -import plugins.util.properties.PropertyDefinition; -import plugins.util.properties.PropertyError; -import plugins.util.properties.TimeTrackingPolicy; -import util.errors.ContractException; - -/** - * Mutable data manager for person properties - * - * @author Shawn Hatch - * - */ - -public final class PersonPropertiesDataManager extends DataManager { - @Override - public void init(DataManagerContext dataManagerContext) { - super.init(dataManagerContext); - this.dataManagerContext = dataManagerContext; - - peopleDataManager = dataManagerContext.getDataManager(PeopleDataManager.class); - RegionsDataManager regionsDataManager = dataManagerContext.getDataManager(RegionsDataManager.class); - - dataManagerContext.addEventLabeler(PersonPropertyUpdateEvent.getEventLabelerForRegionAndProperty(regionsDataManager)); - dataManagerContext.addEventLabeler(PersonPropertyUpdateEvent.getEventLabelerForPersonAndProperty()); - dataManagerContext.addEventLabeler(PersonPropertyUpdateEvent.getEventLabelerForProperty()); - - Set personPropertyIds = personPropertiesPluginData.getPersonPropertyIds(); - for (final PersonPropertyId personPropertyId : personPropertyIds) { - PropertyDefinition personPropertyDefinition = personPropertiesPluginData.getPersonPropertyDefinition(personPropertyId); - personPropertyDefinitions.put(personPropertyId, personPropertyDefinition); - final IndexedPropertyManager indexedPropertyManager = getIndexedPropertyManager(dataManagerContext, personPropertyDefinition, 0); - personPropertyManagerMap.put(personPropertyId, indexedPropertyManager); - } - List people = peopleDataManager.getPeople(); - for (PersonId personId : people) { - for (PersonPropertyId personPropertyId : personPropertyIds) { - Object personPropertyValue = personPropertiesPluginData.getPersonPropertyValue(personId, personPropertyId); - int pId = personId.getValue(); - IndexedPropertyManager propertyManager = personPropertyManagerMap.get(personPropertyId); - propertyManager.setPropertyValue(pId, personPropertyValue); - } - } - dataManagerContext.subscribe(PersonAdditionEvent.class, this::handlePersonAdditionEvent); - dataManagerContext.subscribe(BulkPersonAdditionEvent.class, this::handleBulkPersonAdditionEvent); - dataManagerContext.subscribe(PersonImminentRemovalEvent.class, this::handlePersonImminentRemovalEvent); - - } - - private final Map personPropertyDefinitions = new LinkedHashMap<>(); - - private final Map personPropertyManagerMap = new LinkedHashMap<>(); - - /* - * We keep the person records in a list rather than a map so that we can - * retrieve a person record by index (personId). - */ - - private PeopleDataManager peopleDataManager; - - private IndexedPropertyManager getIndexedPropertyManager(final SimulationContext simulationContext, final PropertyDefinition propertyDefinition, final int intialSize) { - - IndexedPropertyManager indexedPropertyManager; - if (propertyDefinition.getType() == Boolean.class) { - indexedPropertyManager = new BooleanPropertyManager(simulationContext, propertyDefinition, intialSize); - } else if (propertyDefinition.getType() == Float.class) { - indexedPropertyManager = new FloatPropertyManager(simulationContext, propertyDefinition, intialSize); - } else if (propertyDefinition.getType() == Double.class) { - indexedPropertyManager = new DoublePropertyManager(simulationContext, propertyDefinition, intialSize); - } else if (propertyDefinition.getType() == Byte.class) { - indexedPropertyManager = new IntPropertyManager(simulationContext, propertyDefinition, intialSize); - } else if (propertyDefinition.getType() == Short.class) { - indexedPropertyManager = new IntPropertyManager(simulationContext, propertyDefinition, intialSize); - } else if (propertyDefinition.getType() == Integer.class) { - indexedPropertyManager = new IntPropertyManager(simulationContext, propertyDefinition, intialSize); - } else if (propertyDefinition.getType() == Long.class) { - indexedPropertyManager = new IntPropertyManager(simulationContext, propertyDefinition, intialSize); - } else if (Enum.class.isAssignableFrom(propertyDefinition.getType())) { - indexedPropertyManager = new EnumPropertyManager(simulationContext, propertyDefinition, intialSize); - } else { - indexedPropertyManager = new ObjectPropertyManager(simulationContext, propertyDefinition, intialSize); - } - return indexedPropertyManager; - } - - private void validatePersonPropertyValueNotNull(final Object propertyValue) { - if (propertyValue == null) { - throw new ContractException(PersonPropertyError.NULL_PERSON_PROPERTY_VALUE); - } - } - - /* - * Preconditions: all arguments are non-null - */ - private void validateValueCompatibility(final Object propertyId, final PropertyDefinition propertyDefinition, final Object propertyValue) { - if (!propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())) { - throw new ContractException(PropertyError.INCOMPATIBLE_VALUE, - "Property value " + propertyValue + " is not of type " + propertyDefinition.getType().getName() + " and does not match definition of " + propertyId); - } - } - - /** - * Returns the list(no duplicates) people who have the given person property - * value. - * - * @throws ContractException - *
  • {@linkplain PersonPropertyError#NULL_PERSON_PROPERTY_ID} - * if the person property id is null
  • - *
  • {@linkplain PersonPropertyError#UNKNOWN_PERSON_PROPERTY_ID} - * if the person property id is unknown
  • - *
  • {@linkplain PersonPropertyError#NULL_PERSON_PROPERTY_VALUE} - * if the person property value is null
  • - *
  • {@linkplain PropertyError#INCOMPATIBLE_VALUE} if the - * person property value is not compatible with the property - * definition associated with the given person property id
  • - */ - public List getPeopleWithPropertyValue(final PersonPropertyId personPropertyId, final Object personPropertyValue) { - validatePersonPropertyId(personPropertyId); - final PropertyDefinition propertyDefinition = personPropertyDefinitions.get(personPropertyId); - validatePersonPropertyValueNotNull(personPropertyValue); - validateValueCompatibility(personPropertyId, propertyDefinition, personPropertyValue); - - final IndexedPropertyManager indexedPropertyManager = personPropertyManagerMap.get(personPropertyId); - - /* - * We are not maintaining a map from property values to people. We first - * determine the number of people who will be returned so that we can - * size the resulting ArrayList properly. - */ - final int n = peopleDataManager.getPersonIdLimit(); - int count = 0; - for (int personIndex = 0; personIndex < n; personIndex++) { - if (peopleDataManager.personIndexExists(personIndex)) { - final PersonId personId = peopleDataManager.getBoxedPersonId(personIndex).get(); - final Object propertyValue = indexedPropertyManager.getPropertyValue(personId.getValue()); - if (propertyValue.equals(personPropertyValue)) { - count++; - } - } - } - - /* - * Now we fill the list. - */ - final List result = new ArrayList<>(count); - - for (int personIndex = 0; personIndex < n; personIndex++) { - if (peopleDataManager.personIndexExists(personIndex)) { - final PersonId personId = peopleDataManager.getBoxedPersonId(personIndex).get(); - final Object propertyValue = indexedPropertyManager.getPropertyValue(personId.getValue()); - if (propertyValue.equals(personPropertyValue)) { - result.add(personId); - } - } - } - - return result; - - } - - /** - * Returns the number of people who have the given person property value. - * - * @throws ContractException - *
  • {@linkplain PersonPropertyError#NULL_PERSON_PROPERTY_ID} - * if the person property id is null
  • - *
  • {@linkplain PersonPropertyError#UNKNOWN_PERSON_PROPERTY_ID} - * if the person property id is unknown
  • - *
  • {@linkplain PersonPropertyError#NULL_PERSON_PROPERTY_VALUE} - * if the person property value is null
  • - *
  • {@linkplain PropertyError#INCOMPATIBLE_VALUE} if the - * person property value is not compatible with the property - * definition associated with the given person property id
  • - */ - public int getPersonCountForPropertyValue(final PersonPropertyId personPropertyId, final Object personPropertyValue) { - - validatePersonPropertyId(personPropertyId); - final PropertyDefinition propertyDefinition = personPropertyDefinitions.get(personPropertyId); - validatePersonPropertyValueNotNull(personPropertyValue); - validateValueCompatibility(personPropertyId, propertyDefinition, personPropertyValue); - - /* - * We are not maintaining a map from property values to people. We first - * determine the number of people who will be returned so that we can - * size the resulting ArrayList properly. - */ - - final IndexedPropertyManager indexedPropertyManager = personPropertyManagerMap.get(personPropertyId); - final int n = peopleDataManager.getPersonIdLimit(); - int count = 0; - for (int personIndex = 0; personIndex < n; personIndex++) { - if (peopleDataManager.personIndexExists(personIndex)) { - final PersonId personId = peopleDataManager.getBoxedPersonId(personIndex).get(); - final Object propertyValue = indexedPropertyManager.getPropertyValue(personId.getValue()); - if (propertyValue.equals(personPropertyValue)) { - count++; - } - } - } - return count; - - } - - /** - * Returns the property definition for the given person property id - * - * @throws ContractException - *
  • {@linkplain PersonPropertyError#NULL_PERSON_PROPERTY_ID} - * if the person property id is null
  • - *
  • {@linkplain PersonPropertyError#UNKNOWN_PERSON_PROPERTY_ID} - * if the person property id is unknown
  • - */ - public PropertyDefinition getPersonPropertyDefinition(final PersonPropertyId personPropertyId) { - validatePersonPropertyId(personPropertyId); - return personPropertyDefinitions.get(personPropertyId); - } - - /** - * Returns the person property ids - */ - @SuppressWarnings("unchecked") - public Set getPersonPropertyIds() { - - final Set result = new LinkedHashSet<>(personPropertyDefinitions.keySet().size()); - for (final PersonPropertyId personPropertyId : personPropertyDefinitions.keySet()) { - result.add((T) personPropertyId); - } - - return result; - } - - /** - * Returns the time when the person's property was last assigned or zero if - * the value has never been assigned. - * - * @throws ContractException - *
  • {@linkplain PersonError#NULL_PERSON_ID} if the person id - * is null
  • - *
  • {@linkplain PersonError#UNKNOWN_PERSON_ID} if the person - * id is unknown
  • - *
  • {@linkplain PersonPropertyError#NULL_PERSON_PROPERTY_ID} - * if the person property id is null
  • - *
  • {@linkplain PersonPropertyError#UNKNOWN_PERSON_PROPERTY_ID} - * if the person property id is unknown
  • - *
  • {@linkplain PersonPropertyError#PROPERTY_ASSIGNMENT_TIME_NOT_TRACKED} - * if the person property does not have time tracking turned on - * in the associated property definition
  • - * - */ - public double getPersonPropertyTime(final PersonId personId, final PersonPropertyId personPropertyId) { - validatePersonExists(personId); - validatePersonPropertyId(personPropertyId); - validatePersonPropertyAssignmentTimesTracked(personPropertyId); - return personPropertyManagerMap.get(personPropertyId).getPropertyTime(personId.getValue()); - } - - /* - * Precondition : the person property id is valid - */ - private void validatePersonPropertyAssignmentTimesTracked(final PersonPropertyId personPropertyId) { - final PropertyDefinition personPropertyDefinition = personPropertyDefinitions.get(personPropertyId); - if (personPropertyDefinition.getTimeTrackingPolicy() != TimeTrackingPolicy.TRACK_TIME) { - throw new ContractException(PersonPropertyError.PROPERTY_ASSIGNMENT_TIME_NOT_TRACKED); - } - } - - /** - * Returns the current value of the person's property - * - * @throws ContractException - *
  • {@linkplain PersonError#NULL_PERSON_ID} if the person id - * is null
  • - *
  • {@linkplain PersonError#UNKNOWN_PERSON_ID} if the person - * id is unknown
  • - *
  • {@linkplain PersonPropertyError#NULL_PERSON_PROPERTY_ID} - * if the person property id is null
  • - *
  • {@linkplain PersonPropertyError#UNKNOWN_PERSON_PROPERTY_ID} - * if the person property id is unknown
  • - * - */ - @SuppressWarnings("unchecked") - public T getPersonPropertyValue(final PersonId personId, final PersonPropertyId personPropertyId) { - validatePersonExists(personId); - validatePersonPropertyId(personPropertyId); - return (T) personPropertyManagerMap.get(personPropertyId).getPropertyValue(personId.getValue()); - } - - private void validatePersonExists(final PersonId personId) { - if (personId == null) { - throw new ContractException(PersonError.NULL_PERSON_ID); - } - if (!peopleDataManager.personExists(personId)) { - throw new ContractException(PersonError.UNKNOWN_PERSON_ID); - } - } - - private void validatePersonPropertyId(final PersonPropertyId personPropertyId) { - if (personPropertyId == null) { - throw new ContractException(PersonPropertyError.NULL_PERSON_PROPERTY_ID); - } - - if (!personPropertyDefinitions.containsKey(personPropertyId)) { - throw new ContractException(PersonPropertyError.UNKNOWN_PERSON_PROPERTY_ID, personPropertyId); - } - } - - private DataManagerContext dataManagerContext; - - private final PersonPropertiesPluginData personPropertiesPluginData; - - /** - * Constructs the person property data manager from the given plugin data - * - * @throws ContractException - *
  • {@linkplain PersonPropertyError#NULL_PERSON_PROPERTY_PLUGN_DATA} - * if the plugin data is null
  • - */ - public PersonPropertiesDataManager(PersonPropertiesPluginData personPropertiesPluginData) { - if (personPropertiesPluginData == null) { - throw new ContractException(PersonPropertyError.NULL_PERSON_PROPERTY_PLUGN_DATA); - } - this.personPropertiesPluginData = personPropertiesPluginData; - - } - - /** - * Expands the capacity of data structures to hold people by the given - * count. Used to more efficiently prepare for bulk population additions. - * - * @throws ContractException - *
  • {@linkplain PersonError#NEGATIVE_GROWTH_PROJECTION} if - * the count is negative
  • - */ - public void expandCapacity(final int count) { - if (count < 0) { - throw new ContractException(PersonError.NEGATIVE_GROWTH_PROJECTION); - } - if (count > 0) { - for (final PersonPropertyId personPropertyId : personPropertyManagerMap.keySet()) { - IndexedPropertyManager indexedPropertyManager = personPropertyManagerMap.get(personPropertyId); - indexedPropertyManager.incrementCapacity(count); - } - } - } - - /** - * Returns true if and only if the person property id is valid. - */ - public boolean personPropertyIdExists(final PersonPropertyId personPropertyId) { - return personPropertyDefinitions.containsKey(personPropertyId); - } - - /** - * Updates the value of a person's property. Generates a corresponding - * {@linkplain PersonPropertyUpdateEvent} - * - * - * Throws {@link ContractException} - * - *
  • {@link PersonError#NULL_PERSON_ID} if the person id is null
  • - *
  • {@link PersonError#UNKNOWN_PERSON_ID} if the person id is - * unknown
  • - *
  • {@link PersonPropertyError#NULL_PERSON_PROPERTY_ID} if the person - * property id is null
  • - *
  • {@link PersonPropertyError#UNKNOWN_PERSON_PROPERTY_ID} if the person - * property id is unknown
  • - *
  • {@link PersonPropertyError#NULL_PERSON_PROPERTY_VALUE} if the - * property value is null
  • - *
  • {@link PropertyError#INCOMPATIBLE_VALUE} if the property value is not - * compatible with the corresponding property definition
  • - *
  • {@link PropertyError#IMMUTABLE_VALUE} if the corresponding property - * definition marks the property as immutable
  • - * - */ - public void setPersonPropertyValue(final PersonId personId, final PersonPropertyId personPropertyId, final Object personPropertyValue) { - validatePersonExists(personId); - validatePersonPropertyId(personPropertyId); - validatePersonPropertyValueNotNull(personPropertyValue); - final PropertyDefinition propertyDefinition = personPropertyDefinitions.get(personPropertyId); - validateValueCompatibility(personPropertyId, propertyDefinition, personPropertyValue); - validatePropertyMutability(propertyDefinition); - - int pId = personId.getValue(); - IndexedPropertyManager propertyManager = personPropertyManagerMap.get(personPropertyId); - Object oldValue = propertyManager.getPropertyValue(pId); - propertyManager.setPropertyValue(pId, personPropertyValue); - dataManagerContext.releaseEvent(new PersonPropertyUpdateEvent(personId, personPropertyId, oldValue, personPropertyValue)); - } - - private static void validatePropertyMutability(final PropertyDefinition propertyDefinition) { - if (!propertyDefinition.propertyValuesAreMutable()) { - throw new ContractException(PropertyError.IMMUTABLE_VALUE); - } - } - - private void handlePersonAdditionEvent(final DataManagerContext dataManagerContext, final PersonAdditionEvent personAdditionEvent) { - PersonConstructionData personConstructionData = personAdditionEvent.getPersonConstructionData(); - - PersonId personId = personAdditionEvent.getPersonId(); - - List personPropertyAssignments = personConstructionData.getValues(PersonPropertyInitialization.class); - for (final PersonPropertyInitialization personPropertyAssignment : personPropertyAssignments) { - PersonPropertyId personPropertyId = personPropertyAssignment.getPersonPropertyId(); - final Object personPropertyValue = personPropertyAssignment.getValue(); - validatePersonPropertyId(personPropertyId); - validatePersonPropertyValueNotNull(personPropertyValue); - final PropertyDefinition propertyDefinition = personPropertyDefinitions.get(personPropertyId); - validateValueCompatibility(personPropertyId, propertyDefinition, personPropertyValue); - int pId = personId.getValue(); - IndexedPropertyManager propertyManager = personPropertyManagerMap.get(personPropertyId); - propertyManager.setPropertyValue(pId, personPropertyValue); - } - - } - - private void handleBulkPersonAdditionEvent(final DataManagerContext dataManagerContext, final BulkPersonAdditionEvent bulkPersonAdditionEvent) { - PersonId personId = bulkPersonAdditionEvent.getPersonId(); - int pId = personId.getValue(); - - BulkPersonConstructionData bulkPersonConstructionData = bulkPersonAdditionEvent.getBulkPersonConstructionData(); - - List personConstructionDatas = bulkPersonConstructionData.getPersonConstructionDatas(); - for (PersonConstructionData personConstructionData : personConstructionDatas) { - List personPropertyAssignments = personConstructionData.getValues(PersonPropertyInitialization.class); - for (final PersonPropertyInitialization personPropertyAssignment : personPropertyAssignments) { - PersonPropertyId personPropertyId = personPropertyAssignment.getPersonPropertyId(); - final Object personPropertyValue = personPropertyAssignment.getValue(); - validatePersonPropertyId(personPropertyId); - validatePersonPropertyValueNotNull(personPropertyValue); - final PropertyDefinition propertyDefinition = personPropertyDefinitions.get(personPropertyId); - validateValueCompatibility(personPropertyId, propertyDefinition, personPropertyValue); - IndexedPropertyManager propertyManager = personPropertyManagerMap.get(personPropertyId); - propertyManager.setPropertyValue(pId, personPropertyValue); - } - pId++; - } - - } - - private void handlePersonImminentRemovalEvent(final DataManagerContext dataManagerContext, final PersonImminentRemovalEvent personImminentRemovalEvent) { - PersonId personId = personImminentRemovalEvent.getPersonId(); - validatePersonExists(personId); - dataManagerContext.addPlan((context) -> { - for (final PersonPropertyId personPropertyId : personPropertyManagerMap.keySet()) { - final IndexedPropertyManager indexedPropertyManager = personPropertyManagerMap.get(personPropertyId); - indexedPropertyManager.removeId(personId.getValue()); - } - }, dataManagerContext.getTime()); - } - -} diff --git a/gcm3/src/main/java/plugins/personproperties/events/PersonPropertyUpdateEvent.java b/gcm3/src/main/java/plugins/personproperties/events/PersonPropertyUpdateEvent.java deleted file mode 100644 index e32f7d1ca..000000000 --- a/gcm3/src/main/java/plugins/personproperties/events/PersonPropertyUpdateEvent.java +++ /dev/null @@ -1,262 +0,0 @@ -package plugins.personproperties.events; - -import net.jcip.annotations.Immutable; -import nucleus.Event; -import nucleus.EventLabel; -import nucleus.EventLabeler; -import nucleus.EventLabelerId; -import nucleus.SimulationContext; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import plugins.personproperties.datamanagers.PersonPropertiesDataManager; -import plugins.personproperties.support.PersonPropertyError; -import plugins.personproperties.support.PersonPropertyId; -import plugins.regions.datamanagers.RegionsDataManager; -import plugins.regions.support.RegionError; -import plugins.regions.support.RegionId; -import util.errors.ContractException; - -/** - * An observation event indicating that a person's property assignment has - * changed. - * - * @author Shawn Hatch - * - */ - -@Immutable -public class PersonPropertyUpdateEvent implements Event { - - private final PersonId personId; - private final PersonPropertyId personPropertyId; - private final Object previousPropertyValue; - private final Object currentPropertyValue; - - /** - * Creates this event from valid, non-null inputs - * - */ - public PersonPropertyUpdateEvent(final PersonId personId, final PersonPropertyId personPropertyId, final Object previousPropertyValue, final Object currentPropertyValue) { - super(); - this.personId = personId; - this.personPropertyId = personPropertyId; - this.previousPropertyValue = previousPropertyValue; - this.currentPropertyValue = currentPropertyValue; - } - - /** - * Returns the current property value used to construct this event - */ - public Object getCurrentPropertyValue() { - return currentPropertyValue; - } - - /** - * Returns the person property id used to construct this event - */ - public PersonPropertyId getPersonPropertyId() { - return personPropertyId; - } - - /** - * Returns the person id used to construct this event - */ - public PersonId getPersonId() { - return personId; - } - - /** - * Returns the previous property value used to construct this event - */ - public Object getPreviousPropertyValue() { - return previousPropertyValue; - } - - /** - * Returns this event in the form: - * - * "PersonPropertyUpdateEvent [personId=" + personId + ", personPropertyId=" - * + personPropertyId + ", previousPropertyValue=" + previousPropertyValue + - * ", currentPropertyValue=" + currentPropertyValue + "]"; - */ - @Override - public String toString() { - return "PersonPropertyUpdateEvent [personId=" + personId + ", personPropertyId=" + personPropertyId + ", previousPropertyValue=" + previousPropertyValue + ", currentPropertyValue=" - + currentPropertyValue + "]"; - } - - private static enum LabelerId implements EventLabelerId { - PERSON_PROPERTY, PROPERTY, REGION_PROPERTY - } - - /** - * Returns an event label used to subscribe to - * {@link PersonPropertyUpdateEvent} events. Matches on person id and person - * property id. - * - * - * @throws ContractException - * - *
  • {@linkplain PersonError#NULL_PERSON_ID} if the person id - * is null
  • - *
  • {@linkplain PersonError#UNKNOWN_PERSON_ID} if the person - * id is not known
  • - *
  • {@linkplain PersonPropertyError#NULL_PERSON_PROPERTY_ID} - * if the person property id is null
  • - *
  • {@linkplain PersonPropertyError#UNKNOWN_PERSON_PROPERTY_ID} - * if the person property id is not known
  • - * - */ - public static EventLabel getEventLabelByPersonAndProperty(SimulationContext simulationContext, PersonId personId, PersonPropertyId personPropertyId) { - validatePersonPropertyId(simulationContext, personPropertyId); - validatePersonId(simulationContext, personId); - return _getEventLabelByPersonAndProperty(personId, personPropertyId);// - } - - private static EventLabel _getEventLabelByPersonAndProperty(PersonId personId, PersonPropertyId personPropertyId) { - - return EventLabel .builder(PersonPropertyUpdateEvent.class)// - .setEventLabelerId(LabelerId.PERSON_PROPERTY)// - .addKey(personPropertyId)// - .addKey(personId)// - .build();// - } - - /** - * Returns an event labeler for {@link PersonPropertyUpdateEvent} events - * that uses person id and person property id. Automatically added at - * initialization. - */ - public static EventLabeler getEventLabelerForPersonAndProperty() { - return EventLabeler .builder(PersonPropertyUpdateEvent.class)// - .setEventLabelerId(LabelerId.PERSON_PROPERTY)// - .setLabelFunction((context, event) -> _getEventLabelByPersonAndProperty(event.getPersonId(), event.getPersonPropertyId()))// - .build(); - } - - /** - * Returns an event label used to subscribe to - * {@link PersonPropertyUpdateEvent} events. Matches on person property id. - * - * - * @throws ContractException - * - *
  • {@linkplain PersonPropertyError#NULL_PERSON_PROPERTY_ID} - * if the person property id is null
  • - *
  • {@linkplain PersonPropertyError#UNKNOWN_PERSON_PROPERTY_ID} - * if the person property id is not known
  • - * - */ - public static EventLabel getEventLabelByProperty(SimulationContext simulationContext, PersonPropertyId personPropertyId) { - validatePersonPropertyId(simulationContext, personPropertyId); - return _getEventLabelByProperty(personPropertyId);// - } - - private static EventLabel _getEventLabelByProperty(PersonPropertyId personPropertyId) { - - return EventLabel .builder(PersonPropertyUpdateEvent.class)// - .setEventLabelerId(LabelerId.PROPERTY)// - .addKey(personPropertyId)// - .build();// - - } - - /** - * Returns an event labeler for {@link PersonPropertyUpdateEvent} events - * that uses only the person property id. Automatically added at - * initialization. - */ - public static EventLabeler getEventLabelerForProperty() { - return EventLabeler .builder(PersonPropertyUpdateEvent.class)// - .setEventLabelerId(LabelerId.PROPERTY)// - .setLabelFunction((context, event) -> _getEventLabelByProperty(event.getPersonPropertyId()))// - .build(); - } - - /** - * Returns an event label used to subscribe to - * {@link PersonPropertyUpdateEvent} events. Matches on region id and person - * property id. - * - * - * @throws ContractException - * - *
  • {@linkplain RegionError#NULL_REGION_ID} if the region id - * is null
  • - *
  • {@linkplain RegionError#UNKNOWN_REGION_ID} if the region - * id is not known
  • - *
  • {@linkplain PersonPropertyError#NULL_PERSON_PROPERTY_ID} - * if the person property id is null
  • - *
  • {@linkplain PersonPropertyError#UNKNOWN_PERSON_PROPERTY_ID} - * if the person property id is not known
  • - * - */ - public static EventLabel getEventLabelByRegionAndProperty(SimulationContext simulationContext, RegionId regionId, PersonPropertyId personPropertyId) { - validatePersonPropertyId(simulationContext, personPropertyId); - validateRegionId(simulationContext, regionId); - return _getEventLabelByRegionAndProperty(regionId, personPropertyId);// - } - - private static EventLabel _getEventLabelByRegionAndProperty(RegionId regionId, PersonPropertyId personPropertyId) { - - return EventLabel .builder(PersonPropertyUpdateEvent.class)// - .setEventLabelerId(LabelerId.REGION_PROPERTY)// - .addKey(personPropertyId)// - .addKey(regionId)// - .build();// - } - - /** - * Returns an event labeler for {@link PersonPropertyUpdateEvent} events - * that uses the region id and person property id. Automatically added at - * initialization. - */ - public static EventLabeler getEventLabelerForRegionAndProperty(RegionsDataManager regionsDataManager) { - return EventLabeler .builder(PersonPropertyUpdateEvent.class)// - .setEventLabelerId(LabelerId.REGION_PROPERTY)// - .setLabelFunction((context, event) -> { - RegionId regionId = regionsDataManager.getPersonRegion(event.getPersonId()); - return _getEventLabelByRegionAndProperty(regionId, event.getPersonPropertyId()); - }).build(); - } - - private static void validatePersonPropertyId(SimulationContext simulationContext, PersonPropertyId personPropertyId) { - if (personPropertyId == null) { - throw new ContractException(PersonPropertyError.NULL_PERSON_PROPERTY_ID); - } - - if (!simulationContext.getDataManager(PersonPropertiesDataManager.class).personPropertyIdExists(personPropertyId)) { - throw new ContractException(PersonPropertyError.UNKNOWN_PERSON_PROPERTY_ID); - } - } - - private static void validateRegionId(SimulationContext simulationContext, RegionId regionId) { - if (regionId == null) { - throw new ContractException(RegionError.NULL_REGION_ID); - } - - if (!simulationContext.getDataManager(RegionsDataManager.class).regionIdExists(regionId)) { - throw new ContractException(RegionError.UNKNOWN_REGION_ID); - } - } - - private static void validatePersonId(SimulationContext simulationContext, PersonId personId) { - if (personId == null) { - throw new ContractException(PersonError.NULL_PERSON_ID); - } - - if (!simulationContext.getDataManager(PeopleDataManager.class).personExists(personId)) { - throw new ContractException(PersonError.UNKNOWN_PERSON_ID); - } - } - - /** - * Returns the person property id used to create this event - */ - @Override - public Object getPrimaryKeyValue() { - return personPropertyId; - } - -} diff --git a/gcm3/src/main/java/plugins/personproperties/support/PersonPropertyError.java b/gcm3/src/main/java/plugins/personproperties/support/PersonPropertyError.java deleted file mode 100644 index f67239586..000000000 --- a/gcm3/src/main/java/plugins/personproperties/support/PersonPropertyError.java +++ /dev/null @@ -1,36 +0,0 @@ -package plugins.personproperties.support; - -import util.errors.ContractError; -import util.errors.ContractException; - -/** - * An enumeration supporting {@link ContractException} that acts as a general - * description of the exception. - * - * @author Shawn Hatch - * - */ -public enum PersonPropertyError implements ContractError { - NULL_PERSON_PROPERTY_PLUGN_DATA("Null person property plugin data"),// - NULL_PERSON_PROPERTY_DATA_MANAGER("Null person property data manager"),// - NULL_PERSON_PROPERTY_ID("Null person property id"),// - UNKNOWN_PERSON_ID("Unknow person id"), // - NULL_PERSON_PROPERTY_DEFINITION("Null person property definition"),// - PROPERTY_DEFINITION_REQUIRES_DEFAULT("Person property definition does not have an assigned default value"),// - NULL_PERSON_PROPERTY_VALUE("Null person property value"),// - PROPERTY_ASSIGNMENT_TIME_NOT_TRACKED("Property assignment time not actively tracked"),// - UNKNOWN_PERSON_PROPERTY_ID("Unknown person property id"),// - DUPLICATE_PERSON_PROPERTY_DEFINITION("Duplicate person property definition"),// - DUPLICATE_PERSON_PROPERTY_VALUE_ASSIGNMENT("Duplicate person property value assignment");// - - private final String description; - - private PersonPropertyError(final String description) { - this.description = description; - } - - @Override - public String getDescription() { - return description; - } -} diff --git a/gcm3/src/main/java/plugins/personproperties/support/PersonPropertyId.java b/gcm3/src/main/java/plugins/personproperties/support/PersonPropertyId.java deleted file mode 100644 index 7dd682320..000000000 --- a/gcm3/src/main/java/plugins/personproperties/support/PersonPropertyId.java +++ /dev/null @@ -1,14 +0,0 @@ -package plugins.personproperties.support; - -import net.jcip.annotations.ThreadSafe; - -/** - * Marker interface for person property identifiers - * - * @author Shawn Hatch - * - */ -@ThreadSafe -public interface PersonPropertyId { - -} diff --git a/gcm3/src/main/java/plugins/personproperties/support/PersonPropertyInitialization.java b/gcm3/src/main/java/plugins/personproperties/support/PersonPropertyInitialization.java deleted file mode 100644 index 5a48ec2c8..000000000 --- a/gcm3/src/main/java/plugins/personproperties/support/PersonPropertyInitialization.java +++ /dev/null @@ -1,32 +0,0 @@ -package plugins.personproperties.support; - -public class PersonPropertyInitialization { - private final PersonPropertyId personPropertyId; - private final Object value; - - public PersonPropertyInitialization(PersonPropertyId personPropertyId, Object value) { - super(); - this.personPropertyId = personPropertyId; - this.value = value; - } - - public PersonPropertyId getPersonPropertyId() { - return personPropertyId; - } - - public Object getValue() { - return value; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("PersonPropertyAssignment [personPropertyId="); - builder.append(personPropertyId); - builder.append(", value="); - builder.append(value); - builder.append("]"); - return builder.toString(); - } - -} diff --git a/gcm3/src/main/java/plugins/personproperties/support/PersonPropertyLabeler.java b/gcm3/src/main/java/plugins/personproperties/support/PersonPropertyLabeler.java deleted file mode 100644 index c915b2048..000000000 --- a/gcm3/src/main/java/plugins/personproperties/support/PersonPropertyLabeler.java +++ /dev/null @@ -1,76 +0,0 @@ -package plugins.personproperties.support; - -import java.util.LinkedHashSet; -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; - -import nucleus.Event; -import nucleus.NucleusError; -import nucleus.SimulationContext; -import plugins.partitions.support.Labeler; -import plugins.partitions.support.LabelerSensitivity; -import plugins.people.support.PersonId; -import plugins.personproperties.datamanagers.PersonPropertiesDataManager; -import plugins.personproperties.events.PersonPropertyUpdateEvent; -import util.errors.ContractException; - -/** - * A labeler for person properties. The dimension of the labeler is the given - * {@linkplain PersonPropertyId}, the event that stimulates a label update is - * {@linkplain PersonPropertyUpdateEvent} and the labeling function - * is composed from the given Function. - * - * @author Shawn Hatch - * - */ -public final class PersonPropertyLabeler implements Labeler { - - private final PersonPropertyId personPropertyId; - private final Function personPropertyValueLabelingFunction; - private PersonPropertiesDataManager personPropertiesDataManager; - - public PersonPropertyLabeler(PersonPropertyId personPropertyId, Function personPropertyValueLabelingFunction) { - this.personPropertyId = personPropertyId; - this.personPropertyValueLabelingFunction = personPropertyValueLabelingFunction; - } - - private Optional getPersonId(PersonPropertyUpdateEvent personPropertyUpdateEvent) { - PersonId result = null; - if (personPropertyUpdateEvent.getPersonPropertyId().equals(personPropertyId)) { - result = personPropertyUpdateEvent.getPersonId(); - } - return Optional.ofNullable(result); - } - - @Override - public Set> getLabelerSensitivities() { - Set> result = new LinkedHashSet<>(); - result.add(new LabelerSensitivity(PersonPropertyUpdateEvent.class, this::getPersonId)); - return result; - } - - @Override - public Object getLabel(SimulationContext simulationContext, PersonId personId) { - if(simulationContext == null) { - throw new ContractException(NucleusError.NULL_SIMULATION_CONTEXT); - } - if (personPropertiesDataManager == null) { - personPropertiesDataManager = simulationContext.getDataManager(PersonPropertiesDataManager.class); - } - Object personPropertyValue = personPropertiesDataManager.getPersonPropertyValue(personId, personPropertyId); - return personPropertyValueLabelingFunction.apply(personPropertyValue); - } - - @Override - public Object getDimension() { - return personPropertyId; - } - - @Override - public Object getPastLabel(SimulationContext simulationContext, Event event) { - PersonPropertyUpdateEvent personPropertyUpdateEvent =(PersonPropertyUpdateEvent)event; - return personPropertyValueLabelingFunction.apply(personPropertyUpdateEvent.getPreviousPropertyValue()); - } - -} diff --git a/gcm3/src/main/java/plugins/personproperties/support/PropertyFilter.java b/gcm3/src/main/java/plugins/personproperties/support/PropertyFilter.java deleted file mode 100644 index e023b68e9..000000000 --- a/gcm3/src/main/java/plugins/personproperties/support/PropertyFilter.java +++ /dev/null @@ -1,157 +0,0 @@ -package plugins.personproperties.support; - -import java.util.LinkedHashSet; -import java.util.Optional; -import java.util.Set; - -import nucleus.NucleusError; -import nucleus.SimulationContext; -import plugins.partitions.support.Equality; -import plugins.partitions.support.Filter; -import plugins.partitions.support.FilterSensitivity; -import plugins.partitions.support.PartitionError; -import plugins.people.support.PersonId; -import plugins.personproperties.datamanagers.PersonPropertiesDataManager; -import plugins.personproperties.events.PersonPropertyUpdateEvent; -import plugins.util.properties.PropertyDefinition; -import plugins.util.properties.PropertyError; -import util.errors.ContractException; - -public final class PropertyFilter extends Filter { - - private final PersonPropertyId personPropertyId; - private final Object personPropertyValue; - private final Equality equality; - private PersonPropertiesDataManager personPropertiesDataManager; - - private void validatePersonPropertyId(SimulationContext simulationContext, final PersonPropertyId personPropertyId) { - - if (personPropertiesDataManager == null) { - personPropertiesDataManager = simulationContext.getDataManager(PersonPropertiesDataManager.class); - } - - if (personPropertyId == null) { - throw new ContractException(PersonPropertyError.NULL_PERSON_PROPERTY_ID); - } - - if (!personPropertiesDataManager.personPropertyIdExists(personPropertyId)) { - throw new ContractException(PersonPropertyError.UNKNOWN_PERSON_PROPERTY_ID, personPropertyId); - } - } - - private void validateEquality(SimulationContext simulationContext, final Equality equality) { - if (equality == null) { - throw new ContractException(PartitionError.NULL_EQUALITY_OPERATOR); - } - } - - private void validatePersonPropertyValueNotNull(SimulationContext simulationContext, final Object propertyValue) { - if (propertyValue == null) { - throw new ContractException(PersonPropertyError.NULL_PERSON_PROPERTY_VALUE); - } - } - - private void validateValueCompatibility(SimulationContext simulationContext, final Object propertyId, final PropertyDefinition propertyDefinition, final Object propertyValue) { - if (!propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())) { - throw new ContractException(PropertyError.INCOMPATIBLE_VALUE, "Property value " + propertyValue + " is not of type " + propertyDefinition.getType().getName() + " and does not match definition of " + propertyId); - } - } - - private void validateEqualityCompatibility(SimulationContext simulationContext, final Object propertyId, final PropertyDefinition propertyDefinition, final Equality equality) { - - if (equality == Equality.EQUAL) { - return; - } - if (equality == Equality.NOT_EQUAL) { - return; - } - - if (!Comparable.class.isAssignableFrom(propertyDefinition.getType())) { - throw new ContractException(PartitionError.NON_COMPARABLE_ATTRIBUTE, "Property values for " + propertyId + " are not comparable via " + equality); - } - } - - public PropertyFilter(final PersonPropertyId personPropertyId, final Equality equality, final Object personPropertyValue) { - this.personPropertyId = personPropertyId; - this.personPropertyValue = personPropertyValue; - this.equality = equality; - } - - @Override - public void validate(SimulationContext simulationContext) { - if(simulationContext == null) { - throw new ContractException(NucleusError.NULL_SIMULATION_CONTEXT); - } - if (personPropertiesDataManager == null) { - personPropertiesDataManager = simulationContext.getDataManager(PersonPropertiesDataManager.class); - } - - validatePersonPropertyId(simulationContext, personPropertyId); - validateEquality(simulationContext, equality); - validatePersonPropertyValueNotNull(simulationContext, personPropertyValue); - final PropertyDefinition propertyDefinition = personPropertiesDataManager.getPersonPropertyDefinition(personPropertyId); - validateValueCompatibility(simulationContext, personPropertyId, propertyDefinition, personPropertyValue); - validateEqualityCompatibility(simulationContext, personPropertyId, propertyDefinition, equality); - } - - @Override - public boolean evaluate(SimulationContext simulationContext, PersonId personId) { - - if(simulationContext == null) { - throw new ContractException(NucleusError.NULL_SIMULATION_CONTEXT); - } - if (personPropertiesDataManager == null) { - personPropertiesDataManager = simulationContext.getDataManager(PersonPropertiesDataManager.class); - } - - // we do not assume that the returned property value is - // comparable unless we are forced to. - final Object propVal = personPropertiesDataManager.getPersonPropertyValue(personId, personPropertyId); - - return evaluate(propVal); - - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - private boolean evaluate(Object propVal) { - if (equality.equals(Equality.EQUAL)) { - return propVal.equals(personPropertyValue); - } else if (equality.equals(Equality.NOT_EQUAL)) { - return !propVal.equals(personPropertyValue); - } else { - Comparable comparablePropertyValue = (Comparable) propVal; - int evaluation = comparablePropertyValue.compareTo(personPropertyValue); - return equality.isCompatibleComparisonValue(evaluation); - } - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("PropertyFilter [personPropertyId="); - builder.append(personPropertyId); - builder.append(", personPropertyValue="); - builder.append(personPropertyValue); - builder.append(", equality="); - builder.append(equality); - builder.append("]"); - return builder.toString(); - } - - private Optional requiresRefresh(SimulationContext simulationContext, PersonPropertyUpdateEvent event) { - if (event.getPersonPropertyId().equals(personPropertyId)) { - if (evaluate(event.getPreviousPropertyValue()) != evaluate(event.getCurrentPropertyValue())) { - return Optional.of(event.getPersonId()); - } - } - return Optional.empty(); - } - - @Override - public Set> getFilterSensitivities() { - Set> result = new LinkedHashSet<>(); - result.add(new FilterSensitivity(PersonPropertyUpdateEvent.class, this::requiresRefresh)); - return result; - } - -} diff --git a/gcm3/src/main/java/plugins/personproperties/testsupport/PersonPropertiesActionSupport.java b/gcm3/src/main/java/plugins/personproperties/testsupport/PersonPropertiesActionSupport.java deleted file mode 100644 index 27a7fd806..000000000 --- a/gcm3/src/main/java/plugins/personproperties/testsupport/PersonPropertiesActionSupport.java +++ /dev/null @@ -1,109 +0,0 @@ -package plugins.personproperties.testsupport; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -import org.apache.commons.math3.random.RandomGenerator; - -import nucleus.ActorContext; -import nucleus.Plugin; -import nucleus.Simulation; -import nucleus.Simulation.Builder; -import nucleus.testsupport.testplugin.ScenarioPlanCompletionObserver; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestError; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import plugins.people.PeoplePlugin; -import plugins.people.PeoplePluginData; -import plugins.people.support.PersonId; -import plugins.personproperties.PersonPropertiesPlugin; -import plugins.personproperties.PersonPropertiesPluginData; -import plugins.regions.RegionsPlugin; -import plugins.regions.RegionsPluginData; -import plugins.regions.testsupport.TestRegionId; -import plugins.stochastics.StochasticsPlugin; -import plugins.stochastics.StochasticsPluginData; -import util.errors.ContractException; -import util.random.RandomGeneratorProvider; - -public class PersonPropertiesActionSupport { - - public static void testConsumer(int initialPopulation, long seed, Consumer consumer) { - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, consumer)); - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - testConsumers(initialPopulation, seed, testPlugin); - } - - public static void testConsumers(int initialPopulation, long seed, Plugin testPlugin) { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); - - List people = new ArrayList<>(); - for (int i = 0; i < initialPopulation; i++) { - people.add(new PersonId(i)); - - } - Builder builder = Simulation.builder(); - - // add the person property plugin - PersonPropertiesPluginData.Builder personPropertyBuilder = PersonPropertiesPluginData.builder(); - for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { - personPropertyBuilder.definePersonProperty(testPersonPropertyId, testPersonPropertyId.getPropertyDefinition()); - } - for (PersonId personId : people) { - for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { - Object randomPropertyValue = testPersonPropertyId.getRandomPropertyValue(randomGenerator); - personPropertyBuilder.setPersonPropertyValue(personId,testPersonPropertyId, randomPropertyValue); - } - } - PersonPropertiesPluginData personPropertiesPluginData = personPropertyBuilder.build(); - Plugin personPropertyPlugin = PersonPropertiesPlugin.getPersonPropertyPlugin(personPropertiesPluginData); - builder.addPlugin(personPropertyPlugin); - - // add the regions plugin - RegionsPluginData.Builder regionBuilder = RegionsPluginData.builder(); - // add the regions - for (TestRegionId testRegionId : TestRegionId.values()) { - regionBuilder.addRegion(testRegionId); - } - for (PersonId personId : people) { - TestRegionId randomRegionId = TestRegionId.getRandomRegionId(randomGenerator); - regionBuilder.setPersonRegion(personId, randomRegionId); - } - RegionsPluginData regionsPluginData = regionBuilder.build(); - Plugin regionPlugin = RegionsPlugin.getRegionsPlugin(regionsPluginData); - builder.addPlugin(regionPlugin); - - // add the stochastics plugin - StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder().setSeed(seed).build(); - Plugin stochasticPlugin = StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData); - builder.addPlugin(stochasticPlugin); - - // add the people plugin - PeoplePluginData.Builder peopleBuilder = PeoplePluginData.builder(); - for (PersonId personId : people) { - peopleBuilder.addPersonId(personId); - } - PeoplePluginData peoplePluginData = peopleBuilder.build(); - Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(peoplePluginData); - builder.addPlugin(peoplePlugin); - - // add the test plugin - builder.addPlugin(testPlugin); - - // build and execute the engine - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - builder.setOutputConsumer(scenarioPlanCompletionObserver::handleOutput).build().execute(); - - // show that all actions were executed - if (!scenarioPlanCompletionObserver.allPlansExecuted()) { - throw new ContractException(TestError.TEST_EXECUTION_FAILURE); - } - - } -} diff --git a/gcm3/src/main/java/plugins/personproperties/testsupport/TestPersonPropertyId.java b/gcm3/src/main/java/plugins/personproperties/testsupport/TestPersonPropertyId.java deleted file mode 100644 index 04928c4ea..000000000 --- a/gcm3/src/main/java/plugins/personproperties/testsupport/TestPersonPropertyId.java +++ /dev/null @@ -1,140 +0,0 @@ -package plugins.personproperties.testsupport; - -import org.apache.commons.math3.random.RandomGenerator; - -import plugins.personproperties.support.PersonPropertyId; -import plugins.util.properties.PropertyDefinition; -import plugins.util.properties.TimeTrackingPolicy; - -/** - * Enumeration that identifies person property definitions - */ -public enum TestPersonPropertyId implements PersonPropertyId { - PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK( - PropertyDefinition .builder()// - .setType(Boolean.class)// - .setDefaultValue(false)// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.DO_NOT_TRACK_TIME)// - .build()), // - PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK( - PropertyDefinition .builder()// - .setType(Integer.class)// - .setDefaultValue(0)// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.DO_NOT_TRACK_TIME)// - .build() // - ), // - PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK( - PropertyDefinition .builder()// - .setType(Double.class)// - .setDefaultValue(0.0)// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.DO_NOT_TRACK_TIME)// - .build() // - ), // - PERSON_PROPERTY_4_BOOLEAN_MUTABLE_TRACK( - PropertyDefinition .builder()// - .setType(Boolean.class)// - .setDefaultValue(false)// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME)// - .build() // - ), // - PERSON_PROPERTY_5_INTEGER_MUTABLE_TRACK( - PropertyDefinition .builder()// - .setType(Integer.class)// - .setDefaultValue(0)// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME)// - .build() // - ), // - PERSON_PROPERTY_6_DOUBLE_MUTABLE_TRACK( - PropertyDefinition .builder()// - .setType(Double.class)// - .setDefaultValue(0.0)// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME)// - .build() // - ), // - PERSON_PROPERTY_7_BOOLEAN_IMMUTABLE_NO_TRACK( - PropertyDefinition .builder()// - .setType(Boolean.class)// - .setDefaultValue(false)// - .setPropertyValueMutability(false)// - .setTimeTrackingPolicy(TimeTrackingPolicy.DO_NOT_TRACK_TIME)// - .build() // - ), // - PERSON_PROPERTY_8_INTEGER_IMMUTABLE_NO_TRACK( - PropertyDefinition .builder()// - .setType(Integer.class)// - .setDefaultValue(0)// - .setPropertyValueMutability(false)// - .setTimeTrackingPolicy(TimeTrackingPolicy.DO_NOT_TRACK_TIME)// - .build() // - ), // - PERSON_PROPERTY_9_DOUBLE_IMMUTABLE_NO_TRACK( - PropertyDefinition .builder()// - .setType(Double.class)// - .setDefaultValue(0.0)// - .setPropertyValueMutability(false)// - .setTimeTrackingPolicy(TimeTrackingPolicy.DO_NOT_TRACK_TIME)// - .build() // - );// - - /** - * Returns a randomly selected member of this enumeration - */ - public static TestPersonPropertyId getRandomPersonPropertyId(final RandomGenerator randomGenerator) { - return TestPersonPropertyId.values()[randomGenerator.nextInt(TestPersonPropertyId.values().length)]; - } - - /** - * Returns a randomly selected value that is compatible with this member's - * associated property definition. - * - */ - public Object getRandomPropertyValue(final RandomGenerator randomGenerator) { - switch (this) { - case PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK: - return randomGenerator.nextBoolean(); - case PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK: - return randomGenerator.nextInt(); - case PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK: - return randomGenerator.nextDouble(); - case PERSON_PROPERTY_4_BOOLEAN_MUTABLE_TRACK: - return randomGenerator.nextBoolean(); - case PERSON_PROPERTY_5_INTEGER_MUTABLE_TRACK: - return randomGenerator.nextInt(); - case PERSON_PROPERTY_6_DOUBLE_MUTABLE_TRACK: - return randomGenerator.nextDouble(); - case PERSON_PROPERTY_7_BOOLEAN_IMMUTABLE_NO_TRACK: - return randomGenerator.nextBoolean(); - case PERSON_PROPERTY_8_INTEGER_IMMUTABLE_NO_TRACK: - return randomGenerator.nextInt(); - case PERSON_PROPERTY_9_DOUBLE_IMMUTABLE_NO_TRACK: - return randomGenerator.nextDouble(); - default: - throw new RuntimeException("unhandled case: " + this); - - } - } - - private final PropertyDefinition propertyDefinition; - - private TestPersonPropertyId(PropertyDefinition propertyDefinition) { - this.propertyDefinition = propertyDefinition; - } - - public PropertyDefinition getPropertyDefinition() { - return propertyDefinition; - } - - /** - * Returns a new {@link PersonPropertyId} instance. - */ - public static PersonPropertyId getUnknownPersonPropertyId() { - return new PersonPropertyId() { - }; - } -} \ No newline at end of file diff --git a/gcm3/src/main/java/plugins/regions/RegionsPlugin.java b/gcm3/src/main/java/plugins/regions/RegionsPlugin.java deleted file mode 100644 index eca676816..000000000 --- a/gcm3/src/main/java/plugins/regions/RegionsPlugin.java +++ /dev/null @@ -1,26 +0,0 @@ -package plugins.regions; - -import nucleus.Plugin; -import plugins.people.PeoplePluginId; -import plugins.regions.datamanagers.RegionsDataManager; - -public final class RegionsPlugin { - - private RegionsPlugin() { - } - - public static Plugin getRegionsPlugin(RegionsPluginData regionsPluginData) { - - return Plugin .builder()// - .addPluginData(regionsPluginData)// - .setPluginId(RegionsPluginId.PLUGIN_ID)// - .addPluginDependency(PeoplePluginId.PLUGIN_ID)// - .setInitializer((c) -> { - RegionsPluginData pluginData = c.getPluginData(RegionsPluginData.class); - c.addDataManager(new RegionsDataManager(pluginData)); - })// - .build(); - - } - -} diff --git a/gcm3/src/main/java/plugins/regions/RegionsPluginData.java b/gcm3/src/main/java/plugins/regions/RegionsPluginData.java deleted file mode 100644 index ff06a3a75..000000000 --- a/gcm3/src/main/java/plugins/regions/RegionsPluginData.java +++ /dev/null @@ -1,492 +0,0 @@ -package plugins.regions; - -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -import net.jcip.annotations.Immutable; -import nucleus.PluginData; -import nucleus.PluginDataBuilder; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import plugins.regions.support.RegionError; -import plugins.regions.support.RegionId; -import plugins.regions.support.RegionPropertyId; -import plugins.util.properties.PropertyDefinition; -import plugins.util.properties.PropertyError; -import plugins.util.properties.TimeTrackingPolicy; -import util.errors.ContractException; - -/** - * An immutable container of the initial state of regions. It contains:
    - *
      - *
    • region ids
    • - *
    • suppliers of consumers of {@linkplain AgentContext} for region - * initialization
    • - *
    • region property definitions: all regions share a set of property - * definitions with default values, but have individual property values
    • - *
    • region property values
    • - *
    • person region assignments
    • - *
    • person region arrival time tracking policy
    • - *
    - * - * @author Shawn Hatch - * - */ - -@Immutable -public class RegionsPluginData implements PluginData { - - private static class Data { - - private final Map regionPropertyDefinitions = new LinkedHashMap<>(); - - private final Set regionIds = new LinkedHashSet<>(); - - private TimeTrackingPolicy regionArrivalTimeTrackingPolicy; - - private final Map> regionPropertyValues = new LinkedHashMap<>(); - - private final Map personRegions = new LinkedHashMap<>(); - - public Data() { - } - - public Data(Data data) { - regionPropertyDefinitions.putAll(data.regionPropertyDefinitions); - regionIds.addAll(data.regionIds); - regionArrivalTimeTrackingPolicy = data.regionArrivalTimeTrackingPolicy; - for (RegionId regionId : data.regionPropertyValues.keySet()) { - Map map = new LinkedHashMap<>(data.regionPropertyValues.get(regionId)); - regionPropertyValues.put(regionId, map); - } - personRegions.putAll(data.personRegions); - - } - } - - private static void validateRegionExists(final Data data, final RegionId regionId) { - if (regionId == null) { - throw new ContractException(RegionError.NULL_REGION_ID); - } - if (!data.regionIds.contains(regionId)) { - throw new ContractException(RegionError.UNKNOWN_REGION_ID, regionId); - } - } - - public static Builder builder() { - return new Builder(new Data()); - } - - private static void validateData(Data data) { - - for (PersonId personId : data.personRegions.keySet()) { - RegionId regionId = data.personRegions.get(personId); - if (!data.regionIds.contains(regionId)) { - throw new ContractException(RegionError.UNKNOWN_REGION_ID, regionId + " in person region assignments"); - } - } - - for (RegionId regionId : data.regionPropertyValues.keySet()) { - if (!data.regionIds.contains(regionId)) { - throw new ContractException(RegionError.UNKNOWN_REGION_ID, regionId + " in region property values"); - } - Map map = data.regionPropertyValues.get(regionId); - if (map != null) { - for (RegionPropertyId regionPropertyId : map.keySet()) { - PropertyDefinition propertyDefinition = data.regionPropertyDefinitions.get(regionPropertyId); - if (propertyDefinition == null) { - throw new ContractException(RegionError.UNKNOWN_REGION_PROPERTY_ID, regionPropertyId + " for region " + regionId); - } - Object propertyValue = map.get(regionPropertyId); - if (!propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())) { - throw new ContractException(PropertyError.INCOMPATIBLE_VALUE, regionId + ":" + regionPropertyId + " = " + propertyValue); - } - } - } - } - - /* - * For every region property definition that has a null default value, - * ensure that there all corresponding region property values are not - * null and repair the definition. - */ - for (RegionPropertyId regionPropertyId : data.regionPropertyDefinitions.keySet()) { - PropertyDefinition propertyDefinition = data.regionPropertyDefinitions.get(regionPropertyId); - if (!propertyDefinition.getDefaultValue().isPresent()) { - for (RegionId regionId : data.regionIds) { - Object propertyValue = null; - Map propertyValueMap = data.regionPropertyValues.get(regionId); - if (propertyValueMap != null) { - propertyValue = propertyValueMap.get(regionPropertyId); - } - if (propertyValue == null) { - throw new ContractException(RegionError.INSUFFICIENT_REGION_PROPERTY_VALUE_ASSIGNMENT, regionPropertyId); - } - } - } - } - - } - - private static void validateTimeTrackingPolicyNotNull(TimeTrackingPolicy timeTrackingPolicy) { - if (timeTrackingPolicy == null) { - throw new ContractException(RegionError.NULL_TIME_TRACKING_POLICY); - } - } - - private static void validatePersonRegionArrivalTrackingNotSet(Data data) { - if (data.regionArrivalTimeTrackingPolicy != null) { - throw new ContractException(RegionError.DUPLICATE_TIME_TRACKING_POLICY); - } - } - - private static void validateRegionPropertyValueNotSet(final Data data, final RegionId regionId, final RegionPropertyId regionPropertyId) { - final Map propertyMap = data.regionPropertyValues.get(regionId); - if (propertyMap != null) { - if (propertyMap.containsKey(regionPropertyId)) { - throw new ContractException(RegionError.DUPLICATE_REGION_PROPERTY_VALUE, regionPropertyId + " = " + regionId); - } - } - } - - - private static void validateRegionIdNotNull(RegionId regionId) { - if (regionId == null) { - throw new ContractException(RegionError.NULL_REGION_ID); - } - } - - private static void validateRegionPropertyIdNotNull(RegionPropertyId regionPropertyId) { - if (regionPropertyId == null) { - throw new ContractException(RegionError.NULL_REGION_PROPERTY_ID); - } - } - - private static void validateRegionPropertyDefinitionNotNull(PropertyDefinition propertyDefinition) { - if (propertyDefinition == null) { - throw new ContractException(RegionError.NULL_REGION_PROPERTY_DEFINITION); - } - - } - - private static void validateRegionPropertyIsDefined(Data data, RegionPropertyId regionPropertyId) { - - if (!data.regionPropertyDefinitions.containsKey(regionPropertyId)) { - throw new ContractException(RegionError.UNKNOWN_REGION_PROPERTY_ID); - } - } - - private static void validateRegionPropertyIsNotDefined(Data data, RegionPropertyId regionPropertyId) { - if (data.regionPropertyDefinitions.containsKey(regionPropertyId)) { - throw new ContractException(RegionError.DUPLICATE_REGION_PROPERTY_DEFINITION_ASSIGNMENT); - } - } - - public static class Builder implements PluginDataBuilder { - private Data data; - - private Builder(Data data) { - this.data = data; - } - - /** - * Returns the {@link RegionInitialData} from the collected information - * supplied to this builder. - * - * @throws ContractException - * - *
  • {@linkplain RegionError#UNKNOWN_REGION_ID}
  • if a - * region property value was associated with a region id - * that was not properly added with an initial agent - * behavior. - * - *
  • {@linkplain RegionError#UNKNOWN_REGION_PROPERTY_ID}
  • - * if a region property value was associated with a region - * property id that was not defined - * - *
  • {@linkplain PropertyError#INCOMPATIBLE_VALUE}
  • if - * a region property value was associated with a region and - * region property id that is incompatible with the - * corresponding property definition. - * - *
  • {@linkplain RegionError#INSUFFICIENT_REGION_PROPERTY_VALUE_ASSIGNMENT}
  • - * if a region property definition does not have a default - * value and there are no property values added to replace - * that default. - * - */ - public RegionsPluginData build() { - try { - if (data.regionArrivalTimeTrackingPolicy == null) { - data.regionArrivalTimeTrackingPolicy = TimeTrackingPolicy.DO_NOT_TRACK_TIME; - } - validateData(data); - return new RegionsPluginData(data); - } finally { - data = new Data(); - } - } - - /** - * Sets the region property value that overrides the default value of - * the corresponding property definition - * - * @throws ContractException - * - *
  • {@linkplain RegionError#NULL_REGION_ID}
  • if the - * region id is null - * - *
  • {@linkplain RegionError#NULL_REGION_PROPERTY_ID} - *
  • if the region property id is null - * - *
  • {@linkplain RegionError#DUPLICATE_REGION_PROPERTY_VALUE} - *
  • if the region property value was previously defined - * - */ - public Builder setRegionPropertyValue(final RegionId regionId, final RegionPropertyId regionPropertyId, final Object regionPropertyValue) { - validateRegionIdNotNull(regionId); - validateRegionPropertyIdNotNull(regionPropertyId); - validateRegionPropertyValueNotSet(data, regionId, regionPropertyId); - Map propertyMap = data.regionPropertyValues.get(regionId); - if (propertyMap == null) { - propertyMap = new LinkedHashMap<>(); - data.regionPropertyValues.put(regionId, propertyMap); - } - propertyMap.put(regionPropertyId, regionPropertyValue); - return this; - } - - /** - * Sets the person's region - * - * @throws ContractException - * - *
  • {@linkplain PersonError#NULL_PERSON_ID}
  • if the - * person id is null - * - *
  • {@linkplain RegionError#NULL_REGION_ID}
  • if the - * region id is null - * - *
  • {@linkplain RegionError#DUPLICATE_PERSON_REGION_ASSIGNMENT} - *
  • if the person's region was previously defined - * - */ - public Builder setPersonRegion(final PersonId personId, final RegionId regionId) { - validatePersonIdNotNull(personId); - validateRegionIdNotNull(regionId); - validatePersonRegionNotAssigned(data, personId); - data.personRegions.put(personId, regionId); - return this; - } - - - /** - * Sets the tracking policy for region arrival times - * - * @throws ContractException - * - *
  • {@linkplain RegionError#NULL_TIME_TRACKING_POLICY}
  • if - * the timeTrackingPolicy is null - * - *
  • {@linkplain RegionError#DUPLICATE_TIME_TRACKING_POLICY} - *
  • if the timeTrackingPolicy was previously defined - * - */ - public Builder setPersonRegionArrivalTracking(final TimeTrackingPolicy timeTrackingPolicy) { - validateTimeTrackingPolicyNotNull(timeTrackingPolicy); - validatePersonRegionArrivalTrackingNotSet(data); - data.regionArrivalTimeTrackingPolicy = timeTrackingPolicy; - return this; - } - - - - /** - * Adds the region id and its associated agent initial behavior. - * - * @throws ContractException - * - *
  • {@linkplain RegionError#NULL_REGION_ID}
  • if the - * region id is null - */ - public Builder addRegion(final RegionId regionId) { - validateRegionIdNotNull(regionId); - data.regionIds.add(regionId); - return this; - } - - /** - * Defines a region property - * - * @throws ContractException - * - *
  • {@linkplain RegionError#NULL_REGION_PROPERTY_ID}
  • - * if the region property id is null - * - *
  • {@linkplain RegionError#NULL_REGION_PROPERTY_DEFINITION} - *
  • if the property definition is null - * - *
  • {@linkplain RegionError#DUPLICATE_REGION_PROPERTY_DEFINITION_ASSIGNMENT} - *
  • if a property definition for the given property id - * was previously defined. - * - */ - public Builder defineRegionProperty(final RegionPropertyId regionPropertyId, final PropertyDefinition propertyDefinition) { - validateRegionPropertyIdNotNull(regionPropertyId); - validateRegionPropertyDefinitionNotNull(propertyDefinition); - validateRegionPropertyIsNotDefined(data, regionPropertyId); - data.regionPropertyDefinitions.put(regionPropertyId, propertyDefinition); - return this; - } - - } - - private final Data data; - - private RegionsPluginData(Data data) { - this.data = data; - } - - /** - * Returns the {@link PropertyDefinition} for the given - * {@link RegionPropertyId}. - * - * @throws ContractException - * - *
  • {@linkplain RegionError#NULL_REGION_PROPERTY_ID}
  • if - * the region property id is null - *
  • {@linkplain RegionError#UNKNOWN_REGION_PROPERTY_ID}
  • - * if the region property id is known - */ - public PropertyDefinition getRegionPropertyDefinition(final RegionPropertyId regionPropertyId) { - validateRegionPropertyIdNotNull(regionPropertyId); - validateRegionPropertyIsDefined(data, regionPropertyId); - return data.regionPropertyDefinitions.get(regionPropertyId); - } - - /** - * Returns the set of {@link RegionPropertyId} - * - */ - @SuppressWarnings("unchecked") - public Set getRegionPropertyIds() { - Set result = new LinkedHashSet<>(); - for (RegionPropertyId regionPropertyId : data.regionPropertyDefinitions.keySet()) { - result.add((T) regionPropertyId); - } - return result; - } - - /** - * Returns the property value for the given {@link RegionId} and - * {@link RegionPropertyId}. - * - * @throws ContractException - * - *
  • {@linkplain RegionError#NULL_REGION_ID}
  • if the - * region id is null - *
  • {@linkplain RegionError#UNKNOWN_REGION_ID}
  • if the - * region id is unknown - *
  • {@linkplain RegionError#NULL_REGION_PROPERTY_ID}
  • if - * the region property id is null - *
  • {@linkplain RegionError#UNKNOWN_REGION_PROPERTY_ID}
  • - * if the region property id is known - */ - @SuppressWarnings("unchecked") - public T getRegionPropertyValue(final RegionId regionId, final RegionPropertyId regionPropertyId) { - validateRegionExists(data, regionId); - validateRegionPropertyIdNotNull(regionPropertyId); - validateRegionPropertyIsDefined(data, regionPropertyId); - Object result = null; - final Map map = data.regionPropertyValues.get(regionId); - if (map != null) { - result = map.get(regionPropertyId); - } - if (result == null) { - final PropertyDefinition propertyDefinition = data.regionPropertyDefinitions.get(regionPropertyId); - /* - * we verified earlier that every region property either has a value - * or has a property definition that has a default value - */ - result = propertyDefinition.getDefaultValue().get(); - } - return (T) result; - } - - - /** - * Returns the set of {@link RegionId} values contained in this initial - * data. Each region id will correspond to a region agent that is - * automatically added to the simulation during initialization. - */ - @SuppressWarnings("unchecked") - public Set getRegionIds() { - Set result = new LinkedHashSet<>(); - for(RegionId regionId : data.regionIds) { - result.add((T)regionId); - } - return result; - } - - /** - * Returns the {@link TimeTrackingPolicy}. Defaulted to - * {@link TimeTrackingPolicy#DO_NOT_TRACK_TIME} if not set in the builder. - * - */ - public TimeTrackingPolicy getPersonRegionArrivalTrackingPolicy() { - return data.regionArrivalTimeTrackingPolicy; - } - - @Override - public PluginDataBuilder getCloneBuilder() { - return new Builder(new Data(data)); - } - - private static void validatePersonIdNotNull(PersonId personId) { - if (personId == null) { - throw new ContractException(PersonError.NULL_PERSON_ID); - } - } - - private static void validatePersonRegionNotAssigned(Data data, PersonId personId) { - if (data.personRegions.containsKey(personId)) { - throw new ContractException(RegionError.DUPLICATE_PERSON_REGION_ASSIGNMENT, personId); - } - - } - - /** - * Returns the set of {@link PersonId} collected by the builder. - */ - public Set getPersonIds() { - return new LinkedHashSet<>(data.personRegions.keySet()); - } - - /** - * Returns the {@link RegionId} for the given {@link PersonId}. - * - * @throws ContractException - * - *
  • {@linkplain PersonError#NULL_PERSON_ID}
  • if the - * person id is null - *
  • {@linkplain PersonError#UNKNOWN_PERSON_ID}
  • if the - * person id is unknown - */ - @SuppressWarnings("unchecked") - public T getPersonRegion(final PersonId personId) { - validatePersonExists(data, personId); - return (T) data.personRegions.get(personId); - } - - private static void validatePersonExists(final Data data, final PersonId personId) { - if (personId == null) { - throw new ContractException(PersonError.NULL_PERSON_ID); - } - if (!data.personRegions.containsKey(personId)) { - throw new ContractException(PersonError.UNKNOWN_PERSON_ID, personId); - } - } - -} diff --git a/gcm3/src/main/java/plugins/regions/RegionsPluginId.java b/gcm3/src/main/java/plugins/regions/RegionsPluginId.java deleted file mode 100644 index 3de66f763..000000000 --- a/gcm3/src/main/java/plugins/regions/RegionsPluginId.java +++ /dev/null @@ -1,14 +0,0 @@ -package plugins.regions; - -import nucleus.PluginId; -/** - * Static plugin id implementation for the GlobalsPlugin - * - * @author Shawn Hatch - * - */ - -public final class RegionsPluginId implements PluginId { - public final static PluginId PLUGIN_ID = new RegionsPluginId(); - private RegionsPluginId() {}; -} diff --git a/gcm3/src/main/java/plugins/regions/actors/RegionPropertyReport.java b/gcm3/src/main/java/plugins/regions/actors/RegionPropertyReport.java deleted file mode 100644 index 4bc311850..000000000 --- a/gcm3/src/main/java/plugins/regions/actors/RegionPropertyReport.java +++ /dev/null @@ -1,137 +0,0 @@ -package plugins.regions.actors; - -import java.util.LinkedHashSet; -import java.util.Set; - -import nucleus.ActorContext; -import nucleus.EventLabel; -import plugins.regions.datamanagers.RegionsDataManager; -import plugins.regions.events.RegionPropertyUpdateEvent; -import plugins.regions.support.RegionError; -import plugins.regions.support.RegionId; -import plugins.regions.support.RegionPropertyId; -import plugins.reports.support.ReportHeader; -import plugins.reports.support.ReportId; -import plugins.reports.support.ReportItem; -import util.errors.ContractException; - -/** - * A Report that displays assigned region property values over time. - * - * - * Fields - * - * Time -- the time in days when the region property was set - * - * Region -- the region identifier - * - * Property -- the region property identifier - * - * Value -- the value of the region property - * - * @author Shawn Hatch - * - */ -public final class RegionPropertyReport { - - private ReportHeader reportHeader; - - /* - * The constrained set of person properties that will be used in this - * report. They are set during init() - */ - private final Set regionPropertyIds = new LinkedHashSet<>(); - - private ReportHeader getReportHeader() { - if (reportHeader == null) { - reportHeader = ReportHeader .builder()// - .add("Time")// - .add("Region")// - .add("Property")// - .add("Value")// - .build();// - } - return reportHeader; - } - - private void handleRegionPropertyUpdateEvent(ActorContext actorContext, RegionPropertyUpdateEvent regionPropertyUpdateEvent) { - RegionId regionId = regionPropertyUpdateEvent.getRegionId(); - RegionPropertyId regionPropertyId = regionPropertyUpdateEvent.getRegionPropertyId(); - Object propertyValue = regionPropertyUpdateEvent.getCurrentPropertyValue(); - if (regionPropertyIds.contains(regionPropertyId)) { - writeProperty(actorContext, regionId, regionPropertyId, propertyValue); - } - } - - private final ReportId reportId; - - public RegionPropertyReport(ReportId reportId, RegionPropertyId... regionPropertyIds) { - this.reportId = reportId; - for (RegionPropertyId regionPropertyId : regionPropertyIds) { - this.regionPropertyIds.add(regionPropertyId); - } - } - - /** - * Initial behavior for this report. The report subscribes to - * {@linkplain RegionPropertyUpdateEvent} and releases a {@link ReportItem} - * for each region property's initial value. - * - * @throws ContractException - * - *
  • {@linkplain RegionError#UNKNOWN_REGION_PROPERTY_ID} if a - * region property id used in the constructor is unknown
  • - */ - public void init(final ActorContext actorContext) { - RegionsDataManager regionsDataManager = actorContext.getDataManager(RegionsDataManager.class); - - /* - * If no region properties were specified, then assume all are wanted - */ - if (regionPropertyIds.size() == 0) { - regionPropertyIds.addAll(regionsDataManager.getRegionPropertyIds()); - } - - /* - * Ensure that every client supplied property identifier is valid - */ - final Set validPropertyIds = regionsDataManager.getRegionPropertyIds(); - for (final RegionPropertyId regionPropertyId : regionPropertyIds) { - if (!validPropertyIds.contains(regionPropertyId)) { - - throw new ContractException(RegionError.UNKNOWN_REGION_PROPERTY_ID, regionPropertyId); - } - } - - if (regionPropertyIds.equals(regionsDataManager.getRegionPropertyIds())) { - actorContext.subscribe(RegionPropertyUpdateEvent.class, this::handleRegionPropertyUpdateEvent); - } else { - for (RegionPropertyId regionPropertyId : regionPropertyIds) { - EventLabel eventLabelByProperty = RegionPropertyUpdateEvent.getEventLabelByProperty(actorContext, regionPropertyId); - actorContext.subscribe(eventLabelByProperty, this::handleRegionPropertyUpdateEvent); - } - } - - for (final RegionId regionId : regionsDataManager.getRegionIds()) { - for (final RegionPropertyId regionPropertyId : regionPropertyIds) { - Object propertyValue = regionsDataManager.getRegionPropertyValue(regionId, regionPropertyId); - writeProperty(actorContext, regionId, regionPropertyId, propertyValue); - } - } - - } - - private void writeProperty(ActorContext actorContext, final RegionId regionId, final RegionPropertyId regionPropertyId, final Object regionPropertyValue) { - - final ReportItem.Builder reportItemBuilder = ReportItem.builder(); - reportItemBuilder.setReportHeader(getReportHeader()); - reportItemBuilder.setReportId(reportId); - - reportItemBuilder.addValue(actorContext.getTime()); - reportItemBuilder.addValue(regionId.toString()); - reportItemBuilder.addValue(regionPropertyId.toString()); - reportItemBuilder.addValue(regionPropertyValue); - actorContext.releaseOutput(reportItemBuilder.build()); - } - -} \ No newline at end of file diff --git a/gcm3/src/main/java/plugins/regions/actors/RegionTransferReport.java b/gcm3/src/main/java/plugins/regions/actors/RegionTransferReport.java deleted file mode 100644 index 722a14363..000000000 --- a/gcm3/src/main/java/plugins/regions/actors/RegionTransferReport.java +++ /dev/null @@ -1,154 +0,0 @@ -package plugins.regions.actors; - -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; - -import nucleus.ActorContext; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.events.PersonAdditionEvent; -import plugins.people.support.PersonId; -import plugins.regions.datamanagers.RegionsDataManager; -import plugins.regions.events.PersonRegionUpdateEvent; -import plugins.regions.support.RegionId; -import plugins.reports.support.PeriodicReport; -import plugins.reports.support.ReportHeader; -import plugins.reports.support.ReportId; -import plugins.reports.support.ReportItem; -import plugins.reports.support.ReportPeriod; - -/** - * A periodic Report that displays the number of times a person transferred from - * one region to another. Only non-zero transfers are reported. - * - * - * Fields - * - * - * SourceRegion -- the source region identifier - * - * DestinationRegion -- the destination region property identifier - * - * Transfers -- the number of transfers from the source region to the - * destination region - * - * @author Shawn Hatch - * - */ -public final class RegionTransferReport extends PeriodicReport { - - public RegionTransferReport(ReportId reportId, ReportPeriod reportPeriod) { - super(reportId, reportPeriod); - } - - /* - * - * A counter of the number of people transferring between regions. - * - */ - private static class Counter { - int count; - } - - /* - * A mapping from a (Region, Region) tuple to a count of the number of - * transfers. - */ - private final Map> baseMap = new LinkedHashMap<>(); - - /* - * The derived header for this report - */ - private ReportHeader reportHeader; - - private ReportHeader getReportHeader() { - if (reportHeader == null) { - ReportHeader.Builder reportHeaderBuilder = ReportHeader.builder(); - reportHeader = addTimeFieldHeaders(reportHeaderBuilder)// - - .add("source_region")// - .add("destination_region")// - .add("transfers")// - .build();// - } - return reportHeader; - } - - @Override - protected void flush(ActorContext ActorContext) { - - final ReportItem.Builder reportItemBuilder = ReportItem.builder(); - - for (final RegionId sourceRegionId : baseMap.keySet()) { - final Map destinationRegionMap = baseMap.get(sourceRegionId); - for (final RegionId destinationRegionId : destinationRegionMap.keySet()) { - final Counter counter = destinationRegionMap.get(destinationRegionId); - if (counter.count > 0) { - reportItemBuilder.setReportHeader(getReportHeader()); - reportItemBuilder.setReportId(getReportId()); - fillTimeFields(reportItemBuilder); - reportItemBuilder.addValue(sourceRegionId.toString()); - reportItemBuilder.addValue(destinationRegionId.toString()); - reportItemBuilder.addValue(counter.count); - ActorContext.releaseOutput(reportItemBuilder.build()); - counter.count = 0; - } - } - - } - } - - private void handlePersonAdditionEvent(ActorContext ActorContext, PersonAdditionEvent personAdditionEvent) { - PersonId personId = personAdditionEvent.getPersonId(); - final RegionId regionId = regionsDataManager.getPersonRegion(personId); - increment(regionId, regionId); - } - - private void handlePersonRegionUpdateEvent(ActorContext ActorContext, PersonRegionUpdateEvent personRegionUpdateEvent) { - RegionId previousRegionId = personRegionUpdateEvent.getPreviousRegionId(); - RegionId currentRegionId = personRegionUpdateEvent.getCurrentRegionId(); - increment(previousRegionId, currentRegionId); - } - - /* - * Increments the number of region transfers for the give tuple - */ - private void increment(final RegionId sourceRegionId, final RegionId destinationRegionId) { - final Counter counter = baseMap.get(sourceRegionId).get(destinationRegionId); - counter.count++; - } - - private RegionsDataManager regionsDataManager; - - @Override - public void init(final ActorContext ActorContext) { - super.init(ActorContext); - - ActorContext.subscribe(PersonAdditionEvent.class, this::handlePersonAdditionEvent); - ActorContext.subscribe(PersonRegionUpdateEvent.class, this::handlePersonRegionUpdateEvent); - - PeopleDataManager peopleDataManager = ActorContext.getDataManager(PeopleDataManager.class); - regionsDataManager = ActorContext.getDataManager(RegionsDataManager.class); - RegionsDataManager regionsDataManager = ActorContext.getDataManager(RegionsDataManager.class); - - final Set regionIds = regionsDataManager.getRegionIds(); - - /* - * Fill the base map with empty counters - */ - - for (final RegionId sourceRegionId : regionIds) { - final Map destinationRegionMap = new LinkedHashMap<>(); - baseMap.put(sourceRegionId, destinationRegionMap); - for (final RegionId destinationRegionId : regionIds) { - final Counter counter = new Counter(); - destinationRegionMap.put(destinationRegionId, counter); - } - } - - for (PersonId personId : peopleDataManager.getPeople()) { - final RegionId regionId = regionsDataManager.getPersonRegion(personId); - increment(regionId, regionId); - } - } -} \ No newline at end of file diff --git a/gcm3/src/main/java/plugins/regions/datamanagers/RegionsDataManager.java b/gcm3/src/main/java/plugins/regions/datamanagers/RegionsDataManager.java deleted file mode 100644 index 2c3fb3a4d..000000000 --- a/gcm3/src/main/java/plugins/regions/datamanagers/RegionsDataManager.java +++ /dev/null @@ -1,752 +0,0 @@ -package plugins.regions.datamanagers; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; - -import nucleus.DataManager; -import nucleus.DataManagerContext; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.events.BulkPersonAdditionEvent; -import plugins.people.events.PersonAdditionEvent; -import plugins.people.events.PersonImminentRemovalEvent; -import plugins.people.support.BulkPersonConstructionData; -import plugins.people.support.PersonConstructionData; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import plugins.regions.RegionsPlugin; -import plugins.regions.RegionsPluginData; -import plugins.regions.events.PersonRegionUpdateEvent; -import plugins.regions.events.RegionPropertyUpdateEvent; -import plugins.regions.support.RegionError; -import plugins.regions.support.RegionId; -import plugins.regions.support.RegionPropertyId; -import plugins.util.properties.PropertyDefinition; -import plugins.util.properties.PropertyError; -import plugins.util.properties.PropertyValueRecord; -import plugins.util.properties.TimeTrackingPolicy; -import plugins.util.properties.arraycontainers.DoubleValueContainer; -import plugins.util.properties.arraycontainers.IntValueContainer; -import util.errors.ContractException; - -/** - * Mutable data manager that backs the {@linkplain RegionDataView}. This data - * manager is for internal use by the {@link RegionsPlugin} and should not be - * published. - * - * All regions and region properties are established during construction and - * cannot be changed. Region property values are mutable. Limited validation of - * inputs are performed and mutation methods have invocation ordering - * requirements. - * - * @author Shawn Hatch - * - */ -public final class RegionsDataManager extends DataManager { - private DataManagerContext dataManagerContext; - - private final RegionsPluginData regionsPluginData; - - /** - * Creates a Region Data Manager from the given resolver context. - * Preconditions: The context must be a valid and non-null. - */ - public RegionsDataManager(RegionsPluginData regionsPluginData) { - if (regionsPluginData == null) { - throw new ContractException(RegionError.NULL_REGION_PLUGIN_DATA); - } - this.regionsPluginData = regionsPluginData; - } - - private PeopleDataManager peopleDataManager; - - /** - * - *

    - * Initializes all event labelers defined by - * {@linkplain RegionPropertyUpdateEvent} and - * {@linkplain PersonRegionUpdateEvent} - *

    - * - * - *

    - * Subscribes the following events: - *

      - * - *
    • {@linkplain PersonAdditionEvent}
      Sets the - * person's initial region in the {@linkplain RegionLocationDataView} from - * the region reference in the auxiliary data of the event. - * - *
      - *
      - * Throws {@link ContractException} - *
        - *
      • {@linkplain PersonError.UNKNOWN_PERSON_ID} if the person does not - * exist
      • - *
      • {@linkplain RegionError#NULL_REGION_ID} if no region data was - * included in the event
      • - *
      • {@linkplain RegionError#UNKNOWN_REGION_ID} if the region in the event - * is unknown
      • - *
      • {@linkplain RegionError#DUPLICATE_PERSON_ADDITION} if the person was - * previously added
      • - *
      - * - *
    • - * - *
    • {@linkplain BulkPersonAdditionEvent}
      Sets each - * person's initial region in the {@linkplain RegionLocationDataView} from - * the region references in the auxiliary data of the event. - * - *
      - *
      - * Throws {@link ContractException} - *
        - *
      • {@linkplain PersonError.UNKNOWN_PERSON_ID} if the person does not - * exist
      • - * - *
      • {@linkplain RegionError#NULL_REGION_ID} if no region data was - * included in the for some person in event
      • - * - *
      • {@linkplain RegionError#UNKNOWN_REGION_ID} if the region is unknown - * for some person in the event
      • - *
      - * - *
    • {@linkplain RegionError#DUPLICATE_PERSON_ADDITION} if a person was - * previously added
    • - * - * - *
    • - * - *
    • {@linkplain PersonImminentRemovalEvent}
      - * Removes the region assignment data for the person from the - * {@linkplain RegionDataView}
      - *
      - * Throws {@linkplain ContractException} - *
        - *
      • {@linkplain PersonError#UNKNOWN_PERSON_ID} if the person id is - * unknown
      • - *
      • {@linkplain RegionError#DUPLICATE_PERSON_REMOVAL} if the person was - * previously removed. Note : this exception will be delayed until the - * person is finally removed and cannot be found due to a previous - * removal
      • - * - * - *
      - * - *
    • - *
        - *

        - * - * @author Shawn Hatch - * - */ - public void init(DataManagerContext dataManagerContext) { - - super.init(dataManagerContext); - - this.dataManagerContext = dataManagerContext; - - peopleDataManager = dataManagerContext.getDataManager(PeopleDataManager.class); - - /* - * By setting the default value to 0, we are allowing the container to - * grow without having to set values in its array. HOWEVER, THIS IMPLIES - * THAT REGIONS MUST BE CONVERTED TO INTEGER VALUES STARTING AT ONE, NOT - * ZERO. - * - * - */ - regionValues = new IntValueContainer(0); - - regionArrivalTrackingPolicy = regionsPluginData.getPersonRegionArrivalTrackingPolicy(); - if (regionArrivalTrackingPolicy == TimeTrackingPolicy.TRACK_TIME) { - regionArrivalTimes = new DoubleValueContainer(0); - } - - final Set regionIds = regionsPluginData.getRegionIds(); - for (final RegionId regionId : regionIds) { - regionPopulationRecordMap.put(regionId, new PopulationRecord()); - } - - int index = 1; - for (final RegionId regionId : regionIds) { - regionToIndexMap.put(regionId, index++); - } - - indexToRegionMap = new RegionId[regionIds.size() + 1]; - - index = 1; - for (final RegionId regionId : regionIds) { - indexToRegionMap[index++] = regionId; - } - - for (RegionPropertyId regionPropertyId : regionsPluginData.getRegionPropertyIds()) { - PropertyDefinition propertyDefinition = regionsPluginData.getRegionPropertyDefinition(regionPropertyId); - regionPropertyIds.add(regionPropertyId); - regionPropertyDefinitions.put(regionPropertyId, propertyDefinition); - } - - for (final RegionId regionId : regionIds) { - Map map = new LinkedHashMap<>(); - regionPropertyMap.put(regionId, map); - for (RegionPropertyId regionPropertyId : regionPropertyIds) { - final Object regionPropertyValue = regionsPluginData.getRegionPropertyValue(regionId, regionPropertyId); - PropertyValueRecord propertyValueRecord = new PropertyValueRecord(dataManagerContext); - propertyValueRecord.setPropertyValue(regionPropertyValue); - map.put(regionPropertyId,propertyValueRecord); - } - } - - List people = peopleDataManager.getPeople(); - for (PersonId personId : people) { - RegionId regionId = regionsPluginData.getPersonRegion(personId); - final PopulationRecord populationRecord = regionPopulationRecordMap.get(regionId); - populationRecord.populationCount++; - Integer regionIndex = regionToIndexMap.get(regionId).intValue(); - regionValues.setIntValue(personId.getValue(), regionIndex); - } - - if(regionsPluginData.getPersonIds().size()>people.size()) { - throw new ContractException(PersonError.UNKNOWN_PERSON_ID,"There are people in the region plugin data that are not contained in the person data manager"); - } - - dataManagerContext.subscribe(PersonAdditionEvent.class, this::handlePersonAdditionEvent); - dataManagerContext.subscribe(BulkPersonAdditionEvent.class, this::handleBulkPersonAdditionEvent); - dataManagerContext.subscribe(PersonImminentRemovalEvent.class, this::handlePersonImminentRemovalEvent); - - dataManagerContext.addEventLabeler(RegionPropertyUpdateEvent.getEventLabelerForProperty()); - dataManagerContext.addEventLabeler(RegionPropertyUpdateEvent.getEventLabelerForRegionAndProperty()); - dataManagerContext.addEventLabeler(PersonRegionUpdateEvent.getEventLabelerForArrivalRegion()); - dataManagerContext.addEventLabeler(PersonRegionUpdateEvent.getEventLabelerForDepartureRegion()); - dataManagerContext.addEventLabeler(PersonRegionUpdateEvent.getEventLabelerForPerson()); - } - - private void validateValueCompatibility(final Object propertyId, final PropertyDefinition propertyDefinition, final Object propertyValue) { - if (!propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())) { - throw new ContractException(PropertyError.INCOMPATIBLE_VALUE, - "Property value " + propertyValue + " is not of type " + propertyDefinition.getType().getName() + " and does not match definition of " + propertyId); - } - } - - /** - * Expands the capacity of data structures to hold people by the given - * count. Used to more efficiently prepare for bulk population additions. - * - * @throws ContractException - *
      • {@linkplain PersonError#NEGATIVE_GROWTH_PROJECTION} if - * the count is negative
      • - */ - public void expandCapacity(final int count) { - if (count < 0) { - throw new ContractException(PersonError.NEGATIVE_GROWTH_PROJECTION); - } - if (count > 0) { - regionValues.setCapacity(regionValues.getCapacity() + count); - if (regionArrivalTimes != null) { - regionArrivalTimes.setCapacity(regionArrivalTimes.getCapacity() + count); - } - } - } - - private Map> regionPropertyMap = new LinkedHashMap<>(); - - private Set regionPropertyIds = new LinkedHashSet<>(); - - private Map regionPropertyDefinitions = new LinkedHashMap<>(); - - /** - * Returns the property definition for the given {@link RegionPropertyId} - * - * @throws ContractException - *
      • {@linkplain RegionError#NULL_REGION_PROPERTY_ID} if the - * region property id is null
      • - *
      • {@linkplain RegionError#UNKNOWN_REGION_PROPERTY_ID} if - * the region property id is unknown - * - */ - public PropertyDefinition getRegionPropertyDefinition(final RegionPropertyId regionPropertyId) { - validateRegionPropertyId(regionPropertyId); - return regionPropertyDefinitions.get(regionPropertyId); - } - - /** - * Returns the {@link RegionPropertyId} values - */ - @SuppressWarnings("unchecked") - public Set getRegionPropertyIds() { - Set result = new LinkedHashSet<>(regionPropertyDefinitions.keySet().size()); - for (RegionPropertyId regionPropertyId : regionPropertyDefinitions.keySet()) { - result.add((T) regionPropertyId); - } - return result; - } - - /** - * Return true if and only if the given {@link RegionId} exits. Null - * tolerant. - */ - public boolean regionIdExists(RegionId regionId) { - return regionPropertyMap.containsKey(regionId); - } - - /** - * Returns true if and only if the given {@link RegionPropertyId} exists. - * Tolerates nulls. - */ - public boolean regionPropertyIdExists(RegionPropertyId regionPropertyId) { - return regionPropertyDefinitions.containsKey(regionPropertyId); - } - - /** - * Returns the set of {@link RegionId} values that are defined by the - * {@link RegionsPluginData}. - */ - @SuppressWarnings("unchecked") - public Set getRegionIds() { - Set result = new LinkedHashSet<>(regionPropertyMap.size()); - for (RegionId regionId : regionPropertyMap.keySet()) { - result.add((T) regionId); - } - return result; - - } - - /** - * Updates the region's property value and time. Generates a corresponding - * {@linkplain RegionPropertyUpdateEvent} - * - * Throws {@link ContractException} - * - *
      • {@link RegionError#NULL_REGION_ID} if the region id is null - *
      • {@link RegionError#UNKNOWN_REGION_ID} if the region id is unknown - *
      • {@link RegionError#NULL_REGION_PROPERTY_ID} if the property id is - * null - *
      • {@link RegionError#UNKNOWN_REGION_PROPERTY_ID} if the property id is - * unknown - *
      • {@link RegionError#NULL_REGION_PROPERTY_VALUE} if the value is null - *
      • {@link PropertyError#INCOMPATIBLE_VALUE} if the value is incompatible - * with the defined type for the property - *
      • {@link PropertyError#IMMUTABLE_VALUE} if the property has been - * defined as immutable - * - *
      • - */ - - public void setRegionPropertyValue(RegionId regionId, RegionPropertyId regionPropertyId, Object regionPropertyValue) { - - validateRegionId(regionId); - validateRegionPropertyId(regionPropertyId); - validateRegionPropertyValueNotNull(regionPropertyValue); - final PropertyDefinition propertyDefinition = getRegionPropertyDefinition(regionPropertyId); - validateValueCompatibility(regionPropertyId, propertyDefinition, regionPropertyValue); - validatePropertyMutability(propertyDefinition); - - final Object previousPropertyValue = getRegionPropertyValue(regionId, regionPropertyId); - regionPropertyMap.get(regionId).get(regionPropertyId).setPropertyValue(regionPropertyValue); - dataManagerContext.releaseEvent(new RegionPropertyUpdateEvent(regionId, regionPropertyId, previousPropertyValue, regionPropertyValue)); - - } - - private void validatePropertyMutability(final PropertyDefinition propertyDefinition) { - if (!propertyDefinition.propertyValuesAreMutable()) { - throw new ContractException(PropertyError.IMMUTABLE_VALUE); - } - } - - private void validateRegionPropertyValueNotNull(final Object propertyValue) { - if (propertyValue == null) { - throw new ContractException(RegionError.NULL_REGION_PROPERTY_VALUE); - } - } - - /** - * Returns the value of the region property. - * - * @throws ContractException - *
      • {@linkplain RegionError#NULL_REGION_ID} if the region id - * is null
      • - *
      • {@linkplain RegionError#UNKNOWN_REGION_ID} if the region - * id is not known
      • - *
      • {@linkplain RegionError#NULL_REGION_PROPERTY_ID} if the - * region property id is null
      • - *
      • {@linkplain RegionError#UNKNOWN_REGION_PROPERTY_ID} if - * the region property id is unknown
      • - */ - @SuppressWarnings("unchecked") - public T getRegionPropertyValue(RegionId regionId, RegionPropertyId regionPropertyId) { - validateRegionId(regionId); - validateRegionPropertyId(regionPropertyId); - return (T) regionPropertyMap.get(regionId).get(regionPropertyId).getValue(); - } - - /** - * Returns the time when the of the region property was last assigned. - * - * @throws ContractException - *
      • {@linkplain RegionError#NULL_REGION_ID} if the region id - * is null
      • - *
      • {@linkplain RegionError#UNKNOWN_REGION_ID} if the region - * id is not known
      • - *
      • {@linkplain RegionError#NULL_REGION_PROPERTY_ID} if the - * region property id is null
      • - *
      • {@linkplain RegionError#UNKNOWN_REGION_PROPERTY_ID} if - * the region property id is unknown
      • - */ - public double getRegionPropertyTime(RegionId regionId, RegionPropertyId regionPropertyId) { - validateRegionId(regionId); - validateRegionPropertyId(regionPropertyId); - return regionPropertyMap.get(regionId).get(regionPropertyId).getAssignmentTime(); - } - - /* - * Record for maintaining the number of people either globally or regionally. - * Also maintains the time when the population count was - * last changed. PopulationRecords are maintained to eliminate iterations - * over other tracking structures to answer queries about population counts. - */ - private static class PopulationRecord { - private int populationCount; - private double assignmentTime; - } - - /* - * Tracking record for the total number of people in each region. - */ - private final Map regionPopulationRecordMap = new LinkedHashMap<>(); - - /* - * Supports the conversion of region ids into int values. - */ - private final Map regionToIndexMap = new LinkedHashMap<>(); - - /* - * Supports conversion of int into RegionId values - */ - private RegionId[] indexToRegionMap; - - /* - * Stores region identifiers as int values indexed by person id values - */ - private IntValueContainer regionValues; - - /* - * Stores double region arrival values indexed by person id values. - * Maintenance depends upon tracking policy. - */ - private DoubleValueContainer regionArrivalTimes; - - private TimeTrackingPolicy regionArrivalTrackingPolicy; - - /** - * Returns as a List the person identifiers of the people in the given - * region. List elements are unique. - * - * @throws ContractException - *
      • {@linkplain RegionError#NULL_REGION_ID} if - * the c id is null - *
      • {@linkplain RegionError#UNKNOWN_REGION_ID} - * if the region id is not known - */ - public List getPeopleInRegion(final RegionId regionId) { - validateRegionId(regionId); - - int targetRegionIndex = regionToIndexMap.get(regionId).intValue(); - - List result = new ArrayList<>(); - - int n = regionValues.size(); - for (int personIndex = 0; personIndex < n; personIndex++) { - final int regionIndex = regionValues.getValueAsInt(personIndex); - /* - * a region index of zero will not match any valid region, - * indicating that person does not exist - */ - if (targetRegionIndex == regionIndex) { - PersonId personId = peopleDataManager.getBoxedPersonId(personIndex).get(); - result.add(personId); - } - } - - return result; - } - - /** - * Returns the region associated with the given person id. - * - * @throwsContractException - *
      • {@linkplain PersonError#NULL_PERSON_ID} if - * the person id is null - *
      • {@linkplain PersonError#UNKNOWN_PERSON_ID} - * if the person id is unknown - */ - @SuppressWarnings("unchecked") - public T getPersonRegion(final PersonId personId) { - validatePersonExists(personId); - final int r = regionValues.getValueAsInt(personId.getValue()); - return (T) indexToRegionMap[r]; - } - - /** - * Returns the time when then person arrived at their current region. - * - * @throwsContractException - *
      • {@linkplain PersonError#NULL_PERSON_ID} if - * the person id is null - *
      • {@linkplain PersonError#UNKNOWN_PERSON_ID} - * if the person id is unknown - *
      • {@linkplain RegionError#REGION_ARRIVAL_TIMES_NOT_TRACKED} - * if the region arrival times are not being - * tracked
      • - * - */ - public double getPersonRegionArrivalTime(final PersonId personId) { - validatePersonExists(personId); - validatePersonRegionArrivalsTimesTracked(); - return regionArrivalTimes.getValue(personId.getValue()); - } - - /** - * Returns the policy for tracking the last region arrival time for each - * person - */ - public TimeTrackingPolicy getPersonRegionArrivalTrackingPolicy() { - return regionArrivalTrackingPolicy; - } - - /** - * Returns the number of people currently in the given region. - * - * @throws ContractException - *
      • {@linkplain RegionError#NULL_REGION_ID} if the region id - * is null - *
      • {@linkplain RegionError#UNKNOWN_REGION_ID} if the region - * id is not known - */ - public int getRegionPopulationCount(final RegionId regionId) { - validateRegionId(regionId); - return regionPopulationRecordMap.get(regionId).populationCount; - } - - /** - * Returns the time when the current population of the given region was - * established. - * - * @throwsContractException - *
      • {@linkplain RegionError#NULL_REGION_ID} if - * the region id is null - *
      • {@linkplain RegionError#UNKNOWN_REGION_ID} - * if the region id is not known - */ - public double getRegionPopulationTime(final RegionId regionId) { - validateRegionId(regionId); - return regionPopulationRecordMap.get(regionId).assignmentTime; - } - - - - /** - * - * Updates the person's current region and region arrival time. Generates a - * corresponding {@linkplain PersonRegionUpdateEvent} - * - * Throws {@link ContractException} - * - * - *
      • {@link PersonError#NULL_PERSON_ID} if the person id is null
      • - *
      • {@link PersonError#UNKNOWN_PERSON_ID} if the person id is - * unknown
      • - *
      • {@link RegionError#NULL_REGION_ID} if the region id is null
      • - *
      • {@link RegionError#UNKNOWN_REGION_ID} if the region id is - * unknown
      • - * - */ - - public void setPersonRegion(final PersonId personId, final RegionId regionId) { - - validatePersonExists(personId); - validateRegionId(regionId); - - - /* - * Retrieve the int value that represents the current region of the - * person - */ - int regionIndex = regionValues.getValueAsInt(personId.getValue()); - RegionId oldRegionId = indexToRegionMap[regionIndex]; - PopulationRecord populationRecord = regionPopulationRecordMap.get(oldRegionId); - /* - * Update the population count associated with the old region - */ - populationRecord.populationCount--; - populationRecord.assignmentTime = dataManagerContext.getTime(); - - /* - * Update the population count of the new region - */ - populationRecord = regionPopulationRecordMap.get(regionId); - populationRecord.populationCount++; - populationRecord.assignmentTime = dataManagerContext.getTime(); - /* - * Convert the new region id into an int - */ - - regionIndex = regionToIndexMap.get(regionId).intValue(); - /* - * Store in the int at the person's index - */ - regionValues.setIntValue(personId.getValue(), regionIndex); - /* - * If region arrival times are being tracked, do so. - */ - if (regionArrivalTimes != null) { - regionArrivalTimes.setValue(personId.getValue(), dataManagerContext.getTime()); - } - - dataManagerContext.releaseEvent(new PersonRegionUpdateEvent(personId, oldRegionId, regionId)); - - } - - private void validateRegionPropertyId(final RegionPropertyId regionPropertyId) { - if (regionPropertyId == null) { - throw new ContractException(RegionError.NULL_REGION_PROPERTY_ID); - } - if (!regionPropertyIdExists(regionPropertyId)) { - throw new ContractException(RegionError.UNKNOWN_REGION_PROPERTY_ID, regionPropertyId); - } - } - - private void validateRegionId(final RegionId regionId) { - - if (regionId == null) { - throw new ContractException(RegionError.NULL_REGION_ID); - } - - if (!regionIdExists(regionId)) { - throw new ContractException(RegionError.UNKNOWN_REGION_ID, regionId); - } - } - - private void validatePersonExists(final PersonId personId) { - if (personId == null) { - throw new ContractException(PersonError.NULL_PERSON_ID); - } - - if (!peopleDataManager.personExists(personId)) { - throw new ContractException(PersonError.UNKNOWN_PERSON_ID); - } - } - - private void validatePersonRegionArrivalsTimesTracked() { - if (getPersonRegionArrivalTrackingPolicy() != TimeTrackingPolicy.TRACK_TIME) { - throw new ContractException(RegionError.REGION_ARRIVAL_TIMES_NOT_TRACKED); - } - } - - private void validatePersonNotContained(PersonId personId) { - - int regionIndex = regionValues.getValueAsInt(personId.getValue()); - if (regionIndex != 0) { - throw new ContractException(RegionError.DUPLICATE_PERSON_ADDITION); - } - } - - private void validatePersonContained(PersonId personId) { - - int regionIndex = regionValues.getValueAsInt(personId.getValue()); - if (regionIndex == 0) { - throw new ContractException(RegionError.DUPLICATE_PERSON_REMOVAL); - } - } - - private void handlePersonAdditionEvent(final DataManagerContext dataManagerContext, final PersonAdditionEvent personAdditionEvent) { - PersonConstructionData personConstructionData = personAdditionEvent.getPersonConstructionData(); - RegionId regionId = personConstructionData.getValue(RegionId.class).orElse(null); - validateRegionId(regionId); - PersonId personId = personAdditionEvent.getPersonId(); - - validatePersonExists(personId); - validateRegionId(regionId); - validatePersonNotContained(personId); - - /* - * Update the population count of the new region - */ - - final PopulationRecord populationRecord = regionPopulationRecordMap.get(regionId); - populationRecord.populationCount++; - populationRecord.assignmentTime = dataManagerContext.getTime(); - - Integer regionIndex = regionToIndexMap.get(regionId).intValue(); - regionValues.setIntValue(personId.getValue(), regionIndex); - - if (regionArrivalTimes != null) { - regionArrivalTimes.setValue(personId.getValue(), dataManagerContext.getTime()); - } - } - - private void handleBulkPersonAdditionEvent(final DataManagerContext dataManagerContext, final BulkPersonAdditionEvent bulkPersonAdditionEvent) { - BulkPersonConstructionData bulkPersonConstructionData = bulkPersonAdditionEvent.getBulkPersonConstructionData(); - List personConstructionDatas = bulkPersonConstructionData.getPersonConstructionDatas(); - for (PersonConstructionData personConstructionData : personConstructionDatas) { - RegionId regionId = personConstructionData.getValue(RegionId.class).orElse(null); - validateRegionId(regionId); - } - - PersonId personId = bulkPersonAdditionEvent.getPersonId(); - validatePersonExists(personId); - int pId = personId.getValue(); - - for (PersonConstructionData personConstructionData : personConstructionDatas) { - RegionId regionId = personConstructionData.getValue(RegionId.class).get(); - Optional optionalBoxedPersonId = peopleDataManager.getBoxedPersonId(pId); - if (!optionalBoxedPersonId.isPresent()) { - throw new ContractException(PersonError.UNKNOWN_PERSON_ID); - } - PersonId boxedPersonId = optionalBoxedPersonId.get(); - validatePersonNotContained(boxedPersonId); - - final PopulationRecord populationRecord = regionPopulationRecordMap.get(regionId); - populationRecord.populationCount++; - populationRecord.assignmentTime = dataManagerContext.getTime(); - - Integer regionIndex = regionToIndexMap.get(regionId).intValue(); - regionValues.setIntValue(boxedPersonId.getValue(), regionIndex); - - if (regionArrivalTimes != null) { - regionArrivalTimes.setValue(boxedPersonId.getValue(), dataManagerContext.getTime()); - } - pId++; - } - } - - /* - * Removes the person from this data manager. - * - * Precondition : the person must exist and be stored in this manager - * - * @throws ContractException
      • {@linkplain PersonError.NULL_PERSON_ID} if - * the person id is null
      • {@linkplain PersonError.UNKNOWN_PERSON_ID} - * if the person id is unknown
      • - * - */ - private void handlePersonImminentRemovalEvent(final DataManagerContext dataManagerContext, final PersonImminentRemovalEvent personImminentRemovalEvent) { - PersonId personId = personImminentRemovalEvent.getPersonId(); - validatePersonExists(personId); - dataManagerContext.addPlan((context) -> removePerson(personId), dataManagerContext.getTime()); - } - - private void removePerson(final PersonId personId) { - validatePersonContained(personId); - final int regionIndex = regionValues.getValueAsInt(personId.getValue()); - final RegionId oldRegionId = indexToRegionMap[regionIndex]; - final PopulationRecord populationRecord = regionPopulationRecordMap.get(oldRegionId); - populationRecord.populationCount--; - populationRecord.assignmentTime = dataManagerContext.getTime(); - regionValues.setIntValue(personId.getValue(), 0); - } - -} diff --git a/gcm3/src/main/java/plugins/regions/events/PersonRegionUpdateEvent.java b/gcm3/src/main/java/plugins/regions/events/PersonRegionUpdateEvent.java deleted file mode 100644 index 53f5f328d..000000000 --- a/gcm3/src/main/java/plugins/regions/events/PersonRegionUpdateEvent.java +++ /dev/null @@ -1,134 +0,0 @@ -package plugins.regions.events; - -import net.jcip.annotations.Immutable; -import nucleus.Event; -import nucleus.EventLabel; -import nucleus.EventLabeler; -import nucleus.EventLabelerId; -import nucleus.SimulationContext; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import plugins.regions.datamanagers.RegionsDataManager; -import plugins.regions.support.RegionError; -import plugins.regions.support.RegionId; -import util.errors.ContractException; - -@Immutable -public final class PersonRegionUpdateEvent implements Event { - private final PersonId personId; - private final RegionId previousRegionId; - private final RegionId currentRegionId; - - public PersonRegionUpdateEvent(final PersonId personId, final RegionId previousRegionId, final RegionId currentRegionId) { - super(); - this.personId = personId; - this.previousRegionId = previousRegionId; - this.currentRegionId = currentRegionId; - } - - public RegionId getCurrentRegionId() { - return currentRegionId; - } - - public PersonId getPersonId() { - return personId; - } - - public RegionId getPreviousRegionId() { - return previousRegionId; - } - - @Override - public String toString() { - return "PersonRegionUpdateEvent [personId=" + personId + ", previousRegionId=" + previousRegionId + ", currentRegionId=" + currentRegionId + "]"; - } - - private static enum LabelerId implements EventLabelerId { - ARRIVAL, DEPARTURE, PERSON - } - - private static void validateRegionId(SimulationContext simulationContext, RegionId regionId) { - if (regionId == null) { - throw new ContractException(RegionError.NULL_REGION_ID); - } - RegionsDataManager regionsDataManager = simulationContext.getDataManager(RegionsDataManager.class); - if (!regionsDataManager.regionIdExists(regionId)) { - throw new ContractException(RegionError.UNKNOWN_REGION_ID); - } - } - - private static void validatePersonId(SimulationContext simulationContext, PersonId personId) { - if (personId == null) { - throw new ContractException(PersonError.NULL_PERSON_ID); - } - PeopleDataManager peopleDataManager = simulationContext.getDataManager(PeopleDataManager.class); - if (!peopleDataManager.personExists(personId)) { - throw new ContractException(PersonError.UNKNOWN_PERSON_ID); - } - } - - public static EventLabel getEventLabelByArrivalRegion(SimulationContext simulationContext, RegionId regionId) { - validateRegionId(simulationContext, regionId); - return _getEventLabelByArrivalRegion(regionId);// - } - - private static EventLabel _getEventLabelByArrivalRegion(RegionId regionId) { - - return EventLabel .builder(PersonRegionUpdateEvent.class)// - .setEventLabelerId(LabelerId.ARRIVAL)// - .addKey(PersonRegionUpdateEvent.class)// - .addKey(regionId)// - .build();// - } - - public static EventLabeler getEventLabelerForArrivalRegion() { - return EventLabeler .builder(PersonRegionUpdateEvent.class)// - .setEventLabelerId(LabelerId.ARRIVAL)// - .setLabelFunction((context, event) -> _getEventLabelByArrivalRegion(event.getCurrentRegionId()))// - .build(); - } - - public static EventLabel getEventLabelByDepartureRegion(SimulationContext simulationContext, RegionId regionId) { - validateRegionId(simulationContext, regionId); - return _getEventLabelByDepartureRegion(regionId);// - } - - private static EventLabel _getEventLabelByDepartureRegion(RegionId regionId) { - - return EventLabel .builder(PersonRegionUpdateEvent.class)// - .setEventLabelerId(LabelerId.DEPARTURE)// - .addKey(PersonRegionUpdateEvent.class)// - .addKey(regionId)// - .build();// - } - - public static EventLabeler getEventLabelerForDepartureRegion() { - return EventLabeler .builder(PersonRegionUpdateEvent.class)// - .setEventLabelerId(LabelerId.DEPARTURE)// - .setLabelFunction((context, event) -> _getEventLabelByDepartureRegion(event.getPreviousRegionId()))// - .build(); - } - - public static EventLabel getEventLabelByPerson(SimulationContext simulationContext, PersonId personId) { - validatePersonId(simulationContext, personId); - return _getEventLabelByPerson(personId);// - } - - private static EventLabel _getEventLabelByPerson(PersonId personId) { - - return EventLabel .builder(PersonRegionUpdateEvent.class)// - .setEventLabelerId(LabelerId.PERSON)// - .addKey(PersonRegionUpdateEvent.class)// - .addKey(personId)// - .build();// - } - - public static EventLabeler getEventLabelerForPerson() { - return EventLabeler .builder(PersonRegionUpdateEvent.class)// - .setEventLabelerId(LabelerId.PERSON)// - .setLabelFunction((context, event) -> _getEventLabelByPerson(event.getPersonId()))// - .build(); - } - -} diff --git a/gcm3/src/main/java/plugins/regions/events/RegionPropertyUpdateEvent.java b/gcm3/src/main/java/plugins/regions/events/RegionPropertyUpdateEvent.java deleted file mode 100644 index 7cddcc331..000000000 --- a/gcm3/src/main/java/plugins/regions/events/RegionPropertyUpdateEvent.java +++ /dev/null @@ -1,119 +0,0 @@ -package plugins.regions.events; - -import net.jcip.annotations.Immutable; -import nucleus.Event; -import nucleus.EventLabel; -import nucleus.EventLabeler; -import nucleus.EventLabelerId; -import nucleus.SimulationContext; -import plugins.regions.datamanagers.RegionsDataManager; -import plugins.regions.support.RegionError; -import plugins.regions.support.RegionId; -import plugins.regions.support.RegionPropertyId; -import util.errors.ContractException; - -@Immutable -public final class RegionPropertyUpdateEvent implements Event { - private final RegionId regionId; - private final RegionPropertyId regionPropertyId; - private final Object previousPropertyValue; - private final Object currentPropertyValue; - - public RegionPropertyUpdateEvent(RegionId regionId, RegionPropertyId regionPropertyId, Object previousPropertyValue, Object currentPropertyValue) { - super(); - this.regionId = regionId; - this.regionPropertyId = regionPropertyId; - this.previousPropertyValue = previousPropertyValue; - this.currentPropertyValue = currentPropertyValue; - } - - public RegionId getRegionId() { - return regionId; - } - - public RegionPropertyId getRegionPropertyId() { - return regionPropertyId; - } - - public Object getPreviousPropertyValue() { - return previousPropertyValue; - } - - public Object getCurrentPropertyValue() { - return currentPropertyValue; - } - - @Override - public String toString() { - return "RegionPropertyUpdateEvent [regionId=" + regionId + ", regionPropertyId=" + regionPropertyId + ", previousPropertyValue=" + previousPropertyValue + ", currentPropertyValue=" - + currentPropertyValue + "]"; - } - - private static enum LabelerId implements EventLabelerId { - PROPERTY, REGION_PROPERTY - } - - private static void validateRegionPropertyId(SimulationContext simulationContext, RegionPropertyId regionPropertyId) { - RegionsDataManager regionsDataManager = simulationContext.getDataManager(RegionsDataManager.class); - regionsDataManager.getRegionPropertyDefinition(regionPropertyId); - } - - private static void validateRegionId(SimulationContext simulationContext, RegionId regionId) { - if (regionId == null) { - throw new ContractException(RegionError.NULL_REGION_ID); - } - RegionsDataManager regionsDataManager = simulationContext.getDataManager(RegionsDataManager.class); - if (!regionsDataManager.regionIdExists(regionId)) { - throw new ContractException(RegionError.UNKNOWN_REGION_ID); - } - } - - public static EventLabel getEventLabelByRegionAndProperty(SimulationContext simulationContext, RegionId regionId, RegionPropertyId regionPropertyId) { - validateRegionId(simulationContext, regionId); - validateRegionPropertyId(simulationContext, regionPropertyId); - return _getEventLabelByRegionAndProperty(regionId, regionPropertyId);// - } - - private static EventLabel _getEventLabelByRegionAndProperty(RegionId regionId, RegionPropertyId regionPropertyId) { - - return EventLabel .builder(RegionPropertyUpdateEvent.class)// - .setEventLabelerId(LabelerId.REGION_PROPERTY)// - .addKey(regionPropertyId)// - .addKey(regionId)// - .build();// - } - - public static EventLabeler getEventLabelerForRegionAndProperty() { - return EventLabeler .builder(RegionPropertyUpdateEvent.class)// - .setEventLabelerId(LabelerId.REGION_PROPERTY)// - .setLabelFunction((context, event) -> _getEventLabelByRegionAndProperty(event.getRegionId(), event.getRegionPropertyId()))// - .build(); - } - - public static EventLabel getEventLabelByProperty(SimulationContext simulationContext, RegionPropertyId regionPropertyId) { - validateRegionPropertyId(simulationContext, regionPropertyId); - return _getEventLabelByProperty(regionPropertyId);// - } - - private static EventLabel _getEventLabelByProperty(RegionPropertyId regionPropertyId) { - - return EventLabel .builder(RegionPropertyUpdateEvent.class)// - .setEventLabelerId(LabelerId.PROPERTY)// - .addKey(regionPropertyId)// - .build();// - - } - - public static EventLabeler getEventLabelerForProperty() { - return EventLabeler .builder(RegionPropertyUpdateEvent.class)// - .setEventLabelerId(LabelerId.PROPERTY)// - .setLabelFunction((context, event) -> _getEventLabelByProperty(event.getRegionPropertyId()))// - .build(); - } - - @Override - public Object getPrimaryKeyValue() { - return regionPropertyId; - } - -} diff --git a/gcm3/src/main/java/plugins/regions/support/RegionError.java b/gcm3/src/main/java/plugins/regions/support/RegionError.java deleted file mode 100644 index 74c6fda9d..000000000 --- a/gcm3/src/main/java/plugins/regions/support/RegionError.java +++ /dev/null @@ -1,52 +0,0 @@ -package plugins.regions.support; - -import util.errors.ContractError; -import util.errors.ContractException; - -/** - * An enumeration supporting {@link ContractException} that acts as a general - * description of the exception. - * - * @author Shawn Hatch - * - */ -public enum RegionError implements ContractError { - - - - NULL_REGION_ID("Null region id"), - DUPLICATE_REGION_ID("Duplicate region id"), - UNKNOWN_REGION_ID("Unknown region id"), - MISSING_REGION_ASSIGNMENT("Region assignment is missing"), - NULL_REGION_PROPERTY_ID("Null region property id"), - UNKNOWN_REGION_PROPERTY_ID("Unknown region property id"), - NULL_REGION_PROPERTY_DEFINITION("Null region property definition"), - NULL_REGION_PLUGIN_DATA("Null region plugin data"), - NULL_REGION_PROPERTY_VALUE("Null region property value"), - DUPLICATE_PERSON_REGION_ASSIGNMENT("Duplicate person region assignment"), - DUPLICATE_PERSON_ADDITION("Duplicate person region addition"), - DUPLICATE_PERSON_REMOVAL("Duplicate person region removal"), - DUPLICATE_REGION_PROPERTY_VALUE("Duplicate region property value"), - DUPLICATE_REGION_PROPERTY_DEFINITION_ASSIGNMENT("Duplicate region property definition assignment"), - - - NULL_TIME_TRACKING_POLICY("Null time tracking policy"), - DUPLICATE_TIME_TRACKING_POLICY("Duplicate time tracking policy"), - - INSUFFICIENT_REGION_PROPERTY_VALUE_ASSIGNMENT("A regiont property definition default value is null and not replaced with sufficient property value assignments"), - - REGION_ARRIVAL_TIMES_NOT_TRACKED("Person region arrival times not actively tracked"); - - - - private final String description; - - private RegionError(final String description) { - this.description = description; - } - - @Override - public String getDescription() { - return description; - } -} diff --git a/gcm3/src/main/java/plugins/regions/support/RegionFilter.java b/gcm3/src/main/java/plugins/regions/support/RegionFilter.java deleted file mode 100644 index 847dcfaa0..000000000 --- a/gcm3/src/main/java/plugins/regions/support/RegionFilter.java +++ /dev/null @@ -1,96 +0,0 @@ -package plugins.regions.support; - -import java.util.LinkedHashSet; -import java.util.Optional; -import java.util.Set; - -import nucleus.NucleusError; -import nucleus.SimulationContext; -import plugins.partitions.support.Filter; -import plugins.partitions.support.FilterSensitivity; -import plugins.people.support.PersonId; -import plugins.regions.datamanagers.RegionsDataManager; -import plugins.regions.events.PersonRegionUpdateEvent; -import util.errors.ContractException; - -public final class RegionFilter extends Filter { - - private final Set regionIds = new LinkedHashSet<>(); - - private RegionsDataManager regionsDataManager; - - private void validateRegionId( final RegionId regionId) { - - if (regionId == null) { - throw new ContractException(RegionError.NULL_REGION_ID); - } - - if (!regionsDataManager.regionIdExists(regionId)) { - throw new ContractException(RegionError.UNKNOWN_REGION_ID, regionId); - } - } - - public RegionFilter(final RegionId... regionIds) { - for (RegionId regionId : regionIds) { - this.regionIds.add(regionId); - } - } - - @Override - public void validate(SimulationContext simulationContext) { - if (simulationContext == null) { - throw new ContractException(NucleusError.NULL_SIMULATION_CONTEXT); - } - - if (regionsDataManager == null) { - regionsDataManager = simulationContext.getDataManager(RegionsDataManager.class); - } - - for (RegionId regionId : regionIds) { - validateRegionId(regionId); - } - - } - - public RegionFilter(final Set regionIds) { - this.regionIds.addAll(regionIds); - } - - @Override - public boolean evaluate(SimulationContext simulationContext, PersonId personId) { - if (simulationContext == null) { - throw new ContractException(NucleusError.NULL_SIMULATION_CONTEXT); - } - - if (regionsDataManager == null) { - regionsDataManager = simulationContext.getDataManager(RegionsDataManager.class); - } - return regionIds.contains(regionsDataManager.getPersonRegion(personId)); - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("RegionFilter [regionIds="); - builder.append(regionIds); - builder.append("]"); - return builder.toString(); - } - - private Optional requiresRefresh(SimulationContext simulationContext, PersonRegionUpdateEvent event) { - boolean previousRegionIdContained = regionIds.contains(event.getPreviousRegionId()); - boolean currentRegionIdContained = regionIds.contains(event.getCurrentRegionId()); - if (previousRegionIdContained != currentRegionIdContained) { - return Optional.of(event.getPersonId()); - } - return Optional.empty(); - } - - @Override - public Set> getFilterSensitivities() { - Set> result = new LinkedHashSet<>(); - result.add(new FilterSensitivity(PersonRegionUpdateEvent.class, this::requiresRefresh)); - return result; - } - -} diff --git a/gcm3/src/main/java/plugins/regions/support/RegionId.java b/gcm3/src/main/java/plugins/regions/support/RegionId.java deleted file mode 100644 index 6c68900fc..000000000 --- a/gcm3/src/main/java/plugins/regions/support/RegionId.java +++ /dev/null @@ -1,15 +0,0 @@ -package plugins.regions.support; - -import net.jcip.annotations.ThreadSafe; - -/** - * Marker interface for region identifiers. Each region id is - * associated with a region agent in the simulation. - * - * @author Shawn Hatch - * - */ -@ThreadSafe -public interface RegionId { - -} diff --git a/gcm3/src/main/java/plugins/regions/support/RegionLabeler.java b/gcm3/src/main/java/plugins/regions/support/RegionLabeler.java deleted file mode 100644 index 51213218b..000000000 --- a/gcm3/src/main/java/plugins/regions/support/RegionLabeler.java +++ /dev/null @@ -1,76 +0,0 @@ -package plugins.regions.support; - -import java.util.LinkedHashSet; -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; - -import nucleus.Event; -import nucleus.NucleusError; -import nucleus.SimulationContext; -import plugins.partitions.support.Labeler; -import plugins.partitions.support.LabelerSensitivity; -import plugins.people.support.PersonId; -import plugins.regions.datamanagers.RegionsDataManager; -import plugins.regions.events.PersonRegionUpdateEvent; -import util.errors.ContractException; - -/** - * A labeler for regions. The dimension of the labeler is the - * {@linkplain RegionId} class, the event that stimulates a label update is - * {@linkplain PersonRegionUpdateEvent} and the labeling function is - * composed from the given Function. - * - * @author Shawn Hatch - * - */ -public final class RegionLabeler implements Labeler { - - private final Function regionLabelingFunction; - - private RegionsDataManager regionsDataManager; - - /** - * Creates the Region labeler from the given labeling function - * - * - */ - public RegionLabeler(Function regionLabelingFunction) { - this.regionLabelingFunction = regionLabelingFunction; - } - - private Optional getPersonId(PersonRegionUpdateEvent personRegionUpdateEvent) { - return Optional.of(personRegionUpdateEvent.getPersonId()); - } - - @Override - public Set> getLabelerSensitivities() { - Set> result = new LinkedHashSet<>(); - result.add(new LabelerSensitivity(PersonRegionUpdateEvent.class, this::getPersonId)); - return result; - } - - @Override - public Object getLabel(SimulationContext simulationContext, PersonId personId) { - if (simulationContext == null) { - throw new ContractException(NucleusError.NULL_SIMULATION_CONTEXT); - } - if (regionsDataManager == null) { - regionsDataManager = simulationContext.getDataManager(RegionsDataManager.class); - } - RegionId regionId = regionsDataManager.getPersonRegion(personId); - return regionLabelingFunction.apply(regionId); - } - - @Override - public Object getDimension() { - return RegionId.class; - } - - @Override - public Object getPastLabel(SimulationContext simulationContext, Event event) { - PersonRegionUpdateEvent personRegionUpdateEvent = (PersonRegionUpdateEvent) event; - return regionLabelingFunction.apply(personRegionUpdateEvent.getPreviousRegionId()); - } - -} diff --git a/gcm3/src/main/java/plugins/regions/support/RegionPropertyId.java b/gcm3/src/main/java/plugins/regions/support/RegionPropertyId.java deleted file mode 100644 index 858a67c58..000000000 --- a/gcm3/src/main/java/plugins/regions/support/RegionPropertyId.java +++ /dev/null @@ -1,14 +0,0 @@ -package plugins.regions.support; - -import net.jcip.annotations.ThreadSafe; - -/** - * Marker interface for region property identifiers - * - * @author Shawn Hatch - * - */ -@ThreadSafe -public interface RegionPropertyId { - -} diff --git a/gcm3/src/main/java/plugins/regions/testsupport/RegionsActionSupport.java b/gcm3/src/main/java/plugins/regions/testsupport/RegionsActionSupport.java deleted file mode 100644 index e546559ed..000000000 --- a/gcm3/src/main/java/plugins/regions/testsupport/RegionsActionSupport.java +++ /dev/null @@ -1,87 +0,0 @@ -package plugins.regions.testsupport; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -import nucleus.ActorContext; -import nucleus.Plugin; -import nucleus.Simulation; -import nucleus.Simulation.Builder; -import nucleus.testsupport.testplugin.ScenarioPlanCompletionObserver; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestError; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import plugins.people.PeoplePlugin; -import plugins.people.PeoplePluginData; -import plugins.people.support.PersonId; -import plugins.regions.RegionsPlugin; -import plugins.regions.RegionsPluginData; -import plugins.stochastics.StochasticsPlugin; -import plugins.stochastics.StochasticsPluginData; -import plugins.util.properties.TimeTrackingPolicy; -import util.errors.ContractException; - -public final class RegionsActionSupport { - public static void testConsumer(int initialPopulation, long seed, TimeTrackingPolicy timeTrackingPolicy, Consumer consumer) { - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, consumer)); - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - testConsumers(initialPopulation, seed, timeTrackingPolicy, testPlugin); - } - - public static void testConsumers(int initialPopulation, long seed, TimeTrackingPolicy timeTrackingPolicy, Plugin testPlugin) { - List people = new ArrayList<>(); - for (int i = 0; i < initialPopulation; i++) { - people.add(new PersonId(i)); - } - Builder builder = Simulation.builder(); - - // add the region plugin - RegionsPluginData.Builder regionPluginBuilder = RegionsPluginData.builder(); - for (TestRegionId regionId : TestRegionId.values()) { - regionPluginBuilder.addRegion(regionId); - } - - for(TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { - regionPluginBuilder.defineRegionProperty(testRegionPropertyId, testRegionPropertyId.getPropertyDefinition()); - } - TestRegionId testRegionId = TestRegionId.REGION_1; - regionPluginBuilder.setPersonRegionArrivalTracking(timeTrackingPolicy); - for(PersonId personId : people) { - regionPluginBuilder.setPersonRegion(personId, testRegionId); - testRegionId = testRegionId.next(); - } - builder.addPlugin(RegionsPlugin.getRegionsPlugin(regionPluginBuilder.build())); - - // add the people plugin - PeoplePluginData.Builder peopleBuilder = PeoplePluginData.builder(); - for(PersonId personId : people) { - peopleBuilder.addPersonId(personId); - } - PeoplePluginData peoplePluginData = peopleBuilder.build(); - builder.addPlugin(PeoplePlugin.getPeoplePlugin(peoplePluginData)); - - // add the stochastics plugin - builder.addPlugin(StochasticsPlugin.getStochasticsPlugin(StochasticsPluginData.builder().setSeed(seed).build())); - - // add the test plugin - builder.addPlugin(testPlugin); - - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - - // build and execute the engine - builder.setOutputConsumer(scenarioPlanCompletionObserver::handleOutput); - builder.build().execute(); - - // show that all actions were executed - if (!scenarioPlanCompletionObserver.allPlansExecuted()) { - throw new ContractException(TestError.TEST_EXECUTION_FAILURE); - } - - } - -} diff --git a/gcm3/src/main/java/plugins/regions/testsupport/TestRegionPropertyId.java b/gcm3/src/main/java/plugins/regions/testsupport/TestRegionPropertyId.java deleted file mode 100644 index f9838fb74..000000000 --- a/gcm3/src/main/java/plugins/regions/testsupport/TestRegionPropertyId.java +++ /dev/null @@ -1,140 +0,0 @@ -package plugins.regions.testsupport; - -import java.util.ArrayList; -import java.util.List; - -import org.apache.commons.math3.random.RandomGenerator; - -import plugins.regions.support.RegionPropertyId; -import plugins.util.properties.PropertyDefinition; -import plugins.util.properties.TimeTrackingPolicy; - -/** - * Enumeration that identifies region property definitions - */ -public enum TestRegionPropertyId implements RegionPropertyId { - REGION_PROPERTY_1_BOOLEAN_MUTABLE( - PropertyDefinition .builder()// - .setType(Boolean.class)// - .setDefaultValue(false)// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME)// - .build()), // - REGION_PROPERTY_2_INTEGER_MUTABLE( - PropertyDefinition .builder()// - .setType(Integer.class)// - .setDefaultValue(0)// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME)// - .build()// - ), // - REGION_PROPERTY_3_DOUBLE_MUTABLE( - PropertyDefinition .builder()// - .setType(Double.class)// - .setDefaultValue(0.0)// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME)// - .build()// - ), // - REGION_PROPERTY_4_BOOLEAN_IMMUTABLE( - PropertyDefinition .builder()// - .setType(Boolean.class)// - .setDefaultValue(false)// - .setPropertyValueMutability(false)// - .setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME)// - .build()// - ), // - REGION_PROPERTY_5_INTEGER_IMMUTABLE( - PropertyDefinition .builder()// - .setType(Integer.class)// - .setDefaultValue(0)// - .setPropertyValueMutability(false)// - .setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME)// - .build()// - ), // - REGION_PROPERTY_6_DOUBLE_IMMUTABLE( - PropertyDefinition .builder()// - .setType(Double.class)// - .setDefaultValue(0.0)// - .setPropertyValueMutability(false)// - .setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME)// - .build()// - ), // - - ; - - private final PropertyDefinition propertyDefinition; - - private TestRegionPropertyId(PropertyDefinition propertyDefinition) { - this.propertyDefinition = propertyDefinition; - } - - public PropertyDefinition getPropertyDefinition() { - return propertyDefinition; - } - - /** - * Returns a randomly selected member of this enumeration - */ - public static TestRegionPropertyId getRandomRegionPropertyId(final RandomGenerator randomGenerator) { - return TestRegionPropertyId.values()[randomGenerator.nextInt(TestRegionPropertyId.values().length)]; - } - - public static TestRegionPropertyId getRandomMutableRegionPropertyId(final RandomGenerator randomGenerator) { - List candidates = new ArrayList<>(); - for(TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { - if(testRegionPropertyId.getPropertyDefinition().propertyValuesAreMutable()) { - candidates.add(testRegionPropertyId); - } - } - return candidates.get(randomGenerator.nextInt(candidates.size())); - } - - /** - * Return the size of this enum - */ - public static int size() { - return values().length; - } - - /** - * Returns a new {@link RegionPropertyId} instance. - */ - public static RegionPropertyId getUnknownRegionPropertyId() { - return new RegionPropertyId() { - }; - } - - /** - * Returns a randomly selected value that is compatible with this member's - * associated property definition. - * - */ - @SuppressWarnings("unchecked") - public T getRandomPropertyValue(final RandomGenerator randomGenerator) { - switch (this) { - case REGION_PROPERTY_1_BOOLEAN_MUTABLE: - Boolean b1 = randomGenerator.nextBoolean(); - return (T) b1; - case REGION_PROPERTY_2_INTEGER_MUTABLE: - Integer i2 = randomGenerator.nextInt(); - return (T) i2; - case REGION_PROPERTY_3_DOUBLE_MUTABLE: - Double d3 = randomGenerator.nextDouble(); - return (T) d3; - case REGION_PROPERTY_4_BOOLEAN_IMMUTABLE: - Boolean b4 = randomGenerator.nextBoolean(); - return (T) b4; - case REGION_PROPERTY_5_INTEGER_IMMUTABLE: - Integer i5 = randomGenerator.nextInt(); - return (T) i5; - case REGION_PROPERTY_6_DOUBLE_IMMUTABLE: - Double d6 = randomGenerator.nextDouble(); - return (T) d6; - default: - throw new RuntimeException("unhandled case: " + this); - - } - } - -} \ No newline at end of file diff --git a/gcm3/src/main/java/plugins/reports/ReportsPlugin.java b/gcm3/src/main/java/plugins/reports/ReportsPlugin.java deleted file mode 100644 index 0267bffc6..000000000 --- a/gcm3/src/main/java/plugins/reports/ReportsPlugin.java +++ /dev/null @@ -1,53 +0,0 @@ -package plugins.reports; - -import java.util.function.Consumer; - -import nucleus.ActorContext; -import nucleus.Plugin; - -/** - * A plugin providing a report actors to the simulation. - * - * @author Shawn Hatch - * - */ -public class ReportsPlugin { - - private ReportsPlugin() { - } - - /** - * Returns the report plugin. - * - *

        - * Uses ReportsPluginId.PLUGIN_ID as its id - *

        - * - *

        - * Depends on no plugins - *

        - * - *

        - * Provides no data mangers: - *

        - * - *

        - * Provides report actors based on content in the - * {@linkplain ReportsPluginData} - *

        - * - */ - - public static Plugin getReportPlugin(ReportsPluginData reportsPluginData) { - return Plugin .builder()// - .addPluginData(reportsPluginData)// - .setPluginId(ReportsPluginId.PLUGIN_ID)// - .setInitializer((c) -> { - ReportsPluginData pluginData = c.getPluginData(ReportsPluginData.class); - for (Consumer consumer : pluginData.getReports()) { - c.addActor(consumer); - } - }).build();// - } - -} diff --git a/gcm3/src/main/java/plugins/reports/ReportsPluginData.java b/gcm3/src/main/java/plugins/reports/ReportsPluginData.java deleted file mode 100644 index ce84f9714..000000000 --- a/gcm3/src/main/java/plugins/reports/ReportsPluginData.java +++ /dev/null @@ -1,112 +0,0 @@ -package plugins.reports; - -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.function.Consumer; -import java.util.function.Supplier; - -import net.jcip.annotations.Immutable; -import nucleus.ActorContext; -import nucleus.PluginData; -import nucleus.PluginDataBuilder; -import plugins.reports.support.ReportError; -import util.errors.ContractException; - -/** - * An immutable container of the initial state of report actors. It contains:
        - *
          - *
        • report ids
        • - *
        • suppliers of consumers of {@linkplain ReportContext} for report - * initialization
        • - *
        - * - * @author Shawn Hatch - * - */ -@Immutable -public final class ReportsPluginData implements PluginData { - - private static class Data { - private Set>> reports = new LinkedHashSet<>(); - - public Data() { - } - - public Data(Data data) { - this.reports.addAll(data.reports); - } - } - - public final static Builder builder() { - return new Builder(new Data()); - } - - public final static class Builder implements PluginDataBuilder { - private Data data; - - private Builder(Data data) { - this.data = data; - } - - /** - * Returns the {@link ReportsInitialData} from the input collected by - * this builder. Clears the state of the builder. - */ - public ReportsPluginData build() { - try { - return new ReportsPluginData(data); - } finally { - data = new Data(); - } - } - - /** - * Adds the report via its id and it initial behavior as supplied by the - * given supplier. - * - * @throws ContractException - *
      • {@linkplain ReportError#NULL_SUPPLIER} if the - * supplier is null
      • - */ - public Builder addReport(Supplier> supplier) { - - if (supplier == null) { - throw new ContractException(ReportError.NULL_SUPPLIER); - } - data.reports.add(supplier); - return this; - } - - } - - private final Data data; - - private ReportsPluginData(Data data) { - this.data = data; - } - - /** - * Returns the Consumer of ReportContext associated with the given report id - * - * @throws ContractException - *
      • {@linkplain ReportError#NULL_REPORT_ID} if the report id - * is null
      • - *
      • {@linkplain ReportError#UNKNOWN_REPORT_ID} if the report - * id is unknown
      • - *
      • {@linkplain ReportError#NULL_CONSUMER} if the consumer - * generated by the supplier is null
      • - */ - public Set> getReports() { - Set> result= new LinkedHashSet<>(); - for(Supplier> supplier : data.reports) { - result.add( supplier.get()); - } - return result; - } - - @Override - public PluginDataBuilder getCloneBuilder() { - return new Builder(new Data(data)); - } - -} diff --git a/gcm3/src/main/java/plugins/reports/ReportsPluginId.java b/gcm3/src/main/java/plugins/reports/ReportsPluginId.java deleted file mode 100644 index 93567da4e..000000000 --- a/gcm3/src/main/java/plugins/reports/ReportsPluginId.java +++ /dev/null @@ -1,15 +0,0 @@ -package plugins.reports; - -import nucleus.PluginId; - -/** - * Static plugin id implementation for the ReportsPlugin - * - * @author Shawn Hatch - * - */ - -public final class ReportsPluginId implements PluginId { - public final static PluginId PLUGIN_ID = new ReportsPluginId(); - private ReportsPluginId() {}; -} diff --git a/gcm3/src/main/java/plugins/reports/support/LineWriter.java b/gcm3/src/main/java/plugins/reports/support/LineWriter.java deleted file mode 100644 index 9e898fde0..000000000 --- a/gcm3/src/main/java/plugins/reports/support/LineWriter.java +++ /dev/null @@ -1,210 +0,0 @@ -package plugins.reports.support; - -import java.io.BufferedWriter; -import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.nio.charset.CharsetEncoder; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import net.jcip.annotations.GuardedBy; -import net.jcip.annotations.ThreadSafe; -import nucleus.ExperimentContext; -import nucleus.ScenarioStatus; - -/** - * A thread-safe utility that supports tab delimited text based files that have - * a header. This utility manages the writing of report items to a single file. - * It assumes that all such items share a uniform header and establishes the - * header of the output file on the first report item. If the writer is resuming - * from a previous experiment, the header remains at originally written. - * Supports continuation of experiment progress across multiple experiment runs. - * - * @author Shawn Hatch - * - */ -@ThreadSafe -public final class LineWriter { - - private final boolean useExperimentColumns; - private static final String lineSeparator = System.getProperty("line.separator"); - private final Object headerLock = new Object(); - private final BufferedWriter writer; - - @GuardedBy(value = "headerLock") - private boolean headerWritten; - - /** - * Creates this {@link NIOHeaderedOutputItemHandler} The path to the file - * that may or may not exist and may contain some complete or partial - * content from a previous execution of the experiment. If not empty, this - * file must have a header, be tab delimited and have as its first column be - * the scenario id. Partial lines at the end of the file due to an - * ungraceful halt to the previous execution are tolerated. If the file does - * not exist, then its parent directory must exist. - * - * @throws RuntimeException - *
      • if an {@link IOException} is thrown
      • - * - */ - - public LineWriter(final ExperimentContext experimentContext, final Path path, final boolean displayExperimentColumnsInReports) { - - this.useExperimentColumns = displayExperimentColumnsInReports; - - try { - - List outputLines = new ArrayList<>(); - String headerLine = null; - - /* - * If the file is readable then we accept only those lines that - * correspond to a previously executed scenario - */ - if (Files.isRegularFile(path)) { - List inputLines = Files.readAllLines(path); - boolean header = true; - for (String line : inputLines) { - if (!header) { - String[] fields = line.split("\t"); - /* - * It is possible that the last line of a file was only - * partially written because neither the writter's close - * or flush was called during an abrupt shutdown. We - * expect that such cases will not correspond to - * successfully completed simulation execution, but must - * ensure that the parsing of the scenario and - * replication ids can still be performed - */ - if (fields.length > 1) { - int scenarioId = Integer.parseInt(fields[0]); - Optional optional = experimentContext.getScenarioStatus(scenarioId); - if (optional.isPresent() && optional.get().equals(ScenarioStatus.PREVIOUSLY_SUCCEEDED)) { - outputLines.add(line); - } - } - } else { - headerLine = line; - header = false; - } - } - } - - /* - * Remove the old file and write to the file the header and any - * retained lines from the previous execution. - */ - Files.deleteIfExists(path); - CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder(); - OutputStream out = Files.newOutputStream(path, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); - writer = new BufferedWriter(new OutputStreamWriter(out, encoder)); - - if (!outputLines.isEmpty()) { - writer.write(headerLine); - writer.newLine(); - headerWritten = true; - } - - for (String line : outputLines) { - writer.write(line); - writer.newLine(); - } - - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - /** - * Closes the writer, flushing all buffered outputs. - * - * @throws RuntimeException - *
      • if an {@link IOException} is thrown
      • - */ - public void close() { - - try { - writer.close(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - /** - * Writes the report item to file recorded under the given scenario. - * - * @throws RuntimeException - *
      • if an {@link IOException} is thrown
      • - */ - public void write(ExperimentContext experimentContext, int scenarioId, ReportItem reportItem) { - - try { - synchronized (headerLock) { - if (!headerWritten) { - final StringBuilder sb = new StringBuilder(); - - sb.append("scenario"); - - if (useExperimentColumns) { - for (String item : experimentContext.getExperimentMetaData()) { - sb.append("\t"); - sb.append(item); - } - } - - final List headerStrings = reportItem.getReportHeader().getHeaderStrings(); - for (final String headerString : headerStrings) { - sb.append("\t"); - sb.append(headerString); - } - - sb.append(lineSeparator); - writer.write(sb.toString()); - headerWritten = true; - } - } - - final StringBuilder sb = new StringBuilder(); - - sb.append(scenarioId); - if (useExperimentColumns) { - List metaData = experimentContext.getScenarioMetaData(scenarioId).get(); - for (String item : metaData) { - sb.append("\t"); - sb.append(item); - } - } - - for (int i = 0; i < reportItem.size(); i++) { - sb.append("\t"); - sb.append(reportItem.getValue(i)); - } - sb.append(lineSeparator); - writer.write(sb.toString()); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - /** - * Flushes buffered output. Generally used to force the last the full - * reporting of a closed scenario. - * - * @throws RuntimeException - *
      • if an {@link IOException} is thrown
      • - */ - public void flush() { - try { - writer.flush(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - -} \ No newline at end of file diff --git a/gcm3/src/main/java/plugins/reports/support/NIOReportItemHandler.java b/gcm3/src/main/java/plugins/reports/support/NIOReportItemHandler.java deleted file mode 100644 index 4f27c4f27..000000000 --- a/gcm3/src/main/java/plugins/reports/support/NIOReportItemHandler.java +++ /dev/null @@ -1,174 +0,0 @@ -package plugins.reports.support; - -import java.nio.file.Path; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; - -import nucleus.ExperimentContext; -import util.errors.ContractException; - -/** - * An experiment-level output management utility for writing report items to - * multiple files. - * - * @author Shawn Hatch - * - */ -public final class NIOReportItemHandler { - - public static Builder builder() { - return new Builder(); - } - - /** - * Builder class for NIOReportItemHandlerImpl - * - * @author Shawn Hatch - * - */ - public static class Builder { - - private Builder() { - } - - private Data data = new Data(); - - /** - * Add a report by class reference to the NIOReportItemHandler - * - * @throws ContractException - * - *
      • {@linkplain ReportError#NULL_REPORT_ID} if the report - * id is null
      • - *
      • {@linkplain ReportError#NULL_REPORT_PATH} if the path - * is null
      • - * - * - */ - public Builder addReport(final ReportId reportId, final Path path) { - if (path == null) { - throw new ContractException(ReportError.NULL_REPORT_PATH); - } - if (reportId == null) { - throw new ContractException(ReportError.NULL_REPORT_ID); - } - data.reportMap.put(reportId, path); - return this; - } - - private void validate() { - /* - * Ensure that each path is associated with exactly one report id - */ - final Map pathMap = new LinkedHashMap<>(); - for (final ReportId reportId : data.reportMap.keySet()) { - final Path path = data.reportMap.get(reportId); - if (pathMap.containsKey(path)) { - throw new ContractException(ReportError.PATH_COLLISION, path); - } - pathMap.put(path, reportId); - } - - } - - /** - * Builds the NIOReportItemHandlerImpl from the information gathered and - * resets the internal state of this builder. - * - * @throws ContractException - *
      • {@linkplain ReportError#PATH_COLLISION} if multiple - * reports are assigned the same path
      • - * - */ - public NIOReportItemHandler build() { - try { - validate(); - return new NIOReportItemHandler(data); - } finally { - data = new Data(); - } - } - - /** - * Sets the display of experiment columns in all reports. Default value - * is true. - */ - public Builder setDisplayExperimentColumnsInReports(final boolean displayExperimentColumnsInReports) { - data.displayExperimentColumnsInReports = displayExperimentColumnsInReports; - return this; - } - } - - private static class Data { - private final Map reportMap = new LinkedHashMap<>(); - private boolean displayExperimentColumnsInReports = DEFAULT_DISPLAY_EXPERIMENT_COLUMNS; - } - - private final static boolean DEFAULT_DISPLAY_EXPERIMENT_COLUMNS = true; - - private final Map lineWriterMap = Collections.synchronizedMap(new LinkedHashMap<>()); - - private final Map reportMap; - - private final boolean displayExperimentColumnsInReports; - - private NIOReportItemHandler(final Data data) { - - reportMap = data.reportMap; - displayExperimentColumnsInReports = data.displayExperimentColumnsInReports; - } - - private void closeExperiment(ExperimentContext experimentContext) { - synchronized (lineWriterMap) { - for (final LineWriter lineWriter : lineWriterMap.values()) { - lineWriter.close(); - } - } - } - - private void closeSimulation(ExperimentContext experimentContext, final Integer scenarioId) { - synchronized (lineWriterMap) { - for (final LineWriter lineWriter : lineWriterMap.values()) { - lineWriter.flush(); - } - } - } - - private void handleOuput(ExperimentContext experimentContext, Integer scenarioId, ReportItem reportItem) { - final LineWriter lineWriter = lineWriterMap.get(reportItem.getReportId()); - if (lineWriter != null) { - lineWriter.write(experimentContext, scenarioId, reportItem); - } - } - - private void openExperiment(ExperimentContext experimentContext) { - synchronized (lineWriterMap) { - for (final ReportId reportId : reportMap.keySet()) { - final Path path = reportMap.get(reportId); - final LineWriter lineWriter = new LineWriter(experimentContext, path, displayExperimentColumnsInReports); - lineWriterMap.put(reportId, lineWriter); - } - } - } - - /** - * Initializes this report item handler. It subscribes to the following - * experiment level events: - *
          - *
        • Experiment Open : reads and initializes all report files
        • - *
        • Simulation Output : directs report items to the appropriate file - * writer
        • - *
        • Simulation Close : ensures all files are flushed so that the content - * of each file is complete for each closed scenario
        • - *
        • Experiment Close : closes all file writers
        • - *
        - */ - public void init(ExperimentContext experimentContext) { - experimentContext.subscribeToExperimentOpen(this::openExperiment); - experimentContext.subscribeToExperimentClose(this::closeExperiment); - experimentContext.subscribeToSimulationClose(this::closeSimulation); - experimentContext.subscribeToOutput(ReportItem.class, this::handleOuput); - } - -} diff --git a/gcm3/src/main/java/plugins/reports/support/PeriodicReport.java b/gcm3/src/main/java/plugins/reports/support/PeriodicReport.java deleted file mode 100644 index f6ca87521..000000000 --- a/gcm3/src/main/java/plugins/reports/support/PeriodicReport.java +++ /dev/null @@ -1,210 +0,0 @@ -package plugins.reports.support; - -import java.util.function.BiConsumer; - -import nucleus.ActorContext; -import nucleus.Event; -import util.errors.ContractException; - -/** - * The abstract base class for reports that aggregate reporting aligned to a - * {@link ReportPeriod}. The periodic report continually schedules reporting on - * a regular cycle, invoking the flush method and allowing descendant - * implementors to release collected data. This report registers via the context - * to be alerted when the simulation terminates and will perform a final flush - * invocation. This can result in the duplication of time values for last data - * released. - * - * @author Shawn Hatch - * - */ -public abstract class PeriodicReport { - - /** - * Creates the periodic report from the given report period - * - * @throws ContractException - *
      • if the report period is null
      • - */ - public PeriodicReport(ReportId reportId, ReportPeriod reportPeriod) { - if (reportPeriod == null) { - throw new ContractException(ReportError.NULL_REPORT_PERIOD); - } - this.reportPeriod = reportPeriod; - - if (reportId == null) { - throw new ContractException(ReportError.NULL_REPORT_ID); - } - this.reportId = reportId; - } - - /* - * Assume a daily report period and let it be overridden - */ - private ReportPeriod reportPeriod = ReportPeriod.DAILY; - - private ReportId reportId; - - /* - * The day value to be used in report lines - */ - private Integer reportingDay = 0; - - /* - * The hour value to be used in report lines - */ - private Integer reportingHour = 0; - - /** - * Adds the time field column(s) to the given {@link ReportHeaderBuilder} as - * appropriate to the {@link ReportPeriod} specified during construction. - * - * DAILY : Day - * - * HOURLY : Day, Hour - * - * END_OF_SIMULATION has no header additions - */ - protected final ReportHeader.Builder addTimeFieldHeaders(ReportHeader.Builder reportHeaderBuilder) { - switch (reportPeriod) { - case DAILY: - reportHeaderBuilder.add("day"); - break; - case END_OF_SIMULATION: - // do nothing - break; - case HOURLY: - reportHeaderBuilder.add("day"); - reportHeaderBuilder.add("hour"); - break; - default: - throw new RuntimeException("unknown report period " + reportPeriod); - } - return reportHeaderBuilder; - } - - protected final ReportId getReportId() { - return reportId; - } - - /** - * Places the current reporting day and report hour on the report as - * appropriate to the {@link ReportPeriod} specified during construction. - * - */ - protected final void fillTimeFields(final ReportItem.Builder reportItemBuilder) { - - switch (reportPeriod) { - case DAILY: - reportItemBuilder.addValue(reportingDay); - break; - case END_OF_SIMULATION: - // do nothing - break; - case HOURLY: - reportItemBuilder.addValue(reportingDay); - reportItemBuilder.addValue(reportingHour % 24); - break; - default: - throw new RuntimeException("unknown report period " + reportPeriod); - } - } - - /** - * Subscribes to simulation close. Initializes periodic flushing of report - * contents with the first flush scheduled for one time period from - * simulation start. Descendant implementors of PeriodicReport must invoke - * super.init(). - * - * @throws ContractException - *
      • if the report context is null
      • - * - */ - public void init(ActorContext actorContext) { - - if (actorContext == null) { - throw new ContractException(ReportError.NULL_CONTEXT); - } - - actorContext.subscribeToSimulationClose(this::close); - - if (reportPeriod != ReportPeriod.END_OF_SIMULATION) { - setNextPlanTime(); - actorContext.addPassivePlan(this::executePlan, nextPlanTime); - } - } - - private void close(final ActorContext actorContext) { - if (lastFlushTime == null || actorContext.getTime() > lastFlushTime) { - lastFlushTime = actorContext.getTime(); - flush(actorContext); - } - } - - /** - * Provides descendant implementors the opportunity to releases report items - * from the data stored during the time since the last invocation of - * flush(). - */ - protected abstract void flush(final ActorContext actorContext); - - private double nextPlanTime; - private Double lastFlushTime; - - private void setNextPlanTime() { - switch (reportPeriod) { - case DAILY: - nextPlanTime = (reportingDay + 1); - break; - case HOURLY: - nextPlanTime = reportingDay + (double) (reportingHour + 1) / 24; - break; - default: - throw new RuntimeException("unhandled report period " + reportPeriod); - } - } - - /** - * Returns a wrapped version of the given consumer that will ensure proper - * flushing when events are received at the same time as a flushing plan, - * but happen to execute before the plan. Descendant implementors of - * PeriodicReport should use this wrapper when subscribing to events. - */ - protected final BiConsumer getFlushingConsumer(BiConsumer eventConsumer) { - return (c, t) -> { - if (c.getTime() >= nextPlanTime) { - if (lastFlushTime == null || c.getTime() > lastFlushTime) { - lastFlushTime = c.getTime(); - flush(c); - } - } - eventConsumer.accept(c, t); - }; - } - - private void executePlan(final ActorContext actorContext) { - if (lastFlushTime == null || actorContext.getTime() > lastFlushTime) { - lastFlushTime = actorContext.getTime(); - flush(actorContext); - } - - switch (reportPeriod) { - case DAILY: - reportingDay++; - break; - case HOURLY: - reportingHour++; - if (reportingHour == 24) { - reportingHour = 0; - reportingDay++; - } - break; - default: - throw new RuntimeException("unhandled report period " + reportPeriod); - } - - setNextPlanTime(); - actorContext.addPassivePlan(this::executePlan, nextPlanTime); - - } -} diff --git a/gcm3/src/main/java/plugins/reports/support/ReportError.java b/gcm3/src/main/java/plugins/reports/support/ReportError.java deleted file mode 100644 index 1418dfe8f..000000000 --- a/gcm3/src/main/java/plugins/reports/support/ReportError.java +++ /dev/null @@ -1,40 +0,0 @@ -package plugins.reports.support; - -import util.errors.ContractError; -import util.errors.ContractException; - -/** - * An enumeration supporting {@link ContractException} that acts as a general - * description of the exception. - * - * @author Shawn Hatch - * - */ -public enum ReportError implements ContractError { - NULL_CONSUMER("Supplier of Consumer of ActorContext supplied a null consumer"), - NULL_SUPPLIER("Supplier of Consumer of ActorContext is null"), - NULL_REPORT_ID("Null report id"), - NULL_REPORT_PATH("Null report path"), - PATH_COLLISION("Report path shared between multiple reports"), - UNKNOWN_REPORT_ID("Unknown report id"), - NULL_REPORT_INITIAL_DATA("Null report initial data"), - DUPLICATE_REPORT("Duplicate report id"), - NULL_CONTEXT("Null context"), - NULL_REPORT_PERIOD("Null report period"), - NULL_REPORT_HEADER_STRING("Null report header string"), - NULL_REPORT_HEADER("Null report header"), - NULL_REPORT_ITEM_ENTRY("Null report item entry"), - UNSUPPORTED_REPORT_PERIOD("Unsupported report period") - ; - - @Override - public String getDescription() { - return description; - } - - private final String description; - - private ReportError(String description) { - this.description = description; - } -} diff --git a/gcm3/src/main/java/plugins/reports/support/ReportId.java b/gcm3/src/main/java/plugins/reports/support/ReportId.java deleted file mode 100644 index f053794b5..000000000 --- a/gcm3/src/main/java/plugins/reports/support/ReportId.java +++ /dev/null @@ -1,16 +0,0 @@ -package plugins.reports.support; - -import net.jcip.annotations.ThreadSafe; - -/** - * Marker interface for the unique report identifiers. Report items are marked - * with a report id that allows an output manager to determine the final - * disposition of the report item. Report ids must be thread-safe. - * - * @author Shawn Hatch - * - */ -@ThreadSafe -public interface ReportId { - -} diff --git a/gcm3/src/main/java/plugins/reports/support/ReportItem.java b/gcm3/src/main/java/plugins/reports/support/ReportItem.java deleted file mode 100644 index 63eb4c433..000000000 --- a/gcm3/src/main/java/plugins/reports/support/ReportItem.java +++ /dev/null @@ -1,233 +0,0 @@ -package plugins.reports.support; - -import java.util.ArrayList; -import java.util.List; - -import net.jcip.annotations.NotThreadSafe; -import net.jcip.annotations.ThreadSafe; -import util.errors.ContractException; - -/** - * A thread safe(immutable), container that supports output lines for multiple - * reports. The values contained in a report item should be immutable and - * support toString(). - * - * @author Shawn Hatch - * - */ -@ThreadSafe -public final class ReportItem { - - /** - * Returns a new Builder instance. - */ - public static Builder builder() { - return new Builder(); - } - - @NotThreadSafe - public final static class Builder { - private Builder() { - } - - private Scaffold scaffold = new Scaffold(); - - /** - * Adds an entry's string value to the report item. Order should follow the order in the - * {@link ReportHeader} - * - * @throws ContractException - *
      • if the entry is null
      • - */ - public Builder addValue(final Object entry) { - if (entry == null) { - throw new ContractException(ReportError.NULL_REPORT_ITEM_ENTRY); - } - scaffold.values.add(entry.toString()); - return this; - } - - /* - * Null checks for the various fields. - */ - private void validateData() { - - if (scaffold.reportHeader == null) { - throw new ContractException(ReportError.NULL_REPORT_HEADER); - } - - if (scaffold.reportId == null) { - throw new ContractException(ReportError.NULL_REPORT_ID); - } - - } - - /** - * Builds the {@link ReportItem} from the colleced data. - * - * - * @throws ContractException - *
      • {@linkplain ReportError#NULL_REPORT_HEADER} if the collected report header is null
      • - *
      • {@linkplain ReportError#NULL_REPORT_ID} if the collected report id is null
      • - * - */ - public ReportItem build() { - try { - validateData(); - return new ReportItem(scaffold); - } finally { - scaffold = new Scaffold(); - } - } - - /** - * Sets the associated {@link ReportHeader} for this {@link ReportItem}. - * The report header and the report item should have the same order of - * added fiels values. - */ - public Builder setReportHeader(ReportHeader reportHeader) { - if (reportHeader == null) { - throw new ContractException(ReportError.NULL_REPORT_HEADER); - } - scaffold.reportHeader = reportHeader; - return this; - } - - /** - * Sets the report type for this {@link ReportItem}. The report type - * should be the class type of the report that authors the report item. - */ - public Builder setReportId(ReportId reportId) { - if (reportId == null) { - throw new ContractException(ReportError.NULL_REPORT_ID); - } - scaffold.reportId = reportId; - return this; - } - - } - - private static class Scaffold { - private ReportId reportId; - private ReportHeader reportHeader; - private final List values = new ArrayList<>(); - } - - private final ReportId reportId; - - private final List values; - - private final ReportHeader reportHeader; - - private ReportItem(final Scaffold scaffold) { - reportId = scaffold.reportId; - reportHeader = scaffold.reportHeader; - values = scaffold.values; - } - - /** - * Returns the report id for this report item - */ - public ReportId getReportId() { - return reportId; - } - - /** - * Returns the report header for this report item - */ - public ReportHeader getReportHeader() { - return reportHeader; - } - - /** - * Returns the string value stored at the given index - * - *@throws IndexOutOfBoundsException - *
      • if the index < 0
      • - *
      • if the index >= size()
      • - */ - public String getValue(final int index) { - return values.get(index); - } - - /** - * Returns the number of values stored in this report item - * - * @return - */ - public int size() { - return values.size(); - } - - /** - * A string listing the values as added to this ReportItem delimited by - * commas in the form: - * - * ReportItem - * [reportType=reportType,reportHeader=reportHeader,values=[value1, - * value2...]] - */ - @Override - public String toString() { - StringBuilder builder2 = new StringBuilder(); - builder2.append("ReportItem [reportId="); - builder2.append(reportId); - builder2.append(", reportHeader="); - builder2.append(reportHeader); - builder2.append(", values="); - builder2.append(values); - builder2.append("]"); - return builder2.toString(); - } - - /** - * Standard hash code implementation - */ - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((reportHeader == null) ? 0 : reportHeader.hashCode()); - result = prime * result + ((reportId == null) ? 0 : reportId.hashCode()); - result = prime * result + ((values == null) ? 0 : values.hashCode()); - return result; - } - - /** - * Two report items are equal iff and only if their ids, headers and ordered - * values are equal. - */ - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof ReportItem)) { - return false; - } - ReportItem other = (ReportItem) obj; - if (reportHeader == null) { - if (other.reportHeader != null) { - return false; - } - } else if (!reportHeader.equals(other.reportHeader)) { - return false; - } - if (reportId == null) { - if (other.reportId != null) { - return false; - } - } else if (!reportId.equals(other.reportId)) { - return false; - } - if (values == null) { - if (other.values != null) { - return false; - } - } else if (!values.equals(other.values)) { - return false; - } - return true; - } - -} diff --git a/gcm3/src/main/java/plugins/reports/support/ReportPeriod.java b/gcm3/src/main/java/plugins/reports/support/ReportPeriod.java deleted file mode 100644 index 3fcb53d70..000000000 --- a/gcm3/src/main/java/plugins/reports/support/ReportPeriod.java +++ /dev/null @@ -1,12 +0,0 @@ -package plugins.reports.support; - -/** - * An enumeration supporting {@link PeriodicReport} that represents the - * periodicity of the report. - * - * @author Shawn Hatch - * - */ -public enum ReportPeriod { - HOURLY, DAILY, END_OF_SIMULATION -} diff --git a/gcm3/src/main/java/plugins/reports/support/SimpleReportId.java b/gcm3/src/main/java/plugins/reports/support/SimpleReportId.java deleted file mode 100644 index 59b57e31b..000000000 --- a/gcm3/src/main/java/plugins/reports/support/SimpleReportId.java +++ /dev/null @@ -1,69 +0,0 @@ -package plugins.reports.support; - -import net.jcip.annotations.Immutable; -import util.errors.ContractException; - -/** - * A convenience implementor of ReportId that wraps a value. - * - * @author Shawn Hatch - * - */ -@Immutable -public final class SimpleReportId implements ReportId { - - private final Object value; - - /** - * Creates a ReportId from a value. The value must implement a proper equals - * contract and be immutable. - * - * - * @throws ContractException - *
      • {@linkplain ReportError#NULL_REPORT_ID} if the value is - * null
      • - */ - public SimpleReportId(Object value) { - if (value == null) { - throw new ContractException(ReportError.NULL_REPORT_ID); - } - this.value = value; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("SimpleReportId [value="); - builder.append(value); - builder.append("]"); - return builder.toString(); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((value == null) ? 0 : value.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof SimpleReportId)) { - return false; - } - SimpleReportId other = (SimpleReportId) obj; - if (value == null) { - if (other.value != null) { - return false; - } - } else if (!value.equals(other.value)) { - return false; - } - return true; - } - -} diff --git a/gcm3/src/main/java/plugins/reports/testsupport/TestReportItemOutputConsumer.java b/gcm3/src/main/java/plugins/reports/testsupport/TestReportItemOutputConsumer.java deleted file mode 100644 index 0acc9b584..000000000 --- a/gcm3/src/main/java/plugins/reports/testsupport/TestReportItemOutputConsumer.java +++ /dev/null @@ -1,66 +0,0 @@ -package plugins.reports.testsupport; - -import java.util.LinkedHashMap; -import java.util.Map; - -import nucleus.ExperimentContext; -import plugins.reports.support.ReportItem; -import util.wrappers.MutableInteger; - -/** - * Output consumer of report items that counts the number of times each report - * item is added. Intended for experiment-level test support where report items - * are collected from a simulation execution in one instance of this container - * while expected report items are collected in a separate instance. - */ -public final class TestReportItemOutputConsumer { - private Map> reportItems = new LinkedHashMap<>(); - - /** - * Stores the {@link ReportItem} output, keep counts on duplicates. - * - * @throws RuntimeException - * if the input is not a {@link ReportItem} - */ - - private void handleReportItem(ExperimentContext experimentContext, Integer scenarioId, ReportItem reportItem) { - Map map = reportItems.get(scenarioId); - if (map == null) { - map = new LinkedHashMap<>(); - reportItems.put(scenarioId, map); - } - MutableInteger counter = map.get(reportItem); - if (counter == null) { - counter = new MutableInteger(); - map.put(reportItem, counter); - } - counter.increment(); - } - - /** - * Initialize this output consumer. The output consumer registers for - * ReportItem handling. - */ - public synchronized void init(ExperimentContext experimentContext) { - experimentContext.subscribeToOutput(ReportItem.class, this::handleReportItem); - } - - /** - * Returns a Map from scenario id to a Map from report item to an Integer - * count of the number of times that report item was received by this output - * consumer. - */ - public Map> getReportItems() { - Map> result = new LinkedHashMap<>(); - for (Integer sceanarioId : reportItems.keySet()) { - Map sourceMap = reportItems.get(sceanarioId); - Map destinationMap = new LinkedHashMap<>(); - result.put(sceanarioId, destinationMap); - for (ReportItem reportItem : sourceMap.keySet()) { - MutableInteger mutableInteger = sourceMap.get(reportItem); - destinationMap.put(reportItem, mutableInteger.getValue()); - } - } - return result; - } -} \ No newline at end of file diff --git a/gcm3/src/main/java/plugins/resources/ResourcesPlugin.java b/gcm3/src/main/java/plugins/resources/ResourcesPlugin.java deleted file mode 100644 index d3b424e48..000000000 --- a/gcm3/src/main/java/plugins/resources/ResourcesPlugin.java +++ /dev/null @@ -1,27 +0,0 @@ -package plugins.resources; - -import nucleus.Plugin; -import plugins.people.PeoplePluginId; -import plugins.regions.RegionsPluginId; -import plugins.resources.datamanagers.ResourcesDataManager; - -public final class ResourcesPlugin { - - private ResourcesPlugin() { - } - - public static Plugin getResourcesPlugin(ResourcesPluginData resourcesPluginData) { - - return Plugin .builder()// - .setPluginId(ResourcesPluginId.PLUGIN_ID)// - .addPluginData(resourcesPluginData)// - .addPluginDependency(PeoplePluginId.PLUGIN_ID)// - .addPluginDependency(RegionsPluginId.PLUGIN_ID)// - .setInitializer((c) -> { - ResourcesPluginData pluginData = c.getPluginData(ResourcesPluginData.class); - c.addDataManager(new ResourcesDataManager(pluginData)); - })// - .build(); - } - -} diff --git a/gcm3/src/main/java/plugins/resources/ResourcesPluginData.java b/gcm3/src/main/java/plugins/resources/ResourcesPluginData.java deleted file mode 100644 index ef2171500..000000000 --- a/gcm3/src/main/java/plugins/resources/ResourcesPluginData.java +++ /dev/null @@ -1,744 +0,0 @@ -package plugins.resources; - -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Optional; -import java.util.Set; - -import net.jcip.annotations.Immutable; -import nucleus.PluginData; -import nucleus.PluginDataBuilder; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import plugins.regions.support.RegionError; -import plugins.regions.support.RegionId; -import plugins.resources.support.ResourceError; -import plugins.resources.support.ResourceId; -import plugins.resources.support.ResourcePropertyId; -import plugins.util.properties.PropertyDefinition; -import plugins.util.properties.TimeTrackingPolicy; -import util.errors.ContractException; - -/** - * An immutable container of the initial state of resources. It contains:
        - *
          - *
        • resource ids
        • - *
        • resource property definitions
        • - *
        • region resource levels
        • - *
        • person resource levels
        • - *
        - * - * @author Shawn Hatch - * - */ -@Immutable -public final class ResourcesPluginData implements PluginData { - - private static class Data { - - private final Map> resourcePropertyDefinitions; - - private final Map> resourcePropertyValues; - - private final Map> personResourceLevels; - - private final Set resourceIds; - - private final Map> regionResourceLevels; - - private final Map resourceTimeTrackingPolicies; - - public Data() { - resourcePropertyDefinitions = new LinkedHashMap<>(); - resourcePropertyValues = new LinkedHashMap<>(); - personResourceLevels = new LinkedHashMap<>(); - resourceIds = new LinkedHashSet<>(); - regionResourceLevels = new LinkedHashMap<>(); - resourceTimeTrackingPolicies = new LinkedHashMap<>(); - } - - public Data(Data data) { - - resourcePropertyDefinitions = new LinkedHashMap<>(); - for(ResourceId resourceId : data.resourcePropertyDefinitions.keySet()) { - Map map = data.resourcePropertyDefinitions.get(resourceId); - Map newMap = new LinkedHashMap<>(map); - resourcePropertyDefinitions.put(resourceId, newMap); - } - - resourcePropertyValues = new LinkedHashMap<>(); - for(ResourceId resourceId : data.resourcePropertyValues.keySet()) { - Map map = data.resourcePropertyValues.get(resourceId); - Map newMap = new LinkedHashMap<>(map); - resourcePropertyValues.put(resourceId, newMap); - } - - personResourceLevels = new LinkedHashMap<>(); - for(PersonId personId : data.personResourceLevels.keySet()) { - Map map = data.personResourceLevels.get(personId); - Map newMap = new LinkedHashMap<>(map); - personResourceLevels.put(personId, newMap); - } - - resourceIds = new LinkedHashSet<>(data.resourceIds); - - - regionResourceLevels = new LinkedHashMap<>(); - for(RegionId regionId : data.regionResourceLevels.keySet()) { - Map map = data.regionResourceLevels.get(regionId); - Map newMap = new LinkedHashMap<>(map); - regionResourceLevels.put(regionId, newMap); - } - - resourceTimeTrackingPolicies = new LinkedHashMap<>(data.resourceTimeTrackingPolicies); - - } - - } - - private final Data data; - - private ResourcesPluginData(Data data) { - this.data = data; - } - - /** - * Returns a new builder instance - */ - public static Builder builder() { - return new Builder(new Data()); - } - - private static void validateResourceIdNotNull(ResourceId resourceId) { - if (resourceId == null) { - throw new ContractException(ResourceError.NULL_RESOURCE_ID); - } - } - - private static void validateTimeTrackingPolicyNotNull(TimeTrackingPolicy timeTrackingPolicy) { - if (timeTrackingPolicy == null) { - throw new ContractException(ResourceError.NULL_TIME_TRACKING_POLICY); - } - } - - private static void validateResourceTimeTrackingNotSet(final Data data, final ResourceId resourceId) { - if (data.resourceTimeTrackingPolicies.get(resourceId) != null) { - throw new ContractException(ResourceError.DUPLICATE_TIME_TRACKING_POLICY_ASSIGNMENT); - } - } - - private static void validateRegionResourceNotSet(final Data data, final RegionId regionId, final ResourceId resourceId) { - final Map resourceLevelMap = data.regionResourceLevels.get(regionId); - if (resourceLevelMap != null) { - if (resourceLevelMap.containsKey(resourceId)) { - throw new ContractException(ResourceError.DUPLICATE_REGION_RESOURCE_LEVEL_ASSIGNMENT, resourceId + ": " + regionId); - } - } - } - - private static void validateRegionIdNotNull(RegionId regionId) { - if (regionId == null) { - throw new ContractException(RegionError.NULL_REGION_ID); - } - } - - private static void validatePersonIdNotNull(PersonId personId) { - if (personId == null) { - throw new ContractException(PersonError.NULL_PERSON_ID); - } - } - - private static void validateResourceAmount(final long amount) { - if (amount < 0) { - throw new ContractException(ResourceError.NEGATIVE_RESOURCE_AMOUNT, amount); - } - } - - private static void validatePersonResourceLevelNotSet(final Data data, final PersonId personId, final ResourceId resourceId) { - final Map resourceLevelMap = data.personResourceLevels.get(personId); - if (resourceLevelMap != null) { - if (resourceLevelMap.containsKey(resourceId)) { - throw new ContractException(ResourceError.DUPLICATE_PERSON_RESOURCE_LEVEL_ASSIGNMENT, resourceId + ": " + personId); - } - } - } - - private static void validateResourcePropertyIdNotNull(ResourcePropertyId resourcePropertyId) { - if (resourcePropertyId == null) { - throw new ContractException(ResourceError.NULL_RESOURCE_PROPERTY_ID); - } - } - - private static void validateResourcePropertyValueNotNull(Object resourcePropertyValue) { - if (resourcePropertyValue == null) { - throw new ContractException(ResourceError.NULL_RESOURCE_PROPERTY_VALUE); - } - } - - private static void validateResourcePropertyValueNotSet(final Data data, final ResourceId resourceId, final ResourcePropertyId resourcePropertyId) { - final Map propertyMap = data.resourcePropertyValues.get(resourceId); - if (propertyMap != null) { - if (propertyMap.containsKey(resourcePropertyId)) { - throw new ContractException(ResourceError.DUPLICATE_RESOURCE_PROPERTY_VALUE_ASSIGNMENT, resourcePropertyId + ": " + resourceId); - } - } - } - - private static void validateResourcePropertyDefintionNotNull(PropertyDefinition propertyDefinition) { - if (propertyDefinition == null) { - throw new ContractException(ResourceError.NULL_RESOURCE_PROPERTY_DEFINITION); - } - } - - private static void validateResourcePropertyIsNotDefined(final Data data, final ResourceId resourceId, final ResourcePropertyId resourcePropertyId) { - final Map defMap = data.resourcePropertyDefinitions.get(resourceId); - if (defMap != null) { - final PropertyDefinition propertyDefinition = defMap.get(resourcePropertyId); - if (propertyDefinition != null) { - throw new ContractException(ResourceError.DUPLICATE_RESOURCE_PROPERTY_DEFINITION, resourcePropertyId); - } - } - } - - private static void validateResourceDoesNotExist(final Data data, final ResourceId resourceId) { - if (data.resourceIds.contains(resourceId)) { - throw new ContractException(ResourceError.DUPLICATE_RESOURCE_ID, resourceId); - } - } - - /** - * Builder class for ResourceInitialData - * - * @author Shawn Hatch - * - */ - public static class Builder implements PluginDataBuilder{ - private Data data; - - private Builder(Data data) { - this.data = data; - } - - /** - * Returns the ResourceInitialData built from the collected data. - * - * @throws ContractException - * - * - * 1 - *
      • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if a - * resource tracking policy was collected for a resource - * that was not added
      • - * - * 2 - *
      • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if a - * resource property definition was collected for a resource - * that was not added
      • - * - * 3 - *
      • {@linkplain ResourceError#UNKNOWN_RESOURCE_PROPERTY_ID} - * if a resource property value was collected for a resource - * that was not added
      • - * - * 4 - *
      • {@linkplain ResourceError#UNKNOWN_RESOURCE_PROPERTY_ID} - * if a resource property value was collected for a resource - * property that is not associated with the given resource - * id
      • - * - * 5 - *
      • {@linkplain ResourceError#INCOMPATIBLE_VALUE} if a - * resource property value was collected for a resource - * property that is not compatible with the associated - * resource property definition
      • - * - * 6 - *
      • {@linkplain ResourceError#INSUFFICIENT_RESOURCE_PROPERTY_VALUE_ASSIGNMENT} - * if a resource property definition has a null default - * value and there is no assigned resource property value - * for that resource
      • - * - * - * 7 - *
      • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if a - * resource level was collected for a person that is an - * unknown resource id
      • - * - * 8 - *
      • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if a - * resource level was collected for a region that is an - * unknown resource id
      • - * - */ - public ResourcesPluginData build() { - try { - for (final ResourceId resourceId : data.resourceIds) { - final TimeTrackingPolicy timeTrackingPolicy = data.resourceTimeTrackingPolicies.get(resourceId); - if (timeTrackingPolicy == null) { - data.resourceTimeTrackingPolicies.put(resourceId, TimeTrackingPolicy.DO_NOT_TRACK_TIME); - } - } - - validateData(data); - return new ResourcesPluginData(data); - } finally { - data = new Data(); - } - } - - /** - * Adds the given resouce id. - * - * @throws ContractException - *
      • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
      • - *
      • {@linkplain ResourceError#DUPLICATE_RESOURCE_ID} if - * the resource id was previously added
      • - */ - public Builder addResource(final ResourceId resourceId) { - validateResourceIdNotNull(resourceId); - validateResourceDoesNotExist(data, resourceId); - data.resourceIds.add(resourceId); - return this; - } - - /** - * Defines a resource property - * - * @throws ContractException - *
      • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
      • - * - *
      • {@linkplain ResourceError#NULL_RESOURCE_PROPERTY_ID} - *
      • if the resource property id is null - * - *
      • {@linkplain ResourceError#NULL_RESOURCE_PROPERTY_DEFINITION} - *
      • if the property definition is null - * - *
      • {@linkplain ResourceError#DUPLICATE_RESOURCE_PROPERTY_DEFINITION} - *
      • if a resource property definition for the given - * resource id and property id was previously defined. - * - */ - public Builder defineResourceProperty(final ResourceId resourceId, final ResourcePropertyId resourcePropertyId, final PropertyDefinition propertyDefinition) { - validateResourceIdNotNull(resourceId); - validateResourcePropertyIdNotNull(resourcePropertyId); - validateResourcePropertyDefintionNotNull(propertyDefinition); - validateResourcePropertyIsNotDefined(data, resourceId, resourcePropertyId); - Map map = data.resourcePropertyDefinitions.get(resourceId); - if (map == null) { - map = new LinkedHashMap<>(); - data.resourcePropertyDefinitions.put(resourceId, map); - } - map.put(resourcePropertyId, propertyDefinition); - return this; - } - - /** - * Sets a person's initial resource level - * - * @throws ContractException - *
      • {@linkplain PersonError#NULL_PERSON_ID} if the person - * id is null
      • - *
      • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
      • - *
      • {@linkplain ResourceError#NEGATIVE_RESOURCE_AMOUNT} - * if the resource amount is negative
      • * - *
      • {@linkplain ResourceError#DUPLICATE_PERSON_RESOURCE_LEVEL_ASSIGNMENT} - * if the person's resource level was previously - * assigned
      • - */ - - public Builder setPersonResourceLevel(final PersonId personId, final ResourceId resourceId, final long amount) { - validatePersonIdNotNull(personId); - validateResourceIdNotNull(resourceId); - validateResourceAmount(amount); - validatePersonResourceLevelNotSet(data, personId, resourceId); - Map resourceLevelMap = data.personResourceLevels.get(personId); - if (resourceLevelMap == null) { - resourceLevelMap = new LinkedHashMap<>(); - data.personResourceLevels.put(personId, resourceLevelMap); - } - resourceLevelMap.put(resourceId, amount); - return this; - } - - /** - * Sets a region's initial resource level - * - * @throws ContractException - *
      • {@linkplain RegionError#NULL_REGION_ID} if the region - * id is null
      • - *
      • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
      • - *
      • {@linkplain ResourceError#NEGATIVE_RESOURCE_AMOUNT} - * if the resource amount is negative
      • * - *
      • {@linkplain ResourceError#DUPLICATE_REGION_RESOURCE_LEVEL_ASSIGNMENT} - * if the region's resource level was previously - * assigned
      • - */ - - public Builder setRegionResourceLevel(final RegionId regionId, final ResourceId resourceId, final long amount) { - validateRegionIdNotNull(regionId); - validateResourceIdNotNull(resourceId); - validateRegionResourceNotSet(data, regionId, resourceId); - validateResourceAmount(amount); - Map resourceLevelMap = data.regionResourceLevels.get(regionId); - if (resourceLevelMap == null) { - resourceLevelMap = new LinkedHashMap<>(); - data.regionResourceLevels.put(regionId, resourceLevelMap); - } - resourceLevelMap.put(resourceId, amount); - return this; - } - - /** - * Sets a resource property value - * - * @throws ContractException - *
      • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
      • - *
      • {@linkplain ResourceError#NULL_RESOURCE_PROPERTY_ID} - * if the resource property id is null
      • - *
      • {@linkplain ResourceError#NULL_RESOURCE_PROPERTY_VALUE} - * if the resource property value is null
      • - *
      • {@linkplain ResourceError#DUPLICATE_RESOURCE_PROPERTY_VALUE_ASSIGNMENT} - * if the resource property value was previously - * assigned
      • - */ - public Builder setResourcePropertyValue(final ResourceId resourceId, final ResourcePropertyId resourcePropertyId, final Object resourcePropertyValue) { - validateResourceIdNotNull(resourceId); - validateResourcePropertyIdNotNull(resourcePropertyId); - validateResourcePropertyValueNotNull(resourcePropertyValue); - validateResourcePropertyValueNotSet(data, resourceId, resourcePropertyId); - - - Map propertyMap = data.resourcePropertyValues.get(resourceId); - if (propertyMap == null) { - propertyMap = new LinkedHashMap<>(); - data.resourcePropertyValues.put(resourceId, propertyMap); - } - propertyMap.put(resourcePropertyId, resourcePropertyValue); - return this; - } - - /** - * Sets the time tracking policy for a resource - * - * @throws ContractException - *
      • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
      • - *
      • {@linkplain ResourceError.NULL_TIME_TRACKING_POLICY} - * if the tracking policy is null
      • - *
      • {@linkplain ResourceError#DUPLICATE_TIME_TRACKING_POLICY_ASSIGNMENT} - * if the resource tracking policy was previously - * assigned
      • - */ - public Builder setResourceTimeTracking(final ResourceId resourceId, final TimeTrackingPolicy trackValueAssignmentTimes) { - validateResourceIdNotNull(resourceId); - validateTimeTrackingPolicyNotNull(trackValueAssignmentTimes); - validateResourceTimeTrackingNotSet(data, resourceId); - data.resourceTimeTrackingPolicies.put(resourceId, trackValueAssignmentTimes); - return this; - } - - } - - private static void validateData(Data data) { - - // 1 - for (ResourceId resourceId : data.resourceTimeTrackingPolicies.keySet()) { - if (!data.resourceIds.contains(resourceId)) { - throw new ContractException(ResourceError.UNKNOWN_RESOURCE_ID, resourceId + " has a resource tracking policy but is not a known resource id"); - } - } - - // 2 - for (ResourceId resourceId : data.resourcePropertyDefinitions.keySet()) { - if (!data.resourceIds.contains(resourceId)) { - throw new ContractException(ResourceError.UNKNOWN_RESOURCE_ID, resourceId + " has a property definitions but is not a known resource id"); - } - } - - for (ResourceId resourceId : data.resourcePropertyValues.keySet()) { - if (!data.resourceIds.contains(resourceId)) { - // 3 - throw new ContractException(ResourceError.UNKNOWN_RESOURCE_ID, resourceId + " has a property values but is not a known resource id"); - } - - Map propDefMap = data.resourcePropertyDefinitions.get(resourceId); - - Map map = data.resourcePropertyValues.get(resourceId); - for (ResourcePropertyId resourcePropertyId : map.keySet()) { - if (propDefMap == null || !propDefMap.containsKey(resourcePropertyId)) { - // 4 - throw new ContractException(ResourceError.UNKNOWN_RESOURCE_PROPERTY_ID, resourceId + ": " + resourcePropertyId); - } - Object propertyValue = map.get(resourcePropertyId); - PropertyDefinition propertyDefinition = propDefMap.get(resourcePropertyId); - if (!propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())) { - // 5 - throw new ContractException(ResourceError.INCOMPATIBLE_VALUE, resourceId + ": " + resourcePropertyId + ": " + propertyValue); - } - } - - } - - /* - * For every resource property definition that has a null default value, - * ensure that there all corresponding resource property values are not - * null and repair the definition. - */ - for (ResourceId resourceId : data.resourceIds) { - Map propertyDefinitionMap = data.resourcePropertyDefinitions.get(resourceId); - if (propertyDefinitionMap != null) { - for (ResourcePropertyId resourcePropertyId : propertyDefinitionMap.keySet()) { - PropertyDefinition propertyDefinition = propertyDefinitionMap.get(resourcePropertyId); - if (!propertyDefinition.getDefaultValue().isPresent()) { - Object propertyValue = null; - Map propertyValueMap = data.resourcePropertyValues.get(resourceId); - if (propertyValueMap != null) { - propertyValue = propertyValueMap.get(resourcePropertyId); - } - if (propertyValue == null) { - // 6 - throw new ContractException(ResourceError.INSUFFICIENT_RESOURCE_PROPERTY_VALUE_ASSIGNMENT, resourceId + ": " + resourcePropertyId); - } - } - } - } - } - - for (PersonId personId : data.personResourceLevels.keySet()) { - Map map = data.personResourceLevels.get(personId); - for (ResourceId resourceId : map.keySet()) { - if (!data.resourceIds.contains(resourceId)) { - // 7 - throw new ContractException(ResourceError.UNKNOWN_RESOURCE_ID, personId + ": " + resourceId); - } - } - } - - for (RegionId regionId : data.regionResourceLevels.keySet()) { - Map map = data.regionResourceLevels.get(regionId); - for (ResourceId resourceId : map.keySet()) { - if (!data.resourceIds.contains(resourceId)) { - // 8 - throw new ContractException(ResourceError.UNKNOWN_RESOURCE_ID, regionId + ": " + resourceId); - } - } - } - - } - - private static void validateResourcePropertyIsDefined(final Data data, final ResourceId resourceId, final ResourcePropertyId resourcePropertyId) { - Map map = data.resourcePropertyDefinitions.get(resourceId); - if (map == null) { - throw new ContractException(ResourceError.UNKNOWN_RESOURCE_PROPERTY_ID, resourceId); - } - - if (!map.containsKey(resourcePropertyId)) { - throw new ContractException(ResourceError.UNKNOWN_RESOURCE_PROPERTY_ID, resourcePropertyId); - } - } - - /** - * Returns the property definition associated with the resource id and - * resource property id. - * - * @throws ContractException - *
      • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
      • - *
      • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if the - * resource id is unknown
      • - *
      • {@linkplain ResourceError#NULL_RESOURCE_PROPERTY_ID} if - * the resource property id is null
      • - *
      • {@linkplain ResourceError#UNKNOWN_RESOURCE_PROPERTY_ID} - * if the resource property id is unknown
      • - * - */ - public PropertyDefinition getResourcePropertyDefinition(final ResourceId resourceId, final ResourcePropertyId resourcePropertyId) { - validateResourceExists(data, resourceId); - validateResourcePropertyIdNotNull(resourcePropertyId); - validateResourcePropertyIsDefined(data, resourceId, resourcePropertyId); - final Map defMap = data.resourcePropertyDefinitions.get(resourceId); - final PropertyDefinition propertyDefinition = defMap.get(resourcePropertyId); - return propertyDefinition; - } - - /** - * Returns the resource property id associated with the resource. - * - * @throws ContractException - *
      • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
      • - *
      • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if the - * resource id is unknown
      • - */ - @SuppressWarnings("unchecked") - public Set getResourcePropertyIds(final ResourceId resourceId) { - validateResourceExists(data, resourceId); - Set result = new LinkedHashSet<>(); - Map defMap = data.resourcePropertyDefinitions.get(resourceId); - if (defMap != null) { - for (ResourcePropertyId resourcePropertyId : defMap.keySet()) { - result.add((T) resourcePropertyId); - } - } - return result; - } - - private static void validateResourceExists(final Data data, final ResourceId resourceId) { - if (resourceId == null) { - throw new ContractException(ResourceError.NULL_RESOURCE_ID); - } - if (!data.resourceIds.contains(resourceId)) { - throw new ContractException(ResourceError.UNKNOWN_RESOURCE_ID, resourceId); - } - } - - /** - * Returns the resource property value associated with the resource id and - * resource property id. Returns the default value of the associated - * property definition if now value was assigned. - * - * @throws ContractException - *
      • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
      • - *
      • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if the * - * resource id is unknown
      • - *
      • {@linkplain ResourceError#NULL_RESOURCE_PROPERTY_ID} if - * the resource property id is null
      • - *
      • {@linkplain ResourceError#UNKNOWN_RESOURCE_PROPERTY_ID} - * if the resource property id is unknown
      • - */ - @SuppressWarnings("unchecked") - public T getResourcePropertyValue(final ResourceId resourceId, final ResourcePropertyId resourcePropertyId) { - validateResourceExists(data, resourceId); - validateResourcePropertyIdNotNull(resourcePropertyId); - validateResourcePropertyIsDefined(data, resourceId, resourcePropertyId); - - Object result = null; - final Map map = data.resourcePropertyValues.get(resourceId); - if (map != null) { - result = map.get(resourcePropertyId); - } - if (result == null) { - final Map defMap = data.resourcePropertyDefinitions.get(resourceId); - final PropertyDefinition propertyDefinition = defMap.get(resourcePropertyId); - Optional optional = propertyDefinition.getDefaultValue(); - if (optional.isPresent()) { - result = optional.get(); - } - } - return (T)result; - } - - /** - * Returns the person's initial resource level. Returns 0 if no value was - * assigned during the build process. - * - * @throws ContractException - *
      • {@linkplain PersonError#NULL_PERSON_ID} if the person id - * is null
      • - *
      • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
      • - *
      • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if the - * resource id is unknown
      • - */ - public Long getPersonResourceLevel(final PersonId personId, final ResourceId resourceId) { - validatePersonIdNotNull(personId); - validateResourceExists(data, resourceId); - Long result = null; - final Map map = data.personResourceLevels.get(personId); - if (map != null) { - result = map.get(resourceId); - } - if (result == null) { - result = 0L; - } - return result; - } - - /** - * Returns the resource ids - */ - @SuppressWarnings("unchecked") - public Set getResourceIds() { - Set result = new LinkedHashSet<>(data.resourceIds.size()); - for (ResourceId resourceId : data.resourceIds) { - result.add((T) resourceId); - } - return result; - } - - /** - * Returns the region's initial resource level. Returns 0 if no value was - * assigned during the build process. - * - * @throws ContractException - *
      • {@linkplain RegionError#NULL_REGION_ID} if the region id - * is null
      • - *
      • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
      • - *
      • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if the - * resource id is unknown
      • - */ - public Long getRegionResourceLevel(final RegionId regionId, final ResourceId resourceId) { - validateRegionIdNotNull(regionId); - validateResourceExists(data, resourceId); - - Long result = null; - final Map map = data.regionResourceLevels.get(regionId); - - if (map != null) { - result = map.get(resourceId); - } - - if (result == null) { - result = 0L; - } - - return result; - } - - /** - * Returns the tracking policy associated with the resource. Returns - * TimeTrackingPolicy.DO_NOT_TRACK_TIME if no policy was set. - * - * @throws ContractException - *
      • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
      • - *
      • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if the - * resource id is unknown
      • - */ - public TimeTrackingPolicy getPersonResourceTimeTrackingPolicy(final ResourceId resourceId) { - validateResourceExists(data, resourceId); - TimeTrackingPolicy result = data.resourceTimeTrackingPolicies.get(resourceId); - if (result == null) { - result = TimeTrackingPolicy.DO_NOT_TRACK_TIME; - } - return result; - } - - /** - * Returns the region ids associated with assigned resources - */ - public Set getPersonIds() { - return new LinkedHashSet<>(data.personResourceLevels.keySet()); - } - - /** - * Returns the person ids associated with assigned resources - */ - public Set getRegionIds() { - return new LinkedHashSet<>(data.regionResourceLevels.keySet()); - } - - @Override - public PluginDataBuilder getCloneBuilder() { - return new Builder(new Data(data)); - } - -} diff --git a/gcm3/src/main/java/plugins/resources/ResourcesPluginId.java b/gcm3/src/main/java/plugins/resources/ResourcesPluginId.java deleted file mode 100644 index ea52c7c67..000000000 --- a/gcm3/src/main/java/plugins/resources/ResourcesPluginId.java +++ /dev/null @@ -1,14 +0,0 @@ -package plugins.resources; - -import nucleus.PluginId; -/** - * Static plugin id implementation for the GlobalsPlugin - * - * @author Shawn Hatch - * - */ - -public final class ResourcesPluginId implements PluginId { - public final static PluginId PLUGIN_ID = new ResourcesPluginId(); - private ResourcesPluginId() {}; -} diff --git a/gcm3/src/main/java/plugins/resources/actors/PersonResourceReport.java b/gcm3/src/main/java/plugins/resources/actors/PersonResourceReport.java deleted file mode 100644 index 9ef1cbb1d..000000000 --- a/gcm3/src/main/java/plugins/resources/actors/PersonResourceReport.java +++ /dev/null @@ -1,346 +0,0 @@ -package plugins.resources.actors; - -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -import nucleus.ActorContext; -import nucleus.EventLabel; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.events.PersonAdditionEvent; -import plugins.people.events.PersonImminentRemovalEvent; -import plugins.people.support.PersonId; -import plugins.regions.datamanagers.RegionsDataManager; -import plugins.regions.events.PersonRegionUpdateEvent; -import plugins.regions.support.RegionId; -import plugins.reports.support.PeriodicReport; -import plugins.reports.support.ReportHeader; -import plugins.reports.support.ReportId; -import plugins.reports.support.ReportItem; -import plugins.reports.support.ReportPeriod; -import plugins.resources.datamanagers.ResourcesDataManager; -import plugins.resources.events.PersonResourceUpdateEvent; -import plugins.resources.support.ResourceError; -import plugins.resources.support.ResourceId; -import util.errors.ContractException; - -/** - * A periodic Report that displays number of people who have/do not have any - * units of a particular resource with a region. - * - * - * Fields - * - * region -- the region identifier - * - * Resource -- the resource identifier - * - * people_with_resource -- the number of people in the region who have at least - * one unit of the given resource - * - * people_without_resource -- the number of people in the region pair who do not - * have any units of the given resource - * - * @author Shawn Hatch - * - */ -public final class PersonResourceReport extends PeriodicReport { - public PersonResourceReport(ReportId reportId, ReportPeriod reportPeriod, boolean reportPeopleWithoutResources, boolean reportZeroPopulations, ResourceId... resourceIds) { - super(reportId, reportPeriod); - this.reportPeopleWithoutResources = reportPeopleWithoutResources; - this.reportZeroPopulations = reportZeroPopulations; - for (ResourceId resourceId : resourceIds) { - this.resourceIds.add(resourceId); - } - } - - /** - * An enmeration mirroring the differentiation in the report for populations - * of people with and without a resource. - * - * @author Shawn Hatch - * - */ - private static enum InventoryType { - ZERO, POSITIVE - } - - /* - * The resources that will be used in this report. They are derived from the - * values passed in the init() method. - */ - private Set resourceIds = new LinkedHashSet<>(); - - /* - * Boolean for controlling the reporting of people with out resources. Set - * in the init() method. - */ - private boolean reportPeopleWithoutResources; - - /* - * Boolean for controlling the reporting of people with out resources. Set - * in the init() method. - */ - private boolean reportZeroPopulations; - - // Mapping of the (regionId, resource Id, InventoryType) to - // sets of person id. Maintained via the processing of events. - private final Map>>> regionMap = new LinkedHashMap<>(); - - /* - * The derived header for this report - */ - private ReportHeader reportHeader; - - private ReportHeader getReportHeader() { - if (reportHeader == null) { - ReportHeader.Builder reportHeaderBuilder = ReportHeader.builder(); - addTimeFieldHeaders(reportHeaderBuilder)// - .add("region")// - .add("resource")// - .add("people_with_resource"); - if (reportPeopleWithoutResources) { - reportHeaderBuilder.add("people_without_resource"); - } - reportHeader = reportHeaderBuilder.build(); - } - return reportHeader; - } - - /* - * Adds a person to the set of people associated with the given tuple - */ - private void add(final RegionId regionId, final ResourceId resourceId, final InventoryType inventoryType, final PersonId personId) { - final Set people = regionMap.get(regionId).get(resourceId).get(inventoryType); - people.add(personId); - } - - @Override - protected void flush(ActorContext actorContext) { - final ReportItem.Builder reportItemBuilder = ReportItem.builder(); - for (final RegionId regionId : regionMap.keySet()) { - final Map>> resourceMap = regionMap.get(regionId); - for (final ResourceId resourceId : resourceIds) { - final Map> inventoryMap = resourceMap.get(resourceId); - - final int positiveCount = inventoryMap.get(InventoryType.POSITIVE).size(); - int count = positiveCount; - final int zeroCount = inventoryMap.get(InventoryType.ZERO).size(); - if (reportPeopleWithoutResources) { - count += zeroCount; - } - final boolean shouldReport = reportZeroPopulations || (count > 0); - - if (shouldReport) { - reportItemBuilder.setReportHeader(getReportHeader()); - reportItemBuilder.setReportId(getReportId()); - - fillTimeFields(reportItemBuilder); - reportItemBuilder.addValue(regionId.toString()); - reportItemBuilder.addValue(resourceId.toString()); - reportItemBuilder.addValue(positiveCount); - if (reportPeopleWithoutResources) { - reportItemBuilder.addValue(zeroCount); - } - actorContext.releaseOutput(reportItemBuilder.build()); - } - } - - } - } - - private void handlePersonAdditionEvent(ActorContext actorContext, PersonAdditionEvent personAdditionEvent) { - PersonId personId = personAdditionEvent.getPersonId(); - final RegionId regionId = regionsDataManager.getPersonRegion(personId); - - for (final ResourceId resourceId : resourceIds) { - final long personResourceLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); - if (personResourceLevel > 0) { - add(regionId, resourceId, InventoryType.POSITIVE, personId); - } else { - if (reportPeopleWithoutResources) { - add(regionId, resourceId, InventoryType.ZERO, personId); - } - } - } - } - - private void handlePersonImminentRemovalEvent(ActorContext actorContext, PersonImminentRemovalEvent personImminentRemovalEvent) { - - PersonId personId = personImminentRemovalEvent.getPersonId(); - - RegionId regionId = regionsDataManager.getPersonRegion(personId); - - for (ResourceId resourceId : resourceIds) { - Long amount = resourcesDataManager.getPersonResourceLevel(resourceId, personId); - if (amount > 0) { - remove(regionId, resourceId, InventoryType.POSITIVE, personId); - } else { - if (reportPeopleWithoutResources) { - remove(regionId, resourceId, InventoryType.ZERO, personId); - } - } - } - } - - private void handlePersonResourceUpdateEvent(ActorContext actorContext, PersonResourceUpdateEvent personResourceUpdateEvent) { - ResourceId resourceId = personResourceUpdateEvent.getResourceId(); - if (!resourceIds.contains(resourceId)) { - return; - } - PersonId personId = personResourceUpdateEvent.getPersonId(); - long currentLevel = personResourceUpdateEvent.getCurrentResourceLevel(); - long previousLevel = personResourceUpdateEvent.getPreviousResourceLevel(); - long amount = currentLevel - previousLevel; - - if (amount == 0) { - return; - } - if (amount > 0) { - final long personResourceLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); - if (personResourceLevel == amount) { - final RegionId regionId = regionsDataManager.getPersonRegion(personId); - - if (reportPeopleWithoutResources) { - remove(regionId, resourceId, InventoryType.ZERO, personId); - } - add(regionId, resourceId, InventoryType.POSITIVE, personId); - } - } else { - amount = -amount; - final long personResourceLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); - if (personResourceLevel == 0) { - final RegionId regionId = regionsDataManager.getPersonRegion(personId); - remove(regionId, resourceId, InventoryType.POSITIVE, personId); - if (reportPeopleWithoutResources) { - add(regionId, resourceId, InventoryType.ZERO, personId); - } - } - } - - } - - private void handlePersonRegionUpdateEvent(ActorContext actorContext, PersonRegionUpdateEvent personRegionUpdateEvent) { - PersonId personId = personRegionUpdateEvent.getPersonId(); - RegionId previousRegionId = personRegionUpdateEvent.getPreviousRegionId(); - RegionId currentRegionId = personRegionUpdateEvent.getCurrentRegionId(); - - for (final ResourceId resourceId : resourceIds) { - final long personResourceLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); - if (personResourceLevel > 0) { - remove(previousRegionId, resourceId, InventoryType.POSITIVE, personId); - add(currentRegionId, resourceId, InventoryType.POSITIVE, personId); - } else { - if (reportPeopleWithoutResources) { - remove(previousRegionId, resourceId, InventoryType.ZERO, personId); - add(currentRegionId, resourceId, InventoryType.ZERO, personId); - } - } - } - } - - private RegionsDataManager regionsDataManager; - private ResourcesDataManager resourcesDataManager; - - /** - * - * @throws ContractException - *
      • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if a - * resource id passed to the constructor is unknown - *
      • - * - */ - @Override - public void init(final ActorContext actorContext) { - super.init(actorContext); - - actorContext.subscribe(PersonAdditionEvent.class, this::handlePersonAdditionEvent); - actorContext.subscribe(PersonImminentRemovalEvent.class, this::handlePersonImminentRemovalEvent); - actorContext.subscribe(PersonRegionUpdateEvent.class, this::handlePersonRegionUpdateEvent); - - resourcesDataManager = actorContext.getDataManager(ResourcesDataManager.class); - PeopleDataManager peopleDataManager = actorContext.getDataManager(PeopleDataManager.class); - regionsDataManager = actorContext.getDataManager(RegionsDataManager.class); - RegionsDataManager regionsDataManager = actorContext.getDataManager(RegionsDataManager.class); - - /* - * If no resources were selected, then assume that all are desired. - */ - if (resourceIds.size() == 0) { - resourceIds.addAll(resourcesDataManager.getResourceIds()); - } - - /* - * Ensure that the resources are valid - */ - final Set validResourceIds = resourcesDataManager.getResourceIds(); - for (final ResourceId resourceId : resourceIds) { - if (!validResourceIds.contains(resourceId)) { - throw new ContractException(ResourceError.UNKNOWN_RESOURCE_ID, resourceId); - } - } - - // If all resources are covered by this report, then subscribe to the - // event, otherwise subscribe to each resource id - if (resourceIds.equals(resourcesDataManager.getResourceIds())) { - actorContext.subscribe(PersonResourceUpdateEvent.class, this::handlePersonResourceUpdateEvent); - } else { - for (ResourceId resourceId : resourceIds) { - EventLabel eventLabelByResource = PersonResourceUpdateEvent.getEventLabelByResource(actorContext, resourceId); - actorContext.subscribe(eventLabelByResource, this::handlePersonResourceUpdateEvent); - } - } - - /* - * Build the tuple map to empty sets of people in preparation for people - * being added to the simulation - */ - - for (final RegionId regionId : regionsDataManager.getRegionIds()) { - - final Map>> resourceMap = new LinkedHashMap<>(); - regionMap.put(regionId, resourceMap); - - for (final ResourceId resourceId : resourceIds) { - final Map> inventoryMap = new LinkedHashMap<>(); - resourceMap.put(resourceId, inventoryMap); - for (final InventoryType inventoryType : InventoryType.values()) { - final Set people = new LinkedHashSet<>(); - inventoryMap.put(inventoryType, people); - } - } - - } - - /* - * Place the initial population in the mapping - */ - for (final PersonId personId : peopleDataManager.getPeople()) { - for (final ResourceId resourceId : resourceIds) { - final RegionId regionId = regionsDataManager.getPersonRegion(personId); - - final long personResourceLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); - if (personResourceLevel > 0) { - add(regionId, resourceId, InventoryType.POSITIVE, personId); - } else { - if (reportPeopleWithoutResources) { - add(regionId, resourceId, InventoryType.ZERO, personId); - } - } - } - } - - } - - /* - * Removes a person to the set of people associated with the given tuple - */ - private void remove(final RegionId regionId, final ResourceId resourceId, final InventoryType inventoryType, final PersonId personId) { - if (resourceIds.contains(resourceId)) { - final Set people = regionMap.get(regionId).get(resourceId).get(inventoryType); - people.remove(personId); - } - } - -} \ No newline at end of file diff --git a/gcm3/src/main/java/plugins/resources/actors/ResourcePropertyReport.java b/gcm3/src/main/java/plugins/resources/actors/ResourcePropertyReport.java deleted file mode 100644 index 8582d56e4..000000000 --- a/gcm3/src/main/java/plugins/resources/actors/ResourcePropertyReport.java +++ /dev/null @@ -1,86 +0,0 @@ -package plugins.resources.actors; - -import nucleus.ActorContext; -import plugins.reports.support.ReportHeader; -import plugins.reports.support.ReportId; -import plugins.reports.support.ReportItem; -import plugins.resources.datamanagers.ResourcesDataManager; -import plugins.resources.events.ResourcePropertyUpdateEvent; -import plugins.resources.support.ResourceId; -import plugins.resources.support.ResourcePropertyId; - -/** - * A Report that displays assigned resource property values over time. - * - * - * Fields - * - * Time -- the time in days when the global resource was set - * - * Resource -- the resource identifier - * - * Property -- the resource property identifier - * - * Value -- the value of the resource property - * - * @author Shawn Hatch - * - */ -public final class ResourcePropertyReport { - private final ReportId reportId; - public ResourcePropertyReport(ReportId reportId) { - this.reportId = reportId; - } - - private ReportHeader reportHeader; - - private ReportHeader getReportHeader() { - if (reportHeader == null) { - reportHeader = ReportHeader .builder()// - .add("time")// - .add("resource")// - .add("property")// - .add("value")// - .build();// - } - return reportHeader; - } - - private void handleResourcePropertyUpdateEvent(ActorContext actorContext,ResourcePropertyUpdateEvent resourcePropertyUpdateEvent) { - ResourceId resourceId = resourcePropertyUpdateEvent.getResourceId(); - ResourcePropertyId resourcePropertyId = resourcePropertyUpdateEvent.getResourcePropertyId(); - Object currentPropertyValue = resourcePropertyUpdateEvent.getCurrentPropertyValue(); - writeProperty(actorContext,resourceId, resourcePropertyId,currentPropertyValue); - } - - private ResourcesDataManager resourcesDataManager; - - public void init(final ActorContext actorContext) { - - actorContext.subscribe(ResourcePropertyUpdateEvent.class,this::handleResourcePropertyUpdateEvent); - - - resourcesDataManager = actorContext.getDataManager(ResourcesDataManager.class); - for (final ResourceId resourceId : resourcesDataManager.getResourceIds()) { - for (final ResourcePropertyId resourcePropertyId : resourcesDataManager.getResourcePropertyIds(resourceId)) { - Object resourcePropertyValue = resourcesDataManager.getResourcePropertyValue(resourceId, resourcePropertyId); - writeProperty(actorContext,resourceId, resourcePropertyId,resourcePropertyValue); - } - } - } - - private void writeProperty(ActorContext actorContext,final ResourceId resourceId, final ResourcePropertyId resourcePropertyId,Object resourcePropertyValue) { - - - final ReportItem.Builder reportItemBuilder = ReportItem.builder(); - reportItemBuilder.setReportHeader(getReportHeader()); - reportItemBuilder.setReportId(reportId); - - reportItemBuilder.addValue(actorContext.getTime()); - reportItemBuilder.addValue(resourceId.toString()); - reportItemBuilder.addValue(resourcePropertyId.toString()); - reportItemBuilder.addValue(resourcePropertyValue); - actorContext.releaseOutput(reportItemBuilder.build()); - } - -} \ No newline at end of file diff --git a/gcm3/src/main/java/plugins/resources/actors/ResourceReport.java b/gcm3/src/main/java/plugins/resources/actors/ResourceReport.java deleted file mode 100644 index 82bbd3f90..000000000 --- a/gcm3/src/main/java/plugins/resources/actors/ResourceReport.java +++ /dev/null @@ -1,365 +0,0 @@ -package plugins.resources.actors; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -import nucleus.ActorContext; -import nucleus.EventLabel; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.events.PersonAdditionEvent; -import plugins.people.events.PersonImminentRemovalEvent; -import plugins.people.support.PersonId; -import plugins.regions.datamanagers.RegionsDataManager; -import plugins.regions.events.PersonRegionUpdateEvent; -import plugins.regions.support.RegionId; -import plugins.reports.support.PeriodicReport; -import plugins.reports.support.ReportHeader; -import plugins.reports.support.ReportId; -import plugins.reports.support.ReportItem; -import plugins.reports.support.ReportPeriod; -import plugins.resources.datamanagers.ResourcesDataManager; -import plugins.resources.events.PersonResourceUpdateEvent; -import plugins.resources.events.RegionResourceUpdateEvent; -import plugins.resources.support.ResourceError; -import plugins.resources.support.ResourceId; -import util.errors.ContractException; - -/** - * A periodic Report that displays the creation, transfer or consumption of - * resources within a region. Only activities with non-zero action counts are - * reported. - * - * - * Fields - * - * region -- the region identifier - * - * resource -- the resource identifier - * - * activity -- the activity that leads to the creation, transfer or consumption - * of a resource unit(s) - * - * actions -- the number of individual actions that were associated with the - * activity - * - * items -- the number of units of the resource that were associated with the - * activity - * - * - * - * Activities - * - * person_addition -- the addition of a person to the simulation - * - * person_departure -- the removal of a person from the simulation - * - * person_region_arrival -- the arrival of a person into the region from another - * region - * - * person_region_departure -- the departure of a person from the region to - * another region - * - * region_resource_addition -- the creation of a resource unit(s) on the region - * - * person_resource_addition -- the creation of a resource unit(s) on a - * person(associate with simulation bootstrap) - * - * region_resource_removal -- the destruction of a resource unit(s) on the - * region - * - * resource_transfer_into_region -- the transfer of units of resource from - * another region - * - * resource_transfer_out_of_region -- the transfer of units of resource to - * another region - * - * resource_transfer_from_person -- the return of resource units from a person - * in the region to the region - * - * resource_transfer_to_person -- the distribution of resource units to a person - * in the region from the region - * - * resource_removal_from_person -- the destruction of a resource unit(s) on a - * person - * - * - * @author Shawn Hatch - * - */ -public final class ResourceReport extends PeriodicReport { - - public ResourceReport(ReportId reportId, ReportPeriod reportPeriod, ResourceId... resourceIds) { - super(reportId, reportPeriod); - for (ResourceId resourceId : resourceIds) { - this.resourceIds.add(resourceId); - } - - } - - private static enum Activity { - PERSON_ARRIVAL("person_arrival"), - PERSON_DEPARTURE("person_departure"), - PERSON_REGION_ARRIVAL("person_region_arrival"), - PERSON_REGION_DEPARTURE("person_region_departure"), - REGION_RESOURCE_ADDITION("region_resource_addition"), - PERSON_RESOURCE_ADDITION("person_resource_addition"), - REGION_RESOURCE_REMOVAL("region_resource_removal"), - RESOURCE_TRANSFER_INTO_REGION("resource_transfer_into_region"), - RESOURCE_TRANSFER_OUT_OF_REGION("resource_transfer_out_of_region"), - RESOURCE_TRANSFER_FROM_MATERIALS_PRODUCER("resource_transfer_from_materials_producer"), - TRANSFER_RESOURCE_FROM_PERSON("transfer_resource_from_person"), - TRANSFER_RESOURCE_TO_PERSON("transfer_resource_to_person"), - REMOVE_RESOURCE_FROM_PERSON("remove_resource_from_person"); - - private final String displayName; - - Activity(final String displayName) { - this.displayName = displayName; - } - } - - private static class Counter { - private int actionCount; - private long itemCount; - - private void reset() { - actionCount = 0; - itemCount = 0; - } - } - - /* - * The resource covered by this report. Determined in the init() method. - */ - private final List resourceIds = new ArrayList<>(); - - /* - * The mapping of (Region, Resource, Activity) tuples to counters that - * record the number of actions and the number of items handled across those - * actions. - */ - private final Map>> regionMap = new LinkedHashMap<>(); - - /* - * The derived header for this report - */ - private ReportHeader reportHeader; - - private ReportHeader getReportHeader() { - if (reportHeader == null) { - ReportHeader.Builder reportHeaderBuilder = ReportHeader.builder(); - reportHeader = addTimeFieldHeaders(reportHeaderBuilder) .add("region")// - .add("resource")// - .add("activity")// - .add("actions")// - .add("items")// - .build();// - } - return reportHeader; - } - - @Override - protected void flush(ActorContext actorContext) { - final ReportItem.Builder reportItemBuilder = ReportItem.builder(); - for (final RegionId regionId : regionMap.keySet()) { - - final Map> resourceMap = regionMap.get(regionId); - for (final ResourceId resourceId : resourceMap.keySet()) { - final Map activityMap = resourceMap.get(resourceId); - for (final Activity activity : activityMap.keySet()) { - final Counter counter = activityMap.get(activity); - if (counter.actionCount > 0) { - reportItemBuilder.setReportHeader(getReportHeader()); - reportItemBuilder.setReportId(getReportId()); - fillTimeFields(reportItemBuilder); - - reportItemBuilder.addValue(regionId.toString()); - reportItemBuilder.addValue(resourceId.toString()); - reportItemBuilder.addValue(activity.displayName); - reportItemBuilder.addValue(counter.actionCount); - reportItemBuilder.addValue(counter.itemCount); - actorContext.releaseOutput(reportItemBuilder.build()); - counter.reset(); - } - } - } - - } - } - - private void handlePersonAdditionEvent(ActorContext actorContext, PersonAdditionEvent personAdditionEvent) { - PersonId personId = personAdditionEvent.getPersonId(); - final RegionId regionId = regionsDataManager.getPersonRegion(personId); - for (final ResourceId resourceId : resourceIds) { - final long personResourceLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); - if (personResourceLevel > 0) { - increment(regionId, resourceId, Activity.PERSON_ARRIVAL, personResourceLevel); - } - } - } - - private void handlePersonImminentRemovalEvent(ActorContext actorContext, PersonImminentRemovalEvent personImminentRemovalEvent) { - - PersonId personId = personImminentRemovalEvent.getPersonId(); - RegionId regionId = regionsDataManager.getPersonRegion(personId); - - for (ResourceId resourceId : resourceIds) { - final Long personResourceLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); - if (personResourceLevel > 0) { - increment(regionId, resourceId, Activity.PERSON_DEPARTURE, personResourceLevel); - } - } - } - - private void handlePersonResourceUpdateEvent(ActorContext actorContext, PersonResourceUpdateEvent personResourceUpdateEvent) { - - final PersonId personId = personResourceUpdateEvent.getPersonId(); - final ResourceId resourceId = personResourceUpdateEvent.getResourceId(); - final long previousLevel = personResourceUpdateEvent.getPreviousResourceLevel(); - final long currentLevel = personResourceUpdateEvent.getCurrentResourceLevel(); - if (!resourceIds.contains(resourceId)) { - return; - } - long amount = currentLevel - previousLevel; - if (amount > 0) { - final RegionId regionId = regionsDataManager.getPersonRegion(personId); - increment(regionId, resourceId, Activity.PERSON_RESOURCE_ADDITION, amount); - } else { - amount = -amount; - final RegionId regionId = regionsDataManager.getPersonRegion(personId); - increment(regionId, resourceId, Activity.REMOVE_RESOURCE_FROM_PERSON, amount); - } - } - - private void handlePersonRegionUpdateEvent(ActorContext actorContext, PersonRegionUpdateEvent personRegionUpdateEvent) { - PersonId personId = personRegionUpdateEvent.getPersonId(); - RegionId previousRegionId = personRegionUpdateEvent.getPreviousRegionId(); - RegionId currentRegionId = personRegionUpdateEvent.getCurrentRegionId(); - - for (final ResourceId resourceId : resourceIds) { - final long personResourceLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); - if (personResourceLevel > 0) { - increment(currentRegionId, resourceId, Activity.PERSON_REGION_ARRIVAL, personResourceLevel); - increment(previousRegionId, resourceId, Activity.PERSON_REGION_DEPARTURE, personResourceLevel); - } - } - } - - private void handleRegionResourceUpdateEvent(ActorContext actorContext, RegionResourceUpdateEvent regionResourceUpdateEvent) { - - ResourceId resourceId = regionResourceUpdateEvent.getResourceId(); - if (!resourceIds.contains(resourceId)) { - return; - } - RegionId regionId = regionResourceUpdateEvent.getRegionId(); - long previousResourceLevel = regionResourceUpdateEvent.getPreviousResourceLevel(); - long currentResourceLevel = regionResourceUpdateEvent.getCurrentResourceLevel(); - long amount = currentResourceLevel - previousResourceLevel; - if (amount > 0) { - increment(regionId, resourceId, Activity.REGION_RESOURCE_ADDITION, amount); - } else { - amount = -amount; - increment(regionId, resourceId, Activity.REGION_RESOURCE_REMOVAL, amount); - } - } - - /* - * Increments the counter for the given tuple - */ - private void increment(final RegionId regionId, final ResourceId resourceId, final Activity activity, final long count) { - final Map> resourceMap = regionMap.get(regionId); - final Map activityMap = resourceMap.get(resourceId); - final Counter counter = activityMap.get(activity); - counter.actionCount++; - counter.itemCount += count; - } - - private RegionsDataManager regionsDataManager; - private ResourcesDataManager resourcesDataManager; - - /** - * @throws ContractException - *
      • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if a - * resource id passed to the constructor is unknown - *
      • - * - */ - @Override - public void init(final ActorContext actorContext) { - super.init(actorContext); - - actorContext.subscribe(PersonAdditionEvent.class, this::handlePersonAdditionEvent); - actorContext.subscribe(PersonImminentRemovalEvent.class, this::handlePersonImminentRemovalEvent); - actorContext.subscribe(PersonRegionUpdateEvent.class, this::handlePersonRegionUpdateEvent); - actorContext.subscribe(RegionResourceUpdateEvent.class, this::handleRegionResourceUpdateEvent); - - resourcesDataManager = actorContext.getDataManager(ResourcesDataManager.class); - PeopleDataManager peopleDataManager = actorContext.getDataManager(PeopleDataManager.class); - RegionsDataManager regionsDataManager = actorContext.getDataManager(RegionsDataManager.class); - regionsDataManager = actorContext.getDataManager(RegionsDataManager.class); - - if (resourceIds.size() == 0) { - resourceIds.addAll(resourcesDataManager.getResourceIds()); - } - /* - * Ensure that every client supplied resource identifier is valid - */ - final Set validResourceIds = resourcesDataManager.getResourceIds(); - for (final ResourceId resourceId : resourceIds) { - if (!validResourceIds.contains(resourceId)) { - throw new ContractException(ResourceError.UNKNOWN_RESOURCE_ID, resourceId); - } - } - - // If all the resources are included in the report, then subscribe to - // the event, otherwise subscribe to each resource - if (resourceIds.stream().collect(Collectors.toSet()).equals(resourcesDataManager.getResourceIds())) { - actorContext.subscribe(PersonResourceUpdateEvent.class, this::handlePersonResourceUpdateEvent); - } else { - for (ResourceId resourceId : resourceIds) { - EventLabel eventLabelByResource = PersonResourceUpdateEvent.getEventLabelByResource(actorContext, resourceId); - actorContext.subscribe(eventLabelByResource, this::handlePersonResourceUpdateEvent); - } - } - - /* - * Filling the region map with empty counters - */ - for (final RegionId regionId : regionsDataManager.getRegionIds()) { - final Map> resourceMap = new LinkedHashMap<>(); - regionMap.put(regionId, resourceMap); - for (final ResourceId resourceId : resourceIds) { - final Map activityMap = new LinkedHashMap<>(); - resourceMap.put(resourceId, activityMap); - for (final Activity activity : Activity.values()) { - final Counter counter = new Counter(); - activityMap.put(activity, counter); - } - } - } - - for (PersonId personId : peopleDataManager.getPeople()) { - final RegionId regionId = regionsDataManager.getPersonRegion(personId); - - for (final ResourceId resourceId : resourceIds) { - final long personResourceLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); - if (personResourceLevel > 0) { - increment(regionId, resourceId, Activity.PERSON_ARRIVAL, personResourceLevel); - } - } - } - - for (RegionId regionId : regionsDataManager.getRegionIds()) { - for (ResourceId resourceId : resourcesDataManager.getResourceIds()) { - long regionResourceLevel = resourcesDataManager.getRegionResourceLevel(regionId, resourceId); - if (resourceIds.contains(resourceId)) { - increment(regionId, resourceId, Activity.REGION_RESOURCE_ADDITION, regionResourceLevel); - } - } - } - } -} \ No newline at end of file diff --git a/gcm3/src/main/java/plugins/resources/datamanagers/ResourcesDataManager.java b/gcm3/src/main/java/plugins/resources/datamanagers/ResourcesDataManager.java deleted file mode 100644 index 9060f705c..000000000 --- a/gcm3/src/main/java/plugins/resources/datamanagers/ResourcesDataManager.java +++ /dev/null @@ -1,1249 +0,0 @@ -package plugins.resources.datamanagers; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import nucleus.DataManager; -import nucleus.DataManagerContext; -import nucleus.NucleusError; -import nucleus.SimulationContext; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.events.BulkPersonAdditionEvent; -import plugins.people.events.PersonAdditionEvent; -import plugins.people.events.PersonImminentRemovalEvent; -import plugins.people.support.BulkPersonConstructionData; -import plugins.people.support.PersonConstructionData; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import plugins.regions.datamanagers.RegionsDataManager; -import plugins.regions.support.RegionError; -import plugins.regions.support.RegionId; -import plugins.resources.ResourcesPlugin; -import plugins.resources.ResourcesPluginData; -import plugins.resources.events.PersonResourceUpdateEvent; -import plugins.resources.events.RegionResourceUpdateEvent; -import plugins.resources.events.ResourcePropertyUpdateEvent; -import plugins.resources.support.ResourceError; -import plugins.resources.support.ResourceId; -import plugins.resources.support.ResourceInitialization; -import plugins.resources.support.ResourcePropertyId; -import plugins.util.properties.PropertyDefinition; -import plugins.util.properties.PropertyError; -import plugins.util.properties.PropertyValueRecord; -import plugins.util.properties.TimeTrackingPolicy; -import plugins.util.properties.arraycontainers.DoubleValueContainer; -import plugins.util.properties.arraycontainers.IntValueContainer; -import util.errors.ContractException; - -/** - * Mutable data manager that backs the {@linkplain ResourcesDataManager}. This - * data manager is for internal use by the {@link ResourcesPlugin} and should - * not be published. - * - * All resources are defined during construction and cannot be changed. Resource - * property values are mutable and specific to the type of resource. Limited - * validation of inputs are performed and mutation methods have invocation - * ordering requirements. - * - * @author Shawn Hatch - * - */ - -public final class ResourcesDataManager extends DataManager { - /* - * Static utility class for tracking region resources. - */ - private static class RegionResourceRecord { - private final SimulationContext simulationContext; - - private long amount; - - private double assignmentTime; - - public RegionResourceRecord(final SimulationContext simulationContext) { - this.simulationContext = simulationContext; - } - - public void decrementAmount(final long amount) { - if (amount < 0) { - throw new ContractException(ResourceError.NEGATIVE_RESOURCE_AMOUNT); - } - - if (this.amount < amount) { - throw new ContractException(ResourceError.INSUFFICIENT_RESOURCES_AVAILABLE); - } - this.amount = Math.subtractExact(this.amount, amount); - assignmentTime = simulationContext.getTime(); - } - - public long getAmount() { - return amount; - } - - public double getAssignmentTime() { - return assignmentTime; - } - - public void incrementAmount(final long amount) { - if (amount < 0) { - throw new ContractException(ResourceError.NEGATIVE_RESOURCE_AMOUNT); - } - this.amount = Math.addExact(this.amount, amount); - assignmentTime = simulationContext.getTime(); - } - - } - - private PeopleDataManager peopleDataManager; - private RegionsDataManager regionsDataManager; - - // resources - private final Map> resourcePropertyMap = new LinkedHashMap<>(); - - /* - * Stores resource amounts per person keyed by the resourceId - */ - private final Map personResourceValues = new LinkedHashMap<>(); - - private final Map> resourcePropertyDefinitions = new LinkedHashMap<>(); - - /* - * Stores resource assignment times per person keyed by the resourceId. Key - * existence subject to time recording policies specified by the scenario. - */ - private final Map personResourceTimes = new LinkedHashMap<>(); - - private final Map resourceTimeTrackingPolicies = new LinkedHashMap<>(); - - private final Map> regionResources = new LinkedHashMap<>(); - - private final ResourcesPluginData resourcesPluginData; - - private DataManagerContext dataManagerContext; - - /** - * Constructs the PersonResourceManager from the context - * - * @throws ContractException - *
      • {@linkplain NucleusError#NULL_RESOURCE_PLUGIN_DATA} if the plugin data is null
      • - */ - public ResourcesDataManager(final ResourcesPluginData resourcesPluginData) { - if(resourcesPluginData == null) { - throw new ContractException(ResourceError.NULL_RESOURCE_PLUGIN_DATA); - } - this.resourcesPluginData = resourcesPluginData; - } - - /** - * Reduces the resource for the particular person and resource by the - * amount. - * - * @throws RuntimeException - *
      • if the resource id is null
      • - *
      • if the resource id is unknown
      • - *
      • if the person id null
      • - *
      • if the person id has a negative value
      • - *
      • if the amount causes an overflow
      • - */ - private void decrementPersonResourceLevel(final ResourceId resourceId, final PersonId personId, final long resourceAmount) { - personResourceValues.get(resourceId).decrementLongValue(personId.getValue(), resourceAmount); - /* - * if the resource assignment times are being tracked, then record the - * resource time. - */ - final DoubleValueContainer doubleValueContainer = personResourceTimes.get(resourceId); - if (doubleValueContainer != null) { - doubleValueContainer.setValue(personId.getValue(), dataManagerContext.getTime()); - } - } - - /** - * Reduces the resource for the particular region and resource by the - * amount. - * - * @throws RuntimeException - *
      • if the resource id is null
      • - *
      • if the resource id is unknown
      • - *
      • if the region id null
      • - *
      • if the region id is unknown
      • - * - * - * @throws ContractException - *
      • {@linkplain ResourceError#NEGATIVE_RESOURCE_AMOUNT} if - * the amount is negative
      • - *
      • {@linkplain ResourceError#INSUFFICIENT_RESOURCES_AVAILABLE} - * if the amount exceeds the current balance
      • - */ - - private void decrementRegionResourceLevel(final RegionId regionId, final ResourceId resourceId, final long amount) { - final RegionResourceRecord regionResourceRecord = regionResources.get(regionId).get(resourceId); - regionResourceRecord.decrementAmount(amount); - } - - /** - * Expands the capacity of data structures to hold people by the given - * count. Used to more efficiently prepare for bulk population additions. - * - * @throws ContractException - *
      • {@linkplain PersonError#NEGATIVE_GROWTH_PROJECTION} if - * the count is negative
      • - */ - public void expandCapacity(final int count) { - if (count < 0) { - throw new ContractException(PersonError.NEGATIVE_GROWTH_PROJECTION); - } - if (count > 0) { - for (final ResourceId resourceId : personResourceValues.keySet()) { - final IntValueContainer intValueContainer = personResourceValues.get(resourceId); - intValueContainer.setCapacity(intValueContainer.getCapacity() + count); - final TimeTrackingPolicy resourceTimeTrackingPolicy = resourceTimeTrackingPolicies.get(resourceId); - if (resourceTimeTrackingPolicy == TimeTrackingPolicy.TRACK_TIME) { - final DoubleValueContainer doubleValueContainer = personResourceTimes.get(resourceId); - doubleValueContainer.setCapacity(doubleValueContainer.getCapacity() + count); - } - } - } - } - - /** - * Returns the set of people who do not have any of the given resource as a - * list. - * - * @throws ContractException - *
      • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
      • - *
      • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if the - * resource id is unknown
      • - */ - public List getPeopleWithoutResource(final ResourceId resourceId) { - validateResourceId(resourceId); - /* - * First, we loop through all possible person id values and determine - * the exact size of the returned list. - */ - int count = 0; - final IntValueContainer intValueContainer = personResourceValues.get(resourceId); - final int n = peopleDataManager.getPersonIdLimit(); - for (int personIndex = 0; personIndex < n; personIndex++) { - if (peopleDataManager.personIndexExists(personIndex)) { - final long resourceLevel = intValueContainer.getValueAsLong(personIndex); - if (resourceLevel == 0) { - count++; - } - } - } - - /* - * Now we create the list - */ - final List result = new ArrayList<>(count); - - /* - * We loop again and add the people to the list - */ - for (int personId = 0; personId < n; personId++) { - if (peopleDataManager.personIndexExists(personId)) { - final long resourceLevel = intValueContainer.getValueAsLong(personId); - if (resourceLevel == 0) { - result.add(peopleDataManager.getBoxedPersonId(personId).get()); - } - } - } - return result; - } - - /** - * Returns the list of people who have a non-zero level of the resource - * - * @throws ContractException - *
      • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
      • - *
      • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if the - * resource id is unknown
      • - * - */ - public List getPeopleWithResource(final ResourceId resourceId) { - validateResourceId(resourceId); - /* - * First, we loop through all possible person id values and determine - * the exact size of the returned list. - */ - int count = 0; - final IntValueContainer intValueContainer = personResourceValues.get(resourceId); - final int n = peopleDataManager.getPersonIdLimit(); - for (int personId = 0; personId < n; personId++) { - if (peopleDataManager.personIndexExists(personId)) { - final long resourceLevel = intValueContainer.getValueAsLong(personId); - if (resourceLevel > 0) { - count++; - } - } - } - /* - * Now we create the list - */ - final List result = new ArrayList<>(count); - /* - * We loop again and add the people to the list - */ - for (int personIndex = 0; personIndex < n; personIndex++) { - if (peopleDataManager.personIndexExists(personIndex)) { - final long resourceLevel = intValueContainer.getValueAsLong(personIndex); - if (resourceLevel > 0) { - result.add(peopleDataManager.getBoxedPersonId(personIndex).get()); - } - } - } - return result; - - } - - /** - * Returns the region resource level. - * - * @throws ContractException - *
      • {@linkplain RegionError#NULL_REGION_ID} if the region id - * is null
      • - *
      • {@linkplain RegionError#UNKNOWN_REGION_ID} if the region - * id is unknown
      • - *
      • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
      • - *
      • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if the - * resource id is unknown
      • - */ - public long getPersonResourceLevel(final ResourceId resourceId, final PersonId personId) { - validatePersonExists(personId); - validateResourceId(resourceId); - return personResourceValues.get(resourceId).getValueAsLong(personId.getValue()); - } - - /** - * Returns the time when the resource level was last assigned for the given - * person and resource - * - * @throws ContractException - *
      • {@linkplain PersonError#NULL_PERSON_ID} if the person id - * is null
      • - *
      • {@linkplain PersonError#UNKNOWN_PERSON_ID} if the person - * id is unknown
      • - *
      • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
      • - *
      • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if the - * resource id is unknown
      • - *
      • {@linkplain ResourceError#RESOURCE_ASSIGNMENT_TIME_NOT_TRACKED} - * if assignment times are not tracked for the resource when - * applied to people
      • - */ - public double getPersonResourceTime(final ResourceId resourceId, final PersonId personId) { - validatePersonExists(personId); - validateResourceId(resourceId); - validatePersonResourceTimesTracked(resourceId); - final DoubleValueContainer doubleValueContainer = personResourceTimes.get(resourceId); - return doubleValueContainer.getValue(personId.getValue()); - - } - - /** - * Returns the time tracking policy for the given resource - * - * @throws ContractException - *
      • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
      • - *
      • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if the - * resource id is unknown
      • - */ - public TimeTrackingPolicy getPersonResourceTimeTrackingPolicy(final ResourceId resourceId) { - validateResourceId(resourceId); - return resourceTimeTrackingPolicies.get(resourceId); - } - - /** - * Returns the current resource level for the given resource id and region - * id - * - * @throws ContractException - *
      • {@linkplain RegionError#NULL_REGION_ID} if the region id - * is null
      • - *
      • {@linkplain RegionError#UNKNOWN_REGION_ID} if the region - * id is unknown
      • - *
      • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
      • - *
      • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if the - * resource id is unknown
      • - */ - public long getRegionResourceLevel(final RegionId regionId, final ResourceId resourceId) { - validateRegionId(regionId); - validateResourceId(resourceId); - final RegionResourceRecord regionResourceRecord = regionResources.get(regionId).get(resourceId); - return regionResourceRecord.getAmount(); - } - - /** - * Returns the last assignment time for the region resource level - * - * @throws ContractException - *
      • {@linkplain RegionError#NULL_REGION_ID} if the region id - * is null
      • - *
      • {@linkplain RegionError#UNKNOWN_REGION_ID} if the region - * id is unknown
      • - *
      • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
      • - *
      • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if the - * resource id is unknown
      • - */ - public double getRegionResourceTime(final RegionId regionId, final ResourceId resourceId) { - validateRegionId(regionId); - validateResourceId(resourceId); - - final RegionResourceRecord regionResourceRecord = regionResources.get(regionId).get(resourceId); - return regionResourceRecord.getAssignmentTime(); - - } - - /** - * Returns the resource ids - */ - @SuppressWarnings("unchecked") - public Set getResourceIds() { - final Set result = new LinkedHashSet<>(personResourceValues.size()); - for (final ResourceId resourceId : personResourceValues.keySet()) { - result.add((T) resourceId); - } - personResourceValues.keySet(); - return result; - } - - /** - * Returns the property definition for the given resource id and resource - * property id - * - * @throws ContractException - *
      • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
      • - *
      • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if the - * resource id is unknown
      • - *
      • {@linkplain ResourceError#NULL_RESOURCE_PROPERTY_ID} if - * the resource property id is null
      • - *
      • {@linkplain ResourceError#UNKNOWN_RESOURCE_PROPERTY_ID} - * if the resource property id is unknown
      • - */ - public PropertyDefinition getResourcePropertyDefinition(final ResourceId resourceId, final ResourcePropertyId resourcePropertyId) { - validateResourceId(resourceId); - validateResourcePropertyId(resourceId, resourcePropertyId); - return resourcePropertyDefinitions.get(resourceId).get(resourcePropertyId); - } - - /** - * Returns the resource property id values for the given resource id - * - * @throws ContractException - *
      • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
      • - *
      • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if the - * resource id is unknown
      • - */ - @SuppressWarnings("unchecked") - public Set getResourcePropertyIds(final ResourceId resourceId) { - validateResourceId(resourceId); - final Map defMap = resourcePropertyDefinitions.get(resourceId); - final Set result = new LinkedHashSet<>(defMap.keySet().size()); - for (final ResourcePropertyId resourcePropertyId : defMap.keySet()) { - result.add((T) resourcePropertyId); - } - return result; - } - - /** - * Returns the last assignment time for the resource property - * - * @throws ContractException - *
      • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
      • - *
      • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if the - * resource id is unknown
      • - *
      • {@linkplain ResourceError#NULL_RESOURCE_PROPERTY_ID} if - * the resource property id is null
      • - *
      • {@linkplain ResourceError#UNKNOWN_RESOURCE_PROPERTY_ID} - * if the resource property id is unknown
      • - */ - - public double getResourcePropertyTime(final ResourceId resourceId, final ResourcePropertyId resourcePropertyId) { - validateResourceId(resourceId); - validateResourcePropertyId(resourceId, resourcePropertyId); - return resourcePropertyMap.get(resourceId).get(resourcePropertyId).getAssignmentTime(); - } - - /** - * Returns the value of the resource property. - * - * @throws ContractException - *
      • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
      • - *
      • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if the - * resource id is unknown
      • - *
      • {@linkplain ResourceError#NULL_RESOURCE_PROPERTY_ID} if - * the resource property id is null
      • - *
      • {@linkplain ResourceError#UNKNOWN_RESOURCE_PROPERTY_ID} - * if the resource property id is unknown
      • - */ - @SuppressWarnings("unchecked") - public T getResourcePropertyValue(final ResourceId resourceId, final ResourcePropertyId resourcePropertyId) { - validateResourceId(resourceId); - validateResourcePropertyId(resourceId, resourcePropertyId); - return (T) resourcePropertyMap.get(resourceId).get(resourcePropertyId).getValue(); - } - - /** - * Increase the resource for the particular person and resource by the - * amount. - * - * @throws RuntimeException - *
      • if the resource id is null
      • - *
      • if the resource id is unknown
      • - *
      • if the person id null
      • - *
      • if the person id has a negative value
      • - *
      • if the amount causes an overflow
      • - */ - private void incrementPersonResourceLevel(final ResourceId resourceId, final PersonId personId, final long resourceAmount) { - personResourceValues.get(resourceId).incrementLongValue(personId.getValue(), resourceAmount); - /* - * if the resource assignment times are being tracked, then record the - * resource time. - */ - final DoubleValueContainer doubleValueContainer = personResourceTimes.get(resourceId); - if (doubleValueContainer != null) { - doubleValueContainer.setValue(personId.getValue(), dataManagerContext.getTime()); - } - } - - /** - * Increases the resource for the particular region and resource by the - * amount. - * - * @throws RuntimeException - *
      • if the resource id is null
      • - *
      • if the resource id is unknown
      • - *
      • if the region id null
      • - *
      • if the region id is unknown
      • - *
      • if the amount is negative
      • - *
      • if the amount causes an overflow
      • - * - * @throws ContractException - * - *
      • {@linkplain ResourceError#NEGATIVE_RESOURCE_AMOUNT} if - * the amount is negative
      • - */ - private void incrementRegionResourceLevel(final RegionId regionId, final ResourceId resourceId, final long amount) { - final RegionResourceRecord regionResourceRecord = regionResources.get(regionId).get(resourceId); - regionResourceRecord.incrementAmount(amount); - } - - /** - * - *
          - *
        • Adds all event labelers defined by the following events
          - *
            - *
          • {@linkplain PersonResourceUpdateEvent}
          • - *
          • {@linkplain RegionResourceUpdateEvent}
          • - *
          • {@linkplain ResourcePropertyUpdateEvent}
          • - *
          - *
        • - * - *
        • Sets resource property values from the - * {@linkplain ResourcesPluginData}
        • - * - *
        • Sets region resource levels from the - * {@linkplain ResourcesPluginData}
        • - * - *
        • Sets person resource levels from the - * {@linkplain ResourcesPluginData}
        • - * - *

          - * Subscribes to the following events: - * - *

            - * - * - *
          • {@linkplain PersonAdditionEvent}
            Sets the - * person's initial resource levels in the {@linkplain ResourcesDataManager} - * from the ResourceInitialization references in the auxiliary data of the - * event. - * - *
            - *
            - * Throws {@link ContractException} - *
              - *
            • {@linkplain PersonError#NULL_PERSON_ID} if the person id is null
            • - *
            • {@linkplain PersonError#UNKNOWN_PERSON_ID} if the person id is - * unknown
            • - *
            • {@linkplain ResourceError#NULL_RESOURCE_ID} if the auxiliary data - * contains a ResourceInitialization that has a null resource id
            • - *
            • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if the auxiliary data - * contains a ResourceInitialization that has an unknown resource id
            • - *
            • {@linkplain ResourceError#NEGATIVE_RESOURCE_AMOUNT} if the auxiliary - * data contains a ResourceInitialization that has a negative resource - * level
            • - *
            - * - * - *
          • - * ------------------------------------------------------------------------------- - *
          • {@linkplain BulkPersonAdditionEvent}
            Sets each - * person's initial resource levels in the {@linkplain ResourcesDataManager} - * from the ResourceInitialization references in the auxiliary data of the - * event. - * - *
            - *
            - * Throws {@link ContractException} - *
              - *
            • {@linkplain PersonError#NULL_PERSON_ID} if the person id is null
            • - *
            • {@linkplain PersonError#UNKNOWN_PERSON_ID} if the person id is - * unknown
            • - *
            • {@linkplain ResourceError#NULL_RESOURCE_ID} if the auxiliary data - * contains a ResourceInitialization that has a null resource id
            • - *
            • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if the auxiliary data - * contains a ResourceInitialization that has an unknown resource id
            • - *
            • {@linkplain ResourceError#NEGATIVE_RESOURCE_AMOUNT} if the auxiliary - * data contains a ResourceInitialization that has a negative resource - * level
            • - *
            - * - *
          • - * ------------------------------------------------------------------------------- - *
          • {@linkplain PersonImminentRemovalEvent}
            - * Removes the resource assignment data for the person from the - * {@linkplain ResourcesDataManager} by scheduling the removal for the - * current time. This allows the person and their resource levels to remain - * long enough for resolvers, agents and reports to have final reference to - * the person while still associated with any relevant resources.
            - *
            - * Throws {@linkplain ContractException} - *
              - *
            • {@linkplain PersonError#NULL_PERSON_ID} if the person id is null
            • - *
            • {@linkplain PersonError#UNKNOWN_PERSON_ID} if the person id is - * unknown
            • - *
            - * - */ - @Override - public void init(final DataManagerContext dataManagerContext) { - super.init(dataManagerContext); - if (dataManagerContext == null) { - throw new ContractException(NucleusError.NULL_SIMULATION_CONTEXT); - } - this.dataManagerContext = dataManagerContext; - peopleDataManager = dataManagerContext.getDataManager(PeopleDataManager.class); - regionsDataManager = dataManagerContext.getDataManager(RegionsDataManager.class); - - dataManagerContext.addEventLabeler(PersonResourceUpdateEvent.getEventLabelerForRegionAndResource(regionsDataManager)); - dataManagerContext.addEventLabeler(PersonResourceUpdateEvent.getEventLabelerForPersonAndResource()); - dataManagerContext.addEventLabeler(PersonResourceUpdateEvent.getEventLabelerForResource()); - dataManagerContext.addEventLabeler(ResourcePropertyUpdateEvent.getEventLabeler()); - dataManagerContext.addEventLabeler(RegionResourceUpdateEvent.getEventLabelerForRegionAndResource()); - - regionsDataManager = dataManagerContext.getDataManager(RegionsDataManager.class); - - peopleDataManager = dataManagerContext.getDataManager(PeopleDataManager.class); - - // load resource property definitions, property values and time tracking - // policies - for (ResourceId resourceId : resourcesPluginData.getResourceIds()) { - // private final Map - // resourceTimeTrackingPolicies = new LinkedHashMap<>(); - TimeTrackingPolicy timeTrackingPolicy = resourcesPluginData.getPersonResourceTimeTrackingPolicy(resourceId); - resourceTimeTrackingPolicies.put(resourceId, timeTrackingPolicy); - - if (timeTrackingPolicy == TimeTrackingPolicy.TRACK_TIME) { - final DoubleValueContainer doubleValueContainer = new DoubleValueContainer(0D); - personResourceTimes.put(resourceId, doubleValueContainer); - } - - // private final Map> resourcePropertyMap = new - // LinkedHashMap<>(); - // private final Map> resourcePropertyDefinitions = new - // LinkedHashMap<>(); - - final IntValueContainer intValueContainer = new IntValueContainer(0L); - personResourceValues.put(resourceId, intValueContainer); - - Set resourcePropertyIds = resourcesPluginData.getResourcePropertyIds(resourceId); - for (ResourcePropertyId resourcePropertyId : resourcePropertyIds) { - PropertyDefinition propertyDefinition = resourcesPluginData.getResourcePropertyDefinition(resourceId, resourcePropertyId); - Object resourcePropertyValue = resourcesPluginData.getResourcePropertyValue(resourceId, resourcePropertyId); - - Map defMap = resourcePropertyDefinitions.get(resourceId); - if (defMap != null) { - if (defMap.containsKey(resourcePropertyId)) { - throw new ContractException(ResourceError.DUPLICATE_RESOURCE_PROPERTY_DEFINITION, resourcePropertyId); - } - } - - if (defMap == null) { - defMap = new LinkedHashMap<>(); - resourcePropertyDefinitions.put(resourceId, defMap); - } - - defMap.put(resourcePropertyId, propertyDefinition); - - Map map = resourcePropertyMap.get(resourceId); - if (map == null) { - map = new LinkedHashMap<>(); - resourcePropertyMap.put(resourceId, map); - } - - final PropertyValueRecord propertyValueRecord = new PropertyValueRecord(dataManagerContext); - propertyValueRecord.setPropertyValue(resourcePropertyValue); - map.put(resourcePropertyId, propertyValueRecord); - - } - } - - // load the region resources - // private final Map> - // regionResources = new LinkedHashMap<>(); - Set regionIds = regionsDataManager.getRegionIds(); - - for (RegionId regionId : regionIds) { - final Map map = new LinkedHashMap<>(); - regionResources.put(regionId, map); - } - for (ResourceId resourceId : resourcesPluginData.getResourceIds()) { - for (final RegionId regionId : regionResources.keySet()) { - final Map map = regionResources.get(regionId); - map.put(resourceId, new RegionResourceRecord(dataManagerContext)); - } - } - - for (final RegionId regionId : resourcesPluginData.getRegionIds()) { - if (!regionIds.contains(regionId)) { - throw new ContractException(RegionError.UNKNOWN_REGION_ID, regionId + " is an unknown region with initial resources"); - } - for (final ResourceId resourceId : resourcesPluginData.getResourceIds()) { - final Long amount = resourcesPluginData.getRegionResourceLevel(regionId, resourceId); - if (amount != null) { - final RegionResourceRecord regionResourceRecord = regionResources.get(regionId).get(resourceId); - regionResourceRecord.incrementAmount(amount); - } - } - } - - // load the person resources - // private final Map personResourceValues - // = new LinkedHashMap<>(); - // private final Map - // personResourceTimes = new LinkedHashMap<>(); - for (final PersonId personId : resourcesPluginData.getPersonIds()) { - if (!peopleDataManager.personExists(personId)) { - throw new ContractException(PersonError.UNKNOWN_PERSON_ID, personId); - } - for (final ResourceId resourceId : personResourceValues.keySet()) { - final Long resourceAmount = resourcesPluginData.getPersonResourceLevel(personId, resourceId); - if (resourceAmount > 0) { - personResourceValues.get(resourceId).incrementLongValue(personId.getValue(), resourceAmount); - final DoubleValueContainer doubleValueContainer = personResourceTimes.get(resourceId); - if (doubleValueContainer != null) { - doubleValueContainer.setValue(personId.getValue(), dataManagerContext.getTime()); - } - } - } - } - - dataManagerContext.subscribe(PersonAdditionEvent.class, this::handlePersonAdditionEvent); - dataManagerContext.subscribe(BulkPersonAdditionEvent.class, this::handleBulkPersonAdditionEvent); - dataManagerContext.subscribe(PersonImminentRemovalEvent.class, this::handlePersonImminentRemovalEvent); - } - - /** - * Returns true if and only if the given resource id is known - */ - public boolean resourceIdExists(final ResourceId resourceId) { - return personResourceValues.containsKey(resourceId); - } - - /** - * Returns true if and only if there is a resource property defined for the - * given resource id and resource property id - */ - public boolean resourcePropertyIdExists(final ResourceId resourceId, final ResourcePropertyId resourcePropertyId) { - if ((resourceId == null) || (resourcePropertyId == null)) { - return false; - } - - final Map map = resourcePropertyMap.get(resourceId); - - if (map == null) { - return false; - } - - if (!map.containsKey(resourcePropertyId)) { - return false; - } - - return true; - - } - - private void validatePersonExists(final PersonId personId) { - if (personId == null) { - throw new ContractException(PersonError.NULL_PERSON_ID); - } - if (!peopleDataManager.personExists(personId)) { - throw new ContractException(PersonError.UNKNOWN_PERSON_ID); - } - } - - /* - * Preconditions: the resource id must exist - */ - private void validatePersonResourceTimesTracked(final ResourceId resourceId) { - if (resourceTimeTrackingPolicies.get(resourceId) != TimeTrackingPolicy.TRACK_TIME) { - throw new ContractException(ResourceError.RESOURCE_ASSIGNMENT_TIME_NOT_TRACKED); - } - } - - private void validateRegionId(final RegionId regionId) { - - if (regionId == null) { - throw new ContractException(RegionError.NULL_REGION_ID); - } - - if (!regionsDataManager.regionIdExists(regionId)) { - throw new ContractException(RegionError.UNKNOWN_REGION_ID, regionId); - } - } - - private void validateResourceId(final ResourceId resourceId) { - if (resourceId == null) { - throw new ContractException(ResourceError.NULL_RESOURCE_ID); - } - if (!personResourceValues.containsKey(resourceId)) { - throw new ContractException(ResourceError.UNKNOWN_RESOURCE_ID, resourceId); - } - } - - /* - * Precondition : the resource id must exist - */ - private void validateResourcePropertyId(final ResourceId resourceId, final ResourcePropertyId resourcePropertyId) { - if (resourcePropertyId == null) { - throw new ContractException(ResourceError.NULL_RESOURCE_PROPERTY_ID); - } - - final Map map = resourcePropertyMap.get(resourceId); - - if ((map == null) || !map.containsKey(resourcePropertyId)) { - throw new ContractException(ResourceError.UNKNOWN_RESOURCE_PROPERTY_ID, resourcePropertyId); - } - - } - - /** - * Transfers resources from one region to another. Generates the - * corresponding {@linkplain RegionResourceUpdateEvent} events - * for each region. - * - * @throws ContractException - *
          • {@linkplain RegionError#NULL_REGION_ID} if the source - * region is null
          • - *
          • {@linkplain RegionError#UNKNOWN_REGION_ID} if the source - * region is unknown
          • - *
          • {@linkplain RegionError#NULL_REGION_ID} if the - * destination region is null
          • - *
          • {@linkplain RegionError#UNKNOWN_REGION_ID} if the - * destination region is unknown
          • - *
          • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
          • - *
          • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if the - * resource id is unknown
          • - *
          • {@linkplain ResourceError#NEGATIVE_RESOURCE_AMOUNT} if - * the resource amount is negative
          • - *
          • {@linkplain ResourceError#REFLEXIVE_RESOURCE_TRANSFER} if - * the source and destination region are equal
          • - *
          • {@linkplain ResourceError#INSUFFICIENT_RESOURCES_AVAILABLE} - * if the source region does not have sufficient resources to - * support the transfer
          • - *
          • {@linkplain ResourceError#RESOURCE_ARITHMETIC_EXCEPTION} - * if the transfer will cause a numeric overflow in the - * destination region
          • - */ - public void transferResourceBetweenRegions(ResourceId resourceId, RegionId sourceRegionId, RegionId destinationRegionId, long amount) { - - validateRegionId(sourceRegionId); - validateRegionId(destinationRegionId); - validateResourceId(resourceId); - validateNonnegativeResourceAmount(amount); - validateDifferentRegionsForResourceTransfer(sourceRegionId, destinationRegionId); - validateRegionHasSufficientResources(resourceId, sourceRegionId, amount); - - RegionResourceRecord sourceRecord = regionResources.get(sourceRegionId).get(resourceId); - RegionResourceRecord destinationRecord = regionResources.get(destinationRegionId).get(resourceId); - - final long regionResourceLevel = regionResources.get(destinationRegionId).get(resourceId).getAmount(); - - validateResourceAdditionValue(regionResourceLevel, amount); - - final long previousSourceRegionResourceLevel = sourceRecord.getAmount(); - final long previousDestinationRegionResourceLevel = destinationRecord.getAmount(); - - decrementRegionResourceLevel(sourceRegionId, resourceId, amount); - incrementRegionResourceLevel(destinationRegionId, resourceId, amount); - - long currentSourceRegionResourceLevel = sourceRecord.getAmount(); - long currentDestinationRegionResourceLevel = destinationRecord.getAmount(); - - dataManagerContext.releaseEvent(new RegionResourceUpdateEvent(sourceRegionId, resourceId, previousSourceRegionResourceLevel, currentSourceRegionResourceLevel)); - dataManagerContext.releaseEvent(new RegionResourceUpdateEvent(destinationRegionId, resourceId, previousDestinationRegionResourceLevel, currentDestinationRegionResourceLevel)); - - } - - private void validateNonnegativeResourceAmount(final long amount) { - if (amount < 0) { - throw new ContractException(ResourceError.NEGATIVE_RESOURCE_AMOUNT); - } - } - - private void validateDifferentRegionsForResourceTransfer(final RegionId sourceRegionId, final RegionId destinationRegionId) { - if (sourceRegionId.equals(destinationRegionId)) { - throw new ContractException(ResourceError.REFLEXIVE_RESOURCE_TRANSFER); - } - } - - /* - * Preconditions : the region and resource must exist - */ - private void validateRegionHasSufficientResources(final ResourceId resourceId, final RegionId regionId, final long amount) { - RegionResourceRecord regionResourceRecord = regionResources.get(regionId).get(resourceId); - final long currentAmount = regionResourceRecord.getAmount(); - if (currentAmount < amount) { - throw new ContractException(ResourceError.INSUFFICIENT_RESOURCES_AVAILABLE); - } - } - - private void validateResourceAdditionValue(final long currentResourceLevel, final long amount) { - try { - Math.addExact(currentResourceLevel, amount); - } catch (final ArithmeticException e) { - throw new ContractException(ResourceError.RESOURCE_ARITHMETIC_EXCEPTION); - } - } - - /** - * Expends an amount of resource from a person. Generates the corresponding - * {@linkplain PersonResourceUpdateEvent} event - * - * @throws ContractException - * - *
          • {@linkplain PersonError#NULL_PERSON_ID} if the person id - * is null
          • - *
          • {@linkplain PersonError#UNKNOWN_PERSON_ID} if the person - * does not exist
          • - *
          • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
          • - *
          • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if the - * resource id is unknown
          • - *
          • {@linkplain ResourceError#NEGATIVE_RESOURCE_AMOUNT} if - * the amount is negative
          • - *
          • {@linkplain ResourceError#INSUFFICIENT_RESOURCES_AVAILABLE} - * if the person does not have the required amount of the - * resource
          • - * - */ - public void removeResourceFromPerson(ResourceId resourceId, PersonId personId, long amount) { - - validatePersonExists(personId); - validateResourceId(resourceId); - validateNonnegativeResourceAmount(amount); - validatePersonHasSufficientResources(resourceId, personId, amount); - - final long oldLevel = personResourceValues.get(resourceId).getValueAsLong(personId.getValue()); - decrementPersonResourceLevel(resourceId, personId, amount); - final long newLevel = personResourceValues.get(resourceId).getValueAsLong(personId.getValue()); - dataManagerContext.releaseEvent(new PersonResourceUpdateEvent(personId, resourceId, oldLevel, newLevel)); - } - - /* - * Preconditions : the resource and person must exist - */ - private void validatePersonHasSufficientResources(final ResourceId resourceId, final PersonId personId, final long amount) { - final long oldValue = personResourceValues.get(resourceId).getValueAsLong(personId.getValue()); - if (oldValue < amount) { - throw new ContractException(ResourceError.INSUFFICIENT_RESOURCES_AVAILABLE); - } - } - - /** - * Adds an amount of resource to a region. Generates the corresponding - * {@linkplain RegionResourceUpdateEvent} event. - * - * @throws ContractException - * - *
          • {@linkplain RegionError#NULL_REGION_ID} if the region id - * is null
          • - *
          • {@linkplain RegionError#UNKNOWN_REGION_ID} if the region - * id is unknown
          • - *
          • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
          • - *
          • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if the - * resource id is unknown
          • - *
          • {@linkplain ResourceError#NEGATIVE_RESOURCE_AMOUNT} if - * the amount is negative
          • - *
          • {@linkplain ResourceError#RESOURCE_ARITHMETIC_EXCEPTION} - * if the addition results in an overflow
          • - * - * - * - */ - public void addResourceToRegion(ResourceId resourceId, RegionId regionId, long amount) { - validateRegionId(regionId); - validateResourceId(resourceId); - validateNonnegativeResourceAmount(amount); - final long previousResourceLevel = regionResources.get(regionId).get(resourceId).getAmount(); - validateResourceAdditionValue(previousResourceLevel, amount); - incrementRegionResourceLevel(regionId, resourceId, amount); - long currentResourceLevel = regionResources.get(regionId).get(resourceId).getAmount(); - dataManagerContext.releaseEvent(new RegionResourceUpdateEvent(regionId, resourceId, previousResourceLevel, currentResourceLevel)); - } - - /** - * Removes an amount of resource from a region.Generates the corresponding - * {@linkplain RegionResourceUpdateEvent} event< - * - * @throws ContractException - * - *
          • {@linkplain RegionError#NULL_REGION_ID} if the region id - * is null
          • - *
          • {@linkplain RegionError#UNKNOWN_REGION_ID} if the region - * id is unknown
          • - *
          • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
          • - *
          • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if the - * resource id is unknown
          • - *
          • {@linkplain ResourceError#NEGATIVE_RESOURCE_AMOUNT} if - * the amount is negative
          • - *
          • {@linkplain ResourceError#INSUFFICIENT_RESOURCES_AVAILABLE} - * if the region does not have the required amount of the - * resource
          • - * - * - */ - public void removeResourceFromRegion(ResourceId resourceId, RegionId regionId, long amount) { - validateRegionId(regionId); - validateResourceId(resourceId); - validateNonnegativeResourceAmount(amount); - validateRegionHasSufficientResources(resourceId, regionId, amount); - final long previousResourceLevel = regionResources.get(regionId).get(resourceId).getAmount(); - decrementRegionResourceLevel(regionId, resourceId, amount); - long currentResourceLevel = regionResources.get(regionId).get(resourceId).getAmount(); - dataManagerContext.releaseEvent(new RegionResourceUpdateEvent(regionId, resourceId, previousResourceLevel, currentResourceLevel)); - } - - /** - * Assigns a value to a resource property. Generates the corresponding - * {@linkplain ResourcePropertyUpdateEvent} event - * - * @throws ContractException - * - *
          • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
          • - *
          • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if the - * resource id is unknown
          • - *
          • {@linkplain ResourceError#NULL_RESOURCE_PROPERTY_ID} if - * the resource property id is null
          • - *
          • {@linkplain ResourceError#UNKNOWN_RESOURCE_PROPERTY_ID} - * if the resource property id is unknown
          • - *
          • {@linkplain ResourceError#NULL_RESOURCE_PROPERTY_VALUE} - * if the resource property value is null
          • - *
          • {@linkplain PropertyError#INCOMPATIBLE_VALUE} if the - * resource property value is incompatible with the - * corresponding property definition
          • - *
          • {@linkplain PropertyError#IMMUTABLE_VALUE} if the - * property has been defined as immutable
          • - * - * - * - */ - public void setResourcePropertyValue(ResourceId resourceId, ResourcePropertyId resourcePropertyId, Object resourcePropertyValue) { - validateResourceId(resourceId); - validateResourcePropertyId(resourceId, resourcePropertyId); - validateResourcePropertyValueNotNull(resourcePropertyValue); - final PropertyDefinition propertyDefinition = resourcePropertyDefinitions.get(resourceId).get(resourcePropertyId); - validateValueCompatibility(resourcePropertyId, propertyDefinition, resourcePropertyValue); - validatePropertyMutability(propertyDefinition); - final Object oldPropertyValue = resourcePropertyMap.get(resourceId).get(resourcePropertyId).getValue(); - resourcePropertyMap.get(resourceId).get(resourcePropertyId).setPropertyValue(resourcePropertyValue); - dataManagerContext.releaseEvent(new ResourcePropertyUpdateEvent(resourceId, resourcePropertyId, oldPropertyValue, resourcePropertyValue)); - } - - private void validateResourcePropertyValueNotNull(final Object propertyValue) { - if (propertyValue == null) { - throw new ContractException(ResourceError.NULL_RESOURCE_PROPERTY_VALUE); - } - } - - private void validateValueCompatibility(final Object propertyId, final PropertyDefinition propertyDefinition, final Object propertyValue) { - if (!propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())) { - throw new ContractException(PropertyError.INCOMPATIBLE_VALUE, - "Property value " + propertyValue + " is not of type " + propertyDefinition.getType().getName() + " and does not match definition of " + propertyId); - } - } - - private void validatePropertyMutability(final PropertyDefinition propertyDefinition) { - if (!propertyDefinition.propertyValuesAreMutable()) { - throw new ContractException(PropertyError.IMMUTABLE_VALUE); - } - } - - /** - * Transfers an amount of resource from a person to the person's current - * region. Generates the corresponding - * {@linkplain RegionResourceUpdateEvent} and - * {@linkplain PersonResourceUpdateEvent} events - * - * @throws ContractException - * - *
          • {@linkplain PersonError#NULL_PERSON_ID} if the person id - * is null
          • - *
          • {@linkplain PersonError#UNKNOWN_PERSON_ID} if the person - * does not exist
          • - *
          • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
          • - *
          • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if the - * resource id is unknown
          • - *
          • {@linkplain ResourceError#NEGATIVE_RESOURCE_AMOUNT} if - * the amount is negative
          • - *
          • {@linkplain ResourceError#INSUFFICIENT_RESOURCES_AVAILABLE} - * if the person does not have the required amount of the - * resource
          • - *
          • {@linkplain ResourceError#RESOURCE_ARITHMETIC_EXCEPTION} - * if the transfer results in an overflow of the region's - * resource level
          • - */ - public void transferResourceFromPersonToRegion(ResourceId resourceId, PersonId personId, long amount) { - validatePersonExists(personId); - validateResourceId(resourceId); - validateNonnegativeResourceAmount(amount); - validatePersonHasSufficientResources(resourceId, personId, amount); - final RegionId regionId = regionsDataManager.getPersonRegion(personId); - final long previousRegionResourceLevel = regionResources.get(regionId).get(resourceId).getAmount(); - validateResourceAdditionValue(previousRegionResourceLevel, amount); - final long oldLevel = personResourceValues.get(resourceId).getValueAsLong(personId.getValue()); - decrementPersonResourceLevel(resourceId, personId, amount); - final long newLevel = personResourceValues.get(resourceId).getValueAsLong(personId.getValue()); - incrementRegionResourceLevel(regionId, resourceId, amount); - long currentRegionResourceLevel = regionResources.get(regionId).get(resourceId).getAmount(); - dataManagerContext.releaseEvent(new PersonResourceUpdateEvent(personId, resourceId, oldLevel, newLevel)); - dataManagerContext.releaseEvent(new RegionResourceUpdateEvent(regionId, resourceId, previousRegionResourceLevel, currentRegionResourceLevel)); - } - - /** - * Transfers an amount of resource to a person from the person's current - * region. Generates the corresponding - * {@linkplain RegionResourceUpdateEvent} and - * {@linkplain PersonResourceUpdateEvent} events - * - * - * - * @throws ContractException - * - *
          • {@linkplain PersonError#NULL_PERSON_ID} if the person id - * is null
          • - *
          • {@linkplain PersonError#UNKNOWN_PERSON_ID} if the person - * does not exist
          • - *
          • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
          • - *
          • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if the - * resource id is unknown
          • - *
          • {@linkplain ResourceError#NEGATIVE_RESOURCE_AMOUNT} if - * the amount is negative
          • - *
          • {@linkplain ResourceError#INSUFFICIENT_RESOURCES_AVAILABLE} - * if the region does not have the required amount of the - * resource
          • - *
          • {@linkplain ResourceError#RESOURCE_ARITHMETIC_EXCEPTION} - * if the transfer results in an overflow of the person's - * resource level
          • - * - */ - public void transferResourceToPersonFromRegion(ResourceId resourceId, PersonId personId, long amount) { - - validatePersonExists(personId); - validateResourceId(resourceId); - validateNonnegativeResourceAmount(amount); - final RegionId regionId = regionsDataManager.getPersonRegion(personId); - validateRegionHasSufficientResources(resourceId, regionId, amount); - final long personResourceLevel = personResourceValues.get(resourceId).getValueAsLong(personId.getValue()); - validateResourceAdditionValue(personResourceLevel, amount); - - final long previousRegionResourceLevel = regionResources.get(regionId).get(resourceId).getAmount(); - decrementRegionResourceLevel(regionId, resourceId, amount); - incrementPersonResourceLevel(resourceId, personId, amount); - final long newLevel = personResourceValues.get(resourceId).getValueAsLong(personId.getValue()); - long currentRegionResourceLevel = regionResources.get(regionId).get(resourceId).getAmount(); - - dataManagerContext.releaseEvent(new RegionResourceUpdateEvent(regionId, resourceId, previousRegionResourceLevel, currentRegionResourceLevel)); - - dataManagerContext.releaseEvent(new PersonResourceUpdateEvent(personId, resourceId, personResourceLevel, newLevel)); - - } - - private void handlePersonAdditionEvent(final DataManagerContext dataManagerContext, final PersonAdditionEvent personAdditionEvent) { - PersonId personId = personAdditionEvent.getPersonId(); - PersonConstructionData personConstructionData = personAdditionEvent.getPersonConstructionData(); - validatePersonExists(personId); - List resourceAssignments = personConstructionData.getValues(ResourceInitialization.class); - for (final ResourceInitialization resourceAssignment : resourceAssignments) { - ResourceId resourceId = resourceAssignment.getResourceId(); - Long amount = resourceAssignment.getAmount(); - validateResourceId(resourceId); - validateNonnegativeResourceAmount(amount); - } - - for (final ResourceInitialization resourceAssignment : resourceAssignments) { - incrementPersonResourceLevel(resourceAssignment.getResourceId(), personId, resourceAssignment.getAmount()); - } - } - - private void handleBulkPersonAdditionEvent(final DataManagerContext dataManagerContext, final BulkPersonAdditionEvent bulkPersonAdditionEvent) { - PersonId personId = bulkPersonAdditionEvent.getPersonId(); - BulkPersonConstructionData bulkPersonConstructionData = bulkPersonAdditionEvent.getBulkPersonConstructionData(); - List personConstructionDatas = bulkPersonConstructionData.getPersonConstructionDatas(); - for (PersonConstructionData personConstructionData : personConstructionDatas) { - List resourceAssignments = personConstructionData.getValues(ResourceInitialization.class); - for (final ResourceInitialization resourceAssignment : resourceAssignments) { - ResourceId resourceId = resourceAssignment.getResourceId(); - Long amount = resourceAssignment.getAmount(); - validateResourceId(resourceId); - validateNonnegativeResourceAmount(amount); - } - } - - int pId = personId.getValue(); - - for (PersonConstructionData personConstructionData : personConstructionDatas) { - List resourceAssignments = personConstructionData.getValues(ResourceInitialization.class); - PersonId boxedPersonId = peopleDataManager.getBoxedPersonId(pId).get(); - for (final ResourceInitialization resourceAssignment : resourceAssignments) { - incrementPersonResourceLevel(resourceAssignment.getResourceId(), boxedPersonId, resourceAssignment.getAmount()); - } - pId++; - } - - } - - private void handlePersonImminentRemovalEvent(final DataManagerContext dataManagerContext, final PersonImminentRemovalEvent personImminentRemovalEvent) { - validatePersonExists(personImminentRemovalEvent.getPersonId()); - - dataManagerContext.addPlan((context) -> { - PersonId personId = personImminentRemovalEvent.getPersonId(); - for (final IntValueContainer intValueContainer : personResourceValues.values()) { - intValueContainer.setLongValue(personId.getValue(), 0); - } - }, dataManagerContext.getTime()); - } -} diff --git a/gcm3/src/main/java/plugins/resources/events/PersonResourceUpdateEvent.java b/gcm3/src/main/java/plugins/resources/events/PersonResourceUpdateEvent.java deleted file mode 100644 index bede6d8f5..000000000 --- a/gcm3/src/main/java/plugins/resources/events/PersonResourceUpdateEvent.java +++ /dev/null @@ -1,245 +0,0 @@ -package plugins.resources.events; - -import net.jcip.annotations.Immutable; -import nucleus.Event; -import nucleus.EventLabel; -import nucleus.EventLabeler; -import nucleus.EventLabelerId; -import nucleus.SimulationContext; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import plugins.regions.datamanagers.RegionsDataManager; -import plugins.regions.support.RegionError; -import plugins.regions.support.RegionId; -import plugins.resources.datamanagers.ResourcesDataManager; -import plugins.resources.support.ResourceError; -import plugins.resources.support.ResourceId; -import util.errors.ContractException; - -/** - * An observation event indicating that a person's resource level has changed. - * - * @author Shawn Hatch - * - */ -@Immutable -public class PersonResourceUpdateEvent implements Event { - private final PersonId personId; - private final ResourceId resourceId; - private final long previousResourceLevel; - private final long currentResourceLevel; - - /** - * Constructs the event - */ - public PersonResourceUpdateEvent(final PersonId personId, final ResourceId resourceId, final long previousResourceLevel, final long currentResourceLevel) { - this.personId = personId; - this.resourceId = resourceId; - this.previousResourceLevel = previousResourceLevel; - this.currentResourceLevel = currentResourceLevel; - } - - /** - * Returns the resource id used to create this event - */ - public ResourceId getResourceId() { - return resourceId; - } - - /** - * Returns the current resource level used to create this event - */ - public long getCurrentResourceLevel() { - return currentResourceLevel; - } - - /** - * Returns the person id used to create this event - */ - public PersonId getPersonId() { - return personId; - } - - /** - * Returns the previous resource level used to create this event - */ - public long getPreviousResourceLevel() { - return previousResourceLevel; - } - - private static void validatePersonId(SimulationContext simulationContext, PersonId personId) { - if (personId == null) { - throw new ContractException(PersonError.NULL_PERSON_ID); - } - PeopleDataManager peopleDataManager = simulationContext.getDataManager(PeopleDataManager.class); - if (!peopleDataManager.personExists(personId)) { - throw new ContractException(PersonError.UNKNOWN_PERSON_ID, personId); - } - } - - private static void validateRegionId(SimulationContext simulationContext, RegionId regionId) { - if (regionId == null) { - throw new ContractException(RegionError.NULL_REGION_ID); - } - RegionsDataManager regionsDataManager = simulationContext.getDataManager(RegionsDataManager.class); - if (!regionsDataManager.regionIdExists(regionId)) { - throw new ContractException(RegionError.UNKNOWN_REGION_ID, regionId); - } - } - - private static void validateResourceId(SimulationContext simulationContext, ResourceId resourceId) { - if (resourceId == null) { - throw new ContractException(ResourceError.NULL_RESOURCE_ID); - } - ResourcesDataManager resourcesDataManager = simulationContext.getDataManager(ResourcesDataManager.class); - if (!resourcesDataManager.resourceIdExists(resourceId)) { - throw new ContractException(ResourceError.UNKNOWN_RESOURCE_ID, resourceId); - } - } - - private static enum LabelerId implements EventLabelerId { - REGION_RESOURCE, PERSON_RESOURCE, RESOURCE - } - - /** - * Returns an event label used to subscribe to - * {@link PersonResourceUpdateEvent} events. Matches on region id and - * resource id. - * - * - * Preconditions : The context cannot be null - * - * @throws ContractException - * - *
          • {@linkplain RegionError#NULL_REGION_ID} if the region id - * is null
          • - *
          • {@linkplain RegionError#UNKNOWN_REGION_ID} if the region - * id is unknown
          • - *
          • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
          • - *
          • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if the - * resource id is unknown
          • - */ - public static EventLabel getEventLabelByRegionAndResource(SimulationContext simulationContext, RegionId regionId, ResourceId resourceId) { - validateRegionId(simulationContext, regionId); - validateResourceId(simulationContext, resourceId); - return _getEventLabelByRegionAndResource(regionId, resourceId);// - } - - private static EventLabel _getEventLabelByRegionAndResource(RegionId regionId, ResourceId resourceId) { - - return EventLabel .builder(PersonResourceUpdateEvent.class)// - .setEventLabelerId(LabelerId.REGION_RESOURCE)// - .addKey(resourceId)// - .addKey(regionId)// - .build();// - } - - /** - * Returns an event labeler for {@link PersonResourceUpdateEvent} events - * that uses region id and resource id. Automatically added at - * initialization. - */ - public static EventLabeler getEventLabelerForRegionAndResource(RegionsDataManager regionsDataManager) { - return EventLabeler .builder(PersonResourceUpdateEvent.class)// - .setEventLabelerId(LabelerId.REGION_RESOURCE)// - .setLabelFunction((context, event) -> { - RegionId regionId = regionsDataManager.getPersonRegion(event.getPersonId()); - return _getEventLabelByRegionAndResource(regionId, event.getResourceId()); - }).build(); - } - - /** - * Returns an event label used to subscribe to - * {@link PersonResourceUpdateEvent} events. Matches on person id and - * resource id. - * - * - * Preconditions : The context cannot be null - * - * @throws ContractException - * - *
          • {@linkplain PersonError#NULL_PERSON_ID} if the person id - * is null
          • - *
          • {@linkplain PersonError#UNKNOWN_PERSON_ID} if the person - * id is unknown
          • - *
          • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
          • - *
          • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if the - * resource id is unknown
          • - */ - public static EventLabel getEventLabelByPersonAndResource(SimulationContext simulationContext, PersonId personId, ResourceId resourceId) { - validatePersonId(simulationContext, personId); - validateResourceId(simulationContext, resourceId); - return _getEventLabelByPersonAndResource(personId, resourceId);// - } - - private static EventLabel _getEventLabelByPersonAndResource(PersonId personId, ResourceId resourceId) { - - return EventLabel .builder(PersonResourceUpdateEvent.class)// - .setEventLabelerId(LabelerId.PERSON_RESOURCE)// - .addKey(resourceId)// - .addKey(personId)// - .build();// - } - - /** - * Returns an event labeler for {@link PersonResourceUpdateEvent} events - * that uses person id and resource id. Automatically added at - * initialization. - */ - public static EventLabeler getEventLabelerForPersonAndResource() { - return EventLabeler .builder(PersonResourceUpdateEvent.class)// - .setEventLabelerId(LabelerId.PERSON_RESOURCE)// - .setLabelFunction((context, event) -> _getEventLabelByPersonAndResource(event.getPersonId(), event.getResourceId()))// - .build(); - } - - /** - * Returns an event label used to subscribe to - * {@link PersonResourceUpdateEvent} events. Matches on resource id. - * - * - * Preconditions : The context cannot be null - * - * @throws ContractException - * - *
          • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
          • - *
          • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if the - * resource id is unknown
          • - */ - public static EventLabel getEventLabelByResource(SimulationContext simulationContext, ResourceId resourceId) { - validateResourceId(simulationContext, resourceId); - return _getEventLabelByResource(resourceId);// - } - - private static EventLabel _getEventLabelByResource(ResourceId resourceId) { - return EventLabel .builder(PersonResourceUpdateEvent.class)// - .setEventLabelerId(LabelerId.RESOURCE)// - .addKey(resourceId)// - .build();// - } - - /** - * Returns an event labeler for {@link PersonResourceUpdateEvent} events - * that uses resource id. Automatically added at initialization. - */ - - public static EventLabeler getEventLabelerForResource() { - return EventLabeler .builder(PersonResourceUpdateEvent.class)// - .setEventLabelerId(LabelerId.RESOURCE)// - .setLabelFunction((context, event) -> _getEventLabelByResource(event.getResourceId()))// - .build(); - } - - /** - * Returns the resource id used to create this event - */ - @Override - public Object getPrimaryKeyValue() { - return resourceId; - } - -} diff --git a/gcm3/src/main/java/plugins/resources/events/RegionResourceUpdateEvent.java b/gcm3/src/main/java/plugins/resources/events/RegionResourceUpdateEvent.java deleted file mode 100644 index 33f758143..000000000 --- a/gcm3/src/main/java/plugins/resources/events/RegionResourceUpdateEvent.java +++ /dev/null @@ -1,150 +0,0 @@ -package plugins.resources.events; - -import net.jcip.annotations.Immutable; -import nucleus.Event; -import nucleus.EventLabel; -import nucleus.EventLabeler; -import nucleus.EventLabelerId; -import nucleus.SimulationContext; -import plugins.regions.datamanagers.RegionsDataManager; -import plugins.regions.support.RegionError; -import plugins.regions.support.RegionId; -import plugins.resources.datamanagers.ResourcesDataManager; -import plugins.resources.support.ResourceError; -import plugins.resources.support.ResourceId; -import util.errors.ContractException; - -/** - * An observation event indicating that a region's resource level has changed. - * - * @author Shawn Hatch - * - */ - -@Immutable -public class RegionResourceUpdateEvent implements Event { - private final RegionId regionId; - private final ResourceId resourceId; - private final long previousResourceLevel; - private final long currentResourceLevel; - - /** - * Constructs the event - */ - public RegionResourceUpdateEvent(RegionId regionId, ResourceId resourceId, long previousResourceLevel, long currentResourceLevel) { - super(); - this.regionId = regionId; - this.resourceId = resourceId; - this.previousResourceLevel = previousResourceLevel; - this.currentResourceLevel = currentResourceLevel; - } - - /** - * Returns the resource id used to create this event - */ - public ResourceId getResourceId() { - return resourceId; - } - - /** - * Returns the region id used to create this event - */ - public RegionId getRegionId() { - return regionId; - } - - /** - * Returns the previous resource level used to create this event - */ - public long getPreviousResourceLevel() { - return previousResourceLevel; - } - - /** - * Returns the current resource level used to create this event - */ - public long getCurrentResourceLevel() { - return currentResourceLevel; - } - - private static enum LabelerId implements EventLabelerId { - REGION_RESOURCE - } - - private static void validateRegionId(SimulationContext simulationContext, RegionId regionId) { - if (regionId == null) { - throw new ContractException(RegionError.NULL_REGION_ID); - } - RegionsDataManager regionsDataManager = simulationContext.getDataManager(RegionsDataManager.class); - if (!regionsDataManager.regionIdExists(regionId)) { - throw new ContractException(RegionError.UNKNOWN_REGION_ID); - } - } - - private static void validateResourceId(SimulationContext simulationContext, ResourceId resourceId) { - if (resourceId == null) { - throw new ContractException(ResourceError.NULL_RESOURCE_ID); - } - ResourcesDataManager resourcesDataManager = simulationContext.getDataManager(ResourcesDataManager.class); - if (!resourcesDataManager.resourceIdExists(resourceId)) { - throw new ContractException(ResourceError.UNKNOWN_RESOURCE_ID); - } - } - - /** - * Returns an event label used to subscribe to - * {@link RegionResourceUpdateEvent} events. Matches on region id and - * resource id. - * - * - * Preconditions : The context cannot be null - * - * @throws ContractException - * - *
          • {@linkplain RegionError#NULL_REGION_ID} if the region id - * is null
          • - *
          • {@linkplain RegionError#UNKNOWN_REGION_ID} if the region - * id is unknown
          • - *
          • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
          • - *
          • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if the - * resource id is unknown
          • - */ - public static EventLabel getEventLabelByRegionAndResource(SimulationContext simulationContext, RegionId regionId, ResourceId resourceId) { - validateRegionId(simulationContext, regionId); - validateResourceId(simulationContext, resourceId); - return _getEventLabelByRegionAndResource(regionId, resourceId);// - } - - private static EventLabel _getEventLabelByRegionAndResource(RegionId regionId, ResourceId resourceId) { - - return EventLabel .builder(RegionResourceUpdateEvent.class)// - .setEventLabelerId(LabelerId.REGION_RESOURCE)// - .addKey(resourceId)// - .addKey(regionId)// - .build();// - } - - /** - * Returns an event labeler for {@link RegionResourceUpdateEvent} events - * that uses region id and resource id. Automatically added at - * initialization. - */ - public static EventLabeler getEventLabelerForRegionAndResource() { - return EventLabeler .builder(RegionResourceUpdateEvent.class)// - .setEventLabelerId(LabelerId.REGION_RESOURCE)// - .setLabelFunction((context, event) -> _getEventLabelByRegionAndResource(event.getRegionId(), event.getResourceId()))// - .build(); - } - - - - /** - * Returns the resource id used to create this event - */ - @Override - public Object getPrimaryKeyValue() { - return resourceId; - } - -} diff --git a/gcm3/src/main/java/plugins/resources/events/ResourcePropertyUpdateEvent.java b/gcm3/src/main/java/plugins/resources/events/ResourcePropertyUpdateEvent.java deleted file mode 100644 index c79db7aae..000000000 --- a/gcm3/src/main/java/plugins/resources/events/ResourcePropertyUpdateEvent.java +++ /dev/null @@ -1,144 +0,0 @@ -package plugins.resources.events; - -import net.jcip.annotations.Immutable; -import nucleus.Event; -import nucleus.EventLabel; -import nucleus.EventLabeler; -import nucleus.EventLabelerId; -import nucleus.SimulationContext; -import plugins.resources.datamanagers.ResourcesDataManager; -import plugins.resources.support.ResourceError; -import plugins.resources.support.ResourceId; -import plugins.resources.support.ResourcePropertyId; -import util.errors.ContractException; - -/** - * An observation event indicating that a resource property has changed. - * - * @author Shawn Hatch - * - */ -@Immutable -public class ResourcePropertyUpdateEvent implements Event { - private final ResourceId resourceId; - private final ResourcePropertyId resourcePropertyId; - private final Object previousPropertyValue; - private final Object currentPropertyValue; - - /** - * Constructs the event - */ - public ResourcePropertyUpdateEvent(ResourceId resourceId, ResourcePropertyId resourcePropertyId, Object previousPropertyValue, Object currentPropertyValue) { - super(); - this.resourceId = resourceId; - this.resourcePropertyId = resourcePropertyId; - this.previousPropertyValue = previousPropertyValue; - this.currentPropertyValue = currentPropertyValue; - } - - /** - * Returns the resource id used to create this event - */ - public ResourceId getResourceId() { - return resourceId; - } - - /** - * Returns the resource property id used to create this event - */ - public ResourcePropertyId getResourcePropertyId() { - return resourcePropertyId; - } - - /** - * Returns the previous property value used to create this event - */ - public Object getPreviousPropertyValue() { - return previousPropertyValue; - } - - /** - * Returns the current property value used to create this event - */ - public Object getCurrentPropertyValue() { - return currentPropertyValue; - } - - private static enum LabelerId implements EventLabelerId { - RESOURCE_AND_PROPERTY - } - - private static void validateResourceId(SimulationContext simulationContext, ResourceId resourceId) { - if (resourceId == null) { - throw new ContractException(ResourceError.NULL_RESOURCE_ID); - } - ResourcesDataManager resourcesDataManager = simulationContext.getDataManager(ResourcesDataManager.class); - if (!resourcesDataManager.resourceIdExists(resourceId)) { - throw new ContractException(ResourceError.UNKNOWN_RESOURCE_ID); - } - } - - private static void validateResourcePropertyId(SimulationContext simulationContext, ResourceId resourceId, ResourcePropertyId resourcePropertyId) { - if (resourcePropertyId == null) { - throw new ContractException(ResourceError.NULL_RESOURCE_PROPERTY_ID); - } - ResourcesDataManager resourcesDataManager = simulationContext.getDataManager(ResourcesDataManager.class); - if (!resourcesDataManager.resourcePropertyIdExists(resourceId, resourcePropertyId)) { - throw new ContractException(ResourceError.UNKNOWN_RESOURCE_PROPERTY_ID); - } - } - - /** - * Returns an event label used to subscribe to - * {@link ResourcePropertyUpdateEvent} events. Matches on resource id and - * resource property id. - * - * - * Preconditions : The context cannot be null - * - * @throws ContractException - *
          • {@linkplain ResourceError#NULL_RESOURCE_ID} if the - * resource id is null
          • - *
          • {@linkplain ResourceError#UNKNOWN_RESOURCE_ID} if the - * resource id is unknown
          • - *
          • {@linkplain ResourceError#NULL_RESOURCE_PROPERTY_ID} if - * the resource property id is null
          • - *
          • {@linkplain ResourceError#UNKNOWN_RESOURCE_PROPERTY_ID} - * if the resource property id is unknown
          • * - */ - public static EventLabel getEventLabel(SimulationContext simulationContext, ResourceId resourceId, ResourcePropertyId resourcePropertyId) { - validateResourceId(simulationContext, resourceId); - validateResourcePropertyId(simulationContext, resourceId, resourcePropertyId); - return _getEventLabel(resourceId, resourcePropertyId); - } - - private static EventLabel _getEventLabel(ResourceId resourceId, ResourcePropertyId resourcePropertyId) { - - return EventLabel .builder(ResourcePropertyUpdateEvent.class)// - .setEventLabelerId(LabelerId.RESOURCE_AND_PROPERTY)// - .addKey(resourcePropertyId)// - .addKey(resourceId)// - .build(); - } - - /** - * Returns an event labeler for {@link ResourcePropertyUpdateEvent} events - * that uses resource id and resource property id. Automatically added at - * initialization. - */ - public static EventLabeler getEventLabeler() { - return EventLabeler .builder(ResourcePropertyUpdateEvent.class)// - .setEventLabelerId(LabelerId.RESOURCE_AND_PROPERTY)// - .setLabelFunction((context, event) -> _getEventLabel(event.getResourceId(), event.getResourcePropertyId()))// - .build(); - } - - /** - * Returns the resource property id used to create this event - */ - @Override - public Object getPrimaryKeyValue() { - return resourcePropertyId; - } - -} diff --git a/gcm3/src/main/java/plugins/resources/support/ResourceError.java b/gcm3/src/main/java/plugins/resources/support/ResourceError.java deleted file mode 100644 index e7bc864c3..000000000 --- a/gcm3/src/main/java/plugins/resources/support/ResourceError.java +++ /dev/null @@ -1,49 +0,0 @@ -package plugins.resources.support; - -import util.errors.ContractError; -import util.errors.ContractException; - -/** - * An enumeration supporting {@link ContractException} that acts as a general - * description of the exception. - * - * @author Shawn Hatch - * - */ -public enum ResourceError implements ContractError { - - INSUFFICIENT_RESOURCES_AVAILABLE("Resource level is insufficient for transaction amount"), - DUPLICATE_RESOURCE_ID("Duplicate resource"), - DUPLICATE_RESOURCE_PROPERTY_DEFINITION("Duplicate resource property definition"), - DUPLICATE_PERSON_RESOURCE_LEVEL_ASSIGNMENT("Duplicate person resource level assignment"), - DUPLICATE_TIME_TRACKING_POLICY_ASSIGNMENT("Duplicate time tracking policy assignment"), - DUPLICATE_REGION_RESOURCE_LEVEL_ASSIGNMENT("Duplicate region resource level assignment"), - DUPLICATE_RESOURCE_PROPERTY_VALUE_ASSIGNMENT("Duplicate resource property value assignment"), - NEGATIVE_RESOURCE_AMOUNT("Resource amount is negative"), - NULL_RESOURCE_ID("Null resource id"), - NULL_RESOURCE_DATA_MANAGER("Null resource data manager"), - NULL_RESOURCE_PLUGIN_DATA("Null resource plugin data"), - NULL_TIME_TRACKING_POLICY("Null time tracking policy"), - NULL_RESOURCE_PROPERTY_ID("Null resource property id"), - NULL_RESOURCE_PROPERTY_VALUE("Null resource property value"), - NULL_RESOURCE_PROPERTY_DEFINITION("Null resource property definition"), - REFLEXIVE_RESOURCE_TRANSFER("Cannot transfer resources from a region to itself"), - RESOURCE_ARITHMETIC_EXCEPTION("Resource arithmetic resulting in underflow/overflow"), - RESOURCE_ASSIGNMENT_TIME_NOT_TRACKED("Resource assignment time not actively tracked"), - UNKNOWN_RESOURCE_ID("Unknown resource id"), - UNKNOWN_RESOURCE_PROPERTY_ID("Unknown resource property id"), - INCOMPATIBLE_VALUE("Property value is incompatible with the resource property definition"), - INSUFFICIENT_RESOURCE_PROPERTY_VALUE_ASSIGNMENT("A resource property definition default value is null and not replaced with sufficient property value assignments"); - ; - - private final String description; - - private ResourceError(final String description) { - this.description = description; - } - - @Override - public String getDescription() { - return description; - } -} diff --git a/gcm3/src/main/java/plugins/resources/support/ResourceFilter.java b/gcm3/src/main/java/plugins/resources/support/ResourceFilter.java deleted file mode 100644 index 1536a05b2..000000000 --- a/gcm3/src/main/java/plugins/resources/support/ResourceFilter.java +++ /dev/null @@ -1,92 +0,0 @@ -package plugins.resources.support; - -import java.util.LinkedHashSet; -import java.util.Optional; -import java.util.Set; - -import nucleus.NucleusError; -import nucleus.SimulationContext; -import plugins.partitions.support.Equality; -import plugins.partitions.support.Filter; -import plugins.partitions.support.FilterSensitivity; -import plugins.partitions.support.PartitionError; -import plugins.people.support.PersonId; -import plugins.resources.datamanagers.ResourcesDataManager; -import plugins.resources.events.PersonResourceUpdateEvent; -import util.errors.ContractException; - -public final class ResourceFilter extends Filter { - private final ResourceId resourceId; - private final long resourceValue; - private final Equality equality; - private ResourcesDataManager resourcesDataManager; - - private void validateResourceId(SimulationContext simulationContext, final ResourceId resourceId) { - if (resourceId == null) { - throw new ContractException(ResourceError.NULL_RESOURCE_ID); - } - - if (simulationContext == null) { - throw new ContractException(NucleusError.NULL_SIMULATION_CONTEXT); - } - - if (resourcesDataManager == null) { - resourcesDataManager = simulationContext.getDataManager(ResourcesDataManager.class); - } - - if (!resourcesDataManager.resourceIdExists(resourceId)) { - throw new ContractException(ResourceError.UNKNOWN_RESOURCE_ID, resourceId); - } - } - - private void validateEquality(SimulationContext simulationContext, final Equality equality) { - if (equality == null) { - throw new ContractException(PartitionError.NULL_EQUALITY_OPERATOR); - } - } - - public ResourceFilter(final ResourceId resourceId, final Equality equality, final long resourceValue) { - this.resourceId = resourceId; - this.resourceValue = resourceValue; - this.equality = equality; - } - - @Override - public void validate(SimulationContext simulationContext) { - validateEquality(simulationContext, equality); - validateResourceId(simulationContext, resourceId); - } - - @Override - public boolean evaluate(SimulationContext simulationContext, PersonId personId) { - if (simulationContext == null) { - throw new ContractException(NucleusError.NULL_SIMULATION_CONTEXT); - } - - if (resourcesDataManager == null) { - resourcesDataManager = simulationContext.getDataManager(ResourcesDataManager.class); - } - - final long level = resourcesDataManager.getPersonResourceLevel(resourceId, personId); - return equality.isCompatibleComparisonValue(Long.compare(level, resourceValue)); - } - - private Optional requiresRefresh(SimulationContext simulationContext, PersonResourceUpdateEvent event) { - if (event.getResourceId().equals(resourceId)) { - long previousResourceLevel = event.getPreviousResourceLevel(); - long currentResourceLevel = event.getCurrentResourceLevel(); - if (equality.isCompatibleComparisonValue(Long.compare(previousResourceLevel, resourceValue)) != equality.isCompatibleComparisonValue(Long.compare(currentResourceLevel, resourceValue))) { - return Optional.of(event.getPersonId()); - } - } - return Optional.empty(); - } - - @Override - public Set> getFilterSensitivities() { - Set> result = new LinkedHashSet<>(); - result.add(new FilterSensitivity(PersonResourceUpdateEvent.class, this::requiresRefresh)); - return result; - } - -} diff --git a/gcm3/src/main/java/plugins/resources/support/ResourceId.java b/gcm3/src/main/java/plugins/resources/support/ResourceId.java deleted file mode 100644 index e5a55c6c2..000000000 --- a/gcm3/src/main/java/plugins/resources/support/ResourceId.java +++ /dev/null @@ -1,14 +0,0 @@ -package plugins.resources.support; - -import net.jcip.annotations.ThreadSafe; - -/** - * Marker interface for resource identifiers - * - * @author Shawn Hatch - * - */ -@ThreadSafe -public interface ResourceId { - -} diff --git a/gcm3/src/main/java/plugins/resources/support/ResourceInitialization.java b/gcm3/src/main/java/plugins/resources/support/ResourceInitialization.java deleted file mode 100644 index 957aaca49..000000000 --- a/gcm3/src/main/java/plugins/resources/support/ResourceInitialization.java +++ /dev/null @@ -1,32 +0,0 @@ -package plugins.resources.support; - -public class ResourceInitialization { - private final ResourceId resourceId; - private final Long amount; - - public ResourceInitialization(ResourceId resourceId, Long amount) { - super(); - this.resourceId = resourceId; - this.amount = amount; - } - - public ResourceId getResourceId() { - return resourceId; - } - - public Long getAmount() { - return amount; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("ResourceAssignment [resourceId="); - builder.append(resourceId); - builder.append(", amount="); - builder.append(amount); - builder.append("]"); - return builder.toString(); - } - -} diff --git a/gcm3/src/main/java/plugins/resources/support/ResourceLabeler.java b/gcm3/src/main/java/plugins/resources/support/ResourceLabeler.java deleted file mode 100644 index a964f3833..000000000 --- a/gcm3/src/main/java/plugins/resources/support/ResourceLabeler.java +++ /dev/null @@ -1,79 +0,0 @@ -package plugins.resources.support; - -import java.util.LinkedHashSet; -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; - -import nucleus.Event; -import nucleus.NucleusError; -import nucleus.SimulationContext; -import plugins.partitions.support.Labeler; -import plugins.partitions.support.LabelerSensitivity; -import plugins.people.support.PersonId; -import plugins.resources.datamanagers.ResourcesDataManager; -import plugins.resources.events.PersonResourceUpdateEvent; -import util.errors.ContractException; - -/** - * A a labeler for resources. The dimension of the labeler is the given - * {@linkplain ResourceId}, the event that stimulates a label update is - * {@linkplain PersonResourceUpdateEvent} and the labeling function - * is composed from the given Function. - * - * @author Shawn Hatch - * - */ -public final class ResourceLabeler implements Labeler { - - private final ResourceId resourceId; - - private final Function resourceLabelingFunction; - - private ResourcesDataManager resourcesDataManager; - - public ResourceLabeler(ResourceId resourceId, Function resourceLabelingFunction) { - this.resourceId = resourceId; - this.resourceLabelingFunction = resourceLabelingFunction; - } - - private Optional getPersonId(PersonResourceUpdateEvent personResourceUpdateEvent) { - PersonId result = null; - if (personResourceUpdateEvent.getResourceId().equals(resourceId)) { - result = personResourceUpdateEvent.getPersonId(); - } - return Optional.ofNullable(result); - } - - @Override - public Set> getLabelerSensitivities() { - Set> result = new LinkedHashSet<>(); - result.add(new LabelerSensitivity(PersonResourceUpdateEvent.class, this::getPersonId)); - return result; - } - - @Override - public Object getLabel(SimulationContext simulationContext, PersonId personId) { - if (simulationContext == null) { - throw new ContractException(NucleusError.NULL_SIMULATION_CONTEXT); - } - - if (resourcesDataManager == null) { - resourcesDataManager = simulationContext.getDataManager(ResourcesDataManager.class); - } - long personResourceLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); - return resourceLabelingFunction.apply(personResourceLevel); - } - - @Override - public Object getDimension() { - return resourceId; - } - - @Override - public Object getPastLabel(SimulationContext simulationContext, Event event) { - PersonResourceUpdateEvent personResourceUpdateEvent = (PersonResourceUpdateEvent)event; - return resourceLabelingFunction.apply(personResourceUpdateEvent.getPreviousResourceLevel()); - } - -} diff --git a/gcm3/src/main/java/plugins/resources/support/ResourcePropertyId.java b/gcm3/src/main/java/plugins/resources/support/ResourcePropertyId.java deleted file mode 100644 index e0f2c1e82..000000000 --- a/gcm3/src/main/java/plugins/resources/support/ResourcePropertyId.java +++ /dev/null @@ -1,14 +0,0 @@ -package plugins.resources.support; - -import net.jcip.annotations.ThreadSafe; - -/** - * Marker interface for resource property identifiers - * - * @author Shawn Hatch - * - */ -@ThreadSafe -public interface ResourcePropertyId { - -} diff --git a/gcm3/src/main/java/plugins/resources/testsupport/ResourcesActionSupport.java b/gcm3/src/main/java/plugins/resources/testsupport/ResourcesActionSupport.java deleted file mode 100644 index 94f98307e..000000000 --- a/gcm3/src/main/java/plugins/resources/testsupport/ResourcesActionSupport.java +++ /dev/null @@ -1,151 +0,0 @@ -package plugins.resources.testsupport; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -import org.apache.commons.math3.random.RandomGenerator; - -import nucleus.ActorContext; -import nucleus.Plugin; -import nucleus.Simulation; -import nucleus.Simulation.Builder; -import nucleus.testsupport.testplugin.ScenarioPlanCompletionObserver; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestError; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import plugins.people.PeoplePlugin; -import plugins.people.PeoplePluginData; -import plugins.people.support.PersonId; -import plugins.regions.RegionsPlugin; -import plugins.regions.RegionsPluginData; -import plugins.regions.testsupport.TestRegionId; -import plugins.resources.ResourcesPlugin; -import plugins.resources.ResourcesPluginData; -import plugins.stochastics.StochasticsPlugin; -import plugins.stochastics.StochasticsPluginData; -import plugins.util.properties.PropertyDefinition; -import util.errors.ContractException; -import util.random.RandomGeneratorProvider; - -/** - * A static test support class for the resources plugin. Provides convenience - * methods for integrating an action plugin into a resource-based simulation - * test harness. - * - * - * @author Shawn Hatch - * - */ -public class ResourcesActionSupport { - - /** - * Creates an action plugin with an agent that will execute the given - * consumer at time 0. The action plugin and the remaining arguments are - * passed to an invocation of the testConsumers() method. - */ - public static void testConsumer(int initialPopulation, long seed, Consumer consumer) { - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, consumer)); - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - testConsumers(initialPopulation, seed, testPlugin); - } - - /** - * Executes a simulation instance that supports resources plugin testing. - * - * The initial population is added in the initial data. - * - * Resources and their property definitions and initial values are added. No - * resources are allocated to regions or people. - * - * The seed is used to produce randomized initial group types and group - * memberships. - * - * The action plugin is integrated into the simulation run and must contain - * at least one action plan. This helps to ensure that a test that does not - * run completely does not lead to a false positive test evaluation. - * - * @throws ContractException - *
          • {@linkplain ActionError#ACTION_EXECUTION_FAILURE} if not - * all action plans execute or if there are no action plans - * contained in the action plugin
          • - */ - public static void testConsumers(int initialPopulation, long seed, Plugin testPlugin) { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); - - // create a list of people - List people = new ArrayList<>(); - for (int i = 0; i < initialPopulation; i++) { - people.add(new PersonId(i)); - } - - Builder builder = Simulation.builder(); - - // add the resources plugin - ResourcesPluginData.Builder resourcesBuilder = ResourcesPluginData.builder(); - - - for (TestResourceId testResourceId : TestResourceId.values()) { - resourcesBuilder.addResource(testResourceId); - resourcesBuilder.setResourceTimeTracking(testResourceId, testResourceId.getTimeTrackingPolicy()); - } - - for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId.values()) { - TestResourceId testResourceId = testResourcePropertyId.getTestResourceId(); - PropertyDefinition propertyDefinition = testResourcePropertyId.getPropertyDefinition(); - Object propertyValue = testResourcePropertyId.getRandomPropertyValue(randomGenerator); - resourcesBuilder.defineResourceProperty(testResourceId, testResourcePropertyId, propertyDefinition); - resourcesBuilder.setResourcePropertyValue(testResourceId, testResourcePropertyId, propertyValue); - } - - ResourcesPluginData resourcesPluginData = resourcesBuilder.build(); - Plugin resourcesPlugin = ResourcesPlugin.getResourcesPlugin(resourcesPluginData); - builder.addPlugin(resourcesPlugin); - - // add the people plugin - - PeoplePluginData.Builder peopleBuilder = PeoplePluginData.builder(); - for (PersonId personId : people) { - peopleBuilder.addPersonId(personId); - } - PeoplePluginData peoplePluginData = peopleBuilder.build(); - Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(peoplePluginData); - builder.addPlugin(peoplePlugin); - - // add the regions plugin - RegionsPluginData.Builder regionsBuilder = RegionsPluginData.builder(); - for (TestRegionId testRegionId : TestRegionId.values()) { - regionsBuilder.addRegion(testRegionId); - } - for (PersonId personId : people) { - regionsBuilder.setPersonRegion(personId, TestRegionId.getRandomRegionId(randomGenerator)); - } - - RegionsPluginData regionsPluginData = regionsBuilder.build(); - Plugin regionPlugin = RegionsPlugin.getRegionsPlugin(regionsPluginData); - - builder.addPlugin(regionPlugin); - - // add the stochastics plugin - StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder().setSeed(randomGenerator.nextLong()).build(); - Plugin stochasticsPlugin = StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData); - builder.addPlugin(stochasticsPlugin); - - // add the action plugin - builder.addPlugin(testPlugin); - - // build and execute the engine - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - builder.setOutputConsumer(scenarioPlanCompletionObserver::handleOutput).build().execute(); - - // show that all actions were executed - if (!scenarioPlanCompletionObserver.allPlansExecuted()) { - throw new ContractException(TestError.TEST_EXECUTION_FAILURE); - } - } - -} diff --git a/gcm3/src/main/java/plugins/resources/testsupport/TestResourceId.java b/gcm3/src/main/java/plugins/resources/testsupport/TestResourceId.java deleted file mode 100644 index fbca9c4f9..000000000 --- a/gcm3/src/main/java/plugins/resources/testsupport/TestResourceId.java +++ /dev/null @@ -1,55 +0,0 @@ -package plugins.resources.testsupport; - -import org.apache.commons.math3.random.RandomGenerator; - -import plugins.resources.support.ResourceId; -import plugins.util.properties.TimeTrackingPolicy; - -/** - * Enumeration that identifies resources for all tests - */ -public enum TestResourceId implements ResourceId { - RESOURCE_1(TimeTrackingPolicy.TRACK_TIME), - RESOURCE_2(TimeTrackingPolicy.DO_NOT_TRACK_TIME), - RESOURCE_3(TimeTrackingPolicy.TRACK_TIME), - RESOURCE_4(TimeTrackingPolicy.DO_NOT_TRACK_TIME), - RESOURCE_5(TimeTrackingPolicy.TRACK_TIME); - - - private final TimeTrackingPolicy timeTrackingPolicy; - - private TestResourceId(TimeTrackingPolicy timeTrackingPolicy) { - this.timeTrackingPolicy = timeTrackingPolicy; - } - - public TimeTrackingPolicy getTimeTrackingPolicy() { - return timeTrackingPolicy; - } - - public static TestResourceId getRandomResourceId(final RandomGenerator randomGenerator) { - return TestResourceId.values()[randomGenerator.nextInt(TestResourceId.values().length)]; - } - - /** - * Returns a new {@link ResourceId} instance. - */ - public static ResourceId getUnknownResourceId() { - return new ResourceId() { - }; - } - - public static int size() { - return values().length; - } - - private TestResourceId next; - - public TestResourceId next() { - if (next == null) { - next = TestResourceId.values()[(ordinal() + 1) % TestResourceId.values().length]; - } - return next; - } - - -} \ No newline at end of file diff --git a/gcm3/src/main/java/plugins/resources/testsupport/TestResourcePropertyId.java b/gcm3/src/main/java/plugins/resources/testsupport/TestResourcePropertyId.java deleted file mode 100644 index 38325bee8..000000000 --- a/gcm3/src/main/java/plugins/resources/testsupport/TestResourcePropertyId.java +++ /dev/null @@ -1,96 +0,0 @@ -package plugins.resources.testsupport; - -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -import org.apache.commons.math3.random.RandomGenerator; - -import plugins.resources.support.ResourcePropertyId; -import plugins.util.properties.PropertyDefinition; - -/** - * Enumeration that identifies resources for all tests - */ -public enum TestResourcePropertyId implements ResourcePropertyId { - - ResourceProperty_1_1_BOOLEAN_MUTABLE(TestResourceId.RESOURCE_1, PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(false).build()), - ResourceProperty_1_2_INTEGER_MUTABLE(TestResourceId.RESOURCE_1, PropertyDefinition.builder().setType(Integer.class).setDefaultValue(0).build()), - ResourceProperty_1_3_DOUBLE_MUTABLE(TestResourceId.RESOURCE_1, PropertyDefinition.builder().setType(Double.class).setDefaultValue(0.0).build()), - ResourceProperty_2_1_BOOLEAN_MUTABLE(TestResourceId.RESOURCE_2, PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(true).build()), - ResourceProperty_2_2_INTEGER_MUTABLE(TestResourceId.RESOURCE_2, PropertyDefinition.builder().setType(Integer.class).setDefaultValue(5).build()), - ResourceProperty_3_1_BOOLEAN_MUTABLE(TestResourceId.RESOURCE_3, PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(false).build()), - ResourceProperty_3_2_STRING_MUTABLE(TestResourceId.RESOURCE_3, PropertyDefinition.builder().setType(String.class).setDefaultValue("").build()), - ResourceProperty_4_1_BOOLEAN_MUTABLE(TestResourceId.RESOURCE_4, PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(true).build()), - ResourceProperty_5_1_INTEGER_IMMUTABLE(TestResourceId.RESOURCE_5, PropertyDefinition.builder().setType(Integer.class).setDefaultValue(7).setPropertyValueMutability(false).build()), - ResourceProperty_5_1_DOUBLE_IMMUTABLE(TestResourceId.RESOURCE_5, PropertyDefinition.builder().setType(Double.class).setDefaultValue(2.7).setPropertyValueMutability(false).build()); - - private final TestResourceId testResourceId; - private final PropertyDefinition propertyDefinition; - - public PropertyDefinition getPropertyDefinition() { - return propertyDefinition; - } - - private TestResourcePropertyId(TestResourceId testResourceId, PropertyDefinition propertyDefinition) { - this.testResourceId = testResourceId; - this.propertyDefinition = propertyDefinition; - } - - public TestResourceId getTestResourceId() { - return testResourceId; - } - - /** - * Returns a new {@link ResourcePropertyId} instance. - */ - public static ResourcePropertyId getUnknownResourcePropertyId() { - return new ResourcePropertyId() { - }; - } - - public static Set getTestResourcePropertyIds(TestResourceId testResourceId) { - Set result = new LinkedHashSet<>(); - for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId.values()) { - if (testResourcePropertyId.testResourceId == testResourceId) { - result.add(testResourcePropertyId); - } - } - return result; - } - - public static TestResourcePropertyId getRandomResourcePropertyId(TestResourceId testResourceId,final RandomGenerator randomGenerator) { - List list = new ArrayList<>(getTestResourcePropertyIds(testResourceId)); - return list.get(randomGenerator.nextInt(list.size())); - } - - public Object getRandomPropertyValue(final RandomGenerator randomGenerator) { - switch (this) { - case ResourceProperty_1_1_BOOLEAN_MUTABLE: - return randomGenerator.nextBoolean(); - case ResourceProperty_1_2_INTEGER_MUTABLE: - return randomGenerator.nextInt(); - case ResourceProperty_1_3_DOUBLE_MUTABLE: - return randomGenerator.nextDouble(); - case ResourceProperty_2_1_BOOLEAN_MUTABLE: - return randomGenerator.nextBoolean(); - case ResourceProperty_2_2_INTEGER_MUTABLE: - return randomGenerator.nextInt(); - case ResourceProperty_3_1_BOOLEAN_MUTABLE: - return randomGenerator.nextBoolean(); - case ResourceProperty_3_2_STRING_MUTABLE: - return Integer.toString(randomGenerator.nextInt()); - case ResourceProperty_4_1_BOOLEAN_MUTABLE: - return randomGenerator.nextBoolean(); - case ResourceProperty_5_1_DOUBLE_IMMUTABLE: - return randomGenerator.nextDouble(); - case ResourceProperty_5_1_INTEGER_IMMUTABLE: - return randomGenerator.nextInt(); - default: - throw new RuntimeException("unhandled case: " + this); - - } - } - -} \ No newline at end of file diff --git a/gcm3/src/main/java/plugins/stochastics/StochasticsDataManager.java b/gcm3/src/main/java/plugins/stochastics/StochasticsDataManager.java deleted file mode 100644 index ac76c1650..000000000 --- a/gcm3/src/main/java/plugins/stochastics/StochasticsDataManager.java +++ /dev/null @@ -1,137 +0,0 @@ -package plugins.stochastics; - -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -import org.apache.commons.math3.random.RandomGenerator; - -import nucleus.DataManager; -import plugins.stochastics.support.RandomNumberGeneratorId; -import plugins.stochastics.support.StochasticsError; -import util.errors.ContractException; -import util.random.RandomGeneratorProvider; - -/** - * A mutable data manager for random number generators. - * - * @author Shawn Hatch - */ -public final class StochasticsDataManager extends DataManager { - - private Map randomGeneratorMap = new LinkedHashMap<>(); - - private RandomGenerator randomGenerator; - - /** - * Returns the general, non-identified, random number generator was - * initialized with the current base seed value that was initialized from - * the {@linkplain StochasticsPluginData} or reset via - * {@linkplain StochasticsDataManager#resetSeeds(long)} - */ - public RandomGenerator getRandomGenerator() { - return randomGenerator; - } - - /** - * Returns the random generator associated with the given id. If the random - * generator does not exist, a new one is created and seeded using the - * current base seed and the id. - * - * RNG seed = seed + id.toString().hashcode() - * - * @throws ContractException - *
          • {@linkplain StochasticsError#NULL_RANDOM_NUMBER_GENERATOR_ID} - * if the random number generator is null
          • - *
          • {@linkplain StochasticsError#UNKNOWN_RANDOM_NUMBER_GENERATOR_ID} - * if the random number generator is unknown
          • - */ - public RandomGenerator getRandomGeneratorFromId(RandomNumberGeneratorId randomNumberGeneratorId) { - validateRandomNumberGeneratorId(randomNumberGeneratorId); - RandomGenerator result = randomGeneratorMap.get(randomNumberGeneratorId); - if (result == null) { - result = addRandomGenerator(randomNumberGeneratorId); - } - return result; - } - - /** - * Returns the random number generator ids that were contained in the - * initial data of the {@linkplain StochasticsPluginData} or that have been - * added via - * {@linkplain StochasticsDataManager#getRandomGeneratorFromId(RandomNumberGeneratorId)}. - */ - @SuppressWarnings("unchecked") - public Set getRandomNumberGeneratorIds() { - - Set result = new LinkedHashSet<>(randomGeneratorMap.size()); - for (RandomNumberGeneratorId randomNumberGeneratorId : randomGeneratorMap.keySet()) { - result.add((T) randomNumberGeneratorId); - } - return result; - - } - - private void validateRandomNumberGeneratorId(RandomNumberGeneratorId randomNumberGeneratorId) { - if (randomNumberGeneratorId == null) { - throw new ContractException(StochasticsError.NULL_RANDOM_NUMBER_GENERATOR_ID); - } - } - - private long seed; - - /** - * Creates the StochasticsDataManager from the given - * {@linkplain StochasticsPluginData} Random generators associated with - * predefined ids in the StochasticsPluginData are generated in the same - * manner as the method - * {@linkplain StochasticsDataManager#getRandomGeneratorFromId(RandomNumberGeneratorId)} - * - * - */ - public StochasticsDataManager(StochasticsPluginData stochasticsPluginData) { - - // create RandomGenerators for each of the ids using a hash built from - // the id and the replication seed - Set randomNumberGeneratorIds = stochasticsPluginData.getRandomNumberGeneratorIds(); - seed = stochasticsPluginData.getSeed(); - for (RandomNumberGeneratorId randomNumberGeneratorId : randomNumberGeneratorIds) { - addRandomGenerator(randomNumberGeneratorId); - } - // finally, set up the standard RandomGenerator - randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); - - } - - /* - * The random generator should not already exist - */ - private RandomGenerator addRandomGenerator(RandomNumberGeneratorId randomNumberGeneratorId) { - String name = randomNumberGeneratorId.toString(); - long seedForId = name.hashCode() + seed; - RandomGenerator randomGeneratorForID = RandomGeneratorProvider.getRandomGenerator(seedForId); - this.randomGeneratorMap.put(randomNumberGeneratorId, randomGeneratorForID); - return randomGeneratorForID; - } - - /** - * Resets the seeds for all managed random number generators from the given - * seed. - */ - public void resetSeeds(long seed) { - this.seed = seed; - - // reset the default random number generator - randomGenerator.setSeed(seed); - - // reset the id based random number generators - for (RandomNumberGeneratorId randomNumberGeneratorId : randomGeneratorMap.keySet()) { - String name = randomNumberGeneratorId.toString(); - long seedForId = name.hashCode() + seed; - RandomGenerator rng = randomGeneratorMap.get(randomNumberGeneratorId); - rng.setSeed(seedForId); - } - - } -} diff --git a/gcm3/src/main/java/plugins/stochastics/StochasticsPlugin.java b/gcm3/src/main/java/plugins/stochastics/StochasticsPlugin.java deleted file mode 100644 index 860f82379..000000000 --- a/gcm3/src/main/java/plugins/stochastics/StochasticsPlugin.java +++ /dev/null @@ -1,85 +0,0 @@ -package plugins.stochastics; - -import net.jcip.annotations.ThreadSafe; -import nucleus.Plugin; -import util.errors.ContractError; - -/** - * - *

            - * Summary A nucleus plugin for managing random number generators. The - * plugin provides a general random generator as well as a set of random - * generators mapped to a set of identifiers. All random generators are - * implemented by org.apache.commons.math3.random.Well44497b - *

            - * - *

            - * Plugin Datas - *

              - *
            • Stochastics Plugin Data: Provides initial state for the data manager
            • - *
            - *

            - * - *

            - * Events The plugin supports no events. - **

            - * - *

            - * Data Managers - *

              - *
            • StochasticsDataManger: Manages the random generators and provides - * various related capabilities. - *
            - *

            - * - * - * - *

            - * Reports The plugin defines no reports - *

            - * - *

            - * Actors: No actors provided. - *

            - * - * - * - *

            - * Support classes - *

              - *
            • StochasticsError:
            • Enumeration implementing - * {@linkplain ContractError} for this plugin. - *
            • RandomNumberGeneratorId:
            • Marker interface for generator id - * values - *
            - *

            - * - *

            - * Required Plugins This plugin has no plugin dependencies - *

            - * - * @author Shawn Hatch - * - */ -@ThreadSafe -public final class StochasticsPlugin { - - private StochasticsPlugin() { - - } - - /** - * Returns a plugin that will add a StochasticsDataManager to the simulation - * at initialization - */ - public static Plugin getStochasticsPlugin(StochasticsPluginData stochasticsPluginData) { - - return Plugin .builder()// - .addPluginData(stochasticsPluginData)// - .setPluginId(StochasticsPluginId.PLUGIN_ID)// - .setInitializer((c) -> { - StochasticsPluginData pluginData = c.getPluginData(StochasticsPluginData.class); - c.addDataManager(new StochasticsDataManager(pluginData)); - }).build();// - } -} diff --git a/gcm3/src/main/java/plugins/stochastics/StochasticsPluginData.java b/gcm3/src/main/java/plugins/stochastics/StochasticsPluginData.java deleted file mode 100644 index d6f75c900..000000000 --- a/gcm3/src/main/java/plugins/stochastics/StochasticsPluginData.java +++ /dev/null @@ -1,156 +0,0 @@ -package plugins.stochastics; - -import java.util.LinkedHashSet; -import java.util.Set; - -import net.jcip.annotations.ThreadSafe; -import nucleus.PluginData; -import nucleus.PluginDataBuilder; -import plugins.stochastics.support.RandomNumberGeneratorId; -import plugins.stochastics.support.StochasticsError; -import util.errors.ContractException; - -/** - * A thread-safe container for the initial state of random generators. - * - * @author Shawn Hatch - * - */ -@ThreadSafe -public final class StochasticsPluginData implements PluginData { - - /** - * Constructs this plugin from the initial data and seed - */ - private StochasticsPluginData(Data data) { - this.data = data; - } - - @Override - public PluginDataBuilder getCloneBuilder() { - return new Builder(new Data(data)); - } - - /* - * State container class for collecting random number generator ids. - */ - private static class Data { - public Data() { - } - - public Data(Data data) { - this.seed = data.seed; - randomNumberGeneratorIds.addAll(data.randomNumberGeneratorIds); - } - - private Long seed; - private Set randomNumberGeneratorIds = new LinkedHashSet<>(); - } - - /** - * Returns a new builder instance - */ - public static Builder builder() { - return new Builder(new Data()); - } - - /** - * Builder class for StochasticsPluginData - * - * @author Shawn Hatch - * - */ - public static class Builder implements PluginDataBuilder { - private Data data; - - private Builder(Data data) { - this.data = data; - - } - - private void validate() { - if (data.seed == null) { - throw new ContractException(StochasticsError.NULL_SEED); - } - } - - /** - * Returns the StochasticsInitialData formed from the collected - * RandomNumberGeneratorId values. Clears the builder's state. - * - * @throws ContractException - *
          • {@linkplain StochasticsError#NULL_SEED} if the seed - * was not set
          • - * - */ - public StochasticsPluginData build() { - try { - validate(); - return new StochasticsPluginData(data); - } finally { - data = new Data(); - } - } - - /** - * Adds the given RandomNumberGeneratorId to this builder. - * - * @throws ContractException - *
          • {@linkplain StochasticsError#NULL_RANDOM_NUMBER_GENERATOR_ID} - * if the id is null
          • - *
          • {@linkplain StochasticsError#DUPLICATE_RANDOM_NUMBER_GENERATOR_ID} - * if the id was previously added
          • - */ - public Builder addRandomGeneratorId(RandomNumberGeneratorId randomNumberGeneratorId) { - validateRandomNumberGeneratorIdNotNull(randomNumberGeneratorId); - validateRandomNumberGeneratorIdDoesNotExist(data, randomNumberGeneratorId); - data.randomNumberGeneratorIds.add(randomNumberGeneratorId); - return this; - } - - /** - * Sets the seed value. - */ - public Builder setSeed(long seed) { - data.seed = seed; - return this; - } - - } - - private static void validateRandomNumberGeneratorIdNotNull(final Object value) { - if (value == null) { - throw new ContractException(StochasticsError.NULL_RANDOM_NUMBER_GENERATOR_ID); - } - } - - private static void validateRandomNumberGeneratorIdDoesNotExist(final Data data, final RandomNumberGeneratorId randomNumberGeneratorId) { - - if (data.randomNumberGeneratorIds.contains(randomNumberGeneratorId)) { - throw new ContractException(StochasticsError.DUPLICATE_RANDOM_NUMBER_GENERATOR_ID, randomNumberGeneratorId); - } - } - - private final Data data; - - /** - * Returns the RandomNumberGeneratorId values contained in this container - * - */ - @SuppressWarnings("unchecked") - public Set getRandomNumberGeneratorIds() { - Set result = new LinkedHashSet<>(); - for (RandomNumberGeneratorId randomNumberGeneratorId : data.randomNumberGeneratorIds) { - result.add((T) randomNumberGeneratorId); - } - return result; - } - - /** - * Returns the base seed. - */ - public long getSeed() { - return data.seed; - } - -} diff --git a/gcm3/src/main/java/plugins/stochastics/StochasticsPluginId.java b/gcm3/src/main/java/plugins/stochastics/StochasticsPluginId.java deleted file mode 100644 index 671523703..000000000 --- a/gcm3/src/main/java/plugins/stochastics/StochasticsPluginId.java +++ /dev/null @@ -1,13 +0,0 @@ -package plugins.stochastics; - -import nucleus.PluginId; -/** - * Static plugin id implementation for the StochasticsPlugin - * - * @author Shawn Hatch - * - */ -public final class StochasticsPluginId implements PluginId { - public final static PluginId PLUGIN_ID = new StochasticsPluginId(); - private StochasticsPluginId() {}; -} diff --git a/gcm3/src/main/java/plugins/stochastics/support/RandomNumberGeneratorId.java b/gcm3/src/main/java/plugins/stochastics/support/RandomNumberGeneratorId.java deleted file mode 100644 index 9bfbdddc9..000000000 --- a/gcm3/src/main/java/plugins/stochastics/support/RandomNumberGeneratorId.java +++ /dev/null @@ -1,14 +0,0 @@ -package plugins.stochastics.support; - -import net.jcip.annotations.ThreadSafe; - -/** - * Marker interface for random number generator identifiers - * - * @author Shawn Hatch - * - */ -@ThreadSafe -public interface RandomNumberGeneratorId { - -} diff --git a/gcm3/src/main/java/plugins/stochastics/support/StochasticsError.java b/gcm3/src/main/java/plugins/stochastics/support/StochasticsError.java deleted file mode 100644 index a5900e78b..000000000 --- a/gcm3/src/main/java/plugins/stochastics/support/StochasticsError.java +++ /dev/null @@ -1,30 +0,0 @@ -package plugins.stochastics.support; - -import util.errors.ContractError; -import util.errors.ContractException; - -/** - * An enumeration supporting {@link ContractException} that acts as a general - * description of the exception. - * - * @author Shawn Hatch - * - */ -public enum StochasticsError implements ContractError { - NULL_SEED("Null seed value"),// - NULL_RANDOM_NUMBER_GENERATOR("Null random number generator"),// - DUPLICATE_RANDOM_NUMBER_GENERATOR_ID("Duplicate random number generator id"),// - NULL_RANDOM_NUMBER_GENERATOR_ID("Null random number generator id"),// - ; - - private final String description; - - private StochasticsError(final String description) { - this.description = description; - } - - @Override - public String getDescription() { - return description; - } -} diff --git a/gcm3/src/main/java/plugins/stochastics/testsupport/StochasticsActionSupport.java b/gcm3/src/main/java/plugins/stochastics/testsupport/StochasticsActionSupport.java deleted file mode 100644 index b257741f8..000000000 --- a/gcm3/src/main/java/plugins/stochastics/testsupport/StochasticsActionSupport.java +++ /dev/null @@ -1,72 +0,0 @@ -package plugins.stochastics.testsupport; - -import java.util.function.Consumer; - -import nucleus.ActorContext; -import nucleus.Plugin; -import nucleus.Simulation; -import nucleus.testsupport.testplugin.ScenarioPlanCompletionObserver; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestError; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import plugins.stochastics.StochasticsPlugin; -import plugins.stochastics.StochasticsPluginData; -import util.errors.ContractException; - -/** - * A static test support class for the stochastics plugin. Provides convenience - * methods for integrating a test plugin into a stochastic simulation test - * harness. - * - * - * @author Shawn Hatch - * - */ - -public class StochasticsActionSupport { - - /** - * Creates the test plugin containing a test actor initialized by the given - * consumer. Executes the simulation via the - * {@linkplain StochasticsActionSupport#testConsumers(Plugin)} method - * - */ - public static void testConsumer(long seed, Consumer consumer) { - - TestPluginData testPluginData = TestPluginData .builder()// - .addTestActorPlan("actor", new TestActorPlan(0, consumer))// - .build(); - - Plugin plugin = TestPlugin.getTestPlugin(testPluginData); - testConsumers(seed, plugin); - } - - /** - * Executes a simulation composed of the given test plugin and the - * stochastics plugin initialized with the - * {@linkplain TestRandomGeneratorId} randdom generator ids and the given - * seed. - */ - public static void testConsumers(long seed, Plugin testPlugin) { - - StochasticsPluginData.Builder builder = StochasticsPluginData.builder(); - for (TestRandomGeneratorId testRandomGeneratorId : TestRandomGeneratorId.values()) { - builder.addRandomGeneratorId(testRandomGeneratorId); - } - builder.setSeed(seed); - - Plugin stochasticPlugin = StochasticsPlugin.getStochasticsPlugin(builder.build()); - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - Simulation .builder()// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput).addPlugin(testPlugin)// - .addPlugin(stochasticPlugin)// - .build()// - .execute();// - - // show that all actions were executed - if (!scenarioPlanCompletionObserver.allPlansExecuted()) { - throw new ContractException(TestError.TEST_EXECUTION_FAILURE); - } - } -} diff --git a/gcm3/src/main/java/plugins/stochastics/testsupport/TestRandomGeneratorId.java b/gcm3/src/main/java/plugins/stochastics/testsupport/TestRandomGeneratorId.java deleted file mode 100644 index 85baf4bd4..000000000 --- a/gcm3/src/main/java/plugins/stochastics/testsupport/TestRandomGeneratorId.java +++ /dev/null @@ -1,16 +0,0 @@ -package plugins.stochastics.testsupport; - -import plugins.stochastics.support.RandomNumberGeneratorId; -/** - * Enumeration that provides a variety of RandomNumberGeneratorId values for testing purposes - * - * @author Shawn Hatch - */ -public enum TestRandomGeneratorId implements RandomNumberGeneratorId { - DASHER, DANCER, PRANCER, VIXEN, COMET, CUPID, DONNER, BLITZEN; - - public static RandomNumberGeneratorId getUnknownRandomNumberGeneratorId() { - return new RandomNumberGeneratorId() { - }; - } -} diff --git a/gcm3/src/main/java/plugins/util/properties/AbstractIndexedPropertyManager.java b/gcm3/src/main/java/plugins/util/properties/AbstractIndexedPropertyManager.java deleted file mode 100644 index 50219e420..000000000 --- a/gcm3/src/main/java/plugins/util/properties/AbstractIndexedPropertyManager.java +++ /dev/null @@ -1,105 +0,0 @@ -package plugins.util.properties; - -import nucleus.SimulationContext; -import plugins.util.properties.arraycontainers.DoubleValueContainer; -import util.errors.ContractException; - -/** - * The abstract base class for all IndexedPropertyManager implementors. - * - * It implements all property time recording and reverse mapping of property - * values to people. Its implementation of these methods is final. - * - * It also implements setPropertyValue() and descendant classes are expected to - * invoke super.setPropertyValue() - * - * Finally, it leaves the implementation of getPropertyValue() to its descendant - * classes - * - * @author Shawn Hatch - * - */ -public abstract class AbstractIndexedPropertyManager implements IndexedPropertyManager { - - /* - * Contains the assignment times for this property value. Subject to - * tracking policy. - */ - private DoubleValueContainer timeTrackingContainer; - - /* - * The time tracking policy. - */ - private final boolean trackTime; - - private SimulationContext simulationContext; - - /** - * Constructs an AbstractPropertyManger. Establishes the time tracking and - * map option policies from the environment. Establishes the property value - * to people mapping if the MapOption is not NONE. - * - * - * - * @throws ContractException - *
          • {@linkplain PropertyError#NEGATIVE_INITIAL_SIZE} if the - * initial size is negative
          • - *
          • {@linkplain PropertyError#NULL_PROPERTY_DEFINITION} if - * the property definition is null
          • - */ - public AbstractIndexedPropertyManager(SimulationContext simulationContext, PropertyDefinition propertyDefinition, int initialSize) { - this.simulationContext = simulationContext; - if (propertyDefinition == null) { - throw new ContractException(PropertyError.NULL_PROPERTY_DEFINITION); - } - trackTime = propertyDefinition.getTimeTrackingPolicy() == TimeTrackingPolicy.TRACK_TIME; - if (initialSize < 0) { - throw new ContractException(PropertyError.NEGATIVE_INITIAL_SIZE); - } - timeTrackingContainer = new DoubleValueContainer(0, initialSize); - } - - @Override - public void setPropertyValue(int id, Object propertyValue) { - /* - * Record the time value if we are tracking assignment times. - */ - if (id < 0) { - throw new ContractException(PropertyError.NEGATIVE_INDEX); - } - if (trackTime) { - timeTrackingContainer.setValue(id, simulationContext.getTime()); - } - } - - @Override - public final double getPropertyTime(int id) { - if (id < 0) { - throw new ContractException(PropertyError.NEGATIVE_INDEX); - } - - if (trackTime) { - return timeTrackingContainer.getValue(id); - } - - throw new ContractException(PropertyError.TIME_TRACKING_OFF); - - } - - @Override - public void removeId(int id) { - if (id < 0) { - throw new ContractException(PropertyError.NEGATIVE_INDEX); - } - } - - @Override - public void incrementCapacity(int count) { - if (count < 0) { - throw new ContractException(PropertyError.NEGATIVE_CAPACITY_INCREMENT); - } - if (trackTime) { - timeTrackingContainer.setCapacity(timeTrackingContainer.getCapacity() + count); - } - } -} diff --git a/gcm3/src/main/java/plugins/util/properties/BooleanPropertyManager.java b/gcm3/src/main/java/plugins/util/properties/BooleanPropertyManager.java deleted file mode 100644 index 94f92c53d..000000000 --- a/gcm3/src/main/java/plugins/util/properties/BooleanPropertyManager.java +++ /dev/null @@ -1,71 +0,0 @@ -package plugins.util.properties; - -import nucleus.SimulationContext; -import plugins.util.properties.arraycontainers.BooleanContainer; -import util.errors.ContractException; - -/** - * Implementor of IndexedPropertyManager that compresses Boolean property values - * into a bit-based data structure. - * - * @author Shawn Hatch - * - */ -public final class BooleanPropertyManager extends AbstractIndexedPropertyManager { - - /* - * A container, indexed by person id, that stores boolean values as bits. - */ - private BooleanContainer boolContainer; - - /** - * Constructs this BooleanPropertyManager. - * - * @throws ContractException - *
          • {@linkplain PropertyError#NEGATIVE_INITIAL_SIZE} if the - * initial size is negative
          • - *
          • {@linkplain PropertyError#NULL_PROPERTY_DEFINITION} if - * the property definition is null
          • - *
          • {@linkplain PropertyError#PROPERTY_DEFINITION_IMPROPER_TYPE} - * if the property definition's type is not Boolean
          • - *
          • {@linkplain PropertyError#PROPERTY_DEFINITION_MISSING_DEFAULT} - * if the property definition does not have a default value
          • - * - */ - public BooleanPropertyManager(SimulationContext simulationContext, PropertyDefinition propertyDefinition, int initialSize) { - super(simulationContext, propertyDefinition, initialSize); - if (propertyDefinition.getType() != Boolean.class) { - throw new ContractException(PropertyError.PROPERTY_DEFINITION_IMPROPER_TYPE, "Requires a property definition with Boolean type "); - } - if (!propertyDefinition.getDefaultValue().isPresent()) { - throw new ContractException(PropertyError.PROPERTY_DEFINITION_MISSING_DEFAULT); - } - boolean defaultValue = (Boolean) propertyDefinition.getDefaultValue().get(); - - boolContainer = new BooleanContainer(defaultValue, initialSize); - } - - @Override - @SuppressWarnings("unchecked") - public T getPropertyValue(int id) { - if(id<0) { - throw new ContractException(PropertyError.NEGATIVE_INDEX); - } - Boolean result = boolContainer.get(id); - return (T) result; - } - - @Override - public void setPropertyValue(int id, Object propertyValue) { - super.setPropertyValue(id, propertyValue); - Boolean b = (Boolean) propertyValue; - boolContainer.set(id, b.booleanValue()); - } - - @Override - public void incrementCapacity(int count) { - super.incrementCapacity(count); - boolContainer.expandCapacity(count); - } - -} diff --git a/gcm3/src/main/java/plugins/util/properties/DoublePropertyManager.java b/gcm3/src/main/java/plugins/util/properties/DoublePropertyManager.java deleted file mode 100644 index 24a416fb8..000000000 --- a/gcm3/src/main/java/plugins/util/properties/DoublePropertyManager.java +++ /dev/null @@ -1,75 +0,0 @@ -package plugins.util.properties; - -import nucleus.SimulationContext; -import plugins.util.properties.arraycontainers.DoubleValueContainer; -import util.errors.ContractException; - -/** - * Implementor of IndexedPropertyManager that compresses Double property values - * into a double[]-based data structure. - * - * @author Shawn Hatch - * - */ -public final class DoublePropertyManager extends AbstractIndexedPropertyManager { - - /* - * A container, indexed by person id, that stores Double values as an array - * of double. - */ - private DoubleValueContainer doubleValueContainer; - - /** - * Constructs this DoublePropertyManager. - * - * @throws ContractException - *
          • {@linkplain PropertyError#NEGATIVE_INITIAL_SIZE} if the - * initial size is negative
          • - *
          • {@linkplain PropertyError#NULL_PROPERTY_DEFINITION} if - * the property definition is null
          • - *
          • {@linkplain PropertyError#PROPERTY_DEFINITION_IMPROPER_TYPE} - * if the property definition's type is not Double
          • - *
          • {@linkplain PropertyError#PROPERTY_DEFINITION_MISSING_DEFAULT} - * if the property definition does not have a default value
          • - * - */ - public DoublePropertyManager(SimulationContext simulationContext, PropertyDefinition propertyDefinition, int initialSize) { - super(simulationContext, propertyDefinition, initialSize); - - if (propertyDefinition.getType() != Double.class) { - throw new ContractException(PropertyError.PROPERTY_DEFINITION_IMPROPER_TYPE, "Requires a property definition with Double type "); - } - - if (!propertyDefinition.getDefaultValue().isPresent()) { - throw new ContractException(PropertyError.PROPERTY_DEFINITION_MISSING_DEFAULT); - } - - Double defaultValue = (Double) propertyDefinition.getDefaultValue().get(); - - doubleValueContainer = new DoubleValueContainer(defaultValue, initialSize); - } - - @Override - @SuppressWarnings("unchecked") - public T getPropertyValue(int id) { - if(id<0) { - throw new ContractException(PropertyError.NEGATIVE_INDEX); - } - Double result = doubleValueContainer.getValue(id); - return (T) result; - } - - @Override - public void setPropertyValue(int id, Object propertyValue) { - super.setPropertyValue(id, propertyValue); - Double d = (Double) propertyValue; - doubleValueContainer.setValue(id, d); - } - - @Override - public void incrementCapacity(int count) { - super.incrementCapacity(count); - doubleValueContainer.setCapacity(doubleValueContainer.getCapacity() + count); - } - -} diff --git a/gcm3/src/main/java/plugins/util/properties/EnumPropertyManager.java b/gcm3/src/main/java/plugins/util/properties/EnumPropertyManager.java deleted file mode 100644 index 86bfedc97..000000000 --- a/gcm3/src/main/java/plugins/util/properties/EnumPropertyManager.java +++ /dev/null @@ -1,69 +0,0 @@ -package plugins.util.properties; - -import nucleus.SimulationContext; -import plugins.util.properties.arraycontainers.EnumContainer; -import util.errors.ContractException; - -/** - * Implementor of IndexedPropertyManager that compresses Enum property values - * into a byte-based data structure of the various int-like primitives. - * - * @author Shawn Hatch - * - */ -public final class EnumPropertyManager extends AbstractIndexedPropertyManager { - /* - * The storage container. - */ - private EnumContainer enumContainer; - - /** - * Constructs this EnumPropertyManager. - * - * @throws ContractException - *
          • {@linkplain PropertyError#NEGATIVE_INITIAL_SIZE} if the - * initial size is negative
          • - *
          • {@linkplain PropertyError#NULL_PROPERTY_DEFINITION} if - * the property definition is null
          • - *
          • {@linkplain PropertyError#PROPERTY_DEFINITION_MISSING_DEFAULT} - * if the property definition does not have a default value
          • - *
          • {@linkplain PropertyError#PROPERTY_DEFINITION_IMPROPER_TYPE} if the - * property definition's type is not an enumeration
          • - * - */ - public EnumPropertyManager(SimulationContext simulationContext, PropertyDefinition propertyDefinition, int initialSize) { - super(simulationContext, propertyDefinition, initialSize); - - if (!propertyDefinition.getDefaultValue().isPresent()) { - throw new ContractException(PropertyError.PROPERTY_DEFINITION_MISSING_DEFAULT); - } - - if (!Enum.class.isAssignableFrom(propertyDefinition.getType())) { - throw new ContractException(PropertyError.PROPERTY_DEFINITION_IMPROPER_TYPE, "cannot construct from class " + propertyDefinition.getClass().getName()); - } - - enumContainer = new EnumContainer(propertyDefinition.getType(), propertyDefinition.getDefaultValue().get(), initialSize); - } - - @Override - @SuppressWarnings("unchecked") - public T getPropertyValue(int id) { - if(id<0) { - throw new ContractException(PropertyError.NEGATIVE_INDEX); - } - return (T) enumContainer.getValue(id); - } - - @Override - public void setPropertyValue(int id, Object propertyValue) { - super.setPropertyValue(id, propertyValue); - enumContainer.setValue(id, propertyValue); - } - - @Override - public void incrementCapacity(int count) { - super.incrementCapacity(count); - enumContainer.setCapacity(enumContainer.getCapacity() + count); - } - -} diff --git a/gcm3/src/main/java/plugins/util/properties/FloatPropertyManager.java b/gcm3/src/main/java/plugins/util/properties/FloatPropertyManager.java deleted file mode 100644 index 005ed2184..000000000 --- a/gcm3/src/main/java/plugins/util/properties/FloatPropertyManager.java +++ /dev/null @@ -1,65 +0,0 @@ -package plugins.util.properties; - -import nucleus.SimulationContext; -import plugins.util.properties.arraycontainers.FloatValueContainer; -import util.errors.ContractException; - -/** - * Implementor of IndexedPropertyManager that compresses Float property values - * into a float[]-based data structure. - * - * @author Shawn Hatch - * - */ -public final class FloatPropertyManager extends AbstractIndexedPropertyManager { - /* - * A container, indexed by person id, that stores Double values as an array - * of float. - */ - private FloatValueContainer floatValueContainer; - - /** - * Constructs this FloatPropertyManager. - * - * @throws ContractException - *
          • {@linkplain PropertyError#PROPERTY_DEFINITION_IMPROPER_TYPE} - * if the property definition's type is not Boolean
          • - *
          • {@linkplain PropertyError#PROPERTY_DEFINITION_MISSING_DEFAULT} - * if the property definition does not have a default value
          • - */ - public FloatPropertyManager(SimulationContext simulationContext, PropertyDefinition propertyDefinition, int initialSize) { - super(simulationContext, propertyDefinition, initialSize); - if (propertyDefinition.getType() != Float.class) { - throw new ContractException(PropertyError.PROPERTY_DEFINITION_IMPROPER_TYPE,"Requires a property definition with float type"); - } - if (!propertyDefinition.getDefaultValue().isPresent()) { - throw new ContractException(PropertyError.PROPERTY_DEFINITION_MISSING_DEFAULT); - } - Float defaultValue = (Float) propertyDefinition.getDefaultValue().get(); - floatValueContainer = new FloatValueContainer(defaultValue, initialSize); - } - - @Override - @SuppressWarnings("unchecked") - public T getPropertyValue(int id) { - if(id<0) { - throw new ContractException(PropertyError.NEGATIVE_INDEX); - } - Float result = floatValueContainer.getValue(id); - return (T) result; - } - - @Override - public void setPropertyValue(int id, Object propertyValue) { - super.setPropertyValue(id, propertyValue); - Float f = (Float) propertyValue; - floatValueContainer.setValue(id, f); - } - - @Override - public void incrementCapacity(int count) { - super.incrementCapacity(count); - floatValueContainer.setCapacity(floatValueContainer.getCapacity() + count); - } - -} diff --git a/gcm3/src/main/java/plugins/util/properties/IndexedPropertyManager.java b/gcm3/src/main/java/plugins/util/properties/IndexedPropertyManager.java deleted file mode 100644 index a11ecce08..000000000 --- a/gcm3/src/main/java/plugins/util/properties/IndexedPropertyManager.java +++ /dev/null @@ -1,75 +0,0 @@ -package plugins.util.properties; - -import util.errors.ContractException; - -/** - * Common interface to all property managers. A property manager manages - * property values associated with int-based identifiers. - * - * @author Shawn Hatch - * - */ -public interface IndexedPropertyManager { - - /** - * Returns the property value stored for the given id. Does not return null. - * Note that this does not imply that the id exists in the simulation. - * - * @throws ContractException - *
          • {@linkplain PropertyError#NEGATIVE_INDEX} if the id is - * negative
          • - */ - public T getPropertyValue(int id); - - /** - * Returns the assignment time when the id's property was last set. Note - * that this does not imply that the id exists in the simulation. Defaults - * to zero for non-valid id values. - * - * @throws ContractException - *
          • {@linkplain PropertyError#NEGATIVE_INDEX} if the id is - * negative
          • - * - *
          • {@linkplain PropertyError#TIME_TRACKING_OFF} if time - * tracking is off
          • - * - * - * - */ - public double getPropertyTime(int id); - - /** - * Sets the property value stored for the given person. Note that this does - * not imply that the person exists in the simulation. The environment must - * guard against access to removed people. - * - * @throws ContractException - *
          • {@linkplain PropertyError#NEGATIVE_INDEX} if the id is - * negative
          • - * - */ - public void setPropertyValue(int id, Object propertyValue); - - /** - * Removes non-primitive property values for the given id -- use only when - * removing the indicated id from the simulation. - * - * @throws ContractException - *
          • {@linkplain PropertyError#NEGATIVE_INDEX} if the id is - * negative
          • - */ - public void removeId(int id); - - /** - * Sets the capacity for this manager. Indicates to the manager an - * anticipated near term growth so that the manager might more efficiently - * expand to hold more data. - * - * @throws ContractException - *
          • {@linkplain PropertyError#NEGATIVE_CAPACITY_INCREMENT} if - * the count is negative
          • - * - */ - public void incrementCapacity(int count); - -} diff --git a/gcm3/src/main/java/plugins/util/properties/IntPropertyManager.java b/gcm3/src/main/java/plugins/util/properties/IntPropertyManager.java deleted file mode 100644 index bbb101521..000000000 --- a/gcm3/src/main/java/plugins/util/properties/IntPropertyManager.java +++ /dev/null @@ -1,139 +0,0 @@ -package plugins.util.properties; - -import nucleus.SimulationContext; -import plugins.util.properties.arraycontainers.IntValueContainer; -import plugins.util.properties.arraycontainers.IntValueContainer.IntValueType; -import util.errors.ContractException; - -/** - * Implementor of IndexedPropertyManager that compresses Byte, Short, Integer or - * Long property values into a byte-based array data structure. - * - * @author Shawn Hatch - * - */ -public final class IntPropertyManager extends AbstractIndexedPropertyManager { - - /* - * A container, indexed by person id, that stores the various Boxed integral - * types values as bytes. - */ - private IntValueContainer intValueContainer; - - /* - * The particular IntValueType for this property manager as determined by - * the class type associated with the corresponding property definition. - */ - private IntValueType intValueType; - - /** - * Constructs this IntPropertyManager. - * - * @throws ContractException - *
          • {@linkplain PropertyError#PROPERTY_DEFINITION_IMPROPER_TYPE} - * if the property definition's type is not a Byte, Short, Integer or Long
          • - *
          • {@linkplain PropertyError#PROPERTY_DEFINITION_MISSING_DEFAULT} - * if the property definition does not have a default value
          • - */ - public IntPropertyManager(SimulationContext simulationContext, PropertyDefinition propertyDefinition, int initialSize) { - super(simulationContext, propertyDefinition, initialSize); - if (!propertyDefinition.getDefaultValue().isPresent()) { - throw new ContractException(PropertyError.PROPERTY_DEFINITION_MISSING_DEFAULT); - } - - Object defaultValue = propertyDefinition.getDefaultValue().get(); - long longDefaultValue; - if (propertyDefinition.getType() == Byte.class) { - intValueType = IntValueType.BYTE; - } else if (propertyDefinition.getType() == Short.class) { - intValueType = IntValueType.SHORT; - } else if (propertyDefinition.getType() == Integer.class) { - intValueType = IntValueType.INT; - } else if (propertyDefinition.getType() == Long.class) { - intValueType = IntValueType.LONG; - } else { - throw new ContractException(PropertyError.PROPERTY_DEFINITION_IMPROPER_TYPE,"Requires a property definition type of Byte, Short, Integer or Long"); - } - - switch (intValueType) { - case BYTE: - Byte b = (Byte) defaultValue; - longDefaultValue = b.longValue(); - break; - case INT: - Integer i = (Integer) defaultValue; - longDefaultValue = i.longValue(); - break; - case LONG: - Long l = (Long) defaultValue; - longDefaultValue = l.longValue(); - break; - case SHORT: - Short s = (Short) defaultValue; - longDefaultValue = s.longValue(); - break; - default: - throw new RuntimeException("unhandled type " + intValueType); - } - - intValueContainer = new IntValueContainer(longDefaultValue, initialSize); - } - - @Override - @SuppressWarnings("unchecked") - public T getPropertyValue(int id) { - if(id<0) { - throw new ContractException(PropertyError.NEGATIVE_INDEX); - } - - switch (intValueType) { - case BYTE: - Byte b = intValueContainer.getValueAsByte(id); - return (T) b; - case INT: - Integer i = intValueContainer.getValueAsInt(id); - return (T) i; - case LONG: - Long l = intValueContainer.getValueAsLong(id); - return (T) l; - case SHORT: - Short s = intValueContainer.getValueAsShort(id); - return (T) s; - default: - throw new RuntimeException("unhandled type"); - } - } - - @Override - public void setPropertyValue(int id, Object propertyValue) { - super.setPropertyValue(id, propertyValue); - - switch (intValueType) { - case BYTE: - Byte b = (Byte) propertyValue; - intValueContainer.setByteValue(id, b); - break; - case INT: - Integer i = (Integer) propertyValue; - intValueContainer.setIntValue(id, i); - break; - case LONG: - Long l = (Long) propertyValue; - intValueContainer.setLongValue(id, l); - break; - case SHORT: - Short s = (Short) propertyValue; - intValueContainer.setShortValue(id, s); - break; - default: - throw new RuntimeException("unhandled type " + intValueType); - } - } - - @Override - public void incrementCapacity(int count) { - super.incrementCapacity(count); - intValueContainer.setCapacity(intValueContainer.getCapacity() + count); - } - -} diff --git a/gcm3/src/main/java/plugins/util/properties/ObjectPropertyManager.java b/gcm3/src/main/java/plugins/util/properties/ObjectPropertyManager.java deleted file mode 100644 index 70cc06ed7..000000000 --- a/gcm3/src/main/java/plugins/util/properties/ObjectPropertyManager.java +++ /dev/null @@ -1,68 +0,0 @@ -package plugins.util.properties; - -import nucleus.SimulationContext; -import plugins.util.properties.arraycontainers.ObjectValueContainer; -import util.errors.ContractException; - -/** - * Implementor of IndexedPropertyManager that stores Object property values in an - * Object array based data structure. - * - * @author Shawn Hatch - * - */ -public final class ObjectPropertyManager extends AbstractIndexedPropertyManager { - - /* - * A container, indexed by person id, that stores Objects as an array. - */ - private ObjectValueContainer objectValueContainer; - private final Object defaultValue; - - /** - * Constructs this IntPropertyManager. - * - * @throws ContractException - *
          • {@linkplain PropertyError#PROPERTY_DEFINITION_MISSING_DEFAULT} - * if the property definition does not have a default value
          • - */ - public ObjectPropertyManager(SimulationContext simulationContext, PropertyDefinition propertyDefinition, int initialSize) { - super(simulationContext, propertyDefinition, initialSize); - - if (!propertyDefinition.getDefaultValue().isPresent()) { - throw new ContractException(PropertyError.PROPERTY_DEFINITION_MISSING_DEFAULT); - } - - defaultValue = propertyDefinition.getDefaultValue().get(); - objectValueContainer = new ObjectValueContainer(defaultValue, initialSize); - } - - @Override - public T getPropertyValue(int id) { - if(id<0) { - throw new ContractException(PropertyError.NEGATIVE_INDEX); - } - return objectValueContainer.getValue(id); - } - - @Override - public void setPropertyValue(int id, Object propertyValue) { - super.setPropertyValue(id, propertyValue); - objectValueContainer.setValue(id, propertyValue); - } - - @Override - public void removeId(int id) { - // dropping reference to the currently stored value for potential - // garbage collection - super.removeId(id); - objectValueContainer.setValue(id, defaultValue); - } - - @Override - public void incrementCapacity(int count) { - super.incrementCapacity(count); - objectValueContainer.setCapacity(objectValueContainer.getCapacity()+count); - } - -} diff --git a/gcm3/src/main/java/plugins/util/properties/PropertyDefinition.java b/gcm3/src/main/java/plugins/util/properties/PropertyDefinition.java deleted file mode 100644 index 57517e01b..000000000 --- a/gcm3/src/main/java/plugins/util/properties/PropertyDefinition.java +++ /dev/null @@ -1,242 +0,0 @@ -package plugins.util.properties; - -import java.util.Optional; - -import net.jcip.annotations.ThreadSafe; -import util.errors.ContractException; - -/** - * A thread-safe, immutable class that defines a property, but does not indicate - * the role that property is playing or the identifier of the property. - * - * @author Shawn Hatch - * - */ -@ThreadSafe -public final class PropertyDefinition { - - public static Builder builder() { - return new Builder(); - } - - private static class Scaffold { - private Class type = null; - - private boolean propertyValuesAreMutable = true; - - private Object defaultValue = null; - - private TimeTrackingPolicy timeTrackingPolicy = TimeTrackingPolicy.DO_NOT_TRACK_TIME; - } - - /** - * Builder class for {@linkplain PropertyDefinition} - * - * @author Shawn Hatch - * - */ - public static class Builder { - - private Scaffold scaffold = new Scaffold(); - - private Builder() { - } - - /** - * Builds the property definition - * - * @throws ContractException - *
          • {@linkplain PropertyError#NULL_PROPERTY_TYPE} if the - * class type of the definition is not assigned or null
          • - *
          • {@linkplain PropertyError#INCOMPATIBLE_DEFAULT_VALUE}if - * the default value is not null and the class type is not a - * super-type of the default value
          • - * - */ - public PropertyDefinition build() { - try { - return new PropertyDefinition(scaffold); - } finally { - scaffold = new Scaffold(); - } - } - - /** - * Sets the class type. Value must be set by client. - */ - public Builder setType(final Class type) { - scaffold.type = type; - return this; - } - - /** - * Sets property value mutability during simulation run time. Default - * value is true. - */ - public Builder setPropertyValueMutability(boolean propertyValuesAreMutable) { - scaffold.propertyValuesAreMutable = propertyValuesAreMutable; - return this; - } - - /** - * Sets the default value for property values of this definition. Value - * must be set(non-null) by client. - */ - public Builder setDefaultValue(Object defaultValue) { - scaffold.defaultValue = defaultValue; - return this; - } - - /** - * Sets the {@linkplain TimeTrackingPolicy}. Default value is - * {@link TimeTrackingPolicy#DO_NOT_TRACK_TIME} - */ - public Builder setTimeTrackingPolicy(TimeTrackingPolicy timeTrackingPolicy) { - scaffold.timeTrackingPolicy = timeTrackingPolicy; - return this; - } - } - - private final Class type; - - private final boolean propertyValuesAreMutable; - - private final Object defaultValue; - - private final TimeTrackingPolicy timeTrackingPolicy; - - private PropertyDefinition(Scaffold scaffold) { - if (scaffold.type == null) { - throw new ContractException(PropertyError.NULL_PROPERTY_TYPE); - } - - if (scaffold.defaultValue != null) { - if (!scaffold.type.isInstance(scaffold.defaultValue)) { - throw new ContractException(PropertyError.INCOMPATIBLE_DEFAULT_VALUE); - } - } - - this.type = scaffold.type; - - this.propertyValuesAreMutable = scaffold.propertyValuesAreMutable; - - this.defaultValue = scaffold.defaultValue; - - this.timeTrackingPolicy = scaffold.timeTrackingPolicy; - - } - - /** - * Returns the Optional containing default value for the property - * definition. Null values are allowed as a convenience. Any property - * definition that has a null default value must have corresponding property - * value assignments within plugin initial data that cover all cases. - * Property definitions for dynamically generated relationships MUST contain - * non-null default values since they are created after data initialization - * of the plugins. - */ - public Optional getDefaultValue() { - if (defaultValue == null) { - return Optional.empty(); - } - return Optional.of(defaultValue); - } - - /** - * Returns that class type of this definition. It is used to ensure that all - * values assigned to properties have a predictable type from the modeler's - * perspective. Property assignments are descendant class tolerant. For - * example, a property having a defined type of Number, would accept values - * that are Double, Integer or any other descendant type. - * - */ - public Class getType() { - return type; - } - - /** - * The modeler may define a property such that all associated property - * values must be equal to the default value of this property definition. - * Any attempt to assign a value to a property so defined will result in an - * error. This can be used to ensure that some variables remain constant - * throughout the run of a simulation instance. - * - * Returns true if and only if the value of the property must remain - * constant. - */ - public boolean propertyValuesAreMutable() { - return propertyValuesAreMutable; - } - - /** - * Returns the time tracking policy for the property. - */ - public TimeTrackingPolicy getTimeTrackingPolicy() { - return timeTrackingPolicy; - } - - /** - * Boilerplate implementation that uses all fields. - */ - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + (propertyValuesAreMutable ? 1231 : 1237); - result = prime * result + ((defaultValue == null) ? 0 : defaultValue.hashCode()); - result = prime * result + ((timeTrackingPolicy == null) ? 0 : timeTrackingPolicy.hashCode()); - result = prime * result + ((type == null) ? 0 : type.hashCode()); - return result; - } - - /** - * Boilerplate implementation that uses all fields and type equality. - */ - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - PropertyDefinition other = (PropertyDefinition) obj; - if (propertyValuesAreMutable != other.propertyValuesAreMutable) - return false; - if (defaultValue == null) { - if (other.defaultValue != null) - return false; - } else if (!defaultValue.equals(other.defaultValue)) - return false; - if (timeTrackingPolicy != other.timeTrackingPolicy) - return false; - if (type == null) { - return other.type == null; - } else - return type.equals(other.type); - } - - /** - * Standard string representation in the form: - * - * PropertyDefinition [type=someType,mapOption=mapOption, - * constantPropertyValues=true, defaultValue=someValue, - * timeTrackingPolicy=policy] - */ - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("PropertyDefinition [type="); - builder.append(type); - builder.append(", propertyValuesAreMutable="); - builder.append(propertyValuesAreMutable); - builder.append(", defaultValue="); - builder.append(defaultValue); - builder.append(", timeTrackingPolicy="); - builder.append(timeTrackingPolicy); - builder.append("]"); - return builder.toString(); - } - -} \ No newline at end of file diff --git a/gcm3/src/main/java/plugins/util/properties/PropertyError.java b/gcm3/src/main/java/plugins/util/properties/PropertyError.java deleted file mode 100644 index 1f44b0566..000000000 --- a/gcm3/src/main/java/plugins/util/properties/PropertyError.java +++ /dev/null @@ -1,38 +0,0 @@ -package plugins.util.properties; - -import util.errors.ContractError; -import util.errors.ContractException; - -/** - * An enumeration supporting {@link ContractException} that acts as a general - * description of the exception. - * - * @author Shawn Hatch - * - */ -public enum PropertyError implements ContractError { - - NULL_PROPERTY_TYPE("Type for property definition is null"),// - IMMUTABLE_VALUE("This property is defined as immutable"),// - INCOMPATIBLE_DEFAULT_VALUE("Default value is incompatible with the class type"),// - INCOMPATIBLE_VALUE("Property value is incompatible with the property definition"),// - NULL_PROPERTY_DEFINITION("Null property definition"),// - PROPERTY_DEFINITION_IMPROPER_TYPE("Property definition has improper data type"),// - PROPERTY_DEFINITION_MISSING_DEFAULT("Property definition has no default value"),// - NEGATIVE_INITIAL_SIZE("Negative initial size"),// - NEGATIVE_CAPACITY_INCREMENT("Negative capacity increment"),// - NEGATIVE_INDEX("Negative index"),// - TIME_TRACKING_OFF("Time tracking is off"),// - ; - - private final String description; - - private PropertyError(final String description) { - this.description = description; - } - - @Override - public String getDescription() { - return description; - } -} diff --git a/gcm3/src/main/java/plugins/util/properties/PropertyValueRecord.java b/gcm3/src/main/java/plugins/util/properties/PropertyValueRecord.java deleted file mode 100644 index 77733e6db..000000000 --- a/gcm3/src/main/java/plugins/util/properties/PropertyValueRecord.java +++ /dev/null @@ -1,41 +0,0 @@ -package plugins.util.properties; - -import nucleus.SimulationContext; - -/** - * A utility class for holding the value and assignment time for a property. On - * value assignment, this PropertyValueRecord records the current simulation - * time. - */ -public class PropertyValueRecord { - - private Object propertyValue; - private double assignmentTime; - private final SimulationContext simulationContext; - - public PropertyValueRecord(SimulationContext simulationContext) { - this.simulationContext = simulationContext; - } - - /** - * Returns the last assigned value - */ - public Object getValue() { - return propertyValue; - } - - /** - * Sets the current value and records the assignment time - */ - public void setPropertyValue(Object propertyValue) { - this.propertyValue = propertyValue; - assignmentTime = simulationContext.getTime(); - } - - /** - * Returns the time of the last assignment - */ - public double getAssignmentTime() { - return assignmentTime; - } -} \ No newline at end of file diff --git a/gcm3/src/main/java/plugins/util/properties/TimeTrackingPolicy.java b/gcm3/src/main/java/plugins/util/properties/TimeTrackingPolicy.java deleted file mode 100644 index c4c6c14d5..000000000 --- a/gcm3/src/main/java/plugins/util/properties/TimeTrackingPolicy.java +++ /dev/null @@ -1,12 +0,0 @@ -package plugins.util.properties; - -/** - * An enumeration used to control the tracking of assignment times of properties - * and other values. - * - * @author Shawn Hatch - * - */ -public enum TimeTrackingPolicy { - TRACK_TIME, DO_NOT_TRACK_TIME -} diff --git a/gcm3/src/main/java/plugins/util/properties/arraycontainers/BooleanContainer.java b/gcm3/src/main/java/plugins/util/properties/arraycontainers/BooleanContainer.java deleted file mode 100644 index 353f0c395..000000000 --- a/gcm3/src/main/java/plugins/util/properties/arraycontainers/BooleanContainer.java +++ /dev/null @@ -1,103 +0,0 @@ -package plugins.util.properties.arraycontainers; - -import java.util.BitSet; - -import plugins.util.properties.PropertyError; -import util.errors.ContractException; - -/** - * A container that maps non-negative int index values to booleans by storing - * each boolean as a single bit in a BitSet. Returns a default boolean value for - * every non-negative int index value until the value is explicitly set by an - * invocation to the set() method. - * - * @author Shawn Hatch - * - */ -public final class BooleanContainer { - /* - * The default value to return for all indexes that are greater than or - * equal to the bounding index. - */ - private final boolean defaultValue; - - /* - * The lowest index value that has not been the subject of an explicit - * invocation of the set() method. - */ - private int boundingIndex; - - public BooleanContainer(boolean defaultValue) { - this.defaultValue = defaultValue; - bitSet = new BitSet(); - } - - /** - * - * @throws ContractException - * - *
          • {@linkplain PropertyError#NEGATIVE_INITIAL_SIZE} if the capacity is negative
          • - */ - public BooleanContainer(boolean defaultValue, int capacity) { - this.defaultValue = defaultValue; - if (capacity < 0) { - throw new ContractException(PropertyError.NEGATIVE_INITIAL_SIZE); - } - boundingIndex = capacity; - bitSet = new BitSet(boundingIndex); - if (defaultValue) { - bitSet.set(0, boundingIndex, defaultValue); - } - } - - public void expandCapacity(int count) { - if(count>0) { - set(boundingIndex+count-1,defaultValue); - } - } - - /* - * The bitSet containing the bit level representation of the Booleans. - */ - private BitSet bitSet; - - /** - * Returns the boolean value associated with the given index - * - * @throws IndexOutOfBoundsException - * if the specified index is negative - */ - public boolean get(int index) { - /* - * If the index is beyond any we have had set, then return the default - * value. - */ - if (index >= boundingIndex) { - return defaultValue; - } - return bitSet.get(index); - } - - /** - * Set the boolean value associated with the given index - * - * @throws IndexOutOfBoundsException - * if the specified index is negative - * @param index - * @param value - */ - public void set(int index, boolean value) { - if (index < 0) { - throw new IndexOutOfBoundsException("index = " + index); - } - // if the index is new to us, then fill the bitSet with the default from - // the bounding index to the index. - if (index >= boundingIndex) { - int newBoundingIndex = index + 1; - bitSet.set(boundingIndex, newBoundingIndex, defaultValue); - boundingIndex = newBoundingIndex; - } - bitSet.set(index, value); - } - -} \ No newline at end of file diff --git a/gcm3/src/main/java/plugins/util/properties/arraycontainers/DoubleValueContainer.java b/gcm3/src/main/java/plugins/util/properties/arraycontainers/DoubleValueContainer.java deleted file mode 100644 index d99e89f33..000000000 --- a/gcm3/src/main/java/plugins/util/properties/arraycontainers/DoubleValueContainer.java +++ /dev/null @@ -1,157 +0,0 @@ -package plugins.util.properties.arraycontainers; - -import java.util.Arrays; - -import plugins.util.properties.PropertyError; -import util.errors.ContractException; - -/** - * A container that maps non-negative int index values to doubles by storing - * each double in an array. Returns a default double value for every - * non-negative int index value until the value is explicitly set by an - * invocation to the set() method. - * - * @author Shawn Hatch - * - */ -public final class DoubleValueContainer { - - /* - * The array for storing the values - */ - private double[] values; - - /* - * Holds the logical size of the values array based on the highest index - * used in invocations of setValue(). - */ - private int size; - - /* - * The value returned for any non-negative index that has not been set via - * an invocation of setValue(). - */ - private double defaultValue; - - /* - * Grows the length of the values array to be the greater of the given - * capacity and 125% of its current length, filling empty elements in the - * array with the default value. - */ - private void grow(int capacity) { - int oldCapacity = values.length; - int newCapacity = Math.max(capacity, values.length + (values.length >> 2)); - values = Arrays.copyOf(values, newCapacity); - if (defaultValue != 0) { - for (int i = oldCapacity; i < newCapacity; i++) { - values[i] = defaultValue; - } - } - } - - /** - * Returns the value at index - * - * @param index - * @return - * @throws ContractException - *
          • {@linkplain PropertyError#NEGATIVE_INDEX} if index is negative
          • - * - */ - public double getValue(int index) { - double result; - - if(index<0) { - throw new ContractException(PropertyError.NEGATIVE_INDEX); - } - - if (index < values.length) { - result = values[index]; - } else { - result = defaultValue; - } - - return result; - } - - public int size() { - return size; - } - - /** - * Sets the capacity to the given capacity if the current capacity is less - * than the one given. - */ - public void setCapacity(int capacity) { - if (capacity > values.length) { - grow(capacity); - } - } - - /** - * Returns the capacity of this container. Capacity is guaranteed to be - * greater than or equal to size. - */ - public int getCapacity() { - return values.length; - } - - /** - * Returns the default value - * - */ - public double getDefaultValue() { - return defaultValue; - } - - /** - * Constructs the DoubleValueContainer with the given default value. - * - * @param defaultValue - */ - public DoubleValueContainer(double defaultValue) { - this(defaultValue, 16); - } - - /** - * Constructs the DoubleValueContainer with the given default value and - * initial capacity - * - * @param defaultValue - * @param capacity - * - * @throws NegativeArraySizeException - * if the capacity is negative - */ - public DoubleValueContainer(double defaultValue, int capacity) { - values = new double[capacity]; - if (defaultValue != 0) { - for (int i = 0; i < capacity; i++) { - values[i] = defaultValue; - } - } - this.defaultValue = defaultValue; - - } - - /** - * Sets the value at the index to the given value - * - * @throws ContractException - *
          • {@linkplain PropertyError#NEGATIVE_INDEX} if index is - * negative
          • - */ - public void setValue(int index, double value) { - if (index < 0) { - throw new ContractException(PropertyError.NEGATIVE_INDEX); - } - if (index >= values.length) { - grow(index + 1); - } - if (index >= size) { - size = index + 1; - } - values[index] = value; - } - -} diff --git a/gcm3/src/main/java/plugins/util/properties/arraycontainers/EnumContainer.java b/gcm3/src/main/java/plugins/util/properties/arraycontainers/EnumContainer.java deleted file mode 100644 index 221728efd..000000000 --- a/gcm3/src/main/java/plugins/util/properties/arraycontainers/EnumContainer.java +++ /dev/null @@ -1,126 +0,0 @@ -package plugins.util.properties.arraycontainers; - -/** - * A container for retaining enum values from a single enumeration class indexed - * by int index. It is designed to generally require approximately 1 byte per - * index rather than a 4 or 8 byte object reference. - * - * @author Shawn Hatch - * - */ -public final class EnumContainer { - - /* - * We store the enum values by their ord equivalents. - */ - private IntValueContainer intValueContainer; - - /* - * We convert an ord value for an enum into enum member instance via an - * ObjectValueContainer. - */ - private ObjectValueContainer objectValueContainer; - - /* - * The class reference for the enum type - */ - private final Class enumClass; - - /** - * Constructs an instance of EnumContainer. - * - * @throws IllegalArgumentException - *
          • if the class is null - *
          • if the class is not an enumeration - *
          • if the default value is null - *
          • if the default is not a member of the enumeration - */ - public EnumContainer(Class c, Object defaultValue, int capacity) { - if (c == null) { - throw new IllegalArgumentException("null class reference"); - } - if (!Enum.class.isAssignableFrom(c)) { - throw new IllegalArgumentException("cannot construct from class " + c.getName()); - } - if (defaultValue == null) { - throw new IllegalArgumentException("null default value"); - } - if (defaultValue.getClass() != c) { - throw new IllegalArgumentException("default value " + defaultValue + " does not match enum class " + c); - } - if (capacity < 0) { - throw new IllegalArgumentException("capacity " + capacity + " is less than zero"); - } - - enumClass = c; - Enum e = (Enum) defaultValue; - objectValueContainer = new ObjectValueContainer(null, 16); - objectValueContainer.setValue(e.ordinal(), defaultValue); - intValueContainer = new IntValueContainer(e.ordinal(), capacity); - } - - public EnumContainer(Class c, Object defaultValue) { - this(c, defaultValue, 0); - } - - /** - * Set the value at the given index. - * - * @throws IllegalArgumentException - *
          • if the index is negative - *
          • if the value is null - *
          • if the value is not a member of the enumeration - */ - public void setValue(int index, Object value) { - - if (index < 0) { - throw new IllegalArgumentException("negative index: " + index); - } - - if (value == null) { - throw new IllegalArgumentException("null value"); - } - if (value.getClass() != enumClass) { - throw new IllegalArgumentException("improper class type for value " + value + ", expected " + enumClass); - } - - /* - * Convert the object value into an instance of Enum - */ - Enum e = (Enum) value; - - if (objectValueContainer.getValue(e.ordinal()) == null) { - objectValueContainer.setValue(e.ordinal(), value); - } - - intValueContainer.setIntValue(index, e.ordinal()); - - } - - /** - * Set the value at the given index. - * - * @throws ArrayIndexOutOfBoundsException - *
          • if the index is negative - */ - public Object getValue(int index) { - /* - * Retrieve the ordinal value from the index - */ - int ordinal = intValueContainer.getValueAsInt(index); - /* - * Convert the ordinal value into the enum member instance. - */ - return objectValueContainer.getValue(ordinal); - } - - public int getCapacity() { - return intValueContainer.getCapacity(); - } - - public void setCapacity(int capacity) { - intValueContainer.setCapacity(capacity); - } - - -} \ No newline at end of file diff --git a/gcm3/src/main/java/plugins/util/properties/arraycontainers/FloatValueContainer.java b/gcm3/src/main/java/plugins/util/properties/arraycontainers/FloatValueContainer.java deleted file mode 100644 index 25cc8ccd6..000000000 --- a/gcm3/src/main/java/plugins/util/properties/arraycontainers/FloatValueContainer.java +++ /dev/null @@ -1,153 +0,0 @@ -package plugins.util.properties.arraycontainers; - -import java.util.Arrays; - -import plugins.util.properties.PropertyError; -import util.errors.ContractException; - -/** - * A container that maps non-negative int index values to floats by storing each - * float in an array. Returns a default float value for every non-negative int - * index value until the value is explicitly set by an invocation to the set() - * method. - * - * @author Shawn Hatch - * - */ -public final class FloatValueContainer { - /* - * The array for storing the values - */ - private float[] values; - /* - * Holds the logical size of the values array based on the highest index - * used in invocations of setValue(). - */ - private int size; - /* - * The value returned for any non-negative index that has not been set via - * an invocation of setValue(). - */ - private float defaultValue; - - /* - * Grows the length of the values array to be the greater of the given - * capacity and 125% of its current length, filling empty elements in the - * array with the default value. - */ - private void grow(int capacity) { - int oldCapacity = values.length; - int newCapacity = Math.max(capacity, values.length + (values.length >> 2)); - values = Arrays.copyOf(values, newCapacity); - if (defaultValue != 0) { - for (int i = oldCapacity; i < newCapacity; i++) { - values[i] = defaultValue; - } - } - } - - /** - * Returns the value at index - * - * @param index - * @return - * @throws ContractException - *
          • {@linkplain PropertyError#NEGATIVE_INDEX} if index is - * negative
          • - * - */ - public float getValue(int index) { - if (index < 0) { - throw new ContractException(PropertyError.NEGATIVE_INDEX); - } - - float result; - if (index < values.length) { - result = values[index]; - } else { - result = defaultValue; - } - - return result; - } - - public int size() { - return size; - } - - /** - * Sets the capacity to the given capacity if the current capacity is less - * than the one given. - */ - public void setCapacity(int capacity) { - if (capacity > values.length) { - grow(capacity); - } - } - - /** - * Returns the capacity of this container. Capacity is guaranteed to be - * greater than or equal to size. - */ - public int getCapacity() { - return values.length; - } - - /** - * Returns the default value - * - */ - public float getDefaultValue() { - return defaultValue; - } - - /** - * Constructs the floatValueContainer with the given default value. - * - * @param defaultValue - */ - public FloatValueContainer(float defaultValue) { - this(defaultValue, 16); - } - - /** - * Constructs the floatValueContainer with the given default value and - * initial capacity - * - * @param defaultValue - * @param capacity - * - * @throws NegativeArraySizeException - * if the capacity is negative - */ - public FloatValueContainer(float defaultValue, int capacity) { - values = new float[capacity]; - if (defaultValue != 0) { - for (int i = 0; i < capacity; i++) { - values[i] = defaultValue; - } - } - this.defaultValue = defaultValue; - - } - - /** - * Sets the value at the index to the given value - * - * @throws ContractException - *
          • {@linkplain PropertyError#NEGATIVE_INDEX} if index is negative
          • - */ - public void setValue(int index, float value) { - if(index<0) { - throw new ContractException(PropertyError.NEGATIVE_INDEX); - } - if (index >= values.length) { - grow(index + 1); - } - if (index >= size) { - size = index + 1; - } - values[index] = value; - } - -} diff --git a/gcm3/src/main/java/plugins/util/properties/arraycontainers/IntValueContainer.java b/gcm3/src/main/java/plugins/util/properties/arraycontainers/IntValueContainer.java deleted file mode 100644 index 2cc4cf97e..000000000 --- a/gcm3/src/main/java/plugins/util/properties/arraycontainers/IntValueContainer.java +++ /dev/null @@ -1,840 +0,0 @@ -package plugins.util.properties.arraycontainers; - -import java.util.Arrays; - -import plugins.util.properties.PropertyError; -import util.errors.ContractException; - -/** - * A container that maps non-negative int index values to bytes, shorts, ints or - * longs. Values are stored internally as whatever int-type that logically - * represents the highest value in this container. For example, if this - * container contains the values [1,0,14,-20,300] then these would be stored in - * a short array since 300 exceeds the highest value of byte. - * - * - * @author Shawn Hatch - * - */ -public final class IntValueContainer { - - /** - * An enumeration representing the four int-based primitive data types in - * Java. It - * - */ - public static enum IntValueType { - - BYTE(Byte.MIN_VALUE, Byte.MAX_VALUE), SHORT(Short.MIN_VALUE, Short.MAX_VALUE), INT(Integer.MIN_VALUE, Integer.MAX_VALUE), LONG(Long.MIN_VALUE, Long.MAX_VALUE); - - private final long min; - - private final long max; - - /* - * Constructs the IntValueType with its min and max values - */ - private IntValueType(long min, long max) { - this.min = min; - this.max = max; - } - - /* - * Returns true if and only if this IntValueType represents an array - * whose min and max values inclusively bound the given value. - */ - private boolean isCompatibleValue(long value) { - return value >= min && value <= max; - } - - } - - /* - * Rebuilds the subTypeArray to be the most compact representation that is - * value-compatible with the given long. - * - */ - private SubTypeArray rebuildSubTypeArray(long value) { - if (IntValueType.SHORT.isCompatibleValue(value)) { - return new ShortArray(subTypeArray); - } - if (IntValueType.INT.isCompatibleValue(value)) { - return new IntArray(subTypeArray); - } - return new LongArray(subTypeArray); - } - - /* - * Common interface for the four array wrapper classes. - */ - private static interface SubTypeArray { - - public IntValueType getIntValueType(); - - public long getDefaultValue(); - - public long getValue(int index); - - public void setValue(int index, long value); - - public int size(); - - public void setCapacity(int capacity); - - public int getCapacity(); - - } - - /* - * SubTypeArray implementor for longs - */ - private static class LongArray implements SubTypeArray { - private long[] values; - private long defaultValue; - private int size; - - public LongArray(int capacity, long defaultValue) { - values = new long[capacity]; - if (defaultValue != 0) { - for (int i = 0; i < capacity; i++) { - values[i] = defaultValue; - } - } - size = values.length; - this.defaultValue = defaultValue; - } - - public LongArray(SubTypeArray subTypeArray) { - this.defaultValue = subTypeArray.getDefaultValue(); - values = new long[subTypeArray.size()]; - for (int i = 0; i < values.length; i++) { - values[i] = (int) subTypeArray.getValue(i); - } - size = values.length; - } - - private void grow(int capacity) { - int oldCapacity = values.length; - int newCapacity = Math.max(capacity, values.length + (values.length >> 2)); - values = Arrays.copyOf(values, newCapacity); - if (defaultValue != 0) { - for (int i = oldCapacity; i < newCapacity; i++) { - values[i] = defaultValue; - } - } - } - - @Override - public long getValue(int index) { - return values[index]; - } - - @Override - public void setValue(int index, long value) { - if (index >= values.length) { - grow(index + 1); - } - if (index >= size) { - size = index + 1; - } - - values[index] = value; - - } - - @Override - public int size() { - return size; - } - - @Override - public IntValueType getIntValueType() { - return IntValueType.LONG; - } - - @Override - public void setCapacity(int capacity) { - if (capacity > values.length) { - grow(capacity); - } - } - - @Override - public long getDefaultValue() { - return defaultValue; - } - - @Override - public int getCapacity() { - return values.length; - } - - } - /* - * SubTypeArray implementor for ints - */ - - private static class IntArray implements SubTypeArray { - private int[] values; - private int defaultValue; - private int size; - - public IntArray(int capacity, int defaultValue) { - values = new int[capacity]; - if (defaultValue != 0) { - for (int i = 0; i < capacity; i++) { - values[i] = defaultValue; - } - } - size = values.length; - this.defaultValue = defaultValue; - } - - public IntArray(SubTypeArray subTypeArray) { - this.defaultValue = (int) subTypeArray.getDefaultValue(); - values = new int[subTypeArray.size()]; - for (int i = 0; i < values.length; i++) { - values[i] = (int) subTypeArray.getValue(i); - } - size = values.length; - } - - private void grow(int capacity) { - int oldCapacity = values.length; - int newCapacity = Math.max(capacity, values.length + (values.length >> 2)); - values = Arrays.copyOf(values, newCapacity); - if (defaultValue != 0) { - for (int i = oldCapacity; i < newCapacity; i++) { - values[i] = defaultValue; - } - } - } - - @Override - public long getValue(int index) { - return values[index]; - } - - @Override - public void setValue(int index, long value) { - if (index >= values.length) { - grow(index + 1); - } - if (index >= size) { - size = index + 1; - } - - values[index] = (int) value; - } - - @Override - public int size() { - return size; - } - - @Override - public IntValueType getIntValueType() { - return IntValueType.INT; - } - - @Override - public void setCapacity(int capacity) { - if (capacity > values.length) { - grow(capacity); - } - } - - @Override - public long getDefaultValue() { - return defaultValue; - } - - @Override - public int getCapacity() { - return values.length; - } - - } - - /* - * SubTypeArray implementor for shorts - */ - private static class ShortArray implements SubTypeArray { - private short[] values; - private short defaultValue; - private int size; - - public ShortArray(int capacity, short defaultValue) { - values = new short[capacity]; - if (defaultValue != 0) { - for (int i = 0; i < capacity; i++) { - values[i] = defaultValue; - } - } - size = values.length; - this.defaultValue = defaultValue; - } - - public ShortArray(SubTypeArray subTypeArray) { - this.defaultValue = (short) subTypeArray.getDefaultValue(); - values = new short[subTypeArray.size()]; - for (int i = 0; i < values.length; i++) { - values[i] = (short) subTypeArray.getValue(i); - } - size = values.length; - } - - private void grow(int capacity) { - int oldCapacity = values.length; - int newCapacity = Math.max(capacity, values.length + (values.length >> 2)); - values = Arrays.copyOf(values, newCapacity); - if (defaultValue != 0) { - for (int i = oldCapacity; i < newCapacity; i++) { - values[i] = defaultValue; - } - } - } - - @Override - public long getValue(int index) { - return values[index]; - } - - @Override - public void setValue(int index, long value) { - if (index >= values.length) { - grow(index + 1); - } - if (index >= size) { - size = index + 1; - } - - values[index] = (short) value; - } - - @Override - public int size() { - return size; - } - - @Override - public IntValueType getIntValueType() { - return IntValueType.SHORT; - } - - @Override - public void setCapacity(int capacity) { - if (capacity > values.length) { - grow(capacity); - } - } - - @Override - public long getDefaultValue() { - return defaultValue; - } - - @Override - public int getCapacity() { - return values.length; - } - - } - - /* - * SubTypeArray implementor for bytes - */ - private static class ByteArray implements SubTypeArray { - private byte[] values; - private byte defaultValue; - private int size; - - public ByteArray(int capacity, byte defaultValue) { - values = new byte[capacity]; - if (defaultValue != 0) { - for (int i = 0; i < capacity; i++) { - values[i] = defaultValue; - } - } - this.defaultValue = defaultValue; - } - - @Override - public long getValue(int index) { - return values[index]; - } - - @Override - public void setValue(int index, long value) { - if (index >= values.length) { - grow(index + 1); - } - if (index >= size) { - size = index + 1; - } - values[index] = (byte) value; - } - - private void grow(int capacity) { - int oldCapacity = values.length; - int newCapacity = Math.max(capacity, values.length + (values.length >> 2)); - values = Arrays.copyOf(values, newCapacity); - if (defaultValue != 0) { - for (int i = oldCapacity; i < newCapacity; i++) { - values[i] = defaultValue; - } - } - } - - @Override - public int size() { - return size; - } - - @Override - public IntValueType getIntValueType() { - return IntValueType.BYTE; - } - - @Override - public void setCapacity(int capacity) { - if (capacity > values.length) { - grow(capacity); - } - } - - @Override - public long getDefaultValue() { - return defaultValue; - } - - @Override - public int getCapacity() { - return values.length; - } - - } - - /* - * The array holding instance. - */ - private SubTypeArray subTypeArray; - - /** - * Constructs the IntValueContainer with the given default value. - * - * @param defaultValue - */ - public IntValueContainer(long defaultValue) { - this(defaultValue, 16); - } - - /** - * Returns the default value as a byte. - * - * @throws RuntimeException - * if the default value is not compatible with byte - * - */ - public byte getDefaultValueAsByte() { - long result = subTypeArray.getDefaultValue(); - if (!IntValueType.BYTE.isCompatibleValue(result)) { - throw new RuntimeException("incompatible value found " + result); - } - return (byte) result; - } - - /** - * Returns the default value as a short. - * - * @throws RuntimeException - * if the default value is not compatible with short - * - */ - public short getDefaultValueAsShort() { - long result = subTypeArray.getDefaultValue(); - if (!IntValueType.SHORT.isCompatibleValue(result)) { - throw new RuntimeException("incompatible value found " + result); - } - return (short) result; - } - - /** - * Returns the default value as an int. - * - * @throws RuntimeException - * if the default value is not compatible with int - * - */ - public int getDefaultValueAsInt() { - long result = subTypeArray.getDefaultValue(); - if (!IntValueType.INT.isCompatibleValue(result)) { - throw new RuntimeException("incompatible value found " + result); - } - return (int) result; - } - - /** - * Returns the default value as a long. - */ - public long getDefaultValueAsLong() { - long result = subTypeArray.getDefaultValue(); - return result; - } - - /** - * Constructs the IntValueContainer with the given default value and initial - * capacity - * - * @param defaultValue - * @param capacity - * - * @throws NegativeArraySizeException - * if the capacity is negative - */ - public IntValueContainer(long defaultValue, int capacity) { - - if (IntValueType.BYTE.isCompatibleValue(defaultValue)) { - subTypeArray = new ByteArray(capacity, (byte) defaultValue); - } else if (IntValueType.SHORT.isCompatibleValue(defaultValue)) { - subTypeArray = new ShortArray(capacity, (short) defaultValue); - } else if (IntValueType.INT.isCompatibleValue(defaultValue)) { - subTypeArray = new IntArray(capacity, (int) defaultValue); - } else { - subTypeArray = new LongArray(capacity, defaultValue); - } - } - - /** - * Sets the capacity to the given capacity if the current capacity is less - * than the one given. - */ - public void setCapacity(int capacity) { - subTypeArray.setCapacity(capacity); - } - - /** - * Returns the value at index as a byte. - * - * @param index - * @return - * @throws ContractException - *
          • {@linkplain PropertyError#NEGATIVE_INDEX} if index is negative
          • - * - * @throws RuntimeException - *
          • if index < 0 - *
          • if the value to return is not compatible with byte - * - */ - public byte getValueAsByte(int index) { - if(index<0) { - throw new ContractException(PropertyError.NEGATIVE_INDEX); - } - long result; - if (index < subTypeArray.size()) { - result = subTypeArray.getValue(index); - } else { - result = subTypeArray.getDefaultValue(); - } - if (!IntValueType.BYTE.isCompatibleValue(result)) { - throw new RuntimeException("incompatible value found " + result); - } - return (byte) result; - } - - /** - * Returns the value at index as a short. - * - * @param index - * @return - * - * @throws ContractException - *
          • {@linkplain PropertyError#NEGATIVE_INDEX} if index is negative
          • - * @throws RuntimeException - *
          • if index < 0 - *
          • if the value to return is not compatible with short - * - */ - public short getValueAsShort(int index) { - if(index<0) { - throw new ContractException(PropertyError.NEGATIVE_INDEX); - } - long result; - if (index < subTypeArray.size()) { - result = subTypeArray.getValue(index); - } else { - result = subTypeArray.getDefaultValue(); - } - if (!IntValueType.SHORT.isCompatibleValue(result)) { - throw new RuntimeException("incompatible value found " + result); - } - return (short) result; - - } - - /** - * Returns the value at index as a long. - * - * @param index - * @return - * @throws ContractException - *
          • {@linkplain PropertyError#NEGATIVE_INDEX} if index is negative
          • - * @throws RuntimeException - *
          • if the value to return is not compatible with long - * - */ - public int getValueAsInt(int index) { - long result; - if (index < subTypeArray.size()) { - result = subTypeArray.getValue(index); - } else { - result = subTypeArray.getDefaultValue(); - } - if (!IntValueType.INT.isCompatibleValue(result)) { - throw new RuntimeException("incompatible value found " + result + " at index " + index); - } - return (int) result; - } - - /** - * Returns the value at index as a long. - * - * @param index - * @return - * @throws ContractException - *
          • {@linkplain PropertyError#NEGATIVE_INDEX} if index is negative
          • - * - */ - public long getValueAsLong(int index) { - if(index<0) { - throw new ContractException(PropertyError.NEGATIVE_INDEX); - } - long result; - if (index < subTypeArray.size()) { - result = subTypeArray.getValue(index); - } else { - result = subTypeArray.getDefaultValue(); - } - - return result; - } - - /** - * Sets the value at the index to the given byte - * - * @throws ContractException - *
          • {@linkplain PropertyError#NEGATIVE_INDEX} if index is negative
          • - * - */ - public void setByteValue(int index, byte value) { - if(index<0) { - throw new ContractException(PropertyError.NEGATIVE_INDEX); - } - subTypeArray.setValue(index, value); - } - - /** - * Sets the value at the index to the given short - * - * @throws ContractException - *
          • {@linkplain PropertyError#NEGATIVE_INDEX} if index is negative
          • - * - * - */ - public void setShortValue(int index, short value) { - if(index<0) { - throw new ContractException(PropertyError.NEGATIVE_INDEX); - } - if (!subTypeArray.getIntValueType().isCompatibleValue(value)) { - subTypeArray = rebuildSubTypeArray(value); - } - subTypeArray.setValue(index, value); - } - - /** - * Sets the value at the index to the given int - * - * @throws ContractException - *
          • {@linkplain PropertyError#NEGATIVE_INDEX} if index is negative
          • - * - * - */ - public void setIntValue(int index, int value) { - if(index<0) { - throw new ContractException(PropertyError.NEGATIVE_INDEX); - } - if (!subTypeArray.getIntValueType().isCompatibleValue(value)) { - subTypeArray = rebuildSubTypeArray(value); - } - subTypeArray.setValue(index, value); - } - - /** - * Sets the value at the index to the given long - * - * @throws ContractException - *
          • {@linkplain PropertyError#NEGATIVE_INDEX} if index is negative
          • - * - */ - public void setLongValue(int index, long value) { - if(index<0) { - throw new ContractException(PropertyError.NEGATIVE_INDEX); - } - if (!subTypeArray.getIntValueType().isCompatibleValue(value)) { - subTypeArray = rebuildSubTypeArray(value); - } - subTypeArray.setValue(index, value); - } - - /** - * Returns the size of this container, determined by the highest index for - * which a value assignment has occurred. - */ - public int size() { - return subTypeArray.size(); - } - - /** - * Returns the IntValueType for this container. Each IntValueType - * corresponds to the current implementation type of the underlying array of - * primitives. - */ - public IntValueType getIntValueType() { - return subTypeArray.getIntValueType(); - } - - /** - * Returns the capacity of this container. Capacity is guaranteed to be - * greater than or equal to size. - */ - public int getCapacity() { - return subTypeArray.getCapacity(); - } - - /** - * Increments the value at the index by the given byte - * - * @throws ContractException - *
          • {@linkplain PropertyError#NEGATIVE_INDEX} if index is negative
          • - * - */ - public void incrementByteValue(int index, byte value) { - if(index<0) { - throw new ContractException(PropertyError.NEGATIVE_INDEX); - } - - long incrementedValue = Math.addExact(getValueAsLong(index), value); - setLongValue(index, incrementedValue); - } - - /** - * Increments the value at the index by the given short - * - * @throws ContractException - *
          • {@linkplain PropertyError#NEGATIVE_INDEX} if index is negative
          • - * - */ - public void incrementShortValue(int index, short value) { - if(index<0) { - throw new ContractException(PropertyError.NEGATIVE_INDEX); - } - long incrementedValue = Math.addExact(getValueAsLong(index), value); - setLongValue(index, incrementedValue); - } - - /** - * Increments the value at the index by the given int - * - * @throws ContractException - *
          • {@linkplain PropertyError#NEGATIVE_INDEX} if index is negative
          • - * - */ - public void incrementIntValue(int index, int value) { - if(index<0) { - throw new ContractException(PropertyError.NEGATIVE_INDEX); - } - long incrementedValue = Math.addExact(getValueAsLong(index), value); - setLongValue(index, incrementedValue); - } - - /** - * Increments the value at the index by the given long - * - * @throws ContractException - *
          • {@linkplain PropertyError#NEGATIVE_INDEX} if index is negative
          • - * - */ - public void incrementLongValue(int index, long value) { - if(index<0) { - throw new ContractException(PropertyError.NEGATIVE_INDEX); - } - long incrementedValue = Math.addExact(getValueAsLong(index), value); - setLongValue(index, incrementedValue); - } - - /** - * Decrements the value at the index by the given byte - * - * @throws ContractException - *
          • {@linkplain PropertyError#NEGATIVE_INDEX} if index is negative
          • - * - */ - public void decrementByteValue(int index, byte value) { - if(index<0) { - throw new ContractException(PropertyError.NEGATIVE_INDEX); - } - - long decrementedValue = Math.subtractExact(getValueAsLong(index), value); - setLongValue(index, decrementedValue); - } - - /** - * Decrements the value at the index by the given short - * - * @throws ContractException - *
          • {@linkplain PropertyError#NEGATIVE_INDEX} if index is negative
          • - * - */ - public void decrementShortValue(int index, short value) { - if(index<0) { - throw new ContractException(PropertyError.NEGATIVE_INDEX); - } - long decrementedValue = Math.subtractExact(getValueAsLong(index), value); - setLongValue(index, decrementedValue); - } - - /** - * Decrements the value at the index by the given int - * - * @throws ContractException - *
          • {@linkplain PropertyError#NEGATIVE_INDEX} if index is negative
          • - * - */ - public void decrementIntValue(int index, int value) { - if(index<0) { - throw new ContractException(PropertyError.NEGATIVE_INDEX); - } - long decrementedValue = Math.subtractExact(getValueAsLong(index), value); - setLongValue(index, decrementedValue); - } - - /** - * Decrements the value at the index by the given long - * - * @throws ContractException - *
          • {@linkplain PropertyError#NEGATIVE_INDEX} if index is negative
          • - * - * @throws RuntimeException - *
          • if index is negative
          • - *
          • if the value causes an overflow
          • - * - */ - public void decrementLongValue(int index, long value) { - if(index<0) { - throw new ContractException(PropertyError.NEGATIVE_INDEX); - } - long decrementedValue = Math.subtractExact(getValueAsLong(index), value); - setLongValue(index, decrementedValue); - } - -} diff --git a/gcm3/src/main/java/plugins/util/properties/arraycontainers/ObjectValueContainer.java b/gcm3/src/main/java/plugins/util/properties/arraycontainers/ObjectValueContainer.java deleted file mode 100644 index 882389754..000000000 --- a/gcm3/src/main/java/plugins/util/properties/arraycontainers/ObjectValueContainer.java +++ /dev/null @@ -1,103 +0,0 @@ -package plugins.util.properties.arraycontainers; - -import java.util.Arrays; - -/** - * An array-based container for Objects that associates non-negative int indices - * with Objects and returns a default value when no value has been previously - * set for a particular index. This allows this container to return the default - * value for indices outside of the range of the internal array. - * - * @author Shawn Hatch - * - */ -public final class ObjectValueContainer { - - private Object[] elements; - - private final Object defaultValue; - - /** - * Constructs a new ObjectValueContainer with the given default value and - * initial capacity. The default value may be null. - * - * @throws IllegalArgumentException - *
          • if capacity is negative - */ - public ObjectValueContainer(Object defaultValue, int capacity) { - if (capacity < 0) { - throw new IllegalArgumentException("negative capacity: " + capacity); - } - elements = new Object[capacity]; - this.defaultValue = defaultValue; - if (defaultValue != null) { - for (int i = 0; i < capacity; i++) { - elements[i] = defaultValue; - } - } - } - - /** - * Sets the value at the index. - * - * @throws IllegalArgumentException - * if the index is negative - * - */ - public void setValue(int index, Object value) { - if (index < 0) { - throw new IllegalArgumentException("negative index: " + index); - } - if (index >= elements.length) { - grow(index + 1); - } - elements[index] = value; - } - - /** - * Returns the current capacity of this container - */ - public int getCapacity() { - return elements.length; - } - - public void setCapacity(int capacity) { - if(capacity>elements.length) { - grow(capacity); - } - } - - /* - * Grows the capacity of the elements array by at least 1/4 or to the new - * desired capacity, whichever is larger. The empty indices of the array are - * filled with the default value. - */ - private void grow(int capacity) { - int oldCapacity = elements.length; - int newCapacity = Math.max(capacity, elements.length + (elements.length >> 2)); - elements = Arrays.copyOf(elements, newCapacity); - if (defaultValue != null) { - for (int i = oldCapacity; i < newCapacity; i++) { - elements[i] = defaultValue; - } - } - } - - /** - * Returns the Object value associated with the given index. If no object - * value has been associated with the index, returns the default value. - * - * @param index - * @return - */ - @SuppressWarnings("unchecked") - public T getValue(int index) { - if (index < 0) { - throw new IllegalArgumentException("negative index: " + index); - } - if (index >= elements.length) { - return (T) defaultValue; - } - return (T) elements[index]; - } -} \ No newline at end of file diff --git a/gcm3/src/main/java/util/delaunay/GeoDelaunaySolver.java b/gcm3/src/main/java/util/delaunay/GeoDelaunaySolver.java deleted file mode 100644 index 70903be21..000000000 --- a/gcm3/src/main/java/util/delaunay/GeoDelaunaySolver.java +++ /dev/null @@ -1,445 +0,0 @@ -package util.delaunay; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import org.apache.commons.math3.util.FastMath; -import org.apache.commons.math3.util.Pair; - -import util.dimensiontree.VolumetricDimensionTree; -import util.earth.LatLon; -import util.vector.MutableVector3D; -import util.vector.Vector3D; - -public class GeoDelaunaySolver { - - /* - * Utility record used by the spiralizer algorithm - */ - private static class SpiralizerRec implements Comparable> { - T t; - Vector3D v; - double angleToCentroid; - double azimuthAngle; - int step; - - @Override - public int compareTo(SpiralizerRec other) { - int result = Integer.compare(step, other.step); - if (result == 0) { - result = Double.compare(azimuthAngle, other.azimuthAngle); - } - return result; - } - } - - private List spiralize(Map itemLocationMap) { - - SpiralizerRec centroid = new SpiralizerRec<>(); - MutableVector3D centroidVector = new MutableVector3D(); - List> list = new ArrayList<>(); - for (T t : itemLocationMap.keySet()) { - SpiralizerRec rec = new SpiralizerRec<>(); - rec.t = t; - LatLon latLon = itemLocationMap.get(t); - rec.v = getPositionFromGeoCoordinate(latLon); - list.add(rec); - centroidVector.add(rec.v); - } - - centroidVector.normalize(); - centroid.v = new Vector3D(centroidVector); - - MutableVector3D north = new MutableVector3D(0, 0, 1); - MutableVector3D northTangent = new MutableVector3D(centroid.v); - northTangent.rotateToward(north, FastMath.PI / 2); - - double maxGroundRange = Double.NEGATIVE_INFINITY; - for (SpiralizerRec rec : list) { - rec.angleToCentroid = centroid.v.angle(rec.v); - MutableVector3D tangent = new MutableVector3D(centroid.v); - tangent.rotateToward(rec.v, FastMath.PI / 2); - rec.azimuthAngle = northTangent.angle(tangent); - tangent.cross(northTangent); - if (tangent.dot(centroid.v) < 0) { - rec.azimuthAngle *= -1; - } - maxGroundRange = FastMath.max(maxGroundRange, rec.angleToCentroid); - } - - double area = 2 * FastMath.PI * (1 - FastMath.cos(maxGroundRange)); - - double stepDistance = FastMath.sqrt(area / list.size()); - for (SpiralizerRec rec : list) { - rec.step = (int) (rec.angleToCentroid / stepDistance); - } - - Collections.sort(list); - - List result = new ArrayList<>(); - for (SpiralizerRec rec : list) { - result.add(rec.t); - } - return result; - } - - private static class Edge { - int[] vertexIds; - boolean markedForRemoval; - - public Edge(final int[] vertexIds) { - this.vertexIds = vertexIds; - } - - @Override - public boolean equals(final Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (!(obj instanceof Edge)) { - return false; - } - final Edge other = (Edge) obj; - if (!Arrays.equals(vertexIds, other.vertexIds)) { - return false; - } - return true; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = (prime * result) + Arrays.hashCode(vertexIds); - return result; - } - - } - - private static class Triangle { - private double radius; - - public Triangle(double radius) { - this.radius = radius; - } - - private boolean markedForRemoval; - } - - private static class Vertex { - int id; - Vector3D position; - T t; - - public Vertex(final int id, final Vector3D position, T geoCoordinate) { - super(); - this.id = id; - this.position = position; - this.t = geoCoordinate; - } - } - - public static List> solve(Map itemLocationMap) { - return new GeoDelaunaySolver<>(itemLocationMap).solve(); - } - - private final int scaffoldCount = 3; - - private Map itemLocationMap; - - private final Map> triangleToEdgeMap = new LinkedHashMap<>(); - - private final Map> edgeToTriangleMap = new LinkedHashMap<>(); - - private final List> vertexes = new ArrayList<>(); - - private VolumetricDimensionTree searchTree; - - private GeoDelaunaySolver(Map itemLocationMap) { - this.itemLocationMap = itemLocationMap; - } - - private void addTriangle(final int id1, final int id2, final int id3) { - - final int[] ids = new int[3]; - - ids[0] = id1; - ids[1] = id2; - ids[2] = id3; - - Arrays.sort(ids); - - final Vector3D v0 = vertexes.get(ids[0]).position; - final Vector3D v1 = vertexes.get(ids[1]).position; - final Vector3D v2 = vertexes.get(ids[2]).position; - - final Vector3D perp = new Vector3D(v0).cross(v1); - final boolean leftHanded = perp.dot(v2) < 0; - - Vector3D midPoint = new Vector3D(v0).add(v1); - - final MutableVector3D c = new MutableVector3D(v0); - c.cross(v1); - c.cross(midPoint); - - midPoint = new Vector3D(v1).add(v2); - final MutableVector3D d = new MutableVector3D(v1); - d.cross(v2); - d.cross(midPoint); - - c.cross(d); - c.normalize(); - - if (leftHanded) { - c.reverse(); - } - final double radius = c.distanceTo(v0); - - final Triangle triangle = new Triangle(radius); - - int[] edgeIds = new int[2]; - edgeIds[0] = ids[0]; - edgeIds[1] = ids[1]; - final Edge edge1 = new Edge(edgeIds); - List list = edgeToTriangleMap.get(edge1); - if (list == null) { - list = new ArrayList<>(); - edgeToTriangleMap.put(edge1, list); - } - list.add(triangle); - - edgeIds = new int[2]; - edgeIds[0] = ids[1]; - edgeIds[1] = ids[2]; - final Edge edge2 = new Edge(edgeIds); - list = edgeToTriangleMap.get(edge2); - if (list == null) { - list = new ArrayList<>(); - edgeToTriangleMap.put(edge2, list); - } - list.add(triangle); - - edgeIds = new int[2]; - edgeIds[0] = ids[0]; - edgeIds[1] = ids[2]; - final Edge edge3 = new Edge(edgeIds); - list = edgeToTriangleMap.get(edge3); - if (list == null) { - list = new ArrayList<>(); - edgeToTriangleMap.put(edge3, list); - } - list.add(triangle); - final List edgeList = new ArrayList<>(); - edgeList.add(edge1); - edgeList.add(edge2); - edgeList.add(edge3); - triangleToEdgeMap.put(triangle, edgeList); - searchTree.add(c.toArray(), radius, triangle); - } - - private void addTriangles(final Vertex vertex, final List hullEdges) { - for (final Edge edge : hullEdges) { - addTriangle(edge.vertexIds[0], edge.vertexIds[1], vertex.id); - } - } - - private List getEdgesToRemove(final List trianglesToRemove) { - final List result = new ArrayList<>(); - for (final Triangle triangleToRemove : trianglesToRemove) { - final List edges = triangleToEdgeMap.get(triangleToRemove); - for (final Edge edge : edges) { - final List triangles = edgeToTriangleMap.get(edge); - int triangleRemovalCount = 0; - for (final Triangle triangle : triangles) { - if (triangle.markedForRemoval) { - triangleRemovalCount++; - } - } - if (triangleRemovalCount == 2) { - if (!edge.markedForRemoval) { - edge.markedForRemoval = true; - result.add(edge); - } - } - } - } - return result; - } - - private List getHullEdges(final List trianglesToRemove) { - final List result = new ArrayList<>(); - for (final Triangle badTriangle : trianglesToRemove) { - final List edges = triangleToEdgeMap.get(badTriangle); - for (final Edge edge : edges) { - if (!edge.markedForRemoval) { - result.add(edge); - } - } - } - return result; - } - - private List getTrianglesStruckByVertex(final Vertex vertex) { - final List result = new ArrayList<>(); - searchTree.getMembersInSphere(0, vertex.position.toArray()).forEach(triangle -> { - triangle.markedForRemoval = true; - result.add(triangle); - }); - return result; - } - - private Vector3D getPositionFromGeoCoordinate(LatLon latLon) { - double coslat = FastMath.cos(FastMath.toRadians(latLon.getLatitude())); - double coslon = FastMath.cos(FastMath.toRadians(latLon.getLongitude())); - double sinlat = FastMath.sin(FastMath.toRadians(latLon.getLatitude())); - double sinlon = FastMath.sin(FastMath.toRadians(latLon.getLongitude())); - return new Vector3D(coslat * coslon, coslat * sinlon, sinlat); - } - - private void initialize() { - List points = spiralize(itemLocationMap); - - /* - * Add three vertexes for the boundary - */ - for (int i = 0; i < scaffoldCount; i++) { - vertexes.add(new Vertex<>(i, new Vector3D(), null)); - } - - /* - * Add vertexes for each point in the model and calculate the centroid - * of those points - */ - final MutableVector3D centroid = new MutableVector3D(); - int n = points.size(); - for (int i = 0; i < n; i++) { - T t = points.get(i); - LatLon latLon = itemLocationMap.get(t); - final Vector3D v = getPositionFromGeoCoordinate(latLon); - centroid.add(v); - vertexes.add(new Vertex<>(i + scaffoldCount, v, t)); - } - centroid.normalize(); - - double minDotProduct = Double.POSITIVE_INFINITY; - - /* - * Find the maximum angle to any point in the data from the centroid -- - * we use dot products to avoid the acos costs - * - */ - for (int i = scaffoldCount; i < vertexes.size(); i++) { - final Vertex vertex = vertexes.get(i); - minDotProduct = FastMath.min(minDotProduct, centroid.dot(vertex.position)); - } - double maxAngle = FastMath.acos(minDotProduct); - - // if the maxAngle to too close to PI, it could result in an ambiguous - // winding. - if (FastMath.abs(maxAngle - FastMath.PI) < 0.001) { - maxAngle = FastMath.PI + 0.001; - } - - // Push the angle a bit out toward PI since at least one of the data - // point could lie inside one of the tangent planes - - maxAngle += (FastMath.PI - maxAngle) / 100; - - final Vector3D north = new Vector3D(0, 0, 1); - - final List tangentPlaneNormals = new ArrayList<>(); - - /* - * Form the vectors that will be perpendicular to the three planes that - * are tangent to the circle formed by the max angle - * - */ - for (int i = 0; i < scaffoldCount; i++) { - final MutableVector3D v = new MutableVector3D(centroid); - v.rotateToward(north, maxAngle - (FastMath.PI / 2)); - v.rotateAbout(centroid, (2 * i * FastMath.PI) / 3); - tangentPlaneNormals.add(v); - } - - /* - * Find the three intersections of these plane in a right handed - * fashion. The resulting triangle will be right handed if the triangle - * is smaller than a hemisphere and left handed otherwise. Either way, - * the triangle will have all of the data points on its inside. - */ - for (int i = 0; i < scaffoldCount; i++) { - final MutableVector3D v0 = tangentPlaneNormals.get(i); - final MutableVector3D v1 = tangentPlaneNormals.get((i + 1) % 3); - final MutableVector3D scaffoldPoint = new MutableVector3D(v0); - scaffoldPoint.cross(v1); - scaffoldPoint.normalize(); - - Vertex vertex = vertexes.get(i); - vertex.position = new Vector3D(scaffoldPoint); - } - - /* - * We choose the bounds that will fit the unit sphere since we cannot - * easily anticipate the centroid position of each triangle, despite - * knowing all the vertex positions. This will help the tree keep its - * depth to a minimum. - */ - searchTree = VolumetricDimensionTree.builder()// - .setFastRemovals(true)// - .setLowerBounds(new double[] { -1, -1, -1 })// - .setUpperBounds(new double[] { 1, 1, 1 })// - .build();// - - addTriangle(0, 1, 2);// - } - - private void removeEdges(final List edgesToRemove) { - for (final Edge edge : edgesToRemove) { - edgeToTriangleMap.remove(edge); - } - } - - private void removeTriangles(final List trianglesToRemove) { - trianglesToRemove.forEach(triangle -> { - triangleToEdgeMap.remove(triangle).forEach(edge -> { - edgeToTriangleMap.get(edge).remove(triangle); - }); - searchTree.remove(triangle.radius, triangle); - }); - - } - - private List> solve() { - initialize(); - - for (int i = scaffoldCount; i < vertexes.size(); i++) { - final Vertex vertex = vertexes.get(i); - final List trianglesToRemove = getTrianglesStruckByVertex(vertex); - final List edgesToRemove = getEdgesToRemove(trianglesToRemove); - final List hullEdges = getHullEdges(trianglesToRemove); - removeTriangles(trianglesToRemove); - removeEdges(edgesToRemove); - addTriangles(vertex, hullEdges); - } - - List> result = new ArrayList<>(); - edgeToTriangleMap.keySet().forEach(edge -> { - Vertex vertex0 = vertexes.get(edge.vertexIds[0]); - if (vertex0.t != null) { - Vertex vertex1 = vertexes.get(edge.vertexIds[1]); - Pair pair = new Pair<>(vertex0.t, vertex1.t); - result.add(pair); - } - }); - - return result; - } - -} diff --git a/gcm3/src/main/java/util/delaunay/PlanarDelaunaySolver.java b/gcm3/src/main/java/util/delaunay/PlanarDelaunaySolver.java deleted file mode 100644 index 228e2dda6..000000000 --- a/gcm3/src/main/java/util/delaunay/PlanarDelaunaySolver.java +++ /dev/null @@ -1,398 +0,0 @@ -package util.delaunay; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import org.apache.commons.math3.util.FastMath; -import org.apache.commons.math3.util.Pair; - -import util.dimensiontree.VolumetricDimensionTree; -import util.spherical.Chirality; -import util.vector.MutableVector2D; -import util.vector.Vector2D; - -public class PlanarDelaunaySolver { - - private static class Rec implements Comparable> { - T t; - MutableVector2D v; - double distanceToCentroid; - double angle; - int step; - - @Override - public int compareTo(Rec other) { - int result = Integer.compare(step, other.step); - if (result == 0) { - result = Double.compare(angle, other.angle); - } - return result; - } - } - - private List spiralize(Map itemLocationMap) { - - MutableVector2D centroid = new MutableVector2D(); - - List> list = new ArrayList<>(); - for (T t : itemLocationMap.keySet()) { - Rec rec = new Rec<>(); - rec.t = t; - rec.v = new MutableVector2D(itemLocationMap.get(t)); - list.add(rec); - centroid.add(rec.v); - } - - centroid.scale(1.0 / list.size()); - - MutableVector2D xAxis = new MutableVector2D(1, 0); - double maxDistance = Double.NEGATIVE_INFINITY; - - MutableVector2D v = new MutableVector2D(); - for (Rec rec : list) { - rec.distanceToCentroid = centroid.distanceTo(rec.v); - v.assign(rec.v); - v.sub(centroid); - rec.angle = v.angle(xAxis) * v.cross(xAxis); - maxDistance = FastMath.max(maxDistance, rec.distanceToCentroid); - } - - double area = 2 * FastMath.PI * maxDistance * maxDistance; - double stepDistance = FastMath.sqrt(area / list.size()); - - for (Rec rec : list) { - rec.step = (int) (rec.distanceToCentroid / stepDistance); - } - - Collections.sort(list); - - List result = new ArrayList<>(); - for (Rec rec : list) { - result.add(rec.t); - } - return result; - } - - private static class Edge { - int[] vertexIds; - boolean markedForRemoval; - - public Edge(final int[] vertexIds) { - this.vertexIds = vertexIds; - } - - @Override - public boolean equals(final Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (!(obj instanceof Edge)) { - return false; - } - final Edge other = (Edge) obj; - if (!Arrays.equals(vertexIds, other.vertexIds)) { - return false; - } - return true; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = (prime * result) + Arrays.hashCode(vertexIds); - return result; - } - - } - - private static class Triangle { - private double radius; - - public Triangle(double radius) { - this.radius = radius; - } - - private boolean markedForRemoval; - } - - private static class Vertex { - int id; - MutableVector2D position; - T t; - - public Vertex(final int id, final MutableVector2D position, T t) { - super(); - this.id = id; - this.position = position; - this.t = t; - } - } - - public static List> solve(Map itemLocationMap) { - return new PlanarDelaunaySolver<>(itemLocationMap).solve(); - } - - private final int scaffoldCount = 4; - - private Map itemLocationMap; - - private final Map> triangleToEdgeMap = new LinkedHashMap<>(); - - private final Map> edgeToTriangleMap = new LinkedHashMap<>(); - - private final List> vertexes = new ArrayList<>(); - - private VolumetricDimensionTree searchTree; - - private PlanarDelaunaySolver(Map itemLocationMap) { - this.itemLocationMap = itemLocationMap; - } - - private void addTriangle(final int id1, final int id2, final int id3) { - int[] ids = new int[3]; - - ids[0] = id1; - ids[1] = id2; - ids[2] = id3; - - Arrays.sort(ids); - - MutableVector2D a = new MutableVector2D(vertexes.get(ids[0]).position); - MutableVector2D b = new MutableVector2D(vertexes.get(ids[1]).position); - MutableVector2D c = new MutableVector2D(vertexes.get(ids[2]).position); - - MutableVector2D m1 = new MutableVector2D(a); - m1.add(b); - m1.scale(0.5); - - MutableVector2D p1 = new MutableVector2D(a); - p1.sub(b); - p1.perpendicularRotation(Chirality.LEFT_HANDED); - - MutableVector2D m2 = new MutableVector2D(b); - m2.add(c); - m2.scale(0.5); - - MutableVector2D p2 = new MutableVector2D(b); - p2.sub(c); - p2.perpendicularRotation(Chirality.LEFT_HANDED); - - MutableVector2D q = new MutableVector2D(c); - q.sub(b); - - m2.sub(m1); - double j = m2.dot(q) / p1.dot(q); - - MutableVector2D center = new MutableVector2D(p1); - center.scale(j); - center.add(m1); - - double radius = center.distanceTo(a); - - Triangle triangle = new Triangle(radius); - - int[] edgeIds = new int[2]; - edgeIds[0] = ids[0]; - edgeIds[1] = ids[1]; - Edge edge1 = new Edge(edgeIds); - List list = edgeToTriangleMap.get(edge1); - if (list == null) { - list = new ArrayList<>(); - edgeToTriangleMap.put(edge1, list); - } - list.add(triangle); - - edgeIds = new int[2]; - edgeIds[0] = ids[1]; - edgeIds[1] = ids[2]; - Edge edge2 = new Edge(edgeIds); - list = edgeToTriangleMap.get(edge2); - if (list == null) { - list = new ArrayList<>(); - edgeToTriangleMap.put(edge2, list); - } - list.add(triangle); - - edgeIds = new int[2]; - edgeIds[0] = ids[0]; - edgeIds[1] = ids[2]; - Edge edge3 = new Edge(edgeIds); - list = edgeToTriangleMap.get(edge3); - if (list == null) { - list = new ArrayList<>(); - edgeToTriangleMap.put(edge3, list); - } - list.add(triangle); - - List edgeList = new ArrayList<>(); - edgeList.add(edge1); - edgeList.add(edge2); - edgeList.add(edge3); - triangleToEdgeMap.put(triangle, edgeList); - - searchTree.add(center.toArray(), triangle.radius, triangle); - } - - private void addTriangles(final Vertex vertex, final List hullEdges) { - for (final Edge edge : hullEdges) { - addTriangle(edge.vertexIds[0], edge.vertexIds[1], vertex.id); - } - } - - private List getEdgesToRemove(final List trianglesToRemove) { - final List result = new ArrayList<>(); - for (final Triangle triangleToRemove : trianglesToRemove) { - final List edges = triangleToEdgeMap.get(triangleToRemove); - for (final Edge edge : edges) { - final List triangles = edgeToTriangleMap.get(edge); - int triangleRemovalCount = 0; - for (final Triangle triangle : triangles) { - if (triangle.markedForRemoval) { - triangleRemovalCount++; - } - } - if (triangleRemovalCount == 2) { - if (!edge.markedForRemoval) { - edge.markedForRemoval = true; - result.add(edge); - } - } - } - } - return result; - } - - private List getHullEdges(final List trianglesToRemove) { - final List result = new ArrayList<>(); - for (final Triangle badTriangle : trianglesToRemove) { - final List edges = triangleToEdgeMap.get(badTriangle); - for (final Edge edge : edges) { - if (!edge.markedForRemoval) { - result.add(edge); - } - } - } - return result; - } - - private List getTrianglesStruckByVertex(final Vertex vertex) { - final List result = new ArrayList<>(); - searchTree.getMembersInSphere(0, vertex.position.toArray()).forEach(triangle -> { - triangle.markedForRemoval = true; - result.add(triangle); - }); - return result; - } - - private void initialize() { - List points = spiralize(itemLocationMap); - - double maxX = Double.NEGATIVE_INFINITY; - double minX = Double.POSITIVE_INFINITY; - double maxY = Double.NEGATIVE_INFINITY; - double minY = Double.POSITIVE_INFINITY; - - for (T t : itemLocationMap.keySet()) { - Vector2D v = itemLocationMap.get(t); - maxX = FastMath.max(maxX, v.getX()); - minX = FastMath.min(minX, v.getX()); - maxY = FastMath.max(maxY, v.getY()); - minY = FastMath.min(minY, v.getY()); - } - double[] lowerBounds = { minX, minY }; - double[] upperBounds = { maxX, maxY }; - - searchTree = VolumetricDimensionTree.builder()// - .setFastRemovals(true)// - .setLowerBounds(lowerBounds)// - .setUpperBounds(upperBounds)// - .build();// - - double pad = 0.01; - - double padX = (maxX - minX) * pad; - double padY = (maxY - minY) * pad; - - minX -= padX; - maxX += padX; - - minY -= padY; - maxY += padY; - - Vertex vertex0 = new Vertex<>(0, new MutableVector2D(minX, minY), null); - vertexes.add(vertex0); - - Vertex vertex1 = new Vertex<>(1, new MutableVector2D(minX, maxY), null); - vertexes.add(vertex1); - - Vertex vertex2 = new Vertex<>(2, new MutableVector2D(maxX, minY), null); - vertexes.add(vertex2); - - Vertex vertex3 = new Vertex<>(3, new MutableVector2D(maxX, maxY), null); - vertexes.add(vertex3); - - int n = points.size(); - for (int i = 0; i < n; i++) { - T t = points.get(i); - Vector2D v = itemLocationMap.get(t); - MutableVector2D m = new MutableVector2D(v); - vertexes.add(new Vertex<>(i + scaffoldCount, m, t)); - } - - addTriangle(vertex0.id, vertex1.id, vertex2.id); - - addTriangle(vertex1.id, vertex2.id, vertex3.id); - - } - - private void removeEdges(final List edgesToRemove) { - for (final Edge edge : edgesToRemove) { - edgeToTriangleMap.remove(edge); - } - } - - private void removeTriangles(final List trianglesToRemove) { - trianglesToRemove.forEach(triangle -> { - triangleToEdgeMap.remove(triangle).forEach(edge -> { - edgeToTriangleMap.get(edge).remove(triangle); - }); - searchTree.remove(triangle.radius, triangle); - }); - - } - - private List> solve() { - initialize(); - - for (int i = scaffoldCount; i < vertexes.size(); i++) { - final Vertex vertex = vertexes.get(i); - final List trianglesToRemove = getTrianglesStruckByVertex(vertex); - final List edgesToRemove = getEdgesToRemove(trianglesToRemove); - final List hullEdges = getHullEdges(trianglesToRemove); - removeTriangles(trianglesToRemove); - removeEdges(edgesToRemove); - addTriangles(vertex, hullEdges); - } - - List> result = new ArrayList<>(); - edgeToTriangleMap.keySet().forEach(edge -> { - Vertex vertex0 = vertexes.get(edge.vertexIds[0]); - if (vertex0.t != null) { - Vertex vertex1 = vertexes.get(edge.vertexIds[1]); - Pair pair = new Pair<>(vertex0.t, vertex1.t); - result.add(pair); - } - }); - - return result; - } - -} diff --git a/gcm3/src/main/java/util/dimensiontree/DimensionTree.java b/gcm3/src/main/java/util/dimensiontree/DimensionTree.java deleted file mode 100644 index 190c257f7..000000000 --- a/gcm3/src/main/java/util/dimensiontree/DimensionTree.java +++ /dev/null @@ -1,599 +0,0 @@ -package util.dimensiontree; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import util.dimensiontree.internal.CommonState; -import util.dimensiontree.internal.Group; -import util.dimensiontree.internal.NearestMemberQuery; -import util.dimensiontree.internal.Node; -import util.dimensiontree.internal.Rectanguloid; -import util.dimensiontree.internal.Shape; -import util.dimensiontree.internal.Sphere; -import util.errors.ContractException; - -/** - *

            - * Represents a multi-dimensional, generics-based, searchable tree giving - * generally log2(N) retrieval times for stored members. - *

            - * - *

            - * Positions in the tree are represented as arrays of double and the number of - * dimensions in the tree are fixed by its build parameters. The tree's span in - * the multi-dimensional space will contract and expand as needed and does so - * with generally efficient performance. The tree specifically allows for a - * single object to be stored at multiple locations and allows for multiple - * objects to be stored at a single location. - *

            - * - *

            - * The tree supports object retrieval by: - *

          • all objects in the tree
          • - *
          • the closest object to a given point
          • - *
          • spherical and rectanguloid intersection with the tree
          • - *

            - * - *

            - * The tree supports object removal in O(log2(N)) time whenever it is - * constructed with the fast removals option and O(N) without. Fast removals - * requires significantly more memory. - *

            - * - * @author Shawn Hatch - **/ - -public final class DimensionTree { - - private final static int DEFAULTLEAFSIZE = 15; - - public static Builder builder() { - return new Builder(); - } - - private static class Scaffold { - private double[] lowerBounds; - private double[] upperBounds; - private int leafSize = DEFAULTLEAFSIZE; - private boolean fastRemovals = false; - } - - public static class Builder { - - private Scaffold scaffold = new Scaffold(); - - private Builder() { - } - - /** - * Sets the fast removals policy. When fast removals is chosen, the tree - * will remove objects in near constant time. This requires significant - * memory resources and adds time to the storage and retrieval process. - * Without fast removals, the tree must remove objects in order O(N) - * time through exhaustive searching. - */ - public Builder setFastRemovals(boolean fastRemovals) { - scaffold.fastRemovals = fastRemovals; - return this; - } - - /** - * Sets the leaf size used to determine how many objects positions can - * be stored in any particular node of the tree. Setting the value to a - * high number such as 100 will slow down retrieval performance, but - * will reduce memory overhead. Low values, such as 1 will maximize - * memory use since this will cause more nodes to come into existence. - * Both high and low values can slow down the tree's performance when - * adding objects. Common practice is to set the value to - * DEFAULTLEAFSIZE (=15), which works well in most applications. - * Defaulted to 15. - */ - - public Builder setLeafSize(int leafSize) { - scaffold.leafSize = leafSize; - return this; - } - - /** - * Sets the initial lower bounds of the tree. While the tree will expand - * and contract as needed, it is often important to set the bounds to - * roughly the proper magnitude to avoid some initial performance slow - * downs. Lower bounds should not exceed upper bounds. - */ - public Builder setLowerBounds(double[] lowerBounds) { - scaffold.lowerBounds = Arrays.copyOf(lowerBounds, lowerBounds.length); - return this; - } - - /** - * Sets the initial upper bounds of the tree. While the tree will expand - * and contract as needed, it is often important to set the bounds to - * roughly the proper magnitude to avoid some initial performance slow - * downs. Lower bounds should not exceed upper bounds. - */ - public Builder setUpperBounds(double[] upperBounds) { - scaffold.upperBounds = Arrays.copyOf(upperBounds, upperBounds.length); - return this; - } - - /** - * Builds a {@link DimensionTree} from the contributed parameters. - * - * - * @throws ContractException - *
          • {@linkplain DimensionTreeError#NON_POSITIVE_LEAF_SIZE} - * if the selected leaf size is not positive
          • - *
          • {@linkplain DimensionTreeError#LOWER_BOUNDS_ARE_NULL} - * if the lower bounds were not contributed or were - * null
          • - *
          • {@linkplain DimensionTreeError#UPPER_BOUNDS_ARE_NULL} - * if the upper bounds were not contributed or were - * null
          • - *
          • {@linkplain DimensionTreeError#BOUNDS_MISMATCH} if - * the lower and upper bounds do not match in length
          • - *
          • {@linkplain DimensionTreeError#LOWER_BOUNDS_EXCEED_UPPER_BOUNDS} - * if any of the lower bounds exceed the corresponding upper - * bounds
          • - * - * - */ - public DimensionTree build() { - - try { - return new DimensionTree<>(scaffold); - } finally { - scaffold = new Scaffold(); - } - - } - } - - private CommonState commonState; - - private Node root = null; - - /* - * Hidden constructor - */ - private DimensionTree(Scaffold scaffold) { - - if (scaffold.leafSize < 1) { - throw new ContractException(DimensionTreeError.NON_POSITIVE_LEAF_SIZE); - } - - if (scaffold.lowerBounds == null) { - throw new ContractException(DimensionTreeError.LOWER_BOUNDS_ARE_NULL); - } - - if (scaffold.upperBounds == null) { - throw new ContractException(DimensionTreeError.UPPER_BOUNDS_ARE_NULL); - } - - int dimension = scaffold.lowerBounds.length; - - if (scaffold.upperBounds.length != dimension) { - throw new ContractException(DimensionTreeError.BOUNDS_MISMATCH); - } - - for (int i = 0; i < dimension; i++) { - if (scaffold.lowerBounds[i] > scaffold.upperBounds[i]) { - throw new ContractException(DimensionTreeError.LOWER_BOUNDS_EXCEED_UPPER_BOUNDS); - } - } - - commonState = new CommonState(scaffold.leafSize, dimension); - - root = new Node<>(commonState, scaffold.lowerBounds, scaffold.upperBounds); - - if (scaffold.fastRemovals) { - groupMap = new LinkedHashMap<>(); - } - } - - @SuppressWarnings("unchecked") - private void expandRootToFitPosition(double[] position) { - - while (true) { - boolean rootContainsPosition = true; - for (int i = 0; i < commonState.dimension; i++) { - if (root.upperBounds[i] < position[i]) { - rootContainsPosition = false; - break; - } - - if (root.lowerBounds[i] > position[i]) { - rootContainsPosition = false; - break; - } - } - - if (rootContainsPosition) { - break; - } - - int childIndex = 0; - double[] newRootLowerBounds = new double[commonState.dimension]; - double[] newRootUpperBounds = new double[commonState.dimension]; - for (int i = 0; i < commonState.dimension; i++) { - childIndex *= 2; - if (root.lowerBounds[i] > position[i]) { - newRootLowerBounds[i] = 2 * root.lowerBounds[i] - root.upperBounds[i]; - newRootUpperBounds[i] = root.upperBounds[i]; - childIndex += 1; - } else { - newRootUpperBounds[i] = 2 * root.upperBounds[i] - root.lowerBounds[i]; - newRootLowerBounds[i] = root.lowerBounds[i]; - } - } - root.parent = new Node<>(commonState, newRootLowerBounds, newRootUpperBounds); - root.parent.groupCount = root.groupCount; - root.parent.children = new Node[commonState.childCount]; - root.parent.children[childIndex] = root; - root.indexInParent = childIndex; - root = root.parent; - } - } - - /* - * Returns the child that should contain the position. It is assumed that - * the position is contained in this node's bounds. If there is no such - * child, one is created and placed in the appropriate slot of the children. - */ - private Node getChild(Node node, double[] position) { - int childIndex = node.getChildIndex(position); - Node child = node.children[childIndex]; - if (child != null) { - return child; - } - - int index = childIndex; - - double[] childLowerBounds = new double[commonState.dimension]; - double[] childUpperBounds = new double[commonState.dimension]; - for (int i = commonState.dimension - 1; i >= 0; i--) { - if (index % 2 == 1) { - childUpperBounds[i] = node.upperBounds[i]; - childLowerBounds[i] = (node.upperBounds[i] + node.lowerBounds[i]) * 0.5; - } else { - childUpperBounds[i] = (node.upperBounds[i] + node.lowerBounds[i]) * 0.5; - childLowerBounds[i] = node.lowerBounds[i]; - } - index /= 2; - } - - child = new Node<>(commonState, childLowerBounds, childUpperBounds); - node.children[childIndex] = child; - child.parent = node; - child.indexInParent = childIndex; - - return child; - } - - /* - * Rather than have the root node recursively push the new member into the - * tree, we elect to avoid stack loading to save some execution time. - */ - @SuppressWarnings("unchecked") - private Group deep_add(double[] position, T t) { - Group result = null; - Node node = root; - - mainloop: while (true) { - if (node.children == null) { - for (Group memberGroup : node.groups) { - if (memberGroup.canContain(position)) { - if (memberGroup.add(t, position)) { - result = memberGroup; - } - break mainloop; - } - } - - if (node.groups.size() < commonState.leafSize || !node.canFormChildren) { - Group memberGroup = new Group<>(); - memberGroup.node = node; - memberGroup.add(t, position); - node.groups.add(memberGroup); - node.groupCount++; - result = memberGroup; - break mainloop; - } - - // distribute groups into children - node.children = new Node[commonState.childCount]; - for (Group memberGroup : node.groups) { - Node child = getChild(node, memberGroup.position); - child.groupCount++; - child.groups.add(memberGroup); - memberGroup.node = child; - } - node.groups.clear(); - - node = getChild(node, position); - } else { - node = getChild(node, position); - } - } - - if (result != null) { - if (result.members.size() == 1) { - node = result.node; - while (node != null) { - node.groupCount++; - node = node.parent; - } - } - } - return result; - } - - /** - * Adds a member at the given position. Returns true if the member is not - * already associated with the position. Returns false otherwise. - * - * @throws RuntimeException - *
          • if the position is null - *
          • if the member is null - *
          • if the position does not match the dimension of this - * {@link DimensionTree} - */ - public boolean add(double[] position, T member) { - - if (position == null) { - // deception - throw new RuntimeException("null position"); - } - if (position.length != commonState.dimension) { - // deception - throw new RuntimeException("dimensional mismatch"); - } - if (member == null) { - // deception - throw new RuntimeException("null value being added"); - } - expandRootToFitPosition(position); - - Group group = deep_add(position, member); - - if (group != null && groupMap != null) { - List> list = groupMap.get(member); - if (list == null) { - list = new ArrayList<>(); - groupMap.put(member, list); - } - list.add(group); - } - return group != null; - } - - /** - * Return true if and only if the given member is contained in this - * {@link DimensionTree} - * - */ - public boolean contains(T member) { - if (groupMap != null) { - return groupMap.containsKey(member); - } - return root.containsMember(member); - } - - /** - * Returns the member nearest to the given position. - */ - public Optional getNearestMember(double[] position) { - if (position == null) { - // deception - throw new RuntimeException("null position"); - } - if (commonState.dimension != position.length) { - // deception - throw new RuntimeException("dimensional mismatch"); - } - - NearestMemberQuery nearestMemberData = new NearestMemberQuery<>(); - nearestMemberData.position = position; - root.getNearestMember(nearestMemberData); - return Optional.ofNullable(nearestMemberData.closestObject); - } - - /** - * Retrieves all of the objects stored in this {@link DimensionTree}. This - * may include duplicates if any object is stored in multiple locations. - * - */ - - public List getAll() { - List result = new ArrayList<>(); - if (groupMap != null) { - result.addAll(groupMap.keySet()); - } else { - root.getAllMembers(result); - } - return result; - } - - /** - * Retrieves all of the objects stored in the tree within the - * IDimensionalShape. The shape itself need not lie fully inside the tree's - * volume, but must be well formed and agree with the tree's dimension. - * - * @param dimensionalShape - * a non-null IDimensionalShape implementation - * @return an ArrayList of Object containing all unique objects within - * shape's intersection with the tree. - */ - private List getObjectsInDimensionalShape(Shape dimensionalShape) { - List result = new ArrayList<>(); - root.getObjectsInDimensionalShape(dimensionalShape, result); - return result; - } - - /** - * Retrieves all of the objects within the rectanguloid formed by the lower - * and upper bounds. This may include duplicates if any object is stored in - * multiple locations.The rectanguloid itself need not lie fully inside this - * tree's volume. The lengths of the lower and upper bound arrays must agree - * with the dimension of the tree. For each dimension, lowerBounds[i] must - * not exceed upperBounds[i]. - * - * - * @throws RuntimeException - *
          • if the lower bounds are null<\li> - *
          • if the upper bounds are null<\li> - *
          • if the length of the upper bounds does not match the - * dimension of this tree<\li> - *
          • if the length of the lower bounds does not match the - * dimension of this tree<\li> - *
          • if the values of the lower bounds exceed the - * corresponding values of the upper bounds<\li> - */ - - public List getMembersInRectanguloid(double[] lowerBounds, double[] upperBounds) { - if (lowerBounds == null) { - // deception - throw new RuntimeException("null lower bounds"); - } - if (upperBounds == null) { - // deception - throw new RuntimeException("null lower bounds"); - } - if (lowerBounds.length != this.commonState.dimension) { - // deception - throw new RuntimeException("lower bounds do not match dimension of tree"); - } - if (upperBounds.length != this.commonState.dimension) { - // deception - throw new RuntimeException("upper bounds do not match dimension of tree"); - } - for (int i = 0; i < upperBounds.length; i++) { - if (lowerBounds[i] > upperBounds[i]) { - // deception - throw new RuntimeException("lower bounds exceed upper bounds"); - } - } - return getObjectsInDimensionalShape(new Rectanguloid(lowerBounds, upperBounds)); - } - - /** - * Retrieves all of the objects stored in the tree within the radius - * distance about the position. This may include duplicates if any object is - * stored in multiple locations. The position itself need not lie inside - * this tree's volume. - * - * - * - * - * @throw {@link RuntimeException} - *
          • if the radius is negative<\li> - *
          • if the position is null<\li> - *
          • if the position's length does not match the dimension of this - * tree - * - */ - public List getMembersInSphere(double radius, double[] position) { - if (position == null) { - // deception - throw new RuntimeException("null position"); - } - if (this.commonState.dimension != position.length) { - // deception - throw new RuntimeException("dimensional mismatch"); - } - if (radius < 0) { - // deception - throw new RuntimeException("negative radius"); - } - return getObjectsInDimensionalShape(new Sphere(radius, position)); - - } - - private Map>> groupMap; - - /** - * Removes the given member from this {@link DimensionTree} at all locations - * associated with the member. Returns true if the member was contained. - */ - public boolean remove(T member) { - /* - * First, get the list of member groups that contain the member. This - * can come from the nodes via a brute force walk of the entire tree or - * from a map of T to List - */ - List> groups; - if (groupMap == null) { - groups = new ArrayList<>(); - root.retrieveGroupsForMember(groups, member); - } else { - groups = groupMap.remove(member); - } - - /* - * For each member group we will remove the member. For those member - * groups where the member group is now empty, we remove the member - * group from its node and cascade member group counts upward. As we - * move upward toward the root, we record each node that will need to - * collapse, replacing this reference as we move up. Any node that - * reaches a member count of zero will be removed from its parent. - * - * If we have a node that has been selected to collapse, we command the - * node to collapse. - * - * We next walk downward from root looking to move the root downward - * into the tree? - */ - - boolean result = groups != null && !groups.isEmpty(); - if (groups != null) { - for (Group group : groups) { - group.members.remove(member); - if (group.members.size() == 0) { - Node collapseNode = null; - Node node = group.node; - node.groups.remove(group); - while (node != null) { - // reduce the group count - node.groupCount--; - /* - * If the node is now empty and has a parent, remove it - * from its parent - */ - if (node.groupCount == 0) { - if (node.parent != null) { - node.parent.children[node.indexInParent] = null; - } - } - /* - * Don't select a node to collapse if it is being thrown - * out of the tree - */ - if (node.groupCount != 0 && node.groupCount <= commonState.leafSize) { - collapseNode = node; - } - node = node.parent; - } - if (collapseNode != null) { - List> groupsInCollapse = new ArrayList<>(); - collapseNode.retrieveGroups(groupsInCollapse); - collapseNode.groups = groupsInCollapse; - for (Group g : groupsInCollapse) { - g.node = collapseNode; - } - collapseNode.children = null; - } - } - } - } - return result; - } - - // @Override - // public String toString() { - // return root.toString(); - // } - -} \ No newline at end of file diff --git a/gcm3/src/main/java/util/dimensiontree/DimensionTreeError.java b/gcm3/src/main/java/util/dimensiontree/DimensionTreeError.java deleted file mode 100644 index e56c3aa5b..000000000 --- a/gcm3/src/main/java/util/dimensiontree/DimensionTreeError.java +++ /dev/null @@ -1,32 +0,0 @@ -package util.dimensiontree; - -import util.errors.ContractError; -import util.errors.ContractException; - -/** - * An enumeration supporting {@link ContractException} that acts as a general - * description of the exception. - * - * @author Shawn Hatch - * - */ -public enum DimensionTreeError implements ContractError { - - NON_POSITIVE_LEAF_SIZE("non-positive leaf size"), - LOWER_BOUNDS_ARE_NULL("lower bounds are null"), - UPPER_BOUNDS_ARE_NULL("upper bounds are null"), - BOUNDS_MISMATCH("dimensional mismatch between bounds"), - LOWER_BOUNDS_EXCEED_UPPER_BOUNDS("lower bounds exceed upper bounds"), - ; - - private final String description; - - private DimensionTreeError(final String description) { - this.description = description; - } - - @Override - public String getDescription() { - return description; - } -} diff --git a/gcm3/src/main/java/util/dimensiontree/VolumetricDimensionTree.java b/gcm3/src/main/java/util/dimensiontree/VolumetricDimensionTree.java deleted file mode 100644 index dc91ab729..000000000 --- a/gcm3/src/main/java/util/dimensiontree/VolumetricDimensionTree.java +++ /dev/null @@ -1,293 +0,0 @@ -package util.dimensiontree; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import org.apache.commons.math3.util.FastMath; - -public class VolumetricDimensionTree { - private final static int DEFAULTLEAFSIZE = 15; - - private static class DimensionTreeRec { - double maxRadius; - DimensionTree> dimensionTree; - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("DimensionTreeRec [maxRadius="); - builder.append(maxRadius); - builder.append(", dimensionTree=\n"); - builder.append(dimensionTree); - builder.append("\n]"); - return builder.toString(); - } - - } - - private static class InitialTreeSettings { - private double[] lowerBounds; - private double[] upperBounds; - private int leafSize = DEFAULTLEAFSIZE; - private boolean fastRemovals = false; - } - - // public static Builder builder(Class c) { - // return new Builder<>(); - // } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - - private InitialTreeSettings initialTreeSettings = new InitialTreeSettings(); - - private Builder() { - } - - public Builder setFastRemovals(boolean fastRemovals) { - initialTreeSettings.fastRemovals = fastRemovals; - return this; - } - - public Builder setLeafSize(int leafSize) { - initialTreeSettings.leafSize = leafSize; - return this; - } - - public Builder setLowerBounds(double[] lowerBounds) { - initialTreeSettings.lowerBounds = Arrays.copyOf(lowerBounds, lowerBounds.length); - return this; - } - - public Builder setUpperBounds(double[] upperBounds) { - initialTreeSettings.upperBounds = Arrays.copyOf(upperBounds, upperBounds.length); - return this; - } - - public VolumetricDimensionTree build() { - - try { - return new VolumetricDimensionTree<>(initialTreeSettings); - } finally { - initialTreeSettings = new InitialTreeSettings(); - } - - } - } - - private static class LocationRec { - - final double[] position; - final double radius; - final K location; - - public LocationRec(double[] position, double radius, K location) { - this.position = position; - this.location = location; - this.radius = radius; - } - - // public double squareDistance(double[] position) { - // double result = 0; - // for (int i = 0; i < position.length; i++) { - // double delta = (position[i] - this.position[i]); - // result += delta * delta; - // } - // return result; - // } - - public boolean containsPosition(double[] position, double radius) { - double square_distance = 0; - for (int i = 0; i < position.length; i++) { - double d = this.position[i] - position[i]; - square_distance += d * d; - } - - double combinedRadius = this.radius + radius; - return square_distance < combinedRadius * combinedRadius; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("LocationRec [position="); - builder.append(Arrays.toString(position)); - builder.append(", radius="); - builder.append(radius); - builder.append(", location="); - builder.append(location); - builder.append("]"); - return builder.toString(); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((location == null) ? 0 : location.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (!(obj instanceof LocationRec)) { - return false; - } - @SuppressWarnings("rawtypes") - LocationRec other = (LocationRec) obj; - if (location == null) { - if (other.location != null) { - return false; - } - } else if (!location.equals(other.location)) { - return false; - } - return true; - } - - } - - private final InitialTreeSettings initialTreeSettings; - - private Map> treeMap = new LinkedHashMap<>(); - - private DimensionTreeRec defaultDimensionTreeRec; - - private VolumetricDimensionTree(InitialTreeSettings initialTreeSettings) { - this.initialTreeSettings = initialTreeSettings; - defaultDimensionTreeRec = new DimensionTreeRec<>(); - defaultDimensionTreeRec.dimensionTree = DimensionTree .builder()// - .setLowerBounds(initialTreeSettings.lowerBounds)// - .setUpperBounds(initialTreeSettings.upperBounds)// - .setLeafSize(initialTreeSettings.leafSize)// - .setFastRemovals(initialTreeSettings.fastRemovals)// - .build();// - - defaultDimensionTreeRec.maxRadius = 0; - } - - private Integer getIndexFromRadius(double radius) { - - try { - return (int) (FastMath.ceil(FastMath.log(radius))); - } catch (Exception e) { - return null; - } - } - - public void add(double[] position, double radius, T t) { - - // first, determine the DimensionRec we are to use - Integer index = getIndexFromRadius(radius); - - DimensionTreeRec dimensionTreeRec; - if (index == null) { - dimensionTreeRec = defaultDimensionTreeRec; - } else { - dimensionTreeRec = treeMap.get(index); - if (dimensionTreeRec == null) { - dimensionTreeRec = new DimensionTreeRec<>(); - - dimensionTreeRec.dimensionTree = DimensionTree .builder().setLowerBounds(initialTreeSettings.lowerBounds)// - .setUpperBounds(initialTreeSettings.upperBounds)// - .setLeafSize(initialTreeSettings.leafSize)// - .setFastRemovals(initialTreeSettings.fastRemovals)// - .build();// - - dimensionTreeRec.maxRadius = FastMath.exp(index); - treeMap.put(index, dimensionTreeRec); - } - } - LocationRec locationRec = new LocationRec<>(position.clone(), radius, t); - dimensionTreeRec.dimensionTree.add(position, locationRec); - } - - /** - * Removes the member from this tree using the suggested radius to narrow - * the search. If the given radius is different from any radius used to - * store the object, there is no guarantee that the object will be removed. - * This is not guaranteed to remove all occurrences of the member if - * multiple radii and positions were used to add the member. - */ - public boolean remove(double radius, T t) { - - // first, determine the DimensionRec we are to use - Integer index = getIndexFromRadius(radius); - - DimensionTreeRec dimensionTreeRec = null; - if (index == null) { - dimensionTreeRec = defaultDimensionTreeRec; - } else { - dimensionTreeRec = treeMap.get(index); - } - if (dimensionTreeRec != null) { - LocationRec locationRec = new LocationRec<>(null, radius, t); - return dimensionTreeRec.dimensionTree.remove(locationRec); - } - return false; - } - - /** - * Removes all occurences of the given member from this tree - */ - public boolean remove(T t) { - LocationRec locationRec = new LocationRec<>(null, 0, t); - boolean result = defaultDimensionTreeRec.dimensionTree.remove(locationRec); - for (DimensionTreeRec dimensionTreeRec : treeMap.values()) { - result |= dimensionTreeRec.dimensionTree.remove(locationRec); - } - return result; - } - - public boolean contains(T t) { - LocationRec locationRec = new LocationRec<>(null, 0, t); - boolean result = defaultDimensionTreeRec.dimensionTree.contains(locationRec); - if (!result) { - for (DimensionTreeRec dimensionTreeRec : treeMap.values()) { - result |= dimensionTreeRec.dimensionTree.contains(locationRec); - if (result) { - break; - } - } - } - return result; - } - - public List getAll() { - List result = new ArrayList<>(); - for (DimensionTreeRec dimensionTreeRec : treeMap.values()) { - for (LocationRec nodeWrapper : dimensionTreeRec.dimensionTree.getAll()) { - result.add(nodeWrapper.location); - } - } - return result; - } - - public List getMembersInSphere(double radius, double[] position) { - List result = new ArrayList<>(); - for (LocationRec locationRec : defaultDimensionTreeRec.dimensionTree.getMembersInSphere(radius, position)) { - result.add(locationRec.location); - } - for (DimensionTreeRec dimensionTreeRec : treeMap.values()) { - List> potentialIntersections = dimensionTreeRec.dimensionTree.getMembersInSphere(radius + dimensionTreeRec.maxRadius, position); - for (LocationRec locationRec : potentialIntersections) { - if (locationRec.containsPosition(position, radius)) { - result.add(locationRec.location); - } - } - } - return result; - } -} diff --git a/gcm3/src/main/java/util/dimensiontree/internal/CommonState.java b/gcm3/src/main/java/util/dimensiontree/internal/CommonState.java deleted file mode 100644 index debfee457..000000000 --- a/gcm3/src/main/java/util/dimensiontree/internal/CommonState.java +++ /dev/null @@ -1,24 +0,0 @@ -package util.dimensiontree.internal; - -/** - * The common parameters shared by all nodes that takes up less memory than - * storing these values on each node - * - * @author Shawn Hatch - * - */ -public class CommonState { - - public final int leafSize; - - public final int dimension; - - public final int childCount; - - public CommonState(int leafSize, int dimension) { - this.leafSize = leafSize; - this.dimension = dimension; - this.childCount = 1 << dimension; - } - -} diff --git a/gcm3/src/main/java/util/dimensiontree/internal/Group.java b/gcm3/src/main/java/util/dimensiontree/internal/Group.java deleted file mode 100644 index 619f0e4cd..000000000 --- a/gcm3/src/main/java/util/dimensiontree/internal/Group.java +++ /dev/null @@ -1,53 +0,0 @@ -package util.dimensiontree.internal; - -import java.util.ArrayList; -import java.util.List; - -/** - * Represent the members that are associated with a single position. Rather than - * storing members in the leaf nodes, we store member groups. Each member group - * can contain an unlimited number of members, but all such members have exactly - * the same position. This is done to prevent infinite branching when there are - * too many members at the same position. - */ -public class Group { - - public Node node; - - public double position[]; - - public List members = new ArrayList<>(); - - public boolean add(T t, double[] position) { - if (members.size() == 0) { - this.position = position.clone(); - } - if (!members.contains(t)) { - members.add(t); - return true; - } - return false; - } - - public boolean canContain(double[] p) { - if (members.size() == 0) { - return true; - } - for (int i = 0; i < position.length; i++) { - if (p[i] != position[i]) { - return false; - } - } - return true; - } - - public double squareDistanceTo(double[] p) { - double result = 0; - for (int i = 0; i < position.length; i++) { - double value = position[i] - p[i]; - result += value * value; - } - return result; - } - -} \ No newline at end of file diff --git a/gcm3/src/main/java/util/dimensiontree/internal/NearestMemberQuery.java b/gcm3/src/main/java/util/dimensiontree/internal/NearestMemberQuery.java deleted file mode 100644 index 33ce40998..000000000 --- a/gcm3/src/main/java/util/dimensiontree/internal/NearestMemberQuery.java +++ /dev/null @@ -1,32 +0,0 @@ -package util.dimensiontree.internal; - -import java.util.Arrays; - -/** - * Represents the evolving answer to finding the nearest member to a given - * position. - * - * @author Shawn Hatch - */ -public class NearestMemberQuery { - - public double bestSquareDistance = Double.POSITIVE_INFINITY; - - public T closestObject = null; - - public double[] position; - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("NearestMemberQuery [bestSquareDistance="); - builder.append(bestSquareDistance); - builder.append(", closestObject="); - builder.append(closestObject); - builder.append(", position="); - builder.append(Arrays.toString(position)); - builder.append("]"); - return builder.toString(); - } - -} diff --git a/gcm3/src/main/java/util/dimensiontree/internal/Node.java b/gcm3/src/main/java/util/dimensiontree/internal/Node.java deleted file mode 100644 index ba0a180d4..000000000 --- a/gcm3/src/main/java/util/dimensiontree/internal/Node.java +++ /dev/null @@ -1,283 +0,0 @@ -package util.dimensiontree.internal; - -import java.util.ArrayList; -import java.util.List; - -import org.apache.commons.math3.util.FastMath; - -/** - * Represents a single node the in dimension tree. - * - * @author Shawn Hatch - * - */ -public class Node { - - public Node parent = null; - - public Node[] children = null; - - public boolean canFormChildren; - - public double[] lowerBounds; - - public double[] upperBounds; - - private final CommonState commonState; - - public List> groups = new ArrayList<>(); - - public final double squareRadius; - - public int groupCount; - - public int indexInParent = -1; - - // public int childWalkIndex; - - public Node(CommonState commonState, double[] lowerBounds, double[] upperBounds) { - - this.commonState = commonState; - this.lowerBounds = lowerBounds; - this.upperBounds = upperBounds; - canFormChildren = true; - for (int i = 0; i < commonState.dimension; i++) { - double bound = (upperBounds[i] + lowerBounds[i]) * 0.5; - if ((bound >= upperBounds[i]) || (bound <= lowerBounds[i])) { - canFormChildren = false; - break; - } - } - - double sum = 0; - for (int i = 0; i < commonState.dimension; i++) { - double delta = (upperBounds[i] - lowerBounds[i]) / 2; - delta *= delta; - sum += delta; - } - squareRadius = sum; - } - - public boolean containsMember(T t) { - if (children == null) { - for (Group memberGroup : groups) { - if (memberGroup.members.contains(t)) { - return true; - } - } - return false; - } - - for (Node child : children) { - if (child != null) { - if (child.containsMember(t)) { - return true; - } - } - } - return false; - } - - public void getNearestMember(NearestMemberQuery nearestMemberData) { - findInitialNearestMemberSolution(nearestMemberData); - findBetterNearestMemberSolution(nearestMemberData); - } - - /* - * This method drill down to the node that that either contains the position - * or comes fairly close. We use this to quickly reduce the volume around - * the position where a solution might be found. If a solution is found this - * way, there is no guarantee that it will be the best solution, but it very - * often will be very close. - */ - private void findInitialNearestMemberSolution(NearestMemberQuery nearestMemberData) { - if (children == null) { - for (Group memberGroup : groups) { - double squareDistance = memberGroup.squareDistanceTo(nearestMemberData.position); - if ((squareDistance <= nearestMemberData.bestSquareDistance) || (nearestMemberData.bestSquareDistance < 0)) { - nearestMemberData.bestSquareDistance = squareDistance; - nearestMemberData.closestObject = memberGroup.members.get(0); - } - } - return; - } - int childIndex = getChildIndex(nearestMemberData.position); - Node child = children[childIndex]; - if (child == null) { - // calculate the distance to the farthest corner from the position - double greatestSquaredDistance = 0; - double value; - for (int i = 0; i < commonState.dimension; i++) { - double deltaToUpperBound = FastMath.abs(nearestMemberData.position[i] - upperBounds[i]); - double deltaToLowerBound = FastMath.abs(nearestMemberData.position[i] - lowerBounds[i]); - value = FastMath.max(deltaToUpperBound, deltaToLowerBound); - greatestSquaredDistance += value * value; - } - nearestMemberData.bestSquareDistance = greatestSquaredDistance; - return; - } - child.findInitialNearestMemberSolution(nearestMemberData); - } - - /* - * This method searches the entire tree for a solution and tries to - * terminate branching quickly. It depends on first having found a - * reasonable near-solution, otherwise it will walk the entire tree. - */ - private void findBetterNearestMemberSolution(NearestMemberQuery nearestMemberData) { - /* - * We try to exclude any calculations if this node does not overlap the - * sphere given by the current solution. We do this by comparing the - * radius of this node, the radius of the current solution and the - * distance to the query position. We will sometimes fail to reject - * further work, but this will reject most of the potential wasted - * comparisons. - * - */ - - double squareDistanceToPositionFromNodeCenter = 0; - for (int i = 0; i < commonState.dimension; i++) { - double delta = nearestMemberData.position[i] - ((upperBounds[i] + lowerBounds[i]) / 2); - delta *= delta; - squareDistanceToPositionFromNodeCenter += delta; - } - - if (SquareRootInequality.evaluate(squareRadius, nearestMemberData.bestSquareDistance, squareDistanceToPositionFromNodeCenter)) { - return; - } - - if (children == null) { - for (Group memberGroup : groups) { - double squareDistance = memberGroup.squareDistanceTo(nearestMemberData.position); - if ((squareDistance <= nearestMemberData.bestSquareDistance) || (nearestMemberData.bestSquareDistance < 0)) { - nearestMemberData.bestSquareDistance = squareDistance; - nearestMemberData.closestObject = memberGroup.members.get(0); - } - } - return; - } - - for (Node child : children) { - if (child != null) { - child.findBetterNearestMemberSolution(nearestMemberData); - } - } - } - - public void getAllMembers(List list) { - - if (children == null) { - - for (Group memberGroup : groups) { - list.addAll(memberGroup.members); - } - return; - } - - for (Node child : children) { - if (child == null) { - continue; - } - child.getAllMembers(list); - } - } - - public void getObjectsInDimensionalShape(Shape dimensionalShape, List list) { - ShapeIntersectionType shapeIntersectionType = dimensionalShape.intersectsBox(this); - switch (shapeIntersectionType) { - case NONE: - return; - case COMPLETE: - getAllMembers(list); - return; - case PARTIAL: - if (children == null) { - for (Group memberGroup : groups) { - if (dimensionalShape.containsPosition(memberGroup.position)) { - list.addAll(memberGroup.members); - } - } - } else { - for (Node child : children) { - if (child != null) { - child.getObjectsInDimensionalShape(dimensionalShape, list); - } - } - } - return; - default: - throw new RuntimeException("unhandled shape intersection type " + shapeIntersectionType); - } - } - - public int getChildIndex(double[] position) { - /* - * Rather than store an array of booleans for our analysis of child - * bounds, we will use an index value that will be composed and then - * decomposed to eliminate the cost of array construction and garbage - * collection which has been shown in testing to be fairly expensive. - */ - int result = 0; - for (int i = 0; i < commonState.dimension; i++) { - result *= 2; - if (upperBounds[i] + lowerBounds[i] < 2 * position[i]) { - result++; - } - } - return result; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - for (String s : toStrings()) { - sb.append(s); - sb.append("\n"); - } - return sb.toString(); - } - - public void retrieveGroupsForMember(List> groups, T member) { - if (children == null) { - for (Group group : this.groups) { - if (group.members.contains(member)) { - groups.add(group); - } - } - } else { - for (Node child : children) { - if (child != null) { - child.retrieveGroupsForMember(groups, member); - } - } - } - } - - public void retrieveGroups(List> groups) { - if (children == null) { - groups.addAll(this.groups); - } else { - for (Node child : children) { - if (child != null) { - child.retrieveGroups(groups); - } - } - } - } - - private List toStrings() { - List result = new ArrayList<>(); - result.add(groupCount + " : " + groups.size()); - if (children != null) { - for (Node child : children) { - if (child != null) { - for (String s : child.toStrings()) { - result.add("\t" + s); - } - } - } - } - return result; - } - -} \ No newline at end of file diff --git a/gcm3/src/main/java/util/dimensiontree/internal/Rectanguloid.java b/gcm3/src/main/java/util/dimensiontree/internal/Rectanguloid.java deleted file mode 100644 index fdb2b58a6..000000000 --- a/gcm3/src/main/java/util/dimensiontree/internal/Rectanguloid.java +++ /dev/null @@ -1,66 +0,0 @@ -package util.dimensiontree.internal; - -import org.apache.commons.math3.util.FastMath; - -/** - * Represents a rectangular box in the dimension of the tree. - * - * @author Shawn Hatch - * - */ -public class Rectanguloid implements Shape { - - private double[] position = new double[0]; - - private double[] bounds = new double[0]; - - public Rectanguloid(double[] lowerBounds, double[] upperBounds) { - position = new double[lowerBounds.length]; - bounds = new double[lowerBounds.length]; - for (int i = 0; i < position.length; i++) { - position[i] = (upperBounds[i] + lowerBounds[i]) * 0.5; - bounds[i] = (upperBounds[i] - lowerBounds[i]) * 0.5; - } - } - - public double[] bounds() { - return bounds.clone(); - } - - @Override - public boolean containsPosition(double[] position) { - for (int i = 0; i < position.length; i++) { - if (FastMath.abs(this.position[i] - position[i]) > bounds[i]) { - return false; - } - } - return true; - } - - @Override - public ShapeIntersectionType intersectsBox(Node node) { - // double[] lowerBounds, double[] upperBounds - int containmentCount = 0; - for (int i = 0; i < position.length; i++) { - if (position[i] + bounds[i] < node.lowerBounds[i]) { - return ShapeIntersectionType.NONE; - } - if (position[i] - bounds[i] > node.upperBounds[i]) { - return ShapeIntersectionType.NONE; - } - - if ((position[i] + bounds[i] > node.upperBounds[i]) && (position[i] - bounds[i] < node.lowerBounds[i])) { - containmentCount++; - } - } - if (containmentCount == position.length) { - return ShapeIntersectionType.COMPLETE; - } - return ShapeIntersectionType.PARTIAL; - - } - - public double[] position() { - return position.clone(); - } -} diff --git a/gcm3/src/main/java/util/dimensiontree/internal/Shape.java b/gcm3/src/main/java/util/dimensiontree/internal/Shape.java deleted file mode 100644 index 012aea900..000000000 --- a/gcm3/src/main/java/util/dimensiontree/internal/Shape.java +++ /dev/null @@ -1,22 +0,0 @@ -package util.dimensiontree.internal; - -/** - * General interface for shapes used for gathering member from the tree. - * - * @author Shawn Hatch - * - */ -public interface Shape { - - /** - * Returns true if the position is located inside this shape - */ - public boolean containsPosition(double[] position); - - /** - * Returns a ShapeIntersectionType that is the shapes determination of the - * overlap of the shape and the given node. Used to streamline the process - * of gathering members from nodes during intersection tests. - */ - public ShapeIntersectionType intersectsBox(Node node); -} diff --git a/gcm3/src/main/java/util/dimensiontree/internal/ShapeIntersectionType.java b/gcm3/src/main/java/util/dimensiontree/internal/ShapeIntersectionType.java deleted file mode 100644 index 4c973644b..000000000 --- a/gcm3/src/main/java/util/dimensiontree/internal/ShapeIntersectionType.java +++ /dev/null @@ -1,26 +0,0 @@ -package util.dimensiontree.internal; - -/** - * Represents the degree of intersection of the shape and a node. - * - * @author Shawn Hatch - * - */ -public enum ShapeIntersectionType { - /** - * The shape and node have no intersection - */ - NONE, - - /** - * The shape may intersect the node. Members of the node will require - * further comparison to the shape. - */ - PARTIAL, - - /** - * The shape fully contains the node and all members of the node can be - * gathered without further comparison to the shape. - */ - COMPLETE; -} diff --git a/gcm3/src/main/java/util/dimensiontree/internal/Sphere.java b/gcm3/src/main/java/util/dimensiontree/internal/Sphere.java deleted file mode 100644 index 8fe54d230..000000000 --- a/gcm3/src/main/java/util/dimensiontree/internal/Sphere.java +++ /dev/null @@ -1,61 +0,0 @@ -package util.dimensiontree.internal; - -/** - * Represents a sphere in the dimension of the tree. - * - * @author Shawn Hatch - * - */ -public class Sphere implements Shape { - - private final double radius; - private final double sqRadius; - - private double[] position = new double[0]; - - public Sphere(double radius, double[] position) { - this.radius = radius; - this.sqRadius = radius * radius; - this.position = position.clone(); - } - - @Override - public boolean containsPosition(double[] position) { - - double distance = 0; - for (int i = 0; i < position.length; i++) { - double d = this.position[i] - position[i]; - distance += d * d; - } - return distance < sqRadius; - } - - @Override - public ShapeIntersectionType intersectsBox(Node node) { - - double squareDistanceToBoxCenter = 0; - for (int i = 0; i < position.length; i++) { - double value = position[i] - (node.upperBounds[i] + node.lowerBounds[i]) / 2; - squareDistanceToBoxCenter += value * value; - } - - if (SquareRootInequality.evaluate(node.squareRadius, sqRadius, squareDistanceToBoxCenter)) { - return ShapeIntersectionType.NONE; - } - - if (SquareRootInequality.evaluate(squareDistanceToBoxCenter, node.squareRadius, sqRadius)) { - return ShapeIntersectionType.COMPLETE; - } - - return ShapeIntersectionType.PARTIAL; - } - - public double[] position() { - return position.clone(); - } - - public double radius() { - return radius; - } - -} diff --git a/gcm3/src/main/java/util/dimensiontree/internal/SquareRootInequality.java b/gcm3/src/main/java/util/dimensiontree/internal/SquareRootInequality.java deleted file mode 100644 index b79428266..000000000 --- a/gcm3/src/main/java/util/dimensiontree/internal/SquareRootInequality.java +++ /dev/null @@ -1,40 +0,0 @@ -package util.dimensiontree.internal; - -/** - * A utility class for determining if sqrt(a) + sqrt(b) < sqrt(c) without - * calculating square roots. - * - * @author Shawn Hatch - * - */ -public final class SquareRootInequality { - - private SquareRootInequality() { - - } - - /** - * Returns true if and only if sqrt(a) + sqrt(b) < sqrt(c). Used in distance - * comparisons where square distances are known and calculating square roots - * should be avoided. - */ - public static boolean evaluate(double aSquare, double bSquare, double cSquare) { - /* - * We want to know when a+b 0 - */ - double d = cSquare - aSquare - bSquare; - return d >= 0 && 4 * aSquare * bSquare < d * d; - } - -} diff --git a/gcm3/src/main/java/util/earth/Earth.java b/gcm3/src/main/java/util/earth/Earth.java deleted file mode 100644 index 3fed684ff..000000000 --- a/gcm3/src/main/java/util/earth/Earth.java +++ /dev/null @@ -1,156 +0,0 @@ -package util.earth; - -import org.apache.commons.math3.util.FastMath; - -import util.vector.Vector3D; - -/** - * A spherical geo-model for converting various coordinate representations and - * calculating ground ranges. Earth instances are created from approximations to - * the WGS84 oblate earth. - * - * @author Shawn Hatch - * - */ -public class Earth { - - public final static double WGS84_EQUATORIAL_RADIUS_METERS = 6378137; - - public final static double WGS84_POLAR_RADIUS_METERS = 6356752.314245; - - public final static double WGS84_MEAN_RADIUS_METERS = (2 * WGS84_EQUATORIAL_RADIUS_METERS + WGS84_POLAR_RADIUS_METERS) / 3; - - private double radius; - - /** - * Returns the radius of this Earth instance in meters - */ - public double getRadius() { - return radius; - } - - /* - * Hidden constructor. - */ - private Earth() { - - } - - /** - * Static constructor that returns an Earth instance having a radius of - * Earth.WGS84_MEAN_RADIUS_METERS - */ - public static Earth fromMeanRadius() { - Earth result = new Earth(); - result.radius = WGS84_MEAN_RADIUS_METERS; - return result; - } - - /** - * Static constructor that returns an Earth instance having the given radius - */ - public static Earth fromRadius(double radiusMeters) { - Earth result = new Earth(); - result.radius = radiusMeters; - return result; - } - - /** - * Static constructor that returns an Earth instance having a radius of the - * WGS84 earth at the given latitude. - */ - public static Earth fromLatitude(double latitudeDegrees) { - Earth result = new Earth(); - result.radius = getEffectiveEarthRadius(latitudeDegrees); - return result; - } - - /** - * Returns the effective earth radius for the given latitude from the WGS84 - * oblate geo-model - */ - public static double getEffectiveEarthRadius(double latitudeDegrees) { - // calculate the effective spherical earth radius for the given - // latitude from the WGS-84 oblate earth - final double lat = FastMath.toRadians(latitudeDegrees); - return 1.0 / FastMath.sqrt(FastMath.pow(FastMath.cos(lat) / WGS84_EQUATORIAL_RADIUS_METERS, 2) + FastMath.pow(FastMath.sin(lat) / WGS84_POLAR_RADIUS_METERS, 2)); - } - - /** - * Returns the ground distance in meters between the two positions. - */ - public double getGroundDistanceFromECC(Vector3D position1, Vector3D position2) { - return position1.angle(position2) * radius; - } - - /** - * Returns the ground distance in meters between the two positions. - */ - public double getGroundDistanceFromLatLon(LatLon position1, LatLon position2) { - Vector3D v1 = getECCFromLatLon(position1); - Vector3D v2 = getECCFromLatLon(position2); - return v1.angle(v2) * radius; - } - - /** - * Returns the ground distance in meters between the two positions. - */ - public double getGroundDistanceFromLatLonAlt(LatLonAlt position1, LatLonAlt position2) { - Vector3D v1 = getECCFromLatLonAlt(position1); - Vector3D v2 = getECCFromLatLonAlt(position2); - return v1.angle(v2) * radius; - } - - /** - * Converts an ECC position into a LatLonAlt - */ - public LatLonAlt getLatLonAlt(Vector3D v) { - double alt = v.length() - radius; - Vector3D n = v.normalize(); - double z = n.getZ(); - double lat = FastMath.asin(z); - double lon = FastMath.acos(crunch(n.getX() / FastMath.sqrt(1 - z * z))); - if (n.getY() < 0) { - lon *= -1; - } - lat = FastMath.toDegrees(lat); - lon = FastMath.toDegrees(lon); - - return new LatLonAlt(lat, lon, alt); - } - - /** - * Returns an ECC position from the given LatLonAlt - */ - public Vector3D getECCFromLatLonAlt(LatLonAlt latLonAlt) { - double coslat = FastMath.cos(FastMath.toRadians(latLonAlt.getLatitude())); - double coslon = FastMath.cos(FastMath.toRadians(latLonAlt.getLongitude())); - double sinlat = FastMath.sin(FastMath.toRadians(latLonAlt.getLatitude())); - double sinlon = FastMath.sin(FastMath.toRadians(latLonAlt.getLongitude())); - double distance = radius + latLonAlt.getAltitude(); - return new Vector3D(coslat * coslon, coslat * sinlon, sinlat).scale(distance); - } - - /** - * Returns an ECC position from the given LatLon - */ - public Vector3D getECCFromLatLon(LatLon latLon) { - double coslat = FastMath.cos(FastMath.toRadians(latLon.getLatitude())); - double coslon = FastMath.cos(FastMath.toRadians(latLon.getLongitude())); - double sinlat = FastMath.sin(FastMath.toRadians(latLon.getLatitude())); - double sinlon = FastMath.sin(FastMath.toRadians(latLon.getLongitude())); - return new Vector3D(coslat * coslon, coslat * sinlon, sinlat).scale(radius); - } - - private static double crunch(double value) { - - if (value > 1) { - return 1; - } - if (value < -1) { - return -1; - } - return value; - } - -} diff --git a/gcm3/src/main/java/util/earth/EarthGrid.java b/gcm3/src/main/java/util/earth/EarthGrid.java deleted file mode 100644 index 2c4c45458..000000000 --- a/gcm3/src/main/java/util/earth/EarthGrid.java +++ /dev/null @@ -1,86 +0,0 @@ -package util.earth; - -import org.apache.commons.math3.util.FastMath; - -import util.vector.MutableVector2D; -import util.vector.MutableVector3D; -import util.vector.Vector3D; - -/** - * A utility class for converting (x,y) two dimensional grid coordinates to an - * from (lat,lon) coordinates. The grid is constructed at a particular (lat, - * lon) position with an azimuth for grid orientation. The resulting grid is a - * good approximation to the earth's surface for several kilometers. The earth - * is approximated by a sphere using the WGS84 earth radius for the latitude of - * the center of the grid. - * - * @author Shawn Hatch - * - */ -public final class EarthGrid { - - public static final double MIN_ANGLE_FROM_POLE = 0.001; - - private final Earth earth; - - private Vector3D x; - - private Vector3D y; - - private Vector3D z; - - private Vector3D c; - - /** - * Constructs a new EarthGrid centered at the given LatLon where the (x,y) - * grid is right handed from the perspective of an observer above the earth. - * The positive y axis is aligned to the azimth. For example, an azimuth of - * 0 degrees will align the y axis to the north and an azimuth of 90 degrees - * will align the positive y axis to the east. - * - * @throws IllegalArgumentException - *
          • if the center point is closer than - * EarthGrid.MIN_ANGLE_FROM_POLE=0.001 degrees from one of the - * poles.
          • - * - * @param center - * @param azimuthDegrees - */ - public EarthGrid(LatLon center, double azimuthDegrees) { - if ((FastMath.abs(center.getLatitude()) - 90) < MIN_ANGLE_FROM_POLE) { - throw new IllegalArgumentException("Grid cannot be constructed within " + MIN_ANGLE_FROM_POLE + " degrees of a pole"); - } - earth = Earth.fromLatitude(center.getLatitude()); - - c = earth.getECCFromLatLon(center); - - z = c.normalize(); - - x = new Vector3D(0, 0, 1)// - .cross(z)// - .normalize()// - .rotateAbout(z, -FastMath.toRadians(azimuthDegrees));// - - y = z.cross(x).normalize(); - } - - public MutableVector2D getCartesian2DCoordinate(LatLon latLon) { - MutableVector3D v = new MutableVector3D(earth.getECCFromLatLonAlt(new LatLonAlt(latLon))); - v.normalize(); - v.scale(Earth.getEffectiveEarthRadius(latLon.getLatitude())); - v.sub(c); - return new MutableVector2D(v.dot(x), v.dot(y)); - } - - public LatLon getLatLon(MutableVector2D xyCoordinate) { - double zlength = FastMath.sqrt(earth.getRadius() * earth.getRadius() - xyCoordinate.getX() * xyCoordinate.getX() - xyCoordinate.getY() * xyCoordinate.getY()) - earth.getRadius(); - MutableVector3D planarPosition = new MutableVector3D(c); - planarPosition.addScaled(y, xyCoordinate.getY()); - planarPosition.addScaled(x, xyCoordinate.getX()); - planarPosition.addScaled(z, zlength); - - LatLonAlt latLonAlt = earth.getLatLonAlt(new Vector3D(planarPosition)); - LatLon latLon = new LatLon(latLonAlt); - return latLon; - } -} diff --git a/gcm3/src/main/java/util/earth/LatLon.java b/gcm3/src/main/java/util/earth/LatLon.java deleted file mode 100644 index 02fc024d4..000000000 --- a/gcm3/src/main/java/util/earth/LatLon.java +++ /dev/null @@ -1,120 +0,0 @@ -package util.earth; - -import net.jcip.annotations.Immutable; - -/** - * An immutable container class for a pair of latitude/longitude values measured - * in degrees where latitude is in the interval [-90,90] and longitude is in the - * interval [-180,180] - * - * @author Shawn Hatch - * - */ - -@Immutable -public class LatLon { - - private final double latitude; - - private final double longitude; - - /** - * Returns the latitude - */ - public double getLatitude() { - return latitude; - } - - /** - * Returns the longitude - */ - public double getLongitude() { - return longitude; - } - - /** - * Constructs a LatLon from the given latitude and longitude - * - * @throws IllegalArgumentException - *
          • if the latitude is not in the interval [-90,90] - *
          • if the longitude is not in the interval [-180,180] - * - * @param latitude - * @param longitude - */ - public LatLon(double latitude, double longitude) { - if (latitude > 90) { - throw new IllegalArgumentException("Latitude > 90 degrees"); - } - if (latitude < -90) { - throw new IllegalArgumentException("Latitude < -90 degrees"); - } - if (longitude > 180) { - throw new IllegalArgumentException("Longitude > 180 degrees"); - } - if (longitude < -180) { - throw new IllegalArgumentException("Longitude < -180 degrees"); - } - this.latitude = latitude; - this.longitude = longitude; - } - - /** - * Constructs a LatLon from the given LatLonAlt - * - * @throws NullPointerException - *
          • if the latLonAlt is null - * @param latLonAlt - */ - public LatLon(LatLonAlt latLonAlt) { - if (latLonAlt == null) { - throw new NullPointerException("null latLonAlt"); - } - this.latitude = latLonAlt.getLatitude(); - this.longitude = latLonAlt.getLongitude(); - } - - /** - * Returns a string of the form LatLonAlt [latitude=35.0, longitude=128.0] - */ - @Override - public String toString() { - return "LatLon [latitude=" + latitude + ", longitude=" + longitude + "]"; - } - - /** - * Returns a hash code consistent with equals() - */ - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - long temp; - temp = Double.doubleToLongBits(latitude); - result = prime * result + (int) (temp ^ (temp >>> 32)); - temp = Double.doubleToLongBits(longitude); - result = prime * result + (int) (temp ^ (temp >>> 32)); - return result; - } - - /** - * Returns true if and only if this LatLon is compared to another non-null - * instance of LatLon with matching latitude and longitude values - */ - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - LatLon other = (LatLon) obj; - if (Double.doubleToLongBits(latitude) != Double.doubleToLongBits(other.latitude)) - return false; - if (Double.doubleToLongBits(longitude) != Double.doubleToLongBits(other.longitude)) - return false; - return true; - } - -} diff --git a/gcm3/src/main/java/util/earth/LatLonAlt.java b/gcm3/src/main/java/util/earth/LatLonAlt.java deleted file mode 100644 index 2be2ced3c..000000000 --- a/gcm3/src/main/java/util/earth/LatLonAlt.java +++ /dev/null @@ -1,175 +0,0 @@ -package util.earth; - -import util.vector.Vector3D; - -public class LatLonAlt { - - private final double latitude; - - private final double longitude; - - private final double altitude; - - /** - * Returns the latitude - */ - public double getLatitude() { - return latitude; - } - - /** - * Returns the longitude - */ - public double getLongitude() { - return longitude; - } - - /** - * Constructs a LatLonAlt from the given latitude and longitude - * - * @throws IllegalArgumentException - *
          • if the latitude is not in the interval [-90,90]
          • - *
          • if the longitude is not in the interval [-180,180]
          • - * - * @param latitude - * @param longitude - */ - public LatLonAlt(double latitude, double longitude, double altitude) { - if (latitude > 90) { - throw new IllegalArgumentException("Latitude > 90 degrees"); - } - if (latitude < -90) { - throw new IllegalArgumentException("Latitude < -90 degrees"); - } - if (longitude > 180) { - throw new IllegalArgumentException("Longitude > 180 degrees"); - } - if (longitude < -180) { - throw new IllegalArgumentException("Longitude < -180 degrees"); - } - this.latitude = latitude; - this.longitude = longitude; - this.altitude = altitude; - } - - /** - * Constructs a LatLonAlt from the given Vector3D where latitude = x, - * longitude = y and altitude = z - * - * @throws NullPointerException - *
          • if the vector3d is null
          • - * - * @throws IllegalArgumentException - *
          • if the latitude(v.getX()) is not in the interval [-90,90]
          • - *
          • if the longitude(v.getY()) is not in the interval - * [-180,180]
          • - * @param latLonAlt - */ - public LatLonAlt(Vector3D vector3d) { - if (vector3d == null) { - throw new NullPointerException("null Vector3D value"); - } - - this.latitude = vector3d.getX(); - this.longitude = vector3d.getY(); - this.altitude = vector3d.getZ(); - - if (latitude > 90) { - throw new IllegalArgumentException("Latitude > 90 degrees"); - } - if (latitude < -90) { - throw new IllegalArgumentException("Latitude < -90 degrees"); - } - if (longitude > 180) { - throw new IllegalArgumentException("Longitude > 180 degrees"); - } - if (longitude < -180) { - throw new IllegalArgumentException("Longitude < -180 degrees"); - } - - } - - /** - * Constructs a LatLonAlt from the given LatLon with altitude set to zero - * - * @throws NullPointerException - *
          • if the latLon is null - * @param latLonAlt - */ - - public LatLonAlt(LatLon latLon) { - if (latLon == null) { - throw new NullPointerException("null LatLon value"); - } - this.latitude = latLon.getLatitude(); - this.longitude = latLon.getLongitude(); - this.altitude = 0; - - } - - /** - * Returns the altitude - */ - public double getAltitude() { - return altitude; - } - - /** - * Returns a Vector3D from this LatLonAlt where x = latitude, y = longitude - * and z = altitude. - */ - public Vector3D toVector3D() { - return new Vector3D(latitude, longitude, altitude); - } - - /** - * Returns a hash code consistent with equals() - */ - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - long temp; - temp = Double.doubleToLongBits(altitude); - result = prime * result + (int) (temp ^ (temp >>> 32)); - temp = Double.doubleToLongBits(latitude); - result = prime * result + (int) (temp ^ (temp >>> 32)); - temp = Double.doubleToLongBits(longitude); - result = prime * result + (int) (temp ^ (temp >>> 32)); - return result; - } - - /** - * Returns true if and only if this LatLonAlt is compared to another - * non-null instance of LatLonAlt with matching latitude, longitude and - * altitude values - */ - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - LatLonAlt other = (LatLonAlt) obj; - if (Double.doubleToLongBits(altitude) != Double.doubleToLongBits(other.altitude)) - return false; - if (Double.doubleToLongBits(latitude) != Double.doubleToLongBits(other.latitude)) - return false; - if (Double.doubleToLongBits(longitude) != Double.doubleToLongBits(other.longitude)) - return false; - return true; - } - - /** - * Returns a string of the form - * - * LatLonAlt [latitude=35.0, longitude=128.0, altitude=1000.0] - */ - @Override - public String toString() { - return "LatLonAlt [latitude=" + latitude + ", longitude=" + longitude + ", altitude=" + altitude + "]"; - } - -} diff --git a/gcm3/src/main/java/util/errors/ContractError.java b/gcm3/src/main/java/util/errors/ContractError.java deleted file mode 100644 index f4d97a16c..000000000 --- a/gcm3/src/main/java/util/errors/ContractError.java +++ /dev/null @@ -1,13 +0,0 @@ - -package util.errors; - -/** - * Marker interface for the descriptions of runtime exceptions where the source - * of the error is the violation of a contract (precondition) specification. - * - * @author Shawn Hatch - * - */ -public interface ContractError { - public String getDescription(); -} diff --git a/gcm3/src/main/java/util/errors/ContractException.java b/gcm3/src/main/java/util/errors/ContractException.java deleted file mode 100644 index 8899be7c8..000000000 --- a/gcm3/src/main/java/util/errors/ContractException.java +++ /dev/null @@ -1,34 +0,0 @@ -package util.errors; - -/** - * A {@link RuntimeException} that indicates that the cause of the error is very - * likely due to a precondition violation - * - * @author Shawn Hatch - * - */ -public final class ContractException extends RuntimeException { - - private static final long serialVersionUID = -2668978936990585390L; - - private final ContractError contractError; - - public ContractException(final ContractError contractError) { - super(contractError.getDescription()); - this.contractError = contractError; - } - - public ContractException(final ContractError contractError, final Object details) { - super(contractError.getDescription()+": "+details.toString()); - this.contractError = contractError; - } - - /** - * Returns the SimulationErrorType that documents the general issue that - * caused the exception. - */ - public ContractError getErrorType() { - return contractError; - } - -} \ No newline at end of file diff --git a/gcm3/src/main/java/util/geolocator/GeoLocator.java b/gcm3/src/main/java/util/geolocator/GeoLocator.java deleted file mode 100644 index 101ddffe8..000000000 --- a/gcm3/src/main/java/util/geolocator/GeoLocator.java +++ /dev/null @@ -1,232 +0,0 @@ -package util.geolocator; - -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - -import org.apache.commons.math3.util.FastMath; -import org.apache.commons.math3.util.Pair; - -import util.dimensiontree.DimensionTree; -import util.earth.Earth; -import util.earth.LatLonAlt; -import util.vector.Vector2D; -import util.vector.Vector3D; - -/** - * A generics-based utility class for managing point locations on a spherical - * earth of WGS-84 mean radius. This utility specifically finds locations based - * on a lat-lon coordinates and a search radius. - * - * @author Shawn Hatch - * - */ -public class GeoLocator { - - /** - * Returns a new instance of the Builder class - */ - public static Builder builder() { - return new Builder<>(); - } - - /* - * Record for holding the location data collected by the builder - */ - private static class LocationRecord { - private double latDegrees; - private double lonDegrees; - private T location; - - public LocationRecord(double lat, double lon, T location) { - super(); - this.latDegrees = lat; - this.lonDegrees = lon; - this.location = location; - } - } - - /* - * Container for the location records collected by the builder - */ - private static class Scaffold { - List> locationRecords = new ArrayList<>(); - } - - /** - * Builder class for {@linkplain GeoLocator}. Builder instances return to an - * empty and ready state post build invocation. - * - * @author Shawn Hatch - * - * @param - */ - public static class Builder { - private Scaffold scaffold = new Scaffold<>(); - - private Builder() { - } - - /** - * Adds a location at the given lat and lon degrees. - */ - public Builder addLocation(double latDegrees, double lonDegrees, T location) { - LocationRecord locationRecord = new LocationRecord<>(latDegrees, lonDegrees, location); - scaffold.locationRecords.add(locationRecord); - return this; - } - - /** - * Returns a {@linkplain GeoLocator} containing the locations presented - * to this builder. - */ - public GeoLocator build() { - try { - return new GeoLocator<>(scaffold); - } finally { - scaffold = new Scaffold<>(); - } - } - } - - /* - * Record for holding the location data converted to Earth centered - * coordinates (in meters) as an ECC instance. Each such record corresponds - * to one of the location records recorded by the builder. - */ - private static class LocationEccRecord { - - T location; - Vector3D ecc; - - public LocationEccRecord(Vector3D ecc, T location) { - this.location = location; - this.ecc = ecc; - } - - } - - /* - * The tree containing the Location position records - */ - private final DimensionTree> dimensionTree; - - /* - * A utility object for converting to and from lat/lon and ecc coordinates. - * This is a spherical earth using a WGS84 mean earth radius. - */ - private final Earth earth = Earth.fromMeanRadius(); - - private GeoLocator(Scaffold scaffold) { - - /* - * Create the Location Position Records(ecc-vector based) from the - * Location Records(lat-lon based) - */ - List> locationEccRecords = scaffold.locationRecords.stream().map(rec -> { - LatLonAlt latLonAlt = new LatLonAlt(rec.latDegrees, rec.lonDegrees, 0); - Vector3D ecc = earth.getECCFromLatLonAlt(latLonAlt); - return new LocationEccRecord<>(ecc, rec.location); - }).collect(Collectors.toList()); - - /* - * Initialize the bounding box for the tree. This is not strictly - * necessary, but it will improve the tree's performance. - */ - double[] lowerBounds = new double[] { Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY }; - double[] upperBounds = new double[] { Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY }; - - /* - * Refine the bounding box using the Location Position Records - */ - if (locationEccRecords.isEmpty()) { - lowerBounds = new double[] { -earth.getRadius(), -earth.getRadius(), -earth.getRadius() }; - upperBounds = new double[] { earth.getRadius(), earth.getRadius(), earth.getRadius() }; - - } else { - for (LocationEccRecord rec : locationEccRecords) { - double[] positionArray = rec.ecc.toArray(); - for (int i = 0; i < 3; i++) { - lowerBounds[i] = FastMath.min(lowerBounds[i], positionArray[i]); - upperBounds[i] = FastMath.max(upperBounds[i], positionArray[i]); - } - } - } - /* - * Build the tree - */ - dimensionTree = DimensionTree .builder() // - .setLowerBounds(lowerBounds)// - .setUpperBounds(upperBounds)// - .build();// - - /* - * Load the tree with the Location Position Records - */ - locationEccRecords.forEach(rec -> { - dimensionTree.add(rec.ecc.toArray(), rec); - }); - } - - private double getLinearSearchRangeMeters(double groundRangeInMeters) { - double angle = groundRangeInMeters / earth.getRadius(); - Vector2D p = new Vector2D(FastMath.cos(angle), FastMath.sin(angle)); - Vector2D q = new Vector2D(1, 0); - return p.distanceTo(q) * earth.getRadius(); - } - - /** - * Returns the list of locations that are within the given radius in - * kilometers from the given lat-lon position. - */ - public List getLocations(double latDegrees, double lonDegrees, double radiusKilometers) { - double linearSearchRangeMeters = getLinearSearchRangeMeters(radiusKilometers * 1000); - double[] position = earth.getECCFromLatLonAlt(new LatLonAlt(latDegrees, lonDegrees, 0)).toArray(); - return dimensionTree.getMembersInSphere(linearSearchRangeMeters, position).stream()// - .map(rec -> rec.location)// - .collect(Collectors.toList());// - } - - /** - * Returns the list of locations that are within the given radius in - * kilometers from the given lat-lon position as Pairs of location and - * distance(km). The resulting pairs are returned in ascending order by - * distance. - */ - public List> getPrioritizedLocations(double latDegrees, double lonDegrees, double radiusKilometers) { - - double linearSearchRangeMeters = getLinearSearchRangeMeters(radiusKilometers * 1000); - - LatLonAlt latLonAlt = new LatLonAlt(latDegrees, lonDegrees, 0); - Vector3D ecc = earth.getECCFromLatLonAlt(latLonAlt); - double[] positionArray = ecc.toArray(); - - return dimensionTree.getMembersInSphere(linearSearchRangeMeters, positionArray).stream()// - .map(rec -> { - double distance = earth.getGroundDistanceFromECC(ecc, rec.ecc); - return new Pair<>(rec.location, distance / 1000); - })// - .sorted(Comparator.comparingDouble(Pair::getValue))// - .collect(Collectors.toList());// - } - - /** - * Returns the nearest location in this {@linkplain GeoLocator} to the given - * lat-lon position if any can be found. Ties for nearest location result in - * an arbitrary selection. - */ - public Optional getNearestLocation(double latDegrees, double lonDegrees) { - LatLonAlt latLonAlt = new LatLonAlt(latDegrees, lonDegrees, 0); - Vector3D ecc = earth.getECCFromLatLonAlt(latLonAlt); - double[] positionArray = ecc.toArray(); - - Optional> optional = dimensionTree.getNearestMember(positionArray); - if (optional.isPresent()) { - return Optional.of(optional.get().location); - } - return Optional.empty(); - } - -} diff --git a/gcm3/src/main/java/util/graph/Graph.java b/gcm3/src/main/java/util/graph/Graph.java deleted file mode 100644 index a03403fa7..000000000 --- a/gcm3/src/main/java/util/graph/Graph.java +++ /dev/null @@ -1,264 +0,0 @@ -package util.graph; - -import java.util.List; - -import net.jcip.annotations.Immutable; - -@Immutable -public final class Graph { - - protected final MutableGraph mutableGraph; - - Graph(MutableGraph mutableGraph) { - this.mutableGraph = mutableGraph; - } - - /** - * Returns a hash code that is the sum of the hash codes of its nodes and - * edges. - */ - @Override - public int hashCode() { - return mutableGraph.hashCode(); - } - - /** - * Returns true if and only if the given object is an instance of - * {@link Graph} or {@link MutableGraph} that contains the same nodes and - * edges as this {@link Graph} - */ - @Override - public boolean equals(Object obj) { - return mutableGraph.equals(obj); - } - - public static Builder builder() { - return new Builder<>(); - } - - //@Source(status = TestStatus.REQUIRED, proxy = Graph.class) - public static class Builder { - - private MutableGraph mutableGraph = new MutableGraph<>(); - - private Builder() { - - } - - public Graph build() { - try { - return new Graph<>(mutableGraph); - } finally { - mutableGraph = new MutableGraph<>(); - } - } - - /** - * Adds the node to this graph. - */ - public Builder addNode(N node) { - mutableGraph.addNode(node); - return this; - } - - /** - * Adds the edge to this graph, possibly replacing the edge if it is - * already in the graph. Adds the nodes if required. - */ - public Builder addEdge(E edge, N originNode, N destinationNode) { - mutableGraph.addEdge(edge, originNode, destinationNode); - return this; - } - - /** - * Adds the content of the given {@link Graph} to this {@link Graph} - */ - public Builder addAll(Graph graph) { - mutableGraph.addAll(graph); - return this; - } - - /** - * Adds the content of the given {@link MutableGraph} to this - * {@link MutableGraph} - */ - public Builder addAll(MutableGraph graph) { - mutableGraph.addAll(graph); - return this; - } - - } - - /** - * Returns true if and only if the node is contained in the graph. - * - * @param node - * @return - */ - public boolean containsNode(Object node) { - return mutableGraph.containsNode(node); - } - - /** - * Returns true if and only if the edge is contained in the graph. - * - * @param edge - * @return - */ - public boolean containsEdge(Object edge) { - return mutableGraph.containsEdge(edge); - } - - /** - * Returns the number of edges in this graph - * - * @return - */ - public int edgeCount() { - return mutableGraph.edgeCount(); - } - - /** - * Returns the destination node for the given edge. - * - * @param edge - * @return - */ - public N getDestinationNode(E edge) { - return mutableGraph.getDestinationNode(edge); - } - - /** - * Returns the origin node for the given edge. - * - * @param edge - * @return - */ - public N getOriginNode(E edge) { - return mutableGraph.getOriginNode(edge); - } - - /** - * Returns the number of nodes in this graph. - * - * @return - */ - public int nodeCount() { - return mutableGraph.nodeCount(); - } - - /** - * Returns true if and only if the nodeCount() is zero - * - * @return - */ - public boolean isEmpty() { - return mutableGraph.isEmpty(); - } - - /** - * Returns the number of edges going into the given node - * - * @param node - * @return - */ - public int getInboundEdgeCount(N node) { - return mutableGraph.getInboundEdgeCount(node); - } - - /** - * Returns the number of edges going into the given node - * - * @param node - * @return - */ - public int getOutboundEdgeCount(N node) { - return mutableGraph.getOutboundEdgeCount(node); - } - - /** - * Returns true if and only if the given edge connects the given origin and - * destination. - * - * @param edge - * @param origin - * @param destination - * @return - */ - public boolean formsEdgeRelationship(Object edge, Object origin, Object destination) { - return mutableGraph.formsEdgeRelationship(edge, origin, destination); - } - - /** - * Supplies an iterator over all nodes in the graph. - * - * @return - */ - public List getNodes() { - return mutableGraph.getNodes(); - } - - /** - * Supplies an iterable over the edges from the origin node to the - * destination node. - * - * @param originNode - * @param destinationNode - * @return - */ - public List getEdges() { - return mutableGraph.getEdges(); - } - - /** - * Supplies an iterator over all edges in the graph that have node as their - * destination. - * - * @param node - * @return - */ - public List getInboundEdges(N node) { - return mutableGraph.getInboundEdges(node); - } - - /** - * Supplies an iterator over all edges in the graph that have node as their - * origin. - * - * @param node - * @return - */ - public List getOutboundEdges(N node) { - return mutableGraph.getOutboundEdges(node); - } - - /** - * Supplies an iterator over all edges between the given nodes. - * - * @return - */ - public List getEdges(N originNode, N destinationNode) { - return mutableGraph.getEdges(originNode, destinationNode); - } - - /** - * Returns the number of edges in this graph from the origin node to the - * destination node. - * - * @return - */ - public int edgeCount(N originNode, N destinationNode) { - return mutableGraph.edgeCount(originNode, destinationNode); - } - - /** - * Returns a new {@link MutableGraph} instance from the contents of this - * {@link Graph} - */ - public MutableGraph toMutableGraph() { - MutableGraph result = new MutableGraph<>(); - result.addAll(this); - return result; - } - -} diff --git a/gcm3/src/main/java/util/graph/GraphDepthEvaluator.java b/gcm3/src/main/java/util/graph/GraphDepthEvaluator.java deleted file mode 100644 index f4cf7a5b2..000000000 --- a/gcm3/src/main/java/util/graph/GraphDepthEvaluator.java +++ /dev/null @@ -1,124 +0,0 @@ -package util.graph; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.TreeMap; - -import org.apache.commons.math3.util.FastMath; - -import net.jcip.annotations.Immutable; - -/** - * GraphDepthEvaluator is a convenience utility designed to work with acyclic - * graphs that represent dependencies between nodes. Its is used to establish an - * ordering of nodes such that nodes that have no dependencies on other nodes - * are deemed as rank zero, nodes that depend only on rank zero nodes are deemed - * rank 1 and so on. - * - * Dependency in the graph is represented by edges. An edge directed from node A - * to node B is taken to mean that A depends on B. - * - * @author Shawn Hatch - * - */ -@Immutable -public final class GraphDepthEvaluator { - - private GraphDepthEvaluator(Graph graph) { - MutableGraph mutableGraph = new MutableGraph<>(); - mutableGraph.addAll(graph); - int depth = 0; - while (mutableGraph.nodeCount() > 0) { - List nodes = new ArrayList<>(); - - for (N node : mutableGraph.getNodes()) { - if (mutableGraph.getOutboundEdgeCount(node) == 0) { - nodes.add(node); - } - } - - for (N node : nodes) { - mutableGraph.removeNode(node); - } - - depthToNodeSetMap.put(depth, nodes); - for (N node : nodes) { - nodeToDepthMap.put(node, depth); - } - depth++; - - } - maxDepth = FastMath.max(0, depth - 1); - - } - - private final Map> depthToNodeSetMap = new TreeMap<>(); - - private final Map nodeToDepthMap = new LinkedHashMap<>(); - - private final int maxDepth; - - /** - * Returns the maximum depth for any node in the graph. Returns 0 for empty - * graphs. - */ - public int getMaxDepth() { - return maxDepth; - } - - /** - * Returns the depth for the given node. Depth 0 nodes have no predecessors. - * Depth k nodes have predecessors of depth less than k for k>0. - */ - public int getDepth(N node) { - Integer result = nodeToDepthMap.get(node); - if (result == null) { - result = -1; - } - return result; - } - - /** - * Returns the nodes associated with the given depth. Will return an empty - * list for depth values that are negative or exceed the max depth - */ - public List getNodesForDepth(int depth) { - List result = new ArrayList<>(); - List nodes = depthToNodeSetMap.get(depth); - if (nodes != null) { - result.addAll(nodes); - } - return result; - } - - /** - * Returns the nodes of the graph in their ascending rank orders. Within a - * rank, the order is arbitrary but repeatable across instances of - * {@link GraphDepthEvaluator} for any given {@link Graph}. - */ - public List getNodesInRankOrder() { - List result = new ArrayList<>(); - for (List nodes : depthToNodeSetMap.values()) { - result.addAll(nodes); - } - return result; - } - - /** - * Static constructor for {@link GraphDepthEvaluator} that orders the nodes - * of the given graph by the dependency relationship represented by the - * graph's edges. Returns {@link Optional#empty()} if the graph contains any - * cycles. - */ - public static Optional> getGraphDepthEvaluator(Graph graph) { - if (Graphs.getGraphCyclisity(graph) == Graphs.GraphCyclisity.ACYCLIC) { - GraphDepthEvaluator result = new GraphDepthEvaluator<>(graph); - return Optional.of(result); - } - return Optional.empty(); - } - -} diff --git a/gcm3/src/main/java/util/graph/Graphs.java b/gcm3/src/main/java/util/graph/Graphs.java deleted file mode 100644 index 090be736f..000000000 --- a/gcm3/src/main/java/util/graph/Graphs.java +++ /dev/null @@ -1,236 +0,0 @@ -package util.graph; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; - -public final class Graphs { - - // hidden constructor - private Graphs() { - - } - - public static enum GraphCyclisity { - /** - * The graph contains at least one cycle. - */ - CYCLIC, - - /** - * The graph contains no cycles. Empty graphs are acyclic - */ - ACYCLIC, - - } - - /** - * Returns the sub-graph of the input graph that has had all of its acyclic - * parts (sources and sinks) removed. That is, the resultant graph will - * contain only cycles. This method is commonly used to determine whether a - * graph is acyclic, i.e. the returned graph is empty, with no nodes. - */ - public static Graph getSourceSinkReducedGraph(Graph graph) { - MutableGraph mutableGraph = new MutableGraph<>(); - mutableGraph.addAll(graph); - boolean nodeRemoved = true; - while (nodeRemoved) { - nodeRemoved = false; - for (N node : mutableGraph.getNodes()) { - if ((mutableGraph.getInboundEdgeCount(node) == 0) || (mutableGraph.getOutboundEdgeCount(node) == 0)) { - mutableGraph.removeNode(node); - nodeRemoved = true; - } - } - } - return mutableGraph.asGraph(); - } - - /** - * Returns the GraphCyclisity of the given graph - * - */ - public static GraphCyclisity getGraphCyclisity(Graph graph) { - - Graph sourceSinkReducedGraph = getSourceSinkReducedGraph(graph); - - if (sourceSinkReducedGraph.nodeCount() == 0) { - return GraphCyclisity.ACYCLIC; - } - - return GraphCyclisity.CYCLIC; - - } - - /** - * - * An enumeration of the three types of graph connectedness. - * - * Strongly connected graphs are ones in which for every pair (not - * necessarily distinct) of nodes in the graph there exits a Path connecting - * those nodes. Note that a node is not implicitly connected to itself. - * - * Weakly connected graphs are ones where the nodes connect to one another - * if edge directionality is ignored. Formally, let graph G exist with edges - * E and nodes N. Construct a graph GPrime with all of N and E(i.e. a copy - * of G). For each edge e in E, add an oppositely directed to GPrime. G is - * weakly connected graph if and only if GPrime is strongly connected. - * - * Disconnected graphs are those that are neither strongly nor weakly - * connected. - * - * Note that strongly connected graphs are weakly connected. Empty graphs - * are considered strongly connected. - * - * @author Shawn Hatch - * - */ - public static enum GraphConnectedness { - STRONGLYCONNECTED, - - WEAKLYCONNECTED, - - DISCONNECTED - } - - /** - * - * Determines the connectedness of a graph - * - */ - public static GraphConnectedness getGraphConnectedness(Graph graph) { - if (cutGraph(graph).size() == 1) { - Graph sourceSinkReducedGraph = getSourceSinkReducedGraph(graph); - - if (sourceSinkReducedGraph.nodeCount() == graph.nodeCount()) { - return GraphConnectedness.STRONGLYCONNECTED; - } - return GraphConnectedness.WEAKLYCONNECTED; - } - return GraphConnectedness.DISCONNECTED; - } - - /** - * - * Separates a graph into a set of independent weakly connected and - * disconnected sub graphs. All disconnected subgraphs will consist of - * single nodes with no edges. - * - */ - public static List> cutGraph(Graph graph) { - // create a list to receive the graphs - List> result = new ArrayList<>(); - - // create a map to hold all node pairs without regard to directionality - // of the underlying edges. Each node will now have a set of nodes - // associated with it that are linked by edges. - Map> connectionMap = new LinkedHashMap<>(); - - for (E edge : graph.getEdges()) { - - N originNode = graph.getOriginNode(edge); - N destinationNode = graph.getDestinationNode(edge); - - Set set; - - set = connectionMap.get(originNode); - if (set == null) { - set = new LinkedHashSet<>(); - connectionMap.put(originNode, set); - } - set.add(destinationNode); - - set = connectionMap.get(destinationNode); - if (set == null) { - set = new LinkedHashSet<>(); - connectionMap.put(destinationNode, set); - } - set.add(originNode); - } - - // We visit nodes in the graph and explore outward until we cannot find - // any more nodes. Each time that we can no longer find a new node, we - // construct a - // resultant graph and move on to a new node from the main node iterator - - Set visitedNodes = new LinkedHashSet<>(); - LinkedList nodesToVisit = new LinkedList<>(); - N visitedNode; - List nodesForNextGraph; - - for (N node : graph.getNodes()) { - // if we have already explored this node, then there is nothing to - // do - if (!visitedNodes.contains(node)) { - // we have decided to explore the node - nodesToVisit.add(node); - // we create a list to hold all the nodes that result from this - // exploration - nodesForNextGraph = new ArrayList<>(); - // so long as there are more nodes to visit attached to the - // original node, we keep exploring - while (nodesToVisit.size() > 0) { - // we take a node from those to visit and put it on the node - // for the next graph - visitedNode = nodesToVisit.remove(); - nodesForNextGraph.add(visitedNode); - // we also put it on the set of nodes that we have visited - // to keep us from exploring the node redundantly - visitedNodes.add(visitedNode); - // for each node that was connected, we add that node to the - // nodes still left to visit if we have not already visited - // that node - Set set = connectionMap.get(visitedNode); - if (set != null) { - for (N linkedNode : set) { - if (!visitedNodes.contains(linkedNode)) { - nodesToVisit.add(linkedNode); - } - } - } - } - - // we now construct a result graph from the nodesForNextGraph - Graph.Builder builder = Graph.builder(); - for (N node2 : nodesForNextGraph) { - builder.addNode(node2); - } - for (N originNode : nodesForNextGraph) { - for (E edge : graph.getOutboundEdges(originNode)) { - N destinationNode = graph.getDestinationNode(edge); - builder.addEdge(edge, originNode, destinationNode); - } - } - // finally, we create a graph to add to the outgoing - // list of graphs - result.add(builder.build()); - } - } - return result; - } - - /** - * Returns a graph that has the same nodes and edges as the given graph, - * with each edge being reversed - */ - public static Graph getReverseGraph(Graph graph) { - - Graph.Builder builder = Graph.builder(); - - for (N node : graph.getNodes()) { - builder.addNode(node); - } - - for (E edge : graph.getEdges()) { - N destinationNode = graph.getDestinationNode(edge); - N originNode = graph.getOriginNode(edge); - builder.addEdge(edge, destinationNode, originNode); - } - return builder.build(); - } - -} diff --git a/gcm3/src/main/java/util/graph/MutableGraph.java b/gcm3/src/main/java/util/graph/MutableGraph.java deleted file mode 100644 index 87d57fa39..000000000 --- a/gcm3/src/main/java/util/graph/MutableGraph.java +++ /dev/null @@ -1,465 +0,0 @@ -package util.graph; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * - * A generics-based, mutable graph of nodes(N) and edges(E). All iterators are - * immutable and will throw UnsupportedOperationException runtime exception on - * remove(). - * - * @author Shawn Hatch - * - */ -public final class MutableGraph { - - private Set nodes = new LinkedHashSet<>(); - - private Set edges = new LinkedHashSet<>(); - - private Map> inEdges = new LinkedHashMap<>(); - - private Map> outEdges = new LinkedHashMap<>(); - - private Map originNodeMap = new LinkedHashMap<>(); - - private Map destinationNodeMap = new LinkedHashMap<>(); - - private Map>> edgesMap = new LinkedHashMap<>(); - - /** - * Adds the content of the given {@link Graph} to this {@link MutableGraph} - */ - public void addAll(Graph graph) { - - for (N node : graph.getNodes()) { - addNode(node); - } - - for (E edge : graph.getEdges()) { - N originNode = graph.getOriginNode(edge); - N destinationNode = graph.getDestinationNode(edge); - addEdge(edge, originNode, destinationNode); - } - - } - - /* - * Returns a {@link Graph} backed by this {@link MutableGraph}. Mutations in - * this {@link MutableGraph} will be reflected in the resultant {@link - * Graph} - * - * This method is package access only and should only be used to reduce the - * overhead costs of creating Graphs from MutableGraphs. - */ - Graph asGraph() { - return new Graph<>(this); - } - - /** - * Returns a new {@link Graph} instance from the contents of this - * {@link MutableGraph} - */ - public Graph toGraph() { - Graph.Builder builder = Graph.builder(); - builder.addAll(this); - return builder.build(); - } - - /** - * Adds the content of the given {@link MutableGraph} to this - * {@link MutableGraph} - */ - public void addAll(MutableGraph graph) { - - for (N node : graph.getNodes()) { - addNode(node); - } - - for (E edge : graph.getEdges()) { - N originNode = graph.getOriginNode(edge); - N destinationNode = graph.getDestinationNode(edge); - addEdge(edge, originNode, destinationNode); - } - - } - - /** - * Adds the edge to this graph, possibly replacing the edge if it is already - * in the graph. Adds the nodes if required. - */ - public void addEdge(E edge, N originNode, N destinationNode) { - removeEdge(edge); - addNode(originNode); - addNode(destinationNode); - edges.add(edge); - inEdges.get(destinationNode).add(edge); - outEdges.get(originNode).add(edge); - originNodeMap.put(edge, originNode); - destinationNodeMap.put(edge, destinationNode); - - Map> map = edgesMap.get(originNode); - if (map == null) { - map = new LinkedHashMap<>(); - edgesMap.put(originNode, map); - } - Set someEdges = map.get(destinationNode); - if (someEdges == null) { - someEdges = new LinkedHashSet<>(); - map.put(destinationNode, someEdges); - } - someEdges.add(edge); - - } - - /** - * Adds the node to this graph. - */ - public void addNode(N node) { - if (!nodes.contains(node)) { - nodes.add(node); - inEdges.put(node, new LinkedHashSet()); - outEdges.put(node, new LinkedHashSet()); - } - } - - /** - * Returns true if and only if the edge is contained in the graph. - * - * @param edge - * @return - */ - public boolean containsEdge(Object edge) { - return edges.contains(edge); - } - - /** - * Returns true if and only if the node is contained in the graph. - * - * @param node - * @return - */ - public boolean containsNode(Object node) { - return nodes.contains(node); - } - - /** - * Returns the number of edges in this graph - * - * @return - */ - public int edgeCount() { - return edges.size(); - } - - /** - * Returns true if and only if the given edge connects the given origin and - * destination. - * - * @param edge - * @param origin - * @param destination - * @return - */ - public boolean formsEdgeRelationship(Object edge, Object origin, Object destination) { - N originNode = originNodeMap.get(edge); - if (originNode == null) { - return false; - } - if (!originNode.equals(origin)) { - return false; - } - N destinationNode = destinationNodeMap.get(edge); - - if (!destinationNode.equals(destination)) { - return false; - } - return true; - } - - /** - * Returns the destination node for the given edge. - * - * @param edge - * @return - */ - public N getDestinationNode(E edge) { - return destinationNodeMap.get(edge); - } - - /** - * Supplies an iterator over all edges in the graph. - * - * @return - */ - public List getEdges() { - return new ArrayList<>(edges); - } - - /** - * Supplies a list over all edges between the given nodes. - * - * @return - */ - public List getEdges(N originNode, N destinationNode) { - Map> map = edgesMap.get(originNode); - if (map == null) { - return new ArrayList<>(); - } - Set someEdges = map.get(destinationNode); - if (someEdges == null) { - return new ArrayList<>(); - } - return new ArrayList<>(someEdges); - } - - /** - * Returns the number of edges going into the given node - * - * @param node - * @return - */ - public int getInboundEdgeCount(N node) { - Set set = inEdges.get(node); - if (set == null) { - return 0; - } - return set.size(); - } - - /** - * Supplies an iterator over all edges in the graph that have node as their - * destination. - * - * @param node - * @return - */ - public List getInboundEdges(N node) { - Set set = inEdges.get(node); - if (set == null) { - return new ArrayList<>(); - } - return new ArrayList<>(inEdges.get(node)); - } - - /** - * Supplies an iterator over all nodes in the graph. - * - * @return - */ - public List getNodes() { - return new ArrayList<>(nodes); - } - - /** - * Returns the origin node for the given edge. - * - * @param edge - * @return - */ - public N getOriginNode(E edge) { - return originNodeMap.get(edge); - } - - /** - * Returns the number of edges going into the given node - * - * @param node - * @return - */ - public int getOutboundEdgeCount(N node) { - Set set = outEdges.get(node); - if (set == null) { - return 0; - } - return set.size(); - - } - - /** - * Supplies an iterator over all edges in the graph that have node as their - * origin. - * - * @param node - * @return - */ - public List getOutboundEdges(N node) { - Set set = outEdges.get(node); - if (set == null) { - return new ArrayList<>(); - } - return new ArrayList<>(outEdges.get(node)); - } - - /** - * Returns true if and only if the nodeCount() is zero - * - * @return - */ - public boolean isEmpty() { - return nodeCount() == 0; - } - - /** - * Returns the number of nodes in this graph. - * - * @return - */ - public int nodeCount() { - return nodes.size(); - } - - /** - * Removes the edge from the graph. - */ - public void removeEdge(E edge) { - if (!edges.contains(edge)) { - return; - } - - N originNode = originNodeMap.get(edge); - outEdges.get(originNode).remove(edge); - - N destinationNode = destinationNodeMap.get(edge); - inEdges.get(destinationNode).remove(edge); - - edges.remove(edge); - destinationNodeMap.remove(edge); - originNodeMap.remove(edge); - - Map> map = edgesMap.get(originNode); - if (map != null) { - Set someEdges = map.get(destinationNode); - if (someEdges != null) { - someEdges.remove(edge); - if (someEdges.size() == 0) { - map.remove(destinationNode); - } - } - if (map.size() == 0) { - edgesMap.remove(originNode); - } - } - } - - /** - * Removes the node from the graph, removing any associated edges. - */ - public void removeNode(N node) { - if (!nodes.contains(node)) { - return; - } - List list = new ArrayList<>(); - list.addAll(inEdges.get(node)); - list.addAll(outEdges.get(node)); - for (E edge : list) { - removeEdge(edge); - } - inEdges.remove(node); - outEdges.remove(node); - nodes.remove(node); - - } - - /** - * Returns the number of edges in this graph from the origin node to the - * destination node. - * - * @return - */ - public int edgeCount(N originNode, N destinationNode) { - Map> map = edgesMap.get(originNode); - if (map == null) { - return 0; - } - Set someEdges = map.get(destinationNode); - if (someEdges == null) { - return 0; - } - return someEdges.size(); - - } - - /** - * Returns a hash code that is the sum of the hash codes of its nodes and - * edges. - */ - @Override - public int hashCode() { - int result = 0; - - for (N node : getNodes()) { - result += node.hashCode(); - } - - for (E edge : getEdges()) { - result += edge.hashCode(); - } - return result; - } - - /** - * Returns true if and only if the given object is an instance of - * {@link Graph} or {@link MutableGraph} that contains the same nodes and - * edges as this {@link MutableGraph} - */ - - @SuppressWarnings("rawtypes") - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - - MutableGraph other; - if (obj instanceof Graph) { - Graph graph = (Graph) obj; - other = graph.mutableGraph; - } else if (obj instanceof MutableGraph) { - other = (MutableGraph) obj; - } else { - return false; - } - - if (other.nodeCount() != nodeCount()) { - return false; - } - if (other.edgeCount() != edgeCount()) { - return false; - } - - if (nodeCount() != other.nodeCount()) { - return false; - } - - for (N node : getNodes()) { - if (!other.containsNode(node)) { - return false; - } - } - - if (edgeCount() != other.edgeCount()) { - return false; - } - - for (E edge : getEdges()) { - if (!other.containsEdge(edge)) { - return false; - } - N originNode = getOriginNode(edge); - N destinationNode = getDestinationNode(edge); - other.formsEdgeRelationship(edge, originNode, destinationNode); - } - - return true; - } - -} diff --git a/gcm3/src/main/java/util/path/ArrayPathSolver.java b/gcm3/src/main/java/util/path/ArrayPathSolver.java deleted file mode 100644 index 941563b20..000000000 --- a/gcm3/src/main/java/util/path/ArrayPathSolver.java +++ /dev/null @@ -1,109 +0,0 @@ -package util.path; - -import java.lang.reflect.Array; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import util.graph.Graph; -import util.path.Paths.EdgeCostEvaluator; -import util.path.Paths.TravelCostEvaluator; - -/** - * Manages shortest path solutions for a given graph with reasonable efficiency - * using arrays of previous path solutions and their sub-paths. - * - * @author Shawn Hatch - * - */ -public final class ArrayPathSolver implements PathSolver { - - private Graph graph; - - private EdgeCostEvaluator edgeCostEvaluator; - - private TravelCostEvaluator travelCostEvaluator; - - @SuppressWarnings("unchecked") - public ArrayPathSolver(Graph graph, EdgeCostEvaluator edgeCostEvaluator, TravelCostEvaluator travelCostEvaluator) { - this.graph = graph; - this.edgeCostEvaluator = edgeCostEvaluator; - this.travelCostEvaluator = travelCostEvaluator; - int n = graph.nodeCount(); - navigationArray = (E[][]) Array.newInstance(Object.class, n, n); - for (N node : graph.getNodes()) { - nodeMap.put(node, nodeMap.size()); - } - } - - private E[][] navigationArray; - - private Map nodeMap = new LinkedHashMap<>(); - - @Override - public Optional> getPath(N originNode, N destinationNode) { - - if (!graph.containsNode(originNode)) { - return Optional.empty(); - } - - if (!graph.containsNode(destinationNode)) { - return Optional.empty(); - } - - Integer originIndex = nodeMap.get(originNode); - Integer destinationIndex = nodeMap.get(destinationNode); - E e = navigationArray[originIndex][destinationIndex]; - if (e == null) { - if (!solve(originNode, destinationNode)) { - return Optional.empty(); - } - } - - Path.Builder pathBuilder = Path.builder(); - while (true) { - e = navigationArray[originIndex][destinationIndex]; - if (e == null) { - return Optional.empty(); - } - pathBuilder.addEdge(e); - originIndex = nodeMap.get(graph.getDestinationNode(e)); - if (originIndex == destinationIndex) { - break; - } - } - return Optional.of(pathBuilder.build()); - } - - private boolean solve(N origin, N destination) { - - Optional> optional = Paths.getPath(graph, origin, destination, edgeCostEvaluator, travelCostEvaluator); - if (!optional.isPresent()) { - return false; - } - - Path path = optional.get(); - - List originList = new ArrayList<>(); - List destinationList = new ArrayList<>(); - List edges = path.getEdges(); - for (E edge : edges) { - originList.add(graph.getOriginNode(edge)); - destinationList.add(graph.getDestinationNode(edge)); - } - - int n = originList.size(); - for (int i = 0; i < n; i++) { - int sourceIndex = nodeMap.get(originList.get(i)); - for (int j = i; j < n; j++) { - int targetIndex = nodeMap.get(destinationList.get(j)); - navigationArray[sourceIndex][targetIndex] = edges.get(i); - } - } - return true; - - } - -} diff --git a/gcm3/src/main/java/util/path/MapPathSolver.java b/gcm3/src/main/java/util/path/MapPathSolver.java deleted file mode 100644 index 89024d7e9..000000000 --- a/gcm3/src/main/java/util/path/MapPathSolver.java +++ /dev/null @@ -1,113 +0,0 @@ -package util.path; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import util.graph.Graph; -import util.path.Paths.EdgeCostEvaluator; -import util.path.Paths.TravelCostEvaluator; -import util.wrappers.MultiKey; - -/** - * Manages shortest path solutions for a given graph with reasonable efficiency - * using maps of previous path solutions and their sub-paths. - * - * @author Shawn Hatch - * - */ -public class MapPathSolver implements PathSolver { - - private static class SubPath { - - Path basePath; - - Path solvedPath; - - int startIndex; - - int stopIndex; - } - - private Map> pathMap = new LinkedHashMap<>(); - - private Graph graph; - - private Map> subPathMap = new LinkedHashMap<>(); - - private EdgeCostEvaluator edgeCostEvaluator; - - private TravelCostEvaluator travelCostEvaluator; - - /** - * Create a path solver for the given graph using the edge cost evaluator - * and travel cost evaluator - * - * @throws NullPointerException - *
          • if the graph is null
          • - *
          • if the edge cost evaluator is null
          • - *
          • if the travel cost evaluator is null
          • - * - * - */ - public MapPathSolver(Graph graph, EdgeCostEvaluator edgeCostEvaluator, TravelCostEvaluator travelCostEvaluator) { - if (graph == null) { - throw new NullPointerException("graph is null"); - } - if (edgeCostEvaluator == null) { - throw new NullPointerException("edge cost evaluator is null"); - } - if (travelCostEvaluator == null) { - throw new NullPointerException("travel cost evaluator is null"); - } - - this.graph = graph; - this.edgeCostEvaluator = edgeCostEvaluator; - this.travelCostEvaluator = travelCostEvaluator; - } - - @Override - public Optional> getPath(N originNode, N destinationNode) { - Path result; - MultiKey key = new MultiKey(originNode, destinationNode); - SubPath subPath = subPathMap.get(key); - if (subPath != null) { - if (subPath.solvedPath == null) { - List edges = subPath.basePath.getEdges(); - List subEdges = new ArrayList<>(); - for (int i = subPath.startIndex; i < subPath.stopIndex; i++) { - subEdges.add(edges.get(i)); - } - Path.Builder builder = Path.builder(); - subEdges.forEach(edge -> builder.addEdge(edge)); - subPath.solvedPath = builder.build(); - } - result = subPath.solvedPath; - } else { - Optional> optional = Paths.getPath(graph, originNode, destinationNode, edgeCostEvaluator, travelCostEvaluator); - if (!optional.isPresent()) { - return Optional.empty(); - } - - result = optional.get(); - List edges = result.getEdges(); - for (int i = 0; i < edges.size(); i++) { - N startNode = graph.getOriginNode(edges.get(i)); - for (int j = i; j < edges.size(); j++) { - N endNode = graph.getDestinationNode(edges.get(j)); - key = new MultiKey(startNode, endNode); - subPath = new SubPath<>(); - subPath.basePath = result; - subPath.startIndex = i; - subPath.stopIndex = j + 1; - subPathMap.put(key, subPath); - } - } - } - pathMap.put(key, result); - return Optional.of(result); - } - -} diff --git a/gcm3/src/main/java/util/path/Path.java b/gcm3/src/main/java/util/path/Path.java deleted file mode 100644 index e0fd6357d..000000000 --- a/gcm3/src/main/java/util/path/Path.java +++ /dev/null @@ -1,115 +0,0 @@ -package util.path; - -import java.util.ArrayList; -import java.util.List; - -import net.jcip.annotations.Immutable; - -/** - * A Path is an ordered a walk through a set of nodes by way of edges without - * any breaks. A path may be crossing or cyclic, with the getEdges() list - * containing repeated edges. A path may be a degenerate, having no edges. - * Degenerate paths are the expected results for paths that do not exist. - * - * @author Shawn Hatch - * - */ -@Immutable -public final class Path { - - /** - * Returns a new instance of the Builder class - */ - public static Builder builder() { - return new Builder<>(); - } - - public static class Builder { - - private Builder() { - } - - private List edges = new ArrayList<>(); - - public Builder addEdge(T edge) { - edges.add(edge); - return this; - } - - public Path build() { - try { - return new Path<>(edges); - } finally { - edges = new ArrayList<>(); - } - } - } - - private Path(List edges) { - this.edges = edges; - } - - private final List edges; - - /** - * Returns a list over the edges in the path walk. Note that the path may - * cross itself (revisit nodes) and even repeat edges. - * - */ - public List getEdges() { - return new ArrayList<>(edges); - } - - /** - * Returns the number of edges in the path walk through the graph. Note that - * this is NOT necessarily the same value as returned by the edgeCount() - * method, but rather returns a value that may be higher due to revisited - * edges. - * - */ - - public int length() { - return edges.size(); - } - - /** - * Returns true if and only if the path contains no edges - * - * @return - */ - public boolean isEmpty() { - return edges.size() == 0; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((edges == null) ? 0 : edges.hashCode()); - return result; - } - - @SuppressWarnings("rawtypes") - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (!(obj instanceof Path)) { - return false; - } - Path other = (Path) obj; - if (edges == null) { - if (other.edges != null) { - return false; - } - } else if (!edges.equals(other.edges)) { - return false; - } - return true; - } - -} diff --git a/gcm3/src/main/java/util/path/PathSolver.java b/gcm3/src/main/java/util/path/PathSolver.java deleted file mode 100644 index cbf3b21bb..000000000 --- a/gcm3/src/main/java/util/path/PathSolver.java +++ /dev/null @@ -1,16 +0,0 @@ -package util.path; - -import java.util.Optional; - -/** - * Interface for path solvers that retain previously solved paths. - * - * @author Shawn Hatch - * - * - */ -public interface PathSolver { - - public Optional> getPath(N originNode, N destinationNode); - -} diff --git a/gcm3/src/main/java/util/path/Paths.java b/gcm3/src/main/java/util/path/Paths.java deleted file mode 100644 index 73065deda..000000000 --- a/gcm3/src/main/java/util/path/Paths.java +++ /dev/null @@ -1,242 +0,0 @@ -package util.path; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.PriorityQueue; - -import util.graph.Graph; -import util.path.Path.Builder; - -/** - * - * Solves a shortest path through a graph from an origin node to a destination - * node.The solver uses two optional, auxiliary, client supplied objects: 1) an - * EdgeCostEvaluator which returns the cost of an edge and 2) a - * TravelCostEvaluator which determines the shortest possible cost across the - * graph from one node to another. - * - * The EdgeCostEvaluator should always return a non-negative value and should - * return a stable value over the life-span of this utility. If the - * EdgeCostEvaluator is null, the cost of an edge is set arbitrarily to 1. - * - * The TravelCostEvaluator is also optional and exists to give the solver - * insight into long distance costs. It should return a stable non-negative - * value. - * - * A typical TravelCostEvaluator example would be for nodes in a graph that - * represent physical positions. The TravelCostEvaluator could return the - * straight-line minimum distance between nodes. Note that the nodes do not have - * to share an edge nor even be connected in the graph. By supplying a - * TravelCostEvaluator, the client can greatly improve this solver's performance - * in large networks where nodes represent positions in space. Essentially, the - * TravelCostEvaluator allows the algorithm to expand its exploration of the - * graph in an ellipse whose major axis is aligned to the origin and - * destination, rather than doing a expanding sphere search. - * - * If a path cannot be found then a degenerate, node-less path is returned. - * - * @author Shawn Hatch - * - */ -public final class Paths { - - public interface EdgeCostEvaluator { - public double getEdgeCost(E edge); - } - - public static interface TravelCostEvaluator { - public double getMinimumCost(N originNode, N destination); - } - - private static class CostedNode { - - CostedNode(N n, double auxillaryCost) { - this.n = n; - this.auxillaryCost = auxillaryCost; - } - - private boolean visited; - - private final N n; - - private double cost = -1; - - private final double auxillaryCost; - - private E edge; - - } - - private static class PrioritizedNode implements Comparable> { - - private PrioritizedNode(CostedNode nodeWrapper) { - this.costedNode = nodeWrapper; - this.cost = nodeWrapper.cost + nodeWrapper.auxillaryCost; - } - - private final CostedNode costedNode; - - private final double cost; - - @Override - public int compareTo(PrioritizedNode other) { - return Double.compare(cost, other.cost); - } - - } - - private Paths() { - - } - - /** - * Returns an Optional containing a Path of E if such path could be found. - * - * @throws NullPointerException - *
          • if the graph is null
          • - *
          • if the origin node is null
          • - *
          • if the destination node null
          • - *
          • if the edge cost evaluator is null
          • - *
          • if the travel cost evaluator is null
          • - * - */ - public static Optional> getPath(Graph graph, N originNode, N destinationNode, EdgeCostEvaluator edgeCostEvaluator, TravelCostEvaluator travelCostEvaluator) { - - if (graph == null) { - throw new NullPointerException("graph is null"); - } - - if (originNode == null) { - throw new NullPointerException("origin node is null"); - } - - if (destinationNode == null) { - throw new NullPointerException("destination node is null"); - } - - if (edgeCostEvaluator == null) { - throw new NullPointerException("edge cost evaluator is null"); - } - - if (travelCostEvaluator == null) { - throw new NullPointerException("travel cost evaluator is null"); - } - - if (!graph.containsNode(originNode)) { - return Optional.empty(); - } - - if (!graph.containsNode(destinationNode)) { - return Optional.empty(); - } - - // The use of a HashMap instead of a linked hash map is intentional and - // implementors should guard against repercussions of iteration over - // this map. - final Map> map = new HashMap<>(); - PriorityQueue> priorityQueue = new PriorityQueue<>(); - - CostedNode originNodeWrapper = new CostedNode<>(originNode, travelCostEvaluator.getMinimumCost(originNode, destinationNode)); - map.put(originNode, originNodeWrapper); - - // Note that the first node placed on the queue will be unvisited and - // therefore has an invalid cost. This should not matter since it is the - // only node on the queue - priorityQueue.add(new PrioritizedNode<>(originNodeWrapper)); - - while (!priorityQueue.isEmpty()) { - - // pop off the first element - CostedNode pushNodeWrapper = priorityQueue.remove().costedNode; - if (pushNodeWrapper.visited) { - continue; - } - pushNodeWrapper.visited = true; - - CostedNode destinationNodeWrapper = map.get(destinationNode); - if (destinationNodeWrapper != null) { - if (destinationNodeWrapper.cost >= 0) { - if (pushNodeWrapper.cost >= destinationNodeWrapper.cost) { - break; - } - } - } - - for (E edge : graph.getOutboundEdges(pushNodeWrapper.n)) { - N targetNode = graph.getDestinationNode(edge); - CostedNode targetNodeWrapper = map.get(targetNode); - double edgeCost = edgeCostEvaluator.getEdgeCost(edge); - - if (Double.isInfinite(edgeCost)) { - continue; - } - if (pushNodeWrapper.cost >= 0) { - edgeCost += pushNodeWrapper.cost; - } - - if (targetNodeWrapper == null) { - targetNodeWrapper = new CostedNode<>(targetNode, travelCostEvaluator.getMinimumCost(targetNode, destinationNode)); - targetNodeWrapper.cost = edgeCost; - targetNodeWrapper.edge = edge; - map.put(targetNode, targetNodeWrapper); - priorityQueue.add(new PrioritizedNode<>(targetNodeWrapper)); - } else { - if ((targetNodeWrapper.cost < 0) || (edgeCost < targetNodeWrapper.cost)) { - targetNodeWrapper.cost = edgeCost; - targetNodeWrapper.edge = edge; - priorityQueue.add(new PrioritizedNode<>(targetNodeWrapper)); - } - } - } - } - - CostedNode destinationWrapper = map.get(destinationNode); - List edges = new ArrayList<>(); - // assess whether we have a solution - - if ((destinationWrapper != null) && (destinationWrapper.cost >= 0)) { - - N node = destinationNode; - - while (true) { - - CostedNode visitedNodeWrapper = map.get(node); - E edge = visitedNodeWrapper.edge; - visitedNodeWrapper.edge = null; - if (edge == null) { - break; - } - edges.add(edge); - node = graph.getOriginNode(edge); - if (node.equals(originNode)) { - break; - } - } - Collections.reverse(edges); - } - - if (edges.isEmpty()) { - return Optional.empty(); - } - - Builder builder = Path.builder(); - for (E edge : edges) { - builder.addEdge(edge); - } - - return Optional.of(builder.build()); - } - - public static double getCost(Path path, EdgeCostEvaluator edgeCostEvaluator) { - double result = 0; - for (E edge : path.getEdges()) { - result += edgeCostEvaluator.getEdgeCost(edge); - } - return result; - } - -} \ No newline at end of file diff --git a/gcm3/src/main/java/util/random/RandomGeneratorProvider.java b/gcm3/src/main/java/util/random/RandomGeneratorProvider.java deleted file mode 100644 index 3fb2486f5..000000000 --- a/gcm3/src/main/java/util/random/RandomGeneratorProvider.java +++ /dev/null @@ -1,15 +0,0 @@ -package util.random; - -import org.apache.commons.math3.random.RandomGenerator; -import org.apache.commons.math3.random.Well44497b; - -public class RandomGeneratorProvider { - - private RandomGeneratorProvider() { - } - - public static RandomGenerator getRandomGenerator(long seed) { - return new Well44497b(seed); - } - -} diff --git a/gcm3/src/main/java/util/spherical/Chirality.java b/gcm3/src/main/java/util/spherical/Chirality.java deleted file mode 100644 index 68c571972..000000000 --- a/gcm3/src/main/java/util/spherical/Chirality.java +++ /dev/null @@ -1,13 +0,0 @@ -package util.spherical; - -/** - * Enumeration representing the chirality of triangle vertex ordering on the - * surface of a sphere. - * - * @author Shawn Hatch - * - */ -public enum Chirality { - RIGHT_HANDED, // counter clockwise - LEFT_HANDED;// clockwise -} diff --git a/gcm3/src/main/java/util/spherical/MalformedSphericalArcException.java b/gcm3/src/main/java/util/spherical/MalformedSphericalArcException.java deleted file mode 100644 index 0f16660f5..000000000 --- a/gcm3/src/main/java/util/spherical/MalformedSphericalArcException.java +++ /dev/null @@ -1,20 +0,0 @@ -package util.spherical; - -/** - * A RuntimeException thrown when a {@link SphericalArc} cannot be formed due to - * a null input or by two vertices that are too close together. - * - */ -public class MalformedSphericalArcException extends RuntimeException { - - private static final long serialVersionUID = 1L; - - public MalformedSphericalArcException() { - super(); - } - - public MalformedSphericalArcException(String message) { - super(message); - } - -} diff --git a/gcm3/src/main/java/util/spherical/MalformedSphericalPointException.java b/gcm3/src/main/java/util/spherical/MalformedSphericalPointException.java deleted file mode 100644 index b9de9bf2a..000000000 --- a/gcm3/src/main/java/util/spherical/MalformedSphericalPointException.java +++ /dev/null @@ -1,16 +0,0 @@ -package util.spherical; - -/** - * A RuntimeException thrown when a {@link SphericalPoint} cannot be formed due - * to a non-normalizable input. - * - */ -public class MalformedSphericalPointException extends RuntimeException { - - private static final long serialVersionUID = 1L; - - public MalformedSphericalPointException(String message) { - super(message); - } - -} diff --git a/gcm3/src/main/java/util/spherical/MalformedSphericalPolygonException.java b/gcm3/src/main/java/util/spherical/MalformedSphericalPolygonException.java deleted file mode 100644 index 5ce40fff3..000000000 --- a/gcm3/src/main/java/util/spherical/MalformedSphericalPolygonException.java +++ /dev/null @@ -1,19 +0,0 @@ -package util.spherical; - -/** - * A RuntimeException thrown when a {@link SphericalPolygon} cannot be formed - * from its {@link SphericalPoint} values. - */ -public class MalformedSphericalPolygonException extends RuntimeException { - - private static final long serialVersionUID = 1L; - - public MalformedSphericalPolygonException() { - super(); - } - - public MalformedSphericalPolygonException(String message) { - super(message); - } - -} diff --git a/gcm3/src/main/java/util/spherical/MalformedSphericalTriangleException.java b/gcm3/src/main/java/util/spherical/MalformedSphericalTriangleException.java deleted file mode 100644 index 8ab97758f..000000000 --- a/gcm3/src/main/java/util/spherical/MalformedSphericalTriangleException.java +++ /dev/null @@ -1,19 +0,0 @@ -package util.spherical; - -/** - * A RuntimeException thrown when a {@link SphericalTriangle} cannot be formed. - * - */ -public class MalformedSphericalTriangleException extends RuntimeException { - - private static final long serialVersionUID = 1L; - - public MalformedSphericalTriangleException() { - super(); - } - - public MalformedSphericalTriangleException(String message) { - super(message); - } - -} diff --git a/gcm3/src/main/java/util/spherical/SphericalArc.java b/gcm3/src/main/java/util/spherical/SphericalArc.java deleted file mode 100644 index 8e79081d7..000000000 --- a/gcm3/src/main/java/util/spherical/SphericalArc.java +++ /dev/null @@ -1,231 +0,0 @@ -package util.spherical; - -import java.util.Arrays; - -import org.apache.commons.math3.util.FastMath; - -import net.jcip.annotations.Immutable; -import util.vector.MutableVector3D; -import util.vector.Vector3D; - -/** - * Represents an immutable great arc segment on the unit sphere defined by two - * distinct SphericalPoints. Instances are created via included builder class. - * - * @author Shawn Hatch - * - */ - -@Immutable -public class SphericalArc { - - /** - * Hidden constructor - * - * @throws MalformedSphericalArcException - *
          • if the two {@link SphericalPoint} values are too close - * together to properly form a normal vector. - * - * @throws NullPointerException - *
          • if either {@link SphericalPoint} is null - */ - public SphericalArc(SphericalPoint sphericalPoint1, SphericalPoint sphericalPoint2) { - - if (sphericalPoint1 == null) { - throw new NullPointerException("null spherical point contributed to build of spherical arc"); - } - - if (sphericalPoint2 == null) { - throw new NullPointerException("null spherical point contributed to build of spherical arc"); - } - - sphericalPoints = new SphericalPoint[] { sphericalPoint1, sphericalPoint2 }; - perp = sphericalPoint1.getPosition().cross(sphericalPoint2.getPosition()).normalize(); - if (!perp.isNormal()) { - throw new MalformedSphericalArcException("spherical points too similar to form arc"); - } - if (!perp.isPerpendicularTo(sphericalPoint1.getPosition())) { - throw new MalformedSphericalArcException("spherical points too similar to form arc"); - } - if (!perp.isPerpendicularTo(sphericalPoint2.getPosition())) { - throw new MalformedSphericalArcException("spherical points too similar to form arc"); - } - length = sphericalPoint1.getPosition().angle(sphericalPoint2.getPosition()); - } - - private final SphericalPoint[] sphericalPoints; - - /** - * Returns the {@link Chirality} of a SphericalPoint relative to this - * SphericalArc in the natural order of its SphericalPoints. - * - * @throws NullPointerException - *
          • if the given {@link SphericalPoint} is null - * - * @param sphericalPoint - * @return - */ - public Chirality getChirality(SphericalPoint sphericalPoint) { - - double dot = perp.dot(sphericalPoint.getPosition()); - - if (dot >= 0) { - return Chirality.RIGHT_HANDED; - } - return Chirality.LEFT_HANDED; - } - - private final double length; - - private final Vector3D perp; - - /** - * Returns true if and only if this SphericalArc intersects the given - * SphericalArc at a point - * - * @throws NullPointerException - *
          • if the given arc is null - * - * @param arc - * @return - */ - public boolean intersectsArc(SphericalArc arc) { - Chirality chirality1 = getChirality(arc.getSphericalPoint(0)); - Chirality chirality2 = getChirality(arc.getSphericalPoint(1)); - Chirality chirality3 = arc.getChirality(getSphericalPoint(0)); - Chirality chirality4 = arc.getChirality(getSphericalPoint(1)); - return (chirality1 != chirality2) && (chirality3 != chirality4); - } - - /** - * Returns the SphericalPoint associated with the index - * - * @param index - * Values may be 0 or 1 - * - * @throws IndexOutOfBoundsException - * if any other index is used - */ - public SphericalPoint getSphericalPoint(int index) { - return sphericalPoints[index]; - } - - /** - * Returns the arc length of this SphericalArc - * - * @return - */ - public double getLength() { - return length; - } - - /** - * Returns the intersection with the given SphericalArc. If no intersection - * exists, returns null - * - * @param arc - * @return - */ - public SphericalPoint getInterSection(SphericalArc arc) { - - if (!intersectsArc(arc)) { - return null; - } - // we know the arcs intersect - Vector3D a = sphericalPoints[0].getPosition(); - Vector3D b = sphericalPoints[1].getPosition(); - - /* - * The normal vector to each arc's plane will be perpendicular to the - * solution, thus the solution is the cross product of these two - * normals, or their reverse. - */ - Vector3D solution = perp.cross(arc.perp); - - /* - * Does the solution lie on the first arc? It will lie on the arc's - * plane. Thus, if we can show that the intersection lies between the - * arc's end points then the solution is correct. Otherwise, the - * solution must be reversed. - * - */ - boolean containedOnArc = a.cross(solution).dot(perp) >= 0 && b.cross(solution).dot(perp) <= 0; - if (!containedOnArc) { - solution = solution.reverse(); - } - - return new SphericalPoint(solution); - } - - /** - * Returns the distance from the given {@linkplain SphericalPoint} to this - * - * @throws NullPointerException - *
          • if the given {@link SphericalPoint} is null - */ - public double distanceTo(SphericalPoint sphericalPoint) { - Vector3D a = sphericalPoints[0].getPosition(); - Vector3D b = sphericalPoints[1].getPosition(); - - /* - * Create a normal unit vector to the plane containing the two end - * points -- - */ - MutableVector3D p = new MutableVector3D(a); - p.cross(b); - p.normalize(); - - /* - * Create a vector for the input - */ - MutableVector3D q = new MutableVector3D(sphericalPoint.getPosition()); - - // Create a new vector that is q's image in the plane of this arc - MutableVector3D qOnPlane = new MutableVector3D(p); - qOnPlane.scale(-p.dot(q)); - qOnPlane.add(q); - - /* - * Calculate the angle from q to its image on the plane - */ - double angle = q.angle(qOnPlane); - - /* - * If q lies between the end points, then return angle, otherwise return - * the angle between q and the closest end point. - */ - - MutableVector3D v = new MutableVector3D(qOnPlane); - v.cross(a); - double aDot = v.dot(p);// positive if clockwise from a - if (aDot <= 0) { - v.assign(qOnPlane); - v.cross(b); - double bDot = v.dot(p);// positive if clockwise from b - if (bDot >= 0) { - /* - * qOnPlane, from the perspective of the normal, is CCW from A - * and CW from B, so it lies between them - */ - return angle; - } - } - - /* - * q's projection onto the plane does not lie between the end points - */ - - return FastMath.min(q.angle(a), q.angle(b)); - - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("SphericalArc [sphericalPoints="); - builder.append(Arrays.toString(sphericalPoints)); - builder.append("]"); - return builder.toString(); - } - -} diff --git a/gcm3/src/main/java/util/spherical/SphericalPoint.java b/gcm3/src/main/java/util/spherical/SphericalPoint.java deleted file mode 100644 index c54834d37..000000000 --- a/gcm3/src/main/java/util/spherical/SphericalPoint.java +++ /dev/null @@ -1,67 +0,0 @@ -package util.spherical; - -import net.jcip.annotations.Immutable; -import util.vector.MutableVector3D; -import util.vector.Vector3D; - -/** - * Represents an immutable point on the unit sphere. Instances are created via - * included builder class. - * - * @author Shawn Hatch - * - */ -@Immutable -public final class SphericalPoint { - - /** - * Creates a new {@link SphericalPoint} from the given {@link Vector3D} - * - * @throws MalformedSphericalPointException - *
          • if the given {@link Vector3D} is not normalizable - * @throws NullPointerException - *
          • if the given {@link Vector3D} is null - * - */ - public SphericalPoint(Vector3D v) { - - position = v.normalize(); - - if (!position.isNormal()) { - throw new MalformedSphericalPointException("data cannot be normalized onto the unit sphere"); - } - - } - - /** - * Creates a new {@link SphericalPoint} from the given {@link muVector3D} - * - * @throws MalformedSphericalPointException - *
          • if the given {@link Vector3D} is not normalizable - */ - public SphericalPoint(MutableVector3D v) { - position = new Vector3D(v).normalize(); - if (!position.isNormal()) { - throw new MalformedSphericalPointException("data cannot be normalized onto the unit sphere"); - } - } - - private final Vector3D position; - - /** - * Returns this {@link SphericalPoint} as a {@link MutableVector3D} - */ - public Vector3D getPosition() { - return position; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("SphericalPoint [position="); - builder.append(position); - builder.append("]"); - return builder.toString(); - } - -} diff --git a/gcm3/src/main/java/util/spherical/SphericalPolygon.java b/gcm3/src/main/java/util/spherical/SphericalPolygon.java deleted file mode 100644 index 5bf6a1313..000000000 --- a/gcm3/src/main/java/util/spherical/SphericalPolygon.java +++ /dev/null @@ -1,379 +0,0 @@ -package util.spherical; - -import java.util.ArrayList; -import java.util.List; - -import org.apache.commons.math3.util.FastMath; - -import net.jcip.annotations.Immutable; -import util.dimensiontree.VolumetricDimensionTree; -import util.vector.MutableVector3D; - -/** - * Represents an immutable, non-crossing polygon on the surface of a unit - * sphere. - * - * @author Shawn Hatch - * - */ - -@Immutable -public class SphericalPolygon { - - private final static int SEARCH_TREE_THRESHOLD = 30; - - /* - * static class for containing the contributed points that will the form the - * polygon - */ - private static class Scaffold { - private List sphericalPoints = new ArrayList<>(); - boolean useSearchTree = true; - } - - /** - * Builder class for {@link SphericalPolygon} - * - * @author Shawn Hatch - * - */ - public static class Builder { - - /** - * Adds a {@link SphericalPoint} to the {@link SphericalPolygon}. Order - * of addition dictates the order of the vertices of the polygon. - * Winding order may be either left or right handed. - */ - public Builder addSphericalPoint(SphericalPoint sphericalPoint) { - scaffold.sphericalPoints.add(sphericalPoint); - return this; - } - - /** - * Sets the useSearchTree policy for the {@link SphericalPolygon}. - * Default is true; - */ - public Builder setUseSearchTree(boolean useSearchTree) { - scaffold.useSearchTree = useSearchTree; - return this; - } - - private Scaffold scaffold = new Scaffold(); - - /* - * Hidden constructor - */ - private Builder() { - - } - - /** - * Builds a {@link SphericalPolygon} from the supplied - * {@link SphericalPoint} values. - * - * @throws MalformedSphericalPolygonException - * - *
          • if any of the {@link SphericalPoint} values are null - *
          • if there are fewer than three {@link SphericalPoint} - * values - *
          • if the {@link SphericalPoint} values form a crossing - * polygon - *
          • if the polygon is ambiguous - * - */ - public SphericalPolygon build() { - try { - return new SphericalPolygon(scaffold); - } finally { - scaffold = new Scaffold(); - } - } - } - - /** - * Returns a new builder instance for {@link SphericalTriangle} - */ - public static Builder builder() { - return new Builder(); - } - - private final List sphericalPoints; - - private final List sphericalArcs = new ArrayList<>(); - - /** - * Returns the a list of {@link SphericalTriangle} values that cover this - * {@link SphericalPolygon} without overlap. - */ - public List getSphericalTriangles() { - return new ArrayList<>(sphericalTriangles); - } - - private final List sphericalTriangles; - - private final Chirality chirality; - - private final SphericalPoint centroid; - - private final double radius; - - private final VolumetricDimensionTree searchTree; - - /** - * Returns the {@link Chirality} of this {@link SphericalPolygon} relative - * to the natural order of its SphericalPoints. - */ - public Chirality getChirality() { - return chirality; - } - - /** - * Returns true if and only if the given {@link SphericalPoint} is in the - * interior or the arcs of this {@link SphericalPolygon} - */ - public boolean containsPosition(SphericalPoint sphericalPoint) { - if (searchTree == null) { - for (SphericalTriangle sphericalTriangle : sphericalTriangles) { - if (sphericalTriangle.contains(sphericalPoint)) { - return true; - } - } - } else { - List membersInSphere = searchTree.getMembersInSphere(0, sphericalPoint.getPosition().toArray()); - for (SphericalTriangle sphericalTriangle : membersInSphere) { - if (sphericalTriangle.contains(sphericalPoint)) { - return true; - } - } - } - - return false; - } - - /** - * Returns true if this {@link SphericalPolygon} overlaps with any part of - * the given {@link SphericalPolygon} - */ - public boolean intersects(SphericalPolygon sphericalPolygon) { - for (SphericalTriangle triangle1 : sphericalTriangles) { - for (SphericalTriangle triangle2 : sphericalPolygon.getSphericalTriangles()) { - if (triangle1.intersects(triangle2)) { - return true; - } - } - } - return false; - } - - public List getSphericalArcs() { - return new ArrayList<>(sphericalArcs); - - } - - public List getSphericalPoints() { - return new ArrayList<>(sphericalPoints); - } - - /** - * Returns true if this {@link SphericalPolygon} overlaps with any part of - * the given {@link SphericalArc} - */ - public boolean intersects(SphericalArc sphericalArc) { - for (SphericalTriangle triangle : sphericalTriangles) { - if (triangle.intersects(sphericalArc)) { - return true; - } - } - return false; - } - - /** - * Returns true if this {@link SphericalPolygon} overlaps with any part of - * the given {@link SphericalTriangle} - */ - public boolean intersects(SphericalTriangle sphericalTriangle) { - for (SphericalTriangle triangle : sphericalTriangles) { - if (triangle.intersects(sphericalTriangle)) { - return true; - } - } - return false; - } - - private static SphericalTriangle popEar(int index, List points, Chirality chirality) { - // form the triangle from the index - SphericalTriangle t = new SphericalTriangle(points.get((index + -1 + points.size()) % points.size()), points.get(index), points.get((index + 1) % points.size())); - - /* - * if the triangle does not agree with chirality then we are done - */ - if (t.getChirality() != chirality) { - return null; - } - /* - * if the triangle's new side crosses any of the non-attached legs, then - * we are done - */ - - // here are the indices of the attached legs. We will not attempt to - // intersect them with the newly formed arc - int excludedIndex1 = (index - 2 + points.size()) % points.size(); - int excludedIndex2 = (index - 1 + points.size()) % points.size(); - int excludedIndex3 = index; - int excludedIndex4 = (index + 1) % points.size(); - - SphericalArc sphericalArcFormedByTriangle = t.getSphericalArc(2); - - for (int i = 0; i < points.size(); i++) { - if (i != excludedIndex1 && i != excludedIndex2 && i != excludedIndex3 && i != excludedIndex4) { - int j = (i + 1) % points.size(); - SphericalArc potentiallyIntersectingArc = new SphericalArc(points.get(i), points.get(j)); - if (sphericalArcFormedByTriangle.intersectsArc(potentiallyIntersectingArc)) { - return null; - } - } - } - - // if any of the other nodes are in the triangle, then we are done - for (int i = 0; i < points.size(); i++) { - if (i != excludedIndex2 && i != excludedIndex3 && i != excludedIndex4) { - if (t.contains(points.get(i))) { - return null; - } - } - - } - - return t; - } - - private static List triangulate(List sphericalPoints, Chirality chirality) { - - /* - * We will attempt to remove nodes from the list one by one by popping - * an ear off the polygon -- that is, we will remove a triangle and get - * an increasingly simple polygon. If all goes well, we will be left - * with a degenerate polygon (one that has no nodes) - * - */ - - List result = new ArrayList<>(); - List points = new ArrayList<>(sphericalPoints); - - /* - * Roll around the polygon, trying to pop off a node and its - * corresponding triangle. Popping an ear is successful only when the - * triangle so formed is fully inside the polygon. If we fail - * repeatedly, we stop once we have come full circle. - * - */ - - int index = 0; - int failurecount = 0; - while ((points.size() > 2) && (failurecount < points.size())) { - SphericalTriangle sphericalTriangle = popEar(index, points, chirality); - if (sphericalTriangle != null) { - points.remove(index); - index = index % points.size(); - result.add(sphericalTriangle); - failurecount = 0; - } else { - failurecount++; - index = (index + 1) % points.size(); - } - } - - // if there are any nodes remaining, - // then return false and clear out any triangles - // that were formed - - if (points.size() > 2) { - result.clear(); - } - return result; - } - - private SphericalPolygon(Scaffold scaffold) { - - if (scaffold.sphericalPoints.size() < 3) { - throw new MalformedSphericalPolygonException("spherical polygons require at least three points"); - } - - for (SphericalPoint sphericalPoint : scaffold.sphericalPoints) { - if (sphericalPoint == null) { - throw new MalformedSphericalPolygonException("spherical polygons require non-null spherical points"); - } - } - - MutableVector3D v = new MutableVector3D(); - for (SphericalPoint sphericalPoint : scaffold.sphericalPoints) { - v.add(sphericalPoint.getPosition()); - } - centroid = new SphericalPoint(v); - - if (!centroid.getPosition().isFinite()) { - throw new MalformedSphericalPolygonException("the spherical polygon formed from the given spherical points is ambiguous"); - } - - double r = Double.NEGATIVE_INFINITY; - for (SphericalPoint sphericalPoint : scaffold.sphericalPoints) { - r = FastMath.max(r, centroid.getPosition().angle(sphericalPoint.getPosition())); - } - radius = r; - - Chirality chirality = Chirality.RIGHT_HANDED; - List triangles = triangulate(scaffold.sphericalPoints, Chirality.RIGHT_HANDED); - if (triangles.size() == 0) { - chirality = Chirality.LEFT_HANDED; - triangles = triangulate(scaffold.sphericalPoints, Chirality.LEFT_HANDED); - } - - if (triangles.size() == 0) { - throw new MalformedSphericalPolygonException("the spherical points form a crossing polygon"); - } - - this.chirality = chirality; - sphericalTriangles = triangles; - - sphericalPoints = scaffold.sphericalPoints; - - for (int i = 0; i < scaffold.sphericalPoints.size(); i++) { - int j = (i + 1) % scaffold.sphericalPoints.size(); - sphericalArcs.add(new SphericalArc(scaffold.sphericalPoints.get(i), scaffold.sphericalPoints.get(j))); - } - - if (sphericalTriangles.size() > SEARCH_TREE_THRESHOLD && scaffold.useSearchTree) { - searchTree = VolumetricDimensionTree.builder()// - .setFastRemovals(true)// - .setLowerBounds(new double[] { -1, -1, -1 })// - .setUpperBounds(new double[] { 1, 1, 1 })// - .build();// - - for (SphericalTriangle sphericalTriangle : sphericalTriangles) { - double[] triangleCenter = sphericalTriangle.getCentroid().toArray(); - searchTree.add(triangleCenter, sphericalTriangle.getRadius(), sphericalTriangle); - } - } else { - searchTree = null; - } - - } - - /** - * Returns the radius of this {@link SphericalPolygon}. All vertices of this - * polygon lie with the radius around the centroid. - */ - public double getRadius() { - return radius; - } - - /** - * Returns the center of this {@link SphericalPolygon}. All vertices of this - * polygon lie with the radius around the centroid. The centroid is - * calculated as the numerical average of the points that compose the - * polygon. - */ - public SphericalPoint getCentroid() { - return centroid; - } -} diff --git a/gcm3/src/main/java/util/spherical/SphericalTriangle.java b/gcm3/src/main/java/util/spherical/SphericalTriangle.java deleted file mode 100644 index 39a46d213..000000000 --- a/gcm3/src/main/java/util/spherical/SphericalTriangle.java +++ /dev/null @@ -1,252 +0,0 @@ -package util.spherical; - -import org.apache.commons.math3.util.FastMath; - -import net.jcip.annotations.Immutable; -import util.vector.MutableVector3D; -import util.vector.Vector3D; - -/** - * Represents an immutable triangle on the unit sphere. - * - * @author Shawn Hatch - * - */ - -@Immutable -public class SphericalTriangle { - - /** - * Returns the shortest distance from the point to this triangle. Points on - * the inside of the triangle will return 0. - */ - public double distanceTo(SphericalPoint sphericalPoint) { - - if (contains(sphericalPoint)) { - return 0; - } - double result = Double.POSITIVE_INFINITY; - for (SphericalArc sphericalArc : sphericalArcs) { - result = FastMath.min(result, sphericalArc.distanceTo(sphericalPoint)); - } - return result; - } - - private static double getTangentialAngle(Vector3D v1, Vector3D v2, Vector3D v3) { - MutableVector3D p = new MutableVector3D(); - p.assign(v1); - p.cross(v2); - p.cross(v2); - - MutableVector3D q = new MutableVector3D(); - q.assign(v3); - q.cross(v2); - q.cross(v2); - - return p.angle(q); - } - - /** - * Constructs a {@link SphericalTriangle} from the given - * {@link SphericalPoint} values. - * - * @throws MalformedSphericalTriangleException - *
          • if a spherical point is null
          • - * - * - */ - public SphericalTriangle(SphericalPoint sphericalPoint1, SphericalPoint sphericalPoint2, SphericalPoint sphericalPoint3) { - - if (sphericalPoint1 == null) { - throw new MalformedSphericalTriangleException("null spherical point"); - } - - if (sphericalPoint2 == null) { - throw new MalformedSphericalTriangleException("null spherical point"); - } - - if (sphericalPoint3 == null) { - throw new MalformedSphericalTriangleException("null spherical point"); - } - - sphericalPoints = new SphericalPoint[] { sphericalPoint1, sphericalPoint2, sphericalPoint3 }; - - sphericalArcs = new SphericalArc[3]; - - for (int i = 0; i < 3; i++) { - SphericalPoint p1 = sphericalPoints[i]; - SphericalPoint p2 = sphericalPoints[(i + 1) % 3]; - sphericalArcs[i] = new SphericalArc(p1, p2); - } - - chirality = sphericalArcs[0].getChirality(sphericalPoints[2]); - - final Vector3D v0 = sphericalPoints[0].getPosition(); - final Vector3D v1 = sphericalPoints[1].getPosition(); - final Vector3D v2 = sphericalPoints[2].getPosition(); - - final MutableVector3D perp = new MutableVector3D(v0); - perp.cross(v1); - final boolean leftHanded = perp.dot(v2) < 0; - - MutableVector3D midPoint = new MutableVector3D(v0); - midPoint.add(v1); - final MutableVector3D c = new MutableVector3D(v0); - c.cross(v1); - c.cross(midPoint); - - midPoint = new MutableVector3D(v1); - midPoint.add(v2); - final MutableVector3D d = new MutableVector3D(v1); - d.cross(v2); - d.cross(midPoint); - - c.cross(d); - c.normalize(); - - if (leftHanded) { - c.reverse(); - } - radius = c.angle(v0); - - centroid = new Vector3D(c); - - double alpha = getTangentialAngle(v0, v1, v2); - double beta = getTangentialAngle(v1, v2, v0); - double gamma = getTangentialAngle(v2, v0, v1); - - area = (alpha + beta + gamma) - FastMath.PI; - - } - - private final SphericalArc[] sphericalArcs; - - private final SphericalPoint[] sphericalPoints; - - private final Vector3D centroid; - - private final double radius; - - private final double area; - - private final Chirality chirality; - - /** - * Returns true if and only if the given {@link SphericalPoint} is in the - * interior or the arcs of this {@link SphericalTriangle} - */ - public boolean contains(SphericalPoint sphericalPoint) { - for (int i = 0; i < 3; i++) { - if (sphericalArcs[i].getChirality(sphericalPoint) != chirality) { - return false; - } - } - return true; - } - - /** - * Returns the area of this {@link SphericalTriangle} - */ - public double getArea() { - return area; - } - - /** - * Returns the centroid point (a unit vector) of this - * {@link SphericalTriangle} - */ - public Vector3D getCentroid() { - return centroid; - } - - /** - * Returns the radius of this {@link SphericalTriangle}. The radius is - * defined as the distance to the centroid from the vertices. - */ - public double getRadius() { - return radius; - } - - /** - * Returns the {@link Chirality} of this {@link SphericalTriangle} relative - * to the natural order of its SphericalPoints. - */ - public Chirality getChirality() { - return chirality; - } - - /** - * Returns true if this {@link SphericalTriangle} overlaps with any part of - * the given {@link SphericalTriangle} - */ - public boolean intersects(SphericalTriangle sphericalTriangle) { - for (int i = 0; i < 3; i++) { - if (contains(sphericalTriangle.getSphericalPoint(i))) { - return true; - } - if (sphericalTriangle.contains(getSphericalPoint(i))) { - return true; - } - } - - for (int i = 0; i < 3; i++) { - if (intersects(sphericalTriangle.getSphericalArc(i))) { - return true; - } - } - - return false; - } - - /** - * Returns the {@link SphericalArc} that corresponds to the index. These - * arcs are formed from the original {@link SphericalPoint} members that - * define this {@link SphericalTriangle}. Arc[0] is formed(in order) from - * Point[0] and Point[1]. Arc[1] is formed(in order) from Point[1] and - * Point[2]. Arc[2] is formed(in order) from Point[2] and Point[0]. - * - * @param index - * Values may be 0, 1 or 2 - * - * @throws IndexOutOfBoundsException - * if any other index is used - */ - public SphericalArc getSphericalArc(int index) { - return sphericalArcs[index]; - } - - /** - * Returns the {@link SphericalPoint} that corresponds to the index and was - * used to construct this {@link SphericalTriangle} - * - * @param index - * Values may be 0, 1 or 2 - * - * @throws IndexOutOfBoundsException - * if any other index is used - * - */ - public SphericalPoint getSphericalPoint(int index) { - return sphericalPoints[index]; - } - - /** - * Returns true if this {@link SphericalTriangle} intersects the given - * {@link SphericalArc} - */ - public boolean intersects(SphericalArc sphericalArc) { - for (int i = 0; i < 2; i++) { - if (contains(sphericalArc.getSphericalPoint(i))) { - return true; - } - } - - for (int i = 0; i < 3; i++) { - if (sphericalArcs[i].intersectsArc(sphericalArc)) { - return true; - } - } - - return false; - } -} diff --git a/gcm3/src/main/java/util/stats/BinContainer.java b/gcm3/src/main/java/util/stats/BinContainer.java deleted file mode 100644 index 10430a597..000000000 --- a/gcm3/src/main/java/util/stats/BinContainer.java +++ /dev/null @@ -1,269 +0,0 @@ -package util.stats; - -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.TreeMap; - -import org.apache.commons.math3.util.FastMath; - -public final class BinContainer { - - /** - * Constructs the {@link BinContainer} from the given binSize. - * - * @throws RuntimeException - *
          • if the binSize is non-positive - */ - private BinContainer(MutableBinContainer mutableBinContainer) { - this.lowIndex = mutableBinContainer.lowIndex; - this.highIndex = mutableBinContainer.highIndex; - this.binSize = mutableBinContainer.binSize; - - for (Integer key : mutableBinContainer.map.keySet()) { - MutableBin mutableBin = mutableBinContainer.map.get(key); - map.put(key, new Bin(mutableBin)); - } - - for (int i = lowIndex; i <= highIndex; i++) { - Bin bin = map.get(i); - if (bin == null) { - map.put(i, new Bin(i * binSize, (i + 1) * binSize, 0)); - } - } - - } - - public static Builder builder(double binSize) { - return new Builder(binSize); - } - - private final double binSize; - - private final Map map = new TreeMap<>(); - - private final int lowIndex; - - private final int highIndex; - - public static class Builder { - private final double binSize; - private MutableBinContainer mutableBinContainer; - - private Builder(double binSize) { - this.binSize = binSize; - mutableBinContainer = new MutableBinContainer(binSize); - } - - /** - * Builds the {@link BinContainer} from the contributed values. - */ - public BinContainer build() { - try { - return new BinContainer(mutableBinContainer); - } finally { - mutableBinContainer = new MutableBinContainer(binSize); - } - } - - /** - * Adds the given value by the given number of times. - * - * @throws RuntimeException - *
          • if the count is negative - */ - public Builder addValue(double value, int count) { - mutableBinContainer.addValue(value, count); - return this; - } - - } - - /** - * Represents a half open interval [lowerBound,upperBound) that contains a - * count of values. - * - * @author Shawn Hatch - * - */ - - public final static class Bin { - private final double lowerBound; - private final double upperBound; - private final int count; - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + count; - long temp; - temp = Double.doubleToLongBits(lowerBound); - result = prime * result + (int) (temp ^ (temp >>> 32)); - temp = Double.doubleToLongBits(upperBound); - result = prime * result + (int) (temp ^ (temp >>> 32)); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (!(obj instanceof Bin)) { - return false; - } - Bin other = (Bin) obj; - if (count != other.count) { - return false; - } - if (Double.doubleToLongBits(lowerBound) != Double.doubleToLongBits(other.lowerBound)) { - return false; - } - if (Double.doubleToLongBits(upperBound) != Double.doubleToLongBits(other.upperBound)) { - return false; - } - return true; - } - - /** - * Returns the number of data associated with this bin - */ - public int getCount() { - return count; - } - - /** - * Returns the lower bound of this bin - */ - public double getLowerBound() { - return lowerBound; - } - - /** - * Returns the upper bound of this bin - */ - public double getUpperBound() { - return upperBound; - } - - /** - * Constructs the Bin - * - * @throws IllegalArgumentException - *
          • if the lower bound exceeds the upper bound - *
          • if the count is negative - */ - public Bin(double lowerBound, double upperBound, int count) { - if (lowerBound > upperBound) { - throw new IllegalArgumentException("lower bound exceeds upper bound"); - } - - if (count < 0) { - throw new IllegalArgumentException("negative count"); - } - - this.lowerBound = lowerBound; - this.upperBound = upperBound; - this.count = count; - } - - private Bin(MutableBin mutableBin) { - this.lowerBound = mutableBin.lowerBound; - this.upperBound = mutableBin.upperBound; - this.count = mutableBin.count; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("Bin [lowerBound="); - builder.append(lowerBound); - builder.append(", upperBound="); - builder.append(upperBound); - builder.append(", count="); - builder.append(count); - builder.append("]"); - return builder.toString(); - } - } - - private final static class MutableBin { - private final double lowerBound; - private final double upperBound; - private int count; - - private MutableBin(double lowerBound, double upperBound) { - this.lowerBound = lowerBound; - this.upperBound = upperBound; - } - - } - - private static class MutableBinContainer { - private MutableBinContainer(double binSize) { - if (binSize <= 0) { - // deception - throw new RuntimeException("bin size must be positive"); - } - this.binSize = binSize; - } - - private final double binSize; - - private Map map = new LinkedHashMap<>(); - - private int lowIndex = Integer.MAX_VALUE; - - private int highIndex = Integer.MIN_VALUE; - - /** - * Adds a value to the container - * - * @throws IllegalArgumentException - *
          • if the count is negative
          • - */ - public void addValue(double value, int count) { - if (count < 0) { - throw new IllegalArgumentException("count cannot be negative"); - } - int index = (int) FastMath.floor(value / binSize); - MutableBin mutableBin = map.get(index); - if (mutableBin == null) { - lowIndex = FastMath.min(lowIndex, index); - highIndex = FastMath.max(highIndex, index); - mutableBin = new MutableBin(index * binSize, (index + 1) * binSize); - map.put(index, mutableBin); - } - mutableBin.count += count; - } - } - - /** - * Returns the number of bins contained in this {@link BinContainer} - */ - public int binCount() { - return highIndex - lowIndex + 1; - } - - /** - * Returns the bin associated with the given index. Bins are indexed from 0 - * to binCount-1. - */ - public Bin getBin(int index) { - if (index < 0) { - // deception - throw new RuntimeException("bin index out of bounds: " + index); - } - if (index > highIndex - lowIndex) { - // deception - throw new RuntimeException("bin index out of bounds: " + index); - } - int adjustedIndex = lowIndex + index; - return map.get(adjustedIndex); - - } - -} diff --git a/gcm3/src/main/java/util/stats/ImmutableStat.java b/gcm3/src/main/java/util/stats/ImmutableStat.java deleted file mode 100644 index e1b3d28b1..000000000 --- a/gcm3/src/main/java/util/stats/ImmutableStat.java +++ /dev/null @@ -1,258 +0,0 @@ -package util.stats; - -import java.util.Optional; - -import net.jcip.annotations.NotThreadSafe; -import net.jcip.annotations.ThreadSafe; - -/** - * A {@link Stat} implementor that is immutable and is constructed via the - * contained builder class. - * - * @author Shawn Hatch - * - */ -@ThreadSafe -public final class ImmutableStat implements Stat { - - /** - * A container for collecting the five characteristics of a Stat - * - * @author Shawn Hatch - * - */ - private static class Scaffold { - private double mean; - private double variance; - private double max; - private double min; - private int size; - } - - /** - * Returns a new Builder for {@link ImmutableStat} - */ - public static Builder builder() { - return new Builder(); - } - - /** - * A builder class for {@link ImmutableStat} - * - * @author Shawn Hatch - * - */ - @NotThreadSafe - public static class Builder { - private Builder() { - - } - - private Scaffold scaffold = new Scaffold(); - - /** - * Builds the ImmutableStat - * - * @throws IllegalArgumentException - *
          • if the size is negative - *
          • if the size value is one and the min mean and max are - * not equal - *
          • if the size value is one and the variance is not zero - *
          • if the size value is greater than one and the min - * exceeds the max - *
          • if the size value is greater than one and the min - * exceeds the mean - *
          • if the size value is greater than one and the mean - * exceeds the max - *
          • if the size value is greater than one and the - * variance is negative - */ - public ImmutableStat build() { - try { - validateScaffold(); - return new ImmutableStat(scaffold); - } finally { - scaffold = new Scaffold(); - } - } - - /** - * Sets the mean - */ - public Builder setMean(double mean) { - scaffold.mean = mean; - return this; - } - - /** - * Sets the variance - */ - public Builder setVariance(double variance) { - scaffold.variance = variance; - return this; - } - - /** - * Sets the max - */ - public Builder setMax(double max) { - scaffold.max = max; - return this; - } - - /** - * Sets the min - */ - public Builder setMin(double min) { - scaffold.min = min; - return this; - } - - /** - * Sets the size - */ - public Builder setSize(int size) { - scaffold.size = size; - return this; - } - - /* - * Validates the content of the Stat - * - * @throws IllegalArgumentException - * - *
          • if the size is negative - * - *
          • if the size value is one and the min mean and max are not equal - * - *
          • if the size value is one and the variance is not zero - * - *
          • if the size value is greater than one and the min exceeds the max - * - *
          • if the size value is greater than one and the min exceeds the - * mean - * - *
          • if the size value is greater than one and the mean exceeds the - * max - * - *
          • if the size value is greater than one and the variance is - * negative - */ - private void validateScaffold() { - if (scaffold.size < 0) { - throw new IllegalArgumentException("negative size"); - } - if (scaffold.size == 1) { - // min, max and mean must be equal and variance must be zero - if (scaffold.min != scaffold.max) { - throw new IllegalArgumentException("size = 1 implies min=max"); - } - if (scaffold.min != scaffold.mean) { - throw new IllegalArgumentException("size = 1 implies min=mean=max"); - } - if (scaffold.variance != 0) { - throw new IllegalArgumentException("size = 1 implies variance = 0"); - } - } else if (scaffold.size > 1) { - if (scaffold.min > scaffold.max) { - throw new IllegalArgumentException("min exceeds max"); - } - if (scaffold.min > scaffold.mean) { - throw new RuntimeException("min exceeds mean"); - } - - if (scaffold.mean > scaffold.max) { - throw new IllegalArgumentException("mean exceeds max"); - } - - if (scaffold.variance < 0) { - throw new IllegalArgumentException("variance cannot be negative"); - } - } - } - } - - private ImmutableStat(Scaffold scaffold) { - this.mean = scaffold.mean; - this.min = scaffold.min; - this.max = scaffold.max; - this.variance = scaffold.variance; - this.standardDeviation = Math.sqrt(scaffold.variance); - this.size = scaffold.size; - } - - private final double mean; - private final double variance; - private final double standardDeviation; - private final double max; - private final double min; - private final int size; - - @Override - public Optional getMean() { - if (size > 0) { - return Optional.of(mean); - } - return Optional.empty(); - } - - @Override - public Optional getVariance() { - if (size > 0) { - return Optional.of(variance); - } - return Optional.empty(); - } - - @Override - public Optional getStandardDeviation() { - if (size > 0) { - return Optional.of(standardDeviation); - } - return Optional.empty(); - } - - @Override - public Optional getMax() { - if (size > 0) { - return Optional.of(max); - } - return Optional.empty(); - } - - @Override - public Optional getMin() { - if (size > 0) { - return Optional.of(min); - } - return Optional.empty(); - } - - @Override - public int size() { - return size; - } - - /** - * Boilerplate implementation - */ - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("ImmutableStat [mean="); - builder.append(mean); - builder.append(", variance="); - builder.append(variance); - builder.append(", standardDeviation="); - builder.append(standardDeviation); - builder.append(", max="); - builder.append(max); - builder.append(", min="); - builder.append(min); - builder.append(", size="); - builder.append(size); - builder.append("]"); - return builder.toString(); - } - -} diff --git a/gcm3/src/main/java/util/stats/KahanSum.java b/gcm3/src/main/java/util/stats/KahanSum.java deleted file mode 100644 index c4a17de81..000000000 --- a/gcm3/src/main/java/util/stats/KahanSum.java +++ /dev/null @@ -1,33 +0,0 @@ -package util.stats; - -/** - * Implements the error reducing methodology for sums of floating point numbers - * developed by mathematician William Kahan. - * - * @author Shawn Hatch - * - */ -public final class KahanSum { - - private double sum; - - private double error; - - /** - * Adds the value to the error corrected sum - */ - public void add(double value) { - double errorCorrectedValue = value - error; - double tempSum = sum + errorCorrectedValue; - error = (tempSum - sum) - errorCorrectedValue; - sum = tempSum; - } - - /** - * Returns the error corrected sum - */ - public double getSum() { - return sum; - } - -} diff --git a/gcm3/src/main/java/util/stats/MutableStat.java b/gcm3/src/main/java/util/stats/MutableStat.java deleted file mode 100644 index 9a66517d3..000000000 --- a/gcm3/src/main/java/util/stats/MutableStat.java +++ /dev/null @@ -1,228 +0,0 @@ -package util.stats; - -import java.util.Collection; -import java.util.Optional; - -import net.jcip.annotations.NotThreadSafe; - -/** - * Implements B.P. Welford's method for determining sample variance without - * maintaining sample values. Corrects for a significant portion of the rounding - * errors associated with a large number of sample values. Corrects for errors - * that arise from high mean and low variance sample populations. - * - * Adapted from https://www.johndcook.com/blog/standard_deviation/ - * - * @author Shawn Hatch - * - */ -@NotThreadSafe -public final class MutableStat implements Stat { - - public static Stat combineStatsCollection(Collection stats) { - Stat[] result = new Stat[stats.size()]; - result = stats.toArray(result); - return combineStats(result); - } - - /** - * Combines several stats - * - * @throws NullPointerException - *
          • if the stats are null
          • - * - */ - public static Stat combineStats(Stat... stats) { - - if (stats == null) { - throw new NullPointerException(); - } - for (Stat stat : stats) { - if (stat == null) { - throw new NullPointerException(); - } - } - - if (stats.length == 0) { - return new MutableStat(); - } - - double min = Double.MAX_VALUE; - for (Stat stat : stats) { - if (stat.getMin().isPresent()) { - min = Math.min(min, stat.getMin().get()); - } - } - - double max = Double.MIN_VALUE; - for (Stat stat : stats) { - if (stat.getMax().isPresent()) { - max = Math.max(max, stat.getMax().get()); - } - } - - int tn = 0; - for (Stat stat : stats) { - Math.addExact(tn, stat.size()); - } - - double t1 = 0; - double t2 = 0; - - for (Stat stat : stats) { - if (stat.size() > 0) { - int sn = stat.size(); - double s1 = stat.getMean().get(); - s1 *= sn; - double s2 = stat.getVariance().get(); - s2 *= sn; - s2 *= sn; - s2 += s1 * s1; - s2 /= sn; - t1 += s1; - t2 += s2; - tn += sn; - } - } - - double var = t2 * tn - t1 * t1; - var /= tn; - - double mean = t1 / tn; - - MutableStat result = new MutableStat(); - result.min = min; - result.max = max; - result.count = tn; - result.mean = new KahanSum(); - result.mean.add(mean); - result.var = new KahanSum(); - result.var.add(var); - - return result; - } - - public MutableStat() { - - } - - private double min; - private double max; - private KahanSum mean; - private KahanSum var; - - private int count; - - public void clear() { - count = 0; - } - - /** - * Adds a value to this MutableStat - */ - public void add(double value) { - // increment the count - count++; - /* - * if there is only one data point, set the five core value accordinly - */ - if (count == 1) { - mean = new KahanSum(); - mean.add(value); - var = new KahanSum(); - min = value; - max = value; - } else { - /* - * update the min and max - */ - if (max < value) { - max = value; - } - if (min > value) { - min = value; - } - - /* - * Using Welford's method we update the mean and variance. Both are - * kept in Kahan Sums rather than doubles to help reduce the - * accumulation of error. - */ - double oldMean = mean.getSum(); - mean.add((value - oldMean) / count); - var.add((value - oldMean) * (value - mean.getSum())); - } - } - - @Override - public Optional getMean() { - if (count == 0) { - return Optional.empty(); - } - return Optional.of(mean.getSum()); - } - - @Override - public Optional getVariance() { - if (count == 0) { - return Optional.empty(); - } - if (count == 1) { - return Optional.of(0d); - } - double result = (var.getSum() / count); - return Optional.of(result); - } - - @Override - public Optional getStandardDeviation() { - Optional optional = getVariance(); - if (optional.isPresent()) { - return Optional.of(Math.sqrt(optional.get())); - } - return Optional.empty(); - } - - @Override - public Optional getMax() { - if (count == 0) { - return Optional.empty(); - } - return Optional.of(max); - } - - @Override - public Optional getMin() { - if (count == 0) { - return Optional.empty(); - } - return Optional.of(min); - } - - @Override - public int size() { - return count; - } - - /** - * Boilerplate implementation - */ - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("MutableStat [getMean()="); - builder.append(getMean()); - builder.append(", getVariance()="); - builder.append(getVariance()); - builder.append(", getStandardDeviation()="); - builder.append(getStandardDeviation()); - builder.append(", getMax()="); - builder.append(getMax()); - builder.append(", getMin()="); - builder.append(getMin()); - builder.append(", size()="); - builder.append(size()); - builder.append("]"); - return builder.toString(); - } -} diff --git a/gcm3/src/main/java/util/stats/Stat.java b/gcm3/src/main/java/util/stats/Stat.java deleted file mode 100644 index 023fd302a..000000000 --- a/gcm3/src/main/java/util/stats/Stat.java +++ /dev/null @@ -1,53 +0,0 @@ -package util.stats; - -import java.util.Optional; - -/** - * Stat contains the values of several useful statistical values for a sequence, - * but not the values in the sequence. - * - * @author Shawn Hatch - * - */ -public interface Stat { - - /** - * Returns the mean of the sequence. Empty sequences do not have a defined - * mean and the Optional will reflect that no mean value is present. - */ - public Optional getMean(); - - /** - * Returns the variance of the sequence. Empty sequences do not have a - * defined variance and the Optional will reflect that no variance value is - * present. - */ - public Optional getVariance(); - - /** - * Returns the standard deviation of the sequence. Empty sequences do not - * have a defined standard deviation and the Optional will reflect that no - * standard deviation value is present. - */ - public Optional getStandardDeviation(); - - /** - * Returns the max value of the sequence. Empty sequences do not have a - * defined max value and the Optional will reflect that no max value value - * is present. - */ - public Optional getMax(); - - /** - * Returns the min value of the sequence. Empty sequences do not have a - * defined min value and the Optional will reflect that no min value value - * is present. - */ - public Optional getMin(); - - /** - * Returns the number of values in the sequence. - */ - public int size(); - -} diff --git a/gcm3/src/main/java/util/time/TimeElapser.java b/gcm3/src/main/java/util/time/TimeElapser.java deleted file mode 100644 index 39846db56..000000000 --- a/gcm3/src/main/java/util/time/TimeElapser.java +++ /dev/null @@ -1,55 +0,0 @@ -package util.time; - -/** - * A debug-convenience class for measuring elapsed time via System.nanoTime() - * - * @author Shawn Hatch - * - */ -public final class TimeElapser { - - private long startTime; - - /** - * Creates the time elaspser. Time begins accumulating immediately. - */ - public TimeElapser() { - reset(); - } - - /** - * Returns the elapsed time in milliseconds - */ - public double getElapsedMilliSeconds() { - double result = getElapsedNanoSeconds(); - result *= 0.000001; - return result; - } - - /** - * Return the number of nanoseconds since the construction or reset of this - * TimeElapser. Note that nanosecond precision does not imply nanosecond - * accuracy. - * - * @return - */ - public long getElapsedNanoSeconds() { - return System.nanoTime() - startTime; - } - - /** - * Returns the elapsed time in seconds - */ - public double getElapsedSeconds() { - double result = getElapsedNanoSeconds(); - result *= 0.000000001; - return result; - } - - /** - * Sets the elapsed time back to zero. - */ - public void reset() { - startTime = System.nanoTime(); - } -} \ No newline at end of file diff --git a/gcm3/src/main/java/util/tree/MutableTree.java b/gcm3/src/main/java/util/tree/MutableTree.java deleted file mode 100644 index 96e6c0598..000000000 --- a/gcm3/src/main/java/util/tree/MutableTree.java +++ /dev/null @@ -1,675 +0,0 @@ -package util.tree; - -/** - * An extension to the {@link Tree} interface that specifies the mutable - * behavior of {@link Tree}. - * - * - * @author Shawn Hatch - * @author Christopher R. Ludka - * - * @param - */ -public interface MutableTree extends Tree { - - /** - * Links a parent and child together in the tree. - *

            - * There are several major functionalities that exist in addLink. - * Each functionality is described below with corresponding (parent, child) - * value examples and trees that depict the 'Before' and 'After' state of - * the mutation. These major functionalities include: - *

              - *
            • Linking a parent and a child together in the tree - *
            • Swapping existing linkages - *
            • Adding new nodes with linkages in a single method call - *
            - *

            - * The following (parent, child) value examples for the trees below depict - * the mutation for each of these major functionalities. - *

            - * Linking a parent and a child together in the tree: To add a - * linkage to an existing pair of nodes, specify the intended parent and - * child. - *

            - * Example: For the following addLink(parent, child) - * examples the trees below depict the mutation. - *

              - *
            • addLink(1, 2): - * - *
              -	 * Before				After
              -	 * + (depth 0, trunk)		+ (depth 0, trunk)
              -	 * |				|
              -	 * +-+ 1				+-+ 1
              -	 * |				| |
              -	 * +-+ 2				| +-+ 2
              -	 * |				|
              -	 * (part of larger tree)		(part of larger tree)
              -	 * 
              - * - *

              - * - *

            • addLink(2, 1): - * - *
              -	 * Before				After
              -	 * + (depth 0, trunk)		+ (depth 0, trunk)
              -	 * |				|
              -	 * +-+ 1				+-+ 2
              -	 * |				| |
              -	 * +-+ 2				| +-+ 1
              -	 * |				|
              -	 * (part of larger tree)		(part of larger tree)
              -	 * 
              - * - *
            - * - * Swapping existing linkages: To swap an existing linkages, specify - * the new intended parent and child. All descendants will be carried with - * their parent in the swap. An 'ancestry conflict' can occur when - * the addLink parent is currently a descendant of the - * addLink child. When an ancestry conflict occurs the - * addLink parent undergoes a removeLink, with full - * enforcement of the removeLink contract, making the - * addLink parent a root node. Then the addLink child is - * placed under the addLink parent, maintaining all of the - * remaining addLink child descendant linkages. - *

            - * Example: For the following addLink(parent, child) - * examples the trees below depict the mutation. - *

              - *
            • addLink(2, 1) swaps the parent with the child. - * - *
              -	 * Before				After
              -	 * + (depth 0, trunk)		+ (depth 0, trunk)
              -	 * |				|
              -	 * +-+ 1				+-+ 2
              -	 * | |				| |
              -	 * | +-+ 2				| +-+ 1
              -	 * |				|
              -	 * (part of larger tree)		(part of larger tree)
              -	 * 
              - * - *

              - *

            • addLink(2, 3) swaps '3' from a root node to a child of '2'. - * All descendants of '3' are moved as part of the swap as well. - * - *
              -	 * Before				After
              -	 * + (trunk)			+ (trunk)
              -	 * |				|
              -	 * +-+ 1				+-+ 1
              -	 * |				|
              -	 * +-+ 2				+-+ 2
              -	 * |				| |
              -	 * +-+ 3				| +-+ 3
              -	 * | |				| | |
              -	 * | +-+ 3-1 			| | +-+ 3-1
              -	 * | |				| | |
              -	 * --+-+ 3-2			----+-+ 3-2
              -	 * 
              - * - *
            • addLink(3-1, 2): Since '3-1' is currently a descendant of - * '2', the removeLink('3-1') contract is first enforced making - * '3-1' a root node. Then '2' is added as a child of '3-1' where '2' - * maintains it's remaining descendants. - * - *
              -	 * Before				After
              -	 * + (trunk)			+ (trunk)
              -	 * |				|
              -	 * +-+ 1				+-+ 1
              -	 * |				|
              -	 * +-+ 2				+-+ 3-1
              -	 * | | |				| |
              -	 * | | +-+ 3			| +-+ 2
              -	 * | | | |				| | |
              -	 * | | | +-+ 3-1			| | +-+ 3
              -	 * | | | |				| | |
              -	 * ------+-+ 3-2			----+-+ 3-2
              -	 * 
              - * - *
            • addLink(3-1, 3): Since, '3-1' is currently a descendant of - * '3', the removeLink('3-1') contract is first enforced making - * '3-1' a root node. Then '3' is added as a child of '3-1' where '3' - * maintains it's remaining descendants. - * - *
              -	 * Before				After
              -	 * + (trunk)			+ (trunk)
              -	 * |				|
              -	 * +-+ 1				+-+ 1
              -	 * |				|
              -	 * +-+ 2				+-+ 2
              -	 * | | |				| |
              -	 * | | +-+ 3			| +-+ 3-1
              -	 * | | | |				| | |
              -	 * | | | +-+ 3-1			| | +-+ 3
              -	 * | | | |				| | | |
              -	 * ------+-+ 3-2			------+-+ 3-2
              -	 * 
              - * - *
            - *

            - * - * Adding new nodes with linkages in a single method call: The parent - * and child are not required to contained in the tree before calling - * addLink. The given parent and child will be added to the tree if - * not already contained within the tree. This allows addLink to be - * used for quickly populating a tree in any arbitrary order (rather than - * having to first add nodes, then recursively adding links in a specific - * order). The behavior by which nodes are added follows the - * addNode contract. - *

            - * Example: For the following addLink(parent, child) - * examples the trees below depict the mutation. - *

              - *
            • addLink(2, 3): '3' is added to the tree with '2' as it's - * parent. - * - *
              -	 * Before				After
              -	 * + (depth 0, trunk)		+ (depth 0, trunk)
              -	 * |				|
              -	 * +-+ 1				+-+ 1
              -	 * |				|
              -	 * +-+ 2				+-+ 2
              -	 * |				| |
              -	 * (part of larger tree)		|-+-+ 3
              -	 * 				|
              -	 * 				(part of larger tree)
              -	 * 
              - * - *
            • addLink(3, 2): '3' is added to the tree with '2' as it's - * child. - * - *
              -	 * Before				After
              -	 * + (depth 0, trunk)		+ (depth 0, trunk)
              -	 * |				|
              -	 * +-+ 1				+-+ 1
              -	 * |				|
              -	 * +-+ 2				+-+ 3
              -	 * |				| |
              -	 * (part of larger tree)		|-+-+ 2
              -	 * 				|
              -	 * 				(part of larger tree)
              -	 * 
              - * - *
            - * Furthermore, addLink is null tolerant and adds new - * nodes even if the linkage overall cannot be created (in other words, due - * to a null or equal parent and child node being provided to - * addLink). - *

            - * Example: For the following addLink(parent, child) - * examples the trees below depict the mutation. - *

              - *
            • addLink(3, null): '3' is added without a linkage. - *
            • addLink(null, 3): '3' is added without a linkage. - *
            • addLink(3, 3): '3' is added without a linkage. - * - *
              -	 * Before				After
              -	 * + (depth 0, trunk)		+ (depth 0, trunk)
              -	 * |				|
              -	 * +-+ 1				+-+ 1
              -	 * |				|
              -	 * +-+ 2				+-+ 2
              -	 * |				|
              -	 * (part of larger tree)		+-+ 3
              -	 * 				|
              -	 * 				(part of larger tree)
              -	 * 
              - * - *
            - * - * @param parent - * @param child - */ - public void addLink(N parent, N child); - - /** - * Adds the given node to the tree as a root node. - *

            - * No mutation occurs if the node is null or if the node already - * exists in the tree. To add a node as a child to an existing parent (as - * opposed to the truck as a root node) use addLink for convenient - * one step 'add-and-link' functionality. - *

            - * Example: For the following addNode(node) examples the - * trees below depicts the mutation. - *

              - *
            • addNode(3): Adds node '3' to the tree. - * - *
              -	 * Before				After
              -	 * + (depth 0, trunk)		+ (depth 0, trunk)
              -	 * |				|
              -	 * +-+ 1				+-+ 1
              -	 * |				|
              -	 * +-+ 2				+-+ 2
              -	 * |				|
              -	 * (part of larger tree)		+-+ 3
              -	 * 				|
              -	 * 				(part of larger tree)
              -	 * 
              - * - *
            • addNode(2): Imparts no mutation since the tree - * already contains the node. - * - *
              -	 * Before				After
              -	 * + (depth 0, trunk)		+ (depth 0, trunk)
              -	 * |				|
              -	 * +-+ 1				+-+ 1
              -	 * |				|
              -	 * +-+ 2				+-+ 2
              -	 * |				|
              -	 * (part of larger tree)		(part of larger tree)
              -	 * 
              - * - *
            • addNode(null): Imparts no mutation. - * - *
              -	 * Before				After
              -	 * + (depth 0, trunk)		+ (depth 0, trunk)
              -	 * |				|
              -	 * +-+ 1				+-+ 1
              -	 * |				|
              -	 * +-+ 2				+-+ 2
              -	 * |				|
              -	 * (part of larger tree)		(part of larger tree)
              -	 * 
              - *
            - *

            - * - * @param node - */ - public void addNode(N node); - - /** - * Removes the link between the child node and its parent in this tree. If - * the child is null, is not contained in the tree or is a root - * node then no mutation will occur. - *

            - * Example: For the following removeLink(child) examples the - * trees below depict the mutation. - *

              - *
            • removeLink(2): '2' becomes a root node. - * - *
              -	 * Before				After
              -	 * + (depth 0, trunk)		+ (depth 0, trunk)
              -	 * |				|
              -	 * +-+ 1				+-+ 1
              -	 * | |				| 
              -	 * | +-+ 2				+-+ 2
              -	 * |				|
              -	 * (part of larger tree)		(part of larger tree)
              -	 * 
              - * - *
            • removeLink(3): '3' becomes a root node and maintains its - * descendants. - * - *
              -	 * Before				After
              -	 * + (trunk)			+ (trunk)
              -	 * |				|
              -	 * +-+ 1				+-+ 1
              -	 * |				|
              -	 * +-+ 2				+-+ 2
              -	 * | |				|	
              -	 * | +-+ 3				+-+ 3
              -	 * | | |				| |
              -	 * | | +-+ 3-1			| +-+ 3-1
              -	 * | | |				| |
              -	 * ----+-+ 3-2			--+-+ 3-2
              -	 * 
              - * - *
            • removeLink(3-1): '3-1' becomes a root node, '3' remains a - * child of '2', and '3-2' remains a child of '3'. - * - *
              -	 * Before				After
              -	 * + (trunk)			+ (trunk)
              -	 * |				|
              -	 * +-+ 1				+-+ 1
              -	 * |				|
              -	 * +-+ 2				+-+ 2
              -	 * | |				| |	
              -	 * | +-+ 3				| +-+ 3
              -	 * | | |				| |
              -	 * | | +-+ 3-1			| +-+ 3-2
              -	 * | | |				| 
              -	 * ----+-+ 3-2			+-+ 3-1
              -	 * 
              - * - *
            • removeLink(1): Imparts no mutation, since '1' is - * already a root node. - *
            • removeLink(3): Imparts no mutation, since '3' is not - * contained in the tree. - *
            • removeLink(null): Imparts no mutation. - * - *
              -	 * Before				After
              -	 * + (depth 0, trunk)		+ (depth 0, trunk)
              -	 * |				|
              -	 * +-+ 1				+-+ 1
              -	 * | |				| |
              -	 * | +-+ 2				| +-+ 2
              -	 * |				|
              -	 * (part of larger tree)		(part of larger tree)
              -	 * 
              - * - *
            - * - * @param child - */ - public void removeLink(N child); - - /** - * Removes the node from the Tree severing all links to other - * nodes, but leaving all of the node's children in the tree at root level. - *

            - * Example: For the following removeNode(node) examples the - * trees below depict the mutation. - *

              - *
            • removeNode(1): - * - *
              -	 * Before				After
              -	 * + (depth 0, trunk)		+ (depth 0, trunk)
              -	 * |				|
              -	 * +-+ 1				+-+ 2
              -	 * |				|
              -	 * +-+ 2				(part of larger tree)
              -	 * |
              -	 * (part of larger tree)
              -	 * 
              - * - *
            • removeNode(3-1): - * - *
              -	 * Before				After
              -	 * + (trunk)			+ (trunk)
              -	 * |				|
              -	 * +-+ 1				+-+ 1
              -	 * |				|
              -	 * +-+ 2				+-+ 2
              -	 * | |				| |
              -	 * | +-+ 3				| +-+ 3
              -	 * | | |				| | |
              -	 * | | +-+ 3-1			----+-+ 3-2
              -	 * | | |
              -	 * ----+-+ 3-2
              -	 * 
              - * - *
            • removeNode(2): - * - *
              -	 * Before				After
              -	 * + (trunk)			+ (trunk)
              -	 * |				|
              -	 * +-+ 1				+-+ 1
              -	 * |				|
              -	 * +-+ 2				+-+ 3
              -	 * | |				| |
              -	 * | +-+ 3				| +-+ 3-1
              -	 * | | |				| |
              -	 * | | +-+ 3-1			--+-+ 3-2
              -	 * | | |
              -	 * ----+-+ 3-2
              -	 * 
              - * - *
            • removeNode(3): - * - *
              -	 * Before				After
              -	 * + (trunk)			+ (trunk)
              -	 * |				|
              -	 * +-+ 1				+-+ 1
              -	 * |				|
              -	 * +-+ 2				+-+ 2
              -	 * | |				|
              -	 * | +-+ 3				+-+ 3-1
              -	 * | | |				|
              -	 * | | +-+ 3-1			+-+ 3-2
              -	 * | | |
              -	 * ----+-+ 3-2
              -	 * 
              - * - *
            - *

            - * Example: For the following removeNode(node) examples the - * trees below depict no mutation cases. - *

              - *
            • removeNode(3): Imparts no mutation. - *
            • removeNode(null): Imparts no mutation. - * - *
              -	 * Before				After
              -	 * + (depth 0, trunk)		+ (depth 0, trunk)
              -	 * |				|
              -	 * +-+ 1				+-+ 1
              -	 * |				|
              -	 * +-+ 2				+-+ 2
              -	 * |				|
              -	 * (part of larger tree)		(part of larger tree)
              -	 * 
              - * - *
            - * - * @param node - */ - public void removeNode(N node); - - /** - * Removes the node from the Tree and all children of the node's - * descendants are then linked as children of the node's parent, if it - * exists. - *

            - * Example: For the following - * removeNodeAndAdoptDescendants(node) examples the trees below - * depict the mutation. - *

              - * - *
            • removeNodeAndAdoptDescendants(3): Node '3' is removed and - * '2' adopts '3-1' and '3-2'. - * - *
              -	 * Before				After
              -	 * + (trunk)			+ (trunk)
              -	 * |				|
              -	 * +-+ 1				+-+ 1
              -	 * |				|
              -	 * +-+ 2				+-+ 2
              -	 * | |				| |
              -	 * | +-+ 3				| +-+ 3-1
              -	 * | | |				| |
              -	 * | | +-+ 3-1			--+-+ 3-2
              -	 * | | |
              -	 * ----+-+ 3-2
              -	 * 
              - * - *
            • removeNodeAndAdoptDescendants(1): No descendants, acts the - * same as removeNode. - * - *
              -	 * Before				After
              -	 * + (depth 0, trunk)		+ (depth 0, trunk)
              -	 * |				|
              -	 * +-+ 1				+-+ 2
              -	 * |				|
              -	 * +-+ 2				(part of larger tree)
              -	 * |
              -	 * (part of larger tree)
              -	 * 
              - * - *
            • removeNodeAndAdoptDescendants(3-1): No descendants, acts the - * same as removeNode. - * - *
              -	 * Before				After
              -	 * + (trunk)			+ (trunk)
              -	 * |				|
              -	 * +-+ 1				+-+ 1
              -	 * |				|
              -	 * +-+ 2				+-+ 2
              -	 * | |				| |
              -	 * | +-+ 3				| +-+ 3
              -	 * | | |				| | |
              -	 * | | +-+ 3-1			----+-+ 3-2
              -	 * | | |
              -	 * ----+-+ 3-2
              -	 * 
              - * - *
            • removeNodeAndAdoptDescendants(2): '3' becomes a root node - * and maintains its descendants. - * - *
              -	 * Before				After
              -	 * + (trunk)			+ (trunk)
              -	 * |				|
              -	 * +-+ 1				+-+ 1
              -	 * |				|
              -	 * +-+ 2				+-+ 3
              -	 * | |				| |
              -	 * | +-+ 3				| +-+ 3-1
              -	 * | | |				| |
              -	 * | | +-+ 3-1			--+-+ 3-2
              -	 * | | |
              -	 * ----+-+ 3-2
              -	 * 
              - * - *
            - *

            - * Example: For the following removeNode(node) examples the - * trees below depict no mutation cases. - *

              - *
            • removeNodeAndAdoptDescendants(3): Imparts no - * mutation. - *
            • removeNodeAndAdoptDescendants(null): Imparts no - * mutation. - * - *
              -	 * Before				After
              -	 * + (depth 0, trunk)		+ (depth 0, trunk)
              -	 * |				|
              -	 * +-+ 1				+-+ 1
              -	 * |				|
              -	 * +-+ 2				+-+ 2
              -	 * |				|
              -	 * (part of larger tree)		(part of larger tree)
              -	 * 
              - * - *
            - * - * @param node - */ - public void removeNodeAndAdoptDescendants(N node); - - /** - * Removes the node from the Tree and all of its children - * recursively. - *

            - * Example: For the following removeNodeAndDescendants(node) - * examples the trees below depict the mutation. - *

              - * - *
            • removeNodeAndDescendants(3): Node '3' and its descendants - * are removed. - * - *
              -	 * Before				After
              -	 * + (trunk)			+ (trunk)
              -	 * |				|
              -	 * +-+ 1				+-+ 1
              -	 * |				|
              -	 * +-+ 2				--+ 2
              -	 * | |
              -	 * | +-+ 3
              -	 * | | |
              -	 * | | +-+ 3-1
              -	 * | | |
              -	 * ----+-+ 3-2
              -	 * 
              - * - *
            • removeNodeAndDescendants(2): Node '2' and its descendants - * are removed. - * - *
              -	 * Before				After
              -	 * + (trunk)			+ (trunk)
              -	 * |				|
              -	 * +-+ 1				+-+ 1
              -	 * |
              -	 * +-+ 2
              -	 * | |
              -	 * | +-+ 3
              -	 * | | |
              -	 * | | +-+ 3-1
              -	 * | | |
              -	 * ----+-+ 3-2
              -	 * 
              - * - *
            • removeNodeAndDescendants(1): No descendants, acts the same - * as removeNode. - * - *
              -	 * Before				After
              -	 * + (depth 0, trunk)		+ (depth 0, trunk)
              -	 * |				|
              -	 * +-+ 1				+-+ 2
              -	 * |				|
              -	 * +-+ 2				(part of larger tree)
              -	 * |
              -	 * (part of larger tree)
              -	 * 
              - * - *
            • removeNodeAndDescendants(3-1): No descendants, acts the same - * as removeNode. - * - *
              -	 * Before				After
              -	 * + (trunk)			+ (trunk)
              -	 * |				|
              -	 * +-+ 1				+-+ 1
              -	 * |				|
              -	 * +-+ 2				+-+ 2
              -	 * | |				| |
              -	 * | +-+ 3				| +-+ 3
              -	 * | | |				| | |
              -	 * | | +-+ 3-1			----+-+ 3-2
              -	 * | | |
              -	 * ----+-+ 3-2
              -	 * 
              - * - *
            - *

            - * Example: For the following removeNode(node) examples the - * trees below depict no mutation cases. - *

              - *
            • removeNodeAndDescendants(3): Imparts no mutation. - *
            • removeNodeAndDescendants(null): Imparts no mutation. - * - *
              -	 * Before				After
              -	 * + (depth 0, trunk)		+ (depth 0, trunk)
              -	 * |				|
              -	 * +-+ 1				+-+ 1
              -	 * |				|
              -	 * +-+ 2				+-+ 2
              -	 * |				|
              -	 * (part of larger tree)		(part of larger tree)
              -	 * 
              - * - *
            - * - * @param node - */ - public void removeNodeAndDescendants(N node); - -} diff --git a/gcm3/src/main/java/util/tree/Tree.java b/gcm3/src/main/java/util/tree/Tree.java deleted file mode 100644 index ce81938f3..000000000 --- a/gcm3/src/main/java/util/tree/Tree.java +++ /dev/null @@ -1,545 +0,0 @@ -package util.tree; - -import java.util.List; - -/** - * An multi-rooted tree that presents as a forest (the trunk at depth 0 is not - * modeled). The tree contains nodes (or vertices) that are generically typed - * objects held in a collection. By definition, all nodes have exactly one - * parent (or are a root node on the trunk) and zero to many children. Root - * nodes will return null as their parent. - *

            - * Implementors of this interface will follow the equals contract for - * comparisons and containment. As such, any node object can be held in a - * Tree instance at most once. Item inspection is {@link Iterable} - * based. All Tree iterators will throw - * {@link UnsupportedOperationException} on remove. - *

            - * Example: For the following example see the tree below. - *

              - *
            • Depicted is a tree with 3 root nodes {1, 2, 3} at depth 1, where rooted - * node '3' has additional descendant children with a depth 2 and 3. - *
            - * - *
            - * + (depth 0, trunk)
            - * |
            - * +-+ 1 (depth 1, rooted node)
            - * |
            - * +-+ 2
            - * |
            - * +-+ 3
            - * | |
            - * | +-+ 3-1 (depth 2)
            - * | |
            - * | +-+ 3-2
            - * | | |
            - * | | +-+ 3-2-1 (depth 3)
            - * | | |
            - * | | +-+ 3-2-2
            - * | | 
            - * | +-+ 3-3
            - * | | |
            - * | | +-+ 3-3-1
            - * | | |
            - * | | +-+ 3-3-2
            - * |
            - * (part of larger tree)
            - * 
            - * - * @see http://en.wikipedia.org/wiki/Tree_(graph_theory)#rooted_tree - * - * @see http://mathworld.wolfram.com/RootedTree.html - * - * @author Shawn Hatch - * @author Christopher R. Ludka - * - * @param - * the type of nodes maintained by this tree - */ -public interface Tree { - - /** - * Returns true if given object exists in the Tree. - *

            - * If the given object is null will return false since - * null does not explicitly exist in the Tree. - *

            - * Examples: For the following (node) value examples see the tree - * below. - *

              - *
            • containsNode(3): true - *
            • containsNode(3-3): true - *
            • containsNode(3-3-1): true - *
            • containsNode(4-2): false, assuming '4-2' is - * not part of the larger tree. - *
            • containsNode(null): false - *
            - * - *
            -	 * (part of larger tree)
            -	 * |
            -	 * +-+ 3
            -	 * | |
            -	 * | +-+ 3-1
            -	 * | |
            -	 * | +-+ 3-2
            -	 * | | |
            -	 * | | +-+ 3-2-1
            -	 * | | |
            -	 * | | +-+ 3-2-2
            -	 * | | 
            -	 * | +-+ 3-3
            -	 * | | |
            -	 * | | +-+ 3-3-1
            -	 * | | |
            -	 * | | +-+ 3-3-2
            -	 * |
            -	 * (part of larger tree)
            -	 * 
            - * - * @param node - * @return true if given object exists in the Tree - */ - public boolean containsNode(Object node); - - /** - * Returns the number of nodes in this Tree. This count includes - * all rooted nodes and their child descendants. - *

            - * Example: For the following example see the tree below. - *

              - *
            • getAllCount(): 5 - *
            - * - *
            -	 * + (trunk does not contribute to the count)
            -	 * |
            -	 * +-+ 1 (1)
            -	 * |
            -	 * +-+ 2 (2)
            -	 * |
            -	 * +-+ 3 (3)
            -	 * | |
            -	 * | +-+ 3-1 (4)
            -	 * | |
            -	 * --+-+ 3-2 (5)
            -	 * 
            - * - *

            - * Example: For the following example see the unpopulated (empty) - * tree below. - *

              - *
            • getAllCount(): 0 - *
            - * - *
            -	 * + (trunk does not contribute to the count)
            -	 * |
            -	 * unpopulated
            -	 * 
            - * - * @return the number of nodes in this Tree - */ - public int getAllCount(); - - /** - * Returns all nodes in the Tree. - * - *

            - * Example: For the following example see the tree below. - *

              - *
            • getAllNodes(): {@link Iterable} set of nodes {1, 2, 3, 3-1, - * 3-2}. - *
            - * - *
            -	 * + (trunk not in the result)
            -	 * |
            -	 * +-+ 1
            -	 * |
            -	 * +-+ 2
            -	 * |
            -	 * +-+ 3
            -	 * | |
            -	 * | +-+ 3-1
            -	 * | |
            -	 * --+-+ 3-2
            -	 * 
            - * - *

            - * Example: For the following example see the unpopulated (empty) - * tree below. - *

              - *
            • getAllNodes(): {@link Iterable} empty set. - *
            - * - *
            -	 * + (trunk not in the result)
            -	 * |
            -	 * unpopulated
            -	 * 
            - * - * @return all nodes in the Tree - */ - public Iterable getAllNodes(); - public List getAllNodesList(); - - /** - * Returns the number of first order children for the given parent. - *

            - * Providing a null object for the parent node will return a value - * of zero since null does not explicitly exist in the - * Tree. - *

            - * Examples: For the following (parent) value examples see the tree - * below. - *

              - *
            • getChildCount(3): 3, since '3' has the first order children - * of {3-1, 3-2, 3-3}. - *
            • getChildCount(3-3): 2, since '3-3' has the first order - * children of {3-2-1, 3-2-2}. - *
            • getChildCount(3-3-1): 0 - *
            • getChildCount(4-2): 0, assuming '4-2' is not part of - * the larger tree. - *
            • getChildCount(null): 0. Note: To obtain the number of - * rooted nodes use getRootCount. - *
            - * - *
            -	 * (part of larger tree)
            -	 * |
            -	 * +-+ 3
            -	 * | |
            -	 * | +-+ 3-1
            -	 * | |
            -	 * | +-+ 3-2
            -	 * | | |
            -	 * | | +-+ 3-2-1
            -	 * | | |
            -	 * | | +-+ 3-2-2
            -	 * | | 
            -	 * | +-+ 3-3
            -	 * | | |
            -	 * | | +-+ 3-3-1
            -	 * | | |
            -	 * | | +-+ 3-3-2
            -	 * |
            -	 * (part of larger tree)
            -	 * 
            - * - * @param parent - * @return the number of first order children for the given parent - */ - public int getChildCount(N parent); - - /** - * Returns the first order children for the given parent. - *

            - * If the given parent is null or cannot be found in the - * Tree an {@link Iterable} empty set will be returned. - *

            - * Example: For the following getChildNodes(parent) example - * see the tree below. - *

              - *
            • getChildNodes(3): {@link Iterable} set of nodes {3-1, 3-2, - * 3-3}. Note that {3-2-1, 3-2-2, 3-3-1, 3-3-2} are not in the result - * because those nodes are second order children nodes from '3', not first - * order. - *
            - * - *
            -	 * (part of larger tree)
            -	 * |
            -	 * +-+ 3 (given parent)
            -	 * | |
            -	 * | +-+ 3-1 (in result)
            -	 * | |
            -	 * | +-+ 3-2 (in result)
            -	 * | | |
            -	 * | | +-+ 3-2-1 (not in result)
            -	 * | | |
            -	 * | | +-+ 3-2-2 (not in result)
            -	 * | | 
            -	 * | +-+ 3-3 (in result)
            -	 * | | |
            -	 * | | +-+ 3-3-1 (not in result)
            -	 * | | |
            -	 * | | +-+ 3-3-2 (not in result)
            -	 * |
            -	 * (part of larger tree)
            -	 * 
            - *

            - * Examples: For the following (parent) value examples see the tree - * below. - *

              - *
            • getChildNodes(3-1): {@link Iterable} empty set. - *
            • getChildNodes(3-2): {@link Iterable} set of nodes {3-2-1, - * 3-2-2}. - *
            • getChildNodes(4-2): {@link Iterable} empty set, assuming - * '4-2' is not part of the larger tree. - *
            • getChildNodes(null): {@link Iterable} empty set. - * Note: To get all the rooted nodes and their descendants use - * getRootNodes. - *
            - * - *
            -	 * + (trunk)
            -	 * |
            -	 * +-+ 1
            -	 * |
            -	 * +-+ 2
            -	 * |
            -	 * +-+ 3
            -	 * | |
            -	 * | +-+ 3-1
            -	 * | |
            -	 * | +-+ 3-2
            -	 * | | |
            -	 * | | +-+ 3-2-1
            -	 * | | |
            -	 * | | +-+ 3-2-2
            -	 * | | 
            -	 * | +-+ 3-3
            -	 * | | |
            -	 * | | +-+ 3-3-1
            -	 * | | |
            -	 * | | +-+ 3-3-2
            -	 * |
            -	 * (part of larger tree)
            -	 * 
            - * - * @param parent - * @return the first order children for the given parent - */ - public Iterable getChildNodes(N parent); - public List getChildNodesList(N parent); - - /** - * Returns the number of descendant children for the given parent. - *

            - * Providing a null object for the parent node will return a value - * of zero since null does not explicitly exist in the - * Tree. - *

            - * - * Examples: For the following (parent) value examples see the tree - * below. - *

              - *
            • getDescendantCount(3): 7, since '3' has descendants {3-1, - * 3-2, 3-2-1, 3-2-2, 3-3, 3-3-1, 3-3-2}. - *
            • - * getDescendantCount(3-3): 2, since '3-3' has the descendants - * {3-2-1, 3-2-2}. - *
            • getDescendantCount(3-3-1): 0 - *
            • getDescendantCount(4-2): 0, assuming '4-2' is not - * part of the larger tree. - *
            • getDescendantCount(null): 0. Note: To obtain the - * number of rooted nodes use getRootCount. - * - *
              -	 * (part of larger tree)
              -	 * |
              -	 * +-+ 3
              -	 * | |
              -	 * | +-+ 3-1
              -	 * | |
              -	 * | +-+ 3-2
              -	 * | | |
              -	 * | | +-+ 3-2-1
              -	 * | | |
              -	 * | | +-+ 3-2-2
              -	 * | | 
              -	 * | +-+ 3-3
              -	 * | | |
              -	 * | | +-+ 3-3-1
              -	 * | | |
              -	 * | | +-+ 3-3-2
              -	 * |
              -	 * (part of larger tree)
              -	 * 
              - *
            - * - * @param parent - * @return the number of descendant children for the given parent - */ - public int getDescendantCount(N parent); - - /** - * Returns the given parent's descendants. - *

            - * If the given parent is null or cannot be found in the - * Tree an {@link Iterable} empty set will be returned. - *

            - * Example: For the following getChildNodes(parent) examples - * see the tree below. - *

              - *
            • getDescendantNodes(3): {@link Iterable} set of nodes {3-1, - * 3-2, 3-2-1, 3-2-2, 3-3, 3-3-1, 3-3-2}. - *
            • getDescendantNodes(3-2): {@link Iterable} set of nodes - * {3-2-1, 3-2-2}. - *
            • getDescendantNodes(3-1): {@link Iterable} empty set. - *
            • getDescendantNodes(4-2): {@link Iterable} empty set, - * assuming '4-2' is not part of the larger tree. - *
            • getDescendantNodes(null): {@link Iterable} empty set. - * Note: To get all the rooted nodes and their descendants use - * getAllNodes. - * - *
              -	 * (part of larger tree)
              -	 * |
              -	 * +-+ 3 (given parent)
              -	 * | |
              -	 * | +-+ 3-1 (in result)
              -	 * | |
              -	 * | +-+ 3-2 (in result)
              -	 * | | |
              -	 * | | +-+ 3-2-1 (in result)
              -	 * | | |
              -	 * | | +-+ 3-2-2 (in result)
              -	 * | | 
              -	 * | +-+ 3-3 (in result)
              -	 * | | |
              -	 * | | +-+ 3-3-1 (in result)
              -	 * | | |
              -	 * | | +-+ 3-3-2 (in result)
              -	 * |
              -	 * (part of larger tree)
              -	 * 
              - *
            - * - * @param parent - * @return the given parent's descendants - */ - public Iterable getDescendantNodes(N parent); - public List getDescendantNodesList(N parent); - - /** - * Returns the parent of the given child if that child is contained in this - * tree and has a parent contained in this tree. Returns null otherwise. - * - * Recall, by definition all nodes have exactly one parent (or are a root - * node on the trunk) and zero to many children. - *

            - * If the given child is a root node or cannot be found in the Tree - * then a value of null is returned. - *

            - * Example: For the following getParentNode(child) examples - * see the tree below. - *

              - *
            • getParentNode(3-2): '3' - *
            • getParentNode(3): null - *
            • getParentNode(4-2): null, assuming '4-2' is - * not part of the larger tree. - *
            • getParentNode(null): null - * - *
              -	 * + (trunk)
              -	 * |
              -	 * +-+ 1
              -	 * |
              -	 * +-+ 2
              -	 * |
              -	 * +-+ 3
              -	 * | |
              -	 * | +-+ 3-1
              -	 * | |
              -	 * --+-+ 3-2
              -	 * 
              - *
            - * - * @param child - * @return the parent of the given child - */ - public N getParentNode(N child); - - /** - * Returns the number of root nodes in this Tree. Rooted nodes are - * depth 1 in the Tree off the trunk, which is depth 0. - *

            - * Example: For the following example see the tree below. - *

              - *
            • getRootCount(): 3. Nodes {3-1, 3-2} are not part of - * the count because their parent is '3' rather than the trunk. - *
            - * - *
            -	 * + (depth 0, trunk not in the result)
            -	 * |
            -	 * +-+ 1 (depth 1, in the result)
            -	 * |
            -	 * +-+ 2 (depth 1, in the result)
            -	 * |
            -	 * +-+ 3 (depth 1, in the result)
            -	 * | |
            -	 * | +-+ 3-1 (depth 2, not in the result)
            -	 * | |
            -	 * | +-+ 3-2 (depth 2, not in the result)
            -	 * |
            -	 * (part of larger tree)
            -	 * 
            - *

            - * Example: For the following example see the unpopulated (empty) - * tree below. - *

              - *
            • getRootCount(): 0 - *
            - * - *
            -	 * + (trunk does not contribute to the count)
            -	 * |
            -	 * unpopulated
            -	 * 
            - * - * - * @return the number of root nodes in this Tree - */ - public int getRootCount(); - - /** - * Returns the root nodes in this Tree. Rooted nodes are depth 1 in - * the Tree off the trunk, which is depth 0. - *

            - * Example: For the following example see the tree below. - *

              - *
            • getRootNodes(): {@link Iterable} set of root nodes {1, 2, - * 3}. Nodes {3-1, 3-2} are not part of the of the returned set - * because their parent is '3' rather than the trunk. - *
            - * - *
            -	 * + (depth 0, trunk not in the result)
            -	 * |
            -	 * +-+ 1 (depth 1, in the result)
            -	 * |
            -	 * +-+ 2 (depth 1, in the result)
            -	 * |
            -	 * +-+ 3 (depth 1, in the result)
            -	 * | |
            -	 * | +-+ 3-1 (depth 2, not in the result)
            -	 * | |
            -	 * | +-+ 3-2 (depth 2, not in the result)
            -	 * |
            -	 * (part of larger tree)
            -	 * 
            - *

            - * Example: For the following example see the unpopulated (empty) - * tree below. - *

              - *
            • getRootNodes(): {@link Iterable} empty set. - *
            - * - *
            -	 * + (trunk not in the result)
            -	 * |
            -	 * unpopulated
            -	 * 
            - * - * @return the root nodes in this Tree - */ - public Iterable getRootNodes(); - public List getRootNodesList(); - -} diff --git a/gcm3/src/main/java/util/tree/impl/AbstractMutableTree.java b/gcm3/src/main/java/util/tree/impl/AbstractMutableTree.java deleted file mode 100644 index 83273f68b..000000000 --- a/gcm3/src/main/java/util/tree/impl/AbstractMutableTree.java +++ /dev/null @@ -1,337 +0,0 @@ -package util.tree.impl; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import util.tree.MutableTree; - - -/** - * An implementation of {@link MutableTree}. - * - * @author Shawn Hatch - * @author Christopher R. Ludka - * - * @param - * the type of nodes maintained by this tree - */ -public abstract class AbstractMutableTree extends AbstractTree implements MutableTree { - - // Maps parent -> children: - // Note: All nodes are parent candidates so all nodes reside in this map - private Map> parentToChildMap; - - // Maps child --> parent - private Map childToParentMap; - - // maps null virtual parent -> Set(child) - private Set rootNodes; - - protected final void init() { - parentToChildMap = createParentToChildMap(); - childToParentMap = createChildToParentMap(); - rootNodes = createNodeSet(); - } - - @Override - public void addLink(N parent, N child) { - // Add nodes if they do not exist - addNode(parent); - addNode(child); - - // If either node is null return - if (parent == null || child == null) { - return; - } - - // If the link already exists return - if (parent.equals(child)) { - return; - } - - // Update current linkages - if (isDescendant(child, parent)) { - // Remove parent's link if currently a descendant of the child - removeLink(parent); - } - removeLink(child); - rootNodes.remove(child); - - // Dual reference parent -> child linkage - parentToChildMap.get(parent).add(child); - childToParentMap.put(child, parent); - } - - /* - * Returns true if the descendant has a lineage to the ancestor. Begin with - * the given descendant node and walk lineage of the tree 'up' until the - * ancestor is found or the trunk (node == null) is reached. - */ - private boolean isDescendant(N ancestor, N descendant) { - boolean result = false; - N node = descendant; - while (node != null) { - if (node.equals(ancestor)) { - result = true; - break; - } - node = childToParentMap.get(node); - } - return result; - } - - @Override - public void addNode(N node) { - if ((node == null) || containsNode(node)) { - return; - } - - // Created as a root node - rootNodes.add(node); - parentToChildMap.put(node, createNodeSet()); - } - - @Override - public boolean containsNode(Object node) { - if (node == null) { - return false; - } else { - return parentToChildMap.containsKey(node); - } - } - - protected abstract Map createChildToParentMap(); - - protected abstract Set createNodeSet(); - - protected abstract Map> createParentToChildMap(); - - @Override - public int getAllCount() { - return parentToChildMap.size(); - } - - @Override - public Iterable getAllNodes() { - return new ImmutableIterable(parentToChildMap.keySet()); - } - - @Override - public List getAllNodesList() { - return new ArrayList<>(parentToChildMap.keySet()); - } - - @Override - public int getChildCount(N parent) { - Set children = getChildrenSet(parent); - if (children == null) { - return 0; - } - return children.size(); - } - - @Override - public Iterable getChildNodes(N parent) { - Set children = getChildrenSet(parent); - if (children == null) { - return new ImmutableIterable(null); - } - return new ImmutableIterable(children); - } - - @Override - public List getChildNodesList(N parent) { - List result = new ArrayList<>(); - Set children = getChildrenSet(parent); - if (children != null) { - result.addAll(children); - } - return result; - } - - private Set getChildrenSet(N parent) { - if (!containsNode(parent)) { - return null; - } - Set children = parentToChildMap.get(parent); - if (children == null) { - throw new RuntimeException("Parent(" + parent.toString() + ") is not tracking its children."); - } - return children; - } - - @Override - public int getDescendantCount(N parent) { - return getDescendantSet(parent).size(); - } - - @Override - public Iterable getDescendantNodes(N parent) { - return new ImmutableIterable(getDescendantSet(parent)); - } - - @Override - public List getDescendantNodesList(N parent) { - return new ArrayList<>(getDescendantSet(parent)); - } - - private Set getDescendantSet(N parent) { - // Check if parent is valid - Set result = createNodeSet(); - if (!containsNode(parent)) { - return result; - } - - // Initialize - Set tempParentSet = createNodeSet(); - Set tempChildrenSet = createNodeSet(); - Set tempSwap; - tempParentSet.add(parent); - - while (true) { - // Add all parent's children - for (N tempParent : tempParentSet) { - tempChildrenSet.addAll(parentToChildMap.get(tempParent)); - } - - // All parents are added to the result - result.addAll(tempParentSet); - - // If all parents had no children then result is complete - if (tempChildrenSet.size() == 0) { - break; - } - - // Previous parent's children become next round of parents - tempParentSet.clear(); - tempSwap = tempParentSet; - tempParentSet = tempChildrenSet; - tempChildrenSet = tempSwap; - } - - // Remove artifact of the given parent being added - result.remove(parent); - return result; - } - - @Override - public N getParentNode(N node) { - if (node == null) { - return null; - } - return childToParentMap.get(node); - } - - @Override - public int getRootCount() { - return rootNodes.size(); - } - - @Override - public Iterable getRootNodes() { - return new ImmutableIterable(rootNodes); - } - - @Override - public List getRootNodesList() { - return new ArrayList<>(rootNodes); - } - - @Override - public void removeLink(N child) { - if (!containsNode(child)) { - return; - } - - // Remove Child -> Parent reference - N parent = childToParentMap.remove(child); - if (parent == null) { - // Valid only occur if child is a root node - if (!rootNodes.contains(child)) { - throw new RuntimeException("Child(" + child.toString() + ") has a (parent == null) but is not recognized as a root node."); - } - return; - } - - // Remove Parent -> Child reference - Set children = parentToChildMap.get(parent); - if (children == null) { - throw new RuntimeException("Parent(" + parent.toString() + ") did not contain any children when trying to remove the expected child(" + child.toString() + ")."); - } - if (!children.remove(child)) { - throw new RuntimeException("Parent(" + parent.toString() + ") with children(" + children.toString() + ") does not contain the expected child(" + child.toString() + ")"); - } - - // Add the child as a root node - rootNodes.add(child); - } - - @Override - public void removeNode(N node) { - if (!containsNode(node)) { - return; - } - - // Make the node and all it's children root nodes - removeLink(node); - List list = new ArrayList(); - for (N child : getChildNodes(node)) { - list.add(child); - } - for (N child : list) { - removeLink(child); - } - - // Complete removals with validation - if (getChildCount(node) != 0) { - throw new RuntimeException("Node(" + node.toString() + ") has children(" + getChildNodes(node).toString() + ") when about to be removed."); - } - if (!rootNodes.remove(node)) { - throw new RuntimeException("Node(" + node.toString() + ") is not a recognized root node when about to be removed."); - } - if (parentToChildMap.remove(node) == null) { - throw new RuntimeException("Node(" + node.toString() + ") is not a recognized node when about to be removed."); - } - } - - @Override - public void removeNodeAndAdoptDescendants(N node) { - if (!containsNode(node)) { - return; - } - - // Obtain a list of orphans - Set orphans = parentToChildMap.get(node); - List list = new ArrayList(); - for (N child : orphans) { - list.add(child); - } - - // Grand parent adopts the children before they become orphaned. If the - // grand parent is null, then the children will become root nodes. - N grandParent = getParentNode(node); - for (N child : list) { - addLink(grandParent, child); - } - - // Remove the node - removeNode(node); - } - - @Override - public void removeNodeAndDescendants(N node) { - if (!containsNode(node)) { - return; - } - - // Remove the node and descendant nodes - Set nodesToRemove = getDescendantSet(node); - nodesToRemove.add(node); - for (N n : nodesToRemove) { - removeNode(n); - } - } - -} diff --git a/gcm3/src/main/java/util/tree/impl/AbstractTree.java b/gcm3/src/main/java/util/tree/impl/AbstractTree.java deleted file mode 100644 index a9d43edf7..000000000 --- a/gcm3/src/main/java/util/tree/impl/AbstractTree.java +++ /dev/null @@ -1,142 +0,0 @@ -package util.tree.impl; - -import util.tree.Tree; - -/** - * This class implements the hashCode and equals portions of - * the {@link Tree} interface. All iterators returned by this implementation - * will fail to mutate the tree and will throw - * {@link UnsupportedOperationException} if remove is called on the - * iterator. - * - * @author Shawn Hatch - * @author Christopher R. Ludka - * - * @param - * the type of nodes maintained by this tree - */ - -public abstract class AbstractTree implements Tree { - - /** - * Returns true if the object meets the equality criteria for - * {@link tree}. Equality is true if the given object is also a - * Tree, the two trees are the same size, and contain equal - * corresponding pairs of elements occurring in the same order. - *

            - * Recall by definition, all nodes have exactly one parent (possibly the - * null root) and zero to many children. Therefore, order can be determined - * by examining if each node shares the same parent. The same parent is - * determined by examining if the parents areRelated which - * implements the equals contract. - * - * @param obj - * object to be compared for equality with this Tree - * @return true if the specified object is equal to this - * Tree - */ - @SuppressWarnings("unchecked") - @Override - public boolean equals(Object obj) { - // If the object is itself return true - if (this == obj) { - return true; - } - - // Null check - if (obj == null) { - return false; - } - - // If the object is not a tree return false - if (!(obj instanceof Tree)) { - return false; - } - - @SuppressWarnings("rawtypes") - Tree other = (Tree) obj; - - // If the two trees are not the same size return false - if (other.getAllCount() != getAllCount()) { - return false; - } - - // Check that both trees contain 'equal' corresponding pairs of elements - - for (N node : getAllNodes()) { - // Containment check - if (!other.containsNode(node)) { - return false; - } - // Establish that each node shares the same parent by examining if - // those parents are 'equal' - N parent = getParentNode(node); - if (parent == null) { - // Parent is a root node, so the other node must also be a root - // node - if (other.getParentNode(node) != null) { - return false; - } - } else { - if (!parent.equals(other.getParentNode(node))) { - return false; - } - } - } - return true; - } - - /** - * Returns the hash code value for {@link Tree}. The hash code of - * Tree is defined to be the sum of the hash codes of each node in - * the Tree. - * - * @return the hash code value for this map - */ - @Override - public int hashCode() { - int result = 0; - for (N node : getAllNodes()) { - result += node.hashCode(); - } - return result; - } - - /** - * Returns a string representation of this tree. The string representation - * consists of a list of parent-child pairs where the child parts are in the - * order returned by the tree's getAllNodes iterable, enclosed in - * braces ( "{}"). Adjacent pairs are separated by the characters - * ", " (comma and space). Each pair is rendered as the parent - * followed by an arrow sign ("->") followed by the associated - * child. Nodes are converted to strings as by - * {@link String#valueOf(Object)}. - * - * @return a string representation of this tree - */ - @Override - public String toString() { - - StringBuilder sb = new StringBuilder(); - sb.append('{'); - for (N child : getAllNodes()) { - if (sb.length() > 1) { - sb.append(", "); - } - N parent = getParentNode(child); - if (parent == this) { - sb.append("(this Tree)"); - } else { - sb.append(parent); - } - sb.append("->"); - if (child == this) { - sb.append("(this Tree)"); - } else { - sb.append(child); - } - } - sb.append('}'); - return sb.toString(); - } -} diff --git a/gcm3/src/main/java/util/tree/impl/ImmutableIterable.java b/gcm3/src/main/java/util/tree/impl/ImmutableIterable.java deleted file mode 100644 index 58ae74f0c..000000000 --- a/gcm3/src/main/java/util/tree/impl/ImmutableIterable.java +++ /dev/null @@ -1,70 +0,0 @@ -package util.tree.impl; - -import java.util.Iterator; -import java.util.NoSuchElementException; - -/** - * Provides an immutable wrapper around an iterable. Specifically, the iterator - * provided by this class throws an UnsupportedOperationException on remove(). - * - * @author Joshua Mark Rutherford - * @author Shawn Hatch - * - * @param - * The type contained within the iterable. - */ -public final class ImmutableIterable implements Iterable { - - private static class ImmutableIterator implements Iterator { - - private Iterator iterator; - - public ImmutableIterator(Iterator iterator) { - this.iterator = iterator; - } - - @Override - public boolean hasNext() { - if (iterator == null) { - return false; - } - return iterator.hasNext(); - } - - @Override - public K next() { - if (iterator == null) { - throw new NoSuchElementException(); - } - return iterator.next(); - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - } - - /** - * Creates a new instance of the com.saic.afk.common.util.ImmutableIterable - * class. Null is an allowable iterable to be wrapped. - */ - - public ImmutableIterable(Iterable iterable) { - this.iterable = iterable; - } - - /** - * {@inheritDoc} - */ - @Override - public Iterator iterator() { - if (iterable == null) { - return new ImmutableIterator<>(null); - } - return new ImmutableIterator<>(iterable.iterator()); - } - - private Iterable iterable; - -} diff --git a/gcm3/src/main/java/util/tree/impl/ImmutableTree.java b/gcm3/src/main/java/util/tree/impl/ImmutableTree.java deleted file mode 100644 index fe004d1e3..000000000 --- a/gcm3/src/main/java/util/tree/impl/ImmutableTree.java +++ /dev/null @@ -1,111 +0,0 @@ -package util.tree.impl; - -import java.util.List; - -import util.tree.MutableTree; -import util.tree.Tree; - -/** - * A simple implementation of the {@link Tree} interface that uses a - * {@link MutableTree} instance to internally contain the tree. All iterators - * returned by this implementation will fail to mutate the tree and will throw - * {@link UnsupportedOperationException} if remove is called on the - * iterator. - * - * @author Shawn Hatch - * @author Christopher R. Ludka - * - * @param - */ - -// UnmodifiableTree -public final class ImmutableTree extends AbstractTree { - - private Tree tree; - - @SuppressWarnings("unused") - private ImmutableTree() { - //hidden constructor - } - - public ImmutableTree(Tree tree) { - super(); - this.tree = tree; - } - - - @Override - public boolean containsNode(Object obj) { - return tree.containsNode(obj); - } - - @Override - public int getAllCount() { - return tree.getAllCount(); - } - - @Override - public Iterable getAllNodes() { - return tree.getAllNodes(); - } - - @Override - public int getChildCount(N parent) { - return tree.getChildCount(parent); - } - - @Override - public Iterable getChildNodes(N node) { - return tree.getChildNodes(node); - } - - @Override - public int getDescendantCount(N parent) { - return tree.getDescendantCount(parent); - } - - @Override - public Iterable getDescendantNodes(N node) { - return tree.getDescendantNodes(node); - } - - @Override - public N getParentNode(N node) { - return tree.getParentNode(node); - } - - @Override - public int getRootCount() { - return tree.getRootCount(); - } - - @Override - public Iterable getRootNodes() { - return tree.getRootNodes(); - } - - @Override - public String toString() { - return tree.toString(); - } - - @Override - public List getAllNodesList() { - return tree.getAllNodesList(); - } - - @Override - public List getChildNodesList(N parent) { - return tree.getChildNodesList(parent); - } - - @Override - public List getDescendantNodesList(N parent) { - return tree.getDescendantNodesList(parent); - } - - @Override - public List getRootNodesList() { - return tree.getRootNodesList(); - } -} diff --git a/gcm3/src/main/java/util/tree/impl/LinkedHashTree.java b/gcm3/src/main/java/util/tree/impl/LinkedHashTree.java deleted file mode 100644 index 27e699e95..000000000 --- a/gcm3/src/main/java/util/tree/impl/LinkedHashTree.java +++ /dev/null @@ -1,62 +0,0 @@ -package util.tree.impl; - -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -import util.tree.Tree; - -/** - * A mutable tree using where iteration of nodes occurs in the order in which - * nodes are added to this tree. - * - * @author Shawn Hatch - * @author Christopher R. Ludka - * - * @param - */ -public final class LinkedHashTree extends AbstractMutableTree { - - /** - * Constructs an empty mutable LinkedHashTree instance. - */ - public LinkedHashTree() { - this(null); - } - - /** - * Constructs an mutable LinkedHashTree instance with the same - * mappings as the specified tree. - * - * @param - * node type - * @param tree - */ - public LinkedHashTree(Tree tree) { - init(); - if (tree != null) { - for (T child : tree.getAllNodes()) { - T parent = tree.getParentNode(child); - addLink(parent, child); - } - } - - } - - @Override - protected Map createChildToParentMap() { - return new LinkedHashMap(); - } - - @Override - protected Set createNodeSet() { - return new LinkedHashSet(); - } - - @Override - protected Map> createParentToChildMap() { - return new LinkedHashMap>(); - } - -} diff --git a/gcm3/src/main/java/util/tree/impl/SortedTree.java b/gcm3/src/main/java/util/tree/impl/SortedTree.java deleted file mode 100644 index 38970323a..000000000 --- a/gcm3/src/main/java/util/tree/impl/SortedTree.java +++ /dev/null @@ -1,97 +0,0 @@ -package util.tree.impl; - -import java.util.Comparator; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; -import java.util.TreeSet; - -import util.tree.Tree; - -/** - * A mutable tree using Comparable or Comparator for iteration of nodes. - * - * @author Shawn Hatch - * @author Christopher R. Ludka - * - * @param - */ -public final class SortedTree extends AbstractMutableTree { - - private Comparator comparator; - - /** - * Constructs an empty mutable SortedTree instance. - */ - public SortedTree() { - this(null,null); - } - - /** - * Constructs an empty mutable SortedTree instance that will be - * sorted according to the given {@link Comparator}. - * - * @param comparator - */ - public SortedTree(Comparator comparator) { - this(null,comparator); - } - - /** - * Constructs an mutable SortedTree instance with the same mappings - * as the specified tree. - * - * @param - * node type - * @param tree - */ - public SortedTree(Tree tree) { - this(tree, null); - } - - /** - * Constructs an mutable SortedTree instance with the same mappings - * as the specified tree and sorted according to the given - * {@link Comparator}. - * - * @param - * node type - * @param tree - * @param comparator - */ - public SortedTree(Tree tree, Comparator comparator) { - this.comparator = comparator; - init(); - if (tree != null) { - for (T child : tree.getAllNodes()) { - T parent = tree.getParentNode(child); - addLink(parent, child); - } - } - } - - @Override - protected Map createChildToParentMap() { - if (comparator == null) { - return new TreeMap(); - } - return new TreeMap(comparator); - } - - @Override - protected Set createNodeSet() { - if (comparator == null) { - return new TreeSet(); - } - return new TreeSet(comparator); - } - - @Override - protected Map> createParentToChildMap() { - if (comparator == null) { - return new TreeMap>(); - } - return new TreeMap>(comparator); - } - -} diff --git a/gcm3/src/main/java/util/vector/Circle2D.java b/gcm3/src/main/java/util/vector/Circle2D.java deleted file mode 100644 index 9968f5545..000000000 --- a/gcm3/src/main/java/util/vector/Circle2D.java +++ /dev/null @@ -1,565 +0,0 @@ -package util.vector; - -import java.util.List; - -import org.apache.commons.math3.util.FastMath; - -import net.jcip.annotations.Immutable; -import util.spherical.Chirality; - -/** - * A utility for calculating the smallest circle that encompasses a set of 2D - * points. - * - * The utility can use any of four algorithms for forming the circle. - * - * CENTROID - The centroid of the points is used as the center of the circle with - * the radius being the largest distance from any point to that centroid. - * Solution is order O(n). - * - * N3 - The order O(n^3) version of the N4 algorithm. It is a bit more complex - * than N4, but is just a streamlined version of N4 and should return the same - * circle within precision error. - * - * N4 - The order O(n^4) algorithm that returns the smallest circle that will - * contain all of the points. This will return the same solution as Nimrod - * Megiddo's O(n) algorithm. - * - * COLLAPSING_BUBBLE - An order O(n) algorithm that returns the same solution as - * N4 approximately 80+% of the time. The remaining ~20% the algorithm returns - * circles that are generally a fraction of percent larger than the optimal - * solution of N4 and Megiddo's O(n) algorithm. - * - * Future improvements to this class would likely include an implementation of - * Megiddo's O(n) algorithm as well as a version of this utility for spherical - * polygons. - * - * @author Shawn Hatch - * - */ -@Immutable -public class Circle2D { - - public static enum SolutionAlgorithm { - CENTROID, N3, N4, COLLAPSING_BUBBLE - } - - private final Vector2D center; - - private final double radius; - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("Circle2D [radius="); - builder.append(radius); - builder.append(", center="); - builder.append(center); - builder.append("]"); - return builder.toString(); - } - - /** - * Returns the radius of this circle - */ - public double getRadius() { - return radius; - } - - /** - * Returns true if and only if both the center and radius of this circle - * contain finite values - */ - public boolean isFinite() { - return Double.isFinite(radius) && center.isFinite(); - } - - /** - * Returns the center of this circle - */ - public Vector2D getCenter() { - return center; - } - - private Circle2D() { - center = new Vector2D(); - radius = Double.POSITIVE_INFINITY; - } - - private Circle2D(Vector2D a) { - center = a; - radius = 0; - } - - private Circle2D(Vector2D a, double radius) { - center = a; - if (radius < 0) { - throw new RuntimeException("negative radius"); - } - this.radius = radius; - } - - private Circle2D(Vector2D a, Vector2D b) { - center = a.add(b).scale(0.5); - radius = FastMath.max(center.distanceTo(a), center.distanceTo(b)); - } - - /** - * Returns true if and only if the given point is contained in this circle. - */ - public boolean contains(Vector2D point) { - return center.distanceTo(point) <= radius; - } - - /** - * Returns true if and only if no point is outside of this circle. An empty - * list of points will return true. - */ - public boolean contains(List points) { - for (Vector2D point : points) { - if (!contains(point)) { - return false; - } - } - return true; - } - - /** - * Constructs a {@link Circle2D} from the given points via the selected - * algorithm. - * - * N3 and N4 will return the smallest circle possible. N3 is O(n^3) while N4 - * is O(n^4). - * - * CENTROID is the fastest O(n), but in practice may occasionally return a - * circle that may be up to 30% larger than the optimal solution provided by - * N3 - * - * COLLAPSING_BUBBLE a O(n) algorithm that is a bit slower than CENTROID but - * returns the smallest possible circle about 80% of the time. The remaining - * cases will be generally a fraction of a percent larger than optimal. This - * is the current best option for most purposes. - */ - public Circle2D(List points, SolutionAlgorithm solutionAlgorithm) { - /* - * The presumption is that the points will nearly always be distinct. - * Some of the algorithms below work only when given at least three - * points, even when they are not distinct. Thus we handle the first - * three cases here. - */ - - Circle2D c; - switch (points.size()) { - case 0: - c = new Circle2D(); - break; - case 1: - c = new Circle2D(points.get(0)); - break; - case 2: - c = new Circle2D(points.get(0), points.get(1)); - break; - default: - switch (solutionAlgorithm) { - case CENTROID: - c = getCentroidSolution(points); - break; - case N3: - c = getN3Solution(points); - break; - case N4: - c = getN4Solution(points); - break; - case COLLAPSING_BUBBLE: - c = getCollapsingBubbleSolution(points); - break; - default: - throw new RuntimeException("unhandled case " + solutionAlgorithm); - } - break; - } - center = c.center; - radius = c.radius; - } - - private Circle2D getCentroidSolution(List points) { - Circle2D c; - - MutableVector2D v = new MutableVector2D(); - for (Vector2D point : points) { - v.add(point); - } - v.scale(1.0 / points.size()); - - Vector2D center = new Vector2D(v); - double r = Double.NEGATIVE_INFINITY; - for (Vector2D point : points) { - r = FastMath.max(r, center.distanceTo(point)); - } - c = new Circle2D(center, r); - - return c; - } - - /* - * Uses a centroid calculation to find a first point of contact between the - * circle and the points. This circle is then collapsed by moving the center - * toward the first contact point until a second contact point is - * established. The center is then moved toward the midpoint between the - * first two contact points until either a third contact point is found or - * the center reaches the midpoint of the chord. - */ - private Circle2D getCollapsingBubbleSolution(List points) { - - // cen - the evolving best solution to the center - - // a - the first contact point - // b - the second contact point - // c - the third contact point - may not exist - - // Establish the first contact point - - Vector2D a = null; - - Vector2D cen = new Vector2D(); - for (Vector2D point : points) { - cen = cen.add(point); - } - cen = cen.scale(1.0 / points.size()); - - double r = Double.NEGATIVE_INFINITY; - - for (int i = 0; i < points.size(); i++) { - Vector2D point = points.get(i); - double distance = cen.distanceTo(point); - if (distance > r) { - a = point; - r = distance; - } - } - - // Establish the second contact point - Vector2D b = null; - double minJ = 1; - for (int i = 0; i < points.size(); i++) { - Vector2D v = points.get(i); - if (v != a) { - Vector2D m = a.add(v).scale(0.5); - Vector2D q = v.sub(a); - double j = m.sub(cen).dot(q) / a.sub(cen).dot(q); - if (j > 0 && j <= 1) { - if (j < minJ) { - b = v; - minJ = j; - } - } - } - } - cen = a.sub(cen).scale(minJ).add(cen); - r = cen.distanceTo(a); - - // Establish the third contact point - Vector2D m1 = a.add(b).scale(0.5); - minJ = 1; - for (int i = 0; i < points.size(); i++) { - Vector2D v = points.get(i); - Vector2D g = v.sub(a); - if (v != a && v != b) { - Vector2D m2 = a.add(v).scale(0.5); - double j = m2.sub(cen).dot(g) / m1.sub(cen).dot(g); - if (j >= 0 && j <= 1) { - if (j < minJ) { - minJ = j; - } - } - } - } - cen = m1.sub(cen).scale(minJ).add(cen); - - /* - * determine the radius from all the points to ensure every point will - * be inside the circle - */ - r = Double.NEGATIVE_INFINITY; - for (int i = 0; i < points.size(); i++) { - Vector2D point = points.get(i); - double distance = cen.distanceTo(point); - if (distance > r) { - a = point; - r = distance; - } - } - return new Circle2D(cen, r); - } - - private static class Interval { - private final double lowerBound; - private final double upperBound; - - public Interval(double lowerBound, double upperBound) { - this.lowerBound = lowerBound; - this.upperBound = upperBound; - } - - public Interval intersect(Interval interval) { - return new Interval(FastMath.max(lowerBound, interval.lowerBound), FastMath.min(upperBound, interval.upperBound)); - } - - public boolean isNaN() { - return Double.isNaN(lowerBound) || Double.isNaN(upperBound); - } - - public boolean isEmpty() { - return isNaN() || lowerBound > upperBound; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("Interval [lowerBound="); - builder.append(lowerBound); - builder.append(", upperBound="); - builder.append(upperBound); - builder.append("]"); - return builder.toString(); - } - } - - /* - * Returns the range of positions that could act as the center of a circle - * that would have a and b on its circumference and would contain c in its - * interior. - * - * The positions are returned as an interval [j,inf) or (-inf,j] where j is - * the signed distance from the midpoint of the chord formed by a and b and - * the right hand perpendicular bisector of that chord. - */ - private Interval getInterval(Vector2D a, Vector2D b, Vector2D c) { - Interval result; - Vector2D m1 = a.add(b).scale(0.5); - Vector2D p1 = b.sub(a).perpendicularRotation(Chirality.RIGHT_HANDED).normalize(); - Vector2D m2 = a.add(c).scale(0.5); - Vector2D f = a.sub(c); - double j = m2.sub(m1).dot(f) / p1.dot(f); - if (p1.dot(c.sub(m1)) > 0) { - result = new Interval(j, Double.POSITIVE_INFINITY); - } else { - result = new Interval(Double.NEGATIVE_INFINITY, j); - } - return result; - } - - /* - * Returns the smallest circle that has a and b on its circumference where - * the signed distance along the right hand perpendicular bisector of the - * chord formed by a and b is a value in the given interval. This will only - * be valid if the interval was formed from a and b in the getInterval() - * method. - * - */ - private Circle2D getCircle(Vector2D a, Vector2D b, Interval interval) { - Vector2D m = a.add(b).scale(0.5); - Vector2D p = b.sub(a).perpendicularRotation(Chirality.RIGHT_HANDED).normalize(); - double j = 0; - if (interval.lowerBound > 0) { - j = interval.lowerBound; - } else if (interval.upperBound < 0) { - j = interval.upperBound; - } - Vector2D cen = p.scale(j).add(m); - double r = FastMath.max(cen.distanceTo(a), cen.distanceTo(b)); - return new Circle2D(cen, r); - } - - /* - * For each pair of points, determine for each of the remaining points where - * center of the smallest circle might be. The result will be the smallest - * such circle. - */ - private Circle2D getN3Solution(List points) { - /* - * Create a default solution - */ - Circle2D result = new Circle2D(); - /* - * Loop through each pair of points. If the pair can be on a the - * circumference of a circle that contains all the points, the smallest - * such circle will be produced. - */ - for (int i = 0; i < points.size() - 1; i++) { - for (int j = i + 1; j < points.size(); j++) { - /* - * Assume that the full set of points that form the - * perpendicular bisector of the chord formed by points i and j - * will be valid centers a circle containing the remaining - * points. - * - */ - Interval intervalForIJ = new Interval(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY); - for (int k = 0; k < points.size(); k++) { - if (k != i && k != j) { - /* - * Determine the subset of the bisector that will allow - * the kth point to be in a circle that has point i and - * point j on its circumference - */ - Interval intervalForK = getInterval(points.get(i), points.get(j), points.get(k)); - - /* - * Intersect this set of the points with the set so far - * collected. If a NaN has occured in the interval - * solution, we should ignore that solution. This - * indicates that the kth point is VERY close to either - * the ith or jth point. - */ - if (!intervalForK.isNaN()) { - intervalForIJ = intervalForIJ.intersect(intervalForK); - } - /* - * If the interval for the i,j pair becomes empty, we - * need not examine the remaining kth points - */ - if (intervalForIJ.isEmpty()) { - break; - } - } - } - /* - * If the interval is not empty, then determine the center from - * the interval - */ - if (!intervalForIJ.isEmpty()) { - Circle2D circle = getCircle(points.get(i), points.get(j), intervalForIJ); - // if this is a better solution, then take it as the result. - if (circle.isFinite() && circle.radius < result.radius) { - result = circle; - } - } - } - } - - // Precision issues can sometimes cause the radius of the result to fall - // somewhat short, so we ensure the radius will include all of the - // points. - double r = Double.NEGATIVE_INFINITY; - for (Vector2D point : points) { - r = FastMath.max(r, result.center.distanceTo(point)); - } - - return new Circle2D(result.center, r); - } - - private Circle2D getN4Solution(List points) { - - /* - * Create a default solution that is not finite. - */ - Circle2D solution = new Circle2D(); - - /* - * Any circle that contains all the points but does not have any point - * on its circumference is not optimal. Similarly, if the circle contain - * a single point, then the circle could be made smaller. A circle - * having two of the points on its circumference may only be optimal if - * those two points form a bisecting chord of the circle. The center of - * such a circle will be at the midpoint of that chord. For a circle - * having three or more points on its circumference, we need only to use - * two distinct chords -- thus three distinct points -- to determine the - * center. - * - * - * We will examine each pair of points for a potential solution, - * checking that the remaining points fall inside the circle formed. If - * we fail to find a solution using a pairs, we must consider all - * triplets of points. - * - */ - - for (int i = 0; i < points.size() - 1; i++) { - for (int j = i + 1; j < points.size(); j++) { - /* - * Form a circle from the ith and jth points that will have its - * center at the midpoint between them - */ - Circle2D circle = new Circle2D(points.get(i), points.get(j)); - boolean allPointsContained = true; - for (Vector2D point : points) { - if (!circle.contains(point)) { - allPointsContained = false; - break; - } - } - if (allPointsContained && circle.radius < solution.radius) { - solution = circle; - } - } - } - - if (solution.isFinite()) { - /* - * The original default solution was infinite. If it is now finite - * then it had to have been replaced and we thus already have the - * optimal solution. - */ - return solution; - } - - for (int i = 0; i < points.size() - 2; i++) { - for (int j = i + 1; j < points.size() - 1; j++) { - for (int k = j + 1; k < points.size(); k++) { - /* - * We intersect the perpendicular bisectors of the chord ab - * and chord bc to find the center. - * - * We are solving the equation m1 + h1*p1 = m2+ h2*p2 where - * m1 and m2 are the midpoints of the chords. p1 and p2 are - * the perpendicular bisectors of the chords and h1 and h2 - * are the scalar value needed to form the intersection of - * the bisectors. - * - * We form d as the vector from a to b. It is perpendicular - * to the bisector of chord ab. - * - * Thus, (m1 + h1*p1) o d = (m2+ h2*p2) o d = center of - * circle - * - * Since p1 and d are perpendicular, we have - * - * m1 o d = (m2+ h2*p2) o d, so h2 = (m1-m2) o d / p2 o d - * - * This allows us to solve for the center. Note that we may - * drop the calculation of p1 and h1. - * - */ - - Vector2D a = points.get(i); - Vector2D b = points.get(j); - Vector2D c = points.get(k); - Vector2D m1 = a.add(b).scale(0.5); - Vector2D m2 = b.add(c).scale(0.5); - Vector2D d = b.sub(a); - Vector2D p = b.sub(c).perpendicularRotation(Chirality.RIGHT_HANDED); - double h = m1.sub(m2).dot(d) / p.dot(d); - Vector2D cen = p.scale(h).add(m2); - double radius = FastMath.max(cen.distanceTo(a), cen.distanceTo(b)); - radius = FastMath.max(radius, cen.distanceTo(c)); - Circle2D circle = new Circle2D(cen, radius); - - boolean allPointsContained = true; - for (Vector2D point : points) { - if (!circle.contains(point)) { - allPointsContained = false; - break; - } - } - if (allPointsContained && circle.radius < solution.radius) { - solution = circle; - } - } - } - } - - return solution; - } - -} diff --git a/gcm3/src/main/java/util/vector/MutableVector2D.java b/gcm3/src/main/java/util/vector/MutableVector2D.java deleted file mode 100644 index 200c4af46..000000000 --- a/gcm3/src/main/java/util/vector/MutableVector2D.java +++ /dev/null @@ -1,552 +0,0 @@ -package util.vector; - -import org.apache.commons.math3.util.FastMath; - -import util.spherical.Chirality; - -/** - * A mutable 2-dimensional vector class supporting common 2D transforms. - * - * @author Shawn Hatch - * - */ -public final class MutableVector2D { - - private static final long ZERO_LONG_BITS = Double.doubleToLongBits(0); - - public final static double NORMAL_LENGTH_TOLERANCE = 1E-13; - - /* - * corrects a value that should be in the interval [-1,1] - */ - private static double crunch(final double value) { - - if (value > 1) { - return 1; - } - if (value < -1) { - return -1; - } - return value; - } - - /* - * A function that masks Double.doubleToLongBits() by forcing the long bits - * representation of -0 to be that of +0. - */ - private static long toLongBits(final double value) { - if (value == 0) { - return ZERO_LONG_BITS; - } else { - return Double.doubleToLongBits(value); - } - } - - private double x = 0; - - private double y = 0; - - /** - * Constructs a {@link MutableVector2D} where x and y are zero - */ - public MutableVector2D() { - } - - /** - * Constructs a {@link MutableVector2D} with the given x and y values - */ - public MutableVector2D(final double x, final double y) { - this.x = x; - this.y = y; - } - - /** - * Constructs a {@link MutableVector2D} from another {@link MutableVector2D} - * - * @throws NullPointerException - *

          • if v is null - */ - public MutableVector2D(final MutableVector2D v) { - x = v.getX(); - y = v.getY(); - } - - /** - * Constructs a {@link MutableVector2D} from another {@link Vector2D} - * - * @throws NullPointerException - *
          • if v is null - */ - public MutableVector2D(final Vector2D v) { - x = v.getX(); - y = v.getY(); - } - - /** - * Adds the given x and y values to the corresponding values of this - * {@link MutableVector2D} - */ - public void add(final double x, final double y) { - this.x += x; - this.y += y; - } - - /** - * Adds the x and y of the given {@link MutableVector2D} to the - * corresponding values of this {@link MutableVector2D} - * - * @throws NullPointerException - *
          • if v is null - */ - public void add(final MutableVector2D v) { - x += v.getX(); - y += v.getY(); - } - - /** - * Adds the x and y of the given {@link MutableVector2D} to the - * corresponding values of this {@link Vector2D} - * - * @throws NullPointerException - *
          • if v is null - */ - public void add(final Vector2D v) { - x += v.getX(); - y += v.getY(); - } - - /** - * Add the given {@link MutableVector2D} as scaled by the scalar to this - * {@link MutableVector2D} - * - * For example, if v=(5,7) and w = (2,3), then v.addScaled(w,10) would yield - * v=(25,37) - * - */ - public void addScaled(final MutableVector2D v, final double scalar) { - x += v.getX() * scalar; - y += v.getY() * scalar; - } - - /** - * Add the given {@link Vector2D} as scaled by the scalar to this - * {@link MutableVector2D} - * - * For example, if v=(5,7) and w = (2,3), then v.addScaled(w,10) would yield - * v=(25,37) - * - */ - public void addScaled(final Vector2D v, final double scalar) { - x += v.getX() * scalar; - y += v.getY() * scalar; - } - - /** - * Returns the angle in radians between this {@link MutableVector2D} and the - * given {@link MutableVector2D} - */ - public double angle(final MutableVector2D v) { - final double value = dot(v) / (v.length() * length()); - return FastMath.acos(crunch(value)); - } - - /** - * Returns the angle in radians between this {@link MutableVector2D} and the - * given {@link Vector2D} - */ - public double angle(final Vector2D v) { - final double value = dot(v) / (v.length() * length()); - return FastMath.acos(crunch(value)); - } - - /** - * Assign the given values to this {@link MutableVector2D} - */ - public void assign(final double x, final double y) { - this.x = x; - this.y = y; - - } - - /** - * Assign the values of the given {@link MutableVector2D} to this - * {@link MutableVector2D} - */ - public void assign(final MutableVector2D v) { - x = v.getX(); - y = v.getY(); - } - - /** - * Assign the values of the given {@link Vector2D} to this - * {@link MutableVector2D} - */ - public void assign(final Vector2D v) { - x = v.getX(); - y = v.getY(); - } - - /** - * Returns 1 if the acute angle from this {@link MutableVector2D} to the - * given {@link MutableVector2D} is clockwise and -1 if it is counter - * clockwise. Returns 0 if the angle is zero. - */ - public int cross(final MutableVector2D v) { - final double direction = (x * v.y) - (v.x * y); - if (direction < 0) { - return -1; - } - if (direction > 0) { - return 1; - } - return 0; - } - - /** - * Returns 1 if the acute angle from this {@link MutableVector2D} to the - * given {@link Vector2D} is clockwise and -1 if it is counter clockwise. - * Returns 0 if the angle is zero. - */ - public int cross(final Vector2D v) { - final double direction = (x * v.getY()) - (v.getX() * y); - if (direction < 0) { - return -1; - } - if (direction > 0) { - return 1; - } - return 0; - } - - /** - * Returns the distance between this {@link MutableVector2D} and the given - * {@link MutableVector2D} - */ - public double distanceTo(final MutableVector2D v) { - return FastMath.sqrt(((x - v.x) * (x - v.x)) + ((y - v.y) * (y - v.y))); - } - - /** - * Returns the distance between this {@link MutableVector2D} and the given - * {@link Vector2D} - */ - public double distanceTo(final Vector2D v) { - return FastMath.sqrt(((x - v.getX()) * (x - v.getX())) + ((y - v.getY()) * (y - v.getY()))); - } - - /** - * Returns the dot product of this {@link MutableVector2D} and the given - * {@link MutableVector2D} - */ - public double dot(final MutableVector2D v) { - return (x * v.x) + (y * v.y); - } - - /** - * Returns the dot product of this {@link MutableVector2D} and the given - * {@link Vector2D} - */ - public double dot(final Vector2D v) { - return (x * v.getX()) + (y * v.getY()); - } - - /** - * Equals contract of {@link MutableVector2D}. Two vectors a and b are equal - * if their x and y values convert to long bits in the same way. An - * exception is made for -0, which is calculated as if its long bits were - * representing +0. This is done to give a more intuitive meaning of equals. - */ - @Override - public boolean equals(final Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final MutableVector2D other = (MutableVector2D) obj; - if (toLongBits(x) != toLongBits(other.x)) { - return false; - } - if (toLongBits(y) != toLongBits(other.y)) { - return false; - } - return true; - } - - /** - * Returns the value at the given index: 0->x, 1->y - */ - public double get(final int index) { - switch (index) { - case 0: - return x; - case 1: - return y; - default: - throw new RuntimeException("index out of bounds " + index); - } - } - - /** - * Returns the x component value of this {@link MutableVector2D} - */ - public double getX() { - return x; - } - - /** - * Returns the y component value of this {@link MutableVector2D} - */ - public double getY() { - return y; - } - - /** - * Returns a hash code consistent with the equals contract - */ - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - long temp; - temp = toLongBits(x); - result = (prime * result) + (int) (temp ^ (temp >>> 32)); - temp = toLongBits(y); - result = (prime * result) + (int) (temp ^ (temp >>> 32)); - return result; - } - - /** - * Returns true if any of the vector components are positive or - * negative infinity. - * - * @return true if the vector is infinite. - */ - public boolean isInfinite() { - return Double.isInfinite(x) || Double.isInfinite(y); - } - - /** - * Returns true if any of the vector components are NaN 'Not a - * Number'. - */ - public boolean isNaN() { - return Double.isNaN(x) || Double.isNaN(y); - } - - /** - * Returns true if and only if this {@link MutableVector2D} is finite and - * has a length within {@link MutableVector2D#NORMAL_LENGTH_TOLERANCE} of - * unit length. - */ - public boolean isNormal() { - final double len = length(); - return Double.isFinite(len) && (FastMath.abs(len - 1) < NORMAL_LENGTH_TOLERANCE); - } - - /** - * Returns true if any of the vector components are not NaN (i.e. - * 'Not a Number') and not infinite. - */ - public boolean isFinite() { - return !isNaN() && !isInfinite(); - } - - /** - * Returns the length of this {@link MutableVector2D} - */ - public double length() { - return FastMath.sqrt((x * x) + (y * y)); - } - - /** - * Scales this {@link MutableVector2D} so that its length is 1. - */ - public void normalize() { - final double len = length(); - x = x / len; - y = y / len; - } - - /** - * Rotates this {@link MutableVector2D} by 90 degrees under either a - * clockwise(left handed) or counter clockwise(right handed) rotation. - */ - public void perpendicularRotation(final Chirality chirality) { - double newx, newy; - if (chirality == Chirality.LEFT_HANDED) { - newx = y; - newy = -x; - } else { - newx = -y; - newy = x; - } - x = newx; - y = newy; - } - - /** - * Reverses the direction of the this Vector2D. This is equivalent to - * scaling by -1. - */ - public void reverse() { - x *= -1; - y *= -1; - } - - /** - * Rotates this {@link MutableVector2D} about the origin by the given angle - * in radians in a counter clockwise(right handed) manner. - */ - public void rotate(final double theta) { - final MutableVector2D v = new MutableVector2D(-y, x); - v.scale(FastMath.sin(theta)); - scale(FastMath.cos(theta)); - add(v); - } - - /** - * Rotates this {@link MutableVector2D} about the origin through the acute - * angle to the given {@link MutableVector2D} by the given angle in radians. - */ - public void rotateToward(final MutableVector2D v, final double theta) { - rotate(cross(v) * theta); - } - - /** - * Rotates this {@link MutableVector2D} about the origin through the acute - * angle to the given {@link Vector2D} by the given angle in radians. - */ - public void rotateToward(final Vector2D v, final double theta) { - rotate(cross(v) * theta); - } - - /** - * Scales this {@link MutableVector2D} by the scalar - */ - public void scale(final double scalar) { - x *= scalar; - y *= scalar; - } - - /** - * Sets the x component of this {@link MutableVector2D} - */ - public void setX(final double x) { - this.x = x; - } - - /** - * Sets the y component of this {@link MutableVector2D} - */ - public void setY(final double y) { - this.y = y; - } - - /** - * Returns the square distance between this {@link MutableVector2D} and the - * given {@link MutableVector2D} - */ - public double squareDistanceTo(final MutableVector2D v) { - return ((x - v.x) * (x - v.x)) + ((y - v.y) * (y - v.y)); - } - - /** - * Returns the square distance between this {@link MutableVector2D} and the - * given {@link Vector2D} - */ - public double squareDistanceTo(final Vector2D v) { - return ((x - v.getX()) * (x - v.getX())) + ((y - v.getY()) * (y - v.getY())); - } - - /** - * Returns the square length of this {@link MutableVector2D} - */ - public double squareLength() { - return (x * x) + (y * y); - } - - /** - * Subtracts the given x and y values from the corresponding values of this - * {@link MutableVector2D} - */ - public void sub(final double x, final double y) { - this.x -= x; - this.y -= y; - } - - /** - * Subtracts the x and y of the given {@link MutableVector2D} from the - * corresponding values of this {@link MutableVector2D} - * - * @throws NullPointerException - *
          • if v is null - */ - public void sub(final MutableVector2D v) { - x -= v.x; - y -= v.y; - } - - /** - * Subtracts the x and y of the given {@link MutableVector2D} from the - * corresponding values of this {@link Vector2D} - * - * @throws NullPointerException - *
          • if v is null - */ - public void sub(final Vector2D v) { - x -= v.getX(); - y -= v.getY(); - } - - /** - * Returns a length 2 array of double [x,y] - */ - public double[] toArray() { - return new double[] { x, y }; - } - - /** - * Returns the string representation in the form - * - * Vector2D [x=2.57,y=-34.1] - */ - @Override - public String toString() { - return "Vector2D [x=" + x + ", y=" + y + "]"; - } - - /** - * Sets each component of this {@link MutableVector2D} to zero. Note: - * This is the same as calling v.assign(0,0). - */ - public void zero() { - x = 0; - y = 0; - } - - public final static double PERPENDICUALR_ANGLE_TOLERANCE = 1E-13; - - /** - * Returns true if and only if this {@link MutableVector2D} is perpendicular - * to the given {@link MutableVector2D} within the - * {@link Vector3D#PERPENDICUALR_ANGLE_TOLERANCE} - */ - public boolean isPerpendicularTo(MutableVector2D v) { - return FastMath.abs(angle(v) - FastMath.PI / 2) < PERPENDICUALR_ANGLE_TOLERANCE; - } - - /** - * Returns true if and only if this {@link Vector2D} is perpendicular to the - * given {@link Vector2D} within the - * {@link Vector3D#PERPENDICUALR_ANGLE_TOLERANCE} - */ - public boolean isPerpendicularTo(Vector2D v) { - return FastMath.abs(angle(v) - FastMath.PI / 2) < PERPENDICUALR_ANGLE_TOLERANCE; - } - -} diff --git a/gcm3/src/main/java/util/vector/MutableVector3D.java b/gcm3/src/main/java/util/vector/MutableVector3D.java deleted file mode 100644 index ca2b36b7b..000000000 --- a/gcm3/src/main/java/util/vector/MutableVector3D.java +++ /dev/null @@ -1,1010 +0,0 @@ -package util.vector; - -import org.apache.commons.math3.util.FastMath; - -/** - * A mutable vector class representing 3-dimensional components: v = (x,y,z). - * - * (Description adapted from Wikipedia) This is a Euclidean vector (also called - * geometric or spatial vector) that has a length (or magnitude) and direction. - * This vector can be added to other vectors and obeys vector algebra logic. - * 3-dimensional Euclidean space can be represented as coordinate vectors in a - * Cartesian coordinate system. The endpoint of a vector can be identified with - * an ordered list of 3 real numbers (3-tuple). These numbers are the - * coordinates of the endpoint of the vector, with respect to a given Cartesian - * coordinate system, and are typically called the scalar components (or scalar - * projections) of the vector on the axes of the coordinate system. - *

            - * All calculations conform to a right handed system. Furthermore, all - * calculations are 'self-safe', meaning that any arguments passed in a - * computation will not be mutated (i.e. if provided a vector a copy of that - * vector may be made and mutated, but the vector itself will not be mutated). - *

            - * It is important to note, that since this is a fully mutative vector class - * that method chaining is discouraged to ensure proper order of operations - * occur in calculations. This unit will not account for rounding error logic. - * This unit is not null tolerant and passing null as a - * parameter for a vector will result in a {@link NullPointerException}. - * - * @see http://en.wikipedia.org/wiki/Euclidean_vector - * @see http://mathworld.wolfram.com/Vector.html - * - * @author Shawn R. Hatch - * @author Christopher R. Ludka - */ -public final class MutableVector3D { - - private static final long ZERO_LONG_BITS = Double.doubleToLongBits(0); - - public final static double NORMAL_LENGTH_TOLERANCE = 1E-13; - - private static double crunch(final double value) { - - if (value > 1) { - return 1; - } - if (value < -1) { - return -1; - } - return value; - } - - /* - * A function that masks Double.doubleToLongBits() by forcing the long bits - * representation of -0 to be that of +0. - */ - private static long toLongBits(final double value) { - if (value == 0) { - return ZERO_LONG_BITS; - } else { - return Double.doubleToLongBits(value); - } - } - - private double x; - - private double y; - - private double z; - - /** - * Creates a {@link MutableVector3D} with components of (0,0,0). - */ - public MutableVector3D() { - // Do nothing. Defaults to a (0,0,0) vector construction. - } - - /** - * Creates a {@link MutableVector3D} with the component values of (x,y,z). - *

            - * Examples: - *

              - *
            • Vector3D(-1,2,6) = (-1,2,6). - *
            • Vector3D(0,0,0) = (0,0,0). Note: This is equivalent - * to calling Vector3D(). - *
            - * - * @param x - * the x component. - * @param y - * the y component. - * @param z - * the y component. - */ - public MutableVector3D(final double x, final double y, final double z) { - assign(x, y, z); - } - - /** - * Creates a new {@link MutableVector3D} instance that is initialized with - * the coordinates values of the provided {@link MutableVector3D}. - *

            - * Examples: - *

              - *
            • v1 = (-1,2,6): Vector3D(v1) = (-1,2,6). - *
            • v1 = (0,0,0): Vector3D(v1) = (0,0,0). Note: - * This is equivalent to calling Vector3D(). - *
            - * - * @param v - * the initialization vector. - */ - public MutableVector3D(final MutableVector3D v) { - assign(v); - } - - /** - * Creates a new {@link MutableVector3D} instance that is initialized with - * the coordinates values of the provided {@link Vector3D}. - */ - public MutableVector3D(final Vector3D vector3d) { - x = vector3d.getX(); - y = vector3d.getY(); - z = vector3d.getZ(); - } - - /** - * Adds scalar components to this vector. - *

            - * Examples: v1 = (-1,2,6) - *

              - *
            • v1.add(1,-1,-1) = (-1+1,2-1,6-1) = (0,1,5). - *
            • v1.add(0,0,0) = (-1,2,6). - *
            - * - */ - public void add(final double x, final double y, final double z) { - this.x = this.x + x; - this.y = this.y + y; - this.z = this.z + z; - } - - /** - * Adds the given MutableVector3D components to this MutableVector3D. - *

            - * A null vector is treated as a (0,0,0) vector such that no - * addition occurs. - *

            - * Examples: v1 = (-1,2,6): - *

              - *
            • v2 = (1,-1,-1): - * v1.add(v2) = (-1+1,2-1,6-1) = (0,1,5). - *
            • v2 = (0,0,0): v1.add(v2) = (-1,2,6). - *
            - * - * @param v - * the vector to be added. - */ - public void add(final MutableVector3D v) { - x = x + v.x; - y = y + v.y; - z = z + v.z; - } - - /** - * Adds the given {@link Vector3D} components to this vector. - */ - public void add(final Vector3D v) { - x = x + v.getX(); - y = y + v.getY(); - z = z + v.getZ(); - } - - /** - * Scales and then adds the given MutableVector3D. - * - * Adding a vector scaled by 'zero' results in a (0,0,0) vector such that no - * effective scaling or addition occurs. - *

            - * Examples: v1 = (-1,2,6) - *

              - *
            • v2 = (1,-1,-1): - *
                - *
              • - * v1.addScaled(v2,5) = (-1+5*(1),2+5*(-1),6+5*(-1)) = (-1+5,2-5,6-5) = (4,-3,1). - *
              • - * v1.addScaled(v2,0) = (-1+0*(1),2+0*(-1),6+0*(-1)) = (-1+0,2+0,6+0) = (-1,2,6). - *
              - *
            • v2 = (0,0,0): v1.addScaled(v2,5) = (-1,2,6). - *
            - */ - public void addScaled(final MutableVector3D v, final double scalar) { - x = x + (scalar * v.x); - y = y + (scalar * v.y); - z = z + (scalar * v.z); - } - - /** - * Adds the given {@link Vector3D} scaled by the scalar to this - * {@link MutableVector3D} - * - * Adding a vector scaled by 'zero' results in a (0,0,0) vector such that no - * effective scaling or addition occurs. - *

            - * Examples: v1 = (-1,2,6) - *

              - *
            • v2 = (1,-1,-1): - *
                - *
              • - * v1.addScaled(v2,5) = (-1+5*(1),2+5*(-1),6+5*(-1)) = (-1+5,2-5,6-5) = (4,-3,1). - *
              • - * v1.addScaled(v2,0) = (-1+0*(1),2+0*(-1),6+0*(-1)) = (-1+0,2+0,6+0) = (-1,2,6). - *
              - *
            • v2 = (0,0,0): v1.addScaled(v2,5) = (-1,2,6). - *
            - */ - public void addScaled(final Vector3D v, final double scalar) { - x = x + (scalar * v.getX()); - y = y + (scalar * v.getY()); - z = z + (scalar * v.getZ()); - } - - /** - * Returns the angle in radians. - *

            - * Recall, the angle (in radians) between two vectors can be determined - * by:
            - * Alpha = ArcCos[Dot[v1, v2] / (Norm[v1]*Norm[v2])] - *

            - * If the provided vector is a 'zero vector' (i.e. (0,0,0)) the - * angle in radians will result in a NaN. If the provided vector is - * equal to this vector the angle in radians will be 0.0. - *

            - * Examples: v1 = (-1,2,6): - *

              - *
            • v2 = (1,-1,-1): - * v1.angleInRadians(v2) = 2.5175154010533864 - *
            • v2 = (0,0,0): v1.angleInRadians(v2) = NaN - *
            • v1.angleInRadians(v1) = 0.0 - *
            - * - * @see http://en.wikipedia.org/wiki/Dot_product#Geometric_definition - * @see http://mathworld.wolfram.com/DotProduct.html - */ - public double angle(final MutableVector3D v) { - return FastMath.acos(crunch(this.dot(v) / (length() * v.length()))); - } - - /** - * Returns the angle in radians from this {@link MutableVector3D} to the - * given {@link Vector3D} - */ - public double angle(final Vector3D v) { - return FastMath.acos(crunch(this.dot(v) / (length() * v.length()))); - } - - /** - * Assigns this {@link MutableVector3D} with the component values of - * (x,y,z). - *

            - * Examples: v1 = (-1,2,6): - *

              - *
            • v1.assign(1,-1,-1) = (1,-1,-1) - *
            • v1.assign(0,0,0) = (0,0,0) - *
            - * - * @param x - * the x component to be added. - * @param y - * the y component to be added. - * @param z - * the y component to be added. - */ - public void assign(final double x, final double y, final double z) { - this.x = x; - this.y = y; - this.z = z; - } - - /** - * Assigns this vector with the coordinates values of the provided vector. - *

            - * If provided a null vector, a vector with components (0,0,0) is - * assigned. - *

            - * Examples: v1 = (-1,2,6): - *

              - *
            • v2 = (1,-1,-1): v1.assign(v2) = (1,-1,-1). - *
            • v2 = (0,0,0): v1.assign(v2) = (0,0,0). - *
            - * - * @param v - * the vector values to be assigned. - */ - public void assign(final MutableVector3D v) { - x = v.x; - y = v.y; - z = v.z; - } - - /** - * Assigns this vector with the coordinates values of the provided vector. - * - */ - public void assign(final Vector3D v) { - x = v.getX(); - y = v.getY(); - z = v.getZ(); - } - - /** - * Assigns this vector as the right handed cross product (also called the - * 'vector product'). - *

            - * Recall, that given two vectors that the cross product is the vector that - * is perpendicular to them. The cross product vector is therefore normal to - * the plane formed by the given two vectors. This is very useful for - * calculating a surface normal at a particular point given two distinct - * tangent vectors. - *

            - * The calculation performed acts as a 'right cross product', meaning that - * 'this' vector is the 'left' vector and the given vector is the 'right' - * vector for purposes of calculating the cross product. - *

            - * The cross product, v1 X v2, formulation is therefore, - * - *

            -	 * v1.cross(v2)
            -	 *
            -	 * = Norm[v1]Norm[v2]Sin[Alpha]
            -	 *
            -	 *        { i ,  j ,  k }
            -	 * = Det[ {v1x, v1y, v1z} ]
            -	 *        {v2x, v2y, v2z}
            -	 *
            -	 *   {  0 , -v1z,  v1y}    {v2x}
            -	 * = { v1z,   0 , -v1x} ] [{v2y}]
            -	 *   {-v1y,  v1z,   0 }    {v2z)
            -	 *
            -	 *     {(-v1z)(v2y) + (v1y)(v2z)}
            -	 * = [ {(v1z)(v2x)  - (v1x)(v2z)}  ]
            -	 *     {(-v1y)(v2x) + (v1z)(v2y)}
            -	 * 
            - *

            - * If provided a null vector, a vector with components (0,0,0) is - * returned as the cross product. - *

            - * Note: To keep both vector values that form the cross product, - * create a cross product vector as:
            - * vCross = new Vector3D(v1);
            - * vCross.cross(v2); - *

            - * Examples: v1 = (-1,2,6): - *

              - *
            • v2 = (1,-1,-1): v1.cross(v2) = (4,5,-1). - *
            • v2 = (0,0,0): v1.cross(v2) = (0,0,0). - *
            • v1.cross(v1) = (0,0,0). - *
            - * - * @see http://en.wikipedia.org/wiki/Cross_product - * @see http://mathworld.wolfram.com/CrossProduct.html - * - * @param v - * the 'right' cross vector. - */ - public void cross(final MutableVector3D v) { - final double crossx = (y * v.z) - (v.y * z); - final double crossy = (v.x * z) - (x * v.z); - final double crossz = (x * v.y) - (v.x * y); - assign(crossx, crossy, crossz); - } - - /** - * Assigns this vector as the right handed cross product of this - * {@link MutableVector3D} and the given {@link Vector3D} - */ - public void cross(final Vector3D v) { - final double crossx = (y * v.getZ()) - (v.getY() * z); - final double crossy = (v.getX() * z) - (x * v.getZ()); - final double crossz = (x * v.getY()) - (v.getX() * y); - assign(crossx, crossy, crossz); - } - - /** - * Returns the distance between this vector and the given vector. - *

            - * This is Euclidean Distance (e.g. SQRT[(a-x)^2 + (b-y)^2 + (c-z)^2]) - * value. - *

            - * Examples: v1 = (-1,2,6): - *

              - *
            • v2 = (1,-1,-1): - * v1.distanceTo(v2) = 7.874007874011811 - *
            - * - * @see http://en.wikipedia.org/wiki/Euclidean_vector#Length - * - * @param v - * the vector to find the distance to. - * - * @return the distance between this vector and the given vector. - */ - public double distanceTo(final MutableVector3D v) { - return FastMath.sqrt(sqr(v.x - x) + sqr(v.y - y) + sqr(v.z - z)); - } - - /** - * Returns the distance between this vector and the given vector. - */ - public double distanceTo(final Vector3D v) { - return FastMath.sqrt(sqr(v.getX() - x) + sqr(v.getY() - y) + sqr(v.getZ() - z)); - } - - /** - * Returns the dot product of this vector and the given vector. - *

            - * Recall that the Dot Product is also called the "Scalar Product" or "Inner - * Product". - *

            - * The dot product, v1.v2, formulation is therefore, - * - *

            -	 * v1.dot(v2)
            -	 *
            -	 * = Norm[v1]Norm[v2]Sin[Alpha]
            -	 *
            -	 * = Transpose[v1]*v2
            -	 *
            -	 * = (v1.x * v2.x) + (v1.y * v2.y) + (v1.z * v2.z)
            -	 * 
            - * - * Examples: v1 = (-1,2,6): - *
              - *
            • v2 = (1,-1,-1): v1.dot(v2) = -9.0 - *
            • v2 = (0,0,0): v1.dot(v2) = 0.0 - *
            - * - * @see http://en.wikipedia.org/wiki/Dot_product - * @see http://mathworld.wolfram.com/DotProduct.html - * - * @param v - * the vector for the dot product. - * @return the dot product - */ - public double dot(final MutableVector3D v) { - return (x * v.x) + (y * v.y) + (z * v.z); - } - - /** - * Returns the dot product of this {@link MutableVector3D} and the given - * {@link Vector3D}. - */ - public double dot(final Vector3D v) { - return (x * v.getX()) + (y * v.getY()) + (z * v.getZ()); - } - - /** - * Equals contract of Vector3D. Two vectors a and b are equal if their x,y - * and z values convert to long bits in the same way. An exception is made - * for -0, which is calculated as if its long bits were representing +0. - * This is done to give a more intuitive meaning of equals. - */ - @Override - public boolean equals(final Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final MutableVector3D other = (MutableVector3D) obj; - if (toLongBits(x) != toLongBits(other.x)) { - return false; - } - if (toLongBits(y) != toLongBits(other.y)) { - return false; - } - if (toLongBits(z) != toLongBits(other.z)) { - return false; - } - return true; - } - - /** - * Returns the value at the given index: 0->x, 1->y, 2->z - */ - public double get(final int index) { - switch (index) { - case 0: - return x; - case 1: - return y; - case 2: - return z; - default: - throw new RuntimeException("index out of bounds " + index); - } - } - - /** - * Returns the x component of this vector - */ - public double getX() { - return x; - } - - /** - * Returns the y component of this vector - */ - public double getY() { - return y; - } - - /** - * Returns the z component of this vector - */ - public double getZ() { - return z; - } - - /** - * Default generated hash code. - */ - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - long temp; - temp = toLongBits(x); - result = (prime * result) + (int) (temp ^ (temp >>> 32)); - temp = toLongBits(y); - result = (prime * result) + (int) (temp ^ (temp >>> 32)); - temp = toLongBits(z); - result = (prime * result) + (int) (temp ^ (temp >>> 32)); - return result; - } - - /** - * Returns true if any of the vector components are not NaN (i.e. - * 'Not a Number') and is not infinite. - * - * @return true if the vector is not NaN (i.e. 'Not a Number') and - * is not infinite. - */ - public boolean isFinite() { - return !isNaN() && !isInfinite(); - } - - /** - * Returns true if any of the vector components are positive or - * negative infinity. - * - * @return true if the vector is infinite. - */ - public boolean isInfinite() { - return Double.isInfinite(x) || Double.isInfinite(y) || Double.isInfinite(z); - } - - /** - * Returns true if any of the vector components are NaN 'Not a - * Number'. - * - * @return true if the vector is NaN 'Not a Number'. - */ - public boolean isNaN() { - return Double.isNaN(x) || Double.isNaN(y) || Double.isNaN(z); - } - - /** - * Returns true if and only if this vector is finite and has a length within - * {@link MutableVector3D#NORMAL_LENGTH_TOLERANCE} of unit length. - */ - public boolean isNormal() { - final double len = length(); - return Double.isFinite(len) && (FastMath.abs(len - 1) < NORMAL_LENGTH_TOLERANCE); - } - - /** - * Returns the norm of the vector, i.e. its length. - *

            - * This is vector norm (e.g. SQRT[x^2 + y^2 + z^2]) value. - *

            - * Examples: - *

              - *
            • v1 = (-1,2,6): - * v1.norm() = SQRT[41] = 6.4031242374328485 - *
            • v1 = (0,0,0): v1.norm() = SQRT[0] = 0.0 - *
            - * - * @see http://en.wikipedia.org/wiki/Euclidean_vector#Length - * @see http://en.wikipedia.org/wiki/Norm_(mathematics)#Euclidean_norm - * - * @return the norm of the vector - */ - public double length() { - return FastMath.sqrt((x * x) + (y * y) + (z * z)); - } - - /** - * Normalizes the vector. - *

            - * Normalizing a vector scales the vector to a unit vector. If the vector - * has norm (i.g. length) of zero, NaN will result. - *

            - * Examples: - *

              - *
            • v1 = (-1,2,6): - * v1.normalize() = (-0.15617376188860607, 0.31234752377721214, 0.9370425713316364) - *
            • v1 = (0,0,0): v1.normalize() = (NaN, NaN, NaN) - *
            - * - * @see http://en.wikipedia.org/wiki/Normalized_vector - */ - public void normalize() { - scale(1.0 / length()); - } - - /** - * Reverses the vector. - *

            - * This is equivalent to scaling the vector by -1 (i.e. v.scale(-1) - * ). - *

            - * Examples: - *

              - *
            • v1 = (-1,2,6): v1.reverse() = (1,-2,-6) - *
            • v1 = (0,0,0): v1.reverse() = (0,0,0) - *
            - */ - public void reverse() { - scale(-1); - } - - /** - * Rotates this vector about the given vector at the given angle according - * to a right hand coordinate system (i.e. counter-clockwise). - *

            - * Examples: - *

              - *
            • v1 = (-1,2,6), v2 = (1,-1,-1), - * v1.rotate(v2, theta): - *
                - *
              • theta = 0.0 -> (-1.0, 2.0, 6.0) - *
              • theta = Math.PI/2.0 = 1.5707963267948966 -> - * (-5.309401076758505, 0.11324865405187179, 3.577350269189627) - *
              • theta = Math.PI = 3.141592653589793 -> (-5.000000000000002, - * 4.000000000000002, 1.7763568394002505E-15) - *
              • theta = (3.0/2.0)*Math.PI = 4.71238898038469 -> - * (-0.690598923241498, 5.88675134594813, 2.4226497308103747) - *
              • theta = 2.0*Math.PI = 6.283185307179586 -> - * (-0.9999999999999996, 2.000000000000001, 6.0) - *
              - *
            • v1 = (-1,2,6), v2 = (0,0,0), - * theta = Math.PI = 3.141592653589793, - * v1.rotate(v2, theta) -> (NaN, NaN, NaN) - *
            • v1 = (0,0,0), v2 = (1,-1,-1), - * theta = Math.PI = 3.141592653589793, - * v1.rotate(v2, theta) -> (0,0,0) - *
            - * - * @param v - * the vector to rotate about. - * @param theta - * the angle of rotation according to a right hand coordinate - * system (i.e. counter-clockwise). - */ - public void rotateAbout(final MutableVector3D v, final double theta) { - // Normalize the vector - final MutableVector3D normalizedRotator = new MutableVector3D(v); - normalizedRotator.normalize(); - - // parallelProjection is the part of this vector that lies in the - // direction of the rotator - final MutableVector3D parallelProjection = new MutableVector3D(normalizedRotator); - parallelProjection.scale(dot(normalizedRotator)); - - // x_perpendicularProjection is the part of this vector that is - // perpendicular to the rotator. It is the projection of self onto the - // plane perpendicular to the rotator. - final MutableVector3D x_perpendicularProjection = new MutableVector3D(this); - x_perpendicularProjection.sub(parallelProjection); - - // y_perpendicularProjection is the vector that is both in the plane - // perpendicular to the rotator AND perpendicular to the - // x_perpendicularProjection - - final MutableVector3D y_perpendicularProjection = new MutableVector3D(normalizedRotator); - y_perpendicularProjection.cross(x_perpendicularProjection); - - // Note: y_perpendicularProjection and x_perpendicularProjection - // have the same length. This has reduced the problem to a rotation in - // the plane. - - // We will now accumulate the various components after resetting this - // vector to zero. - zero(); - addScaled(x_perpendicularProjection, FastMath.cos(theta)); - addScaled(y_perpendicularProjection, FastMath.sin(theta)); - - // Now by adding back the parallelProjection, we obtain the desired - // result that is off the plane - add(parallelProjection); - } - - /** - * Rotates this vector about the given vector at the given angle according - * to a right hand coordinate system (i.e. counter-clockwise). - */ - public void rotateAbout(final Vector3D v, final double theta) { - // Normalize the vector - final MutableVector3D normalizedRotator = new MutableVector3D(v); - normalizedRotator.normalize(); - - // parallelProjection is the part of this vector that lies in the - // direction of the rotator - final MutableVector3D parallelProjection = new MutableVector3D(normalizedRotator); - parallelProjection.scale(dot(normalizedRotator)); - - // x_perpendicularProjection is the part of this vector that is - // perpendicular to the rotator. It is the projection of self onto the - // plane perpendicular to the rotator. - final MutableVector3D x_perpendicularProjection = new MutableVector3D(this); - x_perpendicularProjection.sub(parallelProjection); - - // y_perpendicularProjection is the vector that is both in the plane - // perpendicular to the rotator AND perpendicular to the - // x_perpendicularProjection - - final MutableVector3D y_perpendicularProjection = new MutableVector3D(normalizedRotator); - y_perpendicularProjection.cross(x_perpendicularProjection); - - // Note: y_perpendicularProjection and x_perpendicularProjection - // have the same length. This has reduced the problem to a rotation in - // the plane. - - // We will now accumulate the various components after resetting this - // vector to zero. - zero(); - addScaled(x_perpendicularProjection, FastMath.cos(theta)); - addScaled(y_perpendicularProjection, FastMath.sin(theta)); - - // Now by adding back the parallelProjection, we obtain the desired - // result that is off the plane - add(parallelProjection); - } - - /** - * Rotates this vector toward the given vector at the given angle according - * to a right hand coordinate system (i.e. counter-clockwise). - *

            - * Examples: - *

              - *
            • v1 = (-1,2,6), v2 = (1,-1,-1): - * v1.rotateToward(v2, theta) produces: - *
                - *
              • theta = 0.0 -> (-0.9999999999999999, 2.0, 6.0) - *
              • theta = Math.PI/2.0 = 1.5707963267948966 -> - * (4.937707198786941, -3.548977049128114, 2.005943549507195) - *
              • theta = Math.PI = 3.141592653589793 -> (1.0000000000000007, - * -2.0000000000000004, -6.0) - *
              • theta = (3.0/2.0)*Math.PI = 4.71238898038469 -> - * (-4.937707198786941, 3.5489770491281134, -2.0059435495071956) - *
              • theta = 2.0*Math.PI = 6.283185307179586 -> - * (-1.000000000000001, 2.000000000000001, 5.999999999999999) - *
              - *
            • v1 = (-1,2,6), v2 = (0,0,0), - * theta = Math.PI = 3.141592653589793, - * v1.rotate(v2, theta) -> (NaN, NaN, NaN) - *
            • v1 = (0,0,0), v2 = (1,-1,-1), - * theta = Math.PI = 3.141592653589793, - * v1.rotate(v2, theta) -> (NaN, NaN, NaN) - *
            - * - * - * @param v - * the vector to rotate toward. - * @param theta - * the angle of rotation according to a right hand coordinate - * system (i.e. counter-clockwise). - */ - public void rotateToward(final MutableVector3D v, final double theta) { - final MutableVector3D perp = new MutableVector3D(this); - perp.cross(v); - rotateAbout(perp, theta); - } - - /** - * Rotates this vector toward the given vector at the given angle according - * to a right hand coordinate system (i.e. counter-clockwise). - */ - public void rotateToward(final Vector3D v, final double theta) { - final MutableVector3D perp = new MutableVector3D(this); - perp.cross(v); - rotateAbout(perp, theta); - } - - /** - * Scales the vector by the given magnitude, e.g. scaling factor. - *

            - * Examples: v1 = (-1,2,6): - *

              - *
            • v1.scale(2) = (2,4,-12) - *
            • v1.scale(-1) = (1,-2,-6), Note: This is the same as - * v1.reverse() - *
            • v1.scale(1) = (-1,2,6) - *
            • v1.scale(0) = (0,0,0) - *
            - * - * @param m - * the magnitude, e.g. scaling factor. - */ - public void scale(final double m) { - x *= m; - y *= m; - z *= m; - } - - /** - * Sets the x component of this vector - */ - public void setX(final double x) { - this.x = x; - } - - /** - * Sets the y component of this vector - */ - public void setY(final double y) { - this.y = y; - } - - /** - * Sets the z component of this vector - */ - public void setZ(final double z) { - this.z = z; - } - - /** - * Squares the provided value. - * - * @param value - * to be squared - * @return the value squared - */ - private double sqr(final double value) { - return value * value; - } - - /** - * Returns the square of the distance to the vector provided. - *

            - * This is Euclidean Distance squared (e.g. (a-x)^2 + (b-y)^2 + (c-z)^2) - * value. - *

            - * Examples: v1 = (-1,2,6): - *

              - *
            • v2 = (1,-1,-1): v1.squareDistanceTo(v2) = 62.0 - *
            - * - * @param v - * the vector to find the distance to. - * @return the square of the distance to the vector provided. - */ - public double squareDistanceTo(final MutableVector3D v) { - return ((v.x - x) * (v.x - x)) + ((v.y - y) * (v.y - y)) + ((v.z - z) * (v.z - z)); - } - - /** - * Returns the square of the distance to the vector provided. - */ - public double squareDistanceTo(final Vector3D v) { - return ((v.getX() - x) * (v.getX() - x)) + ((v.getY() - y) * (v.getY() - y)) + ((v.getZ() - z) * (v.getZ() - z)); - } - - /** - * Returns the length of the vector squared. - *

            - * This is vector norm squared (e.g. x^2 + y^2 + z^2). - *

            - * Examples: - *

              - *
            • v1 = (-1,2,6): v1.normSquared() = 41 - *
            • v1 = (0,0,0): v1.normSquared() = 0 - *
            - * - * @see http://en.wikipedia.org/wiki/Euclidean_vector#Length - * @see http://en.wikipedia.org/wiki/Norm_(mathematics)#Euclidean_norm - * - * @return the norm of the vector - */ - public double squareLength() { - return (x * x) + (y * y) + (z * z); - } - - /** - * Subtracts scalar components from this vector. - *

            - * Examples: Given that v1 = (-1,2,6) - *

              - *
            • v1.sub(1,-1,-1) = (-1-1,2-(-1),6-(-1)) = (-2,3,7) - *
            • v1.sub(0,0,0) = (-1,2,6) - *
            - * - * @param x - * the x component. - * @param y - * the y component. - * @param z - * the y component. - */ - public void sub(final double x, final double y, final double z) { - this.x = this.x - x; - this.y = this.y - y; - this.z = this.z - z; - } - - /** - * Subtracts the given vector's components from this vector. - *

            - * A null vector is treated as a (0,0,0) vector such that no - * addition occurs. - *

            - * Examples: v1 = (-1,2,6) - *

              - *
            • v2 = (1,-1,-1): - * v1.sub(v2) = (-1-1,2-(-1),6-(-1)) = (-2,3,7) - *
            • v2 = (0,0,0): v1.sub(v2) = (-1,2,6) - *
            - * - * @param v - * the vector to be added. - */ - public void sub(final MutableVector3D v) { - x = x - v.x; - y = y - v.y; - z = z - v.z; - } - - /** - * Subtracts the given vector's components from this vector. - * - */ - public void sub(final Vector3D v) { - x = x - v.getX(); - y = y - v.getY(); - z = z - v.getZ(); - } - - /** - * Returns a length 3 array of double [x,y,z] - */ - public double[] toArray() { - return new double[] { x, y, z }; - } - - /** - * Returns the string representation in the form - * - * Vector3D [x=2.57,y=-34.1,z=8.73] - */ - @Override - public String toString() { - return "Vector3D [x=" + x + ", y=" + y + ", z=" + z + "]"; - } - - /** - * Sets each component of this {@link MutableVector3D} to zero. Note: - * This is the same as calling v.assign(0,0,0). - */ - public void zero() { - assign(0, 0, 0); - } - - public final static double PERPENDICUALR_ANGLE_TOLERANCE = 1E-13; - - /** - * Returns true if and only if this {@link MutableVector3D} is perpendicular - * to the given {@link MutableVector3D} within the - * {@link Vector3D#PERPENDICUALR_ANGLE_TOLERANCE} - */ - public boolean isPerpendicularTo(MutableVector3D v) { - return FastMath.abs(angle(v) - FastMath.PI / 2) < PERPENDICUALR_ANGLE_TOLERANCE; - } - - /** - * Returns true if and only if this {@link Vector3D} is perpendicular to the - * given {@link Vector3D} within the - * {@link Vector3D#PERPENDICUALR_ANGLE_TOLERANCE} - */ - public boolean isPerpendicularTo(Vector3D v) { - return FastMath.abs(angle(v) - FastMath.PI / 2) < PERPENDICUALR_ANGLE_TOLERANCE; - } -} \ No newline at end of file diff --git a/gcm3/src/main/java/util/vector/NonNormalVectorException.java b/gcm3/src/main/java/util/vector/NonNormalVectorException.java deleted file mode 100644 index 3778251dc..000000000 --- a/gcm3/src/main/java/util/vector/NonNormalVectorException.java +++ /dev/null @@ -1,18 +0,0 @@ -package util.vector; - -/** - * A RuntimeException thrown when a vector is not normal(length = 1); - */ -public class NonNormalVectorException extends RuntimeException { - - private static final long serialVersionUID = 1L; - - public NonNormalVectorException() { - super(); - } - - public NonNormalVectorException(final String message) { - super(message); - } - -} diff --git a/gcm3/src/main/java/util/vector/Vector2D.java b/gcm3/src/main/java/util/vector/Vector2D.java deleted file mode 100644 index 9570d1149..000000000 --- a/gcm3/src/main/java/util/vector/Vector2D.java +++ /dev/null @@ -1,419 +0,0 @@ -package util.vector; - -import org.apache.commons.math3.util.FastMath; - -import net.jcip.annotations.Immutable; -import util.spherical.Chirality; - -/** - * A immutable 2-dimensional vector class supporting common 2D transforms. - * - * @author Shawn Hatch - * - */ -@Immutable -public final class Vector2D { - - private static final long ZERO_LONG_BITS = Double.doubleToLongBits(0); - - public final static double NORMAL_LENGTH_TOLERANCE = 1E-13; - - /* - * corrects a value that should be in the interval [-1,1] - */ - private static double crunch(final double value) { - - if (value > 1) { - return 1; - } - if (value < -1) { - return -1; - } - return value; - } - - /* - * A function that masks Double.doubleToLongBits() by forcing the long bits - * representation of -0 to be that of +0. - */ - private static long toLongBits(final double value) { - if (value == 0) { - return ZERO_LONG_BITS; - } else { - return Double.doubleToLongBits(value); - } - } - - private final double x; - - private final double y; - - /** - * Constructs a {@link Vector2D} where x and y are zero - */ - public Vector2D() { - x = 0; - y = 0; - } - - /** - * Constructs a {@link Vector2D} with the given x and y values - */ - public Vector2D(final double x, final double y) { - this.x = x; - this.y = y; - } - - /** - * Constructs a {@link Vector2D} from another {@link MutableVector2D} - * - * @throws NullPointerException - *
          • if v is null - */ - public Vector2D(final MutableVector2D v) { - x = v.getX(); - y = v.getY(); - } - - /** - * Constructs a {@link Vector2D} from another {@link Vector2D} - * - * @throws NullPointerException - *
          • if v is null - */ - public Vector2D(final Vector2D v) { - x = v.x; - y = v.y; - } - - /** - * Returns a new {@link Vector2D} instance that is the component-wise sum of - * this {@link Vector2D} and the given values. {@link Vector2D} - */ - public Vector2D add(final double x, final double y) { - return new Vector2D(this.x + x, this.y + y); - } - - /** - * Returns a new {@link Vector2D} instance that is the component-wise sum of - * this {@link Vector2D} and the given {@link Vector2D}. - * - * @throws NullPointerException - *
          • if v is null - */ - public Vector2D add(final Vector2D v) { - return new Vector2D(x + v.x, y + v.y); - } - - /** - * Returns a new {@link Vector2D} instance that is the component-wise sum of - * this {@link Vector2D} and the scalar multiple of the given - * {@link Vector2D}. - * - * @throws NullPointerException - *
          • if v is null - */ - public Vector2D addScaled(final Vector2D v, final double scalar) { - return new Vector2D(x + (v.x * scalar), y + (v.y * scalar)); - } - - /** - * Returns the angle in radians between this {@link Vector2D} and the given - * {@link Vector2D} - * - * @throws NullPointerException - *
          • if v is null - */ - public double angle(final Vector2D v) { - final double value = dot(v) / (v.length() * length()); - return FastMath.acos(crunch(value)); - } - - /** - * Returns 1 if the acute angle from this {@link Vector2D} to the given - * Vector2D is clockwise and -1 if it is counter clockwise. Returns 0 if the - * angle is zero. - * - * @throws NullPointerException - *
          • if v is null - */ - public int cross(final Vector2D v) { - final double direction = (x * v.y) - (v.x * y); - if (direction < 0) { - return -1; - } - if (direction > 0) { - return 1; - } - return 0; - } - - /** - * Returns the distance between this {@link Vector2D} and the given Vector2D - * - * @throws NullPointerException - *
          • if v is null - */ - public double distanceTo(final Vector2D v) { - return FastMath.sqrt(((x - v.x) * (x - v.x)) + ((y - v.y) * (y - v.y))); - } - - /** - * Returns the dot product of this {@link Vector2D} and the given Vector2D - * - * @throws NullPointerException - *
          • if v is null - */ - public double dot(final Vector2D v) { - return (x * v.x) + (y * v.y); - } - - /** - * Equals contract of {@link Vector2D}. Two vectors a and b are equal if - * their x and y values convert to long bits in the same way. An exception - * is made for -0, which is calculated as if its long bits were representing - * +0. This is done to give a more intuitive meaning of equals. - */ - @Override - public boolean equals(final Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final Vector2D other = (Vector2D) obj; - if (toLongBits(x) != toLongBits(other.x)) { - return false; - } - if (toLongBits(y) != toLongBits(other.y)) { - return false; - } - return true; - } - - /** - * Returns the value at the given index: 0->x, 1->y - */ - public double get(final int index) { - switch (index) { - case 0: - return x; - case 1: - return y; - default: - throw new RuntimeException("index out of bounds " + index); - } - } - - /** - * Returns the x component value of this {@link Vector2D} - */ - public double getX() { - return x; - } - - /** - * Returns the y component value of this {@link Vector2D} - */ - public double getY() { - return y; - } - - /** - * Returns a hash code consistent with the equals contract - */ - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - long temp; - temp = toLongBits(x); - result = (prime * result) + (int) (temp ^ (temp >>> 32)); - temp = toLongBits(y); - result = (prime * result) + (int) (temp ^ (temp >>> 32)); - return result; - } - - /** - * Returns true if any of the vector components are positive or - * negative infinity. - * - * @return true if the vector is infinite. - */ - public boolean isInfinite() { - return Double.isInfinite(x) || Double.isInfinite(y); - } - - /** - * Returns true if any of the vector components are NaN 'Not a - * Number'. - */ - public boolean isNaN() { - return Double.isNaN(x) || Double.isNaN(y); - } - - /** - * Returns true if and only if this {@link Vector2D} is finite and has a - * length within {@link Vector2D#NORMAL_LENGTH_TOLERANCE} of unit length. - */ - public boolean isNormal() { - final double len = length(); - return Double.isFinite(len) && (FastMath.abs(len - 1) < NORMAL_LENGTH_TOLERANCE); - } - - /** - * Returns true if any of the vector components are not NaN (i.e. - * 'Not a Number') and not infinite. - */ - public boolean isFinite() { - return !isNaN() && !isInfinite(); - } - - /** - * Returns the length of this {@link Vector2D} - */ - public double length() { - return FastMath.sqrt((x * x) + (y * y)); - } - - /** - * Returns a new {@link Vector2D} instance that if equal to this - * {@link Vector2D} scaled to unit length. - */ - public Vector2D normalize() { - return scale(1.0 / length()); - } - - /** - * Returns a new {@link Vector2D} instance that has the same length as this - * {link Vector2D} and is perpendicular the this {link Vector2D} under - * either a clockwise or counter clockwise rotation. - * - * @throws NullPointerException - *
          • if v is null - */ - public Vector2D perpendicularRotation(final Chirality chirality) { - double newx, newy; - if (chirality == Chirality.LEFT_HANDED) { - newx = y; - newy = -x; - } else { - newx = -y; - newy = x; - } - return new Vector2D(newx, newy); - } - - /** - * Reverses the direction of the this Vector2D. This is equivalent to - * scaling by -1. - */ - public Vector2D reverse() { - return scale(-1); - } - - /** - * Returns a new {@link Vector2D} instance that has the same length as this - * {link Vector2D} and is clockwise rotated by the given angle in radians. - * - * @throws NullPointerException - *
          • if v is null - */ - public Vector2D rotate(final double theta) { - final double sinTheta = FastMath.sin(theta); - final double cosTheta = FastMath.cos(theta); - return new Vector2D((x * cosTheta) - (y * sinTheta), (y * cosTheta) + (x * sinTheta)); - } - - /** - * Returns a new {@link Vector2D} instance that has the same length as this - * {link Vector2D} and is rotated toward the given {@link Vector2D} by the - * given angle in radians. - * - * @throws NullPointerException - *
          • if v is null - */ - public Vector2D rotateToward(final Vector2D v, final double theta) { - return rotate(cross(v) * theta); - } - - /** - * Returns a new {@link Vector2D} instance that is the scaled values of this - * {@link Vector2D}. - * - */ - public Vector2D scale(final double scalar) { - return new Vector2D(x * scalar, y * scalar); - } - - /** - * Returns the square distance between this {@link Vector2D} and the given - * Vector2D - * - * @throws NullPointerException - *
          • if v is null - */ - public double squareDistanceTo(final Vector2D v) { - return ((x - v.x) * (x - v.x)) + ((y - v.y) * (y - v.y)); - } - - /** - * Returns the square length of this {@link Vector2D} - */ - public double squareLength() { - return (x * x) + (y * y); - } - - /** - * Returns a new {@link Vector2D} instance that is the component-wise - * difference of this {@link Vector2D} and the given values. - */ - public Vector2D sub(final double x, final double y) { - return new Vector2D(this.x - x, this.y - y); - } - - /** - * Returns a new {@link Vector2D} instance that is the component-wise - * difference of this {@link Vector2D} and the given {@link Vector2D}. - * - * @throws NullPointerException - *
          • if v is null - * - */ - public Vector2D sub(final Vector2D v) { - return new Vector2D(x - v.x, y - v.y); - } - - /** - * Returns a length 2 array of double [x,y] - */ - public double[] toArray() { - return new double[] { x, y }; - } - - /** - * Returns the string representation in the form - * - * Vector2D [x=2.57,y=-34.1] - */ - @Override - public String toString() { - return "Vector2D [x=" + x + ", y=" + y + "]"; - } - - public final static double PERPENDICUALR_ANGLE_TOLERANCE = 1E-13; - - /** - * Returns true if and only if this {@link Vector2D} is perpendicular to the - * given {@link Vector2D} within the - * {@link Vector3D#PERPENDICUALR_ANGLE_TOLERANCE} - */ - public boolean isPerpendicularTo(Vector2D v) { - return FastMath.abs(angle(v) - FastMath.PI / 2) < PERPENDICUALR_ANGLE_TOLERANCE; - } - -} diff --git a/gcm3/src/main/java/util/vector/Vector3D.java b/gcm3/src/main/java/util/vector/Vector3D.java deleted file mode 100644 index f8fb75184..000000000 --- a/gcm3/src/main/java/util/vector/Vector3D.java +++ /dev/null @@ -1,687 +0,0 @@ -package util.vector; - -import org.apache.commons.math3.util.FastMath; - -import net.jcip.annotations.Immutable; - -/** - * A immutable vector class representing 3-dimensional components: v = (x,y,z). - * - * @author Shawn R. Hatch - */ -@Immutable -public final class Vector3D { - - private static final long ZERO_LONG_BITS = Double.doubleToLongBits(0); - - public final static double NORMAL_LENGTH_TOLERANCE = 1E-13; - - public final static double PERPENDICUALR_ANGLE_TOLERANCE = 1E-13; - - /* - * A function that returns the value if the value is in the interval [-1,1]. - * Returns the nearest value from the interval otherwise. - */ - private static double crunch(final double value) { - - if (value > 1) { - return 1; - } - if (value < -1) { - return -1; - } - return value; - } - - /* - * A function that masks Double.doubleToLongBits() by forcing the long bits - * representation of -0 to be that of +0. - */ - private static long toLongBits(final double value) { - if (value == 0) { - return ZERO_LONG_BITS; - } else { - return Double.doubleToLongBits(value); - } - } - - private final double x; - - private final double y; - - private final double z; - - /** - * Creates a {@link Vector3D} with component values (0,0,0). - */ - public Vector3D() { - x = 0; - y = 0; - z = 0; - } - - /** - * Creates a {@link Vector3D} with the component values (x,y,z). - * - */ - public Vector3D(final double x, final double y, final double z) { - this.x = x; - this.y = y; - this.z = z; - } - - /** - * Creates a {@link Vector3D} instance that is initialized with the - * coordinates values of the provided {@link MutableVector3D}. - */ - - public Vector3D(final MutableVector3D mutableVector3D) { - x = mutableVector3D.getX(); - y = mutableVector3D.getY(); - z = mutableVector3D.getZ(); - } - - /** - * Creates a new {@link Vector3D} instance that is initialized with the - * coordinates values of the provided {@link Vector3D}. - */ - public Vector3D(final Vector3D v) { - x = v.x; - y = v.y; - z = v.z; - } - - /** - * Returns a new {@link Vector3D} instance, adding the given values. - * - *

            - * Examples: v1 = (-1,2,6) - *

              - *
            • v1.add(1,-1,-1) = (-1+1,2-1,6-1) = (0,1,5). - *
            • v1.add(0,0,0) = (-1,2,6). - *
            - */ - public Vector3D add(final double x, final double y, final double z) { - return new Vector3D(this.x + x, this.y + y, this.z + z); - } - - /** - * Returns a new {@link Vector3D} instance, adding the given - * {@link Vector3D}. - * - *

            - * Examples: v1 = (-1,2,6): - *

              - *
            • v2 = (1,-1,-1): - * v1.add(v2) = (-1+1,2-1,6-1) = (0,1,5). - *
            • v2 = (0,0,0): v1.add(v2) = (-1,2,6). - *
            - * - * @param v - * the vector to be added. - */ - public Vector3D add(final Vector3D v) { - return new Vector3D(x + v.x, y + v.y, z + v.z); - } - - /** - * Returns a new {@link Vector3D} instance that is the sum of this - * {@link Vector2D} instance and the scaled values of the given - * {@link Vector3D}. - * - *

            - * Examples: v1 = (-1,2,6) - *

              - *
            • v2 = (1,-1,-1): - *
                - *
              • - * v1.addScaled(v2,5) = (-1+5*(1),2+5*(-1),6+5*(-1)) = (-1+5,2-5,6-5) = (4,-3,1). - *
              • - * v1.addScaled(v2,0) = (-1+0*(1),2+0*(-1),6+0*(-1)) = (-1+0,2+0,6+0) = (-1,2,6). - *
              - *
            • v2 = (0,0,0): v1.addScaled(v2,5) = (-1,2,6). - *
            - * - * @param v - * the vector to be added. - * @param s - * the magnitude of the scale. - */ - public Vector3D addScaled(final Vector3D v, final double scalar) { - return new Vector3D(x + (scalar * v.x), y + (scalar * v.y), z + (scalar * v.z)); - } - - /** - * Returns the measure in radians of the lesser angle between this - * {@link Vector3D} and the given {@link Vector3D}. - *

            - * Recall, the angle (in radians) between two vectors can be determined - * by:
            - * Alpha = ArcCos[Dot[v1, v2] / (Norm[v1]*Norm[v2])] - *

            - * If the provided vector is a 'zero vector' (i.e. (0,0,0)) the - * angle in radians will result in a NaN. If the provided vector is - * equal to this vector the angle in radians will be 0.0. - *

            - * Examples: v1 = (-1,2,6): - *

              - *
            • v2 = (1,-1,-1): - * v1.angleInRadians(v2) = 2.5175154010533864 - *
            • v2 = (0,0,0): v1.angleInRadians(v2) = NaN - *
            • v1.angleInRadians(v1) = 0.0 - *
            - * - * @see http://en.wikipedia.org/wiki/Dot_product#Geometric_definition - * @see http://mathworld.wolfram.com/DotProduct.html - * - */ - public double angle(final Vector3D v) { - return FastMath.acos(crunch(dot(v) / (length() * v.length()))); - } - - /** - * Returns a new {@link Vector3D} that is the right handed cross product of - * this {@link Vector3D} and the given {@link Vector3D}. - *

            - * Recall, that given two vectors that the cross product is the vector that - * is perpendicular to them. The cross product vector is therefore normal to - * the plane formed by the given two vectors. This is very useful for - * calculating a surface normal at a particular point given two distinct - * tangent vectors. - *

            - * The calculation performed acts as a 'right cross product', meaning that - * 'this' vector is the 'left' vector and the given vector is the 'right' - * vector for purposes of calculating the cross product. - *

            - * The cross product, v1 X v2, formulation is therefore, - * - *

            -	 * v1.cross(v2)
            -	 *
            -	 * = Norm[v1]Norm[v2]Sin[Alpha]
            -	 *
            -	 *        { i ,  j ,  k }
            -	 * = Det[ {v1x, v1y, v1z} ]
            -	 *        {v2x, v2y, v2z}
            -	 *
            -	 *   {  0 , -v1z,  v1y}    {v2x}
            -	 * = { v1z,   0 , -v1x} ] [{v2y}]
            -	 *   {-v1y,  v1z,   0 }    {v2z)
            -	 *
            -	 *     {(-v1z)(v2y) + (v1y)(v2z)}
            -	 * = [ {(v1z)(v2x)  - (v1x)(v2z)}  ]
            -	 *     {(-v1y)(v2x) + (v1z)(v2y)}
            -	 * 
            - * - *

            - * Examples: v1 = (-1,2,6): - *

              - *
            • v2 = (1,-1,-1): v1.cross(v2) = (4,5,-1). - *
            • v2 = (0,0,0): v1.cross(v2) = (0,0,0). - *
            • v1.cross(v1) = (0,0,0). - *
            - * - * @see http://en.wikipedia.org/wiki/Cross_product - * @see http://mathworld.wolfram.com/CrossProduct.html - * - * @param v - * the 'right' cross vector. - */ - public Vector3D cross(final Vector3D v) { - final double crossx = (y * v.z) - (v.y * z); - final double crossy = (v.x * z) - (x * v.z); - final double crossz = (x * v.y) - (v.x * y); - return new Vector3D(crossx, crossy, crossz); - } - - /** - * Returns the distance between this {@link Vector3D} and the given - * {@link Vector3D}. - *

            - * This is Euclidean Distance (e.g. SQRT[(a-x)^2 + (b-y)^2 + (c-z)^2]) - * value. - *

            - * Examples: v1 = (-1,2,6): - *

              - *
            • v2 = (1,-1,-1): - * v1.distanceTo(v2) = 7.874007874011811 - *
            - * - * @see http://en.wikipedia.org/wiki/Euclidean_vector#Length - * - * @param v - * the vector to find the distance to. - * - * @return the distance between this vector and the given vector. - */ - public double distanceTo(final Vector3D v) { - return FastMath.sqrt(sqr(v.x - x) + sqr(v.y - y) + sqr(v.z - z)); - } - - /** - * Returns the dot product of this {@link Vector3D} and the given - * {@link Vector3D}. - *

            - * Recall that the Dot Product is also called the "Scalar Product" or "Inner - * Product". - *

            - * The dot product, v1.v2, formulation is therefore, - * - *

            -	 * v1.dot(v2)
            -	 *
            -	 * = Norm[v1]Norm[v2]Sin[Alpha]
            -	 *
            -	 * = Transpose[v1]*v2
            -	 *
            -	 * = (v1.x * v2.x) + (v1.y * v2.y) + (v1.z * v2.z)
            -	 * 
            - * - * Examples: v1 = (-1,2,6): - *
              - *
            • v2 = (1,-1,-1): - * v1.dot(v2) =(-1)*1+2*(-1)+6*(-1)= -9.0 - *
            • v2 = (0,0,0): v1.dot(v2) = 0.0 - *
            - * - * @see http://en.wikipedia.org/wiki/Dot_product - * @see http://mathworld.wolfram.com/DotProduct.html - * - * @param v - * the vector for the dot product. - * @return the dot product - */ - public double dot(final Vector3D v) { - return (x * v.x) + (y * v.y) + (z * v.z); - } - - /** - * Equals contract of {@link Vector3D}. Two vectors a and b are equal if - * their x,y and z values convert to long bits in the same way. An exception - * is made for -0, which is calculated as if its long bits were representing - * +0. This is done to give a more intuitive meaning of equals. - */ - @Override - public boolean equals(final Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final Vector3D other = (Vector3D) obj; - if (toLongBits(x) != toLongBits(other.x)) { - return false; - } - if (toLongBits(y) != toLongBits(other.y)) { - return false; - } - if (toLongBits(z) != toLongBits(other.z)) { - return false; - } - return true; - } - - /** - * Returns the value at the given index: 0->x, 1->y, 2->z - */ - public double get(final int index) { - switch (index) { - case 0: - return x; - case 1: - return y; - case 2: - return z; - default: - throw new RuntimeException("index out of bounds " + index); - } - } - - /** - * Returns the x component of this vector - */ - public double getX() { - return x; - } - - /** - * Returns the y component of this vector - */ - public double getY() { - return y; - } - - /** - * Returns the z component of this vector - */ - public double getZ() { - return z; - } - - /** - * Hash code consistent with equals(). - */ - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - long temp; - temp = toLongBits(x); - result = (prime * result) + (int) (temp ^ (temp >>> 32)); - temp = toLongBits(y); - result = (prime * result) + (int) (temp ^ (temp >>> 32)); - temp = toLongBits(z); - result = (prime * result) + (int) (temp ^ (temp >>> 32)); - return result; - } - - /** - * Returns true if and only if all of the components are finite(not - * NaN, not infinite). - * - */ - public boolean isFinite() { - return !isNaN() && !isInfinite(); - } - - /** - * Returns true if and only if any component is infinite. - * - */ - public boolean isInfinite() { - return Double.isInfinite(x) || Double.isInfinite(y) || Double.isInfinite(z); - } - - /** - * Returns true if and only if any component is NaN. - * - */ - public boolean isNaN() { - return Double.isNaN(x) || Double.isNaN(y) || Double.isNaN(z); - } - - /** - * Returns true if and only if this vector is finite and has a length within - * {@link Vector3D#NORMAL_LENGTH_TOLERANCE} of unit length. - */ - public boolean isNormal() { - final double len = length(); - return isFinite() && Double.isFinite(len) && (FastMath.abs(len - 1) < NORMAL_LENGTH_TOLERANCE); - } - - /** - * Returns true if and only if this {@link Vector3D} is perpendicular to the - * given {@link Vector3D} within the - * {@link Vector3D#PERPENDICUALR_ANGLE_TOLERANCE} - */ - public boolean isPerpendicularTo(Vector3D v) { - return FastMath.abs(angle(v) - FastMath.PI / 2) < PERPENDICUALR_ANGLE_TOLERANCE; - } - - /** - * Returns the length of the vector, i.e. its norm. - *

            - * This is vector length (e.g. SQRT[x^2 + y^2 + z^2]) value. - *

            - * Examples: - *

              - *
            • v1 = (-1,2,6): - * v1.length() = SQRT[41] = 6.4031242374328485 - *
            • v1 = (0,0,0): v1.length() = SQRT[0] = 0.0 - *
            - * - * @see http://en.wikipedia.org/wiki/Euclidean_vector#Length - * @see http://en.wikipedia.org/wiki/Norm_(mathematics)#Euclidean_norm - */ - public double length() { - return FastMath.sqrt((x * x) + (y * y) + (z * z)); - } - - /** - * Returns a new {@link Vector3D} instance that if equal to this - * {@link Vector3D} scaled to unit length. - *

            - * Normalizing a vector scales the vector to a unit vector. If the vector - * has a length of zero, a {@link Vector3D} (NaN,NaN,NaN) will result. - *

            - * Examples: - *

              - *
            • v1 = (-1,2,6): - * v1.normalize() = (-0.15617376188860607, 0.31234752377721214, 0.9370425713316364) - *
            • v1 = (0,0,0): v1.normalize() = (NaN, NaN, NaN) - *
            - * - * @see http://en.wikipedia.org/wiki/Normalized_vector - */ - public Vector3D normalize() { - final double invLength = 1.0 / length(); - return new Vector3D(x * invLength, y * invLength, z * invLength); - } - - /** - * Returns a new {@link Vector3D} instance that is this {@link Vector3D} - * scaled by -1. - * - *

            - * Examples: - *

              - *
            • v1 = (-1,2,6): v1.reverse() = (1,-2,-6) - *
            • v1 = (0,0,0): v1.reverse() = (0,0,0) - *
            - */ - public Vector3D reverse() { - return scale(-1); - } - - /** - * Returns a new {@link Vector3D} instance that is the result of Rotating - * this {@link Vector3D} about the given {@link Vector3D} rotator at the - * given angle in radians via a right hand rotation (i.e. - * counter-clockwise). - *

            - * Examples: - *

              - *
            • v1 = (-1,2,6), v2 = (1,-1,-1), - * v1.rotate(v2, theta): - *
                - *
              • theta = 0.0 -> (-1.0, 2.0, 6.0) - *
              • theta = Math.PI/2.0 = 1.5707963267948966 -> - * (-5.309401076758505, 0.11324865405187179, 3.577350269189627) - *
              • theta = Math.PI = 3.141592653589793 -> (-5.000000000000002, - * 4.000000000000002, 1.7763568394002505E-15) - *
              • theta = (3.0/2.0)*Math.PI = 4.71238898038469 -> - * (-0.690598923241498, 5.88675134594813, 2.4226497308103747) - *
              • theta = 2.0*Math.PI = 6.283185307179586 -> - * (-0.9999999999999996, 2.000000000000001, 6.0) - *
              - *
            • v1 = (-1,2,6), v2 = (0,0,0), - * theta = Math.PI = 3.141592653589793, - * v1.rotate(v2, theta) -> (NaN, NaN, NaN) - *
            • v1 = (0,0,0), v2 = (1,-1,-1), - * theta = Math.PI = 3.141592653589793, - * v1.rotate(v2, theta) -> (0,0,0) - *
            - * - * @param rotator - * the vector to rotate about. - * @param theta - * the angle of rotation according to a right hand coordinate - * system (i.e. counter-clockwise). - */ - public Vector3D rotateAbout(final Vector3D rotator, final double theta) { - final MutableVector3D m = new MutableVector3D(this); - final MutableVector3D r = new MutableVector3D(rotator); - m.rotateAbout(r, theta); - return new Vector3D(m); - } - - /** - * Returns a new {@link Vector3D} instance that result from rotating this - * {@link Vector3D} toward the given {@link Vector3D} at the given angle in - * radians. - *

            - * Examples: - *

              - *
            • v1 = (-1,2,6), v2 = (1,-1,-1): - * v1.rotateToward(v2, theta) produces: - *
                - *
              • theta = 0.0 -> (-0.9999999999999999, 2.0, 6.0) - *
              • theta = Math.PI/2.0 = 1.5707963267948966 -> - * (4.937707198786941, -3.548977049128114, 2.005943549507195) - *
              • theta = Math.PI = 3.141592653589793 -> (1.0000000000000007, - * -2.0000000000000004, -6.0) - *
              • theta = (3.0/2.0)*Math.PI = 4.71238898038469 -> - * (-4.937707198786941, 3.5489770491281134, -2.0059435495071956) - *
              • theta = 2.0*Math.PI = 6.283185307179586 -> - * (-1.000000000000001, 2.000000000000001, 5.999999999999999) - *
              - *
            • v1 = (-1,2,6), v2 = (0,0,0), - * theta = Math.PI = 3.141592653589793, - * v1.rotate(v2, theta) -> (NaN, NaN, NaN) - *
            • v1 = (0,0,0), v2 = (1,-1,-1), - * theta = Math.PI = 3.141592653589793, - * v1.rotate(v2, theta) -> (NaN, NaN, NaN) - *
            - * - * - * @param v - * the vector to rotate toward. - * @param theta - * the angle of rotation - */ - public Vector3D rotateToward(final Vector3D v, final double theta) { - final MutableVector3D m = new MutableVector3D(this); - m.rotateToward(new MutableVector3D(v), theta); - return new Vector3D(m); - } - - /** - * Returns a new {@link Vector3D} instance resulting from the scaling of - * this {@link Vector3D} by the given scaling factor. - *

            - * Examples: v1 = (-1,2,6): - *

              - *
            • v1.scale(2) = (2,4,-12) - *
            • v1.scale(-1) = (1,-2,-6), Note: This is the same as - * v1.reverse() - *
            • v1.scale(1) = (-1,2,6) - *
            • v1.scale(0) = (0,0,0) - *
            - * - * @param m - * the magnitude, e.g. scaling factor. - */ - public Vector3D scale(final double scalar) { - return new Vector3D(x * scalar, y * scalar, z * scalar); - } - - /* - * Squares the provided value. - * - * @param value to be squared - * - * @return the value squared - */ - private double sqr(final double value) { - return value * value; - } - - /** - * Returns the square of the distance from this {@link Vector3D} to the - * given {@link Vector3D}. - *

            - * This is Euclidean Distance squared (e.g. (a-x)^2 + (b-y)^2 + (c-z)^2) - * value. - *

            - * Examples: v1 = (-1,2,6): - *

              - *
            • v2 = (1,-1,-1): v1.squareDistanceTo(v2) = 62.0 - *
            - */ - public double squareDistanceTo(final Vector3D v) { - return ((v.x - x) * (v.x - x)) + ((v.y - y) * (v.y - y)) + ((v.z - z) * (v.z - z)); - } - - /** - * Returns the length of this {@link Vector3D} squared. - *

            - * This is vector norm squared (e.g. x^2 + y^2 + z^2). - *

            - * Examples: - *

              - *
            • v1 = (-1,2,6): v1.normSquared() = 41 - *
            • v1 = (0,0,0): v1.normSquared() = 0 - *
            - * - * @see http://en.wikipedia.org/wiki/Euclidean_vector#Length - * @see http://en.wikipedia.org/wiki/Norm_(mathematics)#Euclidean_norm - */ - public double squareLength() { - return (x * x) + (y * y) + (z * z); - } - - /** - * Returns a new {@link Vector3D} instance resulting from the subtraction of - * the scalar components from this {@link Vector3D}. - *

            - * Examples: Given that v1 = (-1,2,6) - *

              - *
            • v1.sub(1,-1,-1) = (-1-1,2-(-1),6-(-1)) = (-2,3,7) - *
            • v1.sub(0,0,0) = (-1,2,6) - *
            - */ - public Vector3D sub(final double x, final double y, final double z) { - return new Vector3D(this.x - x, this.y - y, this.z - z); - } - - /** - * Returns a new {@link Vector3D} instance resulting from the subtraction of - * the given {@link Vector3D} from this {@link Vector3D}. - *

            - * Examples: v1 = (-1,2,6) - *

              - *
            • v2 = (1,-1,-1): - * v1.sub(v2) = (-1-1,2-(-1),6-(-1)) = (-2,3,7) - *
            • v2 = (0,0,0): v1.sub(v2) = (-1,2,6) - *
            - * - * @param v - * the vector to be added. - */ - public Vector3D sub(final Vector3D v) { - return new Vector3D(x - v.x, y - v.y, z - v.z); - } - - /** - * Returns a length 3 array of double [x,y,z] from this {@link Vector3D} - */ - public double[] toArray() { - return new double[] { x, y, z }; - } - - /** - * Returns the string representation of this {@link Vector3D} in the form - * - * Vector3D [x=2.57,y=-34.1,z=8.73] - */ - @Override - public String toString() { - return "Vector3D [x=" + x + ", y=" + y + ", z=" + z + "]"; - } -} \ No newline at end of file diff --git a/gcm3/src/main/java/util/wrappers/MultiKey.java b/gcm3/src/main/java/util/wrappers/MultiKey.java deleted file mode 100644 index 2073ac7d3..000000000 --- a/gcm3/src/main/java/util/wrappers/MultiKey.java +++ /dev/null @@ -1,162 +0,0 @@ -package util.wrappers; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import net.jcip.annotations.NotThreadSafe; - -/** - * A utility class that allows an ordered set of objects to be used as a key. - * - * @author Shawn Hatch - * - */ -@NotThreadSafe -public final class MultiKey { - public static Builder builder() { - return new Builder(); - } - - /** - * A convenience builder class for MultiKey for situations where it is not - * practical to use the ellipsis based constructor. - * - */ - public static class Builder { - - private Builder() { - } - - private List keys = new ArrayList<>(); - - public Builder addKey(final Object key) { - keys.add(key); - return this; - } - - public MultiKey build() { - - try { - final MultiKey result = new MultiKey(); - result.objects = keys.toArray(); - return result; - } finally { - keys = new ArrayList<>(); - } - } - } - - private Object[] objects; - - private int hashCode; - - private boolean hashCodeDerived; - - /** - * Create a MultiKey by providing a sequence of Objects. - * - * @param objects - */ - public MultiKey(final Object... objects) { - this.objects = Arrays.copyOf(objects, objects.length); - } - - private void deriveHashCode() { - hashCode = Arrays.hashCode(objects); - hashCodeDerived = true; - } - - /** - * Two MultiKeys are equal if and only if their keys are equal. - */ - @Override - public boolean equals(final Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (!(obj instanceof MultiKey)) { - return false; - } - final MultiKey other = (MultiKey) obj; - return Arrays.equals(objects, other.objects); - } - - /** - * Returns the indexed key from the keys provided during construction. - * - * @throws ArrayIndexOutOfBoundsException - * if the index is non-negative or greater than or equal to the - * number of keys used to create this MultiKey. - */ - @SuppressWarnings("unchecked") - public T getKey(final int index) { - return (T) objects[index]; - } - - /** - * Returns the keys that form this Multikey as an Object array. - * - */ - public Object[] getKeys() { - return Arrays.copyOf(objects, objects.length); - } - - /** - * Returns the hasCode for this MultiKey. Returns the value given by - * Arrays.hashCode(this.getKeys()). - */ - @Override - public int hashCode() { - if (!hashCodeDerived) { - deriveHashCode(); - } - return hashCode; - } - - /** - * Returns the number of keys used to create this MultiKey - */ - public int size() { - return objects.length; - } - - /** - * Alternative to toString() that is equivalent to - * Arrays.toString((this.getKeys()). - */ - public String toKeyString() { - return Arrays.toString(objects); - } - - public String toTabString() { - StringBuilder sb = new StringBuilder(); - boolean first = true; - for (Object object : objects) { - if (first) { - first = false; - } else { - sb.append("\t"); - } - sb.append(object); - } - return sb.toString(); - } - - /** - * Returns a standard string for MultiKey that lists each key to string - * separated by commas. - */ - @Override - public String toString() { - final StringBuilder builder = new StringBuilder(); - builder.append("MultiKey [objects="); - builder.append(Arrays.toString(objects)); - builder.append("]"); - return builder.toString(); - } - -} diff --git a/gcm3/src/main/java/util/wrappers/MutableBoolean.java b/gcm3/src/main/java/util/wrappers/MutableBoolean.java deleted file mode 100644 index d671e38a1..000000000 --- a/gcm3/src/main/java/util/wrappers/MutableBoolean.java +++ /dev/null @@ -1,55 +0,0 @@ -package util.wrappers; - -public final class MutableBoolean { - private boolean value; - - public MutableBoolean(boolean value) { - - this.value = value; - } - - public MutableBoolean() { - - } - - public void setValue(boolean value) { - this.value = value; - } - - public boolean getValue() { - return value; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("MutableBoolean [value="); - builder.append(value); - builder.append("]"); - return builder.toString(); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + (value ? 1231 : 1237); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof MutableBoolean)) { - return false; - } - MutableBoolean other = (MutableBoolean) obj; - if (value != other.value) { - return false; - } - return true; - } - -} \ No newline at end of file diff --git a/gcm3/src/main/java/util/wrappers/MutableDouble.java b/gcm3/src/main/java/util/wrappers/MutableDouble.java deleted file mode 100644 index e14e023eb..000000000 --- a/gcm3/src/main/java/util/wrappers/MutableDouble.java +++ /dev/null @@ -1,74 +0,0 @@ -package util.wrappers; - -public final class MutableDouble { - private double value; - - public MutableDouble(double value) { - - this.value = value; - } - - public MutableDouble() { - - } - - public void increment(){ - value++; - } - - public void increment(double value){ - this.value += value; - } - - - public void decrement(){ - value--; - } - - public void decrement(double value){ - this.value -= value; - } - - public void setValue(double value) { - this.value = value; - } - - public double getValue() { - return value; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("MutableDouble [value="); - builder.append(value); - builder.append("]"); - return builder.toString(); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - long temp; - temp = Double.doubleToLongBits(value); - result = prime * result + (int) (temp ^ (temp >>> 32)); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof MutableDouble)) { - return false; - } - MutableDouble other = (MutableDouble) obj; - if (Double.doubleToLongBits(value) != Double.doubleToLongBits(other.value)) { - return false; - } - return true; - } - -} \ No newline at end of file diff --git a/gcm3/src/main/java/util/wrappers/MutableInteger.java b/gcm3/src/main/java/util/wrappers/MutableInteger.java deleted file mode 100644 index d90b33b29..000000000 --- a/gcm3/src/main/java/util/wrappers/MutableInteger.java +++ /dev/null @@ -1,72 +0,0 @@ -package util.wrappers; - -public final class MutableInteger { - private int value; - - public MutableInteger(int value) { - - this.value = value; - } - - public MutableInteger() { - - } - - public void increment(){ - value++; - } - - public void increment(int value){ - this.value += value; - } - - - public void decrement(){ - value--; - } - - public void decrement(int value){ - this.value -= value; - } - - public void setValue(int value) { - this.value = value; - } - - public int getValue() { - return value; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("MutableInteger [value="); - builder.append(value); - builder.append("]"); - return builder.toString(); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + value; - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof MutableInteger)) { - return false; - } - MutableInteger other = (MutableInteger) obj; - if (value != other.value) { - return false; - } - return true; - } - -} \ No newline at end of file diff --git a/gcm3/src/main/java/util/wrappers/MutableLong.java b/gcm3/src/main/java/util/wrappers/MutableLong.java deleted file mode 100644 index 2d4f8d88f..000000000 --- a/gcm3/src/main/java/util/wrappers/MutableLong.java +++ /dev/null @@ -1,75 +0,0 @@ -package util.wrappers; - -public final class MutableLong { - private long value; - - public MutableLong(long value) { - - this.value = value; - } - - public MutableLong() { - - } - - public void increment(){ - value++; - } - - public void increment(long value){ - this.value += value; - } - - - public void decrement(){ - value--; - } - - public void decrement(long value){ - this.value -= value; - } - - public void setValue(long value) { - this.value = value; - } - - public long getValue() { - return value; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("MutableLong [value="); - builder.append(value); - builder.append("]"); - return builder.toString(); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + (int) (value ^ (value >>> 32)); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof MutableLong)) { - return false; - } - MutableLong other = (MutableLong) obj; - if (value != other.value) { - return false; - } - return true; - } - - - - -} \ No newline at end of file diff --git a/gcm3/src/main/java/util/wrappers/MutableObject.java b/gcm3/src/main/java/util/wrappers/MutableObject.java deleted file mode 100644 index 83acc8ef6..000000000 --- a/gcm3/src/main/java/util/wrappers/MutableObject.java +++ /dev/null @@ -1,61 +0,0 @@ -package util.wrappers; - -public final class MutableObject { - private T value; - - public MutableObject(T value) { - this.value = value; - } - - public MutableObject() { - - } - - public void setValue(T value) { - this.value = value; - } - - public T getValue() { - return value; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("MutableObject [value="); - builder.append(value); - builder.append("]"); - return builder.toString(); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((value == null) ? 0 : value.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof MutableObject)) { - return false; - } - @SuppressWarnings("rawtypes") - MutableObject other = (MutableObject) obj; - if (value == null) { - if (other.value != null) { - return false; - } - } else if (!value.equals(other.value)) { - return false; - } - return true; - } - - - -} \ No newline at end of file diff --git a/gcm3/src/test/java/nucleus/AT_ActorContext.java b/gcm3/src/test/java/nucleus/AT_ActorContext.java deleted file mode 100644 index 02a3803bc..000000000 --- a/gcm3/src/test/java/nucleus/AT_ActorContext.java +++ /dev/null @@ -1,1675 +0,0 @@ -package nucleus; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; - -import nucleus.testsupport.testplugin.ScenarioPlanCompletionObserver; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestDataManager; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import nucleus.testsupport.testplugin.TestScenarioReport; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; -import util.wrappers.MultiKey; -import util.wrappers.MutableBoolean; - -/** - * - * Test for the implementation of AgentContext by the nucleus Engine - * - * @author Shawn Hatch - * - */ -@UnitTest(target = ActorContext.class) -public class AT_ActorContext { - - /* - * DataView implementor to support tests - */ - private static class TestDataManager1 extends TestDataManager { - } - - - - private static class TestDataManager3 extends TestDataManager { - - } - - private static class TestDataManager3A extends TestDataManager3 { - - } - - private static class TestDataManager3B extends TestDataManager3 { - - } - - private static class TestDataManager4 extends TestDataManager { - - } - - private static class TestDataManager4A extends TestDataManager4 { - - } - - private static class DataChangeEvent implements Event { - private final DatumType datumType; - private final int value; - - public DataChangeEvent(final DatumType datumType, final int value) { - super(); - this.datumType = datumType; - this.value = value; - } - - @Override - public boolean equals(final Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof DataChangeEvent)) { - return false; - } - final DataChangeEvent other = (DataChangeEvent) obj; - if ((datumType != other.datumType) || (value != other.value)) { - return false; - } - return true; - } - - public DatumType getDatumType() { - return datumType; - } - - @Override - public Object getPrimaryKeyValue() { - return datumType; - } - - public int getValue() { - return value; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = (prime * result) + ((datumType == null) ? 0 : datumType.hashCode()); - result = (prime * result) + value; - return result; - } - - @Override - public String toString() { - final StringBuilder builder = new StringBuilder(); - builder.append("DataChangeEvent [datumType="); - builder.append(datumType); - builder.append(", value="); - builder.append(value); - builder.append("]"); - return builder.toString(); - } - } - - private static enum DatumType { - TYPE_1, TYPE_2 - } - - private static enum Local_Labeler_ID implements EventLabelerId { - TEST_LABELER_ID, OBSERVATION_TEST_LABELER_ID, DATA_CHANGE - } - - private static class BaseEvent implements Event { - - } - - private static class TestEvent implements Event { - - } - - private static enum ValueType { - HIGH, LOW - } - - private static EventLabel getEventLabelByDatumAndValue(final DatumType datumType, final ValueType valueType) { - return EventLabel .builder(DataChangeEvent.class)// - .setEventLabelerId(Local_Labeler_ID.DATA_CHANGE)// - .addKey(datumType)// - .addKey(valueType)// - .build();// - } - - private static EventLabeler getEventLabelerForDataChangeObservation() { - return EventLabeler .builder(DataChangeEvent.class)// - .setEventLabelerId(Local_Labeler_ID.DATA_CHANGE)// - .setLabelFunction((context, event) -> { - ValueType valueType = ValueType.LOW; - if (event.getValue() > 10) { - valueType = ValueType.HIGH; - } - return getEventLabelByDatumAndValue(event.getDatumType(), valueType); - })// - .build(); - } - - /** - * Tests {@link AgentContext#agentExists(AgentId) - */ - @Test - @UnitTestMethod(name = "actorExists", args = { ActorId.class }) - public void testActorExists() { - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - double testTime = 1; - // there are no precondition tests - - // have the test agent show it exists and that other agents do not - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(testTime++, (context) -> { - assertTrue(context.actorExists(new ActorId(0))); - assertFalse(context.actorExists(new ActorId(1))); - assertFalse(context.actorExists(new ActorId(2))); - })); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - // run the simulation - Simulation .builder()// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput)// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that the action plans got executed - assertTrue(scenarioPlanCompletionObserver.allPlansExecuted()); - } - - @Test - @UnitTestMethod(name = "addActor", args = { ActorContext.class }) - public void testAddActor() { - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - MutableBoolean actorWasAdded = new MutableBoolean(); - - // there are no precondition tests - - // have the test agent show it exists and that other agents do not - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - c.addActor((c2) -> actorWasAdded.setValue(true)); - })); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - // run the simulation - Simulation .builder()// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that the action plans got executed - assertTrue(actorWasAdded.getValue()); - } - - @Test - @UnitTestMethod(name = "addEventLabeler", args = { EventLabeler.class }) - public void testAddEventLabeler() { - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - // have the actor test the preconditions - pluginDataBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { - EventLabelerId eventLabelerId = new EventLabelerId() { - }; - - // if the event labeler is null - ContractException contractException = assertThrows(ContractException.class, () -> c.addEventLabeler(null)); - assertEquals(NucleusError.NULL_EVENT_LABELER, contractException.getErrorType()); - - /* - * if the event labeler contains a labeler id that is the id of a - * previously added event labeler - */ - c.addEventLabeler(EventLabeler.builder(BaseEvent.class).setEventLabelerId(eventLabelerId).setLabelFunction((c2, e) -> null).build()); - contractException = assertThrows(ContractException.class, () -> { - EventLabeler eventLabeler = EventLabeler .builder(BaseEvent.class)// - .setEventLabelerId(eventLabelerId)// - .setLabelFunction((c2, e) -> EventLabel .builder(BaseEvent.class)// - .setEventLabelerId(eventLabelerId)// - .addKey(BaseEvent.class)// - .build())// - .build(); - c.addEventLabeler(eventLabeler); - }); - assertEquals(NucleusError.DUPLICATE_LABELER_ID_IN_EVENT_LABELER, contractException.getErrorType()); - - })); - - /* - * create a new event labeler that will be added by the resolver and the - * utilized by an agent. - */ - EventLabelerId id = new EventLabelerId() { - }; - - EventLabeler eventLabeler = EventLabeler .builder(BaseEvent.class)// - .setEventLabelerId(id)// - .setLabelFunction((c, e) -> EventLabel .builder(BaseEvent.class)// - .setEventLabelerId(id)// - .addKey(BaseEvent.class)// - .build()// - ).build(); - - // have the actor add the event labeler - pluginDataBuilder.addTestActorPlan("observer", new TestActorPlan(1, (c) -> { - c.addEventLabeler(eventLabeler); - })); - - /* - * Create a container for the agent to record that it received the Test - * Event and we can conclude that the event labeler had been properly - * added to the simulation. - */ - MutableBoolean eventObserved = new MutableBoolean(); - - // have the agent observe the test event - - pluginDataBuilder.addTestActorPlan("observer", new TestActorPlan(2, (c) -> { - EventLabel eventLabel = EventLabel .builder(BaseEvent.class)// - .setEventLabelerId(id)// - .addKey(BaseEvent.class).build(); - c.subscribe(eventLabel, (c2, e) -> { - eventObserved.setValue(true); - }); - })); - - // have the actor create a test event for the agent to observe - pluginDataBuilder.addTestActorPlan("observer", new TestActorPlan(3, (c) -> { - c.releaseEvent(new BaseEvent()); - })); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - // build and execute the engine - Simulation .builder()// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput)// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that all plans were executed - assertTrue(scenarioPlanCompletionObserver.allPlansExecuted()); - - /* - * Show that the event labeler must have been added to the simulation - * since the agent observed the test event - */ - assertTrue(eventObserved.getValue()); - - } - - /** - * Tests {@link AgentContext#addPlan(Consumer, double, Object) - */ - @Test - @UnitTestMethod(name = "addKeyedPlan", args = { Consumer.class, double.class, Object.class }) - public void testAddKeyedPlan() { - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - // test preconditions - pluginDataBuilder.addTestActorPlan("Alpha", new TestActorPlan(1, (context) -> { - Object key = new Object(); - - double scheduledTime = context.getTime() + 1; - - ContractException contractException = assertThrows(ContractException.class, () -> context.addKeyedPlan(null, scheduledTime, key)); - assertEquals(NucleusError.NULL_PLAN, contractException.getErrorType()); - - contractException = assertThrows(ContractException.class, () -> context.addKeyedPlan((c) -> { - }, 0, key)); - assertEquals(NucleusError.PAST_PLANNING_TIME, contractException.getErrorType()); - - contractException = assertThrows(ContractException.class, () -> context.addKeyedPlan((c) -> { - }, scheduledTime, null)); - assertEquals(NucleusError.NULL_PLAN_KEY, contractException.getErrorType()); - - context.addKeyedPlan((c) -> { - }, scheduledTime, key); - - contractException = assertThrows(ContractException.class, () -> context.addKeyedPlan((c) -> { - }, scheduledTime, key)); - assertEquals(NucleusError.DUPLICATE_PLAN_KEY, contractException.getErrorType()); - - })); - - /* - * have the added test agent add a plan that can be retrieved and thus - * was added successfully - */ - pluginDataBuilder.addTestActorPlan("Alpha", new TestActorPlan(2, (context) -> { - Object key = new Object(); - assertFalse(context.getPlan(key).isPresent()); - context.addKeyedPlan((c) -> { - }, 100, key); - assertTrue(context.getPlan(key).isPresent()); - })); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - // run the simulation - Simulation .builder()// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput)// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that the action plans got executed - assertTrue(scenarioPlanCompletionObserver.allPlansExecuted()); - } - - @Test - @UnitTestMethod(name = "addPassiveKeyedPlan", args = { Consumer.class, Object.class }) - public void testAddPassiveKeyedPlan() { - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - // test preconditions - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (context) -> { - Object key = new Object(); - - double scheduledTime = context.getTime() + 1; - - ContractException contractException = assertThrows(ContractException.class, () -> context.addPassiveKeyedPlan(null, scheduledTime, key)); - assertEquals(NucleusError.NULL_PLAN, contractException.getErrorType()); - - contractException = assertThrows(ContractException.class, () -> context.addPassiveKeyedPlan((c) -> { - }, 0, key)); - assertEquals(NucleusError.PAST_PLANNING_TIME, contractException.getErrorType()); - - contractException = assertThrows(ContractException.class, () -> context.addPassiveKeyedPlan((c) -> { - }, scheduledTime, null)); - assertEquals(NucleusError.NULL_PLAN_KEY, contractException.getErrorType()); - - context.addPassiveKeyedPlan((c) -> { - }, scheduledTime, key); - - contractException = assertThrows(ContractException.class, () -> context.addPassiveKeyedPlan((c) -> { - }, scheduledTime, key)); - assertEquals(NucleusError.DUPLICATE_PLAN_KEY, contractException.getErrorType()); - - })); - - /* - * have the added test agent add a plan that can be retrieved and thus - * was added successfully - */ - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(2, (context) -> { - Object key = new Object(); - - assertFalse(context.getPlan(key).isPresent()); - - context.addPassiveKeyedPlan((c) -> { - }, 3, key); - - assertTrue(context.getPlan(key).isPresent()); - })); - - /* - * Show that passive plans do not execute if there are no remaining - * active plans. To do this, we will schedule a few passive plans, one - * active plan and then a few more passive plans. We will then show that - * the passive plans that come after the last active plan never execute - */ - - // create some containers for passive keys - Set expectedPassiveKeys = new LinkedHashSet<>(); - expectedPassiveKeys.add(1); - expectedPassiveKeys.add(2); - Set actualPassiveKeys = new LinkedHashSet<>(); - - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(4, (context) -> { - - // schedule two passive plans - context.addPassiveKeyedPlan((c) -> { - actualPassiveKeys.add(1); - }, 5, 1); - context.addPassiveKeyedPlan((c) -> { - actualPassiveKeys.add(2); - }, 6, 2); - - // schedule the last active plan - context.addPlan((c) -> { - }, 7); - - // schedule two more passive plans - context.addPassiveKeyedPlan((c) -> { - actualPassiveKeys.add(3); - }, 8, 3); - context.addPassiveKeyedPlan((c) -> { - actualPassiveKeys.add(4); - }, 9, 4); - - })); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - // run the simulation - Simulation .builder()// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // we do not need to show that all plans executed - - // show that the last two passive plans did not execute - assertEquals(expectedPassiveKeys, actualPassiveKeys); - - } - - @Test - @UnitTestMethod(name = "addPassivePlan", args = { Consumer.class, double.class }) - public void testAddPassivePlan() { - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - // test preconditions - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (context) -> { - double scheduledTime = context.getTime() + 1; - - ContractException contractException = assertThrows(ContractException.class, () -> context.addPassivePlan(null, scheduledTime)); - assertEquals(NucleusError.NULL_PLAN, contractException.getErrorType()); - - contractException = assertThrows(ContractException.class, () -> context.addPassivePlan((c) -> { - }, 0)); - assertEquals(NucleusError.PAST_PLANNING_TIME, contractException.getErrorType()); - - })); - - /* - * Show that passive plans do not execute if there are no remaining - * active plans. To do this, we will schedule a few passive plans, one - * active plan and then a few more passive plans. We will then show that - * the passive plans that come after the last active plan never execute - */ - - // create some containers for passive keys - Set expectedPassiveValues = new LinkedHashSet<>(); - expectedPassiveValues.add(1); - expectedPassiveValues.add(2); - Set actualPassiveValues = new LinkedHashSet<>(); - - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(4, (context) -> { - - // schedule two passive plans - context.addPassivePlan((c) -> { - actualPassiveValues.add(1); - }, 5); - context.addPassivePlan((c) -> { - actualPassiveValues.add(2); - }, 6); - - // schedule the last active plan - context.addPlan((c) -> { - }, 7); - - // schedule two more passive plans - context.addPassivePlan((c) -> { - actualPassiveValues.add(3); - }, 8); - context.addPassivePlan((c) -> { - actualPassiveValues.add(4); - }, 9); - - })); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - // run the simulation - Simulation .builder()// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // we do not need to show that all plans executed - - // show that the last two passive plans did not execute - assertEquals(expectedPassiveValues, actualPassiveValues); - - } - - // - - /** - * Tests {@link AgentContext#addPlan(Consumer, double) - */ - @Test - @UnitTestMethod(name = "addPlan", args = { Consumer.class, double.class }) - public void testAddPlan() { - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - // test preconditions - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (context) -> { - double scheduledTime = context.getTime() + 1; - - ContractException contractException = assertThrows(ContractException.class, () -> context.addPlan(null, scheduledTime)); - assertEquals(NucleusError.NULL_PLAN, contractException.getErrorType()); - - contractException = assertThrows(ContractException.class, () -> context.addPlan((c) -> { - }, 0)); - assertEquals(NucleusError.PAST_PLANNING_TIME, contractException.getErrorType()); - - })); - - /* - * Have the actor add a plan and show that that plan executes - */ - - MutableBoolean planExecuted = new MutableBoolean(); - - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(4, (context) -> { - // schedule two passive plans - context.addPlan((c) -> { - planExecuted.setValue(true); - }, 5); - })); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - // run the simulation - Simulation .builder()// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // we do not need to show that all plans executed - - // show that the last two passive plans did not execute - assertTrue(planExecuted.getValue()); - } - - @Test - @UnitTestMethod(name = "getActorId", args = {}) - public void testGetActorId() { - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - double testTime = 1; - // there are no precondition tests - - Set observedActorIds = new LinkedHashSet<>(); - - /* - * Have actors get their own actor ids and show that these ids match the - * expected values established duing the initialization of the - * TestActors. - */ - pluginDataBuilder.addTestActorPlan("Alpha", new TestActorPlan(testTime++, (c) -> { - ActorId actorId = c.getActorId(); - observedActorIds.add(actorId); - assertNotNull(actorId); - - })); - - pluginDataBuilder.addTestActorPlan("Beta", new TestActorPlan(testTime++, (c) -> { - ActorId actorId = c.getActorId(); - observedActorIds.add(actorId); - assertNotNull(actorId); - - })); - - pluginDataBuilder.addTestActorPlan("Gamma", new TestActorPlan(testTime++, (c) -> { - ActorId actorId = c.getActorId(); - observedActorIds.add(actorId); - assertNotNull(actorId); - - })); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - - // run the simulation - Simulation .builder()// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput)// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that all action plans were executed - assertTrue(scenarioPlanCompletionObserver.allPlansExecuted()); - - // show that the number of actor ids matches the number of actor aliases - assertEquals(3, observedActorIds.size()); - } - - @Test - @UnitTestMethod(name = "getDataManager", args = { Class.class }) - public void testGetDataManager() { - - // create the test plugin data builder - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - // create a data manager for the actor to find - - pluginDataBuilder.addTestDataManager("dm1", () -> new TestDataManager1()); - pluginDataBuilder.addTestDataManager("dm3A", () -> new TestDataManager3A()); - pluginDataBuilder.addTestDataManager("dm3B", () -> new TestDataManager3B()); - pluginDataBuilder.addTestDataManager("dm4A", () -> new TestDataManager4A()); - - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0,(c)->{ - c.getDataManager(TestDataManager1.class); - c.getDataManager(TestDataManager3A.class); - c.getDataManager(TestDataManager3B.class); - c.getDataManager(TestDataManager4A.class); - })); - - - // build the action plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - - // execute the engine - Simulation .builder()// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput)// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that the action was executed - assertTrue(scenarioPlanCompletionObserver.allPlansExecuted()); - - // Precondition test 1 - pluginDataBuilder.addTestDataManager("dm3A", () -> new TestDataManager3A()); - pluginDataBuilder.addTestDataManager("dm3B", () -> new TestDataManager3B()); - - // show that ambiguous class matching throws an exception - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - ContractException contractException = assertThrows(ContractException.class, () -> c.getDataManager(TestDataManager3.class)); - assertEquals(NucleusError.AMBIGUOUS_DATA_MANAGER_CLASS, contractException.getErrorType()); - })); - - // build the action plugin - testPluginData = pluginDataBuilder.build(); - testPlugin = TestPlugin.getTestPlugin(testPluginData); - scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - - // execute the engine - Simulation .builder()// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput)// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that the action was executed - assertTrue(scenarioPlanCompletionObserver.allPlansExecuted()); - - // Precondition test 2 - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - ContractException contractException = assertThrows(ContractException.class, () -> c.getDataManager(null)); - assertEquals(NucleusError.NULL_DATA_MANAGER_CLASS, contractException.getErrorType()); - })); - - // build the action plugin - testPluginData = pluginDataBuilder.build(); - testPlugin = TestPlugin.getTestPlugin(testPluginData); - scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - - // execute the engine - Simulation .builder()// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput)// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that the action was executed - assertTrue(scenarioPlanCompletionObserver.allPlansExecuted()); - } - - /** - * Tests {@link AgentContext#getPlan(Object) - */ - @Test - @UnitTestMethod(name = "getPlan", args = { Object.class }) - public void testGetPlan() { - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - // test preconditions - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (context) -> { - ContractException contractException = assertThrows(ContractException.class, () -> context.getPlan(null)); - assertEquals(NucleusError.NULL_PLAN_KEY, contractException.getErrorType()); - })); - - /* - * have the added test agent add a plan that can be retrieved and thus - * was added successfully - */ - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(2, (context) -> { - Object key = new Object(); - assertFalse(context.getPlan(key).isPresent()); - context.addKeyedPlan((c) -> { - }, 100, key); - assertTrue(context.getPlan(key).isPresent()); - })); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - - // run the simulation - Simulation .builder()// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput)// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that the action plans got executed - assertTrue(scenarioPlanCompletionObserver.allPlansExecuted()); - } - - /** - * Tests {@link AgentContext#getPlanKeys() - */ - @Test - @UnitTestMethod(name = "getPlanKeys", args = {}) - public void testGetPlanKeys() { - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - // There are no precondition tests - Set expectedKeys = new LinkedHashSet<>(); - int keyCount = 20; - for (int i = 0; i < keyCount; i++) { - expectedKeys.add(new Object()); - } - - // have the test agent add some plans - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (context) -> { - for (Object key : expectedKeys) { - context.addKeyedPlan((c) -> { - }, 100, key); - } - - Set actualKeys = context.getPlanKeys().stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expectedKeys, actualKeys); - - })); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - - // run the simulation - Simulation .builder()// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput)// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that the action plans got executed - assertTrue(scenarioPlanCompletionObserver.allPlansExecuted()); - } - - /** - * Tests {@link AgentContext#getPlanTime(Object) - */ - @Test - @UnitTestMethod(name = "getPlanTime", args = { Object.class }) - public void testGetPlanTime() { - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - // test preconditions - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (context) -> { - ContractException contractException = assertThrows(ContractException.class, () -> context.getPlanTime(null)); - assertEquals(NucleusError.NULL_PLAN_KEY, contractException.getErrorType()); - })); - - /* - * have the added test agent add a plan and show that the plan time is - * as expected - */ - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(2, (context) -> { - Object key = new Object(); - assertFalse(context.getPlanTime(key).isPresent()); - double expectedPlanTime = 100; - context.addKeyedPlan((c) -> { - }, expectedPlanTime, key); - assertTrue(context.getPlanTime(key).isPresent()); - Double actualPlanTime = context.getPlanTime(key).get(); - assertEquals(expectedPlanTime, actualPlanTime, 0); - })); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - // run the simulation - Simulation .builder()// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput)// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that the action plans got executed - assertTrue(scenarioPlanCompletionObserver.allPlansExecuted()); - } - - @Test - @UnitTestMethod(name = "getTime", args = {}) - - public void testGetTime() { - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - Set planTimes = new LinkedHashSet<>(); - - planTimes.add(4.6); - planTimes.add(13.8764); - planTimes.add(554.345); - planTimes.add(7.95346); - planTimes.add(400.234234); - planTimes.add(3000.12422346); - - /* - * Have the agent build plans to check the time in the simulation - * against the planning time - */ - pluginDataBuilder.addTestActorPlan("Actor 1", new TestActorPlan(0, (context1) -> { - for (Double planTime : planTimes) { - context1.addPlan((context2) -> { - assertEquals(planTime.doubleValue(), context2.getTime(), 0); - }, planTime); - } - })); - - // build the action plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - - // execute the engine - Simulation .builder()// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput)// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that the action was executed - assertTrue(scenarioPlanCompletionObserver.allPlansExecuted()); - } - - /** - * Tests {@link AgentContext#halt() - */ - @Test - @UnitTestMethod(name = "halt", args = {}) - public void testHalt() { - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - // there are no precondition tests - - // have the test agent execute several tasks, with one of the tasks - // halting the simulation - TestActorPlan actionPlan1 = new TestActorPlan(1, (context) -> { - }); - pluginDataBuilder.addTestActorPlan("actor", actionPlan1); - - TestActorPlan actionPlan2 = new TestActorPlan(2, (context) -> { - }); - pluginDataBuilder.addTestActorPlan("actor", actionPlan2); - - TestActorPlan actionPlan3 = new TestActorPlan(3, (context) -> { - context.halt(); - }); - pluginDataBuilder.addTestActorPlan("actor", actionPlan3); - - TestActorPlan actionPlan4 = new TestActorPlan(4, (context) -> { - }); - pluginDataBuilder.addTestActorPlan("actor", actionPlan4); - - TestActorPlan actionPlan5 = new TestActorPlan(5, (context) -> { - }); - pluginDataBuilder.addTestActorPlan("actor", actionPlan5); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - // run the simulation - Simulation .builder()// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that the plans that were scheduled after the halt did not - // execute - assertTrue(actionPlan1.executed()); - assertTrue(actionPlan2.executed()); - assertTrue(actionPlan3.executed()); - assertFalse(actionPlan4.executed()); - assertFalse(actionPlan5.executed()); - - } - - @Test - @UnitTestMethod(name = "releaseOutput", args = {}) - public void testReleaseOutput() { - - // begin building the action plugin - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - // set up the expected output - Set expectedOutput = new LinkedHashSet<>(); - expectedOutput.add("the sly fox"); - expectedOutput.add(15); - expectedOutput.add("the lazy, brown dog"); - expectedOutput.add(45.34513453); - - // have the agent release the output - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - for (Object outputValue : expectedOutput) { - c.releaseOutput(outputValue); - } - })); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - Set actualOutput = new LinkedHashSet<>(); - - /* - * Add an output consumer that will place the output into the - * actualOutput set above and then execute the simulation - */ - Simulation .builder()// - .addPlugin(testPlugin)// - .setOutputConsumer((o) -> { - if (!(o instanceof TestScenarioReport)) { - actualOutput.add(o); - } - })// - .build()// - .execute();// - - // show that the output matches expectations - assertEquals(expectedOutput, actualOutput); - - } - - @Test - @UnitTestMethod(name = "removeActor", args = { ActorId.class }) - public void testRemoveActor() { - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - // have the resolver execute the precondition tests - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - - ContractException contractException = assertThrows(ContractException.class, () -> c.removeActor(null)); - assertEquals(NucleusError.NULL_ACTOR_ID, contractException.getErrorType()); - - contractException = assertThrows(ContractException.class, () -> c.removeActor(new ActorId(1000))); - assertEquals(NucleusError.UNKNOWN_ACTOR_ID, contractException.getErrorType()); - - })); - - List addedActorIds = new ArrayList<>(); - - // have the add a few agents - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - for (int i = 0; i < 10; i++) { - ActorId actorId = c.addActor((c2) -> { - }); - assertTrue(c.actorExists(actorId)); - addedActorIds.add(actorId); - } - })); - - // have the actor remove the added actors - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { - for (ActorId actorId : addedActorIds) { - c.removeActor(actorId); - assertFalse(c.actorExists(actorId)); - } - })); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - - // build and execute the engine - Simulation .builder()// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput)// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that the actions were executed - assertTrue(scenarioPlanCompletionObserver.allPlansExecuted()); - - } - - /** - * Tests {@link AgentContext#removePlan(Object) - */ - @Test - @UnitTestMethod(name = "removePlan", args = { Object.class }) - public void testRemovePlan() { - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - // test preconditions - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (context) -> { - ContractException contractException = assertThrows(ContractException.class, () -> context.removePlan(null)); - assertEquals(NucleusError.NULL_PLAN_KEY, contractException.getErrorType()); - })); - - Object key = new Object(); - MutableBoolean removedPlanHasExecuted = new MutableBoolean(); - - // have the added test agent add a plan - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(2, (context) -> { - context.addKeyedPlan((c2) -> { - removedPlanHasExecuted.setValue(true); - }, 4, key); - })); - - // have the test agent remove the plan and show the plan no longer - // exists - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(3, (context) -> { - assertTrue(context.getPlan(key).isPresent()); - - context.removePlan(key); - - assertFalse(context.getPlan(key).isPresent()); - - })); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - - // run the simulation - Simulation .builder()// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput)// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that the action plans got executed - assertTrue(scenarioPlanCompletionObserver.allPlansExecuted()); - - // show that the remove plan was not executed - assertFalse(removedPlanHasExecuted.getValue()); - } - - /** - * Tests {@link AgentContext#releaseEvent(Event) - */ - @Test - @UnitTestMethod(name = "releaseEvent", args = { Event.class }) - public void testReleaseEvent() { - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - MutableBoolean eventResolved = new MutableBoolean(); - - // Have the actor subscribe to test event and then set the - // eventResolved to true - pluginDataBuilder.addTestActorPlan("alpha", new TestActorPlan(0, (c) -> { - c.subscribe(BaseEvent.class, (c2, e) -> { - eventResolved.setValue(true); - }); - })); - - // have another actor resolve a test event - pluginDataBuilder.addTestActorPlan("beta", new TestActorPlan(1, (context) -> { - context.releaseEvent(new BaseEvent()); - })); - - // precondition tests - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (context) -> { - ContractException contractException = assertThrows(ContractException.class, () -> context.releaseEvent(null)); - assertEquals(NucleusError.NULL_EVENT, contractException.getErrorType()); - })); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - // run the simulation - Simulation .builder()// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that event actually resolved - assertTrue(eventResolved.getValue()); - } - - @Test - @UnitTestMethod(name = "subscribe", args = { Class.class, BiConsumer.class }) - public void testSubscribe_EventClass() { - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - // have an actor perform precondition tests - pluginDataBuilder.addTestActorPlan("precondition checker", new TestActorPlan(0, (context) -> { - Class eventClass = null; - ContractException contractException = assertThrows(ContractException.class, () -> context.subscribe(eventClass, (c, e) -> { - })); - assertEquals(NucleusError.NULL_EVENT_CLASS, contractException.getErrorType()); - - contractException = assertThrows(ContractException.class, () -> context.subscribe(BaseEvent.class, null)); - assertEquals(NucleusError.NULL_EVENT_CONSUMER, contractException.getErrorType()); - })); - - Set receivedEvents = new LinkedHashSet<>(); - - /* - * - * Have an actor subscribe to data change events. - */ - - pluginDataBuilder.addTestActorPlan("subscriber", new TestActorPlan(1, (context) -> { - context.subscribe(DataChangeEvent.class, (c, e) -> { - receivedEvents.add(new MultiKey(c.getTime(), e)); - }); - })); - - /* - * Have another actor generate several data change observation events - * with differing types and values. - */ - pluginDataBuilder.addTestActorPlan("generator", new TestActorPlan(2, (c) -> { - c.releaseEvent(new DataChangeEvent(DatumType.TYPE_1, 0)); - c.releaseEvent(new DataChangeEvent(DatumType.TYPE_2, 5)); - c.releaseEvent(new DataChangeEvent(DatumType.TYPE_1, 20)); - - })); - - pluginDataBuilder.addTestActorPlan("generator", new TestActorPlan(3, (c) -> { - - c.releaseEvent(new DataChangeEvent(DatumType.TYPE_2, 0)); - c.releaseEvent(new DataChangeEvent(DatumType.TYPE_1, 5)); - c.releaseEvent(new DataChangeEvent(DatumType.TYPE_2, 25)); - c.releaseEvent(new DataChangeEvent(DatumType.TYPE_1, 38)); - c.releaseEvent(new DataChangeEvent(DatumType.TYPE_2, 234)); - })); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - - // run the simulation - Simulation .builder()// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput)// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that all plans got executed - assertTrue(scenarioPlanCompletionObserver.allPlansExecuted()); - - // show that all and only the observations corresponding to the - // subscribed event label were delivered to the subscriber actor - Set expectedEvents = new LinkedHashSet<>(); - - expectedEvents.add(new MultiKey(2.0, new DataChangeEvent(DatumType.TYPE_1, 0))); - expectedEvents.add(new MultiKey(2.0, new DataChangeEvent(DatumType.TYPE_2, 5))); - expectedEvents.add(new MultiKey(2.0, new DataChangeEvent(DatumType.TYPE_1, 20))); - expectedEvents.add(new MultiKey(3.0, new DataChangeEvent(DatumType.TYPE_2, 0))); - expectedEvents.add(new MultiKey(3.0, new DataChangeEvent(DatumType.TYPE_1, 5))); - expectedEvents.add(new MultiKey(3.0, new DataChangeEvent(DatumType.TYPE_2, 25))); - expectedEvents.add(new MultiKey(3.0, new DataChangeEvent(DatumType.TYPE_1, 38))); - expectedEvents.add(new MultiKey(3.0, new DataChangeEvent(DatumType.TYPE_2, 234))); - - assertEquals(expectedEvents, receivedEvents); - - } - - /** - * Tests {@link AgentContext#subscribe(EventLabel, AgentEventConsumer) - */ - @Test - @UnitTestMethod(name = "subscribe", args = { EventLabel.class, BiConsumer.class }) - public void testSubscribe_EventLabel() { - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - // have an actor perform precondition tests - pluginDataBuilder.addTestActorPlan("precondition checker", new TestActorPlan(0, (context) -> { - EventLabel eventLabel = EventLabel .builder(TestEvent.class)// - .setEventLabelerId(Local_Labeler_ID.TEST_LABELER_ID).addKey(TestEvent.class)// - .build(); - - context.addEventLabeler( - - EventLabeler.builder(TestEvent.class)// - .setEventLabelerId(Local_Labeler_ID.TEST_LABELER_ID)// - .setLabelFunction((c, e) -> { - return eventLabel; - })// - .build() - - ); - - EventLabel nullEventLabel = null; - ContractException contractException = assertThrows(ContractException.class, () -> context.subscribe(nullEventLabel, (c, e) -> { - })); - assertEquals(NucleusError.NULL_EVENT_LABEL, contractException.getErrorType()); - - contractException = assertThrows(ContractException.class, () -> context.subscribe(eventLabel, null)); - assertEquals(NucleusError.NULL_EVENT_CONSUMER, contractException.getErrorType()); - - EventLabel unknownEventLabel = EventLabel.builder(TestEvent.class)// - .setEventLabelerId(Local_Labeler_ID.DATA_CHANGE)// - .addKey(TestEvent.class)// - .build();// - contractException = assertThrows(ContractException.class, () -> context.subscribe(unknownEventLabel, (c, e) -> { - })); - assertEquals(NucleusError.UNKNOWN_EVENT_LABELER, contractException.getErrorType()); - - })); - - Set receivedEvents = new LinkedHashSet<>(); - - /* - * Have an actor add an event labeler for DataChangeObservation events. - * Then have it subscribe to data change events that are or type 1 and - * high value. When it receives a data change observation, it records it - * as a multi-key in the received events set. - */ - - pluginDataBuilder.addTestActorPlan("subscriber", new TestActorPlan(1, (context) -> { - - context.addEventLabeler(getEventLabelerForDataChangeObservation()); - context.subscribe(getEventLabelByDatumAndValue(DatumType.TYPE_1, ValueType.HIGH), (c, e) -> { - receivedEvents.add(new MultiKey(c.getTime(), e)); - }); - })); - - /* - * Have another actor generate several data change observation events - * with differing types and values. - */ - pluginDataBuilder.addTestActorPlan("generator", new TestActorPlan(2, (c) -> { - c.releaseEvent(new DataChangeEvent(DatumType.TYPE_1, 0)); - c.releaseEvent(new DataChangeEvent(DatumType.TYPE_2, 5)); - c.releaseEvent(new DataChangeEvent(DatumType.TYPE_1, 20)); - c.releaseEvent(new DataChangeEvent(DatumType.TYPE_2, 0)); - c.releaseEvent(new DataChangeEvent(DatumType.TYPE_1, 5)); - c.releaseEvent(new DataChangeEvent(DatumType.TYPE_2, 25)); - c.releaseEvent(new DataChangeEvent(DatumType.TYPE_1, 38)); - c.releaseEvent(new DataChangeEvent(DatumType.TYPE_2, 234)); - })); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - - // run the simulation - Simulation .builder()// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput)// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that all plans got executed - assertTrue(scenarioPlanCompletionObserver.allPlansExecuted()); - - // show that all and only the observations corresponding to the - // subscribed event label were delivered to the subscriber actor - Set expectedEvents = new LinkedHashSet<>(); - expectedEvents.add(new MultiKey(2.0, new DataChangeEvent(DatumType.TYPE_1, 20))); - expectedEvents.add(new MultiKey(2.0, new DataChangeEvent(DatumType.TYPE_1, 38))); - - assertEquals(expectedEvents, receivedEvents); - - } - - @Test - @UnitTestMethod(name = "subscribeToSimulationClose", args = { Consumer.class }) - public void testSubscribeToSimulationClose() { - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - MutableBoolean simCloseEventHandled = new MutableBoolean(); - - // have an actor schedule a few events and subscribe to simulation close - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - c.addPlan((c2) -> { - }, 1); - c.addPlan((c2) -> { - }, 2); - c.addPlan((c2) -> { - }, 3); - c.subscribeToSimulationClose((c2) -> { - simCloseEventHandled.setValue(true); - }); - })); - - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - Simulation .builder()// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that the subscription to simulation close was successful - assertTrue(simCloseEventHandled.getValue()); - - } - - @Test - @UnitTestMethod(name = "unsubscribe", args = { Class.class }) - public void testUnsubscribe_EventClass() { - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - // create some times for the resolver to generate events - List eventGenerationTimes = new ArrayList<>(); - eventGenerationTimes.add(1.0); - eventGenerationTimes.add(2.0); - eventGenerationTimes.add(3.0); - eventGenerationTimes.add(4.0); - eventGenerationTimes.add(5.0); - eventGenerationTimes.add(6.0); - eventGenerationTimes.add(7.0); - eventGenerationTimes.add(8.0); - eventGenerationTimes.add(9.0); - - /* - * At time 0, have the test resolver generate plans to generate events - * at various times - */ - - pluginDataBuilder.addTestActorPlan("generator", new TestActorPlan(0, (c) -> { - - for (Double time : eventGenerationTimes) { - c.addPlan((c2) -> { - c2.releaseEvent(new BaseEvent()); - }, time); - } - })); - - /* - * precondition tests -- have the first actor test all the precondition - * exceptions - */ - pluginDataBuilder.addTestActorPlan("precondition tester", new TestActorPlan(0, (context) -> { - - // if the Event class is null - Class eventClass = null; - ContractException contractException = assertThrows(ContractException.class, () -> context.unsubscribe(eventClass)); - assertEquals(NucleusError.NULL_EVENT_CLASS, contractException.getErrorType()); - - })); - - // create a container for the events that are received by the three - // actors - Set recievedEvents = new LinkedHashSet<>(); - - // have the Alpha actor subscribe to the Test Event at time 0 - pluginDataBuilder.addTestActorPlan("Alpha", new TestActorPlan(0.1, (context) -> { - context.subscribe(BaseEvent.class, (c, e) -> { - recievedEvents.add(new MultiKey("Alpha", c.getTime())); - }); - })); - - // have the Alpha actor unsubscribe to the Test Event at time 5 - pluginDataBuilder.addTestActorPlan("Alpha", new TestActorPlan(5.1, (context) -> { - context.unsubscribe(BaseEvent.class); - })); - - // have the Beta actor subscribe to the Test Event at time 4 - pluginDataBuilder.addTestActorPlan("Beta", new TestActorPlan(4.1, (context) -> { - context.subscribe(BaseEvent.class, (c, e) -> { - recievedEvents.add(new MultiKey("Beta", c.getTime())); - }); - })); - - // have the Beta actor unsubscribe to the Test Event at time 8 - pluginDataBuilder.addTestActorPlan("Beta", new TestActorPlan(8.1, (context) -> { - - context.unsubscribe(BaseEvent.class); - })); - - // have the Gamma actor subscribe to the Test Event at time 6 - pluginDataBuilder.addTestActorPlan("Gamma", new TestActorPlan(6.1, (context) -> { - context.subscribe(BaseEvent.class, (c, e) -> { - recievedEvents.add(new MultiKey("Gamma", c.getTime())); - }); - })); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - - // run the simulation - Simulation .builder()// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput)// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that all action plans were executed - assertTrue(scenarioPlanCompletionObserver.allPlansExecuted()); - - // show that all and only the observations corresponding to the - // subscribed event label were delivered to the actors - Set expectedEvents = new LinkedHashSet<>(); - expectedEvents.add(new MultiKey("Alpha", 1.0)); - expectedEvents.add(new MultiKey("Alpha", 2.0)); - expectedEvents.add(new MultiKey("Alpha", 3.0)); - expectedEvents.add(new MultiKey("Alpha", 4.0)); - expectedEvents.add(new MultiKey("Alpha", 5.0)); - expectedEvents.add(new MultiKey("Beta", 5.0)); - expectedEvents.add(new MultiKey("Beta", 6.0)); - expectedEvents.add(new MultiKey("Beta", 7.0)); - expectedEvents.add(new MultiKey("Beta", 8.0)); - expectedEvents.add(new MultiKey("Gamma", 7.0)); - expectedEvents.add(new MultiKey("Gamma", 8.0)); - expectedEvents.add(new MultiKey("Gamma", 9.0)); - - // show that the expected and actual event records are the same - assertEquals(expectedEvents, recievedEvents); - } - - /** - * Tests {@link AgentContext#unsubscribe(EventLabel) - */ - @Test - @UnitTestMethod(name = "unsubscribe", args = { EventLabel.class }) - public void testUnsubscribe_EventLabel() { - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - /* - * Generate an event label that will match all TestEvents. This will be - * used throughout. - */ - EventLabel eventLabel = EventLabel .builder(BaseEvent.class)// - .setEventLabelerId(Local_Labeler_ID.TEST_LABELER_ID)// - .addKey(BaseEvent.class)// - .build();// - - // create some times for the resolver to generate events - List eventGenerationTimes = new ArrayList<>(); - eventGenerationTimes.add(1.0); - eventGenerationTimes.add(2.0); - eventGenerationTimes.add(3.0); - eventGenerationTimes.add(4.0); - eventGenerationTimes.add(5.0); - eventGenerationTimes.add(6.0); - eventGenerationTimes.add(7.0); - eventGenerationTimes.add(8.0); - eventGenerationTimes.add(9.0); - - /* - * At time 0, have the test resolver generate plans to generate events - * at various times - */ - - pluginDataBuilder.addTestActorPlan("generator", new TestActorPlan(0, (c) -> { - /* - * Add the required event labeler -- we want all TestEvents to be - * passed to all actor subscribers so that we can demonstrate that - * unsubscribing works without complicating the test with filtering - */ - EventLabeler eventLabeler = EventLabeler .builder(BaseEvent.class)// - .setEventLabelerId(Local_Labeler_ID.TEST_LABELER_ID)// - .setLabelFunction((c2, e) -> { - return eventLabel; - })// - .build(); - c.addEventLabeler(eventLabeler); - - for (Double time : eventGenerationTimes) { - c.addPlan((c2) -> { - c2.releaseEvent(new BaseEvent()); - }, time); - } - })); - - /* - * precondition tests -- have the first actor test all the precondition - * exceptions - */ - pluginDataBuilder.addTestActorPlan("precondition tester", new TestActorPlan(0, (context) -> { - - // if the EventLabel is null - EventLabel nullEventLabel = null; - ContractException contractException = assertThrows(ContractException.class, () -> context.unsubscribe(nullEventLabel)); - assertEquals(NucleusError.NULL_EVENT_LABEL, contractException.getErrorType()); - - // if the event labeler id in the event label cannot be resolved to - // a registered event labeler - - EventLabel unknownEventLabel = EventLabel.builder(BaseEvent.class)// - .setEventLabelerId(Local_Labeler_ID.DATA_CHANGE)// - .addKey(BaseEvent.class)// - .build();// - - contractException = assertThrows(ContractException.class, () -> context.unsubscribe(unknownEventLabel)); - assertEquals(NucleusError.UNKNOWN_EVENT_LABELER, contractException.getErrorType()); - - })); - - // create a container for the events that are received by the three - // actors - Set recievedEvents = new LinkedHashSet<>(); - - // have the Alpha actor subscribe to the Test Event at time 0 - pluginDataBuilder.addTestActorPlan("Alpha", new TestActorPlan(0.1, (context) -> { - context.subscribe(eventLabel, (c, e) -> { - recievedEvents.add(new MultiKey("Alpha", c.getTime())); - }); - })); - - // have the Alpha actor unsubscribe to the Test Event at time 5 - pluginDataBuilder.addTestActorPlan("Alpha", new TestActorPlan(5.1, (context) -> { - context.unsubscribe(eventLabel); - })); - - // have the Beta actor subscribe to the Test Event at time 4 - pluginDataBuilder.addTestActorPlan("Beta", new TestActorPlan(4.1, (context) -> { - context.subscribe(eventLabel, (c, e) -> { - recievedEvents.add(new MultiKey("Beta", c.getTime())); - }); - })); - - // have the Beta actor unsubscribe to the Test Event at time 8 - pluginDataBuilder.addTestActorPlan("Beta", new TestActorPlan(8.1, (context) -> { - context.unsubscribe(eventLabel); - })); - - // have the Gamma actor subscribe to the Test Event at time 6 - pluginDataBuilder.addTestActorPlan("Gamma", new TestActorPlan(6.1, (context) -> { - context.subscribe(eventLabel, (c, e) -> { - recievedEvents.add(new MultiKey("Gamma", c.getTime())); - }); - })); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - - // run the simulation - Simulation .builder()// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput)// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that all action plans were executed - assertTrue(scenarioPlanCompletionObserver.allPlansExecuted()); - - // show that all and only the observations corresponding to the - // subscribed event label were delivered to the actors - Set expectedEvents = new LinkedHashSet<>(); - expectedEvents.add(new MultiKey("Alpha", 1.0)); - expectedEvents.add(new MultiKey("Alpha", 2.0)); - expectedEvents.add(new MultiKey("Alpha", 3.0)); - expectedEvents.add(new MultiKey("Alpha", 4.0)); - expectedEvents.add(new MultiKey("Alpha", 5.0)); - expectedEvents.add(new MultiKey("Beta", 5.0)); - expectedEvents.add(new MultiKey("Beta", 6.0)); - expectedEvents.add(new MultiKey("Beta", 7.0)); - expectedEvents.add(new MultiKey("Beta", 8.0)); - expectedEvents.add(new MultiKey("Gamma", 7.0)); - expectedEvents.add(new MultiKey("Gamma", 8.0)); - expectedEvents.add(new MultiKey("Gamma", 9.0)); - - // show that the expected and actual event records are the same - assertEquals(expectedEvents, recievedEvents); - - } - -} \ No newline at end of file diff --git a/gcm3/src/test/java/nucleus/AT_ActorId.java b/gcm3/src/test/java/nucleus/AT_ActorId.java deleted file mode 100644 index 8d679691f..000000000 --- a/gcm3/src/test/java/nucleus/AT_ActorId.java +++ /dev/null @@ -1,71 +0,0 @@ -package nucleus; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = ActorId.class) -public final class AT_ActorId { - - @UnitTestMethod(name = "getValue", args = {}) - @Test - public void testGetValue() { - for (int i = 0; i < 100; i++) { - assertEquals(i, new ActorId(i).getValue()); - } - } - - @UnitTestMethod(name = "toString", args = {}) - @Test - public void testToString() { - for (int i = 0; i < 100; i++) { - assertEquals("ActorId [id=" + i + "]", new ActorId(i).toString()); - } - } - - @UnitTestMethod(name = "hashCode", args = {}) - @Test - public void testHashCode() { - // show equal objects have equal hashcodes - for (int i = 0; i < 10; i++) { - ActorId a = new ActorId(i); - ActorId b = new ActorId(i); - assertEquals(a, b); - assertEquals(a.hashCode(), b.hashCode()); - } - - //show that hash codes are dispersed - Set hashcodes = new LinkedHashSet<>(); - for (int i = 0; i < 1000; i++) { - hashcodes.add(new ActorId(i).hashCode()); - } - assertEquals(1000, hashcodes.size()); - - } - - @UnitTestMethod(name = "equals", args = { Object.class }) - @Test - public void testEquals() { - // show actor ids are equal if and only if they have the same base int - // value - for (int i = 0; i < 10; i++) { - ActorId a = new ActorId(i); - for (int j = 0; j < 10; j++) { - ActorId b = new ActorId(j); - if (i == j) { - assertEquals(a, b); - } else { - assertNotEquals(a, b); - } - } - } - } - -} diff --git a/gcm3/src/test/java/nucleus/AT_DataManager.java b/gcm3/src/test/java/nucleus/AT_DataManager.java deleted file mode 100644 index 29cd0dbe8..000000000 --- a/gcm3/src/test/java/nucleus/AT_DataManager.java +++ /dev/null @@ -1,13 +0,0 @@ -package nucleus; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - -@UnitTest(target = DataManager.class) -public class AT_DataManager { - @Test - public void test() { - // nothing to test - } -} diff --git a/gcm3/src/test/java/nucleus/AT_DataManagerContext.java b/gcm3/src/test/java/nucleus/AT_DataManagerContext.java deleted file mode 100644 index 53cd57cc3..000000000 --- a/gcm3/src/test/java/nucleus/AT_DataManagerContext.java +++ /dev/null @@ -1,1400 +0,0 @@ -package nucleus; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; - -import nucleus.testsupport.testplugin.ScenarioPlanCompletionObserver; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestDataManager; -import nucleus.testsupport.testplugin.TestDataManagerPlan; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import nucleus.testsupport.testplugin.TestScenarioReport; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; -import util.wrappers.MutableBoolean; -import util.wrappers.MutableInteger; - -@UnitTest(target = DataManagerContext.class) - -public class AT_DataManagerContext { - - @Test - @UnitTestMethod(name = "getTime", args = {}) - public void testGetTime() { - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - Set planTimes = new LinkedHashSet<>(); - - planTimes.add(4.6); - planTimes.add(13.8764); - planTimes.add(554.345); - planTimes.add(7.95346); - planTimes.add(400.234234); - planTimes.add(3000.12422346); - - /* - * Have the data manager build plans to check the time in the simulation - * against the planning time - */ - pluginDataBuilder.addTestDataManager("dm", () -> new TestDataManager1()); - pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(0, (context1) -> { - for (Double planTime : planTimes) { - context1.addPlan((context2) -> { - assertEquals(planTime.doubleValue(), context2.getTime(), 0); - }, planTime); - } - })); - - // build the action plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - - // execute the engine - Simulation .builder()// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput)// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that the action was executed - assertTrue(scenarioPlanCompletionObserver.allPlansExecuted()); - - } - - @Test - @UnitTestMethod(name = "releaseOutput", args = { Object.class }) - public void testReleaseOutput() { - - // begin building the action plugin - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - // set up the expected output - Set expectedOutput = new LinkedHashSet<>(); - expectedOutput.add("the sly fox"); - expectedOutput.add(15); - expectedOutput.add("the lazy, brown dog"); - expectedOutput.add(45.34513453); - - // have the data manager release the output - pluginDataBuilder.addTestDataManager("dm", () -> new TestDataManager1()); - pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(1, (c) -> { - for (Object outputValue : expectedOutput) { - c.releaseOutput(outputValue); - } - })); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - Set actualOutput = new LinkedHashSet<>(); - - /* - * Add an output consumer that will place the output into the - * actualOutput set above and then execute the simulation - */ - Simulation .builder()// - .addPlugin(testPlugin)// - .setOutputConsumer((o) -> { - if (!(o instanceof TestScenarioReport)) { - actualOutput.add(o); - } - })// - .build()// - .execute();// - - // show that the output matches expectations - assertEquals(expectedOutput, actualOutput); - - } - - @Test - @UnitTestMethod(name = "subscribeToSimulationClose", args = { Consumer.class }) - public void testSubscribeToSimulationClose() { - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - MutableBoolean simCloseEventHandled = new MutableBoolean(); - - // have a data manager schedule a few events and subscribe to simulation - // close - pluginDataBuilder.addTestDataManager("dm", () -> new TestDataManager1()); - pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(0, (c) -> { - c.addPlan((c2) -> { - }, 1); - c.addPlan((c2) -> { - }, 2); - c.addPlan((c2) -> { - }, 3); - c.subscribeToSimulationClose((c2) -> { - simCloseEventHandled.setValue(true); - }); - })); - - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - Simulation .builder()// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that the subscription to simulation close was successful - assertTrue(simCloseEventHandled.getValue()); - - } - - @Test - @UnitTestMethod(name = "getDataManager", args = { Class.class }) - public void testGetDataManager() { - - // create the test plugin data builder - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - // create a data manager for the actor to find - - pluginDataBuilder.addTestDataManager("dm1", () -> new TestDataManager1()); - pluginDataBuilder.addTestDataManager("dm3A", () -> new TestDataManager3A()); - pluginDataBuilder.addTestDataManager("dm3B", () -> new TestDataManager3B()); - pluginDataBuilder.addTestDataManager("dm4A", () -> new TestDataManager4A()); - - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0,(c)->{ - TestDataManager1 testDataManager1 = c.getDataManager(TestDataManager1.class); - assertNotNull(testDataManager1); - - TestDataManager3A testDataManager3A = c.getDataManager(TestDataManager3A.class); - assertNotNull(testDataManager3A); - - TestDataManager3B testDataManager3B = c.getDataManager(TestDataManager3B.class); - assertNotNull(testDataManager3B); - - TestDataManager4A testDataManager4A = c.getDataManager(TestDataManager4A.class); - assertNotNull(testDataManager4A); - - - - })); - - - // build the action plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - - - - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - - // execute the engine - Simulation .builder()// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput)// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that the action was executed - assertTrue(scenarioPlanCompletionObserver.allPlansExecuted()); - - // Precondition test 1 - - pluginDataBuilder.addTestDataManager("dm3A", () -> new TestDataManager3A()); - pluginDataBuilder.addTestDataManager("dm3B", () -> new TestDataManager3B()); - - pluginDataBuilder.addTestDataManagerPlan("dm3A", new TestDataManagerPlan(4, (c) -> { - ContractException contractException = assertThrows(ContractException.class, () -> c.getDataManager(TestDataManager3.class)); - assertEquals(NucleusError.AMBIGUOUS_DATA_MANAGER_CLASS, contractException.getErrorType()); - })); - - // build the action plugin - testPluginData = pluginDataBuilder.build(); - testPlugin = TestPlugin.getTestPlugin(testPluginData); - - scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - - // execute the engine - Simulation .builder()// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput)// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that the action was executed - assertTrue(scenarioPlanCompletionObserver.allPlansExecuted()); - - // Precondition test 2 - - pluginDataBuilder.addTestDataManager("dm3B", () -> new TestDataManager3B()); - - pluginDataBuilder.addTestDataManagerPlan("dm3B", new TestDataManagerPlan(4, (c) -> { - ContractException contractException = assertThrows(ContractException.class, () -> c.getDataManager(null)); - assertEquals(NucleusError.NULL_DATA_MANAGER_CLASS, contractException.getErrorType()); - })); - - // build the action plugin - testPluginData = pluginDataBuilder.build(); - testPlugin = TestPlugin.getTestPlugin(testPluginData); - - scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - - // execute the engine - Simulation .builder()// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput)// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that the action was executed - assertTrue(scenarioPlanCompletionObserver.allPlansExecuted()); - - } - - @Test - @UnitTestMethod(name = "getDataManagerId", args = {}) - public void testGetDataManagerId() { - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - Set observedDataManagerIds = new LinkedHashSet<>(); - - pluginDataBuilder.addTestDataManager("dm1", () -> new TestDataManager1()); - pluginDataBuilder.addTestDataManagerPlan("dm1", new TestDataManagerPlan(0, (context) -> { - observedDataManagerIds.add(context.getDataManagerId()); - })); - - pluginDataBuilder.addTestDataManager("dm2", () -> new TestDataManager2()); - pluginDataBuilder.addTestDataManagerPlan("dm2", new TestDataManagerPlan(1, (context) -> { - observedDataManagerIds.add(context.getDataManagerId()); - })); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - // run the simulation - Simulation .builder()// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput)// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that the action plans got executed - assertTrue(scenarioPlanCompletionObserver.allPlansExecuted()); - - // show that each data manger has a distinct id - assertEquals(2, observedDataManagerIds.size()); - } - - @Test - @UnitTestMethod(name = "addPlan", args = { Consumer.class, double.class }) - public void testAddPlan() { - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - // test preconditions - pluginDataBuilder.addTestDataManager("dm", () -> new TestDataManager1()); - pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(1, (context) -> { - double scheduledTime = context.getTime() + 1; - - ContractException contractException = assertThrows(ContractException.class, () -> context.addPlan(null, scheduledTime)); - assertEquals(NucleusError.NULL_PLAN, contractException.getErrorType()); - - contractException = assertThrows(ContractException.class, () -> context.addPlan((c) -> { - }, 0)); - assertEquals(NucleusError.PAST_PLANNING_TIME, contractException.getErrorType()); - - })); - - /* - * Have the actor add a plan and show that that plan executes - */ - - MutableBoolean planExecuted = new MutableBoolean(); - - pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(4, (context) -> { - // schedule two passive plans - context.addPlan((c) -> { - planExecuted.setValue(true); - }, 5); - })); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - // run the simulation - Simulation .builder()// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // we do not need to show that all plans executed - - // show that the last two passive plans did not execute - assertTrue(planExecuted.getValue()); - - } - - @Test - @UnitTestMethod(name = "addPassivePlan", args = { Consumer.class, double.class }) - public void testAddPassivePlan() { - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - pluginDataBuilder.addTestDataManager("dm", () -> new TestDataManager1()); - - // test preconditions - pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(1, (context) -> { - double scheduledTime = context.getTime() + 1; - - ContractException contractException = assertThrows(ContractException.class, () -> context.addPassivePlan(null, scheduledTime)); - assertEquals(NucleusError.NULL_PLAN, contractException.getErrorType()); - - contractException = assertThrows(ContractException.class, () -> context.addPassivePlan((c) -> { - }, 0)); - assertEquals(NucleusError.PAST_PLANNING_TIME, contractException.getErrorType()); - - })); - - /* - * Show that passive plans do not execute if there are no remaining - * active plans. To do this, we will schedule a few passive plans, one - * active plan and then a few more passive plans. We will then show that - * the passive plans that come after the last active plan never execute - */ - - // create some containers for passive keys - Set expectedPassiveValues = new LinkedHashSet<>(); - expectedPassiveValues.add(1); - expectedPassiveValues.add(2); - Set actualPassiveValues = new LinkedHashSet<>(); - - pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(4, (context) -> { - - // schedule two passive plans - context.addPassivePlan((c) -> { - actualPassiveValues.add(1); - }, 5); - context.addPassivePlan((c) -> { - actualPassiveValues.add(2); - }, 6); - - // schedule the last active plan - context.addPlan((c) -> { - }, 7); - - // schedule two more passive plans - context.addPassivePlan((c) -> { - actualPassiveValues.add(3); - }, 8); - context.addPassivePlan((c) -> { - actualPassiveValues.add(4); - }, 9); - - })); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - // run the simulation - Simulation .builder()// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // we do not need to show that all plans executed - - // show that the last two passive plans did not execute - assertEquals(expectedPassiveValues, actualPassiveValues); - } - - @Test - @UnitTestMethod(name = "addPassiveKeyedPlan", args = { Consumer.class, double.class, Object.class }) - public void testAddPassiveKeyedPlan() { - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - // test preconditions - pluginDataBuilder.addTestDataManager("dm", () -> new TestDataManager1()); - pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(1, (context) -> { - Object key = new Object(); - - double scheduledTime = context.getTime() + 1; - - ContractException contractException = assertThrows(ContractException.class, () -> context.addPassiveKeyedPlan(null, scheduledTime, key)); - assertEquals(NucleusError.NULL_PLAN, contractException.getErrorType()); - - contractException = assertThrows(ContractException.class, () -> context.addPassiveKeyedPlan((c) -> { - }, 0, key)); - assertEquals(NucleusError.PAST_PLANNING_TIME, contractException.getErrorType()); - - contractException = assertThrows(ContractException.class, () -> context.addPassiveKeyedPlan((c) -> { - }, scheduledTime, null)); - assertEquals(NucleusError.NULL_PLAN_KEY, contractException.getErrorType()); - - context.addPassiveKeyedPlan((c) -> { - }, scheduledTime, key); - - contractException = assertThrows(ContractException.class, () -> context.addPassiveKeyedPlan((c) -> { - }, scheduledTime, key)); - assertEquals(NucleusError.DUPLICATE_PLAN_KEY, contractException.getErrorType()); - - })); - - /* - * have the added test agent add a plan that can be retrieved and thus - * was added successfully - */ - pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(2, (context) -> { - Object key = new Object(); - - assertFalse(context.getPlan(key).isPresent()); - - context.addPassiveKeyedPlan((c) -> { - }, 3, key); - - assertTrue(context.getPlan(key).isPresent()); - })); - - /* - * Show that passive plans do not execute if there are no remaining - * active plans. To do this, we will schedule a few passive plans, one - * active plan and then a few more passive plans. We will then show that - * the passive plans that come after the last active plan never execute - */ - - // create some containers for passive keys - Set expectedPassiveKeys = new LinkedHashSet<>(); - expectedPassiveKeys.add(1); - expectedPassiveKeys.add(2); - Set actualPassiveKeys = new LinkedHashSet<>(); - - pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(4, (context) -> { - - // schedule two passive plans - context.addPassiveKeyedPlan((c) -> { - actualPassiveKeys.add(1); - }, 5, 1); - context.addPassiveKeyedPlan((c) -> { - actualPassiveKeys.add(2); - }, 6, 2); - - // schedule the last active plan - context.addPlan((c) -> { - }, 7); - - // schedule two more passive plans - context.addPassiveKeyedPlan((c) -> { - actualPassiveKeys.add(3); - }, 8, 3); - context.addPassiveKeyedPlan((c) -> { - actualPassiveKeys.add(4); - }, 9, 4); - - })); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - // run the simulation - Simulation .builder()// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // we do not need to show that all plans executed - - // show that the last two passive plans did not execute - assertEquals(expectedPassiveKeys, actualPassiveKeys); - - } - - @Test - @UnitTestMethod(name = "addKeyedPlan", args = { Consumer.class, double.class, Object.class }) - public void testAddKeyedPlan() { - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - // test preconditions - pluginDataBuilder.addTestDataManager("dm", () -> new TestDataManager1()); - pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(1, (context) -> { - Object key = new Object(); - - double scheduledTime = context.getTime() + 1; - - ContractException contractException = assertThrows(ContractException.class, () -> context.addKeyedPlan(null, scheduledTime, key)); - assertEquals(NucleusError.NULL_PLAN, contractException.getErrorType()); - - contractException = assertThrows(ContractException.class, () -> context.addKeyedPlan((c) -> { - }, 0, key)); - assertEquals(NucleusError.PAST_PLANNING_TIME, contractException.getErrorType()); - - contractException = assertThrows(ContractException.class, () -> context.addKeyedPlan((c) -> { - }, scheduledTime, null)); - assertEquals(NucleusError.NULL_PLAN_KEY, contractException.getErrorType()); - - context.addKeyedPlan((c) -> { - }, scheduledTime, key); - - contractException = assertThrows(ContractException.class, () -> context.addKeyedPlan((c) -> { - }, scheduledTime, key)); - assertEquals(NucleusError.DUPLICATE_PLAN_KEY, contractException.getErrorType()); - - })); - - /* - * have the added test agent add a plan that can be retrieved and thus - * was added successfully - */ - pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(2, (context) -> { - Object key = new Object(); - assertFalse(context.getPlan(key).isPresent()); - context.addKeyedPlan((c) -> { - }, 100, key); - assertTrue(context.getPlan(key).isPresent()); - })); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - - // run the simulation - Simulation .builder()// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput)// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that the action plans got executed - assertTrue(scenarioPlanCompletionObserver.allPlansExecuted()); - - } - - @Test - @UnitTestMethod(name = "getPlan", args = { Object.class }) - public void testGetPlan() { - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - // test preconditions - pluginDataBuilder.addTestDataManager("dm", () -> new TestDataManager1()); - pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(1, (context) -> { - ContractException contractException = assertThrows(ContractException.class, () -> context.getPlan(null)); - assertEquals(NucleusError.NULL_PLAN_KEY, contractException.getErrorType()); - })); - - /* - * have the added test agent add a plan that can be retrieved and thus - * was added successfully - */ - pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(2, (context) -> { - Object key = new Object(); - assertFalse(context.getPlan(key).isPresent()); - context.addKeyedPlan((c) -> { - }, 100, key); - assertTrue(context.getPlan(key).isPresent()); - })); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - - // run the simulation - Simulation .builder()// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput)// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that the action plans got executed - assertTrue(scenarioPlanCompletionObserver.allPlansExecuted()); - - } - - @Test - @UnitTestMethod(name = "getPlanTime", args = { Object.class }) - public void testGetPlanTime() { - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - // test preconditions - pluginDataBuilder.addTestDataManager("dm", () -> new TestDataManager1()); - pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(1, (context) -> { - ContractException contractException = assertThrows(ContractException.class, () -> context.getPlanTime(null)); - assertEquals(NucleusError.NULL_PLAN_KEY, contractException.getErrorType()); - })); - - /* - * have the added test agent add a plan and show that the plan time is - * as expected - */ - pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(2, (context) -> { - Object key = new Object(); - assertFalse(context.getPlanTime(key).isPresent()); - double expectedPlanTime = 100; - context.addKeyedPlan((c) -> { - }, expectedPlanTime, key); - assertTrue(context.getPlanTime(key).isPresent()); - Double actualPlanTime = context.getPlanTime(key).get(); - assertEquals(expectedPlanTime, actualPlanTime, 0); - })); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - // run the simulation - Simulation .builder()// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput)// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that the action plans got executed - assertTrue(scenarioPlanCompletionObserver.allPlansExecuted()); - - } - - @Test - @UnitTestMethod(name = "removePlan", args = { Object.class }) - public void testRemovePlan() { - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - // test preconditions - pluginDataBuilder.addTestDataManager("dm", () -> new TestDataManager1()); - pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(1, (context) -> { - ContractException contractException = assertThrows(ContractException.class, () -> context.removePlan(null)); - assertEquals(NucleusError.NULL_PLAN_KEY, contractException.getErrorType()); - })); - - Object key = new Object(); - MutableBoolean removedPlanHasExecuted = new MutableBoolean(); - - // have the added test agent add a plan - pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(2, (context) -> { - context.addKeyedPlan((c2) -> { - removedPlanHasExecuted.setValue(true); - }, 4, key); - })); - - // have the test agent remove the plan and show the plan no longer - // exists - pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(3, (context) -> { - assertTrue(context.getPlan(key).isPresent()); - - context.removePlan(key); - - assertFalse(context.getPlan(key).isPresent()); - - })); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - - // run the simulation - Simulation .builder()// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput)// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that the action plans got executed - assertTrue(scenarioPlanCompletionObserver.allPlansExecuted()); - - // show that the remove plan was not executed - assertFalse(removedPlanHasExecuted.getValue()); - } - - @Test - @UnitTestMethod(name = "getPlanKeys", args = {}) - public void testGetPlanKeys() { - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - // There are no precondition tests - Set expectedKeys = new LinkedHashSet<>(); - int keyCount = 20; - for (int i = 0; i < keyCount; i++) { - expectedKeys.add(new Object()); - } - - // have the test agent add some plans - pluginDataBuilder.addTestDataManager("dm", () -> new TestDataManager1()); - pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(1, (context) -> { - for (Object key : expectedKeys) { - context.addKeyedPlan((c) -> { - }, 100, key); - } - - Set actualKeys = context.getPlanKeys().stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expectedKeys, actualKeys); - - })); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - - // run the simulation - Simulation .builder()// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput)// - .addPlugin(testPlugin)// - .build().execute();// - - // show that the action plans got executed - assertTrue(scenarioPlanCompletionObserver.allPlansExecuted()); - } - - @Test - @UnitTestMethod(name = "releaseEvent", args = { Event.class }) - public void testReleaseEvent() { - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - MutableBoolean eventResolved = new MutableBoolean(); - - // Have the data manager subscribe to test event and then set the - // eventResolved to true - pluginDataBuilder.addTestDataManager("dm1", () -> new TestDataManager1()); - pluginDataBuilder.addTestDataManagerPlan("dm1", new TestDataManagerPlan(0, (c) -> { - c.subscribe(TestEvent.class, (c2, e) -> { - eventResolved.setValue(true); - }); - })); - - // have another data manager resolve a test event - pluginDataBuilder.addTestDataManager("dm2", () -> new TestDataManager2()); - pluginDataBuilder.addTestDataManagerPlan("dm2", new TestDataManagerPlan(1, (context) -> { - context.releaseEvent(new TestEvent()); - })); - - // precondition tests - pluginDataBuilder.addTestDataManagerPlan("dm1", new TestDataManagerPlan(1, (context) -> { - ContractException contractException = assertThrows(ContractException.class, () -> context.releaseEvent(null)); - assertEquals(NucleusError.NULL_EVENT, contractException.getErrorType()); - })); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - // run the simulation - Simulation .builder()// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that event actually resolved - assertTrue(eventResolved.getValue()); - - } - - private static class TestEvent implements Event { - - } - - private static class TestDataManager1 extends TestDataManager { - } - - private static class TestDataManager2 extends TestDataManager { - } - - private static class TestDataManager3 extends TestDataManager { - - } - - private static class TestDataManager3A extends TestDataManager3 { - - } - - private static class TestDataManager3B extends TestDataManager3 { - - } - - private static class TestDataManager4 extends TestDataManager { - - } - - private static class TestDataManager4A extends TestDataManager4 { - - } - - @Test - @UnitTestMethod(name = "actorExists", args = { ActorId.class }) - public void testActorExists() { - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - double testTime = 1; - // there are no precondition tests - - pluginDataBuilder.addTestDataManager("dm", () -> new TestDataManager1()); - - // have the test agent show it exists and that other agents do not - pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(testTime++, (context) -> { - for (int i = 0; i < 3; i++) { - ActorId actorId = new ActorId(i); - assertFalse(context.actorExists(actorId)); - } - for (int i = 0; i < 3; i++) { - ActorId actorId = context.addActor((c) -> { - }); - assertTrue(context.actorExists(actorId)); - } - - })); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - - // run the simulation - Simulation .builder()// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput)// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that the action plans got executed - assertTrue(scenarioPlanCompletionObserver.allPlansExecuted()); - - } - - @Test - - @UnitTestMethod(name = "addActor", args = { Consumer.class }) - public void testAddActor() { - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - MutableBoolean actorWasAdded = new MutableBoolean(); - - // there are no precondition tests - - // have the test agent show it exists and that other agents do not - pluginDataBuilder.addTestDataManager("dm", () -> new TestDataManager1()); - pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(1, (c) -> { - c.addActor((c2) -> actorWasAdded.setValue(true)); - })); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - // run the simulation - Simulation .builder()// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that the action plans got executed - assertTrue(actorWasAdded.getValue()); - - } - - @Test - @UnitTestMethod(name = "removeActor", args = { ActorId.class }) - public void testRemoveActor() { - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - // have the resolver execute the precondition tests - pluginDataBuilder.addTestDataManager("dm", () -> new TestDataManager1()); - pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(0, (c) -> { - - ContractException contractException = assertThrows(ContractException.class, () -> c.removeActor(null)); - assertEquals(NucleusError.NULL_ACTOR_ID, contractException.getErrorType()); - - contractException = assertThrows(ContractException.class, () -> c.removeActor(new ActorId(1000))); - assertEquals(NucleusError.UNKNOWN_ACTOR_ID, contractException.getErrorType()); - - })); - - List addedActorIds = new ArrayList<>(); - - // have the add a few agents - pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(1, (c) -> { - for (int i = 0; i < 10; i++) { - ActorId actorId = c.addActor((c2) -> { - }); - assertTrue(c.actorExists(actorId)); - addedActorIds.add(actorId); - } - })); - - // have the actor remove the added actors - pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(2, (c) -> { - for (ActorId actorId : addedActorIds) { - c.removeActor(actorId); - assertFalse(c.actorExists(actorId)); - } - })); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - - // build and execute the engine - Simulation .builder()// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput)// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that the actions were executed - assertTrue(scenarioPlanCompletionObserver.allPlansExecuted()); - } - - @Test - @UnitTestMethod(name = "halt", args = {}) - public void testHalt() { - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - // there are no precondition tests - - pluginDataBuilder.addTestDataManager("dm", () -> new TestDataManager1()); - - // have the test agent execute several tasks, with one of the tasks - // halting the simulation - TestDataManagerPlan plan1 = new TestDataManagerPlan(1, (context) -> { - }); - pluginDataBuilder.addTestDataManagerPlan("dm", plan1); - - TestDataManagerPlan plan2 = new TestDataManagerPlan(2, (context) -> { - }); - pluginDataBuilder.addTestDataManagerPlan("dm", plan2); - - TestDataManagerPlan plan3 = new TestDataManagerPlan(3, (context) -> { - context.halt(); - }); - pluginDataBuilder.addTestDataManagerPlan("dm", plan3); - - TestDataManagerPlan plan4 = new TestDataManagerPlan(4, (context) -> { - }); - pluginDataBuilder.addTestDataManagerPlan("dm", plan4); - - TestDataManagerPlan plan5 = new TestDataManagerPlan(5, (context) -> { - }); - pluginDataBuilder.addTestDataManagerPlan("dm", plan5); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - // run the simulation - Simulation .builder()// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that the plans that were scheduled after the halt did not - // execute - assertTrue(plan1.executed()); - assertTrue(plan2.executed()); - assertTrue(plan3.executed()); - assertFalse(plan4.executed()); - assertFalse(plan5.executed()); - - } - - private void combinedSubscriptionTest() { - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - /* - * create a container that will record the phases of event resolution - * that were executed by the resolver that reflects the order in which - * they occured - */ - - List observedPhases = new ArrayList<>(); - - /* - * Create a container with the phases we expect in the order we expect - * them. - */ - List expectedPhases = new ArrayList<>(); - expectedPhases.add("execution"); - expectedPhases.add("post-action"); - - // have the resolver test preconditions for all the phases - pluginDataBuilder.addTestDataManager("dm", () -> new TestDataManager1()); - pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(0, (c) -> { - - ContractException contractException = assertThrows(ContractException.class, () -> c.subscribe(null, (c2, e) -> { - })); - assertEquals(NucleusError.NULL_EVENT_CLASS, contractException.getErrorType()); - - contractException = assertThrows(ContractException.class, () -> c.subscribe(TestEvent.class, null)); - assertEquals(NucleusError.NULL_EVENT_CONSUMER, contractException.getErrorType()); - - contractException = assertThrows(ContractException.class, () -> c.subscribePostOrder(null, (c2, e) -> { - })); - assertEquals(NucleusError.NULL_EVENT_CLASS, contractException.getErrorType()); - - contractException = assertThrows(ContractException.class, () -> c.subscribePostOrder(TestEvent.class, null)); - assertEquals(NucleusError.NULL_EVENT_CONSUMER, contractException.getErrorType()); - - })); - - // have the resolver subscribe to the two phases for test events. - pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(0, (c) -> { - - c.subscribe(TestEvent.class, (c2, e) -> { - observedPhases.add("execution"); - }); - - c.subscribePostOrder(TestEvent.class, (c2, e) -> { - observedPhases.add("post-action"); - }); - })); - - // create an agent that will generate a test event - - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - c.releaseEvent(new TestEvent()); - })); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - // build and execute the engine - Simulation .builder()// - .addPlugin(testPlugin)// - .build()// - .execute();// - - /* - * show that the resolver engaged in the three event resolution phases - * in the proper order - */ - assertEquals(expectedPhases, observedPhases); - - } - - @Test - @UnitTestMethod(name = "subscribe", args = { Class.class, BiConsumer.class }) - public void testSubscribe() { - combinedSubscriptionTest(); - } - - @Test - @UnitTestMethod(name = "subscribeToEventPostPhase", args = { Class.class, BiConsumer.class }) - public void testSubscribeToEventPostPhase() { - combinedSubscriptionTest(); - } - - @Test - @UnitTestMethod(name = "unSubscribeToEvent", args = { Class.class }) - public void testUnSubscribeToEvent() { - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - // have the resolver test preconditions - pluginDataBuilder.addTestDataManager("dm", () -> new TestDataManager1()); - pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(0, (c) -> { - ContractException contractException = assertThrows(ContractException.class, () -> c.unSubscribe(null)); - assertEquals(NucleusError.NULL_EVENT_CLASS, contractException.getErrorType()); - })); - - /* - * Create a container to count then number of times a subscription - * execution occured - */ - MutableInteger phaseExecutionCount = new MutableInteger(); - - /* - * have the resolver subscribe to the test event and have it handle each - * type of event handling by incrementing a counter - */ - - pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(0, (c) -> { - - c.subscribe(TestEvent.class, (c2, e) -> { - phaseExecutionCount.increment(); - }); - - c.subscribePostOrder(TestEvent.class, (c2, e) -> { - phaseExecutionCount.increment(); - }); - - })); - - // create an agent that will produce a test event - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - c.releaseEvent(new TestEvent()); - })); - - /* - * Show that the phaseExecutionCount is three after the the agent is - * done - */ - pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(2, (c) -> { - assertEquals(2, phaseExecutionCount.getValue()); - })); - - // have the resolver unsubscribe - pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(3, (c) -> { - c.unSubscribe(TestEvent.class); - })); - - // have the agent generate another test event - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(4, (c) -> { - c.releaseEvent(new TestEvent()); - })); - - /* - * Show that the phaseExecutionCount is still three after the the agent - * is done and thus the resolver is no longer subscribed - */ - pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(5, (c) -> { - assertEquals(2, phaseExecutionCount.getValue()); - })); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - - // build and execute the engine - Simulation .builder()// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput)// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that all actions executed - assertTrue(scenarioPlanCompletionObserver.allPlansExecuted()); - } - - @Test - @UnitTestMethod(name = "addEventLabeler", args = { EventLabeler.class }) - public void testAddEventLabeler() { - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - // have the actor test the preconditions - pluginDataBuilder.addTestDataManager("dm", () -> new TestDataManager1()); - pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(0, (c) -> { - - EventLabelerId eventLabelerId = new EventLabelerId() { - }; - EventLabel multiKeyEventLabel = EventLabel .builder(TestEvent.class)// - .setEventLabelerId(eventLabelerId)// - .addKey(TestEvent.class)// - .build();// - - // if the event labeler is null - ContractException contractException = assertThrows(ContractException.class, () -> c.addEventLabeler(null)); - assertEquals(NucleusError.NULL_EVENT_LABELER, contractException.getErrorType()); - - /* - * if the event labeler contains a labeler id that is the id of a - * previously added event labeler - */ - c.addEventLabeler(EventLabeler.builder(TestEvent.class).setEventLabelerId(eventLabelerId).setLabelFunction((c2, e) -> multiKeyEventLabel).build()); - contractException = assertThrows(ContractException.class, - () -> c.addEventLabeler(EventLabeler.builder(TestEvent.class).setEventLabelerId(eventLabelerId).setLabelFunction((c2, e) -> multiKeyEventLabel).build())); - assertEquals(NucleusError.DUPLICATE_LABELER_ID_IN_EVENT_LABELER, contractException.getErrorType()); - - })); - - /* - * create a new event labeler that will be added by the resolver and the - * utilized by an agent. - */ - EventLabelerId id = new EventLabelerId() { - }; - - EventLabeler eventLabeler = EventLabeler .builder(TestEvent.class)// - .setEventLabelerId(id)// - .setLabelFunction((c, e) -> EventLabel .builder(TestEvent.class)// - .setEventLabelerId(id)// - .addKey(TestEvent.class)// - .build())// - .build(); - - // have the actor add the event labeler - pluginDataBuilder.addTestDataManagerPlan("dm", new TestDataManagerPlan(1, (c) -> { - c.addEventLabeler(eventLabeler); - })); - - /* - * Create a container for the agent to record that it received the Test - * Event and we can conclude that the event labeler had been properly - * added to the simulation. - */ - MutableBoolean eventObserved = new MutableBoolean(); - - // have the agent observe the test event - - pluginDataBuilder.addTestActorPlan("observer", new TestActorPlan(2, (c) -> { - c.subscribe(EventLabel .builder(TestEvent.class)// - .setEventLabelerId(id)// - .addKey(TestEvent.class).build(), - (c2, e) -> { - eventObserved.setValue(true); - }); - })); - - // have the actor create a test event for the agent to observe - pluginDataBuilder.addTestActorPlan("observer", new TestActorPlan(3, (c) -> { - c.releaseEvent(new TestEvent()); - })); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - - // build and execute the engine - Simulation .builder()// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput)// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that all plans were executed - assertTrue(scenarioPlanCompletionObserver.allPlansExecuted()); - - /* - * Show that the event labeler must have been added to the simulation - * since the agent observed the test event - */ - assertTrue(eventObserved.getValue()); - } - - @Test - @UnitTestMethod(name = "subscribersExist", args = { Class.class }) - public void testSubscribersExist() { - - // create an event labeler id - EventLabelerId eventLabelerId = new EventLabelerId() { - }; - - /* - * create a simple event label as a place holder -- all test events will - * be matched - */ - EventLabel eventLabel = EventLabel .builder(TestEvent.class)// - .setEventLabelerId(eventLabelerId)// - .addKey(TestEvent.class)// - .build();// - - // create an event labeler that always returns the label above - EventLabeler eventLabeler = EventLabeler .builder(TestEvent.class)// - .setEventLabelerId(eventLabelerId)// - .setLabelFunction((c2, e) -> { - return eventLabel; - }).build(); - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - // add the first data manager - - /* - * Have the test resolver show that there are initially no subscribers - * to test events. - */ - pluginDataBuilder.addTestDataManager("dm1", () -> new TestDataManager1()); - pluginDataBuilder.addTestDataManagerPlan("dm1", new TestDataManagerPlan(0, (c) -> { - assertFalse(c.subscribersExist(TestEvent.class)); - - // add the event labeler to the context - c.addEventLabeler(eventLabeler); - - })); - - // create an agent and have it subscribe to test events at time 1 - - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - - // subscribe to the event label - c.subscribe(eventLabel, (c2, e) -> { - }); - })); - - // show that the resolver now sees that there are subscribers - pluginDataBuilder.addTestDataManagerPlan("dm1", new TestDataManagerPlan(2, (c) -> { - assertTrue(c.subscribersExist(TestEvent.class)); - })); - - // have the agent unsubscribe - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(3, (c) -> { - c.unsubscribe(eventLabel); - })); - - // show that the resolver see no subscribers - pluginDataBuilder.addTestDataManagerPlan("dm1", new TestDataManagerPlan(4, (c) -> { - assertFalse(c.subscribersExist(TestEvent.class)); - })); - - // add a second data manager - - pluginDataBuilder.addTestDataManager("dm2", () -> new TestDataManager2()); - - pluginDataBuilder.addTestDataManagerPlan("dm2", new TestDataManagerPlan(5, (c) -> { - c.subscribe(TestEvent.class, (c2, e) -> { - }); - })); - - // show that the test resolver now sees that there are subscribers - pluginDataBuilder.addTestDataManagerPlan("dm1", new TestDataManagerPlan(6, (c) -> { - assertTrue(c.subscribersExist(TestEvent.class)); - })); - - // have the second data manager unsubscribe - pluginDataBuilder.addTestDataManagerPlan("dm2", new TestDataManagerPlan(7, (c) -> { - c.unSubscribe(TestEvent.class); - })); - - // show that dm1 now sees that there are no subscribers - pluginDataBuilder.addTestDataManagerPlan("dm1", new TestDataManagerPlan(8, (c) -> { - assertFalse(c.subscribersExist(TestEvent.class)); - })); - - // build the plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - - // build and execute the engine - Simulation .builder()// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput)// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that all actions were executed - assertTrue(scenarioPlanCompletionObserver.allPlansExecuted()); - } - -} diff --git a/gcm3/src/test/java/nucleus/AT_DataManagerId.java b/gcm3/src/test/java/nucleus/AT_DataManagerId.java deleted file mode 100644 index 8b21a18d8..000000000 --- a/gcm3/src/test/java/nucleus/AT_DataManagerId.java +++ /dev/null @@ -1,74 +0,0 @@ -package nucleus; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - - -@UnitTest(target = DataManagerId.class) -public final class AT_DataManagerId { - - @UnitTestMethod(name = "getValue", args = {}) - @Test - public void testGetValue() { - for (int i = 0; i < 100; i++) { - assertEquals(i, new DataManagerId(i).getValue()); - } - } - - - - @UnitTestMethod(name = "toString", args = {}) - @Test - public void testToString() { - for (int i = 0; i < 100; i++) { - assertEquals("DataManagerId [id=" + i + "]", new DataManagerId(i).toString()); - } - } - - @UnitTestMethod(name = "hashCode", args = {}) - @Test - public void testHashCode() { - // show equal objects have equal hashcodes - for (int i = 0; i < 10; i++) { - DataManagerId a = new DataManagerId(i); - DataManagerId b = new DataManagerId(i); - assertEquals(a, b); - assertEquals(a.hashCode(), b.hashCode()); - } - - //show that hash codes are dispersed - Set hashcodes = new LinkedHashSet<>(); - for (int i = 0; i < 1000; i++) { - hashcodes.add(new DataManagerId(i).hashCode()); - } - assertEquals(1000, hashcodes.size()); - - } - - @UnitTestMethod(name = "equals", args = { Object.class }) - @Test - public void testEquals() { - // show data manager ids are equal if and only if they have the same base int - // value - for (int i = 0; i < 10; i++) { - DataManagerId a = new DataManagerId(i); - for (int j = 0; j < 10; j++) { - DataManagerId b = new DataManagerId(j); - if (i == j) { - assertEquals(a, b); - } else { - assertNotEquals(a, b); - } - } - } - } - -} diff --git a/gcm3/src/test/java/nucleus/AT_Dimension.java b/gcm3/src/test/java/nucleus/AT_Dimension.java deleted file mode 100644 index 32f7b77e3..000000000 --- a/gcm3/src/test/java/nucleus/AT_Dimension.java +++ /dev/null @@ -1,139 +0,0 @@ -package nucleus; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import java.util.ArrayList; -import java.util.List; - -import org.apache.commons.math3.util.MathArrays.Function; -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = Dimension.class) -public class AT_Dimension { - - @Test - @UnitTestMethod(name = "builder", args = {}) - public void testBuilder() { - assertNotNull(Dimension.builder()); - } - - private void testGetMetaDataValues(String... values) { - List expectedMetaData = new ArrayList<>(); - for (String value : values) { - expectedMetaData.add(value); - } - - Dimension.Builder builder = Dimension.builder(); - for (String metaDatum : expectedMetaData) { - builder.addMetaDatum(metaDatum); - } - Dimension dimension = builder.build(); - List actualMetaData = dimension.getMetaData(); - assertEquals(expectedMetaData, actualMetaData); - } - - @Test - @UnitTestMethod(name = "getMetaData", args = {}) - public void testGetMetatData() { - // test several numbers and duplications of meta data - testGetMetaDataValues(); - testGetMetaDataValues("A"); - testGetMetaDataValues("B", "A"); - testGetMetaDataValues("B", "B", "Z"); - testGetMetaDataValues("A", "B", "C", "A"); - } - - @Test - @UnitTestMethod(name = "getLevel", args = {}) - public void testGetLevel() { - - Dimension.Builder builder = Dimension.builder(); - builder.addLevel((map) -> { - return new ArrayList<>(); - }); - builder.addLevel((map) -> { - return new ArrayList<>(); - }); - builder.addLevel((map) -> { - return new ArrayList<>(); - }); - builder.addLevel((map) -> { - return new ArrayList<>(); - }); - Dimension dimension = builder.build(); - - for (int i = 0; i < dimension.size(); i++) { - assertNotNull(dimension.getLevel(i)); - } - - } - - @Test - @UnitTestMethod(name = "size", args = {}) - public void testSize() { - Dimension.Builder builder = Dimension.builder(); - Dimension dimension = builder.build(); - assertEquals(0, dimension.size()); - - builder.addLevel((map) -> { - return new ArrayList<>(); - }); - dimension = builder.build(); - assertEquals(1, dimension.size()); - - builder.addLevel((map) -> { - return new ArrayList<>(); - }); - builder.addLevel((map) -> { - return new ArrayList<>(); - }); - dimension = builder.build(); - assertEquals(2, dimension.size()); - - } - - @Test - @UnitTestMethod(target = Dimension.Builder.class, name = "addMetaDatum", args = { String.class }) - public void testAddMetaDatum() { - // test several numbers and duplications of meta data - testGetMetaDataValues(); - testGetMetaDataValues("A"); - testGetMetaDataValues("B", "A"); - testGetMetaDataValues("B", "B", "Z"); - testGetMetaDataValues("A", "B", "C", "A"); - } - - @Test - @UnitTestMethod(target = Dimension.Builder.class, name = "addLevel", args = { Function.class, List.class }) - public void testAddLevel() { - Dimension.Builder builder = Dimension.builder(); - builder.addLevel((map) -> { - return new ArrayList<>(); - }); - builder.addLevel((map) -> { - return new ArrayList<>(); - }); - builder.addLevel((map) -> { - return new ArrayList<>(); - }); - builder.addLevel((map) -> { - return new ArrayList<>(); - }); - Dimension dimension = builder.build(); - - for (int i = 0; i < dimension.size(); i++) { - assertNotNull(dimension.getLevel(i)); - } - } - - @Test - @UnitTestMethod(target = Dimension.Builder.class, name = "build", args = {}) - public void testBuild() { - //covered by other tests - } - -} diff --git a/gcm3/src/test/java/nucleus/AT_Event.java b/gcm3/src/test/java/nucleus/AT_Event.java deleted file mode 100644 index 226326516..000000000 --- a/gcm3/src/test/java/nucleus/AT_Event.java +++ /dev/null @@ -1,34 +0,0 @@ -package nucleus; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -/** - * Test unit for Event - * @author Shawn Hatch - * - */ -@UnitTest(target = Event.class) -public class AT_Event { - - private static class EventA implements Event { - - } - - private static class EventB implements Event { - - } - - @Test - @UnitTestMethod(name = "getPrimaryKeyValue", args = {}) - public void testGetPrimaryKeyValue() { - // show that event types have default primary keys equal to their own - // classes - assertEquals(EventA.class, new EventA().getPrimaryKeyValue()); - assertEquals(EventB.class, new EventB().getPrimaryKeyValue()); - - } -} \ No newline at end of file diff --git a/gcm3/src/test/java/nucleus/AT_EventLabel.java b/gcm3/src/test/java/nucleus/AT_EventLabel.java deleted file mode 100644 index 2f6cff091..000000000 --- a/gcm3/src/test/java/nucleus/AT_EventLabel.java +++ /dev/null @@ -1,239 +0,0 @@ -package nucleus; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - -/** - * Unit test for MultiKeyEventLabel. - * - * @author Shawn Hatch - * - */ -@UnitTest(target = EventLabel.class) -public class AT_EventLabel { - - private static class EventA implements Event { - - } - - private static class EventB implements Event { - - } - - private static class EventC implements Event { - - } - - private static EventLabelerId eventLabelerId1 = new EventLabelerId() { - - }; - - private static EventLabelerId eventLabelerId2 = new EventLabelerId() { - - }; - - @Test - @UnitTestMethod(target = EventLabel.Builder.class, name = "builder", args = {}) - public void testBuilder() { - - // show that a builder instance is returned - assertNotNull(EventLabel.builder(EventA.class)); - - // precondition test: if the class reference is null - ContractException contractException = assertThrows(ContractException.class, () -> EventLabel.builder(null)); - assertEquals(NucleusError.NULL_EVENT_CLASS, contractException.getErrorType()); - } - - @Test - @UnitTestMethod(target = EventLabel.Builder.class, name = "addKey", args = { Object.class }) - public void testAddKey() { - - EventLabel eventLabel = EventLabel.builder(EventA.class).setEventLabelerId(eventLabelerId1).addKey(54).build(); - assertEquals(54, eventLabel.getPrimaryKeyValue()); - - // precondition test: if a key is null - ContractException contractException = assertThrows(ContractException.class, () -> { - EventLabel.builder(EventA.class).setEventLabelerId(eventLabelerId1).addKey(1).addKey(null).addKey(3).build(); - }); - - assertEquals(NucleusError.NULL_EVENT_LABEL_KEY, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(target = EventLabel.Builder.class, name = "setEventLabelerId", args = { EventLabelerId.class }) - public void testSetEventLabelerId() { - EventLabel eventLabel = EventLabel.builder(EventA.class).setEventLabelerId(eventLabelerId1).addKey(54).build(); - assertEquals(eventLabelerId1, eventLabel.getLabelerId()); - - // precondition test: if the labeler id is null - ContractException contractException = assertThrows(ContractException.class, () -> { - EventLabel.builder(EventA.class).setEventLabelerId(null).addKey(54).build(); - }); - - assertEquals(NucleusError.NULL_EVENT_LABELER_ID, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(target = EventLabel.Builder.class, name = "build", args = { EventLabelerId.class }) - public void testBuild() { - - // show that build returns a label - EventLabel eventLabel = EventLabel.builder(EventA.class).setEventLabelerId(eventLabelerId1).addKey(54).build(); - assertNotNull(eventLabel); - - // precondition test: if no keys were added - ContractException contractException = assertThrows(ContractException.class, () -> EventLabel.builder(EventA.class).setEventLabelerId(eventLabelerId1).build()); - assertEquals(NucleusError.NULL_PRIMARY_KEY_VALUE, contractException.getErrorType()); - - // precondition test: if no labeler id is set - contractException = assertThrows(ContractException.class, () -> EventLabel.builder(EventA.class).addKey(54).build()); - assertEquals(NucleusError.NULL_LABELER_ID_IN_EVENT_LABEL, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "hashCode", args = {}) - public void testHashCode() { - // Equal event labels have equal auxiliary keys, but may disagree on - // other constructor arguments - - // Show equal objects have equal hash codes - EventLabel multiKeyEventLabel1 = EventLabel .builder(EventA.class).setEventLabelerId(eventLabelerId1)// - .addKey(1)// - .addKey(2)// - .addKey(3)// - .build(); - - EventLabel multiKeyEventLabel2 = EventLabel .builder(EventB.class).setEventLabelerId(eventLabelerId2)// - .addKey(1)// - .addKey(2)// - .addKey(3)// - .build(); - - assertEquals(multiKeyEventLabel1.hashCode(), multiKeyEventLabel2.hashCode()); - - } - - @Test - @UnitTestMethod(name = "equals", args = { Object.class }) - public void testEquals() { - // Equal event labels have equal auxiliary keys, but may disagree on - // other constructor arguments - - // show that matching auxiliary keys forces equality - EventLabel multiKeyEventLabel1 = EventLabel .builder(EventA.class)// - .setEventLabelerId(eventLabelerId1)// - .addKey(1)// - .addKey(2)// - .addKey(3)// - .build();// - - EventLabel multiKeyEventLabel2 = EventLabel .builder(EventB.class)// - .setEventLabelerId(eventLabelerId2)// - .addKey(1)// - .addKey(2)// - .addKey(3)// - .build();// - - assertEquals(multiKeyEventLabel1, multiKeyEventLabel2); - - // show that non-matching auxiliary keys forcec non-equality - EventLabel multiKeyEventLabel3 = EventLabel .builder(EventA.class)// - .setEventLabelerId(eventLabelerId1)// - .addKey(1)// - .addKey(2)// - .addKey(3)// - .build();// - - EventLabel multiKeyEventLabel4 = EventLabel .builder(EventA.class)// - .setEventLabelerId(eventLabelerId1)// - .addKey(1)// - .addKey(2)// - .build();// - assertNotEquals(multiKeyEventLabel3, multiKeyEventLabel4); - - } - - @Test - @UnitTestMethod(name = "getEventClass", args = {}) - public void testGetEventClass() { - EventLabel eventLabelA = EventLabel .builder(EventA.class)// - .setEventLabelerId(eventLabelerId1)// - .addKey("key")// - .build();// - assertEquals(EventA.class, eventLabelA.getEventClass()); - - EventLabel eventLabelB = EventLabel .builder(EventB.class)// - .setEventLabelerId(eventLabelerId1)// - .addKey("key")// - .build();// - - assertEquals(EventB.class, eventLabelB.getEventClass()); - - EventLabel eventLabelC = EventLabel .builder(EventC.class)// - .setEventLabelerId(eventLabelerId1)// - .addKey("key")// - .build();// - assertEquals(EventC.class, eventLabelC.getEventClass()); - } - - @Test - @UnitTestMethod(name = "getLabelerId", args = {}) - public void testGetLabelerId() { - - EventLabelerId eventLabelerId1 = new EventLabelerId() { - }; - EventLabelerId eventLabelerId2 = new EventLabelerId() { - }; - EventLabelerId eventLabelerId3 = new EventLabelerId() { - }; - - EventLabel eventLabel = EventLabel .builder(EventA.class)// - .setEventLabelerId(eventLabelerId1)// - .addKey("key")// - .build();// - - assertEquals(eventLabelerId1, eventLabel.getLabelerId()); - - eventLabel = EventLabel .builder(EventA.class)// - .setEventLabelerId(eventLabelerId2)// - .addKey("key")// - .build();// - - assertEquals(eventLabelerId2, eventLabel.getLabelerId()); - - eventLabel = EventLabel .builder(EventA.class)// - .setEventLabelerId(eventLabelerId3)// - .addKey("key")// - .build();// - - assertEquals(eventLabelerId3, eventLabel.getLabelerId()); - - } - - @Test - @UnitTestMethod(name = "getPrimaryKeyValue", args = {}) - public void testGetPrimaryKeyValue() { - - for (int i = 0; i < 10; i++) { - Object key = "key" + i; - EventLabel eventLabel = EventLabel .builder(EventA.class)// - .setEventLabelerId(eventLabelerId1)// - .addKey(key)// - .build();// - - assertEquals(key, eventLabel.getPrimaryKeyValue()); - } - } - -} diff --git a/gcm3/src/test/java/nucleus/AT_EventLabeler.java b/gcm3/src/test/java/nucleus/AT_EventLabeler.java deleted file mode 100644 index 99af62066..000000000 --- a/gcm3/src/test/java/nucleus/AT_EventLabeler.java +++ /dev/null @@ -1,202 +0,0 @@ -package nucleus; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import javax.naming.Context; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - -@UnitTest(target = EventLabeler.class) -public class AT_EventLabeler { - - @Test - @UnitTestMethod(target = EventLabeler.Builder.class, name = "build", args = {}) - public void testBuild() { - - // precondition test: if the id is not set - ContractException contractException = assertThrows(ContractException.class, () -> EventLabeler .builder(TestEvent.class)// - .setLabelFunction((c, t) -> { - return EventLabel .builder(TestEvent.class)// - .setEventLabelerId(id)// - .addKey(TestEvent.class)// - .build();// - })// - .build()); - - assertEquals(NucleusError.NULL_LABELER_ID_IN_EVENT_LABELER, contractException.getErrorType()); - - // precondition test: if the label function is not set - - contractException = assertThrows(ContractException.class, () -> EventLabeler.builder(TestEvent.class)// - .setEventLabelerId(id)// - .build()); - - assertEquals(NucleusError.NULL_EVENT_LABEL_FUNCTION, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(target = EventLabeler.Builder.class, name = "setEventLabelerId", args = {}) - public void testSetEventLabelerId() { - EventLabeler eventLabeler = EventLabeler .builder(TestEvent.class)// - .setEventLabelerId(id)// - .setLabelFunction((c, t) -> { - return EventLabel .builder(TestEvent.class)// - .setEventLabelerId(id)// - .addKey(TestEvent.class)// - .build();// - })// - .build(); - assertEquals(id, eventLabeler.getEventLabelerId()); - - // precondition test: if the id is null - ContractException contractException = assertThrows(ContractException.class, () -> EventLabeler .builder(TestEvent.class)// - .setEventLabelerId(null)// - .setLabelFunction((c, t) -> { - return EventLabel .builder(TestEvent.class)// - .setEventLabelerId(id)// - .addKey(TestEvent.class)// - .build();// - })// - .build()); - - assertEquals(NucleusError.NULL_EVENT_LABELER_ID, contractException.getErrorType()); - } - - @Test - @UnitTestMethod(target = EventLabeler.Builder.class, name = "setLabelFunction", args = { Context.class, Event.class }) - public void testSetLabelFunction() { - // create an event label that will be replicated by the simple event - // labeler - EventLabel expectedEventLabel = EventLabel .builder(TestEvent.class)// - .setEventLabelerId(id)// - .addKey(TestEvent.class)// - .addKey(1)// - .addKey(2)// - .addKey(3)// - .build(); - - // create a simple event labeler that will create the same event label - // as above - EventLabeler eventLabeler = EventLabeler .builder(TestEvent.class)// - .setEventLabelerId(id)// - .setLabelFunction((c, t) -> { - return expectedEventLabel; - })// - .build(); - - // generate an event label from the labeler - EventLabel actualEventLabel = eventLabeler.getEventLabel(null, null); - assertEquals(expectedEventLabel, actualEventLabel); - /* - * Show that the generated event label is equal to the expected event - * label - * - * Due to the streamlined, non-standard equality contract of - * MultiKeyEventLabel, we need to show that the non-key fields are also - * equal. - */ - assertEquals(expectedEventLabel.getEventClass(), actualEventLabel.getEventClass()); - assertEquals(expectedEventLabel.getLabelerId(), actualEventLabel.getLabelerId()); - assertEquals(expectedEventLabel.getPrimaryKeyValue(), actualEventLabel.getPrimaryKeyValue()); - - // precondition test: if the label function is set to null - - ContractException contractException = assertThrows(ContractException.class, () -> EventLabeler .builder(TestEvent.class)// - .setEventLabelerId(id)// - .setLabelFunction(null)// - .build()); - assertEquals(NucleusError.NULL_EVENT_LABEL_FUNCTION, contractException.getErrorType()); - } - - @Test - @UnitTestMethod(name = "builder", args = { Class.class }) - public void testBuilder() { - // show that the builder is returned - assertNotNull(EventLabeler.builder(TestEvent.class)); - - // precondition test: if the class reference is null - ContractException contractException = assertThrows(ContractException.class, () -> EventLabeler.builder(null)); - assertEquals(NucleusError.NULL_EVENT_CLASS, contractException.getErrorType()); - } - - private final static EventLabelerId id = new EventLabelerId() { - }; - - private static class TestEvent implements Event { - } - - @Test - @UnitTestMethod(name = "getEventClass", args = {}) - public void testGetEventClass() { - EventLabeler eventLabeler = EventLabeler .builder(TestEvent.class)// - .setEventLabelerId(id)// - .setLabelFunction((c, t) -> { - return EventLabel .builder(TestEvent.class)// - .setEventLabelerId(id)// - .addKey(TestEvent.class)// - .build(); - })// - .build(); - assertEquals(TestEvent.class, eventLabeler.getEventClass()); - } - - @Test - @UnitTestMethod(name = "getEventLabel", args = { Context.class, Event.class }) - public void testGetEventLabel() { - // create an event label that will be replicated by the simple event - // labeler - EventLabel expectedEventLabel = EventLabel .builder(TestEvent.class)// - .setEventLabelerId(id)// - .addKey(TestEvent.class)// - .addKey(1)// - .addKey(2)// - .addKey(3)// - .build();// - - // create a simple event labeler that will create the same event label - // as above - EventLabeler eventLabeler = EventLabeler .builder(TestEvent.class)// - .setEventLabelerId(id).setLabelFunction((c, t) -> { - return expectedEventLabel; - }).build(); - - // generate an event label from the labeler - EventLabel actualEventLabel = eventLabeler.getEventLabel(null, null); - assertEquals(expectedEventLabel, actualEventLabel); - /* - * Show that the generated event label is equal to the expected event - * label - * - * Due to the streamlined, non-standard equality contract of - * MultiKeyEventLabel, we need to show that the non-key fields are also - * equal. - */ - assertEquals(expectedEventLabel.getEventClass(), actualEventLabel.getEventClass()); - assertEquals(expectedEventLabel.getLabelerId(), actualEventLabel.getLabelerId()); - assertEquals(expectedEventLabel.getPrimaryKeyValue(), actualEventLabel.getPrimaryKeyValue()); - - } - - @Test - @UnitTestMethod(name = "getEventLabelerId", args = {}) - public void testGetEventLabelerId() { - EventLabeler eventLabeler = EventLabeler .builder(TestEvent.class)// - .setEventLabelerId(id)// - .setLabelFunction((c, t) -> { - return EventLabel .builder(TestEvent.class)// - .setEventLabelerId(id)// - .addKey(TestEvent.class)// - .build();// - })// - .build(); - assertEquals(id, eventLabeler.getEventLabelerId()); - } - -} diff --git a/gcm3/src/test/java/nucleus/AT_EventLabelerId.java b/gcm3/src/test/java/nucleus/AT_EventLabelerId.java deleted file mode 100644 index d6e74526e..000000000 --- a/gcm3/src/test/java/nucleus/AT_EventLabelerId.java +++ /dev/null @@ -1,13 +0,0 @@ -package nucleus; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - -@UnitTest(target = EventLabelerId.class) -public class AT_EventLabelerId { - @Test - public void test() { - //nothing to test - } -} diff --git a/gcm3/src/test/java/nucleus/AT_Experiment.java b/gcm3/src/test/java/nucleus/AT_Experiment.java deleted file mode 100644 index 88959263f..000000000 --- a/gcm3/src/test/java/nucleus/AT_Experiment.java +++ /dev/null @@ -1,402 +0,0 @@ -package nucleus; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.function.Consumer; -import java.util.stream.IntStream; - -import org.junit.jupiter.api.Test; - -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.wrappers.MultiKey; - -@UnitTest(target = Experiment.class) -public class AT_Experiment { - - @Test - @UnitTestMethod(target = Experiment.Builder.class, name = "addDimension", args = { Dimension.class }) - public void testAddDimension() { - - Dimension dimension1 = Dimension.builder()// - .addMetaDatum("Alpha")// - .addLevel((context) -> { - List result = new ArrayList<>(); - result.add("alpha1"); - return result; - })// - .addLevel((context) -> { - List result = new ArrayList<>(); - result.add("alpha2"); - return result; - })// - .build();// - - Dimension dimension2 = Dimension.builder()// - .addMetaDatum("Beta")// - .addLevel((context) -> { - List result = new ArrayList<>(); - result.add("beta1"); - return result; - })// - .addLevel((context) -> { - List result = new ArrayList<>(); - result.add("beta2"); - return result; - })// - .addLevel((context) -> { - List result = new ArrayList<>(); - result.add("beta3"); - return result; - })// - .build();// - - Set expectedExperimentInstances = new LinkedHashSet<>(); - expectedExperimentInstances.add(new MultiKey("alpha1", "beta1")); - expectedExperimentInstances.add(new MultiKey("alpha2", "beta1")); - expectedExperimentInstances.add(new MultiKey("alpha1", "beta2")); - expectedExperimentInstances.add(new MultiKey("alpha2", "beta2")); - expectedExperimentInstances.add(new MultiKey("alpha1", "beta3")); - expectedExperimentInstances.add(new MultiKey("alpha2", "beta3")); - - Set actualExperimentInstances = new LinkedHashSet<>(); - - Plugin plugin = Plugin .builder()// - .setPluginId(new SimplePluginId("plugin")).setInitializer((c) -> { - c.addActor((c2) -> { - c2.releaseOutput(new Object()); - }); - }).build();// - - Experiment .builder()// - .reportProgressToConsole(false)// - .reportFailuresToConsole(false)// - - .addOutputHandler(c -> { - c.subscribeToOutput(Object.class, (c2, s, e) -> { - List scenarioMetaData = c2.getScenarioMetaData(s).get(); - MultiKey.Builder builder = MultiKey.builder(); - for (String scenarioMetaDatum : scenarioMetaData) { - builder.addKey(scenarioMetaDatum); - } - actualExperimentInstances.add(builder.build()); - }); - }).addPlugin(plugin)// - .addDimension(dimension1)// - .addDimension(dimension2)// - .build()// - .execute();// - - assertEquals(expectedExperimentInstances, actualExperimentInstances); - - } - - @Test - @UnitTestMethod(target = Experiment.Builder.class, name = "addOutputHandler", args = { Consumer.class }) - public void testAddOutputHandler() { - - /* - * Show that an output handler receives data released by the simulation - * by adding a few actors that release data. Add output handlers that - * show that the output is correctly directed. Note the use of - * overlapping types for the output consumers and that the output type - * may be a subclass of the consumer's type. - */ - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - pluginBuilder.addTestActorPlan("Integer Actor", new TestActorPlan(0, (c) -> { - c.releaseOutput(56); - })); - - pluginBuilder.addTestActorPlan("String Actor", new TestActorPlan(0, (c) -> { - c.releaseOutput("string 1"); - })); - - pluginBuilder.addTestActorPlan("Double Actor", new TestActorPlan(0, (c) -> { - c.releaseOutput(34.5); - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - Set actualOutput = new LinkedHashSet<>(); - - Consumer integerOutputHandler = (c) -> { - c.subscribeToOutput(Integer.class, (c2, s, e) -> { - MultiKey.Builder builder = MultiKey.builder(); - List metaData = c2.getScenarioMetaData(s).get(); - builder.addKey("Integer Output Handler"); - for (String metaDatum : metaData) { - builder.addKey(metaDatum); - } - builder.addKey(s); - builder.addKey(e); - actualOutput.add(builder.build()); - }); - }; - - Consumer stringOutputHandler = (c) -> { - c.subscribeToOutput(String.class, (c2, s, e) -> { - actualOutput.add(new MultiKey("String Output Handler", s, e)); - }); - }; - - Consumer doubleOutputHandler = (c) -> { - c.subscribeToOutput(Double.class, (c2, s, e) -> { - actualOutput.add(new MultiKey("Double Output Handler", s, e)); - }); - }; - - Consumer numberOutputHandler = (c) -> { - c.subscribeToOutput(Number.class, (c2, s, e) -> { - actualOutput.add(new MultiKey("Number Output Handler", s, e)); - }); - }; - - Dimension dimension1 = Dimension.builder()// - .addMetaDatum("dim1")// - .addLevel((tmap) -> { - List result = new ArrayList<>(); - result.add("var_1_1"); - return result; - })// - .addLevel((tmap) -> { - List result = new ArrayList<>(); - result.add("var_1_2"); - return result; - })// - .build();// - - Dimension dimension2 = Dimension.builder()// - .addMetaDatum("dim2")// - .addLevel((tmap) -> { - List result = new ArrayList<>(); - result.add("var_2_1"); - return result; - })// - .addLevel((tmap) -> { - List result = new ArrayList<>(); - result.add("var_2_2"); - return result; - })// - .build();// - - Experiment .builder()// - .reportProgressToConsole(false)// - .reportFailuresToConsole(false)// - - .addDimension(dimension1)// - .addDimension(dimension2)// - .addOutputHandler(integerOutputHandler)// - .addOutputHandler(stringOutputHandler)// - .addOutputHandler(doubleOutputHandler)// - .addOutputHandler(numberOutputHandler)// - .addPlugin(testPlugin)// - .build()// - .execute();// - - Set expectedOutput = new LinkedHashSet<>(); - - expectedOutput.add(new MultiKey("Integer Output Handler", "var_1_1", "var_2_1", 0, 56)); - expectedOutput.add(new MultiKey("Number Output Handler", 0, 56)); - expectedOutput.add(new MultiKey("String Output Handler", 0, "string 1")); - expectedOutput.add(new MultiKey("Double Output Handler", 0, 34.5)); - expectedOutput.add(new MultiKey("Number Output Handler", 0, 34.5)); - expectedOutput.add(new MultiKey("Integer Output Handler", "var_1_2", "var_2_1", 1, 56)); - expectedOutput.add(new MultiKey("Number Output Handler", 1, 56)); - expectedOutput.add(new MultiKey("String Output Handler", 1, "string 1")); - expectedOutput.add(new MultiKey("Double Output Handler", 1, 34.5)); - expectedOutput.add(new MultiKey("Number Output Handler", 1, 34.5)); - expectedOutput.add(new MultiKey("Integer Output Handler", "var_1_1", "var_2_2", 2, 56)); - expectedOutput.add(new MultiKey("Number Output Handler", 2, 56)); - expectedOutput.add(new MultiKey("String Output Handler", 2, "string 1")); - expectedOutput.add(new MultiKey("Double Output Handler", 2, 34.5)); - expectedOutput.add(new MultiKey("Number Output Handler", 2, 34.5)); - expectedOutput.add(new MultiKey("Integer Output Handler", "var_1_2", "var_2_2", 3, 56)); - expectedOutput.add(new MultiKey("Number Output Handler", 3, 56)); - expectedOutput.add(new MultiKey("String Output Handler", 3, "string 1")); - expectedOutput.add(new MultiKey("Double Output Handler", 3, 34.5)); - expectedOutput.add(new MultiKey("Number Output Handler", 3, 34.5)); - - assertEquals(expectedOutput, actualOutput); - - } - - @Test - @UnitTestMethod(target = Experiment.Builder.class, name = "addPlugin", args = { Plugin.class }) - public void testAddPlugin() { - - // show that several plugins can be added and that they execute in the - // correct order - - // create plugin ids - PluginId aId = new SimplePluginId("plugin A"); - PluginId bId = new SimplePluginId("plugin B"); - PluginId cId = new SimplePluginId("plugin C"); - - /* - * Build the expected order of initialization based on the dependencies - * between the plugins - */ - List expectedExecutedPlugins = new ArrayList<>(); - expectedExecutedPlugins.add(cId); - expectedExecutedPlugins.add(bId); - expectedExecutedPlugins.add(aId); - - // build a container for the actual initialization order - List actualExecutedPlugins = new ArrayList<>(); - - // create the three plugins with A depending on B and C and B depending - // on C alone. - Plugin pluginA = Plugin .builder()// - .setPluginId(aId)// - .addPluginDependency(cId)// - .addPluginDependency(bId)// - .setInitializer((c) -> { - actualExecutedPlugins.add(aId); - }).build();// - - Plugin pluginB = Plugin .builder()// - .setPluginId(bId)// - .addPluginDependency(cId)// - .setInitializer((c) -> { - actualExecutedPlugins.add(bId); - }).build();// - - Plugin pluginC = Plugin .builder()// - .setPluginId(cId)// - .setInitializer((c) -> { - actualExecutedPlugins.add(cId); - }).build();// - - // create the simulation - Experiment .builder()// - .reportProgressToConsole(false)// - .reportFailuresToConsole(false)// - - .addPlugin(pluginA)// - .addPlugin(pluginB)// - .addPlugin(pluginC)// - .build()// - .execute();// - - // show that the plugins initialized in the correct order - assertEquals(expectedExecutedPlugins, actualExecutedPlugins); - } - - @Test - @UnitTestMethod(target = Experiment.Builder.class, name = "build", args = {}) - public void testBuild() { - // show that an empty experiment will executed - Experiment experiment = Experiment .builder()// - .reportProgressToConsole(false)// - .reportFailuresToConsole(false)// - .build();// - experiment.execute(); - - // Other aspects of the build are covered in the remaining capability - // specific tests - } - - @Test - @UnitTestMethod(target = Experiment.Builder.class, name = "setExperimentProgressConsole", args = { boolean.class }, manual = true) - public void testSetExperimentProgressConsole() { - // should be manually tested - } - - @Test - @UnitTestMethod(target = Experiment.Builder.class, name = "setExperimentProgressLog", args = { Path.class }, manual = true) - public void testSetExperimentProgressLog() { - // should be manually tested - } - - @Test - @UnitTestMethod(target = Experiment.Builder.class, name = "setThreadCount", args = { int.class }) - public void testSetThreadCount() { - - // add two dimensions that will cause the experiment to execute 40 - // simulation instances - Dimension.Builder dimBuilder = Dimension.builder().addMetaDatum("Alpha");// - IntStream.range(0, 5).forEach((i) -> { - dimBuilder.addLevel((context) -> { - List result = new ArrayList<>(); - result.add(Integer.toString(i)); - return result; - }); - }); - - Dimension dimension1 = dimBuilder.build(); - - IntStream.range(0, 8).forEach((i) -> { - dimBuilder.addLevel((context) -> { - List result = new ArrayList<>(); - result.add(Integer.toString(i)); - return result; - }); - }); - - Dimension dimension2 = dimBuilder.build(); - - /* - * Create a thread safe set to record the thread ids that are used by - * each simulation - */ - Set threadIds = Collections.synchronizedSet(new LinkedHashSet<>()); - - /* - * Add a plugin that will add an actor that records the thread id of the - * simulation running that actor - */ - Plugin plugin = Plugin .builder()// - .setPluginId(new SimplePluginId("plugin")).setInitializer((c) -> { - c.addActor((c2) -> { - threadIds.add(Thread.currentThread().getId()); - }); - }).build();// - - // Run the experiment using several threads - Experiment .builder()// - .reportProgressToConsole(false)// - .reportFailuresToConsole(false)// - .addPlugin(plugin)// - .addDimension(dimension1)// - .addDimension(dimension2)// - .setThreadCount(6).build()// - .execute();// - - // We show that more than one thread was used. It is very difficult, - // especially with simulation instances that run very quickly, to reason - // out the number of threads that will be allocated or reused. The best - // we can do is show that the main thread was not used for any - // simulation instance. In practice only a long running manual test can - // demonstrate that the experiment thread management is working as - // intended. - assertFalse(threadIds.contains(Thread.currentThread().getId())); - } - - @Test - @UnitTestMethod(name = "builder", args = {}) - public void testBuilder() { - // show that a builder is returned - assertNotNull(Experiment.builder()); - } - - @Test - @UnitTestMethod(name = "execute", args = {}) - public void testExecute() { - // Covered by remaining tests that execute the experiment. - } - -} diff --git a/gcm3/src/test/java/nucleus/AT_ExperimentContext.java b/gcm3/src/test/java/nucleus/AT_ExperimentContext.java deleted file mode 100644 index bda8f0870..000000000 --- a/gcm3/src/test/java/nucleus/AT_ExperimentContext.java +++ /dev/null @@ -1,688 +0,0 @@ -package nucleus; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.stream.IntStream; - -import org.junit.jupiter.api.Test; - -import nucleus.util.TriConsumer; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.wrappers.MultiKey; -import util.wrappers.MutableDouble; -import util.wrappers.MutableInteger; - -@UnitTest(target = ExperimentContext.class) -public class AT_ExperimentContext { - - @Test - @UnitTestMethod(name = "getElapsedSeconds", args = {}) - public void testGetElapsedSeconds() { - /* - * This is a very limited test of the elapsed time and requires a manual - * test to perform a more robust test. - */ - MutableDouble elapsedSeconds = new MutableDouble(); - Experiment .builder()// - .reportProgressToConsole(false)// - .reportFailuresToConsole(false)// - .addOutputHandler(c -> { - elapsedSeconds.setValue(c.getElapsedSeconds()); - }).build()// - .execute();// - - // show that some time elapsed - assertTrue(elapsedSeconds.getValue() > 0); - } - - @Test - @UnitTestMethod(name = "getExperimentMetaData", args = {}) - public void testGetExperimentMetaData() { - - List actualMetaData = new ArrayList<>(); - Experiment .builder()// - .reportProgressToConsole(false)// - .reportFailuresToConsole(false)// - .addOutputHandler(c -> { - actualMetaData.addAll(c.getExperimentMetaData()); - }).build()// - .execute();// - - // show that the meta data is empty in the absence of dimensions - assertTrue(actualMetaData.isEmpty()); - - // create an experiment with two dimensions with some experiment meta - // data - Dimension dimension1 = Dimension.builder().addMetaDatum("A").addMetaDatum("B").build(); - Dimension dimension2 = Dimension.builder().addMetaDatum("C").addMetaDatum("D").build(); - - Experiment .builder()// - .reportProgressToConsole(false)// - .reportFailuresToConsole(false)// - .addDimension(dimension1)// - .addDimension(dimension2)// - .addOutputHandler(c -> { - actualMetaData.addAll(c.getExperimentMetaData()); - }).build()// - .execute();// - - /* - * The meta data should be added in the order the meta data were added - * to the dimension and the order that the dimensions were added to the - * experiment - */ - List expectedMetaData = new ArrayList<>(); - expectedMetaData.add("A"); - expectedMetaData.add("B"); - expectedMetaData.add("C"); - expectedMetaData.add("D"); - - assertEquals(expectedMetaData, actualMetaData); - - /* - * Show that a different ordering of the dimensions works as expected - */ - - actualMetaData.clear(); - expectedMetaData.clear(); - expectedMetaData.add("C"); - expectedMetaData.add("D"); - expectedMetaData.add("A"); - expectedMetaData.add("B"); - - Experiment .builder()// - .reportProgressToConsole(false)// - .reportFailuresToConsole(false)// - .addDimension(dimension2)// - .addDimension(dimension1)// - .addOutputHandler(c -> { - actualMetaData.addAll(c.getExperimentMetaData()); - }).build()// - .execute();// - - assertEquals(expectedMetaData, actualMetaData); - - } - - @Test - @UnitTestMethod(name = "getScenarioCount", args = {}) - public void testGetScenarioCount() { - - // create an experiment that has no dimension and show that the scenario - // count is 1 - MutableInteger scenarioCount = new MutableInteger(); - - Experiment .builder()// - .reportProgressToConsole(false)// - .reportFailuresToConsole(false)// - - .addOutputHandler((c) -> { - scenarioCount.setValue(c.getScenarioCount()); - })// - .build()// - .execute();// - assertEquals(1, scenarioCount.getValue()); - - // create an experiment with two dimensions, having 10 and 7 levels each - Dimension.Builder dimBuilder = Dimension.builder().addMetaDatum("A").addMetaDatum("B"); - IntStream.range(0, 10).forEach((i) -> { - dimBuilder.addLevel((context) -> { - List result = new ArrayList<>(); - result.add(Integer.toString(i)); - result.add(Integer.toString(3 * i)); - return result; - }); - }); - Dimension dimension1 = dimBuilder.build(); - - dimBuilder.addMetaDatum("X"); - IntStream.range(0, 7).forEach((i) -> { - dimBuilder.addLevel((context) -> { - List result = new ArrayList<>(); - result.add(Integer.toString(i * i)); - return result; - }); - }); - Dimension dimension2 = dimBuilder.build(); - - // execute the experiment - Experiment .builder()// - .reportProgressToConsole(false)// - .reportFailuresToConsole(false)// - - .addDimension(dimension1)// - .addDimension(dimension2)// - .addOutputHandler((c) -> { - scenarioCount.setValue(c.getScenarioCount()); - })// - .build()// - .execute();// - - // show that the experiment has the expected number of scenarios - assertEquals(70, scenarioCount.getValue()); - } - - @Test - @UnitTestMethod(name = "getScenarioMetaData", args = { int.class }) - public void testGetScenarioMetaData() { - - // create an experiment with two dimensions with some experiment meta - // data - Dimension.Builder dimBuilder = Dimension.builder().addMetaDatum("A").addMetaDatum("B"); - IntStream.range(0, 10).forEach((i) -> { - dimBuilder.addLevel((context) -> { - List result = new ArrayList<>(); - result.add(Integer.toString(i)); - result.add(Integer.toString(3 * i)); - return result; - }); - }); - Dimension dimension1 = dimBuilder.build(); - - dimBuilder.addMetaDatum("X"); - IntStream.range(0, 7).forEach((i) -> { - dimBuilder.addLevel((context) -> { - List result = new ArrayList<>(); - result.add(Integer.toString(i * i)); - return result; - }); - }); - Dimension dimension2 = dimBuilder.build(); - - /* - * Create a plugin that will put a single actor into the simulation and - * have that actor release an object to output - */ - Plugin plugin = Plugin .builder()// - .setPluginId(new SimplePluginId("plugin"))// - .setInitializer((c) -> { - c.addActor((c2) -> { - c2.releaseOutput(new Object()); - });// - }).build();// - - /* - * Execute an experiment from the dimension and an output handler that - * will be stimulated exactly once per scenario and record the - * corresponding scenario meta data - */ - - // make a container for the collected scenario id and scenario meta data - // information - Set combinedMetaData = new LinkedHashSet<>(); - Experiment .builder()// - .reportProgressToConsole(false)// - .reportFailuresToConsole(false)// - - .addPlugin(plugin)// - .addDimension(dimension1)// - .addDimension(dimension2)// - .addOutputHandler(c -> { - c.subscribeToOutput(Object.class, (c2, s, e) -> { - MultiKey.Builder builder = MultiKey.builder(); - builder.addKey(s); - for (String metaDatum : c2.getScenarioMetaData(s).get()) { - builder.addKey(Integer.parseInt(metaDatum)); - } - combinedMetaData.add(builder.build()); - }); - })// - .build()// - .execute();// - - /* - * There is no guarantee provided by the experiment that any particular - * scenario id will be associated with any particular values from the - * dimensions. - */ - - // First, we will show that the scenario ids were 0 to 69, inclusive - assertEquals(70, combinedMetaData.size()); - Set observedScenarioIds = new LinkedHashSet<>(); - for (MultiKey multiKey : combinedMetaData) { - Integer scenarioId = multiKey.getKey(0); - assertTrue(scenarioId >= 0); - assertTrue(scenarioId < 70); - observedScenarioIds.add(scenarioId); - } - assertEquals(70, observedScenarioIds.size()); - - // Next we will create a container for the expected meta data - Set expectedMetaData = new LinkedHashSet<>(); - for (int i = 0; i < 10; i++) { - for (int j = 0; j < 7; j++) { - expectedMetaData.add(new MultiKey(i, 3 * i, j * j)); - } - } - - // We derive the actual meta data by dropping the scenario ids from the - // combinedMetaData - Set actualMetaData = new LinkedHashSet<>(); - for (MultiKey multiKey : combinedMetaData) { - actualMetaData.add(new MultiKey(multiKey.getKey(1), multiKey.getKey(2), multiKey.getKey(3))); - } - - assertEquals(expectedMetaData, actualMetaData); - - } - - @Test - @UnitTestMethod(name = "getScenarios", args = { ScenarioStatus.class }) - public void testGetScenarios() { - // create an experiment with two dimensions with some experiment meta - // data - Dimension.Builder dimBuilder = Dimension.builder().addMetaDatum("A").addMetaDatum("B"); - IntStream.range(0, 10).forEach((i) -> { - dimBuilder.addLevel((context) -> { - List result = new ArrayList<>(); - result.add(Integer.toString(i)); - result.add(Integer.toString(3 * i)); - return result; - }); - }); - Dimension dimension1 = dimBuilder.build(); - - dimBuilder.addMetaDatum("X"); - IntStream.range(0, 7).forEach((i) -> { - dimBuilder.addLevel((context) -> { - List result = new ArrayList<>(); - result.add(Integer.toString(i * i)); - return result; - }); - }); - Dimension dimension2 = dimBuilder.build(); - - /* - * Create a plugin that will put a single actor into the simulation and - * have that actor release the scenario id to output - */ - Plugin plugin = Plugin .builder()// - .setPluginId(new SimplePluginId("plugin"))// - .setInitializer((c) -> { - c.addActor((c2) -> { - c2.releaseOutput(new Object()); - });// - }).build();// - - /* - * Execute an experiment from the dimension and an output handler that - * will be stimulated exactly once per scenario and record the scenario - * id - */ - - // make a container for the collected scenario id values - Set actualScenarioIds = new LinkedHashSet<>(); - - // execute the experiment - Experiment .builder()// - .reportProgressToConsole(false)// - .reportFailuresToConsole(false)// - - .addPlugin(plugin)// - .addDimension(dimension1)// - .addDimension(dimension2)// - .addOutputHandler(c -> { - c.subscribeToOutput(Object.class, (c2, s, e) -> { - actualScenarioIds.add(s); - }); - }).build()// - .execute();// - - // show that the scenarios were as expected - Set expectedScenarioIds = new LinkedHashSet<>(); - for (int i = 0; i < 70; i++) { - expectedScenarioIds.add(i); - } - - assertEquals(expectedScenarioIds, actualScenarioIds); - - } - - /* - * An enum support scenario status tests - */ - private static enum StatusPhase { - EXP_OPEN, EXP_CLOSE, SIM_OPEN, SIM_CLOSE; - } - - @Test - @UnitTestMethod(name = "getScenarioStatus", args = { int.class }) - public void testGetScenarioStatus() { - - /* - * Create a container for the scenario id, ASSUMING that the experiment - * executes scenarios in scenario id order, starting from zero. The - * plugin instance that initializes the simulation does not have access - * to the scenario id. - */ - MutableInteger expectedScenarioId = new MutableInteger(); - - // create a plugin that will cause an exception to be thrown during - // plugin initialization for even-numbered scenarios. - Plugin plugin = Plugin .builder()// - .setPluginId(new SimplePluginId("plugin"))// - .setInitializer((c) -> { - int value = expectedScenarioId.getValue(); - expectedScenarioId.increment(); - if (value % 2 == 0) { - throw new RuntimeException(); - } - }).build();// - - // Create a dimension with integer values. We expect these values to - // match the scenario id. - Dimension.Builder dimBuilder = Dimension.builder().addMetaDatum("value"); - IntStream.range(0, 10).forEach((i) -> { - dimBuilder.addLevel((context) -> { - List result = new ArrayList<>(); - result.add(Integer.toString(i)); - return result; - }); - }); - Dimension dimension = dimBuilder.build(); - - // create a container for observed scenario status values - Set observedScenarioStatusInfo = new LinkedHashSet<>(); - - /* - * Execute an experiment that uses the dimension and plugin. Add an - * output handler that will record the status of each scenario at each - * of the phases of the simulation and experiment. Turn off console - * notification of scenario failures. - */ - Experiment .builder()// - .reportProgressToConsole(false)// - .reportFailuresToConsole(false)// - - .addDimension(dimension)// - .addPlugin(plugin)// - .reportFailuresToConsole(false)// - .addOutputHandler((c) -> { - c.subscribeToExperimentOpen((c2) -> { - int scenarioCount = c2.getScenarioCount(); - for (int scenarioId = 0; scenarioId < scenarioCount; scenarioId++) { - ScenarioStatus scenarioStatus = c2.getScenarioStatus(scenarioId).get(); - observedScenarioStatusInfo.add(new MultiKey(StatusPhase.EXP_OPEN, scenarioId, scenarioStatus)); - } - }); - c.subscribeToSimulationOpen((c2, s) -> { - ScenarioStatus scenarioStatus = c2.getScenarioStatus(s).get(); - observedScenarioStatusInfo.add(new MultiKey(StatusPhase.SIM_OPEN, s, scenarioStatus)); - }); - c.subscribeToSimulationClose((c2, s) -> { - ScenarioStatus scenarioStatus = c2.getScenarioStatus(s).get(); - observedScenarioStatusInfo.add(new MultiKey(StatusPhase.SIM_CLOSE, s, scenarioStatus)); - }); - c.subscribeToExperimentClose((c2) -> { - int scenarioCount = c2.getScenarioCount(); - Map> scenariosByStatus = new LinkedHashMap<>(); - for (ScenarioStatus scenarioStatus : ScenarioStatus.values()) { - scenariosByStatus.put(scenarioStatus, new LinkedHashSet<>()); - } - for (int scenarioId = 0; scenarioId < scenarioCount; scenarioId++) { - ScenarioStatus scenarioStatus = c2.getScenarioStatus(scenarioId).get(); - observedScenarioStatusInfo.add(new MultiKey(StatusPhase.EXP_CLOSE, scenarioId, scenarioStatus)); - scenariosByStatus.get(scenarioStatus).add(scenarioId); - } - - /* - * show that the scenarios retrieved by status match - * expectations - */ - for (ScenarioStatus scenarioStatus : ScenarioStatus.values()) { - Set actualScenarios = new LinkedHashSet<>(c2.getScenarios(scenarioStatus)); - assertEquals(scenariosByStatus.get(scenarioStatus), actualScenarios); - } - - }); - })// - .build()// - .execute();// - - /* - * Build the expected observations - */ - - Set expectedScenarioStatusInfo = new LinkedHashSet<>(); - for (int i = 0; i < dimension.size(); i++) { - ScenarioStatus finalStatus = ScenarioStatus.SUCCEDED; - if (i % 2 == 0) { - finalStatus = ScenarioStatus.FAILED; - } - expectedScenarioStatusInfo.add(new MultiKey(StatusPhase.EXP_OPEN, i, ScenarioStatus.READY)); - expectedScenarioStatusInfo.add(new MultiKey(StatusPhase.SIM_OPEN, i, ScenarioStatus.RUNNING)); - expectedScenarioStatusInfo.add(new MultiKey(StatusPhase.SIM_CLOSE, i, finalStatus)); - expectedScenarioStatusInfo.add(new MultiKey(StatusPhase.EXP_CLOSE, i, finalStatus)); - } - - // show that the observations of scenario status match expectations. - assertEquals(expectedScenarioStatusInfo, observedScenarioStatusInfo); - - } - - @Test - @UnitTestMethod(name = "getStatusCount", args = { ScenarioStatus.class }) - public void testGetStatusCount() { - // covered by the test method : testScenarioStatus() - } - - @Test - @UnitTestMethod(name = "subscribeToExperimentClose", args = { Consumer.class }) - public void testSubscribeToExperimentClose() { - /* - * Run an experiment that has several clients of the experiment context - * subscribe to experiment open and show that each one is stimulated - * correctly. - */ - MutableInteger simulationCloseExperimentCount = new MutableInteger(); - int expectedCloseExperimentCount = 5; - - /* - * Begin building the experiment - */ - Experiment.Builder builder = Experiment.builder();// - - /* - * Add the output handlers that will subscribe to the close of the - * experiment and respond by incrementing a counter - */ - for (int i = 0; i < expectedCloseExperimentCount; i++) { - builder.addOutputHandler((c) -> { - c.subscribeToExperimentClose((c2) -> { - simulationCloseExperimentCount.increment(); - }); - });// - } - - // execute the experiment - builder .reportProgressToConsole(false)// - .reportFailuresToConsole(false)// - .build()// - .execute();// - - // show the subscribers did observe the opening of the simulation - assertEquals(expectedCloseExperimentCount, simulationCloseExperimentCount.getValue()); - } - - @Test - @UnitTestMethod(name = "subscribeToExperimentOpen", args = { Consumer.class }) - public void testSubscribeToExperimentOpen() { - /* - * Run an experiment that has several clients of the experiment context - * subscribe to experiment open and show that each one is stimulated - * correctly. - */ - MutableInteger simulationOpenExperimentCount = new MutableInteger(); - int expectedOpenExperimentCount = 5; - - /* - * Begin building the experiment - */ - Experiment.Builder builder = Experiment.builder();// - - /* - * Add the output handlers that will subscribe to the opening of the - * experiment and respond by incrementing a counter - */ - for (int i = 0; i < expectedOpenExperimentCount; i++) { - builder.addOutputHandler((c) -> { - c.subscribeToExperimentOpen((c2) -> { - simulationOpenExperimentCount.increment(); - }); - });// - } - - // execute the experiment - builder .reportProgressToConsole(false)// - .reportFailuresToConsole(false)// - .build()// - .execute();// - - // show the subscribers did observe the opening of the simulation - assertEquals(expectedOpenExperimentCount, simulationOpenExperimentCount.getValue()); - } - - @Test - @UnitTestMethod(name = "subscribeToOutput", args = { Class.class, TriConsumer.class }) - public void testSubscribeToOutput() { - - /* - * Create a plugin that will add a single actor that in turn releases a - * few integers, strings and doubles as output - */ - Plugin plugin = Plugin .builder()// - .setPluginId(new SimplePluginId("plugin"))// - .setInitializer((c) -> { - c.addActor((c2) -> { - c2.releaseOutput(45); - c2.releaseOutput("alpha"); - c2.releaseOutput(16); - c2.releaseOutput("beta"); - c2.releaseOutput(2.0345); - }); - })// - .build();// - - /* - * Execute an experiment having two output handlers for integers and - * strings, but not doubles - */ - Set observedOutput = new LinkedHashSet<>(); - Experiment .builder()// - .reportProgressToConsole(false)// - .reportFailuresToConsole(false)// - - .addPlugin(plugin)// - .addOutputHandler((c) -> { - c.subscribeToOutput(Integer.class, (c2, s, o) -> { - observedOutput.add(new MultiKey("int handler", o)); - }); - })// - .addOutputHandler((c) -> { - c.subscribeToOutput(String.class, (c2, s, o) -> { - observedOutput.add(new MultiKey("string handler", o)); - }); - })// - .build()// - .execute();// - - // show that the handlers received the expected output - Set expectedOutput = new LinkedHashSet<>(); - expectedOutput.add(new MultiKey("int handler", 45)); - expectedOutput.add(new MultiKey("int handler", 16)); - expectedOutput.add(new MultiKey("string handler", "alpha")); - expectedOutput.add(new MultiKey("string handler", "beta")); - - assertEquals(expectedOutput, observedOutput); - - } - - @Test - @UnitTestMethod(name = "", args = { BiConsumer.class, Integer.class }) - public void testSubscribeToSimulationClose() { - /* - * Run an experiment that has several clients of the experiment context - * subscribe to simulation close and show that each one is stimulated - * correctly. - */ - MutableInteger simulationCloseObservationCount = new MutableInteger(); - int expectedCloseObservationCount = 5; - - /* - * Begin building the experiment - */ - Experiment.Builder builder = Experiment.builder();// - - /* - * Add the output handlers that will subscribe to the opening of the - * simulation and respond by incrementing a counter - */ - for (int i = 0; i < expectedCloseObservationCount; i++) { - builder.addOutputHandler((c) -> { - c.subscribeToSimulationClose((c2, s) -> { - simulationCloseObservationCount.increment(); - }); - });// - } - - // execute the experiment - builder .reportProgressToConsole(false)// - .reportFailuresToConsole(false)// - .build()// - .execute();// - - // show the subscribers did observe the opening of the simulation - assertEquals(expectedCloseObservationCount, simulationCloseObservationCount.getValue()); - } - - @Test - @UnitTestMethod(name = "subscribeToSimulationOpen", args = { BiConsumer.class, Integer.class }) - public void testSubscribeToSimulationOpen() { - - /* - * Run an experiment that has several clients of the experiment context - * subscribe to simulation open and show that each one is stimulated - * correctly. - */ - MutableInteger simulationOpenObservationCount = new MutableInteger(); - int expectedOpenObservationCount = 5; - - /* - * Begin building the experiment - */ - Experiment.Builder builder = Experiment.builder();// - - /* - * Add the output handlers that will subscribe to the opening of the - * simulation and respond by incrementing a counter - */ - for (int i = 0; i < expectedOpenObservationCount; i++) { - builder.addOutputHandler((c) -> { - c.subscribeToSimulationOpen((c2, s) -> { - simulationOpenObservationCount.increment(); - }); - });// - } - - // execute the experiment - builder// - .reportProgressToConsole(false)// - .reportFailuresToConsole(false)// - .build()// - .execute();// - - // show the subscribers did observe the opening of the simulation - assertEquals(expectedOpenObservationCount, simulationOpenObservationCount.getValue()); - } - -} diff --git a/gcm3/src/test/java/nucleus/AT_ExperimentStateManager.java b/gcm3/src/test/java/nucleus/AT_ExperimentStateManager.java deleted file mode 100644 index 3960acf48..000000000 --- a/gcm3/src/test/java/nucleus/AT_ExperimentStateManager.java +++ /dev/null @@ -1,18 +0,0 @@ -package nucleus; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - -@UnitTest(target = ExperimentStateManager.class, proxy = {ExperimentStateManager.class,ExperimentContext.class }) - -public class AT_ExperimentStateManager { - - @Test - public void test() { - /* - * All tests of the ExperimentStateManager are covered by the Experiment - * and ExperimentContext tests - */ - } -} diff --git a/gcm3/src/test/java/nucleus/AT_ExperimentStatusConsole.java b/gcm3/src/test/java/nucleus/AT_ExperimentStatusConsole.java deleted file mode 100644 index f7018297b..000000000 --- a/gcm3/src/test/java/nucleus/AT_ExperimentStatusConsole.java +++ /dev/null @@ -1,17 +0,0 @@ -package nucleus; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - -@UnitTest(target = ExperimentStatusConsole.class) -public class AT_ExperimentStatusConsole { - - - @Test - public void testInit() { - /* - * Rather than redirecting System.out, we leave this as a manual test - */ - } -} diff --git a/gcm3/src/test/java/nucleus/AT_NucleusError.java b/gcm3/src/test/java/nucleus/AT_NucleusError.java deleted file mode 100644 index 8d0e7b03b..000000000 --- a/gcm3/src/test/java/nucleus/AT_NucleusError.java +++ /dev/null @@ -1,61 +0,0 @@ -package nucleus; - -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -/** - * Test class for {@link NucleusError} - * - * @author Shawn Hatch - * - */ -@UnitTest(target = NucleusError.class) -public class AT_NucleusError { - - /** - * Tests {@link NucleusError#getDescription()} - */ - @Test - @UnitTestMethod(name = "getDescription", args = {}) - public void testGetDescription() { - // show that each ErrorType has a non-null, non-empty description - for (NucleusError nucleusError : NucleusError.values()) { - assertNotNull(nucleusError.getDescription()); - assertTrue(nucleusError.getDescription().length() > 0); - } - - // show that each description is unique (ignoring case as well) - Set descriptions = new LinkedHashSet<>(); - for (NucleusError nucleusError : NucleusError.values()) { - boolean isUnique = descriptions.add(nucleusError.getDescription().toLowerCase()); - assertTrue(isUnique, nucleusError+" duplicates the description of another member"); - } - } - - /** - * Tests {@link NucleusError#valueOf(String)} - */ - @Test - @UnitTestMethod(name = "valueOf", args = { String.class }) - public void testValueOf() { - // nothing to test - } - - /** - * Tests {@link NucleusError#values()} - */ - @Test - @UnitTestMethod(name = "values", args = {}) - public void testValues() { - // nothing to test - } - -} diff --git a/gcm3/src/test/java/nucleus/AT_Plugin.java b/gcm3/src/test/java/nucleus/AT_Plugin.java deleted file mode 100644 index 32c0b4dcc..000000000 --- a/gcm3/src/test/java/nucleus/AT_Plugin.java +++ /dev/null @@ -1,224 +0,0 @@ -package nucleus; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.function.Consumer; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - -@UnitTest(target = Plugin.class) -public class AT_Plugin { - - private static final class XPluginData implements PluginData { - - @Override - public PluginDataBuilder getCloneBuilder() { - return null; - } - - } - - private static enum PluginIds implements PluginId { - PLUGIN_ID_1, PLUGIN_ID_2, PLUGIN_ID_3, - } - - @Test - @UnitTestMethod(name = "builder", args = {}) - public void testBuilder() { - assertNotNull(Plugin.builder()); - } - - @Test - @UnitTestMethod(name = "getInitializer", args = {}) - public void testGetInitializer() { - Plugin plugin = Plugin .builder()// - .setPluginId(PluginIds.PLUGIN_ID_1)// - .build();// - - assertFalse(plugin.getInitializer().isPresent()); - - Consumer initializer = (c) -> { - }; - - plugin = Plugin .builder()// - .setPluginId(PluginIds.PLUGIN_ID_1)// - .setInitializer(initializer)// - .build();// - - assertTrue(plugin.getInitializer().isPresent()); - assertEquals(initializer, plugin.getInitializer().get()); - - } - - @Test - @UnitTestMethod(name = "getPluginDatas", args = {}) - public void testGetPluginDatas() { - Plugin plugin = Plugin .builder()// - .setPluginId(PluginIds.PLUGIN_ID_1)// - .build();// - - assertTrue(plugin.getPluginDatas().isEmpty()); - - XPluginData xPluginData1 = new XPluginData(); - XPluginData xPluginData2 = new XPluginData(); - Set expectedPluginDatas = new LinkedHashSet<>(); - expectedPluginDatas.add(xPluginData1); - expectedPluginDatas.add(xPluginData2); - - plugin = Plugin .builder()// - .setPluginId(PluginIds.PLUGIN_ID_1)// - .addPluginData(xPluginData1)// - .addPluginData(xPluginData2)// - .build();// - - assertEquals(expectedPluginDatas, plugin.getPluginDatas()); - } - - @Test - @UnitTestMethod(name = "getPluginDependencies", args = {}) - public void testGetPluginDependencies() { - Plugin plugin = Plugin .builder()// - .setPluginId(PluginIds.PLUGIN_ID_1)// - .build();// - - assertTrue(plugin.getPluginDependencies().isEmpty()); - - Set expectedPluginIds = new LinkedHashSet<>(); - expectedPluginIds.add(PluginIds.PLUGIN_ID_2); - expectedPluginIds.add(PluginIds.PLUGIN_ID_3); - - plugin = Plugin .builder()// - .setPluginId(PluginIds.PLUGIN_ID_1)// - .addPluginDependency(PluginIds.PLUGIN_ID_2)// - .addPluginDependency(PluginIds.PLUGIN_ID_3)// - .build();// - - assertEquals(expectedPluginIds, plugin.getPluginDependencies()); - } - - @Test - @UnitTestMethod(name = "getPluginId", args = {}) - public void testGetPluginId() { - - Plugin plugin = Plugin .builder()// - .setPluginId(PluginIds.PLUGIN_ID_1)// - .build();// - - assertEquals(PluginIds.PLUGIN_ID_1, plugin.getPluginId()); - - plugin = Plugin .builder()// - .setPluginId(PluginIds.PLUGIN_ID_2)// - .build();// - - assertEquals(PluginIds.PLUGIN_ID_2, plugin.getPluginId()); - - } - - @Test - @UnitTestMethod(target = Plugin.Builder.class, name = "addPluginData", args = { PluginData.class }) - public void testAddPluginData() { - - XPluginData xPluginData1 = new XPluginData(); - XPluginData xPluginData2 = new XPluginData(); - Set expectedPluginDatas = new LinkedHashSet<>(); - expectedPluginDatas.add(xPluginData1); - expectedPluginDatas.add(xPluginData2); - - Plugin plugin = Plugin .builder()// - .setPluginId(PluginIds.PLUGIN_ID_1)// - .addPluginData(xPluginData1)// - .addPluginData(xPluginData2)// - .build();// - - assertEquals(expectedPluginDatas, plugin.getPluginDatas()); - - // precondition test: if the plugin data is null - ContractException contractException = assertThrows(ContractException.class, () -> Plugin.builder().addPluginData(null)); - assertEquals(NucleusError.NULL_PLUGIN_DATA, contractException.getErrorType()); - } - - @Test - @UnitTestMethod(target = Plugin.Builder.class, name = "addPluginDependency", args = { PluginId.class }) - public void testAddPluginDependency() { - - Set expectedPluginIds = new LinkedHashSet<>(); - expectedPluginIds.add(PluginIds.PLUGIN_ID_2); - expectedPluginIds.add(PluginIds.PLUGIN_ID_3); - - Plugin plugin = Plugin .builder()// - .setPluginId(PluginIds.PLUGIN_ID_1)// - .addPluginDependency(PluginIds.PLUGIN_ID_2)// - .addPluginDependency(PluginIds.PLUGIN_ID_3)// - .build();// - - assertEquals(expectedPluginIds, plugin.getPluginDependencies()); - - // precondition test: if a plugin dependency is null - ContractException contractException = assertThrows(ContractException.class, () -> Plugin.builder().addPluginDependency(null)); - assertEquals(NucleusError.NULL_PLUGIN_ID, contractException.getErrorType()); - } - - @Test - @UnitTestMethod(target = Plugin.Builder.class, name = "build", args = {}) - public void testBuild() { - - Plugin plugin = Plugin.builder().setPluginId(PluginIds.PLUGIN_ID_1).build(); - assertNotNull(plugin); - - // precondition test: if the plugin id was not set - ContractException contractException = assertThrows(ContractException.class, () -> Plugin.builder().build()); - assertEquals(NucleusError.NULL_PLUGIN_ID, contractException.getErrorType()); - } - - @Test - @UnitTestMethod(target = Plugin.Builder.class, name = "setInitializer", args = { Consumer.class }) - public void testSetInitializer() { - - Consumer initializer = (c) -> { - }; - - Plugin plugin = Plugin .builder()// - .setPluginId(PluginIds.PLUGIN_ID_1)// - .setInitializer(initializer)// - .build();// - - assertTrue(plugin.getInitializer().isPresent()); - assertEquals(initializer, plugin.getInitializer().get()); - - // precondition test: if the initializer is null - ContractException contractException = assertThrows(ContractException.class, () -> Plugin.builder().setInitializer(null)); - assertEquals(NucleusError.NULL_PLUGIN_INITIALIZER, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(target = Plugin.Builder.class, name = "setPluginId", args = { PluginId.class }) - public void testSetPluginId() { - Plugin plugin = Plugin .builder()// - .setPluginId(PluginIds.PLUGIN_ID_1)// - .build();// - - assertEquals(PluginIds.PLUGIN_ID_1, plugin.getPluginId()); - - plugin = Plugin .builder()// - .setPluginId(PluginIds.PLUGIN_ID_2)// - .build();// - - assertEquals(PluginIds.PLUGIN_ID_2, plugin.getPluginId()); - - //precondition test: if the plugin id is null - assertThrows(ContractException.class,()->Plugin .builder().setPluginId(null)); - - } - -} diff --git a/gcm3/src/test/java/nucleus/AT_PluginContext.java b/gcm3/src/test/java/nucleus/AT_PluginContext.java deleted file mode 100644 index c37e8d67d..000000000 --- a/gcm3/src/test/java/nucleus/AT_PluginContext.java +++ /dev/null @@ -1,219 +0,0 @@ -package nucleus; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.function.Consumer; - -import org.junit.jupiter.api.Test; - -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestDataManager; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; -import util.wrappers.MutableBoolean; - -/** - * The PluginContext interface is implemented by the {@link Simulation}. These - * tests cover that implementation. - * - * - * @author Shawn Hatch - * - */ -@UnitTest(target = PluginContext.class) -public class AT_PluginContext { - - private static class TestDataManager1 extends TestDataManager { - - @Override - public void init(final DataManagerContext dataManagerContext) { - super.init(dataManagerContext); - } - } - - private static class TestDataManager2 extends TestDataManager { - - @Override - public void init(final DataManagerContext dataManagerContext) { - super.init(dataManagerContext); - } - } - - private static class TestDataManager3 extends TestDataManager { - - @Override - public void init(final DataManagerContext dataManagerContext) { - super.init(dataManagerContext); - } - } - - @Test - @UnitTestMethod(name = "addActor", args = { Consumer.class }) - public void testAddActor() { - - /* - * Create a plugin initializer that will add a few actors. Each actor - * will signal when it has initialized and the initializer will record - * that signal. - */ - Set addedActors = new LinkedHashSet<>(); - int numberOfActorsToAdd = 5; - - /* - * Create a plugin that has its initializer add 5 actors. Each actor - * will retrieve its own actor id and record them - */ - Plugin plugin = Plugin .builder()// - .setPluginId(new SimplePluginId("plugin id"))// - .setInitializer((c) -> { - for (int i = 0; i < numberOfActorsToAdd; i++) { - c.addActor((c2) -> { - addedActors.add(c2.getActorId()); - }); - } - })// - .build();// - - // build and execute the simulation - Simulation .builder()// - .addPlugin(plugin)// - .build()// - .execute();// - - // show that the correct number of actors were added to the simulation - assertEquals(numberOfActorsToAdd, addedActors.size()); - } - - @Test - @UnitTestMethod(name = "addDataManager", args = { DataManager.class }) - public void testAddDataManager() { - - /* - * The TestPluginInitialzer uses the PluginContext to add data managers. - * If we add data managers via the TestPlugin and have an actor show - * that each data manager exists during the simulation run, we can infer - * that the addDataManager of the plugin context must be working - * correctly. - */ - - final MutableBoolean actorExecuted = new MutableBoolean(); - - // add the actors to the action plugin - final TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - pluginDataBuilder.addTestDataManager("A", ()->new TestDataManager1()); - pluginDataBuilder.addTestDataManager("B", ()->new TestDataManager2()); - pluginDataBuilder.addTestDataManager("C", ()->new TestDataManager3()); - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - actorExecuted.setValue(true); - })); - - // build the action plugin - final TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - - // build and execute the engine - Simulation .builder()// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that the assertions were executed - assertTrue(actorExecuted.getValue()); - - } - - private static class PluginData1 implements PluginData { - - @Override - public PluginDataBuilder getCloneBuilder() { - throw new UnsupportedOperationException(); - } - - } - - private static class PluginData2 implements PluginData { - - @Override - public PluginDataBuilder getCloneBuilder() { - throw new UnsupportedOperationException(); - } - - } - - private static class PluginData3 implements PluginData { - @Override - public PluginDataBuilder getCloneBuilder() { - throw new UnsupportedOperationException(); - } - } - - private static class PluginData4 implements PluginData { - @Override - public PluginDataBuilder getCloneBuilder() { - throw new UnsupportedOperationException(); - } - } - - @Test - @UnitTestMethod(name = "getPluginData", args = { Class.class }) - public void testGetPluginData() { - - MutableBoolean assertionsExecuted = new MutableBoolean(); - - /* - * Create a plugin with an initialization method that that execute the - * getPluginData method for each three plugin data items. - */ - Plugin plugin = Plugin .builder()// - .setPluginId(new SimplePluginId("plugin id"))// - .setInitializer((c) -> { - assertNotNull(c.getPluginData(PluginData1.class)); - assertNotNull(c.getPluginData(PluginData2.class)); - assertNotNull(c.getPluginData(PluginData3.class)); - - ContractException contractException = assertThrows(ContractException.class,()->{ - c.getPluginData(PluginData.class); - }); - - assertEquals(NucleusError.AMBIGUOUS_PLUGIN_DATA_CLASS,contractException.getErrorType()); - - contractException = assertThrows(ContractException.class,()->{ - c.getPluginData(PluginData4.class); - }); - - assertEquals(NucleusError.UNKNOWN_PLUGIN_DATA_CLASS,contractException.getErrorType()); - - contractException = assertThrows(ContractException.class,()->{ - c.getPluginData(null); - }); - - assertEquals(NucleusError.NULL_PLUGIN_DATA_CLASS,contractException.getErrorType()); - - - assertionsExecuted.setValue(true); - })// - .addPluginData(new PluginData1())// - .addPluginData(new PluginData2())// - .addPluginData(new PluginData3())// - .build();// - - Simulation .builder()// - .addPlugin(plugin)// - .build()// - .execute();// - - // show that the initializer assertions were executed - assertTrue(assertionsExecuted.getValue()); - - } - -} diff --git a/gcm3/src/test/java/nucleus/AT_PluginData.java b/gcm3/src/test/java/nucleus/AT_PluginData.java deleted file mode 100644 index a9c8f23cf..000000000 --- a/gcm3/src/test/java/nucleus/AT_PluginData.java +++ /dev/null @@ -1,13 +0,0 @@ -package nucleus; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - -@UnitTest(target = PluginData.class) -public class AT_PluginData { - @Test - public void test() { - // nothing to test - } -} diff --git a/gcm3/src/test/java/nucleus/AT_PluginDataBuilder.java b/gcm3/src/test/java/nucleus/AT_PluginDataBuilder.java deleted file mode 100644 index 91a8359e6..000000000 --- a/gcm3/src/test/java/nucleus/AT_PluginDataBuilder.java +++ /dev/null @@ -1,13 +0,0 @@ -package nucleus; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - -@UnitTest(target = PluginDataBuilder.class) -public class AT_PluginDataBuilder { - @Test - public void test() { - // nothing to test - } -} diff --git a/gcm3/src/test/java/nucleus/AT_PluginId.java b/gcm3/src/test/java/nucleus/AT_PluginId.java deleted file mode 100644 index de4d9274c..000000000 --- a/gcm3/src/test/java/nucleus/AT_PluginId.java +++ /dev/null @@ -1,13 +0,0 @@ -package nucleus; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - -@UnitTest(target = PluginId.class) -public class AT_PluginId { - @Test - public void test() { - // nothing to test - } -} diff --git a/gcm3/src/test/java/nucleus/AT_ScenarioStatus.java b/gcm3/src/test/java/nucleus/AT_ScenarioStatus.java deleted file mode 100644 index 2ed222af0..000000000 --- a/gcm3/src/test/java/nucleus/AT_ScenarioStatus.java +++ /dev/null @@ -1,15 +0,0 @@ -package nucleus; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - -@UnitTest(target = ScenarioStatus.class) -public class AT_ScenarioStatus { - - @Test - public void test() { - //nothing to test - } - -} diff --git a/gcm3/src/test/java/nucleus/AT_SimplePluginId.java b/gcm3/src/test/java/nucleus/AT_SimplePluginId.java deleted file mode 100644 index e3a025ac7..000000000 --- a/gcm3/src/test/java/nucleus/AT_SimplePluginId.java +++ /dev/null @@ -1,57 +0,0 @@ -package nucleus; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -@UnitTest(target = SimplePluginId.class) -public class AT_SimplePluginId { - @Test - @UnitTestConstructor(args = { Object.class }) - public void testConstructor() { - assertThrows(RuntimeException.class, () -> new SimplePluginId(null)); - } - - @Test - @UnitTestMethod(name = "equals", args = { Object.class }) - public void testEquals() { - /* - * SimplePluginIds are equal if and only if their contained values are - * equal - */ - - SimplePluginId simplePluginId_1 = new SimplePluginId("A"); - SimplePluginId simplePluginId_2 = new SimplePluginId("B"); - SimplePluginId simplePluginId_3 = new SimplePluginId("A"); - - assertEquals(simplePluginId_1, simplePluginId_3); - assertNotEquals(simplePluginId_1, simplePluginId_2); - - } - - @Test - @UnitTestMethod(name = "hashCode", args = {}) - public void testHashCode() { - /* - * Equal objects have equal hash codes - */ - for (int i = 0; i < 20; i++) { - SimplePluginId simplePluginId_1 = new SimplePluginId(i); - SimplePluginId simplePluginId_2 = new SimplePluginId(i); - assertEquals(simplePluginId_1.hashCode(), simplePluginId_2.hashCode()); - } - } - - @Test - @UnitTestMethod(name = "toString", args = {}) - public void testToString() { - assertEquals("A",new SimplePluginId("A").toString()); - assertEquals("ASDF",new SimplePluginId("ASDF").toString()); - assertEquals(Integer.toString(12),new SimplePluginId(12).toString()); - } -} diff --git a/gcm3/src/test/java/nucleus/AT_Simulation.java b/gcm3/src/test/java/nucleus/AT_Simulation.java deleted file mode 100644 index 72d584936..000000000 --- a/gcm3/src/test/java/nucleus/AT_Simulation.java +++ /dev/null @@ -1,187 +0,0 @@ -package nucleus; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.function.Consumer; - -import org.junit.jupiter.api.Test; - -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; -import util.wrappers.MutableBoolean; - -/** - * Test unit for Engine. See the various Context tests for test's of engine's - * implementation of internal simulation behaviors. - * - * @author Shawn Hatch - * - */ -@UnitTest(target = Simulation.class) -public class AT_Simulation { - - @Test - @UnitTestMethod(name = "execute", args = {}) - public void testExecute() { - - // run the simulation - Simulation simulation = Simulation.builder().build(); - simulation.execute(); - - // precondition test - ContractException contractException = assertThrows(ContractException.class, () -> simulation.execute()); - assertEquals(NucleusError.REPEATED_EXECUTION, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "builder", args = {}) - public void testBuilder() { - assertNotNull(Simulation.builder()); - } - - @Test - @UnitTestMethod(target = Simulation.Builder.class, name = "build", args = {}) - public void testbuild() { - assertNotNull(Simulation.builder().build()); - } - - private static class PluginData1 implements PluginData { - @Override - public PluginDataBuilder getCloneBuilder() { - throw new UnsupportedOperationException(); - } - } - - private static class PluginData2 implements PluginData { - @Override - public PluginDataBuilder getCloneBuilder() { - throw new UnsupportedOperationException(); - } - } - - @Test - @UnitTestMethod(target = Simulation.Builder.class, name = "addPlugin", args = { Plugin.class }) - public void testAddPlugin() { - - /* - * Show that the plugin is added correctly by showing that its init - * method is invoked and that the plugin data are available from the - * plugin context. - */ - - MutableBoolean pluginAssertionsExecuted = new MutableBoolean(); - - Plugin plugin = Plugin .builder()// - .setPluginId(new SimplePluginId("plugin"))// - .addPluginData(new PluginData1())// - .addPluginData(new PluginData2())// - .setInitializer((c) -> { - assertNotNull(c.getPluginData(PluginData1.class)); - assertNotNull(c.getPluginData(PluginData2.class)); - pluginAssertionsExecuted.setValue(true); - })// - .build();// - - Simulation .builder()// - .addPlugin(plugin)// - .build()// - .execute(); - - /* - * Show that the initializer containing the assertions was executed - */ - assertTrue(pluginAssertionsExecuted.getValue()); - - } - - private static class LocalOutputConsumer implements Consumer { - - private final Set receivedItems = new LinkedHashSet<>(); - - @Override - public void accept(Object t) { - receivedItems.add(t); - } - - } - - @Test - @UnitTestMethod(target = Simulation.Builder.class, name = "setOutputConsumer", args = { Consumer.class }) - public void testSetOutputConsumer() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - /* - * Case 1 : there is a non-null output consumer added to the builder - */ - - Set expectedValues = new LinkedHashSet<>(); - expectedValues.add("A"); - expectedValues.add(4.5); - expectedValues.add(424.75F); - expectedValues.add(12); - expectedValues.add(122423533423423453L); - expectedValues.add(false); - - // have the added test agent produce some output - pluginBuilder.addTestActorPlan("Alpha", new TestActorPlan(1, (context) -> { - for (Object value : expectedValues) { - context.releaseOutput(value); - } - })); - - // create two output consumers to show that the builder will only use - // the last one - LocalOutputConsumer localOutputConsumer1 = new LocalOutputConsumer(); - LocalOutputConsumer localOutputConsumer2 = new LocalOutputConsumer(); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - // run the simulation - Simulation .builder()// - .addPlugin(testPlugin)// - .setOutputConsumer(localOutputConsumer1)// - .setOutputConsumer(localOutputConsumer2)// - .build()// - .execute();// - - // show that the first local output consumer did not receive any values - assertTrue(localOutputConsumer1.receivedItems.isEmpty()); - - // show that the second local output consumer received the expected - // values - assertTrue(localOutputConsumer2.receivedItems.containsAll(expectedValues)); - - /* - * Case 2 : there is null output consumer added to the builder - */ - - // show that the setting of null for the output consumer will yield no - // output - - LocalOutputConsumer localOutputConsumer3 = new LocalOutputConsumer(); - - Simulation .builder()// - .addPlugin(testPlugin)// - .setOutputConsumer(localOutputConsumer3)// - .setOutputConsumer(null)// - .build()// - .execute();// - - // show that the first local output consumer did not receive any values - assertTrue(localOutputConsumer3.receivedItems.isEmpty()); - - } - -} diff --git a/gcm3/src/test/java/nucleus/AT_SimulationContext.java b/gcm3/src/test/java/nucleus/AT_SimulationContext.java deleted file mode 100644 index 7340b79e9..000000000 --- a/gcm3/src/test/java/nucleus/AT_SimulationContext.java +++ /dev/null @@ -1,17 +0,0 @@ -package nucleus; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - -@UnitTest(target = SimulationContext.class) -public class AT_SimulationContext { - - @Test - public void test() { - /* - * Nothing to test. SimulationContext implemented by the simulation as - * either a DataManagerContext or an ActorContext - */ - } -} diff --git a/gcm3/src/test/java/nucleus/MT_Experiment.java b/gcm3/src/test/java/nucleus/MT_Experiment.java deleted file mode 100644 index d184275ca..000000000 --- a/gcm3/src/test/java/nucleus/MT_Experiment.java +++ /dev/null @@ -1,80 +0,0 @@ -package nucleus; - -import java.util.ArrayList; - -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; - -/** - * Manual experiment test focusing on the generation and handling of exceptions. - * - * @author Shawn Hatch - * - */ -public class MT_Experiment { - - private MT_Experiment() { - - } - - public static void main(String[] args) { - excecute(); - - } - - private static int counter; - - private static synchronized int incrementCounter() { - counter++; - return counter; - } - - private static void excecute() { - - // MutableInteger scenarioId = new MutableInteger(-1); - - // add two dimension to create six scenarios - Dimension dimension1 = Dimension.builder()// - .addLevel((c) -> { - return new ArrayList<>();// - }).addLevel((c) -> { - return new ArrayList<>();// - }).build(); - - Dimension dimension2 = Dimension.builder()// - .addLevel((c) -> { - return new ArrayList<>(); - }).addLevel((c) -> { - return new ArrayList<>(); - }).addLevel((c) -> { - return new ArrayList<>(); - }).build(); - - // use the test plugin to generate an agent - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - // have one of the six actors throw an exception - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - int index = incrementCounter(); - if (index == 3) { - throw new RuntimeException("test exception"); - } - })); - - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - // build and execute the experiment - Experiment .builder()// - .addPlugin(testPlugin).addDimension(dimension1)// - .addDimension(dimension2)// - .reportProgressToConsole(false)// - .reportFailuresToConsole(false)// - .setThreadCount(4)// - .build()// - .execute(); - - } - -} diff --git a/gcm3/src/test/java/nucleus/testsupport/testplugin/AT_TestActor.java b/gcm3/src/test/java/nucleus/testsupport/testplugin/AT_TestActor.java deleted file mode 100644 index 950ffd54a..000000000 --- a/gcm3/src/test/java/nucleus/testsupport/testplugin/AT_TestActor.java +++ /dev/null @@ -1,84 +0,0 @@ -package nucleus.testsupport.testplugin; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Optional; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import nucleus.ActorContext; -import nucleus.Experiment; -import nucleus.Plugin; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.wrappers.MultiKey; - -@UnitTest(target = TestActor.class) -public class AT_TestActor { - - @Test - @UnitTestMethod(name = "init", args = { ActorContext.class }) - public void testInit() { - // create two aliases - Object alias1 = "actor alias 1"; - Object alias2 = "actor alias 2"; - - // create containers for expected and actual observations - Set expectedObservations = new LinkedHashSet<>(); - expectedObservations.add(new MultiKey(alias1, 3.0)); - expectedObservations.add(new MultiKey(alias2, 3.0)); - expectedObservations.add(new MultiKey(alias1, 4.212)); - expectedObservations.add(new MultiKey(alias1, 5.123)); - expectedObservations.add(new MultiKey(alias2, 43.0)); - expectedObservations.add(new MultiKey(alias1, 12.123)); - expectedObservations.add(new MultiKey(alias1, 8.534)); - expectedObservations.add(new MultiKey(alias2, 1.423)); - - Set actualObservations = new LinkedHashSet<>(); - - // add the actors to the action plugin - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - /* - * Create ActorActionPlans from the expected observations. Each action - * plan will record a Multikey into the actual observations. - */ - for (MultiKey multiKey : expectedObservations) { - Object expectedAlias = multiKey.getKey(0); - Double expectedTime = multiKey.getKey(1); - pluginDataBuilder.addTestActorPlan(expectedAlias, new TestActorPlan(expectedTime, (c) -> { - actualObservations.add(new MultiKey(expectedAlias, c.getTime())); - })); - } - - // build the action plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - ExperimentPlanCompletionObserver experimentPlanCompletionObserver = new ExperimentPlanCompletionObserver(); - - // build and execute the engine - Experiment .builder()// - .reportProgressToConsole(false)// - .reportFailuresToConsole(false)// - .addOutputHandler(experimentPlanCompletionObserver::init)// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that all actions executed - Optional optional = experimentPlanCompletionObserver.getActionCompletionReport(0); - assertTrue(optional.isPresent(), "Scenario did not complete"); - - TestScenarioReport testScenarioReport = optional.get(); - assertTrue(testScenarioReport.isComplete(), "Some planned action were not executed"); - - // show that the actors executed the expected actions - assertEquals(expectedObservations, actualObservations); - - } - -} diff --git a/gcm3/src/test/java/nucleus/testsupport/testplugin/AT_TestActorPlan.java b/gcm3/src/test/java/nucleus/testsupport/testplugin/AT_TestActorPlan.java deleted file mode 100644 index 70e9278ca..000000000 --- a/gcm3/src/test/java/nucleus/testsupport/testplugin/AT_TestActorPlan.java +++ /dev/null @@ -1,151 +0,0 @@ -package nucleus.testsupport.testplugin; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.function.Consumer; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.random.RandomGeneratorProvider; - -@UnitTest(target = TestActorPlan.class) -public class AT_TestActorPlan { - - @Test - @UnitTestConstructor(args = { double.class, Consumer.class }) - public void testConstructor() { - - TestActorPlan testActorPlan = new TestActorPlan(0.0, (c) -> { - }); - assertTrue(testActorPlan.getKey().isPresent()); - } - - @Test - @UnitTestConstructor(args = { double.class, Consumer.class, boolean.class }) - public void testConstructor_withKeyControl() { - TestActorPlan testActorPlan = new TestActorPlan(0.0, (c) -> { - }, false); - assertFalse(testActorPlan.getKey().isPresent()); - - testActorPlan = new TestActorPlan(0.0, (c) -> { - }, true); - assertTrue(testActorPlan.getKey().isPresent()); - } - - @Test - @UnitTestConstructor(args = { TestActorPlan.class }) - public void testConstructor_fromExistingPlan() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7814286176804755234L); - - for (int i = 0; i < 100; i++) { - double scheduledTime = randomGenerator.nextDouble(); - boolean useKey = randomGenerator.nextBoolean(); - TestActorPlan originalTestActorPlan = new TestActorPlan(scheduledTime, (c) -> { - }, useKey); - TestActorPlan newTestActorPlan = new TestActorPlan(originalTestActorPlan); - - assertEquals(scheduledTime, newTestActorPlan.getScheduledTime()); - assertEquals(useKey, newTestActorPlan.getKey().isPresent()); - if (useKey) { - assertEquals(originalTestActorPlan.getKey().get(), newTestActorPlan.getKey().get()); - } - } - } - - /** - * Show that the agent action plan can be executed and will result in - * executed() returning true even if an exception is thrown in the plan. - */ - @Test - @UnitTestMethod(name = "executed", args = {}) - public void testExecuted() { - - TestActorPlan testActorPlan = new TestActorPlan(0.0, (c) -> { - }, false); - assertFalse(testActorPlan.executed()); - testActorPlan.executeAction(null); - assertTrue(testActorPlan.executed()); - - TestActorPlan testActorPlanWithException = new TestActorPlan(0.0, (c) -> { - throw new RuntimeException(); - }, false); - assertFalse(testActorPlanWithException.executed()); - assertThrows(RuntimeException.class, () -> testActorPlanWithException.executeAction(null)); - assertTrue(testActorPlanWithException.executed()); - } - - /** - * Show that action plans have or do not have keys as designed. Show that - * the keys are unique. - */ - @Test - @UnitTestMethod(name = "getKey", args = {}) - public void testGetKey() { - - /* - * Show that an agent action plan created to not have a key in fact does - * not have one - */ - TestActorPlan testActorPlan = new TestActorPlan(0.0, (c) -> { - }, false); - assertFalse(testActorPlan.getKey().isPresent()); - - /* - * Create a container to record the keys for the agent actions plans - * that will help us show that each key is unique - */ - Set keys = new LinkedHashSet<>(); - - // use the constructor with explicit inclusion of a key - for (int i = 0; i < 30; i++) { - testActorPlan = new TestActorPlan(0.0, (c) -> { - }, true); - assertTrue(testActorPlan.getKey().isPresent()); - boolean unique = keys.add(testActorPlan.getKey().get()); - assertTrue(unique); - } - - // use the constructor with implicit inclusion of a key - for (int i = 0; i < 30; i++) { - testActorPlan = new TestActorPlan(0.0, (c) -> { - }); - assertTrue(testActorPlan.getKey().isPresent()); - boolean unique = keys.add(testActorPlan.getKey().get()); - assertTrue(unique); - } - - } - - @Test - @UnitTestMethod(name = "getScheduledTime", args = {}) - public void testGetScheduledTime() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(918257164535899051L); - - // use the various constructors - for (int i = 0; i < 300; i++) { - double planTime = randomGenerator.nextDouble() * 1000; - TestActorPlan testActorPlan = new TestActorPlan(planTime, (c) -> { - }, true); - assertEquals(planTime, testActorPlan.getScheduledTime()); - - testActorPlan = new TestActorPlan(planTime, (c) -> { - }, false); - assertEquals(planTime, testActorPlan.getScheduledTime()); - - testActorPlan = new TestActorPlan(planTime, (c) -> { - }); - assertEquals(planTime, testActorPlan.getScheduledTime()); - } - - } -} diff --git a/gcm3/src/test/java/nucleus/testsupport/testplugin/AT_TestDataManager.java b/gcm3/src/test/java/nucleus/testsupport/testplugin/AT_TestDataManager.java deleted file mode 100644 index 589797508..000000000 --- a/gcm3/src/test/java/nucleus/testsupport/testplugin/AT_TestDataManager.java +++ /dev/null @@ -1,96 +0,0 @@ -package nucleus.testsupport.testplugin; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Optional; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import nucleus.ActorContext; -import nucleus.Experiment; -import nucleus.Plugin; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.wrappers.MultiKey; - -@UnitTest(target = TestActor.class) -public class AT_TestDataManager { - - private static class TestDataManagerType1 extends TestDataManager { - - } - - private static class TestDataManagerType2 extends TestDataManager { - - } - - @Test - @UnitTestMethod(name = "init", args = { ActorContext.class }) - public void testInit() { - // create two aliases - Object alias1 = "alias 1"; - Object alias2 = "alias 2"; - - // create containers for expected and actual observations - Set expectedObservations = new LinkedHashSet<>(); - expectedObservations.add(new MultiKey(alias1, 3.0)); - expectedObservations.add(new MultiKey(alias2, 3.0)); - expectedObservations.add(new MultiKey(alias1, 4.212)); - expectedObservations.add(new MultiKey(alias1, 5.123)); - expectedObservations.add(new MultiKey(alias2, 43.0)); - expectedObservations.add(new MultiKey(alias1, 12.123)); - expectedObservations.add(new MultiKey(alias1, 8.534)); - expectedObservations.add(new MultiKey(alias2, 1.423)); - - Set actualObservations = new LinkedHashSet<>(); - - // add the actors to the action plugin - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - pluginDataBuilder.addTestDataManager(alias1, ()->new TestDataManagerType1()); - pluginDataBuilder.addTestDataManager(alias2, ()->new TestDataManagerType2()); - - /* - * Create ActorActionPlans from the expected observations. Each action - * plan will record a Multikey into the actual observations. - */ - for (MultiKey multiKey : expectedObservations) { - Object expectedAlias = multiKey.getKey(0); - Double expectedTime = multiKey.getKey(1); - pluginDataBuilder.addTestDataManagerPlan(expectedAlias, new TestDataManagerPlan(expectedTime, (c) -> { - actualObservations.add(new MultiKey(expectedAlias, c.getTime())); - })); - } - - - - // build the action plugin - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - ExperimentPlanCompletionObserver experimentPlanCompletionObserver = new ExperimentPlanCompletionObserver(); - - // build and execute the engine - Experiment .builder()// - .reportProgressToConsole(false)// - .reportFailuresToConsole(false)// - .addOutputHandler(experimentPlanCompletionObserver::init)// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that all actions executed - Optional optional = experimentPlanCompletionObserver.getActionCompletionReport(0); - assertTrue(optional.isPresent(), "Scenario did not complete"); - - TestScenarioReport testScenarioReport = optional.get(); - assertTrue(testScenarioReport.isComplete(), "Some plans were not executed"); - - // show that the actors executed the expected actions - assertEquals(expectedObservations, actualObservations); - - } - -} diff --git a/gcm3/src/test/java/nucleus/testsupport/testplugin/AT_TestDataManagerPlan.java b/gcm3/src/test/java/nucleus/testsupport/testplugin/AT_TestDataManagerPlan.java deleted file mode 100644 index cc47ca53c..000000000 --- a/gcm3/src/test/java/nucleus/testsupport/testplugin/AT_TestDataManagerPlan.java +++ /dev/null @@ -1,150 +0,0 @@ -package nucleus.testsupport.testplugin; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.function.Consumer; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.random.RandomGeneratorProvider; - -@UnitTest(target = TestDataManagerPlan.class) -public class AT_TestDataManagerPlan { - - @Test - @UnitTestConstructor(args = { double.class, Consumer.class }) - public void testConstructor() { - - TestDataManagerPlan testDataManagerPlan = new TestDataManagerPlan(0.0, (c) -> { - }); - assertTrue(testDataManagerPlan.getKey().isPresent()); - } - - @Test - @UnitTestConstructor(args = { double.class, Consumer.class, boolean.class }) - public void testConstructor_withKeyControl() { - TestDataManagerPlan testDataManagerPlan = new TestDataManagerPlan(0.0, (c) -> { - }, false); - assertFalse(testDataManagerPlan.getKey().isPresent()); - - testDataManagerPlan = new TestDataManagerPlan(0.0, (c) -> { - }, true); - assertTrue(testDataManagerPlan.getKey().isPresent()); - } - - @Test - @UnitTestConstructor(args = { TestDataManagerPlan.class }) - public void testConstructor_fromExistingPlan() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7814286176804755234L); - - for (int i = 0; i < 100; i++) { - double scheduledTime = randomGenerator.nextDouble(); - boolean useKey = randomGenerator.nextBoolean(); - TestDataManagerPlan originalTestDataManagerPlan = new TestDataManagerPlan(scheduledTime, (c) -> { - }, useKey); - TestDataManagerPlan newTestDataManagerPlan = new TestDataManagerPlan(originalTestDataManagerPlan); - - assertEquals(scheduledTime, newTestDataManagerPlan.getScheduledTime()); - assertEquals(useKey, newTestDataManagerPlan.getKey().isPresent()); - if (useKey) { - assertEquals(originalTestDataManagerPlan.getKey().get(), newTestDataManagerPlan.getKey().get()); - } - } - } - - - @Test - @UnitTestMethod(name = "executed", args = {}) - public void testExecuted() { - - TestDataManagerPlan testDataManagerPlan = new TestDataManagerPlan(0.0, (c) -> { - }, false); - assertFalse(testDataManagerPlan.executed()); - testDataManagerPlan.executeAction(null); - assertTrue(testDataManagerPlan.executed()); - - TestDataManagerPlan testDataManagerPlanWithException = new TestDataManagerPlan(0.0, (c) -> { - throw new RuntimeException(); - }, false); - assertFalse(testDataManagerPlanWithException.executed()); - assertThrows(RuntimeException.class, () -> testDataManagerPlanWithException.executeAction(null)); - assertTrue(testDataManagerPlanWithException.executed()); - } - - /** - * Show that action plans have or do not have keys as designed. Show that - * the keys are unique. - */ - @Test - @UnitTestMethod(name = "getKey", args = {}) - public void testGetKey() { - - /* - * Show that an agent action plan created to not have a key in fact does - * not have one - */ - TestDataManagerPlan testDataManagerPlan = new TestDataManagerPlan(0.0, (c) -> { - }, false); - assertFalse(testDataManagerPlan.getKey().isPresent()); - - /* - * Create a container to record the keys for the agent actions plans - * that will help us show that each key is unique - */ - Set keys = new LinkedHashSet<>(); - - // use the constructor with explicit inclusion of a key - for (int i = 0; i < 30; i++) { - testDataManagerPlan = new TestDataManagerPlan(0.0, (c) -> { - }, true); - assertTrue(testDataManagerPlan.getKey().isPresent()); - boolean unique = keys.add(testDataManagerPlan.getKey().get()); - assertTrue(unique); - } - - // use the constructor with implicit inclusion of a key - for (int i = 0; i < 30; i++) { - testDataManagerPlan = new TestDataManagerPlan(0.0, (c) -> { - }); - assertTrue(testDataManagerPlan.getKey().isPresent()); - boolean unique = keys.add(testDataManagerPlan.getKey().get()); - assertTrue(unique); - } - - } - - @Test - @UnitTestMethod(name = "getScheduledTime", args = {}) - public void testGetScheduledTime() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(9009925072863118451L); - - // use the various constructors - for (int i = 0; i < 300; i++) { - double planTime = randomGenerator.nextDouble() * 1000; - TestDataManagerPlan testDataManagerPlan = new TestDataManagerPlan(planTime, (c) -> { - }, true); - assertEquals(planTime, testDataManagerPlan.getScheduledTime()); - - testDataManagerPlan = new TestDataManagerPlan(planTime, (c) -> { - }, false); - assertEquals(planTime, testDataManagerPlan.getScheduledTime()); - - testDataManagerPlan = new TestDataManagerPlan(planTime, (c) -> { - }); - assertEquals(planTime, testDataManagerPlan.getScheduledTime()); - } - - } - - -} diff --git a/gcm3/src/test/java/nucleus/testsupport/testplugin/AT_TestError.java b/gcm3/src/test/java/nucleus/testsupport/testplugin/AT_TestError.java deleted file mode 100644 index f1d2b2f14..000000000 --- a/gcm3/src/test/java/nucleus/testsupport/testplugin/AT_TestError.java +++ /dev/null @@ -1,34 +0,0 @@ -package nucleus.testsupport.testplugin; - -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = TestError.class) -public class AT_TestError { - - @Test - @UnitTestMethod(name = "getDescription", args = {}) - public void testGetDescription() { - // show that each ErrorType has a non-null, non-empty description - for (TestError nucleusError : TestError.values()) { - assertNotNull(nucleusError.getDescription()); - assertTrue(nucleusError.getDescription().length() > 0); - } - - // show that each description is unique (ignoring case as well) - Set descriptions = new LinkedHashSet<>(); - for (TestError nucleusError : TestError.values()) { - assertTrue(descriptions.add(nucleusError.getDescription().toLowerCase()), nucleusError+": "+"Duplicate ErrorType description: " + nucleusError.getDescription()); - } - } - - -} diff --git a/gcm3/src/test/java/nucleus/testsupport/testplugin/AT_TestExperimentObserver.java b/gcm3/src/test/java/nucleus/testsupport/testplugin/AT_TestExperimentObserver.java deleted file mode 100644 index 4dc53f717..000000000 --- a/gcm3/src/test/java/nucleus/testsupport/testplugin/AT_TestExperimentObserver.java +++ /dev/null @@ -1,172 +0,0 @@ -package nucleus.testsupport.testplugin; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.function.BiConsumer; -import java.util.function.Consumer; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.ExperimentContext; -import nucleus.ScenarioStatus; -import nucleus.util.TriConsumer; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.random.RandomGeneratorProvider; - -@UnitTest(target = ExperimentPlanCompletionObserver.class) -public class AT_TestExperimentObserver { - - private static class MetaOutputConsumer { - private MetaOutputConsumer(TriConsumer consumer) { - this.consumer = consumer; - } - - private TriConsumer consumer; - - @SuppressWarnings("unchecked") - public void accept(ExperimentContext experimentContext, Integer scenarioId, Object object) { - consumer.accept(experimentContext, scenarioId, (T) object); - } - } - - private static class MockExperimentContext implements ExperimentContext { - - @Override - public void subscribeToSimulationOpen(BiConsumer consumer) { - throw new UnsupportedOperationException(); - } - - @Override - public void subscribeToSimulationClose(BiConsumer consumer) { - throw new UnsupportedOperationException(); - - } - - @Override - public void subscribeToExperimentOpen(Consumer consumer) { - throw new UnsupportedOperationException(); - - } - - @Override - public void subscribeToExperimentClose(Consumer consumer) { - throw new UnsupportedOperationException(); - - } - - public void sendTestScenarioReport(Integer scenarioId, TestScenarioReport testScenarioReport) { - metaOutputConsumer.accept(this, scenarioId, testScenarioReport); - } - - private MetaOutputConsumer metaOutputConsumer; - - @Override - public void subscribeToOutput(Class outputClass, TriConsumer consumer) { - - metaOutputConsumer = new MetaOutputConsumer(consumer); - } - - @Override - public Optional getScenarioStatus(int scenarioId) { - throw new UnsupportedOperationException(); - } - - @Override - public int getStatusCount(ScenarioStatus scenarioStatus) { - throw new UnsupportedOperationException(); - } - - @Override - public double getElapsedSeconds() { - throw new UnsupportedOperationException(); - } - - @Override - public Optional> getScenarioMetaData(int scenarioId) { - throw new UnsupportedOperationException(); - } - - @Override - public List getExperimentMetaData() { - throw new UnsupportedOperationException(); - } - - @Override - public int getScenarioCount() { - throw new UnsupportedOperationException(); - } - - @Override - public List getScenarios(ScenarioStatus scenarioStatus) { - throw new UnsupportedOperationException(); - } - - @Override - public Optional getScenarioFailureCause(int scenarioId) { - return Optional.empty(); - } - - - } - - @Test - @UnitTestMethod(name = "init", args = { ExperimentContext.class }) - public void testInit() { - // nothing to test : covered by test of getActionCompletionReport - } - - @Test - @UnitTestMethod(name = "getActionCompletionReport", args = { Integer.class }) - public void getActionCompletionReport() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7048252990105726149L); - - //create a mock experiment that has the minimal capability - MockExperimentContext mockExperimentContext = new MockExperimentContext(); - - //create a TestExperimentObserver to test - ExperimentPlanCompletionObserver experimentPlanCompletionObserver = new ExperimentPlanCompletionObserver(); - experimentPlanCompletionObserver.init(mockExperimentContext); - - //create TestScenarioReport items using random scenario id and completion states - Map expectedTestScenarioReports = new LinkedHashMap<>(); - for (int i = 0; i < 10; i++) { - Integer scenarioId = randomGenerator.nextInt(1000); - Boolean completion = randomGenerator.nextBoolean(); - TestScenarioReport expectedTestScenarioReport = new TestScenarioReport(completion); - expectedTestScenarioReports.put(scenarioId, expectedTestScenarioReport); - } - - //send the TestScenarioReport items to the TestExperimentObserver - for(Integer scenarioId : expectedTestScenarioReports.keySet()) { - TestScenarioReport expectedTestScenarioReport = expectedTestScenarioReports.get(scenarioId); - mockExperimentContext.sendTestScenarioReport(scenarioId, expectedTestScenarioReport); - } - - //show that the correct TestScenarioReport items can be retrieved - for(Integer scenarioId : expectedTestScenarioReports.keySet()) { - TestScenarioReport expectedTestScenarioReport = expectedTestScenarioReports.get(scenarioId); - Optional optional = experimentPlanCompletionObserver.getActionCompletionReport(scenarioId); - assertTrue(optional.isPresent()); - TestScenarioReport actualTestScenarioReport = optional.get(); - assertEquals(expectedTestScenarioReport, actualTestScenarioReport); - } - - //show that an unknown scenario id will not retrieve TestScenarioReport items - - Optional optional = experimentPlanCompletionObserver.getActionCompletionReport(1000); - assertFalse(optional.isPresent()); - - optional = experimentPlanCompletionObserver.getActionCompletionReport(null); - assertFalse(optional.isPresent()); - - - } -} diff --git a/gcm3/src/test/java/nucleus/testsupport/testplugin/AT_TestPluginData.java b/gcm3/src/test/java/nucleus/testsupport/testplugin/AT_TestPluginData.java deleted file mode 100644 index 5eb16bc68..000000000 --- a/gcm3/src/test/java/nucleus/testsupport/testplugin/AT_TestPluginData.java +++ /dev/null @@ -1,346 +0,0 @@ -package nucleus.testsupport.testplugin; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Optional; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = TestPluginData.class) -public class AT_TestPluginData { - - private static class TestDataManager1 extends TestDataManager { - - } - - private static class TestDataManager2 extends TestDataManager { - - } - - private static class TestDataManager3 extends TestDataManager { - - } - - @Test - @UnitTestMethod(name = "builder", args = {}) - public void testBuilder() { - assertNotNull(TestPluginData.builder()); - } - - @Test - @UnitTestMethod(name = "getTestDataManagerType", args = { Object.class }) - public void testGetTestDataManagerType() { - TestPluginData testPluginData = TestPluginData .builder()// - .addTestDataManager("A", () -> new TestDataManager1())// - .addTestDataManager("B", () -> new TestDataManager2())// - .addTestDataManager("C", () -> new TestDataManager3())// - .build();// - - // show that the aliased data manager types are retrievable - Optional optional1 = testPluginData.getTestDataManager("A"); - assertTrue(optional1.isPresent()); - - Optional optional2 = testPluginData.getTestDataManager("B"); - assertTrue(optional2.isPresent()); - - Optional optional3 = testPluginData.getTestDataManager("C"); - assertTrue(optional3.isPresent()); - - Optional optional4 = testPluginData.getTestDataManager("D"); - assertFalse(optional4.isPresent()); - - } - - @Test - @UnitTestMethod(name = "getTestActorPlans", args = { Object.class }) - public void testGetTestActorPlans() { - // create a few TestActorPlan items associated with two aliases - Map> expectedTestActorPlans = new LinkedHashMap<>(); - Set testActorPlans = new LinkedHashSet<>(); - expectedTestActorPlans.put("actor1", testActorPlans); - - testActorPlans.add(new TestActorPlan(1, (c) -> { - })); - testActorPlans.add(new TestActorPlan(2, (c) -> { - })); - testActorPlans.add(new TestActorPlan(3, (c) -> { - })); - - testActorPlans = new LinkedHashSet<>(); - expectedTestActorPlans.put("actor2", testActorPlans); - testActorPlans.add(new TestActorPlan(4, (c) -> { - })); - testActorPlans.add(new TestActorPlan(5, (c) -> { - })); - - // Build the plugin data from the items above - TestPluginData.Builder builder = TestPluginData.builder();// - - for (String alias : expectedTestActorPlans.keySet()) { - testActorPlans = expectedTestActorPlans.get(alias); - for (TestActorPlan testActorPlan : testActorPlans) { - builder.addTestActorPlan(alias, testActorPlan); - } - } - - TestPluginData testPluginData = builder.build(); - - // show that the plans associated with each actors are correct - for (String alias : expectedTestActorPlans.keySet()) { - Set expectedPlans = expectedTestActorPlans.get(alias); - Set actualPlans = new LinkedHashSet<>(testPluginData.getTestActorPlans(alias)); - assertEquals(expectedPlans, actualPlans); - } - - } - - @Test - @UnitTestMethod(name = "getTestActorAliases", args = {}) - public void testGetTestActorAliases() { - - Set expectedAliases = new LinkedHashSet<>(); - expectedAliases.add("A"); - expectedAliases.add("B"); - expectedAliases.add("C"); - - TestPluginData.Builder builder = TestPluginData.builder();// - for (Object alias : expectedAliases) { - builder.addTestActorPlan(alias, new TestActorPlan(0, (c) -> { - })); - } - - TestPluginData testPluginData = builder.build(); - - LinkedHashSet actualAliases = new LinkedHashSet<>(testPluginData.getTestActorAliases()); - assertEquals(expectedAliases, actualAliases); - - } - - @Test - @UnitTestMethod(name = "getCloneBuilder", args = {}) - public void testGetCloneBuilder() { - - TestPluginData.Builder builder = TestPluginData.builder();// - // add actors - - builder.addTestActorPlan("A", new TestActorPlan(0, (c) -> { - })); - - builder.addTestActorPlan("B", new TestActorPlan(0, (c) -> { - })); - builder.addTestActorPlan("B", new TestActorPlan(0, (c) -> { - })); - builder.addTestActorPlan("C", new TestActorPlan(0, (c) -> { - })); - - // add data managers - builder.addTestDataManager("D", ()->new TestDataManager1()); - builder.addTestDataManagerPlan("D", new TestDataManagerPlan(0, (c) -> { - })); - builder.addTestDataManager("E", ()->new TestDataManager2()); - builder.addTestDataManagerPlan("E", new TestDataManagerPlan(0, (c) -> { - })); - builder.addTestDataManagerPlan("E", new TestDataManagerPlan(0, (c) -> { - })); - builder.addTestDataManager("F", ()->new TestDataManager3()); - - // build the plugin data - TestPluginData testPluginData = builder.build(); - - // show that the clone builder is properly initialized -- i.e. it will - // immediately build a clone of the plugin data - TestPluginData.Builder cloneBuilder = testPluginData.getCloneBuilder(); - assertNotNull(cloneBuilder); - TestPluginData testPluginData2 = cloneBuilder.build(); - assertEquals(testPluginData, testPluginData2); - - } - - @Test - @UnitTestMethod(name = "getTestDataManagerPlans", args = { Object.class }) - public void testGetTestDataManagerPlans() { - // create a few plans - Map> testDataManagerPlanMap = new LinkedHashMap<>(); - Set testDataManagerPlans = new LinkedHashSet<>(); - testDataManagerPlans.add(new TestDataManagerPlan(0, (c) -> { - })); - testDataManagerPlans.add(new TestDataManagerPlan(1, (c) -> { - })); - testDataManagerPlanMap.put("A", testDataManagerPlans); - - testDataManagerPlans = new LinkedHashSet<>(); - testDataManagerPlans.add(new TestDataManagerPlan(2, (c) -> { - })); - testDataManagerPlans.add(new TestDataManagerPlan(3, (c) -> { - })); - testDataManagerPlans.add(new TestDataManagerPlan(4, (c) -> { - })); - testDataManagerPlanMap.put("B", testDataManagerPlans); - - // add those plans to the builder - TestPluginData.Builder builder = TestPluginData.builder(); - for (Object alias : testDataManagerPlanMap.keySet()) { - testDataManagerPlans = testDataManagerPlanMap.get(alias); - for (TestDataManagerPlan testDataManagerPlan : testDataManagerPlans) { - builder.addTestDataManagerPlan(alias, testDataManagerPlan); - } - } - - builder.addTestDataManager("A", ()->new TestDataManager1()); - builder.addTestDataManager("B", ()->new TestDataManager2()); - - TestPluginData testPluginData = builder.build(); - - // show that the plugin data contains the expected plans - for (Object alias : testDataManagerPlanMap.keySet()) { - Set expectedPlans = testDataManagerPlanMap.get(alias); - Set actualPlans = new LinkedHashSet<>(testPluginData.getTestDataManagerPlans(alias)); - assertEquals(expectedPlans, actualPlans); - } - - } - - @Test - @UnitTestMethod(name = "getTestDataManagerAliases", args = {}) - public void testGetTestDataManagerAliases() { - Set expectedAliases = new LinkedHashSet<>(); - - TestPluginData.Builder builder = TestPluginData.builder(); - expectedAliases.add("A"); - builder.addTestDataManager("A", ()->new TestDataManager1()); - expectedAliases.add("B"); - builder.addTestDataManager("B", ()->new TestDataManager2()); - expectedAliases.add("C"); - builder.addTestDataManager("C", ()->new TestDataManager3()); - TestPluginData testPluginData = builder.build(); - - LinkedHashSet actualAliases = new LinkedHashSet<>(testPluginData.getTestDataManagerAliases()); - assertEquals(expectedAliases, actualAliases); - - } - - @Test - @UnitTestMethod(target = TestPluginData.Builder.class, name = "addTestActorPlan", args = { Object.class, TestActorPlan.class }) - public void testAddTestActorPlan() { - // create a few plans - Map> testDataManagerPlanMap = new LinkedHashMap<>(); - Set testDataManagerPlans = new LinkedHashSet<>(); - testDataManagerPlans.add(new TestDataManagerPlan(0, (c) -> { - })); - testDataManagerPlans.add(new TestDataManagerPlan(1, (c) -> { - })); - testDataManagerPlanMap.put("A", testDataManagerPlans); - - testDataManagerPlans = new LinkedHashSet<>(); - testDataManagerPlans.add(new TestDataManagerPlan(2, (c) -> { - })); - testDataManagerPlans.add(new TestDataManagerPlan(3, (c) -> { - })); - testDataManagerPlans.add(new TestDataManagerPlan(4, (c) -> { - })); - testDataManagerPlanMap.put("B", testDataManagerPlans); - - // add those plans to the builder - TestPluginData.Builder builder = TestPluginData.builder(); - for (Object alias : testDataManagerPlanMap.keySet()) { - testDataManagerPlans = testDataManagerPlanMap.get(alias); - for (TestDataManagerPlan testDataManagerPlan : testDataManagerPlans) { - builder.addTestDataManagerPlan(alias, testDataManagerPlan); - } - } - - builder.addTestDataManager("A", ()->new TestDataManager1()); - builder.addTestDataManager("B", ()->new TestDataManager2()); - - TestPluginData testPluginData = builder.build(); - - // show that the plugin data contains the expected plans - for (Object alias : testDataManagerPlanMap.keySet()) { - Set expectedPlans = testDataManagerPlanMap.get(alias); - Set actualPlans = new LinkedHashSet<>(testPluginData.getTestDataManagerPlans(alias)); - assertEquals(expectedPlans, actualPlans); - } - } - - @Test - @UnitTestMethod(target = TestPluginData.Builder.class, name = "addTestDataManager", args = { Object.class, Class.class }) - public void testAddTestDataManager() { - // create a few plans - LinkedHashSet expectedDataManagerAliases = new LinkedHashSet<>(); - expectedDataManagerAliases.add("A"); - expectedDataManagerAliases.add("B"); - expectedDataManagerAliases.add("C"); - - // add those plans to the builder - TestPluginData.Builder builder = TestPluginData.builder(); - builder.addTestDataManager("A",()->new TestDataManager1()); - builder.addTestDataManager("B",()->new TestDataManager2()); - builder.addTestDataManager("C",()->new TestDataManager3()); - - TestPluginData testPluginData = builder.build(); - - // show that the plugin data contains the expected plans - LinkedHashSet actualDataManagerAliases = new LinkedHashSet<>(testPluginData.getTestDataManagerAliases()); - assertEquals(expectedDataManagerAliases, actualDataManagerAliases); - } - - @Test - @UnitTestMethod(target = TestPluginData.Builder.class, name = "addTestDataManagerPlan", args = { Object.class, TestDataManagerPlan.class }) - public void testAddTestDataManagerPlan() { - // create a few plans - Map> testDataManagerPlanMap = new LinkedHashMap<>(); - Set testDataManagerPlans = new LinkedHashSet<>(); - testDataManagerPlans.add(new TestDataManagerPlan(0, (c) -> { - })); - testDataManagerPlans.add(new TestDataManagerPlan(1, (c) -> { - })); - testDataManagerPlanMap.put("A", testDataManagerPlans); - - testDataManagerPlans = new LinkedHashSet<>(); - testDataManagerPlans.add(new TestDataManagerPlan(2, (c) -> { - })); - testDataManagerPlans.add(new TestDataManagerPlan(3, (c) -> { - })); - testDataManagerPlans.add(new TestDataManagerPlan(4, (c) -> { - })); - testDataManagerPlanMap.put("B", testDataManagerPlans); - - // add those plans to the builder - TestPluginData.Builder builder = TestPluginData.builder(); - for (Object alias : testDataManagerPlanMap.keySet()) { - testDataManagerPlans = testDataManagerPlanMap.get(alias); - for (TestDataManagerPlan testDataManagerPlan : testDataManagerPlans) { - builder.addTestDataManagerPlan(alias, testDataManagerPlan); - } - } - - builder.addTestDataManager("A", ()->new TestDataManager1()); - builder.addTestDataManager("B", ()->new TestDataManager2()); - - TestPluginData testPluginData = builder.build(); - - // show that the plugin data contains the expected plans - for (Object alias : testDataManagerPlanMap.keySet()) { - Set expectedPlans = testDataManagerPlanMap.get(alias); - Set actualPlans = new LinkedHashSet<>(testPluginData.getTestDataManagerPlans(alias)); - assertEquals(expectedPlans, actualPlans); - } - } - - @Test - @UnitTestMethod(target = TestPluginData.Builder.class, name = "build", args = {}) - public void testBuild() { - // covered by other tests - } - -} diff --git a/gcm3/src/test/java/nucleus/testsupport/testplugin/AT_TestPluginDataManager.java b/gcm3/src/test/java/nucleus/testsupport/testplugin/AT_TestPluginDataManager.java deleted file mode 100644 index dfdd0b39d..000000000 --- a/gcm3/src/test/java/nucleus/testsupport/testplugin/AT_TestPluginDataManager.java +++ /dev/null @@ -1,131 +0,0 @@ -package nucleus.testsupport.testplugin; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = TestPlanDataManager.class) -public class AT_TestPluginDataManager { - private static class TestDataManager1 extends TestDataManager { - - } - - private static class TestDataManager2 extends TestDataManager { - - } - - - - - @Test - @UnitTestConstructor(args = { TestPluginData.class }) - public void test_Constructor() { - // covered by other tests - } - - @Test - @UnitTestMethod(name = "getActorActionPlans", args = { Object.class }) - public void testGetActorActionPlans() { - // create a few TestActorPlan items associated with two aliases - Map> expectedTestActorPlans = new LinkedHashMap<>(); - Set testActorPlans = new LinkedHashSet<>(); - expectedTestActorPlans.put("actor1", testActorPlans); - - testActorPlans.add(new TestActorPlan(1, (c) -> { - })); - testActorPlans.add(new TestActorPlan(2, (c) -> { - })); - testActorPlans.add(new TestActorPlan(3, (c) -> { - })); - - testActorPlans = new LinkedHashSet<>(); - expectedTestActorPlans.put("actor2", testActorPlans); - testActorPlans.add(new TestActorPlan(4, (c) -> { - })); - testActorPlans.add(new TestActorPlan(5, (c) -> { - })); - - // Build the plugin data from the items above - TestPluginData.Builder builder = TestPluginData .builder(); - - for (String alias : expectedTestActorPlans.keySet()) { - testActorPlans = expectedTestActorPlans.get(alias); - for (TestActorPlan testActorPlan : testActorPlans) { - builder.addTestActorPlan(alias, testActorPlan); - } - } - - TestPluginData testPluginData = builder.build(); - TestPlanDataManager testPlanDataManager = new TestPlanDataManager(testPluginData); - - // show that the plans associated with each actors are correct - for (String alias : expectedTestActorPlans.keySet()) { - Set expectedPlans = expectedTestActorPlans.get(alias); - Set actualPlans = new LinkedHashSet<>(testPlanDataManager.getTestActorPlans(alias)); - assertEquals(expectedPlans, actualPlans); - } - } - - - - @Test - @UnitTestMethod(name = "getTestDataManagerPlans", args = { Object.class }) - public void testGetTestDataManagerPlans() { - // build a few test data manager plans - Map> planMap = new LinkedHashMap<>(); - Set planSet = new LinkedHashSet<>(); - planSet.add(new TestDataManagerPlan(0, (c) -> { - })); - planSet.add(new TestDataManagerPlan(1, (c) -> { - })); - planMap.put("A", planSet); - planSet = new LinkedHashSet<>(); - planSet.add(new TestDataManagerPlan(2, (c) -> { - })); - planSet.add(new TestDataManagerPlan(3, (c) -> { - })); - planSet.add(new TestDataManagerPlan(4, (c) -> { - })); - planMap.put("B", planSet); - - // add them to a test plugin data - TestPluginData.Builder builder = TestPluginData.builder(); - for (Object alias : planMap.keySet()) { - - planSet = planMap.get(alias); - for(TestDataManagerPlan testDataManagerPlan : planSet) { - builder.addTestDataManagerPlan(alias, testDataManagerPlan); - } - } - builder.addTestDataManager("A", ()-> new TestDataManager1()); - - builder.addTestDataManager("B", ()-> new TestDataManager2()); - - TestPluginData testPluginData = builder.build(); - - //create the test plugin data manager - TestPlanDataManager testPlanDataManager = new TestPlanDataManager(testPluginData); - - //show that the correct plans are stored - for (Object alias : planMap.keySet()) { - Set expectedPlans = planMap.get(alias); - Set actualPlans = new LinkedHashSet<>(testPlanDataManager.getTestDataManagerPlans(alias)); - assertEquals(expectedPlans, actualPlans); - } - } - - - - - - -} diff --git a/gcm3/src/test/java/nucleus/testsupport/testplugin/AT_TestPluginId.java b/gcm3/src/test/java/nucleus/testsupport/testplugin/AT_TestPluginId.java deleted file mode 100644 index 504ea22de..000000000 --- a/gcm3/src/test/java/nucleus/testsupport/testplugin/AT_TestPluginId.java +++ /dev/null @@ -1,17 +0,0 @@ -package nucleus.testsupport.testplugin; - -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - -@UnitTest(target = TestPluginId.class) -public class AT_TestPluginId { - - @Test - public void test() { - assertNotNull(TestPluginId.PLUGIN_ID); - } - -} diff --git a/gcm3/src/test/java/nucleus/testsupport/testplugin/AT_TestScenarioReport.java b/gcm3/src/test/java/nucleus/testsupport/testplugin/AT_TestScenarioReport.java deleted file mode 100644 index 3b0095832..000000000 --- a/gcm3/src/test/java/nucleus/testsupport/testplugin/AT_TestScenarioReport.java +++ /dev/null @@ -1,81 +0,0 @@ -package nucleus.testsupport.testplugin; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = TestScenarioReport.class) -public class AT_TestScenarioReport { - - @Test - @UnitTestConstructor(args = Boolean.class) - public void testConstructor() { - // covered by other tests - } - - @Test - @UnitTestMethod(name = "equals", args = { Object.class }) - public void testEquals() { - - //create a few reports with different completion states - TestScenarioReport report1 = new TestScenarioReport(true); - TestScenarioReport report2 = new TestScenarioReport(true); - TestScenarioReport report3 = new TestScenarioReport(true); - - TestScenarioReport report4 = new TestScenarioReport(false); - TestScenarioReport report5 = new TestScenarioReport(false); - TestScenarioReport report6 = new TestScenarioReport(false); - - //show reflexive equality - assertEquals(report1, report1); - assertEquals(report2, report2); - assertEquals(report3, report3); - assertEquals(report4, report4); - assertEquals(report5, report5); - assertEquals(report6, report6); - - //show symmetric equality - assertEquals(report1, report2); - assertEquals(report2, report1); - - //show transitivity - assertEquals(report1, report2); - assertEquals(report2, report3); - assertEquals(report1, report3); - - //show that equality is equivalent to completion - assertNotEquals(report1, report4); - - - } - - @Test - @UnitTestMethod(name = "hashCode", args = {}) - public void testHashCode() { - //equal objects have equal hash codes - TestScenarioReport report1 = new TestScenarioReport(true); - TestScenarioReport report2 = new TestScenarioReport(true); - TestScenarioReport report3 = new TestScenarioReport(false); - TestScenarioReport report4 = new TestScenarioReport(false); - - assertEquals(report1.hashCode(), report2.hashCode()); - assertEquals(report3.hashCode(), report4.hashCode()); - } - - @Test - @UnitTestMethod(name = "isComplete", args = {}) - public void testIsComplete() { - TestScenarioReport report1 = new TestScenarioReport(true); - assertTrue(report1.isComplete()); - TestScenarioReport report2 = new TestScenarioReport(false); - assertFalse(report2.isComplete()); - } - -} diff --git a/gcm3/src/test/java/nucleus/testsupport/testplugin/TestActionSupport.java b/gcm3/src/test/java/nucleus/testsupport/testplugin/TestActionSupport.java deleted file mode 100644 index b1e57bc8e..000000000 --- a/gcm3/src/test/java/nucleus/testsupport/testplugin/TestActionSupport.java +++ /dev/null @@ -1,38 +0,0 @@ -package nucleus.testsupport.testplugin; - -import java.util.function.Consumer; - -import nucleus.ActorContext; -import nucleus.Plugin; -import nucleus.Simulation; -import util.errors.ContractException; - -public final class TestActionSupport { - private TestActionSupport() { - } - - public static void testConsumer(Consumer consumer) { - - TestPluginData testPluginData = TestPluginData .builder()// - .addTestActorPlan("actor", new TestActorPlan(0, consumer))// - .build(); - - Plugin plugin = TestPlugin.getTestPlugin(testPluginData); - testConsumers(plugin); - } - - public static void testConsumers(Plugin testPlugin) { - - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - Simulation .builder()// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput)// - .addPlugin(testPlugin)// - .build()// - .execute();// - - // show that all actions were executed - if (!scenarioPlanCompletionObserver.allPlansExecuted()) { - throw new ContractException(TestError.TEST_EXECUTION_FAILURE); - } - } -} diff --git a/gcm3/src/test/java/plugins/globalproperties/AT_GlobalPropertiesPlugin.java b/gcm3/src/test/java/plugins/globalproperties/AT_GlobalPropertiesPlugin.java deleted file mode 100644 index bb8868ff9..000000000 --- a/gcm3/src/test/java/plugins/globalproperties/AT_GlobalPropertiesPlugin.java +++ /dev/null @@ -1,37 +0,0 @@ -package plugins.globalproperties; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import nucleus.Plugin; -import nucleus.PluginId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = GlobalPropertiesPlugin.class) -public class AT_GlobalPropertiesPlugin { - - @Test - @UnitTestMethod(name = "getPlugin", args = { GlobalPropertiesPluginData.class }) - public void testGetPlugin() { - /* - *Show that the plugin contains the plugin data and has the property id and dependencies - */ - - GlobalPropertiesPluginData globalPropertiesPluginData = GlobalPropertiesPluginData.builder().build(); - Plugin globalsPlugin = GlobalPropertiesPlugin.getPlugin(globalPropertiesPluginData); - - assertTrue(globalsPlugin.getPluginDatas().contains(globalPropertiesPluginData)); - assertEquals(GlobalPropertiesPluginId.PLUGIN_ID, globalsPlugin.getPluginId()); - - Set expectedDependencies = new LinkedHashSet<>(); - assertEquals(expectedDependencies, globalsPlugin.getPluginDependencies()); - - } - -} diff --git a/gcm3/src/test/java/plugins/globalproperties/AT_GlobalPropertiesPluginData.java b/gcm3/src/test/java/plugins/globalproperties/AT_GlobalPropertiesPluginData.java deleted file mode 100644 index 807b1a49e..000000000 --- a/gcm3/src/test/java/plugins/globalproperties/AT_GlobalPropertiesPluginData.java +++ /dev/null @@ -1,304 +0,0 @@ -package plugins.globalproperties; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import plugins.globalproperties.support.GlobalPropertiesError; -import plugins.globalproperties.support.GlobalPropertyId; -import plugins.globalproperties.support.SimpleGlobalPropertyId; -import plugins.globalproperties.testsupport.TestGlobalPropertyId; -import plugins.util.properties.PropertyDefinition; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; -import util.random.RandomGeneratorProvider; - -@UnitTest(target = GlobalPropertiesPluginData.class) - -public class AT_GlobalPropertiesPluginData { - - @Test - @UnitTestMethod(target = GlobalPropertiesPluginData.Builder.class, name = "build", args = {}) - public void testBuild() { - - GlobalPropertiesPluginData globalInitialData = GlobalPropertiesPluginData.builder().build(); - assertNotNull(globalInitialData); - - // precondition tests - GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder(); - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(34).build(); - GlobalPropertyId globalPropertyId1 = new SimpleGlobalPropertyId("id 1"); - GlobalPropertyId globalPropertyId2 = new SimpleGlobalPropertyId("id 2"); - - /* - * If a global property value was associated with a global property id - * that was not defined - */ - builder.defineGlobalProperty(globalPropertyId1, propertyDefinition); - builder.setGlobalPropertyValue(globalPropertyId2, 67); - ContractException contractException = assertThrows(ContractException.class, () -> builder.build()); - assertEquals(GlobalPropertiesError.UNKNOWN_GLOBAL_PROPERTY_ID, contractException.getErrorType()); - - /* - * If a global property value was associated with a global property id - * that is incompatible with the corresponding property definition. - */ - builder.defineGlobalProperty(globalPropertyId1, propertyDefinition); - builder.setGlobalPropertyValue(globalPropertyId1, "bad value"); - contractException = assertThrows(ContractException.class, () -> builder.build()); - assertEquals(GlobalPropertiesError.INCOMPATIBLE_VALUE, contractException.getErrorType()); - - /* - * If a global property definition does not have a default value and - * there are no property values added to replace that default. - */ - propertyDefinition = PropertyDefinition.builder().setType(Integer.class).build(); - builder.defineGlobalProperty(globalPropertyId1, propertyDefinition); - contractException = assertThrows(ContractException.class, () -> builder.build()); - assertEquals(GlobalPropertiesError.INSUFFICIENT_GLOBAL_PROPERTY_VALUE_ASSIGNMENT, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(target = GlobalPropertiesPluginData.Builder.class, name = "defineGlobalProperty", args = { GlobalPropertyId.class, PropertyDefinition.class }) - public void testDefineGlobalProperty() { - GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder(); - - // create a container to hold the expected property definitions - Map expectedPropertyDefinitions = new LinkedHashMap<>(); - - // define a few global properties - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(34).build(); - GlobalPropertyId globalPropertyId = new SimpleGlobalPropertyId("id 1"); - expectedPropertyDefinitions.put(globalPropertyId, propertyDefinition); - builder.defineGlobalProperty(globalPropertyId, propertyDefinition); - - propertyDefinition = PropertyDefinition.builder().setType(Double.class).setDefaultValue(234.34).build(); - globalPropertyId = new SimpleGlobalPropertyId("id 2"); - expectedPropertyDefinitions.put(globalPropertyId, propertyDefinition); - builder.defineGlobalProperty(globalPropertyId, propertyDefinition); - - propertyDefinition = PropertyDefinition.builder().setType(String.class).setDefaultValue("default value").build(); - globalPropertyId = new SimpleGlobalPropertyId("id 3"); - expectedPropertyDefinitions.put(globalPropertyId, propertyDefinition); - builder.defineGlobalProperty(globalPropertyId, propertyDefinition); - - // build the initial data - GlobalPropertiesPluginData globalInitialData = builder.build(); - - // show that the property definitions are retrieved by their ids - for (GlobalPropertyId gpid : expectedPropertyDefinitions.keySet()) { - PropertyDefinition expectedPropertyDefinition = expectedPropertyDefinitions.get(gpid); - PropertyDefinition actualPropertyDefinition = globalInitialData.getGlobalPropertyDefinition(gpid); - assertEquals(expectedPropertyDefinition, actualPropertyDefinition); - } - - // precondition tests - - // if the global property id is null - PropertyDefinition propDef = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(17).build(); - ContractException contractException = assertThrows(ContractException.class, () -> builder.defineGlobalProperty(null, propDef)); - assertEquals(GlobalPropertiesError.NULL_GLOBAL_PROPERTY_ID, contractException.getErrorType()); - - // if the property definition is null - contractException = assertThrows(ContractException.class, () -> builder.defineGlobalProperty(new SimpleGlobalPropertyId("id"), null)); - assertEquals(GlobalPropertiesError.NULL_GLOBAL_PROPERTY_DEFINITION, contractException.getErrorType()); - - // if a property definition for the given global property id was - // previously defined. - builder.defineGlobalProperty(new SimpleGlobalPropertyId("id"), propDef); - contractException = assertThrows(ContractException.class, () -> builder.defineGlobalProperty(new SimpleGlobalPropertyId("id"), propDef)); - assertEquals(GlobalPropertiesError.DUPLICATE_GLOBAL_PROPERTY_DEFINITION, contractException.getErrorType()); - - } - - - - @Test - @UnitTestMethod(target = GlobalPropertiesPluginData.Builder.class, name = "setGlobalPropertyValue", args = { GlobalPropertyId.class, Object.class }) - public void testSetGlobalPropertyValue() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(170390875787254562L); - - GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder(); - - // show there are some properties in the support enum - assertTrue(TestGlobalPropertyId.values().length > 0); - - // define some properties - for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(0).build(); - builder.defineGlobalProperty(testGlobalPropertyId, propertyDefinition); - } - // create a container for the expected values of the properties - Map expectedValues = new LinkedHashMap<>(); - - // set the values - for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { - int value = randomGenerator.nextInt(); - builder.setGlobalPropertyValue(testGlobalPropertyId, value); - expectedValues.put(testGlobalPropertyId, value); - } - - // show that the expected values are present - GlobalPropertiesPluginData globalInitialData = builder.build(); - for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { - Integer expectedGlobalPropertyValue = expectedValues.get(testGlobalPropertyId); - Integer actualGlobalPropertyValue = globalInitialData.getGlobalPropertyValue(testGlobalPropertyId); - assertEquals(expectedGlobalPropertyValue, actualGlobalPropertyValue); - } - - /* - * precondition tests -- Note that invalid values are not covered here. - * The build() validates the values to see if they are compatible with - * the corresponding definitions. - */ - - // if the global property id is null - ContractException contractException = assertThrows(ContractException.class, () -> builder.setGlobalPropertyValue(null, 5)); - assertEquals(GlobalPropertiesError.NULL_GLOBAL_PROPERTY_ID, contractException.getErrorType()); - - // if the global property value is null - contractException = assertThrows(ContractException.class, () -> builder.setGlobalPropertyValue(TestGlobalPropertyId.GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE, null)); - assertEquals(GlobalPropertiesError.NULL_GLOBAL_PROPERTY_VALUE, contractException.getErrorType()); - - // if the global property value was previously defined for the given - // global property id - builder.setGlobalPropertyValue(TestGlobalPropertyId.GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE, 4); - contractException = assertThrows(ContractException.class, () -> builder.setGlobalPropertyValue(TestGlobalPropertyId.GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE, 5)); - assertEquals(GlobalPropertiesError.DUPLICATE_GLOBAL_PROPERTY_VALUE_ASSIGNMENT, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "builder", args = {}) - public void testBuilder() { - // show that the builder can be created - assertNotNull(GlobalPropertiesPluginData.builder()); - } - - - - @Test - @UnitTestMethod(name = "getGlobalPropertyDefinition", args = { GlobalPropertyId.class }) - public void testGetGlobalPropertyDefinition() { - GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder(); - - // show there are some properties in the support enum - assertTrue(TestGlobalPropertyId.values().length > 0); - - // create a container for the expected values of the properties - Map expectedGlobalPropertyDefinitions = new LinkedHashMap<>(); - - // define some properties - - for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(0).build(); - builder.defineGlobalProperty(testGlobalPropertyId, propertyDefinition); - expectedGlobalPropertyDefinitions.put(testGlobalPropertyId, propertyDefinition); - } - - // show that the expected property definitions are present - GlobalPropertiesPluginData globalInitialData = builder.build(); - - for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { - PropertyDefinition expectedPropertyDefinition = expectedGlobalPropertyDefinitions.get(testGlobalPropertyId); - PropertyDefinition actualPropertyDefinition = globalInitialData.getGlobalPropertyDefinition(testGlobalPropertyId); - assertEquals(expectedPropertyDefinition, actualPropertyDefinition); - } - - // precondition tests - ContractException contractException = assertThrows(ContractException.class, () -> globalInitialData.getGlobalPropertyDefinition(null)); - assertEquals(GlobalPropertiesError.NULL_GLOBAL_PROPERTY_ID, contractException.getErrorType()); - - contractException = assertThrows(ContractException.class, () -> globalInitialData.getGlobalPropertyDefinition(TestGlobalPropertyId.getUnknownGlobalPropertyId())); - assertEquals(GlobalPropertiesError.UNKNOWN_GLOBAL_PROPERTY_ID, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "getGlobalPropertyIds", args = {}) - public void testGetGlobalPropertyIds() { - - GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder(); - - // show there are some properties in the support enum - assertTrue(TestGlobalPropertyId.values().length > 0); - - // create a container for the expected values of the properties - Set expectedGlobalPropertyIds = new LinkedHashSet<>(); - - // define some properties - - for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(0).build(); - builder.defineGlobalProperty(testGlobalPropertyId, propertyDefinition); - expectedGlobalPropertyIds.add(testGlobalPropertyId); - } - - // show that the expected values are present - GlobalPropertiesPluginData globalInitialData = builder.build(); - assertEquals(expectedGlobalPropertyIds, globalInitialData.getGlobalPropertyIds()); - } - - @Test - @UnitTestMethod(name = "getGlobalPropertyValue", args = { GlobalPropertyId.class }) - public void testGetGlobalPropertyValue() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4250048639082754761L); - - GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder(); - - // show there are some properties in the support enum - assertTrue(TestGlobalPropertyId.values().length > 0); - - // define some properties -- note that we do not set default values to - // test that the values provided explicitly will properly replace the - // default values. - for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).build(); - builder.defineGlobalProperty(testGlobalPropertyId, propertyDefinition); - } - // create a container for the expected values of the properties - Map expectedValues = new LinkedHashMap<>(); - - // set the values - for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { - int value = randomGenerator.nextInt(); - builder.setGlobalPropertyValue(testGlobalPropertyId, value); - expectedValues.put(testGlobalPropertyId, value); - } - - // show that the expected values are present - GlobalPropertiesPluginData globalInitialData = builder.build(); - for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { - Integer expectedGlobalPropertyValue = expectedValues.get(testGlobalPropertyId); - Integer actualGlobalPropertyValue = globalInitialData.getGlobalPropertyValue(testGlobalPropertyId); - assertEquals(expectedGlobalPropertyValue, actualGlobalPropertyValue); - } - - /* - * precondition tests -- Note that invalid values are not covered here. - * The build() validates the values to see if they are compatible with - * the corresponding definitions. - */ - - // if the global property id is null - ContractException contractException = assertThrows(ContractException.class, () -> globalInitialData.getGlobalPropertyValue(null)); - assertEquals(GlobalPropertiesError.NULL_GLOBAL_PROPERTY_ID, contractException.getErrorType()); - - // if the global property value is null - contractException = assertThrows(ContractException.class, () -> globalInitialData.getGlobalPropertyValue(TestGlobalPropertyId.getUnknownGlobalPropertyId())); - assertEquals(GlobalPropertiesError.UNKNOWN_GLOBAL_PROPERTY_ID, contractException.getErrorType()); - - } - -} diff --git a/gcm3/src/test/java/plugins/globalproperties/AT_GlobalPropertiesPluginId.java b/gcm3/src/test/java/plugins/globalproperties/AT_GlobalPropertiesPluginId.java deleted file mode 100644 index 3771cd0a9..000000000 --- a/gcm3/src/test/java/plugins/globalproperties/AT_GlobalPropertiesPluginId.java +++ /dev/null @@ -1,13 +0,0 @@ -package plugins.globalproperties; - -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import tools.annotations.UnitTest; - -@UnitTest(target = GlobalPropertiesPluginId.class) -public class AT_GlobalPropertiesPluginId { - - public void test() { - assertNotNull(GlobalPropertiesPluginId.PLUGIN_ID); - } -} diff --git a/gcm3/src/test/java/plugins/globalproperties/actors/AT_GlobalPropertyReport.java b/gcm3/src/test/java/plugins/globalproperties/actors/AT_GlobalPropertyReport.java deleted file mode 100644 index 30cddf4f0..000000000 --- a/gcm3/src/test/java/plugins/globalproperties/actors/AT_GlobalPropertyReport.java +++ /dev/null @@ -1,173 +0,0 @@ -package plugins.globalproperties.actors; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashMap; -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import nucleus.Experiment; -import nucleus.Plugin; -import nucleus.testsupport.testplugin.ExperimentPlanCompletionObserver; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import plugins.globalproperties.GlobalPropertiesPlugin; -import plugins.globalproperties.GlobalPropertiesPluginData; -import plugins.globalproperties.datamanagers.GlobalPropertiesDataManager; -import plugins.globalproperties.support.GlobalPropertyId; -import plugins.globalproperties.support.SimpleGlobalPropertyId; -import plugins.reports.ReportsPlugin; -import plugins.reports.ReportsPluginData; -import plugins.reports.support.ReportHeader; -import plugins.reports.support.ReportId; -import plugins.reports.support.ReportItem; -import plugins.reports.support.SimpleReportId; -import plugins.reports.testsupport.TestReportItemOutputConsumer; -import plugins.util.properties.PropertyDefinition; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = GlobalPropertyReport.class) -public class AT_GlobalPropertyReport { - - @Test - @UnitTestMethod(name = "init", args = {}) - public void testInit() { - - /* - * We will add one agent and the global property report to the engine. - * We will define a few global properties and the agent will alter - * various global properties over time. Report items from the report - * will be collected in an output consumer. The expected report items - * will be collected in a separate consumer and the consumers will be - * compared for equality. The output consumers properly accounts for - * report item duplications. - */ - - Experiment.Builder builder = Experiment.builder(); - builder.reportProgressToConsole(false); - builder.reportFailuresToConsole(false); - - // add the global property definitions - GlobalPropertiesPluginData.Builder initialDatabuilder = GlobalPropertiesPluginData.builder(); - - GlobalPropertyId globalPropertyId_1 = new SimpleGlobalPropertyId("id_1"); - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(3).build(); - initialDatabuilder.defineGlobalProperty(globalPropertyId_1, propertyDefinition); - - GlobalPropertyId globalPropertyId_2 = new SimpleGlobalPropertyId("id_2"); - propertyDefinition = PropertyDefinition.builder().setType(Double.class).setDefaultValue(6.78).build(); - initialDatabuilder.defineGlobalProperty(globalPropertyId_2, propertyDefinition); - - GlobalPropertyId globalPropertyId_3 = new SimpleGlobalPropertyId("id_3"); - propertyDefinition = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(true).build(); - initialDatabuilder.defineGlobalProperty(globalPropertyId_3, propertyDefinition); - - GlobalPropertiesPluginData globalPropertiesPluginData = initialDatabuilder.build(); - builder.addPlugin(GlobalPropertiesPlugin.getPlugin(globalPropertiesPluginData)); - - // add the report - ReportsPluginData reportsInitialData = ReportsPluginData.builder()// - .addReport(() -> new GlobalPropertyReport(REPORT_ID)::init)// - .build();// - builder.addPlugin(ReportsPlugin.getReportPlugin(reportsInitialData)); - - - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // create an agent and have it assign various global properties at - // various times - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0.0, (c) -> { - /* - * note that this is time 0 and should show that property initial - * values are still reported correctly - */ - GlobalPropertiesDataManager globalPropertiesDataManager = c.getDataManager(GlobalPropertiesDataManager.class); - globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId_1, 67); - })); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1.0, (c) -> { - // two settings of the same property - GlobalPropertiesDataManager globalPropertiesDataManager = c.getDataManager(GlobalPropertiesDataManager.class); - globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId_2, 88.88); - globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId_3, false); - })); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2.0, (c) -> { - GlobalPropertiesDataManager globalPropertiesDataManager = c.getDataManager(GlobalPropertiesDataManager.class); - globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId_1, 100); - globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId_2, 3.45); - globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId_3, true); - })); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(3.0, (c) -> { - GlobalPropertiesDataManager globalPropertiesDataManager = c.getDataManager(GlobalPropertiesDataManager.class); - - - globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId_3, false); - // note the duplicated value - globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId_2, 99.7); - globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId_2, 99.7); - // and now a third setting of the same property to a new value - globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId_2, 100.0); - globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId_3, true); - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - builder.addPlugin(testPlugin); - - // build and execute the experiment - ExperimentPlanCompletionObserver experimentPlanCompletionObserver = new ExperimentPlanCompletionObserver(); - TestReportItemOutputConsumer testReportItemOutputConsumer = new TestReportItemOutputConsumer(); - - builder.addOutputHandler(testReportItemOutputConsumer::init); - builder.addOutputHandler(experimentPlanCompletionObserver::init); - builder.build().execute(); - - // show that all actions were executed - assertTrue(experimentPlanCompletionObserver.getActionCompletionReport(0).get().isComplete()); - - /* - * Collect the expected report items. Note that order does not matter. * - */ - Map expectedReportItems = new LinkedHashMap<>(); - - expectedReportItems.put(getReportItem(0.0, globalPropertyId_1, 3), 1); - expectedReportItems.put(getReportItem(0.0, globalPropertyId_2, 6.78), 1); - expectedReportItems.put(getReportItem(0.0, globalPropertyId_3, true), 1); - expectedReportItems.put(getReportItem(0.0, globalPropertyId_1, 67), 1); - expectedReportItems.put(getReportItem(1.0, globalPropertyId_2, 88.88), 1); - expectedReportItems.put(getReportItem(1.0, globalPropertyId_3, false), 1); - expectedReportItems.put(getReportItem(2.0, globalPropertyId_1, 100), 1); - expectedReportItems.put(getReportItem(2.0, globalPropertyId_2, 3.45), 1); - expectedReportItems.put(getReportItem(2.0, globalPropertyId_3, true), 1); - expectedReportItems.put(getReportItem(3.0, globalPropertyId_3, false), 1); - expectedReportItems.put(getReportItem(3.0, globalPropertyId_2, 99.7), 2); - expectedReportItems.put(getReportItem(3.0, globalPropertyId_2, 100.0), 1); - expectedReportItems.put(getReportItem(3.0, globalPropertyId_3, true), 1); - - Map actualReportItems = testReportItemOutputConsumer.getReportItems().get(0); - - assertEquals(expectedReportItems, actualReportItems); - } - - private static ReportItem getReportItem(Object... values) { - ReportItem.Builder builder = ReportItem.builder(); - builder.setReportId(REPORT_ID); - builder.setReportHeader(REPORT_HEADER); - for (Object value : values) { - builder.addValue(value); - } - return builder.build(); - } - - private static final ReportId REPORT_ID = new SimpleReportId("global property report"); - - private static final ReportHeader REPORT_HEADER = ReportHeader.builder().add("time").add("property").add("value").build(); -} diff --git a/gcm3/src/test/java/plugins/globalproperties/datamanagers/AT_GlobalPropertiesDataManager.java b/gcm3/src/test/java/plugins/globalproperties/datamanagers/AT_GlobalPropertiesDataManager.java deleted file mode 100644 index 84c07b31d..000000000 --- a/gcm3/src/test/java/plugins/globalproperties/datamanagers/AT_GlobalPropertiesDataManager.java +++ /dev/null @@ -1,398 +0,0 @@ -package plugins.globalproperties.datamanagers; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.IntStream; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.DataManagerContext; -import nucleus.EventLabel; -import nucleus.EventLabeler; -import nucleus.NucleusError; -import nucleus.Plugin; -import nucleus.Simulation; -import nucleus.testsupport.testplugin.ScenarioPlanCompletionObserver; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestError; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import plugins.globalproperties.GlobalPropertiesPlugin; -import plugins.globalproperties.GlobalPropertiesPluginData; -import plugins.globalproperties.events.GlobalPropertyUpdateEvent; -import plugins.globalproperties.support.GlobalPropertiesError; -import plugins.globalproperties.support.GlobalPropertyId; -import plugins.globalproperties.support.SimpleGlobalPropertyId; -import plugins.globalproperties.testsupport.GlobalsPropertiesActionSupport; -import plugins.globalproperties.testsupport.TestGlobalPropertyId; -import plugins.util.properties.PropertyDefinition; -import plugins.util.properties.PropertyError; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; -import util.random.RandomGeneratorProvider; -import util.wrappers.MultiKey; - -@UnitTest(target = GlobalPropertiesDataManager.class) - -public final class AT_GlobalPropertiesDataManager { - - ///////////////////////////////// - // from the resolver - //////////////////// - @Test - @UnitTestConstructor(args = { GlobalPropertiesPluginData.class }) - public void testConstructor() { - ContractException contractException = assertThrows(ContractException.class, () -> new GlobalPropertiesDataManager(null)); - assertEquals(GlobalPropertiesError.NULL_GLOBAL_PLUGIN_DATA, contractException.getErrorType()); - } - - @Test - @UnitTestMethod(name = "init", args = { DataManagerContext.class }) - public void testInit() { - - /* - * show that the event labelers for GlobalPropertyUpdateEvent - * were added - */ - GlobalsPropertiesActionSupport.testConsumer((c) -> { - EventLabeler eventLabeler = GlobalPropertyUpdateEvent.getEventLabeler(); - assertNotNull(eventLabeler); - ContractException contractException = assertThrows(ContractException.class, () -> c.addEventLabeler(eventLabeler)); - assertEquals(NucleusError.DUPLICATE_LABELER_ID_IN_EVENT_LABELER, contractException.getErrorType()); - }); - - Map expectedPropertyValues = new LinkedHashMap<>(); - GlobalPropertiesPluginData.Builder globalsPluginBuilder = GlobalPropertiesPluginData.builder(); - for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { - PropertyDefinition propertyDefinition = testGlobalPropertyId.getPropertyDefinition(); - globalsPluginBuilder.defineGlobalProperty(testGlobalPropertyId, propertyDefinition); - expectedPropertyValues.put(testGlobalPropertyId, propertyDefinition.getDefaultValue().get()); - } - // change two of the properties from their default values - globalsPluginBuilder.setGlobalPropertyValue(TestGlobalPropertyId.GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE, true); - expectedPropertyValues.put(TestGlobalPropertyId.GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE, true); - - globalsPluginBuilder.setGlobalPropertyValue(TestGlobalPropertyId.GLOBAL_PROPERTY_2_INTEGER_MUTABLE, 456); - expectedPropertyValues.put(TestGlobalPropertyId.GLOBAL_PROPERTY_2_INTEGER_MUTABLE, 456); - - GlobalPropertiesPluginData globalPropertiesPluginData = globalsPluginBuilder.build(); - Plugin globalsPlugin = GlobalPropertiesPlugin.getPlugin(globalPropertiesPluginData); - - /* - * show that the Global Plugin Data is reflected in the initial state of - * the data manager - */ - TestPluginData.Builder testPluginDataBuilder = TestPluginData.builder(); - - testPluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - // show that the data manager exists - GlobalPropertiesDataManager globalPropertiesDataManager = c.getDataManager(GlobalPropertiesDataManager.class); - - // show that the global property ids are present - Set globalPropertyIds = globalPropertiesDataManager.getGlobalPropertyIds(); - assertEquals(TestGlobalPropertyId.values().length, globalPropertyIds.size()); - for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { - assertTrue(globalPropertyIds.contains(testGlobalPropertyId)); - } - - for (GlobalPropertyId globalPropertyId : expectedPropertyValues.keySet()) { - assertEquals(expectedPropertyValues.get(globalPropertyId), globalPropertiesDataManager.getGlobalPropertyValue(globalPropertyId)); - } - - })); - - TestPluginData testPluginData = testPluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - Simulation .builder()// - .addPlugin(globalsPlugin)// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput).addPlugin(testPlugin)// - .build()// - .execute();// - - // show that all actions were executed - if (!scenarioPlanCompletionObserver.allPlansExecuted()) { - throw new ContractException(TestError.TEST_EXECUTION_FAILURE); - } - - } - - ////////////////////////// - // from the old data manager - //////////////////////////////// - - @Test - @UnitTestMethod(name = "globalPropertyIdExists", args = { GlobalPropertyId.class }) - public void testGlobalPropertyIdExists() { - - GlobalsPropertiesActionSupport.testConsumer((c) -> { - GlobalPropertiesDataManager globalPropertiesDataManager = c.getDataManager(GlobalPropertiesDataManager.class); - for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { - assertTrue(globalPropertiesDataManager.globalPropertyIdExists(testGlobalPropertyId)); - } - - // show that a null global property id will return false - assertFalse(globalPropertiesDataManager.globalPropertyIdExists(null)); - - // show that an unknown global property id will return false - assertFalse(globalPropertiesDataManager.globalPropertyIdExists(new SimpleGlobalPropertyId("bad prop"))); - }); - - } - - /////////////////// - // from the old data view - ////////////////// - - @Test - @UnitTestMethod(name = "setGlobalPropertyValue", args = { GlobalPropertyId.class, Object.class }) - public void testSetGlobalPropertyValue() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7837412421821851663L); - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - TestGlobalPropertyId globalPropertyId = TestGlobalPropertyId.GLOBAL_PROPERTY_2_INTEGER_MUTABLE; - - // create some containers to hold the expected and actual observations - List expectedObservations = new ArrayList<>(); - List actualObservations = new ArrayList<>(); - - // have an observer record changes to the property - pluginDataBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { - EventLabel eventLabel = GlobalPropertyUpdateEvent.getEventLabel(c, globalPropertyId); - c.subscribe(eventLabel, (c2, e) -> { - MultiKey multiKey = new MultiKey(c2.getTime(), e.getGlobalPropertyId(), e.getPreviousPropertyValue(), e.getCurrentPropertyValue()); - actualObservations.add(multiKey); - }); - })); - - // Have the actor set the value of the global property 1 a few times - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - GlobalPropertiesDataManager globalPropertiesDataManager = c.getDataManager(GlobalPropertiesDataManager.class); - Integer currentValue = globalPropertiesDataManager.getGlobalPropertyValue(globalPropertyId); - Integer newValue = globalPropertyId.getRandomPropertyValue(randomGenerator); - globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId, newValue); - expectedObservations.add(new MultiKey(c.getTime(), globalPropertyId, currentValue, newValue)); - - })); - - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { - GlobalPropertiesDataManager globalPropertiesDataManager = c.getDataManager(GlobalPropertiesDataManager.class); - Integer currentValue = globalPropertiesDataManager.getGlobalPropertyValue(globalPropertyId); - Integer newValue = globalPropertyId.getRandomPropertyValue(randomGenerator); - globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId, newValue); - expectedObservations.add(new MultiKey(c.getTime(), globalPropertyId, currentValue, newValue)); - - })); - - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(3, (c) -> { - GlobalPropertiesDataManager globalPropertiesDataManager = c.getDataManager(GlobalPropertiesDataManager.class); - Integer currentValue = globalPropertiesDataManager.getGlobalPropertyValue(globalPropertyId); - Integer newValue = globalPropertyId.getRandomPropertyValue(randomGenerator); - globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId, newValue); - expectedObservations.add(new MultiKey(c.getTime(), globalPropertyId, currentValue, newValue)); - })); - - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - GlobalsPropertiesActionSupport.testConsumers(testPlugin); - - - // show that the observations were correct - assertTrue(expectedObservations.size() > 0); - assertEquals(expectedObservations.size(), actualObservations.size()); - assertEquals(new LinkedHashSet<>(expectedObservations), new LinkedHashSet<>(actualObservations)); - - - // precondition test: if the global property id is null - GlobalsPropertiesActionSupport.testConsumer((c) -> { - GlobalPropertiesDataManager globalPropertiesDataManager = c.getDataManager(GlobalPropertiesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> globalPropertiesDataManager.setGlobalPropertyValue(null, 15)); - assertEquals(GlobalPropertiesError.NULL_GLOBAL_PROPERTY_ID, contractException.getErrorType()); - }); - - // if the global property id is unknown - GlobalsPropertiesActionSupport.testConsumer((c) -> { - GlobalPropertiesDataManager globalPropertiesDataManager = c.getDataManager(GlobalPropertiesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> globalPropertiesDataManager.setGlobalPropertyValue(TestGlobalPropertyId.getUnknownGlobalPropertyId(), 15)); - assertEquals(GlobalPropertiesError.UNKNOWN_GLOBAL_PROPERTY_ID, contractException.getErrorType()); - }); - - // if the property value is null - GlobalsPropertiesActionSupport.testConsumer((c) -> { - GlobalPropertiesDataManager globalPropertiesDataManager = c.getDataManager(GlobalPropertiesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> globalPropertiesDataManager.setGlobalPropertyValue(TestGlobalPropertyId.GLOBAL_PROPERTY_1_BOOLEAN_MUTABLE, null)); - assertEquals(GlobalPropertiesError.NULL_GLOBAL_PROPERTY_VALUE, contractException.getErrorType()); - }); - - // if the global property definition indicates the property is not - // mutable - GlobalsPropertiesActionSupport.testConsumer((c) -> { - GlobalPropertiesDataManager globalPropertiesDataManager = c.getDataManager(GlobalPropertiesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> globalPropertiesDataManager.setGlobalPropertyValue(TestGlobalPropertyId.GLOBAL_PROPERTY_5_INTEGER_IMMUTABLE, 55)); - assertEquals(PropertyError.IMMUTABLE_VALUE, contractException.getErrorType()); - }); - - // if the property value is incompatible with the property definition - GlobalsPropertiesActionSupport.testConsumer((c) -> { - GlobalPropertiesDataManager globalPropertiesDataManager = c.getDataManager(GlobalPropertiesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, - () -> globalPropertiesDataManager.setGlobalPropertyValue(TestGlobalPropertyId.GLOBAL_PROPERTY_2_INTEGER_MUTABLE, "value")); - assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "getGlobalPropertyValue", args = { GlobalPropertyId.class }) - public void testGetGlobalPropertyValue() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1059537118783693383L); - - // show that values can be retrieved - GlobalsPropertiesActionSupport.testConsumer((c) -> { - GlobalPropertiesDataManager globalPropertiesDataManager = c.getDataManager(GlobalPropertiesDataManager.class); - - for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { - PropertyDefinition propertyDefinition = globalPropertiesDataManager.getGlobalPropertyDefinition(testGlobalPropertyId); - if (propertyDefinition.propertyValuesAreMutable()) { - Object expectedValue = testGlobalPropertyId.getRandomPropertyValue(randomGenerator); - globalPropertiesDataManager.setGlobalPropertyValue(testGlobalPropertyId, expectedValue); - Object actualValue = globalPropertiesDataManager.getGlobalPropertyValue(testGlobalPropertyId); - assertEquals(expectedValue, actualValue); - } - } - }); - - // precondition test : if the property id is null - GlobalsPropertiesActionSupport.testConsumer((c) -> { - GlobalPropertiesDataManager globalPropertiesDataManager = c.getDataManager(GlobalPropertiesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> globalPropertiesDataManager.getGlobalPropertyValue(null)); - assertEquals(GlobalPropertiesError.NULL_GLOBAL_PROPERTY_ID, contractException.getErrorType()); - }); - - // precondition test : if the property id is unknown - GlobalsPropertiesActionSupport.testConsumer((c) -> { - GlobalPropertiesDataManager globalPropertiesDataManager = c.getDataManager(GlobalPropertiesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> globalPropertiesDataManager.getGlobalPropertyValue(TestGlobalPropertyId.getUnknownGlobalPropertyId())); - assertEquals(GlobalPropertiesError.UNKNOWN_GLOBAL_PROPERTY_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "getGlobalPropertyTime", args = { GlobalPropertyId.class }) - public void testGetGlobalPropertyTime() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5323616867741088481L); - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - - IntStream.range(0, 10).forEach((i) -> { - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(i, (c) -> { - GlobalPropertiesDataManager globalPropertiesDataManager = c.getDataManager(GlobalPropertiesDataManager.class); - TestGlobalPropertyId globalPropertyId = TestGlobalPropertyId.GLOBAL_PROPERTY_3_DOUBLE_MUTABLE; - Double newValue = globalPropertyId.getRandomPropertyValue(randomGenerator); - globalPropertiesDataManager.setGlobalPropertyValue(globalPropertyId, newValue); - double globalPropertyTime = globalPropertiesDataManager.getGlobalPropertyTime(globalPropertyId); - assertEquals(c.getTime(), globalPropertyTime); - })); - }); - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - GlobalsPropertiesActionSupport.testConsumers(testPlugin); - - GlobalsPropertiesActionSupport.testConsumer((c) -> { - GlobalPropertiesDataManager globalPropertiesDataManager = c.getDataManager(GlobalPropertiesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> globalPropertiesDataManager.getGlobalPropertyTime(null)); - assertEquals(GlobalPropertiesError.NULL_GLOBAL_PROPERTY_ID, contractException.getErrorType()); - }); - - GlobalsPropertiesActionSupport.testConsumer((c) -> { - GlobalPropertiesDataManager globalPropertiesDataManager = c.getDataManager(GlobalPropertiesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> globalPropertiesDataManager.getGlobalPropertyTime(TestGlobalPropertyId.getUnknownGlobalPropertyId())); - assertEquals(GlobalPropertiesError.UNKNOWN_GLOBAL_PROPERTY_ID, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "getGlobalPropertyIds", args = {}) - public void testGetGlobalPropertyIds() { - GlobalsPropertiesActionSupport.testConsumer((c) -> { - GlobalPropertiesDataManager globalPropertiesDataManager = c.getDataManager(GlobalPropertiesDataManager.class); - - Set expectedGlobalPropertyIds = new LinkedHashSet<>(); - for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { - expectedGlobalPropertyIds.add(testGlobalPropertyId); - } - assertEquals(expectedGlobalPropertyIds, globalPropertiesDataManager.getGlobalPropertyIds()); - }); - } - - @Test - @UnitTestMethod(name = "getGlobalPropertyDefinition", args = { GlobalPropertyId.class }) - public void testGetGlobalPropertyDefinition() { - - GlobalsPropertiesActionSupport.testConsumer((c) -> { - GlobalPropertiesDataManager globalPropertiesDataManager = c.getDataManager(GlobalPropertiesDataManager.class); - - for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { - assertEquals(testGlobalPropertyId.getPropertyDefinition(), globalPropertiesDataManager.getGlobalPropertyDefinition(testGlobalPropertyId)); - } - - }); - - // precondition : if the global property id is null - GlobalsPropertiesActionSupport.testConsumer((c) -> { - GlobalPropertiesDataManager globalPropertiesDataManager = c.getDataManager(GlobalPropertiesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> globalPropertiesDataManager.getGlobalPropertyDefinition(null)); - assertEquals(GlobalPropertiesError.NULL_GLOBAL_PROPERTY_ID, contractException.getErrorType()); - - }); - - // precondition : if the global property id is unknown - GlobalsPropertiesActionSupport.testConsumer((c) -> { - GlobalPropertiesDataManager globalPropertiesDataManager = c.getDataManager(GlobalPropertiesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> globalPropertiesDataManager.getGlobalPropertyDefinition(TestGlobalPropertyId.getUnknownGlobalPropertyId())); - assertEquals(GlobalPropertiesError.UNKNOWN_GLOBAL_PROPERTY_ID, contractException.getErrorType()); - - }); - - } - - // 8918160851781792282L - // 7809340800269936345L - // 3093831156319746905L - // 2673180392167300833L - // 3146193944117744750L - // 8844397496811302788L - // 6389030028157632648L - // 8931372624594534340L - // 8198175094111035531L - // 1737728587381330869L - // 2793028735281327135L - // 1775842166173739357L - // 4833198812489028379L - // 7577757860524876797L - // 2691093254317628533L - // 2103113028059167974L - // 762144723674601785L - // 1636683557476673903L - // 2026890812799252344L - // - -} diff --git a/gcm3/src/test/java/plugins/globalproperties/events/AT_GlobalPropertyUpdateEvent.java b/gcm3/src/test/java/plugins/globalproperties/events/AT_GlobalPropertyUpdateEvent.java deleted file mode 100644 index 5aac1422c..000000000 --- a/gcm3/src/test/java/plugins/globalproperties/events/AT_GlobalPropertyUpdateEvent.java +++ /dev/null @@ -1,188 +0,0 @@ -package plugins.globalproperties.events; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.function.Consumer; - -import org.junit.jupiter.api.Test; - -import nucleus.ActorContext; -import nucleus.EventLabel; -import nucleus.EventLabeler; -import nucleus.Plugin; -import nucleus.Simulation; -import nucleus.Simulation.Builder; -import nucleus.SimulationContext; -import nucleus.testsupport.testplugin.ScenarioPlanCompletionObserver; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import plugins.globalproperties.GlobalPropertiesPlugin; -import plugins.globalproperties.GlobalPropertiesPluginData; -import plugins.globalproperties.support.GlobalPropertyId; -import plugins.globalproperties.support.SimpleGlobalPropertyId; -import plugins.globalproperties.testsupport.TestGlobalPropertyId; -import plugins.util.properties.PropertyDefinition; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = GlobalPropertyUpdateEvent.class) - -public class AT_GlobalPropertyUpdateEvent { - - @Test - @UnitTestConstructor(args = { GlobalPropertyId.class, Object.class, Object.class }) - public void testConstructor() { - GlobalPropertyId globalPropertyId = new SimpleGlobalPropertyId("id"); - Integer previousValue = 12; - Integer currentValue = 13; - GlobalPropertyUpdateEvent globalPropertyUpdateEvent = new GlobalPropertyUpdateEvent(globalPropertyId, previousValue, currentValue); - - assertNotNull(globalPropertyUpdateEvent); - - } - - @Test - @UnitTestMethod(name = "getGlobalPropertyId", args = { GlobalPropertyId.class, Object.class, Object.class }) - public void testGetGlobalPropertyId() { - GlobalPropertyId globalPropertyId = new SimpleGlobalPropertyId("id"); - Integer previousValue = 12; - Integer currentValue = 13; - GlobalPropertyUpdateEvent globalPropertyUpdateEvent = new GlobalPropertyUpdateEvent(globalPropertyId, previousValue, currentValue); - - assertEquals(globalPropertyId, globalPropertyUpdateEvent.getGlobalPropertyId()); - - } - - @Test - @UnitTestMethod(name = "getGlobalPropertyId", args = { GlobalPropertyId.class, Object.class, Object.class }) - public void testGetPreviousPropertyValue() { - GlobalPropertyId globalPropertyId = new SimpleGlobalPropertyId("id"); - Integer previousValue = 12; - Integer currentValue = 13; - GlobalPropertyUpdateEvent globalPropertyUpdateEvent = new GlobalPropertyUpdateEvent(globalPropertyId, previousValue, currentValue); - - assertEquals(previousValue, globalPropertyUpdateEvent.getPreviousPropertyValue()); - } - - @Test - @UnitTestMethod(name = "getCurrentPropertyValue", args = {}) - public void testGetCurrentPropertyValue() { - GlobalPropertyId globalPropertyId = new SimpleGlobalPropertyId("id"); - Integer previousValue = 12; - Integer currentValue = 13; - GlobalPropertyUpdateEvent globalPropertyUpdateEvent = new GlobalPropertyUpdateEvent(globalPropertyId, previousValue, currentValue); - - assertEquals(currentValue, globalPropertyUpdateEvent.getCurrentPropertyValue()); - } - - @Test - @UnitTestMethod(name = "toString", args = {}) - public void testToString() { - GlobalPropertyId globalPropertyId = new SimpleGlobalPropertyId("id"); - Integer previousValue = 12; - Integer currentValue = 13; - GlobalPropertyUpdateEvent globalPropertyUpdateEvent = new GlobalPropertyUpdateEvent(globalPropertyId, previousValue, currentValue); - String expectedValue = "GlobalPropertyUpdateEvent [globalPropertyId=id, previousPropertyValue=12, currentPropertyValue=13]"; - String actualValue = globalPropertyUpdateEvent.toString(); - - assertEquals(expectedValue, actualValue); - - } - - @Test - @UnitTestMethod(name = "getEventLabel", args = { SimulationContext.class, GlobalPropertyId.class }) - public void testGetEventLabel() { - testConsumer((c) -> { - for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { - EventLabel eventLabel = GlobalPropertyUpdateEvent.getEventLabel(c, testGlobalPropertyId); - assertEquals(GlobalPropertyUpdateEvent.class, eventLabel.getEventClass()); - assertEquals(testGlobalPropertyId, eventLabel.getPrimaryKeyValue()); - assertEquals(GlobalPropertyUpdateEvent.getEventLabeler().getEventLabelerId(), eventLabel.getLabelerId()); - } - }); - } - - @Test - @UnitTestMethod(name = "getEventLabeler", args = {}) - public void testGetEventLabeler() { - testConsumer((c) -> { - - EventLabeler eventLabeler = GlobalPropertyUpdateEvent.getEventLabeler(); - - // show that the event labeler has the expected event class - assertEquals(GlobalPropertyUpdateEvent.class, eventLabeler.getEventClass()); - - for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { - // show that the event labeler id matches the labeler id - // associated with the corresponding event label - EventLabel eventLabel = GlobalPropertyUpdateEvent.getEventLabel(c, testGlobalPropertyId); - assertEquals(eventLabel.getLabelerId(), eventLabeler.getEventLabelerId()); - - // show that the event labeler produces the expected event - // label - - // create an event - GlobalPropertyUpdateEvent event = new GlobalPropertyUpdateEvent(testGlobalPropertyId, 30, 40); - - // derive the expected event label for this event - EventLabel expectedEventLabel = GlobalPropertyUpdateEvent.getEventLabel(c, testGlobalPropertyId); - - // have the event labeler produce an event label and show it - // is equal to the expected event label - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - assertEquals(expectedEventLabel, actualEventLabel); - - } - }); - } - - @Test - @UnitTestMethod(name = "getPrimaryKeyValue", args = {}) - public void testGetPrimaryKeyValue() { - GlobalPropertyId globalPropertyId = new SimpleGlobalPropertyId("id"); - Integer previousValue = 12; - Integer currentValue = 13; - GlobalPropertyUpdateEvent globalPropertyUpdateEvent = new GlobalPropertyUpdateEvent(globalPropertyId, previousValue, currentValue); - assertEquals(globalPropertyId, globalPropertyUpdateEvent.getPrimaryKeyValue()); - } - - /* - * Runs the engine by loading all plugins necessary to support globals - * and executes the given consumer as an AgentActionPlan. - */ - private void testConsumer(Consumer consumer) { - - Builder builder = Simulation.builder(); - - GlobalPropertiesPluginData.Builder initialDatabuilder = GlobalPropertiesPluginData.builder(); - int defaultValue = 0; - for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setDefaultValue(defaultValue++).setType(Integer.class).build(); - initialDatabuilder.defineGlobalProperty(testGlobalPropertyId, propertyDefinition); - } - GlobalPropertiesPluginData globalInitialData = initialDatabuilder.build(); - - builder.addPlugin(GlobalPropertiesPlugin.getPlugin(globalInitialData)); - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, consumer)); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - builder.addPlugin(testPlugin); - - // build and execute the engine - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - builder.setOutputConsumer(scenarioPlanCompletionObserver::handleOutput).build().execute(); - - // show that all actions were executed - assertTrue(scenarioPlanCompletionObserver.allPlansExecuted()); - - } - -} diff --git a/gcm3/src/test/java/plugins/globalproperties/support/AT_GlobalPropertiesError.java b/gcm3/src/test/java/plugins/globalproperties/support/AT_GlobalPropertiesError.java deleted file mode 100644 index eecec02b4..000000000 --- a/gcm3/src/test/java/plugins/globalproperties/support/AT_GlobalPropertiesError.java +++ /dev/null @@ -1,30 +0,0 @@ -package plugins.globalproperties.support; - -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = GlobalPropertiesError.class) -public class AT_GlobalPropertiesError { - - @Test - @UnitTestMethod(name = "getDescription", args = {}) - public void test() { - //show that each description is a unique, non-null and non-empty string - Set descriptions = new LinkedHashSet<>(); - for(GlobalPropertiesError globalPropertiesError : GlobalPropertiesError.values()) { - String description = globalPropertiesError.getDescription(); - assertNotNull(description,"null description for "+globalPropertiesError); - assertTrue(description.length()>0, "empty string for "+globalPropertiesError); - boolean unique = descriptions.add(description); - assertTrue(unique,"description for "+globalPropertiesError+" is not unique"); - } - } -} diff --git a/gcm3/src/test/java/plugins/globalproperties/support/AT_GlobalPropertyId.java b/gcm3/src/test/java/plugins/globalproperties/support/AT_GlobalPropertyId.java deleted file mode 100644 index 11f5f65e3..000000000 --- a/gcm3/src/test/java/plugins/globalproperties/support/AT_GlobalPropertyId.java +++ /dev/null @@ -1,14 +0,0 @@ -package plugins.globalproperties.support; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - -@UnitTest(target = GlobalPropertyId.class) -public class AT_GlobalPropertyId{ - - @Test - public void test() { - //nothing to test - } -} diff --git a/gcm3/src/test/java/plugins/globalproperties/support/AT_SimpleGlobalPropertyId.java b/gcm3/src/test/java/plugins/globalproperties/support/AT_SimpleGlobalPropertyId.java deleted file mode 100644 index 34e34538f..000000000 --- a/gcm3/src/test/java/plugins/globalproperties/support/AT_SimpleGlobalPropertyId.java +++ /dev/null @@ -1,130 +0,0 @@ -package plugins.globalproperties.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = SimpleGlobalPropertyId.class) -public class AT_SimpleGlobalPropertyId { - - @Test - @UnitTestConstructor(args = { Object.class }) - public void testConstructor() { - assertNotNull(new SimpleGlobalPropertyId(5)); - - assertThrows(RuntimeException.class, () -> new SimpleGlobalPropertyId(null)); - } - - @Test - @UnitTestMethod(name = "toString", args = {}) - public void testToString() { - /* - * Show that the toString of the SimpleGlobalPropertyId equals its input's - * toString - */ - - assertEquals(Integer.toString(5), new SimpleGlobalPropertyId(5).toString()); - assertEquals("table", new SimpleGlobalPropertyId("table").toString()); - assertEquals(Double.toString(2345.5345), new SimpleGlobalPropertyId(2345.5345).toString()); - - } - - @Test - @UnitTestMethod(name = "toString", args = {}) - public void testEquals() { - SimpleGlobalPropertyId id_1 = new SimpleGlobalPropertyId(2); - SimpleGlobalPropertyId id_2 = new SimpleGlobalPropertyId(5); - SimpleGlobalPropertyId id_3 = new SimpleGlobalPropertyId(2); - SimpleGlobalPropertyId id_4 = new SimpleGlobalPropertyId("A"); - SimpleGlobalPropertyId id_5 = new SimpleGlobalPropertyId("A"); - SimpleGlobalPropertyId id_6 = new SimpleGlobalPropertyId("B"); - - assertEquals(id_1, id_1); - assertNotEquals(id_1, id_2); - assertEquals(id_1, id_3); - assertNotEquals(id_1, id_4); - assertNotEquals(id_1, id_5); - assertNotEquals(id_1, id_6); - - assertNotEquals(id_2, id_1); - assertEquals(id_2, id_2); - assertNotEquals(id_2, id_3); - assertNotEquals(id_2, id_4); - assertNotEquals(id_2, id_5); - assertNotEquals(id_2, id_6); - - assertNotEquals(id_2, id_1); - assertEquals(id_2, id_2); - assertNotEquals(id_2, id_3); - assertNotEquals(id_2, id_4); - assertNotEquals(id_2, id_5); - assertNotEquals(id_2, id_6); - - assertEquals(id_3, id_1); - assertNotEquals(id_3, id_2); - assertEquals(id_3, id_3); - assertNotEquals(id_3, id_4); - assertNotEquals(id_3, id_5); - assertNotEquals(id_3, id_6); - - assertNotEquals(id_4, id_1); - assertNotEquals(id_4, id_2); - assertNotEquals(id_4, id_3); - assertEquals(id_4, id_4); - assertEquals(id_4, id_5); - assertNotEquals(id_4, id_6); - - assertNotEquals(id_5, id_1); - assertNotEquals(id_5, id_2); - assertNotEquals(id_5, id_3); - assertEquals(id_5, id_4); - assertEquals(id_5, id_5); - assertNotEquals(id_5, id_6); - - assertNotEquals(id_6, id_1); - assertNotEquals(id_6, id_2); - assertNotEquals(id_6, id_3); - assertNotEquals(id_6, id_4); - assertNotEquals(id_6, id_5); - assertEquals(id_6, id_6); - - assertNotEquals(id_1, null); - assertNotEquals(id_2, null); - assertNotEquals(id_3, null); - assertNotEquals(id_4, null); - assertNotEquals(id_5, null); - assertNotEquals(id_6, null); - - } - - @Test - @UnitTestMethod(name = "hashCode", args = {}) - public void testHashCode() { - - // equal objects have equal hash codes - for (int i = 0; i < 30; i++) { - SimpleGlobalPropertyId s1 = new SimpleGlobalPropertyId(i); - SimpleGlobalPropertyId s2 = new SimpleGlobalPropertyId(i); - assertEquals(s1.hashCode(), s2.hashCode()); - } - - Set hashCodes = new LinkedHashSet<>(); - for (int i = 0; i < 30; i++) { - boolean unique = hashCodes.add(new SimpleGlobalPropertyId(i).hashCode()); - assertTrue(unique); - } - - } - -} diff --git a/gcm3/src/test/java/plugins/globalproperties/testsupport/AT_TestGlobalPropertyId.java b/gcm3/src/test/java/plugins/globalproperties/testsupport/AT_TestGlobalPropertyId.java deleted file mode 100644 index 1eb557fec..000000000 --- a/gcm3/src/test/java/plugins/globalproperties/testsupport/AT_TestGlobalPropertyId.java +++ /dev/null @@ -1,36 +0,0 @@ -package plugins.globalproperties.testsupport; - -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import plugins.globalproperties.support.GlobalPropertyId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = TestGlobalPropertyId.class) -public class AT_TestGlobalPropertyId { - - - @Test - @UnitTestMethod(name = "getUnknownGlobalPropertyId", args = {}) - public void testGetUnknownRegionId() { - Set unknownGlobalPropertyIds = new LinkedHashSet<>(); - for (int i = 0; i < 30; i++) { - GlobalPropertyId unknownGlobalPropertyId = TestGlobalPropertyId.getUnknownGlobalPropertyId(); - assertNotNull(unknownGlobalPropertyId); - boolean unique = unknownGlobalPropertyIds.add(unknownGlobalPropertyId); - assertTrue(unique); - for (TestGlobalPropertyId testGlobalPropertyId : TestGlobalPropertyId.values()) { - assertNotEquals(testGlobalPropertyId, unknownGlobalPropertyId); - } - } - } - - -} diff --git a/gcm3/src/test/java/plugins/groups/AT_GroupsPlugin.java b/gcm3/src/test/java/plugins/groups/AT_GroupsPlugin.java deleted file mode 100644 index 96c5a81b1..000000000 --- a/gcm3/src/test/java/plugins/groups/AT_GroupsPlugin.java +++ /dev/null @@ -1,44 +0,0 @@ -package plugins.groups; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import nucleus.Plugin; -import nucleus.PluginId; -import plugins.people.PeoplePluginId; -import plugins.stochastics.StochasticsPluginId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = GroupsPlugin.class) -public class AT_GroupsPlugin { - - - @Test - @UnitTestMethod(name = "getGroupPlugin", args = { GroupsPluginData.class }) - public void testGetGroupPlugin() { - - GroupsPluginData groupsPluginData = GroupsPluginData.builder().build(); - Plugin groupPlugin = GroupsPlugin.getGroupPlugin(groupsPluginData); - - assertEquals(1,groupPlugin.getPluginDatas().size()); - assertTrue(groupPlugin.getPluginDatas().contains(groupsPluginData)); - - assertEquals(GroupsPluginId.PLUGIN_ID, groupPlugin.getPluginId()); - - Set expectedDependencies = new LinkedHashSet<>(); - expectedDependencies.add(PeoplePluginId.PLUGIN_ID); - expectedDependencies.add(StochasticsPluginId.PLUGIN_ID); - - assertEquals(expectedDependencies, groupPlugin.getPluginDependencies()); - - - - } - -} diff --git a/gcm3/src/test/java/plugins/groups/AT_GroupsPluginData.java b/gcm3/src/test/java/plugins/groups/AT_GroupsPluginData.java deleted file mode 100644 index ffe9cc929..000000000 --- a/gcm3/src/test/java/plugins/groups/AT_GroupsPluginData.java +++ /dev/null @@ -1,705 +0,0 @@ -package plugins.groups; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.EnumSet; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Random; -import java.util.Set; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.PluginData; -import plugins.groups.support.GroupError; -import plugins.groups.support.GroupId; -import plugins.groups.support.GroupPropertyId; -import plugins.groups.support.GroupTypeId; -import plugins.groups.testsupport.TestGroupPropertyId; -import plugins.groups.testsupport.TestGroupTypeId; -import plugins.people.support.PersonId; -import plugins.util.properties.PropertyDefinition; -import plugins.util.properties.PropertyError; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; -import util.random.RandomGeneratorProvider; -import util.wrappers.MultiKey; - -@UnitTest(target = GroupsPluginData.class) -public class AT_GroupsPluginData { - - @Test - @UnitTestMethod(name = "builder", args = {}) - public void testBuilder() { - assertNotNull(GroupsPluginData.builder()); - } - - @Test - @UnitTestMethod(target = GroupsPluginData.Builder.class, name = "build", args = {}) - public void testBuild() { - // show that the builder does not return null - assertNotNull(GroupsPluginData.builder().build()); - - // show that the builder clears its state on build invocation - GroupsPluginData.Builder builder = GroupsPluginData.builder(); - - GroupsPluginData groupInitialData = builder // - .addPersonToGroup(new GroupId(0), new PersonId(0))// - .addGroupTypeId(TestGroupTypeId.GROUP_TYPE_1)// - .addGroup(new GroupId(0), TestGroupTypeId.GROUP_TYPE_1)// - .defineGroupProperty(TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, - TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK.getPropertyDefinition())// - .setGroupPropertyValue(new GroupId(0), TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, true)// - .build();// - - assertFalse(groupInitialData.getGroupIds().isEmpty()); - assertFalse(groupInitialData.getGroupTypeIds().isEmpty()); - - groupInitialData = builder.build(); - assertTrue(groupInitialData.getGroupIds().isEmpty()); - assertTrue(groupInitialData.getGroupTypeIds().isEmpty()); - - // precondition tests - - // if a person was added to a group that was not defined - ContractException contractException = assertThrows(ContractException.class, () -> builder.addPersonToGroup(new GroupId(0), new PersonId(0)).build()); - assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); - - // if a group was added with a group type id that was not defined - contractException = assertThrows(ContractException.class, () -> builder.addGroup(new GroupId(0), TestGroupTypeId.GROUP_TYPE_1).build()); - assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); - - // if a group property definition was defined for a group type id that - // was not defined. - contractException = assertThrows(ContractException.class, () -> builder.defineGroupProperty(TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, - TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK.getPropertyDefinition()).build()); - assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); - - // if a group property value was set for a group id that was not - // defined. - contractException = assertThrows(ContractException.class, () -> { - builder.addGroupTypeId(TestGroupTypeId.GROUP_TYPE_1); - builder.defineGroupProperty(TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, - TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK.getPropertyDefinition()); - builder.setGroupPropertyValue(new GroupId(0), TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, true); - builder.build(); - }); - assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); - - // if a group property value is added for a group property id that is - // not associated with the group. - contractException = assertThrows(ContractException.class, () -> { - builder.addGroupTypeId(TestGroupTypeId.GROUP_TYPE_1); - builder.defineGroupProperty(TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, - TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK.getPropertyDefinition()); - builder.setGroupPropertyValue(new GroupId(0), TestGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, 15); - builder.addGroup(new GroupId(0), TestGroupTypeId.GROUP_TYPE_1); - builder.build(); - }); - assertEquals(GroupError.UNKNOWN_GROUP_PROPERTY_ID, contractException.getErrorType()); - - // if a group property value is added that is incompatible with the - // corresponding property definition - contractException = assertThrows(ContractException.class, () -> { - builder.addGroupTypeId(TestGroupTypeId.GROUP_TYPE_1); - builder.defineGroupProperty(TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, - TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK.getPropertyDefinition()); - builder.setGroupPropertyValue(new GroupId(0), TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, 15); - builder.addGroup(new GroupId(0), TestGroupTypeId.GROUP_TYPE_1); - builder.build(); - }); - assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); - - // if a group property definition does not contain a default value - contractException = assertThrows(ContractException.class, () -> { - builder.addGroupTypeId(TestGroupTypeId.GROUP_TYPE_1); - - builder.defineGroupProperty(TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, PropertyDefinition.builder().setType(Boolean.class).build()); - builder.build(); - }); - assertEquals(GroupError.PROPERTY_DEFINITION_REQUIRES_DEFAULT, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(target = GroupsPluginData.Builder.class, name = "addGroupTypeId", args = { GroupTypeId.class }) - public void testAddGroupTypeId() { - GroupsPluginData.Builder builder = GroupsPluginData.builder(); - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - builder.addGroupTypeId(testGroupTypeId); - } - GroupsPluginData groupInitialData = builder.build(); - - // show that the group type ids exist in the groupInitialData - assertEquals(EnumSet.allOf(TestGroupTypeId.class), groupInitialData.getGroupTypeIds()); - } - - @Test - @UnitTestMethod(target = GroupsPluginData.Builder.class, name = "addGroup", args = { GroupId.class, GroupTypeId.class }) - public void testAddGroup() { - GroupsPluginData.Builder builder = GroupsPluginData.builder(); - int masterGroupId = 0; - Set expectedGroupIds = new LinkedHashSet<>(); - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - builder.addGroupTypeId(testGroupTypeId); - - GroupId groupId = new GroupId(masterGroupId++); - builder.addGroup(groupId, testGroupTypeId); - expectedGroupIds.add(groupId); - - groupId = new GroupId(masterGroupId++); - builder.addGroup(groupId, testGroupTypeId); - expectedGroupIds.add(groupId); - } - GroupsPluginData groupInitialData = builder.build(); - - // show that the group ids that were added are present in the - // groupInitialData - assertEquals(expectedGroupIds, groupInitialData.getGroupIds()); - } - - @Test - @UnitTestMethod(target = GroupsPluginData.Builder.class, name = "defineGroupProperty", args = { GroupTypeId.class, GroupPropertyId.class, PropertyDefinition.class }) - public void testDefineGroupProperty() { - GroupsPluginData.Builder builder = GroupsPluginData.builder(); - - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - builder.addGroupTypeId(testGroupTypeId); - } - - for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { - - builder.defineGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId, testGroupPropertyId.getPropertyDefinition()); - } - - GroupsPluginData groupInitialData = builder.build(); - - // show that each property definition that was added is present in the - // groupInitialData - for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { - PropertyDefinition expectedPropertyDefinition = testGroupPropertyId.getPropertyDefinition(); - PropertyDefinition actualPropertyDefinition = groupInitialData.getGroupPropertyDefinition(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId); - assertEquals(expectedPropertyDefinition, actualPropertyDefinition); - } - } - - @Test - @UnitTestMethod(target = GroupsPluginData.Builder.class, name = "setGroupPropertyValue", args = { GroupId.class, GroupPropertyId.class, Object.class }) - public void testSetGroupPropertyValue() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(206512993284256660L); - - GroupsPluginData.Builder builder = GroupsPluginData.builder(); - - // add in the group types - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - builder.addGroupTypeId(testGroupTypeId); - } - - // define the group properties - for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { - builder.defineGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId, testGroupPropertyId.getPropertyDefinition()); - } - - // create a container to hold expected values - Map expectedValues = new LinkedHashMap<>(); - - /* - * Add a few groups and set about half of the property values, leaving - * the other half to be defined by the default values of the - * corresponding property definitions. - */ - TestGroupTypeId testGroupTypeId = TestGroupTypeId.GROUP_TYPE_1; - for (int i = 0; i < 10; i++) { - GroupId groupId = new GroupId(i); - builder.addGroup(groupId, testGroupTypeId); - - Set testGroupPropertyIds = TestGroupPropertyId.getTestGroupPropertyIds(testGroupTypeId); - for (TestGroupPropertyId testGroupPropertyId : testGroupPropertyIds) { - if (randomGenerator.nextBoolean()) { - Object value = testGroupPropertyId.getRandomPropertyValue(randomGenerator); - builder.setGroupPropertyValue(groupId, testGroupPropertyId, value); - expectedValues.put(new MultiKey(groupId, testGroupPropertyId), value); - } else { - expectedValues.put(new MultiKey(groupId, testGroupPropertyId), testGroupPropertyId.getPropertyDefinition().getDefaultValue().get()); - } - } - // move to the next group type id - testGroupTypeId = testGroupTypeId.next(); - } - - // build the group initial data - GroupsPluginData groupInitialData = builder.build(); - - // show that the expected group property values are present - for (MultiKey multiKey : expectedValues.keySet()) { - GroupId groupId = multiKey.getKey(0); - GroupPropertyId groupPropertyId = multiKey.getKey(1); - Object expectedValue = expectedValues.get(multiKey); - Object actualValue = groupInitialData.getGroupPropertyValue(groupId, groupPropertyId); - assertEquals(expectedValue, actualValue); - } - } - - @Test - @UnitTestMethod(target = GroupsPluginData.Builder.class, name = "addPersonToGroup", args = { GroupId.class, PersonId.class }) - public void testAddPersonToGroup() { - - Random random = new Random(7282493148489771700L); - - Map> expectedGroupAssignments = new LinkedHashMap<>(); - - GroupsPluginData.Builder builder = GroupsPluginData.builder(); - // add in the group types - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - builder.addGroupTypeId(testGroupTypeId); - } - - // create some people - List people = new ArrayList<>(); - for (int i = 0; i < 100; i++) { - people.add(new PersonId(i)); - } - - /* - * Add a few groups and add to those groups 0 to 9 randomly selected - * people. Record the assignments in the expected data structure. - */ - TestGroupTypeId testGroupTypeId = TestGroupTypeId.GROUP_TYPE_1; - for (int i = 0; i < 20; i++) { - // add the group - GroupId groupId = new GroupId(i); - builder.addGroup(groupId, testGroupTypeId); - Set peopleInGroup = new LinkedHashSet<>(); - expectedGroupAssignments.put(groupId, peopleInGroup); - testGroupTypeId = testGroupTypeId.next(); - - // select some people and add them to the group - Collections.shuffle(people, random); - int count = random.nextInt(10); - for (int j = 0; j < count; j++) { - PersonId personId = people.get(j); - builder.addPersonToGroup(groupId, personId); - peopleInGroup.add(personId); - } - } - - // build the group initial data - GroupsPluginData groupInitialData = builder.build(); - - // show that the group memberships are as expected - assertEquals(expectedGroupAssignments.keySet(), groupInitialData.getGroupIds()); - for (GroupId groupId : groupInitialData.getGroupIds()) { - Set actualGroupMembers = groupInitialData.getGroupMembers(groupId); - Set expectedGroupMembers = expectedGroupAssignments.get(groupId); - assertEquals(expectedGroupMembers, actualGroupMembers); - } - - } - - @Test - @UnitTestMethod(name = "getGroupPropertyDefinition", args = { GroupTypeId.class, GroupPropertyId.class }) - public void testGetGroupPropertyDefinition() { - - GroupsPluginData.Builder builder = GroupsPluginData.builder(); - - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - builder.addGroupTypeId(testGroupTypeId); - } - - for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { - - builder.defineGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId, testGroupPropertyId.getPropertyDefinition()); - } - - GroupsPluginData groupInitialData = builder.build(); - - // show that each property definition that was added is present in the - // groupInitialData - for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { - PropertyDefinition expectedPropertyDefinition = testGroupPropertyId.getPropertyDefinition(); - PropertyDefinition actualPropertyDefinition = groupInitialData.getGroupPropertyDefinition(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId); - assertEquals(expectedPropertyDefinition, actualPropertyDefinition); - } - - // precondition tests - - // if the group type id is null - ContractException contractException = assertThrows(ContractException.class, - () -> groupInitialData.getGroupPropertyDefinition(null, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK)); - assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); - - // if the group type id is unknown - contractException = assertThrows(ContractException.class, - () -> groupInitialData.getGroupPropertyDefinition(TestGroupTypeId.getUnknownGroupTypeId(), TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK)); - assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); - - // if the group property id is null - contractException = assertThrows(ContractException.class, () -> groupInitialData.getGroupPropertyDefinition(TestGroupTypeId.GROUP_TYPE_1, null)); - assertEquals(GroupError.NULL_GROUP_PROPERTY_ID, contractException.getErrorType()); - - // if the group property id is not associated with the group type id via - // a property definition - contractException = assertThrows(ContractException.class, () -> groupInitialData.getGroupPropertyDefinition(TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.getUnknownGroupPropertyId())); - assertEquals(GroupError.UNKNOWN_GROUP_PROPERTY_ID, contractException.getErrorType()); - - // if the group property id is not associated with the group type id via - // a property definition - contractException = assertThrows(ContractException.class, - () -> groupInitialData.getGroupPropertyDefinition(TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK)); - assertEquals(GroupError.UNKNOWN_GROUP_PROPERTY_ID, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "getGroupPropertyIds", args = { GroupTypeId.class }) - public void testGetGroupPropertyIds() { - - GroupsPluginData.Builder builder = GroupsPluginData.builder(); - - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - builder.addGroupTypeId(testGroupTypeId); - } - - for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { - builder.defineGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId, testGroupPropertyId.getPropertyDefinition()); - } - - GroupsPluginData groupInitialData = builder.build(); - - // show that the group properties for each group type match expectations - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - Set actualGroupPropertyIds = groupInitialData.getGroupPropertyIds(testGroupTypeId); - Set expectedGroupPropertyIds = TestGroupPropertyId.getTestGroupPropertyIds(testGroupTypeId); - assertEquals(expectedGroupPropertyIds, actualGroupPropertyIds); - } - - // precondition tests - // if the group type id is null - ContractException contractException = assertThrows(ContractException.class, () -> groupInitialData.getGroupPropertyIds(null)); - assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); - - // if the group type id is unknown - contractException = assertThrows(ContractException.class, () -> groupInitialData.getGroupPropertyIds(TestGroupTypeId.getUnknownGroupTypeId())); - assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "getGroupPropertyValue", args = { GroupId.class, GroupPropertyId.class }) - public void testGetGroupPropertyValue() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8435308203966252001L); - - GroupsPluginData.Builder builder = GroupsPluginData.builder(); - - // add in the group types - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - builder.addGroupTypeId(testGroupTypeId); - } - - // define the group properties - for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { - builder.defineGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId, testGroupPropertyId.getPropertyDefinition()); - } - - // create a container to hold expected values - Map expectedValues = new LinkedHashMap<>(); - - /* - * Add a few groups and set about half of the property values, leaving - * the other half to be defined by the default values of the - * corresponding property definitions. - */ - TestGroupTypeId testGroupTypeId = TestGroupTypeId.GROUP_TYPE_1; - for (int i = 0; i < 10; i++) { - GroupId groupId = new GroupId(i); - builder.addGroup(groupId, testGroupTypeId); - - Set testGroupPropertyIds = TestGroupPropertyId.getTestGroupPropertyIds(testGroupTypeId); - for (TestGroupPropertyId testGroupPropertyId : testGroupPropertyIds) { - if (randomGenerator.nextBoolean()) { - Object value = testGroupPropertyId.getRandomPropertyValue(randomGenerator); - builder.setGroupPropertyValue(groupId, testGroupPropertyId, value); - expectedValues.put(new MultiKey(groupId, testGroupPropertyId), value); - } else { - expectedValues.put(new MultiKey(groupId, testGroupPropertyId), testGroupPropertyId.getPropertyDefinition().getDefaultValue().get()); - } - } - // move to the next group type id - testGroupTypeId = testGroupTypeId.next(); - } - - // build the group initial data - GroupsPluginData groupInitialData = builder.build(); - - // show that the expected group property values are present - for (MultiKey multiKey : expectedValues.keySet()) { - GroupId groupId = multiKey.getKey(0); - GroupPropertyId groupPropertyId = multiKey.getKey(1); - Object expectedValue = expectedValues.get(multiKey); - Object actualValue = groupInitialData.getGroupPropertyValue(groupId, groupPropertyId); - assertEquals(expectedValue, actualValue); - } - - // precondition tests - - // if the group id is null - ContractException contractException = assertThrows(ContractException.class, - () -> groupInitialData.getGroupPropertyValue(null, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK)); - assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); - - // if the group id is unknown - contractException = assertThrows(ContractException.class, () -> groupInitialData.getGroupPropertyValue(new GroupId(10000), TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK)); - assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); - - // if the group property id is null - contractException = assertThrows(ContractException.class, () -> groupInitialData.getGroupPropertyValue(new GroupId(0), null)); - assertEquals(GroupError.NULL_GROUP_PROPERTY_ID, contractException.getErrorType()); - - // if the group property id is not associated with the group type id via - // a property definition - contractException = assertThrows(ContractException.class, () -> groupInitialData.getGroupPropertyValue(new GroupId(0), TestGroupPropertyId.getUnknownGroupPropertyId())); - assertEquals(GroupError.UNKNOWN_GROUP_PROPERTY_ID, contractException.getErrorType()); - - contractException = assertThrows(ContractException.class, () -> groupInitialData.getGroupPropertyValue(new GroupId(0), TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK)); - assertEquals(GroupError.UNKNOWN_GROUP_PROPERTY_ID, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "getGroupTypeIds", args = {}) - public void testGetGroupTypeIds() { - GroupsPluginData.Builder builder = GroupsPluginData.builder(); - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - builder.addGroupTypeId(testGroupTypeId); - } - GroupsPluginData groupInitialData = builder.build(); - - // show that the group type ids exist in the groupInitialData - assertEquals(EnumSet.allOf(TestGroupTypeId.class), groupInitialData.getGroupTypeIds()); - } - - @Test - @UnitTestMethod(name = "getGroupIds", args = {}) - public void testGetGroupIds() { - GroupsPluginData.Builder builder = GroupsPluginData.builder(); - int masterGroupId = 0; - Set expectedGroupIds = new LinkedHashSet<>(); - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - builder.addGroupTypeId(testGroupTypeId); - - GroupId groupId = new GroupId(masterGroupId++); - builder.addGroup(groupId, testGroupTypeId); - expectedGroupIds.add(groupId); - - groupId = new GroupId(masterGroupId++); - builder.addGroup(groupId, testGroupTypeId); - expectedGroupIds.add(groupId); - } - GroupsPluginData groupInitialData = builder.build(); - - // show that the group ids that were added are present in the - // groupInitialData - assertEquals(expectedGroupIds, groupInitialData.getGroupIds()); - - } - - @Test - @UnitTestMethod(name = "getGroupMembers", args = { GroupId.class }) - public void testGetGroupMembers() { - - Random random = new Random(4685636461674441597L); - - Map> expectedGroupAssignments = new LinkedHashMap<>(); - - GroupsPluginData.Builder builder = GroupsPluginData.builder(); - // add in the group types - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - builder.addGroupTypeId(testGroupTypeId); - } - - // create some people - List people = new ArrayList<>(); - for (int i = 0; i < 100; i++) { - people.add(new PersonId(i)); - } - - /* - * Add a few groups and add to those groups 0 to 9 randomly selected - * people. Record the assignments in the expected data structure. - */ - TestGroupTypeId testGroupTypeId = TestGroupTypeId.GROUP_TYPE_1; - for (int i = 0; i < 20; i++) { - // add the group - GroupId groupId = new GroupId(i); - builder.addGroup(groupId, testGroupTypeId); - Set peopleInGroup = new LinkedHashSet<>(); - expectedGroupAssignments.put(groupId, peopleInGroup); - testGroupTypeId = testGroupTypeId.next(); - - // select some people and add them to the group - Collections.shuffle(people, random); - int count = random.nextInt(10); - for (int j = 0; j < count; j++) { - PersonId personId = people.get(j); - builder.addPersonToGroup(groupId, personId); - peopleInGroup.add(personId); - } - } - - // build the group initial data - GroupsPluginData groupInitialData = builder.build(); - - // show that the group memberships are as expected - assertEquals(expectedGroupAssignments.keySet(), groupInitialData.getGroupIds()); - for (GroupId groupId : groupInitialData.getGroupIds()) { - Set actualGroupMembers = groupInitialData.getGroupMembers(groupId); - Set expectedGroupMembers = expectedGroupAssignments.get(groupId); - assertEquals(expectedGroupMembers, actualGroupMembers); - } - - // precondition tests - - // if the group id is null - ContractException contractException = assertThrows(ContractException.class, () -> groupInitialData.getGroupMembers(null)); - assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); - - contractException = assertThrows(ContractException.class, () -> groupInitialData.getGroupMembers(new GroupId(100000))); - assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "getGroupTypeId", args = { GroupId.class }) - public void testGetGroupTypeId() { - - GroupsPluginData.Builder builder = GroupsPluginData.builder(); - int masterGroupId = 0; - Map expectedGroupTypes = new LinkedHashMap<>(); - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - builder.addGroupTypeId(testGroupTypeId); - - GroupId groupId = new GroupId(masterGroupId++); - builder.addGroup(groupId, testGroupTypeId); - expectedGroupTypes.put(groupId, testGroupTypeId); - - groupId = new GroupId(masterGroupId++); - builder.addGroup(groupId, testGroupTypeId); - expectedGroupTypes.put(groupId, testGroupTypeId); - } - GroupsPluginData groupInitialData = builder.build(); - - for (GroupId groupId : expectedGroupTypes.keySet()) { - GroupTypeId expecctedGroupTypeId = expectedGroupTypes.get(groupId); - GroupTypeId actualGroupTypeId = groupInitialData.getGroupTypeId(groupId); - assertEquals(expecctedGroupTypeId, actualGroupTypeId); - } - - // precondition tests - - // if the group id is null - ContractException contractException = assertThrows(ContractException.class, () -> groupInitialData.getGroupTypeId(null)); - assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); - - // if the group id is unknown - contractException = assertThrows(ContractException.class, () -> groupInitialData.getGroupTypeId(new GroupId(1000000))); - assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "getCloneBuilder", args = {}) - public void testGetCloneBuilder() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(9130589441333999144L); - - GroupsPluginData.Builder groupPluginDataBuilder = GroupsPluginData.builder(); - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - groupPluginDataBuilder.addGroupTypeId(testGroupTypeId); - } - for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { - groupPluginDataBuilder.defineGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId, testGroupPropertyId.getPropertyDefinition()); - } - int groupCount = 10; - List groups = new ArrayList<>(); - for (int i = 0; i < groupCount; i++) { - GroupId groupId = new GroupId(i); - groups.add(groupId); - TestGroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); - groupPluginDataBuilder.addGroup(groupId, groupTypeId); - for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.getTestGroupPropertyIds(groupTypeId)) { - if (randomGenerator.nextBoolean()) { - Object randomPropertyValue = testGroupPropertyId.getRandomPropertyValue(randomGenerator); - groupPluginDataBuilder.setGroupPropertyValue(groupId, testGroupPropertyId, randomPropertyValue); - } - } - } - - int personCount = 100; - - for (int i = 0; i < personCount; i++) { - PersonId personId = new PersonId(i); - int numberOfGroups = randomGenerator.nextInt(5); - Collections.sort(groups); - for (int j = 0; j < numberOfGroups; j++) { - GroupId groupId = groups.get(j); - groupPluginDataBuilder.addPersonToGroup(groupId, personId); - } - } - GroupsPluginData groupsPluginData = groupPluginDataBuilder.build(); - - PluginData pluginData = groupsPluginData.getCloneBuilder().build(); - - // show that the clone plugin data has the correct type - assertTrue(pluginData instanceof GroupsPluginData); - - GroupsPluginData cloneGroupPluginData = (GroupsPluginData) pluginData; - - // show that the two plugin datas have the same groups - assertEquals(groupsPluginData.getGroupIds(), cloneGroupPluginData.getGroupIds()); - - // show that the two plugin datas have the same group types - assertEquals(groupsPluginData.getGroupTypeIds(), cloneGroupPluginData.getGroupTypeIds()); - - // show that the two plugin datas have the same group property ids - for (GroupTypeId groupTypeId : groupsPluginData.getGroupTypeIds()) { - assertEquals(groupsPluginData.getGroupPropertyIds(groupTypeId), cloneGroupPluginData.getGroupPropertyIds(groupTypeId)); - //show that the two plugin datas have the same group property definitions - for(GroupPropertyId groupPropertyId : groupsPluginData.getGroupPropertyIds(groupTypeId)) { - PropertyDefinition expectedPropertyDefinition = groupsPluginData.getGroupPropertyDefinition(groupTypeId, groupPropertyId); - PropertyDefinition actualPropertyDefinition = cloneGroupPluginData.getGroupPropertyDefinition(groupTypeId, groupPropertyId); - assertEquals(expectedPropertyDefinition,actualPropertyDefinition); - } - } - - // show that the two plugin datas have the same groups - assertEquals(groupsPluginData.getGroupIds(), cloneGroupPluginData.getGroupIds()); - - //show that the groups have the same types - for(GroupId groupId : groupsPluginData.getGroupIds()) { - GroupTypeId expectedGroupTypeId = groupsPluginData.getGroupTypeId(groupId); - GroupTypeId actualGroupTypeId = cloneGroupPluginData.getGroupTypeId(groupId); - assertEquals(expectedGroupTypeId, actualGroupTypeId); - //show that the groups have the property values - for(GroupPropertyId groupPropertyId : groupsPluginData.getGroupPropertyIds(expectedGroupTypeId)) { - Object expectedPropertyValue = groupsPluginData.getGroupPropertyValue(groupId, groupPropertyId); - Object actualPropertyValue = cloneGroupPluginData.getGroupPropertyValue(groupId, groupPropertyId); - assertEquals(expectedPropertyValue, actualPropertyValue); - } - //show that the groups have the members - Set expectedGroupMembers = groupsPluginData.getGroupMembers(groupId); - Set actualGroupMembers = cloneGroupPluginData.getGroupMembers(groupId); - assertEquals(expectedGroupMembers, actualGroupMembers); - } - } - -} diff --git a/gcm3/src/test/java/plugins/groups/actors/AT_GroupPopulationReport.java b/gcm3/src/test/java/plugins/groups/actors/AT_GroupPopulationReport.java deleted file mode 100644 index fb73252c8..000000000 --- a/gcm3/src/test/java/plugins/groups/actors/AT_GroupPopulationReport.java +++ /dev/null @@ -1,414 +0,0 @@ -package plugins.groups.actors; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Random; - -import org.junit.jupiter.api.Test; - -import nucleus.Experiment; -import nucleus.Plugin; -import nucleus.testsupport.testplugin.ExperimentPlanCompletionObserver; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import plugins.groups.GroupsPlugin; -import plugins.groups.GroupsPluginData; -import plugins.groups.datamanagers.GroupsDataManager; -import plugins.groups.support.GroupId; -import plugins.groups.testsupport.TestGroupPropertyId; -import plugins.groups.testsupport.TestGroupTypeId; -import plugins.people.PeoplePlugin; -import plugins.people.PeoplePluginData; -import plugins.people.support.PersonId; -import plugins.reports.ReportsPlugin; -import plugins.reports.ReportsPluginData; -import plugins.reports.support.ReportHeader; -import plugins.reports.support.ReportId; -import plugins.reports.support.ReportItem; -import plugins.reports.support.ReportPeriod; -import plugins.reports.support.SimpleReportId; -import plugins.reports.testsupport.TestReportItemOutputConsumer; -import plugins.stochastics.StochasticsPlugin; -import plugins.stochastics.StochasticsPluginData; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = GroupPopulationReport.class) -public class AT_GroupPopulationReport { - - @Test - @UnitTestMethod(name = "init", args = {}) - public void testHourlyReport() { - - /* - * We will add one agent to move people in and out of groups and create - * and remove groups. Report items from the report will be collected in - * an output consumer and compared to the expected output. - */ - - // add the action plugin - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // have the agent add a new group of type 1 with three people - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(getTime(0, 0, 0), (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); - assertEquals(3, groupId.getValue()); - groupsDataManager.addPersonToGroup(new PersonId(4),groupId); - groupsDataManager.addPersonToGroup(new PersonId(5),groupId); - groupsDataManager.addPersonToGroup(new PersonId(6),groupId); - })); - - // have the agent add a new group of type 1 with three people - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(getTime(1, 1, 15), (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - groupsDataManager.removePersonFromGroup(new PersonId(4),new GroupId(3)); - groupsDataManager.removeGroup(new GroupId(0)); - })); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(getTime(1, 2, 48), (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - groupsDataManager.removeGroup(new GroupId(2)); - - })); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(getTime(1, 5, 14), (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - groupsDataManager.addPersonToGroup( new PersonId(7),new GroupId(3)); - groupsDataManager.addPersonToGroup(new PersonId(8),new GroupId(3)); - groupsDataManager.addPersonToGroup(new PersonId(9),new GroupId(3)); - })); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(getTime(1, 5, 34), (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); - assertEquals(4, groupId.getValue()); - groupsDataManager.addPersonToGroup(new PersonId(3),new GroupId(4)); - groupsDataManager.addPersonToGroup(new PersonId(4),new GroupId(4)); - groupsDataManager.addPersonToGroup(new PersonId(5),new GroupId(4)); - groupsDataManager.addPersonToGroup(new PersonId(6),new GroupId(4)); - groupsDataManager.addPersonToGroup(new PersonId(7),new GroupId(4)); - - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - - - - // place the initial data into the expected output consumer - Map expectedReportItems = new LinkedHashMap<>(); - - for (int hour = 0; hour < 24; hour++) { - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 0, hour, TestGroupTypeId.GROUP_TYPE_1, 3, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 0, hour, TestGroupTypeId.GROUP_TYPE_2, 3, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 0, hour, TestGroupTypeId.GROUP_TYPE_3, 0, 1), 1); - } - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 1, 0, TestGroupTypeId.GROUP_TYPE_1, 3, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 1, 0, TestGroupTypeId.GROUP_TYPE_2, 3, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 1, 0, TestGroupTypeId.GROUP_TYPE_3, 0, 1), 1); - - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 1, 1, TestGroupTypeId.GROUP_TYPE_1, 2, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 1, 1, TestGroupTypeId.GROUP_TYPE_2, 3, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 1, 1, TestGroupTypeId.GROUP_TYPE_3, 0, 1), 1); - - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 1, 2, TestGroupTypeId.GROUP_TYPE_1, 2, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 1, 2, TestGroupTypeId.GROUP_TYPE_2, 3, 1), 1); - - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 1, 3, TestGroupTypeId.GROUP_TYPE_1, 2, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 1, 3, TestGroupTypeId.GROUP_TYPE_2, 3, 1), 1); - - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 1, 4, TestGroupTypeId.GROUP_TYPE_1, 2, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 1, 4, TestGroupTypeId.GROUP_TYPE_2, 3, 1), 1); - - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 1, 5, TestGroupTypeId.GROUP_TYPE_1, 5, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 1, 5, TestGroupTypeId.GROUP_TYPE_2, 3, 1), 1); - - Map actualReportItems = testConsumers(testPlugin, ReportPeriod.HOURLY, 5524610980534223950L); - assertEquals(expectedReportItems, actualReportItems); - } - - /* - * Returns the conversion into double valued days - * - * preconditions: all entries are non-negative and in their natural ranges - */ - private double getTime(int days, int hours, int minutes) { - return days + (double) hours / 24 + (double) minutes / 1440; - } - - @Test - @UnitTestMethod(name = "init", args = {}) - public void testDailyReport() { - - /* - * We will add one agent to move people in and out of groups and create - * and remove groups. Report items from the report will be collected in - * an output consumer and compared to the expected output. - */ - - // add the action plugin - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // have the agent add a new group of type 1 with three people - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); - - assertEquals(3, groupId.getValue()); - groupsDataManager.addPersonToGroup(new PersonId(4),groupId); - groupsDataManager.addPersonToGroup(new PersonId(5),groupId); - groupsDataManager.addPersonToGroup(new PersonId(6),groupId); - - })); - - // have the agent add a new group of type 1 with three people - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1.1, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - groupsDataManager.removePersonFromGroup(new PersonId(4),new GroupId(3)); - groupsDataManager.removeGroup(new GroupId(0)); - })); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2.5, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - groupsDataManager.removeGroup(new GroupId(2)); - - })); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(5.7, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - groupsDataManager.addPersonToGroup(new PersonId(7),new GroupId(3)); - groupsDataManager.addPersonToGroup(new PersonId(8),new GroupId(3)); - groupsDataManager.addPersonToGroup(new PersonId(9),new GroupId(3) ); - })); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(5.8, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); - assertEquals(4, groupId.getValue()); - - groupsDataManager.addPersonToGroup(new PersonId(3),new GroupId(4)); - groupsDataManager.addPersonToGroup(new PersonId(4),new GroupId(4)); - groupsDataManager.addPersonToGroup(new PersonId(5),new GroupId(4)); - groupsDataManager.addPersonToGroup(new PersonId(6),new GroupId(4)); - groupsDataManager.addPersonToGroup(new PersonId(7),new GroupId(4)); - - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - // create a container to hold expected results - Map expectedReportItems = new LinkedHashMap<>(); - - // place the initial data into the expected output consumer - - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 0, TestGroupTypeId.GROUP_TYPE_1, 3, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 0, TestGroupTypeId.GROUP_TYPE_2, 3, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 0, TestGroupTypeId.GROUP_TYPE_3, 0, 1), 1); - - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 1, TestGroupTypeId.GROUP_TYPE_1, 2, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 1, TestGroupTypeId.GROUP_TYPE_2, 3, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 1, TestGroupTypeId.GROUP_TYPE_3, 0, 1), 1); - - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 2, TestGroupTypeId.GROUP_TYPE_1, 2, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 2, TestGroupTypeId.GROUP_TYPE_2, 3, 1), 1); - - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 3, TestGroupTypeId.GROUP_TYPE_1, 2, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 3, TestGroupTypeId.GROUP_TYPE_2, 3, 1), 1); - - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 4, TestGroupTypeId.GROUP_TYPE_1, 2, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 4, TestGroupTypeId.GROUP_TYPE_2, 3, 1), 1); - - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 5, TestGroupTypeId.GROUP_TYPE_1, 5, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 5, TestGroupTypeId.GROUP_TYPE_2, 3, 1), 1); - - Map actualReportItems = testConsumers(testPlugin, ReportPeriod.DAILY, 4023600052052959521L); - assertEquals(expectedReportItems, actualReportItems); - } - - @Test - @UnitTestMethod(name = "init", args = {}) - public void testEndOfSimReport() { - - /* - * We will add one agent to move people in and out of groups and create - * and remove groups. Report items from the report will be collected in - * an output consumer and compared to the expected output. - */ - - // add the action plugin - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // have the agent add a new group of type 1 with three people - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); - assertEquals(3, groupId.getValue()); - groupsDataManager.addPersonToGroup(new PersonId(4),groupId); - groupsDataManager.addPersonToGroup(new PersonId(5),groupId); - groupsDataManager.addPersonToGroup(new PersonId(6),groupId); - })); - - // have the agent add a new group of type 1 with three people - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1.1, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - groupsDataManager.removePersonFromGroup(new PersonId(4),new GroupId(3)); - groupsDataManager.removeGroup(new GroupId(0)); - })); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2.5, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - groupsDataManager.removeGroup(new GroupId(2)); - - })); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(5.7, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - groupsDataManager.addPersonToGroup(new PersonId(7),new GroupId(3)); - groupsDataManager.addPersonToGroup(new PersonId(8),new GroupId(3)); - groupsDataManager.addPersonToGroup(new PersonId(9),new GroupId(3)); - })); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(5.8, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); - assertEquals(4, groupId.getValue()); - groupsDataManager.addPersonToGroup(new PersonId(3),new GroupId(4)); - groupsDataManager.addPersonToGroup(new PersonId(4),new GroupId(4)); - groupsDataManager.addPersonToGroup(new PersonId(5),new GroupId(4)); - groupsDataManager.addPersonToGroup(new PersonId(6),new GroupId(4)); - groupsDataManager.addPersonToGroup(new PersonId(7),new GroupId(4)); - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - - // place the initial data into the expected output consumer - Map expectedReportItems = new LinkedHashMap<>(); - expectedReportItems.put(getReportItem(ReportPeriod.END_OF_SIMULATION, TestGroupTypeId.GROUP_TYPE_1, 5, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.END_OF_SIMULATION, TestGroupTypeId.GROUP_TYPE_2, 3, 1), 1); - - Map actualReportItems = testConsumers(testPlugin, ReportPeriod.END_OF_SIMULATION, 2753155357216960554L); - - assertEquals(expectedReportItems, actualReportItems); - } - - private Map testConsumers(Plugin testPlugin, ReportPeriod reportPeriod, long seed) { - List people = new ArrayList<>(); - for (int i = 0; i < 10; i++) { - people.add(new PersonId(i)); - } - Random random = new Random(seed); - - Experiment.Builder builder = Experiment.builder(); - - // add the group plugin - GroupsPluginData.Builder groupBuilder = GroupsPluginData.builder(); - // add group types - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - groupBuilder.addGroupTypeId(testGroupTypeId); - } - // define group properties - for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { - groupBuilder.defineGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId, testGroupPropertyId.getPropertyDefinition()); - } - - groupBuilder.addGroup(new GroupId(0), TestGroupTypeId.GROUP_TYPE_1); - groupBuilder.addGroup(new GroupId(1), TestGroupTypeId.GROUP_TYPE_2); - groupBuilder.addGroup(new GroupId(2), TestGroupTypeId.GROUP_TYPE_3); - - // add a few of the people to the groups - - groupBuilder.addPersonToGroup(new GroupId(0), new PersonId(0)); - groupBuilder.addPersonToGroup(new GroupId(0), new PersonId(1)); - groupBuilder.addPersonToGroup(new GroupId(0), new PersonId(2)); - - groupBuilder.addPersonToGroup(new GroupId(1), new PersonId(1)); - groupBuilder.addPersonToGroup(new GroupId(1), new PersonId(2)); - groupBuilder.addPersonToGroup(new GroupId(1), new PersonId(3)); - GroupsPluginData groupsPluginData = groupBuilder.build(); - Plugin groupPlugin = GroupsPlugin.getGroupPlugin(groupsPluginData); - builder.addPlugin(groupPlugin); - - // add the people plugin - PeoplePluginData.Builder peopleBuilder = PeoplePluginData.builder(); - for (PersonId personId : people) { - peopleBuilder.addPersonId(personId); - } - PeoplePluginData peoplePluginData = peopleBuilder.build(); - Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(peoplePluginData); - - builder.addPlugin(peoplePlugin); - - // add the report plugin - ReportsPluginData reportsPluginData = ReportsPluginData.builder().addReport(() -> new GroupPopulationReport(REPORT_ID, reportPeriod)::init).build(); - Plugin reportPlugin = ReportsPlugin.getReportPlugin(reportsPluginData); - builder.addPlugin(reportPlugin); - - // add the stochastics plugin - StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder().setSeed(random.nextLong()).build(); - Plugin stochasticsPlugin = StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData); - builder.addPlugin(stochasticsPlugin); - - builder.addPlugin(testPlugin); - - // add the output consumer for the actual report items - ExperimentPlanCompletionObserver experimentPlanCompletionObserver = new ExperimentPlanCompletionObserver(); - TestReportItemOutputConsumer testReportItemOutputConsumer = new TestReportItemOutputConsumer(); - - builder.addOutputHandler(testReportItemOutputConsumer::init); - builder.addOutputHandler(experimentPlanCompletionObserver::init); - builder.reportProgressToConsole(false); - - - // build and execute the engine - builder.build().execute(); - - // show that all actions were executed - assertTrue(experimentPlanCompletionObserver.getActionCompletionReport(0).get().isComplete()); - - return testReportItemOutputConsumer.getReportItems().get(0); - } - - private static ReportItem getReportItem(ReportPeriod reportPeriod, Object... values) { - ReportItem.Builder builder = ReportItem.builder(); - builder.setReportId(REPORT_ID); - - switch (reportPeriod) { - case DAILY: - builder.setReportHeader(REPORT_DAILY_HEADER); - break; - case END_OF_SIMULATION: - builder.setReportHeader(REPORT_EOS_HEADER); - break; - case HOURLY: - builder.setReportHeader(REPORT_HOURLY_HEADER); - break; - default: - throw new RuntimeException("unhandled case " + reportPeriod); - - } - - for (Object value : values) { - builder.addValue(value); - } - return builder.build(); - } - - private static final ReportId REPORT_ID = new SimpleReportId("group population property report"); - - private static final ReportHeader REPORT_DAILY_HEADER = ReportHeader.builder().add("day").add("group_type").add("person_count").add("group_count").build(); - private static final ReportHeader REPORT_HOURLY_HEADER = ReportHeader.builder().add("day").add("hour").add("group_type").add("person_count").add("group_count").build(); - private static final ReportHeader REPORT_EOS_HEADER = ReportHeader.builder().add("group_type").add("person_count").add("group_count").build(); -} diff --git a/gcm3/src/test/java/plugins/groups/actors/AT_GroupPropertyReport.java b/gcm3/src/test/java/plugins/groups/actors/AT_GroupPropertyReport.java deleted file mode 100644 index efd8ae52e..000000000 --- a/gcm3/src/test/java/plugins/groups/actors/AT_GroupPropertyReport.java +++ /dev/null @@ -1,419 +0,0 @@ -package plugins.groups.actors; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashMap; -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import nucleus.Experiment; -import nucleus.Plugin; -import nucleus.testsupport.testplugin.ExperimentPlanCompletionObserver; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import plugins.groups.GroupsPlugin; -import plugins.groups.GroupsPluginData; -import plugins.groups.datamanagers.GroupsDataManager; -import plugins.groups.support.GroupId; -import plugins.groups.support.GroupPropertyId; -import plugins.groups.support.GroupTypeId; -import plugins.groups.testsupport.TestGroupPropertyId; -import plugins.groups.testsupport.TestGroupTypeId; -import plugins.people.PeoplePlugin; -import plugins.people.PeoplePluginData; -import plugins.reports.ReportsPlugin; -import plugins.reports.ReportsPluginData; -import plugins.reports.support.ReportHeader; -import plugins.reports.support.ReportId; -import plugins.reports.support.ReportItem; -import plugins.reports.support.ReportPeriod; -import plugins.reports.support.SimpleReportId; -import plugins.reports.testsupport.TestReportItemOutputConsumer; -import plugins.stochastics.StochasticsPlugin; -import plugins.stochastics.StochasticsPluginData; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = GroupPropertyReport.class) -public class AT_GroupPropertyReport { - - // 4869313312961558350L - - /* - * Returns the conversion into double valued days - * - * preconditions: all entries are non-negative and in their natural ranges - */ - private double getTime(int days, int hours, int minutes) { - return days + (double) hours / 24 + (double) minutes / 1440; - } - - @Test - @UnitTestMethod(target = GroupPropertyReport.Builder.class, name = "addAllProperties", args = { GroupTypeId.class }) - public void testAddAllProperties() { - // test covered by the consumers-based tests in this class - - // precondition tests: - assertThrows(RuntimeException.class, () -> GroupPropertyReport.builder().addAllProperties(null)); - } - - @Test - @UnitTestMethod(target = GroupPropertyReport.Builder.class, name = "addProperty", args = { GroupTypeId.class, GroupPropertyId.class }) - public void testAddProperty() { - // test covered by the consumers-based tests in this class - - // precondition tests: - assertThrows(RuntimeException.class, () -> GroupPropertyReport.builder().addProperty(null, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK)); - assertThrows(RuntimeException.class, () -> GroupPropertyReport.builder().addProperty(TestGroupTypeId.GROUP_TYPE_1, null)); - } - - @Test - @UnitTestMethod(target = GroupPropertyReport.Builder.class, name = "build", args = {}) - public void testBuild() { - // test covered by the consumers-based tests in this class - } - - @Test - @UnitTestMethod(target = GroupPropertyReport.Builder.class, name = "setReportPeriod", args = { ReportPeriod.class, GroupPropertyId.class }) - public void testSetReportPeriod() { - // test covered by the consumers-based tests in this class - // precondition tests: - assertThrows(RuntimeException.class, () -> GroupPropertyReport.builder().setReportPeriod(null)); - assertThrows(RuntimeException.class, () -> GroupPropertyReport.builder().setReportPeriod(ReportPeriod.END_OF_SIMULATION)); - } - - @Test - @UnitTestMethod(target = GroupPropertyReport.Builder.class, name = "setReportId", args = { ReportId.class }) - public void testSetReportId() { - // test covered by the consumers-based tests in this class - - // precondition tests: - assertThrows(RuntimeException.class, () -> GroupPropertyReport.builder().setReportId(null)); - } - - @Test - @UnitTestMethod(name = "init", args = {}) - public void testHourlySelectProperties() { - - /* - * We will add one agent to move assign property values to groups and - * create and remove groups. Report items from the report will be - * collected in an output consumer and compared to the expected output. - */ - - // add the action plugin - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // have the agent add a new group of type 1 with three people - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(getTime(0, 0, 0), (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - - groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); - groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_2); - groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); - groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_2); - groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); - groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_2); - - })); - - // - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(getTime(0, 1, 10), (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - groupsDataManager.setGroupPropertyValue(new GroupId(0), TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, true); - groupsDataManager.setGroupPropertyValue(new GroupId(3), TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 45); - })); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(getTime(0, 2, 3), (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - groupsDataManager.setGroupPropertyValue(new GroupId(2), TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, false); - groupsDataManager.setGroupPropertyValue(new GroupId(4), TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, true); - })); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(getTime(0, 5, 0), (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - groupsDataManager.setGroupPropertyValue(new GroupId(1), TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 123); - groupsDataManager.setGroupPropertyValue(new GroupId(3), TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 123); - groupsDataManager.setGroupPropertyValue(new GroupId(5), TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 123); - })); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(getTime(0, 5, 16), (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - groupsDataManager.setGroupPropertyValue(new GroupId(0), TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, false); - groupsDataManager.setGroupPropertyValue(new GroupId(5), TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 77); - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - // create a container to hold expected results - Map expectedReportItems = new LinkedHashMap<>(); - - // build the expected output - - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 0, 0, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, false, 3), 1); - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 0, 0, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 0, 3), 1); - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 0, 1, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, false, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 0, 1, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, true, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 0, 1, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 0, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 0, 1, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 45, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 0, 2, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, false, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 0, 2, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, true, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 0, 2, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 0, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 0, 2, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 45, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 0, 3, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, false, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 0, 3, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, true, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 0, 3, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 0, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 0, 3, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 45, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 0, 4, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, false, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 0, 4, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, true, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 0, 4, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 0, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 0, 4, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 45, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 0, 5, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, false, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 0, 5, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, true, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 0, 5, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 123, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.HOURLY, 0, 5, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 77, 1), 1); - - // build the report - GroupPropertyReport.Builder builder = GroupPropertyReport.builder(); - builder.setReportId(REPORT_ID); - builder.setReportPeriod(ReportPeriod.HOURLY); - builder.addProperty(TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK); - builder.addProperty(TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK); - GroupPropertyReport groupPropertyReport = builder.build(); - - Map actualReportItems = testConsumers(testPlugin, groupPropertyReport, 6092832510476200219L); - - - assertEquals(expectedReportItems, actualReportItems); - } - - @Test - @UnitTestMethod(name = "init", args = {}) - public void testDailyAllProperties() { - - /* - * We will add one agent to move assign property values to groups and - * create and remove groups. Report items from the report will be - * collected in an output consumer and compared to the expected output. - */ - - // add the action plugin - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // have the agent add a new group of type 1 with three people - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - - GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); - assertEquals(0, groupId.getValue()); - - groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_2); - assertEquals(1, groupId.getValue()); - - groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_3); - assertEquals(2, groupId.getValue()); - - groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); - assertEquals(3, groupId.getValue()); - - groupsDataManager.setGroupPropertyValue(new GroupId(3), TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, true); - groupsDataManager.setGroupPropertyValue(new GroupId(3), TestGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, 45); - groupsDataManager.setGroupPropertyValue(new GroupId(3), TestGroupPropertyId.GROUP_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK, 16.5); - - })); - - // have the agent add a new group of type 1 with three people - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1.1, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - groupsDataManager.removeGroup(new GroupId(0)); - groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_2); - })); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2.5, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - groupsDataManager.setGroupPropertyValue(new GroupId(1), TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 17); - groupsDataManager.setGroupPropertyValue(new GroupId(4), TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK, 800.0); - })); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(5.7, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - groupsDataManager.setGroupPropertyValue(new GroupId(1), TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK, false); - groupsDataManager.setGroupPropertyValue(new GroupId(4), TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 65); - })); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(5.8, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - groupsDataManager.setGroupPropertyValue(new GroupId(1), TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK, true); - groupsDataManager.setGroupPropertyValue(new GroupId(4), TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 127); - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - // create a container to hold expected results - Map expectedReportItems = new LinkedHashMap<>(); - - // build the expected output - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 0, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, true, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 0, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, 45, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 0, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK, 16.5, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 0, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK, false, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 0, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 0, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 0, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK, 0.0, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 0, TestGroupTypeId.GROUP_TYPE_3, TestGroupPropertyId.GROUP_PROPERTY_3_1_BOOLEAN_IMMUTABLE_NO_TRACK, false, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 0, TestGroupTypeId.GROUP_TYPE_3, TestGroupPropertyId.GROUP_PROPERTY_3_2_INTEGER_IMMUTABLE_NO_TRACK, 0, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 0, TestGroupTypeId.GROUP_TYPE_3, TestGroupPropertyId.GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK, 0.0, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 1, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, true, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 1, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, 45, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 1, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK, 16.5, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 1, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK, false, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 1, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 0, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 1, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK, 0.0, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 1, TestGroupTypeId.GROUP_TYPE_3, TestGroupPropertyId.GROUP_PROPERTY_3_1_BOOLEAN_IMMUTABLE_NO_TRACK, false, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 1, TestGroupTypeId.GROUP_TYPE_3, TestGroupPropertyId.GROUP_PROPERTY_3_2_INTEGER_IMMUTABLE_NO_TRACK, 0, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 1, TestGroupTypeId.GROUP_TYPE_3, TestGroupPropertyId.GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK, 0.0, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 2, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, true, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 2, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, 45, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 2, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK, 16.5, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 2, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK, false, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 2, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 0, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 2, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 17, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 2, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK, 0.0, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 2, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK, 800.0, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 2, TestGroupTypeId.GROUP_TYPE_3, TestGroupPropertyId.GROUP_PROPERTY_3_1_BOOLEAN_IMMUTABLE_NO_TRACK, false, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 2, TestGroupTypeId.GROUP_TYPE_3, TestGroupPropertyId.GROUP_PROPERTY_3_2_INTEGER_IMMUTABLE_NO_TRACK, 0, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 2, TestGroupTypeId.GROUP_TYPE_3, TestGroupPropertyId.GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK, 0.0, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 3, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, true, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 3, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, 45, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 3, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK, 16.5, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 3, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK, false, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 3, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 0, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 3, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 17, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 3, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK, 0.0, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 3, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK, 800.0, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 3, TestGroupTypeId.GROUP_TYPE_3, TestGroupPropertyId.GROUP_PROPERTY_3_1_BOOLEAN_IMMUTABLE_NO_TRACK, false, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 3, TestGroupTypeId.GROUP_TYPE_3, TestGroupPropertyId.GROUP_PROPERTY_3_2_INTEGER_IMMUTABLE_NO_TRACK, 0, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 3, TestGroupTypeId.GROUP_TYPE_3, TestGroupPropertyId.GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK, 0.0, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 4, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, true, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 4, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, 45, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 4, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK, 16.5, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 4, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK, false, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 4, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 0, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 4, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 17, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 4, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK, 0.0, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 4, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK, 800.0, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 4, TestGroupTypeId.GROUP_TYPE_3, TestGroupPropertyId.GROUP_PROPERTY_3_1_BOOLEAN_IMMUTABLE_NO_TRACK, false, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 4, TestGroupTypeId.GROUP_TYPE_3, TestGroupPropertyId.GROUP_PROPERTY_3_2_INTEGER_IMMUTABLE_NO_TRACK, 0, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 4, TestGroupTypeId.GROUP_TYPE_3, TestGroupPropertyId.GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK, 0.0, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 5, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, true, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 5, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, 45, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 5, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK, 16.5, 2), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 5, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK, false, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 5, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK, true, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 5, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 17, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 5, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK, 127, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 5, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK, 0.0, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 5, TestGroupTypeId.GROUP_TYPE_2, TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK, 800.0, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 5, TestGroupTypeId.GROUP_TYPE_3, TestGroupPropertyId.GROUP_PROPERTY_3_1_BOOLEAN_IMMUTABLE_NO_TRACK, false, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 5, TestGroupTypeId.GROUP_TYPE_3, TestGroupPropertyId.GROUP_PROPERTY_3_2_INTEGER_IMMUTABLE_NO_TRACK, 0, 1), 1); - expectedReportItems.put(getReportItem(ReportPeriod.DAILY, 5, TestGroupTypeId.GROUP_TYPE_3, TestGroupPropertyId.GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK, 0.0, 1), 1); - - // build the report with all properties selected - GroupPropertyReport.Builder builder = GroupPropertyReport.builder(); - builder.setReportId(REPORT_ID); - builder.setReportPeriod(ReportPeriod.DAILY); - - for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { - builder.addProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId); - } - GroupPropertyReport groupPropertyReport = builder.build(); - - Map actualReportItems = testConsumers(testPlugin, groupPropertyReport, 6092832510476200219L); - - assertEquals(expectedReportItems, actualReportItems); - } - - private Map testConsumers(Plugin testPlugin, GroupPropertyReport groupPropertyReport, long seed) { - - Experiment.Builder builder = Experiment.builder(); - - // add the group plugin - GroupsPluginData.Builder groupBuilder = GroupsPluginData.builder(); - // add group types - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - groupBuilder.addGroupTypeId(testGroupTypeId); - } - // define group properties - for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { - groupBuilder.defineGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId, testGroupPropertyId.getPropertyDefinition()); - } - - GroupsPluginData groupsPluginData = groupBuilder.build(); - Plugin groupPlugin = GroupsPlugin.getGroupPlugin(groupsPluginData); - builder.addPlugin(groupPlugin); - - // add the people plugin - builder.addPlugin(PeoplePlugin.getPeoplePlugin(PeoplePluginData.builder().build())); - - // add the report plugin - ReportsPluginData reportsPluginData = ReportsPluginData.builder().addReport(() -> groupPropertyReport::init).build(); - builder.addPlugin(ReportsPlugin.getReportPlugin(reportsPluginData)); - - // add the stochastics plugin - StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder().setSeed(seed).build(); - builder.addPlugin(StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData)); - - builder.addPlugin(testPlugin); - - // add the output consumer for the actual report items - TestReportItemOutputConsumer testReportItemOutputConsumer = new TestReportItemOutputConsumer(); - builder.addOutputHandler(testReportItemOutputConsumer::init); - - ExperimentPlanCompletionObserver experimentPlanCompletionObserver = new ExperimentPlanCompletionObserver(); - builder.addOutputHandler(experimentPlanCompletionObserver::init); - builder.reportProgressToConsole(false); - - // build and execute the engine - builder.build().execute(); - - // show that all actions were executed - assertTrue(experimentPlanCompletionObserver.getActionCompletionReport(0).get().isComplete()); - - return testReportItemOutputConsumer.getReportItems().get(0); - } - - private static ReportItem getReportItem(ReportPeriod reportPeriod, Object... values) { - ReportItem.Builder builder = ReportItem.builder(); - builder.setReportId(REPORT_ID); - - switch (reportPeriod) { - case DAILY: - builder.setReportHeader(REPORT_DAILY_HEADER); - break; - - case HOURLY: - builder.setReportHeader(REPORT_HOURLY_HEADER); - break; - case END_OF_SIMULATION:// fall through - default: - throw new RuntimeException("unhandled case " + reportPeriod); - - } - - for (Object value : values) { - builder.addValue(value); - } - return builder.build(); - } - - private static final ReportId REPORT_ID = new SimpleReportId("group property report"); - - private static final ReportHeader REPORT_DAILY_HEADER = ReportHeader.builder().add("day").add("group_type").add("property").add("value").add("group_count").build(); - private static final ReportHeader REPORT_HOURLY_HEADER = ReportHeader.builder().add("day").add("hour").add("group_type").add("property").add("value").add("group_count").build(); - -} diff --git a/gcm3/src/test/java/plugins/groups/datamanagers/AT_GroupsDataManager.java b/gcm3/src/test/java/plugins/groups/datamanagers/AT_GroupsDataManager.java deleted file mode 100644 index 08d2accf0..000000000 --- a/gcm3/src/test/java/plugins/groups/datamanagers/AT_GroupsDataManager.java +++ /dev/null @@ -1,2796 +0,0 @@ -package plugins.groups.datamanagers; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.EnumSet; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Random; -import java.util.Set; - -import org.apache.commons.math3.random.RandomGenerator; -import org.apache.commons.math3.util.FastMath; -import org.junit.jupiter.api.Test; - -import nucleus.ActorContext; -import nucleus.EventLabeler; -import nucleus.NucleusError; -import nucleus.Plugin; -import nucleus.Simulation; -import nucleus.Simulation.Builder; -import nucleus.testsupport.testplugin.ScenarioPlanCompletionObserver; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestError; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import plugins.groups.GroupsPlugin; -import plugins.groups.GroupsPluginData; -import plugins.groups.events.GroupAdditionEvent; -import plugins.groups.events.GroupImminentRemovalEvent; -import plugins.groups.events.GroupMembershipAdditionEvent; -import plugins.groups.events.GroupMembershipRemovalEvent; -import plugins.groups.events.GroupPropertyUpdateEvent; -import plugins.groups.support.BulkGroupMembershipData; -import plugins.groups.support.GroupConstructionInfo; -import plugins.groups.support.GroupError; -import plugins.groups.support.GroupId; -import plugins.groups.support.GroupPropertyId; -import plugins.groups.support.GroupSampler; -import plugins.groups.support.GroupTypeId; -import plugins.groups.support.GroupWeightingFunction; -import plugins.groups.testsupport.GroupsActionSupport; -import plugins.groups.testsupport.TestGroupPropertyId; -import plugins.groups.testsupport.TestGroupTypeId; -import plugins.people.PeoplePlugin; -import plugins.people.PeoplePluginData; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.BulkPersonConstructionData; -import plugins.people.support.PersonConstructionData; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import plugins.stochastics.StochasticsDataManager; -import plugins.stochastics.StochasticsPlugin; -import plugins.stochastics.StochasticsPluginData; -import plugins.util.properties.PropertyDefinition; -import plugins.util.properties.PropertyError; -import plugins.util.properties.TimeTrackingPolicy; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; -import util.random.RandomGeneratorProvider; -import util.wrappers.MultiKey; -import util.wrappers.MutableDouble; -import util.wrappers.MutableInteger; -import util.wrappers.MutableObject; - -@UnitTest(target = GroupsDataManager.class) -public class AT_GroupsDataManager { - - @Test - @UnitTestMethod(name = "removeGroup", args = { GroupId.class }) - public void testRemoveGroup() { - - Set removedGroups = new LinkedHashSet<>(); - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - // add a group - GroupId groupId = groupsDataManager.addGroup(testGroupTypeId); - - // show that the returned group id is not null - assertNotNull(groupId); - - // show that the manager indicates the group id exists - assertTrue(groupsDataManager.groupExists(groupId)); - - // remove the group - groupsDataManager.removeGroup(groupId); - - removedGroups.add(groupId); - - } - })); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - // show that the group is no long present - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - for (GroupId groupId : removedGroups) { - assertFalse(groupsDataManager.groupExists(groupId)); - } - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - GroupsActionSupport.testConsumers(30, 3, 5, 8204685090168544876L, testPlugin); - - // precondition test: if the group id is null - GroupsActionSupport.testConsumer(30, 3, 5, 1164752712088660908L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.removeGroup(null)); - assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); - }); - - // precondition test: if the group id is unknown - GroupsActionSupport.testConsumer(30, 3, 5, 6321229743136171684L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.removeGroup(new GroupId(100000))); - assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "removePersonFromGroup", args = { GroupId.class, PersonId.class }) - public void testRemovePersonFromGroup() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // create containers for observations - Set actualObservations = new LinkedHashSet<>(); - Set expectedObservations = new LinkedHashSet<>(); - - // add an agent to observe the group membership additions - - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { - c.subscribe(GroupMembershipAdditionEvent.class, (c2, e) -> { - actualObservations.add(new MultiKey(e.getGroupId(), e.getPersonId())); - }); - - })); - - // add an agent to add members to groups - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - List people = peopleDataManager.getPeople(); - Collections.shuffle(people, new Random(randomGenerator.nextLong())); - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - for (GroupId groupId : groupsDataManager.getGroupIds()) { - Set peopleForGroup = new LinkedHashSet<>(groupsDataManager.getPeopleForGroup(groupId)); - int count = 0; - for (PersonId personId : people) { - if (!peopleForGroup.contains(personId)) { - groupsDataManager.addPersonToGroup(personId, groupId); - expectedObservations.add(new MultiKey(groupId, personId)); - count++; - } - if (count == 3) { - break; - } - } - } - - })); - - // have the observer verify that the observations were correct - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(2, (c) -> { - assertEquals(27, expectedObservations.size()); - assertEquals(expectedObservations, actualObservations); - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - GroupsActionSupport.testConsumers(30, 3, 10, 2733223420384068616L, testPlugin); - - /* precondition test: if the person id is null */ - GroupsActionSupport.testConsumer(30, 3, 10, 667206327628089405L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.removePersonFromGroup(null, groupId)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - }); - - /* precondition test: if the person id is unknown */ - GroupsActionSupport.testConsumer(30, 3, 10, 283038490401536931L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.removePersonFromGroup(new PersonId(10000), groupId)); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - }); - - /* precondition test: if the group id is null */ - GroupsActionSupport.testConsumer(30, 3, 10, 6913106996750459497L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.removePersonFromGroup(personId, null)); - assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); - }); - - /* precondition test: if the group id is unknown */ - GroupsActionSupport.testConsumer(30, 3, 10, 4632472396816795419L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.removePersonFromGroup(personId, new GroupId(10000))); - assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); - }); - - /* precondition test: if the person is not a member of the group */ - GroupsActionSupport.testConsumer(30, 3, 10, 8295961559327801013L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); - PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.removePersonFromGroup(personId, groupId)); - assertEquals(GroupError.NON_GROUP_MEMBERSHIP, contractException.getErrorType()); - }); - - } - - private static enum ExcludedPersonType { - NULL, MEMBER, NON_MEMBER; - } - - @Test - public void testConstructor() { - ContractException contractException = assertThrows(ContractException.class, () -> new GroupsDataManager(null)); - assertEquals(GroupError.NULL_GROUP_INITIALIZATION_DATA, contractException.getErrorType()); - } - - @Test - @UnitTestMethod(name = "addGroup", args = { GroupConstructionInfo.class }) - public void testAddGroup_GroupConstructionInfo() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // create containers for observations - Set expectedGroupObservations = new LinkedHashSet<>(); - Set actualGroupObservations = new LinkedHashSet<>(); - - // have the observer subscribe to group creation - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { - c.subscribe(GroupAdditionEvent.class, (c2, e) -> { - actualGroupObservations.add(e.getGroupId()); - }); - - })); - - // have the agent create add a few groups and collect expected - // observations - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); - GroupConstructionInfo.Builder builder = GroupConstructionInfo.builder(); - - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - builder.setGroupTypeId(testGroupTypeId); - Map expectedPropertyValues = new LinkedHashMap<>(); - for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.getTestGroupPropertyIds(testGroupTypeId)) { - Object value = testGroupPropertyId.getRandomPropertyValue(randomGenerator); - builder.setGroupPropertyValue(testGroupPropertyId, value); - expectedPropertyValues.put(testGroupPropertyId, value); - } - GroupConstructionInfo groupConstructionInfo = builder.build(); - GroupId groupId = groupsDataManager.addGroup(groupConstructionInfo); - - expectedGroupObservations.add(groupId); - - // show that the group was created, has the correct type and has - // the correct property values - assertTrue(groupsDataManager.groupExists(groupId)); - assertEquals(testGroupTypeId, groupsDataManager.getGroupType(groupId)); - for (TestGroupPropertyId testGroupPropertyId : expectedPropertyValues.keySet()) { - Object expectedValue = expectedPropertyValues.get(testGroupPropertyId); - Object actaulValue = groupsDataManager.getGroupPropertyValue(groupId, testGroupPropertyId); - assertEquals(expectedValue, actaulValue); - } - - } - })); - - // show that the group creations were observed - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(3, (c) -> { - assertTrue(expectedGroupObservations.size() > 0); - assertEquals(expectedGroupObservations, actualGroupObservations); - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - GroupsActionSupport.testConsumers(40, 5.0, 20.0, 5865498314869329641L, testPlugin); - - // precondition test: if the group construction info is null - GroupsActionSupport.testConsumer(40, 5.0, 20.0, 5229546252018518751L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - GroupConstructionInfo nullGroupConstructionInfo = null; - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.addGroup(nullGroupConstructionInfo)); - assertEquals(GroupError.NULL_GROUP_CONSTRUCTION_INFO, contractException.getErrorType()); - }); - - /* - * precondition test: if the group type id contained in the group - * construction info is unknown - */ - GroupsActionSupport.testConsumer(40, 5.0, 20.0, 7404840971962130072L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - - ContractException contractException = assertThrows(ContractException.class, - () -> groupsDataManager.addGroup(GroupConstructionInfo.builder().setGroupTypeId(TestGroupTypeId.getUnknownGroupTypeId()).build())); - assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); - }); - - /* - * precondition test:if a group property id contained in the group - * construction info is unknown - */ - GroupsActionSupport.testConsumer(40, 5.0, 20.0, 8782123343145389682L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - - ContractException contractException = assertThrows(ContractException.class, () -> { - GroupConstructionInfo groupConstructionInfo = GroupConstructionInfo .builder()// - .setGroupTypeId(TestGroupTypeId.GROUP_TYPE_1)// - .setGroupPropertyValue(TestGroupPropertyId.getUnknownGroupPropertyId(), 1)// - .build();// - groupsDataManager.addGroup(groupConstructionInfo); - }); - assertEquals(GroupError.UNKNOWN_GROUP_PROPERTY_ID, contractException.getErrorType()); - }); - - /* - * precondition test: if a group property value contained in the group - * construction info is incompatible with the corresponding property - * definition - */ - GroupsActionSupport.testConsumer(40, 5.0, 20.0, 8782123343145389682L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> { - GroupConstructionInfo groupConstructionInfo = GroupConstructionInfo .builder()// - .setGroupTypeId(TestGroupTypeId.GROUP_TYPE_1)// - .setGroupPropertyValue(TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, 1)// - .build();// - groupsDataManager.addGroup(groupConstructionInfo); - }); - assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); - - }); - - } - - @Test - @UnitTestMethod(name = "addGroup", args = { GroupTypeId.class }) - public void testAddGroup_GroupTypeId() { - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // create containers for observations - Set expectedObservations = new LinkedHashSet<>(); - Set actualObservations = new LinkedHashSet<>(); - - // have the observer subscribe to group creation - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { - c.subscribe(GroupAdditionEvent.class, (c2, e) -> { - actualObservations.add(e.getGroupId()); - }); - - })); - - // have the agent create add a few groups and collect expected - // observations - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - - GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); - expectedObservations.add(groupId); - - groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_2); - expectedObservations.add(groupId); - - groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_3); - expectedObservations.add(groupId); - - })); - - // show that the group creations were observed - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(3, (c) -> { - assertTrue(expectedObservations.size() > 0); - assertEquals(expectedObservations, actualObservations); - })); - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - GroupsActionSupport.testConsumers(30, 4.0, 10.0, 8137195527612056024L, testPlugin); - - // precondition tests - GroupsActionSupport.testConsumer(30, 4.0, 10.0, 5229546252018518751L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - GroupTypeId groupTypeId = null; - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.addGroup(groupTypeId)); - assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); - }); - - // precondition tests - GroupsActionSupport.testConsumer(30, 4.0, 10.0, 5229546252018518751L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.addGroup(TestGroupTypeId.getUnknownGroupTypeId())); - assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "addPersonToGroup", args = { GroupId.class, PersonId.class }) - public void testAddPersonToGroup() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // create containers for observations - Set actualObservations = new LinkedHashSet<>(); - Set expectedObservations = new LinkedHashSet<>(); - - // add an agent to observe the group membership additions - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { - c.subscribe(GroupMembershipAdditionEvent.class, (c2, e) -> { - actualObservations.add(new MultiKey(e.getGroupId(), e.getPersonId())); - }); - - })); - - // add an agent to add members to groups - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - List people = peopleDataManager.getPeople(); - Collections.shuffle(people, new Random(randomGenerator.nextLong())); - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - for (GroupId groupId : groupsDataManager.getGroupIds()) { - Set peopleForGroup = new LinkedHashSet<>(groupsDataManager.getPeopleForGroup(groupId)); - int count = 0; - - for (PersonId personId : people) { - - if (!peopleForGroup.contains(personId)) { - groupsDataManager.addPersonToGroup(personId, groupId); - assertTrue(groupsDataManager.isPersonInGroup(personId, groupId)); - expectedObservations.add(new MultiKey(groupId, personId)); - count++; - } - if (count == 3) { - break; - } - } - } - })); - - // have the observer verify that the observations were correct - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(2, (c) -> { - assertEquals(27, expectedObservations.size()); - assertEquals(expectedObservations, actualObservations); - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - GroupsActionSupport.testConsumers(30, 3, 10, 2733223420384068616L, testPlugin); - - // precondition tests: if the person id is null - GroupsActionSupport.testConsumer(30, 3, 10, 2886293572900391101L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.addPersonToGroup(null, groupId)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - }); - - // precondition tests: if the person id is unknown - GroupsActionSupport.testConsumer(30, 3, 10, 5604775963632692909L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.addPersonToGroup(new PersonId(10000), groupId)); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - }); - - // precondition tests: if the group id is null - GroupsActionSupport.testConsumer(30, 3, 10, 3853147120254074375L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.addPersonToGroup(personId, null)); - assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); - }); - - // precondition tests: if the group id is unknown - GroupsActionSupport.testConsumer(30, 3, 10, 7259750239550962667L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.addPersonToGroup(personId, new GroupId(10000))); - assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); - }); - - // precondition tests: if the person is already a member of the group - GroupsActionSupport.testConsumer(30, 3, 10, 3285943689624298882L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); - PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); - groupsDataManager.addPersonToGroup(personId, groupId); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.addPersonToGroup(personId, groupId)); - assertEquals(GroupError.DUPLICATE_GROUP_MEMBERSHIP, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "removePerson", args = { PersonId.class }) - public void testRemovePerson() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // create containers for observations - Set actualObservations = new LinkedHashSet<>(); - Set expectedObservations = new LinkedHashSet<>(); - - // add an agent to observe the group removals - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { - c.subscribe(GroupImminentRemovalEvent.class, (c2, e) -> { - actualObservations.add(e.getGroupId()); - }); - - })); - - // add an agent that removes groups - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - List groupIds = groupsDataManager.getGroupIds(); - assertTrue(groupIds.size() > 5); - for (int i = 0; i < 5; i++) { - GroupId groupId = groupIds.get(i); - groupsDataManager.removeGroup(groupId); - expectedObservations.add(groupId); - } - })); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - for (GroupId groupId : expectedObservations) { - assertFalse(groupsDataManager.groupExists(groupId)); - } - })); - - // have the observer verify that the observations were correct - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(2, (c) -> { - assertTrue(expectedObservations.size() > 0); - assertEquals(expectedObservations, actualObservations); - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - GroupsActionSupport.testConsumers(30, 3, 10, 1454752128175396298L, testPlugin); - - // precondition test: if the group id is null - GroupsActionSupport.testConsumer(30, 3, 10, 7796647463624453709L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.removeGroup(null)); - assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); - }); - - // precondition test: if the group id is unknown - GroupsActionSupport.testConsumer(30, 3, 10, 8368986779885621446L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.removeGroup(new GroupId(10000))); - assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "groupExists", args = { GroupId.class }) - public void testGroupExists() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - List removedGroupIds = new ArrayList<>(); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0.0, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - - // add a group and show it exists - GroupId groupId = groupsDataManager.addGroup(testGroupTypeId); - assertTrue(groupsDataManager.groupExists(groupId)); - // remove the group and record it for later verification - groupsDataManager.removeGroup(groupId); - removedGroupIds.add(groupId); - } - })); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0.0, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - for (GroupId groupId : removedGroupIds) { - // show that the removed groups don't exist - assertFalse(groupsDataManager.groupExists(groupId)); - } - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - GroupsActionSupport.testConsumers(30, 3, 5, 2946647177720026906L, testPlugin); - } - - @Test - @UnitTestMethod(name = "sampleGroup", args = { GroupId.class, GroupSampler.class }) - public void testSampleGroup() { - - GroupsActionSupport.testConsumer(30, 3, 5, 9211292135944399530L, (c) -> { - // establish data views and the lists to groups and people in the - // simulation - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - List groupIds = groupsDataManager.getGroupIds(); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - List people = peopleDataManager.getPeople(); - - /* - * Set up boolean looping over the use of weighting functions in the - * GroupSampler - */ - Set weightingFunctionValues = new LinkedHashSet<>(); - weightingFunctionValues.add(false); - weightingFunctionValues.add(true); - - /* - * Create a weight function that will allow us to exclude about half - * of the people from any group from being selected - */ - GroupWeightingFunction gwf = (c2, p, g) -> { - if (p.getValue() % 2 == 0) { - return 0; - } - return 1.0; - }; - - /* - * Test every group against every excluded person category and use - * of the weighting function - */ - for (GroupId groupId : groupIds) { - for (ExcludedPersonType excludedPersonType : ExcludedPersonType.values()) { - for (Boolean useWeightingFunction : weightingFunctionValues) { - // start building the group sampler - GroupSampler.Builder groupSamplerBuilder = GroupSampler.builder(); - - // Determine the sets of people in and out of the group - List peopleForGroup = groupsDataManager.getPeopleForGroup(groupId); - Set peopleNotInGroupSet = new LinkedHashSet<>(people); - peopleNotInGroupSet.removeAll(peopleForGroup); - List peopleNotInGroupList = new ArrayList<>(peopleNotInGroupSet); - - // Add the weighting function if needed - if (useWeightingFunction) { - groupSamplerBuilder.setGroupWeightingFunction(gwf); - } - - // Add the excluded person based on the category for - // choosing the excluded person - PersonId excludedPersonId = null; - switch (excludedPersonType) { - case MEMBER: - if (!peopleForGroup.isEmpty()) { - excludedPersonId = peopleForGroup.get(randomGenerator.nextInt(peopleForGroup.size())); - } - break; - case NON_MEMBER: - if (!peopleNotInGroupList.isEmpty()) { - excludedPersonId = peopleNotInGroupList.get(randomGenerator.nextInt(peopleNotInGroupList.size())); - } - break; - case NULL: - break; - default: - throw new RuntimeException("unhandled case " + excludedPersonType); - } - groupSamplerBuilder.setExcludedPersonId(excludedPersonId); - - // build the group sampler - GroupSampler groupSampler = groupSamplerBuilder.build(); - - Set eligiblePeople = new LinkedHashSet<>(); - /* - * If we are using the weighting function, then only - * select the odd people as eligible, otherwise select - * everyone in the gropu - */ - if (useWeightingFunction) { - for (PersonId personId : peopleForGroup) { - if (personId.getValue() % 2 == 1) { - eligiblePeople.add(personId); - } - } - } else { - eligiblePeople.addAll(peopleForGroup); - } - - // Remove the excluded person from the eligible people - eligiblePeople.remove(excludedPersonId); - - /* - * If there are no eligible people, then the - * sampleGroup() method should return an empty optional - */ - if (eligiblePeople.isEmpty()) { - Optional optional = groupsDataManager.sampleGroup(groupId, groupSampler); - assertFalse(optional.isPresent()); - } else { - // Draw a reasonable number of people from the group - // and show that they are all eligible people - for (int i = 0; i < eligiblePeople.size(); i++) { - Optional optional = groupsDataManager.sampleGroup(groupId, groupSampler); - assertTrue(optional.isPresent()); - PersonId selectedPersonId = optional.get(); - assertTrue(eligiblePeople.contains(selectedPersonId)); - } - } - - } - } - } - - }); - - /* precondition test: if the group id is null */ - GroupsActionSupport.testConsumer(30, 3, 5, 5080244401642933835L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.sampleGroup(null, GroupSampler.builder().build())); - assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); - }); - - /* precondition test: if the group id is unknown */ - GroupsActionSupport.testConsumer(30, 3, 5, 8782123343145389682L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.sampleGroup(new GroupId(1000000), GroupSampler.builder().build())); - assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); - }); - - /* precondition test: if the group sampler is null */ - GroupsActionSupport.testConsumer(30, 3, 5, 4175298436277522063L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.sampleGroup(new GroupId(0), null)); - assertEquals(GroupError.NULL_GROUP_SAMPLER, contractException.getErrorType()); - }); - - /* precondition test: if the group sampler is null */ - GroupsActionSupport.testConsumer(30, 3, 5, 7404840971962130072L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, - () -> groupsDataManager.sampleGroup(new GroupId(0), GroupSampler.builder().setExcludedPersonId(new PersonId(1000000)).build())); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "setGroupPropertyValue", args = { GroupId.class, GroupPropertyId.class, Object.class }) - public void testSetGroupPropertyValue() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // create data structures for observations - Set expectedObservations = new LinkedHashSet<>(); - Set actualObservations = new LinkedHashSet<>(); - - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { - c.subscribe(GroupPropertyUpdateEvent.class, (c2, e) -> { - actualObservations.add(new MultiKey(e.getGroupId(), e.getGroupPropertyId(), e.getPreviousPropertyValue(), e.getCurrentPropertyValue())); - - }); - })); - - // have an agent change a few group property values - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - List groupIds = groupsDataManager.getGroupIds(); - - // show that there are some groups - assertTrue(groupIds.size() > 0); - - for (GroupId groupId : groupIds) { - TestGroupTypeId testGroupTypeId = groupsDataManager.getGroupType(groupId); - for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.getTestGroupPropertyIds(testGroupTypeId)) { - if (testGroupPropertyId.getPropertyDefinition().propertyValuesAreMutable()) { - Object currentValue = groupsDataManager.getGroupPropertyValue(groupId, testGroupPropertyId); - Object expectedValue = testGroupPropertyId.getRandomPropertyValue(randomGenerator); - groupsDataManager.setGroupPropertyValue(groupId, testGroupPropertyId, expectedValue); - Object actualValue = groupsDataManager.getGroupPropertyValue(groupId, testGroupPropertyId); - assertEquals(expectedValue, actualValue); - expectedObservations.add(new MultiKey(groupId, testGroupPropertyId, currentValue, expectedValue)); - - } - } - } - })); - - // show that the observations of the group property value assignments - // were correct - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(3, (c) -> { - assertEquals(expectedObservations, actualObservations); - })); - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - GroupsActionSupport.testConsumers(100, 3, 5, 4653012806568812031L, testPlugin); - - /* precondition test if the group id is null */ - GroupsActionSupport.testConsumer(100, 3, 5, 3285943689624298882L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - TestGroupPropertyId testGroupPropertyId = TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK; - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.setGroupPropertyValue(null, testGroupPropertyId, true)); - assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); - }); - - /* precondition test if the group id is unknown */ - GroupsActionSupport.testConsumer(100, 3, 5, 3853147120254074375L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - TestGroupPropertyId testGroupPropertyId = TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK; - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.setGroupPropertyValue(new GroupId(100000), testGroupPropertyId, true)); - assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); - }); - - /* precondition test if the group property id is null */ - GroupsActionSupport.testConsumer(100, 3, 5, 5118884606334935158L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.setGroupPropertyValue(groupId, null, true)); - assertEquals(GroupError.NULL_GROUP_PROPERTY_ID, contractException.getErrorType()); - }); - - /* precondition test if the group property id is unknown */ - GroupsActionSupport.testConsumer(100, 3, 5, 6389640203066924425L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.setGroupPropertyValue(groupId, TestGroupPropertyId.getUnknownGroupPropertyId(), true)); - assertEquals(GroupError.UNKNOWN_GROUP_PROPERTY_ID, contractException.getErrorType()); - }); - - /* precondition test if the property value is null */ - GroupsActionSupport.testConsumer(100, 3, 5, 6323361964403648167L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); - TestGroupPropertyId testGroupPropertyId = TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK; - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.setGroupPropertyValue(groupId, testGroupPropertyId, null)); - assertEquals(GroupError.NULL_GROUP_PROPERTY_VALUE, contractException.getErrorType()); - }); - - /* - * precondition test if property value is incompatible with the - * corresponding property definition - */ - GroupsActionSupport.testConsumer(100, 3, 5, 3728888495166492963L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); - TestGroupPropertyId testGroupPropertyId = TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK; - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.setGroupPropertyValue(groupId, testGroupPropertyId, 5)); - assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); - }); - - /* - * precondition test if the corresponding property definition defines - * the property as immutable - */ - GroupsActionSupport.testConsumer(100, 3, 5, 7440937277837294440L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_3); - TestGroupPropertyId testGroupPropertyId = TestGroupPropertyId.GROUP_PROPERTY_3_1_BOOLEAN_IMMUTABLE_NO_TRACK; - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.setGroupPropertyValue(groupId, testGroupPropertyId, true)); - assertEquals(PropertyError.IMMUTABLE_VALUE, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "getGroupCountForGroupType", args = { GroupTypeId.class }) - public void testGetGroupCountForGroupType() { - - GroupsActionSupport.testConsumer(300, 3, 5, 2910747162784803859L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - List groupIds = groupsDataManager.getGroupIds(); - - // show that there are some groups -- we expect about 180 - assertTrue(groupIds.size() > 100); - - // construct containers to hold expectations and actual counts - Map actualCounts = new LinkedHashMap<>(); - Map expectedCounts = new LinkedHashMap<>(); - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - actualCounts.put(testGroupTypeId, new MutableInteger()); - int count = groupsDataManager.getGroupCountForGroupType(testGroupTypeId); - expectedCounts.put(testGroupTypeId, new MutableInteger(count)); - } - - // poll through the groups and increment the corresponding counters - for (GroupId groupId : groupIds) { - actualCounts.get(groupsDataManager.getGroupType(groupId)).increment(); - } - // show that expectation were met - assertEquals(expectedCounts, actualCounts); - - }); - - /* precondition test: if the group type is null */ - GroupsActionSupport.testConsumer(300, 3, 5, 8342387507356594823L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.getGroupCountForGroupType(null)); - assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the group type is unknown */ - GroupsActionSupport.testConsumer(300, 3, 5, 4573510051341354320L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.getGroupCountForGroupType(TestGroupTypeId.getUnknownGroupTypeId())); - assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "getGroupCountForPerson", args = { PersonId.class }) - public void testGetGroupCountForPerson() { - - GroupsActionSupport.testConsumer(300, 3, 5, 6371809280692201768L, (c) -> { - - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - List people = peopleDataManager.getPeople(); - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - - List groupIds = groupsDataManager.getGroupIds(); - - // show that there are some groups -- we expect about 180 - assertTrue(groupIds.size() > 100); - - // construct a container to hold expectations - Map expectedCounts = new LinkedHashMap<>(); - for (PersonId personId : people) { - expectedCounts.put(personId, new MutableInteger()); - } - - // poll through the groups and build the expectations - for (GroupId groupId : groupIds) { - List peopleInGroup = groupsDataManager.getPeopleForGroup(groupId); - for (PersonId personId : peopleInGroup) { - expectedCounts.get(personId).increment(); - } - } - - // show that the counts match the expected counts - for (PersonId personId : people) { - int expectedValue = expectedCounts.get(personId).getValue(); - int actualValue = groupsDataManager.getGroupCountForPerson(personId); - assertEquals(expectedValue, actualValue); - } - - }); - - /* precondition test: if the person id is null */ - GroupsActionSupport.testConsumer(300, 3, 5, 3920152432964044129L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.getGroupCountForPerson(null)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - }); - - /* precondition test: if the person id is unknown */ - GroupsActionSupport.testConsumer(300, 3, 5, 6739633613106510243L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.getGroupCountForPerson(new PersonId(10000))); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "getGroupIds", args = {}) - public void testGetGroupIds() { - - GroupsActionSupport.testConsumer(10, 0, 5, 6455798573295403809L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - - List groupIds = groupsDataManager.getGroupIds(); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - /* - * Show that there are no groups since we selected 0 groups per - * person - */ - assertEquals(0, groupIds.size()); - Set expectedGroupIds = new LinkedHashSet<>(); - for (int i = 0; i < 10; i++) { - GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.getRandomGroupTypeId(randomGenerator)); - expectedGroupIds.add(groupId); - } - - // show that the group ids match the expected group ids - List actualGroupIds = groupsDataManager.getGroupIds(); - assertEquals(expectedGroupIds.size(), actualGroupIds.size()); - assertEquals(expectedGroupIds, new LinkedHashSet<>(actualGroupIds)); - - }); - - } - - @Test - @UnitTestMethod(name = "getGroupPropertyDefinition", args = { GroupTypeId.class, GroupPropertyId.class }) - public void testGetGroupPropertyDefinition() { - - GroupsActionSupport.testConsumer(10, 0, 5, 4462836951642761957L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - - // show that the personGroupDataManger has the expected property - // definitions - for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { - PropertyDefinition expectedPropertyDefinition = testGroupPropertyId.getPropertyDefinition(); - PropertyDefinition actualPropertyDefinition = groupsDataManager.getGroupPropertyDefinition(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId); - assertEquals(expectedPropertyDefinition, actualPropertyDefinition); - } - - // precondition tests - - // if the group type id is null - ContractException contractException = assertThrows(ContractException.class, - () -> groupsDataManager.getGroupPropertyDefinition(null, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK)); - assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); - - // if the group type id is unknown - contractException = assertThrows(ContractException.class, - () -> groupsDataManager.getGroupPropertyDefinition(TestGroupTypeId.getUnknownGroupTypeId(), TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK)); - assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); - - // if the group property id is null - contractException = assertThrows(ContractException.class, () -> groupsDataManager.getGroupPropertyDefinition(TestGroupTypeId.GROUP_TYPE_1, null)); - assertEquals(GroupError.NULL_GROUP_PROPERTY_ID, contractException.getErrorType()); - - // if the group property id is unknown - contractException = assertThrows(ContractException.class, - () -> groupsDataManager.getGroupPropertyDefinition(TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.getUnknownGroupPropertyId())); - assertEquals(GroupError.UNKNOWN_GROUP_PROPERTY_ID, contractException.getErrorType()); - - // if the group property id is unknown - contractException = assertThrows(ContractException.class, - () -> groupsDataManager.getGroupPropertyDefinition(TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK)); - assertEquals(GroupError.UNKNOWN_GROUP_PROPERTY_ID, contractException.getErrorType()); - - }); - - /* precondition test: if the group type id is null */ - GroupsActionSupport.testConsumer(10, 0, 5, 5959643517439959298L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, - () -> groupsDataManager.getGroupPropertyDefinition(null, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK)); - assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the group type id is unknown */ - GroupsActionSupport.testConsumer(10, 0, 5, 9138791522018557245L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, - () -> groupsDataManager.getGroupPropertyDefinition(TestGroupTypeId.getUnknownGroupTypeId(), TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK)); - assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the group property id is null */ - GroupsActionSupport.testConsumer(10, 0, 5, 9138791522018557245L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.getGroupPropertyDefinition(TestGroupTypeId.GROUP_TYPE_1, null)); - assertEquals(GroupError.NULL_GROUP_PROPERTY_ID, contractException.getErrorType()); - }); - - /* precondition test: if the group property id is unknown */ - GroupsActionSupport.testConsumer(10, 0, 5, 9138791522018557245L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, - () -> groupsDataManager.getGroupPropertyDefinition(TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.getUnknownGroupPropertyId())); - assertEquals(GroupError.UNKNOWN_GROUP_PROPERTY_ID, contractException.getErrorType()); - }); - - /* precondition tests: if the group property id is unknown */ - GroupsActionSupport.testConsumer(10, 0, 5, 9138791522018557245L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, - () -> groupsDataManager.getGroupPropertyDefinition(TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK)); - assertEquals(GroupError.UNKNOWN_GROUP_PROPERTY_ID, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "getGroupPropertyExists", args = { GroupTypeId.class, GroupPropertyId.class }) - public void testGetGroupPropertyExists() { - - GroupsActionSupport.testConsumer(10, 0, 5, 8858123829776885259L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - - // show that the personGroupDataManger returns true for the group - // properties that should be present - for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { - assertTrue(groupsDataManager.getGroupPropertyExists(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId)); - } - - // show that other group properties do not exits - assertFalse(groupsDataManager.getGroupPropertyExists(null, null)); - assertFalse(groupsDataManager.getGroupPropertyExists(null, TestGroupPropertyId.getUnknownGroupPropertyId())); - assertFalse(groupsDataManager.getGroupPropertyExists(TestGroupTypeId.getUnknownGroupTypeId(), null)); - assertFalse(groupsDataManager.getGroupPropertyExists(TestGroupTypeId.getUnknownGroupTypeId(), TestGroupPropertyId.getUnknownGroupPropertyId())); - }); - - } - - @Test - @UnitTestMethod(name = "getGroupPropertyIds", args = { GroupTypeId.class }) - public void testGetGroupPropertyIds() { - - GroupsActionSupport.testConsumer(10, 0, 5, 1205481410658607626L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - - // show that the personGroupDataManger returns the correct group - // property ids - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - Set expectedPropertyIds = TestGroupPropertyId.getTestGroupPropertyIds(testGroupTypeId); - Set actualPropertyIds = groupsDataManager.getGroupPropertyIds(testGroupTypeId); - assertEquals(expectedPropertyIds, actualPropertyIds); - } - - }); - - /* precondition test: if the group type id is null */ - GroupsActionSupport.testConsumer(10, 0, 5, 8498668590902665283L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.getGroupPropertyIds(null)); - assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the group type id is unknown */ - GroupsActionSupport.testConsumer(10, 0, 5, 3809094168724176083L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.getGroupPropertyIds(TestGroupTypeId.getUnknownGroupTypeId())); - assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "getGroupPropertyTime", args = { GroupId.class, GroupPropertyId.class }) - public void testGetGroupPropertyTime() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - /* - * Create a container to hold our expectations. The MultiKey will be - * (GroupId,GroupPropertyId) pairs and the MutableDoubles will hold the - * most recent time when each property was set. - */ - Map expectedTimes = new LinkedHashMap<>(); - - /* - * At time = 1, have the agent show that the property values were all - * set at time = 0 and then set those properties to new values - */ - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - List groupIds = groupsDataManager.getGroupIds(); - - // show that we have enough groups to conduct the test - assertTrue(groupIds.size() > 10); - - Set mutableTrackablePropertyIds = new LinkedHashSet<>(); - for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { - PropertyDefinition propertyDefinition = testGroupPropertyId.getPropertyDefinition(); - if (propertyDefinition.propertyValuesAreMutable()) { - if (propertyDefinition.getTimeTrackingPolicy() == TimeTrackingPolicy.TRACK_TIME) { - mutableTrackablePropertyIds.add(testGroupPropertyId); - } - } - } - - // show that we have at least one mutable, trackable property - assertTrue(mutableTrackablePropertyIds.size() > 0); - - // Change all the mutable, trackable property values and record the - // expected time values. - for (TestGroupPropertyId testGroupPropertyId : mutableTrackablePropertyIds) { - TestGroupTypeId testGroupTypeId = testGroupPropertyId.getTestGroupTypeId(); - List groupsForGroupType = groupsDataManager.getGroupsForGroupType(testGroupTypeId); - for (GroupId groupId : groupsForGroupType) { - double groupPropertyTime = groupsDataManager.getGroupPropertyTime(groupId, testGroupPropertyId); - assertEquals(0.0, groupPropertyTime); - expectedTimes.put(new MultiKey(groupId, testGroupPropertyId), new MutableDouble(1.0)); - groupsDataManager.setGroupPropertyValue(groupId, testGroupPropertyId, testGroupPropertyId.getRandomPropertyValue(randomGenerator)); - } - } - })); - - /* - * At time = 2, have the agent show that the property values were all - * set at time = 1 and then set those properties to new values - */ - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - for (MultiKey multiKey : expectedTimes.keySet()) { - GroupId groupId = multiKey.getKey(0); - TestGroupPropertyId testGroupPropertyId = multiKey.getKey(1); - double groupPropertyTime = groupsDataManager.getGroupPropertyTime(groupId, testGroupPropertyId); - MutableDouble mutableDouble = expectedTimes.get(multiKey); - assertEquals(mutableDouble.getValue(), groupPropertyTime); - - mutableDouble.setValue(2.0); - groupsDataManager.setGroupPropertyValue(groupId, testGroupPropertyId, testGroupPropertyId.getRandomPropertyValue(randomGenerator)); - } - - })); - - /* - * At time = 3, have the agent show that the property values were all - * set at time = 2 - */ - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(3, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - - for (MultiKey multiKey : expectedTimes.keySet()) { - GroupId groupId = multiKey.getKey(0); - TestGroupPropertyId testGroupPropertyId = multiKey.getKey(1); - double groupPropertyTime = groupsDataManager.getGroupPropertyTime(groupId, testGroupPropertyId); - MutableDouble mutableDouble = expectedTimes.get(multiKey); - assertEquals(mutableDouble.getValue(), groupPropertyTime); - } - - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - GroupsActionSupport.testConsumers(30, 3, 5, 7313144886869436931L, testPlugin); - - /* - * precondition test: if the group id is null - */ - GroupsActionSupport.testConsumer(30, 3, 5, 4540064428634658468L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, - () -> groupsDataManager.getGroupPropertyTime(null, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK)); - assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); - }); - /* - * precondition test: if the group id is null - */ - GroupsActionSupport.testConsumer(30, 3, 5, 5080244401642933835L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, - () -> groupsDataManager.getGroupPropertyTime(new GroupId(1000000), TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK)); - assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); - }); - - /* - * precondition test: if the group property id is null - */ - GroupsActionSupport.testConsumer(30, 3, 5, 4175298436277522063L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.getGroupPropertyTime(new GroupId(0), null)); - assertEquals(GroupError.NULL_GROUP_PROPERTY_ID, contractException.getErrorType()); - }); - - /* - * precondition test: if the group property id is unknown - */ - GroupsActionSupport.testConsumer(30, 3, 5, 3557052948001350675L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.getGroupPropertyTime(groupId, TestGroupPropertyId.getUnknownGroupPropertyId())); - assertEquals(GroupError.UNKNOWN_GROUP_PROPERTY_ID, contractException.getErrorType()); - }); - - /* - * precondition test: if the group property id is unknown - */ - GroupsActionSupport.testConsumer(30, 3, 5, 7349200768842830982L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); - ContractException contractException = assertThrows(ContractException.class, - () -> groupsDataManager.getGroupPropertyTime(groupId, TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK)); - assertEquals(GroupError.UNKNOWN_GROUP_PROPERTY_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "getGroupPropertyValue", args = { GroupId.class, GroupPropertyId.class }) - public void testGetGroupPropertyValue() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - /* - * Create a container to hold our expectations. The MultiKey will be - * (GroupId,GroupPropertyId) pairs and the Object will hold the most - * recent property value. - */ - Map expectedValues = new LinkedHashMap<>(); - - /* - * At time = 1, have the agent establish the expected values. - */ - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - - List groupIds = groupsDataManager.getGroupIds(); - - // show that we have enough groups to conduct the test - assertTrue(groupIds.size() > 10); - - Set mutableTrackablePropertyIds = new LinkedHashSet<>(); - for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { - PropertyDefinition propertyDefinition = testGroupPropertyId.getPropertyDefinition(); - if (propertyDefinition.propertyValuesAreMutable()) { - if (propertyDefinition.getTimeTrackingPolicy() == TimeTrackingPolicy.TRACK_TIME) { - mutableTrackablePropertyIds.add(testGroupPropertyId); - } - } - } - - // show that we have at least one mutable, trackable property - assertTrue(mutableTrackablePropertyIds.size() > 0); - - // Change all the mutable, trackable property values and record - // those values. - for (TestGroupPropertyId testGroupPropertyId : mutableTrackablePropertyIds) { - TestGroupTypeId testGroupTypeId = testGroupPropertyId.getTestGroupTypeId(); - List groupsForGroupType = groupsDataManager.getGroupsForGroupType(testGroupTypeId); - for (GroupId groupId : groupsForGroupType) { - Object value = groupsDataManager.getGroupPropertyValue(groupId, testGroupPropertyId); - expectedValues.put(new MultiKey(groupId, testGroupPropertyId), value); - } - } - })); - - /* - * At time = 2, have the agent show that the property values still have - * their expected values and then set those properties to new values. - */ - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - for (MultiKey multiKey : expectedValues.keySet()) { - GroupId groupId = multiKey.getKey(0); - TestGroupPropertyId testGroupPropertyId = multiKey.getKey(1); - Object actualValue = groupsDataManager.getGroupPropertyValue(groupId, testGroupPropertyId); - Object expectedValue = expectedValues.get(multiKey); - assertEquals(expectedValue, actualValue); - - Object newValue = testGroupPropertyId.getRandomPropertyValue(randomGenerator); - groupsDataManager.setGroupPropertyValue(groupId, testGroupPropertyId, newValue); - expectedValues.put(multiKey, newValue); - } - - })); - - /* - * At time = 2, have the agent show that the property values still have - * their expected values. - */ - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(3, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - - for (MultiKey multiKey : expectedValues.keySet()) { - GroupId groupId = multiKey.getKey(0); - TestGroupPropertyId testGroupPropertyId = multiKey.getKey(1); - Object actualValue = groupsDataManager.getGroupPropertyValue(groupId, testGroupPropertyId); - Object expectedValue = expectedValues.get(multiKey); - assertEquals(expectedValue, actualValue); - } - - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - GroupsActionSupport.testConsumers(30, 3, 5, 649112407534985381L, testPlugin); - - /* - * precondition test: if the group id is null - */ - GroupsActionSupport.testConsumer(30, 3, 5, 1071603906331418640L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, - () -> groupsDataManager.getGroupPropertyValue(null, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK)); - assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); - }); - - /* - * precondition test: if the group id is null - */ - GroupsActionSupport.testConsumer(30, 3, 5, 7115328473763483106L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, - () -> groupsDataManager.getGroupPropertyValue(new GroupId(1000000), TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK)); - assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); - }); - - /* - * precondition test: if the group property id is null - */ - GroupsActionSupport.testConsumer(30, 3, 5, 2444842488298604050L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.getGroupPropertyValue(new GroupId(0), null)); - assertEquals(GroupError.NULL_GROUP_PROPERTY_ID, contractException.getErrorType()); - }); - - /* - * precondition test: if the group property id is unknown - */ - GroupsActionSupport.testConsumer(30, 3, 5, 1772465526096544640L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.getGroupPropertyValue(groupId, TestGroupPropertyId.getUnknownGroupPropertyId())); - assertEquals(GroupError.UNKNOWN_GROUP_PROPERTY_ID, contractException.getErrorType()); - }); - - /* - * precondition test: if the group property id is unknown - */ - GroupsActionSupport.testConsumer(30, 3, 5, 6994832854288891414L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); - ContractException contractException = assertThrows(ContractException.class, - () -> groupsDataManager.getGroupPropertyValue(groupId, TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK)); - assertEquals(GroupError.UNKNOWN_GROUP_PROPERTY_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "getGroupsForGroupType", args = { GroupTypeId.class }) - public void testGetGroupsForGroupType() { - - GroupsActionSupport.testConsumer(10, 0, 5, 3948247844369837305L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - List groupIds = groupsDataManager.getGroupIds(); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - /* - * Show that there are no groups since we selected 0 groups per - * person - */ - assertEquals(0, groupIds.size()); - Map> expectedTypeToGroupIds = new LinkedHashMap<>(); - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - expectedTypeToGroupIds.put(testGroupTypeId, new LinkedHashSet<>()); - } - for (int i = 0; i < 30; i++) { - TestGroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); - GroupId groupId = groupsDataManager.addGroup(groupTypeId); - expectedTypeToGroupIds.get(groupTypeId).add(groupId); - } - - // show that the group ids match the expected group ids - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - List actualGroupIds = groupsDataManager.getGroupsForGroupType(testGroupTypeId); - Set expectedGroupIds = expectedTypeToGroupIds.get(testGroupTypeId); - assertEquals(expectedGroupIds.size(), actualGroupIds.size()); - assertEquals(expectedGroupIds, new LinkedHashSet<>(actualGroupIds)); - } - - // precondition tests - - // if the group type id is null - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.getGroupsForGroupType(null)); - assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); - - // if the group type id is unknown - contractException = assertThrows(ContractException.class, () -> groupsDataManager.getGroupsForGroupType(TestGroupTypeId.getUnknownGroupTypeId())); - assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); - - }); - - /* precondition test: if the group type id is null */ - GroupsActionSupport.testConsumer(10, 0, 5, 2441670244909950371L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.getGroupsForGroupType(null)); - assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the group type id is unknown */ - GroupsActionSupport.testConsumer(10, 0, 5, 8938160844024056358L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.getGroupsForGroupType(TestGroupTypeId.getUnknownGroupTypeId())); - assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "getGroupsForGroupTypeAndPerson", args = { GroupTypeId.class, PersonId.class }) - public void testGetGroupsForGroupTypeAndPerson() { - - GroupsActionSupport.testConsumer(100, 0, 5, 4847183275886938594L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - List groupIds = groupsDataManager.getGroupIds(); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - List people = peopleDataManager.getPeople(); - - /* - * Show that there are no groups since we selected 0 groups per - * person - */ - assertEquals(0, groupIds.size()); - - Map> expectedDataStructure = new LinkedHashMap<>(); - - // create 60 groups - for (int i = 0; i < 60; i++) { - TestGroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); - GroupId groupId = groupsDataManager.addGroup(groupTypeId); - groupIds.add(groupId); - } - - /* - * For each person pick three groups at random and add the person to - * each group, recording this in the expected data structure - */ - for (PersonId personId : people) { - Collections.shuffle(groupIds, new Random(randomGenerator.nextLong())); - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - MultiKey multiKey = new MultiKey(testGroupTypeId, personId); - expectedDataStructure.put(multiKey, new LinkedHashSet<>()); - } - - for (int i = 0; i < 3; i++) { - GroupId groupId = groupIds.get(i); - groupsDataManager.addPersonToGroup(personId, groupId); - GroupTypeId groupTypeId = groupsDataManager.getGroupType(groupId); - MultiKey multiKey = new MultiKey(groupTypeId, personId); - Set groups = expectedDataStructure.get(multiKey); - groups.add(groupId); - } - } - - // show that the group ids match the expected group ids - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - for (PersonId personId : people) { - List actualGroupIds = groupsDataManager.getGroupsForGroupTypeAndPerson(testGroupTypeId, personId); - MultiKey multiKey = new MultiKey(testGroupTypeId, personId); - Set expectedGroupIds = expectedDataStructure.get(multiKey); - assertEquals(expectedGroupIds.size(), actualGroupIds.size()); - assertEquals(expectedGroupIds, new LinkedHashSet<>(actualGroupIds)); - } - } - }); - - /* precondition test: if the person id is null */ - GroupsActionSupport.testConsumer(100, 0, 5, 5248499346426314201L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.getGroupsForGroupTypeAndPerson(TestGroupTypeId.GROUP_TYPE_1, null)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - }); - - /* precondition test: if the person id is unknown */ - GroupsActionSupport.testConsumer(100, 0, 5, 1445347293441431961L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.getGroupsForGroupTypeAndPerson(TestGroupTypeId.GROUP_TYPE_1, new PersonId(100000))); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - }); - - /* precondition test: if the group type id is null */ - GroupsActionSupport.testConsumer(100, 0, 5, 1445347293441431961L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.getGroupsForGroupTypeAndPerson(null, new PersonId(0))); - assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the group type id is unknown */ - GroupsActionSupport.testConsumer(100, 0, 5, 1445347293441431961L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, - () -> groupsDataManager.getGroupsForGroupTypeAndPerson(TestGroupTypeId.getUnknownGroupTypeId(), new PersonId(0))); - assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "getGroupsForPerson", args = { PersonId.class }) - public void testGetGroupsForPerson() { - - GroupsActionSupport.testConsumer(100, 0, 5, 1095418957424488372L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - List groupIds = groupsDataManager.getGroupIds(); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - List people = peopleDataManager.getPeople(); - - /* - * Show that there are no groups since we selected 0 groups per - * person - */ - assertEquals(0, groupIds.size()); - - Map> expectedDataStructure = new LinkedHashMap<>(); - - // create 60 groups - for (int i = 0; i < 60; i++) { - TestGroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); - GroupId groupId = groupsDataManager.addGroup(groupTypeId); - groupIds.add(groupId); - } - - /* - * For each person pick three groups at random and add the person to - * each group, recording this in the expected data structure - */ - for (PersonId personId : people) { - Collections.shuffle(groupIds, new Random(randomGenerator.nextLong())); - for (int i = 0; i < 3; i++) { - GroupId groupId = groupIds.get(i); - groupsDataManager.addPersonToGroup(personId, groupId); - Set groups = expectedDataStructure.get(personId); - if (groups == null) { - groups = new LinkedHashSet<>(); - expectedDataStructure.put(personId, groups); - } - groups.add(groupId); - } - } - - // show that the group ids match the expected group ids - - for (PersonId personId : people) { - List actualGroupIds = groupsDataManager.getGroupsForPerson(personId); - Set expectedGroupIds = expectedDataStructure.get(personId); - assertNotNull(expectedGroupIds); - assertEquals(expectedGroupIds.size(), actualGroupIds.size()); - assertEquals(expectedGroupIds, new LinkedHashSet<>(actualGroupIds)); - } - - }); - - /* precondition tests: if the person id is null */ - GroupsActionSupport.testConsumer(100, 0, 5, 4037186565913379048L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.getGroupsForPerson(null)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - }); - - /* precondition tests: if the person id is unknown */ - GroupsActionSupport.testConsumer(100, 0, 5, 5901067879853942202L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.getGroupsForPerson(new PersonId(100000))); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "getGroupType", args = { GroupId.class }) - public void testGetGroupType() { - - GroupsActionSupport.testConsumer(100, 0, 5, 5910635654466929788L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - List groupIds = groupsDataManager.getGroupIds(); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - /* - * Show that there are no groups since we selected 0 groups per - * person - */ - assertEquals(0, groupIds.size()); - - Map expectedDataStructure = new LinkedHashMap<>(); - - // create 60 groups - for (int i = 0; i < 60; i++) { - TestGroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); - GroupId groupId = groupsDataManager.addGroup(groupTypeId); - expectedDataStructure.put(groupId, groupTypeId); - } - - // show that the group have the expected types - for (GroupId groupId : expectedDataStructure.keySet()) { - GroupTypeId actualGroupTypeId = groupsDataManager.getGroupType(groupId); - GroupTypeId expectedGroupTypeId = expectedDataStructure.get(groupId); - assertEquals(expectedGroupTypeId, actualGroupTypeId); - } - - }); - - /* precondition test: if the group id is null */ - GroupsActionSupport.testConsumer(100, 0, 5, 4697608906151940983L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.getGroupType(null)); - assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); - }); - - /* precondition test: if the group id is unknown */ - GroupsActionSupport.testConsumer(100, 0, 5, 5074440747148359344L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.getGroupType(new GroupId(100000))); - assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "getGroupTypeCountForPersonId", args = { PersonId.class }) - public void testGetGroupTypeCountForPersonId() { - - GroupsActionSupport.testConsumer(100, 0, 5, 1561008711822589907L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - List groupIds = groupsDataManager.getGroupIds(); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - List people = peopleDataManager.getPeople(); - - /* - * Show that there are no groups since we selected 0 groups per - * person - */ - assertEquals(0, groupIds.size()); - - Map expectedDataStructure = new LinkedHashMap<>(); - - // create 60 groups - TestGroupTypeId groupTypeId = TestGroupTypeId.GROUP_TYPE_1; - for (int i = 0; i < 60; i++) { - GroupId groupId = groupsDataManager.addGroup(groupTypeId); - groupIds.add(groupId); - groupTypeId = groupTypeId.next(); - } - - /* - * For each person pick either one two or three group types and - * record the expected group type count person person. - */ - for (PersonId personId : people) { - int groupTypeCount = randomGenerator.nextInt(3) + 1; - expectedDataStructure.put(personId, groupTypeCount); - groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); - for (int i = 0; i < groupTypeCount; i++) { - List groupsForGroupType = groupsDataManager.getGroupsForGroupType(groupTypeId); - GroupId groupId = groupsForGroupType.get(randomGenerator.nextInt(groupsForGroupType.size())); - groupsDataManager.addPersonToGroup(personId, groupId); - groupTypeId = groupTypeId.next(); - } - } - - // show that the group ids match the expected group ids - - for (PersonId personId : people) { - int actualCount = groupsDataManager.getGroupTypeCountForPersonId(personId); - Integer expectedCount = expectedDataStructure.get(personId); - assertEquals(expectedCount.intValue(), actualCount); - } - - }); - - /* precondition test: if the person id is null */ - GroupsActionSupport.testConsumer(100, 0, 5, 2733980118690868605L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.getGroupTypeCountForPersonId(null)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - }); - - /* precondition test: if the person id is unknown */ - GroupsActionSupport.testConsumer(100, 0, 5, 7646517978722507404L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.getGroupTypeCountForPersonId(new PersonId(100000))); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "getGroupTypeIds", args = {}) - public void testGetGroupTypeIds() { - GroupsActionSupport.testConsumer(10, 3, 5, 1999263877784730672L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - // show that the group ids match the expected group ids - Set groupTypeIds = groupsDataManager.getGroupTypeIds(); - assertEquals(EnumSet.allOf(TestGroupTypeId.class), groupTypeIds); - }); - } - - @Test - @UnitTestMethod(name = "getGroupTypesForPerson", args = { PersonId.class }) - public void testGetGroupTypesForPerson() { - - GroupsActionSupport.testConsumer(100, 0, 5, 2999448198567478958L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - List groupIds = groupsDataManager.getGroupIds(); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - List people = peopleDataManager.getPeople(); - - /* - * Show that there are no groups since we selected 0 groups per - * person - */ - assertEquals(0, groupIds.size()); - - Map> expectedDataStructure = new LinkedHashMap<>(); - - // create 60 groups - TestGroupTypeId groupTypeId = TestGroupTypeId.GROUP_TYPE_1; - for (int i = 0; i < 60; i++) { - GroupId groupId = groupsDataManager.addGroup(groupTypeId); - groupIds.add(groupId); - groupTypeId = groupTypeId.next(); - } - - /* - * For each person pick either one two or three group types and - * record the expected group type count person person. - */ - for (PersonId personId : people) { - Set groupTypes = new LinkedHashSet<>(); - expectedDataStructure.put(personId, groupTypes); - int groupTypeCount = randomGenerator.nextInt(3) + 1; - groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); - for (int i = 0; i < groupTypeCount; i++) { - groupTypes.add(groupTypeId); - List groupsForGroupType = groupsDataManager.getGroupsForGroupType(groupTypeId); - GroupId groupId = groupsForGroupType.get(randomGenerator.nextInt(groupsForGroupType.size())); - groupsDataManager.addPersonToGroup(personId, groupId); - groupTypeId = groupTypeId.next(); - } - } - - // show that the group ids match the expected group ids - - for (PersonId personId : people) { - List actualGroupTypesForPerson = groupsDataManager.getGroupTypesForPerson(personId); - Set expectedGroupTypesForPerson = expectedDataStructure.get(personId); - assertEquals(expectedGroupTypesForPerson.size(), actualGroupTypesForPerson.size()); - assertEquals(expectedGroupTypesForPerson, new LinkedHashSet<>(actualGroupTypesForPerson)); - } - - }); - - /* precondition tests if the person id is null */ - GroupsActionSupport.testConsumer(100, 0, 5, 5882134079494817898L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.getGroupTypesForPerson(null)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - }); - - /* precondition tests if the person id is unknown */ - GroupsActionSupport.testConsumer(100, 0, 5, 4598510399026722120L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.getGroupTypesForPerson(new PersonId(100000))); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "getPeopleForGroup", args = { GroupId.class }) - public void testGetPeopleForGroup() { - - GroupsActionSupport.testConsumer(100, 0, 5, 4550534695972929193L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - - List groupIds = groupsDataManager.getGroupIds(); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - List people = peopleDataManager.getPeople(); - - /* - * Show that there are no groups since we selected 0 groups per - * person - */ - assertEquals(0, groupIds.size()); - - Map> expectedDataStructure = new LinkedHashMap<>(); - - // create 60 groups - TestGroupTypeId groupTypeId = TestGroupTypeId.GROUP_TYPE_1; - for (int i = 0; i < 60; i++) { - GroupId groupId = groupsDataManager.addGroup(groupTypeId); - groupIds.add(groupId); - groupTypeId = groupTypeId.next(); - expectedDataStructure.put(groupId, new LinkedHashSet<>()); - } - groupIds = new ArrayList<>(expectedDataStructure.keySet()); - - /* - * For each person pick either one two or three group types and - * record. - */ - for (PersonId personId : people) { - Collections.shuffle(groupIds, new Random(randomGenerator.nextLong())); - int groupCount = randomGenerator.nextInt(3) + 1; - for (int i = 0; i < groupCount; i++) { - GroupId groupId = groupIds.get(i); - groupsDataManager.addPersonToGroup(personId, groupId); - expectedDataStructure.get(groupId).add(personId); - } - } - - // show that the person ids match the expected person ids - - for (GroupId groupId : groupIds) { - List actualPeople = groupsDataManager.getPeopleForGroup(groupId); - Set expectedPeople = expectedDataStructure.get(groupId); - assertEquals(expectedPeople.size(), actualPeople.size()); - assertEquals(expectedPeople, new LinkedHashSet<>(actualPeople)); - } - - }); - - /* precondition test: if the group id is null */ - GroupsActionSupport.testConsumer(100, 0, 5, 1054111866998260759L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.getPeopleForGroup(null)); - assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); - }); - - /* precondition test: if the group id is unknown */ - GroupsActionSupport.testConsumer(100, 0, 5, 976385337250084757L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.getPeopleForGroup(new GroupId(100000))); - assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "getPeopleForGroupType", args = { GroupTypeId.class }) - public void testGetPeopleForGroupType() { - - GroupsActionSupport.testConsumer(100, 0, 5, 8576174021026036673L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - List groupIds = groupsDataManager.getGroupIds(); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - List people = peopleDataManager.getPeople(); - - /* - * Show that there are no groups since we selected 0 groups per - * person - */ - assertEquals(0, groupIds.size()); - - Map> expectedDataStructure = new LinkedHashMap<>(); - - // create 60 groups - TestGroupTypeId groupTypeId = TestGroupTypeId.GROUP_TYPE_1; - for (int i = 0; i < 60; i++) { - GroupId groupId = groupsDataManager.addGroup(groupTypeId); - groupIds.add(groupId); - groupTypeId = groupTypeId.next(); - expectedDataStructure.put(groupTypeId, new LinkedHashSet<>()); - } - - /* - * For each person pick either one two or three group types and - * record. - */ - for (PersonId personId : people) { - Collections.shuffle(groupIds, new Random(randomGenerator.nextLong())); - int groupCount = randomGenerator.nextInt(3) + 1; - for (int i = 0; i < groupCount; i++) { - GroupId groupId = groupIds.get(i); - groupTypeId = groupsDataManager.getGroupType(groupId); - groupsDataManager.addPersonToGroup(personId, groupId); - expectedDataStructure.get(groupTypeId).add(personId); - } - } - - // show that the person ids match the expected person ids - - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - List actualPeople = groupsDataManager.getPeopleForGroupType(testGroupTypeId); - Set expectedPeople = expectedDataStructure.get(testGroupTypeId); - assertEquals(expectedPeople.size(), actualPeople.size()); - assertEquals(expectedPeople, new LinkedHashSet<>(actualPeople)); - } - - }); - - /* precondition test: if the group id is null */ - GroupsActionSupport.testConsumer(100, 0, 5, 3966867633401336210L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.getPeopleForGroupType(null)); - assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the group id is unknown */ - GroupsActionSupport.testConsumer(100, 0, 5, 4582534442214781870L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.getPeopleForGroupType(TestGroupTypeId.getUnknownGroupTypeId())); - assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "getPersonCountForGroup", args = { GroupId.class }) - public void testGetPersonCountForGroup() { - - GroupsActionSupport.testConsumer(100, 0, 5, 1763603697244834578L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - - List groupIds = groupsDataManager.getGroupIds(); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - List people = peopleDataManager.getPeople(); - - /* - * Show that there are no groups since we selected 0 groups per - * person - */ - assertEquals(0, groupIds.size()); - - Map expectedDataStructure = new LinkedHashMap<>(); - - // create 60 groups - TestGroupTypeId groupTypeId = TestGroupTypeId.GROUP_TYPE_1; - for (int i = 0; i < 60; i++) { - GroupId groupId = groupsDataManager.addGroup(groupTypeId); - groupIds.add(groupId); - groupTypeId = groupTypeId.next(); - expectedDataStructure.put(groupId, new MutableInteger()); - } - - /* - * For each person pick either one two or three group types and - * record. - */ - for (PersonId personId : people) { - Collections.shuffle(groupIds, new Random(randomGenerator.nextLong())); - int groupCount = randomGenerator.nextInt(3) + 1; - for (int i = 0; i < groupCount; i++) { - GroupId groupId = groupIds.get(i); - groupsDataManager.addPersonToGroup(personId, groupId); - expectedDataStructure.get(groupId).increment(); - } - } - - // show that number of people matches expectations - - for (GroupId groupId : groupIds) { - int actualCount = groupsDataManager.getPersonCountForGroup(groupId); - int expectedCount = expectedDataStructure.get(groupId).getValue(); - assertEquals(expectedCount, actualCount); - } - - }); - - /* precondition test: if the group id is null */ - GroupsActionSupport.testConsumer(100, 0, 5, 2981746189003482663L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.getPersonCountForGroup(null)); - assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); - - }); - - /* precondition test: if the group id is unknown */ - GroupsActionSupport.testConsumer(100, 0, 5, 3438693482743062795L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.getPersonCountForGroup(new GroupId(10000000))); - assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "getPersonCountForGroupType", args = { GroupTypeId.class }) - public void testGetPersonCountForGroupType() { - - GroupsActionSupport.testConsumer(100, 0, 5, 5794665230130343350L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - List groupIds = groupsDataManager.getGroupIds(); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - List people = peopleDataManager.getPeople(); - - /* - * Show that there are no groups since we selected 0 groups per - * person - */ - assertEquals(0, groupIds.size()); - - Map> expectedDataStructure = new LinkedHashMap<>(); - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - expectedDataStructure.put(testGroupTypeId, new LinkedHashSet<>()); - } - - // create 60 groups - TestGroupTypeId groupTypeId = TestGroupTypeId.GROUP_TYPE_1; - - for (int i = 0; i < 60; i++) { - GroupId groupId = groupsDataManager.addGroup(groupTypeId); - groupIds.add(groupId); - groupTypeId = groupTypeId.next(); - } - - /* - * For each person pick either one two or three group types and - * record. - */ - for (PersonId personId : people) { - Collections.shuffle(groupIds, new Random(randomGenerator.nextLong())); - int groupCount = randomGenerator.nextInt(3) + 1; - for (int i = 0; i < groupCount; i++) { - GroupId groupId = groupIds.get(i); - groupTypeId = groupsDataManager.getGroupType(groupId); - groupsDataManager.addPersonToGroup(personId, groupId); - expectedDataStructure.get(groupTypeId).add(personId); - } - } - - // show that number of people matches expectations - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - int actualCount = groupsDataManager.getPersonCountForGroupType(testGroupTypeId); - int expectedCount = expectedDataStructure.get(testGroupTypeId).size(); - assertEquals(expectedCount, actualCount); - } - - }); - - /* precondition test: if the group id is null */ - GroupsActionSupport.testConsumer(100, 0, 5, 5829408984346963563L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.getPersonCountForGroupType(null)); - assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the group id is unknown */ - GroupsActionSupport.testConsumer(100, 0, 5, 3769874950212938109L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.getPersonCountForGroupType(TestGroupTypeId.getUnknownGroupTypeId())); - assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "groupTypeIdExists", args = { GroupTypeId.class }) - public void testGroupTypeIdExists() { - - GroupsActionSupport.testConsumer(10, 3, 5, 1172766215251823083L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - assertTrue(groupsDataManager.groupTypeIdExists(testGroupTypeId)); - } - assertFalse(groupsDataManager.groupTypeIdExists(TestGroupTypeId.getUnknownGroupTypeId())); - }); - - } - - @Test - @UnitTestMethod(name = "isPersonInGroup", args = { GroupId.class, PersonId.class }) - public void testIsPersonInGroup() { - - GroupsActionSupport.testConsumer(100, 0, 5, 8319627382232144625L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - List groupIds = groupsDataManager.getGroupIds(); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - List people = peopleDataManager.getPeople(); - - /* - * Show that there are no groups since we selected 0 groups per - * person - */ - assertEquals(0, groupIds.size()); - - Map> expectedDataStructure = new LinkedHashMap<>(); - - // create 60 groups - TestGroupTypeId groupTypeId = TestGroupTypeId.GROUP_TYPE_1; - for (int i = 0; i < 60; i++) { - GroupId groupId = groupsDataManager.addGroup(groupTypeId); - groupIds.add(groupId); - groupTypeId = groupTypeId.next(); - expectedDataStructure.put(groupId, new LinkedHashSet<>()); - } - groupIds = new ArrayList<>(expectedDataStructure.keySet()); - - /* - * For each person pick either one two or three group types and - * record. - */ - for (PersonId personId : people) { - Collections.shuffle(groupIds, new Random(randomGenerator.nextLong())); - int groupCount = randomGenerator.nextInt(3) + 1; - for (int i = 0; i < groupCount; i++) { - GroupId groupId = groupIds.get(i); - groupsDataManager.addPersonToGroup(personId, groupId); - expectedDataStructure.get(groupId).add(personId); - } - } - - // show that the person ids match the expected person ids - - for (GroupId groupId : groupIds) { - Set expectedPeople = expectedDataStructure.get(groupId); - for (PersonId personId : people) { - if (expectedPeople.contains(personId)) { - assertTrue(groupsDataManager.isPersonInGroup(personId, groupId)); - } else { - assertFalse(groupsDataManager.isPersonInGroup(personId, groupId)); - } - } - } - - }); - - /* precondition test: if the group id is null */ - GroupsActionSupport.testConsumer(100, 0, 5, 3623255510968295889L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.isPersonInGroup(new PersonId(0), null)); - assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); - }); - - /* precondition test: if the group id is unknown */ - GroupsActionSupport.testConsumer(100, 0, 5, 825983259283758140L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.isPersonInGroup(new PersonId(0), new GroupId(10000))); - assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); - }); - - /* precondition test: if the person id is null */ - GroupsActionSupport.testConsumer(100, 0, 5, 1009864608566885897L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.isPersonInGroup(null, new GroupId(0))); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - }); - - /* precondition test: if the person id is unknown */ - GroupsActionSupport.testConsumer(100, 0, 5, 5275459426147794240L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> groupsDataManager.isPersonInGroup(new PersonId(1000000), new GroupId(0))); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - }); - - } - - private void testEventLabeler(ActorContext c, EventLabeler eventLabeler) { - assertNotNull(eventLabeler); - ContractException contractException = assertThrows(ContractException.class, () -> c.addEventLabeler(eventLabeler)); - assertEquals(NucleusError.DUPLICATE_LABELER_ID_IN_EVENT_LABELER, contractException.getErrorType()); - } - - @Test - @UnitTestMethod(name = "init", args = { GroupsPluginData.class }) - public void testGroupAdditionEventLabelers() { - - // Have the agent attempt to add the event labelers and show that a - // contract exception is thrown, indicating that the labelers were - // previously added by the resolver. - - GroupsActionSupport.testConsumer(100, 3, 5, 6706902549572603852L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - testEventLabeler(c, GroupAdditionEvent.getEventLabelerForGroupType(groupsDataManager)); - }); - - } - - @Test - @UnitTestMethod(name = "init", args = { GroupsPluginData.class }) - public void testGroupImminentRemovalEventLabelers() { - - // Have the agent attempt to add the event labelers and show that a - // contract exception is thrown, indicating that the labelers were - // previously added by the resolver. - - GroupsActionSupport.testConsumer(100, 3, 5, 6196206924587095446L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - - testEventLabeler(c, GroupImminentRemovalEvent.getEventLabelerForGroup()); - testEventLabeler(c, GroupImminentRemovalEvent.getEventLabelerForGroupType(groupsDataManager)); - }); - - } - - @Test - @UnitTestMethod(name = "init", args = { GroupsPluginData.class }) - public void testGroupPropertyUpdateEventLabelers() { - - // Have the agent attempt to add the event labelers and show that a - // contract exception is thrown, indicating that the labelers were - // previously added by the resolver. - - GroupsActionSupport.testConsumer(100, 3, 5, 4869845127685024578L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - - testEventLabeler(c, GroupPropertyUpdateEvent.getEventLabelerForGroup()); - testEventLabeler(c, GroupPropertyUpdateEvent.getEventLabelerForGroupAndProperty()); - testEventLabeler(c, GroupPropertyUpdateEvent.getEventLabelerForGroupType(groupsDataManager)); - testEventLabeler(c, GroupPropertyUpdateEvent.getEventLabelerForGroupTypeAndProperty(groupsDataManager)); - }); - - } - - @Test - @UnitTestMethod(name = "init", args = { GroupsPluginData.class }) - public void testGroupMembershipAdditionEventLabelers() { - // Have the agent attempt to add the event labelers and show that a - // contract exception is thrown, indicating that the labelers were - // previously added by the resolver. - - GroupsActionSupport.testConsumer(100, 3, 5, 5331119358636307434L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - - testEventLabeler(c, GroupMembershipAdditionEvent.getEventLabelerForGroup()); - testEventLabeler(c, GroupMembershipAdditionEvent.getEventLabelerForGroupAndPerson()); - testEventLabeler(c, GroupMembershipAdditionEvent.getEventLabelerForGroupType(groupsDataManager)); - testEventLabeler(c, GroupMembershipAdditionEvent.getEventLabelerForGroupTypeAndPerson(groupsDataManager)); - testEventLabeler(c, GroupMembershipAdditionEvent.getEventLabelerForPerson()); - }); - - } - - @Test - @UnitTestMethod(name = "init", args = { GroupsPluginData.class }) - public void testGroupMembershipRemovalEventLabelers() { - - // Have the agent attempt to add the event labelers and show that a - // contract exception is thrown, indicating that the labelers were - // previously added by the resolver. - - GroupsActionSupport.testConsumer(100, 3, 5, 774686050832969915L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - - testEventLabeler(c, GroupMembershipRemovalEvent.getEventLabelerForGroup()); - testEventLabeler(c, GroupMembershipRemovalEvent.getEventLabelerForGroupAndPerson()); - testEventLabeler(c, GroupMembershipRemovalEvent.getEventLabelerForGroupType(groupsDataManager)); - testEventLabeler(c, GroupMembershipRemovalEvent.getEventLabelerForGroupTypeAndPerson(groupsDataManager)); - testEventLabeler(c, GroupMembershipRemovalEvent.getEventLabelerForPerson()); - }); - - } - - @Test - @UnitTestMethod(name = "init", args = { GroupsPluginData.class }) - public void testBulkPersonAdditionEvent() { - - // create structures to hold observations - Set expectedGroupObservations = new LinkedHashSet<>(); - Set actualGroupObservations = new LinkedHashSet<>(); - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // add an observer that will observe the new groups being created as - // well as the people being added to the groups - - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { - c.subscribe(GroupAdditionEvent.class, (c2, e) -> { - actualGroupObservations.add(e.getGroupId()); - }); - - })); - - /* - * Have an agent add several people via bulk person creation that - * includes group associations. - * - * Show that groups were added and the people are in the new groups and - * thus the resolver must have handled the corresponding - * BulkPersonAdditionEvent. - */ - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - // establish data views and how many people and groups already exist - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - int personIdOffest = peopleDataManager.getPopulationCount(); - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - int groupIdOffset = groupsDataManager.getGroupIds().size(); - - // create a bulk construction event with 5 new people and 4 new - // groups - BulkPersonConstructionData.Builder builder = BulkPersonConstructionData.builder(); - PersonConstructionData.Builder peopleBuilder = PersonConstructionData.builder(); - BulkGroupMembershipData.Builder membershipBuilder = BulkGroupMembershipData.builder(); - - // add 5 people - builder.add(peopleBuilder.build()); - builder.add(peopleBuilder.build()); - builder.add(peopleBuilder.build()); - builder.add(peopleBuilder.build()); - builder.add(peopleBuilder.build()); - - // add 4 groups - membershipBuilder.addGroup(TestGroupTypeId.GROUP_TYPE_1); - membershipBuilder.addGroup(TestGroupTypeId.GROUP_TYPE_2); - membershipBuilder.addGroup(TestGroupTypeId.GROUP_TYPE_3); - membershipBuilder.addGroup(TestGroupTypeId.GROUP_TYPE_1); - - // record the expected group observations - expectedGroupObservations.add(new GroupId(groupIdOffset + 0)); - expectedGroupObservations.add(new GroupId(groupIdOffset + 1)); - expectedGroupObservations.add(new GroupId(groupIdOffset + 2)); - expectedGroupObservations.add(new GroupId(groupIdOffset + 3)); - - // assign the people to the groups - membershipBuilder.addPersonToGroup(0, 0); - membershipBuilder.addPersonToGroup(0, 1); - membershipBuilder.addPersonToGroup(0, 2); - membershipBuilder.addPersonToGroup(2, 0); - membershipBuilder.addPersonToGroup(2, 1); - membershipBuilder.addPersonToGroup(3, 2); - - builder.addAuxiliaryData(membershipBuilder.build()); - BulkPersonConstructionData bulkPersonConstructionData = builder.build(); - peopleDataManager.addBulkPeople(bulkPersonConstructionData); - - // show that the groups exist and have the appropriate people - - GroupId groupId = new GroupId(0 + groupIdOffset); - assertTrue(groupsDataManager.groupExists(groupId)); - assertEquals(TestGroupTypeId.GROUP_TYPE_1, groupsDataManager.getGroupType(groupId)); - assertEquals(2, groupsDataManager.getPersonCountForGroup(groupId)); - assertTrue(groupsDataManager.isPersonInGroup(new PersonId(personIdOffest + 0), groupId)); - assertTrue(groupsDataManager.isPersonInGroup(new PersonId(personIdOffest + 2), groupId)); - - groupId = new GroupId(1 + groupIdOffset); - assertTrue(groupsDataManager.groupExists(groupId)); - assertEquals(TestGroupTypeId.GROUP_TYPE_2, groupsDataManager.getGroupType(groupId)); - assertEquals(2, groupsDataManager.getPersonCountForGroup(groupId)); - assertTrue(groupsDataManager.isPersonInGroup(new PersonId(personIdOffest + 0), groupId)); - assertTrue(groupsDataManager.isPersonInGroup(new PersonId(personIdOffest + 2), groupId)); - - groupId = new GroupId(2 + groupIdOffset); - assertTrue(groupsDataManager.groupExists(groupId)); - assertEquals(TestGroupTypeId.GROUP_TYPE_3, groupsDataManager.getGroupType(groupId)); - assertEquals(2, groupsDataManager.getPersonCountForGroup(groupId)); - assertTrue(groupsDataManager.isPersonInGroup(new PersonId(personIdOffest + 0), groupId)); - assertTrue(groupsDataManager.isPersonInGroup(new PersonId(personIdOffest + 3), groupId)); - - groupId = new GroupId(3 + groupIdOffset); - assertTrue(groupsDataManager.groupExists(groupId)); - assertEquals(TestGroupTypeId.GROUP_TYPE_1, groupsDataManager.getGroupType(groupId)); - assertEquals(0, groupsDataManager.getPersonCountForGroup(groupId)); - - })); - - // have the observer verify the observations - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(3, (c) -> { - assertEquals(expectedGroupObservations, actualGroupObservations); - })); - - /* - * Initialize with some people and groups - */ - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - GroupsActionSupport.testConsumers(10, 3, 5, 4483791915301705904L, testPlugin); - - /* - * precondition test: if the BulkMembership data exists and contains an - * unknown person id - */ - GroupsActionSupport.testConsumer(10, 3, 5, 3738915539234400027L, (c) -> { - ContractException contractException = assertThrows(ContractException.class, () -> { - BulkPersonConstructionData.Builder builder = BulkPersonConstructionData.builder(); - PersonConstructionData.Builder peopleBuilder = PersonConstructionData.builder(); - BulkGroupMembershipData.Builder membershipBuilder = BulkGroupMembershipData.builder(); - builder.add(peopleBuilder.build()); - membershipBuilder.addGroup(TestGroupTypeId.GROUP_TYPE_1); - membershipBuilder.addPersonToGroup(1, 0); - builder.addAuxiliaryData(membershipBuilder.build()); - BulkPersonConstructionData bulkPersonConstructionData = builder.build(); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - peopleDataManager.addBulkPeople(bulkPersonConstructionData); - }); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - }); - - /* - * precondition test: if the BulkMembership data exists and contains an - * unknown group type id - */ - GroupsActionSupport.testConsumer(10, 3, 5, 5431888419388886834L, (c) -> { - ContractException contractException = assertThrows(ContractException.class, () -> { - BulkPersonConstructionData.Builder builder = BulkPersonConstructionData.builder(); - PersonConstructionData.Builder peopleBuilder = PersonConstructionData.builder(); - BulkGroupMembershipData.Builder membershipBuilder = BulkGroupMembershipData.builder(); - builder.add(peopleBuilder.build()); - membershipBuilder.addGroup(TestGroupTypeId.getUnknownGroupTypeId()); - builder.addAuxiliaryData(membershipBuilder.build()); - BulkPersonConstructionData bulkPersonConstructionData = builder.build(); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - peopleDataManager.addBulkPeople(bulkPersonConstructionData); - }); - assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); - }); - - /* - * precondition test: if the BulkMembership data exists and contains an - * unknown group property id - */ - GroupsActionSupport.testConsumer(10, 3, 5, 5431888419388886834L, (c) -> { - ContractException contractException = assertThrows(ContractException.class, () -> { - BulkPersonConstructionData.Builder builder = BulkPersonConstructionData.builder(); - BulkGroupMembershipData.Builder membershipBuilder = BulkGroupMembershipData.builder(); - builder.add(PersonConstructionData.builder().build()); - membershipBuilder.addGroup(TestGroupTypeId.GROUP_TYPE_1); - membershipBuilder.setGroupPropertyValue(0, TestGroupPropertyId.getUnknownGroupPropertyId(), 5); - builder.addAuxiliaryData(membershipBuilder.build()); - BulkPersonConstructionData bulkPersonConstructionData = builder.build(); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - peopleDataManager.addBulkPeople(bulkPersonConstructionData); - }); - assertEquals(GroupError.UNKNOWN_GROUP_PROPERTY_ID, contractException.getErrorType()); - }); - - /* - * precondition test: if the BulkMembership data exists and contains an - * incompatible group property value - */ - GroupsActionSupport.testConsumer(10, 3, 5, 5431888419388886834L, (c) -> { - ContractException contractException = assertThrows(ContractException.class, () -> { - BulkPersonConstructionData.Builder builder = BulkPersonConstructionData.builder(); - BulkGroupMembershipData.Builder membershipBuilder = BulkGroupMembershipData.builder(); - builder.add(PersonConstructionData.builder().build()); - membershipBuilder.addGroup(TestGroupTypeId.GROUP_TYPE_1); - membershipBuilder.setGroupPropertyValue(0, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, 5); - builder.addAuxiliaryData(membershipBuilder.build()); - BulkPersonConstructionData bulkPersonConstructionData = builder.build(); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - peopleDataManager.addBulkPeople(bulkPersonConstructionData); - }); - assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "init", args = { GroupsPluginData.class }) - public void testPersonImminentRemovalEvent() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - MutableObject pId = new MutableObject<>(); - - /* - * Have the agent add a person and then remove it. There will be a delay - * of 0 time for the person to be removed. - */ - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - - // add a new person - PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); - pId.setValue(personId); - // place the person in all groups - for (GroupId groupId : groupsDataManager.getGroupIds()) { - groupsDataManager.addPersonToGroup(personId, groupId); - } - - // remove the person - peopleDataManager.removePerson(personId); - - })); - - /* - * Have the agent show that the person is no longer present in the - * groups - * - */ - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(3, (c) -> { - - GroupsDataManager personGroupDataManager = c.getDataManager(GroupsDataManager.class); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - - // get a list of all the groups - List groupIds = personGroupDataManager.getGroupIds(); - - // get the last added person - PersonId personId = pId.getValue(); - - // show that the person does not exist - assertFalse(peopleDataManager.personExists(personId)); - - // show that none of the groups contain the person - for (GroupId groupId : groupIds) { - List people = personGroupDataManager.getPeopleForGroup(groupId); - assertFalse(people.contains(personId)); - } - - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - GroupsActionSupport.testConsumers(30, 3, 10, 2908277607868593618L, testPlugin); - - } - - @Test - @UnitTestMethod(name = "init", args = { GroupsPluginData.class }) - public void testGroupDataManagerInitialization() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7212690164088198082L); - - int initialPopulation = 30; - double expectedGroupsPerPerson = 3; - double expectedPeoplePerGroup = 5; - - // create a list of people - List people = new ArrayList<>(); - for (int i = 0; i < initialPopulation; i++) { - people.add(new PersonId(i)); - } - int membershipCount = (int) FastMath.round(initialPopulation * expectedGroupsPerPerson); - int groupCount = (int) FastMath.round(membershipCount / expectedPeoplePerGroup); - - Builder builder = Simulation.builder(); - - // add the group plugin - GroupsPluginData.Builder groupBuilder = GroupsPluginData.builder(); - // add group types - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - groupBuilder.addGroupTypeId(testGroupTypeId); - } - // define group properties - for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { - groupBuilder.defineGroupProperty(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId, testGroupPropertyId.getPropertyDefinition()); - } - - // add the groups and set their properties - List groups = new ArrayList<>(); - for (int i = 0; i < groupCount; i++) { - GroupId groupId = new GroupId(i); - groups.add(groupId); - TestGroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); - groupBuilder.addGroup(groupId, groupTypeId); - for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.getTestGroupPropertyIds(groupTypeId)) { - groupBuilder.setGroupPropertyValue(groupId, testGroupPropertyId, testGroupPropertyId.getRandomPropertyValue(randomGenerator)); - } - } - - // add the group memberships - Set groupMemeberships = new LinkedHashSet<>(); - while (groupMemeberships.size() < membershipCount) { - PersonId personId = people.get(randomGenerator.nextInt(people.size())); - GroupId groupId = groups.get(randomGenerator.nextInt(groups.size())); - groupMemeberships.add(new MultiKey(groupId, personId)); - } - - for (MultiKey multiKey : groupMemeberships) { - GroupId groupId = multiKey.getKey(0); - PersonId personId = multiKey.getKey(1); - groupBuilder.addPersonToGroup(groupId, personId); - } - - GroupsPluginData groupsPluginData = groupBuilder.build(); - - builder.addPlugin(GroupsPlugin.getGroupPlugin(groupsPluginData)); - - // add the people plugin - PeoplePluginData.Builder peopleBuilder = PeoplePluginData.builder(); - - BulkPersonConstructionData.Builder bulkBuilder = BulkPersonConstructionData.builder(); - for (int i = 0; i < initialPopulation; i++) { - bulkBuilder.add(PersonConstructionData.builder().build()); - } - PeoplePluginData peoplePluginData = peopleBuilder.build(); - Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(peoplePluginData); - - builder.addPlugin(peoplePlugin); - - // add the stochastics plugin - StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder().setSeed(randomGenerator.nextLong()).build(); - Plugin stochasticsPlugin = StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData); - builder.addPlugin(stochasticsPlugin); - - // add the action plugin - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // add an agent that will demonstrate that the state of the data manager - // reflects the contents of the group plugin data. - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - GroupsDataManager personGroupDataManager = c.getDataManager(GroupsDataManager.class); - - // show the groups are as expected - List actualGroupIds = personGroupDataManager.getGroupIds(); - Set expectedGroupIds = groupsPluginData.getGroupIds(); - assertEquals(expectedGroupIds.size(), actualGroupIds.size()); - assertEquals(expectedGroupIds, new LinkedHashSet<>(actualGroupIds)); - - // show that each group has the expected type - for (GroupId groupId : personGroupDataManager.getGroupIds()) { - GroupTypeId expectedGroupTypeId = groupsPluginData.getGroupTypeId(groupId); - GroupTypeId actualGroupTypeId = personGroupDataManager.getGroupType(groupId); - assertEquals(expectedGroupTypeId, actualGroupTypeId); - } - - // show the group memberships are the same - for (GroupId groupId : personGroupDataManager.getGroupIds()) { - Set expectedPeople = groupsPluginData.getGroupMembers(groupId); - List actualPeople = personGroupDataManager.getPeopleForGroup(groupId); - assertEquals(expectedPeople.size(), actualPeople.size()); - assertEquals(expectedPeople, new LinkedHashSet<>(actualPeople)); - } - - // show that the group types are the same - Set expectedGroupTypeIds = groupsPluginData.getGroupTypeIds(); - Set actualGroupTypeIds = personGroupDataManager.getGroupTypeIds(); - assertEquals(expectedGroupTypeIds, actualGroupTypeIds); - - // show that the property definitions are the same - for (GroupTypeId groupTypeId : personGroupDataManager.getGroupTypeIds()) { - Set expectedGroupPropertyIds = groupsPluginData.getGroupPropertyIds(groupTypeId); - Set actualGroupPropertyIds = personGroupDataManager.getGroupPropertyIds(groupTypeId); - assertEquals(expectedGroupPropertyIds, actualGroupPropertyIds); - for (GroupPropertyId groupPropertyId : actualGroupPropertyIds) { - PropertyDefinition expectedPropertyDefinition = groupsPluginData.getGroupPropertyDefinition(groupTypeId, groupPropertyId); - PropertyDefinition actualPropertyDefinition = personGroupDataManager.getGroupPropertyDefinition(groupTypeId, groupPropertyId); - assertEquals(expectedPropertyDefinition, actualPropertyDefinition); - } - } - - // show that the group property values are the same - for (GroupId groupId : personGroupDataManager.getGroupIds()) { - GroupTypeId groupTypeId = personGroupDataManager.getGroupType(groupId); - Set groupPropertyIds = personGroupDataManager.getGroupPropertyIds(groupTypeId); - for (GroupPropertyId groupPropertyId : groupPropertyIds) { - Object expectedValue = groupsPluginData.getGroupPropertyValue(groupId, groupPropertyId); - Object actualValue = personGroupDataManager.getGroupPropertyValue(groupId, groupPropertyId); - assertEquals(expectedValue, actualValue); - - } - - } - - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - builder.addPlugin(testPlugin); - - // build and execute the engine - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - builder.setOutputConsumer(scenarioPlanCompletionObserver::handleOutput).build().execute(); - - // show that all actions were executed - if (!scenarioPlanCompletionObserver.allPlansExecuted()) { - throw new ContractException(TestError.TEST_EXECUTION_FAILURE); - } - - } - -} diff --git a/gcm3/src/test/java/plugins/groups/events/AT_GroupAdditionEvent.java b/gcm3/src/test/java/plugins/groups/events/AT_GroupAdditionEvent.java deleted file mode 100644 index 47a21e702..000000000 --- a/gcm3/src/test/java/plugins/groups/events/AT_GroupAdditionEvent.java +++ /dev/null @@ -1,142 +0,0 @@ -package plugins.groups.events; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import nucleus.EventLabel; -import nucleus.EventLabeler; -import nucleus.SimulationContext; -import plugins.groups.datamanagers.GroupsDataManager; -import plugins.groups.support.GroupError; -import plugins.groups.support.GroupId; -import plugins.groups.support.GroupTypeId; -import plugins.groups.testsupport.GroupsActionSupport; -import plugins.groups.testsupport.TestGroupTypeId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - -@UnitTest(target = GroupAdditionEvent.class) -public class AT_GroupAdditionEvent { - - - @Test - @UnitTestConstructor(args = { GroupId.class }) - public void testConstructor() { - // nothing to test - } - - @Test - @UnitTestMethod(name = "getGroupId", args = {}) - public void testGetGroupId() { - GroupId groupId = new GroupId(46); - GroupAdditionEvent groupAdditionEvent = new GroupAdditionEvent(groupId); - assertEquals(groupId, groupAdditionEvent.getGroupId()); - } - - @Test - @UnitTestMethod(name = "getPrimaryKeyValue", args = {}) - public void testGetPrimaryKeyValue() { - GroupId groupId = new GroupId(46); - GroupAdditionEvent groupAdditionEvent = new GroupAdditionEvent(groupId); - assertEquals(GroupAdditionEvent.class, groupAdditionEvent.getPrimaryKeyValue()); - } - - @Test - @UnitTestMethod(name = "getEventLabelByGroupType", args = { SimulationContext.class, GroupTypeId.class }) - public void testGetEventLabelByGroupType() { - - GroupsActionSupport.testConsumer(0, 3, 5, 296314827017119408L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - - Set> eventLabels = new LinkedHashSet<>(); - - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - - EventLabel eventLabel = GroupAdditionEvent.getEventLabelByGroupType(c, testGroupTypeId); - - // show that the event label has the correct event class - assertEquals(GroupAdditionEvent.class, eventLabel.getEventClass()); - - // show that the event label has the correct primary key - assertEquals(GroupAdditionEvent.class, eventLabel.getPrimaryKeyValue()); - - // show that the event label has the same id as its - // associated labeler - EventLabeler eventLabeler = GroupAdditionEvent.getEventLabelerForGroupType(groupsDataManager); - assertEquals(eventLabeler.getEventLabelerId(), eventLabel.getLabelerId()); - - // show that two event labels with the same inputs are equal - EventLabel eventLabel2 = GroupAdditionEvent.getEventLabelByGroupType(c, testGroupTypeId); - assertEquals(eventLabel, eventLabel2); - - // show that equal event labels have equal hash codes - assertEquals(eventLabel.hashCode(), eventLabel2.hashCode()); - - // show that two event labels with different inputs are not - // equal - assertTrue(eventLabels.add(eventLabel)); - } - - // precondition tests - - // if the group type id is null - ContractException contractException = assertThrows(ContractException.class, () -> GroupAdditionEvent.getEventLabelByGroupType(c, null)); - assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); - - // if the group type id is unknown - contractException = assertThrows(ContractException.class, () -> GroupAdditionEvent.getEventLabelByGroupType(c, TestGroupTypeId.getUnknownGroupTypeId())); - assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); - - }); - } - - @Test - @UnitTestMethod(name = "getEventLabelerForGroupType", args = {}) - public void testGetEventLabelerForGroupType() { - - GroupsActionSupport.testConsumer(0, 3, 5, 4044175875975004087L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - - // create an event labeler - EventLabeler eventLabeler = GroupAdditionEvent.getEventLabelerForGroupType(groupsDataManager); - - // show that the event labeler has the correct event class - assertEquals(GroupAdditionEvent.class, eventLabeler.getEventClass()); - - // show that the event labeler produces the expected event label - - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - - // derive the expected event label for this event - EventLabel expectedEventLabel = GroupAdditionEvent.getEventLabelByGroupType(c, testGroupTypeId); - - // show that the event label and event labeler have equal id - // values - assertEquals(expectedEventLabel.getLabelerId(), eventLabeler.getEventLabelerId()); - - GroupId groupId = groupsDataManager.addGroup(testGroupTypeId); - - // create an event - GroupAdditionEvent event = new GroupAdditionEvent(groupId); - - // show that the event labeler produces the correct event - // label - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - - assertEquals(expectedEventLabel, actualEventLabel); - - } - - }); - } -} diff --git a/gcm3/src/test/java/plugins/groups/events/AT_GroupImminentRemovalEvent.java b/gcm3/src/test/java/plugins/groups/events/AT_GroupImminentRemovalEvent.java deleted file mode 100644 index 38ebf2e5a..000000000 --- a/gcm3/src/test/java/plugins/groups/events/AT_GroupImminentRemovalEvent.java +++ /dev/null @@ -1,243 +0,0 @@ -package plugins.groups.events; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import nucleus.EventLabel; -import nucleus.EventLabeler; -import nucleus.SimulationContext; -import plugins.groups.datamanagers.GroupsDataManager; -import plugins.groups.support.GroupError; -import plugins.groups.support.GroupId; -import plugins.groups.support.GroupTypeId; -import plugins.groups.testsupport.GroupsActionSupport; -import plugins.groups.testsupport.TestGroupTypeId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - -@UnitTest(target = GroupImminentRemovalEvent.class) -public class AT_GroupImminentRemovalEvent { - - @Test - @UnitTestConstructor(args = { GroupId.class }) - public void testConstructor() { - // nothing to test - } - - @Test - @UnitTestMethod(name = "getGroupId", args = {}) - public void testGetGroupId() { - GroupId groupId = new GroupId(35); - GroupImminentRemovalEvent groupImminentRemovalEvent = new GroupImminentRemovalEvent(groupId); - assertEquals(groupId, groupImminentRemovalEvent.getGroupId()); - } - - @Test - @UnitTestMethod(name = "getPrimaryKeyValue", args = {}) - public void testGetPrimaryKeyValue() { - GroupId groupId = new GroupId(35); - GroupImminentRemovalEvent groupImminentRemovalEvent = new GroupImminentRemovalEvent(groupId); - assertEquals(GroupImminentRemovalEvent.class, groupImminentRemovalEvent.getPrimaryKeyValue()); - } - - @Test - @UnitTestMethod(name = "getEventLabelByGroup", args = { SimulationContext.class, GroupId.class }) - public void testGetEventLabelByGroup() { - - GroupsActionSupport.testConsumer(0, 3, 5, 4793492577271802585L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - Set> eventLabels = new LinkedHashSet<>(); - TestGroupTypeId testGroupTypeId = TestGroupTypeId.GROUP_TYPE_1; - for (int i = 0;i<10;i++) { - GroupId groupId = groupsDataManager.addGroup(testGroupTypeId); - - testGroupTypeId = testGroupTypeId.next(); - - EventLabel eventLabel = GroupImminentRemovalEvent.getEventLabelByGroup(c, groupId); - - // show that the event label has the correct event class - assertEquals(GroupImminentRemovalEvent.class, eventLabel.getEventClass()); - - // show that the event label has the correct primary key - assertEquals(GroupImminentRemovalEvent.class, eventLabel.getPrimaryKeyValue()); - - // show that the event label has the same id as its - // associated labeler - EventLabeler eventLabeler = GroupImminentRemovalEvent.getEventLabelerForGroup(); - assertEquals(eventLabeler.getEventLabelerId(), eventLabel.getLabelerId()); - - // show that two event labels with the same inputs are equal - EventLabel eventLabel2 = GroupImminentRemovalEvent.getEventLabelByGroup(c, groupId); - assertEquals(eventLabel, eventLabel2); - - // show that equal event labels have equal hash codes - assertEquals(eventLabel.hashCode(), eventLabel2.hashCode()); - - // show that two event labels with different inputs are not - // equal - assertTrue(eventLabels.add(eventLabel)); - } - - // precondition tests - - // if the group type id is null - ContractException contractException = assertThrows(ContractException.class, () -> GroupImminentRemovalEvent.getEventLabelByGroupType(c, null)); - assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); - - // if the group type id is unknown - contractException = assertThrows(ContractException.class, () -> GroupImminentRemovalEvent.getEventLabelByGroupType(c, TestGroupTypeId.getUnknownGroupTypeId())); - assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); - - }); - - } - - @Test - @UnitTestMethod(name = "getEventLabelerForGroup", args = {}) - public void testGetEventLabelerForGroup() { - - GroupsActionSupport.testConsumer(0, 3, 5, 2029538624275094851L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - - // create an event labeler - EventLabeler eventLabeler = GroupImminentRemovalEvent.getEventLabelerForGroup(); - - // show that the event labeler has the correct event class - assertEquals(GroupImminentRemovalEvent.class, eventLabeler.getEventClass()); - - // show that the event labeler produces the expected event label - - TestGroupTypeId testGroupTypeId = TestGroupTypeId.GROUP_TYPE_1; - for (int i = 0;i<10;i++) { - GroupId groupId = groupsDataManager.addGroup(testGroupTypeId); - - testGroupTypeId = testGroupTypeId.next(); - - // derive the expected event label for this event - EventLabel expectedEventLabel = GroupImminentRemovalEvent.getEventLabelByGroup(c, groupId); - - // show that the event label and event labeler have equal id - // values - assertEquals(expectedEventLabel.getLabelerId(), eventLabeler.getEventLabelerId()); - - // create an event - GroupImminentRemovalEvent event = new GroupImminentRemovalEvent(groupId); - - // show that the event labeler produces the correct event - // label - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - - assertEquals(expectedEventLabel, actualEventLabel); - - } - - }); - - - } - - @Test - @UnitTestMethod(name = "getEventLabelByGroupType", args = { SimulationContext.class, GroupTypeId.class }) - public void testGetEventLabelByGroupType() { - - - - GroupsActionSupport.testConsumer(0, 3, 5, 5740881055810962155L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - - Set> eventLabels = new LinkedHashSet<>(); - - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - - EventLabel eventLabel = GroupImminentRemovalEvent.getEventLabelByGroupType(c, testGroupTypeId); - - // show that the event label has the correct event class - assertEquals(GroupImminentRemovalEvent.class, eventLabel.getEventClass()); - - // show that the event label has the correct primary key - assertEquals(GroupImminentRemovalEvent.class, eventLabel.getPrimaryKeyValue()); - - // show that the event label has the same id as its - // associated labeler - EventLabeler eventLabeler = GroupImminentRemovalEvent.getEventLabelerForGroupType(groupsDataManager); - assertEquals(eventLabeler.getEventLabelerId(), eventLabel.getLabelerId()); - - // show that two event labels with the same inputs are equal - EventLabel eventLabel2 = GroupImminentRemovalEvent.getEventLabelByGroupType(c, testGroupTypeId); - assertEquals(eventLabel, eventLabel2); - - // show that equal event labels have equal hash codes - assertEquals(eventLabel.hashCode(), eventLabel2.hashCode()); - - // show that two event labels with different inputs are not - // equal - assertTrue(eventLabels.add(eventLabel)); - } - - // precondition tests - - // if the group type id is null - ContractException contractException = assertThrows(ContractException.class, () -> GroupImminentRemovalEvent.getEventLabelByGroupType(c, null)); - assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); - - // if the group type id is unknown - contractException = assertThrows(ContractException.class, () -> GroupImminentRemovalEvent.getEventLabelByGroupType(c, TestGroupTypeId.getUnknownGroupTypeId())); - assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); - - }); - } - - @Test - @UnitTestMethod(name = "getEventLabelerForGroupType", args = {}) - public void testGetEventLabelerForGroupType() { - - GroupsActionSupport.testConsumer(0, 3, 5, 1065941748199403338L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - - // create an event labeler - EventLabeler eventLabeler = GroupImminentRemovalEvent.getEventLabelerForGroupType(groupsDataManager); - - // show that the event labeler has the correct event class - assertEquals(GroupImminentRemovalEvent.class, eventLabeler.getEventClass()); - - // show that the event labeler produces the expected event label - - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - - // derive the expected event label for this event - EventLabel expectedEventLabel = GroupImminentRemovalEvent.getEventLabelByGroupType(c, testGroupTypeId); - - // show that the event label and event labeler have equal id - // values - assertEquals(expectedEventLabel.getLabelerId(), eventLabeler.getEventLabelerId()); - - GroupId groupId = groupsDataManager.addGroup(testGroupTypeId); - - - // create an event - GroupImminentRemovalEvent event = new GroupImminentRemovalEvent(groupId); - - // show that the event labeler produces the correct event - // label - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - - assertEquals(expectedEventLabel, actualEventLabel); - - } - - }); - - } - -} diff --git a/gcm3/src/test/java/plugins/groups/events/AT_GroupMembershipAdditionEvent.java b/gcm3/src/test/java/plugins/groups/events/AT_GroupMembershipAdditionEvent.java deleted file mode 100644 index edb354025..000000000 --- a/gcm3/src/test/java/plugins/groups/events/AT_GroupMembershipAdditionEvent.java +++ /dev/null @@ -1,545 +0,0 @@ -package plugins.groups.events; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -import javax.naming.Context; - -import org.junit.jupiter.api.Test; - -import nucleus.EventLabel; -import nucleus.EventLabeler; -import plugins.groups.datamanagers.GroupsDataManager; -import plugins.groups.support.GroupError; -import plugins.groups.support.GroupId; -import plugins.groups.support.GroupTypeId; -import plugins.groups.testsupport.GroupsActionSupport; -import plugins.groups.testsupport.TestGroupTypeId; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - -@UnitTest(target = GroupMembershipAdditionEvent.class) -public class AT_GroupMembershipAdditionEvent { - - @Test - @UnitTestConstructor(args = { PersonId.class, GroupId.class }) - public void testConstructor() { - // nothing to test - } - - @Test - @UnitTestMethod(name = "getGroupId", args = {}) - public void testGetGroupId() { - PersonId personId = new PersonId(12); - GroupId groupId = new GroupId(23); - GroupMembershipAdditionEvent groupMembershipAdditionEvent = new GroupMembershipAdditionEvent(personId, groupId); - assertEquals(groupId, groupMembershipAdditionEvent.getGroupId()); - } - - @Test - @UnitTestMethod(name = "getPersonId", args = {}) - public void testGetPersonId() { - PersonId personId = new PersonId(12); - GroupId groupId = new GroupId(23); - GroupMembershipAdditionEvent groupMembershipAdditionEvent = new GroupMembershipAdditionEvent(personId, groupId); - assertEquals(personId, groupMembershipAdditionEvent.getPersonId()); - } - - @Test - @UnitTestMethod(name = "getEventLabelByGroupAndPerson", args = { Context.class, GroupId.class, PersonId.class }) - public void testGetEventLabelByGroupAndPerson() { - - GroupsActionSupport.testConsumer(30, 3, 5, 298549072627101248L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - List groupIds = groupsDataManager.getGroupIds(); - - Set> eventLabels = new LinkedHashSet<>(); - - PersonId personId = new PersonId(0); - - for (GroupId groupId : groupIds) { - - EventLabel eventLabel = GroupMembershipAdditionEvent.getEventLabelByGroupAndPerson(c, groupId, personId); - - // show that the event label has the correct event class - assertEquals(GroupMembershipAdditionEvent.class, eventLabel.getEventClass()); - - // show that the event label has the correct primary key - assertEquals(GroupMembershipAdditionEvent.class, eventLabel.getPrimaryKeyValue()); - - // show that the event label has the same id as its - // associated labeler - EventLabeler eventLabeler = GroupMembershipAdditionEvent.getEventLabelerForGroupAndPerson(); - assertEquals(eventLabeler.getEventLabelerId(), eventLabel.getLabelerId()); - - // show that two event labels with the same inputs are equal - EventLabel eventLabel2 = GroupMembershipAdditionEvent.getEventLabelByGroupAndPerson(c, groupId, personId); - assertEquals(eventLabel, eventLabel2); - - // show that equal event labels have equal hash codes - assertEquals(eventLabel.hashCode(), eventLabel2.hashCode()); - - // show that two event labels with different inputs are not - // equal - assertTrue(eventLabels.add(eventLabel)); - } - - // precondition tests - - // if the group id is null - ContractException contractException = assertThrows(ContractException.class, () -> GroupMembershipAdditionEvent.getEventLabelByGroupAndPerson(c, null, personId)); - assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); - - // if the group id is unknown - contractException = assertThrows(ContractException.class, () -> GroupMembershipAdditionEvent.getEventLabelByGroupAndPerson(c, new GroupId(100000), personId)); - assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); - - // if the person id is null - contractException = assertThrows(ContractException.class, () -> GroupMembershipAdditionEvent.getEventLabelByGroupAndPerson(c, new GroupId(0), null)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - - // if the group id is unknown - contractException = assertThrows(ContractException.class, () -> GroupMembershipAdditionEvent.getEventLabelByGroupAndPerson(c, new GroupId(0), new PersonId(10000))); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - - }); - } - - @Test - @UnitTestMethod(name = "getEventLabelerForGroupAndPerson", args = {}) - public void testGetEventLabelerForGroupAndPerson() { - - GroupsActionSupport.testConsumer(30, 3, 5, 4452567174321509486L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - List groupIds = groupsDataManager.getGroupIds(); - - // create an event labeler - EventLabeler eventLabeler = GroupMembershipAdditionEvent.getEventLabelerForGroupAndPerson(); - - // show that the event labeler has the correct event class - assertEquals(GroupMembershipAdditionEvent.class, eventLabeler.getEventClass()); - - // show that the event labeler produces the expected event label - - PersonId personId = new PersonId(0); - for (GroupId groupId : groupIds) { - - // derive the expected event label for this event - EventLabel expectedEventLabel = GroupMembershipAdditionEvent.getEventLabelByGroupAndPerson(c, groupId, personId); - - // show that the event label and event labeler have equal id - // values - assertEquals(expectedEventLabel.getLabelerId(), eventLabeler.getEventLabelerId()); - - // create an event - GroupMembershipAdditionEvent event = new GroupMembershipAdditionEvent(personId, groupId); - - // show that the event labeler produces the correct event - // label - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - - assertEquals(expectedEventLabel, actualEventLabel); - - } - - }); - - } - - @Test - @UnitTestMethod(name = "getEventLabelByGroup", args = { Context.class, GroupId.class }) - public void testGetEventLabelByGroup() { - - GroupsActionSupport.testConsumer(0, 3, 5, 8484038291544974628L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - Set> eventLabels = new LinkedHashSet<>(); - TestGroupTypeId testGroupTypeId = TestGroupTypeId.GROUP_TYPE_1; - for (int i = 0; i < 10; i++) { - GroupId groupId = groupsDataManager.addGroup(testGroupTypeId); - - testGroupTypeId = testGroupTypeId.next(); - - EventLabel eventLabel = GroupMembershipAdditionEvent.getEventLabelByGroup(c, groupId); - - // show that the event label has the correct event class - assertEquals(GroupMembershipAdditionEvent.class, eventLabel.getEventClass()); - - // show that the event label has the correct primary key - assertEquals(GroupMembershipAdditionEvent.class, eventLabel.getPrimaryKeyValue()); - - // show that the event label has the same id as its - // associated labeler - EventLabeler eventLabeler = GroupMembershipAdditionEvent.getEventLabelerForGroup(); - assertEquals(eventLabeler.getEventLabelerId(), eventLabel.getLabelerId()); - - // show that two event labels with the same inputs are equal - EventLabel eventLabel2 = GroupMembershipAdditionEvent.getEventLabelByGroup(c, groupId); - assertEquals(eventLabel, eventLabel2); - - // show that equal event labels have equal hash codes - assertEquals(eventLabel.hashCode(), eventLabel2.hashCode()); - - // show that two event labels with different inputs are not - // equal - assertTrue(eventLabels.add(eventLabel)); - } - - // precondition tests - - // if the group type id is null - ContractException contractException = assertThrows(ContractException.class, () -> GroupMembershipAdditionEvent.getEventLabelByGroup(c, null)); - assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); - - // if the group type id is unknown - contractException = assertThrows(ContractException.class, () -> GroupMembershipAdditionEvent.getEventLabelByGroup(c, new GroupId(10000))); - assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); - - }); - - } - - @Test - @UnitTestMethod(name = "getEventLabelerForGroup", args = {}) - public void testGetEventLabelerForGroup() { - - GroupsActionSupport.testConsumer(10, 3, 5, 3313438051476160164L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - - // create an event labeler - EventLabeler eventLabeler = GroupMembershipAdditionEvent.getEventLabelerForGroup(); - - // show that the event labeler has the correct event class - assertEquals(GroupMembershipAdditionEvent.class, eventLabeler.getEventClass()); - - // show that the event labeler produces the expected event label - - TestGroupTypeId testGroupTypeId = TestGroupTypeId.GROUP_TYPE_1; - for (int i = 0; i < 10; i++) { - GroupId groupId = groupsDataManager.addGroup(testGroupTypeId); - - testGroupTypeId = testGroupTypeId.next(); - - // derive the expected event label for this event - EventLabel expectedEventLabel = GroupMembershipAdditionEvent.getEventLabelByGroup(c, groupId); - - // show that the event label and event labeler have equal id - // values - assertEquals(expectedEventLabel.getLabelerId(), eventLabeler.getEventLabelerId()); - - // create an event - PersonId personId = new PersonId(0); - GroupMembershipAdditionEvent event = new GroupMembershipAdditionEvent(personId, groupId); - - // show that the event labeler produces the correct event - // label - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - - assertEquals(expectedEventLabel, actualEventLabel); - - } - - }); - - } - - @Test - @UnitTestMethod(name = "getEventLabelByPerson", args = { Context.class, PersonId.class }) - public void testGetEventLabelByPerson() { - - GroupsActionSupport.testConsumer(10, 3, 5, 5181120908681821960L, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - List people = peopleDataManager.getPeople(); - Set> eventLabels = new LinkedHashSet<>(); - - for (PersonId personId : people) { - - EventLabel eventLabel = GroupMembershipAdditionEvent.getEventLabelByPerson(c, personId); - - // show that the event label has the correct event class - assertEquals(GroupMembershipAdditionEvent.class, eventLabel.getEventClass()); - - // show that the event label has the correct primary key - assertEquals(GroupMembershipAdditionEvent.class, eventLabel.getPrimaryKeyValue()); - - // show that the event label has the same id as its - // associated labeler - EventLabeler eventLabeler = GroupMembershipAdditionEvent.getEventLabelerForPerson(); - assertEquals(eventLabeler.getEventLabelerId(), eventLabel.getLabelerId()); - - // show that two event labels with the same inputs are equal - EventLabel eventLabel2 = GroupMembershipAdditionEvent.getEventLabelByPerson(c, personId); - assertEquals(eventLabel, eventLabel2); - - // show that equal event labels have equal hash codes - assertEquals(eventLabel.hashCode(), eventLabel2.hashCode()); - - // show that two event labels with different inputs are not - // equal - assertTrue(eventLabels.add(eventLabel)); - } - - // precondition tests - - // if the group type id is null - ContractException contractException = assertThrows(ContractException.class, () -> GroupMembershipAdditionEvent.getEventLabelByPerson(c, null)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - - // if the group type id is unknown - contractException = assertThrows(ContractException.class, () -> GroupMembershipAdditionEvent.getEventLabelByPerson(c, new PersonId(10000))); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - - }); - } - - @Test - @UnitTestMethod(name = "getEventLabelerForPerson", args = {}) - public void testGetEventLabelerForPerson() { - - GroupsActionSupport.testConsumer(30, 3, 5, 7591006487215638552L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - List groupIds = groupsDataManager.getGroupIds(); - - // create an event labeler - EventLabeler eventLabeler = GroupMembershipAdditionEvent.getEventLabelerForPerson(); - - // show that the event labeler has the correct event class - assertEquals(GroupMembershipAdditionEvent.class, eventLabeler.getEventClass()); - - // show that the event labeler produces the expected event label - - PersonId personId = new PersonId(0); - for (GroupId groupId : groupIds) { - - // derive the expected event label for this event - EventLabel expectedEventLabel = GroupMembershipAdditionEvent.getEventLabelByPerson(c, personId); - - // show that the event label and event labeler have equal id - // values - assertEquals(expectedEventLabel.getLabelerId(), eventLabeler.getEventLabelerId()); - - // create an event - GroupMembershipAdditionEvent event = new GroupMembershipAdditionEvent(personId, groupId); - - // show that the event labeler produces the correct event - // label - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - - assertEquals(expectedEventLabel, actualEventLabel); - - } - - }); - - } - - @Test - @UnitTestMethod(name = "getEventLabelByGroupTypeAndPerson", args = { Context.class, GroupTypeId.class, PersonId.class }) - public void testGetEventLabelByGroupTypeAndPerson() { - - GroupsActionSupport.testConsumer(10, 3, 5, 2396297410749360025L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - Set> eventLabels = new LinkedHashSet<>(); - - PersonId personId = new PersonId(0); - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - - EventLabel eventLabel = GroupMembershipAdditionEvent.getEventLabelByGroupTypeAndPerson(c, testGroupTypeId, personId); - - // show that the event label has the correct event class - assertEquals(GroupMembershipAdditionEvent.class, eventLabel.getEventClass()); - - // show that the event label has the correct primary key - assertEquals(GroupMembershipAdditionEvent.class, eventLabel.getPrimaryKeyValue()); - - // show that the event label has the same id as its - // associated labeler - EventLabeler eventLabeler = GroupMembershipAdditionEvent.getEventLabelerForGroupTypeAndPerson(groupsDataManager); - assertEquals(eventLabeler.getEventLabelerId(), eventLabel.getLabelerId()); - - // show that two event labels with the same inputs are equal - EventLabel eventLabel2 = GroupMembershipAdditionEvent.getEventLabelByGroupTypeAndPerson(c, testGroupTypeId, personId); - assertEquals(eventLabel, eventLabel2); - - // show that equal event labels have equal hash codes - assertEquals(eventLabel.hashCode(), eventLabel2.hashCode()); - - // show that two event labels with different inputs are not - // equal - assertTrue(eventLabels.add(eventLabel)); - } - - // precondition tests - - // if the group type id is null - ContractException contractException = assertThrows(ContractException.class, () -> GroupMembershipAdditionEvent.getEventLabelByGroupTypeAndPerson(c, null, new PersonId(0))); - assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); - - // if the group type id is unknown - contractException = assertThrows(ContractException.class, - () -> GroupMembershipAdditionEvent.getEventLabelByGroupTypeAndPerson(c, TestGroupTypeId.getUnknownGroupTypeId(), new PersonId(0))); - assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); - - // if the person id is null - contractException = assertThrows(ContractException.class, () -> GroupMembershipAdditionEvent.getEventLabelByGroupTypeAndPerson(c, TestGroupTypeId.GROUP_TYPE_1, null)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - - // if the person id is unknown - contractException = assertThrows(ContractException.class, - () -> GroupMembershipAdditionEvent.getEventLabelByGroupTypeAndPerson(c, TestGroupTypeId.GROUP_TYPE_1, new PersonId(1000000))); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - - - }); - } - - @Test - @UnitTestMethod(name = "getEventLabelerForGroupTypeAndPerson", args = {}) - public void testGetEventLabelerForGroupTypeAndPerson() { - - GroupsActionSupport.testConsumer(30, 3, 5, 944196534930517005L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - List groupIds = groupsDataManager.getGroupIds(); - - // create an event labeler - EventLabeler eventLabeler = GroupMembershipAdditionEvent.getEventLabelerForGroupTypeAndPerson(groupsDataManager); - - // show that the event labeler has the correct event class - assertEquals(GroupMembershipAdditionEvent.class, eventLabeler.getEventClass()); - - // show that the event labeler produces the expected event label - - PersonId personId = new PersonId(0); - for (GroupId groupId : groupIds) { - - GroupTypeId groupTypeId = groupsDataManager.getGroupType(groupId); - - // derive the expected event label for this event - EventLabel expectedEventLabel = GroupMembershipAdditionEvent.getEventLabelByGroupTypeAndPerson(c, groupTypeId, personId); - - // show that the event label and event labeler have equal id - // values - assertEquals(expectedEventLabel.getLabelerId(), eventLabeler.getEventLabelerId()); - - // create an event - GroupMembershipAdditionEvent event = new GroupMembershipAdditionEvent(personId, groupId); - - // show that the event labeler produces the correct event - // label - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - - assertEquals(expectedEventLabel, actualEventLabel); - - } - - }); - - } - - @Test - @UnitTestMethod(name = "getEventLabelByGroupType", args = { Context.class, GroupTypeId.class }) - public void testGetEventLabelByGroupType() { - - GroupsActionSupport.testConsumer(0, 3, 5, 4360946626249599442L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - Set> eventLabels = new LinkedHashSet<>(); - - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - - EventLabel eventLabel = GroupMembershipAdditionEvent.getEventLabelByGroupType(c, testGroupTypeId); - - // show that the event label has the correct event class - assertEquals(GroupMembershipAdditionEvent.class, eventLabel.getEventClass()); - - // show that the event label has the correct primary key - assertEquals(GroupMembershipAdditionEvent.class, eventLabel.getPrimaryKeyValue()); - - // show that the event label has the same id as its - // associated labeler - EventLabeler eventLabeler = GroupMembershipAdditionEvent.getEventLabelerForGroupType(groupsDataManager); - assertEquals(eventLabeler.getEventLabelerId(), eventLabel.getLabelerId()); - - // show that two event labels with the same inputs are equal - EventLabel eventLabel2 = GroupMembershipAdditionEvent.getEventLabelByGroupType(c, testGroupTypeId); - assertEquals(eventLabel, eventLabel2); - - // show that equal event labels have equal hash codes - assertEquals(eventLabel.hashCode(), eventLabel2.hashCode()); - - // show that two event labels with different inputs are not - // equal - assertTrue(eventLabels.add(eventLabel)); - } - - // precondition tests - - // if the group type id is null - ContractException contractException = assertThrows(ContractException.class, () -> GroupMembershipAdditionEvent.getEventLabelByGroupType(c, null)); - assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); - - // if the group type id is unknown - contractException = assertThrows(ContractException.class, () -> GroupMembershipAdditionEvent.getEventLabelByGroupType(c, TestGroupTypeId.getUnknownGroupTypeId())); - assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); - - }); - - } - - @Test - @UnitTestMethod(name = "getEventLabelerForGroupType", args = {}) - public void testGetEventLabelerForGroupType() { - - GroupsActionSupport.testConsumer(10, 3, 5, 825213654032168954L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - - // create an event labeler - EventLabeler eventLabeler = GroupMembershipAdditionEvent.getEventLabelerForGroupType(groupsDataManager); - - // show that the event labeler has the correct event class - assertEquals(GroupMembershipAdditionEvent.class, eventLabeler.getEventClass()); - - // show that the event labeler produces the expected event label - - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - - // derive the expected event label for this event - EventLabel expectedEventLabel = GroupMembershipAdditionEvent.getEventLabelByGroupType(c, testGroupTypeId); - - // show that the event label and event labeler have equal id - // values - assertEquals(expectedEventLabel.getLabelerId(), eventLabeler.getEventLabelerId()); - - GroupId groupId = groupsDataManager.addGroup(testGroupTypeId); - - PersonId personId = new PersonId(0); - // create an event - GroupMembershipAdditionEvent event = new GroupMembershipAdditionEvent(personId, groupId); - - // show that the event labeler produces the correct event - // label - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - - assertEquals(expectedEventLabel, actualEventLabel); - - } - - }); - - } -} diff --git a/gcm3/src/test/java/plugins/groups/events/AT_GroupMembershipRemovalEvent.java b/gcm3/src/test/java/plugins/groups/events/AT_GroupMembershipRemovalEvent.java deleted file mode 100644 index 0bd50bb7b..000000000 --- a/gcm3/src/test/java/plugins/groups/events/AT_GroupMembershipRemovalEvent.java +++ /dev/null @@ -1,546 +0,0 @@ -package plugins.groups.events; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -import javax.naming.Context; - -import org.junit.jupiter.api.Test; - -import nucleus.EventLabel; -import nucleus.EventLabeler; -import plugins.groups.datamanagers.GroupsDataManager; -import plugins.groups.support.GroupError; -import plugins.groups.support.GroupId; -import plugins.groups.support.GroupTypeId; -import plugins.groups.testsupport.GroupsActionSupport; -import plugins.groups.testsupport.TestGroupTypeId; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - -@UnitTest(target = GroupMembershipRemovalEvent.class) -public class AT_GroupMembershipRemovalEvent { - - @Test - @UnitTestConstructor(args = { PersonId.class, GroupId.class }) - public void testConstructor() { - // nothing to test - } - - @Test - @UnitTestMethod(name = "getGroupId", args = {}) - public void testGetGroupId() { - PersonId personId = new PersonId(12); - GroupId groupId = new GroupId(23); - GroupMembershipRemovalEvent GroupMembershipRemovalEvent = new GroupMembershipRemovalEvent(personId, groupId); - assertEquals(groupId, GroupMembershipRemovalEvent.getGroupId()); - } - - @Test - @UnitTestMethod(name = "getPersonId", args = {}) - public void testGetPersonId() { - PersonId personId = new PersonId(12); - GroupId groupId = new GroupId(23); - GroupMembershipRemovalEvent GroupMembershipRemovalEvent = new GroupMembershipRemovalEvent(personId, groupId); - assertEquals(personId, GroupMembershipRemovalEvent.getPersonId()); - } - - @Test - @UnitTestMethod(name = "getEventLabelByGroupAndPerson", args = { Context.class, GroupId.class, PersonId.class }) - public void testGetEventLabelByGroupAndPerson() { - - GroupsActionSupport.testConsumer(30, 3, 5, 298549072627101248L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - List groupIds = groupsDataManager.getGroupIds(); - - Set> eventLabels = new LinkedHashSet<>(); - - PersonId personId = new PersonId(0); - - for (GroupId groupId : groupIds) { - - EventLabel eventLabel = GroupMembershipRemovalEvent.getEventLabelByGroupAndPerson(c, groupId, personId); - - // show that the event label has the correct event class - assertEquals(GroupMembershipRemovalEvent.class, eventLabel.getEventClass()); - - // show that the event label has the correct primary key - assertEquals(GroupMembershipRemovalEvent.class, eventLabel.getPrimaryKeyValue()); - - // show that the event label has the same id as its - // associated labeler - EventLabeler eventLabeler = GroupMembershipRemovalEvent.getEventLabelerForGroupAndPerson(); - assertEquals(eventLabeler.getEventLabelerId(), eventLabel.getLabelerId()); - - // show that two event labels with the same inputs are equal - EventLabel eventLabel2 = GroupMembershipRemovalEvent.getEventLabelByGroupAndPerson(c, groupId, personId); - assertEquals(eventLabel, eventLabel2); - - // show that equal event labels have equal hash codes - assertEquals(eventLabel.hashCode(), eventLabel2.hashCode()); - - // show that two event labels with different inputs are not - // equal - assertTrue(eventLabels.add(eventLabel)); - } - - // precondition tests - - // if the group id is null - ContractException contractException = assertThrows(ContractException.class, () -> GroupMembershipRemovalEvent.getEventLabelByGroupAndPerson(c, null, personId)); - assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); - - // if the group id is unknown - contractException = assertThrows(ContractException.class, () -> GroupMembershipRemovalEvent.getEventLabelByGroupAndPerson(c, new GroupId(100000), personId)); - assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); - - // if the person id is null - contractException = assertThrows(ContractException.class, () -> GroupMembershipRemovalEvent.getEventLabelByGroupAndPerson(c, new GroupId(0), null)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - - // if the group id is unknown - contractException = assertThrows(ContractException.class, () -> GroupMembershipRemovalEvent.getEventLabelByGroupAndPerson(c, new GroupId(0), new PersonId(10000))); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - - }); - } - - @Test - @UnitTestMethod(name = "getEventLabelerForGroupAndPerson", args = {}) - public void testGetEventLabelerForGroupAndPerson() { - - GroupsActionSupport.testConsumer(30, 3, 5, 4452567174321509486L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - List groupIds = groupsDataManager.getGroupIds(); - - // create an event labeler - EventLabeler eventLabeler = GroupMembershipRemovalEvent.getEventLabelerForGroupAndPerson(); - - // show that the event labeler has the correct event class - assertEquals(GroupMembershipRemovalEvent.class, eventLabeler.getEventClass()); - - // show that the event labeler produces the expected event label - - PersonId personId = new PersonId(0); - for (GroupId groupId : groupIds) { - - // derive the expected event label for this event - EventLabel expectedEventLabel = GroupMembershipRemovalEvent.getEventLabelByGroupAndPerson(c, groupId, personId); - - // show that the event label and event labeler have equal id - // values - assertEquals(expectedEventLabel.getLabelerId(), eventLabeler.getEventLabelerId()); - - // create an event - GroupMembershipRemovalEvent event = new GroupMembershipRemovalEvent(personId, groupId); - - // show that the event labeler produces the correct event - // label - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - - assertEquals(expectedEventLabel, actualEventLabel); - - } - - }); - - } - - @Test - @UnitTestMethod(name = "getEventLabelByGroup", args = { Context.class, GroupId.class }) - public void testGetEventLabelByGroup() { - - GroupsActionSupport.testConsumer(0, 3, 5, 8484038291544974628L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - Set> eventLabels = new LinkedHashSet<>(); - TestGroupTypeId testGroupTypeId = TestGroupTypeId.GROUP_TYPE_1; - for (int i = 0; i < 10; i++) { - GroupId groupId = groupsDataManager.addGroup(testGroupTypeId); - - - testGroupTypeId = testGroupTypeId.next(); - - EventLabel eventLabel = GroupMembershipRemovalEvent.getEventLabelByGroup(c, groupId); - - // show that the event label has the correct event class - assertEquals(GroupMembershipRemovalEvent.class, eventLabel.getEventClass()); - - // show that the event label has the correct primary key - assertEquals(GroupMembershipRemovalEvent.class, eventLabel.getPrimaryKeyValue()); - - // show that the event label has the same id as its - // associated labeler - EventLabeler eventLabeler = GroupMembershipRemovalEvent.getEventLabelerForGroup(); - assertEquals(eventLabeler.getEventLabelerId(), eventLabel.getLabelerId()); - - // show that two event labels with the same inputs are equal - EventLabel eventLabel2 = GroupMembershipRemovalEvent.getEventLabelByGroup(c, groupId); - assertEquals(eventLabel, eventLabel2); - - // show that equal event labels have equal hash codes - assertEquals(eventLabel.hashCode(), eventLabel2.hashCode()); - - // show that two event labels with different inputs are not - // equal - assertTrue(eventLabels.add(eventLabel)); - } - - // precondition tests - - // if the group type id is null - ContractException contractException = assertThrows(ContractException.class, () -> GroupMembershipRemovalEvent.getEventLabelByGroup(c, null)); - assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); - - // if the group type id is unknown - contractException = assertThrows(ContractException.class, () -> GroupMembershipRemovalEvent.getEventLabelByGroup(c, new GroupId(10000))); - assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); - - }); - - } - - @Test - @UnitTestMethod(name = "getEventLabelerForGroup", args = {}) - public void testGetEventLabelerForGroup() { - - GroupsActionSupport.testConsumer(10, 3, 5, 3313438051476160164L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - - // create an event labeler - EventLabeler eventLabeler = GroupMembershipRemovalEvent.getEventLabelerForGroup(); - - // show that the event labeler has the correct event class - assertEquals(GroupMembershipRemovalEvent.class, eventLabeler.getEventClass()); - - // show that the event labeler produces the expected event label - - TestGroupTypeId testGroupTypeId = TestGroupTypeId.GROUP_TYPE_1; - for (int i = 0; i < 10; i++) { - GroupId groupId = groupsDataManager.addGroup(testGroupTypeId); - - testGroupTypeId = testGroupTypeId.next(); - - // derive the expected event label for this event - EventLabel expectedEventLabel = GroupMembershipRemovalEvent.getEventLabelByGroup(c, groupId); - - // show that the event label and event labeler have equal id - // values - assertEquals(expectedEventLabel.getLabelerId(), eventLabeler.getEventLabelerId()); - - // create an event - PersonId personId = new PersonId(0); - GroupMembershipRemovalEvent event = new GroupMembershipRemovalEvent(personId, groupId); - - // show that the event labeler produces the correct event - // label - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - - assertEquals(expectedEventLabel, actualEventLabel); - - } - - }); - - } - - @Test - @UnitTestMethod(name = "getEventLabelByPerson", args = { Context.class, PersonId.class }) - public void testGetEventLabelByPerson() { - - GroupsActionSupport.testConsumer(10, 3, 5, 5181120908681821960L, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - List people = peopleDataManager.getPeople(); - Set> eventLabels = new LinkedHashSet<>(); - - for (PersonId personId : people) { - - EventLabel eventLabel = GroupMembershipRemovalEvent.getEventLabelByPerson(c, personId); - - // show that the event label has the correct event class - assertEquals(GroupMembershipRemovalEvent.class, eventLabel.getEventClass()); - - // show that the event label has the correct primary key - assertEquals(GroupMembershipRemovalEvent.class, eventLabel.getPrimaryKeyValue()); - - // show that the event label has the same id as its - // associated labeler - EventLabeler eventLabeler = GroupMembershipRemovalEvent.getEventLabelerForPerson(); - assertEquals(eventLabeler.getEventLabelerId(), eventLabel.getLabelerId()); - - // show that two event labels with the same inputs are equal - EventLabel eventLabel2 = GroupMembershipRemovalEvent.getEventLabelByPerson(c, personId); - assertEquals(eventLabel, eventLabel2); - - // show that equal event labels have equal hash codes - assertEquals(eventLabel.hashCode(), eventLabel2.hashCode()); - - // show that two event labels with different inputs are not - // equal - assertTrue(eventLabels.add(eventLabel)); - } - - // precondition tests - - // if the group type id is null - ContractException contractException = assertThrows(ContractException.class, () -> GroupMembershipRemovalEvent.getEventLabelByPerson(c, null)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - - // if the group type id is unknown - contractException = assertThrows(ContractException.class, () -> GroupMembershipRemovalEvent.getEventLabelByPerson(c, new PersonId(10000))); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - - }); - } - - @Test - @UnitTestMethod(name = "getEventLabelerForPerson", args = {}) - public void testGetEventLabelerForPerson() { - - GroupsActionSupport.testConsumer(30, 3, 5, 7591006487215638552L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - List groupIds = groupsDataManager.getGroupIds(); - - // create an event labeler - EventLabeler eventLabeler = GroupMembershipRemovalEvent.getEventLabelerForPerson(); - - // show that the event labeler has the correct event class - assertEquals(GroupMembershipRemovalEvent.class, eventLabeler.getEventClass()); - - // show that the event labeler produces the expected event label - - PersonId personId = new PersonId(0); - for (GroupId groupId : groupIds) { - - // derive the expected event label for this event - EventLabel expectedEventLabel = GroupMembershipRemovalEvent.getEventLabelByPerson(c, personId); - - // show that the event label and event labeler have equal id - // values - assertEquals(expectedEventLabel.getLabelerId(), eventLabeler.getEventLabelerId()); - - // create an event - GroupMembershipRemovalEvent event = new GroupMembershipRemovalEvent(personId, groupId); - - // show that the event labeler produces the correct event - // label - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - - assertEquals(expectedEventLabel, actualEventLabel); - - } - - }); - - } - - @Test - @UnitTestMethod(name = "getEventLabelByGroupTypeAndPerson", args = { Context.class, GroupTypeId.class, PersonId.class }) - public void testGetEventLabelByGroupTypeAndPerson() { - - GroupsActionSupport.testConsumer(10, 3, 5, 2396297410749360025L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - Set> eventLabels = new LinkedHashSet<>(); - - PersonId personId = new PersonId(0); - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - - EventLabel eventLabel = GroupMembershipRemovalEvent.getEventLabelByGroupTypeAndPerson(c, testGroupTypeId, personId); - - // show that the event label has the correct event class - assertEquals(GroupMembershipRemovalEvent.class, eventLabel.getEventClass()); - - // show that the event label has the correct primary key - assertEquals(GroupMembershipRemovalEvent.class, eventLabel.getPrimaryKeyValue()); - - // show that the event label has the same id as its - // associated labeler - EventLabeler eventLabeler = GroupMembershipRemovalEvent.getEventLabelerForGroupTypeAndPerson(groupsDataManager); - assertEquals(eventLabeler.getEventLabelerId(), eventLabel.getLabelerId()); - - // show that two event labels with the same inputs are equal - EventLabel eventLabel2 = GroupMembershipRemovalEvent.getEventLabelByGroupTypeAndPerson(c, testGroupTypeId, personId); - assertEquals(eventLabel, eventLabel2); - - // show that equal event labels have equal hash codes - assertEquals(eventLabel.hashCode(), eventLabel2.hashCode()); - - // show that two event labels with different inputs are not - // equal - assertTrue(eventLabels.add(eventLabel)); - } - - // precondition tests - - // if the group type id is null - ContractException contractException = assertThrows(ContractException.class, () -> GroupMembershipRemovalEvent.getEventLabelByGroupTypeAndPerson(c, null, new PersonId(0))); - assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); - - // if the group type id is unknown - contractException = assertThrows(ContractException.class, - () -> GroupMembershipRemovalEvent.getEventLabelByGroupTypeAndPerson(c, TestGroupTypeId.getUnknownGroupTypeId(), new PersonId(0))); - assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); - - // if the person id is null - contractException = assertThrows(ContractException.class, () -> GroupMembershipRemovalEvent.getEventLabelByGroupTypeAndPerson(c, TestGroupTypeId.GROUP_TYPE_1, null)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - - // if the person id is unknown - contractException = assertThrows(ContractException.class, - () -> GroupMembershipRemovalEvent.getEventLabelByGroupTypeAndPerson(c, TestGroupTypeId.GROUP_TYPE_1, new PersonId(1000000))); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - - - }); - } - - @Test - @UnitTestMethod(name = "getEventLabelerForGroupTypeAndPerson", args = {}) - public void testGetEventLabelerForGroupTypeAndPerson() { - - GroupsActionSupport.testConsumer(30, 3, 5, 944196534930517005L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - - List groupIds = groupsDataManager.getGroupIds(); - - // create an event labeler - EventLabeler eventLabeler = GroupMembershipRemovalEvent.getEventLabelerForGroupTypeAndPerson(groupsDataManager); - - // show that the event labeler has the correct event class - assertEquals(GroupMembershipRemovalEvent.class, eventLabeler.getEventClass()); - - // show that the event labeler produces the expected event label - - PersonId personId = new PersonId(0); - for (GroupId groupId : groupIds) { - - GroupTypeId groupTypeId = groupsDataManager.getGroupType(groupId); - - // derive the expected event label for this event - EventLabel expectedEventLabel = GroupMembershipRemovalEvent.getEventLabelByGroupTypeAndPerson(c, groupTypeId, personId); - - // show that the event label and event labeler have equal id - // values - assertEquals(expectedEventLabel.getLabelerId(), eventLabeler.getEventLabelerId()); - - // create an event - GroupMembershipRemovalEvent event = new GroupMembershipRemovalEvent(personId, groupId); - - // show that the event labeler produces the correct event - // label - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - - assertEquals(expectedEventLabel, actualEventLabel); - - } - - }); - - } - - @Test - @UnitTestMethod(name = "getEventLabelByGroupType", args = { Context.class, GroupTypeId.class }) - public void testGetEventLabelByGroupType() { - - GroupsActionSupport.testConsumer(0, 3, 5, 4360946626249599442L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - Set> eventLabels = new LinkedHashSet<>(); - - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - - EventLabel eventLabel = GroupMembershipRemovalEvent.getEventLabelByGroupType(c, testGroupTypeId); - - // show that the event label has the correct event class - assertEquals(GroupMembershipRemovalEvent.class, eventLabel.getEventClass()); - - // show that the event label has the correct primary key - assertEquals(GroupMembershipRemovalEvent.class, eventLabel.getPrimaryKeyValue()); - - // show that the event label has the same id as its - // associated labeler - EventLabeler eventLabeler = GroupMembershipRemovalEvent.getEventLabelerForGroupType(groupsDataManager); - assertEquals(eventLabeler.getEventLabelerId(), eventLabel.getLabelerId()); - - // show that two event labels with the same inputs are equal - EventLabel eventLabel2 = GroupMembershipRemovalEvent.getEventLabelByGroupType(c, testGroupTypeId); - assertEquals(eventLabel, eventLabel2); - - // show that equal event labels have equal hash codes - assertEquals(eventLabel.hashCode(), eventLabel2.hashCode()); - - // show that two event labels with different inputs are not - // equal - assertTrue(eventLabels.add(eventLabel)); - } - - // precondition tests - - // if the group type id is null - ContractException contractException = assertThrows(ContractException.class, () -> GroupMembershipRemovalEvent.getEventLabelByGroupType(c, null)); - assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); - - // if the group type id is unknown - contractException = assertThrows(ContractException.class, () -> GroupMembershipRemovalEvent.getEventLabelByGroupType(c, TestGroupTypeId.getUnknownGroupTypeId())); - assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); - - }); - - } - - @Test - @UnitTestMethod(name = "getEventLabelerForGroupType", args = {}) - public void testGetEventLabelerForGroupType() { - - GroupsActionSupport.testConsumer(10, 3, 5, 825213654032168954L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - - // create an event labeler - EventLabeler eventLabeler = GroupMembershipRemovalEvent.getEventLabelerForGroupType(groupsDataManager); - - // show that the event labeler has the correct event class - assertEquals(GroupMembershipRemovalEvent.class, eventLabeler.getEventClass()); - - // show that the event labeler produces the expected event label - - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - - // derive the expected event label for this event - EventLabel expectedEventLabel = GroupMembershipRemovalEvent.getEventLabelByGroupType(c, testGroupTypeId); - - // show that the event label and event labeler have equal id - // values - assertEquals(expectedEventLabel.getLabelerId(), eventLabeler.getEventLabelerId()); - - GroupId groupId = groupsDataManager.addGroup(testGroupTypeId); - - PersonId personId = new PersonId(0); - // create an event - GroupMembershipRemovalEvent event = new GroupMembershipRemovalEvent(personId, groupId); - - // show that the event labeler produces the correct event - // label - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - - assertEquals(expectedEventLabel, actualEventLabel); - - } - - }); - - } -} diff --git a/gcm3/src/test/java/plugins/groups/events/AT_GroupPropertyUpdateEvent.java b/gcm3/src/test/java/plugins/groups/events/AT_GroupPropertyUpdateEvent.java deleted file mode 100644 index 5bddde729..000000000 --- a/gcm3/src/test/java/plugins/groups/events/AT_GroupPropertyUpdateEvent.java +++ /dev/null @@ -1,495 +0,0 @@ -package plugins.groups.events; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -import javax.naming.Context; - -import org.junit.jupiter.api.Test; - -import nucleus.EventLabel; -import nucleus.EventLabeler; -import plugins.groups.datamanagers.GroupsDataManager; -import plugins.groups.support.GroupError; -import plugins.groups.support.GroupId; -import plugins.groups.support.GroupPropertyId; -import plugins.groups.support.GroupTypeId; -import plugins.groups.testsupport.GroupsActionSupport; -import plugins.groups.testsupport.TestGroupPropertyId; -import plugins.groups.testsupport.TestGroupTypeId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - -@UnitTest(target = GroupPropertyUpdateEvent.class) -public class AT_GroupPropertyUpdateEvent { - - @Test - @UnitTestConstructor(args = { GroupId.class, GroupPropertyId.class, Object.class, Object.class }) - public void testConstructor() { - // nothing to test - } - - @Test - @UnitTestMethod(name = "getCurrentPropertyValue", args = {}) - public void testGetCurrentPropertyValue() { - GroupId groupId = new GroupId(30); - GroupPropertyId groupPropertyId = TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK; - Object previousValue = "previous"; - Object currentValue = "current"; - GroupPropertyUpdateEvent groupPropertyUpdateEvent = new GroupPropertyUpdateEvent(groupId, groupPropertyId, previousValue, currentValue); - assertEquals(currentValue, groupPropertyUpdateEvent.getCurrentPropertyValue()); - } - - @Test - @UnitTestMethod(name = "getGroupId", args = {}) - public void testGetGroupId() { - GroupId groupId = new GroupId(30); - GroupPropertyId groupPropertyId = TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK; - Object previousValue = "previous"; - Object currentValue = "current"; - GroupPropertyUpdateEvent groupPropertyUpdateEvent = new GroupPropertyUpdateEvent(groupId, groupPropertyId, previousValue, currentValue); - assertEquals(groupId, groupPropertyUpdateEvent.getGroupId()); - } - - @Test - @UnitTestMethod(name = "getGroupPropertyId", args = {}) - public void testGetGroupPropertyId() { - GroupId groupId = new GroupId(30); - GroupPropertyId groupPropertyId = TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK; - Object previousValue = "previous"; - Object currentValue = "current"; - GroupPropertyUpdateEvent groupPropertyUpdateEvent = new GroupPropertyUpdateEvent(groupId, groupPropertyId, previousValue, currentValue); - assertEquals(groupPropertyId, groupPropertyUpdateEvent.getGroupPropertyId()); - } - - @Test - @UnitTestMethod(name = "getPreviousPropertyValue", args = {}) - public void testGetPreviousPropertyValue() { - GroupId groupId = new GroupId(30); - GroupPropertyId groupPropertyId = TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK; - Object previousValue = "previous"; - Object currentValue = "current"; - GroupPropertyUpdateEvent groupPropertyUpdateEvent = new GroupPropertyUpdateEvent(groupId, groupPropertyId, previousValue, currentValue); - assertEquals(previousValue, groupPropertyUpdateEvent.getPreviousPropertyValue()); - } - - @Test - @UnitTestMethod(name = "getEventLabelByGroupAndProperty", args = { Context.class, GroupId.class, GroupPropertyId.class }) - public void testGetEventLabelByGroupAndProperty() { - - GroupsActionSupport.testConsumer(10, 3, 5, 2608996249142401870L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - List groupIds = groupsDataManager.getGroupIds(); - - Set> eventLabels = new LinkedHashSet<>(); - - for (GroupId groupId : groupIds) { - GroupTypeId groupTypeId = groupsDataManager.getGroupType(groupId); - Set groupPropertyIds = groupsDataManager.getGroupPropertyIds(groupTypeId); - - for (GroupPropertyId groupPropertyId : groupPropertyIds) { - - EventLabel eventLabel = GroupPropertyUpdateEvent.getEventLabelByGroupAndProperty(c, groupId, groupPropertyId); - - // show that the event label has the correct event class - assertEquals(GroupPropertyUpdateEvent.class, eventLabel.getEventClass()); - - // show that the event label has the correct primary key - assertEquals(GroupPropertyUpdateEvent.class, eventLabel.getPrimaryKeyValue()); - - // show that the event label has the same id as its - // associated labeler - EventLabeler eventLabeler = GroupPropertyUpdateEvent.getEventLabelerForGroupAndProperty(); - assertEquals(eventLabeler.getEventLabelerId(), eventLabel.getLabelerId()); - - // show that two event labels with the same inputs are equal - EventLabel eventLabel2 = GroupPropertyUpdateEvent.getEventLabelByGroupAndProperty(c, groupId, groupPropertyId); - assertEquals(eventLabel, eventLabel2); - - // show that equal event labels have equal hash codes - assertEquals(eventLabel.hashCode(), eventLabel2.hashCode()); - - // show that two event labels with different inputs are not - // equal - assertTrue(eventLabels.add(eventLabel)); - } - } - - // precondition tests - - // if the group id is null - ContractException contractException = assertThrows(ContractException.class, - () -> GroupPropertyUpdateEvent.getEventLabelByGroupAndProperty(c, null, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK)); - assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); - - // if the group id is unknown - contractException = assertThrows(ContractException.class, - () -> GroupPropertyUpdateEvent.getEventLabelByGroupAndProperty(c, new GroupId(100000), TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK)); - assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); - - // if the group property id is null - contractException = assertThrows(ContractException.class, () -> GroupPropertyUpdateEvent.getEventLabelByGroupAndProperty(c, new GroupId(0), null)); - assertEquals(GroupError.NULL_GROUP_PROPERTY_ID, contractException.getErrorType()); - - // if the group property id is unknown - contractException = assertThrows(ContractException.class, - () -> GroupPropertyUpdateEvent.getEventLabelByGroupAndProperty(c, new GroupId(0), TestGroupPropertyId.getUnknownGroupPropertyId())); - assertEquals(GroupError.UNKNOWN_GROUP_PROPERTY_ID, contractException.getErrorType()); - - // if the group property id is unknown - contractException = assertThrows(ContractException.class, - () -> GroupPropertyUpdateEvent.getEventLabelByGroupAndProperty(c, new GroupId(0), TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK)); - assertEquals(GroupError.UNKNOWN_GROUP_PROPERTY_ID, contractException.getErrorType()); - - }); - } - - @Test - @UnitTestMethod(name = "getEventLabelerForGroupAndProperty", args = {}) - public void testGetEventLabelerForGroupAndProperty() { - - GroupsActionSupport.testConsumer(30, 3, 5, 8688886270722853901L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - List groupIds = groupsDataManager.getGroupIds(); - - // create an event labeler - EventLabeler eventLabeler = GroupPropertyUpdateEvent.getEventLabelerForGroupAndProperty(); - - // show that the event labeler has the correct event class - assertEquals(GroupPropertyUpdateEvent.class, eventLabeler.getEventClass()); - - // show that the event labeler produces the expected event label - - for (GroupId groupId : groupIds) { - GroupTypeId groupTypeId = groupsDataManager.getGroupType(groupId); - Set groupPropertyIds = groupsDataManager.getGroupPropertyIds(groupTypeId); - - for (GroupPropertyId groupPropertyId : groupPropertyIds) { - // derive the expected event label for this event - EventLabel expectedEventLabel = GroupPropertyUpdateEvent.getEventLabelByGroupAndProperty(c, groupId, groupPropertyId); - - // show that the event label and event labeler have equal id - // values - assertEquals(expectedEventLabel.getLabelerId(), eventLabeler.getEventLabelerId()); - - // create an event - GroupPropertyUpdateEvent event = new GroupPropertyUpdateEvent(groupId, groupPropertyId, "previous", "current"); - - // show that the event labeler produces the correct event - // label - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - - assertEquals(expectedEventLabel, actualEventLabel); - - } - } - - }); - } - - @Test - @UnitTestMethod(name = "getEventLabelByGroup", args = { Context.class, GroupId.class }) - public void testGetEventLabelByGroup() { - - GroupsActionSupport.testConsumer(10, 3, 5, 7912737444879496875L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - List groupIds = groupsDataManager.getGroupIds(); - - Set> eventLabels = new LinkedHashSet<>(); - - for (GroupId groupId : groupIds) { - - EventLabel eventLabel = GroupPropertyUpdateEvent.getEventLabelByGroup(c, groupId); - - // show that the event label has the correct event class - assertEquals(GroupPropertyUpdateEvent.class, eventLabel.getEventClass()); - - // show that the event label has the correct primary key - assertEquals(GroupPropertyUpdateEvent.class, eventLabel.getPrimaryKeyValue()); - - // show that the event label has the same id as its - // associated labeler - EventLabeler eventLabeler = GroupPropertyUpdateEvent.getEventLabelerForGroup(); - assertEquals(eventLabeler.getEventLabelerId(), eventLabel.getLabelerId()); - - // show that two event labels with the same inputs are equal - EventLabel eventLabel2 = GroupPropertyUpdateEvent.getEventLabelByGroup(c, groupId); - assertEquals(eventLabel, eventLabel2); - - // show that equal event labels have equal hash codes - assertEquals(eventLabel.hashCode(), eventLabel2.hashCode()); - - // show that two event labels with different inputs are not - // equal - assertTrue(eventLabels.add(eventLabel)); - } - - // precondition tests - - // if the group id is null - ContractException contractException = assertThrows(ContractException.class, () -> GroupPropertyUpdateEvent.getEventLabelByGroup(c, null)); - assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); - - // if the group id is unknown - contractException = assertThrows(ContractException.class, () -> GroupPropertyUpdateEvent.getEventLabelByGroup(c, new GroupId(100000))); - assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); - - }); - - } - - @Test - @UnitTestMethod(name = "getEventLabelerForGroup", args = {}) - public void testGetEventLabelerForGroup() { - - GroupsActionSupport.testConsumer(30, 3, 5, 5829392632134617932L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - List groupIds = groupsDataManager.getGroupIds(); - - // create an event labeler - EventLabeler eventLabeler = GroupPropertyUpdateEvent.getEventLabelerForGroup(); - - // show that the event labeler has the correct event class - assertEquals(GroupPropertyUpdateEvent.class, eventLabeler.getEventClass()); - - // show that the event labeler produces the expected event label - - for (GroupId groupId : groupIds) { - - // derive the expected event label for this event - EventLabel expectedEventLabel = GroupPropertyUpdateEvent.getEventLabelByGroup(c, groupId); - - // show that the event label and event labeler have equal id - // values - assertEquals(expectedEventLabel.getLabelerId(), eventLabeler.getEventLabelerId()); - - // create an event - GroupPropertyUpdateEvent event = new GroupPropertyUpdateEvent(groupId, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, "previous", "current"); - - // show that the event labeler produces the correct event - // label - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - - assertEquals(expectedEventLabel, actualEventLabel); - - } - - }); - - } - - @Test - @UnitTestMethod(name = "getEventLabelByGroupTypeAndProperty", args = { Context.class, GroupTypeId.class, GroupPropertyId.class }) - public void testGetEventLabelByGroupTypeAndProperty() { - - GroupsActionSupport.testConsumer(10, 3, 5, 7297949839974902355L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - - Set> eventLabels = new LinkedHashSet<>(); - - for (GroupTypeId groupTypeId : TestGroupTypeId.values()) { - Set groupPropertyIds = groupsDataManager.getGroupPropertyIds(groupTypeId); - for (GroupPropertyId groupPropertyId : groupPropertyIds) { - - EventLabel eventLabel = GroupPropertyUpdateEvent.getEventLabelByGroupTypeAndProperty(c, groupTypeId, groupPropertyId); - - // show that the event label has the correct event class - assertEquals(GroupPropertyUpdateEvent.class, eventLabel.getEventClass()); - - // show that the event label has the correct primary key - assertEquals(GroupPropertyUpdateEvent.class, eventLabel.getPrimaryKeyValue()); - - // show that the event label has the same id as its - // associated labeler - EventLabeler eventLabeler = GroupPropertyUpdateEvent.getEventLabelerForGroupTypeAndProperty(groupsDataManager); - assertEquals(eventLabeler.getEventLabelerId(), eventLabel.getLabelerId()); - - // show that two event labels with the same inputs are equal - EventLabel eventLabel2 = GroupPropertyUpdateEvent.getEventLabelByGroupTypeAndProperty(c, groupTypeId, groupPropertyId); - assertEquals(eventLabel, eventLabel2); - - // show that equal event labels have equal hash codes - assertEquals(eventLabel.hashCode(), eventLabel2.hashCode()); - - // show that two event labels with different inputs are not - // equal - assertTrue(eventLabels.add(eventLabel)); - } - } - - // precondition tests - - // if the group id is null - ContractException contractException = assertThrows(ContractException.class, - () -> GroupPropertyUpdateEvent.getEventLabelByGroupTypeAndProperty(c, null, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK)); - assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); - - // if the group id is unknown - contractException = assertThrows(ContractException.class, () -> GroupPropertyUpdateEvent.getEventLabelByGroupTypeAndProperty(c, TestGroupTypeId.getUnknownGroupTypeId(), - TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK)); - assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); - - // if the group property id is null - contractException = assertThrows(ContractException.class, () -> GroupPropertyUpdateEvent.getEventLabelByGroupTypeAndProperty(c, TestGroupTypeId.GROUP_TYPE_1, null)); - assertEquals(GroupError.NULL_GROUP_PROPERTY_ID, contractException.getErrorType()); - - // if the group property id is unknown - contractException = assertThrows(ContractException.class, - () -> GroupPropertyUpdateEvent.getEventLabelByGroupTypeAndProperty(c, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.getUnknownGroupPropertyId())); - assertEquals(GroupError.UNKNOWN_GROUP_PROPERTY_ID, contractException.getErrorType()); - - // if the group property id is unknown - contractException = assertThrows(ContractException.class, - () -> GroupPropertyUpdateEvent.getEventLabelByGroupTypeAndProperty(c, TestGroupTypeId.GROUP_TYPE_1, TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK)); - assertEquals(GroupError.UNKNOWN_GROUP_PROPERTY_ID, contractException.getErrorType()); - - }); - } - - @Test - @UnitTestMethod(name = "getEventLabelerForGroupTypeAndProperty", args = {}) - public void testGetEventLabelerForGroupTypeAndProperty() { - - GroupsActionSupport.testConsumer(30, 3, 5, 9005403678043381761L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - - // create an event labeler - EventLabeler eventLabeler = GroupPropertyUpdateEvent.getEventLabelerForGroupTypeAndProperty(groupsDataManager); - - // show that the event labeler has the correct event class - assertEquals(GroupPropertyUpdateEvent.class, eventLabeler.getEventClass()); - - // show that the event labeler produces the expected event label - - for (GroupTypeId groupTypeId : TestGroupTypeId.values()) { - Set groupPropertyIds = groupsDataManager.getGroupPropertyIds(groupTypeId); - GroupId groupId = groupsDataManager.getGroupsForGroupType(groupTypeId).get(0); - - for (GroupPropertyId groupPropertyId : groupPropertyIds) { - // derive the expected event label for this event - EventLabel expectedEventLabel = GroupPropertyUpdateEvent.getEventLabelByGroupTypeAndProperty(c, groupTypeId, groupPropertyId); - - // show that the event label and event labeler have equal id - // values - assertEquals(expectedEventLabel.getLabelerId(), eventLabeler.getEventLabelerId()); - - // create an event - GroupPropertyUpdateEvent event = new GroupPropertyUpdateEvent(groupId, groupPropertyId, "previous", "current"); - - // show that the event labeler produces the correct event - // label - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - - assertEquals(expectedEventLabel, actualEventLabel); - - } - } - - }); - } - - @Test - @UnitTestMethod(name = "getEventLabelByGroupType", args = { Context.class, GroupTypeId.class }) - public void testGetEventLabelByGroupType() { - - GroupsActionSupport.testConsumer(10, 3, 5, 676016189463079696L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - Set> eventLabels = new LinkedHashSet<>(); - - for (GroupTypeId groupTypeId : TestGroupTypeId.values()) { - - EventLabel eventLabel = GroupPropertyUpdateEvent.getEventLabelByGroupType(c, groupTypeId); - - // show that the event label has the correct event class - assertEquals(GroupPropertyUpdateEvent.class, eventLabel.getEventClass()); - - // show that the event label has the correct primary key - assertEquals(GroupPropertyUpdateEvent.class, eventLabel.getPrimaryKeyValue()); - - // show that the event label has the same id as its - // associated labeler - EventLabeler eventLabeler = GroupPropertyUpdateEvent.getEventLabelerForGroupType(groupsDataManager); - assertEquals(eventLabeler.getEventLabelerId(), eventLabel.getLabelerId()); - - // show that two event labels with the same inputs are equal - EventLabel eventLabel2 = GroupPropertyUpdateEvent.getEventLabelByGroupType(c, groupTypeId); - assertEquals(eventLabel, eventLabel2); - - // show that equal event labels have equal hash codes - assertEquals(eventLabel.hashCode(), eventLabel2.hashCode()); - - // show that two event labels with different inputs are not - // equal - assertTrue(eventLabels.add(eventLabel)); - - } - - // precondition tests - - // if the group id is null - ContractException contractException = assertThrows(ContractException.class, () -> GroupPropertyUpdateEvent.getEventLabelByGroupType(c, null)); - assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); - - // if the group id is unknown - contractException = assertThrows(ContractException.class, () -> GroupPropertyUpdateEvent.getEventLabelByGroupType(c, TestGroupTypeId.getUnknownGroupTypeId())); - assertEquals(GroupError.UNKNOWN_GROUP_TYPE_ID, contractException.getErrorType()); - - }); - - } - - @Test - @UnitTestMethod(name = "getEventLabelerForGroupType", args = {}) - public void testGetEventLabelerForGroupType() { - - GroupsActionSupport.testConsumer(30, 3, 5, 6063816259833737907L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - - // create an event labeler - EventLabeler eventLabeler = GroupPropertyUpdateEvent.getEventLabelerForGroupType(groupsDataManager); - - // show that the event labeler has the correct event class - assertEquals(GroupPropertyUpdateEvent.class, eventLabeler.getEventClass()); - - // show that the event labeler produces the expected event label - - for (GroupTypeId groupTypeId : TestGroupTypeId.values()) { - - GroupId groupId = groupsDataManager.getGroupsForGroupType(groupTypeId).get(0); - - // derive the expected event label for this event - EventLabel expectedEventLabel = GroupPropertyUpdateEvent.getEventLabelByGroupType(c, groupTypeId); - - // show that the event label and event labeler have equal id - // values - assertEquals(expectedEventLabel.getLabelerId(), eventLabeler.getEventLabelerId()); - - // create an event - GroupPropertyUpdateEvent event = new GroupPropertyUpdateEvent(groupId, TestGroupPropertyId.GROUP_PROPERTY_1_1_BOOLEAN_MUTABLE_NO_TRACK, "previous", "current"); - - // show that the event labeler produces the correct event - // label - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - - assertEquals(expectedEventLabel, actualEventLabel); - - } - - }); - } - -} diff --git a/gcm3/src/test/java/plugins/groups/support/AT_BulkGroupMembershipData.java b/gcm3/src/test/java/plugins/groups/support/AT_BulkGroupMembershipData.java deleted file mode 100644 index 30f4f89f7..000000000 --- a/gcm3/src/test/java/plugins/groups/support/AT_BulkGroupMembershipData.java +++ /dev/null @@ -1,418 +0,0 @@ -package plugins.groups.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Random; -import java.util.Set; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import plugins.groups.testsupport.TestGroupPropertyId; -import plugins.groups.testsupport.TestGroupTypeId; -import plugins.people.support.PersonError; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; -import util.random.RandomGeneratorProvider; -import util.wrappers.MultiKey; - -@UnitTest(target = BulkGroupMembershipData.class) -public class AT_BulkGroupMembershipData { - - @Test - @UnitTestMethod(name = "", args = {}) - public void testBuilder() { - assertNotNull(BulkGroupMembershipData.builder()); - } - - @Test - @UnitTestMethod(target = BulkGroupMembershipData.Builder.class, name = "setGroupPropertyValue", args = { int.class, GroupPropertyId.class, Object.class }) - public void testSetGroupPropertyValue() { - - BulkGroupMembershipData bulkGroupMembershipData = BulkGroupMembershipData// - .builder()// - .addGroup(TestGroupTypeId.GROUP_TYPE_1)// - .addGroup(TestGroupTypeId.GROUP_TYPE_2)// - .addGroup(TestGroupTypeId.GROUP_TYPE_3)// - .setGroupPropertyValue(0, TestGroupPropertyId.GROUP_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK, 23.4)// - .setGroupPropertyValue(0, TestGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, 19)// - .setGroupPropertyValue(1, TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK, true)// - .setGroupPropertyValue(2, TestGroupPropertyId.GROUP_PROPERTY_3_2_INTEGER_IMMUTABLE_NO_TRACK, 88)// - .setGroupPropertyValue(2, TestGroupPropertyId.GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK, 14.7)// - .build();// - - assertEquals(23.4, bulkGroupMembershipData.getGroupPropertyValue(0, TestGroupPropertyId.GROUP_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK).get()); - assertEquals(19, bulkGroupMembershipData.getGroupPropertyValue(0, TestGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK).get()); - assertEquals(true, bulkGroupMembershipData.getGroupPropertyValue(1, TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK).get()); - assertEquals(88, bulkGroupMembershipData.getGroupPropertyValue(2, TestGroupPropertyId.GROUP_PROPERTY_3_2_INTEGER_IMMUTABLE_NO_TRACK).get()); - assertEquals(14.7, bulkGroupMembershipData.getGroupPropertyValue(2, TestGroupPropertyId.GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK).get()); - - // precondition test: if the group id is negative - ContractException contractException = assertThrows(ContractException.class, () -> BulkGroupMembershipData// - .builder()// - .addGroup(TestGroupTypeId.GROUP_TYPE_1)// - .setGroupPropertyValue(-1, - TestGroupPropertyId.GROUP_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK, 23.4)// - .build()); - - assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); - - // precondition test: if the group property id is null - contractException = assertThrows(ContractException.class, () -> BulkGroupMembershipData// - .builder()// - .addGroup(TestGroupTypeId.GROUP_TYPE_1)// - .setGroupPropertyValue(0, null, 23.4)// - .build()); - - assertEquals(GroupError.NULL_GROUP_PROPERTY_ID, contractException.getErrorType()); - - // precondition test: if the property value is null - contractException = assertThrows(ContractException.class, () -> BulkGroupMembershipData// - .builder()// - .addGroup(TestGroupTypeId.GROUP_TYPE_1)// - .setGroupPropertyValue(0, TestGroupPropertyId.GROUP_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK, null)// - .build()); - - assertEquals(GroupError.NULL_GROUP_PROPERTY_VALUE, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(target = BulkGroupMembershipData.Builder.class, name = "build", args = {}) - public void testBuild() { - - /* - * precondition : if a group membership was a negative group index - */ - ContractException contractException = assertThrows(ContractException.class, () -> BulkGroupMembershipData.builder().addPersonToGroup(0, 0).build()); - assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); - - /* - * precondition : if a group membership was added for a group index that - * was not added as a group - */ - contractException = assertThrows(ContractException.class, () -> BulkGroupMembershipData.builder().addPersonToGroup(0, -1).build()); - assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); - } - - @Test - @UnitTestMethod(target = BulkGroupMembershipData.Builder.class, name = "addGroup", args = { GroupTypeId.class }) - public void testAddGroup() { - - BulkGroupMembershipData bulkGroupMembershipData = BulkGroupMembershipData// - .builder()// - .addGroup(TestGroupTypeId.GROUP_TYPE_1)// - .addGroup(TestGroupTypeId.GROUP_TYPE_2)// - .addGroup(TestGroupTypeId.GROUP_TYPE_3)// - .build();// - - assertEquals(3, bulkGroupMembershipData.getGroupCount()); - - assertEquals(TestGroupTypeId.GROUP_TYPE_1, bulkGroupMembershipData.getGroupTypeId(0)); - - assertEquals(TestGroupTypeId.GROUP_TYPE_2, bulkGroupMembershipData.getGroupTypeId(1)); - - assertEquals(TestGroupTypeId.GROUP_TYPE_3, bulkGroupMembershipData.getGroupTypeId(2)); - - // precondition test : if the group type id is null - ContractException contractException = assertThrows(ContractException.class, () -> BulkGroupMembershipData.builder().addGroup(null)); - assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); - } - - @Test - @UnitTestMethod(target = BulkGroupMembershipData.Builder.class, name = "addPersonToGroup", args = { int.class, int.class }) - public void testAddPersonToGroup() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4636241749018649588L); - BulkGroupMembershipData.Builder builder = BulkGroupMembershipData.builder(); - - // using 10 groups and 25 people, select 50 unique, random pairings - - // create containers to hold expected group memberships and actual group - // memberships - Set expectedPairs = new LinkedHashSet<>(); - Set actualPairs = new LinkedHashSet<>(); - - // add the 10 groups - for (int i = 0; i < 10; i++) { - builder.addGroup(TestGroupTypeId.getRandomGroupTypeId(randomGenerator)); - } - - // create the 250 pairs - List pairs = new ArrayList<>(); - for (int i = 0; i < 25; i++) { - for (int j = 0; j < 10; j++) { - pairs.add(new MultiKey(i, j)); - } - } - - // select 50 of the pairs at random to add to the builder - Collections.shuffle(pairs, new Random(randomGenerator.nextLong())); - for (int i = 0; i < 50; i++) { - MultiKey multiKey = (pairs.get(i)); - Integer personIndex = multiKey.getKey(0); - Integer groupIndex = multiKey.getKey(1); - builder.addPersonToGroup(personIndex, groupIndex); - expectedPairs.add(multiKey); - } - - // build the bulk group membership data - BulkGroupMembershipData bulkGroupMembershipData = builder.build();// - - // determine the actual pairing from the bulk group membership data - List personIndices = bulkGroupMembershipData.getPersonIndices(); - for (Integer personIndex : personIndices) { - List groupIndices = bulkGroupMembershipData.getGroupIndicesForPersonIndex(personIndex); - for (Integer groupIndex : groupIndices) { - actualPairs.add(new MultiKey(personIndex, groupIndex)); - } - } - - // show that the bulk group membership data contains the expected - // pairings - assertEquals(expectedPairs, actualPairs); - - // precondition test if the person index is negative - ContractException contractException = assertThrows(ContractException.class, () -> BulkGroupMembershipData.builder().addPersonToGroup(-1, 0)); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - - // precondition test: if the group index is negative - contractException = assertThrows(ContractException.class, () -> BulkGroupMembershipData.builder().addPersonToGroup(0, -1)); - assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); - - // precondition test: if the person is already associated with the group - contractException = assertThrows(ContractException.class, () -> BulkGroupMembershipData.builder().addPersonToGroup(0, 0).addPersonToGroup(0, 0)); - assertEquals(GroupError.DUPLICATE_GROUP_MEMBERSHIP, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "getGroupCount", args = {}) - public void testGetGroupCount() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6040970044186644538L); - - // show that zero to nine groups result in the correct group count - for (int i = 0; i < 10; i++) { - BulkGroupMembershipData.Builder builder = BulkGroupMembershipData.builder(); - for (int j = 0; j < i; j++) { - builder.addGroup(TestGroupTypeId.getRandomGroupTypeId(randomGenerator)); - } - BulkGroupMembershipData bulkGroupMembershipData = builder.build(); - assertEquals(i, bulkGroupMembershipData.getGroupCount()); - } - } - - @Test - @UnitTestMethod(name = "getGroupTypeId", args = { int.class }) - public void testGetGroupTypeId() { - Map expectedGroupTypes = new LinkedHashMap<>(); - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8757283026856511464L); - - // Add 30 groups with randomized types - BulkGroupMembershipData.Builder builder = BulkGroupMembershipData.builder(); - for (int i = 9; i < 30; i++) { - GroupTypeId groupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); - builder.addGroup(groupTypeId); - expectedGroupTypes.put(expectedGroupTypes.size(), groupTypeId); - } - BulkGroupMembershipData bulkGroupMembershipData = builder.build(); - - // show that the 30 groups have the correct type - for (Integer index : expectedGroupTypes.keySet()) { - assertEquals(expectedGroupTypes.get(index), bulkGroupMembershipData.getGroupTypeId(index)); - } - - // precondition test: if the group index is negative - ContractException contractException = assertThrows(ContractException.class, () -> bulkGroupMembershipData.getGroupTypeId(-1)); - assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); - - // precondition test: if the group index >= the number of groups that - // were added - contractException = assertThrows(ContractException.class, () -> bulkGroupMembershipData.getGroupTypeId(30)); - assertEquals(GroupError.UNKNOWN_GROUP_ID, contractException.getErrorType()); - } - - @Test - @UnitTestMethod(name = "getGroupIndicesForPersonIndex", args = { int.class }) - public void testGetGroupIndicesForPersonIndex() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3006860401462487897L); - BulkGroupMembershipData.Builder builder = BulkGroupMembershipData.builder(); - - // using 10 groups and 25 people, select 50 unique, random pairings - - // create containers to hold expected group memberships and actual group - // memberships - Set expectedPairs = new LinkedHashSet<>(); - Set actualPairs = new LinkedHashSet<>(); - - // add the 10 groups - for (int i = 0; i < 10; i++) { - builder.addGroup(TestGroupTypeId.getRandomGroupTypeId(randomGenerator)); - } - - // create the 250 pairs - List pairs = new ArrayList<>(); - for (int i = 0; i < 25; i++) { - for (int j = 0; j < 10; j++) { - pairs.add(new MultiKey(i, j)); - } - } - - // select 50 of the pairs at random to add to the builder - Collections.shuffle(pairs, new Random(randomGenerator.nextLong())); - for (int i = 0; i < 50; i++) { - MultiKey multiKey = (pairs.get(i)); - Integer personIndex = multiKey.getKey(0); - Integer groupIndex = multiKey.getKey(1); - builder.addPersonToGroup(personIndex, groupIndex); - expectedPairs.add(multiKey); - } - - // build the bulk group membership data - BulkGroupMembershipData bulkGroupMembershipData = builder.build();// - - // determine the actual pairing from the bulk group membership data - List personIndices = bulkGroupMembershipData.getPersonIndices(); - for (Integer personIndex : personIndices) { - List groupIndices = bulkGroupMembershipData.getGroupIndicesForPersonIndex(personIndex); - for (Integer groupIndex : groupIndices) { - actualPairs.add(new MultiKey(personIndex, groupIndex)); - } - assertThrows(UnsupportedOperationException.class, ()->groupIndices.add(1234)); - } - - // show that the bulk group membership data contains the expected - // pairings - assertEquals(expectedPairs, actualPairs); - - // precondition tests -- none - - } - - @Test - @UnitTestMethod(name = "getPersonIndices", args = {}) - public void testGetPersonIndices() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7269766947053474864L); - BulkGroupMembershipData.Builder builder = BulkGroupMembershipData.builder(); - - // show that the getPersonIndices returns an empty list if no people - // were added - List people = builder.build().getPersonIndices(); - assertNotNull(people); - assertTrue(people.isEmpty()); - - // using 10 groups and 25 people, select 50 unique, random pairings - - // create a container to hold expected people - Set expectedPeople = new LinkedHashSet<>(); - - // add the 10 groups - for (int i = 0; i < 10; i++) { - builder.addGroup(TestGroupTypeId.getRandomGroupTypeId(randomGenerator)); - } - - // create the 250 pairs - List pairs = new ArrayList<>(); - for (int i = 0; i < 25; i++) { - for (int j = 0; j < 10; j++) { - pairs.add(new MultiKey(i, j)); - } - } - - // select 50 of the pairs at random to add to the builder - Collections.shuffle(pairs, new Random(randomGenerator.nextLong())); - for (int i = 0; i < 50; i++) { - MultiKey multiKey = (pairs.get(i)); - Integer personIndex = multiKey.getKey(0); - Integer groupIndex = multiKey.getKey(1); - builder.addPersonToGroup(personIndex, groupIndex); - expectedPeople.add(personIndex); - } - - // build the bulk group membership data - BulkGroupMembershipData bulkGroupMembershipData = builder.build();// - - // determine the actual people from the bulk group membership data - List actualPeople = bulkGroupMembershipData.getPersonIndices(); - assertEquals(expectedPeople.size(), actualPeople.size()); - assertEquals(expectedPeople, new LinkedHashSet<>(actualPeople)); - - // precondition tests -- none - } - - @Test - @UnitTestMethod(name = "getGroupPropertyIds", args = { int.class }) - public void testGetGroupPropertyIds() { - BulkGroupMembershipData bulkGroupMembershipData = BulkGroupMembershipData// - .builder()// - .addGroup(TestGroupTypeId.GROUP_TYPE_1)// - .addGroup(TestGroupTypeId.GROUP_TYPE_2)// - .addGroup(TestGroupTypeId.GROUP_TYPE_3)// - .setGroupPropertyValue(0, TestGroupPropertyId.GROUP_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK, 23.4)// - .setGroupPropertyValue(0, TestGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, 19)// - .setGroupPropertyValue(1, TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK, true)// - .setGroupPropertyValue(2, TestGroupPropertyId.GROUP_PROPERTY_3_2_INTEGER_IMMUTABLE_NO_TRACK, 88)// - .setGroupPropertyValue(2, TestGroupPropertyId.GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK, 14.7)// - .build();// - - // show that property values that were set can be retrieved - Set expectedGroupPropertyIds = new LinkedHashSet<>(); - expectedGroupPropertyIds.add(TestGroupPropertyId.GROUP_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK); - expectedGroupPropertyIds.add(TestGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK); - assertEquals(expectedGroupPropertyIds, bulkGroupMembershipData.getGroupPropertyIds(0)); - - expectedGroupPropertyIds = new LinkedHashSet<>(); - expectedGroupPropertyIds.add(TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK); - assertEquals(expectedGroupPropertyIds, bulkGroupMembershipData.getGroupPropertyIds(1)); - - expectedGroupPropertyIds = new LinkedHashSet<>(); - expectedGroupPropertyIds.add(TestGroupPropertyId.GROUP_PROPERTY_3_2_INTEGER_IMMUTABLE_NO_TRACK); - expectedGroupPropertyIds.add(TestGroupPropertyId.GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK); - assertEquals(expectedGroupPropertyIds, bulkGroupMembershipData.getGroupPropertyIds(2)); - } - - @Test - @UnitTestMethod(name = "getGroupPropertyValue", args = { int.class, GroupPropertyId.class }) - public void testGetGroupPropertyValue() { - BulkGroupMembershipData bulkGroupMembershipData = BulkGroupMembershipData// - .builder()// - .addGroup(TestGroupTypeId.GROUP_TYPE_1)// - .addGroup(TestGroupTypeId.GROUP_TYPE_2)// - .addGroup(TestGroupTypeId.GROUP_TYPE_3)// - .setGroupPropertyValue(0, TestGroupPropertyId.GROUP_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK, 23.4)// - .setGroupPropertyValue(0, TestGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, 19)// - .setGroupPropertyValue(1, TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK, true)// - .setGroupPropertyValue(2, TestGroupPropertyId.GROUP_PROPERTY_3_2_INTEGER_IMMUTABLE_NO_TRACK, 88)// - .setGroupPropertyValue(2, TestGroupPropertyId.GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK, 14.7)// - .build();// - - // show that property values that were set can be retrieved - assertEquals(23.4, bulkGroupMembershipData.getGroupPropertyValue(0, TestGroupPropertyId.GROUP_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK).get()); - assertEquals(19, bulkGroupMembershipData.getGroupPropertyValue(0, TestGroupPropertyId.GROUP_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK).get()); - assertEquals(true, bulkGroupMembershipData.getGroupPropertyValue(1, TestGroupPropertyId.GROUP_PROPERTY_2_1_BOOLEAN_MUTABLE_TRACK).get()); - assertEquals(88, bulkGroupMembershipData.getGroupPropertyValue(2, TestGroupPropertyId.GROUP_PROPERTY_3_2_INTEGER_IMMUTABLE_NO_TRACK).get()); - assertEquals(14.7, bulkGroupMembershipData.getGroupPropertyValue(2, TestGroupPropertyId.GROUP_PROPERTY_3_3_DOUBLE_IMMUTABLE_NO_TRACK).get()); - - // show that property values that were not set cannot be retrieved - assertFalse(bulkGroupMembershipData.getGroupPropertyValue(2, TestGroupPropertyId.GROUP_PROPERTY_3_1_BOOLEAN_IMMUTABLE_NO_TRACK).isPresent()); - assertFalse(bulkGroupMembershipData.getGroupPropertyValue(1, TestGroupPropertyId.GROUP_PROPERTY_2_2_INTEGER_MUTABLE_TRACK).isPresent()); - assertFalse(bulkGroupMembershipData.getGroupPropertyValue(1, TestGroupPropertyId.GROUP_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK).isPresent()); - - } - -} diff --git a/gcm3/src/test/java/plugins/groups/support/AT_GroupError.java b/gcm3/src/test/java/plugins/groups/support/AT_GroupError.java deleted file mode 100644 index dd5906ac3..000000000 --- a/gcm3/src/test/java/plugins/groups/support/AT_GroupError.java +++ /dev/null @@ -1,30 +0,0 @@ -package plugins.groups.support; - -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = GroupError.class) -public class AT_GroupError { - - @Test - @UnitTestMethod(name = "getDescription", args = {}) - public void test() { - //show that each description is a unique, non-null and non-empty string - Set descriptions = new LinkedHashSet<>(); - for(GroupError groupError : GroupError.values()) { - String description = groupError.getDescription(); - assertNotNull(description,"null description for "+groupError); - assertTrue(description.length()>0, "empty string for "+groupError); - boolean unique = descriptions.add(description); - assertTrue(unique,"description for "+groupError+" is not unique"); - } - } -} diff --git a/gcm3/src/test/java/plugins/groups/support/AT_GroupId.java b/gcm3/src/test/java/plugins/groups/support/AT_GroupId.java deleted file mode 100644 index 42a6d1a57..000000000 --- a/gcm3/src/test/java/plugins/groups/support/AT_GroupId.java +++ /dev/null @@ -1,14 +0,0 @@ -package plugins.groups.support; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - -@UnitTest(target = GroupId.class) -public class AT_GroupId { - - @Test - public void test() { - //nothing to test - } -} diff --git a/gcm3/src/test/java/plugins/groups/support/AT_GroupLabeler.java b/gcm3/src/test/java/plugins/groups/support/AT_GroupLabeler.java deleted file mode 100644 index efe20aa79..000000000 --- a/gcm3/src/test/java/plugins/groups/support/AT_GroupLabeler.java +++ /dev/null @@ -1,134 +0,0 @@ -package plugins.groups.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; - -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; - -import org.junit.jupiter.api.Test; - -import nucleus.SimulationContext; -import plugins.groups.datamanagers.GroupsDataManager; -import plugins.groups.events.GroupMembershipAdditionEvent; -import plugins.groups.events.GroupMembershipRemovalEvent; -import plugins.groups.testsupport.GroupsActionSupport; -import plugins.groups.testsupport.TestGroupTypeId; -import plugins.partitions.support.LabelerSensitivity; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - -@UnitTest(target = GroupLabeler.class) -public final class AT_GroupLabeler { - - @Test - @UnitTestConstructor(args = { Function.class }) - public void testConstructor() { - assertNotNull(new GroupLabeler((g) -> null)); - } - - @Test - @UnitTestMethod(name = "getLabelerSensitivities", args = {}) - public void testGetLabelerSensitivities() { - - Set> labelerSensitivities = new GroupLabeler((g) -> null).getLabelerSensitivities(); - - // show that we get back some labeler sensitivities - assertNotNull(labelerSensitivities); - - // we expect exactly two - assertEquals(2, labelerSensitivities.size()); - - boolean groupMembershipAdditionEventSensitivityFound = false; - boolean groupMembershipRemovalEventSensitivityFound = false; - for (LabelerSensitivity labelerSensitivity : labelerSensitivities) { - if (labelerSensitivity.getEventClass() == GroupMembershipAdditionEvent.class) { - groupMembershipAdditionEventSensitivityFound = true; - PersonId personId = new PersonId(45253); - - Optional optional = labelerSensitivity.getPersonId(new GroupMembershipAdditionEvent(personId, new GroupId(56))); - assertTrue(optional.isPresent()); - PersonId actualPersonId = optional.get(); - assertEquals(personId, actualPersonId); - - } else if (labelerSensitivity.getEventClass() == GroupMembershipRemovalEvent.class) { - groupMembershipRemovalEventSensitivityFound = true; - PersonId personId = new PersonId(45253); - - Optional optional = labelerSensitivity.getPersonId(new GroupMembershipRemovalEvent(personId, new GroupId(56))); - assertTrue(optional.isPresent()); - PersonId actualPersonId = optional.get(); - assertEquals(personId, actualPersonId); - - } else { - fail("unknown labeler sensitivity"); - } - } - - // show that we found both labeler sensitivities - assertTrue(groupMembershipAdditionEventSensitivityFound); - assertTrue(groupMembershipRemovalEventSensitivityFound); - - } - - @Test - @UnitTestMethod(name = "getLabel", args = { SimulationContext.class, PersonId.class }) - public void testGetLabel() { - - GroupsActionSupport.testConsumer(30, 3, 5, 5880749882920317232L, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - - Function func = (g) -> { - int result = 0; - for (GroupTypeId groupTypeId : g.getGroupTypeIds()) { - TestGroupTypeId testGroupTypeId = (TestGroupTypeId) groupTypeId; - result += (testGroupTypeId.ordinal() + 1) * g.getGroupCount(groupTypeId); - } - return result; - }; - - GroupLabeler groupLabeler = new GroupLabeler(func); - - for (PersonId personId : peopleDataManager.getPeople()) { - GroupTypeCountMap.Builder builder = GroupTypeCountMap.builder(); - for(GroupTypeId groupTypeId : groupsDataManager.getGroupTypeIds()){ - builder.setCount(groupTypeId, groupsDataManager.getGroupCountForGroupTypeAndPerson(groupTypeId, personId)); - } - GroupTypeCountMap groupTypeCountMap = builder.build(); - Object expectedLabel = func.apply(groupTypeCountMap); - Object actualLabel = groupLabeler.getLabel(c, personId); - assertEquals(expectedLabel, actualLabel); - } - - //precondition tests - - //if the person id is null - ContractException contractException = assertThrows(ContractException.class,()-> groupLabeler.getLabel(c, null)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - - //if the person id is unknown - contractException = assertThrows(ContractException.class,()-> groupLabeler.getLabel(c, new PersonId(100000))); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - - - }); - } - - @Test - @UnitTestMethod(name = "getDimension", args = {}) - public void testGetDimension() { - Function f = (g) -> null; - assertEquals(GroupTypeId.class, new GroupLabeler(f).getDimension()); - } - -} diff --git a/gcm3/src/test/java/plugins/groups/support/AT_GroupMemberFilter.java b/gcm3/src/test/java/plugins/groups/support/AT_GroupMemberFilter.java deleted file mode 100644 index 14bd3d2f3..000000000 --- a/gcm3/src/test/java/plugins/groups/support/AT_GroupMemberFilter.java +++ /dev/null @@ -1,118 +0,0 @@ -package plugins.groups.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -import javax.naming.Context; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import plugins.groups.datamanagers.GroupsDataManager; -import plugins.groups.events.GroupMembershipAdditionEvent; -import plugins.groups.events.GroupMembershipRemovalEvent; -import plugins.groups.testsupport.GroupsActionSupport; -import plugins.groups.testsupport.TestGroupTypeId; -import plugins.partitions.support.Filter; -import plugins.partitions.support.FilterSensitivity; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import plugins.stochastics.StochasticsDataManager; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - -@UnitTest(target = GroupMemberFilter.class) -public class AT_GroupMemberFilter { - - @Test - @UnitTestConstructor(args = { Context.class, GroupId.class }) - public void testConstructor() { - - GroupsActionSupport.testConsumer(100, 3, 10, 8499169041100865476L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - List groupIds = groupsDataManager.getGroupIds(); - assertFalse(groupIds.isEmpty()); - for (GroupId groupId : groupIds) { - final Filter filter = new GroupMemberFilter(groupId); - assertNotNull(filter); - } - - - // precondition tests - ContractException contractException = assertThrows(ContractException.class, () -> new GroupMemberFilter(null).validate(c)); - assertEquals(GroupError.NULL_GROUP_ID, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "getFilterSensitivities", args = {}) - public void testGetFilterSensitivities() { - - GroupsActionSupport.testConsumer(100, 3, 10, 7283631979607042406L, (c) -> { - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - - GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); - - Filter filter = new GroupMemberFilter(groupId); - - Set> expected = new LinkedHashSet<>(); - expected.add(GroupMembershipAdditionEvent.class); - expected.add(GroupMembershipRemovalEvent.class); - - Set> filterSensitivities = filter.getFilterSensitivities(); - assertNotNull(filterSensitivities); - assertEquals(filterSensitivities.size(), 2); - - Set> actual = new LinkedHashSet<>(); - for (FilterSensitivity filterSensitivity : filterSensitivities) { - Class eventClass = filterSensitivity.getEventClass(); - actual.add(eventClass); - } - assertEquals(expected, actual); - - }); - - } - - @Test - @UnitTestMethod(name = "evaluate", args = { Context.class, PersonId.class }) - public void testEvaluate() { - - GroupsActionSupport.testConsumer(100, 3, 10, 6248106595116941770L, (c) -> { - RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - GroupId groupId = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_3); - Filter filter = new GroupMemberFilter(groupId); - - for (PersonId personId : peopleDataManager.getPeople()) { - if (randomGenerator.nextBoolean()) { - groupsDataManager.addPersonToGroup(personId,groupId); - } - } - - for (PersonId personId : peopleDataManager.getPeople()) { - boolean expected = groupsDataManager.isPersonInGroup(personId,groupId); - boolean actual = filter.evaluate(c, personId); - assertEquals(expected, actual); - } - - /* precondition: if the person id is null */ - ContractException contractException = assertThrows(ContractException.class, () -> filter.evaluate(c, null)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - - /* precondition: if the person id is unknown */ - contractException = assertThrows(ContractException.class, () -> filter.evaluate(c, new PersonId(123412342))); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - }); - } -} diff --git a/gcm3/src/test/java/plugins/groups/support/AT_GroupPropertyId.java b/gcm3/src/test/java/plugins/groups/support/AT_GroupPropertyId.java deleted file mode 100644 index d89b479ab..000000000 --- a/gcm3/src/test/java/plugins/groups/support/AT_GroupPropertyId.java +++ /dev/null @@ -1,14 +0,0 @@ -package plugins.groups.support; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - -@UnitTest(target = GroupPropertyId.class) -public class AT_GroupPropertyId { - - @Test - public void test() { - //nothing to test - } -} diff --git a/gcm3/src/test/java/plugins/groups/support/AT_GroupSampler.java b/gcm3/src/test/java/plugins/groups/support/AT_GroupSampler.java deleted file mode 100644 index 45f70afce..000000000 --- a/gcm3/src/test/java/plugins/groups/support/AT_GroupSampler.java +++ /dev/null @@ -1,92 +0,0 @@ -package plugins.groups.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; - -import plugins.people.support.PersonId; -import plugins.stochastics.testsupport.TestRandomGeneratorId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -/** - * Test class for {@link GroupSamplerInfo} - * - * @author Shawn Hatch - * - */ -@UnitTest(target = GroupSampler.class) -public class AT_GroupSampler { - - /** - * Tests {@linkplain GroupSampler#builder() - */ - @Test - @UnitTestMethod(name = "builder", args = {}) - public void testBuilder() { - - GroupSampler groupSampler = GroupSampler.builder().build(); - - assertNotNull(groupSampler); - - assertNotNull(groupSampler.getExcludedPerson()); - assertFalse(groupSampler.getExcludedPerson().isPresent()); - - assertNotNull(groupSampler.getWeightingFunction()); - assertFalse(groupSampler.getWeightingFunction().isPresent()); - - assertNotNull(groupSampler.getRandomNumberGeneratorId()); - assertFalse(groupSampler.getRandomNumberGeneratorId().isPresent()); - - } - - /** - * Tests {@linkplain GroupSampler#getExcludedPerson() - */ - @Test - @UnitTestMethod(name = "getExcludedPerson", args = {}) - public void testGetExcludedPerson() { - GroupSampler groupSampler = GroupSampler.builder().setExcludedPersonId(new PersonId(67)).build(); - assertNotNull(groupSampler); - assertNotNull(groupSampler.getExcludedPerson()); - assertTrue(groupSampler.getExcludedPerson().isPresent()); - assertEquals(67, groupSampler.getExcludedPerson().get().getValue()); - } - - /** - * Tests {@linkplain GroupSamplerInfo#getRandomNumberGeneratorId() - */ - @Test - @UnitTestMethod(name = "getRandomNumberGeneratorId", args = {}) - public void testGetRandomNumberGeneratorId() { - GroupSampler groupSampler = GroupSampler.builder().setRandomNumberGeneratorId(TestRandomGeneratorId.DASHER).setRandomNumberGeneratorId(TestRandomGeneratorId.VIXEN).build(); - - assertNotNull(groupSampler); - assertNotNull(groupSampler.getRandomNumberGeneratorId()); - assertTrue(groupSampler.getRandomNumberGeneratorId().isPresent()); - assertEquals(TestRandomGeneratorId.VIXEN, groupSampler.getRandomNumberGeneratorId().get()); - } - - /** - * Tests {@linkplain GroupSamplerInfo#getWeightingFunction() - */ - @Test - @UnitTestMethod(name = "getWeightingFunction", args = {}) - public void testGetLabelSetWeightingFunction() { - - double expectedValue = 17.5; - GroupSampler groupSampler = GroupSampler.builder().setGroupWeightingFunction((context, personId, groupId) -> expectedValue).build(); - - assertNotNull(groupSampler); - assertNotNull(groupSampler.getWeightingFunction()); - assertTrue(groupSampler.getWeightingFunction().isPresent()); - GroupWeightingFunction groupWeightingFunction = groupSampler.getWeightingFunction().get(); - assertNotNull(groupWeightingFunction); - - assertEquals(expectedValue, groupWeightingFunction.getWeight(null, null, null), 0); - } - -} diff --git a/gcm3/src/test/java/plugins/groups/support/AT_GroupTypeCountMap.java b/gcm3/src/test/java/plugins/groups/support/AT_GroupTypeCountMap.java deleted file mode 100644 index 0a8728481..000000000 --- a/gcm3/src/test/java/plugins/groups/support/AT_GroupTypeCountMap.java +++ /dev/null @@ -1,212 +0,0 @@ -package plugins.groups.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import plugins.groups.testsupport.TestGroupTypeId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; -import util.random.RandomGeneratorProvider; - -/** - * Test class for {@link GroupTypeCountMap} - * - * @author Shawn Hatch - * - */ -@UnitTest(target = GroupTypeCountMap.class) -public class AT_GroupTypeCountMap { - - /** - * Tests {@linkplain GroupTypeCountMap#equals(Object) - */ - @Test - @UnitTestMethod(name = "equals", args = { Object.class }) - public void testEquals() { - // 4832988525233013426L - /* - * Show various cases demonstrating that build order and implied zero - * values do not influence the equals contract - */ - - // order should not matter - GroupTypeCountMap.Builder builder = GroupTypeCountMap.builder(); - builder.setCount(TestGroupTypeId.GROUP_TYPE_1, 5); - builder.setCount(TestGroupTypeId.GROUP_TYPE_2, 7); - GroupTypeCountMap groupTypeCountMap1 = builder.build(); - - builder.setCount(TestGroupTypeId.GROUP_TYPE_2, 7); - builder.setCount(TestGroupTypeId.GROUP_TYPE_1, 5); - GroupTypeCountMap groupTypeCountMap2 = builder.build(); - - assertEquals(groupTypeCountMap1, groupTypeCountMap2); - - // implied zero values should not matter - builder.setCount(TestGroupTypeId.GROUP_TYPE_3, 0); - builder.setCount(TestGroupTypeId.GROUP_TYPE_2, 7); - builder.setCount(TestGroupTypeId.GROUP_TYPE_1, 5); - groupTypeCountMap1 = builder.build(); - - builder.setCount(TestGroupTypeId.GROUP_TYPE_2, 7); - builder.setCount(TestGroupTypeId.GROUP_TYPE_1, 5); - groupTypeCountMap2 = builder.build(); - - assertEquals(groupTypeCountMap1, groupTypeCountMap2); - - // differences in positive counts matter - - builder.setCount(TestGroupTypeId.GROUP_TYPE_1, 5); - builder.setCount(TestGroupTypeId.GROUP_TYPE_2, 7); - groupTypeCountMap1 = builder.build(); - - builder.setCount(TestGroupTypeId.GROUP_TYPE_1, 5); - builder.setCount(TestGroupTypeId.GROUP_TYPE_2, 3); - groupTypeCountMap2 = builder.build(); - - assertNotEquals(groupTypeCountMap1, groupTypeCountMap2); - } - - /** - * Tests {@linkplain GroupTypeCountMap#hashCode() - */ - @Test - @UnitTestMethod(name = "hashCode", args = {}) - public void testHashCode() { - /* - * Equal objects have equal hash codes - */ - - // order should not matter - GroupTypeCountMap.Builder builder = GroupTypeCountMap.builder(); - builder.setCount(TestGroupTypeId.GROUP_TYPE_1, 5); - builder.setCount(TestGroupTypeId.GROUP_TYPE_2, 7); - GroupTypeCountMap groupTypeCountMap1 = builder.build(); - - builder.setCount(TestGroupTypeId.GROUP_TYPE_2, 7); - builder.setCount(TestGroupTypeId.GROUP_TYPE_1, 5); - GroupTypeCountMap groupTypeCountMap2 = builder.build(); - - assertEquals(groupTypeCountMap1.hashCode(), groupTypeCountMap2.hashCode()); - - // implied zero values should not matter - builder.setCount(TestGroupTypeId.GROUP_TYPE_3, 0); - builder.setCount(TestGroupTypeId.GROUP_TYPE_2, 7); - builder.setCount(TestGroupTypeId.GROUP_TYPE_1, 5); - groupTypeCountMap1 = builder.build(); - - builder.setCount(TestGroupTypeId.GROUP_TYPE_2, 7); - builder.setCount(TestGroupTypeId.GROUP_TYPE_1, 5); - groupTypeCountMap2 = builder.build(); - - assertEquals(groupTypeCountMap1.hashCode(), groupTypeCountMap2.hashCode()); - - } - - /** - * Tests {@linkplain GroupTypeCountMap#getGroupCount(GroupTypeId) - */ - @Test - @UnitTestMethod(name = "getGroupCount", args = { GroupTypeId.class }) - public void testGetGroupCount() { - // covered by testBuilder() test method - } - - /** - * Tests {@linkplain GroupTypeCountMap#toString() - */ - @Test - @UnitTestMethod(name = "toString", args = {}) - public void testToString() { - GroupTypeCountMap.Builder builder = GroupTypeCountMap.builder(); - - int count = 1; - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - builder.setCount(testGroupTypeId, count++); - } - GroupTypeCountMap groupTypeCountMap = builder.build(); - - String expectedValue = "GroupTypeCountMap [GROUP_TYPE_1=1, GROUP_TYPE_2=2, GROUP_TYPE_3=3]"; - String actualValue = groupTypeCountMap.toString(); - - assertEquals(expectedValue, actualValue); - } - - /** - * Tests {@linkplain GroupTypeCountMap#builder() - */ - @Test - @UnitTestMethod(name = "builder", args = {}) - public void testBuilder() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1353590720789078598L); - - for (int i = 0; i < 20; i++) { - Map expectedValues = new LinkedHashMap<>(); - GroupTypeCountMap.Builder builder = GroupTypeCountMap.builder(); - - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - expectedValues.put(testGroupTypeId, 0); - if (randomGenerator.nextBoolean()) { - int count = randomGenerator.nextInt(3); - builder.setCount(testGroupTypeId, count); - expectedValues.put(testGroupTypeId, count); - } - } - GroupTypeCountMap groupTypeCountMap = builder.build(); - - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - int expectedValue = expectedValues.get(testGroupTypeId); - int actualValue = groupTypeCountMap.getGroupCount(testGroupTypeId); - assertEquals(expectedValue, actualValue); - } - } - - // precondition checks - ContractException contractException = assertThrows(ContractException.class, () -> GroupTypeCountMap.builder().setCount(null, 10)); - assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); - - contractException = assertThrows(ContractException.class, () -> GroupTypeCountMap.builder().setCount(TestGroupTypeId.GROUP_TYPE_1, -1)); - assertEquals(GroupError.NEGATIVE_GROUP_COUNT, contractException.getErrorType()); - - - - } - - // public java.util.Set gcm.simulation.GroupTypeCountMap.getGroupTypeIds() - /** - * Tests {@linkplain GroupTypeCountMap#getGroupTypeIds() - */ - @Test - @UnitTestMethod(name = "getGroupTypeIds", args = {}) - public void testGetGroupTypeIds() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1310699113269703296L); - - for (int i = 0; i < 20; i++) { - Set expectedGroupTypeIds = new LinkedHashSet<>(); - GroupTypeCountMap.Builder builder = GroupTypeCountMap.builder(); - - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - - if (randomGenerator.nextBoolean()) { - expectedGroupTypeIds.add(testGroupTypeId); - builder.setCount(testGroupTypeId, 1); - } - } - GroupTypeCountMap groupTypeCountMap = builder.build(); - - Set actualGroupTypeIds = groupTypeCountMap.getGroupTypeIds(); - assertEquals(expectedGroupTypeIds, actualGroupTypeIds); - } - - } -} diff --git a/gcm3/src/test/java/plugins/groups/support/AT_GroupTypeId.java b/gcm3/src/test/java/plugins/groups/support/AT_GroupTypeId.java deleted file mode 100644 index b6889682c..000000000 --- a/gcm3/src/test/java/plugins/groups/support/AT_GroupTypeId.java +++ /dev/null @@ -1,14 +0,0 @@ -package plugins.groups.support; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - -@UnitTest(target = GroupTypeId.class) -public class AT_GroupTypeId { - - @Test - public void test() { - //nothing to test - } -} diff --git a/gcm3/src/test/java/plugins/groups/support/AT_GroupTypesForPersonFilter.java b/gcm3/src/test/java/plugins/groups/support/AT_GroupTypesForPersonFilter.java deleted file mode 100644 index 7afebecba..000000000 --- a/gcm3/src/test/java/plugins/groups/support/AT_GroupTypesForPersonFilter.java +++ /dev/null @@ -1,141 +0,0 @@ -package plugins.groups.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.NucleusError; -import nucleus.SimulationContext; -import plugins.groups.datamanagers.GroupsDataManager; -import plugins.groups.events.GroupMembershipAdditionEvent; -import plugins.groups.events.GroupMembershipRemovalEvent; -import plugins.groups.testsupport.GroupsActionSupport; -import plugins.groups.testsupport.TestGroupTypeId; -import plugins.partitions.support.Equality; -import plugins.partitions.support.Filter; -import plugins.partitions.support.FilterSensitivity; -import plugins.partitions.support.PartitionError; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import plugins.stochastics.StochasticsDataManager; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - -@UnitTest(target = GroupTypesForPersonFilter.class) -public class AT_GroupTypesForPersonFilter { - - @Test - @UnitTestConstructor(args = { SimulationContext.class, Equality.class, int.class }) - public void testConstructor() { - // nothing to test - } - - @Test - @UnitTestMethod(name = "validate", args = {}) - public void testValidate() { - //precondition tests - - //if the equality operator is null - GroupsActionSupport.testConsumer(100, 3, 10, 1499199255771310930L, (c) -> { - ContractException contractException = assertThrows(ContractException.class, () -> new GroupTypesForPersonFilter(null, 5).validate(c)); - assertEquals(PartitionError.NULL_EQUALITY_OPERATOR, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "getFilterSensitivities", args = {}) - public void testGetFilterSensitivities() { - - GroupsActionSupport.testConsumer(100, 3, 10, 770617124373530907L, (c) -> { - Filter filter = new GroupTypesForPersonFilter(Equality.EQUAL, 5); - - Set> expected = new LinkedHashSet<>(); - expected.add(GroupMembershipAdditionEvent.class); - expected.add(GroupMembershipRemovalEvent.class); - - Set> filterSensitivities = filter.getFilterSensitivities(); - assertNotNull(filterSensitivities); - assertEquals(filterSensitivities.size(), 2); - - Set> actual = new LinkedHashSet<>(); - for (FilterSensitivity filterSensitivity : filterSensitivities) { - Class eventClass = filterSensitivity.getEventClass(); - actual.add(eventClass); - } - assertEquals(expected, actual); - }); - - } - - @Test - @UnitTestMethod(name = "evaluate", args = { SimulationContext.class, PersonId.class }) - public void testEvaluate() { - - GroupsActionSupport.testConsumer(100, 3, 10, 2954287333801626073L, (c) -> { - - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - List people = peopleDataManager.getPeople(); - RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); - - GroupId groupId1 = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); - GroupId groupId2 = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_2); - GroupId groupId3 = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_3); - - Filter filter = new GroupTypesForPersonFilter(Equality.EQUAL, 2); - - assertEquals(100,people.size()); - for (PersonId personId : people) { - int typeCount = randomGenerator.nextInt(4); - switch (typeCount) { - case 0: - break; - case 1: - groupsDataManager.addPersonToGroup(personId,groupId1); - break; - case 2: - groupsDataManager.addPersonToGroup(personId,groupId1); - groupsDataManager.addPersonToGroup(personId,groupId2); - break; - default: - groupsDataManager.addPersonToGroup(personId,groupId1); - groupsDataManager.addPersonToGroup(personId,groupId2); - groupsDataManager.addPersonToGroup(personId,groupId3); - break; - } - - } - - for (PersonId personId : people) { - boolean expected = groupsDataManager.getGroupTypeCountForPersonId(personId) == 2; - boolean actual = filter.evaluate(c, personId); - assertEquals(expected, actual); - } - - /* precondition: if the context is null */ - ContractException contractException = assertThrows(ContractException.class, () -> filter.evaluate(null, new PersonId(0))); - assertEquals(NucleusError.NULL_SIMULATION_CONTEXT, contractException.getErrorType()); - - /* precondition: if the person id is null */ - contractException = assertThrows(ContractException.class, () -> filter.evaluate(c, null)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - - /* precondition: if the person id is unknown */ - contractException = assertThrows(ContractException.class, () -> filter.evaluate(c, new PersonId(123412342))); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - - }); - - } -} diff --git a/gcm3/src/test/java/plugins/groups/support/AT_GroupWeightingFunction.java b/gcm3/src/test/java/plugins/groups/support/AT_GroupWeightingFunction.java deleted file mode 100644 index 98b48ff6a..000000000 --- a/gcm3/src/test/java/plugins/groups/support/AT_GroupWeightingFunction.java +++ /dev/null @@ -1,12 +0,0 @@ -package plugins.groups.support; - -import tools.annotations.UnitTest; - - -@UnitTest(target = GroupWeightingFunction.class) -public class AT_GroupWeightingFunction { - - public void test() { - //nothing to test - } -} diff --git a/gcm3/src/test/java/plugins/groups/support/AT_GroupsForPersonAndGroupTypeFilter.java b/gcm3/src/test/java/plugins/groups/support/AT_GroupsForPersonAndGroupTypeFilter.java deleted file mode 100644 index a0443c731..000000000 --- a/gcm3/src/test/java/plugins/groups/support/AT_GroupsForPersonAndGroupTypeFilter.java +++ /dev/null @@ -1,144 +0,0 @@ -package plugins.groups.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -import javax.naming.Context; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.SimulationContext; -import plugins.groups.datamanagers.GroupsDataManager; -import plugins.groups.events.GroupMembershipAdditionEvent; -import plugins.groups.events.GroupMembershipRemovalEvent; -import plugins.groups.testsupport.GroupsActionSupport; -import plugins.groups.testsupport.TestGroupTypeId; -import plugins.partitions.support.Equality; -import plugins.partitions.support.Filter; -import plugins.partitions.support.FilterSensitivity; -import plugins.partitions.support.PartitionError; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import plugins.stochastics.StochasticsDataManager; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - -@UnitTest(target = GroupsForPersonAndGroupTypeFilter.class) -public class AT_GroupsForPersonAndGroupTypeFilter { - - @Test - @UnitTestConstructor(args = { SimulationContext.class, GroupTypeId.class, Equality.class, int.class }) - public void testConstructor() { - - GroupsActionSupport.testConsumer(100, 3, 10, 5854778167265102928L, (c) -> { - - final Filter filter = new GroupsForPersonAndGroupTypeFilter(TestGroupTypeId.GROUP_TYPE_1, Equality.EQUAL, 5); - assertNotNull(filter); - - // precondition tests - - // if the group type id is null - ContractException contractException = assertThrows(ContractException.class, () -> new GroupsForPersonAndGroupTypeFilter(null, Equality.EQUAL, 5).validate(c)); - assertEquals(GroupError.NULL_GROUP_TYPE_ID, contractException.getErrorType()); - - // if the equality operator is null - contractException = assertThrows(ContractException.class, () -> new GroupsForPersonAndGroupTypeFilter(TestGroupTypeId.GROUP_TYPE_1, null, 5).validate(c)); - assertEquals(PartitionError.NULL_EQUALITY_OPERATOR, contractException.getErrorType()); - - }); - - } - - @Test - @UnitTestMethod(name = "getFilterSensitivities", args = {}) - public void testGetFilterSensitivities() { - - GroupsActionSupport.testConsumer(100, 3, 10, 1469082977858605268L, (c) -> { - Filter filter = new GroupsForPersonAndGroupTypeFilter(TestGroupTypeId.GROUP_TYPE_1, Equality.EQUAL, 5); - - Set> expected = new LinkedHashSet<>(); - expected.add(GroupMembershipAdditionEvent.class); - expected.add(GroupMembershipRemovalEvent.class); - - Set> filterSensitivities = filter.getFilterSensitivities(); - assertNotNull(filterSensitivities); - assertEquals(filterSensitivities.size(), 2); - - Set> actual = new LinkedHashSet<>(); - for (FilterSensitivity filterSensitivity : filterSensitivities) { - Class eventClass = filterSensitivity.getEventClass(); - actual.add(eventClass); - } - assertEquals(expected, actual); - - - }); - - } - - @Test - @UnitTestMethod(name = "evaluate", args = { Context.class, PersonId.class }) - public void testEvaluate() { - - GroupsActionSupport.testConsumer(100, 0, 10, 4592268926831796100L, (c) -> { - RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - List people = peopleDataManager.getPeople(); - - GroupId groupId1 = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); - GroupId groupId2 = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); - GroupId groupId3 = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); - - - Filter filter = new GroupsForPersonAndGroupTypeFilter(TestGroupTypeId.GROUP_TYPE_1, Equality.EQUAL, 2); - - assertEquals(100,people.size()); - - for (PersonId personId : people) { - int groupCount = randomGenerator.nextInt(4); - switch (groupCount) { - case 0: - break; - case 1: - groupsDataManager.addPersonToGroup(personId,groupId1); - break; - case 2: - groupsDataManager.addPersonToGroup(personId,groupId1); - groupsDataManager.addPersonToGroup(personId,groupId2); - break; - default: - groupsDataManager.addPersonToGroup(personId,groupId1); - groupsDataManager.addPersonToGroup(personId,groupId2); - groupsDataManager.addPersonToGroup(personId,groupId3); - break; - } - - } - - for (PersonId personId : people) { - boolean expected = groupsDataManager.getGroupCountForGroupTypeAndPerson(TestGroupTypeId.GROUP_TYPE_1, personId) == 2; - boolean actual = filter.evaluate(c, personId); - assertEquals(expected, actual); - } - - /* precondition: if the person id is null */ - ContractException contractException = assertThrows(ContractException.class, () -> filter.evaluate(c, null)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - - /* precondition: if the person id is unknown */ - contractException = assertThrows(ContractException.class, () -> filter.evaluate(c, new PersonId(123412342))); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - - }); - } -} diff --git a/gcm3/src/test/java/plugins/groups/support/AT_GroupsForPersonFilter.java b/gcm3/src/test/java/plugins/groups/support/AT_GroupsForPersonFilter.java deleted file mode 100644 index ca31f97d7..000000000 --- a/gcm3/src/test/java/plugins/groups/support/AT_GroupsForPersonFilter.java +++ /dev/null @@ -1,140 +0,0 @@ -package plugins.groups.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.NucleusError; -import nucleus.SimulationContext; -import plugins.groups.datamanagers.GroupsDataManager; -import plugins.groups.events.GroupMembershipAdditionEvent; -import plugins.groups.events.GroupMembershipRemovalEvent; -import plugins.groups.testsupport.GroupsActionSupport; -import plugins.groups.testsupport.TestGroupTypeId; -import plugins.partitions.support.Equality; -import plugins.partitions.support.Filter; -import plugins.partitions.support.FilterSensitivity; -import plugins.partitions.support.PartitionError; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.PersonId; -import plugins.stochastics.StochasticsDataManager; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - - -@UnitTest(target = GroupsForPersonFilter.class) -public class AT_GroupsForPersonFilter { - - @Test - @UnitTestConstructor(args = { SimulationContext.class, Equality.class, int.class }) - public void testConstructor() { - // nothing to test - } - - @Test - @UnitTestMethod(name = "validate", args = { SimulationContext.class, Equality.class, int.class }) - public void testValidate() { - GroupsActionSupport.testConsumer(100, 3, 10, 5329703278551588697L, (c) -> { - // precondition tests - - // if the equality operator is null - ContractException contractException = assertThrows(ContractException.class, () -> new GroupsForPersonFilter(null, 5).validate(c)); - assertEquals(PartitionError.NULL_EQUALITY_OPERATOR, contractException.getErrorType()); - - }); - } - - @Test - @UnitTestMethod(name = "getFilterSensitivities", args = {}) - public void testGetFilterSensitivities() { - GroupsActionSupport.testConsumer(100, 3, 10, 8314387061888020596L, (c) -> { - Filter filter = new GroupsForPersonFilter(Equality.EQUAL, 5); - - Set> expected = new LinkedHashSet<>(); - expected.add(GroupMembershipAdditionEvent.class); - expected.add(GroupMembershipRemovalEvent.class); - - Set> filterSensitivities = filter.getFilterSensitivities(); - assertNotNull(filterSensitivities); - assertEquals(filterSensitivities.size(), 2); - - Set> actual = new LinkedHashSet<>(); - for (FilterSensitivity filterSensitivity : filterSensitivities) { - Class eventClass = filterSensitivity.getEventClass(); - actual.add(eventClass); - } - assertEquals(expected, actual); - - }); - } - - @Test - @UnitTestMethod(name = "evaluate", args = { SimulationContext.class, PersonId.class }) - public void testEvaluate() { - - GroupsActionSupport.testConsumer(100, 0, 10, 6164158277278234559L, (c) -> { - RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - - List people = peopleDataManager.getPeople(); - - GroupId groupId1 = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_1); - GroupId groupId2 = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_2); - GroupId groupId3 = groupsDataManager.addGroup(TestGroupTypeId.GROUP_TYPE_3); - - - Filter filter = new GroupsForPersonFilter(Equality.EQUAL, 2); - - assertEquals(100, people.size()); - - for (PersonId personId : people) { - int typeCount = randomGenerator.nextInt(4); - switch (typeCount) { - case 0: - break; - case 1: - groupsDataManager.addPersonToGroup(personId,groupId1); - break; - case 2: - groupsDataManager.addPersonToGroup(personId,groupId1); - groupsDataManager.addPersonToGroup(personId,groupId2); - break; - default: - groupsDataManager.addPersonToGroup(personId,groupId1); - groupsDataManager.addPersonToGroup(personId,groupId2); - groupsDataManager.addPersonToGroup(personId,groupId3); - break; - } - - } - - for (PersonId personId : people) { - boolean expected = groupsDataManager.getGroupCountForPerson(personId) == 2; - boolean actual = filter.evaluate(c, personId); - assertEquals(expected, actual); - } - - /* precondition: if the context is null */ - ContractException contractException = assertThrows(ContractException.class, () -> filter.evaluate(null, new PersonId(0))); - assertEquals(NucleusError.NULL_SIMULATION_CONTEXT, contractException.getErrorType()); - - /* precondition: if the person id is null */ - assertThrows(RuntimeException.class, () -> filter.evaluate(c, null)); - - /* precondition: if the person id is unknown */ - assertThrows(RuntimeException.class, () -> filter.evaluate(c, new PersonId(123412342))); - - }); - - } -} diff --git a/gcm3/src/test/java/plugins/groups/testsupport/AT_GroupsActionSupport.java b/gcm3/src/test/java/plugins/groups/testsupport/AT_GroupsActionSupport.java deleted file mode 100644 index 952f5979a..000000000 --- a/gcm3/src/test/java/plugins/groups/testsupport/AT_GroupsActionSupport.java +++ /dev/null @@ -1,65 +0,0 @@ -package plugins.groups.testsupport; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.function.Consumer; - -import org.junit.jupiter.api.Test; - -import nucleus.testsupport.testplugin.TestError; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import plugins.groups.datamanagers.GroupsDataManager; -import plugins.groups.support.GroupId; -import plugins.people.datamanagers.PeopleDataManager; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; -import util.wrappers.MutableBoolean; - -//@UnitTest(target = GroupsActionSupport.class) -public class AT_GroupsActionSupport { - - @Test - @UnitTestMethod(name = "testConsumer", args = { int.class, double.class, double.class, long.class, Consumer.class }) - public void testConsumer() { - MutableBoolean executed = new MutableBoolean(); - GroupsActionSupport.testConsumer(100, 3, 5, 234L, (c) -> { - - // show that there are 100 people - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - assertEquals(100, peopleDataManager.getPopulationCount()); - - // show that there are 60 groups - GroupsDataManager groupsDataManager = c.getDataManager(GroupsDataManager.class); - assertEquals(60, groupsDataManager.getGroupIds().size()); - - // show that there are 300 group memberships - int membershipCount = 0; - for (GroupId groupId : groupsDataManager.getGroupIds()) { - membershipCount += groupsDataManager.getPersonCountForGroup(groupId); - - } - assertEquals(300, membershipCount); - - // show that the group properties exist - for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { - assertTrue(groupsDataManager.getGroupPropertyExists(testGroupPropertyId.getTestGroupTypeId(), testGroupPropertyId)); - } - - executed.setValue(true); - }); - assertTrue(executed.getValue()); - } - - @Test - @UnitTestMethod(name = "testConsumers", args = { int.class, double.class, double.class, long.class, Consumer.class }) - public void testConsumers() { - ContractException contractException = assertThrows(ContractException.class, - () -> GroupsActionSupport.testConsumers(100, 3, 5, 234L, TestPlugin.getTestPlugin(TestPluginData.builder().build()))); - - assertEquals(TestError.TEST_EXECUTION_FAILURE, contractException.getErrorType()); - } - -} diff --git a/gcm3/src/test/java/plugins/groups/testsupport/AT_TestGroupPropertyId.java b/gcm3/src/test/java/plugins/groups/testsupport/AT_TestGroupPropertyId.java deleted file mode 100644 index 98d357e8a..000000000 --- a/gcm3/src/test/java/plugins/groups/testsupport/AT_TestGroupPropertyId.java +++ /dev/null @@ -1,104 +0,0 @@ -package plugins.groups.testsupport; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.EnumSet; -import java.util.LinkedHashSet; -import java.util.Set; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import plugins.groups.support.GroupPropertyId; -import plugins.util.properties.PropertyDefinition; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.random.RandomGeneratorProvider; - -@UnitTest(target = TestGroupPropertyId.class) -public class AT_TestGroupPropertyId { - - @Test - @UnitTestMethod(name = "getPropertyDefinition", args = {}) - public void testGetPropertyDefinition() { - for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { - assertNotNull(testGroupPropertyId.getPropertyDefinition()); - } - - } - - @Test - @UnitTestMethod(name = "getTestGroupTypeId", args = {}) - public void testGetTestGroupTypeId() { - for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { - assertNotNull(testGroupPropertyId.getTestGroupTypeId()); - } - } - - @Test - @UnitTestMethod(name = "getTestGroupPropertyIds", args = { TestGroupTypeId.class }) - public void testGetTestGroupPropertyIds() { - - - - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - //show that each group type has at least one associated property id - Set testGroupPropertyIds = TestGroupPropertyId.getTestGroupPropertyIds(testGroupTypeId); - assertNotNull(testGroupPropertyIds); - assertFalse(testGroupPropertyIds.isEmpty()); - - //show that each such property id is associated with that group type - for (TestGroupPropertyId testGroupPropertyId : testGroupPropertyIds) { - assertEquals(testGroupTypeId, testGroupPropertyId.getTestGroupTypeId()); - } - } - } - - @Test - @UnitTestMethod(name = "getUnknownGroupPropertyId", args = {}) - public void testGetUnknownGroupPropertyId() { - /* - * Shows that a generated unknown group property id is unique, not null - * and not a member of the enum - */ - Set testProperties = EnumSet.allOf(TestGroupPropertyId.class); - Set unknownGroupPropertyIds = new LinkedHashSet<>(); - for (int i = 0; i < 30; i++) { - GroupPropertyId unknownGroupPropertyId = TestGroupPropertyId.getUnknownGroupPropertyId(); - assertNotNull(unknownGroupPropertyId); - boolean unique = unknownGroupPropertyIds.add(unknownGroupPropertyId); - assertTrue(unique); - assertFalse(testProperties.contains(unknownGroupPropertyId)); - } - } - - @Test - @UnitTestMethod(name = "getRandomPropertyValue", args = { RandomGenerator.class }) - public void testGetRandomPropertyValue() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6173923848365818813L); - - /* - * Show that randomly generated values are compatible with the - * associated property definition. Show that the values are reasonably - * unique - */ - for (TestGroupPropertyId testGroupPropertyId : TestGroupPropertyId.values()) { - PropertyDefinition propertyDefinition = testGroupPropertyId.getPropertyDefinition(); - Set values = new LinkedHashSet<>(); - for (int i = 0; i < 100; i++) { - Object propertyValue = testGroupPropertyId.getRandomPropertyValue(randomGenerator); - values.add(propertyValue); - assertTrue(propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())); - } - //show that the values are reasonable unique - if (propertyDefinition.getType() != Boolean.class) { - assertTrue(values.size() > 10); - } else { - assertEquals(2, values.size()); - } - } - } -} diff --git a/gcm3/src/test/java/plugins/groups/testsupport/AT_TestGroupTypeId.java b/gcm3/src/test/java/plugins/groups/testsupport/AT_TestGroupTypeId.java deleted file mode 100644 index ebdd64d71..000000000 --- a/gcm3/src/test/java/plugins/groups/testsupport/AT_TestGroupTypeId.java +++ /dev/null @@ -1,92 +0,0 @@ -package plugins.groups.testsupport; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.EnumSet; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import plugins.groups.support.GroupTypeId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.random.RandomGeneratorProvider; -import util.wrappers.MutableInteger; - -@UnitTest(target = TestGroupTypeId.class) -public class AT_TestGroupTypeId { - - @Test - @UnitTestMethod(name = "getRandomGroupTypeId", args = { RandomGenerator.class }) - public void testGetRandomGroupTypeId() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2320032453802629402L); - - /* - * Show that randomly generated type ids are non-null and reasonably - * distributed - */ - - Map counterMap = new LinkedHashMap<>(); - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - counterMap.put(testGroupTypeId, new MutableInteger()); - } - int sampleSize = 1000; - for (int i = 0; i < sampleSize; i++) { - TestGroupTypeId testGroupTypeId = TestGroupTypeId.getRandomGroupTypeId(randomGenerator); - assertNotNull(testGroupTypeId); - counterMap.get(testGroupTypeId).increment(); - } - - int expectedSize = sampleSize / TestGroupTypeId.values().length; - int lowExpectedSize = 4 * expectedSize / 5; - int highExpectedSize = 6 * expectedSize / 5; - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - MutableInteger mutableInteger = counterMap.get(testGroupTypeId); - assertTrue(mutableInteger.getValue() > lowExpectedSize); - assertTrue(mutableInteger.getValue() < highExpectedSize); - } - - } - - @Test - @UnitTestMethod(name = "size", args = {}) - public void testSize() { - assertEquals(TestGroupTypeId.values().length, TestGroupTypeId.size()); - } - - @Test - @UnitTestMethod(name = "next", args = {}) - public void testNext() { - for (TestGroupTypeId testGroupTypeId : TestGroupTypeId.values()) { - int index = (testGroupTypeId.ordinal() + 1) % TestGroupTypeId.values().length; - TestGroupTypeId expectedNext = TestGroupTypeId.values()[index]; - assertEquals(expectedNext, testGroupTypeId.next()); - } - } - - @Test - @UnitTestMethod(name = "getUnknownGroupTypeId", args = {}) - public void testGetUnknownGroupTypeId() { - /* - * Shows that a generated unknown group type id is unique, not null and - * not a member of the enum - */ - Set testGroupTypeIds = EnumSet.allOf(TestGroupTypeId.class); - Set unknownGroupTypeIds = new LinkedHashSet<>(); - for (int i = 0; i < 30; i++) { - GroupTypeId unknownGroupTypeId = TestGroupTypeId.getUnknownGroupTypeId(); - assertNotNull(unknownGroupTypeId); - boolean unique = unknownGroupTypeIds.add(unknownGroupTypeId); - assertTrue(unique); - assertFalse(testGroupTypeIds.contains(unknownGroupTypeId)); - } - } - -} diff --git a/gcm3/src/test/java/plugins/materials/AT_MaterialsPlugin.java b/gcm3/src/test/java/plugins/materials/AT_MaterialsPlugin.java deleted file mode 100644 index 65a931fc1..000000000 --- a/gcm3/src/test/java/plugins/materials/AT_MaterialsPlugin.java +++ /dev/null @@ -1,40 +0,0 @@ -package plugins.materials; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import nucleus.Plugin; -import nucleus.PluginId; -import plugins.regions.RegionsPluginId; -import plugins.resources.ResourcesPluginId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = MaterialsPlugin.class) -public class AT_MaterialsPlugin { - - @Test - @UnitTestMethod(name = "getMaterialsPlugin", args = { MaterialsPluginData.class }) - public void testGetMaterialsPlugin() { - MaterialsPluginData materialsPluginData = MaterialsPluginData.builder().build(); - Plugin materialsPlugin = MaterialsPlugin.getMaterialsPlugin(materialsPluginData); - - assertEquals(1, materialsPlugin.getPluginDatas().size()); - assertTrue(materialsPlugin.getPluginDatas().contains(materialsPluginData)); - - assertEquals(MaterialsPluginId.PLUGIN_ID, materialsPlugin.getPluginId()); - - Set expectedDependencies = new LinkedHashSet<>(); - - expectedDependencies.add(RegionsPluginId.PLUGIN_ID); - expectedDependencies.add(ResourcesPluginId.PLUGIN_ID); - assertEquals(expectedDependencies, materialsPlugin.getPluginDependencies()); - - } - -} diff --git a/gcm3/src/test/java/plugins/materials/AT_MaterialsPluginData.java b/gcm3/src/test/java/plugins/materials/AT_MaterialsPluginData.java deleted file mode 100644 index 05b66ab7d..000000000 --- a/gcm3/src/test/java/plugins/materials/AT_MaterialsPluginData.java +++ /dev/null @@ -1,1787 +0,0 @@ -package plugins.materials; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.EnumSet; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Random; -import java.util.Set; -import java.util.function.Supplier; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.PluginData; -import nucleus.PluginDataBuilder; -import plugins.materials.support.BatchId; -import plugins.materials.support.BatchPropertyId; -import plugins.materials.support.MaterialId; -import plugins.materials.support.MaterialsError; -import plugins.materials.support.MaterialsProducerId; -import plugins.materials.support.MaterialsProducerPropertyId; -import plugins.materials.support.StageId; -import plugins.materials.testsupport.TestBatchPropertyId; -import plugins.materials.testsupport.TestMaterialId; -import plugins.materials.testsupport.TestMaterialsProducerId; -import plugins.materials.testsupport.TestMaterialsProducerPropertyId; -import plugins.resources.support.ResourceError; -import plugins.resources.support.ResourceId; -import plugins.resources.testsupport.TestResourceId; -import plugins.util.properties.PropertyDefinition; -import plugins.util.properties.PropertyError; -import plugins.util.properties.TimeTrackingPolicy; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; -import util.random.RandomGeneratorProvider; -import util.wrappers.MultiKey; - -@UnitTest(target = MaterialsPluginData.class) -public class AT_MaterialsPluginData { - - @Test - @UnitTestMethod(name = "builder", args = {}) - public void testBuilder() { - assertNotNull(MaterialsPluginData.builder()); - } - - @Test - @UnitTestMethod(target = MaterialsPluginData.Builder.class, name = "build", args = {}) - public void testBuild() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8543723876953755503L); - - MaterialsPluginData materialsInitialData = MaterialsPluginData.builder().build(); - - assertNotNull(materialsInitialData); - - assertTrue(materialsInitialData.getBatchIds().isEmpty()); - assertTrue(materialsInitialData.getMaterialIds().isEmpty()); - assertTrue(materialsInitialData.getResourceIds().isEmpty()); - assertTrue(materialsInitialData.getStageIds().isEmpty()); - - // precondition tests - - /* - * if a batch property is associated with a material id that was not - * properly added - */ - ContractException contractException = assertThrows(ContractException.class, () -> { - MaterialId materialId = TestMaterialId.MATERIAL_1; - TestBatchPropertyId propertyId = TestBatchPropertyId.BATCH_PROPERTY_1_1_BOOLEAN_IMMUTABLE_NO_TRACK; - PropertyDefinition propertyDefinition = propertyId.getPropertyDefinition(); - MaterialsPluginData .builder()// - .defineBatchProperty(materialId, propertyId, propertyDefinition)// - .build();// - }); - assertEquals(MaterialsError.UNKNOWN_MATERIAL_ID, contractException.getErrorType()); - - /* - * if a batch property is defined without a default value - */ - contractException = assertThrows(ContractException.class, () -> { - MaterialId materialId = TestMaterialId.MATERIAL_1; - TestBatchPropertyId propertyId = TestBatchPropertyId.BATCH_PROPERTY_1_1_BOOLEAN_IMMUTABLE_NO_TRACK; - PropertyDefinition propertyDefinition = PropertyDefinition .builder()// - .setType(Boolean.class)// - .setPropertyValueMutability(false)// - .setTimeTrackingPolicy(TimeTrackingPolicy.DO_NOT_TRACK_TIME)// - .build();// - - MaterialsPluginData .builder()// - .addMaterial(materialId)// - .defineBatchProperty(materialId, propertyId, propertyDefinition).build();// - }); - assertEquals(MaterialsError.PROPERTY_DEFINITION_REQUIRES_DEFAULT, contractException.getErrorType()); - - /* - * if a materials property value is associated with a materials producer - * id that was not properly added - */ - contractException = assertThrows(ContractException.class, () -> { - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; - TestMaterialsProducerPropertyId propertyId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; - PropertyDefinition propertyDefinition = propertyId.getPropertyDefinition();// - Object value = propertyId.getRandomPropertyValue(randomGenerator); - - MaterialsPluginData .builder()// - .defineMaterialsProducerProperty(propertyId, propertyDefinition)// - .setMaterialsProducerPropertyValue(materialsProducerId, propertyId, value)// - .build();// - }); - assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - - /* - * if a materials property value is associated with a materials producer - * property id that was not properly defined - */ - contractException = assertThrows(ContractException.class, () -> { - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; - TestMaterialsProducerPropertyId propertyId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; - Object value = propertyId.getRandomPropertyValue(randomGenerator); - - MaterialsPluginData .builder()// - .addMaterialsProducerId(materialsProducerId)// - .setMaterialsProducerPropertyValue(materialsProducerId, propertyId, value)// - .build();// - }); - assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_PROPERTY_ID, contractException.getErrorType()); - - /* - * if a materials property value is associated with a value that is not - * compatible with the corresponding property definition - * - */ - - contractException = assertThrows(ContractException.class, () -> { - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; - TestMaterialsProducerPropertyId propertyId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; - PropertyDefinition propertyDefinition = propertyId.getPropertyDefinition();// - Object value = 12; - - MaterialsPluginData .builder()// - .addMaterialsProducerId(materialsProducerId)// - .defineMaterialsProducerProperty(propertyId, propertyDefinition)// - .setMaterialsProducerPropertyValue(materialsProducerId, propertyId, value)// - .build();// - }); - assertEquals(MaterialsError.INCOMPATIBLE_MATERIALS_PRODUCER_PROPERTY_VALUE, contractException.getErrorType()); - - /* - * if a materials property is defined without a default value and there - * is not an assigned property value for each added materials producer - */ - contractException = assertThrows(ContractException.class, () -> { - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; - TestMaterialsProducerPropertyId propertyId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; - - PropertyDefinition propertyDefinition = PropertyDefinition .builder()// - .setType(Boolean.class)// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.DO_NOT_TRACK_TIME)// - .build(); - - MaterialsPluginData .builder()// - .addMaterialsProducerId(materialsProducerId)// - .defineMaterialsProducerProperty(propertyId, propertyDefinition)// - .build();// - }); - assertEquals(MaterialsError.INSUFFICIENT_MATERIALS_PRODUCER_PROPERTY_VALUE_ASSIGNMENT, contractException.getErrorType()); - - /* - * if a materials resource level is set for a material producer id that - * was not properly added - */ - - contractException = assertThrows(ContractException.class, () -> { - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; - TestResourceId testResourceId = TestResourceId.RESOURCE_2; - Long resourceLevel = 10L; - - MaterialsPluginData .builder()// - .setMaterialsProducerResourceLevel(materialsProducerId, testResourceId, resourceLevel) // - .build();// - }); - assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - - /* - * if a batch is associated with at material that was not properly added - */ - contractException = assertThrows(ContractException.class, () -> { - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; - TestMaterialId testMaterialId = TestMaterialId.MATERIAL_3; - BatchId batchId = new BatchId(67); - double amount = 345.543; - - MaterialsPluginData .builder()// - .addBatch(batchId, testMaterialId, amount, materialsProducerId)// - .addMaterialsProducerId(materialsProducerId)// - .build();// - }); - assertEquals(MaterialsError.UNKNOWN_MATERIAL_ID, contractException.getErrorType()); - - /* - * if a batch is associated with at material producer that was not - * properly added - */ - // MaterialsError. - contractException = assertThrows(ContractException.class, () -> { - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; - TestMaterialId testMaterialId = TestMaterialId.MATERIAL_3; - BatchId batchId = new BatchId(67); - double amount = 345.543; - - MaterialsPluginData .builder()// - .addBatch(batchId, testMaterialId, amount, materialsProducerId)// - .addMaterial(testMaterialId)// - .build();// - }); - assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - - /* - * if a batch property is associated with batch id that was not properly - * added - */ - contractException = assertThrows(ContractException.class, () -> { - - TestBatchPropertyId testBatchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK; - PropertyDefinition propertyDefinition = testBatchPropertyId.getPropertyDefinition(); - testBatchPropertyId.getRandomPropertyValue(randomGenerator); - TestMaterialId testMaterialId = TestMaterialId.MATERIAL_1; - - BatchId batchId = new BatchId(67); - - MaterialsPluginData .builder()// - .defineBatchProperty(testMaterialId, testBatchPropertyId, propertyDefinition)// - .addMaterial(testMaterialId)// - .setBatchPropertyValue(batchId, testBatchPropertyId, batchId)// - .build();// - }); - assertEquals(MaterialsError.UNKNOWN_BATCH_ID, contractException.getErrorType()); - - /* - * if a batch property is associated with batch property id that was not - * properly defined - */ - contractException = assertThrows(ContractException.class, () -> { - TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; - TestBatchPropertyId testBatchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK; - Object value = testBatchPropertyId.getRandomPropertyValue(randomGenerator); - TestMaterialId testMaterialId = TestMaterialId.MATERIAL_1; - - BatchId batchId = new BatchId(67); - double amount = 345.54; - - MaterialsPluginData .builder()// - .addBatch(batchId, testMaterialId, amount, testMaterialsProducerId)// - .addMaterial(testMaterialId)// - .setBatchPropertyValue(batchId, testBatchPropertyId, value)// - .addMaterialsProducerId(testMaterialsProducerId)// - .build();// - }); - assertEquals(MaterialsError.UNKNOWN_BATCH_PROPERTY_ID, contractException.getErrorType()); - - /* - * if a batch property value is incompatible with the corresponding - * property definition - */ - - contractException = assertThrows(ContractException.class, () -> { - TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; - TestBatchPropertyId testBatchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_1_3_DOUBLE_MUTABLE_NO_TRACK; - PropertyDefinition propertyDefinition = testBatchPropertyId.getPropertyDefinition(); - Object incompatibleValue = "bad value"; - TestMaterialId testMaterialId = TestMaterialId.MATERIAL_1; - - BatchId batchId = new BatchId(67); - double amount = 345.54; - - MaterialsPluginData .builder()// - .addBatch(batchId, testMaterialId, amount, testMaterialsProducerId)// - .addMaterial(testMaterialId)// - .setBatchPropertyValue(batchId, testBatchPropertyId, incompatibleValue)// - .addMaterialsProducerId(testMaterialsProducerId)// - .defineBatchProperty(testMaterialId, testBatchPropertyId, propertyDefinition)// - .build();// - }); - assertEquals(MaterialsError.INCOMPATIBLE_BATCH_PROPERTY_VALUE, contractException.getErrorType()); - - /* - * if a stage is associated with a materials producer id that was not - * properly added - */ - contractException = assertThrows(ContractException.class, () -> { - TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; - StageId stageId = new StageId(543); - boolean offered = false; - - MaterialsPluginData .builder()// - .addStage(stageId, offered, testMaterialsProducerId)// - .build();// - }); - assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - - /* - * if a batch is associated with a stage id that was not properly added - */ - - contractException = assertThrows(ContractException.class, () -> { - TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; - StageId stageId = new StageId(543); - BatchId batchId = new BatchId(55); - double amount = 86.0; - TestMaterialId testMaterialId = TestMaterialId.MATERIAL_3; - - MaterialsPluginData .builder()// - .addBatch(batchId, testMaterialId, amount, testMaterialsProducerId)// - .addBatchToStage(stageId, batchId)// - .addMaterial(testMaterialId)// - .addMaterialsProducerId(testMaterialsProducerId)// - .build();// - }); - assertEquals(MaterialsError.UNKNOWN_STAGE_ID, contractException.getErrorType()); - - /* - * if a stage is associated with a batch id that was not properly added - */ - contractException = assertThrows(ContractException.class, () -> { - TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; - StageId stageId = new StageId(543); - BatchId batchId = new BatchId(55); - TestMaterialId testMaterialId = TestMaterialId.MATERIAL_3; - boolean offered = false; - - MaterialsPluginData .builder()// - .addStage(stageId, offered, testMaterialsProducerId)// - .addBatchToStage(stageId, batchId)// - .addMaterial(testMaterialId)// - .addMaterialsProducerId(testMaterialsProducerId)// - .build();// - }); - assertEquals(MaterialsError.UNKNOWN_BATCH_ID, contractException.getErrorType()); - - /* - * if a batch is associated with more than one stage - */ - contractException = assertThrows(ContractException.class, () -> { - TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; - StageId stageId1 = new StageId(543); - StageId stageId2 = new StageId(659); - BatchId batchId = new BatchId(55); - TestMaterialId testMaterialId = TestMaterialId.MATERIAL_3; - boolean offered = false; - double amount = 765.87; - - MaterialsPluginData .builder()// - .addBatch(batchId, testMaterialId, amount, testMaterialsProducerId)// - .addStage(stageId1, offered, testMaterialsProducerId)// - .addStage(stageId2, offered, testMaterialsProducerId)// - .addBatchToStage(stageId1, batchId)// - .addBatchToStage(stageId2, batchId)// - .addMaterial(testMaterialId)// - .addMaterialsProducerId(testMaterialsProducerId)// - .build();// - }); - assertEquals(MaterialsError.BATCH_ALREADY_STAGED, contractException.getErrorType()); - - /* - * if a batch is associated with a stage that is not owned by the same - * materials producer as the batch - */ - // MaterialsError. - contractException = assertThrows(ContractException.class, () -> { - TestMaterialsProducerId testMaterialsProducerId1 = TestMaterialsProducerId.MATERIALS_PRODUCER_1; - TestMaterialsProducerId testMaterialsProducerId2 = TestMaterialsProducerId.MATERIALS_PRODUCER_2; - StageId stageId = new StageId(543); - BatchId batchId = new BatchId(55); - TestMaterialId testMaterialId = TestMaterialId.MATERIAL_3; - boolean offered = false; - double amount = 765.87; - - MaterialsPluginData .builder()// - .addBatch(batchId, testMaterialId, amount, testMaterialsProducerId1)// - .addStage(stageId, offered, testMaterialsProducerId2)// - .addBatchToStage(stageId, batchId)// - .addMaterial(testMaterialId)// - .addMaterialsProducerId(testMaterialsProducerId1)// - .addMaterialsProducerId(testMaterialsProducerId2)// - .build();// - }); - assertEquals(MaterialsError.BATCH_STAGED_TO_DIFFERENT_OWNER, contractException.getErrorType()); - } - - @Test - @UnitTestMethod(target = MaterialsPluginData.Builder.class, name = "addBatch", args = { BatchId.class, MaterialId.class, double.class, MaterialsProducerId.class }) - public void testAddBatch() { - BatchId batchId = new BatchId(456); - MaterialId materialId = TestMaterialId.MATERIAL_1; - double amount = 16.7; - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_3; - - MaterialsPluginData materialsInitialData = MaterialsPluginData .builder()// - .addBatch(batchId, materialId, amount, materialsProducerId)// - .addMaterial(materialId)// - .addMaterialsProducerId(materialsProducerId).build();// - - assertTrue(materialsInitialData.getBatchIds().contains(batchId)); - assertEquals(materialId, materialsInitialData.getBatchMaterial(batchId)); - assertEquals(amount, materialsInitialData.getBatchAmount(batchId)); - assertEquals(materialsProducerId, materialsInitialData.getBatchMaterialsProducer(batchId)); - - // precondition tests - - // if the batch id is null - ContractException contractException = assertThrows(ContractException.class, () -> MaterialsPluginData.builder().addBatch(null, materialId, amount, materialsProducerId)); - assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); - - // if the material id is null - contractException = assertThrows(ContractException.class, () -> MaterialsPluginData.builder().addBatch(batchId, null, amount, materialsProducerId)); - assertEquals(MaterialsError.NULL_MATERIAL_ID, contractException.getErrorType()); - - // if the material amount is infinite - contractException = assertThrows(ContractException.class, () -> MaterialsPluginData.builder().addBatch(batchId, materialId, Double.POSITIVE_INFINITY, materialsProducerId)); - assertEquals(MaterialsError.NON_FINITE_MATERIAL_AMOUNT, contractException.getErrorType()); - - // if the material amount is negative - contractException = assertThrows(ContractException.class, () -> MaterialsPluginData.builder().addBatch(batchId, materialId, -1, materialsProducerId)); - assertEquals(MaterialsError.NEGATIVE_MATERIAL_AMOUNT, contractException.getErrorType()); - - // if the materials producer id is null - contractException = assertThrows(ContractException.class, () -> MaterialsPluginData.builder().addBatch(batchId, materialId, amount, null)); - assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(target = MaterialsPluginData.Builder.class, name = "addBatchToStage", args = { StageId.class, BatchId.class }) - public void testAddBatchToStage() { - BatchId batchId = new BatchId(456); - StageId stageId = new StageId(543); - MaterialId materialId = TestMaterialId.MATERIAL_1; - double amount = 16.7; - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_3; - - MaterialsPluginData materialsInitialData = MaterialsPluginData .builder()// - .addBatch(batchId, materialId, amount, materialsProducerId)// - .addBatchToStage(stageId, batchId)// - .addStage(stageId, false, materialsProducerId)// - .addMaterial(materialId)// - .addMaterialsProducerId(materialsProducerId)// - .build();// - - assertTrue(materialsInitialData.getStageBatches(stageId).contains(batchId)); - - // precondition tests - - // if the stage id is null - ContractException contractException = assertThrows(ContractException.class, () -> MaterialsPluginData.builder().addBatchToStage(null, batchId)); - assertEquals(MaterialsError.NULL_STAGE_ID, contractException.getErrorType()); - - // if the batch id is null - contractException = assertThrows(ContractException.class, () -> MaterialsPluginData.builder().addBatchToStage(stageId, null)); - assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(target = MaterialsPluginData.Builder.class, name = "addMaterial", args = { MaterialId.class }) - public void testAddMaterial() { - - MaterialId materialId = TestMaterialId.MATERIAL_1; - - MaterialsPluginData materialsInitialData = MaterialsPluginData .builder()// - .addMaterial(materialId)// - .build();// - - assertTrue(materialsInitialData.getMaterialIds().contains(materialId)); - - // precondition tests - - // if the material id is null - ContractException contractException = assertThrows(ContractException.class, () -> MaterialsPluginData.builder().addMaterial(null)); - assertEquals(MaterialsError.NULL_MATERIAL_ID, contractException.getErrorType()); - - // if the material was previously added - contractException = assertThrows(ContractException.class, () -> { - MaterialsPluginData.builder().addMaterial(materialId).addMaterial(materialId); - }); - assertEquals(MaterialsError.DUPLICATE_MATERIAL, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(target = MaterialsPluginData.Builder.class, name = "addMaterialsProducerId", args = { MaterialsProducerId.class, Supplier.class }) - public void testAddMaterialsProducerId() { - MaterialsProducerId materialsProducerId1 = TestMaterialsProducerId.MATERIALS_PRODUCER_1; - MaterialsProducerId materialsProducerId2 = TestMaterialsProducerId.MATERIALS_PRODUCER_2; - - MaterialsPluginData materialsInitialData = MaterialsPluginData .builder()// - .addMaterialsProducerId(materialsProducerId1)// - .addMaterialsProducerId(materialsProducerId2)// - .build();// - - // show that the materials producer ids were added - assertTrue(materialsInitialData.getMaterialsProducerIds().contains(materialsProducerId1)); - - assertTrue(materialsInitialData.getMaterialsProducerIds().contains(materialsProducerId2)); - - // precondition tests - - // if the material id is null - ContractException contractException = assertThrows(ContractException.class, () -> MaterialsPluginData.builder().addMaterialsProducerId(null)); - assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(target = MaterialsPluginData.Builder.class, name = "addStage", args = { StageId.class, boolean.class, MaterialsProducerId.class }) - public void testAddStage() { - StageId stageId = new StageId(456); - boolean offered = true; - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_3; - - MaterialsPluginData materialsInitialData = MaterialsPluginData .builder()// - .addStage(stageId, offered, materialsProducerId)// - .addMaterialsProducerId(materialsProducerId)// - .build();// - assertTrue(materialsInitialData.getStageIds().contains(stageId)); - assertEquals(offered, materialsInitialData.isStageOffered(stageId)); - assertEquals(materialsProducerId, materialsInitialData.getStageMaterialsProducer(stageId)); - - offered = false; - materialsInitialData = MaterialsPluginData .builder()// - .addStage(stageId, offered, materialsProducerId)// - .addMaterialsProducerId(materialsProducerId)// - .build();// - - assertTrue(materialsInitialData.getStageIds().contains(stageId)); - assertEquals(offered, materialsInitialData.isStageOffered(stageId)); - assertEquals(materialsProducerId, materialsInitialData.getStageMaterialsProducer(stageId)); - - // precondition tests - - // if the stage id is null - ContractException contractException = assertThrows(ContractException.class, () -> MaterialsPluginData.builder().addStage(null, true, materialsProducerId)); - assertEquals(MaterialsError.NULL_STAGE_ID, contractException.getErrorType()); - - // if the materials producer id is null - contractException = assertThrows(ContractException.class, () -> MaterialsPluginData.builder().addStage(stageId, true, null)); - assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(target = MaterialsPluginData.Builder.class, name = "defineBatchProperty", args = { MaterialId.class, BatchPropertyId.class, PropertyDefinition.class }) - public void testDefineBatchProperty() { - - MaterialsPluginData.Builder builder = MaterialsPluginData.builder();// - for (TestMaterialId testMaterialId : TestMaterialId.values()) { - builder.addMaterial(testMaterialId); - } - for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.values()) { - builder.defineBatchProperty(testBatchPropertyId.getTestMaterialId(), testBatchPropertyId, testBatchPropertyId.getPropertyDefinition()); - } - MaterialsPluginData materialsInitialData = builder.build();// - - for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.values()) { - TestMaterialId testMaterialId = testBatchPropertyId.getTestMaterialId(); - assertTrue(materialsInitialData.getBatchPropertyIds(testMaterialId).contains(testBatchPropertyId)); - PropertyDefinition actualPropertyDefinition = materialsInitialData.getBatchPropertyDefinition(testMaterialId, testBatchPropertyId); - PropertyDefinition expectedPropertyDefinition = testBatchPropertyId.getPropertyDefinition(); - assertEquals(expectedPropertyDefinition, actualPropertyDefinition); - } - - // precondition tests - - TestBatchPropertyId testBatchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK; - TestMaterialId testMaterialId = testBatchPropertyId.getTestMaterialId(); - PropertyDefinition propertyDefinition = testBatchPropertyId.getPropertyDefinition(); - - // if the batch property id is null - ContractException contractException = assertThrows(ContractException.class, () -> MaterialsPluginData.builder().defineBatchProperty(testMaterialId, null, propertyDefinition)); - assertEquals(MaterialsError.NULL_BATCH_PROPERTY_ID, contractException.getErrorType()); - - // if the material id is null - contractException = assertThrows(ContractException.class, () -> MaterialsPluginData.builder().defineBatchProperty(null, testBatchPropertyId, propertyDefinition)); - assertEquals(MaterialsError.NULL_MATERIAL_ID, contractException.getErrorType()); - - // if the property definition is null - contractException = assertThrows(ContractException.class, () -> MaterialsPluginData.builder().defineBatchProperty(testMaterialId, testBatchPropertyId, null)); - assertEquals(PropertyError.NULL_PROPERTY_DEFINITION, contractException.getErrorType()); - - // if the property definition was previously defined - contractException = assertThrows(ContractException.class, () -> { - MaterialsPluginData .builder()// - .defineBatchProperty(testMaterialId, testBatchPropertyId, propertyDefinition)// - .defineBatchProperty(testMaterialId, testBatchPropertyId, propertyDefinition);// - }); - assertEquals(MaterialsError.DUPLICATE_BATCH_PROPERTY_DEFINITION, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(target = MaterialsPluginData.Builder.class, name = "defineMaterialsProducerProperty", args = { MaterialsProducerPropertyId.class, PropertyDefinition.class }) - public void testDefineMaterialsProducerProperty() { - MaterialsPluginData.Builder builder = MaterialsPluginData.builder();// - - for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId.values()) { - builder.defineMaterialsProducerProperty(testMaterialsProducerPropertyId, testMaterialsProducerPropertyId.getPropertyDefinition()); - } - MaterialsPluginData materialsInitialData = builder.build();// - - for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId.values()) { - assertTrue(materialsInitialData.getMaterialsProducerPropertyIds().contains(testMaterialsProducerPropertyId)); - PropertyDefinition actualPropertyDefinition = materialsInitialData.getMaterialsProducerPropertyDefinition(testMaterialsProducerPropertyId); - PropertyDefinition expectedPropertyDefinition = testMaterialsProducerPropertyId.getPropertyDefinition(); - assertEquals(expectedPropertyDefinition, actualPropertyDefinition); - } - - // precondition tests - - TestMaterialsProducerPropertyId testMaterialsProducerPropertyId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK; - - PropertyDefinition propertyDefinition = testMaterialsProducerPropertyId.getPropertyDefinition(); - - // if the materials producer property id is null - ContractException contractException = assertThrows(ContractException.class, () -> MaterialsPluginData.builder().defineMaterialsProducerProperty(null, propertyDefinition)); - assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_PROPERTY_ID, contractException.getErrorType()); - - // if the property definition is null - contractException = assertThrows(ContractException.class, () -> MaterialsPluginData.builder().defineMaterialsProducerProperty(testMaterialsProducerPropertyId, null)); - assertEquals(PropertyError.NULL_PROPERTY_DEFINITION, contractException.getErrorType()); - - // if the materials producer property was previously defined - contractException = assertThrows(ContractException.class, () -> { - MaterialsPluginData .builder()// - .defineMaterialsProducerProperty(testMaterialsProducerPropertyId, propertyDefinition)// - .defineMaterialsProducerProperty(testMaterialsProducerPropertyId, propertyDefinition);// - }); - assertEquals(MaterialsError.DUPLICATE_MATERIALS_PRODUCER_PROPERTY_DEFINITION, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(target = MaterialsPluginData.Builder.class, name = "setBatchPropertyValue", args = { BatchId.class, BatchPropertyId.class, Object.class }) - public void testSetBatchPropertyValue() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4884114879424388887L); - - /* - * Add 30 batches with about half of the batch properties being set to - * randomized values and the other half set to the default for the - * property definition - */ - MaterialsPluginData.Builder builder = MaterialsPluginData.builder();// - - for (TestMaterialId testMaterialId : TestMaterialId.values()) { - builder.addMaterial(testMaterialId); - } - for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.values()) { - builder.defineBatchProperty(testBatchPropertyId.getTestMaterialId(), testBatchPropertyId, testBatchPropertyId.getPropertyDefinition()); - } - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - builder.addMaterialsProducerId(testMaterialsProducerId); - } - - // create a container to hold expected batch property values - Map expectedBatchPropertyValues = new LinkedHashMap<>(); - - // add the batches - for (int i = 0; i < 30; i++) { - BatchId batchId = new BatchId(i); - TestMaterialId testMaterialId = TestMaterialId.getRandomMaterialId(randomGenerator); - builder.addBatch(batchId, testMaterialId, randomGenerator.nextDouble(), TestMaterialsProducerId.getRandomMaterialsProducerId(randomGenerator)); - for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.getTestBatchPropertyIds(testMaterialId)) { - MultiKey multiKey = new MultiKey(batchId, testBatchPropertyId); - Object propertyValue; - if (randomGenerator.nextBoolean()) { - propertyValue = testBatchPropertyId.getRandomPropertyValue(randomGenerator); - builder.setBatchPropertyValue(batchId, testBatchPropertyId, propertyValue); - - } else { - propertyValue = testBatchPropertyId.getPropertyDefinition().getDefaultValue().get(); - } - expectedBatchPropertyValues.put(multiKey, propertyValue); - } - } - - // build the MaterialsInitialization - MaterialsPluginData materialsInitialData = builder.build();// - - // show that the MaterialsInitialization returns the expected batch - // property values - for (MultiKey multiKey : expectedBatchPropertyValues.keySet()) { - BatchId batchid = multiKey.getKey(0); - BatchPropertyId batchPropertyId = multiKey.getKey(1); - Object expectedValue = expectedBatchPropertyValues.get(multiKey); - Object actualValue = materialsInitialData.getBatchPropertyValue(batchid, batchPropertyId); - assertEquals(expectedValue, actualValue); - } - - // precondition tests - - BatchId batchId = new BatchId(0); - TestBatchPropertyId testBatchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK; - Object propertyValue = 17; - - // if the batch id is null - ContractException contractException = assertThrows(ContractException.class, () -> MaterialsPluginData.builder().setBatchPropertyValue(null, testBatchPropertyId, propertyValue)); - assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); - - // if the batch property id is null - contractException = assertThrows(ContractException.class, () -> MaterialsPluginData.builder().setBatchPropertyValue(batchId, null, propertyValue)); - assertEquals(MaterialsError.NULL_BATCH_PROPERTY_ID, contractException.getErrorType()); - - // if the batch property value is null - contractException = assertThrows(ContractException.class, () -> MaterialsPluginData.builder().setBatchPropertyValue(batchId, testBatchPropertyId, null)); - assertEquals(MaterialsError.NULL_BATCH_PROPERTY_VALUE, contractException.getErrorType()); - - // if the batch property value was previously set - contractException = assertThrows(ContractException.class, () -> { - MaterialsPluginData .builder()// - .setBatchPropertyValue(batchId, testBatchPropertyId, propertyValue)// - .setBatchPropertyValue(batchId, testBatchPropertyId, propertyValue);// - }); - assertEquals(MaterialsError.DUPLICATE_BATCH_PROPERTY_VALUE_ASSIGNMENT, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(target = MaterialsPluginData.Builder.class, name = "setMaterialsProducerPropertyValue", args = { MaterialsProducerId.class, MaterialsProducerPropertyId.class, Object.class }) - public void testSetMaterialsProducerPropertyValue() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5680332692938057510L); - MaterialsPluginData.Builder builder = MaterialsPluginData.builder();// - - for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId.values()) { - builder.defineMaterialsProducerProperty(testMaterialsProducerPropertyId, testMaterialsProducerPropertyId.getPropertyDefinition()); - } - - Map expectedPropertyValues = new LinkedHashMap<>(); - - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - builder.addMaterialsProducerId(testMaterialsProducerId); - for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId.values()) { - MultiKey multiKey = new MultiKey(testMaterialsProducerId, testMaterialsProducerPropertyId); - Object propertyValue; - if (randomGenerator.nextBoolean()) { - propertyValue = testMaterialsProducerPropertyId.getRandomPropertyValue(randomGenerator); - builder.setMaterialsProducerPropertyValue(testMaterialsProducerId, testMaterialsProducerPropertyId, propertyValue); - } else { - propertyValue = testMaterialsProducerPropertyId.getPropertyDefinition().getDefaultValue().get(); - } - expectedPropertyValues.put(multiKey, propertyValue); - } - } - - MaterialsPluginData materialsInitialData = builder.build();// - - for (MultiKey multiKey : expectedPropertyValues.keySet()) { - Object expectedValue = expectedPropertyValues.get(multiKey); - TestMaterialsProducerId testMaterialsProducerId = multiKey.getKey(0); - TestMaterialsProducerPropertyId testMaterialsProducerPropertyId = multiKey.getKey(1); - Object actualValue = materialsInitialData.getMaterialsProducerPropertyValue(testMaterialsProducerId, testMaterialsProducerPropertyId); - assertEquals(expectedValue, actualValue); - } - - // precondition tests - - TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; - TestMaterialsProducerPropertyId testMaterialsProducerPropertyId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK; - Object propertyValue = 45.6; - - // if the materials producer id is null - ContractException contractException = assertThrows(ContractException.class, - () -> MaterialsPluginData.builder().setMaterialsProducerPropertyValue(null, testMaterialsProducerPropertyId, propertyValue)); - assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - - // if the materials producer property id is null - contractException = assertThrows(ContractException.class, () -> MaterialsPluginData.builder().setMaterialsProducerPropertyValue(testMaterialsProducerId, null, propertyValue)); - assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_PROPERTY_ID, contractException.getErrorType()); - - // if the materials producer property value is null - contractException = assertThrows(ContractException.class, - () -> MaterialsPluginData.builder().setMaterialsProducerPropertyValue(testMaterialsProducerId, testMaterialsProducerPropertyId, null)); - assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_PROPERTY_VALUE, contractException.getErrorType()); - - // if the materials producer property value was previously set - contractException = assertThrows(ContractException.class, () -> { - MaterialsPluginData .builder()// - .setMaterialsProducerPropertyValue(testMaterialsProducerId, testMaterialsProducerPropertyId, propertyValue)// - .setMaterialsProducerPropertyValue(testMaterialsProducerId, testMaterialsProducerPropertyId, propertyValue); - }); - assertEquals(MaterialsError.DUPLICATE_MATERIALS_PRODUCER_PROPERTY_VALUE_ASSIGNMENT, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(target = MaterialsPluginData.Builder.class, name = "setMaterialsProducerResourceLevel", args = { MaterialsProducerId.class, ResourceId.class, long.class }) - public void testSetMaterialsProducerResourceLevel() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3277582868385203332L); - - Map expectedResourceLevels = new LinkedHashMap<>(); - - MaterialsPluginData.Builder builder = MaterialsPluginData.builder(); - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - builder.addMaterialsProducerId(testMaterialsProducerId); - for (TestResourceId testResourceId : TestResourceId.values()) { - MultiKey multiKey = new MultiKey(testMaterialsProducerId, testResourceId); - long level = 0; - if (randomGenerator.nextBoolean()) { - level = randomGenerator.nextInt(100); - builder.setMaterialsProducerResourceLevel(testMaterialsProducerId, testResourceId, level); - } - expectedResourceLevels.put(multiKey, level); - } - } - - MaterialsPluginData materialsInitialData = builder.build(); - - for (MultiKey multiKey : expectedResourceLevels.keySet()) { - long expectedValue = expectedResourceLevels.get(multiKey); - TestMaterialsProducerId testMaterialsProducerId = multiKey.getKey(0); - TestResourceId testResourceId = multiKey.getKey(1); - Long actualValue = materialsInitialData.getMaterialsProducerResourceLevel(testMaterialsProducerId, testResourceId); - assertEquals(expectedValue, actualValue); - } - - // precondition tests - - TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; - TestResourceId testResourceId = TestResourceId.RESOURCE_3; - long level = 345; - - // if the materials producer id is null - ContractException contractException = assertThrows(ContractException.class, () -> MaterialsPluginData.builder().setMaterialsProducerResourceLevel(null, testResourceId, level)); - assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - - // if the resource id is null - contractException = assertThrows(ContractException.class, () -> MaterialsPluginData.builder().setMaterialsProducerResourceLevel(testMaterialsProducerId, null, level)); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - - // if the resource amount is negative - contractException = assertThrows(ContractException.class, () -> MaterialsPluginData.builder().setMaterialsProducerResourceLevel(testMaterialsProducerId, testResourceId, -1)); - assertEquals(ResourceError.NEGATIVE_RESOURCE_AMOUNT, contractException.getErrorType()); - - // if the materials producer resource level was previously set - contractException = assertThrows(ContractException.class, () -> { - MaterialsPluginData .builder()// - .setMaterialsProducerResourceLevel(testMaterialsProducerId, testResourceId, level)// - .setMaterialsProducerResourceLevel(testMaterialsProducerId, testResourceId, level);// - }); - assertEquals(MaterialsError.DUPLICATE_MATERIALS_PRODUCER_RESOURCE_ASSIGNMENT, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "getBatchAmount", args = { BatchId.class }) - public void testGetBatchAmount() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6746980823689022132L); - - Map expectedBatchAmounts = new LinkedHashMap<>(); - - MaterialsPluginData.Builder builder = MaterialsPluginData.builder(); - - for (int i = 0; i < 20; i++) { - BatchId batchId = new BatchId(i); - MaterialId materialId = TestMaterialId.getRandomMaterialId(randomGenerator); - double amount = randomGenerator.nextDouble(); - TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId.getRandomMaterialsProducerId(randomGenerator); - builder.addBatch(batchId, materialId, amount, testMaterialsProducerId); - expectedBatchAmounts.put(batchId, amount); - } - - for (TestMaterialId testMaterialId : TestMaterialId.values()) { - builder.addMaterial(testMaterialId); - } - - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - builder.addMaterialsProducerId(testMaterialsProducerId); - } - - MaterialsPluginData materialsInitialData = builder.build(); - - for (BatchId batchId : expectedBatchAmounts.keySet()) { - Double expectedValue = expectedBatchAmounts.get(batchId); - Double actualAmount = materialsInitialData.getBatchAmount(batchId); - assertEquals(expectedValue, actualAmount); - } - - // precondition tests - - // if the batch id is null - ContractException contractException = assertThrows(ContractException.class, () -> materialsInitialData.getBatchAmount(null)); - assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); - - // if the batch id is unknown - contractException = assertThrows(ContractException.class, () -> materialsInitialData.getBatchAmount(new BatchId(10000000))); - assertEquals(MaterialsError.UNKNOWN_BATCH_ID, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "getBatchIds", args = {}) - public void testGetBatchIds() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1361793252807708004L); - - Set expectedBatchIds = new LinkedHashSet<>(); - - MaterialsPluginData.Builder builder = MaterialsPluginData.builder(); - - for (int i = 0; i < 20; i++) { - BatchId batchId = new BatchId(i); - MaterialId materialId = TestMaterialId.getRandomMaterialId(randomGenerator); - double amount = randomGenerator.nextDouble(); - TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId.getRandomMaterialsProducerId(randomGenerator); - builder.addBatch(batchId, materialId, amount, testMaterialsProducerId); - expectedBatchIds.add(batchId); - } - - for (TestMaterialId testMaterialId : TestMaterialId.values()) { - builder.addMaterial(testMaterialId); - } - - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - builder.addMaterialsProducerId(testMaterialsProducerId); - } - - MaterialsPluginData materialsInitialData = builder.build(); - assertEquals(expectedBatchIds, materialsInitialData.getBatchIds()); - - } - - @Test - @UnitTestMethod(name = "getBatchMaterial", args = { BatchId.class }) - public void testGetBatchMaterial() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(116943580559448312L); - - MaterialsPluginData.Builder builder = MaterialsPluginData.builder(); - - Map expectedMaterialIds = new LinkedHashMap<>(); - - for (int i = 0; i < 20; i++) { - BatchId batchId = new BatchId(i); - MaterialId materialId = TestMaterialId.getRandomMaterialId(randomGenerator); - double amount = randomGenerator.nextDouble(); - TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId.getRandomMaterialsProducerId(randomGenerator); - builder.addBatch(batchId, materialId, amount, testMaterialsProducerId); - expectedMaterialIds.put(batchId, materialId); - } - - for (TestMaterialId testMaterialId : TestMaterialId.values()) { - builder.addMaterial(testMaterialId); - } - - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - builder.addMaterialsProducerId(testMaterialsProducerId); - } - - MaterialsPluginData materialsInitialData = builder.build(); - - for (BatchId batchId : expectedMaterialIds.keySet()) { - MaterialId expectedMaterialId = expectedMaterialIds.get(batchId); - Object actualMaterialId = materialsInitialData.getBatchMaterial(batchId); - assertEquals(expectedMaterialId, actualMaterialId); - } - - // precondition tests - - // if the batch id is null - ContractException contractException = assertThrows(ContractException.class, () -> materialsInitialData.getBatchMaterial(null)); - assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); - - // if the batch id is unknown - contractException = assertThrows(ContractException.class, () -> materialsInitialData.getBatchMaterial(new BatchId(10000))); - assertEquals(MaterialsError.UNKNOWN_BATCH_ID, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "getBatchMaterialsProducer", args = { BatchId.class }) - public void testGetBatchMaterialsProducer() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4201153583410535220L); - - MaterialsPluginData.Builder builder = MaterialsPluginData.builder(); - - Map expectedMaterialProducerIds = new LinkedHashMap<>(); - - for (int i = 0; i < 20; i++) { - BatchId batchId = new BatchId(i); - MaterialId materialId = TestMaterialId.getRandomMaterialId(randomGenerator); - double amount = randomGenerator.nextDouble(); - TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId.getRandomMaterialsProducerId(randomGenerator); - builder.addBatch(batchId, materialId, amount, testMaterialsProducerId); - expectedMaterialProducerIds.put(batchId, testMaterialsProducerId); - } - - for (TestMaterialId testMaterialId : TestMaterialId.values()) { - builder.addMaterial(testMaterialId); - } - - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - builder.addMaterialsProducerId(testMaterialsProducerId); - } - - MaterialsPluginData materialsInitialData = builder.build(); - - for (BatchId batchId : expectedMaterialProducerIds.keySet()) { - MaterialsProducerId expectedMaterialsProducerId = expectedMaterialProducerIds.get(batchId); - Object actualMaterialsProducerId = materialsInitialData.getBatchMaterialsProducer(batchId); - assertEquals(expectedMaterialsProducerId, actualMaterialsProducerId); - } - - // precondition tests - - // if the batch id is null - ContractException contractException = assertThrows(ContractException.class, () -> materialsInitialData.getBatchMaterial(null)); - assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); - - // if the batch id is unknown - contractException = assertThrows(ContractException.class, () -> materialsInitialData.getBatchMaterial(new BatchId(10000))); - assertEquals(MaterialsError.UNKNOWN_BATCH_ID, contractException.getErrorType()); - } - - @Test - @UnitTestMethod(name = "getBatchPropertyDefinition", args = { MaterialId.class, BatchPropertyId.class }) - public void testGetBatchPropertyDefinition() { - - MaterialsPluginData.Builder builder = MaterialsPluginData.builder();// - - for (TestMaterialId testMaterialId : TestMaterialId.values()) { - builder.addMaterial(testMaterialId); - } - - for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.values()) { - builder.defineBatchProperty(testBatchPropertyId.getTestMaterialId(), testBatchPropertyId, testBatchPropertyId.getPropertyDefinition()); - } - - // build the MaterialsInitialization - MaterialsPluginData materialsInitialData = builder.build();// - - // show that the MaterialsInitialization returns the expected batch - // property definitions - for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.values()) { - PropertyDefinition expectedPropertyDefinition = testBatchPropertyId.getPropertyDefinition(); - TestMaterialId testMaterialId = testBatchPropertyId.getTestMaterialId(); - PropertyDefinition actualPropertyDefinition = materialsInitialData.getBatchPropertyDefinition(testMaterialId, testBatchPropertyId); - assertEquals(expectedPropertyDefinition, actualPropertyDefinition); - } - - // precondition tests - - TestMaterialId testMaterialId = TestMaterialId.MATERIAL_2; - TestBatchPropertyId testBatchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK; - - // if the material id is null - ContractException contractException = assertThrows(ContractException.class, () -> materialsInitialData.getBatchPropertyDefinition(null, testBatchPropertyId)); - assertEquals(MaterialsError.NULL_MATERIAL_ID, contractException.getErrorType()); - - // if the material id is unknown - contractException = assertThrows(ContractException.class, () -> materialsInitialData.getBatchPropertyDefinition(TestMaterialId.getUnknownMaterialId(), testBatchPropertyId)); - assertEquals(MaterialsError.UNKNOWN_MATERIAL_ID, contractException.getErrorType()); - - // if the batch property id is null - contractException = assertThrows(ContractException.class, () -> materialsInitialData.getBatchPropertyDefinition(testMaterialId, null)); - assertEquals(MaterialsError.NULL_BATCH_PROPERTY_ID, contractException.getErrorType()); - - // if the batch property id is unknown - contractException = assertThrows(ContractException.class, () -> materialsInitialData.getBatchPropertyDefinition(testMaterialId, TestBatchPropertyId.getUnknownBatchPropertyId())); - assertEquals(MaterialsError.UNKNOWN_BATCH_PROPERTY_ID, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "getBatchPropertyIds", args = { MaterialId.class }) - public void testGetBatchPropertyIds() { - - MaterialsPluginData.Builder builder = MaterialsPluginData.builder();// - - for (TestMaterialId testMaterialId : TestMaterialId.values()) { - builder.addMaterial(testMaterialId); - } - - for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.values()) { - builder.defineBatchProperty(testBatchPropertyId.getTestMaterialId(), testBatchPropertyId, testBatchPropertyId.getPropertyDefinition()); - } - - // build the MaterialsInitialization - MaterialsPluginData materialsInitialData = builder.build();// - - // show that the MaterialsInitialization returns the expected batch - // property ids - - for (TestMaterialId testMaterialId : TestMaterialId.values()) { - Set expectedBatchPropertyIds = TestBatchPropertyId.getTestBatchPropertyIds(testMaterialId); - Set actualBatchPropertyIds = materialsInitialData.getBatchPropertyIds(testMaterialId); - assertEquals(expectedBatchPropertyIds, actualBatchPropertyIds); - } - - // precondition tests - - // if the material id is null - ContractException contractException = assertThrows(ContractException.class, () -> materialsInitialData.getBatchPropertyIds(null)); - assertEquals(MaterialsError.NULL_MATERIAL_ID, contractException.getErrorType()); - - // if the material id is unknown - contractException = assertThrows(ContractException.class, () -> materialsInitialData.getBatchPropertyIds(TestMaterialId.getUnknownMaterialId())); - assertEquals(MaterialsError.UNKNOWN_MATERIAL_ID, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "getBatchPropertyValue", args = { BatchId.class, BatchPropertyId.class }) - public void testGetBatchPropertyValue() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4884114879424388887L); - - /* - * Add 30 batches with about half of the batch properties being set to - * randomized values and the other half set to the default for the - * property definition - */ - MaterialsPluginData.Builder builder = MaterialsPluginData.builder();// - - for (TestMaterialId testMaterialId : TestMaterialId.values()) { - builder.addMaterial(testMaterialId); - } - for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.values()) { - builder.defineBatchProperty(testBatchPropertyId.getTestMaterialId(), testBatchPropertyId, testBatchPropertyId.getPropertyDefinition()); - } - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - builder.addMaterialsProducerId(testMaterialsProducerId); - } - - // create a container to hold expected batch property values - Map expectedBatchPropertyValues = new LinkedHashMap<>(); - - // add the batches - for (int i = 0; i < 30; i++) { - BatchId batchId = new BatchId(i); - TestMaterialId testMaterialId = TestMaterialId.getRandomMaterialId(randomGenerator); - builder.addBatch(batchId, testMaterialId, randomGenerator.nextDouble(), TestMaterialsProducerId.getRandomMaterialsProducerId(randomGenerator)); - for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.getTestBatchPropertyIds(testMaterialId)) { - MultiKey multiKey = new MultiKey(batchId, testBatchPropertyId); - Object propertyValue; - if (randomGenerator.nextBoolean()) { - propertyValue = testBatchPropertyId.getRandomPropertyValue(randomGenerator); - builder.setBatchPropertyValue(batchId, testBatchPropertyId, propertyValue); - - } else { - propertyValue = testBatchPropertyId.getPropertyDefinition().getDefaultValue().get(); - } - expectedBatchPropertyValues.put(multiKey, propertyValue); - } - } - - // build the MaterialsInitialization - MaterialsPluginData materialsInitialData = builder.build();// - - // show that the MaterialsInitialization returns the expected batch - // property values - for (MultiKey multiKey : expectedBatchPropertyValues.keySet()) { - BatchId batchid = multiKey.getKey(0); - BatchPropertyId batchPropertyId = multiKey.getKey(1); - Object expectedValue = expectedBatchPropertyValues.get(multiKey); - Object actualValue = materialsInitialData.getBatchPropertyValue(batchid, batchPropertyId); - assertEquals(expectedValue, actualValue); - } - - // precondition tests - - BatchId batchId = new BatchId(0); - TestBatchPropertyId testBatchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_2_2_INTEGER_IMMUTABLE_TRACK; - - // if the batch id is null - ContractException contractException = assertThrows(ContractException.class, () -> materialsInitialData.getBatchPropertyValue(null, testBatchPropertyId)); - assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); - - // if the batch id is unknown - contractException = assertThrows(ContractException.class, () -> materialsInitialData.getBatchPropertyValue(new BatchId(10000), testBatchPropertyId)); - assertEquals(MaterialsError.UNKNOWN_BATCH_ID, contractException.getErrorType()); - - // if the batch property id is null - contractException = assertThrows(ContractException.class, () -> materialsInitialData.getBatchPropertyValue(batchId, null)); - assertEquals(MaterialsError.NULL_BATCH_PROPERTY_ID, contractException.getErrorType()); - - // if the batch property id is unknown - contractException = assertThrows(ContractException.class, () -> materialsInitialData.getBatchPropertyValue(batchId, TestBatchPropertyId.getUnknownBatchPropertyId())); - assertEquals(MaterialsError.UNKNOWN_BATCH_PROPERTY_ID, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "getMaterialIds", args = {}) - public void testGetMaterialIds() { - - MaterialsPluginData.Builder builder = MaterialsPluginData.builder(); - - for (TestMaterialId testMaterialId : TestMaterialId.values()) { - builder.addMaterial(testMaterialId); - } - MaterialsPluginData materialsInitialData = builder.build();// - assertEquals(EnumSet.allOf(TestMaterialId.class), materialsInitialData.getMaterialIds()); - - } - - @Test - @UnitTestMethod(name = "getMaterialsProducerIds", args = {}) - public void testGetMaterialsProducerIds() { - - MaterialsPluginData.Builder builder = MaterialsPluginData.builder();// - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - builder.addMaterialsProducerId(testMaterialsProducerId); - } - MaterialsPluginData materialsInitialData = builder.build();// - - // show that the materials producer ids were added - assertEquals(EnumSet.allOf(TestMaterialsProducerId.class), materialsInitialData.getMaterialsProducerIds()); - - } - - @Test - @UnitTestMethod(name = "getMaterialsProducerPropertyDefinition", args = { MaterialsProducerPropertyId.class }) - public void testGetMaterialsProducerPropertyDefinition() { - - MaterialsPluginData.Builder builder = MaterialsPluginData.builder();// - - for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId.values()) { - builder.defineMaterialsProducerProperty(testMaterialsProducerPropertyId, testMaterialsProducerPropertyId.getPropertyDefinition()); - } - MaterialsPluginData materialsInitialData = builder.build();// - - for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId.values()) { - assertTrue(materialsInitialData.getMaterialsProducerPropertyIds().contains(testMaterialsProducerPropertyId)); - PropertyDefinition actualPropertyDefinition = materialsInitialData.getMaterialsProducerPropertyDefinition(testMaterialsProducerPropertyId); - PropertyDefinition expectedPropertyDefinition = testMaterialsProducerPropertyId.getPropertyDefinition(); - assertEquals(expectedPropertyDefinition, actualPropertyDefinition); - } - - // precondition tests - - // if the materials producer property id is null - ContractException contractException = assertThrows(ContractException.class, () -> materialsInitialData.getMaterialsProducerPropertyDefinition(null)); - assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_PROPERTY_ID, contractException.getErrorType()); - - // if the materials producer property id is unknown - contractException = assertThrows(ContractException.class, - () -> materialsInitialData.getMaterialsProducerPropertyDefinition(TestMaterialsProducerPropertyId.getUnknownMaterialsProducerPropertyId())); - assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_PROPERTY_ID, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "getMaterialsProducerPropertyIds", args = {}) - public void testGetMaterialsProducerPropertyIds() { - MaterialsPluginData.Builder builder = MaterialsPluginData.builder();// - - for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId.values()) { - builder.defineMaterialsProducerProperty(testMaterialsProducerPropertyId, testMaterialsProducerPropertyId.getPropertyDefinition()); - } - MaterialsPluginData materialsInitialData = builder.build();// - assertEquals(EnumSet.allOf(TestMaterialsProducerPropertyId.class), materialsInitialData.getMaterialsProducerPropertyIds()); - - } - - @Test - @UnitTestMethod(name = "getMaterialsProducerPropertyValue", args = { MaterialsProducerId.class, MaterialsProducerPropertyId.class }) - public void testGetMaterialsProducerPropertyValue() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(175219330466509056L); - MaterialsPluginData.Builder builder = MaterialsPluginData.builder();// - - for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId.values()) { - builder.defineMaterialsProducerProperty(testMaterialsProducerPropertyId, testMaterialsProducerPropertyId.getPropertyDefinition()); - } - - Map expectedPropertyValues = new LinkedHashMap<>(); - - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - builder.addMaterialsProducerId(testMaterialsProducerId); - for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId.values()) { - MultiKey multiKey = new MultiKey(testMaterialsProducerId, testMaterialsProducerPropertyId); - Object propertyValue; - if (randomGenerator.nextBoolean()) { - propertyValue = testMaterialsProducerPropertyId.getRandomPropertyValue(randomGenerator); - builder.setMaterialsProducerPropertyValue(testMaterialsProducerId, testMaterialsProducerPropertyId, propertyValue); - } else { - propertyValue = testMaterialsProducerPropertyId.getPropertyDefinition().getDefaultValue().get(); - } - expectedPropertyValues.put(multiKey, propertyValue); - } - } - - MaterialsPluginData materialsInitialData = builder.build();// - - for (MultiKey multiKey : expectedPropertyValues.keySet()) { - Object expectedValue = expectedPropertyValues.get(multiKey); - TestMaterialsProducerId testMaterialsProducerId = multiKey.getKey(0); - TestMaterialsProducerPropertyId testMaterialsProducerPropertyId = multiKey.getKey(1); - Object actualValue = materialsInitialData.getMaterialsProducerPropertyValue(testMaterialsProducerId, testMaterialsProducerPropertyId); - assertEquals(expectedValue, actualValue); - } - - // precondition tests - - TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; - TestMaterialsProducerPropertyId testMaterialsProducerPropertyId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK; - - // if the materials producer id is null - ContractException contractException = assertThrows(ContractException.class, () -> materialsInitialData.getMaterialsProducerPropertyValue(null, testMaterialsProducerPropertyId)); - assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - - // if the materials producer id is unknown - contractException = assertThrows(ContractException.class, - () -> materialsInitialData.getMaterialsProducerPropertyValue(TestMaterialsProducerId.getUnknownMaterialsProducerId(), testMaterialsProducerPropertyId)); - assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - - // if the materials producer property id is null - contractException = assertThrows(ContractException.class, () -> materialsInitialData.getMaterialsProducerPropertyValue(testMaterialsProducerId, null)); - assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_PROPERTY_ID, contractException.getErrorType()); - - // if the materials producer property id is unknown - contractException = assertThrows(ContractException.class, - () -> materialsInitialData.getMaterialsProducerPropertyValue(testMaterialsProducerId, TestMaterialsProducerPropertyId.getUnknownMaterialsProducerPropertyId())); - assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_PROPERTY_ID, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "getMaterialsProducerResourceLevel", args = { MaterialsProducerId.class, ResourceId.class }) - public void testGetMaterialsProducerResourceLevel() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4448010834982849838L); - - Map expectedResourceLevels = new LinkedHashMap<>(); - - MaterialsPluginData.Builder builder = MaterialsPluginData.builder(); - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - builder.addMaterialsProducerId(testMaterialsProducerId); - for (TestResourceId testResourceId : TestResourceId.values()) { - MultiKey multiKey = new MultiKey(testMaterialsProducerId, testResourceId); - long level = 0; - if (randomGenerator.nextBoolean()) { - level = randomGenerator.nextInt(100); - builder.setMaterialsProducerResourceLevel(testMaterialsProducerId, testResourceId, level); - } - expectedResourceLevels.put(multiKey, level); - } - } - - MaterialsPluginData materialsInitialData = builder.build(); - - for (MultiKey multiKey : expectedResourceLevels.keySet()) { - long expectedValue = expectedResourceLevels.get(multiKey); - TestMaterialsProducerId testMaterialsProducerId = multiKey.getKey(0); - TestResourceId testResourceId = multiKey.getKey(1); - Long actualValue = materialsInitialData.getMaterialsProducerResourceLevel(testMaterialsProducerId, testResourceId); - assertEquals(expectedValue, actualValue); - } - - // precondition tests - - TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; - TestResourceId testResourceId = TestResourceId.RESOURCE_3; - - // if the materials producer id is null - ContractException contractException = assertThrows(ContractException.class, () -> materialsInitialData.getMaterialsProducerResourceLevel(null, testResourceId)); - assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - - // if the materials producer id is unknown - contractException = assertThrows(ContractException.class, - () -> materialsInitialData.getMaterialsProducerResourceLevel(TestMaterialsProducerId.getUnknownMaterialsProducerId(), testResourceId)); - assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - - // if the resource id is unknown - contractException = assertThrows(ContractException.class, () -> materialsInitialData.getMaterialsProducerResourceLevel(testMaterialsProducerId, null)); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "getStageBatches", args = { StageId.class }) - public void testGetStageBatches() { - - MaterialsPluginData.Builder builder = MaterialsPluginData.builder(); - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(273625089589349694L); - Random random = new Random(randomGenerator.nextLong()); - - // construct a container to hold the expected stage/batch relationships - - Map> expectedRelationships = new LinkedHashMap<>(); - - // for each materials producer, add 50 batches, 10 stages and assign 0 - // to 3 batches per stage - int stageIndex = 0; - int batchIndex = 0; - - for (TestMaterialId testMaterialId : TestMaterialId.values()) { - builder.addMaterial(testMaterialId); - } - - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - - builder.addMaterialsProducerId(testMaterialsProducerId); - - List batchIds = new ArrayList<>(); - for (int i = 0; i < 50; i++) { - BatchId batchId = new BatchId(batchIndex++); - batchIds.add(batchId); - TestMaterialId testMaterialId = TestMaterialId.getRandomMaterialId(randomGenerator); - double amount = randomGenerator.nextDouble(); - builder.addBatch(batchId, testMaterialId, amount, testMaterialsProducerId); - } - - List stageIds = new ArrayList<>(); - for (int i = 0; i < 10; i++) { - StageId stageId = new StageId(stageIndex++); - stageIds.add(stageId); - expectedRelationships.put(stageId, new LinkedHashSet<>()); - boolean offered = randomGenerator.nextBoolean(); - builder.addStage(stageId, offered, testMaterialsProducerId); - } - - Collections.shuffle(batchIds, random); - for (int i = 0; i < 30; i++) { - BatchId batchId = batchIds.get(i); - StageId stageId = stageIds.get(randomGenerator.nextInt(stageIds.size())); - expectedRelationships.get(stageId).add(batchId); - builder.addBatchToStage(stageId, batchId); - } - } - - MaterialsPluginData materialsInitialData = builder.build(); - - for (StageId stageId : expectedRelationships.keySet()) { - Set expectedBatches = expectedRelationships.get(stageId); - Set actualBatches = materialsInitialData.getStageBatches(stageId); - assertEquals(expectedBatches, actualBatches); - } - - // precondition tests - - // if the stage id is null - ContractException contractException = assertThrows(ContractException.class, () -> materialsInitialData.getStageBatches(null)); - assertEquals(MaterialsError.NULL_STAGE_ID, contractException.getErrorType()); - - // if the batch id is null - contractException = assertThrows(ContractException.class, () -> materialsInitialData.getStageBatches(new StageId(10000000))); - assertEquals(MaterialsError.UNKNOWN_STAGE_ID, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "getStageIds", args = {}) - public void testGetStageIds() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3725911532254654669L); - - MaterialsPluginData.Builder builder = MaterialsPluginData.builder(); - - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - builder.addMaterialsProducerId(testMaterialsProducerId); - } - Set expectedStageIds = new LinkedHashSet<>(); - for (int i = 0; i < 100; i++) { - StageId stageId = new StageId(i); - expectedStageIds.add(stageId); - boolean offered = randomGenerator.nextBoolean(); - TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId.getRandomMaterialsProducerId(randomGenerator); - builder.addStage(stageId, offered, testMaterialsProducerId); - } - - MaterialsPluginData materialsInitialData = builder.build();// - - assertEquals(expectedStageIds, materialsInitialData.getStageIds()); - - } - - @Test - @UnitTestMethod(name = "getStageMaterialsProducer", args = { StageId.class }) - public void testGetStageMaterialsProducer() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4722411464538864709L); - - MaterialsPluginData.Builder builder = MaterialsPluginData.builder(); - - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - builder.addMaterialsProducerId(testMaterialsProducerId); - } - - Map expectedMaterialsProducerIds = new LinkedHashMap<>(); - for (int i = 0; i < 100; i++) { - StageId stageId = new StageId(i); - boolean offered = randomGenerator.nextBoolean(); - TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId.getRandomMaterialsProducerId(randomGenerator); - expectedMaterialsProducerIds.put(stageId, testMaterialsProducerId); - builder.addStage(stageId, offered, testMaterialsProducerId); - } - - MaterialsPluginData materialsInitialData = builder.build();// - - for (StageId stageId : expectedMaterialsProducerIds.keySet()) { - MaterialsProducerId expectedMaterialsProducerId = expectedMaterialsProducerIds.get(stageId); - MaterialsProducerId actualMaterialsProducerId = materialsInitialData.getStageMaterialsProducer(stageId); - assertEquals(expectedMaterialsProducerId, actualMaterialsProducerId); - } - - // precondition tests - - // if the stage id is null - ContractException contractException = assertThrows(ContractException.class, () -> materialsInitialData.getStageMaterialsProducer(null)); - assertEquals(MaterialsError.NULL_STAGE_ID, contractException.getErrorType()); - - // if the stage id is unknown - contractException = assertThrows(ContractException.class, () -> materialsInitialData.getStageMaterialsProducer(new StageId(10000000))); - assertEquals(MaterialsError.UNKNOWN_STAGE_ID, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "isStageOffered", args = { StageId.class }) - public void testIsStageOffered() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1042601351499648378L); - - MaterialsPluginData.Builder builder = MaterialsPluginData.builder(); - - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - builder.addMaterialsProducerId(testMaterialsProducerId); - } - - Map expectedStageOffers = new LinkedHashMap<>(); - for (int i = 0; i < 100; i++) { - StageId stageId = new StageId(i); - boolean offered = randomGenerator.nextBoolean(); - TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId.getRandomMaterialsProducerId(randomGenerator); - expectedStageOffers.put(stageId, offered); - builder.addStage(stageId, offered, testMaterialsProducerId); - } - - MaterialsPluginData materialsInitialData = builder.build();// - - for (StageId stageId : expectedStageOffers.keySet()) { - Boolean expectedOfferedState = expectedStageOffers.get(stageId); - Boolean actualOfferedState = materialsInitialData.isStageOffered(stageId); - assertEquals(expectedOfferedState, actualOfferedState); - } - - // precondition tests - - // if the stage id is null - ContractException contractException = assertThrows(ContractException.class, () -> materialsInitialData.isStageOffered(null)); - assertEquals(MaterialsError.NULL_STAGE_ID, contractException.getErrorType()); - - // if the stage id is unknown - contractException = assertThrows(ContractException.class, () -> materialsInitialData.isStageOffered(new StageId(10000000))); - assertEquals(MaterialsError.UNKNOWN_STAGE_ID, contractException.getErrorType()); - } - - @Test - @UnitTestMethod(name = "getResourceIds", args = {}) - public void testGetResourceIds() { - - MaterialsPluginData.Builder builder = MaterialsPluginData.builder(); - - MaterialsPluginData materialsInitialData = builder.build();// - - assertTrue(materialsInitialData.getResourceIds().isEmpty()); - - TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; - long amount = 45L; - for (TestResourceId testResourceId : TestResourceId.values()) { - builder.setMaterialsProducerResourceLevel(testMaterialsProducerId, testResourceId, amount++); - testMaterialsProducerId = testMaterialsProducerId.next(); - } - - for (TestMaterialsProducerId producerId : TestMaterialsProducerId.values()) { - builder.addMaterialsProducerId(producerId); - } - - materialsInitialData = builder.build(); - - assertEquals(EnumSet.allOf(TestResourceId.class), materialsInitialData.getResourceIds()); - } - - @Test - @UnitTestMethod(name = "getCloneBuilder", args = {}) - public void testGetCloneBuilder() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1064212917574117854L); - - MaterialsPluginData.Builder builder = MaterialsPluginData.builder(); - - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - builder.addMaterialsProducerId(testMaterialsProducerId); - } - for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId.values()) { - builder.defineMaterialsProducerProperty(testMaterialsProducerPropertyId, testMaterialsProducerPropertyId.getPropertyDefinition()); - } - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId.values()) { - if (randomGenerator.nextBoolean()) { - builder.setMaterialsProducerPropertyValue(testMaterialsProducerId, testMaterialsProducerPropertyId, testMaterialsProducerPropertyId.getRandomPropertyValue(randomGenerator)); - } - } - } - Map> stageMap = new LinkedHashMap<>(); - for (int i = 0; i < 30; i++) { - boolean offered = i % 2 == 0; - StageId stageId = new StageId(i); - TestMaterialsProducerId materialsProducerId = TestMaterialsProducerId.getRandomMaterialsProducerId(randomGenerator); - builder.addStage(stageId, offered, materialsProducerId); - List list = stageMap.get(materialsProducerId); - if (list == null) { - list = new ArrayList<>(); - stageMap.put(materialsProducerId, list); - } - list.add(stageId); - } - - for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.values()) { - builder.defineBatchProperty(testBatchPropertyId.getTestMaterialId(), testBatchPropertyId, testBatchPropertyId.getPropertyDefinition()); - } - - for (TestMaterialId testMaterialId : TestMaterialId.values()) { - builder.addMaterial(testMaterialId); - } - - for (int i = 0; i < 150; i++) { - BatchId batchId = new BatchId(i); - TestMaterialsProducerId materialsProducerId = TestMaterialsProducerId.getRandomMaterialsProducerId(randomGenerator); - TestMaterialId randomMaterialId = TestMaterialId.getRandomMaterialId(randomGenerator); - builder.addBatch(batchId, randomMaterialId, randomGenerator.nextDouble(), materialsProducerId); - if (randomGenerator.nextBoolean()) { - List stages = stageMap.get(materialsProducerId); - if (!stages.isEmpty()) { - StageId stageId = stages.get(randomGenerator.nextInt(stages.size())); - builder.addBatchToStage(stageId, batchId); - } - } - - - for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.getTestBatchPropertyIds(randomMaterialId)) { - if (randomGenerator.nextBoolean()) { - builder.setBatchPropertyValue(batchId, testBatchPropertyId, testBatchPropertyId.getRandomPropertyValue(randomGenerator)); - } - } - } - - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - for (TestResourceId testResourceId : TestResourceId.values()) { - if (randomGenerator.nextBoolean()) { - int amount = randomGenerator.nextInt(10); - builder.setMaterialsProducerResourceLevel(testMaterialsProducerId, testResourceId, amount); - } - } - } - - MaterialsPluginData materialsPluginData = builder.build(); - // show the clone builder is not null - PluginDataBuilder cloneBuilder = materialsPluginData.getCloneBuilder(); - assertNotNull(cloneBuilder); - // show that the clone plugin data is not null - PluginData pluginData = cloneBuilder.build(); - assertNotNull(false); - - // show that the clone plugin data has the correct type - assertTrue(pluginData instanceof MaterialsPluginData); - - MaterialsPluginData clonePluginData = (MaterialsPluginData) pluginData; - - // show that the two plugin datas have the same material producer ids - assertEquals(materialsPluginData.getMaterialsProducerIds(), clonePluginData.getMaterialsProducerIds()); - - // show that the two plugin datas have the same material producer - // property ids - assertEquals(materialsPluginData.getMaterialsProducerPropertyIds(), clonePluginData.getMaterialsProducerPropertyIds()); - - // show that the two plugin datas have the same material producer - // property definitions - for (MaterialsProducerPropertyId materialsProducerPropertyId : materialsPluginData.getMaterialsProducerPropertyIds()) { - PropertyDefinition expectedPropertyDefinition = materialsPluginData.getMaterialsProducerPropertyDefinition(materialsProducerPropertyId); - PropertyDefinition actualPropertyDefinition = clonePluginData.getMaterialsProducerPropertyDefinition(materialsProducerPropertyId); - assertEquals(expectedPropertyDefinition, actualPropertyDefinition); - } - - // show that the two plugin datas have the same material producer - // property values - for (MaterialsProducerId materialsProducerId : materialsPluginData.getMaterialsProducerIds()) { - for (MaterialsProducerPropertyId materialsProducerPropertyId : materialsPluginData.getMaterialsProducerPropertyIds()) { - Object expectedValue = materialsPluginData.getMaterialsProducerPropertyValue(materialsProducerId, materialsProducerPropertyId); - Object actualValue = clonePluginData.getMaterialsProducerPropertyValue(materialsProducerId, materialsProducerPropertyId); - assertEquals(expectedValue, actualValue); - } - } - - // show that the two plugin datas have the same resource ids - assertEquals(materialsPluginData.getResourceIds(), clonePluginData.getResourceIds()); - - // show that the two plugin datas have the same stage ids - assertEquals(materialsPluginData.getStageIds(), clonePluginData.getStageIds()); - - // show that the two plugin datas have the same stage offer states - for (StageId stageId : materialsPluginData.getStageIds()) { - assertEquals(materialsPluginData.isStageOffered(stageId), clonePluginData.isStageOffered(stageId)); - } - - // show that the two plugin datas have the same stage material producers - for (StageId stageId : materialsPluginData.getStageIds()) { - MaterialsProducerId expectedMaterialsProducerId = materialsPluginData.getStageMaterialsProducer(stageId); - MaterialsProducerId actualMaterialsProducerId = clonePluginData.getStageMaterialsProducer(stageId); - assertEquals(expectedMaterialsProducerId, actualMaterialsProducerId); - } - - // show that the two plugin datas have the same stage batches - for (StageId stageId : materialsPluginData.getStageIds()) { - Set expectedStageBatches = materialsPluginData.getStageBatches(stageId); - Set actualStageBatches = clonePluginData.getStageBatches(stageId); - assertEquals(expectedStageBatches, actualStageBatches); - } - - // show that the two plugin datas have the same material ids - assertEquals(materialsPluginData.getMaterialIds(), clonePluginData.getMaterialIds()); - - // show that the two plugin datas have the same material producer - // resource levels - for (TestResourceId testResourceId : TestResourceId.values()) { - for (MaterialsProducerId materialsProducerId : materialsPluginData.getMaterialsProducerIds()) { - Long expectedLevel = materialsPluginData.getMaterialsProducerResourceLevel(materialsProducerId, testResourceId); - Long actualLevel = clonePluginData.getMaterialsProducerResourceLevel(materialsProducerId, testResourceId); - assertEquals(expectedLevel, actualLevel); - } - } - - // show that the two plugin datas have the same batch property ids - for (TestMaterialId testMaterialId : TestMaterialId.values()) { - assertEquals(materialsPluginData.getBatchPropertyIds(testMaterialId), clonePluginData.getBatchPropertyIds(testMaterialId)); - } - - // show that the two plugin datas have the same batch property definitions - for (TestMaterialId testMaterialId : TestMaterialId.values()) { - for(BatchPropertyId batchPropertyId : materialsPluginData.getBatchPropertyIds(testMaterialId)) { - PropertyDefinition expectedPropertyDefinition = materialsPluginData.getBatchPropertyDefinition(testMaterialId, batchPropertyId); - PropertyDefinition actualPropertyDefinition = clonePluginData.getBatchPropertyDefinition(testMaterialId, batchPropertyId); - assertEquals(expectedPropertyDefinition, actualPropertyDefinition); - } - } - - // show that the two plugin datas have the same batch ids - assertEquals(materialsPluginData.getBatchIds(), clonePluginData.getBatchIds()); - for(BatchId batchId : materialsPluginData.getBatchIds()) { - //show that the amounts are equal - Double expectedAmount = materialsPluginData.getBatchAmount(batchId); - Double actualAmount = clonePluginData.getBatchAmount(batchId); - assertEquals(expectedAmount, actualAmount); - - //show that the materials are equal - MaterialId expectedMaterialId = materialsPluginData.getBatchMaterial(batchId); - MaterialId actualMaterialId = clonePluginData.getBatchMaterial(batchId); - assertEquals(expectedMaterialId, actualMaterialId); - - //show that the materials producers - MaterialsProducerId expectedMaterialsProducerId = materialsPluginData.getBatchMaterialsProducer(batchId); - MaterialsProducerId actualMaterialsProducerId = clonePluginData.getBatchMaterialsProducer(batchId); - assertEquals(expectedMaterialsProducerId, actualMaterialsProducerId); - - for(BatchPropertyId batchPropertyId : materialsPluginData.getBatchPropertyIds(expectedMaterialId)){ - Object expectedValue = materialsPluginData.getBatchPropertyValue(batchId, batchPropertyId); - Object actualValue = clonePluginData.getBatchPropertyValue(batchId, batchPropertyId); - assertEquals(expectedValue, actualValue); - } - } - - } - -} diff --git a/gcm3/src/test/java/plugins/materials/actors/AT_BatchStatusReport.java b/gcm3/src/test/java/plugins/materials/actors/AT_BatchStatusReport.java deleted file mode 100644 index 51ae43b46..000000000 --- a/gcm3/src/test/java/plugins/materials/actors/AT_BatchStatusReport.java +++ /dev/null @@ -1,240 +0,0 @@ -package plugins.materials.actors; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Optional; -import java.util.Random; -import java.util.Set; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.ActorContext; -import nucleus.Plugin; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import plugins.materials.datamangers.MaterialsDataManager; -import plugins.materials.support.BatchId; -import plugins.materials.support.MaterialId; -import plugins.materials.support.MaterialsProducerId; -import plugins.materials.support.StageId; -import plugins.materials.testsupport.MaterialsActionSupport; -import plugins.materials.testsupport.TestBatchPropertyId; -import plugins.materials.testsupport.TestMaterialId; -import plugins.materials.testsupport.TestMaterialsProducerId; -import plugins.reports.support.ReportHeader; -import plugins.reports.support.ReportId; -import plugins.reports.support.ReportItem; -import plugins.reports.support.ReportItem.Builder; -import plugins.reports.support.SimpleReportId; -import plugins.stochastics.StochasticsDataManager; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = BatchStatusReport.class) - -public final class AT_BatchStatusReport { - - private ReportItem getReportItemFromBatch(ActorContext agentContext, BatchId batchId) { - MaterialsDataManager materialsDataManager = agentContext.getDataManager(MaterialsDataManager.class); - MaterialsProducerId batchProducer = materialsDataManager.getBatchProducer(batchId); - MaterialId materialId = materialsDataManager.getBatchMaterial(batchId); - double amount = materialsDataManager.getBatchAmount(batchId); - Optional optionalStageId = materialsDataManager.getBatchStageId(batchId); - String stageString = ""; - if (optionalStageId.isPresent()) { - stageString = optionalStageId.get().toString(); - } - - ReportItem reportItem = getReportItem(agentContext.getTime(), // - batchId, // - batchProducer, // - stageString, // - materialId, // - amount// - );// - - return reportItem; - } - - @Test - @UnitTestMethod(name = "init", args = {}) - public void testInit() { - - Set expectedReportItems = new LinkedHashSet<>(); - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - double actionTime = 0; - - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - - // add a few batches - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - for (int i = 0; i < 20; i++) { - TestMaterialId materialId = TestMaterialId.getRandomMaterialId(randomGenerator); - double amount = randomGenerator.nextDouble(); - BatchId batchId = materialsDataManager.addBatch(testMaterialsProducerId,materialId, amount); - expectedReportItems.add(getReportItemFromBatch(c, batchId)); - } - - })); - - // transfer material between batches - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { - - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - for (TestMaterialId testMaterialId : TestMaterialId.values()) { - List batches = materialsDataManager.getInventoryBatchesByMaterialId(testMaterialsProducerId, testMaterialId); - - if (batches.size() > 1) { - for (int i = 0; i < batches.size(); i++) { - int index1 = randomGenerator.nextInt(batches.size()); - int index2 = randomGenerator.nextInt(batches.size() - 1); - if (index2 >= index1) { - index2++; - } - BatchId batchId1 = batches.get(index1); - BatchId batchId2 = batches.get(index2); - double portion = randomGenerator.nextDouble(); - double amount = materialsDataManager.getBatchAmount(batchId1); - double transferAmount = amount *= portion; - materialsDataManager.transferMaterialBetweenBatches(batchId1, batchId2, transferAmount); - expectedReportItems.add(getReportItemFromBatch(c, batchId1)); - expectedReportItems.add(getReportItemFromBatch(c, batchId2)); - } - } - } - })); - - // destroy some batches - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - Random random = new Random(randomGenerator.nextLong()); - List inventoryBatches = materialsDataManager.getInventoryBatches(testMaterialsProducerId); - Collections.shuffle(inventoryBatches, random); - int destructionCount = inventoryBatches.size() / 5; - for (int i = 0; i < destructionCount; i++) { - BatchId batchId = inventoryBatches.get(i); - expectedReportItems.add(getReportItemFromBatch(c, batchId)); - materialsDataManager.removeBatch(batchId); - } - })); - - // set some batch property values - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - List inventoryBatches = materialsDataManager.getInventoryBatches(testMaterialsProducerId); - - for (BatchId batchId : inventoryBatches) { - TestMaterialId materialId = materialsDataManager.getBatchMaterial(batchId); - TestBatchPropertyId propertyId = TestBatchPropertyId.getRandomMutableBatchPropertyId(materialId, randomGenerator); - Object value = propertyId.getRandomPropertyValue(randomGenerator); - materialsDataManager.setBatchPropertyValue(batchId, propertyId, value); - expectedReportItems.add(getReportItemFromBatch(c, batchId)); - } - })); - - // put some of the batches on stages - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - List stageIds = new ArrayList<>(); - for (int i = 0; i < 3; i++) { - StageId stageId = materialsDataManager.addStage(testMaterialsProducerId); - stageIds.add(stageId); - } - - List inventoryBatches = materialsDataManager.getInventoryBatches(testMaterialsProducerId); - for (BatchId batchId : inventoryBatches) { - if (randomGenerator.nextBoolean()) { - StageId stageId = stageIds.get(randomGenerator.nextInt(stageIds.size())); - materialsDataManager.moveBatchToStage(batchId, stageId); - expectedReportItems.add(getReportItemFromBatch(c, batchId)); - } - } - - })); - - // take some of the batches off of stages - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - List stageIds = materialsDataManager.getStages(testMaterialsProducerId); - - for (StageId stageId : stageIds) { - List batches = materialsDataManager.getStageBatches(stageId); - for (BatchId batchId : batches) { - if (randomGenerator.nextBoolean()) { - materialsDataManager.moveBatchToInventory(batchId); - expectedReportItems.add(getReportItemFromBatch(c, batchId)); - } - } - } - })); - - } - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - Set actualReportItems = MaterialsActionSupport.testConsumers(8914112012010329946L, testPlugin, new BatchStatusReport(REPORT_ID)::init); - - assertEquals(expectedReportItems, actualReportItems); - } - - private static ReportItem getReportItem(Object... values) { - Builder builder = ReportItem.builder(); - builder.setReportId(REPORT_ID); - builder.setReportHeader(REPORT_HEADER); - for (Object value : values) { - builder.addValue(value); - } - return builder.build(); - } - - private static final ReportId REPORT_ID = new SimpleReportId("report"); - - private static final ReportHeader REPORT_HEADER = getReportHeader(); - - private static ReportHeader getReportHeader() { - - ReportHeader.Builder builder = ReportHeader .builder()// - .add("time")// - .add("batch")// - .add("materials_producer")// - .add("stage")// - .add("material")// - .add("amount");// - - for (TestMaterialId testMaterialId : TestMaterialId.values()) { - for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.getTestBatchPropertyIds(testMaterialId)) { - builder.add(testMaterialId + "." + testBatchPropertyId); - } - } - - return builder.build(); - - } - -} diff --git a/gcm3/src/test/java/plugins/materials/actors/AT_MaterialsProducerPropertyReport.java b/gcm3/src/test/java/plugins/materials/actors/AT_MaterialsProducerPropertyReport.java deleted file mode 100644 index 8ab7b6930..000000000 --- a/gcm3/src/test/java/plugins/materials/actors/AT_MaterialsProducerPropertyReport.java +++ /dev/null @@ -1,115 +0,0 @@ -package plugins.materials.actors; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.ActorContext; -import nucleus.Plugin; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import plugins.materials.datamangers.MaterialsDataManager; -import plugins.materials.support.MaterialsProducerId; -import plugins.materials.support.MaterialsProducerPropertyId; -import plugins.materials.testsupport.MaterialsActionSupport; -import plugins.materials.testsupport.TestMaterialsProducerId; -import plugins.materials.testsupport.TestMaterialsProducerPropertyId; -import plugins.reports.support.ReportHeader; -import plugins.reports.support.ReportId; -import plugins.reports.support.ReportItem; -import plugins.reports.support.ReportItem.Builder; -import plugins.reports.support.SimpleReportId; -import plugins.stochastics.StochasticsDataManager; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = MaterialsProducerPropertyReport.class) -public final class AT_MaterialsProducerPropertyReport { - - private ReportItem getReportItemFromPropertyId(ActorContext agentContext, MaterialsProducerId materialsProducerId, MaterialsProducerPropertyId materialsProducerPropertyId) { - - MaterialsDataManager materialsDataManager = agentContext.getDataManager(MaterialsDataManager.class); - Object propertyValue = materialsDataManager.getMaterialsProducerPropertyValue(materialsProducerId, materialsProducerPropertyId); - - ReportItem reportItem = getReportItem(// - agentContext.getTime(), // - materialsProducerId, // - materialsProducerPropertyId, // - propertyValue // - );// - - return reportItem; - } - - @Test - @UnitTestMethod(name = "init", args = {}) - public void testInit() { - - Set expectedReportItems = new LinkedHashSet<>(); - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - double actionTime = 0; - - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId.values()) { - expectedReportItems.add(getReportItemFromPropertyId(c, testMaterialsProducerId, testMaterialsProducerPropertyId)); - } - } - })); - - for (int i = 0; i < 100; i++) { - - // set a property value - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId.getRandomMaterialsProducerId(randomGenerator); - TestMaterialsProducerPropertyId testMaterialsProducerPropertyId = TestMaterialsProducerPropertyId.getRandomMutableMaterialsProducerPropertyId(randomGenerator); - Object propertyValue = testMaterialsProducerPropertyId.getRandomPropertyValue(randomGenerator); - materialsDataManager.setMaterialsProducerPropertyValue(testMaterialsProducerId, testMaterialsProducerPropertyId, propertyValue); - expectedReportItems.add(getReportItemFromPropertyId(c, testMaterialsProducerId, testMaterialsProducerPropertyId)); - - })); - - } - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - Set actualReportItems = MaterialsActionSupport.testConsumers(8759226038479000135L, testPlugin, new MaterialsProducerPropertyReport(REPORT_ID)::init); - - assertEquals(expectedReportItems, actualReportItems); - } - - private static ReportItem getReportItem(Object... values) { - Builder builder = ReportItem.builder(); - builder.setReportId(REPORT_ID); - builder.setReportHeader(REPORT_HEADER); - for (Object value : values) { - builder.addValue(value); - } - return builder.build(); - } - - private static final ReportId REPORT_ID = new SimpleReportId("report"); - - private static final ReportHeader REPORT_HEADER = getReportHeader(); - - private static ReportHeader getReportHeader() { - ReportHeader.Builder builder = ReportHeader .builder()// - .add("time")// - .add("materials_producer")// - .add("property")// - .add("value");// - return builder.build(); - } - -} \ No newline at end of file diff --git a/gcm3/src/test/java/plugins/materials/actors/AT_MaterialsProducerResourceReport.java b/gcm3/src/test/java/plugins/materials/actors/AT_MaterialsProducerResourceReport.java deleted file mode 100644 index ee0f2bd55..000000000 --- a/gcm3/src/test/java/plugins/materials/actors/AT_MaterialsProducerResourceReport.java +++ /dev/null @@ -1,139 +0,0 @@ -package plugins.materials.actors; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -import org.apache.commons.math3.random.RandomGenerator; -import org.apache.commons.math3.util.FastMath; -import org.junit.jupiter.api.Test; - -import nucleus.ActorContext; -import nucleus.Plugin; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import plugins.materials.datamangers.MaterialsDataManager; -import plugins.materials.support.MaterialsProducerId; -import plugins.materials.support.StageId; -import plugins.materials.testsupport.MaterialsActionSupport; -import plugins.materials.testsupport.TestMaterialsProducerId; -import plugins.regions.testsupport.TestRegionId; -import plugins.reports.support.ReportHeader; -import plugins.reports.support.ReportId; -import plugins.reports.support.ReportItem; -import plugins.reports.support.ReportItem.Builder; -import plugins.reports.support.SimpleReportId; -import plugins.resources.support.ResourceId; -import plugins.resources.testsupport.TestResourceId; -import plugins.stochastics.StochasticsDataManager; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.random.RandomGeneratorProvider; - -@UnitTest(target = MaterialsProducerResourceReport.class) -public final class AT_MaterialsProducerResourceReport { - - private ReportItem getReportItemFromResourceId(ActorContext agentContext, MaterialsProducerId materialsProducerId, ResourceId resourceId, long amount) { - - String actionName; - if (amount < 0) { - actionName = "Removed"; - } else { - actionName = "Added"; - } - - long reportedAmount = FastMath.abs(amount); - - return getReportItem(agentContext.getTime(), resourceId, materialsProducerId, actionName, reportedAmount); - - } - - @Test - @UnitTestMethod(name = "init", args = {}) - public void testInit() { - Set expectedReportItems = new LinkedHashSet<>(); - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - - RandomGenerator rg = RandomGeneratorProvider.getRandomGenerator(8635270533185454765L); - - double actionTime = 0; - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - for (TestResourceId testResourceId : TestResourceId.values()) { - expectedReportItems.add(getReportItemFromResourceId(c, testMaterialsProducerId, testResourceId, 0L)); - } - } - })); - - List producerIds = new ArrayList<>(); - for (int i = 0; i < 100; i++) { - TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId.getRandomMaterialsProducerId(rg); - producerIds.add(testMaterialsProducerId); - } - - for (TestMaterialsProducerId testMaterialsProducerId : producerIds) { - - // set a resource value - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - TestResourceId testResourceId = TestResourceId.getRandomResourceId(randomGenerator); - - if(randomGenerator.nextBoolean()) { - long amount = randomGenerator.nextInt(100)+1; - StageId stageId = materialsDataManager.addStage(testMaterialsProducerId); - materialsDataManager.convertStageToResource(stageId, testResourceId, amount); - expectedReportItems.add(getReportItemFromResourceId(c, testMaterialsProducerId, testResourceId, amount)); - }else { - long resourceLevel = materialsDataManager.getMaterialsProducerResourceLevel(testMaterialsProducerId, testResourceId); - if(resourceLevel>0) { - long amount = randomGenerator.nextInt((int)resourceLevel)+1; - TestRegionId testRegionId = TestRegionId.getRandomRegionId(randomGenerator); - materialsDataManager.transferResourceToRegion(testMaterialsProducerId, testResourceId, testRegionId, amount); - expectedReportItems.add(getReportItemFromResourceId(c, testMaterialsProducerId, testResourceId, -amount)); - } - } - })); - } - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - Set actualReportItems = MaterialsActionSupport.testConsumers(8759226038479000135L, testPlugin, new MaterialsProducerResourceReport(REPORT_ID)::init); - - assertEquals(expectedReportItems, actualReportItems); - } - - private static ReportItem getReportItem(Object... values) { - Builder builder = ReportItem.builder(); - builder.setReportId(REPORT_ID); - builder.setReportHeader(REPORT_HEADER); - for (Object value : values) { - builder.addValue(value); - } - return builder.build(); - } - - private static final ReportId REPORT_ID = new SimpleReportId("report"); - - private static final ReportHeader REPORT_HEADER = getReportHeader(); - - private static ReportHeader getReportHeader() { - return ReportHeader .builder()// - .add("time")// - .add("resource")// - .add("materials_producer")// - .add("action")// - .add("amount")// - .build(); - - } - -} diff --git a/gcm3/src/test/java/plugins/materials/actors/AT_StageReport.java b/gcm3/src/test/java/plugins/materials/actors/AT_StageReport.java deleted file mode 100644 index 1c5c3800a..000000000 --- a/gcm3/src/test/java/plugins/materials/actors/AT_StageReport.java +++ /dev/null @@ -1,198 +0,0 @@ -package plugins.materials.actors; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.ActorContext; -import nucleus.Plugin; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import plugins.materials.datamangers.MaterialsDataManager; -import plugins.materials.support.MaterialsProducerId; -import plugins.materials.support.StageId; -import plugins.materials.testsupport.MaterialsActionSupport; -import plugins.materials.testsupport.TestMaterialsProducerId; -import plugins.reports.support.ReportHeader; -import plugins.reports.support.ReportId; -import plugins.reports.support.ReportItem; -import plugins.reports.support.SimpleReportId; -import plugins.stochastics.StochasticsDataManager; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.random.RandomGeneratorProvider; - -@UnitTest(target = StageReport.class) -public final class AT_StageReport { - - private static enum Action { - CREATED("Create"), - - DESTROYED("Destroy"), - - OFFERED("Offer"), - - TRANSFERRED("Transfer"); - - private final String displayName; - - private Action(final String displayName) { - this.displayName = displayName; - } - } - - private ReportItem getReportItem(ActorContext agentContext, StageId stageId, MaterialsProducerId materialsProducerId, boolean isOffered, Action action) { - - return ReportItem .builder()// - .setReportId(REPORT_ID)// - .setReportHeader(REPORT_HEADER)// - .addValue(agentContext.getTime())// - .addValue(stageId)// - .addValue(materialsProducerId)// - .addValue(action.displayName)// - .addValue(isOffered)// - .build();// - } - - @Test - @UnitTestMethod(name = "init", args = {}) - public void testInit() { - /* - * Create containers for the expected and actual report items that will - * be generated by various stage related events. - * - */ - Set expectedReportItems = new LinkedHashSet<>(); - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // Generate 500 stage-based actions and record the expected report items - RandomGenerator rg = RandomGeneratorProvider.getRandomGenerator(8635270533185454765L); - - double actionTime = 0; - - // create a list of 500 producer ids at random - List producerIds = new ArrayList<>(); - for (int i = 0; i < 500; i++) { - TestMaterialsProducerId testMaterialsProducerId = TestMaterialsProducerId.getRandomMaterialsProducerId(rg); - producerIds.add(testMaterialsProducerId); - } - - // for each producer id, execute and action and increment time for the - // plan - for (TestMaterialsProducerId testMaterialsProducerId : producerIds) { - - /* - * Have the producer execute one of the four actions: stage - * creation, stage destruction, stage offer status change and stage - * transfer - * - */ - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - List stages = materialsDataManager.getStages(testMaterialsProducerId); - List offeredStages = materialsDataManager.getOfferedStages(testMaterialsProducerId); - - // select one of the four possible actions at random based on - // the availability of the action - - // should we create a stage? -- we can always create a new - // stage, so assume that is what we will do - Action action = Action.CREATED; - int candidateActionCount = 1; - - // should we destroy a non-offered stage? - if (stages.size() > offeredStages.size()) { - candidateActionCount++; - if (randomGenerator.nextDouble() < 1.0 / candidateActionCount) { - action = Action.DESTROYED; - } - } - - // should we transfer an offered stage? - if (offeredStages.size() > 0) { - candidateActionCount++; - if (randomGenerator.nextDouble() < 1.0 / candidateActionCount) { - action = Action.TRANSFERRED; - } - } - - // should we change the offer state of a stage? - if (stages.size() > 0) { - candidateActionCount++; - if (randomGenerator.nextDouble() < 1.0 / candidateActionCount) { - action = Action.OFFERED; - } - } - - StageId stageId; - boolean stageOffered; - switch (action) { - case CREATED: - stageId = materialsDataManager.addStage(testMaterialsProducerId); - stageOffered = materialsDataManager.isStageOffered(stageId); - expectedReportItems.add(getReportItem(c, stageId, testMaterialsProducerId, stageOffered, action)); - break; - case DESTROYED: - stages.removeAll(offeredStages); - stageId = stages.get(randomGenerator.nextInt(stages.size())); - stageOffered = materialsDataManager.isStageOffered(stageId); - materialsDataManager.removeStage(stageId, false); - expectedReportItems.add(getReportItem(c, stageId, testMaterialsProducerId, stageOffered, action)); - break; - case OFFERED: - stageId = stages.get(randomGenerator.nextInt(stages.size())); - stageOffered = materialsDataManager.isStageOffered(stageId); - materialsDataManager.setStageOfferState(stageId, !stageOffered); - expectedReportItems.add(getReportItem(c, stageId, testMaterialsProducerId, !stageOffered, action)); - break; - case TRANSFERRED: - stageId = offeredStages.get(randomGenerator.nextInt(offeredStages.size())); - stageOffered = materialsDataManager.isStageOffered(stageId); - List candidateProducerIds = new ArrayList<>(materialsDataManager.getMaterialsProducerIds()); - candidateProducerIds.remove(testMaterialsProducerId); - MaterialsProducerId materialsProducerId = candidateProducerIds.get(randomGenerator.nextInt(candidateProducerIds.size())); - materialsDataManager.transferOfferedStage(stageId, materialsProducerId); - expectedReportItems.add(getReportItem(c, stageId, materialsProducerId, true, action)); - expectedReportItems.add(getReportItem(c, stageId, materialsProducerId, false, Action.OFFERED)); - break; - default: - throw new RuntimeException("unhandled action type"); - } - - })); - } - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - Set actualReportItems = MaterialsActionSupport.testConsumers(542686524159732447L, testPlugin, new StageReport(REPORT_ID)::init); - - assertEquals(expectedReportItems, actualReportItems); - } - - private static final ReportId REPORT_ID = new SimpleReportId("report"); - - private static final ReportHeader REPORT_HEADER = getReportHeader(); - - private static ReportHeader getReportHeader() { - return ReportHeader .builder()// - .add("time")// - .add("stage")// - .add("materials_producer")// - .add("action")// - .add("offered")// - .build(); - - } - -} diff --git a/gcm3/src/test/java/plugins/materials/datamanagers/AT_MaterialsDataManager.java b/gcm3/src/test/java/plugins/materials/datamanagers/AT_MaterialsDataManager.java deleted file mode 100644 index 6afb2f80f..000000000 --- a/gcm3/src/test/java/plugins/materials/datamanagers/AT_MaterialsDataManager.java +++ /dev/null @@ -1,4427 +0,0 @@ -package plugins.materials.datamanagers; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.EnumSet; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Random; -import java.util.Set; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.DataManagerContext; -import nucleus.EventLabel; -import nucleus.EventLabeler; -import nucleus.NucleusError; -import nucleus.Plugin; -import nucleus.Simulation; -import nucleus.Simulation.Builder; -import nucleus.testsupport.testplugin.ScenarioPlanCompletionObserver; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestError; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import plugins.materials.MaterialsPlugin; -import plugins.materials.MaterialsPluginData; -import plugins.materials.datamangers.MaterialsDataManager; -import plugins.materials.events.BatchAdditionEvent; -import plugins.materials.events.BatchAmountUpdateEvent; -import plugins.materials.events.BatchImminentRemovalEvent; -import plugins.materials.events.BatchPropertyUpdateEvent; -import plugins.materials.events.MaterialsProducerPropertyUpdateEvent; -import plugins.materials.events.MaterialsProducerResourceUpdateEvent; -import plugins.materials.events.StageAdditionEvent; -import plugins.materials.events.StageImminentRemovalEvent; -import plugins.materials.events.StageMaterialsProducerUpdateEvent; -import plugins.materials.events.StageMembershipAdditionEvent; -import plugins.materials.events.StageMembershipRemovalEvent; -import plugins.materials.events.StageOfferUpdateEvent; -import plugins.materials.support.BatchConstructionInfo; -import plugins.materials.support.BatchId; -import plugins.materials.support.BatchPropertyId; -import plugins.materials.support.MaterialId; -import plugins.materials.support.MaterialsError; -import plugins.materials.support.MaterialsProducerId; -import plugins.materials.support.MaterialsProducerPropertyId; -import plugins.materials.support.StageId; -import plugins.materials.testsupport.MaterialsActionSupport; -import plugins.materials.testsupport.TestBatchPropertyId; -import plugins.materials.testsupport.TestMaterialId; -import plugins.materials.testsupport.TestMaterialsProducerId; -import plugins.materials.testsupport.TestMaterialsProducerPropertyId; -import plugins.people.PeoplePlugin; -import plugins.people.PeoplePluginData; -import plugins.regions.RegionsPlugin; -import plugins.regions.RegionsPluginData; -import plugins.regions.support.RegionError; -import plugins.regions.support.RegionId; -import plugins.regions.testsupport.TestRegionId; -import plugins.resources.ResourcesPlugin; -import plugins.resources.ResourcesPluginData; -import plugins.resources.datamanagers.ResourcesDataManager; -import plugins.resources.events.RegionResourceUpdateEvent; -import plugins.resources.support.ResourceError; -import plugins.resources.support.ResourceId; -import plugins.resources.testsupport.TestResourceId; -import plugins.resources.testsupport.TestResourcePropertyId; -import plugins.stochastics.StochasticsDataManager; -import plugins.stochastics.StochasticsPlugin; -import plugins.stochastics.StochasticsPluginData; -import plugins.util.properties.PropertyDefinition; -import plugins.util.properties.PropertyError; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; -import util.random.RandomGeneratorProvider; -import util.wrappers.MultiKey; -import util.wrappers.MutableDouble; -import util.wrappers.MutableLong; - -public class AT_MaterialsDataManager { - - @Test - @UnitTestConstructor(args = { MaterialsPluginData.class }) - public void testConstructor() { - - ContractException contractException = assertThrows(ContractException.class, () -> new MaterialsDataManager(null)); - assertEquals(MaterialsError.NULL_MATERIALS_PLUGIN_DATA, contractException.getErrorType()); - } - - @Test - @UnitTestMethod(name = "addBatch", args = { BatchConstructionInfo.class }) - public void testAddBatch_BatchConstructionInfo() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // create containers to hold observations - Set expectedBatchObservations = new LinkedHashSet<>(); - Set actualBatchObservations = new LinkedHashSet<>(); - - /* create an observer actor that will observe the batch creations */ - - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { - c.subscribe(BatchAdditionEvent.class, (c2, e) -> { - actualBatchObservations.add(e.getBatchId()); - }); - })); - - // create some batches and show that their various features are correct - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - for (int i = 0; i < 20; i++) { - BatchConstructionInfo.Builder builder = BatchConstructionInfo.builder(); - TestMaterialId testMaterialId = TestMaterialId.getRandomMaterialId(randomGenerator); - builder.setMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - builder.setMaterialId(testMaterialId); - double amount = randomGenerator.nextDouble(); - builder.setAmount(amount);// - for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.getTestBatchPropertyIds(testMaterialId)) { - builder.setPropertyValue(testBatchPropertyId, testBatchPropertyId.getRandomPropertyValue(randomGenerator)); - } - BatchConstructionInfo batchConstructionInfo = builder.build();// - - BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); - assertEquals(testMaterialId, materialsDataManager.getBatchMaterial(batchId)); - assertEquals(amount, materialsDataManager.getBatchAmount(batchId)); - - expectedBatchObservations.add(batchId); - } - - })); - - /* - * have the observer show that the observations are properly generated - */ - - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(2, (c) -> { - assertTrue(expectedBatchObservations.size() > 0); - assertEquals(expectedBatchObservations, actualBatchObservations); - - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - MaterialsActionSupport.testConsumers(1063265892920576062L, testPlugin); - - /* precondition test: if the materials producer is null */ - MaterialsActionSupport.testConsumer(5818867905165255006L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> { - BatchConstructionInfo.Builder builder = BatchConstructionInfo.builder(); - builder.setMaterialId(TestMaterialId.MATERIAL_1); - builder.setAmount(0.1234); - BatchConstructionInfo batchConstructionInfo = builder.build(); - materialsDataManager.addBatch(batchConstructionInfo); - }); - assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - }); - - /* precondition test: if the materials producer is null */ - MaterialsActionSupport.testConsumer(7126499343584962390L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> { - BatchConstructionInfo.Builder builder = BatchConstructionInfo.builder(); - builder.setMaterialId(TestMaterialId.MATERIAL_1); - builder.setAmount(0.1234); - builder.setMaterialsProducerId(TestMaterialsProducerId.getUnknownMaterialsProducerId()); - BatchConstructionInfo batchConstructionInfo = builder.build(); - - materialsDataManager.addBatch(batchConstructionInfo); - }); - assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - }); - - /* - * precondition test: if the batch construction info in the event is - * null - */ - MaterialsActionSupport.testConsumer(6921778272119512748L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> { - materialsDataManager.addBatch(null); - }); - assertEquals(MaterialsError.NULL_BATCH_CONSTRUCTION_INFO, contractException.getErrorType()); - }); - - /* - * precondition test: if the material id in the batch construction info - * is null - */ - MaterialsActionSupport.testConsumer(3677913497762052761L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> { - BatchConstructionInfo.Builder builder = BatchConstructionInfo.builder(); - builder.setAmount(0.1234); - builder.setMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - BatchConstructionInfo batchConstructionInfo = builder.build(); - materialsDataManager.addBatch(batchConstructionInfo); - - }); - assertEquals(MaterialsError.NULL_MATERIAL_ID, contractException.getErrorType()); - }); - - /* - * precondition test: if the material id in the batch construction info - * is unknown - */ - MaterialsActionSupport.testConsumer(6823349146270705865L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> { - BatchConstructionInfo.Builder builder = BatchConstructionInfo.builder(); - builder.setMaterialId(TestMaterialId.getUnknownMaterialId()); - builder.setAmount(0.1234); - builder.setMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_3); - BatchConstructionInfo batchConstructionInfo = builder.build(); - materialsDataManager.addBatch(batchConstructionInfo); - - }); - assertEquals(MaterialsError.UNKNOWN_MATERIAL_ID, contractException.getErrorType()); - }); - - /* - * precondition test: if the amount in the batch construction info is - * not finite - */ - MaterialsActionSupport.testConsumer(1740687746013988916L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> { - BatchConstructionInfo.Builder builder = BatchConstructionInfo.builder(); - builder.setMaterialId(TestMaterialId.MATERIAL_1); - builder.setAmount(Double.POSITIVE_INFINITY); - builder.setMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - BatchConstructionInfo batchConstructionInfo = builder.build(); - materialsDataManager.addBatch(batchConstructionInfo); - }); - assertEquals(MaterialsError.NON_FINITE_MATERIAL_AMOUNT, contractException.getErrorType()); - }); - - /* - * precondition test: if the amount in the batch construction info is - * negative - */ - MaterialsActionSupport.testConsumer(3552750401177629416L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> { - BatchConstructionInfo.Builder builder = BatchConstructionInfo.builder(); - builder.setMaterialId(TestMaterialId.MATERIAL_1); - builder.setAmount(-1.0); - builder.setMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_2); - BatchConstructionInfo batchConstructionInfo = builder.build(); - materialsDataManager.addBatch(batchConstructionInfo); - }); - assertEquals(MaterialsError.NEGATIVE_MATERIAL_AMOUNT, contractException.getErrorType()); - }); - - /* - * precondition test: if the batch construction info contains a null - * batch property id - */ - MaterialsActionSupport.testConsumer(2067301487300157385L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> { - BatchConstructionInfo.Builder builder = BatchConstructionInfo.builder(); - builder.setMaterialId(TestMaterialId.MATERIAL_1); - builder.setAmount(0.1234); - builder.setPropertyValue(null, 15); - builder.setMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - BatchConstructionInfo batchConstructionInfo = builder.build(); - materialsDataManager.addBatch(batchConstructionInfo); - }); - assertEquals(MaterialsError.NULL_BATCH_PROPERTY_ID, contractException.getErrorType()); - }); - - /* - * precondition test: if the batch construction info contains an unknown - * batch property id - */ - MaterialsActionSupport.testConsumer(3866738227501386466L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> { - BatchConstructionInfo.Builder builder = BatchConstructionInfo.builder(); - builder.setMaterialId(TestMaterialId.MATERIAL_1); - builder.setAmount(0.1234); - builder.setMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - builder.setPropertyValue(TestBatchPropertyId.getUnknownBatchPropertyId(), 15); - BatchConstructionInfo batchConstructionInfo = builder.build(); - materialsDataManager.addBatch(batchConstructionInfo); - }); - assertEquals(MaterialsError.UNKNOWN_BATCH_PROPERTY_ID, contractException.getErrorType()); - }); - - /* - * precondition test: if the batch construction info contains a batch - * property value that is incompatible with the corresponding property - * def - */ - MaterialsActionSupport.testConsumer(2067301487300157385L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> { - BatchConstructionInfo.Builder builder = BatchConstructionInfo.builder(); - builder.setMaterialId(TestMaterialId.MATERIAL_1); - builder.setAmount(0.1234); - builder.setMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - builder.setPropertyValue(TestBatchPropertyId.BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, 2.3); - BatchConstructionInfo batchConstructionInfo = builder.build(); - materialsDataManager.addBatch(batchConstructionInfo); - }); - assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); - }); - - /* - * precondition test: if the batch construction info contains a batch - * property value that is incompatible with the corresponding property - * def - */ - MaterialsActionSupport.testConsumer(8126846490003696164L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> { - BatchConstructionInfo.Builder builder = BatchConstructionInfo.builder(); - builder.setMaterialId(TestMaterialId.MATERIAL_1); - builder.setAmount(0.1234); - builder.setMaterialsProducerId(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - builder.setPropertyValue(TestBatchPropertyId.BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, 2.3); - BatchConstructionInfo batchConstructionInfo = builder.build(); - materialsDataManager.addBatch(batchConstructionInfo); - }); - assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "addBatch", args = { MaterialsProducerId.class, MaterialId.class, double.class }) - public void testAddBatch_MaterialId() { - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // create containers to hold observations - Set expectedBatchObservations = new LinkedHashSet<>(); - Set actualBatchObservations = new LinkedHashSet<>(); - - /* create an observer actor that will observe the batch creations */ - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { - c.subscribe(BatchAdditionEvent.class, (c2, e) -> { - actualBatchObservations.add(e.getBatchId()); - }); - })); - - // create some batches and show that their various features are correct - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - for (int i = 0; i < 20; i++) { - BatchConstructionInfo.Builder builder = BatchConstructionInfo.builder(); - TestMaterialId testMaterialId = TestMaterialId.getRandomMaterialId(randomGenerator); - builder.setMaterialId(testMaterialId); - double amount = randomGenerator.nextDouble(); - builder.setAmount(amount);// - BatchId batchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, testMaterialId, amount); - assertEquals(testMaterialId, materialsDataManager.getBatchMaterial(batchId)); - assertEquals(amount, materialsDataManager.getBatchAmount(batchId)); - - expectedBatchObservations.add(batchId); - } - - })); - - /* - * have the observer show that the observations are properly generated - */ - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(2, (c) -> { - assertEquals(expectedBatchObservations, actualBatchObservations); - - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - MaterialsActionSupport.testConsumers(8688242236073344545L, testPlugin); - - /* precondition test: if the materials producer is null */ - MaterialsActionSupport.testConsumer(248695003930151563L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> { - materialsDataManager.addBatch(null, TestMaterialId.MATERIAL_1, 0.1234); - }); - assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - }); - - /* precondition test: if the materials producer is unknown */ - MaterialsActionSupport.testConsumer(4435615926145456787L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> { - materialsDataManager.addBatch(TestMaterialsProducerId.getUnknownMaterialsProducerId(), TestMaterialId.MATERIAL_1, 0.1234); - }); - assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - }); - - /* - * precondition test: if the material id in the batch construction info - * is null - */ - MaterialsActionSupport.testConsumer(281162150914663405L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> { - materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, null, 0.1234); - }); - assertEquals(MaterialsError.NULL_MATERIAL_ID, contractException.getErrorType()); - }); - - /* precondition test: if the material id is unknown */ - MaterialsActionSupport.testConsumer(4108216709703776214L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> { - materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.getUnknownMaterialId(), 0.1234); - }); - assertEquals(MaterialsError.UNKNOWN_MATERIAL_ID, contractException.getErrorType()); - }); - - /* precondition test: if the amount is not finite */ - MaterialsActionSupport.testConsumer(1456036643803016956L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> { - materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, Double.POSITIVE_INFINITY); - }); - assertEquals(MaterialsError.NON_FINITE_MATERIAL_AMOUNT, contractException.getErrorType()); - }); - - /* precondition test: if the amount is negative */ - MaterialsActionSupport.testConsumer(2577507368270644288L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> { - materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, -1.0); - }); - assertEquals(MaterialsError.NEGATIVE_MATERIAL_AMOUNT, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "addStage", args = { MaterialsProducerId.class }) - public void testAddStage() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - double actionTime = 0; - - // create containers to hold observations - Set expectedObservations = new LinkedHashSet<>(); - Set actualObservations = new LinkedHashSet<>(); - - // have a actor observe stage creations - - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { - c.subscribe(StageAdditionEvent.class, (c2, e) -> { - actualObservations.add(new MultiKey(c2.getTime(), e.getStageId())); - }); - })); - - // produce stages at various times - for (int i = 0; i < 10; i++) { - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StageId stageId = materialsDataManager.addStage(testMaterialsProducerId); - - // show that the stage exists and belongs to the producer - assertTrue(materialsDataManager.stageExists(stageId)); - assertEquals(testMaterialsProducerId, materialsDataManager.getStageProducer(stageId)); - - // generated expected observations - expectedObservations.add(new MultiKey(c.getTime(), stageId)); - })); - } - } - - // have the observer show that the observations are correct - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { - assertTrue(expectedObservations.size() > 0); - assertEquals(expectedObservations, actualObservations); - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - MaterialsActionSupport.testConsumers(1344617610771747654L, testPlugin); - - /* precondition test if the materials producer is null */ - MaterialsActionSupport.testConsumer(2938510662832987631L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.addStage(null)); - assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - }); - - /* precondition test if the materials producer is unknown */ - MaterialsActionSupport.testConsumer(2938510662832987631L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.addStage(TestMaterialsProducerId.getUnknownMaterialsProducerId())); - assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "batchExists", args = { BatchId.class }) - public void testBatchExists() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - /* - * Create a data structure to hold batch ids that have been removed and - * require flow of control to leave the actor before removal can be - * confirmed - */ - Map> removalConfirmationBatches = new LinkedHashMap<>(); - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - removalConfirmationBatches.put(testMaterialsProducerId, new LinkedHashSet<>()); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - Set confimationBatches = removalConfirmationBatches.get(testMaterialsProducerId); - for (TestMaterialId testMaterialId : TestMaterialId.values()) { - double value = randomGenerator.nextDouble() * 100; - // add the batch an show it exists - BatchId batchId = materialsDataManager.addBatch(testMaterialsProducerId, testMaterialId, value); - assertTrue(materialsDataManager.batchExists(batchId)); - // remove the batch and show that it still exists - materialsDataManager.removeBatch(batchId); - assertTrue(materialsDataManager.batchExists(batchId)); - confimationBatches.add(batchId); - } - // show that null and unknown batch ids return false - assertFalse(materialsDataManager.batchExists(null)); - assertFalse(materialsDataManager.batchExists(new BatchId(10000000))); - - })); - - // show that the batches removed above are now gone - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - Set confimationBatches = removalConfirmationBatches.get(testMaterialsProducerId); - for (BatchId batchId : confimationBatches) { - assertFalse(materialsDataManager.batchExists(batchId)); - } - })); - } - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - MaterialsActionSupport.testConsumers(3680467733415023569L, testPlugin); - } - - @Test - @UnitTestMethod(name = "batchPropertyIdExists", args = { MaterialId.class, BatchPropertyId.class }) - public void testBatchPropertyIdExists() { - - MaterialsActionSupport.testConsumer(4250860228077588132L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - - for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.values()) { - MaterialId associatedMaterialId = testBatchPropertyId.getTestMaterialId(); - for (TestMaterialId testMaterialId : TestMaterialId.values()) { - if (testMaterialId.equals(associatedMaterialId)) { - assertTrue(materialsDataManager.batchPropertyIdExists(testMaterialId, testBatchPropertyId)); - } else { - assertFalse(materialsDataManager.batchPropertyIdExists(testMaterialId, testBatchPropertyId)); - } - } - } - - assertFalse(materialsDataManager.batchPropertyIdExists(TestMaterialId.MATERIAL_1, null)); - assertFalse(materialsDataManager.batchPropertyIdExists(null, TestBatchPropertyId.BATCH_PROPERTY_1_1_BOOLEAN_IMMUTABLE_NO_TRACK)); - assertFalse(materialsDataManager.batchPropertyIdExists(null, null)); - - // precondition tests : none - - }); - - } - - @Test - @UnitTestMethod(name = "convertStageToBatch", args = { StageId.class, MaterialId.class, double.class }) - public void testConvertStageToBatch() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - double actionTime = 0; - - // create containers to hold observations - Set expectedObservations = new LinkedHashSet<>(); - Set actualObservations = new LinkedHashSet<>(); - - // have an actor observe - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { - - c.subscribe(BatchImminentRemovalEvent.class, (c2, e) -> { - actualObservations.add(new MultiKey(c2.getTime(), e.getBatchId(), "removal")); - }); - - c.subscribe(BatchAdditionEvent.class, (c2, e) -> { - actualObservations.add(new MultiKey(c2.getTime(), e.getBatchId(), "creation")); - }); - - c.subscribe(StageImminentRemovalEvent.class, (c2, e) -> { - actualObservations.add(new MultiKey(c2.getTime(), e.getStageId())); - }); - - })); - - // have the producers generate batches via stage conversion - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - - List stagesToConfirm = new ArrayList<>(); - List batchesToConfirm = new ArrayList<>(); - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - for (int i = 0; i < 50; i++) { - StageId stageId = materialsDataManager.addStage(testMaterialsProducerId); - - MaterialId materialId; - double amount; - int batchCount = randomGenerator.nextInt(3); - for (int j = 0; j < batchCount; j++) { - materialId = TestMaterialId.getRandomMaterialId(randomGenerator); - amount = randomGenerator.nextDouble() + 0.01; - BatchId batchId = materialsDataManager.addBatch(testMaterialsProducerId, materialId, amount); - materialsDataManager.moveBatchToStage(batchId, stageId); - } - materialId = TestMaterialId.getRandomMaterialId(randomGenerator); - amount = randomGenerator.nextDouble() + 0.01; - List stageBatches = materialsDataManager.getStageBatches(stageId); - BatchId producedBatchId = materialsDataManager.convertStageToBatch(stageId, materialId, amount); - - // record the stages and batches that should be removed, but - // only after the current actor activation - stagesToConfirm.add(stageId); - batchesToConfirm.addAll(stageBatches); - - // show that the stage was properly converted - assertTrue(materialsDataManager.batchExists(producedBatchId)); - assertEquals(materialId, materialsDataManager.getBatchMaterial(producedBatchId)); - assertEquals(amount, materialsDataManager.getBatchAmount(producedBatchId)); - - // generate the expected observations - for (BatchId batchId : stageBatches) { - expectedObservations.add(new MultiKey(c.getTime(), batchId, "creation")); - expectedObservations.add(new MultiKey(c.getTime(), batchId, "removal")); - } - expectedObservations.add(new MultiKey(c.getTime(), producedBatchId, "creation")); - expectedObservations.add(new MultiKey(c.getTime(), stageId)); - } - })); - - // show that the stages and batches used to generate the new batches - // were in fact removed - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - for (StageId stageId : stagesToConfirm) { - assertFalse(materialsDataManager.stageExists(stageId)); - } - for (BatchId batchId : batchesToConfirm) { - assertFalse(materialsDataManager.batchExists(batchId)); - } - })); - } - - // have the observer show that the correct observations were generated - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { - assertTrue(expectedObservations.size() > 0); - assertEquals(expectedObservations, actualObservations); - })); - - /* precondition test: if the material id is null */ - MaterialsActionSupport.testConsumer(5594572733415411831L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - double amount = 12.5; - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.convertStageToBatch(stageId, null, amount)); - assertEquals(MaterialsError.NULL_MATERIAL_ID, contractException.getErrorType()); - }); - - /* precondition test: if the material id is unknown */ - MaterialsActionSupport.testConsumer(5132874324434783837L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - double amount = 12.5; - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.convertStageToBatch(stageId, TestMaterialId.getUnknownMaterialId(), amount)); - assertEquals(MaterialsError.UNKNOWN_MATERIAL_ID, contractException.getErrorType()); - - }); - - /* precondition test: if the stage id is null */ - MaterialsActionSupport.testConsumer(195083586581127005L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - MaterialId materialId = TestMaterialId.MATERIAL_1; - double amount = 12.5; - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.convertStageToBatch(null, materialId, amount)); - assertEquals(MaterialsError.NULL_STAGE_ID, contractException.getErrorType()); - - }); - - /* precondition test: if stage id is unknown */ - MaterialsActionSupport.testConsumer(6716241372071908817L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - MaterialId materialId = TestMaterialId.MATERIAL_1; - double amount = 12.5; - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.convertStageToBatch(new StageId(10000000), materialId, amount)); - assertEquals(MaterialsError.UNKNOWN_STAGE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the stage is offered */ - MaterialsActionSupport.testConsumer(696988531477059866L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - MaterialId materialId = TestMaterialId.MATERIAL_1; - double amount = 12.5; - materialsDataManager.setStageOfferState(stageId, true); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.convertStageToBatch(stageId, materialId, amount)); - assertEquals(MaterialsError.OFFERED_STAGE_UNALTERABLE, contractException.getErrorType()); - }); - - /* precondition test: if the material amount is not finite */ - MaterialsActionSupport.testConsumer(1976879256674379671L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - MaterialId materialId = TestMaterialId.MATERIAL_1; - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.convertStageToBatch(stageId, materialId, Double.POSITIVE_INFINITY)); - assertEquals(MaterialsError.NON_FINITE_MATERIAL_AMOUNT, contractException.getErrorType()); - }); - - /* precondition test: if the material amount is negative */ - MaterialsActionSupport.testConsumer(8026304657517692525L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - MaterialId materialId = TestMaterialId.MATERIAL_1; - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.convertStageToBatch(stageId, materialId, -1.0)); - assertEquals(MaterialsError.NEGATIVE_MATERIAL_AMOUNT, contractException.getErrorType()); - }); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - MaterialsActionSupport.testConsumers(855059044560726814L, testPlugin); - - } - - @Test - @UnitTestMethod(name = "convertStageToBatch", args = { StageId.class, MaterialId.class, double.class }) - public void testStageToResourceConversionEvent() { - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - double actionTime = 0; - - // create containers to hold observations - Set expectedObservations = new LinkedHashSet<>(); - Set actualObservations = new LinkedHashSet<>(); - - // have an actor observe - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { - - c.subscribe(BatchImminentRemovalEvent.class, (c2, e) -> { - actualObservations.add(new MultiKey(c2.getTime(), e.getBatchId())); - }); - - for (TestResourceId testResourceId : TestResourceId.values()) { - c.subscribe(MaterialsProducerResourceUpdateEvent.getEventLabelByResource(c, testResourceId), (c2, e) -> { - actualObservations.add(new MultiKey(c2.getTime(), e.getMaterialsProducerId(), e.getResourceId(), e.getPreviousResourceLevel(), e.getCurrentResourceLevel())); - }); - } - - c.subscribe(StageImminentRemovalEvent.class, (c2, e) -> { - actualObservations.add(new MultiKey(c2.getTime(), e.getStageId())); - }); - - })); - - // have the producers generate resources via stage conversion - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - - List stagesToConfirm = new ArrayList<>(); - List batchesToConfirm = new ArrayList<>(); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - for (int i = 0; i < 50; i++) { - StageId stageId = materialsDataManager.addStage(testMaterialsProducerId); - - MaterialId materialId; - double amount; - int batchCount = randomGenerator.nextInt(3); - for (int j = 0; j < batchCount; j++) { - materialId = TestMaterialId.getRandomMaterialId(randomGenerator); - amount = randomGenerator.nextDouble() + 0.01; - BatchId batchId = materialsDataManager.addBatch(testMaterialsProducerId, materialId, amount); - materialsDataManager.moveBatchToStage(batchId, stageId); - } - ResourceId resourceId = TestResourceId.getRandomResourceId(randomGenerator); - - long resourceAmount = randomGenerator.nextInt(100) + 1; - long previousResourceLevel = materialsDataManager.getMaterialsProducerResourceLevel(testMaterialsProducerId, resourceId); - long expectedResourceLevel = previousResourceLevel + resourceAmount; - List stageBatches = materialsDataManager.getStageBatches(stageId); - materialsDataManager.convertStageToResource(stageId, resourceId, resourceAmount); - - // record the stages and batches that should be removed, but - // only after the current actor activation - stagesToConfirm.add(stageId); - batchesToConfirm.addAll(stageBatches); - - // show that the stage was properly converted - long currentResourceLevel = materialsDataManager.getMaterialsProducerResourceLevel(testMaterialsProducerId, resourceId); - assertEquals(expectedResourceLevel, currentResourceLevel); - - // generate the expected observations - for (BatchId batchId : stageBatches) { - expectedObservations.add(new MultiKey(c.getTime(), batchId)); - } - expectedObservations.add(new MultiKey(c.getTime(), testMaterialsProducerId, resourceId, previousResourceLevel, currentResourceLevel)); - expectedObservations.add(new MultiKey(c.getTime(), stageId)); - } - })); - - // show that the stages and batches used to generate the new batches - // were in fact removed - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - for (StageId stageId : stagesToConfirm) { - assertFalse(materialsDataManager.stageExists(stageId)); - } - for (BatchId batchId : batchesToConfirm) { - assertFalse(materialsDataManager.batchExists(batchId)); - } - })); - } - - // have the observer show that the correct observations were generated - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { - assertTrue(expectedObservations.size() > 0); - assertEquals(expectedObservations, actualObservations); - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - MaterialsActionSupport.testConsumers(7708324840412313909L, testPlugin); - - /* precondition test: if the resource id is null */ - MaterialsActionSupport.testConsumer(684895513326133078L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - long amount = 15L; - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.convertStageToResource(stageId, null, amount)); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the resource id is unknown */ - MaterialsActionSupport.testConsumer(4496439455291273002L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - long amount = 15L; - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.convertStageToResource(stageId, TestResourceId.getUnknownResourceId(), amount)); - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - }); - - /* precondition test: */ - MaterialsActionSupport.testConsumer(7915777615994239053L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ResourceId resourceId = TestResourceId.RESOURCE_1; - long amount = 15L; - // if the stage id is null - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.convertStageToResource(null, resourceId, amount)); - assertEquals(MaterialsError.NULL_STAGE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the stage id is unknown */ - MaterialsActionSupport.testConsumer(7939732368430243050L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ResourceId resourceId = TestResourceId.RESOURCE_1; - long amount = 15L; - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.convertStageToResource(new StageId(10000000), resourceId, amount)); - assertEquals(MaterialsError.UNKNOWN_STAGE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the stage is offered */ - MaterialsActionSupport.testConsumer(5426191091405983240L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - ResourceId resourceId = TestResourceId.RESOURCE_1; - long amount = 15L; - materialsDataManager.setStageOfferState(stageId, true); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.convertStageToResource(stageId, resourceId, amount)); - assertEquals(MaterialsError.OFFERED_STAGE_UNALTERABLE, contractException.getErrorType()); - materialsDataManager.setStageOfferState(stageId, false); - }); - - /* precondition test: if the the resource amount is negative */ - MaterialsActionSupport.testConsumer(677670960081138598L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - ResourceId resourceId = TestResourceId.RESOURCE_1; - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.convertStageToResource(stageId, resourceId, -1L)); - assertEquals(ResourceError.NEGATIVE_RESOURCE_AMOUNT, contractException.getErrorType()); - }); - - /* - * precondition test: if the resource amount would cause an overflow of - * the materials producer's resource level - */ - MaterialsActionSupport.testConsumer(3420597827763142806L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - ResourceId resourceId = TestResourceId.RESOURCE_1; - // first ensure that there is some small amount of resource stored - // on the producer - StageId stageId2 = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - materialsDataManager.convertStageToResource(stageId2, resourceId, 10); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.convertStageToResource(stageId, resourceId, Long.MAX_VALUE)); - assertEquals(ResourceError.RESOURCE_ARITHMETIC_EXCEPTION, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "getBatchAmount", args = { BatchId.class }) - public void testGetBatchAmount() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - for (int i = 0; i < 100; i++) { - TestMaterialId testMaterialId = TestMaterialId.getRandomMaterialId(randomGenerator); - double value = randomGenerator.nextDouble() * 100; - BatchId batchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, testMaterialId, value); - - // show that the batch matches the inputs - assertEquals(value, materialsDataManager.getBatchAmount(batchId)); - } - - // precondition tests : none - - // if the batch id is null - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.getBatchAmount(null)); - assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); - - // if the batch id is unknown - contractException = assertThrows(ContractException.class, () -> materialsDataManager.getBatchAmount(new BatchId(10000000))); - assertEquals(MaterialsError.UNKNOWN_BATCH_ID, contractException.getErrorType()); - - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - MaterialsActionSupport.testConsumers(1333558356470864456L, testPlugin); - } - - @Test - @UnitTestMethod(name = "getBatchMaterial", args = { BatchId.class }) - public void testGetBatchMaterial() { - - MaterialsActionSupport.testConsumer(2922188778885130752L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - for (int i = 0; i < 100; i++) { - TestMaterialId testMaterialId = TestMaterialId.getRandomMaterialId(randomGenerator); - double value = randomGenerator.nextDouble() * 100; - BatchId batchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_2, testMaterialId, value); - - // show that the batch matches the inputs - assertEquals(testMaterialId, materialsDataManager.getBatchMaterial(batchId)); - } - - }); - - /* precondition test: if the batch id is null */ - MaterialsActionSupport.testConsumer(8694113802920961598L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.getBatchMaterial(null)); - assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); - }); - - /* precondition test: if the batch id is unknown */ - MaterialsActionSupport.testConsumer(6524569565798029395L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException // if the batch id is unknown - contractException = assertThrows(ContractException.class, () -> materialsDataManager.getBatchMaterial(new BatchId(10000000))); - assertEquals(MaterialsError.UNKNOWN_BATCH_ID, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "getBatchProducer", args = { BatchId.class }) - public void testGetBatchProducer() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - for (int i = 0; i < 10; i++) { - - TestMaterialId testMaterialId = TestMaterialId.getRandomMaterialId(randomGenerator); - double value = randomGenerator.nextDouble() * 100; - BatchId batchId = materialsDataManager.addBatch(testMaterialsProducerId, testMaterialId, value); - - // show that the batch matches the inputs - assertEquals(testMaterialsProducerId, materialsDataManager.getBatchProducer(batchId)); - } - - })); - } - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - MaterialsActionSupport.testConsumers(8873616248377004295L, testPlugin); - - /* precondition test : if the batch id is null */ - MaterialsActionSupport.testConsumer(1422948417739515067L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.getBatchProducer(null)); - assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); - - }); - - /* precondition test : if the batch id is unknown */ - MaterialsActionSupport.testConsumer(6083037726892077495L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.getBatchProducer(new BatchId(10000000))); - assertEquals(MaterialsError.UNKNOWN_BATCH_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "getBatchPropertyDefinition", args = { MaterialId.class, BatchPropertyId.class }) - public void testGetBatchPropertyDefinition() { - - MaterialsActionSupport.testConsumer(4785939121817102392L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - - for (TestMaterialId testMaterialId : TestMaterialId.values()) { - - Set testBatchPropertyIds = TestBatchPropertyId.getTestBatchPropertyIds(testMaterialId); - for (TestBatchPropertyId testBatchPropertyId : testBatchPropertyIds) { - PropertyDefinition expectedPropertyDefinition = testBatchPropertyId.getPropertyDefinition(); - PropertyDefinition actualPropertyDefinition = materialsDataManager.getBatchPropertyDefinition(testMaterialId, testBatchPropertyId); - assertEquals(expectedPropertyDefinition, actualPropertyDefinition); - } - } - - }); - - /* precondition tests if the material id is null */ - MaterialsActionSupport.testConsumer(5856664286545303775L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - TestBatchPropertyId testBatchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK; - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.getBatchPropertyDefinition(null, testBatchPropertyId)); - assertEquals(MaterialsError.NULL_MATERIAL_ID, contractException.getErrorType()); - }); - - /* precondition tests if the material id is unknown */ - MaterialsActionSupport.testConsumer(3682262623372578238L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - TestBatchPropertyId testBatchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_2_3_DOUBLE_MUTABLE_TRACK; - ContractException contractException = assertThrows(ContractException.class, - () -> materialsDataManager.getBatchPropertyDefinition(TestMaterialId.getUnknownMaterialId(), testBatchPropertyId)); - assertEquals(MaterialsError.UNKNOWN_MATERIAL_ID, contractException.getErrorType()); - }); - - /* precondition tests if the batch property id is null */ - MaterialsActionSupport.testConsumer(2977320444281387466L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.getBatchPropertyDefinition(TestMaterialId.MATERIAL_1, null)); - assertEquals(MaterialsError.NULL_BATCH_PROPERTY_ID, contractException.getErrorType()); - }); - - /* precondition tests if the batch property id is unknown */ - MaterialsActionSupport.testConsumer(712791219730643932L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, - () -> materialsDataManager.getBatchPropertyDefinition(TestMaterialId.MATERIAL_1, TestBatchPropertyId.getUnknownBatchPropertyId())); - assertEquals(MaterialsError.UNKNOWN_BATCH_PROPERTY_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "getBatchPropertyIds", args = { MaterialId.class }) - public void testGetBatchPropertyIds() { - - MaterialsActionSupport.testConsumer(8657082858154514151L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - - for (TestMaterialId testMaterialId : TestMaterialId.values()) { - Set expectedBatchPropertyIds = TestBatchPropertyId.getTestBatchPropertyIds(testMaterialId); - Set actualBatchPropertyIds = materialsDataManager.getBatchPropertyIds(testMaterialId); - assertEquals(expectedBatchPropertyIds, actualBatchPropertyIds); - } - - // precondition tests - - // if the material id is null - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.getBatchPropertyIds(null)); - assertEquals(MaterialsError.NULL_MATERIAL_ID, contractException.getErrorType()); - - // if the material id is unknown - contractException = assertThrows(ContractException.class, () -> materialsDataManager.getBatchPropertyIds(TestMaterialId.getUnknownMaterialId())); - assertEquals(MaterialsError.UNKNOWN_MATERIAL_ID, contractException.getErrorType()); - - }); - - /* precondition test: if the material id is null */ - MaterialsActionSupport.testConsumer(6822125249787156609L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.getBatchPropertyIds(null)); - assertEquals(MaterialsError.NULL_MATERIAL_ID, contractException.getErrorType()); - }); - - /* precondition test: if the material id is unknown */ - MaterialsActionSupport.testConsumer(7025275053813907413L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.getBatchPropertyIds(TestMaterialId.getUnknownMaterialId())); - assertEquals(MaterialsError.UNKNOWN_MATERIAL_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "getBatchPropertyTime", args = { BatchId.class, BatchPropertyId.class }) - public void testGetBatchPropertyTime() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // create a data structure to hold the assignments we expect to - // retrieve. - Map expectedAssignmentTimes = new LinkedHashMap<>(); - - // create an actor - - /* - * Have the actor add 50 randomized batches and record the assignment - * times for all properties - */ - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - // create a few batches - for (int i = 0; i < 50; i++) { - MaterialId materialId = TestMaterialId.getRandomMaterialId(randomGenerator); - double amount = randomGenerator.nextDouble(); - BatchId batchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_3, materialId, amount); - Set batchPropertyIds = materialsDataManager.getBatchPropertyIds(materialId); - for (TestBatchPropertyId batchPropertyId : batchPropertyIds) { - expectedAssignmentTimes.put(new MultiKey(batchId, batchPropertyId), new MutableDouble(c.getTime())); - } - } - - })); - - /* - * Have the actor alter about 1/3 of batch property values at 10 - * distinct times, recording the new assignment times as we go. - */ - for (int i = 1; i < 10; i++) { - double actionTime = i; - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - // plan several times to alter some of the batch properties - - // alter the batch properties - - List inventoryBatches = materialsDataManager.getInventoryBatches(TestMaterialsProducerId.MATERIALS_PRODUCER_3); - for (BatchId batchId : inventoryBatches) { - MaterialId materialId = materialsDataManager.getBatchMaterial(batchId); - Set batchPropertyIds = materialsDataManager.getBatchPropertyIds(materialId); - for (TestBatchPropertyId batchPropertyId : batchPropertyIds) { - if (batchPropertyId.getPropertyDefinition().propertyValuesAreMutable()) { - if (randomGenerator.nextDouble() < 0.5) { - Object value = batchPropertyId.getRandomPropertyValue(randomGenerator); - materialsDataManager.setBatchPropertyValue(batchId, batchPropertyId, value); - MutableDouble mutableDouble = expectedAssignmentTimes.get(new MultiKey(batchId, batchPropertyId)); - mutableDouble.setValue(c.getTime()); - } - } - } - } - - })); - } - - /* - * Have the actor compare the assignment times at time = 10 to the - * expected values. - */ - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(10, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - for (MaterialsProducerId materialsProducerId : materialsDataManager.getMaterialsProducerIds()) { - List inventoryBatches = materialsDataManager.getInventoryBatches(materialsProducerId); - for (BatchId batchId : inventoryBatches) { - MaterialId materialId = materialsDataManager.getBatchMaterial(batchId); - Set batchPropertyIds = materialsDataManager.getBatchPropertyIds(materialId); - for (TestBatchPropertyId batchPropertyId : batchPropertyIds) { - if (randomGenerator.nextDouble() < 0.33) { - MutableDouble mutableDouble = expectedAssignmentTimes.get(new MultiKey(batchId, batchPropertyId)); - double expectedAssignmentTime = mutableDouble.getValue(); - double actualAssignmentTime = materialsDataManager.getBatchPropertyTime(batchId, batchPropertyId); - assertEquals(expectedAssignmentTime, actualAssignmentTime); - } - } - } - } - - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - MaterialsActionSupport.testConsumers(1470041164645430466L, testPlugin); - - /* precondition test: if the batch id is null */ - MaterialsActionSupport.testConsumer(411385203720638722L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - BatchPropertyId batchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_1_1_BOOLEAN_IMMUTABLE_NO_TRACK; - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.getBatchPropertyTime(null, batchPropertyId)); - assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); - - }); - /* precondition test: if the batch id is unknown */ - MaterialsActionSupport.testConsumer(6352485251167807955L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - BatchPropertyId batchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_1_1_BOOLEAN_IMMUTABLE_NO_TRACK; - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.getBatchPropertyTime(new BatchId(100000), batchPropertyId)); - assertEquals(MaterialsError.UNKNOWN_BATCH_ID, contractException.getErrorType()); - }); - - /* precondition test: if the batch property id is null */ - MaterialsActionSupport.testConsumer(3856953954489485161L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - BatchId batchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_3, TestMaterialId.MATERIAL_2, 15L); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.getBatchPropertyTime(batchId, null)); - assertEquals(MaterialsError.NULL_BATCH_PROPERTY_ID, contractException.getErrorType()); - }); - - /* precondition test: if the batch property id is unknown */ - MaterialsActionSupport.testConsumer(2978468228127714889L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - BatchId batchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_2, 65L); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.getBatchPropertyTime(batchId, TestBatchPropertyId.getUnknownBatchPropertyId())); - assertEquals(MaterialsError.UNKNOWN_BATCH_PROPERTY_ID, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "getBatchPropertyValue", args = { BatchId.class, BatchPropertyId.class }) - public void testGetBatchPropertyValue() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - /* - * create a data structure to hold the assignments we expect to - * retrieve. - */ - - Map expectedValues = new LinkedHashMap<>(); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - - /* - * Have the actor add 50 randomized batches and record the values - * for all properties - */ - - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - // create a few batches - for (int i = 0; i < 50; i++) { - - MaterialId materialId = TestMaterialId.getRandomMaterialId(randomGenerator); - double amount = randomGenerator.nextDouble(); - BatchId batchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_2, materialId, amount); - - Set batchPropertyIds = materialsDataManager.getBatchPropertyIds(materialId); - for (TestBatchPropertyId batchPropertyId : batchPropertyIds) { - Object value = materialsDataManager.getBatchPropertyValue(batchId, batchPropertyId); - expectedValues.put(new MultiKey(batchId, batchPropertyId), value); - } - } - - // alter randomly chosen batch properties - - for (int i = 0; i < 200; i++) { - - List inventoryBatches = materialsDataManager.getInventoryBatches(TestMaterialsProducerId.MATERIALS_PRODUCER_2); - BatchId batchId = inventoryBatches.get(randomGenerator.nextInt(inventoryBatches.size())); - TestMaterialId materialId = materialsDataManager.getBatchMaterial(batchId); - TestBatchPropertyId batchPropertyId = TestBatchPropertyId.getRandomMutableBatchPropertyId(materialId, randomGenerator); - Object value = batchPropertyId.getRandomPropertyValue(randomGenerator); - materialsDataManager.setBatchPropertyValue(batchId, batchPropertyId, value); - expectedValues.put(new MultiKey(batchId, batchPropertyId), value); - - } - - })); - - // have an actor show that the batches have the expected property values - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - for (MaterialsProducerId materialsProducerId : materialsDataManager.getMaterialsProducerIds()) { - List inventoryBatches = materialsDataManager.getInventoryBatches(materialsProducerId); - for (BatchId batchId : inventoryBatches) { - MaterialId materialId = materialsDataManager.getBatchMaterial(batchId); - Set batchPropertyIds = materialsDataManager.getBatchPropertyIds(materialId); - for (TestBatchPropertyId batchPropertyId : batchPropertyIds) { - Object expectedValue = expectedValues.get(new MultiKey(batchId, batchPropertyId)); - Object actualValue = materialsDataManager.getBatchPropertyValue(batchId, batchPropertyId); - assertEquals(expectedValue, actualValue); - } - } - } - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - MaterialsActionSupport.testConsumers(1629075115765446254L, testPlugin); - - /* precondition test: if the batch id is null */ - MaterialsActionSupport.testConsumer(7782292483170344303L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - BatchPropertyId batchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_1_1_BOOLEAN_IMMUTABLE_NO_TRACK; - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.getBatchPropertyValue(null, batchPropertyId)); - assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); - }); - - /* precondition test: if the batch id is unknown */ - MaterialsActionSupport.testConsumer(2235610256211958684L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - BatchPropertyId batchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_1_1_BOOLEAN_IMMUTABLE_NO_TRACK; - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.getBatchPropertyValue(new BatchId(100000), batchPropertyId)); - assertEquals(MaterialsError.UNKNOWN_BATCH_ID, contractException.getErrorType()); - }); - - /* precondition test: if the batch property id is null */ - MaterialsActionSupport.testConsumer(4276253162944402582L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - BatchId batchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_2, 45L); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.getBatchPropertyValue(batchId, null)); - assertEquals(MaterialsError.NULL_BATCH_PROPERTY_ID, contractException.getErrorType()); - }); - - /* precondition test: if the batch property id is unknown */ - MaterialsActionSupport.testConsumer(211483511977100214L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - BatchId batchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_2, 45L); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.getBatchPropertyValue(batchId, TestBatchPropertyId.getUnknownBatchPropertyId())); - assertEquals(MaterialsError.UNKNOWN_BATCH_PROPERTY_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "getBatchStageId", args = { BatchId.class }) - public void testGetBatchStageId() { - - MaterialsActionSupport.testConsumer(472707250737446845L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - // create a stage and a batch - StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_3); - - BatchId batchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_3, TestMaterialId.MATERIAL_2, 4.5); - - // show that the batch does not have a stage - Optional optionalOwningStageId = materialsDataManager.getBatchStageId(batchId); - assertFalse(optionalOwningStageId.isPresent()); - - // put the batch onto the stage - materialsDataManager.moveBatchToStage(batchId, stageId); - - // show that the batch is on the stage - optionalOwningStageId = materialsDataManager.getBatchStageId(batchId); - assertTrue(optionalOwningStageId.isPresent()); - assertEquals(stageId, optionalOwningStageId.get()); - - // put the batch back into inventory - materialsDataManager.moveBatchToInventory(batchId); - - // show that the batch is not on any stage - optionalOwningStageId = materialsDataManager.getBatchStageId(batchId); - assertFalse(optionalOwningStageId.isPresent()); - - }); - /* precondition test: if the batch id is null */ - MaterialsActionSupport.testConsumer(8182230906627557939L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.getBatchStageId(null)); - assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); - }); - - /* precondition test: if the batch id is unknown */ - MaterialsActionSupport.testConsumer(3682958492574276233L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.getBatchStageId(new BatchId(100000))); - assertEquals(MaterialsError.UNKNOWN_BATCH_ID, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "getBatchTime", args = { BatchId.class }) - public void testGetBatchTime() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - Map expectedBatchTimes = new LinkedHashMap<>(); - - Arrays.asList(TestMaterialsProducerId.values()).stream().forEach((mpid) -> { - - }); - ; - - // build batches at several times - for (int i = 1; i <= 10; i++) { - - double planTime = i; - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(planTime, (c) -> { - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - TestMaterialsProducerId materialsProducerId = TestMaterialsProducerId.getRandomMaterialsProducerId(randomGenerator); - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - - // build a few batches - int numberOfBatches = randomGenerator.nextInt(5) + 1; - for (int j = 0; j < numberOfBatches; j++) { - - TestMaterialId materialId = TestMaterialId.getRandomMaterialId(randomGenerator); - double amount = randomGenerator.nextDouble(); - BatchId batchId = materialsDataManager.addBatch(materialsProducerId, materialId, amount); - expectedBatchTimes.put(batchId, c.getTime()); - } - - // show that all batches have the expected batch times - - List inventoryBatches = materialsDataManager.getInventoryBatches(materialsProducerId); - - for (BatchId batchId : inventoryBatches) { - double expectedBatchTime = expectedBatchTimes.get(batchId); - double actualBatchTime = materialsDataManager.getBatchTime(batchId); - assertEquals(expectedBatchTime, actualBatchTime); - } - - })); - - } - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - MaterialsActionSupport.testConsumers(8449887495666455982L, testPlugin); - - /* precondition test: if the batch id is null */ - MaterialsActionSupport.testConsumer(2942652850143901549L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.getBatchTime(null)); - assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); - }); - - /* precondition test: if the batch id is unknown */ - MaterialsActionSupport.testConsumer(8578067293001466760L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.getBatchTime(new BatchId(1000000))); - assertEquals(MaterialsError.UNKNOWN_BATCH_ID, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "getInventoryBatches", args = { MaterialsProducerId.class }) - public void testGetInventoryBatches() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // create a data structure to hold expectd inventories - Map> expectedInventoryBatchesByProducer = new LinkedHashMap<>(); - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - expectedInventoryBatchesByProducer.put(testMaterialsProducerId, new LinkedHashSet<>()); - } - - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - Set expectedInventoryBatches = expectedInventoryBatchesByProducer.get(testMaterialsProducerId); - - // create some batches - for (int i = 0; i < 100; i++) { - BatchId batchId = materialsDataManager.addBatch(testMaterialsProducerId, TestMaterialId.getRandomMaterialId(randomGenerator), randomGenerator.nextInt(100)); - expectedInventoryBatches.add(batchId); - } - - // show that the inventory batches are correct - List inventory = materialsDataManager.getInventoryBatches(testMaterialsProducerId); - Set actualInventoryBatches = new LinkedHashSet<>(inventory); - assertEquals(inventory.size(), actualInventoryBatches.size()); - assertEquals(expectedInventoryBatches, actualInventoryBatches); - - // create some stages and put some of the batches onto stages - List batches = new ArrayList<>(expectedInventoryBatches); - Collections.shuffle(batches, new Random(randomGenerator.nextLong())); - int batchIndex = 0; - for (int i = 0; i < 5; i++) { - StageId stageId = materialsDataManager.addStage(testMaterialsProducerId); - for (int j = 0; j < 5; j++) { - BatchId batchId = batches.get(batchIndex++); - materialsDataManager.moveBatchToStage(batchId, stageId); - expectedInventoryBatches.remove(batchId); - } - } - - // destroy a few inventory batches - for (int i = 0; i < 10; i++) { - BatchId batchId = batches.get(batchIndex++); - materialsDataManager.removeBatch(batchId); - expectedInventoryBatches.remove(batchId); - } - - })); - - } - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - Set expectedInventoryBatches = expectedInventoryBatchesByProducer.get(testMaterialsProducerId); - - // show that the inventory batches are correct - List batchList = materialsDataManager.getInventoryBatches(testMaterialsProducerId); - Set actualInventoryBatches = new LinkedHashSet<>(batchList); - assertEquals(batchList.size(), actualInventoryBatches.size()); - assertEquals(expectedInventoryBatches, actualInventoryBatches); - } - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - MaterialsActionSupport.testConsumers(6343917844917632364L, testPlugin); - - /* precondition tests if the materials producerId id is null */ - MaterialsActionSupport.testConsumer(6759896268818524420L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.getInventoryBatches(null)); - assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - }); - - /* precondition tests if the materials producerId id is unknown */ - MaterialsActionSupport.testConsumer(944257921550728616L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.getInventoryBatches(TestMaterialsProducerId.getUnknownMaterialsProducerId())); - assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "getInventoryBatchesByMaterialId", args = { MaterialsProducerId.class, MaterialId.class }) - public void testGetInventoryBatchesByMaterialId() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // create a data structure to hold the expected inventories - Map>> expectedInventoryBatchesMap = new LinkedHashMap<>(); - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - Map> materialToBatchesMap = new LinkedHashMap<>(); - expectedInventoryBatchesMap.put(testMaterialsProducerId, materialToBatchesMap); - for (TestMaterialId testMaterialId : TestMaterialId.values()) { - materialToBatchesMap.put(testMaterialId, new LinkedHashSet<>()); - } - } - /* - * Have the various material producers create batches and place them in - * inventory. Add some batches to stages. Destroy some of the batches. - */ - for (int k = 0; k < 10; k++) { - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.getRandomMaterialsProducerId(randomGenerator); - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - Map> materialToBatchesMap = expectedInventoryBatchesMap.get(materialsProducerId); - - // create some (100) batches - for (int i = 0; i < 100; i++) { - - MaterialId materialId = TestMaterialId.getRandomMaterialId(randomGenerator); - BatchId batchId = materialsDataManager.addBatch(materialsProducerId, materialId, randomGenerator.nextInt(100)); - materialToBatchesMap.get(materialId).add(batchId); - } - - // show that the inventory batches are correct - for (TestMaterialId testMaterialId : TestMaterialId.values()) { - List batchList = materialsDataManager.getInventoryBatchesByMaterialId(materialsProducerId, testMaterialId); - Set actualInventoryBatches = new LinkedHashSet<>(batchList); - assertEquals(batchList.size(), actualInventoryBatches.size()); - Set expectedInventoryBatches = materialToBatchesMap.get(testMaterialId); - assertEquals(expectedInventoryBatches, actualInventoryBatches); - } - - /* - * Create some stages and put some (25) of the batches onto - * stages - * - */ - List batches = new ArrayList<>(); - for (Set expectedInventoryBatches : materialToBatchesMap.values()) { - batches.addAll(expectedInventoryBatches); - } - - Collections.shuffle(batches, new Random(randomGenerator.nextLong())); - int batchIndex = 0; - for (int i = 0; i < 5; i++) { - StageId stageId = materialsDataManager.addStage(materialsProducerId); - for (int j = 0; j < 5; j++) { - BatchId batchId = batches.get(batchIndex++); - materialsDataManager.moveBatchToStage(batchId, stageId); - MaterialId batchMaterial = materialsDataManager.getBatchMaterial(batchId); - materialToBatchesMap.get(batchMaterial).remove(batchId); - - } - } - - // destroy a few inventory batches - for (int i = 0; i < 10; i++) { - BatchId batchId = batches.get(batchIndex++); - MaterialId batchMaterial = materialsDataManager.getBatchMaterial(batchId); - materialsDataManager.removeBatch(batchId); - materialToBatchesMap.get(batchMaterial).remove(batchId); - } - - })); - } - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - - // show that the inventory batches are correct - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - Map> materialToBatchesMap = expectedInventoryBatchesMap.get(testMaterialsProducerId); - for (TestMaterialId testMaterialId : TestMaterialId.values()) { - List invBatches = materialsDataManager.getInventoryBatchesByMaterialId(testMaterialsProducerId, testMaterialId); - Set actualInventoryBatches = new LinkedHashSet<>(invBatches); - assertEquals(invBatches.size(), actualInventoryBatches.size()); - Set expectedInventoryBatches = materialToBatchesMap.get(testMaterialId); - assertEquals(expectedInventoryBatches, actualInventoryBatches); - } - } - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - MaterialsActionSupport.testConsumers(8436700054410844417L, testPlugin); - - /* precondition test: if the materials producerId id is null */ - MaterialsActionSupport.testConsumer(8066333940937253765L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.getInventoryBatchesByMaterialId(null, TestMaterialId.MATERIAL_1)); - assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - }); - - /* precondition test: if the materials producerId id is unknown */ - MaterialsActionSupport.testConsumer(3143917391309849287L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, - () -> materialsDataManager.getInventoryBatchesByMaterialId(TestMaterialsProducerId.getUnknownMaterialsProducerId(), TestMaterialId.MATERIAL_2)); - assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - }); - - /* precondition test: if the material id is null */ - MaterialsActionSupport.testConsumer(874196115936784556L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.getInventoryBatchesByMaterialId(TestMaterialsProducerId.MATERIALS_PRODUCER_1, null)); - assertEquals(MaterialsError.NULL_MATERIAL_ID, contractException.getErrorType()); - }); - - /* precondition test: if the material id is null */ - MaterialsActionSupport.testConsumer(9112311292467047420L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, - () -> materialsDataManager.getInventoryBatchesByMaterialId(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.getUnknownMaterialId())); - assertEquals(MaterialsError.UNKNOWN_MATERIAL_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "getMaterialIds", args = {}) - public void testGetMaterialIds() { - MaterialsActionSupport.testConsumer(6611654668838622496L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - assertEquals(EnumSet.allOf(TestMaterialId.class), materialsDataManager.getMaterialIds()); - }); - } - - @Test - @UnitTestMethod(name = "getMaterialsProducerIds", args = {}) - public void testGetMaterialsProducerIds() { - - MaterialsActionSupport.testConsumer(3824970086302200338L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - assertEquals(EnumSet.allOf(TestMaterialsProducerId.class), materialsDataManager.getMaterialsProducerIds()); - }); - } - - @Test - @UnitTestMethod(name = "materialIdExists", args = { MaterialId.class }) - public void testMaterialIdExists() { - - MaterialsActionSupport.testConsumer(6918669723394457093L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - - for (TestMaterialId testMaterialId : TestMaterialId.values()) { - assertTrue(materialsDataManager.materialIdExists(testMaterialId)); - } - assertFalse(materialsDataManager.materialIdExists(TestMaterialId.getUnknownMaterialId())); - assertFalse(materialsDataManager.materialIdExists(null)); - }); - } - - @Test - @UnitTestMethod(name = "getMaterialsProducerPropertyDefinition", args = { MaterialsProducerPropertyId.class }) - public void testGetMaterialsProducerPropertyDefinition() { - - MaterialsActionSupport.testConsumer(7151961147034751776L, (c) -> { - MaterialsDataManager materialsDaView = c.getDataManager(MaterialsDataManager.class); - - for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId.values()) { - PropertyDefinition expectedPropertyDefinition = testMaterialsProducerPropertyId.getPropertyDefinition(); - PropertyDefinition actualPropertyDefinition = materialsDaView.getMaterialsProducerPropertyDefinition(testMaterialsProducerPropertyId); - assertEquals(expectedPropertyDefinition, actualPropertyDefinition); - } - - }); - - /* precondition test: if the materials producer property id is null */ - MaterialsActionSupport.testConsumer(4030472148503907839L, (c) -> { - MaterialsDataManager materialsDaView = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDaView.getMaterialsProducerPropertyDefinition(null)); - assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_PROPERTY_ID, contractException.getErrorType()); - }); - - /* - * precondition test: if the materials producer property id is unknown - */ - MaterialsActionSupport.testConsumer(863172317284141879L, (c) -> { - MaterialsDataManager materialsDaView = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, - () -> materialsDaView.getMaterialsProducerPropertyDefinition(TestMaterialsProducerPropertyId.getUnknownMaterialsProducerPropertyId())); - assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_PROPERTY_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "getMaterialsProducerPropertyIds", args = {}) - public void testGetMaterialsProducerPropertyIds() { - MaterialsActionSupport.testConsumer(8718225529106870071L, (c) -> { - MaterialsDataManager materialsDaView = c.getDataManager(MaterialsDataManager.class); - assertEquals(EnumSet.allOf(TestMaterialsProducerPropertyId.class), materialsDaView.getMaterialsProducerPropertyIds()); - }); - } - - @Test - @UnitTestMethod(name = "getMaterialsProducerPropertyTime", args = { MaterialsProducerId.class, MaterialsProducerPropertyId.class }) - public void testGetMaterialsProducerPropertyTime() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // create a structure to hold expected assignment times for materials - // producer property values - Map expectedTimesMap = new LinkedHashMap<>(); - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId.values()) { - expectedTimesMap.put(new MultiKey(testMaterialsProducerId, testMaterialsProducerPropertyId), new MutableDouble()); - } - } - - // determine a reasonable number of changes per time - int propertyChangeCount = TestMaterialsProducerId.size() * TestMaterialsProducerPropertyId.size() / 10; - - // update several random property values at various times - for (int i = 0; i < 10; i++) { - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - - for (int j = 0; j < propertyChangeCount; j++) { - TestMaterialsProducerId materialsProducerId = TestMaterialsProducerId.getRandomMaterialsProducerId(randomGenerator); - TestMaterialsProducerPropertyId materialsProducerPropertyId = TestMaterialsProducerPropertyId.getRandomMutableMaterialsProducerPropertyId(randomGenerator); - Object propertyValue = materialsProducerPropertyId.getRandomPropertyValue(randomGenerator); - materialsDataManager.setMaterialsProducerPropertyValue(materialsProducerId, materialsProducerPropertyId, propertyValue); - MultiKey multiKey = new MultiKey(materialsProducerId, materialsProducerPropertyId); - expectedTimesMap.get(multiKey).setValue(c.getTime()); - } - })); - } - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(10, (c) -> { - MaterialsDataManager materialsDaView = c.getDataManager(MaterialsDataManager.class); - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId.values()) { - MultiKey multiKey = new MultiKey(testMaterialsProducerId, testMaterialsProducerPropertyId); - double expectedTime = expectedTimesMap.get(multiKey).getValue(); - double actualTime = materialsDaView.getMaterialsProducerPropertyTime(testMaterialsProducerId, testMaterialsProducerPropertyId); - assertEquals(expectedTime, actualTime); - } - } - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - MaterialsActionSupport.testConsumers(4362229716953652532L, testPlugin); - - /* precondition test: if the materials producerId id is null */ - MaterialsActionSupport.testConsumer(8047663013308359028L, (c) -> { - MaterialsDataManager materialsDaView = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, - () -> materialsDaView.getMaterialsProducerPropertyTime(null, TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_4_BOOLEAN_MUTABLE_TRACK)); - assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - }); - - /* precondition test: if the materials producerId id is unknown */ - MaterialsActionSupport.testConsumer(7076209560671384217L, (c) -> { - MaterialsDataManager materialsDaView = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDaView.getMaterialsProducerPropertyTime(TestMaterialsProducerId.getUnknownMaterialsProducerId(), - TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_4_BOOLEAN_MUTABLE_TRACK)); - assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - }); - - /* precondition test: if the materials producerId property id is null */ - MaterialsActionSupport.testConsumer(8444324674368897195L, (c) -> { - MaterialsDataManager materialsDaView = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDaView.getMaterialsProducerPropertyTime(TestMaterialsProducerId.MATERIALS_PRODUCER_1, null)); - assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_PROPERTY_ID, contractException.getErrorType()); - }); - - /* - * precondition test: if the materials producerId property id is unknown - */ - MaterialsActionSupport.testConsumer(3195486517854831744L, (c) -> { - MaterialsDataManager materialsDaView = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, - () -> materialsDaView.getMaterialsProducerPropertyTime(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialsProducerPropertyId.getUnknownMaterialsProducerPropertyId())); - assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_PROPERTY_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "getMaterialsProducerPropertyValue", args = { MaterialsProducerId.class, MaterialsProducerPropertyId.class }) - public void testGetMaterialsProducerPropertyValue() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // create a structure to hold expected assignment times for materials - // producer property values - Map expectedValuesMap = new LinkedHashMap<>(); - - // initialize the materials data manager - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - MaterialsDataManager materialsDaView = c.getDataManager(MaterialsDataManager.class); - - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId.values()) { - MultiKey multiKey = new MultiKey(testMaterialsProducerId, testMaterialsProducerPropertyId); - Object value = materialsDaView.getMaterialsProducerPropertyValue(testMaterialsProducerId, testMaterialsProducerPropertyId); - expectedValuesMap.put(multiKey, value); - } - } - - })); - - // determine a reasonable number of changes per time - int propertyChangeCount = TestMaterialsProducerId.size() * TestMaterialsProducerPropertyId.size() / 10; - - // update several random property values at various times - for (int i = 0; i < 10; i++) { - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - for (int j = 0; j < propertyChangeCount; j++) { - TestMaterialsProducerId materialsProducerId = TestMaterialsProducerId.getRandomMaterialsProducerId(randomGenerator); - TestMaterialsProducerPropertyId materialsProducerPropertyId = TestMaterialsProducerPropertyId.getRandomMutableMaterialsProducerPropertyId(randomGenerator); - Object propertyValue = materialsProducerPropertyId.getRandomPropertyValue(randomGenerator); - materialsDataManager.setMaterialsProducerPropertyValue(materialsProducerId, materialsProducerPropertyId, propertyValue); - MultiKey multiKey = new MultiKey(materialsProducerId, materialsProducerPropertyId); - expectedValuesMap.put(multiKey, propertyValue); - } - })); - } - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(10, (c) -> { - MaterialsDataManager materialsDaView = c.getDataManager(MaterialsDataManager.class); - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId.values()) { - MultiKey multiKey = new MultiKey(testMaterialsProducerId, testMaterialsProducerPropertyId); - Object expectedValue = expectedValuesMap.get(multiKey); - Object actualValue = materialsDaView.getMaterialsProducerPropertyValue(testMaterialsProducerId, testMaterialsProducerPropertyId); - assertEquals(expectedValue, actualValue); - } - } - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - MaterialsActionSupport.testConsumers(3587272435527239583L, testPlugin); - - /* precondition test: if the materials producerId id is null */ - MaterialsActionSupport.testConsumer(4247143641356704364L, (c) -> { - MaterialsDataManager materialsDaView = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, - () -> materialsDaView.getMaterialsProducerPropertyValue(null, TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_4_BOOLEAN_MUTABLE_TRACK)); - assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - }); - - /* precondition test: if the materials producerId id is unknown */ - MaterialsActionSupport.testConsumer(7731689857034028615L, (c) -> { - MaterialsDataManager materialsDaView = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDaView.getMaterialsProducerPropertyValue(TestMaterialsProducerId.getUnknownMaterialsProducerId(), - TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_4_BOOLEAN_MUTABLE_TRACK)); - assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - }); - - /* precondition test: if the materials producerId property id is null */ - MaterialsActionSupport.testConsumer(1004792420489047936L, (c) -> { - MaterialsDataManager materialsDaView = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDaView.getMaterialsProducerPropertyValue(TestMaterialsProducerId.MATERIALS_PRODUCER_1, null)); - assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_PROPERTY_ID, contractException.getErrorType()); - }); - - /* - * precondition test: if the materials producerId property id is unknown - */ - MaterialsActionSupport.testConsumer(133851619411326116L, (c) -> { - MaterialsDataManager materialsDaView = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, - () -> materialsDaView.getMaterialsProducerPropertyValue(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialsProducerPropertyId.getUnknownMaterialsProducerPropertyId())); - assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_PROPERTY_ID, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "getMaterialsProducerResourceLevel", args = { MaterialsProducerId.class, ResourceId.class }) - public void testGetMaterialsProducerResourceLevel() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // create a structure to hold expected resource levels for producers - Map expectedLevelsMap = new LinkedHashMap<>(); - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - for (TestResourceId testResourceId : TestResourceId.values()) { - expectedLevelsMap.put(new MultiKey(testMaterialsProducerId, testResourceId), new MutableLong()); - } - } - - // determine a reasonable number of changes per time - int resourceLevelChangeCount = TestMaterialsProducerId.size() * TestResourceId.size() / 10; - - // update several random resource levels values at various times - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - - for (int j = 0; j < resourceLevelChangeCount; j++) { - - StageId stageId = materialsDataManager.addStage(testMaterialsProducerId); - TestResourceId testResourceId = TestResourceId.getRandomResourceId(randomGenerator); - Long resourceLevel = (long) (randomGenerator.nextInt(100) + 1); - materialsDataManager.convertStageToResource(stageId, testResourceId, resourceLevel); - MultiKey multiKey = new MultiKey(testMaterialsProducerId, testResourceId); - MutableLong mutableLong = expectedLevelsMap.get(multiKey); - mutableLong.increment(resourceLevel); - } - })); - } - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(10, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - for (TestResourceId testResourceId : TestResourceId.values()) { - MultiKey multiKey = new MultiKey(testMaterialsProducerId, testResourceId); - long expectedLevel = expectedLevelsMap.get(multiKey).getValue(); - long actualLevel = materialsDataManager.getMaterialsProducerResourceLevel(testMaterialsProducerId, testResourceId); - assertEquals(expectedLevel, actualLevel); - } - } - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - MaterialsActionSupport.testConsumers(1633676078121550637L, testPlugin); - - /* precondition test: if the materials producerId id is null */ - MaterialsActionSupport.testConsumer(11082575022266925L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.getMaterialsProducerResourceLevel(null, TestResourceId.RESOURCE_1)); - assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - }); - - /* precondition test: if the materials producerId id is unknown */ - MaterialsActionSupport.testConsumer(6362058221207452078L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, - () -> materialsDataManager.getMaterialsProducerResourceLevel(TestMaterialsProducerId.getUnknownMaterialsProducerId(), TestResourceId.RESOURCE_1)); - assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - }); - - /* precondition test: if the resource id is null */ - MaterialsActionSupport.testConsumer(7531288497048301736L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, - () -> materialsDataManager.getMaterialsProducerResourceLevel(TestMaterialsProducerId.MATERIALS_PRODUCER_1, null)); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the resource id is unknown */ - MaterialsActionSupport.testConsumer(2745862264533327311L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, - () -> materialsDataManager.getMaterialsProducerResourceLevel(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestResourceId.getUnknownResourceId())); - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "getMaterialsProducerResourceTime", args = { MaterialsProducerId.class, ResourceId.class }) - public void testGetMaterialsProducerResourceTime() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // create a structure to hold expected assignment times for producer - // resource levels - Map expectedTimesMap = new LinkedHashMap<>(); - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - for (TestResourceId testResourceId : TestResourceId.values()) { - expectedTimesMap.put(new MultiKey(testMaterialsProducerId, testResourceId), new MutableDouble()); - } - } - - // update several random resource levels at various times - double plantime = 0; - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(plantime++, (c) -> { - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - for (int j = 0; j < 5; j++) { - TestResourceId testResourceId = TestResourceId.getRandomResourceId(randomGenerator); - Integer resourceLevel = randomGenerator.nextInt(100) + 1; - StageId stageId = materialsDataManager.addStage(testMaterialsProducerId); - materialsDataManager.convertStageToResource(stageId, testResourceId, resourceLevel); - MultiKey multiKey = new MultiKey(testMaterialsProducerId, testResourceId); - expectedTimesMap.get(multiKey).setValue(c.getTime()); - } - })); - } - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(10, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - for (TestResourceId testResourceId : TestResourceId.values()) { - MultiKey multiKey = new MultiKey(testMaterialsProducerId, testResourceId); - double expectedTime = expectedTimesMap.get(multiKey).getValue(); - double actualTime = materialsDataManager.getMaterialsProducerResourceTime(testMaterialsProducerId, testResourceId); - assertEquals(expectedTime, actualTime); - } - } - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - MaterialsActionSupport.testConsumers(102509608008549692L, testPlugin); - - /* precondition test: if the materials producerId id is null */ - MaterialsActionSupport.testConsumer(5802266741184871831L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.getMaterialsProducerResourceTime(null, TestResourceId.RESOURCE_1)); - assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - }); - - /* precondition test: if the materials producerId id is unknown */ - MaterialsActionSupport.testConsumer(4254859094661916110L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, - () -> materialsDataManager.getMaterialsProducerResourceTime(TestMaterialsProducerId.getUnknownMaterialsProducerId(), TestResourceId.RESOURCE_1)); - assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - }); - - /* precondition test: if the resource id is null */ - MaterialsActionSupport.testConsumer(3911919336328300644L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, - () -> materialsDataManager.getMaterialsProducerResourceTime(TestMaterialsProducerId.MATERIALS_PRODUCER_1, null)); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the resource id is unknown */ - MaterialsActionSupport.testConsumer(2654216033515042203L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, - () -> materialsDataManager.getMaterialsProducerResourceTime(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestResourceId.getUnknownResourceId())); - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "getOfferedStages", args = { MaterialsProducerId.class }) - public void testGetOfferedStages() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - /* - * create several stages and place about half of them into the - * offer state - */ - Set expectedStageOffers = new LinkedHashSet<>(); - - for (int i = 0; i < 100; i++) { - StageId stageId = materialsDataManager.addStage(testMaterialsProducerId); - if (randomGenerator.nextBoolean()) { - expectedStageOffers.add(stageId); - materialsDataManager.setStageOfferState(stageId, true); - } - } - - List stages = materialsDataManager.getOfferedStages(testMaterialsProducerId); - Set actualStages = new LinkedHashSet<>(stages); - assertEquals(stages.size(), actualStages.size()); - assertEquals(expectedStageOffers, actualStages); - - })); - } - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - MaterialsActionSupport.testConsumers(7995017020582510238L, testPlugin); - - /* precondition tests if the materials producerId id is null */ - MaterialsActionSupport.testConsumer(1728234953072549300L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.getOfferedStages(null)); - assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - }); - - /* precondition tests if the materials producerId id is unknown */ - MaterialsActionSupport.testConsumer(809512240800144004L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.getOfferedStages(TestMaterialsProducerId.getUnknownMaterialsProducerId())); - assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "getStageBatches", args = { StageId.class }) - public void testGetStageBatches() { - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // create a structure to hold expectations - Map> expectedStageBatches = new LinkedHashMap<>(); - - // have each of the materials producers create and stage some batches - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - // create some stages and batches - for (int i = 0; i < 10; i++) { - StageId stageId = materialsDataManager.addStage(testMaterialsProducerId); - int batchCount = randomGenerator.nextInt(3) + 1; - Set batches = new LinkedHashSet<>(); - for (int j = 0; j < batchCount; j++) { - BatchId batchId = materialsDataManager.addBatch(testMaterialsProducerId, TestMaterialId.getRandomMaterialId(randomGenerator), randomGenerator.nextInt(100)); - batches.add(batchId); - materialsDataManager.moveBatchToStage(batchId, stageId); - } - expectedStageBatches.put(stageId, batches); - } - })); - } - // have an actor show that the batches are staged as expected - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - Map> actualStageBatches = new LinkedHashMap<>(); - for (MaterialsProducerId materialsProducerId : materialsDataManager.getMaterialsProducerIds()) { - List stageIds = materialsDataManager.getStages(materialsProducerId); - for (StageId stageId : stageIds) { - actualStageBatches.put(stageId, new LinkedHashSet<>(materialsDataManager.getStageBatches(stageId))); - } - } - - assertEquals(expectedStageBatches, actualStageBatches); - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - MaterialsActionSupport.testConsumers(3682458920522952415L, testPlugin); - - /* precondition tests if the stage id is null */ - MaterialsActionSupport.testConsumer(7434749084817685354L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.getStageBatches(null)); - assertEquals(MaterialsError.NULL_STAGE_ID, contractException.getErrorType()); - }); - - /* precondition tests if the stage id is unknown */ - MaterialsActionSupport.testConsumer(8988415576850624232L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.getStageBatches(new StageId(10000000))); - assertEquals(MaterialsError.UNKNOWN_STAGE_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "getStageBatchesByMaterialId", args = { StageId.class, MaterialId.class }) - public void testGetStageBatchesByMaterialId() { - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // build a structure to hold the batches expected per stage and - // material id - Map>> expectedStageBatches = new LinkedHashMap<>(); - - // have each of the materials producers create and stage some batches - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - // create some stages and batches - for (int i = 0; i < 10; i++) { - StageId stageId = materialsDataManager.addStage(testMaterialsProducerId); - int batchCount = randomGenerator.nextInt(10) + 1; - Map> map = new LinkedHashMap<>(); - for (int j = 0; j < batchCount; j++) { - TestMaterialId materialId = TestMaterialId.getRandomMaterialId(randomGenerator); - Set batches = map.get(materialId); - if (batches == null) { - batches = new LinkedHashSet<>(); - map.put(materialId, batches); - } - BatchId batchId = materialsDataManager.addBatch(testMaterialsProducerId, materialId, randomGenerator.nextInt(100)); - batches.add(batchId); - materialsDataManager.moveBatchToStage(batchId, stageId); - } - expectedStageBatches.put(stageId, map); - } - - })); - } - - // have an actor show that the batches are staged as expected - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - - /* - * gather the actual stage batches in a structure identical to the - * expected values - */ - Map>> actualStageBatches = new LinkedHashMap<>(); - for (MaterialsProducerId materialsProducerId : materialsDataManager.getMaterialsProducerIds()) { - List stageIds = materialsDataManager.getStages(materialsProducerId); - - for (StageId stageId : stageIds) { - Map> map = new LinkedHashMap<>(); - actualStageBatches.put(stageId, map); - List stageBatches = materialsDataManager.getStageBatches(stageId); - for (BatchId batchId : stageBatches) { - MaterialId materialId = materialsDataManager.getBatchMaterial(batchId); - Set set = map.get(materialId); - if (set == null) { - set = new LinkedHashSet<>(); - map.put(materialId, set); - } - set.add(batchId); - } - } - } - - // show that the actual batches are retrievable by stage and - // material id correctly - assertEquals(expectedStageBatches, actualStageBatches); - - })); - - // precondition tests - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - - // if the stage id is null - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.getStageBatchesByMaterialId(null, TestMaterialId.MATERIAL_1)); - assertEquals(MaterialsError.NULL_STAGE_ID, contractException.getErrorType()); - - // if the stage id is unknown - contractException = assertThrows(ContractException.class, () -> materialsDataManager.getStageBatchesByMaterialId(new StageId(10000000), TestMaterialId.MATERIAL_1)); - assertEquals(MaterialsError.UNKNOWN_STAGE_ID, contractException.getErrorType()); - - // if the material id is null - contractException = assertThrows(ContractException.class, () -> materialsDataManager.getStageBatchesByMaterialId(new StageId(0), null)); - assertEquals(MaterialsError.NULL_MATERIAL_ID, contractException.getErrorType()); - - // if the material id is unknown - contractException = assertThrows(ContractException.class, () -> materialsDataManager.getStageBatchesByMaterialId(new StageId(0), TestMaterialId.getUnknownMaterialId())); - assertEquals(MaterialsError.UNKNOWN_MATERIAL_ID, contractException.getErrorType()); - - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - MaterialsActionSupport.testConsumers(2013967899243685546L, testPlugin); - } - - @Test - @UnitTestMethod(name = "getStageProducer", args = { StageId.class }) - public void testGetStageProducer() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StageId stageId = materialsDataManager.addStage(testMaterialsProducerId); - assertEquals(testMaterialsProducerId, materialsDataManager.getStageProducer(stageId)); - })); - } - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - MaterialsActionSupport.testConsumers(4322374809851867527L, testPlugin); - - /* precondition test: if the stage id is null */ - MaterialsActionSupport.testConsumer(3429442631390139742L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.getStageProducer(null)); - assertEquals(MaterialsError.NULL_STAGE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the stage id is unknown */ - MaterialsActionSupport.testConsumer(8553021441594074433L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.getStageProducer(new StageId(1000000000))); - assertEquals(MaterialsError.UNKNOWN_STAGE_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "getStages", args = { MaterialsProducerId.class }) - public void testGetStages() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - Set expectedStages = new LinkedHashSet<>(); - - int count = randomGenerator.nextInt(10) + 1; - for (int i = 0; i < count; i++) { - StageId stageId = materialsDataManager.addStage(testMaterialsProducerId); - expectedStages.add(stageId); - } - - List stages = materialsDataManager.getStages(testMaterialsProducerId); - - Set actualStageIds = new LinkedHashSet<>(stages); - assertEquals(stages.size(), actualStageIds.size()); - assertEquals(expectedStages, actualStageIds); - })); - } - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - MaterialsActionSupport.testConsumers(3431193355375533655L, testPlugin); - - /* precondition test: if the materials producer id is null */ - MaterialsActionSupport.testConsumer(8012112350970626114L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.getStages(null)); - assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - }); - - /* precondition test: if the materials producer id is unknown */ - MaterialsActionSupport.testConsumer(4013634140214782310L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.getStages(TestMaterialsProducerId.getUnknownMaterialsProducerId())); - assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "isStageOffered", args = { StageId.class }) - public void testIsStageOffered() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - - for (int i = 0; i < 100; i++) { - StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - assertFalse(materialsDataManager.isStageOffered(stageId)); - materialsDataManager.setStageOfferState(stageId, true); - assertTrue(materialsDataManager.isStageOffered(stageId)); - } - - // precondition tests - - // if the stage id is null - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.isStageOffered(null)); - assertEquals(MaterialsError.NULL_STAGE_ID, contractException.getErrorType()); - - // if the stage id is unknown - contractException = assertThrows(ContractException.class, () -> materialsDataManager.isStageOffered(new StageId(1000000))); - assertEquals(MaterialsError.UNKNOWN_STAGE_ID, contractException.getErrorType()); - - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - MaterialsActionSupport.testConsumers(475901778920012875L, testPlugin); - } - - @Test - @UnitTestMethod(name = "materialsProducerIdExists", args = { MaterialsProducerId.class }) - public void testMaterialsProducerIdExists() { - - MaterialsActionSupport.testConsumer(4005535514531641716L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - assertTrue(materialsDataManager.materialsProducerIdExists(testMaterialsProducerId)); - } - assertFalse(materialsDataManager.materialsProducerIdExists(TestMaterialsProducerId.getUnknownMaterialsProducerId())); - assertFalse(materialsDataManager.materialsProducerIdExists(null)); - }); - - } - - @Test - @UnitTestMethod(name = "materialsProducerPropertyIdExists", args = { MaterialsProducerPropertyId.class }) - public void testMaterialsProducerPropertyIdExists() { - - MaterialsActionSupport.testConsumer(8256309156804000329L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - - for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId.values()) { - assertTrue(materialsDataManager.materialsProducerPropertyIdExists(testMaterialsProducerPropertyId)); - } - assertFalse(materialsDataManager.materialsProducerPropertyIdExists(TestMaterialsProducerPropertyId.getUnknownMaterialsProducerPropertyId())); - assertFalse(materialsDataManager.materialsProducerPropertyIdExists(null)); - }); - } - - @Test - @UnitTestMethod(name = "stageExists", args = { StageId.class }) - public void testStageExists() { - MaterialsActionSupport.testConsumer(4646356228574091149L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - for (int i = 0; i < 10; i++) { - StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - assertTrue(materialsDataManager.stageExists(stageId)); - } - assertFalse(materialsDataManager.stageExists(null)); - assertFalse(materialsDataManager.stageExists(new StageId(123))); - }); - } - - @Test - @UnitTestMethod(name = "moveBatchToInventory", args = { BatchId.class }) - public void testMoveBatchToInventoryEvent() { - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - double actionTime = 0; - - // create containers to hold observations - Set expectedObservations = new LinkedHashSet<>(); - Set actualObservations = new LinkedHashSet<>(); - - // have an observer record batches being removed from stages - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { - c.subscribe(StageMembershipRemovalEvent.class, (c2, e) -> { - MultiKey multiKey = new MultiKey(c2.getTime(), e.getBatchId(), e.getStageId()); - actualObservations.add(multiKey); - }); - })); - - /* - * Have the materials producers create batches and place about half of - * them on stages - */ - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - - for (int i = 0; i < 40; i++) { - materialsDataManager.addBatch(testMaterialsProducerId, TestMaterialId.getRandomMaterialId(randomGenerator), randomGenerator.nextDouble() + 0.01); - - } - - for (int i = 0; i < 5; i++) { - materialsDataManager.addStage(testMaterialsProducerId); - } - - List stages = materialsDataManager.getStages(testMaterialsProducerId); - List batches = materialsDataManager.getInventoryBatches(testMaterialsProducerId); - for (BatchId batchId : batches) { - if (randomGenerator.nextBoolean()) { - StageId stageId = stages.get(randomGenerator.nextInt(stages.size())); - materialsDataManager.moveBatchToStage(batchId, stageId); - } - } - - })); - } - - /* - * Have the materials producers return some about half of the staged - * batches to inventory and show that the batches are now in inventory - */ - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - - List stages = materialsDataManager.getStages(testMaterialsProducerId); - for (StageId stageId : stages) { - List batches = materialsDataManager.getStageBatches(stageId); - for (BatchId batchId : batches) { - if (randomGenerator.nextBoolean()) { - materialsDataManager.moveBatchToInventory(batchId); - // show that the batch was returned to inventory - assertFalse(materialsDataManager.getBatchStageId(batchId).isPresent()); - assertTrue(materialsDataManager.getInventoryBatches(testMaterialsProducerId).contains(batchId)); - // create the observation - MultiKey multiKey = new MultiKey(c.getTime(), batchId, stageId); - expectedObservations.add(multiKey); - } - } - } - })); - } - - // have the observer show that the correct observations were made - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { - assertTrue(expectedObservations.size() > 0); - assertEquals(expectedObservations, actualObservations); - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - MaterialsActionSupport.testConsumers(5136057466059323708L, testPlugin); - - /* precondition test: if the batch id is null */ - MaterialsActionSupport.testConsumer(9033912130601526542L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.moveBatchToInventory(null)); - assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); - }); - - /* precondition test: if the batch id is unknown */ - MaterialsActionSupport.testConsumer(4357313141781993780L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.moveBatchToInventory(new BatchId(10000000))); - assertEquals(MaterialsError.UNKNOWN_BATCH_ID, contractException.getErrorType()); - }); - - /* precondition test: if the batch is not staged */ - MaterialsActionSupport.testConsumer(5899034328012868517L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - BatchId batchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 5.0); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.moveBatchToInventory(batchId)); - assertEquals(MaterialsError.BATCH_NOT_STAGED, contractException.getErrorType()); - }); - - /* precondition test: if the stage containing the batch is offered */ - MaterialsActionSupport.testConsumer(6151702850690578711L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - BatchId batchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 5.0); - StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - materialsDataManager.moveBatchToStage(batchId, stageId); - materialsDataManager.setStageOfferState(stageId, true); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.moveBatchToInventory(batchId)); - assertEquals(MaterialsError.OFFERED_STAGE_UNALTERABLE, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "moveBatchToStage", args = { BatchId.class, StageId.class }) - public void testMoveBatchToStageEvent() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - double actionTime = 0; - - // create containers to hold observations - Set expectedObservations = new LinkedHashSet<>(); - Set actualObservations = new LinkedHashSet<>(); - - // have an observer record observations of batches being assigned to - // stages - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { - c.subscribe(StageMembershipAdditionEvent.class, (c2, e) -> { - MultiKey multiKey = new MultiKey(c2.getTime(), e.getBatchId(), e.getStageId()); - actualObservations.add(multiKey); - }); - })); - - // have the producers create several batches and stages - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - for (int i = 0; i < 40; i++) { - materialsDataManager.addBatch(testMaterialsProducerId, TestMaterialId.getRandomMaterialId(randomGenerator), randomGenerator.nextDouble() + 0.01); - } - for (int i = 0; i < 5; i++) { - materialsDataManager.addStage(testMaterialsProducerId); - } - })); - } - - // have the producers put about half of their batches onto stages - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - List stages = materialsDataManager.getStages(testMaterialsProducerId); - List batches = materialsDataManager.getInventoryBatches(testMaterialsProducerId); - for (BatchId batchId : batches) { - if (randomGenerator.nextBoolean()) { - StageId stageId = stages.get(randomGenerator.nextInt(stages.size())); - materialsDataManager.moveBatchToStage(batchId, stageId); - - // show that the batch is now on the stage - Optional optionalStageId = materialsDataManager.getBatchStageId(batchId); - assertTrue(optionalStageId.isPresent()); - StageId actualStageId = optionalStageId.get(); - assertEquals(stageId, actualStageId); - - // add the expected observation - MultiKey multiKey = new MultiKey(c.getTime(), batchId, stageId); - expectedObservations.add(multiKey); - } - } - - })); - } - // have the observer show that the correct observations were generated - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { - assertTrue(expectedObservations.size() > 0); - assertEquals(expectedObservations, actualObservations); - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - MaterialsActionSupport.testConsumers(6845954292451913670L, testPlugin); - - /* precondition test: if the batch id is null */ - MaterialsActionSupport.testConsumer(0, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.moveBatchToStage(null, stageId)); - assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); - }); - - /* precondition test: if the batch id is unknown */ - MaterialsActionSupport.testConsumer(0, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.moveBatchToStage(new BatchId(100000000), stageId)); - assertEquals(MaterialsError.UNKNOWN_BATCH_ID, contractException.getErrorType()); - }); - - /* precondition test: if the batch is already staged */ - MaterialsActionSupport.testConsumer(0, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - BatchId batchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_2, 5.6); - StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - materialsDataManager.moveBatchToStage(batchId, stageId); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.moveBatchToStage(batchId, stageId)); - assertEquals(MaterialsError.BATCH_ALREADY_STAGED, contractException.getErrorType()); - }); - - /* precondition test: if the stage id is null */ - MaterialsActionSupport.testConsumer(0, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - BatchId batchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_2, 5.6); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.moveBatchToStage(batchId, null)); - assertEquals(MaterialsError.NULL_STAGE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the stage id is unknown */ - MaterialsActionSupport.testConsumer(0, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - BatchId batchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_2, 5.6); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.moveBatchToStage(batchId, new StageId(10000000))); - assertEquals(MaterialsError.UNKNOWN_STAGE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the stage is offered */ - MaterialsActionSupport.testConsumer(0, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - BatchId batchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_2, 5.6); - StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - materialsDataManager.setStageOfferState(stageId, true); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.moveBatchToStage(batchId, stageId)); - assertEquals(MaterialsError.OFFERED_STAGE_UNALTERABLE, contractException.getErrorType()); - }); - - /* - * precondition test: if batch and stage do not have the same owning - * materials producer - */ - MaterialsActionSupport.testConsumer(0, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - BatchId batchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_2, TestMaterialId.MATERIAL_2, 12); - StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.moveBatchToStage(batchId, stageId)); - assertEquals(MaterialsError.BATCH_STAGED_TO_DIFFERENT_OWNER, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "removeBatch", args = { BatchId.class }) - public void testRemoveBatch() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - double actionTime = 0; - - // construct data structures to hold observations - Set expectedObservations = new LinkedHashSet<>(); - Set actualObservations = new LinkedHashSet<>(); - - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { - c.subscribe(BatchImminentRemovalEvent.class, (c2, e) -> { - actualObservations.add(e.getBatchId()); - }); - })); - - /* - * Add batches -- although we will concentrate on producer 1, we will - * have the other producers generate batches for use in precondition - * tests - */ - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - for (int i = 0; i < 50; i++) { - materialsDataManager.addBatch(testMaterialsProducerId, TestMaterialId.getRandomMaterialId(randomGenerator), randomGenerator.nextDouble()); - } - })); - } - - // remove some batches - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - List inventoryBatches = materialsDataManager.getInventoryBatches(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - for (BatchId batchId : inventoryBatches) { - if (randomGenerator.nextBoolean()) { - materialsDataManager.removeBatch(batchId); - expectedObservations.add(batchId); - } - } - })); - - // show that the batches were indeed removed - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - for (BatchId batchId : expectedObservations) { - assertFalse(materialsDataManager.batchExists(batchId)); - } - })); - - // show that the observations were properly generated - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { - assertTrue(expectedObservations.size() > 0); - assertEquals(expectedObservations, actualObservations); - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - MaterialsActionSupport.testConsumers(2869388661813620663L, testPlugin); - - /* precondition test: if the batch id is null */ - MaterialsActionSupport.testConsumer(6338675888020916426L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.removeBatch(null)); - assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); - }); - - /* precondition test: if the batch id is unknown */ - MaterialsActionSupport.testConsumer(3451796010171778050L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.removeBatch(new BatchId(100000))); - assertEquals(MaterialsError.UNKNOWN_BATCH_ID, contractException.getErrorType()); - }); - - /* precondition test: if the batch is on an offered stage */ - MaterialsActionSupport.testConsumer(2022222016456137374L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - BatchId batchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 1.0); - StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - materialsDataManager.moveBatchToStage(batchId, stageId); - materialsDataManager.setStageOfferState(stageId, true); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.removeBatch(batchId)); - assertEquals(MaterialsError.OFFERED_STAGE_UNALTERABLE, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "removeStage", args = { StageId.class, boolean.class }) - public void testRemoveStage() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - double actionTime = 0; - - // create containers to hold observations - Set expectedObservations = new LinkedHashSet<>(); - Set actualObservations = new LinkedHashSet<>(); - - // have a agent observe stage creations - - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { - - c.subscribe(StageImminentRemovalEvent.class, (c2, e) -> { - actualObservations.add(new MultiKey(c2.getTime(), e.getStageId())); - }); - - c.subscribe(StageMembershipRemovalEvent.class, (c2, e) -> { - actualObservations.add(new MultiKey(c2.getTime(), e.getBatchId(), e.getStageId())); - }); - - c.subscribe(BatchImminentRemovalEvent.class, (c2, e) -> { - actualObservations.add(new MultiKey(c2.getTime(), e.getBatchId())); - }); - })); - - // have the producers create some stages with batches - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - for (int i = 0; i < 50; i++) { - StageId stageId = materialsDataManager.addStage(testMaterialsProducerId); - int batchCount = randomGenerator.nextInt(3); - for (int j = 0; j < batchCount; j++) { - BatchId batchId = materialsDataManager.addBatch(testMaterialsProducerId, TestMaterialId.getRandomMaterialId(randomGenerator), randomGenerator.nextDouble() + 0.01); - materialsDataManager.moveBatchToStage(batchId, stageId); - } - } - })); - } - - // have the producers destroy all their stages, returning about half of - // the batches to inventory - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - - List stagesToConfirm = new ArrayList<>(); - List batchesToConfirm = new ArrayList<>(); - Set expectedInventoryBatches = new LinkedHashSet<>(); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - List stages = materialsDataManager.getStages(testMaterialsProducerId); - for (StageId stageId : stages) { - boolean destroyBatches = randomGenerator.nextBoolean(); - List stageBatches = materialsDataManager.getStageBatches(stageId); - materialsDataManager.removeStage(stageId, destroyBatches); - - /* - * record the batch and stage ids that will be removed after - * the current agent activation so that they can be - * confirmed later - */ - if (destroyBatches) { - batchesToConfirm.addAll(stageBatches); - } else { - expectedInventoryBatches.addAll(stageBatches); - } - stagesToConfirm.add(stageId); - - // generate the expected observations - if (destroyBatches) { - for (BatchId batchId : stageBatches) { - expectedObservations.add(new MultiKey(c.getTime(), batchId)); - } - } - - expectedObservations.add(new MultiKey(c.getTime(), stageId)); - - } - })); - - // show that the expected batches and stages were removed - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - for (StageId stageId : stagesToConfirm) { - assertFalse(materialsDataManager.stageExists(stageId)); - } - for (BatchId batchId : batchesToConfirm) { - assertFalse(materialsDataManager.batchExists(batchId)); - } - List inventoryBatches = materialsDataManager.getInventoryBatches(testMaterialsProducerId); - Set actualInventoryBatches = new LinkedHashSet<>(inventoryBatches); - assertEquals(expectedInventoryBatches, actualInventoryBatches); - })); - - } - - // have the observer show that the correct observations were generated - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { - assertTrue(expectedObservations.size() > 0); - assertEquals(expectedObservations, actualObservations); - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - MaterialsActionSupport.testConsumers(886447125697525680L, testPlugin); - - /* precondition test: if the stage id is null */ - MaterialsActionSupport.testConsumer(0, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.removeStage(null, false)); - assertEquals(MaterialsError.NULL_STAGE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the stage id is unknown */ - MaterialsActionSupport.testConsumer(0, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.removeStage(new StageId(1000000), false)); - assertEquals(MaterialsError.UNKNOWN_STAGE_ID, contractException.getErrorType()); - }); - - /* precondition test: if stage is offered */ - MaterialsActionSupport.testConsumer(0, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - materialsDataManager.setStageOfferState(stageId, true); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.removeStage(stageId, false)); - assertEquals(MaterialsError.OFFERED_STAGE_UNALTERABLE, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "setBatchPropertyValue", args = { BatchId.class, BatchPropertyId.class, Object.class }) - public void testBatchPropertyValueAssignmentEvent() { - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - double actionTime = 0; - - // create containers for observations - Set expectedObservations = new LinkedHashSet<>(); - Set actualObservations = new LinkedHashSet<>(); - - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { - c.subscribe(BatchPropertyUpdateEvent.class, (c2, e) -> { - MultiKey multiKey = new MultiKey(c2.getTime(), e.getBatchId(), e.getBatchPropertyId(), e.getPreviousPropertyValue(), e.getCurrentPropertyValue()); - actualObservations.add(multiKey); - }); - })); - - // create some batches - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - for (int i = 0; i < 10; i++) { - materialsDataManager.addBatch(testMaterialsProducerId, TestMaterialId.getRandomMaterialId(randomGenerator), randomGenerator.nextDouble() + 0.01); - } - })); - } - - // Alter about half of the mutable properties of batches over 5 - // different times - for (int i = 0; i < 5; i++) { - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - List inventoryBatches = materialsDataManager.getInventoryBatches(testMaterialsProducerId); - for (BatchId batchId : inventoryBatches) { - TestMaterialId batchMaterial = materialsDataManager.getBatchMaterial(batchId); - for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.getTestBatchPropertyIds(batchMaterial)) { - if (testBatchPropertyId.getPropertyDefinition().propertyValuesAreMutable()) { - if (randomGenerator.nextBoolean()) { - Object newPropertyValue = testBatchPropertyId.getRandomPropertyValue(randomGenerator); - Object oldPropertyValue = materialsDataManager.getBatchPropertyValue(batchId, testBatchPropertyId); - expectedObservations.add(new MultiKey(c.getTime(), batchId, testBatchPropertyId, oldPropertyValue, newPropertyValue)); - materialsDataManager.setBatchPropertyValue(batchId, testBatchPropertyId, newPropertyValue); - - // show that the new property value is - // present - assertEquals(newPropertyValue, materialsDataManager.getBatchPropertyValue(batchId, testBatchPropertyId)); - } - } - } - } - })); - } - } - - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { - assertTrue(expectedObservations.size() > 0); - assertEquals(expectedObservations, actualObservations); - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - MaterialsActionSupport.testConsumers(6834204103777199004L, testPlugin); - - /* precondition test: if the batch id is null */ - MaterialsActionSupport.testConsumer(0, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - BatchPropertyId batchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK; - Object propertyValue = 56; - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.setBatchPropertyValue(null, batchPropertyId, propertyValue)); - assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); - }); - - /* precondition test: if the batch id is unknown */ - MaterialsActionSupport.testConsumer(0, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - BatchPropertyId batchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK; - Object propertyValue = 56; - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.setBatchPropertyValue(new BatchId(100000), batchPropertyId, propertyValue)); - assertEquals(MaterialsError.UNKNOWN_BATCH_ID, contractException.getErrorType()); - }); - - /* precondition test: if the batch property id is null */ - MaterialsActionSupport.testConsumer(0, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - MaterialId materialId = TestMaterialId.MATERIAL_1; - BatchId batchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, materialId, 1.0); - Object propertyValue = 56; - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.setBatchPropertyValue(batchId, null, propertyValue)); - assertEquals(MaterialsError.NULL_BATCH_PROPERTY_ID, contractException.getErrorType()); - }); - - /* precondition test: if the batch property id is unknown */ - MaterialsActionSupport.testConsumer(0, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - MaterialId materialId = TestMaterialId.MATERIAL_1; - BatchId batchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, materialId, 1.0); - Object propertyValue = 56; - ContractException contractException = assertThrows(ContractException.class, - () -> materialsDataManager.setBatchPropertyValue(batchId, TestBatchPropertyId.getUnknownBatchPropertyId(), propertyValue)); - assertEquals(MaterialsError.UNKNOWN_BATCH_PROPERTY_ID, contractException.getErrorType()); - }); - - /* precondition test: if batch property is not mutable */ - MaterialsActionSupport.testConsumer(0, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - MaterialId materialId = TestMaterialId.MATERIAL_1; - BatchId batchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, materialId, 1.0); - ContractException contractException = assertThrows(ContractException.class, - () -> materialsDataManager.setBatchPropertyValue(batchId, TestBatchPropertyId.BATCH_PROPERTY_1_1_BOOLEAN_IMMUTABLE_NO_TRACK, false)); - assertEquals(PropertyError.IMMUTABLE_VALUE, contractException.getErrorType()); - }); - - /* precondition test: if the batch property value is null */ - MaterialsActionSupport.testConsumer(0, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - MaterialId materialId = TestMaterialId.MATERIAL_1; - BatchId batchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, materialId, 1.0); - BatchPropertyId batchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK; - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.setBatchPropertyValue(batchId, batchPropertyId, null)); - assertEquals(MaterialsError.NULL_BATCH_PROPERTY_VALUE, contractException.getErrorType()); - }); - - /* - * precondition test: if the batch property value is not compatible with - * the corresponding property definition - */ - MaterialsActionSupport.testConsumer(0, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - MaterialId materialId = TestMaterialId.MATERIAL_1; - BatchId batchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, materialId, 1.0); - BatchPropertyId batchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK; - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.setBatchPropertyValue(batchId, batchPropertyId, 12.4)); - assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); - }); - - /* precondition test: if the batch in on an offered stage */ - MaterialsActionSupport.testConsumer(0, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - MaterialId materialId = TestMaterialId.MATERIAL_1; - BatchId batchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, materialId, 1.0); - BatchPropertyId batchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK; - Object propertyValue = 56; - StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - materialsDataManager.moveBatchToStage(batchId, stageId); - materialsDataManager.setStageOfferState(stageId, true); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.setBatchPropertyValue(batchId, batchPropertyId, propertyValue)); - assertEquals(MaterialsError.OFFERED_STAGE_UNALTERABLE, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "testSetMaterialsProducerPropertyValue", args = {}) - public void testMaterialsProducerPropertyValueAssignmentEvent() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - double actionTime = 0; - - Set expectedObservations = new LinkedHashSet<>(); - Set actualObservations = new LinkedHashSet<>(); - - // have an agent observe all changes to all producer property values - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId.values()) { - EventLabel eventLabel = MaterialsProducerPropertyUpdateEvent.getEventLabelByMaterialsProducerAndProperty(c, testMaterialsProducerId, - testMaterialsProducerPropertyId); - c.subscribe(eventLabel, (c2, e) -> { - MultiKey multiKey = new MultiKey(c2.getTime(), e.getMaterialsProducerId(), e.getMaterialsProducerPropertyId(), e.getPreviousPropertyValue(), e.getCurrentPropertyValue()); - actualObservations.add(multiKey); - }); - } - } - })); - - for (int i = 0; i < 200; i++) { - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { - // pick a random materials producer property and update it to a - // random value - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.getRandomMaterialsProducerId(randomGenerator); - TestMaterialsProducerPropertyId testMaterialsProducerPropertyId = TestMaterialsProducerPropertyId.getRandomMutableMaterialsProducerPropertyId(randomGenerator); - Object oldValue = materialsDataManager.getMaterialsProducerPropertyValue(materialsProducerId, testMaterialsProducerPropertyId); - Object newValue = testMaterialsProducerPropertyId.getRandomPropertyValue(randomGenerator); - materialsDataManager.setMaterialsProducerPropertyValue(materialsProducerId, testMaterialsProducerPropertyId, newValue); - - // show that the new value is present - assertEquals(newValue, materialsDataManager.getMaterialsProducerPropertyValue(materialsProducerId, testMaterialsProducerPropertyId)); - - // record the expected observation - MultiKey multiKey = new MultiKey(c.getTime(), materialsProducerId, testMaterialsProducerPropertyId, oldValue, newValue); - expectedObservations.add(multiKey); - })); - } - - // have the observer show that the proper observations were generated - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { - assertTrue(expectedObservations.size() > 0); - assertEquals(expectedObservations, actualObservations); - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - MaterialsActionSupport.testConsumers(3888305479377600267L, testPlugin); - - /* precondition test: if the materials producer id is null */ - MaterialsActionSupport.testConsumer(6542684073527908815L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - MaterialsProducerPropertyId materialsProducerPropertyId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK; - Object propertyValue = 5; - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.setMaterialsProducerPropertyValue(null, materialsProducerPropertyId, propertyValue)); - assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - }); - - /* precondition test: if the materials producer id is unknown */ - MaterialsActionSupport.testConsumer(7681762910631637513L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - MaterialsProducerPropertyId materialsProducerPropertyId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK; - Object propertyValue = 5; - ContractException contractException = assertThrows(ContractException.class, - () -> materialsDataManager.setMaterialsProducerPropertyValue(TestMaterialsProducerId.getUnknownMaterialsProducerId(), materialsProducerPropertyId, propertyValue)); - assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - }); - - /* precondition test: if the materials producer property id is null */ - MaterialsActionSupport.testConsumer(5021355716878157868L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; - Object propertyValue = 5; - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.setMaterialsProducerPropertyValue(materialsProducerId, null, propertyValue)); - assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_PROPERTY_ID, contractException.getErrorType()); - }); - - /* - * precondition test: if the materials producer property id is unknown - */ - MaterialsActionSupport.testConsumer(6739449188760503613L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; - Object propertyValue = 5; - ContractException contractException = assertThrows(ContractException.class, - () -> materialsDataManager.setMaterialsProducerPropertyValue(materialsProducerId, TestMaterialsProducerPropertyId.getUnknownMaterialsProducerPropertyId(), propertyValue)); - assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_PROPERTY_ID, contractException.getErrorType()); - }); - - /* precondition test: if the materials producer property is immutable */ - MaterialsActionSupport.testConsumer(3015359065220869477L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; - Object propertyValue = 5; - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.setMaterialsProducerPropertyValue(materialsProducerId, - TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_8_INTEGER_IMMUTABLE_NO_TRACK, propertyValue)); - assertEquals(PropertyError.IMMUTABLE_VALUE, contractException.getErrorType()); - }); - - /* precondition test: if the property value is null */ - MaterialsActionSupport.testConsumer(2614476067199172944L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; - MaterialsProducerPropertyId materialsProducerPropertyId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK; - ContractException contractException = assertThrows(ContractException.class, - () -> materialsDataManager.setMaterialsProducerPropertyValue(materialsProducerId, materialsProducerPropertyId, null)); - assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_PROPERTY_VALUE, contractException.getErrorType()); - }); - - /* - * precondition test: if the property value is incompatible with the - * corresponding property definition - */ - MaterialsActionSupport.testConsumer(5704066697861534404L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; - MaterialsProducerPropertyId materialsProducerPropertyId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK; - ContractException contractException = assertThrows(ContractException.class, - () -> materialsDataManager.setMaterialsProducerPropertyValue(materialsProducerId, materialsProducerPropertyId, 12.5)); - assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "setStageOfferState", args = { StageId.class, boolean.class }) - public void testStageOfferEvent() { - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - double actionTime = 0; - - // create containers to hold observations - Set expectedObservations = new LinkedHashSet<>(); - Set actualObservations = new LinkedHashSet<>(); - - // have a agent observe stage creations - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { - c.subscribe(StageOfferUpdateEvent.class, (c2, e) -> { - actualObservations.add(new MultiKey(c2.getTime(), e.getStageId(), e.isPreviousOfferState(), e.isCurrentOfferState())); - }); - })); - - // have the producers create a few stages - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - for (int i = 0; i < 5; i++) { - materialsDataManager.addStage(testMaterialsProducerId); - } - })); - } - - // have the producers make a few random offer state changes - for (int i = 0; i < 10; i++) { - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - List stages = materialsDataManager.getStages(testMaterialsProducerId); - StageId stageId = stages.get(randomGenerator.nextInt(stages.size())); - boolean isOffered = materialsDataManager.isStageOffered(stageId); - materialsDataManager.setStageOfferState(stageId, !isOffered); - - // show that the offer state changed - boolean newOfferState = materialsDataManager.isStageOffered(stageId); - assertNotEquals(isOffered, newOfferState); - - // generate the expected observation - expectedObservations.add(new MultiKey(c.getTime(), stageId, isOffered, !isOffered)); - })); - } - } - - // have the observer show that the correct observations were generated - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { - assertTrue(expectedObservations.size() > 0); - assertEquals(expectedObservations, actualObservations); - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - MaterialsActionSupport.testConsumers(916177724112971509L, testPlugin); - - /* precondition test: if the stage id is null */ - MaterialsActionSupport.testConsumer(5384463547664747393L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.setStageOfferState(null, true)); - assertEquals(MaterialsError.NULL_STAGE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the stage id is unknown */ - MaterialsActionSupport.testConsumer(319106508275202491L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.setStageOfferState(new StageId(10000000), true)); - assertEquals(MaterialsError.UNKNOWN_STAGE_ID, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "transferMaterialBetweenBatches", args = { BatchId.class, BatchId.class, double.class }) - public void testBatchContentShiftEvent() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - double actionTime = 0; - - // create data structures to - - Set expectedObservations = new LinkedHashSet<>(); - Set actualObservations = new LinkedHashSet<>(); - - // have an agent observe the movement of materials between batches - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { - c.subscribe(BatchAmountUpdateEvent.class, (c2, e) -> { - MultiKey multiKey = new MultiKey(e.getBatchId(), e.getPreviousAmount(), e.getCurrentAmount()); - actualObservations.add(multiKey); - }); - })); - - // Have the materials producers create a few batches, ensuring the - // amount in each batch is positive - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - for (TestMaterialId testMaterialId : TestMaterialId.values()) { - for (int i = 0; i < 10; i++) { - materialsDataManager.addBatch(testMaterialsProducerId, testMaterialId, randomGenerator.nextDouble() + 0.1); - } - } - })); - } - - /* - * We will concentrate on just producer 2 for most of the test but will - * utilize batches in other producers in some tests - */ - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; - - // have the materials producer swap material amount around - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - for (TestMaterialId testMaterialId : TestMaterialId.values()) { - List batches = materialsDataManager.getInventoryBatchesByMaterialId(materialsProducerId, testMaterialId); - int index1 = randomGenerator.nextInt(batches.size()); - int index2 = randomGenerator.nextInt(batches.size()); - if (index2 == index1) { - index2++; - index2 = index2 % batches.size(); - } - BatchId batchId1 = batches.get(index1); - BatchId batchId2 = batches.get(index2); - double batchAmount1 = materialsDataManager.getBatchAmount(batchId1); - double batchAmount2 = materialsDataManager.getBatchAmount(batchId2); - // ensure that we transfer a positive amount, but not the whole - // amount - double portion = randomGenerator.nextDouble() * 0.8 + 0.1; - double transferAmount = batchAmount1 * portion; - materialsDataManager.transferMaterialBetweenBatches(batchId1, batchId2, transferAmount); - - double batchAmount3 = materialsDataManager.getBatchAmount(batchId1); - double batchAmount4 = materialsDataManager.getBatchAmount(batchId2); - - assertEquals(batchAmount1 - transferAmount, batchAmount3, 0.00000000001); - assertEquals(batchAmount2 + transferAmount, batchAmount4, 0.00000000001); - - MultiKey multiKey = new MultiKey(batchId1, batchAmount1, batchAmount3); - expectedObservations.add(multiKey); - - multiKey = new MultiKey(batchId2, batchAmount2, batchAmount4); - expectedObservations.add(multiKey); - } - })); - - // Have the observer show that the observations were generated correctly - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { - assertEquals(expectedObservations, actualObservations); - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - MaterialsActionSupport.testConsumers(6185579658134885353L, testPlugin); - - /* precondition test: if the source batch id is null */ - MaterialsActionSupport.testConsumer(4531031694400929670L, (c) -> { - ContractException contractException = assertThrows(ContractException.class, () -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - BatchId destinationBatchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 1.0); - materialsDataManager.transferMaterialBetweenBatches(null, destinationBatchId, 0.1); - }); - assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); - }); - - /* precondition test: if the source batch id is unknown */ - MaterialsActionSupport.testConsumer(6565712732641056695L, (c) -> { - ContractException contractException = assertThrows(ContractException.class, () -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - BatchId sourceBatchId = new BatchId(100000); - BatchId destinationBatchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 1.0); - materialsDataManager.transferMaterialBetweenBatches(sourceBatchId, destinationBatchId, 0.1); - }); - assertEquals(MaterialsError.UNKNOWN_BATCH_ID, contractException.getErrorType()); - }); - - /* precondition test: if the destination batch id is null */ - MaterialsActionSupport.testConsumer(2331763843587032989L, (c) -> { - ContractException contractException = assertThrows(ContractException.class, () -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - BatchId sourceBatchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 1.0); - BatchId destinationBatchId = null; - materialsDataManager.transferMaterialBetweenBatches(sourceBatchId, destinationBatchId, 0.1); - }); - assertEquals(MaterialsError.NULL_BATCH_ID, contractException.getErrorType()); - }); - - /* precondition test: if the destination batch id is unknown */ - MaterialsActionSupport.testConsumer(6191035775701595700L, (c) -> { - ContractException contractException = assertThrows(ContractException.class, () -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - BatchId sourceBatchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 1.0); - BatchId destinationBatchId = new BatchId(10000000); - materialsDataManager.transferMaterialBetweenBatches(sourceBatchId, destinationBatchId, 0.1); - }); - assertEquals(MaterialsError.UNKNOWN_BATCH_ID, contractException.getErrorType()); - }); - - /* - * precondition test: if the source and destination batches are the same - */ - MaterialsActionSupport.testConsumer(3554772523918373156L, (c) -> { - ContractException contractException = assertThrows(ContractException.class, () -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - BatchId sourceBatchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 1.0); - BatchId destinationBatchId = sourceBatchId; - materialsDataManager.transferMaterialBetweenBatches(sourceBatchId, destinationBatchId, 0.1); - }); - assertEquals(MaterialsError.REFLEXIVE_BATCH_SHIFT, contractException.getErrorType()); - }); - - /* - * precondition test: if the batches do not have the same material type - */ - MaterialsActionSupport.testConsumer(7162782209932339508L, (c) -> { - ContractException contractException = assertThrows(ContractException.class, () -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - BatchId sourceBatchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 1.0); - BatchId destinationBatchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_2, 1.0); - materialsDataManager.transferMaterialBetweenBatches(sourceBatchId, destinationBatchId, 0.1); - }); - assertEquals(MaterialsError.MATERIAL_TYPE_MISMATCH, contractException.getErrorType()); - }); - - /* - * precondition test: if the batches have different owning materials - * producers - */ - MaterialsActionSupport.testConsumer(5391338933894482101L, (c) -> { - ContractException contractException = assertThrows(ContractException.class, () -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - BatchId sourceBatchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 1.0); - BatchId destinationBatchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_2, TestMaterialId.MATERIAL_1, 1.0); - materialsDataManager.transferMaterialBetweenBatches(sourceBatchId, destinationBatchId, 0.1); - }); - assertEquals(MaterialsError.BATCH_SHIFT_WITH_MULTIPLE_OWNERS, contractException.getErrorType()); - }); - - /* precondition test: if the source batch is on a stage is offered */ - MaterialsActionSupport.testConsumer(3272165493215915111L, (c) -> { - ContractException contractException = assertThrows(ContractException.class, () -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - BatchId sourceBatchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 1.0); - StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - materialsDataManager.moveBatchToStage(sourceBatchId, stageId); - materialsDataManager.setStageOfferState(stageId, true); - BatchId destinationBatchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 1.0); - materialsDataManager.transferMaterialBetweenBatches(sourceBatchId, destinationBatchId, 0.1); - }); - assertEquals(MaterialsError.OFFERED_STAGE_UNALTERABLE, contractException.getErrorType()); - }); - - /* - * precondition test: if the destination batch is on a stage is offered - */ - MaterialsActionSupport.testConsumer(472094558069917703L, (c) -> { - ContractException contractException = assertThrows(ContractException.class, () -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - BatchId sourceBatchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 1.0); - BatchId destinationBatchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 1.0); - StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - materialsDataManager.moveBatchToStage(destinationBatchId, stageId); - materialsDataManager.setStageOfferState(stageId, true); - materialsDataManager.transferMaterialBetweenBatches(sourceBatchId, destinationBatchId, 0.1); - }); - assertEquals(MaterialsError.OFFERED_STAGE_UNALTERABLE, contractException.getErrorType()); - }); - - /* precondition test: if the shift amount is not a finite number */ - MaterialsActionSupport.testConsumer(2032850391793850929L, (c) -> { - ContractException contractException = assertThrows(ContractException.class, () -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - BatchId sourceBatchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 1.0); - BatchId destinationBatchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 1.0); - materialsDataManager.transferMaterialBetweenBatches(sourceBatchId, destinationBatchId, Double.POSITIVE_INFINITY); - }); - assertEquals(MaterialsError.NON_FINITE_MATERIAL_AMOUNT, contractException.getErrorType()); - }); - - /* precondition test: if the shift amount is negative */ - MaterialsActionSupport.testConsumer(5245408361837825062L, (c) -> { - ContractException contractException = assertThrows(ContractException.class, () -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - BatchId sourceBatchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 1.0); - BatchId destinationBatchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 1.0); - materialsDataManager.transferMaterialBetweenBatches(sourceBatchId, destinationBatchId, -0.5); - }); - assertEquals(MaterialsError.NEGATIVE_MATERIAL_AMOUNT, contractException.getErrorType()); - }); - - /* - * precondition test: if the shift amount exceeds the available material - * on the source batch - */ - MaterialsActionSupport.testConsumer(8724998204446972180L, (c) -> { - ContractException contractException = assertThrows(ContractException.class, () -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - BatchId sourceBatchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 1.0); - BatchId destinationBatchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, 1.0); - materialsDataManager.transferMaterialBetweenBatches(sourceBatchId, destinationBatchId, 2.0); - }); - assertEquals(MaterialsError.INSUFFICIENT_MATERIAL_AVAILABLE, contractException.getErrorType()); - }); - - /* - * precondition test: if the shift amount would cause an overflow on the - * destination batch - */ - MaterialsActionSupport.testConsumer(3007064903100070620L, (c) -> { - ContractException contractException = assertThrows(ContractException.class, () -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - BatchId sourceBatchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, Double.MAX_VALUE); - BatchId destinationBatchId = materialsDataManager.addBatch(TestMaterialsProducerId.MATERIALS_PRODUCER_1, TestMaterialId.MATERIAL_1, Double.MAX_VALUE); - double amount = Double.MAX_VALUE / 2; - materialsDataManager.transferMaterialBetweenBatches(sourceBatchId, destinationBatchId, amount); - }); - assertEquals(MaterialsError.MATERIAL_ARITHMETIC_EXCEPTION, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "transferOfferedStage", args = { StageId.class, MaterialsProducerId.class }) - public void testTransferOfferedStage() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - double actionTime = 0; - - // create containers to hold observations - Set expectedObservations = new LinkedHashSet<>(); - Set actualObservations = new LinkedHashSet<>(); - - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { - c.subscribe(StageMaterialsProducerUpdateEvent.class, (c2, e) -> { - MultiKey multiKey = new MultiKey(c.getTime(), e.getStageId(), e.getPreviousMaterialsProducerId(), e.getCurrentMaterialsProducerId()); - actualObservations.add(multiKey); - }); - - c.subscribe(StageOfferUpdateEvent.class, (c2, e) -> { - MultiKey multiKey = new MultiKey(c.getTime(), e.getStageId()); - actualObservations.add(multiKey); - - }); - })); - - int stagesPerProducer = 5; - int transferCount = stagesPerProducer * TestMaterialsProducerId.values().length; - - // have the materials producers create a few offered stages - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - for (int i = 0; i < stagesPerProducer; i++) { - StageId stageId = materialsDataManager.addStage(testMaterialsProducerId); - for (int j = 0; j < 3; j++) { - BatchId batchId = materialsDataManager.addBatch(testMaterialsProducerId, TestMaterialId.getRandomMaterialId(randomGenerator), randomGenerator.nextDouble() + 0.01); - materialsDataManager.moveBatchToStage(batchId, stageId); - } - materialsDataManager.setStageOfferState(stageId, true); - MultiKey multiKey = new MultiKey(c.getTime(), stageId); - expectedObservations.add(multiKey); - } - })); - } - - // have an agent transfer offered stages - for (int i = 0; i < transferCount; i++) { - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - // determine the stages to transfer - - List stagesToTransfer = new ArrayList<>(); - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - List offeredStages = materialsDataManager.getOfferedStages(testMaterialsProducerId); - stagesToTransfer.addAll(offeredStages); - } - StageId stageId = stagesToTransfer.get(randomGenerator.nextInt(stagesToTransfer.size())); - - // select a producer at random to receive the transfered - // stage - - MaterialsProducerId stageProducer = materialsDataManager.getStageProducer(stageId); - List candidateProducers = new ArrayList<>(materialsDataManager.getMaterialsProducerIds()); - candidateProducers.remove(stageProducer); - MaterialsProducerId altProducer = candidateProducers.get(randomGenerator.nextInt(candidateProducers.size())); - - // transfer the stage - materialsDataManager.transferOfferedStage(stageId, altProducer); - - // show that the stage was properly transferred - assertEquals(altProducer, materialsDataManager.getStageProducer(stageId)); - - // record expected observations - - MultiKey multiKey = new MultiKey(c.getTime(), stageId, stageProducer, altProducer); - expectedObservations.add(multiKey); - - multiKey = new MultiKey(c.getTime(), stageId); - expectedObservations.add(multiKey); - - })); - } - - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { - assertTrue(expectedObservations.size() > 0); - assertEquals(expectedObservations, actualObservations); - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - MaterialsActionSupport.testConsumers(3739485201643969207L, testPlugin); - - /* precondition test: if the stage id is null */ - MaterialsActionSupport.testConsumer(1130010427224075392L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - MaterialsProducerId altProducer = TestMaterialsProducerId.MATERIALS_PRODUCER_2; - materialsDataManager.setStageOfferState(stageId, true); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.transferOfferedStage(null, altProducer)); - assertEquals(MaterialsError.NULL_STAGE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the stage id is unknown */ - MaterialsActionSupport.testConsumer(3682112595474000731L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - MaterialsProducerId altProducer = TestMaterialsProducerId.MATERIALS_PRODUCER_2; - materialsDataManager.setStageOfferState(stageId, true); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.transferOfferedStage(new StageId(10000000), altProducer)); - assertEquals(MaterialsError.UNKNOWN_STAGE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the materials producer id is null */ - MaterialsActionSupport.testConsumer(2900230239814256887L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - materialsDataManager.setStageOfferState(stageId, true); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.transferOfferedStage(stageId, null)); - assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - }); - - /* precondition test: if the materials producer id is unknown */ - MaterialsActionSupport.testConsumer(452781590728467653L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - materialsDataManager.setStageOfferState(stageId, true); - ContractException contractException = assertThrows(ContractException.class, - () -> materialsDataManager.transferOfferedStage(stageId, TestMaterialsProducerId.getUnknownMaterialsProducerId())); - assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - }); - - /* precondition test: if the stage is not offered */ - MaterialsActionSupport.testConsumer(6778669475043282422L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - MaterialsProducerId altProducer = TestMaterialsProducerId.MATERIALS_PRODUCER_2; - materialsDataManager.setStageOfferState(stageId, true); - materialsDataManager.setStageOfferState(stageId, false); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.transferOfferedStage(stageId, altProducer)); - assertEquals(MaterialsError.UNOFFERED_STAGE_NOT_TRANSFERABLE, contractException.getErrorType()); - materialsDataManager.setStageOfferState(stageId, true); - }); - - /* - * precondition test: if the source and destination materials producers - * are the same - */ - MaterialsActionSupport.testConsumer(4646205108657064829L, (c) -> { - - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - materialsDataManager.setStageOfferState(stageId, true); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.transferOfferedStage(stageId, TestMaterialsProducerId.MATERIALS_PRODUCER_1)); - assertEquals(MaterialsError.REFLEXIVE_STAGE_TRANSFER, contractException.getErrorType()); - - }); - } - - @Test - @UnitTestMethod(name = "transferResourceToRegion", args = { MaterialsProducerId.class, ResourceId.class, RegionId.class, long.class }) - public void testTransferResourceToRegion() { - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - double actionTime = 0; - - // create containers to hold observations - Set expectedObservations = new LinkedHashSet<>(); - Set actualObservations = new LinkedHashSet<>(); - - // have a agent observe resource transfers from producers to regions. - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { - /* - * When transferring resources from a producer to a region, a - * RegionResourceAdditionEvent is generated. This is not an - * observation, but is the contracted reaction event that is then - * processed by the resources package. To assess that this event is - * indeed generated we could add a custom resolver, but choose - * instead to have the observer agent subscribe to the resulting - * RegionResourceUpdateEvent - */ - - for (TestRegionId testRegionId : TestRegionId.values()) { - for (TestResourceId testResourceId : TestResourceId.values()) { - c.subscribe(RegionResourceUpdateEvent.getEventLabelByRegionAndResource(c, testRegionId, testResourceId), (c2, e) -> { - MultiKey multiKey = new MultiKey(c.getTime(), e.getResourceId(), e.getRegionId(), e.getPreviousResourceLevel(), e.getCurrentResourceLevel()); - actualObservations.add(multiKey); - }); - } - } - - for (TestResourceId testResourceId : TestResourceId.values()) { - c.subscribe(MaterialsProducerResourceUpdateEvent.getEventLabelByResource(c, testResourceId), (c2, e) -> { - MultiKey multiKey = new MultiKey(c.getTime(), e.getResourceId(), e.getMaterialsProducerId(), e.getPreviousResourceLevel(), e.getCurrentResourceLevel()); - actualObservations.add(multiKey); - }); - } - - })); - - // have the producers generate some resources - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - for (TestResourceId testResourceId : TestResourceId.values()) { - if (randomGenerator.nextBoolean()) { - StageId stageId = materialsDataManager.addStage(testMaterialsProducerId); - long amount = (randomGenerator.nextInt(1000) + 100); - materialsDataManager.convertStageToResource(stageId, testResourceId, amount); - MultiKey multiKey = new MultiKey(c.getTime(), testResourceId, testMaterialsProducerId, 0L, amount); - expectedObservations.add(multiKey); - } - } - - })); - } - - // have an agent distribute the resources over time - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - for (TestResourceId testResourceId : TestResourceId.values()) { - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(actionTime++, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - long materialsProducerResourceLevel = materialsDataManager.getMaterialsProducerResourceLevel(testMaterialsProducerId, testResourceId); - if (materialsProducerResourceLevel > 0) { - long amountToTransfer = randomGenerator.nextInt((int) materialsProducerResourceLevel) + 1; - TestRegionId regionId = TestRegionId.getRandomRegionId(randomGenerator); - long regionResourceLevel = resourcesDataManager.getRegionResourceLevel(regionId, testResourceId); - materialsDataManager.transferResourceToRegion(testMaterialsProducerId, testResourceId, regionId, amountToTransfer); - - // show that the resource was transfered - long currentProducerResourceLevel = materialsDataManager.getMaterialsProducerResourceLevel(testMaterialsProducerId, testResourceId); - long currentRegionResourceLevel = resourcesDataManager.getRegionResourceLevel(regionId, testResourceId); - - assertEquals(materialsProducerResourceLevel - amountToTransfer, currentProducerResourceLevel); - assertEquals(regionResourceLevel + amountToTransfer, currentRegionResourceLevel); - - // record the expected observations - - MultiKey multiKey = new MultiKey(c.getTime(), testResourceId, regionId, regionResourceLevel, currentRegionResourceLevel); - expectedObservations.add(multiKey); - multiKey = new MultiKey(c.getTime(), testResourceId, testMaterialsProducerId, materialsProducerResourceLevel, currentProducerResourceLevel); - expectedObservations.add(multiKey); - - } - })); - } - } - - // have the observer show that the observations are correct - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(actionTime++, (c) -> { - assertTrue(expectedObservations.size() > 0); - assertEquals(expectedObservations, actualObservations); - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - MaterialsActionSupport.testConsumers(2289322490697828226L, testPlugin); - - /* precondition test: if the resource id is null */ - MaterialsActionSupport.testConsumer(1367796071113751106L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; - ResourceId resourceId = TestResourceId.RESOURCE_3; - RegionId regionId = TestRegionId.REGION_4; - long amountToTransfer = 45L; - StageId stageId = materialsDataManager.addStage(materialsProducerId); - materialsDataManager.convertStageToResource(stageId, resourceId, amountToTransfer); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.transferResourceToRegion(materialsProducerId, null, regionId, amountToTransfer)); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the resource id is unknown */ - MaterialsActionSupport.testConsumer(8014590590926533288L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; - ResourceId resourceId = TestResourceId.RESOURCE_3; - RegionId regionId = TestRegionId.REGION_4; - long amountToTransfer = 45L; - StageId stageId = materialsDataManager.addStage(materialsProducerId); - materialsDataManager.convertStageToResource(stageId, resourceId, amountToTransfer); - ContractException contractException = assertThrows(ContractException.class, - () -> materialsDataManager.transferResourceToRegion(materialsProducerId, TestResourceId.getUnknownResourceId(), regionId, amountToTransfer)); - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the region id is null */ - MaterialsActionSupport.testConsumer(4865873025074936636L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; - ResourceId resourceId = TestResourceId.RESOURCE_3; - long amountToTransfer = 45L; - StageId stageId = materialsDataManager.addStage(materialsProducerId); - materialsDataManager.convertStageToResource(stageId, resourceId, amountToTransfer); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.transferResourceToRegion(materialsProducerId, resourceId, null, amountToTransfer)); - assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); - }); - - /* precondition test: if the region id is unknown */ - MaterialsActionSupport.testConsumer(4472671173642659805L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; - ResourceId resourceId = TestResourceId.RESOURCE_3; - long amountToTransfer = 45L; - StageId stageId = materialsDataManager.addStage(materialsProducerId); - materialsDataManager.convertStageToResource(stageId, resourceId, amountToTransfer); - ContractException contractException = assertThrows(ContractException.class, - () -> materialsDataManager.transferResourceToRegion(materialsProducerId, resourceId, TestRegionId.getUnknownRegionId(), amountToTransfer)); - assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); - }); - - /* precondition test: if the materials producer id is null */ - MaterialsActionSupport.testConsumer(6956131170154399460L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; - ResourceId resourceId = TestResourceId.RESOURCE_3; - RegionId regionId = TestRegionId.REGION_4; - long amountToTransfer = 45L; - StageId stageId = materialsDataManager.addStage(materialsProducerId); - materialsDataManager.convertStageToResource(stageId, resourceId, amountToTransfer); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.transferResourceToRegion(null, resourceId, regionId, amountToTransfer)); - assertEquals(MaterialsError.NULL_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - }); - - /* precondition test: if the materials producer id is unknown */ - MaterialsActionSupport.testConsumer(1760306489660703762L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; - ResourceId resourceId = TestResourceId.RESOURCE_3; - RegionId regionId = TestRegionId.REGION_4; - long amountToTransfer = 45L; - StageId stageId = materialsDataManager.addStage(materialsProducerId); - materialsDataManager.convertStageToResource(stageId, resourceId, amountToTransfer); - ContractException contractException = assertThrows(ContractException.class, - () -> materialsDataManager.transferResourceToRegion(TestMaterialsProducerId.getUnknownMaterialsProducerId(), resourceId, regionId, amountToTransfer)); - assertEquals(MaterialsError.UNKNOWN_MATERIALS_PRODUCER_ID, contractException.getErrorType()); - }); - - /* precondition test: if the materials amount is negative */ - MaterialsActionSupport.testConsumer(2214714534579989103L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; - ResourceId resourceId = TestResourceId.RESOURCE_3; - RegionId regionId = TestRegionId.REGION_4; - long amountToTransfer = 45L; - StageId stageId = materialsDataManager.addStage(materialsProducerId); - materialsDataManager.convertStageToResource(stageId, resourceId, amountToTransfer); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.transferResourceToRegion(materialsProducerId, resourceId, regionId, -1L)); - assertEquals(ResourceError.NEGATIVE_RESOURCE_AMOUNT, contractException.getErrorType()); - }); - - /* - * precondition test: if the materials amount exceeds the resource level - * of the materials producer - */ - MaterialsActionSupport.testConsumer(8260344965557977221L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; - ResourceId resourceId = TestResourceId.RESOURCE_3; - RegionId regionId = TestRegionId.REGION_4; - long amountToTransfer = 45L; - StageId stageId = materialsDataManager.addStage(materialsProducerId); - materialsDataManager.convertStageToResource(stageId, resourceId, amountToTransfer); - ContractException contractException = assertThrows(ContractException.class, - () -> materialsDataManager.transferResourceToRegion(materialsProducerId, resourceId, regionId, amountToTransfer * 2)); - assertEquals(ResourceError.INSUFFICIENT_RESOURCES_AVAILABLE, contractException.getErrorType()); - }); - - /* - * precondition test: if the materials amount would cause an overflow of - * the regions resource level - */ - MaterialsActionSupport.testConsumer(4416313459810970424L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; - ResourceId resourceId = TestResourceId.RESOURCE_3; - RegionId regionId = TestRegionId.REGION_4; - long amountToTransfer = 45L; - StageId stageId = materialsDataManager.addStage(materialsProducerId); - materialsDataManager.convertStageToResource(stageId, resourceId, amountToTransfer); - materialsDataManager.transferResourceToRegion(materialsProducerId, resourceId, regionId, amountToTransfer); - - /* - * There is currently some of the resource present, so we will add - * half of the max value of long two times in a row. That will cause - * the region to overflow while keeping the producer from doing so - * - */ - long hugeAmount = Long.MAX_VALUE / 2; - stageId = materialsDataManager.addStage(materialsProducerId); - materialsDataManager.convertStageToResource(stageId, resourceId, hugeAmount); - materialsDataManager.transferResourceToRegion(materialsProducerId, resourceId, regionId, hugeAmount); - - stageId = materialsDataManager.addStage(materialsProducerId); - materialsDataManager.convertStageToResource(stageId, resourceId, hugeAmount); - ContractException contractException = assertThrows(ContractException.class, () -> materialsDataManager.transferResourceToRegion(materialsProducerId, resourceId, regionId, hugeAmount)); - assertEquals(ResourceError.RESOURCE_ARITHMETIC_EXCEPTION, contractException.getErrorType()); - - }); - - } - - @Test - @UnitTestMethod(name = "init", args = { DataManagerContext.class }) - public void testStageOfferUpdateEventLabelers() { - MaterialsActionSupport.testConsumer(645534075810555962L, (c) -> { - EventLabeler eventLabeler1 = StageOfferUpdateEvent.getEventLabelerForStage(); - assertNotNull(eventLabeler1); - ContractException contractException = assertThrows(ContractException.class, () -> c.addEventLabeler(eventLabeler1)); - assertEquals(NucleusError.DUPLICATE_LABELER_ID_IN_EVENT_LABELER, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "init", args = { DataManagerContext.class }) - public void testMaterialsProducerPropertyUpdateEventLabelers() { - /* - * Have the agent attempt to add the event labeler and show that a - * contract exception is thrown, indicating that the labeler was - * previously added by the resolver. - */ - MaterialsActionSupport.testConsumer(301724267100798742L, (c) -> { - EventLabeler eventLabeler = MaterialsProducerPropertyUpdateEvent.getEventLabelerForMaterialsProducerAndProperty(); - assertNotNull(eventLabeler); - ContractException contractException = assertThrows(ContractException.class, () -> c.addEventLabeler(eventLabeler)); - assertEquals(NucleusError.DUPLICATE_LABELER_ID_IN_EVENT_LABELER, contractException.getErrorType()); - - }); - } - - @Test - @UnitTestMethod(name = "init", args = { DataManagerContext.class }) - public void testMaterialsProducerResourceUpdateEventLabelers() { - - /* - * Have the agent attempt to add the event labeler and show that a - * contract exception is thrown, indicating that the labeler was - * previously added by the resolver. - */ - MaterialsActionSupport.testConsumer(13119761810425715L, (c) -> { - EventLabeler eventLabeler1 = MaterialsProducerResourceUpdateEvent.getEventLabelerForMaterialsProducerAndResource(); - assertNotNull(eventLabeler1); - ContractException contractException = assertThrows(ContractException.class, () -> c.addEventLabeler(eventLabeler1)); - assertEquals(NucleusError.DUPLICATE_LABELER_ID_IN_EVENT_LABELER, contractException.getErrorType()); - - EventLabeler eventLabeler2 = MaterialsProducerResourceUpdateEvent.getEventLabelerForResource(); - assertNotNull(eventLabeler2); - contractException = assertThrows(ContractException.class, () -> c.addEventLabeler(eventLabeler2)); - assertEquals(NucleusError.DUPLICATE_LABELER_ID_IN_EVENT_LABELER, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "init", args = { DataManagerContext.class }) - public void testStageMaterialsProducerUpdateEventLabelers() { - - MaterialsActionSupport.testConsumer(6871307284439549691L, (c) -> { - - EventLabeler eventLabeler1 = StageMaterialsProducerUpdateEvent.getEventLabelerForDestination(); - assertNotNull(eventLabeler1); - ContractException contractException = assertThrows(ContractException.class, () -> c.addEventLabeler(eventLabeler1)); - assertEquals(NucleusError.DUPLICATE_LABELER_ID_IN_EVENT_LABELER, contractException.getErrorType()); - - EventLabeler eventLabeler2 = StageMaterialsProducerUpdateEvent.getEventLabelerForSource(); - assertNotNull(eventLabeler2); - contractException = assertThrows(ContractException.class, () -> c.addEventLabeler(eventLabeler2)); - assertEquals(NucleusError.DUPLICATE_LABELER_ID_IN_EVENT_LABELER, contractException.getErrorType()); - - EventLabeler eventLabeler3 = StageMaterialsProducerUpdateEvent.getEventLabelerForStage(); - assertNotNull(eventLabeler3); - contractException = assertThrows(ContractException.class, () -> c.addEventLabeler(eventLabeler3)); - assertEquals(NucleusError.DUPLICATE_LABELER_ID_IN_EVENT_LABELER, contractException.getErrorType()); - - }); - } - - @Test - @UnitTestMethod(name = "init", args = { DataManagerContext.class }) - public void testMaterialsDataManagerInitialState() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5272599676969321594L); - - Builder builder = Simulation.builder(); - - /* - * Add the materials plugin, utilizing all materials initial data - * methods to provide sufficient variety to test the proper loading of - * initial data. Note that stage and batch ids do not start with zero. - * This will force the renumbering of all batches and stages and - * complicate the testing a bit, but will show that the resolver is - * correctly renumbering the ids. - */ - MaterialsPluginData.Builder materialsBuilder = MaterialsPluginData.builder(); - - int bId = 0; - int sId = 0; - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - List batches = new ArrayList<>(); - - for (int i = 0; i < 50; i++) { - - TestMaterialId testMaterialId = TestMaterialId.getRandomMaterialId(randomGenerator); - double amount = randomGenerator.nextDouble(); - BatchId batchId = new BatchId(bId++); - materialsBuilder.addBatch(batchId, testMaterialId, amount, testMaterialsProducerId); - batches.add(batchId); - for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.getTestBatchPropertyIds(testMaterialId)) { - if (randomGenerator.nextBoolean()) { - materialsBuilder.setBatchPropertyValue(batchId, testBatchPropertyId, testBatchPropertyId.getRandomPropertyValue(randomGenerator)); - } - } - - } - - List stages = new ArrayList<>(); - - for (int i = 0; i < 10; i++) { - StageId stageId = new StageId(sId++); - stages.add(stageId); - boolean offered = i % 2 == 0; - materialsBuilder.addStage(stageId, offered, testMaterialsProducerId); - } - - Collections.shuffle(batches, new Random(randomGenerator.nextLong())); - for (int i = 0; i < 30; i++) { - BatchId batchId = batches.get(i); - StageId stageId = stages.get(randomGenerator.nextInt(stages.size())); - materialsBuilder.addBatchToStage(stageId, batchId); - } - } - - for (TestMaterialId testMaterialId : TestMaterialId.values()) { - materialsBuilder.addMaterial(testMaterialId); - } - - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - materialsBuilder.addMaterialsProducerId(testMaterialsProducerId); - for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId.values()) { - if (randomGenerator.nextBoolean()) { - materialsBuilder.setMaterialsProducerPropertyValue(testMaterialsProducerId, testMaterialsProducerPropertyId, - testMaterialsProducerPropertyId.getRandomPropertyValue(randomGenerator)); - } - } - for (TestResourceId testResourceId : TestResourceId.values()) { - if (randomGenerator.nextBoolean()) { - long value = randomGenerator.nextInt(15) + 1; - materialsBuilder.setMaterialsProducerResourceLevel(testMaterialsProducerId, testResourceId, value); - } - } - } - - for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId.values()) { - materialsBuilder.defineMaterialsProducerProperty(testMaterialsProducerPropertyId, testMaterialsProducerPropertyId.getPropertyDefinition()); - } - - for (TestMaterialId testMaterialId : TestMaterialId.values()) { - Set testBatchPropertyIds = TestBatchPropertyId.getTestBatchPropertyIds(testMaterialId); - for (TestBatchPropertyId testBatchPropertyId : testBatchPropertyIds) { - materialsBuilder.defineBatchProperty(testMaterialId, testBatchPropertyId, testBatchPropertyId.getPropertyDefinition()); - } - } - MaterialsPluginData materialsPluginData = materialsBuilder.build(); - Plugin materialsPlugin = MaterialsPlugin.getMaterialsPlugin(materialsPluginData); - - builder.addPlugin(materialsPlugin); - - // add the resources plugin - ResourcesPluginData.Builder resourcesBuilder = ResourcesPluginData.builder(); - - for (TestResourceId testResourceId : TestResourceId.values()) { - resourcesBuilder.addResource(testResourceId); - resourcesBuilder.setResourceTimeTracking(testResourceId, testResourceId.getTimeTrackingPolicy()); - } - - for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId.values()) { - TestResourceId testResourceId = testResourcePropertyId.getTestResourceId(); - PropertyDefinition propertyDefinition = testResourcePropertyId.getPropertyDefinition(); - Object propertyValue = testResourcePropertyId.getRandomPropertyValue(randomGenerator); - resourcesBuilder.defineResourceProperty(testResourceId, testResourcePropertyId, propertyDefinition); - resourcesBuilder.setResourcePropertyValue(testResourceId, testResourcePropertyId, propertyValue); - } - - ResourcesPluginData resourcesPluginData = resourcesBuilder.build(); - Plugin resourcesPlugin = ResourcesPlugin.getResourcesPlugin(resourcesPluginData); - builder.addPlugin(resourcesPlugin); - - // add the people plugin - - PeoplePluginData.Builder peopleBuilder = PeoplePluginData.builder(); - PeoplePluginData peoplePluginData = peopleBuilder.build(); - Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(peoplePluginData); - builder.addPlugin(peoplePlugin); - - // add the regions plugin - RegionsPluginData.Builder regionsBuilder = RegionsPluginData.builder(); - for (TestRegionId testRegionId : TestRegionId.values()) { - regionsBuilder.addRegion(testRegionId); - } - RegionsPluginData regionsPluginData = regionsBuilder.build(); - Plugin regionPlugin = RegionsPlugin.getRegionsPlugin(regionsPluginData); - builder.addPlugin(regionPlugin); - - // add the stochastics plugin - StochasticsPluginData.Builder stochasticsBuilder = StochasticsPluginData.builder(); - stochasticsBuilder.setSeed(randomGenerator.nextLong()); - StochasticsPluginData stochasticsPluginData = stochasticsBuilder.build(); - Plugin stochasticsPlugin = StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData); - builder.addPlugin(stochasticsPlugin); - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // create an agent to show that materials initial data was properly - // loaded as reflected in the data view - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - - // show that the correct materials producer ids are present - assertEquals(materialsPluginData.getMaterialsProducerIds(), materialsDataManager.getMaterialsProducerIds()); - - // show that the correct material ids are present - assertEquals(materialsPluginData.getMaterialIds(), materialsDataManager.getMaterialIds()); - - // show that the resource ids used for initial resource levels are - // all contained in the resource plugin - assertTrue(resourcesDataManager.getResourceIds().containsAll(materialsPluginData.getResourceIds())); - - // show that the material property ids are correct - assertEquals(materialsPluginData.getMaterialsProducerPropertyIds(), materialsDataManager.getMaterialsProducerPropertyIds()); - - // show that the material producer property definitions are correct - for (MaterialsProducerPropertyId materialsProducerPropertyId : materialsPluginData.getMaterialsProducerPropertyIds()) { - assertEquals(materialsPluginData.getMaterialsProducerPropertyDefinition(materialsProducerPropertyId), - materialsDataManager.getMaterialsProducerPropertyDefinition(materialsProducerPropertyId)); - } - - /* - * Show that the material producer property values are correct. Show - * that the initial resource levels are correct. - */ - for (MaterialsProducerId materialsProducerId : materialsPluginData.getMaterialsProducerIds()) { - for (MaterialsProducerPropertyId materialsProducerPropertyId : materialsPluginData.getMaterialsProducerPropertyIds()) { - Object expectedValue = materialsPluginData.getMaterialsProducerPropertyValue(materialsProducerId, materialsProducerPropertyId); - Object actualValue = materialsDataManager.getMaterialsProducerPropertyValue(materialsProducerId, materialsProducerPropertyId); - assertEquals(expectedValue, actualValue); - } - for (ResourceId resourceId : resourcesDataManager.getResourceIds()) { - Long expectedResourceLevel = materialsPluginData.getMaterialsProducerResourceLevel(materialsProducerId, resourceId); - Long actualResourceLevel = materialsDataManager.getMaterialsProducerResourceLevel(materialsProducerId, resourceId); - assertEquals(expectedResourceLevel, actualResourceLevel); - } - } - /* - * Show that each material is associated with the correct batch - * property ids. Show that each batch property id has the correct - * definition. - */ - for (MaterialId materialId : materialsPluginData.getMaterialIds()) { - assertEquals(materialsPluginData.getBatchPropertyIds(materialId), materialsDataManager.getBatchPropertyIds(materialId)); - for (BatchPropertyId batchPropertyId : materialsPluginData.getBatchPropertyIds(materialId)) { - PropertyDefinition expectedPropertyDefinition = materialsPluginData.getBatchPropertyDefinition(materialId, batchPropertyId); - PropertyDefinition actualPropertyDefinition = materialsDataManager.getBatchPropertyDefinition(materialId, batchPropertyId); - assertEquals(expectedPropertyDefinition, actualPropertyDefinition); - } - } - - // Show that the correct initial batches are present. - Set expectedBatchIds = new LinkedHashSet<>(); - for (BatchId batchId : materialsPluginData.getBatchIds()) { - expectedBatchIds.add(batchId); - } - - Set actualBatchIds = new LinkedHashSet<>(); - for (MaterialsProducerId materialsProducerId : materialsDataManager.getMaterialsProducerIds()) { - actualBatchIds.addAll(materialsDataManager.getInventoryBatches(materialsProducerId)); - List stages = materialsDataManager.getStages(materialsProducerId); - for (StageId stageId : stages) { - actualBatchIds.addAll(materialsDataManager.getStageBatches(stageId)); - } - } - assertEquals(expectedBatchIds, actualBatchIds); - - // Show that the batches have the correct material id, materials - // producer and amounts - - for (BatchId batchId : materialsPluginData.getBatchIds()) { - - MaterialId expectedMaterialId = materialsPluginData.getBatchMaterial(batchId); - MaterialId actualMaterialId = materialsDataManager.getBatchMaterial(batchId); - assertEquals(expectedMaterialId, actualMaterialId); - - MaterialsProducerId expectedMaterialsProducerId = materialsPluginData.getBatchMaterialsProducer(batchId); - MaterialsProducerId actualMaterialsProducerId = materialsDataManager.getBatchProducer(batchId); - assertEquals(expectedMaterialsProducerId, actualMaterialsProducerId); - - double expectedAmount = materialsPluginData.getBatchAmount(batchId); - double actualAmount = materialsDataManager.getBatchAmount(batchId); - assertEquals(expectedAmount, actualAmount); - - for (BatchPropertyId batchPropertyId : materialsPluginData.getBatchPropertyIds(expectedMaterialId)) { - Object expectedValue = materialsPluginData.getBatchPropertyValue(batchId, batchPropertyId); - Object actualValue = materialsDataManager.getBatchPropertyValue(batchId, batchPropertyId); - assertEquals(expectedValue, actualValue); - } - - } - - // Show that the correct initial stages are present with their - // normalized id values. - Set expectedStageIds = new LinkedHashSet<>(); - for (StageId stageId : materialsPluginData.getStageIds()) { - expectedStageIds.add(stageId); - } - - Set actualStageIds = new LinkedHashSet<>(); - for (MaterialsProducerId materialsProducerId : materialsDataManager.getMaterialsProducerIds()) { - actualStageIds.addAll(materialsDataManager.getStages(materialsProducerId)); - - } - assertEquals(expectedStageIds, actualStageIds); - - for (StageId stageId : materialsPluginData.getStageIds()) { - - MaterialsProducerId expectedMaterialsProducerId = materialsPluginData.getStageMaterialsProducer(stageId); - MaterialsProducerId actualMaterialsProducerId = materialsDataManager.getStageProducer(stageId); - assertEquals(expectedMaterialsProducerId, actualMaterialsProducerId); - - boolean expectedOfferState = materialsPluginData.isStageOffered(stageId); - boolean actualOfferStage = materialsDataManager.isStageOffered(stageId); - assertEquals(expectedOfferState, actualOfferStage); - - Set expectedBatches = new LinkedHashSet<>(); - for (BatchId batchId : materialsPluginData.getStageBatches(stageId)) { - expectedBatches.add(batchId); - } - Set actualBatches = new LinkedHashSet<>(materialsDataManager.getStageBatches(stageId)); - assertEquals(expectedBatches, actualBatches); - } - - })); - - // add the test plugin - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - builder.addPlugin(testPlugin); - - // build and execute the engine - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - builder.setOutputConsumer(scenarioPlanCompletionObserver::handleOutput).build().execute(); - - // show that all actions were executed - if (!scenarioPlanCompletionObserver.allPlansExecuted()) { - throw new ContractException(TestError.TEST_EXECUTION_FAILURE); - } - - } - -} - -// 8277504917871630332L -// 4500197565082426867L -// 2399868938024618609L -// 5823063295468505460L -// 9100489142422084377L -// 1770963491884402214L -// 7476217693568936576L -// 634295252083735224L -// 2280447971214286422L -// 5934623336442806540L -// 7790672070612068341L -// 6299160091429650509L -// 4263004528407655722L -// 1197340496276035340L -// 7113124757110761182L -// 7717129621773530177L -// 8257912800058500489L -// 657312934605678252L -// 6161288826877476435L diff --git a/gcm3/src/test/java/plugins/materials/events/AT_BatchAdditionEvent.java b/gcm3/src/test/java/plugins/materials/events/AT_BatchAdditionEvent.java deleted file mode 100644 index 1c5f39537..000000000 --- a/gcm3/src/test/java/plugins/materials/events/AT_BatchAdditionEvent.java +++ /dev/null @@ -1,45 +0,0 @@ -package plugins.materials.events; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -import plugins.materials.support.BatchId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = BatchAdditionEvent.class) -public class AT_BatchAdditionEvent { - - @Test - @UnitTestConstructor(args = { BatchId.class }) - public void testConstructor() { - // nothing to test - } - - @Test - @UnitTestMethod(name = "getPrimaryKeyValue", args = {}) - public void testGetPrimaryKeyValue() { - BatchId batchId = new BatchId(56456); - BatchAdditionEvent batchAdditionEvent = new BatchAdditionEvent(batchId); - assertEquals(BatchAdditionEvent.class, batchAdditionEvent.getPrimaryKeyValue()); - } - - @Test - @UnitTestMethod(name = "getBatchId", args = {}) - public void testGetBatchId() { - BatchId batchId = new BatchId(56456); - BatchAdditionEvent batchAdditionEvent = new BatchAdditionEvent(batchId); - assertEquals(batchId, batchAdditionEvent.getBatchId()); - } - - @Test - @UnitTestMethod(name = "toString", args = {}) - public void testToString() { - BatchId batchId = new BatchId(56456); - BatchAdditionEvent batchAdditionEvent = new BatchAdditionEvent(batchId); - assertEquals("BatchAdditionEvent [batchId=56456]", batchAdditionEvent.toString()); - } - -} diff --git a/gcm3/src/test/java/plugins/materials/events/AT_BatchAmountUpdateEvent.java b/gcm3/src/test/java/plugins/materials/events/AT_BatchAmountUpdateEvent.java deleted file mode 100644 index c274aac45..000000000 --- a/gcm3/src/test/java/plugins/materials/events/AT_BatchAmountUpdateEvent.java +++ /dev/null @@ -1,72 +0,0 @@ -package plugins.materials.events; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -import plugins.materials.support.BatchId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; - - -@UnitTest(target = BatchAmountUpdateEvent.class) -public class AT_BatchAmountUpdateEvent { - - @Test - @UnitTestConstructor(args = {BatchId.class, double.class, double.class}) - public void testConstructor() { - //nothing to test - } - - @Test - @UnitTestMethod(name="getPrimaryKeyValue",args = {}) - public void testGetPrimaryKeyValue() { - BatchId batchId = new BatchId(23423); - double previousAmount = 14.5; - double currentAmount = 44.7; - BatchAmountUpdateEvent batchAmountUpdateEvent = new BatchAmountUpdateEvent(batchId, previousAmount, currentAmount); - assertEquals(BatchAmountUpdateEvent.class, batchAmountUpdateEvent.getPrimaryKeyValue()); - } - - @Test - @UnitTestMethod(name="getBatchId",args = {}) - public void testGetBatchId() { - BatchId batchId = new BatchId(23423); - double previousAmount = 14.5; - double currentAmount = 44.7; - BatchAmountUpdateEvent batchAmountUpdateEvent = new BatchAmountUpdateEvent(batchId, previousAmount, currentAmount); - assertEquals(batchId, batchAmountUpdateEvent.getBatchId()); - } - - @Test - @UnitTestMethod(name="getPreviousAmount",args = {}) - public void testGetPreviousAmount() { - BatchId batchId = new BatchId(23423); - double previousAmount = 14.5; - double currentAmount = 44.7; - BatchAmountUpdateEvent batchAmountUpdateEvent = new BatchAmountUpdateEvent(batchId, previousAmount, currentAmount); - assertEquals(previousAmount, batchAmountUpdateEvent.getPreviousAmount()); - } - - @Test - @UnitTestMethod(name="getCurrentAmount",args = {}) - public void testGetCurrentAmount() { - BatchId batchId = new BatchId(23423); - double previousAmount = 14.5; - double currentAmount = 44.7; - BatchAmountUpdateEvent batchAmountUpdateEvent = new BatchAmountUpdateEvent(batchId, previousAmount, currentAmount); - assertEquals(currentAmount, batchAmountUpdateEvent.getCurrentAmount()); - } - - @Test - @UnitTestMethod(name="toString",args = {}) - public void testToString() { - BatchId batchId = new BatchId(23423); - double previousAmount = 14.5; - double currentAmount = 44.7; - BatchAmountUpdateEvent batchAmountUpdateEvent = new BatchAmountUpdateEvent(batchId, previousAmount, currentAmount); - assertEquals("BatchAmountUpdateEvent [batchId=23423, previousAmount=14.5, currentAmount=44.7]", batchAmountUpdateEvent.toString()); - } - -} diff --git a/gcm3/src/test/java/plugins/materials/events/AT_BatchImminentRemovalEvent.java b/gcm3/src/test/java/plugins/materials/events/AT_BatchImminentRemovalEvent.java deleted file mode 100644 index d1e863372..000000000 --- a/gcm3/src/test/java/plugins/materials/events/AT_BatchImminentRemovalEvent.java +++ /dev/null @@ -1,45 +0,0 @@ -package plugins.materials.events; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -import plugins.materials.support.BatchId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = BatchImminentRemovalEvent.class) -public class AT_BatchImminentRemovalEvent { - - @Test - @UnitTestConstructor(args = { BatchId.class }) - public void testConstructor() { - // nothing to test - } - - @Test - @UnitTestMethod(name = "getPrimaryKeyValue", args = {}) - public void testGetPrimaryKeyValue() { - BatchId batchId = new BatchId(7867); - BatchImminentRemovalEvent batchImminentRemovalEvent = new BatchImminentRemovalEvent(batchId); - assertEquals(BatchImminentRemovalEvent.class, batchImminentRemovalEvent.getPrimaryKeyValue()); - } - - @Test - @UnitTestMethod(name = "getBatchId", args = {}) - public void testGetBatchId() { - BatchId batchId = new BatchId(7867); - BatchImminentRemovalEvent batchImminentRemovalEvent = new BatchImminentRemovalEvent(batchId); - assertEquals(batchId, batchImminentRemovalEvent.getBatchId()); - } - - @Test - @UnitTestMethod(name = "toString", args = {}) - public void testToString() { - BatchId batchId = new BatchId(7867); - BatchImminentRemovalEvent batchImminentRemovalEvent = new BatchImminentRemovalEvent(batchId); - assertEquals("BatchImminentRemovalEvent [batchId=7867]", batchImminentRemovalEvent.toString()); - } - -} diff --git a/gcm3/src/test/java/plugins/materials/events/AT_BatchPropertyUpdateEvent.java b/gcm3/src/test/java/plugins/materials/events/AT_BatchPropertyUpdateEvent.java deleted file mode 100644 index 800e4ed39..000000000 --- a/gcm3/src/test/java/plugins/materials/events/AT_BatchPropertyUpdateEvent.java +++ /dev/null @@ -1,92 +0,0 @@ -package plugins.materials.events; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -import nucleus.Event; -import plugins.materials.support.BatchId; -import plugins.materials.support.BatchPropertyId; -import plugins.materials.testsupport.TestBatchPropertyId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = BatchPropertyUpdateEvent.class) -public class AT_BatchPropertyUpdateEvent implements Event { - - @Test - @UnitTestConstructor(args = { BatchId.class, BatchPropertyId.class, Object.class, Object.class }) - public void testConstructor() { - // nothing to test - } - - @Test - @UnitTestMethod(name = "getPrimaryKeyValue", args = {}) - public void testGetPrimaryKeyValue() { - BatchId batchId = new BatchId(5348); - BatchPropertyId batchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK; - Object previousPropertyValue = 45; - Object currentPropertyValue = 643; - BatchPropertyUpdateEvent batchPropertyUpdateEvent = new BatchPropertyUpdateEvent(batchId, batchPropertyId, previousPropertyValue, currentPropertyValue); - assertEquals(BatchPropertyUpdateEvent.class, batchPropertyUpdateEvent.getPrimaryKeyValue()); - } - - @Test - @UnitTestMethod(name = "getBatchId", args = {}) - public void testGetBatchId() { - BatchId batchId = new BatchId(5348); - BatchPropertyId batchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK; - Object previousPropertyValue = 45; - Object currentPropertyValue = 643; - BatchPropertyUpdateEvent batchPropertyUpdateEvent = new BatchPropertyUpdateEvent(batchId, batchPropertyId, previousPropertyValue, currentPropertyValue); - assertEquals(batchId, batchPropertyUpdateEvent.getBatchId()); - } - - @Test - @UnitTestMethod(name = "getBatchPropertyId", args = {}) - public void testGetBatchPropertyId() { - BatchId batchId = new BatchId(5348); - BatchPropertyId batchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK; - Object previousPropertyValue = 45; - Object currentPropertyValue = 643; - BatchPropertyUpdateEvent batchPropertyUpdateEvent = new BatchPropertyUpdateEvent(batchId, batchPropertyId, previousPropertyValue, currentPropertyValue); - assertEquals(batchPropertyId, batchPropertyUpdateEvent.getBatchPropertyId()); - } - - @Test - @UnitTestMethod(name = "getPreviousPropertyValue", args = {}) - public void testGetPreviousPropertyValue() { - BatchId batchId = new BatchId(5348); - BatchPropertyId batchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK; - Object previousPropertyValue = 45; - Object currentPropertyValue = 643; - BatchPropertyUpdateEvent batchPropertyUpdateEvent = new BatchPropertyUpdateEvent(batchId, batchPropertyId, previousPropertyValue, currentPropertyValue); - assertEquals(previousPropertyValue, batchPropertyUpdateEvent.getPreviousPropertyValue()); - } - - @Test - @UnitTestMethod(name = "getCurrentPropertyValue", args = {}) - public void testGetCurrentPropertyValue() { - BatchId batchId = new BatchId(5348); - BatchPropertyId batchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK; - Object previousPropertyValue = 45; - Object currentPropertyValue = 643; - BatchPropertyUpdateEvent batchPropertyUpdateEvent = new BatchPropertyUpdateEvent(batchId, batchPropertyId, previousPropertyValue, currentPropertyValue); - assertEquals(currentPropertyValue, batchPropertyUpdateEvent.getCurrentPropertyValue()); - } - - @Test - @UnitTestMethod(name = "toString", args = {}) - public void testToString() { - BatchId batchId = new BatchId(5348); - BatchPropertyId batchPropertyId = TestBatchPropertyId.BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK; - Object previousPropertyValue = 45; - Object currentPropertyValue = 643; - BatchPropertyUpdateEvent batchPropertyUpdateEvent = new BatchPropertyUpdateEvent(batchId, batchPropertyId, previousPropertyValue, currentPropertyValue); - String expectedValue = "BatchPropertyUpdateEvent [batchId=5348, batchPropertyId=BATCH_PROPERTY_1_2_INTEGER_MUTABLE_NO_TRACK, previousPropertyValue=45, currentPropertyValue=643]"; - String actualValue = batchPropertyUpdateEvent.toString(); - assertEquals(expectedValue, actualValue); - } - -} diff --git a/gcm3/src/test/java/plugins/materials/events/AT_MaterialsProducerPropertyUpdateEvent.java b/gcm3/src/test/java/plugins/materials/events/AT_MaterialsProducerPropertyUpdateEvent.java deleted file mode 100644 index 835dec010..000000000 --- a/gcm3/src/test/java/plugins/materials/events/AT_MaterialsProducerPropertyUpdateEvent.java +++ /dev/null @@ -1,153 +0,0 @@ -package plugins.materials.events; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -import nucleus.EventLabel; -import nucleus.EventLabeler; -import nucleus.SimulationContext; -import plugins.materials.support.MaterialsProducerId; -import plugins.materials.support.MaterialsProducerPropertyId; -import plugins.materials.testsupport.MaterialsActionSupport; -import plugins.materials.testsupport.TestMaterialsProducerId; -import plugins.materials.testsupport.TestMaterialsProducerPropertyId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = MaterialsProducerPropertyUpdateEvent.class) -public class AT_MaterialsProducerPropertyUpdateEvent { - - @Test - @UnitTestConstructor(args = { MaterialsProducerId.class, MaterialsProducerPropertyId.class, Object.class, Object.class }) - public void testConstructor() { - //nothing to test - } - - @Test - @UnitTestMethod(name = "getPrimaryKeyValue", args = {}) - public void testGetPrimaryKeyValue() { - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_3; - MaterialsProducerPropertyId materialsProducerPropertyId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK; - Object previousPropertyValue = 896.5; - Object currentPropertyValue = 3762.87; - MaterialsProducerPropertyUpdateEvent materialsProducerPropertyUpdateEvent = new MaterialsProducerPropertyUpdateEvent(materialsProducerId, - materialsProducerPropertyId, previousPropertyValue, currentPropertyValue); - assertEquals(materialsProducerPropertyId, materialsProducerPropertyUpdateEvent.getPrimaryKeyValue()); - } - - @Test - @UnitTestMethod(name = "getMaterialsProducerId", args = {}) - public void testGetMaterialsProducerId() { - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_3; - MaterialsProducerPropertyId materialsProducerPropertyId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK; - Object previousPropertyValue = 896.5; - Object currentPropertyValue = 3762.87; - MaterialsProducerPropertyUpdateEvent materialsProducerPropertyUpdateEvent = new MaterialsProducerPropertyUpdateEvent(materialsProducerId, - materialsProducerPropertyId, previousPropertyValue, currentPropertyValue); - assertEquals(materialsProducerId, materialsProducerPropertyUpdateEvent.getMaterialsProducerId()); - } - - @Test - @UnitTestMethod(name = "getMaterialsProducerPropertyId", args = {}) - public void testGetMaterialsProducerPropertyId() { - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_3; - MaterialsProducerPropertyId materialsProducerPropertyId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK; - Object previousPropertyValue = 896.5; - Object currentPropertyValue = 3762.87; - MaterialsProducerPropertyUpdateEvent materialsProducerPropertyUpdateEvent = new MaterialsProducerPropertyUpdateEvent(materialsProducerId, - materialsProducerPropertyId, previousPropertyValue, currentPropertyValue); - assertEquals(materialsProducerPropertyId, materialsProducerPropertyUpdateEvent.getMaterialsProducerPropertyId()); - } - - @Test - @UnitTestMethod(name = "getPreviousPropertyValue", args = {}) - public void testGgetPreviousPropertyValue() { - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_3; - MaterialsProducerPropertyId materialsProducerPropertyId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK; - Object previousPropertyValue = 896.5; - Object currentPropertyValue = 3762.87; - MaterialsProducerPropertyUpdateEvent materialsProducerPropertyUpdateEvent = new MaterialsProducerPropertyUpdateEvent(materialsProducerId, - materialsProducerPropertyId, previousPropertyValue, currentPropertyValue); - assertEquals(previousPropertyValue, materialsProducerPropertyUpdateEvent.getPreviousPropertyValue()); - } - - @Test - @UnitTestMethod(name = "getCurrentPropertyValue", args = {}) - public void testGetCurrentPropertyValue() { - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_3; - MaterialsProducerPropertyId materialsProducerPropertyId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK; - Object previousPropertyValue = 896.5; - Object currentPropertyValue = 3762.87; - MaterialsProducerPropertyUpdateEvent materialsProducerPropertyUpdateEvent = new MaterialsProducerPropertyUpdateEvent(materialsProducerId, - materialsProducerPropertyId, previousPropertyValue, currentPropertyValue); - assertEquals(currentPropertyValue, materialsProducerPropertyUpdateEvent.getCurrentPropertyValue()); - } - - @Test - @UnitTestMethod(name = "toString", args = {}) - public void testToString() { - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_3; - MaterialsProducerPropertyId materialsProducerPropertyId = TestMaterialsProducerPropertyId.MATERIALS_PRODUCER_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK; - Object previousPropertyValue = 896.5; - Object currentPropertyValue = 3762.87; - MaterialsProducerPropertyUpdateEvent materialsProducerPropertyUpdateEvent = new MaterialsProducerPropertyUpdateEvent(materialsProducerId, - materialsProducerPropertyId, previousPropertyValue, currentPropertyValue); - - String expectedValue = "MaterialsProducerPropertyUpdateEvent [materialsProducerId=MATERIALS_PRODUCER_3, materialsProducerPropertyId=MATERIALS_PRODUCER_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK, previousPropertyValue=896.5, currentPropertyValue=3762.87]"; - String actualValue = materialsProducerPropertyUpdateEvent.toString(); - assertEquals(expectedValue, actualValue); - } - - @Test - @UnitTestMethod(name = "getEventLabelByMaterialsProducerAndProperty", args = { SimulationContext.class, MaterialsProducerId.class, MaterialsProducerPropertyId.class }) - public void testGetEventLabelByMaterialsProducerAndProperty() { - MaterialsActionSupport.testConsumer(6182040571479306522L, (c) -> { - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId.values()) { - EventLabel eventLabel = MaterialsProducerPropertyUpdateEvent.getEventLabelByMaterialsProducerAndProperty(c, - testMaterialsProducerId, testMaterialsProducerPropertyId); - assertEquals(MaterialsProducerPropertyUpdateEvent.class, eventLabel.getEventClass()); - assertEquals(testMaterialsProducerPropertyId, eventLabel.getPrimaryKeyValue()); - assertEquals(MaterialsProducerPropertyUpdateEvent.getEventLabelerForMaterialsProducerAndProperty().getEventLabelerId(), eventLabel.getLabelerId()); - } - } - }); - } - - @Test - @UnitTestMethod(name = "getEventLabelerForMaterialsProducerAndProperty", args = {}) - public void testGetEventLabelerForMaterialsProducerAndProperty() { - - MaterialsActionSupport.testConsumer(3114721828344288343L, (c) -> { - // show that the event labeler can be constructed has the correct - // values - EventLabeler eventLabeler = MaterialsProducerPropertyUpdateEvent.getEventLabelerForMaterialsProducerAndProperty(); - assertEquals(MaterialsProducerPropertyUpdateEvent.class, eventLabeler.getEventClass()); - - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId.values()) { - assertEquals(MaterialsProducerPropertyUpdateEvent.getEventLabelByMaterialsProducerAndProperty(c, testMaterialsProducerId,testMaterialsProducerPropertyId).getLabelerId(), eventLabeler.getEventLabelerId()); - - // show that the event labeler produces the expected event - // label - - // create an event - MaterialsProducerPropertyUpdateEvent event = new MaterialsProducerPropertyUpdateEvent(testMaterialsProducerId, testMaterialsProducerPropertyId, 45,72); - - // derive the expected event label for this event - EventLabel expectedEventLabel = MaterialsProducerPropertyUpdateEvent.getEventLabelByMaterialsProducerAndProperty(c, - testMaterialsProducerId,testMaterialsProducerPropertyId); - - // have the event labeler produce an event label and show it - // is equal to the expected event label - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - assertEquals(expectedEventLabel, actualEventLabel); - - } - } - }); - } - -} diff --git a/gcm3/src/test/java/plugins/materials/events/AT_MaterialsProducerResourceUpdateEvent.java b/gcm3/src/test/java/plugins/materials/events/AT_MaterialsProducerResourceUpdateEvent.java deleted file mode 100644 index 5bfc97e62..000000000 --- a/gcm3/src/test/java/plugins/materials/events/AT_MaterialsProducerResourceUpdateEvent.java +++ /dev/null @@ -1,198 +0,0 @@ -package plugins.materials.events; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -import nucleus.EventLabel; -import nucleus.EventLabeler; -import nucleus.SimulationContext; -import plugins.materials.support.MaterialsProducerId; -import plugins.materials.testsupport.MaterialsActionSupport; -import plugins.materials.testsupport.TestMaterialsProducerId; -import plugins.resources.support.ResourceId; -import plugins.resources.testsupport.TestResourceId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = MaterialsProducerResourceUpdateEvent.class) -public class AT_MaterialsProducerResourceUpdateEvent { - - @Test - @UnitTestConstructor(args = { MaterialsProducerId.class, ResourceId.class, long.class, long.class }) - public void testConstructor() { - //nothing to test - } - - @Test - @UnitTestMethod(name = "getPrimaryKeyValue", args = {}) - public void testGetPrimaryKeyValue() { - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_3; - ResourceId resourceId = TestResourceId.RESOURCE_4; - long previousResourceLevel = 23L; - long currentResourceLevel = 346L; - MaterialsProducerResourceUpdateEvent materialsProducerResourceUpdateEvent = new MaterialsProducerResourceUpdateEvent(materialsProducerId, resourceId, - previousResourceLevel, currentResourceLevel); - assertEquals(resourceId, materialsProducerResourceUpdateEvent.getPrimaryKeyValue()); - } - - @Test - @UnitTestMethod(name = "getMaterialsProducerId", args = {}) - public void testGetMaterialsProducerId() { - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_3; - ResourceId resourceId = TestResourceId.RESOURCE_4; - long previousResourceLevel = 23L; - long currentResourceLevel = 346L; - MaterialsProducerResourceUpdateEvent materialsProducerResourceUpdateEvent = new MaterialsProducerResourceUpdateEvent(materialsProducerId, resourceId, - previousResourceLevel, currentResourceLevel); - assertEquals(materialsProducerId, materialsProducerResourceUpdateEvent.getMaterialsProducerId()); - } - - @Test - @UnitTestMethod(name = "getResourceId", args = {}) - public void testGetResourceId() { - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_3; - ResourceId resourceId = TestResourceId.RESOURCE_4; - long previousResourceLevel = 23L; - long currentResourceLevel = 346L; - MaterialsProducerResourceUpdateEvent materialsProducerResourceUpdateEvent = new MaterialsProducerResourceUpdateEvent(materialsProducerId, resourceId, - previousResourceLevel, currentResourceLevel); - assertEquals(resourceId, materialsProducerResourceUpdateEvent.getResourceId()); - } - - @Test - @UnitTestMethod(name = "getPreviousResourceLevel", args = {}) - public void testGetPreviousResourceLevel() { - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_3; - ResourceId resourceId = TestResourceId.RESOURCE_4; - long previousResourceLevel = 23L; - long currentResourceLevel = 346L; - MaterialsProducerResourceUpdateEvent materialsProducerResourceUpdateEvent = new MaterialsProducerResourceUpdateEvent(materialsProducerId, resourceId, - previousResourceLevel, currentResourceLevel); - assertEquals(previousResourceLevel, materialsProducerResourceUpdateEvent.getPreviousResourceLevel()); - } - - @Test - @UnitTestMethod(name = "getCurrentResourceLevel", args = {}) - public void testGetCurrentResourceLevel() { - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_3; - ResourceId resourceId = TestResourceId.RESOURCE_4; - long previousResourceLevel = 23L; - long currentResourceLevel = 346L; - MaterialsProducerResourceUpdateEvent materialsProducerResourceUpdateEvent = new MaterialsProducerResourceUpdateEvent(materialsProducerId, resourceId, - previousResourceLevel, currentResourceLevel); - assertEquals(currentResourceLevel, materialsProducerResourceUpdateEvent.getCurrentResourceLevel()); - } - - @Test - @UnitTestMethod(name = "toString", args = {}) - public void testToString() { - MaterialsProducerId materialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_3; - ResourceId resourceId = TestResourceId.RESOURCE_4; - long previousResourceLevel = 23L; - long currentResourceLevel = 346L; - MaterialsProducerResourceUpdateEvent materialsProducerResourceUpdateEvent = new MaterialsProducerResourceUpdateEvent(materialsProducerId, resourceId, - previousResourceLevel, currentResourceLevel); - String expectedValue = "MaterialsProducerResourceUpdateEvent [materialsProducerId=MATERIALS_PRODUCER_3, resourceId=RESOURCE_4, previousResourceLevel=23, currentResourceLevel=346]"; - String actualValue = materialsProducerResourceUpdateEvent.toString(); - assertEquals(expectedValue, actualValue); - } - - @Test - @UnitTestMethod(name = "getEventLabelByMaterialsProducerAndResource", args = { SimulationContext.class, MaterialsProducerId.class, ResourceId.class }) - public void testGetEventLabelByMaterialsProducerAndResource() { - MaterialsActionSupport.testConsumer(7613656660266127922L, (c) -> { - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - for (TestResourceId testResourceId : TestResourceId.values()) { - EventLabel eventLabel = MaterialsProducerResourceUpdateEvent.getEventLabelByMaterialsProducerAndResource(c, - testMaterialsProducerId, testResourceId); - assertEquals(MaterialsProducerResourceUpdateEvent.class, eventLabel.getEventClass()); - assertEquals(testResourceId, eventLabel.getPrimaryKeyValue()); - assertEquals(MaterialsProducerResourceUpdateEvent.getEventLabelerForMaterialsProducerAndResource().getEventLabelerId(), eventLabel.getLabelerId()); - } - } - }); - } - - @Test - @UnitTestMethod(name = "getEventLabelerForMaterialsProducerAndResource", args = {}) - public void testGetEventLabelerForMaterialsProducerAndResource() { - MaterialsActionSupport.testConsumer(3426923014578127127L, (c) -> { - // show that the event labeler can be constructed has the correct - // values - EventLabeler eventLabeler = MaterialsProducerResourceUpdateEvent.getEventLabelerForMaterialsProducerAndResource(); - assertEquals(MaterialsProducerResourceUpdateEvent.class, eventLabeler.getEventClass()); - - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - for (TestResourceId testResourceId : TestResourceId.values()) { - assertEquals(MaterialsProducerResourceUpdateEvent.getEventLabelByMaterialsProducerAndResource(c, testMaterialsProducerId,testResourceId).getLabelerId(), eventLabeler.getEventLabelerId()); - - // show that the event labeler produces the expected event - // label - - // create an event - MaterialsProducerResourceUpdateEvent event = new MaterialsProducerResourceUpdateEvent(testMaterialsProducerId, testResourceId, 45L,72L); - - // derive the expected event label for this event - EventLabel expectedEventLabel = MaterialsProducerResourceUpdateEvent.getEventLabelByMaterialsProducerAndResource(c, - testMaterialsProducerId,testResourceId); - - // have the event labeler produce an event label and show it - // is equal to the expected event label - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - assertEquals(expectedEventLabel, actualEventLabel); - - } - } - }); - } - - @Test - @UnitTestMethod(name = "getEventLabelByResource", args = { SimulationContext.class, ResourceId.class }) - public void testGetEventLabelByResource() { - MaterialsActionSupport.testConsumer(6642554036399629036L, (c) -> { - for (TestResourceId testResourceId : TestResourceId.values()) { - EventLabel eventLabel = MaterialsProducerResourceUpdateEvent.getEventLabelByResource(c, testResourceId); - assertEquals(MaterialsProducerResourceUpdateEvent.class, eventLabel.getEventClass()); - assertEquals(testResourceId, eventLabel.getPrimaryKeyValue()); - assertEquals(MaterialsProducerResourceUpdateEvent.getEventLabelerForResource().getEventLabelerId(), eventLabel.getLabelerId()); - } - }); - } - - @Test - @UnitTestMethod(name = "getEventLabelerForResource", args = {}) - public void testGetEventLabelerForResource() { - - MaterialsActionSupport.testConsumer(1027156783814158843L, (c) -> { - // show that the event labeler can be constructed has the correct - // values - EventLabeler eventLabeler = MaterialsProducerResourceUpdateEvent.getEventLabelerForResource(); - assertEquals(MaterialsProducerResourceUpdateEvent.class, eventLabeler.getEventClass()); - - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - for (TestResourceId testResourceId : TestResourceId.values()) { - assertEquals(MaterialsProducerResourceUpdateEvent.getEventLabelByResource(c, testResourceId).getLabelerId(), eventLabeler.getEventLabelerId()); - - // show that the event labeler produces the expected event - // label - - // create an event - MaterialsProducerResourceUpdateEvent event = new MaterialsProducerResourceUpdateEvent(testMaterialsProducerId, testResourceId, 45L,72L); - - // derive the expected event label for this event - EventLabel expectedEventLabel = MaterialsProducerResourceUpdateEvent.getEventLabelByResource(c, - testResourceId); - - // have the event labeler produce an event label and show it - // is equal to the expected event label - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - assertEquals(expectedEventLabel, actualEventLabel); - - } - } - }); - } - -} diff --git a/gcm3/src/test/java/plugins/materials/events/AT_StageAdditionEvent.java b/gcm3/src/test/java/plugins/materials/events/AT_StageAdditionEvent.java deleted file mode 100644 index b0cf71cd1..000000000 --- a/gcm3/src/test/java/plugins/materials/events/AT_StageAdditionEvent.java +++ /dev/null @@ -1,48 +0,0 @@ -package plugins.materials.events; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -import nucleus.Event; -import plugins.materials.support.StageId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = StageAdditionEvent.class) -public class AT_StageAdditionEvent implements Event { - - - @Test - @UnitTestConstructor(args = {StageId.class}) - public void testConstructor() { - //nothing to test - } - - @Test - @UnitTestMethod(name="getPrimaryKeyValue",args = {}) - public void testGetPrimaryKeyValue() { - StageId stageId = new StageId(534); - StageAdditionEvent stageAdditionEvent = new StageAdditionEvent(stageId); - assertEquals(StageAdditionEvent.class,stageAdditionEvent.getPrimaryKeyValue()); - } - - - @Test - @UnitTestMethod(name="getStageId",args = {}) - public void testGetStageId() { - StageId stageId = new StageId(534); - StageAdditionEvent stageAdditionEvent = new StageAdditionEvent(stageId); - assertEquals(stageId,stageAdditionEvent.getStageId()); - } - - @Test - @UnitTestMethod(name="toString",args = {}) - public void testToString() { - StageId stageId = new StageId(534); - StageAdditionEvent stageAdditionEvent = new StageAdditionEvent(stageId); - assertEquals("StageCreation [stageId=534]",stageAdditionEvent.toString()); - } - -} diff --git a/gcm3/src/test/java/plugins/materials/events/AT_StageImminentRemovalEvent.java b/gcm3/src/test/java/plugins/materials/events/AT_StageImminentRemovalEvent.java deleted file mode 100644 index e6eec3d38..000000000 --- a/gcm3/src/test/java/plugins/materials/events/AT_StageImminentRemovalEvent.java +++ /dev/null @@ -1,46 +0,0 @@ -package plugins.materials.events; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -import plugins.materials.support.StageId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = StageImminentRemovalEvent.class) - -public class AT_StageImminentRemovalEvent { - - @Test - @UnitTestConstructor(args = { StageId.class }) - public void testConstructor() { - // nothing to test - } - - @Test - @UnitTestMethod(name = "getPrimaryKeyValue", args = {}) - public void testGetPrimaryKeyValue() { - StageId stageId = new StageId(4534); - StageImminentRemovalEvent stageImminentRemovalEvent = new StageImminentRemovalEvent(stageId); - assertEquals(StageImminentRemovalEvent.class, stageImminentRemovalEvent.getPrimaryKeyValue()); - } - - @Test - @UnitTestMethod(name = "getStageId", args = {}) - public void testGetStageId() { - StageId stageId = new StageId(4534); - StageImminentRemovalEvent stageImminentRemovalEvent = new StageImminentRemovalEvent(stageId); - assertEquals(stageId, stageImminentRemovalEvent.getStageId()); - } - - @Test - @UnitTestMethod(name = "toString", args = {}) - public void testToString() { - StageId stageId = new StageId(4534); - StageImminentRemovalEvent stageImminentRemovalEvent = new StageImminentRemovalEvent(stageId); - assertEquals("StageImminentRemovalEvent [stageId=4534]", stageImminentRemovalEvent.toString()); - } - -} diff --git a/gcm3/src/test/java/plugins/materials/events/AT_StageMaterialsProducerUpdateEvent.java b/gcm3/src/test/java/plugins/materials/events/AT_StageMaterialsProducerUpdateEvent.java deleted file mode 100644 index 062338f40..000000000 --- a/gcm3/src/test/java/plugins/materials/events/AT_StageMaterialsProducerUpdateEvent.java +++ /dev/null @@ -1,247 +0,0 @@ -package plugins.materials.events; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -import nucleus.EventLabel; -import nucleus.EventLabeler; -import nucleus.SimulationContext; -import plugins.materials.datamangers.MaterialsDataManager; -import plugins.materials.support.MaterialsProducerId; -import plugins.materials.support.StageId; -import plugins.materials.testsupport.MaterialsActionSupport; -import plugins.materials.testsupport.TestMaterialsProducerId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = StageMaterialsProducerUpdateEvent.class) -public class AT_StageMaterialsProducerUpdateEvent { - - @Test - @UnitTestConstructor(args = { StageId.class, MaterialsProducerId.class, MaterialsProducerId.class }) - public void testConstructor() { - // nothing to test - } - - @Test - @UnitTestMethod(name = "getPrimaryKeyValue", args = {}) - public void testGetPrimaryKeyValue() { - StageId stageId = new StageId(344); - MaterialsProducerId previousMaterialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; - MaterialsProducerId currentMaterialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; - StageMaterialsProducerUpdateEvent stageMaterialsProducerUpdateEvent = new StageMaterialsProducerUpdateEvent(stageId, previousMaterialsProducerId, - currentMaterialsProducerId); - assertEquals(StageMaterialsProducerUpdateEvent.class, stageMaterialsProducerUpdateEvent.getPrimaryKeyValue()); - } - - @Test - @UnitTestMethod(name = "getStageId", args = {}) - public void testGetStageId() { - StageId stageId = new StageId(344); - MaterialsProducerId previousMaterialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; - MaterialsProducerId currentMaterialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; - StageMaterialsProducerUpdateEvent stageMaterialsProducerUpdateEvent = new StageMaterialsProducerUpdateEvent(stageId, previousMaterialsProducerId, - currentMaterialsProducerId); - assertEquals(stageId, stageMaterialsProducerUpdateEvent.getStageId()); - } - - @Test - @UnitTestMethod(name = "getPreviousMaterialsProducerId", args = {}) - public void testGetPreviousMaterialsProducerId() { - StageId stageId = new StageId(344); - MaterialsProducerId previousMaterialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; - MaterialsProducerId currentMaterialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; - StageMaterialsProducerUpdateEvent stageMaterialsProducerUpdateEvent = new StageMaterialsProducerUpdateEvent(stageId, previousMaterialsProducerId, - currentMaterialsProducerId); - assertEquals(previousMaterialsProducerId, stageMaterialsProducerUpdateEvent.getPreviousMaterialsProducerId()); - } - - @Test - @UnitTestMethod(name = "getCurrentMaterialsProducerId", args = {}) - public void testGetCurrentMaterialsProducerId() { - StageId stageId = new StageId(344); - MaterialsProducerId previousMaterialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; - MaterialsProducerId currentMaterialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; - StageMaterialsProducerUpdateEvent stageMaterialsProducerUpdateEvent = new StageMaterialsProducerUpdateEvent(stageId, previousMaterialsProducerId, - currentMaterialsProducerId); - assertEquals(currentMaterialsProducerId, stageMaterialsProducerUpdateEvent.getCurrentMaterialsProducerId()); - } - - @Test - @UnitTestMethod(name = "toString", args = {}) - public void testToString() { - StageId stageId = new StageId(344); - MaterialsProducerId previousMaterialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_1; - MaterialsProducerId currentMaterialsProducerId = TestMaterialsProducerId.MATERIALS_PRODUCER_2; - StageMaterialsProducerUpdateEvent stageMaterialsProducerUpdateEvent = new StageMaterialsProducerUpdateEvent(stageId, previousMaterialsProducerId, - currentMaterialsProducerId); - String expectedValue = "StageMaterialsProducerUpdateEvent [stageId=344, previousMaterialsProducerId=MATERIALS_PRODUCER_1, currentMaterialsProducerId=MATERIALS_PRODUCER_2]"; - String actualValue = stageMaterialsProducerUpdateEvent.toString(); - assertEquals(expectedValue, actualValue); - } - - @Test - @UnitTestMethod(name = "getEventLabelByDestination", args = { SimulationContext.class, MaterialsProducerId.class }) - public void testGetEventLabelByDestination() { - - MaterialsActionSupport.testConsumer(5070867343126122585L, (c) -> { - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - EventLabel eventLabel = StageMaterialsProducerUpdateEvent.getEventLabelByDestination(c, testMaterialsProducerId); - assertEquals(StageMaterialsProducerUpdateEvent.class, eventLabel.getEventClass()); - assertEquals(StageMaterialsProducerUpdateEvent.class, eventLabel.getPrimaryKeyValue()); - assertEquals(StageMaterialsProducerUpdateEvent.getEventLabelByDestination(c, testMaterialsProducerId).getLabelerId(), eventLabel.getLabelerId()); - } - }); - } - - @Test - @UnitTestMethod(name = "getEventLabelerForDestination", args = {}) - public void testGetEventLabelerForDestination() { - - MaterialsActionSupport.testConsumer(1319347369419715424L, (c) -> { - // show that the event labeler can be constructed has the correct - // values - EventLabeler eventLabeler = StageMaterialsProducerUpdateEvent.getEventLabelerForDestination(); - assertEquals(StageMaterialsProducerUpdateEvent.class, eventLabeler.getEventClass()); - - for (TestMaterialsProducerId previousMaterialsProducerId : TestMaterialsProducerId.values()) { - for (TestMaterialsProducerId currentMaterialsProducerId : TestMaterialsProducerId.values()) { - for (int i = 0; i < 10; i++) { - StageId stageId = new StageId(i); - assertEquals(StageMaterialsProducerUpdateEvent.getEventLabelByDestination(c, currentMaterialsProducerId).getLabelerId(), eventLabeler.getEventLabelerId()); - - // show that the event labeler produces the expected - // event - // label - - // create an event - StageMaterialsProducerUpdateEvent event = new StageMaterialsProducerUpdateEvent(stageId, previousMaterialsProducerId, currentMaterialsProducerId); - - // derive the expected event label for this event - EventLabel expectedEventLabel = StageMaterialsProducerUpdateEvent.getEventLabelByDestination(c, - currentMaterialsProducerId); - - // have the event labeler produce an event label and - // show it - // is equal to the expected event label - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - assertEquals(expectedEventLabel, actualEventLabel); - } - } - } - - }); - } - - @Test - @UnitTestMethod(name = "getEventLabelBySource", args = { SimulationContext.class, MaterialsProducerId.class }) - public void testGetEventLabelBySource() { - - MaterialsActionSupport.testConsumer(1144625150509891316L, (c) -> { - for (TestMaterialsProducerId testMaterialsProducerId : TestMaterialsProducerId.values()) { - EventLabel eventLabel = StageMaterialsProducerUpdateEvent.getEventLabelBySource(c, testMaterialsProducerId); - assertEquals(StageMaterialsProducerUpdateEvent.class, eventLabel.getEventClass()); - assertEquals(StageMaterialsProducerUpdateEvent.class, eventLabel.getPrimaryKeyValue()); - assertEquals(StageMaterialsProducerUpdateEvent.getEventLabelBySource(c, testMaterialsProducerId).getLabelerId(), eventLabel.getLabelerId()); - } - }); - - } - - @Test - @UnitTestMethod(name = "getEventLabelerForSource", args = {}) - public void testGetEventLabelerForSource() { - - MaterialsActionSupport.testConsumer(4315809780717887025L, (c) -> { - // show that the event labeler can be constructed has the correct - // values - EventLabeler eventLabeler = StageMaterialsProducerUpdateEvent.getEventLabelerForSource(); - assertEquals(StageMaterialsProducerUpdateEvent.class, eventLabeler.getEventClass()); - - for (TestMaterialsProducerId previousMaterialsProducerId : TestMaterialsProducerId.values()) { - for (TestMaterialsProducerId currentMaterialsProducerId : TestMaterialsProducerId.values()) { - for (int i = 0; i < 10; i++) { - StageId stageId = new StageId(i); - assertEquals(StageMaterialsProducerUpdateEvent.getEventLabelBySource(c, previousMaterialsProducerId).getLabelerId(), eventLabeler.getEventLabelerId()); - - // show that the event labeler produces the expected - // event - // label - - // create an event - StageMaterialsProducerUpdateEvent event = new StageMaterialsProducerUpdateEvent(stageId, previousMaterialsProducerId, currentMaterialsProducerId); - - // derive the expected event label for this event - EventLabel expectedEventLabel = StageMaterialsProducerUpdateEvent.getEventLabelBySource(c, - previousMaterialsProducerId); - - // have the event labeler produce an event label and - // show it - // is equal to the expected event label - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - assertEquals(expectedEventLabel, actualEventLabel); - } - } - } - - }); - } - - @Test - @UnitTestMethod(name = "getEventLabelByStage", args = { SimulationContext.class, StageId.class }) - public void testGetEventLabelByStage() { - - MaterialsActionSupport.testConsumer(2260703021186632066L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - for (int i = 0; i < 10; i++) { - StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - EventLabel eventLabel = StageMaterialsProducerUpdateEvent.getEventLabelByStage(c, stageId); - assertEquals(StageMaterialsProducerUpdateEvent.class, eventLabel.getEventClass()); - assertEquals(StageMaterialsProducerUpdateEvent.class, eventLabel.getPrimaryKeyValue()); - assertEquals(StageMaterialsProducerUpdateEvent.getEventLabelByStage(c, stageId).getLabelerId(), eventLabel.getLabelerId()); - } - }); - } - - @Test - @UnitTestMethod(name = "getEventLabelerForStage", args = {}) - public void testGetEventLabelerForStage() { - - MaterialsActionSupport.testConsumer(6880728031897161656L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - - // show that the event labeler can be constructed has the correct - // values - EventLabeler eventLabeler = StageMaterialsProducerUpdateEvent.getEventLabelerForStage(); - assertEquals(StageMaterialsProducerUpdateEvent.class, eventLabeler.getEventClass()); - - for (TestMaterialsProducerId previousMaterialsProducerId : TestMaterialsProducerId.values()) { - for (TestMaterialsProducerId currentMaterialsProducerId : TestMaterialsProducerId.values()) { - for (int i = 0; i < 10; i++) { - StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_1); - assertEquals(StageMaterialsProducerUpdateEvent.getEventLabelByStage(c, stageId).getLabelerId(), eventLabeler.getEventLabelerId()); - - /* - * show that the event labeler produces the expected - * event label - */ - - // create an event - StageMaterialsProducerUpdateEvent event = new StageMaterialsProducerUpdateEvent(stageId, previousMaterialsProducerId, currentMaterialsProducerId); - - // derive the expected event label for this event - EventLabel expectedEventLabel = StageMaterialsProducerUpdateEvent.getEventLabelByStage(c, stageId); - - // have the event labeler produce an event label and - // show it - // is equal to the expected event label - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - assertEquals(expectedEventLabel, actualEventLabel); - } - } - } - }); - } -} \ No newline at end of file diff --git a/gcm3/src/test/java/plugins/materials/events/AT_StageMembershipAdditionEvent.java b/gcm3/src/test/java/plugins/materials/events/AT_StageMembershipAdditionEvent.java deleted file mode 100644 index 43b9177da..000000000 --- a/gcm3/src/test/java/plugins/materials/events/AT_StageMembershipAdditionEvent.java +++ /dev/null @@ -1,59 +0,0 @@ -package plugins.materials.events; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -import plugins.materials.support.BatchId; -import plugins.materials.support.StageId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = StageMembershipAdditionEvent.class) -public class AT_StageMembershipAdditionEvent { - - - @Test - @UnitTestConstructor(args = {BatchId.class, StageId.class}) - public void testConstructor() { - //nothing to test - } - - @Test - @UnitTestMethod(name="getPrimaryKeyValue",args = {}) - public void testGetPrimaryKeyValue() { - BatchId batchId = new BatchId(6); - StageId stageId = new StageId(252); - StageMembershipAdditionEvent stageMembershipAdditionEvent = new StageMembershipAdditionEvent(batchId, stageId); - assertEquals(StageMembershipAdditionEvent.class, stageMembershipAdditionEvent.getPrimaryKeyValue()); - } - - @Test - @UnitTestMethod(name="getBatchId",args = {}) - public void testGetBatchId() { - BatchId batchId = new BatchId(6); - StageId stageId = new StageId(252); - StageMembershipAdditionEvent stageMembershipAdditionEvent = new StageMembershipAdditionEvent(batchId, stageId); - assertEquals(batchId, stageMembershipAdditionEvent.getBatchId()); - } - - @Test - @UnitTestMethod(name="getStageId",args = {}) - public void testGetStageId() { - BatchId batchId = new BatchId(6); - StageId stageId = new StageId(252); - StageMembershipAdditionEvent stageMembershipAdditionEvent = new StageMembershipAdditionEvent(batchId, stageId); - assertEquals(stageId, stageMembershipAdditionEvent.getStageId()); - } - - @Test - @UnitTestMethod(name="toString",args = {}) - public void testToString() { - BatchId batchId = new BatchId(6); - StageId stageId = new StageId(252); - StageMembershipAdditionEvent stageMembershipAdditionEvent = new StageMembershipAdditionEvent(batchId, stageId); - assertEquals("StageMembershipAdditionEvent [batchId=6, stageId=252]", stageMembershipAdditionEvent.toString()); - } - -} diff --git a/gcm3/src/test/java/plugins/materials/events/AT_StageMembershipRemovalEvent.java b/gcm3/src/test/java/plugins/materials/events/AT_StageMembershipRemovalEvent.java deleted file mode 100644 index b287ad6be..000000000 --- a/gcm3/src/test/java/plugins/materials/events/AT_StageMembershipRemovalEvent.java +++ /dev/null @@ -1,57 +0,0 @@ -package plugins.materials.events; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -import plugins.materials.support.BatchId; -import plugins.materials.support.StageId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = StageMembershipRemovalEvent.class) -public class AT_StageMembershipRemovalEvent { - - @Test - @UnitTestConstructor(args = {BatchId.class, StageId.class}) - public void testConstructor() { - //nothing to test - } - - @Test - @UnitTestMethod(name="getPrimaryKeyValue",args = {}) - public void testGetPrimaryKeyValue() { - BatchId batchId = new BatchId(23); - StageId stageId = new StageId(765); - StageMembershipRemovalEvent stageMembershipRemovalEvent = new StageMembershipRemovalEvent(batchId, stageId); - assertEquals(StageMembershipRemovalEvent.class,stageMembershipRemovalEvent.getPrimaryKeyValue()); - } - - @Test - @UnitTestMethod(name="getBatchId",args = {}) - public void testGetBatchId() { - BatchId batchId = new BatchId(23); - StageId stageId = new StageId(765); - StageMembershipRemovalEvent stageMembershipRemovalEvent = new StageMembershipRemovalEvent(batchId, stageId); - assertEquals(batchId,stageMembershipRemovalEvent.getBatchId()); - } - - @Test - @UnitTestMethod(name="getStageId",args = {}) - public void testGetStageId() { - BatchId batchId = new BatchId(23); - StageId stageId = new StageId(765); - StageMembershipRemovalEvent stageMembershipRemovalEvent = new StageMembershipRemovalEvent(batchId, stageId); - assertEquals(stageId,stageMembershipRemovalEvent.getStageId()); - } - - @Test - @UnitTestMethod(name="toString",args = {}) - public void testToString() { - BatchId batchId = new BatchId(23); - StageId stageId = new StageId(765); - StageMembershipRemovalEvent stageMembershipRemovalEvent = new StageMembershipRemovalEvent(batchId, stageId); - assertEquals("StageMembershipRemovalEvent [batchId=23, stageId=765]",stageMembershipRemovalEvent.toString()); - } -} diff --git a/gcm3/src/test/java/plugins/materials/events/AT_StageOfferUpdateEvent.java b/gcm3/src/test/java/plugins/materials/events/AT_StageOfferUpdateEvent.java deleted file mode 100644 index f22bcc11a..000000000 --- a/gcm3/src/test/java/plugins/materials/events/AT_StageOfferUpdateEvent.java +++ /dev/null @@ -1,125 +0,0 @@ -package plugins.materials.events; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -import nucleus.EventLabel; -import nucleus.EventLabeler; -import nucleus.SimulationContext; -import plugins.materials.datamangers.MaterialsDataManager; -import plugins.materials.support.StageId; -import plugins.materials.testsupport.MaterialsActionSupport; -import plugins.materials.testsupport.TestMaterialsProducerId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = StageOfferUpdateEvent.class) -public class AT_StageOfferUpdateEvent { - - @Test - @UnitTestConstructor(args = { StageId.class, boolean.class, boolean.class }) - public void testConstructor() { - // nothing to test - } - - @Test - @UnitTestMethod(name = "getPrimaryKeyValue", args = {}) - public void testGetPrimaryKeyValue() { - // nothing to test - } - - @Test - @UnitTestMethod(name = "getStageId", args = {}) - public void testGetStageId() { - StageId stageId = new StageId(543); - boolean previousOfferState = true; - boolean currentOfferState = false; - StageOfferUpdateEvent stageOfferUpdateEvent = new StageOfferUpdateEvent(stageId, previousOfferState, currentOfferState); - assertEquals(stageId, stageOfferUpdateEvent.getStageId()); - } - - @Test - @UnitTestMethod(name = "isPreviousOfferState", args = {}) - public void testIsPreviousOfferState() { - StageId stageId = new StageId(543); - boolean previousOfferState = true; - boolean currentOfferState = false; - StageOfferUpdateEvent stageOfferUpdateEvent = new StageOfferUpdateEvent(stageId, previousOfferState, currentOfferState); - assertEquals(previousOfferState, stageOfferUpdateEvent.isPreviousOfferState()); - } - - @Test - @UnitTestMethod(name = "isCurrentOfferState", args = {}) - public void testIsCurrentOfferState() { - StageId stageId = new StageId(543); - boolean previousOfferState = true; - boolean currentOfferState = false; - StageOfferUpdateEvent stageOfferUpdateEvent = new StageOfferUpdateEvent(stageId, previousOfferState, currentOfferState); - assertEquals(currentOfferState, stageOfferUpdateEvent.isCurrentOfferState()); - } - - @Test - @UnitTestMethod(name = "toString", args = {}) - public void testToString() { - StageId stageId = new StageId(543); - boolean previousOfferState = true; - boolean currentOfferState = false; - StageOfferUpdateEvent stageOfferUpdateEvent = new StageOfferUpdateEvent(stageId, previousOfferState, currentOfferState); - assertEquals("StageOfferUpdateEvent [stageId=543, previousOfferState=true, currentOfferState=false]", stageOfferUpdateEvent.toString()); - } - - @Test - @UnitTestMethod(name = "getEventLabelByStage", args = { SimulationContext.class, StageId.class }) - public void testGetEventLabelByStage() { - - MaterialsActionSupport.testConsumer(1144625150509891316L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - for (int i = 0; i < 10; i++) { - StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_2); - EventLabel eventLabel = StageOfferUpdateEvent.getEventLabelByStage(c, stageId); - assertEquals(StageOfferUpdateEvent.class, eventLabel.getEventClass()); - assertEquals(StageOfferUpdateEvent.class, eventLabel.getPrimaryKeyValue()); - assertEquals(StageOfferUpdateEvent.getEventLabelByStage(c, stageId).getLabelerId(), eventLabel.getLabelerId()); - } - }); - } - - @Test - @UnitTestMethod(name = "getEventLabelerForStage", args = {}) - public void testGetEventLabelerForStage() { - - MaterialsActionSupport.testConsumer(305963642755178739L, (c) -> { - MaterialsDataManager materialsDataManager = c.getDataManager(MaterialsDataManager.class); - - // show that the event labeler can be constructed has the correct - // values - EventLabeler eventLabeler = StageOfferUpdateEvent.getEventLabelerForStage(); - assertEquals(StageOfferUpdateEvent.class, eventLabeler.getEventClass()); - - for (int i = 0; i < 10; i++) { - StageId stageId = materialsDataManager.addStage(TestMaterialsProducerId.MATERIALS_PRODUCER_3); - assertEquals(StageOfferUpdateEvent.getEventLabelByStage(c, stageId).getLabelerId(), eventLabeler.getEventLabelerId()); - - /* - * show that the event labeler produces the expected event label - */ - - // create an event - StageOfferUpdateEvent event = new StageOfferUpdateEvent(stageId, true, false); - - // derive the expected event label for this event - EventLabel expectedEventLabel = StageOfferUpdateEvent.getEventLabelByStage(c, stageId); - - // have the event labeler produce an event label and - // show it - // is equal to the expected event label - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - assertEquals(expectedEventLabel, actualEventLabel); - } - }); - - } - -} diff --git a/gcm3/src/test/java/plugins/materials/support/AT_BatchConstructionInfo.java b/gcm3/src/test/java/plugins/materials/support/AT_BatchConstructionInfo.java deleted file mode 100644 index e0a81b906..000000000 --- a/gcm3/src/test/java/plugins/materials/support/AT_BatchConstructionInfo.java +++ /dev/null @@ -1,131 +0,0 @@ -package plugins.materials.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashMap; -import java.util.Map; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import plugins.materials.testsupport.TestBatchPropertyId; -import plugins.materials.testsupport.TestMaterialId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.random.RandomGeneratorProvider; - -@UnitTest(target = BatchConstructionInfo.class) -public class AT_BatchConstructionInfo { - - @Test - @UnitTestMethod(name = "builder", args = {}) - public void testBuilder() { - assertNotNull(BatchConstructionInfo.builder()); - } - - @Test - @UnitTestMethod(target = BatchConstructionInfo.Builder.class, name = "build", args = {}) - public void testBuild() { - BatchConstructionInfo batchConstructionInfo = BatchConstructionInfo.builder().build(); - assertNotNull(batchConstructionInfo); - assertEquals(0, batchConstructionInfo.getAmount()); - assertNull(batchConstructionInfo.getMaterialId()); - assertTrue(batchConstructionInfo.getPropertyValues().isEmpty()); - } - - @Test - @UnitTestMethod(target = BatchConstructionInfo.Builder.class, name = "setAmount", args = { double.class }) - public void testSetAmount() { - for (int i = 0; i < 10; i++) { - double amount = 1000 * i; - BatchConstructionInfo batchConstructionInfo = BatchConstructionInfo .builder()// - .setAmount(amount)// - .build();// - assertEquals(amount, batchConstructionInfo.getAmount()); - } - } - - @Test - @UnitTestMethod(target = BatchConstructionInfo.Builder.class, name = "setMaterialId", args = { MaterialId.class }) - public void testSetMaterialId() { - for (TestMaterialId testMaterialId : TestMaterialId.values()) { - BatchConstructionInfo batchConstructionInfo = BatchConstructionInfo .builder()// - .setMaterialId(testMaterialId)// - .build();// - assertEquals(testMaterialId, batchConstructionInfo.getMaterialId()); - } - - } - - @Test - @UnitTestMethod(target = BatchConstructionInfo.Builder.class, name = "setPropertyValue", args = { BatchPropertyId.class, Object.class }) - public void testSetPropertyValue() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1174771995707697849L); - BatchConstructionInfo.Builder builder = BatchConstructionInfo.builder();// - for (TestMaterialId testMaterialId : TestMaterialId.values()) { - builder.setMaterialId(testMaterialId);// - Map expectedPropertyValues = new LinkedHashMap<>(); - for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.getTestBatchPropertyIds(testMaterialId)) { - Object value = testBatchPropertyId.getRandomPropertyValue(randomGenerator); - expectedPropertyValues.put(testBatchPropertyId, value); - builder.setPropertyValue(testBatchPropertyId, value);// - } - BatchConstructionInfo batchConstructionInfo = builder.build();// - assertEquals(testMaterialId, batchConstructionInfo.getMaterialId()); - - Map propertyValues = batchConstructionInfo.getPropertyValues(); - assertEquals(expectedPropertyValues, propertyValues); - } - - } - - @Test - @UnitTestMethod(name = "getMaterialId", args = {}) - public void testGetMaterialId() { - - for (TestMaterialId testMaterialId : TestMaterialId.values()) { - BatchConstructionInfo batchConstructionInfo = BatchConstructionInfo .builder()// - .setMaterialId(testMaterialId)// - .build();// - assertEquals(testMaterialId, batchConstructionInfo.getMaterialId()); - } - } - - @Test - @UnitTestMethod(name = "getAmount", args = {}) - public void testGetAmount() { - for (int i = 0; i < 10; i++) { - double amount = 1000 * i; - BatchConstructionInfo batchConstructionInfo = BatchConstructionInfo .builder()// - .setAmount(amount)// - .build();// - assertEquals(amount, batchConstructionInfo.getAmount()); - } - } - - @Test - @UnitTestMethod(name = "getPropertyValues", args = {}) - public void testGetPropertyValues() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1805920219436314340L); - BatchConstructionInfo.Builder builder = BatchConstructionInfo.builder();// - for (TestMaterialId testMaterialId : TestMaterialId.values()) { - builder.setMaterialId(testMaterialId);// - Map expectedPropertyValues = new LinkedHashMap<>(); - for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.getTestBatchPropertyIds(testMaterialId)) { - Object value = testBatchPropertyId.getRandomPropertyValue(randomGenerator); - expectedPropertyValues.put(testBatchPropertyId, value); - builder.setPropertyValue(testBatchPropertyId, value);// - } - BatchConstructionInfo batchConstructionInfo = builder.build();// - assertEquals(testMaterialId, batchConstructionInfo.getMaterialId()); - - Map propertyValues = batchConstructionInfo.getPropertyValues(); - assertEquals(expectedPropertyValues, propertyValues); - } - } - -} diff --git a/gcm3/src/test/java/plugins/materials/support/AT_BatchId.java b/gcm3/src/test/java/plugins/materials/support/AT_BatchId.java deleted file mode 100644 index b3846b77e..000000000 --- a/gcm3/src/test/java/plugins/materials/support/AT_BatchId.java +++ /dev/null @@ -1,14 +0,0 @@ -package plugins.materials.support; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - -@UnitTest(target = BatchId.class) -public class AT_BatchId { - - @Test - public void test() { - //nothing to test - } -} diff --git a/gcm3/src/test/java/plugins/materials/support/AT_BatchPropertyId.java b/gcm3/src/test/java/plugins/materials/support/AT_BatchPropertyId.java deleted file mode 100644 index 634ca1df9..000000000 --- a/gcm3/src/test/java/plugins/materials/support/AT_BatchPropertyId.java +++ /dev/null @@ -1,13 +0,0 @@ -package plugins.materials.support; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - -@UnitTest(target = BatchPropertyId.class) -public class AT_BatchPropertyId { - @Test - public void test() { - // nothing to test - } -} diff --git a/gcm3/src/test/java/plugins/materials/support/AT_MaterialId.java b/gcm3/src/test/java/plugins/materials/support/AT_MaterialId.java deleted file mode 100644 index b086d31e5..000000000 --- a/gcm3/src/test/java/plugins/materials/support/AT_MaterialId.java +++ /dev/null @@ -1,14 +0,0 @@ -package plugins.materials.support; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - -@UnitTest(target = MaterialId.class) -public class AT_MaterialId { - - @Test - public void test() { - // nothing to test - } -} diff --git a/gcm3/src/test/java/plugins/materials/support/AT_MaterialsError.java b/gcm3/src/test/java/plugins/materials/support/AT_MaterialsError.java deleted file mode 100644 index 097b62a4c..000000000 --- a/gcm3/src/test/java/plugins/materials/support/AT_MaterialsError.java +++ /dev/null @@ -1,30 +0,0 @@ -package plugins.materials.support; - -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = MaterialsError.class) -public class AT_MaterialsError { - - @Test - @UnitTestMethod(name = "getDescription", args = {}) - public void test() { - //show that each description is a unique, non-null and non-empty string - Set descriptions = new LinkedHashSet<>(); - for(MaterialsError materialsError : MaterialsError.values()) { - String description = materialsError.getDescription(); - assertNotNull(description,"null description for "+materialsError); - assertTrue(description.length()>0, "empty string for "+materialsError); - boolean unique = descriptions.add(description); - assertTrue(unique,"description for "+materialsError+" is not unique"); - } - } -} diff --git a/gcm3/src/test/java/plugins/materials/support/AT_MaterialsProducerId.java b/gcm3/src/test/java/plugins/materials/support/AT_MaterialsProducerId.java deleted file mode 100644 index 0553b1657..000000000 --- a/gcm3/src/test/java/plugins/materials/support/AT_MaterialsProducerId.java +++ /dev/null @@ -1,14 +0,0 @@ -package plugins.materials.support; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - -@UnitTest(target = MaterialsProducerId.class) -public class AT_MaterialsProducerId { - - @Test - public void test() { - //nothing to test - } -} diff --git a/gcm3/src/test/java/plugins/materials/support/AT_MaterialsProducerPropertyId.java b/gcm3/src/test/java/plugins/materials/support/AT_MaterialsProducerPropertyId.java deleted file mode 100644 index 90d67885e..000000000 --- a/gcm3/src/test/java/plugins/materials/support/AT_MaterialsProducerPropertyId.java +++ /dev/null @@ -1,14 +0,0 @@ -package plugins.materials.support; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - -@UnitTest(target = MaterialsProducerPropertyId.class) -public class AT_MaterialsProducerPropertyId { - - @Test - public void test() { - //nothing to test - } -} diff --git a/gcm3/src/test/java/plugins/materials/support/AT_StageId.java b/gcm3/src/test/java/plugins/materials/support/AT_StageId.java deleted file mode 100644 index 6832f9353..000000000 --- a/gcm3/src/test/java/plugins/materials/support/AT_StageId.java +++ /dev/null @@ -1,16 +0,0 @@ -package plugins.materials.support; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - - -@UnitTest(target = StageId.class) -public class AT_StageId { - - @Test - public void test() { - //nothing to test - } - -} diff --git a/gcm3/src/test/java/plugins/materials/testsupport/AT_MaterialsActionSupport.java b/gcm3/src/test/java/plugins/materials/testsupport/AT_MaterialsActionSupport.java deleted file mode 100644 index 0abbb382b..000000000 --- a/gcm3/src/test/java/plugins/materials/testsupport/AT_MaterialsActionSupport.java +++ /dev/null @@ -1,15 +0,0 @@ -package plugins.materials.testsupport; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - -@UnitTest(target = MaterialsActionSupport.class) -public class AT_MaterialsActionSupport { - - @Test - public void test() { - //place holder - } - -} diff --git a/gcm3/src/test/java/plugins/materials/testsupport/AT_TestBatchPropertyId.java b/gcm3/src/test/java/plugins/materials/testsupport/AT_TestBatchPropertyId.java deleted file mode 100644 index 43fe9a670..000000000 --- a/gcm3/src/test/java/plugins/materials/testsupport/AT_TestBatchPropertyId.java +++ /dev/null @@ -1,92 +0,0 @@ -package plugins.materials.testsupport; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import plugins.materials.support.BatchPropertyId; -import plugins.util.properties.PropertyDefinition; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.random.RandomGeneratorProvider; - -@UnitTest(target = TestBatchPropertyId.class) -public class AT_TestBatchPropertyId { - - @Test - @UnitTestMethod(name = "getPropertyDefinition", args = {}) - public void testGetPropertyDefinition() { - for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.values()) { - PropertyDefinition propertyDefinition = testBatchPropertyId.getPropertyDefinition(); - assertNotNull(propertyDefinition); - assertTrue(propertyDefinition.getDefaultValue().isPresent()); - } - } - - @Test - @UnitTestMethod(name = "getTestMaterialId", args = {}) - public void testGetTestMaterialId() { - for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.values()) { - assertNotNull(testBatchPropertyId.getTestMaterialId()); - } - } - - @Test - @UnitTestMethod(name = "getTestBatchPropertyIds", args = { TestMaterialId.class }) - public void testGetTestBatchPropertyIds() { - for (TestMaterialId testMaterialId : TestMaterialId.values()) { - Set expectedBatchPropertyIds = new LinkedHashSet<>(); - for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.values()) { - if (testMaterialId.equals(testBatchPropertyId.getTestMaterialId())) { - expectedBatchPropertyIds.add(testBatchPropertyId); - } - } - Set actualBatchPropertyIds = TestBatchPropertyId.getTestBatchPropertyIds(testMaterialId); - assertEquals(expectedBatchPropertyIds, actualBatchPropertyIds); - } - } - - @Test - @UnitTestMethod(name = "getUnknownBatchPropertyId", args = { TestMaterialId.class }) - public void testGetUnknownBatchPropertyId() { - BatchPropertyId unknownBatchPropertyId = TestBatchPropertyId.getUnknownBatchPropertyId(); - assertNotNull(unknownBatchPropertyId); - for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.values()) { - assertNotEquals(testBatchPropertyId, unknownBatchPropertyId); - } - BatchPropertyId unknownBatchPropertyId2 = TestBatchPropertyId.getUnknownBatchPropertyId(); - assertNotEquals(unknownBatchPropertyId, unknownBatchPropertyId2); - } - - @Test - @UnitTestMethod(name = "getRandomMutableBatchPropertyId", args = { TestMaterialId.class, RandomGenerator.class }) - public void testGetRandomMutableBatchPropertyId() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7432312917768892660L); - for (TestMaterialId testMaterialId : TestMaterialId.values()) { - TestBatchPropertyId batchPropertyId = TestBatchPropertyId.getRandomMutableBatchPropertyId(testMaterialId, randomGenerator); - assertNotNull(batchPropertyId); - assertEquals(testMaterialId, batchPropertyId.getTestMaterialId()); - } - } - - @Test - @UnitTestMethod(name = "getRandomPropertyValue", args = { RandomGenerator.class }) - public void testGetRandomPropertyValue() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2839187347342327244L); - for (TestBatchPropertyId testBatchPropertyId : TestBatchPropertyId.values()) { - PropertyDefinition propertyDefinition = testBatchPropertyId.getPropertyDefinition(); - for (int i = 0; i < 10; i++) { - Object propertyValue = testBatchPropertyId.getRandomPropertyValue(randomGenerator); - assertNotNull(propertyValue); - assertTrue(propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())); - } - } - } -} diff --git a/gcm3/src/test/java/plugins/materials/testsupport/AT_TestMaterialId.java b/gcm3/src/test/java/plugins/materials/testsupport/AT_TestMaterialId.java deleted file mode 100644 index 9a05818eb..000000000 --- a/gcm3/src/test/java/plugins/materials/testsupport/AT_TestMaterialId.java +++ /dev/null @@ -1,51 +0,0 @@ -package plugins.materials.testsupport; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import plugins.materials.support.MaterialId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.random.RandomGeneratorProvider; - -@UnitTest(target = TestMaterialId.class) -public class AT_TestMaterialId { - - @Test - @UnitTestMethod(name = "getRandomMaterialId", args = { RandomGenerator.class }) - public void testGetRandomMaterialId() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3905846017447134736L); - for (int i = 0; i < 10; i++) { - assertNotNull(TestMaterialId.getRandomMaterialId(randomGenerator)); - } - } - - @Test - @UnitTestMethod(name = "size", args = {}) - public void testSize() { - assertEquals(TestMaterialId.values().length, TestMaterialId.size()); - } - - @Test - @UnitTestMethod(name = "next", args = {}) - public void testNext() { - TestMaterialId[] values = TestMaterialId.values(); - for (int i = 0; i < values.length; i++) { - assertEquals(values[(i + 1) % values.length], values[i].next()); - } - } - - @Test - @UnitTestMethod(name = "getUnknownMaterialId", args = {}) - public void testGetUnknownMaterialId() { - MaterialId unknownMaterialId = TestMaterialId.getUnknownMaterialId(); - assertNotNull(unknownMaterialId); - MaterialId unknownMaterialId2 = TestMaterialId.getUnknownMaterialId(); - assertNotEquals(unknownMaterialId, unknownMaterialId2); - } - -} diff --git a/gcm3/src/test/java/plugins/materials/testsupport/AT_TestMaterialsProducerId.java b/gcm3/src/test/java/plugins/materials/testsupport/AT_TestMaterialsProducerId.java deleted file mode 100644 index ab92bd754..000000000 --- a/gcm3/src/test/java/plugins/materials/testsupport/AT_TestMaterialsProducerId.java +++ /dev/null @@ -1,50 +0,0 @@ -package plugins.materials.testsupport; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import plugins.materials.support.MaterialsProducerId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.random.RandomGeneratorProvider; - -@UnitTest(target = TestMaterialsProducerId.class) -public class AT_TestMaterialsProducerId { - - @Test - @UnitTestMethod(name = "getRandomMaterialsProducerId", args = { RandomGenerator.class }) - public void testGetRandomMaterialsProducerId() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2141886758156469650L); - for (int i = 0; i < 10; i++) { - assertNotNull(TestMaterialsProducerId.getRandomMaterialsProducerId(randomGenerator)); - } - } - - @Test - @UnitTestMethod(name = "size", args = {}) - public void testSize() { - assertEquals(TestMaterialsProducerId.values().length, TestMaterialId.size()); - } - - @Test - @UnitTestMethod(name = "next", args = {}) - public void testNext() { - TestMaterialsProducerId[] values = TestMaterialsProducerId.values(); - for (int i = 0; i < values.length; i++) { - assertEquals(values[(i + 1) % values.length], values[i].next()); - } - } - - @Test - @UnitTestMethod(name = "getUnknownMaterialsProducerId", args = {}) - public void testGetUnknownMaterialsProducerId() { - MaterialsProducerId unknownMaterialsProducerId = TestMaterialsProducerId.getUnknownMaterialsProducerId(); - assertNotNull(unknownMaterialsProducerId); - MaterialsProducerId unknownMaterialsProducerId2 = TestMaterialsProducerId.getUnknownMaterialsProducerId(); - assertNotEquals(unknownMaterialsProducerId, unknownMaterialsProducerId2); - } -} diff --git a/gcm3/src/test/java/plugins/materials/testsupport/AT_TestMaterialsProducerPropertyId.java b/gcm3/src/test/java/plugins/materials/testsupport/AT_TestMaterialsProducerPropertyId.java deleted file mode 100644 index 3f308c644..000000000 --- a/gcm3/src/test/java/plugins/materials/testsupport/AT_TestMaterialsProducerPropertyId.java +++ /dev/null @@ -1,91 +0,0 @@ -package plugins.materials.testsupport; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import plugins.materials.support.MaterialsProducerPropertyId; -import plugins.util.properties.PropertyDefinition; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.random.RandomGeneratorProvider; - -@UnitTest(target = TestMaterialsProducerPropertyId.class) -public class AT_TestMaterialsProducerPropertyId { - - @Test - @UnitTestMethod(name = "getPropertyDefinition", args = {}) - public void testGetPropertyDefinition() { - for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId.values()) { - PropertyDefinition propertyDefinition = testMaterialsProducerPropertyId.getPropertyDefinition(); - assertNotNull(propertyDefinition); - assertTrue(propertyDefinition.getDefaultValue().isPresent()); - } - } - - @Test - @UnitTestMethod(name = "getUnknownMaterialsProducerPropertyId", args = {}) - public void testGetUnknownMaterialsProducerPropertyId() { - MaterialsProducerPropertyId unknownMaterialsProducerPropertyId = TestMaterialsProducerPropertyId.getUnknownMaterialsProducerPropertyId(); - assertNotNull(unknownMaterialsProducerPropertyId); - for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId.values()) { - assertNotEquals(testMaterialsProducerPropertyId, unknownMaterialsProducerPropertyId); - } - MaterialsProducerPropertyId unknownMaterialsProducerPropertyId2 = TestMaterialsProducerPropertyId.getUnknownMaterialsProducerPropertyId(); - assertNotEquals(unknownMaterialsProducerPropertyId, unknownMaterialsProducerPropertyId2); - - } - - @Test - @UnitTestMethod(name = "getRandomPropertyValue", args = { RandomGenerator.class }) - public void testGetRandomPropertyValue() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7973900878959109442L); - - for (TestMaterialsProducerPropertyId testMaterialsProducerPropertyId : TestMaterialsProducerPropertyId.values()) { - PropertyDefinition propertyDefinition = testMaterialsProducerPropertyId.getPropertyDefinition(); - for (int i = 0; i < 10; i++) { - Object value = testMaterialsProducerPropertyId.getRandomPropertyValue(randomGenerator); - assertNotNull(value); - assertTrue(propertyDefinition.getType().isAssignableFrom(value.getClass())); - } - } - - } - - @Test - @UnitTestMethod(name = "getRandomMaterialsProducerPropertyId", args = { RandomGenerator.class }) - public void testGetRandomMaterialsProducerPropertyId() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5963531689679394818L); - - for (int i = 0; i < 10; i++) { - TestMaterialsProducerPropertyId producerPropertyId = TestMaterialsProducerPropertyId.getRandomMaterialsProducerPropertyId(randomGenerator); - assertNotNull(producerPropertyId); - } - - } - - @Test - @UnitTestMethod(name = "getRandomMutableMaterialsProducerPropertyId", args = { RandomGenerator.class }) - public void testGetRandomMutableMaterialsProducerPropertyId() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8476649750185982818L); - for (int i = 0; i < 10; i++) { - TestMaterialsProducerPropertyId producerPropertyId = TestMaterialsProducerPropertyId.getRandomMutableMaterialsProducerPropertyId(randomGenerator); - assertNotNull(producerPropertyId); - assertTrue(producerPropertyId.getPropertyDefinition().propertyValuesAreMutable()); - } - - - } - - @Test - @UnitTestMethod(name = "size", args = {}) - public void testSize() { - assertEquals(TestMaterialsProducerPropertyId.values().length, TestMaterialsProducerPropertyId.size()); - } -} diff --git a/gcm3/src/test/java/plugins/partitions/AT_PartitionsPlugin.java b/gcm3/src/test/java/plugins/partitions/AT_PartitionsPlugin.java deleted file mode 100644 index 71e2bd37a..000000000 --- a/gcm3/src/test/java/plugins/partitions/AT_PartitionsPlugin.java +++ /dev/null @@ -1,37 +0,0 @@ -package plugins.partitions; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import nucleus.Plugin; -import nucleus.PluginId; -import plugins.people.PeoplePluginId; -import plugins.stochastics.StochasticsPluginId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = PartitionsPlugin.class) -public final class AT_PartitionsPlugin { - - @Test - @UnitTestMethod(name = "getPartitionsPlugin", args = {}) - public void testGetPartitionsPlugin() { - Plugin partitionsPlugin = PartitionsPlugin.getPartitionsPlugin(); - - assertTrue(partitionsPlugin.getPluginDatas().isEmpty()); - - assertEquals(PartitionsPluginId.PLUGIN_ID, partitionsPlugin.getPluginId()); - - Set expectedDependencies = new LinkedHashSet<>(); - expectedDependencies.add(PeoplePluginId.PLUGIN_ID); - expectedDependencies.add(StochasticsPluginId.PLUGIN_ID); - assertEquals(expectedDependencies, partitionsPlugin.getPluginDependencies()); - - } - -} diff --git a/gcm3/src/test/java/plugins/partitions/datamanagers/AT_PartitionsDataManager.java b/gcm3/src/test/java/plugins/partitions/datamanagers/AT_PartitionsDataManager.java deleted file mode 100644 index f3f84905f..000000000 --- a/gcm3/src/test/java/plugins/partitions/datamanagers/AT_PartitionsDataManager.java +++ /dev/null @@ -1,1727 +0,0 @@ -package plugins.partitions.datamanagers; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Random; -import java.util.Set; -import java.util.function.Function; - -import org.apache.commons.math3.random.RandomGenerator; -import org.apache.commons.math3.util.FastMath; -import org.junit.jupiter.api.Test; - -import nucleus.ActorContext; -import nucleus.DataManagerContext; -import nucleus.Plugin; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import plugins.partitions.support.Equality; -import plugins.partitions.support.Filter; -import plugins.partitions.support.LabelSet; -import plugins.partitions.support.LabelSetWeightingFunction; -import plugins.partitions.support.Partition; -import plugins.partitions.support.PartitionError; -import plugins.partitions.support.PartitionSampler; -import plugins.partitions.testsupport.PartitionsActionSupport; -import plugins.partitions.testsupport.attributes.AttributesDataManager; -import plugins.partitions.testsupport.attributes.support.AttributeFilter; -import plugins.partitions.testsupport.attributes.support.AttributeLabeler; -import plugins.partitions.testsupport.attributes.support.TestAttributeId; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.events.PersonImminentRemovalEvent; -import plugins.people.support.BulkPersonConstructionData; -import plugins.people.support.PersonConstructionData; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import plugins.stochastics.StochasticsDataManager; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; -import util.random.RandomGeneratorProvider; -import util.wrappers.MutableInteger; - -@UnitTest(target = PartitionsDataManager.class) -public final class AT_PartitionsDataManager { - - /* - * Assigns randomized values for all attributes to all people. Values are - * assigned to be consistent with the static labeling functions. - */ - private static void assignRandomAttributes(final ActorContext c) { - final PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - final StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - final RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - for (final PersonId personId : peopleDataManager.getPeople()) { - - boolean b = randomGenerator.nextBoolean(); - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, b); - - b = randomGenerator.nextBoolean(); - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_1, b); - - int i = randomGenerator.nextInt(100); - attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_0, i); - - i = randomGenerator.nextInt(100); - attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_1, i); - - double d = randomGenerator.nextDouble() * 100; - attributesDataManager.setAttributeValue(personId, TestAttributeId.DOUBLE_0, d); - - d = randomGenerator.nextDouble() * 100; - attributesDataManager.setAttributeValue(personId, TestAttributeId.DOUBLE_1, d); - - } - } - - private static Function INT_0_LABELFUNCTION = (value) -> { - final int v = (Integer) value; - return v % 3; - }; - - private static Function INT_1_LABELFUNCTION = (value) -> { - final int v = (Integer) value; - if (v < 40) { - return true; - } - return false; - }; - - private static Function DOUBLE_0_LABELFUNCTION = (value) -> { - final double v = (Double) value; - if (v < 33) { - return "A"; - } - if (v < 67) { - return "B"; - } - return "C"; - }; - - private static Function DOUBLE_1_LABELFUNCTION = (value) -> { - final double v = (Double) value; - return v < 90; - }; - - /* - * Creates a map from LabelSet to PersonId that covers all people who have - * an attribute value of true for BOOLEAN_0 and false for BOOLEAN_1, to be - * consistent with the filter used in the partition addition test. Label - * sets consist of labels for INT_0, INT_1, DOUBLE_0 and DOUBLE_1. - */ - private static Map> getExpectedStructure(final ActorContext c) { - final PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - final AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - final Map> expectedPeople = new LinkedHashMap<>(); - for (final PersonId personId : peopleDataManager.getPeople()) { - - final Boolean b0 = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); - final Boolean b1 = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_1); - if (b0 && !b1) { - - final Integer i0 = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_0); - final Object label_i0 = INT_0_LABELFUNCTION.apply(i0); - - final Integer i1 = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_1); - final Object label_i1 = INT_1_LABELFUNCTION.apply(i1); - - final Double d0 = attributesDataManager.getAttributeValue(personId, TestAttributeId.DOUBLE_0); - final Object label_d0 = DOUBLE_0_LABELFUNCTION.apply(d0); - - final Double d1 = attributesDataManager.getAttributeValue(personId, TestAttributeId.DOUBLE_1); - final Object label_d1 = DOUBLE_1_LABELFUNCTION.apply(d1); - - final LabelSet labelSet = LabelSet .builder()// - .setLabel(TestAttributeId.INT_0, label_i0)// - .setLabel(TestAttributeId.INT_1, label_i1)// - .setLabel(TestAttributeId.DOUBLE_0, label_d0)// - .setLabel(TestAttributeId.DOUBLE_1, label_d1)// - .build();// - - Set people = expectedPeople.get(labelSet); - if (people == null) { - people = new LinkedHashSet<>(); - expectedPeople.put(labelSet, people); - } - people.add(personId); - } - } - return expectedPeople; - - } - - /* - * Compares the expected alignment of people to label sets to the population - * partition's content via assertions. - */ - private static void showPartitionIsCorrect(final ActorContext c, final Map> expectedPartitionStructure, final Object key) { - - final PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); - - // derive the number of people in the expected partition structure - int expectedPersonCount = 0; - for (final LabelSet labelSet : expectedPartitionStructure.keySet()) { - final Set expectedPeople = expectedPartitionStructure.get(labelSet); - expectedPersonCount += expectedPeople.size(); - } - - // Show that the number of people in the partition matches the expected - // count of people. - final int actualPersonCount = partitionsDataManager.getPersonCount(key); - assertEquals(expectedPersonCount, actualPersonCount); - - /* - * Show that each label set in the expected structure is associated with - * the same people in the population partition. - * - * Since we know that the expected partition structure and the - * population partition have the same number of people and that no - * person can be associated with two label sets, we know there are no - * uncounted people in the population partition and thus the two data - * structures match. - */ - for (final LabelSet labelSet : expectedPartitionStructure.keySet()) { - final Set expectedPeople = expectedPartitionStructure.get(labelSet); - final List actualpeople = partitionsDataManager.getPeople(key, labelSet); - assertEquals(expectedPeople.size(), actualpeople.size()); - assertEquals(expectedPeople, new LinkedHashSet<>(actualpeople)); - } - } - - /* - * Removes the given number of people from the simulation, chosen at random. - * Person count may exceed the current population size. - */ - private static void removePeople(final ActorContext c, final int numberOfPeopleToRemove) { - final PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - final StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - final long seed = stochasticsDataManager.getRandomGenerator().nextLong(); - final Random random = new Random(seed); - final List people = peopleDataManager.getPeople(); - Collections.shuffle(people, random); - final int correctedNumberOfPeopleToRemove = FastMath.min(numberOfPeopleToRemove, people.size()); - for (int i = 0; i < correctedNumberOfPeopleToRemove; i++) { - peopleDataManager.removePerson(people.get(i)); - } - } - - /* - * Adds the given number of people to the simulation - */ - private static void addPeople(final ActorContext c, final int numberOfPeopleToAdd) { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - for (int i = 0; i < numberOfPeopleToAdd; i++) { - peopleDataManager.addPerson(PersonConstructionData.builder().build()); - } - } - - @Test - @UnitTestMethod(name = "addPartition", args = { Partition.class, Object.class }) - public void testAddPartition() { - - // Have the simulation initialized with 1000 people. Have an agent - // execute a partition addition and multiple changes to the population - // and their attributes to show that the partition resolver maintains - // the partition. - - PartitionsActionSupport.testConsumer(1000, 5127268948453841557L, (c) -> { - // get the partition data view - final PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); - - // initialize people's attributes - assignRandomAttributes(c); - - // create a key to use for a new partition - final Object key = new Object(); - - // show that the population partition does not yet exist - assertFalse(partitionsDataManager.partitionExists(key)); - - /* - * Add the partition. We will filter to select people who have - * BOOLEAN_0 as true and BOOLEAN_1 as false. The remaining - * attributes will be used to define the cells of the partition via - * the four static labeling functions defined in this class. - */ - final Filter filter0 = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); - final Filter filter1 = new AttributeFilter(TestAttributeId.BOOLEAN_1, Equality.EQUAL, false); - final Filter filter = filter0.and(filter1); - final Partition partition = Partition .builder()// - .setFilter(filter)// - .addLabeler(new AttributeLabeler(TestAttributeId.INT_0, INT_0_LABELFUNCTION))// - .addLabeler(new AttributeLabeler(TestAttributeId.INT_1, INT_1_LABELFUNCTION))// - .addLabeler(new AttributeLabeler(TestAttributeId.DOUBLE_0, DOUBLE_0_LABELFUNCTION))// - .addLabeler(new AttributeLabeler(TestAttributeId.DOUBLE_1, DOUBLE_1_LABELFUNCTION))// - .build();// - - partitionsDataManager.addPartition(partition, key); - - // show that the partition was added - assertTrue(partitionsDataManager.partitionExists(key)); - - /* - * Get the expected structure by examining each person and grouping - * them by the label sets that we expect to find in the partition - */ - Map> expectedPartitionStructure = getExpectedStructure(c); - - /* - * Show that the expected structure matches the actual structure of - * the partition - */ - showPartitionIsCorrect(c, expectedPartitionStructure, key); - - /* - * Perform various changes to the people and their attributes. - */ - removePeople(c, 100); - addPeople(c, 120); - assignRandomAttributes(c); - - /* - * Get the expected structure again now that there have been changes - * to people's attributes - */ - expectedPartitionStructure = getExpectedStructure(c); - - /* - * Show that the expected structure matches the actual structure of - * the partition and thus the partition resolver must be maintaining - * the partition as stated in the contract. - */ - showPartitionIsCorrect(c, expectedPartitionStructure, key); - - }); - - // if the key is already allocated to another population partition - PartitionsActionSupport.testConsumer(0, 1137046131619466337L, (c) -> { - - PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); - Object key = new Object(); - - partitionsDataManager.addPartition(Partition.builder().build(), key); - ContractException contractException = assertThrows(ContractException.class, () -> partitionsDataManager.addPartition(Partition.builder().build(), key)); - assertEquals(PartitionError.DUPLICATE_PARTITION, contractException.getErrorType()); - - }); - - // precondition: if the partition is null - PartitionsActionSupport.testConsumer(0, 1137046131619466337L, (c) -> { - - PartitionsDataManager partitionsDataManager = new PartitionsDataManager(); - Object key = new Object(); - - ContractException contractException = assertThrows(ContractException.class, () -> partitionsDataManager.addPartition(null, key)); - assertEquals(PartitionError.NULL_PARTITION, contractException.getErrorType()); - - }); - - // precondition: if the key is null - PartitionsActionSupport.testConsumer(0, 1137046131619466337L, (c) -> { - PartitionsDataManager partitionsDataManager = new PartitionsDataManager(); - ContractException contractException = assertThrows(ContractException.class, () -> partitionsDataManager.addPartition(Partition.builder().build(), null)); - assertEquals(PartitionError.NULL_PARTITION_KEY, contractException.getErrorType()); - - }); - - } - - @Test - @UnitTestMethod(name = "removePartition", args = { Object.class }) - public void testRemovePartition() { - - PartitionsActionSupport.testConsumer(0, 5767679585616452606L, (c) -> { - - PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); - Object key = new Object(); - - // show that the partition does not yet exist - assertFalse(partitionsDataManager.partitionExists(key)); - - Partition partition = Partition.builder().build(); - partitionsDataManager.addPartition(partition, key); - - // show that the partition was added - assertTrue(partitionsDataManager.partitionExists(key)); - - // show that partition is removed - partitionsDataManager.removePartition(key); - assertFalse(partitionsDataManager.partitionExists(key)); - - }); - } - - @Test - @UnitTestMethod(name = "partitionExists", args = { Object.class }) - public void testPartitionExists() { - - PartitionsActionSupport.testConsumer(0, 1968926333881399732L, (c) -> { - PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); - - // create containers to hold known and unknown keys - Set knownKeys = new LinkedHashSet<>(); - for (int i = 0; i < 10; i++) { - Object key = "key" + i; - knownKeys.add(key); - } - - Set unknownKeys = new LinkedHashSet<>(); - for (int i = 10; i < 20; i++) { - Object key = "key" + i; - unknownKeys.add(key); - } - - // add a partition for each key - for (Object key : knownKeys) { - Partition partition = Partition.builder().build(); - partitionsDataManager.addPartition(partition, key); - } - - // show that the known keys will have a partition - for (Object key : knownKeys) { - assertTrue(partitionsDataManager.partitionExists(key)); - } - - // show that the unknown keys will not have a partition - for (Object key : unknownKeys) { - assertFalse(partitionsDataManager.partitionExists(key)); - } - - // show that the null key has no partition - assertFalse(partitionsDataManager.partitionExists(null)); - - }); - } - - @Test - @UnitTestMethod(name = "contains", args = { PersonId.class, Object.class }) - public void testContains() { - - PartitionsActionSupport.testConsumer(100, 607630153604184177L, (c) -> { - PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - - // create a partition where half the population is in the partition - Object key = new Object(); - Partition partition = Partition .builder()// - .setFilter(new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true))// - .addLabeler(new AttributeLabeler(TestAttributeId.INT_0, (v) -> 3)).build();// - - partitionsDataManager.addPartition(partition, key); - - // change the BOOLEAN_0 randomly for every person - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - for (PersonId personId : peopleDataManager.getPeople()) { - boolean newValue = randomGenerator.nextBoolean(); - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, newValue); - } - - // show that there is at least one person in the partition and one - // person outside the partition - int personCountInPartition = partitionsDataManager.getPersonCount(key); - assertTrue(personCountInPartition < peopleDataManager.getPopulationCount()); - assertTrue(personCountInPartition > 0); - - // show that a person is in the partition if and only if their - // BOOLEAN_0 attribute is true - for (PersonId personId : peopleDataManager.getPeople()) { - boolean expectPersonInPartition = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); - boolean actualPersonInPartition = partitionsDataManager.contains(personId, key); - assertEquals(expectPersonInPartition, actualPersonInPartition); - } - - }); - } - - @Test - @UnitTestMethod(name = "contains", args = { PersonId.class, LabelSet.class, Object.class }) - public void testContains_LabelSet() { - - PartitionsActionSupport.testConsumer(100, 7338572401998066291L, (c) -> { - PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - - /* - * Define functions that will convert attribute values into labels - * for attributes INT_0, INT_1, DOUBLE_0, and DOUBLE_1. We will use - * these in the partition's labeling - */ - Function int_0_labelFunction = (value) -> { - int v = (Integer) value; - return v % 3; - }; - - Function int_1_labelFunction = (value) -> { - int v = (Integer) value; - if (v < 40) { - return true; - } - return false; - }; - - Function double_0_labelFunction = (value) -> { - double v = (Double) value; - if (v < 33) { - return "A"; - } - if (v < 67) { - return "B"; - } - return "C"; - }; - - Function double_1_labelFunction = (value) -> { - double v = (Double) value; - return v < 90; - }; - - // create a partition where half the population is in the partition - // with labeling - Object key = new Object(); - Partition partition = Partition .builder()// - .setFilter(new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true))// - .addLabeler(new AttributeLabeler(TestAttributeId.INT_0, int_0_labelFunction)).addLabeler(new AttributeLabeler(TestAttributeId.INT_1, int_1_labelFunction)) - .addLabeler(new AttributeLabeler(TestAttributeId.DOUBLE_0, double_0_labelFunction)) - .addLabeler(new AttributeLabeler(TestAttributeId.DOUBLE_1, double_1_labelFunction)).build();// - - partitionsDataManager.addPartition(partition, key); - - // alter people's attributes randomly - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - for (PersonId personId : peopleDataManager.getPeople()) { - int intValue = (int) (randomGenerator.nextDouble() * 100); - attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_0, intValue); - - intValue = (int) (randomGenerator.nextDouble() * 100); - attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_1, intValue); - - double doubleValue = randomGenerator.nextDouble() * 100; - attributesDataManager.setAttributeValue(personId, TestAttributeId.DOUBLE_0, doubleValue); - - doubleValue = randomGenerator.nextDouble() * 100; - attributesDataManager.setAttributeValue(personId, TestAttributeId.DOUBLE_1, doubleValue); - - boolean booleanValue = randomGenerator.nextBoolean(); - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, booleanValue); - - booleanValue = randomGenerator.nextBoolean(); - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_1, booleanValue); - - } - - // Create a label set to use in the contains query - LabelSet queryLabelSet = LabelSet .builder()// - .setLabel(TestAttributeId.INT_0, 0)// - .setLabel(TestAttributeId.DOUBLE_0, "A")// - .build();// - - /* - * Show that a person is in the partition under the query label if - * and only if their BOOLEAN_0 attribute is true and their INT_0, - * and DOUBLE_0 labels are 0 and A - */ - for (PersonId personId : peopleDataManager.getPeople()) { - boolean contained = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); - - Integer int0Value = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_0); - Integer int0Label = (Integer) int_0_labelFunction.apply(int0Value); - - Double double0Value = attributesDataManager.getAttributeValue(personId, TestAttributeId.DOUBLE_0); - String double0Label = (String) double_0_labelFunction.apply(double0Value); - - boolean expectPersonInPartitionUnderLabel = contained && int0Label.equals(0) && double0Label.equals("A"); - - boolean actualPersonInPartitionUnderLabel = partitionsDataManager.contains(personId, queryLabelSet, key); - assertEquals(expectPersonInPartitionUnderLabel, actualPersonInPartitionUnderLabel); - } - - // precondition tests - PersonId personId = new PersonId(0); - assertTrue(peopleDataManager.personExists(personId)); - - PersonId unknownPersonId = new PersonId(10000000); - assertFalse(peopleDataManager.personExists(unknownPersonId)); - - Object unknownKey = new Object(); - - LabelSet badLabelSet = LabelSet.builder().setLabel(TestAttributeId.BOOLEAN_1, 0).build(); - - // if the person id is null - ContractException contractException = assertThrows(ContractException.class, () -> partitionsDataManager.contains(null, queryLabelSet, key)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - - // if the person id is unknown - contractException = assertThrows(ContractException.class, () -> partitionsDataManager.contains(unknownPersonId, queryLabelSet, key)); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - - // if the key is null - contractException = assertThrows(ContractException.class, () -> partitionsDataManager.contains(personId, queryLabelSet, null)); - assertEquals(PartitionError.NULL_PARTITION_KEY, contractException.getErrorType()); - - // if the key is unknown - contractException = assertThrows(ContractException.class, () -> partitionsDataManager.contains(personId, queryLabelSet, unknownKey)); - assertEquals(PartitionError.UNKNOWN_POPULATION_PARTITION_KEY, contractException.getErrorType()); - - // if the label set is null - contractException = assertThrows(ContractException.class, () -> partitionsDataManager.contains(personId, null, key)); - assertEquals(PartitionError.NULL_LABEL_SET, contractException.getErrorType()); - - // if the label contains a dimension not present in the partition - contractException = assertThrows(ContractException.class, () -> partitionsDataManager.contains(personId, badLabelSet, key)); - assertEquals(PartitionError.INCOMPATIBLE_LABEL_SET, contractException.getErrorType()); - - }); - } - - @Test - @UnitTestMethod(name = "getPeople", args = { Object.class }) - public void testGetPeople() { - - // initialized with 100 people - PartitionsActionSupport.testConsumer(100, 6033209037401060593L, (c) -> { - - // establish data views - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - - // create a container to hold the people we expect to find in the - // partition - Set expectedPeople = new LinkedHashSet<>(); - - // alter people's attributes randomly - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - for (PersonId personId : peopleDataManager.getPeople()) { - int intValue = (int) (randomGenerator.nextDouble() * 100); - attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_0, intValue); - - boolean booleanValue = randomGenerator.nextBoolean(); - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, booleanValue); - - if (booleanValue) { - expectedPeople.add(personId); - } - } - - // create a partition that will contain about half of the population - Object key = new Object(); - Partition partition = Partition .builder()// - .setFilter(new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true))// - .addLabeler(new AttributeLabeler(TestAttributeId.INT_0, (value) -> { - int v = (Integer) value; - return v / 10; - }))// - .build(); - partitionsDataManager.addPartition(partition, key); - - // get the people in the partition - List peopleInPartition = partitionsDataManager.getPeople(key); - Set actualPeople = new LinkedHashSet<>(peopleInPartition); - // show that the list of people contained no duplicates - assertEquals(peopleInPartition.size(), actualPeople.size()); - - // show that there were the expected people - assertEquals(expectedPeople, actualPeople); - - }); - } - - @Test - @UnitTestMethod(name = "getPeople", args = { Object.class, LabelSet.class }) - public void testGetPeople_LabelSet() { - // initialized with 100 people - PartitionsActionSupport.testConsumer(100, 7761046492495930843L, (c) -> { - - // establish data views - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - - // define a function that will convert an integer into another - // integer that will be used as a labeling function for the - // partition - Function attributeValueLabelingFunction = (value) -> { - int v = (Integer) value; - return v / 10; - }; - - // alter people's INT_0 and BOOLEAN_0 attributes randomly - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - for (PersonId personId : peopleDataManager.getPeople()) { - int intValue = (int) (randomGenerator.nextDouble() * 100); - attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_0, intValue); - - boolean booleanValue = randomGenerator.nextBoolean(); - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, booleanValue); - - } - - // create a partition that will contain about half of the population - Object key = new Object(); - Partition partition = Partition .builder()// - .setFilter(new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true))// - .addLabeler(new AttributeLabeler(TestAttributeId.INT_0, attributeValueLabelingFunction))// - .build(); - - partitionsDataManager.addPartition(partition, key); - - // create a container to hold the people we expect to find in the - // partition associated with labels - Map> expectedLabelToPeopleMap = new LinkedHashMap<>(); - - // fill the expectedLabelToPeopleMap - for (PersonId personId : peopleDataManager.getPeople()) { - // will the person pass the filter? - - Boolean booleanValue = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); - if (booleanValue) { - // determine the label that should be associated with the - // person - Integer intValue = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_0); - Object labelValue = attributeValueLabelingFunction.apply(intValue); - - // place the person in the expectedLabelToPeopleMap - Set expectedPeople = expectedLabelToPeopleMap.get(labelValue); - if (expectedPeople == null) { - expectedPeople = new LinkedHashSet<>(); - expectedLabelToPeopleMap.put(labelValue, expectedPeople); - } - expectedPeople.add(personId); - } - } - - // for each label in the expectedLabelToPeopleMap, get the people in - // the partition who match that label value - for (Object labelValue : expectedLabelToPeopleMap.keySet()) { - - // get the people we expect to find - Set expectedPeople = expectedLabelToPeopleMap.get(labelValue); - - // get the people that the partition associates with the label - LabelSet labelSet = LabelSet.builder().setLabel(TestAttributeId.INT_0, labelValue).build(); - List peopleInPartition = partitionsDataManager.getPeople(key, labelSet); - Set actualPeople = new LinkedHashSet<>(peopleInPartition); - - /* - * Show that the list of people returned from the population - * partition contains no duplicates - */ - assertEquals(peopleInPartition.size(), actualPeople.size()); - - // show that expected and actual people are equal - assertEquals(expectedPeople, actualPeople); - } - - }); - } - - @Test - @UnitTestMethod(name = "getPeopleCountMap", args = { Object.class, LabelSet.class }) - public void testGetPeopleCountMap() { - // initialized with 1000 people - PartitionsActionSupport.testConsumer(1000, 3993911184725585603L, (c) -> { - - // establish data views - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - - /* - * Define functions that will convert attribute values into labels - * for attributes INT_0, INT_1, DOUBLE_0, and DOUBLE_1. We will use - * these in the partition's labeling - */ - Function int_0_labelFunction = (value) -> { - int v = (Integer) value; - return v % 3; - }; - - Function int_1_labelFunction = (value) -> { - int v = (Integer) value; - if (v < 40) { - return true; - } - return false; - }; - - Function double_0_labelFunction = (value) -> { - double v = (Double) value; - if (v < 33) { - return "A"; - } - if (v < 67) { - return "B"; - } - return "C"; - }; - - Function double_1_labelFunction = (value) -> { - double v = (Double) value; - return v < 90; - }; - - // alter people's attributes randomly - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - for (PersonId personId : peopleDataManager.getPeople()) { - int intValue = (int) (randomGenerator.nextDouble() * 100); - attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_0, intValue); - - intValue = (int) (randomGenerator.nextDouble() * 100); - attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_1, intValue); - - double doubleValue = randomGenerator.nextDouble() * 100; - attributesDataManager.setAttributeValue(personId, TestAttributeId.DOUBLE_0, doubleValue); - - doubleValue = randomGenerator.nextDouble() * 100; - attributesDataManager.setAttributeValue(personId, TestAttributeId.DOUBLE_1, doubleValue); - - boolean booleanValue = randomGenerator.nextBoolean(); - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, booleanValue); - - booleanValue = randomGenerator.nextBoolean(); - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_1, booleanValue); - - } - - /* - * Create a partition that will contain about half of the population - * by filtering on BOOLEAN_0. We will partition on INT_0, INT_1, - * DOUBLE_0 and DOUBLE_1. Note that we do not use BOOLEAN_1 as part - * of the partition. - */ - Object key = new Object(); - Partition partition = Partition .builder()// - .setFilter(new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true))// - .addLabeler(new AttributeLabeler(TestAttributeId.INT_0, int_0_labelFunction))// - .addLabeler(new AttributeLabeler(TestAttributeId.INT_1, int_1_labelFunction))// - .addLabeler(new AttributeLabeler(TestAttributeId.DOUBLE_0, double_0_labelFunction))// - .addLabeler(new AttributeLabeler(TestAttributeId.DOUBLE_1, double_1_labelFunction))// - .build(); - - partitionsDataManager.addPartition(partition, key); - - /* - * Create a container to hold the number of people we expect to find - * in the partition for every label set that is associated with at - * least one person. - */ - Map expectedPartitionContentMap = new LinkedHashMap<>(); - - // fill the expectedLabelToPeopleMap - for (PersonId personId : peopleDataManager.getPeople()) { - // will the person pass the filter? - Boolean booleanValue = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); - if (booleanValue) { - - // construct the label set for this person we expect to - // exist in the partition - LabelSet.Builder labelSetBuilder = LabelSet.builder(); - - Integer intValue = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_0); - Object labelValue = int_0_labelFunction.apply(intValue); - labelSetBuilder.setLabel(TestAttributeId.INT_0, labelValue); - - intValue = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_1); - labelValue = int_1_labelFunction.apply(intValue); - labelSetBuilder.setLabel(TestAttributeId.INT_1, labelValue); - - Double doubleValue = attributesDataManager.getAttributeValue(personId, TestAttributeId.DOUBLE_0); - labelValue = double_0_labelFunction.apply(doubleValue); - labelSetBuilder.setLabel(TestAttributeId.DOUBLE_0, labelValue); - - doubleValue = attributesDataManager.getAttributeValue(personId, TestAttributeId.DOUBLE_1); - labelValue = double_1_labelFunction.apply(doubleValue); - labelSetBuilder.setLabel(TestAttributeId.DOUBLE_1, labelValue); - - LabelSet labelSet = labelSetBuilder.build(); - - // place the person in the expectedLabelToPeopleMap - MutableInteger mutableInteger = expectedPartitionContentMap.get(labelSet); - if (mutableInteger == null) { - mutableInteger = new MutableInteger(); - expectedPartitionContentMap.put(labelSet, mutableInteger); - } - mutableInteger.increment(); - } - } - - /* - * We will form our query using two of the four partition dimensions - * so that the maps returned by the queries will contain multiple - * members. - * - * We want to test create queries using INT_0 and DOUBLE_0 across - * all their known label values, but also want include some label - * values we known will not be present in the partition. - */ - - Set int_0_label_values = new LinkedHashSet<>(); - int_0_label_values.add(0); - int_0_label_values.add(1); - int_0_label_values.add(2); - int_0_label_values.add(3);// will not match any person - - Set double_0_label_values = new LinkedHashSet<>(); - double_0_label_values.add("A"); - double_0_label_values.add("B"); - double_0_label_values.add("C"); - double_0_label_values.add("D");// will not match any person - - for (Integer int_0_label_value : int_0_label_values) { - for (String double_0_label_value : double_0_label_values) { - - /* - * Create a label set for the query that does not contain - * all the attribute labels and has legitimate values for - * each dimension. - */ - LabelSet.Builder labelSetBuilder = LabelSet.builder(); - labelSetBuilder.setLabel(TestAttributeId.INT_0, int_0_label_value); - labelSetBuilder.setLabel(TestAttributeId.DOUBLE_0, double_0_label_value); - LabelSet queryLabelSet = labelSetBuilder.build(); - - /* - * We are only interested in those parts of the - * expectedPartitionContentMap that match the query's label - * set. - */ - Map expectedCountMap = new LinkedHashMap<>(); - for (LabelSet labelSet : expectedPartitionContentMap.keySet()) { - boolean isMatchingLabelSet = true; - for (Object dimension : queryLabelSet.getDimensions()) { - Optional queryLabel = queryLabelSet.getLabel(dimension); - Optional label = labelSet.getLabel(dimension); - if (!queryLabel.equals(label)) { - isMatchingLabelSet = false; - break; - } - } - if (isMatchingLabelSet) { - MutableInteger mutableInteger = expectedPartitionContentMap.get(labelSet); - expectedCountMap.put(labelSet, mutableInteger.getValue()); - } - } - - /* - * Show that the count map we receive from the partition - * matches our expectation - */ - Map actualCountMap = partitionsDataManager.getPeopleCountMap(key, queryLabelSet); - assertEquals(expectedCountMap, actualCountMap); - } - } - }); - - } - - @Test - @UnitTestMethod(name = "getPersonCount", args = { Object.class }) - public void getPersonCount() { - - // initialized with 100 people - PartitionsActionSupport.testConsumer(100, 1559429415782871174L, (c) -> { - - // establish data views - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - - // create a couter to hold the number people we expect to find in - // the - // partition - int expectedPeopleCount = 0; - - // alter people's attributes randomly - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - for (PersonId personId : peopleDataManager.getPeople()) { - int intValue = (int) (randomGenerator.nextDouble() * 100); - attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_0, intValue); - - boolean booleanValue = randomGenerator.nextBoolean(); - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, booleanValue); - if (booleanValue) { - expectedPeopleCount++; - } - } - - // create a partition that will contain about half of the population - Object key = new Object(); - Partition partition = Partition .builder()// - .setFilter(new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true))// - .addLabeler(new AttributeLabeler(TestAttributeId.INT_0, (value) -> { - int v = (Integer) value; - return v / 10; - }))// - .build(); - partitionsDataManager.addPartition(partition, key); - - // get the people in the partition - int actualCount = partitionsDataManager.getPersonCount(key); - - // show that there were the expected people - assertEquals(expectedPeopleCount, actualCount); - - }); - - } - - @Test - @UnitTestMethod(name = "getPersonCount", args = { Object.class, LabelSet.class }) - public void testGetPersonCount_LabelSet() { - // initialized with 100 people - PartitionsActionSupport.testConsumer(100, 3217787540697556531L, (c) -> { - - // establish data views - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - - // define a function that will convert an integer into another - // integer that will be used as a labeling function for the - // partition - Function attributeValueLabelingFunction = (value) -> { - int v = (Integer) value; - return v % 10; - }; - - // alter people's INT_0 and BOOLEAN_0 attributes randomly - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - for (PersonId personId : peopleDataManager.getPeople()) { - int intValue = (int) (randomGenerator.nextDouble() * 100); - attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_0, intValue); - - boolean booleanValue = randomGenerator.nextBoolean(); - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, booleanValue); - - } - - // create a partition that will contain about half of the population - Object key = new Object(); - Partition partition = Partition .builder()// - .setFilter(new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true))// - .addLabeler(new AttributeLabeler(TestAttributeId.INT_0, attributeValueLabelingFunction))// - .build(); - - partitionsDataManager.addPartition(partition, key); - - // create a container to hold the people we expect to find in the - // partition associated with labels - Map expectedPeopleCountMap = new LinkedHashMap<>(); - - // fill the expectedLabelToPeopleMap - for (PersonId personId : peopleDataManager.getPeople()) { - // will the person pass the filter? - Boolean booleanValue = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); - if (booleanValue) { - // determine the label that should be associated with the - // person - Integer intValue = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_0); - Object labelValue = attributeValueLabelingFunction.apply(intValue); - - // place the person in the expectedLabelToPeopleMap - MutableInteger mutableInteger = expectedPeopleCountMap.get(labelValue); - if (mutableInteger == null) { - mutableInteger = new MutableInteger(); - expectedPeopleCountMap.put(labelValue, mutableInteger); - } - mutableInteger.increment(); - } - } - - // for each label in the expectedLabelToPeopleMap, get the people in - // the partition who match that label value - for (Object labelValue : expectedPeopleCountMap.keySet()) { - - // get the people we expect to find - MutableInteger expectedPeopleCount = expectedPeopleCountMap.get(labelValue); - - // get the people that the partition associates with the label - LabelSet labelSet = LabelSet.builder().setLabel(TestAttributeId.INT_0, labelValue).build(); - int actualPersonCount = partitionsDataManager.getPersonCount(key, labelSet); - - // show that expected and actual people counts are equal - assertEquals(expectedPeopleCount.getValue(), actualPersonCount); - } - - }); - - } - - private static enum ExcludedPersonType { - NULL, MATCHING_MEMBER, NON_MATCHING_MEMBER, NON_MEMBER; - } - - @Test - @UnitTestMethod(name = "samplePartition", args = { Object.class, PartitionSampler.class }) - public void testSamplePartition_General() { - - /* - * Tests the sample mechanism under a variety of partition samplers. - * - * The partition is formed from 4 labeling functions over the attributes - * - * INT_0-> 0, 1, 2 - * - * INT_1 -> TRUE, FALSE - * - * DOUBLE_0 -> A, B, C - * - * DOUBLE_1 -> TRUE, FALSE - * - * Filtering for the partition is either on or off. The filter passes - * when the attribute BOOLEAN_0 is true. - * - * The partition sampler will optionally set its excluded person to - * null, a person not in the partition, a person in the partition who is - * not expected to match the sampler's label set and a person who does - * match the sampler's label set. - * - * The partition sampler will optionally use a label set. The label set - * will be composed of combinations of labels over INT_0 and DOUBLE_0, - * using label values that are associated with people and some that are - * not. - * - * The partition sampler will optionally use a weighting function. The - * weighting function will return 1 for any person having a label of - * TRUE for INT_1 and 0 otherwise. - * - * This test does not demonstrate precondition checks, proper use of - * random number generator ids, or the proper distribution of results - * aligned to the weighting function other that the simple binary - * alignment for the weighting function described above. - * - * Each combination is run with a randomly generated seed value. - * - */ - - Set int_0_label_values = new LinkedHashSet<>(); - int_0_label_values.add(0); - int_0_label_values.add(1); - int_0_label_values.add(2); - int_0_label_values.add(3);// will not match any person - - Set double_0_label_values = new LinkedHashSet<>(); - double_0_label_values.add("A"); - double_0_label_values.add("B"); - double_0_label_values.add("C"); - double_0_label_values.add("D");// will not match any person - - Set weightingFunctionValues = new LinkedHashSet<>(); - weightingFunctionValues.add(false); - weightingFunctionValues.add(true); - - Set useFilterValues = new LinkedHashSet<>(); - useFilterValues.add(false); - useFilterValues.add(true); - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7729976665156925181L); - - for (ExcludedPersonType excludedPersonType : ExcludedPersonType.values()) { - for (Boolean useWeightingFunction : weightingFunctionValues) { - for (Boolean useFilter : useFilterValues) { - long seed = randomGenerator.nextLong(); - executeSamplingTest(seed, useFilter, excludedPersonType, useWeightingFunction, null, null); - for (Integer int_0_label_value : int_0_label_values) { - for (String double_0_label_value : double_0_label_values) { - seed = randomGenerator.nextLong(); - executeSamplingTest(seed, useFilter, excludedPersonType, useWeightingFunction, int_0_label_value, double_0_label_value); - } - } - } - } - } - - } - - private void executeSamplingTest(long seed, Boolean useFilter, ExcludedPersonType excludedPersonType, Boolean useWeightingFunction, Integer int_0_label_value, String double_0_label_value) { - - PartitionsActionSupport.testConsumer(1000, seed, (c) -> { - - // remember to test with general and COMET to show they get - // different results? - - // establish data views - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - - /* - * Define functions that will convert attribute values into labels - * for attributes INT_0, INT_1, DOUBLE_0, and DOUBLE_1. We will use - * these in the partition's labeling - */ - Function int_0_labelFunction = (value) -> { - int v = (Integer) value; - return v % 3; - }; - - Function int_1_labelFunction = (value) -> { - int v = (Integer) value; - if (v < 40) { - return true; - } - return false; - }; - - Function double_0_labelFunction = (value) -> { - double v = (Double) value; - if (v < 33) { - return "A"; - } - if (v < 67) { - return "B"; - } - return "C"; - }; - - Function double_1_labelFunction = (value) -> { - double v = (Double) value; - return v < 90; - }; - - // determine the people in the world - List peopleInTheWorld = peopleDataManager.getPeople(); - - // alter people's attributes randomly - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - for (PersonId personId : peopleInTheWorld) { - int intValue = (int) (randomGenerator.nextDouble() * 100); - attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_0, intValue); - - intValue = (int) (randomGenerator.nextDouble() * 100); - attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_1, intValue); - - double doubleValue = randomGenerator.nextDouble() * 100; - attributesDataManager.setAttributeValue(personId, TestAttributeId.DOUBLE_0, doubleValue); - - doubleValue = randomGenerator.nextDouble() * 100; - attributesDataManager.setAttributeValue(personId, TestAttributeId.DOUBLE_1, doubleValue); - - boolean booleanValue = randomGenerator.nextBoolean(); - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, booleanValue); - - booleanValue = randomGenerator.nextBoolean(); - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_1, booleanValue); - - } - - /* - * Create a partition that may filter about half of the population - * on BOOLEAN_0. We will partition on INT_0, INT_1, DOUBLE_0 and - * DOUBLE_1. Note that we do not use BOOLEAN_1 as part of the - * partition. - */ - Object key = new Object(); - Partition.Builder partitionBuilder = Partition.builder(); - if (useFilter) { - partitionBuilder// - .setFilter(new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true));// - } - partitionBuilder// - .addLabeler(new AttributeLabeler(TestAttributeId.INT_0, int_0_labelFunction))// - .addLabeler(new AttributeLabeler(TestAttributeId.INT_1, int_1_labelFunction))// - .addLabeler(new AttributeLabeler(TestAttributeId.DOUBLE_0, double_0_labelFunction))// - .addLabeler(new AttributeLabeler(TestAttributeId.DOUBLE_1, double_1_labelFunction)); - - Partition partition = partitionBuilder.build(); - - partitionsDataManager.addPartition(partition, key); - - /* - * Create a label set for the query that does not contain all the - * attribute labels and has legitimate values for each dimension. - */ - LabelSet queryLabelSet = null; - if (int_0_label_value != null && double_0_label_value != null) { - LabelSet.Builder labelSetBuilder = LabelSet.builder(); - labelSetBuilder.setLabel(TestAttributeId.INT_0, int_0_label_value); - labelSetBuilder.setLabel(TestAttributeId.DOUBLE_0, double_0_label_value); - queryLabelSet = labelSetBuilder.build(); - } - - // determine the people in the partition - Set expectedPeopleInPartition = new LinkedHashSet<>(); - - for (PersonId personId : peopleInTheWorld) { - - if (useFilter) { - Boolean personInPartition = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); - if (personInPartition) { - expectedPeopleInPartition.add(personId); - } - } else { - expectedPeopleInPartition.add(personId); - } - - } - - // determine the people who will match the query label set - Set expectedPeopleMatchingQueryLabelSet = new LinkedHashSet<>(); - if (queryLabelSet != null) { - for (PersonId personId : expectedPeopleInPartition) { - // will the person pass the filter? - - Integer intValue = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_0); - Object labelValue = int_0_labelFunction.apply(intValue); - if (labelValue.equals(int_0_label_value)) { - Double doubleValue = attributesDataManager.getAttributeValue(personId, TestAttributeId.DOUBLE_0); - labelValue = double_0_labelFunction.apply(doubleValue); - if (labelValue.equals(double_0_label_value)) { - expectedPeopleMatchingQueryLabelSet.add(personId); - } - - } - - } - } else { - expectedPeopleMatchingQueryLabelSet.addAll(expectedPeopleInPartition); - } - - PartitionSampler.Builder partitionSamplerBuilder = PartitionSampler.builder(); - partitionSamplerBuilder.setLabelSet(queryLabelSet);// - - // Select the excluded person - PersonId excludedPersonId = null; - switch (excludedPersonType) { - case MATCHING_MEMBER: - for (PersonId personId : expectedPeopleMatchingQueryLabelSet) { - excludedPersonId = personId; - break; - } - - break; - case NON_MATCHING_MEMBER: - for (PersonId personId : expectedPeopleInPartition) { - if (!expectedPeopleMatchingQueryLabelSet.contains(personId)) { - excludedPersonId = personId; - break; - } - } - break; - - case NON_MEMBER: - if (useFilter) { - for (PersonId personId : peopleInTheWorld) { - if (!expectedPeopleInPartition.contains(personId)) { - excludedPersonId = personId; - break; - } - } - } - break; - - case NULL: - // do nothing - break; - default: - throw new RuntimeException("unhandled case: " + excludedPersonType); - } - partitionSamplerBuilder.setExcludedPerson(excludedPersonId); - - Set expectedPeopleMatchingPartitionSampler = new LinkedHashSet<>(expectedPeopleMatchingQueryLabelSet); - expectedPeopleMatchingPartitionSampler.remove(excludedPersonId); - - if (useWeightingFunction) { - Iterator iterator = expectedPeopleMatchingPartitionSampler.iterator(); - while (iterator.hasNext()) { - PersonId personId = iterator.next(); - Integer int_1_attributeValue = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_1); - Boolean passed = (Boolean) int_1_labelFunction.apply(int_1_attributeValue); - if (!passed) { - iterator.remove(); - } - } - - LabelSetWeightingFunction labelSetWeightingFunction = (c2, labelSet) -> { - Boolean value = (Boolean) labelSet.getLabel(TestAttributeId.INT_1).get(); - if (value) { - return 1; - } else { - return 0; - } - }; - partitionSamplerBuilder.setLabelSetWeightingFunction(labelSetWeightingFunction); - } - - PartitionSampler partitionSampler = partitionSamplerBuilder.build(); - - int samplingCount = FastMath.min(expectedPeopleMatchingQueryLabelSet.size() + 1, 10); - - for (int i = 0; i < samplingCount; i++) { - Optional optional = partitionsDataManager.samplePartition(key, partitionSampler); - if (optional.isPresent()) { - PersonId selectedPerson = optional.get(); - assertTrue(expectedPeopleMatchingPartitionSampler.contains(selectedPerson)); - } else { - assertTrue(expectedPeopleMatchingPartitionSampler.isEmpty()); - } - } - - }); - - } - - @Test - @UnitTestMethod(name = "samplePartition", args = { Object.class, PartitionSampler.class }) - public void testSamplePartition_PreconditionChecks() { - - // precondition: if the key is null - PartitionsActionSupport.testConsumer(10, 8368182028203057994L, (c) -> { - PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); - - Object key = new Object(); - Partition partition = Partition.builder().setFilter(Filter.allPeople()).build(); - partitionsDataManager.addPartition(partition, key); - - PartitionSampler partitionSampler = PartitionSampler.builder().build(); - - // first we show that the values we will be using are valid - assertNotNull(partitionsDataManager.samplePartition(key, partitionSampler)); - - ContractException contractException = assertThrows(ContractException.class, () -> partitionsDataManager.samplePartition(null, partitionSampler)); - assertEquals(PartitionError.NULL_PARTITION_KEY, contractException.getErrorType()); - - }); - - // precondition: if the key is unknown - PartitionsActionSupport.testConsumer(10, 2301450217287059237L, (c) -> { - PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); - - Object key = new Object(); - Partition partition = Partition.builder().setFilter(Filter.allPeople()).build(); - partitionsDataManager.addPartition(partition, key); - - PartitionSampler partitionSampler = PartitionSampler.builder().build(); - - Object unknownKey = new Object(); - - // first we show that the values we will be using are valid - assertNotNull(partitionsDataManager.samplePartition(key, partitionSampler)); - - ContractException contractException = assertThrows(ContractException.class, () -> partitionsDataManager.samplePartition(unknownKey, partitionSampler)); - assertEquals(PartitionError.UNKNOWN_POPULATION_PARTITION_KEY, contractException.getErrorType()); - - }); - - PartitionsActionSupport.testConsumer(10, 8837909864261179707L, (c) -> { - PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); - - Object key = new Object(); - Partition partition = Partition.builder().setFilter(Filter.allPeople()).build(); - partitionsDataManager.addPartition(partition, key); - - PartitionSampler partitionSampler = PartitionSampler.builder().build(); - - // first we show that the values we will be using are valid - assertNotNull(partitionsDataManager.samplePartition(key, partitionSampler)); - - // if the partition sampler is null - ContractException contractException = assertThrows(ContractException.class, () -> partitionsDataManager.samplePartition(key, null)); - assertEquals(PartitionError.NULL_PARTITION_SAMPLER, contractException.getErrorType()); - - }); - - /* - * precondition: if the partition sampler has a label set containing - * dimensions not present in the population partition - */ - PartitionsActionSupport.testConsumer(10, 1697817005173536231L, (c) -> { - PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); - - Object key = new Object(); - Partition partition = Partition.builder().setFilter(Filter.allPeople()).build(); - partitionsDataManager.addPartition(partition, key); - - PartitionSampler partitionSampler = PartitionSampler.builder().build(); - - LabelSet labelSet = LabelSet.builder().setLabel(TestAttributeId.INT_0, 15).build(); - PartitionSampler partitionSamplerWithBadDimension = PartitionSampler.builder().setLabelSet(labelSet).build(); - - // first we show that the values we will be using are valid - assertNotNull(partitionsDataManager.samplePartition(key, partitionSampler)); - - ContractException contractException = assertThrows(ContractException.class, () -> partitionsDataManager.samplePartition(key, partitionSamplerWithBadDimension)); - assertEquals(PartitionError.INCOMPATIBLE_LABEL_SET, contractException.getErrorType()); - - }); - - /* - * precondition: if the partition sampler has an excluded person that - * does not exist - */ - PartitionsActionSupport.testConsumer(10, 624346712512051803L, (c) -> { - PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); - - Object key = new Object(); - Partition partition = Partition.builder().setFilter(Filter.allPeople()).build(); - partitionsDataManager.addPartition(partition, key); - - PartitionSampler partitionSampler = PartitionSampler.builder().build(); - - PartitionSampler partitionSamplerWithUnknownExcludedPerson = PartitionSampler.builder().setExcludedPerson(new PersonId(10000)).build(); - - // first we show that the values we will be using are valid - assertNotNull(partitionsDataManager.samplePartition(key, partitionSampler)); - - ContractException contractException = assertThrows(ContractException.class, () -> partitionsDataManager.samplePartition(key, partitionSamplerWithUnknownExcludedPerson)); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - - }); - - } - - @Test - @UnitTestMethod(name = "init", args = { DataManagerContext.class }) - public void testPartitionDataManagerInitialization() { - PartitionsActionSupport.testConsumer(0, 2954766214498605129L, (c) -> { - PartitionsDataManager dataManager = c.getDataManager(PartitionsDataManager.class); - assertNotNull(dataManager); - }); - } - - @Test - @UnitTestMethod(name = "init", args = { DataManagerContext.class }) - public void testPersonAdditionEvent() { - PartitionsActionSupport.testConsumer(100, 6964380012813498875L, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); - - /* - * Create keys for the two population partitions. One that accepts - * people with attribute BOOLEAN_0 = true and the other with - * BOOLEAN_0 = false. - */ - Object key1 = new Object(); - Object key2 = new Object(); - - // add the partitions - Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); - Partition partition1 = Partition.builder().setFilter(filter).build(); - partitionsDataManager.addPartition(partition1, key1); - - filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, false); - Partition partition2 = Partition.builder().setFilter(filter).build(); - partitionsDataManager.addPartition(partition2, key2); - - // add a new person, by default they will have BOOLEAN_0 = false - // determine the person id of the person just added - PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); - - // show that the person is not a member of partition 1 - assertFalse(partitionsDataManager.contains(personId, key1)); - - // show that the person is a member of partition 2 - assertTrue(partitionsDataManager.contains(personId, key2)); - - }); - } - - @Test - @UnitTestMethod(name = "init", args = { DataManagerContext.class }) - public void testPersonImminentRemovalEvent() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - /* - * Create a key for a partition of interest that will contain a person - * we are about to delete - */ - Object key = new Object(); - - /* - * Add an agent that will create a partition that will contain 10 people - * of interest who will be removed later. - */ - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - - // select 10 people - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); - List people = peopleDataManager.getPeople(); - List peopleOfInterest = new ArrayList<>(); - for (int i = 0; i < 10; i++) { - peopleOfInterest.add(people.get(i)); - } - - /* - * Give these people an attribute BOOLEAN_0 a value of true so they - * will be included in the partition - */ - for (PersonId personId : peopleOfInterest) { - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, true); - } - - /* - * Create a partition that will include the people of interest - */ - Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); - Partition partition = Partition.builder().setFilter(filter).build(); - partitionsDataManager.addPartition(partition, key); - - // show that the partition does contain the people of interest - List actualPeople = partitionsDataManager.getPeople(key); - assertEquals(peopleOfInterest.size(), actualPeople.size()); - assertEquals(new LinkedHashSet<>(peopleOfInterest), new LinkedHashSet<>(actualPeople)); - - })); - - /* - * Create an observer that subscribes to the - * PersonImminentRemovalEvent. This will be used to show that - * a report or any other observer can still see a person and their - * membership in a partition even though the removal of the person is - * already underway. - * - * The report will record the ids of the people who were in the removal - * process - */ - List peopleVerifiedByReport = new ArrayList<>(); - - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { - c.subscribe(PersonImminentRemovalEvent.class, (c2, e) -> { - - PersonId personId = e.getPersonId(); - - // show that the person is still in the partition - PartitionsDataManager partitionsDataManager = c2.getDataManager(PartitionsDataManager.class); - assertTrue(partitionsDataManager.contains(personId, key)); - - // add the person to the verified list for later use - peopleVerifiedByReport.add(personId); - - }); - })); - - /* - * Have the agent remove the people who are in the partition from the - * simulation. The people will temporarily remain in the simulation and - * will only leave when the planning system moves to the next plan. - */ - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - - // Remove from the simulation the people who are in the partition - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); - List people = partitionsDataManager.getPeople(key); - for (PersonId personId : people) { - peopleDataManager.removePerson(personId); - } - - // show that the people still exist - - for (PersonId personId : people) { - assertTrue(peopleDataManager.personExists(personId)); - } - List peopleImmediatelyAfterRemoval = partitionsDataManager.getPeople(key); - assertEquals(people, peopleImmediatelyAfterRemoval); - - })); - - /* - * Have the agent verify that the people are gone and that the partition - * no longer contains them. Note that this plan is for the same time as - * the plan above but is guaranteed to execute after that plan. - */ - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - /* - * Show that the report, as an observer of the removals, was able to - * observe each removal and still perceived each person as being a - * member of the partition. - */ - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - assertEquals(10, peopleVerifiedByReport.size()); - - // show that each of these people is no longer in the simulation - for (PersonId personId : peopleVerifiedByReport) { - assertFalse(peopleDataManager.personExists(personId)); - } - - // show that the partition is empty - PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); - assertEquals(0, partitionsDataManager.getPersonCount(key)); - - })); - - // build and add the action plugin to the engine - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - PartitionsActionSupport.testConsumers(100, 6406306513403641718L, testPlugin); - } - - @Test - @UnitTestMethod(name = "init", args = { DataManagerContext.class }) - public void testBulkPersonAdditionEvent() { - PartitionsActionSupport.testConsumer(100, 2561425586247460069L, (c) -> { - PartitionsDataManager partitionsDataManager = c.getDataManager(PartitionsDataManager.class); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - /* - * Create keys for the two population partitions. One that accepts - * people with attribute BOOLEAN_0 = true and the other with - * BOOLEAN_0 = false. - */ - Object key1 = new Object(); - Object key2 = new Object(); - - // add the partitions - Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); - Partition partition1 = Partition.builder().setFilter(filter).build(); - partitionsDataManager.addPartition(partition1, key1); - - filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, false); - Partition partition2 = Partition.builder().setFilter(filter).build(); - partitionsDataManager.addPartition(partition2, key2); - - // determine the person ids of the people before the bulk addition - List priorPeople = peopleDataManager.getPeople(); - - // add three new people, by default they will have BOOLEAN_0 = false - PersonConstructionData.Builder personBuilder = PersonConstructionData.builder(); - BulkPersonConstructionData bulkPersonConstructionData = BulkPersonConstructionData.builder().add(personBuilder.build()).add(personBuilder.build()).add(personBuilder.build()).build(); - peopleDataManager.addBulkPeople(bulkPersonConstructionData); - - // determine the new people who were added - List newPeople = peopleDataManager.getPeople(); - newPeople.removeAll(priorPeople); - - // show that there are three new people - assertEquals(3, newPeople.size()); - - // show that the new people are not members of partition 1 - for (PersonId personId : newPeople) { - assertFalse(partitionsDataManager.contains(personId, key1)); - } - // show that the new people are members of partition 2 - for (PersonId personId : newPeople) { - assertTrue(partitionsDataManager.contains(personId, key2)); - } - }); - } - -} \ No newline at end of file diff --git a/gcm3/src/test/java/plugins/partitions/support/AT_DegeneratePopulationPartitionImpl.java b/gcm3/src/test/java/plugins/partitions/support/AT_DegeneratePopulationPartitionImpl.java deleted file mode 100644 index 01ad75b2a..000000000 --- a/gcm3/src/test/java/plugins/partitions/support/AT_DegeneratePopulationPartitionImpl.java +++ /dev/null @@ -1,827 +0,0 @@ -package plugins.partitions.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - - -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; - -import org.apache.commons.math3.random.RandomGenerator; -import org.apache.commons.math3.util.FastMath; -import org.junit.jupiter.api.Test; - -import nucleus.Event; -import nucleus.SimulationContext; -import plugins.partitions.testsupport.PartitionsActionSupport; -import plugins.partitions.testsupport.attributes.AttributesDataManager; -import plugins.partitions.testsupport.attributes.events.AttributeUpdateEvent; -import plugins.partitions.testsupport.attributes.support.AttributeFilter; -import plugins.partitions.testsupport.attributes.support.AttributeLabeler; -import plugins.partitions.testsupport.attributes.support.TestAttributeId; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.PersonConstructionData; -import plugins.people.support.PersonId; -import plugins.stochastics.StochasticsDataManager; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; -import util.random.RandomGeneratorProvider; - -@UnitTest(target = DegeneratePopulationPartitionImpl.class) -public class AT_DegeneratePopulationPartitionImpl { - - @Test - @UnitTestConstructor(args = { SimulationContext.class, Partition.class }) - public void testConstructor() { - - PartitionsActionSupport.testConsumer(100, 3760806761100897313L, (c) -> { - // establish data view - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - // select about half of the people - Set expectedPeople = new LinkedHashSet<>(); - for (PersonId personId : peopleDataManager.getPeople()) { - if (randomGenerator.nextBoolean()) { - expectedPeople.add(personId); - } - } - - // set attribute BOOLEAN_0 to true for those people - for (PersonId personId : expectedPeople) { - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, true); - } - - // create the population partition - Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); - Partition partition = Partition.builder().setFilter(filter).build(); - PopulationPartition populationPartition = new DegeneratePopulationPartitionImpl(c, partition); - - // show that the population partition contains the expected people - List actualPeople = populationPartition.getPeople(); - assertEquals(expectedPeople.size(), actualPeople.size()); - assertEquals(expectedPeople, new LinkedHashSet<>(actualPeople)); - - // precondition tests - // if the context is null - assertThrows(RuntimeException.class, () -> new DegeneratePopulationPartitionImpl(null, partition)); - - // if the partition is null - assertThrows(RuntimeException.class, () -> new DegeneratePopulationPartitionImpl(c, null)); - - // if the partition is not degenerate - ContractException contractException = assertThrows(ContractException.class, - () -> new DegeneratePopulationPartitionImpl(c, Partition.builder().addLabeler(new AttributeLabeler(TestAttributeId.BOOLEAN_0, (v) -> v)).build())); - assertEquals(PartitionError.NON_DEGENERATE_PARTITION, contractException.getErrorType()); - - }); - } - - @Test - @UnitTestMethod(name = "attemptPersonAddition", args = { PersonId.class }) - public void testAttemptPersonAddition() { - - PartitionsActionSupport.testConsumer(100, 2545018253500191849L, (c) -> { - // establish data views - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - - /* - * Create the population partition filtering on attribute BOOLEAN_0 - * = true - */ - Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); - Partition partition = Partition.builder().setFilter(filter).build(); - PopulationPartition populationPartition = new DegeneratePopulationPartitionImpl(c, partition); - - // precondition test: - assertThrows(RuntimeException.class, () -> populationPartition.attemptPersonAddition(null)); - - /* - * Add new people, setting the attribute to alternating values of - * true and false - */ - for (int i = 0; i < 20; i++) { - - PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); - - boolean attributeValue = i % 2 == 0; - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, attributeValue); - - populationPartition.attemptPersonAddition(personId); - - /* - * Show that the person is in the population partition if and - * only if their attribute value was set to true - */ - assertEquals(attributeValue, populationPartition.contains(personId)); - } - }); - } - - @Test - @UnitTestMethod(name = "attemptPersonRemoval", args = { PersonId.class }) - public void testAttemptPersonRemoval() { - PartitionsActionSupport.testConsumer(100, 1924419629240381672L, (c) -> { - // establish data views - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - /* - * Create a container for the people we expect to be contained in - * the population partition. - */ - Set expectedPeople = new LinkedHashSet<>(); - - // select about half of the people to have attribute BOOLEAN_0 value - // of true - for (PersonId personId : peopleDataManager.getPeople()) { - if (randomGenerator.nextBoolean()) { - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, true); - expectedPeople.add(personId); - } - } - - /* - * Create the population partition filtering on attribute BOOLEAN_0 - * = true - */ - Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); - Partition partition = Partition.builder().setFilter(filter).build(); - PopulationPartition populationPartition = new DegeneratePopulationPartitionImpl(c, partition); - - // show that the expected people are in the population partition - List actualPeople = populationPartition.getPeople(); - assertEquals(expectedPeople.size(), actualPeople.size()); - assertEquals(expectedPeople, new LinkedHashSet<>(actualPeople)); - - /* - * Remove people and show that they are no longer in the partition - */ - for (PersonId personId : expectedPeople) { - peopleDataManager.removePerson(personId); - populationPartition.attemptPersonRemoval(personId); - // show that the person was removed - assertFalse(populationPartition.contains(personId)); - } - }); - } - - @Test - @UnitTestMethod(name = "handleEvent", args = { Event.class }) - public void testHandleEvent() { - - PartitionsActionSupport.testConsumer(100, 5331854470768144150L, (c) -> { - // establish data views - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - - for (PersonId personId : peopleDataManager.getPeople()) { - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, randomGenerator.nextBoolean()); - } - - /* - * Create the population partition filtering on attribute BOOLEAN_0 - * = true - */ - Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); - Partition partition = Partition.builder().setFilter(filter).build(); - PopulationPartition populationPartition = new DegeneratePopulationPartitionImpl(c, partition); - - for (PersonId personId : peopleDataManager.getPeople()) { - Boolean b0 = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); - - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, !b0); - populationPartition.handleEvent(new AttributeUpdateEvent(personId, TestAttributeId.BOOLEAN_0, b0, !b0)); - - assertEquals(!b0, populationPartition.contains(personId)); - - } - - }); - } - - @Test - @UnitTestMethod(name = "validateLabelSetInfo", args = { LabelSet.class }) - public void testValidateLabelSetInfo() { - PartitionsActionSupport.testConsumer(100, 7896267308674363012L, (c) -> { - /* - * Create the population partition filtering on attribute BOOLEAN_0 - * = true - */ - Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); - Partition partition = Partition.builder().setFilter(filter).build(); - PopulationPartition populationPartition = new DegeneratePopulationPartitionImpl(c, partition); - - LabelSet labelSet = LabelSet.builder().setLabel(TestAttributeId.BOOLEAN_1, 2).build(); - assertFalse(populationPartition.validateLabelSetInfo(labelSet)); - - assertTrue(populationPartition.validateLabelSetInfo(LabelSet.builder().build())); - - }); - } - - @Test - @UnitTestMethod(name = "getPeopleCount", args = {}) - public void testGetPeopleCount() { - - PartitionsActionSupport.testConsumer(100, 2295886123984917407L, (c) -> { - // establish data views - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - /* - * Create a container for the people we expect to be contained in - * the population partition. - */ - Set expectedPeople = new LinkedHashSet<>(); - - // select about half of the people to have attribute BOOLEAN_0 value - // of true - for (PersonId personId : peopleDataManager.getPeople()) { - if (randomGenerator.nextBoolean()) { - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, true); - expectedPeople.add(personId); - } - } - - /* - * Create the population partition filtering on attribute BOOLEAN_0 - * = true - */ - Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); - Partition partition = Partition.builder().setFilter(filter).build(); - PopulationPartition populationPartition = new DegeneratePopulationPartitionImpl(c, partition); - - // show that the people count matches expectations - assertEquals(expectedPeople.size(), populationPartition.getPeopleCount()); - - /* - * Change the attributes for the expected people and show that the - * expected count is correct - */ - int expectedPeopleCount = expectedPeople.size(); - for (PersonId personId : expectedPeople) { - expectedPeopleCount--; - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, false); - populationPartition.handleEvent(new AttributeUpdateEvent(personId, TestAttributeId.BOOLEAN_0, true, false)); - assertEquals(expectedPeopleCount, populationPartition.getPeopleCount()); - } - }); - } - - @Test - @UnitTestMethod(name = "getPeopleCount", args = { LabelSet.class }) - public void testGetPeopleCount_LabelSet() { - PartitionsActionSupport.testConsumer(1000, 1957059921486084637L, (c) -> { - - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - - // Randomize the attribute values for all people - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - for (PersonId personId : peopleDataManager.getPeople()) { - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, randomGenerator.nextBoolean()); - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_1, randomGenerator.nextBoolean()); - } - - // build a container to hold the expected relationship from label - // sets to people - Set expectedPeople = new LinkedHashSet<>(); - for (PersonId personId : peopleDataManager.getPeople()) { - Boolean b0 = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); - Boolean b1 = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_1); - if (b0 && !b1) { - expectedPeople.add(personId); - } - } - - /* - * Create the population partition filtering on attribute BOOLEAN_0 - * = true - */ - Filter filter_0 = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); - Filter filter_1 = new AttributeFilter(TestAttributeId.BOOLEAN_1, Equality.EQUAL, false); - Filter filter = filter_0.and(filter_1); - Partition partition = Partition.builder().setFilter(filter).build(); - - PopulationPartition populationPartition = new DegeneratePopulationPartitionImpl(c, partition); - - // show that the people count matches expectations - List actualPeople = populationPartition.getPeople(LabelSet.builder().build()); - assertEquals(expectedPeople.size(), actualPeople.size()); - - }); - - } - - @Test - @UnitTestMethod(name = "getPeopleCountMap", args = { LabelSet.class }) - public void testGetPeopleCountMap() { - - PartitionsActionSupport.testConsumer(1000, 5254073186909000918L, (c) -> { - - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - // randomize BOOLEAN_0 attribute values - for (PersonId personId : peopleDataManager.getPeople()) { - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, randomGenerator.nextBoolean()); - } - // build the population partition with the BOOLEAN_0 - Partition partition = Partition.builder().setFilter(new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true)).build(); - DegeneratePopulationPartitionImpl populationPartition = new DegeneratePopulationPartitionImpl(c, partition); - - Map peopleCountMap = populationPartition.getPeopleCountMap(LabelSet.builder().build()); - - assertEquals(1, peopleCountMap.size()); - LabelSet keyLabelSet = peopleCountMap.keySet().iterator().next(); - assertTrue(keyLabelSet.isEmpty()); - Integer count = peopleCountMap.get(keyLabelSet); - assertEquals(populationPartition.getPeopleCount(), count); - - }); - } - - @Test - @UnitTestMethod(name = "contains", args = { PersonId.class }) - public void testContains() { - PartitionsActionSupport.testConsumer(100, 2907418341194860848L, (c) -> { - // establish data views - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - /* - * Create a container for the people we expect to be contained in - * the population partition. - */ - Set expectedPeople = new LinkedHashSet<>(); - - // select about half of the people to have attribute BOOLEAN_0 value - // of true - for (PersonId personId : peopleDataManager.getPeople()) { - if (randomGenerator.nextBoolean()) { - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, true); - expectedPeople.add(personId); - } - } - - /* - * Create the population partition filtering on attribute BOOLEAN_0 - * = true - */ - Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); - Partition partition = Partition.builder().setFilter(filter).build(); - PopulationPartition populationPartition = new DegeneratePopulationPartitionImpl(c, partition); - - // show that the person data view contains the people we expect - assertEquals(expectedPeople.size(), populationPartition.getPeople().size()); - for (PersonId personId : peopleDataManager.getPeople()) { - assertEquals(expectedPeople.contains(personId), populationPartition.contains(personId)); - } - }); - } - - @Test - @UnitTestMethod(name = "contains", args = { PersonId.class, LabelSet.class }) - public void testContains_LabelSet() { - PartitionsActionSupport.testConsumer(100, 2888054511830289156L, (c) -> { - // establish data views - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - /* - * Create a container for the people we expect to be contained in - * the population partition. - */ - Set expectedPeople = new LinkedHashSet<>(); - - // select about half of the people to have attribute BOOLEAN_0 value - // of true - for (PersonId personId : peopleDataManager.getPeople()) { - if (randomGenerator.nextBoolean()) { - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, true); - expectedPeople.add(personId); - } - } - - /* - * Create the population partition filtering on attribute BOOLEAN_0 - * = true - */ - Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); - Partition partition = Partition.builder().setFilter(filter).build(); - PopulationPartition populationPartition = new DegeneratePopulationPartitionImpl(c, partition); - - LabelSet labelSet = LabelSet.builder().build(); - // show that the person data view contains the people we expect - assertEquals(expectedPeople.size(), populationPartition.getPeople().size()); - for (PersonId personId : peopleDataManager.getPeople()) { - assertEquals(expectedPeople.contains(personId), populationPartition.contains(personId, labelSet)); - } - }); - - } - - @Test - @UnitTestMethod(name = "getPeople", args = { LabelSet.class }) - public void testGetPeople_LabelSet() { - PartitionsActionSupport.testConsumer(1000, 8577028018353363458L, (c) -> { - - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - - // Randomize the attribute values for all people - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - for (PersonId personId : peopleDataManager.getPeople()) { - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, randomGenerator.nextBoolean()); - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_1, randomGenerator.nextBoolean()); - } - - // build a container to hold the expected relationship from label - // sets to people - Set expectedPeople = new LinkedHashSet<>(); - for (PersonId personId : peopleDataManager.getPeople()) { - Boolean b0 = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); - Boolean b1 = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_1); - if (b0 && !b1) { - expectedPeople.add(personId); - } - } - - /* - * Create the population partition filtering on attribute BOOLEAN_0 - * = true - */ - Filter filter_0 = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); - Filter filter_1 = new AttributeFilter(TestAttributeId.BOOLEAN_1, Equality.EQUAL, false); - Filter filter = filter_0.and(filter_1); - Partition partition = Partition.builder().setFilter(filter).build(); - - PopulationPartition populationPartition = new DegeneratePopulationPartitionImpl(c, partition); - - // show that the people count matches expectations - List actualPeople = populationPartition.getPeople(LabelSet.builder().build()); - assertEquals(expectedPeople.size(), actualPeople.size()); - assertEquals(expectedPeople, new LinkedHashSet<>(actualPeople)); - - }); - - } - - @Test - @UnitTestMethod(name = "getPeople", args = {}) - public void testGetPeople() { - PartitionsActionSupport.testConsumer(100, 3706541397073246652L, (c) -> { - // establish data views - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - /* - * Create a container for the people we expect to be contained in - * the population partition. - */ - Set expectedPeople = new LinkedHashSet<>(); - - // select about half of the people to have attribute BOOLEAN_0 value - // of true - for (PersonId personId : peopleDataManager.getPeople()) { - if (randomGenerator.nextBoolean()) { - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, true); - expectedPeople.add(personId); - } - } - - /* - * Create the population partition filtering on attribute BOOLEAN_0 - * = true - */ - Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); - Partition partition = Partition.builder().setFilter(filter).build(); - PopulationPartition populationPartition = new DegeneratePopulationPartitionImpl(c, partition); - - // show that the person data view contains the people we expect - assertEquals(expectedPeople.size(), populationPartition.getPeople().size()); - assertEquals(expectedPeople, new LinkedHashSet<>(populationPartition.getPeople())); - - }); - } - - private static enum ExcludedPersonType { - NULL, MATCHING_MEMBER, NON_MATCHING_MEMBER, NON_MEMBER; - } - - @Test - @UnitTestMethod(name = "samplePartition", args = { PartitionSampler.class }) - public void testSamplePartition() { - - /* - * Tests the sample mechanism under a variety of partition samplers. - * - * The partition is formed from 4 labeling functions over the attributes - * - * INT_0-> 0, 1, 2 - * - * INT_1 -> TRUE, FALSE - * - * DOUBLE_0 -> A, B, C - * - * DOUBLE_1 -> TRUE, FALSE - * - * Filtering for the partition is either on or off. The filter passes - * when the attribute BOOLEAN_0 is true. - * - * The partition sampler will optionally set its excluded person to - * null, a person not in the partition, a person in the partition who is - * not expected to match the sampler's label set and a person who does - * match the sampler's label set. - * - * The partition sampler will optionally use a label set. The label set - * will either be null or empty since degenerate population partitions - * do no contain labelers. - * - * The partition sampler will optionally use a weighting function. The - * weighting function will return 1 for any person having a label of - * TRUE for INT_1 and 0 otherwise. - * - * This test does not demonstrate precondition checks, proper use of - * random number generator ids, or the proper distribution of results - * aligned to the weighting function other that the simple binary - * alignment for the weighting function described above. - * - * Each combination is run with a randomly generated seed value. - * - */ - - Set int_0_label_values = new LinkedHashSet<>(); - int_0_label_values.add(0); - int_0_label_values.add(1); - int_0_label_values.add(2); - int_0_label_values.add(3);// will not match any person - - Set double_0_label_values = new LinkedHashSet<>(); - double_0_label_values.add("A"); - double_0_label_values.add("B"); - double_0_label_values.add("C"); - double_0_label_values.add("D");// will not match any person - - Set weightingFunctionValues = new LinkedHashSet<>(); - weightingFunctionValues.add(false); - weightingFunctionValues.add(true); - - Set useFilterValues = new LinkedHashSet<>(); - useFilterValues.add(false); - useFilterValues.add(true); - - Set useLabelSetValues = new LinkedHashSet<>(); - useLabelSetValues.add(false); - useLabelSetValues.add(true); - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8925918754735468568L); - - for (ExcludedPersonType excludedPersonType : ExcludedPersonType.values()) { - for (Boolean useWeightingFunction : weightingFunctionValues) { - for (Boolean useFilter : useFilterValues) { - long seed = randomGenerator.nextLong(); - for (Boolean useLabelSet : useLabelSetValues) { - seed = randomGenerator.nextLong(); - executeSamplingTest(seed, useFilter, excludedPersonType, useWeightingFunction, useLabelSet); - } - } - } - } - } - - private void executeSamplingTest(long seed, Boolean useFilter, ExcludedPersonType excludedPersonType, Boolean useWeightingFunction, boolean useLabelSet) { - - PartitionsActionSupport.testConsumer(1000, seed, (c) -> { - - // remember to test with general and COMET to show they get - // different results? - - // establish data views - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - - /* - * Define functions that will convert attribute values into labels - * for attributes INT_0, INT_1, DOUBLE_0, and DOUBLE_1. We will use - * these in the partition's labeling - */ - Function int_0_labelFunction = (value) -> { - int v = (Integer) value; - return v % 3; - }; - - Function int_1_labelFunction = (value) -> { - int v = (Integer) value; - if (v < 40) { - return true; - } - return false; - }; - - Function double_0_labelFunction = (value) -> { - double v = (Double) value; - if (v < 33) { - return "A"; - } - if (v < 67) { - return "B"; - } - return "C"; - }; - - Function double_1_labelFunction = (value) -> { - double v = (Double) value; - return v < 90; - }; - - // determine the people in the world - List peopleInTheWorld = peopleDataManager.getPeople(); - - // alter people's attributes randomly - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - for (PersonId personId : peopleInTheWorld) { - int intValue = (int) (randomGenerator.nextDouble() * 100); - attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_0, intValue); - - intValue = (int) (randomGenerator.nextDouble() * 100); - attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_1, intValue); - - double doubleValue = randomGenerator.nextDouble() * 100; - attributesDataManager.setAttributeValue(personId, TestAttributeId.DOUBLE_0, doubleValue); - - doubleValue = randomGenerator.nextDouble() * 100; - attributesDataManager.setAttributeValue(personId, TestAttributeId.DOUBLE_1, doubleValue); - - boolean booleanValue = randomGenerator.nextBoolean(); - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, booleanValue); - - booleanValue = randomGenerator.nextBoolean(); - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_1, booleanValue); - - } - - /* - * Create a partition that may filter about half of the population - * on BOOLEAN_0. We will partition on INT_0, INT_1, DOUBLE_0 and - * DOUBLE_1. Note that we do not use BOOLEAN_1 as part of the - * partition. - */ - Partition.Builder partitionBuilder = Partition.builder(); - if (useFilter) { - partitionBuilder// - .setFilter(new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true));// - } - partitionBuilder// - .addLabeler(new AttributeLabeler(TestAttributeId.INT_0, int_0_labelFunction))// - .addLabeler(new AttributeLabeler(TestAttributeId.INT_1, int_1_labelFunction))// - .addLabeler(new AttributeLabeler(TestAttributeId.DOUBLE_0, double_0_labelFunction))// - .addLabeler(new AttributeLabeler(TestAttributeId.DOUBLE_1, double_1_labelFunction)); - - Partition partition = partitionBuilder.build(); - - PopulationPartitionImpl populationPartition = new PopulationPartitionImpl(c, partition); - - /* - * Create a label set for the query. - */ - LabelSet queryLabelSet = null; - if (useLabelSet) { - queryLabelSet = LabelSet.builder().build(); - } - - // determine the people in the partition - Set expectedPeopleInPartition = new LinkedHashSet<>(); - - for (PersonId personId : peopleInTheWorld) { - - if (useFilter) { - Boolean personInPartition = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); - if (personInPartition) { - expectedPeopleInPartition.add(personId); - } - } else { - expectedPeopleInPartition.add(personId); - } - - } - - // determine the people who will match the query label set - Set expectedPeopleMatchingQueryLabelSet = new LinkedHashSet<>(); - expectedPeopleMatchingQueryLabelSet.addAll(expectedPeopleInPartition); - - PartitionSampler.Builder partitionSamplerBuilder = PartitionSampler.builder(); - partitionSamplerBuilder.setLabelSet(queryLabelSet);// - - // Select the excluded person - PersonId excludedPersonId = null; - switch (excludedPersonType) { - case MATCHING_MEMBER: - for (PersonId personId : expectedPeopleMatchingQueryLabelSet) { - excludedPersonId = personId; - break; - } - - break; - case NON_MATCHING_MEMBER: - for (PersonId personId : expectedPeopleInPartition) { - if (!expectedPeopleMatchingQueryLabelSet.contains(personId)) { - excludedPersonId = personId; - break; - } - } - break; - - case NON_MEMBER: - if (useFilter) { - for (PersonId personId : peopleInTheWorld) { - if (!expectedPeopleInPartition.contains(personId)) { - excludedPersonId = personId; - break; - } - } - } - break; - - case NULL: - // do nothing - break; - default: - throw new RuntimeException("unhandled case: " + excludedPersonType); - } - partitionSamplerBuilder.setExcludedPerson(excludedPersonId); - - Set expectedPeopleMatchingPartitionSampler = new LinkedHashSet<>(expectedPeopleMatchingQueryLabelSet); - expectedPeopleMatchingPartitionSampler.remove(excludedPersonId); - - if (useWeightingFunction) { - Iterator iterator = expectedPeopleMatchingPartitionSampler.iterator(); - while (iterator.hasNext()) { - PersonId personId = iterator.next(); - Integer int_1_attributeValue = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_1); - Boolean passed = (Boolean) int_1_labelFunction.apply(int_1_attributeValue); - if (!passed) { - iterator.remove(); - } - } - - LabelSetWeightingFunction labelSetWeightingFunction = (c2, labelSet) -> { - Boolean value = (Boolean) labelSet.getLabel(TestAttributeId.INT_1).get(); - if (value) { - return 1; - } else { - return 0; - } - }; - partitionSamplerBuilder.setLabelSetWeightingFunction(labelSetWeightingFunction); - } - - PartitionSampler partitionSampler = partitionSamplerBuilder.build(); - - int samplingCount = FastMath.min(expectedPeopleMatchingQueryLabelSet.size() + 1, 10); - - for (int i = 0; i < samplingCount; i++) { - Optional optional = populationPartition.samplePartition(partitionSampler); - if (optional.isPresent()) { - PersonId selectedPerson = optional.get(); - assertTrue(expectedPeopleMatchingPartitionSampler.contains(selectedPerson)); - } else { - assertTrue(expectedPeopleMatchingPartitionSampler.isEmpty()); - } - } - - }); - - } - -} diff --git a/gcm3/src/test/java/plugins/partitions/support/AT_Equality.java b/gcm3/src/test/java/plugins/partitions/support/AT_Equality.java deleted file mode 100644 index f02c1d4b7..000000000 --- a/gcm3/src/test/java/plugins/partitions/support/AT_Equality.java +++ /dev/null @@ -1,95 +0,0 @@ -package plugins.partitions.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -/** - * Test class for {@link Equality} - * - * @author Shawn Hatch - * - */ -@UnitTest(target = Equality.class) -public class AT_Equality { - - /** - * Tests {@link Equality#isCompatibleComparisonValue(int)} - */ - @Test - @UnitTestMethod(name = "isCompatibleComparisonValue", args = { int.class }) - public void testIsCompatibleComparisonValue() { - - assertEquals(6, Equality.values().length); - - /* - * Show that the six Equality members return the proper compatibility - * with integer comparison values. - */ - - for (int i = 1; i <= 10; i++) { - assertFalse(Equality.EQUAL.isCompatibleComparisonValue(-i)); - assertTrue(Equality.EQUAL.isCompatibleComparisonValue(0)); - assertFalse(Equality.EQUAL.isCompatibleComparisonValue(i)); - - assertTrue(Equality.NOT_EQUAL.isCompatibleComparisonValue(-i)); - assertFalse(Equality.NOT_EQUAL.isCompatibleComparisonValue(0)); - assertTrue(Equality.NOT_EQUAL.isCompatibleComparisonValue(i)); - - assertTrue(Equality.LESS_THAN.isCompatibleComparisonValue(-i)); - assertFalse(Equality.LESS_THAN.isCompatibleComparisonValue(0)); - assertFalse(Equality.LESS_THAN.isCompatibleComparisonValue(i)); - - assertTrue(Equality.LESS_THAN_EQUAL.isCompatibleComparisonValue(-i)); - assertTrue(Equality.LESS_THAN_EQUAL.isCompatibleComparisonValue(0)); - assertFalse(Equality.LESS_THAN_EQUAL.isCompatibleComparisonValue(i)); - - assertFalse(Equality.GREATER_THAN.isCompatibleComparisonValue(-i)); - assertFalse(Equality.GREATER_THAN.isCompatibleComparisonValue(0)); - assertTrue(Equality.GREATER_THAN.isCompatibleComparisonValue(i)); - - assertFalse(Equality.GREATER_THAN_EQUAL.isCompatibleComparisonValue(-i)); - assertTrue(Equality.GREATER_THAN_EQUAL.isCompatibleComparisonValue(0)); - assertTrue(Equality.GREATER_THAN_EQUAL.isCompatibleComparisonValue(i)); - } - } - - /** - * Tests {@link Equality#getNegation(Equality)} - */ - @Test - @UnitTestMethod(name = "getNegation", args = { Equality.class }) - public void testGetNegation() { - assertEquals(6, Equality.values().length); - assertEquals(Equality.NOT_EQUAL, Equality.getNegation(Equality.EQUAL)); - assertEquals(Equality.EQUAL, Equality.getNegation(Equality.NOT_EQUAL)); - assertEquals(Equality.LESS_THAN_EQUAL, Equality.getNegation(Equality.GREATER_THAN)); - assertEquals(Equality.LESS_THAN, Equality.getNegation(Equality.GREATER_THAN_EQUAL)); - assertEquals(Equality.GREATER_THAN_EQUAL, Equality.getNegation(Equality.LESS_THAN)); - assertEquals(Equality.GREATER_THAN, Equality.getNegation(Equality.LESS_THAN_EQUAL)); - } - - /** - * Tests {@link Equality#valueOf(String)} - */ - @Test - @UnitTestMethod(name = "valueOf", args = { String.class }) - public void testValueOf() { - // nothing to test - } - - /** - * Tests {@link Equality#values()} - */ - @Test - @UnitTestMethod(name = "values", args = {}) - public void testValues() { - // nothing to test - } - -} diff --git a/gcm3/src/test/java/plugins/partitions/support/AT_EventPredicate.java b/gcm3/src/test/java/plugins/partitions/support/AT_EventPredicate.java deleted file mode 100644 index d73b6fef1..000000000 --- a/gcm3/src/test/java/plugins/partitions/support/AT_EventPredicate.java +++ /dev/null @@ -1,14 +0,0 @@ -package plugins.partitions.support; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - -@UnitTest(target = EventPredicate.class) -public class AT_EventPredicate { - @Test - public void test() { - // nothing to test - } - -} \ No newline at end of file diff --git a/gcm3/src/test/java/plugins/partitions/support/AT_Filter.java b/gcm3/src/test/java/plugins/partitions/support/AT_Filter.java deleted file mode 100644 index 88c1ba177..000000000 --- a/gcm3/src/test/java/plugins/partitions/support/AT_Filter.java +++ /dev/null @@ -1,250 +0,0 @@ -package plugins.partitions.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Optional; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import nucleus.Event; -import nucleus.SimulationContext; -import plugins.partitions.testsupport.PartitionsActionSupport; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.PersonId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - - -@UnitTest(target = Filter.class) -public class AT_Filter { - - private static class LocalFilter extends Filter { - - private final Set> filterSensitivities = new LinkedHashSet<>(); - - @SafeVarargs - public LocalFilter(FilterSensitivity... filterSensitivities) { - for (FilterSensitivity filterSensitivity : filterSensitivities) { - this.filterSensitivities.add(filterSensitivity); - } - } - - @Override - public boolean evaluate(SimulationContext context, PersonId personId) { - return false; - } - - @Override - public Set> getFilterSensitivities() { - return new LinkedHashSet<>(filterSensitivities); - } - - @Override - public void validate(SimulationContext context) { - // do nothing - - } - } - - private static Optional eventPredicate(SimulationContext context, Event event) { - return Optional.of(new PersonId(4)); - } - - /** - * Tests {@link Filter#and(Filter)} - */ - @Test - @UnitTestMethod(name = "and", args = { Filter.class }) - public void testAnd() { - PartitionsActionSupport.testConsumer(100, 254308828477050611L, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - /* - * Show that there are enough people in the simulation to make a - * valid test - */ - assertEquals(100,peopleDataManager.getPopulationCount()); - - // create the filters - Filter filter = Filter.allPeople().and(Filter.allPeople()); - for (PersonId personId : peopleDataManager.getPeople()) { - assertTrue(filter.evaluate(c, personId)); - } - - filter = Filter.allPeople().and(Filter.noPeople()); - for (PersonId personId : peopleDataManager.getPeople()) { - assertFalse(filter.evaluate(c, personId)); - } - - filter = Filter.noPeople().and(Filter.allPeople()); - for (PersonId personId : peopleDataManager.getPeople()) { - assertFalse(filter.evaluate(c, personId)); - } - - filter = Filter.noPeople().and(Filter.noPeople()); - for (PersonId personId : peopleDataManager.getPeople()) { - assertFalse(filter.evaluate(c, personId)); - } - - FilterSensitivity fsA = new FilterSensitivity<>(Event.class, AT_Filter::eventPredicate); - FilterSensitivity fsB = new FilterSensitivity<>(Event.class, AT_Filter::eventPredicate); - FilterSensitivity fsC = new FilterSensitivity<>(Event.class, AT_Filter::eventPredicate); - FilterSensitivity fsD = new FilterSensitivity<>(Event.class, AT_Filter::eventPredicate); - filter = new LocalFilter(fsA, fsB, fsC).and(new LocalFilter(fsA, fsD)); - - Set> expectedFilterSensitivities = new LinkedHashSet<>(); - expectedFilterSensitivities.add(fsA); - expectedFilterSensitivities.add(fsB); - expectedFilterSensitivities.add(fsC); - expectedFilterSensitivities.add(fsD); - Set> actualFilterSensitivities = filter.getFilterSensitivities(); - assertEquals(expectedFilterSensitivities, actualFilterSensitivities); - - // precondition tests - - //if the filter is null - ContractException contractException = assertThrows(ContractException.class, () -> Filter.allPeople().and(null)); - assertEquals(PartitionError.NULL_FILTER, contractException.getErrorType()); - }); - - } - - /** - * Tests {@link Filter#or(Filter)} - */ - @Test - @UnitTestMethod(name = "or", args = { Filter.class }) - public void testOr() { - PartitionsActionSupport.testConsumer(100, 921279696119043098L, (c) -> { - - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - - /* - * Show that there are enough people in the simulation to make a - * valid test - */ - assertEquals(100,peopleDataManager.getPopulationCount()); - - // create the filters - Filter filter = Filter.allPeople().or(Filter.allPeople()); - for (PersonId personId : peopleDataManager.getPeople()) { - assertTrue(filter.evaluate(c, personId)); - } - - filter = Filter.allPeople().or(Filter.noPeople()); - for (PersonId personId : peopleDataManager.getPeople()) { - assertTrue(filter.evaluate(c, personId)); - } - - filter = Filter.noPeople().or(Filter.allPeople()); - for (PersonId personId : peopleDataManager.getPeople()) { - assertTrue(filter.evaluate(c, personId)); - } - - filter = Filter.noPeople().or(Filter.noPeople()); - for (PersonId personId : peopleDataManager.getPeople()) { - assertFalse(filter.evaluate(c, personId)); - } - - FilterSensitivity fsA = new FilterSensitivity<>(Event.class, AT_Filter::eventPredicate); - FilterSensitivity fsB = new FilterSensitivity<>(Event.class, AT_Filter::eventPredicate); - FilterSensitivity fsC = new FilterSensitivity<>(Event.class, AT_Filter::eventPredicate); - FilterSensitivity fsD = new FilterSensitivity<>(Event.class, AT_Filter::eventPredicate); - filter = new LocalFilter(fsA, fsB, fsC).or(new LocalFilter(fsA, fsD)); - - Set> expectedFilterSensitivities = new LinkedHashSet<>(); - expectedFilterSensitivities.add(fsA); - expectedFilterSensitivities.add(fsB); - expectedFilterSensitivities.add(fsC); - expectedFilterSensitivities.add(fsD); - Set> actualFilterSensitivities = filter.getFilterSensitivities(); - assertEquals(expectedFilterSensitivities, actualFilterSensitivities); - - // precondition test - - //if the filter is null - - ContractException contractException = assertThrows(ContractException.class, () -> Filter.allPeople().or(null)); - assertEquals(PartitionError.NULL_FILTER, contractException.getErrorType()); - - }); - - } - - /** - * Tests {@link Filter#negate()} - */ - @Test - @UnitTestMethod(name = "negate", args = {}) - public void testNegate() { - PartitionsActionSupport.testConsumer(100, 4038710674336002107L, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - /* - * Show that there are enough people in the simulation to make a - * valid test - */ - assertEquals(100,peopleDataManager.getPopulationCount()); - - Filter filter = Filter.allPeople().negate(); - - for (PersonId personId : peopleDataManager.getPeople()) { - assertFalse(filter.evaluate(c, personId)); - } - - filter = Filter.noPeople().negate(); - for (PersonId personId : peopleDataManager.getPeople()) { - assertTrue(filter.evaluate(c, personId)); - } - - assertEquals(filter.getFilterSensitivities().size(), 0); - }); - } - - - /** - * Tests {@link Filter#allPeople()} - */ - @Test - @UnitTestMethod(name = "allPeople", args = {}) - public void testAllPeople() { - PartitionsActionSupport.testConsumer(30, 847391904888351863L, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - // show that the test is valid - assertTrue(peopleDataManager.getPopulationCount() > 0); - - final Filter filter = Filter.allPeople(); - - for (PersonId personId : peopleDataManager.getPeople()) { - assertTrue(filter.evaluate(c, personId)); - } - assertEquals(filter.getFilterSensitivities().size(), 0); - }); - - } - - /** - * Tests {@link Filter#noPeople()} - */ - @Test - @UnitTestMethod(name = "noPeople", args = {}) - public void testNoPeople() { - PartitionsActionSupport.testConsumer(100, 6400633994679307999L, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - assertEquals(100,peopleDataManager.getPopulationCount()); - - final Filter filter = Filter.noPeople(); - - for (PersonId personId : peopleDataManager.getPeople()) { - assertFalse(filter.evaluate(c, personId)); - } - - assertEquals(filter.getFilterSensitivities().size(), 0); - }); - - } -} diff --git a/gcm3/src/test/java/plugins/partitions/support/AT_FilterSensitivity.java b/gcm3/src/test/java/plugins/partitions/support/AT_FilterSensitivity.java deleted file mode 100644 index 10e2adb91..000000000 --- a/gcm3/src/test/java/plugins/partitions/support/AT_FilterSensitivity.java +++ /dev/null @@ -1,71 +0,0 @@ -package plugins.partitions.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.Optional; - -import javax.naming.Context; - -import org.junit.jupiter.api.Test; - -import nucleus.Event; -import plugins.partitions.testsupport.PartitionsActionSupport; -import plugins.people.support.PersonId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = FilterSensitivity.class) -public class AT_FilterSensitivity { - - @Test - @UnitTestConstructor(args = { Class.class, EventPredicate.class }) - public void testConstructor() { - // nothing to test - } - - private static class Event2 implements Event{} - private static class Event3 implements Event{} - - @Test - @UnitTestMethod(name = "getEventClass", args = {}) - public void testGetEventClass() { - - FilterSensitivity filterSensitivity1 = new FilterSensitivity<>(Event.class, (c, e) -> Optional.empty()); - assertEquals(Event.class, filterSensitivity1.getEventClass()); - - /* - * Note that we are using two event types here just to show that it - * works. These events do not carry person information and normally a - * FilterSensitivity is only used with such events. - */ - FilterSensitivity filterSensitivity2 = new FilterSensitivity<>(Event2.class, (c, e) -> Optional.empty()); - assertEquals(Event2.class, filterSensitivity2.getEventClass()); - - FilterSensitivity filterSensitivity3 = new FilterSensitivity<>(Event3.class, (c, e) -> Optional.empty()); - assertEquals(Event3.class, filterSensitivity3.getEventClass()); - - } - - @Test - @UnitTestMethod(name = "requiresRefresh", args = { Context.class, Event.class }) - public void testRequiresRefresh() { - PartitionsActionSupport.testConsumer(10, 8678712526990350206L, (context)->{ - FilterSensitivity filterSensitivity = new FilterSensitivity<>(Event.class, (c, e) -> Optional.empty()); - Optional optional = filterSensitivity.requiresRefresh(context, new Event() { - }); - assertFalse(optional.isPresent()); - - PersonId personId = new PersonId(0); - filterSensitivity = new FilterSensitivity<>(Event.class, (c, e) -> Optional.of(personId)); - optional = filterSensitivity.requiresRefresh(context, new Event() { - }); - assertTrue(optional.isPresent()); - assertEquals(personId, optional.get()); - }); - - } - -} \ No newline at end of file diff --git a/gcm3/src/test/java/plugins/partitions/support/AT_LabelFunction.java b/gcm3/src/test/java/plugins/partitions/support/AT_LabelFunction.java deleted file mode 100644 index e8fd78c45..000000000 --- a/gcm3/src/test/java/plugins/partitions/support/AT_LabelFunction.java +++ /dev/null @@ -1,13 +0,0 @@ -package plugins.partitions.support; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - -@UnitTest(target = LabelFunction.class) -public class AT_LabelFunction { - @Test - public void test() { - //nothing to test - } -} \ No newline at end of file diff --git a/gcm3/src/test/java/plugins/partitions/support/AT_LabelSet.java b/gcm3/src/test/java/plugins/partitions/support/AT_LabelSet.java deleted file mode 100644 index bad38885d..000000000 --- a/gcm3/src/test/java/plugins/partitions/support/AT_LabelSet.java +++ /dev/null @@ -1,133 +0,0 @@ -package plugins.partitions.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Optional; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -/** - * Test class for {@link LabelSetInfo} - * - * @author Shawn Hatch - * - */ -@UnitTest(target = LabelSet.class) - -public class AT_LabelSet { - - private static enum Dimension { - DIM_1, DIM_2, DIM_3, DIM_4, DIM_5; - } - - /** - * Tests {@linkplain LabelSet#builder() - */ - @Test - @UnitTestMethod(name = "builder", args = {}) - public void testBuilder() { - assertNotNull(LabelSet.builder()); - } - - /** - * Tests {@linkplain LabelSet#isEmpty() - */ - @Test - @UnitTestMethod(name = "isEmpty", args = {}) - public void testIsEmpty() { - - LabelSet labelSet = LabelSet.builder().build(); - assertTrue(labelSet.isEmpty()); - - labelSet = LabelSet.builder().setLabel(Dimension.DIM_1, "compartment label").build(); - assertFalse(labelSet.isEmpty()); - - labelSet = LabelSet.builder().setLabel(Dimension.DIM_2, "group label").build(); - assertFalse(labelSet.isEmpty()); - - labelSet = LabelSet.builder().setLabel(Dimension.DIM_3, "region label").build(); - assertFalse(labelSet.isEmpty()); - - labelSet = LabelSet.builder().setLabel(Dimension.DIM_4, "resource label").build(); - assertFalse(labelSet.isEmpty()); - - labelSet = LabelSet.builder().setLabel(Dimension.DIM_5, "property label").build(); - assertFalse(labelSet.isEmpty()); - - } - - /** - * Tests {@linkplain LabelSet#getLabel(Object) - */ - @Test - @UnitTestMethod(name = "getLabel", args = {}) - public void testGetCompartmentLabel() { - Object expectedCompartmentLabel = "Compartment Label"; - LabelSet labelSet = LabelSet.builder().setLabel(Dimension.DIM_1, expectedCompartmentLabel).build(); - Optional optionalLabel = labelSet.getLabel(Dimension.DIM_1); - assertTrue(optionalLabel.isPresent()); - Object actualCompartmentLabel = optionalLabel.get(); - assertEquals(expectedCompartmentLabel, actualCompartmentLabel); - - } - - /** - * Tests {@linkplain LabelSet#getDimensions() - */ - @Test - @UnitTestMethod(name = "getDimensions", args = {}) - public void testGetPersonPropertyLabel() { - // getDimensions() - LabelSet.Builder builder = LabelSet.builder(); - Set expectedDimensions = new LinkedHashSet<>(); - for (int i = 0; i < 10; i++) { - expectedDimensions.add(i); - builder.setLabel(i, Integer.toString(i)); - } - LabelSet labelSet = builder.build(); - Set actualDimensions = labelSet.getDimensions(); - assertEquals(expectedDimensions, actualDimensions); - } - - /** - * Tests {@linkplain LabelSet#equals(Object) - */ - @Test - @UnitTestMethod(name = "equals", args = { Object.class }) - public void testEquals() { - LabelSet labelSet1 = LabelSet.builder().setLabel(Dimension.DIM_1, "compartment label").build(); - LabelSet labelSet2 = LabelSet.builder().setLabel(Dimension.DIM_1, "compartment label").build(); - LabelSet labelSet3 = LabelSet.builder().setLabel(Dimension.DIM_1, "compartment label2").build(); - - assertFalse(labelSet1 == labelSet2); - assertTrue(labelSet1.equals(labelSet1)); - assertTrue(labelSet1.equals(labelSet2)); - assertTrue(labelSet2.equals(labelSet1)); - assertFalse(labelSet1.equals(labelSet3)); - - } - - /** - * Tests {@linkplain LabelSet#hashCode() - */ - @Test - @UnitTestMethod(name = "hashCode", args = {}) - public void testHashCode() { - - LabelSet labelSet1 = LabelSet.builder().setLabel(Dimension.DIM_1, "compartment label").build(); - LabelSet labelSet2 = LabelSet.builder().setLabel(Dimension.DIM_1, "compartment label").build(); - - assertFalse(labelSet1 == labelSet2); - assertEquals(labelSet1, labelSet2); - assertEquals(labelSet1.hashCode(), labelSet2.hashCode()); - } - -} diff --git a/gcm3/src/test/java/plugins/partitions/support/AT_LabelSetWeightingFunction.java b/gcm3/src/test/java/plugins/partitions/support/AT_LabelSetWeightingFunction.java deleted file mode 100644 index 90d0567ce..000000000 --- a/gcm3/src/test/java/plugins/partitions/support/AT_LabelSetWeightingFunction.java +++ /dev/null @@ -1,14 +0,0 @@ -package plugins.partitions.support; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - -@UnitTest(target = LabelSetWeightingFunction.class) -public class AT_LabelSetWeightingFunction { - @Test - public void test() { - // nothing to test - } - -} diff --git a/gcm3/src/test/java/plugins/partitions/support/AT_Labeler.java b/gcm3/src/test/java/plugins/partitions/support/AT_Labeler.java deleted file mode 100644 index a076fad49..000000000 --- a/gcm3/src/test/java/plugins/partitions/support/AT_Labeler.java +++ /dev/null @@ -1,20 +0,0 @@ -package plugins.partitions.support; - -import tools.annotations.UnitTest; - -/** - * A partition labeler creates an object label for a person. The labeler has an - * object dimension which is a dimension within a partition and the label - * occupies a level in that dimension. - * - * @author Shawn Hatch - * - */ - -@UnitTest(target = Labeler.class) -public class AT_Labeler { - public void test() { - // nothing to test - } - -} diff --git a/gcm3/src/test/java/plugins/partitions/support/AT_LabelerSensitivity.java b/gcm3/src/test/java/plugins/partitions/support/AT_LabelerSensitivity.java deleted file mode 100644 index 16296cde1..000000000 --- a/gcm3/src/test/java/plugins/partitions/support/AT_LabelerSensitivity.java +++ /dev/null @@ -1,66 +0,0 @@ -package plugins.partitions.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.Optional; -import java.util.function.Function; - -import org.junit.jupiter.api.Test; - -import nucleus.Event; -import plugins.people.support.PersonId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = LabelerSensitivity.class) -public final class AT_LabelerSensitivity { - - - @Test - @UnitTestConstructor(args = { Class.class, Function.class }) - public void testConstructor() { - // nothing to test - } - private static class Event2 implements Event{} - private static class Event3 implements Event{} - @Test - @UnitTestMethod(name = "getEventClass", args = {}) - public void testGetEventClass() { - LabelerSensitivity labelerSensitivity1 = new LabelerSensitivity<>(Event.class, (e) -> Optional.ofNullable(null)); - assertEquals(Event.class, labelerSensitivity1.getEventClass()); - - /* - * Note that we are using two event types here just to show that it - * works. These events do not carry person information and normally a - * LabelerSensitivity is only used with such events. - */ - - LabelerSensitivity labelerSensitivity2 = new LabelerSensitivity<>(Event2.class, (e) -> Optional.ofNullable(null)); - assertEquals(Event2.class, labelerSensitivity2.getEventClass()); - - LabelerSensitivity labelerSensitivity3 = new LabelerSensitivity<>(Event3.class, (e) -> Optional.ofNullable(null)); - assertEquals(Event3.class, labelerSensitivity3.getEventClass()); - - } - - @Test - @UnitTestMethod(name = "getPersonId", args = { Event.class }) - public void testGetPersonId() { - - LabelerSensitivity labelerSensitivity = new LabelerSensitivity<>(Event.class, (e) -> Optional.empty()); - Optional optional = labelerSensitivity.getPersonId( new Event() { - }); - assertFalse(optional.isPresent()); - - PersonId personId = new PersonId(0); - labelerSensitivity = new LabelerSensitivity<>(Event.class, (e) -> Optional.of(personId)); - optional = labelerSensitivity.getPersonId(new Event() { - }); - assertTrue(optional.isPresent()); - assertEquals(personId, optional.get()); - } - -} \ No newline at end of file diff --git a/gcm3/src/test/java/plugins/partitions/support/AT_Partition.java b/gcm3/src/test/java/plugins/partitions/support/AT_Partition.java deleted file mode 100644 index 664eb783f..000000000 --- a/gcm3/src/test/java/plugins/partitions/support/AT_Partition.java +++ /dev/null @@ -1,96 +0,0 @@ -package plugins.partitions.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import plugins.partitions.testsupport.attributes.support.AttributeLabeler; -import plugins.partitions.testsupport.attributes.support.TestAttributeId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -/** - * Test class for {@link PartitionInfo} - * - * @author Shawn Hatch - * - */ -@UnitTest(target = Partition.class) -public class AT_Partition { - - /** - * Tests {@linkplain Partition#builder() - */ - @Test - @UnitTestMethod(name = "builder", args = {}) - public void testBuilder() { - Partition partition = Partition.builder().build(); - assertNotNull(partition); - assertFalse(partition.getFilter().isPresent()); - assertTrue(partition.getLabelers().isEmpty()); - assertTrue(partition.isDegenerate()); - } - - /** - * Tests {@linkplain Partition#getLabelers() - */ - @Test - @UnitTestMethod(name = "getLabelers", args = {}) - public void testGetLabelers() { - - - - Set expectedLabelers = new LinkedHashSet<>(); - expectedLabelers.add(new AttributeLabeler(TestAttributeId.BOOLEAN_0, (v)->new Object())); - expectedLabelers.add(new AttributeLabeler(TestAttributeId.BOOLEAN_1, (v)->new Object())); - expectedLabelers.add(new AttributeLabeler(TestAttributeId.DOUBLE_0, (v)->new Object())); - - Partition.Builder builder = Partition.builder(); - for (Labeler labeler : expectedLabelers) { - builder.addLabeler(labeler); - } - - Partition partition = builder.build(); - - Set actualLabelers = partition.getLabelers(); - - assertEquals(expectedLabelers, actualLabelers); - - } - - /** - * Tests {@linkplain Partition#getFilter() - */ - @Test - @UnitTestMethod(name = "getFilter", args = {}) - public void testGetFilter() { - - Partition partition = Partition.builder().build();// - assertFalse(partition.getFilter().isPresent()); - - partition = Partition.builder().setFilter(Filter.allPeople()).build();// - assertTrue(partition.getFilter().isPresent()); - - } - - /** - * Tests {@linkplain Partition#isDegenerate() - */ - @Test - @UnitTestMethod(name = "isDegenerate", args = {}) - public void testIsDegenerate() { - - Partition partition = Partition.builder().build();// - assertTrue(partition.isDegenerate()); - - partition = Partition.builder().addLabeler(new AttributeLabeler(TestAttributeId.BOOLEAN_0, (v)->new Object())).build(); - assertFalse(partition.isDegenerate()); - } - -} diff --git a/gcm3/src/test/java/plugins/partitions/support/AT_PartitionError.java b/gcm3/src/test/java/plugins/partitions/support/AT_PartitionError.java deleted file mode 100644 index 78afb4ad5..000000000 --- a/gcm3/src/test/java/plugins/partitions/support/AT_PartitionError.java +++ /dev/null @@ -1,38 +0,0 @@ -package plugins.partitions.support; - -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - -/** - * An enumeration supporting {@link ContractException} that acts as a general - * description of the exception. - * - * @author Shawn Hatch - * - */ -@UnitTest(target = PartitionError.class) -public class AT_PartitionError { - @Test - @UnitTestMethod(name = "getDescription", args = {}) - public void test() { - // show that each description is a unique, non-null and non-empty string - Set descriptions = new LinkedHashSet<>(); - for (PartitionError partitionError : PartitionError.values()) { - String description = partitionError.getDescription(); - assertNotNull(description, "null description for " + partitionError); - assertTrue(description.length() > 0, "empty string for " + partitionError); - boolean unique = descriptions.add(description); - assertTrue(unique, "description for " + partitionError + " is not unique"); - } - } - -} diff --git a/gcm3/src/test/java/plugins/partitions/support/AT_PartitionSampler.java b/gcm3/src/test/java/plugins/partitions/support/AT_PartitionSampler.java deleted file mode 100644 index 3b3615220..000000000 --- a/gcm3/src/test/java/plugins/partitions/support/AT_PartitionSampler.java +++ /dev/null @@ -1,105 +0,0 @@ -package plugins.partitions.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; - -import plugins.people.support.PersonId; -import plugins.stochastics.testsupport.TestRandomGeneratorId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -/** - * Test class for {@link PartitionSampler} - * - * @author Shawn Hatch - * - */ -@UnitTest(target = PartitionSampler.class) -public class AT_PartitionSampler { - - /** - * Tests {@linkplain PartitionSampler#builder() - */ - @Test - @UnitTestMethod(name = "builder", args = {}) - public void testBuilder() { - assertNotNull(PartitionSampler.builder()); - } - - /** - * Tests {@linkplain PartitionSampler#getExcludedPerson() - */ - @Test - @UnitTestMethod(name = "getExcludedPerson", args = {}) - public void testGetExcludedPerson() { - PartitionSampler partitionSampler = PartitionSampler.builder().setExcludedPerson(new PersonId(67)).build(); - assertNotNull(partitionSampler); - assertNotNull(partitionSampler.getExcludedPerson()); - assertTrue(partitionSampler.getExcludedPerson().isPresent()); - assertEquals(67, partitionSampler.getExcludedPerson().get().getValue()); - } - - /** - * Tests {@linkplain PartitionSampler#getRandomNumberGeneratorId() - */ - @Test - @UnitTestMethod(name = "getRandomNumberGeneratorId", args = {}) - public void testGetRandomNumberGeneratorId() { - PartitionSampler partitionSampler = PartitionSampler.builder().setRandomNumberGeneratorId(TestRandomGeneratorId.DASHER).setRandomNumberGeneratorId(TestRandomGeneratorId.VIXEN).build(); - - assertNotNull(partitionSampler); - assertNotNull(partitionSampler.getRandomNumberGeneratorId()); - assertTrue(partitionSampler.getRandomNumberGeneratorId().isPresent()); - assertEquals(TestRandomGeneratorId.VIXEN, partitionSampler.getRandomNumberGeneratorId().get()); - } - - private static enum Dimensions{ - DIM_1,DIM_2; - } - - /** - * Tests {@linkplain PartitionSampler#getLabelSet() - */ - @Test - @UnitTestMethod(name = "getLabelSet", args = {}) - public void testGetLabelSet() { - PartitionSampler partitionSampler = PartitionSampler.builder().setLabelSet(LabelSet .builder()// - .setLabel(Dimensions.DIM_1, "compartmentLabel")// - .setLabel(Dimensions.DIM_2, "regionLabel").build())// - .build(); - - assertNotNull(partitionSampler); - assertNotNull(partitionSampler.getLabelSet()); - assertTrue(partitionSampler.getLabelSet().isPresent()); - LabelSet labelSet = partitionSampler.getLabelSet().get(); - - assertTrue(labelSet.getLabel(Dimensions.DIM_1).isPresent()); - assertEquals("compartmentLabel", labelSet.getLabel(Dimensions.DIM_1).get()); - assertTrue(labelSet.getLabel(Dimensions.DIM_2).isPresent()); - assertEquals("regionLabel", labelSet.getLabel(Dimensions.DIM_2).get()); - } - - /** - * Tests {@linkplain PartitionSampler#getLabelSetWeightingFunction() - */ - @Test - @UnitTestMethod(name = "getLabelSetWeightingFunction", args = {}) - public void testGetLabelSetWeightingFunction() { - - double expectedValue = 17.5; - - PartitionSampler partitionSampler = PartitionSampler.builder().setLabelSetWeightingFunction((context, labelSet) -> expectedValue).build(); - - assertNotNull(partitionSampler); - assertNotNull(partitionSampler.getLabelSetWeightingFunction()); - assertTrue(partitionSampler.getLabelSetWeightingFunction().isPresent()); - LabelSetWeightingFunction labelSetWeightingFunction = partitionSampler.getLabelSetWeightingFunction().get(); - assertNotNull(labelSetWeightingFunction); - - assertEquals(expectedValue, labelSetWeightingFunction.getWeight(null, null), 0); - } - -} diff --git a/gcm3/src/test/java/plugins/partitions/support/AT_PopulationPartition.java b/gcm3/src/test/java/plugins/partitions/support/AT_PopulationPartition.java deleted file mode 100644 index fdf730a75..000000000 --- a/gcm3/src/test/java/plugins/partitions/support/AT_PopulationPartition.java +++ /dev/null @@ -1,16 +0,0 @@ -package plugins.partitions.support; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - - -@UnitTest(target = PopulationPartition.class) -public class AT_PopulationPartition { - - @Test - public void test() { - //nothing to test - } - -} diff --git a/gcm3/src/test/java/plugins/partitions/support/AT_PopulationPartitionImpl.java b/gcm3/src/test/java/plugins/partitions/support/AT_PopulationPartitionImpl.java deleted file mode 100644 index c3eadf12f..000000000 --- a/gcm3/src/test/java/plugins/partitions/support/AT_PopulationPartitionImpl.java +++ /dev/null @@ -1,1032 +0,0 @@ -package plugins.partitions.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; - -import org.apache.commons.math3.random.RandomGenerator; -import org.apache.commons.math3.util.FastMath; -import org.junit.jupiter.api.Test; - -import nucleus.ActorContext; -import nucleus.Event; -import nucleus.SimulationContext; -import plugins.partitions.testsupport.PartitionsActionSupport; -import plugins.partitions.testsupport.attributes.AttributesDataManager; -import plugins.partitions.testsupport.attributes.events.AttributeUpdateEvent; -import plugins.partitions.testsupport.attributes.support.AttributeFilter; -import plugins.partitions.testsupport.attributes.support.AttributeLabeler; -import plugins.partitions.testsupport.attributes.support.TestAttributeId; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.PersonConstructionData; -import plugins.people.support.PersonId; -import plugins.stochastics.StochasticsDataManager; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.random.RandomGeneratorProvider; - -@UnitTest(target = PopulationPartitionImpl.class) -public class AT_PopulationPartitionImpl { - - @Test - @UnitTestConstructor(args = { SimulationContext.class, Partition.class }) - public void testConstructor() { - PartitionsActionSupport.testConsumer(100, 2997202170895856110L, (c) -> { - // establish data view - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - // select about half of the people - Set expectedPeople = new LinkedHashSet<>(); - for (PersonId personId : peopleDataManager.getPeople()) { - if (randomGenerator.nextBoolean()) { - expectedPeople.add(personId); - } - } - - // set attribute BOOLEAN_0 to true for those people - for (PersonId personId : expectedPeople) { - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, true); - } - - // create the population partition - Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); - Partition partition = Partition.builder().setFilter(filter).build(); - PopulationPartition populationPartition = new PopulationPartitionImpl(c, partition); - - // show that the population partition contains the expected people - List actualPeople = populationPartition.getPeople(); - assertEquals(expectedPeople.size(), actualPeople.size()); - assertEquals(expectedPeople, new LinkedHashSet<>(actualPeople)); - - // precondition tests - // if the context is null - assertThrows(RuntimeException.class, () -> new PopulationPartitionImpl(null, partition)); - - // if the partition is null - assertThrows(RuntimeException.class, () -> new PopulationPartitionImpl(c, null)); - - }); - - } - - @Test - @UnitTestMethod(name = "attemptPersonAddition", args = { PersonId.class }) - public void testAttemptPersonAddition() { - - PartitionsActionSupport.testConsumer(100, 3063819509780972206L, (c) -> { - // establish data views - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - - /* - * Create the population partition filtering on attribute BOOLEAN_0 - * = true - */ - Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); - Partition partition = Partition.builder().setFilter(filter).build(); - PopulationPartition populationPartition = new PopulationPartitionImpl(c, partition); - - // precondition test: - assertThrows(RuntimeException.class, () -> populationPartition.attemptPersonAddition(null)); - - /* - * Add new people, setting the attribute to alternating values of - * true and false - */ - for (int i = 0; i < 20; i++) { - PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); - boolean attributeValue = i % 2 == 0; - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, attributeValue); - populationPartition.attemptPersonAddition(personId); - - /* - * Show that the person is in the population partition if and - * only if their attribute value was set to true - */ - assertEquals(attributeValue, populationPartition.contains(personId)); - } - }); - } - - @Test - @UnitTestMethod(name = "attemptPersonRemoval", args = { PersonId.class }) - public void testAttemptPersonRemoval() { - - PartitionsActionSupport.testConsumer(100, 4856457716960397685L, (c) -> { - // establish data views - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - /* - * Create a container for the people we expect to be contained in - * the population partition. - */ - Set expectedPeople = new LinkedHashSet<>(); - - // select about half of the people to have attribute BOOLEAN_0 value - // of true - for (PersonId personId : peopleDataManager.getPeople()) { - if (randomGenerator.nextBoolean()) { - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, true); - expectedPeople.add(personId); - } - } - - /* - * Create the population partition filtering on attribute BOOLEAN_0 - * = true - */ - Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); - Partition partition = Partition.builder().setFilter(filter).build(); - PopulationPartition populationPartition = new PopulationPartitionImpl(c, partition); - - // show that the expected people are in the population partition - List actualPeople = populationPartition.getPeople(); - assertEquals(expectedPeople.size(), actualPeople.size()); - assertEquals(expectedPeople, new LinkedHashSet<>(actualPeople)); - - /* - * Remove people and show that they are no longer in the partition - */ - for (PersonId personId : expectedPeople) { - peopleDataManager.removePerson(personId); - populationPartition.attemptPersonRemoval(personId); - // show that the person was removed - assertFalse(populationPartition.contains(personId)); - } - }); - } - - @Test - @UnitTestMethod(name = "handleEvent", args = { Event.class }) - public void testHandleEvent() { - PartitionsActionSupport.testConsumer(100, 8982209428616460818L, (c) -> { - // establish data views - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - - for (PersonId personId : peopleDataManager.getPeople()) { - attributesDataManager.setAttributeValue(personId,TestAttributeId.BOOLEAN_0, randomGenerator.nextBoolean()); - attributesDataManager.setAttributeValue(personId,TestAttributeId.BOOLEAN_1, randomGenerator.nextBoolean()); - } - - /* - * Create the population partition filtering on attribute BOOLEAN_0 - * = true - */ - - Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); - Partition partition = Partition.builder().addLabeler(new AttributeLabeler(TestAttributeId.BOOLEAN_1, (v) -> v)).setFilter(filter).build(); - PopulationPartition populationPartition = new PopulationPartitionImpl(c, partition); - - for (PersonId personId : peopleDataManager.getPeople()) { - Boolean b0 = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); - Boolean b1 = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_1); - - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, !b0); - populationPartition.handleEvent(new AttributeUpdateEvent(personId, TestAttributeId.BOOLEAN_0, b0, !b0)); - - assertEquals(!b0, populationPartition.contains(personId)); - - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_1, !b1); - populationPartition.handleEvent(new AttributeUpdateEvent(personId, TestAttributeId.BOOLEAN_1, b1, !b1)); - - if(!b0) { - LabelSet labelSet = LabelSet.builder().setLabel(TestAttributeId.BOOLEAN_1, !b1).build(); - assertTrue(populationPartition.contains(personId,labelSet)); - - labelSet = LabelSet.builder().setLabel(TestAttributeId.BOOLEAN_1, b1).build(); - assertFalse(populationPartition.contains(personId,labelSet)); - } - } - - }); - } - - @Test - @UnitTestMethod(name = "validateLabelSetInfo", args = { LabelSet.class }) - public void testValidateLabelSetInfo() { - - PartitionsActionSupport.testConsumer(100, 4662203440339012044L, (c) -> { - /* - * Create the population partition filtering on attribute BOOLEAN_0 - * = true - */ - Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); - Partition partition = Partition .builder().setFilter(filter).addLabeler(new AttributeLabeler(TestAttributeId.BOOLEAN_1, (v) -> 1)) - .addLabeler(new AttributeLabeler(TestAttributeId.INT_0, (i) -> "value")).build(); - PopulationPartition populationPartition = new PopulationPartitionImpl(c, partition); - - LabelSet labelSet = LabelSet.builder().setLabel(TestAttributeId.BOOLEAN_1, 2).build(); - assertTrue(populationPartition.validateLabelSetInfo(labelSet)); - - labelSet = LabelSet.builder().setLabel(TestAttributeId.INT_0, 2).build(); - assertTrue(populationPartition.validateLabelSetInfo(labelSet)); - - labelSet = LabelSet.builder().setLabel(TestAttributeId.INT_1, 2).build(); - assertFalse(populationPartition.validateLabelSetInfo(labelSet)); - - }); - } - - @Test - @UnitTestMethod(name = "getPeopleCount", args = {}) - public void testGetPeopleCount() { - PartitionsActionSupport.testConsumer(100, 9050139615348413060L, (c) -> { - // establish data views - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - /* - * Create a container for the people we expect to be contained in - * the population partition. - */ - Set expectedPeople = new LinkedHashSet<>(); - - // select about half of the people to have attribute BOOLEAN_0 value - // of true - for (PersonId personId : peopleDataManager.getPeople()) { - if (randomGenerator.nextBoolean()) { - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, true); - expectedPeople.add(personId); - } - } - - /* - * Create the population partition filtering on attribute BOOLEAN_0 - * = true - */ - Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); - Partition partition = Partition.builder().setFilter(filter).build(); - PopulationPartition populationPartition = new PopulationPartitionImpl(c, partition); - - // show that the people count matches expectations - assertEquals(expectedPeople.size(), populationPartition.getPeopleCount()); - - /* - * Change the attributes for the expected people and show that the - * expected count is correct - */ - int expectedPeopleCount = expectedPeople.size(); - for (PersonId personId : expectedPeople) { - expectedPeopleCount--; - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, false); - populationPartition.handleEvent(new AttributeUpdateEvent(personId, TestAttributeId.BOOLEAN_0, true, false)); - assertEquals(expectedPeopleCount, populationPartition.getPeopleCount()); - } - }); - } - - private static Function INT_0_LABELFUNCTION = (value) -> { - final int v = (Integer) value; - return v % 3; - }; - - private static Function INT_1_LABELFUNCTION = (value) -> { - final int v = (Integer) value; - if (v < 40) { - return true; - } - return false; - }; - - private static Function DOUBLE_0_LABELFUNCTION = (value) -> { - final double v = (Double) value; - if (v < 33) { - return "A"; - } - if (v < 67) { - return "B"; - } - return "C"; - }; - - private static Function DOUBLE_1_LABELFUNCTION = (value) -> { - final double v = (Double) value; - return v < 90; - }; - - /* - * Assigns randomized values for all attributes to all people. Values are - * assigned to be consistent with the static labeling functions. - */ - private static void assignRandomAttributes(final ActorContext c) { - final PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - final StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - final RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - for (final PersonId personId : peopleDataManager.getPeople()) { - boolean b = randomGenerator.nextBoolean(); - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, b); - - b = randomGenerator.nextBoolean(); - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_1, b); - - int i = randomGenerator.nextInt(100); - attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_0, i); - - i = randomGenerator.nextInt(100); - attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_1, i); - - double d = randomGenerator.nextDouble() * 100; - attributesDataManager.setAttributeValue(personId, TestAttributeId.DOUBLE_0, d); - - d = randomGenerator.nextDouble() * 100; - attributesDataManager.setAttributeValue(personId, TestAttributeId.DOUBLE_1, d); - } - } - - /* - * Creates a map from LabelSet to PersonId that covers all people who have - * an attribute value of true for BOOLEAN_0 and false for BOOLEAN_1, to be - * consistent with the filter used in the partition addition test. Label - * sets consist of labels for INT_0, INT_1, DOUBLE_0 and DOUBLE_1. - */ - private static Map> getExpectedStructure(final ActorContext c) { - final PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - final AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - final Map> expectedPeople = new LinkedHashMap<>(); - for (final PersonId personId : peopleDataManager.getPeople()) { - - final Boolean b0 = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); - final Boolean b1 = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_1); - if (b0 && !b1) { - - final Integer i0 = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_0); - final Object label_i0 = INT_0_LABELFUNCTION.apply(i0); - - final Integer i1 = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_1); - final Object label_i1 = INT_1_LABELFUNCTION.apply(i1); - - final Double d0 = attributesDataManager.getAttributeValue(personId, TestAttributeId.DOUBLE_0); - final Object label_d0 = DOUBLE_0_LABELFUNCTION.apply(d0); - - final Double d1 = attributesDataManager.getAttributeValue(personId, TestAttributeId.DOUBLE_1); - final Object label_d1 = DOUBLE_1_LABELFUNCTION.apply(d1); - - final LabelSet labelSet = LabelSet .builder()// - .setLabel(TestAttributeId.INT_0, label_i0)// - .setLabel(TestAttributeId.INT_1, label_i1)// - .setLabel(TestAttributeId.DOUBLE_0, label_d0)// - .setLabel(TestAttributeId.DOUBLE_1, label_d1)// - .build();// - - Set people = expectedPeople.get(labelSet); - if (people == null) { - people = new LinkedHashSet<>(); - expectedPeople.put(labelSet, people); - } - people.add(personId); - } - } - return expectedPeople; - - } - - @Test - @UnitTestMethod(name = "getPeopleCount", args = { LabelSet.class }) - public void testGetPeopleCount_LabelSet() { - PartitionsActionSupport.testConsumer(1000, 8522399796145249846L, (c) -> { - - // Randomize the attribute values for all people - assignRandomAttributes(c); - - // build a container to hold the expected relationship from label - // sets to people - Map> expectedStructure = getExpectedStructure(c); - - /* - * Create the population partition filtering on attribute BOOLEAN_0 - * = true - */ - Filter filter_0 = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); - Filter filter_1 = new AttributeFilter(TestAttributeId.BOOLEAN_1, Equality.EQUAL, false); - Filter filter = filter_0.and(filter_1); - Partition partition = Partition .builder().addLabeler(new AttributeLabeler(TestAttributeId.INT_0, INT_0_LABELFUNCTION))// - .addLabeler(new AttributeLabeler(TestAttributeId.INT_1, INT_1_LABELFUNCTION))// - .addLabeler(new AttributeLabeler(TestAttributeId.DOUBLE_0, DOUBLE_0_LABELFUNCTION))// - .addLabeler(new AttributeLabeler(TestAttributeId.DOUBLE_1, DOUBLE_1_LABELFUNCTION))// - .setFilter(filter)// - .build();// - - PopulationPartition populationPartition = new PopulationPartitionImpl(c, partition); - - // show that the people count matches expectations - int expectedCount = 0; - for (LabelSet labelSet : expectedStructure.keySet()) { - expectedCount += expectedStructure.get(labelSet).size(); - } - assertEquals(expectedCount, populationPartition.getPeopleCount()); - - for (LabelSet labelSet : expectedStructure.keySet()) { - Set expectedPeople = expectedStructure.get(labelSet); - List actualPeople = populationPartition.getPeople(labelSet); - assertEquals(expectedPeople.size(), actualPeople.size()); - } - - }); - } - - @Test - @UnitTestMethod(name = "getPeopleCountMap", args = { LabelSet.class }) - public void testGetPeopleCountMap() { - PartitionsActionSupport.testConsumer(1000, 4793886153660135719L, (c) -> { - - // Randomize the attribute values for all people - assignRandomAttributes(c); - - /* - * Create the population partition filtering on attribute BOOLEAN_0 - * = true and BOOLEAN_1 = false - */ - Filter filter_0 = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); - Filter filter_1 = new AttributeFilter(TestAttributeId.BOOLEAN_1, Equality.EQUAL, false); - Filter filter = filter_0.and(filter_1); - Partition partition = Partition .builder().addLabeler(new AttributeLabeler(TestAttributeId.INT_0, INT_0_LABELFUNCTION))// - .addLabeler(new AttributeLabeler(TestAttributeId.INT_1, INT_1_LABELFUNCTION))// - .addLabeler(new AttributeLabeler(TestAttributeId.DOUBLE_0, DOUBLE_0_LABELFUNCTION))// - .addLabeler(new AttributeLabeler(TestAttributeId.DOUBLE_1, DOUBLE_1_LABELFUNCTION))// - .setFilter(filter)// - .build();// - - PopulationPartition populationPartition = new PopulationPartitionImpl(c, partition); - - List int_0_labelValues = new ArrayList<>(); - int_0_labelValues.add(0); - int_0_labelValues.add(1); - int_0_labelValues.add(2); - int_0_labelValues.add(3); - - List double_0_labelValues = new ArrayList<>(); - double_0_labelValues.add("A"); - double_0_labelValues.add("B"); - double_0_labelValues.add("C"); - double_0_labelValues.add("D"); - - // build a container to hold the expected relationship from label - // sets to people - Map> expectedStructure = new LinkedHashMap<>(); - - // build the keys of the expected structure - LabelSet.Builder labelSetBuilder = LabelSet.builder(); - for (Integer int_0_labelValue : int_0_labelValues) { - for (String double_0_labelValue : double_0_labelValues) { - LabelSet labelSet = labelSetBuilder.setLabel(TestAttributeId.INT_0, int_0_labelValue).setLabel(TestAttributeId.DOUBLE_0, double_0_labelValue).build(); - expectedStructure.put(labelSet, new LinkedHashMap<>()); - } - } - - // build the values of the expected structure - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - for (PersonId personId : peopleDataManager.getPeople()) { - Boolean b0 = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); - Boolean b1 = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_1); - if (b0 && !b1) { - Integer i0 = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_0); - Object int_0_label_value = INT_0_LABELFUNCTION.apply(i0); - Double d0 = attributesDataManager.getAttributeValue(personId, TestAttributeId.DOUBLE_0); - Object double_0_label_value = DOUBLE_0_LABELFUNCTION.apply(d0); - Integer i1 = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_1); - Object int_1_label_value = INT_1_LABELFUNCTION.apply(i1); - Double d1 = attributesDataManager.getAttributeValue(personId, TestAttributeId.DOUBLE_1); - Object double_1_label_value = DOUBLE_1_LABELFUNCTION.apply(d1); - - LabelSet labelSet = labelSetBuilder.setLabel(TestAttributeId.INT_0, int_0_label_value).setLabel(TestAttributeId.DOUBLE_0, double_0_label_value).build(); - Map map = expectedStructure.get(labelSet); - labelSet = labelSetBuilder .setLabel(TestAttributeId.INT_0, int_0_label_value).setLabel(TestAttributeId.DOUBLE_0, double_0_label_value) - .setLabel(TestAttributeId.INT_1, int_1_label_value).setLabel(TestAttributeId.DOUBLE_1, double_1_label_value).build(); - Integer count = map.get(labelSet); - if (count == null) { - count = 0; - } - count = count + 1; - map.put(labelSet, count); - } - } - - // show that every expected people count map corresponds to an - // identical people count map from the population partition - for (LabelSet labelSet : expectedStructure.keySet()) { - Map expectedPeopleCountMap = expectedStructure.get(labelSet); - Map actualPeopleCountMap = populationPartition.getPeopleCountMap(labelSet); - assertEquals(expectedPeopleCountMap, actualPeopleCountMap); - } - - }); - - } - - @Test - @UnitTestMethod(name = "contains", args = { PersonId.class }) - public void testContains() { - PartitionsActionSupport.testConsumer(100, 2652052463264971998L, (c) -> { - // establish data views - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - - /* - * Create a container for the people we expect to be contained in - * the population partition. - */ - Set expectedPeople = new LinkedHashSet<>(); - - // select about half of the people to have attribute BOOLEAN_0 value - // of true - for (PersonId personId : peopleDataManager.getPeople()) { - if (randomGenerator.nextBoolean()) { - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, true); - expectedPeople.add(personId); - - } - } - //System.out.println("expectedPeople = "+expectedPeople); - - - /* - * Create the population partition filtering on attribute BOOLEAN_0 - * = true - */ - Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); - Partition partition = Partition.builder().setFilter(filter).build(); - PopulationPartition populationPartition = new PopulationPartitionImpl(c, partition); - - // show that the person data view contains the people we expect - assertEquals(expectedPeople.size(), populationPartition.getPeople().size()); - for (PersonId personId : peopleDataManager.getPeople()) { - //System.out.println(personId+" : "+ populationPartition.contains(personId)); - assertEquals(expectedPeople.contains(personId), populationPartition.contains(personId)); - } - }); - } - - @Test - @UnitTestMethod(name = "contains", args = { PersonId.class, LabelSet.class }) - public void testContains_LabelSet() { - PartitionsActionSupport.testConsumer(1000, 827063967966581841L, (c) -> { - - // Randomize the attribute values for all people - assignRandomAttributes(c); - - // build a container to hold the expected relationship from label - // sets to people - Map> expectedStructure = getExpectedStructure(c); - - /* - * Create the population partition filtering on attribute BOOLEAN_0 - * = true - */ - Filter filter_0 = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); - Filter filter_1 = new AttributeFilter(TestAttributeId.BOOLEAN_1, Equality.EQUAL, false); - Filter filter = filter_0.and(filter_1); - Partition partition = Partition .builder().addLabeler(new AttributeLabeler(TestAttributeId.INT_0, INT_0_LABELFUNCTION))// - .addLabeler(new AttributeLabeler(TestAttributeId.INT_1, INT_1_LABELFUNCTION))// - .addLabeler(new AttributeLabeler(TestAttributeId.DOUBLE_0, DOUBLE_0_LABELFUNCTION))// - .addLabeler(new AttributeLabeler(TestAttributeId.DOUBLE_1, DOUBLE_1_LABELFUNCTION))// - .setFilter(filter)// - .build();// - - PopulationPartition populationPartition = new PopulationPartitionImpl(c, partition); - - // show that the people count matches expectations - int expectedCount = 0; - for (LabelSet labelSet : expectedStructure.keySet()) { - expectedCount += expectedStructure.get(labelSet).size(); - } - assertEquals(expectedCount, populationPartition.getPeopleCount()); - - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - List allPeople = peopleDataManager.getPeople(); - - // show that each label set contains the people expected - for (LabelSet labelSet : expectedStructure.keySet()) { - Set expectedPeople = expectedStructure.get(labelSet); - for (PersonId personId : allPeople) { - assertEquals(expectedPeople.contains(personId), populationPartition.contains(personId, labelSet)); - } - } - - }); - } - - @Test - @UnitTestMethod(name = "getPeople", args = { LabelSet.class }) - public void testGetPeople_LabelSet() { - PartitionsActionSupport.testConsumer(1000, 1040083420377037302L, (c) -> { - - // Randomize the attribute values for all people - assignRandomAttributes(c); - - // build a container to hold the expected relationship from label - // sets to people - Map> expectedStructure = getExpectedStructure(c); - - /* - * Create the population partition filtering on attribute BOOLEAN_0 - * = true - */ - Filter filter_0 = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); - Filter filter_1 = new AttributeFilter(TestAttributeId.BOOLEAN_1, Equality.EQUAL, false); - Filter filter = filter_0.and(filter_1); - Partition partition = Partition .builder().addLabeler(new AttributeLabeler(TestAttributeId.INT_0, INT_0_LABELFUNCTION))// - .addLabeler(new AttributeLabeler(TestAttributeId.INT_1, INT_1_LABELFUNCTION))// - .addLabeler(new AttributeLabeler(TestAttributeId.DOUBLE_0, DOUBLE_0_LABELFUNCTION))// - .addLabeler(new AttributeLabeler(TestAttributeId.DOUBLE_1, DOUBLE_1_LABELFUNCTION))// - .setFilter(filter)// - .build();// - - PopulationPartition populationPartition = new PopulationPartitionImpl(c, partition); - - // show that the people count matches expectations - int expectedCount = 0; - for (LabelSet labelSet : expectedStructure.keySet()) { - expectedCount += expectedStructure.get(labelSet).size(); - } - assertEquals(expectedCount, populationPartition.getPeopleCount()); - - for (LabelSet labelSet : expectedStructure.keySet()) { - Set expectedPeople = expectedStructure.get(labelSet); - List actualPeople = populationPartition.getPeople(labelSet); - assertEquals(expectedPeople.size(), actualPeople.size()); - assertEquals(expectedPeople, new LinkedHashSet<>(actualPeople)); - } - - }); - } - - @Test - @UnitTestMethod(name = "getPeople", args = {}) - public void testGetPeople() { - PartitionsActionSupport.testConsumer(100, 4597503339659285165L, (c) -> { - // establish data views - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - - /* - * Create a container for the people we expect to be contained in - * the population partition. - */ - Set expectedPeople = new LinkedHashSet<>(); - - // select about half of the people to have attribute BOOLEAN_0 value - // of true - for (PersonId personId : peopleDataManager.getPeople()) { - if (randomGenerator.nextBoolean()) { - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, true); - expectedPeople.add(personId); - } - } - - /* - * Create the population partition filtering on attribute BOOLEAN_0 - * = true - */ - Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); - Partition partition = Partition.builder().addLabeler(new AttributeLabeler(TestAttributeId.BOOLEAN_1, (v) -> v)).setFilter(filter).build(); - PopulationPartition populationPartition = new PopulationPartitionImpl(c, partition); - - // show that the person data view contains the people we expect - assertEquals(expectedPeople.size(), populationPartition.getPeople().size()); - assertEquals(expectedPeople, new LinkedHashSet<>(populationPartition.getPeople())); - - }); - } - - private static enum ExcludedPersonType { - NULL, MATCHING_MEMBER, NON_MATCHING_MEMBER, NON_MEMBER; - } - - @Test - @UnitTestMethod(name = "samplePartition", args = { PartitionSampler.class }) - public void testSamplePartition() { - /* - * Tests the sample mechanism under a variety of partition samplers. - * - * The partition is formed from 4 labeling functions over the attributes - * - * INT_0-> 0, 1, 2 - * - * INT_1 -> TRUE, FALSE - * - * DOUBLE_0 -> A, B, C - * - * DOUBLE_1 -> TRUE, FALSE - * - * Filtering for the partition is either on or off. The filter passes - * when the attribute BOOLEAN_0 is true. - * - * The partition sampler will optionally set its excluded person to - * null, a person not in the partition, a person in the partition who is - * not expected to match the sampler's label set and a person who does - * match the sampler's label set. - * - * The partition sampler will optionally use a label set. The label set - * will be composed of combinations of labels over INT_0 and DOUBLE_0, - * using label values that are associated with people and some that are - * not. - * - * The partition sampler will optionally use a weighting function. The - * weighting function will return 1 for any person having a label of - * TRUE for INT_1 and 0 otherwise. - * - * This test does not demonstrate precondition checks, proper use of - * random number generator ids, or the proper distribution of results - * aligned to the weighting function other that the simple binary - * alignment for the weighting function described above. - * - * Each combination is run with a randomly generated seed value. - * - */ - - Set int_0_label_values = new LinkedHashSet<>(); - int_0_label_values.add(0); - int_0_label_values.add(1); - int_0_label_values.add(2); - int_0_label_values.add(3);// will not match any person - - Set double_0_label_values = new LinkedHashSet<>(); - double_0_label_values.add("A"); - double_0_label_values.add("B"); - double_0_label_values.add("C"); - double_0_label_values.add("D");// will not match any person - - Set weightingFunctionValues = new LinkedHashSet<>(); - weightingFunctionValues.add(false); - weightingFunctionValues.add(true); - - Set useFilterValues = new LinkedHashSet<>(); - useFilterValues.add(false); - useFilterValues.add(true); - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6166310781583500795L); - - for (ExcludedPersonType excludedPersonType : ExcludedPersonType.values()) { - for (Boolean useWeightingFunction : weightingFunctionValues) { - for (Boolean useFilter : useFilterValues) { - long seed = randomGenerator.nextLong(); - executeSamplingTest(seed, useFilter, excludedPersonType, useWeightingFunction, null, null); - for (Integer int_0_label_value : int_0_label_values) { - for (String double_0_label_value : double_0_label_values) { - seed = randomGenerator.nextLong(); - executeSamplingTest(seed, useFilter, excludedPersonType, useWeightingFunction, int_0_label_value, double_0_label_value); - } - } - } - } - } - - } - - private void executeSamplingTest(long seed, Boolean useFilter, ExcludedPersonType excludedPersonType, Boolean useWeightingFunction, Integer int_0_label_value, String double_0_label_value) { - - PartitionsActionSupport.testConsumer(1000, seed, (c) -> { - - // remember to test with general and COMET to show they get - // different results? - - // establish data views - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - - /* - * Define functions that will convert attribute values into labels - * for attributes INT_0, INT_1, DOUBLE_0, and DOUBLE_1. We will use - * these in the partition's labeling - */ - Function int_0_labelFunction = (value) -> { - int v = (Integer) value; - return v % 3; - }; - - Function int_1_labelFunction = (value) -> { - int v = (Integer) value; - if (v < 40) { - return true; - } - return false; - }; - - Function double_0_labelFunction = (value) -> { - double v = (Double) value; - if (v < 33) { - return "A"; - } - if (v < 67) { - return "B"; - } - return "C"; - }; - - Function double_1_labelFunction = (value) -> { - double v = (Double) value; - return v < 90; - }; - - // determine the people in the world - List peopleInTheWorld = peopleDataManager.getPeople(); - - // alter people's attributes randomly - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - for (PersonId personId : peopleInTheWorld) { - int intValue = (int) (randomGenerator.nextDouble() * 100); - attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_0, intValue); - - intValue = (int) (randomGenerator.nextDouble() * 100); - attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_1, intValue); - - double doubleValue = randomGenerator.nextDouble() * 100; - attributesDataManager.setAttributeValue(personId, TestAttributeId.DOUBLE_0, doubleValue); - - doubleValue = randomGenerator.nextDouble() * 100; - attributesDataManager.setAttributeValue(personId, TestAttributeId.DOUBLE_1, doubleValue); - - boolean booleanValue = randomGenerator.nextBoolean(); - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, booleanValue); - - booleanValue = randomGenerator.nextBoolean(); - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_1, booleanValue); - - } - - /* - * Create a partition that may filter about half of the population - * on BOOLEAN_0. We will partition on INT_0, INT_1, DOUBLE_0 and - * DOUBLE_1. Note that we do not use BOOLEAN_1 as part of the - * partition. - */ - Partition.Builder partitionBuilder = Partition.builder(); - if (useFilter) { - partitionBuilder// - .setFilter(new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true));// - } - partitionBuilder// - .addLabeler(new AttributeLabeler(TestAttributeId.INT_0, int_0_labelFunction))// - .addLabeler(new AttributeLabeler(TestAttributeId.INT_1, int_1_labelFunction))// - .addLabeler(new AttributeLabeler(TestAttributeId.DOUBLE_0, double_0_labelFunction))// - .addLabeler(new AttributeLabeler(TestAttributeId.DOUBLE_1, double_1_labelFunction)); - - Partition partition = partitionBuilder.build(); - - PopulationPartitionImpl populationPartition = new PopulationPartitionImpl(c, partition); - - /* - * Create a label set for the query that does not contain all the - * attribute labels and has legitimate values for each dimension. - */ - LabelSet queryLabelSet = null; - if (int_0_label_value != null && double_0_label_value != null) { - LabelSet.Builder labelSetBuilder = LabelSet.builder(); - labelSetBuilder.setLabel(TestAttributeId.INT_0, int_0_label_value); - labelSetBuilder.setLabel(TestAttributeId.DOUBLE_0, double_0_label_value); - queryLabelSet = labelSetBuilder.build(); - } - - // determine the people in the partition - Set expectedPeopleInPartition = new LinkedHashSet<>(); - - for (PersonId personId : peopleInTheWorld) { - - if (useFilter) { - Boolean personInPartition = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); - if (personInPartition) { - expectedPeopleInPartition.add(personId); - } - } else { - expectedPeopleInPartition.add(personId); - } - - } - - // determine the people who will match the query label set - Set expectedPeopleMatchingQueryLabelSet = new LinkedHashSet<>(); - if (queryLabelSet != null) { - for (PersonId personId : expectedPeopleInPartition) { - // will the person pass the filter? - - Integer intValue = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_0); - Object labelValue = int_0_labelFunction.apply(intValue); - if (labelValue.equals(int_0_label_value)) { - Double doubleValue = attributesDataManager.getAttributeValue(personId, TestAttributeId.DOUBLE_0); - labelValue = double_0_labelFunction.apply(doubleValue); - if (labelValue.equals(double_0_label_value)) { - expectedPeopleMatchingQueryLabelSet.add(personId); - } - - } - - } - } else { - expectedPeopleMatchingQueryLabelSet.addAll(expectedPeopleInPartition); - } - - PartitionSampler.Builder partitionSamplerBuilder = PartitionSampler.builder(); - partitionSamplerBuilder.setLabelSet(queryLabelSet);// - - // Select the excluded person - PersonId excludedPersonId = null; - switch (excludedPersonType) { - case MATCHING_MEMBER: - for (PersonId personId : expectedPeopleMatchingQueryLabelSet) { - excludedPersonId = personId; - break; - } - - break; - case NON_MATCHING_MEMBER: - for (PersonId personId : expectedPeopleInPartition) { - if (!expectedPeopleMatchingQueryLabelSet.contains(personId)) { - excludedPersonId = personId; - break; - } - } - break; - - case NON_MEMBER: - if (useFilter) { - for (PersonId personId : peopleInTheWorld) { - if (!expectedPeopleInPartition.contains(personId)) { - excludedPersonId = personId; - break; - } - } - } - break; - - case NULL: - // do nothing - break; - default: - throw new RuntimeException("unhandled case: " + excludedPersonType); - } - partitionSamplerBuilder.setExcludedPerson(excludedPersonId); - - Set expectedPeopleMatchingPartitionSampler = new LinkedHashSet<>(expectedPeopleMatchingQueryLabelSet); - expectedPeopleMatchingPartitionSampler.remove(excludedPersonId); - - if (useWeightingFunction) { - Iterator iterator = expectedPeopleMatchingPartitionSampler.iterator(); - while (iterator.hasNext()) { - PersonId personId = iterator.next(); - Integer int_1_attributeValue = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_1); - Boolean passed = (Boolean) int_1_labelFunction.apply(int_1_attributeValue); - if (!passed) { - iterator.remove(); - } - } - - LabelSetWeightingFunction labelSetWeightingFunction = (c2, labelSet) -> { - Boolean value = (Boolean) labelSet.getLabel(TestAttributeId.INT_1).get(); - if (value) { - return 1; - } else { - return 0; - } - }; - partitionSamplerBuilder.setLabelSetWeightingFunction(labelSetWeightingFunction); - } - - PartitionSampler partitionSampler = partitionSamplerBuilder.build(); - - int samplingCount = FastMath.min(expectedPeopleMatchingQueryLabelSet.size() + 1, 10); - - for (int i = 0; i < samplingCount; i++) { - Optional optional = populationPartition.samplePartition(partitionSampler); - if (optional.isPresent()) { - PersonId selectedPerson = optional.get(); - assertTrue(expectedPeopleMatchingPartitionSampler.contains(selectedPerson)); - } else { - assertTrue(expectedPeopleMatchingPartitionSampler.isEmpty()); - } - } - - }); - - } -} diff --git a/gcm3/src/test/java/plugins/partitions/support/AT_Tuplator.java b/gcm3/src/test/java/plugins/partitions/support/AT_Tuplator.java deleted file mode 100644 index 8fc1c7b89..000000000 --- a/gcm3/src/test/java/plugins/partitions/support/AT_Tuplator.java +++ /dev/null @@ -1,142 +0,0 @@ -package plugins.partitions.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import plugins.partitions.support.Tuplator.Builder; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.random.RandomGeneratorProvider; - -/** - * Test class for {@link Tuplator} - * - * @author Shawn Hatch - * - */ -@UnitTest(target = Tuplator.class) -public class AT_Tuplator { - - - /** - * Tests {@link Tuplator#size()} - */ - @Test - @UnitTestMethod(name = "size", args = {}) - public void testSize() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7820715406750309229L); - for (int i = 0; i < 100; i++) { - Builder builder = Tuplator.builder(); - int dimensionCount = randomGenerator.nextInt(4) + 1; - int expectedSize = 1; - for (int j = 0; j < dimensionCount; j++) { - int dimSize = randomGenerator.nextInt(10) + 1; - expectedSize *= dimSize; - builder.addDimension(dimSize); - } - int actualSize = builder.build().size(); - assertEquals(expectedSize, actualSize); - } - } - - /** - * Tests {@link Tuplator#builder()} - */ - @Test - @UnitTestMethod(name = "builder", args = {}) - public void testBuilder() { - // covered by other tests - } - - /** - * Tests {@link Tuplator#dimensions()} - */ - @Test - @UnitTestMethod(name = "dimensions", args = {}) - public void testDimensions() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7661626069466374878L); - for (int i = 0; i < 100; i++) { - Builder builder = Tuplator.builder(); - int dimensionCount = randomGenerator.nextInt(4) + 1; - for (int j = 0; j < dimensionCount; j++) { - int dimSize = randomGenerator.nextInt(10) + 1; - builder.addDimension(dimSize); - } - int actualDimensionCount = builder.build().dimensions(); - assertEquals(dimensionCount, actualDimensionCount); - } - } - - /** - * Tests {@link Tuplator#fillTuple(int, int[])} - */ - @Test - @UnitTestMethod(name = "fillTuple", args = { int.class, int[].class }) - public void testFillTuple() { - - Tuplator tuplator = Tuplator.builder().addDimension(2).addDimension(3).addDimension(5).build(); - - int[] tuple = new int[tuplator.dimensions()]; - - List expectedArrays = new ArrayList<>(); - - expectedArrays.add(new int[] { 0, 0, 0 }); - expectedArrays.add(new int[] { 1, 0, 0 }); - expectedArrays.add(new int[] { 0, 1, 0 }); - expectedArrays.add(new int[] { 1, 1, 0 }); - expectedArrays.add(new int[] { 0, 2, 0 }); - expectedArrays.add(new int[] { 1, 2, 0 }); - expectedArrays.add(new int[] { 0, 0, 1 }); - expectedArrays.add(new int[] { 1, 0, 1 }); - expectedArrays.add(new int[] { 0, 1, 1 }); - expectedArrays.add(new int[] { 1, 1, 1 }); - expectedArrays.add(new int[] { 0, 2, 1 }); - expectedArrays.add(new int[] { 1, 2, 1 }); - expectedArrays.add(new int[] { 0, 0, 2 }); - expectedArrays.add(new int[] { 1, 0, 2 }); - expectedArrays.add(new int[] { 0, 1, 2 }); - expectedArrays.add(new int[] { 1, 1, 2 }); - expectedArrays.add(new int[] { 0, 2, 2 }); - expectedArrays.add(new int[] { 1, 2, 2 }); - expectedArrays.add(new int[] { 0, 0, 3 }); - expectedArrays.add(new int[] { 1, 0, 3 }); - expectedArrays.add(new int[] { 0, 1, 3 }); - expectedArrays.add(new int[] { 1, 1, 3 }); - expectedArrays.add(new int[] { 0, 2, 3 }); - expectedArrays.add(new int[] { 1, 2, 3 }); - expectedArrays.add(new int[] { 0, 0, 4 }); - expectedArrays.add(new int[] { 1, 0, 4 }); - expectedArrays.add(new int[] { 0, 1, 4 }); - expectedArrays.add(new int[] { 1, 1, 4 }); - expectedArrays.add(new int[] { 0, 2, 4 }); - expectedArrays.add(new int[] { 1, 2, 4 }); - - for (int i = 0; i < tuplator.size(); i++) { - tuplator.fillTuple(i, tuple); - assertTrue(Arrays.equals(expectedArrays.get(i), tuple)); - } - /** - * precondition tests - */ - assertThrows(IndexOutOfBoundsException.class, ()->tuplator.fillTuple(-2, tuple)); - assertThrows(IndexOutOfBoundsException.class, ()->tuplator.fillTuple(-1, tuple)); - assertThrows(IndexOutOfBoundsException.class, ()->tuplator.fillTuple(tuplator.size(), tuple)); - assertThrows(IndexOutOfBoundsException.class, ()->tuplator.fillTuple(tuplator.size()+1, tuple)); - assertThrows(IllegalArgumentException.class, ()->tuplator.fillTuple(0, null)); - assertThrows(IllegalArgumentException.class, ()->tuplator.fillTuple(0, new int[tuplator.dimensions()-1])); - assertThrows(IllegalArgumentException.class, ()->tuplator.fillTuple(0, new int[tuplator.dimensions()+1])); - - - } - -} diff --git a/gcm3/src/test/java/plugins/partitions/support/containers/AT_BasePeopleContainer.java b/gcm3/src/test/java/plugins/partitions/support/containers/AT_BasePeopleContainer.java deleted file mode 100644 index 63b65192d..000000000 --- a/gcm3/src/test/java/plugins/partitions/support/containers/AT_BasePeopleContainer.java +++ /dev/null @@ -1,77 +0,0 @@ -package plugins.partitions.support.containers; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.SimulationContext; -import plugins.partitions.support.PartitionError; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.PersonId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - - -@UnitTest(target = BasePeopleContainer.class) -public class AT_BasePeopleContainer { - - - private PeopleContainer getPeopleContainer(SimulationContext context) { - return new BasePeopleContainer(context); - } - - @Test - @UnitTestConstructor(args = {PeopleDataManager.class}) - public void testConstructor() { - ContractException contractException = assertThrows(ContractException.class,()->new TreeBitSetPeopleContainer(null)); - assertEquals(PartitionError.NULL_PERSON_DATA_VIEW, contractException.getErrorType()); - } - - @Test - @UnitTestMethod(name = "getPeople", args= {}) - public void testGetPeople(){ - PeopleContainerTester.testGetPeople(this::getPeopleContainer,473484353360778267L); - } - - @Test - @UnitTestMethod(name = "safeAdd", args= {PersonId.class}) - public void testSafeAdd(){ - PeopleContainerTester.testSafeAdd(this::getPeopleContainer,1067260810269284703L); - } - - - @Test - @UnitTestMethod(name = "unsafeAdd", args= {PersonId.class}) - public void testUnsafeAdd(){ - PeopleContainerTester.testUnsafeAdd(this::getPeopleContainer,2640497434656632684L); - } - - @Test - @UnitTestMethod(name = "remove", args= {PersonId.class}) - public void testRemove(){ - PeopleContainerTester.testRemove(this::getPeopleContainer,1461315035567239819L); - } - - @Test - @UnitTestMethod(name = "size", args= {}) - public void testSize(){ - PeopleContainerTester.testSize(this::getPeopleContainer,5880341220076297803L); - } - - @Test - @UnitTestMethod(name = "", args= {PersonId.class}) - public void testContains(){ - PeopleContainerTester.testContains(this::getPeopleContainer,6865277196728541573L); - } - - @Test - @UnitTestMethod(name = "getRandomPersonId", args= {RandomGenerator.class}) - public void testGetRandomPersonId(){ - PeopleContainerTester.testGetRandomPersonId(this::getPeopleContainer,1976658500916036734L); - } - -} \ No newline at end of file diff --git a/gcm3/src/test/java/plugins/partitions/support/containers/AT_IntSetPeopleContainer.java b/gcm3/src/test/java/plugins/partitions/support/containers/AT_IntSetPeopleContainer.java deleted file mode 100644 index a1faedc87..000000000 --- a/gcm3/src/test/java/plugins/partitions/support/containers/AT_IntSetPeopleContainer.java +++ /dev/null @@ -1,77 +0,0 @@ -package plugins.partitions.support.containers; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.SimulationContext; -import plugins.partitions.support.PartitionError; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.PersonId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - - -@UnitTest(target = IntSetPeopleContainer.class) -public class AT_IntSetPeopleContainer { - - - private PeopleContainer getPeopleContainer(SimulationContext context) { - return new IntSetPeopleContainer(); - } - - @Test - @UnitTestConstructor(args = {PeopleDataManager.class}) - public void testConstructor() { - ContractException contractException = assertThrows(ContractException.class,()->new TreeBitSetPeopleContainer(null)); - assertEquals(PartitionError.NULL_PERSON_DATA_VIEW, contractException.getErrorType()); - } - - @Test - @UnitTestMethod(name = "getPeople", args= {}) - public void testGetPeople(){ - PeopleContainerTester.testGetPeople(this::getPeopleContainer,473484353360778267L); - } - - @Test - @UnitTestMethod(name = "safeAdd", args= {PersonId.class}) - public void testSafeAdd(){ - PeopleContainerTester.testSafeAdd(this::getPeopleContainer,1067260810269284703L); - } - - - @Test - @UnitTestMethod(name = "unsafeAdd", args= {PersonId.class}) - public void testUnsafeAdd(){ - PeopleContainerTester.testUnsafeAdd(this::getPeopleContainer,2640497434656632684L); - } - - @Test - @UnitTestMethod(name = "remove", args= {PersonId.class}) - public void testRemove(){ - PeopleContainerTester.testRemove(this::getPeopleContainer,1461315035567239819L); - } - - @Test - @UnitTestMethod(name = "size", args= {}) - public void testSize(){ - PeopleContainerTester.testSize(this::getPeopleContainer,5880341220076297803L); - } - - @Test - @UnitTestMethod(name = "", args= {PersonId.class}) - public void testContains(){ - PeopleContainerTester.testContains(this::getPeopleContainer,6865277196728541573L); - } - - @Test - @UnitTestMethod(name = "getRandomPersonId", args= {RandomGenerator.class}) - public void testGetRandomPersonId(){ - PeopleContainerTester.testGetRandomPersonId(this::getPeopleContainer,1976658500916036734L); - } - -} \ No newline at end of file diff --git a/gcm3/src/test/java/plugins/partitions/support/containers/AT_PeopleContainer.java b/gcm3/src/test/java/plugins/partitions/support/containers/AT_PeopleContainer.java deleted file mode 100644 index 702b21258..000000000 --- a/gcm3/src/test/java/plugins/partitions/support/containers/AT_PeopleContainer.java +++ /dev/null @@ -1,15 +0,0 @@ -package plugins.partitions.support.containers; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - -@UnitTest(target = PeopleContainer.class) -public class AT_PeopleContainer { - - @Test - public void test() { - //nothing to test - } - -} \ No newline at end of file diff --git a/gcm3/src/test/java/plugins/partitions/support/containers/AT_TreeBitSetPeopleContainer.java b/gcm3/src/test/java/plugins/partitions/support/containers/AT_TreeBitSetPeopleContainer.java deleted file mode 100644 index 0891817c8..000000000 --- a/gcm3/src/test/java/plugins/partitions/support/containers/AT_TreeBitSetPeopleContainer.java +++ /dev/null @@ -1,77 +0,0 @@ -package plugins.partitions.support.containers; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.SimulationContext; -import plugins.partitions.support.PartitionError; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.PersonId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - - -@UnitTest(target = TreeBitSetPeopleContainer.class) -public class AT_TreeBitSetPeopleContainer { - - - private PeopleContainer getPeopleContainer(SimulationContext context) { - return new TreeBitSetPeopleContainer(context.getDataManager(PeopleDataManager.class)); - } - - @Test - @UnitTestConstructor(args = {PeopleDataManager.class}) - public void testConstructor() { - ContractException contractException = assertThrows(ContractException.class,()->new TreeBitSetPeopleContainer(null)); - assertEquals(PartitionError.NULL_PERSON_DATA_VIEW, contractException.getErrorType()); - } - - @Test - @UnitTestMethod(name = "getPeople", args= {}) - public void testGetPeople(){ - PeopleContainerTester.testGetPeople(this::getPeopleContainer,473484353360778267L); - } - - @Test - @UnitTestMethod(name = "safeAdd", args= {PersonId.class}) - public void testSafeAdd(){ - PeopleContainerTester.testSafeAdd(this::getPeopleContainer,1067260810269284703L); - } - - - @Test - @UnitTestMethod(name = "unsafeAdd", args= {PersonId.class}) - public void testUnsafeAdd(){ - PeopleContainerTester.testUnsafeAdd(this::getPeopleContainer,2640497434656632684L); - } - - @Test - @UnitTestMethod(name = "remove", args= {PersonId.class}) - public void testRemove(){ - PeopleContainerTester.testRemove(this::getPeopleContainer,1461315035567239819L); - } - - @Test - @UnitTestMethod(name = "size", args= {}) - public void testSize(){ - PeopleContainerTester.testSize(this::getPeopleContainer,5880341220076297803L); - } - - @Test - @UnitTestMethod(name = "", args= {PersonId.class}) - public void testContains(){ - PeopleContainerTester.testContains(this::getPeopleContainer,6865277196728541573L); - } - - @Test - @UnitTestMethod(name = "getRandomPersonId", args= {RandomGenerator.class}) - public void testGetRandomPersonId(){ - PeopleContainerTester.testGetRandomPersonId(this::getPeopleContainer,1976658500916036734L); - } - -} \ No newline at end of file diff --git a/gcm3/src/test/java/plugins/partitions/testsupport/attributes/AT_AttributesDataManager.java b/gcm3/src/test/java/plugins/partitions/testsupport/attributes/AT_AttributesDataManager.java deleted file mode 100644 index f906366e2..000000000 --- a/gcm3/src/test/java/plugins/partitions/testsupport/attributes/AT_AttributesDataManager.java +++ /dev/null @@ -1,271 +0,0 @@ -package plugins.partitions.testsupport.attributes; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.EnumSet; -import java.util.LinkedHashSet; -import java.util.Set; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.DataManagerContext; -import nucleus.EventLabeler; -import nucleus.NucleusError; -import nucleus.Plugin; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import plugins.partitions.testsupport.PartitionsActionSupport; -import plugins.partitions.testsupport.attributes.events.AttributeUpdateEvent; -import plugins.partitions.testsupport.attributes.support.AttributeDefinition; -import plugins.partitions.testsupport.attributes.support.AttributeError; -import plugins.partitions.testsupport.attributes.support.AttributeId; -import plugins.partitions.testsupport.attributes.support.TestAttributeId; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import plugins.stochastics.StochasticsDataManager; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - -@UnitTest(target = AttributesDataManager.class) -public class AT_AttributesDataManager { - - @Test - @UnitTestMethod(name = "init", args = { DataManagerContext.class }) - public void testAttributesDataViewInitialization() { - PartitionsActionSupport.testConsumer(0, 5241628071704306523L, (c) -> { - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - - assertEquals(EnumSet.allOf(TestAttributeId.class), attributesDataManager.getAttributeIds()); - - for (TestAttributeId testAttributeId : TestAttributeId.values()) { - assertEquals(testAttributeId.getAttributeDefinition(), attributesDataManager.getAttributeDefinition(testAttributeId)); - } - }); - } - - @Test - @UnitTestMethod(name = "init", args = { DataManagerContext.class }) - public void testAttributeUpdateEventLabelers() { - PartitionsActionSupport.testConsumer(0, 5241628071704306523L, (c) -> { - EventLabeler eventLabeler = AttributeUpdateEvent.getEventLabeler(); - assertNotNull(eventLabeler); - ContractException contractException = assertThrows(ContractException.class, () -> c.addEventLabeler(eventLabeler)); - assertEquals(NucleusError.DUPLICATE_LABELER_ID_IN_EVENT_LABELER, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "setAttributeValue", args = { PersonId.class, AttributeId.class, Object.class }) - - public void testSetAttributeValue() { - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - // add an agent that will observe attribute changes - Set peopleObserved = new LinkedHashSet<>(); - Set expectedPersonIds = new LinkedHashSet<>(); - for (int i = 0; i < 10; i++) { - expectedPersonIds.add(new PersonId(i)); - } - - pluginDataBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { - c.subscribe(AttributeUpdateEvent.getEventLabel(c, TestAttributeId.BOOLEAN_0), (c2, e) -> { - peopleObserved.add(e.getPersonId()); - }); - })); - - // add an agent that will show that the AttributesDataView is properly - // initialized - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - - for (PersonId personId : peopleDataManager.getPeople()) { - Boolean value = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); - - assertFalse(value); - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, true); - - value = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); - assertTrue(value); - } - - })); - - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - PartitionsActionSupport.testConsumers(expectedPersonIds.size(), 5241628071704306523L, testPlugin); - - // show that the correct observations were made; - assertEquals(expectedPersonIds, peopleObserved); - - } - - @Test - @UnitTestConstructor(args = { AttributesPluginData.class }) - public void testConstructor() { - ContractException contractException = assertThrows(ContractException.class, () -> new AttributesDataManager(null)); - assertEquals(AttributeError.NULL_ATTRIBUTE_INITIAL_DATA, contractException.getErrorType()); - } - - @Test - @UnitTestMethod(name = "attributeExists", args = { AttributeId.class }) - public void testAttributeExists() { - PartitionsActionSupport.testConsumer(100, 6136319242032948471L, (c) -> { - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - - for (TestAttributeId testAttributeId : TestAttributeId.values()) { - assertTrue(attributesDataManager.attributeExists(testAttributeId)); - } - - assertFalse(attributesDataManager.attributeExists(TestAttributeId.getUnknownAttributeId())); - - assertFalse(attributesDataManager.attributeExists(null)); - }); - } - - @Test - @UnitTestMethod(name = "getAttributeDefinition", args = { AttributeId.class }) - public void testGetAttributeDefinition() { - - PartitionsActionSupport.testConsumer(100, 7056826773207732206L, (c) -> { - - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - - for (TestAttributeId testAttributeId : TestAttributeId.values()) { - AttributeDefinition expectedAttributeDefinition = testAttributeId.getAttributeDefinition(); - AttributeDefinition actualAttributeDefinition = attributesDataManager.getAttributeDefinition(testAttributeId); - assertEquals(expectedAttributeDefinition, actualAttributeDefinition); - } - - // precondition tests: - - // if the attribute id is null - ContractException contractException = assertThrows(ContractException.class, () -> attributesDataManager.getAttributeDefinition(null)); - assertEquals(AttributeError.NULL_ATTRIBUTE_ID, contractException.getErrorType()); - - // if the attribute id unknown - contractException = assertThrows(ContractException.class, () -> attributesDataManager.getAttributeDefinition(TestAttributeId.getUnknownAttributeId())); - assertEquals(AttributeError.UNKNOWN_ATTRIBUTE_ID, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "getAttributeIds", args = {}) - public void testGetAttributeIds() { - PartitionsActionSupport.testConsumer(100, 3922883805893258744L, (c) -> { - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - assertEquals(EnumSet.allOf(TestAttributeId.class), attributesDataManager.getAttributeIds()); - }); - } - - @Test - @UnitTestMethod(name = "getAttributeValue", args = { PersonId.class, AttributeId.class }) - public void testGetAttributeValue() { - - PartitionsActionSupport.testConsumer(100, 3296963241519285254L, (c) -> { - - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - - // show that there are people in the test - assertEquals(100, peopleDataManager.getPopulationCount()); - - // set random attribute values on people - for (PersonId personId : peopleDataManager.getPeople()) { - - Boolean b0_expected = randomGenerator.nextBoolean(); - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, b0_expected); - Boolean b0_actual = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); - assertEquals(b0_expected, b0_actual); - - Boolean b1_expected = randomGenerator.nextBoolean(); - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_1, b1_expected); - Boolean b1_actual = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_1); - assertEquals(b1_expected, b1_actual); - - Integer i0_expected = randomGenerator.nextInt(); - attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_0, i0_expected); - Integer i0_actual = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_0); - assertEquals(i0_expected, i0_actual); - - Integer i1_expected = randomGenerator.nextInt(); - attributesDataManager.setAttributeValue(personId, TestAttributeId.INT_1, i1_expected); - Integer i1_actual = attributesDataManager.getAttributeValue(personId, TestAttributeId.INT_1); - assertEquals(i1_expected, i1_actual); - - Double d0_expected = randomGenerator.nextDouble(); - attributesDataManager.setAttributeValue(personId, TestAttributeId.DOUBLE_0, d0_expected); - Double d0_actual = attributesDataManager.getAttributeValue(personId, TestAttributeId.DOUBLE_0); - assertEquals(d0_expected, d0_actual); - - Double d1_expected = randomGenerator.nextDouble(); - attributesDataManager.setAttributeValue(personId, TestAttributeId.DOUBLE_1, d1_expected); - Double d1_actual = attributesDataManager.getAttributeValue(personId, TestAttributeId.DOUBLE_1); - assertEquals(d1_expected, d1_actual); - - } - - }); - - PartitionsActionSupport.testConsumer(100, 3296963241519285254L, (c) -> { - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - - PersonId goodPersonId = new PersonId(0); - - // if the attribute id is null - ContractException contractException = assertThrows(ContractException.class, () -> attributesDataManager.getAttributeValue(goodPersonId, null)); - assertEquals(AttributeError.NULL_ATTRIBUTE_ID, contractException.getErrorType()); - - }); - - PartitionsActionSupport.testConsumer(100, 3296963241519285254L, (c) -> { - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - - PersonId goodPersonId = new PersonId(0); - AttributeId badAttributeId = TestAttributeId.getUnknownAttributeId(); - - // if the attribute id unknown - ContractException contractException = assertThrows(ContractException.class, () -> attributesDataManager.getAttributeValue(goodPersonId, badAttributeId)); - assertEquals(AttributeError.UNKNOWN_ATTRIBUTE_ID, contractException.getErrorType()); - - }); - - PartitionsActionSupport.testConsumer(100, 3296963241519285254L, (c) -> { - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - - AttributeId goodAttributeId = TestAttributeId.BOOLEAN_0; - - // if the person id is null - ContractException contractException = assertThrows(ContractException.class, () -> attributesDataManager.getAttributeValue(null, goodAttributeId)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - - }); - - PartitionsActionSupport.testConsumer(100, 3296963241519285254L, (c) -> { - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - PersonId badPersonId = new PersonId(10000000); - AttributeId goodAttributeId = TestAttributeId.BOOLEAN_0; - // if the person id is unknown - ContractException contractException = assertThrows(ContractException.class, () -> attributesDataManager.getAttributeValue(badPersonId, goodAttributeId)); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - - }); - - } - -} diff --git a/gcm3/src/test/java/plugins/partitions/testsupport/attributes/AT_AttributesPlugin.java b/gcm3/src/test/java/plugins/partitions/testsupport/attributes/AT_AttributesPlugin.java deleted file mode 100644 index 662536c58..000000000 --- a/gcm3/src/test/java/plugins/partitions/testsupport/attributes/AT_AttributesPlugin.java +++ /dev/null @@ -1,89 +0,0 @@ -package plugins.partitions.testsupport.attributes; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import nucleus.Plugin; -import nucleus.PluginData; -import nucleus.PluginId; -import nucleus.Simulation; -import nucleus.testsupport.testplugin.ScenarioPlanCompletionObserver; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import nucleus.testsupport.testplugin.TestPluginData.Builder; -import plugins.partitions.PartitionsPlugin; -import plugins.partitions.PartitionsPluginId; -import plugins.people.PeoplePlugin; -import plugins.people.PeoplePluginData; -import plugins.people.PeoplePluginId; -import plugins.stochastics.StochasticsPlugin; -import plugins.stochastics.StochasticsPluginData; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = AttributesPlugin.class) -public class AT_AttributesPlugin { - - @Test - @UnitTestMethod(name = "getAttributesPlugin", args = { AttributesPluginData.class }) - public void testGetAttributesPlugin() { - - /* - * Create an attributes plugin - */ - AttributesPluginData attributesPluginData = AttributesPluginData.builder().build(); - Plugin attributesPlugin = AttributesPlugin.getAttributesPlugin(attributesPluginData); - - // show that the plugin data is present - Set pluginDatas = attributesPlugin.getPluginDatas(); - assertNotNull(pluginDatas); - assertEquals(1, pluginDatas.size()); - assertTrue(pluginDatas.contains(attributesPluginData)); - - // show that the plugin has the expected id - assertEquals(AttributesPluginId.PLUGIN_ID, attributesPlugin.getPluginId()); - - // show that the plugin has the correct dependencies - Set expectedDependencies = new LinkedHashSet<>(); - expectedDependencies.add(PeoplePluginId.PLUGIN_ID); - expectedDependencies.add(PartitionsPluginId.PLUGIN_ID); - Set actualDependencies = attributesPlugin.getPluginDependencies(); - assertEquals(expectedDependencies, actualDependencies); - - //show that the plugin contributed the AttributesDataManager to the simulation - Builder testPluginDataBuilder = TestPluginData.builder(); - - testPluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(0,(c)->{ - AttributesDataManager dataManager = c.getDataManager(AttributesDataManager.class); - assertNotNull(dataManager); - })); - - TestPluginData testPluginData = testPluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - - Simulation .builder()// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput)// - .addPlugin(StochasticsPlugin.getStochasticsPlugin(StochasticsPluginData.builder().setSeed(435346454564566L).build()))// - .addPlugin(PeoplePlugin.getPeoplePlugin(PeoplePluginData.builder().build()))// - .addPlugin(PartitionsPlugin.getPartitionsPlugin()) - .addPlugin(attributesPlugin)// - .addPlugin(testPlugin)// - .build()// - .execute();// - - - - assertTrue(scenarioPlanCompletionObserver.allPlansExecuted()); - - } - -} diff --git a/gcm3/src/test/java/plugins/partitions/testsupport/attributes/AT_AttributesPluginData.java b/gcm3/src/test/java/plugins/partitions/testsupport/attributes/AT_AttributesPluginData.java deleted file mode 100644 index 571f60e27..000000000 --- a/gcm3/src/test/java/plugins/partitions/testsupport/attributes/AT_AttributesPluginData.java +++ /dev/null @@ -1,99 +0,0 @@ -package plugins.partitions.testsupport.attributes; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.util.EnumSet; - -import org.junit.jupiter.api.Test; - -import plugins.partitions.testsupport.attributes.support.AttributeDefinition; -import plugins.partitions.testsupport.attributes.support.AttributeError; -import plugins.partitions.testsupport.attributes.support.AttributeId; -import plugins.partitions.testsupport.attributes.support.TestAttributeId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - -@UnitTest(target = AttributesPluginData.class) -public class AT_AttributesPluginData { - - @Test - @UnitTestMethod(name = "builder", args = {}) - public void testBuilder() { - assertNotNull(AttributesPluginData.builder()); - } - - @Test - @UnitTestMethod(target = AttributesPluginData.Builder.class, name = "build", args = {}) - public void testBuild() { - assertNotNull(AttributesPluginData.builder().build()); - } - - @Test - @UnitTestMethod(target = AttributesPluginData.Builder.class, name = "defineAttribute", args = { AttributeId.class, AttributeDefinition.class }) - public void testDefineAttribute() { - AttributesPluginData.Builder builder = AttributesPluginData.builder(); - for (TestAttributeId testAttributeId : TestAttributeId.values()) { - builder.defineAttribute(testAttributeId, testAttributeId.getAttributeDefinition()); - } - AttributesPluginData attributesPluginData = builder.build(); - assertEquals(EnumSet.allOf(TestAttributeId.class), attributesPluginData.getAttributeIds()); - for (TestAttributeId testAttributeId : TestAttributeId.values()) { - assertEquals(testAttributeId.getAttributeDefinition(), attributesPluginData.getAttributeDefinition(testAttributeId)); - } - - // precondition tests - - // if the attribute id is null - ContractException contractException = assertThrows(ContractException.class, () -> builder.defineAttribute(null, TestAttributeId.BOOLEAN_0.getAttributeDefinition())); - assertEquals(AttributeError.NULL_ATTRIBUTE_ID, contractException.getErrorType()); - - // if the attribute definition is null - contractException = assertThrows(ContractException.class, () -> builder.defineAttribute(TestAttributeId.BOOLEAN_0, null)); - assertEquals(AttributeError.NULL_ATTRIBUTE_DEFINITION, contractException.getErrorType()); - - // if the attribute id was previously added - builder.defineAttribute(TestAttributeId.BOOLEAN_0, TestAttributeId.BOOLEAN_0.getAttributeDefinition()); - contractException = assertThrows(ContractException.class, () -> builder.defineAttribute(TestAttributeId.BOOLEAN_0, TestAttributeId.BOOLEAN_0.getAttributeDefinition())); - assertEquals(AttributeError.DUPLICATE_ATTRIBUTE_DEFINITION, contractException.getErrorType()); - } - - @Test - @UnitTestMethod(name = "getAttributeDefinition", args = { AttributeId.class }) - public void testGetAttributeDefinition() { - AttributesPluginData.Builder builder = AttributesPluginData.builder(); - for (TestAttributeId testAttributeId : TestAttributeId.values()) { - builder.defineAttribute(testAttributeId, testAttributeId.getAttributeDefinition()); - } - AttributesPluginData attributesPluginData = builder.build(); - assertEquals(EnumSet.allOf(TestAttributeId.class), attributesPluginData.getAttributeIds()); - for (TestAttributeId testAttributeId : TestAttributeId.values()) { - assertEquals(testAttributeId.getAttributeDefinition(), attributesPluginData.getAttributeDefinition(testAttributeId)); - } - - //precondition tests - - // if the attribute id is null - ContractException contractException = assertThrows(ContractException.class, () -> attributesPluginData.getAttributeDefinition(null)); - assertEquals(AttributeError.NULL_ATTRIBUTE_ID, contractException.getErrorType()); - - // if the attribute id is unknown - contractException = assertThrows(ContractException.class, () -> attributesPluginData.getAttributeDefinition(TestAttributeId.getUnknownAttributeId())); - assertEquals(AttributeError.UNKNOWN_ATTRIBUTE_ID, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "getAttributeIds", args = {}) - public void testGetAttributeIds() { - AttributesPluginData.Builder builder = AttributesPluginData.builder(); - for (TestAttributeId testAttributeId : TestAttributeId.values()) { - builder.defineAttribute(testAttributeId, testAttributeId.getAttributeDefinition()); - } - AttributesPluginData attributesPluginData = builder.build(); - assertEquals(EnumSet.allOf(TestAttributeId.class), attributesPluginData.getAttributeIds()); - } - -} diff --git a/gcm3/src/test/java/plugins/partitions/testsupport/attributes/events/AT_AttributeUpdateEvent.java b/gcm3/src/test/java/plugins/partitions/testsupport/attributes/events/AT_AttributeUpdateEvent.java deleted file mode 100644 index 0eb85cafe..000000000 --- a/gcm3/src/test/java/plugins/partitions/testsupport/attributes/events/AT_AttributeUpdateEvent.java +++ /dev/null @@ -1,77 +0,0 @@ -package plugins.partitions.testsupport.attributes.events; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -import nucleus.Event; -import plugins.partitions.testsupport.attributes.support.AttributeId; -import plugins.partitions.testsupport.attributes.support.TestAttributeId; -import plugins.people.support.PersonId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = AttributeUpdateEvent.class) -public class AT_AttributeUpdateEvent implements Event { - - @Test - @UnitTestConstructor(args = { PersonId.class, AttributeId.class, Object.class, Object.class }) - public void testConstructor() { - // nothing to test - } - - @Test - @UnitTestMethod(name = "getCurrentValue", args = {}) - public void testGetCurrentValue() { - PersonId personId = new PersonId(10); - AttributeId attributeId = TestAttributeId.INT_0; - for (int i = 0; i < 10; i++) { - int currentValue = i * 2 + 3; - AttributeUpdateEvent attributeUpdateEvent = new AttributeUpdateEvent(personId, attributeId, 0, currentValue); - assertEquals(currentValue, attributeUpdateEvent.getCurrentValue()); - } - } - - @Test - @UnitTestMethod(name = "getAttributeId", args = {}) - public void testGetAttributeId() { - PersonId personId = new PersonId(10); - for (TestAttributeId testAttributeId : TestAttributeId.values()) { - AttributeUpdateEvent attributeUpdateEvent = new AttributeUpdateEvent(personId, testAttributeId, false, true); - assertEquals(testAttributeId, attributeUpdateEvent.getAttributeId()); - } - - } - - @Test - @UnitTestMethod(name = "getPersonId", args = {}) - public void testGetPersonId() { - AttributeId attributeId = TestAttributeId.BOOLEAN_0; - for (int i = 0; i < 10; i++) { - PersonId personId = new PersonId(i); - AttributeUpdateEvent attributeUpdateEvent = new AttributeUpdateEvent(personId, attributeId, false, true); - assertEquals(personId, attributeUpdateEvent.getPersonId()); - } - } - - @Test - @UnitTestMethod(name = "getPreviousValue", args = {}) - public void testGetPreviousValue() { - PersonId personId = new PersonId(10); - AttributeId attributeId = TestAttributeId.INT_0; - for (int i = 0; i < 10; i++) { - int previousValue = i * 2 + 3; - AttributeUpdateEvent attributeUpdateEvent = new AttributeUpdateEvent(personId, attributeId, previousValue, 0); - assertEquals(previousValue, attributeUpdateEvent.getPreviousValue()); - } - } - - @Test - @UnitTestMethod(name = "getPrimaryKeyValue", args = {}) - public void testGetPrimaryKeyValue() { - for (TestAttributeId testAttributeId : TestAttributeId.values()) { - assertEquals(testAttributeId, new AttributeUpdateEvent(null, testAttributeId, null, null).getPrimaryKeyValue()); - } - } -} diff --git a/gcm3/src/test/java/plugins/partitions/testsupport/attributes/support/AT_AttributeError.java b/gcm3/src/test/java/plugins/partitions/testsupport/attributes/support/AT_AttributeError.java deleted file mode 100644 index 482047fc8..000000000 --- a/gcm3/src/test/java/plugins/partitions/testsupport/attributes/support/AT_AttributeError.java +++ /dev/null @@ -1,38 +0,0 @@ -package plugins.partitions.testsupport.attributes.support; - -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - -/** - * An enumeration supporting {@link ContractException} that acts as a general - * description of the exception. - * - * @author Shawn Hatch - * - */ -@UnitTest(target = AttributeError.class) -public class AT_AttributeError { - @Test - @UnitTestMethod(name = "getDescription", args = {}) - public void test() { - // show that each description is a unique, non-null and non-empty string - Set descriptions = new LinkedHashSet<>(); - for (AttributeError attributeError : AttributeError.values()) { - String description = attributeError.getDescription(); - assertNotNull(description, "null description for " + attributeError); - assertTrue(description.length() > 0, "empty string for " + attributeError); - boolean unique = descriptions.add(description); - assertTrue(unique, "description for " + attributeError + " is not unique"); - } - } - -} diff --git a/gcm3/src/test/java/plugins/partitions/testsupport/attributes/support/AT_AttributeFilter.java b/gcm3/src/test/java/plugins/partitions/testsupport/attributes/support/AT_AttributeFilter.java deleted file mode 100644 index 7fcdfab07..000000000 --- a/gcm3/src/test/java/plugins/partitions/testsupport/attributes/support/AT_AttributeFilter.java +++ /dev/null @@ -1,254 +0,0 @@ -package plugins.partitions.testsupport.attributes.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.Set; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.Plugin; -import nucleus.Simulation; -import nucleus.Simulation.Builder; -import nucleus.SimulationContext; -import nucleus.testsupport.testplugin.ScenarioPlanCompletionObserver; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestError; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import plugins.partitions.PartitionsPlugin; -import plugins.partitions.support.Equality; -import plugins.partitions.support.Filter; -import plugins.partitions.support.FilterSensitivity; -import plugins.partitions.support.PartitionError; -import plugins.partitions.testsupport.PartitionsActionSupport; -import plugins.partitions.testsupport.attributes.AttributesDataManager; -import plugins.partitions.testsupport.attributes.AttributesPlugin; -import plugins.partitions.testsupport.attributes.AttributesPluginData; -import plugins.partitions.testsupport.attributes.events.AttributeUpdateEvent; -import plugins.people.PeoplePlugin; -import plugins.people.PeoplePluginData; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.PersonId; -import plugins.stochastics.StochasticsDataManager; -import plugins.stochastics.StochasticsPlugin; -import plugins.stochastics.StochasticsPluginData; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - -@UnitTest(target = AttributeFilter.class) -public final class AT_AttributeFilter { - - @Test - @UnitTestConstructor(args = { AttributeId.class, Equality.class, Object.class }) - public void testConstructor() { - // nothing to test - } - - private static enum LocalAttributeId implements AttributeId { - DATA_ID - } - - private static class Data { - private final int value; - - public Data(int value) { - this.value = value; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + value; - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof Data)) { - return false; - } - Data other = (Data) obj; - if (value != other.value) { - return false; - } - return true; - } - } - - @Test - @UnitTestMethod(name = "validate", args = { SimulationContext.class }) - public void testValidate() { - int initialPopulation = 100; - - final Builder builder = Simulation.builder(); - // define some person attributes - final AttributesPluginData.Builder attributesBuilder = AttributesPluginData.builder(); - for (final TestAttributeId testAttributeId : TestAttributeId.values()) { - attributesBuilder.defineAttribute(testAttributeId, testAttributeId.getAttributeDefinition()); - } - AttributeDefinition attributeDefinition = AttributeDefinition.builder().setDefaultValue(new Data(7)).setType(Data.class).build(); - attributesBuilder.defineAttribute(LocalAttributeId.DATA_ID, attributeDefinition); - - Plugin attributesPlugin = AttributesPlugin.getAttributesPlugin(attributesBuilder.build()); - - builder.addPlugin(attributesPlugin); - - final PeoplePluginData.Builder peopleBuilder = PeoplePluginData.builder(); - for (int i = 0; i < initialPopulation; i++) { - peopleBuilder.addPersonId(new PersonId(i)); - } - - PeoplePluginData peoplePluginData = peopleBuilder.build(); - Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(peoplePluginData); - builder.addPlugin(peoplePlugin); - - builder.addPlugin(StochasticsPlugin.getStochasticsPlugin(StochasticsPluginData.builder().setSeed(7698506335486677498L).build())); - - builder.addPlugin(PartitionsPlugin.getPartitionsPlugin()); - - // and add the action plugin to the engine - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - // if the filter's attribute id is null - ContractException contractException = assertThrows(ContractException.class, () -> new AttributeFilter(null, Equality.EQUAL, false).validate(c)); - assertEquals(AttributeError.NULL_ATTRIBUTE_ID, contractException.getErrorType()); - - // if the filter's equality operator is null - contractException = assertThrows(ContractException.class, () -> new AttributeFilter(TestAttributeId.BOOLEAN_0, null, false).validate(c)); - assertEquals(PartitionError.NULL_EQUALITY_OPERATOR, contractException.getErrorType()); - - // if the filter's value is null - contractException = assertThrows(ContractException.class, () -> new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, null).validate(c)); - assertEquals(AttributeError.NULL_ATTRIBUTE_VALUE, contractException.getErrorType()); - - // if the filter's value is incompatible with the attribute - // definition associated with the filter's attribute id. - contractException = assertThrows(ContractException.class, () -> new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, 5).validate(c)); - assertEquals(AttributeError.INCOMPATIBLE_VALUE, contractException.getErrorType()); - - // if the filter's value is not a COMPARABLE when the filter's - // equality operator is not EQUALS or NOT_EQUALS. - - contractException = assertThrows(ContractException.class, () -> new AttributeFilter(LocalAttributeId.DATA_ID, Equality.GREATER_THAN, new Data(12)).validate(c)); - assertEquals(PartitionError.NON_COMPARABLE_ATTRIBUTE, contractException.getErrorType()); - - })); - - TestPluginData testPluginData = pluginBuilder.build(); - builder.addPlugin(TestPlugin.getTestPlugin(testPluginData)); - - // build and execute the engine - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - builder// - .setOutputConsumer(scenarioPlanCompletionObserver::handleOutput)// - .build()// - .execute(); - - // show that all actions were executed - if (!scenarioPlanCompletionObserver.allPlansExecuted()) { - throw new ContractException(TestError.TEST_EXECUTION_FAILURE); - } - - } - - @Test - @UnitTestMethod(name = "evaluate", args = { SimulationContext.class, PersonId.class }) - public void testEvaluate() { - PartitionsActionSupport.testConsumer(100, 2853953940626718331L, (c) -> { - Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - for (PersonId personId : peopleDataManager.getPeople()) { - boolean value = randomGenerator.nextBoolean(); - attributesDataManager.setAttributeValue(personId, TestAttributeId.BOOLEAN_0, value); - boolean expected = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); - boolean actual = filter.evaluate(c, personId); - assertEquals(expected, actual); - } - - - }); - - /* precondition: if the context is null */ - PartitionsActionSupport.testConsumer(100, 1011872226453537614L, (c) -> { - Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); - assertThrows(RuntimeException.class, () -> filter.evaluate(null, new PersonId(0))); - }); - - /* precondition: if the person id is null */ - PartitionsActionSupport.testConsumer(100, 6858667758520667469L, (c) -> { - Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); - assertThrows(RuntimeException.class, () -> filter.evaluate(c, null)); - }); - - /* precondition: if the person id is unknown */ - PartitionsActionSupport.testConsumer(100, 9106972672436024633L, (c) -> { - Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, true); - assertThrows(RuntimeException.class, () -> filter.evaluate(c, new PersonId(123412342))); - }); - - } - - @Test - @UnitTestMethod(name = "getFilterSensitivities", args = {}) - public void testGetFilterSensitivities() { - - PartitionsActionSupport.testConsumer(100, 3455263917994200075L, (c) -> { - - // create an attribute filter - Filter filter = new AttributeFilter(TestAttributeId.BOOLEAN_0, Equality.EQUAL, false); - - /* - * show the filter has a single sensitivity - */ - Set> filterSensitivities = filter.getFilterSensitivities(); - assertNotNull(filterSensitivities); - assertEquals(filterSensitivities.size(), 1); - - /* - * show that this sensitivity is associated with - * AttributeUpdateEvent events. - */ - FilterSensitivity filterSensitivity = filterSensitivities.iterator().next(); - assertEquals(AttributeUpdateEvent.class, filterSensitivity.getEventClass()); - - /* - * Show that the sensitivity requires refresh for - * AttributeUpdateEvent events if and only if the - * attribute ids are equal and the event has different previous and - * current values. - */ - PersonId personId = new PersonId(0); - - AttributeUpdateEvent attributeUpdateEvent = new AttributeUpdateEvent(personId, TestAttributeId.BOOLEAN_0, false, true); - - assertTrue(filterSensitivity.requiresRefresh(c, attributeUpdateEvent).isPresent()); - - attributeUpdateEvent = new AttributeUpdateEvent(personId, TestAttributeId.BOOLEAN_0, false, false); - - assertFalse(filterSensitivity.requiresRefresh(c, attributeUpdateEvent).isPresent()); - - attributeUpdateEvent = new AttributeUpdateEvent(personId, TestAttributeId.BOOLEAN_1, false, true); - - assertFalse(filterSensitivity.requiresRefresh(c, attributeUpdateEvent).isPresent()); - - }); - - } - -} diff --git a/gcm3/src/test/java/plugins/partitions/testsupport/attributes/support/AT_AttributeId.java b/gcm3/src/test/java/plugins/partitions/testsupport/attributes/support/AT_AttributeId.java deleted file mode 100644 index ddc82c7ec..000000000 --- a/gcm3/src/test/java/plugins/partitions/testsupport/attributes/support/AT_AttributeId.java +++ /dev/null @@ -1,12 +0,0 @@ -package plugins.partitions.testsupport.attributes.support; - -import tools.annotations.UnitTest; - -@UnitTest(target = AttributeId.class) -public class AT_AttributeId { - - public void test() { - // nothing to test - } - -} diff --git a/gcm3/src/test/java/plugins/partitions/testsupport/attributes/support/AT_AttributeLabeler.java b/gcm3/src/test/java/plugins/partitions/testsupport/attributes/support/AT_AttributeLabeler.java deleted file mode 100644 index 387fdd023..000000000 --- a/gcm3/src/test/java/plugins/partitions/testsupport/attributes/support/AT_AttributeLabeler.java +++ /dev/null @@ -1,138 +0,0 @@ -package plugins.partitions.testsupport.attributes.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; - -import org.junit.jupiter.api.Test; - -import nucleus.Plugin; -import nucleus.SimulationContext; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import plugins.partitions.support.LabelerSensitivity; -import plugins.partitions.testsupport.PartitionsActionSupport; -import plugins.partitions.testsupport.attributes.AttributesDataManager; -import plugins.partitions.testsupport.attributes.events.AttributeUpdateEvent; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - -@UnitTest(target = AttributeLabeler.class) -public final class AT_AttributeLabeler { - - @Test - @UnitTestConstructor(args = { AttributeId.class, Function.class }) - public void testConstructor() { - // nothing to test - } - - @Test - @UnitTestMethod(name = "getLabelerSensitivities", args = { AttributeId.class, Function.class }) - public void testGetLabelerSensitivities() { - /* - * Get the labeler sensitivities and show that they are consistent with - * their documented behaviors. - */ - - AttributeLabeler attributeLabeler = new AttributeLabeler(TestAttributeId.BOOLEAN_0, (c) -> null); - - Set> labelerSensitivities = attributeLabeler.getLabelerSensitivities(); - - // show that there is exactly one sensitivity - assertEquals(1, labelerSensitivities.size()); - - // show that the sensitivity is associated with - // AttributeUpdateEvent - LabelerSensitivity labelerSensitivity = labelerSensitivities.iterator().next(); - assertEquals(AttributeUpdateEvent.class, labelerSensitivity.getEventClass()); - - // show that the sensitivity will return the person id from a - // AttributeUpdateEvent - PersonId personId = new PersonId(56); - AttributeUpdateEvent attributeUpdateEvent = new AttributeUpdateEvent(personId, TestAttributeId.BOOLEAN_0, false, true); - Optional optional = labelerSensitivity.getPersonId(attributeUpdateEvent); - assertTrue(optional.isPresent()); - assertEquals(personId, optional.get()); - - } - - @Test - @UnitTestMethod(name = "getLabel", args = { SimulationContext.class, PersonId.class }) - public void testGetLabel() { - // build an attribute function - Function function = (c) -> { - Boolean value = (Boolean) c; - if (value) { - return "A"; - } - return "B"; - }; - - AttributeLabeler attributeLabeler = new AttributeLabeler(TestAttributeId.BOOLEAN_0, function); - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - - - /* - * - */ - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - AttributesDataManager attributesDataManager = c.getDataManager(AttributesDataManager.class); - List people = peopleDataManager.getPeople(); - for (PersonId personId : people) { - - // get the person's attribute value and apply the function - // directly - Boolean b0 = attributesDataManager.getAttributeValue(personId, TestAttributeId.BOOLEAN_0); - Object expectedLabel = function.apply(b0); - - // get the label from the person id - Object actualLabel = attributeLabeler.getLabel(c, personId); - - // show that the two labels are equal - assertEquals(expectedLabel, actualLabel); - - } - })); - - // test preconditions - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - - // if the person does not exist - ContractException contractException = assertThrows(ContractException.class, () -> attributeLabeler.getLabel(c, new PersonId(-1))); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - - // if the person id is null - contractException = assertThrows(ContractException.class, () -> attributeLabeler.getLabel(c, null)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - PartitionsActionSupport.testConsumers(10, 4676319446289433016L, testPlugin); - - } - - @Test - @UnitTestMethod(name = "getDimension", args = {}) - public void testGetDimension() { - for (TestAttributeId testAttributeId : TestAttributeId.values()) { - assertEquals(testAttributeId, new AttributeLabeler(testAttributeId, (c) -> null).getDimension()); - } - } - -} diff --git a/gcm3/src/test/java/plugins/partitions/testsupport/attributes/support/AT_TestAttributeId.java b/gcm3/src/test/java/plugins/partitions/testsupport/attributes/support/AT_TestAttributeId.java deleted file mode 100644 index 8b9fe94cb..000000000 --- a/gcm3/src/test/java/plugins/partitions/testsupport/attributes/support/AT_TestAttributeId.java +++ /dev/null @@ -1,46 +0,0 @@ -package plugins.partitions.testsupport.attributes.support; - -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = TestAttributeId.class) -public class AT_TestAttributeId { - - @Test - @UnitTestMethod(name = "getUnknownAttributeId", args = {}) - public void testGetUnknownAttributeId() { - /* - * Show that a generated unknown attribute id is not null and not a - * member of the enum - */ - Set attributeIds = new LinkedHashSet<>(); - for (int i = 0; i < 30; i++) { - AttributeId unknownAttributeId = TestAttributeId.getUnknownAttributeId(); - assertNotNull(unknownAttributeId); - boolean unique = attributeIds.add(unknownAttributeId); - assertTrue(unique); - for (TestAttributeId testAttributeId : TestAttributeId.values()) { - assertNotEquals(testAttributeId, unknownAttributeId); - } - } - } - - @Test - @UnitTestMethod(name = "getAttributeDefinition", args = {}) - public void testGetAttributeDefinition() { - // show that each member has an attribute definition - for (TestAttributeId testAttributeId : TestAttributeId.values()) { - assertNotNull(testAttributeId.getAttributeDefinition()); - } - } - -} diff --git a/gcm3/src/test/java/plugins/people/AT_PeoplePlugin.java b/gcm3/src/test/java/plugins/people/AT_PeoplePlugin.java deleted file mode 100644 index 7f2d1ed2c..000000000 --- a/gcm3/src/test/java/plugins/people/AT_PeoplePlugin.java +++ /dev/null @@ -1,32 +0,0 @@ -package plugins.people; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; - -import nucleus.Plugin; -import plugins.globalproperties.GlobalPropertiesPluginData; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = PeoplePlugin.class) -public class AT_PeoplePlugin { - - - @Test - @UnitTestMethod(name = "getPlugin", args = { GlobalPropertiesPluginData.class }) - public void testGetPlugin() { - - - PeoplePluginData peoplePluginData = PeoplePluginData.builder().build(); - Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(peoplePluginData); - - assertTrue(peoplePlugin.getPluginDatas().contains(peoplePluginData)); - assertEquals(PeoplePluginId.PLUGIN_ID, peoplePlugin.getPluginId()); - - assertTrue(peoplePlugin.getPluginDependencies().isEmpty()); - - } - -} diff --git a/gcm3/src/test/java/plugins/people/AT_PeoplePluginData.java b/gcm3/src/test/java/plugins/people/AT_PeoplePluginData.java deleted file mode 100644 index 590d1dfa3..000000000 --- a/gcm3/src/test/java/plugins/people/AT_PeoplePluginData.java +++ /dev/null @@ -1,116 +0,0 @@ -package plugins.people; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - -@UnitTest(target = PeoplePluginData.class) -public final class AT_PeoplePluginData { - @Test - @UnitTestMethod(name = "builder", args = {}) - public void testBuilder() { - assertNotNull(PeoplePluginData.builder()); - } - - @Test - @UnitTestMethod(target = PeoplePluginData.Builder.class, name = "build", args = {}) - public void testBuild() { - PeoplePluginData peoplePluginData = PeoplePluginData.builder().build(); - assertTrue(peoplePluginData.getPersonIds().isEmpty()); - - Set expectedPersonIds = new LinkedHashSet<>(); - for(int i = 0;i<10;i++) { - expectedPersonIds.add(new PersonId(3*1+5)); - } - PeoplePluginData.Builder builder = PeoplePluginData.builder(); - for(PersonId personId : expectedPersonIds) { - builder.addPersonId(personId); - } - - peoplePluginData = builder.build(); - assertEquals(expectedPersonIds, peoplePluginData.getPersonIds()); - - } - - @Test - @UnitTestMethod(target = PeoplePluginData.Builder.class, name = "addPersonId", args = { PersonId.class }) - public void testAddPersonId() { - PeoplePluginData peoplePluginData = PeoplePluginData.builder().build(); - assertTrue(peoplePluginData.getPersonIds().isEmpty()); - - Set expectedPersonIds = new LinkedHashSet<>(); - for(int i = 0;i<10;i++) { - expectedPersonIds.add(new PersonId(3*1+5)); - } - PeoplePluginData.Builder builder = PeoplePluginData.builder(); - for(PersonId personId : expectedPersonIds) { - builder.addPersonId(personId); - } - - peoplePluginData = builder.build(); - assertEquals(expectedPersonIds, peoplePluginData.getPersonIds()); - - //precondition tests - builder.addPersonId(new PersonId(5)); - - ContractException contractException = assertThrows(ContractException.class,()->builder.addPersonId(new PersonId(5))); - assertEquals(PersonError.DUPLICATE_PERSON_ID, contractException.getErrorType()); - - contractException = assertThrows(ContractException.class,()->builder.addPersonId(null)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "getPersonIds", args = {}) - public void testGetPersonIds() { - PeoplePluginData peoplePluginData = PeoplePluginData.builder().build(); - assertTrue(peoplePluginData.getPersonIds().isEmpty()); - - Set expectedPersonIds = new LinkedHashSet<>(); - for(int i = 0;i<10;i++) { - expectedPersonIds.add(new PersonId(3*1+5)); - } - PeoplePluginData.Builder builder = PeoplePluginData.builder(); - for(PersonId personId : expectedPersonIds) { - builder.addPersonId(personId); - } - - peoplePluginData = builder.build(); - assertEquals(expectedPersonIds, peoplePluginData.getPersonIds()); - - } - - @Test - @UnitTestMethod(name = "getCloneBuilder", args = {}) - public void testGetCloneBuilder() { - - Set expectedPersonIds = new LinkedHashSet<>(); - for(int i = 0;i<10;i++) { - expectedPersonIds.add(new PersonId(3*1+5)); - } - PeoplePluginData.Builder builder = PeoplePluginData.builder(); - for(PersonId personId : expectedPersonIds) { - builder.addPersonId(personId); - } - - PeoplePluginData peoplePluginData = builder.build(); - PeoplePluginData peoplePluginData2 = (PeoplePluginData)peoplePluginData.getCloneBuilder().build(); - - assertEquals(expectedPersonIds, peoplePluginData2.getPersonIds()); - } - - -} diff --git a/gcm3/src/test/java/plugins/people/AT_PeoplePluginId.java b/gcm3/src/test/java/plugins/people/AT_PeoplePluginId.java deleted file mode 100644 index 36763cef0..000000000 --- a/gcm3/src/test/java/plugins/people/AT_PeoplePluginId.java +++ /dev/null @@ -1,13 +0,0 @@ -package plugins.people; - -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import tools.annotations.UnitTest; - -@UnitTest(target = PeoplePluginId.class) -public class AT_PeoplePluginId { - - public void test() { - assertNotNull(PeoplePluginId.PLUGIN_ID); - } -} diff --git a/gcm3/src/test/java/plugins/people/datamanagers/AT_PeopleDataManager.java b/gcm3/src/test/java/plugins/people/datamanagers/AT_PeopleDataManager.java deleted file mode 100644 index bcfc1878d..000000000 --- a/gcm3/src/test/java/plugins/people/datamanagers/AT_PeopleDataManager.java +++ /dev/null @@ -1,463 +0,0 @@ -package plugins.people.datamanagers; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; - -import org.apache.commons.math3.util.FastMath; -import org.junit.jupiter.api.Test; - -import nucleus.Plugin; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import plugins.people.PeoplePluginData; -import plugins.people.events.BulkPersonAdditionEvent; -import plugins.people.events.PersonAdditionEvent; -import plugins.people.events.PersonImminentRemovalEvent; -import plugins.people.support.BulkPersonConstructionData; -import plugins.people.support.PersonConstructionData; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import plugins.people.testsupport.PeopleActionSupport; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - -@UnitTest(target = PeopleDataManager.class) -public final class AT_PeopleDataManager { - - @Test - @UnitTestMethod(name = "personIndexExists", args = { int.class }) - public void testPersonIndexExists() { - - PeopleActionSupport.testConsumer(0,(c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - - // initially there are no people despite the initial size - assertFalse(peopleDataManager.personIndexExists(-1)); - assertFalse(peopleDataManager.personIndexExists(0)); - assertFalse(peopleDataManager.personIndexExists(1)); - - // show that we can add a few people and for each the manager will - // indicate that they exist - for (int i = 0; i < 10; i++) { - PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); - assertTrue(peopleDataManager.personIndexExists(personId.getValue())); - } - - // show that people who should not exist, actually don't exist - assertFalse(peopleDataManager.personIndexExists(-1)); - for (int i = 10; i < 20; i++) { - assertFalse(peopleDataManager.personIndexExists(i)); - } - - }); - - } - - @Test - @UnitTestMethod(name = "getPersonIdLimit", args = {}) - public void testGetPersonIdLimit() { - PeopleActionSupport.testConsumer(0,(c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - /* - * Initially there are no people despite the initial size, so we - * expect the limit to be zero. - */ - assertEquals(0, peopleDataManager.getPersonIdLimit()); - - // show that the limit increments as PersonId values are added - for (int i = 0; i < 10; i++) { - PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); - assertEquals(personId.getValue() + 1, peopleDataManager.getPersonIdLimit()); - } - - }); - - } - - @Test - @UnitTestMethod(name = "getBoxedPersonId", args = { int.class }) - public void testGetBoxedPersonId() { - PeopleActionSupport.testConsumer(0,(c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - // show that the boxed person id is correct - for (int i = 0; i < 10; i++) { - Optional optional = peopleDataManager.getBoxedPersonId(i); - assertFalse(optional.isPresent()); - - PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); - optional = peopleDataManager.getBoxedPersonId(i); - assertTrue(optional.isPresent()); - - PersonId boxedPersonId = optional.get(); - assertEquals(personId, boxedPersonId); - } - }); - - } - - @Test - @UnitTestMethod(name = "addPerson", args = { PersonConstructionData.class }) - public void testaddPerson() { - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - // create containers to hold observations - Set observedPersonIds = new LinkedHashSet<>(); - Set expectedPersonIds = new LinkedHashSet<>(); - - for (int i = 0; i < 10; i++) { - PersonId personId = new PersonId(i); - expectedPersonIds.add(personId); - } - - pluginDataBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { - c.subscribe(PersonAdditionEvent.class, (c2, e) -> observedPersonIds.add(e.getPersonId())); - })); - - // have the agent add a few people and show they were added - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - for (PersonId expectedPersonId : expectedPersonIds) { - PersonId actualPersonId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); - assertEquals(expectedPersonId, actualPersonId); - assertTrue(peopleDataManager.personExists(actualPersonId)); - } - })); - - // precondition tests - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> peopleDataManager.addPerson(null)); - assertEquals(PersonError.NULL_PERSON_CONSTRUCTION_DATA, contractException.getErrorType()); - })); - - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin plugin = TestPlugin.getTestPlugin(testPluginData); - - PeopleActionSupport.testConsumers(0,plugin); - - // show that the expected and acutual observations match - assertEquals(expectedPersonIds, observedPersonIds); - } - - @Test - @UnitTestMethod(name = "addBulkPeople", args = { BulkPersonConstructionData.class }) - public void testBulkPersonCreationEvent() { - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - // create containers to hold observations - Set observedBulkPersonConstructionData = new LinkedHashSet<>(); - Set expectedBulkPersonConstructionData = new LinkedHashSet<>(); - - // generate three BulkPersonConstructionData instances that contain - // unique data - int uniqueId = 0; - for (int i = 0; i < 3; i++) { - BulkPersonConstructionData.Builder bulkBuilder = BulkPersonConstructionData.builder(); - for (int j = 0; j < 10; j++) { - PersonConstructionData personConstructionData = PersonConstructionData.builder().add(uniqueId++).build(); - bulkBuilder.add(personConstructionData); - } - expectedBulkPersonConstructionData.add(bulkBuilder.build()); - } - - pluginDataBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { - c.subscribe(BulkPersonAdditionEvent.class, (c2, e) -> observedBulkPersonConstructionData.add(e.getBulkPersonConstructionData())); - })); - - // have the agent add a bulk people and show the people were added - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - for (BulkPersonConstructionData bulkPersonConstructionData : expectedBulkPersonConstructionData) { - List priorPeople = peopleDataManager.getPeople(); - peopleDataManager.addBulkPeople(bulkPersonConstructionData); - - List postPeople = peopleDataManager.getPeople(); - postPeople.removeAll(priorPeople); - int expectedNewPeople = bulkPersonConstructionData.getPersonConstructionDatas().size(); - assertEquals(expectedNewPeople, postPeople.size()); - } - })); - - // precondition tests - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> peopleDataManager.addBulkPeople(null)); - assertEquals(PersonError.NULL_BULK_PERSON_CONSTRUCTION_DATA, contractException.getErrorType()); - })); - - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin plugin = TestPlugin.getTestPlugin(testPluginData); - - PeopleActionSupport.testConsumers(0,plugin); - - // show that the expected and acutual observations match - assertEquals(expectedBulkPersonConstructionData, observedBulkPersonConstructionData); - } - - @Test - @UnitTestMethod(name = "personExists", args = { PersonId.class }) - public void testPersonExists() { - - PeopleActionSupport.testConsumer(0,(c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - - for (int i = 0; i < 10; i++) { - peopleDataManager.addPerson(PersonConstructionData.builder().build()); - } - - assertFalse(peopleDataManager.personExists(new PersonId(-1))); - - for (int i = 0; i < 10; i++) { - assertTrue(peopleDataManager.personExists(new PersonId(i))); - } - - for (int i = 10; i < 20; i++) { - assertFalse(peopleDataManager.personExists(new PersonId(i))); - } - - }); - - } - - @Test - @UnitTestMethod(name = "getPeople", args = {}) - public void testGetPeople() { - - List expectedPeople = new ArrayList<>(); - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - - // add some people - - for (int i = 0; i < 10; i++) { - PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().build()); - expectedPeople.add(personId); - } - - List actualPeople = peopleDataManager.getPeople(); - assertEquals(expectedPeople.size(), actualPeople.size()); - assertEquals(new LinkedHashSet<>(expectedPeople), new LinkedHashSet<>(actualPeople)); - - })); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - - // remove a few people - for (int i = 0; i < 5; i++) { - PersonId personId = new PersonId(i); - peopleDataManager.removePerson(personId); - expectedPeople.remove(personId); - } - - })); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - // show that the removals resulted in the correct people - List actualPeople = peopleDataManager.getPeople(); - assertEquals(expectedPeople.size(), actualPeople.size()); - assertEquals(new LinkedHashSet<>(expectedPeople), new LinkedHashSet<>(actualPeople)); - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - PeopleActionSupport.testConsumers(0,testPlugin); - } - - @Test - @UnitTestMethod(name = "removePerson", args = { PersonId.class }) - public void testRemovePerson() { - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - // add a container to collect the observed removals - Set observedRemovals = new LinkedHashSet<>(); - Set expectedRemovals = new LinkedHashSet<>(); - for (int i = 0; i < 5; i++) { - expectedRemovals.add(new PersonId(i)); - } - - // have the observer subscribe to the removals and record them onto the - // observed removals - pluginDataBuilder.addTestActorPlan("observer", new TestActorPlan(1, (c) -> { - c.subscribe(PersonImminentRemovalEvent.class, (c2, e) -> observedRemovals.add(e.getPersonId())); - })); - - // have the agent add a few people - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - for (int i = 0; i < 10; i++) { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - peopleDataManager.addPerson(PersonConstructionData.builder().build()); - } - })); - - // have the agent remove some people - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { - for (int i = 0; i < 5; i++) { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - peopleDataManager.removePerson(new PersonId(i)); - } - })); - - // precondition tests - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(3, (c) -> { - - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - - ContractException contractException = assertThrows(ContractException.class, () -> peopleDataManager.removePerson(null)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - - contractException = assertThrows(ContractException.class, () -> peopleDataManager.removePerson(new PersonId(1000))); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - - })); - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - PeopleActionSupport.testConsumers(0,testPlugin); - - // show that the observed removals match the expected removals - assertEquals(expectedRemovals, observedRemovals); - } - - @Test - @UnitTestConstructor(args = { PeoplePluginData.class }) - public void testConstructor() { - // nothing to test - } - - @Test - @UnitTestMethod(name = "expandCapacity", args = { int.class }) - public void testExpandCapacity() { - PeopleActionSupport.testConsumer(0,(c) -> { - // show that a negative growth causes an exception - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> peopleDataManager.expandCapacity(-1)); - assertEquals(PersonError.NEGATIVE_GROWTH_PROJECTION, contractException.getErrorType()); - }); - - // use manual tests for non-negative growth - } - - @Test - @UnitTestMethod(name = "getPopulationCount", args = {}) - public void testGetPopulationCount() { - - PeopleActionSupport.testConsumer(0,(c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - // show the population count grows as we add people - for (int i = 0; i < 10; i++) { - assertEquals(i, peopleDataManager.getPopulationCount()); - peopleDataManager.addPerson(PersonConstructionData.builder().build()); - assertEquals(i + 1, peopleDataManager.getPopulationCount()); - } - }); - - } - - @Test - @UnitTestMethod(name = "getProjectedPopulationCount", args = {}) - public void testGetProjectedPopulationCount() { - - PeopleActionSupport.testConsumer(0,(c) -> { - - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - - assertEquals(0, peopleDataManager.getProjectedPopulationCount()); - - /* - * Add a few people so we are not working from a zero-base and show - * the projected population count is correct - */ - for (int i = 0; i < 10; i++) { - peopleDataManager.addPerson(PersonConstructionData.builder().build()); - } - assertEquals(10, peopleDataManager.getProjectedPopulationCount()); - - // show that expanding the capacity results in the correct projected - // count - peopleDataManager.expandCapacity(30); - assertEquals(40, peopleDataManager.getProjectedPopulationCount()); - - /* - * show that adding people will not change the projected population - * count until the actual population catches up - */ - for (int i = 0; i < 100; i++) { - peopleDataManager.addPerson(PersonConstructionData.builder().build()); - int expectedValue = FastMath.max(40, peopleDataManager.getPopulationCount()); - assertEquals(expectedValue, peopleDataManager.getProjectedPopulationCount()); - } - - // show that expanding multiple times works as well - peopleDataManager.expandCapacity(100); - assertEquals(210, peopleDataManager.getProjectedPopulationCount()); - - peopleDataManager.expandCapacity(100); - assertEquals(310, peopleDataManager.getProjectedPopulationCount()); - }); - } - - @Test - @UnitTestMethod(name = "getPopulationTime", args = {}) - public void testGetPopulationTime() { - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - - assertEquals(0.0, peopleDataManager.getPopulationTime()); - - // add some people - for (int i = 0; i < 10; i++) { - peopleDataManager.addPerson(PersonConstructionData.builder().build()); - assertEquals(c.getTime(), peopleDataManager.getPopulationTime()); - } - - })); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - peopleDataManager.removePerson(new PersonId(0)); - })); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - assertEquals(c.getTime(), peopleDataManager.getPopulationTime()); - })); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - peopleDataManager.removePerson(new PersonId(1)); - })); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - assertEquals(c.getTime(), peopleDataManager.getPopulationTime()); - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - PeopleActionSupport.testConsumers(0,testPlugin); - } - -} diff --git a/gcm3/src/test/java/plugins/people/events/AT_BulkPersonAdditionEvent.java b/gcm3/src/test/java/plugins/people/events/AT_BulkPersonAdditionEvent.java deleted file mode 100644 index 64a9f37f1..000000000 --- a/gcm3/src/test/java/plugins/people/events/AT_BulkPersonAdditionEvent.java +++ /dev/null @@ -1,62 +0,0 @@ -package plugins.people.events; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.junit.jupiter.api.Test; - -import nucleus.Event; -import plugins.people.support.BulkPersonConstructionData; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - -@UnitTest(target = BulkPersonAdditionEvent.class) -public class AT_BulkPersonAdditionEvent implements Event { - - @Test - @UnitTestConstructor(args = { PersonId.class, BulkPersonConstructionData.class }) - public void testConstruction() { - - // precondition tests - PersonId personId = new PersonId(0); - BulkPersonConstructionData bulkPersonConstructionData = BulkPersonConstructionData.builder().build(); - - ContractException contractException = assertThrows(ContractException.class, () -> new BulkPersonAdditionEvent(null, bulkPersonConstructionData)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - - contractException = assertThrows(ContractException.class, () -> new BulkPersonAdditionEvent(personId, null)); - assertEquals(PersonError.NULL_BULK_PERSON_CONSTRUCTION_DATA, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "getPersonId", args = {}) - public void testGetPersonId() { - - BulkPersonConstructionData bulkPersonConstructionData = BulkPersonConstructionData.builder().build(); - - for (int i = 0; i < 10; i++) { - PersonId personId = new PersonId(i); - BulkPersonAdditionEvent bulkPersonAdditionEvent = new BulkPersonAdditionEvent(personId, bulkPersonConstructionData); - assertEquals(personId, bulkPersonAdditionEvent.getPersonId()); - } - - } - - @Test - @UnitTestMethod(name = "getBulkPersonConstructionData", args = {}) - public void testGetBulkPersonConstructionData() { - PersonId personId = new PersonId(45); - BulkPersonConstructionData bulkPersonConstructionData = BulkPersonConstructionData.builder().build(); - BulkPersonAdditionEvent bulkPersonAdditionEvent = new BulkPersonAdditionEvent(personId, bulkPersonConstructionData); - assertEquals(bulkPersonConstructionData, bulkPersonAdditionEvent.getBulkPersonConstructionData()); - } - - - - -} diff --git a/gcm3/src/test/java/plugins/people/events/AT_PersonAdditionEvent.java b/gcm3/src/test/java/plugins/people/events/AT_PersonAdditionEvent.java deleted file mode 100644 index e8bfb9d65..000000000 --- a/gcm3/src/test/java/plugins/people/events/AT_PersonAdditionEvent.java +++ /dev/null @@ -1,54 +0,0 @@ -package plugins.people.events; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.junit.jupiter.api.Test; - -import nucleus.Event; -import plugins.people.support.PersonConstructionData; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - -@UnitTest(target = PersonAdditionEvent.class) -public class AT_PersonAdditionEvent implements Event { - - @Test - @UnitTestConstructor(args = { PersonId.class, PersonConstructionData.class }) - public void testConstructor() { - PersonId personId = new PersonId(0); - PersonConstructionData personConstructionData = PersonConstructionData.builder().build(); - - ContractException contractException = assertThrows(ContractException.class, () -> new PersonAdditionEvent(null, personConstructionData)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - - contractException = assertThrows(ContractException.class, () -> new PersonAdditionEvent(personId, null)); - assertEquals(PersonError.NULL_PERSON_CONSTRUCTION_DATA, contractException.getErrorType()); - } - - @Test - @UnitTestMethod(name = "getPersonId", args = {}) - public void testGetPersonId() { - for (int i = 0; i < 10; i++) { - PersonId personId = new PersonId(i); - PersonConstructionData personConstructionData = PersonConstructionData.builder().build(); - PersonAdditionEvent personAdditionEvent = new PersonAdditionEvent(personId, personConstructionData); - - assertEquals(personId, personAdditionEvent.getPersonId()); - } - } - - @Test - @UnitTestMethod(name = "getPersonConstructionData", args = {}) - public void testGetPersonConstructionData() { - PersonId personId = new PersonId(0); - PersonConstructionData personConstructionData = PersonConstructionData.builder().build(); - PersonAdditionEvent personAdditionEvent = new PersonAdditionEvent(personId, personConstructionData); - - assertEquals(personConstructionData, personAdditionEvent.getPersonConstructionData()); - } -} diff --git a/gcm3/src/test/java/plugins/people/events/AT_PersonImminentRemovalEvent.java b/gcm3/src/test/java/plugins/people/events/AT_PersonImminentRemovalEvent.java deleted file mode 100644 index 46a7ea716..000000000 --- a/gcm3/src/test/java/plugins/people/events/AT_PersonImminentRemovalEvent.java +++ /dev/null @@ -1,51 +0,0 @@ -package plugins.people.events; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.junit.jupiter.api.Test; - -import nucleus.Event; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - -@UnitTest(target = PersonImminentRemovalEvent.class) -public class AT_PersonImminentRemovalEvent implements Event { - - @Test - @UnitTestConstructor(args = { PersonId.class }) - public void testConstructor() { - - // precondition tests - ContractException contractException = assertThrows(ContractException.class, () -> new PersonImminentRemovalEvent(null)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "getPersonId", args = { PersonId.class }) - public void testGetPersonId() { - for (int i = 0; i < 10; i++) { - PersonId personId = new PersonId(i); - PersonImminentRemovalEvent personImminentRemovalEvent = new PersonImminentRemovalEvent(personId); - assertEquals(personId, personImminentRemovalEvent.getPersonId()); - } - } - - @Test - @UnitTestMethod(name = "toString", args = {}) - public void testToString() { - for (int i = 0; i < 10; i++) { - PersonId personId = new PersonId(i); - PersonImminentRemovalEvent personImminentRemovalEvent = new PersonImminentRemovalEvent(personId); - String expectedValue = "PersonImminentRemovalEvent [personId="+i+"]"; - String actualValue =personImminentRemovalEvent.toString(); - assertEquals(expectedValue, actualValue); - } - - } -} diff --git a/gcm3/src/test/java/plugins/people/support/AT_BulkPersonConstructionData.java b/gcm3/src/test/java/plugins/people/support/AT_BulkPersonConstructionData.java deleted file mode 100644 index 4d2d0e7d3..000000000 --- a/gcm3/src/test/java/plugins/people/support/AT_BulkPersonConstructionData.java +++ /dev/null @@ -1,213 +0,0 @@ -package plugins.people.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import org.junit.jupiter.api.Test; - -import nucleus.Event; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - -@UnitTest(target = BulkPersonConstructionData.class) -public final class AT_BulkPersonConstructionData implements Event { - - @Test - @UnitTestMethod(name = "builder", args = {}) - public void testBuilder() { - assertNotNull(BulkPersonConstructionData.builder()); - } - - @Test - @UnitTestMethod(target = BulkPersonConstructionData.Builder.class, name = "build", args = {}) - public void testBuild() { - assertNotNull(BulkPersonConstructionData.builder().build()); - } - - @Test - @UnitTestMethod(target = BulkPersonConstructionData.Builder.class, name = "add", args = { PersonConstructionData.class }) - public void testAdd() { - BulkPersonConstructionData.Builder builder = BulkPersonConstructionData.builder(); - - List expectedPersonConstructionData = new ArrayList<>(); - for (int i = 0; i < 10; i++) { - expectedPersonConstructionData.add(PersonConstructionData.builder().add(i).build()); - } - - for (PersonConstructionData personConstructionData : expectedPersonConstructionData) { - builder.add(personConstructionData); - } - - // show the person construction data returned match the ones contributed - // in the correct order - assertEquals(expectedPersonConstructionData, builder.build().getPersonConstructionDatas()); - } - - @Test - @UnitTestMethod(target = BulkPersonConstructionData.Builder.class, name = "addAuxiliaryData", args = { Object.class }) - public void testAddAuxiliaryData() { - Map, List> expectedValues = new LinkedHashMap<>(); - - List values = new ArrayList<>(); - values.add("aux1"); - values.add(15); - values.add(3); - values.add("aux2"); - values.add("aux1"); - values.add(3); - values.add(false); - - /* - * Fill the builder and add to the expected values map - */ - BulkPersonConstructionData.Builder builder = BulkPersonConstructionData.builder(); - for (Object value : values) { - builder.addAuxiliaryData(value); - List list = expectedValues.get(value.getClass()); - if (list == null) { - list = new ArrayList<>(); - expectedValues.put(value.getClass(), list); - } - list.add(value); - } - - /* - * Show that the bulk construction data returns the auxiliary data by - * type and in the correct order - */ - BulkPersonConstructionData bulkPersonConstructionData = builder.build(); - for (Class c : expectedValues.keySet()) { - List expectedList = expectedValues.get(c); - List actualList = bulkPersonConstructionData.getValues(c); - assertEquals(expectedList, actualList); - } - - // show that types that have no values return an empty list - assertTrue(bulkPersonConstructionData.getValues(Double.class).isEmpty()); - - ContractException contractException = assertThrows(ContractException.class, () -> BulkPersonConstructionData.builder().addAuxiliaryData(null)); - assertEquals(PersonError.NULL_AUXILIARY_DATA, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "getPersonConstructionDatas", args = {}) - public void testGetPersonConstructionDatas() { - BulkPersonConstructionData.Builder builder = BulkPersonConstructionData.builder(); - - List expectedPersonConstructionData = new ArrayList<>(); - for (int i = 0; i < 10; i++) { - expectedPersonConstructionData.add(PersonConstructionData.builder().add(i).build()); - } - - for (PersonConstructionData personConstructionData : expectedPersonConstructionData) { - builder.add(personConstructionData); - } - - // show the person construction data returned match the ones contributed - // in the correct order - assertEquals(expectedPersonConstructionData, builder.build().getPersonConstructionDatas()); - } - - @Test - @UnitTestMethod(name = "getValue", args = { Class.class }) - public void testGetValue() { - Map, List> expectedValues = new LinkedHashMap<>(); - - List values = new ArrayList<>(); - values.add("aux1"); - values.add(15); - values.add(3); - values.add("aux2"); - values.add("aux1"); - values.add(3); - values.add(false); - - /* - * Fill the builder and add to the expected values map - */ - BulkPersonConstructionData.Builder builder = BulkPersonConstructionData.builder(); - for (Object value : values) { - builder.addAuxiliaryData(value); - List list = expectedValues.get(value.getClass()); - if (list == null) { - list = new ArrayList<>(); - expectedValues.put(value.getClass(), list); - } - list.add(value); - } - - /* - * Show that the bulk construction data returns the first auxiliary data - * by type - * - */ - BulkPersonConstructionData bulkPersonConstructionData = builder.build(); - for (Class c : expectedValues.keySet()) { - Object expectedValue = expectedValues.get(c).get(0); - Optional optional = bulkPersonConstructionData.getValue(c); - assertTrue(optional.isPresent()); - Object actualValue = optional.get(); - assertEquals(expectedValue, actualValue); - } - - // show that types not contained return - - Optional optional = bulkPersonConstructionData.getValue(Double.class); - assertFalse(optional.isPresent()); - - } - - @Test - @UnitTestMethod(name = "build", args = { Class.class }) - public void testGetValues() { - Map, List> expectedValues = new LinkedHashMap<>(); - - List values = new ArrayList<>(); - values.add("aux1"); - values.add(15); - values.add(3); - values.add("aux2"); - values.add("aux1"); - values.add(3); - values.add(false); - - /* - * Fill the builder and add to the expected values map - */ - BulkPersonConstructionData.Builder builder = BulkPersonConstructionData.builder(); - for (Object value : values) { - builder.addAuxiliaryData(value); - List list = expectedValues.get(value.getClass()); - if (list == null) { - list = new ArrayList<>(); - expectedValues.put(value.getClass(), list); - } - list.add(value); - } - - /* - * Show that the bulk construction data returns the auxiliary data by - * type and in the correct order - */ - BulkPersonConstructionData bulkPersonConstructionData = builder.build(); - for (Class c : expectedValues.keySet()) { - List expectedList = expectedValues.get(c); - List actualList = bulkPersonConstructionData.getValues(c); - assertEquals(expectedList, actualList); - } - - // show that types that have no values return an empty list - assertTrue(bulkPersonConstructionData.getValues(Double.class).isEmpty()); - } -} diff --git a/gcm3/src/test/java/plugins/people/support/AT_PersonError.java b/gcm3/src/test/java/plugins/people/support/AT_PersonError.java deleted file mode 100644 index f89ec6648..000000000 --- a/gcm3/src/test/java/plugins/people/support/AT_PersonError.java +++ /dev/null @@ -1,30 +0,0 @@ -package plugins.people.support; - -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = PersonError.class) -public class AT_PersonError { - - @Test - @UnitTestMethod(name = "getDescription", args = {}) - public void test() { - //show that each description is a unique, non-null and non-empty string - Set descriptions = new LinkedHashSet<>(); - for(PersonError personError : PersonError.values()) { - String description = personError.getDescription(); - assertNotNull(description,"null description for "+personError); - assertTrue(description.length()>0, "empty string for "+personError); - boolean unique = descriptions.add(description); - assertTrue(unique,"description for "+personError+" is not unique"); - } - } -} diff --git a/gcm3/src/test/java/plugins/people/support/AT_PersonId.java b/gcm3/src/test/java/plugins/people/support/AT_PersonId.java deleted file mode 100644 index cd8bae5c9..000000000 --- a/gcm3/src/test/java/plugins/people/support/AT_PersonId.java +++ /dev/null @@ -1,94 +0,0 @@ -package plugins.people.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; - -/** - * Identifier for all people - * - * @author Shawn Hatch - * - */ - -@UnitTest(target = PersonId.class) -public final class AT_PersonId { - - @Test - @UnitTestConstructor(args = { int.class }) - public void testConstructor() { - for (int i = 0; i < 10; i++) { - PersonId personId = new PersonId(i); - assertEquals(i, personId.getValue()); - } - } - - @Test - @UnitTestMethod(name = "compareTo", args = { PersonId.class }) - public void testCompareTo() { - for (int i = 0; i < 10; i++) { - PersonId personA = new PersonId(i); - for (int j = 0; j < 10; j++) { - PersonId personB = new PersonId(j); - int comparisonValue = personA.compareTo(personB); - if (i < j) { - assertTrue(comparisonValue < 0); - } else if (i > j) { - assertTrue(comparisonValue > 0); - } else { - assertTrue(comparisonValue == 0); - } - } - } - } - - @Test - @UnitTestMethod(name = "equals", args = { Object.class }) - public void testEquals() { - for (int i = 0; i < 10; i++) { - PersonId personA = new PersonId(i); - for (int j = 0; j < 10; j++) { - PersonId personB = new PersonId(j); - if (i == j) { - assertEquals(personA,personB); - } else { - assertNotEquals(personA,personB); - } - } - } - } - - @Test - @UnitTestMethod(name = "getValue", args = {}) - public void testGetValue() { - for (int i = 0; i < 10; i++) { - PersonId person = new PersonId(i); - assertEquals(i, person.getValue()); - } - } - - @Test - @UnitTestMethod(name = "hashCode", args = {}) - public void testHashCode() { - for (int i = 0; i < 10; i++) { - PersonId person = new PersonId(i); - assertEquals(i, person.hashCode()); - } - } - - @Test - @UnitTestMethod(name = "toString", args = {}) - public void testToString() { - for (int i = 0; i < 10; i++) { - PersonId person = new PersonId(i); - assertEquals(Integer.toString(i), person.toString()); - } - } - -} diff --git a/gcm3/src/test/java/plugins/personproperties/AT_PersonPropertiesPlugin.java b/gcm3/src/test/java/plugins/personproperties/AT_PersonPropertiesPlugin.java deleted file mode 100644 index ddaa16c0b..000000000 --- a/gcm3/src/test/java/plugins/personproperties/AT_PersonPropertiesPlugin.java +++ /dev/null @@ -1,40 +0,0 @@ -package plugins.personproperties; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import nucleus.Plugin; -import nucleus.PluginId; -import plugins.people.PeoplePluginId; -import plugins.regions.RegionsPluginId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = PersonPropertiesPlugin.class) -public class AT_PersonPropertiesPlugin { - - @Test - @UnitTestMethod(name = "getPersonPropertyPlugin", args = {PersonPropertiesPluginData.class}) - public void testGetPersonPropertyPlugin() { - PersonPropertiesPluginData personPropertiesPluginData = PersonPropertiesPluginData.builder().build(); - Plugin personPropertiesPlugin = PersonPropertiesPlugin.getPersonPropertyPlugin(personPropertiesPluginData); - - assertEquals(1,personPropertiesPlugin.getPluginDatas().size()); - assertTrue(personPropertiesPlugin.getPluginDatas().contains(personPropertiesPluginData)); - - assertEquals(PersonPropertiesPluginId.PLUGIN_ID, personPropertiesPlugin.getPluginId()); - - Set expectedDependencies = new LinkedHashSet<>(); - expectedDependencies.add(PeoplePluginId.PLUGIN_ID); - expectedDependencies.add(RegionsPluginId.PLUGIN_ID); - - assertEquals(expectedDependencies, personPropertiesPlugin.getPluginDependencies()); - - } - -} diff --git a/gcm3/src/test/java/plugins/personproperties/AT_PersonPropertiesPluginId.java b/gcm3/src/test/java/plugins/personproperties/AT_PersonPropertiesPluginId.java deleted file mode 100644 index bff178775..000000000 --- a/gcm3/src/test/java/plugins/personproperties/AT_PersonPropertiesPluginId.java +++ /dev/null @@ -1,13 +0,0 @@ -package plugins.personproperties; - -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import tools.annotations.UnitTest; - -@UnitTest(target = PersonPropertiesPluginId.class) -public class AT_PersonPropertiesPluginId { - - public void test() { - assertNotNull(PersonPropertiesPluginId.PLUGIN_ID); - } -} diff --git a/gcm3/src/test/java/plugins/personproperties/AT_PersonPropertyPluginData.java b/gcm3/src/test/java/plugins/personproperties/AT_PersonPropertyPluginData.java deleted file mode 100644 index c0d629fab..000000000 --- a/gcm3/src/test/java/plugins/personproperties/AT_PersonPropertyPluginData.java +++ /dev/null @@ -1,224 +0,0 @@ -package plugins.personproperties; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.EnumSet; -import java.util.Set; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.PluginData; -import nucleus.PluginDataBuilder; -import plugins.people.support.PersonId; -import plugins.personproperties.support.PersonPropertyError; -import plugins.personproperties.support.PersonPropertyId; -import plugins.personproperties.testsupport.TestPersonPropertyId; -import plugins.util.properties.PropertyDefinition; -import plugins.util.properties.TimeTrackingPolicy; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; -import util.random.RandomGeneratorProvider; - -@UnitTest(target = PersonPropertiesPluginData.class) -public class AT_PersonPropertyPluginData { - - @Test - @UnitTestMethod(name = "builder", args = {}) - public void testBuilder() { - assertNotNull(PersonPropertiesPluginData.builder()); - } - - @Test - @UnitTestMethod(target = PersonPropertiesPluginData.Builder.class, name = "build", args = {}) - public void testBuild() { - assertNotNull(PersonPropertiesPluginData.builder().build()); - } - - @Test - @UnitTestMethod(target = PersonPropertiesPluginData.Builder.class, name = "definePersonProperty", args = { PersonPropertyId.class, PropertyDefinition.class }) - public void testDefinePersonProperty() { - // create a builder - PersonPropertiesPluginData.Builder personPropertyBuilder = PersonPropertiesPluginData.builder(); - - // fill the builder with property definitions - for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { - personPropertyBuilder.definePersonProperty(testPersonPropertyId, testPersonPropertyId.getPropertyDefinition()); - } - - // build the person property initial data - PersonPropertiesPluginData personPropertyInitialData = personPropertyBuilder.build(); - - /* - * Show that the definitions returned by the initial data match the - * expected definitions. - */ - for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { - PropertyDefinition expectedPropertyDefinition = testPersonPropertyId.getPropertyDefinition(); - PropertyDefinition actualPropertyDefinition = personPropertyInitialData.getPersonPropertyDefinition(testPersonPropertyId); - assertEquals(expectedPropertyDefinition, actualPropertyDefinition); - } - - // precondition tests - - // if the person property id is null - ContractException contractException = assertThrows(ContractException.class, () -> { - PersonPropertiesPluginData.Builder builder = PersonPropertiesPluginData.builder(); - TestPersonPropertyId testPersonPropertyId = TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; - builder.definePersonProperty(null, testPersonPropertyId.getPropertyDefinition()); - }); - assertEquals(PersonPropertyError.NULL_PERSON_PROPERTY_ID, contractException.getErrorType()); - - // if the person property definition value is null - contractException = assertThrows(ContractException.class, () -> { - PersonPropertiesPluginData.Builder builder = PersonPropertiesPluginData.builder(); - TestPersonPropertyId testPersonPropertyId = TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; - builder.definePersonProperty(testPersonPropertyId, null); - }); - assertEquals(PersonPropertyError.NULL_PERSON_PROPERTY_DEFINITION, contractException.getErrorType()); - - // if the person property definition is already added - contractException = assertThrows(ContractException.class, () -> { - PersonPropertiesPluginData.Builder builder = PersonPropertiesPluginData.builder(); - TestPersonPropertyId testPersonPropertyId = TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; - builder.definePersonProperty(testPersonPropertyId, testPersonPropertyId.getPropertyDefinition()); - builder.definePersonProperty(testPersonPropertyId, testPersonPropertyId.getPropertyDefinition()); - }); - assertEquals(PersonPropertyError.DUPLICATE_PERSON_PROPERTY_DEFINITION, contractException.getErrorType()); - - // if the person property definition does not have a default value - contractException = assertThrows(ContractException.class, () -> { - PersonPropertiesPluginData.Builder builder = PersonPropertiesPluginData.builder(); - TestPersonPropertyId testPersonPropertyId = TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; - PropertyDefinition propertyDefinition = PropertyDefinition .builder()// - .setType(Boolean.class)// - // .setDefaultValue(false)// - .setPropertyValueMutability(true)// - .setTimeTrackingPolicy(TimeTrackingPolicy.DO_NOT_TRACK_TIME)// - .build(); - - builder.definePersonProperty(testPersonPropertyId, propertyDefinition); - - }); - assertEquals(PersonPropertyError.PROPERTY_DEFINITION_REQUIRES_DEFAULT, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "getPersonPropertyDefinition", args = { PersonPropertyId.class }) - public void testGetPersonPropertyDefinition() { - // create a builder - PersonPropertiesPluginData.Builder personPropertyBuilder = PersonPropertiesPluginData.builder(); - - // fill the builder with property definitions - for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { - personPropertyBuilder.definePersonProperty(testPersonPropertyId, testPersonPropertyId.getPropertyDefinition()); - } - - // build the person property initial data - PersonPropertiesPluginData personPropertiesPluginData = personPropertyBuilder.build(); - - /* - * Show that the definitions returned by the initial data match the - * expected definitions. - */ - for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { - PropertyDefinition expectedPropertyDefinition = testPersonPropertyId.getPropertyDefinition(); - PropertyDefinition actualPropertyDefinition = personPropertiesPluginData.getPersonPropertyDefinition(testPersonPropertyId); - assertEquals(expectedPropertyDefinition, actualPropertyDefinition); - } - - // precondition tests - - // if the person property id is null - ContractException contractException = assertThrows(ContractException.class, () -> personPropertiesPluginData.getPersonPropertyDefinition(null)); - assertEquals(PersonPropertyError.NULL_PERSON_PROPERTY_ID, contractException.getErrorType()); - - // if the person property id is unknown - contractException = assertThrows(ContractException.class, () -> personPropertiesPluginData.getPersonPropertyDefinition(TestPersonPropertyId.getUnknownPersonPropertyId())); - assertEquals(PersonPropertyError.UNKNOWN_PERSON_PROPERTY_ID, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "getPersonPropertyIds", args = {}) - public void testGetPersonPropertyIds() { - - // create a builder - PersonPropertiesPluginData.Builder personPropertyBuilder = PersonPropertiesPluginData.builder(); - - // fill the builder with property definitions - for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { - personPropertyBuilder.definePersonProperty(testPersonPropertyId, testPersonPropertyId.getPropertyDefinition()); - } - - // build the person property initial data - PersonPropertiesPluginData personPropertyInitialData = personPropertyBuilder.build(); - - /* - * Show that the person proproperty ids match expectations - */ - assertEquals(EnumSet.allOf(TestPersonPropertyId.class), personPropertyInitialData.getPersonPropertyIds()); - - } - - @Test - @UnitTestMethod(name = "getCloneBuilder", args = {}) - public void testGetCloneBuilder() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3666595741799189966L); - PersonPropertiesPluginData.Builder pluginBuilder = PersonPropertiesPluginData.builder(); - - for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { - pluginBuilder.definePersonProperty(testPersonPropertyId, testPersonPropertyId.getPropertyDefinition()); - } - int personCount = 100; - for (int i = 0; i < personCount; i++) { - PersonId personId = new PersonId(i); - for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { - if (randomGenerator.nextBoolean()) { - Object randomPropertyValue = testPersonPropertyId.getRandomPropertyValue(randomGenerator); - pluginBuilder.setPersonPropertyValue(personId, testPersonPropertyId, randomPropertyValue); - } - } - } - - PersonPropertiesPluginData personPropertiesPluginData = pluginBuilder.build(); - - PluginDataBuilder cloneBuilder = personPropertiesPluginData.getCloneBuilder(); - assertNotNull(cloneBuilder); - PluginData pluginData = cloneBuilder.build(); - assertTrue(pluginData instanceof PersonPropertiesPluginData); - PersonPropertiesPluginData clonePersonPropertiesPluginData = (PersonPropertiesPluginData) pluginData; - - // show that the two plugin datas have the same property ids - Set expectedPersonPropertyIds = personPropertiesPluginData.getPersonPropertyIds(); - Set actualPersonPropertyIds = clonePersonPropertiesPluginData.getPersonPropertyIds(); - assertEquals(expectedPersonPropertyIds, actualPersonPropertyIds); - - // show that the two plugin datas have the same property definitions - for (PersonPropertyId personPropertyId : personPropertiesPluginData.getPersonPropertyIds()) { - PropertyDefinition expectedPropertyDefinition = personPropertiesPluginData.getPersonPropertyDefinition(personPropertyId); - PropertyDefinition actualPropertyDefinition = clonePersonPropertiesPluginData.getPersonPropertyDefinition(personPropertyId); - assertEquals(expectedPropertyDefinition, actualPropertyDefinition); - } - - // show that the two plugin datas have the same people - Set expectedPersonIds = personPropertiesPluginData.getPersonIds(); - Set actualPersonIds = clonePersonPropertiesPluginData.getPersonIds(); - assertEquals(expectedPersonIds, actualPersonIds); - - - for(PersonId personId : personPropertiesPluginData.getPersonIds()) { - for(PersonPropertyId personPropertyId : personPropertiesPluginData.getPersonPropertyIds()) { - Object expectedPropertyValue = personPropertiesPluginData.getPersonPropertyValue(personId, personPropertyId); - Object actualPropertyValue = clonePersonPropertiesPluginData.getPersonPropertyValue(personId, personPropertyId); - assertEquals(expectedPropertyValue,actualPropertyValue); - } - } - - } -} diff --git a/gcm3/src/test/java/plugins/personproperties/datamanagers/AT_PersonPropertyDataManager.java b/gcm3/src/test/java/plugins/personproperties/datamanagers/AT_PersonPropertyDataManager.java deleted file mode 100644 index e3de410b3..000000000 --- a/gcm3/src/test/java/plugins/personproperties/datamanagers/AT_PersonPropertyDataManager.java +++ /dev/null @@ -1,1008 +0,0 @@ -package plugins.personproperties.datamanagers; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.naming.Context; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.DataManagerContext; -import nucleus.EventLabel; -import nucleus.EventLabeler; -import nucleus.NucleusError; -import nucleus.Plugin; -import nucleus.Simulation; -import nucleus.Simulation.Builder; -import nucleus.testsupport.testplugin.ScenarioPlanCompletionObserver; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import plugins.people.PeoplePlugin; -import plugins.people.PeoplePluginData; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.BulkPersonConstructionData; -import plugins.people.support.PersonConstructionData; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import plugins.personproperties.PersonPropertiesPlugin; -import plugins.personproperties.PersonPropertiesPluginData; -import plugins.personproperties.events.PersonPropertyUpdateEvent; -import plugins.personproperties.support.PersonPropertyError; -import plugins.personproperties.support.PersonPropertyId; -import plugins.personproperties.support.PersonPropertyInitialization; -import plugins.personproperties.testsupport.PersonPropertiesActionSupport; -import plugins.personproperties.testsupport.TestPersonPropertyId; -import plugins.regions.RegionsPlugin; -import plugins.regions.RegionsPluginData; -import plugins.regions.datamanagers.RegionsDataManager; -import plugins.regions.testsupport.TestRegionId; -import plugins.stochastics.StochasticsDataManager; -import plugins.stochastics.StochasticsPlugin; -import plugins.stochastics.StochasticsPluginData; -import plugins.util.properties.PropertyDefinition; -import plugins.util.properties.PropertyError; -import plugins.util.properties.TimeTrackingPolicy; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; -import util.random.RandomGeneratorProvider; -import util.wrappers.MultiKey; -import util.wrappers.MutableInteger; - -@UnitTest(target = PersonPropertiesDataManager.class) -public final class AT_PersonPropertyDataManager { - - @Test - @UnitTestMethod(name = "getPeopleWithPropertyValue", args = { PersonPropertyId.class, Object.class }) - public void testGetPeopleWithPropertyValue() { - - PersonPropertiesActionSupport.testConsumer(100, 7917315534360369845L, (c) -> { - - // establish data views - PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - /* - * Assign random values of 1, 2 or 3 for property 2 to all people. - * Build a structure to hold expected results. - */ - List people = peopleDataManager.getPeople(); - Map> expectedValuesToPeople = new LinkedHashMap<>(); - for (int i = 0; i < 3; i++) { - expectedValuesToPeople.put(i, new LinkedHashSet<>()); - } - - for (PersonId personId : people) { - int value = randomGenerator.nextInt(3); - personPropertiesDataManager.setPersonPropertyValue(personId, TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, value); - expectedValuesToPeople.get(value).add(personId); - } - - // show that the proper people are returned for each value - for (Integer value : expectedValuesToPeople.keySet()) { - List actualPeople = personPropertiesDataManager.getPeopleWithPropertyValue(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, value); - Set expectedPeople = expectedValuesToPeople.get(value); - assertEquals(expectedPeople.size(), actualPeople.size()); - assertEquals(expectedPeople, new LinkedHashSet<>(actualPeople)); - } - - }); - - } - - @Test - @UnitTestMethod(name = "getPersonCountForPropertyValue", args = { PersonPropertyId.class, Object.class }) - public void testGetPersonCountForPropertyValue() { - - PersonPropertiesActionSupport.testConsumer(100, 686456599634987511L, (c) -> { - - // establish data views - PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - /* - * Assign random values of 1, 2 or 3 for property 2 to all people. - * Build a structure to hold expected results. - */ - List people = peopleDataManager.getPeople(); - Map expectedValuesToPeople = new LinkedHashMap<>(); - for (int i = 0; i < 3; i++) { - expectedValuesToPeople.put(i, new MutableInteger()); - } - - for (PersonId personId : people) { - int value = randomGenerator.nextInt(3); - personPropertiesDataManager.setPersonPropertyValue(personId, TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, value); - expectedValuesToPeople.get(value).increment(); - } - - // show that the proper counts are returned for each value - for (Integer value : expectedValuesToPeople.keySet()) { - int actualCount = personPropertiesDataManager.getPersonCountForPropertyValue(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, value); - MutableInteger mutableInteger = expectedValuesToPeople.get(value); - assertEquals(mutableInteger.getValue(), actualCount); - } - - }); - } - - @Test - @UnitTestMethod(name = "getPersonPropertyDefinition", args = { PersonPropertyId.class }) - public void testGetPersonPropertyDefinition() { - - PersonPropertiesActionSupport.testConsumer(0, 138806179316502662L, (c) -> { - PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); - - // show that the person property definitions match expectations - for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { - PropertyDefinition expectedPropertyDefinition = testPersonPropertyId.getPropertyDefinition(); - PropertyDefinition actualPropertyDefinition = personPropertiesDataManager.getPersonPropertyDefinition(testPersonPropertyId); - assertEquals(expectedPropertyDefinition, actualPropertyDefinition); - } - - // precondition tests - - // if the person property id is null - ContractException contractException = assertThrows(ContractException.class, () -> personPropertiesDataManager.getPersonPropertyDefinition(null)); - assertEquals(PersonPropertyError.NULL_PERSON_PROPERTY_ID, contractException.getErrorType()); - - // if the person property id is unknown - contractException = assertThrows(ContractException.class, () -> personPropertiesDataManager.getPersonPropertyDefinition(TestPersonPropertyId.getUnknownPersonPropertyId())); - assertEquals(PersonPropertyError.UNKNOWN_PERSON_PROPERTY_ID, contractException.getErrorType()); - - }); - } - - @Test - @UnitTestMethod(name = "getPersonPropertyIds", args = {}) - public void testGetPersonPropertyIds() { - PersonPropertiesActionSupport.testConsumer(0, 8485097765777963229L, (c) -> { - PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); - EnumSet expectedPropertyIds = EnumSet.allOf(TestPersonPropertyId.class); - Set actualPropertyIds = personPropertiesDataManager.getPersonPropertyIds(); - assertEquals(expectedPropertyIds, actualPropertyIds); - }); - } - - @Test - @UnitTestMethod(name = "getPersonPropertyTime", args = { PersonId.class, PersonPropertyId.class }) - public void testGetPersonPropertyTime() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // show that all person property times are 0 for the time-tracked - // properties - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - - PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); - - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - List people = peopleDataManager.getPeople(); - for (PersonId personId : people) { - double personPropertyTime = personPropertiesDataManager.getPersonPropertyTime(personId, TestPersonPropertyId.PERSON_PROPERTY_4_BOOLEAN_MUTABLE_TRACK); - assertEquals(0.0, personPropertyTime); - personPropertyTime = personPropertiesDataManager.getPersonPropertyTime(personId, TestPersonPropertyId.PERSON_PROPERTY_5_INTEGER_MUTABLE_TRACK); - assertEquals(0.0, personPropertyTime); - personPropertyTime = personPropertiesDataManager.getPersonPropertyTime(personId, TestPersonPropertyId.PERSON_PROPERTY_6_DOUBLE_MUTABLE_TRACK); - assertEquals(0.0, personPropertyTime); - } - })); - - // Set property 5 for all people at time 1 - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); - List people = peopleDataManager.getPeople(); - RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); - for (PersonId personId : people) { - personPropertiesDataManager.setPersonPropertyValue(personId, TestPersonPropertyId.PERSON_PROPERTY_5_INTEGER_MUTABLE_TRACK, randomGenerator.nextInt()); - } - })); - - // Set property 6 for all people at time 2 - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { - - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); - List people = peopleDataManager.getPeople(); - RandomGenerator randomGenerator = c.getDataManager(StochasticsDataManager.class).getRandomGenerator(); - for (PersonId personId : people) { - personPropertiesDataManager.setPersonPropertyValue(personId, TestPersonPropertyId.PERSON_PROPERTY_6_DOUBLE_MUTABLE_TRACK, randomGenerator.nextDouble()); - } - })); - - // show that the person property times agree with the times above - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(3, (c) -> { - PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - List people = peopleDataManager.getPeople(); - for (PersonId personId : people) { - double personPropertyTime = personPropertiesDataManager.getPersonPropertyTime(personId, TestPersonPropertyId.PERSON_PROPERTY_4_BOOLEAN_MUTABLE_TRACK); - assertEquals(0.0, personPropertyTime); - personPropertyTime = personPropertiesDataManager.getPersonPropertyTime(personId, TestPersonPropertyId.PERSON_PROPERTY_5_INTEGER_MUTABLE_TRACK); - assertEquals(1.0, personPropertyTime); - personPropertyTime = personPropertiesDataManager.getPersonPropertyTime(personId, TestPersonPropertyId.PERSON_PROPERTY_6_DOUBLE_MUTABLE_TRACK); - assertEquals(2.0, personPropertyTime); - } - })); - - // precondition tests - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(4, (c) -> { - PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); - - PersonId personId = new PersonId(0); - PersonId unknownPersonId = new PersonId(100000); - PersonPropertyId personPropertyId = TestPersonPropertyId.PERSON_PROPERTY_5_INTEGER_MUTABLE_TRACK; - PersonPropertyId unknownPersonPropertyId = TestPersonPropertyId.getUnknownPersonPropertyId(); - PersonPropertyId untrackedPersonPropertyId = TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; - - // if the person id is null - ContractException contractException = assertThrows(ContractException.class, () -> personPropertiesDataManager.getPersonPropertyTime(null, personPropertyId)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - - // if the person id is unknown - contractException = assertThrows(ContractException.class, () -> personPropertiesDataManager.getPersonPropertyTime(unknownPersonId, personPropertyId)); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - - // if the person property id is null - contractException = assertThrows(ContractException.class, () -> personPropertiesDataManager.getPersonPropertyTime(personId, null)); - assertEquals(PersonPropertyError.NULL_PERSON_PROPERTY_ID, contractException.getErrorType()); - - // if the person property id is unknown - contractException = assertThrows(ContractException.class, () -> personPropertiesDataManager.getPersonPropertyTime(personId, unknownPersonPropertyId)); - assertEquals(PersonPropertyError.UNKNOWN_PERSON_PROPERTY_ID, contractException.getErrorType()); - - // if the person property does not have time tracking turned on in - // the associated property definition - contractException = assertThrows(ContractException.class, () -> personPropertiesDataManager.getPersonPropertyTime(personId, untrackedPersonPropertyId)); - assertEquals(PersonPropertyError.PROPERTY_ASSIGNMENT_TIME_NOT_TRACKED, contractException.getErrorType()); - - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - PersonPropertiesActionSupport.testConsumers(10, 6980289425630085602L, testPlugin); - - } - - @Test - @UnitTestMethod(name = "getPersonPropertyValue", args = { PersonId.class, PersonPropertyId.class }) - public void testGetPersonPropertyValue() { - - PersonPropertiesActionSupport.testConsumer(10, 816143115345188642L, (c) -> { - PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - // create a container to hold expectations - Map expectedValues = new LinkedHashMap<>(); - - // assign random values for property 2 for all the people - List people = peopleDataManager.getPeople(); - for (PersonId personId : people) { - int value = randomGenerator.nextInt(); - personPropertiesDataManager.setPersonPropertyValue(personId, TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, value); - expectedValues.put(personId, value); - } - - // show that the values retrieved match expectations - for (PersonId personId : people) { - Integer expectedValue = expectedValues.get(personId); - Integer actualValue = personPropertiesDataManager.getPersonPropertyValue(personId, TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK); - assertEquals(expectedValue, actualValue); - } - - // precondition tests - PersonId personId = new PersonId(0); - PersonId unknownPersonId = new PersonId(100000); - PersonPropertyId personPropertyId = TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK; - PersonPropertyId unknownPersonPropertyId = TestPersonPropertyId.getUnknownPersonPropertyId(); - - // if the person id is null - ContractException contractException = assertThrows(ContractException.class, () -> personPropertiesDataManager.getPersonPropertyValue(null, personPropertyId)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - - // if the person id is unknown - contractException = assertThrows(ContractException.class, () -> personPropertiesDataManager.getPersonPropertyValue(unknownPersonId, personPropertyId)); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - - // if the person property id is null - contractException = assertThrows(ContractException.class, () -> personPropertiesDataManager.getPersonPropertyValue(personId, null)); - assertEquals(PersonPropertyError.NULL_PERSON_PROPERTY_ID, contractException.getErrorType()); - - // if the person property id is unknown - contractException = assertThrows(ContractException.class, () -> personPropertiesDataManager.getPersonPropertyValue(personId, unknownPersonPropertyId)); - assertEquals(PersonPropertyError.UNKNOWN_PERSON_PROPERTY_ID, contractException.getErrorType()); - - }); - } - - @Test - @UnitTestConstructor(args = { Context.class, PersonPropertiesPluginData.class }) - public void testConstructor() { - ContractException contractException = assertThrows(ContractException.class, () -> new PersonPropertiesDataManager(null)); - assertEquals(PersonPropertyError.NULL_PERSON_PROPERTY_PLUGN_DATA, contractException.getErrorType()); - } - - @Test - @UnitTestMethod(name = "expandCapacity", args = { int.class }) - public void testExpandCapacity() { - PersonPropertiesActionSupport.testConsumer(20, 7153865371557964932L, (c) -> { - // show that a negative growth causes an exception - PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> personPropertiesDataManager.expandCapacity(-1)); - assertEquals(PersonError.NEGATIVE_GROWTH_PROJECTION, contractException.getErrorType()); - }); - // use manual tests for non-negative growth - } - - @Test - @UnitTestMethod(name = "personPropertyIdExists", args = { PersonPropertyId.class }) - public void testPersonPropertyIdExists() { - - PersonPropertiesActionSupport.testConsumer(0, 4797443283568888200L, (c) -> { - PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); - for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { - assertTrue(personPropertiesDataManager.personPropertyIdExists(testPersonPropertyId)); - } - assertFalse(personPropertiesDataManager.personPropertyIdExists(TestPersonPropertyId.getUnknownPersonPropertyId())); - }); - - } - - @Test - @UnitTestMethod(name = "setPersonPropertyValue", args = { PersonId.class, PersonPropertyId.class, Object.class }) - public void testSetPersonPropertyValue() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // create some containers to hold the expected and actual observations - // for later comparison - Set expectedObservations = new LinkedHashSet<>(); - Set actualObservations = new LinkedHashSet<>(); - - // add an agent that will observe changes to all person properties - - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { - for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { - EventLabel eventLabel = PersonPropertyUpdateEvent.getEventLabelByProperty(c, testPersonPropertyId); - c.subscribe(eventLabel, (c2, e) -> { - actualObservations.add(new MultiKey(e.getPersonId(), e.getPersonPropertyId(), e.getPreviousPropertyValue(), e.getCurrentPropertyValue())); - }); - } - })); - - /* - * Add an agent that will alter person property values and record the - * corresponding expected observations. - */ - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - - // establish data views - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - // select all the property ids that are mutable - Set mutableProperties = new LinkedHashSet<>(); - for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { - boolean mutable = testPersonPropertyId.getPropertyDefinition().propertyValuesAreMutable(); - if (mutable) { - mutableProperties.add(testPersonPropertyId); - } - } - - // get the people - List people = peopleDataManager.getPeople(); - - // set all their mutable property values, recording the expected - // observations - for (PersonId personId : people) { - for (TestPersonPropertyId testPersonPropertyId : mutableProperties) { - - // determine the new and current values - Object newValue = testPersonPropertyId.getRandomPropertyValue(randomGenerator); - Object currentValue = personPropertiesDataManager.getPersonPropertyValue(personId, testPersonPropertyId); - - // record the expected observation - expectedObservations.add(new MultiKey(personId, testPersonPropertyId, currentValue, newValue)); - - // update the person property - personPropertiesDataManager.setPersonPropertyValue(personId, testPersonPropertyId, newValue); - - // show that the value changed - Object actualValue = personPropertiesDataManager.getPersonPropertyValue(personId, testPersonPropertyId); - assertEquals(newValue, actualValue); - } - } - })); - - // have the agent perform precondition checks - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { - PersonId personId = new PersonId(0); - PersonPropertyId personPropertyId = TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; - PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); - PersonPropertyId immutablePersonPropertyId = TestPersonPropertyId.PERSON_PROPERTY_7_BOOLEAN_IMMUTABLE_NO_TRACK; - Object value = true; - - PersonId unknownPersonId = new PersonId(100000); - PersonPropertyId unknownPersonPropertyId = TestPersonPropertyId.getUnknownPersonPropertyId(); - Object incompatibleValue = 12; - - // if the person id is null - ContractException contractException = assertThrows(ContractException.class, () -> personPropertiesDataManager.setPersonPropertyValue(null, personPropertyId, value)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - - // if the person id is unknown - contractException = assertThrows(ContractException.class, () -> personPropertiesDataManager.setPersonPropertyValue(unknownPersonId, personPropertyId, value)); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - - // if the person property id is null - contractException = assertThrows(ContractException.class, () -> personPropertiesDataManager.setPersonPropertyValue(personId, null, value)); - assertEquals(PersonPropertyError.NULL_PERSON_PROPERTY_ID, contractException.getErrorType()); - - // if the person property id is unknown - contractException = assertThrows(ContractException.class, () -> personPropertiesDataManager.setPersonPropertyValue(personId, unknownPersonPropertyId, value)); - assertEquals(PersonPropertyError.UNKNOWN_PERSON_PROPERTY_ID, contractException.getErrorType()); - - // if the property value is null - contractException = assertThrows(ContractException.class, () -> personPropertiesDataManager.setPersonPropertyValue(personId, personPropertyId, null)); - assertEquals(PersonPropertyError.NULL_PERSON_PROPERTY_VALUE, contractException.getErrorType()); - - // if the property value is not compatible with the corresponding - // property definition - contractException = assertThrows(ContractException.class, () -> personPropertiesDataManager.setPersonPropertyValue(personId, personPropertyId, incompatibleValue)); - assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); - - // if the corresponding property definition marks the property as - // immutable - contractException = assertThrows(ContractException.class, () -> personPropertiesDataManager.setPersonPropertyValue(personId, immutablePersonPropertyId, value)); - assertEquals(PropertyError.IMMUTABLE_VALUE, contractException.getErrorType()); - - })); - - // have the observer show that the expected observations were actually - // observed - - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(3, (c) -> { - assertEquals(expectedObservations, actualObservations); - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - PersonPropertiesActionSupport.testConsumers(10, 2321272063791878719L, testPlugin); - - } - - @Test - @UnitTestMethod(name = "init", args = { DataManagerContext.class }) - public void testPersonPropertyUpdateEventLabelers() { - - /* - * For each labeler, show that the labeler was previously added, - * presumably by the resolver. - */ - - PersonPropertiesActionSupport.testConsumer(100, 4585617051924828596L, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - EventLabeler eventLabelerForRegionAndProperty = PersonPropertyUpdateEvent.getEventLabelerForRegionAndProperty(regionsDataManager); - assertNotNull(eventLabelerForRegionAndProperty); - ContractException contractException = assertThrows(ContractException.class, () -> c.addEventLabeler(eventLabelerForRegionAndProperty)); - assertEquals(NucleusError.DUPLICATE_LABELER_ID_IN_EVENT_LABELER, contractException.getErrorType()); - }); - - PersonPropertiesActionSupport.testConsumer(100, 3679887899361025474L, (c) -> { - EventLabeler eventLabelerForPersonAndProperty = PersonPropertyUpdateEvent.getEventLabelerForPersonAndProperty(); - assertNotNull(eventLabelerForPersonAndProperty); - ContractException contractException = assertThrows(ContractException.class, () -> c.addEventLabeler(eventLabelerForPersonAndProperty)); - assertEquals(NucleusError.DUPLICATE_LABELER_ID_IN_EVENT_LABELER, contractException.getErrorType()); - }); - - PersonPropertiesActionSupport.testConsumer(100, 7374053088167649497L, (c) -> { - EventLabeler eventLabelerForProperty = PersonPropertyUpdateEvent.getEventLabelerForProperty(); - assertNotNull(eventLabelerForProperty); - ContractException contractException = assertThrows(ContractException.class, () -> c.addEventLabeler(eventLabelerForProperty)); - assertEquals(NucleusError.DUPLICATE_LABELER_ID_IN_EVENT_LABELER, contractException.getErrorType()); - - }); - - } - - @Test - @UnitTestMethod(name = "init", args = { DataManagerContext.class }) - public void testPersonPropertyDataManagerInitialization() { - - List people = new ArrayList<>(); - for (int i = 0; i < 10; i++) { - people.add(new PersonId(i)); - } - - // create a random generator - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2693836950854697940L); - - Builder builder = Simulation.builder(); - - // add the people plugin - PeoplePluginData.Builder peopleBuilder = PeoplePluginData.builder(); - for (PersonId personId : people) { - peopleBuilder.addPersonId(personId); - } - - PeoplePluginData peoplePluginData = peopleBuilder.build(); - Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(peoplePluginData); - builder.addPlugin(peoplePlugin); - - // add the person property plugin - PersonPropertiesPluginData.Builder personPropertyBuilder = PersonPropertiesPluginData.builder(); - for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { - personPropertyBuilder.definePersonProperty(testPersonPropertyId, testPersonPropertyId.getPropertyDefinition()); - } - for (PersonId personId : people) { - for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { - if(randomGenerator.nextBoolean()) { - Object randomPropertyValue = testPersonPropertyId.getRandomPropertyValue(randomGenerator); - personPropertyBuilder.setPersonPropertyValue(personId, testPersonPropertyId, randomPropertyValue); - } - } - } - PersonPropertiesPluginData personPropertiesPluginData = personPropertyBuilder.build(); - Plugin personPropertyPlugin = PersonPropertiesPlugin.getPersonPropertyPlugin(personPropertiesPluginData); - builder.addPlugin(personPropertyPlugin); - - // add the regions plugin - RegionsPluginData.Builder regionBuilder = RegionsPluginData.builder(); - - // add the regions - for (TestRegionId testRegionId : TestRegionId.values()) { - regionBuilder.addRegion(testRegionId); - } - for (PersonId personId : people) { - TestRegionId randomRegionId = TestRegionId.getRandomRegionId(randomGenerator); - regionBuilder.setPersonRegion(personId, randomRegionId); - } - RegionsPluginData regionsPluginData = regionBuilder.build(); - Plugin regionPlugin = RegionsPlugin.getRegionsPlugin(regionsPluginData); - - builder.addPlugin(regionPlugin); - - // add the stochastics plugin - StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder().setSeed(randomGenerator.nextLong()).build(); - Plugin stochasticsPlugin = StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData); - builder.addPlugin(stochasticsPlugin); - - // add the action plugin - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - /* - * Add an agent that will show that the person property data view is - * properly initialized from the person property initial data - */ - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c2) -> { - // get the person property data view - PersonPropertiesDataManager personPropertiesDataManager = c2.getDataManager(PersonPropertiesDataManager.class); - PeopleDataManager peopleDataManager = c2.getDataManager(PeopleDataManager.class); - - // show that the property ids are correct - assertEquals(personPropertiesPluginData.getPersonPropertyIds(), personPropertiesDataManager.getPersonPropertyIds()); - - // show that the property definitions are correct - for (PersonPropertyId personPropertyId : personPropertiesPluginData.getPersonPropertyIds()) { - PropertyDefinition expectedPropertyDefinition = personPropertiesPluginData.getPersonPropertyDefinition(personPropertyId); - PropertyDefinition actualPropertyDefinition = personPropertiesDataManager.getPersonPropertyDefinition(personPropertyId); - assertEquals(expectedPropertyDefinition, actualPropertyDefinition); - } - - // show that the person property values are set to the default - // values - List personIds = peopleDataManager.getPeople(); - assertTrue(personIds.size() > 0); - for (PersonId personId : people) { - for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { - Object expectedValue = personPropertiesPluginData.getPersonPropertyValue(personId, testPersonPropertyId); - Object actualValue = personPropertiesDataManager.getPersonPropertyValue(personId, testPersonPropertyId); - assertEquals(expectedValue, actualValue); - - boolean timeTrackingOn = testPersonPropertyId.getPropertyDefinition().getTimeTrackingPolicy().equals(TimeTrackingPolicy.TRACK_TIME); - if (timeTrackingOn) { - assertEquals(0.0, personPropertiesDataManager.getPersonPropertyTime(personId, testPersonPropertyId)); - } - } - } - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - builder.addPlugin(testPlugin); - - // build and execute the engine - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - builder.setOutputConsumer(scenarioPlanCompletionObserver::handleOutput).build().execute(); - - // show that all actions were executed - assertTrue(scenarioPlanCompletionObserver.allPlansExecuted()); - - } - - @Test - @UnitTestMethod(name = "init", args = { DataManagerContext.class }) - public void testPersonAdditionEvent() { - - PersonPropertiesActionSupport.testConsumer(100, 4771130331997762252L, (c) -> { - // establish data views - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); - - // get the random generator for use later - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - // add a person with some person property auxiliary data - PersonConstructionData.Builder personBuilder = PersonConstructionData.builder(); - - // create a container to hold expectations - Map expectedPropertyValues = new LinkedHashMap<>(); - - // set the expectation to the default values of all the properties - Set personPropertyIds = personPropertiesDataManager.getPersonPropertyIds(); - for (PersonPropertyId personPropertyId : personPropertyIds) { - PropertyDefinition personPropertyDefinition = personPropertiesDataManager.getPersonPropertyDefinition(personPropertyId); - Object value = personPropertyDefinition.getDefaultValue().get(); - expectedPropertyValues.put(personPropertyId, value); - } - - // set two properties to random values and record them in the - // expected data - int iValue = randomGenerator.nextInt(); - personBuilder.add(new PersonPropertyInitialization(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, iValue)); - expectedPropertyValues.put(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, iValue); - - double dValue = randomGenerator.nextDouble(); - personBuilder.add(new PersonPropertyInitialization(TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK, dValue)); - expectedPropertyValues.put(TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK, dValue); - personBuilder.add(TestRegionId.REGION_1); - PersonConstructionData personConstructionData = personBuilder.build(); - // add the person and get its person id - PersonId personId = peopleDataManager.addPerson(personConstructionData); - - // show that the person exists - assertTrue(peopleDataManager.personExists(personId)); - - // show that the person has the correct property values - for (PersonPropertyId personPropertyId : personPropertyIds) { - Object expectedValue = expectedPropertyValues.get(personPropertyId); - Object actualValue = personPropertiesDataManager.getPersonPropertyValue(personId, personPropertyId); - assertEquals(expectedValue, actualValue); - } - - }); - - /* - * precondition test: if the event contains a - * PersonPropertyInitialization that has a person property value that is - * not compatible with the corresponding property definition - */ - PersonPropertiesActionSupport.testConsumer(100, 5194635938533128930L, (c) -> { - // add a person with some person property auxiliary data - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - PersonConstructionData.Builder personBuilder = PersonConstructionData.builder(); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - ContractException contractException = assertThrows(ContractException.class, () -> { - personBuilder.add(TestRegionId.getRandomRegionId(randomGenerator)); - personBuilder.add(new PersonPropertyInitialization(TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, 45)); - PersonConstructionData constructionData = personBuilder.build(); - peopleDataManager.addPerson(constructionData); - }); - assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); - - }); - - /* - * precondition test: if the event contains a - * PersonPropertyInitialization that has a null person property value - */ - PersonPropertiesActionSupport.testConsumer(100, 4349734439660163798L, (c) -> { - // add a person with some person property auxiliary data - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - PersonConstructionData.Builder personBuilder = PersonConstructionData.builder(); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - ContractException contractException = assertThrows(ContractException.class, () -> { - personBuilder.add(TestRegionId.getRandomRegionId(randomGenerator)); - personBuilder.add(new PersonPropertyInitialization(TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, null)); - PersonConstructionData constructionData = personBuilder.build(); - peopleDataManager.addPerson(constructionData); - }); - assertEquals(PersonPropertyError.NULL_PERSON_PROPERTY_VALUE, contractException.getErrorType()); - - }); - - /* - * precondition test: if the event contains a - * PersonPropertyInitialization that has an unknown person property id - */ - PersonPropertiesActionSupport.testConsumer(100, 2152152824636786936L, (c) -> { - // add a person with some person property auxiliary data - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - PersonConstructionData.Builder personBuilder = PersonConstructionData.builder(); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - ContractException contractException = assertThrows(ContractException.class, () -> { - personBuilder.add(TestRegionId.getRandomRegionId(randomGenerator)); - personBuilder.add(new PersonPropertyInitialization(TestPersonPropertyId.getUnknownPersonPropertyId(), false)); - PersonConstructionData constructionData = personBuilder.build(); - peopleDataManager.addPerson(constructionData); - }); - assertEquals(PersonPropertyError.UNKNOWN_PERSON_PROPERTY_ID, contractException.getErrorType()); - - }); - /* - * precondition test: if the event contains a - * PersonPropertyInitialization that has a null person property id - */ - PersonPropertiesActionSupport.testConsumer(100, 8379070211267955743L, (c) -> { - // add a person with some person property auxiliary data - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - PersonConstructionData.Builder personBuilder = PersonConstructionData.builder(); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - // if the event contains a PersonPropertyInitialization that has a - // null person property id - ContractException contractException = assertThrows(ContractException.class, () -> { - personBuilder.add(TestRegionId.getRandomRegionId(randomGenerator)); - personBuilder.add(new PersonPropertyInitialization(null, false)); - PersonConstructionData constructionData = personBuilder.build(); - peopleDataManager.addPerson(constructionData); - }); - assertEquals(PersonPropertyError.NULL_PERSON_PROPERTY_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "init", args = { DataManagerContext.class }) - public void testBulkPersonAdditionEvent() { - PersonPropertiesActionSupport.testConsumer(0, 2547218192811543040L, (c) -> { - // establish data views - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); - - // get the random generator for use later - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - // set the number of people we will add - int bulkPersonCount = 20; - - /* - * Create a container to hold expectations. Note that we choose to - * not assign about half of the property values so that we can - * demonstrate that default values are being used correctly. - */ - Map> expectedPropertyValues = new LinkedHashMap<>(); - Map> shouldBeAssigned = new LinkedHashMap<>(); - - /* - * for each person, set the expectations randomly to either the - * default value of the property or a new randomized value - */ - - for (int i = 0; i < bulkPersonCount; i++) { - PersonId personId = new PersonId(i); - Map propertyValueMap = new LinkedHashMap<>(); - expectedPropertyValues.put(personId, propertyValueMap); - - Map propertyAssignmentMap = new LinkedHashMap<>(); - shouldBeAssigned.put(personId, propertyAssignmentMap); - - for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { - - if (randomGenerator.nextBoolean()) { - Object value = testPersonPropertyId.getRandomPropertyValue(randomGenerator); - propertyValueMap.put(testPersonPropertyId, value); - propertyAssignmentMap.put(testPersonPropertyId, true); - } else { - PropertyDefinition personPropertyDefinition = testPersonPropertyId.getPropertyDefinition(); - Object value = personPropertyDefinition.getDefaultValue().get(); - propertyValueMap.put(testPersonPropertyId, value); - propertyAssignmentMap.put(testPersonPropertyId, false); - } - - } - } - - // Construct the bulk add event from the expected values - BulkPersonConstructionData.Builder bulkBuilder = BulkPersonConstructionData.builder(); - PersonConstructionData.Builder personBuilder = PersonConstructionData.builder(); - - for (PersonId personId : expectedPropertyValues.keySet()) { - Map propertyValueMap = expectedPropertyValues.get(personId); - Map propertyAssignmentMap = shouldBeAssigned.get(personId); - for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { - Boolean shouldAssignProperty = propertyAssignmentMap.get(testPersonPropertyId); - if (shouldAssignProperty) { - Object value = propertyValueMap.get(testPersonPropertyId); - personBuilder.add(new PersonPropertyInitialization(testPersonPropertyId, value)); - } - } - - personBuilder.add(TestRegionId.getRandomRegionId(randomGenerator)); - - bulkBuilder.add(personBuilder.build()); - } - - // add the people via the bulk creation event - peopleDataManager.addBulkPeople(bulkBuilder.build()); - - // show that the people exist - for (PersonId personId : expectedPropertyValues.keySet()) { - assertTrue(peopleDataManager.personExists(personId)); - } - - // show that the people have the correct property values - for (PersonId personId : expectedPropertyValues.keySet()) { - Map propertyValueMap = expectedPropertyValues.get(personId); - for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { - Object expectedValue = propertyValueMap.get(testPersonPropertyId); - Object actualValue = personPropertiesDataManager.getPersonPropertyValue(personId, testPersonPropertyId); - assertEquals(expectedValue, actualValue); - } - } - - // precondition tests - // if the event contains a PersonPropertyInitialization that has a - // null person property id - ContractException contractException = assertThrows(ContractException.class, () -> { - personBuilder.add(TestRegionId.getRandomRegionId(randomGenerator)); - personBuilder.add(new PersonPropertyInitialization(null, false)); - bulkBuilder.add(personBuilder.build()); - peopleDataManager.addBulkPeople(bulkBuilder.build()); - }); - assertEquals(PersonPropertyError.NULL_PERSON_PROPERTY_ID, contractException.getErrorType()); - - // if the event contains a PersonPropertyInitialization that has an - // unknown person property id - contractException = assertThrows(ContractException.class, () -> { - personBuilder.add(TestRegionId.getRandomRegionId(randomGenerator)); - personBuilder.add(new PersonPropertyInitialization(TestPersonPropertyId.getUnknownPersonPropertyId(), false)); - bulkBuilder.add(personBuilder.build()); - peopleDataManager.addBulkPeople(bulkBuilder.build()); - }); - assertEquals(PersonPropertyError.UNKNOWN_PERSON_PROPERTY_ID, contractException.getErrorType()); - - // if the event contains a PersonPropertyInitialization that has a - // null person property value - contractException = assertThrows(ContractException.class, () -> { - personBuilder.add(TestRegionId.getRandomRegionId(randomGenerator)); - personBuilder.add(new PersonPropertyInitialization(TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, null)); - bulkBuilder.add(personBuilder.build()); - peopleDataManager.addBulkPeople(bulkBuilder.build()); - }); - assertEquals(PersonPropertyError.NULL_PERSON_PROPERTY_VALUE, contractException.getErrorType()); - - // if the event contains a PersonPropertyInitialization that has a - // person property value that is not compatible with the - // corresponding property definition - contractException = assertThrows(ContractException.class, () -> { - personBuilder.add(TestRegionId.getRandomRegionId(randomGenerator)); - personBuilder.add(new PersonPropertyInitialization(TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, 45)); - bulkBuilder.add(personBuilder.build()); - peopleDataManager.addBulkPeople(bulkBuilder.build()); - }); - assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "init", args = { DataManagerContext.class }) - public void testPersonImminentRemovalEvent() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - /* - * Have the agent remove a person and show that their properties remain - * during the current span of this agent's activation - */ - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - PersonId personId = new PersonId(0); - - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); - - assertTrue(peopleDataManager.personExists(personId)); - - PersonPropertyId personPropertyId = TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK; - - // Set the property value to a non-default value. - Integer expectedPropertyValue = 999; - personPropertiesDataManager.setPersonPropertyValue(personId, personPropertyId, expectedPropertyValue); - - // remove the person - peopleDataManager.removePerson(personId); - - // show that the property value is still present - Object actualPropertyValue = personPropertiesDataManager.getPersonPropertyValue(personId, personPropertyId); - assertEquals(expectedPropertyValue, actualPropertyValue); - - })); - - // Have the agent now show that these person properties are no longer - // available - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - - PersonId personId = new PersonId(0); - - // show that the person does not exist - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - assertFalse(peopleDataManager.personExists(personId)); - - PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); - - PersonPropertyId personPropertyId = TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK; - - ContractException contractException = assertThrows(ContractException.class, () -> personPropertiesDataManager.getPersonPropertyValue(personId, personPropertyId)); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - PersonPropertiesActionSupport.testConsumers(10, 2020442537537236753L, testPlugin); - - } - - // - // 3100440347097616280L - // 4627357002700907595L - // 3525837507821440138L - // 3404160152913070084L - // 8802528032031272978L - // 7460910928660168768L - // 1498052576475289605L - // 3969826324474876300L - // 1426493903052832076L - // 15986402242167215L - // 6427460339373497496L - // 2948423500320063643L - // 4844530530516661042L - // 3419491894809824507L - // 598790801002069301L - // 1814619428719379985L - // 1992913840461043389L - // 2470473626116352915L - // 1697643618060460580L - // 4289247820442564478L - // 5464618450744160243L - // 6590208175805030029L - // 2042914967398742326L -} diff --git a/gcm3/src/test/java/plugins/personproperties/events/AT_PersonPropertyUpdateEvent.java b/gcm3/src/test/java/plugins/personproperties/events/AT_PersonPropertyUpdateEvent.java deleted file mode 100644 index 023319a79..000000000 --- a/gcm3/src/test/java/plugins/personproperties/events/AT_PersonPropertyUpdateEvent.java +++ /dev/null @@ -1,366 +0,0 @@ -package plugins.personproperties.events; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import nucleus.Event; -import nucleus.EventLabel; -import nucleus.EventLabeler; -import nucleus.SimulationContext; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.PersonId; -import plugins.personproperties.support.PersonPropertyId; -import plugins.personproperties.testsupport.PersonPropertiesActionSupport; -import plugins.personproperties.testsupport.TestPersonPropertyId; -import plugins.regions.datamanagers.RegionsDataManager; -import plugins.regions.support.RegionId; -import plugins.regions.testsupport.TestRegionId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = PersonPropertyUpdateEvent.class) -public class AT_PersonPropertyUpdateEvent implements Event { - - @Test - @UnitTestConstructor(args = { PersonId.class, PersonPropertyId.class, Object.class, Object.class }) - public void testConstructor() { - // nothing to test - } - - @Test - @UnitTestMethod(name = "getCurrentPropertyValue", args = {}) - public void testGetCurrentPropertyValue() { - PersonId personId = new PersonId(10); - PersonPropertyId personPropertyId = TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; - Object previousValue = 0; - for (int i = 0; i < 10; i++) { - Object currentValue = i; - PersonPropertyUpdateEvent personPropertyUpdateEvent = new PersonPropertyUpdateEvent(personId, personPropertyId, previousValue, currentValue); - assertEquals(currentValue, personPropertyUpdateEvent.getCurrentPropertyValue()); - } - } - - @Test - @UnitTestMethod(name = "getPersonPropertyId", args = {}) - public void testGetPersonPropertyId() { - PersonId personId = new PersonId(10); - Object previousValue = 0; - Object currentValue = 1; - for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { - PersonPropertyUpdateEvent personPropertyUpdateEvent = new PersonPropertyUpdateEvent(personId, testPersonPropertyId, previousValue, currentValue); - assertEquals(testPersonPropertyId, personPropertyUpdateEvent.getPersonPropertyId()); - } - } - - @Test - @UnitTestMethod(name = "getPersonId", args = {}) - public void testGetPersonId() { - PersonPropertyId personPropertyId = TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; - Object previousValue = 0; - Object currentValue = 1; - for (int i = 0; i < 10; i++) { - PersonId personId = new PersonId(i); - PersonPropertyUpdateEvent personPropertyUpdateEvent = new PersonPropertyUpdateEvent(personId, personPropertyId, previousValue, currentValue); - assertEquals(personId, personPropertyUpdateEvent.getPersonId()); - } - } - - @Test - @UnitTestMethod(name = "getPreviousPropertyValue", args = {}) - public void testGetPreviousPropertyValue() { - PersonId personId = new PersonId(10); - PersonPropertyId personPropertyId = TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; - - Object currentValue = 1; - for (int i = 0; i < 10; i++) { - Object previousValue = i; - PersonPropertyUpdateEvent personPropertyUpdateEvent = new PersonPropertyUpdateEvent(personId, personPropertyId, previousValue, currentValue); - assertEquals(previousValue, personPropertyUpdateEvent.getPreviousPropertyValue()); - } - - } - - @Test - @UnitTestMethod(name = "toString", args = {}) - public void testToString() { - PersonId personId = new PersonId(10); - PersonPropertyId personPropertyId = TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; - Object previousValue = 0; - Object currentValue = 1; - PersonPropertyUpdateEvent personPropertyUpdateEvent = new PersonPropertyUpdateEvent(personId, personPropertyId, previousValue, currentValue); - String actualValue = personPropertyUpdateEvent.toString(); - String expectedValue = "PersonPropertyUpdateEvent [personId=10, personPropertyId=PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, previousPropertyValue=0, currentPropertyValue=1]"; - assertEquals(actualValue, expectedValue); - } - - @Test - @UnitTestMethod(name = "getEventLabelByPersonAndProperty", args = { SimulationContext.class, PersonId.class, PersonPropertyId.class }) - public void testGetEventLabelByPersonAndProperty() { - - PersonPropertiesActionSupport.testConsumer(5, 4447674464104241765L, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - List people = peopleDataManager.getPeople(); - - Set> eventLabels = new LinkedHashSet<>(); - - for (PersonId personId : people) { - for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { - - EventLabel eventLabel = PersonPropertyUpdateEvent.getEventLabelByPersonAndProperty(c, personId, testPersonPropertyId); - - // show that the event label has the correct event class - assertEquals(PersonPropertyUpdateEvent.class, eventLabel.getEventClass()); - - // show that the event label has the correct primary key - assertEquals(testPersonPropertyId, eventLabel.getPrimaryKeyValue()); - - // show that the event label has the same id as its - // associated labeler - EventLabeler eventLabeler = PersonPropertyUpdateEvent.getEventLabelerForPersonAndProperty(); - assertEquals(eventLabeler.getEventLabelerId(), eventLabel.getLabelerId()); - - // show that two event labels with the same inputs are equal - EventLabel eventLabel2 = PersonPropertyUpdateEvent.getEventLabelByPersonAndProperty(c, personId, testPersonPropertyId); - assertEquals(eventLabel, eventLabel2); - - // show that equal event labels have equal hash codes - assertEquals(eventLabel.hashCode(), eventLabel2.hashCode()); - - // show that two event labels with different inputs are not - // equal - assertTrue(eventLabels.add(eventLabel)); - } - } - }); - - } - - @Test - @UnitTestMethod(name = "getEventLabelerForPersonAndProperty", args = {}) - public void testGetEventLabelerForPersonAndProperty() { - - PersonPropertiesActionSupport.testConsumer(5, 1295505199200349679L, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - List people = peopleDataManager.getPeople(); - - // create an event labeler - EventLabeler eventLabeler = PersonPropertyUpdateEvent.getEventLabelerForPersonAndProperty(); - - // show that the event labeler has the correct event class - assertEquals(PersonPropertyUpdateEvent.class, eventLabeler.getEventClass()); - - // show that the event labeler produces the expected event label - for (PersonId personId : people) { - for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { - - // derive the expected event label for this event - EventLabel expectedEventLabel = PersonPropertyUpdateEvent.getEventLabelByPersonAndProperty(c, personId, testPersonPropertyId); - - // show that the event label and event labeler have equal id - // values - assertEquals(expectedEventLabel.getLabelerId(), eventLabeler.getEventLabelerId()); - - // create an event - PersonPropertyUpdateEvent event = new PersonPropertyUpdateEvent(personId, testPersonPropertyId, 1, 2); - - // show that the event labeler produces the correct an event - // label - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - - assertEquals(expectedEventLabel, actualEventLabel); - - } - } - }); - } - - @Test - @UnitTestMethod(name = "getEventLabelByProperty", args = { SimulationContext.class, PersonPropertyId.class }) - public void testGetEventLabelByProperty() { - - PersonPropertiesActionSupport.testConsumer(0, 3639063830450063191L, (c) -> { - - Set> eventLabels = new LinkedHashSet<>(); - - for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { - - EventLabel eventLabel = PersonPropertyUpdateEvent.getEventLabelByProperty(c, testPersonPropertyId); - - // show that the event label has the correct event class - assertEquals(PersonPropertyUpdateEvent.class, eventLabel.getEventClass()); - - // show that the event label has the correct primary key - assertEquals(testPersonPropertyId, eventLabel.getPrimaryKeyValue()); - - // show that the event label has the same id as its - // associated labeler - EventLabeler eventLabeler = PersonPropertyUpdateEvent.getEventLabelerForProperty(); - assertEquals(eventLabeler.getEventLabelerId(), eventLabel.getLabelerId()); - - // show that two event labels with the same inputs are equal - EventLabel eventLabel2 = PersonPropertyUpdateEvent.getEventLabelByProperty(c, testPersonPropertyId); - assertEquals(eventLabel, eventLabel2); - - // show that equal event labels have equal hash codes - assertEquals(eventLabel.hashCode(), eventLabel2.hashCode()); - - // show that two event labels with different inputs are not - // equal - assertTrue(eventLabels.add(eventLabel)); - } - - }); - - } - - @Test - @UnitTestMethod(name = "getEventLabelerForProperty", args = {}) - public void testGetEventLabelerForProperty() { - - - PersonPropertiesActionSupport.testConsumer(0, 1006134798657400111L, (c) -> { - - - // create an event labeler - EventLabeler eventLabeler = PersonPropertyUpdateEvent.getEventLabelerForProperty(); - - // show that the event labeler has the correct event class - assertEquals(PersonPropertyUpdateEvent.class, eventLabeler.getEventClass()); - - // show that the event labeler produces the expected event label - - - - for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { - - // derive the expected event label for this event - EventLabel expectedEventLabel = PersonPropertyUpdateEvent.getEventLabelByProperty(c, testPersonPropertyId); - - // show that the event label and event labeler have equal id - // values - assertEquals(expectedEventLabel.getLabelerId(), eventLabeler.getEventLabelerId()); - - // create an event - PersonPropertyUpdateEvent event = new PersonPropertyUpdateEvent(new PersonId(0), testPersonPropertyId, 1, 2); - - // show that the event labeler produces the correct an event - // label - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - - assertEquals(expectedEventLabel, actualEventLabel); - - } - - }); - - } - - @Test - @UnitTestMethod(name = "getEventLabelByRegionAndProperty", args = { SimulationContext.class, RegionId.class, PersonPropertyId.class }) - public void testGetEventLabelByRegionAndProperty() { - - PersonPropertiesActionSupport.testConsumer(0, 7020781813930698612L, (c) -> { - - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - - Set> eventLabels = new LinkedHashSet<>(); - - for (TestRegionId testRegionId : TestRegionId.values()) { - for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { - - EventLabel eventLabel = PersonPropertyUpdateEvent.getEventLabelByRegionAndProperty(c, testRegionId, testPersonPropertyId); - - // show that the event label has the correct event class - assertEquals(PersonPropertyUpdateEvent.class, eventLabel.getEventClass()); - - // show that the event label has the correct primary key - assertEquals(testPersonPropertyId, eventLabel.getPrimaryKeyValue()); - - // show that the event label has the same id as its - // associated labeler - EventLabeler eventLabeler = PersonPropertyUpdateEvent.getEventLabelerForRegionAndProperty(regionsDataManager); - assertEquals(eventLabeler.getEventLabelerId(), eventLabel.getLabelerId()); - - // show that two event labels with the same inputs are equal - EventLabel eventLabel2 = PersonPropertyUpdateEvent.getEventLabelByRegionAndProperty(c, testRegionId, testPersonPropertyId); - assertEquals(eventLabel, eventLabel2); - - // show that equal event labels have equal hash codes - assertEquals(eventLabel.hashCode(), eventLabel2.hashCode()); - - // show that two event labels with different inputs are not - // equal - assertTrue(eventLabels.add(eventLabel)); - } - } - }); - - } - - @Test - @UnitTestMethod(name = "getEventLabelerForRegionAndProperty", args = {}) - public void testGetEventLabelerForRegionAndProperty() { - - PersonPropertiesActionSupport.testConsumer(50, 7370040718450691849L, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - - // create an event labeler - EventLabeler eventLabeler = PersonPropertyUpdateEvent.getEventLabelerForRegionAndProperty(regionsDataManager); - - // show that the event labeler has the correct event class - assertEquals(PersonPropertyUpdateEvent.class, eventLabeler.getEventClass()); - - // show that the event labeler produces the expected event label - for (TestRegionId testRegionId : TestRegionId.values()) { - - //we will need to select a person from the region to run this test correctly - List peopleInRegion = regionsDataManager.getPeopleInRegion(testRegionId); - if(peopleInRegion.isEmpty()) { - continue; - } - PersonId personId = peopleInRegion.get(0); - for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { - - // derive the expected event label for this event - EventLabel expectedEventLabel = PersonPropertyUpdateEvent.getEventLabelByRegionAndProperty(c, testRegionId, testPersonPropertyId); - - // show that the event label and event labeler have equal id - // values - assertEquals(expectedEventLabel.getLabelerId(), eventLabeler.getEventLabelerId()); - - // create an event - PersonPropertyUpdateEvent event = new PersonPropertyUpdateEvent(personId, testPersonPropertyId, 1, 2); - - // show that the event labeler produces the correct an event - // label - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - - assertEquals(expectedEventLabel, actualEventLabel); - - } - } - }); - - } - - @Test - @UnitTestMethod(name = "getPrimaryKeyValue", args = {}) - public void testGetPrimaryKeyValue() { - - PersonId personId = new PersonId(10); - Object previousValue = 0; - Object currentValue = 1; - for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { - PersonPropertyUpdateEvent personPropertyUpdateEvent = new PersonPropertyUpdateEvent(personId, testPersonPropertyId, previousValue, currentValue); - assertEquals(testPersonPropertyId, personPropertyUpdateEvent.getPrimaryKeyValue()); - } - } - -} diff --git a/gcm3/src/test/java/plugins/personproperties/support/AT_PersonPropertyError.java b/gcm3/src/test/java/plugins/personproperties/support/AT_PersonPropertyError.java deleted file mode 100644 index c4fa29eea..000000000 --- a/gcm3/src/test/java/plugins/personproperties/support/AT_PersonPropertyError.java +++ /dev/null @@ -1,30 +0,0 @@ -package plugins.personproperties.support; - -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = PersonPropertyError.class) -public class AT_PersonPropertyError { - - @Test - @UnitTestMethod(name = "getDescription", args = {}) - public void test() { - //show that each description is a unique, non-null and non-empty string - Set descriptions = new LinkedHashSet<>(); - for(PersonPropertyError personPropertyError : PersonPropertyError.values()) { - String description = personPropertyError.getDescription(); - assertNotNull(description,"null description for "+personPropertyError); - assertTrue(description.length()>0, "empty string for "+personPropertyError); - boolean unique = descriptions.add(description); - assertTrue(unique,"description for "+personPropertyError+" is not unique"); - } - } -} diff --git a/gcm3/src/test/java/plugins/personproperties/support/AT_PersonPropertyId.java b/gcm3/src/test/java/plugins/personproperties/support/AT_PersonPropertyId.java deleted file mode 100644 index df644e233..000000000 --- a/gcm3/src/test/java/plugins/personproperties/support/AT_PersonPropertyId.java +++ /dev/null @@ -1,14 +0,0 @@ -package plugins.personproperties.support; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - -@UnitTest(target = PersonPropertyId.class) -public class AT_PersonPropertyId { - @Test - public void test() { - // nothing to test - } - -} diff --git a/gcm3/src/test/java/plugins/personproperties/support/AT_PersonPropertyInitialization.java b/gcm3/src/test/java/plugins/personproperties/support/AT_PersonPropertyInitialization.java deleted file mode 100644 index deaf10cb2..000000000 --- a/gcm3/src/test/java/plugins/personproperties/support/AT_PersonPropertyInitialization.java +++ /dev/null @@ -1,39 +0,0 @@ -package plugins.personproperties.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -import plugins.personproperties.testsupport.TestPersonPropertyId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = PersonPropertyInitialization.class) -public class AT_PersonPropertyInitialization { - - @Test - @UnitTestConstructor(args = { PersonPropertyId.class, Object.class }) - public void testConstructor() { - // nothing to test - } - - @Test - @UnitTestMethod(name = "getPersonPropertyId", args = {}) - public void testGetPersonPropertyId() { - PersonPropertyId personPropertyId = TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK; - Double value = 2.7; - PersonPropertyInitialization personPropertyInitialization = new PersonPropertyInitialization(personPropertyId, value); - assertEquals(personPropertyId, personPropertyInitialization.getPersonPropertyId()); - } - - @Test - @UnitTestMethod(name = "getValue", args = {}) - public void testGetValue() { - PersonPropertyId personPropertyId = TestPersonPropertyId.PERSON_PROPERTY_3_DOUBLE_MUTABLE_NO_TRACK; - Double value = 2.7; - PersonPropertyInitialization personPropertyInitialization = new PersonPropertyInitialization(personPropertyId, value); - assertEquals(value, personPropertyInitialization.getValue()); - } - -} diff --git a/gcm3/src/test/java/plugins/personproperties/support/AT_PersonPropertyLabeler.java b/gcm3/src/test/java/plugins/personproperties/support/AT_PersonPropertyLabeler.java deleted file mode 100644 index 5f8c539a4..000000000 --- a/gcm3/src/test/java/plugins/personproperties/support/AT_PersonPropertyLabeler.java +++ /dev/null @@ -1,167 +0,0 @@ -package plugins.personproperties.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.SimulationContext; -import plugins.partitions.support.LabelerSensitivity; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import plugins.personproperties.datamanagers.PersonPropertiesDataManager; -import plugins.personproperties.events.PersonPropertyUpdateEvent; -import plugins.personproperties.testsupport.PersonPropertiesActionSupport; -import plugins.personproperties.testsupport.TestPersonPropertyId; -import plugins.stochastics.StochasticsDataManager; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - -@UnitTest(target = PersonPropertyLabeler.class) -public class AT_PersonPropertyLabeler { - - @Test - @UnitTestConstructor(args = { PersonPropertyId.class, Function.class, Object.class }) - public void testConstructor() { - // nothing to test - } - - @Test - @UnitTestMethod(name = "getLabelerSensitivities", args = { PersonPropertyId.class, Function.class, Object.class }) - public void testGetLabelerSensitivities() { - - /* - * Get the labeler sensitivities and show that they are consistent with - * their documented behaviors. - */ - - PersonPropertyId personPropertyId = TestPersonPropertyId.PERSON_PROPERTY_4_BOOLEAN_MUTABLE_TRACK; - PersonPropertyLabeler personPropertyLabeler = new PersonPropertyLabeler(personPropertyId, (c) -> null); - - Set> labelerSensitivities = personPropertyLabeler.getLabelerSensitivities(); - - // show that there is exactly one sensitivity - assertEquals(1, labelerSensitivities.size()); - - // show that the sensitivity is associated with - // PersonPropertyUpdateEvent - LabelerSensitivity labelerSensitivity = labelerSensitivities.iterator().next(); - assertEquals(PersonPropertyUpdateEvent.class, labelerSensitivity.getEventClass()); - - /* - * Show that the sensitivity will return the person id from a - * PersonPropertyUpdateEvent if the event matches the - * person property id. - */ - PersonId personId = new PersonId(56); - PersonPropertyUpdateEvent personPropertyUpdateEvent = new PersonPropertyUpdateEvent(personId, personPropertyId, false, true); - Optional optional = labelerSensitivity.getPersonId(personPropertyUpdateEvent); - assertTrue(optional.isPresent()); - assertEquals(personId, optional.get()); - - /* - * Show that the sensitivity will return an empty optional of person id - * from a PersonPropertyUpdateEvent if the event does not - * match the person property id. - */ - - personPropertyId = TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; - personPropertyUpdateEvent = new PersonPropertyUpdateEvent(personId, personPropertyId, false, true); - optional = labelerSensitivity.getPersonId(personPropertyUpdateEvent); - assertFalse(optional.isPresent()); - - } - - - - @Test - @UnitTestMethod(name = "getLabel", args = { SimulationContext.class, PersonId.class }) - public void testGetLabel() { - /* - * Have the agent show that the person property labeler produces a label - * for each person that is consistent with the function passed to the - * person property labeler. - */ - PersonPropertiesActionSupport.testConsumer(10, 6445109933336671672L, (c) -> { - // establish data views - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - // select a property to work with - PersonPropertyId personPropertyId = TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK; - - /* - * Assign random values to the people so that we can get some - * variety in the labels - */ - List people = peopleDataManager.getPeople(); - for (PersonId personId : people) { - personPropertiesDataManager.setPersonPropertyValue(personId, personPropertyId, randomGenerator.nextBoolean()); - } - - /* - * build a person property labeler with a function that can be - * tested - */ - Function function = (input) -> { - Boolean value = (Boolean) input; - if (value) { - return "A"; - } - return "B"; - }; - - PersonPropertyLabeler personPropertyLabeler = new PersonPropertyLabeler(personPropertyId, function); - - /* - * Apply the labeler to each person and compare it to the more - * direct use of the labeler's function - */ - for (PersonId personId : people) { - - // get the person's property value and apply the function directly - Boolean value = personPropertiesDataManager.getPersonPropertyValue(personId, personPropertyId); - Object expectedLabel = function.apply(value); - - // get the label from the person id - Object actualLabel = personPropertyLabeler.getLabel(c, personId); - - // show that the two labels are equal - assertEquals(expectedLabel, actualLabel); - - } - - // precondition tests - - // if the person does not exist - ContractException contractException = assertThrows(ContractException.class, () -> personPropertyLabeler.getLabel(c, new PersonId(-1))); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - - // if the person id is null - contractException = assertThrows(ContractException.class, () -> personPropertyLabeler.getLabel(c, null)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "getDimension", args = {}) - public void testGetDimension() { - for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { - assertEquals(testPersonPropertyId, new PersonPropertyLabeler(testPersonPropertyId, (c) -> null).getDimension()); - } - } - -} diff --git a/gcm3/src/test/java/plugins/personproperties/support/AT_PropertyFilter.java b/gcm3/src/test/java/plugins/personproperties/support/AT_PropertyFilter.java deleted file mode 100644 index df337d5b2..000000000 --- a/gcm3/src/test/java/plugins/personproperties/support/AT_PropertyFilter.java +++ /dev/null @@ -1,143 +0,0 @@ -package plugins.personproperties.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.util.Set; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.NucleusError; -import nucleus.SimulationContext; -import plugins.partitions.support.Equality; -import plugins.partitions.support.Filter; -import plugins.partitions.support.FilterSensitivity; -import plugins.partitions.support.PartitionError; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import plugins.personproperties.datamanagers.PersonPropertiesDataManager; -import plugins.personproperties.events.PersonPropertyUpdateEvent; -import plugins.personproperties.testsupport.PersonPropertiesActionSupport; -import plugins.personproperties.testsupport.TestPersonPropertyId; -import plugins.stochastics.StochasticsDataManager; -import plugins.util.properties.PropertyError; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - -/** - * Test unit for {@link PropertyFilter}. - * - * @author Shawn Hatch - * - */ -@UnitTest(target = PropertyFilter.class) -public class AT_PropertyFilter { - - - - /** - * Tests - * {@link PropertyFilter#PropertyFilter(Context, PersonPropertyId, Equality, Object)} - */ - @Test - @UnitTestConstructor(args = { SimulationContext.class, PersonPropertyId.class, Equality.class, long.class }) - public void testConstructor() { - // nothing to test - } - - /** - * Tests - * {@link PropertyFilter#PropertyFilter(Context, PersonPropertyId, Equality, Object)} - */ - @Test - @UnitTestMethod(name = "validate", args = {}) - public void testValidate() { - - PersonPropertiesActionSupport.testConsumer(100, 7889475921077680704L, (c)->{ - final Filter filter = new PropertyFilter(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, Equality.EQUAL, 12); - assertNotNull(filter); - - - - ContractException contractException = assertThrows(ContractException.class, () -> new PropertyFilter(TestPersonPropertyId.getUnknownPersonPropertyId(), Equality.EQUAL, 12).validate(c)); - assertEquals(PersonPropertyError.UNKNOWN_PERSON_PROPERTY_ID, contractException.getErrorType()); - - contractException = assertThrows(ContractException.class, () -> new PropertyFilter(null, Equality.EQUAL, 12L).validate(c)); - assertEquals(PersonPropertyError.NULL_PERSON_PROPERTY_ID, contractException.getErrorType()); - - contractException = assertThrows(ContractException.class, () -> new PropertyFilter(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, null, 12).validate(c)); - assertEquals(PartitionError.NULL_EQUALITY_OPERATOR, contractException.getErrorType()); - - contractException = assertThrows(ContractException.class, () -> new PropertyFilter(TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK, Equality.EQUAL, "bad value").validate(c)); - assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); - - }); - - - } - - /** - * Tests {@link PropertyFilter#getFilterSensitivities()} - */ - @Test - @UnitTestMethod(name = "getFilterSensitivities", args = {}) - public void testGetFilterSensitivities() { - - Filter filter = new PropertyFilter(TestPersonPropertyId.PERSON_PROPERTY_1_BOOLEAN_MUTABLE_NO_TRACK, Equality.EQUAL, 12); - - Set> filterSensitivities = filter.getFilterSensitivities(); - assertNotNull(filterSensitivities); - assertEquals(filterSensitivities.size(), 1); - - FilterSensitivity filterSensitivity = filterSensitivities.iterator().next(); - assertEquals(PersonPropertyUpdateEvent.class, filterSensitivity.getEventClass()); - } - - /** - * Tests {@link PropertyFilter#evaluate(Context, PersonId)} - */ - @Test - @UnitTestMethod(name = "evaluate", args = { SimulationContext.class, PersonId.class }) - public void testEvaluate() { - - PersonPropertiesActionSupport.testConsumer(100, 9037413907425227057L, (c)->{ - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - PersonPropertiesDataManager personPropertiesDataManager = c.getDataManager(PersonPropertiesDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - TestPersonPropertyId testPersonPropertyId = TestPersonPropertyId.PERSON_PROPERTY_2_INTEGER_MUTABLE_NO_TRACK; - - Filter filter = new PropertyFilter(testPersonPropertyId, Equality.GREATER_THAN, 12); - - for (PersonId personId : peopleDataManager.getPeople()) { - int value = randomGenerator.nextInt(10) + 7; - personPropertiesDataManager.setPersonPropertyValue(personId, testPersonPropertyId, value); - } - - for (PersonId personId : peopleDataManager.getPeople()) { - Integer value = personPropertiesDataManager.getPersonPropertyValue(personId, testPersonPropertyId); - boolean expected = value > 12; - boolean actual = filter.evaluate(c, personId); - assertEquals(expected, actual); - } - - /* precondition: if the context is null */ - ContractException contractException = assertThrows(ContractException.class, () -> filter.evaluate(null, new PersonId(0))); - assertEquals(NucleusError.NULL_SIMULATION_CONTEXT,contractException.getErrorType()); - - /* precondition: if the person id is null */ - contractException = assertThrows(ContractException.class, () -> filter.evaluate(c, null)); - assertEquals(PersonError.NULL_PERSON_ID,contractException.getErrorType()); - - /* precondition: if the person id is unknown */ - contractException = assertThrows(ContractException.class, () -> filter.evaluate(c, new PersonId(123412342))); - assertEquals(PersonError.UNKNOWN_PERSON_ID,contractException.getErrorType()); - }); - } -} diff --git a/gcm3/src/test/java/plugins/personproperties/testsupport/AT_TestPersonPropertyId.java b/gcm3/src/test/java/plugins/personproperties/testsupport/AT_TestPersonPropertyId.java deleted file mode 100644 index a4c76dbc1..000000000 --- a/gcm3/src/test/java/plugins/personproperties/testsupport/AT_TestPersonPropertyId.java +++ /dev/null @@ -1,103 +0,0 @@ -package plugins.personproperties.testsupport; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.EnumSet; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import plugins.personproperties.support.PersonPropertyId; -import plugins.util.properties.PropertyDefinition; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.random.RandomGeneratorProvider; -import util.wrappers.MutableInteger; - -@UnitTest(target = TestPersonPropertyId.class) -public class AT_TestPersonPropertyId implements PersonPropertyId { - - @Test - @UnitTestMethod(name = "getRandomPersonPropertyId", args = { RandomGenerator.class }) - public void testGetRandomPersonPropertyId() { - Map countMap = new LinkedHashMap<>(); - for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { - countMap.put(testPersonPropertyId, new MutableInteger()); - } - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5471359242434395756L); - int sampleCount = 1000; - for (int i = 0; i < sampleCount; i++) { - TestPersonPropertyId randomPersonPropertyId = TestPersonPropertyId.getRandomPersonPropertyId(randomGenerator); - assertNotNull(randomPersonPropertyId); - countMap.get(randomPersonPropertyId).increment(); - } - - int minCount = sampleCount / TestPersonPropertyId.values().length; - minCount *= 4; - minCount /= 5; - for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { - assertTrue(countMap.get(testPersonPropertyId).getValue() > minCount); - } - } - - @Test - @UnitTestMethod(name = "getRandomPropertyValue", args = { RandomGenerator.class }) - public void testGetRandomPropertyValue() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8507625070108242089L); - - /* - * Show that randomly generated values are compatible with the - * associated property definition. Show that the values are reasonably - * unique - */ - for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { - PropertyDefinition propertyDefinition = testPersonPropertyId.getPropertyDefinition(); - Set values = new LinkedHashSet<>(); - for (int i = 0; i < 100; i++) { - Object propertyValue = testPersonPropertyId.getRandomPropertyValue(randomGenerator); - values.add(propertyValue); - assertTrue(propertyDefinition.getType().isAssignableFrom(propertyValue.getClass())); - } - //show that the values are reasonable unique - if (propertyDefinition.getType() != Boolean.class) { - assertTrue(values.size() > 10); - } else { - assertEquals(2, values.size()); - } - } - } - - @Test - @UnitTestMethod(name = "getPropertyDefinition", args = {}) - public void testGetPropertyDefinition() { - for (TestPersonPropertyId testPersonPropertyId : TestPersonPropertyId.values()) { - PropertyDefinition propertyDefinition = testPersonPropertyId.getPropertyDefinition(); - assertNotNull(propertyDefinition); - } - } - - @Test - @UnitTestMethod(name = "getUnknownPersonPropertyId", args = {}) - public void testGetUnknownPersonPropertyId() { - /* - * Shows that a generated unknown person property id is unique, not null - * and not a member of the enum - */ - Set testProperties = EnumSet.allOf(TestPersonPropertyId.class); - Set unknownPersonPropertyIds = new LinkedHashSet<>(); - for (int i = 0; i < 30; i++) { - PersonPropertyId unknownPersonPropertyId = TestPersonPropertyId.getUnknownPersonPropertyId(); - assertNotNull(unknownPersonPropertyId); - boolean unique = unknownPersonPropertyIds.add(unknownPersonPropertyId); - assertTrue(unique); - assertFalse(testProperties.contains(unknownPersonPropertyId)); - } - } -} \ No newline at end of file diff --git a/gcm3/src/test/java/plugins/regions/AT_RegionPlugin.java b/gcm3/src/test/java/plugins/regions/AT_RegionPlugin.java deleted file mode 100644 index 0e5211076..000000000 --- a/gcm3/src/test/java/plugins/regions/AT_RegionPlugin.java +++ /dev/null @@ -1,41 +0,0 @@ -package plugins.regions; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import nucleus.Plugin; -import nucleus.PluginId; -import plugins.people.PeoplePluginId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = RegionsPlugin.class) -public class AT_RegionPlugin { - - - - @Test - @UnitTestMethod(name = "getRegionPlugin", args = {RegionsPluginData.class}) - public void testGetRegionPlugin() { - RegionsPluginData regionsPluginData = RegionsPluginData.builder().build(); - Plugin regionPlugin = RegionsPlugin.getRegionsPlugin(regionsPluginData); - - assertEquals(1,regionPlugin.getPluginDatas().size()); - assertTrue(regionPlugin.getPluginDatas().contains(regionsPluginData)); - - assertEquals(RegionsPluginId.PLUGIN_ID, regionPlugin.getPluginId()); - - Set expectedDependencies = new LinkedHashSet<>(); - expectedDependencies.add(PeoplePluginId.PLUGIN_ID); - - assertEquals(expectedDependencies, regionPlugin.getPluginDependencies()); - - } - - -} diff --git a/gcm3/src/test/java/plugins/regions/AT_RegionPluginData.java b/gcm3/src/test/java/plugins/regions/AT_RegionPluginData.java deleted file mode 100644 index 9e2f61bbf..000000000 --- a/gcm3/src/test/java/plugins/regions/AT_RegionPluginData.java +++ /dev/null @@ -1,480 +0,0 @@ -package plugins.regions; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.PluginData; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import plugins.regions.support.RegionError; -import plugins.regions.support.RegionId; -import plugins.regions.support.RegionPropertyId; -import plugins.regions.testsupport.TestRegionId; -import plugins.regions.testsupport.TestRegionPropertyId; -import plugins.util.properties.PropertyDefinition; -import plugins.util.properties.PropertyError; -import plugins.util.properties.TimeTrackingPolicy; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; -import util.random.RandomGeneratorProvider; - -/** - * Test unit for {@linkplain RegionsPluginData}. Tests for - * RegionPluginData.Builder are limited to precondition tests and are otherwise - * covered via the class level tests. Note that the builder does not impose any - * ordering on the invocation of its methods and many validation tests are - * deferred to the build invocation. - * - * @author Shawn Hatch - * - */ -@UnitTest(target = RegionsPluginData.class) -public class AT_RegionPluginData { - - @Test - @UnitTestMethod(name = "builder", args = {}) - public void testBuilder() { - // show that we can create a builder - assertNotNull(RegionsPluginData.builder()); - } - - @Test - @UnitTestMethod(name = "getRegionIds", args = {}) - public void testGetRegionIds() { - // use the test region ids - Set expectedRegionIds = new LinkedHashSet<>(); - for (TestRegionId testRegionId : TestRegionId.values()) { - expectedRegionIds.add(testRegionId); - } - - // show that the regions added are present - RegionsPluginData.Builder builder = RegionsPluginData.builder(); - for (TestRegionId testRegionId : TestRegionId.values()) { - builder.addRegion(testRegionId); - } - RegionsPluginData regionsPluginData = builder.build(); - - Set actualRegionIds = regionsPluginData.getRegionIds(); - assertEquals(expectedRegionIds, actualRegionIds); - } - - @Test - @UnitTestMethod(name = "getRegionPropertyDefinition", args = { RegionId.class, RegionPropertyId.class }) - public void testGetRegionPropertyDefinition() { - RegionsPluginData.Builder builder = RegionsPluginData.builder(); - /* - * Place the various properties defined in TestRegionPropertyId into the - * builder and associate them with distinct property definitions. Each - * property definition will differ by its initial value. - */ - for (TestRegionId testRegionId : TestRegionId.values()) { - builder.addRegion(testRegionId); - - } - - int defaultValue = 0; - Map expectedDefinitions = new LinkedHashMap<>(); - for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(defaultValue++).build(); - expectedDefinitions.put(testRegionPropertyId, propertyDefinition); - builder.defineRegionProperty(testRegionPropertyId, propertyDefinition); - } - - // build the region initial data - RegionsPluginData regionsPluginData = builder.build(); - - /* - * Retrieve all of the property definitions in the region initial data - * and place them in a map for comparison. - */ - Map actualDefinitions = new LinkedHashMap<>(); - - Set regionPropertyIds = regionsPluginData.getRegionPropertyIds(); - for (RegionPropertyId regionPropertyId : regionPropertyIds) { - PropertyDefinition propertyDefinition = regionsPluginData.getRegionPropertyDefinition(regionPropertyId); - actualDefinitions.put(regionPropertyId, propertyDefinition); - } - - // show that the two maps are equal - assertEquals(expectedDefinitions, actualDefinitions); - - // precondition tests - - // if the region property id is null - ContractException contractException = assertThrows(ContractException.class, () -> regionsPluginData.getRegionPropertyDefinition(null)); - assertEquals(RegionError.NULL_REGION_PROPERTY_ID, contractException.getErrorType()); - - // if the region property id is unknown - contractException = assertThrows(ContractException.class, () -> regionsPluginData.getRegionPropertyDefinition(TestRegionPropertyId.getUnknownRegionPropertyId())); - assertEquals(RegionError.UNKNOWN_REGION_PROPERTY_ID, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "getRegionPropertyIds", args = { RegionId.class }) - public void testGetRegionPropertyIds() { - RegionsPluginData.Builder builder = RegionsPluginData.builder(); - /* - * Place the various region/property pairs defined in TestRegionId into - * the builder and associate them with distinct property definitions. - */ - - Set expectedPropertyIds = new LinkedHashSet<>(); - for (TestRegionId testRegionId : TestRegionId.values()) { - builder.addRegion(testRegionId); - - } - - for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(0).build(); - builder.defineRegionProperty(testRegionPropertyId, propertyDefinition); - expectedPropertyIds.add(testRegionPropertyId); - } - - // build the region initial data - RegionsPluginData regionsPluginData = builder.build(); - - /* - * Retrieve all of the property defintions in the region inital data and - * place them in a map for comparison. - */ - Set actualPropertyIds = regionsPluginData.getRegionPropertyIds(); - - // show that the two sets are equal - assertEquals(expectedPropertyIds, actualPropertyIds); - - // no precondition tests - - } - - @Test - @UnitTestMethod(name = "getRegionPropertyValue", args = { RegionId.class, RegionPropertyId.class }) - public void testGetRegionPropertyValue() { - RegionsPluginData.Builder builder = RegionsPluginData.builder(); - /* - * Place the various region/property pairs defined in TestRegionId into - * the builder and associate them with distinct property values. Each - * property value will be unique. - */ - - Map> expectedPropertyValues = new LinkedHashMap<>(); - for (TestRegionId testRegionId : TestRegionId.values()) { - builder.addRegion(testRegionId); - Map map = new LinkedHashMap<>(); - expectedPropertyValues.put(testRegionId, map); - } - - for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(0).build(); - builder.defineRegionProperty(testRegionPropertyId, propertyDefinition); - } - - int propertyValue = 0; - for (TestRegionId testRegionId : TestRegionId.values()) { - for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { - expectedPropertyValues.get(testRegionId).put(testRegionPropertyId, propertyValue); - builder.setRegionPropertyValue(testRegionId, testRegionPropertyId, propertyValue); - propertyValue++; - } - } - - // build the region initial data - RegionsPluginData regionsPluginData = builder.build(); - - /* - * Retrieve all of the property values in the region inital data and - * place them in a map for comparison. - */ - Map> actualPropertyValues = new LinkedHashMap<>(); - for (RegionId regionId : regionsPluginData.getRegionIds()) { - Map map = new LinkedHashMap<>(); - actualPropertyValues.put(regionId, map); - Set regionPropertyIds = regionsPluginData.getRegionPropertyIds(); - for (RegionPropertyId regionPropertyId : regionPropertyIds) { - Object regionPropertyValue = regionsPluginData.getRegionPropertyValue(regionId, regionPropertyId); - map.put(regionPropertyId, regionPropertyValue); - } - } - - // show that the two maps are equal - assertEquals(expectedPropertyValues, actualPropertyValues); - - // precondition tests - - // create some valid inputs to help with the precondition tests - RegionId validRegionId = TestRegionId.REGION_1; - RegionPropertyId validRegionPropertyId = TestRegionPropertyId.REGION_PROPERTY_1_BOOLEAN_MUTABLE; - - // if the region id is null - ContractException contractException = assertThrows(ContractException.class, () -> regionsPluginData.getRegionPropertyValue(null, validRegionPropertyId)); - assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); - - // if the region id is unknown - contractException = assertThrows(ContractException.class, () -> regionsPluginData.getRegionPropertyValue(TestRegionId.getUnknownRegionId(), validRegionPropertyId)); - assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); - - // if the region property id is null - contractException = assertThrows(ContractException.class, () -> regionsPluginData.getRegionPropertyValue(validRegionId, null)); - assertEquals(RegionError.NULL_REGION_PROPERTY_ID, contractException.getErrorType()); - - // if the region property id is unknown - contractException = assertThrows(ContractException.class, () -> regionsPluginData.getRegionPropertyValue(validRegionId, TestRegionPropertyId.getUnknownRegionPropertyId())); - assertEquals(RegionError.UNKNOWN_REGION_PROPERTY_ID, contractException.getErrorType()); - } - - @Test - @UnitTestMethod(name = "getPersonRegionArrivalTrackingPolicy", args = {}) - public void testGetPersonRegionArrivalTrackingPolicy() { - - RegionsPluginData.Builder builder = RegionsPluginData.builder(); - - for (TimeTrackingPolicy timeTrackingPolicy : TimeTrackingPolicy.values()) { - builder.setPersonRegionArrivalTracking(timeTrackingPolicy); - RegionsPluginData regionsPluginData = builder.build(); - assertEquals(timeTrackingPolicy, regionsPluginData.getPersonRegionArrivalTrackingPolicy()); - } - } - - @Test - @UnitTestMethod(target = RegionsPluginData.Builder.class, name = "build", args = {}) - public void testBuild() { - RegionsPluginData.Builder builder = RegionsPluginData.builder(); - // show the builder does not return null - assertNotNull(builder.build()); - - // precondition tests - - /* - * if a region property value was associated with a region id that was - * not properly added with an initial agent behavior. - */ - RegionPropertyId regionPropertyId = TestRegionPropertyId.REGION_PROPERTY_1_BOOLEAN_MUTABLE; - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setDefaultValue(0).setType(Integer.class).build(); - builder.defineRegionProperty(regionPropertyId, propertyDefinition); - builder.setRegionPropertyValue(TestRegionId.REGION_1, regionPropertyId, 5); - ContractException contractException = assertThrows(ContractException.class, () -> builder.build()); - assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); - - /* - * if a region property value was associated with a region property id - * that was not defined - */ - builder.addRegion(TestRegionId.REGION_1); - builder.setRegionPropertyValue(TestRegionId.REGION_1, regionPropertyId, 5); - contractException = assertThrows(ContractException.class, () -> builder.build()); - assertEquals(RegionError.UNKNOWN_REGION_PROPERTY_ID, contractException.getErrorType()); - - /* - * if a region property value was associated with a region and region - * property id that is incompatible with the corresponding property - * definition. - */ - builder.addRegion(TestRegionId.REGION_1); - builder.defineRegionProperty(regionPropertyId, propertyDefinition); - builder.setRegionPropertyValue(TestRegionId.REGION_1, regionPropertyId, "invalid value"); - contractException = assertThrows(ContractException.class, () -> builder.build()); - assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); - - /* - * if a region property definition does not have a default value and - * there are no property values added to replace that default. - */ - builder.addRegion(TestRegionId.REGION_1); - propertyDefinition = PropertyDefinition.builder().setType(Double.class).build(); - builder.defineRegionProperty(regionPropertyId, propertyDefinition); - contractException = assertThrows(ContractException.class, () -> builder.build()); - assertEquals(RegionError.INSUFFICIENT_REGION_PROPERTY_VALUE_ASSIGNMENT, contractException.getErrorType()); - } - - @Test - @UnitTestMethod(target = RegionsPluginData.Builder.class, name = "defineRegionProperty", args = { RegionId.class, RegionPropertyId.class, PropertyDefinition.class }) - public void testDefineRegionProperty() { - RegionsPluginData.Builder builder = RegionsPluginData.builder(); - - RegionPropertyId regionPropertyId = TestRegionPropertyId.REGION_PROPERTY_1_BOOLEAN_MUTABLE; - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setDefaultValue(9).setType(Integer.class).build(); - - // if the region property id is null - ContractException contractException = assertThrows(ContractException.class, () -> builder.defineRegionProperty(null, propertyDefinition)); - assertEquals(RegionError.NULL_REGION_PROPERTY_ID, contractException.getErrorType()); - - // if the property definition is null - contractException = assertThrows(ContractException.class, () -> builder.defineRegionProperty(regionPropertyId, null)); - assertEquals(RegionError.NULL_REGION_PROPERTY_DEFINITION, contractException.getErrorType()); - - /* - * if a property definition for the given region id and property id was - * previously defined. - */ - builder.defineRegionProperty(regionPropertyId, propertyDefinition); - contractException = assertThrows(ContractException.class, () -> builder.defineRegionProperty(regionPropertyId, propertyDefinition)); - assertEquals(RegionError.DUPLICATE_REGION_PROPERTY_DEFINITION_ASSIGNMENT, contractException.getErrorType()); - } - - @Test - @UnitTestMethod(target = RegionsPluginData.Builder.class, name = "addRegion", args = { RegionId.class, RegionPropertyId.class, PropertyDefinition.class }) - public void testAddRegion() { - RegionsPluginData.Builder builder = RegionsPluginData.builder(); - - // if the region id is null - ContractException contractException = assertThrows(ContractException.class, () -> builder.addRegion(null)); - assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); - } - - @Test - @UnitTestMethod(target = RegionsPluginData.Builder.class, name = "setRegionPropertyValue", args = { RegionId.class, RegionPropertyId.class, Object.class }) - public void testSetRegionPropertyValue() { - RegionsPluginData.Builder builder = RegionsPluginData.builder(); - - RegionId regionId = TestRegionId.REGION_1; - - RegionPropertyId regionPropertyId = TestRegionPropertyId.REGION_PROPERTY_1_BOOLEAN_MUTABLE; - Object validValue = 5; - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setDefaultValue(0).setType(Integer.class).build(); - - builder.addRegion(regionId); - builder.defineRegionProperty(regionPropertyId, propertyDefinition); - - // if the region id is null - ContractException contractException = assertThrows(ContractException.class, () -> builder.setRegionPropertyValue(null, regionPropertyId, validValue)); - assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); - - // if the region property id is null - contractException = assertThrows(ContractException.class, () -> builder.setRegionPropertyValue(regionId, null, validValue)); - assertEquals(RegionError.NULL_REGION_PROPERTY_ID, contractException.getErrorType()); - - // if the region property value was previously defined - builder.setRegionPropertyValue(regionId, regionPropertyId, validValue); - contractException = assertThrows(ContractException.class, () -> builder.setRegionPropertyValue(regionId, regionPropertyId, validValue)); - assertEquals(RegionError.DUPLICATE_REGION_PROPERTY_VALUE, contractException.getErrorType()); - - // Note: Invalid values will not throw an exception and are caught - // during the build invocation. - } - - @Test - @UnitTestMethod(target = RegionsPluginData.Builder.class, name = "setPersonRegionArrivalTracking", args = { TimeTrackingPolicy.class }) - public void testSetPersonRegionArrivalTracking() { - RegionsPluginData.Builder builder = RegionsPluginData.builder(); - - // if the timeTrackingPolicy is null - ContractException contractException = assertThrows(ContractException.class, () -> builder.setPersonRegionArrivalTracking(null)); - assertEquals(RegionError.NULL_TIME_TRACKING_POLICY, contractException.getErrorType()); - - // if the timeTrackingPolicy was previously defined - builder.setPersonRegionArrivalTracking(TimeTrackingPolicy.TRACK_TIME); - contractException = assertThrows(ContractException.class, () -> builder.setPersonRegionArrivalTracking(TimeTrackingPolicy.DO_NOT_TRACK_TIME)); - assertEquals(RegionError.DUPLICATE_TIME_TRACKING_POLICY, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(target = RegionsPluginData.Builder.class, name = "setPersonRegion", args = { PersonId.class, RegionId.class }) - public void testSetPersonRegion() { - RegionsPluginData.Builder builder = RegionsPluginData.builder(); - - PersonId personId = new PersonId(45); - RegionId regionId = TestRegionId.REGION_1; - - // if the person id is null - ContractException contractException = assertThrows(ContractException.class, () -> builder.setPersonRegion(null, regionId)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - - // if the region id is null - contractException = assertThrows(ContractException.class, () -> builder.setPersonRegion(personId, null)); - assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); - - // if the person's region was previously defined - builder.setPersonRegion(personId, regionId); - contractException = assertThrows(ContractException.class, () -> builder.setPersonRegion(personId, regionId)); - assertEquals(RegionError.DUPLICATE_PERSON_REGION_ASSIGNMENT, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "getCloneBuilder", args = {}) - public void testGetCloneBuilder() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6712645837048772782L); - RegionsPluginData.Builder regionPluginDataBuilder = RegionsPluginData.builder(); - regionPluginDataBuilder.setPersonRegionArrivalTracking(TimeTrackingPolicy.TRACK_TIME); - for (TestRegionId testRegionId : TestRegionId.values()) { - regionPluginDataBuilder.addRegion(testRegionId); - } - for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { - regionPluginDataBuilder.defineRegionProperty(testRegionPropertyId, testRegionPropertyId.getPropertyDefinition()); - } - for (TestRegionId testRegionId : TestRegionId.values()) { - for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { - if (randomGenerator.nextBoolean()) { - Object randomPropertyValue = testRegionPropertyId.getRandomPropertyValue(randomGenerator); - regionPluginDataBuilder.setRegionPropertyValue(testRegionId, testRegionPropertyId, randomPropertyValue); - } - } - } - int personCount = 100; - for (int i = 0; i < personCount; i++) { - PersonId personId = new PersonId(i); - TestRegionId randomRegionId = TestRegionId.getRandomRegionId(randomGenerator); - regionPluginDataBuilder.setPersonRegion(personId, randomRegionId); - } - - RegionsPluginData regionsPluginData = regionPluginDataBuilder.build(); - - PluginData pluginData = regionsPluginData.getCloneBuilder().build(); - - // show that the clone plugin data has the correct type - assertTrue(pluginData instanceof RegionsPluginData); - RegionsPluginData cloneRegionPluginData = (RegionsPluginData) pluginData; - - // show that the two plugin datas have the same arrival tracking policy - assertEquals(regionsPluginData.getPersonRegionArrivalTrackingPolicy(), cloneRegionPluginData.getPersonRegionArrivalTrackingPolicy()); - - // show that the two plugin datas have the same region ids - assertEquals(regionsPluginData.getRegionIds(), cloneRegionPluginData.getRegionIds()); - - // show that the two plugin datas have the same region property ids - assertEquals(regionsPluginData.getRegionPropertyIds(), cloneRegionPluginData.getRegionPropertyIds()); - - // show that the two plugin datas have the same region property - // definitions - for (RegionPropertyId regionPropertyId : regionsPluginData.getRegionPropertyIds()) { - PropertyDefinition expectedPropertyDefinition = regionsPluginData.getRegionPropertyDefinition(regionPropertyId); - PropertyDefinition actualPropertyDefinition = cloneRegionPluginData.getRegionPropertyDefinition(regionPropertyId); - assertEquals(expectedPropertyDefinition, actualPropertyDefinition); - } - - // show that the two plugin datas have the same region property values - for (RegionId regionId : regionsPluginData.getRegionIds()) { - for (RegionPropertyId regionPropertyId : regionsPluginData.getRegionPropertyIds()) { - Object expectedPropertyValue = regionsPluginData.getRegionPropertyValue(regionId, regionPropertyId); - Object actualPropertyValue = cloneRegionPluginData.getRegionPropertyValue(regionId, regionPropertyId); - assertEquals(expectedPropertyValue, actualPropertyValue); - } - } - - // show that the two plugin datas have the same people - Set expectedPersonIds = regionsPluginData.getPersonIds(); - Set actualPersonIds = cloneRegionPluginData.getPersonIds(); - assertEquals(expectedPersonIds, actualPersonIds); - // show that the two plugin datas have assigned the people to the same - // regions - for (PersonId personId : expectedPersonIds) { - RegionId expectedRegionId = regionsPluginData.getPersonRegion(personId); - RegionId actualRegionId = cloneRegionPluginData.getPersonRegion(personId); - assertEquals(expectedRegionId, actualRegionId); - } - - } - -} diff --git a/gcm3/src/test/java/plugins/regions/AT_RegionPluginId.java b/gcm3/src/test/java/plugins/regions/AT_RegionPluginId.java deleted file mode 100644 index b604eb3b9..000000000 --- a/gcm3/src/test/java/plugins/regions/AT_RegionPluginId.java +++ /dev/null @@ -1,13 +0,0 @@ -package plugins.regions; - -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import tools.annotations.UnitTest; - -@UnitTest(target = RegionsPluginId.class) -public class AT_RegionPluginId { - - public void test() { - assertNotNull(RegionsPluginId.PLUGIN_ID); - } -} diff --git a/gcm3/src/test/java/plugins/regions/actors/AT_RegionPropertyReport.java b/gcm3/src/test/java/plugins/regions/actors/AT_RegionPropertyReport.java deleted file mode 100644 index 591bcf5aa..000000000 --- a/gcm3/src/test/java/plugins/regions/actors/AT_RegionPropertyReport.java +++ /dev/null @@ -1,210 +0,0 @@ -package plugins.regions.actors; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashMap; -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import nucleus.Experiment; -import nucleus.Plugin; -import nucleus.testsupport.testplugin.ExperimentPlanCompletionObserver; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import plugins.people.PeoplePlugin; -import plugins.people.PeoplePluginData; -import plugins.regions.RegionsPlugin; -import plugins.regions.RegionsPluginData; -import plugins.regions.datamanagers.RegionsDataManager; -import plugins.regions.support.RegionId; -import plugins.regions.support.RegionPropertyId; -import plugins.regions.support.SimpleRegionId; -import plugins.regions.support.SimpleRegionPropertyId; -import plugins.reports.ReportsPlugin; -import plugins.reports.ReportsPluginData; -import plugins.reports.support.ReportHeader; -import plugins.reports.support.ReportId; -import plugins.reports.support.ReportItem; -import plugins.reports.support.SimpleReportId; -import plugins.reports.testsupport.TestReportItemOutputConsumer; -import plugins.stochastics.StochasticsPlugin; -import plugins.stochastics.StochasticsPluginData; -import plugins.util.properties.PropertyDefinition; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = RegionPropertyReport.class) -public class AT_RegionPropertyReport { - - @Test - @UnitTestMethod(name = "init", args = {}) - public void testInit() { - - /* - * We will add three regions, one agent and the region property report - * to the engine. The regions will have a few properties and the agent - * will alter various region properties over time. Report items from the - * report will be collected in an output consumer. The expected report - * items will be collected in a separate consumer and the consumers will - * be compared for equality. The output consumers properly accounts for - * report item duplications. - */ - - Experiment.Builder builder = Experiment.builder(); - - RegionsPluginData.Builder regionBuilder = RegionsPluginData.builder(); - - // add regions A, B and C - RegionId regionA = new SimpleRegionId("Region_A"); - regionBuilder.addRegion(regionA); - RegionId regionB = new SimpleRegionId("Region_B"); - regionBuilder.addRegion(regionB); - RegionId regionC = new SimpleRegionId("Region_C"); - regionBuilder.addRegion(regionC); - - // add the region properties - RegionPropertyId prop_age = new SimpleRegionPropertyId("prop_age"); - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setDefaultValue(3).setType(Integer.class).build(); - regionBuilder.defineRegionProperty(prop_age, propertyDefinition); - - RegionPropertyId prop_infected = new SimpleRegionPropertyId("prop_infected"); - propertyDefinition = PropertyDefinition.builder().setDefaultValue(false).setType(Boolean.class).build(); - regionBuilder.defineRegionProperty(prop_infected, propertyDefinition); - - RegionPropertyId prop_length = new SimpleRegionPropertyId("prop_length"); - propertyDefinition = PropertyDefinition.builder().setDefaultValue(10.0).setType(Double.class).build(); - regionBuilder.defineRegionProperty(prop_length, propertyDefinition); - - RegionPropertyId prop_height = new SimpleRegionPropertyId("prop_height"); - propertyDefinition = PropertyDefinition.builder().setDefaultValue(5.0).setType(Double.class).build(); - regionBuilder.defineRegionProperty(prop_height, propertyDefinition); - - RegionPropertyId prop_policy = new SimpleRegionPropertyId("prop_policy"); - propertyDefinition = PropertyDefinition.builder().setDefaultValue("start").setType(String.class).build(); - regionBuilder.defineRegionProperty(prop_policy, propertyDefinition); - - builder.addPlugin(RegionsPlugin.getRegionsPlugin(regionBuilder.build())); - - // add the report - ReportsPluginData reportsPluginData = ReportsPluginData .builder()// - .addReport(() -> new RegionPropertyReport(REPORT_ID)::init)// - .build();// - - builder.addPlugin(ReportsPlugin.getReportPlugin(reportsPluginData)); - - // add remaining plugins - builder.addPlugin(PeoplePlugin.getPeoplePlugin(PeoplePluginData.builder().build())); - builder.addPlugin(StochasticsPlugin.getStochasticsPlugin(StochasticsPluginData.builder().setSeed(8833508541323194123L).build())); - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // create an agent and have it assign various region properties at - // various times - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0.0, (c) -> { - /* - * note that this is time 0 and should show that property initial - * values are still reported correctly - */ - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - regionsDataManager.setRegionPropertyValue(regionC, prop_policy, "move"); - })); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1.0, (c) -> { - // two settings of the same property - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - regionsDataManager.setRegionPropertyValue(regionA, prop_age, 45); - regionsDataManager.setRegionPropertyValue(regionA, prop_age, 45); - })); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2.0, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - regionsDataManager.setRegionPropertyValue(regionA, prop_age, 100); - regionsDataManager.setRegionPropertyValue(regionB, prop_height, 13.6); - regionsDataManager.setRegionPropertyValue(regionC, prop_policy, "hold"); - - })); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(3.0, (c) -> { - - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - regionsDataManager.setRegionPropertyValue(regionC, prop_policy, "terminate"); - - // note the duplicated value - - regionsDataManager.setRegionPropertyValue(regionB, prop_height, 99.7); - regionsDataManager.setRegionPropertyValue(regionB, prop_height, 99.7); - - // and now a third setting of the same property to a new value - regionsDataManager.setRegionPropertyValue(regionB, prop_height, 100.0); - regionsDataManager.setRegionPropertyValue(regionB, prop_length, 60.0); - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - builder.addPlugin(testPlugin); - - // build and execute the engine - ExperimentPlanCompletionObserver experimentPlanCompletionObserver = new ExperimentPlanCompletionObserver(); - - TestReportItemOutputConsumer reportItemOutputConsumer = new TestReportItemOutputConsumer(); - builder.addOutputHandler(reportItemOutputConsumer::init); - builder.addOutputHandler(experimentPlanCompletionObserver::init); - builder.reportProgressToConsole(false); - builder.reportFailuresToConsole(false); - builder.build().execute(); - - // show that all actions were executed - assertTrue(experimentPlanCompletionObserver.getActionCompletionReport(0).get().isComplete()); - - /* - * Collect the expected report items. Note that order does not matter. * - */ - Map expectedReportItems = new LinkedHashMap<>(); - - expectedReportItems.put(getReportItem(0.0, regionA, prop_age, 3), 1); - expectedReportItems.put(getReportItem(0.0, regionA, prop_infected, false), 1); - expectedReportItems.put(getReportItem(0.0, regionB, prop_height, 5.0), 1); - expectedReportItems.put(getReportItem(0.0, regionB, prop_length, 10.0), 1); - expectedReportItems.put(getReportItem(0.0, regionC, prop_policy, "start"), 1); - expectedReportItems.put(getReportItem(1.0, regionA, prop_age, 45), 2); - expectedReportItems.put(getReportItem(2.0, regionA, prop_age, 100), 1); - expectedReportItems.put(getReportItem(2.0, regionB, prop_height, 13.6), 1); - expectedReportItems.put(getReportItem(2.0, regionC, prop_policy, "hold"), 1); - expectedReportItems.put(getReportItem(3.0, regionC, prop_policy, "terminate"), 1); - expectedReportItems.put(getReportItem(3.0, regionB, prop_height, 99.7), 2); - expectedReportItems.put(getReportItem(3.0, regionB, prop_height, 100.0), 1); - expectedReportItems.put(getReportItem(3.0, regionB, prop_length, 60.0), 1); - expectedReportItems.put(getReportItem(0.0, regionC, prop_policy, "move"), 1); - expectedReportItems.put(getReportItem(0.0, regionA, prop_length, 10.0), 1); - expectedReportItems.put(getReportItem(0.0, regionA, prop_height, 5.0), 1); - expectedReportItems.put(getReportItem(0.0, regionA, prop_policy, "start"), 1); - expectedReportItems.put(getReportItem(0.0, regionB, prop_age, 3), 1); - expectedReportItems.put(getReportItem(0.0, regionB, prop_infected, false), 1); - expectedReportItems.put(getReportItem(0.0, regionB, prop_policy, "start"), 1); - expectedReportItems.put(getReportItem(0.0, regionC, prop_age, 3), 1); - expectedReportItems.put(getReportItem(0.0, regionC, prop_infected, false), 1); - expectedReportItems.put(getReportItem(0.0, regionC, prop_length, 10.0), 1); - expectedReportItems.put(getReportItem(0.0, regionC, prop_height, 5.0), 1); - - Map actualReportItems = reportItemOutputConsumer.getReportItems().get(0); - - assertEquals(expectedReportItems, actualReportItems); - } - - private static ReportItem getReportItem(Object... values) { - ReportItem.Builder builder = ReportItem.builder(); - builder.setReportId(REPORT_ID); - builder.setReportHeader(REPORT_HEADER); - for (Object value : values) { - builder.addValue(value); - } - return builder.build(); - } - - private static final ReportId REPORT_ID = new SimpleReportId("region property report"); - - private static final ReportHeader REPORT_HEADER = ReportHeader.builder().add("Time").add("Region").add("Property").add("Value").build(); -} diff --git a/gcm3/src/test/java/plugins/regions/datamanagers/AT_RegionsDataManager.java b/gcm3/src/test/java/plugins/regions/datamanagers/AT_RegionsDataManager.java deleted file mode 100644 index ee5a550e6..000000000 --- a/gcm3/src/test/java/plugins/regions/datamanagers/AT_RegionsDataManager.java +++ /dev/null @@ -1,1605 +0,0 @@ -package plugins.regions.datamanagers; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.DataManagerContext; -import nucleus.EventLabel; -import nucleus.EventLabeler; -import nucleus.NucleusError; -import nucleus.Plugin; -import nucleus.Simulation; -import nucleus.Simulation.Builder; -import nucleus.testsupport.testplugin.ScenarioPlanCompletionObserver; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestError; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import plugins.people.PeoplePlugin; -import plugins.people.PeoplePluginData; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.events.BulkPersonAdditionEvent; -import plugins.people.events.PersonAdditionEvent; -import plugins.people.events.PersonImminentRemovalEvent; -import plugins.people.support.BulkPersonConstructionData; -import plugins.people.support.PersonConstructionData; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import plugins.regions.RegionsPlugin; -import plugins.regions.RegionsPluginData; -import plugins.regions.events.PersonRegionUpdateEvent; -import plugins.regions.events.RegionPropertyUpdateEvent; -import plugins.regions.support.RegionError; -import plugins.regions.support.RegionId; -import plugins.regions.support.RegionPropertyId; -import plugins.regions.testsupport.RegionsActionSupport; -import plugins.regions.testsupport.TestRegionId; -import plugins.regions.testsupport.TestRegionPropertyId; -import plugins.stochastics.StochasticsDataManager; -import plugins.stochastics.StochasticsPlugin; -import plugins.stochastics.StochasticsPluginData; -import plugins.util.properties.PropertyDefinition; -import plugins.util.properties.PropertyError; -import plugins.util.properties.TimeTrackingPolicy; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; -import util.random.RandomGeneratorProvider; -import util.wrappers.MultiKey; -import util.wrappers.MutableDouble; -import util.wrappers.MutableInteger; - -@UnitTest(target = RegionsDataManager.class) -public class AT_RegionsDataManager { - - @Test - @UnitTestMethod(name = "expandCapacity", args = { int.class }) - public void testExpandCapacity() { - RegionsActionSupport.testConsumer(20, 3161087621160007875L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - // show that a negative growth causes an exception - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> regionsDataManager.expandCapacity(-1)); - assertEquals(PersonError.NEGATIVE_GROWTH_PROJECTION, contractException.getErrorType()); - }); - - // use manual tests for non-negative growth - } - - @Test - @UnitTestConstructor(args = { RegionsPluginData.class }) - public void testConstructor() { - // this test is covered by the remaining tests - ContractException contractException = assertThrows(ContractException.class, () -> new RegionsDataManager(null)); - assertEquals(RegionError.NULL_REGION_PLUGIN_DATA, contractException.getErrorType()); - } - - @Test - @UnitTestMethod(name = "getPeopleInRegion", args = { RegionId.class }) - public void testGetPeopleInRegion() { - - // create a container to hold expectations - Map> expectedPeopelInRegions = new LinkedHashMap<>(); - for (TestRegionId testRegionId : TestRegionId.values()) { - expectedPeopelInRegions.put(testRegionId, new LinkedHashSet<>()); - } - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // show that each region is empty at time zero - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - for (TestRegionId testRegionId : TestRegionId.values()) { - assertEquals(0, regionsDataManager.getPeopleInRegion(testRegionId).size()); - } - })); - - // add some people - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - for (int i = 0; i < 100; i++) { - TestRegionId regionId = TestRegionId.getRandomRegionId(stochasticsDataManager.getRandomGenerator()); - PersonConstructionData personConstructionData = PersonConstructionData.builder().add(regionId).build(); - PersonId personId = peopleDataManager.addPerson(personConstructionData); - expectedPeopelInRegions.get(regionId).add(personId); - } - })); - - // show that the people in the regions match expectations - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - for (TestRegionId testRegionId : TestRegionId.values()) { - Set expectedPeople = expectedPeopelInRegions.get(testRegionId); - LinkedHashSet actualPeople = new LinkedHashSet<>(regionsDataManager.getPeopleInRegion(testRegionId)); - assertEquals(expectedPeople, actualPeople); - } - })); - - // build and add the action plugin - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - RegionsActionSupport.testConsumers(0, 3347423560010833899L, TimeTrackingPolicy.TRACK_TIME, testPlugin); - - // precondition test: if the region id is null - RegionsActionSupport.testConsumer(0, 9052181434511982170L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - - ContractException contractException = assertThrows(ContractException.class, () -> regionsDataManager.getPeopleInRegion(null)); - assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); - }); - - // precondition test: if the region id is unknown - RegionsActionSupport.testConsumer(0, 1410190102298165957L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - - // if the region id is unknown - ContractException contractException = assertThrows(ContractException.class, () -> regionsDataManager.getPeopleInRegion(TestRegionId.getUnknownRegionId())); - assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "getPersonRegion", args = { PersonId.class }) - public void testGetPersonRegion() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // create a container to hold expectations - Map expectedPersonRegions = new LinkedHashMap<>(); - - int numberOfPeople = 100; - - /* - * Add some people and show that their regions are correctly assigned. - */ - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - for (int i = 0; i < numberOfPeople; i++) { - // select a region at random - TestRegionId regionId = TestRegionId.getRandomRegionId(stochasticsDataManager.getRandomGenerator()); - // create the person - PersonConstructionData personConstructionData = PersonConstructionData.builder().add(regionId).build(); - PersonId personId = peopleDataManager.addPerson(personConstructionData); - // show that the person has the correct region - assertEquals(regionId, regionsDataManager.getPersonRegion(personId)); - // add the person to the expectations - expectedPersonRegions.put(personId, regionId); - } - })); - - // move people over time and show that each time they are moved the - // correct region is reported - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - double planTime = 0; - for (PersonId personId : peopleDataManager.getPeople()) { - c.addPlan((c2) -> { - // determine the person's current region - RegionsDataManager regionsDataManager = c2.getDataManager(RegionsDataManager.class); - TestRegionId regionId = regionsDataManager.getPersonRegion(personId); - // select the next region for the person - regionId = regionId.next(); - // move the person - regionsDataManager.setPersonRegion(personId, regionId); - /* - * show that the region arrival time for the person is the - * current time in the simulation - */ - assertEquals(regionId, regionsDataManager.getPersonRegion(personId)); - // update the expectations - expectedPersonRegions.put(personId, regionId); - }, planTime); - planTime++; - } - })); - - double postPersonMovementTime = numberOfPeople; - - /* - * Show that the people region arrival times are maintained over time - */ - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(postPersonMovementTime, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - for (PersonId personId : peopleDataManager.getPeople()) { - RegionId expectedRegionId = expectedPersonRegions.get(personId); - RegionId actualRegionId = regionsDataManager.getPersonRegion(personId); - assertEquals(expectedRegionId, actualRegionId); - } - })); - // build and add the action plugin - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - RegionsActionSupport.testConsumers(0, 5151111920517015649L, TimeTrackingPolicy.TRACK_TIME, testPlugin); - - // precondition test: if the person id is null - RegionsActionSupport.testConsumer(0, 1490027040692903854L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> regionsDataManager.getPersonRegion(null)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - }); - - // precondition test: if the person id is unknown - RegionsActionSupport.testConsumer(0, 2144445839100475443L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> regionsDataManager.getPersonRegion(new PersonId(-1))); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "getPersonRegionArrivalTime", args = { PersonId.class }) - public void testGetPersonRegionArrivalTime() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // create a container to hold expectations - Map expectedPersonRegionArrivalTimes = new LinkedHashMap<>(); - - int numberOfPeople = 100; - - /* - * Add some people and show that their region arrival times are zero. - */ - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - for (int i = 0; i < numberOfPeople; i++) { - // select a region at random - TestRegionId regionId = TestRegionId.getRandomRegionId(stochasticsDataManager.getRandomGenerator()); - // create the person - PersonConstructionData personConstructionData = PersonConstructionData.builder().add(regionId).build(); - PersonId personId = peopleDataManager.addPerson(personConstructionData); - - // show that the person has a region arrival time of zero - assertEquals(0.0, regionsDataManager.getPersonRegionArrivalTime(personId)); - - // add the person to the expectations - expectedPersonRegionArrivalTimes.put(personId, new MutableDouble()); - } - })); - - // move people over time and show that each time they are moved the - // their arrival time is correct - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - double planTime = 0; - for (PersonId personId : peopleDataManager.getPeople()) { - c.addPlan((c2) -> { - // determine the person's current region - RegionsDataManager regionsDataManager = c2.getDataManager(RegionsDataManager.class); - TestRegionId regionId = regionsDataManager.getPersonRegion(personId); - // select the next region for the person - regionId = regionId.next(); - // move the person - regionsDataManager.setPersonRegion(personId, regionId); - /* - * show that the region arrival time for the person is the - * current time in the simulation - */ - assertEquals(c2.getTime(), regionsDataManager.getPersonRegionArrivalTime(personId)); - // update the expectations - expectedPersonRegionArrivalTimes.get(personId).setValue(c2.getTime()); - }, planTime); - planTime++; - } - })); - - double postPersonMovementTime = numberOfPeople; - - /* - * Show that the people region arrival times are maintained over time - */ - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(postPersonMovementTime, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - for (PersonId personId : peopleDataManager.getPeople()) { - double expectedArrivalTime = expectedPersonRegionArrivalTimes.get(personId).getValue(); - double actualArrivalTime = regionsDataManager.getPersonRegionArrivalTime(personId); - assertEquals(expectedArrivalTime, actualArrivalTime); - } - })); - - // build and add the action plugin - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - RegionsActionSupport.testConsumers(0, 2278422620232176214L, TimeTrackingPolicy.TRACK_TIME, testPlugin); - - // precondition test: if region arrival times are not being tracked - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - for (int i = 0; i < numberOfPeople; i++) { - // select a region at random - TestRegionId regionId = TestRegionId.getRandomRegionId(stochasticsDataManager.getRandomGenerator()); - // create the person - PersonConstructionData personConstructionData = PersonConstructionData.builder().add(regionId).build(); - peopleDataManager.addPerson(personConstructionData); - } - })); - - - - // build and add the action plugin - testPluginData = pluginBuilder.build(); - testPlugin = TestPlugin.getTestPlugin(testPluginData); - RegionsActionSupport.testConsumers(0, 9214210856215652451L, TimeTrackingPolicy.TRACK_TIME, testPlugin); - - // precondition test: if region arrival times are not being tracked - RegionsActionSupport.testConsumer(10, 1906010286127446114L, TimeTrackingPolicy.DO_NOT_TRACK_TIME, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - - // if region arrival times are not being tracked - ContractException contractException = assertThrows(ContractException.class, () -> regionsDataManager.getPersonRegionArrivalTime(new PersonId(0))); - assertEquals(RegionError.REGION_ARRIVAL_TIMES_NOT_TRACKED, contractException.getErrorType()); - }); - - // precondition test: if the person id is null - RegionsActionSupport.testConsumer(0, 9214210856215652451L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> regionsDataManager.getPersonRegionArrivalTime(null)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - }); - - // precondition test: if the person id is unknown - RegionsActionSupport.testConsumer(0, 9132391945335483479L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> regionsDataManager.getPersonRegionArrivalTime(new PersonId(-1))); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "getPersonRegionArrivalTrackingPolicy", args = {}) - public void testGetPersonRegionArrivalTrackingPolicy() { - for (TimeTrackingPolicy timeTrackingPolicy : TimeTrackingPolicy.values()) { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2934280155665825436L); - RegionsActionSupport.testConsumer(0, randomGenerator.nextLong(), timeTrackingPolicy, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - assertEquals(timeTrackingPolicy, regionsDataManager.getPersonRegionArrivalTrackingPolicy()); - }); - } - } - - @Test - @UnitTestMethod(name = "getRegionIds", args = {}) - public void testGetRegionIds() { - RegionsActionSupport.testConsumer(0, 9132391945335483479L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - - Set expectedRegionIds = new LinkedHashSet<>(); - for (TestRegionId testRegionId : TestRegionId.values()) { - expectedRegionIds.add(testRegionId); - } - assertEquals(expectedRegionIds, regionsDataManager.getRegionIds()); - }); - } - - @Test - @UnitTestMethod(name = "getRegionPopulationCount", args = { RegionId.class }) - public void testGetRegionPopulationCount() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // show that each region has no people - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - for (TestRegionId testRegionId : TestRegionId.values()) { - assertEquals(0, regionsDataManager.getRegionPopulationCount(testRegionId)); - } - })); - - // show that adding people results in the correct population counts - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - int n = TestRegionId.values().length; - for (int i = 0; i < 3 * n; i++) { - TestRegionId regionId = TestRegionId.values()[i % n]; - PersonConstructionData personConstructionData = PersonConstructionData.builder().add(regionId).build(); - peopleDataManager.addPerson(personConstructionData); - } - - for (TestRegionId testRegionId : TestRegionId.values()) { - assertEquals(3, regionsDataManager.getRegionPopulationCount(testRegionId)); - } - - })); - - // precondition tests - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - - // if the region id is null - ContractException contractException = assertThrows(ContractException.class, () -> regionsDataManager.getRegionPopulationCount(null)); - assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); - - // if the region id is unknown - contractException = assertThrows(ContractException.class, () -> regionsDataManager.getRegionPopulationCount(TestRegionId.getUnknownRegionId())); - assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); - - })); - - // build and add the action plugin - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - RegionsActionSupport.testConsumers(0, 1525815460460902517L, TimeTrackingPolicy.TRACK_TIME, testPlugin); - - } - - @Test - @UnitTestMethod(name = "getRegionPopulationTime", args = { RegionId.class }) - public void testGetRegionPopulationTime() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // show that each region has a zero population time - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - for (TestRegionId testRegionId : TestRegionId.values()) { - assertEquals(0, regionsDataManager.getRegionPopulationTime(testRegionId)); - } - - })); - - Map expectedAssignmentTimes = new LinkedHashMap<>(); - for (TestRegionId testRegionId : TestRegionId.values()) { - expectedAssignmentTimes.put(testRegionId, new MutableDouble()); - } - - int numberOfPeople = 100; - - // show that adding people over time results in the correct population - // times - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - for (int i = 0; i < numberOfPeople; i++) { - double planTime = i; - c.addPlan((c2) -> { - RegionsDataManager regionsDataManager = c2.getDataManager(RegionsDataManager.class); - StochasticsDataManager stochasticsDataManager = c2.getDataManager(StochasticsDataManager.class); - TestRegionId regionId = TestRegionId.getRandomRegionId(stochasticsDataManager.getRandomGenerator()); - PersonConstructionData personConstructionData = PersonConstructionData.builder().add(regionId).build(); - PeopleDataManager peopleDataManager = c2.getDataManager(PeopleDataManager.class); - peopleDataManager.addPerson(personConstructionData); - assertEquals(c2.getTime(), regionsDataManager.getRegionPopulationTime(regionId), 0); - expectedAssignmentTimes.get(regionId).setValue(c2.getTime()); - }, planTime); - } - })); - - // show that the proper region population times are maintained - // after all the person additions are complete. - double postPersonAdditionTime = numberOfPeople; - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(postPersonAdditionTime, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - for (TestRegionId testRegionId : TestRegionId.values()) { - double expectedRegionPopulationTime = expectedAssignmentTimes.get(testRegionId).getValue(); - double actualRegionPopulationTime = regionsDataManager.getRegionPopulationTime(testRegionId); - assertEquals(expectedRegionPopulationTime, actualRegionPopulationTime); - } - })); - - // build and add the action plugin - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - RegionsActionSupport.testConsumers(numberOfPeople, 2430955549982485988L, TimeTrackingPolicy.TRACK_TIME, testPlugin); - - // precondition test: if the region id is null - RegionsActionSupport.testConsumer(numberOfPeople, 3091951498637393024L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> regionsDataManager.getRegionPopulationTime(null)); - assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); - - }); - - // precondition tests: if the region id is unknown - RegionsActionSupport.testConsumer(numberOfPeople, 2415744693759237392L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> regionsDataManager.getRegionPopulationTime(TestRegionId.getUnknownRegionId())); - assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); - - }); - - } - - @Test - @UnitTestMethod(name = "getRegionPropertyDefinition", args = { RegionPropertyId.class }) - public void testGetRegionPropertyDefinition() { - RegionsActionSupport.testConsumer(0, 8915683065425449883L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - - Set regionPropertyIds = regionsDataManager.getRegionPropertyIds(); - assertEquals(TestRegionPropertyId.size(), regionPropertyIds.size()); - for (TestRegionPropertyId testRegionPropertyId : regionPropertyIds) { - PropertyDefinition expectedPropertyDefinition = testRegionPropertyId.getPropertyDefinition(); - PropertyDefinition actualPropertyDefinition = regionsDataManager.getRegionPropertyDefinition(testRegionPropertyId); - assertEquals(expectedPropertyDefinition, actualPropertyDefinition); - } - - }); - - // precondition check: if the region property id is null - RegionsActionSupport.testConsumer(0, 4217775232224320101L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> regionsDataManager.getRegionPropertyDefinition(null)); - assertEquals(RegionError.NULL_REGION_PROPERTY_ID, contractException.getErrorType()); - }); - - // precondition check: if the region property id is unknown - RegionsActionSupport.testConsumer(0, 1425794836864585647L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> regionsDataManager.getRegionPropertyDefinition(TestRegionPropertyId.getUnknownRegionPropertyId())); - assertEquals(RegionError.UNKNOWN_REGION_PROPERTY_ID, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "getRegionPropertyIds", args = {}) - public void testGetRegionPropertyIds() { - RegionsActionSupport.testConsumer(0, 2658585233315606268L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - Set expectedRegionPropertyIds = new LinkedHashSet<>(); - for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { - expectedRegionPropertyIds.add(testRegionPropertyId); - } - assertEquals(expectedRegionPropertyIds, regionsDataManager.getRegionPropertyIds()); - }); - // no precondition tests - - } - - @Test - @UnitTestMethod(name = "getRegionPropertyValue", args = { RegionId.class, RegionPropertyId.class }) - public void testGetRegionPropertyValue() { - - Map expectedPropertyValues = new LinkedHashMap<>(); - for (TestRegionId testRegionId : TestRegionId.values()) { - for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { - Object value = testRegionPropertyId.getPropertyDefinition().getDefaultValue().get(); - expectedPropertyValues.put(new MultiKey(testRegionId, testRegionPropertyId), value); - } - } - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // show that changes to the property values properly reflect the - // previous values - - for (int i = 0; i < 300; i++) { - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(i, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - TestRegionId testRegionId = TestRegionId.getRandomRegionId(randomGenerator); - TestRegionPropertyId testRegionPropertyId = TestRegionPropertyId.getRandomMutableRegionPropertyId(randomGenerator); - - // show that the property has the correct value - MultiKey multiKey = new MultiKey(testRegionId, testRegionPropertyId); - Object expectedPropertyValue = expectedPropertyValues.get(multiKey); - - Object actualPropertyValue = regionsDataManager.getRegionPropertyValue(testRegionId, testRegionPropertyId); - assertEquals(expectedPropertyValue, actualPropertyValue); - - Object newPropertyValue = testRegionPropertyId.getRandomPropertyValue(randomGenerator); - regionsDataManager.setRegionPropertyValue(testRegionId, testRegionPropertyId, newPropertyValue); - expectedPropertyValues.put(multiKey, newPropertyValue); - - })); - } - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - RegionsActionSupport.testConsumers(0, 8784099691519492811L, TimeTrackingPolicy.TRACK_TIME, testPlugin); - - // precondition check: if the region id is null - RegionsActionSupport.testConsumer(0, 8784099691519492811L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - RegionPropertyId knownRegionPropertyId = TestRegionPropertyId.REGION_PROPERTY_1_BOOLEAN_MUTABLE; - ContractException contractException = assertThrows(ContractException.class, () -> regionsDataManager.getRegionPropertyValue(null, knownRegionPropertyId)); - assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); - }); - - // precondition check: if the region id is not known - RegionsActionSupport.testConsumer(0, 1546629608367614750L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - RegionId unknownRegionId = TestRegionId.getUnknownRegionId(); - RegionPropertyId knownRegionPropertyId = TestRegionPropertyId.REGION_PROPERTY_1_BOOLEAN_MUTABLE; - ContractException contractException = assertThrows(ContractException.class, () -> regionsDataManager.getRegionPropertyValue(unknownRegionId, knownRegionPropertyId)); - assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); - }); - - // precondition check: if the region property id is null - RegionsActionSupport.testConsumer(0, 2997323471294141386L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - RegionId knownRegionId = TestRegionId.REGION_1; - ContractException contractException = assertThrows(ContractException.class, () -> regionsDataManager.getRegionPropertyValue(knownRegionId, null)); - assertEquals(RegionError.NULL_REGION_PROPERTY_ID, contractException.getErrorType()); - }); - - // precondition check: if the region property id is unknown - RegionsActionSupport.testConsumer(0, 3797498566412748237L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - RegionId knownRegionId = TestRegionId.REGION_1; - RegionPropertyId unknownRegionPropertyId = TestRegionPropertyId.getUnknownRegionPropertyId(); - ContractException contractException = assertThrows(ContractException.class, () -> regionsDataManager.getRegionPropertyValue(knownRegionId, unknownRegionPropertyId)); - assertEquals(RegionError.UNKNOWN_REGION_PROPERTY_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "getRegionPropertyTime", args = { RegionId.class, RegionPropertyId.class }) - public void testGetRegionPropertyTime() { - Map expectedPropertyTimes = new LinkedHashMap<>(); - for (TestRegionId testRegionId : TestRegionId.values()) { - for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { - expectedPropertyTimes.put(new MultiKey(testRegionId, testRegionPropertyId), new MutableDouble()); - } - } - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // show that the property times are currently zero - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - assertTrue(regionsDataManager.getRegionIds().size() > 0); - assertTrue(regionsDataManager.getRegionPropertyIds().size() > 0); - for (RegionId regionId : regionsDataManager.getRegionIds()) { - for (RegionPropertyId regionPropertyId : regionsDataManager.getRegionPropertyIds()) { - double regionPropertyTime = regionsDataManager.getRegionPropertyTime(regionId, regionPropertyId); - assertEquals(0, regionPropertyTime, 0); - } - } - })); - - // show that changes to the property values properly reflect the time - // the occured - - for (int i = 0; i < 300; i++) { - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(i, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - TestRegionId testRegionId = TestRegionId.getRandomRegionId(randomGenerator); - TestRegionPropertyId testRegionPropertyId = TestRegionPropertyId.getRandomMutableRegionPropertyId(randomGenerator); - - // show that the property has the correct time - MutableDouble mutableDouble = expectedPropertyTimes.get(new MultiKey(testRegionId, testRegionPropertyId)); - double expectedPropertyTime = mutableDouble.getValue(); - double actualPropertyTime = regionsDataManager.getRegionPropertyTime(testRegionId, testRegionPropertyId); - assertEquals(expectedPropertyTime, actualPropertyTime); - - Object newPropertyValue = testRegionPropertyId.getRandomPropertyValue(randomGenerator); - - regionsDataManager.setRegionPropertyValue(testRegionId, testRegionPropertyId, newPropertyValue); - assertEquals(c.getTime(), regionsDataManager.getRegionPropertyTime(testRegionId, testRegionPropertyId)); - mutableDouble.setValue(c.getTime()); - })); - } - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - RegionsActionSupport.testConsumers(0, 1085097084913380645L, TimeTrackingPolicy.TRACK_TIME, testPlugin); - - // precondition check: if the region id is null - RegionsActionSupport.testConsumer(0, 9165213921588406384L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - RegionPropertyId knownRegionPropertyId = TestRegionPropertyId.REGION_PROPERTY_1_BOOLEAN_MUTABLE; - ContractException contractException = assertThrows(ContractException.class, () -> regionsDataManager.getRegionPropertyTime(null, knownRegionPropertyId)); - assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); - }); - - // precondition check: if the region id is not known - RegionsActionSupport.testConsumer(0, 1546629608367614750L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - RegionId unknownRegionId = TestRegionId.getUnknownRegionId(); - RegionPropertyId knownRegionPropertyId = TestRegionPropertyId.REGION_PROPERTY_1_BOOLEAN_MUTABLE; - ContractException contractException = assertThrows(ContractException.class, () -> regionsDataManager.getRegionPropertyTime(unknownRegionId, knownRegionPropertyId)); - assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); - }); - - // precondition check: if the region property id is null - RegionsActionSupport.testConsumer(0, 7141175136643291537L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - RegionId knownRegionId = TestRegionId.REGION_1; - ContractException contractException = assertThrows(ContractException.class, () -> regionsDataManager.getRegionPropertyTime(knownRegionId, null)); - assertEquals(RegionError.NULL_REGION_PROPERTY_ID, contractException.getErrorType()); - }); - - // precondition check: if the region property id is unknown - RegionsActionSupport.testConsumer(0, 2200230008116664966L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - RegionId knownRegionId = TestRegionId.REGION_1; - RegionPropertyId unknownRegionPropertyId = TestRegionPropertyId.getUnknownRegionPropertyId(); - ContractException contractException = assertThrows(ContractException.class, () -> regionsDataManager.getRegionPropertyTime(knownRegionId, unknownRegionPropertyId)); - assertEquals(RegionError.UNKNOWN_REGION_PROPERTY_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "regionIdExists", args = { RegionId.class }) - public void testRegionIdExists() { - RegionsActionSupport.testConsumer(0, 3797498566412748237L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - - // show that null region ids do not exist - assertFalse(regionsDataManager.regionIdExists(null)); - - // show that the region ids added do exist - for (TestRegionId testRegionId : TestRegionId.values()) { - assertTrue(regionsDataManager.regionIdExists(testRegionId)); - } - - // show that an unknown region id does not exist - assertFalse(regionsDataManager.regionIdExists(TestRegionId.getUnknownRegionId())); - - }); - - } - - @Test - @UnitTestMethod(name = "regionPropertyIdExists", args = { RegionPropertyId.class }) - public void testRegionPropertyIdExists() { - RegionsActionSupport.testConsumer(0, 3797498566412748237L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - - // show that the property ids exist - for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { - assertTrue(regionsDataManager.regionPropertyIdExists(testRegionPropertyId)); - } - - // show that null references return false - assertFalse(regionsDataManager.regionPropertyIdExists(null)); - - // show that unknown region property ids return false - assertFalse(regionsDataManager.regionPropertyIdExists(TestRegionPropertyId.getUnknownRegionPropertyId())); - - }); - - } - - @Test - @UnitTestMethod(name = "setPersonRegion", args = { PersonId.class, RegionId.class }) - public void testSetPersonRegion() { - int numberOfPeople = 30; - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // create some containers for movement observations - List recievedObservations = new ArrayList<>(); - List expectedObservations = new ArrayList<>(); - - /* - * Have the observer agent observe all movements and record those - * observations - */ - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { - - for (TestRegionId testRegionId : TestRegionId.values()) { - EventLabel eventLabel = PersonRegionUpdateEvent.getEventLabelByArrivalRegion(c, testRegionId); - c.subscribe(eventLabel, (c2, e) -> { - recievedObservations.add(new MultiKey(e.getPreviousRegionId(), e.getCurrentRegionId(), e.getPersonId(), c2.getTime())); - }); - } - })); - - /* - * Have the mover agent move every person over time and show that each - * person is where we expect them to be - */ - pluginBuilder.addTestActorPlan("mover", new TestActorPlan(1, (c) -> { - - /* - * Make sure that there are actually people in the simulation so - * that test is actually testing something - */ - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - - List people = peopleDataManager.getPeople(); - assertTrue(people.size() > 0); - - // schedule a time for each person to be moved to a new region - double planTime = 10; - for (final PersonId personId : people) { - c.addPlan((c2) -> { - TestRegionId regionId = regionsDataManager.getPersonRegion(personId); - TestRegionId nextRegionId = regionId.next(); - regionsDataManager.setPersonRegion(personId, nextRegionId); - - // show that the person's region is updated - assertEquals(nextRegionId, regionsDataManager.getPersonRegion(personId)); - expectedObservations.add(new MultiKey(regionId, nextRegionId, personId, c2.getTime())); - - // show that the person's region arrival time is - // updated - assertEquals(c2.getTime(), regionsDataManager.getPersonRegionArrivalTime(personId)); - - }, planTime); - planTime += 5; - } - })); - - // build the plugin - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - RegionsActionSupport.testConsumers(numberOfPeople, 5655227215512656797L, TimeTrackingPolicy.TRACK_TIME, testPlugin); - - // show that the observations were correct - assertEquals(expectedObservations.size(), recievedObservations.size()); - assertEquals(new LinkedHashSet<>(expectedObservations), new LinkedHashSet<>(recievedObservations)); - - /* - * precondition test: if the person id is null - */ - RegionsActionSupport.testConsumer(numberOfPeople, 9048586333860290178L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - // Select a person at random from the simulation and create a person - // id outside of the simulation - - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - List people = peopleDataManager.getPeople(); - PersonId personId = people.get(randomGenerator.nextInt(people.size())); - - // establish the person's current region and next region - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - TestRegionId currentRegionId = regionsDataManager.getPersonRegion(personId); - TestRegionId nextRegionId = currentRegionId.next(); - - ContractException contractException = assertThrows(ContractException.class, () -> regionsDataManager.setPersonRegion(null, nextRegionId)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - - }); - - /* - * precondition test: if the person id is unknown - */ - RegionsActionSupport.testConsumer(numberOfPeople, 6693022571477538917L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - // Select a person at random from the simulation and create a person - // id outside of the simulation - - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - List people = peopleDataManager.getPeople(); - PersonId personId = people.get(randomGenerator.nextInt(people.size())); - PersonId badPersonId = new PersonId(people.size()); - - // establish the person's current region and next region - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - TestRegionId currentRegionId = regionsDataManager.getPersonRegion(personId); - TestRegionId nextRegionId = currentRegionId.next(); - - ContractException contractException = assertThrows(ContractException.class, () -> regionsDataManager.setPersonRegion(badPersonId, nextRegionId)); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - }); - - /* - * precondition test: if the region id is null - */ - RegionsActionSupport.testConsumer(numberOfPeople, 1385204599279421266L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - // Select a person at random from the simulation and create a person - // id outside of the simulation - - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - List people = peopleDataManager.getPeople(); - PersonId personId = people.get(randomGenerator.nextInt(people.size())); - - // establish the person's current region and next region - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - - // - ContractException contractException = assertThrows(ContractException.class, () -> regionsDataManager.setPersonRegion(personId, null)); - assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); - - }); - - /* - * precondition test: if the region id is unknown - */ - RegionsActionSupport.testConsumer(numberOfPeople, 6025662871362676118L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - // Select a person at random from the simulation and create a person - // id outside of the simulation - - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - List people = peopleDataManager.getPeople(); - PersonId personId = people.get(randomGenerator.nextInt(people.size())); - - // establish the person's current region and next region - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - - // create a non-existent region id - RegionId unknownRegionId = TestRegionId.getUnknownRegionId(); - - ContractException contractException = assertThrows(ContractException.class, () -> regionsDataManager.setPersonRegion(personId, unknownRegionId)); - assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); - - }); - - } - - @Test - @UnitTestMethod(name = "setRegionPropertyValue", args = { RegionId.class, RegionPropertyId.class, Object.class }) - public void testRegionPropertyValueAssignmentEvent() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // create containers to hold actual and expected observations - List actualObservations = new ArrayList<>(); - List expectedObservations = new ArrayList<>(); - - // Have the observer agent start observations record them - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { - - EventLabel eventLabel = RegionPropertyUpdateEvent.getEventLabelByRegionAndProperty(c, TestRegionId.REGION_1, - TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE); - c.subscribe(eventLabel, (c2, e) -> { - actualObservations.add(new MultiKey(c2.getTime(), e.getRegionId(), e.getRegionPropertyId(), e.getCurrentPropertyValue())); - }); - - eventLabel = RegionPropertyUpdateEvent.getEventLabelByRegionAndProperty(c, TestRegionId.REGION_2, TestRegionPropertyId.REGION_PROPERTY_3_DOUBLE_MUTABLE); - c.subscribe(eventLabel, (c2, e) -> { - actualObservations.add(new MultiKey(c2.getTime(), e.getRegionId(), e.getRegionPropertyId(), e.getCurrentPropertyValue())); - }); - - })); - - // Have the update agent make various region property updates over - // time - pluginBuilder.addTestActorPlan("update", new TestActorPlan(0, (c) -> { - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - for (int i = 0; i < 10; i++) { - c.addPlan((c2) -> { - StochasticsDataManager stochasticsDataManager2 = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator2 = stochasticsDataManager2.getRandomGenerator(); - RegionsDataManager regionsDataManager = c2.getDataManager(RegionsDataManager.class); - Integer newValue = randomGenerator2.nextInt(); - regionsDataManager.setRegionPropertyValue(TestRegionId.REGION_1, TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE, newValue); - Integer actualValue = regionsDataManager.getRegionPropertyValue(TestRegionId.REGION_1, TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE); - assertEquals(newValue, actualValue); - double valueTime = regionsDataManager.getRegionPropertyTime(TestRegionId.REGION_1, TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE); - assertEquals(c2.getTime(), valueTime); - expectedObservations.add(new MultiKey(c2.getTime(), TestRegionId.REGION_1, TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE, newValue)); - }, randomGenerator.nextDouble() * 1000); - } - - for (int i = 0; i < 10; i++) { - c.addPlan((c2) -> { - StochasticsDataManager stochasticsDataManager2 = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator2 = stochasticsDataManager2.getRandomGenerator(); - RegionsDataManager regionsDataManager = c2.getDataManager(RegionsDataManager.class); - Double newValue = randomGenerator2.nextDouble(); - regionsDataManager.setRegionPropertyValue(TestRegionId.REGION_2, TestRegionPropertyId.REGION_PROPERTY_3_DOUBLE_MUTABLE, newValue); - Double actualValue = regionsDataManager.getRegionPropertyValue(TestRegionId.REGION_2, TestRegionPropertyId.REGION_PROPERTY_3_DOUBLE_MUTABLE); - assertEquals(newValue, actualValue); - double valueTime = regionsDataManager.getRegionPropertyTime(TestRegionId.REGION_2, TestRegionPropertyId.REGION_PROPERTY_3_DOUBLE_MUTABLE); - assertEquals(c2.getTime(), valueTime); - expectedObservations.add(new MultiKey(c2.getTime(), TestRegionId.REGION_2, TestRegionPropertyId.REGION_PROPERTY_3_DOUBLE_MUTABLE, newValue)); - }, randomGenerator.nextDouble() * 1000); - } - - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - RegionsActionSupport.testConsumers(0, 4630021532130673951L, TimeTrackingPolicy.TRACK_TIME, testPlugin); - - // show that the observed changes match expectations - assertEquals(expectedObservations.size(), actualObservations.size()); - - assertEquals(new LinkedHashSet<>(expectedObservations), new LinkedHashSet<>(actualObservations)); - - // precondition check: if the region id is null - RegionsActionSupport.testConsumer(0, 7347707922069273812L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - RegionPropertyId regionPropertyId = TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE; - Object propertyValue = 67; - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - - ContractException contractException = assertThrows(ContractException.class, () -> regionsDataManager.setRegionPropertyValue(null, regionPropertyId, propertyValue)); - assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); - - }); - - // precondition check: if the region id is unknown - RegionsActionSupport.testConsumer(0, 3075330757105736185L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - - RegionId unknownRegionId = TestRegionId.getUnknownRegionId(); - RegionPropertyId regionPropertyId = TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE; - Object propertyValue = 67; - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - - ContractException contractException = assertThrows(ContractException.class, () -> regionsDataManager.setRegionPropertyValue(unknownRegionId, regionPropertyId, propertyValue)); - assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); - - }); - - // precondition check: if the region property id is null - RegionsActionSupport.testConsumer(0, 4169934733913962790L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - RegionId regionId = TestRegionId.REGION_1; - Object propertyValue = 67; - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - - ContractException contractException = assertThrows(ContractException.class, () -> regionsDataManager.setRegionPropertyValue(regionId, null, propertyValue)); - assertEquals(RegionError.NULL_REGION_PROPERTY_ID, contractException.getErrorType()); - - }); - - // precondition check: if the region property id is unknown - RegionsActionSupport.testConsumer(0, 5578070775436119166L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - RegionId regionId = TestRegionId.REGION_1; - RegionPropertyId unknownRegionPropertyId = TestRegionPropertyId.getUnknownRegionPropertyId(); - Object propertyValue = 67; - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - - ContractException contractException = assertThrows(ContractException.class, () -> regionsDataManager.setRegionPropertyValue(regionId, unknownRegionPropertyId, propertyValue)); - assertEquals(RegionError.UNKNOWN_REGION_PROPERTY_ID, contractException.getErrorType()); - - }); - - // precondition check: if the region property value is null - RegionsActionSupport.testConsumer(0, 217279748753596418L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - RegionId regionId = TestRegionId.REGION_1; - RegionPropertyId regionPropertyId = TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE; - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - - ContractException contractException = assertThrows(ContractException.class, () -> regionsDataManager.setRegionPropertyValue(regionId, regionPropertyId, null)); - assertEquals(RegionError.NULL_REGION_PROPERTY_VALUE, contractException.getErrorType()); - - }); - - // precondition check: if the region property value is incompatible with - // the defined type for the property - RegionsActionSupport.testConsumer(0, 7043526072670323223L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - RegionId regionId = TestRegionId.REGION_1; - RegionPropertyId regionPropertyId = TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE; - Object incompatiblePropertyValue = "incompatible value"; - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - - ContractException contractException = assertThrows(ContractException.class, () -> regionsDataManager.setRegionPropertyValue(regionId, regionPropertyId, incompatiblePropertyValue)); - assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); - - }); - - // precondition check: if the region id is unknown - RegionsActionSupport.testConsumer(0, 8501593854721316109L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - RegionId unknownRegionId = TestRegionId.getUnknownRegionId(); - RegionPropertyId regionPropertyId = TestRegionPropertyId.REGION_PROPERTY_2_INTEGER_MUTABLE; - Object propertyValue = 67; - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - - ContractException contractException = assertThrows(ContractException.class, () -> regionsDataManager.setRegionPropertyValue(unknownRegionId, regionPropertyId, propertyValue)); - assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); - - }); - - // precondition check: if the region property value is null - RegionsActionSupport.testConsumer(0, 8501593854721316109L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - RegionId regionId = TestRegionId.REGION_1; - RegionPropertyId immutableRegionPropertyId = TestRegionPropertyId.REGION_PROPERTY_5_INTEGER_IMMUTABLE; - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - - ContractException contractException = assertThrows(ContractException.class, () -> regionsDataManager.setRegionPropertyValue(regionId, immutableRegionPropertyId, null)); - assertEquals(RegionError.NULL_REGION_PROPERTY_VALUE, contractException.getErrorType()); - - }); - } - - - - @Test - @UnitTestMethod(name = "init", args = {DataManagerContext.class}) - public void testRegionPluginData() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4454658950052475227L); - int initialPopulation = 30; - List people = new ArrayList<>(); - for(int i = 0;i { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - // show that the initial state of the region data manager matches - // the state of the region plugin data - - assertEquals(regionsPluginData.getPersonRegionArrivalTrackingPolicy(), regionsDataManager.getPersonRegionArrivalTrackingPolicy()); - assertEquals(regionsPluginData.getRegionIds(), regionsDataManager.getRegionIds()); - assertEquals(regionsPluginData.getRegionPropertyIds(), regionsDataManager.getRegionPropertyIds()); - for (RegionPropertyId regionPropertyId : regionsPluginData.getRegionPropertyIds()) { - PropertyDefinition expectedPropertyDefinition = regionsPluginData.getRegionPropertyDefinition(regionPropertyId); - PropertyDefinition actualPropertyDefinition = regionsDataManager.getRegionPropertyDefinition(regionPropertyId); - assertEquals(expectedPropertyDefinition, actualPropertyDefinition); - } - for (RegionId regionId : regionsPluginData.getRegionIds()) { - for (RegionPropertyId regionPropertyId : regionsPluginData.getRegionPropertyIds()) { - Object expectedValue = regionsPluginData.getRegionPropertyValue(regionId, regionPropertyId); - Object actualValue = regionsDataManager.getRegionPropertyValue(regionId, regionPropertyId); - assertEquals(expectedValue, actualValue); - } - } - - })); - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - builder.addPlugin(testPlugin); - - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - - // build and execute the engine - builder.setOutputConsumer(scenarioPlanCompletionObserver::handleOutput); - builder.build().execute(); - - // show that all actions were executed - if (!scenarioPlanCompletionObserver.allPlansExecuted()) { - throw new ContractException(TestError.TEST_EXECUTION_FAILURE); - } - - } - - /** - * Shows that all event {@linkplain PersonRegionUpdateEvent} - * labelers are created - */ - @Test - @UnitTestMethod(name = "init", args = {DataManagerContext.class}) - public void testPersonRegionUpdateEventLabelers() { - RegionsActionSupport.testConsumer(0, 2734071676096451334L, TimeTrackingPolicy.DO_NOT_TRACK_TIME, (c) -> { - EventLabeler eventLabelerForArrivalRegion = PersonRegionUpdateEvent.getEventLabelerForArrivalRegion(); - assertNotNull(eventLabelerForArrivalRegion); - ContractException contractException = assertThrows(ContractException.class, () -> c.addEventLabeler(eventLabelerForArrivalRegion)); - assertEquals(NucleusError.DUPLICATE_LABELER_ID_IN_EVENT_LABELER, contractException.getErrorType()); - - EventLabeler eventLabelerForDepartureRegion = PersonRegionUpdateEvent.getEventLabelerForDepartureRegion(); - assertNotNull(eventLabelerForDepartureRegion); - contractException = assertThrows(ContractException.class, () -> c.addEventLabeler(eventLabelerForDepartureRegion)); - assertEquals(NucleusError.DUPLICATE_LABELER_ID_IN_EVENT_LABELER, contractException.getErrorType()); - - EventLabeler eventLabelerForPerson = PersonRegionUpdateEvent.getEventLabelerForPerson(); - assertNotNull(eventLabelerForPerson); - contractException = assertThrows(ContractException.class, () -> c.addEventLabeler(eventLabelerForPerson)); - assertEquals(NucleusError.DUPLICATE_LABELER_ID_IN_EVENT_LABELER, contractException.getErrorType()); - - }); - } - - /** - * Shows that all event {@linkplain RegionPropertyUpdateEvent} - * labelers are created - */ - @Test - @UnitTestMethod(name = "init", args = {DataManagerContext.class}) - public void testRegionPropertyUpdateEventLabelers() { - - RegionsActionSupport.testConsumer(0, 4228466028646070532L, TimeTrackingPolicy.DO_NOT_TRACK_TIME, (c) -> { - EventLabeler eventLabeler1 = RegionPropertyUpdateEvent.getEventLabelerForProperty(); - assertNotNull(eventLabeler1); - ContractException contractException = assertThrows(ContractException.class, () -> c.addEventLabeler(eventLabeler1)); - assertEquals(NucleusError.DUPLICATE_LABELER_ID_IN_EVENT_LABELER, contractException.getErrorType()); - - EventLabeler eventLabeler2 = RegionPropertyUpdateEvent.getEventLabelerForRegionAndProperty(); - assertNotNull(eventLabeler2); - contractException = assertThrows(ContractException.class, () -> c.addEventLabeler(eventLabeler2)); - assertEquals(NucleusError.DUPLICATE_LABELER_ID_IN_EVENT_LABELER, contractException.getErrorType()); - - }); - - } - - @Test - @UnitTestMethod(name = "init", args = {DataManagerContext.class}) - public void testPluginDataLoaded() { - - long seed = 4228466028646070532L; - - int initialPopulation = 100; - - List people = new ArrayList<>(); - for (int i = 0; i < initialPopulation; i++) { - people.add(new PersonId(i)); - } - Builder builder = Simulation.builder(); - - // add the region plugin - RegionsPluginData.Builder regionPluginBuilder = RegionsPluginData.builder(); - for (TestRegionId regionId : TestRegionId.values()) { - regionPluginBuilder.addRegion(regionId); - } - - for(TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { - regionPluginBuilder.defineRegionProperty(testRegionPropertyId, testRegionPropertyId.getPropertyDefinition()); - } - TestRegionId testRegionId = TestRegionId.REGION_1; - regionPluginBuilder.setPersonRegionArrivalTracking(TimeTrackingPolicy.TRACK_TIME); - for(PersonId personId : people) { - regionPluginBuilder.setPersonRegion(personId, testRegionId); - testRegionId = testRegionId.next(); - } - RegionsPluginData regionsPluginData = regionPluginBuilder.build(); - builder.addPlugin(RegionsPlugin.getRegionsPlugin(regionsPluginData)); - - // add the people plugin - PeoplePluginData.Builder peopleBuilder = PeoplePluginData.builder(); - for(PersonId personId : people) { - peopleBuilder.addPersonId(personId); - } - PeoplePluginData peoplePluginData = peopleBuilder.build(); - builder.addPlugin(PeoplePlugin.getPeoplePlugin(peoplePluginData)); - - // add the report plugin -// builder.addPlugin(ReportsPlugin.getReportPlugin(ReportsPluginData.builder().build())); - - // add the stochastics plugin - builder.addPlugin(StochasticsPlugin.getStochasticsPlugin(StochasticsPluginData.builder().setSeed(seed).build())); - - // add the partitions plugin - //builder.addPlugin(PartitionsPlugin.getPartitionsPlugin()); - - // add the test plugin - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0,(c)->{ - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - for(PersonId personId : people) { - RegionId actualRegionId = regionsDataManager.getPersonRegion(personId); - RegionId expectedRegionId = regionsPluginData.getPersonRegion(personId); - assertEquals(actualRegionId, expectedRegionId); - } - - })); - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - builder.addPlugin(testPlugin); - - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - - // build and execute the engine - builder.setOutputConsumer(scenarioPlanCompletionObserver::handleOutput); - builder.build().execute(); - - // show that all actions were executed - if (!scenarioPlanCompletionObserver.allPlansExecuted()) { - throw new ContractException(TestError.TEST_EXECUTION_FAILURE); - } - - - } - - @Test - @UnitTestMethod(name = "init", args = {DataManagerContext.class}) - public void testPersonAdditionEvent() { - /* - * Have the agent create some people over time and show that each person - * is in the correct region at the correct time - */ - RegionsActionSupport.testConsumer(0, 8294774271110836859L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - for (int i = 0; i < 100; i++) { - c.addPlan((c2) -> { - StochasticsDataManager stochasticsDataManager2 = c2.getDataManager(StochasticsDataManager.class); - RegionsDataManager regionsDataManager = c2.getDataManager(RegionsDataManager.class); - PeopleDataManager peopleDataManager = c2.getDataManager(PeopleDataManager.class); - - /* - * Generate a random region to for the new person and add - * the person - */ - TestRegionId randomRegionId = TestRegionId.getRandomRegionId(stochasticsDataManager2.getRandomGenerator()); - PersonConstructionData personConstructionData = PersonConstructionData.builder().add(randomRegionId).build(); - PersonId personId = peopleDataManager.addPerson(personConstructionData); - - /* - * Show that the person is in the correct region with the - * correct region arrival time - */ - RegionId personRegionId = regionsDataManager.getPersonRegion(personId); - assertEquals(randomRegionId, personRegionId); - assertEquals(c2.getTime(), regionsDataManager.getPersonRegionArrivalTime(personId)); - - }, randomGenerator.nextDouble() * 1000); - } - - }); - - // precondition check: if no region data was included in the event - RegionsActionSupport.testConsumer(0, 8294774271110836859L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - PersonConstructionData personConstructionData = PersonConstructionData.builder().build(); - ContractException contractException = assertThrows(ContractException.class, () -> peopleDataManager.addPerson(personConstructionData)); - assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); - }); - - // precondition check: if the region in the event is unknown - RegionsActionSupport.testConsumer(0, 2879410509293373914L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - PersonConstructionData personConstructionData = PersonConstructionData.builder().add(TestRegionId.getUnknownRegionId()).build(); - ContractException contractException = assertThrows(ContractException.class, () -> peopleDataManager.addPerson(personConstructionData)); - assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); - }); - - // precondition check: if the person id does not exist - RegionsActionSupport.testConsumer(0, 5311711819224332248L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - /* - * Note : it is not possible to force the PersonDataManager to - * release such an event, so we release it from an actor - */ - PersonConstructionData personConstructionData = PersonConstructionData.builder().add(TestRegionId.REGION_1).build(); - PersonAdditionEvent personAdditionEvent = new PersonAdditionEvent(new PersonId(10000), personConstructionData); - ContractException contractException = assertThrows(ContractException.class, () -> c.releaseEvent(personAdditionEvent)); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - }); - - // precondition check: if the person was previously added - RegionsActionSupport.testConsumer(0, 5824136557013438265L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - /* - * Note : it is not possible to force the PersonDataManager to - * release such an event, so we release it from an actor - */ - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - PersonConstructionData personConstructionData = PersonConstructionData.builder().add(TestRegionId.REGION_1).build(); - PersonId personId = peopleDataManager.addPerson(personConstructionData); - - PersonAdditionEvent personAdditionEvent = new PersonAdditionEvent(personId, personConstructionData); - - ContractException contractException = assertThrows(ContractException.class, () -> c.releaseEvent(personAdditionEvent)); - assertEquals(RegionError.DUPLICATE_PERSON_ADDITION, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "init", args = {DataManagerContext.class}) - public void testBulkPersonAdditionEvent() { - - /* - * Have the agent create some people over time and show that each person - * is in the correct region at the correct time - */ - RegionsActionSupport.testConsumer(0, 2654453328570666100L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - for (int i = 0; i < 100; i++) { - c.addPlan((c2) -> { - StochasticsDataManager stochasticsDataManager2 = c2.getDataManager(StochasticsDataManager.class); - RegionsDataManager regionsDataManager = c2.getDataManager(RegionsDataManager.class); - PeopleDataManager peopleDataManager = c2.getDataManager(PeopleDataManager.class); - - RandomGenerator rng = stochasticsDataManager2.getRandomGenerator(); - /* - * Generate a random region to for each new person and add - * the person - */ - Map expectedRegions = new LinkedHashMap<>(); - BulkPersonConstructionData.Builder bulkBuilder = BulkPersonConstructionData.builder(); - PersonConstructionData.Builder personBuilder = PersonConstructionData.builder(); - int personCount = rng.nextInt(5) + 1; - - for (int j = 0; j < personCount; j++) { - RegionId randomRegionId = TestRegionId.getRandomRegionId(rng); - personBuilder.add(randomRegionId); - expectedRegions.put(j, randomRegionId); - bulkBuilder.add(personBuilder.build()); - } - BulkPersonConstructionData bulkPersonConstructionData = bulkBuilder.build(); - PersonId personId = peopleDataManager.addBulkPeople(bulkPersonConstructionData).get(); - - int basePersonIndex = personId.getValue(); - - /* - * Show that each person is in the correct region with the - * correct region arrival time - */ - for (int j = 0; j < personCount; j++) { - personId = new PersonId(j + basePersonIndex); - RegionId personRegionId = regionsDataManager.getPersonRegion(personId); - RegionId randomRegionId = expectedRegions.get(j); - assertEquals(randomRegionId, personRegionId); - assertEquals(c2.getTime(), regionsDataManager.getPersonRegionArrivalTime(personId)); - } - - }, randomGenerator.nextDouble() * 1000); - } - - }); - - // precondition check: - // precondition check: if no region data was included in the event - RegionsActionSupport.testConsumer(0, 7059505726403414171L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - PersonConstructionData personConstructionData = PersonConstructionData.builder().build(); - BulkPersonConstructionData bulkPersonConstructionData = BulkPersonConstructionData.builder().add(personConstructionData).build(); - ContractException contractException = assertThrows(ContractException.class, () -> peopleDataManager.addBulkPeople(bulkPersonConstructionData)); - assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); - }); - - // precondition check: if the region in the event is unknown - RegionsActionSupport.testConsumer(0, 5120242925932651968L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - PersonConstructionData personConstructionData = PersonConstructionData.builder().add(TestRegionId.getUnknownRegionId()).build(); - BulkPersonConstructionData bulkPersonConstructionData = BulkPersonConstructionData.builder().add(personConstructionData).build(); - ContractException contractException = assertThrows(ContractException.class, () -> peopleDataManager.addBulkPeople(bulkPersonConstructionData)); - assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); - }); - - // precondition check: if the person id does not exist - RegionsActionSupport.testConsumer(0, 5968783102821781999L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - /* - * Note : it is not possible to force the PersonDataManager to - * release such an event, so we release it from an actor - */ - PersonConstructionData personConstructionData = PersonConstructionData.builder().add(TestRegionId.REGION_1).build(); - BulkPersonConstructionData bulkPersonConstructionData = BulkPersonConstructionData.builder().add(personConstructionData).build(); - BulkPersonAdditionEvent bulkPersonAdditionEvent = new BulkPersonAdditionEvent(new PersonId(45), bulkPersonConstructionData); - ContractException contractException = assertThrows(ContractException.class, () -> c.releaseEvent(bulkPersonAdditionEvent)); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - }); - - // precondition check: if the person was previously added - RegionsActionSupport.testConsumer(0, 8092390328840929050L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - /* - * Note : it is not possible to force the PersonDataManager to - * release such an event, so we release it from an actor - */ - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - PersonConstructionData personConstructionData = PersonConstructionData.builder().add(TestRegionId.REGION_1).build(); - PersonId personId = peopleDataManager.addPerson(personConstructionData); - - BulkPersonConstructionData bulkPersonConstructionData = BulkPersonConstructionData.builder().add(personConstructionData).build(); - BulkPersonAdditionEvent bulkPersonAdditionEvent = new BulkPersonAdditionEvent(personId, bulkPersonConstructionData); - - ContractException contractException = assertThrows(ContractException.class, () -> c.releaseEvent(bulkPersonAdditionEvent)); - assertEquals(RegionError.DUPLICATE_PERSON_ADDITION, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "init", args = {DataManagerContext.class}) - public void testPersonImminentRemovalEvent() { - - MutableInteger pId = new MutableInteger(); - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - /* - * Have the actor add a person and then remove it. There will be a delay - * of 0 time for the person to be removed. - */ - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - - PersonConstructionData personConstructionData = PersonConstructionData.builder().add(TestRegionId.REGION_1).build(); - PersonId personId = peopleDataManager.addPerson(personConstructionData); - pId.setValue(personId.getValue()); - - int regionPopulationCount = regionsDataManager.getRegionPopulationCount(TestRegionId.REGION_1); - assertEquals(1, regionPopulationCount); - assertEquals(TestRegionId.REGION_1, regionsDataManager.getPersonRegion(personId)); - peopleDataManager.removePerson(personId); - - })); - - /* - * Have the actor show that the person is no longer in the location data - * view - * - */ - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(3, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - PersonId personId = new PersonId(pId.getValue()); - - int regionPopulationCount = regionsDataManager.getRegionPopulationCount(TestRegionId.REGION_1); - assertEquals(0, regionPopulationCount); - ContractException contractException = assertThrows(ContractException.class, () -> regionsDataManager.getPersonRegion(personId)); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - - })); - - // build action plugin - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - RegionsActionSupport.testConsumers(0, 163202760371564041L, TimeTrackingPolicy.TRACK_TIME, testPlugin); - - // precondition test: if the person id is unknown - RegionsActionSupport.testConsumer(0, 2314376445339268382L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - PersonImminentRemovalEvent personImminentRemovalEvent = new PersonImminentRemovalEvent(new PersonId(1000)); - ContractException contractException = assertThrows(ContractException.class, () -> c.releaseEvent(personImminentRemovalEvent)); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - }); - - /* - * Precondition test: if the person was previously removed. The - * exception will be thrown after the consumer fully executes and will - * bubble up and out of the simulation instance being executed and thus - * must be captured outside of the static test methods. - */ - ContractException contractException = assertThrows(ContractException.class, () -> { - RegionsActionSupport.testConsumer(0, 2314376445339268382L, TimeTrackingPolicy.TRACK_TIME, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().add(TestRegionId.REGION_1).build()); - peopleDataManager.removePerson(personId); - PersonImminentRemovalEvent personImminentRemovalEvent = new PersonImminentRemovalEvent(personId); - c.releaseEvent(personImminentRemovalEvent); - }); - }); - assertEquals(RegionError.DUPLICATE_PERSON_REMOVAL, contractException.getErrorType()); - - } - -} diff --git a/gcm3/src/test/java/plugins/regions/events/AT_PersonRegionUpdateEvent.java b/gcm3/src/test/java/plugins/regions/events/AT_PersonRegionUpdateEvent.java deleted file mode 100644 index 09360fa44..000000000 --- a/gcm3/src/test/java/plugins/regions/events/AT_PersonRegionUpdateEvent.java +++ /dev/null @@ -1,228 +0,0 @@ -package plugins.regions.events; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import javax.naming.Context; - -import org.junit.jupiter.api.Test; - -import nucleus.EventLabel; -import nucleus.EventLabeler; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.PersonConstructionData; -import plugins.people.support.PersonId; -import plugins.regions.support.RegionId; -import plugins.regions.testsupport.RegionsActionSupport; -import plugins.regions.testsupport.TestRegionId; -import plugins.util.properties.TimeTrackingPolicy; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = PersonRegionUpdateEvent.class) -public class AT_PersonRegionUpdateEvent { - - @Test - @UnitTestConstructor(args = { PersonId.class, RegionId.class, RegionId.class }) - public void testConstructor() { - PersonId personId = new PersonId(456); - RegionId previousRegionId = TestRegionId.REGION_1; - RegionId currentRegionId = TestRegionId.REGION_2; - PersonRegionUpdateEvent event = new PersonRegionUpdateEvent(personId, previousRegionId, currentRegionId); - assertNotNull(event); - } - - @Test - @UnitTestMethod(name = "getCurrentRegionId", args = {}) - public void testGetCurrentRegionId() { - PersonId personId = new PersonId(456); - RegionId previousRegionId = TestRegionId.REGION_1; - RegionId currentRegionId = TestRegionId.REGION_2; - PersonRegionUpdateEvent event = new PersonRegionUpdateEvent(personId, previousRegionId, currentRegionId); - assertEquals(currentRegionId, event.getCurrentRegionId()); - } - - @Test - @UnitTestMethod(name = "getPreviousRegionId", args = {}) - public void testGetPreviousRegionId() { - PersonId personId = new PersonId(456); - RegionId previousRegionId = TestRegionId.REGION_1; - RegionId currentRegionId = TestRegionId.REGION_2; - PersonRegionUpdateEvent event = new PersonRegionUpdateEvent(personId, previousRegionId, currentRegionId); - assertEquals(previousRegionId, event.getPreviousRegionId()); - } - - @Test - @UnitTestMethod(name = "getPersonId", args = {}) - public void testGetPersonId() { - PersonId personId = new PersonId(456); - RegionId previousRegionId = TestRegionId.REGION_1; - RegionId currentRegionId = TestRegionId.REGION_2; - PersonRegionUpdateEvent event = new PersonRegionUpdateEvent(personId, previousRegionId, currentRegionId); - assertEquals(personId, event.getPersonId()); - } - - @Test - @UnitTestMethod(name = "toString", args = {}) - public void testToString() { - - PersonId personId = new PersonId(456); - RegionId previousRegionId = TestRegionId.REGION_1; - RegionId currentRegionId = TestRegionId.REGION_2; - PersonRegionUpdateEvent event = new PersonRegionUpdateEvent(personId, previousRegionId, currentRegionId); - - String actualValue = event.toString(); - String expectedValue = "PersonRegionUpdateEvent [personId=456, previousRegionId=REGION_1, currentRegionId=REGION_2]"; - assertEquals(expectedValue, actualValue); - } - - @Test - @UnitTestMethod(name = "getEventLabelByArrivalRegion", args = { Context.class, RegionId.class }) - public void testGetEventLabelByArrivalRegion() { - RegionsActionSupport.testConsumer(0, 1834330681874393158L, TimeTrackingPolicy.DO_NOT_TRACK_TIME, (c) -> { - for (TestRegionId testRegionId : TestRegionId.values()) { - EventLabel eventLabel = PersonRegionUpdateEvent.getEventLabelByArrivalRegion(c, testRegionId); - assertEquals(PersonRegionUpdateEvent.class, eventLabel.getEventClass()); - assertEquals(PersonRegionUpdateEvent.class, eventLabel.getPrimaryKeyValue()); - assertEquals(PersonRegionUpdateEvent.getEventLabelerForArrivalRegion().getEventLabelerId(), eventLabel.getLabelerId()); - } - }); - } - - @Test - @UnitTestMethod(name = "getEventLabelByDepartureRegion", args = { Context.class, RegionId.class }) - public void testGetEventLabelByDepartureRegion() { - - RegionsActionSupport.testConsumer(0, 1200361918333577992L, TimeTrackingPolicy.DO_NOT_TRACK_TIME, (c) -> { - for (TestRegionId testRegionId : TestRegionId.values()) { - EventLabel eventLabel = PersonRegionUpdateEvent.getEventLabelByDepartureRegion(c, testRegionId); - assertEquals(PersonRegionUpdateEvent.class, eventLabel.getEventClass()); - assertEquals(PersonRegionUpdateEvent.class, eventLabel.getPrimaryKeyValue()); - assertEquals(PersonRegionUpdateEvent.getEventLabelerForDepartureRegion().getEventLabelerId(), eventLabel.getLabelerId()); - } - }); - } - - @Test - @UnitTestMethod(name = "getEventLabelByPerson", args = { Context.class, PersonId.class }) - public void getEventLabelByPerson() { - - RegionsActionSupport.testConsumer(0, 3991680549375011891L, TimeTrackingPolicy.DO_NOT_TRACK_TIME, (c) -> { - for (TestRegionId testRegionId : TestRegionId.values()) { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - PersonConstructionData personConstructionData = PersonConstructionData.builder().add(testRegionId).build(); - PersonId personId = peopleDataManager.addPerson(personConstructionData); - - EventLabel eventLabel = PersonRegionUpdateEvent.getEventLabelByPerson(c, personId); - assertEquals(PersonRegionUpdateEvent.class, eventLabel.getEventClass()); - assertEquals(PersonRegionUpdateEvent.class, eventLabel.getPrimaryKeyValue()); - assertEquals(PersonRegionUpdateEvent.getEventLabelerForPerson().getEventLabelerId(), eventLabel.getLabelerId()); - } - }); - } - - @Test - @UnitTestMethod(name = "getEventLabelerForArrivalRegion", args = {}) - public void testGetEventLabelerForArrivalRegion() { - RegionsActionSupport.testConsumer(0, 6696076014058054790L, TimeTrackingPolicy.DO_NOT_TRACK_TIME, (c) -> { - // show that the event labeler can be constructed has the correct - // values - EventLabeler eventLabeler = PersonRegionUpdateEvent.getEventLabelerForArrivalRegion(); - assertEquals(PersonRegionUpdateEvent.class, eventLabeler.getEventClass()); - - for (TestRegionId testRegionId : TestRegionId.values()) { - assertEquals(PersonRegionUpdateEvent.getEventLabelByArrivalRegion(c, testRegionId).getLabelerId(), eventLabeler.getEventLabelerId()); - - // show that the event labeler produces the expected event - // label - - // create an event - PersonRegionUpdateEvent event = new PersonRegionUpdateEvent(new PersonId(0), TestRegionId.REGION_1, TestRegionId.REGION_2); - - // derive the expected event label for this event - EventLabel expectedEventLabel = PersonRegionUpdateEvent.getEventLabelByArrivalRegion(c, TestRegionId.REGION_2); - - // have the event labeler produce an event label and show it - // is equal to the expected event label - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - assertEquals(expectedEventLabel, actualEventLabel); - - } - }); - } - - @Test - @UnitTestMethod(name = "getEventLabelerForDepartureRegion", args = {}) - public void testGetEventLabelerForDepartureRegion() { - - RegionsActionSupport.testConsumer(0, 5507742922324601760L, TimeTrackingPolicy.DO_NOT_TRACK_TIME, (c) -> { - // show that the event labeler can be constructed has the correct - // values - EventLabeler eventLabeler = PersonRegionUpdateEvent.getEventLabelerForDepartureRegion(); - assertEquals(PersonRegionUpdateEvent.class, eventLabeler.getEventClass()); - - for (TestRegionId testRegionId : TestRegionId.values()) { - assertEquals(PersonRegionUpdateEvent.getEventLabelByDepartureRegion(c, testRegionId).getLabelerId(), eventLabeler.getEventLabelerId()); - - // show that the event labeler produces the expected event - // label - - // create an event - PersonRegionUpdateEvent event = new PersonRegionUpdateEvent(new PersonId(0), TestRegionId.REGION_1, TestRegionId.REGION_2); - - // derive the expected event label for this event - EventLabel expectedEventLabel = PersonRegionUpdateEvent.getEventLabelByDepartureRegion(c, TestRegionId.REGION_1); - - // have the event labeler produce an event label and show it - // is equal to the expected event label - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - assertEquals(expectedEventLabel, actualEventLabel); - - } - }); - } - - @Test - @UnitTestMethod(name = "getEventLabelerForPerson", args = {}) - public void testGetEventLabelerForPerson() { - - RegionsActionSupport.testConsumer(0, 6570752422605720457L, TimeTrackingPolicy.DO_NOT_TRACK_TIME, (c) -> { - // show that the event labeler can be constructed has the correct - // values - EventLabeler eventLabeler = PersonRegionUpdateEvent.getEventLabelerForPerson(); - assertEquals(PersonRegionUpdateEvent.class, eventLabeler.getEventClass()); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - PersonId personId = peopleDataManager.addPerson(PersonConstructionData.builder().add(TestRegionId.REGION_1).build()); - - for (TestRegionId regionId : TestRegionId.values()) { - TestRegionId nextRegionId = regionId.next(); - - assertEquals(PersonRegionUpdateEvent.getEventLabelByPerson(c, personId).getLabelerId(), eventLabeler.getEventLabelerId()); - - // show that the event labeler produces the expected event - // label - - // create an event - PersonRegionUpdateEvent event = new PersonRegionUpdateEvent(personId, regionId, nextRegionId); - - // derive the expected event label for this event - EventLabel expectedEventLabel = PersonRegionUpdateEvent.getEventLabelByPerson(c, personId); - - // have the event labeler produce an event label and show it - // is equal to the expected event label - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - assertEquals(expectedEventLabel, actualEventLabel); - - } - }); - } - - @Test - @UnitTestMethod(name = "getPrimaryKeyValue", args = {}) - public void testGetPrimaryKeyValue() { - PersonRegionUpdateEvent personRegionUpdateEvent = new PersonRegionUpdateEvent(new PersonId(12), TestRegionId.REGION_2, TestRegionId.REGION_4); - assertEquals(PersonRegionUpdateEvent.class, personRegionUpdateEvent.getPrimaryKeyValue()); - } - -} diff --git a/gcm3/src/test/java/plugins/regions/events/AT_RegionPropertyUpdateEvent.java b/gcm3/src/test/java/plugins/regions/events/AT_RegionPropertyUpdateEvent.java deleted file mode 100644 index 97becc46d..000000000 --- a/gcm3/src/test/java/plugins/regions/events/AT_RegionPropertyUpdateEvent.java +++ /dev/null @@ -1,190 +0,0 @@ -package plugins.regions.events; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -import nucleus.EventLabel; -import nucleus.EventLabeler; -import nucleus.SimulationContext; -import plugins.regions.support.RegionId; -import plugins.regions.support.RegionPropertyId; -import plugins.regions.testsupport.RegionsActionSupport; -import plugins.regions.testsupport.TestRegionId; -import plugins.regions.testsupport.TestRegionPropertyId; -import plugins.util.properties.TimeTrackingPolicy; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = RegionPropertyUpdateEvent.class) -public class AT_RegionPropertyUpdateEvent { - - @Test - @UnitTestConstructor(args = { RegionId.class, RegionPropertyId.class, Object.class, Object.class }) - public void testContstructor() { - // Nothing to test here. All fields covered by other tests. - } - - @Test - @UnitTestMethod(name = "getRegionId", args = { RegionId.class, RegionPropertyId.class, Object.class, Object.class }) - public void testGetRegionId() { - for (TestRegionId testRegionId : TestRegionId.values()) { - RegionPropertyId regionPropertyId = TestRegionPropertyId.REGION_PROPERTY_1_BOOLEAN_MUTABLE; - Object previousValue = true; - Object currentValue = false; - RegionPropertyUpdateEvent event = new RegionPropertyUpdateEvent(testRegionId, regionPropertyId, previousValue, currentValue); - assertEquals(testRegionId, event.getRegionId()); - } - } - - @Test - @UnitTestMethod(name = "getRegionPropertyId", args = {}) - public void testGetRegionPropertyId() { - for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { - RegionId regionId = TestRegionId.REGION_2; - Object previousValue = true; - Object currentValue = false; - RegionPropertyUpdateEvent event = new RegionPropertyUpdateEvent(regionId, testRegionPropertyId, previousValue, currentValue); - assertEquals(testRegionPropertyId, event.getRegionPropertyId()); - } - } - - @Test - @UnitTestMethod(name = "getPreviousPropertyValue", args = {}) - public void testGetPreviousPropertyValue() { - for (int i = 0; i < 10; i++) { - RegionId regionId = TestRegionId.REGION_2; - RegionPropertyId regionPropertyId = TestRegionPropertyId.REGION_PROPERTY_5_INTEGER_IMMUTABLE; - Object previousValue = i; - Object currentValue = false; - RegionPropertyUpdateEvent event = new RegionPropertyUpdateEvent(regionId, regionPropertyId, previousValue, currentValue); - assertEquals(previousValue, event.getPreviousPropertyValue()); - } - } - - @Test - @UnitTestMethod(name = "getCurrentPropertyValue", args = {}) - public void testGetCurrentPropertyValue() { - for (int i = 0; i < 10; i++) { - RegionId regionId = TestRegionId.REGION_2; - RegionPropertyId regionPropertyId = TestRegionPropertyId.REGION_PROPERTY_5_INTEGER_IMMUTABLE; - Object previousValue = true; - Object currentValue = i; - RegionPropertyUpdateEvent event = new RegionPropertyUpdateEvent(regionId, regionPropertyId, previousValue, currentValue); - assertEquals(currentValue, event.getCurrentPropertyValue()); - } - } - - @Test - @UnitTestMethod(name = "toString", args = {}) - public void testToString() { - RegionId regionId = TestRegionId.REGION_2; - RegionPropertyId regionPropertyId = TestRegionPropertyId.REGION_PROPERTY_5_INTEGER_IMMUTABLE; - Object previousValue = 45; - Object currentValue = 88; - RegionPropertyUpdateEvent event = new RegionPropertyUpdateEvent(regionId, regionPropertyId, previousValue, currentValue); - String actualValue = event.toString(); - String expectedValue = "RegionPropertyUpdateEvent [regionId=REGION_2, regionPropertyId=REGION_PROPERTY_5_INTEGER_IMMUTABLE, previousPropertyValue=45, currentPropertyValue=88]"; - assertEquals(expectedValue, actualValue); - } - - @Test - @UnitTestMethod(name = "getEventLabelByProperty", args = { SimulationContext.class, RegionPropertyId.class }) - public void testGetEventLabelByProperty() { - RegionsActionSupport.testConsumer(0, 7066615060417862369L, TimeTrackingPolicy.DO_NOT_TRACK_TIME, (c) -> { - for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { - EventLabel eventLabel = RegionPropertyUpdateEvent.getEventLabelByProperty(c, testRegionPropertyId); - assertEquals(RegionPropertyUpdateEvent.class, eventLabel.getEventClass()); - assertEquals(testRegionPropertyId, eventLabel.getPrimaryKeyValue()); - assertEquals(RegionPropertyUpdateEvent.getEventLabelerForProperty().getEventLabelerId(), eventLabel.getLabelerId()); - } - }); - } - - @Test - @UnitTestMethod(name = "getEventLabelByRegionAndProperty", args = { SimulationContext.class, RegionId.class, RegionPropertyId.class }) - public void testGetEventLabelByRegionAndProperty() { - RegionsActionSupport.testConsumer(0, 7397296219745259412L, TimeTrackingPolicy.DO_NOT_TRACK_TIME, (c) -> { - for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { - for (TestRegionId testRegionId : TestRegionId.values()) { - EventLabel eventLabel = RegionPropertyUpdateEvent.getEventLabelByRegionAndProperty(c, testRegionId, testRegionPropertyId); - assertEquals(RegionPropertyUpdateEvent.class, eventLabel.getEventClass()); - assertEquals(testRegionPropertyId, eventLabel.getPrimaryKeyValue()); - assertEquals(RegionPropertyUpdateEvent.getEventLabelerForRegionAndProperty().getEventLabelerId(), eventLabel.getLabelerId()); - } - } - }); - } - - @Test - @UnitTestMethod(name = "getEventLabelerForRegionAndProperty", args = {}) - public void testGetEventLabelerForRegionAndProperty() { - RegionsActionSupport.testConsumer(0, 8940599178011626507L, TimeTrackingPolicy.DO_NOT_TRACK_TIME, (c) -> { - // show that the event labeler can be constructed has the correct - // values - EventLabeler eventLabeler = RegionPropertyUpdateEvent.getEventLabelerForRegionAndProperty(); - assertEquals(RegionPropertyUpdateEvent.class, eventLabeler.getEventClass()); - - for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { - for (TestRegionId testRegionId : TestRegionId.values()) { - assertEquals(RegionPropertyUpdateEvent.getEventLabelByRegionAndProperty(c, testRegionId, testRegionPropertyId).getLabelerId(), eventLabeler.getEventLabelerId()); - - // show that the event labeler produces the expected event - // label - - // create an event - RegionPropertyUpdateEvent event = new RegionPropertyUpdateEvent(testRegionId, testRegionPropertyId, 15, 20); - - // derive the expected event label for this event - EventLabel expectedEventLabel = RegionPropertyUpdateEvent.getEventLabelByRegionAndProperty(c, testRegionId, testRegionPropertyId); - - // have the event labeler produce an event label and show it - // is equal to the expected event label - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - assertEquals(expectedEventLabel, actualEventLabel); - - } - } - }); - } - - @Test - @UnitTestMethod(name = "getEventLabelerForProperty", args = {}) - public void testGetEventLabelerForProperty() { - RegionsActionSupport.testConsumer(0, 6696076014058054790L, TimeTrackingPolicy.DO_NOT_TRACK_TIME, (c) -> { - // show that the event labeler can be constructed has the correct - // values - EventLabeler eventLabeler = RegionPropertyUpdateEvent.getEventLabelerForProperty(); - assertEquals(RegionPropertyUpdateEvent.class, eventLabeler.getEventClass()); - - for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { - assertEquals(RegionPropertyUpdateEvent.getEventLabelByProperty(c, testRegionPropertyId).getLabelerId(), eventLabeler.getEventLabelerId()); - - // show that the event labeler produces the expected event - // label - - // create an event - RegionPropertyUpdateEvent event = new RegionPropertyUpdateEvent(TestRegionId.REGION_1, testRegionPropertyId, 15, 20); - - // derive the expected event label for this event - EventLabel expectedEventLabel = RegionPropertyUpdateEvent.getEventLabelByProperty(c, testRegionPropertyId); - - // have the event labeler produce an event label and show it - // is equal to the expected event label - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - assertEquals(expectedEventLabel, actualEventLabel); - - } - }); - } - - @Test - @UnitTestMethod(name = "getPrimaryKeyValue", args = {}) - public void testGetPrimaryKeyValue() { - for (TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { - RegionPropertyUpdateEvent regionPropertyUpdateEvent = new RegionPropertyUpdateEvent(TestRegionId.REGION_2, testRegionPropertyId, 10, 15); - assertEquals(testRegionPropertyId, regionPropertyUpdateEvent.getPrimaryKeyValue()); - } - } -} diff --git a/gcm3/src/test/java/plugins/regions/support/AT_RegionError.java b/gcm3/src/test/java/plugins/regions/support/AT_RegionError.java deleted file mode 100644 index a04de52cc..000000000 --- a/gcm3/src/test/java/plugins/regions/support/AT_RegionError.java +++ /dev/null @@ -1,30 +0,0 @@ -package plugins.regions.support; - -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = RegionError.class) -public class AT_RegionError { - - @Test - @UnitTestMethod(name = "getDescription", args = {}) - public void test() { - //show that each description is a unique, non-null and non-empty string - Set descriptions = new LinkedHashSet<>(); - for(RegionError regionError : RegionError.values()) { - String description = regionError.getDescription(); - assertNotNull(description,"null description for "+regionError); - assertTrue(description.length()>0, "empty string for "+regionError); - boolean unique = descriptions.add(description); - assertTrue(unique,"description for "+regionError+" is not unique"); - } - } -} diff --git a/gcm3/src/test/java/plugins/regions/support/AT_RegionFilter.java b/gcm3/src/test/java/plugins/regions/support/AT_RegionFilter.java deleted file mode 100644 index ba92251fa..000000000 --- a/gcm3/src/test/java/plugins/regions/support/AT_RegionFilter.java +++ /dev/null @@ -1,95 +0,0 @@ -package plugins.regions.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import nucleus.SimulationContext; -import plugins.partitions.support.Filter; -import plugins.partitions.support.FilterSensitivity; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.PersonId; -import plugins.regions.datamanagers.RegionsDataManager; -import plugins.regions.events.PersonRegionUpdateEvent; -import plugins.regions.testsupport.RegionsActionSupport; -import plugins.regions.testsupport.TestRegionId; -import plugins.util.properties.TimeTrackingPolicy; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - -@UnitTest(target = RegionFilter.class) -public class AT_RegionFilter { - - - @Test - @UnitTestConstructor(args = { Set.class }) - public void testConstructor() { - RegionsActionSupport.testConsumer(100, 7513298944605144297L,TimeTrackingPolicy.DO_NOT_TRACK_TIME, (c) -> { - - /* precondition: if the set is null */ - Set regionIds = null; - - assertThrows(RuntimeException.class, () -> new RegionFilter(regionIds)); - - /* precondition: if the region is unknown */ - ContractException contractException = assertThrows(ContractException.class, () -> new RegionFilter(TestRegionId.getUnknownRegionId()).validate(c)); - assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); - - assertThrows(RuntimeException.class, () -> new RegionFilter(null, TestRegionId.REGION_1).validate(c)); - - }); - - } - - - @Test - @UnitTestMethod(name = "getFilterSensitivities", args = {}) - public void testGetFilterSensitivities() { - RegionsActionSupport.testConsumer(100, 4278456048187470819L,TimeTrackingPolicy.DO_NOT_TRACK_TIME, (c) -> { - - Filter filter = new RegionFilter(TestRegionId.REGION_1); - - Set> filterSensitivities = filter.getFilterSensitivities(); - assertNotNull(filterSensitivities); - assertEquals(filterSensitivities.size(), 1); - - FilterSensitivity filterSensitivity = filterSensitivities.iterator().next(); - assertEquals(PersonRegionUpdateEvent.class, filterSensitivity.getEventClass()); - }); - } - - @Test - @UnitTestMethod(name = "evaluate", args = { SimulationContext.class, PersonId.class }) - public void testEvaluate() { - RegionsActionSupport.testConsumer(100, 8908124836418429909L,TimeTrackingPolicy.DO_NOT_TRACK_TIME, (c) -> { - - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - - Filter filter = new RegionFilter(TestRegionId.REGION_1, TestRegionId.REGION_2); - - for (PersonId personId : peopleDataManager.getPeople()) { - boolean expected = regionsDataManager.getPersonRegion(personId).equals(TestRegionId.REGION_1) || regionsDataManager.getPersonRegion(personId).equals(TestRegionId.REGION_2); - boolean actual = filter.evaluate(c, personId); - assertEquals(expected, actual); - } - - /* precondition: if the context is null */ - assertThrows(RuntimeException.class, () -> filter.evaluate(null, new PersonId(0))); - - /* precondition: if the person id is null */ - assertThrows(RuntimeException.class, () -> filter.evaluate(c, null)); - - /* precondition: if the person id is unknown */ - assertThrows(RuntimeException.class, () -> filter.evaluate(c, new PersonId(123412342))); - - }); - - } -} diff --git a/gcm3/src/test/java/plugins/regions/support/AT_RegionId.java b/gcm3/src/test/java/plugins/regions/support/AT_RegionId.java deleted file mode 100644 index ffc13c03f..000000000 --- a/gcm3/src/test/java/plugins/regions/support/AT_RegionId.java +++ /dev/null @@ -1,14 +0,0 @@ -package plugins.regions.support; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - -@UnitTest(target = RegionId.class) -public class AT_RegionId { - - @Test - public void test() { - //nothing to test - } -} diff --git a/gcm3/src/test/java/plugins/regions/support/AT_RegionLabeler.java b/gcm3/src/test/java/plugins/regions/support/AT_RegionLabeler.java deleted file mode 100644 index c291acfa9..000000000 --- a/gcm3/src/test/java/plugins/regions/support/AT_RegionLabeler.java +++ /dev/null @@ -1,165 +0,0 @@ -package plugins.regions.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.List; -import java.util.Set; -import java.util.function.Function; - -import org.junit.jupiter.api.Test; - -import nucleus.Plugin; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import plugins.partitions.support.LabelerSensitivity; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.PersonConstructionData; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import plugins.regions.datamanagers.RegionsDataManager; -import plugins.regions.events.PersonRegionUpdateEvent; -import plugins.regions.testsupport.RegionsActionSupport; -import plugins.regions.testsupport.TestRegionId; -import plugins.util.properties.TimeTrackingPolicy; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - -@UnitTest(target = RegionLabeler.class) -public class AT_RegionLabeler { - - @Test - @UnitTestConstructor(args = { Function.class }) - public void testConstructor() { - assertNotNull(new RegionLabeler((c) -> null)); - } - - @Test - @UnitTestMethod(name = "getDimension", args = {}) - public void testGetDimension() { - assertEquals(RegionId.class, new RegionLabeler((c) -> null).getDimension()); - } - - @Test - @UnitTestMethod(name = "getLabel", args = {}) - public void testGetLabel() { - - /* - * Create a region labeler from a function. Have an agent apply the - * function directly to a person's region to get a label for that - * person. Get the label from the region labeler from the person id - * alone. Compare the two labels for equality. - */ - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // build a region labeler with a function that can be tested - Function function = (c) -> { - TestRegionId testRegionId = (TestRegionId) c; - return testRegionId.ordinal(); - }; - - RegionLabeler regionLabeler = new RegionLabeler(function); - - - - // add a few people to the simulation spread across the various - // regions - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - int numberOfPeople = 2 * TestRegionId.size(); - - // show that there will be people - assertTrue(numberOfPeople > 0); - - for (int i = 0; i < numberOfPeople; i++) { - RegionId regionId = TestRegionId.values()[i % TestRegionId.size()]; - PersonConstructionData personConstructionData = PersonConstructionData.builder().add(regionId).build(); - peopleDataManager.addPerson(personConstructionData); - } - })); - - - - /* - * Have the agent show that the region labeler created above - * produces a label for each person that is consistent with the function - * passed to the region labeler. - */ - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - - List people = peopleDataManager.getPeople(); - for (PersonId personId : people) { - - // get the person's region and apply the function directly - RegionId regionId = regionsDataManager.getPersonRegion(personId); - Object expectedLabel = function.apply(regionId); - - // get the label from the person id - Object actualLabel = regionLabeler.getLabel(c, personId); - - // show that the two labels are equal - assertEquals(expectedLabel, actualLabel); - - } - })); - - //test preconditions - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { - - //if the person does not exist - ContractException contractException = assertThrows(ContractException.class, ()-> regionLabeler.getLabel(c, new PersonId(-1))); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - - //if the person id is null - contractException = assertThrows(ContractException.class, ()-> regionLabeler.getLabel(c, null)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - - - })); - - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - RegionsActionSupport.testConsumers(0, 343017070904588574L,TimeTrackingPolicy.DO_NOT_TRACK_TIME, testPlugin); - - } - - @Test - @UnitTestMethod(name = "getLabelerSensitivities", args = {}) - public void testGetLabelerSensitivities() { - - /* - * Get the labeler sensitivities and show that they are consistent with - * their documented behaviors. - */ - - RegionLabeler regionLabeler = new RegionLabeler((c) -> null); - - Set> labelerSensitivities = regionLabeler.getLabelerSensitivities(); - - // show that there is exactly one sensitivity - assertEquals(1, labelerSensitivities.size()); - - // show that the sensitivity is associated with - // PersonRegionUpdateEvent - LabelerSensitivity labelerSensitivity = labelerSensitivities.iterator().next(); - assertEquals(PersonRegionUpdateEvent.class, labelerSensitivity.getEventClass()); - - // show that the sensitivity will return the person id from a - // PersonRegionUpdateEvent - PersonId personId = new PersonId(56); - PersonRegionUpdateEvent personRegionUpdateEvent = new PersonRegionUpdateEvent(personId, TestRegionId.REGION_1, - TestRegionId.REGION_2); - labelerSensitivity.getPersonId(personRegionUpdateEvent); - - } - -} diff --git a/gcm3/src/test/java/plugins/regions/support/AT_RegionPropertyId.java b/gcm3/src/test/java/plugins/regions/support/AT_RegionPropertyId.java deleted file mode 100644 index bff97d3d1..000000000 --- a/gcm3/src/test/java/plugins/regions/support/AT_RegionPropertyId.java +++ /dev/null @@ -1,14 +0,0 @@ -package plugins.regions.support; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - -@UnitTest(target = RegionPropertyId.class) -public class AT_RegionPropertyId{ - - @Test - public void test() { - //nothing to test - } -} diff --git a/gcm3/src/test/java/plugins/regions/support/AT_SimpleRegionId.java b/gcm3/src/test/java/plugins/regions/support/AT_SimpleRegionId.java deleted file mode 100644 index f6eeee5f0..000000000 --- a/gcm3/src/test/java/plugins/regions/support/AT_SimpleRegionId.java +++ /dev/null @@ -1,130 +0,0 @@ -package plugins.regions.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = SimpleRegionId.class) -public class AT_SimpleRegionId { - - @Test - @UnitTestConstructor(args = { Object.class }) - public void testConstructor() { - assertNotNull(new SimpleRegionId(5)); - - assertThrows(NullPointerException.class, () -> new SimpleRegionId(null)); - } - - @Test - @UnitTestMethod(name = "toString", args = {}) - public void testToString() { - /* - * Show that the toString of the SimpleRegionId equals its input's - * toString - */ - - assertEquals(Integer.toString(5), new SimpleRegionId(5).toString()); - assertEquals("table", new SimpleRegionId("table").toString()); - assertEquals(Double.toString(2345.5345), new SimpleRegionId(2345.5345).toString()); - - } - - @Test - @UnitTestMethod(name = "toString", args = {}) - public void testEquals() { - SimpleRegionId id_1 = new SimpleRegionId(2); - SimpleRegionId id_2 = new SimpleRegionId(5); - SimpleRegionId id_3 = new SimpleRegionId(2); - SimpleRegionId id_4 = new SimpleRegionId("A"); - SimpleRegionId id_5 = new SimpleRegionId("A"); - SimpleRegionId id_6 = new SimpleRegionId("B"); - - assertEquals(id_1, id_1); - assertNotEquals(id_1, id_2); - assertEquals(id_1, id_3); - assertNotEquals(id_1, id_4); - assertNotEquals(id_1, id_5); - assertNotEquals(id_1, id_6); - - assertNotEquals(id_2, id_1); - assertEquals(id_2, id_2); - assertNotEquals(id_2, id_3); - assertNotEquals(id_2, id_4); - assertNotEquals(id_2, id_5); - assertNotEquals(id_2, id_6); - - assertNotEquals(id_2, id_1); - assertEquals(id_2, id_2); - assertNotEquals(id_2, id_3); - assertNotEquals(id_2, id_4); - assertNotEquals(id_2, id_5); - assertNotEquals(id_2, id_6); - - assertEquals(id_3, id_1); - assertNotEquals(id_3, id_2); - assertEquals(id_3, id_3); - assertNotEquals(id_3, id_4); - assertNotEquals(id_3, id_5); - assertNotEquals(id_3, id_6); - - assertNotEquals(id_4, id_1); - assertNotEquals(id_4, id_2); - assertNotEquals(id_4, id_3); - assertEquals(id_4, id_4); - assertEquals(id_4, id_5); - assertNotEquals(id_4, id_6); - - assertNotEquals(id_5, id_1); - assertNotEquals(id_5, id_2); - assertNotEquals(id_5, id_3); - assertEquals(id_5, id_4); - assertEquals(id_5, id_5); - assertNotEquals(id_5, id_6); - - assertNotEquals(id_6, id_1); - assertNotEquals(id_6, id_2); - assertNotEquals(id_6, id_3); - assertNotEquals(id_6, id_4); - assertNotEquals(id_6, id_5); - assertEquals(id_6, id_6); - - assertNotEquals(id_1, null); - assertNotEquals(id_2, null); - assertNotEquals(id_3, null); - assertNotEquals(id_4, null); - assertNotEquals(id_5, null); - assertNotEquals(id_6, null); - - } - - @Test - @UnitTestMethod(name = "hashCode", args = {}) - public void testHashCode() { - - // equal objects have equal hash codes - for (int i = 0; i < 30; i++) { - SimpleRegionId s1 = new SimpleRegionId(i); - SimpleRegionId s2 = new SimpleRegionId(i); - assertEquals(s1.hashCode(), s2.hashCode()); - } - - Set hashCodes = new LinkedHashSet<>(); - for (int i = 0; i < 30; i++) { - boolean unique = hashCodes.add(new SimpleRegionId(i).hashCode()); - assertTrue(unique); - } - - } - -} diff --git a/gcm3/src/test/java/plugins/regions/support/AT_SimpleRegionPropertyId.java b/gcm3/src/test/java/plugins/regions/support/AT_SimpleRegionPropertyId.java deleted file mode 100644 index 1b90a705e..000000000 --- a/gcm3/src/test/java/plugins/regions/support/AT_SimpleRegionPropertyId.java +++ /dev/null @@ -1,128 +0,0 @@ -package plugins.regions.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; - - -public class AT_SimpleRegionPropertyId{ - - @Test - @UnitTestConstructor(args = { Object.class }) - public void testConstructor() { - assertNotNull(new SimpleRegionPropertyId(5)); - - assertThrows(NullPointerException.class, () -> new SimpleRegionPropertyId(null)); - } - - @Test - @UnitTestMethod(name = "toString", args = {}) - public void testToString() { - /* - * Show that the toString of the SimpleRegionPropertyId equals its input's - * toString - */ - - assertEquals(Integer.toString(5), new SimpleRegionPropertyId(5).toString()); - assertEquals("table", new SimpleRegionPropertyId("table").toString()); - assertEquals(Double.toString(2345.5345), new SimpleRegionPropertyId(2345.5345).toString()); - - } - - @Test - @UnitTestMethod(name = "toString", args = {}) - public void testEquals() { - SimpleRegionPropertyId id_1 = new SimpleRegionPropertyId(2); - SimpleRegionPropertyId id_2 = new SimpleRegionPropertyId(5); - SimpleRegionPropertyId id_3 = new SimpleRegionPropertyId(2); - SimpleRegionPropertyId id_4 = new SimpleRegionPropertyId("A"); - SimpleRegionPropertyId id_5 = new SimpleRegionPropertyId("A"); - SimpleRegionPropertyId id_6 = new SimpleRegionPropertyId("B"); - - assertEquals(id_1, id_1); - assertNotEquals(id_1, id_2); - assertEquals(id_1, id_3); - assertNotEquals(id_1, id_4); - assertNotEquals(id_1, id_5); - assertNotEquals(id_1, id_6); - - assertNotEquals(id_2, id_1); - assertEquals(id_2, id_2); - assertNotEquals(id_2, id_3); - assertNotEquals(id_2, id_4); - assertNotEquals(id_2, id_5); - assertNotEquals(id_2, id_6); - - assertNotEquals(id_2, id_1); - assertEquals(id_2, id_2); - assertNotEquals(id_2, id_3); - assertNotEquals(id_2, id_4); - assertNotEquals(id_2, id_5); - assertNotEquals(id_2, id_6); - - assertEquals(id_3, id_1); - assertNotEquals(id_3, id_2); - assertEquals(id_3, id_3); - assertNotEquals(id_3, id_4); - assertNotEquals(id_3, id_5); - assertNotEquals(id_3, id_6); - - assertNotEquals(id_4, id_1); - assertNotEquals(id_4, id_2); - assertNotEquals(id_4, id_3); - assertEquals(id_4, id_4); - assertEquals(id_4, id_5); - assertNotEquals(id_4, id_6); - - assertNotEquals(id_5, id_1); - assertNotEquals(id_5, id_2); - assertNotEquals(id_5, id_3); - assertEquals(id_5, id_4); - assertEquals(id_5, id_5); - assertNotEquals(id_5, id_6); - - assertNotEquals(id_6, id_1); - assertNotEquals(id_6, id_2); - assertNotEquals(id_6, id_3); - assertNotEquals(id_6, id_4); - assertNotEquals(id_6, id_5); - assertEquals(id_6, id_6); - - assertNotEquals(id_1, null); - assertNotEquals(id_2, null); - assertNotEquals(id_3, null); - assertNotEquals(id_4, null); - assertNotEquals(id_5, null); - assertNotEquals(id_6, null); - - } - - @Test - @UnitTestMethod(name = "hashCode", args = {}) - public void testHashCode() { - - // equal objects have equal hash codes - for (int i = 0; i < 30; i++) { - SimpleRegionPropertyId s1 = new SimpleRegionPropertyId(i); - SimpleRegionPropertyId s2 = new SimpleRegionPropertyId(i); - assertEquals(s1.hashCode(), s2.hashCode()); - } - - Set hashCodes = new LinkedHashSet<>(); - for (int i = 0; i < 30; i++) { - boolean unique = hashCodes.add(new SimpleRegionPropertyId(i).hashCode()); - assertTrue(unique); - } - - } -} diff --git a/gcm3/src/test/java/plugins/regions/testsupport/AT_TestRegionPropertyId.java b/gcm3/src/test/java/plugins/regions/testsupport/AT_TestRegionPropertyId.java deleted file mode 100644 index ca39dc2b9..000000000 --- a/gcm3/src/test/java/plugins/regions/testsupport/AT_TestRegionPropertyId.java +++ /dev/null @@ -1,52 +0,0 @@ -package plugins.regions.testsupport; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import plugins.regions.support.RegionPropertyId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = TestRegionPropertyId.class) -public class AT_TestRegionPropertyId { - - /** - * Shows that a generated unknown region property id is not null and - * not a member of the enum and is unique. - */ - @Test - @UnitTestMethod(name = "getUnknownRegionPropertyId", args = {}) - public void testGetUnknownRegionPropertyId() { - RegionPropertyId unknownRegionPropertyId = TestRegionPropertyId.getUnknownRegionPropertyId(); - assertNotNull(unknownRegionPropertyId); - for(TestRegionPropertyId testRegionPropertyId : TestRegionPropertyId.values()) { - assertNotEquals(testRegionPropertyId, unknownRegionPropertyId); - } - - Set unknownRegionPropertyIds = new LinkedHashSet<>(); - for(int i = 0;i<10;i++) { - unknownRegionPropertyId = TestRegionPropertyId.getUnknownRegionPropertyId(); - assertNotNull(unknownRegionPropertyId); - boolean unique = unknownRegionPropertyIds.add(unknownRegionPropertyId); - assertTrue(unique); - } - } - - /** - * Shows that size() returns the number of members in the TestRegionId - * enum - */ - @Test - @UnitTestMethod(name = "size", args = {}) - public void testSize() { - assertEquals(TestRegionPropertyId.values().length, TestRegionPropertyId.size()); - } - -} diff --git a/gcm3/src/test/java/plugins/reports/AT_ReportPlugin.java b/gcm3/src/test/java/plugins/reports/AT_ReportPlugin.java deleted file mode 100644 index c797cb7a0..000000000 --- a/gcm3/src/test/java/plugins/reports/AT_ReportPlugin.java +++ /dev/null @@ -1,59 +0,0 @@ -package plugins.reports; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import nucleus.Plugin; -import nucleus.Simulation; -import plugins.reports.support.ReportId; -import plugins.reports.support.SimpleReportId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = ReportsPlugin.class) -public class AT_ReportPlugin { - - @Test - @UnitTestMethod(name = "getReportPlugin", args = {}) - public void testGetReportPlugin() { - //Build the report plugin from two reports - ReportId reportId_1 = new SimpleReportId("report 1"); - ReportId reportId_2 = new SimpleReportId("report 2"); - - Set expectedReportIds = new LinkedHashSet<>(); - expectedReportIds.add(reportId_1); - expectedReportIds.add(reportId_2); - - Set observedReportIds = new LinkedHashSet<>(); - - ReportsPluginData.Builder builder = ReportsPluginData.builder(); - - builder.addReport(() -> (c) -> { - observedReportIds.add(reportId_1); - }); - builder.addReport(() -> (c) -> { - observedReportIds.add(reportId_2); - }); - - ReportsPluginData reportsPluginData = builder.build(); - - Plugin reportPlugin = ReportsPlugin.getReportPlugin(reportsPluginData); - - //show that the report has the reports plugin data - reportPlugin.getPluginDatas().contains(reportsPluginData); - - //show that the plugin has the correct idea - assertEquals(ReportsPluginId.PLUGIN_ID, reportPlugin.getPluginId()); - - //show that plugin correctly adds the reports to the simulation - Simulation.builder().addPlugin(reportPlugin).build().execute(); - - assertEquals(observedReportIds, expectedReportIds); - - } - -} diff --git a/gcm3/src/test/java/plugins/reports/AT_ReportsPluginData.java b/gcm3/src/test/java/plugins/reports/AT_ReportsPluginData.java deleted file mode 100644 index d7300c08a..000000000 --- a/gcm3/src/test/java/plugins/reports/AT_ReportsPluginData.java +++ /dev/null @@ -1,140 +0,0 @@ -package plugins.reports; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.function.Consumer; -import java.util.function.Supplier; - -import org.junit.jupiter.api.Test; - -import nucleus.ActorContext; -import plugins.reports.support.ReportId; -import plugins.reports.support.SimpleReportId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = ReportsPluginData.class) -public class AT_ReportsPluginData { - - @Test - @UnitTestMethod(name = "builder", args = {}) - public void testBuilder() { - assertNotNull(ReportsPluginData.builder()); - } - - @Test - @UnitTestMethod(target = ReportsPluginData.Builder.class, name = "build", args = {}) - public void testBuild() { - assertNotNull(ReportsPluginData.builder().build()); - } - - @Test - @UnitTestMethod(target = ReportsPluginData.Builder.class, name = "addReport", args = { Supplier.class }) - public void testAddReport() { - ReportId reportId_1 = new SimpleReportId("report 1"); - ReportId reportId_2 = new SimpleReportId("report 2"); - - Set expectedReportIds = new LinkedHashSet<>(); - expectedReportIds.add(reportId_1); - expectedReportIds.add(reportId_2); - - Set observedReportIds = new LinkedHashSet<>(); - - ReportsPluginData.Builder builder = ReportsPluginData.builder(); - builder.addReport(() -> (c) -> { - observedReportIds.add(reportId_1); - }); - builder.addReport(() -> (c) -> { - observedReportIds.add(reportId_2); - }); - - ReportsPluginData reportsPluginData = builder.build(); - - Set> reports = reportsPluginData.getReports(); - - assertNotNull(reports); - - for (Consumer report : reports) { - assertNotNull(report); - report.accept(null); - } - - assertEquals(observedReportIds, expectedReportIds); - - } - - @Test - @UnitTestMethod(name = "getReports", args = {}) - public void testGetReportIds() { - - ReportId reportId_1 = new SimpleReportId("report 1"); - ReportId reportId_2 = new SimpleReportId("report 2"); - - Set expectedReportIds = new LinkedHashSet<>(); - expectedReportIds.add(reportId_1); - expectedReportIds.add(reportId_2); - - Set observedReportIds = new LinkedHashSet<>(); - - ReportsPluginData.Builder builder = ReportsPluginData.builder(); - builder.addReport(() -> (c) -> { - observedReportIds.add(reportId_1); - }); - builder.addReport(() -> (c) -> { - observedReportIds.add(reportId_2); - }); - - ReportsPluginData reportsPluginData = builder.build(); - - Set> reports = reportsPluginData.getReports(); - - assertNotNull(reports); - - for (Consumer report : reports) { - assertNotNull(report); - report.accept(null); - } - - assertEquals(observedReportIds, expectedReportIds); - - } - - @Test - @UnitTestMethod(name = "getCloneBuilder", args = {}) - public void testGetCloneBuilder() { - ReportId reportId_1 = new SimpleReportId("report 1"); - ReportId reportId_2 = new SimpleReportId("report 2"); - - Set expectedReportIds = new LinkedHashSet<>(); - expectedReportIds.add(reportId_1); - expectedReportIds.add(reportId_2); - - Set observedReportIds = new LinkedHashSet<>(); - - ReportsPluginData.Builder builder = ReportsPluginData.builder(); - builder.addReport(() -> (c) -> { - observedReportIds.add(reportId_1); - }); - builder.addReport(() -> (c) -> { - observedReportIds.add(reportId_2); - }); - - ReportsPluginData reportsPluginData = builder.build(); - ReportsPluginData cloneReportsPluginData = (ReportsPluginData)reportsPluginData.getCloneBuilder().build(); - - Set> reports = cloneReportsPluginData.getReports(); - - assertNotNull(reports); - - for (Consumer report : reports) { - assertNotNull(report); - report.accept(null); - } - - assertEquals(observedReportIds, expectedReportIds); - } - -} diff --git a/gcm3/src/test/java/plugins/reports/support/AT_PeriodicReport.java b/gcm3/src/test/java/plugins/reports/support/AT_PeriodicReport.java deleted file mode 100644 index d3e806532..000000000 --- a/gcm3/src/test/java/plugins/reports/support/AT_PeriodicReport.java +++ /dev/null @@ -1,426 +0,0 @@ -package plugins.reports.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.ArrayList; -import java.util.List; - -import org.apache.commons.math3.util.FastMath; -import org.junit.jupiter.api.Test; - -import nucleus.ActorContext; -import nucleus.Plugin; -import nucleus.Simulation; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import plugins.reports.ReportsPlugin; -import plugins.reports.ReportsPluginData; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; -import util.wrappers.MutableDouble; -import util.wrappers.MutableInteger; - -@UnitTest(target = PeriodicReport.class) -public class AT_PeriodicReport { - - private static class TestReport extends PeriodicReport { - - public TestReport(ReportId reportId, ReportPeriod reportPeriod) { - super(reportId, reportPeriod); - } - - @Override - protected void flush(ActorContext reportContext) { - } - - } - - /* - * Used to test the fillTimeFields() method when the period is - * ReportPeriod.DAILY - */ - private static class DailyTestReport extends PeriodicReport { - - private MutableInteger testCounter = new MutableInteger(); - - public DailyTestReport(ReportId reportId, ReportPeriod reportPeriod) { - super(reportId, reportPeriod); - if (reportPeriod != ReportPeriod.DAILY) { - throw new RuntimeException("must be a daily report"); - } - } - - @Override - protected void flush(ActorContext reportContext) { - testCounter.increment(); - ReportHeader.Builder reportHeaderBuilder = ReportHeader.builder(); - this.addTimeFieldHeaders(reportHeaderBuilder); - ReportHeader reportHeader = reportHeaderBuilder.build(); - ReportItem.Builder reportItemBuilder = ReportItem.builder(); - - fillTimeFields(reportItemBuilder); - - ReportItem reportItem = reportItemBuilder.setReportId(getReportId()).setReportHeader(reportHeader).build(); - - int dayValue = (int) FastMath.ceil(reportContext.getTime()); - dayValue--; - String expectedTimeString = Integer.toString(dayValue); - String actualTimeString = reportItem.getValue(0); - assertEquals(expectedTimeString, actualTimeString); - - } - - } - - /* - * Used to test the fillTimeFields() method when the period is - * ReportPeriod.HOURLY - */ - private static class HourlyTestReport extends PeriodicReport { - - private MutableInteger testCounter = new MutableInteger(); - - public HourlyTestReport(ReportId reportId, ReportPeriod reportPeriod) { - super(reportId, reportPeriod); - if (reportPeriod != ReportPeriod.HOURLY) { - throw new RuntimeException("must be an hourly report"); - } - - } - - @Override - protected void flush(ActorContext reportContext) { - testCounter.increment(); - ReportHeader.Builder reportHeaderBuilder = ReportHeader.builder(); - this.addTimeFieldHeaders(reportHeaderBuilder); - ReportHeader reportHeader = reportHeaderBuilder.build(); - ReportItem.Builder reportItemBuilder = ReportItem.builder(); - - fillTimeFields(reportItemBuilder); - - ReportItem reportItem = reportItemBuilder.setReportId(getReportId()).setReportHeader(reportHeader).build(); - double time = reportContext.getTime(); - double hourScaledTime = time * 24; - double closestHourTime = FastMath.floor(time * 24); - int hour = (int) closestHourTime; - if (hourScaledTime - closestHourTime < 0.01) { - hour--; - } - int expectedDay = hour / 24; - int expectedHour = hour % 24; - String expectedHourTimeString = Integer.toString(expectedHour); - String expectedDayTimeString = Integer.toString(expectedDay); - String actualDayTimeString = reportItem.getValue(0); - String actualHourTimeString = reportItem.getValue(1); - assertEquals(expectedDayTimeString, actualDayTimeString); - assertEquals(expectedHourTimeString, actualHourTimeString); - } - - } - - /* - * Used to test the fillTimeFields() method when the period is - * ReportPeriod.END_OF_SIMULATION - */ - private static class EndOfSimulationTestReport extends PeriodicReport { - - private MutableInteger flushCount = new MutableInteger(); - private MutableDouble flushTime = new MutableDouble(); - - public EndOfSimulationTestReport(ReportId reportId, ReportPeriod reportPeriod) { - super(reportId, reportPeriod); - if (reportPeriod != ReportPeriod.END_OF_SIMULATION) { - throw new RuntimeException("must be an hourly report"); - } - - } - - @Override - protected void flush(ActorContext reportContext) { - flushTime.setValue(reportContext.getTime()); - flushCount.increment(); - - ReportHeader.Builder reportHeaderBuilder = ReportHeader.builder(); - addTimeFieldHeaders(reportHeaderBuilder); - ReportHeader reportHeader = reportHeaderBuilder.build(); - ReportItem.Builder reportItemBuilder = ReportItem.builder(); - fillTimeFields(reportItemBuilder); - - ReportItem reportItem = reportItemBuilder.setReportId(getReportId()).setReportHeader(reportHeader).build(); - - assertEquals(0, reportItem.size()); - } - - } - - private static class InitTestReport extends PeriodicReport { - private List flushTimes = new ArrayList<>(); - - public InitTestReport(ReportId reportId, ReportPeriod reportPeriod) { - super(reportId, reportPeriod); - } - - @Override - protected void flush(ActorContext reportContext) { - flushTimes.add(reportContext.getTime()); - } - - } - - @Test - @UnitTestConstructor(args = { ReportPeriod.class }) - public void testConstructor() { - ContractException contractException = assertThrows(ContractException.class, () -> new TestReport(null, ReportPeriod.DAILY)); - assertEquals(ReportError.NULL_REPORT_ID, contractException.getErrorType()); - - contractException = assertThrows(ContractException.class, () -> new TestReport(new SimpleReportId("report"), null)); - assertEquals(ReportError.NULL_REPORT_PERIOD, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "addTimeFieldHeaders", args = { ReportHeader.Builder.class }) - public void testAddTimeFieldHeaders() { - - ReportHeader.Builder reportHeaderBuilder = ReportHeader.builder(); - - ReportId reportId = new SimpleReportId("report"); - - TestReport testReport = new TestReport(reportId, ReportPeriod.HOURLY); - testReport.addTimeFieldHeaders(reportHeaderBuilder); - ReportHeader reportHeader = reportHeaderBuilder.build(); - List headerStrings = reportHeader.getHeaderStrings(); - assertEquals(2, headerStrings.size()); - assertEquals("day", headerStrings.get(0)); - assertEquals("hour", headerStrings.get(1)); - - testReport = new TestReport(reportId, ReportPeriod.DAILY); - testReport.addTimeFieldHeaders(reportHeaderBuilder); - reportHeader = reportHeaderBuilder.build(); - headerStrings = reportHeader.getHeaderStrings(); - assertEquals(1, headerStrings.size()); - assertEquals("day", headerStrings.get(0)); - - testReport = new TestReport(reportId, ReportPeriod.END_OF_SIMULATION); - testReport.addTimeFieldHeaders(reportHeaderBuilder); - reportHeader = reportHeaderBuilder.build(); - headerStrings = reportHeader.getHeaderStrings(); - assertEquals(0, headerStrings.size()); - - } - - @Test - @UnitTestMethod(name = "fillTimeFields", args = { ReportItem.Builder.class }) - public void testFillTimeFields_Daily() { - double simulationEndTime = 10.6; - - Simulation.Builder builder = Simulation.builder(); - - ReportId reportId = new SimpleReportId("report"); - DailyTestReport dailyTestReport = new DailyTestReport(reportId, ReportPeriod.DAILY); - ReportsPluginData reportsInitialData = ReportsPluginData.builder().addReport(() -> { - return dailyTestReport::init; - }).build(); - - // add the reports plugin - builder.addPlugin(ReportsPlugin.getReportPlugin(reportsInitialData)); - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // create an agent that will make the engine run for a few days - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(simulationEndTime, (c) -> { - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - builder.addPlugin(testPlugin); - - // build and execute the simulation - builder.build().execute(); - - // show that the daily test report actually executed its tests - int expectedFlushExecutionCount = (int) FastMath.ceil(simulationEndTime); - assertEquals(expectedFlushExecutionCount, dailyTestReport.testCounter.getValue()); - - } - - @Test - @UnitTestMethod(name = "fillTimeFields", args = { ReportItem.Builder.class }) - public void testFillTimeFields_Hourly() { - double simulationEndTime = 3.6; - - Simulation.Builder builder = Simulation.builder(); - - ReportId reportId = new SimpleReportId("report"); - HourlyTestReport hourlyTestReport = new HourlyTestReport(reportId,ReportPeriod.HOURLY); - ReportsPluginData reportsInitialData = ReportsPluginData.builder().addReport(() -> { - return hourlyTestReport::init; - }).build(); - Plugin reportPlugin = ReportsPlugin.getReportPlugin(reportsInitialData); - - // add the reports plugin - builder.addPlugin(reportPlugin); - - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // create an agent that will make the engine run for a few days - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(simulationEndTime, (c) -> { - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - builder.addPlugin(testPlugin); - - // build and execute the engine - builder.build().execute(); - - // show that the hourly test report actually executed its tests - int expectedFlushExecutionCount = (int) FastMath.ceil(24 * simulationEndTime); - assertEquals(expectedFlushExecutionCount, hourlyTestReport.testCounter.getValue()); - - } - - @Test - @UnitTestMethod(name = "fillTimeFields", args = { ReportItem.Builder.class }) - public void testFillTimeFields_EndOfSimulation() { - double simulationEndTime = 3.6; - - Simulation.Builder builder = Simulation.builder(); - - ReportId reportId = new SimpleReportId("report"); - EndOfSimulationTestReport endOfSimulationTestReport = new EndOfSimulationTestReport(reportId,ReportPeriod.END_OF_SIMULATION); - ReportsPluginData reportsInitialData = ReportsPluginData.builder().addReport( () -> { - return endOfSimulationTestReport::init; - }).build(); - - Plugin reportPlugin = ReportsPlugin.getReportPlugin(reportsInitialData); - - // add the reports plugin - builder.addPlugin(reportPlugin); - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // create an agent that will make the engine run for a few days - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(simulationEndTime, (c) -> { - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - builder.addPlugin(testPlugin); - - // build and execute the engine - builder.build().execute(); - - /* - * Show that the end of simulation test report executed its test exactly - * once at the end of the simulation - */ - - assertEquals(1, endOfSimulationTestReport.flushCount.getValue()); - assertEquals(simulationEndTime, endOfSimulationTestReport.flushTime.getValue()); - - } - - @Test - @UnitTestMethod(name = "init", args = { ActorContext.class }) - public void testInit() { - - for (ReportPeriod reportPeriod : ReportPeriod.values()) { - - double simulationEndTime = 10.6; - - Simulation.Builder builder = Simulation.builder(); - - ReportId reportId = new SimpleReportId("report"); - InitTestReport initTestReport = new InitTestReport(reportId,reportPeriod); - ReportsPluginData reportsInitialData = ReportsPluginData.builder().addReport( () -> { - return initTestReport::init; - }).build(); - - // add the reports plugin - Plugin reportPlugin = ReportsPlugin.getReportPlugin(reportsInitialData); - builder.addPlugin(reportPlugin); - - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // create an agent that will make the engine run for a few days - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(simulationEndTime, (c) -> { - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - builder.addPlugin(testPlugin); - - // build and execute the engine - builder.build().execute(); - - // show that the report is flushed at the end of the simulation - int lastIndex = initTestReport.flushTimes.size() - 1; - assertTrue(lastIndex >= 0); - double lastFlushTime = initTestReport.flushTimes.get(lastIndex); - assertEquals(simulationEndTime, lastFlushTime); - - // show that the report is flushed on the given reporting period - - // calculate the expected flushing times as a function of the report - // period - List expectedTimes = new ArrayList<>(); - switch (reportPeriod) { - case DAILY: - int lastDay = (int) simulationEndTime; - for (int i = 1; i <= lastDay; i++) { - double time = i; - expectedTimes.add(time); - } - expectedTimes.add(simulationEndTime); - break; - case END_OF_SIMULATION: - expectedTimes.add(simulationEndTime); - // expectedTimes.addAll(initTestReport.flushTimes); - break; - case HOURLY: - - int lastHour = (int) (simulationEndTime * 24); - for (int i = 1; i <= lastHour; i++) { - double time = i; - time /= 24; - expectedTimes.add(time); - } - expectedTimes.add(simulationEndTime); - - break; - default: - throw new RuntimeException("unhandled report period " + reportPeriod); - } - - // show that the expected and actual times are reasonably equal - assertEquals(expectedTimes.size(), initTestReport.flushTimes.size()); - - for (int i = 0; i < expectedTimes.size(); i++) { - assertEquals(expectedTimes.get(i), initTestReport.flushTimes.get(i), 0.00001); - } - - // precondition tests - - TestReport testReport = new TestReport(reportId,ReportPeriod.DAILY); - ContractException contractException = assertThrows(ContractException.class, () -> testReport.init(null)); - assertEquals(ReportError.NULL_CONTEXT, contractException.getErrorType()); - } - - // precondition tests - TestReport testReport = new TestReport(new SimpleReportId("report"),ReportPeriod.DAILY); - ContractException contractException = assertThrows(ContractException.class, () -> testReport.init(null)); - assertEquals(ReportError.NULL_CONTEXT, contractException.getErrorType()); - } - -} diff --git a/gcm3/src/test/java/plugins/reports/support/AT_ReportItem.java b/gcm3/src/test/java/plugins/reports/support/AT_ReportItem.java deleted file mode 100644 index 42d7ca00c..000000000 --- a/gcm3/src/test/java/plugins/reports/support/AT_ReportItem.java +++ /dev/null @@ -1,285 +0,0 @@ -package plugins.reports.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; -import util.random.RandomGeneratorProvider; - -@UnitTest(target = ReportItem.class) -public final class AT_ReportItem { - - @Test - @UnitTestMethod(name = "builder", args = {}) - public void testBuilder() { - assertNotNull(ReportItem.builder()); - } - - @Test - @UnitTestMethod(target = ReportItem.Builder.class, name = "addValue", args = { Object.class }) - public void testAddValue() { - - for (int i = 0; i < 10; i++) { - ReportItem reportItem = ReportItem .builder()// - .setReportId(new SimpleReportId("report"))// - .setReportHeader(ReportHeader.builder().build())// - .addValue(i)// - .addValue(i - 1)// - .build();// - - assertEquals(Integer.toString(i), reportItem.getValue(0)); - assertEquals(Integer.toString(i - 1), reportItem.getValue(1)); - } - - // precondition tests - ContractException contractException = assertThrows(ContractException.class, () -> ReportItem.builder().addValue(null)); - assertEquals(ReportError.NULL_REPORT_ITEM_ENTRY, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(target = ReportItem.Builder.class, name = "build", args = { Object.class }) - public void testBuild() { - - // precondition tests - - ContractException contractException = assertThrows(ContractException.class, () -> { - ReportItem.builder().setReportId(new SimpleReportId("report")).build(); - }); - assertEquals(ReportError.NULL_REPORT_HEADER, contractException.getErrorType()); - - contractException = assertThrows(ContractException.class, () -> { - ReportItem.builder().setReportHeader(ReportHeader.builder().build()).build(); - }); - assertEquals(ReportError.NULL_REPORT_ID, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(target = ReportItem.Builder.class, name = "setReportHeader", args = { ReportHeader.class }) - public void testSetReportHeader() { - ReportHeader reportHeader = ReportHeader.builder().add("A").add("B").build(); - - ReportItem reportItem = ReportItem.builder().setReportHeader(reportHeader).setReportId(new SimpleReportId("report")).build(); - - assertEquals(reportHeader, reportItem.getReportHeader()); - - // precondition tests - ContractException contractException = assertThrows(ContractException.class, () -> ReportItem.builder().setReportHeader(null)); - assertEquals(ReportError.NULL_REPORT_HEADER, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(target = ReportItem.Builder.class, name = "setReportId", args = { ReportId.class }) - public void testSetReportId() { - - SimpleReportId reportId = new SimpleReportId("report"); - - ReportItem reportItem = ReportItem.builder().setReportHeader(ReportHeader.builder().build()).setReportId(reportId).build(); - - assertEquals(reportId, reportItem.getReportId()); - - // precondition tests - ContractException contractException = assertThrows(ContractException.class, () -> ReportItem.builder().setReportId(null)); - assertEquals(ReportError.NULL_REPORT_ID, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "getReportId", args = {}) - public void testGetReportId() { - SimpleReportId reportId = new SimpleReportId("report"); - - ReportItem reportItem = ReportItem.builder().setReportHeader(ReportHeader.builder().build()).setReportId(reportId).build(); - - assertEquals(reportId, reportItem.getReportId()); - } - - @Test - @UnitTestMethod(name = "getReportHeader", args = {}) - - public void testGetReportHeader() { - ReportHeader reportHeader = ReportHeader.builder().add("A").add("B").build(); - - ReportItem reportItem = ReportItem.builder().setReportHeader(reportHeader).setReportId(new SimpleReportId("report")).build(); - - assertEquals(reportHeader, reportItem.getReportHeader()); - } - - @Test - @UnitTestMethod(name = "getValue", args = { int.class }) - - public void testGetValue() { - ReportHeader reportHeader = ReportHeader.builder().add("A").add("B").add("C").add("D").build(); - ReportItem reportItem = ReportItem .builder()// - .setReportHeader(reportHeader)// - .setReportId(new SimpleReportId("report"))// - .addValue("alpha")// - .addValue(12)// - .addValue(false)// - .addValue(123.42)// - .build();// - - assertEquals("alpha", reportItem.getValue(0)); - assertEquals(Integer.toString(12), reportItem.getValue(1)); - assertEquals(Boolean.toString(false), reportItem.getValue(2)); - assertEquals(Double.toString(123.42), reportItem.getValue(3)); - - } - - @Test - @UnitTestMethod(name = "size", args = {}) - - public void testSize() { - ReportHeader reportHeader = ReportHeader.builder().build(); - SimpleReportId reportId = new SimpleReportId("report"); - - for (int i = 0; i < 10; i++) { - ReportItem.Builder builder = ReportItem.builder().setReportHeader(reportHeader).setReportId(reportId); - for (int j = 0; j < i; j++) { - builder.addValue(j); - } - ReportItem reportItem = builder.build(); - assertEquals(i, reportItem.size()); - } - - } - - @Test - @UnitTestMethod(name = "toString", args = {}) - public void testToString() { - ReportHeader reportHeader = ReportHeader.builder().build(); - SimpleReportId reportId = new SimpleReportId("report"); - - ReportItem reportItem = ReportItem.builder().setReportHeader(reportHeader).setReportId(reportId).addValue("A").addValue("B").build(); - - String expectedValue = "ReportItem [reportId=SimpleReportId [value=report], reportHeader=ReportHeader [headerStrings=[]], values=[A, B]]"; - String actualValue = reportItem.toString(); - assertEquals(expectedValue, actualValue); - } - - @Test - @UnitTestMethod(name = "hashCode", args = {}) - public void testHashCode() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7481311225319288863L); - /* - * Show equal report items have equal hash codes. We will focus on the - * values part since other tests should cover the report id and header. - */ - - ReportHeader reportHeader = ReportHeader.builder().build(); - SimpleReportId reportId = new SimpleReportId("report"); - - ReportItem reportItem1 = ReportItem.builder().setReportHeader(reportHeader).setReportId(reportId).build(); - ReportItem reportItem2 = ReportItem.builder().setReportHeader(reportHeader).setReportId(reportId).build(); - assertEquals(reportItem1.hashCode(), reportItem2.hashCode()); - - reportItem1 = ReportItem.builder().setReportHeader(reportHeader).setReportId(reportId).addValue("A").addValue("B").build(); - reportItem2 = ReportItem.builder().setReportHeader(reportHeader).setReportId(reportId).addValue("A").addValue("B").build(); - assertEquals(reportItem1.hashCode(), reportItem2.hashCode()); - - /* - * Show that the hash codes are reasonable dispersed - * - */ - Set hashCodes = new LinkedHashSet<>(); - ReportItem.Builder builder = ReportItem.builder(); - - int sampleCount = 1000; - for (int i = 0; i < sampleCount; i++) { - builder.setReportHeader(reportHeader).setReportId(reportId); - int fieldCount = randomGenerator.nextInt(3) + 1; - for(int j = 0;j4*sampleCount/5); - - } - - private static Character generateRandomCharacter(RandomGenerator randomGenerator) { - int i = randomGenerator.nextInt(26) + 97; - return (char) i; - } - - private static String generateRandomString(RandomGenerator randomGenerator, int length) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < length; i++) { - sb.append(generateRandomCharacter(randomGenerator)); - } - return sb.toString(); - } - - @Test - @UnitTestMethod(name = "equals", args = { Object.class }) - - public void testEquals() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7481311225319288863L); - - ReportHeader reportHeader = ReportHeader.builder().build(); - SimpleReportId reportId = new SimpleReportId("report"); - - - /* - * Show that equal objects are equal - * - */ - ReportItem.Builder builder1 = ReportItem.builder(); - ReportItem.Builder builder2 = ReportItem.builder(); - - int sampleCount = 100; - for (int i = 0; i < sampleCount; i++) { - builder1.setReportHeader(reportHeader).setReportId(reportId); - builder2.setReportHeader(reportHeader).setReportId(reportId); - int fieldCount = randomGenerator.nextInt(3) + 1; - for(int j = 0;j> expectedReportItems = new LinkedHashMap<>(); - - @Test - @UnitTestMethod(name = "getReportItems", args = {}) - public void testGetReportItems() { - /* - * Have an actor generate a few report items with occasional duplicate - * over two scenarios. Record expected results and compare to the report - * items returned by the - */ - - TestReportItemOutputConsumer testReportItemOutputConsumer = new TestReportItemOutputConsumer(); - - Experiment.Builder experimentBuilder = Experiment.builder(); - experimentBuilder.addOutputHandler(testReportItemOutputConsumer::init); - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - pluginBuilder.addTestActorPlan("scenario tracker", new TestActorPlan(0, (c) -> { - scenarioId.increment(); - })); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - - ReportItem reportItem; - if (scenarioId.getValue() == 0) { - reportItem = generateReportItem(ReportIds.REPORT_ID_1, 3.4, false); - } else { - reportItem = generateReportItem(ReportIds.REPORT_ID_1, 6.7, true); - } - - record(reportItem, 2); - c.releaseOutput(reportItem); - c.releaseOutput(reportItem); - })); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { - - ReportItem reportItem; - if (scenarioId.getValue() == 0) { - reportItem = generateReportItem(ReportIds.REPORT_ID_2, 12, "tango", false); - } else { - reportItem = generateReportItem(ReportIds.REPORT_ID_3, "bravo", 3.4); - } - - record(reportItem, 1); - c.releaseOutput(reportItem); - })); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(3, (c) -> { - - ReportItem reportItem; - if (scenarioId.getValue() == 0) { - reportItem = generateReportItem(ReportIds.REPORT_ID_1, 6.29, true); - } else { - reportItem = generateReportItem(ReportIds.REPORT_ID_2, 45, "foxtrot", true); - } - - record(reportItem, 3); - c.releaseOutput(reportItem); - c.releaseOutput(reportItem); - c.releaseOutput(reportItem); - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - Dimension dimension = Dimension .builder()// - .addLevel((t) -> { - ArrayList result = new ArrayList<>(); - result.add("alpha1"); - return result; - })// - .addLevel((t) -> { - ArrayList result = new ArrayList<>(); - result.add("alpha2"); - return result; - })// - .addMetaDatum("Alpha")// - .build();// - - experimentBuilder.addDimension(dimension); - experimentBuilder.addPlugin(testPlugin); - experimentBuilder.setThreadCount(0); - experimentBuilder.reportProgressToConsole(false); - experimentBuilder.build().execute(); - - Map> actualReportItems = testReportItemOutputConsumer.getReportItems(); - assertEquals(expectedReportItems, actualReportItems); - - } - - private static void record(ReportItem reportItem, int count) { - int scenId = scenarioId.getValue(); - Map map = expectedReportItems.get(scenId); - if (map == null) { - map = new LinkedHashMap<>(); - expectedReportItems.put(scenId, map); - } - map.put(reportItem, count); - } - - private static ReportItem generateReportItem(ReportIds reportIds, Object... values) { - - ReportItem.Builder builder = ReportItem.builder();// - builder.setReportId(reportIds.reportId);// - builder.setReportHeader(reportIds.reportHeader);// - for (Object value : values) { - builder.addValue(value); - } - return builder.build();// - } - - @Test - @UnitTestMethod(name = "init", args = { ExperimentContext.class }) - public void testInit() { - // covered by testGetReportItems - } - -} diff --git a/gcm3/src/test/java/plugins/resources/AT_ResourcesPlugin.java b/gcm3/src/test/java/plugins/resources/AT_ResourcesPlugin.java deleted file mode 100644 index 8e885613d..000000000 --- a/gcm3/src/test/java/plugins/resources/AT_ResourcesPlugin.java +++ /dev/null @@ -1,43 +0,0 @@ -package plugins.resources; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import nucleus.Plugin; -import nucleus.PluginId; -import plugins.people.PeoplePluginId; -import plugins.regions.RegionsPluginId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = ResourcesPlugin.class) -public class AT_ResourcesPlugin { - - - - @Test - @UnitTestMethod(name = "getResourcesPlugin(ResourcesPluginData)", args = { ResourcesPluginData.class }) - public void testGetResourcesPlugin() { - - ResourcesPluginData resourcesPluginData = ResourcesPluginData.builder().build(); - Plugin resourcesPlugin = ResourcesPlugin.getResourcesPlugin(resourcesPluginData); - - assertEquals(1,resourcesPlugin.getPluginDatas().size()); - assertTrue(resourcesPlugin.getPluginDatas().contains(resourcesPluginData)); - - assertEquals(ResourcesPluginId.PLUGIN_ID, resourcesPlugin.getPluginId()); - - Set expectedDependencies = new LinkedHashSet<>(); - expectedDependencies.add(PeoplePluginId.PLUGIN_ID); - expectedDependencies.add(RegionsPluginId.PLUGIN_ID); - - assertEquals(expectedDependencies, resourcesPlugin.getPluginDependencies()); - - } - -} diff --git a/gcm3/src/test/java/plugins/resources/AT_ResourcesPluginData.java b/gcm3/src/test/java/plugins/resources/AT_ResourcesPluginData.java deleted file mode 100644 index 0d171a5e3..000000000 --- a/gcm3/src/test/java/plugins/resources/AT_ResourcesPluginData.java +++ /dev/null @@ -1,1017 +0,0 @@ -package plugins.resources; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.PluginData; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import plugins.regions.support.RegionError; -import plugins.regions.support.RegionId; -import plugins.regions.testsupport.TestRegionId; -import plugins.resources.support.ResourceError; -import plugins.resources.support.ResourceId; -import plugins.resources.support.ResourcePropertyId; -import plugins.resources.testsupport.TestResourceId; -import plugins.resources.testsupport.TestResourcePropertyId; -import plugins.util.properties.PropertyDefinition; -import plugins.util.properties.TimeTrackingPolicy; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; -import util.random.RandomGeneratorProvider; -import util.wrappers.MultiKey; -import util.wrappers.MutableInteger; - -@UnitTest(target = ResourcesPluginData.class) -public final class AT_ResourcesPluginData { - - @Test - @UnitTestMethod(name = "builder", args = {}) - public void testBuilder() { - assertNotNull(ResourcesPluginData.builder()); - } - - @Test - @UnitTestMethod(target = ResourcesPluginData.Builder.class, name = "build", args = {}) - public void testBuild() { - ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); - assertNotNull(builder.build()); - - // precondition tests - - /* - * if a resource tracking policy was collected for a resource that was - * not added - */ - ContractException contractException = assertThrows(ContractException.class, () -> // - builder .setResourceTimeTracking(TestResourceId.RESOURCE_1, TestResourceId.RESOURCE_1.getTimeTrackingPolicy())// - .build());// - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - - /* - * if a resource property definition was collected for a resource that - * was not added - */ - contractException = assertThrows(ContractException.class, () -> { - ResourceId resourceId = TestResourceId.RESOURCE_1; - ResourcePropertyId resourcePropertyId = TestResourcePropertyId.ResourceProperty_1_1_BOOLEAN_MUTABLE; - PropertyDefinition propertyDefinition = TestResourcePropertyId.ResourceProperty_1_1_BOOLEAN_MUTABLE.getPropertyDefinition(); - builder.defineResourceProperty(resourceId, resourcePropertyId, propertyDefinition).build(); - });// - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - - /* - * if a resource property value was collected for a resource that was - * not added - */ - contractException = assertThrows(ContractException.class, () -> { - ResourceId resourceId = TestResourceId.RESOURCE_1; - ResourcePropertyId resourcePropertyId = TestResourcePropertyId.ResourceProperty_1_1_BOOLEAN_MUTABLE; - Boolean value = false; - builder.setResourcePropertyValue(resourceId, resourcePropertyId, value).build(); - });// - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - - /* - * if a resource property value was collected for a resource property - * that is not associated with the given resource id - */ - contractException = assertThrows(ContractException.class, () -> { - ResourceId resourceId = TestResourceId.RESOURCE_1; - ResourcePropertyId resourcePropertyId = TestResourcePropertyId.ResourceProperty_1_1_BOOLEAN_MUTABLE; - Boolean value = false; - builder.addResource(resourceId); - builder.setResourcePropertyValue(resourceId, resourcePropertyId, value); - builder.build(); - });// - assertEquals(ResourceError.UNKNOWN_RESOURCE_PROPERTY_ID, contractException.getErrorType()); - - /* - * if a resource property value was collected for a resource property - * that is not compatible with the associated resource property - * definition - */ - contractException = assertThrows(ContractException.class, () -> { - ResourceId resourceId = TestResourceId.RESOURCE_1; - ResourcePropertyId resourcePropertyId = TestResourcePropertyId.ResourceProperty_1_1_BOOLEAN_MUTABLE; - PropertyDefinition propertyDefinition = TestResourcePropertyId.ResourceProperty_1_1_BOOLEAN_MUTABLE.getPropertyDefinition(); - Integer value = 5; - builder.addResource(resourceId); - builder.defineResourceProperty(resourceId, resourcePropertyId, propertyDefinition); - builder.setResourcePropertyValue(resourceId, resourcePropertyId, value); - builder.build(); - });// - assertEquals(ResourceError.INCOMPATIBLE_VALUE, contractException.getErrorType()); - - /* - * if a resource property definition has a null default value and there - * is no assigned resource property value for that resource - */ - contractException = assertThrows(ContractException.class, () -> { - ResourceId resourceId = TestResourceId.RESOURCE_1; - ResourcePropertyId resourcePropertyId = TestResourcePropertyId.ResourceProperty_1_1_BOOLEAN_MUTABLE; - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Boolean.class).build(); - builder.addResource(resourceId); - builder.defineResourceProperty(resourceId, resourcePropertyId, propertyDefinition); - builder.build(); - });// - assertEquals(ResourceError.INSUFFICIENT_RESOURCE_PROPERTY_VALUE_ASSIGNMENT, contractException.getErrorType()); - /* - * if a resource level was collected for a person that is an unknown - * resource id - */ - contractException = assertThrows(ContractException.class, () -> { - ResourceId resourceId = TestResourceId.RESOURCE_1; - Long value = 566L; - builder.setPersonResourceLevel(new PersonId(0), resourceId, value).build(); - });// - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - - /* - * if a resource level was collected for a region that is an unknown - * resource id - */ - contractException = assertThrows(ContractException.class, () -> { - ResourceId resourceId = TestResourceId.RESOURCE_1; - Long value = 566L; - builder.setRegionResourceLevel(TestRegionId.REGION_1, resourceId, value).build(); - });// - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(target = ResourcesPluginData.Builder.class, name = "", args = {}) - public void testAddResource() { - - ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); - Set expectedResourceIds = new LinkedHashSet<>(); - for (TestResourceId testResourceId : TestResourceId.values()) { - builder.addResource(testResourceId); - expectedResourceIds.add(testResourceId); - } - ResourcesPluginData resourceInitialData = builder.build(); - assertEquals(expectedResourceIds, resourceInitialData.getResourceIds()); - - // precondition tests - - // if the resource id is null - ContractException contractException = assertThrows(ContractException.class, () -> ResourcesPluginData.builder().addResource(null)); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - - // if the resource id was previously added - contractException = assertThrows(ContractException.class, () -> ResourcesPluginData.builder().addResource(TestResourceId.RESOURCE_1).addResource(TestResourceId.RESOURCE_1)); - assertEquals(ResourceError.DUPLICATE_RESOURCE_ID, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(target = ResourcesPluginData.Builder.class, name = "defineResourceProperty", args = { ResourceId.class, ResourcePropertyId.class, PropertyDefinition.class }) - public void testDefineResourceProperty() { - - ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); - for (TestResourceId testResourceId : TestResourceId.values()) { - builder.addResource(testResourceId); - Set testResourcePropertyIds = TestResourcePropertyId.getTestResourcePropertyIds(testResourceId); - for (TestResourcePropertyId testResourcePropertyId : testResourcePropertyIds) { - PropertyDefinition propertyDefinition = testResourcePropertyId.getPropertyDefinition(); - builder.defineResourceProperty(testResourceId, testResourcePropertyId, propertyDefinition); - } - } - - ResourcesPluginData resourceInitialData = builder.build(); - - for (TestResourceId testResourceId : TestResourceId.values()) { - Set testResourcePropertyIds = TestResourcePropertyId.getTestResourcePropertyIds(testResourceId); - for (TestResourcePropertyId testResourcePropertyId : testResourcePropertyIds) { - PropertyDefinition expectedPropertyDefinition = testResourcePropertyId.getPropertyDefinition(); - PropertyDefinition actualPropertyDefinition = resourceInitialData.getResourcePropertyDefinition(testResourceId, testResourcePropertyId); - assertEquals(expectedPropertyDefinition, actualPropertyDefinition); - } - } - - // precondition tests - - ResourceId resourceId = TestResourceId.RESOURCE_2; - ResourcePropertyId resourcePropertyId = TestResourcePropertyId.ResourceProperty_2_2_INTEGER_MUTABLE; - PropertyDefinition propertyDefinition = TestResourcePropertyId.ResourceProperty_2_2_INTEGER_MUTABLE.getPropertyDefinition(); - - // if the resource id is null - ContractException contractException = assertThrows(ContractException.class, () -> { - ResourcesPluginData.builder().defineResourceProperty(null, resourcePropertyId, propertyDefinition); - }); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - - // if the resource property id is null - contractException = assertThrows(ContractException.class, () -> { - ResourcesPluginData.builder().defineResourceProperty(resourceId, null, propertyDefinition); - }); - assertEquals(ResourceError.NULL_RESOURCE_PROPERTY_ID, contractException.getErrorType()); - - // if the property definition is null - contractException = assertThrows(ContractException.class, () -> { - ResourcesPluginData.builder().defineResourceProperty(resourceId, resourcePropertyId, null); - }); - assertEquals(ResourceError.NULL_RESOURCE_PROPERTY_DEFINITION, contractException.getErrorType()); - - // if a resource property definition for the given resource id and - // property id was previously defined. - contractException = assertThrows(ContractException.class, () -> { - ResourcesPluginData.builder().defineResourceProperty(resourceId, resourcePropertyId, propertyDefinition).defineResourceProperty(resourceId, resourcePropertyId, propertyDefinition); - }); - assertEquals(ResourceError.DUPLICATE_RESOURCE_PROPERTY_DEFINITION, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(target = ResourcesPluginData.Builder.class, name = "setPersonResourceLevel", args = { PersonId.class, ResourceId.class, long.class }) - public void testSetPersonResourceLevel() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6539895160899665826L); - - // add the resources - ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); - for (TestResourceId testResourceId : TestResourceId.values()) { - builder.addResource(testResourceId); - } - - Map expectedValues = new LinkedHashMap<>(); - - // add up to 30 people - Set people = new LinkedHashSet<>(); - for (int i = 0; i < 30; i++) { - people.add(new PersonId(randomGenerator.nextInt())); - } - assertTrue(people.size() > 20); - for (PersonId personId : people) { - for (TestResourceId testResourceId : TestResourceId.values()) { - MultiKey multiKey = new MultiKey(personId, testResourceId); - MutableInteger mutableInteger = new MutableInteger(); - expectedValues.put(multiKey, mutableInteger); - if (randomGenerator.nextBoolean()) { - int amount = randomGenerator.nextInt(10); - builder.setPersonResourceLevel(personId, testResourceId, amount); - mutableInteger.setValue(amount); - } - } - } - - ResourcesPluginData resourceInitialData = builder.build(); - - for (PersonId personId : people) { - for (TestResourceId testResourceId : TestResourceId.values()) { - MultiKey multiKey = new MultiKey(personId, testResourceId); - MutableInteger mutableInteger = expectedValues.get(multiKey); - int expectedAmount = mutableInteger.getValue(); - Long personResourceLevel = resourceInitialData.getPersonResourceLevel(personId, testResourceId); - assertEquals(expectedAmount, personResourceLevel); - } - } - - // precondition tests - PersonId personId = new PersonId(0); - ResourceId resourceId = TestResourceId.RESOURCE_5; - long amount = 678; - - // if the person id is null - ContractException contractException = assertThrows(ContractException.class, () -> builder.setPersonResourceLevel(null, resourceId, amount)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - - // if the resource id is null - contractException = assertThrows(ContractException.class, () -> builder.setPersonResourceLevel(personId, null, amount)); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - - // if the resource amount is negative - contractException = assertThrows(ContractException.class, () -> builder.setPersonResourceLevel(personId, resourceId, -5L)); - assertEquals(ResourceError.NEGATIVE_RESOURCE_AMOUNT, contractException.getErrorType()); - - // if the person's resource level was previously assigned - contractException = assertThrows(ContractException.class, () -> { - ResourcesPluginData.builder().setPersonResourceLevel(personId, resourceId, amount).setPersonResourceLevel(personId, resourceId, amount); - }); - assertEquals(ResourceError.DUPLICATE_PERSON_RESOURCE_LEVEL_ASSIGNMENT, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(target = ResourcesPluginData.Builder.class, name = "setRegionResourceLevel", args = { RegionId.class, ResourceId.class, long.class }) - public void testSetRegionResourceLevel() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8877834706249831995L); - - // add the resources - ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); - for (TestResourceId testResourceId : TestResourceId.values()) { - builder.addResource(testResourceId); - } - - Map expectedValues = new LinkedHashMap<>(); - - for (TestRegionId testRegionId : TestRegionId.values()) { - for (TestResourceId testResourceId : TestResourceId.values()) { - MultiKey multiKey = new MultiKey(testRegionId, testResourceId); - MutableInteger mutableInteger = new MutableInteger(); - expectedValues.put(multiKey, mutableInteger); - if (randomGenerator.nextBoolean()) { - int amount = randomGenerator.nextInt(10); - builder.setRegionResourceLevel(testRegionId, testResourceId, amount); - mutableInteger.setValue(amount); - } - } - } - - ResourcesPluginData resourceInitialData = builder.build(); - - for (TestRegionId testRegionId : TestRegionId.values()) { - for (TestResourceId testResourceId : TestResourceId.values()) { - MultiKey multiKey = new MultiKey(testRegionId, testResourceId); - MutableInteger mutableInteger = expectedValues.get(multiKey); - int expectedAmount = mutableInteger.getValue(); - Long regionResourceLevel = resourceInitialData.getRegionResourceLevel(testRegionId, testResourceId); - assertEquals(expectedAmount, regionResourceLevel); - } - } - - // precondition tests - RegionId regionId = TestRegionId.REGION_3; - ResourceId resourceId = TestResourceId.RESOURCE_5; - long amount = 678; - - // if the region id is null - ContractException contractException = assertThrows(ContractException.class, () -> builder.setRegionResourceLevel(null, resourceId, amount)); - assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); - - // if the resource id is null - contractException = assertThrows(ContractException.class, () -> builder.setRegionResourceLevel(regionId, null, amount)); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - - // if the resource amount is negative - contractException = assertThrows(ContractException.class, () -> builder.setRegionResourceLevel(regionId, resourceId, -5L)); - assertEquals(ResourceError.NEGATIVE_RESOURCE_AMOUNT, contractException.getErrorType()); - - // if the person's resource level was previously assigned - contractException = assertThrows(ContractException.class, () -> { - ResourcesPluginData.builder().setRegionResourceLevel(regionId, resourceId, amount).setRegionResourceLevel(regionId, resourceId, amount); - }); - assertEquals(ResourceError.DUPLICATE_REGION_RESOURCE_LEVEL_ASSIGNMENT, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(target = ResourcesPluginData.Builder.class, name = "setResourcePropertyValue", args = { ResourceId.class, ResourcePropertyId.class, Object.class }) - public void testSetResourcePropertyValue() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7516798209205913252L); - - Map expectedValues = new LinkedHashMap<>(); - - ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); - for (TestResourceId testResourceId : TestResourceId.values()) { - builder.addResource(testResourceId); - Set testResourcePropertyIds = TestResourcePropertyId.getTestResourcePropertyIds(testResourceId); - for (TestResourcePropertyId testResourcePropertyId : testResourcePropertyIds) { - MultiKey multiKey = new MultiKey(testResourceId, testResourcePropertyId); - PropertyDefinition propertyDefinition = testResourcePropertyId.getPropertyDefinition(); - expectedValues.put(multiKey, propertyDefinition.getDefaultValue().get()); - builder.defineResourceProperty(testResourceId, testResourcePropertyId, propertyDefinition); - if (randomGenerator.nextBoolean()) { - Object value = testResourcePropertyId.getRandomPropertyValue(randomGenerator); - builder.setResourcePropertyValue(testResourceId, testResourcePropertyId, value); - expectedValues.put(multiKey, value); - } - } - } - - ResourcesPluginData resourceInitialData = builder.build(); - - for (TestResourceId testResourceId : TestResourceId.values()) { - Set testResourcePropertyIds = TestResourcePropertyId.getTestResourcePropertyIds(testResourceId); - for (TestResourcePropertyId testResourcePropertyId : testResourcePropertyIds) { - MultiKey multiKey = new MultiKey(testResourceId, testResourcePropertyId); - Object expectedValue = expectedValues.get(multiKey); - Object actualValue = resourceInitialData.getResourcePropertyValue(testResourceId, testResourcePropertyId); - assertEquals(expectedValue, actualValue); - } - } - - // precondition tests - ResourceId resourceId = TestResourceId.RESOURCE_3; - ResourcePropertyId resourcePropertyId = TestResourcePropertyId.ResourceProperty_2_2_INTEGER_MUTABLE; - - // if the resource id is null - ContractException contractException = assertThrows(ContractException.class, () -> builder.setResourcePropertyValue(null, resourcePropertyId, 5)); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - - // if the resource property id is null - contractException = assertThrows(ContractException.class, () -> builder.setResourcePropertyValue(resourceId, null, 5)); - assertEquals(ResourceError.NULL_RESOURCE_PROPERTY_ID, contractException.getErrorType()); - - // if the resource property value is null - contractException = assertThrows(ContractException.class, () -> builder.setResourcePropertyValue(resourceId, null, 5)); - assertEquals(ResourceError.NULL_RESOURCE_PROPERTY_ID, contractException.getErrorType()); - - // if the resource property value was previously assigned - contractException = assertThrows(ContractException.class, () -> { - ResourcesPluginData.builder().setResourcePropertyValue(resourceId, resourcePropertyId, 5).setResourcePropertyValue(resourceId, resourcePropertyId, 5); - }); - assertEquals(ResourceError.DUPLICATE_RESOURCE_PROPERTY_VALUE_ASSIGNMENT, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(target = ResourcesPluginData.Builder.class, name = "setResourceTimeTracking", args = { ResourceId.class, TimeTrackingPolicy.class }) - public void testSetResourceTimeTracking() { - // 6539895160899665826L - - int i = 0; - Map expectedValues = new LinkedHashMap<>(); - - ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); - for (TestResourceId testResourceId : TestResourceId.values()) { - if (testResourceId != TestResourceId.RESOURCE_5) { - builder.addResource(testResourceId); - int index = i % TimeTrackingPolicy.values().length; - TimeTrackingPolicy timeTrackingPolicy = TimeTrackingPolicy.values()[index]; - builder.setResourceTimeTracking(testResourceId, timeTrackingPolicy); - expectedValues.put(testResourceId, timeTrackingPolicy); - } - } - builder.addResource(TestResourceId.RESOURCE_5); - expectedValues.put(TestResourceId.RESOURCE_5, TimeTrackingPolicy.DO_NOT_TRACK_TIME); - - ResourcesPluginData resourceInitialData = builder.build(); - for (TestResourceId testResourceId : TestResourceId.values()) { - TimeTrackingPolicy expectedPolicy = expectedValues.get(testResourceId); - TimeTrackingPolicy actualPolicy = resourceInitialData.getPersonResourceTimeTrackingPolicy(testResourceId); - assertEquals(expectedPolicy, actualPolicy); - } - - // precondition tests - ResourceId resourceId = TestResourceId.RESOURCE_2; - TimeTrackingPolicy timeTrackingPolicy = TimeTrackingPolicy.TRACK_TIME; - - // if the resource id is null - // ResourceError#NULL_RESOURCE_ID - ContractException contractException = assertThrows(ContractException.class, () -> builder.setResourceTimeTracking(null, timeTrackingPolicy)); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - - // if the tracking policy is null - // ResourceError.NULL_TIME_TRACKING_POLICY - contractException = assertThrows(ContractException.class, () -> builder.setResourceTimeTracking(resourceId, null)); - assertEquals(ResourceError.NULL_TIME_TRACKING_POLICY, contractException.getErrorType()); - - // if the resource tracking policy was previously assigned - // ResourceError#DUPLICATE_TIME_TRACKING_POLICY_ASSIGNMENT - contractException = assertThrows(ContractException.class, () -> { - ResourcesPluginData.builder().setResourceTimeTracking(resourceId, timeTrackingPolicy).setResourceTimeTracking(resourceId, timeTrackingPolicy); - }); - assertEquals(ResourceError.DUPLICATE_TIME_TRACKING_POLICY_ASSIGNMENT, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "getResourcePropertyDefinition", args = { ResourceId.class, ResourcePropertyId.class }) - public void testGetResourcePropertyDefinition() { - // 1866861448895276970L - - ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); - for (TestResourceId testResourceId : TestResourceId.values()) { - builder.addResource(testResourceId); - Set testResourcePropertyIds = TestResourcePropertyId.getTestResourcePropertyIds(testResourceId); - for (TestResourcePropertyId testResourcePropertyId : testResourcePropertyIds) { - PropertyDefinition propertyDefinition = testResourcePropertyId.getPropertyDefinition(); - builder.defineResourceProperty(testResourceId, testResourcePropertyId, propertyDefinition); - } - } - - ResourcesPluginData resourceInitialData = builder.build(); - - for (TestResourceId testResourceId : TestResourceId.values()) { - builder.addResource(testResourceId); - Set testResourcePropertyIds = TestResourcePropertyId.getTestResourcePropertyIds(testResourceId); - for (TestResourcePropertyId testResourcePropertyId : testResourcePropertyIds) { - PropertyDefinition expectedPropertyDefinition = testResourcePropertyId.getPropertyDefinition(); - PropertyDefinition actualPropertyDefinition = resourceInitialData.getResourcePropertyDefinition(testResourceId, testResourcePropertyId); - assertEquals(expectedPropertyDefinition, actualPropertyDefinition); - } - } - - // precondition tests - - // if the resource id is null - ContractException contractException = assertThrows(ContractException.class, () -> { - resourceInitialData.getResourcePropertyDefinition(null, TestResourcePropertyId.ResourceProperty_1_1_BOOLEAN_MUTABLE); - }); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - - // if the resource id is unknown - contractException = assertThrows(ContractException.class, () -> { - resourceInitialData.getResourcePropertyDefinition(TestResourceId.getUnknownResourceId(), TestResourcePropertyId.ResourceProperty_1_1_BOOLEAN_MUTABLE); - }); - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - - // if the resource property id is null - contractException = assertThrows(ContractException.class, () -> { - resourceInitialData.getResourcePropertyDefinition(TestResourceId.RESOURCE_1, null); - }); - assertEquals(ResourceError.NULL_RESOURCE_PROPERTY_ID, contractException.getErrorType()); - - // if the resource property id is unknown - contractException = assertThrows(ContractException.class, () -> { - resourceInitialData.getResourcePropertyDefinition(TestResourceId.RESOURCE_1, TestResourcePropertyId.getUnknownResourcePropertyId()); - }); - assertEquals(ResourceError.UNKNOWN_RESOURCE_PROPERTY_ID, contractException.getErrorType()); - - contractException = assertThrows(ContractException.class, () -> { - resourceInitialData.getResourcePropertyDefinition(TestResourceId.RESOURCE_1, TestResourcePropertyId.ResourceProperty_2_2_INTEGER_MUTABLE); - }); - assertEquals(ResourceError.UNKNOWN_RESOURCE_PROPERTY_ID, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "getResourcePropertyIds", args = { ResourceId.class }) - public void testGetResourcePropertyIds() { - // 7475098698397765251L - ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); - for (TestResourceId testResourceId : TestResourceId.values()) { - builder.addResource(testResourceId); - Set testResourcePropertyIds = TestResourcePropertyId.getTestResourcePropertyIds(testResourceId); - for (TestResourcePropertyId testResourcePropertyId : testResourcePropertyIds) { - PropertyDefinition propertyDefinition = testResourcePropertyId.getPropertyDefinition(); - builder.defineResourceProperty(testResourceId, testResourcePropertyId, propertyDefinition); - } - } - - ResourcesPluginData resourceInitialData = builder.build(); - - for (TestResourceId testResourceId : TestResourceId.values()) { - - Set expectedResourcePropertyIds = TestResourcePropertyId.getTestResourcePropertyIds(testResourceId); - Set actualResourcePropertyIds = resourceInitialData.getResourcePropertyIds(testResourceId); - assertEquals(expectedResourcePropertyIds, actualResourcePropertyIds); - } - - // precondition tests - - // if the resource id is null - ContractException contractException = assertThrows(ContractException.class, () -> resourceInitialData.getResourcePropertyIds(null)); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - - // if the resource id is unknown - contractException = assertThrows(ContractException.class, () -> resourceInitialData.getResourcePropertyIds(TestResourceId.getUnknownResourceId())); - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "getResourcePropertyValue", args = { ResourceId.class, ResourcePropertyId.class }) - public void testGetResourcePropertyValue() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1876340540126853882L); - - Map expectedValues = new LinkedHashMap<>(); - - ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); - for (TestResourceId testResourceId : TestResourceId.values()) { - builder.addResource(testResourceId); - Set testResourcePropertyIds = TestResourcePropertyId.getTestResourcePropertyIds(testResourceId); - for (TestResourcePropertyId testResourcePropertyId : testResourcePropertyIds) { - MultiKey multiKey = new MultiKey(testResourceId, testResourcePropertyId); - PropertyDefinition propertyDefinition = testResourcePropertyId.getPropertyDefinition(); - expectedValues.put(multiKey, propertyDefinition.getDefaultValue().get()); - builder.defineResourceProperty(testResourceId, testResourcePropertyId, propertyDefinition); - if (randomGenerator.nextBoolean()) { - Object value = testResourcePropertyId.getRandomPropertyValue(randomGenerator); - builder.setResourcePropertyValue(testResourceId, testResourcePropertyId, value); - expectedValues.put(multiKey, value); - } - } - } - - ResourcesPluginData resourceInitialData = builder.build(); - - for (TestResourceId testResourceId : TestResourceId.values()) { - Set testResourcePropertyIds = TestResourcePropertyId.getTestResourcePropertyIds(testResourceId); - for (TestResourcePropertyId testResourcePropertyId : testResourcePropertyIds) { - MultiKey multiKey = new MultiKey(testResourceId, testResourcePropertyId); - Object expectedValue = expectedValues.get(multiKey); - Object actualValue = resourceInitialData.getResourcePropertyValue(testResourceId, testResourcePropertyId); - assertEquals(expectedValue, actualValue); - } - } - - // precondition tests - - ResourceId resourceId = TestResourceId.RESOURCE_3; - ResourcePropertyId resourcePropertyId = TestResourcePropertyId.ResourceProperty_2_2_INTEGER_MUTABLE; - - // if the resource id is null - ContractException contractException = assertThrows(ContractException.class, () -> resourceInitialData.getResourcePropertyValue(null, resourcePropertyId)); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - - // if the resource id is unknown - contractException = assertThrows(ContractException.class, () -> resourceInitialData.getResourcePropertyValue(TestResourceId.getUnknownResourceId(), resourcePropertyId)); - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - - // if the resource property id is null - contractException = assertThrows(ContractException.class, () -> resourceInitialData.getResourcePropertyValue(resourceId, null)); - assertEquals(ResourceError.NULL_RESOURCE_PROPERTY_ID, contractException.getErrorType()); - - // if the resource property id is unknown - contractException = assertThrows(ContractException.class, () -> resourceInitialData.getResourcePropertyValue(resourceId, TestResourcePropertyId.getUnknownResourcePropertyId())); - assertEquals(ResourceError.UNKNOWN_RESOURCE_PROPERTY_ID, contractException.getErrorType()); - - contractException = assertThrows(ContractException.class, () -> resourceInitialData.getResourcePropertyValue(resourceId, TestResourcePropertyId.ResourceProperty_5_1_INTEGER_IMMUTABLE)); - assertEquals(ResourceError.UNKNOWN_RESOURCE_PROPERTY_ID, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "getPersonResourceLevel", args = { PersonId.class, ResourceId.class }) - public void testGetPersonResourceLevel() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2902745806851600371L); - - // add the resources - ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); - for (TestResourceId testResourceId : TestResourceId.values()) { - builder.addResource(testResourceId); - } - - Map expectedValues = new LinkedHashMap<>(); - - // add up to 30 people - Set people = new LinkedHashSet<>(); - for (int i = 0; i < 30; i++) { - people.add(new PersonId(randomGenerator.nextInt())); - } - assertTrue(people.size() > 20); - for (PersonId personId : people) { - for (TestResourceId testResourceId : TestResourceId.values()) { - MultiKey multiKey = new MultiKey(personId, testResourceId); - MutableInteger mutableInteger = new MutableInteger(); - expectedValues.put(multiKey, mutableInteger); - if (randomGenerator.nextBoolean()) { - int amount = randomGenerator.nextInt(10); - builder.setPersonResourceLevel(personId, testResourceId, amount); - mutableInteger.setValue(amount); - } - } - } - - ResourcesPluginData resourceInitialData = builder.build(); - - for (PersonId personId : people) { - for (TestResourceId testResourceId : TestResourceId.values()) { - MultiKey multiKey = new MultiKey(personId, testResourceId); - MutableInteger mutableInteger = expectedValues.get(multiKey); - int expectedAmount = mutableInteger.getValue(); - Long personResourceLevel = resourceInitialData.getPersonResourceLevel(personId, testResourceId); - assertEquals(expectedAmount, personResourceLevel); - } - } - - // precondition tests - PersonId personId = new PersonId(0); - ResourceId resourceId = TestResourceId.RESOURCE_5; - - // if the person id is null - ContractException contractException = assertThrows(ContractException.class, () -> resourceInitialData.getPersonResourceLevel(null, resourceId)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - - // if the resource id is null - contractException = assertThrows(ContractException.class, () -> resourceInitialData.getPersonResourceLevel(personId, null)); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - - // if the resource id is unknown - contractException = assertThrows(ContractException.class, () -> resourceInitialData.getPersonResourceLevel(personId, TestResourceId.getUnknownResourceId())); - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "getResourceIds", args = {}) - public void testGetResourceIds() { - - ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); - Set expectedResourceIds = new LinkedHashSet<>(); - for (TestResourceId testResourceId : TestResourceId.values()) { - builder.addResource(testResourceId); - expectedResourceIds.add(testResourceId); - } - ResourcesPluginData resourceInitialData = builder.build(); - assertEquals(expectedResourceIds, resourceInitialData.getResourceIds()); - - } - - @Test - @UnitTestMethod(name = "getRegionResourceLevel", args = { RegionId.class, ResourceId.class }) - public void testGetRegionResourceLevel() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6794457915874374469L); - - // add the resources - ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); - for (TestResourceId testResourceId : TestResourceId.values()) { - builder.addResource(testResourceId); - } - - Map expectedValues = new LinkedHashMap<>(); - - for (TestRegionId testRegionId : TestRegionId.values()) { - for (TestResourceId testResourceId : TestResourceId.values()) { - MultiKey multiKey = new MultiKey(testRegionId, testResourceId); - MutableInteger mutableInteger = new MutableInteger(); - expectedValues.put(multiKey, mutableInteger); - if (randomGenerator.nextBoolean()) { - int amount = randomGenerator.nextInt(10); - builder.setRegionResourceLevel(testRegionId, testResourceId, amount); - mutableInteger.setValue(amount); - } - } - } - - ResourcesPluginData resourceInitialData = builder.build(); - - for (TestRegionId testRegionId : TestRegionId.values()) { - for (TestResourceId testResourceId : TestResourceId.values()) { - MultiKey multiKey = new MultiKey(testRegionId, testResourceId); - MutableInteger mutableInteger = expectedValues.get(multiKey); - int expectedAmount = mutableInteger.getValue(); - Long regionResourceLevel = resourceInitialData.getRegionResourceLevel(testRegionId, testResourceId); - assertEquals(expectedAmount, regionResourceLevel); - } - } - - // precondition tests - RegionId regionId = TestRegionId.REGION_3; - ResourceId resourceId = TestResourceId.RESOURCE_5; - - // if the region id is null - ContractException contractException = assertThrows(ContractException.class, () -> resourceInitialData.getRegionResourceLevel(null, resourceId)); - assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); - - // if the resource id is null - contractException = assertThrows(ContractException.class, () -> resourceInitialData.getRegionResourceLevel(regionId, null)); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - - // if the resource id is unknown - contractException = assertThrows(ContractException.class, () -> resourceInitialData.getRegionResourceLevel(regionId, TestResourceId.getUnknownResourceId())); - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "getPersonResourceTimeTrackingPolicy", args = { ResourceId.class }) - public void testGetPersonResourceTimeTrackingPolicy() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2435473102993121457L); - - Map expectedValues = new LinkedHashMap<>(); - - ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); - for (TestResourceId testResourceId : TestResourceId.values()) { - builder.addResource(testResourceId); - Set testResourcePropertyIds = TestResourcePropertyId.getTestResourcePropertyIds(testResourceId); - for (TestResourcePropertyId testResourcePropertyId : testResourcePropertyIds) { - MultiKey multiKey = new MultiKey(testResourceId, testResourcePropertyId); - PropertyDefinition propertyDefinition = testResourcePropertyId.getPropertyDefinition(); - expectedValues.put(multiKey, propertyDefinition.getDefaultValue().get()); - builder.defineResourceProperty(testResourceId, testResourcePropertyId, propertyDefinition); - if (randomGenerator.nextBoolean()) { - Object value = testResourcePropertyId.getRandomPropertyValue(randomGenerator); - builder.setResourcePropertyValue(testResourceId, testResourcePropertyId, value); - expectedValues.put(multiKey, value); - } - } - } - - ResourcesPluginData resourceInitialData = builder.build(); - - for (TestResourceId testResourceId : TestResourceId.values()) { - Set testResourcePropertyIds = TestResourcePropertyId.getTestResourcePropertyIds(testResourceId); - for (TestResourcePropertyId testResourcePropertyId : testResourcePropertyIds) { - MultiKey multiKey = new MultiKey(testResourceId, testResourcePropertyId); - Object expectedValue = expectedValues.get(multiKey); - Object actualValue = resourceInitialData.getResourcePropertyValue(testResourceId, testResourcePropertyId); - assertEquals(expectedValue, actualValue); - } - } - - // precondition tests - ResourceId resourceId = TestResourceId.RESOURCE_3; - ResourcePropertyId resourcePropertyId = TestResourcePropertyId.ResourceProperty_2_2_INTEGER_MUTABLE; - - // if the resource id is null - ContractException contractException = assertThrows(ContractException.class, () -> builder.setResourcePropertyValue(null, resourcePropertyId, 5)); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - - // if the resource property id is null - contractException = assertThrows(ContractException.class, () -> builder.setResourcePropertyValue(resourceId, null, 5)); - assertEquals(ResourceError.NULL_RESOURCE_PROPERTY_ID, contractException.getErrorType()); - - // if the resource property value is null - contractException = assertThrows(ContractException.class, () -> builder.setResourcePropertyValue(resourceId, null, 5)); - assertEquals(ResourceError.NULL_RESOURCE_PROPERTY_ID, contractException.getErrorType()); - - // if the resource property value was previously assigned - contractException = assertThrows(ContractException.class, () -> { - ResourcesPluginData.builder().setResourcePropertyValue(resourceId, resourcePropertyId, 5).setResourcePropertyValue(resourceId, resourcePropertyId, 5); - }); - assertEquals(ResourceError.DUPLICATE_RESOURCE_PROPERTY_VALUE_ASSIGNMENT, contractException.getErrorType()); - - } - - @Test - @UnitTestMethod(name = "getPersonIds", args = {}) - public void testGetPersonIds() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1188005474782684784L); - - // add the resources - ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); - for (TestResourceId testResourceId : TestResourceId.values()) { - builder.addResource(testResourceId); - } - - Map expectedValues = new LinkedHashMap<>(); - - // add up to 30 people - Set people = new LinkedHashSet<>(); - for (int i = 0; i < 30; i++) { - people.add(new PersonId(randomGenerator.nextInt())); - } - assertTrue(people.size() > 20); - - Set expectedPeople = new LinkedHashSet<>(); - - for (PersonId personId : people) { - for (TestResourceId testResourceId : TestResourceId.values()) { - MultiKey multiKey = new MultiKey(personId, testResourceId); - MutableInteger mutableInteger = new MutableInteger(); - expectedValues.put(multiKey, mutableInteger); - if (randomGenerator.nextBoolean()) { - int amount = randomGenerator.nextInt(10); - builder.setPersonResourceLevel(personId, testResourceId, amount); - expectedPeople.add(personId); - mutableInteger.setValue(amount); - } - } - } - - ResourcesPluginData resourceInitialData = builder.build(); - - assertEquals(expectedPeople, resourceInitialData.getPersonIds()); - - } - - @Test - @UnitTestMethod(name = "getRegionIds", args = {}) - public void testGetRegionIds() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7912521358724932418L); - - // add the resources - ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); - for (TestResourceId testResourceId : TestResourceId.values()) { - builder.addResource(testResourceId); - } - - Set expectedRegionIds = new LinkedHashSet<>(); - for (TestRegionId testRegionId : TestRegionId.values()) { - - for (TestResourceId testResourceId : TestResourceId.values()) { - if (randomGenerator.nextBoolean()) { - int amount = randomGenerator.nextInt(10); - builder.setRegionResourceLevel(testRegionId, testResourceId, amount); - expectedRegionIds.add(testRegionId); - } - } - } - - ResourcesPluginData resourceInitialData = builder.build(); - - assertEquals(expectedRegionIds, resourceInitialData.getRegionIds()); - } - - @Test - @UnitTestMethod(name = "getCloneBuilder", args = {}) - public void testGetCloneBuilder() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7644775230297230691L); - ResourcesPluginData.Builder pluginDataBuilder = ResourcesPluginData.builder(); - - for (TestResourceId testResourceId : TestResourceId.values()) { - TimeTrackingPolicy timeTrackingPolicy = TimeTrackingPolicy.DO_NOT_TRACK_TIME; - if (randomGenerator.nextBoolean()) { - timeTrackingPolicy = TimeTrackingPolicy.TRACK_TIME; - } - pluginDataBuilder.setResourceTimeTracking(testResourceId, timeTrackingPolicy); - } - - for (TestResourceId testResourceId : TestResourceId.values()) { - pluginDataBuilder.addResource(testResourceId); - } - for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId.values()) { - pluginDataBuilder.defineResourceProperty(testResourcePropertyId.getTestResourceId(), testResourcePropertyId, testResourcePropertyId.getPropertyDefinition()); - } - - for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId.values()) { - if (randomGenerator.nextBoolean()) { - pluginDataBuilder.setResourcePropertyValue(testResourcePropertyId.getTestResourceId(), testResourcePropertyId, testResourcePropertyId.getRandomPropertyValue(randomGenerator)); - } - } - - for (TestRegionId testRegionId : TestRegionId.values()) { - for (TestResourceId testResourceId : TestResourceId.values()) { - if (randomGenerator.nextBoolean()) { - long value = randomGenerator.nextInt(1000); - pluginDataBuilder.setRegionResourceLevel(testRegionId, testResourceId, value); - } - } - } - - for (int i = 0; i < 10; i++) { - PersonId personId = new PersonId(i * i); - for (TestResourceId testResourceId : TestResourceId.values()) { - if (randomGenerator.nextBoolean()) { - long value = randomGenerator.nextInt(5); - pluginDataBuilder.setPersonResourceLevel(personId, testResourceId, value); - } - } - } - - ResourcesPluginData resourcesPluginData = pluginDataBuilder.build(); - - PluginData pluginData = resourcesPluginData.getCloneBuilder().build(); - - // show that the plugin data is of the expected type - assertTrue(pluginData instanceof ResourcesPluginData); - - ResourcesPluginData cloneResourcesPluginData = (ResourcesPluginData) pluginData; - - assertEquals(resourcesPluginData.getResourceIds(), cloneResourcesPluginData.getResourceIds()); - - assertEquals(resourcesPluginData.getPersonIds(), cloneResourcesPluginData.getPersonIds()); - - for (ResourceId resourceId : resourcesPluginData.getResourceIds()) { - for (PersonId personId : resourcesPluginData.getPersonIds()) { - Long expectedLevel = resourcesPluginData.getPersonResourceLevel(personId, resourceId); - Long actualLevel = cloneResourcesPluginData.getPersonResourceLevel(personId, resourceId); - assertEquals(expectedLevel, actualLevel); - } - } - - for (ResourceId resourceId : resourcesPluginData.getResourceIds()) { - TimeTrackingPolicy expectedPolicy = resourcesPluginData.getPersonResourceTimeTrackingPolicy(resourceId); - TimeTrackingPolicy actualPolicy = cloneResourcesPluginData.getPersonResourceTimeTrackingPolicy(resourceId); - assertEquals(expectedPolicy, actualPolicy); - } - for (ResourceId resourceId : resourcesPluginData.getResourceIds()) { - assertEquals(resourcesPluginData.getResourcePropertyIds(resourceId), cloneResourcesPluginData.getResourcePropertyIds(resourceId)); - } - - for (ResourceId resourceId : resourcesPluginData.getResourceIds()) { - for(ResourcePropertyId resourcePropertyId : resourcesPluginData.getResourcePropertyIds(resourceId)) { - Object expectedValue = resourcesPluginData.getResourcePropertyValue(resourceId, resourcePropertyId); - Object actualValue = cloneResourcesPluginData.getResourcePropertyValue(resourceId, resourcePropertyId); - assertEquals(expectedValue, actualValue); - } - } - - for (ResourceId resourceId : resourcesPluginData.getResourceIds()) { - for(ResourcePropertyId resourcePropertyId : resourcesPluginData.getResourcePropertyIds(resourceId)) { - PropertyDefinition expectedPropertyDefinition = resourcesPluginData.getResourcePropertyDefinition(resourceId, resourcePropertyId); - PropertyDefinition actualPropertyDefinition = cloneResourcesPluginData.getResourcePropertyDefinition(resourceId, resourcePropertyId); - assertEquals(expectedPropertyDefinition, actualPropertyDefinition); - } - } - - assertEquals(resourcesPluginData.getRegionIds(), cloneResourcesPluginData.getRegionIds()); - - for(RegionId regionId : resourcesPluginData.getRegionIds()) { - for (ResourceId resourceId : resourcesPluginData.getResourceIds()) { - Long expectedLevel = resourcesPluginData.getRegionResourceLevel(regionId, resourceId); - Long actualLevel = cloneResourcesPluginData.getRegionResourceLevel(regionId, resourceId); - assertEquals(expectedLevel, actualLevel); - } - } - - - - - - } - -} diff --git a/gcm3/src/test/java/plugins/resources/AT_ResourcesPluginId.java b/gcm3/src/test/java/plugins/resources/AT_ResourcesPluginId.java deleted file mode 100644 index ae944fdc5..000000000 --- a/gcm3/src/test/java/plugins/resources/AT_ResourcesPluginId.java +++ /dev/null @@ -1,13 +0,0 @@ -package plugins.resources; - -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import tools.annotations.UnitTest; - -@UnitTest(target = ResourcesPluginId.class) -public class AT_ResourcesPluginId { - - public void test() { - assertNotNull(ResourcesPluginId.PLUGIN_ID); - } -} diff --git a/gcm3/src/test/java/plugins/resources/actors/AT_ResourcePropertyReport.java b/gcm3/src/test/java/plugins/resources/actors/AT_ResourcePropertyReport.java deleted file mode 100644 index edacdcf99..000000000 --- a/gcm3/src/test/java/plugins/resources/actors/AT_ResourcePropertyReport.java +++ /dev/null @@ -1,222 +0,0 @@ -package plugins.resources.actors; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.Experiment; -import nucleus.Plugin; -import nucleus.testsupport.testplugin.ExperimentPlanCompletionObserver; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import plugins.people.PeoplePlugin; -import plugins.people.PeoplePluginData; -import plugins.people.support.PersonId; -import plugins.regions.RegionsPlugin; -import plugins.regions.RegionsPluginData; -import plugins.regions.testsupport.TestRegionId; -import plugins.reports.ReportsPlugin; -import plugins.reports.ReportsPluginData; -import plugins.reports.support.ReportHeader; -import plugins.reports.support.ReportId; -import plugins.reports.support.ReportItem; -import plugins.reports.support.SimpleReportId; -import plugins.reports.testsupport.TestReportItemOutputConsumer; -import plugins.resources.ResourcesPlugin; -import plugins.resources.ResourcesPluginData; -import plugins.resources.datamanagers.ResourcesDataManager; -import plugins.resources.testsupport.TestResourceId; -import plugins.resources.testsupport.TestResourcePropertyId; -import plugins.stochastics.StochasticsPlugin; -import plugins.stochastics.StochasticsPluginData; -import plugins.util.properties.PropertyDefinition; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.random.RandomGeneratorProvider; - -@UnitTest(target = ResourcePropertyReport.class) -public class AT_ResourcePropertyReport { - - @Test - @UnitTestMethod(name = "init", args = {}) - public void testInit() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8914112012010329946L); - int initialPopulation = 20; - - // create a list of people - List people = new ArrayList<>(); - for (int i = 0; i < initialPopulation; i++) { - people.add(new PersonId(i)); - } - - Experiment.Builder builder = Experiment.builder(); - - // add the resources plugin - ResourcesPluginData.Builder resourcesBuilder = ResourcesPluginData.builder(); - - for (TestResourceId testResourceId : TestResourceId.values()) { - resourcesBuilder.addResource(testResourceId); - resourcesBuilder.setResourceTimeTracking(testResourceId, testResourceId.getTimeTrackingPolicy()); - } - - for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId.values()) { - TestResourceId testResourceId = testResourcePropertyId.getTestResourceId(); - PropertyDefinition propertyDefinition = testResourcePropertyId.getPropertyDefinition(); - Object propertyValue = testResourcePropertyId.getRandomPropertyValue(randomGenerator); - resourcesBuilder.defineResourceProperty(testResourceId, testResourcePropertyId, propertyDefinition); - resourcesBuilder.setResourcePropertyValue(testResourceId, testResourcePropertyId, propertyValue); - } - ResourcesPluginData resourcesPluginData = resourcesBuilder.build(); - Plugin resourcesPlugin = ResourcesPlugin.getResourcesPlugin(resourcesPluginData); - builder.addPlugin(resourcesPlugin); - - // add the people plugin - - PeoplePluginData.Builder peopleBuilder = PeoplePluginData.builder(); - - for (PersonId personId : people) { - peopleBuilder.addPersonId(personId); - } - PeoplePluginData peoplePluginData = peopleBuilder.build(); - Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(peoplePluginData); - builder.addPlugin(peoplePlugin); - - // add the regions plugin - RegionsPluginData.Builder regionsBuilder = RegionsPluginData.builder(); - for (TestRegionId testRegionId : TestRegionId.values()) { - regionsBuilder.addRegion(testRegionId); - } - for (PersonId personId : people) { - regionsBuilder.setPersonRegion(personId, TestRegionId.getRandomRegionId(randomGenerator)); - } - RegionsPluginData regionsPluginData = regionsBuilder.build(); - Plugin regionPlugin = RegionsPlugin.getRegionsPlugin(regionsPluginData); - builder.addPlugin(regionPlugin); - - // add the report plugin - - ReportsPluginData.Builder reportsBuilder = ReportsPluginData.builder(); - reportsBuilder.addReport(() -> new ResourcePropertyReport(REPORT_ID)::init); - ReportsPluginData reportsPluginData = reportsBuilder.build(); - Plugin reportPlugin = ReportsPlugin.getReportPlugin(reportsPluginData); - builder.addPlugin(reportPlugin); - - - // add the stochastics plugin - StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder().setSeed(randomGenerator.nextLong()).build(); - Plugin stochasticsPlugin = StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData); - builder.addPlugin(stochasticsPlugin); - - - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // // create an agent and have it assign various resource properties at - // // various times - - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0.0, (c) -> { - /* - * note that this is time 0 and should show that property initial - * values are still reported correctly - */ - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_3, TestResourcePropertyId.ResourceProperty_3_2_STRING_MUTABLE, "A"); - })); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1.0, (c) -> { - // two settings of the same property - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_2, TestResourcePropertyId.ResourceProperty_2_2_INTEGER_MUTABLE, 45); - resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_1, TestResourcePropertyId.ResourceProperty_1_3_DOUBLE_MUTABLE, 36.7); - })); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2.0, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_4, TestResourcePropertyId.ResourceProperty_4_1_BOOLEAN_MUTABLE, true); - resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_2, TestResourcePropertyId.ResourceProperty_2_1_BOOLEAN_MUTABLE, false); - })); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(3.0, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_4, TestResourcePropertyId.ResourceProperty_4_1_BOOLEAN_MUTABLE, true); - - - // note the duplicated value - resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_1, TestResourcePropertyId.ResourceProperty_1_3_DOUBLE_MUTABLE, 2.5); - resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_1, TestResourcePropertyId.ResourceProperty_1_3_DOUBLE_MUTABLE, 2.5); - - - // and now a third setting of the same property to a new value - resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_1, TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE, 100); - resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_1, TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE, 60); - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - builder.addPlugin(testPlugin); - - // build and execute the engine - TestReportItemOutputConsumer testReportItemOutputConsumer = new TestReportItemOutputConsumer(); - ExperimentPlanCompletionObserver experimentPlanCompletionObserver = new ExperimentPlanCompletionObserver(); - builder.addOutputHandler(testReportItemOutputConsumer::init); - builder.addOutputHandler(experimentPlanCompletionObserver::init); - builder.reportProgressToConsole(false); - builder.build().execute(); - - // show that all actions were executed - assertTrue(experimentPlanCompletionObserver.getActionCompletionReport(0).get().isComplete()); - - /* - * Collect the expected report items. Note that order does not matter. * - */ - Map expectedMap = new LinkedHashMap<>(); - expectedMap.put(getReportItem(0.0, TestResourceId.RESOURCE_1, TestResourcePropertyId.ResourceProperty_1_1_BOOLEAN_MUTABLE, true),1); - expectedMap.put(getReportItem(0.0, TestResourceId.RESOURCE_1, TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE, 1673029105),1); - expectedMap.put(getReportItem(0.0, TestResourceId.RESOURCE_1, TestResourcePropertyId.ResourceProperty_1_3_DOUBLE_MUTABLE, 0.9762970538942173),1); - expectedMap.put(getReportItem(0.0, TestResourceId.RESOURCE_2, TestResourcePropertyId.ResourceProperty_2_1_BOOLEAN_MUTABLE, true),1); - expectedMap.put(getReportItem(0.0, TestResourceId.RESOURCE_2, TestResourcePropertyId.ResourceProperty_2_2_INTEGER_MUTABLE, 1818034648),1); - expectedMap.put(getReportItem(0.0, TestResourceId.RESOURCE_3, TestResourcePropertyId.ResourceProperty_3_1_BOOLEAN_MUTABLE, true),1); - expectedMap.put(getReportItem(0.0, TestResourceId.RESOURCE_3, TestResourcePropertyId.ResourceProperty_3_2_STRING_MUTABLE, 319183829),1); - expectedMap.put(getReportItem(0.0, TestResourceId.RESOURCE_4, TestResourcePropertyId.ResourceProperty_4_1_BOOLEAN_MUTABLE, true),1); - expectedMap.put(getReportItem(0.0, TestResourceId.RESOURCE_5, TestResourcePropertyId.ResourceProperty_5_1_INTEGER_IMMUTABLE, 704893369),1); - expectedMap.put(getReportItem(0.0, TestResourceId.RESOURCE_5, TestResourcePropertyId.ResourceProperty_5_1_DOUBLE_IMMUTABLE, 0.7547798894049567),1); - expectedMap.put(getReportItem(0.0, TestResourceId.RESOURCE_3, TestResourcePropertyId.ResourceProperty_3_2_STRING_MUTABLE, "A"),1); - expectedMap.put(getReportItem(1.0, TestResourceId.RESOURCE_2, TestResourcePropertyId.ResourceProperty_2_2_INTEGER_MUTABLE, 45),1); - expectedMap.put(getReportItem(1.0, TestResourceId.RESOURCE_1, TestResourcePropertyId.ResourceProperty_1_3_DOUBLE_MUTABLE, 36.7),1); - expectedMap.put(getReportItem(2.0, TestResourceId.RESOURCE_4, TestResourcePropertyId.ResourceProperty_4_1_BOOLEAN_MUTABLE, true),1); - expectedMap.put(getReportItem(2.0, TestResourceId.RESOURCE_2, TestResourcePropertyId.ResourceProperty_2_1_BOOLEAN_MUTABLE, false),1); - expectedMap.put(getReportItem(3.0, TestResourceId.RESOURCE_4, TestResourcePropertyId.ResourceProperty_4_1_BOOLEAN_MUTABLE, true),1); - expectedMap.put(getReportItem(3.0, TestResourceId.RESOURCE_1, TestResourcePropertyId.ResourceProperty_1_3_DOUBLE_MUTABLE, 2.5),2); - expectedMap.put(getReportItem(3.0, TestResourceId.RESOURCE_1, TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE, 100),1); - expectedMap.put(getReportItem(3.0, TestResourceId.RESOURCE_1, TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE, 60),1); - - Map actualMap = testReportItemOutputConsumer.getReportItems().get(0); - - - assertEquals(expectedMap, actualMap); - - } - - private static ReportItem getReportItem(Object... values) { - ReportItem.Builder builder = ReportItem.builder(); - builder.setReportId(REPORT_ID); - builder.setReportHeader(REPORT_HEADER); - for (Object value : values) { - builder.addValue(value); - } - return builder.build(); - } - - private static final ReportId REPORT_ID = new SimpleReportId("resource property report"); - - private static final ReportHeader REPORT_HEADER = ReportHeader.builder().add("time").add("resource").add("property").add("value").build(); -} diff --git a/gcm3/src/test/java/plugins/resources/datamanagers/AT_ResourcesDataManager.java b/gcm3/src/test/java/plugins/resources/datamanagers/AT_ResourcesDataManager.java deleted file mode 100644 index 7144c1f92..000000000 --- a/gcm3/src/test/java/plugins/resources/datamanagers/AT_ResourcesDataManager.java +++ /dev/null @@ -1,2767 +0,0 @@ -package plugins.resources.datamanagers; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.ActorContext; -import nucleus.EventLabeler; -import nucleus.NucleusError; -import nucleus.Plugin; -import nucleus.Simulation; -import nucleus.Simulation.Builder; -import nucleus.testsupport.testplugin.ScenarioPlanCompletionObserver; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestError; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import plugins.people.PeoplePlugin; -import plugins.people.PeoplePluginData; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.BulkPersonConstructionData; -import plugins.people.support.PersonConstructionData; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import plugins.regions.RegionsPlugin; -import plugins.regions.RegionsPluginData; -import plugins.regions.datamanagers.RegionsDataManager; -import plugins.regions.support.RegionError; -import plugins.regions.support.RegionId; -import plugins.regions.testsupport.TestRegionId; -import plugins.resources.ResourcesPlugin; -import plugins.resources.ResourcesPluginData; -import plugins.resources.events.PersonResourceUpdateEvent; -import plugins.resources.events.RegionResourceUpdateEvent; -import plugins.resources.events.ResourcePropertyUpdateEvent; -import plugins.resources.support.ResourceError; -import plugins.resources.support.ResourceId; -import plugins.resources.support.ResourceInitialization; -import plugins.resources.support.ResourcePropertyId; -import plugins.resources.testsupport.ResourcesActionSupport; -import plugins.resources.testsupport.TestResourceId; -import plugins.resources.testsupport.TestResourcePropertyId; -import plugins.stochastics.StochasticsDataManager; -import plugins.stochastics.StochasticsPlugin; -import plugins.stochastics.StochasticsPluginData; -import plugins.util.properties.PropertyDefinition; -import plugins.util.properties.PropertyError; -import plugins.util.properties.TimeTrackingPolicy; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; -import util.random.RandomGeneratorProvider; -import util.wrappers.MultiKey; -import util.wrappers.MutableDouble; -import util.wrappers.MutableInteger; -import util.wrappers.MutableObject; - -@UnitTest(target = ResourcesDataManager.class) -public final class AT_ResourcesDataManager { - @Test - @UnitTestMethod(name = "init", args = {}) - public void testPersonImminentRemovalEvent() { - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // Have an actor add a person with resources and then remove that person - - MutableObject mutablePersonId = new MutableObject<>(); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - - // create a person and set their resources - PersonConstructionData personConstructionData = PersonConstructionData.builder().add(TestRegionId.REGION_1).build(); - PersonId personId = peopleDataManager.addPerson(personConstructionData); - mutablePersonId.setValue(personId); - - for (TestResourceId testResourceId : TestResourceId.values()) { - resourcesDataManager.addResourceToRegion(testResourceId, TestRegionId.REGION_1, 100); - resourcesDataManager.transferResourceToPersonFromRegion(testResourceId, personId, 1); - } - - peopleDataManager.removePerson(personId); - - // show that the person still exists - assertTrue(peopleDataManager.personExists(personId)); - })); - - // Have the actor show that the person does not exist and there are no - // resources for that person. This is done at the same time as the - // person removal, but due to ordering will execute after the person is - // fully eliminated - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - PersonId personId = mutablePersonId.getValue(); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - - assertFalse(peopleDataManager.personExists(personId)); - - for (TestResourceId testResourceId : TestResourceId.values()) { - assertThrows(ContractException.class, () -> resourcesDataManager.getPersonResourceLevel(testResourceId, personId)); - } - - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - ResourcesActionSupport.testConsumers(0, 5231820238498733928L, testPlugin); - - } - - @Test - @UnitTestMethod(name = "getPeopleWithoutResource", args = { ResourceId.class }) - public void testGetPeopleWithoutResource() { - - ResourcesActionSupport.testConsumer(100, 3641510187112920884L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - Set expectedPeople = new LinkedHashSet<>(); - // give about half of the people the resource - for (PersonId personId : peopleDataManager.getPeople()) { - if (randomGenerator.nextBoolean()) { - RegionId regionId = regionsDataManager.getPersonRegion(personId); - resourcesDataManager.addResourceToRegion(TestResourceId.RESOURCE_5, regionId, 5); - resourcesDataManager.transferResourceToPersonFromRegion(TestResourceId.RESOURCE_5, personId, 5); - } else { - expectedPeople.add(personId); - } - } - // show that those who did not get the resource are returned - List actualPeople = resourcesDataManager.getPeopleWithoutResource(TestResourceId.RESOURCE_5); - assertEquals(expectedPeople.size(), actualPeople.size()); - assertEquals(expectedPeople, new LinkedHashSet<>(actualPeople)); - - }); - - /* precondition test: if the resource id is null */ - ResourcesActionSupport.testConsumer(100, 3473450607674582992L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.getPeopleWithoutResource(null)); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the resource id is unknown */ - ResourcesActionSupport.testConsumer(100, 1143781261828756924L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.getPeopleWithoutResource(TestResourceId.getUnknownResourceId())); - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestConstructor(args = { ResourcesPluginData.class }) - public void testConstructor() { - ContractException contractException = assertThrows(ContractException.class, () -> new ResourcesDataManager(null)); - assertEquals(ResourceError.NULL_RESOURCE_PLUGIN_DATA, contractException.getErrorType()); - } - - @Test - @UnitTestMethod(name = "expandCapacity", args = { int.class }) - public void testExpandCapacity() { - ResourcesActionSupport.testConsumer(100, 9107703044214388523L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.expandCapacity(-1)); - assertEquals(PersonError.NEGATIVE_GROWTH_PROJECTION, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "getPeopleWithResource", args = { ResourceId.class }) - public void testGetPeopleWithResource() { - - ResourcesActionSupport.testConsumer(100, 1030108367649001208L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - Set expectedPeople = new LinkedHashSet<>(); - // give about half of the people the resource - for (PersonId personId : peopleDataManager.getPeople()) { - if (randomGenerator.nextBoolean()) { - RegionId regionId = regionsDataManager.getPersonRegion(personId); - resourcesDataManager.addResourceToRegion(TestResourceId.RESOURCE_5, regionId, 5); - resourcesDataManager.transferResourceToPersonFromRegion(TestResourceId.RESOURCE_5, personId, 5); - expectedPeople.add(personId); - } - } - // show that those who did not get the resource are returned - List actualPeople = resourcesDataManager.getPeopleWithResource(TestResourceId.RESOURCE_5); - assertEquals(expectedPeople.size(), actualPeople.size()); - assertEquals(expectedPeople, new LinkedHashSet<>(actualPeople)); - - }); - - /* precondition test: if the resource id is null */ - ResourcesActionSupport.testConsumer(100, 319392144027980087L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.getPeopleWithoutResource(null)); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the resource id is unknown */ - ResourcesActionSupport.testConsumer(100, 8576038889544967878L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.getPeopleWithoutResource(TestResourceId.getUnknownResourceId())); - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "getPersonResourceLevel", args = { ResourceId.class, PersonId.class }) - public void testGetPersonResourceLevel() { - - ResourcesActionSupport.testConsumer(20, 110987310555566746L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - - List people = peopleDataManager.getPeople(); - - // give random amounts of resource to random people - for (int i = 0; i < 1000; i++) { - PersonId personId = people.get(randomGenerator.nextInt(people.size())); - ResourceId resourceId = TestResourceId.getRandomResourceId(randomGenerator); - int amount = randomGenerator.nextInt(5); - long expectedLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); - expectedLevel += amount; - - RegionId regionId = regionsDataManager.getPersonRegion(personId); - resourcesDataManager.addResourceToRegion(resourceId, regionId, amount); - resourcesDataManager.transferResourceToPersonFromRegion(resourceId, personId, amount); - long actualLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); - assertEquals(expectedLevel, actualLevel); - } - - }); - - /* precondition test: if the resource id is null */ - ResourcesActionSupport.testConsumer(20, 5173387308794126450L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.getPersonResourceLevel(null, new PersonId(0))); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - }); - /* precondition test: if the resource id is unknown */ - ResourcesActionSupport.testConsumer(20, 5756572221517144312L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.getPersonResourceLevel(TestResourceId.getUnknownResourceId(), new PersonId(0))); - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - - }); - - /* precondition test: if the person id null */ - ResourcesActionSupport.testConsumer(20, 1392115005391991861L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.getPersonResourceLevel(TestResourceId.RESOURCE_1, null)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - }); - /* precondition test: if the person id has a negative value */ - ResourcesActionSupport.testConsumer(20, 3500853843230804485L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.getPersonResourceLevel(TestResourceId.RESOURCE_1, new PersonId(-1))); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "getPersonResourceTime", args = { ResourceId.class, PersonId.class }) - public void testGetPersonResourceTime() { - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - Map expectedTimes = new LinkedHashMap<>(); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - // establish data views - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - - // establish the people and resources - Set resourceIds = resourcesDataManager.getResourceIds(); - List people = peopleDataManager.getPeople(); - - // initialize the expected times - for (PersonId personId : people) { - for (ResourceId resourceId : resourceIds) { - expectedTimes.put(new MultiKey(personId, resourceId), new MutableDouble()); - } - } - - // show that there are at least two resources that are being time - // tracked - int trackedResourceCount = 0; - for (ResourceId resourceId : resourceIds) { - TimeTrackingPolicy personResourceTimeTrackingPolicy = resourcesDataManager.getPersonResourceTimeTrackingPolicy(resourceId); - if (personResourceTimeTrackingPolicy == TimeTrackingPolicy.TRACK_TIME) { - trackedResourceCount++; - } - } - assertTrue(trackedResourceCount > 1); - - // give random amounts of resource to random people - for (int i = 0; i < people.size(); i++) { - PersonId personId = people.get(randomGenerator.nextInt(people.size())); - ResourceId resourceId = TestResourceId.getRandomResourceId(randomGenerator); - int amount = randomGenerator.nextInt(5) + 1; - - RegionId regionId = regionsDataManager.getPersonRegion(personId); - resourcesDataManager.addResourceToRegion(resourceId, regionId, amount); - resourcesDataManager.transferResourceToPersonFromRegion(resourceId, personId, amount); - - expectedTimes.get(new MultiKey(personId, resourceId)).setValue(c.getTime()); - } - - })); - - // make more resource updates at time 1 - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - // establish data views - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - - // establish the people and resources - List people = peopleDataManager.getPeople(); - - // give random amounts of resource to random people - for (int i = 0; i < people.size(); i++) { - PersonId personId = people.get(randomGenerator.nextInt(people.size())); - ResourceId resourceId = TestResourceId.getRandomResourceId(randomGenerator); - int amount = randomGenerator.nextInt(5) + 1; - - RegionId regionId = regionsDataManager.getPersonRegion(personId); - resourcesDataManager.addResourceToRegion(resourceId, regionId, amount); - resourcesDataManager.transferResourceToPersonFromRegion(resourceId, personId, amount); - - expectedTimes.get(new MultiKey(personId, resourceId)).setValue(c.getTime()); - } - - })); - - // make more resource updates at time 2 - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { - // establish data views - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - - // establish the people and resources - List people = peopleDataManager.getPeople(); - - // give random amounts of resource to random people - - for (int i = 0; i < people.size(); i++) { - PersonId personId = people.get(randomGenerator.nextInt(people.size())); - ResourceId resourceId = TestResourceId.getRandomResourceId(randomGenerator); - int amount = randomGenerator.nextInt(5) + 1; - RegionId regionId = regionsDataManager.getPersonRegion(personId); - resourcesDataManager.addResourceToRegion(resourceId, regionId, amount); - resourcesDataManager.transferResourceToPersonFromRegion(resourceId, personId, amount); - expectedTimes.get(new MultiKey(personId, resourceId)).setValue(c.getTime()); - } - - })); - - // test the person resource times - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(3, (c) -> { - // establish data views - - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - - // show that the person resource times match expectations - int actualAssertionsCount = 0; - for (MultiKey multiKey : expectedTimes.keySet()) { - PersonId personId = multiKey.getKey(0); - ResourceId resourceId = multiKey.getKey(1); - TimeTrackingPolicy personResourceTimeTrackingPolicy = resourcesDataManager.getPersonResourceTimeTrackingPolicy(resourceId); - if (personResourceTimeTrackingPolicy == TimeTrackingPolicy.TRACK_TIME) { - double expectedTime = expectedTimes.get(multiKey).getValue(); - double actualTime = resourcesDataManager.getPersonResourceTime(resourceId, personId); - assertEquals(expectedTime, actualTime); - actualAssertionsCount++; - } - } - /* - * Show that the number of time values that were tested is equal to - * the size of the population times the number of time-tracked - * resources - */ - - int trackedResourceCount = 0; - for (ResourceId resourceId : resourcesDataManager.getResourceIds()) { - TimeTrackingPolicy personResourceTimeTrackingPolicy = resourcesDataManager.getPersonResourceTimeTrackingPolicy(resourceId); - if (personResourceTimeTrackingPolicy == TimeTrackingPolicy.TRACK_TIME) { - trackedResourceCount++; - } - } - - int expectedAssertionsCount = peopleDataManager.getPopulationCount() * trackedResourceCount; - assertEquals(expectedAssertionsCount, actualAssertionsCount); - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - ResourcesActionSupport.testConsumers(30, 3274189520478045515L, testPlugin); - - /* - * precondition test: if the assignment times for the resource are not - * tracked - */ - ResourcesActionSupport.testConsumer(30, 4631279382559646912L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.getPersonResourceTime(TestResourceId.RESOURCE_2, new PersonId(0))); - assertEquals(ResourceError.RESOURCE_ASSIGNMENT_TIME_NOT_TRACKED, contractException.getErrorType()); - }); - - /* precondition test: if the resource id is null */ - ResourcesActionSupport.testConsumer(30, 2409228447197751995L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.getPersonResourceTime(null, new PersonId(0))); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the resource id is unknown */ - ResourcesActionSupport.testConsumer(30, 6640524810334992305L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.getPersonResourceTime(TestResourceId.getUnknownResourceId(), new PersonId(0))); - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the person id null */ - ResourcesActionSupport.testConsumer(30, 6775179388362303664L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.getPersonResourceTime(TestResourceId.RESOURCE_1, null)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - }); - - /* precondition test: if the person id has a negative value */ - ResourcesActionSupport.testConsumer(30, 2970818265707036394L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.getPersonResourceTime(TestResourceId.RESOURCE_1, new PersonId(-1))); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "getPersonResourceTimeTrackingPolicy", args = { ResourceId.class }) - public void testGetPersonResourceTimeTrackingPolicy() { - - ResourcesActionSupport.testConsumer(5, 757175164544632409L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - for (TestResourceId testResourceId : TestResourceId.values()) { - TimeTrackingPolicy actualPolicy = resourcesDataManager.getPersonResourceTimeTrackingPolicy(testResourceId); - TimeTrackingPolicy expectedPolicy = testResourceId.getTimeTrackingPolicy(); - assertEquals(expectedPolicy, actualPolicy); - } - }); - - /* precondition test: if the resource id is null */ - ResourcesActionSupport.testConsumer(5, 1761534115327431429L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.getPersonResourceTimeTrackingPolicy(null)); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the resource id is unknown */ - ResourcesActionSupport.testConsumer(5, 7202590650313787556L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.getPersonResourceTimeTrackingPolicy(TestResourceId.getUnknownResourceId())); - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "getRegionResourceLevel", args = { RegionId.class, ResourceId.class }) - public void testGetRegionResourceLevel() { - - ResourcesActionSupport.testConsumer(20, 6606932435911201728L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - - List regionIds = new ArrayList<>(regionsDataManager.getRegionIds()); - - // give random amounts of resource to random regions - for (int i = 0; i < 1000; i++) { - RegionId regionId = regionIds.get(randomGenerator.nextInt(regionIds.size())); - ResourceId resourceId = TestResourceId.getRandomResourceId(randomGenerator); - int amount = randomGenerator.nextInt(5); - long expectedLevel = resourcesDataManager.getRegionResourceLevel(regionId, resourceId); - expectedLevel += amount; - resourcesDataManager.addResourceToRegion(resourceId, regionId, amount); - long actualLevel = resourcesDataManager.getRegionResourceLevel(regionId, resourceId); - assertEquals(expectedLevel, actualLevel); - } - - }); - - /* precondition test: if the resource id is null */ - ResourcesActionSupport.testConsumer(20, 1436454351032688103L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.getRegionResourceLevel(TestRegionId.REGION_1, null)); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the resource id is unknown */ - ResourcesActionSupport.testConsumer(20, 7954290176104108412L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.getRegionResourceLevel(TestRegionId.REGION_1, TestResourceId.getUnknownResourceId())); - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the region id null */ - ResourcesActionSupport.testConsumer(20, 936653403265146113L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.getRegionResourceLevel(null, TestResourceId.RESOURCE_1)); - assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); - }); - - /* precondition test: if the region id is unknown */ - ResourcesActionSupport.testConsumer(20, 8256630838791330328L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.getRegionResourceLevel(TestRegionId.getUnknownRegionId(), TestResourceId.RESOURCE_1)); - assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "getRegionResourceTime", args = { RegionId.class, ResourceId.class }) - public void testGetRegionResourceTime() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - Map expectedTimes = new LinkedHashMap<>(); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - // establish data views - - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - - // establish the people and resources - Set resourceIds = resourcesDataManager.getResourceIds(); - List regionIds = new ArrayList<>(regionsDataManager.getRegionIds()); - - // initialize the expected times - for (RegionId regionId : regionIds) { - for (ResourceId resourceId : resourceIds) { - expectedTimes.put(new MultiKey(regionId, resourceId), new MutableDouble()); - } - } - - // give random amounts of resource to random regions - for (int i = 0; i < regionIds.size(); i++) { - RegionId regionId = regionIds.get(randomGenerator.nextInt(regionIds.size())); - ResourceId resourceId = TestResourceId.getRandomResourceId(randomGenerator); - int amount = randomGenerator.nextInt(5) + 1; - resourcesDataManager.addResourceToRegion(resourceId, regionId, amount); - expectedTimes.get(new MultiKey(regionId, resourceId)).setValue(c.getTime()); - } - - })); - - // make more resource updates at time 1 - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - // establish data views - - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - - // establish the regions - List regionIds = new ArrayList<>(regionsDataManager.getRegionIds()); - - // give random amounts of resource to random regions - for (int i = 0; i < regionIds.size(); i++) { - RegionId regionId = regionIds.get(randomGenerator.nextInt(regionIds.size())); - ResourceId resourceId = TestResourceId.getRandomResourceId(randomGenerator); - int amount = randomGenerator.nextInt(5) + 1; - resourcesDataManager.addResourceToRegion(resourceId, regionId, amount); - expectedTimes.get(new MultiKey(regionId, resourceId)).setValue(c.getTime()); - } - - })); - - // make more resource updates at time 2 - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { - // establish data views - - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - // establish the regions - List regionIds = new ArrayList<>(regionsDataManager.getRegionIds()); - - // give random amounts of resource to random regions - for (int i = 0; i < regionIds.size(); i++) { - RegionId regionId = regionIds.get(randomGenerator.nextInt(regionIds.size())); - ResourceId resourceId = TestResourceId.getRandomResourceId(randomGenerator); - int amount = randomGenerator.nextInt(5) + 1; - resourcesDataManager.addResourceToRegion(resourceId, regionId, amount); - expectedTimes.get(new MultiKey(regionId, resourceId)).setValue(c.getTime()); - } - - })); - - // test the person resource times - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(3, (c) -> { - // establish data views - - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - - // show that the region resource times match expectations - int actualAssertionsCount = 0; - for (MultiKey multiKey : expectedTimes.keySet()) { - RegionId regionId = multiKey.getKey(0); - ResourceId resourceId = multiKey.getKey(1); - double expectedTime = expectedTimes.get(multiKey).getValue(); - double actualTime = resourcesDataManager.getRegionResourceTime(regionId, resourceId); - assertEquals(expectedTime, actualTime); - actualAssertionsCount++; - } - /* - * Show that the number of time values that were tested is equal to - * the size of the population times the number of resources - */ - int expectedAssertionsCount = regionsDataManager.getRegionIds().size() * resourcesDataManager.getResourceIds().size(); - assertEquals(expectedAssertionsCount, actualAssertionsCount); - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - ResourcesActionSupport.testConsumers(30, 6128764970683025350L, testPlugin); - - /* - * precondition test: if the assignment times for the resource are not - * tracked - */ - ResourcesActionSupport.testConsumer(30, 3888561557931148149L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.getPersonResourceTime(TestResourceId.RESOURCE_2, new PersonId(0))); - assertEquals(ResourceError.RESOURCE_ASSIGNMENT_TIME_NOT_TRACKED, contractException.getErrorType()); - }); - - /* precondition test: if the resource id is null */ - ResourcesActionSupport.testConsumer(30, 9045818580061726595L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.getPersonResourceTime(null, new PersonId(0))); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the resource id is unknown */ - ResourcesActionSupport.testConsumer(30, 5592254382530100326L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.getPersonResourceTime(TestResourceId.getUnknownResourceId(), new PersonId(0))); - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the person id null */ - ResourcesActionSupport.testConsumer(30, 1245016103076447355L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.getPersonResourceTime(TestResourceId.RESOURCE_1, null)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - }); - - /* precondition test: if the person id has a negative value */ - ResourcesActionSupport.testConsumer(30, 4010540244741787446L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.getPersonResourceTime(TestResourceId.RESOURCE_1, new PersonId(-1))); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "getResourceIds", args = {}) - public void testGetResourceIds() { - - ResourcesActionSupport.testConsumer(5, 2601236547109660988L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - - // show that the resource ids are the test resource ids - Set expectedResourceIds = new LinkedHashSet<>(); - for (TestResourceId testResourceId : TestResourceId.values()) { - expectedResourceIds.add(testResourceId); - } - assertEquals(expectedResourceIds, resourcesDataManager.getResourceIds()); - - }); - } - - @Test - @UnitTestMethod(name = "getResourcePropertyDefinition", args = { ResourceId.class, ResourcePropertyId.class }) - public void testGetResourcePropertyDefinition() { - - ResourcesActionSupport.testConsumer(5, 7619546908709928867L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - // show that each of the resource property definitions from the test - // resource property enum are present - for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId.values()) { - PropertyDefinition expectedPropertyDefinition = testResourcePropertyId.getPropertyDefinition(); - PropertyDefinition actualPropertyDefinition = resourcesDataManager.getResourcePropertyDefinition(testResourcePropertyId.getTestResourceId(), testResourcePropertyId); - assertEquals(expectedPropertyDefinition, actualPropertyDefinition); - } - }); - } - - @Test - @UnitTestMethod(name = "getResourcePropertyIds", args = { ResourceId.class }) - public void testGetResourcePropertyIds() { - - ResourcesActionSupport.testConsumer(5, 1203402714876510055L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - // show that the resource property ids are the test resource - // property ids - for (TestResourceId testResourceId : TestResourceId.values()) { - Set expectedPropertyIds = TestResourcePropertyId.getTestResourcePropertyIds(testResourceId); - Set actualPropertyIds = resourcesDataManager.getResourcePropertyIds(testResourceId); - assertEquals(expectedPropertyIds, actualPropertyIds); - } - - }); - - /* precondition tests if the resource id is null */ - ResourcesActionSupport.testConsumer(5, 3551512082879672269L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.getResourcePropertyIds(null)); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - }); - - /* precondition tests if the resource id unknown */ - ResourcesActionSupport.testConsumer(5, 7372199991315732905L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.getResourcePropertyIds(TestResourceId.getUnknownResourceId())); - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "getResourcePropertyTime", args = { ResourceId.class, ResourcePropertyId.class }) - public void testGetResourcePropertyTime() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - Map expectedTimes = new LinkedHashMap<>(); - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - // establish data views - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - // establish the resources - Set resourceIds = resourcesDataManager.getResourceIds(); - - // initialize the expected times - for (TestResourceId testResourceId : TestResourceId.values()) { - for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId.getTestResourcePropertyIds(testResourceId)) { - expectedTimes.put(new MultiKey(testResourceId, testResourcePropertyId), new MutableDouble()); - } - } - - // set random values to the resource properties - for (int i = 0; i < resourceIds.size(); i++) { - TestResourceId testResourceId = TestResourceId.getRandomResourceId(randomGenerator); - TestResourcePropertyId testResourcePropertyId = TestResourcePropertyId.getRandomResourcePropertyId(testResourceId, randomGenerator); - PropertyDefinition propertyDefinition = resourcesDataManager.getResourcePropertyDefinition(testResourceId, testResourcePropertyId); - if (propertyDefinition.propertyValuesAreMutable()) { - Object value = testResourcePropertyId.getRandomPropertyValue(randomGenerator); - resourcesDataManager.setResourcePropertyValue(testResourceId, testResourcePropertyId, value); - expectedTimes.get(new MultiKey(testResourceId, testResourcePropertyId)).setValue(c.getTime()); - } - } - - })); - - // make more resource property updates at time 1 - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - // establish data views - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - // establish the resources - Set resourceIds = resourcesDataManager.getResourceIds(); - - // set random values to the resource properties - for (int i = 0; i < resourceIds.size(); i++) { - TestResourceId testResourceId = TestResourceId.getRandomResourceId(randomGenerator); - TestResourcePropertyId testResourcePropertyId = TestResourcePropertyId.getRandomResourcePropertyId(testResourceId, randomGenerator); - PropertyDefinition propertyDefinition = resourcesDataManager.getResourcePropertyDefinition(testResourceId, testResourcePropertyId); - if (propertyDefinition.propertyValuesAreMutable()) { - Object value = testResourcePropertyId.getRandomPropertyValue(randomGenerator); - resourcesDataManager.setResourcePropertyValue(testResourceId, testResourcePropertyId, value); - expectedTimes.get(new MultiKey(testResourceId, testResourcePropertyId)).setValue(c.getTime()); - } - } - - })); - - // make more resource property updates at time 2 - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { - // establish data views - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - // establish the resources - Set resourceIds = resourcesDataManager.getResourceIds(); - - // set random values to the resource properties - for (int i = 0; i < resourceIds.size(); i++) { - TestResourceId testResourceId = TestResourceId.getRandomResourceId(randomGenerator); - TestResourcePropertyId testResourcePropertyId = TestResourcePropertyId.getRandomResourcePropertyId(testResourceId, randomGenerator); - PropertyDefinition propertyDefinition = resourcesDataManager.getResourcePropertyDefinition(testResourceId, testResourcePropertyId); - if (propertyDefinition.propertyValuesAreMutable()) { - Object value = testResourcePropertyId.getRandomPropertyValue(randomGenerator); - resourcesDataManager.setResourcePropertyValue(testResourceId, testResourcePropertyId, value); - expectedTimes.get(new MultiKey(testResourceId, testResourcePropertyId)).setValue(c.getTime()); - } - } - - })); - - // test the person resource times - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(3, (c) -> { - // establish data views - - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - - // show that the person resource times match expectations - int actualAssertionsCount = 0; - for (MultiKey multiKey : expectedTimes.keySet()) { - ResourceId resourceId = multiKey.getKey(0); - ResourcePropertyId resourcePropertyId = multiKey.getKey(1); - double expectedTime = expectedTimes.get(multiKey).getValue(); - double actualTime = resourcesDataManager.getResourcePropertyTime(resourceId, resourcePropertyId); - assertEquals(expectedTime, actualTime); - actualAssertionsCount++; - } - /* - * Show that the number of time values that were tested is equal to - * the number of properties - */ - - int expectedAssertionsCount = TestResourcePropertyId.values().length; - assertEquals(expectedAssertionsCount, actualAssertionsCount); - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - ResourcesActionSupport.testConsumers(10, 9211924242349528396L, testPlugin); - - /* precondition test: if the resource id is null */ - ResourcesActionSupport.testConsumer(10, 1319950978123668303L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, - () -> resourcesDataManager.getResourcePropertyTime(null, TestResourcePropertyId.ResourceProperty_1_1_BOOLEAN_MUTABLE)); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - }); - /* precondition test: if the resource id is unknown */ - ResourcesActionSupport.testConsumer(10, 250698207522319222L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, - () -> resourcesDataManager.getResourcePropertyTime(TestResourceId.getUnknownResourceId(), TestResourcePropertyId.ResourceProperty_1_1_BOOLEAN_MUTABLE)); - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - }); - /* precondition test: if the resource property id is null */ - ResourcesActionSupport.testConsumer(10, 5885550428859775099L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.getResourcePropertyTime(TestResourceId.RESOURCE_1, null)); - assertEquals(ResourceError.NULL_RESOURCE_PROPERTY_ID, contractException.getErrorType()); - }); - /* precondition test: if the resource property id is unknown */ - ResourcesActionSupport.testConsumer(10, 6053540186403572061L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, - () -> resourcesDataManager.getResourcePropertyTime(TestResourceId.RESOURCE_1, TestResourcePropertyId.ResourceProperty_2_1_BOOLEAN_MUTABLE)); - assertEquals(ResourceError.UNKNOWN_RESOURCE_PROPERTY_ID, contractException.getErrorType()); - }); - - /* precondition test: if the resource property id is unknown */ - ResourcesActionSupport.testConsumer(10, 6439495795797811534L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, - () -> resourcesDataManager.getResourcePropertyTime(TestResourceId.RESOURCE_1, TestResourcePropertyId.getUnknownResourcePropertyId())); - assertEquals(ResourceError.UNKNOWN_RESOURCE_PROPERTY_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "getResourcePropertyValue", args = { ResourceId.class, ResourcePropertyId.class }) - public void testGetResourcePropertyValue() { - - ResourcesActionSupport.testConsumer(10, 8757871520559824784L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - // establish the expected values of all resource properties - Map expectedValues = new LinkedHashMap<>(); - for (TestResourceId testResourceId : TestResourceId.values()) { - for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId.getTestResourcePropertyIds(testResourceId)) { - Object propertyValue = resourcesDataManager.getResourcePropertyValue(testResourceId, testResourcePropertyId); - expectedValues.put(new MultiKey(testResourceId, testResourcePropertyId), propertyValue); - } - } - - // make a few random resource property updates - int updateCount = 0; - for (int i = 0; i < 1000; i++) { - TestResourceId testResourceId = TestResourceId.getRandomResourceId(randomGenerator); - TestResourcePropertyId testResourcePropertyId = TestResourcePropertyId.getRandomResourcePropertyId(testResourceId, randomGenerator); - PropertyDefinition resourcePropertyDefinition = resourcesDataManager.getResourcePropertyDefinition(testResourceId, testResourcePropertyId); - if (resourcePropertyDefinition.propertyValuesAreMutable()) { - Object propertyValue = testResourcePropertyId.getRandomPropertyValue(randomGenerator); - resourcesDataManager.setResourcePropertyValue(testResourceId, testResourcePropertyId, propertyValue); - expectedValues.put(new MultiKey(testResourceId, testResourcePropertyId), propertyValue); - updateCount++; - } - } - - /* - * Show that the number of updates was reasonable - some of the - * properties are not mutable so it will be <1000 - */ - assertTrue(updateCount > 500); - - // show that the values of the resource propeties are correct - for (MultiKey multiKey : expectedValues.keySet()) { - TestResourceId testResourceId = multiKey.getKey(0); - TestResourcePropertyId testResourcePropertyId = multiKey.getKey(1); - Object expectedValue = expectedValues.get(multiKey); - Object actualValue = resourcesDataManager.getResourcePropertyValue(testResourceId, testResourcePropertyId); - assertEquals(expectedValue, actualValue); - } - - }); - - /* precondition test: if the resource id is null */ - ResourcesActionSupport.testConsumer(10, 5856579804289926491L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, - () -> resourcesDataManager.getResourcePropertyValue(null, TestResourcePropertyId.ResourceProperty_1_1_BOOLEAN_MUTABLE)); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the resource id unknown */ - ResourcesActionSupport.testConsumer(10, 1735955680485266104L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, - () -> resourcesDataManager.getResourcePropertyValue(TestResourceId.getUnknownResourceId(), TestResourcePropertyId.ResourceProperty_1_1_BOOLEAN_MUTABLE)); - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the resource property id is null */ - ResourcesActionSupport.testConsumer(10, 5544999164968796966L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.getResourcePropertyValue(TestResourceId.RESOURCE_1, null)); - assertEquals(ResourceError.NULL_RESOURCE_PROPERTY_ID, contractException.getErrorType()); - }); - - /* precondition test: if the resource property id unknown */ - ResourcesActionSupport.testConsumer(10, 3394498124288646142L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, - () -> resourcesDataManager.getResourcePropertyValue(TestResourceId.RESOURCE_1, TestResourcePropertyId.ResourceProperty_2_1_BOOLEAN_MUTABLE)); - assertEquals(ResourceError.UNKNOWN_RESOURCE_PROPERTY_ID, contractException.getErrorType()); - }); - - /* precondition test: if the resource property id unknown */ - ResourcesActionSupport.testConsumer(10, 2505584646755789288L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, - () -> resourcesDataManager.getResourcePropertyValue(TestResourceId.RESOURCE_1, TestResourcePropertyId.getUnknownResourcePropertyId())); - assertEquals(ResourceError.UNKNOWN_RESOURCE_PROPERTY_ID, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "resourceIdExists", args = { ResourceId.class }) - public void testResourceIdExists() { - - ResourcesActionSupport.testConsumer(5, 4964974931601945506L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - - // show that the resource ids that exist are the test resource ids - - for (TestResourceId testResourceId : TestResourceId.values()) { - assertTrue(resourcesDataManager.resourceIdExists(testResourceId)); - } - assertFalse(resourcesDataManager.resourceIdExists(TestResourceId.getUnknownResourceId())); - assertFalse(resourcesDataManager.resourceIdExists(null)); - - }); - } - - @Test - @UnitTestMethod(name = "resourcePropertyIdExists", args = { ResourceId.class, ResourcePropertyId.class }) - public void testResourcePropertyIdExists() { - - ResourcesActionSupport.testConsumer(5, 8074706630609416041L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - - // show that the resource property ids that exist are the test - // resource property ids - - for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId.values()) { - assertTrue(resourcesDataManager.resourcePropertyIdExists(testResourcePropertyId.getTestResourceId(), testResourcePropertyId)); - } - - assertFalse(resourcesDataManager.resourcePropertyIdExists(TestResourceId.RESOURCE_1, TestResourcePropertyId.ResourceProperty_2_1_BOOLEAN_MUTABLE)); - assertFalse(resourcesDataManager.resourcePropertyIdExists(null, TestResourcePropertyId.ResourceProperty_2_1_BOOLEAN_MUTABLE)); - assertFalse(resourcesDataManager.resourcePropertyIdExists(TestResourceId.RESOURCE_1, null)); - assertFalse(resourcesDataManager.resourcePropertyIdExists(TestResourceId.RESOURCE_1, TestResourcePropertyId.getUnknownResourcePropertyId())); - assertFalse(resourcesDataManager.resourcePropertyIdExists(TestResourceId.getUnknownResourceId(), TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE)); - assertFalse(resourcesDataManager.resourcePropertyIdExists(TestResourceId.getUnknownResourceId(), TestResourcePropertyId.getUnknownResourcePropertyId())); - - }); - } - - @Test - @UnitTestMethod(name = "setResourcePropertyValue", args = { ResourceId.class, ResourcePropertyId.class, Object.class }) - public void testSetResourcePropertyValue() { - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - Set expectedObservations = new LinkedHashSet<>(); - Set actualObservations = new LinkedHashSet<>(); - - // Have an actor observe the resource property changes - - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { - - for (TestResourceId testResourceId : TestResourceId.values()) { - Set testResourcePropertyIds = TestResourcePropertyId.getTestResourcePropertyIds(testResourceId); - for (TestResourcePropertyId testResourcePropertyId : testResourcePropertyIds) { - c.subscribe(ResourcePropertyUpdateEvent.getEventLabel(c, testResourceId, testResourcePropertyId), (c2, e) -> { - actualObservations.add(new MultiKey(e.getResourceId(), e.getResourcePropertyId(), e.getPreviousPropertyValue(), e.getCurrentPropertyValue())); - }); - } - } - })); - - // Have an actor assign resource properties - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - - for (TestResourceId testResourceId : TestResourceId.values()) { - Set testResourcePropertyIds = TestResourcePropertyId.getTestResourcePropertyIds(testResourceId); - for (TestResourcePropertyId testResourcePropertyId : testResourcePropertyIds) { - PropertyDefinition propertyDefinition = resourcesDataManager.getResourcePropertyDefinition(testResourceId, testResourcePropertyId); - if (propertyDefinition.propertyValuesAreMutable()) { - // update the property value - Object resourcePropertyValue = resourcesDataManager.getResourcePropertyValue(testResourceId, testResourcePropertyId); - Object expectedValue = testResourcePropertyId.getRandomPropertyValue(randomGenerator); - resourcesDataManager.setResourcePropertyValue(testResourceId, testResourcePropertyId, expectedValue); - // show that the property value was changed - Object actualValue = resourcesDataManager.getResourcePropertyValue(testResourceId, testResourcePropertyId); - assertEquals(expectedValue, actualValue); - - expectedObservations.add(new MultiKey(testResourceId, testResourcePropertyId, resourcePropertyValue, expectedValue)); - } - } - } - - })); - - // Have the observer show the the observations were properly generated - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - assertEquals(expectedObservations, actualObservations); - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - ResourcesActionSupport.testConsumers(0, 8240654442453940072L, testPlugin); - - /* precondition test: if the resource id is null */ - ResourcesActionSupport.testConsumer(0, 8603231391482244436L, (c) -> { - ResourcePropertyId resourcePropertyId = TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE; - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - Object value = 10; - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.setResourcePropertyValue(null, resourcePropertyId, value)); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the resource id is unknown */ - ResourcesActionSupport.testConsumer(0, 4345368701918830681L, (c) -> { - ResourcePropertyId resourcePropertyId = TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE; - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - Object value = 10; - ContractException contractException = assertThrows(ContractException.class, - () -> resourcesDataManager.setResourcePropertyValue(TestResourceId.getUnknownResourceId(), resourcePropertyId, value)); - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the resource property id is null */ - ResourcesActionSupport.testConsumer(0, 697099694521127247L, (c) -> { - ResourceId resourceId = TestResourceId.RESOURCE_1; - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - Object value = 10; - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.setResourcePropertyValue(resourceId, null, value)); - assertEquals(ResourceError.NULL_RESOURCE_PROPERTY_ID, contractException.getErrorType()); - }); - - /* precondition test: if the resource property id is unknown */ - ResourcesActionSupport.testConsumer(0, 5208483875882077960L, (c) -> { - ResourceId resourceId = TestResourceId.RESOURCE_1; - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - Object value = 10; - ContractException contractException = assertThrows(ContractException.class, - () -> resourcesDataManager.setResourcePropertyValue(resourceId, TestResourcePropertyId.getUnknownResourcePropertyId(), value)); - assertEquals(ResourceError.UNKNOWN_RESOURCE_PROPERTY_ID, contractException.getErrorType()); - }); - - /* precondition test: if the resource property value is null */ - ResourcesActionSupport.testConsumer(0, 1862818482356534123L, (c) -> { - ResourceId resourceId = TestResourceId.RESOURCE_1; - ResourcePropertyId resourcePropertyId = TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE; - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.setResourcePropertyValue(resourceId, resourcePropertyId, null)); - assertEquals(ResourceError.NULL_RESOURCE_PROPERTY_VALUE, contractException.getErrorType()); - }); - - /* - * precondition test: if the resource property value is incompatible - * with the corresponding property definition - */ - ResourcesActionSupport.testConsumer(0, 8731358919842250070L, (c) -> { - ResourceId resourceId = TestResourceId.RESOURCE_1; - ResourcePropertyId resourcePropertyId = TestResourcePropertyId.ResourceProperty_1_2_INTEGER_MUTABLE; - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.setResourcePropertyValue(resourceId, resourcePropertyId, 23.4)); - assertEquals(PropertyError.INCOMPATIBLE_VALUE, contractException.getErrorType()); - }); - - /* precondition test: if the property has been defined as immutable */ - ResourcesActionSupport.testConsumer(0, 2773568485593496806L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - Object value = 10; - ContractException contractException = assertThrows(ContractException.class, - () -> resourcesDataManager.setResourcePropertyValue(TestResourceId.RESOURCE_5, TestResourcePropertyId.ResourceProperty_5_1_INTEGER_IMMUTABLE, value)); - assertEquals(PropertyError.IMMUTABLE_VALUE, contractException.getErrorType()); - }); - - } - - private void testEventLabeler(ActorContext c, EventLabeler eventLabeler) { - assertNotNull(eventLabeler); - ContractException contractException = assertThrows(ContractException.class, () -> c.addEventLabeler(eventLabeler)); - assertEquals(NucleusError.DUPLICATE_LABELER_ID_IN_EVENT_LABELER, contractException.getErrorType()); - } - - @Test - @UnitTestMethod(name = "init", args = {}) - public void testPersonResourceUpdateEventLabelers() { - - // Have the actor attempt to add the event labelers and show that a - // contract exception is thrown, indicating that the labelers were - // previously added by the resolver. - - ResourcesActionSupport.testConsumer(100, 4062799122381184575L, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - testEventLabeler(c, PersonResourceUpdateEvent.getEventLabelerForPersonAndResource()); - testEventLabeler(c, PersonResourceUpdateEvent.getEventLabelerForRegionAndResource(regionsDataManager)); - testEventLabeler(c, PersonResourceUpdateEvent.getEventLabelerForResource()); - }); - - } - - @Test - @UnitTestMethod(name = "init", args = {}) - public void testRegionResourceUpdateEventLabelers() { - - // - // Have the actor attempt to add the event labelers and show that a - // contract exception is thrown, indicating that the labelers were - // previously added by the resolver. - - ResourcesActionSupport.testConsumer(100, 8290874716343051269L, (c) -> { - testEventLabeler(c, RegionResourceUpdateEvent.getEventLabelerForRegionAndResource()); - }); - - } - - @Test - @UnitTestMethod(name = "init", args = {}) - public void testResourcePropertyUpdateEventLabelers() { - - // Have the actor attempt to add the event labelers and show that a - // contract exception is thrown, indicating that the labelers were - // previously added by the resolver. - - ResourcesActionSupport.testConsumer(100, 3611119165176896462L, (c) -> { - testEventLabeler(c, ResourcePropertyUpdateEvent.getEventLabeler()); - }); - - } - - @Test - @UnitTestMethod(name = "removeResourceFromPerson", args = { ResourceId.class, PersonId.class, long.class }) - public void testPersonResourceRemovalEvent() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - Set expectedObservations = new LinkedHashSet<>(); - Set actualObservations = new LinkedHashSet<>(); - - // Have and actor give resources to people - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - - List people = peopleDataManager.getPeople(); - // add resources to all the people - for (PersonId personId : people) { - for (TestResourceId testResourceId : TestResourceId.values()) { - RegionId regionId = regionsDataManager.getPersonRegion(personId); - resourcesDataManager.addResourceToRegion(testResourceId, regionId, 100L); - resourcesDataManager.transferResourceToPersonFromRegion(testResourceId, personId, 100L); - } - } - })); - - // have an actor observe the changes to person resources - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(1, (c) -> { - for (TestResourceId testResourceId : TestResourceId.values()) { - c.subscribe(PersonResourceUpdateEvent.getEventLabelByResource(c, testResourceId), (c2, e) -> { - actualObservations.add(new MultiKey(e.getPersonId(), e.getResourceId(), e.getPreviousResourceLevel(), e.getCurrentResourceLevel())); - }); - } - })); - - // Have the actor remove resources from people - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { - - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - - List people = peopleDataManager.getPeople(); - - // remove random amounts of resources from people - int transfercount = 0; - for (int i = 0; i < 40; i++) { - // select a random person and resource - PersonId personId = people.get(randomGenerator.nextInt(people.size())); - TestResourceId resourceId = TestResourceId.getRandomResourceId(randomGenerator); - - long personResourceLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); - // ensure that the person has a positive amount of the resource - if (personResourceLevel > 0) { - - // select an amount to remove - long amount = randomGenerator.nextInt((int) personResourceLevel) + 1; - resourcesDataManager.removeResourceFromPerson(resourceId, personId, amount); - transfercount++; - - // show that the amount was transferred - long expectedPersonResourceLevel = personResourceLevel - amount; - long actualPersonResorceLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); - assertEquals(expectedPersonResourceLevel, actualPersonResorceLevel); - - expectedObservations.add(new MultiKey(personId, resourceId, personResourceLevel, expectedPersonResourceLevel)); - - } - - } - - // show that enough transfers occurred to make a valid test - assertTrue(transfercount > 10); - - })); - - // Have the observer show that the generated observations were correct - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(3, (c) -> { - assertEquals(expectedObservations, actualObservations); - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - ResourcesActionSupport.testConsumers(50, 6476360369877622233L, testPlugin); - - /////////////////// - /* precondition test: if the person id is null */ - ResourcesActionSupport.testConsumer(50, 6476360369877622233L, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ResourceId resourceId = TestResourceId.RESOURCE_1; - PersonId personId = new PersonId(0); - long amount = 10; - // add resource to the person to ensure the precondition tests will - // work - RegionId regionId = regionsDataManager.getPersonRegion(personId); - resourcesDataManager.addResourceToRegion(resourceId, regionId, 100L); - resourcesDataManager.transferResourceToPersonFromRegion(resourceId, personId, 100L); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.removeResourceFromPerson(resourceId, null, amount)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - }); - - /* precondition test: if the person does not exist */ - ResourcesActionSupport.testConsumer(50, 6476360369877622233L, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ResourceId resourceId = TestResourceId.RESOURCE_1; - PersonId personId = new PersonId(0); - long amount = 10; - // add resource to the person to ensure the precondition tests will - // work - RegionId regionId = regionsDataManager.getPersonRegion(personId); - resourcesDataManager.addResourceToRegion(resourceId, regionId, 100L); - resourcesDataManager.transferResourceToPersonFromRegion(resourceId, personId, 100L); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.removeResourceFromPerson(resourceId, new PersonId(1000), amount)); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - }); - - /* precondition test: if the resource id is null */ - ResourcesActionSupport.testConsumer(50, 6476360369877622233L, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ResourceId resourceId = TestResourceId.RESOURCE_1; - PersonId personId = new PersonId(0); - long amount = 10; - // add resource to the person to ensure the precondition tests will - // work - RegionId regionId = regionsDataManager.getPersonRegion(personId); - resourcesDataManager.addResourceToRegion(resourceId, regionId, 100L); - resourcesDataManager.transferResourceToPersonFromRegion(resourceId, personId, 100L); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.removeResourceFromPerson(null, personId, amount)); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the resource id is unknown */ - ResourcesActionSupport.testConsumer(50, 6476360369877622233L, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ResourceId resourceId = TestResourceId.RESOURCE_1; - PersonId personId = new PersonId(0); - long amount = 10; - // add resource to the person to ensure the precondition tests will - // work - RegionId regionId = regionsDataManager.getPersonRegion(personId); - resourcesDataManager.addResourceToRegion(resourceId, regionId, 100L); - resourcesDataManager.transferResourceToPersonFromRegion(resourceId, personId, 100L); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.removeResourceFromPerson(TestResourceId.getUnknownResourceId(), personId, amount)); - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the amount is negative */ - ResourcesActionSupport.testConsumer(50, 6476360369877622233L, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ResourceId resourceId = TestResourceId.RESOURCE_1; - PersonId personId = new PersonId(0); - // add resource to the person to ensure the precondition tests will - // work - RegionId regionId = regionsDataManager.getPersonRegion(personId); - resourcesDataManager.addResourceToRegion(resourceId, regionId, 100L); - resourcesDataManager.transferResourceToPersonFromRegion(resourceId, personId, 100L); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.removeResourceFromPerson(resourceId, personId, -1)); - assertEquals(ResourceError.NEGATIVE_RESOURCE_AMOUNT, contractException.getErrorType()); - - }); - - /* - * precondition test: if the person does not have the required amount of - * the resource - */ - ResourcesActionSupport.testConsumer(50, 6476360369877622233L, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ResourceId resourceId = TestResourceId.RESOURCE_1; - PersonId personId = new PersonId(0); - // add resource to the person to ensure the precondition tests will - // work - RegionId regionId = regionsDataManager.getPersonRegion(personId); - resourcesDataManager.addResourceToRegion(resourceId, regionId, 100L); - resourcesDataManager.transferResourceToPersonFromRegion(resourceId, personId, 100L); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.removeResourceFromPerson(resourceId, personId, 10000)); - assertEquals(ResourceError.INSUFFICIENT_RESOURCES_AVAILABLE, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "removeResourceFromRegion", args = { ResourceId.class, RegionId.class, long.class }) - public void testRegionResourceRemovalEvent() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - Set expectedObservations = new LinkedHashSet<>(); - Set actualObservations = new LinkedHashSet<>(); - - // Have the actor add resources to the regions - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - // add resources to the regions - for (TestRegionId testRegionId : TestRegionId.values()) { - for (TestResourceId testResourceId : TestResourceId.values()) { - resourcesDataManager.addResourceToRegion(testResourceId, testRegionId, 100L); - } - } - - })); - - // Have an actor observe the resource being removed from regions - - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(1, (c) -> { - - for (TestRegionId testRegionId : TestRegionId.values()) { - for (TestResourceId testResourceId : TestResourceId.values()) { - c.subscribe(RegionResourceUpdateEvent.getEventLabelByRegionAndResource(c, testRegionId, testResourceId), (c2, e) -> { - actualObservations.add(new MultiKey(e.getRegionId(), e.getResourceId(), e.getPreviousResourceLevel(), e.getCurrentResourceLevel())); - }); - } - } - - })); - - // Have the actor remove resources from regions - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { - - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - - // remove random amounts of resources from regions - int transfercount = 0; - for (int i = 0; i < 40; i++) { - // select random regions and resources - TestRegionId regionId = TestRegionId.getRandomRegionId(randomGenerator); - TestResourceId resourceId = TestResourceId.getRandomResourceId(randomGenerator); - long regionLevel = resourcesDataManager.getRegionResourceLevel(regionId, resourceId); - - if (regionLevel > 0) { - // select an amount to add - long amount = randomGenerator.nextInt((int) regionLevel) + 1; - resourcesDataManager.removeResourceFromRegion(resourceId, regionId, amount); - transfercount++; - - // show that the amount was added - long expectedRegionLevel = regionLevel - amount; - long actualRegionLevel = resourcesDataManager.getRegionResourceLevel(regionId, resourceId); - assertEquals(expectedRegionLevel, actualRegionLevel); - - expectedObservations.add(new MultiKey(regionId, resourceId, regionLevel, expectedRegionLevel)); - } - } - - // show that enough removals occurred to make a valid test - assertTrue(transfercount > 10); - - })); - - // Have the observer show that the observations were correctly generated - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(3, (c) -> { - assertEquals(expectedObservations, actualObservations); - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - ResourcesActionSupport.testConsumers(0, 3784957617927969790L, testPlugin); - - /* precondition test: if the region id is null */ - ResourcesActionSupport.testConsumer(0, 3784957617927969790L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ResourceId resourceId = TestResourceId.RESOURCE_1; - long amount = 10; - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.removeResourceFromRegion(resourceId, null, amount)); - assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); - }); - - /* precondition test: if the region id is unknown */ - ResourcesActionSupport.testConsumer(0, 3784957617927969790L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ResourceId resourceId = TestResourceId.RESOURCE_1; - long amount = 10; - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.removeResourceFromRegion(resourceId, TestRegionId.getUnknownRegionId(), amount)); - assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); - }); - - /* precondition test: if the resource id is null */ - ResourcesActionSupport.testConsumer(0, 3784957617927969790L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - RegionId regionId = TestRegionId.REGION_1; - long amount = 10; - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.removeResourceFromRegion(null, regionId, amount)); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the resource id is unknown */ - ResourcesActionSupport.testConsumer(0, 3784957617927969790L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - RegionId regionId = TestRegionId.REGION_1; - long amount = 10; - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.removeResourceFromRegion(TestResourceId.getUnknownResourceId(), regionId, amount)); - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the amount is negative */ - ResourcesActionSupport.testConsumer(0, 3784957617927969790L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ResourceId resourceId = TestResourceId.RESOURCE_1; - RegionId regionId = TestRegionId.REGION_1; - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.removeResourceFromRegion(resourceId, regionId, -1L)); - assertEquals(ResourceError.NEGATIVE_RESOURCE_AMOUNT, contractException.getErrorType()); - }); - - /* - * precondition test: if the region does not have the required amount of - * the resource - */ - ResourcesActionSupport.testConsumer(0, 3784957617927969790L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ResourceId resourceId = TestResourceId.RESOURCE_1; - RegionId regionId = TestRegionId.REGION_1; - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.removeResourceFromRegion(resourceId, regionId, 10000000L)); - assertEquals(ResourceError.INSUFFICIENT_RESOURCES_AVAILABLE, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "transferResourceBetweenRegions", args = { ResourceId.class, RegionId.class, RegionId.class, long.class }) - public void testTransferResourceBetweenRegions() { - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - Set actualObservations = new LinkedHashSet<>(); - Set expectedObservations = new LinkedHashSet<>(); - - // create an actor to observe resource transfers - - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { - for (TestRegionId testRegionId : TestRegionId.values()) { - for (TestResourceId testResourceId : TestResourceId.values()) { - c.subscribe(RegionResourceUpdateEvent.getEventLabelByRegionAndResource(c, testRegionId, testResourceId), (c2, e) -> { - actualObservations.add(new MultiKey(e.getRegionId(), e.getResourceId(), e.getPreviousResourceLevel(), e.getCurrentResourceLevel())); - }); - } - } - })); - - // create an actor that will transfer resources between regions - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - - // add resources to all the regions - for (TestRegionId testRegionId : TestRegionId.values()) { - for (TestResourceId testResourceId : TestResourceId.values()) { - resourcesDataManager.addResourceToRegion(testResourceId, testRegionId, 100L); - expectedObservations.add(new MultiKey(testRegionId, testResourceId, 0L, 100L)); - } - } - - // transfer random amounts of resources between regions - int transfercount = 0; - for (int i = 0; i < 40; i++) { - // select random regions and resource - TestRegionId regionId1 = TestRegionId.getRandomRegionId(randomGenerator); - TestRegionId regionId2 = TestRegionId.getRandomRegionId(randomGenerator); - TestResourceId resourceId = TestResourceId.getRandomResourceId(randomGenerator); - // ensure the regions are different - if (!regionId1.equals(regionId2)) { - long region1Level = resourcesDataManager.getRegionResourceLevel(regionId1, resourceId); - // ensure that the first region has a positive amount of the - // resource - if (region1Level > 0) { - // establish the current level of the second region - long region2Level = resourcesDataManager.getRegionResourceLevel(regionId2, resourceId); - // select an amount to transfer - long amount = randomGenerator.nextInt((int) region1Level) + 1; - resourcesDataManager.transferResourceBetweenRegions(resourceId, regionId1, regionId2, amount); - transfercount++; - - // show that the amount was transferred - long expectedRegion1Level = region1Level - amount; - long expectedRegion2Level = region2Level + amount; - - long actualRegion1Level = resourcesDataManager.getRegionResourceLevel(regionId1, resourceId); - long actualRegion2Level = resourcesDataManager.getRegionResourceLevel(regionId2, resourceId); - assertEquals(expectedRegion1Level, actualRegion1Level); - assertEquals(expectedRegion2Level, actualRegion2Level); - - expectedObservations.add(new MultiKey(regionId1, resourceId, region1Level, expectedRegion1Level)); - expectedObservations.add(new MultiKey(regionId2, resourceId, region2Level, expectedRegion2Level)); - - } - } - } - - // show that enough transfers occurred to make a valid test - assertTrue(transfercount > 10); - - })); - - // have the observer show that the correct observations were made - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(2, (c) -> { - assertEquals(expectedObservations, actualObservations); - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - ResourcesActionSupport.testConsumers(0, 7976375269741360076L, testPlugin); - - /* precondition test: if the source region is null */ - ResourcesActionSupport.testConsumer(0, 2545276913032843668L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ResourceId resourceId = TestResourceId.RESOURCE_1; - RegionId regionId2 = TestRegionId.REGION_2; - long amount = 10; - - // add resources to all the regions to ensure the precondition tests - // will work - for (TestRegionId testRegionId : TestRegionId.values()) { - for (TestResourceId testResourceId : TestResourceId.values()) { - resourcesDataManager.addResourceToRegion(testResourceId, testRegionId, 100L); - } - } - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.transferResourceBetweenRegions(resourceId, null, regionId2, amount)); - assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); - }); - - /* precondition test: if the source region is unknown */ - ResourcesActionSupport.testConsumer(0, 1182536948902380826L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ResourceId resourceId = TestResourceId.RESOURCE_1; - RegionId regionId2 = TestRegionId.REGION_2; - long amount = 10; - // add resources to all the regions to ensure the precondition tests - // will work - for (TestRegionId testRegionId : TestRegionId.values()) { - for (TestResourceId testResourceId : TestResourceId.values()) { - resourcesDataManager.addResourceToRegion(testResourceId, testRegionId, 100L); - } - } - ContractException contractException = assertThrows(ContractException.class, - () -> resourcesDataManager.transferResourceBetweenRegions(resourceId, TestRegionId.getUnknownRegionId(), regionId2, amount)); - assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); - }); - - /* precondition test: if the destination region is null */ - ResourcesActionSupport.testConsumer(0, 3358578155263941L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ResourceId resourceId = TestResourceId.RESOURCE_1; - RegionId regionId1 = TestRegionId.REGION_1; - long amount = 10; - // add resources to all the regions to ensure the precondition tests - // will work - for (TestRegionId testRegionId : TestRegionId.values()) { - for (TestResourceId testResourceId : TestResourceId.values()) { - resourcesDataManager.addResourceToRegion(testResourceId, testRegionId, 100L); - } - } - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.transferResourceBetweenRegions(resourceId, regionId1, null, amount)); - assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); - }); - - /* precondition test: if the destination region is unknown */ - ResourcesActionSupport.testConsumer(0, 289436879730670757L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ResourceId resourceId = TestResourceId.RESOURCE_1; - RegionId regionId1 = TestRegionId.REGION_1; - long amount = 10; - // add resources to all the regions to ensure the precondition tests - // will work - for (TestRegionId testRegionId : TestRegionId.values()) { - for (TestResourceId testResourceId : TestResourceId.values()) { - resourcesDataManager.addResourceToRegion(testResourceId, testRegionId, 100L); - } - } - ContractException contractException = assertThrows(ContractException.class, - () -> resourcesDataManager.transferResourceBetweenRegions(resourceId, regionId1, TestRegionId.getUnknownRegionId(), amount)); - assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); - }); - - /* precondition test: if the resource id is null */ - ResourcesActionSupport.testConsumer(0, 3690172166437098600L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - RegionId regionId1 = TestRegionId.REGION_1; - RegionId regionId2 = TestRegionId.REGION_2; - long amount = 10; - // add resources to all the regions to ensure the precondition tests - // will work - for (TestRegionId testRegionId : TestRegionId.values()) { - for (TestResourceId testResourceId : TestResourceId.values()) { - resourcesDataManager.addResourceToRegion(testResourceId, testRegionId, 100L); - } - } - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.transferResourceBetweenRegions(null, regionId1, regionId2, amount)); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the resource id is unknown */ - ResourcesActionSupport.testConsumer(0, 7636787584894783093L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - RegionId regionId1 = TestRegionId.REGION_1; - RegionId regionId2 = TestRegionId.REGION_2; - long amount = 10; - // add resources to all the regions to ensure the precondition tests - // will work - for (TestRegionId testRegionId : TestRegionId.values()) { - for (TestResourceId testResourceId : TestResourceId.values()) { - resourcesDataManager.addResourceToRegion(testResourceId, testRegionId, 100L); - } - } - ContractException contractException = assertThrows(ContractException.class, - () -> resourcesDataManager.transferResourceBetweenRegions(TestResourceId.getUnknownResourceId(), regionId1, regionId2, amount)); - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the resource amount is negative */ - ResourcesActionSupport.testConsumer(0, 1320571074133841280L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ResourceId resourceId = TestResourceId.RESOURCE_1; - RegionId regionId1 = TestRegionId.REGION_1; - RegionId regionId2 = TestRegionId.REGION_2; - // add resources to all the regions to ensure the precondition tests - // will work - for (TestRegionId testRegionId : TestRegionId.values()) { - for (TestResourceId testResourceId : TestResourceId.values()) { - resourcesDataManager.addResourceToRegion(testResourceId, testRegionId, 100L); - } - } - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.transferResourceBetweenRegions(resourceId, regionId1, regionId2, -1)); - assertEquals(ResourceError.NEGATIVE_RESOURCE_AMOUNT, contractException.getErrorType()); - }); - - /* precondition test: if the source and destination region are equal */ - ResourcesActionSupport.testConsumer(0, 2402299633191289724L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ResourceId resourceId = TestResourceId.RESOURCE_1; - RegionId regionId1 = TestRegionId.REGION_1; - long amount = 10; - // add resources to all the regions to ensure the precondition tests - // will work - for (TestRegionId testRegionId : TestRegionId.values()) { - for (TestResourceId testResourceId : TestResourceId.values()) { - resourcesDataManager.addResourceToRegion(testResourceId, testRegionId, 100L); - } - } - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.transferResourceBetweenRegions(resourceId, regionId1, regionId1, amount)); - assertEquals(ResourceError.REFLEXIVE_RESOURCE_TRANSFER, contractException.getErrorType()); - }); - - /* - * precondition test: if the source region does not have sufficient - * resources to support the transfer - */ - ResourcesActionSupport.testConsumer(0, 9136536902267748610L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ResourceId resourceId = TestResourceId.RESOURCE_1; - RegionId regionId1 = TestRegionId.REGION_1; - RegionId regionId2 = TestRegionId.REGION_2; - // add resources to all the regions to ensure the precondition tests - // will work - for (TestRegionId testRegionId : TestRegionId.values()) { - for (TestResourceId testResourceId : TestResourceId.values()) { - resourcesDataManager.addResourceToRegion(testResourceId, testRegionId, 100L); - } - } - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.transferResourceBetweenRegions(resourceId, regionId1, regionId2, 100000L)); - assertEquals(ResourceError.INSUFFICIENT_RESOURCES_AVAILABLE, contractException.getErrorType()); - }); - - /* - * precondition test: if the transfer will cause a numeric overflow in - * the destination region - */ - ResourcesActionSupport.testConsumer(0, 342832088592207841L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ResourceId resourceId = TestResourceId.RESOURCE_1; - RegionId regionId1 = TestRegionId.REGION_1; - RegionId regionId2 = TestRegionId.REGION_2; - long amount = 10; - // add resources to all the regions to ensure the precondition tests - // will work - for (TestRegionId testRegionId : TestRegionId.values()) { - for (TestResourceId testResourceId : TestResourceId.values()) { - resourcesDataManager.addResourceToRegion(testResourceId, testRegionId, 100L); - } - } - // fill region 2 to the max long value - long fillAmount = Long.MAX_VALUE - resourcesDataManager.getRegionResourceLevel(regionId2, resourceId); - resourcesDataManager.addResourceToRegion(resourceId, regionId2, fillAmount); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.transferResourceBetweenRegions(resourceId, regionId1, regionId2, amount)); - assertEquals(ResourceError.RESOURCE_ARITHMETIC_EXCEPTION, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "transferResourceFromPersonToRegion", args = { ResourceId.class, PersonId.class, long.class }) - public void testResourceTransferFromPersonEvent() { - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - Set expectedObservations = new LinkedHashSet<>(); - Set actualObservations = new LinkedHashSet<>(); - - // Have an actor give people resources - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - - List people = peopleDataManager.getPeople(); - - // add resources to all people - for (PersonId personId : people) { - RegionId regionId = regionsDataManager.getPersonRegion(personId); - for (TestResourceId testResourceId : TestResourceId.values()) { - resourcesDataManager.addResourceToRegion(testResourceId, regionId, 100L); - resourcesDataManager.transferResourceToPersonFromRegion(testResourceId, personId, 100L); - } - } - - })); - - // Have an actor observe the resource changes - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(1, (c) -> { - - for (TestRegionId testRegionId : TestRegionId.values()) { - for (TestResourceId testResourceId : TestResourceId.values()) { - c.subscribe(RegionResourceUpdateEvent.getEventLabelByRegionAndResource(c, testRegionId, testResourceId), (c2, e) -> { - actualObservations.add(new MultiKey(e.getRegionId(), e.getResourceId(), e.getPreviousResourceLevel(), e.getCurrentResourceLevel())); - }); - } - } - for (TestResourceId testResourceId : TestResourceId.values()) { - c.subscribe(PersonResourceUpdateEvent.getEventLabelByResource(c, testResourceId), (c2, e) -> { - actualObservations.add(new MultiKey(e.getPersonId(), e.getResourceId(), e.getPreviousResourceLevel(), e.getCurrentResourceLevel())); - - }); - } - - })); - - // Have an actor return resources from people back to their regions - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { - - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - - List people = peopleDataManager.getPeople(); - - int transferCount = 0; - // transfer resources back to the regions from the people - for (int i = 0; i < 100; i++) { - PersonId personId = people.get(randomGenerator.nextInt(people.size())); - ResourceId resourceId = TestResourceId.getRandomResourceId(randomGenerator); - RegionId regionId = regionsDataManager.getPersonRegion(personId); - long personResourceLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); - long regionResourceLevel = resourcesDataManager.getRegionResourceLevel(regionId, resourceId); - - if (personResourceLevel > 0) { - long amount = randomGenerator.nextInt((int) personResourceLevel); - long expectedPersonResourceLevel = personResourceLevel - amount; - long expectedRegionResourceLevel = regionResourceLevel + amount; - resourcesDataManager.transferResourceFromPersonToRegion(resourceId, personId, amount); - transferCount++; - long actualPersonResourceLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); - long actualRegionResourceLevel = resourcesDataManager.getRegionResourceLevel(regionId, resourceId); - assertEquals(expectedPersonResourceLevel, actualPersonResourceLevel); - assertEquals(expectedRegionResourceLevel, actualRegionResourceLevel); - - expectedObservations.add(new MultiKey(regionId, resourceId, regionResourceLevel, expectedRegionResourceLevel)); - expectedObservations.add(new MultiKey(personId, resourceId, personResourceLevel, expectedPersonResourceLevel)); - - } - } - - // show that a reasonable number of transfers occurred - assertTrue(transferCount > 20); - - })); - - // Have the observer show that the observations were properly generated - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(3, (c) -> { - assertEquals(expectedObservations, actualObservations); - })); - - // Have an actor test preconditions - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(4, (c) -> { - - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - // precondition tests - PersonId personId = new PersonId(0); - ResourceId resourceId = TestResourceId.RESOURCE_4; - long amount = 10; - - // add resources to the person to support the precondition tests - - RegionId regionId = regionsDataManager.getPersonRegion(personId); - for (TestResourceId testResourceId : TestResourceId.values()) { - resourcesDataManager.addResourceToRegion(testResourceId, regionId, 100L); - resourcesDataManager.transferResourceToPersonFromRegion(testResourceId, personId, 100L); - } - - // if the person id is null - - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.transferResourceFromPersonToRegion(resourceId, null, amount)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - - // if the person does not exist - contractException = assertThrows(ContractException.class, () -> resourcesDataManager.transferResourceFromPersonToRegion(resourceId, new PersonId(3434), amount)); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - - // if the resource id is null - contractException = assertThrows(ContractException.class, () -> resourcesDataManager.transferResourceFromPersonToRegion(null, personId, amount)); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - - // if the resource id is unknown - contractException = assertThrows(ContractException.class, () -> resourcesDataManager.transferResourceFromPersonToRegion(TestResourceId.getUnknownResourceId(), personId, amount)); - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - - // if the amount is negative - contractException = assertThrows(ContractException.class, () -> resourcesDataManager.transferResourceFromPersonToRegion(resourceId, personId, -1L)); - assertEquals(ResourceError.NEGATIVE_RESOURCE_AMOUNT, contractException.getErrorType()); - - // if the person does not have the required amount of the resource - contractException = assertThrows(ContractException.class, () -> resourcesDataManager.transferResourceFromPersonToRegion(resourceId, personId, 1000000)); - assertEquals(ResourceError.INSUFFICIENT_RESOURCES_AVAILABLE, contractException.getErrorType()); - - // if the transfer results in an overflow of the region's resource - // level - - // fill the region - long regionResourceLevel = resourcesDataManager.getRegionResourceLevel(regionId, resourceId); - resourcesDataManager.removeResourceFromRegion(resourceId, regionId, regionResourceLevel); - resourcesDataManager.addResourceToRegion(resourceId, regionId, Long.MAX_VALUE); - - // empty the person - long personResourceLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); - resourcesDataManager.removeResourceFromPerson(resourceId, personId, personResourceLevel); - - // transfer the region's inventory to the person - resourcesDataManager.transferResourceToPersonFromRegion(resourceId, personId, Long.MAX_VALUE); - - // add one more unit to the region - resourcesDataManager.addResourceToRegion(resourceId, regionId, 1L); - - // attempt to transfer the person's inventory back to the region - - contractException = assertThrows(ContractException.class, () -> resourcesDataManager.transferResourceFromPersonToRegion(resourceId, personId, Long.MAX_VALUE)); - assertEquals(ResourceError.RESOURCE_ARITHMETIC_EXCEPTION, contractException.getErrorType()); - - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - ResourcesActionSupport.testConsumers(30, 3166011813977431605L, testPlugin); - - ResourcesActionSupport.testConsumer(30, 3166011813977431605L, (c) -> { - - }); - - } - - @Test - @UnitTestMethod(name = "transferResourceToPersonFromRegion", args = { ResourceId.class, PersonId.class, long.class }) - public void testResourceTransferToPersonEvent() { - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - Set expectedObservations = new LinkedHashSet<>(); - Set actualObservations = new LinkedHashSet<>(); - - // Have an actor add resources to the regions - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - RegionsDataManager regionLocationDataManager = c.getDataManager(RegionsDataManager.class); - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - - List people = peopleDataManager.getPeople(); - - // add resources to all regions - for (PersonId personId : people) { - RegionId regionId = regionLocationDataManager.getPersonRegion(personId); - for (TestResourceId testResourceId : TestResourceId.values()) { - resourcesDataManager.addResourceToRegion(testResourceId, regionId, 1000L); - } - } - - })); - - // Have an actor observe the resource transfers - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(1, (c) -> { - - for (TestRegionId testRegionId : TestRegionId.values()) { - for (TestResourceId testResourceId : TestResourceId.values()) { - c.subscribe(RegionResourceUpdateEvent.getEventLabelByRegionAndResource(c, testRegionId, testResourceId), (c2, e) -> { - actualObservations.add(new MultiKey(e.getRegionId(), e.getResourceId(), e.getPreviousResourceLevel(), e.getCurrentResourceLevel())); - }); - } - } - for (TestResourceId testResourceId : TestResourceId.values()) { - c.subscribe(PersonResourceUpdateEvent.getEventLabelByResource(c, testResourceId), (c2, e) -> { - actualObservations.add(new MultiKey(e.getPersonId(), e.getResourceId(), e.getPreviousResourceLevel(), e.getCurrentResourceLevel())); - - }); - } - - })); - - // Have an actor transfer the resources to people - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { - - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - RegionsDataManager regionLocationDataManager = c.getDataManager(RegionsDataManager.class); - - List people = peopleDataManager.getPeople(); - - int transferCount = 0; - // transfer resources back to the people - for (int i = 0; i < 100; i++) { - PersonId personId = people.get(randomGenerator.nextInt(people.size())); - ResourceId resourceId = TestResourceId.getRandomResourceId(randomGenerator); - RegionId regionId = regionLocationDataManager.getPersonRegion(personId); - long personResourceLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); - long regionResourceLevel = resourcesDataManager.getRegionResourceLevel(regionId, resourceId); - - if (regionResourceLevel > 0) { - long amount = randomGenerator.nextInt((int) regionResourceLevel); - long expectedPersonResourceLevel = personResourceLevel + amount; - long expectedRegionResourceLevel = regionResourceLevel - amount; - resourcesDataManager.transferResourceToPersonFromRegion(resourceId, personId, amount); - transferCount++; - long actualPersonResourceLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); - long actualRegionResourceLevel = resourcesDataManager.getRegionResourceLevel(regionId, resourceId); - assertEquals(expectedPersonResourceLevel, actualPersonResourceLevel); - assertEquals(expectedRegionResourceLevel, actualRegionResourceLevel); - - expectedObservations.add(new MultiKey(regionId, resourceId, regionResourceLevel, expectedRegionResourceLevel)); - expectedObservations.add(new MultiKey(personId, resourceId, personResourceLevel, expectedPersonResourceLevel)); - - } - } - - // show that a reasonable number of transfers occurred - assertTrue(transferCount > 20); - - })); - - // Have an actor show that the proper observations were generated - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(3, (c) -> { - assertEquals(expectedObservations, actualObservations); - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - ResourcesActionSupport.testConsumers(30, 3808042869854225459L, testPlugin); - - /* precondition test: if the person id is null */ - ResourcesActionSupport.testConsumer(30, 2628501738627419743L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - PersonId personId = new PersonId(0); - ResourceId resourceId = TestResourceId.RESOURCE_4; - long amount = 10; - // add resources to the region to support the precondition tests - RegionId regionId = regionsDataManager.getPersonRegion(personId); - for (TestResourceId testResourceId : TestResourceId.values()) { - resourcesDataManager.addResourceToRegion(testResourceId, regionId, 100L); - } - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.transferResourceToPersonFromRegion(resourceId, null, amount)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - }); - - /* precondition test: if the person does not exist */ - ResourcesActionSupport.testConsumer(30, 4172586983768511485L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - PersonId personId = new PersonId(0); - ResourceId resourceId = TestResourceId.RESOURCE_4; - long amount = 10; - // add resources to the region to support the precondition tests - RegionId regionId = regionsDataManager.getPersonRegion(personId); - for (TestResourceId testResourceId : TestResourceId.values()) { - resourcesDataManager.addResourceToRegion(testResourceId, regionId, 100L); - } - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.transferResourceToPersonFromRegion(resourceId, new PersonId(3434), amount)); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - }); - - /* precondition test: if the resource id is null */ - ResourcesActionSupport.testConsumer(30, 6256935891787853979L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - PersonId personId = new PersonId(0); - long amount = 10; - // add resources to the region to support the precondition tests - RegionId regionId = regionsDataManager.getPersonRegion(personId); - for (TestResourceId testResourceId : TestResourceId.values()) { - resourcesDataManager.addResourceToRegion(testResourceId, regionId, 100L); - } - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.transferResourceToPersonFromRegion(null, personId, amount)); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the resource id is unknown */ - ResourcesActionSupport.testConsumer(30, 6949348067383487020L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - PersonId personId = new PersonId(0); - long amount = 10; - // add resources to the region to support the precondition tests - RegionId regionId = regionsDataManager.getPersonRegion(personId); - for (TestResourceId testResourceId : TestResourceId.values()) { - resourcesDataManager.addResourceToRegion(testResourceId, regionId, 100L); - } - ContractException contractException = assertThrows(ContractException.class, - () -> resourcesDataManager.transferResourceToPersonFromRegion(TestResourceId.getUnknownResourceId(), personId, amount)); - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the amount is negative */ - ResourcesActionSupport.testConsumer(30, 6911979438110217773L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - PersonId personId = new PersonId(0); - ResourceId resourceId = TestResourceId.RESOURCE_4; - // add resources to the region to support the precondition tests - RegionId regionId = regionsDataManager.getPersonRegion(personId); - for (TestResourceId testResourceId : TestResourceId.values()) { - resourcesDataManager.addResourceToRegion(testResourceId, regionId, 100L); - } - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.transferResourceToPersonFromRegion(resourceId, personId, -1L)); - assertEquals(ResourceError.NEGATIVE_RESOURCE_AMOUNT, contractException.getErrorType()); - }); - - /* - * precondition test: if the region does not have the required amount of - * the resource - */ - ResourcesActionSupport.testConsumer(30, 1022333582572896703L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - PersonId personId = new PersonId(0); - ResourceId resourceId = TestResourceId.RESOURCE_4; - // add resources to the region to support the precondition tests - RegionId regionId = regionsDataManager.getPersonRegion(personId); - for (TestResourceId testResourceId : TestResourceId.values()) { - resourcesDataManager.addResourceToRegion(testResourceId, regionId, 100L); - } - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.transferResourceToPersonFromRegion(resourceId, personId, 1000000)); - assertEquals(ResourceError.INSUFFICIENT_RESOURCES_AVAILABLE, contractException.getErrorType()); - }); - - /* - * precondition test: if the transfer results in an overflow of the - * person's resource level - */ - ResourcesActionSupport.testConsumer(30, 1989550065510462161L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - PersonId personId = new PersonId(0); - ResourceId resourceId = TestResourceId.RESOURCE_4; - // add resources to the region to support the precondition tests - RegionId regionId = regionsDataManager.getPersonRegion(personId); - for (TestResourceId testResourceId : TestResourceId.values()) { - resourcesDataManager.addResourceToRegion(testResourceId, regionId, 100L); - } - // fill the region - long regionResourceLevel = resourcesDataManager.getRegionResourceLevel(regionId, resourceId); - resourcesDataManager.removeResourceFromRegion(resourceId, regionId, regionResourceLevel); - resourcesDataManager.addResourceToRegion(resourceId, regionId, Long.MAX_VALUE); - - // empty the person - long personResourceLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); - resourcesDataManager.removeResourceFromPerson(resourceId, personId, personResourceLevel); - - // transfer the region's inventory to the person - resourcesDataManager.transferResourceToPersonFromRegion(resourceId, personId, Long.MAX_VALUE); - - // add one more unit to the region - resourcesDataManager.addResourceToRegion(resourceId, regionId, 1L); - - // attempt to transfer the on unit to the person - - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.transferResourceToPersonFromRegion(resourceId, personId, 1L)); - assertEquals(ResourceError.RESOURCE_ARITHMETIC_EXCEPTION, contractException.getErrorType()); - - }); - - } - - @Test - @UnitTestMethod(name = "addResourceToRegion", args = { ResourceId.class, RegionId.class, long.class }) - public void testRegionResourceAdditionEvent() { - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - Set expectedObservations = new LinkedHashSet<>(); - Set actualObservations = new LinkedHashSet<>(); - - // Have an actor to observe the resource changes - - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(0, (c) -> { - for (TestRegionId testRegionId : TestRegionId.values()) { - for (TestResourceId testResourceId : TestResourceId.values()) { - c.subscribe(RegionResourceUpdateEvent.getEventLabelByRegionAndResource(c, testRegionId, testResourceId), (c2, e) -> { - actualObservations.add(new MultiKey(e.getRegionId(), e.getResourceId(), e.getPreviousResourceLevel(), e.getCurrentResourceLevel())); - }); - } - } - })); - - // Have an actor add resources to regions - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - - // add random amounts of resources to regions - int transfercount = 0; - for (int i = 0; i < 40; i++) { - // select random regions and resources - TestRegionId regionId = TestRegionId.getRandomRegionId(randomGenerator); - TestResourceId resourceId = TestResourceId.getRandomResourceId(randomGenerator); - long regionLevel = resourcesDataManager.getRegionResourceLevel(regionId, resourceId); - - // select an amount to add - long amount = randomGenerator.nextInt(100) + 1; - resourcesDataManager.addResourceToRegion(resourceId, regionId, amount); - transfercount++; - - // show that the amount was added - long expectedRegionLevel = regionLevel + amount; - long actualRegionLevel = resourcesDataManager.getRegionResourceLevel(regionId, resourceId); - assertEquals(expectedRegionLevel, actualRegionLevel); - - expectedObservations.add(new MultiKey(regionId, resourceId, regionLevel, expectedRegionLevel)); - - } - - // show that enough additions occurred to make a valid test - assertTrue(transfercount > 10); - - })); - - // have the observer show that the correct observations were generated - pluginBuilder.addTestActorPlan("observer", new TestActorPlan(2, (c) -> { - assertEquals(expectedObservations, actualObservations); - - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - ResourcesActionSupport.testConsumers(0, 2273638431976256278L, testPlugin); - - /* precondition test: if the region id is null */ - ResourcesActionSupport.testConsumer(0, 6097938300290796293L, (c) -> { - ResourceId resourceId = TestResourceId.RESOURCE_1; - long amount = 10; - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.addResourceToRegion(resourceId, null, amount)); - assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); - }); - - /* precondition test: if the region id is unknown */ - ResourcesActionSupport.testConsumer(0, 1284607529543124944L, (c) -> { - ResourceId resourceId = TestResourceId.RESOURCE_1; - long amount = 10; - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.addResourceToRegion(resourceId, TestRegionId.getUnknownRegionId(), amount)); - assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); - }); - - /* precondition test: if the resource id is null */ - ResourcesActionSupport.testConsumer(0, 5929063621703486118L, (c) -> { - RegionId regionId = TestRegionId.REGION_1; - long amount = 10; - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.addResourceToRegion(null, regionId, amount)); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the resource id is unknown */ - ResourcesActionSupport.testConsumer(0, 1240045272882068003L, (c) -> { - RegionId regionId = TestRegionId.REGION_1; - long amount = 10; - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.addResourceToRegion(TestResourceId.getUnknownResourceId(), regionId, amount)); - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - }); - - /* precondition test: if the amount is negative */ - ResourcesActionSupport.testConsumer(0, 2192023733930104434L, (c) -> { - ResourceId resourceId = TestResourceId.RESOURCE_1; - RegionId regionId = TestRegionId.REGION_1; - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.addResourceToRegion(resourceId, regionId, -1)); - assertEquals(ResourceError.NEGATIVE_RESOURCE_AMOUNT, contractException.getErrorType()); - }); - - /* precondition test: if the addition results in an overflow */ - ResourcesActionSupport.testConsumer(0, 4518775448744653729L, (c) -> { - ResourceId resourceId = TestResourceId.RESOURCE_1; - RegionId regionId = TestRegionId.REGION_1; - long amount = 10; - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - resourcesDataManager.addResourceToRegion(resourceId, regionId, amount); - ContractException contractException = assertThrows(ContractException.class, () -> resourcesDataManager.addResourceToRegion(resourceId, regionId, Long.MAX_VALUE)); - assertEquals(ResourceError.RESOURCE_ARITHMETIC_EXCEPTION, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "init", args = {}) - public void testPersonAdditionEvent() { - - // Have an actor create a few people with random resource levels - ResourcesActionSupport.testConsumer(0, 5441878385875188805L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - // create 30 people, testing each in turn for their resource levels - for (int i = 0; i < 30; i++) { - PersonConstructionData.Builder builder = PersonConstructionData.builder(); - // give the person and region - builder.add(TestRegionId.getRandomRegionId(randomGenerator)); - - // give the person a positive resource level for about half of - // the resources - Map expectedResources = new LinkedHashMap<>(); - for (TestResourceId testResourceId : TestResourceId.values()) { - MutableInteger mutableInteger = new MutableInteger(); - expectedResources.put(testResourceId, mutableInteger); - if (randomGenerator.nextBoolean()) { - int amount = randomGenerator.nextInt(30) + 1; - mutableInteger.setValue(amount); - ResourceInitialization resourceInitialization = new ResourceInitialization(testResourceId, (long) amount); - builder.add(resourceInitialization); - } - } - - // create the person which will in turn generate the - // PersonAdditionEvent - PersonId personId = peopleDataManager.addPerson(builder.build()); - - // show that the person has the correct resource levels - for (TestResourceId testResourceId : TestResourceId.values()) { - int actualPersonResourceLevel = (int) resourcesDataManager.getPersonResourceLevel(testResourceId, personId); - int expectedPersonResourceLevel = expectedResources.get(testResourceId).getValue(); - assertEquals(expectedPersonResourceLevel, actualPersonResourceLevel); - } - } - - }); - - /* precondition test: */ - ResourcesActionSupport.testConsumer(0, 3508334533286675130L, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - - /* - * Precondition tests for the validity of the person id are shadowed - * by other plugins and cannot be easily tested - */ - - /* - * if the auxiliary data contains a ResourceInitialization that has - * a null resource id - */ - - ContractException contractException = assertThrows(ContractException.class, () -> { - - peopleDataManager.addPerson(PersonConstructionData.builder().add(TestRegionId.REGION_1).add(new ResourceInitialization(null, 15L)).build()); - }); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - - }); - - /* precondition test: */ - ResourcesActionSupport.testConsumer(0, 7458875943724352968L, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - - /* - * Precondition tests for the validity of the person id are shadowed - * by other plugins and cannot be easily tested - */ - - /* - * if the auxiliary data contains a ResourceInitialization that has - * an unknown resource id - */ - ContractException contractException = assertThrows(ContractException.class, () -> { - peopleDataManager.addPerson(PersonConstructionData .builder() - - .add(TestRegionId.REGION_2).add(new ResourceInitialization(TestResourceId.getUnknownResourceId(), 15L)).build()); - }); - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - - }); - - /* precondition test: */ - ResourcesActionSupport.testConsumer(0, 3702960689314847457L, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - - /* - * Precondition tests for the validity of the person id are shadowed - * by other plugins and cannot be easily tested - */ - - /* - * if the auxiliary data contains a ResourceInitialization that has - * a negative resource level - */ - ContractException contractException = assertThrows(ContractException.class, () -> { - peopleDataManager.addPerson(PersonConstructionData .builder() - - .add(TestRegionId.REGION_3).add(new ResourceInitialization(TestResourceId.RESOURCE_1, -15L)).build()); - }); - assertEquals(ResourceError.NEGATIVE_RESOURCE_AMOUNT, contractException.getErrorType()); - - }); - } - - @Test - @UnitTestMethod(name = "init", args = {}) - public void testBulkPersonAdditionEvent() { - - // Have an actor create a few people with random resource levels in a - // bulk construction request - ResourcesActionSupport.testConsumer(0, 1373835434254978465L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - // prepare builders - BulkPersonConstructionData.Builder bulkBuilder = BulkPersonConstructionData.builder(); - PersonConstructionData.Builder personBuilder = PersonConstructionData.builder(); - // create a map to hold expected resource levels - Map> expectedResources = new LinkedHashMap<>(); - - int numberOfPeople = 30; - - // add the people to the construction data - for (int i = 0; i < numberOfPeople; i++) { - // build the map of expected resources for the person - Map expectationForPerson = new LinkedHashMap<>(); - expectedResources.put(i, expectationForPerson); - // assign a region to the person - personBuilder.add(TestRegionId.getRandomRegionId(randomGenerator)); - // give the person a positive resource level for about half of - // the resources - for (TestResourceId testResourceId : TestResourceId.values()) { - MutableInteger mutableInteger = new MutableInteger(); - expectationForPerson.put(testResourceId, mutableInteger); - if (randomGenerator.nextBoolean()) { - int amount = randomGenerator.nextInt(30) + 1; - mutableInteger.setValue(amount); - ResourceInitialization resourceInitialization = new ResourceInitialization(testResourceId, (long) amount); - personBuilder.add(resourceInitialization); - } - } - bulkBuilder.add(personBuilder.build()); - } - - // create the people which will in turn generate the - // BulkPersonAdditionEvent - int personIdOffset = peopleDataManager.addBulkPeople(bulkBuilder.build()).get().getValue(); - - // show that each person has the correct resource levels - for (int i = 0; i < numberOfPeople; i++) { - Map expectationForPerson = expectedResources.get(i); - PersonId personId = new PersonId(personIdOffset + i); - for (TestResourceId testResourceId : TestResourceId.values()) { - int actualPersonResourceLevel = (int) resourcesDataManager.getPersonResourceLevel(testResourceId, personId); - int expectedPersonResourceLevel = expectationForPerson.get(testResourceId).getValue(); - assertEquals(expectedPersonResourceLevel, actualPersonResourceLevel); - } - } - - }); - - /* - * Precondition tests for the validity of the person id are shadowed by - * other plugins and cannot be easily tested - */ - - /* - * precondition test: if the auxiliary data contains a - * ResourceInitialization that has a null resource id - */ - ResourcesActionSupport.testConsumer(0, 4090942440102620995L, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> { - PersonConstructionData personConstructionData = PersonConstructionData .builder()// - - .add(TestRegionId.REGION_1)// - .add(new ResourceInitialization(null, 15L))// - .build();// - BulkPersonConstructionData bulkPersonConstructionData = BulkPersonConstructionData.builder().add(personConstructionData).build(); - peopleDataManager.addBulkPeople(bulkPersonConstructionData); - }); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - }); - - /* - * precondition test: if the auxiliary data contains a - * ResourceInitialization that has an unknown resource id - */ - ResourcesActionSupport.testConsumer(0, 4932056019543858717L, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> { - PersonConstructionData personConstructionData = PersonConstructionData .builder()// - .add(TestRegionId.REGION_2)// - .add(new ResourceInitialization(TestResourceId.getUnknownResourceId(), 15L))// - .build();// - BulkPersonConstructionData bulkPersonConstructionData = BulkPersonConstructionData.builder().add(personConstructionData).build(); - peopleDataManager.addBulkPeople(bulkPersonConstructionData); - }); - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - }); - - /* - * precondition test: if the auxiliary data contains a - * ResourceInitialization that has a negative resource level - */ - ResourcesActionSupport.testConsumer(0, 4192802703078518338L, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> { - PersonConstructionData personConstructionData = PersonConstructionData .builder()// - .add(TestRegionId.REGION_3)// - .add(new ResourceInitialization(TestResourceId.RESOURCE_1, -15L))// - .build();// - BulkPersonConstructionData bulkPersonConstructionData = BulkPersonConstructionData.builder().add(personConstructionData).build(); - peopleDataManager.addBulkPeople(bulkPersonConstructionData); - }); - assertEquals(ResourceError.NEGATIVE_RESOURCE_AMOUNT, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "init", args = {}) - public void testInitializeResourceDataManager() { - - int initialPopulation = 10; - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1828556358289827784L); - - // create a list of people - List people = new ArrayList<>(); - for (int i = 0; i < initialPopulation; i++) { - people.add(new PersonId(i)); - } - - Builder builder = Simulation.builder(); - - // add the resources plugin - ResourcesPluginData.Builder resourcesBuilder = ResourcesPluginData.builder(); - - for (int i = 0; i < initialPopulation; i++) { - PersonId personId = new PersonId(i); - for (TestResourceId testResourceId : TestResourceId.values()) { - resourcesBuilder.setPersonResourceLevel(personId, testResourceId, randomGenerator.nextInt(5)); - } - } - - for (TestRegionId testRegionId : TestRegionId.values()) { - for (TestResourceId testResourceId : TestResourceId.values()) { - resourcesBuilder.setRegionResourceLevel(testRegionId, testResourceId, randomGenerator.nextInt(5)); - } - } - - for (TestResourceId testResourceId : TestResourceId.values()) { - resourcesBuilder.addResource(testResourceId); - resourcesBuilder.setResourceTimeTracking(testResourceId, testResourceId.getTimeTrackingPolicy()); - } - - for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId.values()) { - TestResourceId testResourceId = testResourcePropertyId.getTestResourceId(); - PropertyDefinition propertyDefinition = testResourcePropertyId.getPropertyDefinition(); - Object propertyValue = testResourcePropertyId.getRandomPropertyValue(randomGenerator); - resourcesBuilder.defineResourceProperty(testResourceId, testResourcePropertyId, propertyDefinition); - resourcesBuilder.setResourcePropertyValue(testResourceId, testResourcePropertyId, propertyValue); - } - - ResourcesPluginData resourcesPluginData = resourcesBuilder.build(); - Plugin resourcesPlugin = ResourcesPlugin.getResourcesPlugin(resourcesPluginData); - builder.addPlugin(resourcesPlugin); - - // add the people plugin - - PeoplePluginData.Builder peopleBuilder = PeoplePluginData.builder(); - - for (PersonId personId : people) { - peopleBuilder.addPersonId(personId); - } - PeoplePluginData peoplePluginData = peopleBuilder.build(); - Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(peoplePluginData); - builder.addPlugin(peoplePlugin); - - - - // add the regions plugin - RegionsPluginData.Builder regionsBuilder = RegionsPluginData.builder(); - for (TestRegionId testRegionId : TestRegionId.values()) { - regionsBuilder.addRegion(testRegionId); - } - for (PersonId personId : people) { - regionsBuilder.setPersonRegion(personId, TestRegionId.getRandomRegionId(randomGenerator)); - } - RegionsPluginData regionsPluginData = regionsBuilder.build(); - Plugin regionPlugin = RegionsPlugin.getRegionsPlugin(regionsPluginData); - builder.addPlugin(regionPlugin); - - - - // add the stochastics plugin - StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder().setSeed(randomGenerator.nextLong()).build(); - Plugin stochasticsPlugin = StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData); - builder.addPlugin(stochasticsPlugin); - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - - List personIds = peopleDataManager.getPeople(); - assertEquals(new LinkedHashSet<>(personIds), resourcesPluginData.getPersonIds()); - - Set expectedRegionIds = regionsDataManager.getRegionIds(); - Set actualRegionIds = resourcesPluginData.getRegionIds(); - assertEquals(expectedRegionIds, actualRegionIds); - - for (RegionId regionId : resourcesPluginData.getRegionIds()) { - for (ResourceId resourceId : resourcesPluginData.getResourceIds()) { - long expectedRegionResourceLevel = resourcesPluginData.getRegionResourceLevel(regionId, resourceId); - long actualRegionResourceLevel = resourcesDataManager.getRegionResourceLevel(regionId, resourceId); - assertEquals(expectedRegionResourceLevel, actualRegionResourceLevel); - } - } - - for (ResourceId resourceId : resourcesPluginData.getResourceIds()) { - TimeTrackingPolicy expectedPolicy = resourcesPluginData.getPersonResourceTimeTrackingPolicy(resourceId); - TimeTrackingPolicy actualPolicy = resourcesDataManager.getPersonResourceTimeTrackingPolicy(resourceId); - assertEquals(expectedPolicy, actualPolicy); - } - - assertEquals(resourcesPluginData.getResourceIds(), resourcesDataManager.getResourceIds()); - - for (PersonId personId : personIds) { - for (ResourceId resourceId : resourcesPluginData.getResourceIds()) { - long expectedPersonResourceLevel = resourcesPluginData.getPersonResourceLevel(personId, resourceId); - long actualPersonResourceLevel = resourcesDataManager.getPersonResourceLevel(resourceId, personId); - assertEquals(expectedPersonResourceLevel, actualPersonResourceLevel); - } - } - for (ResourceId resourceId : resourcesPluginData.getResourceIds()) { - Set expectedResourcePropertyIds = resourcesPluginData.getResourcePropertyIds(resourceId); - Set actualResourcePropertyIds = resourcesDataManager.getResourcePropertyIds(resourceId); - assertEquals(expectedResourcePropertyIds, actualResourcePropertyIds); - - for(ResourcePropertyId resourcePropertyId : expectedResourcePropertyIds) { - PropertyDefinition expectedDefinition = resourcesPluginData.getResourcePropertyDefinition(resourceId, resourcePropertyId); - PropertyDefinition actualDefinition = resourcesDataManager.getResourcePropertyDefinition(resourceId, resourcePropertyId); - assertEquals(expectedDefinition, actualDefinition); - - Object expectedValue = resourcesPluginData.getResourcePropertyValue(resourceId, resourcePropertyId); - Object actualValue = resourcesDataManager.getResourcePropertyValue(resourceId, resourcePropertyId); - assertEquals(expectedValue, actualValue); - - } - } - - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - // add the action plugin - builder.addPlugin(testPlugin); - - // build and execute the engine - ScenarioPlanCompletionObserver scenarioPlanCompletionObserver = new ScenarioPlanCompletionObserver(); - builder.setOutputConsumer(scenarioPlanCompletionObserver::handleOutput).build().execute(); - - // show that all actions were executed - if (!scenarioPlanCompletionObserver.allPlansExecuted()) { - throw new ContractException(TestError.TEST_EXECUTION_FAILURE); - } - - } - - - // 4043641365602447479L - // 5107085853667531414L - // 5551635264070855342L - // 1345117947886682832L - // 2870952108296201475L - // 9101711257710159283L - // 4216397684435821705L - // 9022862258230350395L - // 217976606974469406L - // 8125399461811894989L - // 4130610902285408287L - // 4039871222190675923L - // 8909938597230752836L - // 3776094770483573425L - // 4146350189128134907L - // 8356399638914398643L - // 3890936504108305392L - -} diff --git a/gcm3/src/test/java/plugins/resources/events/AT_PersonResourceUpdateEvent.java b/gcm3/src/test/java/plugins/resources/events/AT_PersonResourceUpdateEvent.java deleted file mode 100644 index 191c45f75..000000000 --- a/gcm3/src/test/java/plugins/resources/events/AT_PersonResourceUpdateEvent.java +++ /dev/null @@ -1,422 +0,0 @@ -package plugins.resources.events; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -import javax.naming.Context; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.Event; -import nucleus.EventLabel; -import nucleus.EventLabeler; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import plugins.regions.datamanagers.RegionsDataManager; -import plugins.regions.support.RegionError; -import plugins.regions.support.RegionId; -import plugins.regions.testsupport.TestRegionId; -import plugins.resources.datamanagers.ResourcesDataManager; -import plugins.resources.support.ResourceError; -import plugins.resources.support.ResourceId; -import plugins.resources.testsupport.ResourcesActionSupport; -import plugins.resources.testsupport.TestResourceId; -import plugins.stochastics.StochasticsDataManager; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - - -@UnitTest(target = PersonResourceUpdateEvent.class) -public class AT_PersonResourceUpdateEvent implements Event { - - @Test - @UnitTestConstructor(args = { PersonId.class, ResourceId.class, long.class, long.class }) - public void testConstructor() { - // nothing to test - } - - @Test - @UnitTestMethod(name = "getResourceId", args = {}) - public void testGetResourceId() { - PersonId personId = new PersonId(7645); - ResourceId resourceId = TestResourceId.RESOURCE_2; - long previousResourceLevel = 45L; - long currentResourceLevel = 398L; - PersonResourceUpdateEvent personResourceUpdateEvent = new PersonResourceUpdateEvent(personId, resourceId, previousResourceLevel, currentResourceLevel); - assertEquals(resourceId, personResourceUpdateEvent.getResourceId()); - } - - @Test - @UnitTestMethod(name = "getCurrentResourceLevel", args = {}) - public void testGetCurrentResourceLevel() { - PersonId personId = new PersonId(7645); - ResourceId resourceId = TestResourceId.RESOURCE_2; - long previousResourceLevel = 45L; - long currentResourceLevel = 398L; - PersonResourceUpdateEvent personResourceUpdateEvent = new PersonResourceUpdateEvent(personId, resourceId, previousResourceLevel, currentResourceLevel); - assertEquals(currentResourceLevel, personResourceUpdateEvent.getCurrentResourceLevel()); - } - - @Test - @UnitTestMethod(name = "getPersonId", args = {}) - public void testGetPersonId() { - PersonId personId = new PersonId(7645); - ResourceId resourceId = TestResourceId.RESOURCE_2; - long previousResourceLevel = 45L; - long currentResourceLevel = 398L; - PersonResourceUpdateEvent personResourceUpdateEvent = new PersonResourceUpdateEvent(personId, resourceId, previousResourceLevel, currentResourceLevel); - assertEquals(personId, personResourceUpdateEvent.getPersonId()); - } - - @Test - @UnitTestMethod(name = "getPreviousResourceLevel", args = {}) - public void testGetPreviousResourceLevel() { - PersonId personId = new PersonId(7645); - ResourceId resourceId = TestResourceId.RESOURCE_2; - long previousResourceLevel = 45L; - long currentResourceLevel = 398L; - PersonResourceUpdateEvent personResourceUpdateEvent = new PersonResourceUpdateEvent(personId, resourceId, previousResourceLevel, currentResourceLevel); - assertEquals(previousResourceLevel, personResourceUpdateEvent.getPreviousResourceLevel()); - } - - @Test - @UnitTestMethod(name = "getEventLabelByRegionAndResource", args = { Context.class, RegionId.class, ResourceId.class }) - public void testGetEventLabelByRegionAndResource() { - ResourcesActionSupport.testConsumer(10, 7912737444879496875L, (c) -> { - - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - Set regionIds = regionsDataManager.getRegionIds(); - Set resourceIds = resourcesDataManager.getResourceIds(); - - Set> eventLabels = new LinkedHashSet<>(); - - for (RegionId regionId : regionIds) { - for (ResourceId resourceId : resourceIds) { - - EventLabel eventLabel = PersonResourceUpdateEvent.getEventLabelByRegionAndResource(c, regionId, resourceId); - - // show that the event label has the correct event class - assertEquals(PersonResourceUpdateEvent.class, eventLabel.getEventClass()); - - // show that the event label has the correct primary key - assertEquals(resourceId, eventLabel.getPrimaryKeyValue()); - - // show that the event label has the same id as its - // associated labeler - EventLabeler eventLabeler = PersonResourceUpdateEvent.getEventLabelerForRegionAndResource(regionsDataManager); - assertEquals(eventLabeler.getEventLabelerId(), eventLabel.getLabelerId()); - - // show that two event labels with the same inputs are equal - EventLabel eventLabel2 = PersonResourceUpdateEvent.getEventLabelByRegionAndResource(c, regionId, resourceId); - assertEquals(eventLabel, eventLabel2); - - // show that equal event labels have equal hash codes - assertEquals(eventLabel.hashCode(), eventLabel2.hashCode()); - - // show that two event labels with different inputs are not - // equal - assertTrue(eventLabels.add(eventLabel)); - } - } - - // precondition tests - - // if the region id is null - ContractException contractException = assertThrows(ContractException.class, - () -> PersonResourceUpdateEvent.getEventLabelByRegionAndResource(c, null, TestResourceId.RESOURCE_1)); - assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); - - // if the region id is unknown - contractException = assertThrows(ContractException.class, - () -> PersonResourceUpdateEvent.getEventLabelByRegionAndResource(c, TestRegionId.getUnknownRegionId(), TestResourceId.RESOURCE_1)); - assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); - - // if the resource id is null - contractException = assertThrows(ContractException.class, () -> PersonResourceUpdateEvent.getEventLabelByRegionAndResource(c, TestRegionId.REGION_1, null)); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - - // if the resource id is unknown - contractException = assertThrows(ContractException.class, - () -> PersonResourceUpdateEvent.getEventLabelByRegionAndResource(c, TestRegionId.REGION_1, TestResourceId.getUnknownResourceId())); - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - - }); - } - - @Test - @UnitTestMethod(name = "getEventLabelerForRegionAndResource", args = {}) - public void testGetEventLabelerForRegionAndResource() { - ResourcesActionSupport.testConsumer(30, 5829392632134617932L, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - Set regionIds = regionsDataManager.getRegionIds(); - Set resourceIds = resourcesDataManager.getResourceIds(); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - // create an event labeler - EventLabeler eventLabeler = PersonResourceUpdateEvent.getEventLabelerForRegionAndResource(regionsDataManager); - - // show that the event labeler has the correct event class - assertEquals(PersonResourceUpdateEvent.class, eventLabeler.getEventClass()); - - // show that the event labeler produces the expected event label - - for (RegionId regionId : regionIds) { - List peopleInRegion = regionsDataManager.getPeopleInRegion(regionId); - if (peopleInRegion.size() > 0) { - for (ResourceId resourceId : resourceIds) { - PersonId personId = peopleInRegion.get(randomGenerator.nextInt(peopleInRegion.size())); - - // derive the expected event label for this event - EventLabel expectedEventLabel = PersonResourceUpdateEvent.getEventLabelByRegionAndResource(c, regionId, resourceId); - - // show that the event label and event labeler have - // equal id - // values - assertEquals(expectedEventLabel.getLabelerId(), eventLabeler.getEventLabelerId()); - - // create an event - PersonResourceUpdateEvent event = new PersonResourceUpdateEvent(personId, resourceId, 10L, 30L); - - // show that the event labeler produces the correct - // event - // label - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - - assertEquals(expectedEventLabel, actualEventLabel); - - } - } - } - - }); - - } - - @Test - @UnitTestMethod(name = "getEventLabelByPersonAndResource", args = { Context.class, PersonId.class, ResourceId.class }) - public void testGetEventLabelByPersonAndResource() { - ResourcesActionSupport.testConsumer(10, 7912737444879496875L, (c) -> { - - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - List people = peopleDataManager.getPeople(); - Set resourceIds = resourcesDataManager.getResourceIds(); - - Set> eventLabels = new LinkedHashSet<>(); - - for (PersonId personId : people) { - for (ResourceId resourceId : resourceIds) { - - EventLabel eventLabel = PersonResourceUpdateEvent.getEventLabelByPersonAndResource(c, personId, resourceId); - - // show that the event label has the correct event class - assertEquals(PersonResourceUpdateEvent.class, eventLabel.getEventClass()); - - // show that the event label has the correct primary key - assertEquals(resourceId, eventLabel.getPrimaryKeyValue()); - - // show that the event label has the same id as its - // associated labeler - EventLabeler eventLabeler = PersonResourceUpdateEvent.getEventLabelerForPersonAndResource(); - assertEquals(eventLabeler.getEventLabelerId(), eventLabel.getLabelerId()); - - // show that two event labels with the same inputs are equal - EventLabel eventLabel2 = PersonResourceUpdateEvent.getEventLabelByPersonAndResource(c, personId, resourceId); - assertEquals(eventLabel, eventLabel2); - - // show that equal event labels have equal hash codes - assertEquals(eventLabel.hashCode(), eventLabel2.hashCode()); - - // show that two event labels with different inputs are not - // equal - assertTrue(eventLabels.add(eventLabel)); - } - } - - // precondition tests - - // if the person id is null - ContractException contractException = assertThrows(ContractException.class, - () -> PersonResourceUpdateEvent.getEventLabelByPersonAndResource(c, null, TestResourceId.RESOURCE_1)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - - // if the person id is unknown - contractException = assertThrows(ContractException.class, () -> PersonResourceUpdateEvent.getEventLabelByPersonAndResource(c, new PersonId(11110), TestResourceId.RESOURCE_1)); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - - // if the resource id is null - contractException = assertThrows(ContractException.class, () -> PersonResourceUpdateEvent.getEventLabelByPersonAndResource(c, new PersonId(0), null)); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - - // if the resource id is unknown - contractException = assertThrows(ContractException.class, - () -> PersonResourceUpdateEvent.getEventLabelByPersonAndResource(c, new PersonId(0), TestResourceId.getUnknownResourceId())); - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - - }); - } - - @Test - @UnitTestMethod(name = "getEventLabelerForPersonAndResource", args = {}) - public void testGetEventLabelerForPersonAndResource() { - ResourcesActionSupport.testConsumer(30, 5829392632134617932L, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - List people = peopleDataManager.getPeople(); - Set resourceIds = resourcesDataManager.getResourceIds(); - - // create an event labeler - EventLabeler eventLabeler = PersonResourceUpdateEvent.getEventLabelerForPersonAndResource(); - - // show that the event labeler has the correct event class - assertEquals(PersonResourceUpdateEvent.class, eventLabeler.getEventClass()); - - // show that the event labeler produces the expected event label - - for (PersonId personId : people) { - - for (ResourceId resourceId : resourceIds) { - - // derive the expected event label for this event - EventLabel expectedEventLabel = PersonResourceUpdateEvent.getEventLabelByPersonAndResource(c, personId, resourceId); - - // show that the event label and event labeler have - // equal id - // values - assertEquals(expectedEventLabel.getLabelerId(), eventLabeler.getEventLabelerId()); - - // create an event - PersonResourceUpdateEvent event = new PersonResourceUpdateEvent(personId, resourceId, 10L, 30L); - - // show that the event labeler produces the correct - // event - // label - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - - assertEquals(expectedEventLabel, actualEventLabel); - - } - - } - - }); - } - - @Test - @UnitTestMethod(name = "getEventLabelByResource", args = { Context.class, ResourceId.class }) - public void testGetEventLabelByResource() { - ResourcesActionSupport.testConsumer(10, 7912737444879496875L, (c) -> { - - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - Set resourceIds = resourcesDataManager.getResourceIds(); - - Set> eventLabels = new LinkedHashSet<>(); - - for (ResourceId resourceId : resourceIds) { - - EventLabel eventLabel = PersonResourceUpdateEvent.getEventLabelByResource(c, resourceId); - - // show that the event label has the correct event class - assertEquals(PersonResourceUpdateEvent.class, eventLabel.getEventClass()); - - // show that the event label has the correct primary key - assertEquals(resourceId, eventLabel.getPrimaryKeyValue()); - - // show that the event label has the same id as its - // associated labeler - EventLabeler eventLabeler = PersonResourceUpdateEvent.getEventLabelerForResource(); - assertEquals(eventLabeler.getEventLabelerId(), eventLabel.getLabelerId()); - - // show that two event labels with the same inputs are equal - EventLabel eventLabel2 = PersonResourceUpdateEvent.getEventLabelByResource(c, resourceId); - assertEquals(eventLabel, eventLabel2); - - // show that equal event labels have equal hash codes - assertEquals(eventLabel.hashCode(), eventLabel2.hashCode()); - - // show that two event labels with different inputs are not - // equal - assertTrue(eventLabels.add(eventLabel)); - } - - // precondition tests - - // if the resource id is null - ContractException contractException = assertThrows(ContractException.class, () -> PersonResourceUpdateEvent.getEventLabelByResource(c, null)); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - - // if the resource id is unknown - contractException = assertThrows(ContractException.class, () -> PersonResourceUpdateEvent.getEventLabelByResource(c, TestResourceId.getUnknownResourceId())); - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - - }); - } - - @Test - @UnitTestMethod(name = "getEventLabelerForResource", args = {}) - public void testGetEventLabelerForResource() { - ResourcesActionSupport.testConsumer(30, 5829392632134617932L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - Set resourceIds = resourcesDataManager.getResourceIds(); - - // create an event labeler - EventLabeler eventLabeler = PersonResourceUpdateEvent.getEventLabelerForResource(); - - // show that the event labeler has the correct event class - assertEquals(PersonResourceUpdateEvent.class, eventLabeler.getEventClass()); - - // show that the event labeler produces the expected event label - - for (ResourceId resourceId : resourceIds) { - - // derive the expected event label for this event - EventLabel expectedEventLabel = PersonResourceUpdateEvent.getEventLabelByResource(c, resourceId); - - // show that the event label and event labeler have - // equal id - // values - assertEquals(expectedEventLabel.getLabelerId(), eventLabeler.getEventLabelerId()); - - // create an event - PersonResourceUpdateEvent event = new PersonResourceUpdateEvent(new PersonId(0), resourceId, 10L, 30L); - - // show that the event labeler produces the correct - // event - // label - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - - assertEquals(expectedEventLabel, actualEventLabel); - - } - - }); - } - - @Test - @UnitTestMethod(name = "getPrimaryKeyValue", args = {}) - public void testGetPrimaryKeyValue() { - - for (TestResourceId testResourceId : TestResourceId.values()) { - PersonId personId = new PersonId(7645); - long previousResourceLevel = 45L; - long currentResourceLevel = 398L; - PersonResourceUpdateEvent personResourceUpdateEvent = new PersonResourceUpdateEvent(personId, testResourceId, previousResourceLevel, currentResourceLevel); - assertEquals(testResourceId, personResourceUpdateEvent.getPrimaryKeyValue()); - } - } - -} diff --git a/gcm3/src/test/java/plugins/resources/events/AT_RegionResourceUpdateEvent.java b/gcm3/src/test/java/plugins/resources/events/AT_RegionResourceUpdateEvent.java deleted file mode 100644 index e2b148fe5..000000000 --- a/gcm3/src/test/java/plugins/resources/events/AT_RegionResourceUpdateEvent.java +++ /dev/null @@ -1,206 +0,0 @@ -package plugins.resources.events; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; - -import javax.naming.Context; - -import org.junit.jupiter.api.Test; - -import nucleus.Event; -import nucleus.EventLabel; -import nucleus.EventLabeler; -import plugins.regions.datamanagers.RegionsDataManager; -import plugins.regions.support.RegionError; -import plugins.regions.support.RegionId; -import plugins.regions.testsupport.TestRegionId; -import plugins.resources.datamanagers.ResourcesDataManager; -import plugins.resources.support.ResourceError; -import plugins.resources.support.ResourceId; -import plugins.resources.testsupport.ResourcesActionSupport; -import plugins.resources.testsupport.TestResourceId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - -@UnitTest(target = RegionResourceUpdateEvent.class) -public class AT_RegionResourceUpdateEvent implements Event { - - @Test - @UnitTestConstructor(args = { RegionId.class, ResourceId.class, long.class, long.class }) - public void testConstructor() { - // nothing to test - } - - @Test - @UnitTestMethod(name = "getResourceId", args = {}) - public void testGetResourceId() { - RegionId regionId = TestRegionId.REGION_4; - ResourceId resourceId = TestResourceId.RESOURCE_2; - long previousResourceLevel = 45L; - long currentResourceLevel = 398L; - RegionResourceUpdateEvent regionResourceUpdateEvent = new RegionResourceUpdateEvent(regionId, resourceId, previousResourceLevel, currentResourceLevel); - assertEquals(resourceId, regionResourceUpdateEvent.getResourceId()); - } - - @Test - @UnitTestMethod(name = "getRegionId", args = {}) - public void testGetRegionId() { - RegionId regionId = TestRegionId.REGION_4; - ResourceId resourceId = TestResourceId.RESOURCE_2; - long previousResourceLevel = 45L; - long currentResourceLevel = 398L; - RegionResourceUpdateEvent regionResourceUpdateEvent = new RegionResourceUpdateEvent(regionId, resourceId, previousResourceLevel, currentResourceLevel); - assertEquals(regionId, regionResourceUpdateEvent.getRegionId()); - } - - @Test - @UnitTestMethod(name = "getPreviousResourceLevel", args = {}) - public void testGetPreviousResourceLevel() { - RegionId regionId = TestRegionId.REGION_4; - ResourceId resourceId = TestResourceId.RESOURCE_2; - long previousResourceLevel = 45L; - long currentResourceLevel = 398L; - RegionResourceUpdateEvent regionResourceUpdateEvent = new RegionResourceUpdateEvent(regionId, resourceId, previousResourceLevel, currentResourceLevel); - assertEquals(previousResourceLevel, regionResourceUpdateEvent.getPreviousResourceLevel()); - } - - @Test - @UnitTestMethod(name = "getCurrentResourceLevel", args = {}) - public void testGetCurrentResourceLevel() { - RegionId regionId = TestRegionId.REGION_4; - ResourceId resourceId = TestResourceId.RESOURCE_2; - long previousResourceLevel = 45L; - long currentResourceLevel = 398L; - RegionResourceUpdateEvent regionResourceUpdateEvent = new RegionResourceUpdateEvent(regionId, resourceId, previousResourceLevel, currentResourceLevel); - assertEquals(currentResourceLevel, regionResourceUpdateEvent.getCurrentResourceLevel()); - } - - @Test - @UnitTestMethod(name = "getEventLabelByRegionAndResource", args = { Context.class, RegionId.class, ResourceId.class }) - public void testGetEventLabelByRegionAndResource() { - ResourcesActionSupport.testConsumer(10, 7912737444879496875L, (c) -> { - - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - Set regionIds = regionsDataManager.getRegionIds(); - Set resourceIds = resourcesDataManager.getResourceIds(); - - Set> eventLabels = new LinkedHashSet<>(); - - for (RegionId regionId : regionIds) { - for (ResourceId resourceId : resourceIds) { - - EventLabel eventLabel = RegionResourceUpdateEvent.getEventLabelByRegionAndResource(c, regionId, resourceId); - - // show that the event label has the correct event class - assertEquals(RegionResourceUpdateEvent.class, eventLabel.getEventClass()); - - // show that the event label has the correct primary key - assertEquals(resourceId, eventLabel.getPrimaryKeyValue()); - - // show that the event label has the same id as its - // associated labeler - EventLabeler eventLabeler = RegionResourceUpdateEvent.getEventLabelerForRegionAndResource(); - assertEquals(eventLabeler.getEventLabelerId(), eventLabel.getLabelerId()); - - // show that two event labels with the same inputs are equal - EventLabel eventLabel2 = RegionResourceUpdateEvent.getEventLabelByRegionAndResource(c, regionId, resourceId); - assertEquals(eventLabel, eventLabel2); - - // show that equal event labels have equal hash codes - assertEquals(eventLabel.hashCode(), eventLabel2.hashCode()); - - // show that two event labels with different inputs are not - // equal - assertTrue(eventLabels.add(eventLabel)); - } - } - - // precondition tests - - // if the region id is null - ContractException contractException = assertThrows(ContractException.class, - () -> RegionResourceUpdateEvent.getEventLabelByRegionAndResource(c, null, TestResourceId.RESOURCE_1)); - assertEquals(RegionError.NULL_REGION_ID, contractException.getErrorType()); - - // if the region id is unknown - contractException = assertThrows(ContractException.class, - () -> RegionResourceUpdateEvent.getEventLabelByRegionAndResource(c, TestRegionId.getUnknownRegionId(), TestResourceId.RESOURCE_1)); - assertEquals(RegionError.UNKNOWN_REGION_ID, contractException.getErrorType()); - - // if the resource id is null - contractException = assertThrows(ContractException.class, () -> RegionResourceUpdateEvent.getEventLabelByRegionAndResource(c, TestRegionId.REGION_5, null)); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - - // if the resource id is unknown - contractException = assertThrows(ContractException.class, - () -> RegionResourceUpdateEvent.getEventLabelByRegionAndResource(c, TestRegionId.REGION_5, TestResourceId.getUnknownResourceId())); - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - - }); - } - - @Test - @UnitTestMethod(name = "getEventLabelerForRegionAndResource", args = {}) - public void testGetEventLabelerForRegionAndResource() { - ResourcesActionSupport.testConsumer(30, 5829392632134617932L, (c) -> { - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - Set regionIds = regionsDataManager.getRegionIds(); - Set resourceIds = resourcesDataManager.getResourceIds(); - - // create an event labeler - EventLabeler eventLabeler = RegionResourceUpdateEvent.getEventLabelerForRegionAndResource(); - - // show that the event labeler has the correct event class - assertEquals(RegionResourceUpdateEvent.class, eventLabeler.getEventClass()); - - // show that the event labeler produces the expected event label - - for (RegionId regionId : regionIds) { - - for (ResourceId resourceId : resourceIds) { - - // derive the expected event label for this event - EventLabel expectedEventLabel = RegionResourceUpdateEvent.getEventLabelByRegionAndResource(c, regionId, resourceId); - - // show that the event label and event labeler have - // equal id - // values - assertEquals(expectedEventLabel.getLabelerId(), eventLabeler.getEventLabelerId()); - - // create an event - RegionResourceUpdateEvent event = new RegionResourceUpdateEvent(regionId, resourceId, 10L, 30L); - - // show that the event labeler produces the correct - // event - // label - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - - assertEquals(expectedEventLabel, actualEventLabel); - - } - } - - }); - } - - @Test - @UnitTestMethod(name = "getPrimaryKeyValue", args = {}) - public void testGetPrimaryKeyValue() { - for (TestResourceId testResourceId : TestResourceId.values()) { - RegionId regionId = TestRegionId.REGION_4; - long previousResourceLevel = 45L; - long currentResourceLevel = 398L; - RegionResourceUpdateEvent regionResourceUpdateEvent = new RegionResourceUpdateEvent(regionId, testResourceId, previousResourceLevel, currentResourceLevel); - assertEquals(testResourceId, regionResourceUpdateEvent.getPrimaryKeyValue()); - } - } - -} diff --git a/gcm3/src/test/java/plugins/resources/events/AT_ResourcePropertyUpdateEvent.java b/gcm3/src/test/java/plugins/resources/events/AT_ResourcePropertyUpdateEvent.java deleted file mode 100644 index 359b7e9ed..000000000 --- a/gcm3/src/test/java/plugins/resources/events/AT_ResourcePropertyUpdateEvent.java +++ /dev/null @@ -1,200 +0,0 @@ -package plugins.resources.events; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; - -import javax.naming.Context; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.Event; -import nucleus.EventLabel; -import nucleus.EventLabeler; -import plugins.resources.support.ResourceError; -import plugins.resources.support.ResourceId; -import plugins.resources.support.ResourcePropertyId; -import plugins.resources.testsupport.ResourcesActionSupport; -import plugins.resources.testsupport.TestResourceId; -import plugins.resources.testsupport.TestResourcePropertyId; -import plugins.stochastics.StochasticsDataManager; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - -@UnitTest(target = ResourcePropertyUpdateEvent.class) - -public class AT_ResourcePropertyUpdateEvent implements Event { - - @Test - @UnitTestConstructor(args = { ResourceId.class, ResourcePropertyId.class, Object.class, Object.class }) - public void testConstructor() { - // nothing to test - } - - @Test - @UnitTestMethod(name = "getResourceId", args = {}) - public void testGetResourceId() { - ResourceId resourceId = TestResourceId.RESOURCE_3; - ResourcePropertyId resourcePropertyId = TestResourcePropertyId.ResourceProperty_3_2_STRING_MUTABLE; - Object previousValue = "previous"; - Object currentValue = "current"; - ResourcePropertyUpdateEvent resourcePropertyUpdateEvent = new ResourcePropertyUpdateEvent(resourceId, resourcePropertyId, previousValue, currentValue); - assertEquals(resourceId, resourcePropertyUpdateEvent.getResourceId()); - } - - @Test - @UnitTestMethod(name = "getResourcePropertyId", args = {}) - public void testGetResourcePropertyId() { - ResourceId resourceId = TestResourceId.RESOURCE_3; - ResourcePropertyId resourcePropertyId = TestResourcePropertyId.ResourceProperty_3_2_STRING_MUTABLE; - Object previousValue = "previous"; - Object currentValue = "current"; - ResourcePropertyUpdateEvent resourcePropertyUpdateEvent = new ResourcePropertyUpdateEvent(resourceId, resourcePropertyId, previousValue, currentValue); - assertEquals(resourcePropertyId, resourcePropertyUpdateEvent.getResourcePropertyId()); - } - - @Test - @UnitTestMethod(name = "getPreviousPropertyValue", args = {}) - public void testGetPreviousPropertyValue() { - ResourceId resourceId = TestResourceId.RESOURCE_3; - ResourcePropertyId resourcePropertyId = TestResourcePropertyId.ResourceProperty_3_2_STRING_MUTABLE; - Object previousValue = "previous"; - Object currentValue = "current"; - ResourcePropertyUpdateEvent resourcePropertyUpdateEvent = new ResourcePropertyUpdateEvent(resourceId, resourcePropertyId, previousValue, currentValue); - assertEquals(previousValue, resourcePropertyUpdateEvent.getPreviousPropertyValue()); - } - - @Test - @UnitTestMethod(name = "getCurrentPropertyValue", args = {}) - public void testGetCurrentPropertyValue() { - ResourceId resourceId = TestResourceId.RESOURCE_3; - ResourcePropertyId resourcePropertyId = TestResourcePropertyId.ResourceProperty_3_2_STRING_MUTABLE; - Object previousValue = "previous"; - Object currentValue = "current"; - ResourcePropertyUpdateEvent resourcePropertyUpdateEvent = new ResourcePropertyUpdateEvent(resourceId, resourcePropertyId, previousValue, currentValue); - assertEquals(currentValue, resourcePropertyUpdateEvent.getCurrentPropertyValue()); - } - - @Test - @UnitTestMethod(name = "getEventLabel", args = { Context.class, ResourceId.class, ResourcePropertyId.class }) - public void testGetEventLabel() { - ResourcesActionSupport.testConsumer(10, 7912737444879496875L, (c) -> { - - Set> eventLabels = new LinkedHashSet<>(); - - for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId.values()) { - ResourceId resourceId = testResourcePropertyId.getTestResourceId(); - - EventLabel eventLabel = ResourcePropertyUpdateEvent.getEventLabel(c, resourceId, testResourcePropertyId); - - // show that the event label has the correct event class - assertEquals(ResourcePropertyUpdateEvent.class, eventLabel.getEventClass()); - - // show that the event label has the correct primary key - assertEquals(testResourcePropertyId, eventLabel.getPrimaryKeyValue()); - - // show that the event label has the same id as its - // associated labeler - EventLabeler eventLabeler = ResourcePropertyUpdateEvent.getEventLabeler(); - assertEquals(eventLabeler.getEventLabelerId(), eventLabel.getLabelerId()); - - // show that two event labels with the same inputs are equal - EventLabel eventLabel2 = ResourcePropertyUpdateEvent.getEventLabel(c, resourceId, testResourcePropertyId); - assertEquals(eventLabel, eventLabel2); - - // show that equal event labels have equal hash codes - assertEquals(eventLabel.hashCode(), eventLabel2.hashCode()); - - // show that two event labels with different inputs are not - // equal - assertTrue(eventLabels.add(eventLabel)); - } - - // precondition tests - - // if the resource id is null - ContractException contractException = assertThrows(ContractException.class, - () -> ResourcePropertyUpdateEvent.getEventLabel(c, null, TestResourcePropertyId.ResourceProperty_1_3_DOUBLE_MUTABLE)); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - - // if the resource id is unknown - contractException = assertThrows(ContractException.class, - () -> ResourcePropertyUpdateEvent.getEventLabel(c, TestResourceId.getUnknownResourceId(), TestResourcePropertyId.ResourceProperty_1_3_DOUBLE_MUTABLE)); - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - - // if the resource property id is null - contractException = assertThrows(ContractException.class, - () -> ResourcePropertyUpdateEvent.getEventLabel(c, TestResourceId.RESOURCE_1, null)); - assertEquals(ResourceError.NULL_RESOURCE_PROPERTY_ID, contractException.getErrorType()); - - // if the resource property id is unknown - contractException = assertThrows(ContractException.class, - () -> ResourcePropertyUpdateEvent.getEventLabel(c, TestResourceId.RESOURCE_1, TestResourcePropertyId.getUnknownResourcePropertyId())); - assertEquals(ResourceError.UNKNOWN_RESOURCE_PROPERTY_ID, contractException.getErrorType()); - - }); - } - - @Test - @UnitTestMethod(name = "getEventLabeler", args = {}) - public void testGetEventLabeler() { - ResourcesActionSupport.testConsumer(30, 5829392632134617932L, (c) -> { - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - // create an event labeler - EventLabeler eventLabeler = ResourcePropertyUpdateEvent.getEventLabeler(); - - // show that the event labeler has the correct event class - assertEquals(ResourcePropertyUpdateEvent.class, eventLabeler.getEventClass()); - - // show that the event labeler produces the expected event label - - for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId.values()) { - ResourceId resourceId = testResourcePropertyId.getTestResourceId(); - - // derive the expected event label for this event - EventLabel expectedEventLabel = ResourcePropertyUpdateEvent.getEventLabel(c, resourceId, testResourcePropertyId); - - /* - * show that the event label and event labeler have equal id - * values - */ - assertEquals(expectedEventLabel.getLabelerId(), eventLabeler.getEventLabelerId()); - - // create an event - Object previousValue = testResourcePropertyId.getRandomPropertyValue(randomGenerator); - Object currentValue = testResourcePropertyId.getRandomPropertyValue(randomGenerator); - ResourcePropertyUpdateEvent event = new ResourcePropertyUpdateEvent(resourceId, testResourcePropertyId, previousValue, currentValue); - - /* - * show that the event labeler produces the correct event label - */ - EventLabel actualEventLabel = eventLabeler.getEventLabel(c, event); - - assertEquals(expectedEventLabel, actualEventLabel); - - } - - }); - } - - @Test - @UnitTestMethod(name = "getPrimaryKeyValue", args = {}) - public void testGetPrimaryKeyValue() { - for (TestResourcePropertyId testResourcePropertyId : TestResourcePropertyId.values()) { - ResourceId resourceId = testResourcePropertyId.getTestResourceId(); - Object previousValue = "previous"; - Object currentValue = "current"; - ResourcePropertyUpdateEvent resourcePropertyUpdateEvent = new ResourcePropertyUpdateEvent(resourceId, testResourcePropertyId, previousValue, currentValue); - assertEquals(testResourcePropertyId, resourcePropertyUpdateEvent.getPrimaryKeyValue()); - } - } - -} diff --git a/gcm3/src/test/java/plugins/resources/support/AT_ResourceError.java b/gcm3/src/test/java/plugins/resources/support/AT_ResourceError.java deleted file mode 100644 index 0761a0e8f..000000000 --- a/gcm3/src/test/java/plugins/resources/support/AT_ResourceError.java +++ /dev/null @@ -1,30 +0,0 @@ -package plugins.resources.support; - -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = ResourceError.class) -public class AT_ResourceError { - - @Test - @UnitTestMethod(name = "getDescription", args = {}) - public void test() { - //show that each description is a unique, non-null and non-empty string - Set descriptions = new LinkedHashSet<>(); - for(ResourceError resourceError : ResourceError.values()) { - String description = resourceError.getDescription(); - assertNotNull(description,"null description for "+resourceError); - assertTrue(description.length()>0, "empty string for "+resourceError); - boolean unique = descriptions.add(description); - assertTrue(unique,"description for "+resourceError+" is not unique"); - } - } -} diff --git a/gcm3/src/test/java/plugins/resources/support/AT_ResourceFilter.java b/gcm3/src/test/java/plugins/resources/support/AT_ResourceFilter.java deleted file mode 100644 index d1beae724..000000000 --- a/gcm3/src/test/java/plugins/resources/support/AT_ResourceFilter.java +++ /dev/null @@ -1,128 +0,0 @@ -package plugins.resources.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.util.Set; - -import javax.naming.Context; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.NucleusError; -import plugins.partitions.support.Equality; -import plugins.partitions.support.Filter; -import plugins.partitions.support.FilterSensitivity; -import plugins.partitions.support.PartitionError; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.PersonId; -import plugins.regions.datamanagers.RegionsDataManager; -import plugins.regions.support.RegionId; -import plugins.resources.datamanagers.ResourcesDataManager; -import plugins.resources.events.PersonResourceUpdateEvent; -import plugins.resources.testsupport.ResourcesActionSupport; -import plugins.resources.testsupport.TestResourceId; -import plugins.stochastics.StochasticsDataManager; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - -/** - * Test unit for {@link ResourceFilter}. - * - * @author Shawn Hatch - * - */ -@UnitTest(target = ResourceFilter.class) -public class AT_ResourceFilter { - @Test - @UnitTestMethod(name = "getFilterSensitivities", args = {}) - public void testGetFilterSensitivities() { - - ResourcesActionSupport.testConsumer(12, 5802033011343021047L, (c) -> { - Filter filter = new ResourceFilter(TestResourceId.RESOURCE_1, Equality.EQUAL, 12L); - - Set> filterSensitivities = filter.getFilterSensitivities(); - assertNotNull(filterSensitivities); - assertEquals(filterSensitivities.size(), 1); - - FilterSensitivity filterSensitivity = filterSensitivities.iterator().next(); - assertEquals(PersonResourceUpdateEvent.class, filterSensitivity.getEventClass()); - - }); - - } - - @Test - @UnitTestMethod(name = "validate", args = { Context.class }) - public void testValidate() { - - ResourcesActionSupport.testConsumer(12, 6989281647149803633L, (c) -> { - // if the equality operator is null - ContractException contractException = assertThrows(ContractException.class, () -> new ResourceFilter(TestResourceId.RESOURCE_1, null, 12L).validate(c)); - assertEquals(PartitionError.NULL_EQUALITY_OPERATOR, contractException.getErrorType()); - - // ResourceError.NULL_RESOURCE_ID - contractException = assertThrows(ContractException.class, () -> new ResourceFilter(null, Equality.GREATER_THAN, 12L).validate(c)); - assertEquals(ResourceError.NULL_RESOURCE_ID, contractException.getErrorType()); - - // NucleusError.NULL_CONTEXT - contractException = assertThrows(ContractException.class, () -> new ResourceFilter(TestResourceId.RESOURCE_1, Equality.GREATER_THAN, 12L).validate(null)); - assertEquals(NucleusError.NULL_SIMULATION_CONTEXT, contractException.getErrorType()); - - // ResourceError.UNKNOWN_RESOURCE_ID - contractException = assertThrows(ContractException.class, () -> new ResourceFilter(TestResourceId.getUnknownResourceId(), Equality.GREATER_THAN, 12L).validate(c)); - assertEquals(ResourceError.UNKNOWN_RESOURCE_ID, contractException.getErrorType()); - }); - - } - - @Test - @UnitTestMethod(name = "evaluate", args = { Context.class, PersonId.class }) - public void testEvaluate() { - - ResourcesActionSupport.testConsumer(100, 5313696152098995059L, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - Filter filter = new ResourceFilter(TestResourceId.RESOURCE_1, Equality.GREATER_THAN, 12L); - - for (PersonId personId : peopleDataManager.getPeople()) { - long amount = randomGenerator.nextInt(10) + 7; - RegionId regionId = regionsDataManager.getPersonRegion(personId); - resourcesDataManager.addResourceToRegion(TestResourceId.RESOURCE_1, regionId, amount); - resourcesDataManager.transferResourceToPersonFromRegion(TestResourceId.RESOURCE_1, personId, amount); - } - - for (PersonId personId : peopleDataManager.getPeople()) { - long personResourceLevel = resourcesDataManager.getPersonResourceLevel(TestResourceId.RESOURCE_1, personId); - boolean expected = personResourceLevel > 12L; - boolean actual = filter.evaluate(c, personId); - assertEquals(expected, actual); - } - - /* precondition: if the context is null */ - assertThrows(RuntimeException.class, () -> filter.evaluate(null, new PersonId(0))); - - /* precondition: if the person id is null */ - assertThrows(RuntimeException.class, () -> filter.evaluate(c, null)); - - /* precondition: if the person id is unknown */ - assertThrows(RuntimeException.class, () -> filter.evaluate(c, new PersonId(123412342))); - }); - - } - - @Test - @UnitTestConstructor(args = {}) - public void testConstructor() { - // nothing to test - } - -} diff --git a/gcm3/src/test/java/plugins/resources/support/AT_ResourceId.java b/gcm3/src/test/java/plugins/resources/support/AT_ResourceId.java deleted file mode 100644 index 68ca6d4d9..000000000 --- a/gcm3/src/test/java/plugins/resources/support/AT_ResourceId.java +++ /dev/null @@ -1,14 +0,0 @@ -package plugins.resources.support; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - -@UnitTest(target = ResourceId.class) -public class AT_ResourceId { - - @Test - public void test() { - //nothing to test - } -} diff --git a/gcm3/src/test/java/plugins/resources/support/AT_ResourceInitialization.java b/gcm3/src/test/java/plugins/resources/support/AT_ResourceInitialization.java deleted file mode 100644 index f2a4fe471..000000000 --- a/gcm3/src/test/java/plugins/resources/support/AT_ResourceInitialization.java +++ /dev/null @@ -1,46 +0,0 @@ -package plugins.resources.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -import plugins.resources.testsupport.TestResourceId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = ResourceInitialization.class) -public class AT_ResourceInitialization { - - @Test - @UnitTestConstructor(args = { ResourceId.class, Long.class }) - public void testConstructor() { - // nothing to test - } - - @Test - @UnitTestMethod(name = "getResourceId", args = {}) - public void testGetResourceId() { - for (TestResourceId testResourceId : TestResourceId.values()) { - ResourceInitialization resourceInitialization = new ResourceInitialization(testResourceId, 123L); - assertEquals(testResourceId, resourceInitialization.getResourceId()); - } - } - - @Test - @UnitTestMethod(name = "getAmount", args = {}) - public void testGetAmount() { - for (long value = 0; value < 10; value++) { - ResourceInitialization resourceInitialization = new ResourceInitialization(TestResourceId.RESOURCE_3, value); - assertEquals(value, resourceInitialization.getAmount().longValue()); - } - } - - @Test - @UnitTestMethod(name = "toString", args = {}) - public void testToString() { - ResourceInitialization resourceInitialization = new ResourceInitialization(TestResourceId.RESOURCE_3, 15L); - assertEquals("ResourceAssignment [resourceId=RESOURCE_3, amount=15]", resourceInitialization.toString()); - } - -} diff --git a/gcm3/src/test/java/plugins/resources/support/AT_ResourceLabeler.java b/gcm3/src/test/java/plugins/resources/support/AT_ResourceLabeler.java deleted file mode 100644 index a10f90e54..000000000 --- a/gcm3/src/test/java/plugins/resources/support/AT_ResourceLabeler.java +++ /dev/null @@ -1,202 +0,0 @@ -package plugins.resources.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; - -import javax.naming.Context; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.Event; -import nucleus.Plugin; -import nucleus.SimulationContext; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import plugins.partitions.support.LabelerSensitivity; -import plugins.people.datamanagers.PeopleDataManager; -import plugins.people.support.PersonError; -import plugins.people.support.PersonId; -import plugins.regions.datamanagers.RegionsDataManager; -import plugins.regions.support.RegionId; -import plugins.resources.datamanagers.ResourcesDataManager; -import plugins.resources.events.PersonResourceUpdateEvent; -import plugins.resources.testsupport.ResourcesActionSupport; -import plugins.resources.testsupport.TestResourceId; -import plugins.stochastics.StochasticsDataManager; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - -@UnitTest(target = ResourceLabeler.class) -public final class AT_ResourceLabeler { - - @Test - @UnitTestConstructor(args = { ResourceId.class, Function.class }) - public void testConstructor() { - assertNotNull(new ResourceLabeler(TestResourceId.RESOURCE_3, (v) -> null)); - } - - @Test - @UnitTestMethod(name = "getLabelerSensitivities", args = {}) - public void testGetLabelerSensitivities() { - /* - * Get the labeler sensitivities and show that they are consistent with - * their documented behaviors. - */ - ResourceLabeler resourceLabeler = new ResourceLabeler(TestResourceId.RESOURCE_1, (c) -> null); - - Set> labelerSensitivities = resourceLabeler.getLabelerSensitivities(); - - // show that there is exactly one sensitivity - assertEquals(1, labelerSensitivities.size()); - - // show that the sensitivity is associated with - // PersonResourceUpdateEvent - LabelerSensitivity labelerSensitivity = labelerSensitivities.iterator().next(); - assertEquals(PersonResourceUpdateEvent.class, labelerSensitivity.getEventClass()); - - // show that the sensitivity will return the person id from a - // PersonResourceUpdateEvent - PersonId personId = new PersonId(56); - - // show that an event that does not match the resource type will not - // return a person id - PersonResourceUpdateEvent personResourceUpdateEvent = new PersonResourceUpdateEvent(personId, TestResourceId.RESOURCE_4, 25L, 17L); - Optional optional = labelerSensitivity.getPersonId(personResourceUpdateEvent); - assertFalse(optional.isPresent()); - - // show that an event that matches the resource type will return the - // correct person id - personResourceUpdateEvent = new PersonResourceUpdateEvent(personId, TestResourceId.RESOURCE_1, 25L, 17L); - optional = labelerSensitivity.getPersonId(personResourceUpdateEvent); - assertTrue(optional.isPresent()); - - assertEquals(personId, optional.get()); - } - - @Test - @UnitTestMethod(name = "getLabel", args = { SimulationContext.class, PersonId.class }) - public void testGetLabel() { - /* - * Create a resource labeler from a function. Have an agent apply the - * function directly to a person's resource to get a label for that - * person. Get the label from the resource labeler from the person id - * alone. Compare the two labels for equality. - */ - - TestPluginData.Builder pluginBuilder = TestPluginData.builder(); - - // build a resource labeler with a function that can be tested - Function function = (v) -> { - return v % 2; - }; - - ResourceLabeler resourceLabeler = new ResourceLabeler(TestResourceId.RESOURCE_1, function); - - // distribute random resources across people - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> { - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - RegionsDataManager regionsDataManager = c.getDataManager(RegionsDataManager.class); - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - for (PersonId personId : peopleDataManager.getPeople()) { - RegionId regionId = regionsDataManager.getPersonRegion(personId); - for (TestResourceId testResourceId : TestResourceId.values()) { - long amount = randomGenerator.nextInt(100) + 1; - resourcesDataManager.addResourceToRegion(testResourceId, regionId, amount); - resourcesDataManager.transferResourceToPersonFromRegion(testResourceId, personId, amount); - } - } - })); - - /* - * Have the agent show that the resource labeler created above - * produces a label for each person that is consistent with the function - * passed to the resource labeler. - */ - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(1, (c) -> { - PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); - ResourcesDataManager resourcesDataManager = c.getDataManager(ResourcesDataManager.class); - List people = peopleDataManager.getPeople(); - for (PersonId personId : people) { - - // get the person's resource and apply the function directly - long personResourceLevel = resourcesDataManager.getPersonResourceLevel(TestResourceId.RESOURCE_1, personId); - Object expectedLabel = function.apply(personResourceLevel); - - // get the label from the person id - Object actualLabel = resourceLabeler.getLabel(c, personId); - - // show that the two labels are equal - assertEquals(expectedLabel, actualLabel); - - } - })); - - // test preconditions - pluginBuilder.addTestActorPlan("actor", new TestActorPlan(2, (c) -> { - - // if the person does not exist - ContractException contractException = assertThrows(ContractException.class, () -> resourceLabeler.getLabel(c, new PersonId(-1))); - assertEquals(PersonError.UNKNOWN_PERSON_ID, contractException.getErrorType()); - - // if the person id is null - contractException = assertThrows(ContractException.class, () -> resourceLabeler.getLabel(c, null)); - assertEquals(PersonError.NULL_PERSON_ID, contractException.getErrorType()); - - })); - - TestPluginData testPluginData = pluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - ResourcesActionSupport.testConsumers(10, 7394122902151816457L, testPlugin); - - } - - @Test - @UnitTestMethod(name = "getDimension", args = {}) - public void testGetDimension() { - for (TestResourceId testResourceId : TestResourceId.values()) { - assertEquals(testResourceId, new ResourceLabeler(testResourceId, (c) -> null).getDimension()); - } - } - - @Test - @UnitTestMethod(name = "getPastLabel", args = { Context.class, Event.class }) - public void testGetPastLabel() { - ResourcesActionSupport.testConsumer(10, 6601261985382450295L, (c) -> { - - final PersonId personId = new PersonId(45); - final ResourceId resourceId = TestResourceId.RESOURCE_4; - long previousResourceLevel = 14L; - final long currentResourceLevel = 99L; - - Function function = (v) -> { - return v % 2; - }; - - ResourceLabeler resourceLabeler = new ResourceLabeler(TestResourceId.RESOURCE_1, function); - - for (int i = 0; i < 10; i++) { - previousResourceLevel = i; - Object expectedLabel = function.apply(previousResourceLevel); - PersonResourceUpdateEvent personResourceUpdateEvent = new PersonResourceUpdateEvent(personId, resourceId, previousResourceLevel, currentResourceLevel); - Object actualLabel = resourceLabeler.getPastLabel(c, personResourceUpdateEvent); - assertEquals(expectedLabel, actualLabel); - } - }); - - } - -} diff --git a/gcm3/src/test/java/plugins/resources/support/AT_ResourcePropertyId.java b/gcm3/src/test/java/plugins/resources/support/AT_ResourcePropertyId.java deleted file mode 100644 index d70189da7..000000000 --- a/gcm3/src/test/java/plugins/resources/support/AT_ResourcePropertyId.java +++ /dev/null @@ -1,14 +0,0 @@ -package plugins.resources.support; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - -@UnitTest(target = ResourcePropertyId.class) -public class AT_ResourcePropertyId { - - @Test - public void test() { - //nothing to test - } -} diff --git a/gcm3/src/test/java/plugins/stochastics/AT_StochasticsDataManager.java b/gcm3/src/test/java/plugins/stochastics/AT_StochasticsDataManager.java deleted file mode 100644 index 75d877c31..000000000 --- a/gcm3/src/test/java/plugins/stochastics/AT_StochasticsDataManager.java +++ /dev/null @@ -1,191 +0,0 @@ -package plugins.stochastics; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.DataManagerContext; -import plugins.stochastics.support.RandomNumberGeneratorId; -import plugins.stochastics.support.StochasticsError; -import plugins.stochastics.testsupport.StochasticsActionSupport; -import plugins.stochastics.testsupport.TestRandomGeneratorId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; - -@UnitTest(target = StochasticsDataManager.class) -public class AT_StochasticsDataManager { - - @Test - @UnitTestMethod(name = "init", args = { DataManagerContext.class }) - public void testInit() { - // nothing to test - } - - @Test - @UnitTestMethod(name = "getRandomNumberGeneratorIds", args = {}) - public void testGetRandomNumberGeneratorIds() { - - StochasticsActionSupport.testConsumer(2276874395058795370L, (c) -> { - - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - - Set actualRandomNumberGeneratorIds = stochasticsDataManager.getRandomNumberGeneratorIds(); - - Set expectedRandomGeneratorIds = new LinkedHashSet<>(); - for (TestRandomGeneratorId testRandomGeneratorId : TestRandomGeneratorId.values()) { - expectedRandomGeneratorIds.add(testRandomGeneratorId); - } - assertEquals(expectedRandomGeneratorIds, actualRandomNumberGeneratorIds); - }); - } - - @Test - @UnitTestMethod(name = "getRandomGeneratorFromId", args = { RandomNumberGeneratorId.class }) - public void testGetRandomGeneratorFromId() { - - // show that random generators can be retrieved by ids. - StochasticsActionSupport.testConsumer(5489824520767978373L, (c) -> { - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - for (TestRandomGeneratorId testRandomGeneratorId : TestRandomGeneratorId.values()) { - RandomGenerator randomGeneratorFromId = stochasticsDataManager.getRandomGeneratorFromId(testRandomGeneratorId); - assertNotNull(randomGeneratorFromId); - } - }); - - // show that an unknown random number generator id will retrieve a random generator - StochasticsActionSupport.testConsumer(2276874395058795370L, (c) -> { - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - - RandomNumberGeneratorId randomNumberGeneratorIdA = new RandomNumberGeneratorId() { - @Override - public String toString() { - return "some string"; - } - }; - - RandomNumberGeneratorId randomNumberGeneratorIdB = new RandomNumberGeneratorId() { - @Override - public String toString() { - return "some string"; - } - }; - - - //show that random number generators can be retrieved for new id values - RandomGenerator randomGeneratorFromIdA = stochasticsDataManager.getRandomGeneratorFromId(randomNumberGeneratorIdA); - assertNotNull(randomGeneratorFromIdA); - - RandomGenerator randomGeneratorFromIdB = stochasticsDataManager.getRandomGeneratorFromId(randomNumberGeneratorIdB); - assertNotNull(randomGeneratorFromIdB); - - //show that the random generators are identical since their ids evaluate to same string and were generated under the same base seed value(no reseed invocations between generators) - - for(int i = 0;i<10;i++) { - long valueA = randomGeneratorFromIdA.nextLong(); - long valueB = randomGeneratorFromIdB.nextLong(); - assertEquals(valueA, valueB); - } - - }); - - - // precondition test : if the random number generator is null - StochasticsActionSupport.testConsumer(5489824520767978373L, (c) -> { - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - ContractException contractException = assertThrows(ContractException.class, () -> stochasticsDataManager.getRandomGeneratorFromId(null)); - assertEquals(StochasticsError.NULL_RANDOM_NUMBER_GENERATOR_ID, contractException.getErrorType()); - }); - - - } - - @Test - @UnitTestMethod(name = "getRandomGenerator", args = {}) - public void testGetRandomGenerator() { - // show that random generators can be retrieved by ids - StochasticsActionSupport.testConsumer(683597885444214892L, (c) -> { - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - RandomGenerator randomGeneratorFromId = stochasticsDataManager.getRandomGenerator(); - assertNotNull(randomGeneratorFromId); - }); - } - - @Test - @UnitTestConstructor(args = { StochasticsPluginData.class }) - public void testConstructor() { - // test of constructor is covered by the method tests - } - - @Test - @UnitTestMethod(name = "resetSeeds", args = { long.class }) - public void testResetSeeds() { - - StochasticsActionSupport.testConsumer(7392476210385850542L, (c) -> { - - StochasticsDataManager stochasticsDataManager = c.getDataManager(StochasticsDataManager.class); - - RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); - - long seed1 = randomGenerator.nextLong(); - - long seed2 = randomGenerator.nextLong(); - - /* - * Establish the first long generated by each random number - * generator after resetting the seed to seed1 - */ - stochasticsDataManager.resetSeeds(seed1); - - randomGenerator = stochasticsDataManager.getRandomGenerator(); - long expectedGeneratorValue = randomGenerator.nextLong(); - - Map expectedGeneratorValues = new LinkedHashMap<>(); - for (TestRandomGeneratorId testRandomGeneratorId : TestRandomGeneratorId.values()) { - long value = stochasticsDataManager.getRandomGeneratorFromId(testRandomGeneratorId).nextLong(); - expectedGeneratorValues.put(testRandomGeneratorId, value); - } - - /* - * re-seed to a new seed and show that the initial longs returned - * changed for all generators - */ - stochasticsDataManager.resetSeeds(seed2); - randomGenerator = stochasticsDataManager.getRandomGenerator(); - - assertNotEquals(expectedGeneratorValue, randomGenerator.nextLong()); - - for (TestRandomGeneratorId testRandomGeneratorId : TestRandomGeneratorId.values()) { - long value = stochasticsDataManager.getRandomGeneratorFromId(testRandomGeneratorId).nextLong(); - assertNotEquals(expectedGeneratorValues.get(testRandomGeneratorId), value); - } - - /* - * re-seed to the original seed and show that the values are as they - * were - */ - stochasticsDataManager.resetSeeds(seed1); - randomGenerator = stochasticsDataManager.getRandomGenerator(); - - assertEquals(expectedGeneratorValue, randomGenerator.nextLong()); - - for (TestRandomGeneratorId testRandomGeneratorId : TestRandomGeneratorId.values()) { - long value = stochasticsDataManager.getRandomGeneratorFromId(testRandomGeneratorId).nextLong(); - assertEquals(expectedGeneratorValues.get(testRandomGeneratorId), value); - } - - }); - - } - -} diff --git a/gcm3/src/test/java/plugins/stochastics/AT_StochasticsPlugin.java b/gcm3/src/test/java/plugins/stochastics/AT_StochasticsPlugin.java deleted file mode 100644 index cee22a15f..000000000 --- a/gcm3/src/test/java/plugins/stochastics/AT_StochasticsPlugin.java +++ /dev/null @@ -1,131 +0,0 @@ -package plugins.stochastics; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; - -import nucleus.Plugin; -import nucleus.Simulation; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = StochasticsPlugin.class) -public class AT_StochasticsPlugin { - - @Test - @UnitTestMethod(name = "getPlugin", args = { StochasticsPluginData.class }) - public void testGetPlugin() { - - StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder().setSeed(34534).build(); - Plugin stochasticsPlugin = StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData); - - // show the plugin is not null - assertNotNull(stochasticsPlugin); - - // show that the plugin has no dependencies - assertTrue(stochasticsPlugin.getPluginDependencies().isEmpty()); - - // show that the plugin has the correct id - assertEquals(StochasticsPluginId.PLUGIN_ID, stochasticsPlugin.getPluginId()); - - /* - * Show that the plugin establishes the StochasticsDataManager - */ - TestPluginData.Builder testPluginBuilder = TestPluginData.builder(); - - TestPluginData testPluginData = testPluginBuilder.build(); - Plugin testPlugin = TestPlugin.getTestPlugin(testPluginData); - - Simulation .builder()// - .addPlugin(testPlugin)// - .addPlugin(stochasticsPlugin)// - .build()// - .execute();// - - } - - //the code below should show that the state of the data manager properly reflects the plugin data - -// @Test -// @UnitTestMethod(name = "init", args = { ResolverContext.class }) -// public void testStochasticsDataViewInitialzation() { -// long seed = 745645785689L; -// -// // show that the stochastics data view is published and has the correct -// // state -// -// // show that we are contributing random generator ids -// assertTrue(TestRandomGeneratorId.values().length > 0); -// -// // build the initial data -// Set expectedRandomGeneratorIds = new LinkedHashSet<>(); -// StochasticsPlugin.Builder builder = StochasticsPlugin.builder(); -// for (TestRandomGeneratorId testRandomGeneratorId : TestRandomGeneratorId.values()) { -// expectedRandomGeneratorIds.add(testRandomGeneratorId); -// builder.addRandomGeneratorId(testRandomGeneratorId); -// } -// builder.setSeed(seed); -// StochasticsPlugin stochasticsPlugin = builder.build(); -// -// List publishedDataViews = new ArrayList<>(); -// -// // build the manager -// MockResolverContext mockResolverContext = MockResolverContext.builder().setPublishDataViewConsumer((d) -> publishedDataViews.add(d)).build(); -// StochasticsResolver stochasticsResolver = new StochasticsResolver(stochasticsPlugin); -// stochasticsResolver.init(mockResolverContext); -// -// // show that only one data view was published -// assertEquals(1, publishedDataViews.size()); -// -// // show that the published data view is not null -// DataView dataView = publishedDataViews.get(0); -// assertNotNull(dataView); -// -// // show that the published data view is a StochasticsDataView -// assertEquals(StochasticsDataView.class, dataView.getClass()); -// -// StochasticsDataView stochasticsDataView = (StochasticsDataView) dataView; -// -// // show that the data view returns the correct random generator -// RandomGenerator randomGenerator = stochasticsDataView.getRandomGenerator(); -// // show that the random generator is not null -// assertNotNull(randomGenerator); -// // show that the random generator is the expected implementor -// assertEquals(Well44497b.class, randomGenerator.getClass()); -// -// // show that the random generator is likely to have been seeded -// // correctly -// Well44497b well44497b = new Well44497b(seed); -// for (int i = 0; i < 100; i++) { -// assertEquals(well44497b.nextLong(), randomGenerator.nextLong()); -// } -// -// // show that the data view returns the correct random generator ids -// Set actualRandomNumberGeneratorIds = stochasticsDataView.getRandomNumberGeneratorIds(); -// assertEquals(expectedRandomGeneratorIds, actualRandomNumberGeneratorIds); -// -// // show that the random generators associated with id values are correct -// for (TestRandomGeneratorId testRandomGeneratorId : TestRandomGeneratorId.values()) { -// // show that the data view returns the correct random generator -// randomGenerator = stochasticsDataView.getRandomGeneratorFromId(testRandomGeneratorId); -// // show that the random generator is not null -// assertNotNull(randomGenerator); -// // show that the random generator is the expected implementor -// assertEquals(Well44497b.class, randomGenerator.getClass()); -// -// // show that the random generator is likely to have been seeded -// // correctly -// well44497b = new Well44497b(seed + testRandomGeneratorId.toString().hashCode()); -// for (int i = 0; i < 100; i++) { -// assertEquals(well44497b.nextLong(), randomGenerator.nextLong()); -// } -// } -// -// } - - -} diff --git a/gcm3/src/test/java/plugins/stochastics/AT_StochasticsPluginData.java b/gcm3/src/test/java/plugins/stochastics/AT_StochasticsPluginData.java deleted file mode 100644 index 476380a47..000000000 --- a/gcm3/src/test/java/plugins/stochastics/AT_StochasticsPluginData.java +++ /dev/null @@ -1,111 +0,0 @@ -package plugins.stochastics; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.PluginDataBuilder; -import plugins.stochastics.support.RandomNumberGeneratorId; -import plugins.stochastics.support.StochasticsError; -import plugins.stochastics.testsupport.TestRandomGeneratorId; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; -import util.random.RandomGeneratorProvider; - -@UnitTest(target = StochasticsPluginData.class) -public class AT_StochasticsPluginData { - - @Test - @UnitTestMethod(name = "getCloneBuilder", args = {}) - public void testGetCloneBuilder() { - - - StochasticsPluginData stochasticsPluginData = StochasticsPluginData .builder()// - .setSeed(4970625656919510170L)// - .addRandomGeneratorId(TestRandomGeneratorId.BLITZEN)// - .addRandomGeneratorId(TestRandomGeneratorId.COMET)// - .build();// - - //show that the clone builder is not null - PluginDataBuilder cloneBuilder = stochasticsPluginData.getCloneBuilder(); - assertNotNull(cloneBuilder); - StochasticsPluginData cloneData = (StochasticsPluginData) cloneBuilder.build(); - - //show that the clone builder is properly initialized - assertEquals(cloneData.getRandomNumberGeneratorIds(), stochasticsPluginData.getRandomNumberGeneratorIds()); - assertEquals(cloneData.getSeed(), stochasticsPluginData.getSeed()); - - } - - @Test - @UnitTestMethod(name = "getSeed", args = {}) - public void testGetSeed() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4970625656919510170L); - for (int i = 0; i < 30; i++) { - long seed = randomGenerator.nextLong(); - StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder().setSeed(seed).build(); - assertEquals(seed, stochasticsPluginData.getSeed()); - } - } - - @Test - @UnitTestMethod(name = "getRandomNumberGeneratorIds", args = {}) - public void testGetRandomNumberGeneratorIds() { - Set expectedRandomNumberGeneratorIds = new LinkedHashSet<>(); - StochasticsPluginData.Builder builder = StochasticsPluginData.builder(); - for (TestRandomGeneratorId testRandomGeneratorId : TestRandomGeneratorId.values()) { - expectedRandomNumberGeneratorIds.add(testRandomGeneratorId); - builder.addRandomGeneratorId(testRandomGeneratorId); - } - builder.setSeed(3244635455542808061L); - StochasticsPluginData stochasticsPluginData = builder.build(); - Set actualRandomNumberGeneratorIds = stochasticsPluginData.getRandomNumberGeneratorIds(); - assertEquals(expectedRandomNumberGeneratorIds, actualRandomNumberGeneratorIds); - } - - @Test - @UnitTestMethod(name = "builder", args = {}) - public void testBuilder() { - // show that the builder returns a non-null instance of - // StochasticsPluginData.Builder - assertNotNull(StochasticsPluginData.builder()); - } - - @Test - @UnitTestMethod(target = StochasticsPluginData.Builder.class, name = "build", args = {}) - public void testBuild() { - // test covered by remaining tests - } - - @Test - @UnitTestMethod(target = StochasticsPluginData.Builder.class, name = "setSeed", args = { long.class }) - public void testSetSeed() { - long seed = 235234623445234756L; - StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder().setSeed(seed).build(); - assertEquals(seed, stochasticsPluginData.getSeed()); - } - - @Test - @UnitTestMethod(target = StochasticsPluginData.Builder.class, name = "addRandomGeneratorId", args = { RandomNumberGeneratorId.class }) - public void testAddRandomGeneratorId() { - - for (TestRandomGeneratorId testRandomGeneratorId : TestRandomGeneratorId.values()) { - StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder().setSeed(4300202782621809065L).addRandomGeneratorId(testRandomGeneratorId).build(); - assertTrue(stochasticsPluginData.getRandomNumberGeneratorIds().contains(testRandomGeneratorId)); - } - - // precondition tests - ContractException contractException = assertThrows(ContractException.class, () -> StochasticsPluginData.builder().setSeed(1130627593613916615L).addRandomGeneratorId(null)); - assertEquals(StochasticsError.NULL_RANDOM_NUMBER_GENERATOR_ID, contractException.getErrorType()); - - } - -} diff --git a/gcm3/src/test/java/plugins/stochastics/AT_StochasticsPluginId.java b/gcm3/src/test/java/plugins/stochastics/AT_StochasticsPluginId.java deleted file mode 100644 index 777f259ce..000000000 --- a/gcm3/src/test/java/plugins/stochastics/AT_StochasticsPluginId.java +++ /dev/null @@ -1,17 +0,0 @@ -package plugins.stochastics; - -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - -@UnitTest(target = StochasticsPluginId.class) -public class AT_StochasticsPluginId { - - @Test - public void test() { - assertNotNull(StochasticsPluginId.PLUGIN_ID); - } - -} diff --git a/gcm3/src/test/java/plugins/stochastics/support/AT_RandomNumberGeneratorId.java b/gcm3/src/test/java/plugins/stochastics/support/AT_RandomNumberGeneratorId.java deleted file mode 100644 index fea6ad76c..000000000 --- a/gcm3/src/test/java/plugins/stochastics/support/AT_RandomNumberGeneratorId.java +++ /dev/null @@ -1,13 +0,0 @@ -package plugins.stochastics.support; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - -@UnitTest(target = RandomNumberGeneratorId.class) -public class AT_RandomNumberGeneratorId { - @Test - public void test() { - // no test required - } -} diff --git a/gcm3/src/test/java/plugins/stochastics/support/AT_StochasticsError.java b/gcm3/src/test/java/plugins/stochastics/support/AT_StochasticsError.java deleted file mode 100644 index 7842ae9da..000000000 --- a/gcm3/src/test/java/plugins/stochastics/support/AT_StochasticsError.java +++ /dev/null @@ -1,31 +0,0 @@ -package plugins.stochastics.support; - -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = StochasticsError.class) -public class AT_StochasticsError { - - @Test - @UnitTestMethod(name = "getDescription", args = {}) - public void test() { - - //show that each description is a unique, non-null and non-empty string - Set descriptions = new LinkedHashSet<>(); - for(StochasticsError stochasticsError : StochasticsError.values()) { - String description = stochasticsError.getDescription(); - assertNotNull(description,"null description for "+stochasticsError); - assertTrue(description.length()>0, "empty string for "+stochasticsError); - boolean unique = descriptions.add(description); - assertTrue(unique,"description for "+stochasticsError+" is not unique"); - } - } -} diff --git a/gcm3/src/test/java/plugins/stochastics/testsupport/AT_StochasticsActionSupport.java b/gcm3/src/test/java/plugins/stochastics/testsupport/AT_StochasticsActionSupport.java deleted file mode 100644 index 6cadd3c26..000000000 --- a/gcm3/src/test/java/plugins/stochastics/testsupport/AT_StochasticsActionSupport.java +++ /dev/null @@ -1,44 +0,0 @@ -package plugins.stochastics.testsupport; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.function.Consumer; - -import org.junit.jupiter.api.Test; - -import nucleus.ActorContext; -import nucleus.Plugin; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.wrappers.MutableBoolean; - -@UnitTest(target = StochasticsActionSupport.class) -public class AT_StochasticsActionSupport { - - @Test - @UnitTestMethod(name = "testConsumer", args = { Consumer.class }) - public void testTestConsumer() { - MutableBoolean actorExecuted = new MutableBoolean(); - Consumer consumer = (c) -> actorExecuted.setValue(true); - StochasticsActionSupport.testConsumer(45235233432345378L, consumer); - assertTrue(actorExecuted.getValue()); - } - - @Test - @UnitTestMethod(name = "testConsumers", args = { Plugin.class }) - public void testTestConsumers() { - MutableBoolean actorExecuted = new MutableBoolean(); - - TestPluginData.Builder builder = TestPluginData.builder(); - builder.addTestActorPlan("actor", new TestActorPlan(0, (c) -> actorExecuted.setValue(true))); - TestPluginData testPluginData = builder.build(); - Plugin plugin = TestPlugin.getTestPlugin(testPluginData); - StochasticsActionSupport.testConsumers(45235233432345378L, plugin); - - assertTrue(actorExecuted.getValue()); - } - -} diff --git a/gcm3/src/test/java/plugins/util/properties/AT_AbstractIndexedPropertyManager.java b/gcm3/src/test/java/plugins/util/properties/AT_AbstractIndexedPropertyManager.java deleted file mode 100644 index a1455ad70..000000000 --- a/gcm3/src/test/java/plugins/util/properties/AT_AbstractIndexedPropertyManager.java +++ /dev/null @@ -1,171 +0,0 @@ -package plugins.util.properties; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.util.stream.IntStream; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.DataManagerContext; -import nucleus.Plugin; -import nucleus.SimulationContext; -import nucleus.testsupport.testplugin.TestActionSupport; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestDataManager; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; -import util.random.RandomGeneratorProvider; - -@UnitTest(target = AbstractIndexedPropertyManager.class) - -public class AT_AbstractIndexedPropertyManager { - - /* - * - * A simple concrete extension of AbstractIndexedPropertyManager used to - * test AbstractIndexedPropertyManager. - * - */ - private static class SimplePropertyManager extends AbstractIndexedPropertyManager { - - public SimplePropertyManager(SimulationContext context, PropertyDefinition propertyDefinition, int initialSize) { - super(context, propertyDefinition, initialSize); - } - - @Override - public T getPropertyValue(int id) { - return null; - } - - } - - @Test - @UnitTestConstructor(args = { SimulationContext.class, PropertyDefinition.class, int.class }) - public void testConstructor() { - TestActionSupport.testConsumer((c) -> { - - PropertyDefinition goodPropertyDefinition = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(false).build(); - - // if the property definition is null - ContractException contractException = assertThrows(ContractException.class, () -> new BooleanPropertyManager(c, null, 0)); - assertEquals(PropertyError.NULL_PROPERTY_DEFINITION, contractException.getErrorType()); - - // if the initial size is negative - contractException = assertThrows(ContractException.class, () -> new BooleanPropertyManager(c, goodPropertyDefinition, -1)); - assertEquals(PropertyError.NEGATIVE_INITIAL_SIZE, contractException.getErrorType()); - - SimplePropertyManager simplePropertyManager = new SimplePropertyManager(c, goodPropertyDefinition, 0); - assertNotNull(simplePropertyManager); - }); - } - - @Test - @UnitTestMethod(name = "setPropertyValue", args = { int.class, Object.class }) - public void testSetPropertyValue() { - TestActionSupport.testConsumer((c) -> { - - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(false).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - - // precondition tests - SimplePropertyManager simplePropertyManager = new SimplePropertyManager(c, propertyDefinition, 0); - ContractException contractException = assertThrows(ContractException.class, () -> simplePropertyManager.setPropertyValue(-1, false)); - assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); - }); - } - - /* - * Local data manager used to properly initialize an ObjectPropertyManager - * for use in time sensitive tests - */ - private static class LocalDM extends TestDataManager { - public SimplePropertyManager simplePropertyManager; - - @Override - public void init(DataManagerContext dataManagerContext) { - super.init(dataManagerContext); - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(false).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - simplePropertyManager = new SimplePropertyManager(dataManagerContext, propertyDefinition, 0); - } - } - - @Test - @UnitTestMethod(name = "getPropertyTime", args = { int.class }) - public void testGetPropertyTime() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1003433950467196390L); - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - IntStream.range(0, 1000).forEach((i -> { - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(i, (c) -> { - LocalDM localDM = c.getDataManager(LocalDM.class); - int id = randomGenerator.nextInt(300); - boolean value = randomGenerator.nextBoolean(); - SimplePropertyManager simplePropertyManager = localDM.simplePropertyManager; - simplePropertyManager.setPropertyValue(id, value); - // show that the property time for the id was properly set - assertEquals(c.getTime(), simplePropertyManager.getPropertyTime(id), 0); - })); - })); - - // add the local data manager - pluginDataBuilder.addTestDataManager("dm", ()->new LocalDM()); - - // build and run the simulation - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin plugin = TestPlugin.getTestPlugin(testPluginData); - TestActionSupport.testConsumers(plugin); - - // precondition test: if time tracking is no engaged - TestActionSupport.testConsumer((c) -> { - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(false).build(); - SimplePropertyManager spm = new SimplePropertyManager(c, propertyDefinition, 0); - ContractException contractException = assertThrows(ContractException.class, () -> spm.getPropertyTime(0)); - assertEquals(PropertyError.TIME_TRACKING_OFF, contractException.getErrorType()); - }); - - // precondition test: if a property time is retrieved for a negative - // index - TestActionSupport.testConsumer((c) -> { - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(false).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - SimplePropertyManager spm = new SimplePropertyManager(c, propertyDefinition, 0); - ContractException contractException = assertThrows(ContractException.class, () -> spm.getPropertyTime(-1)); - assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "removeId", args = { int.class }) - public void testRemoveId() { - TestActionSupport.testConsumer((c) -> { - // precondition tests - PropertyDefinition def = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(true).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - SimplePropertyManager spm = new SimplePropertyManager(c, def, 0); - - ContractException contractException = assertThrows(ContractException.class, () -> spm.removeId(-1)); - assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "incrementCapacity", args = { int.class }) - public void testIncrementCapacity() { - TestActionSupport.testConsumer((c) -> { - - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(false).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - - SimplePropertyManager simplePropertyManager = new SimplePropertyManager(c, propertyDefinition, 0); - - // precondition tests - ContractException contractException = assertThrows(ContractException.class, () -> simplePropertyManager.incrementCapacity(-1)); - assertEquals(PropertyError.NEGATIVE_CAPACITY_INCREMENT, contractException.getErrorType()); - }); - } -} diff --git a/gcm3/src/test/java/plugins/util/properties/AT_BooleanPropertyManager.java b/gcm3/src/test/java/plugins/util/properties/AT_BooleanPropertyManager.java deleted file mode 100644 index e15b6e6e9..000000000 --- a/gcm3/src/test/java/plugins/util/properties/AT_BooleanPropertyManager.java +++ /dev/null @@ -1,289 +0,0 @@ -package plugins.util.properties; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.stream.IntStream; - -import javax.naming.Context; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.DataManagerContext; -import nucleus.Plugin; -import nucleus.testsupport.testplugin.TestActionSupport; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestDataManager; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; -import util.random.RandomGeneratorProvider; - -/** - * Common interface to all person property managers. A person property manager - * manages all the property values for people for a particular person property - * identifier. - * - * @author Shawn Hatch - * - */ - -@UnitTest(target = BooleanPropertyManager.class) -public class AT_BooleanPropertyManager { - - @Test - @UnitTestMethod(name = "getPropertyValue", args = { int.class }) - public void testGetPropertyValue() { - TestActionSupport.testConsumer((c) -> { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4879223247393954289L); - - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(false).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - - BooleanPropertyManager booleanPropertyManager = new BooleanPropertyManager(c, propertyDefinition, 0); - - /* - * We will set the first 300 values multiple times at random - */ - Map expectedValues = new LinkedHashMap<>(); - - for (int i = 0; i < 1000; i++) { - int id = randomGenerator.nextInt(300); - boolean value = randomGenerator.nextBoolean(); - expectedValues.put(id, value); - booleanPropertyManager.setPropertyValue(id, value); - } - - /* - * if the value was set above, then it should equal the last value - * place in the expected values, otherwise it will have the default - * value. - */ - for (int i = 0; i < 300; i++) { - if (expectedValues.containsKey(i)) { - assertEquals(expectedValues.get(i), booleanPropertyManager.getPropertyValue(i)); - - } else { - assertFalse((Boolean) booleanPropertyManager.getPropertyValue(i)); - - } - } - - // precondition tests - ContractException contractException = assertThrows(ContractException.class, () -> booleanPropertyManager.getPropertyValue(-1)); - assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); - }); - - } - - /* - * Local data manager used to properly initialize an BooleanPropertyManager - * for use in time sensitive tests - */ - private static class LocalDM extends TestDataManager { - public BooleanPropertyManager booleanPropertyManager; - - @Override - public void init(DataManagerContext dataManagerContext) { - super.init(dataManagerContext); - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(false).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - booleanPropertyManager = new BooleanPropertyManager(dataManagerContext, propertyDefinition, 0); - } - } - - @Test - @UnitTestMethod(name = "getPropertyTime", args = { int.class }) - public void testGetPropertyTime() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6779797760333524552L); - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - IntStream.range(0, 1000).forEach((i -> { - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(i, (c) -> { - LocalDM localDM = c.getDataManager(LocalDM.class); - int id = randomGenerator.nextInt(300); - boolean value = randomGenerator.nextBoolean(); - BooleanPropertyManager booleanPropertyManager = localDM.booleanPropertyManager; - booleanPropertyManager.setPropertyValue(id, value); - // show that the property time for the id was properly set - assertEquals(c.getTime(), booleanPropertyManager.getPropertyTime(id), 0); - })); - })); - - // add the local data manager - pluginDataBuilder.addTestDataManager("dm", ()->new LocalDM()); - - // build and run the simulation - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin plugin = TestPlugin.getTestPlugin(testPluginData); - TestActionSupport.testConsumers(plugin); - - // precondition tests: - TestActionSupport.testConsumer((c) -> { - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(false).build(); - BooleanPropertyManager booleanPropertyManager = new BooleanPropertyManager(c, propertyDefinition, 0); - ContractException contractException = assertThrows(ContractException.class, () -> booleanPropertyManager.getPropertyTime(0)); - assertEquals(PropertyError.TIME_TRACKING_OFF, contractException.getErrorType()); - }); - - TestActionSupport.testConsumer((c) -> { - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(false).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - BooleanPropertyManager booleanPropertyManager = new BooleanPropertyManager(c, propertyDefinition, 0); - ContractException contractException = assertThrows(ContractException.class, () -> booleanPropertyManager.getPropertyTime(-1)); - assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "setPropertyValue", args = { int.class, Object.class }) - public void testSetPropertyValue() { - TestActionSupport.testConsumer((c) -> { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4827517950755837724L); - - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(false).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - - BooleanPropertyManager booleanPropertyManager = new BooleanPropertyManager(c, propertyDefinition, 0); - - /* - * We will set the first 300 values multiple times at random - */ - Map expectedValues = new LinkedHashMap<>(); - - for (int i = 0; i < 1000; i++) { - int id = randomGenerator.nextInt(300); - boolean value = randomGenerator.nextBoolean(); - expectedValues.put(id, value); - booleanPropertyManager.setPropertyValue(id, value); - } - - /* - * if the value was set above, then it should equal the last value - * place in the expected values, otherwise it will have the default - * value. - */ - for (int i = 0; i < 300; i++) { - if (expectedValues.containsKey(i)) { - assertEquals(expectedValues.get(i), booleanPropertyManager.getPropertyValue(i)); - - } else { - assertFalse((Boolean) booleanPropertyManager.getPropertyValue(i)); - - } - } - - // precondition tests - ContractException contractException = assertThrows(ContractException.class, () -> booleanPropertyManager.setPropertyValue(-1, false)); - assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "removeId", args = { int.class }) - public void testRemoveId() { - TestActionSupport.testConsumer((c) -> { - - // we will first test the manager with an initial value of false - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(false).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - - BooleanPropertyManager booleanPropertyManager = new BooleanPropertyManager(c, propertyDefinition, 0); - - // initially, the value should be the default value for the manager - assertFalse((Boolean) booleanPropertyManager.getPropertyValue(5)); - - // after setting the value we should be able to retrieve a true - // value - booleanPropertyManager.setPropertyValue(5, true); - assertTrue((Boolean) booleanPropertyManager.getPropertyValue(5)); - - // removing the id from the manager should have no effect, since we - // do - // not waste time setting the value back to the default - booleanPropertyManager.removeId(5); - - assertTrue((Boolean) booleanPropertyManager.getPropertyValue(5)); - - // we will next test the manager with an initial value of true - propertyDefinition = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(true).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - - booleanPropertyManager = new BooleanPropertyManager(c, propertyDefinition, 0); - - // initially, the value should be the default value for the manager - assertTrue((Boolean) booleanPropertyManager.getPropertyValue(5)); - - // after setting the value we should be able to retrieve a true - // value - booleanPropertyManager.setPropertyValue(5, false); - assertFalse((Boolean) booleanPropertyManager.getPropertyValue(5)); - - // removing the id from the manager should have no effect, since we - // do - // not waste time setting the value back to the default - booleanPropertyManager.removeId(5); - - assertFalse((Boolean) booleanPropertyManager.getPropertyValue(5)); - - // precondition tests - PropertyDefinition def = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(true).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - BooleanPropertyManager bpm = new BooleanPropertyManager(c, def, 0); - - ContractException contractException = assertThrows(ContractException.class, () -> bpm.removeId(-1)); - assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); - }); - } - - @Test - @UnitTestConstructor(args = { Context.class, PropertyDefinition.class, int.class }) - public void testConstructor() { - TestActionSupport.testConsumer((c) -> { - - PropertyDefinition goodPropertyDefinition = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(false).build(); - PropertyDefinition badPropertyDefinition = PropertyDefinition.builder().setType(Double.class).setDefaultValue(2.3).build(); - PropertyDefinition badBooleanPropertyDefinition = PropertyDefinition.builder().setType(Boolean.class).build(); - - // precondition tests - - // if the property definition is null - ContractException contractException = assertThrows(ContractException.class, () -> new BooleanPropertyManager(c, null, 0)); - assertEquals(PropertyError.NULL_PROPERTY_DEFINITION, contractException.getErrorType()); - - // if the property definition does not have a type of Boolean.class - contractException = assertThrows(ContractException.class, () -> new BooleanPropertyManager(c, badPropertyDefinition, 0)); - assertEquals(PropertyError.PROPERTY_DEFINITION_IMPROPER_TYPE, contractException.getErrorType()); - - // if the property definition does not contain a default value - contractException = assertThrows(ContractException.class, () -> new BooleanPropertyManager(c, badBooleanPropertyDefinition, 0)); - assertEquals(PropertyError.PROPERTY_DEFINITION_MISSING_DEFAULT, contractException.getErrorType()); - - // if the initial size is negative - contractException = assertThrows(ContractException.class, () -> new BooleanPropertyManager(c, goodPropertyDefinition, -1)); - assertEquals(PropertyError.NEGATIVE_INITIAL_SIZE, contractException.getErrorType()); - - BooleanPropertyManager booleanPropertyManager = new BooleanPropertyManager(c, goodPropertyDefinition, 0); - assertNotNull(booleanPropertyManager); - }); - } - - @Test - @UnitTestMethod(name = "incrementCapacity", args = { int.class }) - public void testIncrementCapacity() { - TestActionSupport.testConsumer((c) -> { - - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(false).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - - BooleanPropertyManager booleanPropertyManager = new BooleanPropertyManager(c, propertyDefinition, 0); - - // precondition tests - ContractException contractException = assertThrows(ContractException.class, () -> booleanPropertyManager.incrementCapacity(-1)); - assertEquals(PropertyError.NEGATIVE_CAPACITY_INCREMENT, contractException.getErrorType()); - }); - } - -} diff --git a/gcm3/src/test/java/plugins/util/properties/AT_DoublePropertyManager.java b/gcm3/src/test/java/plugins/util/properties/AT_DoublePropertyManager.java deleted file mode 100644 index 14c64be9c..000000000 --- a/gcm3/src/test/java/plugins/util/properties/AT_DoublePropertyManager.java +++ /dev/null @@ -1,305 +0,0 @@ -package plugins.util.properties; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.stream.IntStream; - -import javax.naming.Context; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.DataManagerContext; -import nucleus.Plugin; -import nucleus.testsupport.testplugin.TestActionSupport; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestDataManager; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; -import util.random.RandomGeneratorProvider; - -/** - * Common interface to all person property managers. A person property manager - * manages all the property values for people for a particular person property - * identifier. - * - * @author Shawn Hatch - * - */ - -@UnitTest(target = DoublePropertyManager.class) -public class AT_DoublePropertyManager { - - @Test - @UnitTestMethod(name = "getPropertyValue", args = { int.class }) - public void testGetPropertyValue() { - TestActionSupport.testConsumer((c) -> { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1599837792379294459L); - - double defaultValue = 423.645; - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Double.class).setDefaultValue(defaultValue).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - - DoublePropertyManager doublePropertyManager = new DoublePropertyManager(c, propertyDefinition, 0); - - /* - * We will set the first 300 values multiple times at random - */ - Map expectedValues = new LinkedHashMap<>(); - - for (int i = 0; i < 1000; i++) { - int id = randomGenerator.nextInt(300); - double value = randomGenerator.nextDouble(); - expectedValues.put(id, value); - doublePropertyManager.setPropertyValue(id, value); - } - - /* - * if the value was set above, then it should equal the last value - * place in the expected values, otherwise it will have the default - * value. - */ - for (int i = 0; i < 300; i++) { - if (expectedValues.containsKey(i)) { - assertEquals(expectedValues.get(i), doublePropertyManager.getPropertyValue(i)); - - } else { - assertEquals(defaultValue, (Double) doublePropertyManager.getPropertyValue(i)); - - } - } - - // precondition tests - ContractException contractException = assertThrows(ContractException.class, () -> doublePropertyManager.getPropertyValue(-1)); - assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); - }); - } - - /* - * Local data manager used to properly initialize an ObjectPropertyManager - * for use in time sensitive tests - */ - private static class LocalDM extends TestDataManager { - public DoublePropertyManager doublePropertyManager; - - @Override - public void init(DataManagerContext dataManagerContext) { - super.init(dataManagerContext); - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Double.class).setDefaultValue(342.4234).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - doublePropertyManager = new DoublePropertyManager(dataManagerContext, propertyDefinition, 0); - } - } - - @Test - @UnitTestMethod(name = "getPropertyTime", args = { int.class }) - public void testGetPropertyTime() { - /** - * Returns the assignment time when the id's property was last set. Note - * that this does not imply that the id exists in the simulation. - * - * @throws RuntimeException - * if time tracking is not turned on for this property via - * the policies established in the scenario. - * - */ - // public double getPropertyTime(int id); - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2349682401845769564L); - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - IntStream.range(0, 1000).forEach((i -> { - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(i, (c) -> { - LocalDM localDM = c.getDataManager(LocalDM.class); - int id = randomGenerator.nextInt(300); - double value = randomGenerator.nextDouble(); - DoublePropertyManager doublePropertyManager = localDM.doublePropertyManager; - doublePropertyManager.setPropertyValue(id, value); - // show that the property time for the id was properly set - assertEquals(c.getTime(), doublePropertyManager.getPropertyTime(id), 0); - })); - })); - - // add the local data manager - pluginDataBuilder.addTestDataManager("dm", ()->new LocalDM()); - - // build and run the simulation - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin plugin = TestPlugin.getTestPlugin(testPluginData); - TestActionSupport.testConsumers(plugin); - - // precondition tests: - TestActionSupport.testConsumer((c) -> { - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Double.class).setDefaultValue(4.5).build(); - DoublePropertyManager dpm = new DoublePropertyManager(c, propertyDefinition, 0); - ContractException contractException = assertThrows(ContractException.class, () -> dpm.getPropertyTime(0)); - assertEquals(PropertyError.TIME_TRACKING_OFF, contractException.getErrorType()); - }); - - TestActionSupport.testConsumer((c) -> { - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Double.class).setDefaultValue(4.5).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - DoublePropertyManager dpm = new DoublePropertyManager(c, propertyDefinition, 0); - ContractException contractException = assertThrows(ContractException.class, () -> dpm.getPropertyTime(-1)); - assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "setPropertyValue", args = { int.class, Object.class }) - public void testSetPropertyValue() { - TestActionSupport.testConsumer((c) -> { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1599837792379294459L); - - double defaultValue = 423.645; - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Double.class).setDefaultValue(defaultValue).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - - DoublePropertyManager doublePropertyManager = new DoublePropertyManager(c, propertyDefinition, 0); - - /* - * We will set the first 300 values multiple times at random - */ - Map expectedValues = new LinkedHashMap<>(); - - for (int i = 0; i < 1000; i++) { - int id = randomGenerator.nextInt(300); - double value = randomGenerator.nextDouble(); - expectedValues.put(id, value); - doublePropertyManager.setPropertyValue(id, value); - } - - /* - * if the value was set above, then it should equal the last value - * place in the expected values, otherwise it will have the default - * value. - */ - for (int i = 0; i < 300; i++) { - if (expectedValues.containsKey(i)) { - assertEquals(expectedValues.get(i), doublePropertyManager.getPropertyValue(i)); - - } else { - assertEquals(defaultValue, (Double) doublePropertyManager.getPropertyValue(i)); - - } - } - - // precondition tests - - ContractException contractException = assertThrows(ContractException.class, () -> doublePropertyManager.setPropertyValue(-1, 23.4)); - assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "removeId", args = { int.class }) - public void testRemoveId() { - TestActionSupport.testConsumer((c) -> { - /* - * Should have no effect on the value that is stored for the sake of - * efficiency. - */ - - // we will first test the manager with an initial value of false - double defaultValue = 6.2345345; - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Double.class).setDefaultValue(defaultValue).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - - DoublePropertyManager doublePropertyManager = new DoublePropertyManager(c, propertyDefinition, 0); - - // initially, the value should be the default value for the manager - assertEquals(defaultValue, (Double) doublePropertyManager.getPropertyValue(5), 0); - - // after setting the value we should be able to retrieve a new value - double newValue = 34534.4; - doublePropertyManager.setPropertyValue(5, newValue); - assertEquals(newValue, (Double) doublePropertyManager.getPropertyValue(5), 0); - - // removing the id from the manager should have no effect, since we - // do - // not waste time setting the value back to the default - doublePropertyManager.removeId(5); - - assertEquals(newValue, (Double) doublePropertyManager.getPropertyValue(5), 0); - - // we will next test the manager with an initial value of true - propertyDefinition = PropertyDefinition.builder().setType(Double.class).setDefaultValue(defaultValue).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - - doublePropertyManager = new DoublePropertyManager(c, propertyDefinition, 0); - - // initially, the value should be the default value for the manager - assertEquals(defaultValue, (Double) doublePropertyManager.getPropertyValue(5), 0); - - // after setting the value we should be able to retrieve the new - // value - doublePropertyManager.setPropertyValue(5, newValue); - assertEquals(newValue, (Double) doublePropertyManager.getPropertyValue(5), 0); - - // removing the id from the manager should have no effect, since we - // do - // not waste time setting the value back to the default - doublePropertyManager.removeId(5); - - assertEquals(newValue, (Double) doublePropertyManager.getPropertyValue(5), 0); - - // precondition tests - ContractException contractException = assertThrows(ContractException.class, () -> { - PropertyDefinition def = PropertyDefinition.builder().setType(Double.class).setDefaultValue(4534.4).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - DoublePropertyManager dpm = new DoublePropertyManager(c, def, 0); - dpm.removeId(-1); - }); - assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); - }); - } - - @Test - @UnitTestConstructor(args = { Context.class, PropertyDefinition.class, int.class }) - public void testConstructor() { - TestActionSupport.testConsumer((c) -> { - - PropertyDefinition goodPropertyDefinition = PropertyDefinition.builder().setType(Double.class).setDefaultValue(2.3).build(); - PropertyDefinition badPropertyDefinition = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(false).build(); - PropertyDefinition badDoublePropertyDefinition = PropertyDefinition.builder().setType(Double.class).build(); - - // precondition tests - - // if the property definition is null - ContractException contractException = assertThrows(ContractException.class, () -> new DoublePropertyManager(c, null, 0)); - assertEquals(PropertyError.NULL_PROPERTY_DEFINITION, contractException.getErrorType()); - - // if the property definition does not have a type of Double.class - contractException = assertThrows(ContractException.class, () -> new DoublePropertyManager(c, badPropertyDefinition, 0)); - assertEquals(PropertyError.PROPERTY_DEFINITION_IMPROPER_TYPE, contractException.getErrorType()); - - // if the property definition does not contain a default value - contractException = assertThrows(ContractException.class, () -> new DoublePropertyManager(c, badDoublePropertyDefinition, 0)); - assertEquals(PropertyError.PROPERTY_DEFINITION_MISSING_DEFAULT, contractException.getErrorType()); - - // if the initial size is negative - contractException = assertThrows(ContractException.class, () -> new DoublePropertyManager(c, goodPropertyDefinition, -1)); - assertEquals(PropertyError.NEGATIVE_INITIAL_SIZE, contractException.getErrorType()); - - DoublePropertyManager doublePropertyManager = new DoublePropertyManager(c, goodPropertyDefinition, 0); - assertNotNull(doublePropertyManager); - }); - } - - @Test - @UnitTestMethod(name = "incrementCapacity", args = { int.class }) - public void testIncrementCapacity() { - TestActionSupport.testConsumer((c) -> { - - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Double.class).setDefaultValue(2.42).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - - DoublePropertyManager doublePropertyManager = new DoublePropertyManager(c, propertyDefinition, 0); - - // precondition tests - ContractException contractException = assertThrows(ContractException.class, () -> doublePropertyManager.incrementCapacity(-1)); - assertEquals(PropertyError.NEGATIVE_CAPACITY_INCREMENT, contractException.getErrorType()); - }); - } - -} diff --git a/gcm3/src/test/java/plugins/util/properties/AT_EnumPropertyManager.java b/gcm3/src/test/java/plugins/util/properties/AT_EnumPropertyManager.java deleted file mode 100644 index f5a7e7970..000000000 --- a/gcm3/src/test/java/plugins/util/properties/AT_EnumPropertyManager.java +++ /dev/null @@ -1,308 +0,0 @@ -package plugins.util.properties; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.stream.IntStream; - -import javax.naming.Context; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.DataManagerContext; -import nucleus.Plugin; -import nucleus.testsupport.testplugin.TestActionSupport; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestDataManager; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; -import util.random.RandomGeneratorProvider; - -/** - * Common interface to all person property managers. A person property manager - * manages all the property values for people for a particular person property - * identifier. - * - * @author Shawn Hatch - * - */ - -@UnitTest(target = EnumPropertyManager.class) -public class AT_EnumPropertyManager { - - @Test - @UnitTestMethod(name = "getPropertyValue", args = { int.class }) - public void testGetPropertyValue() { - TestActionSupport.testConsumer((c) -> { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5102684240650614254L); - - Color defaultValue = Color.YELLOW; - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Color.class).setDefaultValue(defaultValue).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - - EnumPropertyManager enumPropertyManager = new EnumPropertyManager(c, propertyDefinition, 0); - - /* - * We will set the first 300 values multiple times at random - */ - Map expectedValues = new LinkedHashMap<>(); - - for (int i = 0; i < 1000; i++) { - int id = randomGenerator.nextInt(300); - Color value = Color.values()[randomGenerator.nextInt(Color.values().length)]; - expectedValues.put(id, value); - enumPropertyManager.setPropertyValue(id, value); - } - - /* - * if the value was set above, then it should equal the last value - * place in the expected values, otherwise it will have the default - * value. - */ - for (int i = 0; i < 300; i++) { - if (expectedValues.containsKey(i)) { - assertEquals(expectedValues.get(i), enumPropertyManager.getPropertyValue(i)); - - } else { - assertEquals(defaultValue, (Color) enumPropertyManager.getPropertyValue(i)); - - } - } - - // precondition tests - ContractException contractException = assertThrows(ContractException.class, () -> enumPropertyManager.getPropertyValue(-1)); - assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); - }); - } - - /* - * Local data manager used to properly initialize an EnumPropertyManager for - * use in time sensitive tests - */ - private static class LocalDM extends TestDataManager { - public EnumPropertyManager enumPropertyManager; - - @Override - public void init(DataManagerContext dataManagerContext) { - super.init(dataManagerContext); - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Color.class).setDefaultValue(Color.YELLOW).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - enumPropertyManager = new EnumPropertyManager(dataManagerContext, propertyDefinition, 0); - } - } - - @Test - @UnitTestMethod(name = "getPropertyTime", args = { int.class }) - public void testGetPropertyTime() { - /** - * Returns the assignment time when the id's property was last set. Note - * that this does not imply that the id exists in the simulation. - * - * @throws RuntimeException - * if time tracking is not turned on for this property via - * the policies established in the scenario. - * - */ - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2965406559079298427L); - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - IntStream.range(0, 1000).forEach((i -> { - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(i, (c) -> { - LocalDM localDM = c.getDataManager(LocalDM.class); - int id = randomGenerator.nextInt(300); - Color value = Color.values()[randomGenerator.nextInt(3)]; - EnumPropertyManager enumPropertyManager = localDM.enumPropertyManager; - enumPropertyManager.setPropertyValue(id, value); - // show that the property time for the id was properly set - assertEquals(c.getTime(), enumPropertyManager.getPropertyTime(id), 0); - })); - })); - - // add the local data manager - pluginDataBuilder.addTestDataManager("dm", ()->new LocalDM()); - - // build and run the simulation - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin plugin = TestPlugin.getTestPlugin(testPluginData); - TestActionSupport.testConsumers(plugin); - - // precondition tests: - TestActionSupport.testConsumer((c) -> { - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Color.class).setDefaultValue(Color.BLUE).build(); - EnumPropertyManager epm = new EnumPropertyManager(c, propertyDefinition, 0); - ContractException contractException = assertThrows(ContractException.class, () -> epm.getPropertyTime(0)); - assertEquals(PropertyError.TIME_TRACKING_OFF, contractException.getErrorType()); - }); - - TestActionSupport.testConsumer((c) -> { - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Color.class).setDefaultValue(Color.BLUE).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - EnumPropertyManager epm = new EnumPropertyManager(c, propertyDefinition, 0); - ContractException contractException = assertThrows(ContractException.class, () -> epm.getPropertyTime(-1)); - assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "setPropertyValue", args = { int.class, Object.class }) - public void testSetPropertyValue() { - TestActionSupport.testConsumer((c) -> { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6716984272666831621L); - - Color defaultValue = Color.YELLOW; - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Color.class).setDefaultValue(defaultValue).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - - EnumPropertyManager enumPropertyManager = new EnumPropertyManager(c, propertyDefinition, 0); - - /* - * We will set the first 300 values multiple times at random - */ - Map expectedValues = new LinkedHashMap<>(); - - for (int i = 0; i < 1000; i++) { - int id = randomGenerator.nextInt(300); - Color value = Color.values()[randomGenerator.nextInt(Color.values().length)]; - expectedValues.put(id, value); - enumPropertyManager.setPropertyValue(id, value); - } - - /* - * if the value was set above, then it should equal the last value - * place in the expected values, otherwise it will have the default - * value. - */ - for (int i = 0; i < 300; i++) { - if (expectedValues.containsKey(i)) { - assertEquals(expectedValues.get(i), enumPropertyManager.getPropertyValue(i)); - - } else { - assertEquals(defaultValue, (Color) enumPropertyManager.getPropertyValue(i)); - - } - } - - // precondition tests - ContractException contractException = assertThrows(ContractException.class, () -> enumPropertyManager.setPropertyValue(-1, Color.BLUE)); - assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "removeId", args = { int.class }) - public void testRemoveId() { - TestActionSupport.testConsumer((c) -> { - /* - * Should have no effect on the value that is stored for the sake of - * efficiency. - */ - - // we will first test the manager with an initial value of false - Color defaultValue = Color.RED; - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Color.class).setDefaultValue(defaultValue).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - - EnumPropertyManager enumPropertyManager = new EnumPropertyManager(c, propertyDefinition, 0); - - // initially, the value should be the default value for the manager - assertEquals(defaultValue, (Color) enumPropertyManager.getPropertyValue(5)); - - // after setting the value we should be able to retrieve a new value - Color newValue = Color.BLUE; - enumPropertyManager.setPropertyValue(5, newValue); - assertEquals(newValue, (Color) enumPropertyManager.getPropertyValue(5)); - - // removing the id from the manager should have no effect, since we - // do - // not waste time setting the value back to the default - enumPropertyManager.removeId(5); - - assertEquals(newValue, (Color) enumPropertyManager.getPropertyValue(5)); - - // we will next test the manager with an initial value of true - propertyDefinition = PropertyDefinition.builder().setType(Color.class).setDefaultValue(defaultValue).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - - enumPropertyManager = new EnumPropertyManager(c, propertyDefinition, 0); - - // initially, the value should be the default value for the manager - assertEquals(defaultValue, (Color) enumPropertyManager.getPropertyValue(5)); - - // after setting the value we should be able to retrieve the new - // value - enumPropertyManager.setPropertyValue(5, newValue); - assertEquals(newValue, (Color) enumPropertyManager.getPropertyValue(5)); - - // removing the id from the manager should have no effect, since we - // do - // not waste time setting the value back to the default - enumPropertyManager.removeId(5); - - assertEquals(newValue, (Color) enumPropertyManager.getPropertyValue(5)); - - // precondition tests - // precondition tests - PropertyDefinition def = PropertyDefinition.builder().setType(Color.class).setDefaultValue(Color.YELLOW).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - EnumPropertyManager epm = new EnumPropertyManager(c, def, 0); - - ContractException contractException = assertThrows(ContractException.class, () -> epm.removeId(-1)); - - assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); - }); - } - - // Helper enum - private static enum Color { - RED, YELLOW, BLUE; - } - - @Test - @UnitTestConstructor(args = { Context.class, PropertyDefinition.class, int.class }) - public void testConstructor() { - TestActionSupport.testConsumer((c) -> { - - PropertyDefinition goodPropertyDefinition = PropertyDefinition.builder().setType(Color.class).setDefaultValue(Color.BLUE).build(); - PropertyDefinition badPropertyDefinition = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(false).build(); - PropertyDefinition badDoublePropertyDefinition = PropertyDefinition.builder().setType(Double.class).build(); - - // if the property definition is null - ContractException contractException = assertThrows(ContractException.class, () -> new EnumPropertyManager(c, null, 0)); - assertEquals(PropertyError.NULL_PROPERTY_DEFINITION, contractException.getErrorType()); - - // if the property definition does not have a type of Enum.class - contractException = assertThrows(ContractException.class, () -> new EnumPropertyManager(c, badPropertyDefinition, 0)); - assertEquals(PropertyError.PROPERTY_DEFINITION_IMPROPER_TYPE, contractException.getErrorType()); - - // if the property definition does not contain a default value - contractException = assertThrows(ContractException.class, () -> new EnumPropertyManager(c, badDoublePropertyDefinition, 0)); - assertEquals(PropertyError.PROPERTY_DEFINITION_MISSING_DEFAULT, contractException.getErrorType()); - - // if the initial size is negative - contractException = assertThrows(ContractException.class, () -> new EnumPropertyManager(c, goodPropertyDefinition, -1)); - assertEquals(PropertyError.NEGATIVE_INITIAL_SIZE, contractException.getErrorType()); - - EnumPropertyManager enumPropertyManager = new EnumPropertyManager(c, goodPropertyDefinition, 0); - assertNotNull(enumPropertyManager); - }); - } - - @Test - @UnitTestMethod(name = "incrementCapacity", args = { int.class }) - public void testIncrementCapacity() { - TestActionSupport.testConsumer((c) -> { - - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Color.class).setDefaultValue(Color.RED).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - - EnumPropertyManager enumPropertyManager = new EnumPropertyManager(c, propertyDefinition, 0); - - // precondition tests - ContractException contractException = assertThrows(ContractException.class, () -> enumPropertyManager.incrementCapacity(-1)); - assertEquals(PropertyError.NEGATIVE_CAPACITY_INCREMENT, contractException.getErrorType()); - }); - } - -} diff --git a/gcm3/src/test/java/plugins/util/properties/AT_FloatPropertyManager.java b/gcm3/src/test/java/plugins/util/properties/AT_FloatPropertyManager.java deleted file mode 100644 index 3afe79a42..000000000 --- a/gcm3/src/test/java/plugins/util/properties/AT_FloatPropertyManager.java +++ /dev/null @@ -1,295 +0,0 @@ -package plugins.util.properties; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.stream.IntStream; - -import javax.naming.Context; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.DataManagerContext; -import nucleus.Plugin; -import nucleus.testsupport.testplugin.TestActionSupport; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestDataManager; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; -import util.random.RandomGeneratorProvider; - -/** - * Common interface to all person property managers. A person property manager - * manages all the property values for people for a particular person property - * identifier. - * - * @author Shawn Hatch - * - */ - -@UnitTest(target = FloatPropertyManager.class) -public class AT_FloatPropertyManager { - - @Test - @UnitTestMethod(name = "getPropertyValue", args = { int.class }) - public void testGetPropertyValue() { - - TestActionSupport.testConsumer((c) -> { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6087185710247012204L); - - float defaultValue = 423.645F; - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Float.class).setDefaultValue(defaultValue).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - - FloatPropertyManager floatPropertyManager = new FloatPropertyManager(c, propertyDefinition, 0); - - /* - * We will set the first 300 values multiple times at random - */ - Map expectedValues = new LinkedHashMap<>(); - - for (int i = 0; i < 1000; i++) { - int id = randomGenerator.nextInt(300); - float value = randomGenerator.nextFloat(); - expectedValues.put(id, value); - floatPropertyManager.setPropertyValue(id, value); - } - - /* - * if the value was set above, then it should equal the last value - * place in the expected values, otherwise it will have the default - * value. - */ - for (int i = 0; i < 300; i++) { - if (expectedValues.containsKey(i)) { - assertEquals(expectedValues.get(i), floatPropertyManager.getPropertyValue(i)); - - } else { - assertEquals(defaultValue, (Float) floatPropertyManager.getPropertyValue(i)); - - } - } - - // precondition tests - ContractException contractException = assertThrows(ContractException.class, () -> floatPropertyManager.getPropertyValue(-1)); - assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); - }); - } - - /* - * Local data manager used to properly initialize an ObjectPropertyManager - * for use in time sensitive tests - */ - private static class LocalDM extends TestDataManager { - public FloatPropertyManager floatPropertyManager; - - @Override - public void init(DataManagerContext dataManagerContext) { - super.init(dataManagerContext); - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Float.class).setDefaultValue(342.4234F).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - floatPropertyManager = new FloatPropertyManager(dataManagerContext, propertyDefinition, 0); - } - } - - @Test - @UnitTestMethod(name = "getPropertyTime", args = { int.class }) - public void testGetPropertyTime() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6894984813418975068L); - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - IntStream.range(0, 1000).forEach((i -> { - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(i, (c) -> { - LocalDM localDM = c.getDataManager(LocalDM.class); - int id = randomGenerator.nextInt(300); - float value = randomGenerator.nextFloat(); - FloatPropertyManager floatPropertyManager = localDM.floatPropertyManager; - floatPropertyManager.setPropertyValue(id, value); - // show that the property time for the id was properly set - assertEquals(c.getTime(), floatPropertyManager.getPropertyTime(id), 0); - })); - })); - - // add the local data manager - pluginDataBuilder.addTestDataManager("dm", ()->new LocalDM()); - - // build and run the simulation - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin plugin = TestPlugin.getTestPlugin(testPluginData); - TestActionSupport.testConsumers(plugin); - - // precondition test: if time tracking is not engaged - TestActionSupport.testConsumer((c) -> { - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Float.class).setDefaultValue(2.2F).build(); - FloatPropertyManager fpm = new FloatPropertyManager(c, propertyDefinition, 0); - ContractException contractException = assertThrows(ContractException.class, () -> fpm.getPropertyTime(0)); - assertEquals(PropertyError.TIME_TRACKING_OFF, contractException.getErrorType()); - }); - - // precondition test: if a property time is retrieved for a negative - // index - TestActionSupport.testConsumer((c) -> { - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Float.class).setDefaultValue(2.2F).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - FloatPropertyManager fpm = new FloatPropertyManager(c, propertyDefinition, 0); - ContractException contractException = assertThrows(ContractException.class, () -> fpm.getPropertyTime(-1)); - assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "setPropertyValue", args = { int.class, Object.class }) - public void testSetPropertyValue() { - TestActionSupport.testConsumer((c) -> { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6087185710247012204L); - - float defaultValue = 423.645F; - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Float.class).setDefaultValue(defaultValue).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - - FloatPropertyManager floatPropertyManager = new FloatPropertyManager(c, propertyDefinition, 0); - - /* - * We will set the first 300 values multiple times at random - */ - Map expectedValues = new LinkedHashMap<>(); - - for (int i = 0; i < 1000; i++) { - int id = randomGenerator.nextInt(300); - float value = randomGenerator.nextFloat(); - expectedValues.put(id, value); - floatPropertyManager.setPropertyValue(id, value); - } - - /* - * if the value was set above, then it should equal the last value - * place in the expected values, otherwise it will have the default - * value. - */ - for (int i = 0; i < 300; i++) { - if (expectedValues.containsKey(i)) { - assertEquals(expectedValues.get(i), floatPropertyManager.getPropertyValue(i)); - - } else { - assertEquals(defaultValue, (Float) floatPropertyManager.getPropertyValue(i)); - - } - } - - // precondition tests - ContractException contractException = assertThrows(ContractException.class, () -> floatPropertyManager.setPropertyValue(-1, 3.4F)); - assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "removeId", args = { int.class }) - public void testRemoveId() { - - TestActionSupport.testConsumer((c) -> { - /* - * Should have no effect on the value that is stored for the sake of - * efficiency. - */ - - // we will first test the manager with an initial value of false - float defaultValue = 6.2345345F; - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Float.class).setDefaultValue(defaultValue).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - - FloatPropertyManager floatPropertyManager = new FloatPropertyManager(c, propertyDefinition, 0); - - // initially, the value should be the default value for the manager - assertEquals(defaultValue, (Float) floatPropertyManager.getPropertyValue(5), 0); - - // after setting the value we should be able to retrieve a new value - float newValue = 34534.4F; - floatPropertyManager.setPropertyValue(5, newValue); - assertEquals(newValue, (Float) floatPropertyManager.getPropertyValue(5), 0); - - // removing the id from the manager should have no effect, since we - // do - // not waste time setting the value back to the default - floatPropertyManager.removeId(5); - - assertEquals(newValue, (Float) floatPropertyManager.getPropertyValue(5), 0); - - // we will next test the manager with an initial value of true - propertyDefinition = PropertyDefinition.builder().setType(Float.class).setDefaultValue(defaultValue).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - - floatPropertyManager = new FloatPropertyManager(c, propertyDefinition, 0); - - // initially, the value should be the default value for the manager - assertEquals(defaultValue, (Float) floatPropertyManager.getPropertyValue(5), 0); - - // after setting the value we should be able to retrieve the new - // value - floatPropertyManager.setPropertyValue(5, newValue); - assertEquals(newValue, (Float) floatPropertyManager.getPropertyValue(5), 0); - - // removing the id from the manager should have no effect, since we - // do - // not waste time setting the value back to the default - floatPropertyManager.removeId(5); - - assertEquals(newValue, (Float) floatPropertyManager.getPropertyValue(5), 0); - - // precondition tests - PropertyDefinition def = PropertyDefinition.builder().setType(Float.class).setDefaultValue(4.5F).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - FloatPropertyManager fpm = new FloatPropertyManager(c, def, 0); - - ContractException contractException = assertThrows(ContractException.class, () -> fpm.removeId(-1)); - assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); - }); - } - - @Test - @UnitTestConstructor(args = { Context.class, PropertyDefinition.class, int.class }) - public void testConstructor() { - TestActionSupport.testConsumer((c) -> { - - PropertyDefinition goodPropertyDefinition = PropertyDefinition.builder().setType(Float.class).setDefaultValue(2.3F).build(); - PropertyDefinition badPropertyDefinition = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(false).build(); - PropertyDefinition badFloatPropertyDefinition = PropertyDefinition.builder().setType(Float.class).build(); - - // if the property definition is null - ContractException contractException = assertThrows(ContractException.class, () -> new FloatPropertyManager(c, null, 0)); - assertEquals(PropertyError.NULL_PROPERTY_DEFINITION, contractException.getErrorType()); - - // if the property definition does not have a type of Float.class - contractException = assertThrows(ContractException.class, () -> new FloatPropertyManager(c, badPropertyDefinition, 0)); - assertEquals(PropertyError.PROPERTY_DEFINITION_IMPROPER_TYPE, contractException.getErrorType()); - - // if the property definition does not contain a default value - contractException = assertThrows(ContractException.class, () -> new FloatPropertyManager(c, badFloatPropertyDefinition, 0)); - assertEquals(PropertyError.PROPERTY_DEFINITION_MISSING_DEFAULT, contractException.getErrorType()); - - // if the initial size is negative - contractException = assertThrows(ContractException.class, () -> new FloatPropertyManager(c, goodPropertyDefinition, -1)); - assertEquals(PropertyError.NEGATIVE_INITIAL_SIZE, contractException.getErrorType()); - - FloatPropertyManager doublePropertyManager = new FloatPropertyManager(c, goodPropertyDefinition, 0); - assertNotNull(doublePropertyManager); - }); - } - - @Test - @UnitTestMethod(name = "incrementCapacity", args = { int.class }) - public void testIncrementCapacity() { - TestActionSupport.testConsumer((c) -> { - - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Float.class).setDefaultValue(234.42F).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - - FloatPropertyManager floatPropertyManager = new FloatPropertyManager(c, propertyDefinition, 0); - - // precondition tests - ContractException contractException = assertThrows(ContractException.class, () -> floatPropertyManager.incrementCapacity(-1)); - assertEquals(PropertyError.NEGATIVE_CAPACITY_INCREMENT, contractException.getErrorType()); - }); - } - -} diff --git a/gcm3/src/test/java/plugins/util/properties/AT_IndexedPropertyManager.java b/gcm3/src/test/java/plugins/util/properties/AT_IndexedPropertyManager.java deleted file mode 100644 index ea098a6d9..000000000 --- a/gcm3/src/test/java/plugins/util/properties/AT_IndexedPropertyManager.java +++ /dev/null @@ -1,15 +0,0 @@ -package plugins.util.properties; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - - -@UnitTest(target = IndexedPropertyManager.class) -public class AT_IndexedPropertyManager { - - @Test - public void test() { - //no test required - } -} diff --git a/gcm3/src/test/java/plugins/util/properties/AT_IntPropertyManager.java b/gcm3/src/test/java/plugins/util/properties/AT_IntPropertyManager.java deleted file mode 100644 index 029238a35..000000000 --- a/gcm3/src/test/java/plugins/util/properties/AT_IntPropertyManager.java +++ /dev/null @@ -1,282 +0,0 @@ -package plugins.util.properties; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.stream.IntStream; - -import javax.naming.Context; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.DataManagerContext; -import nucleus.testsupport.testplugin.TestActionSupport; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestDataManager; -import nucleus.testsupport.testplugin.TestPluginData; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; -import util.random.RandomGeneratorProvider; - -/** - * Common interface to all person property managers. A person property manager - * manages all the property values for people for a particular person property - * identifier. - * - * @author Shawn Hatch - * - */ -@UnitTest(target = IntPropertyManager.class) -public class AT_IntPropertyManager { - - @Test - @UnitTestMethod(name = "getPropertyValue", args = { int.class }) - public void testGetPropertyValue() { - TestActionSupport.testConsumer((c) -> { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5297426971018191882L); - - int defaultValue = 423; - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(defaultValue).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - - IntPropertyManager intPropertyManager = new IntPropertyManager(c, propertyDefinition, 0); - - /* - * We will set the first 300 values multiple times at random - */ - Map expectedValues = new LinkedHashMap<>(); - - for (int i = 0; i < 1000; i++) { - int id = randomGenerator.nextInt(300); - int value = randomGenerator.nextInt(); - expectedValues.put(id, value); - intPropertyManager.setPropertyValue(id, value); - } - - /* - * if the value was set above, then it should equal the last value - * place in the expected values, otherwise it will have the default - * value. - */ - for (int i = 0; i < 300; i++) { - if (expectedValues.containsKey(i)) { - assertEquals(expectedValues.get(i), intPropertyManager.getPropertyValue(i)); - - } else { - assertEquals(defaultValue, ((Integer) intPropertyManager.getPropertyValue(i)).intValue()); - } - } - - // precondition tests - ContractException contractException = assertThrows(ContractException.class, () -> intPropertyManager.getPropertyValue(-1)); - assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); - }); - } - - /* - * Local data manager used to properly initialize an ObjectPropertyManager - * for use in time sensitive tests - */ - public static class LocalDM extends TestDataManager { - public IntPropertyManager intPropertyManager; - - @Override - public void init(DataManagerContext dataManagerContext) { - super.init(dataManagerContext); - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(342).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - intPropertyManager = new IntPropertyManager(dataManagerContext, propertyDefinition, 0); - } - } - - @Test - @UnitTestMethod(name = "getPropertyTime", args = { int.class }) - public void testGetPropertyTime() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7115872240650739867L); - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - // Plan 1000 changes - IntStream.range(0, 1000).forEach((i -> { - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(i, (c) -> { - LocalDM localDM = c.getDataManager(LocalDM.class); - int id = randomGenerator.nextInt(300); - int value = randomGenerator.nextInt(); - - IntPropertyManager intPropertyManager = localDM.intPropertyManager; - - intPropertyManager.setPropertyValue(id, value); - // show that the property time for the id was properly set - assertEquals(c.getTime(), intPropertyManager.getPropertyTime(id), 0); - })); - })); - - // precondition tests: - TestActionSupport.testConsumer((c) -> { - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(2).build(); - IntPropertyManager ipm = new IntPropertyManager(c, propertyDefinition, 0); - ContractException contractException = assertThrows(ContractException.class, () -> ipm.getPropertyTime(0)); - assertEquals(PropertyError.TIME_TRACKING_OFF, contractException.getErrorType()); - }); - - TestActionSupport.testConsumer((c) -> { - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(2).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - IntPropertyManager ipm = new IntPropertyManager(c, propertyDefinition, 0); - ContractException contractException = assertThrows(ContractException.class, () -> ipm.getPropertyTime(-1)); - assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "setPropertyValue", args = { int.class, Object.class }) - public void testSetPropertyValue() { - TestActionSupport.testConsumer((c) -> { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5297426971018191882L); - - int defaultValue = 423; - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(defaultValue).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - - IntPropertyManager intPropertyManager = new IntPropertyManager(c, propertyDefinition, 0); - - /* - * We will set the first 300 values multiple times at random - */ - Map expectedValues = new LinkedHashMap<>(); - - for (int i = 0; i < 1000; i++) { - int id = randomGenerator.nextInt(300); - int value = randomGenerator.nextInt(); - expectedValues.put(id, value); - intPropertyManager.setPropertyValue(id, value); - } - - /* - * if the value was set above, then it should equal the last value - * place in the expected values, otherwise it will have the default - * value. - */ - for (int i = 0; i < 300; i++) { - if (expectedValues.containsKey(i)) { - assertEquals(expectedValues.get(i), intPropertyManager.getPropertyValue(i)); - - } else { - assertEquals(defaultValue, ((Integer) intPropertyManager.getPropertyValue(i)).intValue()); - } - } - - // precondition tests - ContractException contractException = assertThrows(ContractException.class, () -> intPropertyManager.setPropertyValue(-1, 23)); - assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "removeId", args = { int.class }) - public void testRemoveId() { - TestActionSupport.testConsumer((c) -> { - /* - * Should have no effect on the value that is stored for the sake of - * efficiency. - */ - - // we will first test the manager with an initial value of 6 - int defaultValue = 6; - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(defaultValue).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - - IntPropertyManager intPropertyManager = new IntPropertyManager(c, propertyDefinition, 0); - - // initially, the value should be the default value for the manager - assertEquals(defaultValue, ((Integer) intPropertyManager.getPropertyValue(5)).intValue()); - - // after setting the value we should be able to retrieve a new value - int newValue = 34534; - intPropertyManager.setPropertyValue(5, newValue); - assertEquals(newValue, ((Integer) intPropertyManager.getPropertyValue(5)).intValue()); - - // removing the id from the manager should have no effect, since we - // do - // not waste time setting the value back to the default - intPropertyManager.removeId(5); - - assertEquals(newValue, ((Integer) intPropertyManager.getPropertyValue(5)).intValue()); - - // we will next test the manager with an initial value of true - propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(defaultValue).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - - intPropertyManager = new IntPropertyManager(c, propertyDefinition, 0); - - // initially, the value should be the default value for the manager - assertEquals(defaultValue, ((Integer) intPropertyManager.getPropertyValue(5)).intValue()); - - // after setting the value we should be able to retrieve the new - // value - intPropertyManager.setPropertyValue(5, newValue); - assertEquals(newValue, ((Integer) intPropertyManager.getPropertyValue(5)).intValue(), 0); - - // removing the id from the manager should have no effect, since we - // do - // not waste time setting the value back to the default - intPropertyManager.removeId(5); - - assertEquals(newValue, ((Integer) intPropertyManager.getPropertyValue(5)).intValue(), 0); - - // precondition tests - PropertyDefinition def = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(3).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - IntPropertyManager ipm = new IntPropertyManager(c, def, 0); - - ContractException contractException = assertThrows(ContractException.class, () -> ipm.removeId(-1)); - assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); - }); - } - - @Test - @UnitTestConstructor(args = { Context.class, PropertyDefinition.class, int.class }) - public void testConstructor() { - TestActionSupport.testConsumer((c) -> { - - PropertyDefinition goodPropertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(2).build(); - PropertyDefinition badPropertyDefinition = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(false).build(); - PropertyDefinition badIntPropertyDefinition = PropertyDefinition.builder().setType(Integer.class).build(); - - // if the property definition is null - ContractException contractException = assertThrows(ContractException.class, () -> new IntPropertyManager(c, null, 0)); - assertEquals(PropertyError.NULL_PROPERTY_DEFINITION, contractException.getErrorType()); - - // if the property definition does not have a type of Double.class - contractException = assertThrows(ContractException.class, () -> new IntPropertyManager(c, badPropertyDefinition, 0)); - assertEquals(PropertyError.PROPERTY_DEFINITION_IMPROPER_TYPE, contractException.getErrorType()); - - // if the property definition does not contain a default value - contractException = assertThrows(ContractException.class, () -> new IntPropertyManager(c, badIntPropertyDefinition, 0)); - assertEquals(PropertyError.PROPERTY_DEFINITION_MISSING_DEFAULT, contractException.getErrorType()); - - // if the initial size is negative - contractException = assertThrows(ContractException.class, () -> new IntPropertyManager(c, goodPropertyDefinition, -1)); - assertEquals(PropertyError.NEGATIVE_INITIAL_SIZE, contractException.getErrorType()); - - IntPropertyManager doublePropertyManager = new IntPropertyManager(c, goodPropertyDefinition, 0); - assertNotNull(doublePropertyManager); - }); - } - - @Test - @UnitTestMethod(name = "incrementCapacity", args = { int.class }) - public void testIncrementCapacity() { - TestActionSupport.testConsumer((c) -> { - - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(234).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - - IntPropertyManager intPropertyManager = new IntPropertyManager(c, propertyDefinition, 0); - - // precondition tests - ContractException contractException = assertThrows(ContractException.class, () -> intPropertyManager.incrementCapacity(-1)); - assertEquals(PropertyError.NEGATIVE_CAPACITY_INCREMENT, contractException.getErrorType()); - }); - } - -} diff --git a/gcm3/src/test/java/plugins/util/properties/AT_ObjectPropertyManager.java b/gcm3/src/test/java/plugins/util/properties/AT_ObjectPropertyManager.java deleted file mode 100644 index e7b69a417..000000000 --- a/gcm3/src/test/java/plugins/util/properties/AT_ObjectPropertyManager.java +++ /dev/null @@ -1,311 +0,0 @@ -package plugins.util.properties; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.stream.IntStream; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import nucleus.DataManagerContext; -import nucleus.Plugin; -import nucleus.SimulationContext; -import nucleus.testsupport.testplugin.TestActionSupport; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestDataManager; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.errors.ContractException; -import util.random.RandomGeneratorProvider; - -/** - * Common interface to all person property managers. A person property manager - * manages all the property values for people for a particular person property - * identifier. - * - * @author Shawn Hatch - * - */ - -@UnitTest(target = ObjectPropertyManager.class) -public class AT_ObjectPropertyManager { - - @Test - @UnitTestMethod(name = "getPropertyValue", args = { int.class }) - public void testGetPropertyValue() { - TestActionSupport.testConsumer((c) -> { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6268125375257441705L); - - String defaultValue = "YELLOW"; - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(String.class).setDefaultValue(defaultValue).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - - ObjectPropertyManager objectPropertyManager = new ObjectPropertyManager(c, propertyDefinition, 0); - - /* - * We will set the first 300 values multiple times at random - */ - Map expectedValues = new LinkedHashMap<>(); - - for (int i = 0; i < 1000; i++) { - int id = randomGenerator.nextInt(300); - String value = getRandomString(randomGenerator); - expectedValues.put(id, value); - objectPropertyManager.setPropertyValue(id, value); - } - - /* - * if the value was set above, then it should equal the last value - * place in the expected values, otherwise it will have the default - * value. - */ - for (int i = 0; i < 300; i++) { - if (expectedValues.containsKey(i)) { - assertEquals(expectedValues.get(i), objectPropertyManager.getPropertyValue(i)); - - } else { - assertEquals(defaultValue, (String) objectPropertyManager.getPropertyValue(i)); - - } - } - - // precondition tests - ContractException contractException = assertThrows(ContractException.class, () -> objectPropertyManager.getPropertyValue(-1)); - assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); - }); - } - - /* - * Local data manager used to properly initialize an ObjectPropertyManager - * for use in time sensitive tests - */ - private static class LocalDM extends TestDataManager { - public ObjectPropertyManager objectPropertyManager; - - @Override - public void init(DataManagerContext dataManagerContext) { - super.init(dataManagerContext); - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(String.class).setDefaultValue("YELLOW").setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - objectPropertyManager = new ObjectPropertyManager(dataManagerContext, propertyDefinition, 0); - } - } - - @Test - @UnitTestMethod(name = "getPropertyTime", args = { int.class }) - public void testGetPropertyTime() { - - /* - * Execute random changes to an object property for several people. - */ - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3180659211825142278L); - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - // Plan 1000 changes - IntStream.range(0, 1000).forEach((i -> { - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(i, (c) -> { - LocalDM localDM = c.getDataManager(LocalDM.class); - int id = randomGenerator.nextInt(300); - String value = getRandomString(randomGenerator); - ObjectPropertyManager objectPropertyManager = localDM.objectPropertyManager; - objectPropertyManager.setPropertyValue(id, value); - // show that the property time for the id was properly set - assertEquals(c.getTime(), objectPropertyManager.getPropertyTime(id), 0); - })); - })); - - // add the local data manager - pluginDataBuilder.addTestDataManager("dm",()-> new LocalDM()); - - // build and run the simulation - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin plugin = TestPlugin.getTestPlugin(testPluginData); - TestActionSupport.testConsumers(plugin); - - // precondition test: if time tracking is no engaged - TestActionSupport.testConsumer((c) -> { - PropertyDefinition propertyDefinition = PropertyDefinition .builder()// - .setType(Boolean.class)// - .setDefaultValue(false)// - .build();// - ObjectPropertyManager opm = new ObjectPropertyManager(c, propertyDefinition, 0); - ContractException contractException = assertThrows(ContractException.class, () -> opm.getPropertyTime(0)); - assertEquals(PropertyError.TIME_TRACKING_OFF, contractException.getErrorType()); - }); - - // precondition test: if a property time is retrieved for a negative - // index - TestActionSupport.testConsumer((c) -> { - PropertyDefinition propertyDefinition = PropertyDefinition .builder()// - .setType(Boolean.class)// - .setDefaultValue(false)// - .setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME)// - .build();// - ObjectPropertyManager opm = new ObjectPropertyManager(c, propertyDefinition, 0); - ContractException contractException = assertThrows(ContractException.class, () -> opm.getPropertyTime(-1)); - assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); - }); - - } - - private static String getRandomString(RandomGenerator randomGenerator) { - switch (randomGenerator.nextInt(3)) { - case 0: - return "RED"; - case 1: - return "YELLOW"; - default: - return "BLUE"; - } - } - - @Test - @UnitTestMethod(name = "setPropertyValue", args = { int.class, Object.class }) - public void testSetPropertyValue() { - TestActionSupport.testConsumer((c) -> { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6268125375257441705L); - - String defaultValue = "YELLOW"; - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(String.class).setDefaultValue(defaultValue).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - - ObjectPropertyManager objectPropertyManager = new ObjectPropertyManager(c, propertyDefinition, 0); - - /* - * We will set the first 300 values multiple times at random - */ - Map expectedValues = new LinkedHashMap<>(); - - for (int i = 0; i < 1000; i++) { - int id = randomGenerator.nextInt(300); - String value = getRandomString(randomGenerator); - expectedValues.put(id, value); - objectPropertyManager.setPropertyValue(id, value); - } - - /* - * if the value was set above, then it should equal the last value - * place in the expected values, otherwise it will have the default - * value. - */ - for (int i = 0; i < 300; i++) { - if (expectedValues.containsKey(i)) { - assertEquals(expectedValues.get(i), objectPropertyManager.getPropertyValue(i)); - - } else { - assertEquals(defaultValue, (String) objectPropertyManager.getPropertyValue(i)); - - } - } - - // precondition tests - ContractException contractException = assertThrows(ContractException.class, () -> objectPropertyManager.setPropertyValue(-1, "value")); - assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); - }); - } - - @Test - @UnitTestMethod(name = "removeId", args = { int.class }) - public void testRemoveId() { - - TestActionSupport.testConsumer((c) -> { - /* - * Should have no effect on the value that is stored for the sake of - * efficiency. - */ - - // we will first test the manager with an initial value of false - String defaultValue = "RED"; - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(String.class).setDefaultValue(defaultValue).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - - ObjectPropertyManager objectPropertyManager = new ObjectPropertyManager(c, propertyDefinition, 0); - - // initially, the value should be the default value for the manager - assertEquals(defaultValue, (String) objectPropertyManager.getPropertyValue(5)); - - // after setting the value we should be able to retrieve a new value - String newValue = "BLUE"; - objectPropertyManager.setPropertyValue(5, newValue); - assertEquals(newValue, (String) objectPropertyManager.getPropertyValue(5)); - - // removing the id from the manager should return the value to the - // deafault - objectPropertyManager.removeId(5); - - assertEquals(defaultValue, (String) objectPropertyManager.getPropertyValue(5)); - - // we will next test the manager with an initial value of true - propertyDefinition = PropertyDefinition.builder().setType(String.class).setDefaultValue(defaultValue).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - - objectPropertyManager = new ObjectPropertyManager(c, propertyDefinition, 0); - - // initially, the value should be the default value for the manager - assertEquals(defaultValue, (String) objectPropertyManager.getPropertyValue(5)); - - // after setting the value we should be able to retrieve the new - // value - objectPropertyManager.setPropertyValue(5, newValue); - assertEquals(newValue, (String) objectPropertyManager.getPropertyValue(5)); - - // removing the id from the manager should return the value to the - // default - objectPropertyManager.removeId(5); - - assertEquals(defaultValue, (String) objectPropertyManager.getPropertyValue(5)); - - // precondition tests - PropertyDefinition def = PropertyDefinition.builder().setType(Boolean.class).setDefaultValue(true).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - ObjectPropertyManager opm = new ObjectPropertyManager(c, def, 0); - - ContractException contractException = assertThrows(ContractException.class, () -> opm.removeId(-1)); - assertEquals(PropertyError.NEGATIVE_INDEX, contractException.getErrorType()); - }); - } - - @Test - @UnitTestConstructor(args = { SimulationContext.class, PropertyDefinition.class, int.class }) - public void testConstructor() { - TestActionSupport.testConsumer((c) -> { - - PropertyDefinition goodPropertyDefinition = PropertyDefinition.builder().setType(Object.class).setDefaultValue("BLUE").build(); - - PropertyDefinition badDoublePropertyDefinition = PropertyDefinition.builder().setType(Object.class).build(); - - // if the property definition is null - ContractException contractException = assertThrows(ContractException.class, () -> new ObjectPropertyManager(c, null, 0)); - assertEquals(PropertyError.NULL_PROPERTY_DEFINITION, contractException.getErrorType()); - - // if the property definition does not contain a default value - contractException = assertThrows(ContractException.class, () -> new ObjectPropertyManager(c, badDoublePropertyDefinition, 0)); - assertEquals(PropertyError.PROPERTY_DEFINITION_MISSING_DEFAULT, contractException.getErrorType()); - - // if the initial size is negative - contractException = assertThrows(ContractException.class, () -> new ObjectPropertyManager(c, goodPropertyDefinition, -1)); - assertEquals(PropertyError.NEGATIVE_INITIAL_SIZE, contractException.getErrorType()); - - ObjectPropertyManager objectPropertyManager = new ObjectPropertyManager(c, goodPropertyDefinition, 0); - assertNotNull(objectPropertyManager); - }); - } - - @Test - @UnitTestMethod(name = "incrementCapacity", args = { int.class }) - public void testIncrementCapacity() { - TestActionSupport.testConsumer((c) -> { - - PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Integer.class).setDefaultValue(234).setTimeTrackingPolicy(TimeTrackingPolicy.TRACK_TIME).build(); - - ObjectPropertyManager objectPropertyManager = new ObjectPropertyManager(c, propertyDefinition, 0); - - // precondition tests - ContractException contractException = assertThrows(ContractException.class, () -> objectPropertyManager.incrementCapacity(-1)); - assertEquals(PropertyError.NEGATIVE_CAPACITY_INCREMENT, contractException.getErrorType()); - }); - } - -} diff --git a/gcm3/src/test/java/plugins/util/properties/AT_PropertyError.java b/gcm3/src/test/java/plugins/util/properties/AT_PropertyError.java deleted file mode 100644 index b7f202070..000000000 --- a/gcm3/src/test/java/plugins/util/properties/AT_PropertyError.java +++ /dev/null @@ -1,30 +0,0 @@ -package plugins.util.properties; - -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = PropertyError.class) -public class AT_PropertyError { - - @Test - @UnitTestMethod(name = "getDescription", args = {}) - public void test() { - //show that each description is a unique, non-null and non-empty string - Set descriptions = new LinkedHashSet<>(); - for(PropertyError propertyError : PropertyError.values()) { - String description = propertyError.getDescription(); - assertNotNull(description,"null description for "+propertyError); - assertTrue(description.length()>0, "empty string for "+propertyError); - boolean unique = descriptions.add(description); - assertTrue(unique,"description for "+propertyError+" is not unique"); - } - } -} diff --git a/gcm3/src/test/java/plugins/util/properties/AT_PropertyValueRecord.java b/gcm3/src/test/java/plugins/util/properties/AT_PropertyValueRecord.java deleted file mode 100644 index ac45a55ce..000000000 --- a/gcm3/src/test/java/plugins/util/properties/AT_PropertyValueRecord.java +++ /dev/null @@ -1,110 +0,0 @@ -package plugins.util.properties; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import org.junit.jupiter.api.Test; - -import nucleus.Plugin; -import nucleus.SimulationContext; -import nucleus.testsupport.testplugin.TestActionSupport; -import nucleus.testsupport.testplugin.TestActorPlan; -import nucleus.testsupport.testplugin.TestPlugin; -import nucleus.testsupport.testplugin.TestPluginData; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = PropertyValueRecord.class) -public class AT_PropertyValueRecord { - - /** - * test for {@link PropertyValueRecord#getValue()} - */ - @Test - @UnitTestMethod(name = "getValue", args = {}) - public void testGetValue() { - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(345.6, (c) -> { - PropertyValueRecord propertyValueRecord = new PropertyValueRecord(c); - propertyValueRecord.setPropertyValue("cat"); - assertEquals("cat", propertyValueRecord.getValue()); - })); - - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(456.2, (c) -> { - PropertyValueRecord propertyValueRecord = new PropertyValueRecord(c); - propertyValueRecord.setPropertyValue("dog"); - assertEquals("dog", propertyValueRecord.getValue()); - - })); - - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin plugin = TestPlugin.getTestPlugin(testPluginData); - TestActionSupport.testConsumers(plugin); - } - - /** - * test for {@link PropertyValueRecord#setPropertyValue(Object)} - */ - @Test - @UnitTestMethod(name = "setPropertyValue", args = { Object.class }) - public void testSetPropertyValue() { - - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(345.6, (c) -> { - PropertyValueRecord propertyValueRecord = new PropertyValueRecord(c); - propertyValueRecord.setPropertyValue("cat"); - assertEquals("cat", propertyValueRecord.getValue()); - })); - - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(456.2, (c) -> { - PropertyValueRecord propertyValueRecord = new PropertyValueRecord(c); - propertyValueRecord.setPropertyValue("dog"); - assertEquals("dog", propertyValueRecord.getValue()); - - })); - - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin plugin = TestPlugin.getTestPlugin(testPluginData); - TestActionSupport.testConsumers(plugin); - } - - @Test - @UnitTestMethod(name = "getAssignmentTime", args = {}) - public void testGetAssignmentTime() { - TestPluginData.Builder pluginDataBuilder = TestPluginData.builder(); - - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(345.6, (c) -> { - PropertyValueRecord propertyValueRecord = new PropertyValueRecord(c); - propertyValueRecord.setPropertyValue("cat"); - assertEquals(c.getTime(), propertyValueRecord.getAssignmentTime(), 0); - })); - - pluginDataBuilder.addTestActorPlan("actor", new TestActorPlan(456.2, (c) -> { - PropertyValueRecord propertyValueRecord = new PropertyValueRecord(c); - propertyValueRecord.setPropertyValue("dog"); - assertEquals(c.getTime(), propertyValueRecord.getAssignmentTime(), 0); - - })); - - TestPluginData testPluginData = pluginDataBuilder.build(); - Plugin plugin = TestPlugin.getTestPlugin(testPluginData); - TestActionSupport.testConsumers(plugin); - - } - - @Test - @UnitTestConstructor(args = { SimulationContext.class }) - public void testConstructor() { - TestActionSupport.testConsumer((c) -> { - PropertyValueRecord propertyValueRecord = new PropertyValueRecord(c); - assertNotNull(propertyValueRecord); - assertEquals(0, propertyValueRecord.getAssignmentTime()); - }); - - } - -} \ No newline at end of file diff --git a/gcm3/src/test/java/plugins/util/properties/AT_TimeTrackingPolicy.java b/gcm3/src/test/java/plugins/util/properties/AT_TimeTrackingPolicy.java deleted file mode 100644 index 717d977c6..000000000 --- a/gcm3/src/test/java/plugins/util/properties/AT_TimeTrackingPolicy.java +++ /dev/null @@ -1,13 +0,0 @@ -package plugins.util.properties; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - -@UnitTest(target = TimeTrackingPolicy.class) -public class AT_TimeTrackingPolicy { - @Test - public void test() { - // nothing to test - } -} diff --git a/gcm3/src/test/java/plugins/util/properties/arraycontainers/AT_BooleanContainer.java b/gcm3/src/test/java/plugins/util/properties/arraycontainers/AT_BooleanContainer.java deleted file mode 100644 index 40ee641ca..000000000 --- a/gcm3/src/test/java/plugins/util/properties/arraycontainers/AT_BooleanContainer.java +++ /dev/null @@ -1,108 +0,0 @@ -package plugins.util.properties.arraycontainers; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.Random; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; - -/** - * Test class for {@link BooleanContainer} - * - * @author Shawn Hatch - * - */ -@UnitTest(target = BooleanContainer.class) -public class AT_BooleanContainer { - - /** - * Test {@link BooleanContainer#BooleanContainer(boolean)} - */ - @Test - @UnitTestConstructor(args = { boolean.class }) - public void testConstructor_Boolean() { - BooleanContainer booleanContainer = new BooleanContainer(true); - - for (int i = 0; i < 10; i++) { - assertTrue(booleanContainer.get(i)); - } - - booleanContainer = new BooleanContainer(false); - - for (int i = 0; i < 10; i++) { - assertFalse(booleanContainer.get(i)); - } - - } - - /** - * Test {@link BooleanContainer#BooleanContainer(boolean, int)} - */ - @Test - @UnitTestConstructor(args = { boolean.class, int.class }) - public void testConstructor_BooleanInt() { - - BooleanContainer booleanContainer = new BooleanContainer(true, 100); - - for (int i = 0; i < 10; i++) { - assertTrue(booleanContainer.get(i)); - } - - booleanContainer = new BooleanContainer(false, 100); - - for (int i = 0; i < 10; i++) { - assertFalse(booleanContainer.get(i)); - } - - } - - /** - * Test {@link BooleanContainer#get(int)} - */ - @Test - @UnitTestMethod(name = "get", args = { int.class }) - public void testGet() { - Random random = new Random(53463457457456456L); - int n = 1000; - boolean[] array = new boolean[n]; - for (int i = 0; i < n; i++) { - array[i] = random.nextBoolean(); - } - - BooleanContainer booleanContainer = new BooleanContainer(true); - - for (int i = 0; i < n; i++) { - booleanContainer.set(i, array[i]); - } - - for (int i = 0; i < n; i++) { - assertEquals(array[i], booleanContainer.get(i)); - } - - booleanContainer = new BooleanContainer(true, n); - - for (int i = 0; i < n; i++) { - booleanContainer.set(i, array[i]); - } - - for (int i = 0; i < n; i++) { - assertEquals(array[i], booleanContainer.get(i)); - } - - } - - /** - * Test {@link BooleanContainer#set(int, boolean)} - */ - @Test - @UnitTestMethod(name = "set", args = { int.class, boolean.class }) - public void testSet() { - // proxy via testGet() - } -} diff --git a/gcm3/src/test/java/plugins/util/properties/arraycontainers/AT_DoubleValueContainer.java b/gcm3/src/test/java/plugins/util/properties/arraycontainers/AT_DoubleValueContainer.java deleted file mode 100644 index a2f574798..000000000 --- a/gcm3/src/test/java/plugins/util/properties/arraycontainers/AT_DoubleValueContainer.java +++ /dev/null @@ -1,190 +0,0 @@ -package plugins.util.properties.arraycontainers; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = DoubleValueContainer.class) -public class AT_DoubleValueContainer { - - /** - * Tests {@link DoubleValueContainer#DoubleValueContainer(double)} - */ - @Test - @UnitTestConstructor(args = { double.class }) - public void testConstructor_Double() { - DoubleValueContainer doubleValueContainer = new DoubleValueContainer(0); - assertNotNull(doubleValueContainer); - } - - /** - * Tests {@link DoubleValueContainer#DoubleValueContainer(double, int)} - */ - @Test - @UnitTestConstructor(args = { double.class, int.class }) - public void testConstructor_DoubleInt() { - - DoubleValueContainer doubleValueContainer = new DoubleValueContainer(0, 1000); - assertNotNull(doubleValueContainer); - assertTrue(doubleValueContainer.getCapacity() >= 1000); - - // pre conditions - assertThrows(NegativeArraySizeException.class, () -> new DoubleValueContainer(0, -1)); - - } - - /** - * Tests {@link DoubleValueContainer#getCapacity()} - */ - @Test - @UnitTestMethod(name = "getCapacity", args = {}) - public void testGetCapacity() { - DoubleValueContainer doubleValueContainer = new DoubleValueContainer(0); - - assertTrue(doubleValueContainer.getCapacity() >= doubleValueContainer.size()); - - doubleValueContainer.setValue(1, 123.4); - assertTrue(doubleValueContainer.getCapacity() >= doubleValueContainer.size()); - - doubleValueContainer.setValue(34, 36.4); - assertTrue(doubleValueContainer.getCapacity() >= doubleValueContainer.size()); - - doubleValueContainer.setValue(10, 15.4); - assertTrue(doubleValueContainer.getCapacity() >= doubleValueContainer.size()); - - doubleValueContainer.setValue(137, 25.26); - assertTrue(doubleValueContainer.getCapacity() >= doubleValueContainer.size()); - - doubleValueContainer.setValue(1000, 123.6345); - assertTrue(doubleValueContainer.getCapacity() >= doubleValueContainer.size()); - } - - /** - * Tests {@link DoubleValueContainer#getDefaultValue()} - */ - @Test - @UnitTestMethod(name = "getDefaultValue", args = {}) - public void testGetDefaultValue() { - double defaultValue = 0; - DoubleValueContainer doubleValueContainer = new DoubleValueContainer(defaultValue); - assertEquals(defaultValue, doubleValueContainer.getDefaultValue(), 0); - - defaultValue = -10; - doubleValueContainer = new DoubleValueContainer(defaultValue); - assertEquals(defaultValue, doubleValueContainer.getDefaultValue(), 0); - - defaultValue = 10; - doubleValueContainer = new DoubleValueContainer(defaultValue); - assertEquals(defaultValue, doubleValueContainer.getDefaultValue(), 0); - - } - - /** - * Tests {@link DoubleValueContainer#getValue(int)} - */ - @Test - @UnitTestMethod(name = "getValue", args = { int.class }) - public void testGetValue() { - double defaultValue = -345.34; - DoubleValueContainer doubleValueContainer = new DoubleValueContainer(defaultValue); - int highIndex = 1000; - double delta = 2.3452346; - - double[] doubles = new double[highIndex]; - for (int i = 0; i < doubles.length; i++) { - doubles[i] = delta + i; - } - for (int i = 0; i < doubles.length; i++) { - doubleValueContainer.setValue(i, doubles[i]); - } - - for (int i = 0; i < doubles.length; i++) { - assertEquals(doubles[i], doubleValueContainer.getValue(i), 0); - } - - // show that the default value is returned for indices that have not yet - // had value assignments - for (int i = 0; i < 5; i++) { - assertEquals(doubleValueContainer.getValue(i + highIndex), defaultValue, 0); - } - - // pre-condition tests - - // if index < 0 - assertThrows(RuntimeException.class, () -> doubleValueContainer.getValue(-1)); - - } - - /** - * Tests {@link DoubleValueContainer#setCapacity(int)} - */ - @Test - @UnitTestMethod(name = "setCapacity", args = { int.class }) - public void testSetCapacity() { - DoubleValueContainer doubleValueContainer = new DoubleValueContainer(0); - - int expectedCapacity = 5; - doubleValueContainer.setCapacity(expectedCapacity); - assertTrue(doubleValueContainer.getCapacity() >= expectedCapacity); - - expectedCapacity = 15; - doubleValueContainer.setCapacity(expectedCapacity); - assertTrue(doubleValueContainer.getCapacity() >= expectedCapacity); - - expectedCapacity = 50; - doubleValueContainer.setCapacity(expectedCapacity); - assertTrue(doubleValueContainer.getCapacity() >= expectedCapacity); - - expectedCapacity = 1000; - doubleValueContainer.setCapacity(expectedCapacity); - assertTrue(doubleValueContainer.getCapacity() >= expectedCapacity); - } - - /** - * Tests {@link DoubleValueContainer#setValue(int, double)} - */ - @Test - @UnitTestMethod(name = "setValue", args = { int.class, double.class }) - public void testSetValue() { - DoubleValueContainer doubleValueContainer = new DoubleValueContainer(0); - - // long value - double value = 12123.234; - doubleValueContainer.setValue(0, value); - assertEquals(value, doubleValueContainer.getValue(0), 0); - - // pre-condition tests - assertThrows(RuntimeException.class, () -> doubleValueContainer.setValue(-1, 234.63)); - - } - - /** - * Tests {@link DoubleValueContainer#size()} - */ - @Test - @UnitTestMethod(name = "size", args = {}) - public void testSize() { - DoubleValueContainer doubleValueContainer = new DoubleValueContainer(0, 100); - assertEquals(0, doubleValueContainer.size()); - - doubleValueContainer.setValue(3, 352.2345); - assertEquals(4, doubleValueContainer.size()); - - doubleValueContainer.setValue(1, 7456.63); - assertEquals(4, doubleValueContainer.size()); - - doubleValueContainer.setValue(15, 99.1576); - assertEquals(16, doubleValueContainer.size()); - - doubleValueContainer.setValue(300, 247.989762); - assertEquals(301, doubleValueContainer.size()); - } - -} diff --git a/gcm3/src/test/java/plugins/util/properties/arraycontainers/AT_EnumContainer.java b/gcm3/src/test/java/plugins/util/properties/arraycontainers/AT_EnumContainer.java deleted file mode 100644 index f4ebe0f42..000000000 --- a/gcm3/src/test/java/plugins/util/properties/arraycontainers/AT_EnumContainer.java +++ /dev/null @@ -1,167 +0,0 @@ -package plugins.util.properties.arraycontainers; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Random; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; - -/** - * Test class for {@link EnumContainer} - * - * @author Shawn Hatch - * - */ - -@UnitTest(target = EnumContainer.class) -public class AT_EnumContainer { - public enum Animal { - CAT, PIG, SHEEP, DOG, HORSE; - } - - /** - * Tests {@link EnumContainer#EnumContainer(Class, Object)} - */ - @Test - @UnitTestConstructor(args = { Class.class, Object.class }) - public void testConstructor_ClassObject() { - assertNotNull(new EnumContainer(Animal.class, Animal.DOG)); - - // Test preconditions - - // if the class is null - assertThrows(IllegalArgumentException.class, () -> new EnumContainer(null, Animal.DOG)); - - // if the class is not an enumeration - assertThrows(IllegalArgumentException.class, () -> new EnumContainer(Integer.class, Animal.DOG)); - - // if the default value is null - assertThrows(IllegalArgumentException.class, () -> new EnumContainer(Animal.class, null)); - - // if the default is not a member of the enum - assertThrows(IllegalArgumentException.class, () -> new EnumContainer(Animal.class, 234)); - - } - - /** - * Tests {@link EnumContainer#EnumContainer(Class, Object, int)} - */ - @Test - @UnitTestConstructor(args = { Class.class, Object.class, int.class }) - public void testConstructor_ClassObjectInt() { - assertNotNull(new EnumContainer(Animal.class, Animal.DOG)); - - // Test preconditions - - // if the class is null - assertThrows(IllegalArgumentException.class, () -> new EnumContainer(null, Animal.DOG, 100)); - - // if the class is not an enumeration - assertThrows(IllegalArgumentException.class, () -> new EnumContainer(Integer.class, Animal.DOG, 100)); - - // if the default value is null - assertThrows(IllegalArgumentException.class, () -> new EnumContainer(Animal.class, null, 100)); - - // if the default is not a member of the enum - assertThrows(IllegalArgumentException.class, () -> new EnumContainer(Animal.class, 234, 100)); - - // if the capacity is negative - assertThrows(IllegalArgumentException.class, () -> new EnumContainer(Animal.class, Animal.DOG, -1)); - - } - - /** - * Tests {@link EnumContainer#getValue(int)} - */ - @Test - @UnitTestMethod(name = "getValue", args = { int.class }) - public void testGetValue() { - EnumContainer enumContainer = new EnumContainer(Animal.class, Animal.DOG); - enumContainer.setValue(3, Animal.CAT); - enumContainer.setValue(5, Animal.CAT); - enumContainer.setValue(2, Animal.SHEEP); - enumContainer.setValue(0, Animal.HORSE); - enumContainer.setValue(2, Animal.PIG); - - assertEquals(Animal.HORSE, enumContainer.getValue(0)); - assertEquals(Animal.DOG, enumContainer.getValue(1)); - assertEquals(Animal.PIG, enumContainer.getValue(2)); - assertEquals(Animal.CAT, enumContainer.getValue(3)); - assertEquals(Animal.DOG, enumContainer.getValue(4)); - assertEquals(Animal.CAT, enumContainer.getValue(5)); - assertEquals(Animal.DOG, enumContainer.getValue(6)); - - enumContainer = new EnumContainer(Animal.class, Animal.DOG, 100); - enumContainer.setValue(3, Animal.CAT); - enumContainer.setValue(5, Animal.CAT); - enumContainer.setValue(2, Animal.SHEEP); - enumContainer.setValue(0, Animal.HORSE); - enumContainer.setValue(2, Animal.PIG); - - assertEquals(Animal.HORSE, enumContainer.getValue(0)); - assertEquals(Animal.DOG, enumContainer.getValue(1)); - assertEquals(Animal.PIG, enumContainer.getValue(2)); - assertEquals(Animal.CAT, enumContainer.getValue(3)); - assertEquals(Animal.DOG, enumContainer.getValue(4)); - assertEquals(Animal.CAT, enumContainer.getValue(5)); - assertEquals(Animal.DOG, enumContainer.getValue(6)); - - // Test pre-conditions - - EnumContainer preConditionEnumContainer = enumContainer; - // if the index is negative - assertThrows(ArrayIndexOutOfBoundsException.class, () -> preConditionEnumContainer.getValue(-1)); - - } - - /** - * Test {@link EnumContainer#setValue(int, Object)} - */ - @Test - @UnitTestMethod(name = "setValue", args = { int.class, Object.class }) - public void testSetValue() { - - Map animalMap = new LinkedHashMap<>(); - - EnumContainer enumContainer = new EnumContainer(Animal.class, Animal.DOG); - Random random = new Random(4545456567994423L); - for (int i = 0; i < 1000; i++) { - int index = random.nextInt(6); - int ord = random.nextInt(Animal.values().length); - Animal animal = Animal.values()[ord]; - animalMap.put(index, animal); - enumContainer.setValue(index, animal); - assertEquals(animal, enumContainer.getValue(index)); - } - - enumContainer = new EnumContainer(Animal.class, Animal.DOG, 100); - for (int i = 0; i < 1000; i++) { - int index = random.nextInt(6); - int ord = random.nextInt(Animal.values().length); - Animal animal = Animal.values()[ord]; - animalMap.put(index, animal); - enumContainer.setValue(index, animal); - assertEquals(animal, enumContainer.getValue(index)); - } - - // Test pre-conditions - EnumContainer preConditionEnumContainer = enumContainer; - // if the index is negative - assertThrows(IllegalArgumentException.class, () -> preConditionEnumContainer.setValue(-1, Animal.HORSE)); - - // if the value is null - assertThrows(IllegalArgumentException.class, () -> preConditionEnumContainer.setValue(1, null)); - - // if the value is not a member of the enumeration - assertThrows(IllegalArgumentException.class, () -> preConditionEnumContainer.setValue(1, 45)); - } - -} diff --git a/gcm3/src/test/java/plugins/util/properties/arraycontainers/AT_FloatValueContainer.java b/gcm3/src/test/java/plugins/util/properties/arraycontainers/AT_FloatValueContainer.java deleted file mode 100644 index d81867340..000000000 --- a/gcm3/src/test/java/plugins/util/properties/arraycontainers/AT_FloatValueContainer.java +++ /dev/null @@ -1,195 +0,0 @@ -package plugins.util.properties.arraycontainers; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; - -/** - * Test class for {@link FloatValueContainer} - * - * @author Shawn Hatch - * - */ -@UnitTest(target = FloatValueContainer.class) -public class AT_FloatValueContainer { - - /** - * Tests {@link FloatValueContainer#FloatValueContainer(float, int)} - */ - @Test - @UnitTestConstructor(args = { float.class, int.class }) - public void testConstructor_FloatInt() { - - FloatValueContainer floatValueContainer = new FloatValueContainer(0, 1000); - assertNotNull(floatValueContainer); - assertTrue(floatValueContainer.getCapacity() >= 1000); - - // pre conditions - assertThrows(NegativeArraySizeException.class, () -> new FloatValueContainer(0, -1)); - } - - /** - * Tests {@link FloatValueContainer#FloatValueContainer(float)} - */ - @Test - @UnitTestConstructor(args = { float.class }) - public void testConstructor_Float() { - FloatValueContainer floatValueContainer = new FloatValueContainer(0); - assertNotNull(floatValueContainer); - } - - /** - * Tests {@link FloatValueContainer#getCapacity()} - */ - @Test - @UnitTestMethod(name = "getCapacity", args = {}) - public void testGetCapacity() { - FloatValueContainer floatValueContainer = new FloatValueContainer(0); - - assertTrue(floatValueContainer.getCapacity() >= floatValueContainer.size()); - - floatValueContainer.setValue(1, 123.4f); - assertTrue(floatValueContainer.getCapacity() >= floatValueContainer.size()); - - floatValueContainer.setValue(34, 36.4f); - assertTrue(floatValueContainer.getCapacity() >= floatValueContainer.size()); - - floatValueContainer.setValue(10, 15.4f); - assertTrue(floatValueContainer.getCapacity() >= floatValueContainer.size()); - - floatValueContainer.setValue(137, 25.26f); - assertTrue(floatValueContainer.getCapacity() >= floatValueContainer.size()); - - floatValueContainer.setValue(1000, 123.6345f); - assertTrue(floatValueContainer.getCapacity() >= floatValueContainer.size()); - } - - /** - * Tests {@link FloatValueContainer#getDefaultValue()} - */ - @Test - @UnitTestMethod(name = "getDefaultValue", args = {}) - public void testGetDefaultValue() { - float defaultValue = 0; - FloatValueContainer floatValueContainer = new FloatValueContainer(defaultValue); - assertEquals(defaultValue, floatValueContainer.getDefaultValue(), 0); - - defaultValue = -10; - floatValueContainer = new FloatValueContainer(defaultValue); - assertEquals(defaultValue, floatValueContainer.getDefaultValue(), 0); - - defaultValue = 10; - floatValueContainer = new FloatValueContainer(defaultValue); - assertEquals(defaultValue, floatValueContainer.getDefaultValue(), 0); - - } - - /** - * Test {@link FloatValueContainer#getValue(int)} - */ - @Test - @UnitTestMethod(name = "getValue", args = { int.class }) - public void testGetValue() { - float defaultValue = -345.34f; - FloatValueContainer floatValueContainer = new FloatValueContainer(defaultValue); - int highIndex = 1000; - float delta = 2.3452346f; - - float[] floats = new float[highIndex]; - for (int i = 0; i < floats.length; i++) { - floats[i] = delta + i; - } - for (int i = 0; i < floats.length; i++) { - floatValueContainer.setValue(i, floats[i]); - } - - for (int i = 0; i < floats.length; i++) { - assertEquals(floats[i], floatValueContainer.getValue(i), 0); - } - - // show that the default value is returned for indices that have not yet - // had value assignments - for (int i = 0; i < 5; i++) { - assertEquals(floatValueContainer.getValue(i + highIndex), defaultValue, 0); - } - - // pre-condition tests - - // if index < 0 - assertThrows(RuntimeException.class, () -> floatValueContainer.getValue(-1)); - - } - - /** - * Tests {@link FloatValueContainer#setCapacity(int)} - */ - @Test - @UnitTestMethod(name = "setCapacity", args = { int.class }) - public void testSetCapacity() { - FloatValueContainer floatValueContainer = new FloatValueContainer(0); - - int expectedCapacity = 5; - floatValueContainer.setCapacity(expectedCapacity); - assertTrue(floatValueContainer.getCapacity() >= expectedCapacity); - - expectedCapacity = 15; - floatValueContainer.setCapacity(expectedCapacity); - assertTrue(floatValueContainer.getCapacity() >= expectedCapacity); - - expectedCapacity = 50; - floatValueContainer.setCapacity(expectedCapacity); - assertTrue(floatValueContainer.getCapacity() >= expectedCapacity); - - expectedCapacity = 1000; - floatValueContainer.setCapacity(expectedCapacity); - assertTrue(floatValueContainer.getCapacity() >= expectedCapacity); - } - - /** - * Test {@link FloatValueContainer#setValue(int, float)} - */ - @Test - @UnitTestMethod(name = "setValue", args = { int.class, float.class }) - public void testSetValue() { - FloatValueContainer floatValueContainer = new FloatValueContainer(0); - - // long value - float value = 12123.234f; - floatValueContainer.setValue(0, value); - assertEquals(value, floatValueContainer.getValue(0), 0); - - // pre-condition tests - assertThrows(RuntimeException.class, () -> floatValueContainer.setValue(-1, 234.63f)); - - } - - /** - * Tests {@link FloatValueContainer#size()} - */ - @Test - @UnitTestMethod(name = "size", args = {}) - public void testSize() { - FloatValueContainer floatValueContainer = new FloatValueContainer(0, 100); - assertEquals(0, floatValueContainer.size()); - - floatValueContainer.setValue(3, 352.2345f); - assertEquals(4, floatValueContainer.size()); - - floatValueContainer.setValue(1, 7456.63f); - assertEquals(4, floatValueContainer.size()); - - floatValueContainer.setValue(15, 99.1576f); - assertEquals(16, floatValueContainer.size()); - - floatValueContainer.setValue(300, 247.989762f); - assertEquals(301, floatValueContainer.size()); - } - -} diff --git a/gcm3/src/test/java/plugins/util/properties/arraycontainers/AT_IntValueContainer.java b/gcm3/src/test/java/plugins/util/properties/arraycontainers/AT_IntValueContainer.java deleted file mode 100644 index c946353fb..000000000 --- a/gcm3/src/test/java/plugins/util/properties/arraycontainers/AT_IntValueContainer.java +++ /dev/null @@ -1,652 +0,0 @@ -package plugins.util.properties.arraycontainers; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; - -import plugins.util.properties.arraycontainers.IntValueContainer.IntValueType; -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; - -/** - * Test class for {@link IntValueContainer} - * - * @author Shawn Hatch - * - */ -@UnitTest(target = IntValueContainer.class) -public class AT_IntValueContainer { - - /** - * Test for {@link IntValueContainer#getValueAsByte(int)} - */ - @Test - @UnitTestMethod(name = "getValueAsByte", args = { int.class }) - public void testGetValueAsByte() { - long defaultValue = 123; - IntValueContainer intValueContainer = new IntValueContainer(defaultValue); - int highIndex = 1000; - - byte[] bytes = new byte[highIndex]; - for (int i = 0; i < bytes.length; i++) { - byte b = (byte) (i % 256 - 128); - bytes[i] = b; - } - for (int i = 0; i < bytes.length; i++) { - intValueContainer.setByteValue(i, bytes[i]); - } - - for (int i = 0; i < bytes.length; i++) { - assertEquals(intValueContainer.getValueAsByte(i), bytes[i]); - } - - // show that the default value is returned for indices that have not yet - // had value assignments - for (int i = 0; i < 5; i++) { - assertEquals(intValueContainer.getValueAsByte(i + highIndex), defaultValue); - } - - // pre-condition tests - - // if index < 0 - assertThrows(RuntimeException.class, () -> intValueContainer.getValueAsByte(-1)); - - // if the value to return is not compatible with byte - intValueContainer.setIntValue(highIndex, 240); - assertThrows(RuntimeException.class, () -> intValueContainer.getValueAsByte(highIndex)); - - } - - /** - * Test for {@link IntValueContainer#getValueAsInt(int)} - */ - @Test - @UnitTestMethod(name = "getValueAsInt", args = { int.class }) - public void testGetValueAsInt() { - long defaultValue = 9546754; - IntValueContainer intValueContainer = new IntValueContainer(defaultValue); - int highIndex = 1000; - - int[] ints = new int[highIndex]; - for (int i = 0; i < ints.length; i++) { - ints[i] = i; - } - for (int i = 0; i < ints.length; i++) { - intValueContainer.setIntValue(i, ints[i]); - } - - for (int i = 0; i < ints.length; i++) { - assertEquals(intValueContainer.getValueAsInt(i), ints[i]); - } - - // show that the default value is returned for indices that have not yet - // had value assignments - for (int i = 0; i < 5; i++) { - assertEquals(intValueContainer.getValueAsInt(i + highIndex), defaultValue); - } - - // pre-condition tests - - // if index < 0 - assertThrows(RuntimeException.class, () -> intValueContainer.getValueAsInt(-1)); - - // if the value to return is not compatible with int - intValueContainer.setLongValue(highIndex, 535445345543L); - assertThrows(RuntimeException.class, () -> intValueContainer.getValueAsInt(highIndex)); - } - - /** - * Test for {@link IntValueContainer#getValueAsLong(int)} - */ - @Test - @UnitTestMethod(name = "getValueAsLong", args = { int.class }) - public void testGetValueAsLong() { - long defaultValue = 9546754; - IntValueContainer intValueContainer = new IntValueContainer(defaultValue); - int highIndex = 1000; - - long[] longs = new long[highIndex]; - for (int i = 0; i < longs.length; i++) { - longs[i] = 745644534457456L + i; - } - for (int i = 0; i < longs.length; i++) { - intValueContainer.setLongValue(i, longs[i]); - } - - for (int i = 0; i < longs.length; i++) { - assertEquals(intValueContainer.getValueAsLong(i), longs[i]); - } - - // show that the default value is returned for indices that have not yet - // had value assignments - for (int i = 0; i < 5; i++) { - assertEquals(intValueContainer.getValueAsLong(i + highIndex), defaultValue); - } - - // pre-condition tests - - // if index < 0 - assertThrows(RuntimeException.class, () -> intValueContainer.getValueAsLong(-1)); - - // if the value to return is not compatible with long -- can't fail - // since all values are compatible with long. - - } - - /** - * Test for {@link IntValueContainer#getValueAsShort(int)} - */ - @Test - @UnitTestMethod(name = "getValueAsShort", args = { int.class }) - public void testGetValueAsShort() { - short defaultValue = 30467; - IntValueContainer intValueContainer = new IntValueContainer(defaultValue); - int highIndex = 1000; - - short[] shorts = new short[highIndex]; - for (int i = 0; i < shorts.length; i++) { - short s = (short) (i % (256 * 256) - 128 * 256); - shorts[i] = s; - } - for (int i = 0; i < shorts.length; i++) { - intValueContainer.setShortValue(i, shorts[i]); - } - - for (int i = 0; i < shorts.length; i++) { - assertEquals(intValueContainer.getValueAsShort(i), shorts[i]); - } - - // show that the default value is returned for indices that have not yet - // had value assignments - for (int i = 0; i < 5; i++) { - assertEquals(intValueContainer.getValueAsShort(i + highIndex), defaultValue); - } - - // pre-condition tests - - // if index < 0 - assertThrows(RuntimeException.class, () -> intValueContainer.getValueAsShort(-1)); - - // if the value to return is not compatible with short - intValueContainer.setIntValue(highIndex, 40000); - assertThrows(RuntimeException.class, () -> intValueContainer.getValueAsShort(highIndex)); - - } - - /** - * Test for {@link IntValueContainer#setCapacity(int)} - */ - @Test - @UnitTestMethod(name = "setCapacity", args = { int.class }) - public void testSetCapacity() { - IntValueContainer intValueContainer = new IntValueContainer(0); - - int expectedCapacity = 5; - intValueContainer.setCapacity(expectedCapacity); - assertTrue(intValueContainer.getCapacity() >= expectedCapacity); - - expectedCapacity = 15; - intValueContainer.setCapacity(expectedCapacity); - assertTrue(intValueContainer.getCapacity() >= expectedCapacity); - - expectedCapacity = 50; - intValueContainer.setCapacity(expectedCapacity); - assertTrue(intValueContainer.getCapacity() >= expectedCapacity); - - expectedCapacity = 1000; - intValueContainer.setCapacity(expectedCapacity); - assertTrue(intValueContainer.getCapacity() >= expectedCapacity); - - } - - /** - * Test for {@link IntValueContainer#getCapacity()} - */ - @Test - @UnitTestMethod(name = "getCapacity", args = {}) - public void testGetCapacity() { - - IntValueContainer intValueContainer = new IntValueContainer(0); - - assertTrue(intValueContainer.getCapacity() >= intValueContainer.size()); - - intValueContainer.setIntValue(1, 1234); - assertTrue(intValueContainer.getCapacity() >= intValueContainer.size()); - - intValueContainer.setIntValue(34, 364); - assertTrue(intValueContainer.getCapacity() >= intValueContainer.size()); - - intValueContainer.setIntValue(10, 154); - assertTrue(intValueContainer.getCapacity() >= intValueContainer.size()); - - intValueContainer.setIntValue(137, 2526); - assertTrue(intValueContainer.getCapacity() >= intValueContainer.size()); - - intValueContainer.setLongValue(1000, 1234534234234234234L); - assertTrue(intValueContainer.getCapacity() >= intValueContainer.size()); - - } - - /** - * Test for {@link IntValueContainer#setLongValue(int, long)} - */ - @Test - @UnitTestMethod(name = "setLongValue", args = { int.class, long.class }) - public void testSetLongValue() { - IntValueContainer intValueContainer = new IntValueContainer(0); - - // long value - long l = 523423463534562345L; - intValueContainer.setLongValue(0, l); - assertEquals(l, intValueContainer.getValueAsLong(0)); - - // pre-condition tests - long l2 = 1; - assertThrows(RuntimeException.class, () -> intValueContainer.setLongValue(-1, l2)); - - } - - /** - * Test for {@link IntValueContainer#setIntValue(int, int)} - */ - @Test - @UnitTestMethod(name = "setIntValue", args = { int.class, int.class }) - public void testSetIntValue() { - IntValueContainer intValueContainer = new IntValueContainer(0); - - // int value - int i = 70000; - intValueContainer.setIntValue(0, i); - assertEquals(i, intValueContainer.getValueAsInt(0)); - - // pre-condition tests - int i2 = 1; - assertThrows(RuntimeException.class, () -> intValueContainer.setIntValue(-1, i2)); - } - - /** - * Test for {@link IntValueContainer#setShortValue(int, short)} - */ - - @Test - @UnitTestMethod(name = "setShortValue", args = { int.class, short.class }) - public void testSetShortValue() { - IntValueContainer intValueContainer = new IntValueContainer(0); - - // short value - short s = 300; - intValueContainer.setShortValue(0, s); - assertEquals(s, intValueContainer.getValueAsShort(0)); - - // pre-condition tests - short s2 = 1; - assertThrows(RuntimeException.class, () -> intValueContainer.setShortValue(-1, s2)); - - } - - /** - * Test for {@link IntValueContainer#setByteValue(int, byte)} - */ - - @Test - @UnitTestMethod(name = "setByteValue", args = { int.class, byte.class }) - public void testSetByteValue() { - IntValueContainer intValueContainer = new IntValueContainer(0); - - // byte value - byte b = 5; - intValueContainer.setByteValue(0, b); - assertEquals(b, intValueContainer.getValueAsByte(0)); - - // pre-condition tests - byte b2 = 1; - assertThrows(RuntimeException.class, () -> intValueContainer.setByteValue(-1, b2)); - - } - - /** - * Test for {@link IntValueContainer#incrementIntValue(int, int)} - */ - - @Test - @UnitTestMethod(name = "incrementIntValue", args = { int.class, int.class }) - public void testIncrementIntValue() { - IntValueContainer intValueContainer = new IntValueContainer(0); - - // int value - int i1 = 70000; - intValueContainer.setIntValue(0, i1); - int i2 = 2000; - intValueContainer.incrementIntValue(0, i2); - assertEquals(i1 + i2, intValueContainer.getValueAsInt(0)); - - // pre-condition tests - int i3 = 1; - assertThrows(RuntimeException.class, () -> intValueContainer.incrementIntValue(-1, i3)); - intValueContainer.setLongValue(0, Long.MAX_VALUE); - - assertThrows(ArithmeticException.class, () -> intValueContainer.incrementIntValue(0, i3)); - } - - /** - * Test for {@link IntValueContainer#incrementLongValue(int, long)} - */ - @Test - @UnitTestMethod(name = "incrementLongValue", args = { int.class, long.class }) - public void testIncrementLongValue() { - IntValueContainer intValueContainer = new IntValueContainer(0); - - // long value - long l1 = 523423463534562345L; - intValueContainer.setLongValue(0, l1); - long l2 = 66457456456456456L; - intValueContainer.incrementLongValue(0, l2); - assertEquals(l1 + l2, intValueContainer.getValueAsLong(0)); - - // pre-condition tests - long l3 = 1; - assertThrows(RuntimeException.class, () -> intValueContainer.incrementLongValue(-1, l3)); - intValueContainer.setLongValue(0, Long.MAX_VALUE); - - assertThrows(ArithmeticException.class, () -> intValueContainer.incrementLongValue(0, l3)); - } - - /** - * Test for {@link IntValueContainer#incrementShortValue(int, short)} - */ - @Test - @UnitTestMethod(name = "incrementShortValue", args = { int.class, short.class }) - public void testIncrementShortValue() { - IntValueContainer intValueContainer = new IntValueContainer(0); - - // short value - short s1 = 300; - intValueContainer.setShortValue(0, s1); - short s2 = 100; - intValueContainer.incrementShortValue(0, s2); - assertEquals(s1 + s2, intValueContainer.getValueAsShort(0)); - - // pre-condition tests - short s3 = 1; - assertThrows(RuntimeException.class, () -> intValueContainer.incrementShortValue(-1, s3)); - intValueContainer.setLongValue(0, Long.MAX_VALUE); - - assertThrows(ArithmeticException.class, () -> intValueContainer.incrementShortValue(0, s3)); - } - - /** - * Test for {@link IntValueContainer#incrementByteValue(int, byte)} - */ - @Test - @UnitTestMethod(name = "incrementByteValue", args = { int.class, byte.class }) - public void testIncrementByteValue() { - IntValueContainer intValueContainer = new IntValueContainer(0); - - // byte value - byte b1 = 5; - intValueContainer.setByteValue(0, b1); - byte b2 = 12; - intValueContainer.incrementByteValue(0, b2); - assertEquals(b1 + b2, intValueContainer.getValueAsByte(0)); - - // pre-condition tests - byte b3 = 1; - assertThrows(RuntimeException.class, () -> intValueContainer.incrementByteValue(-1, b3)); - intValueContainer.setLongValue(0, Long.MAX_VALUE); - - assertThrows(ArithmeticException.class, () -> intValueContainer.incrementByteValue(0, b3)); - } - - /** - * Test for {@link IntValueContainer#decrementByteValue(int, byte)} - */ - @Test - @UnitTestMethod(name = "decrementByteValue", args = { int.class, byte.class }) - public void testDecrementByteValue() { - IntValueContainer intValueContainer = new IntValueContainer(0); - - // byte value - byte b1 = 5; - intValueContainer.setByteValue(0, b1); - - byte b2 = 12; - intValueContainer.decrementByteValue(0, b2); - assertEquals(b1 - b2, intValueContainer.getValueAsByte(0)); - - // pre-condition tests - byte b3 = 1; - assertThrows(RuntimeException.class, () -> intValueContainer.decrementByteValue(-1, b3)); - intValueContainer.setLongValue(0, Long.MIN_VALUE); - - assertThrows(ArithmeticException.class, () -> intValueContainer.decrementByteValue(0, b3)); - } - - /** - * Test for {@link IntValueContainer#decrementShortValue(int, short)} - */ - @Test - @UnitTestMethod(name = "decrementShortValue", args = { int.class, short.class }) - public void testDecrementShortValue() { - IntValueContainer intValueContainer = new IntValueContainer(0); - - // short value - short s1 = 300; - intValueContainer.setShortValue(0, s1); - short s2 = 100; - intValueContainer.decrementShortValue(0, s2); - assertEquals(s1 - s2, intValueContainer.getValueAsShort(0)); - - // pre-condition tests - short s3 = 1; - assertThrows(RuntimeException.class, () -> intValueContainer.decrementShortValue(-1, s3)); - intValueContainer.setLongValue(0, Long.MIN_VALUE); - - assertThrows(ArithmeticException.class, () -> intValueContainer.decrementShortValue(0, s3)); - } - - /** - * Test for {@link IntValueContainer#decrementIntValue(int, int)} - */ - @Test - @UnitTestMethod(name = "decrementIntValue", args = { int.class, int.class }) - public void testDecrementIntValue() { - IntValueContainer intValueContainer = new IntValueContainer(0); - - // int value - int i1 = 70000; - intValueContainer.setIntValue(0, i1); - int i2 = 2000; - intValueContainer.decrementIntValue(0, i2); - assertEquals(i1 - i2, intValueContainer.getValueAsInt(0)); - - // pre-condition tests - int i3 = 1; - assertThrows(RuntimeException.class, () -> intValueContainer.decrementIntValue(-1, i3)); - intValueContainer.setLongValue(0, Long.MIN_VALUE); - - assertThrows(ArithmeticException.class, () -> intValueContainer.decrementIntValue(0, i3)); - } - - /** - * Test for {@link IntValueContainer#decrementLongValue(int, long)} - */ - @Test - @UnitTestMethod(name = "decrementLongValue", args = { int.class, long.class }) - public void testDecrementLongValue() { - IntValueContainer intValueContainer = new IntValueContainer(0); - - // long value - long l1 = 523423463534562345L; - intValueContainer.setLongValue(0, l1); - long l2 = 66457456456456456L; - intValueContainer.decrementLongValue(0, l2); - assertEquals(l1 - l2, intValueContainer.getValueAsLong(0)); - - // pre-condition tests - long l3 = 1; - assertThrows(RuntimeException.class, () -> intValueContainer.decrementLongValue(-1, l3)); - intValueContainer.setLongValue(0, Long.MIN_VALUE); - - assertThrows(ArithmeticException.class, () -> intValueContainer.decrementLongValue(0, l3)); - } - - /** - * Test for {@link IntValueContainer#IntValueContainer(long)} - */ - @Test - @UnitTestConstructor(args = { long.class }) - public void testConstructor_Long() { - IntValueContainer intValueContainer = new IntValueContainer(12); - assertNotNull(intValueContainer); - } - - /** - * Test for {@link IntValueContainer#IntValueContainer(long, int)} - */ - @Test - @UnitTestConstructor(args = { long.class, int.class }) - public void testConstructor_LongInt() { - IntValueContainer intValueContainer = new IntValueContainer(12, 0); - assertNotNull(intValueContainer); - - intValueContainer = new IntValueContainer(12, 30); - assertNotNull(intValueContainer); - - assertThrows(NegativeArraySizeException.class, () -> new IntValueContainer(12, -1)); - - } - - /** - * Test for {@link IntValueContainer#size()} - */ - @Test - @UnitTestMethod(name = "size", args = {}) - public void testSize() { - - IntValueContainer intValueContainer = new IntValueContainer(0, 100); - assertEquals(0, intValueContainer.size()); - - intValueContainer.setIntValue(3, 352); - assertEquals(4, intValueContainer.size()); - - intValueContainer.setIntValue(1, 7456); - assertEquals(4, intValueContainer.size()); - - intValueContainer.setIntValue(15, 99); - assertEquals(16, intValueContainer.size()); - - intValueContainer.setIntValue(300, 247); - assertEquals(301, intValueContainer.size()); - } - - /** - * Test for {@link IntValueContainer#getDefaultValueAsByte()} - */ - @Test - @UnitTestMethod(name = "getDefaultValueAsByte", args = {}) - public void testGetDefaultValueAsByte() { - byte expected = 120; - IntValueContainer intValueContainer = new IntValueContainer(expected); - byte actual = intValueContainer.getDefaultValueAsByte(); - assertEquals(expected, actual); - - // pre-condition tests - - // default short - assertThrows(RuntimeException.class, () -> new IntValueContainer(30000).getDefaultValueAsByte()); - - // default int - assertThrows(RuntimeException.class, () -> new IntValueContainer(120000).getDefaultValueAsByte()); - - // default long - assertThrows(RuntimeException.class, () -> new IntValueContainer(123124235123234234L).getDefaultValueAsByte()); - - } - - /** - * Test for {@link IntValueContainer#getDefaultValueAsShort()} - */ - @Test - @UnitTestMethod(name = "getDefaultValueAsShort", args = {}) - public void testGetDefaultValueAsShort() { - short expected = 32000; - IntValueContainer intValueContainer = new IntValueContainer(expected); - short actual = intValueContainer.getDefaultValueAsShort(); - assertEquals(expected, actual); - - // pre-condition tests - - // default int - assertThrows(RuntimeException.class, () -> new IntValueContainer(120000).getDefaultValueAsShort()); - - // default long - assertThrows(RuntimeException.class, () -> new IntValueContainer(123124235123234234L).getDefaultValueAsShort()); - } - - /** - * Test for {@link IntValueContainer#getDefaultValueAsInt()} - */ - @Test - @UnitTestMethod(name = "getDefaultValueAsInt", args = {}) - public void testGetDefaultValueAsInt() { - int expected = 52000; - IntValueContainer intValueContainer = new IntValueContainer(expected); - int actual = intValueContainer.getDefaultValueAsInt(); - assertEquals(expected, actual); - - // pre-condition tests - - // default long - assertThrows(RuntimeException.class, () -> new IntValueContainer(123124235123234234L).getDefaultValueAsInt()); - } - - /** - * Test for {@link IntValueContainer#getDefaultValueAsLong()} - */ - @Test - @UnitTestMethod(name = "getDefaultValueAsLong", args = {}) - public void testGetDefaultValueAsLong() { - long expected = 364534534534534345L; - IntValueContainer intValueContainer = new IntValueContainer(expected); - long actual = intValueContainer.getDefaultValueAsLong(); - assertEquals(expected, actual); - - // pre-condition tests -- none - } - - /** - * Test for {@link IntValueContainer#getIntValueType()} - */ - @Test - @UnitTestMethod(name = "getIntValueType", args = {}) - public void testGetIntValueType() { - IntValueContainer intValueContainer = new IntValueContainer(0); - assertEquals(IntValueType.BYTE, intValueContainer.getIntValueType()); - - intValueContainer.setIntValue(0, 1); - assertEquals(1, intValueContainer.size()); - assertEquals(IntValueType.BYTE, intValueContainer.getIntValueType()); - - intValueContainer.setIntValue(1, 130); - assertEquals(IntValueType.SHORT, intValueContainer.getIntValueType()); - assertEquals(2, intValueContainer.size()); - - intValueContainer.setIntValue(2, 70000); - assertEquals(IntValueType.INT, intValueContainer.getIntValueType()); - assertEquals(3, intValueContainer.size()); - - intValueContainer.setLongValue(3, 123123123123123123L); - assertEquals(IntValueType.LONG, intValueContainer.getIntValueType()); - assertEquals(4, intValueContainer.size()); - - intValueContainer.setIntValue(4, 1); - assertEquals(IntValueType.LONG, intValueContainer.getIntValueType()); - assertEquals(5, intValueContainer.size()); - - } - -} diff --git a/gcm3/src/test/java/plugins/util/properties/arraycontainers/AT_ObjectValueContainer.java b/gcm3/src/test/java/plugins/util/properties/arraycontainers/AT_ObjectValueContainer.java deleted file mode 100644 index ccc4fd5c9..000000000 --- a/gcm3/src/test/java/plugins/util/properties/arraycontainers/AT_ObjectValueContainer.java +++ /dev/null @@ -1,102 +0,0 @@ -package plugins.util.properties.arraycontainers; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; - -/** - * Test class for {@link ObjectValueContainer} - * - * @author Shawn Hatch - * - */ -@UnitTest(target = ObjectValueContainer.class) -public class AT_ObjectValueContainer { - - /** - * Tests {@link ObjectValueContainer#ObjectValueContainer(Object, int)} - */ - @Test - @UnitTestConstructor(args = { Object.class, int.class }) - public void testConstructor() { - String defaultValue = "default"; - ObjectValueContainer objectValueContainer = new ObjectValueContainer(defaultValue, 20); - assertNotNull(objectValueContainer); - - objectValueContainer = new ObjectValueContainer(null, 20); - assertNotNull(objectValueContainer); - - // pre-condition tests - assertThrows(IllegalArgumentException.class, () -> new ObjectValueContainer(null, -4)); - - } - - /** - * Tests {@link ObjectValueContainer#setValue(int, Object)} - */ - @Test - @UnitTestMethod(name = "setValue", args = { int.class, Object.class }) - public void testSetValue() { - String defaultValue = "default"; - ObjectValueContainer objectValueContainer = new ObjectValueContainer(defaultValue, 20); - objectValueContainer.setValue(3, "dog"); - objectValueContainer.setValue(1, "cat"); - objectValueContainer.setValue(4, "pig"); - objectValueContainer.setValue(7, "cow"); - objectValueContainer.setValue(3, "bat"); - objectValueContainer.setValue(5, null); - - assertEquals(defaultValue, objectValueContainer.getValue(0)); - assertEquals("cat", objectValueContainer.getValue(1)); - assertEquals(defaultValue, objectValueContainer.getValue(2)); - assertEquals("bat", objectValueContainer.getValue(3)); - assertEquals("pig", objectValueContainer.getValue(4)); - assertNull(objectValueContainer.getValue(5)); - assertEquals(defaultValue, objectValueContainer.getValue(6)); - assertEquals("cow", objectValueContainer.getValue(7)); - assertEquals(defaultValue, objectValueContainer.getValue(8)); - assertEquals(defaultValue, objectValueContainer.getValue(9)); - - // test pre-conditions - assertThrows(IllegalArgumentException.class, () -> objectValueContainer.setValue(-1, "frog")); - } - - /** - * Tests {@link ObjectValueContainer#getValue(int)} - */ - @Test - @UnitTestMethod(name = "getValue", args = { int.class }) - public void testGetValue() { - - String defaultValue = "default"; - ObjectValueContainer objectValueContainer = new ObjectValueContainer(defaultValue, 20); - objectValueContainer.setValue(3, "dog"); - objectValueContainer.setValue(1, "cat"); - objectValueContainer.setValue(4, "pig"); - objectValueContainer.setValue(7, "cow"); - objectValueContainer.setValue(3, "bat"); - objectValueContainer.setValue(5, null); - - assertEquals(defaultValue, objectValueContainer.getValue(0)); - assertEquals("cat", objectValueContainer.getValue(1)); - assertEquals(defaultValue, objectValueContainer.getValue(2)); - assertEquals("bat", objectValueContainer.getValue(3)); - assertEquals("pig", objectValueContainer.getValue(4)); - assertNull(objectValueContainer.getValue(5)); - assertEquals(defaultValue, objectValueContainer.getValue(6)); - assertEquals("cow", objectValueContainer.getValue(7)); - assertEquals(defaultValue, objectValueContainer.getValue(8)); - assertEquals(defaultValue, objectValueContainer.getValue(9)); - - // test pre-conditions - assertThrows(IllegalArgumentException.class, () -> objectValueContainer.getValue(-1)); - } - -} diff --git a/gcm3/src/test/java/tools/SeedGenerator.java b/gcm3/src/test/java/tools/SeedGenerator.java deleted file mode 100644 index da8b6f96d..000000000 --- a/gcm3/src/test/java/tools/SeedGenerator.java +++ /dev/null @@ -1,24 +0,0 @@ -package tools; - -import java.util.Random; - -import org.apache.commons.math3.util.FastMath; -/** - * Produces 30 non-negative random longs. Negatives present copy/paste issues. - * @author Shawn Hatch - * - */ -public final class SeedGenerator { - - private SeedGenerator() { - - } - - public static void main(String[] args) { - Random random = new Random(); - for (int i = 0; i < 1; i++) { - System.out.println(FastMath.abs(random.nextLong())+"L"); - } - } - -} diff --git a/gcm3/src/test/java/tools/annotations/UnitTest.java b/gcm3/src/test/java/tools/annotations/UnitTest.java deleted file mode 100644 index e39427136..000000000 --- a/gcm3/src/test/java/tools/annotations/UnitTest.java +++ /dev/null @@ -1,24 +0,0 @@ -package tools.annotations; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Annotation for JUnit test classes that are tested via a proxy test. Any class - * marked with this annotation should not be part of the public GCM API and is - * considered tested by the test of the class that is the proxy. - * - * @author Shawn Hatch - * - */ - -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target({ ElementType.TYPE }) -public @interface UnitTest { - Class target(); - Class[] proxy() default Object.class; -} \ No newline at end of file diff --git a/gcm3/src/test/java/tools/annotations/UnitTestConstructor.java b/gcm3/src/test/java/tools/annotations/UnitTestConstructor.java deleted file mode 100644 index c2d95b081..000000000 --- a/gcm3/src/test/java/tools/annotations/UnitTestConstructor.java +++ /dev/null @@ -1,25 +0,0 @@ -package tools.annotations; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Annotation for JUnit test classes that are tested via a proxy test. Any class - * marked with this annotation should not be part of the public GCM API and is - * considered tested by the test of the class that is the proxy. - * - * @author Shawn Hatch - * - */ - -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target({ ElementType.METHOD }) -public @interface UnitTestConstructor { - Class target() default Object.class; - - Class[] args(); -} \ No newline at end of file diff --git a/gcm3/src/test/java/tools/annotations/UnitTestMethod.java b/gcm3/src/test/java/tools/annotations/UnitTestMethod.java deleted file mode 100644 index d2284c5f9..000000000 --- a/gcm3/src/test/java/tools/annotations/UnitTestMethod.java +++ /dev/null @@ -1,30 +0,0 @@ -package tools.annotations; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Annotation for JUnit test classes that are tested via a proxy test. Any class - * marked with this annotation should not be part of the public GCM API and is - * considered tested by the test of the class that is the proxy. - * - * @author Shawn Hatch - * - */ - -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target({ ElementType.METHOD }) -public @interface UnitTestMethod { - String name(); - - Class target() default Object.class; - - Class[] args(); - - boolean manual() default false; - -} \ No newline at end of file diff --git a/gcm3/src/test/java/tools/codestats/CodeCountReport.java b/gcm3/src/test/java/tools/codestats/CodeCountReport.java deleted file mode 100644 index 336387cb9..000000000 --- a/gcm3/src/test/java/tools/codestats/CodeCountReport.java +++ /dev/null @@ -1,322 +0,0 @@ -package tools.codestats; - -import java.io.IOException; -import java.nio.file.FileVisitResult; -import java.nio.file.FileVisitor; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; - -import util.tree.MutableTree; -import util.tree.Tree; -import util.tree.impl.SortedTree; - -public class CodeCountReport { - - private CodeCountReport() {} - - private int totalCodeLineCount; - - private int totalCommentLineCount; - - private int totalBlankLineCount; - - private String detailsReport; - - public int getTotalCodeLineCount() { - return totalCodeLineCount; - } - - public int getTotalCommentLineCount() { - return totalCommentLineCount; - } - - public int getTotalBlankLineCount() { - return totalBlankLineCount; - } - - public String getDetailsReport() { - return detailsReport; - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - - private Builder() { - } - - private void cascadeLineCounts(Tree tree, Node node) { - for (Node childNode : tree.getChildNodes(node)) { - cascadeLineCounts(tree, childNode); - } - for (Node childNode : tree.getChildNodes(node)) { - for (LineType lineType : LineType.values()) { - node.increment(lineType, childNode.getLineCount(lineType)); - } - } - - } - - public CodeCountReport build() { - try { - - final CounterFileVisitor counterFileVisitor = new CounterFileVisitor(); - for (Path directory : directories) { - try { - Files.walkFileTree(directory, counterFileVisitor); - } catch (final IOException e) { - throw new RuntimeException(e); - } - } - - MutableTree tree = counterFileVisitor.tree; - - Node rootNode = new Node(Paths.get("All")); - - List currentRootNodes = new ArrayList<>(); - for (Node node : tree.getRootNodes()) { - currentRootNodes.add(node); - - } - for (Node node : currentRootNodes) { - tree.addLink(rootNode, node); - } - cascadeLineCounts(tree, rootNode); - - CodeCountReport codeCountReport = new CodeCountReport(); - codeCountReport.totalBlankLineCount = rootNode.getLineCount(LineType.BLANK); - codeCountReport.totalCodeLineCount = rootNode.getLineCount(LineType.CODE); - codeCountReport.totalCommentLineCount = rootNode.getLineCount(LineType.COMMENT); - int totalLineCount = codeCountReport.totalBlankLineCount + codeCountReport.totalCodeLineCount + codeCountReport.totalCommentLineCount; - - StringBuilder sb = new StringBuilder(); - for (LineType lineType : LineType.values()) { - sb.append(lineType); - sb.append("\t"); - } - sb.append(totalLineCount); - sb.append("\n"); - - for (Node node : counterFileVisitor.tree.getAllNodes()) { - - for (LineType lineType : LineType.values()) { - sb.append(node.getLineCount(lineType)); - sb.append("\t"); - } - sb.append("\t"); - // sb.append(node.getPath()); - - Iterator iterator = node.getPath().iterator(); - while (iterator.hasNext()) { - sb.append("\t"); - sb.append(iterator.next()); - } - sb.append("\n"); - - } - - codeCountReport.detailsReport = sb.toString(); - return codeCountReport; - - } finally { - directories = new LinkedHashSet<>(); - } - } - - private Set directories = new LinkedHashSet<>(); - - public Builder addDirectory(String directoryName) { - Path dir = Paths.get(directoryName); - if (Files.isDirectory(dir)) { - directories.add(dir); - } - return this; - } - } - - private static class CounterFileVisitor implements FileVisitor { - - private MutableTree tree = new SortedTree<>(); - private Map map = new LinkedHashMap<>(); - - @Override - public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { - Objects.requireNonNull(dir); - Objects.requireNonNull(attrs); - - Node node = new Node(dir); - tree.addNode(node); - map.put(dir, node); - Node parentCounter = map.get(dir.getParent()); - if (parentCounter != null) { - tree.addLink(parentCounter, node); - } - - return FileVisitResult.CONTINUE; - } - - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attr) { - Node node = new Node(file); - map.put(file, node); - Node parentCounter = map.get(file.getParent()); - - tree.addLink(parentCounter, node); - return FileVisitResult.CONTINUE; - } - - @Override - public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { - Objects.requireNonNull(file); - if (exc != null) { - throw exc; - } - return FileVisitResult.CONTINUE; - } - - @Override - public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { - Objects.requireNonNull(dir); - if (exc != null) { - throw exc; - } - return FileVisitResult.CONTINUE; - } - } - - private static class Node implements Comparable { - private Map counterMap = new LinkedHashMap<>(); - - private final Path path; - - public Node(Path path) { - for (LineType lineType : LineType.values()) { - counterMap.put(lineType, new Counter()); - } - this.path = path; - if (Files.isRegularFile(path)) { - exploreFile(); - } - } - - private void exploreFile() { - /* - * 1234 - */ - - // String importString = "import"; - - if (path.toString().endsWith(".java")) { - List lines; - try { - lines = Files.readAllLines(path); - } catch (IOException e) { - lines = new ArrayList<>(); - } - boolean blockCommentActive = false; - for (String line : lines) { - - boolean commentFound = false; - int n = line.length(); - StringBuilder sb = new StringBuilder(); - - int i = 0; - while (i < n) { - char c = line.charAt(i); - if (blockCommentActive) { - commentFound = true; - if (c == '/') { - if (i > 0) { - char b = line.charAt(i - 1); - if (b == '*') { - blockCommentActive = false; - } - } - } - } else { - if (c == '/') { - if (i < n - 1) { - char d = line.charAt(i + 1); - if (d == '/') { - commentFound = true; - i = n; - break; - } else { - if (d == '*') { - commentFound = true; - blockCommentActive = true; - i++; - } - } - } - } else { - - sb.append(c); - } - } - i++; - } - - line = sb.toString().trim(); - - if (line.length() > 0) { - increment(LineType.CODE, 1); - } else { - if (commentFound) { - increment(LineType.COMMENT, 1); - } else { - increment(LineType.BLANK, 1); - } - } - } - } else if (path.toString().endsWith(".js")) { - List lines; - try { - lines = Files.readAllLines(path); - } catch (IOException e) { - lines = new ArrayList<>(); - } - increment(LineType.CODE, lines.size()); - } - - } - - public void increment(LineType lineType, int count) { - counterMap.get(lineType).count += count; - } - - public int getLineCount(LineType lineType) { - return counterMap.get(lineType).count; - } - - public Path getPath() { - return path; - } - - @Override - public int compareTo(Node node) { - return path.compareTo(node.path); - } - } - - private static enum LineType { - CODE, COMMENT, BLANK; - - } - - private static class Counter { - private int count; - } -} diff --git a/gcm3/src/test/java/tools/meta/DependencyGraph.java b/gcm3/src/test/java/tools/meta/DependencyGraph.java deleted file mode 100644 index db7eaf54e..000000000 --- a/gcm3/src/test/java/tools/meta/DependencyGraph.java +++ /dev/null @@ -1,49 +0,0 @@ -package tools.meta; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; -import java.util.Optional; - -import util.graph.Graph; -import util.graph.GraphDepthEvaluator; - -public class DependencyGraph { - private final Path file; - - private DependencyGraph(Path file) { - this.file = file; - } - - private void execute() throws IOException { - List lines = Files.readAllLines(file); - - Graph.Builder builder = Graph.builder(); - - for (String line : lines) { - String[] splits = line.split("\t"); - builder.addEdge(new Object(), splits[0].trim(), splits[1].trim()); - } - Graph graph = builder.build(); - - Optional> optional = GraphDepthEvaluator.getGraphDepthEvaluator(graph); - if (optional.isPresent()) { - GraphDepthEvaluator graphDepthEvaluator = optional.get(); - int maxDepth = graphDepthEvaluator.getMaxDepth(); - for (int depth = 0; depth <= maxDepth; depth++) { - List nodesForDepth = graphDepthEvaluator.getNodesForDepth(depth); - System.out.println(depth + ": " + nodesForDepth); - - } - } else { - System.out.println("cannot perform depth evaluation"); - } - - } - - public static void main(String[] args) throws IOException { - new DependencyGraph(Paths.get("c:\\temp\\dependencies.txt")).execute(); - } -} diff --git a/gcm3/src/test/java/tools/meta/DependencyTracer.java b/gcm3/src/test/java/tools/meta/DependencyTracer.java deleted file mode 100644 index 59eb664c0..000000000 --- a/gcm3/src/test/java/tools/meta/DependencyTracer.java +++ /dev/null @@ -1,125 +0,0 @@ -package tools.meta; - -import java.io.IOException; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -public class DependencyTracer { - - private static boolean isJavaFile(Path file) { - return Files.isRegularFile(file) && file.toString().endsWith(".java"); - } - - private final class SourceFileVisitor extends SimpleFileVisitor { - private Set imports = new LinkedHashSet<>(); - - @Override - public FileVisitResult visitFile(final Path file, final BasicFileAttributes attr) { - if (isJavaFile(file)) { - try { - List lines = Files.readAllLines(file); - for (String line : lines) { - if (line.startsWith(importSuffix)) { - if (compact) { - int n = importSuffix.length() + 1; - while (n < line.length()) { - if (line.charAt(n) == '.') { - break; - } - n++; - } - - String dirString = dir.toString(); - dirString = dirString.substring(47, dirString.length()); - String lineString = line.substring(15, n); - if (!dirString.equals(lineString)) { - imports.add(dirString + "\t" + lineString); - } - } else { - imports.add(line); - } - } - } - } catch (IOException e) { - e.printStackTrace(); - } - } - return FileVisitResult.CONTINUE; - } - } - - private final Path dir; - private final String importSuffix; - private final boolean compact; - - private DependencyTracer(Path dir, String importSuffix, boolean compact) { - this.dir = dir; - this.importSuffix = importSuffix; - this.compact = compact; - } - - private void execute() throws IOException { - final SourceFileVisitor sourceFileVisitor = new SourceFileVisitor(); - Files.walkFileTree(dir, sourceFileVisitor); - List imports = new ArrayList<>(sourceFileVisitor.imports); - Collections.sort(imports); - for (String importLine : imports) { - System.out.println(importLine); - } - } - - private static void big() throws IOException { - Path dir = Paths.get("C:\\git_repos\\ASPR-6\\gcm2\\src\\main\\java\\plugins"); - - List pluginNames = new ArrayList<>(); - - pluginNames.add("components"); - - pluginNames.add("groups"); - pluginNames.add("globals"); - pluginNames.add("materials"); - pluginNames.add("partitions"); - pluginNames.add("people"); - pluginNames.add("personproperties"); - pluginNames.add("regions"); - pluginNames.add("reports"); - pluginNames.add("resources"); - pluginNames.add("stochastics"); - - for (String pluginName : pluginNames) { - Path pluginDir = dir.resolve(pluginName); - - String importSuffix = "import plugins."; - boolean compact = true; - new DependencyTracer(pluginDir, importSuffix, compact).execute(); - } - - } - - private static void small(String pluginName) throws IOException { - Path dir = Paths.get("C:\\git_repos\\ASPR-6\\gcm2\\src\\main\\java\\plugins"); - Path pluginDir = dir.resolve(pluginName); - String importSuffix = "import plugins."; - boolean compact = false; - new DependencyTracer(pluginDir, importSuffix, compact).execute(); - } - - public static void main(String[] args) throws IOException { - boolean useBig = true; - if (useBig) { - big(); - } else { - small("compartments"); - } - } - -} diff --git a/gcm3/src/test/java/tools/meta/PluginDependencyTest.java b/gcm3/src/test/java/tools/meta/PluginDependencyTest.java deleted file mode 100644 index 5bd3bf5be..000000000 --- a/gcm3/src/test/java/tools/meta/PluginDependencyTest.java +++ /dev/null @@ -1,148 +0,0 @@ -package tools.meta; - - - -public class PluginDependencyTest { -// private PluginDependencyTest() { -// -// } -// -// private static class Stub implements Comparable { -// private final int loadedOrder; -// private int dependencyOrder; -// private final Plugin plugin; -// -// public Stub(Plugin plugin, int loadedOrder) { -// this.loadedOrder = loadedOrder; -// this.plugin = plugin; -// } -// -// @Override -// public int compareTo(Stub stub) { -// int result = Integer.compare(dependencyOrder, stub.dependencyOrder); -// if (result == 0) { -// result = Integer.compare(loadedOrder, stub.loadedOrder); -// } -// return result; -// } -// -// } -// -// private void execute() { -// -// List plugins = new ArrayList<>(); -// plugins.add(new StochasticsPlugin(StochasticsInitialData.builder().build(), 234234L)); -// plugins.add(new ReportPlugin(ReportsInitialData.builder().build())); -// plugins.add(new GlobalPlugin(GlobalInitialData.builder().build())); -// plugins.add(new PeoplePlugin(PeopleInitialData.builder().build())); -// plugins.add(new PartitionsPlugin()); -// plugins.add(new RegionPlugin(RegionInitialData.builder().build())); -// plugins.add(new PersonPropertiesPlugin(PersonPropertyInitialData.builder().build())); -// plugins.add(new GroupPlugin(GroupInitialData.builder().build())); -// plugins.add(new ResourcesPlugin(ResourceInitialData.builder().build())); -// plugins.add(new MaterialsPlugin(MaterialsInitialization.builder().build())); -// plugins.add(new GCMPlugin()); -// plugins.add(new PropertiesPlugin()); -// plugins.add(new ComponentPlugin()); -// -// Collections.shuffle(plugins); -// -// /////////////////////////////////////// -// // check for null inputs -// for (Plugin plugin : plugins) { -// if (plugin == null) { -// throw new RuntimeException("null plugin"); -// } -// } -// // load structures and check for duplicate plugins -// List stubs = new ArrayList<>(); -// Map, Stub> map = new LinkedHashMap<>(); -// int loadedOrder = 0; -// for (Plugin plugin : plugins) { -// Stub stub = new Stub(plugin, loadedOrder++); -// stubs.add(stub); -// if (map.containsKey(plugin.getClass())) { -// throw new RuntimeException("duplicate plugin " + plugin.getClass()); -// } -// map.put(plugin.getClass(), stub); -// } -// -// // check for missing plugins -// for (Stub stub : stubs) { -// for (Class dependencyClass : stub.plugin.getPluginDependencies()) { -// Stub dependencyStub = map.get(dependencyClass); -// if (dependencyStub == null) { -// throw new RuntimeException("cannot locate instance of " + dependencyClass + " needed for " + stub.plugin.getClass().getSimpleName()); -// } -// } -// } -// -// // build the dependency graph -// MutableGraph m = new MutableGraph<>(); -// for (Stub stub : stubs) { -// m.addNode(stub); -// for (Class dependencyClass : stub.plugin.getPluginDependencies()) { -// Stub dependencyStub = map.get(dependencyClass); -// m.addEdge(new Object(), stub, dependencyStub); -// } -// } -// -// Optional> optional = GraphDepthEvaluator.getGraphDepthEvaluator(m.toGraph()); -// -// if (!optional.isPresent()) { -// Graph g = m.toGraph(); -// g = Graphs.getSourceSinkReducedGraph(g); -// List> cutGraphs = Graphs.cutGraph(g); -// StringBuilder sb = new StringBuilder(); -// String lineSeparator = System.getProperty("line.separator"); -// sb.append(lineSeparator); -// boolean firstCutGraph = true; -// -// for (Graph cutGraph : cutGraphs) { -// if(firstCutGraph) { -// firstCutGraph = false; -// }else { -// sb.append(lineSeparator); -// } -// sb.append("Circular plugin dependency group: "); -// sb.append(lineSeparator); -// Set nodes = cutGraph.getNodes().stream().collect(Collectors.toCollection(LinkedHashSet::new)); -// -// for (Stub node : nodes) { -// sb.append("\t"); -// sb.append(node.plugin.getClass().getSimpleName()); -// sb.append(" requires:"); -// sb.append(lineSeparator); -// for(Class pluginClass : node.plugin.getPluginDependencies()) { -// Stub dependencyNode = map.get(pluginClass); -// if(nodes.contains(dependencyNode)) { -// sb.append("\t"); -// sb.append("\t"); -// sb.append(dependencyNode.plugin.getClass().getSimpleName()); -// sb.append(lineSeparator); -// } -// } -// } -// } -// -// throw new RuntimeException(sb.toString()); -// } -// -// GraphDepthEvaluator graphDepthEvaluator = optional.get(); -// -// for (Stub stub : stubs) { -// stub.dependencyOrder = graphDepthEvaluator.getDepth(stub); -// } -// -// Collections.sort(stubs); -// -// for (Stub stub : stubs) { -// System.out.println(stub.dependencyOrder + "\t" + stub.loadedOrder + "\t" + stub.plugin.getClass().getSimpleName()); -// } -// } -// -// public static void main(String[] args) { -// new PluginDependencyTest().execute(); -// -// } -} diff --git a/gcm3/src/test/java/tools/meta/TestPlan.java b/gcm3/src/test/java/tools/meta/TestPlan.java deleted file mode 100644 index cc8ca5591..000000000 --- a/gcm3/src/test/java/tools/meta/TestPlan.java +++ /dev/null @@ -1,784 +0,0 @@ -package tools.meta; - -//import java.io.File; -//import java.io.IOException; -//import java.lang.reflect.Constructor; -//import java.lang.reflect.Method; -//import java.lang.reflect.Modifier; -//import java.nio.file.FileVisitResult; -//import java.nio.file.Files; -//import java.nio.file.Path; -//import java.nio.file.Paths; -//import java.nio.file.SimpleFileVisitor; -//import java.nio.file.attribute.BasicFileAttributes; -//import java.util.ArrayList; -//import java.util.LinkedHashMap; -//import java.util.LinkedHashSet; -//import java.util.List; -//import java.util.Map; -//import java.util.Set; -// -//import org.junit.jupiter.api.Test; -// -//import util.TimeElapser; -//import util.annotations.UnitTest; -//import util.annotations.UnitTestConstructor; -//import util.annotations.UnitTestMethod; - -/** - * A script covering the details of the GCM Test Plan. It produces a console - * report that measures the completeness/status of the test classes. It does not - * measure the correctness of any test, but rather shows which tests exist and - * their status. - * - * @author Shawn Hatch - * - */ -public class TestPlan { -// private static boolean isJavaFile(Path file) { -// return Files.isRegularFile(file) && file.toString().endsWith(".java"); -// } -// -// private static String getClassName(Path sourcePath, Path file) { -// return file.toString().substring(sourcePath.toString().length() + 1, file.toString().length() - 5).replace(File.separator, "."); -// } -// -// /** -// * Assumes that the source path and file are consistent -// */ -// private static Class getClassFromFile(Path sourcePath, Path file) { -// try { -// String className = getClassName(sourcePath, file); -// return Class.forName(className); -// } catch (ClassNotFoundException e) { -// throw new RuntimeException(e); -// } -// } -// -// private enum WarningType { -// -// SOURCE_METHOD_CANNOT_BE_RESOLVED("The source method for a test method cannot be resolved"), -// -// SOURCE_CONSTRUCTOR_CANNOT_BE_RESOLVED("The source constructor for a test method cannot be resolved"), -// -// PROXY_LEADS_OUTSIDE_SOURCE_FOLDER("Source class marked with proxy coverage leads to a class not in the source folder"), -// -// PROXY_HAS_LOWER_TEST_STATUS("Source class marked with proxy coverage leads to a class that has a lower test status"), -// -// CIRCULAR_PROXIES("Source class marked with proxy coverage leads to a circular proxy relationship"), -// -// TEST_CLASS_LINKED_OUTSIDE_SOURCE("Test class linked to source class that is not in the source folder"), -// -// TEST_METHOD_NOT_MAPPED_TO_PROPER_SOURCE_METHOD("Test method linked to unknown source method"), -// -// TEST_METHOD_NOT_MAPPED_TO_PROPER_SOURCE_CONSTRUCTOR("Test method linked to unknown source contructor"), -// -// TEST_METHOD_LINKED_TO_PROXIED_SOURCE("Test method linked to source method that is proxied to another source class"), -// -// TEST_METHOD_TESTS_SOURCE_METHOD_THAT_DOES_NOT_REQUIRE_A_TEST("Test method tests source method that does not require a test"), -// -// TEST_METHOD_TESTS_SOURCE_CONSTRUCTOR_THAT_DOES_NOT_REQUIRE_A_TEST("Test method tests source constructor that does not require a test"), -// -// SOURCE_METHOD_REQUIRES_TEST("Source method requires a test method but does not have one"), -// -// SOURCE_CONSTRUCTOR_REQUIRES_TEST("Source constructor requires a test method but does not have one"), -// -// SUITE_CLASS_MISSING_TEST_CLASS("Test class not listed in the Suite Test"), -// -// SUITE_CLASS_CONTAINS_NON_TEST_CLASS("Suite Test contains non test class"), -// -// UNIT_TEST_ANNOTATION_LACKS_SOURCE_CLASS("Unit test annotation lacks source class reference"), -// -// UNIT_CONSTRUCTOR_ANNOTATION_WITHOUT_TEST_ANNOTATION("Test method is marked with @UnitTestConstructor but does not have a corresponding @Test annotation"), -// -// UNIT_METHOD_ANNOTATION_WITHOUT_TEST_ANNOTATION("Test method is marked with @UnitTestMethod but does not have a corresponding @Test annotation"), -// -// UNIT_CONSTRUCTOR_AND_METHOD_ANNOTATIONS_PRESENT("Test method is marked with borth @UnitTestMethod and @UnitTestConstructor annotations"), -// -// TEST_ANNOTATION_WITHOUT_UNIT_ANNOTATION("Test method is marked with @Test but does not have a corresponding @UnitTestMethod or @UnitTestConstructor"), -// -// NONSTATIC_SUBCLASS("Non-static public subclasses are not testable"); -// -// private final String description; -// -// private WarningType(String description) { -// this.description = description; -// } -// } -// -// private Map> warningMap = new LinkedHashMap<>(); -// -// private void addWarning(WarningType warningType, Object details) { -// warningMap.get(warningType).add(details.toString()); -// } -// -// private final static class SourceClassRec { -// -// private final Class sourceClass; -// private final TestStatus testStatus; -// private final Class proxyClass; -// -// public SourceClassRec(final Class sourceClass, TestStatus testStatus, Class proxyClass) { -// this.sourceClass = sourceClass; -// this.testStatus = testStatus; -// this.proxyClass = proxyClass; -// } -// -// public Class getProxyClass() { -// return proxyClass; -// } -// -// public Class getSourceClass() { -// return sourceClass; -// } -// -// public TestStatus getTestStatus() { -// return testStatus; -// } -// } -// -// private Source getSource(Class c) { -// Class target = c; -// while (target != null) { -// Source source = target.getAnnotation(Source.class); -// if (source != null) { -// return source; -// } -// target = target.getDeclaringClass(); -// } -// return null; -// } -// -// private void probeClass(Class c) { -// -// TestStatus testStatus; -// Class proxyClass; -// // final Source source = c.getAnnotation(Source.class); -// final Source source = getSource(c); -// if (source != null) { -// testStatus = source.status(); -// if (source.proxy() != Object.class) { -// proxyClass = source.proxy(); -// } else { -// proxyClass = null; -// } -// } else { -// testStatus = TestStatus.REQUIRED; -// proxyClass = null; -// } -// -// final SourceClassRec sourceClassRec = new SourceClassRec(c, testStatus, proxyClass); -// sourceClassRecs.put(sourceClassRec.getSourceClass(), sourceClassRec); -// -// final Method[] methods = c.getMethods(); -// for (final Method method : methods) { -// -// boolean addRec = method.getDeclaringClass().equals(c); -// addRec &= !method.isBridge(); -// addRec &= !method.isSynthetic(); -// addRec &= !(Modifier.isAbstract(method.getModifiers()) && c.isInterface()); -// -// if (addRec) { -// TestStatus methodTestStatus = testStatus; -// final SourceMethodRec sourceMethodRec = new SourceMethodRec(method, methodTestStatus, proxyClass != null); -// sourceMethodRecs.put(sourceMethodRec.getMethod(), sourceMethodRec); -// } -// } -// -// Constructor[] constructors = c.getConstructors(); -// for (final Constructor constructor : constructors) { -// boolean addRec = constructor.getDeclaringClass().equals(c); -// addRec &= !constructor.isSynthetic(); -// if (addRec) { -// TestStatus constructorTestStatus = testStatus; -// final SourceConstructorRec sourceConstructorRec = new SourceConstructorRec(constructor, constructorTestStatus, proxyClass != null); -// sourceConstructorRecs.put(sourceConstructorRec.getConstructor(), sourceConstructorRec); -// } -// } -// -// } -// -// private Set> getClasses(Class c) { -// Set> result = new LinkedHashSet<>(); -// getClasses(c, result); -// return result; -// } -// -// private void getClasses(Class c, Set> set) { -// if (c.isAnnotation()) { -// return; -// } -// set.add(c); -// Class[] declaredClasses = c.getDeclaredClasses(); -// for (Class subClass : declaredClasses) { -// if (Modifier.isPublic(subClass.getModifiers())) { -// if (Modifier.isStatic(subClass.getModifiers())) { -// getClasses(subClass, set); -// } else { -// addWarning(WarningType.NONSTATIC_SUBCLASS, subClass); -// } -// } -// } -// } -// -// private final class SourceFileVisitor extends SimpleFileVisitor { -// @Override -// public FileVisitResult visitFile(final Path file, final BasicFileAttributes attr) { -// if (isJavaFile(file)) { -// final Class c = getClassFromFile(sourcePath, file); -// for (Class c2 : getClasses(c)) { -// probeClass(c2); -// } -// } -// return FileVisitResult.CONTINUE; -// } -// } -// -// private final static class SourceConstructorRec { -// -// private final Constructor constructor; -// -// private final boolean isProxied; -// -// private final TestStatus testStatus; -// -// public SourceConstructorRec(final Constructor constructor, TestStatus testStatus, boolean isProxied) { -// this.constructor = constructor; -// this.testStatus = testStatus; -// this.isProxied = isProxied; -// } -// -// public Constructor getConstructor() { -// return constructor; -// } -// -// public TestStatus getTestStatus() { -// return testStatus; -// } -// -// public boolean isProxied() { -// return isProxied; -// } -// -// } -// -// private final static class SourceMethodRec { -// -// private final Method method; -// -// private final boolean isProxied; -// -// private final TestStatus testStatus; -// -// public SourceMethodRec(final Method method, TestStatus testStatus, boolean isProxied) { -// this.method = method; -// this.testStatus = testStatus; -// this.isProxied = isProxied; -// } -// -// public Method getMethod() { -// return method; -// } -// -// public TestStatus getTestStatus() { -// return testStatus; -// } -// -// public boolean isProxied() { -// return isProxied; -// } -// -// @Override -// public String toString() { -// StringBuilder builder = new StringBuilder(); -// builder.append("SourceMethodRec [method="); -// builder.append(method); -// builder.append(", isProxied="); -// builder.append(isProxied); -// builder.append(", testStatus="); -// builder.append(testStatus); -// builder.append("]"); -// return builder.toString(); -// } -// -// } -// -// private final static class TestClassRec { -// -// private final Class testClass; -// -// private final Class sourceClass; -// -// public TestClassRec(final Class testClass) { -// final UnitTest unitTest = testClass.getAnnotation(UnitTest.class); -// this.testClass = testClass; -// sourceClass = unitTest.target(); -// } -// -// public Class getSourceClass() { -// return sourceClass; -// } -// -// public Class getTestClass() { -// return testClass; -// } -// -// } -// -// private final class TestFileVisitor extends SimpleFileVisitor { -// @Override -// public FileVisitResult visitFile(final Path file, final BasicFileAttributes attr) { -// /* -// * For a file to be a test file, it must be 1) a java file and 2) be -// * annotated with a UnitTest annotation. -// * -// * The UnitTest annotation must have a non-null source class -// * reference. The validity of the source class reference is examined -// * in the downstream process after all TestClassRecs have been -// * loaded. -// * -// * -// */ -// -// if (isJavaFile(file)) { -// final Class c = getClassFromFile(testPath, file); -// -// final UnitTest unitTest = c.getAnnotation(UnitTest.class); -// if (unitTest != null) { -// if (unitTest.target() == null) { -// addWarning(WarningType.UNIT_TEST_ANNOTATION_LACKS_SOURCE_CLASS, c.getCanonicalName()); -// } else { -// final TestClassRec testClassRec = new TestClassRec(c); -// testClassRecs.put(testClassRec.getTestClass(), testClassRec); -// final Method[] methods = c.getMethods(); -// for (final Method testMethod : methods) { -// final Test test = testMethod.getAnnotation(Test.class); -// final UnitTestMethod unitTestMethod = testMethod.getAnnotation(UnitTestMethod.class); -// final UnitTestConstructor unitTestConstructor = testMethod.getAnnotation(UnitTestConstructor.class); -// -// if (test == null) { -// if (unitTestMethod == null) { -// if (unitTestConstructor == null) { -// // case 0 -// // ignore the method, it is benign -// } else { -// // case 1 -// addWarning(WarningType.UNIT_CONSTRUCTOR_ANNOTATION_WITHOUT_TEST_ANNOTATION, testMethod); -// } -// } else { -// if (unitTestConstructor == null) { -// // case 2 -// addWarning(WarningType.UNIT_METHOD_ANNOTATION_WITHOUT_TEST_ANNOTATION, testMethod); -// } else { -// // case 3 -// addWarning(WarningType.UNIT_CONSTRUCTOR_AND_METHOD_ANNOTATIONS_PRESENT, testMethod); -// } -// } -// } else { -// if (unitTestMethod == null) { -// if (unitTestConstructor == null) { -// // case 4 -// addWarning(WarningType.TEST_ANNOTATION_WITHOUT_UNIT_ANNOTATION, testMethod); -// } else { -// // case 5 -// // add the unit constructor rec -// Constructor sourceConstructor; -// try { -// if (unitTestConstructor.target() != Object.class) { -// sourceConstructor = unitTestConstructor.target().getConstructor(unitTestConstructor.args()); -// } else { -// sourceConstructor = unitTest.target().getConstructor(unitTestConstructor.args()); -// } -// } catch (NoSuchMethodException | SecurityException e) { -// sourceConstructor = null; -// } -// if (sourceConstructor != null) { -// final TestConstructorRec testConstructorRec = new TestConstructorRec(testMethod, sourceConstructor); -// testConstructorRecs.put(testConstructorRec.getSourceConstructor(), testConstructorRec); -// } else { -// addWarning(WarningType.SOURCE_CONSTRUCTOR_CANNOT_BE_RESOLVED, testMethod); -// } -// -// } -// } else { -// if (unitTestConstructor == null) { -// // case 6 -// // add the unit method rec -// -// Method sourceMethod; -// try { -// if (unitTestMethod.target() != Object.class) { -// sourceMethod = unitTestMethod.target().getMethod(unitTestMethod.name(), unitTestMethod.args()); -// } else { -// sourceMethod = unitTest.target().getMethod(unitTestMethod.name(), unitTestMethod.args()); -// } -// } catch (NoSuchMethodException | SecurityException e) { -// sourceMethod = null; -// } -// if (sourceMethod != null) { -// final TestMethodRec testMethodRec = new TestMethodRec(testMethod, sourceMethod); -// testMethodRecs.put(testMethodRec.getSourceMethod(), testMethodRec); -// } else { -// addWarning(WarningType.SOURCE_METHOD_CANNOT_BE_RESOLVED, testMethod); -// } -// } else { -// // case 7 -// addWarning(WarningType.UNIT_CONSTRUCTOR_AND_METHOD_ANNOTATIONS_PRESENT, testMethod); -// } -// } -// } -// -// // if ((test != null) && (unitTestMethod != null)) { -// // Method sourceMethod; -// // try { -// // sourceMethod = -// // unitTest.target().getMethod(unitTestMethod.name(), -// // unitTestMethod.args()); -// // } catch (NoSuchMethodException | -// // SecurityException e) { -// // sourceMethod = null; -// // } -// // if (sourceMethod != null) { -// // final TestMethodRec testMethodRec = new -// // TestMethodRec(testMethod, sourceMethod); -// // testMethodRecs.put(testMethodRec.getSourceMethod(), -// // testMethodRec); -// // } else { -// // addWarning(WarningType.SOURCE_METHOD_CANNOT_BE_RESOLVED, -// // testMethod); -// // } -// // } -// -// } -// } -// } -// } -// return FileVisitResult.CONTINUE; -// } -// } -// -// private final static class TestMethodRec { -// -// private final Method testMethod; -// -// private final Method sourceMethod; -// -// public TestMethodRec(final Method testMethod, Method sourceMethod) { -// this.testMethod = testMethod; -// this.sourceMethod = sourceMethod; -// } -// -// public Method getSourceMethod() { -// return sourceMethod; -// } -// -// public Method getTestMethod() { -// return testMethod; -// } -// } -// -// private final static class TestConstructorRec { -// -// private final Method testMethod; -// -// private final Constructor sourceConstructor; -// -// public TestConstructorRec(final Method testMethod, Constructor sourceConstructor) { -// this.testMethod = testMethod; -// this.sourceConstructor = sourceConstructor; -// } -// -// public Constructor getSourceConstructor() { -// return sourceConstructor; -// } -// -// public Method getTestMethod() { -// return testMethod; -// } -// } -// -// public static void main(final String[] args) { -// -// // Should point to src/main/java -// final Path sourcePath = Paths.get(args[0]); -// -// // Should point to src/test/java -// final Path testPath = Paths.get(args[1]); -// -// // Should use true or false -// final boolean produceSourceInfo = Boolean.parseBoolean(args[2]); -// -// final TestPlan testPlan = new TestPlan(sourcePath, testPath, produceSourceInfo); -// -// testPlan.execute(); -// } -// -// private final Path sourcePath; -// -// private final Path testPath; -// -// private final boolean produceSourceInfo; -// -// private Map, SourceClassRec> sourceClassRecs = new LinkedHashMap<>(); -// -// private Map sourceMethodRecs = new LinkedHashMap<>(); -// -// private Map, SourceConstructorRec> sourceConstructorRecs = new LinkedHashMap<>(); -// -// private Map, TestClassRec> testClassRecs = new LinkedHashMap<>(); -// -// private Map testMethodRecs = new LinkedHashMap<>(); -// -// private Map, TestConstructorRec> testConstructorRecs = new LinkedHashMap<>(); -// -// private TestPlan(final Path sourcePath, final Path testPath, final boolean produceSourceInfo) { -// for (WarningType warningType : WarningType.values()) { -// warningMap.put(warningType, new ArrayList()); -// } -// this.sourcePath = sourcePath; -// this.testPath = testPath; -// this.produceSourceInfo = produceSourceInfo; -// } -// -// private void reportWarnings() { -// boolean noWarnings = true; -// for (WarningType warningType : WarningType.values()) { -// List warnings = warningMap.get(warningType); -// if (!warnings.isEmpty()) { -// noWarnings = false; -// System.out.println("(" + warnings.size() + ")" + warningType.description); -// int n = warnings.size(); -// for (int i = 0; i < n; i++) { -// String warning = warnings.get(i); -// System.out.println("\t" + warning); -// } -// System.out.println(); -// } -// } -// if (noWarnings) { -// System.out.println("Test code is consistent with source code"); -// } -// } -// -// private void validateTestClassRecs() { -// // Show that every test class links to a source class -// for (TestClassRec testClassRec : testClassRecs.values()) { -// if (!sourceClassRecs.containsKey(testClassRec.getSourceClass())) { -// addWarning(WarningType.TEST_CLASS_LINKED_OUTSIDE_SOURCE, testClassRec.getTestClass().getCanonicalName()); -// } -// } -// -// } -// -// private void validateSourceClassRecs() { -// // show that every proxied source class rec leads to through to other -// // source class recs, terminating in a non-proxied source class rec with -// // each succeeding parent record having a non-decreasing status -// -// for (SourceClassRec sourceClassRec : sourceClassRecs.values()) { -// TestStatus testStatus = sourceClassRec.getTestStatus(); -// SourceClassRec s = sourceClassRec; -// Set visitedSourceClassRecs = new LinkedHashSet<>(); -// while (true) { -// if (s == null) { -// addWarning(WarningType.PROXY_LEADS_OUTSIDE_SOURCE_FOLDER, sourceClassRec.getSourceClass().getCanonicalName()); -// break; -// } -// TestStatus nextTestStatus = s.getTestStatus(); -// if (nextTestStatus.compareTo(testStatus) > 0) { -// addWarning(WarningType.PROXY_HAS_LOWER_TEST_STATUS, sourceClassRec.getSourceClass().getCanonicalName()); -// break; -// } -// testStatus = nextTestStatus; -// if (!visitedSourceClassRecs.add(s)) { -// addWarning(WarningType.CIRCULAR_PROXIES, sourceClassRec.getSourceClass().getCanonicalName()); -// break; -// } -// if (s.getProxyClass() == null) { -// // we have terminated in a non-proxy class -// break; -// } -// s = sourceClassRecs.get(s.getProxyClass()); -// } -// } -// -// } -// -// private void validateTestMethodRecs() { -// // show that each test method links to a source method that is required -// // and non-proxied -// for (TestMethodRec testMethodRec : testMethodRecs.values()) { -// SourceMethodRec sourceMethodRec = sourceMethodRecs.get(testMethodRec.getSourceMethod()); -// if (sourceMethodRec == null) { -// addWarning(WarningType.TEST_METHOD_NOT_MAPPED_TO_PROPER_SOURCE_METHOD, testMethodRec.getTestMethod()); -// } else { -// if (sourceMethodRec.isProxied()) { -// addWarning(WarningType.TEST_METHOD_LINKED_TO_PROXIED_SOURCE, testMethodRec.getTestMethod()); -// } else { -// if (sourceMethodRec.getTestStatus() != TestStatus.REQUIRED) { -// addWarning(WarningType.TEST_METHOD_TESTS_SOURCE_METHOD_THAT_DOES_NOT_REQUIRE_A_TEST, testMethodRec.getTestMethod()); -// } -// } -// } -// } -// } -// -// private void validateTestConstructorRecs() { -// // show that each test constructor rec links to a source constructor -// // that is required -// // and non-proxied -// for (TestConstructorRec testConstructorRec : testConstructorRecs.values()) { -// SourceConstructorRec sourceConstructorRec = sourceConstructorRecs.get(testConstructorRec.getSourceConstructor()); -// if (sourceConstructorRec == null) { -// addWarning(WarningType.TEST_METHOD_NOT_MAPPED_TO_PROPER_SOURCE_CONSTRUCTOR, testConstructorRec.getTestMethod()); -// } else { -// if (sourceConstructorRec.isProxied()) { -// addWarning(WarningType.TEST_METHOD_LINKED_TO_PROXIED_SOURCE, testConstructorRec.getTestMethod()); -// } else { -// if (sourceConstructorRec.getTestStatus() != TestStatus.REQUIRED) { -// addWarning(WarningType.TEST_METHOD_TESTS_SOURCE_CONSTRUCTOR_THAT_DOES_NOT_REQUIRE_A_TEST, testConstructorRec.getTestMethod()); -// } -// } -// } -// } -// } -// -// private void validateSourceMethodRecs() { -// for (SourceMethodRec sourceMethodRec : sourceMethodRecs.values()) { -// if (!sourceMethodRec.isProxied() && sourceMethodRec.testStatus == TestStatus.REQUIRED) { -// TestMethodRec testMethodRec = testMethodRecs.get(sourceMethodRec.getMethod()); -// if (testMethodRec == null) { -// addWarning(WarningType.SOURCE_METHOD_REQUIRES_TEST, sourceMethodRec.getMethod()); -// } -// } -// } -// } -// -// private void validateSourceConstructorRecs() { -// for (SourceConstructorRec sourceConstructorRec : sourceConstructorRecs.values()) { -// if (!sourceConstructorRec.isProxied() && sourceConstructorRec.testStatus == TestStatus.REQUIRED) { -// TestConstructorRec testConstructorRec = testConstructorRecs.get(sourceConstructorRec.getConstructor()); -// if (testConstructorRec == null) { -// addWarning(WarningType.SOURCE_CONSTRUCTOR_REQUIRES_TEST, sourceConstructorRec.getConstructor()); -// } -// } -// } -// } -// -// private void loadSourceClasses() { -// final SourceFileVisitor sourceFileVisitor = new SourceFileVisitor(); -// try { -// Files.walkFileTree(sourcePath, sourceFileVisitor); -// } catch (final IOException e) { -// throw new RuntimeException(e); -// } -// } -// -// private void loadTestClasses() { -// final TestFileVisitor testFileVisitor = new TestFileVisitor(); -// try { -// Files.walkFileTree(testPath, testFileVisitor); -// } catch (final IOException e) { -// throw new RuntimeException(e); -// } -// } -// -// private void reportSourceInfo() { -// if (produceSourceInfo) { -// -// System.out.println(new StringBuilder()// -// .append("source class")// -// .append("\t")// -// .append("class status")// -// .append("\t")// -// .append("proxy class")// -// .toString());// -// for (SourceClassRec sourceClassRec : sourceClassRecs.values()) { -// String proxyClassName = ""; -// if (sourceClassRec.proxyClass != null) { -// proxyClassName = sourceClassRec.proxyClass.getName(); -// } -// -// System.out.println(new StringBuilder()// -// .append(sourceClassRec.sourceClass.getName())// -// .append("\t")// -// .append(sourceClassRec.testStatus)// -// .append("\t")// -// .append(proxyClassName)// -// .toString());// -// } -// System.out.println(); -// -// System.out.println(new StringBuilder()// -// .append("source class").append("\t")// -// .append("source method")// -// .append("\t")// -// .append("method status")// -// .append("\t")// -// .append("is proxied")// -// .toString());// -// -// for (SourceMethodRec sourceMethodRec : sourceMethodRecs.values()) { -// System.out.println(new StringBuilder()// -// .append(sourceMethodRec.method.getDeclaringClass().getName()).append("\t")// -// .append(sourceMethodRec.method.getName())// -// .append("\t")// -// .append(sourceMethodRec.testStatus)// -// .append("\t")// -// .append(sourceMethodRec.isProxied)// -// .toString());// -// } -// -// System.out.println(); -// System.out.println(new StringBuilder()// -// .append("source class").append("\t")// -// .append("source constructor")// -// .append("\t")// -// .append("constructor status")// -// .append("\t")// -// .append("is proxied")// -// .toString());// -// -// for (SourceConstructorRec sourceConstructorRec : sourceConstructorRecs.values()) { -// System.out.println(new StringBuilder()// -// .append(sourceConstructorRec.constructor.getDeclaringClass().getName()).append("\t")// -// .append(sourceConstructorRec.constructor.getName())// -// .append("\t")// -// .append(sourceConstructorRec.testStatus)// -// .append("\t")// -// .append(sourceConstructorRec.isProxied)// -// .toString());// -// } -// System.out.println(); -// } -// } -// -// private void execute() { -// -// TimeElapser timeElapser = new TimeElapser(); -// -// loadSourceClasses(); -// -// loadTestClasses(); -// -// validateSourceClassRecs(); -// -// validateTestClassRecs(); -// -// validateTestMethodRecs(); -// -// validateTestConstructorRecs(); -// -// validateSourceMethodRecs(); -// -// validateSourceConstructorRecs(); -// -// reportSourceInfo(); -// -// reportWarnings(); -// -// System.out.println("Test plan execution time = " + timeElapser.getElapsedMilliSeconds() + " milliseconds"); -// -// } - -} diff --git a/gcm3/src/test/java/util/delaunay/AT_GeoDelaunaySolver.java b/gcm3/src/test/java/util/delaunay/AT_GeoDelaunaySolver.java deleted file mode 100644 index c27418ba1..000000000 --- a/gcm3/src/test/java/util/delaunay/AT_GeoDelaunaySolver.java +++ /dev/null @@ -1,14 +0,0 @@ -package util.delaunay; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - -@UnitTest(target = GeoDelaunaySolver.class) -public class AT_GeoDelaunaySolver { - - @Test - public void test() { - //placeholder - } -} diff --git a/gcm3/src/test/java/util/delaunay/AT_PlanarDelaunaySolver.java b/gcm3/src/test/java/util/delaunay/AT_PlanarDelaunaySolver.java deleted file mode 100644 index 0342bc04d..000000000 --- a/gcm3/src/test/java/util/delaunay/AT_PlanarDelaunaySolver.java +++ /dev/null @@ -1,14 +0,0 @@ -package util.delaunay; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - -@UnitTest(target = PlanarDelaunaySolver.class) -public class AT_PlanarDelaunaySolver { - - @Test - public void test() { - //placeholder - } -} diff --git a/gcm3/src/test/java/util/delaunay/geovisualizer/GeoVisualizerDriver.java b/gcm3/src/test/java/util/delaunay/geovisualizer/GeoVisualizerDriver.java deleted file mode 100644 index c1da4c615..000000000 --- a/gcm3/src/test/java/util/delaunay/geovisualizer/GeoVisualizerDriver.java +++ /dev/null @@ -1,70 +0,0 @@ -package util.delaunay.geovisualizer; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import org.apache.commons.math3.util.Pair; - -import util.delaunay.GeoDelaunaySolver; -import util.earth.LatLon; -import util.time.TimeElapser; - -public class GeoVisualizerDriver { - - private GeoVisualizerDriver() { - - } - - public static final class ClientData { - - private final String id; - - private final int population; - - public ClientData(String id, int population) { - this.id = id; - this.population = population; - } - - public String getId() { - return id; - } - - public int getPopulation() { - return population; - } - - } - - public static void main(String[] args) throws IOException { - - Path tractsFile = Paths.get(args[0]); - - Map dataMap = new LinkedHashMap<>(); - - Files.readAllLines(tractsFile).stream().skip(1).forEach(line -> { - String[] strings = line.split(","); - String id = strings[0]; - double lon = Double.parseDouble(strings[1]); - double lat = Double.parseDouble(strings[2]); - int population = Integer.parseInt(strings[3]); - ClientData clientData = new ClientData(id, population); - LatLon latLon = new LatLon(lat, lon); - dataMap.put(clientData, latLon); - }); - - TimeElapser timeElapser = new TimeElapser(); - - List> pairs = GeoDelaunaySolver.solve(dataMap); - - System.out.println("Solver time = " + timeElapser.getElapsedSeconds() + " seconds"); - - new GeoVisualzerFrame(dataMap, pairs); - } - -} diff --git a/gcm3/src/test/java/util/delaunay/geovisualizer/GeoVisualizerPanel.java b/gcm3/src/test/java/util/delaunay/geovisualizer/GeoVisualizerPanel.java deleted file mode 100644 index 65beae4ec..000000000 --- a/gcm3/src/test/java/util/delaunay/geovisualizer/GeoVisualizerPanel.java +++ /dev/null @@ -1,916 +0,0 @@ -package util.delaunay.geovisualizer; - -import java.awt.BasicStroke; -import java.awt.Color; -import java.awt.Font; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Point; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.KeyEvent; -import java.awt.event.KeyListener; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.awt.event.MouseMotionListener; -import java.awt.event.MouseWheelEvent; -import java.awt.event.MouseWheelListener; -import java.text.DecimalFormat; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; - -import javax.swing.JPanel; -import javax.swing.Timer; - -import org.apache.commons.math3.util.FastMath; -import org.apache.commons.math3.util.Pair; - -import util.dimensiontree.DimensionTree; -import util.earth.Earth; -import util.earth.LatLon; -import util.earth.LatLonAlt; -import util.graph.Graph; -import util.path.Path; -import util.path.Paths; -import util.time.TimeElapser; -import util.vector.MutableVector3D; -import util.vector.Vector3D; - -public class GeoVisualizerPanel extends JPanel { - - private static class Link { - int first; - int second; - - public Link(int first, int second) { - super(); - this.first = first; - this.second = second; - } - - } - - private static class Model { - List positions = new ArrayList<>(); - List links = new ArrayList<>(); - } - - private static final long serialVersionUID = -8746718644903555611L; - private Timer timer; - private boolean paintOnTimer = true; - private final int timerDelayMilliseconds = 30; - private Model dataModel; - private final Camera camera; - - private Model worldGridModel; - - private void buildWorldGridModel(Earth earth) { - worldGridModel = new Model(); - // build the longitude lines - int id = 0; - for (int i = -180; i < 180; i += 15) { - int baseId = id; - for (int j = -90; j < 90; j++) { - LatLon latLon = new LatLon(j, i); - worldGridModel.positions.add(earth.getECCFromLatLon(latLon)); - Link link; - if (j == 89) { - link = new Link(id, baseId); - } else { - link = new Link(id, id + 1); - } - worldGridModel.links.add(link); - id++; - } - } - - for (int i = -90; i < 90; i += 15) { - int baseId = id; - for (int j = -180; j < 180; j++) { - LatLon latLon = new LatLon(i, j); - worldGridModel.positions.add(earth.getECCFromLatLon(latLon)); - Link link; - if (j == 179) { - link = new Link(id, baseId); - } else { - link = new Link(id, id + 1); - } - worldGridModel.links.add(link); - id++; - } - } - - } - - private void buildDataModel(Earth earth, Map dataMap, List> links) { - dataModel = new Model(); - - Map map = new LinkedHashMap<>(); - - for (T t : dataMap.keySet()) { - map.put(t, map.size()); - } - - for (T t : dataMap.keySet()) { - LatLon latLon = dataMap.get(t); - Vector3D v = earth.getECCFromLatLon(latLon); - dataModel.positions.add(v); - } - - links.forEach(pair -> { - Integer index1 = map.get(pair.getFirst()); - Integer index2 = map.get(pair.getSecond()); - Link link = new Link(index1, index2); - dataModel.links.add(link); - }); - - } - - private Earth earth; - - private static class Edge { - Link link; - - public Edge(Link link) { - super(); - this.link = link; - } - - } - - private PathManager pathManager; - - public GeoVisualizerPanel(Map dataMap, List> links) { - earth = Earth.fromMeanRadius(); - - buildWorldGridModel(earth); - - buildDataModel(earth, dataMap, links); - - MutableVector3D initialCameraPosition = new MutableVector3D(); - - pathManager = new PathManager(earth, dataModel); - - dataModel.positions.forEach(v -> initialCameraPosition.add(v)); - initialCameraPosition.normalize(); - initialCameraPosition.scale(earth.getRadius() + 500_000); - - camera = new Camera(earth, new Vector3D(initialCameraPosition)); - - addMouseListener(new MouseListenerImpl()); - addMouseWheelListener(new MouseWheelListenerImpl()); - addKeyListener(new KeyListenerImpl()); - addMouseMotionListener(new MouseMotionListenerImpl()); - - setBackground(Color.black); - - timer = new Timer(timerDelayMilliseconds, new ActionListenerImpl()); - timer.start(); - - } - - private class MouseMotionListenerImpl implements MouseMotionListener { - - @Override - public void mouseDragged(MouseEvent e) { - camera.setMouseDragEnd(e.getPoint()); - paintOnTimer = true; - } - - @Override - public void mouseMoved(MouseEvent e) { - camera.setMousePoint(e.getPoint()); - paintOnTimer = true; - } - - } - - private class KeyListenerImpl implements KeyListener { - - @Override - public void keyTyped(KeyEvent e) { - - } - - @Override - public void keyPressed(KeyEvent e) { - - switch (e.getKeyCode()) { - case KeyEvent.VK_LEFT: - camera.left(); - break; - case KeyEvent.VK_RIGHT: - camera.right(); - break; - case KeyEvent.VK_UP: - camera.up(); - break; - case KeyEvent.VK_DOWN: - camera.down(); - break; - case KeyEvent.VK_EQUALS: - camera.fovyUp(); - break; - case KeyEvent.VK_MINUS: - camera.fovyDown(); - break; - case KeyEvent.VK_S: - setPathSource(); - break; - case KeyEvent.VK_D: - setPathDestination(); - break; - - default: - return; - } - // repaint(); - paintOnTimer = true; - } - - @Override - public void keyReleased(KeyEvent e) { - - } - - } - - private class MouseWheelListenerImpl implements MouseWheelListener { - - @Override - public void mouseWheelMoved(MouseWheelEvent e) { - int wheelRotation = e.getWheelRotation(); - if (wheelRotation < 0) { - camera.zoom(true); - } else { - camera.zoom(false); - } - paintOnTimer = true; - } - - } - - private class ActionListenerImpl implements ActionListener { - - @Override - public void actionPerformed(ActionEvent e) { - if (paintOnTimer) { - paintOnTimer = false; - repaint(); - } - } - } - - private static class PathManager { - private boolean recalcPath = true; - private final Model dataModel; - private final Earth earth; - - private double getEdgeCost(Edge edge) { - Vector3D a = dataModel.positions.get(edge.link.first); - Vector3D b = dataModel.positions.get(edge.link.second); - return earth.getGroundDistanceFromECC(a, b); - } - - private double getTravelCost(Integer id1, Integer id2) { - Vector3D a = dataModel.positions.get(id1); - Vector3D b = dataModel.positions.get(id2); - return earth.getGroundDistanceFromECC(a, b); - } - - public PathManager(Earth earth, Model dataModel) { - this.earth = earth; - this.dataModel = dataModel; - positionTree = DimensionTree.builder()// - .setLowerBounds(new double[] { -earth.getRadius(), -earth.getRadius(), -earth.getRadius() })// - .setUpperBounds(new double[] { earth.getRadius(), earth.getRadius(), earth.getRadius() })// - .build();// - - Graph.Builder builder = Graph.builder(); - - for (int i = 0; i < dataModel.positions.size(); i++) { - builder.addNode(i); - } - for (Link link : dataModel.links) { - builder.addEdge(new Edge(link), link.first, link.second); - builder.addEdge(new Edge(link), link.second, link.first); - } - graph = builder.build(); - - for (int i = 0; i < dataModel.positions.size(); i++) { - Vector3D position = dataModel.positions.get(i); - positionTree.add(position.toArray(), i); - } - } - - public Vector3D getPathSourcePosition() { - return dataModel.positions.get(pathSourceIndex); - } - - public Vector3D getPathDestinationPosition() { - return dataModel.positions.get(pathDestinationIndex); - } - - private int pathSourceIndex; - - private int pathDestinationIndex; - - private final DimensionTree positionTree; - - private final Graph graph; - - private Set pathLinks = new LinkedHashSet<>(); - - private Set getPathLinks() { - if (recalcPath) { - pathLinks.clear(); - if (pathSourceIndex != pathDestinationIndex) { - TimeElapser timeElapser = new TimeElapser(); - Optional> path = Paths.getPath(graph, pathSourceIndex, pathDestinationIndex, this::getEdgeCost, this::getTravelCost); - System.out.println("path time\t" + timeElapser.getElapsedMilliSeconds()); - if (path.isPresent()) { - path.get().getEdges().forEach(edge -> pathLinks.add(edge.link)); - } - } - recalcPath = false; - } - return new LinkedHashSet<>(pathLinks); - } - - private void setPathSource(Vector3D position) { - Optional nearestMember = positionTree.getNearestMember(position.toArray()); - if (nearestMember.isPresent()) { - if (pathSourceIndex != nearestMember.get()) { - pathSourceIndex = nearestMember.get(); - recalcPath = true; - } - } - - } - - private void setPathDestination(Vector3D position) { - Optional nearestMember = positionTree.getNearestMember(position.toArray()); - if (nearestMember.isPresent()) { - if (pathDestinationIndex != nearestMember.get()) { - pathDestinationIndex = nearestMember.get(); - recalcPath = true; - } - } - } - - } - - private void setPathSource() { - if (camera.mousePoint == null) { - return; - } - Vector3D mousePosition = camera.get3DPositionFromPoint(camera.mousePoint); - - if (mousePosition == null) { - return; - } - pathManager.setPathSource(mousePosition); - paintOnTimer = true; - } - - private void setPathDestination() { - if (camera.mousePoint == null) { - return; - } - Vector3D mousePosition = camera.get3DPositionFromPoint(camera.mousePoint); - - if (mousePosition == null) { - return; - } - pathManager.setPathDestination(mousePosition); - paintOnTimer = true; - } - - private static class Camera { - private final Earth earth; - private final double panFactor = FastMath.PI / 500_000_000; - - private double screenUnitConversionRatio = 1;// depends on fovy and - // screenHeight - private double fovy = 15; - private double screenWidth; - private double screenHeight; - - private MutableVector3D cameraPosition = new MutableVector3D(); - - private MutableVector3D head = new MutableVector3D();// depends on - // camera - // position - private MutableVector3D nose = new MutableVector3D();// depends on - // camera - // position - private MutableVector3D left = new MutableVector3D();// depends on - // camera - // position - - public double getAltitudeMeters() { - return cameraPosition.length() - earth.getRadius(); - } - - private void recalcScreenUnitConversionRatio() { - screenUnitConversionRatio = this.screenHeight / (2 * FastMath.tan(FastMath.toRadians(fovy))); - } - - public void setMouseDragStart(Point point) { - mouseDragStartPosition = get3DPositionFromPoint(point); - } - - private Point mouseDragEndPoint; - - public void setMouseDragEnd(Point point) { - mouseDragEndPoint = point; - } - - public void stopMouseDrag() { - mouseDragStartPosition = null; - mouseDragEndPoint = null; - } - - private Vector3D mouseDragStartPosition; - - public void moveCameraToDrag() { - - if (mouseDragStartPosition != null && mouseDragEndPoint != null) { - - Vector3D endPosition = get3DPositionFromPoint(mouseDragEndPoint); - if (endPosition == null) { - // the mouse was dragged off the world - return; - } - double angle = mouseDragStartPosition.angle(endPosition); - MutableVector3D rotator = new MutableVector3D(endPosition); - rotator.cross(mouseDragStartPosition); - rotator.normalize(); - - MutableVector3D v = new MutableVector3D(cameraPosition); - v.rotateAbout(rotator, angle); - if (v.isFinite()) { - cameraPosition.assign(v); - calculateVectorsFromCameraPosition(); - } - } - } - - // this does not require a repaint - private Point mousePoint; - - private void setMousePoint(Point mousePoint) { - this.mousePoint = mousePoint; - } - - private Optional getLatLonOfMousePosition() { - if (mousePoint == null) { - return Optional.empty(); - } - Vector3D mousePosition = get3DPositionFromPoint(mousePoint); - - if (mousePosition == null) { - return Optional.empty(); - } - - LatLonAlt latLonAlt = earth.getLatLonAlt(mousePosition); - LatLon latLon = new LatLon(latLonAlt); - return Optional.of(latLon); - } - - private Vector3D get3DPositionFromPoint(Point point) { - - /* - * We will convert from screen coordinates to a vector that points - * from the camera to the viewing plane. Recall that the viewing - * plane is one unit away from the camera in the direction of he - * nose. - */ - - double x = point.getX(); - double y = point.getY(); - - x -= screenWidth / 2; - x /= screenUnitConversionRatio; - - y -= screenHeight / 2; - y /= screenUnitConversionRatio; - - MutableVector3D u = new MutableVector3D(); - MutableVector3D v = new MutableVector3D(); - - v.assign(left); - v.scale(-x); - u.add(v); - - v.assign(head); - v.scale(-y); - u.add(v); - - u.add(nose); - - /* - * We now normalize u and determine how far we scale u such that it - * will reach the surface of the earth. - * - * Let d = distance from the camera to the surface of the earth - * along the pointing vector u - * - * Let c = length of the camera vector - * - * Let theta = angle from the camera to u - * - * Let r = radius of the earth - * - * By the law of cosines : d^2 +c^2 -2cd cos(theta) = r^2 - * - * We solve for d, using the smallest root(the first intersection - * with the earth). - * - */ - u.normalize(); - - double c = cameraPosition.length(); - - v.assign(cameraPosition); - v.reverse(); - // double cosTheta = FastMath.cos(u.angleInRadians(v)); - double cosTheta = u.dot(v) / v.length(); - - double value = earth.getRadius() * earth.getRadius() + c * c * (cosTheta * cosTheta - 1); - if (value < 0) { - // there are no real roots - return null; - } - value = FastMath.sqrt(value); - - double d = c * cosTheta - value; - - /* - * We now know the scaling factor to apply to u. After we add back - * he camera position, we will have the position on the earth's - * surface - */ - u.scale(d); - u.add(cameraPosition); - return new Vector3D(u); - } - - public List getCameraStatus() { - - LatLonAlt latLonAlt = earth.getLatLonAlt(new Vector3D(cameraPosition)); - - List result = new ArrayList<>(); - result.add("Fovy = " + fovy); - result.add("lat = " + latLonAlt.getLatitude()); - result.add("lon = " + latLonAlt.getLongitude()); - double altKm = latLonAlt.getAltitude() / 1000; - result.add("alt(km) = " + altKm); - - Optional latLonOfMousePosition = getLatLonOfMousePosition(); - if (latLonOfMousePosition.isPresent()) { - LatLon latLon = latLonOfMousePosition.get(); - DecimalFormat decimalFormat = new DecimalFormat("#.##"); - result.add(decimalFormat.format(latLon.getLatitude()) + " " + decimalFormat.format(latLon.getLongitude())); - - } - - return result; - } - - private void calculateVectorsFromCameraPosition() { - nose.assign(cameraPosition); - nose.reverse(); - nose.normalize(); - - MutableVector3D north = new MutableVector3D(0, 0, 1); - - head.assign(cameraPosition); - head.rotateToward(north, FastMath.PI / 2); - head.normalize(); - - left.assign(head); - left.cross(nose); - left.normalize(); - } - - public void fovyUp() { - fovy += 5; - fovy = FastMath.min(180, fovy); - recalcScreenUnitConversionRatio(); - } - - public void fovyDown() { - fovy -= 5; - fovy = FastMath.max(5, fovy); - recalcScreenUnitConversionRatio(); - } - - public void zoom(boolean zoomIn) { - // determine where the mouse appears to be on the earth's surface - Vector3D currentPositionOfMouse = get3DPositionFromPoint(mousePoint); - if (currentPositionOfMouse == null) { - return; - } - - double alt = cameraPosition.length() - earth.getRadius(); - if (zoomIn) { - alt *= 0.9; - alt = FastMath.max(100, alt); - } else { - alt *= 1.1; - alt = FastMath.min(50_000_000, alt); - } - - // zoom as expected, just scaling the camera position - cameraPosition.normalize(); - cameraPosition.scale(earth.getRadius() + alt); - calculateVectorsFromCameraPosition(); - - if (!zoomIn && alt > 8_000_000) { - // zooming out and keeping location under the mouse constant - // looks odd - // if we get to high - return; - } - - // determine where the mouse appears to be now that we have zoomed - Vector3D newPositionOfMouse = get3DPositionFromPoint(mousePoint); - if (newPositionOfMouse == null) { - // we are off the world - return; - } - - /* - * Rotate the camera so that the new position of the mouse is now - * the old position of the mouse. This ensures that we keep the - * point where the mouse is constant during zoom. - */ - MutableVector3D v = new MutableVector3D(newPositionOfMouse); - v.cross(currentPositionOfMouse); - double angle = currentPositionOfMouse.angle(newPositionOfMouse); - MutableVector3D newCamera = new MutableVector3D(cameraPosition); - newCamera.rotateAbout(v, angle); - - if (newCamera.isFinite()) { - cameraPosition.assign(newCamera); - calculateVectorsFromCameraPosition(); - } - - } - - public int getWorldScreenRadius() { - double alt = cameraPosition.length() - earth.getRadius(); - return (int) (screenUnitConversionRatio * earth.getRadius() / FastMath.sqrt(2 * alt * earth.getRadius() + alt * alt)); - } - - public void up() { - double alt = cameraPosition.length() - earth.getRadius(); - Vector3D north = new Vector3D(0, 0, 1); - cameraPosition.rotateToward(north, panFactor * alt); - calculateVectorsFromCameraPosition(); - } - - public void down() { - double alt = cameraPosition.length() - earth.getRadius(); - Vector3D north = new Vector3D(0, 0, 1); - cameraPosition.rotateToward(north, -panFactor * alt); - calculateVectorsFromCameraPosition(); - } - - public void left() { - double alt = cameraPosition.length() - earth.getRadius(); - Vector3D north = new Vector3D(0, 0, 1); - cameraPosition.rotateAbout(north, -panFactor * alt); - calculateVectorsFromCameraPosition(); - - } - - public void right() { - double alt = cameraPosition.length() - earth.getRadius(); - Vector3D north = new Vector3D(0, 0, 1); - cameraPosition.rotateAbout(north, panFactor * alt); - calculateVectorsFromCameraPosition(); - - } - - public void setScreen(int screenWidth, int screenHeight) { - if (this.screenWidth != screenWidth) { - this.screenWidth = screenWidth; - } - if (this.screenHeight != screenHeight) { - this.screenHeight = screenHeight; - recalcScreenUnitConversionRatio(); - } - - } - - public Camera(Earth earth, Vector3D initialCameraPosition) { - this.earth = earth; - cameraPosition = new MutableVector3D(initialCameraPosition); - calculateVectorsFromCameraPosition(); - } - - public Point getPointFrom3DPosition(Vector3D position) { - MutableVector3D v = new MutableVector3D(position); - v.sub(cameraPosition); - - /* - * If the position is occluded by the earth then don't show the data - */ - double alt = cameraPosition.length() - earth.getRadius(); - double squareExclusionDistance = (2 * earth.getRadius() + alt) * alt; - if (v.squareLength() > squareExclusionDistance) { - return null; - } - - double x = -v.dot(left); - double y = -v.dot(head); - double z = v.dot(nose); - - x /= z; - x *= screenUnitConversionRatio; - x += screenWidth / 2; - int xCoord = (int) FastMath.round(x); - - if (xCoord < -0.1 * screenWidth) { - return null; - } - if (xCoord > 1.1 * screenWidth) { - return null; - } - - y /= z; - y *= screenUnitConversionRatio; - y += screenHeight / 2; - int yCoord = (int) FastMath.round(y); - - if (yCoord < -0.1 * screenHeight) { - return null; - } - if (yCoord > 1.1 * screenHeight) { - return null; - } - return new Point(xCoord, yCoord); - } - - } - - @Override - public void paintComponent(Graphics g) { - super.paintComponent(g); - Graphics2D g2 = (Graphics2D) g; - - camera.setScreen(getWidth(), getHeight()); - camera.moveCameraToDrag(); - - double baseWidth = Math.min(getWidth(), getHeight()); - baseWidth = baseWidth / FastMath.sqrt(dataModel.positions.size()); - int pew = (int) (baseWidth / 10); - pew = FastMath.max(pew, 1); - pew = FastMath.min(pew, 10); - int pathEdgeWidth = pew; - - int nr = (int) (baseWidth / 10); - nr = FastMath.max(nr, 2); - nr = FastMath.min(nr, 10); - int nodeRadius = nr; - - // paint the green globe - g2.setColor(new Color(161, 194, 78)); - int worldScreenRaius = camera.getWorldScreenRadius(); - g2.fillOval(getWidth() / 2 - worldScreenRaius, getHeight() / 2 - worldScreenRaius, 2 * worldScreenRaius, 2 * worldScreenRaius); - - // paint the path source and destination - - g2.setColor(Color.BLUE); - Vector3D pathSourcePosition = pathManager.getPathSourcePosition(); - Point pathSourcePoint = camera.getPointFrom3DPosition(pathSourcePosition); - if (pathSourcePoint != null) { - g2.fillOval(pathSourcePoint.x - 15, pathSourcePoint.y - 15, 30, 30); - } - - g2.setColor(Color.ORANGE); - Vector3D pathDestinationPosition = pathManager.getPathDestinationPosition(); - Point pathDestinationPoint = camera.getPointFrom3DPosition(pathDestinationPosition); - if (pathDestinationPoint != null) { - g2.fillOval(pathDestinationPoint.x - 15, pathDestinationPoint.y - 15, 30, 30); - } - - Set pathLinks = pathManager.getPathLinks(); - - // paint the links between the points in the data model - - dataModel.links.forEach(link -> { - if (pathLinks.contains(link)) { - g2.setStroke(new BasicStroke(pathEdgeWidth * 3)); - g2.setColor(Color.RED); - } else { - g2.setStroke(new BasicStroke(pathEdgeWidth)); - g2.setColor(Color.yellow); - } - Vector3D originPosition = dataModel.positions.get(link.first); - Vector3D destinationPosition = dataModel.positions.get(link.second); - List smoothPath = getSmoothPath(originPosition, destinationPosition); - for (int i = 0; i < smoothPath.size() - 1; i++) { - Vector3D a = smoothPath.get(i); - Vector3D b = smoothPath.get(i + 1); - Point originPoint = camera.getPointFrom3DPosition(a); - Point destinationPoint = camera.getPointFrom3DPosition(b); - if (originPoint != null && destinationPoint != null) { - g2.drawLine(originPoint.x, originPoint.y, destinationPoint.x, destinationPoint.y); - } - - } - - }); - - /* - * paint the nodes in the data model -- fade in nodes starting from 1000 - * km altitude to help with frame rate - */ - double altitude = camera.getAltitudeMeters(); - if (altitude <= 1_000_000) { - int alpha = (int) (255 * (1_000_000 - altitude) / 1_000_000); - alpha = Math.min(alpha, 255); - g2.setColor(new Color(255, 0, 0, alpha)); - dataModel.positions.forEach(position -> { - Point point = camera.getPointFrom3DPosition(position); - if (point != null) { - g2.fillOval(point.x - nodeRadius, point.y - nodeRadius, 2 * nodeRadius, 2 * nodeRadius); - } - }); - } - - // paint the world grid lines - g2.setColor(new Color(33, 180, 239)); - worldGridModel.links.forEach(link -> { - Vector3D originPosition = worldGridModel.positions.get(link.first); - Vector3D destinationPosition = worldGridModel.positions.get(link.second); - Point originPoint = camera.getPointFrom3DPosition(originPosition); - Point destinationPoint = camera.getPointFrom3DPosition(destinationPosition); - if (originPoint != null && destinationPoint != null) { - g2.drawLine(originPoint.x, originPoint.y, destinationPoint.x, destinationPoint.y); - } - }); - - // paint the camera's stats - g2.setColor(Color.white); - g2.setFont(new Font("Serif", Font.BOLD, 30)); - int yOffset = 50; - for (String stat : camera.getCameraStatus()) { - g2.drawString(stat, 10, yOffset); - yOffset += 30; - } - - } - - private List getSmoothPath(Vector3D a, Vector3D b) { - List result = new ArrayList<>(); - double angle = a.angle(b); - int steps = (int) FastMath.ceil(180 * angle / FastMath.PI); - for (int i = 0; i < steps; i++) { - double stepAngle = (i * angle) / steps; - result.add(a.rotateToward(b, stepAngle)); - } - result.add(b); - return result; - } - - private class MouseListenerImpl implements MouseListener { - - @Override - public void mouseClicked(MouseEvent e) { - - } - - @Override - public void mousePressed(MouseEvent e) { - camera.setMouseDragStart(e.getPoint()); - paintOnTimer = true; - } - - @Override - public void mouseReleased(MouseEvent e) { - camera.stopMouseDrag(); - paintOnTimer = true; - } - - @Override - public void mouseEntered(MouseEvent e) { - } - - @Override - public void mouseExited(MouseEvent e) { - // do nothing - - } - - } - -} diff --git a/gcm3/src/test/java/util/delaunay/geovisualizer/GeoVisualzerFrame.java b/gcm3/src/test/java/util/delaunay/geovisualizer/GeoVisualzerFrame.java deleted file mode 100644 index 1d917a39e..000000000 --- a/gcm3/src/test/java/util/delaunay/geovisualizer/GeoVisualzerFrame.java +++ /dev/null @@ -1,29 +0,0 @@ -package util.delaunay.geovisualizer; - -import java.awt.Frame; -import java.util.List; -import java.util.Map; - -import javax.swing.JFrame; - -import org.apache.commons.math3.util.Pair; - -import util.earth.LatLon; - -public class GeoVisualzerFrame extends JFrame { - private static final long serialVersionUID = -5106781364986923139L; - - public GeoVisualzerFrame(Map dataMap, List> links) { - super(); - setSize(500, 500); - setLocation(0, 0); - setExtendedState(Frame.MAXIMIZED_BOTH); - setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - GeoVisualizerPanel geoVisualizerPanel = new GeoVisualizerPanel(dataMap, links); - setContentPane(geoVisualizerPanel); - // give the panel focus so that key listeners will work - geoVisualizerPanel.setFocusable(true); - setVisible(true); - } - -} diff --git a/gcm3/src/test/java/util/delaunay/planarvisualizer/PlanarVisualizerDriver.java b/gcm3/src/test/java/util/delaunay/planarvisualizer/PlanarVisualizerDriver.java deleted file mode 100644 index c6702b771..000000000 --- a/gcm3/src/test/java/util/delaunay/planarvisualizer/PlanarVisualizerDriver.java +++ /dev/null @@ -1,38 +0,0 @@ -package util.delaunay.planarvisualizer; - -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import org.apache.commons.math3.random.RandomGenerator; -import org.apache.commons.math3.util.Pair; - -import util.delaunay.PlanarDelaunaySolver; -import util.random.RandomGeneratorProvider; -import util.time.TimeElapser; -import util.vector.Vector2D; - -public class PlanarVisualizerDriver { - - public static void main(String[] args) { - long seed = 147623563453456L; - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); - - int nodeCount = 10_000; - - Map dataMap = new LinkedHashMap<>(); - for (int i = 0; i < nodeCount; i++) { - Vector2D v = new Vector2D(randomGenerator.nextDouble() * 1000 - 500, randomGenerator.nextDouble() * 1000 - 500); - dataMap.put(new Object(), v); - } - - TimeElapser timeElapser = new TimeElapser(); - List> pairs = PlanarDelaunaySolver.solve(dataMap); - System.out.println("PlanarDelaunaySolver time = " + timeElapser.getElapsedMilliSeconds()); - - System.out.println("pairs = " + pairs.size()); - - new PlanarVisualzerFrame(dataMap, pairs); - } - -} diff --git a/gcm3/src/test/java/util/delaunay/planarvisualizer/PlanarVisualizerPanel.java b/gcm3/src/test/java/util/delaunay/planarvisualizer/PlanarVisualizerPanel.java deleted file mode 100644 index 8a4bc41eb..000000000 --- a/gcm3/src/test/java/util/delaunay/planarvisualizer/PlanarVisualizerPanel.java +++ /dev/null @@ -1,307 +0,0 @@ -package util.delaunay.planarvisualizer; - -import java.awt.BasicStroke; -import java.awt.Color; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Point; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.swing.JPanel; -import javax.swing.Timer; - -import org.apache.commons.math3.util.FastMath; -import org.apache.commons.math3.util.Pair; - -import util.vector.MutableVector2D; -import util.vector.Vector2D; - -public class PlanarVisualizerPanel extends JPanel { - private static final long serialVersionUID = -8746718644903555611L; - private Timer timer; - private boolean paintOnTimer; - private final int timerDelayMilliseconds = 1; - - private final PointModelProvider pointModelProvider; - - public PlanarVisualizerPanel(Map dataMap, List> links) { - - pointModelProvider = new PointModelProvider<>(dataMap, links); - - addMouseListener(new MouseListenerImpl()); - - setBackground(Color.darkGray); - - timer = new Timer(timerDelayMilliseconds, new ActionListenerImpl()); - timer.start(); - - } - - private class ActionListenerImpl implements ActionListener { - - @Override - public void actionPerformed(ActionEvent e) { - // if (paintOnTimer) { - // buildPath(); - // repaint(); - // } - } - } - - @Override - public void paintComponent(Graphics g) { - super.paintComponent(g); - Graphics2D g2 = (Graphics2D) g; - displayModel(g2); - } - - private static class Link { - private int first; - private int second; - - public Link(int first, int second) { - super(); - this.first = first; - this.second = second; - } - - } - - private class PointModelProvider { - private final Map dataMap; - private List points; - private int lastWidth; - private int lastHeight; - private List links = new ArrayList<>(); - private Set linkIndices = new LinkedHashSet<>(); - - public PointModelProvider(Map dataMap, List> links) { - this.dataMap = dataMap; - - Map map = new LinkedHashMap<>(); - - for (T t : dataMap.keySet()) { - map.put(t, map.size()); - } - - for (Pair pair : links) { - this.links.add(new Link(map.get(pair.getFirst()), map.get(pair.getSecond()))); - } - - for (Link link : this.links) { - linkIndices.add(link.first); - linkIndices.add(link.second); - } - } - - public boolean isLinkIndex(int index) { - return linkIndices.contains(index); - } - - public List getPoints() { - int currentWidth = getWidth(); - int currentHeight = getHeight(); - if (currentHeight != lastHeight || currentWidth != lastWidth || points == null) { - lastWidth = currentWidth; - lastHeight = currentHeight; - buildPoints(); - } - return points; - } - - public List getLinks() { - return links; - } - - private void buildPoints() { - - MutableVector2D upperLeftDataPosition = new MutableVector2D(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); - MutableVector2D lowerRightDataPosition = new MutableVector2D(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY); - - for (T t : dataMap.keySet()) { - Vector2D v = dataMap.get(t); - - if (v.getX() < upperLeftDataPosition.getX()) { - upperLeftDataPosition.setX(v.getX()); - } - - if (v.getY() < upperLeftDataPosition.getY()) { - upperLeftDataPosition.setY(v.getY()); - } - - if (v.getX() > lowerRightDataPosition.getX()) { - lowerRightDataPosition.setX(v.getX()); - } - if (v.getY() > lowerRightDataPosition.getY()) { - lowerRightDataPosition.setY(v.getY()); - } - } - - double deltaX = lowerRightDataPosition.getX() - upperLeftDataPosition.getX(); - double deltaY = lowerRightDataPosition.getY() - upperLeftDataPosition.getY(); - - double sX = lastWidth; - sX /= deltaX; - - double sY = lastHeight; - sY /= deltaY; - - /* - * Adjust the scalar factor so the points are not on the edge of the - * screen - */ - double scalar = Math.min(sX, sY) * 0.9; - - MutableVector2D screenCenter = new MutableVector2D(lastWidth, lastHeight); - screenCenter.scale(0.5); - - /* - * dataCenter is the center of the box that bounds the data, rather - * than the centroid of the data - */ - MutableVector2D dataCenter = new MutableVector2D(lowerRightDataPosition); - dataCenter.add(upperLeftDataPosition); - dataCenter.scale(0.5); - - /* - * Map each point in the data to the screen coordinates. Note that - * this projection takes data center to screen center. - */ - points = new ArrayList<>(); - for (T t : dataMap.keySet()) { - MutableVector2D v = new MutableVector2D(dataMap.get(t)); - v.sub(dataCenter); - v.scale(scalar); - v.add(screenCenter); - Point point = new Point((int) v.getX(), (int) v.getY()); - points.add(point); - } - } - - } - - private void displayModel(Graphics2D g2) { - - // Transform the data model into a point model that will fit on the - // screen - List points = pointModelProvider.getPoints(); - - double baseWidth = Math.min(getWidth(), getHeight()); - // int modelEdgeWidth = (int) Math.max(1, baseWidth); - // int pathEdgeWidth = 2 * modelEdgeWidth; - // int nodeRadius = 5 * modelEdgeWidth; - - baseWidth = baseWidth / FastMath.sqrt(points.size()); - - int pathEdgeWidth = (int) (baseWidth / 10); - pathEdgeWidth = FastMath.max(pathEdgeWidth, 1); - pathEdgeWidth = FastMath.min(pathEdgeWidth, 10); - int nodeRadius = (int) (baseWidth / 10); - nodeRadius = FastMath.max(nodeRadius, 1); - nodeRadius = FastMath.min(nodeRadius, 10); - - // paint the point links - g2.setStroke(new BasicStroke(pathEdgeWidth)); - g2.setColor(Color.yellow); - List links = pointModelProvider.getLinks(); - for (Link link : links) { - Point originPoint = points.get(link.first); - g2.fillOval(originPoint.x - nodeRadius, originPoint.y - nodeRadius, 2 * nodeRadius, 2 * nodeRadius); - - Point destinationPoint = points.get(link.second); - g2.fillOval(destinationPoint.x - nodeRadius, destinationPoint.y - nodeRadius, 2 * nodeRadius, 2 * nodeRadius); - - g2.drawLine(originPoint.x, originPoint.y, destinationPoint.x, destinationPoint.y); - } - - // paint the nodes - g2.setColor(Color.red); - for (int i = 0; i < points.size(); i++) { - if (!pointModelProvider.isLinkIndex(i)) { - Point point = points.get(i); - g2.fillOval(point.x - nodeRadius, point.y - nodeRadius, 2 * nodeRadius, 2 * nodeRadius); - } - } - - // if (pointModel.pointCount() < 20) { - // g2.setColor(Color.white); - // g2.setFont(new Font("Serif", Font.BOLD, 20)); - //// PointModel planarPointModel = - // pointModelProvider.getPlanarPointModel(); - // for (int i = 0; i < pointModel.pointCount(); i++) { - // Point point = pointModel.getPoint(i); - //// PlanarPoint planarPoint = planarPointModel.getPoint(i); - // StringBuilder sb = new StringBuilder(); - // sb.append(Integer.toString(i)); - //// sb.append(" ( "); - //// sb.append(planarPoint.getX()); - //// sb.append(" , "); - //// sb.append(planarPoint.getY()); - //// sb.append(" )"); - // g2.drawString(sb.toString(), point.x - nodeRadius, point.y - - // nodeRadius); - // } - // } - - } - - private void handleMouseClicked(MouseEvent e) { - switch (e.getButton()) { - case MouseEvent.BUTTON1: - repaint(); - break; - case MouseEvent.BUTTON2: - paintOnTimer = !paintOnTimer; - break; - case MouseEvent.BUTTON3: - // buildPath(); - break; - default: - return; - } - - repaint(); - } - - private class MouseListenerImpl implements MouseListener { - - @Override - public void mouseClicked(MouseEvent e) { - handleMouseClicked(e); - } - - @Override - public void mousePressed(MouseEvent e) { - // do nothing - - } - - @Override - public void mouseReleased(MouseEvent e) { - // do nothing - - } - - @Override - public void mouseEntered(MouseEvent e) { - // do nothing - } - - @Override - public void mouseExited(MouseEvent e) { - // do nothing - - } - - } - -} diff --git a/gcm3/src/test/java/util/delaunay/planarvisualizer/PlanarVisualzerFrame.java b/gcm3/src/test/java/util/delaunay/planarvisualizer/PlanarVisualzerFrame.java deleted file mode 100644 index ecc552b5f..000000000 --- a/gcm3/src/test/java/util/delaunay/planarvisualizer/PlanarVisualzerFrame.java +++ /dev/null @@ -1,29 +0,0 @@ -package util.delaunay.planarvisualizer; - -import java.awt.Frame; -import java.util.List; -import java.util.Map; - -import javax.swing.JFrame; - -import org.apache.commons.math3.util.Pair; - -import util.vector.Vector2D; - -public class PlanarVisualzerFrame extends JFrame { - private static final long serialVersionUID = -5106781364986923139L; - - public PlanarVisualzerFrame(Map dataMap, List> links) { - super(); - setSize(1500, 1000); - setLocation(0, 0); - setExtendedState(Frame.MAXIMIZED_BOTH); - setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - PlanarVisualizerPanel planarVisualizerPanel = new PlanarVisualizerPanel(dataMap, links); - setContentPane(planarVisualizerPanel); - // give the panel focus so that key listeners will work - planarVisualizerPanel.setFocusable(true); - setVisible(true); - } - -} diff --git a/gcm3/src/test/java/util/dimensiontree/AT_DimensionTree.java b/gcm3/src/test/java/util/dimensiontree/AT_DimensionTree.java deleted file mode 100644 index a5e0376a6..000000000 --- a/gcm3/src/test/java/util/dimensiontree/AT_DimensionTree.java +++ /dev/null @@ -1,536 +0,0 @@ -package util.dimensiontree; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - -import org.apache.commons.math3.random.RandomGenerator; -import org.apache.commons.math3.util.FastMath; -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.random.RandomGeneratorProvider; -import util.time.TimeElapser; - -/** - * Test class for {@link DimensionTree} - * - * @author Shawn Hatch - * - */ - -@UnitTest(target = DimensionTree.class) - -public class AT_DimensionTree { - - private static class Record { - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("Record [id="); - builder.append(id); - builder.append(", position="); - builder.append(Arrays.toString(position)); - builder.append("]"); - return builder.toString(); - } - - private final int id; - private final double[] position = new double[2]; - - public Record(int id, double x, double y) { - this.id = id; - position[0] = x; - position[1] = y; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + id; - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (!(obj instanceof Record)) { - return false; - } - Record other = (Record) obj; - if (id != other.id) { - return false; - } - return true; - } - - } - - /** - * Tests {@link DimensionTree#getMembersInSphere(double, double[]) - */ - @Test - @UnitTestMethod(name = "getMembersInSphere", args = { double.class, double[].class }) - public void testGetMembersInSphere() { - - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4720556754557132042L); - - DimensionTree tree = // - DimensionTree .builder()// - .setLowerBounds(new double[] { 0, 0 })// - .setUpperBounds(new double[] { 100, 100 })// - .build(); // - - List records = new ArrayList<>(); - - int n = 1000; - for (int i = 0; i < n; i++) { - Record record = new Record(i, randomGenerator.nextDouble() * 100, randomGenerator.nextDouble() * 100); - records.add(record); - } - - for (Record record : records) { - tree.add(record.position, record); - } - - double searchRadius = 10; - - for (int i = 0; i < n; i++) { - double[] position = new double[2]; - position[0] = randomGenerator.nextDouble() * 120 - 10; - position[1] = randomGenerator.nextDouble() * 120 - 10; - - List list = new ArrayList<>(); - - for (Record record : records) { - double deltaX = record.position[0] - position[0]; - double deltaY = record.position[1] - position[1]; - double distance = FastMath.sqrt(deltaX * deltaX + deltaY * deltaY); - if (distance < searchRadius) { - list.add(record); - } - } - - Set expectedRecords = new LinkedHashSet<>(list); - - list = tree.getMembersInSphere(searchRadius, position); - - Set actualRecords = new LinkedHashSet<>(list); - - assertEquals(expectedRecords, actualRecords); - } - - } - - /** - * Tests {@link DimensionTree#getNearestMember(double[]) - */ - @Test - @UnitTestMethod(name = "getNearestMember", args = { double[].class }) - public void testGetNearestMember() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1704585910834495981L); - - DimensionTree tree = // - DimensionTree .builder()// - .setLowerBounds(new double[] { -10, -10 })// - .setUpperBounds(new double[] { -1, -1 })// - .build(); // - - List records = new ArrayList<>(); - - int n = 1000; - for (int i = 0; i < n; i++) { - Record record = new Record(i, randomGenerator.nextDouble() * 100, randomGenerator.nextDouble() * 100); - records.add(record); - } - - for (Record record : records) { - tree.add(record.position, record); - } - - for (int i = 0; i < n; i++) { - double[] position = new double[2]; - position[0] = randomGenerator.nextDouble() * 120 - 10; - position[1] = randomGenerator.nextDouble() * 120 - 10; - - Record expectedClosestRecord = null; - double bestDistance = Double.POSITIVE_INFINITY; - for (Record record : records) { - double deltaX = record.position[0] - position[0]; - double deltaY = record.position[1] - position[1]; - double distance = FastMath.sqrt(deltaX * deltaX + deltaY * deltaY); - if (expectedClosestRecord == null || distance < bestDistance) { - expectedClosestRecord = record; - bestDistance = distance; - } - } - - Optional actualClosestRecord = tree.getNearestMember(position); - assertTrue(actualClosestRecord.isPresent()); - assertEquals(expectedClosestRecord, actualClosestRecord.get()); - - } - - } - - /** - * Tests {@link DimensionTree#getMembersInRectanguloid(double[], double[]) - */ - @Test - @UnitTestMethod(name = "getMembersInRectanguloid", args = { double[].class, double[].class }) - public void testGetMembersInRectanguloid() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7067981743992714824L); - - DimensionTree tree = // - DimensionTree .builder()// - .setLowerBounds(new double[] { 0, 0 })// - .setUpperBounds(new double[] { 1, 1 })// - .build(); // - - List records = new ArrayList<>(); - - int n = 1000; - for (int i = 0; i < n; i++) { - Record record = new Record(i, randomGenerator.nextDouble() * 100, randomGenerator.nextDouble() * 100); - records.add(record); - } - - for (Record record : records) { - tree.add(record.position, record); - } - - // StopWatch bruteForceStopWatch = new StopWatch(); - // StopWatch treeStopWatch = new StopWatch(); - for (int i = 0; i < n; i++) { - double[] upperBounds = new double[2]; - double[] lowerBounds = new double[2]; - lowerBounds[0] = randomGenerator.nextDouble() * 120 - 10; - lowerBounds[1] = randomGenerator.nextDouble() * 120 - 10; - upperBounds[0] = lowerBounds[0] + randomGenerator.nextDouble() * 10 + 1; - upperBounds[1] = lowerBounds[1] + randomGenerator.nextDouble() * 10 + 1; - - Set expectedRecords = new LinkedHashSet<>(); - // bruteForceStopWatch.start(); - for (Record record : records) { - boolean reject = false; - for (int j = 0; j < 2; j++) { - reject |= record.position[j] > upperBounds[j]; - reject |= record.position[j] < lowerBounds[j]; - } - if (!reject) { - expectedRecords.add(record); - } - } - // bruteForceStopWatch.stop(); - - // treeStopWatch.start(); - Set actualRecords = tree.getMembersInRectanguloid(lowerBounds, upperBounds).stream().collect(Collectors.toCollection(LinkedHashSet::new)); - // treeStopWatch.stop(); - - assertEquals(expectedRecords, actualRecords); - - } - // System.out.println("brute force " + - // bruteForceStopWatch.getElapsedMilliSeconds()); - // System.out.println("tree " + treeStopWatch.getElapsedMilliSeconds()); - - } - - /** - * Tests {@link DimensionTree#getAll() - */ - @Test - @UnitTestMethod(name = "getAll", args = {}) - public void testGetAll() { - /* - * See test for add() - */ - } - - /** - * Tests {@link DimensionTree#add(double[], Object) - */ - @Test - @UnitTestMethod(name = "add", args = { double[].class, Object.class }) - public void testAdd() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7699215262915366096L); - - DimensionTree tree = // - DimensionTree .builder()// - .setLowerBounds(new double[] { 0, 0 })// - .setUpperBounds(new double[] { 1, 1 })// - .setLeafSize(15).build(); // - - List records = new ArrayList<>(); - - int n = 100; - for (int i = 0; i < n; i++) { - Record record = new Record(i, randomGenerator.nextDouble() * 100, randomGenerator.nextDouble() * 100); - records.add(record); - tree.add(record.position, record); - } - - Set expectedRecords = records.stream().collect(Collectors.toCollection(LinkedHashSet::new)); - Set actualRecords = tree.getAll().stream().collect(Collectors.toCollection(LinkedHashSet::new)); - - assertEquals(expectedRecords, actualRecords); - - // We add the records again, this should result in each record being in - // two places in the tree and the tree returning twice as many records. - // Note that two records are equal if their id values match. - for (int i = 0; i < n; i++) { - Record record = new Record(i, randomGenerator.nextDouble() * 100, randomGenerator.nextDouble() * 100); - tree.add(record.position, record); - } - - assertEquals(records.size() * 2, tree.getAll().size()); - - expectedRecords = records.stream().collect(Collectors.toCollection(LinkedHashSet::new)); - actualRecords = tree.getAll().stream().collect(Collectors.toCollection(LinkedHashSet::new)); - - assertEquals(expectedRecords, actualRecords); - - } - - /** - * Tests {@link DimensionTree#contains(Object) - */ - @Test - @UnitTestMethod(name = "contains", args = { Object.class }) - public void testContains() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7408475895380756180L); - - DimensionTree tree = // - DimensionTree .builder()// - .setLowerBounds(new double[] { 0, 0 })// - .setUpperBounds(new double[] { 1, 1 })// - .setLeafSize(15).build(); // - - List records = new ArrayList<>(); - - int n = 100; - for (int i = 0; i < n; i++) { - Record record = new Record(i, randomGenerator.nextDouble() * 100, randomGenerator.nextDouble() * 100); - records.add(record); - if (i < n / 2) { - tree.add(record.position, record); - } - } - - for (int i = 0; i < n; i++) { - Record record = records.get(i); - if (i < n / 2) { - assertTrue(tree.contains(record)); - } else { - assertFalse(tree.contains(record)); - } - } - - } - - /** - * Tests {@link DimensionTree#remove(Object) - */ - @Test - @UnitTestMethod(name = "remove", args = { Object.class }) - public void testRemove() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7030760224206012399L); - - DimensionTree tree = // - DimensionTree .builder()// - .setLowerBounds(new double[] { 0, 0 })// - .setUpperBounds(new double[] { 100, 100 })// - .setFastRemovals(true)// - .setLeafSize(50)// - .build(); // - - List records = new ArrayList<>(); - - int n = 100; - for (int i = 0; i < n; i++) { - Record record = new Record(i, randomGenerator.nextDouble() * 100, randomGenerator.nextDouble() * 100); - records.add(record); - if (i % 2 == 0) { - tree.add(record.position, record); - tree.add(record.position, record); - tree.add(record.position, record); - record.position[0] = randomGenerator.nextDouble() * 100; - record.position[1] = randomGenerator.nextDouble() * 100; - tree.add(record.position, record); - tree.add(record.position, record); - } - } - - // System.out.println(tree); - - for (int i = 0; i < records.size(); i++) { - Record record = records.get(i); - boolean removed = tree.remove(record); - assertEquals(i % 2 == 0, removed); - // System.out.println(tree); - } - - } - - /** - * Tests {@link DimensionTree#builder()} - */ - @Test - @UnitTestMethod(name = "builder", args = {}) - public void testBuilder() { - /* - * Precondition tests - */ - - // first show that the following arguments to the builder form a tree. - DimensionTree tree = DimensionTree .builder()// - .setLowerBounds(new double[] { 0, 0 })// - .setUpperBounds(new double[] { 100, 100 })// - .setFastRemovals(true)// - .setLeafSize(50)// - .build(); // - assertNotNull(tree); - - // if the selected leaf size is not positive - assertThrows(RuntimeException.class, () -> { - DimensionTree .builder()// - .setLowerBounds(new double[] { 0, 0 })// - .setUpperBounds(new double[] { 100, 100 })// - .setFastRemovals(true)// - .setLeafSize(-50)// - .build(); // - }); - - // if the lower bounds were not contributed or were null - assertThrows(RuntimeException.class, () -> { - DimensionTree .builder()// - // .setLowerBounds(new double[] { 0, 0 })// - .setUpperBounds(new double[] { 100, 100 })// - .setFastRemovals(true)// - .setLeafSize(50)// - .build(); // - }); - - assertThrows(RuntimeException.class, () -> { - DimensionTree .builder()// - .setLowerBounds(null)// - .setUpperBounds(new double[] { 100, 100 })// - .setFastRemovals(true)// - .setLeafSize(50)// - .build(); // - }); - - // if the upper bounds were not contributed or were null - assertThrows(RuntimeException.class, () -> { - DimensionTree .builder()// - .setLowerBounds(new double[] { 0, 0 })// - // .setUpperBounds(new double[] { 100, 100 })// - .setFastRemovals(true)// - .setLeafSize(50)// - .build(); // - }); - - assertThrows(RuntimeException.class, () -> { - DimensionTree .builder()// - .setLowerBounds(new double[] { 0, 0 })// - .setUpperBounds(null)// - .setFastRemovals(true)// - .setLeafSize(50)// - .build(); // - }); - - // if the lower and upper bounds do not match in length - assertThrows(RuntimeException.class, () -> { - DimensionTree .builder()// - .setLowerBounds(new double[] { 0, 0 })// - .setUpperBounds(new double[] { 100, 100, 100 })// - .setFastRemovals(true)// - .setLeafSize(50)// - .build(); // - }); - - assertThrows(RuntimeException.class, () -> { - DimensionTree .builder()// - .setLowerBounds(new double[] { 0, 0, 0 })// - .setUpperBounds(new double[] { 100, 100 })// - .setFastRemovals(true)// - .setLeafSize(50)// - .build(); // - }); - - // if any of the lower bounds exceed the corresponding - assertThrows(RuntimeException.class, () -> { - DimensionTree .builder()// - .setLowerBounds(new double[] { 101, 0 })// - .setUpperBounds(new double[] { 100, 100 })// - .setFastRemovals(true)// - .setLeafSize(50)// - .build(); // - }); - - assertThrows(RuntimeException.class, () -> { - DimensionTree .builder()// - .setLowerBounds(new double[] { 0, 101 })// - .setUpperBounds(new double[] { 100, 100 })// - .setFastRemovals(true)// - .setLeafSize(50)// - .build(); // - }); - - } - - // @Test - public void testPerformance() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3684348766628697684L); - - DimensionTree tree = // - DimensionTree .builder()// - .setLowerBounds(new double[] { 0, 0 })// - .setUpperBounds(new double[] { 100, 100 })// - .setFastRemovals(true)// - .setLeafSize(15)// - .build(); // - - List records = new ArrayList<>(); - - int n = 600_000; - for (int i = 0; i < n; i++) { - Record record = new Record(i, randomGenerator.nextDouble() * 100, randomGenerator.nextDouble() * 100); - records.add(record); - } - TimeElapser timeElapser = new TimeElapser(); - - for (Record record : records) { - tree.add(record.position, record); - } - System.out.println("Add time = " + timeElapser.getElapsedMilliSeconds()); - timeElapser.reset(); - for (Record record : records) { - tree.remove(record); - } - System.out.println("Remove time = " + timeElapser.getElapsedMilliSeconds()); - } - -} diff --git a/gcm3/src/test/java/util/dimensiontree/AT_VolumetricDimensionTree.java b/gcm3/src/test/java/util/dimensiontree/AT_VolumetricDimensionTree.java deleted file mode 100644 index 020f42b77..000000000 --- a/gcm3/src/test/java/util/dimensiontree/AT_VolumetricDimensionTree.java +++ /dev/null @@ -1,436 +0,0 @@ -package util.dimensiontree; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.random.RandomGeneratorProvider; -import util.vector.Vector2D; - -/** - * Test class for {@link VolumetricDimensionTree} - * - * @author Shawn Hatch - * - */ - -@UnitTest(target = VolumetricDimensionTree.class) - -public class AT_VolumetricDimensionTree { - - - private static class Record { - private final Vector2D position; - private final double radius; - private final int id; - - public Record(int id, Vector2D position, double radius) { - this.position = position; - this.radius = radius; - this.id = id; - if (radius < 0) { - throw new RuntimeException("negative radius"); - } - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + id; - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (!(obj instanceof Record)) { - return false; - } - Record other = (Record) obj; - if (id != other.id) { - return false; - } - return true; - } - - } - - /** - * Tests {@link VolumetricDimensionTree#getMembersInSphere(double, double[]) - */ - @Test - @UnitTestMethod(name = "getMembersInSphere", args = { double.class, double[].class }) - public void testGetMembersInSphere() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6999798233863944694L); - - VolumetricDimensionTree tree = // - VolumetricDimensionTree .builder()// - .setLowerBounds(new double[] { 0, 0 })// - .setUpperBounds(new double[] { 100, 100 })// - .build(); // - - List records = new ArrayList<>(); - - int n = 1000; - for (int i = 0; i < n; i++) { - Vector2D position = new Vector2D(randomGenerator.nextDouble() * 100, randomGenerator.nextDouble() * 100); - double radius = randomGenerator.nextDouble() * 10; - Record record = new Record(i, position, radius); - records.add(record); - } - - for (Record record : records) { - tree.add(record.position.toArray(), record.radius, record); - } - - double searchRadius = 10; - - for (int i = 0; i < n; i++) { - Vector2D searchPosition = new Vector2D(randomGenerator.nextDouble() * 120 - 10, randomGenerator.nextDouble() * 120 - 10); - - Set expectedRecords = new LinkedHashSet<>(); - - for (Record record : records) { - double distance = record.position.distanceTo(searchPosition); - if (distance <= searchRadius + record.radius) { - expectedRecords.add(record); - } - } - - Set actualRecords = tree.getMembersInSphere(searchRadius, searchPosition.toArray()).stream().collect(Collectors.toCollection(LinkedHashSet::new)); - - assertEquals(expectedRecords, actualRecords); - } - } - - /** - * Tests {@link VolumetricDimensionTree#getAll() - */ - @Test - @UnitTestMethod(name = "getAll", args = {}) - public void testGetAll() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7850509241104624831L); - - VolumetricDimensionTree tree = // - VolumetricDimensionTree .builder()// - .setLowerBounds(new double[] { 0, 0 })// - .setUpperBounds(new double[] { 100, 100 })// - .build(); // - - List records = new ArrayList<>(); - - int n = 1000; - for (int i = 0; i < n; i++) { - Vector2D position = new Vector2D(randomGenerator.nextDouble() * 100, randomGenerator.nextDouble() * 100); - double radius = randomGenerator.nextDouble() * 10; - Record record = new Record(i, position, radius); - records.add(record); - } - - for (Record record : records) { - tree.add(record.position.toArray(), record.radius, record); - } - - Set expected = records.stream().collect(Collectors.toCollection(LinkedHashSet::new)); - Set actual = tree.getAll().stream().collect(Collectors.toCollection(LinkedHashSet::new)); - - assertEquals(expected, actual); - } - - /** - * Tests {@link VolumetricDimensionTree#add(double[], double, Object) - */ - @Test - @UnitTestMethod(name = "add", args = { double[].class, double.class, Object.class }) - public void testAdd() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8144068492710937714L); - - VolumetricDimensionTree tree = // - VolumetricDimensionTree .builder()// - .setLowerBounds(new double[] { 0, 0 })// - .setUpperBounds(new double[] { 100, 100 })// - .build(); // - - List records = new ArrayList<>(); - - int n = 1000; - for (int i = 0; i < n; i++) { - Vector2D position = new Vector2D(randomGenerator.nextDouble() * 100, randomGenerator.nextDouble() * 100); - double radius = randomGenerator.nextDouble() * 10; - Record record = new Record(i, position, radius); - records.add(record); - tree.add(record.position.toArray(), record.radius, record); - } - - Set expectedRecords = records.stream().collect(Collectors.toCollection(LinkedHashSet::new)); - Set actualRecords = tree.getAll().stream().collect(Collectors.toCollection(LinkedHashSet::new)); - - assertEquals(expectedRecords, actualRecords); - - /* - * We add the records again, this should result in each record being in - * two places in the tree and the tree returning twice as many records. - * Note that two records are equal if their id values match. - */ - for (int i = 0; i < n; i++) { - Vector2D position = new Vector2D(randomGenerator.nextDouble() * 100, randomGenerator.nextDouble() * 100); - double radius = randomGenerator.nextDouble() * 10; - Record record = new Record(i, position, radius); - tree.add(record.position.toArray(), record.radius, record); - } - - assertEquals(records.size() * 2, tree.getAll().size()); - - expectedRecords = records.stream().collect(Collectors.toCollection(LinkedHashSet::new)); - actualRecords = tree.getAll().stream().collect(Collectors.toCollection(LinkedHashSet::new)); - - assertEquals(expectedRecords, actualRecords); - - } - - /** - * Tests {@link VolumetricDimensionTree#contains(Object) - */ - @Test - @UnitTestMethod(name = "contains", args = { Object.class }) - public void testContains() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7101516806363352895L); - - VolumetricDimensionTree tree = // - VolumetricDimensionTree .builder()// - .setLowerBounds(new double[] { 0, 0 })// - .setUpperBounds(new double[] { 100, 100 })// - .build(); // - - List records = new ArrayList<>(); - - int n = 1000; - for (int i = 0; i < n; i++) { - Vector2D position = new Vector2D(randomGenerator.nextDouble() * 100, randomGenerator.nextDouble() * 100); - double radius = randomGenerator.nextDouble() * 10; - Record record = new Record(i, position, radius); - records.add(record); - } - - for (int i = 0; i < records.size(); i++) { - Record record = records.get(i); - if (i % 2 == 0) { - tree.add(record.position.toArray(), record.radius, record); - } - } - - for (int i = 0; i < records.size(); i++) { - Record record = records.get(i); - boolean contained = tree.contains(record); - assertEquals(i % 2 == 0, contained); - } - } - - /** - * Tests {@link VolumetricDimensionTree#remove(Object) - * - */ - @Test - @UnitTestMethod(name = "remove", args = { Object.class }) - public void testRemove_Object() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5635646758825222227L); - - VolumetricDimensionTree tree = // - VolumetricDimensionTree .builder()// - .setLowerBounds(new double[] { 0, 0 })// - .setUpperBounds(new double[] { 100, 100 })// - .build(); // - - List records = new ArrayList<>(); - - int n = 1000; - for (int i = 0; i < n; i++) { - Vector2D position = new Vector2D(randomGenerator.nextDouble() * 100, randomGenerator.nextDouble() * 100); - double radius = randomGenerator.nextDouble() * 10; - Record record = new Record(i, position, radius); - records.add(record); - } - - for (int i = 0; i < records.size(); i++) { - Record record = records.get(i); - if (i % 2 == 0) { - tree.add(record.position.toArray(), record.radius, record); - } - } - - for (int i = 0; i < records.size(); i++) { - Record record = records.get(i); - boolean removed = tree.remove(record); - assertEquals(i % 2 == 0, removed); - } - - } - - /** - * Tests {@link VolumetricDimensionTree#remove(double, Object) - */ - @Test - @UnitTestMethod(name = "remove", args = { double.class, Object.class }) - public void testRemove() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4986556383522453940L); - - VolumetricDimensionTree tree = // - VolumetricDimensionTree .builder()// - .setLowerBounds(new double[] { 0, 0 })// - .setUpperBounds(new double[] { 100, 100 })// - .build(); // - - List records = new ArrayList<>(); - - int n = 1000; - for (int i = 0; i < n; i++) { - Vector2D position = new Vector2D(randomGenerator.nextDouble() * 100, randomGenerator.nextDouble() * 100); - double radius = randomGenerator.nextDouble() * 10; - Record record = new Record(i, position, radius); - records.add(record); - } - - for (int i = 0; i < records.size(); i++) { - Record record = records.get(i); - if (i % 2 == 0) { - tree.add(record.position.toArray(), record.radius, record); - } - } - - for (int i = 0; i < records.size(); i++) { - Record record = records.get(i); - boolean removed = tree.remove(record.radius, record); - assertEquals(i % 2 == 0, removed); - } - - } - - /** - * Tests {@link VolumetricDimensionTree#builder()} - */ - @Test - @UnitTestMethod(name = "builder", args = {}) - public void testBuilder() { - /* - * Precondition tests - */ - - // first show that the following arguments to the builder form a tree. - VolumetricDimensionTree tree = VolumetricDimensionTree .builder()// - .setLowerBounds(new double[] { 0, 0 })// - .setUpperBounds(new double[] { 100, 100 })// - .setFastRemovals(true)// - .setLeafSize(50)// - .build(); // - assertNotNull(tree); - - // if the selected leaf size is not positive - assertThrows(RuntimeException.class, () -> { - VolumetricDimensionTree .builder()// - .setLowerBounds(new double[] { 0, 0 })// - .setUpperBounds(new double[] { 100, 100 })// - .setFastRemovals(true)// - .setLeafSize(-50)// - .build(); // - }); - - // if the lower bounds were not contributed or were null - assertThrows(RuntimeException.class, () -> { - VolumetricDimensionTree .builder()// - // .setLowerBounds(new double[] { 0, 0 })// - .setUpperBounds(new double[] { 100, 100 })// - .setFastRemovals(true)// - .setLeafSize(50)// - .build(); // - }); - - assertThrows(RuntimeException.class, () -> { - VolumetricDimensionTree .builder()// - .setLowerBounds(null)// - .setUpperBounds(new double[] { 100, 100 })// - .setFastRemovals(true)// - .setLeafSize(50)// - .build(); // - }); - - // if the upper bounds were not contributed or were null - assertThrows(RuntimeException.class, () -> { - VolumetricDimensionTree .builder()// - .setLowerBounds(new double[] { 0, 0 })// - // .setUpperBounds(new double[] { 100, 100 - // })// - .setFastRemovals(true)// - .setLeafSize(50)// - .build(); // - }); - - assertThrows(RuntimeException.class, () -> { - VolumetricDimensionTree .builder()// - .setLowerBounds(new double[] { 0, 0 })// - .setUpperBounds(null)// - .setFastRemovals(true)// - .setLeafSize(50)// - .build(); // - }); - - // if the lower and upper bounds do not match in length - assertThrows(RuntimeException.class, () -> { - VolumetricDimensionTree .builder()// - .setLowerBounds(new double[] { 0, 0 })// - .setUpperBounds(new double[] { 100, 100, 100 })// - .setFastRemovals(true)// - .setLeafSize(50)// - .build(); // - }); - - assertThrows(RuntimeException.class, () -> { - VolumetricDimensionTree .builder()// - .setLowerBounds(new double[] { 0, 0, 0 })// - .setUpperBounds(new double[] { 100, 100 })// - .setFastRemovals(true)// - .setLeafSize(50)// - .build(); // - }); - - // if any of the lower bounds exceed the corresponding - assertThrows(RuntimeException.class, () -> { - VolumetricDimensionTree .builder()// - .setLowerBounds(new double[] { 101, 0 })// - .setUpperBounds(new double[] { 100, 100 })// - .setFastRemovals(true)// - .setLeafSize(50)// - .build(); // - }); - - assertThrows(RuntimeException.class, () -> { - VolumetricDimensionTree .builder()// - .setLowerBounds(new double[] { 0, 101 })// - .setUpperBounds(new double[] { 100, 100 })// - .setFastRemovals(true)// - .setLeafSize(50)// - .build(); // - }); - - } - -} diff --git a/gcm3/src/test/java/util/earth/AT_Earth.java b/gcm3/src/test/java/util/earth/AT_Earth.java deleted file mode 100644 index d1c66c354..000000000 --- a/gcm3/src/test/java/util/earth/AT_Earth.java +++ /dev/null @@ -1,420 +0,0 @@ -package util.earth; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.LinkedHashMap; -import java.util.Map; - -import org.apache.commons.math3.random.RandomGenerator; -import org.apache.commons.math3.util.FastMath; -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.random.RandomGeneratorProvider; -import util.vector.Vector3D; - -/** - * Test class for {@link Earth} - * - * @author Shawn Hatch - * - */ -@UnitTest(target = Earth.class) -public class AT_Earth { - - - private static final double TOLERANCE = 0.0001; - - /** - * Tests {@linkplain Earth#fromLatitude(double) - */ - @Test - @UnitTestMethod(name = "fromLatitude", args = { double.class }) - public void testFromLatitude() { - for (int i = -89; i < 90; i++) { - double latitude = i; - Earth earth = Earth.fromLatitude(latitude); - assertEquals(Earth.getEffectiveEarthRadius(latitude), earth.getRadius(), 0); - } - } - - /** - * Tests {@linkplain Earth#fromRadius(double) - */ - @Test - @UnitTestMethod(name = "fromRadius", args = { double.class }) - public void testFromRadius() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3373674409757874366L); - for (int i = 0; i < 100; i++) { - double radius = 10_000_000 * randomGenerator.nextDouble(); - Earth earth = Earth.fromRadius(radius); - assertEquals(radius, earth.getRadius(), 0); - } - } - - /** - * Tests {@linkplain Earth#fromMeanRadius() - */ - @Test - @UnitTestMethod(name = "fromMeanRadius", args = {}) - public void testFromMeanRadius() { - Earth earth = Earth.fromMeanRadius(); - assertEquals(Earth.WGS84_MEAN_RADIUS_METERS, earth.getRadius(), 0); - } - - /** - * Tests {@linkplain Earth#getEffectiveEarthRadius(double) - */ - @Test - @UnitTestMethod(name = "getEffectiveEarthRadius", args = { double.class }) - public void testGetEffectiveEarthRadius() { - Map expectedValues = new LinkedHashMap<>(); - - /* - * This is a set of value calculated in a spreadsheet - */ - expectedValues.put(-90.0, 6356752.314245); - expectedValues.put(-89.0, 6356758.79502017); - expectedValues.put(-88.0, 6356778.22956872); - expectedValues.put(-87.0, 6356810.59456859); - expectedValues.put(-86.0, 6356855.8511794); - expectedValues.put(-85.0, 6356913.94508685); - expectedValues.put(-84.0, 6356984.80656496); - expectedValues.put(-83.0, 6357068.35055594); - expectedValues.put(-82.0, 6357164.47676767); - expectedValues.put(-81.0, 6357273.06978866); - expectedValues.put(-80.0, 6357393.99922046); - expectedValues.put(-79.0, 6357527.11982738); - expectedValues.put(-78.0, 6357672.27170322); - expectedValues.put(-77.0, 6357829.28045518); - expectedValues.put(-76.0, 6357997.95740442); - expectedValues.put(-75.0, 6358178.09980335); - expectedValues.put(-74.0, 6358369.49106933); - expectedValues.put(-73.0, 6358571.90103454); - expectedValues.put(-72.0, 6358785.08621185); - expectedValues.put(-71.0, 6359008.79007641); - expectedValues.put(-70.0, 6359242.74336262); - expectedValues.put(-69.0, 6359486.66437636); - expectedValues.put(-68.0, 6359740.259322); - expectedValues.put(-67.0, 6360003.2226439); - expectedValues.put(-66.0, 6360275.23738223); - expectedValues.put(-65.0, 6360555.97554252); - expectedValues.put(-64.0, 6360845.0984787); - expectedValues.put(-63.0, 6361142.25728927); - expectedValues.put(-62.0, 6361447.09322607); - expectedValues.put(-61.0, 6361759.23811542); - expectedValues.put(-60.0, 6362078.31479093); - expectedValues.put(-59.0, 6362403.93753785); - expectedValues.put(-58.0, 6362735.7125482); - expectedValues.put(-57.0, 6363073.23838636); - expectedValues.put(-56.0, 6363416.10646463); - expectedValues.put(-55.0, 6363763.90152812); - expectedValues.put(-54.0, 6364116.20214861); - expectedValues.put(-53.0, 6364472.58122664); - expectedValues.put(-52.0, 6364832.60650153); - expectedValues.put(-51.0, 6365195.84106847); - expectedValues.put(-50.0, 6365561.84390236); - expectedValues.put(-49.0, 6365930.17038759); - expectedValues.put(-48.0, 6366300.37285333); - expectedValues.put(-47.0, 6366672.00111357); - expectedValues.put(-46.0, 6367044.60301136); - expectedValues.put(-45.0, 6367417.72496659); - expectedValues.put(-44.0, 6367790.91252667); - expectedValues.put(-43.0, 6368163.71091936); - expectedValues.put(-42.0, 6368535.66560729); - expectedValues.put(-41.0, 6368906.32284322); - expectedValues.put(-40.0, 6369275.23022561); - expectedValues.put(-39.0, 6369641.93725361); - expectedValues.put(-38.0, 6370005.99588093); - expectedValues.put(-37.0, 6370366.96106781); - expectedValues.put(-36.0, 6370724.3913304); - expectedValues.put(-35.0, 6371077.84928683); - expectedValues.put(-34.0, 6371426.90219937); - expectedValues.put(-33.0, 6371771.12251176); - expectedValues.put(-32.0, 6372110.08838128); - expectedValues.put(-31.0, 6372443.38420466); - expectedValues.put(-30.0, 6372770.60113715); - expectedValues.put(-29.0, 6373091.33760414); - expectedValues.put(-28.0, 6373405.19980463); - expectedValues.put(-27.0, 6373711.80220578); - expectedValues.put(-26.0, 6374010.76802791); - expectedValues.put(-25.0, 6374301.72971937); - expectedValues.put(-24.0, 6374584.32942045); - expectedValues.put(-23.0, 6374858.21941589); - expectedValues.put(-22.0, 6375123.06257517); - expectedValues.put(-21.0, 6375378.53278006); - expectedValues.put(-20.0, 6375624.31533886); - expectedValues.put(-19.0, 6375860.10738663); - expectedValues.put(-18.0, 6376085.61827093); - expectedValues.put(-17.0, 6376300.56992252); - expectedValues.put(-16.0, 6376504.69721036); - expectedValues.put(-15.0, 6376697.74828063); - expectedValues.put(-14.0, 6376879.48487901); - expectedValues.put(-13.0, 6377049.68265598); - expectedValues.put(-12.0, 6377208.13145452); - expectedValues.put(-11.0, 6377354.63557993); - expectedValues.put(-10.0, 6377489.01405124); - expectedValues.put(-9.0, 6377611.100834); - expectedValues.put(-8.0, 6377720.74505387); - expectedValues.put(-7.0, 6377817.81119097); - expectedValues.put(-6.0, 6377902.1792545); - expectedValues.put(-5.0, 6377973.74493746); - expectedValues.put(-4.0, 6378032.41975114); - expectedValues.put(-3.0, 6378078.1311394); - expectedValues.put(-2.0, 6378110.82257225); - expectedValues.put(-1.0, 6378130.45361891); - expectedValues.put(0.0, 6378137.0); - expectedValues.put(1.0, 6378130.45361891); - expectedValues.put(2.0, 6378110.82257225); - expectedValues.put(3.0, 6378078.1311394); - expectedValues.put(4.0, 6378032.41975114); - expectedValues.put(5.0, 6377973.74493746); - expectedValues.put(6.0, 6377902.1792545); - expectedValues.put(7.0, 6377817.81119097); - expectedValues.put(8.0, 6377720.74505387); - expectedValues.put(9.0, 6377611.100834); - expectedValues.put(10.0, 6377489.01405124); - expectedValues.put(11.0, 6377354.63557993); - expectedValues.put(12.0, 6377208.13145452); - expectedValues.put(13.0, 6377049.68265598); - expectedValues.put(14.0, 6376879.48487901); - expectedValues.put(15.0, 6376697.74828063); - expectedValues.put(16.0, 6376504.69721036); - expectedValues.put(17.0, 6376300.56992252); - expectedValues.put(18.0, 6376085.61827093); - expectedValues.put(19.0, 6375860.10738663); - expectedValues.put(20.0, 6375624.31533886); - expectedValues.put(21.0, 6375378.53278006); - expectedValues.put(22.0, 6375123.06257517); - expectedValues.put(23.0, 6374858.21941589); - expectedValues.put(24.0, 6374584.32942045); - expectedValues.put(25.0, 6374301.72971937); - expectedValues.put(26.0, 6374010.76802791); - expectedValues.put(27.0, 6373711.80220578); - expectedValues.put(28.0, 6373405.19980463); - expectedValues.put(29.0, 6373091.33760414); - expectedValues.put(30.0, 6372770.60113715); - expectedValues.put(31.0, 6372443.38420466); - expectedValues.put(32.0, 6372110.08838128); - expectedValues.put(33.0, 6371771.12251176); - expectedValues.put(34.0, 6371426.90219937); - expectedValues.put(35.0, 6371077.84928683); - expectedValues.put(36.0, 6370724.3913304); - expectedValues.put(37.0, 6370366.96106781); - expectedValues.put(38.0, 6370005.99588093); - expectedValues.put(39.0, 6369641.93725361); - expectedValues.put(40.0, 6369275.23022561); - expectedValues.put(41.0, 6368906.32284322); - expectedValues.put(42.0, 6368535.66560729); - expectedValues.put(43.0, 6368163.71091936); - expectedValues.put(44.0, 6367790.91252667); - expectedValues.put(45.0, 6367417.72496659); - expectedValues.put(46.0, 6367044.60301136); - expectedValues.put(47.0, 6366672.00111357); - expectedValues.put(48.0, 6366300.37285333); - expectedValues.put(49.0, 6365930.17038759); - expectedValues.put(50.0, 6365561.84390236); - expectedValues.put(51.0, 6365195.84106847); - expectedValues.put(52.0, 6364832.60650153); - expectedValues.put(53.0, 6364472.58122664); - expectedValues.put(54.0, 6364116.20214861); - expectedValues.put(55.0, 6363763.90152812); - expectedValues.put(56.0, 6363416.10646463); - expectedValues.put(57.0, 6363073.23838636); - expectedValues.put(58.0, 6362735.7125482); - expectedValues.put(59.0, 6362403.93753785); - expectedValues.put(60.0, 6362078.31479093); - expectedValues.put(61.0, 6361759.23811542); - expectedValues.put(62.0, 6361447.09322607); - expectedValues.put(63.0, 6361142.25728927); - expectedValues.put(64.0, 6360845.0984787); - expectedValues.put(65.0, 6360555.97554252); - expectedValues.put(66.0, 6360275.23738223); - expectedValues.put(67.0, 6360003.2226439); - expectedValues.put(68.0, 6359740.259322); - expectedValues.put(69.0, 6359486.66437636); - expectedValues.put(70.0, 6359242.74336262); - expectedValues.put(71.0, 6359008.79007641); - expectedValues.put(72.0, 6358785.08621185); - expectedValues.put(73.0, 6358571.90103454); - expectedValues.put(74.0, 6358369.49106933); - expectedValues.put(75.0, 6358178.09980335); - expectedValues.put(76.0, 6357997.95740442); - expectedValues.put(77.0, 6357829.28045518); - expectedValues.put(78.0, 6357672.27170322); - expectedValues.put(79.0, 6357527.11982738); - expectedValues.put(80.0, 6357393.99922046); - expectedValues.put(81.0, 6357273.06978866); - expectedValues.put(82.0, 6357164.47676767); - expectedValues.put(83.0, 6357068.35055594); - expectedValues.put(84.0, 6356984.80656496); - expectedValues.put(85.0, 6356913.94508685); - expectedValues.put(86.0, 6356855.8511794); - expectedValues.put(87.0, 6356810.59456859); - expectedValues.put(88.0, 6356778.22956872); - expectedValues.put(89.0, 6356758.79502017); - expectedValues.put(90.0, 6356752.314245); - - for (Double lat : expectedValues.keySet()) { - Double expectedRadius = expectedValues.get(lat); - assertEquals(expectedRadius, Earth.getEffectiveEarthRadius(lat), TOLERANCE); - } - } - - /** - * Tests {@linkplain Earth#getRadius() - */ - @Test - @UnitTestMethod(name = "getRadius", args = {}) - public void testGetRadius() { - // covered by testFromRadius - } - - /** - * Tests {@linkplain Earth#getECCFromLatLonAlt(LatLonAlt) - */ - @Test - @UnitTestMethod(name = "getECCFromLatLonAlt", args = { LatLonAlt.class }) - public void testGetECCFromLatLonAlt() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7867550291868680129L); - - for (int i = 0; i < 1000; i++) { - double radius = randomGenerator.nextDouble() * 6_000_000 + 1_000_000; - Earth earth = Earth.fromRadius(radius); - - double lat = randomGenerator.nextDouble() * 180 - 90; - double lon = randomGenerator.nextDouble() * 360 - 180; - double alt = randomGenerator.nextDouble() * 100_000; - - double coslat = FastMath.cos(FastMath.toRadians(lat)); - double coslon = FastMath.cos(FastMath.toRadians(lon)); - double sinlat = FastMath.sin(FastMath.toRadians(lat)); - double sinlon = FastMath.sin(FastMath.toRadians(lon)); - double distance = radius + alt; - Vector3D v = new Vector3D(coslat * coslon, coslat * sinlon, sinlat).scale(distance); - - LatLonAlt latLonAlt = new LatLonAlt(lat, lon, alt); - - Vector3D ecc = earth.getECCFromLatLonAlt(latLonAlt); - assertEquals(v.getX(), ecc.getX(), TOLERANCE); - assertEquals(v.getY(), ecc.getY(), TOLERANCE); - assertEquals(v.getZ(), ecc.getZ(), TOLERANCE); - } - - } - - /** - * Tests {@linkplain Earth#getLatLonAlt(Vector3D) - */ - @Test - @UnitTestMethod(name = "getLatLonAlt", args = { Vector3D.class }) - public void testGetLatLonAlt() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1462458115304058705L); - - for (int i = 0; i < 1000; i++) { - double radius = randomGenerator.nextDouble() * 6_000_000 + 1_000_000; - Earth earth = Earth.fromRadius(radius); - - double lat = randomGenerator.nextDouble() * 180 - 90; - double lon = randomGenerator.nextDouble() * 360 - 180; - double alt = randomGenerator.nextDouble() * 100_000; - - LatLonAlt expectedLatLonAlt = new LatLonAlt(lat, lon, alt); - Vector3D ecc = earth.getECCFromLatLonAlt(expectedLatLonAlt); - - LatLonAlt actualLatLonAlt = earth.getLatLonAlt(ecc); - - assertEquals(expectedLatLonAlt.getLatitude(), actualLatLonAlt.getLatitude(), TOLERANCE); - assertEquals(expectedLatLonAlt.getLongitude(), actualLatLonAlt.getLongitude(), TOLERANCE); - assertEquals(expectedLatLonAlt.getAltitude(), actualLatLonAlt.getAltitude(), TOLERANCE); - } - - } - - /** - * Tests {@linkplain Earth#getECCFromLatLon(LatLon)) - */ - @Test - @UnitTestMethod(name = "getECCFromLatLon", args = { LatLon.class }) - public void testGetECCFromLatLon() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4107285998527165778L); - - for (int i = 0; i < 1000; i++) { - double radius = randomGenerator.nextDouble() * 6_000_000 + 1_000_000; - Earth earth = Earth.fromRadius(radius); - - double lat = randomGenerator.nextDouble() * 180 - 90; - double lon = randomGenerator.nextDouble() * 360 - 180; - - double coslat = FastMath.cos(FastMath.toRadians(lat)); - double coslon = FastMath.cos(FastMath.toRadians(lon)); - double sinlat = FastMath.sin(FastMath.toRadians(lat)); - double sinlon = FastMath.sin(FastMath.toRadians(lon)); - - Vector3D v = new Vector3D(coslat * coslon, coslat * sinlon, sinlat).scale(radius); - - LatLon latLon = new LatLon(lat, lon); - - Vector3D ecc = earth.getECCFromLatLon(latLon); - assertEquals(v.getX(), ecc.getX(), TOLERANCE); - assertEquals(v.getY(), ecc.getY(), TOLERANCE); - assertEquals(v.getZ(), ecc.getZ(), TOLERANCE); - } - } - - /** - * Tests {@linkplain Earth#getGroundDistanceFromECC(Vector3D, Vector3D)) - */ - @Test - @UnitTestMethod(name = "getGroundDistanceFromECC", args = { Vector3D.class, Vector3D.class }) - public void testGetGroundDistanceFromECC() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1693567521780632468L); - - for (int i = 0; i < 1000; i++) { - double radius = randomGenerator.nextDouble() * 6_000_000 + 1_000_000; - Earth earth = Earth.fromRadius(radius); - - double lat1 = randomGenerator.nextDouble() * 180 - 90; - double lon1 = randomGenerator.nextDouble() * 360 - 180; - - Vector3D ecc1 = earth.getECCFromLatLon(new LatLon(lat1, lon1)); - - double lat2 = randomGenerator.nextDouble() * 180 - 90; - double lon2 = randomGenerator.nextDouble() * 360 - 180; - - Vector3D ecc2 = earth.getECCFromLatLon(new LatLon(lat2, lon2)); - - double expectedGroundDistance = ecc1.angle(ecc2) * earth.getRadius(); - double actualGroundDistance = earth.getGroundDistanceFromECC(ecc1, ecc2); - - assertEquals(expectedGroundDistance, actualGroundDistance, 0); - - } - } - - /** - * Tests {@linkplain Earth#getGroundDistanceFromLatLon(LatLon, LatLon)) - */ - @Test - @UnitTestMethod(name = "getGroundDistanceFromLatLon", args = { LatLon.class, LatLon.class }) - public void testGetGroundDistanceFromLatLon() { - - // covered by testGetGroundDistanceFromECC() - } - - /** - * Tests {@linkplain Earth#getGroundDistanceFromLatLonAlt(LatLonAlt, - * LatLonAlt)) - */ - @Test - @UnitTestMethod(name = "getGroundDistanceFromLatLonAlt", args = { LatLonAlt.class, LatLonAlt.class }) - public void testGetGroundDistanceFromLatLonAlt() { - - // covered by testGetGroundDistanceFromECC() - } -} diff --git a/gcm3/src/test/java/util/earth/AT_LatLon.java b/gcm3/src/test/java/util/earth/AT_LatLon.java deleted file mode 100644 index 4412bd459..000000000 --- a/gcm3/src/test/java/util/earth/AT_LatLon.java +++ /dev/null @@ -1,174 +0,0 @@ -package util.earth; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.random.RandomGeneratorProvider; - -/** - * Test class for {@link LatLon} - * - * @author Shawn Hatch - * - */ - -@UnitTest(target = LatLon.class) -public class AT_LatLon { - - private static final double TOLERANCE = 0.0001; - - - /** - * Tests {@link LatLon#getLatitude()} - */ - @Test - @UnitTestMethod(name = "getLatitude", args = {}) - public void testGetLatitude() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(50223838619731639L); - for (int i = 0; i < 100; i++) { - double latitude = randomGenerator.nextDouble() * 180 - 90; - double longitude = 35; - LatLon latLon = new LatLon(latitude, longitude); - assertEquals(latitude, latLon.getLatitude(), TOLERANCE); - } - } - - /** - * Tests {@link LatLon#getLongitude()} - */ - - @Test - @UnitTestMethod(name = "getLongitude", args = {}) - public void testGetLongitude() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(62285879847543313L); - for (int i = 0; i < 100; i++) { - double latitude = 35; - double longitude = randomGenerator.nextDouble() * 360 - 180; - LatLon latLon = new LatLon(latitude, longitude); - assertEquals(longitude, latLon.getLongitude(), TOLERANCE); - } - } - - /** - * Tests {@link LatLon#toString()} - */ - @Test - @UnitTestMethod(name = "toString", args = {}) - public void testToString() { - LatLon latLon = new LatLon(35, 128); - assertEquals("LatLon [latitude=35.0, longitude=128.0]", latLon.toString()); - - latLon = new LatLon(-35, 128); - assertEquals("LatLon [latitude=-35.0, longitude=128.0]", latLon.toString()); - - latLon = new LatLon(35, -128); - assertEquals("LatLon [latitude=35.0, longitude=-128.0]", latLon.toString()); - - latLon = new LatLon(-35, -128); - assertEquals("LatLon [latitude=-35.0, longitude=-128.0]", latLon.toString()); - } - - /** - * Tests {@link LatLon#hashCode()} - */ - @Test - @UnitTestMethod(name = "hashCode", args = {}) - public void testHashCode() { - - // Show equal objects have equal hash codes - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2874707211911558639L); - for (int i = 0; i < 100; i++) { - double latitude = randomGenerator.nextDouble() * 180 - 90; - double longitude = randomGenerator.nextDouble() * 360 - 180; - LatLon latLon1 = new LatLon(latitude, longitude); - LatLon latLon2 = new LatLon(latitude, longitude); - assertEquals(latLon1.hashCode(), latLon2.hashCode()); - } - - } - - /** - * Tests {@link LatLon#equals(Object)} - */ - @Test - @UnitTestMethod(name = "equals", args = { Object.class }) - public void testEquals() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7754088364991626671L); - for (int i = 0; i < 100; i++) { - double latitude = randomGenerator.nextDouble() * 180 - 90; - double longitude = randomGenerator.nextDouble() * 360 - 180; - LatLon latLon1 = new LatLon(latitude, longitude); - LatLon latLon2 = new LatLon(latitude, longitude); - LatLon latLon3 = new LatLon(latitude, longitude); - - // reflexive - assertEquals(latLon2, latLon1); - - // associative - assertEquals(latLon1, latLon2); - assertEquals(latLon2, latLon1); - - // transitive - assertEquals(latLon1, latLon3); - assertEquals(latLon2, latLon3); - } - } - - /** - * Tests {@link LatLon#LatLon(double, double)} - */ - @Test - @UnitTestConstructor(args = { double.class, double.class }) - public void testConstructor_Doubles() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4045297957712951231L); - for (int i = 0; i < 100; i++) { - double latitude = randomGenerator.nextDouble() * 180 - 90; - double longitude = randomGenerator.nextDouble() * 360 - 180; - LatLon latLon = new LatLon(latitude, longitude); - assertEquals(latitude, latLon.getLatitude(), TOLERANCE); - assertEquals(longitude, latLon.getLongitude(), TOLERANCE); - } - - // pre-condition tests - - assertThrows(RuntimeException.class, () -> new LatLon(-91, 0)); - - assertThrows(RuntimeException.class, () -> new LatLon(91, 0)); - - assertThrows(RuntimeException.class, () -> new LatLon(0, 181)); - - assertThrows(RuntimeException.class, () -> new LatLon(0, -181)); - - } - - /** - * Tests {@link LatLon#LatLon(LatLonAlt)} - */ - @Test - @UnitTestConstructor(args = { LatLonAlt.class }) - public void testConstructor_LatLonAlt() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5811539292023379121L); - for (int i = 0; i < 100; i++) { - double latitude = randomGenerator.nextDouble() * 180 - 90; - double longitude = randomGenerator.nextDouble() * 360 - 180; - double altitude = randomGenerator.nextDouble() * 10000 - 5000; - LatLonAlt latLonAlt = new LatLonAlt(latitude, longitude, altitude); - LatLon latLon = new LatLon(latLonAlt); - assertEquals(latitude, latLon.getLatitude(), TOLERANCE); - assertEquals(longitude, latLon.getLongitude(), TOLERANCE); - } - - // pre-condition tests - assertThrows(RuntimeException.class, () -> new LatLon(null)); - - } - -} diff --git a/gcm3/src/test/java/util/earth/AT_LatLonAlt.java b/gcm3/src/test/java/util/earth/AT_LatLonAlt.java deleted file mode 100644 index 6a7d0d578..000000000 --- a/gcm3/src/test/java/util/earth/AT_LatLonAlt.java +++ /dev/null @@ -1,270 +0,0 @@ -package util.earth; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.random.RandomGeneratorProvider; -import util.vector.Vector3D; - -/** - * Test class for {@link LatLonAlt} - * - * @author Shawn Hatch - * - */ -@UnitTest(target = LatLonAlt.class) -public class AT_LatLonAlt { - - private static final double TOLERANCE = 0.0001; - - - - /** - * Tests {@link LatLonAlt#getLatitude()} - */ - @Test - @UnitTestMethod(name = "getLatitude", args = {}) - public void testGetLatitude() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7062845400521947521L); - for (int i = 0; i < 100; i++) { - double latitude = randomGenerator.nextDouble() * 180 - 90; - double longitude = 35; - double altitude = 1000; - LatLonAlt latLonAlt = new LatLonAlt(latitude, longitude, altitude); - assertEquals(latitude, latLonAlt.getLatitude(), TOLERANCE); - } - } - - /** - * Tests {@link LatLonAlt#getLongitude()} - */ - @Test - @UnitTestMethod(name = "getLongitude", args = {}) - public void testGetLongitude() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(9178151003715988391L); - for (int i = 0; i < 100; i++) { - double latitude = 35; - double longitude = randomGenerator.nextDouble() * 360 - 180; - double altitude = 1000; - LatLonAlt latLonAlt = new LatLonAlt(latitude, longitude, altitude); - assertEquals(longitude, latLonAlt.getLongitude(), TOLERANCE); - } - } - - /** - * Tests {@link LatLonAlt#getAltitude()} - */ - @Test - @UnitTestMethod(name = "getAltitude", args = {}) - public void testGetAltitude() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3210941165573781662L); - for (int i = 0; i < 100; i++) { - double latitude = 35; - double longitude = 128; - double altitude = randomGenerator.nextDouble() * 10000 - 5000; - LatLonAlt latLonAlt = new LatLonAlt(latitude, longitude, altitude); - assertEquals(altitude, latLonAlt.getAltitude(), TOLERANCE); - } - } - - /** - * Tests {@link LatLonAlt#toString()} - */ - @Test - @UnitTestMethod(name = "toString", args = {}) - public void testToString() { - LatLonAlt latLonAlt = new LatLonAlt(35, 128, 1000); - assertEquals("LatLonAlt [latitude=35.0, longitude=128.0, altitude=1000.0]", latLonAlt.toString()); - - latLonAlt = new LatLonAlt(-35, 128, 1000); - assertEquals("LatLonAlt [latitude=-35.0, longitude=128.0, altitude=1000.0]", latLonAlt.toString()); - - latLonAlt = new LatLonAlt(35, -128, 1000); - assertEquals("LatLonAlt [latitude=35.0, longitude=-128.0, altitude=1000.0]", latLonAlt.toString()); - - latLonAlt = new LatLonAlt(-35, -128, 1000); - assertEquals("LatLonAlt [latitude=-35.0, longitude=-128.0, altitude=1000.0]", latLonAlt.toString()); - - latLonAlt = new LatLonAlt(35, 128, -1000); - assertEquals("LatLonAlt [latitude=35.0, longitude=128.0, altitude=-1000.0]", latLonAlt.toString()); - - latLonAlt = new LatLonAlt(-35, 128, -1000); - assertEquals("LatLonAlt [latitude=-35.0, longitude=128.0, altitude=-1000.0]", latLonAlt.toString()); - - latLonAlt = new LatLonAlt(35, -128, -1000); - assertEquals("LatLonAlt [latitude=35.0, longitude=-128.0, altitude=-1000.0]", latLonAlt.toString()); - - latLonAlt = new LatLonAlt(-35, -128, -1000); - assertEquals("LatLonAlt [latitude=-35.0, longitude=-128.0, altitude=-1000.0]", latLonAlt.toString()); - } - - /** - * Tests {@link LatLonAlt#hashCode()} - */ - @Test - @UnitTestMethod(name = "hashCode", args = {}) - public void testHashCode() { - - // Show equal objects have equal hash codes - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2791517325027305404L); - for (int i = 0; i < 100; i++) { - double latitude = randomGenerator.nextDouble() * 180 - 90; - double longitude = randomGenerator.nextDouble() * 360 - 180; - double altitude = randomGenerator.nextDouble() * 10000 - 5000; - LatLonAlt latLonAlt1 = new LatLonAlt(latitude, longitude, altitude); - LatLonAlt latLonAlt2 = new LatLonAlt(latitude, longitude, altitude); - assertEquals(latLonAlt1.hashCode(), latLonAlt2.hashCode()); - } - - } - - /** - * Tests {@link LatLonAlt#equals(Object)} - */ - @Test - @UnitTestMethod(name = "equals", args = { Object.class }) - public void testEquals() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3854552470387902715L); - for (int i = 0; i < 100; i++) { - double latitude = randomGenerator.nextDouble() * 180 - 90; - double longitude = randomGenerator.nextDouble() * 360 - 180; - double altitude = randomGenerator.nextDouble() * 10000 - 5000; - LatLonAlt latLonAlt1 = new LatLonAlt(latitude, longitude, altitude); - LatLonAlt latLonAlt2 = new LatLonAlt(latitude, longitude, altitude); - LatLonAlt latLonAlt3 = new LatLonAlt(latitude, longitude, altitude); - - // reflexive - assertEquals(latLonAlt1, latLonAlt1); - - // associative - assertEquals(latLonAlt1, latLonAlt2); - assertEquals(latLonAlt2, latLonAlt1); - - // transitive - assertEquals(latLonAlt1, latLonAlt3); - assertEquals(latLonAlt2, latLonAlt3); - } - } - - /** - * Tests {@link LatLonAlt#LatLonAlt(LatLon)} - */ - @Test - @UnitTestConstructor(args = { LatLon.class }) - public void testConstructor_LatLon() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7719374094024568257L); - for (int i = 0; i < 100; i++) { - double latitude = randomGenerator.nextDouble() * 180 - 90; - double longitude = randomGenerator.nextDouble() * 360 - 180; - - LatLon latLon = new LatLon(latitude, longitude); - LatLonAlt latLonAlt = new LatLonAlt(latLon); - - assertEquals(latitude, latLonAlt.getLatitude(), TOLERANCE); - assertEquals(longitude, latLonAlt.getLongitude(), TOLERANCE); - assertEquals(0, latLonAlt.getAltitude(), 0); - } - - // pre-condition tests - assertThrows(RuntimeException.class, () -> { - Vector3D v = null; - new LatLonAlt(v); - }); - - } - - /** - * Tests {@link LatLonAlt#LatLonAlt(LatLon)} - * - * Tests {@link LatLonAlt#LatLonAlt(Vector3D)} - * - * Tests {@link LatLonAlt#LatLonAlt(double, double, double)} - */ - @Test - @UnitTestConstructor(args = { Vector3D.class }) - public void testConstructor_Vector3D() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1423864170984280158L); - for (int i = 0; i < 100; i++) { - double latitude = randomGenerator.nextDouble() * 180 - 90; - double longitude = randomGenerator.nextDouble() * 360 - 180; - double altitude = randomGenerator.nextDouble() * 10000 - 5000; - - Vector3D v = new Vector3D(latitude, longitude, altitude); - LatLonAlt latLonAlt = new LatLonAlt(v); - - assertEquals(latitude, latLonAlt.getLatitude(), TOLERANCE); - assertEquals(longitude, latLonAlt.getLongitude(), TOLERANCE); - assertEquals(altitude, latLonAlt.getAltitude(), TOLERANCE); - } - - // pre-condition tests - assertThrows(RuntimeException.class, () -> { - Vector3D v = null; - new LatLonAlt(v); - }); - - assertThrows(RuntimeException.class, () -> new LatLonAlt(new Vector3D(-91, 0, 1000))); - - assertThrows(RuntimeException.class, () -> new LatLonAlt(new Vector3D(91, 0, 1000))); - - assertThrows(RuntimeException.class, () -> new LatLonAlt(new Vector3D(0, 181, 1000))); - - assertThrows(RuntimeException.class, () -> new LatLonAlt(new Vector3D(0, -181, 1000))); - - } - - /** - * Tests {@link LatLonAlt#LatLonAlt(double, double, double)} - */ - @Test - @UnitTestConstructor(args = { double.class, double.class, double.class }) - public void testConstructor_Doubles() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5395751744049862772L); - for (int i = 0; i < 100; i++) { - double latitude = randomGenerator.nextDouble() * 180 - 90; - double longitude = randomGenerator.nextDouble() * 360 - 180; - double altitude = randomGenerator.nextDouble() * 10000 - 5000; - - LatLonAlt latLonAlt = new LatLonAlt(latitude, longitude, altitude); - assertEquals(latitude, latLonAlt.getLatitude(), TOLERANCE); - assertEquals(longitude, latLonAlt.getLongitude(), TOLERANCE); - assertEquals(altitude, latLonAlt.getAltitude(), TOLERANCE); - - } - - assertThrows(RuntimeException.class, () -> new LatLonAlt(-91, 0, 1000)); - - assertThrows(RuntimeException.class, () -> new LatLonAlt(91, 0, 1000)); - - assertThrows(RuntimeException.class, () -> new LatLonAlt(0, 181, 1000)); - - assertThrows(RuntimeException.class, () -> new LatLonAlt(0, -181, 1000)); - - } - - /** - * Tests {@link LatLonAlt#toVector3D()} - */ - @Test - @UnitTestMethod(name = "toVector3D", args = {}) - public void testToVector3D() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1014093707230518248L); - for (int i = 0; i < 100; i++) { - double latitude = randomGenerator.nextDouble() * 180 - 90; - double longitude = randomGenerator.nextDouble() * 360 - 180; - double altitude = randomGenerator.nextDouble() * 10000 - 5000; - LatLonAlt latLonAlt = new LatLonAlt(latitude, longitude, altitude); - Vector3D v = latLonAlt.toVector3D(); - assertEquals(latLonAlt.getLatitude(), v.getX(), TOLERANCE); - assertEquals(latLonAlt.getLongitude(), v.getY(), TOLERANCE); - assertEquals(latLonAlt.getAltitude(), v.getZ(), TOLERANCE); - } - } - -} diff --git a/gcm3/src/test/java/util/geolocator/AT_GeoLocator.java b/gcm3/src/test/java/util/geolocator/AT_GeoLocator.java deleted file mode 100644 index 42523066f..000000000 --- a/gcm3/src/test/java/util/geolocator/AT_GeoLocator.java +++ /dev/null @@ -1,215 +0,0 @@ -package util.geolocator; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - -import org.apache.commons.math3.random.RandomGenerator; -import org.apache.commons.math3.util.FastMath; -import org.apache.commons.math3.util.Pair; -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.earth.Earth; -import util.earth.LatLon; -import util.earth.LatLonAlt; -import util.geolocator.GeoLocator.Builder; -import util.random.RandomGeneratorProvider; -import util.vector.Vector3D; - -/** - * Test class for {@link GeoLocatorO} - * - * @author Shawn Hatch - * - */ -@UnitTest(target = GeoLocator.class) -public class AT_GeoLocator { - - - - private static LatLon generateRandomizedLatLon(RandomGenerator randomGenerator, double lat, double lon, double radiusKilometers) { - Earth earth = Earth.fromMeanRadius(); - Vector3D center = earth.getECCFromLatLon(new LatLon(lat, lon)); - Vector3D north = new Vector3D(0, 0, 1); - double distance = FastMath.sqrt(randomGenerator.nextDouble()) * radiusKilometers * 1000; - double angle = distance / earth.getRadius(); - double rotationAngle = randomGenerator.nextDouble() * 2 * FastMath.PI; - Vector3D v = center.rotateToward(north, angle).rotateAbout(center, rotationAngle); - LatLonAlt latLonAlt = earth.getLatLonAlt(v); - return new LatLon(latLonAlt); - } - - private static List generateLocations(RandomGenerator randomGenerator, double lat, double lon, double radiusKilometers, int count) { - List result = new ArrayList<>(); - for (int i = 0; i < count; i++) { - LatLon latLon = generateRandomizedLatLon(randomGenerator, lat, lon, radiusKilometers); - - result.add(latLon); - } - return result; - } - - private GeoLocator generateGeoLocator(List locations) { - Builder builder = GeoLocator.builder(); - locations.forEach(location -> builder.addLocation(location.getLatitude(), location.getLongitude(), location)); - return builder.build(); - } - - /** - * Tests {@link GeoLocator#builder()} - */ - @Test - @UnitTestMethod(name = "builder", args = {}) - public void testBuilder() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2026864228657861590L); - List locations = generateLocations(randomGenerator, 35, 128, 50, 100); - generateGeoLocator(locations); - } - - /** - * Tests {@link GeoLocator#getLocations(double, double, double)} - */ - @Test - @UnitTestMethod(name = "getLocations", args = { double.class, double.class, double.class }) - public void testGetLocations() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1240444416174704003L); - - // Generate 100 random locations in a 50 kilometer radius region - double lat = 35; - double lon = 128; - double radiusKilometers = 50; - List locations = generateLocations(randomGenerator, lat, lon, radiusKilometers, 100); - - // Create a GeoLocator from the generated locations - GeoLocator geoLocator = generateGeoLocator(locations); - - int testCount = 100; - - // search random spots in that region with a 10 kilometer search radius - double searchRadiusKilometers = 10; - Earth earth = Earth.fromMeanRadius(); - for (int i = 0; i < testCount; i++) { - LatLon latLon = generateRandomizedLatLon(randomGenerator, lat, lon, radiusKilometers); - - // Determine the expected locations that fall within the search - // radius - Set expectedLocations = locations.stream().filter(location -> { - return earth.getGroundDistanceFromLatLon(latLon, location) <= searchRadiusKilometers * 1000; - }).collect(Collectors.toCollection(LinkedHashSet::new)); - - // Get the locations from the GeoLocator - Set actualLocations = geoLocator.getLocations(latLon.getLatitude(), latLon.getLongitude(), searchRadiusKilometers).stream().collect(Collectors.toCollection(LinkedHashSet::new)); - - // compare the two sets - assertEquals(expectedLocations, actualLocations); - } - } - - /** - * Tests {@link GeoLocator#getNearestLocation(double, double)} - */ - @Test - @UnitTestMethod(name = "getNearestLocation", args = { double.class, double.class }) - public void testGetNearestLocation() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4915853160875930674L); - - // Generate 100 random locations in a 50 kilometer radius region - double lat = 35; - double lon = 128; - double radiusKilometers = 50; - List locations = generateLocations(randomGenerator, lat, lon, radiusKilometers, 100); - - // Create a GeoLocator from the generated locations - GeoLocator geoLocator = generateGeoLocator(locations); - - int testCount = 100; - - // search random spots in that region with a 10 kilometer search radius - - Earth earth = Earth.fromMeanRadius(); - for (int i = 0; i < testCount; i++) { - LatLon latLon = generateRandomizedLatLon(randomGenerator, lat, lon, radiusKilometers); - - // Determine the expected locations that fall within the search - // radius - LatLon expectedLocation = null; - double lowestDistance = Double.POSITIVE_INFINITY; - for (LatLon location : locations) { - double distance = earth.getGroundDistanceFromLatLon(latLon, location); - if (distance < lowestDistance) { - lowestDistance = distance; - expectedLocation = location; - } - } - - // Get the locations from the GeoLocator - Optional actual = geoLocator.getNearestLocation(latLon.getLatitude(), latLon.getLongitude()); - assertTrue(actual.isPresent()); - LatLon actualLocation = actual.get(); - - // compare the two sets - assertEquals(expectedLocation, actualLocation); - } - } - - /** - * Tests {@link GeoLocator#getPrioritizedLocations(double, double, double)} - */ - @Test - @UnitTestMethod(name = "getPrioritizedLocations", args = { double.class, double.class, double.class }) - public void testGetPrioritizedLocations() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3451435198166238489L); - - // Generate 100 random locations in a 50 kilometer radius region - double lat = 35; - double lon = 128; - double radiusKilometers = 50; - List locations = generateLocations(randomGenerator, lat, lon, radiusKilometers, 100); - - // Create a GeoLocator from the generated locations - GeoLocator geoLocator = generateGeoLocator(locations); - - int testCount = 100; - - // search random spots in that region with a 10 kilometer search radius - double searchRadiusKilometers = 10; - Earth earth = Earth.fromMeanRadius(); - for (int i = 0; i < testCount; i++) { - LatLon latLon = generateRandomizedLatLon(randomGenerator, lat, lon, radiusKilometers); - - // Determine the expected locations that fall within the search - // radius - List> expectedLocations = new ArrayList<>(); - for (LatLon location : locations) { - double distance = earth.getGroundDistanceFromLatLon(latLon, location) / 1000; - if (distance <= searchRadiusKilometers) { - Pair pair = new Pair<>(location, distance); - expectedLocations.add(pair); - } - } - - Collections.sort(expectedLocations, new Comparator>() { - @Override - public int compare(Pair pair1, Pair pair2) { - return Double.compare(pair1.getSecond(), pair2.getSecond()); - } - }); - - // Get the locations from the GeoLocator - List> actualLocations = geoLocator.getPrioritizedLocations(latLon.getLatitude(), latLon.getLongitude(), searchRadiusKilometers).stream().collect(Collectors.toCollection(ArrayList::new)); - - // compare the two sets - assertEquals(expectedLocations, actualLocations); - } - } -} diff --git a/gcm3/src/test/java/util/graph/AT_Graph.java b/gcm3/src/test/java/util/graph/AT_Graph.java deleted file mode 100644 index 1342d633f..000000000 --- a/gcm3/src/test/java/util/graph/AT_Graph.java +++ /dev/null @@ -1,734 +0,0 @@ -package util.graph; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -/** - * Test class for {@link Graph} - * - * @author Shawn Hatch - * - */ -@UnitTest(target = Graph.class) -public class AT_Graph { - - private void testConstructionWithGraph() { - Graph.Builder builder1 = Graph.builder(); - - // build a few nodes and edges - builder1.addEdge(45, "A", "B"); - builder1.addEdge(38, "B", "C"); - - // now add another graph - Graph.Builder builder2 = Graph.builder(); - builder2.addNode("J"); - builder2.addNode("K"); - builder2.addNode("L"); - builder2.addEdge(1, "K", "J"); - builder2.addEdge(2, "J", "K"); - Graph graph2 = builder2.build(); - - // add the second graph to the first - builder1.addAll(graph2); - Graph graph1 = builder1.build(); - assertTrue(graph1.containsNode("A")); - assertTrue(graph1.containsNode("B")); - assertTrue(graph1.containsNode("C")); - assertTrue(graph1.containsNode("J")); - assertTrue(graph1.containsNode("K")); - assertTrue(graph1.containsNode("L")); - - assertEquals(6, graph1.nodeCount()); - - assertTrue(graph1.containsEdge(1)); - assertTrue(graph1.containsEdge(2)); - assertTrue(graph1.containsEdge(38)); - assertTrue(graph1.containsEdge(45)); - - assertEquals(4, graph1.edgeCount()); - - assertEquals("K", graph1.getOriginNode(1)); - assertEquals("J", graph1.getDestinationNode(1)); - - assertEquals("J", graph1.getOriginNode(2)); - assertEquals("K", graph1.getDestinationNode(2)); - - assertEquals("A", graph1.getOriginNode(45)); - assertEquals("B", graph1.getDestinationNode(45)); - - assertEquals("B", graph1.getOriginNode(38)); - assertEquals("C", graph1.getDestinationNode(38)); - - } - - private void testConstructionWithMutableGraph() { - Graph.Builder builder1 = Graph.builder(); - - // build a few nodes and edges - builder1.addEdge(45, "A", "B"); - builder1.addEdge(38, "B", "C"); - - // now add another graph - MutableGraph mutableGraph = new MutableGraph<>(); - mutableGraph.addNode("J"); - mutableGraph.addNode("K"); - mutableGraph.addNode("L"); - mutableGraph.addEdge(1, "K", "J"); - mutableGraph.addEdge(2, "J", "K"); - - // add the second graph to the first - builder1.addAll(mutableGraph); - Graph graph1 = builder1.build(); - - assertTrue(graph1.containsNode("A")); - assertTrue(graph1.containsNode("B")); - assertTrue(graph1.containsNode("C")); - assertTrue(graph1.containsNode("J")); - assertTrue(graph1.containsNode("K")); - assertTrue(graph1.containsNode("L")); - - assertEquals(6, graph1.nodeCount()); - - assertTrue(graph1.containsEdge(1)); - assertTrue(graph1.containsEdge(2)); - assertTrue(graph1.containsEdge(38)); - assertTrue(graph1.containsEdge(45)); - - assertEquals(4, graph1.edgeCount()); - - assertEquals("K", graph1.getOriginNode(1)); - assertEquals("J", graph1.getDestinationNode(1)); - - assertEquals("J", graph1.getOriginNode(2)); - assertEquals("K", graph1.getDestinationNode(2)); - - assertEquals("A", graph1.getOriginNode(45)); - assertEquals("B", graph1.getDestinationNode(45)); - - assertEquals("B", graph1.getOriginNode(38)); - assertEquals("C", graph1.getDestinationNode(38)); - - } - - /** - * Tests {@link Graph#builder()} - */ - @Test - @UnitTestMethod(name = "builder", args = {}) - public void testBuilder() { - /* - * We will only test the builder methods for adding an entire Graph or - * MutableGraph. The other builder methods are tested via the remaining - * tests. - */ - testConstructionWithGraph(); - testConstructionWithMutableGraph(); - } - - /** - * Tests {@link Graph#containsEdge(Object)} - */ - @Test - @UnitTestMethod(name = "containsEdge", args = { Object.class }) - public void testContainsEdge() { - Graph.Builder builder = Graph.builder(); - builder.addEdge("A->B", "A", "B"); - builder.addNode("C"); - builder.addEdge("B->C", "B", "C"); - - Graph graph = builder.build(); - - assertTrue(graph.containsEdge("A->B")); - - assertTrue(graph.containsEdge("B->C")); - - assertFalse(graph.containsEdge("A->C")); - } - - /** - * Tests {@link Graph#containsNode(Object)} - */ - @Test - @UnitTestMethod(name = "containsNode", args = { Object.class }) - public void testContainsNode() { - Graph.Builder builder = Graph.builder(); - builder.addNode("A"); - builder.addNode("B"); - builder.addNode("C"); - - Graph graph = builder.build(); - - assertTrue(graph.containsNode("A")); - - assertTrue(graph.containsNode("B")); - - assertTrue(graph.containsNode("C")); - - assertFalse(graph.containsNode("D")); - } - - /** - * Tests {@link Graph#edgeCount(Object, Object)} - */ - @Test - @UnitTestMethod(name = "edgeCount", args = { Object.class, Object.class }) - public void testEdgeCount_Objects() { - Graph.Builder builder = Graph.builder(); - builder.addEdge("E1", "B", "C"); - builder.addEdge("E2", "C", "B"); - builder.addEdge("E3", "B", "C"); - builder.addEdge("E4", "B", "A"); - builder.addEdge("E5", "B", "D"); - builder.addEdge("E6", "A", "B"); - builder.addEdge("E7", "A", "A"); - - Graph graph = builder.build(); - assertEquals(1, graph.edgeCount("A", "A")); - assertEquals(1, graph.edgeCount("A", "B")); - assertEquals(0, graph.edgeCount("A", "C")); - assertEquals(0, graph.edgeCount("A", "D")); - assertEquals(1, graph.edgeCount("B", "A")); - assertEquals(0, graph.edgeCount("B", "B")); - assertEquals(2, graph.edgeCount("B", "C")); - assertEquals(1, graph.edgeCount("B", "D")); - assertEquals(0, graph.edgeCount("C", "A")); - assertEquals(1, graph.edgeCount("C", "B")); - assertEquals(0, graph.edgeCount("C", "C")); - assertEquals(0, graph.edgeCount("C", "D")); - assertEquals(0, graph.edgeCount("D", "A")); - assertEquals(0, graph.edgeCount("D", "B")); - assertEquals(0, graph.edgeCount("D", "C")); - assertEquals(0, graph.edgeCount("D", "D")); - - } - - /** - * Tests {@link Graph#edgeCount()} - */ - @Test - @UnitTestMethod(name = "edgeCount", args = {}) - public void testEdgeCount_NoArgs() { - Graph.Builder builder = Graph.builder(); - builder.addEdge("A->B", "A", "B"); - - // reversed edges should count as two edges - builder.addEdge("B->C", "B", "C"); - builder.addEdge("C->B", "C", "B"); - - // this edge connects B to C, but is a new edge - builder.addEdge("X", "B", "C"); - - // we are replacing the edge and so this should only count as one edge - builder.addEdge("B->A", "B", "A"); - builder.addEdge("B->A", "B", "X"); - - Graph graph = builder.build(); - assertEquals(5, graph.edgeCount()); - - } - - /** - * Tests {@link Graph#formsEdgeRelationship(Object, Object, Object)} - */ - @Test - @UnitTestMethod(name = "formsEdgeRelationship", args = { Object.class, Object.class, Object.class }) - public void testFormsEdgeRelationship() { - Graph.Builder builder = Graph.builder(); - builder.addEdge("A->B", "A", "B"); - - // reversed edges should count as two edges - builder.addEdge("B->C", "B", "C"); - builder.addEdge("C->B", "C", "B"); - - // this edge connects B to C, but is a new edge - builder.addEdge("X", "B", "C"); - - // we are replacing the edge and so this should only count as one edge - builder.addEdge("B->A", "B", "A"); - builder.addEdge("B->A", "B", "X"); - - Graph graph = builder.build(); - - assertTrue(graph.formsEdgeRelationship("A->B", "A", "B")); - assertTrue(graph.formsEdgeRelationship("B->C", "B", "C")); - assertTrue(graph.formsEdgeRelationship("C->B", "C", "B")); - assertTrue(graph.formsEdgeRelationship("X", "B", "C")); - assertTrue(graph.formsEdgeRelationship("B->A", "B", "X")); - - assertFalse(graph.formsEdgeRelationship("B->A", "B", "A")); - - } - - /** - * Tests {@link Graph#getDestinationNode(Object)} - */ - @Test - @UnitTestMethod(name = "getDestinationNode", args = { Object.class }) - public void testGetDestinationNode() { - Graph.Builder builder = Graph.builder(); - builder.addEdge("A->B", "A", "B"); - - // reversed edges should count as two edges - builder.addEdge("B->C", "B", "C"); - builder.addEdge("C->B", "C", "B"); - - // this edge connects B to C, but is a new edge - builder.addEdge("X", "B", "C"); - - // we are replacing the edge and so this should only count as one edge - builder.addEdge("B->A", "B", "A"); - builder.addEdge("B->A", "B", "X"); - - Graph graph = builder.build(); - - assertEquals("B", graph.getDestinationNode("A->B")); - assertEquals("C", graph.getDestinationNode("B->C")); - assertEquals("B", graph.getDestinationNode("C->B")); - assertEquals("C", graph.getDestinationNode("X")); - assertEquals("X", graph.getDestinationNode("B->A")); - - assertNotEquals("A", graph.getDestinationNode("B->A")); - } - - /** - * Tests {@link Graph#getEdges()} - */ - @Test - @UnitTestMethod(name = "getEdges", args = {}) - public void testGetEdges_NoArgs() { - Graph.Builder builder = Graph.builder(); - builder.addEdge("A->B", "A", "B"); - builder.addEdge("B->C", "B", "C"); - builder.addEdge("C->B", "C", "B"); - builder.addEdge("B->A", "B", "A"); - - Set expected = new LinkedHashSet<>(); - expected.add("A->B"); - expected.add("B->C"); - expected.add("C->B"); - expected.add("B->A"); - - Graph graph = builder.build(); - - Set actual = graph.getEdges().stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - } - - /** - * Tests{@link Graph#getEdges(Object, Object)} - */ - @Test - @UnitTestMethod(name = "getEdges", args = { Object.class, Object.class }) - public void testGetEdges_Objects() { - Graph.Builder builder = Graph.builder(); - builder.addEdge("E1", "B", "C"); - builder.addEdge("E2", "C", "B"); - builder.addEdge("E3", "B", "C"); - builder.addEdge("E4", "B", "A"); - builder.addEdge("E5", "B", "D"); - builder.addEdge("E6", "A", "B"); - builder.addEdge("E7", "A", "A"); - Graph graph = builder.build(); - - Set expected = new LinkedHashSet<>(); - ; - Set actual; - - expected.clear(); - expected.add("E7"); - actual = graph.getEdges("A", "A").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected.clear(); - expected.add("E6"); - actual = graph.getEdges("A", "B").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected.clear(); - actual = graph.getEdges("A", "C").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected.clear(); - actual = graph.getEdges("A", "D").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected.clear(); - expected.add("E4"); - actual = graph.getEdges("B", "A").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected.clear(); - actual = graph.getEdges("B", "B").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected.clear(); - expected.add("E1"); - expected.add("E3"); - actual = graph.getEdges("B", "C").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected.clear(); - expected.add("E5"); - actual = graph.getEdges("B", "D").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected.clear(); - actual = graph.getEdges("C", "A").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected.clear(); - expected.add("E2"); - actual = graph.getEdges("C", "B").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected.clear(); - actual = graph.getEdges("C", "C").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected.clear(); - actual = graph.getEdges("C", "D").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected.clear(); - actual = graph.getEdges("D", "A").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected.clear(); - actual = graph.getEdges("D", "B").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected.clear(); - actual = graph.getEdges("D", "C").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected.clear(); - actual = graph.getEdges("D", "D").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - } - - /** - * Tests {@link Graph#getInboundEdgeCount(Object)} - */ - @Test - @UnitTestMethod(name = "getInboundEdgeCount", args = { Object.class }) - public void testGetInboundEdgeCount() { - Graph.Builder builder = Graph.builder(); - builder.addEdge("A->B", "A", "B"); - builder.addEdge("C->B", "C", "B"); - - builder.addEdge("B->C", "B", "C"); - - builder.addEdge("B->A", "B", "A"); - builder.addEdge("A->A", "A", "A"); - - builder.addNode("D"); - - Graph graph = builder.build(); - - assertEquals(2, graph.getInboundEdgeCount("A")); - assertEquals(2, graph.getInboundEdgeCount("B")); - assertEquals(1, graph.getInboundEdgeCount("C")); - assertEquals(0, graph.getInboundEdgeCount("D")); - } - - /** - * Tests {@link Graph#getInboundEdges(Object)} - */ - @Test - @UnitTestMethod(name = "getInboundEdges", args = { Object.class }) - public void testGetInboundEdges() { - Graph.Builder builder = Graph.builder(); - builder.addEdge("A->B", "A", "B"); - builder.addEdge("C->B", "C", "B"); - - builder.addEdge("B->C", "B", "C"); - - builder.addEdge("B->A", "B", "A"); - builder.addEdge("A->A", "A", "A"); - - builder.addNode("D"); - - Graph graph = builder.build(); - - Set expected = new LinkedHashSet<>(); - expected.add("B->A"); - expected.add("A->A"); - assertEquals(expected, graph.getInboundEdges("A").stream().collect(Collectors.toCollection(LinkedHashSet::new))); - - expected.clear(); - expected.add("A->B"); - expected.add("C->B"); - assertEquals(expected, graph.getInboundEdges("B").stream().collect(Collectors.toCollection(LinkedHashSet::new))); - - expected.clear(); - expected.add("B->C"); - assertEquals(expected, graph.getInboundEdges("C").stream().collect(Collectors.toCollection(LinkedHashSet::new))); - - expected.clear(); - assertEquals(expected, graph.getInboundEdges("D").stream().collect(Collectors.toCollection(LinkedHashSet::new))); - - } - - /** - * Tests {@link Graph#getNodes()} - */ - @Test - @UnitTestMethod(name = "getNodes", args = {}) - public void testGetNodes() { - Set expected = new LinkedHashSet<>(); - expected.add("A"); - expected.add("B"); - expected.add("C"); - expected.add("D"); - expected.add("E"); - - Graph.Builder builder = Graph.builder(); - for (String node : expected) { - builder.addNode(node); - } - - Graph graph = builder.build(); - - Set actual = graph.getNodes().stream().collect(Collectors.toCollection(LinkedHashSet::new)); - - assertEquals(expected, actual); - } - - /** - * Tests {@link Graph#getOriginNode(Object)} - */ - @Test - @UnitTestMethod(name = "getOriginNode", args = { Object.class }) - public void testGetOriginNode() { - Graph.Builder builder = Graph.builder(); - builder.addEdge("A->B", "A", "B"); - - // reversed edges should count as two edges - builder.addEdge("B->C", "B", "C"); - builder.addEdge("C->B", "C", "B"); - - // this edge connects B to C, but is a new edge - builder.addEdge("X", "B", "C"); - - // we are replacing the edge and so this should only count as one edge - builder.addEdge("B->A", "B", "A"); - builder.addEdge("B->A", "B", "X"); - - Graph graph = builder.build(); - - assertEquals("A", graph.getOriginNode("A->B")); - assertEquals("B", graph.getOriginNode("B->C")); - assertEquals("C", graph.getOriginNode("C->B")); - assertEquals("B", graph.getOriginNode("X")); - assertEquals("B", graph.getOriginNode("B->A")); - - } - - /** - * Tests {@link Graph#getOutboundEdgeCount(Object)} - */ - @Test - @UnitTestMethod(name = "getOutboundEdgeCount", args = { Object.class }) - public void testGetOutboundEdgeCount() { - Graph.Builder builder = Graph.builder(); - builder.addEdge("A->B", "A", "B"); - builder.addEdge("C->B", "C", "B"); - - builder.addEdge("B->C", "B", "C"); - - builder.addEdge("B->A", "B", "A"); - builder.addEdge("A->A", "A", "A"); - - builder.addEdge("A->D", "A", "D"); - - Graph graph = builder.build(); - - assertEquals(3, graph.getOutboundEdgeCount("A")); - assertEquals(2, graph.getOutboundEdgeCount("B")); - assertEquals(1, graph.getOutboundEdgeCount("C")); - assertEquals(0, graph.getOutboundEdgeCount("D")); - } - - /** - * Tests {@link Graph#getOutboundEdges(Object)} - */ - @Test - @UnitTestMethod(name = "getOutboundEdges", args = { Object.class }) - public void testGetOutboundEdges() { - Graph.Builder builder = Graph.builder(); - builder.addEdge("A->B", "A", "B"); - builder.addEdge("C->B", "C", "B"); - - builder.addEdge("B->C", "B", "C"); - - builder.addEdge("B->A", "B", "A"); - builder.addEdge("A->A", "A", "A"); - - builder.addNode("D"); - - Graph graph = builder.build(); - - Set expected = new LinkedHashSet<>(); - expected.add("A->A"); - expected.add("A->B"); - assertEquals(expected, graph.getOutboundEdges("A").stream().collect(Collectors.toCollection(LinkedHashSet::new))); - - expected.clear(); - expected.add("B->C"); - expected.add("B->A"); - assertEquals(expected, graph.getOutboundEdges("B").stream().collect(Collectors.toCollection(LinkedHashSet::new))); - - expected.clear(); - expected.add("C->B"); - assertEquals(expected, graph.getOutboundEdges("C").stream().collect(Collectors.toCollection(LinkedHashSet::new))); - - expected.clear(); - assertEquals(expected, graph.getOutboundEdges("D").stream().collect(Collectors.toCollection(LinkedHashSet::new))); - - } - - /** - * Tests {@link Graph#isEmpty()} - */ - @Test - @UnitTestMethod(name = "isEmpty", args = {}) - public void testIsEmpty() { - Graph.Builder builder = Graph.builder(); - builder.addNode("A"); - Graph graph = builder.build(); - - assertFalse(graph.isEmpty()); - - builder.addEdge("A->B", "A", "B"); - graph = builder.build(); - assertFalse(graph.isEmpty()); - - graph = builder.build(); - assertTrue(graph.isEmpty()); - } - - /** - * Tests {@link Graph#nodeCount()} - */ - @Test - @UnitTestMethod(name = "nodeCount", args = {}) - public void testNodeCount() { - Graph.Builder builder = Graph.builder(); - Graph graph = builder.build(); - assertEquals(0, graph.nodeCount()); - - builder.addNode("A"); - graph = builder.build(); - assertEquals(1, graph.nodeCount()); - - builder.addNode("A"); - builder.addNode("B"); - graph = builder.build(); - assertEquals(2, graph.nodeCount()); - - builder.addNode("A"); - builder.addNode("A"); - builder.addNode("B"); - graph = builder.build(); - assertEquals(2, graph.nodeCount()); - } - - /** - * Tests {@link Graph#toMutableGraph()} - */ - @Test - @UnitTestMethod(name = "toMutableGraph", args = {}) - public void testToMutableGraph() { - Graph.Builder builder = Graph.builder(); - builder.addEdge("A->B", "A", "B"); - builder.addEdge("A->C", "A", "C"); - builder.addEdge("B->A", "B", "A"); - builder.addEdge("B->D", "B", "D"); - builder.addNode("E"); - - Graph graph = builder.build(); - - MutableGraph mutableGraph = graph.toMutableGraph(); - - assertEquals(graph, mutableGraph); - - } - - /** - * Tests {@link Graph#hashCode()} - */ - @Test - @UnitTestMethod(name = "hashCode", args = {}) - public void testHashCode() { - Graph.Builder builder = Graph.builder(); - builder.addEdge("A->B", "A", "B"); - builder.addEdge("A->C", "A", "C"); - builder.addEdge("B->A", "B", "A"); - builder.addEdge("B->D", "B", "D"); - builder.addNode("E"); - - Graph graph1 = builder.build(); - - int expected = "A".hashCode(); - expected += "B".hashCode(); - expected += "C".hashCode(); - expected += "D".hashCode(); - expected += "E".hashCode(); - expected += "A->B".hashCode(); - expected += "A->C".hashCode(); - expected += "B->A".hashCode(); - expected += "B->D".hashCode(); - - int actual = graph1.hashCode(); - - assertEquals(expected, actual); - - builder.addNode("E"); - builder.addEdge("B->D", "B", "D"); - builder.addEdge("B->A", "B", "A"); - builder.addEdge("A->C", "A", "C"); - builder.addEdge("A->B", "A", "B"); - Graph graph2 = builder.build(); - assertEquals(graph1, graph2); - assertEquals(graph1.hashCode(), graph2.hashCode()); - - } - - /** - * Tests {@link Graph#equals(Object)} - */ - @Test - @UnitTestMethod(name = "equals", args = { Object.class }) - public void testEquals() { - Graph.Builder builder = Graph.builder(); - - builder.addEdge("A->B", "A", "B"); - builder.addEdge("A->C", "A", "C"); - builder.addEdge("B->A", "B", "A"); - builder.addEdge("B->D", "B", "D"); - builder.addNode("E"); - Graph graph1 = builder.build(); - - builder.addNode("E"); - builder.addEdge("B->D", "B", "D"); - builder.addEdge("B->A", "B", "A"); - builder.addEdge("A->C", "A", "C"); - builder.addEdge("A->B", "A", "B"); - Graph graph2 = builder.build(); - - assertEquals(graph1, graph2); - } - -} diff --git a/gcm3/src/test/java/util/graph/AT_GraphDepthEvaluator.java b/gcm3/src/test/java/util/graph/AT_GraphDepthEvaluator.java deleted file mode 100644 index 0db8bd737..000000000 --- a/gcm3/src/test/java/util/graph/AT_GraphDepthEvaluator.java +++ /dev/null @@ -1,169 +0,0 @@ -package util.graph; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -/** - * Test class for {@link GraphDepthEvaluator} - * - * @author Shawn Hatch - * - */ -@UnitTest(target = GraphDepthEvaluator.class) -public class AT_GraphDepthEvaluator { - - /** - * Tests {@link GraphDepthEvaluator#getGraphDepthEvaluator(Graph)} - */ - @Test - @UnitTestMethod(name = "getGraphDepthEvaluator", args = { Graph.class }) - public void testGetGraphDepthEvaluator() { - MutableGraph m = new MutableGraph<>(); - // empty graphs are acyclic - Optional> optional = GraphDepthEvaluator.getGraphDepthEvaluator(m.toGraph()); - assertTrue(optional.isPresent()); - - m.addEdge("A->B", "A", "B"); - optional = GraphDepthEvaluator.getGraphDepthEvaluator(m.toGraph()); - assertTrue(optional.isPresent()); - - // the graph has a cycle and so no depth evaluator is generated - m.addEdge("B->A", "B", "A"); - optional = GraphDepthEvaluator.getGraphDepthEvaluator(m.toGraph()); - assertFalse(optional.isPresent()); - - } - - /** - * Tests {@link GraphDepthEvaluator#getDepth(Object)} - */ - @Test - @UnitTestMethod(name = "getDepth", args = { Object.class }) - public void testGetDepth() { - MutableGraph m = new MutableGraph<>(); - m.addEdge("A->B", "A", "B"); - m.addEdge("A->C", "A", "C"); - m.addEdge("C->D", "C", "D"); - m.addEdge("A->D", "A", "D"); - - Optional> optional = GraphDepthEvaluator.getGraphDepthEvaluator(m.toGraph()); - - assertTrue(optional.isPresent()); - GraphDepthEvaluator graphDepthEvaluator = optional.get(); - assertEquals(2, graphDepthEvaluator.getDepth("A")); - assertEquals(0, graphDepthEvaluator.getDepth("B")); - assertEquals(1, graphDepthEvaluator.getDepth("C")); - assertEquals(0, graphDepthEvaluator.getDepth("D")); - - } - - /** - * Tests {@link GraphDepthEvaluator#getMaxDepth()} - */ - @Test - @UnitTestMethod(name = "getMaxDepth", args = {}) - public void testGetMaxDepth() { - MutableGraph m = new MutableGraph<>(); - m.addEdge("A->B", "A", "B"); - m.addEdge("A->C", "A", "C"); - m.addEdge("C->D", "C", "D"); - m.addEdge("A->D", "A", "D"); - m.addEdge("D->E", "D", "E"); - - Optional> optional = GraphDepthEvaluator.getGraphDepthEvaluator(m.toGraph()); - - assertTrue(optional.isPresent()); - GraphDepthEvaluator graphDepthEvaluator = optional.get(); - assertEquals(3, graphDepthEvaluator.getMaxDepth()); - - } - - /** - * Tests {@link GraphDepthEvaluator#getNodesForDepth(int)} - */ - @Test - @UnitTestMethod(name = "getNodesForDepth", args = { int.class }) - public void testGetNodesForDepth() { - MutableGraph m = new MutableGraph<>(); - m.addEdge("A->B", "A", "B"); - m.addEdge("A->C", "A", "C"); - m.addEdge("C->D", "C", "D"); - m.addEdge("A->D", "A", "D"); - m.addEdge("D->E", "D", "E"); - - Optional> optional = GraphDepthEvaluator.getGraphDepthEvaluator(m.toGraph()); - - assertTrue(optional.isPresent()); - GraphDepthEvaluator graphDepthEvaluator = optional.get(); - - assertEquals(3, graphDepthEvaluator.getMaxDepth()); - - Set expected = new LinkedHashSet<>(); - expected.add("A"); - Set actual = graphDepthEvaluator.getNodesForDepth(3).stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected = new LinkedHashSet<>(); - expected.add("C"); - actual = graphDepthEvaluator.getNodesForDepth(2).stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected = new LinkedHashSet<>(); - expected.add("D"); - actual = graphDepthEvaluator.getNodesForDepth(1).stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected = new LinkedHashSet<>(); - expected.add("B"); - expected.add("E"); - actual = graphDepthEvaluator.getNodesForDepth(0).stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - } - - /** - * Tests {@link GraphDepthEvaluator#getNodesInRankOrder()} - */ - @Test - @UnitTestMethod(name = "getNodesInRankOrder", args = {}) - public void testGetNodesInRankOrder() { - MutableGraph m = new MutableGraph<>(); - m.addEdge("A->B", "A", "B"); - m.addEdge("A->C", "A", "C"); - m.addEdge("C->D", "C", "D"); - m.addEdge("A->D", "A", "D"); - m.addEdge("D->E", "D", "E"); - - Optional> optional = GraphDepthEvaluator.getGraphDepthEvaluator(m.toGraph()); - - assertTrue(optional.isPresent()); - GraphDepthEvaluator graphDepthEvaluator = optional.get(); - - List actual = graphDepthEvaluator.getNodesInRankOrder(); - - // show that the values returned contain each node exactly once - assertEquals(m.nodeCount(), actual.size()); - assertEquals(m.nodeCount(), actual.stream().collect(Collectors.toCollection(LinkedHashSet::new)).size()); - - int previousDepth = 0; - for (String node : actual) { - int depth = graphDepthEvaluator.getDepth(node); - assertTrue(depth >= previousDepth); - previousDepth = depth; - } - - } - -} diff --git a/gcm3/src/test/java/util/graph/AT_Graphs.java b/gcm3/src/test/java/util/graph/AT_Graphs.java deleted file mode 100644 index ca4fc509f..000000000 --- a/gcm3/src/test/java/util/graph/AT_Graphs.java +++ /dev/null @@ -1,186 +0,0 @@ -package util.graph; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.graph.Graphs.GraphConnectedness; -import util.graph.Graphs.GraphCyclisity; - -/** - * Test class for {@link Graphs} - * - * @author Shawn Hatch - * - */ -@UnitTest(target = Graphs.class) -public class AT_Graphs { - - /** - * Tests {@link Graphs#cutGraph(Graph)} - */ - @Test - @UnitTestMethod(name = "cutGraph", args = { Graph.class }) - public void testCutGraph() { - - assertEquals(0,Graphs.cutGraph(new MutableGraph<>().toGraph()).size()); - - MutableGraph m = new MutableGraph<>(); - m.addEdge("A->B", "A", "B"); - m.addEdge("B->C", "B", "C"); - m.addEdge("D->E", "D", "E"); - m.addNode("F"); - - Set> expected = new LinkedHashSet<>(); - - MutableGraph a = new MutableGraph<>(); - a.addEdge("A->B", "A", "B"); - a.addEdge("B->C", "B", "C"); - expected.add(a.toGraph()); - - MutableGraph b = new MutableGraph<>(); - b.addEdge("D->E", "D", "E"); - expected.add(b.toGraph()); - - MutableGraph c = new MutableGraph<>(); - c.addNode("F"); - expected.add(c.toGraph()); - - Set> actual = Graphs.cutGraph(m.toGraph()).stream().collect(Collectors.toCollection(LinkedHashSet::new)); - - assertEquals(expected, actual); - - } - - /** - * Tests {@link Graphs#getGraphConnectedness(Graph)} - */ - @Test - @UnitTestMethod(name = "getGraphConnectedness", args = { Graph.class }) - public void testGetGraphConnectedness() { - MutableGraph m = new MutableGraph<>(); - assertEquals(GraphConnectedness.DISCONNECTED, Graphs.getGraphConnectedness(m.toGraph())); - - m.addNode("A"); - m.addNode("B"); - m.addNode("C"); - assertEquals(GraphConnectedness.DISCONNECTED, Graphs.getGraphConnectedness(m.toGraph())); - - m.addEdge("A->B", "A", "B"); - assertEquals(GraphConnectedness.DISCONNECTED, Graphs.getGraphConnectedness(m.toGraph())); - - m.addEdge("B->C", "B", "C"); - assertEquals(GraphConnectedness.WEAKLYCONNECTED, Graphs.getGraphConnectedness(m.toGraph())); - - m.addEdge("B->A", "B", "A"); - assertEquals(GraphConnectedness.WEAKLYCONNECTED, Graphs.getGraphConnectedness(m.toGraph())); - - m.addEdge("C->B", "C", "B"); - assertEquals(GraphConnectedness.STRONGLYCONNECTED, Graphs.getGraphConnectedness(m.toGraph())); - - } - - /** - * Tests {@link Graphs#getGraphCyclisity(Graph)} - */ - @Test - @UnitTestMethod(name = "getGraphCyclisity", args = { Graph.class }) - public void testGetGraphCyclisity() { - MutableGraph m = new MutableGraph<>(); - // empty graphs are acyclic - assertEquals(GraphCyclisity.ACYCLIC, Graphs.getGraphCyclisity(m.toGraph())); - - m.addNode("A"); - m.addNode("B"); - m.addNode("C"); - assertEquals(GraphCyclisity.ACYCLIC, Graphs.getGraphCyclisity(m.toGraph())); - - m.addEdge("A->B", "A", "B"); - assertEquals(GraphCyclisity.ACYCLIC, Graphs.getGraphCyclisity(m.toGraph())); - - m.addEdge("B->A", "B", "A"); - assertEquals(GraphCyclisity.CYCLIC, Graphs.getGraphCyclisity(m.toGraph())); - - m.addEdge("B->C", "B", "C"); - assertEquals(GraphCyclisity.CYCLIC, Graphs.getGraphCyclisity(m.toGraph())); - - m.addEdge("C->A", "C", "A"); - assertEquals(GraphCyclisity.CYCLIC, Graphs.getGraphCyclisity(m.toGraph())); - - } - - /** - * Tests {@link Graphs#getReverseGraph(Graph)} - */ - @Test - @UnitTestMethod(name = "getReverseGraph", args = { Graph.class }) - public void testGetReverseGraph() { - MutableGraph m = new MutableGraph<>(); - m.addEdge("A->B", "A", "B"); - m.addEdge("A->C", "A", "C"); - m.addEdge("B->C", "B", "C"); - m.addEdge("C->D", "C", "D"); - m.addNode("E"); - - MutableGraph m2 = new MutableGraph<>(); - m2.addEdge("A->B", "B", "A"); - m2.addEdge("A->C", "C", "A"); - m2.addEdge("B->C", "C", "B"); - m2.addEdge("C->D", "D", "C"); - m2.addNode("E"); - - Graph expected = m2.toGraph(); - - Graph actual = Graphs.getReverseGraph(m.toGraph()); - - assertEquals(expected, actual); - - } - - /** - * Tests {@link Graphs#getSourceSinkReducedGraph(Graph)} - */ - @Test - @UnitTestMethod(name = "getSourceSinkReducedGraph", args = { Graph.class }) - public void testGetSourceSinkReducedGraph() { - MutableGraph m = new MutableGraph<>(); - m.addEdge("A->B", "A", "B"); - m.addEdge("A->C", "A", "C"); - m.addEdge("B->C", "B", "C"); - m.addEdge("C->D", "C", "D"); - m.addNode("E"); - - Graph reducedGraph = Graphs.getSourceSinkReducedGraph(m.toGraph()); - assertTrue(reducedGraph.isEmpty()); - - m.addEdge("D->C", "D", "C"); - reducedGraph = Graphs.getSourceSinkReducedGraph(m.toGraph()); - assertEquals(2, reducedGraph.edgeCount()); - assertEquals(2, reducedGraph.nodeCount()); - assertTrue(reducedGraph.containsEdge("C->D")); - assertTrue(reducedGraph.containsEdge("D->C")); - assertTrue(reducedGraph.containsNode("C")); - assertTrue(reducedGraph.containsNode("D")); - - m.addEdge("E->E", "E", "E"); - reducedGraph = Graphs.getSourceSinkReducedGraph(m.toGraph()); - reducedGraph = Graphs.getSourceSinkReducedGraph(m.toGraph()); - assertEquals(3, reducedGraph.edgeCount()); - assertEquals(3, reducedGraph.nodeCount()); - assertTrue(reducedGraph.containsEdge("C->D")); - assertTrue(reducedGraph.containsEdge("D->C")); - assertTrue(reducedGraph.containsEdge("E->E")); - assertTrue(reducedGraph.containsNode("C")); - assertTrue(reducedGraph.containsNode("D")); - assertTrue(reducedGraph.containsNode("E")); - - } - -} diff --git a/gcm3/src/test/java/util/graph/AT_MutableGraph.java b/gcm3/src/test/java/util/graph/AT_MutableGraph.java deleted file mode 100644 index 944ab2356..000000000 --- a/gcm3/src/test/java/util/graph/AT_MutableGraph.java +++ /dev/null @@ -1,787 +0,0 @@ -package util.graph; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; - -/** - * Test class for {@link MutableGraph} - * - * @author Shawn Hatch - * - */ -@UnitTest(target = MutableGraph.class) -public class AT_MutableGraph { - - /** - * Tests {@link MutableGraph#addAll(MutableGraph)} - */ - @Test - @UnitTestMethod(name = "addAll", args = { MutableGraph.class }) - public void testAddAllForMutableGraph() { - MutableGraph mutableGraph1 = new MutableGraph<>(); - mutableGraph1.addEdge("A->B", "A", "B"); - mutableGraph1.addEdge("A->C", "A", "C"); - mutableGraph1.addEdge("B->C", "B", "C"); - mutableGraph1.addEdge("C->A", "C", "A"); - - MutableGraph mutableGraph2 = new MutableGraph<>(); - mutableGraph2.addEdge("A->B", "J", "K");// this will replace the - // existing edge - mutableGraph2.addEdge("J->V", "J", "V");// this will be a new edge - mutableGraph2.addEdge("B->A", "B", "A");// this will be a new edge - mutableGraph2.addEdge("C->A", "C", "A");// this will the same as an - // existing edge - - mutableGraph1.addAll(mutableGraph2); - - assertEquals(6, mutableGraph1.edgeCount()); - assertEquals(6, mutableGraph1.nodeCount()); - - assertTrue(mutableGraph1.containsNode("A")); - assertTrue(mutableGraph1.containsNode("B")); - assertTrue(mutableGraph1.containsNode("C")); - assertTrue(mutableGraph1.containsNode("J")); - assertTrue(mutableGraph1.containsNode("V")); - assertTrue(mutableGraph1.containsNode("K")); - - assertTrue(mutableGraph1.containsEdge("A->C")); - assertTrue(mutableGraph1.containsEdge("B->C")); - assertTrue(mutableGraph1.containsEdge("C->A")); - assertTrue(mutableGraph1.containsEdge("A->B")); - assertTrue(mutableGraph1.containsEdge("J->V")); - assertTrue(mutableGraph1.containsEdge("B->A")); - - assertEquals("A", mutableGraph1.getOriginNode("A->C")); - assertEquals("B", mutableGraph1.getOriginNode("B->C")); - assertEquals("C", mutableGraph1.getOriginNode("C->A")); - assertEquals("J", mutableGraph1.getOriginNode("A->B")); - assertEquals("J", mutableGraph1.getOriginNode("J->V")); - assertEquals("B", mutableGraph1.getOriginNode("B->A")); - - assertEquals("C", mutableGraph1.getDestinationNode("A->C")); - assertEquals("C", mutableGraph1.getDestinationNode("B->C")); - assertEquals("A", mutableGraph1.getDestinationNode("C->A")); - assertEquals("K", mutableGraph1.getDestinationNode("A->B")); - assertEquals("V", mutableGraph1.getDestinationNode("J->V")); - assertEquals("A", mutableGraph1.getDestinationNode("B->A")); - } - - /** - * Tests {@link MutableGraph#addAll(Graph)} - */ - @Test - @UnitTestMethod(name = "addAll", args = { Graph.class }) - public void testAddAllForGraph() { - MutableGraph mutableGraph1 = new MutableGraph<>(); - mutableGraph1.addEdge("A->B", "A", "B"); - mutableGraph1.addEdge("A->C", "A", "C"); - mutableGraph1.addEdge("B->C", "B", "C"); - mutableGraph1.addEdge("C->A", "C", "A"); - - Graph.Builder builder = Graph.builder(); - builder.addEdge("A->B", "J", "K");// this will replace the existing edge - builder.addEdge("J->V", "J", "V");// this will be a new edge - builder.addEdge("B->A", "B", "A");// this will be a new edge - builder.addEdge("C->A", "C", "A");// this will the same as an existing - // edge - - mutableGraph1.addAll(builder.build()); - - assertEquals(6, mutableGraph1.edgeCount()); - assertEquals(6, mutableGraph1.nodeCount()); - - assertTrue(mutableGraph1.containsNode("A")); - assertTrue(mutableGraph1.containsNode("B")); - assertTrue(mutableGraph1.containsNode("C")); - assertTrue(mutableGraph1.containsNode("J")); - assertTrue(mutableGraph1.containsNode("V")); - assertTrue(mutableGraph1.containsNode("K")); - - assertTrue(mutableGraph1.containsEdge("A->C")); - assertTrue(mutableGraph1.containsEdge("B->C")); - assertTrue(mutableGraph1.containsEdge("C->A")); - assertTrue(mutableGraph1.containsEdge("A->B")); - assertTrue(mutableGraph1.containsEdge("J->V")); - assertTrue(mutableGraph1.containsEdge("B->A")); - - assertEquals("A", mutableGraph1.getOriginNode("A->C")); - assertEquals("B", mutableGraph1.getOriginNode("B->C")); - assertEquals("C", mutableGraph1.getOriginNode("C->A")); - assertEquals("J", mutableGraph1.getOriginNode("A->B")); - assertEquals("J", mutableGraph1.getOriginNode("J->V")); - assertEquals("B", mutableGraph1.getOriginNode("B->A")); - - assertEquals("C", mutableGraph1.getDestinationNode("A->C")); - assertEquals("C", mutableGraph1.getDestinationNode("B->C")); - assertEquals("A", mutableGraph1.getDestinationNode("C->A")); - assertEquals("K", mutableGraph1.getDestinationNode("A->B")); - assertEquals("V", mutableGraph1.getDestinationNode("J->V")); - assertEquals("A", mutableGraph1.getDestinationNode("B->A")); - } - - /** - * Tests {@link MutableGraph#addEdge(Object, Object, Object)} - */ - @Test - @UnitTestMethod(name = "addEdge", args = { Object.class, Object.class, Object.class }) - public void testAddEdge() { - MutableGraph mutableGraph = new MutableGraph<>(); - mutableGraph.addEdge("A->B", "A", "B"); - - assertTrue(mutableGraph.containsNode("A")); - assertTrue(mutableGraph.containsNode("B")); - assertTrue(mutableGraph.containsEdge("A->B")); - assertEquals("A", mutableGraph.getOriginNode("A->B")); - assertEquals("B", mutableGraph.getDestinationNode("A->B")); - - // show that the edge can replace an existing edge - mutableGraph.addEdge("A->B", "B", "C"); - assertEquals("B", mutableGraph.getOriginNode("A->B")); - assertEquals("C", mutableGraph.getDestinationNode("A->B")); - - } - - /** - * Tests {@link MutableGraph#MutableGraph())} - */ - @Test - @UnitTestConstructor(args = {}) - public void testConstructor() { - MutableGraph mutableGraph = new MutableGraph<>(); - assertNotNull(mutableGraph); - assertEquals(0, mutableGraph.nodeCount()); - assertEquals(0, mutableGraph.edgeCount()); - } - - /** - * Tests {@link MutableGraph#addNode(Object)} - */ - @Test - @UnitTestMethod(name = "addNode", args = { Object.class }) - public void testAddNode() { - MutableGraph mutableGraph = new MutableGraph<>(); - mutableGraph.addNode(1); - mutableGraph.addNode(2); - mutableGraph.addNode(3); - mutableGraph.addNode(4); - - assertTrue(mutableGraph.containsNode(1)); - assertTrue(mutableGraph.containsNode(2)); - assertTrue(mutableGraph.containsNode(3)); - assertTrue(mutableGraph.containsNode(4)); - - // re-adding should have no effect - mutableGraph.addNode(4); - assertTrue(mutableGraph.containsNode(4)); - } - - /** - * Tests {@link MutableGraph#containsEdge(Object)} - */ - @Test - @UnitTestMethod(name = "containsEdge", args = { Object.class }) - public void testContainsEdge() { - MutableGraph mutableGraph = new MutableGraph<>(); - mutableGraph.addEdge("A->B", "A", "B"); - mutableGraph.addEdge("A->C", "A", "C"); - mutableGraph.addEdge("B->A", "B", "A"); - - assertTrue(mutableGraph.containsEdge("A->B")); - assertTrue(mutableGraph.containsEdge("A->C")); - assertTrue(mutableGraph.containsEdge("B->A")); - - } - - /** - * Tests {@link MutableGraph#containsNode(Object)} - */ - @Test - @UnitTestMethod(name = "containsNode", args = { Object.class }) - public void testContainsNode() { - MutableGraph mutableGraph = new MutableGraph<>(); - mutableGraph.addNode("A"); - mutableGraph.addNode("B"); - mutableGraph.addNode("C"); - - assertTrue(mutableGraph.containsNode("A")); - assertTrue(mutableGraph.containsNode("B")); - assertTrue(mutableGraph.containsNode("C")); - } - - /** - * Tests {@link MutableGraph#edgeCount(Object, Object)} - */ - @Test - @UnitTestMethod(name = "edgeCount", args = { Object.class, Object.class }) - public void testEdgeCount_WithNodes() { - MutableGraph mutableGraph = new MutableGraph<>(); - mutableGraph.addEdge("A->B_1", "A", "B"); - mutableGraph.addEdge("A->B_2", "A", "B"); - mutableGraph.addEdge("A->B_3", "A", "B"); - mutableGraph.addEdge("A->C_1", "A", "C"); - mutableGraph.addEdge("A->C_2", "A", "C"); - mutableGraph.addEdge("B->C", "B", "C"); - mutableGraph.addEdge("B->D_1", "B", "D"); - mutableGraph.addEdge("A->D_2", "A", "D"); - - assertEquals(8, mutableGraph.edgeCount()); - - assertEquals(3, mutableGraph.edgeCount("A", "B")); - assertEquals(2, mutableGraph.edgeCount("A", "C")); - assertEquals(1, mutableGraph.edgeCount("B", "C")); - assertEquals(1, mutableGraph.edgeCount("B", "D")); - assertEquals(1, mutableGraph.edgeCount("A", "D")); - assertEquals(0, mutableGraph.edgeCount("C", "D")); - } - - /** - * Tests {@link MutableGraph#edgeCount()} - */ - @Test - @UnitTestMethod(name = "edgeCount", args = {}) - public void testEdgeCount_WithoutNodes() { - MutableGraph mutableGraph = new MutableGraph<>(); - mutableGraph.addEdge("A->B_1", "A", "B"); - mutableGraph.addEdge("A->B_2", "A", "B"); - mutableGraph.addEdge("A->B_3", "A", "B"); - mutableGraph.addEdge("A->C_1", "A", "C"); - mutableGraph.addEdge("A->C_2", "A", "C"); - mutableGraph.addEdge("B->C", "B", "C"); - mutableGraph.addEdge("B->D_1", "B", "D"); - mutableGraph.addEdge("A->D_2", "A", "D"); - - assertEquals(8, mutableGraph.edgeCount()); - - assertEquals(3, mutableGraph.edgeCount("A", "B")); - assertEquals(2, mutableGraph.edgeCount("A", "C")); - assertEquals(1, mutableGraph.edgeCount("B", "C")); - assertEquals(1, mutableGraph.edgeCount("B", "D")); - assertEquals(1, mutableGraph.edgeCount("A", "D")); - assertEquals(0, mutableGraph.edgeCount("C", "D")); - } - - /** - * Tests {@link MutableGraph#equals(Object)} - */ - @Test - @UnitTestMethod(name = "equals", args = { Object.class }) - public void testEquals() { - MutableGraph mutableGraph1 = new MutableGraph<>(); - mutableGraph1.addEdge("A->B", "A", "B"); - mutableGraph1.addNode("C"); - - MutableGraph mutableGraph2 = new MutableGraph<>(); - mutableGraph2.addEdge("A->B", "A", "B"); - mutableGraph2.addNode("C"); - - assertEquals(mutableGraph1, mutableGraph2); - mutableGraph2.addNode("D"); - - assertNotEquals(mutableGraph1, mutableGraph2); - - Graph.Builder builder = Graph.builder(); - builder.addEdge("A->B", "A", "B"); - builder.addNode("C"); - Graph graph = builder.build(); - - assertEquals(mutableGraph1, graph); - - mutableGraph1.addNode("D"); - assertNotEquals(mutableGraph1, graph); - - } - - /** - * Tests {@link MutableGraph#formsEdgeRelationship(Object, Object, Object)} - */ - @Test - @UnitTestMethod(name = "formsEdgeRelationship", args = { Object.class, Object.class, Object.class }) - public void testFormsEdgeRelationship() { - MutableGraph mutableGraph = new MutableGraph<>(); - mutableGraph.addEdge("A->B", "A", "B"); - mutableGraph.addEdge("A->C", "A", "C"); - mutableGraph.addEdge("B->C", "B", "C"); - - assertTrue(mutableGraph.formsEdgeRelationship("A->B", "A", "B")); - assertTrue(mutableGraph.formsEdgeRelationship("A->C", "A", "C")); - assertTrue(mutableGraph.formsEdgeRelationship("B->C", "B", "C")); - - assertFalse(mutableGraph.formsEdgeRelationship("A->B", "A", "C")); - assertFalse(mutableGraph.formsEdgeRelationship("A->C", "A", "B")); - assertFalse(mutableGraph.formsEdgeRelationship("B->C", "B", "A")); - - } - - /** - * Tests {@link MutableGraph#getDestinationNode(Object)} - */ - @Test - @UnitTestMethod(name = "getDestinationNode", args = { Object.class }) - public void testGetDestinationNode() { - MutableGraph mutableGraph = new MutableGraph<>(); - mutableGraph.addEdge("A->B", "A", "B"); - mutableGraph.addEdge("A->C", "A", "C"); - mutableGraph.addEdge("B->C", "B", "C"); - - assertEquals("B", mutableGraph.getDestinationNode("A->B")); - assertEquals("C", mutableGraph.getDestinationNode("A->C")); - assertEquals("C", mutableGraph.getDestinationNode("B->C")); - - } - - /** - * Tests {@link MutableGraph#getEdges()} - */ - @Test - @UnitTestMethod(name = "getEdges", args = {}) - public void testGetEdges_NoArgs() { - MutableGraph mutableGraph = new MutableGraph<>(); - mutableGraph.addEdge("A->B", "A", "B"); - mutableGraph.addEdge("A->C", "A", "C"); - mutableGraph.addEdge("B->C", "B", "C"); - - Set expected = new LinkedHashSet<>(); - expected.add("A->B"); - expected.add("A->C"); - expected.add("B->C"); - - Set actual = mutableGraph.getEdges().stream().collect(Collectors.toCollection(LinkedHashSet::new)); - - assertEquals(expected, actual); - } - - /** - * Tests {@link MutableGraph#getEdges(Object, Object)} - */ - @Test - @UnitTestMethod(name = "getEdges", args = { Object.class, Object.class }) - public void testGetEdges_Objects() { - MutableGraph mutableGraph = new MutableGraph<>(); - - mutableGraph.addEdge("E1", "B", "C"); - mutableGraph.addEdge("E2", "C", "B"); - mutableGraph.addEdge("E3", "B", "C"); - mutableGraph.addEdge("E4", "B", "A"); - mutableGraph.addEdge("E5", "B", "D"); - mutableGraph.addEdge("E6", "A", "B"); - mutableGraph.addEdge("E7", "A", "A"); - - Set expected = new LinkedHashSet<>(); - ; - Set actual; - - expected.clear(); - expected.add("E7"); - actual = mutableGraph.getEdges("A", "A").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected.clear(); - expected.add("E6"); - actual = mutableGraph.getEdges("A", "B").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected.clear(); - actual = mutableGraph.getEdges("A", "C").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected.clear(); - actual = mutableGraph.getEdges("A", "D").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected.clear(); - expected.add("E4"); - actual = mutableGraph.getEdges("B", "A").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected.clear(); - actual = mutableGraph.getEdges("B", "B").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected.clear(); - expected.add("E1"); - expected.add("E3"); - actual = mutableGraph.getEdges("B", "C").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected.clear(); - expected.add("E5"); - actual = mutableGraph.getEdges("B", "D").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected.clear(); - actual = mutableGraph.getEdges("C", "A").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected.clear(); - expected.add("E2"); - actual = mutableGraph.getEdges("C", "B").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected.clear(); - actual = mutableGraph.getEdges("C", "C").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected.clear(); - actual = mutableGraph.getEdges("C", "D").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected.clear(); - actual = mutableGraph.getEdges("D", "A").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected.clear(); - actual = mutableGraph.getEdges("D", "B").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected.clear(); - actual = mutableGraph.getEdges("D", "C").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected.clear(); - actual = mutableGraph.getEdges("D", "D").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - } - - /** - * Tests {@link MutableGraph#getInboundEdgeCount(Object)} - */ - @Test - @UnitTestMethod(name = "getInboundEdgeCount", args = { Object.class }) - public void testGetInboundEdgeCount() { - MutableGraph mutableGraph = new MutableGraph<>(); - mutableGraph.addEdge("A->B", "A", "B"); - mutableGraph.addEdge("A->C", "A", "C"); - mutableGraph.addEdge("B->C", "B", "C"); - mutableGraph.addEdge("B->D", "B", "D"); - mutableGraph.addEdge("D->A", "D", "A"); - mutableGraph.addEdge("D->B", "D", "B"); - - assertEquals(1, mutableGraph.getInboundEdgeCount("A")); - assertEquals(2, mutableGraph.getInboundEdgeCount("B")); - assertEquals(2, mutableGraph.getInboundEdgeCount("C")); - assertEquals(1, mutableGraph.getInboundEdgeCount("D")); - - } - - /** - * Tests {@link MutableGraph#getInboundEdges(Object)} - */ - @Test - @UnitTestMethod(name = "getInboundEdges", args = { Object.class }) - public void testGetInboundEdges() { - MutableGraph mutableGraph = new MutableGraph<>(); - mutableGraph.addEdge("A->B", "A", "B"); - mutableGraph.addEdge("A->C", "A", "C"); - mutableGraph.addEdge("B->C", "B", "C"); - mutableGraph.addEdge("B->D", "B", "D"); - mutableGraph.addEdge("D->A", "D", "A"); - mutableGraph.addEdge("D->B", "D", "B"); - - Set expected = new LinkedHashSet<>(); - expected.add("D->A"); - Set actual = mutableGraph.getInboundEdges("A").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected = new LinkedHashSet<>(); - expected.add("A->B"); - expected.add("D->B"); - actual = mutableGraph.getInboundEdges("B").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected = new LinkedHashSet<>(); - expected.add("A->C"); - expected.add("B->C"); - actual = mutableGraph.getInboundEdges("C").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected = new LinkedHashSet<>(); - expected.add("B->D"); - actual = mutableGraph.getInboundEdges("D").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - } - - /** - * Tests {@link MutableGraph#getNodes()} - */ - @Test - @UnitTestMethod(name = "getNodes", args = {}) - public void testGetNodes() { - MutableGraph mutableGraph = new MutableGraph<>(); - List nodes = new ArrayList<>(); - nodes.add("A"); - nodes.add("B"); - nodes.add("C"); - nodes.add("D"); - nodes.add("A"); - nodes.add("B"); - - Set expected = new LinkedHashSet<>(nodes); - - for (String node : nodes) { - mutableGraph.addNode(node); - } - - List nodesAsList = mutableGraph.getNodes(); - assertEquals(4, nodesAsList.size()); - - Set actual = nodesAsList.stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - } - - /** - * Tests {@link MutableGraph#getOriginNode(Object)} - */ - @Test - @UnitTestMethod(name = "getOriginNode", args = { Object.class }) - public void testGetOriginNode() { - MutableGraph mutableGraph = new MutableGraph<>(); - mutableGraph.addEdge("A->B", "A", "B"); - mutableGraph.addEdge("A->C", "A", "C"); - mutableGraph.addEdge("B->C", "B", "C"); - mutableGraph.addEdge("B->D", "B", "D"); - mutableGraph.addEdge("D->A", "D", "A"); - mutableGraph.addEdge("D->B", "D", "B"); - - assertEquals("A", mutableGraph.getOriginNode("A->B")); - assertEquals("A", mutableGraph.getOriginNode("A->C")); - assertEquals("B", mutableGraph.getOriginNode("B->C")); - assertEquals("B", mutableGraph.getOriginNode("B->D")); - assertEquals("D", mutableGraph.getOriginNode("D->A")); - assertEquals("D", mutableGraph.getOriginNode("D->B")); - } - - /** - * Tests {@link MutableGraph#getOutboundEdgeCount(Object)} - */ - @Test - @UnitTestMethod(name = "getOutboundEdgeCount", args = { Object.class }) - public void testGetOutboundEdgeCount() { - MutableGraph mutableGraph = new MutableGraph<>(); - mutableGraph.addEdge("A->B", "A", "B"); - mutableGraph.addEdge("A->C", "A", "C"); - mutableGraph.addEdge("B->C", "B", "C"); - mutableGraph.addEdge("B->D", "B", "D"); - mutableGraph.addEdge("D->A", "D", "A"); - mutableGraph.addEdge("D->B", "D", "B"); - - assertEquals(2, mutableGraph.getOutboundEdgeCount("A")); - assertEquals(2, mutableGraph.getOutboundEdgeCount("B")); - assertEquals(0, mutableGraph.getOutboundEdgeCount("C")); - assertEquals(2, mutableGraph.getOutboundEdgeCount("D")); - } - - /** - * Tests {@link MutableGraph#getOutboundEdges(Object)} - */ - @Test - @UnitTestMethod(name = "getOutboundEdges", args = { Object.class }) - public void testGetOutboundEdges() { - MutableGraph mutableGraph = new MutableGraph<>(); - mutableGraph.addEdge("A->B", "A", "B"); - mutableGraph.addEdge("A->C", "A", "C"); - mutableGraph.addEdge("B->C", "B", "C"); - mutableGraph.addEdge("B->D", "B", "D"); - mutableGraph.addEdge("D->A", "D", "A"); - mutableGraph.addEdge("D->B", "D", "B"); - - Set expected = new LinkedHashSet<>(); - expected.add("A->B"); - expected.add("A->C"); - Set actual = mutableGraph.getOutboundEdges("A").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected = new LinkedHashSet<>(); - expected.add("B->C"); - expected.add("B->D"); - actual = mutableGraph.getOutboundEdges("B").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected = new LinkedHashSet<>(); - actual = mutableGraph.getOutboundEdges("C").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - - expected = new LinkedHashSet<>(); - expected.add("D->A"); - expected.add("D->B"); - actual = mutableGraph.getOutboundEdges("D").stream().collect(Collectors.toCollection(LinkedHashSet::new)); - assertEquals(expected, actual); - } - - /** - * Tests {@link MutableGraph#hashCode()} - */ - @Test - @UnitTestMethod(name = "hashCode", args = {}) - public void testHashCode() { - MutableGraph mutableGraph1 = new MutableGraph<>(); - mutableGraph1.addEdge("A->B", "A", "B"); - mutableGraph1.addEdge("A->C", "A", "C"); - mutableGraph1.addEdge("B->C", "B", "C"); - mutableGraph1.addEdge("B->D", "B", "D"); - mutableGraph1.addEdge("D->A", "D", "A"); - mutableGraph1.addEdge("D->B", "D", "B"); - - int expected = "A".hashCode(); - expected += "B".hashCode(); - expected += "C".hashCode(); - expected += "D".hashCode(); - expected += "A->B".hashCode(); - expected += "A->C".hashCode(); - expected += "B->C".hashCode(); - expected += "B->D".hashCode(); - expected += "D->A".hashCode(); - expected += "D->B".hashCode(); - - int actual = mutableGraph1.hashCode(); - assertEquals(expected, actual); - - // build a copy with the edges added in a different order - MutableGraph mutableGraph2 = new MutableGraph<>(); - mutableGraph2.addEdge("D->B", "D", "B"); - mutableGraph2.addEdge("D->A", "D", "A"); - mutableGraph2.addEdge("B->D", "B", "D"); - mutableGraph2.addEdge("B->C", "B", "C"); - mutableGraph2.addEdge("A->C", "A", "C"); - mutableGraph2.addEdge("A->B", "A", "B"); - - assertEquals(mutableGraph1, mutableGraph2); - - // equal objects have equal hash codes - assertEquals(mutableGraph1.hashCode(), mutableGraph2.hashCode()); - - } - - /** - * Tests {@link MutableGraph#isEmpty()} - */ - @Test - @UnitTestMethod(name = "isEmpty", args = {}) - public void testIsEmpty() { - MutableGraph mutableGraph = new MutableGraph<>(); - assertTrue(mutableGraph.isEmpty()); - - mutableGraph.addNode("A"); - assertFalse(mutableGraph.isEmpty()); - - mutableGraph.removeNode("A"); - assertTrue(mutableGraph.isEmpty()); - } - - /** - * Tests {@link MutableGraph#nodeCount()} - */ - @Test - @UnitTestMethod(name = "nodeCount", args = {}) - public void testNodeCount() { - MutableGraph mutableGraph = new MutableGraph<>(); - assertEquals(0, mutableGraph.nodeCount()); - for (int i = 0; i < 10; i++) { - mutableGraph.addNode(i); - assertEquals(i + 1, mutableGraph.nodeCount()); - } - - for (int i = 9; i >= 0; i--) { - mutableGraph.removeNode(i); - assertEquals(i, mutableGraph.nodeCount()); - } - } - - /** - * Tests {@link MutableGraph#removeEdge(Object)} - */ - @Test - @UnitTestMethod(name = "removeEdge", args = { Object.class }) - public void testRemoveEdge() { - MutableGraph mutableGraph = new MutableGraph<>(); - mutableGraph.addEdge("A->B", "A", "B"); - mutableGraph.addEdge("A->C", "A", "C"); - mutableGraph.addEdge("B->C", "B", "C"); - mutableGraph.addEdge("B->D", "B", "D"); - mutableGraph.addEdge("D->A", "D", "A"); - mutableGraph.addEdge("D->B", "D", "B"); - - assertEquals(6, mutableGraph.edgeCount()); - mutableGraph.removeEdge("A->B"); - assertEquals(5, mutableGraph.edgeCount()); - assertFalse(mutableGraph.containsEdge("A->B")); - mutableGraph.removeEdge("A->B"); - assertEquals(5, mutableGraph.edgeCount()); - assertFalse(mutableGraph.containsEdge("A->B")); - - mutableGraph.removeEdge("B->C"); - assertEquals(4, mutableGraph.edgeCount()); - assertFalse(mutableGraph.containsEdge("B->C")); - mutableGraph.removeEdge("B->C"); - assertEquals(4, mutableGraph.edgeCount()); - assertFalse(mutableGraph.containsEdge("B->C")); - } - - /** - * Tests {@link MutableGraph#removeNode(Object)} - */ - @Test - @UnitTestMethod(name = "removeNode", args = { Object.class }) - public void testRemoveNode() { - MutableGraph mutableGraph = new MutableGraph<>(); - mutableGraph.addNode("A"); - mutableGraph.addNode("B"); - mutableGraph.addNode("C"); - mutableGraph.addNode("D"); - mutableGraph.addNode("E"); - mutableGraph.addNode("F"); - - assertEquals(6, mutableGraph.nodeCount()); - mutableGraph.removeNode("A"); - assertEquals(5, mutableGraph.nodeCount()); - assertFalse(mutableGraph.containsNode("A")); - mutableGraph.removeNode("A"); - assertEquals(5, mutableGraph.nodeCount()); - assertFalse(mutableGraph.containsNode("A")); - - assertEquals(5, mutableGraph.nodeCount()); - mutableGraph.removeNode("B"); - assertEquals(4, mutableGraph.nodeCount()); - assertFalse(mutableGraph.containsNode("B")); - mutableGraph.removeNode("B"); - assertEquals(4, mutableGraph.nodeCount()); - assertFalse(mutableGraph.containsNode("B")); - - } - - /** - * Tests {@link MutableGraph#toGraph()} - */ - @Test - @UnitTestMethod(name = "toGraph", args = {}) - public void testToGraph() { - - MutableGraph mutableGraph = new MutableGraph<>(); - mutableGraph.addEdge("A->B", "A", "B"); - mutableGraph.addEdge("A->C", "A", "C"); - mutableGraph.addEdge("B->A", "B", "A"); - mutableGraph.addEdge("B->D", "B", "D"); - mutableGraph.addNode("E"); - - Graph graph = mutableGraph.toGraph(); - - assertEquals(graph, mutableGraph); - - } -} diff --git a/gcm3/src/test/java/util/path/AT_ArrayPathSolver.java b/gcm3/src/test/java/util/path/AT_ArrayPathSolver.java deleted file mode 100644 index 6cf3a6145..000000000 --- a/gcm3/src/test/java/util/path/AT_ArrayPathSolver.java +++ /dev/null @@ -1,187 +0,0 @@ -package util.path; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.graph.Graph; -import util.path.Paths.EdgeCostEvaluator; -import util.path.Paths.TravelCostEvaluator; -import util.vector.Vector2D; - -/** - * Test class for {@link ArrayPathSolver} - * - * @author Shawn Hatch - * - */ -@UnitTest(target = ArrayPathSolver.class) -public class AT_ArrayPathSolver { - /* - * The travel cost will be the straight-line distance - */ - private final static TravelCostEvaluator TRAVEL_COST_EVALUATOR = new TravelCostEvaluator() { - - @Override - public double getMinimumCost(Node originNode, Node destination) { - return originNode.position.distanceTo(destination.position); - } - }; - - private final static EdgeCostEvaluator EDGE_COST_EVALUATOR = new EdgeCostEvaluator() { - - @Override - public double getEdgeCost(Edge edge) { - return edge.cost(); - } - }; - - private static class Edge { - private final Node originNode; - private final Node destinationNode; - private final String name; - - private Edge(Node originNode, Node destinationNode) { - this.originNode = originNode; - this.destinationNode = destinationNode; - this.name = originNode.name + "->" + destinationNode.name; - - } - - private double cost() { - return originNode.position.distanceTo(destinationNode.position); - } - - @Override - public String toString() { - return name; - } - - } - - private static class Node { - private final String name; - private final Vector2D position; - - private Node(String name, Vector2D position) { - this.name = name; - this.position = position; - } - - @Override - public String toString() { - return name; - } - - } - - /** - * Tests - * {@link ArrayPathSolver#ArrayPathSolver(Graph, EdgeCostEvaluator, TravelCostEvaluator)} - */ - @Test - @UnitTestConstructor(args = { Graph.class, EdgeCostEvaluator.class, TravelCostEvaluator.class }) - public void testConstructor() { - Graph.Builder builder = Graph.builder(); - ArrayPathSolver arrayPathSolver = new ArrayPathSolver<>(builder.build(), (e) -> 0.0, (n1, n2) -> 0); - assertNotNull(arrayPathSolver); - } - - /** - * Tests {@link ArrayPathSolver#getPath(Object, Object)} - */ - @Test - @UnitTestMethod(name = "getPath", args = { Object.class, Object.class }) - public void testGetPath() { - - // create a few nodes - Node nodeA = new Node("A", new Vector2D(15, 7)); - Node nodeB = new Node("B", new Vector2D(12, 4)); - Node nodeC = new Node("C", new Vector2D(17, 4)); - Node nodeD = new Node("D", new Vector2D(10, 1)); - Node nodeE = new Node("E", new Vector2D(16, 2)); - Node nodeF = new Node("F", new Vector2D(20, 3)); - Node nodeG = new Node("G", new Vector2D(21, 7)); - - // create a few edges - Edge edgeAB = new Edge(nodeA, nodeB); - Edge edgeAC = new Edge(nodeA, nodeC); - Edge edgeBC = new Edge(nodeB, nodeC); - Edge edgeBD = new Edge(nodeB, nodeD); - Edge edgeCE = new Edge(nodeC, nodeE); - Edge edgeED = new Edge(nodeE, nodeD); - Edge edgeEF = new Edge(nodeE, nodeF); - Edge edgeFG = new Edge(nodeF, nodeG); - Edge edgeGA = new Edge(nodeG, nodeA); - - List edges = new ArrayList<>(); - edges.add(edgeAB); - edges.add(edgeAC); - edges.add(edgeBC); - edges.add(edgeBD); - edges.add(edgeCE); - edges.add(edgeED); - edges.add(edgeEF); - edges.add(edgeFG); - edges.add(edgeGA); - - // build the graph - Graph.Builder graphBuilder = Graph.builder(); - edges.forEach(edge -> graphBuilder.addEdge(edge, edge.originNode, edge.destinationNode)); - Graph graph = graphBuilder.build(); - - PathSolver pathSolver = new ArrayPathSolver<>(graph, EDGE_COST_EVALUATOR, TRAVEL_COST_EVALUATOR); - - Path.Builder pathBuilder = Path.builder(); - - // solve for path from A to D - pathBuilder.addEdge(edgeAB); - pathBuilder.addEdge(edgeBD); - Path expectedPath = pathBuilder.build(); - Optional> optionalPath = pathSolver.getPath(nodeA, nodeD); - assertTrue(optionalPath.isPresent()); - Path actualPath = optionalPath.get(); - assertEquals(expectedPath, actualPath); - - /* - * solve for path from D to F -- there is no such path so the optional - * should not be present. - */ - optionalPath = pathSolver.getPath(nodeD, nodeF); - assertFalse(optionalPath.isPresent()); - - // solve for path from A to A - pathBuilder.addEdge(edgeAC); - pathBuilder.addEdge(edgeCE); - pathBuilder.addEdge(edgeEF); - pathBuilder.addEdge(edgeFG); - pathBuilder.addEdge(edgeGA); - expectedPath = pathBuilder.build(); - optionalPath = pathSolver.getPath(nodeA, nodeA); - assertTrue(optionalPath.isPresent()); - actualPath = optionalPath.get(); - assertEquals(expectedPath, actualPath); - - // solve for path from G to D - pathBuilder.addEdge(edgeGA); - pathBuilder.addEdge(edgeAB); - pathBuilder.addEdge(edgeBD); - expectedPath = pathBuilder.build(); - optionalPath = pathSolver.getPath(nodeG, nodeD); - assertTrue(optionalPath.isPresent()); - actualPath = optionalPath.get(); - assertEquals(expectedPath, actualPath); - - } - -} diff --git a/gcm3/src/test/java/util/path/AT_MapPathSolver.java b/gcm3/src/test/java/util/path/AT_MapPathSolver.java deleted file mode 100644 index ec7922688..000000000 --- a/gcm3/src/test/java/util/path/AT_MapPathSolver.java +++ /dev/null @@ -1,187 +0,0 @@ -package util.path; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.graph.Graph; -import util.path.Paths.EdgeCostEvaluator; -import util.path.Paths.TravelCostEvaluator; -import util.vector.Vector2D; - -/** - * Test class for {@link MapPathSolver} - * - * @author Shawn Hatch - * - */ -@UnitTest(target = MapPathSolver.class) -public class AT_MapPathSolver { - /* - * The travel cost will be the straight-line distance - */ - private final static TravelCostEvaluator TRAVEL_COST_EVALUATOR = new TravelCostEvaluator() { - - @Override - public double getMinimumCost(Node originNode, Node destination) { - return originNode.position.distanceTo(destination.position); - } - }; - - private final static EdgeCostEvaluator EDGE_COST_EVALUATOR = new EdgeCostEvaluator() { - - @Override - public double getEdgeCost(Edge edge) { - return edge.cost(); - } - }; - - private static class Edge { - private final Node originNode; - private final Node destinationNode; - private final String name; - - private Edge(Node originNode, Node destinationNode) { - this.originNode = originNode; - this.destinationNode = destinationNode; - this.name = originNode.name + "->" + destinationNode.name; - - } - - private double cost() { - return originNode.position.distanceTo(destinationNode.position); - } - - @Override - public String toString() { - return name; - } - - } - - private static class Node { - private final String name; - private final Vector2D position; - - private Node(String name, Vector2D position) { - this.name = name; - this.position = position; - } - - @Override - public String toString() { - return name; - } - - } - - /** - * Tests - * {@link MapPathSolver#MapPathSolver(Graph, EdgeCostEvaluator, TravelCostEvaluator)} - */ - @Test - @UnitTestConstructor(args = { Graph.class, EdgeCostEvaluator.class, TravelCostEvaluator.class }) - public void testConstructor() { - Graph.Builder builder = Graph.builder(); - MapPathSolver arrayPathSolver = new MapPathSolver<>(builder.build(), (e) -> 0.0, (n1, n2) -> 0); - assertNotNull(arrayPathSolver); - } - - /** - * Tests {@link MapPathSolver#getPath(Object, Object)} - */ - @Test - @UnitTestMethod(name = "getPath", args = { Object.class, Object.class }) - public void testGetPath() { - - // create a few nodes - Node nodeA = new Node("A", new Vector2D(15, 7)); - Node nodeB = new Node("B", new Vector2D(12, 4)); - Node nodeC = new Node("C", new Vector2D(17, 4)); - Node nodeD = new Node("D", new Vector2D(10, 1)); - Node nodeE = new Node("E", new Vector2D(16, 2)); - Node nodeF = new Node("F", new Vector2D(20, 3)); - Node nodeG = new Node("G", new Vector2D(21, 7)); - - // create a few edges - Edge edgeAB = new Edge(nodeA, nodeB); - Edge edgeAC = new Edge(nodeA, nodeC); - Edge edgeBC = new Edge(nodeB, nodeC); - Edge edgeBD = new Edge(nodeB, nodeD); - Edge edgeCE = new Edge(nodeC, nodeE); - Edge edgeED = new Edge(nodeE, nodeD); - Edge edgeEF = new Edge(nodeE, nodeF); - Edge edgeFG = new Edge(nodeF, nodeG); - Edge edgeGA = new Edge(nodeG, nodeA); - - List edges = new ArrayList<>(); - edges.add(edgeAB); - edges.add(edgeAC); - edges.add(edgeBC); - edges.add(edgeBD); - edges.add(edgeCE); - edges.add(edgeED); - edges.add(edgeEF); - edges.add(edgeFG); - edges.add(edgeGA); - - // build the graph - Graph.Builder graphBuilder = Graph.builder(); - edges.forEach(edge -> graphBuilder.addEdge(edge, edge.originNode, edge.destinationNode)); - Graph graph = graphBuilder.build(); - - PathSolver pathSolver = new MapPathSolver<>(graph, EDGE_COST_EVALUATOR, TRAVEL_COST_EVALUATOR); - - Path.Builder pathBuilder = Path.builder(); - - // solve for path from A to D - pathBuilder.addEdge(edgeAB); - pathBuilder.addEdge(edgeBD); - Path expectedPath = pathBuilder.build(); - Optional> optionalPath = pathSolver.getPath(nodeA, nodeD); - assertTrue(optionalPath.isPresent()); - Path actualPath = optionalPath.get(); - assertEquals(expectedPath, actualPath); - - /* - * solve for path from D to F -- there is no such path so the optional - * should not be present. - */ - optionalPath = pathSolver.getPath(nodeD, nodeF); - assertFalse(optionalPath.isPresent()); - - // solve for path from A to A - pathBuilder.addEdge(edgeAC); - pathBuilder.addEdge(edgeCE); - pathBuilder.addEdge(edgeEF); - pathBuilder.addEdge(edgeFG); - pathBuilder.addEdge(edgeGA); - expectedPath = pathBuilder.build(); - optionalPath = pathSolver.getPath(nodeA, nodeA); - assertTrue(optionalPath.isPresent()); - actualPath = optionalPath.get(); - assertEquals(expectedPath, actualPath); - - // solve for path from G to D - pathBuilder.addEdge(edgeGA); - pathBuilder.addEdge(edgeAB); - pathBuilder.addEdge(edgeBD); - expectedPath = pathBuilder.build(); - optionalPath = pathSolver.getPath(nodeG, nodeD); - assertTrue(optionalPath.isPresent()); - actualPath = optionalPath.get(); - assertEquals(expectedPath, actualPath); - - } - -} diff --git a/gcm3/src/test/java/util/path/AT_Path.java b/gcm3/src/test/java/util/path/AT_Path.java deleted file mode 100644 index d49175d14..000000000 --- a/gcm3/src/test/java/util/path/AT_Path.java +++ /dev/null @@ -1,188 +0,0 @@ -package util.path; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.ArrayList; -import java.util.List; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -/** - * Test class for {@link Path} - * - * @author Shawn Hatch - * - */ -@UnitTest(target = Path.class) -public class AT_Path { - - /** - * Tests {@link Path#builder()} - */ - @Test - @UnitTestMethod(name = "builder", args = {}) - public void testBuilder() { - Path.Builder builder = Path.builder(); - Path path = builder.build(); - assertNotNull(path); - - builder.addEdge(1); - builder.addEdge(3); - builder.addEdge(4); - builder.addEdge(5); - builder.addEdge(6); - builder.addEdge(1); - path = builder.build(); - assertNotNull(path); - - } - - /** - * Tests {@link Path#equals(Object)} - */ - @Test - @UnitTestMethod(name = "equals", args = { Object.class }) - public void testEquals() { - Path.Builder builder = Path.builder(); - - builder.addEdge(1); - builder.addEdge(3); - builder.addEdge(4); - builder.addEdge(5); - builder.addEdge(6); - builder.addEdge(1); - Path path1 = builder.build(); - - builder.addEdge(1); - builder.addEdge(3); - builder.addEdge(4); - builder.addEdge(5); - builder.addEdge(6); - builder.addEdge(1); - Path path2 = builder.build(); - - assertEquals(path1, path2); - assertEquals(path1, path1); - assertEquals(path2, path1); - - builder.addEdge(1); - builder.addEdge(3); - builder.addEdge(4); - builder.addEdge(5); - builder.addEdge(6); - // builder.addEdge(1); - - Path path3 = builder.build(); - assertNotEquals(path1, path3); - - } - - /** - * Tests {@link Path#hashCode()} - */ - @Test - @UnitTestMethod(name = "hashCode", args = {}) - public void testHashCode() { - Path.Builder builder = Path.builder(); - - builder.addEdge(1); - builder.addEdge(3); - builder.addEdge(4); - builder.addEdge(5); - builder.addEdge(6); - builder.addEdge(1); - Path path1 = builder.build(); - - builder.addEdge(1); - builder.addEdge(3); - builder.addEdge(4); - builder.addEdge(5); - builder.addEdge(6); - builder.addEdge(1); - Path path2 = builder.build(); - - assertEquals(path1, path2); - - assertEquals(path1.hashCode(), path2.hashCode()); - - } - - /** - * Tests {@link Path#getEdges()} - */ - @Test - @UnitTestMethod(name = "getEdges", args = {}) - public void testGetEdges() { - Path.Builder builder = Path.builder(); - - List expected = new ArrayList<>(); - - expected.add(1); - expected.add(3); - expected.add(4); - expected.add(5); - expected.add(6); - expected.add(1); - - for (Integer edge : expected) { - builder.addEdge(edge); - } - - Path path = builder.build(); - List actual = path.getEdges(); - assertEquals(expected, actual); - - } - - /** - * Tests {@link Path#isEmpty()} - */ - @Test - @UnitTestMethod(name = "isEmpty", args = {}) - public void testIsEmpty() { - Path.Builder builder = Path.builder(); - Path path = builder.build(); - assertTrue(path.isEmpty()); - - builder.addEdge(1); - path = builder.build(); - assertFalse(path.isEmpty()); - } - - /** - * Tests {@link Path#length()} - */ - @Test - @UnitTestMethod(name = "length", args = {}) - public void testLength() { - Path.Builder builder = Path.builder(); - Path path = builder.build(); - assertEquals(0, path.length()); - - builder.addEdge(1); - builder.addEdge(2); - builder.addEdge(3); - builder.addEdge(4); - builder.addEdge(5); - builder.addEdge(6); - path = builder.build(); - assertEquals(6, path.length()); - - builder.addEdge(1); - builder.addEdge(2); - builder.addEdge(3); - builder.addEdge(2); - builder.addEdge(1); - path = builder.build(); - assertEquals(5, path.length()); - - } - -} \ No newline at end of file diff --git a/gcm3/src/test/java/util/path/AT_Paths.java b/gcm3/src/test/java/util/path/AT_Paths.java deleted file mode 100644 index b309f6207..000000000 --- a/gcm3/src/test/java/util/path/AT_Paths.java +++ /dev/null @@ -1,201 +0,0 @@ -package util.path; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.graph.Graph; -import util.path.Paths.EdgeCostEvaluator; -import util.path.Paths.TravelCostEvaluator; -import util.vector.Vector2D; - -/** - * Test class for {@link Paths} - * - * @author Shawn Hatch - * - */ -@UnitTest(target = Paths.class) -public class AT_Paths { - - /** - * Tests {@link Paths#getCost(Path, EdgeCostEvaluator)} - */ - @Test - @UnitTestMethod(name = "getCost", args = { Path.class, EdgeCostEvaluator.class }) - public void testGetCost() { - Path.Builder builder = Path.builder(); - Path path = builder.build(); - assertEquals(0, Paths.getCost(path, (edge) -> 1), 0); - - builder.addEdge("A->B"); - builder.addEdge("B->C"); - builder.addEdge("C->D"); - path = builder.build(); - assertEquals(3, Paths.getCost(path, (edge) -> 1), 0); - - builder.addEdge("A->B"); - builder.addEdge("B->C"); - builder.addEdge("C->D"); - path = builder.build(); - assertEquals(2, Paths.getCost(path, (edge) -> { - if (edge.contains("B")) { - return 1; - } - return 0; - }), 0); - - } - - private static class Edge { - private final Node originNode; - private final Node destinationNode; - private final String name; - - private Edge(Node originNode, Node destinationNode) { - this.originNode = originNode; - this.destinationNode = destinationNode; - this.name = originNode.name + "->" + destinationNode.name; - - } - - private double cost() { - return originNode.position.distanceTo(destinationNode.position); - } - - @Override - public String toString() { - return name; - } - - } - - private static class Node { - private final String name; - private final Vector2D position; - - private Node(String name, Vector2D position) { - this.name = name; - this.position = position; - } - - @Override - public String toString() { - return name; - } - - } - - /* - * The travel cost will be the straight-line distance - */ - private final static TravelCostEvaluator TRAVEL_COST_EVALUATOR = new TravelCostEvaluator() { - - @Override - public double getMinimumCost(Node originNode, Node destination) { - return originNode.position.distanceTo(destination.position); - } - }; - - private final static EdgeCostEvaluator EDGE_COST_EVALUATOR = new EdgeCostEvaluator() { - - @Override - public double getEdgeCost(Edge edge) { - return edge.cost(); - } - }; - - /** - * Tests - * {@link Paths#getPath(Graph, Object, Object, EdgeCostEvaluator, TravelCostEvaluator)} - */ - @Test - @UnitTestMethod(name = "getPath", args = { Graph.class, Object.class, Object.class, EdgeCostEvaluator.class, TravelCostEvaluator.class }) - public void testGetPath() { - - // create a few nodes - Node nodeA = new Node("A", new Vector2D(15, 7)); - Node nodeB = new Node("B", new Vector2D(12, 4)); - Node nodeC = new Node("C", new Vector2D(17, 4)); - Node nodeD = new Node("D", new Vector2D(10, 1)); - Node nodeE = new Node("E", new Vector2D(16, 2)); - Node nodeF = new Node("F", new Vector2D(20, 3)); - Node nodeG = new Node("G", new Vector2D(21, 7)); - - // create a few edges - Edge edgeAB = new Edge(nodeA, nodeB); - Edge edgeAC = new Edge(nodeA, nodeC); - Edge edgeBC = new Edge(nodeB, nodeC); - Edge edgeBD = new Edge(nodeB, nodeD); - Edge edgeCE = new Edge(nodeC, nodeE); - Edge edgeED = new Edge(nodeE, nodeD); - Edge edgeEF = new Edge(nodeE, nodeF); - Edge edgeFG = new Edge(nodeF, nodeG); - Edge edgeGA = new Edge(nodeG, nodeA); - - List edges = new ArrayList<>(); - edges.add(edgeAB); - edges.add(edgeAC); - edges.add(edgeBC); - edges.add(edgeBD); - edges.add(edgeCE); - edges.add(edgeED); - edges.add(edgeEF); - edges.add(edgeFG); - edges.add(edgeGA); - - // build the graph - Graph.Builder graphBuilder = Graph.builder(); - edges.forEach(edge -> graphBuilder.addEdge(edge, edge.originNode, edge.destinationNode)); - Graph graph = graphBuilder.build(); - - Path.Builder pathBuilder = Path.builder(); - - // solve for path from A to D - pathBuilder.addEdge(edgeAB); - pathBuilder.addEdge(edgeBD); - Path expectedPath = pathBuilder.build(); - Optional> optionalPath = Paths.getPath(graph, nodeA, nodeD, EDGE_COST_EVALUATOR, TRAVEL_COST_EVALUATOR); - assertTrue(optionalPath.isPresent()); - Path actualPath = optionalPath.get(); - assertEquals(expectedPath, actualPath); - - /* - * solve for path from D to F -- there is no such path so the optional - * should not be present. - */ - optionalPath = Paths.getPath(graph, nodeD, nodeF, EDGE_COST_EVALUATOR, TRAVEL_COST_EVALUATOR); - assertFalse(optionalPath.isPresent()); - - // solve for path from A to A - pathBuilder.addEdge(edgeAC); - pathBuilder.addEdge(edgeCE); - pathBuilder.addEdge(edgeEF); - pathBuilder.addEdge(edgeFG); - pathBuilder.addEdge(edgeGA); - expectedPath = pathBuilder.build(); - optionalPath = Paths.getPath(graph, nodeA, nodeA, EDGE_COST_EVALUATOR, TRAVEL_COST_EVALUATOR); - assertTrue(optionalPath.isPresent()); - actualPath = optionalPath.get(); - assertEquals(expectedPath, actualPath); - - // solve for path from G to D - pathBuilder.addEdge(edgeGA); - pathBuilder.addEdge(edgeAB); - pathBuilder.addEdge(edgeBD); - expectedPath = pathBuilder.build(); - optionalPath = Paths.getPath(graph, nodeG, nodeD, EDGE_COST_EVALUATOR, TRAVEL_COST_EVALUATOR); - assertTrue(optionalPath.isPresent()); - actualPath = optionalPath.get(); - assertEquals(expectedPath, actualPath); - - } -} diff --git a/gcm3/src/test/java/util/random/AT_RandomGeneratorProvider.java b/gcm3/src/test/java/util/random/AT_RandomGeneratorProvider.java deleted file mode 100644 index ba152d4fd..000000000 --- a/gcm3/src/test/java/util/random/AT_RandomGeneratorProvider.java +++ /dev/null @@ -1,37 +0,0 @@ -package util.random; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; - -@UnitTest(target = RandomGeneratorProvider.class) -public class AT_RandomGeneratorProvider { - - @Test - @UnitTestMethod(name = "getRandomGenerator", args = { long.class }) - public void testGetRandomGenerator() { - //show that a random generator is returned and that each is different, but repeatable - Set initialValues = new LinkedHashSet<>(); - for (long seed = 0L; seed < 10L; seed++) { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); - assertNotNull(randomGenerator); - boolean added = initialValues.add(randomGenerator.nextLong()); - assertTrue(added); - - RandomGenerator duplicateRandomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); - assertNotNull(duplicateRandomGenerator); - added = initialValues.add(duplicateRandomGenerator.nextLong()); - assertFalse(added); - - } - } -} diff --git a/gcm3/src/test/java/util/spherical/AT_SphericalArc.java b/gcm3/src/test/java/util/spherical/AT_SphericalArc.java deleted file mode 100644 index 4ea145dcc..000000000 --- a/gcm3/src/test/java/util/spherical/AT_SphericalArc.java +++ /dev/null @@ -1,540 +0,0 @@ -package util.spherical; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.apache.commons.math3.random.RandomGenerator; -import org.apache.commons.math3.util.FastMath; -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.random.RandomGeneratorProvider; -import util.vector.Vector3D; - -/** - * Test class for {@link SphericalArc} - * - * @author Shawn Hatch - * - */ -@UnitTest(target = SphericalArc.class) -public class AT_SphericalArc { - - - - /** - * Tests {@link SphericalArc#SphericalArc(SphericalPoint, SphericalPoint)} - */ - @Test - @UnitTestConstructor(args = { SphericalPoint.class, SphericalPoint.class }) - public void testConstructor() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1864570857384195815L); - - double x1 = randomGenerator.nextDouble() * 2 - 1; - double y1 = randomGenerator.nextDouble() * 2 - 1; - double z1 = randomGenerator.nextDouble() * 2 - 1; - - SphericalPoint sphericalPoint1 = new SphericalPoint(new Vector3D(x1, y1, z1)); - - double x2 = randomGenerator.nextDouble() * 2 - 1; - double y2 = randomGenerator.nextDouble() * 2 - 1; - double z2 = randomGenerator.nextDouble() * 2 - 1; - - SphericalPoint sphericalPoint2 = new SphericalPoint(new Vector3D(x2, y2, z2)); - - assertThrows(NullPointerException.class, () -> new SphericalArc(sphericalPoint1, null)); - assertThrows(NullPointerException.class, () -> new SphericalArc(null, sphericalPoint2)); - assertThrows(NullPointerException.class, () -> new SphericalArc(null, null)); - assertThrows(MalformedSphericalArcException.class, () -> new SphericalArc(sphericalPoint1, sphericalPoint1)); - - } - - /** - * Tests {@link SphericalArc#getLength()} - */ - @Test - @UnitTestMethod(name = "getLength", args = {}) - public void testGetLength() { - for (int i = 0; i < 100; i++) { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(675015272753785672L); - double x1 = randomGenerator.nextDouble() * 2 - 1; - double y1 = randomGenerator.nextDouble() * 2 - 1; - double z1 = randomGenerator.nextDouble() * 2 - 1; - Vector3D v1 = new Vector3D(x1, y1, z1); - SphericalPoint sphericalPoint1 = new SphericalPoint(v1); - - double x2 = randomGenerator.nextDouble() * 2 - 1; - double y2 = randomGenerator.nextDouble() * 2 - 1; - double z2 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v2 = new Vector3D(x2, y2, z2); - SphericalPoint sphericalPoint2 = new SphericalPoint(v2); - - SphericalArc sphericalArc = new SphericalArc(sphericalPoint1, sphericalPoint2); - assertEquals(v1.angle(v2), sphericalArc.getLength(), Vector3D.NORMAL_LENGTH_TOLERANCE); - - } - - } - - /** - * Tests {@link SphericalArc#distanceTo(SphericalPoint)} - */ - @Test - @UnitTestMethod(name = "distanceTo", args = { SphericalPoint.class }) - public void testDistanceTo() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8163625096438805694L); - - double x = randomGenerator.nextDouble() * 2 - 1; - double y = randomGenerator.nextDouble() * 2 - 1; - double z = randomGenerator.nextDouble() * 2 - 1; - - SphericalPoint sphericalPointA = new SphericalPoint(new Vector3D(x, y, z)); - x = randomGenerator.nextDouble() * 2 - 1; - y = randomGenerator.nextDouble() * 2 - 1; - z = randomGenerator.nextDouble() * 2 - 1; - - SphericalPoint sphericalPointB = new SphericalPoint(new Vector3D(x, y, z)); - - SphericalArc sphericalArc1 = new SphericalArc(sphericalPointA, sphericalPointB); - - /* - * Precondition test - * - */ - - assertThrows(NullPointerException.class, () -> sphericalArc1.distanceTo(null)); - - for (int i = 0; i < 100; i++) { - // Generate two randomized points and generate a arc - - double x1 = randomGenerator.nextDouble() * 2 - 1; - double y1 = randomGenerator.nextDouble() * 2 - 1; - double z1 = randomGenerator.nextDouble() * 2 - 1; - Vector3D v1 = new Vector3D(x1, y1, z1); - SphericalPoint sphericalPoint1 = new SphericalPoint(v1); - - double x2 = randomGenerator.nextDouble() * 2 - 1; - double y2 = randomGenerator.nextDouble() * 2 - 1; - double z2 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v2 = new Vector3D(x2, y2, z2); - SphericalPoint sphericalPoint2 = new SphericalPoint(v2); - - SphericalArc sphericalArc = new SphericalArc(sphericalPoint1, sphericalPoint2); - - /* - * Our strategy will be to create a randomized third point where we - * can surmise the distance without having to use the same - * algorithm. This will have us start with one of the points, then - * rotate it toward the other point and finally rotate that away - * from the plane containing the arc. Two case will be used: first - * we will consider points that are closer to the body of the arc - * than the either end point, second we will consider points that - * are closer to one of the end points. - */ - - /* - * First, generate a point that is closer to the arc between the - * points than to either end point. - */ - - double angle = v2.angle(v1); - angle *= randomGenerator.nextDouble(); - /* - * v3 is a point on the arc between the end points - */ - Vector3D v3 = v1.rotateToward(v2, angle); - /* - * perp is a randomized vector that is perpendicular to the plane - * containing the arc - */ - Vector3D perp = v1.cross(v2).normalize(); - if (randomGenerator.nextBoolean()) { - perp = perp.reverse(); - } - /* - * We now rotate v3 out of the plane by some random angle [-90, 90) - * degrees. This angle will be the expected distance to the arc. - */ - angle = (randomGenerator.nextDouble() - 0.5) * FastMath.PI; - - /* - * The absolute value of this angle will be the expected distance - * from the arc to v3 - */ - double expectedDistance = FastMath.abs(angle); - - v3 = v3.rotateToward(perp, angle); - - double actualDistance = sphericalArc.distanceTo(new SphericalPoint(v3)); - - assertEquals(expectedDistance, actualDistance, 1E-10, Integer.toString(i)); - - /* - * Next, generate a point that is closer to one of the end points. - * We rotate v1 toward v2 by [angle,2PI-angle) radians. - */ - angle = v2.angle(v1); - angle += randomGenerator.nextDouble() * (2 * FastMath.PI - angle); - /* - * v3 is a point on the arc that does not lie between the end points - */ - v3 = v1.rotateToward(v2, angle); - - /* - * We now rotate v3 out of the plane by some random angle [-90, 90) - * degrees. - */ - angle = (randomGenerator.nextDouble() - 0.5) * FastMath.PI; - - v3 = v3.rotateToward(perp, angle); - - /* - * We expect the distance from v3 to the arc will be the smallest - * angle from v3 to either v1 or v2. - */ - expectedDistance = FastMath.min(v3.angle(v1), v3.angle(v2)); - - actualDistance = sphericalArc.distanceTo(new SphericalPoint(v3)); - - assertEquals(expectedDistance, actualDistance, Vector3D.NORMAL_LENGTH_TOLERANCE, Integer.toString(i)); - - } - } - - /** - * Tests {@link SphericalArc#getInterSection(SphericalArc)} - */ - @Test - @UnitTestMethod(name = "getInterSection", args = { SphericalArc.class }) - public void testGetInterSection() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(22569187792469557L); - - double x = randomGenerator.nextDouble() * 2 - 1; - double y = randomGenerator.nextDouble() * 2 - 1; - double z = randomGenerator.nextDouble() * 2 - 1; - - SphericalPoint sphericalPointA = new SphericalPoint(new Vector3D(x, y, z)); - x = randomGenerator.nextDouble() * 2 - 1; - y = randomGenerator.nextDouble() * 2 - 1; - z = randomGenerator.nextDouble() * 2 - 1; - - SphericalPoint sphericalPointB = new SphericalPoint(new Vector3D(x, y, z)); - - SphericalArc sphericalArc = new SphericalArc(sphericalPointA, sphericalPointB); - - /* - * Precondition test - */ - - assertThrows(NullPointerException.class, () -> sphericalArc.getInterSection(null)); - - /* - * Initialize a counter for the number of crossed arcs that are in the - * test. This will help show that the test encounters a variety of - * crossing and non-crossing arcs. - */ - int positiveCrossCount = 0; - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 2 - 1; - double y1 = randomGenerator.nextDouble() * 2 - 1; - double z1 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v1 = new Vector3D(x1, y1, z1); - SphericalPoint sphericalPoint1 = new SphericalPoint(v1); - - double x2 = randomGenerator.nextDouble() * 2 - 1; - double y2 = randomGenerator.nextDouble() * 2 - 1; - double z2 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v2 = new Vector3D(x2, y2, z2); - SphericalPoint sphericalPoint2 = new SphericalPoint(v2); - - SphericalArc sphericalArc12 = new SphericalArc(sphericalPoint1, sphericalPoint2); - - double x3 = randomGenerator.nextDouble() * 2 - 1; - double y3 = randomGenerator.nextDouble() * 2 - 1; - double z3 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v3 = new Vector3D(x3, y3, z3); - SphericalPoint sphericalPoint3 = new SphericalPoint(v3); - - double x4 = randomGenerator.nextDouble() * 2 - 1; - double y4 = randomGenerator.nextDouble() * 2 - 1; - double z4 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v4 = new Vector3D(x4, y4, z4); - SphericalPoint sphericalPoint4 = new SphericalPoint(v4); - - SphericalArc sphericalArc34 = new SphericalArc(sphericalPoint3, sphericalPoint4); - - /* - * Derive the vectors that are normal to the planes of each arc - */ - Vector3D p12 = v1.cross(v2); - - Vector3D p34 = v3.cross(v4); - - /* - * If the arcs cross, then from the perspective of each arc, the - * other arc's end points must lie on opposite sides of the arc. - * This will mean that the dot product of the normal for an arc with - * each end points of the other arc will have opposite signs. Thus - * the product of the two dot products cannot be positive. - */ - - boolean arc12SplitsArc34 = p12.dot(v3) * p12.dot(v4) <= 0; - boolean arc342SplitsArc12 = p34.dot(v1) * p34.dot(v2) <= 0; - boolean expectIntersection = arc12SplitsArc34 && arc342SplitsArc12; - if (expectIntersection) { - positiveCrossCount++; - SphericalPoint interSectionPoint = sphericalArc12.getInterSection(sphericalArc34); - - assertNotNull(interSectionPoint, Integer.toString(i)); - - // we will now show that the intersection point is on both arcs - - Vector3D intersectionPosition = interSectionPoint.getPosition(); - - // is the intersection on the plane of both arcs? - Vector3D perp = v1.cross(v2); - assertEquals(FastMath.PI / 2, intersectionPosition.angle(perp), 1E-10, Integer.toString(i)); - - } - - } - // show that the number of crossed arcs that were tested is reasonably - // high - assertTrue(positiveCrossCount > 10); - - } - - /** - * Tests {@link SphericalArc#getSphericalPoint(int)} - */ - @Test - @UnitTestMethod(name = "getSphericalPoint", args = { int.class }) - public void testGetSphericalPoint() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4795695625801637985L); - - for (int i = 0; i < 100; i++) { - double x0 = randomGenerator.nextDouble() * 2 - 1; - double y0 = randomGenerator.nextDouble() * 2 - 1; - double z0 = randomGenerator.nextDouble() * 2 - 1; - - SphericalPoint sphericalPoint0 = new SphericalPoint(new Vector3D(x0, y0, z0)); - - double x1 = randomGenerator.nextDouble() * 2 - 1; - double y1 = randomGenerator.nextDouble() * 2 - 1; - double z1 = randomGenerator.nextDouble() * 2 - 1; - - SphericalPoint sphericalPoint1 = new SphericalPoint(new Vector3D(x1, y1, z1)); - - SphericalArc sphericalArc = new SphericalArc(sphericalPoint0, sphericalPoint1); - - SphericalPoint actualSphericalPoint0 = sphericalArc.getSphericalPoint(0); - assertEquals(sphericalPoint0, actualSphericalPoint0); - - SphericalPoint actualSphericalPoint1 = sphericalArc.getSphericalPoint(1); - assertEquals(sphericalPoint1, actualSphericalPoint1); - - // precondition tests - assertThrows(IndexOutOfBoundsException.class, () -> sphericalArc.getSphericalPoint(-1)); - assertThrows(IndexOutOfBoundsException.class, () -> sphericalArc.getSphericalPoint(2)); - } - - } - - /** - * Tests {@link SphericalArc#intersectsArc(SphericalArc)} - */ - @Test - @UnitTestMethod(name = "intersectsArc", args = { SphericalArc.class }) - public void testIntersectsArc() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7924664054024487057L); - - double x = randomGenerator.nextDouble() * 2 - 1; - double y = randomGenerator.nextDouble() * 2 - 1; - double z = randomGenerator.nextDouble() * 2 - 1; - - SphericalPoint sphericalPointA = new SphericalPoint(new Vector3D(x, y, z)); - x = randomGenerator.nextDouble() * 2 - 1; - y = randomGenerator.nextDouble() * 2 - 1; - z = randomGenerator.nextDouble() * 2 - 1; - - SphericalPoint sphericalPointB = new SphericalPoint(new Vector3D(x, y, z)); - - SphericalArc sphericalArc = new SphericalArc(sphericalPointA, sphericalPointB); - - /* - * Precondition test - */ - - assertThrows(NullPointerException.class, () -> sphericalArc.intersectsArc(null)); - - /* - * Initialize a counter for the number of crossed arcs that are in the - * test. This will help show that the test encounters a variety of - * crossing and non-crossing arcs. - */ - int positiveCrossCount = 0; - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 2 - 1; - double y1 = randomGenerator.nextDouble() * 2 - 1; - double z1 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v1 = new Vector3D(x1, y1, z1); - SphericalPoint sphericalPoint1 = new SphericalPoint(v1); - - double x2 = randomGenerator.nextDouble() * 2 - 1; - double y2 = randomGenerator.nextDouble() * 2 - 1; - double z2 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v2 = new Vector3D(x2, y2, z2); - SphericalPoint sphericalPoint2 = new SphericalPoint(v2); - - SphericalArc sphericalArc12 = new SphericalArc(sphericalPoint1, sphericalPoint2); - - double x3 = randomGenerator.nextDouble() * 2 - 1; - double y3 = randomGenerator.nextDouble() * 2 - 1; - double z3 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v3 = new Vector3D(x3, y3, z3); - SphericalPoint sphericalPoint3 = new SphericalPoint(v3); - - double x4 = randomGenerator.nextDouble() * 2 - 1; - double y4 = randomGenerator.nextDouble() * 2 - 1; - double z4 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v4 = new Vector3D(x4, y4, z4); - SphericalPoint sphericalPoint4 = new SphericalPoint(v4); - - SphericalArc sphericalArc34 = new SphericalArc(sphericalPoint3, sphericalPoint4); - - /* - * Derive the vectors that are normal to the planes of each arc - */ - Vector3D p12 = v1.cross(v2); - - Vector3D p34 = v3.cross(v4); - - /* - * If the arcs cross, then from the perspective of each arc, the - * other arc's end points must lie on opposite sides of the arc. - * This will mean that the dot product of the normal for an arc with - * each end points of the other arc will have opposite signs. Thus - * the product of the two dot products cannot be positive. - */ - - boolean arc12SplitsArc34 = p12.dot(v3) * p12.dot(v4) <= 0; - boolean arc342SplitsArc12 = p34.dot(v1) * p34.dot(v2) <= 0; - boolean expected = arc12SplitsArc34 && arc342SplitsArc12; - if (expected) { - positiveCrossCount++; - } - boolean actual = sphericalArc12.intersectsArc(sphericalArc34); - - assertEquals(expected, actual); - - } - // show that the number of crossed arcs that were tested is reasonably - // high - assertTrue(positiveCrossCount > 10); - - } - - /** - * Tests {@link SphericalArc#getChirality(SphericalPoint)} - */ - @Test - @UnitTestMethod(name = "getChirality", args = { SphericalPoint.class }) - public void testGetChirality() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5793327393482219366L); - for (int i = 0; i < 100; i++) { - double x1 = randomGenerator.nextDouble() * 2 - 1; - double y1 = randomGenerator.nextDouble() * 2 - 1; - double z1 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v1 = new Vector3D(x1, y1, z1); - SphericalPoint sphericalPoint1 = new SphericalPoint(v1); - - double x2 = randomGenerator.nextDouble() * 2 - 1; - double y2 = randomGenerator.nextDouble() * 2 - 1; - double z2 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v2 = new Vector3D(x2, y2, z2); - SphericalPoint sphericalPoint2 = new SphericalPoint(v2); - - SphericalArc sphericalArc = new SphericalArc(sphericalPoint1, sphericalPoint2); - - double x3 = randomGenerator.nextDouble() * 2 - 1; - double y3 = randomGenerator.nextDouble() * 2 - 1; - double z3 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v3 = new Vector3D(x3, y3, z3); - SphericalPoint sphericalPoint3 = new SphericalPoint(v3); - - // precondition test - assertThrows(NullPointerException.class, () -> sphericalArc.getChirality(null)); - - Vector3D perp = v1.cross(v2); - Chirality expectedChirality; - if (perp.dot(v3) >= 0) { - expectedChirality = Chirality.RIGHT_HANDED; - } else { - expectedChirality = Chirality.LEFT_HANDED; - } - - Chirality actualChirality = sphericalArc.getChirality(sphericalPoint3); - assertEquals(expectedChirality, actualChirality); - } - } - - /** - * Tests {@link SphericalArc#toString()} - */ - @Test - @UnitTestMethod(name = "toString", args = {}) - public void testToString() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8407732758361446047L); - - for (int i = 0; i < 100; i++) { - double x0 = randomGenerator.nextDouble() * 2 - 1; - double y0 = randomGenerator.nextDouble() * 2 - 1; - double z0 = randomGenerator.nextDouble() * 2 - 1; - - SphericalPoint sphericalPoint0 = new SphericalPoint(new Vector3D(x0, y0, z0)); - - double x1 = randomGenerator.nextDouble() * 2 - 1; - double y1 = randomGenerator.nextDouble() * 2 - 1; - double z1 = randomGenerator.nextDouble() * 2 - 1; - - SphericalPoint sphericalPoint1 = new SphericalPoint(new Vector3D(x1, y1, z1)); - - SphericalArc sphericalArc = new SphericalArc(sphericalPoint0, sphericalPoint1); - - StringBuilder sb = new StringBuilder(); - sb.append("SphericalArc [sphericalPoints=["); - sb.append(sphericalPoint0); - sb.append(", "); - sb.append(sphericalPoint1); - sb.append("]]"); - - String expected = sb.toString(); - - String actual = sphericalArc.toString(); - - assertEquals(expected, actual); - - } - - } - -} diff --git a/gcm3/src/test/java/util/spherical/AT_SphericalPoint.java b/gcm3/src/test/java/util/spherical/AT_SphericalPoint.java deleted file mode 100644 index 8eadb048d..000000000 --- a/gcm3/src/test/java/util/spherical/AT_SphericalPoint.java +++ /dev/null @@ -1,130 +0,0 @@ -package util.spherical; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.apache.commons.math3.random.RandomGenerator; -import org.apache.commons.math3.util.FastMath; -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.random.RandomGeneratorProvider; -import util.vector.MutableVector3D; -import util.vector.Vector3D; - -/** - * Test class for {@link SphericalPoint} - * - * @author Shawn Hatch - * - */ -@UnitTest(target = SphericalPoint.class) -public class AT_SphericalPoint { - - - - /** - * Tests {@link SphericalPoint#SphericalPoint(MutableVector3D)} - */ - @Test - @UnitTestConstructor(args = { MutableVector3D.class }) - public void testConstructors_MutableVector3D() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1115082964305662816L); - - for (int i = 0; i < 100; i++) { - double x = randomGenerator.nextDouble() * 2 - 1; - double y = randomGenerator.nextDouble() * 2 - 1; - double z = randomGenerator.nextDouble() * 2 - 1; - - double length = FastMath.sqrt(x * x + y * y + z * z); - SphericalPoint sphericalPoint = new SphericalPoint(new MutableVector3D(x, y, z)); - - assertTrue(FastMath.abs(x / length - sphericalPoint.getPosition().getX()) < Vector3D.NORMAL_LENGTH_TOLERANCE); - assertTrue(FastMath.abs(y / length - sphericalPoint.getPosition().getY()) < Vector3D.NORMAL_LENGTH_TOLERANCE); - assertTrue(FastMath.abs(z / length - sphericalPoint.getPosition().getZ()) < Vector3D.NORMAL_LENGTH_TOLERANCE); - - } - - } - - /** - * Tests {@link SphericalPoint#SphericalPoint(Vector3D)} - */ - @Test - @UnitTestConstructor(args = { Vector3D.class }) - public void testConstructors_Vector3D() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1115082964305662816L); - - for (int i = 0; i < 100; i++) { - double x = randomGenerator.nextDouble() * 2 - 1; - double y = randomGenerator.nextDouble() * 2 - 1; - double z = randomGenerator.nextDouble() * 2 - 1; - - double length = FastMath.sqrt(x * x + y * y + z * z); - SphericalPoint sphericalPoint = new SphericalPoint(new Vector3D(x, y, z)); - - assertTrue(FastMath.abs(x / length - sphericalPoint.getPosition().getX()) < Vector3D.NORMAL_LENGTH_TOLERANCE); - assertTrue(FastMath.abs(y / length - sphericalPoint.getPosition().getY()) < Vector3D.NORMAL_LENGTH_TOLERANCE); - assertTrue(FastMath.abs(z / length - sphericalPoint.getPosition().getZ()) < Vector3D.NORMAL_LENGTH_TOLERANCE); - - assertThrows(MalformedSphericalPointException.class, () -> new SphericalPoint(new Vector3D(0, 0, 0))); - assertThrows(MalformedSphericalPointException.class, () -> new SphericalPoint(new MutableVector3D(0, 0, 0))); - - } - - // precondition tests - - } - - /** - * Tests {@link SphericalPoint#getPosition()} - */ - @Test - @UnitTestMethod(name = "getPosition", args = {}) - public void testGetPosition() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4303335398336843747L); - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 2 - 1; - double y = randomGenerator.nextDouble() * 2 - 1; - double z = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v = new Vector3D(x, y, z); - SphericalPoint sphericalPoint = new SphericalPoint(v); - v = v.normalize(); - Vector3D u = sphericalPoint.getPosition(); - assertEquals(v, u); - } - - } - - /** - * Tests {@link SphericalPoint#toString()} - */ - @Test - @UnitTestMethod(name = "toString", args = {}) - public void testToString() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4890458493568164342L); - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 2 - 1; - double y = randomGenerator.nextDouble() * 2 - 1; - double z = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v = new Vector3D(x, y, z); - SphericalPoint sphericalPoint = new SphericalPoint(v); - v = v.normalize(); - - String expected = v.toString(); - expected = "SphericalPoint [position=" + expected + "]"; - - String actual = sphericalPoint.toString(); - assertEquals(expected, actual); - } - - } - -} diff --git a/gcm3/src/test/java/util/spherical/AT_SphericalPolygon.java b/gcm3/src/test/java/util/spherical/AT_SphericalPolygon.java deleted file mode 100644 index af55c2df6..000000000 --- a/gcm3/src/test/java/util/spherical/AT_SphericalPolygon.java +++ /dev/null @@ -1,610 +0,0 @@ -package util.spherical; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -import org.apache.commons.math3.random.RandomGenerator; -import org.apache.commons.math3.util.FastMath; -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.earth.Earth; -import util.earth.LatLon; -import util.random.RandomGeneratorProvider; -import util.spherical.SphericalPolygon.Builder; -import util.vector.Vector3D; - -/** - * Test class for {@link SphericalPolygon} - * - * @author Shawn Hatch - * - */ -@UnitTest(target = SphericalPolygon.class) -public class AT_SphericalPolygon { - - - private static SphericalPoint generateRandomizedSphericalPoint(RandomGenerator randomGenerator) { - double x = randomGenerator.nextDouble() * 2 - 1; - double y = randomGenerator.nextDouble() * 2 - 1; - double z = randomGenerator.nextDouble() * 2 - 1; - return new SphericalPoint(new Vector3D(x, y, z)); - } - - private static LocalBuilder localBuilder() { - return new LocalBuilder(); - } - - private static class LocalBuilder { - private Earth earth = Earth.fromMeanRadius(); - private SphericalPolygon.Builder builder = SphericalPolygon.builder(); - - public LocalBuilder add(double lat, double lon) { - builder.addSphericalPoint(new SphericalPoint(earth.getECCFromLatLon(new LatLon(lat, lon)))); - return this; - } - - public SphericalPolygon build() { - return builder.build(); - } - } - - /** - * Tests {@link SphericalPolygon#builder()} - */ - @Test - @UnitTestMethod(name = "builder", args = {}) - public void testBuilder() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5068048243871963894L); - - // Show that an empty set of vertices will throw a - // MalformedSphericalPolygonException - Builder builder = SphericalPolygon.builder(); - assertThrows(MalformedSphericalPolygonException.class, () -> builder.build()); - - // Show that a single vertex will throw a - // MalformedSphericalPolygonException - builder.addSphericalPoint(generateRandomizedSphericalPoint(randomGenerator)); - assertThrows(MalformedSphericalPolygonException.class, () -> builder.build()); - - // Show that two vertices will throw a - // MalformedSphericalPolygonException - builder.addSphericalPoint(generateRandomizedSphericalPoint(randomGenerator)); - builder.addSphericalPoint(generateRandomizedSphericalPoint(randomGenerator)); - assertThrows(MalformedSphericalPolygonException.class, () -> builder.build()); - - // Show that null vertices will throw a - // MalformedSphericalPolygonException - builder.addSphericalPoint(null); - builder.addSphericalPoint(generateRandomizedSphericalPoint(randomGenerator)); - builder.addSphericalPoint(generateRandomizedSphericalPoint(randomGenerator)); - assertThrows(MalformedSphericalPolygonException.class, () -> builder.build()); - - // Show that a crossing edges will throw a - // MalformedSphericalPolygonException - assertThrows(MalformedSphericalPolygonException.class, () -> localBuilder()// - .add(0, 0)// - .add(30, 0)// - .add(0, 20)// - .add(30, 20)// - .build()); - - // Show that an ambiguous set of points will throw a - // MalformedSphericalPolygonException -- In practice, this is a very - // difficult condition to create, so we choose to pass this test via - // manual inspection of the SphereicalPolygon class. - - // assertThrows(() -> localBuilder()// - // .add(0, 0)// - // .add(0, 120)// - // .add(0, -120)// - // .build(), - // MalformedSphericalPolygonException.class); - - } - - private SphericalPolygon generateSphericalPolygon(RandomGenerator randomGenerator, Chirality chirality) { - - switch (chirality) { - case RIGHT_HANDED: - return localBuilder()// - .add(38.69724712, -101.5135275)// - .add(37.92632876, -99.96309844)// - .add(37.96933031, -97.32288686)// - .add(39.35733124, -95.45661273)// - .add(38.35690025, -98.8421335)// - .add(39.46582205, -98.42992687)// - .add(39.16371461, -97.35415071)// - .add(39.66032273, -96.34105164)// - .add(40.80930453, -96.82301542)// - .add(40.53524646, -99.62446993)// - .add(39.72191785, -101.7482263)// - .build();// - case LEFT_HANDED: - return localBuilder()// - .add(39.72191785, -101.7482263)// - .add(40.53524646, -99.62446993)// - .add(40.80930453, -96.82301542)// - .add(39.66032273, -96.34105164)// - .add(39.16371461, -97.35415071)// - .add(39.46582205, -98.42992687)// - .add(38.35690025, -98.8421335)// - .add(39.35733124, -95.45661273)// - .add(37.96933031, -97.32288686)// - .add(37.92632876, -99.96309844)// - .add(38.69724712, -101.5135275)// - .build();// - default: - throw new RuntimeException("unhandled case"); - } - - } - - /** - * Tests {@link SphericalPolygon#containsPosition(SphericalPoint)} - */ - @Test - @UnitTestMethod(name = "containsPosition", args = { SphericalPoint.class }) - public void testContainsPosition() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6065504530416184047L); - - SphericalPolygon sphericalPolygon = generateSphericalPolygon(randomGenerator, Chirality.LEFT_HANDED); - - // Determine the centroid of the points of the polygon - List sphericalTriangles = sphericalPolygon.getSphericalTriangles(); - - Vector3D centroid = new Vector3D(); - for (SphericalPoint sphericalPoint : sphericalPolygon.getSphericalPoints()) { - centroid = centroid.add(sphericalPoint.getPosition()); - } - centroid.normalize(); - - // Determine the largest angle to the vertices from the centroid - double maxAngle = 0; - for (SphericalPoint sphericalPoint : sphericalPolygon.getSphericalPoints()) { - maxAngle = FastMath.max(maxAngle, centroid.angle(sphericalPoint.getPosition())); - } - - /* - * Generate a large cluster of points in the general region of the - * polygon. For each test point, compare the result of the polygon - * intersection with the target point with the intersection of the - * polygon's triangles with the target point. - */ - Vector3D north = new Vector3D(0, 0, 1); - int testCount = 10000; - int hitCount = 0; - for (int i = 0; i < testCount; i++) { - double radiusAngle = FastMath.sqrt(randomGenerator.nextDouble()) * maxAngle; - double rotationAngle = randomGenerator.nextDouble() * 2 * FastMath.PI; - Vector3D v = centroid.rotateToward(north, radiusAngle).rotateAbout(centroid, rotationAngle); - - SphericalPoint sphericalPoint = new SphericalPoint(v); - - boolean actual = sphericalPolygon.containsPosition(sphericalPoint); - boolean expected = false; - for (SphericalTriangle sphericalTriangle : sphericalTriangles) { - expected |= sphericalTriangle.contains(sphericalPoint); - } - if (expected) { - hitCount++; - } - assertEquals(expected, actual); - } - - /* - * Show that a reasonable number of the test points fell inside the - * polygon and outside the polygon. - */ - assertTrue(hitCount > testCount / 10); - assertTrue(hitCount < 9 * testCount / 10); - } - - /** - * Tests {@link SphericalPolygon#getChirality()} - */ - @Test - @UnitTestMethod(name = "getChirality", args = {}) - public void testGetChirality() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5040370953904598541L); - - for (Chirality chirality : Chirality.values()) { - SphericalPolygon sphericalPolygon = generateSphericalPolygon(randomGenerator, chirality); - assertEquals(chirality, sphericalPolygon.getChirality()); - } - } - - /** - * Tests {@link SphericalPolygon#getCentroid()} - */ - @Test - @UnitTestMethod(name = "getCentroid", args = {}) - public void testGetCentroid() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3996062030446397343L); - - for (Chirality chirality : Chirality.values()) { - SphericalPolygon sphericalPolygon = generateSphericalPolygon(randomGenerator, chirality); - Vector3D v = new Vector3D(); - for (SphericalPoint sphericalPoint : sphericalPolygon.getSphericalPoints()) { - v = v.add(sphericalPoint.getPosition()); - } - SphericalPoint expected = new SphericalPoint(v); - - SphericalPoint actual = sphericalPolygon.getCentroid(); - - for (int index = 0; index < 3; index++) { - double expectedCoordinateValue = expected.getPosition().get(index); - double actualCoordinateValue = actual.getPosition().get(index); - double delta = FastMath.abs(expectedCoordinateValue - actualCoordinateValue); - assertTrue(delta < Vector3D.NORMAL_LENGTH_TOLERANCE); - } - - } - } - - /** - * Tests {@link SphericalPolygon#getRadius()} - */ - @Test - @UnitTestMethod(name = "getRadius", args = {}) - public void testGetRadius() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7740289660333498822L); - - for (Chirality chirality : Chirality.values()) { - SphericalPolygon sphericalPolygon = generateSphericalPolygon(randomGenerator, chirality); - - SphericalPoint centroid = sphericalPolygon.getCentroid(); - - double expectedRadius = Double.NEGATIVE_INFINITY; - for (SphericalPoint sphericalPoint : sphericalPolygon.getSphericalPoints()) { - expectedRadius = FastMath.max(expectedRadius, sphericalPoint.getPosition().angle(centroid.getPosition())); - } - - double actualRadius = sphericalPolygon.getRadius(); - - assertEquals(expectedRadius, actualRadius, Vector3D.NORMAL_LENGTH_TOLERANCE); - - } - } - - /** - * Tests {@link SphericalPolygon#getSphericalArcs()} - */ - @Test - @UnitTestMethod(name = "getSphericalArcs", args = {}) - public void testGetSphericalArcs() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2507505792488258607L); - - SphericalPolygon sphericalPolygon = generateSphericalPolygon(randomGenerator, Chirality.LEFT_HANDED); - - // Get the spherical points from the polygon - List sphericalPoints = sphericalPolygon.getSphericalPoints(); - - /* - * Generate arcs from the points and wrap them so that they can be - * compared to the arcs from the polygon. Note that SphericalArc does - * not provide an equals() implementation. - */ - Set expected = new LinkedHashSet<>(); - for (int i = 0; i < sphericalPoints.size(); i++) { - int j = (i + 1) % sphericalPoints.size(); - SphericalPoint sphericalPoint1 = sphericalPoints.get(i); - SphericalPoint sphericalPoint2 = sphericalPoints.get(j); - SphericalArc sphericalArc = new SphericalArc(sphericalPoint1, sphericalPoint2); - expected.add(new SphericalArcWrapper(sphericalArc)); - } - - Set actual = sphericalPolygon.getSphericalArcs().stream().map(arc -> new SphericalArcWrapper(arc)).collect(Collectors.toCollection(LinkedHashSet::new)); - - assertEquals(expected, actual); - } - - private static class SphericalArcWrapper { - private final Vector3D position1; - private final Vector3D position2; - - public SphericalArcWrapper(SphericalArc sphericalArc) { - position1 = sphericalArc.getSphericalPoint(0).getPosition(); - position2 = sphericalArc.getSphericalPoint(1).getPosition(); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((position1 == null) ? 0 : position1.hashCode()); - result = prime * result + ((position2 == null) ? 0 : position2.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (!(obj instanceof SphericalArcWrapper)) { - return false; - } - SphericalArcWrapper other = (SphericalArcWrapper) obj; - if (position1 == null) { - if (other.position1 != null) { - return false; - } - } else if (!position1.equals(other.position1)) { - return false; - } - if (position2 == null) { - if (other.position2 != null) { - return false; - } - } else if (!position2.equals(other.position2)) { - return false; - } - return true; - } - } - - /** - * Tests {@link SphericalPolygon#getSphericalPoints()} - */ - @Test - @UnitTestMethod(name = "getSphericalPoints", args = {}) - public void testGetSphericalPoints() { - Earth earth = Earth.fromMeanRadius(); - - List expected = new ArrayList<>(); - - expected.add(new SphericalPoint(earth.getECCFromLatLon(new LatLon(38.69724712, -101.5135275)))); - expected.add(new SphericalPoint(earth.getECCFromLatLon(new LatLon(37.92632876, -99.96309844)))); - expected.add(new SphericalPoint(earth.getECCFromLatLon(new LatLon(37.96933031, -97.32288686)))); - expected.add(new SphericalPoint(earth.getECCFromLatLon(new LatLon(39.35733124, -95.45661273)))); - expected.add(new SphericalPoint(earth.getECCFromLatLon(new LatLon(38.35690025, -98.8421335)))); - expected.add(new SphericalPoint(earth.getECCFromLatLon(new LatLon(39.46582205, -98.42992687)))); - expected.add(new SphericalPoint(earth.getECCFromLatLon(new LatLon(39.16371461, -97.35415071)))); - expected.add(new SphericalPoint(earth.getECCFromLatLon(new LatLon(39.66032273, -96.34105164)))); - expected.add(new SphericalPoint(earth.getECCFromLatLon(new LatLon(40.80930453, -96.82301542)))); - expected.add(new SphericalPoint(earth.getECCFromLatLon(new LatLon(40.53524646, -99.62446993)))); - expected.add(new SphericalPoint(earth.getECCFromLatLon(new LatLon(39.72191785, -101.7482263)))); - - SphericalPolygon.Builder builder = SphericalPolygon.builder(); - for (SphericalPoint sphericalPoint : expected) { - builder.addSphericalPoint(sphericalPoint); - } - - SphericalPolygon sphericalPolygon = builder.build(); - - List actual = sphericalPolygon.getSphericalPoints(); - - assertEquals(expected, actual); - } - - /** - * Tests {@link SphericalPolygon#getSphericalTriangles()} - */ - @Test - @UnitTestMethod(name = "getSphericalTriangles", args = {}) - public void testGetSphericalTriangles() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6353521777153484531L); - - SphericalPolygon sphericalPolygon = generateSphericalPolygon(randomGenerator, Chirality.LEFT_HANDED); - - List sphericalTriangles = sphericalPolygon.getSphericalTriangles(); - - Vector3D north = new Vector3D(0, 0, 1); - Vector3D center = sphericalPolygon.getCentroid().getPosition(); - - // Generate a bunch of points that are in the general area of the - // polygon. If the triangles and the polygon agree in all cases, we - // conclude that the triangles do indeed cover the polygon. - int intersectionCount = 0; - for (int i = 0; i < 1000; i++) { - Vector3D v = center.rotateToward(north, FastMath.sqrt(randomGenerator.nextDouble()) * sphericalPolygon.getRadius()); - v = v.rotateAbout(center, randomGenerator.nextDouble() * 2 * FastMath.PI); - SphericalPoint sphericalPoint = new SphericalPoint(v); - - boolean expected = false; - for (SphericalTriangle sphericalTriangle : sphericalTriangles) { - if (sphericalTriangle.contains(sphericalPoint)) { - expected = true; - break; - } - } - if (expected) { - intersectionCount++; - } - - boolean actual = sphericalPolygon.containsPosition(sphericalPoint); - - assertEquals(expected, actual); - } - - - - assertTrue(intersectionCount > 100); - assertTrue(intersectionCount < 900); - - } - - /** - * Tests {@link SphericalPolygon#intersects(SphericalArc)} - * - */ - @Test - @UnitTestMethod(name = "intersects", args = { SphericalArc.class }) - public void testIntersects_Arcs() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2244326004441352995L); - - SphericalPolygon sphericalPolygon = generateSphericalPolygon(randomGenerator, Chirality.LEFT_HANDED); - - List sphericalTriangles = sphericalPolygon.getSphericalTriangles(); - - Vector3D north = new Vector3D(0, 0, 1); - Vector3D center = sphericalPolygon.getCentroid().getPosition(); - - // Generate a bunch of arcs that are in the general area of the - // polygon. If the triangles and the polygon agree in all cases, we - // conclude that the triangles do indeed cover the polygon. - int intersectionCount = 0; - for (int i = 0; i < 1000; i++) { - Vector3D v1 = center.rotateToward(north, FastMath.sqrt(randomGenerator.nextDouble()) * sphericalPolygon.getRadius() * 2); - v1 = v1.rotateAbout(center, randomGenerator.nextDouble() * 2 * FastMath.PI); - SphericalPoint sphericalPoint1 = new SphericalPoint(v1); - - Vector3D v2 = center.rotateToward(north, FastMath.sqrt(randomGenerator.nextDouble()) * sphericalPolygon.getRadius() * 2); - v2 = v2.rotateAbout(center, randomGenerator.nextDouble() * 2 * FastMath.PI); - SphericalPoint sphericalPoint2 = new SphericalPoint(v2); - - SphericalArc sphericalArc = new SphericalArc(sphericalPoint1, sphericalPoint2); - - boolean expected = false; - for (SphericalTriangle sphericalTriangle : sphericalTriangles) { - if (sphericalTriangle.intersects(sphericalArc)) { - expected = true; - break; - } - } - if (expected) { - intersectionCount++; - } - - boolean actual = sphericalPolygon.intersects(sphericalArc); - - assertEquals(expected, actual); - - } - assertTrue(intersectionCount > 100); - assertTrue(intersectionCount < 900); - - } - - /** - * Tests {@link SphericalPolygon#intersects(SphericalTriangle)} - */ - @Test - @UnitTestMethod(name = "intersects", args = { SphericalTriangle.class }) - public void testIntersects_Triangles() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5946152543146292840L); - - SphericalPolygon sphericalPolygon = generateSphericalPolygon(randomGenerator, Chirality.LEFT_HANDED); - - List sphericalTriangles = sphericalPolygon.getSphericalTriangles(); - - Vector3D north = new Vector3D(0, 0, 1); - Vector3D center = sphericalPolygon.getCentroid().getPosition(); - - // Generate a bunch of arcs that are in the general area of the - // polygon. If the triangles and the polygon agree in all cases, we - // conclude that the triangles do indeed cover the polygon. - int intersectionCount = 0; - for (int i = 0; i < 1000; i++) { - Vector3D v1 = center.rotateToward(north, FastMath.sqrt(randomGenerator.nextDouble()) * sphericalPolygon.getRadius() * 2); - v1 = v1.rotateAbout(center, randomGenerator.nextDouble() * 2 * FastMath.PI); - SphericalPoint sphericalPoint1 = new SphericalPoint(v1); - - Vector3D v2 = center.rotateToward(north, FastMath.sqrt(randomGenerator.nextDouble()) * sphericalPolygon.getRadius() * 2); - v2 = v2.rotateAbout(center, randomGenerator.nextDouble() * 2 * FastMath.PI); - SphericalPoint sphericalPoint2 = new SphericalPoint(v2); - - Vector3D v3 = center.rotateToward(north, FastMath.sqrt(randomGenerator.nextDouble()) * sphericalPolygon.getRadius() * 2); - v3 = v3.rotateAbout(center, randomGenerator.nextDouble() * 2 * FastMath.PI); - SphericalPoint sphericalPoint3 = new SphericalPoint(v3); - - SphericalTriangle testTriangle = new SphericalTriangle(sphericalPoint1, sphericalPoint2, sphericalPoint3); - - boolean expected = false; - for (SphericalTriangle sphericalTriangle : sphericalTriangles) { - if (sphericalTriangle.intersects(testTriangle)) { - expected = true; - break; - } - } - if (expected) { - intersectionCount++; - } - - boolean actual = sphericalPolygon.intersects(testTriangle); - - assertEquals(expected, actual); - - } - assertTrue(intersectionCount > 100); - assertTrue(intersectionCount < 900); - - } - - /** - * Tests {@link SphericalPolygon#intersects(SphericalPolygon)} - */ - @Test - @UnitTestMethod(name = "intersects", args = { SphericalPolygon.class }) - public void testIntersects_Polygons() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7005502162196438809L); - - SphericalPolygon sphericalPolygon = generateSphericalPolygon(randomGenerator, Chirality.LEFT_HANDED); - - List sphericalTriangles = sphericalPolygon.getSphericalTriangles(); - - Vector3D north = new Vector3D(0, 0, 1); - Vector3D center = sphericalPolygon.getCentroid().getPosition(); - - // Generate a bunch of arcs that are in the general area of the - // polygon. If the triangles and the polygon agree in all cases, we - // conclude that the triangles do indeed cover the polygon. - int intersectionCount = 0; - int wellformedCount = 0; - for (int i = 0; i < 1000; i++) { - SphericalPolygon.Builder builder = SphericalPolygon.builder(); - - for (int j = 0; j < 4; j++) { - Vector3D v = center.rotateToward(north, FastMath.sqrt(randomGenerator.nextDouble()) * sphericalPolygon.getRadius() * 2); - v = v.rotateAbout(center, randomGenerator.nextDouble() * 2 * FastMath.PI); - builder.addSphericalPoint(new SphericalPoint(v)); - } - boolean wellFormed; - SphericalPolygon testPolygon = null; - try { - testPolygon = builder.build(); - wellFormed = true; - } catch (MalformedSphericalPolygonException e) { - wellFormed = false; - } - - if (wellFormed) { - wellformedCount++; - List testTriangles = testPolygon.getSphericalTriangles(); - - boolean expected = false; - for (SphericalTriangle sphericalTriangle : sphericalTriangles) { - for (SphericalTriangle testTriangle : testTriangles) { - if (sphericalTriangle.intersects(testTriangle)) { - expected = true; - break; - } - } - } - if (expected) { - intersectionCount++; - } - boolean actual = sphericalPolygon.intersects(testPolygon); - assertEquals(expected, actual); - } - - } - assertTrue(wellformedCount > 500); - assertTrue(intersectionCount > 0); - assertTrue(intersectionCount < wellformedCount); - } - -} diff --git a/gcm3/src/test/java/util/spherical/AT_SphericalTriangle.java b/gcm3/src/test/java/util/spherical/AT_SphericalTriangle.java deleted file mode 100644 index f12080342..000000000 --- a/gcm3/src/test/java/util/spherical/AT_SphericalTriangle.java +++ /dev/null @@ -1,665 +0,0 @@ -package util.spherical; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.apache.commons.math3.random.RandomGenerator; -import org.apache.commons.math3.util.FastMath; -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.random.RandomGeneratorProvider; -import util.vector.Vector3D; - -/** - * Test class for {@link SphericalTriangle} - * - * @author Shawn Hatch - * - */ -@UnitTest(target = SphericalTriangle.class) -public class AT_SphericalTriangle { - - /** - * Tests - * {@link SphericalTriangle#SphericalTriangle(SphericalPoint, SphericalPoint, SphericalPoint)} - */ - @Test - @UnitTestConstructor(args = { SphericalPoint.class, SphericalPoint.class, SphericalPoint.class }) - public void testConstructor() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1516981394291616444L); - - double x1 = randomGenerator.nextDouble() * 2 - 1; - double y1 = randomGenerator.nextDouble() * 2 - 1; - double z1 = randomGenerator.nextDouble() * 2 - 1; - - SphericalPoint sphericalPoint1 = new SphericalPoint(new Vector3D(x1, y1, z1)); - - double x2 = randomGenerator.nextDouble() * 2 - 1; - double y2 = randomGenerator.nextDouble() * 2 - 1; - double z2 = randomGenerator.nextDouble() * 2 - 1; - - SphericalPoint sphericalPoint2 = new SphericalPoint(new Vector3D(x2, y2, z2)); - - double x3 = randomGenerator.nextDouble() * 2 - 1; - double y3 = randomGenerator.nextDouble() * 2 - 1; - double z3 = randomGenerator.nextDouble() * 2 - 1; - - SphericalPoint sphericalPoint3 = new SphericalPoint(new Vector3D(x3, y3, z3)); - - assertThrows(MalformedSphericalTriangleException.class, () -> new SphericalTriangle(null, sphericalPoint2, sphericalPoint3)); - - assertThrows(MalformedSphericalTriangleException.class, () -> new SphericalTriangle(sphericalPoint1, null, sphericalPoint3)); - - assertThrows(MalformedSphericalTriangleException.class, () -> new SphericalTriangle(sphericalPoint1, sphericalPoint2, null)); - } - - /** - * Tests {@link SphericalTriangle#contains(SphericalPoint)} - */ - @Test - @UnitTestMethod(name = "contains", args = { SphericalPoint.class }) - public void testContains() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(9196011045677894651L); - int containsCount = 0; - for (int i = 0; i < 1000; i++) { - // Generate a randomized spherical triangle - double x1 = randomGenerator.nextDouble() * 2 - 1; - double y1 = randomGenerator.nextDouble() * 2 - 1; - double z1 = randomGenerator.nextDouble() * 2 - 1; - - SphericalPoint sphericalPoint1 = new SphericalPoint(new Vector3D(x1, y1, z1)); - - double x2 = randomGenerator.nextDouble() * 2 - 1; - double y2 = randomGenerator.nextDouble() * 2 - 1; - double z2 = randomGenerator.nextDouble() * 2 - 1; - - SphericalPoint sphericalPoint2 = new SphericalPoint(new Vector3D(x2, y2, z2)); - - double x3 = randomGenerator.nextDouble() * 2 - 1; - double y3 = randomGenerator.nextDouble() * 2 - 1; - double z3 = randomGenerator.nextDouble() * 2 - 1; - - SphericalPoint sphericalPoint3 = new SphericalPoint(new Vector3D(x3, y3, z3)); - - SphericalTriangle sphericalTriangle = new SphericalTriangle(sphericalPoint1, sphericalPoint2, sphericalPoint3); - - /* - * Find the center of the triangle and use the triangle's radius to - * generate a point that is nearby, but may be either inside or - * outside the triangle. - */ - - Vector3D centroid = sphericalTriangle.getCentroid(); - - double radius = sphericalTriangle.getRadius(); - - Vector3D north = new Vector3D(0, 0, 1); - - Vector3D v = north.rotateAbout(centroid, randomGenerator.nextDouble() * 2 * FastMath.PI); - - v = centroid.rotateToward(v, randomGenerator.nextDouble() * radius); - - SphericalPoint testPoint = new SphericalPoint(v); - - /* - * From the perspective of each arc of the triangle, the test point - * is either right handed or left handed. Since the arcs are ordered - * such that a->b, b->c and c->a, we see that the chirality of the - * test point will be the same for all arcs if and only if the test - * point is inside the triangle. - */ - Chirality chirality0 = sphericalTriangle.getSphericalArc(0).getChirality(testPoint); - Chirality chirality1 = sphericalTriangle.getSphericalArc(1).getChirality(testPoint); - Chirality chirality2 = sphericalTriangle.getSphericalArc(2).getChirality(testPoint); - - boolean expected = chirality0 == chirality1 && chirality0 == chirality2; - if (expected) { - containsCount++; - } - - boolean actual = sphericalTriangle.contains(testPoint); - - assertEquals(expected, actual); - - } - /* - * We show that there are at least a few hits and misses - */ - assertTrue(containsCount > 100); - assertTrue(containsCount < 900); - - } - - /** - * Tests {@link SphericalTriangle#distanceTo(SphericalPoint)} - */ - @Test - @UnitTestMethod(name = "distanceTo", args = { SphericalPoint.class }) - public void testDistanceTo() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7990273909659377244L); - int containsCount = 0; - for (int i = 0; i < 1000; i++) { - // Generate a randomized spherical triangle - double x1 = randomGenerator.nextDouble() * 2 - 1; - double y1 = randomGenerator.nextDouble() * 2 - 1; - double z1 = randomGenerator.nextDouble() * 2 - 1; - - SphericalPoint sphericalPoint1 = new SphericalPoint(new Vector3D(x1, y1, z1)); - - double x2 = randomGenerator.nextDouble() * 2 - 1; - double y2 = randomGenerator.nextDouble() * 2 - 1; - double z2 = randomGenerator.nextDouble() * 2 - 1; - - SphericalPoint sphericalPoint2 = new SphericalPoint(new Vector3D(x2, y2, z2)); - - double x3 = randomGenerator.nextDouble() * 2 - 1; - double y3 = randomGenerator.nextDouble() * 2 - 1; - double z3 = randomGenerator.nextDouble() * 2 - 1; - - SphericalPoint sphericalPoint3 = new SphericalPoint(new Vector3D(x3, y3, z3)); - - SphericalTriangle sphericalTriangle = new SphericalTriangle(sphericalPoint1, sphericalPoint2, sphericalPoint3); - - /* - * Find the center of the triangle and use the triangle's radius to - * generate a point that is nearby, but may be either inside or - * outside the triangle. - */ - - Vector3D centroid = sphericalTriangle.getCentroid(); - - double radius = sphericalTriangle.getRadius(); - - Vector3D north = new Vector3D(0, 0, 1); - - Vector3D v = north.rotateAbout(centroid, randomGenerator.nextDouble() * 2 * FastMath.PI); - - v = centroid.rotateToward(v, randomGenerator.nextDouble() * radius * 2); - - SphericalPoint testPoint = new SphericalPoint(v); - - double expectedDistance = 0; - if (!sphericalTriangle.contains(testPoint)) { - expectedDistance = Double.POSITIVE_INFINITY; - expectedDistance = FastMath.min(expectedDistance, sphericalTriangle.getSphericalArc(0).distanceTo(testPoint)); - expectedDistance = FastMath.min(expectedDistance, sphericalTriangle.getSphericalArc(1).distanceTo(testPoint)); - expectedDistance = FastMath.min(expectedDistance, sphericalTriangle.getSphericalArc(2).distanceTo(testPoint)); - } else { - containsCount++; - } - double actualDistance = sphericalTriangle.distanceTo(testPoint); - - assertEquals(expectedDistance, actualDistance, 1E-10); - - } - /* - * We show that there are at least a few hits and misses - */ - assertTrue(containsCount > 100); - assertTrue(containsCount < 900); - - } - - /** - * Tests {@link SphericalTriangle#getArea()} - */ - @Test - @UnitTestMethod(name = "getArea", args = {}) - public void testGetArea() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2718440263756572706L); - - for (int i = 0; i < 100; i++) { - // Generate a randomized spherical triangle - double x1 = randomGenerator.nextDouble() * 2 - 1; - double y1 = randomGenerator.nextDouble() * 2 - 1; - double z1 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v1 = new Vector3D(x1, y1, z1); - SphericalPoint sphericalPoint1 = new SphericalPoint(v1); - - double x2 = randomGenerator.nextDouble() * 2 - 1; - double y2 = randomGenerator.nextDouble() * 2 - 1; - double z2 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v2 = new Vector3D(x2, y2, z2); - SphericalPoint sphericalPoint2 = new SphericalPoint(v2); - - double x3 = randomGenerator.nextDouble() * 2 - 1; - double y3 = randomGenerator.nextDouble() * 2 - 1; - double z3 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v3 = new Vector3D(x3, y3, z3); - SphericalPoint sphericalPoint3 = new SphericalPoint(v3); - - SphericalTriangle sphericalTriangle = new SphericalTriangle(sphericalPoint1, sphericalPoint2, sphericalPoint3); - - /* - * Determine the angle for each of the vertices - */ - double angle1 = getAngle(v1, v2, v3); - double angle2 = getAngle(v2, v3, v1); - double angle3 = getAngle(v3, v1, v2); - double expectedArea = angle1 + angle2 + angle3 - FastMath.PI; - - double actualArea = sphericalTriangle.getArea(); - assertEquals(expectedArea, actualArea, 1E-10); - } - - } - - /* - * Returns the angle formed at v2 from v1 and v3 - */ - private static double getAngle(Vector3D v1, Vector3D v2, Vector3D v3) { - return v1.cross(v2).cross(v2).angle(v3.cross(v2).cross(v2)); - } - - /** - * Tests {@link SphericalTriangle#getCentroid()} - */ - @Test - @UnitTestMethod(name = "getCentroid", args = {}) - public void testGetCentroid() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8983815604783536421L); - - for (int i = 0; i < 100; i++) { - // Generate a randomized spherical triangle - double x0 = randomGenerator.nextDouble() * 2 - 1; - double y0 = randomGenerator.nextDouble() * 2 - 1; - double z0 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v0 = new Vector3D(x0, y0, z0); - SphericalPoint sphericalPoint0 = new SphericalPoint(v0); - - double x1 = randomGenerator.nextDouble() * 2 - 1; - double y1 = randomGenerator.nextDouble() * 2 - 1; - double z1 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v1 = new Vector3D(x1, y1, z1); - SphericalPoint sphericalPoint1 = new SphericalPoint(v1); - - double x2 = randomGenerator.nextDouble() * 2 - 1; - double y2 = randomGenerator.nextDouble() * 2 - 1; - double z2 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v2 = new Vector3D(x2, y2, z2); - SphericalPoint sphericalPoint2 = new SphericalPoint(v2); - - SphericalTriangle sphericalTriangle = new SphericalTriangle(sphericalPoint0, sphericalPoint1, sphericalPoint2); - - Vector3D actualCentroid = sphericalTriangle.getCentroid(); - - /* - * The centroid is expected to be the intersection of the - * perpendicular bisectors of the sides of the triangle. For each - * arc, we calculate the midpoint of the arc and the normal of the - * arc. The cross product of these will be the normal to the plane - * containing the centroid and the midpoint of the arc. The cross - * product of these normals will be the centroid, or its opposite. - */ - - Vector3D p1 = v0.normalize().add(v2.normalize()).cross(v0.cross(v2)); - Vector3D p2 = v1.normalize().add(v2.normalize()).cross(v1.cross(v2)); - Vector3D expectedCentroid = p1.cross(p2); - - // We may need to reverse the expected centroid, depending on which - // side of the v0->v1 arc the v2 point falls. - boolean reverse = v0.cross(v1).dot(v2) < 0; - if (reverse) { - expectedCentroid = expectedCentroid.reverse(); - } - // Finally, we must normalize the expected centroid - expectedCentroid = expectedCentroid.normalize(); - assertTrue(equalVectors(expectedCentroid, actualCentroid)); - - } - } - - private static boolean equalVectors(Vector3D v1, Vector3D v2) { - return FastMath.abs(v1.getX() - v2.getX()) < 1E-10 && FastMath.abs(v1.getY() - v2.getY()) < 1E-10 && FastMath.abs(v1.getZ() - v2.getZ()) < 1E-10; - } - - /** - * Tests {@link SphericalTriangle#getChirality()} - */ - @Test - @UnitTestMethod(name = "getChirality", args = {}) - public void testGetChirality() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8318155766608307004L); - - for (int i = 0; i < 100; i++) { - // Generate a randomized spherical triangle - double x1 = randomGenerator.nextDouble() * 2 - 1; - double y1 = randomGenerator.nextDouble() * 2 - 1; - double z1 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v1 = new Vector3D(x1, y1, z1); - SphericalPoint sphericalPoint1 = new SphericalPoint(v1); - - double x2 = randomGenerator.nextDouble() * 2 - 1; - double y2 = randomGenerator.nextDouble() * 2 - 1; - double z2 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v2 = new Vector3D(x2, y2, z2); - SphericalPoint sphericalPoint2 = new SphericalPoint(v2); - - double x3 = randomGenerator.nextDouble() * 2 - 1; - double y3 = randomGenerator.nextDouble() * 2 - 1; - double z3 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v3 = new Vector3D(x3, y3, z3); - SphericalPoint sphericalPoint3 = new SphericalPoint(v3); - - SphericalTriangle sphericalTriangle = new SphericalTriangle(sphericalPoint1, sphericalPoint2, sphericalPoint3); - - SphericalArc sphericalArc = new SphericalArc(sphericalPoint1, sphericalPoint2); - Chirality expectedChirality = sphericalArc.getChirality(sphericalPoint3); - - Chirality actualChirality = sphericalTriangle.getChirality(); - - assertEquals(expectedChirality, actualChirality); - - } - } - - /** - * Tests {@link SphericalTriangle#getRadius()} - */ - @Test - @UnitTestMethod(name = "getRadius", args = {}) - public void testGetRadius() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(858839356108815818L); - - for (int i = 0; i < 100; i++) { - // Generate a randomized spherical triangle - double x1 = randomGenerator.nextDouble() * 2 - 1; - double y1 = randomGenerator.nextDouble() * 2 - 1; - double z1 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v1 = new Vector3D(x1, y1, z1); - SphericalPoint sphericalPoint1 = new SphericalPoint(v1); - - double x2 = randomGenerator.nextDouble() * 2 - 1; - double y2 = randomGenerator.nextDouble() * 2 - 1; - double z2 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v2 = new Vector3D(x2, y2, z2); - SphericalPoint sphericalPoint2 = new SphericalPoint(v2); - - double x3 = randomGenerator.nextDouble() * 2 - 1; - double y3 = randomGenerator.nextDouble() * 2 - 1; - double z3 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v3 = new Vector3D(x3, y3, z3); - SphericalPoint sphericalPoint3 = new SphericalPoint(v3); - - SphericalTriangle sphericalTriangle = new SphericalTriangle(sphericalPoint1, sphericalPoint2, sphericalPoint3); - - Vector3D centroid = sphericalTriangle.getCentroid(); - - double expectedRadius = centroid.angle(v1); - double actualRadius = sphericalTriangle.getRadius(); - assertEquals(expectedRadius, actualRadius, 1E-10); - } - } - - /** - * Tests {@link SphericalTriangle#getSphericalArc(int)} - */ - @Test - @UnitTestMethod(name = "getSphericalArc", args = { int.class }) - public void testGetSphericalArc() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7281272754332690959L); - - for (int i = 0; i < 100; i++) { - // Generate a randomized spherical triangle - - double x0 = randomGenerator.nextDouble() * 2 - 1; - double y0 = randomGenerator.nextDouble() * 2 - 1; - double z0 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v0 = new Vector3D(x0, y0, z0); - SphericalPoint sphericalPoint0 = new SphericalPoint(v0); - - double x1 = randomGenerator.nextDouble() * 2 - 1; - double y1 = randomGenerator.nextDouble() * 2 - 1; - double z1 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v1 = new Vector3D(x1, y1, z1); - SphericalPoint sphericalPoint1 = new SphericalPoint(v1); - - double x2 = randomGenerator.nextDouble() * 2 - 1; - double y2 = randomGenerator.nextDouble() * 2 - 1; - double z2 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v2 = new Vector3D(x2, y2, z2); - SphericalPoint sphericalPoint2 = new SphericalPoint(v2); - - SphericalTriangle sphericalTriangle = new SphericalTriangle(sphericalPoint0, sphericalPoint1, sphericalPoint2); - - SphericalArc sphericalArc0 = new SphericalArc(sphericalPoint0, sphericalPoint1); - SphericalArc sphericalArc1 = new SphericalArc(sphericalPoint1, sphericalPoint2); - SphericalArc sphericalArc2 = new SphericalArc(sphericalPoint2, sphericalPoint0); - - assertTrue(equalArcs(sphericalArc0, sphericalTriangle.getSphericalArc(0))); - assertTrue(equalArcs(sphericalArc1, sphericalTriangle.getSphericalArc(1))); - assertTrue(equalArcs(sphericalArc2, sphericalTriangle.getSphericalArc(2))); - - } - } - - private boolean equalArcs(SphericalArc sphericalArc1, SphericalArc sphericalArc2) { - return equalPoints(sphericalArc1.getSphericalPoint(0), sphericalArc2.getSphericalPoint(0)) && equalPoints(sphericalArc1.getSphericalPoint(1), sphericalArc2.getSphericalPoint(1)); - } - - private boolean equalPoints(SphericalPoint sphericalPoint1, SphericalPoint sphericalPoint2) { - Vector3D position1 = sphericalPoint1.getPosition(); - Vector3D position2 = sphericalPoint2.getPosition(); - - return FastMath.abs(position1.getX() - position2.getX()) < 1E-10 && FastMath.abs(position1.getY() - position2.getY()) < 1E-10 && FastMath.abs(position1.getZ() - position2.getZ()) < 1E-10; - } - - /** - * Tests {@link SphericalTriangle#getSphericalPoint(int)} - */ - @Test - @UnitTestMethod(name = "getSphericalPoint", args = { int.class }) - public void testGetSphericalPoint() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5799569940995229412L); - - for (int i = 0; i < 100; i++) { - // Generate a randomized spherical triangle - - double x0 = randomGenerator.nextDouble() * 2 - 1; - double y0 = randomGenerator.nextDouble() * 2 - 1; - double z0 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v0 = new Vector3D(x0, y0, z0); - SphericalPoint sphericalPoint0 = new SphericalPoint(v0); - - double x1 = randomGenerator.nextDouble() * 2 - 1; - double y1 = randomGenerator.nextDouble() * 2 - 1; - double z1 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v1 = new Vector3D(x1, y1, z1); - SphericalPoint sphericalPoint1 = new SphericalPoint(v1); - - double x2 = randomGenerator.nextDouble() * 2 - 1; - double y2 = randomGenerator.nextDouble() * 2 - 1; - double z2 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v2 = new Vector3D(x2, y2, z2); - SphericalPoint sphericalPoint2 = new SphericalPoint(v2); - - SphericalTriangle sphericalTriangle = new SphericalTriangle(sphericalPoint0, sphericalPoint1, sphericalPoint2); - - assertEquals(sphericalPoint0, sphericalTriangle.getSphericalPoint(0)); - assertEquals(sphericalPoint1, sphericalTriangle.getSphericalPoint(1)); - assertEquals(sphericalPoint2, sphericalTriangle.getSphericalPoint(2)); - } - } - - /** - * Tests {@link SphericalTriangle#intersects(SphericalTriangle)} - */ - @Test - @UnitTestMethod(name = "intersects", args = { SphericalTriangle.class }) - public void testIntersects_Triangle() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3736978332919299911L); - - int intersectionCount = 0; - for (int i = 0; i < 1000; i++) { - // Generate two randomized spherical triangles - - double x0 = randomGenerator.nextDouble() * 2 - 1; - double y0 = randomGenerator.nextDouble() * 2 - 1; - double z0 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v0 = new Vector3D(x0, y0, z0); - SphericalPoint sphericalPoint0 = new SphericalPoint(v0); - - double x1 = randomGenerator.nextDouble() * 2 - 1; - double y1 = randomGenerator.nextDouble() * 2 - 1; - double z1 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v1 = new Vector3D(x1, y1, z1); - SphericalPoint sphericalPoint1 = new SphericalPoint(v1); - - double x2 = randomGenerator.nextDouble() * 2 - 1; - double y2 = randomGenerator.nextDouble() * 2 - 1; - double z2 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v2 = new Vector3D(x2, y2, z2); - SphericalPoint sphericalPoint2 = new SphericalPoint(v2); - - SphericalTriangle sphericalTriangle1 = new SphericalTriangle(sphericalPoint0, sphericalPoint1, sphericalPoint2); - - double x3 = randomGenerator.nextDouble() * 2 - 1; - double y3 = randomGenerator.nextDouble() * 2 - 1; - double z3 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v3 = new Vector3D(x3, y3, z3); - SphericalPoint sphericalPoint3 = new SphericalPoint(v3); - - double x4 = randomGenerator.nextDouble() * 2 - 1; - double y4 = randomGenerator.nextDouble() * 2 - 1; - double z4 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v4 = new Vector3D(x4, y4, z4); - SphericalPoint sphericalPoint4 = new SphericalPoint(v4); - - double x5 = randomGenerator.nextDouble() * 2 - 1; - double y5 = randomGenerator.nextDouble() * 2 - 1; - double z5 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v5 = new Vector3D(x5, y5, z5); - SphericalPoint sphericalPoint5 = new SphericalPoint(v5); - - SphericalTriangle sphericalTriangle2 = new SphericalTriangle(sphericalPoint3, sphericalPoint4, sphericalPoint5); - - // Does any point of either triangle lie inside the other triangle? - boolean expected = false; - for (int j = 0; j < 3; j++) { - expected |= sphericalTriangle1.contains(sphericalTriangle2.getSphericalPoint(j)); - expected |= sphericalTriangle2.contains(sphericalTriangle1.getSphericalPoint(j)); - } - - // Does any arc of either triangle cross the other triangles - for (int j = 0; j < 3; j++) { - expected |= sphericalTriangle1.intersects(sphericalTriangle2.getSphericalArc(j)); - expected |= sphericalTriangle2.intersects(sphericalTriangle1.getSphericalArc(j)); - } - if (expected) { - intersectionCount++; - } - - boolean actual = sphericalTriangle1.intersects(sphericalTriangle2); - - assertEquals(expected, actual); - - } - - assertTrue(intersectionCount > 100); - assertTrue(intersectionCount < 900); - } - - /** - * Tests {@link SphericalTriangle#intersects(SphericalArc)} - */ - @Test - @UnitTestMethod(name = "intersects", args = { SphericalArc.class }) - public void testIntersects_Arc() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2722369694139955276L); - - int intersectionCount = 0; - for (int i = 0; i < 1000; i++) { - // Generate a randomized spherical triangle and a randomize - // spherical arc - - double x0 = randomGenerator.nextDouble() * 2 - 1; - double y0 = randomGenerator.nextDouble() * 2 - 1; - double z0 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v0 = new Vector3D(x0, y0, z0); - SphericalPoint sphericalPoint0 = new SphericalPoint(v0); - - double x1 = randomGenerator.nextDouble() * 2 - 1; - double y1 = randomGenerator.nextDouble() * 2 - 1; - double z1 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v1 = new Vector3D(x1, y1, z1); - SphericalPoint sphericalPoint1 = new SphericalPoint(v1); - - double x2 = randomGenerator.nextDouble() * 2 - 1; - double y2 = randomGenerator.nextDouble() * 2 - 1; - double z2 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v2 = new Vector3D(x2, y2, z2); - SphericalPoint sphericalPoint2 = new SphericalPoint(v2); - - SphericalTriangle sphericalTriangle = new SphericalTriangle(sphericalPoint0, sphericalPoint1, sphericalPoint2); - - double x3 = randomGenerator.nextDouble() * 2 - 1; - double y3 = randomGenerator.nextDouble() * 2 - 1; - double z3 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v3 = new Vector3D(x3, y3, z3); - SphericalPoint sphericalPoint3 = new SphericalPoint(v3); - - double x4 = randomGenerator.nextDouble() * 2 - 1; - double y4 = randomGenerator.nextDouble() * 2 - 1; - double z4 = randomGenerator.nextDouble() * 2 - 1; - - Vector3D v4 = new Vector3D(x4, y4, z4); - SphericalPoint sphericalPoint4 = new SphericalPoint(v4); - - SphericalArc sphericalArc = new SphericalArc(sphericalPoint3, sphericalPoint4); - - // Does any point of the arc lie inside the triangle? - boolean expected = false; - for (int j = 0; j < 2; j++) { - expected |= sphericalTriangle.contains(sphericalArc.getSphericalPoint(j)); - } - - // Does any arc of the triangle cross the arc? - for (int j = 0; j < 3; j++) { - expected |= sphericalTriangle.getSphericalArc(j).intersectsArc(sphericalArc); - } - if (expected) { - intersectionCount++; - } - - boolean actual = sphericalTriangle.intersects(sphericalArc); - - assertEquals(expected, actual); - - } - assertTrue(intersectionCount > 100); - assertTrue(intersectionCount < 900); - } -} diff --git a/gcm3/src/test/java/util/stats/AT_BinContainer.java b/gcm3/src/test/java/util/stats/AT_BinContainer.java deleted file mode 100644 index 9d1dc7663..000000000 --- a/gcm3/src/test/java/util/stats/AT_BinContainer.java +++ /dev/null @@ -1,285 +0,0 @@ -package util.stats; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.util.ArrayList; -import java.util.List; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.stats.BinContainer.Bin; -import util.stats.BinContainer.Builder; - -/** - * Test class for {@link BinContainer} - * - * @author Shawn Hatch - * - */ -@UnitTest(target = BinContainer.class) -public class AT_BinContainer { - /** - * Tests {@link BinContainer.Bin#Bin(double, double, int)} construction - */ - @Test - @UnitTestConstructor(target = BinContainer.Bin.class, args = { double.class, double.class, int.class }) - public void testBinConstructor() { - double lowerBound = 12.5; - double upperBound = 15.123; - int count = 55; - Bin bin = new Bin(lowerBound, upperBound, count); - assertEquals(lowerBound, bin.getLowerBound(), 0); - assertEquals(upperBound, bin.getUpperBound(), 0); - assertEquals(count, bin.getCount()); - - // precondition checks - assertThrows(IllegalArgumentException.class, () -> new Bin(33, 31, 4)); - - assertThrows(IllegalArgumentException.class, () -> new Bin(30, 40, -1)); - - } - - /** - * Tests {@link BinContainer#builder(double)} construction - */ - @Test - @UnitTestMethod(name = "builder", args = { double.class }) - public void testBuilder() { - BinContainer.Builder builder = BinContainer.builder(3); - builder.addValue(2.3, 5); - builder.addValue(3, 2); - builder.addValue(10, 1); - builder.addValue(11, 0); - builder.addValue(-10, 3); - BinContainer binContainer = builder.build(); - - List expected = new ArrayList<>(); - expected.add(new Bin(-12.0, -9.0, 3)); - expected.add(new Bin(-9.0, -6.0, 0)); - expected.add(new Bin(-6.0, -3.0, 0)); - expected.add(new Bin(-3.0, 0.0, 0)); - expected.add(new Bin(0.0, 3.0, 5)); - expected.add(new Bin(3.0, 6.0, 2)); - expected.add(new Bin(6.0, 9.0, 0)); - expected.add(new Bin(9.0, 12.0, 1)); - - List actual = new ArrayList<>(); - for (int i = 0; i < binContainer.binCount(); i++) { - actual.add(binContainer.getBin(i)); - } - - assertEquals(expected, actual); - assertThrows(RuntimeException.class, () -> BinContainer.builder(0)); - - assertThrows(RuntimeException.class, () -> BinContainer.builder(-0.5)); - assertThrows(RuntimeException.class, () -> { - Builder builder2 = BinContainer.builder(5); - builder2.addValue(13, -1); - }); - } - - /** - * Tests {@link BinContainer#binCount()} - */ - @Test - @UnitTestMethod(name = "binCount", args = {}) - public void testBinCount() { - BinContainer.Builder builder = BinContainer.builder(3); - builder.addValue(2.3, 5); - builder.addValue(3, 2); - builder.addValue(10, 1); - builder.addValue(11, 0); - builder.addValue(-10, 3); - BinContainer binContainer = builder.build(); - - assertEquals(8, binContainer.binCount()); - } - - /** - * Tests {@link BinContainer#getBin(int)} - */ - @Test - @UnitTestMethod(name = "getBin", args = { int.class }) - public void testGetBin() { - BinContainer.Builder builder = BinContainer.builder(3); - builder.addValue(2.3, 5); - builder.addValue(3, 2); - builder.addValue(10, 1); - builder.addValue(11, 0); - builder.addValue(-10, 3); - BinContainer binContainer = builder.build(); - - assertEquals(new Bin(-12.0, -9.0, 3), binContainer.getBin(0)); - assertEquals(new Bin(-9.0, -6.0, 0), binContainer.getBin(1)); - assertEquals(new Bin(-6.0, -3.0, 0), binContainer.getBin(2)); - assertEquals(new Bin(-3.0, 0.0, 0), binContainer.getBin(3)); - assertEquals(new Bin(0.0, 3.0, 5), binContainer.getBin(4)); - assertEquals(new Bin(3.0, 6.0, 2), binContainer.getBin(5)); - assertEquals(new Bin(6.0, 9.0, 0), binContainer.getBin(6)); - assertEquals(new Bin(9.0, 12.0, 1), binContainer.getBin(7)); - - // precondition tests - assertThrows(RuntimeException.class, () -> binContainer.getBin(-1)); - assertThrows(RuntimeException.class, () -> binContainer.getBin(1000000)); - - } - - /** - * Tests {@link BinContainer.Bin#toString()} - */ - @Test - @UnitTestMethod(target = Bin.class, name = "toString", args = {}) - public void testBinToString() { - BinContainer binContainer = BinContainer.builder(3)// - .addValue(2.1, 5)// - .addValue(5.6, 1)// - .addValue(12.67, 3)// - .build();// - - List expectedStrings = new ArrayList<>(); - expectedStrings.add("Bin [lowerBound=0.0, upperBound=3.0, count=5]"); - expectedStrings.add("Bin [lowerBound=3.0, upperBound=6.0, count=1]"); - expectedStrings.add("Bin [lowerBound=6.0, upperBound=9.0, count=0]"); - expectedStrings.add("Bin [lowerBound=9.0, upperBound=12.0, count=0]"); - expectedStrings.add("Bin [lowerBound=12.0, upperBound=15.0, count=3]"); - - List actualStrings = new ArrayList<>(); - for (int i = 0; i < binContainer.binCount(); i++) { - actualStrings.add(binContainer.getBin(i).toString()); - } - assertEquals(expectedStrings, actualStrings); - } - - /** - * Tests {@link BinContainer.Bin#hashCode()} - */ - @Test - @UnitTestMethod(target = Bin.class, name = "hashCode", args = {}) - public void testBinHashCode() { - BinContainer binContainer1 = BinContainer .builder(3).addValue(2.1, 5)// - .addValue(5.6, 1)// - .addValue(12.67, 3)// - .build();// - - BinContainer binContainer2 = BinContainer .builder(3).addValue(2.1, 5)// - .addValue(5.6, 1)// - .addValue(12.67, 3)// - .build();// - - assertEquals(binContainer1.binCount(), binContainer2.binCount()); - - for (int i = 0; i < binContainer1.binCount(); i++) { - Bin bin1 = binContainer1.getBin(i); - Bin bin2 = binContainer2.getBin(i); - assertEquals(bin1, bin2); - assertEquals(bin1.hashCode(), bin2.hashCode()); - } - - } - - /** - * Tests {@link BinContainer.Bin#equals()} - */ - @Test - @UnitTestMethod(target = Bin.class, name = "equals", args = { Object.class }) - public void testBinEquals() { - BinContainer binContainer1 = BinContainer .builder(3).addValue(2.1, 5)// - .addValue(5.6, 1)// - .addValue(6.3, 1)// - .addValue(12.67, 3)// - .build();// - - BinContainer binContainer2 = BinContainer .builder(3).addValue(2.1, 5)// - .addValue(5.6, 1)// - .addValue(6.3, 1)// - .addValue(12.67, 3)// - .build();// - - assertEquals(binContainer1.binCount(), binContainer2.binCount()); - - int binCount = binContainer1.binCount(); - for (int i = 0; i < binCount; i++) { - Bin bin1 = binContainer1.getBin(i); - Bin bin2 = binContainer2.getBin(i); - - assertEquals(bin1, bin2); - assertEquals(bin1, bin1); - assertNotEquals(bin1, null); - assertNotEquals(bin1, new Object()); - } - for (int i = 0; i < binCount; i++) { - for (int j = i + 1; j < binCount; j++) { - Bin bin1 = binContainer1.getBin(i); - Bin bin2 = binContainer1.getBin(j); - assertNotEquals(bin1, bin2); - } - } - } - - /** - * Tests {@link BinContainer.Bin#getLowerBound()} - */ - @Test - @UnitTestMethod(target = Bin.class, name = "getLowerBound", args = {}) - public void testGetLowerBound() { - BinContainer binContainer = BinContainer.builder(3)// - .addValue(2.1, 5)// - .addValue(5.6, 1)// - .addValue(6.3, 1)// - .addValue(12.67, 3)// - .build();// - - assertEquals(0.0, binContainer.getBin(0).getLowerBound(), 0); - assertEquals(3.0, binContainer.getBin(1).getLowerBound(), 0); - assertEquals(6.0, binContainer.getBin(2).getLowerBound(), 0); - assertEquals(9.0, binContainer.getBin(3).getLowerBound(), 0); - assertEquals(12.0, binContainer.getBin(4).getLowerBound(), 0); - - } - - /** - * Tests {@link BinContainer.Bin#getUpperBound()} - */ - @Test - @UnitTestMethod(target = Bin.class, name = "getUpperBound", args = {}) - public void testGetUpperBound() { - BinContainer binContainer = BinContainer.builder(3)// - .addValue(2.1, 5)// - .addValue(5.6, 1)// - .addValue(6.3, 1)// - .addValue(12.67, 3)// - .build();// - - assertEquals(3.0, binContainer.getBin(0).getUpperBound(), 0); - assertEquals(6.0, binContainer.getBin(1).getUpperBound(), 0); - assertEquals(9.0, binContainer.getBin(2).getUpperBound(), 0); - assertEquals(12.0, binContainer.getBin(3).getUpperBound(), 0); - assertEquals(15.0, binContainer.getBin(4).getUpperBound(), 0); - } - - /** - * Tests {@link BinContainer.Bin#getCount()} - */ - @Test - @UnitTestMethod(target = Bin.class, name = "getCount", args = {}) - public void testGetCount() { - BinContainer binContainer = BinContainer.builder(3)// - .addValue(2.1, 5)// - .addValue(5.6, 1)// - .addValue(6.3, 1)// - .addValue(12.67, 3)// - .build();// - - assertEquals(5, binContainer.getBin(0).getCount()); - assertEquals(1, binContainer.getBin(1).getCount()); - assertEquals(1, binContainer.getBin(2).getCount()); - assertEquals(0, binContainer.getBin(3).getCount()); - assertEquals(3, binContainer.getBin(4).getCount()); - } - -} diff --git a/gcm3/src/test/java/util/stats/AT_ImmutableStat.java b/gcm3/src/test/java/util/stats/AT_ImmutableStat.java deleted file mode 100644 index b3b1d38b1..000000000 --- a/gcm3/src/test/java/util/stats/AT_ImmutableStat.java +++ /dev/null @@ -1,221 +0,0 @@ -package util.stats; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.apache.commons.math3.random.RandomGenerator; -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestMethod; -import util.random.RandomGeneratorProvider; - -/** - * Test class for {@link ImmutableStat} - * - * @author Shawn Hatch - * - */ -@UnitTest(target = ImmutableStat.class) -public class AT_ImmutableStat { - - - /** - * Tests {@link ImmutableStat#builder()} - */ - @Test - @UnitTestMethod(name = "builder", args = {}) - public void testBuilder() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7777875192439812269L); - - ImmutableStat immutableStat = ImmutableStat .builder().setMax(0)// - .setMin(0)// - .setMean(0)// - .setSize(1)// - .setVariance(0)// - .build();// - - assertNotNull(immutableStat); - - /* - * if the size is negative - */ - assertThrows(RuntimeException.class, () -> { - ImmutableStat .builder().setMax(0)// - .setMin(0)// - .setMean(0)// - .setSize(-1)// - .setVariance(0)// - .build();// - - }); - - /* - * if the size value is one and the min mean and max are not equal - */ - assertThrows(RuntimeException.class, () -> { - ImmutableStat .builder().setMax(1)// - .setMin(0)// - .setMean(0)// - .setSize(1)// - .setVariance(0)// - .build();// - - }); - - /* if the size value is one and the variance is not zero */ - assertThrows(RuntimeException.class, () -> { - ImmutableStat .builder().setMax(0)// - .setMin(0)// - .setMean(0)// - .setSize(1)// - .setVariance(1)// - .build();// - - }); - - /* - * if the size value is greater than one and the min exceeds the max - */ - assertThrows(RuntimeException.class, () -> { - ImmutableStat .builder().setMax(0)// - .setMin(1)// - .setMean(0)// - .setSize(2)// - .setVariance(0)// - .build();// - - }); - - /* - * if the size value is greater than one and the min exceeds the mean - */ - assertThrows(RuntimeException.class, () -> { - ImmutableStat .builder().setMax(2)// - .setMin(1)// - .setMean(0)// - .setSize(2)// - .setVariance(0)// - .build();// - - }); - - /* - * if the size value is greater than one and the mean exceeds the max - */ - assertThrows(RuntimeException.class, () -> { - ImmutableStat .builder().setMax(2)// - .setMin(1)// - .setMean(3)// - .setSize(2)// - .setVariance(0)// - .build();// - - }); - - /* - * if the size value is greater than one and the variance is negative - */ - assertThrows(RuntimeException.class, () -> { - ImmutableStat .builder().setMax(0)// - .setMin(0)// - .setMean(0)// - .setSize(2)// - .setVariance(-1)// - .build();// - - }); - - for (int i = 0; i < 100; i++) { - double max = randomGenerator.nextDouble() * 100; - double min = randomGenerator.nextDouble() * max; - double mean = randomGenerator.nextDouble() * (max - min) + min; - int size = randomGenerator.nextInt(30) + 2; - double variance = (randomGenerator.nextDouble() * 0.1 + 0.5) * (max - min) + min; - - immutableStat = ImmutableStat .builder()// - .setMax(max)// - .setMin(min)// - .setMean(mean)// - .setSize(size)// - .setVariance(variance)// - .build();// - - assertEquals(max, immutableStat.getMax().get(), 0); - assertEquals(min, immutableStat.getMin().get(), 0); - assertEquals(mean, immutableStat.getMean().get(), 0); - assertEquals(variance, immutableStat.getVariance().get(), 0); - assertEquals(size, immutableStat.size()); - } - - } - - /** - * Tests {@link ImmutableStat#getMean()} - */ - @Test - @UnitTestMethod(name = "getMean", args = {}) - public void testGetMean() { - // covered by testBuilder() - } - - /** - * Tests {@link ImmutableStat#getMax()} - */ - @Test - @UnitTestMethod(name = "getMax", args = {}) - public void testGetMax() { - // covered by testBuilder() - } - - /** - * Tests {@link ImmutableStat#getMin()} - */ - @Test - @UnitTestMethod(name = "getMin", args = {}) - public void testGetMin() { - // covered by testBuilder() - } - - /** - * Tests {@link ImmutableStat#getStandardDeviation()} - */ - @Test - @UnitTestMethod(name = "getStandardDeviation", args = {}) - public void testGetStandardDeviation() { - // covered by testBuilder() - } - - /** - * Tests {@link ImmutableStat#getVariance()} - */ - @Test - @UnitTestMethod(name = "getVariance", args = {}) - public void testGetVariance() { - // covered by testBuilder() - } - - /** - * Tests {@link ImmutableStat#size()} - */ - @Test - @UnitTestMethod(name = "size", args = {}) - public void testSize() { - // covered by testBuilder() - } - - /** - * Tests {@link ImmutableStat#toString()} - */ - @Test - @UnitTestMethod(name = "toString", args = {}) - public void testToString() { - ImmutableStat immutableStat = ImmutableStat.builder().setMax(3.4).setMin(1.1).setMean(1.9).setSize(20).setVariance(0.5).build(); - - String expected = "ImmutableStat [mean=1.9, variance=0.5, standardDeviation=0.7071067811865476, max=3.4, min=1.1, size=20]"; - String actual = immutableStat.toString(); - assertEquals(expected, actual); - } - -} diff --git a/gcm3/src/test/java/util/stats/AT_MutableStat.java b/gcm3/src/test/java/util/stats/AT_MutableStat.java deleted file mode 100644 index 6204de642..000000000 --- a/gcm3/src/test/java/util/stats/AT_MutableStat.java +++ /dev/null @@ -1,332 +0,0 @@ -package util.stats; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Optional; - -import org.apache.commons.math3.random.RandomGenerator; -import org.apache.commons.math3.util.FastMath; -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.random.RandomGeneratorProvider; - -/** - * Test class for {@link MutableStat} - * - * @author Shawn Hatch - * - */ -@UnitTest(target = MutableStat.class) -public class AT_MutableStat { - - - private static final double TOLERANCE = 0.0001; - - private static void showSimilar(Stat stat1, Stat stat2) { - assertEquals(stat1.size(), stat2.size()); - if (stat1.size() > 0) { - showSimilarValues(stat1.getMin().get(), stat2.getMin().get()); - showSimilarValues(stat1.getMax().get(), stat2.getMax().get()); - showSimilarValues(stat1.getMean().get(), stat2.getMean().get()); - showSimilarValues(stat1.getStandardDeviation().get(), stat2.getStandardDeviation().get()); - showSimilarValues(stat1.getVariance().get(), stat2.getVariance().get()); - } - } - - private static void showSimilarValues(double value1, double value2) { - double mid = (value1 + value2) / 2; - double portion = Math.abs((mid - value1) / mid); - if (Double.isFinite(portion)) { - assertTrue(portion < TOLERANCE); - } else { - double diff = Math.abs(value2 - value1); - assertTrue(diff < TOLERANCE); - } - } - - /** - * Tests {@link MutableStat#combineStats(Stat...)} - */ - @Test - @UnitTestMethod(name = "combineStats", args = { Stat[].class }) - public void testCombineStats() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5642328443843803200L); - - for (int k = 0; k < 100; k++) { - int n = randomGenerator.nextInt(10); - MutableStat stat1 = new MutableStat(); - List values1 = new ArrayList<>(); - for (int i = 0; i < n; i++) { - double value = randomGenerator.nextDouble() * 100 + 50; - values1.add(value); - stat1.add(value); - } - - // make sure that at least one of the stat objects is non-empty - n = randomGenerator.nextInt(10) + 1; - MutableStat stat2 = new MutableStat(); - List values2 = new ArrayList<>(); - for (int i = 0; i < n; i++) { - double value = randomGenerator.nextDouble() * 90 + 70; - values2.add(value); - stat2.add(value); - } - - MutableStat expectedStat = new MutableStat(); - for (int i = 0; i < values1.size(); i++) { - expectedStat.add(values1.get(i)); - } - for (int i = 0; i < values2.size(); i++) { - expectedStat.add(values2.get(i)); - } - - Stat combinedStat = MutableStat.combineStats(stat1, stat2); - - showSimilar(expectedStat, combinedStat); - } - } - - /** - * Tests {@link MutableStat#combineStatsCollection(Collection)} - */ - @Test - @UnitTestMethod(name = "combineStatsCollection", args = { Collection.class }) - public void testCombineStatsCollection() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2697343520338303649L); - - for (int k = 0; k < 100; k++) { - int n = randomGenerator.nextInt(10); - MutableStat stat1 = new MutableStat(); - List values1 = new ArrayList<>(); - for (int i = 0; i < n; i++) { - double value = randomGenerator.nextDouble() * 100 + 50; - values1.add(value); - stat1.add(value); - } - - // make sure that at least one of the stat objects is non-empty - n = randomGenerator.nextInt(10) + 1; - MutableStat stat2 = new MutableStat(); - List values2 = new ArrayList<>(); - for (int i = 0; i < n; i++) { - double value = randomGenerator.nextDouble() * 90 + 70; - values2.add(value); - stat2.add(value); - } - - MutableStat expectedStat = new MutableStat(); - for (int i = 0; i < values1.size(); i++) { - expectedStat.add(values1.get(i)); - } - for (int i = 0; i < values2.size(); i++) { - expectedStat.add(values2.get(i)); - } - - List stats = new ArrayList<>(); - stats.add(stat1); - stats.add(stat2); - - Stat combinedStat = MutableStat.combineStatsCollection(stats); - - showSimilar(expectedStat, combinedStat); - } - } - - /** - * Tests {@link MutableStat#add(double)} - */ - @Test - @UnitTestMethod(name = "add", args = { double.class }) - public void testAdd() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4090068094278660804L); - - for (int i = 0; i < 1000; i++) { - MutableStat mutableStat = new MutableStat(); - List values = new ArrayList<>(); - int n = randomGenerator.nextInt(30) + 1; - for (int j = 0; j < n; j++) { - values.add(randomGenerator.nextDouble() * 100 - 50); - } - for (Double value : values) { - mutableStat.add(value); - } - - // test max - Optional actualMaxOptional = mutableStat.getMax(); - assertTrue(actualMaxOptional.isPresent()); - double actualMax = actualMaxOptional.get(); - double expectedMax = Double.NEGATIVE_INFINITY; - for (Double value : values) { - expectedMax = FastMath.max(expectedMax, value); - } - showSimilarValues(expectedMax, actualMax); - - // test size - assertEquals(values.size(), mutableStat.size()); - - // test min - Optional actualMinOptional = mutableStat.getMin(); - assertTrue(actualMinOptional.isPresent()); - double actualMin = actualMinOptional.get(); - double expectedMin = Double.POSITIVE_INFINITY; - for (Double value : values) { - expectedMin = FastMath.min(expectedMin, value); - } - showSimilarValues(expectedMin, actualMin); - - // test mean - Optional actualMeanOptional = mutableStat.getMean(); - assertTrue(actualMeanOptional.isPresent()); - double actualMean = actualMeanOptional.get(); - double expectedMean = 0; - for (Double value : values) { - expectedMean += value; - } - expectedMean /= values.size(); - showSimilarValues(expectedMean, actualMean); - - // test variance - double expectedVariance = 0; - for (Double value : values) { - expectedVariance += (value - expectedMean) * (value - expectedMean); - } - expectedVariance /= values.size(); - - Optional optionalVariance = mutableStat.getVariance(); - assertTrue(optionalVariance.isPresent()); - double actualVariance = optionalVariance.get(); - showSimilarValues(expectedVariance, actualVariance); - - // test standard deviation - double expectedStandardDeviation = FastMath.sqrt(expectedVariance); - Optional optionalStandardDeviation = mutableStat.getStandardDeviation(); - assertTrue(optionalStandardDeviation.isPresent()); - Double actualStandarDeviation = optionalStandardDeviation.get(); - showSimilarValues(expectedStandardDeviation, actualStandarDeviation); - } - - } - - /** - * Tests {@link MutableStat#getMean()} - */ - @Test - @UnitTestMethod(name = "getMean", args = {}) - public void testGetMean() { - // covered by testAdd() - } - - /** - * Tests {@link MutableStat#getMax()} - */ - @Test - @UnitTestMethod(name = "getMax", args = {}) - public void testGetMax() { - // covered by testAdd() - } - - /** - * Tests {@link MutableStat#getMin()} - */ - @Test - @UnitTestMethod(name = "getMin", args = {}) - public void testGetMin() { - // covered by testAdd() - } - - /** - * Tests {@link MutableStat#getStandardDeviation()} - */ - @Test - @UnitTestMethod(name = "getStandardDeviation", args = {}) - public void testGetStandardDeviation() { - // covered by testAdd() - } - - /** - * Tests {@link MutableStat#getVariance()} - */ - @Test - @UnitTestMethod(name = "getVariance", args = {}) - public void testGetVariance() { - // covered by testAdd() - } - - /** - * Tests {@link MutableStat#MutableStat()} - */ - @Test - @UnitTestConstructor(args = {}) - public void testConstructor() { - MutableStat mutableStat = new MutableStat(); - assertEquals(0, mutableStat.size()); - assertFalse(mutableStat.getMax().isPresent()); - assertFalse(mutableStat.getMin().isPresent()); - assertFalse(mutableStat.getMean().isPresent()); - assertFalse(mutableStat.getStandardDeviation().isPresent()); - assertFalse(mutableStat.getVariance().isPresent()); - } - - /** - * Tests {@link MutableStat#clear()} - */ - @Test - @UnitTestMethod(name = "clear", args = {}) - public void testClear() { - MutableStat mutableStat = new MutableStat(); - mutableStat.add(1); - mutableStat.add(2); - - assertTrue(mutableStat.size() > 0); - assertTrue(mutableStat.getMax().isPresent()); - assertTrue(mutableStat.getMin().isPresent()); - assertTrue(mutableStat.getMean().isPresent()); - assertTrue(mutableStat.getStandardDeviation().isPresent()); - assertTrue(mutableStat.getVariance().isPresent()); - - mutableStat.clear(); - - assertEquals(0, mutableStat.size()); - assertFalse(mutableStat.getMax().isPresent()); - assertFalse(mutableStat.getMin().isPresent()); - assertFalse(mutableStat.getMean().isPresent()); - assertFalse(mutableStat.getStandardDeviation().isPresent()); - assertFalse(mutableStat.getVariance().isPresent()); - - } - - /** - * Tests {@link MutableStat#size()} - */ - @Test - @UnitTestMethod(name = "size", args = {}) - public void testSize() { - // covered by testAdd() - } - - /** - * Tests {@link MutableStat#toString()} - */ - @Test - @UnitTestMethod(name = "toString", args = {}) - public void testToString() { - MutableStat mutableStat = new MutableStat(); - mutableStat.add(1); - mutableStat.add(2); - String expected = "MutableStat [getMean()=Optional[1.5], getVariance()=Optional[0.25], getStandardDeviation()=Optional[0.5], getMax()=Optional[2.0], getMin()=Optional[1.0], size()=2]"; - - String actual = mutableStat.toString(); - - assertEquals(expected, actual); - - } - -} diff --git a/gcm3/src/test/java/util/time/AT_TimeElapser.java b/gcm3/src/test/java/util/time/AT_TimeElapser.java deleted file mode 100644 index d037973cc..000000000 --- a/gcm3/src/test/java/util/time/AT_TimeElapser.java +++ /dev/null @@ -1,15 +0,0 @@ -package util.time; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - -@UnitTest(target = TimeElapser.class) -public class AT_TimeElapser { - - @Test - public void test() { - //placeholder test. TimeElapser is tested by manual inspection. - } - -} diff --git a/gcm3/src/test/java/util/tree/AT_Tree.java b/gcm3/src/test/java/util/tree/AT_Tree.java deleted file mode 100644 index 1b57b9548..000000000 --- a/gcm3/src/test/java/util/tree/AT_Tree.java +++ /dev/null @@ -1,15 +0,0 @@ -package util.tree; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; - -@UnitTest(target = Tree.class) -public class AT_Tree { - - - @Test - public void test() { - //placeholder - } -} diff --git a/gcm3/src/test/java/util/vector/AT_MutableVector2D.java b/gcm3/src/test/java/util/vector/AT_MutableVector2D.java deleted file mode 100644 index 631286936..000000000 --- a/gcm3/src/test/java/util/vector/AT_MutableVector2D.java +++ /dev/null @@ -1,1500 +0,0 @@ -package util.vector; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.apache.commons.math3.random.RandomGenerator; -import org.apache.commons.math3.util.FastMath; -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.random.RandomGeneratorProvider; -import util.spherical.Chirality; - -/** - * Test class for {@link MutableVector2D} - * - * @author Shawn Hatch - * - */ -@UnitTest(target = MutableVector2D.class) -public class AT_MutableVector2D { - - private static final double TOLERANCE = 0.000001; - - - - /** - * Tests {@linkplain MutableVector2D#add(MutableVector2D)} - */ - @Test - @UnitTestMethod(name = "add", args = { MutableVector2D.class }) - public void testAdd_MutableVector2D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(747753114709649380L); - - for (int i = 0; i < 100; i++) { - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v1 = new MutableVector2D(x1, y1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v2 = new MutableVector2D(x2, y2); - - v1.add(v2); - - assertEquals(x1 + x2, v1.getX(), 0); - assertEquals(y1 + y2, v1.getY(), 0); - - v1 = new MutableVector2D(x1, y1); - v1.add(x2, y2); - - } - } - - /** - * Tests {@linkplain MutableVector2D#add(double, double)} - */ - @Test - @UnitTestMethod(name = "add", args = { double.class, double.class }) - public void testAdd_DoubleDouble() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1765120437173433394L); - - for (int i = 0; i < 100; i++) { - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v1 = new MutableVector2D(x1, y1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - - v1 = new MutableVector2D(x1, y1); - v1.add(x2, y2); - - assertEquals(x1 + x2, v1.getX(), 0); - assertEquals(y1 + y2, v1.getY(), 0); - - } - } - - /** - * Tests {@linkplain MutableVector2D#add(Vector2D)} - */ - @Test - @UnitTestMethod(name = "add", args = { Vector2D.class }) - public void testAdd_Vector2D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1775748119067564239L); - - for (int i = 0; i < 100; i++) { - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v1 = new MutableVector2D(x1, y1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - - v1 = new MutableVector2D(x1, y1); - Vector2D v3 = new Vector2D(x2, y2); - v1.add(v3); - - // Tests {@linkplain MutableVector2D#add(Vector2D)} - assertEquals(x1 + x2, v1.getX(), 0); - assertEquals(y1 + y2, v1.getY(), 0); - } - } - - /** - * Tests {@linkplain MutableVector2D#Vector2D()} - */ - @Test - @UnitTestConstructor(args = {}) - public void testConstructors_Empty() { - - MutableVector2D v = new MutableVector2D(); - - assertEquals(0, v.getX(), 0); - assertEquals(0, v.getY(), 0); - - } - - /** - * Tests {@linkplain MutableVector2D#Vector2D(MutableVector2D)} - */ - @Test - @UnitTestConstructor(args = { MutableVector2D.class }) - public void testConstructors_MutableVector2D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4953024764558110367L); - - for (int i = 0; i < 100; i++) { - - MutableVector2D v = new MutableVector2D(); - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - - v = new MutableVector2D(x, y); - - MutableVector2D v2 = new MutableVector2D(v); - - // Tests {@linkplain MutableVector2D#Vector2D(MutableVector2D)} - assertEquals(v.getX(), v2.getX(), 0); - assertEquals(v.getY(), v2.getY(), 0); - } - } - - /** - * Tests {@linkplain MutableVector2D#MutableVector2D(Vector2D)} - */ - @Test - @UnitTestConstructor(args = { Vector2D.class }) - public void testConstructors_Vector2D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6697539793873697342L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v3 = new Vector2D(x, y); - - MutableVector2D v = new MutableVector2D(v3); - - assertEquals(x, v.getX(), 0); - assertEquals(y, v.getY(), 0); - - } - } - - /** - * Tests {@linkplain MutableVector2D#Vector2D(double, double)} - */ - @Test - @UnitTestConstructor(args = { double.class, double.class }) - public void testConstructors_Doubles() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(675407316550380164L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v = new MutableVector2D(x, y); - - assertEquals(x, v.getX(), 0); - assertEquals(y, v.getY(), 0); - } - } - - /** - * Tests {@linkplain MutableVector2D#addScaled(MutableVector2D, double)} - */ - @Test - @UnitTestMethod(name = "addScaled", args = { MutableVector2D.class, double.class }) - public void testAddScaled_MutableVector() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6215331323321750938L); - - for (int i = 0; i < 100; i++) { - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - MutableVector2D v1 = new MutableVector2D(x1, y1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v2 = new MutableVector2D(x2, y2); - - double scale = randomGenerator.nextDouble() * 1000 - 500; - - v1.addScaled(v2, scale); - - assertEquals(x1 + x2 * scale, v1.getX(), 0); - assertEquals(y1 + y2 * scale, v1.getY(), 0); - } - } - - /** - * Tests {@linkplain MutableVector2D#addScaled(Vector2D, double)} - */ - @Test - @UnitTestMethod(name = "addScaled", args = { Vector2D.class, double.class }) - public void testAddScaled_Vector() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1981525905399256435L); - - for (int i = 0; i < 100; i++) { - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - MutableVector2D v1 = new MutableVector2D(x1, y1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v2 = new Vector2D(x2, y2); - - double scale = randomGenerator.nextDouble() * 1000 - 500; - - v1.addScaled(v2, scale); - - assertEquals(x1 + x2 * scale, v1.getX(), 0); - assertEquals(y1 + y2 * scale, v1.getY(), 0); - } - - } - - /** - * Tests {@linkplain MutableVector2D#angle(Vector2D)} - */ - @Test - @UnitTestMethod(name = "angle", args = { Vector2D.class }) - public void testAngle_Vector() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3234050965506605506L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v1 = new MutableVector2D(x1, y1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v2 = new Vector2D(x2, y2); - - double length1 = FastMath.sqrt(x1 * x1 + y1 * y1); - double length2 = FastMath.sqrt(x2 * x2 + y2 * y2); - double dotProduct = x1 * x2 + y1 * y2; - double cosTheta = dotProduct / (length1 * length2); - double expectedValue = FastMath.acos(cosTheta); - - double actualValue = v1.angle(v2); - - assertEquals(expectedValue, actualValue, TOLERANCE); - - } - } - - /** - * Tests {@linkplain MutableVector2D#angle(MutableVector2D)} - */ - @Test - @UnitTestMethod(name = "angle", args = { MutableVector2D.class }) - public void testAngle_MutableVector2D() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6724319230891603465L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v1 = new MutableVector2D(x1, y1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v2 = new MutableVector2D(x2, y2); - - double length1 = FastMath.sqrt(x1 * x1 + y1 * y1); - double length2 = FastMath.sqrt(x2 * x2 + y2 * y2); - double dotProduct = x1 * x2 + y1 * y2; - double cosTheta = dotProduct / (length1 * length2); - double expectedValue = FastMath.acos(cosTheta); - - double actualValue = v1.angle(v2); - - assertEquals(expectedValue, actualValue, TOLERANCE); - - } - - } - - /** - * Tests {@linkplain MutableVector2D#assign(MutableVector2D)} - */ - @Test - @UnitTestMethod(name = "assign", args = { MutableVector2D.class }) - public void testAssign_MutableVector() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4060103816892775107L); - - // Tests {@linkplain MutableVector2D#assign(MutableVector2D)} - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v1 = new MutableVector2D(x1, y1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v2 = new MutableVector2D(x2, y2); - v2.assign(v1); - - assertEquals(x1, v2.getX(), TOLERANCE); - assertEquals(y1, v2.getY(), TOLERANCE); - - } - - } - - /** - * Tests {@linkplain MutableVector2D#assign(Vector2D)} - */ - @Test - @UnitTestMethod(name = "assign", args = { Vector2D.class }) - public void testAssign_Vector() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5850595925938665305L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v1 = new Vector2D(x1, y1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v2 = new MutableVector2D(x2, y2); - v2.assign(v1); - - assertEquals(x1, v2.getX(), TOLERANCE); - assertEquals(y1, v2.getY(), TOLERANCE); - - } - } - - /** - * Tests {@linkplain MutableVector2D#assign(double, double)} - */ - @Test - @UnitTestMethod(name = "assign", args = { double.class, double.class }) - public void testAssign_Doubles() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1722582342083801695L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v1 = new MutableVector2D(x1, y1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - - v1.assign(x2, y2); - - assertEquals(x2, v1.getX(), TOLERANCE); - assertEquals(y2, v1.getY(), TOLERANCE); - } - } - - /** - * Tests {@linkplain MutableVector2D#cross(MutableVector2D)} - */ - @Test - @UnitTestMethod(name = "cross", args = { MutableVector2D.class }) - public void testCross_MutableVector() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5493200042200712993L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v1 = new MutableVector2D(x1, y1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v2 = new MutableVector2D(x2, y2); - int actual = v2.cross(v1); - - double zComponentOf3DCrossProduct = (x2 * y1) - (x1 * y2); - int expected; - if (zComponentOf3DCrossProduct < 0) { - expected = -1; - } else { - expected = 1; - } - assertEquals(expected, actual); - - } - - } - - /** - * Tests {@linkplain MutableVector2D#cross(Vector2D)} - */ - @Test - @UnitTestMethod(name = "cross", args = { Vector2D.class }) - public void testCross_Vector() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2743696133291921471L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v1 = new Vector2D(x1, y1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v2 = new MutableVector2D(x2, y2); - int actual = v2.cross(v1); - - double zComponentOf3DCrossProduct = (x2 * y1) - (x1 * y2); - int expected; - if (zComponentOf3DCrossProduct < 0) { - expected = -1; - } else { - expected = 1; - } - assertEquals(expected, actual); - - } - } - - /** - * Tests {@linkplain MutableVector2D#distanceTo(MutableVector2D)} - */ - @Test - @UnitTestMethod(name = "distanceTo", args = { MutableVector2D.class }) - public void testDistanceTo_MutableVector2D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4615581309644565423L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v1 = new MutableVector2D(x1, y1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v2 = new MutableVector2D(x2, y2); - - double expected = FastMath.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); - - double actual = v2.distanceTo(v1); - - assertEquals(expected, actual, TOLERANCE); - - } - } - - /** - * Tests {@linkplain MutableVector2D#distanceTo(Vector2D)} - */ - @Test - @UnitTestMethod(name = "distanceTo", args = { Vector2D.class }) - public void testDistanceTo_Vector2D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7649019140878027553L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v1 = new Vector2D(x1, y1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v2 = new MutableVector2D(x2, y2); - - double expected = FastMath.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); - - double actual = v2.distanceTo(v1); - - assertEquals(expected, actual, TOLERANCE); - - } - - } - - /** - * Tests {@linkplain MutableVector2D#dot(MutableVector2D)} - */ - @Test - @UnitTestMethod(name = "dot", args = { MutableVector2D.class }) - public void testDot_MutableVector2D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5487028293556391537L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v1 = new MutableVector2D(x1, y1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v2 = new MutableVector2D(x2, y2); - - double expected = x1 * x2 + y1 * y2; - - double actual = v2.dot(v1); - - assertEquals(expected, actual, TOLERANCE); - - } - - } - - /** - * Tests {@linkplain MutableVector2D#dot(Vector2D))} - */ - @Test - @UnitTestMethod(name = "dot", args = { Vector2D.class }) - public void testDot_Vector2D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7050351241341904105L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v1 = new Vector2D(x1, y1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v2 = new MutableVector2D(x2, y2); - - double expected = x1 * x2 + y1 * y2; - - double actual = v2.dot(v1); - - assertEquals(expected, actual, TOLERANCE); - - } - } - - /** - * Tests {@linkplain MutableVector2D#zero()} - */ - @Test - @UnitTestMethod(name = "zero", args = {}) - public void testZero() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6725468903284973938L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v = new MutableVector2D(x, y); - - assertEquals(x, v.getX(), 0); - assertEquals(y, v.getY(), 0); - - v.zero(); - - assertEquals(0, v.getX(), 0); - assertEquals(0, v.getY(), 0); - - } - } - - /** - * Tests {@linkplain MutableVector2D#get(int)} - */ - @Test - @UnitTestMethod(name = "get", args = { int.class }) - public void testGet() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5651219733795356716L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v = new MutableVector2D(x, y); - - assertEquals(x, v.get(0), 0); - assertEquals(y, v.get(1), 0); - - } - } - - /** - * Tests {@linkplain MutableVector2D#getX()} - */ - @Test - @UnitTestMethod(name = "getX", args = {}) - public void testGetX() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2694122746003169803L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v = new MutableVector2D(x, y); - - assertEquals(x, v.getX(), 0); - } - } - - /** - * Tests {@linkplain MutableVector2D#getY()} - */ - @Test - @UnitTestMethod(name = "getY", args = {}) - public void testGetY() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(452804940306359312L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v = new MutableVector2D(x, y); - - assertEquals(y, v.getY(), 0); - } - } - - /** - * Tests {@linkplain MutableVector2D#scale(double)} - */ - @Test - @UnitTestMethod(name = "scale", args = { double.class }) - public void testScale() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7342985176084803934L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v = new MutableVector2D(x, y); - double scalar = randomGenerator.nextDouble() * 1000 - 500; - - v.scale(scalar); - - assertEquals(x * scalar, v.getX(), 0); - assertEquals(y * scalar, v.getY(), 0); - } - } - - /** - * Tests {@linkplain MutableVector2D#setX(double)} - */ - @Test - @UnitTestMethod(name = "setX", args = { double.class }) - public void testSetX() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7594665551747971351L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v = new MutableVector2D(x, y); - - double newX = randomGenerator.nextDouble() * 1000 - 500; - - v.setX(newX); - - assertEquals(newX, v.getX(), 0); - assertEquals(y, v.getY(), 0); - } - } - - /** - * Tests {@linkplain MutableVector2D#setY(double)} - */ - @Test - @UnitTestMethod(name = "setY", args = { double.class }) - public void testSetY() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3745398517861233669L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v = new MutableVector2D(x, y); - - double newY = randomGenerator.nextDouble() * 1000 - 500; - - v.setY(newY); - - assertEquals(x, v.getX(), 0); - assertEquals(newY, v.getY(), 0); - - } - } - - /** - * Tests {@linkplain MutableVector2D#sub(MutableVector2D)} - */ - @Test - @UnitTestMethod(name = "sub", args = { MutableVector2D.class }) - public void testSub_MutableVector2D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6934284701754808487L); - - for (int i = 0; i < 100; i++) { - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - MutableVector2D v1 = new MutableVector2D(x1, y1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v2 = new MutableVector2D(x2, y2); - - v1.sub(v2); - - assertEquals(x1 - x2, v1.getX(), 0); - assertEquals(y1 - y2, v1.getY(), 0); - } - - } - - /** - * Tests {@linkplain MutableVector2D#sub(Vector2D)} - */ - @Test - @UnitTestMethod(name = "sub", args = { Vector2D.class }) - public void testSub_Vector2D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5759597061407181797L); - - for (int i = 0; i < 100; i++) { - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - MutableVector2D v1 = new MutableVector2D(x1, y1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v2 = new Vector2D(x2, y2); - - v1.sub(v2); - - assertEquals(x1 - x2, v1.getX(), 0); - assertEquals(y1 - y2, v1.getY(), 0); - } - } - - /** - * Tests {@linkplain MutableVector2D#sub(double, double)} - */ - @Test - @UnitTestMethod(name = "sub", args = { double.class, double.class }) - public void testSub_Doubles() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4488004263175079178L); - - for (int i = 0; i < 100; i++) { - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - MutableVector2D v1 = new MutableVector2D(x1, y1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - - v1 = new MutableVector2D(x1, y1); - v1.sub(x2, y2); - - assertEquals(x1 - x2, v1.getX(), 0); - assertEquals(y1 - y2, v1.getY(), 0); - } - } - - /** - * Tests {@linkplain MutableVector2D#isInfinite()} - * - */ - @Test - @UnitTestMethod(name = "isInfinite", args = {}) - public void testIsInfinite() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(852808991624264230L); - - for (int i = 0; i < 100; i++) { - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - MutableVector2D v = new MutableVector2D(); - - v.assign(x, y); - assertFalse(v.isInfinite()); - v.setX(Double.POSITIVE_INFINITY); - assertTrue(v.isInfinite()); - - v.assign(x, y); - assertFalse(v.isInfinite()); - v.setX(Double.NEGATIVE_INFINITY); - assertTrue(v.isInfinite()); - - v.assign(x, y); - assertFalse(v.isInfinite()); - v.setY(Double.POSITIVE_INFINITY); - assertTrue(v.isInfinite()); - - v.assign(x, y); - assertFalse(v.isInfinite()); - v.setY(Double.NEGATIVE_INFINITY); - assertTrue(v.isInfinite()); - } - } - - /** - * Tests {@linkplain MutableVector2D#isNaN()} - * - */ - @Test - @UnitTestMethod(name = "isNaN", args = {}) - public void testIsNaN() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6344060856602552434L); - - for (int i = 0; i < 100; i++) { - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - MutableVector2D v = new MutableVector2D(); - - v.assign(x, y); - assertFalse(v.isNaN()); - v.setX(Double.NaN); - assertTrue(v.isNaN()); - - v.assign(x, y); - assertFalse(v.isNaN()); - v.setY(Double.NaN); - assertTrue(v.isNaN()); - } - } - - /** - * Tests {@linkplain MutableVector2D#isFinite()} - * - */ - @Test - @UnitTestMethod(name = "isFinite", args = {}) - public void testIsFinite() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8677841486644718998L); - - for (int i = 0; i < 100; i++) { - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - MutableVector2D v = new MutableVector2D(); - - v.assign(x, y); - assertTrue(v.isFinite()); - v.setX(Double.NaN); - assertFalse(v.isFinite()); - - v.assign(x, y); - assertTrue(v.isFinite()); - v.setX(Double.NEGATIVE_INFINITY); - assertFalse(v.isFinite()); - - v.assign(x, y); - assertTrue(v.isFinite()); - v.setX(Double.POSITIVE_INFINITY); - assertFalse(v.isFinite()); - - v.assign(x, y); - assertTrue(v.isFinite()); - v.setY(Double.NaN); - assertFalse(v.isFinite()); - - v.assign(x, y); - assertTrue(v.isFinite()); - v.setY(Double.NEGATIVE_INFINITY); - assertFalse(v.isFinite()); - - v.assign(x, y); - assertTrue(v.isFinite()); - v.setY(Double.POSITIVE_INFINITY); - assertFalse(v.isFinite()); - - } - } - - /** - * Tests {@linkplain MutableVector2D#squareDistanceTo(MutableVector2D)} - */ - @Test - @UnitTestMethod(name = "squareDistanceTo", args = { MutableVector2D.class }) - public void testSquareDistanceTo_MutableVector2D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3118694974463156175L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v1 = new MutableVector2D(x1, y1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v2 = new MutableVector2D(x2, y2); - - double expected = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2); - - double actual = v2.squareDistanceTo(v1); - - assertEquals(expected, actual, 0); - - } - - } - - /** - * Tests {@linkplain MutableVector2D#squareDistanceTo(Vector2D)} - */ - @Test - @UnitTestMethod(name = "squareDistanceTo", args = { Vector2D.class }) - public void testSquareDistanceTo_Vector2D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1850329192838045338L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v1 = new Vector2D(x1, y1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v2 = new MutableVector2D(x2, y2); - - double expected = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2); - - double actual = v2.squareDistanceTo(v1); - - assertEquals(expected, actual, 0); - - } - } - - /** - * Tests {@linkplain MutableVector2D#reverse()} - */ - @Test - @UnitTestMethod(name = "reverse", args = {}) - public void testReverse() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6922804374672425062L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v = new MutableVector2D(x, y); - v.reverse(); - - assertEquals(-x, v.getX(), 0); - assertEquals(-y, v.getY(), 0); - } - } - - /** - * Tests {@linkplain MutableVector2D#length()} - */ - @Test - @UnitTestMethod(name = "length", args = {}) - public void testLength() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4939922046347545520L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v = new MutableVector2D(x, y); - double expectedLength = FastMath.sqrt(x * x + y * y); - double actualLength = v.length(); - - assertEquals(expectedLength, actualLength, TOLERANCE); - } - } - - /** - * Tests {@linkplain MutableVector2D#squareLength()} - */ - @Test - @UnitTestMethod(name = "squareLength", args = {}) - public void testSquareLength() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3766758455538226142L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v = new MutableVector2D(x, y); - double expectedLength = x * x + y * y; - double actualLength = v.squareLength(); - - assertEquals(expectedLength, actualLength, 0); - } - } - - /** - * Tests {@linkplain MutableVector2D#toArray()} - */ - @Test - @UnitTestMethod(name = "toArray", args = {}) - public void testToArray() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6552417458281120706L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v = new MutableVector2D(x, y); - double[] array = v.toArray(); - - assertEquals(v.getX(), array[0], 0); - assertEquals(v.getY(), array[1], 0); - } - } - - /** - * Tests {@linkplain MutableVector2D#normalize()} - */ - @Test - @UnitTestMethod(name = "normalize", args = {}) - public void testNormalize() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4958663962485721256L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v = new MutableVector2D(x, y); - v.normalize(); - - assertEquals(1, v.length(), TOLERANCE); - - } - } - - /** - * Tests {@linkplain MutableVector2D#equals(Object)} - */ - @Test - @UnitTestMethod(name = "equals", args = { Object.class }) - public void testEquals() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6650742161199839711L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v1 = new MutableVector2D(x, y); - - MutableVector2D v2 = new MutableVector2D(x, y); - - MutableVector2D v3 = new MutableVector2D(x, y); - - // reflexive - assertEquals(v1, v1); - - // symetric - assertEquals(v1, v2); - assertEquals(v2, v1); - - // transitive - assertEquals(v1, v2); - assertEquals(v2, v3); - assertEquals(v3, v1); - - } - } - - /** - * Tests {@linkplain MutableVector2D#hashCode()} - */ - @Test - @UnitTestMethod(name = "hashCode", args = {}) - public void testHashCode() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6711537897020009773L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v1 = new MutableVector2D(x, y); - - MutableVector2D v2 = new MutableVector2D(x, y); - - assertEquals(v1.hashCode(), v2.hashCode()); - - } - } - - /** - * Tests {@linkplain MutableVector2D#toString()} - */ - @Test - @UnitTestMethod(name = "toString", args = {}) - public void testToString() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7265036568767953542L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v = new MutableVector2D(x, y); - - String expected = "Vector2D [x=" + x + ", y=" + y + "]"; - - String actual = v.toString(); - - assertEquals(expected, actual); - } - } - - /** - * Tests {@linkplain MutableVector2D#rotate(double)} - * - */ - @Test - @UnitTestMethod(name = "rotate", args = { double.class }) - public void testRotate() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(679273489159068423L); - - for (int i = 0; i < 100; i++) { - // ensure that v1 is not too close to a zero vector - MutableVector2D v1 = new MutableVector2D(); - while (v1.length() < TOLERANCE) { - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - v1 = new MutableVector2D(x1, y1); - } - - // Copy v1 and rotate the copy - MutableVector2D v2 = new MutableVector2D(v1); - double theta = randomGenerator.nextDouble() * 2 * FastMath.PI; - v2.rotate(theta); - - // v1 under rotation should have its length preserved - assertEquals(v1.length(), v2.length(), TOLERANCE); - - // v2 and v1 should have theta as their angle - double expectedAngle = theta; - while (expectedAngle < 0) { - expectedAngle += 2 * FastMath.PI; - } - while (expectedAngle > FastMath.PI) { - expectedAngle = 2 * FastMath.PI - expectedAngle; - } - assertEquals(expectedAngle, v2.angle(v1), TOLERANCE); - - // v2 and v1 should be oriented correctly - if (theta < FastMath.PI) { - assertEquals(1, v1.cross(v2)); - } else { - assertEquals(-1, v1.cross(v2)); - } - - // v2 when rotated back should return to its original position - v2.rotate(-theta); - assertEquals(v1.getX(), v2.getX(), TOLERANCE); - assertEquals(v1.getY(), v2.getY(), TOLERANCE); - } - } - - /** - * Tests {@linkplain MutableVector2D#rotateToward(MutableVector2D, double)} - */ - @Test - @UnitTestMethod(name = "rotateToward", args = { MutableVector2D.class, double.class }) - public void testRotateToward_MutableVector2D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7377823496934970629L); - - for (int i = 0; i < 100; i++) { - - // Ensure that v1 is not too close to the zero vector - MutableVector2D v1 = new MutableVector2D(); - while (v1.length() < TOLERANCE) { - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - v1 = new MutableVector2D(x1, y1); - } - - // Ensure that v2 is not too close to the zero vector - MutableVector2D v2 = new MutableVector2D(); - while (v2.length() < TOLERANCE) { - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - v2 = new MutableVector2D(x2, y2); - } - - double theta = randomGenerator.nextDouble() * 2 * FastMath.PI; - - MutableVector2D v3 = new MutableVector2D(v2); - v3.rotateToward(v1, theta); - - // Rotation toward another vector is equivalent to plain rotation - // with a possible sign change due to the relative orientation of - // the two vectors. - MutableVector2D v4 = new MutableVector2D(v2); - if (v2.cross(v1) > 0) { - v4.rotate(theta); - } else { - v4.rotate(-theta); - } - - assertEquals(v4.getX(), v3.getX(), TOLERANCE); - assertEquals(v4.getY(), v3.getY(), TOLERANCE); - - } - - } - - /** - * Tests {@linkplain MutableVector2D#rotateToward(Vector2D, double)} - */ - @Test - @UnitTestMethod(name = "rotateToward", args = { Vector2D.class, double.class }) - public void testRotateToward_Vector2D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8015075822914071291L); - - for (int i = 0; i < 100; i++) { - - // Ensure that v1 is not too close to the zero vector - Vector2D v1 = new Vector2D(); - while (v1.length() < TOLERANCE) { - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - v1 = new Vector2D(x1, y1); - } - - // Ensure that v2 is not too close to the zero vector - MutableVector2D v2 = new MutableVector2D(); - while (v2.length() < TOLERANCE) { - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - v2 = new MutableVector2D(x2, y2); - } - - double theta = randomGenerator.nextDouble() * 2 * FastMath.PI; - - MutableVector2D v3 = new MutableVector2D(v2); - v3.rotateToward(v1, theta); - - // Rotation toward another vector is equivalent to plain rotation - // with a possible sign change due to the relative orientation of - // the two vectors. - MutableVector2D v4 = new MutableVector2D(v2); - if (v2.cross(v1) > 0) { - v4.rotate(theta); - } else { - v4.rotate(-theta); - } - - assertEquals(v4.getX(), v3.getX(), TOLERANCE); - assertEquals(v4.getY(), v3.getY(), TOLERANCE); - - } - - } - - /** - * Tests {@linkplain MutableVector2D#isPerpendicularTo(Vector2D)} - */ - @Test - @UnitTestMethod(name = "isPerpendicularTo", args = { Vector2D.class }) - public void testIsPerpendicularTo_Vector2D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1251112927236624067L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v1 = new MutableVector2D(x1, y1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v2 = new MutableVector2D(x2, y2); - - Vector2D v3 = new Vector2D(v1); - v3 = v3.rotateToward(new Vector2D(v2), FastMath.toRadians(90)); - - assertTrue(v1.isPerpendicularTo(v3)); - - v3 = new Vector2D(v1); - v3 = v3.rotateToward(new Vector2D(v2), FastMath.PI / 2 - 2 * MutableVector3D.PERPENDICUALR_ANGLE_TOLERANCE); - assertFalse(v1.isPerpendicularTo(v3)); - - v3 = new Vector2D(v1); - v3 = v3.rotateToward(new Vector2D(v2), FastMath.PI / 2 + 2 * MutableVector3D.PERPENDICUALR_ANGLE_TOLERANCE); - assertFalse(v1.isPerpendicularTo(v3)); - } - - } - - /** - * Tests {@linkplain MutableVector2D#isPerpendicularTo(MutableVector2D)} - */ - @Test - @UnitTestMethod(name = "isPerpendicularTo", args = { MutableVector2D.class }) - public void testIsPerpendicularTo_MutableVector2D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1953671451170035329L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v1 = new MutableVector2D(x1, y1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v2 = new MutableVector2D(x2, y2); - - MutableVector2D v3 = new MutableVector2D(v1); - v3.rotateToward(v2, FastMath.toRadians(90)); - - assertTrue(v1.isPerpendicularTo(v3)); - - v3 = new MutableVector2D(v1); - v3.rotateToward(v2, FastMath.PI / 2 - 2 * MutableVector3D.PERPENDICUALR_ANGLE_TOLERANCE); - assertFalse(v1.isPerpendicularTo(v3)); - - v3 = new MutableVector2D(v1); - v3.rotateToward(v2, FastMath.PI / 2 + 2 * MutableVector3D.PERPENDICUALR_ANGLE_TOLERANCE); - assertFalse(v1.isPerpendicularTo(v3)); - } - - } - - /** - * Tests {@linkplain MutableVector2D#isNormal()} - */ - @Test - @UnitTestMethod(name = "isNormal", args = {}) - public void testIsNormal() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(366173807860588361L); - - int activeTestCount = 0; - for (int i = 0; i < 100; i++) { - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector2D v = new MutableVector2D(x, y); - - if (FastMath.abs(v.length() - 1) > MutableVector3D.NORMAL_LENGTH_TOLERANCE) { - v.normalize(); - assertTrue(v.isNormal()); - activeTestCount++; - - MutableVector2D u = new MutableVector2D(v); - u.scale(1 - 2 * MutableVector3D.NORMAL_LENGTH_TOLERANCE); - assertFalse(u.isNormal()); - - u = new MutableVector2D(v); - u.scale(1 + 2 * MutableVector3D.NORMAL_LENGTH_TOLERANCE); - assertFalse(u.isNormal()); - } - } - assertTrue(activeTestCount > 90); - } - - /** - * Tests {@linkplain MutableVector2D#perpendicularRotation(Chirality)} - */ - @Test - @UnitTestMethod(name = "perpendicularRotation", args = { Chirality.class }) - public void testPerpendicularRotation() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(437585069656161491L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - MutableVector2D v1 = new MutableVector2D(x1, y1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - MutableVector2D v2 = new MutableVector2D(x2, y2); - - v2.assign(v1); - v2.perpendicularRotation(Chirality.LEFT_HANDED); - // v2 should be perpendicular to v1 - assertEquals(FastMath.PI / 2, v2.angle(v1), TOLERANCE); - // v2 is clockwise of v1, so the cross product points up - assertEquals(1, v2.cross(v1)); - - v2.assign(v1); - v2.perpendicularRotation(Chirality.RIGHT_HANDED); - // v2 should be perpendicular to v1 - assertEquals(FastMath.PI / 2, v2.angle(v1), TOLERANCE); - // v2 is clockwise of v1, so the cross product points down - assertEquals(-1, v2.cross(v1)); - - } - - } - -} diff --git a/gcm3/src/test/java/util/vector/AT_MutableVector3D.java b/gcm3/src/test/java/util/vector/AT_MutableVector3D.java deleted file mode 100644 index 04ea18718..000000000 --- a/gcm3/src/test/java/util/vector/AT_MutableVector3D.java +++ /dev/null @@ -1,1724 +0,0 @@ -package util.vector; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.apache.commons.math3.random.RandomGenerator; -import org.apache.commons.math3.util.FastMath; -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.random.RandomGeneratorProvider; - -/** - * Test class for {@link MutableVector3D} - * - * @author Shawn Hatch - * - */ -@UnitTest(target = MutableVector3D.class) -public class AT_MutableVector3D { - - private static final double TOLERANCE = 0.000001; - - /** - * Tests {@linkplain MutableVector3D#add(MutableVector3D)} - */ - @Test - @UnitTestMethod(name = "add", args = { MutableVector3D.class }) - public void testAdd_MutableVector3D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8344611551021569928L); - - for (int i = 0; i < 100; i++) { - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - MutableVector3D v1 = new MutableVector3D(x1, y1, z1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v2 = new MutableVector3D(x2, y2, z2); - v1.add(v2); - - // Tests {@linkplain MutableVector3D#add(MutableVector3D)} - assertEquals(x1 + x2, v1.getX(), 0); - assertEquals(y1 + y2, v1.getY(), 0); - assertEquals(z1 + z2, v1.getZ(), 0); - } - } - - /** - * Tests {@linkplain MutableVector3D#add(Vector3D)} - */ - @Test - @UnitTestMethod(name = "add", args = { Vector3D.class }) - public void testAdd_Vector3D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(487766521597469529L); - - for (int i = 0; i < 100; i++) { - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - MutableVector3D v1 = new MutableVector3D(x1, y1, z1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v2 = new Vector3D(x2, y2, z2); - v1.add(v2); - - // Tests {@linkplain MutableVector3D#add(Vector3D)} - assertEquals(x1 + x2, v1.getX(), 0); - assertEquals(y1 + y2, v1.getY(), 0); - assertEquals(z1 + z2, v1.getZ(), 0); - - } - } - - /** - * Tests {@linkplain MutableVector3D#add(double, double, double)} - */ - @Test - @UnitTestMethod(name = "add", args = { double.class, double.class, double.class }) - public void testAdd_Doubles() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4500585622291483492L); - - for (int i = 0; i < 100; i++) { - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - MutableVector3D v1 = new MutableVector3D(x1, y1, z1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - v1.add(x2, y2, z2); - - // Tests {@linkplain MutableVector3D#add(double, double, double)} - assertEquals(x1 + x2, v1.getX(), 0); - assertEquals(y1 + y2, v1.getY(), 0); - assertEquals(z1 + z2, v1.getZ(), 0); - - } - } - - /** - * Tests {@linkplain MutableVector3D#Vector3D()} - */ - @Test - @UnitTestConstructor(args = {}) - public void testConstructors_Empty() { - - MutableVector3D v = new MutableVector3D(); - - assertEquals(0, v.getX(), 0); - assertEquals(0, v.getY(), 0); - assertEquals(0, v.getZ(), 0); - } - - /** - * Tests {@linkplain MutableVector3D#Vector3D(MutableVector3D)} - */ - @Test - @UnitTestConstructor(args = { MutableVector3D.class }) - public void testConstructors_MutableVector3D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(584615761790582524L); - - for (int i = 0; i < 100; i++) { - MutableVector3D v = new MutableVector3D(); - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - v = new MutableVector3D(x, y, z); - - MutableVector3D v2 = new MutableVector3D(v); - - assertEquals(x, v2.getX(), 0); - assertEquals(y, v2.getY(), 0); - assertEquals(z, v2.getZ(), 0); - - } - } - - /** - * Tests {@linkplain MutableVector3D#Vector3D(Vector3D)} - */ - @Test - @UnitTestConstructor(args = { Vector3D.class }) - public void testConstructors_Vector3D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8962175229980057680L); - - for (int i = 0; i < 100; i++) { - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v = new Vector3D(x, y, z); - - MutableVector3D v2 = new MutableVector3D(v); - - assertEquals(x, v2.getX(), 0); - assertEquals(y, v2.getY(), 0); - assertEquals(z, v2.getZ(), 0); - } - } - - /** - * Tests {@linkplain MutableVector3D#Vector3D(double, double, double)} - */ - @Test - @UnitTestConstructor(args = { double.class, double.class, double.class }) - public void testConstructors_Doubles() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(110008594144948233L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v = new MutableVector3D(x, y, z); - - assertEquals(x, v.getX(), 0); - assertEquals(y, v.getY(), 0); - assertEquals(z, v.getZ(), 0); - - } - } - - /** - * Tests {@linkplain MutableVector3D#addScaled(Vector3D, double)} - */ - @Test - @UnitTestMethod(name = "addScaled", args = { Vector3D.class, double.class }) - public void testAddScaled_Vector3D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2338666481384433285L); - - for (int i = 0; i < 100; i++) { - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - MutableVector3D v1 = new MutableVector3D(x1, y1, z1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - double scale = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v3 = new Vector3D(x2, y2, z2); - scale = randomGenerator.nextDouble() * 1000 - 500; - v1.addScaled(v3, scale); - - assertEquals(x1 + x2 * scale, v1.getX(), 0); - assertEquals(y1 + y2 * scale, v1.getY(), 0); - assertEquals(z1 + z2 * scale, v1.getZ(), 0); - } - } - - /** - * Tests {@linkplain MutableVector3D#addScaled(MutableVector3D, double)} - */ - @Test - @UnitTestMethod(name = "addScaled", args = { MutableVector3D.class, double.class }) - public void testAddScaled_MutableVector3D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6770082568533230814L); - - for (int i = 0; i < 100; i++) { - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - MutableVector3D v1 = new MutableVector3D(x1, y1, z1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v2 = new MutableVector3D(x2, y2, z2); - - double scale = randomGenerator.nextDouble() * 1000 - 500; - - v1.addScaled(v2, scale); - - // Tests {@linkplain MutableVector3D#addScaled(MutableVector3D, - // double)} - assertEquals(x1 + x2 * scale, v1.getX(), 0); - assertEquals(y1 + y2 * scale, v1.getY(), 0); - assertEquals(z1 + z2 * scale, v1.getZ(), 0); - - } - } - - /** - * Tests {@linkplain MutableVector3D#angle(MutableVector3D)} - */ - @Test - @UnitTestMethod(name = "angle", args = { MutableVector3D.class }) - public void testAngle_MutableVector3D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8975718787496880209L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v1 = new MutableVector3D(x1, y1, z1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v2 = new MutableVector3D(x2, y2, z2); - - double length1 = FastMath.sqrt(x1 * x1 + y1 * y1 + z1 * z1); - double length2 = FastMath.sqrt(x2 * x2 + y2 * y2 + z2 * z2); - double dotProduct = x1 * x2 + y1 * y2 + z1 * z2; - double cosTheta = dotProduct / (length1 * length2); - double expectedValue = FastMath.acos(cosTheta); - - double actualValue = v1.angle(v2); - - assertEquals(expectedValue, actualValue, TOLERANCE); - - } - } - - /** - * Tests {@linkplain MutableVector3D#angle(Vector3D)} - */ - @Test - @UnitTestMethod(name = "angle", args = { Vector3D.class }) - public void testAngle_Vector3D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5742149925489685535L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v1 = new MutableVector3D(x1, y1, z1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - double length1 = FastMath.sqrt(x1 * x1 + y1 * y1 + z1 * z1); - double length2 = FastMath.sqrt(x2 * x2 + y2 * y2 + z2 * z2); - double dotProduct = x1 * x2 + y1 * y2 + z1 * z2; - double cosTheta = dotProduct / (length1 * length2); - double expectedValue = FastMath.acos(cosTheta); - - Vector3D v3 = new Vector3D(x2, y2, z2); - double actualValue = v1.angle(v3); - assertEquals(expectedValue, actualValue, TOLERANCE); - - } - } - - /** - * Tests {@linkplain MutableVector3D#assign(MutableVector3D)} - */ - @Test - @UnitTestMethod(name = "assign", args = { MutableVector3D.class }) - public void testAssign_MutableVector3D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2514541920498591972L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v1 = new MutableVector3D(x1, y1, z1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v2 = new MutableVector3D(x2, y2, z2); - v2.assign(v1); - - assertEquals(x1, v2.getX(), TOLERANCE); - assertEquals(y1, v2.getY(), TOLERANCE); - assertEquals(z1, v2.getZ(), TOLERANCE); - - } - } - - /** - * Tests {@linkplain MutableVector3D#assign(Vector3D)} - */ - @Test - @UnitTestMethod(name = "assign", args = { Vector3D.class }) - public void testAssign_Vector3D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3353166711276217630L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v1 = new MutableVector3D(x1, y1, z1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v3 = new Vector3D(x2, y2, z2); - v1.assign(v3); - - assertEquals(x2, v1.getX(), TOLERANCE); - assertEquals(y2, v1.getY(), TOLERANCE); - assertEquals(z2, v1.getZ(), TOLERANCE); - } - } - - /** - * Tests {@linkplain MutableVector3D#assign(double, double, double)} - */ - @Test - @UnitTestMethod(name = "assign", args = { double.class, double.class, double.class }) - public void testAssign_Doubles() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2947081292012315306L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v1 = new MutableVector3D(x1, y1, z1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - v1.assign(x2, y2, z2); - - assertEquals(x2, v1.getX(), TOLERANCE); - assertEquals(y2, v1.getY(), TOLERANCE); - assertEquals(z2, v1.getZ(), TOLERANCE); - - } - } - - /** - * Tests {@linkplain MutableVector3D#cross(MutableVector3D)} - */ - @Test - @UnitTestMethod(name = "cross", args = { MutableVector3D.class }) - public void testCross_MutableVector3D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6476733743808996150L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v1 = new MutableVector3D(x1, y1, z1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v2 = new MutableVector3D(x2, y2, z2); - v2.cross(v1); - - double x3 = y2 * z1 - y1 * z2; - double y3 = x1 * z2 - x2 * z1; - double z3 = x2 * y1 - x1 * y2; - - // Tests {@linkplain MutableVector3D#cross(MutableVector3D)} - assertEquals(x3, v2.getX(), TOLERANCE); - assertEquals(y3, v2.getY(), TOLERANCE); - assertEquals(z3, v2.getZ(), TOLERANCE); - - } - } - - /** - * Tests {@linkplain MutableVector3D#cross(Vector3D))} - */ - @Test - @UnitTestMethod(name = "cross", args = { Vector3D.class }) - public void testCross_Vector3D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1707507467350285888L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v1 = new MutableVector3D(x1, y1, z1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v3 = new Vector3D(x2, y2, z2); - - double x3 = y1 * z2 - y2 * z1; - double y3 = x2 * z1 - x1 * z2; - double z3 = x1 * y2 - x2 * y1; - - v1.cross(v3); - - assertEquals(x3, v1.getX(), TOLERANCE); - assertEquals(y3, v1.getY(), TOLERANCE); - assertEquals(z3, v1.getZ(), TOLERANCE); - - } - } - - /** - * Tests {@linkplain MutableVector3D#distanceTo(MutableVector3D)} - */ - @Test - @UnitTestMethod(name = "distanceTo", args = { MutableVector3D.class }) - public void testDistanceTo_MutableVector3D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(459371333183410198L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v1 = new MutableVector3D(x1, y1, z1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v2 = new MutableVector3D(x2, y2, z2); - - double expected = FastMath.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) + (z1 - z2) * (z1 - z2)); - - double actual = v2.distanceTo(v1); - - // Tests {@linkplain MutableVector3D#distanceTo(MutableVector3D)} - assertEquals(expected, actual, TOLERANCE); - - Vector3D v3 = new Vector3D(x1, y1, z1); - actual = v2.distanceTo(v3); - - // Tests {@linkplain MutableVector3D#distanceTo(Vector3D)} - assertEquals(expected, actual, TOLERANCE); - - } - } - - /** - * Tests {@linkplain MutableVector3D#distanceTo(MutableVector3D)} - */ - @Test - @UnitTestMethod(name = "distanceTo", args = { Vector3D.class }) - public void testDistanceTo_Vector3D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4822545293429239908L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v1 = new MutableVector3D(x1, y1, z1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v2 = new MutableVector3D(x2, y2, z2); - - double expected = FastMath.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) + (z1 - z2) * (z1 - z2)); - - double actual = v2.distanceTo(v1); - - // Tests {@linkplain MutableVector3D#distanceTo(MutableVector3D)} - assertEquals(expected, actual, TOLERANCE); - - Vector3D v3 = new Vector3D(x1, y1, z1); - actual = v2.distanceTo(v3); - - // Tests {@linkplain MutableVector3D#distanceTo(Vector3D)} - assertEquals(expected, actual, TOLERANCE); - - } - } - - /** - * Tests {@linkplain MutableVector3D#dot(Vector3D)} - */ - @Test - @UnitTestMethod(name = "dot", args = { Vector3D.class }) - public void testDot_Vector3D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5394780683515263533L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - MutableVector3D v1 = new MutableVector3D(x1, y1, z1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - Vector3D v2 = new Vector3D(x2, y2, z2); - - double expected = x1 * x2 + y1 * y2 + z1 * z2; - - double actual = v1.dot(v2); - - assertEquals(expected, actual, TOLERANCE); - } - } - - /** - * Tests {@linkplain MutableVector3D#dot(MutableVector3D)} - */ - @Test - @UnitTestMethod(name = "dot", args = { MutableVector3D.class }) - public void testDot_MutableVector3D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8651152995066569737L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v1 = new MutableVector3D(x1, y1, z1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v2 = new MutableVector3D(x2, y2, z2); - - double expected = x1 * x2 + y1 * y2 + z1 * z2; - - double actual = v2.dot(v1); - - assertEquals(expected, actual, TOLERANCE); - - } - } - - /** - * Tests {@linkplain MutableVector3D#zero()} - */ - @Test - @UnitTestMethod(name = "zero", args = {}) - public void testZero() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2385233064662346292L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v = new MutableVector3D(x, y, z); - - assertEquals(x, v.getX(), 0); - assertEquals(y, v.getY(), 0); - assertEquals(z, v.getZ(), 0); - - v.zero(); - - assertEquals(0, v.getX(), 0); - assertEquals(0, v.getY(), 0); - assertEquals(0, v.getZ(), 0); - - } - } - - /** - * Tests {@linkplain MutableVector3D#get(int)} - */ - @Test - @UnitTestMethod(name = "get", args = { int.class }) - public void testGet() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3920072590253396227L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v = new MutableVector3D(x, y, z); - - assertEquals(x, v.get(0), 0); - assertEquals(y, v.get(1), 0); - assertEquals(z, v.get(2), 0); - - } - } - - /** - * Tests {@linkplain MutableVector3D#getX()} - */ - @Test - @UnitTestMethod(name = "getX", args = {}) - public void testGetX() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2879010938976412896L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v = new MutableVector3D(x, y, z); - - assertEquals(x, v.getX(), 0); - } - } - - /** - * Tests {@linkplain MutableVector3D#getY()} - */ - @Test - @UnitTestMethod(name = "getY", args = {}) - public void testGetY() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7608232687910162616L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v = new MutableVector3D(x, y, z); - - assertEquals(y, v.getY(), 0); - } - } - - /** - * Tests {@linkplain MutableVector3D#getZ()} - */ - @Test - @UnitTestMethod(name = "getZ", args = {}) - public void testGetZ() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1724897021905892388L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v = new MutableVector3D(x, y, z); - - assertEquals(z, v.getZ(), 0); - } - } - - /** - * Tests {@linkplain MutableVector3D#scale(double)} - */ - @Test - @UnitTestMethod(name = "scale", args = { double.class }) - public void testScale() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(306065735500287558L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v = new MutableVector3D(x, y, z); - double scalar = randomGenerator.nextDouble() * 1000 - 500; - - v.scale(scalar); - - assertEquals(x * scalar, v.getX(), 0); - assertEquals(y * scalar, v.getY(), 0); - assertEquals(z * scalar, v.getZ(), 0); - } - } - - /** - * Tests {@linkplain MutableVector3D#setX(double)} - */ - @Test - @UnitTestMethod(name = "setX", args = { double.class }) - public void testSetX() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5685640957927601005L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v = new MutableVector3D(x, y, z); - - double newX = randomGenerator.nextDouble() * 1000 - 500; - - v.setX(newX); - - assertEquals(newX, v.getX(), 0); - assertEquals(y, v.getY(), 0); - assertEquals(z, v.getZ(), 0); - } - } - - /** - * Tests {@linkplain MutableVector3D#setY(double)} - */ - @Test - @UnitTestMethod(name = "setY", args = { double.class }) - public void testSetY() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(9071265328505700087L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v = new MutableVector3D(x, y, z); - - double newY = randomGenerator.nextDouble() * 1000 - 500; - - v.setY(newY); - - assertEquals(x, v.getX(), 0); - assertEquals(newY, v.getY(), 0); - assertEquals(z, v.getZ(), 0); - - } - } - - /** - * Tests {@linkplain MutableVector3D#setZ(double)} - */ - @Test - @UnitTestMethod(name = "setZ", args = { double.class }) - public void testSetZ() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4186928576633187463L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v = new MutableVector3D(x, y, z); - - double newZ = randomGenerator.nextDouble() * 1000 - 500; - - v.setZ(newZ); - - assertEquals(x, v.getX(), 0); - assertEquals(y, v.getY(), 0); - assertEquals(newZ, v.getZ(), 0); - - } - } - - /** - * Tests {@linkplain MutableVector3D#sub(MutableVector3D)} - */ - @Test - @UnitTestMethod(name = "sub", args = { MutableVector3D.class }) - public void testSub_MutableVector3D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2474685339056144518L); - - for (int i = 0; i < 100; i++) { - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - MutableVector3D v1 = new MutableVector3D(x1, y1, z1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v2 = new MutableVector3D(x2, y2, z2); - - v1.sub(v2); - - // Tests {@linkplain MutableVector3D#sub(MutableVector3D)} - assertEquals(x1 - x2, v1.getX(), 0); - assertEquals(y1 - y2, v1.getY(), 0); - assertEquals(z1 - z2, v1.getZ(), 0); - } - } - - /** - * Tests {@linkplain MutableVector3D#sub(Vector3D)} - */ - @Test - @UnitTestMethod(name = "sub", args = { Vector3D.class }) - public void testSub_Vector3D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4393443088953985351L); - - for (int i = 0; i < 100; i++) { - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - MutableVector3D v1 = new MutableVector3D(x1, y1, z1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - v1 = new MutableVector3D(x1, y1, z1); - Vector3D v2 = new Vector3D(x2, y2, z2); - v1.sub(v2); - - // Tests {@linkplain MutableVector3D#sub(Vector3D)} - assertEquals(x1 - x2, v1.getX(), 0); - assertEquals(y1 - y2, v1.getY(), 0); - assertEquals(z1 - z2, v1.getZ(), 0); - - } - } - - /** - * Tests {@linkplain MutableVector3D#sub(double, double, double)} - */ - @Test - @UnitTestMethod(name = "sub", args = { double.class, double.class, double.class }) - public void testSub_Doubles() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1956097062031880352L); - - for (int i = 0; i < 100; i++) { - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - MutableVector3D v1 = new MutableVector3D(x1, y1, z1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - v1.sub(x2, y2, z2); - - assertEquals(x1 - x2, v1.getX(), 0); - assertEquals(y1 - y2, v1.getY(), 0); - assertEquals(z1 - z2, v1.getZ(), 0); - } - } - - /** - * Tests {@linkplain MutableVector3D#isInfinite()} - * - */ - @Test - @UnitTestMethod(name = "isInfinite", args = {}) - public void testIsInfinite() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1333539969014219742L); - - for (int i = 0; i < 100; i++) { - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - MutableVector3D v = new MutableVector3D(); - - v.assign(x, y, z); - assertFalse(v.isInfinite()); - v.setX(Double.POSITIVE_INFINITY); - assertTrue(v.isInfinite()); - - v.assign(x, y, z); - assertFalse(v.isInfinite()); - v.setX(Double.NEGATIVE_INFINITY); - assertTrue(v.isInfinite()); - - v.assign(x, y, z); - assertFalse(v.isInfinite()); - v.setY(Double.POSITIVE_INFINITY); - assertTrue(v.isInfinite()); - - v.assign(x, y, z); - assertFalse(v.isInfinite()); - v.setY(Double.NEGATIVE_INFINITY); - assertTrue(v.isInfinite()); - - v.assign(x, y, z); - assertFalse(v.isInfinite()); - v.setZ(Double.POSITIVE_INFINITY); - assertTrue(v.isInfinite()); - - v.assign(x, y, z); - assertFalse(v.isInfinite()); - v.setZ(Double.NEGATIVE_INFINITY); - assertTrue(v.isInfinite()); - - } - } - - /** - * Tests {@linkplain MutableVector3D#isNaN()} - * - */ - @Test - @UnitTestMethod(name = "isNaN", args = {}) - public void testIsNaN() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3604098144852879234L); - - for (int i = 0; i < 100; i++) { - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - MutableVector3D v = new MutableVector3D(); - - v.assign(x, y, z); - assertFalse(v.isNaN()); - v.setX(Double.NaN); - assertTrue(v.isNaN()); - - v.assign(x, y, z); - assertFalse(v.isNaN()); - v.setY(Double.NaN); - assertTrue(v.isNaN()); - - v.assign(x, y, z); - assertFalse(v.isNaN()); - v.setZ(Double.NaN); - assertTrue(v.isNaN()); - - } - } - - /** - * Tests {@linkplain MutableVector3D#isFinite()} - * - */ - @Test - @UnitTestMethod(name = "isFinite", args = {}) - public void testIsFinite() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2810214958316938556L); - - for (int i = 0; i < 100; i++) { - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - MutableVector3D v = new MutableVector3D(); - - v.assign(x, y, z); - assertTrue(v.isFinite()); - v.setX(Double.NaN); - assertFalse(v.isFinite()); - - v.assign(x, y, z); - assertTrue(v.isFinite()); - v.setX(Double.NEGATIVE_INFINITY); - assertFalse(v.isFinite()); - - v.assign(x, y, z); - assertTrue(v.isFinite()); - v.setX(Double.POSITIVE_INFINITY); - assertFalse(v.isFinite()); - - v.assign(x, y, z); - assertTrue(v.isFinite()); - v.setY(Double.NaN); - assertFalse(v.isFinite()); - - v.assign(x, y, z); - assertTrue(v.isFinite()); - v.setY(Double.NEGATIVE_INFINITY); - assertFalse(v.isFinite()); - - v.assign(x, y, z); - assertTrue(v.isFinite()); - v.setY(Double.POSITIVE_INFINITY); - assertFalse(v.isFinite()); - - v.assign(x, y, z); - assertTrue(v.isFinite()); - v.setZ(Double.NaN); - assertFalse(v.isFinite()); - - v.assign(x, y, z); - assertTrue(v.isFinite()); - v.setZ(Double.NEGATIVE_INFINITY); - assertFalse(v.isFinite()); - - v.assign(x, y, z); - assertTrue(v.isFinite()); - v.setZ(Double.POSITIVE_INFINITY); - assertFalse(v.isFinite()); - - } - } - - /** - * Tests {@linkplain MutableVector3D#squareDistanceTo(MutableVector3D)} - */ - @Test - @UnitTestMethod(name = "squareDistanceTo", args = { MutableVector3D.class }) - public void testSquareDistanceTo_MutableVector3D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8371430877204504151L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v1 = new MutableVector3D(x1, y1, z1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v2 = new MutableVector3D(x2, y2, z2); - - double expected = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) + (z1 - z2) * (z1 - z2); - - double actual = v2.squareDistanceTo(v1); - - // Tests {@linkplain - // MutableVector3D#squareDistanceTo(MutableVector3D)} - assertEquals(expected, actual, 0); - - } - } - - /** - * Tests {@linkplain MutableVector3D#squareDistanceTo(Vector3D)} - */ - @Test - @UnitTestMethod(name = "squareDistanceTo", args = { Vector3D.class }) - public void testSquareDistanceTo() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(9067451765919254892L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v1 = new MutableVector3D(x1, y1, z1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v2 = new MutableVector3D(x2, y2, z2); - - double expected = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) + (z1 - z2) * (z1 - z2); - - Vector3D v3 = new Vector3D(v2); - double actual = v1.squareDistanceTo(v3); - - // Tests {@linkplain MutableVector3D#squareDistanceTo(Vector3D)} - assertEquals(expected, actual, 0); - } - } - - /** - * Tests {@linkplain MutableVector3D#reverse()} - */ - @Test - @UnitTestMethod(name = "reverse", args = {}) - public void testReverse() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(423952531786020950L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v = new MutableVector3D(x, y, z); - v.reverse(); - - assertEquals(-x, v.getX(), 0); - assertEquals(-y, v.getY(), 0); - assertEquals(-z, v.getZ(), 0); - } - } - - /** - * Tests {@linkplain MutableVector3D#length()} - */ - @Test - @UnitTestMethod(name = "length", args = {}) - public void testLength() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7468957433345943292L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v = new MutableVector3D(x, y, z); - double expectedLength = FastMath.sqrt(x * x + y * y + z * z); - double actualLength = v.length(); - - assertEquals(expectedLength, actualLength, TOLERANCE); - } - } - - /** - * Tests {@linkplain MutableVector3D#squareLength()} - */ - @Test - @UnitTestMethod(name = "squareLength", args = {}) - public void testSquareLength() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4345370822530975467L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v = new MutableVector3D(x, y, z); - double expectedLength = x * x + y * y + z * z; - double actualLength = v.squareLength(); - - assertEquals(expectedLength, actualLength, 0); - } - } - - /** - * Tests {@linkplain MutableVector3D#toArray()} - */ - @Test - @UnitTestMethod(name = "toArray", args = {}) - public void testToArray() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2523438676987919308L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v = new MutableVector3D(x, y, z); - double[] array = v.toArray(); - - assertEquals(v.getX(), array[0], 0); - assertEquals(v.getY(), array[1], 0); - assertEquals(v.getZ(), array[2], 0); - } - } - - /** - * Tests {@linkplain MutableVector3D#normalize()} - */ - @Test - @UnitTestMethod(name = "normalize", args = {}) - public void testNormalize() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7788343538322017822L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v = new MutableVector3D(x, y, z); - v.normalize(); - - assertEquals(1, v.length(), TOLERANCE); - - } - } - - /** - * Tests {@linkplain MutableVector3D#isNormal()} - */ - @Test - @UnitTestMethod(name = "isNormal", args = {}) - public void testIsNormal() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5620260804759666952L); - - int activeTestCount = 0; - for (int i = 0; i < 100; i++) { - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v = new MutableVector3D(x, y, z); - - if (FastMath.abs(v.length() - 1) > MutableVector3D.NORMAL_LENGTH_TOLERANCE) { - v.normalize(); - assertTrue(v.isNormal()); - activeTestCount++; - - MutableVector3D u = new MutableVector3D(v); - u.scale(1 - 2 * MutableVector3D.NORMAL_LENGTH_TOLERANCE); - assertFalse(u.isNormal()); - - u = new MutableVector3D(v); - u.scale(1 + 2 * MutableVector3D.NORMAL_LENGTH_TOLERANCE); - assertFalse(u.isNormal()); - } - } - assertTrue(activeTestCount > 90); - } - - /** - * Tests {@linkplain MutableVector3D#isPerpendicularTo(MutableVector3D)} - */ - @Test - @UnitTestMethod(name = "isPerpendicularTo", args = { MutableVector3D.class }) - public void testIsPerpendicularTo_MutableVector3D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3973464373436914741L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v1 = new MutableVector3D(x1, y1, z1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v2 = new MutableVector3D(x2, y2, z2); - - MutableVector3D v3 = new MutableVector3D(v1); - v3.rotateToward(v2, FastMath.toRadians(90)); - - assertTrue(v1.isPerpendicularTo(v3)); - - v3 = new MutableVector3D(v1); - v3.rotateToward(v2, FastMath.PI / 2 - 2 * MutableVector3D.PERPENDICUALR_ANGLE_TOLERANCE); - assertFalse(v1.isPerpendicularTo(v3)); - - v3 = new MutableVector3D(v1); - v3.rotateToward(v2, FastMath.PI / 2 + 2 * MutableVector3D.PERPENDICUALR_ANGLE_TOLERANCE); - assertFalse(v1.isPerpendicularTo(v3)); - } - - } - - /** - * Tests {@linkplain MutableVector3D#isPerpendicularTo(Vector3D)} - */ - @Test - @UnitTestMethod(name = "isPerpendicularTo", args = { Vector3D.class }) - public void testIsPerpendicularTo_Vector3D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3455017692650645966L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v1 = new MutableVector3D(x1, y1, z1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v2 = new MutableVector3D(x2, y2, z2); - - Vector3D v3 = new Vector3D(v1); - v3 = v3.rotateToward(new Vector3D(v2), FastMath.toRadians(90)); - - assertTrue(v1.isPerpendicularTo(v3)); - - v3 = new Vector3D(v1); - v3 = v3.rotateToward(new Vector3D(v2), FastMath.PI / 2 - 2 * MutableVector3D.PERPENDICUALR_ANGLE_TOLERANCE); - assertFalse(v1.isPerpendicularTo(v3)); - - v3 = new Vector3D(v1); - v3 = v3.rotateToward(new Vector3D(v2), FastMath.PI / 2 + 2 * MutableVector3D.PERPENDICUALR_ANGLE_TOLERANCE); - assertFalse(v1.isPerpendicularTo(v3)); - } - - } - - /** - * Tests {@linkplain MutableVector3D#equals(Object)} - */ - @Test - @UnitTestMethod(name = "equals", args = { Object.class }) - public void testEquals() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3526185731258727774L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v1 = new MutableVector3D(x, y, z); - - MutableVector3D v2 = new MutableVector3D(x, y, z); - - MutableVector3D v3 = new MutableVector3D(x, y, z); - - // reflexive - assertEquals(v1, v1); - - // symetric - assertEquals(v1, v2); - assertEquals(v2, v1); - - // transitive - assertEquals(v1, v2); - assertEquals(v2, v3); - assertEquals(v3, v1); - - } - } - - /** - * Tests {@linkplain MutableVector3D#hashCode()} - */ - @Test - @UnitTestMethod(name = "hashCode", args = {}) - public void testHashCode() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8254478139726956208L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v1 = new MutableVector3D(x, y, z); - - MutableVector3D v2 = new MutableVector3D(x, y, z); - - assertEquals(v1.hashCode(), v2.hashCode()); - - } - } - - /** - * Tests {@linkplain MutableVector3D#toString()} - */ - @Test - @UnitTestMethod(name = "toString", args = {}) - public void testToString() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3891477282762024709L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v = new MutableVector3D(x, y, z); - - String expected = "Vector3D [x=" + x + ", y=" + y + ", z=" + z + "]"; - - String actual = v.toString(); - - assertEquals(expected, actual); - } - } - - /** - * Tests {@linkplain MutableVector3D#rotateAbout(MutableVector3D, double)} - */ - @Test - @UnitTestMethod(name = "rotateAbout", args = { MutableVector3D.class, double.class }) - public void testRotateAbout_MutableVector3D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3529177607589452768L); - for (int i = 0; i < 100; i++) { - - // v1 will be used as a rotator, so we ensure that it has a - // reasonable length - MutableVector3D v1 = new MutableVector3D(); - while (v1.length() < 0.0000001) { - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - v1 = new MutableVector3D(x1, y1, z1); - } - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v2 = new MutableVector3D(x2, y2, z2); - - double theta = randomGenerator.nextDouble() * 2 * FastMath.PI; - - MutableVector3D v = new MutableVector3D(v2); - - v.rotateAbout(v1, theta); - - // v2 under rotation should have its length preserved - assertEquals(v2.length(), v.length(), TOLERANCE); - - // v2 under rotation should have its angle to v1 preserved - assertEquals(v2.angle(v1), v.angle(v1), TOLERANCE); - - // v2 when rotated back should return to its original position - v.rotateAbout(v1, -theta); - assertEquals(v2.getX(), v.getX(), TOLERANCE); - assertEquals(v2.getY(), v.getY(), TOLERANCE); - assertEquals(v2.getZ(), v.getZ(), TOLERANCE); - - } - - } - - /** - * Tests {@linkplain MutableVector3D#rotateAbout(Vector3D, double)} - */ - @Test - @UnitTestMethod(name = "rotateAbout", args = { Vector3D.class, double.class }) - public void testRotateAbout_Vector3D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1368205874815878591L); - for (int i = 0; i < 100; i++) { - - // v1 will be used as a rotator, so we ensure that it has a - // reasonable length - Vector3D v1 = new Vector3D(); - while (v1.length() < 0.0000001) { - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - v1 = new Vector3D(x1, y1, z1); - } - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v2 = new MutableVector3D(x2, y2, z2); - - double theta = randomGenerator.nextDouble() * 2 * FastMath.PI; - - MutableVector3D v = new MutableVector3D(v2); - - v.rotateAbout(v1, theta); - - // v2 under rotation should have its length preserved - assertEquals(v2.length(), v.length(), TOLERANCE); - - // v2 under rotation should have its angle to v1 preserved - assertEquals(v2.angle(v1), v.angle(v1), TOLERANCE); - - // v2 when rotated back should return to its original position - v.rotateAbout(v1, -theta); - assertEquals(v2.getX(), v.getX(), TOLERANCE); - assertEquals(v2.getY(), v.getY(), TOLERANCE); - assertEquals(v2.getZ(), v.getZ(), TOLERANCE); - - } - } - - /** - * Tests {@linkplain MutableVector3D#rotateToward(Vector3D, double)} - */ - @Test - @UnitTestMethod(name = "rotateToward", args = { Vector3D.class, double.class }) - public void testRotateToward() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5587152698910126450L); - - for (int i = 0; i < 100; i++) { - - // v1 will be used as a rotator, so we ensure that it has a - // reasonable length - Vector3D v1 = new Vector3D(); - while (v1.length() < 0.0000001) { - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - v1 = new Vector3D(x1, y1, z1); - } - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v2 = new MutableVector3D(x2, y2, z2); - - double theta = randomGenerator.nextDouble() * 2 * FastMath.PI; - - MutableVector3D v = new MutableVector3D(v2); - - v.rotateToward(v1, theta); - double angle = v2.angle(v1); - - // v2 under rotation should have its length preserved - assertEquals(v2.length(), v.length(), TOLERANCE); - - // v2 under rotation should have its angle to v1 changed by theta - double expectedAngle = theta - angle; - while (expectedAngle < 0) { - expectedAngle += FastMath.PI * 2; - } - while (expectedAngle > 2 * FastMath.PI) { - expectedAngle -= 2 * FastMath.PI; - } - while (expectedAngle > FastMath.PI) { - expectedAngle = 2 * FastMath.PI - expectedAngle; - } - - double actualAngle = v.angle(v1); - - assertEquals(expectedAngle, actualAngle, TOLERANCE); - - // v2 when rotated back should return to its original position - MutableVector3D a = new MutableVector3D(v2); - a.cross(v1); - - MutableVector3D b = new MutableVector3D(v); - b.cross(v1); - - if (a.dot(b) > 0) { - v.rotateToward(v1, -theta); - } else { - v.rotateToward(v1, theta); - } - - assertEquals(v2.getX(), v.getX(), TOLERANCE); - assertEquals(v2.getY(), v.getY(), TOLERANCE); - assertEquals(v2.getZ(), v.getZ(), TOLERANCE); - - } - } - - /** - * Tests {@linkplain MutableVector3D#rotateToward(MutableVector3D, double)} - */ - @Test - @UnitTestMethod(name = "rotateToward", args = { MutableVector3D.class, double.class }) - public void testRotateToward_MutableVector3D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6289030470744653680L); - - for (int i = 0; i < 100; i++) { - - // v1 will be used as a rotator, so we ensure that it has a - // reasonable length - MutableVector3D v1 = new MutableVector3D(); - while (v1.length() < 0.0000001) { - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - v1 = new MutableVector3D(x1, y1, z1); - } - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v2 = new MutableVector3D(x2, y2, z2); - - double theta = randomGenerator.nextDouble() * 2 * FastMath.PI; - - MutableVector3D v = new MutableVector3D(v2); - - v.rotateToward(v1, theta); - double angle = v1.angle(v2); - - // v2 under rotation should have its length preserved - assertEquals(v2.length(), v.length(), TOLERANCE); - - // v2 under rotation should have its angle to v1 changed by theta - double expectedAngle = theta - angle; - while (expectedAngle < 0) { - expectedAngle += FastMath.PI * 2; - } - while (expectedAngle > 2 * FastMath.PI) { - expectedAngle -= 2 * FastMath.PI; - } - while (expectedAngle > FastMath.PI) { - expectedAngle = 2 * FastMath.PI - expectedAngle; - } - - double actualAngle = v.angle(v1); - - assertEquals(expectedAngle, actualAngle, TOLERANCE); - - // v2 when rotated back should return to its original position - MutableVector3D a = new MutableVector3D(v2); - a.cross(v1); - - MutableVector3D b = new MutableVector3D(v); - b.cross(v1); - - if (a.dot(b) > 0) { - v.rotateToward(v1, -theta); - } else { - v.rotateToward(v1, theta); - } - - assertEquals(v2.getX(), v.getX(), TOLERANCE); - assertEquals(v2.getY(), v.getY(), TOLERANCE); - assertEquals(v2.getZ(), v.getZ(), TOLERANCE); - - } - - } - -} diff --git a/gcm3/src/test/java/util/vector/AT_Vector2D.java b/gcm3/src/test/java/util/vector/AT_Vector2D.java deleted file mode 100644 index 92ac4da6c..000000000 --- a/gcm3/src/test/java/util/vector/AT_Vector2D.java +++ /dev/null @@ -1,1008 +0,0 @@ -package util.vector; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.apache.commons.math3.random.RandomGenerator; -import org.apache.commons.math3.util.FastMath; -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.random.RandomGeneratorProvider; -import util.spherical.Chirality; - -/** - * Test class for {@link Vector2D} - * - * @author Shawn Hatch - * - */ -@UnitTest(target = Vector2D.class) -public class AT_Vector2D { - - private static final double TOLERANCE = 0.000001; - - /** - * Tests {@linkplain Vector2D#add(Vector2D)} - */ - @Test - @UnitTestMethod(name = "add", args = { Vector2D.class }) - public void testAdd_Vector2D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7048654979720366561L); - - for (int i = 0; i < 100; i++) { - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v1 = new Vector2D(x1, y1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v2 = new Vector2D(x2, y2); - - Vector2D v3 = v1.add(v2); - - assertEquals(x1 + x2, v3.getX(), 0); - assertEquals(y1 + y2, v3.getY(), 0); - } - } - - /** - * Tests {@linkplain Vector2D#add(double, double)} - * - * Tests {@linkplain Vector2D#add(Vector2D)} - */ - @Test - @UnitTestMethod(name = "add", args = { double.class, double.class }) - public void testAdd_Doubles() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7043233391431344104L); - - for (int i = 0; i < 100; i++) { - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v1 = new Vector2D(x1, y1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v2 = v1.add(x2, y2); - - assertEquals(x1 + x2, v2.getX(), 0); - assertEquals(y1 + y2, v2.getY(), 0); - } - - } - - /** - * Tests {@linkplain Vector2D#Vector2D()} - */ - @Test - @UnitTestConstructor(args = {}) - public void testConstructors_Empty() { - Vector2D v = new Vector2D(); - assertEquals(0, v.getX(), 0); - assertEquals(0, v.getY(), 0); - } - - /** - * Tests {@linkplain Vector2D#Vector2D(MutableVector2D)} - */ - @Test - @UnitTestConstructor(args = { MutableVector2D.class }) - public void testConstructors_MutableVector2D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7256970997578026212L); - - for (int i = 0; i < 100; i++) { - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - MutableVector2D v1 = new MutableVector2D(x, y); - - Vector2D v2 = new Vector2D(v1); - - assertEquals(v2.getX(), x, 0); - assertEquals(v2.getY(), y, 0); - } - - } - - /** - * Tests {@linkplain Vector2D#Vector2D(Vector2D)} - */ - @Test - @UnitTestConstructor(args = { Vector2D.class }) - public void testConstructors_Vector2D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2499300329261423411L); - - for (int i = 0; i < 100; i++) { - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - Vector2D v1 = new Vector2D(x, y); - - Vector2D v2 = new Vector2D(v1); - - assertEquals(v2.getX(), x, 0); - assertEquals(v2.getY(), y, 0); - } - - } - - /** - * Tests {@linkplain Vector2D#Vector2D(double, double)} - */ - @Test - @UnitTestConstructor(args = { double.class, double.class }) - public void testConstructors_Doubles() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6200717978363268201L); - - for (int i = 0; i < 100; i++) { - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - Vector2D v1 = new Vector2D(x, y); - - assertEquals(v1.getX(), x, 0); - assertEquals(v1.getY(), y, 0); - } - } - - /** - * - * Tests {@linkplain Vector2D#addScaled(Vector2D, double)} - * - */ - @Test - @UnitTestMethod(name = "addScaled", args = { Vector2D.class, double.class }) - public void testAddScaled() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5477630435621881354L); - - for (int i = 0; i < 100; i++) { - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - Vector2D v1 = new Vector2D(x1, y1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v2 = new Vector2D(x2, y2); - - double scale = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v3 = v1.addScaled(v2, scale); - - assertEquals(x1 + x2 * scale, v3.getX(), 0); - assertEquals(y1 + y2 * scale, v3.getY(), 0); - } - - } - - /** - * - * Tests {@linkplain Vector2D#angle(Vector2D)} - * - */ - @Test - @UnitTestMethod(name = "angle", args = { Vector2D.class }) - public void testAngle() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5443394431789515021L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v1 = new Vector2D(x1, y1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v2 = new Vector2D(x2, y2); - - double length1 = FastMath.sqrt(x1 * x1 + y1 * y1); - double length2 = FastMath.sqrt(x2 * x2 + y2 * y2); - double dotProduct = x1 * x2 + y1 * y2; - double cosTheta = dotProduct / (length1 * length2); - double expectedValue = FastMath.acos(cosTheta); - - double actualValue = v1.angle(v2); - - assertEquals(expectedValue, actualValue, TOLERANCE); - - } - } - - /** - * Tests {@linkplain Vector2D#cross(Vector2D)} - */ - @Test - @UnitTestMethod(name = "cross", args = { Vector2D.class }) - public void testCross() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2072134709060069627L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v1 = new Vector2D(x1, y1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v2 = new Vector2D(x2, y2); - int actual = v2.cross(v1); - - double zComponentOf3DCrossProduct = (x2 * y1) - (x1 * y2); - int expected; - if (zComponentOf3DCrossProduct < 0) { - expected = -1; - } else { - expected = 1; - } - assertEquals(expected, actual); - - } - } - - /** - * Tests {@linkplain Vector2D#distanceTo(Vector2D)} - */ - @Test - @UnitTestMethod(name = "distanceTo", args = { Vector2D.class }) - public void testDistanceTo() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8108866891186001273L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v1 = new Vector2D(x1, y1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v2 = new Vector2D(x2, y2); - - double expected = FastMath.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); - - double actual = v2.distanceTo(v1); - - assertEquals(expected, actual, TOLERANCE); - - } - - } - - /** - * Tests {@linkplain Vector2D#dot(Vector2D))} - */ - @Test - @UnitTestMethod(name = "dot", args = { Vector2D.class }) - public void testDot() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5455897510253329686L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v1 = new Vector2D(x1, y1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v2 = new Vector2D(x2, y2); - - double expected = x1 * x2 + y1 * y2; - - double actual = v2.dot(v1); - - assertEquals(expected, actual, TOLERANCE); - - } - } - - /** - * Tests {@linkplain Vector2D#get(int)} - */ - @Test - @UnitTestMethod(name = "get", args = { int.class }) - public void testGet() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1280663175219265421L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v = new Vector2D(x, y); - - assertEquals(x, v.get(0), 0); - assertEquals(y, v.get(1), 0); - - } - } - - /** - * Tests {@linkplain Vector2D#getX()} - */ - @Test - @UnitTestMethod(name = "getX", args = {}) - public void testGetX() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5972054182695362046L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v = new Vector2D(x, y); - - assertEquals(x, v.getX(), 0); - } - } - - /** - * Tests {@linkplain Vector2D#getY()} - */ - @Test - @UnitTestMethod(name = "getY", args = {}) - public void testGetY() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7269151178334818715L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v = new Vector2D(x, y); - - assertEquals(y, v.getY(), 0); - } - } - - /** - * Tests {@linkplain Vector2D#scale(double)} - */ - @Test - @UnitTestMethod(name = "scale", args = { double.class }) - public void testScale() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6482923353153373662L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v = new Vector2D(x, y); - double scalar = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v1 = v.scale(scalar); - - assertEquals(x * scalar, v1.getX(), 0); - assertEquals(y * scalar, v1.getY(), 0); - } - } - - /** - * Tests {@linkplain Vector2D#sub(Vector2D)} - */ - @Test - @UnitTestMethod(name = "sub", args = { Vector2D.class }) - public void testSub_Vector2D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2615726850696406829L); - - for (int i = 0; i < 100; i++) { - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - Vector2D v1 = new Vector2D(x1, y1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v2 = new Vector2D(x2, y2); - - v1 = v1.sub(v2); - - assertEquals(x1 - x2, v1.getX(), 0); - assertEquals(y1 - y2, v1.getY(), 0); - } - - } - - /** - * Tests {@linkplain Vector2D#sub(double, double)} - */ - @Test - @UnitTestMethod(name = "sub", args = { double.class, double.class }) - public void testSub_Doubles() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(594158034033440503L); - - for (int i = 0; i < 100; i++) { - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - Vector2D v1 = new Vector2D(x1, y1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - - v1 = new Vector2D(x1, y1); - v1 = v1.sub(x2, y2); - - assertEquals(x1 - x2, v1.getX(), 0); - assertEquals(y1 - y2, v1.getY(), 0); - } - } - - /** - * Tests {@linkplain Vector2D#isInfinite()} - * - */ - @Test - @UnitTestMethod(name = "isInfinite", args = {}) - public void testIsInfinite() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2322202277366389607L); - - for (int i = 0; i < 100; i++) { - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v = new Vector2D(x, y); - assertFalse(v.isInfinite()); - - v = new Vector2D(Double.POSITIVE_INFINITY, y); - assertTrue(v.isInfinite()); - - v = new Vector2D(x, Double.POSITIVE_INFINITY); - assertTrue(v.isInfinite()); - - v = new Vector2D(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); - assertTrue(v.isInfinite()); - - v = new Vector2D(Double.NEGATIVE_INFINITY, y); - assertTrue(v.isInfinite()); - - v = new Vector2D(x, Double.NEGATIVE_INFINITY); - assertTrue(v.isInfinite()); - - v = new Vector2D(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY); - assertTrue(v.isInfinite()); - } - } - - /** - * Tests {@linkplain Vector2D#isNaN()} - * - */ - @Test - @UnitTestMethod(name = "isNaN", args = {}) - public void testIsNaN() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4021866300032499888L); - - for (int i = 0; i < 100; i++) { - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v = new Vector2D(x, y); - assertFalse(v.isNaN()); - - v = new Vector2D(x, Double.NaN); - assertTrue(v.isNaN()); - - v = new Vector2D(Double.NaN, y); - assertTrue(v.isNaN()); - - v = new Vector2D(Double.NaN, Double.NaN); - assertTrue(v.isNaN()); - - } - } - - /** - * Tests {@linkplain Vector2D#isFinite()} - * - */ - @Test - @UnitTestMethod(name = "isFinite", args = {}) - public void testIsFinite() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2864965851085774662L); - - for (int i = 0; i < 100; i++) { - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - Vector2D v = new Vector2D(x, y); - assertTrue(v.isFinite()); - - v = new Vector2D(Double.NaN, y); - assertFalse(v.isFinite()); - - v = new Vector2D(Double.POSITIVE_INFINITY, y); - assertFalse(v.isFinite()); - - v = new Vector2D(Double.NEGATIVE_INFINITY, y); - assertFalse(v.isFinite()); - - v = new Vector2D(x, Double.NaN); - assertFalse(v.isFinite()); - - v = new Vector2D(x, Double.POSITIVE_INFINITY); - assertFalse(v.isFinite()); - - v = new Vector2D(x, Double.NEGATIVE_INFINITY); - assertFalse(v.isFinite()); - - v = new Vector2D(Double.NaN, Double.NaN); - assertFalse(v.isFinite()); - - v = new Vector2D(Double.NaN, Double.NEGATIVE_INFINITY); - assertFalse(v.isFinite()); - - v = new Vector2D(Double.NaN, Double.POSITIVE_INFINITY); - assertFalse(v.isFinite()); - - v = new Vector2D(Double.NEGATIVE_INFINITY, Double.NaN); - assertFalse(v.isFinite()); - - v = new Vector2D(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY); - assertFalse(v.isFinite()); - - v = new Vector2D(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY); - assertFalse(v.isFinite()); - - v = new Vector2D(Double.POSITIVE_INFINITY, Double.NaN); - assertFalse(v.isFinite()); - - v = new Vector2D(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY); - assertFalse(v.isFinite()); - - v = new Vector2D(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); - assertFalse(v.isFinite()); - - } - } - - /** - * Tests {@linkplain Vector2D#squareDistanceTo(Vector2D)} - */ - @Test - @UnitTestMethod(name = "squareDistanceTo", args = { Vector2D.class }) - public void testSquareDistanceTo() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(972530617533616033L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v1 = new Vector2D(x1, y1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v2 = new Vector2D(x2, y2); - - double expected = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2); - - double actual = v2.squareDistanceTo(v1); - - assertEquals(expected, actual, 0); - } - } - - /** - * Tests {@linkplain Vector2D#reverse()} - */ - @Test - @UnitTestMethod(name = "reverse", args = {}) - public void testReverse() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2281430038941666208L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v = new Vector2D(x, y); - Vector2D v1 = v.reverse(); - - assertEquals(-x, v1.getX(), 0); - assertEquals(-y, v1.getY(), 0); - } - } - - /** - * Tests {@linkplain Vector2D#length()} - */ - @Test - @UnitTestMethod(name = "length", args = {}) - public void testLength() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4831419001108209090L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v = new Vector2D(x, y); - double expectedLength = FastMath.sqrt(x * x + y * y); - double actualLength = v.length(); - - assertEquals(expectedLength, actualLength, TOLERANCE); - } - } - - /** - * Tests {@linkplain Vector2D#squareLength()} - */ - @Test - @UnitTestMethod(name = "squareLength", args = {}) - public void testSquareLength() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1646469899835942461L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v = new Vector2D(x, y); - double expectedLength = x * x + y * y; - double actualLength = v.squareLength(); - - assertEquals(expectedLength, actualLength, 0); - } - } - - /** - * Tests {@linkplain Vector2D#toArray()} - */ - @Test - @UnitTestMethod(name = "toArray", args = {}) - public void testToArray() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8359854340367149037L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v = new Vector2D(x, y); - double[] array = v.toArray(); - - assertEquals(v.getX(), array[0], 0); - assertEquals(v.getY(), array[1], 0); - } - } - - /** - * Tests {@linkplain Vector2D#normalize()} - */ - @Test - @UnitTestMethod(name = "normalize", args = {}) - public void testNormalize() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2056526750467808604L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v = new Vector2D(x, y); - Vector2D v1 = v.normalize(); - - assertEquals(1, v1.length(), TOLERANCE); - - } - } - - /** - * Tests {@linkplain Vector2D#equals(Object))} - */ - @Test - @UnitTestMethod(name = "equals", args = { Object.class }) - public void testEquals() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1772313867718129269L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v1 = new Vector2D(x, y); - - Vector2D v2 = new Vector2D(x, y); - - Vector2D v3 = new Vector2D(x, y); - - // reflexive - assertEquals(v1, v1); - - // symetric - assertEquals(v1, v2); - assertEquals(v2, v1); - - // transitive - assertEquals(v1, v2); - assertEquals(v2, v3); - assertEquals(v3, v1); - - } - } - - /** - * Tests {@linkplain Vector2D#hashCode()} - */ - @Test - @UnitTestMethod(name = "hashCode", args = {}) - public void testHashCode() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4728150157129004474L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v1 = new Vector2D(x, y); - - Vector2D v2 = new Vector2D(x, y); - - assertEquals(v1.hashCode(), v2.hashCode()); - - } - } - - /** - * Tests {@linkplain Vector2D#toString()} - */ - @Test - @UnitTestMethod(name = "toString", args = {}) - public void testToString() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6250827979384795121L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v = new Vector2D(x, y); - - String expected = "Vector2D [x=" + x + ", y=" + y + "]"; - - String actual = v.toString(); - - assertEquals(expected, actual); - } - } - - /** - * Tests {@linkplain Vector2D#rotate(double)} - * - */ - @Test - @UnitTestMethod(name = "rotate", args = { double.class }) - public void testRotate() { - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3324225447982087438L); - - for (int i = 0; i < 100; i++) { - // ensure that v1 is not too close to a zero vector - Vector2D v1 = new Vector2D(); - while (v1.length() < TOLERANCE) { - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - v1 = new Vector2D(x1, y1); - } - - // Copy v1 and rotate the copy - Vector2D v2 = new Vector2D(v1); - double theta = randomGenerator.nextDouble() * 2 * FastMath.PI; - v2 = v2.rotate(theta); - - // v1 under rotation should have its length preserved - assertEquals(v1.length(), v2.length(), TOLERANCE); - - // v2 and v1 should have theta as their angle - double expectedAngle = theta; - while (expectedAngle < 0) { - expectedAngle += 2 * FastMath.PI; - } - while (expectedAngle > FastMath.PI) { - expectedAngle = 2 * FastMath.PI - expectedAngle; - } - assertEquals(expectedAngle, v2.angle(v1), TOLERANCE); - - // v2 and v1 should be oriented correctly - if (theta < FastMath.PI) { - assertEquals(1, v1.cross(v2)); - } else { - assertEquals(-1, v1.cross(v2)); - } - - // v2 when rotated back should return to its original position - Vector2D v3 = v2.rotate(-theta); - assertEquals(v1.getX(), v3.getX(), TOLERANCE); - assertEquals(v1.getY(), v3.getY(), TOLERANCE); - } - } - - /** - * - * Tests {@linkplain Vector2D#rotateToward(Vector2D, double)} - */ - @Test - @UnitTestMethod(name = "rotateToward", args = { Vector2D.class, double.class }) - public void testRotateToward() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4005002637954997121L); - - for (int i = 0; i < 100; i++) { - - // Ensure that v1 is not too close to the zero vector - Vector2D v1 = new Vector2D(); - while (v1.length() < TOLERANCE) { - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - v1 = new Vector2D(x1, y1); - } - - // Ensure that v2 is not too close to the zero vector - Vector2D v2 = new Vector2D(); - while (v2.length() < TOLERANCE) { - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - v2 = new Vector2D(x2, y2); - } - - double theta = randomGenerator.nextDouble() * 2 * FastMath.PI; - - Vector2D v3 = new Vector2D(v2); - v3.rotateToward(v1, theta); - - // Rotation toward another vector is equivalent to plain rotation - // with a possible sign change due to the relative orientation of - // the two vectors. - Vector2D v4 = new Vector2D(v2); - if (v2.cross(v1) > 0) { - v4.rotate(theta); - } else { - v4.rotate(-theta); - } - - assertEquals(v4.getX(), v3.getX(), TOLERANCE); - assertEquals(v4.getY(), v3.getY(), TOLERANCE); - - } - - } - - /** - * Tests {@linkplain Vector2D#perpendicularRotation(Chirality)} - */ - @Test - @UnitTestMethod(name = "perpendicularRotation", args = { Chirality.class }) - public void testPerpendicularRotation() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4389418071564368339L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - Vector2D v1 = new Vector2D(x1, y1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - Vector2D v2 = new Vector2D(x2, y2); - - v2 = v1.perpendicularRotation(Chirality.LEFT_HANDED); - // v2 should be perpendicular to v1 - assertEquals(FastMath.PI / 2, v2.angle(v1), TOLERANCE); - // v2 is clockwise of v1, so the cross product points up - assertEquals(1, v2.cross(v1)); - - v2 = v1.perpendicularRotation(Chirality.RIGHT_HANDED); - // v2 should be perpendicular to v1 - assertEquals(FastMath.PI / 2, v2.angle(v1), TOLERANCE); - // v2 is clockwise of v1, so the cross product points down - assertEquals(-1, v2.cross(v1)); - - } - - } - - /** - * Tests {@linkplain Vector2D#isNormal()} - */ - @Test - @UnitTestMethod(name = "isNormal", args = {}) - public void testIsNormal() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3582310914345350872L); - - int activeTestCount = 0; - for (int i = 0; i < 100; i++) { - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v = new Vector2D(x, y); - - if (FastMath.abs(v.length() - 1) > MutableVector3D.NORMAL_LENGTH_TOLERANCE) { - v = v.normalize(); - assertTrue(v.isNormal()); - activeTestCount++; - - Vector2D u = v.scale(1 - 2 * MutableVector3D.NORMAL_LENGTH_TOLERANCE); - assertFalse(u.isNormal()); - - u = v.scale(1 + 2 * MutableVector3D.NORMAL_LENGTH_TOLERANCE); - assertFalse(u.isNormal()); - } - } - assertTrue(activeTestCount > 90); - } - - /** - * Tests {@linkplain Vector2D#isPerpendicularTo(Vector2D)} - */ - @Test - @UnitTestMethod(name = "isPerpendicularTo", args = { Vector2D.class }) - public void testIsPerpendicularTo() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8574565110919873045L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v1 = new Vector2D(x1, y1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - - Vector2D v2 = new Vector2D(x2, y2); - - Vector2D v3 = new Vector2D(v1); - v3 = v3.rotateToward(new Vector2D(v2), FastMath.toRadians(90)); - - assertTrue(v1.isPerpendicularTo(v3)); - - v3 = new Vector2D(v1); - v3 = v3.rotateToward(new Vector2D(v2), FastMath.PI / 2 - 2 * MutableVector3D.PERPENDICUALR_ANGLE_TOLERANCE); - assertFalse(v1.isPerpendicularTo(v3)); - - v3 = new Vector2D(v1); - v3 = v3.rotateToward(new Vector2D(v2), FastMath.PI / 2 + 2 * MutableVector3D.PERPENDICUALR_ANGLE_TOLERANCE); - assertFalse(v1.isPerpendicularTo(v3)); - } - } -} diff --git a/gcm3/src/test/java/util/vector/AT_Vector3D.java b/gcm3/src/test/java/util/vector/AT_Vector3D.java deleted file mode 100644 index 54636845e..000000000 --- a/gcm3/src/test/java/util/vector/AT_Vector3D.java +++ /dev/null @@ -1,1087 +0,0 @@ -package util.vector; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.apache.commons.math3.random.RandomGenerator; -import org.apache.commons.math3.util.FastMath; -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; -import util.random.RandomGeneratorProvider; - -/** - * Test class for {@link Vector3D} - * - * @author Shawn Hatch - * - */ -@UnitTest(target = Vector3D.class) -public class AT_Vector3D { - - private static final double TOLERANCE = 0.000001; - - - - /** - * Tests {@linkplain Vector3D#add(Vector3D)} - */ - @Test - @UnitTestMethod(name = "add", args = { Vector3D.class }) - public void testAdd_Vector3D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7836660604999880350L); - - for (int i = 0; i < 100; i++) { - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - Vector3D v1 = new Vector3D(x1, y1, z1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - Vector3D v2 = new Vector3D(x2, y2, z2); - - v1 = v1.add(v2); - - assertEquals(x1 + x2, v1.getX(), 0); - assertEquals(y1 + y2, v1.getY(), 0); - assertEquals(z1 + z2, v1.getZ(), 0); - - } - } - - /** - * Tests {@linkplain Vector3D#add(double, double, double)} - */ - @Test - @UnitTestMethod(name = "add", args = { double.class, double.class, double.class }) - public void testAdd_Doubles() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7125211715849393284L); - - for (int i = 0; i < 100; i++) { - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - Vector3D v1 = new Vector3D(x1, y1, z1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v2 = v1.add(x2, y2, z2); - - // Tests {@linkplain Vector3D#add(double, double, double)} - assertEquals(x1 + x2, v2.getX(), 0); - assertEquals(y1 + y2, v2.getY(), 0); - assertEquals(z1 + z2, v2.getZ(), 0); - } - } - - /** - * Tests {@linkplain Vector3D#Vector3D()} - */ - @Test - @UnitTestConstructor(args = {}) - public void testConstructors_Empty() { - - Vector3D v = new Vector3D(); - - assertEquals(0, v.getX(), 0); - assertEquals(0, v.getY(), 0); - assertEquals(0, v.getZ(), 0); - } - - /** - * Tests {@linkplain Vector3D#Vector3D(MutableVector3D)} - */ - @Test - @UnitTestConstructor(args = { MutableVector3D.class }) - public void testConstructors_MutableVector3D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6902413344220415519L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v = new MutableVector3D(x, y, z); - - Vector3D v2 = new Vector3D(v); - - assertEquals(x, v2.getX(), 0); - assertEquals(y, v2.getY(), 0); - assertEquals(z, v2.getZ(), 0); - - } - } - - /** - * Tests {@linkplain Vector3D#Vector3D(Vector3D)} - */ - @Test - @UnitTestConstructor(args = { Vector3D.class }) - public void testConstructors_Vector3D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5764958408005452265L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v = new Vector3D(x, y, z); - - Vector3D v2 = new Vector3D(v); - - assertEquals(x, v2.getX(), 0); - assertEquals(y, v2.getY(), 0); - assertEquals(z, v2.getZ(), 0); - } - } - - /** - * Tests {@linkplain Vector3D#Vector3D(double, double, double)} - */ - @Test - @UnitTestConstructor(args = { double.class, double.class, double.class }) - public void testConstructors_Doubles() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4063392888561806538L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v = new Vector3D(x, y, z); - - assertEquals(x, v.getX(), 0); - assertEquals(y, v.getY(), 0); - assertEquals(z, v.getZ(), 0); - - } - } - - /** - * Tests {@linkplain Vector3D#addScaled(Vector3D, double)} - * - */ - @Test - @UnitTestMethod(name = "addScaled", args = { Vector3D.class, double.class }) - public void testAddScaled() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3690133693531615503L); - - for (int i = 0; i < 100; i++) { - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - MutableVector3D v1 = new MutableVector3D(x1, y1, z1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v2 = new MutableVector3D(x2, y2, z2); - - double scale = randomGenerator.nextDouble() * 1000 - 500; - - v1.addScaled(v2, scale); - - // Tests {@linkplain Vector3D#addScaled(MutableVector3D, double)} - assertEquals(x1 + x2 * scale, v1.getX(), 0); - assertEquals(y1 + y2 * scale, v1.getY(), 0); - assertEquals(z1 + z2 * scale, v1.getZ(), 0); - - v1.assign(x1, y1, z1); - Vector3D v3 = new Vector3D(x2, y2, z2); - scale = randomGenerator.nextDouble() * 1000 - 500; - v1.addScaled(v3, scale); - - // Tests {@linkplain Vector3D#addScaled(Vector3D, double)} - assertEquals(x1 + x2 * scale, v1.getX(), 0); - assertEquals(y1 + y2 * scale, v1.getY(), 0); - assertEquals(z1 + z2 * scale, v1.getZ(), 0); - } - } - - /** - * - * Tests {@linkplain Vector3D#angle(Vector3D)} - * - */ - @Test - @UnitTestMethod(name = "angle", args = { Vector3D.class }) - public void testAngle() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(951350942348320391L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v1 = new Vector3D(x1, y1, z1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v2 = new Vector3D(x2, y2, z2); - - double length1 = FastMath.sqrt(x1 * x1 + y1 * y1 + z1 * z1); - double length2 = FastMath.sqrt(x2 * x2 + y2 * y2 + z2 * z2); - double dotProduct = x1 * x2 + y1 * y2 + z1 * z2; - double cosTheta = dotProduct / (length1 * length2); - double expectedValue = FastMath.acos(cosTheta); - - double actualValue = v1.angle(v2); - - assertEquals(expectedValue, actualValue, TOLERANCE); - - } - } - - /** - * Tests {@linkplain Vector3D#cross(Vector3D))} - */ - @Test - @UnitTestMethod(name = "cross", args = { Vector3D.class }) - public void testCross() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6840870992153579167L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v1 = new Vector3D(x1, y1, z1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - MutableVector3D v2 = new MutableVector3D(x2, y2, z2); - v2.cross(v1); - - double x3 = y2 * z1 - y1 * z2; - double y3 = x1 * z2 - x2 * z1; - double z3 = x2 * y1 - x1 * y2; - - assertEquals(x3, v2.getX(), TOLERANCE); - assertEquals(y3, v2.getY(), TOLERANCE); - assertEquals(z3, v2.getZ(), TOLERANCE); - } - } - - /** - * Tests {@linkplain Vector3D#distanceTo(Vector3D)} - */ - @Test - @UnitTestMethod(name = "distanceTo", args = { Vector3D.class }) - public void testDistanceTo() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7530267238221574008L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v1 = new Vector3D(x1, y1, z1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v2 = new Vector3D(x2, y2, z2); - - double expected = FastMath.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) + (z1 - z2) * (z1 - z2)); - - double actual = v2.distanceTo(v1); - - // Tests {@linkplain MutableVector3D#distanceTo(MutableVector3D)} - assertEquals(expected, actual, TOLERANCE); - } - } - - /** - * Tests {@linkplain Vector3D#dot(Vector3D)} - */ - @Test - @UnitTestMethod(name = "dot", args = { Vector3D.class }) - public void testDot() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7381648601624753148L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v1 = new Vector3D(x1, y1, z1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v2 = new Vector3D(x2, y2, z2); - - double expected = x1 * x2 + y1 * y2 + z1 * z2; - - double actual = v2.dot(v1); - - assertEquals(expected, actual, TOLERANCE); - } - } - - /** - * Tests {@linkplain Vector3D#get(int)} - */ - @Test - @UnitTestMethod(name = "get", args = { int.class }) - public void testGet() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5822494443076549477L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v = new Vector3D(x, y, z); - - assertEquals(x, v.get(0), 0); - assertEquals(y, v.get(1), 0); - assertEquals(z, v.get(2), 0); - - } - } - - /** - * Tests {@linkplain Vector3D#getX()} - */ - @Test - @UnitTestMethod(name = "getX", args = {}) - public void testGetX() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5619096689466232458L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v = new Vector3D(x, y, z); - - assertEquals(x, v.getX(), 0); - } - } - - /** - * Tests {@linkplain Vector3D#getY()} - */ - @Test - @UnitTestMethod(name = "getY", args = {}) - public void testGetY() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(9011643938864970700L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v = new Vector3D(x, y, z); - - assertEquals(y, v.getY(), 0); - } - } - - /** - * Tests {@linkplain Vector3D#getZ()} - */ - @Test - @UnitTestMethod(name = "getZ", args = {}) - public void testGetZ() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8418814888059666319L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v = new Vector3D(x, y, z); - - assertEquals(z, v.getZ(), 0); - } - } - - /** - * Tests {@linkplain Vector3D#scale(double)} - */ - @Test - @UnitTestMethod(name = "scale", args = { double.class }) - public void testScale() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(788959907719176256L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v = new Vector3D(x, y, z); - double scalar = randomGenerator.nextDouble() * 1000 - 500; - - v = v.scale(scalar); - - assertEquals(x * scalar, v.getX(), 0); - assertEquals(y * scalar, v.getY(), 0); - assertEquals(z * scalar, v.getZ(), 0); - } - } - - /** - * Tests {@linkplain Vector3D#sub(Vector3D)} - */ - @Test - @UnitTestMethod(name = "sub", args = { Vector3D.class }) - public void testSub_Vector3D() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7254476776881600886L); - - for (int i = 0; i < 100; i++) { - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - Vector3D v1 = new Vector3D(x1, y1, z1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v2 = new Vector3D(x2, y2, z2); - - v1 = v1.sub(v2); - - // Tests {@linkplain Vector3D#sub(MutableVector3D)} - assertEquals(x1 - x2, v1.getX(), 0); - assertEquals(y1 - y2, v1.getY(), 0); - assertEquals(z1 - z2, v1.getZ(), 0); - - v1 = new Vector3D(x1, y1, z1); - v1 = v1.sub(x2, y2, z2); - - // Tests {@linkplain Vector3D#sub(double, double, double)} - assertEquals(x1 - x2, v1.getX(), 0); - assertEquals(y1 - y2, v1.getY(), 0); - assertEquals(z1 - z2, v1.getZ(), 0); - - } - } - - /** - * Tests {@linkplain Vector3D#sub(double, double, double)} - */ - @Test - @UnitTestMethod(name = "sub", args = { double.class, double.class, double.class }) - public void testSub_Doubles() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6577953305162290972L); - - for (int i = 0; i < 100; i++) { - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - Vector3D v1 = new Vector3D(x1, y1, z1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v2 = new Vector3D(x2, y2, z2); - - v1 = v1.sub(v2); - - // Tests {@linkplain Vector3D#sub(MutableVector3D)} - assertEquals(x1 - x2, v1.getX(), 0); - assertEquals(y1 - y2, v1.getY(), 0); - assertEquals(z1 - z2, v1.getZ(), 0); - - v1 = new Vector3D(x1, y1, z1); - v1 = v1.sub(x2, y2, z2); - - // Tests {@linkplain Vector3D#sub(double, double, double)} - assertEquals(x1 - x2, v1.getX(), 0); - assertEquals(y1 - y2, v1.getY(), 0); - assertEquals(z1 - z2, v1.getZ(), 0); - - } - } - - /** - * Tests {@linkplain Vector3D#isInfinite()} - * - */ - @Test - @UnitTestMethod(name = "isInfinite", args = {}) - public void testIsInfinite() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7403620333466856112L); - - for (int i = 0; i < 100; i++) { - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v = new Vector3D(x, y, z); - assertFalse(v.isInfinite()); - - v = new Vector3D(Double.POSITIVE_INFINITY, y, z); - assertTrue(v.isInfinite()); - - v = new Vector3D(Double.NEGATIVE_INFINITY, y, z); - assertTrue(v.isInfinite()); - - v = new Vector3D(x, Double.POSITIVE_INFINITY, z); - assertTrue(v.isInfinite()); - - v = new Vector3D(x, Double.NEGATIVE_INFINITY, z); - assertTrue(v.isInfinite()); - - v = new Vector3D(x, y, Double.POSITIVE_INFINITY); - assertTrue(v.isInfinite()); - - v = new Vector3D(x, y, Double.NEGATIVE_INFINITY); - assertTrue(v.isInfinite()); - } - } - - /** - * Tests {@linkplain Vector3D#isNaN()} - */ - @Test - @UnitTestMethod(name = "isNaN", args = {}) - public void testIsNaN() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2792478867330703600L); - - for (int i = 0; i < 100; i++) { - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - Vector3D v = new Vector3D(x, y, z); - assertFalse(v.isNaN()); - - v = new Vector3D(Double.NaN, y, z); - assertTrue(v.isNaN()); - - v = new Vector3D(x, Double.NaN, z); - assertTrue(v.isNaN()); - - v = new Vector3D(x, y, Double.NaN); - assertTrue(v.isNaN()); - } - } - - /** - * Tests {@linkplain Vector3D#isFinite()} - * - */ - @Test - @UnitTestMethod(name = "isFinite", args = {}) - public void testIsFinite() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5468039318586500858L); - - for (int i = 0; i < 100; i++) { - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v = new Vector3D(x, y, z); - assertTrue(v.isFinite()); - - v = new Vector3D(Double.NaN, y, z); - assertFalse(v.isFinite()); - - v = new Vector3D(Double.NEGATIVE_INFINITY, y, z); - assertFalse(v.isFinite()); - - v = new Vector3D(Double.POSITIVE_INFINITY, y, z); - assertFalse(v.isFinite()); - - v = new Vector3D(x, Double.NaN, z); - assertFalse(v.isFinite()); - - v = new Vector3D(x, Double.NEGATIVE_INFINITY, z); - assertFalse(v.isFinite()); - - v = new Vector3D(x, Double.POSITIVE_INFINITY, z); - assertFalse(v.isFinite()); - - v = new Vector3D(x, y, Double.NaN); - assertFalse(v.isFinite()); - - v = new Vector3D(x, y, Double.NEGATIVE_INFINITY); - assertFalse(v.isFinite()); - - v = new Vector3D(x, y, Double.POSITIVE_INFINITY); - assertFalse(v.isFinite()); - - } - } - - /** - * Tests {@linkplain Vector3D#squareDistanceTo(Vector3D)} - */ - @Test - @UnitTestMethod(name = "squareDistanceTo", args = { Vector3D.class }) - public void testSquareDistanceTo() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(4408285762517228447L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v1 = new Vector3D(x1, y1, z1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v2 = new Vector3D(x2, y2, z2); - - double expected = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) + (z1 - z2) * (z1 - z2); - - double actual = v2.squareDistanceTo(v1); - - assertEquals(expected, actual, 0); - - } - } - - /** - * Tests {@linkplain Vector3D#reverse()} - */ - @Test - @UnitTestMethod(name = "reverse", args = {}) - public void testReverse() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1607695966329330649L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v = new Vector3D(x, y, z); - v = v.reverse(); - - assertEquals(-x, v.getX(), 0); - assertEquals(-y, v.getY(), 0); - assertEquals(-z, v.getZ(), 0); - } - } - - /** - * Tests {@linkplain Vector3D#length()} - */ - @Test - @UnitTestMethod(name = "length", args = {}) - public void testLength() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(892356071941813021L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v = new Vector3D(x, y, z); - double expectedLength = FastMath.sqrt(x * x + y * y + z * z); - double actualLength = v.length(); - - assertEquals(expectedLength, actualLength, TOLERANCE); - } - } - - /** - * Tests {@linkplain Vector3D#squareLength()} - */ - @Test - @UnitTestMethod(name = "squareLength", args = {}) - public void testSquareLength() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5116359435826650990L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v = new Vector3D(x, y, z); - double expectedLength = x * x + y * y + z * z; - double actualLength = v.squareLength(); - - assertEquals(expectedLength, actualLength, 0); - } - } - - /** - * Tests {@linkplain Vector3D#toArray()} - */ - @Test - @UnitTestMethod(name = "toArray", args = {}) - public void testToArray() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1585465023735434312L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v = new Vector3D(x, y, z); - double[] array = v.toArray(); - - assertEquals(v.getX(), array[0], 0); - assertEquals(v.getY(), array[1], 0); - assertEquals(v.getZ(), array[2], 0); - } - } - - /** - * Tests {@linkplain Vector3D#normalize()} - */ - @Test - @UnitTestMethod(name = "normalize", args = {}) - public void testNormalize() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7753068915847520635L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v = new Vector3D(x, y, z); - v = v.normalize(); - - assertEquals(1, v.length(), TOLERANCE); - - } - } - - /** - * Tests {@linkplain Vector3D#isNormal()} - */ - @Test - @UnitTestMethod(name = "isNormal", args = {}) - public void testIsNormal() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8273142075158529624L); - - int activeTestCount = 0; - for (int i = 0; i < 100; i++) { - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v = new Vector3D(x, y, z); - - if (FastMath.abs(v.length() - 1) > Vector3D.NORMAL_LENGTH_TOLERANCE) { - v = v.normalize(); - assertTrue(v.isNormal()); - activeTestCount++; - - Vector3D u = new Vector3D(v); - u = u.scale(1 - 2 * Vector3D.NORMAL_LENGTH_TOLERANCE); - assertFalse(u.isNormal()); - - u = new Vector3D(v); - u = u.scale(1 + 2 * Vector3D.NORMAL_LENGTH_TOLERANCE); - assertFalse(u.isNormal()); - } - } - assertTrue(activeTestCount > 90); - } - - /** - * Tests {@linkplain Vector3D#isPerpendicularTo(Vector3D)} - */ - @Test - @UnitTestMethod(name = "isPerpendicularTo", args = { Vector3D.class }) - public void testIsPerpendicularTo() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7136200979729764353L); - - for (int i = 0; i < 100; i++) { - - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v1 = new Vector3D(x1, y1, z1); - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v2 = new Vector3D(x2, y2, z2); - - Vector3D v3 = new Vector3D(v1); - v3 = v3.rotateToward(new Vector3D(v2), FastMath.toRadians(90)); - - assertTrue(v1.isPerpendicularTo(v3)); - - v3 = new Vector3D(v1); - v3 = v3.rotateToward(new Vector3D(v2), FastMath.PI / 2 - 2 * Vector3D.PERPENDICUALR_ANGLE_TOLERANCE); - assertFalse(v1.isPerpendicularTo(v3)); - - v3 = new Vector3D(v1); - v3 = v3.rotateToward(new Vector3D(v2), FastMath.PI / 2 + 2 * Vector3D.PERPENDICUALR_ANGLE_TOLERANCE); - assertFalse(v1.isPerpendicularTo(v3)); - } - - } - - /** - * Tests {@linkplain Vector3D#equals(Object)} - */ - @Test - @UnitTestMethod(name = "equals", args = { Object.class }) - public void testEquals() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(713907792984443541L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v1 = new Vector3D(x, y, z); - - Vector3D v2 = new Vector3D(x, y, z); - - Vector3D v3 = new Vector3D(x, y, z); - - // reflexive - assertEquals(v1, v1); - - // symetric - assertEquals(v1, v2); - assertEquals(v2, v1); - - // transitive - assertEquals(v1, v2); - assertEquals(v2, v3); - assertEquals(v3, v1); - - } - } - - /** - * Tests {@linkplain Vector3D#hashCode()} - */ - @Test - @UnitTestMethod(name = "hashCode", args = {}) - public void testHashCode() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1626510424735965103L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v1 = new Vector3D(x, y, z); - - Vector3D v2 = new Vector3D(x, y, z); - - assertEquals(v1.hashCode(), v2.hashCode()); - - } - } - - /** - * Tests {@linkplain Vector3D#toString()} - */ - @Test - @UnitTestMethod(name = "toString", args = {}) - public void testToString() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8190009794791481605L); - - for (int i = 0; i < 100; i++) { - - double x = randomGenerator.nextDouble() * 1000 - 500; - double y = randomGenerator.nextDouble() * 1000 - 500; - double z = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v = new Vector3D(x, y, z); - - String expected = "Vector3D [x=" + x + ", y=" + y + ", z=" + z + "]"; - - String actual = v.toString(); - - assertEquals(expected, actual); - } - } - - /** - * Tests {@linkplain Vector3D#rotateAbout(Vector3D, double)} - * - */ - @Test - @UnitTestMethod(name = "rotateAbout", args = { Vector3D.class, double.class }) - public void testRotateAbout() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1807065951741732731L); - - for (int i = 0; i < 100; i++) { - - // v1 will be used as a rotator, so we ensure that it has a - // reasonable length - Vector3D v1 = new Vector3D(); - while (v1.length() < 0.0000001) { - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - v1 = new Vector3D(x1, y1, z1); - } - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v2 = new Vector3D(x2, y2, z2); - - double theta = randomGenerator.nextDouble() * 2 * FastMath.PI; - - Vector3D v = new Vector3D(v2); - - v = v.rotateAbout(v1, theta); - - // v2 under rotation should have its length preserved - assertEquals(v2.length(), v.length(), TOLERANCE); - - // v2 under rotation should have its angle to v1 preserved - assertEquals(v2.angle(v1), v.angle(v1), TOLERANCE); - - // v2 when rotated back should return to its original position - v = v.rotateAbout(v1, -theta); - assertEquals(v2.getX(), v.getX(), TOLERANCE); - assertEquals(v2.getY(), v.getY(), TOLERANCE); - assertEquals(v2.getZ(), v.getZ(), TOLERANCE); - - } - } - - /** - * - * Tests {@linkplain Vector3D#rotateToward(Vector3D, double)} - */ - @Test - @UnitTestMethod(name = "rotateToward", args = { Vector3D.class, double.class }) - public void testRotateToward() { - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(540427496183068832L); - - for (int i = 0; i < 100; i++) { - - // v1 will be used as a rotator, so we ensure that it has a - // reasonable length - Vector3D v1 = new Vector3D(); - while (v1.length() < 0.0000001) { - double x1 = randomGenerator.nextDouble() * 1000 - 500; - double y1 = randomGenerator.nextDouble() * 1000 - 500; - double z1 = randomGenerator.nextDouble() * 1000 - 500; - v1 = new Vector3D(x1, y1, z1); - } - - double x2 = randomGenerator.nextDouble() * 1000 - 500; - double y2 = randomGenerator.nextDouble() * 1000 - 500; - double z2 = randomGenerator.nextDouble() * 1000 - 500; - - Vector3D v2 = new Vector3D(x2, y2, z2); - - double theta = randomGenerator.nextDouble() * 2 * FastMath.PI; - - Vector3D v = new Vector3D(v2); - - v = v.rotateToward(v1, theta); - double angle = v2.angle(v1); - - // v2 under rotation should have its length preserved - assertEquals(v2.length(), v.length(), TOLERANCE); - - // v2 under rotation should have its angle to v1 changed by theta - double expectedAngle = theta - angle; - while (expectedAngle < 0) { - expectedAngle += FastMath.PI * 2; - } - while (expectedAngle > 2 * FastMath.PI) { - expectedAngle -= 2 * FastMath.PI; - } - while (expectedAngle > FastMath.PI) { - expectedAngle = 2 * FastMath.PI - expectedAngle; - } - - double actualAngle = v.angle(v1); - - assertEquals(expectedAngle, actualAngle, TOLERANCE); - - // v2 when rotated back should return to its original position - Vector3D a = new Vector3D(v2); - a = a.cross(v1); - - Vector3D b = new Vector3D(v); - b = b.cross(v1); - - if (a.dot(b) > 0) { - v = v.rotateToward(v1, -theta); - } else { - v = v.rotateToward(v1, theta); - } - - assertEquals(v2.getX(), v.getX(), TOLERANCE); - assertEquals(v2.getY(), v.getY(), TOLERANCE); - assertEquals(v2.getZ(), v.getZ(), TOLERANCE); - - } - } - -} diff --git a/gcm3/src/test/java/util/vector/MT_CircleTest.java b/gcm3/src/test/java/util/vector/MT_CircleTest.java deleted file mode 100644 index 7345a309e..000000000 --- a/gcm3/src/test/java/util/vector/MT_CircleTest.java +++ /dev/null @@ -1,128 +0,0 @@ -package util.vector; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import org.apache.commons.math3.random.RandomGenerator; -import org.apache.commons.math3.util.FastMath; -import org.apache.commons.math3.util.Pair; - -import util.delaunay.planarvisualizer.PlanarVisualzerFrame; -import util.random.RandomGeneratorProvider; -import util.vector.Circle2D.SolutionAlgorithm; - -/** - * A manual test class for {@linkplain Circle2D} comparing the performance of - * the various algorithms for forming a minimal bounding circle about a list of - * 2D positions. - * - * @author Shawn Hatch - * - */ - -public class MT_CircleTest { - - - private static Circle2D generateCircle(RandomGenerator randomGenerator, SolutionAlgorithm solutionAlgorithm, List points) { - Circle2D circle2d = new Circle2D(points, solutionAlgorithm); - // System.out.println(solutionAlgorithm.toString() + " " + circle2d); - for (int i = 0; i < points.size(); i++) { - Vector2D point = points.get(i); - assertTrue(circle2d.contains(point)); - } - return circle2d; - } - - private void testWithStats() { - - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(134563453453453L); - - StringBuilder sb = new StringBuilder(); - for (SolutionAlgorithm solutionAlgorithm : SolutionAlgorithm.values()) { - sb.append(solutionAlgorithm); - sb.append("\t"); - } - System.out.println(sb.toString()); - - for (int i = 0; i < 100; i++) { - int n = 300; - List points = new ArrayList<>(); - for (int j = 0; j < n; j++) { - Vector2D point = new Vector2D(randomGenerator.nextDouble(), randomGenerator.nextDouble() / 10); - points.add(point); - } - - Map map = new LinkedHashMap<>(); - for (SolutionAlgorithm solutionAlgorithm : SolutionAlgorithm.values()) { - Circle2D circle2d = generateCircle(randomGenerator, solutionAlgorithm, points); - map.put(solutionAlgorithm, circle2d.getRadius()); - } - sb = new StringBuilder(); - for (SolutionAlgorithm solutionAlgorithm : SolutionAlgorithm.values()) { - sb.append(map.get(solutionAlgorithm)); - sb.append("\t"); - } - System.out.println(sb.toString()); - - } - - } - - /** - * Non-JUnit test that displays the various solutions in a swing application - */ - public static void main(String[] args) { - new MT_CircleTest().testWithStats(); - - - - RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(134563453453453L); - int n = 30; - List points = new ArrayList<>(); - for (int i = 0; i < n; i++) { - Vector2D point = new Vector2D(randomGenerator.nextDouble(), randomGenerator.nextDouble()); - points.add(point); - } - - Map dataMap = new LinkedHashMap<>(); - int base = 0; - for (int i = 0; i < points.size(); i++) { - dataMap.put(i + base, points.get(i)); - } - - int cirleSteps = 100; - - base += points.size(); - List> pairs = new ArrayList<>(); - - for (SolutionAlgorithm solutionAlgorithm : SolutionAlgorithm.values()) { - Circle2D circle2d = generateCircle(randomGenerator, solutionAlgorithm, points); - - for (int i = 0; i < cirleSteps; i++) { - Vector2D center = circle2d.getCenter(); - double angle = FastMath.PI * 2 * i / cirleSteps; - Vector2D v = new Vector2D(circle2d.getRadius(), 0).rotate(angle).add(center); - dataMap.put(base + i, v); - } - - for (int i = 0; i < cirleSteps - 1; i++) { - pairs.add(new Pair<>(i + base, i + base + 1)); - } - - base += cirleSteps; - - } - - new PlanarVisualzerFrame(dataMap, pairs); - } - - private MT_CircleTest() { - - } - -} diff --git a/gcm3/src/test/java/util/wrappers/AT_MultiKey.java b/gcm3/src/test/java/util/wrappers/AT_MultiKey.java deleted file mode 100644 index 6055cd1b1..000000000 --- a/gcm3/src/test/java/util/wrappers/AT_MultiKey.java +++ /dev/null @@ -1,368 +0,0 @@ -package util.wrappers; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotSame; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.junit.jupiter.api.Test; - -import tools.annotations.UnitTest; -import tools.annotations.UnitTestConstructor; -import tools.annotations.UnitTestMethod; - -/** - * Test class for {@link MultiKey} - * - * @author Shawn Hatch - * - */ -@UnitTest(target = MultiKey.class) -public class AT_MultiKey { - - /** - * Tests the equals contract for {@link AT_MultiKey#equals(Object)} - */ - @Test - @UnitTestMethod(name = "equals", args = { Object.class }) - public void testEqualsContract() { - final MultiKey multiKey1 = new MultiKey(3, "B", false); - final MultiKey multiKey2 = new MultiKey(3, "B", false); - final MultiKey multiKey3 = new MultiKey(3, "B", false); - final MultiKey multiKey4 = new MultiKey("X", "X", 17, 44); - final MultiKey multiKey5 = new MultiKey("A", 2.75, true); - final MultiKey multiKey6 = new MultiKey(); - - /* - * Show that what appear as different objects are different - */ - - assertFalse(multiKey1.equals(multiKey4)); - assertFalse(multiKey1.equals(multiKey5)); - assertFalse(multiKey1.equals(multiKey6)); - assertFalse(multiKey4.equals(multiKey5)); - assertFalse(multiKey4.equals(multiKey6)); - assertFalse(multiKey5.equals(multiKey6)); - - /* - * Show that we have distinct instances - */ - assertNotSame(multiKey1, multiKey2); - - /* - * Show that reflexivity holds - */ - assertEquals(multiKey1, multiKey1); - assertEquals(multiKey2, multiKey2); - assertEquals(multiKey3, multiKey3); - assertEquals(multiKey4, multiKey4); - assertEquals(multiKey5, multiKey5); - assertEquals(multiKey6, multiKey6); - /* - * Show that symmetry holds - */ - assertEquals(multiKey1, multiKey2); - assertEquals(multiKey2, multiKey1); - - /* - * Show that transitivity holds - */ - assertEquals(multiKey1, multiKey2); - assertEquals(multiKey2, multiKey3); - assertEquals(multiKey1, multiKey3); - - /* - * Show equal objects have equal hash codes - */ - assertEquals(multiKey1.hashCode(), multiKey2.hashCode()); - assertEquals(multiKey2.hashCode(), multiKey3.hashCode()); - - } - - /** - * Tests {@link MultiKey#getKey(int)} - */ - @Test - @UnitTestMethod(name = "getKey", args = { int.class }) - public void testGetKey() { - - MultiKey multiKey = new MultiKey(3, "B", false); - assertEquals(multiKey.getKey(0), Integer.valueOf(3)); - assertEquals(multiKey.getKey(1), "B"); - assertEquals(multiKey.getKey(2), false); - assertEquals(3, multiKey.size()); - - multiKey = new MultiKey("X", "X", 17, 44); - assertEquals(multiKey.getKey(0), "X"); - assertEquals(multiKey.getKey(1), "X"); - assertEquals(multiKey.getKey(2), Integer.valueOf(17)); - assertEquals(multiKey.getKey(3), Integer.valueOf(44)); - assertEquals(4, multiKey.size()); - - multiKey = new MultiKey("A", 2.75, true); - assertEquals(multiKey.getKey(0), "A"); - assertEquals(multiKey.getKey(1), Double.valueOf(2.75)); - assertEquals(multiKey.getKey(2), true); - assertEquals(3, multiKey.size()); - - // precondition tests - assertThrows(ArrayIndexOutOfBoundsException.class, () -> new MultiKey(1, 2, 3).getKey(3)); - - } - - /** - * Tests {@link MultiKey#getKeys()} - */ - @Test - @UnitTestMethod(name = "getKeys", args = {}) - public void testGetKeys() { - MultiKey multiKey = new MultiKey(3, "B", false); - Object[] expectedKeys = new Object[] { 3, "B", false }; - assertArrayEquals(expectedKeys, multiKey.getKeys()); - - multiKey = new MultiKey("X", "X", 17, 44); - expectedKeys = new Object[] { "X", "X", 17, 44 }; - assertArrayEquals(expectedKeys, multiKey.getKeys()); - - multiKey = new MultiKey("A", 2.75, true); - expectedKeys = new Object[] { "A", 2.75, true }; - assertArrayEquals(expectedKeys, multiKey.getKeys()); - } - - /** - * Tests {@link MultiKey#builder()} - */ - @Test - @UnitTestMethod(name = "builder", target = MultiKey.class, args = {}) - public void testMultiKeyBuilder_Constructor() { - /* - * We will show that the MultiKeyBuilder produces the expected MultiKey - * values and is reusable. - */ - - MultiKey builderMultiKey = MultiKey .builder()// - .addKey(3)// - .addKey("B")// - .addKey(false)// - .build();// - - assertEquals(builderMultiKey.getKey(0), Integer.valueOf(3)); - assertEquals(builderMultiKey.getKey(1), "B"); - assertEquals(builderMultiKey.getKey(2), false); - assertEquals(3, builderMultiKey.size()); - MultiKey constructorMultiKey = new MultiKey(3, "B", false); - assertEquals(constructorMultiKey, builderMultiKey); - - builderMultiKey = MultiKey .builder().addKey("X")// - .addKey("X")// - .addKey(17)// - .addKey(44)// - .build();// - assertEquals(builderMultiKey.getKey(0), "X"); - assertEquals(builderMultiKey.getKey(1), "X"); - assertEquals(builderMultiKey.getKey(2), Integer.valueOf(17)); - assertEquals(builderMultiKey.getKey(3), Integer.valueOf(44)); - assertEquals(4, builderMultiKey.size()); - constructorMultiKey = new MultiKey("X", "X", 17, 44); - assertEquals(constructorMultiKey, builderMultiKey); - - builderMultiKey = MultiKey .builder()// - .addKey("A")// - .addKey(2.75)// - .addKey(true)// - .build();// - - assertEquals(builderMultiKey.getKey(0), "A"); - assertEquals(builderMultiKey.getKey(1), Double.valueOf(2.75)); - assertEquals(builderMultiKey.getKey(2), true); - assertEquals(3, builderMultiKey.size()); - constructorMultiKey = new MultiKey("A", 2.75, true); - assertEquals(constructorMultiKey, builderMultiKey); - - } - - /** - * Tests {@link MultiKey.MultiKeyBuilder#addKey(Object)} - */ - @Test - @UnitTestMethod(name = "addKey", target = MultiKey.Builder.class, args = { Object.class }) - public void testMultiKeyBuilder_AddKey() { - // covered by testMultiKeyBuilder_Constructor - } - - /** - * Tests {@link MultiKey.MultiKeyBuilder#build()} - */ - @Test - @UnitTestMethod(name = "build", target = MultiKey.Builder.class, args = {}) - public void testMultiKeyBuilder_Build() { - // covered by testMultiKeyBuilder_Constructor - } - - /** - * Tests {@link MultiKey#MultiKey(Object...)} - */ - @Test - @UnitTestConstructor(args = { Object[].class }) - public void testConstructor() { - /* - * We will show that the MultiKey constructor produces the expected - * MultiKey - */ - - MultiKey multiKey = new MultiKey(3, "B", false); - assertEquals(multiKey.getKey(0), Integer.valueOf(3)); - assertEquals(multiKey.getKey(1), "B"); - assertEquals(multiKey.getKey(2), false); - assertEquals(3, multiKey.size()); - - multiKey = new MultiKey("X", "X", 17, 44); - assertEquals(multiKey.getKey(0), "X"); - assertEquals(multiKey.getKey(1), "X"); - assertEquals(multiKey.getKey(2), Integer.valueOf(17)); - assertEquals(multiKey.getKey(3), Integer.valueOf(44)); - assertEquals(4, multiKey.size()); - - multiKey = new MultiKey("A", 2.75, true); - assertEquals(multiKey.getKey(0), "A"); - assertEquals(multiKey.getKey(1), Double.valueOf(2.75)); - assertEquals(multiKey.getKey(2), true); - assertEquals(3, multiKey.size()); - - } - - /** - * Tests {@link MultiKey#size()} - */ - @Test - @UnitTestMethod(name = "size", args = {}) - public void testSize() { - MultiKey multiKey = new MultiKey(); - assertEquals(0, multiKey.size()); - - multiKey = new MultiKey("D"); - assertEquals(1, multiKey.size()); - - multiKey = new MultiKey(4.5, 12); - assertEquals(2, multiKey.size()); - - multiKey = new MultiKey(3, "B", false); - assertEquals(3, multiKey.size()); - - multiKey = new MultiKey("X", "X", 17, 44); - assertEquals(4, multiKey.size()); - - } - - /** - * Tests {@link MultiKey#toKeyString()} - */ - @Test - @UnitTestMethod(name = "toKeyString", args = {}) - public void testToKeyString() { - MultiKey multiKey = new MultiKey(); - assertEquals("[]", multiKey.toKeyString()); - - multiKey = new MultiKey("D"); - assertEquals("[D]", multiKey.toKeyString()); - - multiKey = new MultiKey(4.5, 12); - assertEquals("[4.5, 12]", multiKey.toKeyString()); - - multiKey = new MultiKey(3, "B", false); - assertEquals("[3, B, false]", multiKey.toKeyString()); - - multiKey = new MultiKey("X", "X", 17, 44); - assertEquals("[X, X, 17, 44]", multiKey.toKeyString()); - } - - /** - * Tests {@link MultiKey#toTabString()} - */ - @Test - @UnitTestMethod(name = "toTabString", args = {}) - public void testToTabString() { - MultiKey multiKey = new MultiKey(); - assertEquals("", multiKey.toTabString()); - - multiKey = new MultiKey("D"); - assertEquals("D", multiKey.toTabString()); - - multiKey = new MultiKey(4.5, 12); - - assertEquals("4.5\t12", multiKey.toTabString()); - - multiKey = new MultiKey(3, "B", false); - assertEquals("3\tB\tfalse", multiKey.toTabString()); - - multiKey = new MultiKey("X", "X", 17, 44); - assertEquals("X\tX\t17\t44", multiKey.toTabString()); - } - - /** - * Tests {@link MultiKey#toString()} - */ - @Test - @UnitTestMethod(name = "toString", args = {}) - public void testToString() { - MultiKey multiKey = new MultiKey(); - assertEquals("MultiKey [objects=[]]", multiKey.toString()); - - multiKey = new MultiKey("D"); - assertEquals("MultiKey [objects=[D]]", multiKey.toString()); - - multiKey = new MultiKey(4.5, 12); - assertEquals("MultiKey [objects=[4.5, 12]]", multiKey.toString()); - - multiKey = new MultiKey(3, "B", false); - assertEquals("MultiKey [objects=[3, B, false]]", multiKey.toString()); - - multiKey = new MultiKey("X", "X", 17, 44); - assertEquals("MultiKey [objects=[X, X, 17, 44]]", multiKey.toString()); - } - - /** - * Tests {@link MultiKey#equals(Object)} - */ - @Test - @UnitTestMethod(name = "equals", args = { Object.class }) - public void testEquals() { - MultiKey multiKey1 = new MultiKey(3, "B", false); - MultiKey multiKey2 = new MultiKey(3, "B", false); - MultiKey multiKey3 = new MultiKey(3, "A", false); - MultiKey multiKey4 = new MultiKey(3, "b", false); - - // reflexive - assertEquals(multiKey1, multiKey1); - assertEquals(multiKey2, multiKey2); - assertEquals(multiKey3, multiKey3); - assertEquals(multiKey4, multiKey4); - - // symmetric - assertEquals(multiKey1, multiKey2); - assertEquals(multiKey2, multiKey1); - - // transitive -- no need - - // unequal - assertNotEquals(multiKey1, multiKey3); - assertNotEquals(multiKey1, multiKey4); - assertNotEquals(multiKey2, multiKey3); - assertNotEquals(multiKey2, multiKey4); - assertNotEquals(multiKey3, multiKey4); - - } - - /** - * Tests {@link MultiKey#hashCode()} - */ - @Test - @UnitTestMethod(name = "hashCode", args = {}) - public void testHashCode() { - MultiKey multiKey1 = new MultiKey(3, "B", false); - MultiKey multiKey2 = new MultiKey(3, "B", false); - assertEquals(multiKey1.hashCode(), multiKey2.hashCode()); - } -} diff --git a/tutorials/lessons/lesson_01_hello_world/pom.xml b/tutorials/lessons/lesson_01_hello_world/pom.xml new file mode 100644 index 000000000..39750c5da --- /dev/null +++ b/tutorials/lessons/lesson_01_hello_world/pom.xml @@ -0,0 +1,79 @@ + + 4.0.0 + + + ASPR + https://www.phe.gov + + + + gov.hhs.aspr.ms.gcm.tutorials + lesson_01_hello_world + ${revision} + jar + gcm hello world tutorial + An introduction to GCM via a single line of code that executes the simulation + + + + UTF-8 + 17 + 17 + ${gcm.version} + + 1.3.0 + + 4.0.0-SNAPSHOT + + + + + + gov.hhs.aspr.ms + gcm + ${gcm.version} + + + + + + Shawn Hatch + Leidos + https://www.leidos.com + + + + + + + org.codehaus.mojo + flatten-maven-plugin + ${flatten-maven-plugin.version} + + + + flatten + process-resources + + flatten + + + true + + + + + flatten.clean + clean + + clean + + + + + + + + diff --git a/tutorials/lessons/lesson_01_hello_world/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_1.java b/tutorials/lessons/lesson_01_hello_world/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_1.java new file mode 100644 index 000000000..84c95133e --- /dev/null +++ b/tutorials/lessons/lesson_01_hello_world/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_1.java @@ -0,0 +1,23 @@ +/*start code_ref=hello_world_short|code_cap=Building and executing an empty simulation.*/ +package gov.hhs.aspr.ms.gcm.lessons; + +import gov.hhs.aspr.ms.gcm.nucleus.Simulation; + +public final class Example_1 { + + public static void main(String[] args) { + Simulation.builder().build().execute(); + } + /* end */ + + public static void alternateMain(String[] args) { + /* + * start code_ref=hello_world_long|code_cap=Building and executing an empty + * simulation broken out into discrete commands. + */ + Simulation.Builder builder = Simulation.builder(); + Simulation simulation = builder.build(); + simulation.execute(); + /* end */ + } +} diff --git a/tutorials/lessons/lesson_02_plugins/pom.xml b/tutorials/lessons/lesson_02_plugins/pom.xml new file mode 100644 index 000000000..ae22129f5 --- /dev/null +++ b/tutorials/lessons/lesson_02_plugins/pom.xml @@ -0,0 +1,79 @@ + + 4.0.0 + + + ASPR + https://www.phe.gov + + + + gov.hhs.aspr.ms.gcm.tutorials + lesson_02_plugins + ${revision} + jar + gcm lesson 02 plugins + Tutorial introducing plugins as the component architecture of GCM + + + + UTF-8 + 17 + 17 + ${gcm.version} + + 1.3.0 + + 4.0.0-SNAPSHOT + + + + + + gov.hhs.aspr.ms + gcm + ${gcm.version} + + + + + + Shawn Hatch + Leidos + https://www.leidos.com + + + + + + + org.codehaus.mojo + flatten-maven-plugin + ${flatten-maven-plugin.version} + + + + flatten + process-resources + + flatten + + + true + + + + + flatten.clean + clean + + clean + + + + + + + + diff --git a/tutorials/lessons/lesson_02_plugins/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_2.java b/tutorials/lessons/lesson_02_plugins/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_2.java new file mode 100644 index 000000000..b8314f750 --- /dev/null +++ b/tutorials/lessons/lesson_02_plugins/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_2.java @@ -0,0 +1,29 @@ +package gov.hhs.aspr.ms.gcm.lessons; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.nucleus.SimplePluginId; +import gov.hhs.aspr.ms.gcm.nucleus.Simulation; + +/*start code_ref=plugins_intro_to_plugins|code_cap=A simple plugin added to the simulation. Plugins act as modules for all components contributed to the simulation.*/ +public final class Example_2 { + + private Example_2() { + } + + public static void main(String[] args) { + + PluginId pluginId = new SimplePluginId("example plugin"); + + Plugin plugin = Plugin.builder()// + .setPluginId(pluginId)// + .build(); + + Simulation.builder()// + .addPlugin(plugin)// + .build()// + .execute(); + } + +} +/* end */ diff --git a/tutorials/lessons/lesson_03_actors/pom.xml b/tutorials/lessons/lesson_03_actors/pom.xml new file mode 100644 index 000000000..cb1de698e --- /dev/null +++ b/tutorials/lessons/lesson_03_actors/pom.xml @@ -0,0 +1,79 @@ + + 4.0.0 + + + ASPR + https://www.phe.gov + + + + gov.hhs.aspr.ms.gcm.tutorials + lesson_03_actors + ${revision} + jar + gcm lesson 03 actors + Tutorial introducing actors + + + + UTF-8 + 17 + 17 + ${gcm.version} + + 1.3.0 + + 4.0.0-SNAPSHOT + + + + + + gov.hhs.aspr.ms + gcm + ${gcm.version} + + + + + + Shawn Hatch + Leidos + https://www.leidos.com + + + + + + + org.codehaus.mojo + flatten-maven-plugin + ${flatten-maven-plugin.version} + + + + flatten + process-resources + + flatten + + + true + + + + + flatten.clean + clean + + clean + + + + + + + + diff --git a/tutorials/lessons/lesson_03_actors/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_3.java b/tutorials/lessons/lesson_03_actors/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_3.java new file mode 100644 index 000000000..2d40d1880 --- /dev/null +++ b/tutorials/lessons/lesson_03_actors/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_3.java @@ -0,0 +1,72 @@ +package gov.hhs.aspr.ms.gcm.lessons; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.PluginContext; +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.nucleus.SimplePluginId; +import gov.hhs.aspr.ms.gcm.nucleus.Simulation; + +public final class Example_3 { + + private Example_3() { + } + + /** + * Introducing the addition of a plugin and an actor + */ + public static void main(String[] args) { + /* start code_ref=actors_actor_context_using_lambdas|code_cap=A single actor writes output to the console during its initialization.*/ + PluginId pluginId = new SimplePluginId("example plugin"); + + Plugin plugin = Plugin.builder()// + .setPluginId(pluginId)// + .setInitializer(pluginContext -> { + System.out.println("plugin being initialized -- we will add one actor"); + pluginContext.addActor(actorContext -> { + System.out.println("actor being initialized"); + System.out.println("my id = " + actorContext.getActorId()); + System.out.println("time = " + actorContext.getTime()); + }); + })// + .build(); + + Simulation.builder()// + .addPlugin(plugin)// + .build()// + .execute(); + /* end */ + } + + /* start code_ref=actors_plugin_initializer|code_cap=A single actor is being added to the simulation at initialization.*/ + public static void pluginInit(PluginContext pluginContext) { + System.out.println("plugin being initialized -- we will add one actor"); + pluginContext.addActor(Example_3::actorInit); + } + /* end */ + + /* start code_ref=actors_actor_init|code_cap=The actor prints out some identifying information when it initializes.*/ + public static void actorInit(ActorContext actorContext) { + System.out.println("actor being initialized"); + System.out.println("my id = " + actorContext.getActorId()); + System.out.println("time = " + actorContext.getTime()); + } + /* end */ + + public static void altMain(String[] args) { + /* start code_ref=actors_intro_to_plugin_context|code_cap=An initializer uses a plugin context to execute the initialization logic at the beginning of each simulation.*/ + PluginId pluginId = new SimplePluginId("example plugin"); + + Plugin plugin = Plugin.builder()// + .setPluginId(pluginId)// + .setInitializer(Example_3::pluginInit)// + .build(); + + Simulation.builder()// + .addPlugin(plugin)// + .build()// + .execute(); + /* end */ + + } +} diff --git a/tutorials/lessons/lesson_03_actors/src/main/resources/output/actors_lesson_3_output.txt b/tutorials/lessons/lesson_03_actors/src/main/resources/output/actors_lesson_3_output.txt new file mode 100644 index 000000000..d0b37eecf --- /dev/null +++ b/tutorials/lessons/lesson_03_actors/src/main/resources/output/actors_lesson_3_output.txt @@ -0,0 +1,6 @@ +/* start code_ref=actors_lesson_3_output|code_cap=Output from the single actor as it initializes.*/ +plugin being initialized -- we will add one actor +actor being initialized +my id = ActorId [id=0] +time = 0.0 +/* end */ \ No newline at end of file diff --git a/tutorials/lessons/lesson_04_data_managers/pom.xml b/tutorials/lessons/lesson_04_data_managers/pom.xml new file mode 100644 index 000000000..2b7a20cff --- /dev/null +++ b/tutorials/lessons/lesson_04_data_managers/pom.xml @@ -0,0 +1,79 @@ + + 4.0.0 + + + ASPR + https://www.phe.gov + + + + gov.hhs.aspr.ms.gcm.tutorials + lesson_04_data_managers + ${revision} + jar + gcm lesson 04 data managers + Tutorial introducing data managers + + + + UTF-8 + 17 + 17 + ${gcm.version} + + 1.3.0 + + 4.0.0-SNAPSHOT + + + + + + gov.hhs.aspr.ms + gcm + ${gcm.version} + + + + + + Shawn Hatch + Leidos + https://www.leidos.com + + + + + + + org.codehaus.mojo + flatten-maven-plugin + ${flatten-maven-plugin.version} + + + + flatten + process-resources + + flatten + + + true + + + + + flatten.clean + clean + + clean + + + + + + + + diff --git a/tutorials/lessons/lesson_04_data_managers/src/main/java/gov/hhs/aspr/ms/gcm/lessons/ExampleActor.java b/tutorials/lessons/lesson_04_data_managers/src/main/java/gov/hhs/aspr/ms/gcm/lessons/ExampleActor.java new file mode 100644 index 000000000..9b5f0e893 --- /dev/null +++ b/tutorials/lessons/lesson_04_data_managers/src/main/java/gov/hhs/aspr/ms/gcm/lessons/ExampleActor.java @@ -0,0 +1,23 @@ + +package gov.hhs.aspr.ms.gcm.lessons; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; + +/* start code_ref=data_managers_example_actor|code_cap=The example actor initializes by making plans to update the alpha property on a daily basis.*/ +public final class ExampleActor { + + public void init(ActorContext actorContext) { + System.out.println("Example Actor is initialized and will plan to set Alpha"); + + ExampleDataManager exampleDataManager = actorContext.getDataManager(ExampleDataManager.class); + + for (double planTime = 0; planTime < 10; planTime++) { + actorContext.addPlan((context) -> { + int alpha = exampleDataManager.getAlpha(); + alpha++; + exampleDataManager.setAlpha(alpha); + }, planTime); + } + } +} +/* end */ diff --git a/tutorials/lessons/lesson_04_data_managers/src/main/java/gov/hhs/aspr/ms/gcm/lessons/ExampleDataManager.java b/tutorials/lessons/lesson_04_data_managers/src/main/java/gov/hhs/aspr/ms/gcm/lessons/ExampleDataManager.java new file mode 100644 index 000000000..63f89f066 --- /dev/null +++ b/tutorials/lessons/lesson_04_data_managers/src/main/java/gov/hhs/aspr/ms/gcm/lessons/ExampleDataManager.java @@ -0,0 +1,41 @@ +package gov.hhs.aspr.ms.gcm.lessons; + +import gov.hhs.aspr.ms.gcm.nucleus.DataManager; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; + +/* start code_ref=data_managers_example_data_manager|code_cap=The example data manager manages the state of two properties and prints to the console when changes are made.*/ +public final class ExampleDataManager extends DataManager { + + private int alpha = 7; + + private double beta = 1.2345; + + private DataManagerContext dataManagerContext; + + @Override + public void init(DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + this.dataManagerContext = dataManagerContext; + System.out.println("ExampleDataManager is initialized"); + } + + public int getAlpha() { + return alpha; + } + + public void setAlpha(int alpha) { + this.alpha = alpha; + System.out.println("ExampleDataManager sets alpha = " + alpha + " at time = " + dataManagerContext.getTime()); + } + + public double getBeta() { + return beta; + } + + public void setBeta(double beta) { + this.beta = beta; + System.out.println("ExampleDataManager sets beta = " + beta + " at time = " + dataManagerContext.getTime()); + } + +} +/* end */ diff --git a/tutorials/lessons/lesson_04_data_managers/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_4.java b/tutorials/lessons/lesson_04_data_managers/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_4.java new file mode 100644 index 000000000..d37bed5b8 --- /dev/null +++ b/tutorials/lessons/lesson_04_data_managers/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_4.java @@ -0,0 +1,39 @@ +package gov.hhs.aspr.ms.gcm.lessons; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.nucleus.SimplePluginId; +import gov.hhs.aspr.ms.gcm.nucleus.Simulation; + +public final class Example_4 { + + private Example_4() { + } + + /** + * Introducing the addition of a data manager. Note that we are adding the actor + * now via a method reference. The data manager will have two values, Alpha and + * Beta. We also introduce the ability of the Actor to plan. + */ + + /* start code_ref=data_managers_intro_to_data_managers|code_cap=A data manager is added to the simulation.*/ + public static void main(String[] args) { + + PluginId pluginId = new SimplePluginId("example plugin"); + + Plugin plugin = Plugin.builder()// + .setPluginId(pluginId)// + .setInitializer(pluginContext -> { + pluginContext.addActor(new ExampleActor()::init); + pluginContext.addDataManager(new ExampleDataManager()); + })// + .build(); + + Simulation.builder()// + .addPlugin(plugin)// + .build()// + .execute(); + } + /* end */ + +} diff --git a/tutorials/lessons/lesson_04_data_managers/src/main/resources/output/data_managers_lesson_4_output.txt b/tutorials/lessons/lesson_04_data_managers/src/main/resources/output/data_managers_lesson_4_output.txt new file mode 100644 index 000000000..9d7997fe1 --- /dev/null +++ b/tutorials/lessons/lesson_04_data_managers/src/main/resources/output/data_managers_lesson_4_output.txt @@ -0,0 +1,14 @@ +/* start code_ref=data_managers_lesson_4_output|code_cap=Output from the example actor and example data manager showing the alpha property initialization and subsequent evolution.*/ +ExampleDataManager is initialized +Example Actor is initialized and will plan to set Alpha +ExampleDataManager sets alpha = 8 at time = 0.0 +ExampleDataManager sets alpha = 9 at time = 1.0 +ExampleDataManager sets alpha = 10 at time = 2.0 +ExampleDataManager sets alpha = 11 at time = 3.0 +ExampleDataManager sets alpha = 12 at time = 4.0 +ExampleDataManager sets alpha = 13 at time = 5.0 +ExampleDataManager sets alpha = 14 at time = 6.0 +ExampleDataManager sets alpha = 15 at time = 7.0 +ExampleDataManager sets alpha = 16 at time = 8.0 +ExampleDataManager sets alpha = 17 at time = 9.0 +/* end */ \ No newline at end of file diff --git a/tutorials/lessons/lesson_05_events/pom.xml b/tutorials/lessons/lesson_05_events/pom.xml new file mode 100644 index 000000000..738fa24c5 --- /dev/null +++ b/tutorials/lessons/lesson_05_events/pom.xml @@ -0,0 +1,79 @@ + + 4.0.0 + + + ASPR + https://www.phe.gov + + + + gov.hhs.aspr.ms.gcm.tutorials + lesson_05_events + ${revision} + jar + gcm lesson 05 events + Tutorial introducing events + + + + UTF-8 + 17 + 17 + ${gcm.version} + + 1.3.0 + + 4.0.0-SNAPSHOT + + + + + + gov.hhs.aspr.ms + gcm + ${gcm.version} + + + + + + Shawn Hatch + Leidos + https://www.leidos.com + + + + + + + org.codehaus.mojo + flatten-maven-plugin + ${flatten-maven-plugin.version} + + + + flatten + process-resources + + flatten + + + true + + + + + flatten.clean + clean + + clean + + + + + + + + diff --git a/tutorials/lessons/lesson_05_events/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Actor1.java b/tutorials/lessons/lesson_05_events/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Actor1.java new file mode 100644 index 000000000..032c41fc8 --- /dev/null +++ b/tutorials/lessons/lesson_05_events/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Actor1.java @@ -0,0 +1,31 @@ + +package gov.hhs.aspr.ms.gcm.lessons; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; + +/* start code_ref=events_actor_1_mutates_values|code_cap=Actor 1 schedules updates to both the alpha and beta properties.*/ +public final class Actor1 { + + public void init(ActorContext actorContext) { + ExampleDataManager exampleDataManager = actorContext.getDataManager(ExampleDataManager.class); + + for (double planTime = 1; planTime <= 10; planTime++) { + actorContext.addPlan((context) -> { + int alpha = exampleDataManager.getAlpha(); + alpha++; + exampleDataManager.setAlpha(alpha); + }, planTime); + } + + for (int i = 1; i <= 5; i++) { + double planTime = i * 3.5; + actorContext.addPlan((context) -> { + double beta = exampleDataManager.getBeta(); + beta *= 2; + exampleDataManager.setBeta(beta); + }, planTime); + } + + } +} +/* end */ diff --git a/tutorials/lessons/lesson_05_events/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Actor2.java b/tutorials/lessons/lesson_05_events/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Actor2.java new file mode 100644 index 000000000..f95218c51 --- /dev/null +++ b/tutorials/lessons/lesson_05_events/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Actor2.java @@ -0,0 +1,17 @@ +package gov.hhs.aspr.ms.gcm.lessons; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.EventFilter; + +/* start code_ref=events_actor_2_reacts_to_AlphaChangeEvent|code_cap=Actor 2 reacts to changes in the alpha property.*/ +public final class Actor2 { + public void init(ActorContext actorContext) { + + EventFilter eventFilter = EventFilter.builder(AlphaChangeEvent.class).build(); + + actorContext.subscribe(eventFilter, (context, event) -> { + System.out.println("Actor2 observes event " + event + " at time = " + context.getTime()); + }); + } +} +/* end */ \ No newline at end of file diff --git a/tutorials/lessons/lesson_05_events/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Actor3.java b/tutorials/lessons/lesson_05_events/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Actor3.java new file mode 100644 index 000000000..7701c53ac --- /dev/null +++ b/tutorials/lessons/lesson_05_events/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Actor3.java @@ -0,0 +1,16 @@ + +package gov.hhs.aspr.ms.gcm.lessons; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.EventFilter; + +/* start code_ref=events_actor_3_reacts_to_BetaChangeEvent|code_cap=Actor 3 reacts to changes in the beta property.*/ +public final class Actor3 { + public void init(ActorContext actorContext) { + EventFilter eventFilter = EventFilter.builder(BetaChangeEvent.class).build(); + actorContext.subscribe(eventFilter, (context, event) -> { + System.out.println("Actor3 observes event " + event + " at time = " + context.getTime()); + }); + } +} +/* end */ \ No newline at end of file diff --git a/tutorials/lessons/lesson_05_events/src/main/java/gov/hhs/aspr/ms/gcm/lessons/AlphaChangeEvent.java b/tutorials/lessons/lesson_05_events/src/main/java/gov/hhs/aspr/ms/gcm/lessons/AlphaChangeEvent.java new file mode 100644 index 000000000..bf200944f --- /dev/null +++ b/tutorials/lessons/lesson_05_events/src/main/java/gov/hhs/aspr/ms/gcm/lessons/AlphaChangeEvent.java @@ -0,0 +1,40 @@ +package gov.hhs.aspr.ms.gcm.lessons; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import net.jcip.annotations.Immutable; + +@Immutable +/* start code_ref=events_alpha_change_event|code_cap=An event to notify that the alpha property has been updated. */ +public final class AlphaChangeEvent implements Event { + + private final int previousAlpha; + + private final int currentAlpha; + + public AlphaChangeEvent(int previousAlpha, int currentAlpha) { + super(); + this.previousAlpha = previousAlpha; + this.currentAlpha = currentAlpha; + } + + public int getPreviousAlpha() { + return previousAlpha; + } + + public int getCurrentAlpha() { + return currentAlpha; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("AlphaChangeEvent [previousAlpha="); + builder.append(previousAlpha); + builder.append(", currentAlpha="); + builder.append(currentAlpha); + builder.append("]"); + return builder.toString(); + } + +} +/* end */ diff --git a/tutorials/lessons/lesson_05_events/src/main/java/gov/hhs/aspr/ms/gcm/lessons/BetaChangeEvent.java b/tutorials/lessons/lesson_05_events/src/main/java/gov/hhs/aspr/ms/gcm/lessons/BetaChangeEvent.java new file mode 100644 index 000000000..998d09f04 --- /dev/null +++ b/tutorials/lessons/lesson_05_events/src/main/java/gov/hhs/aspr/ms/gcm/lessons/BetaChangeEvent.java @@ -0,0 +1,40 @@ +package gov.hhs.aspr.ms.gcm.lessons; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import net.jcip.annotations.Immutable; + +@Immutable +/* start code_ref=events_beta_change_event|code_cap=An event to notify that the beta property has been updated.*/ +public final class BetaChangeEvent implements Event { + + private final double previousBeta; + + private final double currentBeta; + + public BetaChangeEvent(double previousBeta, double currentBeta) { + super(); + this.previousBeta = previousBeta; + this.currentBeta = currentBeta; + } + + public double getPreviousBeta() { + return previousBeta; + } + + public double getCurrentBeta() { + return currentBeta; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("BetaChangeEvent [previousBeta="); + builder.append(previousBeta); + builder.append(", currentBeta="); + builder.append(currentBeta); + builder.append("]"); + return builder.toString(); + } + +} +/* end */ diff --git a/tutorials/lessons/lesson_05_events/src/main/java/gov/hhs/aspr/ms/gcm/lessons/ExampleDataManager.java b/tutorials/lessons/lesson_05_events/src/main/java/gov/hhs/aspr/ms/gcm/lessons/ExampleDataManager.java new file mode 100644 index 000000000..a41642c60 --- /dev/null +++ b/tutorials/lessons/lesson_05_events/src/main/java/gov/hhs/aspr/ms/gcm/lessons/ExampleDataManager.java @@ -0,0 +1,60 @@ +package gov.hhs.aspr.ms.gcm.lessons; + +import gov.hhs.aspr.ms.gcm.nucleus.DataManager; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; +import gov.hhs.aspr.ms.gcm.nucleus.Event; + +public final class ExampleDataManager extends DataManager { + + private int alpha = 7; + private double beta = 1.2345; + private DataManagerContext dataManagerContext; + + @Override + public void init(DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + this.dataManagerContext = dataManagerContext; + dataManagerContext.subscribe(AlphaChangeMutationEvent.class, this::handleAlphaChangeMutationEvent); + dataManagerContext.subscribe(BetaChangeMutationEvent.class, this::handleBetaChangeMutationEvent); + } + + public int getAlpha() { + return alpha; + } + + public double getBeta() { + return beta; + } + + /* start code_ref=events_intro_to_event_generation|code_cap=The alpha and beta updates are managed via private mutation events.*/ + private static record AlphaChangeMutationEvent(int alpha) implements Event { + } + + public void setAlpha(int alpha) { + dataManagerContext.releaseMutationEvent(new AlphaChangeMutationEvent(alpha)); + } + + private void handleAlphaChangeMutationEvent(DataManagerContext dataManagerContext, + AlphaChangeMutationEvent alphaChangeMutationEvent) { + int alpha = alphaChangeMutationEvent.alpha(); + int previousValue = this.alpha; + this.alpha = alpha; + dataManagerContext.releaseObservationEvent(new AlphaChangeEvent(previousValue, this.alpha)); + } + + private static record BetaChangeMutationEvent(double beta) implements Event { + } + + public void setBeta(double beta) { + dataManagerContext.releaseMutationEvent(new BetaChangeMutationEvent(beta)); + } + + private void handleBetaChangeMutationEvent(DataManagerContext dataManagerContext, + BetaChangeMutationEvent betaChangeMutationEvent) { + double beta = betaChangeMutationEvent.beta(); + double previousValue = this.beta; + this.beta = beta; + dataManagerContext.releaseObservationEvent(new BetaChangeEvent(previousValue, this.beta)); + } + /* end */ +} diff --git a/tutorials/lessons/lesson_05_events/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_5.java b/tutorials/lessons/lesson_05_events/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_5.java new file mode 100644 index 000000000..9212d2f8d --- /dev/null +++ b/tutorials/lessons/lesson_05_events/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_5.java @@ -0,0 +1,32 @@ +package gov.hhs.aspr.ms.gcm.lessons; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.nucleus.SimplePluginId; +import gov.hhs.aspr.ms.gcm.nucleus.Simulation; + +public final class Example_5 { + + private Example_5() { + } + + public static void main(String[] args) { + + PluginId pluginId = new SimplePluginId("example plugin"); + + Plugin plugin = Plugin.builder()// + .setPluginId(pluginId)// + .setInitializer(pluginContext -> { + pluginContext.addActor(new Actor1()::init); + pluginContext.addActor(new Actor2()::init); + pluginContext.addActor(new Actor3()::init); + pluginContext.addDataManager(new ExampleDataManager()); + })// + .build(); + + Simulation.builder()// + .addPlugin(plugin)// + .build()// + .execute(); + } +} diff --git a/tutorials/lessons/lesson_05_events/src/main/resources/output/events_lesson_5_output.txt b/tutorials/lessons/lesson_05_events/src/main/resources/output/events_lesson_5_output.txt new file mode 100644 index 000000000..6b5840533 --- /dev/null +++ b/tutorials/lessons/lesson_05_events/src/main/resources/output/events_lesson_5_output.txt @@ -0,0 +1,17 @@ +/* start code_ref=events_lesson_5_output|code_cap=Output from Actors 2 and 3 as they observe changes to the alpha and beta properties.*/ +Actor2 observes event AlphaChangeEvent [previousAlpha=7, currentAlpha=8] at time = 1.0 +Actor2 observes event AlphaChangeEvent [previousAlpha=8, currentAlpha=9] at time = 2.0 +Actor2 observes event AlphaChangeEvent [previousAlpha=9, currentAlpha=10] at time = 3.0 +Actor3 observes event BetaChangeEvent [previousBeta=1.2345, currentBeta=2.469] at time = 3.5 +Actor2 observes event AlphaChangeEvent [previousAlpha=10, currentAlpha=11] at time = 4.0 +Actor2 observes event AlphaChangeEvent [previousAlpha=11, currentAlpha=12] at time = 5.0 +Actor2 observes event AlphaChangeEvent [previousAlpha=12, currentAlpha=13] at time = 6.0 +Actor2 observes event AlphaChangeEvent [previousAlpha=13, currentAlpha=14] at time = 7.0 +Actor3 observes event BetaChangeEvent [previousBeta=2.469, currentBeta=4.938] at time = 7.0 +Actor2 observes event AlphaChangeEvent [previousAlpha=14, currentAlpha=15] at time = 8.0 +Actor2 observes event AlphaChangeEvent [previousAlpha=15, currentAlpha=16] at time = 9.0 +Actor2 observes event AlphaChangeEvent [previousAlpha=16, currentAlpha=17] at time = 10.0 +Actor3 observes event BetaChangeEvent [previousBeta=4.938, currentBeta=9.876] at time = 10.5 +Actor3 observes event BetaChangeEvent [previousBeta=9.876, currentBeta=19.752] at time = 14.0 +Actor3 observes event BetaChangeEvent [previousBeta=19.752, currentBeta=39.504] at time = 17.5 +/* end */ \ No newline at end of file diff --git a/tutorials/lessons/lesson_06_plugin_dependencies/pom.xml b/tutorials/lessons/lesson_06_plugin_dependencies/pom.xml new file mode 100644 index 000000000..6d435ef3b --- /dev/null +++ b/tutorials/lessons/lesson_06_plugin_dependencies/pom.xml @@ -0,0 +1,79 @@ + + 4.0.0 + + + ASPR + https://www.phe.gov + + + + gov.hhs.aspr.ms.gcm.tutorials + lesson_06_plugin_dependencies + ${revision} + jar + gcm lesson 06 plugin dependencies + Tutorial introducing plugin dependencies + + + + UTF-8 + 17 + 17 + ${gcm.version} + + 1.3.0 + + 4.0.0-SNAPSHOT + + + + + + gov.hhs.aspr.ms + gcm + ${gcm.version} + + + + + + Shawn Hatch + Leidos + https://www.leidos.com + + + + + + + org.codehaus.mojo + flatten-maven-plugin + ${flatten-maven-plugin.version} + + + + flatten + process-resources + + flatten + + + true + + + + + flatten.clean + clean + + clean + + + + + + + + diff --git a/tutorials/lessons/lesson_06_plugin_dependencies/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_6.java b/tutorials/lessons/lesson_06_plugin_dependencies/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_6.java new file mode 100644 index 000000000..727bc5176 --- /dev/null +++ b/tutorials/lessons/lesson_06_plugin_dependencies/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_6.java @@ -0,0 +1,70 @@ +package gov.hhs.aspr.ms.gcm.lessons; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.family.FamilyDataManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.ModelActor; +import gov.hhs.aspr.ms.gcm.lessons.plugins.people.PersonDataManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.VaccinationDataManager; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.nucleus.SimplePluginId; +import gov.hhs.aspr.ms.gcm.nucleus.Simulation; + +public final class Example_6 { + + private Example_6() { + } + + /* + * start code_ref=plugin_dependencies_connecting_the_plugins|code_cap=The + * people, vaccine, family and model plugins are contributed to the simulation. + * On execution, the model plugin's single actor schedules the vaccination of + * each person as well as a few random removals of people from the simulation. + */ + public static void main(String[] args) { + + PluginId peoplePluginId = new SimplePluginId("people plugin"); + Plugin peoplePlugin = Plugin.builder()// + .setPluginId(peoplePluginId)// + .setInitializer(pluginContext -> { + pluginContext.addDataManager(new PersonDataManager()); + })// + .build(); + + PluginId vaccinePluginId = new SimplePluginId("vaccine plugin"); + Plugin vaccinePlugin = Plugin.builder()// + .setPluginId(vaccinePluginId)// + .addPluginDependency(peoplePluginId)// + .setInitializer(pluginContext -> { + pluginContext.addDataManager(new VaccinationDataManager()); + })// + .build(); + + PluginId familyPluginId = new SimplePluginId("family plugin"); + Plugin familyPlugin = Plugin.builder()// + .setPluginId(familyPluginId)// + .addPluginDependency(peoplePluginId)// + .setInitializer(pluginContext -> { + pluginContext.addDataManager(new FamilyDataManager()); + })// + .build(); + + PluginId modelPluginId = new SimplePluginId("model plugin"); + Plugin modelPlugin = Plugin.builder()// + .setPluginId(modelPluginId)// + .setInitializer(pluginContext -> { + pluginContext.addActor(new ModelActor()::init); + + })// + .build(); + + Simulation.builder()// + .addPlugin(vaccinePlugin)// + .addPlugin(familyPlugin)// + .addPlugin(peoplePlugin)// + .addPlugin(modelPlugin)// + .build()// + .execute(); + } + /* end */ + +} diff --git a/tutorials/lessons/lesson_06_plugin_dependencies/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/family/FamilyDataManager.java b/tutorials/lessons/lesson_06_plugin_dependencies/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/family/FamilyDataManager.java new file mode 100644 index 000000000..f459c5243 --- /dev/null +++ b/tutorials/lessons/lesson_06_plugin_dependencies/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/family/FamilyDataManager.java @@ -0,0 +1,84 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.family; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.people.PersonDataManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.people.PersonId; +import gov.hhs.aspr.ms.gcm.lessons.plugins.people.PersonRemovalEvent; +import gov.hhs.aspr.ms.gcm.nucleus.DataManager; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; + +/* start code_ref=plugin_dependencies_family_data_manager|code_cap=The family data manager manages families, their person members and various information methods.*/ +public final class FamilyDataManager extends DataManager { + + private int masterFamilyId; + private Map> familyMap = new LinkedHashMap<>(); + private Map personMap = new LinkedHashMap<>(); + private PersonDataManager personDataManager; + + @Override + public void init(DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + personDataManager = dataManagerContext.getDataManager(PersonDataManager.class); + dataManagerContext.subscribe(PersonRemovalEvent.class, this::handlePersonRemovalEvent); + } + + private void handlePersonRemovalEvent(DataManagerContext dataManagerContext, + PersonRemovalEvent personRemovalEvent) { + PersonId personId = personRemovalEvent.getPersonId(); + FamilyId familyId = personMap.remove(personId); + if (familyId != null) { + familyMap.get(familyId).remove(personId); + } + System.out.println( + "Family Data Manager is removing person " + personId + " at time = " + dataManagerContext.getTime()); + } + + public FamilyId addFamily() { + FamilyId familyId = new FamilyId(masterFamilyId++); + familyMap.put(familyId, new LinkedHashSet<>()); + return familyId; + } + + public boolean familyExists(FamilyId familyId) { + return familyMap.keySet().contains(familyId); + } + + public List getFamilyMembers(FamilyId familyId) { + if (!familyExists(familyId)) { + throw new RuntimeException("unknown family " + familyId); + } + return new ArrayList<>(familyMap.get(familyId)); + } + + public Optional getFamilyId(PersonId personId) { + if (!personDataManager.personExists(personId)) { + throw new RuntimeException("unknown person " + personId); + } + FamilyId familyId = personMap.get(personId); + return Optional.ofNullable(familyId); + } + + public void addFamilyMember(PersonId personId, FamilyId familyId) { + if (!personDataManager.personExists(personId)) { + throw new RuntimeException("unknown person " + personId); + } + if (!familyExists(familyId)) { + throw new RuntimeException("unknown family " + familyId); + } + FamilyId currentFamilyId = personMap.get(personId); + if (currentFamilyId != null) { + throw new RuntimeException("person " + personId + " is already assigned to family " + currentFamilyId); + } + familyMap.get(familyId).add(personId); + personMap.put(personId, familyId); + } + +} +/* end */ diff --git a/tutorials/lessons/lesson_06_plugin_dependencies/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/family/FamilyId.java b/tutorials/lessons/lesson_06_plugin_dependencies/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/family/FamilyId.java new file mode 100644 index 000000000..a2d195b28 --- /dev/null +++ b/tutorials/lessons/lesson_06_plugin_dependencies/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/family/FamilyId.java @@ -0,0 +1,49 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.family; + +import net.jcip.annotations.Immutable; + +@Immutable +/* start code_ref=plugin_dependencies_defining_a_family_id|code_cap=The family id, like the person id, simply wraps an int.*/ +public final class FamilyId implements Comparable { + + private final int id; + + public FamilyId(int id) { + this.id = id; + } + + public int getValue() { + return id; + } + + @Override + public int compareTo(FamilyId familyId) { + return Integer.compare(id, familyId.id); + } + + @Override + public int hashCode() { + return id; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof FamilyId)) { + return false; + } + FamilyId other = (FamilyId) obj; + if (id != other.id) { + return false; + } + return true; + } + + @Override + public String toString() { + return Integer.toString(id); + } +} +/* end */ diff --git a/tutorials/lessons/lesson_06_plugin_dependencies/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelActor.java b/tutorials/lessons/lesson_06_plugin_dependencies/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelActor.java new file mode 100644 index 000000000..e20cb342c --- /dev/null +++ b/tutorials/lessons/lesson_06_plugin_dependencies/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelActor.java @@ -0,0 +1,77 @@ + +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.family.FamilyDataManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.family.FamilyId; +import gov.hhs.aspr.ms.gcm.lessons.plugins.people.PersonDataManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.people.PersonId; +import gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.VaccinationDataManager; +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; + +public final class ModelActor { + + public void init(ActorContext actorContext) { + + // get the data managers that will be needed to add people and families + PersonDataManager personDataManager = actorContext.getDataManager(PersonDataManager.class); + FamilyDataManager familyDataManager = actorContext.getDataManager(FamilyDataManager.class); + + // get a source of randomness + Random random = new Random(123L); + + // add people in families + for (int i = 0; i < 4; i++) { + FamilyId familyId = familyDataManager.addFamily(); + int familySize = random.nextInt(5); + for (int j = 0; j < familySize; j++) { + PersonId personId = personDataManager.addPerson(); + familyDataManager.addFamilyMember(personId, familyId); + } + } + + // add extra people not in families + for (int i = 0; i < 3; i++) { + personDataManager.addPerson(); + } + + // plan out randomized vaccinations, one person per day + List people = new ArrayList<>(personDataManager.getPeople()); + + // randomize the people + Collections.shuffle(people); + + // schedule the vaccinations + double planTime = 1; + for (PersonId personId : people) { + actorContext.addPlan((context) -> vaccinatePerson(context, personId), planTime++); + } + + // plan the removal of people from the simulation starting at day + // randomize the people + Collections.shuffle(people); + + planTime = 3; + // schedule some person removals + for (PersonId personId : people) { + actorContext.addPlan((c) -> personDataManager.removePerson(personId), planTime++); + } + } + + private void vaccinatePerson(ActorContext actorContext, PersonId personId) { + // The person may have already been removed from the simulation, so we + // check that before trying to vaccinate them. + PersonDataManager personDataManager = actorContext.getDataManager(PersonDataManager.class); + if (personDataManager.personExists(personId)) { + VaccinationDataManager vaccinationDataManager = actorContext.getDataManager(VaccinationDataManager.class); + vaccinationDataManager.vaccinatePerson(personId); + System.out.println("Person " + personId + " was vaccinated at time = " + actorContext.getTime()); + } else { + System.out.println("Failed to vaccinate Person " + personId + " at time = " + actorContext.getTime()); + } + } +} diff --git a/tutorials/lessons/lesson_06_plugin_dependencies/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/people/PersonDataManager.java b/tutorials/lessons/lesson_06_plugin_dependencies/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/people/PersonDataManager.java new file mode 100644 index 000000000..488bf9067 --- /dev/null +++ b/tutorials/lessons/lesson_06_plugin_dependencies/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/people/PersonDataManager.java @@ -0,0 +1,57 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.people; + +import java.util.LinkedHashSet; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.nucleus.DataManager; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; +import gov.hhs.aspr.ms.gcm.nucleus.Event; + +/* start code_ref=plugin_dependencies_defining_a_person_data_manager|code_cap=The PersonDataManager manages people by adding people, removing people and releasing person removal events.*/ +public final class PersonDataManager extends DataManager { + + private int masterPersonId; + + private Set people = new LinkedHashSet<>(); + + private DataManagerContext dataManagerContext; + + @Override + public void init(DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + this.dataManagerContext = dataManagerContext; + dataManagerContext.subscribe(PersonRemovalMutationEvent.class, this::handlePersonRemovalMutationEvent); + } + + public PersonId addPerson() { + PersonId personId = new PersonId(masterPersonId++); + people.add(personId); + return personId; + } + + public boolean personExists(PersonId personId) { + return people.contains(personId); + } + + public Set getPeople() { + return new LinkedHashSet<>(people); + } + + private static record PersonRemovalMutationEvent(PersonId personId) implements Event { + } + + public void removePerson(PersonId personId) { + dataManagerContext.releaseMutationEvent(new PersonRemovalMutationEvent(personId)); + } + + private void handlePersonRemovalMutationEvent(DataManagerContext dataManagerContext, + PersonRemovalMutationEvent personRemovalMutationEvent) { + PersonId personId = personRemovalMutationEvent.personId(); + if (!personExists(personId)) { + throw new RuntimeException("person " + personId + " does not exist"); + } + people.remove(personId); + dataManagerContext.releaseObservationEvent(new PersonRemovalEvent(personId)); + } +} +/* end */ \ No newline at end of file diff --git a/tutorials/lessons/lesson_06_plugin_dependencies/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/people/PersonId.java b/tutorials/lessons/lesson_06_plugin_dependencies/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/people/PersonId.java new file mode 100644 index 000000000..4896975be --- /dev/null +++ b/tutorials/lessons/lesson_06_plugin_dependencies/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/people/PersonId.java @@ -0,0 +1,49 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.people; + +import net.jcip.annotations.Immutable; + +@Immutable +/* start code_ref=plugin_dependencies_defining_a_person_id|code_cap=The PersonId class defines people and wraps an int value.*/ +public final class PersonId implements Comparable { + + private final int id; + + public PersonId(int id) { + this.id = id; + } + + public int getValue() { + return id; + } + + @Override + public int compareTo(PersonId personId) { + return Integer.compare(id, personId.id); + } + + @Override + public int hashCode() { + return id; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof PersonId)) { + return false; + } + PersonId other = (PersonId) obj; + if (id != other.id) { + return false; + } + return true; + } + + @Override + public String toString() { + return Integer.toString(id); + } +} +/* end */ diff --git a/tutorials/lessons/lesson_06_plugin_dependencies/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/people/PersonRemovalEvent.java b/tutorials/lessons/lesson_06_plugin_dependencies/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/people/PersonRemovalEvent.java new file mode 100644 index 000000000..713dc7f8b --- /dev/null +++ b/tutorials/lessons/lesson_06_plugin_dependencies/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/people/PersonRemovalEvent.java @@ -0,0 +1,21 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.people; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import net.jcip.annotations.Immutable; + +@Immutable +/* start code_ref=plugin_dependencies_person_removal_event|code_cap=An event signifying that a person has been removed from the simulation.*/ +public final class PersonRemovalEvent implements Event { + + private final PersonId personId; + + public PersonRemovalEvent(PersonId personId) { + this.personId = personId; + } + + public PersonId getPersonId() { + return personId; + } + +} +/* end */ diff --git a/tutorials/lessons/lesson_06_plugin_dependencies/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/VaccinationDataManager.java b/tutorials/lessons/lesson_06_plugin_dependencies/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/VaccinationDataManager.java new file mode 100644 index 000000000..6d739caed --- /dev/null +++ b/tutorials/lessons/lesson_06_plugin_dependencies/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/VaccinationDataManager.java @@ -0,0 +1,59 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine; + +import java.util.LinkedHashSet; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.people.PersonDataManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.people.PersonId; +import gov.hhs.aspr.ms.gcm.lessons.plugins.people.PersonRemovalEvent; +import gov.hhs.aspr.ms.gcm.nucleus.DataManager; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; + +/* start code_ref=plugin_dependencies_vaccine_data_manager|code_cap=The vaccination manager tracks the vaccination status of each person, reacting to the person removal as needed.*/ +public final class VaccinationDataManager extends DataManager { + + private Set vaccinatedPeople = new LinkedHashSet<>(); + + private PersonDataManager personDataManager; + + @Override + public void init(DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + dataManagerContext.subscribe(PersonRemovalEvent.class, this::handlePersonRemovalEvent); + personDataManager = dataManagerContext.getDataManager(PersonDataManager.class); + } + + private void handlePersonRemovalEvent(DataManagerContext dataManagerContext, + PersonRemovalEvent personRemovalEvent) { + PersonId personId = personRemovalEvent.getPersonId(); + vaccinatedPeople.remove(personId); + System.out.println("Vaccination Data Manager is removing person " + personId + " at time = " + + dataManagerContext.getTime()); + } + + public Set getVaccinatedPeople() { + return new LinkedHashSet<>(vaccinatedPeople); + } + + public Set getUnvaccinatedPeople() { + Set people = personDataManager.getPeople(); + people.removeAll(vaccinatedPeople); + return people; + } + + public boolean isPersonVaccinated(PersonId personId) { + if (!personDataManager.personExists(personId)) { + throw new RuntimeException("unknown person " + personId); + } + return vaccinatedPeople.contains(personId); + } + + public void vaccinatePerson(PersonId personId) { + if (!personDataManager.personExists(personId)) { + throw new RuntimeException("unknown person " + personId); + } + vaccinatedPeople.add(personId); + } + +} +/* end */ diff --git a/tutorials/lessons/lesson_06_plugin_dependencies/src/main/resources/output/plugin_dependencies_output.txt b/tutorials/lessons/lesson_06_plugin_dependencies/src/main/resources/output/plugin_dependencies_output.txt new file mode 100644 index 000000000..9b7d1e1bb --- /dev/null +++ b/tutorials/lessons/lesson_06_plugin_dependencies/src/main/resources/output/plugin_dependencies_output.txt @@ -0,0 +1,32 @@ +/* start code_ref=plugin_dependencies_output|code_cap=The output shows the combined reporting from actors and data managers as they report vaccination progress.*/ +Person 7 was vaccinated at time = 1.0 +Person 2 was vaccinated at time = 2.0 +Person 5 was vaccinated at time = 3.0 +Vaccination Data Manager is removing person 2 at time = 3.0 +Family Data Manager is removing person 2 at time = 3.0 +Person 6 was vaccinated at time = 4.0 +Vaccination Data Manager is removing person 1 at time = 4.0 +Family Data Manager is removing person 1 at time = 4.0 +Person 3 was vaccinated at time = 5.0 +Vaccination Data Manager is removing person 8 at time = 5.0 +Family Data Manager is removing person 8 at time = 5.0 +Failed to vaccinate Person 1 at time = 6.0 +Vaccination Data Manager is removing person 6 at time = 6.0 +Family Data Manager is removing person 6 at time = 6.0 +Person 0 was vaccinated at time = 7.0 +Vaccination Data Manager is removing person 7 at time = 7.0 +Family Data Manager is removing person 7 at time = 7.0 +Person 9 was vaccinated at time = 8.0 +Vaccination Data Manager is removing person 5 at time = 8.0 +Family Data Manager is removing person 5 at time = 8.0 +Failed to vaccinate Person 8 at time = 9.0 +Vaccination Data Manager is removing person 3 at time = 9.0 +Family Data Manager is removing person 3 at time = 9.0 +Person 4 was vaccinated at time = 10.0 +Vaccination Data Manager is removing person 4 at time = 10.0 +Family Data Manager is removing person 4 at time = 10.0 +Vaccination Data Manager is removing person 0 at time = 11.0 +Family Data Manager is removing person 0 at time = 11.0 +Vaccination Data Manager is removing person 9 at time = 12.0 +Family Data Manager is removing person 9 at time = 12.0 +/* end */ \ No newline at end of file diff --git a/tutorials/lessons/lesson_07_plugin_dependency_graph/pom.xml b/tutorials/lessons/lesson_07_plugin_dependency_graph/pom.xml new file mode 100644 index 000000000..dffbca6fa --- /dev/null +++ b/tutorials/lessons/lesson_07_plugin_dependency_graph/pom.xml @@ -0,0 +1,79 @@ + + 4.0.0 + + + ASPR + https://www.phe.gov + + + + gov.hhs.aspr.ms.gcm.tutorials + lesson_07_plugin_dependency_graph + ${revision} + jar + gcm lesson 07 plugin dependency graph + Tutorial introducing the plugin dependency acyclic, directed graph + + + + UTF-8 + 17 + 17 + ${gcm.version} + + 1.3.0 + + 4.0.0-SNAPSHOT + + + + + + gov.hhs.aspr.ms + gcm + ${gcm.version} + + + + + + Shawn Hatch + Leidos + https://www.leidos.com + + + + + + + org.codehaus.mojo + flatten-maven-plugin + ${flatten-maven-plugin.version} + + + + flatten + process-resources + + flatten + + + true + + + + + flatten.clean + clean + + clean + + + + + + + + diff --git a/tutorials/lessons/lesson_07_plugin_dependency_graph/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_7.java b/tutorials/lessons/lesson_07_plugin_dependency_graph/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_7.java new file mode 100644 index 000000000..1064a6355 --- /dev/null +++ b/tutorials/lessons/lesson_07_plugin_dependency_graph/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_7.java @@ -0,0 +1,67 @@ +package gov.hhs.aspr.ms.gcm.lessons; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.family.FamilyDataManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.ModelActor; +import gov.hhs.aspr.ms.gcm.lessons.plugins.people.PersonDataManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.VaccinationDataManager; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.nucleus.SimplePluginId; +import gov.hhs.aspr.ms.gcm.nucleus.Simulation; + +public final class Example_7 { + + private Example_7() { + } + + /** + * Emphasize the lack of order, and the plugin dependencies + */ + public static void main(String[] args) { + + PluginId peoplePluginId = new SimplePluginId("people plugin"); + PluginId familyPluginId = new SimplePluginId("family plugin"); + PluginId vaccinePluginId = new SimplePluginId("vaccine plugin"); + + Plugin peoplePlugin = Plugin.builder()// + .setPluginId(peoplePluginId)// + .setInitializer(pluginContext -> { + pluginContext.addDataManager(new PersonDataManager()); + })// + .build(); + + Plugin vaccinePlugin = Plugin.builder()// + .setPluginId(vaccinePluginId)// + .addPluginDependency(peoplePluginId)// + .addPluginDependency(familyPluginId)// + .setInitializer(pluginContext -> { + pluginContext.addDataManager(new VaccinationDataManager()); + })// + .build(); + + Plugin familyPlugin = Plugin.builder()// + .setPluginId(familyPluginId)// + .addPluginDependency(peoplePluginId)// + .setInitializer(pluginContext -> { + pluginContext.addDataManager(new FamilyDataManager()); + })// + .build(); + + PluginId modelPluginId = new SimplePluginId("model plugin"); + Plugin modelPlugin = Plugin.builder()// + .setPluginId(modelPluginId)// + .setInitializer(pluginContext -> { + pluginContext.addActor(new ModelActor()::init); + + })// + .build(); + + Simulation.builder()// + .addPlugin(vaccinePlugin)// + .addPlugin(familyPlugin)// + .addPlugin(peoplePlugin)// + .addPlugin(modelPlugin)// + .build()// + .execute(); + } +} diff --git a/tutorials/lessons/lesson_07_plugin_dependency_graph/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/family/FamilyDataManager.java b/tutorials/lessons/lesson_07_plugin_dependency_graph/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/family/FamilyDataManager.java new file mode 100644 index 000000000..7967043f2 --- /dev/null +++ b/tutorials/lessons/lesson_07_plugin_dependency_graph/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/family/FamilyDataManager.java @@ -0,0 +1,85 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.family; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.people.PersonDataManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.people.PersonId; +import gov.hhs.aspr.ms.gcm.lessons.plugins.people.PersonRemovalEvent; +import gov.hhs.aspr.ms.gcm.nucleus.DataManager; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; + +public final class FamilyDataManager extends DataManager { + + private int masterFamilyId; + private Map> familyMap = new LinkedHashMap<>(); + private Map personMap = new LinkedHashMap<>(); + private PersonDataManager personDataManager; + + @Override + public void init(DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + personDataManager = dataManagerContext.getDataManager(PersonDataManager.class); + + dataManagerContext.subscribe(PersonRemovalEvent.class, this::handlePersonRemovalEvent); + } + + private void handlePersonRemovalEvent(DataManagerContext dataManagerContext, + PersonRemovalEvent personRemovalEvent) { + PersonId personId = personRemovalEvent.getPersonId(); + FamilyId familyId = personMap.remove(personId); + if (familyId != null) { + familyMap.get(familyId).remove(personId); + } + System.out.println( + "Family Data Manager is removing person " + personId + " at time = " + dataManagerContext.getTime()); + } + + public FamilyId addFamily() { + FamilyId familyId = new FamilyId(masterFamilyId++); + familyMap.put(familyId, new LinkedHashSet<>()); + return familyId; + } + + public boolean familyExists(FamilyId familyId) { + return familyMap.keySet().contains(familyId); + } + + public List getFamilyMembers(FamilyId familyId) { + if (!familyExists(familyId)) { + throw new RuntimeException("unknown family " + familyId); + } + return new ArrayList<>(familyMap.get(familyId)); + } + + public Optional getFamilyId(PersonId personId) { + if (!personDataManager.personExists(personId)) { + throw new RuntimeException("unknown person " + personId); + } + FamilyId familyId = personMap.get(personId); + return Optional.ofNullable(familyId); + } + + public void addFamilyMember(PersonId personId, FamilyId familyId) { + if (!personDataManager.personExists(personId)) { + throw new RuntimeException("unknown person " + personId); + } + if (!familyExists(familyId)) { + throw new RuntimeException("unknown family " + familyId); + } + + FamilyId currentFamilyId = personMap.get(personId); + if (currentFamilyId != null) { + throw new RuntimeException("person " + personId + " is already assigned to family " + currentFamilyId); + } + familyMap.get(familyId).add(personId); + personMap.put(personId, familyId); + + } + +} diff --git a/tutorials/lessons/lesson_07_plugin_dependency_graph/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/family/FamilyId.java b/tutorials/lessons/lesson_07_plugin_dependency_graph/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/family/FamilyId.java new file mode 100644 index 000000000..decf5ad1b --- /dev/null +++ b/tutorials/lessons/lesson_07_plugin_dependency_graph/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/family/FamilyId.java @@ -0,0 +1,47 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.family; + +import net.jcip.annotations.Immutable; + +@Immutable +public final class FamilyId implements Comparable { + + private final int id; + + public FamilyId(int id) { + this.id = id; + } + + public int getValue() { + return id; + } + + @Override + public int compareTo(FamilyId familyId) { + return Integer.compare(id, familyId.id); + } + + @Override + public int hashCode() { + return id; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof FamilyId)) { + return false; + } + FamilyId other = (FamilyId) obj; + if (id != other.id) { + return false; + } + return true; + } + + @Override + public String toString() { + return Integer.toString(id); + } +} diff --git a/tutorials/lessons/lesson_07_plugin_dependency_graph/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelActor.java b/tutorials/lessons/lesson_07_plugin_dependency_graph/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelActor.java new file mode 100644 index 000000000..e20cb342c --- /dev/null +++ b/tutorials/lessons/lesson_07_plugin_dependency_graph/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelActor.java @@ -0,0 +1,77 @@ + +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.family.FamilyDataManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.family.FamilyId; +import gov.hhs.aspr.ms.gcm.lessons.plugins.people.PersonDataManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.people.PersonId; +import gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.VaccinationDataManager; +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; + +public final class ModelActor { + + public void init(ActorContext actorContext) { + + // get the data managers that will be needed to add people and families + PersonDataManager personDataManager = actorContext.getDataManager(PersonDataManager.class); + FamilyDataManager familyDataManager = actorContext.getDataManager(FamilyDataManager.class); + + // get a source of randomness + Random random = new Random(123L); + + // add people in families + for (int i = 0; i < 4; i++) { + FamilyId familyId = familyDataManager.addFamily(); + int familySize = random.nextInt(5); + for (int j = 0; j < familySize; j++) { + PersonId personId = personDataManager.addPerson(); + familyDataManager.addFamilyMember(personId, familyId); + } + } + + // add extra people not in families + for (int i = 0; i < 3; i++) { + personDataManager.addPerson(); + } + + // plan out randomized vaccinations, one person per day + List people = new ArrayList<>(personDataManager.getPeople()); + + // randomize the people + Collections.shuffle(people); + + // schedule the vaccinations + double planTime = 1; + for (PersonId personId : people) { + actorContext.addPlan((context) -> vaccinatePerson(context, personId), planTime++); + } + + // plan the removal of people from the simulation starting at day + // randomize the people + Collections.shuffle(people); + + planTime = 3; + // schedule some person removals + for (PersonId personId : people) { + actorContext.addPlan((c) -> personDataManager.removePerson(personId), planTime++); + } + } + + private void vaccinatePerson(ActorContext actorContext, PersonId personId) { + // The person may have already been removed from the simulation, so we + // check that before trying to vaccinate them. + PersonDataManager personDataManager = actorContext.getDataManager(PersonDataManager.class); + if (personDataManager.personExists(personId)) { + VaccinationDataManager vaccinationDataManager = actorContext.getDataManager(VaccinationDataManager.class); + vaccinationDataManager.vaccinatePerson(personId); + System.out.println("Person " + personId + " was vaccinated at time = " + actorContext.getTime()); + } else { + System.out.println("Failed to vaccinate Person " + personId + " at time = " + actorContext.getTime()); + } + } +} diff --git a/tutorials/lessons/lesson_07_plugin_dependency_graph/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/people/PersonDataManager.java b/tutorials/lessons/lesson_07_plugin_dependency_graph/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/people/PersonDataManager.java new file mode 100644 index 000000000..d21af53de --- /dev/null +++ b/tutorials/lessons/lesson_07_plugin_dependency_graph/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/people/PersonDataManager.java @@ -0,0 +1,58 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.people; + +import java.util.LinkedHashSet; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.nucleus.DataManager; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; +import gov.hhs.aspr.ms.gcm.nucleus.Event; + +public final class PersonDataManager extends DataManager { + + private int masterPersonId; + + private Set people = new LinkedHashSet<>(); + + private DataManagerContext dataManagerContext; + + @Override + public void init(DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + this.dataManagerContext = dataManagerContext; + + dataManagerContext.subscribe(PersonRemovalMutationEvent.class, this::handlePersonRemovalMutationEvent); + + } + + public PersonId addPerson() { + PersonId personId = new PersonId(masterPersonId++); + people.add(personId); + return personId; + } + + public boolean personExists(PersonId personId) { + return people.contains(personId); + } + + public Set getPeople() { + return new LinkedHashSet<>(people); + } + + private static record PersonRemovalMutationEvent(PersonId personId) implements Event { + } + + public void removePerson(PersonId personId) { + dataManagerContext.releaseMutationEvent(new PersonRemovalMutationEvent(personId)); + } + + private void handlePersonRemovalMutationEvent(DataManagerContext dataManagerContext, + PersonRemovalMutationEvent personRemovalMutationEvent) { + PersonId personId = personRemovalMutationEvent.personId(); + if (!personExists(personId)) { + throw new RuntimeException("person " + personId + " does not exist"); + } + people.remove(personId); + dataManagerContext.releaseObservationEvent(new PersonRemovalEvent(personId)); + } + +} diff --git a/tutorials/lessons/lesson_07_plugin_dependency_graph/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/people/PersonId.java b/tutorials/lessons/lesson_07_plugin_dependency_graph/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/people/PersonId.java new file mode 100644 index 000000000..cefae1736 --- /dev/null +++ b/tutorials/lessons/lesson_07_plugin_dependency_graph/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/people/PersonId.java @@ -0,0 +1,47 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.people; + +import net.jcip.annotations.Immutable; + +@Immutable +public final class PersonId implements Comparable { + + private final int id; + + public PersonId(int id) { + this.id = id; + } + + public int getValue() { + return id; + } + + @Override + public int compareTo(PersonId personId) { + return Integer.compare(id, personId.id); + } + + @Override + public int hashCode() { + return id; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof PersonId)) { + return false; + } + PersonId other = (PersonId) obj; + if (id != other.id) { + return false; + } + return true; + } + + @Override + public String toString() { + return Integer.toString(id); + } +} diff --git a/tutorials/lessons/lesson_07_plugin_dependency_graph/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/people/PersonRemovalEvent.java b/tutorials/lessons/lesson_07_plugin_dependency_graph/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/people/PersonRemovalEvent.java new file mode 100644 index 000000000..4bdcc5cf5 --- /dev/null +++ b/tutorials/lessons/lesson_07_plugin_dependency_graph/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/people/PersonRemovalEvent.java @@ -0,0 +1,19 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.people; + +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import net.jcip.annotations.Immutable; + +@Immutable +public final class PersonRemovalEvent implements Event { + + private final PersonId personId; + + public PersonRemovalEvent(PersonId personId) { + this.personId = personId; + } + + public PersonId getPersonId() { + return personId; + } + +} diff --git a/tutorials/lessons/lesson_07_plugin_dependency_graph/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/VaccinationDataManager.java b/tutorials/lessons/lesson_07_plugin_dependency_graph/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/VaccinationDataManager.java new file mode 100644 index 000000000..ae51956a9 --- /dev/null +++ b/tutorials/lessons/lesson_07_plugin_dependency_graph/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/VaccinationDataManager.java @@ -0,0 +1,85 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.family.FamilyDataManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.family.FamilyId; +import gov.hhs.aspr.ms.gcm.lessons.plugins.people.PersonDataManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.people.PersonId; +import gov.hhs.aspr.ms.gcm.lessons.plugins.people.PersonRemovalEvent; +import gov.hhs.aspr.ms.gcm.nucleus.DataManager; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; + +public final class VaccinationDataManager extends DataManager { + + private Set vaccinatedPeople = new LinkedHashSet<>(); + + private PersonDataManager personDataManager; + private FamilyDataManager familyDataManager; + + @Override + public void init(DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + dataManagerContext.subscribe(PersonRemovalEvent.class, this::handlePersonRemovalEvent); + personDataManager = dataManagerContext.getDataManager(PersonDataManager.class); + familyDataManager = dataManagerContext.getDataManager(FamilyDataManager.class); + } + + private void handlePersonRemovalEvent(DataManagerContext dataManagerContext, + PersonRemovalEvent personRemovalEvent) { + PersonId personId = personRemovalEvent.getPersonId(); + vaccinatedPeople.remove(personId); + System.out.println("Vaccination Data Manager is removing person " + personId + " at time = " + + dataManagerContext.getTime()); + } + + public Set getVaccinatedPeople() { + return new LinkedHashSet<>(vaccinatedPeople); + } + + public Set getUnvaccinatedPeople() { + Set people = personDataManager.getPeople(); + people.removeAll(vaccinatedPeople); + return people; + } + + public boolean isPersonVaccinated(PersonId personId) { + if (!personDataManager.personExists(personId)) { + throw new RuntimeException("unknown person " + personId); + } + return vaccinatedPeople.contains(personId); + } + + public void vaccinatePerson(PersonId personId) { + if (!personDataManager.personExists(personId)) { + throw new RuntimeException("unknown person " + personId); + } + + vaccinatedPeople.add(personId); + + } + + /* start code_ref=dag_dependent_query|code_cap=By adding a plugin dependencies on the people and family plugins, the vaccine data manager can now answer questions about the vaccine status of family members*/ + public List getUnvaccinatedFamilyMembers(PersonId personId) { + if (!personDataManager.personExists(personId)) { + throw new RuntimeException("unknown person " + personId); + } + List result = new ArrayList<>(); + Optional optional = familyDataManager.getFamilyId(personId); + if (optional.isPresent()) { + FamilyId familyId = optional.get(); + List familyMembers = familyDataManager.getFamilyMembers(familyId); + for (PersonId familyMemeberId : familyMembers) { + if (!isPersonVaccinated(familyMemeberId)) { + result.add(personId); + } + } + } + return result; + } + /* end */ +} diff --git a/tutorials/lessons/lesson_08_plugin_data/pom.xml b/tutorials/lessons/lesson_08_plugin_data/pom.xml new file mode 100644 index 000000000..7a53530d6 --- /dev/null +++ b/tutorials/lessons/lesson_08_plugin_data/pom.xml @@ -0,0 +1,79 @@ + + 4.0.0 + + + ASPR + https://www.phe.gov + + + + gov.hhs.aspr.ms.gcm.tutorials + lesson_08_plugin_data + ${revision} + jar + gcm lesson 08 plugin data + Tutorial introducing the plugin data + + + + UTF-8 + 17 + 17 + ${gcm.version} + + 1.3.0 + + 4.0.0-SNAPSHOT + + + + + + gov.hhs.aspr.ms + gcm + ${gcm.version} + + + + + + Shawn Hatch + Leidos + https://www.leidos.com + + + + + + + org.codehaus.mojo + flatten-maven-plugin + ${flatten-maven-plugin.version} + + + + flatten + process-resources + + flatten + + + true + + + + + flatten.clean + clean + + clean + + + + + + + + diff --git a/tutorials/lessons/lesson_08_plugin_data/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_8.java b/tutorials/lessons/lesson_08_plugin_data/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_8.java new file mode 100644 index 000000000..0d2577406 --- /dev/null +++ b/tutorials/lessons/lesson_08_plugin_data/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_8.java @@ -0,0 +1,50 @@ +package gov.hhs.aspr.ms.gcm.lessons; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.disease.DiseasePlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.disease.DiseasePluginData; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.ModelPlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.policy.PolicyPlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.policy.PolicyPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.Simulation; + +/* start code_ref=plugin_data_example_runner|code_cap=Example 8 executes more succinctly by use of static plugin classes. */ +public final class Example_8 { + + private Example_8() { + } + + private static DiseasePluginData getDiseasePluginData() { + return DiseasePluginData.builder()// + .setR0(1.5)// + .setAsymptomaticDays(4.0)// + .setSymptomaticDays(12.0)// + .build(); + } + + private static PolicyPluginData getPolicyPluginData() { + return PolicyPluginData.builder()// + .setDistributeVaccineLocally(true)// + .setSchoolClosingInfectionRate(0.05)// + .build(); + } + + public static void main(String[] args) { + + DiseasePluginData diseasePluginData = getDiseasePluginData(); + Plugin diseasePlugin = DiseasePlugin.getDiseasePlugin(diseasePluginData); + + PolicyPluginData policyPluginData = getPolicyPluginData(); + Plugin policyPlugin = PolicyPlugin.getPolicyPlugin(policyPluginData); + + Plugin modelPlugin = ModelPlugin.getModelPlugin(); + + Simulation.builder()// + .addPlugin(diseasePlugin)// + .addPlugin(modelPlugin)// + .addPlugin(policyPlugin)// + .build()// + .execute(); + } +} +/* end */ diff --git a/tutorials/lessons/lesson_08_plugin_data/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseaseDataManager.java b/tutorials/lessons/lesson_08_plugin_data/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseaseDataManager.java new file mode 100644 index 000000000..b4e823e8d --- /dev/null +++ b/tutorials/lessons/lesson_08_plugin_data/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseaseDataManager.java @@ -0,0 +1,54 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.disease; + +import gov.hhs.aspr.ms.gcm.nucleus.DataManager; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; + +public final class DiseaseDataManager extends DataManager { + + private double r0 = 7; + + private double asymptomaticDays; + + private double symptomaticDays; + + private final DiseasePluginData diseasePluginData; + + public DiseaseDataManager(DiseasePluginData diseasePluginData) { + this.diseasePluginData = diseasePluginData; + } + + @Override + public void init(DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + + r0 = diseasePluginData.getR0(); + asymptomaticDays = diseasePluginData.getAsymptomaticDays(); + symptomaticDays = diseasePluginData.getSymptomaticDays(); + + } + + public double getR0() { + return r0; + } + + public double getAsymptomaticDays() { + return asymptomaticDays; + } + + public double getSymptomaticDays() { + return symptomaticDays; + } + + public void setR0(double r0) { + this.r0 = r0; + } + + public void setAsymptomaticDays(double asymptomaticDays) { + this.asymptomaticDays = asymptomaticDays; + } + + public void setSymptomaticDays(double symptomaticDays) { + this.symptomaticDays = symptomaticDays; + } + +} diff --git a/tutorials/lessons/lesson_08_plugin_data/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseasePlugin.java b/tutorials/lessons/lesson_08_plugin_data/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseasePlugin.java new file mode 100644 index 000000000..2f2d17949 --- /dev/null +++ b/tutorials/lessons/lesson_08_plugin_data/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseasePlugin.java @@ -0,0 +1,25 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.disease; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; + +/* start code_ref=plugin_data_plugin|code_cap=The DiseasePlugin class is a static class for creating the disease plugin.*/ +public final class DiseasePlugin { + + private DiseasePlugin() { + + } + + public static Plugin getDiseasePlugin(DiseasePluginData diseasePluginData) { + + return Plugin.builder()// + .addPluginData(diseasePluginData)// + .setPluginId(DiseasePluginId.PLUGIN_ID)// + .setInitializer((pluginContext) -> { + DiseasePluginData pluginData = pluginContext.getPluginData(DiseasePluginData.class).get(); + pluginContext.addDataManager(new DiseaseDataManager(pluginData)); + })// + .build(); + } + +} +/* end */ \ No newline at end of file diff --git a/tutorials/lessons/lesson_08_plugin_data/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseasePluginData.java b/tutorials/lessons/lesson_08_plugin_data/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseasePluginData.java new file mode 100644 index 000000000..166d758dc --- /dev/null +++ b/tutorials/lessons/lesson_08_plugin_data/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseasePluginData.java @@ -0,0 +1,158 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.disease; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.PluginDataBuilder; +import net.jcip.annotations.Immutable; + +@Immutable +public final class DiseasePluginData implements PluginData { + /* start code_ref=plugin_data_internal_data|code_cap=The disease plugin data collects the various general disease properties used to initialize the disease data manager.*/ + private static class Data { + + private double r0; + + private double asymptomaticDays; + + private double symptomaticDays; + + private Data() { + } + + private Data(final Data data) { + r0 = data.r0; + asymptomaticDays = data.asymptomaticDays; + symptomaticDays = data.symptomaticDays; + } + /* end */ + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + long temp; + temp = Double.doubleToLongBits(asymptomaticDays); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(r0); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(symptomaticDays); + result = prime * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + if (Double.doubleToLongBits(asymptomaticDays) != Double.doubleToLongBits(other.asymptomaticDays)) { + return false; + } + if (Double.doubleToLongBits(r0) != Double.doubleToLongBits(other.r0)) { + return false; + } + if (Double.doubleToLongBits(symptomaticDays) != Double.doubleToLongBits(other.symptomaticDays)) { + return false; + } + return true; + } + + } + + /* start code_ref=plugin_data_builder_class|code_cap=The builder class for the immutable disease plugin data class.*/ + public static class Builder implements PluginDataBuilder { + private Data data; + + private Builder(final Data data) { + this.data = data; + } + + @Override + public DiseasePluginData build() { + + return new DiseasePluginData(new Data(data)); + + } + + public Builder setAsymptomaticDays(final double asymptomaticDays) { + data.asymptomaticDays = asymptomaticDays; + return this; + } + + public Builder setR0(final double r0) { + data.r0 = r0; + return this; + } + + public Builder setSymptomaticDays(final double symptomaticDays) { + data.symptomaticDays = symptomaticDays; + return this; + } + } + + public static Builder builder() { + return new Builder(new Data()); + } + /* end */ + + /* start code_ref=plugin_data_private_constructor|code_cap=The disease plugin data is constructed from the collected data in a private constructor.*/ + private final Data data; + + private DiseasePluginData(final Data data) { + this.data = data; + } + + /* end */ + + /* start code_ref=plugin_data_accessor_methods|code_cap=The disease plugin data grants access to its immutable field values.*/ + public double getAsymptomaticDays() { + return data.asymptomaticDays; + } + + public double getR0() { + return data.r0; + } + + public double getSymptomaticDays() { + return data.symptomaticDays; + } + /* end */ + + /* start code_ref=plugin_data_clone_builder|code_cap=The disease plugin data creates a copy of its data and places it in the returned plugin data builder.*/ + @Override + public PluginDataBuilder getCloneBuilder() { + return new Builder(new Data(data)); + } + /* end */ + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof DiseasePluginData)) { + return false; + } + DiseasePluginData other = (DiseasePluginData) obj; + if (data == null) { + if (other.data != null) { + return false; + } + } else if (!data.equals(other.data)) { + return false; + } + return true; + } + +} diff --git a/tutorials/lessons/lesson_08_plugin_data/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseasePluginId.java b/tutorials/lessons/lesson_08_plugin_data/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseasePluginId.java new file mode 100644 index 000000000..9ddbc9b21 --- /dev/null +++ b/tutorials/lessons/lesson_08_plugin_data/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseasePluginId.java @@ -0,0 +1,18 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.disease; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.nucleus.SimplePluginId; + +/** + * Static plugin id implementation for the GlobalsPlugin + * + * + */ +/* start code_ref=plugin_data_plugin_id|code_cap=The plugin id for the disease plugin is implemented as a static constant.*/ +public final class DiseasePluginId implements PluginId { + private DiseasePluginId() { + } + + public final static PluginId PLUGIN_ID = new SimplePluginId("disease plugin id"); +} +/* end */ diff --git a/tutorials/lessons/lesson_08_plugin_data/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelActor.java b/tutorials/lessons/lesson_08_plugin_data/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelActor.java new file mode 100644 index 000000000..6b9fd2b3e --- /dev/null +++ b/tutorials/lessons/lesson_08_plugin_data/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelActor.java @@ -0,0 +1,19 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.disease.DiseaseDataManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.policy.PolicyDataManager; +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; + +public final class ModelActor { + + public void init(ActorContext actorContext) { + DiseaseDataManager diseaseDataManager = actorContext.getDataManager(DiseaseDataManager.class); + System.out.println("r0 = " + diseaseDataManager.getR0()); + System.out.println("asymptomatic days = " + diseaseDataManager.getAsymptomaticDays()); + System.out.println("symptomatic days = " + diseaseDataManager.getSymptomaticDays()); + + PolicyDataManager policyDataManager = actorContext.getDataManager(PolicyDataManager.class); + System.out.println("school closing infection rate = " + policyDataManager.getSchoolClosingInfectionRate()); + System.out.println("distribute vaccine locally = " + policyDataManager.distributeVaccineLocally()); + } +} diff --git a/tutorials/lessons/lesson_08_plugin_data/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java b/tutorials/lessons/lesson_08_plugin_data/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java new file mode 100644 index 000000000..ff58356b9 --- /dev/null +++ b/tutorials/lessons/lesson_08_plugin_data/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java @@ -0,0 +1,21 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; + +public final class ModelPlugin { + + private ModelPlugin() { + + } + + public static Plugin getModelPlugin() { + + return Plugin.builder()// + .setPluginId(ModelPluginId.PLUGIN_ID)// + .setInitializer((c) -> { + c.addActor(new ModelActor()::init); + })// + .build(); + } + +} diff --git a/tutorials/lessons/lesson_08_plugin_data/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java b/tutorials/lessons/lesson_08_plugin_data/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java new file mode 100644 index 000000000..4d0a6a3cd --- /dev/null +++ b/tutorials/lessons/lesson_08_plugin_data/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java @@ -0,0 +1,18 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.nucleus.SimplePluginId; + +/** + * Static plugin id implementation for the GlobalsPlugin + * + * + */ + +public final class ModelPluginId implements PluginId { + private ModelPluginId() { + }; + + public final static PluginId PLUGIN_ID = new SimplePluginId("model plugin id"); + +} diff --git a/tutorials/lessons/lesson_08_plugin_data/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyDataManager.java b/tutorials/lessons/lesson_08_plugin_data/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyDataManager.java new file mode 100644 index 000000000..18b21f7dd --- /dev/null +++ b/tutorials/lessons/lesson_08_plugin_data/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyDataManager.java @@ -0,0 +1,29 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.policy; + +import gov.hhs.aspr.ms.gcm.nucleus.DataManager; + +public final class PolicyDataManager extends DataManager { + private double schoolClosingInfectionRate; + private boolean distributeVaccineLocally; + + public PolicyDataManager(PolicyPluginData policyPluginData) { + + } + + public double getSchoolClosingInfectionRate() { + return schoolClosingInfectionRate; + } + + public void setSchoolClosingInfectionRate(double schoolClosingInfectionRate) { + this.schoolClosingInfectionRate = schoolClosingInfectionRate; + } + + public boolean distributeVaccineLocally() { + return distributeVaccineLocally; + } + + public void setDistributeVaccineLocally(boolean distributeVaccineLocally) { + this.distributeVaccineLocally = distributeVaccineLocally; + } + +} diff --git a/tutorials/lessons/lesson_08_plugin_data/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyPlugin.java b/tutorials/lessons/lesson_08_plugin_data/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyPlugin.java new file mode 100644 index 000000000..3f1e6e8ad --- /dev/null +++ b/tutorials/lessons/lesson_08_plugin_data/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyPlugin.java @@ -0,0 +1,23 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.policy; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; + +public final class PolicyPlugin { + + private PolicyPlugin() { + + } + + public static Plugin getPolicyPlugin(PolicyPluginData policyPluginData) { + + return Plugin.builder()// + .addPluginData(policyPluginData)// + .setPluginId(PolicyPluginId.PLUGIN_ID)// + .setInitializer((c) -> { + PolicyPluginData pluginData = c.getPluginData(PolicyPluginData.class).get(); + c.addDataManager(new PolicyDataManager(pluginData)); + })// + .build(); + } + +} diff --git a/tutorials/lessons/lesson_08_plugin_data/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyPluginData.java b/tutorials/lessons/lesson_08_plugin_data/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyPluginData.java new file mode 100644 index 000000000..dd9f2169b --- /dev/null +++ b/tutorials/lessons/lesson_08_plugin_data/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyPluginData.java @@ -0,0 +1,129 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.policy; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.PluginDataBuilder; +import net.jcip.annotations.Immutable; + +@Immutable +public final class PolicyPluginData implements PluginData { + + private static class Data { + + private double schoolClosingInfectionRate; + private boolean distributeVaccineLocally; + + private Data() { + } + + private Data(final Data data) { + schoolClosingInfectionRate = data.schoolClosingInfectionRate; + distributeVaccineLocally = data.distributeVaccineLocally; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (distributeVaccineLocally ? 1231 : 1237); + long temp; + temp = Double.doubleToLongBits(schoolClosingInfectionRate); + result = prime * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + if (distributeVaccineLocally != other.distributeVaccineLocally) { + return false; + } + if (Double.doubleToLongBits(schoolClosingInfectionRate) != Double + .doubleToLongBits(other.schoolClosingInfectionRate)) { + return false; + } + return true; + } + + } + + public static class Builder implements PluginDataBuilder { + private Data data; + + private Builder(final Data data) { + this.data = data; + } + + @Override + public PolicyPluginData build() { + return new PolicyPluginData(new Data(data)); + } + + public Builder setSchoolClosingInfectionRate(double schoolClosingInfectionRate) { + data.schoolClosingInfectionRate = schoolClosingInfectionRate; + return this; + } + + public Builder setDistributeVaccineLocally(boolean distributeVaccineLocally) { + data.distributeVaccineLocally = distributeVaccineLocally; + return this; + } + + } + + public static Builder builder() { + return new Builder(new Data()); + } + + private final Data data; + + private PolicyPluginData(final Data data) { + this.data = data; + } + + public double getSchoolClosingInfectionRate() { + return data.schoolClosingInfectionRate; + } + + public boolean isDistributeVaccineLocally() { + return data.distributeVaccineLocally; + } + + @Override + public PluginDataBuilder getCloneBuilder() { + return new Builder(new Data(data)); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof PolicyPluginData)) { + return false; + } + PolicyPluginData other = (PolicyPluginData) obj; + if (data == null) { + if (other.data != null) { + return false; + } + } else if (!data.equals(other.data)) { + return false; + } + return true; + } + +} diff --git a/tutorials/lessons/lesson_08_plugin_data/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyPluginId.java b/tutorials/lessons/lesson_08_plugin_data/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyPluginId.java new file mode 100644 index 000000000..a53922cf6 --- /dev/null +++ b/tutorials/lessons/lesson_08_plugin_data/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyPluginId.java @@ -0,0 +1,18 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.policy; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.nucleus.SimplePluginId; + +/** + * Static plugin id implementation for the GlobalsPlugin + * + * + */ + +public final class PolicyPluginId implements PluginId { + private PolicyPluginId() { + }; + + public final static PluginId PLUGIN_ID = new SimplePluginId("policy plugin id"); + +} diff --git a/tutorials/lessons/lesson_09_experiments/pom.xml b/tutorials/lessons/lesson_09_experiments/pom.xml new file mode 100644 index 000000000..98ec5692d --- /dev/null +++ b/tutorials/lessons/lesson_09_experiments/pom.xml @@ -0,0 +1,79 @@ + + 4.0.0 + + + ASPR + https://www.phe.gov + + + + gov.hhs.aspr.ms.gcm.tutorials + lesson_09_experiments + ${revision} + jar + gcm lesson 09 experiments + Tutorial introducing experiments + + + + UTF-8 + 17 + 17 + ${gcm.version} + + 1.3.0 + + 4.0.0-SNAPSHOT + + + + + + gov.hhs.aspr.ms + gcm + ${gcm.version} + + + + + + Shawn Hatch + Leidos + https://www.leidos.com + + + + + + + org.codehaus.mojo + flatten-maven-plugin + ${flatten-maven-plugin.version} + + + + flatten + process-resources + + flatten + + + true + + + + + flatten.clean + clean + + clean + + + + + + + + diff --git a/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_9_A.java b/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_9_A.java new file mode 100644 index 000000000..060cc4ae9 --- /dev/null +++ b/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_9_A.java @@ -0,0 +1,52 @@ +package gov.hhs.aspr.ms.gcm.lessons; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.disease.DiseasePlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.disease.DiseasePluginData; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.ModelPlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.policy.PolicyPlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.policy.PolicyPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.Experiment; +import gov.hhs.aspr.ms.gcm.nucleus.ExperimentStatusConsole; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; + +/* start code_ref=experiments_example_9_A|code_cap=Example 9 replaces Example 8's use of the simulation with an experiment.*/ +public final class Example_9_A { + + private Example_9_A() { + } + + private static DiseasePluginData getDiseasePluginData() { + return DiseasePluginData.builder()// + .setR0(1.5)// + .setAsymptomaticDays(4.0)// + .setSymptomaticDays(12.0)// + .build(); + } + + private static PolicyPluginData getPolicyPluginData() { + return PolicyPluginData.builder()// + .setDistributeVaccineLocally(true)// + .setSchoolClosingInfectionRate(0.05)// + .build(); + } + + public static void main(String[] args) { + + DiseasePluginData diseasePluginData = getDiseasePluginData(); + Plugin diseasePlugin = DiseasePlugin.getDiseasePlugin(diseasePluginData); + + PolicyPluginData policyPluginData = getPolicyPluginData(); + Plugin policyPlugin = PolicyPlugin.getPolicyPlugin(policyPluginData); + + Plugin modelPlugin = ModelPlugin.getModelPlugin(); + + Experiment.builder()// + .addPlugin(diseasePlugin)// + .addPlugin(modelPlugin)// + .addPlugin(policyPlugin)// + .addExperimentContextConsumer(ExperimentStatusConsole.builder().build())// + .build()// + .execute(); + } +} +/* end */ diff --git a/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_9_B.java b/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_9_B.java new file mode 100644 index 000000000..735ea04bb --- /dev/null +++ b/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_9_B.java @@ -0,0 +1,80 @@ +package gov.hhs.aspr.ms.gcm.lessons; + +import java.util.ArrayList; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.disease.DiseasePlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.disease.DiseasePluginData; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.ModelPlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.policy.PolicyPlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.policy.PolicyPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.Dimension; +import gov.hhs.aspr.ms.gcm.nucleus.Experiment; +import gov.hhs.aspr.ms.gcm.nucleus.FunctionalDimension; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; + +public final class Example_9_B { + + private Example_9_B() { + } + + private static DiseasePluginData getDiseasePluginData() { + return DiseasePluginData.builder()// + .setR0(1.5)// + .setAsymptomaticDays(4.0)// + .setSymptomaticDays(12.0)// + .build(); + } + + private static PolicyPluginData getPolicyPluginData() { + return PolicyPluginData.builder()// + .setDistributeVaccineLocally(true)// + .setSchoolClosingInfectionRate(0.05)// + .build(); + } + + + /* start code_ref=experiments_example_9_B|code_cap=Example 9 B introduces a single dimension that sets the R0 value of the disease plugin data to two values.*/ + public static void main(String[] args) { + + DiseasePluginData diseasePluginData = getDiseasePluginData(); + Plugin diseasePlugin = DiseasePlugin.getDiseasePlugin(diseasePluginData); + + PolicyPluginData policyPluginData = getPolicyPluginData(); + Plugin policyPlugin = PolicyPlugin.getPolicyPlugin(policyPluginData); + + Plugin modelPlugin = ModelPlugin.getModelPlugin(); + + Dimension dimension = FunctionalDimension.builder()// + .addLevel((context) -> { + DiseasePluginData.Builder builder = context.getPluginDataBuilder(DiseasePluginData.Builder.class); + double r0 = 2.5; + builder.setR0(r0); + ArrayList result = new ArrayList<>(); + result.add(Double.toString(r0)); + return result; + })// + + .addLevel((context) -> { + DiseasePluginData.Builder builder = context.getPluginDataBuilder(DiseasePluginData.Builder.class); + double r0 = 2.0; + builder.setR0(r0); + ArrayList result = new ArrayList<>(); + result.add(Double.toString(r0)); + return result; + })// + + .addMetaDatum("r0")// + + .build(); + + Experiment.builder()// + .addPlugin(diseasePlugin)// + .addPlugin(modelPlugin)// + .addPlugin(policyPlugin)// + .addDimension(dimension)// + .build()// + .execute(); + } + /* end */ + +} diff --git a/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_9_C.java b/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_9_C.java new file mode 100644 index 000000000..b9a08d850 --- /dev/null +++ b/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_9_C.java @@ -0,0 +1,87 @@ +package gov.hhs.aspr.ms.gcm.lessons; + +import java.util.ArrayList; +import java.util.List; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.disease.DiseasePlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.disease.DiseasePluginData; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.ModelPlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.policy.PolicyPlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.policy.PolicyPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.Dimension; +import gov.hhs.aspr.ms.gcm.nucleus.Experiment; +import gov.hhs.aspr.ms.gcm.nucleus.FunctionalDimension; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; + +/* start code_ref=experiments_example_steamlined_dimension|code_cap=Example 9 C improves on the creation of the R0 dimension.*/ +public final class Example_9_C { + + private Example_9_C() { + } + + private static DiseasePluginData getDiseasePluginData() { + return DiseasePluginData.builder()// + .setR0(1.5)// + .setAsymptomaticDays(4.0)// + .setSymptomaticDays(12.0)// + .build(); + } + + private static PolicyPluginData getPolicyPluginData() { + return PolicyPluginData.builder()// + .setDistributeVaccineLocally(true)// + .setSchoolClosingInfectionRate(0.05)// + .build(); + } + + private static Dimension getDimension() { + FunctionalDimension.Builder builder = FunctionalDimension.builder();// + + List r0Values = new ArrayList<>(); + r0Values.add(0.5); + r0Values.add(0.75); + r0Values.add(1.0); + r0Values.add(1.5); + r0Values.add(2.0); + r0Values.add(2.5); + + for (Double r0 : r0Values) { + builder.addLevel((context) -> { + DiseasePluginData.Builder pluginDataBuilder = context + .getPluginDataBuilder(DiseasePluginData.Builder.class); + pluginDataBuilder.setR0(r0); + ArrayList result = new ArrayList<>(); + result.add(Double.toString(r0)); + return result; + });// + } + builder.addMetaDatum("r0");// + + return builder.build(); + } + /* end */ + + /* start code_ref=experiments_example_9_C|code_cap=Execution of the experiment is cleaner.*/ + public static void main(String[] args) { + + DiseasePluginData diseasePluginData = getDiseasePluginData(); + Plugin diseasePlugin = DiseasePlugin.getDiseasePlugin(diseasePluginData); + + PolicyPluginData policyPluginData = getPolicyPluginData(); + Plugin policyPlugin = PolicyPlugin.getPolicyPlugin(policyPluginData); + + Plugin modelPlugin = ModelPlugin.getModelPlugin(); + + Dimension dimension = getDimension(); + + Experiment.builder()// + .addPlugin(diseasePlugin)// + .addPlugin(modelPlugin)// + .addPlugin(policyPlugin)// + .addDimension(dimension)// + .build()// + .execute(); + } + + /* end */ +} diff --git a/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_9_D.java b/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_9_D.java new file mode 100644 index 000000000..c8d7dfa1e --- /dev/null +++ b/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_9_D.java @@ -0,0 +1,121 @@ +package gov.hhs.aspr.ms.gcm.lessons; + +import java.util.ArrayList; +import java.util.List; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.disease.DiseasePlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.disease.DiseasePluginData; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.ModelPlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.policy.PolicyPlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.policy.PolicyPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.Dimension; +import gov.hhs.aspr.ms.gcm.nucleus.Experiment; +import gov.hhs.aspr.ms.gcm.nucleus.FunctionalDimension; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; + +public final class Example_9_D { + + private Example_9_D() { + } + + private static DiseasePluginData getDiseasePluginData() { + return DiseasePluginData.builder()// + .setR0(1.5)// + .setAsymptomaticDays(4.0)// + .setSymptomaticDays(12.0)// + .build(); + } + + private static PolicyPluginData getPolicyPluginData() { + return PolicyPluginData.builder()// + .setDistributeVaccineLocally(true)// + .setSchoolClosingInfectionRate(0.05)// + .build(); + } + + private static Dimension getR0Dimension() { + + FunctionalDimension.Builder builder = FunctionalDimension.builder();// + + List r0Values = new ArrayList<>(); + r0Values.add(1.5); + r0Values.add(2.0); + r0Values.add(2.5); + + for (Double r0 : r0Values) { + builder.addLevel((context) -> { + DiseasePluginData.Builder pluginDataBuilder = context + .getPluginDataBuilder(DiseasePluginData.Builder.class); + pluginDataBuilder.setR0(r0); + ArrayList result = new ArrayList<>(); + result.add(Double.toString(r0)); + return result; + });// + } + builder.addMetaDatum("r0");// + + return builder.build(); + + } + + /* start code_ref=experiements_policy_dimension|code_cap=A dimension representing school related policies is added. Note that this dimension has four levels and covers two policies.*/ + private static Dimension getPolicyDimension() { + FunctionalDimension.Builder builder = FunctionalDimension.builder();// + + List schoolClosingInfectionRates = new ArrayList<>(); + schoolClosingInfectionRates.add(0.05); + schoolClosingInfectionRates.add(0.10); + + List localVaccineDistributionValues = new ArrayList<>(); + localVaccineDistributionValues.add(false); + localVaccineDistributionValues.add(true); + + for (Boolean localVaccineDistribution : localVaccineDistributionValues) { + for (Double schoolClosingInfectionRate : schoolClosingInfectionRates) { + builder.addLevel((context) -> { + PolicyPluginData.Builder pluginDataBuilder = context + .getPluginDataBuilder(PolicyPluginData.Builder.class); + pluginDataBuilder.setSchoolClosingInfectionRate(schoolClosingInfectionRate); + pluginDataBuilder.setDistributeVaccineLocally(localVaccineDistribution); + + ArrayList result = new ArrayList<>(); + result.add(Double.toString(schoolClosingInfectionRate)); + result.add(Boolean.toString(localVaccineDistribution)); + return result; + });// + } + } + builder.addMetaDatum("school_closing_infection_rate");// + builder.addMetaDatum("distribute_vaccine_locally");// + + return builder.build(); + } + /* end */ + + /* start code_ref=experiements_example_9_D|code_cap=The new policy dimension is added to the experiment with four levels. The R0 dimension was reduced to three levels. Thus the experiment will run twelve scenarios.*/ + public static void main(String[] args) { + + DiseasePluginData diseasePluginData = getDiseasePluginData(); + Plugin diseasePlugin = DiseasePlugin.getDiseasePlugin(diseasePluginData); + + PolicyPluginData policyPluginData = getPolicyPluginData(); + Plugin policyPlugin = PolicyPlugin.getPolicyPlugin(policyPluginData); + + Plugin modelPlugin = ModelPlugin.getModelPlugin(); + + Dimension r0Dimension = getR0Dimension(); + + Dimension policyDimension = getPolicyDimension(); + + Experiment.builder()// + .addPlugin(diseasePlugin)// + .addPlugin(modelPlugin)// + .addPlugin(policyPlugin)// + .addDimension(r0Dimension)// + .addDimension(policyDimension)// + .build()// + .execute(); + } + /* end */ + +} diff --git a/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_9_E.java b/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_9_E.java new file mode 100644 index 000000000..7676b8879 --- /dev/null +++ b/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_9_E.java @@ -0,0 +1,129 @@ +package gov.hhs.aspr.ms.gcm.lessons; + +import java.util.ArrayList; +import java.util.List; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.disease.DiseasePlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.disease.DiseasePluginData; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.ModelPlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.policy.PolicyPlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.policy.PolicyPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.Dimension; +import gov.hhs.aspr.ms.gcm.nucleus.Experiment; +import gov.hhs.aspr.ms.gcm.nucleus.ExperimentParameterData; +import gov.hhs.aspr.ms.gcm.nucleus.FunctionalDimension; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; + +public final class Example_9_E { + + private Example_9_E() { + } + + private static DiseasePluginData getDiseasePluginData() { + return DiseasePluginData.builder()// + .setR0(1.5)// + .setAsymptomaticDays(4.0)// + .setSymptomaticDays(12.0)// + .build(); + } + + private static PolicyPluginData getPolicyPluginData() { + return PolicyPluginData.builder()// + .setDistributeVaccineLocally(true)// + .setSchoolClosingInfectionRate(0.05)// + .build(); + } + + private static Dimension getR0Dimension() { + + FunctionalDimension.Builder builder = FunctionalDimension.builder();// + + List r0Values = new ArrayList<>(); + r0Values.add(1.5); + r0Values.add(2.0); + r0Values.add(2.5); + + for (Double r0 : r0Values) { + builder.addLevel((context) -> { + DiseasePluginData.Builder pluginDataBuilder = context + .getPluginDataBuilder(DiseasePluginData.Builder.class); + pluginDataBuilder.setR0(r0); + ArrayList result = new ArrayList<>(); + result.add(Double.toString(r0)); + return result; + });// + } + builder.addMetaDatum("r0");// + + return builder.build(); + + } + + private static Dimension getPolicyDimension() { + FunctionalDimension.Builder builder = FunctionalDimension.builder();// + + List schoolClosingInfectionRates = new ArrayList<>(); + schoolClosingInfectionRates.add(0.05); + schoolClosingInfectionRates.add(0.10); + + List localVaccineDistributionValues = new ArrayList<>(); + localVaccineDistributionValues.add(false); + localVaccineDistributionValues.add(true); + + for (Boolean localVaccineDistribution : localVaccineDistributionValues) { + for (Double schoolClosingInfectionRate : schoolClosingInfectionRates) { + builder.addLevel((context) -> { + PolicyPluginData.Builder pluginDataBuilder = context + .getPluginDataBuilder(PolicyPluginData.Builder.class); + pluginDataBuilder.setSchoolClosingInfectionRate(schoolClosingInfectionRate); + pluginDataBuilder.setDistributeVaccineLocally(localVaccineDistribution); + + ArrayList result = new ArrayList<>(); + result.add(Double.toString(schoolClosingInfectionRate)); + result.add(Boolean.toString(localVaccineDistribution)); + return result; + });// + } + } + builder.addMetaDatum("school_closing_infection_rate");// + builder.addMetaDatum("distribute_vaccine_locally");// + + return builder.build(); + + } + + /* start code_ref=experiements_example_9_E|code_cap=Executing the 12 scenarios of the previous experiment with four threads.*/ + public static void main(String[] args) { + + DiseasePluginData diseasePluginData = getDiseasePluginData(); + Plugin diseasePlugin = DiseasePlugin.getDiseasePlugin(diseasePluginData); + + PolicyPluginData policyPluginData = getPolicyPluginData(); + Plugin policyPlugin = PolicyPlugin.getPolicyPlugin(policyPluginData); + + Plugin modelPlugin = ModelPlugin.getModelPlugin(); + + Dimension r0Dimension = getR0Dimension(); + + Dimension policyDimension = getPolicyDimension(); + + ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()// + .setThreadCount(4)// + .build(); + + /* + * Adding threads. Scrambled output + */ + Experiment.builder()// + .addPlugin(diseasePlugin)// + .addPlugin(modelPlugin)// + .addPlugin(policyPlugin)// + .addDimension(r0Dimension)// + .addDimension(policyDimension)// + .setExperimentParameterData(experimentParameterData)// + .build()// + .execute(); + } + /* end */ + +} diff --git a/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseaseDataManager.java b/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseaseDataManager.java new file mode 100644 index 000000000..b4e823e8d --- /dev/null +++ b/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseaseDataManager.java @@ -0,0 +1,54 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.disease; + +import gov.hhs.aspr.ms.gcm.nucleus.DataManager; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; + +public final class DiseaseDataManager extends DataManager { + + private double r0 = 7; + + private double asymptomaticDays; + + private double symptomaticDays; + + private final DiseasePluginData diseasePluginData; + + public DiseaseDataManager(DiseasePluginData diseasePluginData) { + this.diseasePluginData = diseasePluginData; + } + + @Override + public void init(DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + + r0 = diseasePluginData.getR0(); + asymptomaticDays = diseasePluginData.getAsymptomaticDays(); + symptomaticDays = diseasePluginData.getSymptomaticDays(); + + } + + public double getR0() { + return r0; + } + + public double getAsymptomaticDays() { + return asymptomaticDays; + } + + public double getSymptomaticDays() { + return symptomaticDays; + } + + public void setR0(double r0) { + this.r0 = r0; + } + + public void setAsymptomaticDays(double asymptomaticDays) { + this.asymptomaticDays = asymptomaticDays; + } + + public void setSymptomaticDays(double symptomaticDays) { + this.symptomaticDays = symptomaticDays; + } + +} diff --git a/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseasePlugin.java b/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseasePlugin.java new file mode 100644 index 000000000..aa53397e4 --- /dev/null +++ b/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseasePlugin.java @@ -0,0 +1,23 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.disease; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; + +public final class DiseasePlugin { + + private DiseasePlugin() { + + } + + public static Plugin getDiseasePlugin(DiseasePluginData diseasePluginData) { + + return Plugin.builder()// + .addPluginData(diseasePluginData)// + .setPluginId(DiseasePluginId.PLUGIN_ID)// + .setInitializer((c) -> { + DiseasePluginData pluginData = c.getPluginData(DiseasePluginData.class).get(); + c.addDataManager(new DiseaseDataManager(pluginData)); + })// + .build(); + } + +} diff --git a/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseasePluginData.java b/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseasePluginData.java new file mode 100644 index 000000000..c73cba01d --- /dev/null +++ b/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseasePluginData.java @@ -0,0 +1,147 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.disease; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.PluginDataBuilder; +import net.jcip.annotations.Immutable; + +@Immutable +public final class DiseasePluginData implements PluginData { + + private static class Data { + + private double r0; + + private double asymptomaticDays; + + private double symptomaticDays; + + private Data() { + } + + private Data(final Data data) { + r0 = data.r0; + asymptomaticDays = data.asymptomaticDays; + symptomaticDays = data.symptomaticDays; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + long temp; + temp = Double.doubleToLongBits(asymptomaticDays); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(r0); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(symptomaticDays); + result = prime * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + if (Double.doubleToLongBits(asymptomaticDays) != Double.doubleToLongBits(other.asymptomaticDays)) { + return false; + } + if (Double.doubleToLongBits(r0) != Double.doubleToLongBits(other.r0)) { + return false; + } + if (Double.doubleToLongBits(symptomaticDays) != Double.doubleToLongBits(other.symptomaticDays)) { + return false; + } + return true; + } + + } + + public static class Builder implements PluginDataBuilder { + private Data data; + + private Builder(final Data data) { + this.data = data; + } + + @Override + public DiseasePluginData build() { + return new DiseasePluginData(new Data(data)); + } + + public Builder setAsymptomaticDays(final double asymptomaticDays) { + data.asymptomaticDays = asymptomaticDays; + return this; + } + + public Builder setR0(final double r0) { + data.r0 = r0; + return this; + } + + public Builder setSymptomaticDays(final double symptomaticDays) { + data.symptomaticDays = symptomaticDays; + return this; + } + + } + + public static Builder builder() { + return new Builder(new Data()); + } + + private final Data data; + + private DiseasePluginData(final Data data) { + this.data = data; + } + + public double getAsymptomaticDays() { + return data.asymptomaticDays; + } + + public double getR0() { + return data.r0; + } + + public double getSymptomaticDays() { + return data.symptomaticDays; + } + + @Override + public PluginDataBuilder getCloneBuilder() { + return new Builder(new Data(data)); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof DiseasePluginData)) { + return false; + } + DiseasePluginData other = (DiseasePluginData) obj; + if (data == null) { + if (other.data != null) { + return false; + } + } else if (!data.equals(other.data)) { + return false; + } + return true; + } + +} diff --git a/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseasePluginId.java b/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseasePluginId.java new file mode 100644 index 000000000..bbc3a27dd --- /dev/null +++ b/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseasePluginId.java @@ -0,0 +1,18 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.disease; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.nucleus.SimplePluginId; + +/** + * Static plugin id implementation for the GlobalsPlugin + * + * + */ + +public final class DiseasePluginId implements PluginId { + private DiseasePluginId() { + }; + + public final static PluginId PLUGIN_ID = new SimplePluginId("disease plugin id"); + +} diff --git a/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelActor.java b/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelActor.java new file mode 100644 index 000000000..03ed20310 --- /dev/null +++ b/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelActor.java @@ -0,0 +1,23 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.disease.DiseaseDataManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.policy.PolicyDataManager; +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; + +public final class ModelActor { + + public void init(ActorContext actorContext) { + DiseaseDataManager diseaseDataManager = actorContext.getDataManager(DiseaseDataManager.class); + System.out.println("Model Actor initializing"); + String tab = "\t"; + System.out.println(tab + "r0 = " + diseaseDataManager.getR0()); + System.out.println(tab + "asymptomatic days = " + diseaseDataManager.getAsymptomaticDays()); + System.out.println(tab + "symptomatic days = " + diseaseDataManager.getSymptomaticDays()); + + PolicyDataManager policyDataManager = actorContext.getDataManager(PolicyDataManager.class); + System.out + .println(tab + "school closing infection rate = " + policyDataManager.getSchoolClosingInfectionRate()); + System.out.println(tab + "distribute vaccine locally = " + policyDataManager.distributeVaccineLocally()); + System.out.println(); + } +} diff --git a/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java b/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java new file mode 100644 index 000000000..ff58356b9 --- /dev/null +++ b/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java @@ -0,0 +1,21 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; + +public final class ModelPlugin { + + private ModelPlugin() { + + } + + public static Plugin getModelPlugin() { + + return Plugin.builder()// + .setPluginId(ModelPluginId.PLUGIN_ID)// + .setInitializer((c) -> { + c.addActor(new ModelActor()::init); + })// + .build(); + } + +} diff --git a/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java b/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java new file mode 100644 index 000000000..4d0a6a3cd --- /dev/null +++ b/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java @@ -0,0 +1,18 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.nucleus.SimplePluginId; + +/** + * Static plugin id implementation for the GlobalsPlugin + * + * + */ + +public final class ModelPluginId implements PluginId { + private ModelPluginId() { + }; + + public final static PluginId PLUGIN_ID = new SimplePluginId("model plugin id"); + +} diff --git a/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyDataManager.java b/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyDataManager.java new file mode 100644 index 000000000..c01f74f6c --- /dev/null +++ b/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyDataManager.java @@ -0,0 +1,39 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.policy; + +import gov.hhs.aspr.ms.gcm.nucleus.DataManager; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; + +public final class PolicyDataManager extends DataManager { + private double schoolClosingInfectionRate; + private boolean distributeVaccineLocally; + + private final PolicyPluginData policyPluginData; + + public PolicyDataManager(PolicyPluginData policyPluginData) { + this.policyPluginData = policyPluginData; + } + + @Override + public void init(DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + schoolClosingInfectionRate = policyPluginData.getSchoolClosingInfectionRate(); + distributeVaccineLocally = policyPluginData.distributeVaccineLocally(); + } + + public double getSchoolClosingInfectionRate() { + return schoolClosingInfectionRate; + } + + public void setSchoolClosingInfectionRate(double schoolClosingInfectionRate) { + this.schoolClosingInfectionRate = schoolClosingInfectionRate; + } + + public boolean distributeVaccineLocally() { + return distributeVaccineLocally; + } + + public void setDistributeVaccineLocally(boolean distributeVaccineLocally) { + this.distributeVaccineLocally = distributeVaccineLocally; + } + +} diff --git a/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyPlugin.java b/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyPlugin.java new file mode 100644 index 000000000..3f1e6e8ad --- /dev/null +++ b/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyPlugin.java @@ -0,0 +1,23 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.policy; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; + +public final class PolicyPlugin { + + private PolicyPlugin() { + + } + + public static Plugin getPolicyPlugin(PolicyPluginData policyPluginData) { + + return Plugin.builder()// + .addPluginData(policyPluginData)// + .setPluginId(PolicyPluginId.PLUGIN_ID)// + .setInitializer((c) -> { + PolicyPluginData pluginData = c.getPluginData(PolicyPluginData.class).get(); + c.addDataManager(new PolicyDataManager(pluginData)); + })// + .build(); + } + +} diff --git a/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyPluginData.java b/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyPluginData.java new file mode 100644 index 000000000..f488dcc83 --- /dev/null +++ b/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyPluginData.java @@ -0,0 +1,129 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.policy; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.PluginDataBuilder; +import net.jcip.annotations.Immutable; + +@Immutable +public final class PolicyPluginData implements PluginData { + + private static class Data { + + private double schoolClosingInfectionRate; + private boolean distributeVaccineLocally; + + private Data() { + } + + private Data(final Data data) { + schoolClosingInfectionRate = data.schoolClosingInfectionRate; + distributeVaccineLocally = data.distributeVaccineLocally; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (distributeVaccineLocally ? 1231 : 1237); + long temp; + temp = Double.doubleToLongBits(schoolClosingInfectionRate); + result = prime * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + if (distributeVaccineLocally != other.distributeVaccineLocally) { + return false; + } + if (Double.doubleToLongBits(schoolClosingInfectionRate) != Double + .doubleToLongBits(other.schoolClosingInfectionRate)) { + return false; + } + return true; + } + + } + + public static class Builder implements PluginDataBuilder { + private Data data; + + private Builder(final Data data) { + this.data = data; + } + + @Override + public PolicyPluginData build() { + return new PolicyPluginData(new Data(data)); + } + + public Builder setSchoolClosingInfectionRate(double schoolClosingInfectionRate) { + data.schoolClosingInfectionRate = schoolClosingInfectionRate; + return this; + } + + public Builder setDistributeVaccineLocally(boolean distributeVaccineLocally) { + data.distributeVaccineLocally = distributeVaccineLocally; + return this; + } + + } + + public static Builder builder() { + return new Builder(new Data()); + } + + private final Data data; + + private PolicyPluginData(final Data data) { + this.data = data; + } + + public double getSchoolClosingInfectionRate() { + return data.schoolClosingInfectionRate; + } + + public boolean distributeVaccineLocally() { + return data.distributeVaccineLocally; + } + + @Override + public PluginDataBuilder getCloneBuilder() { + return new Builder(new Data(data)); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof PolicyPluginData)) { + return false; + } + PolicyPluginData other = (PolicyPluginData) obj; + if (data == null) { + if (other.data != null) { + return false; + } + } else if (!data.equals(other.data)) { + return false; + } + return true; + } + +} diff --git a/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyPluginId.java b/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyPluginId.java new file mode 100644 index 000000000..a53922cf6 --- /dev/null +++ b/tutorials/lessons/lesson_09_experiments/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyPluginId.java @@ -0,0 +1,18 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.policy; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.nucleus.SimplePluginId; + +/** + * Static plugin id implementation for the GlobalsPlugin + * + * + */ + +public final class PolicyPluginId implements PluginId { + private PolicyPluginId() { + }; + + public final static PluginId PLUGIN_ID = new SimplePluginId("policy plugin id"); + +} diff --git a/tutorials/lessons/lesson_09_experiments/src/main/resources/output/experiements_example_9_D_output_1.txt b/tutorials/lessons/lesson_09_experiments/src/main/resources/output/experiements_example_9_D_output_1.txt new file mode 100644 index 000000000..66eaeee5e --- /dev/null +++ b/tutorials/lessons/lesson_09_experiments/src/main/resources/output/experiements_example_9_D_output_1.txt @@ -0,0 +1,16 @@ +/* start code_ref=experiements_example_9_D_output_1|code_cap=The first two scenarios.*/ +Model Actor initializing + r0 = 1.5 + asymptomatic days = 4.0 + symptomatic days = 12.0 + school closing infection rate = 0.05 + distribute vaccine locally = false + +Model Actor initializing + r0 = 2.0 + asymptomatic days = 4.0 + symptomatic days = 12.0 + school closing infection rate = 0.05 + distribute vaccine locally = false + +/* end */ \ No newline at end of file diff --git a/tutorials/lessons/lesson_09_experiments/src/main/resources/output/experiements_example_9_D_output_2.txt b/tutorials/lessons/lesson_09_experiments/src/main/resources/output/experiements_example_9_D_output_2.txt new file mode 100644 index 000000000..9bf16b3d2 --- /dev/null +++ b/tutorials/lessons/lesson_09_experiments/src/main/resources/output/experiements_example_9_D_output_2.txt @@ -0,0 +1,16 @@ +/* start code_ref=experiements_example_9_D_output_2|code_cap=The last two scenarios.*/ +Model Actor initializing + r0 = 2.0 + asymptomatic days = 4.0 + symptomatic days = 12.0 + school closing infection rate = 0.1 + distribute vaccine locally = true + +Model Actor initializing + r0 = 2.5 + asymptomatic days = 4.0 + symptomatic days = 12.0 + school closing infection rate = 0.1 + distribute vaccine locally = true + +/* end */ \ No newline at end of file diff --git a/tutorials/lessons/lesson_09_experiments/src/main/resources/output/experiements_example_9_E_output.txt b/tutorials/lessons/lesson_09_experiments/src/main/resources/output/experiements_example_9_E_output.txt new file mode 100644 index 000000000..321144688 --- /dev/null +++ b/tutorials/lessons/lesson_09_experiments/src/main/resources/output/experiements_example_9_E_output.txt @@ -0,0 +1,21 @@ +/* start code_ref=experiements_example_9_E_output|code_cap=The output from the four threads running the twelve scenarios is a bit jumbled and hard to follow. This will get resolved later with improved output handling.*/ +Model Actor initializing +Model Actor initializing + r0 = 1.5 +Model Actor initializing +Model Actor initializing + r0 = 1.5 + asymptomatic days = 4.0 + symptomatic days = 12.0 + r0 = 2.5 + school closing infection rate = 0.05 + asymptomatic days = 4.0 + asymptomatic days = 4.0 + symptomatic days = 12.0 + r0 = 2.0 + school closing infection rate = 0.1 + distribute vaccine locally = false + +... + +/* end */ \ No newline at end of file diff --git a/tutorials/lessons/lesson_09_experiments/src/main/resources/output/experiments_example_9_A_output.txt b/tutorials/lessons/lesson_09_experiments/src/main/resources/output/experiments_example_9_A_output.txt new file mode 100644 index 000000000..e816ea53d --- /dev/null +++ b/tutorials/lessons/lesson_09_experiments/src/main/resources/output/experiments_example_9_A_output.txt @@ -0,0 +1,13 @@ +/* start code_ref=experiments_example_9_A_output|code_cap=The output of the experiment version is the same as the simulation since the experiment contains exactly one scenario.*/ +Model Actor initializing + r0 = 1.5 + asymptomatic days = 4.0 + symptomatic days = 12.0 + school closing infection rate = 0.05 + distribute vaccine locally = true + +1 of 1 scenario, 100% complete. Expected experiment completion in 0:00:00 +Experiment completion of 1 scenario in 0:00:00: + SUCCEDED : 1 +end of experiment status console +/* end */ \ No newline at end of file diff --git a/tutorials/lessons/lesson_09_experiments/src/main/resources/output/experiments_output.txt b/tutorials/lessons/lesson_09_experiments/src/main/resources/output/experiments_output.txt new file mode 100644 index 000000000..3fa557763 --- /dev/null +++ b/tutorials/lessons/lesson_09_experiments/src/main/resources/output/experiments_output.txt @@ -0,0 +1,44 @@ +/* start code_ref=experiments_output|code_cap=The model actor writes output for each of the five scenarios corresponding to the five values of the R0 dimension.*/ +Model Actor initializing + r0 = 0.5 + asymptomatic days = 4.0 + symptomatic days = 12.0 + school closing infection rate = 0.05 + distribute vaccine locally = true + +Model Actor initializing + r0 = 0.75 + asymptomatic days = 4.0 + symptomatic days = 12.0 + school closing infection rate = 0.05 + distribute vaccine locally = true + +Model Actor initializing + r0 = 1.0 + asymptomatic days = 4.0 + symptomatic days = 12.0 + school closing infection rate = 0.05 + distribute vaccine locally = true + +Model Actor initializing + r0 = 1.5 + asymptomatic days = 4.0 + symptomatic days = 12.0 + school closing infection rate = 0.05 + distribute vaccine locally = true + +Model Actor initializing + r0 = 2.0 + asymptomatic days = 4.0 + symptomatic days = 12.0 + school closing infection rate = 0.05 + distribute vaccine locally = true + +Model Actor initializing + r0 = 2.5 + asymptomatic days = 4.0 + symptomatic days = 12.0 + school closing infection rate = 0.05 + distribute vaccine locally = true + +/* end */ \ No newline at end of file diff --git a/tutorials/lessons/lesson_10_output/pom.xml b/tutorials/lessons/lesson_10_output/pom.xml new file mode 100644 index 000000000..b33b3b67f --- /dev/null +++ b/tutorials/lessons/lesson_10_output/pom.xml @@ -0,0 +1,79 @@ + + 4.0.0 + + + ASPR + https://www.phe.gov + + + + gov.hhs.aspr.ms.gcm.tutorials + lesson_10_output + ${revision} + jar + gcm lesson 10 output + Tutorial introducing output + + + + UTF-8 + 17 + 17 + ${gcm.version} + + 1.3.0 + + 4.0.0-SNAPSHOT + + + + + + gov.hhs.aspr.ms + gcm + ${gcm.version} + + + + + + Shawn Hatch + Leidos + https://www.leidos.com + + + + + + + org.codehaus.mojo + flatten-maven-plugin + ${flatten-maven-plugin.version} + + + + flatten + process-resources + + flatten + + + true + + + + + flatten.clean + clean + + clean + + + + + + + + diff --git a/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_10_A.java b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_10_A.java new file mode 100644 index 000000000..8415b5499 --- /dev/null +++ b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_10_A.java @@ -0,0 +1,50 @@ +package gov.hhs.aspr.ms.gcm.lessons; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.disease.DiseasePlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.disease.DiseasePluginData; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.ModelPlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.policy.PolicyPlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.policy.PolicyPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.nucleus.Simulation; + +/* start code_ref=output_example_10_A|code_cap=The simulation sends the released output from the contexts to an output consumer.*/ +public final class Example_10_A { + + private Example_10_A() { + } + + private static DiseasePluginData getDiseasePluginData() { + return DiseasePluginData.builder()// + .setR0(1.5)// + .setAsymptomaticDays(4.0)// + .setSymptomaticDays(12.0)// + .build(); + } + + private static PolicyPluginData getPolicyPluginData() { + return PolicyPluginData.builder()// + .setDistributeVaccineLocally(true)// + .setSchoolClosingInfectionRate(0.05)// + .build(); + } + + public static void main(String[] args) { + + DiseasePluginData diseasePluginData = getDiseasePluginData(); + Plugin diseasePlugin = DiseasePlugin.getDiseasePlugin(diseasePluginData); + + PolicyPluginData policyPluginData = getPolicyPluginData(); + Plugin policyPlugin = PolicyPlugin.getPolicyPlugin(policyPluginData); + + Plugin modelPlugin = ModelPlugin.getModelPlugin(); + + Simulation.builder()// + .addPlugin(diseasePlugin)// + .addPlugin(modelPlugin)// + .addPlugin(policyPlugin)// + .setOutputConsumer(new OutputConsumer_A()).build()// + .execute(); + } +} +/* end */ \ No newline at end of file diff --git a/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_10_B.java b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_10_B.java new file mode 100644 index 000000000..dd3bd0b2d --- /dev/null +++ b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_10_B.java @@ -0,0 +1,128 @@ + +package gov.hhs.aspr.ms.gcm.lessons; + +import java.util.ArrayList; +import java.util.List; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.disease.DiseasePlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.disease.DiseasePluginData; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.ModelPlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.policy.PolicyPlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.policy.PolicyPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.Dimension; +import gov.hhs.aspr.ms.gcm.nucleus.Experiment; +import gov.hhs.aspr.ms.gcm.nucleus.ExperimentParameterData; +import gov.hhs.aspr.ms.gcm.nucleus.FunctionalDimension; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; + +public final class Example_10_B { + + private Example_10_B() { + } + + private static DiseasePluginData getDiseasePluginData() { + return DiseasePluginData.builder()// + .setR0(1.5)// + .setAsymptomaticDays(4.0)// + .setSymptomaticDays(12.0)// + .build(); + } + + private static PolicyPluginData getPolicyPluginData() { + return PolicyPluginData.builder()// + .setDistributeVaccineLocally(true)// + .setSchoolClosingInfectionRate(0.05)// + .build(); + } + + private static Dimension getR0Dimension() { + + FunctionalDimension.Builder builder = FunctionalDimension.builder();// + + List r0Values = new ArrayList<>(); + r0Values.add(1.5); + r0Values.add(2.0); + r0Values.add(2.5); + + for (Double r0 : r0Values) { + builder.addLevel((context) -> { + DiseasePluginData.Builder pluginDataBuilder = context + .getPluginDataBuilder(DiseasePluginData.Builder.class); + pluginDataBuilder.setR0(r0); + ArrayList result = new ArrayList<>(); + result.add(Double.toString(r0)); + return result; + });// + } + builder.addMetaDatum("r0");// + + return builder.build(); + + } + + private static Dimension getPolicyDimension() { + FunctionalDimension.Builder builder = FunctionalDimension.builder();// + + List schoolClosingInfectionRates = new ArrayList<>(); + schoolClosingInfectionRates.add(0.05); + schoolClosingInfectionRates.add(0.10); + + List localVaccineDistributionValues = new ArrayList<>(); + localVaccineDistributionValues.add(false); + localVaccineDistributionValues.add(true); + + for (Boolean localVaccineDistribution : localVaccineDistributionValues) { + for (Double schoolClosingInfectionRate : schoolClosingInfectionRates) { + builder.addLevel((context) -> { + PolicyPluginData.Builder pluginDataBuilder = context + .getPluginDataBuilder(PolicyPluginData.Builder.class); + pluginDataBuilder.setSchoolClosingInfectionRate(schoolClosingInfectionRate); + pluginDataBuilder.setDistributeVaccineLocally(localVaccineDistribution); + + ArrayList result = new ArrayList<>(); + result.add(Double.toString(schoolClosingInfectionRate)); + result.add(Boolean.toString(localVaccineDistribution)); + return result; + });// + } + } + builder.addMetaDatum("school_closing_infection_rate");// + builder.addMetaDatum("distribute_vaccine_locally");// + + return builder.build(); + + } + + /* start code_ref=output_example_10_B|code_cap=The experiment is now involved in the output process. A new output consumer is used that has access to scenario level information.*/ + public static void main(String[] args) { + + DiseasePluginData diseasePluginData = getDiseasePluginData(); + Plugin diseasePlugin = DiseasePlugin.getDiseasePlugin(diseasePluginData); + + PolicyPluginData policyPluginData = getPolicyPluginData(); + Plugin policyPlugin = PolicyPlugin.getPolicyPlugin(policyPluginData); + + Plugin modelPlugin = ModelPlugin.getModelPlugin(); + + Dimension r0Dimension = getR0Dimension(); + + Dimension policyDimension = getPolicyDimension(); + + ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()// + .setThreadCount(4)// + .build(); + + Experiment.builder()// + .addPlugin(diseasePlugin)// + .addPlugin(modelPlugin)// + .addPlugin(policyPlugin)// + .addDimension(r0Dimension)// + .addDimension(policyDimension)// + .addExperimentContextConsumer(new OutputConsumer_B())// + .setExperimentParameterData(experimentParameterData)// + .build()// + .execute(); + } + /* end */ + +} diff --git a/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_10_C.java b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_10_C.java new file mode 100644 index 000000000..06b137dee --- /dev/null +++ b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_10_C.java @@ -0,0 +1,124 @@ +package gov.hhs.aspr.ms.gcm.lessons; + +import java.util.ArrayList; +import java.util.List; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.disease.DiseasePlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.disease.DiseasePluginData; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.ModelPlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.policy.PolicyPlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.policy.PolicyPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.Dimension; +import gov.hhs.aspr.ms.gcm.nucleus.Experiment; +import gov.hhs.aspr.ms.gcm.nucleus.ExperimentParameterData; +import gov.hhs.aspr.ms.gcm.nucleus.FunctionalDimension; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; + +public final class Example_10_C { + + private Example_10_C() { + } + + private static DiseasePluginData getDiseasePluginData() { + return DiseasePluginData.builder()// + .setR0(1.5)// + .setAsymptomaticDays(4.0)// + .setSymptomaticDays(12.0)// + .build(); + } + + private static PolicyPluginData getPolicyPluginData() { + return PolicyPluginData.builder()// + .setDistributeVaccineLocally(true)// + .setSchoolClosingInfectionRate(0.05)// + .build(); + } + + private static Dimension getR0Dimension() { + + FunctionalDimension.Builder builder = FunctionalDimension.builder();// + + List r0Values = new ArrayList<>(); + r0Values.add(1.5); + r0Values.add(2.0); + r0Values.add(2.5); + + for (Double r0 : r0Values) { + builder.addLevel((context) -> { + DiseasePluginData.Builder pluginDataBuilder = context + .getPluginDataBuilder(DiseasePluginData.Builder.class); + pluginDataBuilder.setR0(r0); + ArrayList result = new ArrayList<>(); + result.add(Double.toString(r0)); + return result; + });// + } + builder.addMetaDatum("r0");// + + return builder.build(); + + } + + private static Dimension getPolicyDimension() { + FunctionalDimension.Builder builder = FunctionalDimension.builder();// + + List schoolClosingInfectionRates = new ArrayList<>(); + schoolClosingInfectionRates.add(0.05); + schoolClosingInfectionRates.add(0.10); + + List localVaccineDistributionValues = new ArrayList<>(); + localVaccineDistributionValues.add(false); + localVaccineDistributionValues.add(true); + + for (Boolean localVaccineDistribution : localVaccineDistributionValues) { + for (Double schoolClosingInfectionRate : schoolClosingInfectionRates) { + builder.addLevel((context) -> { + PolicyPluginData.Builder pluginDataBuilder = context + .getPluginDataBuilder(PolicyPluginData.Builder.class); + pluginDataBuilder.setSchoolClosingInfectionRate(schoolClosingInfectionRate); + pluginDataBuilder.setDistributeVaccineLocally(localVaccineDistribution); + + ArrayList result = new ArrayList<>(); + result.add(Double.toString(schoolClosingInfectionRate)); + result.add(Boolean.toString(localVaccineDistribution)); + return result; + });// + } + } + builder.addMetaDatum("school_closing_infection_rate");// + builder.addMetaDatum("distribute_vaccine_locally");// + + return builder.build(); + + } + + public static void main(String[] args) { + + DiseasePluginData diseasePluginData = getDiseasePluginData(); + Plugin diseasePlugin = DiseasePlugin.getDiseasePlugin(diseasePluginData); + + PolicyPluginData policyPluginData = getPolicyPluginData(); + Plugin policyPlugin = PolicyPlugin.getPolicyPlugin(policyPluginData); + + Plugin modelPlugin = ModelPlugin.getModelPlugin(); + + Dimension r0Dimension = getR0Dimension(); + + Dimension policyDimension = getPolicyDimension(); + + ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()// + .setThreadCount(4)// + .build(); + + Experiment.builder()// + .addPlugin(diseasePlugin)// + .addPlugin(modelPlugin)// + .addPlugin(policyPlugin)// + .addDimension(r0Dimension)// + .addDimension(policyDimension)// + .addExperimentContextConsumer(new OutputConsumer_C())// + .setExperimentParameterData(experimentParameterData)// + .build()// + .execute(); + } +} diff --git a/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_10_D.java b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_10_D.java new file mode 100644 index 000000000..c6ac0b9d4 --- /dev/null +++ b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_10_D.java @@ -0,0 +1,124 @@ +package gov.hhs.aspr.ms.gcm.lessons; + +import java.util.ArrayList; +import java.util.List; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.disease.DiseasePlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.disease.DiseasePluginData; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.ModelPlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.policy.PolicyPlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.policy.PolicyPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.Dimension; +import gov.hhs.aspr.ms.gcm.nucleus.Experiment; +import gov.hhs.aspr.ms.gcm.nucleus.ExperimentParameterData; +import gov.hhs.aspr.ms.gcm.nucleus.FunctionalDimension; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; + +public final class Example_10_D { + + private Example_10_D() { + } + + private static DiseasePluginData getDiseasePluginData() { + return DiseasePluginData.builder()// + .setR0(1.5)// + .setAsymptomaticDays(4.0)// + .setSymptomaticDays(12.0)// + .build(); + } + + private static PolicyPluginData getPolicyPluginData() { + return PolicyPluginData.builder()// + .setDistributeVaccineLocally(true)// + .setSchoolClosingInfectionRate(0.05)// + .build(); + } + + private static Dimension getR0Dimension() { + + FunctionalDimension.Builder builder = FunctionalDimension.builder();// + + List r0Values = new ArrayList<>(); + r0Values.add(1.5); + r0Values.add(2.0); + r0Values.add(2.5); + + for (Double r0 : r0Values) { + builder.addLevel((context) -> { + DiseasePluginData.Builder pluginDataBuilder = context + .getPluginDataBuilder(DiseasePluginData.Builder.class); + pluginDataBuilder.setR0(r0); + ArrayList result = new ArrayList<>(); + result.add(Double.toString(r0)); + return result; + });// + } + builder.addMetaDatum("r0");// + + return builder.build(); + + } + + private static Dimension getPolicyDimension() { + FunctionalDimension.Builder builder = FunctionalDimension.builder();// + + List schoolClosingInfectionRates = new ArrayList<>(); + schoolClosingInfectionRates.add(0.05); + schoolClosingInfectionRates.add(0.10); + + List localVaccineDistributionValues = new ArrayList<>(); + localVaccineDistributionValues.add(false); + localVaccineDistributionValues.add(true); + + for (Boolean localVaccineDistribution : localVaccineDistributionValues) { + for (Double schoolClosingInfectionRate : schoolClosingInfectionRates) { + builder.addLevel((context) -> { + PolicyPluginData.Builder pluginDataBuilder = context + .getPluginDataBuilder(PolicyPluginData.Builder.class); + pluginDataBuilder.setSchoolClosingInfectionRate(schoolClosingInfectionRate); + pluginDataBuilder.setDistributeVaccineLocally(localVaccineDistribution); + + ArrayList result = new ArrayList<>(); + result.add(Double.toString(schoolClosingInfectionRate)); + result.add(Boolean.toString(localVaccineDistribution)); + return result; + });// + } + } + builder.addMetaDatum("school_closing_infection_rate");// + builder.addMetaDatum("distribute_vaccine_locally");// + + return builder.build(); + + } + + public static void main(String[] args) { + + DiseasePluginData diseasePluginData = getDiseasePluginData(); + Plugin diseasePlugin = DiseasePlugin.getDiseasePlugin(diseasePluginData); + + PolicyPluginData policyPluginData = getPolicyPluginData(); + Plugin policyPlugin = PolicyPlugin.getPolicyPlugin(policyPluginData); + + Plugin modelPlugin = ModelPlugin.getModelPlugin(); + + Dimension r0Dimension = getR0Dimension(); + + Dimension policyDimension = getPolicyDimension(); + + ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()// + .setThreadCount(4)// + .build(); + + Experiment.builder()// + .addPlugin(diseasePlugin)// + .addPlugin(modelPlugin)// + .addPlugin(policyPlugin)// + .addDimension(r0Dimension)// + .addDimension(policyDimension)// + .addExperimentContextConsumer(new OutputConsumer_D())// + .setExperimentParameterData(experimentParameterData)// + .build()// + .execute(); + } +} diff --git a/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/OutputConsumer_A.java b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/OutputConsumer_A.java new file mode 100644 index 000000000..628d1efa9 --- /dev/null +++ b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/OutputConsumer_A.java @@ -0,0 +1,14 @@ +package gov.hhs.aspr.ms.gcm.lessons; + +import java.util.function.Consumer; + +/* start code_ref=output_consumer_A|code_cap=Output consumer A simply prints output to the console.*/ +public class OutputConsumer_A implements Consumer { + + @Override + public void accept(Object t) { + System.out.println(t); + } + +} +/* end */ diff --git a/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/OutputConsumer_B.java b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/OutputConsumer_B.java new file mode 100644 index 000000000..9cee70e2d --- /dev/null +++ b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/OutputConsumer_B.java @@ -0,0 +1,19 @@ +package gov.hhs.aspr.ms.gcm.lessons; + +import java.util.function.Consumer; + +import gov.hhs.aspr.ms.gcm.nucleus.ExperimentContext; + +/* start code_ref=output_consumer_B|code_cap=Output consumer B has access to the experiment level data, so it prints the output to the console as before, but also adds the relevant scenario id.*/ +public class OutputConsumer_B implements Consumer { + + @Override + public void accept(ExperimentContext experimentContext) { + experimentContext.subscribeToOutput(Object.class, this::handleOutput); + } + + private void handleOutput(ExperimentContext experimentContext, Integer scenarioId, Object output) { + System.out.println("scenario " + scenarioId + ": " + output); + } +} +/* end */ diff --git a/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/OutputConsumer_C.java b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/OutputConsumer_C.java new file mode 100644 index 000000000..232d460a1 --- /dev/null +++ b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/OutputConsumer_C.java @@ -0,0 +1,41 @@ +package gov.hhs.aspr.ms.gcm.lessons; + +import java.util.function.Consumer; + +import gov.hhs.aspr.ms.gcm.nucleus.ExperimentContext; + +/* start code_ref=output_consumer_C|code_cap=The output consumer C demonstrates the broader life cycle of the experiment context by printing out experiment and scenario status while still printing output to the console.*/ +public class OutputConsumer_C implements Consumer { + + @Override + public void accept(ExperimentContext experimentContext) { + experimentContext.subscribeToOutput(Object.class, this::handleOutput); + + experimentContext.subscribeToExperimentOpen(this::handleExperimentOpen); + experimentContext.subscribeToExperimentClose(this::handleExperimentClose); + + experimentContext.subscribeToSimulationOpen(this::handleSimulationOpen); + experimentContext.subscribeToSimulationClose(this::handleSimulationClose); + } + + private void handleOutput(ExperimentContext experimentContext, Integer scenarioId, Object output) { + System.out.println("scenario " + scenarioId + ": " + output); + } + + private void handleExperimentOpen(ExperimentContext experimentContext) { + System.out.println("the experiment is open"); + } + + private void handleExperimentClose(ExperimentContext experimentContext) { + System.out.println("the experiment is closed"); + } + + private void handleSimulationOpen(ExperimentContext experimentContext, Integer scenarioId) { + System.out.println("scenario " + scenarioId + " is open"); + } + + private void handleSimulationClose(ExperimentContext experimentContext, Integer scenarioId) { + System.out.println("scenario " + scenarioId + " is closed"); + } +} +/* end */ diff --git a/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/OutputConsumer_D.java b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/OutputConsumer_D.java new file mode 100644 index 000000000..0d874cf30 --- /dev/null +++ b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/OutputConsumer_D.java @@ -0,0 +1,36 @@ +package gov.hhs.aspr.ms.gcm.lessons; + +import java.util.StringJoiner; +import java.util.function.Consumer; + +import gov.hhs.aspr.ms.gcm.nucleus.ExperimentContext; + +/* start code_ref=output_consumer_D|code_cap=OutputConsumer_D demonstrates that the meta data collected from the dimensions is available from the experiment context. Thus output can be associated with the scenario's meta data.*/ +public class OutputConsumer_D implements Consumer { + + @Override + public void accept(ExperimentContext experimentContext) { + experimentContext.subscribeToExperimentOpen(this::handleExperimentOpen); + experimentContext.subscribeToSimulationOpen(this::handleSimulationOpen); + } + + private void handleExperimentOpen(ExperimentContext experimentContext) { + + StringJoiner joiner = new StringJoiner("\t", "", ""); + joiner.add("scenario"); + experimentContext.getExperimentMetaData().forEach(joiner::add); + + System.out.println(joiner); + } + + private void handleSimulationOpen(ExperimentContext experimentContext, Integer scenarioId) { + + StringJoiner joiner = new StringJoiner("\t", "", ""); + joiner.add(scenarioId.toString()); + experimentContext.getScenarioMetaData(scenarioId).forEach(joiner::add); + + System.out.println(joiner); + } + +} +/* end */ diff --git a/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseaseDataManager.java b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseaseDataManager.java new file mode 100644 index 000000000..b4e823e8d --- /dev/null +++ b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseaseDataManager.java @@ -0,0 +1,54 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.disease; + +import gov.hhs.aspr.ms.gcm.nucleus.DataManager; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; + +public final class DiseaseDataManager extends DataManager { + + private double r0 = 7; + + private double asymptomaticDays; + + private double symptomaticDays; + + private final DiseasePluginData diseasePluginData; + + public DiseaseDataManager(DiseasePluginData diseasePluginData) { + this.diseasePluginData = diseasePluginData; + } + + @Override + public void init(DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + + r0 = diseasePluginData.getR0(); + asymptomaticDays = diseasePluginData.getAsymptomaticDays(); + symptomaticDays = diseasePluginData.getSymptomaticDays(); + + } + + public double getR0() { + return r0; + } + + public double getAsymptomaticDays() { + return asymptomaticDays; + } + + public double getSymptomaticDays() { + return symptomaticDays; + } + + public void setR0(double r0) { + this.r0 = r0; + } + + public void setAsymptomaticDays(double asymptomaticDays) { + this.asymptomaticDays = asymptomaticDays; + } + + public void setSymptomaticDays(double symptomaticDays) { + this.symptomaticDays = symptomaticDays; + } + +} diff --git a/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseasePlugin.java b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseasePlugin.java new file mode 100644 index 000000000..aa53397e4 --- /dev/null +++ b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseasePlugin.java @@ -0,0 +1,23 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.disease; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; + +public final class DiseasePlugin { + + private DiseasePlugin() { + + } + + public static Plugin getDiseasePlugin(DiseasePluginData diseasePluginData) { + + return Plugin.builder()// + .addPluginData(diseasePluginData)// + .setPluginId(DiseasePluginId.PLUGIN_ID)// + .setInitializer((c) -> { + DiseasePluginData pluginData = c.getPluginData(DiseasePluginData.class).get(); + c.addDataManager(new DiseaseDataManager(pluginData)); + })// + .build(); + } + +} diff --git a/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseasePluginData.java b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseasePluginData.java new file mode 100644 index 000000000..c73cba01d --- /dev/null +++ b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseasePluginData.java @@ -0,0 +1,147 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.disease; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.PluginDataBuilder; +import net.jcip.annotations.Immutable; + +@Immutable +public final class DiseasePluginData implements PluginData { + + private static class Data { + + private double r0; + + private double asymptomaticDays; + + private double symptomaticDays; + + private Data() { + } + + private Data(final Data data) { + r0 = data.r0; + asymptomaticDays = data.asymptomaticDays; + symptomaticDays = data.symptomaticDays; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + long temp; + temp = Double.doubleToLongBits(asymptomaticDays); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(r0); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(symptomaticDays); + result = prime * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + if (Double.doubleToLongBits(asymptomaticDays) != Double.doubleToLongBits(other.asymptomaticDays)) { + return false; + } + if (Double.doubleToLongBits(r0) != Double.doubleToLongBits(other.r0)) { + return false; + } + if (Double.doubleToLongBits(symptomaticDays) != Double.doubleToLongBits(other.symptomaticDays)) { + return false; + } + return true; + } + + } + + public static class Builder implements PluginDataBuilder { + private Data data; + + private Builder(final Data data) { + this.data = data; + } + + @Override + public DiseasePluginData build() { + return new DiseasePluginData(new Data(data)); + } + + public Builder setAsymptomaticDays(final double asymptomaticDays) { + data.asymptomaticDays = asymptomaticDays; + return this; + } + + public Builder setR0(final double r0) { + data.r0 = r0; + return this; + } + + public Builder setSymptomaticDays(final double symptomaticDays) { + data.symptomaticDays = symptomaticDays; + return this; + } + + } + + public static Builder builder() { + return new Builder(new Data()); + } + + private final Data data; + + private DiseasePluginData(final Data data) { + this.data = data; + } + + public double getAsymptomaticDays() { + return data.asymptomaticDays; + } + + public double getR0() { + return data.r0; + } + + public double getSymptomaticDays() { + return data.symptomaticDays; + } + + @Override + public PluginDataBuilder getCloneBuilder() { + return new Builder(new Data(data)); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof DiseasePluginData)) { + return false; + } + DiseasePluginData other = (DiseasePluginData) obj; + if (data == null) { + if (other.data != null) { + return false; + } + } else if (!data.equals(other.data)) { + return false; + } + return true; + } + +} diff --git a/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseasePluginId.java b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseasePluginId.java new file mode 100644 index 000000000..bbc3a27dd --- /dev/null +++ b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseasePluginId.java @@ -0,0 +1,18 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.disease; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.nucleus.SimplePluginId; + +/** + * Static plugin id implementation for the GlobalsPlugin + * + * + */ + +public final class DiseasePluginId implements PluginId { + private DiseasePluginId() { + }; + + public final static PluginId PLUGIN_ID = new SimplePluginId("disease plugin id"); + +} diff --git a/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelActor.java b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelActor.java new file mode 100644 index 000000000..ce99692e5 --- /dev/null +++ b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelActor.java @@ -0,0 +1,23 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.disease.DiseaseDataManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.policy.PolicyDataManager; +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; + +public final class ModelActor { + /* start code_ref=output_model_actor_init|code_cap=The model actor now reports output via the release output method provided by its context. */ + public void init(ActorContext actorContext) { + DiseaseDataManager diseaseDataManager = actorContext.getDataManager(DiseaseDataManager.class); + actorContext.releaseOutput("Model Actor initializing"); + String tab = "\t"; + actorContext.releaseOutput(tab + "r0 = " + diseaseDataManager.getR0()); + actorContext.releaseOutput(tab + "asymptomatic days = " + diseaseDataManager.getAsymptomaticDays()); + actorContext.releaseOutput(tab + "symptomatic days = " + diseaseDataManager.getSymptomaticDays()); + PolicyDataManager policyDataManager = actorContext.getDataManager(PolicyDataManager.class); + actorContext.releaseOutput( + tab + "school closing infection rate = " + policyDataManager.getSchoolClosingInfectionRate()); + actorContext + .releaseOutput(tab + "distribute vaccine locally = " + policyDataManager.distributeVaccineLocally()); + } + /* end */ +} diff --git a/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java new file mode 100644 index 000000000..ff58356b9 --- /dev/null +++ b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java @@ -0,0 +1,21 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; + +public final class ModelPlugin { + + private ModelPlugin() { + + } + + public static Plugin getModelPlugin() { + + return Plugin.builder()// + .setPluginId(ModelPluginId.PLUGIN_ID)// + .setInitializer((c) -> { + c.addActor(new ModelActor()::init); + })// + .build(); + } + +} diff --git a/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java new file mode 100644 index 000000000..4d0a6a3cd --- /dev/null +++ b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java @@ -0,0 +1,18 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.nucleus.SimplePluginId; + +/** + * Static plugin id implementation for the GlobalsPlugin + * + * + */ + +public final class ModelPluginId implements PluginId { + private ModelPluginId() { + }; + + public final static PluginId PLUGIN_ID = new SimplePluginId("model plugin id"); + +} diff --git a/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyDataManager.java b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyDataManager.java new file mode 100644 index 000000000..c01f74f6c --- /dev/null +++ b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyDataManager.java @@ -0,0 +1,39 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.policy; + +import gov.hhs.aspr.ms.gcm.nucleus.DataManager; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; + +public final class PolicyDataManager extends DataManager { + private double schoolClosingInfectionRate; + private boolean distributeVaccineLocally; + + private final PolicyPluginData policyPluginData; + + public PolicyDataManager(PolicyPluginData policyPluginData) { + this.policyPluginData = policyPluginData; + } + + @Override + public void init(DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + schoolClosingInfectionRate = policyPluginData.getSchoolClosingInfectionRate(); + distributeVaccineLocally = policyPluginData.distributeVaccineLocally(); + } + + public double getSchoolClosingInfectionRate() { + return schoolClosingInfectionRate; + } + + public void setSchoolClosingInfectionRate(double schoolClosingInfectionRate) { + this.schoolClosingInfectionRate = schoolClosingInfectionRate; + } + + public boolean distributeVaccineLocally() { + return distributeVaccineLocally; + } + + public void setDistributeVaccineLocally(boolean distributeVaccineLocally) { + this.distributeVaccineLocally = distributeVaccineLocally; + } + +} diff --git a/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyPlugin.java b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyPlugin.java new file mode 100644 index 000000000..3f1e6e8ad --- /dev/null +++ b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyPlugin.java @@ -0,0 +1,23 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.policy; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; + +public final class PolicyPlugin { + + private PolicyPlugin() { + + } + + public static Plugin getPolicyPlugin(PolicyPluginData policyPluginData) { + + return Plugin.builder()// + .addPluginData(policyPluginData)// + .setPluginId(PolicyPluginId.PLUGIN_ID)// + .setInitializer((c) -> { + PolicyPluginData pluginData = c.getPluginData(PolicyPluginData.class).get(); + c.addDataManager(new PolicyDataManager(pluginData)); + })// + .build(); + } + +} diff --git a/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyPluginData.java b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyPluginData.java new file mode 100644 index 000000000..f488dcc83 --- /dev/null +++ b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyPluginData.java @@ -0,0 +1,129 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.policy; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.PluginDataBuilder; +import net.jcip.annotations.Immutable; + +@Immutable +public final class PolicyPluginData implements PluginData { + + private static class Data { + + private double schoolClosingInfectionRate; + private boolean distributeVaccineLocally; + + private Data() { + } + + private Data(final Data data) { + schoolClosingInfectionRate = data.schoolClosingInfectionRate; + distributeVaccineLocally = data.distributeVaccineLocally; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (distributeVaccineLocally ? 1231 : 1237); + long temp; + temp = Double.doubleToLongBits(schoolClosingInfectionRate); + result = prime * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + if (distributeVaccineLocally != other.distributeVaccineLocally) { + return false; + } + if (Double.doubleToLongBits(schoolClosingInfectionRate) != Double + .doubleToLongBits(other.schoolClosingInfectionRate)) { + return false; + } + return true; + } + + } + + public static class Builder implements PluginDataBuilder { + private Data data; + + private Builder(final Data data) { + this.data = data; + } + + @Override + public PolicyPluginData build() { + return new PolicyPluginData(new Data(data)); + } + + public Builder setSchoolClosingInfectionRate(double schoolClosingInfectionRate) { + data.schoolClosingInfectionRate = schoolClosingInfectionRate; + return this; + } + + public Builder setDistributeVaccineLocally(boolean distributeVaccineLocally) { + data.distributeVaccineLocally = distributeVaccineLocally; + return this; + } + + } + + public static Builder builder() { + return new Builder(new Data()); + } + + private final Data data; + + private PolicyPluginData(final Data data) { + this.data = data; + } + + public double getSchoolClosingInfectionRate() { + return data.schoolClosingInfectionRate; + } + + public boolean distributeVaccineLocally() { + return data.distributeVaccineLocally; + } + + @Override + public PluginDataBuilder getCloneBuilder() { + return new Builder(new Data(data)); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof PolicyPluginData)) { + return false; + } + PolicyPluginData other = (PolicyPluginData) obj; + if (data == null) { + if (other.data != null) { + return false; + } + } else if (!data.equals(other.data)) { + return false; + } + return true; + } + +} diff --git a/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyPluginId.java b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyPluginId.java new file mode 100644 index 000000000..a53922cf6 --- /dev/null +++ b/tutorials/lessons/lesson_10_output/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyPluginId.java @@ -0,0 +1,18 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.policy; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.nucleus.SimplePluginId; + +/** + * Static plugin id implementation for the GlobalsPlugin + * + * + */ + +public final class PolicyPluginId implements PluginId { + private PolicyPluginId() { + }; + + public final static PluginId PLUGIN_ID = new SimplePluginId("policy plugin id"); + +} diff --git a/tutorials/lessons/lesson_10_output/src/main/resources/output/output_consumer_B_output.txt b/tutorials/lessons/lesson_10_output/src/main/resources/output/output_consumer_B_output.txt new file mode 100644 index 000000000..6428252ee --- /dev/null +++ b/tutorials/lessons/lesson_10_output/src/main/resources/output/output_consumer_B_output.txt @@ -0,0 +1,33 @@ +/* start code_ref=output_consumer_B_output|code_cap=The output is still a bit scrambled, but each row now has the relevant scenario id.*/ +scenario 1: Model Actor initializing +scenario 2: Model Actor initializing +scenario 3: Model Actor initializing +scenario 0: Model Actor initializing +scenario 3: r0 = 1.5 +scenario 0: r0 = 1.5 +scenario 2: r0 = 2.5 +scenario 2: asymptomatic days = 4.0 +scenario 1: r0 = 2.0 +scenario 2: symptomatic days = 12.0 +scenario 1: asymptomatic days = 4.0 +scenario 0: asymptomatic days = 4.0 +scenario 2: school closing infection rate = 0.05 +scenario 3: asymptomatic days = 4.0 +scenario 2: distribute vaccine locally = false +scenario 0: symptomatic days = 12.0 +scenario 1: symptomatic days = 12.0 +scenario 0: school closing infection rate = 0.05 +scenario 1: school closing infection rate = 0.05 +scenario 3: symptomatic days = 12.0 +scenario 3: school closing infection rate = 0.1 +scenario 1: distribute vaccine locally = false +scenario 0: distribute vaccine locally = false +scenario 3: distribute vaccine locally = false +scenario 5: Model Actor initializing +scenario 4: Model Actor initializing +scenario 5: r0 = 2.5 +scenario 4: r0 = 2.0 + +... + +/* end */ \ No newline at end of file diff --git a/tutorials/lessons/lesson_10_output/src/main/resources/output/output_consumer_C_output_1.txt b/tutorials/lessons/lesson_10_output/src/main/resources/output/output_consumer_C_output_1.txt new file mode 100644 index 000000000..ad5ef7448 --- /dev/null +++ b/tutorials/lessons/lesson_10_output/src/main/resources/output/output_consumer_C_output_1.txt @@ -0,0 +1,20 @@ +/* start code_ref=output_consumer_C_output_1|code_cap=The first output lines for OutputConsumer_C.*/ + +the experiment is open +scenario 0 is open +scenario 1 is open +scenario 2 is open +scenario 3 is open +scenario 0: Model Actor initializing +scenario 2: Model Actor initializing +scenario 1: Model Actor initializing +scenario 2: r0 = 2.5 +scenario 3: Model Actor initializing +scenario 2: asymptomatic days = 4.0 +scenario 1: r0 = 2.0 +scenario 0: r0 = 1.5 +scenario 2: symptomatic days = 12.0 + +... + +/* end */ \ No newline at end of file diff --git a/tutorials/lessons/lesson_10_output/src/main/resources/output/output_consumer_C_output_2.txt b/tutorials/lessons/lesson_10_output/src/main/resources/output/output_consumer_C_output_2.txt new file mode 100644 index 000000000..b96b672a4 --- /dev/null +++ b/tutorials/lessons/lesson_10_output/src/main/resources/output/output_consumer_C_output_2.txt @@ -0,0 +1,16 @@ +/* start code_ref=output_consumer_C_output_2|code_cap=The last output lines for OutputConsumer_C.*/ + +... + +scenario 11: Model Actor initializing +scenario 10: distribute vaccine locally = true +scenario 11: r0 = 2.5 +scenario 11: asymptomatic days = 4.0 +scenario 10 is closed +scenario 11: symptomatic days = 12.0 +scenario 11: school closing infection rate = 0.1 +scenario 11: distribute vaccine locally = true +scenario 11 is closed +the experiment is closed + +/* end */ \ No newline at end of file diff --git a/tutorials/lessons/lesson_10_output/src/main/resources/output/output_consumer_D_output.txt b/tutorials/lessons/lesson_10_output/src/main/resources/output/output_consumer_D_output.txt new file mode 100644 index 000000000..d318cb6bb --- /dev/null +++ b/tutorials/lessons/lesson_10_output/src/main/resources/output/output_consumer_D_output.txt @@ -0,0 +1,15 @@ +/* start code_ref=output_consumer_D_output|code_cap=The output of consumer D showing the scenario meta data for the twelve scenarios.*/ +scenario r0 school_closing_infection_rate distribute_vaccine_locally +0 1.5 0.05 false +1 2.0 0.05 false +2 2.5 0.05 false +3 1.5 0.1 false +4 2.0 0.1 false +5 2.5 0.1 false +6 1.5 0.05 true +7 2.0 0.05 true +8 2.5 0.05 true +9 1.5 0.1 true +10 2.0 0.1 true +11 2.5 0.1 true +/* end */ \ No newline at end of file diff --git a/tutorials/lessons/lesson_11_stochastics_plugin/pom.xml b/tutorials/lessons/lesson_11_stochastics_plugin/pom.xml new file mode 100644 index 000000000..b580bd2f2 --- /dev/null +++ b/tutorials/lessons/lesson_11_stochastics_plugin/pom.xml @@ -0,0 +1,79 @@ + + 4.0.0 + + + ASPR + https://www.phe.gov + + + + gov.hhs.aspr.ms.gcm.tutorials + lesson_11_stochastics_plugin + ${revision} + jar + gcm lesson 11 stochastic plugin + Tutorial introducing the stochastics plugin + + + + UTF-8 + 17 + 17 + ${gcm.version} + + 1.3.0 + + 4.0.0-SNAPSHOT + + + + + + gov.hhs.aspr.ms + gcm + ${gcm.version} + + + + + + Shawn Hatch + Leidos + https://www.leidos.com + + + + + + + org.codehaus.mojo + flatten-maven-plugin + ${flatten-maven-plugin.version} + + + + flatten + process-resources + + flatten + + + true + + + + + flatten.clean + clean + + clean + + + + + + + + diff --git a/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_11_A.java b/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_11_A.java new file mode 100644 index 000000000..d92b550dc --- /dev/null +++ b/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_11_A.java @@ -0,0 +1,105 @@ +package gov.hhs.aspr.ms.gcm.lessons; + +import java.util.ArrayList; +import java.util.List; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.disease.DiseasePlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.disease.DiseasePluginData; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.ModelPlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.policy.PolicyPlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.policy.PolicyPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.Dimension; +import gov.hhs.aspr.ms.gcm.nucleus.Experiment; +import gov.hhs.aspr.ms.gcm.nucleus.ExperimentParameterData; +import gov.hhs.aspr.ms.gcm.nucleus.FunctionalDimension; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; + +public final class Example_11_A { + + private Example_11_A() { + } + + private static DiseasePluginData getDiseasePluginData() { + return DiseasePluginData.builder()// + .setR0(1.5)// + .setAsymptomaticDays(4.0)// + .setSymptomaticDays(12.0)// + .build(); + } + + private static PolicyPluginData getPolicyPluginData() { + return PolicyPluginData.builder()// + .setDistributeVaccineLocally(true)// + .setSchoolClosingInfectionRate(0.05)// + .build(); + } + + /* start code_ref=stochastics_plugin_policy_dimension|code_cap=The policy dimension has four levels for the infection rates that trigger school closure.*/ + private static Dimension getPolicyDimension() { + FunctionalDimension.Builder builder = FunctionalDimension.builder();// + + List schoolClosingInfectionRates = new ArrayList<>(); + schoolClosingInfectionRates.add(0.05); + schoolClosingInfectionRates.add(0.10); + schoolClosingInfectionRates.add(0.15); + schoolClosingInfectionRates.add(0.20); + + for (Double schoolClosingInfectionRate : schoolClosingInfectionRates) { + builder.addLevel((context) -> { + PolicyPluginData.Builder pluginDataBuilder = context + .getPluginDataBuilder(PolicyPluginData.Builder.class); + pluginDataBuilder.setSchoolClosingInfectionRate(schoolClosingInfectionRate); + + ArrayList result = new ArrayList<>(); + result.add(Double.toString(schoolClosingInfectionRate)); + + return result; + });// + } + + builder.addMetaDatum("school_closing_infection_rate");// + + return builder.build(); + + } + /* end */ + + /* start code_ref=stochastics_plugin_example_11_A|code_cap=Example 11 introduces the stochastics plugin and executes four scenarios. The random seed for each scenario will be identical. */ + public static void main(String[] args) { + + DiseasePluginData diseasePluginData = getDiseasePluginData(); + Plugin diseasePlugin = DiseasePlugin.getDiseasePlugin(diseasePluginData); + + PolicyPluginData policyPluginData = getPolicyPluginData(); + Plugin policyPlugin = PolicyPlugin.getPolicyPlugin(policyPluginData); + + Plugin modelPlugin = ModelPlugin.getModelPlugin(); + + WellState wellState = WellState.builder().setSeed(0).build(); + StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder().setMainRNGState(wellState) + .build(); + Plugin stochasticsPlugin = StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData); + + Dimension policyDimension = getPolicyDimension(); + + ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()// + .setThreadCount(4)// + .setHaltOnException(true)// + .build(); + + Experiment.builder()// + .addPlugin(stochasticsPlugin)// + .addPlugin(diseasePlugin)// + .addPlugin(modelPlugin)// + .addPlugin(policyPlugin)// + .addDimension(policyDimension)// + .addExperimentContextConsumer(new SimpleOutputConsumer())// + .setExperimentParameterData(experimentParameterData)// + .build()// + .execute(); + } + /* end */ +} diff --git a/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_11_B.java b/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_11_B.java new file mode 100644 index 000000000..6bee9fcad --- /dev/null +++ b/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_11_B.java @@ -0,0 +1,140 @@ +package gov.hhs.aspr.ms.gcm.lessons; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.stream.IntStream; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.disease.DiseasePlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.disease.DiseasePluginData; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.ModelPlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.policy.PolicyPlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.policy.PolicyPluginData; +import gov.hhs.aspr.ms.gcm.nucleus.Dimension; +import gov.hhs.aspr.ms.gcm.nucleus.Experiment; +import gov.hhs.aspr.ms.gcm.nucleus.ExperimentParameterData; +import gov.hhs.aspr.ms.gcm.nucleus.FunctionalDimension; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; + +public final class Example_11_B { + + private Example_11_B() { + } + + private static DiseasePluginData getDiseasePluginData() { + return DiseasePluginData.builder()// + .setR0(1.5)// + .setAsymptomaticDays(4.0)// + .setSymptomaticDays(12.0)// + .build(); + } + + private static PolicyPluginData getPolicyPluginData() { + return PolicyPluginData.builder()// + .setDistributeVaccineLocally(true)// + .setSchoolClosingInfectionRate(0.05)// + .build(); + } + + private static Dimension getPolicyDimension() { + FunctionalDimension.Builder builder = FunctionalDimension.builder();// + + List schoolClosingInfectionRates = new ArrayList<>(); + schoolClosingInfectionRates.add(0.05); + schoolClosingInfectionRates.add(0.10); + schoolClosingInfectionRates.add(0.15); + schoolClosingInfectionRates.add(0.20); + + for (Double schoolClosingInfectionRate : schoolClosingInfectionRates) { + builder.addLevel((context) -> { + PolicyPluginData.Builder pluginDataBuilder = context + .getPluginDataBuilder(PolicyPluginData.Builder.class); + pluginDataBuilder.setSchoolClosingInfectionRate(schoolClosingInfectionRate); + + ArrayList result = new ArrayList<>(); + result.add(Double.toString(schoolClosingInfectionRate)); + + return result; + });// + } + + builder.addMetaDatum("school_closing_infection_rate");// + + return builder.build(); + + } + + /* start code_ref=stochastics_plugin_stochastics_dimension|code_cap=The stochastics dimension introduces three random seeds that will be used in creating the scenarios. Note that seeds are generated outside of the levels within the dimension. */ + private static Dimension getStochasticsDimension(long seed) { + FunctionalDimension.Builder builder = FunctionalDimension.builder();// + + Random random = new Random(seed); + + List seedValues = new ArrayList<>(); + for (int i = 0; i < 3; i++) { + seedValues.add(random.nextLong()); + } + + IntStream.range(0, seedValues.size()).forEach((i) -> { + builder.addLevel((context) -> { + StochasticsPluginData.Builder stochasticsPluginDataBuilder = context + .getPluginDataBuilder(StochasticsPluginData.Builder.class); + long seedValue = seedValues.get(i); + WellState wellState = WellState.builder().setSeed(seedValue).build(); + stochasticsPluginDataBuilder.setMainRNGState(wellState); + + ArrayList result = new ArrayList<>(); + result.add(Integer.toString(i)); + result.add(Long.toString(seedValue) + "L"); + + return result; + }); + }); + + builder.addMetaDatum("seed index");// + builder.addMetaDatum("seed value");// + + return builder.build(); + } + + /* end */ + + /* start code_ref=stochastics_plugin_example_11_B|code_cap=The experiment uses the stochastics dimension, resulting in twelve scenarios.*/ + public static void main(String[] args) { + + DiseasePluginData diseasePluginData = getDiseasePluginData(); + Plugin diseasePlugin = DiseasePlugin.getDiseasePlugin(diseasePluginData); + + PolicyPluginData policyPluginData = getPolicyPluginData(); + Plugin policyPlugin = PolicyPlugin.getPolicyPlugin(policyPluginData); + + Plugin modelPlugin = ModelPlugin.getModelPlugin(); + WellState wellState = WellState.builder().setSeed(0).build(); + StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder().setMainRNGState(wellState) + .build(); + Plugin stochasticsPlugin = StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData); + + Dimension policyDimension = getPolicyDimension(); + Dimension stochasticsDimension = getStochasticsDimension(539847398756272L); + + ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()// + .setThreadCount(4)// + .build(); + + Experiment.builder()// + .addPlugin(stochasticsPlugin)// + .addPlugin(diseasePlugin)// + .addPlugin(modelPlugin)// + .addPlugin(policyPlugin)// + .addDimension(policyDimension)// + .addDimension(stochasticsDimension)// + .addExperimentContextConsumer(new SimpleOutputConsumer())// + .setExperimentParameterData(experimentParameterData)// + .build()// + .execute(); + } + /* end */ +} diff --git a/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/SimpleOutputConsumer.java b/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/SimpleOutputConsumer.java new file mode 100644 index 000000000..18be6ad2d --- /dev/null +++ b/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/SimpleOutputConsumer.java @@ -0,0 +1,33 @@ +package gov.hhs.aspr.ms.gcm.lessons; + +import java.util.StringJoiner; +import java.util.function.Consumer; + +import gov.hhs.aspr.ms.gcm.nucleus.ExperimentContext; + +public class SimpleOutputConsumer implements Consumer { + + @Override + public void accept(ExperimentContext experimentContext) { + experimentContext.subscribeToOutput(Object.class, this::handleOutput); + experimentContext.subscribeToExperimentOpen(this::handleExperimentOpen); + } + + private void handleOutput(ExperimentContext experimentContext, Integer scenarioId, Object output) { + StringJoiner joiner = new StringJoiner("\t", "", ""); + + joiner.add(" " + scenarioId.toString()); + experimentContext.getScenarioMetaData(scenarioId).forEach(joiner::add); + joiner.add(output.toString()); + System.out.println(joiner); + } + + private void handleExperimentOpen(ExperimentContext experimentContext) { + StringJoiner joiner = new StringJoiner("\t", "", ""); + joiner.add(" scenario"); + experimentContext.getExperimentMetaData().forEach(joiner::add); + joiner.add("output"); + System.out.println(joiner); + } + +} diff --git a/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseaseDataManager.java b/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseaseDataManager.java new file mode 100644 index 000000000..351c6696b --- /dev/null +++ b/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseaseDataManager.java @@ -0,0 +1,57 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.disease; + +import gov.hhs.aspr.ms.gcm.nucleus.DataManager; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; + +public final class DiseaseDataManager extends DataManager { + + private double r0 = 7; + + private double asymptomaticDays; + + private double symptomaticDays; + + private final DiseasePluginData diseasePluginData; + + private DataManagerContext dataManagerContext; + + public DiseaseDataManager(DiseasePluginData diseasePluginData) { + this.diseasePluginData = diseasePluginData; + } + + @Override + public void init(DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + this.dataManagerContext = dataManagerContext; + r0 = diseasePluginData.getR0(); + asymptomaticDays = diseasePluginData.getAsymptomaticDays(); + symptomaticDays = diseasePluginData.getSymptomaticDays(); + + } + + public double getR0() { + return r0; + } + + public double getAsymptomaticDays() { + return asymptomaticDays; + } + + public double getSymptomaticDays() { + return symptomaticDays; + } + + public void setR0(double r0) { + this.r0 = r0; + dataManagerContext.releaseOutput("setting R0 to " + r0 + " at time = " + dataManagerContext.getTime()); + } + + public void setAsymptomaticDays(double asymptomaticDays) { + this.asymptomaticDays = asymptomaticDays; + } + + public void setSymptomaticDays(double symptomaticDays) { + this.symptomaticDays = symptomaticDays; + } + +} diff --git a/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseasePlugin.java b/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseasePlugin.java new file mode 100644 index 000000000..aa53397e4 --- /dev/null +++ b/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseasePlugin.java @@ -0,0 +1,23 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.disease; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; + +public final class DiseasePlugin { + + private DiseasePlugin() { + + } + + public static Plugin getDiseasePlugin(DiseasePluginData diseasePluginData) { + + return Plugin.builder()// + .addPluginData(diseasePluginData)// + .setPluginId(DiseasePluginId.PLUGIN_ID)// + .setInitializer((c) -> { + DiseasePluginData pluginData = c.getPluginData(DiseasePluginData.class).get(); + c.addDataManager(new DiseaseDataManager(pluginData)); + })// + .build(); + } + +} diff --git a/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseasePluginData.java b/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseasePluginData.java new file mode 100644 index 000000000..c73cba01d --- /dev/null +++ b/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseasePluginData.java @@ -0,0 +1,147 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.disease; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.PluginDataBuilder; +import net.jcip.annotations.Immutable; + +@Immutable +public final class DiseasePluginData implements PluginData { + + private static class Data { + + private double r0; + + private double asymptomaticDays; + + private double symptomaticDays; + + private Data() { + } + + private Data(final Data data) { + r0 = data.r0; + asymptomaticDays = data.asymptomaticDays; + symptomaticDays = data.symptomaticDays; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + long temp; + temp = Double.doubleToLongBits(asymptomaticDays); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(r0); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(symptomaticDays); + result = prime * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + if (Double.doubleToLongBits(asymptomaticDays) != Double.doubleToLongBits(other.asymptomaticDays)) { + return false; + } + if (Double.doubleToLongBits(r0) != Double.doubleToLongBits(other.r0)) { + return false; + } + if (Double.doubleToLongBits(symptomaticDays) != Double.doubleToLongBits(other.symptomaticDays)) { + return false; + } + return true; + } + + } + + public static class Builder implements PluginDataBuilder { + private Data data; + + private Builder(final Data data) { + this.data = data; + } + + @Override + public DiseasePluginData build() { + return new DiseasePluginData(new Data(data)); + } + + public Builder setAsymptomaticDays(final double asymptomaticDays) { + data.asymptomaticDays = asymptomaticDays; + return this; + } + + public Builder setR0(final double r0) { + data.r0 = r0; + return this; + } + + public Builder setSymptomaticDays(final double symptomaticDays) { + data.symptomaticDays = symptomaticDays; + return this; + } + + } + + public static Builder builder() { + return new Builder(new Data()); + } + + private final Data data; + + private DiseasePluginData(final Data data) { + this.data = data; + } + + public double getAsymptomaticDays() { + return data.asymptomaticDays; + } + + public double getR0() { + return data.r0; + } + + public double getSymptomaticDays() { + return data.symptomaticDays; + } + + @Override + public PluginDataBuilder getCloneBuilder() { + return new Builder(new Data(data)); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof DiseasePluginData)) { + return false; + } + DiseasePluginData other = (DiseasePluginData) obj; + if (data == null) { + if (other.data != null) { + return false; + } + } else if (!data.equals(other.data)) { + return false; + } + return true; + } + +} diff --git a/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseasePluginId.java b/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseasePluginId.java new file mode 100644 index 000000000..bbc3a27dd --- /dev/null +++ b/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/disease/DiseasePluginId.java @@ -0,0 +1,18 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.disease; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.nucleus.SimplePluginId; + +/** + * Static plugin id implementation for the GlobalsPlugin + * + * + */ + +public final class DiseasePluginId implements PluginId { + private DiseasePluginId() { + }; + + public final static PluginId PLUGIN_ID = new SimplePluginId("disease plugin id"); + +} diff --git a/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelActor.java b/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelActor.java new file mode 100644 index 000000000..f49a66804 --- /dev/null +++ b/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelActor.java @@ -0,0 +1,23 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.disease.DiseaseDataManager; +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; + +public final class ModelActor { + + public void init(ActorContext actorContext) { + DiseaseDataManager diseaseDataManager = actorContext.getDataManager(DiseaseDataManager.class); + StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + for (int i = 0; i < 3; i++) { + double deltaTime = randomGenerator.nextDouble() * 10 + 1; + actorContext.addPlan((c) -> { + double newR0Value = randomGenerator.nextDouble() + 1; + diseaseDataManager.setR0(newR0Value); + }, actorContext.getTime() + deltaTime); + } + } +} diff --git a/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java b/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java new file mode 100644 index 000000000..ff58356b9 --- /dev/null +++ b/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java @@ -0,0 +1,21 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; + +public final class ModelPlugin { + + private ModelPlugin() { + + } + + public static Plugin getModelPlugin() { + + return Plugin.builder()// + .setPluginId(ModelPluginId.PLUGIN_ID)// + .setInitializer((c) -> { + c.addActor(new ModelActor()::init); + })// + .build(); + } + +} diff --git a/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java b/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java new file mode 100644 index 000000000..4d0a6a3cd --- /dev/null +++ b/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java @@ -0,0 +1,18 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.nucleus.SimplePluginId; + +/** + * Static plugin id implementation for the GlobalsPlugin + * + * + */ + +public final class ModelPluginId implements PluginId { + private ModelPluginId() { + }; + + public final static PluginId PLUGIN_ID = new SimplePluginId("model plugin id"); + +} diff --git a/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyDataManager.java b/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyDataManager.java new file mode 100644 index 000000000..c01f74f6c --- /dev/null +++ b/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyDataManager.java @@ -0,0 +1,39 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.policy; + +import gov.hhs.aspr.ms.gcm.nucleus.DataManager; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; + +public final class PolicyDataManager extends DataManager { + private double schoolClosingInfectionRate; + private boolean distributeVaccineLocally; + + private final PolicyPluginData policyPluginData; + + public PolicyDataManager(PolicyPluginData policyPluginData) { + this.policyPluginData = policyPluginData; + } + + @Override + public void init(DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + schoolClosingInfectionRate = policyPluginData.getSchoolClosingInfectionRate(); + distributeVaccineLocally = policyPluginData.distributeVaccineLocally(); + } + + public double getSchoolClosingInfectionRate() { + return schoolClosingInfectionRate; + } + + public void setSchoolClosingInfectionRate(double schoolClosingInfectionRate) { + this.schoolClosingInfectionRate = schoolClosingInfectionRate; + } + + public boolean distributeVaccineLocally() { + return distributeVaccineLocally; + } + + public void setDistributeVaccineLocally(boolean distributeVaccineLocally) { + this.distributeVaccineLocally = distributeVaccineLocally; + } + +} diff --git a/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyPlugin.java b/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyPlugin.java new file mode 100644 index 000000000..3f1e6e8ad --- /dev/null +++ b/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyPlugin.java @@ -0,0 +1,23 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.policy; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; + +public final class PolicyPlugin { + + private PolicyPlugin() { + + } + + public static Plugin getPolicyPlugin(PolicyPluginData policyPluginData) { + + return Plugin.builder()// + .addPluginData(policyPluginData)// + .setPluginId(PolicyPluginId.PLUGIN_ID)// + .setInitializer((c) -> { + PolicyPluginData pluginData = c.getPluginData(PolicyPluginData.class).get(); + c.addDataManager(new PolicyDataManager(pluginData)); + })// + .build(); + } + +} diff --git a/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyPluginData.java b/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyPluginData.java new file mode 100644 index 000000000..f488dcc83 --- /dev/null +++ b/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyPluginData.java @@ -0,0 +1,129 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.policy; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.PluginDataBuilder; +import net.jcip.annotations.Immutable; + +@Immutable +public final class PolicyPluginData implements PluginData { + + private static class Data { + + private double schoolClosingInfectionRate; + private boolean distributeVaccineLocally; + + private Data() { + } + + private Data(final Data data) { + schoolClosingInfectionRate = data.schoolClosingInfectionRate; + distributeVaccineLocally = data.distributeVaccineLocally; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (distributeVaccineLocally ? 1231 : 1237); + long temp; + temp = Double.doubleToLongBits(schoolClosingInfectionRate); + result = prime * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + if (distributeVaccineLocally != other.distributeVaccineLocally) { + return false; + } + if (Double.doubleToLongBits(schoolClosingInfectionRate) != Double + .doubleToLongBits(other.schoolClosingInfectionRate)) { + return false; + } + return true; + } + + } + + public static class Builder implements PluginDataBuilder { + private Data data; + + private Builder(final Data data) { + this.data = data; + } + + @Override + public PolicyPluginData build() { + return new PolicyPluginData(new Data(data)); + } + + public Builder setSchoolClosingInfectionRate(double schoolClosingInfectionRate) { + data.schoolClosingInfectionRate = schoolClosingInfectionRate; + return this; + } + + public Builder setDistributeVaccineLocally(boolean distributeVaccineLocally) { + data.distributeVaccineLocally = distributeVaccineLocally; + return this; + } + + } + + public static Builder builder() { + return new Builder(new Data()); + } + + private final Data data; + + private PolicyPluginData(final Data data) { + this.data = data; + } + + public double getSchoolClosingInfectionRate() { + return data.schoolClosingInfectionRate; + } + + public boolean distributeVaccineLocally() { + return data.distributeVaccineLocally; + } + + @Override + public PluginDataBuilder getCloneBuilder() { + return new Builder(new Data(data)); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof PolicyPluginData)) { + return false; + } + PolicyPluginData other = (PolicyPluginData) obj; + if (data == null) { + if (other.data != null) { + return false; + } + } else if (!data.equals(other.data)) { + return false; + } + return true; + } + +} diff --git a/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyPluginId.java b/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyPluginId.java new file mode 100644 index 000000000..a53922cf6 --- /dev/null +++ b/tutorials/lessons/lesson_11_stochastics_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/policy/PolicyPluginId.java @@ -0,0 +1,18 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.policy; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.nucleus.SimplePluginId; + +/** + * Static plugin id implementation for the GlobalsPlugin + * + * + */ + +public final class PolicyPluginId implements PluginId { + private PolicyPluginId() { + }; + + public final static PluginId PLUGIN_ID = new SimplePluginId("policy plugin id"); + +} diff --git a/tutorials/lessons/lesson_11_stochastics_plugin/src/main/resources/output/stochastics_plugin_11_A_output.txt b/tutorials/lessons/lesson_11_stochastics_plugin/src/main/resources/output/stochastics_plugin_11_A_output.txt new file mode 100644 index 000000000..9552b662f --- /dev/null +++ b/tutorials/lessons/lesson_11_stochastics_plugin/src/main/resources/output/stochastics_plugin_11_A_output.txt @@ -0,0 +1,15 @@ +/* start code_ref=stochastics_plugin_11_A_output|code_cap=The output for example 11 shows the four scenarios each reporting three changes to R0 by the model actor.*/ + scenario school_closing_infection_rate output + 1 0.1 setting R0 to 1.432233562051883 at time = 3.252869296309885 + 1 0.1 setting R0 to 1.1828080336720215 at time = 4.115633147309184 + 0 0.05 setting R0 to 1.432233562051883 at time = 3.252869296309885 + 3 0.2 setting R0 to 1.432233562051883 at time = 3.252869296309885 + 1 0.1 setting R0 to 1.895526664357549 at time = 8.614888683848772 + 0 0.05 setting R0 to 1.1828080336720215 at time = 4.115633147309184 + 2 0.15 setting R0 to 1.432233562051883 at time = 3.252869296309885 + 3 0.2 setting R0 to 1.1828080336720215 at time = 4.115633147309184 + 0 0.05 setting R0 to 1.895526664357549 at time = 8.614888683848772 + 2 0.15 setting R0 to 1.1828080336720215 at time = 4.115633147309184 + 3 0.2 setting R0 to 1.895526664357549 at time = 8.614888683848772 + 2 0.15 setting R0 to 1.895526664357549 at time = 8.614888683848772 +/* end */ \ No newline at end of file diff --git a/tutorials/lessons/lesson_11_stochastics_plugin/src/main/resources/output/stochastics_plugin_11_B_output.txt b/tutorials/lessons/lesson_11_stochastics_plugin/src/main/resources/output/stochastics_plugin_11_B_output.txt new file mode 100644 index 000000000..233b1f466 --- /dev/null +++ b/tutorials/lessons/lesson_11_stochastics_plugin/src/main/resources/output/stochastics_plugin_11_B_output.txt @@ -0,0 +1,39 @@ +/* start code_ref=stochastics_plugin_11_B_output|code_cap=The output show that twelve scearnios result in 36 output lines since the model actor update R0 three times per scenario.*/ + scenario school_closing_infection_rate seed_index seed_value output + 3 0.2 0 1768604912325913878L setting R0 to 1.4595120508977955 at time = 5.672294645832067 + 2 0.15 0 1768604912325913878L setting R0 to 1.4595120508977955 at time = 5.672294645832067 + 3 0.2 0 1768604912325913878L setting R0 to 1.672857576347001 at time = 9.396161237311702 + 1 0.1 0 1768604912325913878L setting R0 to 1.4595120508977955 at time = 5.672294645832067 + 0 0.05 0 1768604912325913878L setting R0 to 1.4595120508977955 at time = 5.672294645832067 + 3 0.2 0 1768604912325913878L setting R0 to 1.4552243930124602 at time = 9.545450999459224 + 1 0.1 0 1768604912325913878L setting R0 to 1.672857576347001 at time = 9.396161237311702 + 2 0.15 0 1768604912325913878L setting R0 to 1.672857576347001 at time = 9.396161237311702 + 0 0.05 0 1768604912325913878L setting R0 to 1.672857576347001 at time = 9.396161237311702 + 1 0.1 0 1768604912325913878L setting R0 to 1.4552243930124602 at time = 9.545450999459224 + 2 0.15 0 1768604912325913878L setting R0 to 1.4552243930124602 at time = 9.545450999459224 + 0 0.05 0 1768604912325913878L setting R0 to 1.4552243930124602 at time = 9.545450999459224 + 4 0.05 1 2407662077113051075L setting R0 to 1.7124977513361193 at time = 1.878219018807409 + 4 0.05 1 2407662077113051075L setting R0 to 1.4297609713254456 at time = 2.4215934269433106 + 4 0.05 1 2407662077113051075L setting R0 to 1.5167619787625548 at time = 5.992476899450338 + 5 0.1 1 2407662077113051075L setting R0 to 1.7124977513361193 at time = 1.878219018807409 + 5 0.1 1 2407662077113051075L setting R0 to 1.4297609713254456 at time = 2.4215934269433106 + 5 0.1 1 2407662077113051075L setting R0 to 1.5167619787625548 at time = 5.992476899450338 + 6 0.15 1 2407662077113051075L setting R0 to 1.7124977513361193 at time = 1.878219018807409 + 6 0.15 1 2407662077113051075L setting R0 to 1.4297609713254456 at time = 2.4215934269433106 + 6 0.15 1 2407662077113051075L setting R0 to 1.5167619787625548 at time = 5.992476899450338 + 7 0.2 1 2407662077113051075L setting R0 to 1.7124977513361193 at time = 1.878219018807409 + 7 0.2 1 2407662077113051075L setting R0 to 1.4297609713254456 at time = 2.4215934269433106 + 7 0.2 1 2407662077113051075L setting R0 to 1.5167619787625548 at time = 5.992476899450338 + 9 0.1 2 -2698580492431892402L setting R0 to 1.9665140789775497 at time = 4.990978097055602 + 9 0.1 2 -2698580492431892402L setting R0 to 1.9525439902956225 at time = 6.227836574620975 + 9 0.1 2 -2698580492431892402L setting R0 to 1.8972135699711736 at time = 7.764180353240558 + 8 0.05 2 -2698580492431892402L setting R0 to 1.9665140789775497 at time = 4.990978097055602 + 8 0.05 2 -2698580492431892402L setting R0 to 1.9525439902956225 at time = 6.227836574620975 + 8 0.05 2 -2698580492431892402L setting R0 to 1.8972135699711736 at time = 7.764180353240558 + 10 0.15 2 -2698580492431892402L setting R0 to 1.9665140789775497 at time = 4.990978097055602 + 10 0.15 2 -2698580492431892402L setting R0 to 1.9525439902956225 at time = 6.227836574620975 + 10 0.15 2 -2698580492431892402L setting R0 to 1.8972135699711736 at time = 7.764180353240558 + 11 0.2 2 -2698580492431892402L setting R0 to 1.9665140789775497 at time = 4.990978097055602 + 11 0.2 2 -2698580492431892402L setting R0 to 1.9525439902956225 at time = 6.227836574620975 + 11 0.2 2 -2698580492431892402L setting R0 to 1.8972135699711736 at time = 7.764180353240558 +/* end */ \ No newline at end of file diff --git a/tutorials/lessons/lesson_12_reports_plugin/pom.xml b/tutorials/lessons/lesson_12_reports_plugin/pom.xml new file mode 100644 index 000000000..0a446c3f1 --- /dev/null +++ b/tutorials/lessons/lesson_12_reports_plugin/pom.xml @@ -0,0 +1,79 @@ + + 4.0.0 + + + ASPR + https://www.phe.gov + + + + gov.hhs.aspr.ms.gcm.tutorials + lesson_12_reports_plugin + ${revision} + jar + gcm lesson 12 reports plugin + Tutorial introducing the reports plugin + + + + UTF-8 + 17 + 17 + ${gcm.version} + + 1.3.0 + + 4.0.0-SNAPSHOT + + + + + + gov.hhs.aspr.ms + gcm + ${gcm.version} + + + + + + Shawn Hatch + Leidos + https://www.leidos.com + + + + + + + org.codehaus.mojo + flatten-maven-plugin + ${flatten-maven-plugin.version} + + + + flatten + process-resources + + flatten + + + true + + + + + flatten.clean + clean + + clean + + + + + + + + diff --git a/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_12.java b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_12.java new file mode 100644 index 000000000..a3871b945 --- /dev/null +++ b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_12.java @@ -0,0 +1,117 @@ +package gov.hhs.aspr.ms.gcm.lessons; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.family.FamilyPlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.family.FamilyPluginData; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.ModelLabel; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.ModelPlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.person.PersonPlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.VaccinePlugin; +import gov.hhs.aspr.ms.gcm.nucleus.Dimension; +import gov.hhs.aspr.ms.gcm.nucleus.Experiment; +import gov.hhs.aspr.ms.gcm.nucleus.FunctionalDimension; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.NIOReportItemHandler; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; + +public final class Example_12 { + + private Example_12() { + } + + /* start code_ref=reports_plugin_family_dimension|code_cap=The family dimension set the maximum family size to four values.*/ + private static Dimension getFamilySizeDimension() { + FunctionalDimension.Builder builder = FunctionalDimension.builder();// + + List maxFamilySizes = new ArrayList<>(); + + maxFamilySizes.add(3); + maxFamilySizes.add(5); + maxFamilySizes.add(7); + maxFamilySizes.add(10); + + for (Integer maxFamilySize : maxFamilySizes) { + builder.addLevel((context) -> { + FamilyPluginData.Builder pluginDataBuilder = context + .getPluginDataBuilder(FamilyPluginData.Builder.class); + pluginDataBuilder.setMaxFamilySize(maxFamilySize); + + ArrayList result = new ArrayList<>(); + result.add(Double.toString(maxFamilySize)); + + return result; + });// + } + + builder.addMetaDatum("max_family_size");// + + return builder.build(); + + } + /* end */ + + /* start code_ref=reports_plugin_example_12_plugins|code_cap= Initialization of the various plugins. */ + public static void main(String[] args) throws IOException { + if (args.length == 0) { + throw new RuntimeException("One output directory argument is required"); + } + Path outputDirectory = Paths.get(args[0]); + if (!Files.exists(outputDirectory)) { + Files.createDirectory(outputDirectory); + } else { + if (!Files.isDirectory(outputDirectory)) { + throw new IOException("Provided path is not a directory"); + } + } + + Plugin personPlugin = PersonPlugin.getPersonPlugin(); + + Plugin vaccinePlugin = VaccinePlugin.getVaccinePlugin(); + + Plugin modelPlugin = ModelPlugin.getModelPlugin(); + + WellState wellState = WellState.builder().setSeed(452363456L).build(); + StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder().setMainRNGState(wellState) + .build(); + Plugin stochasticsPlugin = StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData); + + FamilyPluginData familyPluginData = FamilyPluginData.builder()// + .setFamilyCount(30)// + .setMaxFamilySize(5)// + .build(); + Plugin familyPlugin = FamilyPlugin.getFamilyPlugin(familyPluginData); + + /* end */ + + /* start code_ref=reports_plugin_nio|code_cap=The three reports in this experiment each produce report items and release them as output. The NIOReportItemHandler is initialized here by indicating the file associated with each report. */ + NIOReportItemHandler nioReportItemHandler = NIOReportItemHandler.builder()// + .addReport(ModelLabel.FAMILY_VACCINE_REPORT, outputDirectory.resolve("family_vaccine_report.xls"))// + .addReport(ModelLabel.HOURLY_VACCINE_REPORT, outputDirectory.resolve("hourly_vaccine_report.xls"))// + .addReport(ModelLabel.STATELESS_VACCINE_REPORT, outputDirectory.resolve("stateless_vaccine_report.xls"))// + .build(); + /* end */ + + /* start code_ref=reports_plugin_example_12_execution|code_cap=The experiment is executed using the NIOReportItemHandler as an experiment output consumer.*/ + Dimension familySizeDimension = getFamilySizeDimension(); + + Experiment.builder()// + .addPlugin(vaccinePlugin)// + .addPlugin(familyPlugin)// + .addPlugin(personPlugin)// + .addPlugin(modelPlugin)// + .addPlugin(stochasticsPlugin)// + .addDimension(familySizeDimension)// + .addExperimentContextConsumer(nioReportItemHandler)// + .build()// + .execute(); + /* end */ + } +} diff --git a/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/family/FamilyPlugin.java b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/family/FamilyPlugin.java new file mode 100644 index 000000000..1d831c85d --- /dev/null +++ b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/family/FamilyPlugin.java @@ -0,0 +1,24 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.family; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.family.datamanagers.FamilyDataManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.person.PersonPluginId; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; + +public class FamilyPlugin { + + private FamilyPlugin() { + } + + public static Plugin getFamilyPlugin(FamilyPluginData familyPluginData) { + + return Plugin.builder()// + .addPluginData(familyPluginData)// + .setPluginId(FamilyPluginId.PLUGIN_ID)// + .addPluginDependency(PersonPluginId.PLUGIN_ID)// + .setInitializer((c) -> { + FamilyPluginData pluginData = c.getPluginData(FamilyPluginData.class).get(); + c.addDataManager(new FamilyDataManager(pluginData)); + })// + .build(); + } +} diff --git a/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/family/FamilyPluginData.java b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/family/FamilyPluginData.java new file mode 100644 index 000000000..e663d630f --- /dev/null +++ b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/family/FamilyPluginData.java @@ -0,0 +1,144 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.family; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginData; +import gov.hhs.aspr.ms.gcm.nucleus.PluginDataBuilder; +import net.jcip.annotations.Immutable; + +@Immutable +public final class FamilyPluginData implements PluginData { + + private static class Data { + + private int familyCount; + + private int maxFamilySize; + + private Data() { + } + + private Data(final Data data) { + familyCount = data.familyCount; + maxFamilySize = data.maxFamilySize; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + familyCount; + result = prime * result + maxFamilySize; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Data)) { + return false; + } + Data other = (Data) obj; + if (familyCount != other.familyCount) { + return false; + } + if (maxFamilySize != other.maxFamilySize) { + return false; + } + return true; + } + } + + public static class Builder implements PluginDataBuilder { + private Data data; + + private Builder(final Data data) { + this.data = data; + } + + @Override + public FamilyPluginData build() { + return new FamilyPluginData(new Data(data)); + } + + /** + * Sets the family count + * + * @throws IllegalArgumentException + *
          • if the family count is negative
          • + */ + public Builder setFamilyCount(final int familyCount) { + if (familyCount < 0) { + throw new IllegalArgumentException("negative family count"); + } + data.familyCount = familyCount; + return this; + } + + /** + * Sets the maximum family size + * + * @throws IllegalArgumentException + *
          • if the max family size is negative
          • + */ + public Builder setMaxFamilySize(final int maxFamilySize) { + if (maxFamilySize < 0) { + throw new IllegalArgumentException("negative max family count"); + } + data.maxFamilySize = maxFamilySize; + return this; + } + + } + + public static Builder builder() { + return new Builder(new Data()); + } + + private final Data data; + + private FamilyPluginData(final Data data) { + this.data = data; + } + + public int getFamilyCount() { + return data.familyCount; + } + + public int getMaxFamilySize() { + return data.maxFamilySize; + } + + @Override + public PluginDataBuilder getCloneBuilder() { + return new Builder(new Data(data)); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof FamilyPluginData)) { + return false; + } + FamilyPluginData other = (FamilyPluginData) obj; + if (data == null) { + if (other.data != null) { + return false; + } + } else if (!data.equals(other.data)) { + return false; + } + return true; + } + +} diff --git a/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/family/FamilyPluginId.java b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/family/FamilyPluginId.java new file mode 100644 index 000000000..26297a913 --- /dev/null +++ b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/family/FamilyPluginId.java @@ -0,0 +1,18 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.family; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.nucleus.SimplePluginId; + +/** + * Static plugin id implementation for the Family Plugin + * + * + */ + +public final class FamilyPluginId implements PluginId { + private FamilyPluginId() { + }; + + public final static PluginId PLUGIN_ID = new SimplePluginId("family plugin id"); + +} diff --git a/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/family/datamanagers/FamilyDataManager.java b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/family/datamanagers/FamilyDataManager.java new file mode 100644 index 000000000..3083904ad --- /dev/null +++ b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/family/datamanagers/FamilyDataManager.java @@ -0,0 +1,145 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.family.datamanagers; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.family.FamilyPluginData; +import gov.hhs.aspr.ms.gcm.lessons.plugins.family.events.FamilyAdditionEvent; +import gov.hhs.aspr.ms.gcm.lessons.plugins.family.events.FamilyMemberShipAdditionEvent; +import gov.hhs.aspr.ms.gcm.lessons.plugins.family.support.FamilyId; +import gov.hhs.aspr.ms.gcm.lessons.plugins.person.datamanagers.PersonDataManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.person.events.PersonRemovalEvent; +import gov.hhs.aspr.ms.gcm.lessons.plugins.person.support.PersonId; +import gov.hhs.aspr.ms.gcm.nucleus.DataManager; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; +import gov.hhs.aspr.ms.gcm.nucleus.Event; + +public final class FamilyDataManager extends DataManager { + + private int masterFamilyId; + private int initialFamilyCount; + private int maxFamilySize; + private Map> familyMap = new LinkedHashMap<>(); + private Map personMap = new LinkedHashMap<>(); + private PersonDataManager personDataManager; + private final FamilyPluginData familyPluginData; + private DataManagerContext dataManagerContext; + + public FamilyDataManager(FamilyPluginData familyPluginData) { + this.familyPluginData = familyPluginData; + } + + @Override + public void init(DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + this.dataManagerContext = dataManagerContext; + personDataManager = dataManagerContext.getDataManager(PersonDataManager.class); + dataManagerContext.subscribe(PersonRemovalEvent.class, this::handlePersonRemovalEvent); + this.initialFamilyCount = familyPluginData.getFamilyCount(); + this.maxFamilySize = familyPluginData.getMaxFamilySize(); + + dataManagerContext.subscribe(FamilyAdditionMutationEvent.class, this::handleFamilyAdditionMutationEvent); + dataManagerContext.subscribe(FamilyMemberShipAdditionMutationEvent.class, + this::handleFamilyMemberShipAdditionMutationEvent); + } + + private void handlePersonRemovalEvent(DataManagerContext dataManagerContext, + PersonRemovalEvent personRemovalEvent) { + PersonId personId = personRemovalEvent.getPersonId(); + FamilyId familyId = personMap.remove(personId); + if (familyId != null) { + familyMap.get(familyId).remove(personId); + } + System.out.println( + "Family Data Manager is removing person " + personId + " at time = " + dataManagerContext.getTime()); + } + + private static record FamilyAdditionMutationEvent(FamilyId familyId) implements Event { + } + + public FamilyId addFamily() { + FamilyId familyId = new FamilyId(masterFamilyId++); + dataManagerContext.releaseMutationEvent(new FamilyAdditionMutationEvent(familyId)); + return familyId; + } + + private void handleFamilyAdditionMutationEvent(DataManagerContext dataManagerContext, + FamilyAdditionMutationEvent familyAdditionMutationEvent) { + FamilyId familyId = familyAdditionMutationEvent.familyId(); + familyMap.put(familyId, new LinkedHashSet<>()); + dataManagerContext.releaseObservationEvent(new FamilyAdditionEvent(familyId)); + } + + public boolean familyExists(FamilyId familyId) { + return familyMap.keySet().contains(familyId); + } + + public List getFamilyMembers(FamilyId familyId) { + if (!familyExists(familyId)) { + throw new RuntimeException("unknown family " + familyId); + } + return new ArrayList<>(familyMap.get(familyId)); + } + + public int getFamilySize(FamilyId familyId) { + if (!familyExists(familyId)) { + throw new RuntimeException("unknown family " + familyId); + } + return familyMap.get(familyId).size(); + } + + public Set getFamilyIds() { + return new LinkedHashSet<>(familyMap.keySet()); + } + + public Optional getFamilyId(PersonId personId) { + if (!personDataManager.personExists(personId)) { + throw new RuntimeException("unknown person " + personId); + } + FamilyId familyId = personMap.get(personId); + return Optional.ofNullable(familyId); + } + + public int getInitialFamilyCount() { + return this.initialFamilyCount; + } + + public int getMaxFamilySize() { + return this.maxFamilySize; + } + + private static record FamilyMemberShipAdditionMutationEvent(PersonId personId, FamilyId familyId) implements Event { + } + + public void addFamilyMember(PersonId personId, FamilyId familyId) { + dataManagerContext.releaseMutationEvent(new FamilyMemberShipAdditionMutationEvent(personId, familyId)); + } + + private void handleFamilyMemberShipAdditionMutationEvent(DataManagerContext dataManagerContext, + FamilyMemberShipAdditionMutationEvent familyMemberShipAdditionMutationEvent) { + PersonId personId = familyMemberShipAdditionMutationEvent.personId(); + FamilyId familyId = familyMemberShipAdditionMutationEvent.familyId(); + if (!personDataManager.personExists(personId)) { + throw new RuntimeException("unknown person " + personId); + } + if (!familyExists(familyId)) { + throw new RuntimeException("unknown family " + familyId); + } + + FamilyId currentFamilyId = personMap.get(personId); + if (currentFamilyId != null) { + throw new RuntimeException("person " + personId + " is already assigned to family " + currentFamilyId); + } + + familyMap.get(familyId).add(personId); + personMap.put(personId, familyId); + + dataManagerContext.releaseObservationEvent(new FamilyMemberShipAdditionEvent(familyId, personId)); + } + +} diff --git a/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/family/events/FamilyAdditionEvent.java b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/family/events/FamilyAdditionEvent.java new file mode 100644 index 000000000..866aa4b44 --- /dev/null +++ b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/family/events/FamilyAdditionEvent.java @@ -0,0 +1,30 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.family.events; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.family.support.FamilyId; +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import net.jcip.annotations.Immutable; + +@Immutable +public final class FamilyAdditionEvent implements Event { + + private final FamilyId familyId; + + public FamilyAdditionEvent(FamilyId familyId) { + super(); + this.familyId = familyId; + } + + public FamilyId getFamilyId() { + return familyId; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("FamilyAdditionEvent [familyId="); + builder.append(familyId); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/family/events/FamilyMemberShipAdditionEvent.java b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/family/events/FamilyMemberShipAdditionEvent.java new file mode 100644 index 000000000..98f6eb111 --- /dev/null +++ b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/family/events/FamilyMemberShipAdditionEvent.java @@ -0,0 +1,37 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.family.events; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.family.support.FamilyId; +import gov.hhs.aspr.ms.gcm.lessons.plugins.person.support.PersonId; +import gov.hhs.aspr.ms.gcm.nucleus.Event; + +public final class FamilyMemberShipAdditionEvent implements Event { + + private final FamilyId familyId; + private final PersonId personId; + + public FamilyMemberShipAdditionEvent(FamilyId familyId, PersonId personId) { + super(); + this.familyId = familyId; + this.personId = personId; + } + + public FamilyId getFamilyId() { + return familyId; + } + + public PersonId getPersonId() { + return personId; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("FamilyMemberShipAdditionEvent [familyId="); + builder.append(familyId); + builder.append(", personId="); + builder.append(personId); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/family/support/FamilyId.java b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/family/support/FamilyId.java new file mode 100644 index 000000000..5ed72d96a --- /dev/null +++ b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/family/support/FamilyId.java @@ -0,0 +1,47 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.family.support; + +import net.jcip.annotations.Immutable; + +@Immutable +public final class FamilyId implements Comparable { + + private final int id; + + public FamilyId(int id) { + this.id = id; + } + + public int getValue() { + return id; + } + + @Override + public int compareTo(FamilyId familyId) { + return Integer.compare(id, familyId.id); + } + + @Override + public int hashCode() { + return id; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof FamilyId)) { + return false; + } + FamilyId other = (FamilyId) obj; + if (id != other.id) { + return false; + } + return true; + } + + @Override + public String toString() { + return Integer.toString(id); + } +} diff --git a/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelLabel.java b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelLabel.java new file mode 100644 index 000000000..1e3bceea8 --- /dev/null +++ b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelLabel.java @@ -0,0 +1,7 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; + +public enum ModelLabel implements ReportLabel { + FAMILY_VACCINE_REPORT, HOURLY_VACCINE_REPORT, STATELESS_VACCINE_REPORT; +} diff --git a/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java new file mode 100644 index 000000000..0a0ba9748 --- /dev/null +++ b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java @@ -0,0 +1,20 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; + +public class ModelPlugin { + + private ModelPlugin() { + } + + public static Plugin getModelPlugin() { + + return Plugin.builder()// + .setPluginId(ModelPluginId.PLUGIN_ID)// + .setInitializer((c) -> { + c.addActor(new PopulationLoader()::init); + c.addActor(new VaccineScheduler()::init); + })// + .build(); + } +} diff --git a/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java new file mode 100644 index 000000000..332b6f956 --- /dev/null +++ b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java @@ -0,0 +1,18 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.nucleus.SimplePluginId; + +/** + * Static plugin id implementation for the Model Plugin + * + * + */ + +public final class ModelPluginId implements PluginId { + private ModelPluginId() { + }; + + public final static PluginId PLUGIN_ID = new SimplePluginId("model plugin id"); + +} diff --git a/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/PopulationLoader.java b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/PopulationLoader.java new file mode 100644 index 000000000..713cf41a7 --- /dev/null +++ b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/PopulationLoader.java @@ -0,0 +1,49 @@ + +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.family.datamanagers.FamilyDataManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.family.support.FamilyId; +import gov.hhs.aspr.ms.gcm.lessons.plugins.person.datamanagers.PersonDataManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.person.support.PersonId; +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; + +public final class PopulationLoader { + + public void init(ActorContext actorContext) { + + // get the data managers that will be needed to add people and families + PersonDataManager personDataManager = actorContext.getDataManager(PersonDataManager.class); + FamilyDataManager familyDataManager = actorContext.getDataManager(FamilyDataManager.class); + StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + int familyCount = familyDataManager.getInitialFamilyCount(); + int maxFamilySize = familyDataManager.getMaxFamilySize(); + + // add people in families + for (int i = 0; i < familyCount; i++) { + FamilyId familyId = familyDataManager.addFamily(); + int familySize; + if (maxFamilySize < 3) { + familySize = maxFamilySize; + } else { + familySize = randomGenerator.nextInt(maxFamilySize - 2) + 2; + } + + for (int j = 0; j < familySize; j++) { + PersonId personId = personDataManager.addPerson(); + familyDataManager.addFamilyMember(personId, familyId); + } + } + + // add some more individuals + int individualCount = familyCount / 10; + for (int i = 0; i < individualCount; i++) { + personDataManager.addPerson(); + } + } + +} diff --git a/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/VaccineScheduler.java b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/VaccineScheduler.java new file mode 100644 index 000000000..58ae0c2a2 --- /dev/null +++ b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/VaccineScheduler.java @@ -0,0 +1,52 @@ + +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.person.datamanagers.PersonDataManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.person.support.PersonId; +import gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.datamanagers.VaccinationDataManager; +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; + +public final class VaccineScheduler { + + public void init(ActorContext actorContext) { + actorContext.addPlan(this::scheduleVaccinations, 1); + } + + private void scheduleVaccinations(ActorContext actorContext) { + + // get the data managers that will be needed to add people and families + PersonDataManager personDataManager = actorContext.getDataManager(PersonDataManager.class); + StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + // plan out randomized vaccinations, one person per day + List people = new ArrayList<>(personDataManager.getPeople()); + + // randomize the people + Collections.shuffle(people); + + // schedule the vaccinations + double planTime = 1; + for (PersonId personId : people) { + actorContext.addPlan((context) -> vaccinatePerson(context, personId), planTime); + planTime += randomGenerator.nextDouble() * 0.1; + } + } + + private void vaccinatePerson(ActorContext actorContext, PersonId personId) { + // The person may have already been removed from the simulation, so we + // check that before trying to vaccinate them. + PersonDataManager personDataManager = actorContext.getDataManager(PersonDataManager.class); + if (personDataManager.personExists(personId)) { + VaccinationDataManager vaccinationDataManager = actorContext.getDataManager(VaccinationDataManager.class); + vaccinationDataManager.vaccinatePerson(personId); + } + } +} diff --git a/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/person/PersonPlugin.java b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/person/PersonPlugin.java new file mode 100644 index 000000000..015447add --- /dev/null +++ b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/person/PersonPlugin.java @@ -0,0 +1,20 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.person; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.person.datamanagers.PersonDataManager; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; + +public class PersonPlugin { + + private PersonPlugin() { + } + + public static Plugin getPersonPlugin() { + + return Plugin.builder()// + .setPluginId(PersonPluginId.PLUGIN_ID)// + .setInitializer((c) -> { + c.addDataManager(new PersonDataManager()); + })// + .build(); + } +} diff --git a/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/person/PersonPluginId.java b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/person/PersonPluginId.java new file mode 100644 index 000000000..eb9a33a15 --- /dev/null +++ b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/person/PersonPluginId.java @@ -0,0 +1,18 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.person; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.nucleus.SimplePluginId; + +/** + * Static plugin id implementation for the Model Plugin + * + * + */ + +public final class PersonPluginId implements PluginId { + private PersonPluginId() { + }; + + public final static PluginId PLUGIN_ID = new SimplePluginId("person plugin id"); + +} diff --git a/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/person/datamanagers/PersonDataManager.java b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/person/datamanagers/PersonDataManager.java new file mode 100644 index 000000000..a36f11202 --- /dev/null +++ b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/person/datamanagers/PersonDataManager.java @@ -0,0 +1,71 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.person.datamanagers; + +import java.util.LinkedHashSet; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.person.events.PersonAdditionEvent; +import gov.hhs.aspr.ms.gcm.lessons.plugins.person.events.PersonRemovalEvent; +import gov.hhs.aspr.ms.gcm.lessons.plugins.person.support.PersonId; +import gov.hhs.aspr.ms.gcm.nucleus.DataManager; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; +import gov.hhs.aspr.ms.gcm.nucleus.Event; + +public final class PersonDataManager extends DataManager { + + private int masterPersonId; + + private Set people = new LinkedHashSet<>(); + + private DataManagerContext dataManagerContext; + + @Override + public void init(DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + this.dataManagerContext = dataManagerContext; + dataManagerContext.subscribe(PersonAdditionMutationEvent.class, this::handlePersonAdditionMutationEvent); + dataManagerContext.subscribe(PersonRemovalMutationEvent.class, this::handlePersonRemovalMutationEvent); + } + + private static record PersonAdditionMutationEvent(PersonId personId) implements Event { + } + + public PersonId addPerson() { + PersonId personId = new PersonId(masterPersonId++); + dataManagerContext.releaseMutationEvent(new PersonAdditionMutationEvent(personId)); + return personId; + } + + private void handlePersonAdditionMutationEvent(DataManagerContext dataManagerContext, + PersonAdditionMutationEvent personAdditionMutationEvent) { + PersonId personId = personAdditionMutationEvent.personId(); + people.add(personId); + dataManagerContext.releaseObservationEvent(new PersonAdditionEvent(personId)); + } + + public boolean personExists(PersonId personId) { + return people.contains(personId); + } + + public Set getPeople() { + return new LinkedHashSet<>(people); + } + + private static record PersonRemovalMutationEvent(PersonId personId) implements Event { + } + + public void removePerson(PersonId personId) { + dataManagerContext.releaseMutationEvent(new PersonRemovalMutationEvent(personId)); + } + + private void handlePersonRemovalMutationEvent(DataManagerContext dataManagerContext, + PersonRemovalMutationEvent personRemovalMutationEvent) { + PersonId personId = personRemovalMutationEvent.personId(); + if (!personExists(personId)) { + throw new RuntimeException("person " + personId + " does not exist"); + } + people.remove(personId); + dataManagerContext.releaseObservationEvent(new PersonRemovalEvent(personId)); + + } + +} diff --git a/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/person/events/PersonAdditionEvent.java b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/person/events/PersonAdditionEvent.java new file mode 100644 index 000000000..f98998bc7 --- /dev/null +++ b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/person/events/PersonAdditionEvent.java @@ -0,0 +1,20 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.person.events; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.person.support.PersonId; +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import net.jcip.annotations.Immutable; + +@Immutable +public final class PersonAdditionEvent implements Event { + + private final PersonId personId; + + public PersonAdditionEvent(PersonId personId) { + this.personId = personId; + } + + public PersonId getPersonId() { + return personId; + } + +} diff --git a/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/person/events/PersonRemovalEvent.java b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/person/events/PersonRemovalEvent.java new file mode 100644 index 000000000..ad3a62538 --- /dev/null +++ b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/person/events/PersonRemovalEvent.java @@ -0,0 +1,20 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.person.events; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.person.support.PersonId; +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import net.jcip.annotations.Immutable; + +@Immutable +public final class PersonRemovalEvent implements Event { + + private final PersonId personId; + + public PersonRemovalEvent(PersonId personId) { + this.personId = personId; + } + + public PersonId getPersonId() { + return personId; + } + +} diff --git a/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/person/support/PersonId.java b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/person/support/PersonId.java new file mode 100644 index 000000000..f47ce6a7f --- /dev/null +++ b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/person/support/PersonId.java @@ -0,0 +1,47 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.person.support; + +import net.jcip.annotations.Immutable; + +@Immutable +public final class PersonId implements Comparable { + + private final int id; + + public PersonId(int id) { + this.id = id; + } + + public int getValue() { + return id; + } + + @Override + public int compareTo(PersonId personId) { + return Integer.compare(id, personId.id); + } + + @Override + public int hashCode() { + return id; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof PersonId)) { + return false; + } + PersonId other = (PersonId) obj; + if (id != other.id) { + return false; + } + return true; + } + + @Override + public String toString() { + return Integer.toString(id); + } +} diff --git a/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/VaccinePlugin.java b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/VaccinePlugin.java new file mode 100644 index 000000000..2d9d0bc86 --- /dev/null +++ b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/VaccinePlugin.java @@ -0,0 +1,34 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.family.FamilyPluginId; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.ModelLabel; +import gov.hhs.aspr.ms.gcm.lessons.plugins.person.PersonPluginId; +import gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.datamanagers.VaccinationDataManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.reports.FamilyVaccineReport; +import gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.reports.HourlyVaccineReport; +import gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.reports.StatelessVaccineReport; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; + +public class VaccinePlugin { + + private VaccinePlugin() { + } + + public static Plugin getVaccinePlugin() { + + return Plugin.builder()// + .setPluginId(VaccinePluginId.PLUGIN_ID)// + .addPluginDependency(PersonPluginId.PLUGIN_ID)// + .addPluginDependency(FamilyPluginId.PLUGIN_ID)// + .setInitializer((c) -> { + c.addDataManager(new VaccinationDataManager()); + c.addReport(new FamilyVaccineReport(ModelLabel.FAMILY_VACCINE_REPORT)::init);// + c.addReport(new HourlyVaccineReport(ModelLabel.HOURLY_VACCINE_REPORT, ReportPeriod.HOURLY)::init);// + c.addReport( + new StatelessVaccineReport(ModelLabel.STATELESS_VACCINE_REPORT, ReportPeriod.HOURLY)::init);// + })// + .build(); + + } +} diff --git a/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/VaccinePluginId.java b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/VaccinePluginId.java new file mode 100644 index 000000000..0cce5093e --- /dev/null +++ b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/VaccinePluginId.java @@ -0,0 +1,18 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.nucleus.SimplePluginId; + +/** + * Static plugin id implementation for the Vaccine Plugin + * + * + */ + +public final class VaccinePluginId implements PluginId { + private VaccinePluginId() { + }; + + public final static PluginId PLUGIN_ID = new SimplePluginId("vaccine plugin id"); + +} diff --git a/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/datamanagers/VaccinationDataManager.java b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/datamanagers/VaccinationDataManager.java new file mode 100644 index 000000000..a5ab1b78b --- /dev/null +++ b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/datamanagers/VaccinationDataManager.java @@ -0,0 +1,99 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.datamanagers; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.family.datamanagers.FamilyDataManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.family.support.FamilyId; +import gov.hhs.aspr.ms.gcm.lessons.plugins.person.datamanagers.PersonDataManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.person.events.PersonRemovalEvent; +import gov.hhs.aspr.ms.gcm.lessons.plugins.person.support.PersonId; +import gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.events.VaccinationEvent; +import gov.hhs.aspr.ms.gcm.nucleus.DataManager; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; +import gov.hhs.aspr.ms.gcm.nucleus.Event; + +public final class VaccinationDataManager extends DataManager { + + private Set vaccinatedPeople = new LinkedHashSet<>(); + + private PersonDataManager personDataManager; + private FamilyDataManager familyDataManager; + private DataManagerContext dataManagerContext; + + @Override + public void init(DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + dataManagerContext.subscribe(PersonRemovalEvent.class, this::handlePersonRemovalEvent); + personDataManager = dataManagerContext.getDataManager(PersonDataManager.class); + familyDataManager = dataManagerContext.getDataManager(FamilyDataManager.class); + this.dataManagerContext = dataManagerContext; + + dataManagerContext.subscribe(VaccinationMutationEvent.class, this::handleVaccinationMutationEvent); + + } + + private void handlePersonRemovalEvent(DataManagerContext dataManagerContext, + PersonRemovalEvent personRemovalEvent) { + PersonId personId = personRemovalEvent.getPersonId(); + vaccinatedPeople.remove(personId); + } + + public Set getVaccinatedPeople() { + return new LinkedHashSet<>(vaccinatedPeople); + } + + public Set getUnvaccinatedPeople() { + Set people = personDataManager.getPeople(); + people.removeAll(vaccinatedPeople); + return people; + } + + public boolean isPersonVaccinated(PersonId personId) { + if (!personDataManager.personExists(personId)) { + throw new RuntimeException("unknown person " + personId); + } + return vaccinatedPeople.contains(personId); + } + + private static record VaccinationMutationEvent(PersonId personId) implements Event { + } + + public void vaccinatePerson(PersonId personId) { + dataManagerContext.releaseMutationEvent(new VaccinationMutationEvent(personId)); + } + + private void handleVaccinationMutationEvent(DataManagerContext dataManagerContext, + VaccinationMutationEvent vaccinationMutationEvent) { + PersonId personId = vaccinationMutationEvent.personId(); + if (!personDataManager.personExists(personId)) { + throw new RuntimeException("unknown person " + personId); + } + + vaccinatedPeople.add(personId); + dataManagerContext.releaseObservationEvent(new VaccinationEvent(personId)); + + } + + public List getUnvaccinatedFamilyMembers(PersonId personId) { + if (!personDataManager.personExists(personId)) { + throw new RuntimeException("unknown person " + personId); + } + List result = new ArrayList<>(); + Optional optional = familyDataManager.getFamilyId(personId); + if (optional.isPresent()) { + FamilyId familyId = optional.get(); + List familyMembers = familyDataManager.getFamilyMembers(familyId); + for (PersonId familyMemeberId : familyMembers) { + if (!isPersonVaccinated(familyMemeberId)) { + result.add(personId); + } + } + } + return result; + } + +} diff --git a/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/events/VaccinationEvent.java b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/events/VaccinationEvent.java new file mode 100644 index 000000000..1a8c2a448 --- /dev/null +++ b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/events/VaccinationEvent.java @@ -0,0 +1,29 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.events; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.person.support.PersonId; +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import net.jcip.annotations.Immutable; + +@Immutable +public final class VaccinationEvent implements Event { + private final PersonId personId; + + public VaccinationEvent(PersonId personId) { + super(); + this.personId = personId; + } + + public PersonId getPersonId() { + return personId; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("VaccinationEvent [personId="); + builder.append(personId); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/reports/FamilyVaccineReport.java b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/reports/FamilyVaccineReport.java new file mode 100644 index 000000000..e04322caf --- /dev/null +++ b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/reports/FamilyVaccineReport.java @@ -0,0 +1,287 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.reports; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.family.datamanagers.FamilyDataManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.family.events.FamilyAdditionEvent; +import gov.hhs.aspr.ms.gcm.lessons.plugins.family.events.FamilyMemberShipAdditionEvent; +import gov.hhs.aspr.ms.gcm.lessons.plugins.family.support.FamilyId; +import gov.hhs.aspr.ms.gcm.lessons.plugins.person.datamanagers.PersonDataManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.person.events.PersonAdditionEvent; +import gov.hhs.aspr.ms.gcm.lessons.plugins.person.support.PersonId; +import gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.datamanagers.VaccinationDataManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.events.VaccinationEvent; +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import util.wrappers.MutableInteger; + +public class FamilyVaccineReport { + + /* start code_ref=reports_plugin_family_vaccine_report_enums|code_cap=The family vaccine report defines two enums for the vaccination status of families and individuals. */ + private static enum FamilyVaccineStatus { + NONE("unvacinated_families"), // + PARTIAL("partially_vaccinated_families"), // + FULL("fully_vaccinated_families");// + + private final String description; + + private FamilyVaccineStatus(final String description) { + this.description = description; + } + } + + private static enum IndividualVaccineStatus { + NONE("unvaccinated_individuals"), // + FULL("vaccinated_individuals");// + + private final String description; + + private IndividualVaccineStatus(final String description) { + this.description = description; + } + } + /* end */ + + /* start code_ref=reports_plugin_family_vaccine_report_fields|code_cap=The family vaccine report collects summary data as events unfold and requires a few private data structures to record these events. */ + private final ReportLabel reportLabel; + + private ReportHeader reportHeader; + + private ReportContext reportContext; + + private VaccinationDataManager vaccinationDataManager; + + private FamilyDataManager familyDataManager; + + private final Map statusToFamiliesMap = new LinkedHashMap<>(); + + private final Map familyToStatusMap = new LinkedHashMap<>(); + + private final Map statusToIndividualsMap = new LinkedHashMap<>(); + + private final Map individualToStatusMap = new LinkedHashMap<>(); + /* end */ + + /* start code_ref=reports_plugin_family_vaccine_report_constructor|code_cap=The report initializes its data structures.*/ + public FamilyVaccineReport(final ReportLabel reportLabel) { + this.reportLabel = reportLabel; + + final ReportHeader.Builder builder = ReportHeader.builder(); + builder.add("time"); + for (final FamilyVaccineStatus familyVaccineStatus : FamilyVaccineStatus.values()) { + builder.add(familyVaccineStatus.description); + } + for (final IndividualVaccineStatus individualVaccineStatus : IndividualVaccineStatus.values()) { + builder.add(individualVaccineStatus.description); + } + reportHeader = builder.build(); + } + /* end */ + + /* start code_ref=reports_plugin_family_vaccine_report_handling_events|code_cap=The report will need to have handlers for each of the subscribed events. */ + private void handleFamilyAdditionEvent(final ReportContext reportContext, + final FamilyAdditionEvent familyAdditionEvent) { + refreshFamilyStatus(familyAdditionEvent.getFamilyId()); + } + + private void handleFamilyMemberShipAdditionEvent(final ReportContext reportContext, + final FamilyMemberShipAdditionEvent familyMemberShipAdditionEvent) { + individualToStatusMap.remove(familyMemberShipAdditionEvent.getPersonId()); + refreshFamilyStatus(familyMemberShipAdditionEvent.getFamilyId()); + } + + private void handlePersonAdditionEvent(final ReportContext reportContext, + final PersonAdditionEvent personAdditionEvent) { + final PersonId personId = personAdditionEvent.getPersonId(); + final Optional optional = familyDataManager.getFamilyId(personId); + if (optional.isEmpty()) { + refreshIndividualStatus(personId); + } else { + final FamilyId familyId = optional.get(); + refreshFamilyStatus(familyId); + } + } + + private void handleVaccinationEvent(final ReportContext reportContext, final VaccinationEvent vaccinationEvent) { + final PersonId personId = vaccinationEvent.getPersonId(); + + final Optional optional = familyDataManager.getFamilyId(personId); + + if (optional.isEmpty()) { + refreshIndividualStatus(personId); + } else { + final FamilyId familyId = optional.get(); + refreshFamilyStatus(familyId); + } + } + /* end */ + + /* start code_ref=reports_plugin_family_vaccine_report_init_subscriptions|code_cap=The report must subscribe to the events that are pertinent to reporting individual and family vaccination status.*/ + public void init(final ReportContext reportContext) { + this.reportContext = reportContext; + /* + * Subscribe to all the relevant events + */ + reportContext.subscribe(VaccinationEvent.class, this::handleVaccinationEvent); + reportContext.subscribe(FamilyAdditionEvent.class, this::handleFamilyAdditionEvent); + reportContext.subscribe(FamilyMemberShipAdditionEvent.class, this::handleFamilyMemberShipAdditionEvent); + reportContext.subscribe(PersonAdditionEvent.class, this::handlePersonAdditionEvent); + /* end */ + + /* + * Some of the events may have already occurred before we initialize this + * report, so we will need to build up out status maps + */ + + /* start code_ref=reports_plugin_family_vaccine_report_init_setup|code_cap=The local data structures are initialized from the current vaccine states. */ + familyDataManager = reportContext.getDataManager(FamilyDataManager.class); + vaccinationDataManager = reportContext.getDataManager(VaccinationDataManager.class); + PersonDataManager personDataManager = reportContext.getDataManager(PersonDataManager.class); + + for (final FamilyVaccineStatus familyVaccineStatus : FamilyVaccineStatus.values()) { + statusToFamiliesMap.put(familyVaccineStatus, new MutableInteger()); + } + + for (final IndividualVaccineStatus individualVaccineStatus : IndividualVaccineStatus.values()) { + statusToIndividualsMap.put(individualVaccineStatus, new MutableInteger()); + } + /* end */ + + // determine the family vaccine status for every family + /* start code_ref=reports_plugin_family_vaccine_report_init_family_status|code_cap=Determining vaccine status for each family.*/ + for (final FamilyId familyId : familyDataManager.getFamilyIds()) { + + final int familySize = familyDataManager.getFamilySize(familyId); + final List familyMembers = familyDataManager.getFamilyMembers(familyId); + int vaccinatedCount = 0; + for (final PersonId personId : familyMembers) { + if (vaccinationDataManager.isPersonVaccinated(personId)) { + vaccinatedCount++; + } + } + FamilyVaccineStatus status; + + if (vaccinatedCount == 0) { + status = FamilyVaccineStatus.NONE; + } else if (vaccinatedCount == familySize) { + status = FamilyVaccineStatus.FULL; + } else { + status = FamilyVaccineStatus.PARTIAL; + } + + statusToFamiliesMap.get(status).increment(); + familyToStatusMap.put(familyId, status); + + } + /* end */ + + // ensure that any person not assigned to a family is still counted + + /* start code_ref=reports_plugin_family_vaccine_report_init_person_status|code_cap=Capturing individuals who have no family association. */ + for (final PersonId personId : personDataManager.getPeople()) { + if (familyDataManager.getFamilyId(personId).isEmpty()) { + + IndividualVaccineStatus status; + if (vaccinationDataManager.isPersonVaccinated(personId)) { + status = IndividualVaccineStatus.FULL; + } else { + status = IndividualVaccineStatus.NONE; + } + statusToIndividualsMap.get(status).increment(); + individualToStatusMap.put(personId, status); + } + } + /* end */ + + /* start code_ref=reports_plugin_family_vaccine_report_init_releasing_output|code_cap=The initial state of the report is released as a single report item.*/ + releaseReportItem(); + /* end */ + } + + /* + * start code_ref=reports_plugin_family_vaccine_report_refeshing_family_status|code_cap=Events that effect the status of a family are processed centrally. + */ + private void refreshFamilyStatus(final FamilyId familyId) { + + final int familySize = familyDataManager.getFamilySize(familyId); + final List familyMembers = familyDataManager.getFamilyMembers(familyId); + int vaccinatedCount = 0; + for (final PersonId personId : familyMembers) { + if (vaccinationDataManager.isPersonVaccinated(personId)) { + vaccinatedCount++; + } + } + FamilyVaccineStatus newStatus; + + if (vaccinatedCount == 0) { + newStatus = FamilyVaccineStatus.NONE; + } else if (vaccinatedCount == familySize) { + newStatus = FamilyVaccineStatus.FULL; + } else { + newStatus = FamilyVaccineStatus.PARTIAL; + } + + final FamilyVaccineStatus currentStatus = familyToStatusMap.get(familyId); + if (currentStatus == newStatus) { + return; + } + if (currentStatus != null) { + statusToFamiliesMap.get(currentStatus).decrement(); + } + statusToFamiliesMap.get(newStatus).increment(); + familyToStatusMap.put(familyId, newStatus); + releaseReportItem(); + } + /* end */ + + /* + * start code_ref=reports_plugin_family_vaccine_report_refeshing_individual_status|code_cap=Events that effect the status of an individual are processed centrally. + */ + private void refreshIndividualStatus(final PersonId personId) { + IndividualVaccineStatus newStatus; + if (vaccinationDataManager.isPersonVaccinated(personId)) { + newStatus = IndividualVaccineStatus.FULL; + } else { + newStatus = IndividualVaccineStatus.NONE; + } + + final IndividualVaccineStatus currentStatus = individualToStatusMap.get(personId); + + if (currentStatus == newStatus) { + return; + } + + if (currentStatus != null) { + statusToIndividualsMap.get(currentStatus).decrement(); + } + statusToIndividualsMap.get(newStatus).increment(); + individualToStatusMap.put(personId, newStatus); + releaseReportItem(); + } + /* end */ + + /* + * start code_ref=reports_plugin_family_vaccine_report_init_releasing_report_item|code_cap=Each time a family or individual have a relevant change a report item is released. + */ + private void releaseReportItem() { + final ReportItem.Builder builder = ReportItem.builder().setReportLabel(reportLabel) + .setReportHeader(reportHeader); + builder.addValue(reportContext.getTime()); + for (final FamilyVaccineStatus familyVaccineStatus : statusToFamiliesMap.keySet()) { + MutableInteger mutableInteger = statusToFamiliesMap.get(familyVaccineStatus); + builder.addValue(mutableInteger.getValue()); + } + for (final IndividualVaccineStatus individualVaccineStatus : statusToIndividualsMap.keySet()) { + MutableInteger mutableInteger = statusToIndividualsMap.get(individualVaccineStatus); + builder.addValue(mutableInteger.getValue()); + } + final ReportItem reportItem = builder.build(); + reportContext.releaseOutput(reportItem); + } + /* end */ +} diff --git a/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/reports/HourlyVaccineReport.java b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/reports/HourlyVaccineReport.java new file mode 100644 index 000000000..00c1d1d21 --- /dev/null +++ b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/reports/HourlyVaccineReport.java @@ -0,0 +1,227 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.reports; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.family.datamanagers.FamilyDataManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.family.events.FamilyAdditionEvent; +import gov.hhs.aspr.ms.gcm.lessons.plugins.family.events.FamilyMemberShipAdditionEvent; +import gov.hhs.aspr.ms.gcm.lessons.plugins.family.support.FamilyId; +import gov.hhs.aspr.ms.gcm.lessons.plugins.person.datamanagers.PersonDataManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.person.events.PersonAdditionEvent; +import gov.hhs.aspr.ms.gcm.lessons.plugins.person.support.PersonId; +import gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.datamanagers.VaccinationDataManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.events.VaccinationEvent; +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.PeriodicReport; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import util.wrappers.MutableInteger; + +public class HourlyVaccineReport extends PeriodicReport { + /* start code_ref=reports_plugin_hourly_vaccine_constructor|code_cap=The hourly vaccine report covers the same content as the family vaccine report. Rather than report events as they happen, it instead periodically summarizes these events.*/ + public HourlyVaccineReport(ReportLabel reportLabel, ReportPeriod reportPeriod) { + super(reportLabel, reportPeriod); + + ReportHeader.Builder builder = ReportHeader.builder(); + addTimeFieldHeaders(builder); + for (FamilyVaccineStatus familyVaccineStatus : FamilyVaccineStatus.values()) { + builder.add(familyVaccineStatus.description); + } + for (IndividualVaccineStatus individualVaccineStatus : IndividualVaccineStatus.values()) { + builder.add(individualVaccineStatus.description); + } + reportHeader = builder.build(); + } + /* end */ + + private VaccinationDataManager vaccinationDataManager; + + private FamilyDataManager familyDataManager; + + private Map statusToFamiliesMap = new LinkedHashMap<>(); + private Map familyToStatusMap = new LinkedHashMap<>(); + + private Map statusToIndividualsMap = new LinkedHashMap<>(); + private Map individualToStatusMap = new LinkedHashMap<>(); + + private static enum FamilyVaccineStatus { + NONE("unvacinated_families"), PARTIAL("partially_vaccinated_families"), FULL("fully_vaccinated_families"); + + private final String description; + + private FamilyVaccineStatus(String description) { + this.description = description; + } + } + + private static enum IndividualVaccineStatus { + NONE("unvaccinated_individuals"), FULL("vaccinated_individuals"); + + private final String description; + + private IndividualVaccineStatus(String description) { + this.description = description; + } + + } + + private void refreshFamilyStatus(FamilyId familyId) { + + int familySize = familyDataManager.getFamilySize(familyId); + List familyMembers = familyDataManager.getFamilyMembers(familyId); + int vaccinatedCount = 0; + for (PersonId personId : familyMembers) { + if (vaccinationDataManager.isPersonVaccinated(personId)) { + vaccinatedCount++; + } + } + FamilyVaccineStatus newStatus; + + if (vaccinatedCount == 0) { + newStatus = FamilyVaccineStatus.NONE; + } else if (vaccinatedCount == familySize) { + newStatus = FamilyVaccineStatus.FULL; + } else { + newStatus = FamilyVaccineStatus.PARTIAL; + } + + FamilyVaccineStatus currentStatus = familyToStatusMap.get(familyId); + if (currentStatus == newStatus) { + return; + } + if (currentStatus != null) { + statusToFamiliesMap.get(currentStatus).decrement(); + } + statusToFamiliesMap.get(newStatus).increment(); + familyToStatusMap.put(familyId, newStatus); + + } + + private void refreshIndividualStatus(PersonId personId) { + IndividualVaccineStatus newStatus; + if (vaccinationDataManager.isPersonVaccinated(personId)) { + newStatus = IndividualVaccineStatus.FULL; + } else { + newStatus = IndividualVaccineStatus.NONE; + } + + IndividualVaccineStatus currentStatus = individualToStatusMap.get(personId); + + if (currentStatus == newStatus) { + return; + } + + if (currentStatus != null) { + statusToIndividualsMap.get(currentStatus).decrement(); + } + statusToIndividualsMap.get(newStatus).increment(); + individualToStatusMap.put(personId, newStatus); + + } + + /* start code_ref=reports_plugin_hourly_vaccine_initialization|code_cap=The same subscriptions are created as before.*/ + protected void prepare(ReportContext reportContext) { + + /* + * Subscribe to all the relevant events + */ + + reportContext.subscribe(VaccinationEvent.class, this::handleVaccinationEvent); + reportContext.subscribe(FamilyAdditionEvent.class, this::handleFamilyAdditionEvent); + reportContext.subscribe(FamilyMemberShipAdditionEvent.class, this::handleFamilyMemberShipAdditionEvent); + reportContext.subscribe(PersonAdditionEvent.class, this::handlePersonAdditionEvent); + + /* end */ + + /* + * Some of the events may have already occurred before we initialize this + * report, so we will need to build up out status maps + */ + + familyDataManager = reportContext.getDataManager(FamilyDataManager.class); + vaccinationDataManager = reportContext.getDataManager(VaccinationDataManager.class); + PersonDataManager personDataManager = reportContext.getDataManager(PersonDataManager.class); + + for (FamilyVaccineStatus familyVaccineStatus : FamilyVaccineStatus.values()) { + statusToFamiliesMap.put(familyVaccineStatus, new MutableInteger()); + } + + for (IndividualVaccineStatus individualVaccineStatus : IndividualVaccineStatus.values()) { + statusToIndividualsMap.put(individualVaccineStatus, new MutableInteger()); + } + + // determine the family vaccine status for every family + for (FamilyId familyId : familyDataManager.getFamilyIds()) { + refreshFamilyStatus(familyId); + } + + // ensure that any person not assigned to a family is still counted + for (PersonId personId : personDataManager.getPeople()) { + if (familyDataManager.getFamilyId(personId).isEmpty()) { + refreshIndividualStatus(personId); + } + } + } + + private void handlePersonAdditionEvent(ReportContext reportContext, PersonAdditionEvent personAdditionEvent) { + PersonId personId = personAdditionEvent.getPersonId(); + Optional optional = familyDataManager.getFamilyId(personId); + if (optional.isEmpty()) { + refreshIndividualStatus(personId); + } else { + FamilyId familyId = optional.get(); + refreshFamilyStatus(familyId); + } + + } + + private void handleVaccinationEvent(ReportContext reportContext, VaccinationEvent vaccinationEvent) { + PersonId personId = vaccinationEvent.getPersonId(); + + Optional optional = familyDataManager.getFamilyId(personId); + + if (optional.isEmpty()) { + refreshIndividualStatus(personId); + } else { + FamilyId familyId = optional.get(); + refreshFamilyStatus(familyId); + } + } + + private void handleFamilyAdditionEvent(ReportContext reportContext, FamilyAdditionEvent familyAdditionEvent) { + refreshFamilyStatus(familyAdditionEvent.getFamilyId()); + } + + private void handleFamilyMemberShipAdditionEvent(ReportContext reportContext, + FamilyMemberShipAdditionEvent familyMemberShipAdditionEvent) { + individualToStatusMap.remove(familyMemberShipAdditionEvent.getPersonId()); + refreshFamilyStatus(familyMemberShipAdditionEvent.getFamilyId()); + } + + private ReportHeader reportHeader; + + @Override + /* start code_ref=reports_plugin_hourly_vaccine_flush|code_cap=Once an hour the report releases a report item that summarizes the family and individual vaccine status. */ + protected void flush(ReportContext reportContext) { + ReportItem.Builder builder = ReportItem.builder()// + .setReportLabel(getReportLabel())// + .setReportHeader(reportHeader); + fillTimeFields(builder); + for (FamilyVaccineStatus familyVaccineStatus : statusToFamiliesMap.keySet()) { + MutableInteger mutableInteger = statusToFamiliesMap.get(familyVaccineStatus); + builder.addValue(mutableInteger.getValue()); + } + for (IndividualVaccineStatus individualVaccineStatus : statusToIndividualsMap.keySet()) { + MutableInteger mutableInteger = statusToIndividualsMap.get(individualVaccineStatus); + builder.addValue(mutableInteger.getValue()); + } + ReportItem reportItem = builder.build(); + reportContext.releaseOutput(reportItem); + } + /* end */ +} diff --git a/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/reports/StatelessVaccineReport.java b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/reports/StatelessVaccineReport.java new file mode 100644 index 000000000..97a0c4af9 --- /dev/null +++ b/tutorials/lessons/lesson_12_reports_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/reports/StatelessVaccineReport.java @@ -0,0 +1,118 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.reports; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.family.datamanagers.FamilyDataManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.family.support.FamilyId; +import gov.hhs.aspr.ms.gcm.lessons.plugins.person.datamanagers.PersonDataManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.person.support.PersonId; +import gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.datamanagers.VaccinationDataManager; +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.PeriodicReport; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import util.wrappers.MutableInteger; + +public class StatelessVaccineReport extends PeriodicReport { + + private static enum VaccineStatus { + FAMILY_NONE("unvacinated_families"), FAMILY_PARTIAL("partially_vaccinated_families"), + FAMILY_FULL("fully_vaccinated_families"), INDIVIDUAL_NONE("unvaccinated_individuals"), + INDIVIDUAL_FULL("vaccinated_individuals"); + + private final String description; + + private VaccineStatus(String description) { + this.description = description; + } + } + + public StatelessVaccineReport(ReportLabel reportLabel, ReportPeriod reportPeriod) { + super(reportLabel, reportPeriod); + } + + @Override + /* start code_ref=reports_plugin_stateless_vaccine_flush|code_cap= The stateless vaccine report does not process any events. Instead, it periodically derives the report item by polling the relevant data managers.*/ + protected void flush(ReportContext reportContext) { + + FamilyDataManager familyDataManager = reportContext.getDataManager(FamilyDataManager.class); + VaccinationDataManager vaccinationDataManager = reportContext.getDataManager(VaccinationDataManager.class); + PersonDataManager personDataManager = reportContext.getDataManager(PersonDataManager.class); + + Map statusMap = new LinkedHashMap<>(); + for (VaccineStatus vaccineStatus : VaccineStatus.values()) { + statusMap.put(vaccineStatus, new MutableInteger()); + } + + // determine the family vaccine status for every family + for (FamilyId familyId : familyDataManager.getFamilyIds()) { + VaccineStatus vaccineStatus = getFamilyStatus(familyId, vaccinationDataManager, familyDataManager); + statusMap.get(vaccineStatus).increment(); + } + + // ensure that any person not assigned to a family is still counted + for (PersonId personId : personDataManager.getPeople()) { + if (familyDataManager.getFamilyId(personId).isEmpty()) { + VaccineStatus vaccineStatus = getIndividualStatus(personId, vaccinationDataManager); + statusMap.get(vaccineStatus).increment(); + } + } + ReportHeader.Builder headerBuilder = ReportHeader.builder(); + addTimeFieldHeaders(headerBuilder); + for (VaccineStatus vaccineStatus : VaccineStatus.values()) { + headerBuilder.add(vaccineStatus.description); + } + ReportHeader reportHeader = headerBuilder.build(); + + ReportItem.Builder builder = ReportItem.builder()// + .setReportLabel(getReportLabel())// + .setReportHeader(reportHeader); + fillTimeFields(builder); + for (VaccineStatus vaccineStatus : VaccineStatus.values()) { + int value = statusMap.get(vaccineStatus).getValue(); + builder.addValue(value); + } + + ReportItem reportItem = builder.build(); + reportContext.releaseOutput(reportItem); + + } + /* end */ + + private VaccineStatus getFamilyStatus(FamilyId familyId, VaccinationDataManager vaccinationDataManager, + FamilyDataManager familyDataManager) { + + int familySize = familyDataManager.getFamilySize(familyId); + List familyMembers = familyDataManager.getFamilyMembers(familyId); + int vaccinatedCount = 0; + for (PersonId personId : familyMembers) { + if (vaccinationDataManager.isPersonVaccinated(personId)) { + vaccinatedCount++; + } + } + VaccineStatus result; + + if (vaccinatedCount == 0) { + result = VaccineStatus.FAMILY_NONE; + } else if (vaccinatedCount == familySize) { + result = VaccineStatus.FAMILY_FULL; + } else { + result = VaccineStatus.FAMILY_PARTIAL; + } + return result; + } + + private VaccineStatus getIndividualStatus(PersonId personId, VaccinationDataManager vaccinationDataManager) { + VaccineStatus result; + if (vaccinationDataManager.isPersonVaccinated(personId)) { + result = VaccineStatus.INDIVIDUAL_FULL; + } else { + result = VaccineStatus.INDIVIDUAL_NONE; + } + return result; + } +} diff --git a/tutorials/lessons/lesson_12_reports_plugin/src/main/resources/output/reports_plugin_family_vaccine_output.txt b/tutorials/lessons/lesson_12_reports_plugin/src/main/resources/output/reports_plugin_family_vaccine_output.txt new file mode 100644 index 000000000..78aae4386 --- /dev/null +++ b/tutorials/lessons/lesson_12_reports_plugin/src/main/resources/output/reports_plugin_family_vaccine_output.txt @@ -0,0 +1,26 @@ +/* start code_ref=reports_plugin_family_vaccine_output|code_cap=Excerpt of the family vaccine report.*/ + scenario max_family_size time unvacinated_families partially_vaccinated_families fully_vaccinated_families unvaccinated_individuals vaccinated_individuals +0 3.0 0.0 0 0 0 0 0 +0 3.0 0.0 1 0 0 0 0 +0 3.0 0.0 1 0 0 1 0 +0 3.0 0.0 1 0 0 2 0 +0 3.0 0.0 2 0 0 2 0 +... +0 3.0 0.0 15 0 0 30 0 +0 3.0 0.0 16 0 0 30 0 +0 3.0 0.0 16 0 0 31 0 +0 3.0 0.0 16 0 0 32 0 +0 3.0 0.0 17 0 0 32 0 +... +0 3.0 1.0603090039611633 28 2 0 63 0 +0 3.0 1.114413651330966 27 3 0 63 0 +0 3.0 1.1516487861564502 26 4 0 63 0 +0 3.0 1.1871612468129367 25 5 0 63 0 +0 3.0 1.2426374003057261 25 4 1 63 0 +... +3 10.0 8.981789652547315 0 4 26 163 3 +3 10.0 9.047774924066472 0 3 27 163 3 +3 10.0 9.101648563188967 0 2 28 163 3 +3 10.0 9.18260688675053 0 1 29 163 3 +3 10.0 9.210064962863902 0 0 30 163 3 +/* end */ \ No newline at end of file diff --git a/tutorials/lessons/lesson_12_reports_plugin/src/main/resources/output/reports_plugin_hourly_vaccine_output.txt b/tutorials/lessons/lesson_12_reports_plugin/src/main/resources/output/reports_plugin_hourly_vaccine_output.txt new file mode 100644 index 000000000..0e1f3e23d --- /dev/null +++ b/tutorials/lessons/lesson_12_reports_plugin/src/main/resources/output/reports_plugin_hourly_vaccine_output.txt @@ -0,0 +1,26 @@ +/* start code_ref=reports_plugin_hourly_vaccine_output|code_cap=Excerpt from the hourly vaccine report.*/ +scenario max_family_size day hour unvacinated_families partially_vaccinated_families fully_vaccinated_families unvaccinated_individuals vaccinated_individuals +0 3.0 0 0 0 0 0 0 0 +0 3.0 0 1 30 0 0 63 0 +0 3.0 0 2 30 0 0 63 0 +0 3.0 0 3 30 0 0 63 0 +0 3.0 0 4 30 0 0 63 0 +... +0 3.0 2 11 9 17 4 62 1 +0 3.0 2 12 9 17 4 62 1 +0 3.0 2 13 8 17 5 62 1 +0 3.0 2 14 8 17 5 62 1 +0 3.0 2 15 8 17 5 62 1 +... +2 7.0 3 3 8 21 1 128 1 +2 7.0 3 4 8 21 1 128 1 +2 7.0 3 5 8 21 1 128 1 +2 7.0 3 6 8 21 1 128 1 +2 7.0 3 7 8 21 1 128 1 +... +3 10.0 9 2 0 3 27 163 3 +3 10.0 9 3 0 2 28 163 3 +3 10.0 9 4 0 2 28 163 3 +3 10.0 9 5 0 1 29 163 3 +3 10.0 9 6 0 0 30 163 3 +/* end */ \ No newline at end of file diff --git a/tutorials/lessons/lesson_13_global_properties_plugin/pom.xml b/tutorials/lessons/lesson_13_global_properties_plugin/pom.xml new file mode 100644 index 000000000..464296204 --- /dev/null +++ b/tutorials/lessons/lesson_13_global_properties_plugin/pom.xml @@ -0,0 +1,79 @@ + + 4.0.0 + + + ASPR + https://www.phe.gov + + + + gov.hhs.aspr.ms.gcm.tutorials + lesson_13_global_properties_plugin + ${revision} + jar + gcm lesson 13 global properties plugin + Tutorial introducing the global properties plugin + + + + UTF-8 + 17 + 17 + ${gcm.version} + + 1.3.0 + + 4.0.0-SNAPSHOT + + + + + + gov.hhs.aspr.ms + gcm + ${gcm.version} + + + + + + Shawn Hatch + Leidos + https://www.leidos.com + + + + + + + org.codehaus.mojo + flatten-maven-plugin + ${flatten-maven-plugin.version} + + + + flatten + process-resources + + flatten + + + true + + + + + flatten.clean + clean + + clean + + + + + + + + diff --git a/tutorials/lessons/lesson_13_global_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_13.java b/tutorials/lessons/lesson_13_global_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_13.java new file mode 100644 index 000000000..643cee1ac --- /dev/null +++ b/tutorials/lessons/lesson_13_global_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_13.java @@ -0,0 +1,140 @@ +package gov.hhs.aspr.ms.gcm.lessons; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.math3.util.Pair; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.ModelPlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.ModelReportLabel; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.GlobalProperty; +import gov.hhs.aspr.ms.gcm.nucleus.Dimension; +import gov.hhs.aspr.ms.gcm.nucleus.Experiment; +import gov.hhs.aspr.ms.gcm.nucleus.FunctionalDimension; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.GlobalPropertiesPlugin; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.reports.GlobalPropertyReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.NIOReportItemHandler; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; + +public final class Example_13 { + + private Example_13() { + } + + /* start code_ref= global_proerties_plugin_get_property_data|code_cap=Initializing the global properties plugin data with three property definitions.*/ + private static GlobalPropertiesPluginData getGlobalPropertiesPluginData() { + GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder();// + + PropertyDefinition propertyDefinition = PropertyDefinition.builder()// + .setType(Double.class)// + .setDefaultValue(2.0)// + .setPropertyValueMutability(false)// + .build(); + builder.defineGlobalProperty(GlobalProperty.ALPHA, propertyDefinition, 0); + + propertyDefinition = PropertyDefinition.builder()// + .setType(Double.class)// + .setDefaultValue(5.0)// + .setPropertyValueMutability(false)// + .build(); + + builder.defineGlobalProperty(GlobalProperty.BETA, propertyDefinition, 0); + + propertyDefinition = PropertyDefinition.builder()// + .setType(Double.class)// + .setDefaultValue(1.0)// + .setPropertyValueMutability(true)// + .build(); + + builder.defineGlobalProperty(GlobalProperty.GAMMA, propertyDefinition, 0); + + return builder.build(); + } + /* end */ + + /* start code_ref= global_proerties_plugin_alpha_beta_dimension|code_cap=A dimension is created that adds five pairs of values over the ALPHA and BETA global properties. */ + private static Dimension getAlphaBetaDimension() { + List> alphaBetaPairs = new ArrayList<>(); + alphaBetaPairs.add(new Pair<>(3.0, 10.0)); + alphaBetaPairs.add(new Pair<>(12.0, 25.0)); + alphaBetaPairs.add(new Pair<>(30.0, 40.0)); + alphaBetaPairs.add(new Pair<>(45.0, 70.0)); + alphaBetaPairs.add(new Pair<>(80.0, 100.0)); + + FunctionalDimension.Builder dimensionBuilder = FunctionalDimension.builder(); + + for (Pair pair : alphaBetaPairs) { + dimensionBuilder.addLevel((c) -> { + List result = new ArrayList<>(); + GlobalPropertiesPluginData.Builder builder = c + .getPluginDataBuilder(GlobalPropertiesPluginData.Builder.class); + builder.setGlobalPropertyValue(GlobalProperty.ALPHA, pair.getFirst(), 0); + builder.setGlobalPropertyValue(GlobalProperty.BETA, pair.getSecond(), 0); + result.add(pair.getFirst().toString()); + result.add(pair.getSecond().toString()); + return result; + }); + } + + dimensionBuilder.addMetaDatum(GlobalProperty.ALPHA.toString()); + dimensionBuilder.addMetaDatum(GlobalProperty.BETA.toString()); + + return dimensionBuilder.build(); + } + + /* end */ + + /* start code_ref= global_proerties_plugin_example_13|code_cap= Using the global properties plugin to add three global properties. */ + public static void main(String[] args) throws IOException { + if (args.length == 0) { + throw new RuntimeException("One output directory argument is required"); + } + Path outputDirectory = Paths.get(args[0]); + if (!Files.exists(outputDirectory)) { + Files.createDirectory(outputDirectory); + } else { + if (!Files.isDirectory(outputDirectory)) { + throw new IOException("Provided path is not a directory"); + } + } + + GlobalPropertiesPluginData globalPropertiesPluginData = getGlobalPropertiesPluginData(); + + GlobalPropertyReportPluginData globalPropertyReportPluginData = GlobalPropertyReportPluginData.builder()// + .setReportLabel(ModelReportLabel.GLOBAL_PROPERTY_REPORT)// + .setDefaultInclusion(true)// + .build(); + + Plugin globalPropertiesPlugin = GlobalPropertiesPlugin.builder() + .setGlobalPropertiesPluginData(globalPropertiesPluginData) + .setGlobalPropertyReportPluginData(globalPropertyReportPluginData)// + .getGlobalPropertiesPlugin(); + + Plugin modelPlugin = ModelPlugin.getModelPlugin(); + + NIOReportItemHandler nioReportItemHandler = // + NIOReportItemHandler.builder()// + .addReport(ModelReportLabel.GLOBAL_PROPERTY_REPORT, // + outputDirectory.resolve("global property report.xls"))// + .build(); + + Dimension alphaBetaDimension = getAlphaBetaDimension(); + + Experiment.builder()// + .addPlugin(globalPropertiesPlugin)// + .addPlugin(modelPlugin)// + .addExperimentContextConsumer(nioReportItemHandler)// + .addDimension(alphaBetaDimension)// + .build()// + .execute();// + + } + /* end */ + +} diff --git a/tutorials/lessons/lesson_13_global_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java b/tutorials/lessons/lesson_13_global_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java new file mode 100644 index 000000000..b88e7184b --- /dev/null +++ b/tutorials/lessons/lesson_13_global_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java @@ -0,0 +1,17 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors.GammaActor; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; + +public final class ModelPlugin { + private ModelPlugin() { + + } + + public static Plugin getModelPlugin() { + return Plugin.builder()// + .setPluginId(ModelPluginId.PLUGIN_ID).setInitializer((c) -> { + c.addActor(new GammaActor()::init); + }).build(); + } +} diff --git a/tutorials/lessons/lesson_13_global_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java b/tutorials/lessons/lesson_13_global_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java new file mode 100644 index 000000000..9c7193b57 --- /dev/null +++ b/tutorials/lessons/lesson_13_global_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java @@ -0,0 +1,11 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.nucleus.SimplePluginId; + +public final class ModelPluginId { + private ModelPluginId() { + } + + public final static PluginId PLUGIN_ID = new SimplePluginId("model plugin"); +} diff --git a/tutorials/lessons/lesson_13_global_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelReportLabel.java b/tutorials/lessons/lesson_13_global_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelReportLabel.java new file mode 100644 index 000000000..3f10fa8b6 --- /dev/null +++ b/tutorials/lessons/lesson_13_global_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelReportLabel.java @@ -0,0 +1,7 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; + +public enum ModelReportLabel implements ReportLabel { + GLOBAL_PROPERTY_REPORT +} diff --git a/tutorials/lessons/lesson_13_global_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/GammaActor.java b/tutorials/lessons/lesson_13_global_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/GammaActor.java new file mode 100644 index 000000000..b466ae248 --- /dev/null +++ b/tutorials/lessons/lesson_13_global_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/GammaActor.java @@ -0,0 +1,26 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors; + +import java.util.stream.IntStream; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.GlobalProperty; +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesDataManager; + +/* start code_ref= global_proerties_plugin_gamma_actor|code_cap=The gamma actor sets the value of the GAMMA property over time as a function of the ALPHA and BETA properties.*/ +public final class GammaActor { + + public void init(ActorContext actorContext) { + int count = 10; + IntStream.range(0, count).forEach((i) -> { + actorContext.addPlan((c) -> { + GlobalPropertiesDataManager globalPropertiesDataManager = c + .getDataManager(GlobalPropertiesDataManager.class); + Double alpha = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.ALPHA); + Double beta = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.BETA); + double gamma = (beta - alpha) * i / count + alpha; + globalPropertiesDataManager.setGlobalPropertyValue(GlobalProperty.GAMMA, gamma); + }, i + 1); + }); + } +} +/* end */ diff --git a/tutorials/lessons/lesson_13_global_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/GlobalProperty.java b/tutorials/lessons/lesson_13_global_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/GlobalProperty.java new file mode 100644 index 000000000..c73c7431a --- /dev/null +++ b/tutorials/lessons/lesson_13_global_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/GlobalProperty.java @@ -0,0 +1,7 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.support; + +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertyId; + +public enum GlobalProperty implements GlobalPropertyId { + ALPHA, BETA, GAMMA; +} diff --git a/tutorials/lessons/lesson_13_global_properties_plugin/src/main/resources/output/global_proerties_plugin_output.txt b/tutorials/lessons/lesson_13_global_properties_plugin/src/main/resources/output/global_proerties_plugin_output.txt new file mode 100644 index 000000000..3b8a568dd --- /dev/null +++ b/tutorials/lessons/lesson_13_global_properties_plugin/src/main/resources/output/global_proerties_plugin_output.txt @@ -0,0 +1,25 @@ +/* start code_ref=global_proerties_plugin_output|code_cap=An excerpt of the global property report showing the three global property values over time in the five scenarios.*/ +scenario ALPHA BETA time property value +0 3.0 10.0 0.0 ALPHA 3.0 +0 3.0 10.0 0.0 BETA 10.0 +0 3.0 10.0 0.0 GAMMA 1.0 +0 3.0 10.0 1.0 GAMMA 3.0 +0 3.0 10.0 2.0 GAMMA 3.7 +0 3.0 10.0 3.0 GAMMA 4.4 +0 3.0 10.0 4.0 GAMMA 5.1 +0 3.0 10.0 5.0 GAMMA 5.8 +0 3.0 10.0 6.0 GAMMA 6.5 +0 3.0 10.0 7.0 GAMMA 7.2 +... +1 12.0 25.0 9.0 GAMMA 22.4 +1 12.0 25.0 10.0 GAMMA 23.7 +2 30.0 40.0 0.0 ALPHA 30.0 +2 30.0 40.0 0.0 BETA 40.0 +2 30.0 40.0 0.0 GAMMA 1.0 +2 30.0 40.0 1.0 GAMMA 30.0 +... +4 80.0 100.0 7.0 GAMMA 92.0 +4 80.0 100.0 8.0 GAMMA 94.0 +4 80.0 100.0 9.0 GAMMA 96.0 +4 80.0 100.0 10.0 GAMMA 98.0 +/* end */ \ No newline at end of file diff --git a/tutorials/lessons/lesson_14_people_plugin/pom.xml b/tutorials/lessons/lesson_14_people_plugin/pom.xml new file mode 100644 index 000000000..13437c569 --- /dev/null +++ b/tutorials/lessons/lesson_14_people_plugin/pom.xml @@ -0,0 +1,79 @@ + + 4.0.0 + + + ASPR + https://www.phe.gov + + + + gov.hhs.aspr.ms.gcm.tutorials + lesson_14_people_plugin + ${revision} + jar + gcm lesson 14 people plugin + Tutorial introducing the people plugin + + + + UTF-8 + 17 + 17 + ${gcm.version} + + 1.3.0 + + 4.0.0-SNAPSHOT + + + + + + gov.hhs.aspr.ms + gcm + ${gcm.version} + + + + + + Shawn Hatch + Leidos + https://www.leidos.com + + + + + + + org.codehaus.mojo + flatten-maven-plugin + ${flatten-maven-plugin.version} + + + + flatten + process-resources + + flatten + + + true + + + + + flatten.clean + clean + + clean + + + + + + + + diff --git a/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_14.java b/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_14.java new file mode 100644 index 000000000..b43d04ccc --- /dev/null +++ b/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_14.java @@ -0,0 +1,130 @@ +package gov.hhs.aspr.ms.gcm.lessons; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.IntStream; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.ModelPlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.ModelReportLabel; +import gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.VaccinePlugin; +import gov.hhs.aspr.ms.gcm.nucleus.Dimension; +import gov.hhs.aspr.ms.gcm.nucleus.Experiment; +import gov.hhs.aspr.ms.gcm.nucleus.FunctionalDimension; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePlugin; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeoplePluginData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonRange; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.NIOReportItemHandler; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import util.random.RandomGeneratorProvider; + +public final class Example_14 { + + private Example_14() { + } + + /* start code_ref= people_plugin_stochastics_dimension|code_cap=The stochastics dimension contains levels for each replication value. Note that the generation of the random seed values occurs outside of the lambda code.*/ + private static Dimension getStochasticsDimension(int replicationCount, long seed) { + FunctionalDimension.Builder builder = FunctionalDimension.builder();// + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + + List seedValues = new ArrayList<>(); + for (int i = 0; i < replicationCount; i++) { + seedValues.add(randomGenerator.nextLong()); + } + + IntStream.range(0, seedValues.size()).forEach((i) -> { + builder.addLevel((context) -> { + StochasticsPluginData.Builder stochasticsPluginDataBuilder = context + .getPluginDataBuilder(StochasticsPluginData.Builder.class); + long seedValue = seedValues.get(i); + WellState wellState = WellState.builder().setSeed(seedValue).build(); + stochasticsPluginDataBuilder.setMainRNGState(wellState); + + ArrayList result = new ArrayList<>(); + result.add(Integer.toString(i)); + result.add(Long.toString(seedValue) + "L"); + + return result; + });// + }); + + builder.addMetaDatum("seed_index");// + builder.addMetaDatum("seed_value");// + + return builder.build(); + } + /* end */ + + /* start code_ref= people_plugin_example_14_init|code_cap=The population trace and vaccination reports are associated with corresponding file names via the NIO report item handler.*/ + public static void main(String[] args) throws IOException { + if (args.length == 0) { + throw new RuntimeException("One output directory argument is required"); + } + Path outputDirectory = Paths.get(args[0]); + if (!Files.exists(outputDirectory)) { + Files.createDirectory(outputDirectory); + } else { + if (!Files.isDirectory(outputDirectory)) { + throw new IOException("Provided path is not a directory"); + } + } + + // reports + NIOReportItemHandler nioReportItemHandler = // + NIOReportItemHandler.builder()// + .addReport(ModelReportLabel.POPULATION_TRACE, // + outputDirectory.resolve("population_trace_report.xls"))// + .addReport(ModelReportLabel.VACCINATION, // + outputDirectory.resolve("vaccination_report.xls"))// + .build(); + + /* end */ + /* start code_ref= people_plugin_example_14_adding_plugins|code_cap=The various plugins are initialized with data and added to the experiment.*/ + + // create the people plugin with an initial population of ten people, + // numbered 1, 3, 5,...,19 + PeoplePluginData.Builder peoplePluginDataBuilder = PeoplePluginData.builder(); + for (int i = 0; i < 10; i++) { + PersonId personId = new PersonId(i * 2 + 1); + peoplePluginDataBuilder.addPersonRange(new PersonRange(personId.getValue(), personId.getValue())); + } + PeoplePluginData peoplePluginData = peoplePluginDataBuilder.build(); + Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(peoplePluginData); + + // create the stochastics plugin and build a dimension with 5 seed + // values + WellState wellState = WellState.builder().setSeed(463390897335624435L).build(); + StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder().setMainRNGState(wellState) + .build(); + Plugin stochasticsPlugin = StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData); + + Dimension stochasticsDimension = getStochasticsDimension(5, 8265427588292179209L); + + // create the vaccine and model plugins + Plugin vaccinePlugin = VaccinePlugin.getVaccinePlugin(); + Plugin modelPlugin = ModelPlugin.getModelPlugin(); + + Experiment.builder()// + .addPlugin(modelPlugin)// + .addPlugin(peoplePlugin)// + .addPlugin(stochasticsPlugin)// + .addPlugin(vaccinePlugin)// + .addExperimentContextConsumer(nioReportItemHandler)// + .addDimension(stochasticsDimension)// + .build()// + .execute();// + } + /* end */ + +} diff --git a/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java b/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java new file mode 100644 index 000000000..afd362b8c --- /dev/null +++ b/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java @@ -0,0 +1,21 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors.PopulationManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors.Vaccinator; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.reports.PopulationTraceReport; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; + +public final class ModelPlugin { + private ModelPlugin() { + + } + + public static Plugin getModelPlugin() { + return Plugin.builder()// + .setPluginId(ModelPluginId.PLUGIN_ID).setInitializer((c) -> { + c.addActor(new Vaccinator()::init); + c.addActor(new PopulationManager()::init); + c.addReport(new PopulationTraceReport(ModelReportLabel.POPULATION_TRACE)::init); + }).build(); + } +} diff --git a/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java b/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java new file mode 100644 index 000000000..9c7193b57 --- /dev/null +++ b/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java @@ -0,0 +1,11 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.nucleus.SimplePluginId; + +public final class ModelPluginId { + private ModelPluginId() { + } + + public final static PluginId PLUGIN_ID = new SimplePluginId("model plugin"); +} diff --git a/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelReportLabel.java b/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelReportLabel.java new file mode 100644 index 000000000..7b9cbbb55 --- /dev/null +++ b/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelReportLabel.java @@ -0,0 +1,7 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; + +public enum ModelReportLabel implements ReportLabel { + VACCINATION, POPULATION_TRACE +} diff --git a/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/PopulationManager.java b/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/PopulationManager.java new file mode 100644 index 000000000..ea8c4c9c8 --- /dev/null +++ b/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/PopulationManager.java @@ -0,0 +1,43 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors; + +import java.util.List; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.support.VaccineInitialization; +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonConstructionData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; + +public final class PopulationManager { + + /* start code_ref= people_plugin_population_manager|code_cap= The population manager schedules 100 randomized actions to either add or remove people.*/ + public void init(ActorContext actorContext) { + StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + double planTime = randomGenerator.nextDouble(); + for (int i = 0; i < 100; i++) { + actorContext.addPlan((c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + if (randomGenerator.nextDouble() < 0.1) { + List people = peopleDataManager.getPeople(); + if (!people.isEmpty()) { + PersonId personId = people.get(randomGenerator.nextInt(people.size())); + peopleDataManager.removePerson(personId); + } + } else { + int intialVaccineCount = randomGenerator.nextInt(3); + VaccineInitialization vaccineInitialization = new VaccineInitialization(intialVaccineCount); + PersonConstructionData personConstructionData = PersonConstructionData.builder()// + .add(vaccineInitialization)// + .build(); + peopleDataManager.addPerson(personConstructionData); + } + }, planTime); + planTime += randomGenerator.nextDouble(); + } + } + /* end */ +} diff --git a/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/Vaccinator.java b/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/Vaccinator.java new file mode 100644 index 000000000..f5c487cff --- /dev/null +++ b/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/Vaccinator.java @@ -0,0 +1,33 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors; + +import java.util.List; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.datamanagers.VaccinationDataManager; +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; + +public final class Vaccinator { + /* start code_ref= people_plugin_vaccinator|code_cap=The vaccinator administers 300 vaccine doses over 100 days.*/ + public void init(ActorContext actorContext) { + StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + double planTime = randomGenerator.nextDouble(); + for (int i = 0; i < 300; i++) { + actorContext.addPlan((c) -> { + PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class); + VaccinationDataManager vaccinationDataManager = c.getDataManager(VaccinationDataManager.class); + List people = peopleDataManager.getPeople(); + if (!people.isEmpty()) { + PersonId personId = people.get(randomGenerator.nextInt(people.size())); + vaccinationDataManager.vaccinatePerson(personId); + } + }, planTime); + planTime += randomGenerator.nextDouble() / 3; + } + } + /* end */ +} diff --git a/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/reports/PopulationTraceReport.java b/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/reports/PopulationTraceReport.java new file mode 100644 index 000000000..2c48a4046 --- /dev/null +++ b/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/reports/PopulationTraceReport.java @@ -0,0 +1,64 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.reports; + +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.events.PersonAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.events.PersonImminentRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; + +public final class PopulationTraceReport { + private final ReportLabel reportLabel; + + private static enum Action { + ADDITION, REMOVAL + } + + private ReportContext reportContext; + + private ReportHeader reportHeader = ReportHeader.builder()// + .add("time")// + .add("personId")// + .add("action")// + .build(); + + public PopulationTraceReport(ReportLabel reportLabel) { + this.reportLabel = reportLabel; + } + + public void init(ReportContext reportContext) { + this.reportContext = reportContext; + PeopleDataManager peopleDataManager = reportContext.getDataManager(PeopleDataManager.class); + + reportContext.subscribe(PersonAdditionEvent.class, this::handlePersonAdditionEvent); + reportContext.subscribe(PersonImminentRemovalEvent.class, this::handlePersonImminentRemovalEvent); + + for (PersonId personId : peopleDataManager.getPeople()) { + generateReportItem(Action.ADDITION, personId); + } + + } + + private void handlePersonImminentRemovalEvent(ReportContext reportContext, + PersonImminentRemovalEvent personImminentRemovalEvent) { + generateReportItem(Action.REMOVAL, personImminentRemovalEvent.personId()); + } + + private void handlePersonAdditionEvent(ReportContext reportContext, PersonAdditionEvent personAdditionEvent) { + generateReportItem(Action.ADDITION, personAdditionEvent.personId()); + } + + private void generateReportItem(Action action, PersonId personId) { + ReportItem reportItem = ReportItem.builder()// + .setReportLabel(reportLabel)// + .setReportHeader(reportHeader)// + .addValue(reportContext.getTime())// + .addValue(personId)// + .addValue(action)// + .build(); + reportContext.releaseOutput(reportItem); + } + +} diff --git a/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/VaccinePlugin.java b/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/VaccinePlugin.java new file mode 100644 index 000000000..c4983d988 --- /dev/null +++ b/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/VaccinePlugin.java @@ -0,0 +1,27 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.ModelReportLabel; +import gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.datamanagers.VaccinationDataManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.reports.VaccineReport; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePluginId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; + +public class VaccinePlugin { + + private VaccinePlugin() { + } + + public static Plugin getVaccinePlugin() { + + return Plugin.builder()// + .setPluginId(VaccinePluginId.PLUGIN_ID)// + .addPluginDependency(PeoplePluginId.PLUGIN_ID)// + .setInitializer((c) -> { + c.addDataManager(new VaccinationDataManager()); + c.addReport(new VaccineReport(ModelReportLabel.VACCINATION, ReportPeriod.DAILY, 6)::init); + })// + .build(); + + } +} diff --git a/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/VaccinePluginId.java b/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/VaccinePluginId.java new file mode 100644 index 000000000..0cce5093e --- /dev/null +++ b/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/VaccinePluginId.java @@ -0,0 +1,18 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.nucleus.SimplePluginId; + +/** + * Static plugin id implementation for the Vaccine Plugin + * + * + */ + +public final class VaccinePluginId implements PluginId { + private VaccinePluginId() { + }; + + public final static PluginId PLUGIN_ID = new SimplePluginId("vaccine plugin id"); + +} diff --git a/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/datamanagers/VaccinationDataManager.java b/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/datamanagers/VaccinationDataManager.java new file mode 100644 index 000000000..73149eb11 --- /dev/null +++ b/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/datamanagers/VaccinationDataManager.java @@ -0,0 +1,176 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.datamanagers; + +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.support.VaccineError; +import gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.support.VaccineInitialization; +import gov.hhs.aspr.ms.gcm.nucleus.DataManager; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.events.PersonImminentAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.events.PersonRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import util.errors.ContractException; +import util.wrappers.MutableInteger; + +public final class VaccinationDataManager extends DataManager { + /* start code_ref= people_plugin_vaccine_counts|code_cap=The vaccination data manager uses a simple map from person id to a counter to track the number of vaccinations for each person.*/ + private Map vaccinationCounts = new LinkedHashMap<>(); + /* end */ + private PeopleDataManager personDataManager; + + private DataManagerContext dataManagerContext; + + @Override + /* start code_ref= people_plugin_vaccination_data_manager|code_cap=The vaccination data manager initializes by recording initial vaccine counts for each person and subscribing to person addition, person removal and person vaccination events.*/ + public void init(DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + dataManagerContext.subscribe(PersonRemovalEvent.class, this::handlePersonRemovalEvent); + dataManagerContext.subscribe(PersonImminentAdditionEvent.class, this::handlePersonImminentAdditionEvent); + personDataManager = dataManagerContext.getDataManager(PeopleDataManager.class); + this.dataManagerContext = dataManagerContext; + for (PersonId personId : personDataManager.getPeople()) { + vaccinationCounts.put(personId, new MutableInteger()); + } + dataManagerContext.subscribe(VaccinationMutationEvent.class, this::handleVaccinationMutationEvent); + } + /* end */ + + /* start code_ref= people_plugin_vaccination_handling_person_removal|code_cap= The vaccination manager removes people from its count tracking as needed. Newly added people may enter into the simulation with some vaccinations.*/ + + private void handlePersonRemovalEvent(DataManagerContext dataManagerContext, + PersonRemovalEvent personRemovalEvent) { + PersonId personId = personRemovalEvent.personId(); + vaccinationCounts.remove(personId); + } + + private void handlePersonImminentAdditionEvent(DataManagerContext dataManagerContext, + PersonImminentAdditionEvent personImminentAdditionEvent) { + PersonId personId = personImminentAdditionEvent.personId(); + validateNewPersonId(personId); + MutableInteger mutableInteger = new MutableInteger(); + vaccinationCounts.put(personId, mutableInteger); + Optional optional = personImminentAdditionEvent// + .personConstructionData()// + .getValue(VaccineInitialization.class); + if (optional.isPresent()) { + VaccineInitialization vaccineInitialization = optional.get(); + int vaccineCount = vaccineInitialization.getVaccineCount(); + validateInitialVaccineCount(vaccineCount); + mutableInteger.setValue(vaccineCount); + } + } + /* end */ + + private void validateInitialVaccineCount(int initialVaccineCount) { + if (initialVaccineCount < 0) { + throw new ContractException(VaccineError.NEGATIVE_VACCINE_COUNT); + } + } + + private void validateNewPersonId(PersonId personId) { + if (personId == null) { + throw new ContractException(PersonError.NEGATIVE_PERSON_ID); + } + if (vaccinationCounts.containsKey(personId)) { + throw new RuntimeException("Person is already tracked " + personId); + } + } + + /** + * Returns the set of people who have had at least one vaccine + */ + public Set getVaccinatedPeople() { + Set result = new LinkedHashSet<>(); + for (PersonId personId : vaccinationCounts.keySet()) { + MutableInteger mutableInteger = vaccinationCounts.get(personId); + if (mutableInteger.getValue() > 0) { + result.add(personId); + } + } + return result; + } + + /** + * Returns the set of people who have not been vaccinated + */ + public Set getUnvaccinatedPeople() { + Set result = new LinkedHashSet<>(); + for (PersonId personId : vaccinationCounts.keySet()) { + MutableInteger mutableInteger = vaccinationCounts.get(personId); + if (mutableInteger.getValue() == 0) { + result.add(personId); + } + } + return result; + } + + /** + * Returns true if and only if the person is vaccinated + * + * @throws ContractException + *
          • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
          • + *
          • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person id is unknown
          • + */ + public boolean isPersonVaccinated(PersonId personId) { + validatePersonId(personId); + return vaccinationCounts.get(personId).getValue() > 0; + } + + private static record VaccinationMutationEvent(PersonId personId) implements Event { + } + + /** + * Increases the vaccine count for a person + * + * @throws ContractException + *
          • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
          • + *
          • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person id is unknown
          • + * + */ + public void vaccinatePerson(PersonId personId) { + dataManagerContext.releaseMutationEvent(new VaccinationMutationEvent(personId)); + } + + private void handleVaccinationMutationEvent(DataManagerContext dataManagerContext, + VaccinationMutationEvent vaccinationMutationEvent) { + PersonId personId = vaccinationMutationEvent.personId(); + validatePersonId(personId); + vaccinationCounts.get(personId).increment(); + } + + /** + * Returns the number of vaccines a person has recieved + * + * @throws ContractException + *
          • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
          • + *
          • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person id is unknown
          • + * + */ + public int getPersonVaccinationCount(PersonId personId) { + validatePersonId(personId); + return vaccinationCounts.get(personId).getValue(); + } + + private void validatePersonId(PersonId personId) { + if (personId == null) { + throw new ContractException(PersonError.NEGATIVE_PERSON_ID); + } + if (!personDataManager.personExists(personId)) { + throw new ContractException(PersonError.UNKNOWN_PERSON_ID); + } + } + +} diff --git a/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/reports/VaccineReport.java b/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/reports/VaccineReport.java new file mode 100644 index 000000000..43488cbe4 --- /dev/null +++ b/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/reports/VaccineReport.java @@ -0,0 +1,76 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.reports; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.apache.commons.math3.util.FastMath; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.datamanagers.VaccinationDataManager; +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.PeriodicReport; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import util.wrappers.MutableInteger; + +public final class VaccineReport extends PeriodicReport { + + private final int maxVaccinedCount; + + public VaccineReport(ReportLabel reportLabel, ReportPeriod reportPeriod, int maxVaccineCount) { + super(reportLabel, reportPeriod); + + this.maxVaccinedCount = FastMath.max(0, maxVaccineCount); + + ReportHeader.Builder builder = ReportHeader.builder(); + addTimeFieldHeaders(builder); + for (int i = 0; i < maxVaccineCount; i++) { + builder.add("count_" + i); + } + builder.add("count_" + maxVaccineCount + "+"); + + reportHeader = builder.build(); + } + + private VaccinationDataManager vaccinationDataManager; + + private PeopleDataManager peopleDataManager; + + protected void prepare(ReportContext reportContext) { + vaccinationDataManager = reportContext.getDataManager(VaccinationDataManager.class); + peopleDataManager = reportContext.getDataManager(PeopleDataManager.class); + } + + private ReportHeader reportHeader; + + @Override + protected void flush(ReportContext reportContext) { + Map peopleByVaccineCount = new LinkedHashMap<>(); + for (int i = 0; i <= maxVaccinedCount; i++) { + peopleByVaccineCount.put(i, new MutableInteger()); + } + for (PersonId personId : peopleDataManager.getPeople()) { + int vaccinationCount = vaccinationDataManager.getPersonVaccinationCount(personId); + MutableInteger mutableInteger = peopleByVaccineCount.get(vaccinationCount); + if (mutableInteger == null) { + mutableInteger = peopleByVaccineCount.get(maxVaccinedCount); + } + mutableInteger.increment(); + } + ReportItem.Builder builder = ReportItem.builder()// + .setReportLabel(getReportLabel())// + .setReportHeader(reportHeader); + fillTimeFields(builder); + + for (int i = 0; i <= maxVaccinedCount; i++) { + builder.addValue(peopleByVaccineCount.get(i).getValue()); + } + + ReportItem reportItem = builder.build(); + reportContext.releaseOutput(reportItem); + } + +} diff --git a/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/support/VaccineError.java b/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/support/VaccineError.java new file mode 100644 index 000000000..8cccfbf7f --- /dev/null +++ b/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/support/VaccineError.java @@ -0,0 +1,28 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.support; + +import util.errors.ContractError; +import util.errors.ContractException; + +/** + * An enumeration supporting {@link ContractException} that acts as a general + * description of the exception. + * + * + */ +public enum VaccineError implements ContractError { + + NEGATIVE_VACCINE_COUNT("Negative vaccine count for person initialization"), + + ; + + private final String description; + + private VaccineError(final String description) { + this.description = description; + } + + @Override + public String getDescription() { + return description; + } +} diff --git a/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/support/VaccineInitialization.java b/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/support/VaccineInitialization.java new file mode 100644 index 000000000..1ed4f2737 --- /dev/null +++ b/tutorials/lessons/lesson_14_people_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/support/VaccineInitialization.java @@ -0,0 +1,23 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.support; + +/** + * Represents the initial vaccine count for a person + * + */ + +import net.jcip.annotations.Immutable; + +@Immutable +public final class VaccineInitialization { + private final int vaccineCount; + + public VaccineInitialization(int vaccineCount) { + super(); + this.vaccineCount = vaccineCount; + } + + public int getVaccineCount() { + return vaccineCount; + } + +} diff --git a/tutorials/lessons/lesson_14_people_plugin/src/main/resources/output/people_plugin_output_1.txt b/tutorials/lessons/lesson_14_people_plugin/src/main/resources/output/people_plugin_output_1.txt new file mode 100644 index 000000000..db078d170 --- /dev/null +++ b/tutorials/lessons/lesson_14_people_plugin/src/main/resources/output/people_plugin_output_1.txt @@ -0,0 +1,46 @@ +/* start code_ref=people_plugin_output_1|code_cap=An excerpt of the population trace report.*/ +scenario seed_index seed_value time personId action +0 0 1126862960420803077L 0.0 1 ADDITION +0 0 1126862960420803077L 0.0 3 ADDITION +0 0 1126862960420803077L 0.0 5 ADDITION +0 0 1126862960420803077L 0.0 7 ADDITION +0 0 1126862960420803077L 0.0 9 ADDITION +0 0 1126862960420803077L 0.0 11 ADDITION +0 0 1126862960420803077L 0.0 13 ADDITION +0 0 1126862960420803077L 0.0 15 ADDITION +0 0 1126862960420803077L 0.0 17 ADDITION +0 0 1126862960420803077L 0.0 19 ADDITION +0 0 1126862960420803077L 0.8335755305728809 19 REMOVAL +0 0 1126862960420803077L 1.2826070218004488 20 ADDITION +0 0 1126862960420803077L 1.6263298581013381 21 ADDITION +... +1 1 -4486033808643580070L 0.0 17 ADDITION +1 1 -4486033808643580070L 0.0 19 ADDITION +1 1 -4486033808643580070L 0.0667491016080557 20 ADDITION +1 1 -4486033808643580070L 0.9322293185952581 21 ADDITION +1 1 -4486033808643580070L 1.6514182977849594 22 ADDITION +1 1 -4486033808643580070L 2.1177839497510775 23 ADDITION +1 1 -4486033808643580070L 2.262388362711321 24 ADDITION +1 1 -4486033808643580070L 2.4082707578367586 25 ADDITION +1 1 -4486033808643580070L 2.8132397563814484 26 ADDITION +1 1 -4486033808643580070L 2.910386189663668 27 ADDITION +1 1 -4486033808643580070L 3.1314041818648652 28 ADDITION +1 1 -4486033808643580070L 3.978290704435972 29 ADDITION +1 1 -4486033808643580070L 4.8481078195035 30 ADDITION +1 1 -4486033808643580070L 5.775356841709518 31 ADDITION +1 1 -4486033808643580070L 6.071421352969509 32 ADDITION +1 1 -4486033808643580070L 6.449381013881234 33 ADDITION +... +4 4 2435395143614485495L 44.87104815363575 100 ADDITION +4 4 2435395143614485495L 45.05332879157074 101 ADDITION +4 4 2435395143614485495L 45.12891915346638 102 ADDITION +4 4 2435395143614485495L 45.5197380031486 103 ADDITION +4 4 2435395143614485495L 45.83693067456144 104 ADDITION +4 4 2435395143614485495L 46.29575692100794 105 ADDITION +4 4 2435395143614485495L 46.874418645363875 106 ADDITION +4 4 2435395143614485495L 47.347370214427606 107 ADDITION +4 4 2435395143614485495L 48.31664660786532 108 ADDITION +4 4 2435395143614485495L 49.20534099806731 109 ADDITION +4 4 2435395143614485495L 49.252453666282506 110 ADDITION +4 4 2435395143614485495L 49.53782286245757 111 ADDITION +/* end */ \ No newline at end of file diff --git a/tutorials/lessons/lesson_14_people_plugin/src/main/resources/output/people_plugin_output_2.txt b/tutorials/lessons/lesson_14_people_plugin/src/main/resources/output/people_plugin_output_2.txt new file mode 100644 index 000000000..82ab7d2f8 --- /dev/null +++ b/tutorials/lessons/lesson_14_people_plugin/src/main/resources/output/people_plugin_output_2.txt @@ -0,0 +1,43 @@ +/* start code_ref=people_plugin_output_2|code_cap=An excerpt of the vaccination report.*/ +scenario seed_index seed_value day count_0 count_1 count_2 count_3 count_4 count_5 count_6+ +0 0 1126862960420803077L 0 10 0 0 0 0 0 0 +0 0 1126862960420803077L 1 7 2 0 0 0 0 0 +0 0 1126862960420803077L 2 4 5 2 0 0 0 0 +0 0 1126862960420803077L 3 2 3 8 0 0 0 0 +0 0 1126862960420803077L 4 1 2 10 0 1 0 0 +0 0 1126862960420803077L 5 2 3 6 3 3 0 0 +0 0 1126862960420803077L 6 1 5 5 3 2 2 0 +0 0 1126862960420803077L 7 0 7 4 4 2 3 0 +0 0 1126862960420803077L 8 0 7 6 5 2 2 1 +... +0 0 1126862960420803077L 52 6 13 13 7 10 9 22 +0 0 1126862960420803077L 53 7 13 15 7 10 9 22 +0 0 1126862960420803077L 54 7 13 16 7 10 9 22 +1 1 -4486033808643580070L 0 10 0 0 0 0 0 0 +1 1 -4486033808643580070L 1 9 3 0 0 0 0 0 +1 1 -4486033808643580070L 2 5 6 2 0 0 0 0 +1 1 -4486033808643580070L 3 5 8 4 1 0 0 0 +1 1 -4486033808643580070L 4 5 6 5 3 1 0 0 +1 1 -4486033808643580070L 5 4 6 6 2 3 0 0 +1 1 -4486033808643580070L 6 2 6 7 3 4 0 0 +1 1 -4486033808643580070L 7 2 4 11 4 4 0 0 +... +3 3 -821383327301461075L 42 5 13 15 9 8 10 15 +3 3 -821383327301461075L 43 5 13 15 9 8 11 15 +3 3 -821383327301461075L 44 6 12 15 10 8 9 17 +3 3 -821383327301461075L 45 6 13 15 9 8 7 19 +3 3 -821383327301461075L 46 6 13 15 8 8 7 20 +3 3 -821383327301461075L 47 6 14 17 5 11 6 21 +3 3 -821383327301461075L 48 5 14 17 6 10 5 22 +3 3 -821383327301461075L 49 5 13 18 7 9 6 22 +... +4 4 2435395143614485495L 42 11 12 11 10 12 7 19 +4 4 2435395143614485495L 43 9 13 12 9 11 8 20 +4 4 2435395143614485495L 44 7 14 11 10 10 8 21 +4 4 2435395143614485495L 45 7 16 10 11 9 7 23 +4 4 2435395143614485495L 46 7 16 13 10 11 6 24 +4 4 2435395143614485495L 47 8 14 15 10 11 7 24 +4 4 2435395143614485495L 48 9 13 15 11 11 7 24 +4 4 2435395143614485495L 49 9 13 15 12 11 7 24 +4 4 2435395143614485495L 50 12 13 15 12 11 7 24 +/* end */ \ No newline at end of file diff --git a/tutorials/lessons/lesson_15_regions_plugin/pom.xml b/tutorials/lessons/lesson_15_regions_plugin/pom.xml new file mode 100644 index 000000000..a16e0c520 --- /dev/null +++ b/tutorials/lessons/lesson_15_regions_plugin/pom.xml @@ -0,0 +1,79 @@ + + 4.0.0 + + + ASPR + https://www.phe.gov + + + + gov.hhs.aspr.ms.gcm.tutorials + lesson_15_regions_plugin + ${revision} + jar + gcm lesson 15 regions plugin + Tutorial introducing the regions plugin + + + + UTF-8 + 17 + 17 + ${gcm.version} + + 1.3.0 + + 4.0.0-SNAPSHOT + + + + + + gov.hhs.aspr.ms + gcm + ${gcm.version} + + + + + + Shawn Hatch + Leidos + https://www.leidos.com + + + + + + + org.codehaus.mojo + flatten-maven-plugin + ${flatten-maven-plugin.version} + + + + flatten + process-resources + + flatten + + + true + + + + + flatten.clean + clean + + clean + + + + + + + + diff --git a/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_15.java b/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_15.java new file mode 100644 index 000000000..6b0f4448e --- /dev/null +++ b/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_15.java @@ -0,0 +1,249 @@ +package gov.hhs.aspr.ms.gcm.lessons; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.IntStream; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.ModelPlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.ModelReportLabel; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.Region; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.RegionProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.VaccinePlugin; +import gov.hhs.aspr.ms.gcm.nucleus.Dimension; +import gov.hhs.aspr.ms.gcm.nucleus.Experiment; +import gov.hhs.aspr.ms.gcm.nucleus.FunctionalDimension; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePlugin; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeoplePluginData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonRange; +import gov.hhs.aspr.ms.gcm.plugins.regions.RegionsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.regions.reports.RegionPropertyReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.regions.reports.RegionTransferReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.NIOReportItemHandler; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import util.random.RandomGeneratorProvider; + +public final class Example_15 { + + private final Path outputDirectory; + + private Example_15(Path outputDirectory) { + this.outputDirectory = outputDirectory; + } + + private List initialPeople = new ArrayList<>(); + private List initialRegions = new ArrayList<>(); + private RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(524055747550937602L); + + /* start code_ref= regions_plugin_example_15_nio|code_cap= The region property, region transfer and vaccination reports are mapped to distinct file names. */ + private NIOReportItemHandler getNIOReportItemHandler() { + return NIOReportItemHandler.builder()// + .addReport(ModelReportLabel.REGION_PROPERTY_REPORT, // + outputDirectory.resolve("region_property_report.xls"))// + .addReport(ModelReportLabel.REGION_TRANSFER_REPORT, // + outputDirectory.resolve("region_transfer_report.xls"))// + .addReport(ModelReportLabel.VACCINATION, // + outputDirectory.resolve("vaccine_report.xls"))// + .build(); + } + /* end */ + + /* start code_ref= regions_plugin_example_getting_people_plugin|code_cap=The people plugin is initialized with the starting populaiton. */ + private Plugin getPeoplePlugin() { + PeoplePluginData.Builder peoplePluginDataBuilder = PeoplePluginData.builder(); + for (PersonId personId : initialPeople) { + peoplePluginDataBuilder.addPersonRange(new PersonRange(personId.getValue(), personId.getValue())); + } + PeoplePluginData peoplePluginData = peoplePluginDataBuilder.build(); + return PeoplePlugin.getPeoplePlugin(peoplePluginData); + } + /* end */ + + /* start code_ref= regions_plugin_example_getting_regions_plugin|code_cap=The regions plugin is initialized with the starting regions and people, with each person assigned to a randomly selected region. The two region-based reports are also initialized and added to the region plugin's data.*/ + private Plugin getRegionsPlugin() { + // create the region plugin with an initial five regions, each region + // having 200 people + RegionsPluginData.Builder regionsPluginDataBuilder = RegionsPluginData.builder(); + for (Region region : initialRegions) { + regionsPluginDataBuilder.addRegion(region); + } + + for (PersonId personId : initialPeople) { + Region region = initialRegions.get(randomGenerator.nextInt(initialRegions.size())); + regionsPluginDataBuilder.addPerson(personId, region); + } + + PropertyDefinition propertyDefinition = PropertyDefinition.builder()// + .setType(Double.class)// + .setPropertyValueMutability(false)// + .build(); + regionsPluginDataBuilder.defineRegionProperty(RegionProperty.LAT, propertyDefinition); + regionsPluginDataBuilder.defineRegionProperty(RegionProperty.LON, propertyDefinition); + + for (Region region : initialRegions) { + regionsPluginDataBuilder.setRegionPropertyValue(region, RegionProperty.LAT, + randomGenerator.nextDouble() + 45.0); + regionsPluginDataBuilder.setRegionPropertyValue(region, RegionProperty.LON, + randomGenerator.nextDouble() + 128.0); + } + + RegionsPluginData regionsPluginData = regionsPluginDataBuilder.build(); + + RegionPropertyReportPluginData regionPropertyReportPluginData = // + RegionPropertyReportPluginData.builder()// + .setReportLabel(ModelReportLabel.REGION_PROPERTY_REPORT)// + .build(); + + RegionTransferReportPluginData regionTransferReportPluginData = RegionTransferReportPluginData.builder()// + .setReportLabel(ModelReportLabel.REGION_TRANSFER_REPORT)// + .setReportPeriod(ReportPeriod.END_OF_SIMULATION)// + .build();// + + return RegionsPlugin.builder()// + .setRegionsPluginData(regionsPluginData)// + .setRegionPropertyReportPluginData(regionPropertyReportPluginData)// + .setRegionTransferReportPluginData(regionTransferReportPluginData)// + .getRegionsPlugin(); + } + /* end */ + + /* start code_ref= regions_plugin_example_15_intialize_people_regions|code_cap=Lists of initial people and regions are created and will be used to initialize the various plugins. */ + private void initializePeopleAndRegions() { + for (int i = 0; i < 1000; i++) { + initialPeople.add(new PersonId(i)); + } + for (int i = 0; i < 5; i++) { + initialRegions.add(new Region(i)); + } + } + /* end */ + + /* start code_ref= regions_plugin_example_15_stochastics|code_cap=The stochastics plugin is initialized with a random seed value. A dimension is added to add new seeds to the resulting scenarios. */ + private Plugin getStochasticsPlugin() { + + WellState wellState = WellState.builder().setSeed(randomGenerator.nextLong()).build(); + StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder()// + .setMainRNGState(wellState).build(); + return StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData); + } + + private Dimension getStochasticsDimension(int replicationCount, long seed) { + FunctionalDimension.Builder builder = FunctionalDimension.builder();// + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + + List seedValues = new ArrayList<>(); + for (int i = 0; i < replicationCount; i++) { + seedValues.add(randomGenerator.nextLong()); + } + + IntStream.range(0, seedValues.size()).forEach((i) -> { + builder.addLevel((context) -> { + StochasticsPluginData.Builder stochasticsPluginDataBuilder = context + .getPluginDataBuilder(StochasticsPluginData.Builder.class); + long seedValue = seedValues.get(i); + WellState wellState = WellState.builder().setSeed(seedValue).build(); + stochasticsPluginDataBuilder.setMainRNGState(wellState); + + ArrayList result = new ArrayList<>(); + result.add(Integer.toString(i)); + result.add(Long.toString(seedValue) + "L"); + + return result; + });// + }); + + builder.addMetaDatum("seed index");// + builder.addMetaDatum("seed value");// + + return builder.build(); + } + /* end */ + + /* start code_ref= regions_plugin_example_15_execute|code_cap= The various plugins are gathered from their initial data.*/ + private void execute() { + /* + * Create person ids and region ids that are shared across the plugins + */ + initializePeopleAndRegions(); + + /* + * Create the reports + */ + + NIOReportItemHandler nioReportItemHandler = getNIOReportItemHandler(); + + /* + * Create the people plugin filled with 1000 people + */ + Plugin peoplePlugin = getPeoplePlugin(); + + /* + * Create the region plugin 5 regions, each having a lat and lon and assign the + * people to random regions. + * + */ + Plugin regionsPlugin = getRegionsPlugin(); + + /* + * create the stochastics plugin and build a dimension with 5 seed values + */ + Plugin stochasticsPlugin = getStochasticsPlugin(); + Dimension stochasticsDimension = getStochasticsDimension(5, randomGenerator.nextLong()); + + /* + * Create the vaccine and model plugins + */ + Plugin vaccinePlugin = VaccinePlugin.getVaccinePlugin(); + + Plugin modelPlugin = ModelPlugin.getModelPlugin(); + + /* end */ + + /* + * Assemble and execute the experiment + */ + /* start code_ref= regions_plugin_example_15_executing_experiment |code_cap=The experiment is run with five scenarios, each using distinct random seed values.*/ + Experiment.builder()// + .addPlugin(modelPlugin)// + .addPlugin(regionsPlugin)// + .addPlugin(peoplePlugin)// + .addPlugin(stochasticsPlugin)// + .addPlugin(vaccinePlugin)// + .addExperimentContextConsumer(nioReportItemHandler)// + .addDimension(stochasticsDimension)// + .build()// + .execute();// + /* end */ + } + + /* start code_ref= regions_plugin_example_15_main|code_cap=Executing example 15 with an output directory. */ + public static void main(String[] args) throws IOException { + if (args.length == 0) { + throw new RuntimeException("One output directory argument is required"); + } + Path outputDirectory = Paths.get(args[0]); + if (!Files.exists(outputDirectory)) { + Files.createDirectory(outputDirectory); + } else { + if (!Files.isDirectory(outputDirectory)) { + throw new IOException("Provided path is not a directory"); + } + } + + new Example_15(outputDirectory).execute(); + } + /* end */ +} diff --git a/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelError.java b/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelError.java new file mode 100644 index 000000000..3f316f445 --- /dev/null +++ b/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelError.java @@ -0,0 +1,26 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import util.errors.ContractError; +import util.errors.ContractException; + +/** + * An enumeration supporting {@link ContractException} that acts as a general + * description of the exception. + * + * + */ +public enum ModelError implements ContractError { + + NEGATIVE_REGION_ID("Negative region id"),; + + private final String description; + + private ModelError(final String description) { + this.description = description; + } + + @Override + public String getDescription() { + return description; + } +} diff --git a/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java b/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java new file mode 100644 index 000000000..3032006f5 --- /dev/null +++ b/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java @@ -0,0 +1,21 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors.PersonMover; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors.RegionCreator; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors.Vaccinator; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; + +public final class ModelPlugin { + private ModelPlugin() { + + } + + public static Plugin getModelPlugin() { + return Plugin.builder()// + .setPluginId(ModelPluginId.PLUGIN_ID).setInitializer((c) -> { + c.addActor(new PersonMover()::init); + c.addActor(new Vaccinator()::init); + c.addActor(new RegionCreator()::init); + }).build(); + } +} diff --git a/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java b/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java new file mode 100644 index 000000000..9c7193b57 --- /dev/null +++ b/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java @@ -0,0 +1,11 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.nucleus.SimplePluginId; + +public final class ModelPluginId { + private ModelPluginId() { + } + + public final static PluginId PLUGIN_ID = new SimplePluginId("model plugin"); +} diff --git a/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelReportLabel.java b/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelReportLabel.java new file mode 100644 index 000000000..77d2fcb9b --- /dev/null +++ b/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelReportLabel.java @@ -0,0 +1,7 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; + +public enum ModelReportLabel implements ReportLabel { + REGION_TRANSFER_REPORT, REGION_PROPERTY_REPORT, VACCINATION +} diff --git a/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/Region.java b/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/Region.java new file mode 100644 index 000000000..4f5c8d8c5 --- /dev/null +++ b/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/Region.java @@ -0,0 +1,61 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * Identifier for all regions + * + * + */ + +@Immutable +/* start code_ref= regions_plugin_implementing_regionId|code_cap=The region id is implemented as a wrapper class of int.*/ +public final class Region implements RegionId { + + private final int id; + + /** + * Constructs the region + * + * @throws ContractException + *
          • {@linkplain ModelError#NEGATIVE_REGION_ID}
          • + */ + public Region(int id) { + if (id < 0) { + throw new ContractException(ModelError.NEGATIVE_REGION_ID); + } + this.id = id; + } + + public int getValue() { + return id; + } + + @Override + public int hashCode() { + return id; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Region)) { + return false; + } + Region other = (Region) obj; + if (id != other.id) { + return false; + } + return true; + } + + @Override + public String toString() { + return "Region_" + id; + } +} +/* end */ diff --git a/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/RegionProperty.java b/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/RegionProperty.java new file mode 100644 index 000000000..26ff3656e --- /dev/null +++ b/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/RegionProperty.java @@ -0,0 +1,7 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionPropertyId; + +public enum RegionProperty implements RegionPropertyId { + LAT, LON, VACCINE_PRIORITY; +} diff --git a/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/PersonMover.java b/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/PersonMover.java new file mode 100644 index 000000000..5ef9d7f79 --- /dev/null +++ b/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/PersonMover.java @@ -0,0 +1,54 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; + +public class PersonMover { + + /* start code_ref= regions_plugin_person_mover_move_person|code_cap= The person mover actor attempts to move a randomly selected person from their current region to a new region. */ + private void moveRandomPerson(ActorContext actorContext) { + StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + PeopleDataManager peopleDataManager = actorContext.getDataManager(PeopleDataManager.class); + RegionsDataManager regionsDataManager = actorContext.getDataManager(RegionsDataManager.class); + + // pick a random person + List people = peopleDataManager.getPeople(); + if (people.isEmpty()) { + return; + } + PersonId personId = people.get(randomGenerator.nextInt(people.size())); + + // pick a new random new region for that person + List regionIds = new ArrayList<>(regionsDataManager.getRegionIds()); + RegionId personRegion = regionsDataManager.getPersonRegion(personId); + regionIds.remove(personRegion); + if (regionIds.isEmpty()) { + return; + } + RegionId newPersonRegion = regionIds.get(randomGenerator.nextInt(regionIds.size())); + + // assign the region to the person + regionsDataManager.setPersonRegion(personId, newPersonRegion); + } + /* end */ + + /* start code_ref= regions_plugin_person_mover_init|code_cap=The person mover actor plans for 1000 movements of people over time.*/ + public void init(ActorContext actorContext) { + for (int i = 0; i < 1000; i++) { + double planTime = ((double) i) * 0.1; + actorContext.addPlan(this::moveRandomPerson, planTime); + } + } + /* end */ + +} diff --git a/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/RegionCreator.java b/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/RegionCreator.java new file mode 100644 index 000000000..667ea32c6 --- /dev/null +++ b/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/RegionCreator.java @@ -0,0 +1,50 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors; + +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.apache.commons.math3.util.FastMath; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.Region; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.RegionProperty; +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionConstructionData; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionConstructionData.Builder; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; + +public class RegionCreator { + /* start code_ref= regions_plugin_region_creator_add_region|code_cap=When the region creator actor adds a new region, it assigns a random lat-lon corrdinate and possibly assigns a vaccine priority status to the region. */ + private void addRegion(ActorContext actorContext) { + RegionsDataManager regionsDataManager = actorContext.getDataManager(RegionsDataManager.class); + StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + + Set regions = regionsDataManager.getRegionIds(); + int maxRegionValue = -1; + for (Region region : regions) { + int value = region.getValue(); + maxRegionValue = FastMath.max(value, maxRegionValue); + } + Region newRegion = new Region(maxRegionValue + 1); + Builder regionBuilder = RegionConstructionData.builder().setRegionId(newRegion); + regionBuilder.setRegionPropertyValue(RegionProperty.LAT, 35 + randomGenerator.nextDouble()); + regionBuilder.setRegionPropertyValue(RegionProperty.LON, 128 + randomGenerator.nextDouble()); + + if (regionsDataManager.regionPropertyIdExists(RegionProperty.VACCINE_PRIORITY)) { + regionBuilder.setRegionPropertyValue(RegionProperty.VACCINE_PRIORITY, randomGenerator.nextBoolean()); + } + RegionConstructionData regionConstructionData = regionBuilder.build(); + regionsDataManager.addRegion(regionConstructionData); + } + + /* end */ + /* start code_ref= regions_plugin_region_creator_init|code_cap=The region creator actor plans the addition of five new regions. */ + public void init(ActorContext actorContext) { + for (int i = 0; i < 5; i++) { + double planTime = 20 * i + 1; + actorContext.addPlan(this::addRegion, planTime); + } + } + /* end */ +} diff --git a/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/Vaccinator.java b/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/Vaccinator.java new file mode 100644 index 000000000..fe5186d43 --- /dev/null +++ b/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/Vaccinator.java @@ -0,0 +1,128 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.RegionProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.VaccinationDataManager; +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionPropertyDefinitionInitialization; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; + +public final class Vaccinator { + + private RegionsDataManager regionsDataManager; + private VaccinationDataManager vaccinationDataManager; + private RandomGenerator randomGenerator; + + /* start code_ref= regions_plugin_vaccinator_alter_priority_property|code_cap= Toggling the vaccine priority for a randomly selected region.*/ + private void alterVaccinePriorityPropertyOnRandomRegion(ActorContext actorContext) { + List regionids = new ArrayList<>(regionsDataManager.getRegionIds()); + if (regionids.isEmpty()) { + return; + } + RegionId regionId = regionids.get(randomGenerator.nextInt(regionids.size())); + Boolean vaccinePriority = regionsDataManager.getRegionPropertyValue(regionId, RegionProperty.VACCINE_PRIORITY); + regionsDataManager.setRegionPropertyValue(regionId, RegionProperty.VACCINE_PRIORITY, !vaccinePriority); + } + /* end */ + + /* start code_ref= regions_plugin_vaccinator_priority_property|code_cap= On day 50, the vaccinator defines the Boolean VACCINE PRIORITY property and assigns randomized values to the existing regions. It then plans for updates to 50 regional vaccine priority property values over 50 days.*/ + private void addVaccinePriorityPropertyToRegions(ActorContext actorContext) { + + PropertyDefinition propertyDefinition = // + PropertyDefinition.builder()// + .setType(Boolean.class)// + .setDefaultValue(false)// + .build(); + + RegionPropertyDefinitionInitialization.Builder defBuilder = RegionPropertyDefinitionInitialization.builder()// + .setPropertyDefinition(propertyDefinition)// + .setRegionPropertyId(RegionProperty.VACCINE_PRIORITY); + + for (RegionId regionId : regionsDataManager.getRegionIds()) { + defBuilder.addPropertyValue(regionId, randomGenerator.nextBoolean()); + } + + RegionPropertyDefinitionInitialization regionPropertyDefinitionInitialization = defBuilder.build(); + regionsDataManager.defineRegionProperty(regionPropertyDefinitionInitialization); + + for (int i = 0; i < 50; i++) { + double planTime = actorContext.getTime() + i; + actorContext.addPlan(this::alterVaccinePriorityPropertyOnRandomRegion, planTime); + } + } + + /* end */ + + /* start code_ref= regions_plugin_vaccinator_vaccinate_random_person|code_cap= The vaccinator selects a person at random from the population to vaccinate. If the region is using the VACCINE_PRIORITY policy, then those with the least number of vaccinations have preference.*/ + private void vaccinateRandomPerson(ActorContext actorContext) { + + List regionIds = new ArrayList<>(regionsDataManager.getRegionIds()); + if (regionIds.isEmpty()) { + return; + } + RegionId regionId = regionIds.get(randomGenerator.nextInt(regionIds.size())); + List peopleInRegion = regionsDataManager.getPeopleInRegion(regionId); + + Boolean prioritizePeople = false; + boolean vaccinePriorityPropertyExists = regionsDataManager + .regionPropertyIdExists(RegionProperty.VACCINE_PRIORITY); + if (vaccinePriorityPropertyExists) { + prioritizePeople = regionsDataManager.getRegionPropertyValue(regionId, RegionProperty.VACCINE_PRIORITY); + } + + PersonId selectedPersonId = null; + if (prioritizePeople) { + int minVaccinationCount = Integer.MAX_VALUE; + for (PersonId personId : peopleInRegion) { + int personVaccinationCount = vaccinationDataManager.getPersonVaccinationCount(personId); + if (personVaccinationCount < minVaccinationCount) { + minVaccinationCount = personVaccinationCount; + } + } + List eligiblePeople = new ArrayList<>(); + for (PersonId personId : peopleInRegion) { + int personVaccinationCount = vaccinationDataManager.getPersonVaccinationCount(personId); + if (personVaccinationCount == minVaccinationCount) { + eligiblePeople.add(personId); + } + } + if (!eligiblePeople.isEmpty()) { + selectedPersonId = eligiblePeople.get(randomGenerator.nextInt(eligiblePeople.size())); + } + } else { + if (!peopleInRegion.isEmpty()) { + selectedPersonId = peopleInRegion.get(randomGenerator.nextInt(peopleInRegion.size())); + } + } + + if (selectedPersonId != null) { + vaccinationDataManager.vaccinatePerson(selectedPersonId); + } + } + /* end */ + + /* start code_ref= regions_plugin_vaccinator_init|code_cap=The vaccinator initializes by planning the vaccination of 5000 people carried out over approximately 50 days. */ + public void init(ActorContext actorContext) { + StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class); + randomGenerator = stochasticsDataManager.getRandomGenerator(); + regionsDataManager = actorContext.getDataManager(RegionsDataManager.class); + vaccinationDataManager = actorContext.getDataManager(VaccinationDataManager.class); + + double planTime = randomGenerator.nextDouble(); + for (int i = 0; i < 5000; i++) { + actorContext.addPlan(this::vaccinateRandomPerson, planTime); + planTime += randomGenerator.nextDouble() * 0.02; + } + + actorContext.addPlan(this::addVaccinePriorityPropertyToRegions, 50); + } + /* end */ +} diff --git a/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/VaccinationDataManager.java b/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/VaccinationDataManager.java new file mode 100644 index 000000000..42a5a65a8 --- /dev/null +++ b/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/VaccinationDataManager.java @@ -0,0 +1,175 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine; + +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.nucleus.DataManager; +import gov.hhs.aspr.ms.gcm.nucleus.DataManagerContext; +import gov.hhs.aspr.ms.gcm.nucleus.Event; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.events.PersonImminentAdditionEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.events.PersonRemovalEvent; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonError; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import util.errors.ContractException; +import util.wrappers.MutableInteger; + +public final class VaccinationDataManager extends DataManager { + + private Map vaccinationCounts = new LinkedHashMap<>(); + + private PeopleDataManager personDataManager; + + private DataManagerContext dataManagerContext; + + @Override + public void init(DataManagerContext dataManagerContext) { + super.init(dataManagerContext); + dataManagerContext.subscribe(PersonRemovalEvent.class, this::handlePersonRemovalEvent); + dataManagerContext.subscribe(PersonImminentAdditionEvent.class, this::handlePersonImminentAdditionEvent); + personDataManager = dataManagerContext.getDataManager(PeopleDataManager.class); + this.dataManagerContext = dataManagerContext; + List people = personDataManager.getPeople(); + for (PersonId personId : people) { + vaccinationCounts.put(personId, new MutableInteger()); + } + + dataManagerContext.subscribe(VaccinationMutationEvent.class, this::handleVaccinationEvent); + + } + + private void handlePersonRemovalEvent(DataManagerContext dataManagerContext, + PersonRemovalEvent personRemovalEvent) { + PersonId personId = personRemovalEvent.personId(); + vaccinationCounts.remove(personId); + } + + private void handlePersonImminentAdditionEvent(DataManagerContext dataManagerContext, // + PersonImminentAdditionEvent personImminentAdditionEvent) { + PersonId personId = personImminentAdditionEvent.personId(); + validateNewPersonId(personId); + MutableInteger mutableInteger = new MutableInteger(); + vaccinationCounts.put(personId, mutableInteger); + Optional optional = // + personImminentAdditionEvent// + .personConstructionData()// + .getValue(VaccineInitialization.class); + if (optional.isPresent()) { + VaccineInitialization vaccineInitialization = optional.get(); + int vaccineCount = vaccineInitialization.getVaccineCount(); + validateInitialVaccineCount(vaccineCount); + mutableInteger.setValue(vaccineCount); + } + } + + private void validateInitialVaccineCount(int initialVaccineCount) { + if (initialVaccineCount < 0) { + throw new ContractException(VaccineError.NEGATIVE_VACCINE_COUNT); + } + } + + private void validateNewPersonId(PersonId personId) { + if (personId == null) { + throw new ContractException(PersonError.NEGATIVE_PERSON_ID); + } + if (vaccinationCounts.containsKey(personId)) { + throw new RuntimeException("Person is already tracked " + personId); + } + } + + /** + * Returns the set of people who have had at least one vaccine + */ + public Set getVaccinatedPeople() { + Set result = new LinkedHashSet<>(); + for (PersonId personId : vaccinationCounts.keySet()) { + MutableInteger mutableInteger = vaccinationCounts.get(personId); + if (mutableInteger.getValue() > 0) { + result.add(personId); + } + } + return result; + } + + /** + * Returns the set of people who have not been vaccinated + */ + public Set getUnvaccinatedPeople() { + Set result = new LinkedHashSet<>(); + for (PersonId personId : vaccinationCounts.keySet()) { + MutableInteger mutableInteger = vaccinationCounts.get(personId); + if (mutableInteger.getValue() == 0) { + result.add(personId); + } + } + return result; + } + + /** + * Returns true if and only if the person is vaccinated + * + * @throws ContractException + *
          • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
          • + *
          • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person id is unknown
          • + */ + public boolean isPersonVaccinated(PersonId personId) { + validatePersonId(personId); + return vaccinationCounts.get(personId).getValue() > 0; + } + + private static record VaccinationMutationEvent(PersonId personId) implements Event { + } + + /** + * Increases the vaccine count for a person + * + * @throws ContractException + *
          • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
          • + *
          • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person id is unknown
          • + * + */ + + public void vaccinatePerson(PersonId personId) { + dataManagerContext.releaseMutationEvent(new VaccinationMutationEvent(personId)); + } + + private void handleVaccinationEvent(DataManagerContext dataManagerContext, + VaccinationMutationEvent vaccinationMutationEvent) { + PersonId personId = vaccinationMutationEvent.personId(); + validatePersonId(personId); + vaccinationCounts.get(personId).increment(); + } + + /** + * Returns the number of vaccines a person has recieved + * + * @throws ContractException + *
          • {@linkplain PersonError#NULL_PERSON_ID} if the + * person id is null
          • + *
          • {@linkplain PersonError#UNKNOWN_PERSON_ID} if + * the person id is unknown
          • + * + */ + public int getPersonVaccinationCount(PersonId personId) { + validatePersonId(personId); + return vaccinationCounts.get(personId).getValue(); + } + + private void validatePersonId(PersonId personId) { + if (personId == null) { + throw new ContractException(PersonError.NEGATIVE_PERSON_ID); + } + if (!personDataManager.personExists(personId)) { + throw new ContractException(PersonError.UNKNOWN_PERSON_ID); + } + } + +} diff --git a/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/VaccineError.java b/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/VaccineError.java new file mode 100644 index 000000000..f146750a8 --- /dev/null +++ b/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/VaccineError.java @@ -0,0 +1,28 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine; + +import util.errors.ContractError; +import util.errors.ContractException; + +/** + * An enumeration supporting {@link ContractException} that acts as a general + * description of the exception. + * + * + */ +public enum VaccineError implements ContractError { + + NEGATIVE_VACCINE_COUNT("Negative vaccine count for person initialization"), + + ; + + private final String description; + + private VaccineError(final String description) { + this.description = description; + } + + @Override + public String getDescription() { + return description; + } +} diff --git a/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/VaccineInitialization.java b/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/VaccineInitialization.java new file mode 100644 index 000000000..8bc863dfe --- /dev/null +++ b/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/VaccineInitialization.java @@ -0,0 +1,23 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine; + +/** + * Represents the initial vaccine count for a person + * + */ + +import net.jcip.annotations.Immutable; + +@Immutable +public final class VaccineInitialization { + private final int vaccineCount; + + public VaccineInitialization(int vaccineCount) { + super(); + this.vaccineCount = vaccineCount; + } + + public int getVaccineCount() { + return vaccineCount; + } + +} diff --git a/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/VaccinePlugin.java b/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/VaccinePlugin.java new file mode 100644 index 000000000..788872ee7 --- /dev/null +++ b/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/VaccinePlugin.java @@ -0,0 +1,27 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.ModelReportLabel; +import gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.reports.VaccineReport; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePluginId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; + +public class VaccinePlugin { + + private VaccinePlugin() { + } + + public static Plugin getVaccinePlugin() { + + return Plugin.builder()// + .setPluginId(VaccinePluginId.PLUGIN_ID)// + .addPluginDependency(PeoplePluginId.PLUGIN_ID)// + .setInitializer((c) -> { + c.addDataManager(new VaccinationDataManager()); + c.addReport( + new VaccineReport(ModelReportLabel.VACCINATION, ReportPeriod.END_OF_SIMULATION, 6)::init); + })// + .build(); + + } +} diff --git a/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/VaccinePluginId.java b/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/VaccinePluginId.java new file mode 100644 index 000000000..0cce5093e --- /dev/null +++ b/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/VaccinePluginId.java @@ -0,0 +1,18 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.nucleus.SimplePluginId; + +/** + * Static plugin id implementation for the Vaccine Plugin + * + * + */ + +public final class VaccinePluginId implements PluginId { + private VaccinePluginId() { + }; + + public final static PluginId PLUGIN_ID = new SimplePluginId("vaccine plugin id"); + +} diff --git a/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/reports/VaccineReport.java b/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/reports/VaccineReport.java new file mode 100644 index 000000000..9ec618f92 --- /dev/null +++ b/tutorials/lessons/lesson_15_regions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/vaccine/reports/VaccineReport.java @@ -0,0 +1,76 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.reports; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.apache.commons.math3.util.FastMath; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.vaccine.VaccinationDataManager; +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.PeriodicReport; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import util.wrappers.MutableInteger; + +public final class VaccineReport extends PeriodicReport { + + private final int maxVaccinedCount; + + public VaccineReport(ReportLabel reportLabel, ReportPeriod reportPeriod, int maxVaccineCount) { + super(reportLabel, reportPeriod); + + this.maxVaccinedCount = FastMath.max(0, maxVaccineCount); + + ReportHeader.Builder builder = ReportHeader.builder(); + addTimeFieldHeaders(builder); + for (int i = 0; i < maxVaccineCount; i++) { + builder.add("count_" + i); + } + builder.add("count_" + maxVaccineCount + "+"); + + reportHeader = builder.build(); + } + + private VaccinationDataManager vaccinationDataManager; + + private PeopleDataManager peopleDataManager; + + protected void prepare(ReportContext reportContext) { + vaccinationDataManager = reportContext.getDataManager(VaccinationDataManager.class); + peopleDataManager = reportContext.getDataManager(PeopleDataManager.class); + } + + private ReportHeader reportHeader; + + @Override + protected void flush(ReportContext reportContext) { + Map peopleByVaccineCount = new LinkedHashMap<>(); + for (int i = 0; i <= maxVaccinedCount; i++) { + peopleByVaccineCount.put(i, new MutableInteger()); + } + for (PersonId personId : peopleDataManager.getPeople()) { + int vaccinationCount = vaccinationDataManager.getPersonVaccinationCount(personId); + MutableInteger mutableInteger = peopleByVaccineCount.get(vaccinationCount); + if (mutableInteger == null) { + mutableInteger = peopleByVaccineCount.get(maxVaccinedCount); + } + mutableInteger.increment(); + } + ReportItem.Builder builder = ReportItem.builder()// + .setReportLabel(getReportLabel())// + .setReportHeader(reportHeader); + fillTimeFields(builder); + + for (int i = 0; i <= maxVaccinedCount; i++) { + builder.addValue(peopleByVaccineCount.get(i).getValue()); + } + + ReportItem reportItem = builder.build(); + reportContext.releaseOutput(reportItem); + } + +} diff --git a/tutorials/lessons/lesson_15_regions_plugin/src/main/resources/output/regions_plugin_output_1.txt b/tutorials/lessons/lesson_15_regions_plugin/src/main/resources/output/regions_plugin_output_1.txt new file mode 100644 index 000000000..00fb2396d --- /dev/null +++ b/tutorials/lessons/lesson_15_regions_plugin/src/main/resources/output/regions_plugin_output_1.txt @@ -0,0 +1,29 @@ +/* start code_ref=regions_plugin_output_1|code_cap=Excerpts from the region transfer report.*/ +scenario seed_index seed_value source_region destination_region transfers +0 0 -7834265884293137617L Region_2 Region_2 215 +0 0 -7834265884293137617L Region_0 Region_0 188 +0 0 -7834265884293137617L Region_3 Region_3 208 +0 0 -7834265884293137617L Region_4 Region_4 215 +0 0 -7834265884293137617L Region_1 Region_1 174 +0 0 -7834265884293137617L Region_4 Region_3 26 +0 0 -7834265884293137617L Region_4 Region_0 36 +0 0 -7834265884293137617L Region_3 Region_4 24 +... +1 1 -7320358285742045393L Region_2 Region_2 215 +1 1 -7320358285742045393L Region_0 Region_0 188 +1 1 -7320358285742045393L Region_3 Region_3 208 +1 1 -7320358285742045393L Region_4 Region_4 215 +1 1 -7320358285742045393L Region_1 Region_1 174 +... +2 2 -4619948863677044400L Region_3 Region_0 26 +2 2 -4619948863677044400L Region_3 Region_1 33 +2 2 -4619948863677044400L Region_0 Region_4 22 +2 2 -4619948863677044400L Region_1 Region_4 29 +2 2 -4619948863677044400L Region_4 Region_0 32 +... +4 4 -7584580254621783722L Region_8 Region_4 1 +4 4 -7584580254621783722L Region_9 Region_1 1 +4 4 -7584580254621783722L Region_9 Region_0 2 +4 4 -7584580254621783722L Region_9 Region_6 2 + +/* end */ \ No newline at end of file diff --git a/tutorials/lessons/lesson_15_regions_plugin/src/main/resources/output/regions_plugin_output_2.txt b/tutorials/lessons/lesson_15_regions_plugin/src/main/resources/output/regions_plugin_output_2.txt new file mode 100644 index 000000000..3b992f40c --- /dev/null +++ b/tutorials/lessons/lesson_15_regions_plugin/src/main/resources/output/regions_plugin_output_2.txt @@ -0,0 +1,30 @@ +/* start code_ref=regions_plugin_output_2|code_cap=Excerpts from the region property report.*/ +scenario seed_index seed_value time region property value +0 0 -7834265884293137617L 0.0 Region_0 LAT 45.08307901948476 +0 0 -7834265884293137617L 0.0 Region_0 LON 128.5497267736204 +0 0 -7834265884293137617L 0.0 Region_1 LAT 45.73317078392115 +0 0 -7834265884293137617L 0.0 Region_1 LON 128.98292164396958 +0 0 -7834265884293137617L 0.0 Region_2 LAT 45.74702447122078 +0 0 -7834265884293137617L 0.0 Region_2 LON 128.5118606592755 +0 0 -7834265884293137617L 0.0 Region_3 LAT 45.8303102139607 +0 0 -7834265884293137617L 0.0 Region_3 LON 128.55192626408567 +0 0 -7834265884293137617L 0.0 Region_4 LAT 45.59334365958997 +0 0 -7834265884293137617L 0.0 Region_4 LON 128.7915941303198 +0 0 -7834265884293137617L 1.0 Region_5 LAT 35.99754757587744 +0 0 -7834265884293137617L 1.0 Region_5 LON 128.2594279657217 +0 0 -7834265884293137617L 21.0 Region_6 LAT 35.84682720256188 +0 0 -7834265884293137617L 21.0 Region_6 LON 128.2211833000355 +0 0 -7834265884293137617L 41.0 Region_7 LAT 35.07267582475035 +0 0 -7834265884293137617L 41.0 Region_7 LON 128.37457313813837 +0 0 -7834265884293137617L 50.0 Region_0 VACCINE_PRIORITY false +0 0 -7834265884293137617L 50.0 Region_1 VACCINE_PRIORITY false +... +4 4 -7584580254621783722L 93.0 Region_2 VACCINE_PRIORITY false +4 4 -7584580254621783722L 94.0 Region_1 VACCINE_PRIORITY true +4 4 -7584580254621783722L 95.0 Region_2 VACCINE_PRIORITY true +4 4 -7584580254621783722L 96.0 Region_7 VACCINE_PRIORITY true +4 4 -7584580254621783722L 97.0 Region_0 VACCINE_PRIORITY false +4 4 -7584580254621783722L 98.0 Region_1 VACCINE_PRIORITY false +4 4 -7584580254621783722L 99.0 Region_0 VACCINE_PRIORITY true + +/* end */ \ No newline at end of file diff --git a/tutorials/lessons/lesson_15_regions_plugin/src/main/resources/output/regions_plugin_output_3.txt b/tutorials/lessons/lesson_15_regions_plugin/src/main/resources/output/regions_plugin_output_3.txt new file mode 100644 index 000000000..8a8aa9cd8 --- /dev/null +++ b/tutorials/lessons/lesson_15_regions_plugin/src/main/resources/output/regions_plugin_output_3.txt @@ -0,0 +1,8 @@ +/* start code_ref=regions_plugin_output_3|code_cap=The vaccine report shows vaccine counts at the end of each simulation.*/ +scenario seed_index seed_value count_0 count_1 count_2 count_3 count_4 count_5 count_6+ +0 0 -7834265884293137617L 13 67 145 168 170 148 289 +1 1 -7320358285742045393L 13 63 141 181 187 133 282 +2 2 -4619948863677044400L 17 53 157 196 176 140 261 +3 3 3282202756261196294L 10 72 159 166 163 146 284 +4 4 -7584580254621783722L 4 66 153 198 163 136 280 +/* end */ \ No newline at end of file diff --git a/tutorials/lessons/lesson_16_person_properties_plugin/pom.xml b/tutorials/lessons/lesson_16_person_properties_plugin/pom.xml new file mode 100644 index 000000000..1cd2c20d2 --- /dev/null +++ b/tutorials/lessons/lesson_16_person_properties_plugin/pom.xml @@ -0,0 +1,79 @@ + + 4.0.0 + + + ASPR + https://www.phe.gov + + + + gov.hhs.aspr.ms.gcm.tutorials + lesson_16_person_properties_plugin + ${revision} + jar + gcm lesson 16 person properties plugin + Tutorial introducing the person properties plugin + + + + UTF-8 + 17 + 17 + ${gcm.version} + + 1.3.0 + + 4.0.0-SNAPSHOT + + + + + + gov.hhs.aspr.ms + gcm + ${gcm.version} + + + + + + Shawn Hatch + Leidos + https://www.leidos.com + + + + + + + org.codehaus.mojo + flatten-maven-plugin + ${flatten-maven-plugin.version} + + + + flatten + process-resources + + flatten + + + true + + + + + flatten.clean + clean + + clean + + + + + + + + diff --git a/tutorials/lessons/lesson_16_person_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_16.java b/tutorials/lessons/lesson_16_person_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_16.java new file mode 100644 index 000000000..07a1ecbfc --- /dev/null +++ b/tutorials/lessons/lesson_16_person_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_16.java @@ -0,0 +1,293 @@ +package gov.hhs.aspr.ms.gcm.lessons; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.stream.IntStream; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.GlobalProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.ModelPlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.ModelReportLabel; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.PersonProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.Region; +import gov.hhs.aspr.ms.gcm.nucleus.Dimension; +import gov.hhs.aspr.ms.gcm.nucleus.Experiment; +import gov.hhs.aspr.ms.gcm.nucleus.ExperimentParameterData; +import gov.hhs.aspr.ms.gcm.nucleus.FunctionalDimension; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.GlobalPropertiesPlugin; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePlugin; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeoplePluginData; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.PersonPropertiesPlugin; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.reports.PersonPropertyReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.regions.RegionsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.NIOReportItemHandler; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import util.random.RandomGeneratorProvider; + +public final class Example_16 { + + private final Path outputDirectory; + + private Example_16(Path outputDirectory) { + this.outputDirectory = outputDirectory; + } + + private RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(524055747550937602L); + + private NIOReportItemHandler getNIOReportItemHandler() { + return NIOReportItemHandler.builder()// + .addReport(ModelReportLabel.PERSON_PROPERTY_REPORT, + outputDirectory.resolve("person_property_report.xls"))// + .addReport(ModelReportLabel.VACCINATION, outputDirectory.resolve("vaccination_report.xls"))// + .build(); + } + + private Plugin getPeoplePlugin() { + PeoplePluginData peoplePluginData = PeoplePluginData.builder().build(); + return PeoplePlugin.getPeoplePlugin(peoplePluginData); + } + + private Plugin getRegionsPlugin() { + RegionsPluginData.Builder regionsPluginDataBuilder = RegionsPluginData.builder(); + + for (int i = 0; i < 5; i++) { + regionsPluginDataBuilder.addRegion(new Region(i)); + } + RegionsPluginData regionsPluginData = regionsPluginDataBuilder.build(); + return RegionsPlugin.builder().setRegionsPluginData(regionsPluginData).getRegionsPlugin(); + } + + /* start code_ref= person_properties_get_person_properties_plugin|code_cap= The person properties plugin is built with the four person properties needed to model each person. The person property report is set to report only at the end of the simulation.*/ + private Plugin getPersonPropertiesPlugin() { + PersonPropertiesPluginData.Builder builder = PersonPropertiesPluginData.builder(); + PropertyDefinition propertyDefinition = PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(0)// + .build(); + builder.definePersonProperty(PersonProperty.EDUCATION_ATTEMPTS, propertyDefinition, 0, false); + builder.definePersonProperty(PersonProperty.VACCINE_ATTEMPTS, propertyDefinition, 0, false); + + propertyDefinition = PropertyDefinition.builder()// + .setType(Boolean.class)// + .build(); + builder.definePersonProperty(PersonProperty.REFUSES_VACCINE, propertyDefinition, 0, false); + + propertyDefinition = PropertyDefinition.builder()// + .setType(Boolean.class)// + .setDefaultValue(false)// + .build(); + builder.definePersonProperty(PersonProperty.VACCINATED, propertyDefinition, 0, false); + + PersonPropertiesPluginData personPropertiesPluginData = builder.build(); + + PersonPropertyReportPluginData personPropertyReportPluginData = PersonPropertyReportPluginData.builder()// + .setReportLabel(ModelReportLabel.PERSON_PROPERTY_REPORT)// + .setReportPeriod(ReportPeriod.END_OF_SIMULATION)// + .setDefaultInclusion(true)// + .build();// + + return PersonPropertiesPlugin.builder()// + .setPersonPropertiesPluginData(personPropertiesPluginData)// + .setPersonPropertyReportPluginData(personPropertyReportPluginData)// + .getPersonPropertyPlugin(); + } + /* end */ + + private Plugin getStochasticsPlugin() { + WellState wellState = WellState.builder().setSeed(randomGenerator.nextLong()).build(); + StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder()// + .setMainRNGState(wellState)// + .build(); + + return StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData); + } + + private Dimension getGlobalPropertyDimension(GlobalPropertyId globalPropertyId, String header, double[] values) { + FunctionalDimension.Builder dimensionBuilder = FunctionalDimension.builder();// + IntStream.range(0, values.length).forEach((i) -> { + dimensionBuilder.addLevel((context) -> { + GlobalPropertiesPluginData.Builder builder = context + .getPluginDataBuilder(GlobalPropertiesPluginData.Builder.class); + double value = values[i]; + builder.setGlobalPropertyValue(globalPropertyId, value, 0); + ArrayList result = new ArrayList<>(); + result.add(Double.toString(value)); + return result; + });// + }); + dimensionBuilder.addMetaDatum(header);// + return dimensionBuilder.build(); + } + + private Dimension getVaccineRefusalProbabilityDimension() { + double[] values = new double[] { 0.0, 0.25, 0.5, 0.75, 1.0 }; + return getGlobalPropertyDimension(GlobalProperty.VACCINE_REFUSAL_PROBABILITY, "intial_refusal_probability", + values); + } + + private Dimension getImmunityStartTimeDimension() { + double[] values = new double[] { 120.0, 180.0 }; + return getGlobalPropertyDimension(GlobalProperty.IMMUNITY_START_TIME, "immunity_start_time", values); + } + + private Dimension getImmunityProbabilityDimension() { + double[] values = new double[] { 0.0, 0.1, 0.2 }; + return getGlobalPropertyDimension(GlobalProperty.IMMUNITY_PROBABILITY, "immunity_probabilty", values); + } + + private Dimension getVaccineAttemptIntervalDimension() { + double[] values = new double[] { 30.0, 45.0, 60.0 }; + + return getGlobalPropertyDimension(GlobalProperty.VACCINE_ATTEMPT_INTERVAL, "vaccine_atttempt_interval", values); + } + + private Dimension getEducationAttemptIntervalDimension() { + double[] values = new double[] { 30.0, 60.0, 180.0 }; + return getGlobalPropertyDimension(GlobalProperty.EDUCATION_ATTEMPT_INTERVAL, "education_attempt_interval", + values); + } + + private Dimension getEducationSuccessRatedimension() { + double[] values = new double[] { 0.0, 0.1, 0.2 }; + return getGlobalPropertyDimension(GlobalProperty.EDUCATION_SUCCESS_RATE, "education_success_rate", values); + } + + /* start code_ref= person_properties_get_global_properties_plugin|code_cap=The global properties plugin is initialized with several properties. */ + private Plugin getGlobalPropertiesPlugin() { + GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder();// + + PropertyDefinition propertyDefinition = PropertyDefinition.builder()// + .setType(Double.class)// + .setDefaultValue(0.0)// + .setPropertyValueMutability(false)// + .build(); + + builder.defineGlobalProperty(GlobalProperty.IMMUNITY_START_TIME, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.VACCINE_ATTEMPT_INTERVAL, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.EDUCATION_ATTEMPT_INTERVAL, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.EDUCATION_SUCCESS_RATE, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.VACCINE_REFUSAL_PROBABILITY, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.IMMUNITY_PROBABILITY, propertyDefinition, 0); + + propertyDefinition = PropertyDefinition.builder()// + .setType(Double.class)// + .setDefaultValue(365.0)// + .setPropertyValueMutability(false)// + .build(); + builder.defineGlobalProperty(GlobalProperty.SIMULATION_DURATION, propertyDefinition, 0); + + propertyDefinition = PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(1000)// + .setPropertyValueMutability(false)// + .build(); + + builder.defineGlobalProperty(GlobalProperty.POPULATION_SIZE, propertyDefinition, 0); + + GlobalPropertiesPluginData globalPropertiesPluginData = builder.build(); + + return GlobalPropertiesPlugin.builder().setGlobalPropertiesPluginData(globalPropertiesPluginData) + .getGlobalPropertiesPlugin(); + + } + + /* end */ + /* start code_ref= person_properties_example_16_execute|code_cap=The various plugins are gathered from their initial data. */ + private void execute() { + + /* + * Create the global properties plugin + */ + Plugin globalPropertiesPlugin = getGlobalPropertiesPlugin(); + + /* + * Create the reports + */ + + NIOReportItemHandler nioReportItemHandler = getNIOReportItemHandler(); + + /* + * Create the people plugin filled with 1000 people + */ + Plugin peoplePlugin = getPeoplePlugin(); + + /* + * Create the region plugin 5 regions, each having a lat and lon and assign the + * people to random regions. + * + */ + Plugin regionsPlugin = getRegionsPlugin(); + + // Create the person properties plugin + Plugin personPropertiesPlugin = getPersonPropertiesPlugin(); + + /* + * create the stochastics plugin + */ + Plugin stochasticsPlugin = getStochasticsPlugin(); + + Plugin modelPlugin = ModelPlugin.getModelPlugin(); + /* end */ + + /* + * Assemble and execute the experiment + */ + + /* start code_ref= person_properties_execute_experiment|code_cap=The experiment executes 810 scenarios on 8 threads.*/ + + ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()// + .setThreadCount(8)// + .build(); + + Experiment.builder()// + + .addPlugin(personPropertiesPlugin)// + .addPlugin(globalPropertiesPlugin)// + .addPlugin(modelPlugin)// + .addPlugin(regionsPlugin)// + .addPlugin(peoplePlugin)// + .addPlugin(stochasticsPlugin)// + + .addDimension(getImmunityStartTimeDimension())// + .addDimension(getImmunityProbabilityDimension())// + .addDimension(getVaccineAttemptIntervalDimension())// + .addDimension(getEducationAttemptIntervalDimension())// + .addDimension(getEducationSuccessRatedimension())// + .addDimension(getVaccineRefusalProbabilityDimension())// + .addExperimentContextConsumer(nioReportItemHandler)// + .setExperimentParameterData(experimentParameterData)// + .build()// + .execute();// + /* end */ + } + + /* start code_ref= person_properties_example_16_main|code_cap=Executing example 16 with an output directory. */ + public static void main(String[] args) throws IOException { + if (args.length == 0) { + throw new RuntimeException("One output directory argument is required"); + } + Path outputPath = Paths.get(args[0]); + if (!Files.exists(outputPath)) { + Files.createDirectory(outputPath); + } else { + if (!Files.isDirectory(outputPath)) { + throw new IOException("Provided path is not a directory"); + } + } + new Example_16(outputPath).execute(); + } + /* end */ +} diff --git a/tutorials/lessons/lesson_16_person_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/GlobalProperty.java b/tutorials/lessons/lesson_16_person_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/GlobalProperty.java new file mode 100644 index 000000000..c93624b7e --- /dev/null +++ b/tutorials/lessons/lesson_16_person_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/GlobalProperty.java @@ -0,0 +1,18 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertyId; + +public enum GlobalProperty implements GlobalPropertyId { + IMMUNITY_START_TIME, // the time in days until immunity person property is added + IMMUNITY_PROBABILITY, // the probability that person will be immune when the immunity property is + // added + VACCINE_ATTEMPT_INTERVAL, // the maximum time between vaccine attempts + EDUCATION_ATTEMPT_INTERVAL, // the maximum time between vaccine education attempts + EDUCATION_SUCCESS_RATE, // the probability of changing the refusal person property per attempt + POPULATION_SIZE, // the initial size of the population + VACCINE_REFUSAL_PROBABILITY, // the probability that a person will refuse vaccination at the start of the + // simulation + SIMULATION_DURATION,// the total time the simulation will run + + ; +} diff --git a/tutorials/lessons/lesson_16_person_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelError.java b/tutorials/lessons/lesson_16_person_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelError.java new file mode 100644 index 000000000..3f316f445 --- /dev/null +++ b/tutorials/lessons/lesson_16_person_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelError.java @@ -0,0 +1,26 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import util.errors.ContractError; +import util.errors.ContractException; + +/** + * An enumeration supporting {@link ContractException} that acts as a general + * description of the exception. + * + * + */ +public enum ModelError implements ContractError { + + NEGATIVE_REGION_ID("Negative region id"),; + + private final String description; + + private ModelError(final String description) { + this.description = description; + } + + @Override + public String getDescription() { + return description; + } +} diff --git a/tutorials/lessons/lesson_16_person_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java b/tutorials/lessons/lesson_16_person_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java new file mode 100644 index 000000000..1e10022fb --- /dev/null +++ b/tutorials/lessons/lesson_16_person_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java @@ -0,0 +1,23 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors.PopulationLoader; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors.Vaccinator; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors.VaccineEducator; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.reports.VaccineReport; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; + +public final class ModelPlugin { + private ModelPlugin() { + + } + + public static Plugin getModelPlugin() { + return Plugin.builder()// + .setPluginId(ModelPluginId.PLUGIN_ID).setInitializer((c) -> { + c.addActor(new VaccineEducator()::init); + c.addActor(new Vaccinator()::init); + c.addActor(new PopulationLoader()::init); + c.addReport(new VaccineReport(ModelReportLabel.VACCINATION)::init); + }).build(); + } +} diff --git a/tutorials/lessons/lesson_16_person_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java b/tutorials/lessons/lesson_16_person_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java new file mode 100644 index 000000000..9c7193b57 --- /dev/null +++ b/tutorials/lessons/lesson_16_person_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java @@ -0,0 +1,11 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.nucleus.SimplePluginId; + +public final class ModelPluginId { + private ModelPluginId() { + } + + public final static PluginId PLUGIN_ID = new SimplePluginId("model plugin"); +} diff --git a/tutorials/lessons/lesson_16_person_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelReportLabel.java b/tutorials/lessons/lesson_16_person_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelReportLabel.java new file mode 100644 index 000000000..4ef0f33e8 --- /dev/null +++ b/tutorials/lessons/lesson_16_person_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelReportLabel.java @@ -0,0 +1,7 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; + +public enum ModelReportLabel implements ReportLabel { + PERSON_PROPERTY_REPORT, VACCINATION +} diff --git a/tutorials/lessons/lesson_16_person_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/PersonProperty.java b/tutorials/lessons/lesson_16_person_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/PersonProperty.java new file mode 100644 index 000000000..f88d063e7 --- /dev/null +++ b/tutorials/lessons/lesson_16_person_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/PersonProperty.java @@ -0,0 +1,12 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyId; + +public enum PersonProperty implements PersonPropertyId { + REFUSES_VACCINE, // + EDUCATION_ATTEMPTS, // + VACCINE_ATTEMPTS, // + IS_IMMUNE, // + VACCINATED,// boolean state of being vaccinated + ; +} diff --git a/tutorials/lessons/lesson_16_person_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/Region.java b/tutorials/lessons/lesson_16_person_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/Region.java new file mode 100644 index 000000000..b4b2a79c5 --- /dev/null +++ b/tutorials/lessons/lesson_16_person_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/Region.java @@ -0,0 +1,59 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * Identifier for all regions + * + * + */ + +@Immutable +public final class Region implements RegionId { + + private final int id; + + /** + * Constructs the region + * + * @throws ContractException + *
          • {@linkplain ModelError#NEGATIVE_REGION_ID}
          • + */ + public Region(int id) { + if (id < 0) { + throw new ContractException(ModelError.NEGATIVE_REGION_ID); + } + this.id = id; + } + + public int getValue() { + return id; + } + + @Override + public int hashCode() { + return id; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Region)) { + return false; + } + Region other = (Region) obj; + if (id != other.id) { + return false; + } + return true; + } + + @Override + public String toString() { + return "Region_" + id; + } +} diff --git a/tutorials/lessons/lesson_16_person_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/PopulationLoader.java b/tutorials/lessons/lesson_16_person_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/PopulationLoader.java new file mode 100644 index 000000000..b528d7a73 --- /dev/null +++ b/tutorials/lessons/lesson_16_person_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/PopulationLoader.java @@ -0,0 +1,95 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.GlobalProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.PersonProperty; +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonConstructionData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonConstructionData.Builder; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyDefinitionInitialization; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyValueInitialization; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; + +public class PopulationLoader { + private RandomGenerator randomGenerator; + private PeopleDataManager peopleDataManager; + private PersonPropertiesDataManager personPropertiesDataManager; + private GlobalPropertiesDataManager globalPropertiesDataManager; + + /* + * start code_ref= person_properties_population_loader_add_immunity_property|code_cap=At the + * time set via the global property, IMMUNITY_START_TIME, the population loader + * defines the person property, IS_IMMUNE, and sets the property value for each + * person. + */ + private void addImmunityProperty() { + PersonPropertyDefinitionInitialization.Builder builder = PersonPropertyDefinitionInitialization.builder(); + builder.setPersonPropertyId(PersonProperty.IS_IMMUNE); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setType(Boolean.class).build(); + builder.setPropertyDefinition(propertyDefinition); + double immunityProbability = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.IMMUNITY_PROBABILITY); + + for (PersonId personId : peopleDataManager.getPeople()) { + boolean isImmune = randomGenerator.nextDouble() < immunityProbability; + builder.addPropertyValue(personId, isImmune); + } + PersonPropertyDefinitionInitialization personPropertyDefinitionInitialization = builder.build(); + personPropertiesDataManager.definePersonProperty(personPropertyDefinitionInitialization); + } + /* end */ + + /* + * start code_ref= person_properties_population_loader_init|code_cap= The + * population loader initializes by creating people dictated by the + * POPULATION_SIZE global property. Each person is assigned a region and random + * value for the person property, REFUSES_VACCINE, based on the global property, + * VACCINE_REFUSAL_PROBABILITY. + */ + public void init(ActorContext actorContext) { + peopleDataManager = actorContext.getDataManager(PeopleDataManager.class); + personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class); + globalPropertiesDataManager = actorContext.getDataManager(GlobalPropertiesDataManager.class); + RegionsDataManager regionsDataManager = actorContext.getDataManager(RegionsDataManager.class); + StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class); + randomGenerator = stochasticsDataManager.getRandomGenerator(); + List regionIds = new ArrayList<>(regionsDataManager.getRegionIds()); + + int populationSize = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.POPULATION_SIZE); + double refusalProbability = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.VACCINE_REFUSAL_PROBABILITY); + + Builder personConstructionDataBuilder = PersonConstructionData.builder(); + for (int i = 0; i < populationSize; i++) { + RegionId regionId = regionIds.get(randomGenerator.nextInt(regionIds.size())); + personConstructionDataBuilder.add(regionId); + + boolean refusesVaccine = randomGenerator.nextDouble() < refusalProbability; + PersonPropertyValueInitialization personPropertyInitialization = new PersonPropertyValueInitialization( + PersonProperty.REFUSES_VACCINE, refusesVaccine); + personConstructionDataBuilder.add(personPropertyInitialization); + PersonConstructionData personConstructionData = personConstructionDataBuilder.build(); + peopleDataManager.addPerson(personConstructionData); + } + + double simulationDuration = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.SIMULATION_DURATION); + actorContext.addPlan((c) -> c.halt(), simulationDuration); + + double immunityStartTime = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.IMMUNITY_START_TIME); + actorContext.addPlan((c) -> addImmunityProperty(), immunityStartTime); + } + /* end */ +} diff --git a/tutorials/lessons/lesson_16_person_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/Vaccinator.java b/tutorials/lessons/lesson_16_person_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/Vaccinator.java new file mode 100644 index 000000000..42f5fb6e7 --- /dev/null +++ b/tutorials/lessons/lesson_16_person_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/Vaccinator.java @@ -0,0 +1,126 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors; + +import java.util.List; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.GlobalProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.PersonProperty; +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.EventFilter; +import gov.hhs.aspr.ms.gcm.nucleus.Plan; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.events.PersonPropertyUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; + +public final class Vaccinator { + + private PeopleDataManager peopleDataManager; + private RandomGenerator randomGenerator; + private PersonPropertiesDataManager personPropertiesDataManager; + private GlobalPropertiesDataManager globalPropertiesDataManager; + private double vaccineAttemptInterval; + private ActorContext actorContext; + + /* start code_ref= person_properties_vaccinator_vaccinate_person|code_cap= With each vaccination attempt, the vaccinator updates the VACCINE_ATTEMPTS person property for the person. People who refuse vaccination are scheduled for another vaccination attempt. */ + private void vaccinatePerson(PersonId personId) { + int vaccineAttempts = personPropertiesDataManager.getPersonPropertyValue(personId, + PersonProperty.VACCINE_ATTEMPTS); + personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.VACCINE_ATTEMPTS, + vaccineAttempts + 1); + + boolean isImmune = false; + if (personPropertiesDataManager.personPropertyIdExists(PersonProperty.IS_IMMUNE)) { + isImmune = personPropertiesDataManager.getPersonPropertyValue(personId, PersonProperty.IS_IMMUNE); + } + + Boolean refusesVaccine = personPropertiesDataManager.getPersonPropertyValue(personId, + PersonProperty.REFUSES_VACCINE); + if (!isImmune) { + if (refusesVaccine) { + double planTime = actorContext.getTime() + randomGenerator.nextDouble() * vaccineAttemptInterval; + Object planKey = personId; + + Plan plan = Plan.builder(ActorContext.class)// + .setCallbackConsumer((c) -> vaccinatePerson(personId))// + .setKey(planKey)// + .setTime(planTime)// + .build();// + + actorContext.addPlan(plan); + } else { + personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.VACCINATED, true); + } + } + } + /* end */ + + /* start code_ref= person_properties_vaccinator_handle_vaccine_acceptance|code_cap=When a person stops refusing vaccination, the vaccinator immediately attempts the vaccination of that person. */ + private void handleVaccineAcceptance(ActorContext actorContext, + PersonPropertyUpdateEvent personPropertyUpdateEvent) { + /* + * We know that the person property is PersonProperty.REFUSES_VACCINE since we + * used an event filter when subscribing + */ + Boolean refusesVaccine = personPropertyUpdateEvent.getCurrentPropertyValue(); + if (!refusesVaccine) { + PersonId personId = personPropertyUpdateEvent.personId(); + // drop the current plan + actorContext.removePlan(personId); + vaccinatePerson(personId); + } + } + /* end */ + + /* start code_ref= person_properties_vaccinator_plan_vaccination|code_cap= Each unvaccinated person has a planned vaccination based on the VACCINE_ATTEMPT_INTERVAL global property. */ + private void planVaccination(PersonId personId) { + double planTime = actorContext.getTime() + randomGenerator.nextDouble() * vaccineAttemptInterval; + Object planKey = personId; + Plan plan = Plan.builder(ActorContext.class)// + .setCallbackConsumer((c) -> vaccinatePerson(personId))// + .setKey(planKey)// + .setTime(planTime)// + .build();// + actorContext.addPlan(plan); + } + + private void handleNewPerson(PersonId personId) { + boolean vaccinated = personPropertiesDataManager.getPersonPropertyValue(personId, PersonProperty.VACCINATED); + if (!vaccinated) { + planVaccination(personId); + } + } + /* end */ + + /* start code_ref= person_properties_vaccinator_init|code_cap=The vaccinator initializes by planning vaccination attempts for each person who is unvaccinated. It also subscribes to changes in the vaccine refusal property for all people so that when a person stops refusing vaccine, they can be vaccinated immediately.*/ + public void init(ActorContext actorContext) { + this.actorContext = actorContext; + StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class); + randomGenerator = stochasticsDataManager.getRandomGenerator(); + peopleDataManager = actorContext.getDataManager(PeopleDataManager.class); + personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class); + globalPropertiesDataManager = actorContext.getDataManager(GlobalPropertiesDataManager.class); + + List unvaccinatedPeople = personPropertiesDataManager + .getPeopleWithPropertyValue(PersonProperty.VACCINATED, false); + vaccineAttemptInterval = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.VACCINE_ATTEMPT_INTERVAL); + for (PersonId personId : unvaccinatedPeople) { + planVaccination(personId); + } + + EventFilter eventFilter = personPropertiesDataManager// + .getEventFilterForPersonPropertyUpdateEvent(PersonProperty.REFUSES_VACCINE); + + actorContext.subscribe(eventFilter, this::handleVaccineAcceptance); + + actorContext.subscribe(peopleDataManager.getEventFilterForPersonAdditionEvent(), (c, e) -> { + handleNewPerson(e.personId()); + }); + + } + /* end */ +} diff --git a/tutorials/lessons/lesson_16_person_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/VaccineEducator.java b/tutorials/lessons/lesson_16_person_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/VaccineEducator.java new file mode 100644 index 000000000..4dfd63f99 --- /dev/null +++ b/tutorials/lessons/lesson_16_person_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/VaccineEducator.java @@ -0,0 +1,90 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors; + +import java.util.List; +import java.util.function.Consumer; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.GlobalProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.PersonProperty; +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; + +public class VaccineEducator { + + private PersonPropertiesDataManager personPropertiesDataManager; + private GlobalPropertiesDataManager globalPropertiesDataManager; + private double educationAttemptInterval; + private double educationSuccessRate; + private RandomGenerator randomGenerator; + private ActorContext actorContext; + + /* start code_ref= person_properties_vaccine_educator_educate_person|code_cap= After updating the number of educational attempts for a person, the vaccine educator succeeds in educating the person to stop refusing vaccination based on the global property, EDUCATION_SUCCESS_RATE. */ + private void educatePerson(PersonId personId) { + int educationAttempts = personPropertiesDataManager.getPersonPropertyValue(personId, + PersonProperty.EDUCATION_ATTEMPTS); + personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.EDUCATION_ATTEMPTS, + educationAttempts + 1); + + if (randomGenerator.nextDouble() < educationSuccessRate) { + personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.REFUSES_VACCINE, false); + } else { + planEducation(personId); + } + } + /* end */ + + /* start code_ref= person_properties_vaccine_educator_handle_new_person|code_cap= Attempts to educate a person on vaccination are scheduled at random times in the future based on the global property, EDUCATION_ATTEMPT_INTERVAL. */ + private void planEducation(PersonId personId) { + double planTime = actorContext.getTime() + randomGenerator.nextDouble() * educationAttemptInterval; + Consumer plan = (c) -> educatePerson(personId); + actorContext.addPlan(plan, planTime); + } + + private void handleNewPerson(PersonId personId) { + boolean vaccinated = personPropertiesDataManager.getPersonPropertyValue(personId, PersonProperty.VACCINATED); + if (!vaccinated) { + Boolean refusesVaccine = personPropertiesDataManager.getPersonPropertyValue(personId, + PersonProperty.REFUSES_VACCINE); + if (refusesVaccine) { + planEducation(personId); + } + } + } + /* end */ + + /* start code_ref= person_properties_vaccine_educator_init|code_cap=The vaccine educator initializes by planning the education for each person who refuses vaccination.*/ + public void init(ActorContext actorContext) { + this.actorContext = actorContext; + + StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class); + randomGenerator = stochasticsDataManager.getRandomGenerator(); + PeopleDataManager peopleDataManager = actorContext.getDataManager(PeopleDataManager.class); + personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class); + globalPropertiesDataManager = actorContext.getDataManager(GlobalPropertiesDataManager.class); + + educationAttemptInterval = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.EDUCATION_ATTEMPT_INTERVAL); + educationSuccessRate = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.EDUCATION_SUCCESS_RATE); + + List unvaccinatedPeople = personPropertiesDataManager + .getPeopleWithPropertyValue(PersonProperty.VACCINATED, false); + for (PersonId personId : unvaccinatedPeople) { + Boolean refusesVaccine = personPropertiesDataManager.getPersonPropertyValue(personId, + PersonProperty.REFUSES_VACCINE); + if (refusesVaccine) { + planEducation(personId); + } + } + + actorContext.subscribe(peopleDataManager.getEventFilterForPersonAdditionEvent(), (c, e) -> { + handleNewPerson(e.personId()); + }); + } + /* end */ +} diff --git a/tutorials/lessons/lesson_16_person_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/reports/VaccineReport.java b/tutorials/lessons/lesson_16_person_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/reports/VaccineReport.java new file mode 100644 index 000000000..a3c85384f --- /dev/null +++ b/tutorials/lessons/lesson_16_person_properties_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/reports/VaccineReport.java @@ -0,0 +1,76 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.reports; + +import java.util.List; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.PersonProperty; +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; + +public final class VaccineReport { + + private final ReportLabel reportLabel; + + public VaccineReport(ReportLabel reportLabel) { + this.reportLabel = reportLabel; + } + + public void init(ReportContext reportContext) { + reportContext.subscribeToSimulationClose(this::report); + } + + private ReportHeader reportHeader = ReportHeader.builder()// + .add("vaccinated_immune")// + .add("vaccinated_susceptible")// + .add("unvaccinated_immune")// + .add("unvaccinated_susceptible")// + .build(); + + private void report(ReportContext reportContext) { + PeopleDataManager peopleDataManager = reportContext.getDataManager(PeopleDataManager.class); + PersonPropertiesDataManager personPropertiesDataManager = reportContext + .getDataManager(PersonPropertiesDataManager.class); + + int vaccinated_immune = 0; + int vaccinated_susceptible = 0; + int unvaccinated_immune = 0; + int unvaccinated_susceptible = 0; + + List people = peopleDataManager.getPeople(); + for (PersonId personId : people) { + boolean vaccinated = personPropertiesDataManager.getPersonPropertyValue(personId, + PersonProperty.VACCINATED); + boolean immune = personPropertiesDataManager.getPersonPropertyValue(personId, PersonProperty.IS_IMMUNE); + if (vaccinated) { + if (immune) { + vaccinated_immune++; + } else { + vaccinated_susceptible++; + } + } else { + if (immune) { + unvaccinated_immune++; + } else { + unvaccinated_susceptible++; + } + } + } + + ReportItem.Builder builder = ReportItem.builder()// + .setReportLabel(reportLabel)// + .setReportHeader(reportHeader); + + builder.addValue(vaccinated_immune); + builder.addValue(vaccinated_susceptible); + builder.addValue(unvaccinated_immune); + builder.addValue(unvaccinated_susceptible); + + ReportItem reportItem = builder.build(); + reportContext.releaseOutput(reportItem); + } + +} diff --git a/tutorials/lessons/lesson_17_groups_plugin/pom.xml b/tutorials/lessons/lesson_17_groups_plugin/pom.xml new file mode 100644 index 000000000..8d88121dd --- /dev/null +++ b/tutorials/lessons/lesson_17_groups_plugin/pom.xml @@ -0,0 +1,79 @@ + + 4.0.0 + + + ASPR + https://www.phe.gov + + + + gov.hhs.aspr.ms.gcm.tutorials + lesson_17_groups_plugin + ${revision} + jar + gcm lesson 17 groups plugin + Tutorial introducing the groups plugin + + + + UTF-8 + 17 + 17 + ${gcm.version} + + 1.3.0 + + 4.0.0-SNAPSHOT + + + + + + gov.hhs.aspr.ms + gcm + ${gcm.version} + + + + + + Shawn Hatch + Leidos + https://www.leidos.com + + + + + + + org.codehaus.mojo + flatten-maven-plugin + ${flatten-maven-plugin.version} + + + + flatten + process-resources + + flatten + + + true + + + + + flatten.clean + clean + + clean + + + + + + + + diff --git a/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_17.java b/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_17.java new file mode 100644 index 000000000..44fce0aba --- /dev/null +++ b/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_17.java @@ -0,0 +1,320 @@ +package gov.hhs.aspr.ms.gcm.lessons; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.stream.IntStream; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.ModelPlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.ModelReportLabel; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.DiseaseState; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.GlobalProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.GroupProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.GroupType; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.PersonProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.Region; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.SchoolStatus; +import gov.hhs.aspr.ms.gcm.nucleus.Dimension; +import gov.hhs.aspr.ms.gcm.nucleus.Experiment; +import gov.hhs.aspr.ms.gcm.nucleus.ExperimentParameterData; +import gov.hhs.aspr.ms.gcm.nucleus.FunctionalDimension; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.GlobalPropertiesPlugin; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.groups.GroupsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.groups.datamanagers.GroupsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.groups.reports.GroupPopulationReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePlugin; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeoplePluginData; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.PersonPropertiesPlugin; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.reports.PersonPropertyReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.regions.RegionsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.NIOReportItemHandler; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import util.random.RandomGeneratorProvider; + +public final class Example_17 { + + private final Path outputDirectory; + + private Example_17(Path outputDirectory) { + this.outputDirectory = outputDirectory; + } + + private RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(9032703880551658180L); + + private NIOReportItemHandler getNIOReportItemHandler() { + return NIOReportItemHandler.builder()// + .addReport(ModelReportLabel.GROUP_POPULATON, outputDirectory.resolve("group_population_report.xls"))// + .addReport(ModelReportLabel.PERSON_PROPERTY, outputDirectory.resolve("person_property_report.xls"))// + .addReport(ModelReportLabel.DISEASE_STATE, outputDirectory.resolve("disease_state_report.xls"))// + .addReport(ModelReportLabel.CONTAGION, outputDirectory.resolve("contagion_report.xls"))// + .build(); + } + + private Plugin getPeoplePlugin() { + PeoplePluginData peoplePluginData = PeoplePluginData.builder().build(); + return PeoplePlugin.getPeoplePlugin(peoplePluginData); + } + + /* start code_ref= groups_plugin_example_17_groups_plugin|code_cap=The groups plugin includes a tele-work property for work places and open status properties for schools.*/ + private Plugin getGroupsPlugin() { + GroupsPluginData.Builder builder = GroupsPluginData.builder(); + for (GroupType groupType : GroupType.values()) { + builder.addGroupTypeId(groupType); + } + PropertyDefinition propertyDefinition = PropertyDefinition.builder()// + .setType(Boolean.class)// + .setDefaultValue(false)// + .build(); + + builder.defineGroupProperty(GroupType.WORK, GroupProperty.TELEWORK, propertyDefinition); + + propertyDefinition = PropertyDefinition.builder()// + .setType(SchoolStatus.class)// + .setDefaultValue(SchoolStatus.OPEN)// + .build(); + + builder.defineGroupProperty(GroupType.SCHOOL, GroupProperty.SCHOOL_STATUS, propertyDefinition); + + GroupsPluginData groupsPluginData = builder.build(); + + GroupPopulationReportPluginData groupPopulationReportPluginData = // + GroupPopulationReportPluginData.builder()// + .setReportLabel(ModelReportLabel.GROUP_POPULATON)// + .setReportPeriod(ReportPeriod.END_OF_SIMULATION)// + .build();// + return GroupsPlugin.builder()// + .setGroupsPluginData(groupsPluginData)// + .setGroupPopulationReportPluginData(groupPopulationReportPluginData)// + .getGroupsPlugin(); + } + + /* end */ + private Plugin getStochasticsPlugin() { + + WellState wellState = WellState.builder().setSeed(randomGenerator.nextLong()).build(); + StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder()// + .setMainRNGState(wellState)// + .build(); + + return StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData); + } + + /* + * start code_ref= groups_plugin_example_17_person_properties|code_cap=The person properties plugin is initialized with several properties. + */ + private Plugin getPersonPropertiesPlugin() { + + PersonPropertiesPluginData.Builder builder = PersonPropertiesPluginData.builder(); + + PropertyDefinition propertyDefinition = PropertyDefinition.builder()// + .setType(Integer.class)// + .build(); + + builder.definePersonProperty(PersonProperty.AGE, propertyDefinition, 0, false);// + + propertyDefinition = PropertyDefinition.builder()// + .setType(DiseaseState.class)// + .setDefaultValue(DiseaseState.SUSCEPTIBLE).build(); + + builder.definePersonProperty(PersonProperty.DISEASE_STATE, propertyDefinition, 0, false);// + + propertyDefinition = PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(0).build(); + + builder.definePersonProperty(PersonProperty.INFECTED_COUNT, propertyDefinition, 0, false);// + + PersonPropertiesPluginData personPropertiesPluginData = builder.build(); + + PersonPropertyReportPluginData personPropertyReportPluginData = PersonPropertyReportPluginData.builder()// + .setReportLabel(ModelReportLabel.PERSON_PROPERTY)// + .setReportPeriod(ReportPeriod.DAILY)// + .includePersonProperty(PersonProperty.DISEASE_STATE)// + .build(); + + return PersonPropertiesPlugin.builder()// + .setPersonPropertiesPluginData(personPropertiesPluginData)// + .setPersonPropertyReportPluginData(personPropertyReportPluginData)// + .getPersonPropertyPlugin(); + } + /* end */ + + /* start code_ref= groups_plugin_example_17_global_properties|code_cap= The + * global properties plugin is initialized with several properties*/ + private Plugin getGlobalPropertiesPlugin() { + GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder();// + + PropertyDefinition propertyDefinition = PropertyDefinition.builder()// + .setType(Double.class)// + .setPropertyValueMutability(false)// + .setDefaultValue(0.0).build(); + + builder.defineGlobalProperty(GlobalProperty.SUSCEPTIBLE_POPULATION_PROPORTION, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.AVERAGE_HOME_SIZE, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.AVERAGE_SCHOOL_SIZE, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.AVERAGE_WORK_SIZE, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.CHILD_POPULATION_PROPORTION, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.SENIOR_POPULATION_PROPORTION, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.R0, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.TELEWORK_INFECTION_THRESHOLD, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.TELEWORK_PROBABILTY, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.SCHOOL_COHORT_INFECTION_THRESHOLD, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.SCHOOL_CLOSURE_INFECTION_THRESHOLD, propertyDefinition, 0); + + propertyDefinition = PropertyDefinition.builder()// + .setType(Integer.class)// + .setPropertyValueMutability(false)// + .build(); + builder.defineGlobalProperty(GlobalProperty.INITIAL_INFECTIONS, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.MIN_INFECTIOUS_PERIOD, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.MAX_INFECTIOUS_PERIOD, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.POPULATION_SIZE, propertyDefinition, 0); + + builder.setGlobalPropertyValue(GlobalProperty.POPULATION_SIZE, 10_000, 0); + builder.setGlobalPropertyValue(GlobalProperty.SUSCEPTIBLE_POPULATION_PROPORTION, 1.0, 0); + builder.setGlobalPropertyValue(GlobalProperty.INITIAL_INFECTIONS, 10, 0); + builder.setGlobalPropertyValue(GlobalProperty.MIN_INFECTIOUS_PERIOD, 3, 0); + builder.setGlobalPropertyValue(GlobalProperty.MAX_INFECTIOUS_PERIOD, 12, 0); + builder.setGlobalPropertyValue(GlobalProperty.R0, 2.0, 0); + builder.setGlobalPropertyValue(GlobalProperty.CHILD_POPULATION_PROPORTION, 0.235, 0); + builder.setGlobalPropertyValue(GlobalProperty.SENIOR_POPULATION_PROPORTION, 0.169, 0); + builder.setGlobalPropertyValue(GlobalProperty.AVERAGE_HOME_SIZE, 2.5, 0); + builder.setGlobalPropertyValue(GlobalProperty.AVERAGE_SCHOOL_SIZE, 250.0, 0); + builder.setGlobalPropertyValue(GlobalProperty.AVERAGE_WORK_SIZE, 30.0, 0); + + GlobalPropertiesPluginData globalPropertiesPluginData = builder.build(); + + return GlobalPropertiesPlugin.builder().setGlobalPropertiesPluginData(globalPropertiesPluginData) + .getGlobalPropertiesPlugin(); + } + + /* end */ + private Plugin getRegionsPlugin() { + RegionsPluginData.Builder regionsPluginDataBuilder = RegionsPluginData.builder(); + + for (int i = 0; i < 5; i++) { + regionsPluginDataBuilder.addRegion(new Region(i)); + } + RegionsPluginData regionsPluginData = regionsPluginDataBuilder.build(); + return RegionsPlugin.builder().setRegionsPluginData(regionsPluginData).getRegionsPlugin(); + } + + private Dimension getGlobalPropertyDimension(GlobalPropertyId globalPropertyId, String header, double[] values) { + FunctionalDimension.Builder dimensionBuilder = FunctionalDimension.builder();// + IntStream.range(0, values.length).forEach((i) -> { + dimensionBuilder.addLevel((context) -> { + GlobalPropertiesPluginData.Builder builder = context + .getPluginDataBuilder(GlobalPropertiesPluginData.Builder.class); + double value = values[i]; + builder.setGlobalPropertyValue(globalPropertyId, value, 0); + ArrayList result = new ArrayList<>(); + result.add(Double.toString(value)); + return result; + });// + }); + dimensionBuilder.addMetaDatum(header);// + return dimensionBuilder.build(); + } + + /* + * start code_ref= groups_plugin_example_17_execute|code_cap=The various plugins + * are gathered from their initial data, dimensions are added and the experiment + * is executed over 36 scenarios using 8 threads. + */ + private void execute() { + + ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()// + .setThreadCount(8)// + .build(); + + Experiment.builder() + + .addPlugin(getGlobalPropertiesPlugin())// + .addPlugin(getPersonPropertiesPlugin())// + .addPlugin(getRegionsPlugin())// + .addPlugin(getPeoplePlugin())// + .addPlugin(getGroupsPlugin())// + .addPlugin(getStochasticsPlugin())// + .addPlugin(ModelPlugin.getModelPlugin())// + + .addDimension(getTeleworkProbabilityDimension())// + .addDimension(getTeleworkInfectionThresholdDimension())// + .addDimension(getSchoolDimension())// + + .addExperimentContextConsumer(getNIOReportItemHandler())// + .setExperimentParameterData(experimentParameterData)// + .build()// + .execute();// + } + /* end */ + + /* + * start code_ref= groups_plugin_example_17_init|code_cap=Executing example 17 + * with an output directory. + */ + public static void main(String[] args) throws IOException { + if (args.length == 0) { + throw new RuntimeException("One output directory argument is required"); + } + Path outputDirectory = Paths.get(args[0]); + if (!Files.exists(outputDirectory)) { + Files.createDirectory(outputDirectory); + } else { + if (!Files.isDirectory(outputDirectory)) { + throw new IOException("Provided path is not a directory"); + } + } + + new Example_17(outputDirectory).execute(); + } + /* end */ + + private Dimension getTeleworkProbabilityDimension() { + double[] values = new double[] { 0.1, 0.3, 0.5, 0.8 }; + return getGlobalPropertyDimension(GlobalProperty.TELEWORK_PROBABILTY, "telework_probabilty", values); + } + + private Dimension getTeleworkInfectionThresholdDimension() { + double[] values = new double[] { 0.001, 0.01, 0.1 }; + return getGlobalPropertyDimension(GlobalProperty.TELEWORK_INFECTION_THRESHOLD, "telework_infection_threshold", + values); + } + + private Dimension getSchoolDimension() { + double[] cohortValues = { 0.001, 0.01, 0.1 }; + double[] closureValues = { 0.01, 0.02, 0.2 }; + + FunctionalDimension.Builder dimensionBuilder = FunctionalDimension.builder();// + IntStream.range(0, cohortValues.length).forEach((i) -> { + dimensionBuilder.addLevel((context) -> { + GlobalPropertiesPluginData.Builder builder = context + .getPluginDataBuilder(GlobalPropertiesPluginData.Builder.class); + double cohortValue = cohortValues[i]; + builder.setGlobalPropertyValue(GlobalProperty.SCHOOL_COHORT_INFECTION_THRESHOLD, cohortValue, 0); + double closureValue = closureValues[i]; + builder.setGlobalPropertyValue(GlobalProperty.SCHOOL_CLOSURE_INFECTION_THRESHOLD, closureValue, 0); + ArrayList result = new ArrayList<>(); + result.add(Double.toString(cohortValue)); + result.add(Double.toString(closureValue)); + return result; + });// + }); + dimensionBuilder.addMetaDatum("school_cohort_infection_threshold");// + dimensionBuilder.addMetaDatum("school_closure_infection_threshold");// + return dimensionBuilder.build(); + } + +} diff --git a/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java b/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java new file mode 100644 index 000000000..021f13e9e --- /dev/null +++ b/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java @@ -0,0 +1,29 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors.InfectionManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors.PopulationLoader; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors.SchoolManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors.TeleworkManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.reports.ContagionReport; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.reports.DiseaseStateReport; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; + +public final class ModelPlugin { + private ModelPlugin() { + + } + + public static Plugin getModelPlugin() { + return Plugin.builder()// + .setPluginId(ModelPluginId.PLUGIN_ID).setInitializer((c) -> { + c.addActor(new PopulationLoader()::init); + c.addActor(new InfectionManager()::init); + c.addActor(new TeleworkManager()::init); + c.addActor(new SchoolManager()::init); + c.addReport(new DiseaseStateReport(ModelReportLabel.DISEASE_STATE, + ReportPeriod.END_OF_SIMULATION)::init);// + c.addReport(new ContagionReport(ModelReportLabel.CONTAGION)::init);// + }).build(); + } +} diff --git a/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java b/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java new file mode 100644 index 000000000..9c7193b57 --- /dev/null +++ b/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java @@ -0,0 +1,11 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.nucleus.SimplePluginId; + +public final class ModelPluginId { + private ModelPluginId() { + } + + public final static PluginId PLUGIN_ID = new SimplePluginId("model plugin"); +} diff --git a/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelReportLabel.java b/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelReportLabel.java new file mode 100644 index 000000000..61bdcc30d --- /dev/null +++ b/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelReportLabel.java @@ -0,0 +1,7 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; + +public enum ModelReportLabel implements ReportLabel { + GROUP_POPULATON, PERSON_PROPERTY, DISEASE_STATE, CONTAGION,; +} diff --git a/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/InfectionManager.java b/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/InfectionManager.java new file mode 100644 index 000000000..2401909bb --- /dev/null +++ b/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/InfectionManager.java @@ -0,0 +1,148 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Random; + +import org.apache.commons.math3.random.RandomGenerator; +import org.apache.commons.math3.util.FastMath; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.DiseaseState; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.GlobalProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.GroupProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.GroupType; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.PersonProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.SchoolStatus; +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.groups.datamanagers.GroupsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupId; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupSampler; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupTypeId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; + +public class InfectionManager { + private ActorContext actorContext; + private PersonPropertiesDataManager personPropertiesDataManager; + private GroupsDataManager groupsDataManager; + private RandomGenerator randomGenerator; + private int minInfectiousPeriod; + private int maxInfectiousPeriod; + private double infectionInterval; + + /* start code_ref= groups_plugin_infection_manager_init|code_cap=The infection manager initializes by infecting the initially infected people in the first day. */ + public void init(ActorContext actorContext) { + this.actorContext = actorContext; + + StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class); + randomGenerator = stochasticsDataManager.getRandomGenerator(); + Random random = new Random(randomGenerator.nextLong()); + + groupsDataManager = actorContext.getDataManager(GroupsDataManager.class); + GlobalPropertiesDataManager globalPropertiesDataManager = actorContext + .getDataManager(GlobalPropertiesDataManager.class); + personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class); + List susceptiblePeople = personPropertiesDataManager + .getPeopleWithPropertyValue(PersonProperty.DISEASE_STATE, DiseaseState.SUSCEPTIBLE); + List susceptibleAdults = new ArrayList<>(); + for (PersonId personId : susceptiblePeople) { + int age = personPropertiesDataManager.getPersonPropertyValue(personId, PersonProperty.AGE); + if (age > 18) { + susceptibleAdults.add(personId); + } + } + + Collections.shuffle(susceptibleAdults, random); + + minInfectiousPeriod = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.MIN_INFECTIOUS_PERIOD); + maxInfectiousPeriod = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.MAX_INFECTIOUS_PERIOD); + double r0 = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.R0); + infectionInterval = (double) (minInfectiousPeriod + maxInfectiousPeriod) / (2 * r0); + + int initialInfections = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.INITIAL_INFECTIONS); + initialInfections = FastMath.min(initialInfections, susceptibleAdults.size()); + + for (int i = 0; i < initialInfections; i++) { + PersonId personId = susceptibleAdults.get(i); + double planTime = randomGenerator.nextDouble() * 0.5 + 0.25; + actorContext.addPlan((c) -> infectPerson(personId), planTime); + } + } + /* end */ + + /* start code_ref= groups_plugin_infection_manager_infect_person|code_cap= When a person is infected, the number of possible infectious contacts is determined and planned. After the last infectious contact, the person is scheduled to become recovered.*/ + private void infectPerson(PersonId personId) { + personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.DISEASE_STATE, + DiseaseState.INFECTIOUS); + int infectiousDays = randomGenerator.nextInt(maxInfectiousPeriod - minInfectiousPeriod) + minInfectiousPeriod; + int infectionCount = (int) FastMath.round(((double) infectiousDays / infectionInterval)); + double planTime = actorContext.getTime(); + for (int j = 0; j < infectionCount; j++) { + planTime += infectionInterval; + actorContext.addPlan((c) -> infectContact(personId), planTime); + } + actorContext.addPlan((c) -> endInfectiousness(personId), planTime); + } + /* end */ + + /* start code_ref= groups_plugin_infection_manager_infect_contact|code_cap= The infection manager attempts to infect a susceptible person found in a randomly selected group associated with the currently infected person. */ + private void infectContact(PersonId personId) { + List groupsForPerson = groupsDataManager.getGroupsForPerson(personId); + GroupId groupId = groupsForPerson.get(randomGenerator.nextInt(groupsForPerson.size())); + + // work groups doing telework have a 50% contact mitigation + GroupTypeId groupTypeId = groupsDataManager.getGroupType(groupId); + if (groupTypeId.equals(GroupType.WORK)) { + boolean teleworkGroup = groupsDataManager.getGroupPropertyValue(groupId, GroupProperty.TELEWORK); + if (teleworkGroup) { + if (randomGenerator.nextBoolean()) { + return; + } + } + } + + // school groups in COHORT mode have a 50% contact mitigation + // school groups in CLOSED mode have a 100% contact mitigation + if (groupTypeId.equals(GroupType.SCHOOL)) { + SchoolStatus schoolStatus = groupsDataManager.getGroupPropertyValue(groupId, GroupProperty.SCHOOL_STATUS); + switch (schoolStatus) { + case COHORT: + if (randomGenerator.nextBoolean()) { + return; + } + break; + case CLOSED: + return; + default: + // no mitigation + break; + } + } + + GroupSampler groupSampler = GroupSampler.builder().setExcludedPersonId(personId).build(); + Optional optional = groupsDataManager.sampleGroup(groupId, groupSampler); + if (optional.isPresent()) { + PersonId contactedPerson = optional.get(); + DiseaseState diseaseState = personPropertiesDataManager.getPersonPropertyValue(contactedPerson, + PersonProperty.DISEASE_STATE); + if (diseaseState == DiseaseState.SUSCEPTIBLE) { + int infectedCount = personPropertiesDataManager.getPersonPropertyValue(personId, + PersonProperty.INFECTED_COUNT); + infectedCount++; + personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.INFECTED_COUNT, + infectedCount); + infectPerson(contactedPerson); + } + } + } + /* end */ + + private void endInfectiousness(PersonId personId) { + personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.DISEASE_STATE, + DiseaseState.RECOVERED); + } +} diff --git a/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/PopulationLoader.java b/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/PopulationLoader.java new file mode 100644 index 000000000..053db0665 --- /dev/null +++ b/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/PopulationLoader.java @@ -0,0 +1,213 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.apache.commons.math3.util.FastMath; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.DiseaseState; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.GlobalProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.GroupType; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.PersonProperty; +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.groups.datamanagers.GroupsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupConstructionInfo; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupId; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonConstructionData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyValueInitialization; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; + +public class PopulationLoader { + + private RandomGenerator randomGenerator; + private RegionsDataManager regionsDataManager; + private PersonPropertiesDataManager personPropertiesDataManager; + private GroupsDataManager groupsDataManager; + private PeopleDataManager peopleDataManager; + private double susceptibleProbability; + private double childPopulationProportion; + private double seniorPopulationProportion; + private double averageHomeSize; + private double averageSchoolSize; + private double averageWorkSize; + + /* start code_ref= groups_plugin_population_loader_init_region_population|code_cap=The population for a region is initialized with each person being assigned an age, an immunity status and a region.*/ + private void initializeRegionPopulation(RegionId regionId, int populationSize) { + + double n = populationSize; + int homeCount = (int) (n / averageHomeSize) + 1; + int childCount = (int) (n * childPopulationProportion); + int adultCount = populationSize - childCount; + homeCount = FastMath.min(homeCount, adultCount); + int seniorCount = (int) (n * seniorPopulationProportion); + seniorCount = FastMath.min(seniorCount, adultCount); + int workingAdultCount = adultCount - seniorCount; + int workCount = (int) ((double) workingAdultCount / averageWorkSize) + 1; + int schoolCount = (int) ((double) childCount / averageSchoolSize) + 1; + + // create the population + for (int i = 0; i < populationSize; i++) { + int age; + if (i < seniorCount) { + age = randomGenerator.nextInt(25) + 65; + } else if (i < adultCount) { + age = randomGenerator.nextInt(18) + (65 - 18); + } else { + age = randomGenerator.nextInt(18); + } + PersonPropertyValueInitialization ageInitialization = new PersonPropertyValueInitialization( + PersonProperty.AGE, age); + + DiseaseState diseaseState = DiseaseState.IMMUNE; + if (randomGenerator.nextDouble() < susceptibleProbability) { + diseaseState = DiseaseState.SUSCEPTIBLE; + } + + PersonPropertyValueInitialization diseaseInitialization = new PersonPropertyValueInitialization( + PersonProperty.DISEASE_STATE, diseaseState); + PersonConstructionData personConstructionData = PersonConstructionData.builder()// + .add(ageInitialization)// + .add(diseaseInitialization)// + .add(regionId)// + .build(); + peopleDataManager.addPerson(personConstructionData); + } + /* end */ + /* start code_ref= groups_plugin_population_loader_adding_groups|code_cap=The home, work and school groups are added to the groups data manager. */ + // create the home groups + List homeGroupIds = new ArrayList<>(); + for (int i = 0; i < homeCount; i++) { + GroupConstructionInfo groupConstructionInfo = GroupConstructionInfo.builder().setGroupTypeId(GroupType.HOME) + .build(); + GroupId groupId = groupsDataManager.addGroup(groupConstructionInfo); + homeGroupIds.add(groupId); + } + + // create the work groups + List workGroupIds = new ArrayList<>(); + for (int i = 0; i < workCount; i++) { + GroupConstructionInfo groupConstructionInfo = GroupConstructionInfo.builder().setGroupTypeId(GroupType.WORK) + .build(); + GroupId groupId = groupsDataManager.addGroup(groupConstructionInfo); + workGroupIds.add(groupId); + } + + // create the school groups + List schoolGroupIds = new ArrayList<>(); + for (int i = 0; i < schoolCount; i++) { + GroupConstructionInfo groupConstructionInfo = GroupConstructionInfo.builder() + .setGroupTypeId(GroupType.SCHOOL).build(); + GroupId groupId = groupsDataManager.addGroup(groupConstructionInfo); + schoolGroupIds.add(groupId); + } + /* end */ + + // determine the subsets of people by age + /* start code_ref= groups_plugin_population_loader_age_subsets|code_cap=The people are separated into age related lists. */ + List peopleInRegion = regionsDataManager.getPeopleInRegion(regionId); + List adults = new ArrayList<>(); + List children = new ArrayList<>(); + List workingAdults = new ArrayList<>(); + for (PersonId personId : peopleInRegion) { + int age = personPropertiesDataManager.getPersonPropertyValue(personId, PersonProperty.AGE); + if (age < 18) { + children.add(personId); + } else { + adults.add(personId); + if (age < 65) { + workingAdults.add(personId); + } + } + } + /* end */ + /* start code_ref= groups_plugin_population_loader_group_assignments|code_cap=People are assigned to homes, work places and schools. */ + Random random = new Random(randomGenerator.nextLong()); + /* + * Randomize the adults and assign them to the home groups such that there is at + * least one adult in each home + */ + Collections.shuffle(adults, random); + // put one adult in each home + for (int i = 0; i < homeGroupIds.size(); i++) { + PersonId personId = adults.get(i); + GroupId groupId = homeGroupIds.get(i); + groupsDataManager.addPersonToGroup(personId, groupId); + } + + // assign the remaining adults at random to homes + for (int i = homeGroupIds.size(); i < adults.size(); i++) { + PersonId personId = adults.get(i); + GroupId groupId = homeGroupIds.get(randomGenerator.nextInt(homeGroupIds.size())); + groupsDataManager.addPersonToGroup(personId, groupId); + } + + // assign working age adults to work groups + for (int i = 0; i < workingAdults.size(); i++) { + PersonId personId = workingAdults.get(i); + GroupId groupId = workGroupIds.get(randomGenerator.nextInt(workGroupIds.size())); + groupsDataManager.addPersonToGroup(personId, groupId); + } + + // assign children to school groups + for (int i = 0; i < children.size(); i++) { + PersonId personId = children.get(i); + GroupId groupId = schoolGroupIds.get(randomGenerator.nextInt(schoolGroupIds.size())); + groupsDataManager.addPersonToGroup(personId, groupId); + } + + // assign children to home groups + for (int i = 0; i < children.size(); i++) { + PersonId personId = children.get(i); + GroupId groupId = homeGroupIds.get(randomGenerator.nextInt(homeGroupIds.size())); + groupsDataManager.addPersonToGroup(personId, groupId); + } + } + + /* end */ + /* start code_ref= groups_plugin_population_loader_init|code_cap=The population loader initializes by establishing various constants from the global properties and establishing the population of each region. */ + public void init(ActorContext actorContext) { + personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class); + groupsDataManager = actorContext.getDataManager(GroupsDataManager.class); + StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class); + randomGenerator = stochasticsDataManager.getRandomGenerator(); + peopleDataManager = actorContext.getDataManager(PeopleDataManager.class); + GlobalPropertiesDataManager globalPropertiesDataManager = actorContext + .getDataManager(GlobalPropertiesDataManager.class); + regionsDataManager = actorContext.getDataManager(RegionsDataManager.class); + + int populationSize = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.POPULATION_SIZE); + susceptibleProbability = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.SUSCEPTIBLE_POPULATION_PROPORTION); + childPopulationProportion = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.CHILD_POPULATION_PROPORTION); + seniorPopulationProportion = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.SENIOR_POPULATION_PROPORTION); + averageHomeSize = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.AVERAGE_HOME_SIZE); + averageSchoolSize = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.AVERAGE_SCHOOL_SIZE); + averageWorkSize = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.AVERAGE_WORK_SIZE); + + Set regionIds = regionsDataManager.getRegionIds(); + int regionSize = populationSize / regionIds.size(); + int leftoverPeople = populationSize % regionIds.size(); + + for (RegionId regionId : regionIds) { + int regionPopulation = regionSize; + if (leftoverPeople > 0) { + leftoverPeople--; + regionPopulation++; + } + initializeRegionPopulation(regionId, regionPopulation); + } + } + /* end */ +} diff --git a/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/SchoolManager.java b/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/SchoolManager.java new file mode 100644 index 000000000..89ee1e474 --- /dev/null +++ b/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/SchoolManager.java @@ -0,0 +1,133 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors; + +import java.util.List; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.DiseaseState; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.GlobalProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.GroupProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.GroupType; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.PersonProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.SchoolStatus; +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.Plan; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.groups.datamanagers.GroupsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupConstructionInfo; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; + +public class SchoolManager { + private ActorContext actorContext; + private GroupsDataManager groupsDataManager; + private PersonPropertiesDataManager personPropertiesDataManager; + private double cohortThreshold; + private double closureThreshold; + private final double reviewInterval = 7; + + /* start code_ref= groups_plugin_school_manager_init|code_cap=The school manager initializes by establishing some property constants and planning school status review for seven days after the simulation starts.*/ + public void init(ActorContext actorContext) { + this.actorContext = actorContext; + personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class); + groupsDataManager = actorContext.getDataManager(GroupsDataManager.class); + GlobalPropertiesDataManager globalPropertiesDataManager = actorContext + .getDataManager(GlobalPropertiesDataManager.class); + cohortThreshold = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.SCHOOL_COHORT_INFECTION_THRESHOLD); + closureThreshold = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.SCHOOL_CLOSURE_INFECTION_THRESHOLD); + planNextReview(); + } + + private void planNextReview() { + double planTime = actorContext.getTime() + reviewInterval; + Plan plan = Plan.builder(ActorContext.class)// + .setCallbackConsumer(this::reviewSchools)// + .setActive(false)// + .setTime(planTime)// + .build(); + actorContext.addPlan(plan); + } + + private void reviewSchools(ActorContext actorContext) { + List schoolGroupIds = groupsDataManager.getGroupsForGroupType(GroupType.SCHOOL); + for (GroupId groupId : schoolGroupIds) { + reviewSchool(groupId); + } + planNextReview(); + } + + /* end */ + + /* start code_ref= groups_plugin_school_manager_review_school|code_cap=Each school is reviewed on a weekly basis. As the fraction of students who are infected increases, the school transitions from OPEN to COHORT to CLOSED.*/ + private void reviewSchool(GroupId groupId) { + + int infectiousCount = 0; + List peopleForGroup = groupsDataManager.getPeopleForGroup(groupId); + for (PersonId personId : peopleForGroup) { + DiseaseState diseaseState = personPropertiesDataManager.getPersonPropertyValue(personId, + PersonProperty.DISEASE_STATE); + if (diseaseState == DiseaseState.INFECTIOUS) { + infectiousCount++; + } + } + + double infectiousFraction = infectiousCount; + if (!peopleForGroup.isEmpty()) { + infectiousFraction /= peopleForGroup.size(); + } + + SchoolStatus schoolStatus = groupsDataManager.getGroupPropertyValue(groupId, GroupProperty.SCHOOL_STATUS); + + switch (schoolStatus) { + case OPEN: + if (infectiousFraction >= cohortThreshold) { + splitSchoolIntoCohorts(groupId); + } + break; + case COHORT: + if (infectiousFraction >= closureThreshold) { + closeSchool(groupId); + } + break; + case CLOSED: + // do nothing + break; + default: + throw new RuntimeException("unhandled case " + schoolStatus); + } + } + + /* end */ + + /* start code_ref= groups_plugin_school_manager_close_schools|code_cap=When a school is closed, all the students are removed from the school group so that infection can no longer spread via school-based contact. */ + private void closeSchool(GroupId groupId) { + groupsDataManager.setGroupPropertyValue(groupId, GroupProperty.SCHOOL_STATUS, SchoolStatus.CLOSED); + List people = groupsDataManager.getPeopleForGroup(groupId); + for (PersonId personId : people) { + groupsDataManager.removePersonFromGroup(personId, groupId); + } + } + /* end */ + + /* start code_ref= groups_plugin_school_manager_split_schools|code_cap= When a school moves to COHORT status, a new group is added to the simulation and half of the students move to this new group.*/ + private void splitSchoolIntoCohorts(GroupId groupId) { + GroupConstructionInfo groupConstructionInfo = GroupConstructionInfo.builder().setGroupTypeId(GroupType.SCHOOL) + .build(); + GroupId newGroupId = groupsDataManager.addGroup(groupConstructionInfo); + + List peopleForGroup = groupsDataManager.getPeopleForGroup(groupId); + for (int i = 0; i < peopleForGroup.size(); i++) { + if (i % 2 == 0) { + PersonId personId = peopleForGroup.get(i); + groupsDataManager.removePersonFromGroup(personId, groupId); + groupsDataManager.addPersonToGroup(personId, newGroupId); + } + } + + groupsDataManager.setGroupPropertyValue(newGroupId, GroupProperty.SCHOOL_STATUS, SchoolStatus.COHORT); + groupsDataManager.setGroupPropertyValue(groupId, GroupProperty.SCHOOL_STATUS, SchoolStatus.COHORT); + + } + /* end */ +} diff --git a/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/TeleworkManager.java b/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/TeleworkManager.java new file mode 100644 index 000000000..55ed072a2 --- /dev/null +++ b/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/TeleworkManager.java @@ -0,0 +1,78 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors; + +import java.util.List; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.DiseaseState; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.GlobalProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.GroupProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.GroupType; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.PersonProperty; +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.Plan; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.groups.datamanagers.GroupsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupId; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; + +public class TeleworkManager { + + private final double reviewInterval = 7; + private ActorContext actorContext; + + /* start code_ref= groups_plugin_telework_manager_init|code_cap= The telework manager initializes by scheduling a telework status review for seven days from the start of the simulation.*/ + public void init(ActorContext actorContext) { + this.actorContext = actorContext; + scheduleNextReview(); + } + + private void scheduleNextReview() { + double planTime = actorContext.getTime() + reviewInterval; + Plan plan = Plan.builder(ActorContext.class)// + .setCallbackConsumer(this::reviewTeleworkStatus)// + .setActive(false)// + .setTime(planTime)// + .build(); + + actorContext.addPlan(plan); + } + + /* end */ + /* start code_ref= groups_plugin_telework_review_status|code_cap= The telework manager schedules a review every seven days until a threshold of infections is reached. Once the threshold is achieved, work places are randomly selected to use telework until the end of the simulation.*/ + private void reviewTeleworkStatus(ActorContext actorContext) { + StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class); + RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator(); + PeopleDataManager peopleDataManager = actorContext.getDataManager(PeopleDataManager.class); + PersonPropertiesDataManager personPropertiesDataManager = actorContext + .getDataManager(PersonPropertiesDataManager.class); + GroupsDataManager groupsDataManager = actorContext.getDataManager(GroupsDataManager.class); + GlobalPropertiesDataManager globalPropertiesDataManager = actorContext + .getDataManager(GlobalPropertiesDataManager.class); + double threshold = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.TELEWORK_INFECTION_THRESHOLD); + double teleworkProbability = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.TELEWORK_PROBABILTY); + + int infectiousCount = personPropertiesDataManager.getPersonCountForPropertyValue(PersonProperty.DISEASE_STATE, + DiseaseState.INFECTIOUS); + int populationCount = peopleDataManager.getPopulationCount(); + + double infectiousFraction = infectiousCount; + infectiousFraction /= populationCount; + + if (infectiousFraction >= threshold) { + List workGroupIds = groupsDataManager.getGroupsForGroupType(GroupType.WORK); + for (GroupId groupId : workGroupIds) { + if (randomGenerator.nextDouble() < teleworkProbability) { + groupsDataManager.setGroupPropertyValue(groupId, GroupProperty.TELEWORK, true); + } + } + } else { + scheduleNextReview(); + } + } + /* end */ +} diff --git a/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/reports/ContagionReport.java b/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/reports/ContagionReport.java new file mode 100644 index 000000000..ed9033fb7 --- /dev/null +++ b/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/reports/ContagionReport.java @@ -0,0 +1,71 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.reports; + +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import org.apache.commons.math3.util.FastMath; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.DiseaseState; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.PersonProperty; +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import util.wrappers.MutableInteger; + +public final class ContagionReport { + private final ReportHeader reportHeader = ReportHeader.builder().add("infected").add("count").build(); + private final ReportLabel reportLabel; + + public ContagionReport(ReportLabel reportLabel) { + this.reportLabel = reportLabel; + } + + public void init(ReportContext reportContext) { + reportContext.subscribeToSimulationClose(this::report); + } + + private void report(ReportContext reportContext) { + PersonPropertiesDataManager personPropertiesDataManager = reportContext + .getDataManager(PersonPropertiesDataManager.class); + List people = personPropertiesDataManager.getPeopleWithPropertyValue(PersonProperty.DISEASE_STATE, + DiseaseState.RECOVERED); + + Map countMap = new TreeMap<>(); + + int maxInfectedCount = 0; + for (PersonId personId : people) { + int infectedCount = personPropertiesDataManager.getPersonPropertyValue(personId, + PersonProperty.INFECTED_COUNT); + maxInfectedCount = FastMath.max(maxInfectedCount, infectedCount); + MutableInteger mutableInteger = countMap.get(infectedCount); + if (mutableInteger == null) { + mutableInteger = new MutableInteger(); + countMap.put(infectedCount, mutableInteger); + } + mutableInteger.increment(); + } + + for (int i = 0; i < maxInfectedCount; i++) { + if (!countMap.containsKey(i)) { + countMap.put(i, new MutableInteger()); + } + } + + for (Integer i : countMap.keySet()) { + ReportItem.Builder reportItemBuilder = ReportItem.builder(); + MutableInteger mutableInteger = countMap.get(i); + reportItemBuilder.setReportLabel(reportLabel); + reportItemBuilder.setReportHeader(reportHeader); + reportItemBuilder.addValue(i); + reportItemBuilder.addValue(mutableInteger.getValue()); + ReportItem reportItem = reportItemBuilder.build(); + reportContext.releaseOutput(reportItem); + } + + } + +} diff --git a/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/reports/DiseaseStateReport.java b/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/reports/DiseaseStateReport.java new file mode 100644 index 000000000..38751327b --- /dev/null +++ b/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/reports/DiseaseStateReport.java @@ -0,0 +1,59 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.reports; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.DiseaseState; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.PersonProperty; +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.PeriodicReport; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; + +/** + * A report that groups people at the end of the simulation by their shared + * person property values. + * + * + */ +public final class DiseaseStateReport extends PeriodicReport { + + public DiseaseStateReport(ReportLabel reportLabel, ReportPeriod reportPeriod) { + super(reportLabel, reportPeriod); + } + + private ReportHeader reportHeader; + + private ReportHeader getReportHeader() { + if (reportHeader == null) { + ReportHeader.Builder reportHeaderBuilder = ReportHeader.builder(); + addTimeFieldHeaders(reportHeaderBuilder);// + for (DiseaseState diseaseState : DiseaseState.values()) { + reportHeaderBuilder.add(diseaseState.toString().toLowerCase()); + } + reportHeader = reportHeaderBuilder.build(); + } + return reportHeader; + } + + @Override + protected void flush(ReportContext reportContext) { + ReportItem.Builder reportItemBuilder = ReportItem.builder(); + reportItemBuilder.setReportLabel(getReportLabel()); + reportItemBuilder.setReportHeader(getReportHeader()); + fillTimeFields(reportItemBuilder); + + PersonPropertiesDataManager personPropertiesDataManager = reportContext + .getDataManager(PersonPropertiesDataManager.class); + for (DiseaseState diseaseState : DiseaseState.values()) { + int count = personPropertiesDataManager.getPersonCountForPropertyValue(PersonProperty.DISEASE_STATE, + diseaseState); + reportItemBuilder.addValue(count); + } + + ReportItem reportItem = reportItemBuilder.build(); + reportContext.releaseOutput(reportItem); + + } + +} diff --git a/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/DiseaseState.java b/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/DiseaseState.java new file mode 100644 index 000000000..8dee0da9a --- /dev/null +++ b/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/DiseaseState.java @@ -0,0 +1,8 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.support; + +public enum DiseaseState { + IMMUNE, // the person is immune + SUSCEPTIBLE, INFECTIOUS, // the person is infected + RECOVERED, + +} diff --git a/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/GlobalProperty.java b/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/GlobalProperty.java new file mode 100644 index 000000000..0f149b356 --- /dev/null +++ b/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/GlobalProperty.java @@ -0,0 +1,31 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.support; + +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertyId; + +public enum GlobalProperty implements GlobalPropertyId { + SUSCEPTIBLE_POPULATION_PROPORTION, // the fraction of the population that is + // susceptible + INITIAL_INFECTIONS, // the number of adults initially infected + MIN_INFECTIOUS_PERIOD, // the minimum number of days a person is infectious + MAX_INFECTIOUS_PERIOD, // the maximum number of days a person is infectious + POPULATION_SIZE, // the initial size of the population + CHILD_POPULATION_PROPORTION, // the fraction of the population between the + // ages of 0 and 18, inclusive + SENIOR_POPULATION_PROPORTION, // the fraction of the population 65 and older + R0, // the expected number of people a single person will infect if all + // contacts are susceptible and transmission success is 100% + AVERAGE_HOME_SIZE, // the average number of people per household + AVERAGE_SCHOOL_SIZE, // the average number of student in a school + AVERAGE_WORK_SIZE, // the average number of people per work place + TELEWORK_INFECTION_THRESHOLD, // the total infection density that triggers + // some work places to institute telework + // mode + TELEWORK_PROBABILTY, // the probability that a work place will convert to + // telework mode once telework is allowed + SCHOOL_COHORT_INFECTION_THRESHOLD, // the infection density within a school + // that triggers the school moving to a + // split cohort + SCHOOL_CLOSURE_INFECTION_THRESHOLD,// the infection density within a school + // cohort that triggers the closure of + // that cohort. +} diff --git a/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/GroupProperty.java b/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/GroupProperty.java new file mode 100644 index 000000000..77d03db72 --- /dev/null +++ b/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/GroupProperty.java @@ -0,0 +1,7 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.support; + +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupPropertyId; + +public enum GroupProperty implements GroupPropertyId { + TELEWORK, SCHOOL_STATUS,; +} diff --git a/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/GroupType.java b/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/GroupType.java new file mode 100644 index 000000000..0f474af70 --- /dev/null +++ b/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/GroupType.java @@ -0,0 +1,7 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.support; + +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupTypeId; + +public enum GroupType implements GroupTypeId { + HOME, SCHOOL, WORK; +} diff --git a/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/ModelError.java b/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/ModelError.java new file mode 100644 index 000000000..661adff3d --- /dev/null +++ b/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/ModelError.java @@ -0,0 +1,26 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.support; + +import util.errors.ContractError; +import util.errors.ContractException; + +/** + * An enumeration supporting {@link ContractException} that acts as a general + * description of the exception. + * + * + */ +public enum ModelError implements ContractError { + + NEGATIVE_REGION_ID("Negative region id"),; + + private final String description; + + private ModelError(final String description) { + this.description = description; + } + + @Override + public String getDescription() { + return description; + } +} diff --git a/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/PersonProperty.java b/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/PersonProperty.java new file mode 100644 index 000000000..751e16ee4 --- /dev/null +++ b/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/PersonProperty.java @@ -0,0 +1,9 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.support; + +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyId; + +public enum PersonProperty implements PersonPropertyId { + AGE, DISEASE_STATE, INFECTED_COUNT + + ; +} diff --git a/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/Region.java b/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/Region.java new file mode 100644 index 000000000..226996149 --- /dev/null +++ b/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/Region.java @@ -0,0 +1,59 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.support; + +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * Identifier for all regions + * + * + */ + +@Immutable +public final class Region implements RegionId { + + private final int id; + + /** + * Constructs the region + * + * @throws ContractException + *
          • {@linkplain ModelError#NEGATIVE_REGION_ID}
          • + */ + public Region(int id) { + if (id < 0) { + throw new ContractException(ModelError.NEGATIVE_REGION_ID); + } + this.id = id; + } + + public int getValue() { + return id; + } + + @Override + public int hashCode() { + return id; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Region)) { + return false; + } + Region other = (Region) obj; + if (id != other.id) { + return false; + } + return true; + } + + @Override + public String toString() { + return "Region_" + id; + } +} diff --git a/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/SchoolStatus.java b/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/SchoolStatus.java new file mode 100644 index 000000000..c7d8b1cb6 --- /dev/null +++ b/tutorials/lessons/lesson_17_groups_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/SchoolStatus.java @@ -0,0 +1,5 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.support; + +public enum SchoolStatus { + OPEN, COHORT, CLOSED; +} diff --git a/tutorials/lessons/lesson_18_resources_plugin/pom.xml b/tutorials/lessons/lesson_18_resources_plugin/pom.xml new file mode 100644 index 000000000..9fbd4bf7f --- /dev/null +++ b/tutorials/lessons/lesson_18_resources_plugin/pom.xml @@ -0,0 +1,79 @@ + + 4.0.0 + + + ASPR + https://www.phe.gov + + + + gov.hhs.aspr.ms.gcm.tutorials + lesson_18_resources_plugin + ${revision} + jar + gcm lesson 18 resources plugin + Tutorial introducing the resources plugin + + + + UTF-8 + 17 + 17 + ${gcm.version} + + 1.3.0 + + 4.0.0-SNAPSHOT + + + + + + gov.hhs.aspr.ms + gcm + ${gcm.version} + + + + + + Shawn Hatch + Leidos + https://www.leidos.com + + + + + + + org.codehaus.mojo + flatten-maven-plugin + ${flatten-maven-plugin.version} + + + + flatten + process-resources + + flatten + + + true + + + + + flatten.clean + clean + + clean + + + + + + + + diff --git a/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_18.java b/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_18.java new file mode 100644 index 000000000..1b8f6e8ed --- /dev/null +++ b/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_18.java @@ -0,0 +1,325 @@ +package gov.hhs.aspr.ms.gcm.lessons; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.stream.IntStream; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.GlobalProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.ModelPlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.ModelReportLabel; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.PersonProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.Region; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.Resource; +import gov.hhs.aspr.ms.gcm.nucleus.Dimension; +import gov.hhs.aspr.ms.gcm.nucleus.Experiment; +import gov.hhs.aspr.ms.gcm.nucleus.ExperimentParameterData; +import gov.hhs.aspr.ms.gcm.nucleus.FunctionalDimension; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.GlobalPropertiesPlugin; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePlugin; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeoplePluginData; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.PersonPropertiesPlugin; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.regions.RegionsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.NIOReportItemHandler; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import gov.hhs.aspr.ms.gcm.plugins.resources.ResourcesPlugin; +import gov.hhs.aspr.ms.gcm.plugins.resources.datamanagers.ResourcesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.resources.reports.PersonResourceReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import util.random.RandomGeneratorProvider; + +public final class Example_18 { + + private final Path outputDirectory; + + private Example_18(Path outputDirectory) { + this.outputDirectory = outputDirectory; + } + + private RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(9032703880551658180L); + + /* start code_ref=resources_getResourcesPlugin|code_cap=The resource plugin is initialized with defining the two resource ids at time zero with time tracking turned on. The person resource report is set to report at the end of the simulation.*/ + private Plugin getResourcesPlugin() { + ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); + for (ResourceId resourceId : Resource.values()) { + builder.addResource(resourceId, 0.0, true); + } + ResourcesPluginData resourcesPluginData = builder.build(); + + PersonResourceReportPluginData personResourceReportPluginData = PersonResourceReportPluginData// + .builder()// + .setReportLabel(ModelReportLabel.PERSON_RESOURCE_REPORT)// + .setReportPeriod(ReportPeriod.END_OF_SIMULATION)// + .build(); + + return ResourcesPlugin.builder()// + .setResourcesPluginData(resourcesPluginData)// + .setPersonResourceReportPluginData(personResourceReportPluginData)// + .getResourcesPlugin();// + } + /* end */ + + private NIOReportItemHandler getNIOReportItemHandler() { + return NIOReportItemHandler.builder()// + .addReport(ModelReportLabel.PERSON_RESOURCE_REPORT, + outputDirectory.resolve("person_resource_report.xls"))// + .addReport(ModelReportLabel.TREATMENT_REPORT, outputDirectory.resolve("treatment_report.xls"))// + .addReport(ModelReportLabel.DEATH_REPORT, outputDirectory.resolve("death_report.xls"))// + .addReport(ModelReportLabel.QUESTIONNAIRE_REPORT, outputDirectory.resolve("questionnaire_report.xls"))// + .build(); + } + + private Plugin getPeoplePlugin() { + PeoplePluginData peoplePluginData = PeoplePluginData.builder().build(); + return PeoplePlugin.getPeoplePlugin(peoplePluginData); + } + + private Plugin getRegionsPlugin() { + RegionsPluginData.Builder regionsPluginDataBuilder = RegionsPluginData.builder(); + + for (int i = 0; i < 5; i++) { + regionsPluginDataBuilder.addRegion(new Region(i)); + } + RegionsPluginData regionsPluginData = regionsPluginDataBuilder.build(); + return RegionsPlugin.builder().setRegionsPluginData(regionsPluginData).getRegionsPlugin(); + } + + private Plugin getStochasticsPlugin() { + WellState wellState = WellState.builder().setSeed(randomGenerator.nextLong()).build(); + StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder()// + + .setMainRNGState(wellState)// + .build(); + + return StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData); + } + + /* start code_ref=resources_getPersonPropertiesPlugin|code_cap=The person properties plugin is initialized with several properties.*/ + private Plugin getPersonPropertiesPlugin() { + + PersonPropertiesPluginData.Builder builder = PersonPropertiesPluginData.builder(); + + PropertyDefinition propertyDefinition = PropertyDefinition.builder()// + .setType(Boolean.class)// + .setDefaultValue(false)// + .build(); + + builder.definePersonProperty(PersonProperty.IMMUNE, propertyDefinition, 0, false);// + builder.definePersonProperty(PersonProperty.INFECTED, propertyDefinition, 0, false);// + builder.definePersonProperty(PersonProperty.HOSPITALIZED, propertyDefinition, 0, false);// + builder.definePersonProperty(PersonProperty.TREATED_WITH_ANTIVIRAL, propertyDefinition, 0, false);// + builder.definePersonProperty(PersonProperty.DEAD_IN_HOME, propertyDefinition, 0, false);// + builder.definePersonProperty(PersonProperty.DEAD_IN_HOSPITAL, propertyDefinition, 0, false);// + + propertyDefinition = PropertyDefinition.builder()// + .setType(Boolean.class)// + .setDefaultValue(false)// + .build(); + builder.definePersonProperty(PersonProperty.RECEIVED_QUESTIONNAIRE, propertyDefinition, 0, true);// + + PersonPropertiesPluginData personPropertiesPluginData = builder.build(); + + return PersonPropertiesPlugin.builder().setPersonPropertiesPluginData(personPropertiesPluginData) + .getPersonPropertyPlugin(); + } + /* end */ + + /* start code_ref=resources_getGlobalPropertiesPlugin|code_cap=The global properties plugin is initialized with several properties.*/ + private Plugin getGlobalPropertiesPlugin() { + GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder();// + + PropertyDefinition propertyDefinition = PropertyDefinition.builder()// + .setType(Double.class)// + .setDefaultValue(0.0)// + .setPropertyValueMutability(false)// + .build(); + + builder.defineGlobalProperty(GlobalProperty.SUSCEPTIBLE_POPULATION_PROPORTION, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.MAXIMUM_SYMPTOM_ONSET_TIME, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.ANTIVIRAL_COVERAGE_TIME, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.ANTIVIRAL_SUCCESS_RATE, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.HOSPITAL_SUCCESS_WITH_ANTIVIRAL, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.HOSPITAL_SUCCESS_WITHOUT_ANTIVIRAL, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.HOSPITAL_BEDS_PER_PERSON, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.ANTIVIRAL_DOSES_PER_PERSON, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.HOSPITAL_STAY_DURATION_MIN, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.HOSPITAL_STAY_DURATION_MAX, propertyDefinition, 0); + + propertyDefinition = PropertyDefinition.builder()// + .setType(Integer.class)// + .setDefaultValue(10000)// + .setPropertyValueMutability(false)// + .build(); + + builder.defineGlobalProperty(GlobalProperty.POPULATION_SIZE, propertyDefinition, 0); + + GlobalPropertiesPluginData globalPropertiesPluginData = builder.build(); + + return GlobalPropertiesPlugin.builder().setGlobalPropertiesPluginData(globalPropertiesPluginData) + .getGlobalPropertiesPlugin(); + } + /* end */ + + private Dimension getGlobalPropertyDimension(GlobalPropertyId globalPropertyId, String header, double[] values) { + FunctionalDimension.Builder dimensionBuilder = FunctionalDimension.builder();// + IntStream.range(0, values.length).forEach((i) -> { + dimensionBuilder.addLevel((context) -> { + GlobalPropertiesPluginData.Builder builder = context + .getPluginDataBuilder(GlobalPropertiesPluginData.Builder.class); + double value = values[i]; + builder.setGlobalPropertyValue(globalPropertyId, value, 0); + ArrayList result = new ArrayList<>(); + result.add(Double.toString(value)); + return result; + });// + }); + dimensionBuilder.addMetaDatum(header);// + return dimensionBuilder.build(); + } + + private Dimension getHospitalStayDurationDimension() { + double[] minValues = { 2.0, 5.0 }; + double[] maxValues = { 5.0, 10.0 }; + + FunctionalDimension.Builder dimensionBuilder = FunctionalDimension.builder();// + IntStream.range(0, minValues.length).forEach((i) -> { + dimensionBuilder.addLevel((context) -> { + GlobalPropertiesPluginData.Builder builder = context + .getPluginDataBuilder(GlobalPropertiesPluginData.Builder.class); + double minValue = minValues[i]; + builder.setGlobalPropertyValue(GlobalProperty.HOSPITAL_STAY_DURATION_MIN, minValue, 0); + double maxValue = maxValues[i]; + builder.setGlobalPropertyValue(GlobalProperty.HOSPITAL_STAY_DURATION_MAX, maxValue, 0); + ArrayList result = new ArrayList<>(); + result.add(Double.toString(minValue)); + result.add(Double.toString(maxValue)); + return result; + });// + }); + dimensionBuilder.addMetaDatum("hospital_stay_duration_min");// + dimensionBuilder.addMetaDatum("hospital_stay_duration_max");// + return dimensionBuilder.build(); + } + + private Dimension getAntiviralDosesPerPersonDimension() { + double[] values = new double[] { .10, 0.20, 0.5 }; + return getGlobalPropertyDimension(GlobalProperty.ANTIVIRAL_DOSES_PER_PERSON, "antiviral_doses_per_person", + values); + } + + private Dimension getHospitalBedsPerPersonDimension() { + double[] values = new double[] { 0.001, 0.003, 0.005 }; + return getGlobalPropertyDimension(GlobalProperty.HOSPITAL_BEDS_PER_PERSON, "hospital_beds_per_person", values); + } + + private Dimension getHospitalSuccessDimension() { + double[] minValues = { 0.30, 5.0 }; + double[] maxValues = { 0.50, 0.75 }; + + FunctionalDimension.Builder dimensionBuilder = FunctionalDimension.builder();// + IntStream.range(0, minValues.length).forEach((i) -> { + dimensionBuilder.addLevel((context) -> { + GlobalPropertiesPluginData.Builder builder = context + .getPluginDataBuilder(GlobalPropertiesPluginData.Builder.class); + double minValue = minValues[i]; + builder.setGlobalPropertyValue(GlobalProperty.HOSPITAL_SUCCESS_WITHOUT_ANTIVIRAL, minValue, 0); + double maxValue = maxValues[i]; + builder.setGlobalPropertyValue(GlobalProperty.HOSPITAL_SUCCESS_WITH_ANTIVIRAL, maxValue, 0); + ArrayList result = new ArrayList<>(); + result.add(Double.toString(minValue)); + result.add(Double.toString(maxValue)); + return result; + });// + }); + dimensionBuilder.addMetaDatum("hospital_success_without_antiviral");// + dimensionBuilder.addMetaDatum("hospital_success_with_antiviral");// + return dimensionBuilder.build(); + } + + private Dimension getAntiviralSuccessRateDimension() { + double[] values = new double[] { .50, 0.8 }; + return getGlobalPropertyDimension(GlobalProperty.ANTIVIRAL_SUCCESS_RATE, "antiviral_success_rate", values); + } + + private Dimension getAntiviralCoverageTimeDimension() { + double[] values = new double[] { 10.0, 15.0 }; + return getGlobalPropertyDimension(GlobalProperty.ANTIVIRAL_COVERAGE_TIME, "antiviral_coverage_time", values); + } + + private Dimension getMaximumSymptomOnsetTimeDimension() { + double[] values = new double[] { 60, 120 }; + return getGlobalPropertyDimension(GlobalProperty.MAXIMUM_SYMPTOM_ONSET_TIME, "maximum_symptom_onset_time", + values); + } + + private Dimension getSusceptiblePopulationProportionDimension() { + double[] values = new double[] { 0.25, 0.5, 0.75 }; + return getGlobalPropertyDimension(GlobalProperty.SUSCEPTIBLE_POPULATION_PROPORTION, + "susceptible_population_proportion", values); + } + + /* start code_ref=resources_execute|code_cap=The various plugins are gathered from their initial data, dimensions are added and the experiment is executed over 864 scenarios using 8 threads.*/ + private void execute() { + + ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()// + .setThreadCount(8)// + .build(); + + Experiment.builder() + + .addPlugin(getResourcesPlugin())// + .addPlugin(getGlobalPropertiesPlugin())// + .addPlugin(getPersonPropertiesPlugin())// + .addPlugin(getRegionsPlugin())// + .addPlugin(getPeoplePlugin())// + .addPlugin(getStochasticsPlugin())// + .addPlugin(ModelPlugin.getModelPlugin())// + + .addDimension(getMaximumSymptomOnsetTimeDimension())// + .addDimension(getSusceptiblePopulationProportionDimension())// + .addDimension(getAntiviralCoverageTimeDimension())// + .addDimension(getAntiviralSuccessRateDimension())// + .addDimension(getHospitalSuccessDimension())// + .addDimension(getHospitalBedsPerPersonDimension())// + .addDimension(getAntiviralDosesPerPersonDimension())// + .addDimension(getHospitalStayDurationDimension())// + + .addExperimentContextConsumer(getNIOReportItemHandler())// + .setExperimentParameterData(experimentParameterData)// + .build()// + .execute();// + } + /* end */ + + /* start code_ref=resources_main|code_cap=Executing example 18 with an output directory.*/ + public static void main(String[] args) throws IOException { + if (args.length == 0) { + throw new RuntimeException("One output directory argument is required"); + } + Path outputDirectory = Paths.get(args[0]); + if (!Files.exists(outputDirectory)) { + Files.createDirectory(outputDirectory); + } else { + if (!Files.isDirectory(outputDirectory)) { + throw new IOException("Provided path is not a directory"); + } + } + + new Example_18(outputDirectory).execute(); + } + /* end */ + +} diff --git a/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/GlobalProperty.java b/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/GlobalProperty.java new file mode 100644 index 000000000..be7b15f45 --- /dev/null +++ b/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/GlobalProperty.java @@ -0,0 +1,21 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertyId; + +public enum GlobalProperty implements GlobalPropertyId { + SUSCEPTIBLE_POPULATION_PROPORTION, // the fraction of the population that is susceptible + MAXIMUM_SYMPTOM_ONSET_TIME, // the last time where any person will have onset of symptoms + ANTIVIRAL_COVERAGE_TIME, // the amount of time for the antiviral to be effective + ANTIVIRAL_SUCCESS_RATE, // the probability that the antiviral will be effective + HOSPITAL_SUCCESS_WITH_ANTIVIRAL, // the probability that hospital treatment will be effective for people who + // previously had antiviral treatment + HOSPITAL_SUCCESS_WITHOUT_ANTIVIRAL, // the probability that hospital treatment will be effective for people who + // previously had no antiviral treatment + HOSPITAL_STAY_DURATION_MIN, // The minimum duration of a hospital stay + HOSPITAL_STAY_DURATION_MAX, // The maximum duration of a hospital stay + POPULATION_SIZE, // the number of people across all regions. Regions will not be uniformly + // populated. + HOSPITAL_BEDS_PER_PERSON, // The number of hospital beds per person on average stored in the regions. + ANTIVIRAL_DOSES_PER_PERSON,// The number of antiviral doses per person on average stored in the regions. + ; +} diff --git a/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelError.java b/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelError.java new file mode 100644 index 000000000..3f316f445 --- /dev/null +++ b/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelError.java @@ -0,0 +1,26 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import util.errors.ContractError; +import util.errors.ContractException; + +/** + * An enumeration supporting {@link ContractException} that acts as a general + * description of the exception. + * + * + */ +public enum ModelError implements ContractError { + + NEGATIVE_REGION_ID("Negative region id"),; + + private final String description; + + private ModelError(final String description) { + this.description = description; + } + + @Override + public String getDescription() { + return description; + } +} diff --git a/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java b/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java new file mode 100644 index 000000000..bb7d53727 --- /dev/null +++ b/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java @@ -0,0 +1,31 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors.PopulationLoader; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors.QuestionnaireDistributor; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors.ResourceLoader; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors.TreatmentManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.reports.DeathReport; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.reports.QuestionnaireReport; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.reports.TreatmentReport; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; + +public final class ModelPlugin { + private ModelPlugin() { + + } + + public static Plugin getModelPlugin() { + return Plugin.builder()// + .setPluginId(ModelPluginId.PLUGIN_ID).setInitializer((c) -> { + c.addActor(new PopulationLoader()::init); + c.addActor(new ResourceLoader()::init); + c.addActor(new TreatmentManager()::init); + c.addActor(new QuestionnaireDistributor()::init); + + c.addReport(new TreatmentReport(ModelReportLabel.TREATMENT_REPORT)::init);// + c.addReport(new DeathReport(ModelReportLabel.DEATH_REPORT)::init);// + c.addReport(new QuestionnaireReport(ModelReportLabel.QUESTIONNAIRE_REPORT)::init);// + + }).build(); + } +} diff --git a/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java b/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java new file mode 100644 index 000000000..9c7193b57 --- /dev/null +++ b/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java @@ -0,0 +1,11 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.nucleus.SimplePluginId; + +public final class ModelPluginId { + private ModelPluginId() { + } + + public final static PluginId PLUGIN_ID = new SimplePluginId("model plugin"); +} diff --git a/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelReportLabel.java b/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelReportLabel.java new file mode 100644 index 000000000..186e20dc5 --- /dev/null +++ b/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelReportLabel.java @@ -0,0 +1,7 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; + +public enum ModelReportLabel implements ReportLabel { + PERSON_RESOURCE_REPORT, TREATMENT_REPORT, DEATH_REPORT, QUESTIONNAIRE_REPORT,; +} diff --git a/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/PersonProperty.java b/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/PersonProperty.java new file mode 100644 index 000000000..11bd89b3b --- /dev/null +++ b/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/PersonProperty.java @@ -0,0 +1,14 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyId; + +public enum PersonProperty implements PersonPropertyId { + IMMUNE, // the person is immune + INFECTED, // the person is infected + TREATED_WITH_ANTIVIRAL, // the person received antiviral treatment + HOSPITALIZED, // the person received hospital treatment + DEAD_IN_HOSPITAL, // the person dies in the hospital + DEAD_IN_HOME, // the person dies in the home + RECEIVED_QUESTIONNAIRE,// the person receives the questionnaire + ; +} diff --git a/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/Region.java b/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/Region.java new file mode 100644 index 000000000..b4b2a79c5 --- /dev/null +++ b/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/Region.java @@ -0,0 +1,59 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * Identifier for all regions + * + * + */ + +@Immutable +public final class Region implements RegionId { + + private final int id; + + /** + * Constructs the region + * + * @throws ContractException + *
          • {@linkplain ModelError#NEGATIVE_REGION_ID}
          • + */ + public Region(int id) { + if (id < 0) { + throw new ContractException(ModelError.NEGATIVE_REGION_ID); + } + this.id = id; + } + + public int getValue() { + return id; + } + + @Override + public int hashCode() { + return id; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Region)) { + return false; + } + Region other = (Region) obj; + if (id != other.id) { + return false; + } + return true; + } + + @Override + public String toString() { + return "Region_" + id; + } +} diff --git a/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/Resource.java b/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/Resource.java new file mode 100644 index 000000000..c59d857b9 --- /dev/null +++ b/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/Resource.java @@ -0,0 +1,9 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; + +/* start code_ref=resources_resource_enum|code_cap=Resource ids are implemented as an enumeration.*/ +public enum Resource implements ResourceId { + ANTI_VIRAL_MED, HOSPITAL_BED; +} +/* end */ diff --git a/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/PopulationLoader.java b/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/PopulationLoader.java new file mode 100644 index 000000000..dfb316dbf --- /dev/null +++ b/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/PopulationLoader.java @@ -0,0 +1,103 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.GlobalProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.PersonProperty; +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonConstructionData; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyValueInitialization; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import util.wrappers.MutableDouble; + +public class PopulationLoader { + private RegionId defaultRegionId; + private Map regionMap = new LinkedHashMap<>(); + private RandomGenerator randomGenerator; + private RegionsDataManager regionsDataManager; + + private void buildUnbalancedRegions() { + + for (RegionId regionId : regionsDataManager.getRegionIds()) { + double value = randomGenerator.nextDouble(); + value = value * 0.9 + .1; + regionMap.put(regionId, new MutableDouble(value)); + defaultRegionId = regionId; + } + double sum = 0; + for (RegionId regionId : regionMap.keySet()) { + sum += regionMap.get(regionId).getValue(); + } + for (RegionId regionId : regionMap.keySet()) { + MutableDouble mutableDouble = regionMap.get(regionId); + double value = mutableDouble.getValue(); + value /= sum; + mutableDouble.setValue(value); + } + sum = 0; + for (RegionId regionId : regionMap.keySet()) { + MutableDouble mutableDouble = regionMap.get(regionId); + double value = mutableDouble.getValue(); + sum += value; + mutableDouble.setValue(sum); + } + } + + private RegionId getRandomRegionId() { + double value = randomGenerator.nextDouble(); + for (RegionId regionId : regionMap.keySet()) { + MutableDouble mutableDouble = regionMap.get(regionId); + if (mutableDouble.getValue() >= value) { + return regionId; + } + } + return defaultRegionId; + } + + /* start code_ref=resources_population_loader_init|code_cap= The population loader initializes the population by assigning to each person a randomly selected region id and immunity status. */ + public void init(ActorContext actorContext) { + + StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class); + randomGenerator = stochasticsDataManager.getRandomGenerator(); + PeopleDataManager peopleDataManager = actorContext.getDataManager(PeopleDataManager.class); + GlobalPropertiesDataManager globalPropertiesDataManager = actorContext + .getDataManager(GlobalPropertiesDataManager.class); + regionsDataManager = actorContext.getDataManager(RegionsDataManager.class); + + int populationSize = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.POPULATION_SIZE); + double susceptibleProbability = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.SUSCEPTIBLE_POPULATION_PROPORTION); + double immuneProbabilty = 1 - susceptibleProbability; + + /* + * Derive mapping from region to probability that a person will be assigned to + * that region that will likely not put the same number of people in each + * region. + */ + buildUnbalancedRegions(); + + /* + * Add each person to the simulation. Determine their region id and the immune + * state. The other person properties will have default values. + */ + for (int i = 0; i < populationSize; i++) { + RegionId regionId = getRandomRegionId(); + boolean immune = randomGenerator.nextDouble() < immuneProbabilty; + PersonPropertyValueInitialization personPropertyInitialization = new PersonPropertyValueInitialization( + PersonProperty.IMMUNE, immune); + PersonConstructionData personConstructionData = PersonConstructionData.builder()// + .add(personPropertyInitialization)// + .add(regionId)// + .build(); + peopleDataManager.addPerson(personConstructionData); + } + } + /* end */ +} diff --git a/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/QuestionnaireDistributor.java b/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/QuestionnaireDistributor.java new file mode 100644 index 000000000..6ff79c5f2 --- /dev/null +++ b/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/QuestionnaireDistributor.java @@ -0,0 +1,71 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.PersonProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.Resource; +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.EventFilter; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.resources.datamanagers.ResourcesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.resources.events.PersonResourceUpdateEvent; + +public class QuestionnaireDistributor { + private PersonPropertiesDataManager personPropertiesDataManager; + + /* start code_ref=resources_QuestionnaireDistributor_init|code_cap=The questionnaire distributor initializes by subscribing to anti-viral and hospital bed person resource updates.*/ + public void init(ActorContext actorContext) { + ResourcesDataManager resourcesDataManager = actorContext.getDataManager(ResourcesDataManager.class); + personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class); + + EventFilter eventFilter = resourcesDataManager + .getEventFilterForPersonResourceUpdateEvent(Resource.ANTI_VIRAL_MED); + actorContext.subscribe(eventFilter, this::handleAntiViralDistribution); + + eventFilter = resourcesDataManager.getEventFilterForPersonResourceUpdateEvent(Resource.HOSPITAL_BED); + actorContext.subscribe(eventFilter, this::handleHospitalBedDistribution); + + } + /* end */ + + /* + * start code_ref=resources_QuestionnaireDistributor_handleAntiViralDistribution|code_cap=The questionnaire distributor distributes a questionnaire to each person who ends their anti-viral treatment and is not also hospitalized. + */ + private void handleAntiViralDistribution(ActorContext actorContext, + PersonResourceUpdateEvent personResourceUpdateEvent) { + PersonId personId = personResourceUpdateEvent.personId(); + boolean hasAntiviral = personResourceUpdateEvent.currentResourceLevel() > 0; + if (!hasAntiviral) { + boolean hospitalized = personPropertiesDataManager.getPersonPropertyValue(personId, + PersonProperty.HOSPITALIZED); + if (!hospitalized) { + distributeQuestionaire(personId); + } + } + } + /* end */ + + /* + * start code_ref=resources_QuestionnaireDistributor_handleAntiHospitalBedDistribution|code_cap=The questionnaire distributor distributes a questionnaire to each person that leaves the hospital. + */ + private void handleHospitalBedDistribution(ActorContext actorContext, + PersonResourceUpdateEvent personResourceUpdateEvent) { + PersonId personId = personResourceUpdateEvent.personId(); + boolean hasBed = personResourceUpdateEvent.currentResourceLevel() > 0; + boolean dead = personIsDead(personId); + if (!hasBed && !dead) { + distributeQuestionaire(personId); + } + } + + /* end */ + private boolean personIsDead(PersonId personId) { + boolean deadInHome = personPropertiesDataManager.getPersonPropertyValue(personId, PersonProperty.DEAD_IN_HOME); + boolean deadInHospital = personPropertiesDataManager.getPersonPropertyValue(personId, + PersonProperty.DEAD_IN_HOSPITAL); + return deadInHome || deadInHospital; + } + + private void distributeQuestionaire(PersonId personId) { + personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.RECEIVED_QUESTIONNAIRE, true); + } +} diff --git a/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/ResourceLoader.java b/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/ResourceLoader.java new file mode 100644 index 000000000..19c2089aa --- /dev/null +++ b/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/ResourceLoader.java @@ -0,0 +1,50 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors; + +import java.util.Set; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.GlobalProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.Resource; +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.resources.datamanagers.ResourcesDataManager; + +public class ResourceLoader { + + /* + * Allocate antiviral doses and hospital beds uniformly to all regions. + */ + + /* start code_ref=resources_resource_loader_init|code_cap= The resource loader initializes the anti-viral medication doses and hospital beds for each region. */ + public void init(ActorContext actorContext) { + + RegionsDataManager regionsDataManager = actorContext.getDataManager(RegionsDataManager.class); + ResourcesDataManager resourcesDataManager = actorContext.getDataManager(ResourcesDataManager.class); + GlobalPropertiesDataManager globalPropertiesDataManager = actorContext + .getDataManager(GlobalPropertiesDataManager.class); + int populationSize = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.POPULATION_SIZE); + Set regionIds = regionsDataManager.getRegionIds(); + + double dosesPerPerson = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.ANTIVIRAL_DOSES_PER_PERSON); + + double totalDoses = dosesPerPerson * populationSize; + int doseCount = (int) totalDoses; + int doseCountPerRegion = doseCount / regionIds.size(); + + double bedsPerPerson = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.HOSPITAL_BEDS_PER_PERSON); + + double totalBeds = bedsPerPerson * populationSize; + int bedCount = (int) totalBeds; + int bedCountPerRegion = bedCount / regionIds.size(); + + for (RegionId regionId : regionIds) { + resourcesDataManager.addResourceToRegion(Resource.ANTI_VIRAL_MED, regionId, doseCountPerRegion); + resourcesDataManager.addResourceToRegion(Resource.HOSPITAL_BED, regionId, bedCountPerRegion); + } + } + /* end */ + +} diff --git a/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/TreatmentManager.java b/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/TreatmentManager.java new file mode 100644 index 000000000..d8bbeefea --- /dev/null +++ b/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/TreatmentManager.java @@ -0,0 +1,166 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors; + +import java.util.List; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.GlobalProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.PersonProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.Resource; +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.resources.datamanagers.ResourcesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; + +public class TreatmentManager { + + private ActorContext actorContext; + private double antiviralCoverageTime; + private double antiviralSuccessRate; + private double hospitalSuccessWithAntiviral; + private double hospitalSuccessWithoutAntiviral; + private double hospitalStayDurationMin; + private double hospitalStayDurationMax; + + private PersonPropertiesDataManager personPropertiesDataManager; + private ResourcesDataManager resourcesDataManager; + private RegionsDataManager regionsDataManager; + private RandomGenerator randomGenerator; + + /* + * Determine whether the hospitalization was a success taking into account + * whether the person received an antiviral treatment before entering the + * hospital. If the treatment was successful then mark the person as immune. + * Otherwise, mark the person as a hospital death. + */ + /* start code_ref=resources_treatment_manager_assessHospitalization|code_cap= The treatment manager determines whether the hospitalization was a success taking into account whether the person received an antiviral treatment before entering the hospital. If the treatment was successful then the person is marked as immune. Otherwise, the person is marked as a hospital death.*/ + private void assessHospitalization(PersonId personId) { + + boolean treatedWithAntiViral = personPropertiesDataManager.getPersonPropertyValue(personId, + PersonProperty.TREATED_WITH_ANTIVIRAL); + double probabilityOfSuccess; + if (treatedWithAntiViral) { + probabilityOfSuccess = hospitalSuccessWithAntiviral; + } else { + probabilityOfSuccess = hospitalSuccessWithoutAntiviral; + } + if (randomGenerator.nextDouble() < probabilityOfSuccess) { + personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.IMMUNE, true); + } else { + personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.DEAD_IN_HOSPITAL, true); + } + resourcesDataManager.transferResourceFromPersonToRegion(Resource.HOSPITAL_BED, personId, 1L); + } + /* end */ + + /* + * Try to allocate a hospital bed to the person. If the bed is available, then + * schedule an assessment of the treatment success. Otherwise, mark the person + * as a home death. + */ + + /* start code_ref=resources_treatment_manager_hospitalizePerson|code_cap= The treatment manager attempts to find a hospital bed for a person who has not been treated or for whom treatment failed. If no hospital bed is available, the person is set to die in their home.*/ + private void hospitalizePerson(PersonId personId) { + RegionId regionId = regionsDataManager.getPersonRegion(personId); + + long availableHospitalBeds = resourcesDataManager.getRegionResourceLevel(regionId, Resource.HOSPITAL_BED); + + if (availableHospitalBeds > 0) { + resourcesDataManager.transferResourceToPersonFromRegion(Resource.HOSPITAL_BED, personId, 1L); + + personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.HOSPITALIZED, true); + + double hospitalizationDuration = (hospitalStayDurationMax - hospitalStayDurationMin) + * randomGenerator.nextDouble() + hospitalStayDurationMin; + + actorContext.addPlan((c) -> assessHospitalization(personId), + actorContext.getTime() + hospitalizationDuration); + } else { + personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.DEAD_IN_HOME, true); + } + } + /* end */ + + /* + * Expend the antiviral resource from the person. If the antiviral succeeded, + * then mark the person as immune. Otherwise, hospitalize the person immediately + */ + /* start code_ref=resources_treatment_manager_assessAntiviralTreatment|code_cap=The treatment manager assesses the success of the anti-viral treatment of a person and expends the medication. If the treatment was unsuccessful the person is immediately hospitalized. */ + private void assessAntiviralTreatment(PersonId personId) { + + resourcesDataManager.removeResourceFromPerson(Resource.ANTI_VIRAL_MED, personId, 1L); + if (randomGenerator.nextDouble() < antiviralSuccessRate) { + personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.IMMUNE, true); + } else { + hospitalizePerson(personId); + } + } + /* end */ + + /* + * Try to allocate one dose of the antiviral drug to the person. If the dose is + * available, then schedule an assessment of its success after the necessary + * waiting period. Otherwise, hospitalize the person immediately. + */ + /* start code_ref=resources_treatment_manager_treatWithAntiviral|code_cap= The treatment manager attempts to treat an infected person with anti-viral medication, if it is available. If no dose is available, the person is immediately hospitalized. */ + private void treatWithAntiviral(PersonId personId) { + + RegionId regionId = regionsDataManager.getPersonRegion(personId); + + long regionResourceLevel = resourcesDataManager.getRegionResourceLevel(regionId, Resource.ANTI_VIRAL_MED); + + if (regionResourceLevel > 0) { + resourcesDataManager.transferResourceToPersonFromRegion(Resource.ANTI_VIRAL_MED, personId, 1L); + + personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.TREATED_WITH_ANTIVIRAL, true); + + actorContext.addPlan((c) -> assessAntiviralTreatment(personId), + actorContext.getTime() + antiviralCoverageTime); + } else { + hospitalizePerson(personId); + } + } + /* end */ + + /* start code_ref=resources_treatment_manager_init|code_cap=The treatment manager establishes various constants and infects all non-immune people, scheduling them for anti-viral treatment. */ + public void init(ActorContext actorContext) { + this.actorContext = actorContext; + regionsDataManager = actorContext.getDataManager(RegionsDataManager.class); + resourcesDataManager = actorContext.getDataManager(ResourcesDataManager.class); + StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class); + randomGenerator = stochasticsDataManager.getRandomGenerator(); + personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class); + GlobalPropertiesDataManager globalPropertiesDataManager = actorContext + .getDataManager(GlobalPropertiesDataManager.class); + + double maximumSymptomOnsetTime = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.MAXIMUM_SYMPTOM_ONSET_TIME); + antiviralCoverageTime = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.ANTIVIRAL_COVERAGE_TIME); + antiviralSuccessRate = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.ANTIVIRAL_SUCCESS_RATE); + hospitalSuccessWithAntiviral = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.HOSPITAL_SUCCESS_WITH_ANTIVIRAL); + hospitalSuccessWithoutAntiviral = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.HOSPITAL_SUCCESS_WITHOUT_ANTIVIRAL); + hospitalStayDurationMin = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.HOSPITAL_STAY_DURATION_MIN); + hospitalStayDurationMax = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.HOSPITAL_STAY_DURATION_MAX); + + List susceptiblePeople = personPropertiesDataManager.getPeopleWithPropertyValue(PersonProperty.IMMUNE, + false); + for (PersonId personId : susceptiblePeople) { + double symptomOnsetTime = randomGenerator.nextDouble() * maximumSymptomOnsetTime; + personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.INFECTED, true); + + actorContext.addPlan((c) -> treatWithAntiviral(personId), symptomOnsetTime); + } + } + /* end */ + +} diff --git a/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/reports/DeathReport.java b/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/reports/DeathReport.java new file mode 100644 index 000000000..f0e9ea6c6 --- /dev/null +++ b/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/reports/DeathReport.java @@ -0,0 +1,97 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.reports; + +import java.util.List; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.PersonProperty; +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; + +/** + * A report that groups people at the end of the simulation by their shared + * person property values. + * + * + */ +public final class DeathReport { + private final ReportLabel reportLabel; + + public DeathReport(ReportLabel reportLabel) { + this.reportLabel = reportLabel; + } + + public void init(ReportContext reportContext) { + reportContext.subscribeToSimulationClose(this::report); + } + + private void report(ReportContext reportContext) { + RegionsDataManager regionsDataManager = reportContext.getDataManager(RegionsDataManager.class); + PersonPropertiesDataManager personPropertiesDataManager = reportContext + .getDataManager(PersonPropertiesDataManager.class); + + ReportHeader reportHeader = ReportHeader.builder()// + .add("region")// + .add("pop_size")// + .add("deaths")// + .add("deaths_in_home")// + .add("deaths_in_hospital")// + .add("per_capita_deaths")// + .add("per_capita_deaths_in_home")// + .add("per_capita_deaths_in_hospital")// + .build(); + + for (RegionId regionId : regionsDataManager.getRegionIds()) { + List peopleInRegion = regionsDataManager.getPeopleInRegion(regionId); + int popSize = peopleInRegion.size(); + int homeDeathCount = 0; + int hospitalDeathCount = 0; + + for (PersonId personId : peopleInRegion) { + Boolean deadInHospital = personPropertiesDataManager.getPersonPropertyValue(personId, + PersonProperty.DEAD_IN_HOSPITAL); + if (deadInHospital) { + hospitalDeathCount++; + } + Boolean deadInHome = personPropertiesDataManager.getPersonPropertyValue(personId, + PersonProperty.DEAD_IN_HOME); + if (deadInHome) { + homeDeathCount++; + } + } + int deathCount = homeDeathCount + hospitalDeathCount; + double perCapitaDeaths = 0; + double perCapitaHomeDeaths = 0; + double perCapitaHospitalDeaths = 0; + if (peopleInRegion.size() > 0) { + perCapitaDeaths = ((double) deathCount) / popSize; + perCapitaHomeDeaths = ((double) homeDeathCount) / popSize; + perCapitaHospitalDeaths = ((double) hospitalDeathCount) / popSize; + + } + ReportItem.Builder reportItemBuilder = ReportItem.builder(); + reportItemBuilder.setReportHeader(reportHeader); + reportItemBuilder.setReportLabel(reportLabel); + reportItemBuilder.addValue(regionId); + reportItemBuilder.addValue(peopleInRegion.size()); + reportItemBuilder.addValue(deathCount); + reportItemBuilder.addValue(homeDeathCount); + reportItemBuilder.addValue(hospitalDeathCount); + reportItemBuilder.addValue(perCapitaDeaths); + reportItemBuilder.addValue(perCapitaHomeDeaths); + reportItemBuilder.addValue(perCapitaHospitalDeaths); + + ReportItem reportItem = reportItemBuilder.build(); + /* + * Release the report item from the simulation + */ + reportContext.releaseOutput(reportItem); + + } + + } +} diff --git a/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/reports/QuestionnaireReport.java b/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/reports/QuestionnaireReport.java new file mode 100644 index 000000000..b1345e1ca --- /dev/null +++ b/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/reports/QuestionnaireReport.java @@ -0,0 +1,78 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.reports; + +import java.util.List; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.PersonProperty; +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import util.stats.MutableStat; + +/** + * A report that groups people at the end of the simulation by their shared + * person property values. + * + * + */ +public final class QuestionnaireReport { + private final ReportLabel reportLabel; + + public QuestionnaireReport(ReportLabel reportLabel) { + this.reportLabel = reportLabel; + } + + public void init(ReportContext reportContext) { + reportContext.subscribeToSimulationClose(this::report); + } + + private void report(ReportContext reportContext) { + PersonPropertiesDataManager personPropertiesDataManager = reportContext + .getDataManager(PersonPropertiesDataManager.class); + + ReportHeader reportHeader = ReportHeader.builder()// + .add("delivery rate")// + .add("mean delivery time")// + .add("stdev delivery time")// + .build(); + + ReportItem.Builder reportItemBuilder = ReportItem.builder(); + List infectedPeople = personPropertiesDataManager.getPeopleWithPropertyValue(PersonProperty.INFECTED, + true); + + MutableStat mutableStat = new MutableStat(); + + for (PersonId personId : infectedPeople) { + Boolean receivedQuestionnaire = personPropertiesDataManager.getPersonPropertyValue(personId, + PersonProperty.RECEIVED_QUESTIONNAIRE); + if (receivedQuestionnaire) { + double questionnaireTime = personPropertiesDataManager.getPersonPropertyTime(personId, + PersonProperty.RECEIVED_QUESTIONNAIRE); + mutableStat.add(questionnaireTime); + } + } + + double mean = mutableStat.getMean().orElse(0.0); + double stdev = mutableStat.getStandardDeviation().orElse(0.0); + + int completionCount = mutableStat.size(); + double deliveryRate = 0; + if (infectedPeople.size() > 0) { + deliveryRate = completionCount; + deliveryRate /= infectedPeople.size(); + } + + reportItemBuilder.setReportHeader(reportHeader); + reportItemBuilder.setReportLabel(reportLabel); + reportItemBuilder.addValue(deliveryRate); + reportItemBuilder.addValue(mean); + reportItemBuilder.addValue(stdev); + + ReportItem reportItem = reportItemBuilder.build(); + + reportContext.releaseOutput(reportItem); + + } +} diff --git a/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/reports/TreatmentReport.java b/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/reports/TreatmentReport.java new file mode 100644 index 000000000..a684eca26 --- /dev/null +++ b/tutorials/lessons/lesson_18_resources_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/reports/TreatmentReport.java @@ -0,0 +1,112 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.reports; + +import java.util.LinkedHashMap; +import java.util.Map; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.PersonProperty; +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import util.wrappers.MultiKey; +import util.wrappers.MutableInteger; + +/** + * A report that groups people at the end of the simulation by their shared + * person property values. + * + * + */ +public final class TreatmentReport { + private final ReportLabel reportLabel; + + public TreatmentReport(ReportLabel reportLabel) { + this.reportLabel = reportLabel; + } + + public void init(ReportContext reportContext) { + reportContext.subscribeToSimulationClose(this::report); + } + + private void report(ReportContext reportContext) { + PeopleDataManager peopleDataManager = reportContext.getDataManager(PeopleDataManager.class); + PersonPropertiesDataManager personPropertiesDataManager = reportContext + .getDataManager(PersonPropertiesDataManager.class); + /* + * Build a map from a multikey to a counter. Each person's ordered person + * property values will form the multikey. The counter is incremented for each + * person matching the unique multikeys. + */ + Map map = new LinkedHashMap<>(); + for (PersonId personId : peopleDataManager.getPeople()) { + + Boolean immune = personPropertiesDataManager.getPersonPropertyValue(personId, PersonProperty.IMMUNE); + Boolean infected = personPropertiesDataManager.getPersonPropertyValue(personId, PersonProperty.INFECTED); + Boolean treatedWithAntiviral = personPropertiesDataManager.getPersonPropertyValue(personId, + PersonProperty.TREATED_WITH_ANTIVIRAL); + Boolean hospitalized = personPropertiesDataManager.getPersonPropertyValue(personId, + PersonProperty.HOSPITALIZED); + Boolean deadInHospital = personPropertiesDataManager.getPersonPropertyValue(personId, + PersonProperty.DEAD_IN_HOSPITAL); + Boolean deadInHome = personPropertiesDataManager.getPersonPropertyValue(personId, + PersonProperty.DEAD_IN_HOME); + + MultiKey multiKey = new MultiKey(immune, infected, treatedWithAntiviral, hospitalized, deadInHospital, + deadInHome); + MutableInteger mutableInteger = map.get(multiKey); + if (mutableInteger == null) { + mutableInteger = new MutableInteger(); + map.put(multiKey, mutableInteger); + } + mutableInteger.increment(); + } + + /* + * Build the header of the report using the headers that correspond to the + * ordered key values in the multikeys. + */ + ReportHeader reportHeader = ReportHeader.builder()// + .add("immune")// + .add("infected")// + .add("treated_with_antiviral")// + .add("hospitalized")// + .add("dead_in_hospital")// + .add("dead_in_home")// + .add("people")// + .build(); + + /* + * Form a report item for each multikey, taking the ordered property values from + * the multikey and using them as inputs to the report item + */ + for (MultiKey multiKey : map.keySet()) { + ReportItem.Builder reportItemBuilder = ReportItem.builder(); + int personCount = map.get(multiKey).getValue(); + boolean immune = multiKey.getKey(0); + boolean infected = multiKey.getKey(1); + boolean treatedWithAntiviral = multiKey.getKey(2); + boolean hospitalized = multiKey.getKey(3); + boolean deadInHospital = multiKey.getKey(4); + boolean deadInHome = multiKey.getKey(5); + + reportItemBuilder.setReportHeader(reportHeader); + reportItemBuilder.setReportLabel(reportLabel); + reportItemBuilder.addValue(immune); + reportItemBuilder.addValue(infected); + reportItemBuilder.addValue(treatedWithAntiviral); + reportItemBuilder.addValue(hospitalized); + reportItemBuilder.addValue(deadInHospital); + reportItemBuilder.addValue(deadInHome); + reportItemBuilder.addValue(personCount); + ReportItem reportItem = reportItemBuilder.build(); + /* + * Release the report item from the simulation + */ + reportContext.releaseOutput(reportItem); + } + + } +} diff --git a/tutorials/lessons/lesson_19_materials_plugin/pom.xml b/tutorials/lessons/lesson_19_materials_plugin/pom.xml new file mode 100644 index 000000000..5cec904ca --- /dev/null +++ b/tutorials/lessons/lesson_19_materials_plugin/pom.xml @@ -0,0 +1,79 @@ + + 4.0.0 + + + ASPR + https://www.phe.gov + + + + gov.hhs.aspr.ms.gcm.tutorials + lesson_19_materials_plugin + ${revision} + jar + gcm lesson 19 materials plugin + Tutorial introducing the materials plugin + + + + UTF-8 + 17 + 17 + ${gcm.version} + + 1.3.0 + + 4.0.0-SNAPSHOT + + + + + + gov.hhs.aspr.ms + gcm + ${gcm.version} + + + + + + Shawn Hatch + Leidos + https://www.leidos.com + + + + + + + org.codehaus.mojo + flatten-maven-plugin + ${flatten-maven-plugin.version} + + + + flatten + process-resources + + flatten + + + true + + + + + flatten.clean + clean + + clean + + + + + + + + diff --git a/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_19.java b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_19.java new file mode 100644 index 000000000..9ad5debd4 --- /dev/null +++ b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_19.java @@ -0,0 +1,316 @@ +package gov.hhs.aspr.ms.gcm.lessons; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.stream.IntStream; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.ModelPlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.DiseaseState; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.GlobalProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.GroupType; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.Material; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.MaterialsProducer; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.ModelReportLabel; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.PersonProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.Region; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.Resource; +import gov.hhs.aspr.ms.gcm.nucleus.Dimension; +import gov.hhs.aspr.ms.gcm.nucleus.Experiment; +import gov.hhs.aspr.ms.gcm.nucleus.ExperimentParameterData; +import gov.hhs.aspr.ms.gcm.nucleus.FunctionalDimension; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.GlobalPropertiesPlugin; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertyId; +import gov.hhs.aspr.ms.gcm.plugins.groups.GroupsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.groups.datamanagers.GroupsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.materials.MaterialsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.materials.datamangers.MaterialsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePlugin; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeoplePluginData; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.PersonPropertiesPlugin; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.reports.PersonPropertyReportPluginData; +import gov.hhs.aspr.ms.gcm.plugins.regions.RegionsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.NIOReportItemHandler; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import gov.hhs.aspr.ms.gcm.plugins.resources.ResourcesPlugin; +import gov.hhs.aspr.ms.gcm.plugins.resources.datamanagers.ResourcesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; +import util.random.RandomGeneratorProvider; + +public final class Example_19 { + + private final Path outputDirectory; + + public static void main(final String[] args) throws IOException { + if (args.length == 0) { + throw new RuntimeException("One output directory argument is required"); + } + Path outputDirectory = Paths.get(args[0]); + if (!Files.exists(outputDirectory)) { + Files.createDirectory(outputDirectory); + } else { + if (!Files.isDirectory(outputDirectory)) { + throw new IOException("Provided path is not a directory"); + } + } + + new Example_19(outputDirectory).execute(); + } + + private final RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(9032703880551658180L); + + private Example_19(Path outputDirectory) { + this.outputDirectory = outputDirectory; + } + /* start code_ref=materials_plugin_example_19_execute|code_cap=The various plugins are gathered from their initial data, dimensions are added and the experiment is executed over 81 scenarios using 8 threads. */ + + private void execute() { + + ExperimentParameterData experimentParameterData = ExperimentParameterData.builder()// + .setThreadCount(8)// + .build(); + + Experiment.builder()// + .addPlugin(getMaterialsPlugin())// + .addPlugin(getResourcesPlugin())// + .addPlugin(getGlobalPropertiesPlugin())// + .addPlugin(getPersonPropertiesPlugin())// + .addPlugin(getStochasticsPlugin())// + .addPlugin(getRegionsPlugin())// + .addPlugin(getPeoplePlugin())// + .addPlugin(getGroupsPlugin())// + .addPlugin(ModelPlugin.getModelPlugin())// + + .addDimension(getInfectionThresholdDimension())// + .addDimension(getCommunityContactRateDimension())// + .addDimension(getIntialInfectionsDimension())// + .addDimension(getR0Dimension())// + + .addExperimentContextConsumer(getNIOReportItemHandler())// + .setExperimentParameterData(experimentParameterData).build()// + .execute();// + + } + + /* end */ + private Dimension getCommunityContactRateDimension() { + final Double[] values = new Double[] { 0.0, 0.01, 0.05 }; + return getGlobalPropertyDimension(GlobalProperty.COMMUNITY_CONTACT_RATE, "community_contact_rate", values); + } + + /* start code_ref=materials_plugin_example_19_global_properties_plugin|code_cap=The global properties plugin is initialized with several properties.*/ + private Plugin getGlobalPropertiesPlugin() { + final GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder();// + + PropertyDefinition propertyDefinition = PropertyDefinition.builder()// + .setType(Double.class)// + .setPropertyValueMutability(false)// + .setDefaultValue(0.0)// + .build(); + + builder.defineGlobalProperty(GlobalProperty.SUSCEPTIBLE_POPULATION_PROPORTION, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.AVERAGE_HOME_SIZE, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.AVERAGE_SCHOOL_SIZE, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.AVERAGE_WORK_SIZE, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.CHILD_POPULATION_PROPORTION, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.SENIOR_POPULATION_PROPORTION, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.R0, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.COMMUNITY_CONTACT_RATE, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.INFECTION_THRESHOLD, propertyDefinition, 0); + + propertyDefinition = PropertyDefinition.builder()// + .setType(Integer.class)// + .setPropertyValueMutability(false)// + .build(); + builder.defineGlobalProperty(GlobalProperty.INITIAL_INFECTIONS, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.MIN_INFECTIOUS_PERIOD, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.MAX_INFECTIOUS_PERIOD, propertyDefinition, 0); + builder.defineGlobalProperty(GlobalProperty.POPULATION_SIZE, propertyDefinition, 0); + + propertyDefinition = PropertyDefinition.builder()// + .setType(Boolean.class)// + .setDefaultValue(false)// + .setPropertyValueMutability(true)// + .build(); + builder.defineGlobalProperty(GlobalProperty.MANUFACTURE_VACCINE, propertyDefinition, 0); + + builder.setGlobalPropertyValue(GlobalProperty.POPULATION_SIZE, 10_000, 0); + builder.setGlobalPropertyValue(GlobalProperty.SUSCEPTIBLE_POPULATION_PROPORTION, 1.0, 0); + builder.setGlobalPropertyValue(GlobalProperty.INITIAL_INFECTIONS, 1, 0); + builder.setGlobalPropertyValue(GlobalProperty.MIN_INFECTIOUS_PERIOD, 7, 0); + builder.setGlobalPropertyValue(GlobalProperty.MAX_INFECTIOUS_PERIOD, 14, 0); + builder.setGlobalPropertyValue(GlobalProperty.R0, 2.0, 0); + builder.setGlobalPropertyValue(GlobalProperty.CHILD_POPULATION_PROPORTION, 0.235, 0); + builder.setGlobalPropertyValue(GlobalProperty.SENIOR_POPULATION_PROPORTION, 0.169, 0); + builder.setGlobalPropertyValue(GlobalProperty.AVERAGE_HOME_SIZE, 2.5, 0); + builder.setGlobalPropertyValue(GlobalProperty.AVERAGE_SCHOOL_SIZE, 250.0, 0); + builder.setGlobalPropertyValue(GlobalProperty.AVERAGE_WORK_SIZE, 30.0, 0); + builder.setGlobalPropertyValue(GlobalProperty.INFECTION_THRESHOLD, 0.0, 0); + builder.setGlobalPropertyValue(GlobalProperty.COMMUNITY_CONTACT_RATE, 0.0, 0); + + final GlobalPropertiesPluginData globalPropertiesPluginData = builder.build(); + + return GlobalPropertiesPlugin.builder().setGlobalPropertiesPluginData(globalPropertiesPluginData) + .getGlobalPropertiesPlugin(); + + } + /* end */ + + private Dimension getGlobalPropertyDimension(final GlobalPropertyId globalPropertyId, final String header, + final Object[] values) { + final FunctionalDimension.Builder dimensionBuilder = FunctionalDimension.builder();// + IntStream.range(0, values.length).forEach((i) -> { + dimensionBuilder.addLevel((context) -> { + final GlobalPropertiesPluginData.Builder builder = context + .getPluginDataBuilder(GlobalPropertiesPluginData.Builder.class); + final Object value = values[i]; + builder.setGlobalPropertyValue(globalPropertyId, value, 0); + final ArrayList result = new ArrayList<>(); + result.add(value.toString()); + return result; + });// + }); + dimensionBuilder.addMetaDatum(header);// + return dimensionBuilder.build(); + } + + private Plugin getGroupsPlugin() { + final GroupsPluginData.Builder builder = GroupsPluginData.builder(); + for (final GroupType groupType : GroupType.values()) { + builder.addGroupTypeId(groupType); + } + final GroupsPluginData groupsPluginData = builder.build(); + return GroupsPlugin.builder().setGroupsPluginData(groupsPluginData).getGroupsPlugin(); + } + + private Dimension getIntialInfectionsDimension() { + final Integer[] values = new Integer[] { 1, 10, 100 }; + return getGlobalPropertyDimension(GlobalProperty.INITIAL_INFECTIONS, "initial_infections", values); + } + + /* start code_ref=materials_plugin_example_19_materials_plugin|code_cap= The materials plugin establishes the materials producer ids and the material types.*/ + private Plugin getMaterialsPlugin() { + final MaterialsPluginData.Builder builder = MaterialsPluginData.builder(); + for (final MaterialsProducer materialsProducer : MaterialsProducer.values()) { + builder.addMaterialsProducerId(materialsProducer); + } + for (final Material material : Material.values()) { + builder.addMaterial(material); + } + final MaterialsPluginData materialsPluginData = builder.build(); + return MaterialsPlugin.builder().setMaterialsPluginData(materialsPluginData).getMaterialsPlugin(); + } + /* end */ + + private Plugin getPeoplePlugin() { + final PeoplePluginData peoplePluginData = PeoplePluginData.builder().build(); + return PeoplePlugin.getPeoplePlugin(peoplePluginData); + } + + /* start code_ref=materials_plugin_example_19_person_properties_plugin|code_cap=The person properties plugin includes person property definitions and the data for the person property report. */ + private Plugin getPersonPropertiesPlugin() { + + final PersonPropertiesPluginData.Builder builder = PersonPropertiesPluginData.builder(); + + PropertyDefinition propertyDefinition = PropertyDefinition.builder()// + .setType(Boolean.class)// + .setDefaultValue(false)// + .build(); + + builder.definePersonProperty(PersonProperty.VACCINATED, propertyDefinition, 0, false);// + builder.definePersonProperty(PersonProperty.VACCINE_SCHEDULED, propertyDefinition, 0, false);// + + propertyDefinition = PropertyDefinition.builder()// + .setType(Integer.class)// + .build();// + builder.definePersonProperty(PersonProperty.AGE, propertyDefinition, 0, false);// + + propertyDefinition = PropertyDefinition.builder()// + .setType(DiseaseState.class)// + .setDefaultValue(DiseaseState.SUSCEPTIBLE)// + .build(); + + builder.definePersonProperty(PersonProperty.DISEASE_STATE, propertyDefinition, 0, false);// + + final PersonPropertiesPluginData personPropertiesPluginData = builder.build(); + + PersonPropertyReportPluginData personPropertyReportPluginData = // + PersonPropertyReportPluginData.builder()// + .setReportLabel(ModelReportLabel.PERSON_PROPERTY_REPORT)// + .setReportPeriod(ReportPeriod.DAILY)// + .includePersonProperty(PersonProperty.VACCINATED)// + .includePersonProperty(PersonProperty.VACCINE_SCHEDULED)// + .build(); + + return PersonPropertiesPlugin.builder()// + .setPersonPropertiesPluginData(personPropertiesPluginData)// + .setPersonPropertyReportPluginData(personPropertyReportPluginData)// + .getPersonPropertyPlugin(); + + } + /* end */ + + private Dimension getR0Dimension() { + final Double[] values = new Double[] { 2.0, 2.5, 3.0 }; + return getGlobalPropertyDimension(GlobalProperty.R0, "R0", values); + } + + private Dimension getInfectionThresholdDimension() { + final Double[] values = new Double[] { 0.01, 0.02, 0.05 }; + return getGlobalPropertyDimension(GlobalProperty.INFECTION_THRESHOLD, "infection_threshold", values); + } + + private Plugin getRegionsPlugin() { + final RegionsPluginData.Builder regionsPluginDataBuilder = RegionsPluginData.builder(); + + for (int i = 0; i < 1; i++) { + regionsPluginDataBuilder.addRegion(new Region(i)); + } + final RegionsPluginData regionsPluginData = regionsPluginDataBuilder.build(); + return RegionsPlugin.builder().setRegionsPluginData(regionsPluginData).getRegionsPlugin(); + } + + private NIOReportItemHandler getNIOReportItemHandler() { + return NIOReportItemHandler.builder()// + .addReport(ModelReportLabel.DISEASE_STATE_REPORT, outputDirectory.resolve("disease_state_report.xls"))// + .addReport(ModelReportLabel.PERSON_PROPERTY_REPORT, + outputDirectory.resolve("person_property_report.xls"))// + .addReport(ModelReportLabel.VACCINE_REPORT, outputDirectory.resolve("vaccine_report.xls"))// + .addReport(ModelReportLabel.VACCINE_PRODUCTION_REPORT, + outputDirectory.resolve("vaccine_production_report.xls"))// + .build(); + } + + /* start code_ref=materials_plugin_example_19_resources_plugin|code_cap=The resources plugin is created with the single VACCINE resource id.*/ + private Plugin getResourcesPlugin() { + final ResourcesPluginData.Builder builder = ResourcesPluginData.builder(); + for (final ResourceId resourcId : Resource.values()) { + builder.addResource(resourcId, 0.0, true); + } + final ResourcesPluginData resourcesPluginData = builder.build(); + return ResourcesPlugin.builder().setResourcesPluginData(resourcesPluginData).getResourcesPlugin(); + } + /* end */ + + private Plugin getStochasticsPlugin() { + WellState wellState = WellState.builder().setSeed(randomGenerator.nextLong()).build(); + final StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder()// + .setMainRNGState(wellState)// + .build(); + + return StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData); + } + +} diff --git a/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java new file mode 100644 index 000000000..2c653abcd --- /dev/null +++ b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java @@ -0,0 +1,33 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors.ContactManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors.PopulationLoader; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors.Vaccinator; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.reports.DiseaseStateReport; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.reports.VaccineProductionReport; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.reports.VaccineReport; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.ModelReportLabel; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; + +public final class ModelPlugin { + public static Plugin getModelPlugin() { + return Plugin.builder()// + .setPluginId(ModelPluginId.PLUGIN_ID).setInitializer((c) -> { + c.addActor(new PopulationLoader()::init); + c.addActor(new ContactManager()::init); + c.addActor(new Vaccinator()::init); + + c.addReport(new DiseaseStateReport(ModelReportLabel.DISEASE_STATE_REPORT, + ReportPeriod.END_OF_SIMULATION)::init);// + c.addReport(new VaccineReport(ModelReportLabel.VACCINE_REPORT, ReportPeriod.DAILY)::init);// + c.addReport(new VaccineProductionReport(ModelReportLabel.VACCINE_PRODUCTION_REPORT, + ReportPeriod.DAILY)::init);// + + }).build(); + } + + private ModelPlugin() { + + } +} diff --git a/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java new file mode 100644 index 000000000..8f6df5e72 --- /dev/null +++ b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java @@ -0,0 +1,11 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.nucleus.SimplePluginId; + +public final class ModelPluginId { + public final static PluginId PLUGIN_ID = new SimplePluginId("model plugin"); + + private ModelPluginId() { + } +} diff --git a/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/AntigenProducer.java b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/AntigenProducer.java new file mode 100644 index 000000000..ab0591534 --- /dev/null +++ b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/AntigenProducer.java @@ -0,0 +1,198 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.math3.util.FastMath; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.GlobalProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.Material; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.MaterialManufactureSpecification; +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.materials.datamangers.MaterialsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchConstructionInfo; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.StageConversionInfo; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.StageId; + +public final class AntigenProducer { + private ActorContext actorContext; + + private final MaterialsProducerId materialsProducerId; + + private MaterialsDataManager materialsDataManager; + + private GlobalPropertiesDataManager globalPropertiesDataManager; + + private final Map materialRecs = new LinkedHashMap<>(); + + private final int stageCapacity = 60;// to be non-constraining, + // stageCapacity = + // fermentationTime/batchAssemblyDuration + + private final double fermentationTime = 15.0; + + private final double antigenUnits = 25.0; + + private final double batchAssemblyDuration = 0.25; + + private double lastBatchAssemblyEndTime; + + public AntigenProducer(final MaterialsProducerId materialsProducerId) { + this.materialsProducerId = materialsProducerId; + } + + private void addMaterialRec(final MaterialId materialId, final MaterialManufactureSpecification.Builder builder) { + final BatchConstructionInfo batchConstructionInfo = BatchConstructionInfo.builder()// + .setMaterialId(materialId)// + .setMaterialsProducerId(materialsProducerId)// + .build();// + final BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + builder.setBatchId(batchId); + builder.setMaterialId(materialId); + final MaterialManufactureSpecification materialManufactureSpecification = builder.build(); + materialRecs.put(materialId, materialManufactureSpecification); + } + + /* start code_ref=materials_plugin_antigen_producer_end_fermentation|code_cap=When an antigen containing stage is ready for release, the antigen producer converts the stage into a batch of antigen and re-stages this batch on an offered stage.*/ + private void endFermentationStage(final StageId stageId) { + final BatchId batch = materialsDataManager.convertStageToBatch(// + StageConversionInfo.builder()// + .setAmount(antigenUnits)// + .setMaterialId(Material.ANTIGEN)// + .setStageId(stageId)// + .build());// + + final StageId antigenStage = materialsDataManager.addStage(materialsProducerId); + materialsDataManager.moveBatchToStage(batch, antigenStage); + materialsDataManager.setStageOfferState(antigenStage, true); + planFermentation(); + } + /* end */ + + private boolean hasSufficientMaterialsForNewStage() { + for (final MaterialId materialId : materialRecs.keySet()) { + final MaterialManufactureSpecification materialManufactureSpecification = materialRecs.get(materialId); + final double batchAmount = materialsDataManager + .getBatchAmount(materialManufactureSpecification.getBatchId()); + if (batchAmount < materialManufactureSpecification.getStageAmount()) { + return false; + } + } + return true; + } + + /* start code_ref=materials_plugin_antigen_producer_init|code_cap=The antigen producer initializes by subscribing to stage transfers from itself as well as changes to the manufacturing policy. */ + public void init(final ActorContext actorContext) { + this.actorContext = actorContext; + materialsDataManager = actorContext.getDataManager(MaterialsDataManager.class); + globalPropertiesDataManager = actorContext.getDataManager(GlobalPropertiesDataManager.class); + addMaterialRec(Material.GROWTH_MEDIUM, MaterialManufactureSpecification.builder()// + .setDeliveryAmount(35.0)// + .setDeliveryDelay(7.0)// + .setStageAmount(1.0));// + + addMaterialRec(Material.VIRUS, MaterialManufactureSpecification.builder()// + .setDeliveryAmount(100.0)// + .setDeliveryDelay(21.0)// + .setStageAmount(1.0));// + + // each time a stage is transferred + actorContext.subscribe( + materialsDataManager.getEventFilterForStageMaterialsProducerUpdateEvent_BySource(materialsProducerId), + (c, e) -> planFermentation()); + + // each time the manufacture policy is changed + actorContext.subscribe(globalPropertiesDataManager.getEventFilterForGlobalPropertyUpdateEvent( + GlobalProperty.MANUFACTURE_VACCINE), (c, e) -> planFermentation()); + + planFermentation(); + } + + /* end */ + private void orderMaterial(final MaterialId materialId) { + final MaterialManufactureSpecification materialRec = materialRecs.get(materialId); + if (materialRec.isOnOrder()) { + return; + } + + double requiredAmount = stageCapacity * materialRec.getStageAmount(); + requiredAmount /= batchAssemblyDuration; + requiredAmount *= materialRec.getDeliveryDelay(); + + final List batches = materialsDataManager.getInventoryBatchesByMaterialId(materialsProducerId, + materialId); + double currentAmount = 0; + for (final BatchId batchId : batches) { + currentAmount += materialsDataManager.getBatchAmount(batchId); + } + double amountToOrder = requiredAmount - currentAmount; + if (amountToOrder <= 0) { + return; + } + + amountToOrder = FastMath.ceil(amountToOrder / materialRec.getDeliveryAmount()) + * materialRec.getDeliveryAmount(); + final double deliveryTime = materialRec.getDeliveryDelay() + actorContext.getTime(); + final double amount = amountToOrder; + materialRec.toggleOnOrder(); + + actorContext.addPlan((c) -> receiveMaterial(materialId, amount), deliveryTime); + } + + private void orderMaterials() { + for (final MaterialId materialId : materialRecs.keySet()) { + orderMaterial(materialId); + } + } + + /* start code_ref=materials_plugin_antigen_producer_plan_fermentation|code_cap=Responding to events that may allow for additional manufacture of antigen stages, the antigen producer attempts to continue manufacturing.*/ + private void planFermentation() { + + final Boolean continueManufature = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.MANUFACTURE_VACCINE); + if (!continueManufature) { + return; + } + orderMaterials(); + + while (!stagesAtCapacity() && hasSufficientMaterialsForNewStage()) { + final StageId stageId = materialsDataManager.addStage(materialsProducerId); + for (final MaterialId materialId : materialRecs.keySet()) { + final MaterialManufactureSpecification materialManufactureSpecification = materialRecs.get(materialId); + final BatchId newBatchId = materialsDataManager.addBatch(BatchConstructionInfo.builder() + .setMaterialsProducerId(materialsProducerId).setMaterialId(materialId).build()); + materialsDataManager.transferMaterialBetweenBatches(materialManufactureSpecification.getBatchId(), + newBatchId, materialManufactureSpecification.getStageAmount()); + materialsDataManager.moveBatchToStage(newBatchId, stageId); + } + final double batchAssemblyStartTime = FastMath.max(actorContext.getTime(), lastBatchAssemblyEndTime); + final double fermentationStartTime = batchAssemblyStartTime + batchAssemblyDuration; + lastBatchAssemblyEndTime = fermentationStartTime; + final double planTime = fermentationStartTime + fermentationTime; + actorContext.addPlan((c) -> endFermentationStage(stageId), planTime); + } + } + /* end */ + + private void receiveMaterial(final MaterialId materialId, final double amount) { + final MaterialManufactureSpecification materialRec = materialRecs.get(materialId); + materialRec.toggleOnOrder(); + + final BatchId newBatchId = materialsDataManager.addBatch(BatchConstructionInfo.builder() + .setMaterialsProducerId(materialsProducerId).setMaterialId(materialId).setAmount(amount).build()); + materialsDataManager.transferMaterialBetweenBatches(newBatchId, materialRec.getBatchId(), amount); + materialsDataManager.removeBatch(newBatchId); + planFermentation(); + } + + private boolean stagesAtCapacity() { + final List stages = materialsDataManager.getStages(materialsProducerId); + return stages.size() >= stageCapacity; + } + +} diff --git a/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/ContactManager.java b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/ContactManager.java new file mode 100644 index 000000000..a359651f7 --- /dev/null +++ b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/ContactManager.java @@ -0,0 +1,139 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Random; + +import org.apache.commons.math3.random.RandomGenerator; +import org.apache.commons.math3.util.FastMath; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.DiseaseState; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.GlobalProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.PersonProperty; +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.groups.datamanagers.GroupsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupId; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupSampler; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; + +public class ContactManager { + private ActorContext actorContext; + private PersonPropertiesDataManager personPropertiesDataManager; + private GroupsDataManager groupsDataManager; + private PeopleDataManager peopleDataManager; + private RandomGenerator randomGenerator; + private int minInfectiousPeriod; + private int maxInfectiousPeriod; + private double infectionInterval; + private double communityContactRate; + + private void endInfectiousness(final PersonId personId) { + personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.DISEASE_STATE, + DiseaseState.RECOVERED); + } + + /* start code_ref=materials_plugin_contact_manager_infect_contact|code_cap=The contact manager attempts to make an infectious contact from an infected person to a randomly selected susceptible person who is not vaccinated from either the general public or from one of the infected person's groups. */ + private void infectContact(final PersonId personId) { + + if (randomGenerator.nextDouble() < communityContactRate) { + final List people = peopleDataManager.getPeople(); + people.remove(personId); + if (people.size() > 0) { + final PersonId contactedPerson = people.get(randomGenerator.nextInt(people.size())); + final DiseaseState diseaseState = personPropertiesDataManager.getPersonPropertyValue(contactedPerson, + PersonProperty.DISEASE_STATE); + final boolean vaccinated = personPropertiesDataManager.getPersonPropertyValue(contactedPerson, + PersonProperty.VACCINATED); + if ((diseaseState == DiseaseState.SUSCEPTIBLE) && !vaccinated) { + infectPerson(contactedPerson); + } + } + } else { + final List groupsForPerson = groupsDataManager.getGroupsForPerson(personId); + final GroupId groupId = groupsForPerson.get(randomGenerator.nextInt(groupsForPerson.size())); + final GroupSampler groupSampler = GroupSampler.builder().setExcludedPersonId(personId).build(); + final Optional optional = groupsDataManager.sampleGroup(groupId, groupSampler); + if (optional.isPresent()) { + final PersonId contactedPerson = optional.get(); + final DiseaseState diseaseState = personPropertiesDataManager.getPersonPropertyValue(contactedPerson, + PersonProperty.DISEASE_STATE); + final boolean vaccinated = personPropertiesDataManager.getPersonPropertyValue(contactedPerson, + PersonProperty.VACCINATED); + if ((diseaseState == DiseaseState.SUSCEPTIBLE) && !vaccinated) { + infectPerson(contactedPerson); + } + } + } + } + /* end */ + + /* start code_ref=materials_plugin_contact_manager_infect_person|code_cap= When a person is infected, the number of possible infectious contacts is determined and planned. After the last infectious contact, the person is scheduled to become recovered. */ + private void infectPerson(final PersonId personId) { + personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.DISEASE_STATE, + DiseaseState.INFECTIOUS); + final int infectiousDays = randomGenerator.nextInt(maxInfectiousPeriod - minInfectiousPeriod) + + minInfectiousPeriod; + final int infectionCount = (int) FastMath.round((infectiousDays / infectionInterval)); + + double planTime = actorContext.getTime(); + + for (int j = 0; j < infectionCount; j++) { + planTime += infectionInterval; + actorContext.addPlan((c) -> infectContact(personId), planTime); + } + actorContext.addPlan((c) -> endInfectiousness(personId), planTime); + } + + /* end */ + /* start code_ref=materials_plugin_contact_manager_init|code_cap=The contact manager initializes by infecting the initially infected people in the first day.*/ + public void init(final ActorContext actorContext) { + this.actorContext = actorContext; + + final StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class); + randomGenerator = stochasticsDataManager.getRandomGenerator(); + final Random random = new Random(randomGenerator.nextLong()); + + peopleDataManager = actorContext.getDataManager(PeopleDataManager.class); + + groupsDataManager = actorContext.getDataManager(GroupsDataManager.class); + final GlobalPropertiesDataManager globalPropertiesDataManager = actorContext + .getDataManager(GlobalPropertiesDataManager.class); + communityContactRate = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.COMMUNITY_CONTACT_RATE); + + personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class); + final List susceptiblePeople = personPropertiesDataManager + .getPeopleWithPropertyValue(PersonProperty.DISEASE_STATE, DiseaseState.SUSCEPTIBLE); + final List susceptibleAdults = new ArrayList<>(); + for (final PersonId personId : susceptiblePeople) { + final int age = personPropertiesDataManager.getPersonPropertyValue(personId, PersonProperty.AGE); + if (age > 18) { + susceptibleAdults.add(personId); + } + } + + Collections.shuffle(susceptibleAdults, random); + + minInfectiousPeriod = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.MIN_INFECTIOUS_PERIOD); + maxInfectiousPeriod = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.MAX_INFECTIOUS_PERIOD); + final double r0 = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.R0); + infectionInterval = (minInfectiousPeriod + maxInfectiousPeriod) / (2 * r0); + + int initialInfections = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.INITIAL_INFECTIONS); + initialInfections = FastMath.min(initialInfections, susceptibleAdults.size()); + + for (int i = 0; i < initialInfections; i++) { + final PersonId personId = susceptibleAdults.get(i); + final double planTime = (randomGenerator.nextDouble() * 0.5) + 0.25; + actorContext.addPlan((c) -> infectPerson(personId), planTime); + } + } + /* end */ + +} diff --git a/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/PopulationLoader.java b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/PopulationLoader.java new file mode 100644 index 000000000..9a9093c98 --- /dev/null +++ b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/PopulationLoader.java @@ -0,0 +1,202 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.apache.commons.math3.util.FastMath; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.DiseaseState; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.GlobalProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.GroupType; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.PersonProperty; +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.groups.datamanagers.GroupsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupConstructionInfo; +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupId; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonConstructionData; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyValueInitialization; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; + +public class PopulationLoader { + + private RandomGenerator randomGenerator; + private RegionsDataManager regionsDataManager; + private PersonPropertiesDataManager personPropertiesDataManager; + private GroupsDataManager groupsDataManager; + private PeopleDataManager peopleDataManager; + private double susceptibleProbability; + private double childPopulationProportion; + private double seniorPopulationProportion; + private double averageHomeSize; + private double averageWorkSize; + private double averageSchoolSize; + + public void init(final ActorContext actorContext) { + personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class); + groupsDataManager = actorContext.getDataManager(GroupsDataManager.class); + final StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class); + randomGenerator = stochasticsDataManager.getRandomGenerator(); + peopleDataManager = actorContext.getDataManager(PeopleDataManager.class); + final GlobalPropertiesDataManager globalPropertiesDataManager = actorContext + .getDataManager(GlobalPropertiesDataManager.class); + regionsDataManager = actorContext.getDataManager(RegionsDataManager.class); + + final int populationSize = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.POPULATION_SIZE); + susceptibleProbability = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.SUSCEPTIBLE_POPULATION_PROPORTION); + childPopulationProportion = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.CHILD_POPULATION_PROPORTION); + seniorPopulationProportion = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.SENIOR_POPULATION_PROPORTION); + averageHomeSize = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.AVERAGE_HOME_SIZE); + averageSchoolSize = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.AVERAGE_SCHOOL_SIZE); + averageWorkSize = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.AVERAGE_WORK_SIZE); + + final Set regionIds = regionsDataManager.getRegionIds(); + final int regionSize = populationSize / regionIds.size(); + int leftoverPeople = populationSize % regionIds.size(); + + for (final RegionId regionId : regionIds) { + int regionPopulation = regionSize; + if (leftoverPeople > 0) { + leftoverPeople--; + regionPopulation++; + } + initializeRegionPopulation(regionId, regionPopulation); + } + } + + private void initializeRegionPopulation(final RegionId regionId, final int populationSize) { + + final double n = populationSize; + int homeCount = (int) (n / averageHomeSize) + 1; + final int childCount = (int) (n * childPopulationProportion); + final int adultCount = populationSize - childCount; + homeCount = FastMath.min(homeCount, adultCount); + int seniorCount = (int) (n * seniorPopulationProportion); + seniorCount = FastMath.min(seniorCount, adultCount); + final int workingAdultCount = adultCount - seniorCount; + final int workCount = (int) (workingAdultCount / averageWorkSize) + 1; + final int schoolCount = (int) (childCount / averageSchoolSize) + 1; + + // create the population + for (int i = 0; i < populationSize; i++) { + int age; + if (i < seniorCount) { + age = randomGenerator.nextInt(25) + 65; + } else if (i < adultCount) { + age = randomGenerator.nextInt(18) + (65 - 18); + } else { + age = randomGenerator.nextInt(18); + } + final PersonPropertyValueInitialization ageInitialization = new PersonPropertyValueInitialization( + PersonProperty.AGE, age); + + DiseaseState diseaseState = DiseaseState.IMMUNE; + if (randomGenerator.nextDouble() < susceptibleProbability) { + diseaseState = DiseaseState.SUSCEPTIBLE; + } + + final PersonPropertyValueInitialization diseaseInitialization = new PersonPropertyValueInitialization( + PersonProperty.DISEASE_STATE, diseaseState); + final PersonConstructionData personConstructionData = PersonConstructionData.builder()// + .add(ageInitialization)// + .add(diseaseInitialization)// + .add(regionId)// + .build(); + peopleDataManager.addPerson(personConstructionData); + } + + // create the home groups + final List homeGroupIds = new ArrayList<>(); + for (int i = 0; i < homeCount; i++) { + final GroupConstructionInfo groupConstructionInfo = GroupConstructionInfo.builder() + .setGroupTypeId(GroupType.HOME).build(); + final GroupId groupId = groupsDataManager.addGroup(groupConstructionInfo); + homeGroupIds.add(groupId); + } + + // create the work groups + final List workGroupIds = new ArrayList<>(); + for (int i = 0; i < workCount; i++) { + final GroupConstructionInfo groupConstructionInfo = GroupConstructionInfo.builder() + .setGroupTypeId(GroupType.WORK).build(); + final GroupId groupId = groupsDataManager.addGroup(groupConstructionInfo); + workGroupIds.add(groupId); + } + + // create the school groups + final List schoolGroupIds = new ArrayList<>(); + for (int i = 0; i < schoolCount; i++) { + final GroupConstructionInfo groupConstructionInfo = GroupConstructionInfo.builder() + .setGroupTypeId(GroupType.SCHOOL).build(); + final GroupId groupId = groupsDataManager.addGroup(groupConstructionInfo); + schoolGroupIds.add(groupId); + } + + // determine the subsets of people by age + final List peopleInRegion = regionsDataManager.getPeopleInRegion(regionId); + final List adults = new ArrayList<>(); + final List children = new ArrayList<>(); + final List workingAdults = new ArrayList<>(); + for (final PersonId personId : peopleInRegion) { + final int age = personPropertiesDataManager.getPersonPropertyValue(personId, PersonProperty.AGE); + if (age < 18) { + children.add(personId); + } else { + adults.add(personId); + if (age < 65) { + workingAdults.add(personId); + } + } + } + + final Random random = new Random(randomGenerator.nextLong()); + /* + * Randomize the adults and assign them to the home groups such that there is at + * least one adult in each home + */ + Collections.shuffle(adults, random); + // put one adult in each home + for (int i = 0; i < homeGroupIds.size(); i++) { + final PersonId personId = adults.get(i); + final GroupId groupId = homeGroupIds.get(i); + groupsDataManager.addPersonToGroup(personId, groupId); + } + + // assign the remaining adults at random to homes + for (int i = homeGroupIds.size(); i < adults.size(); i++) { + final PersonId personId = adults.get(i); + final GroupId groupId = homeGroupIds.get(randomGenerator.nextInt(homeGroupIds.size())); + groupsDataManager.addPersonToGroup(personId, groupId); + } + + // assign working age adults to work groups + for (final PersonId personId : workingAdults) { + final GroupId groupId = workGroupIds.get(randomGenerator.nextInt(workGroupIds.size())); + groupsDataManager.addPersonToGroup(personId, groupId); + } + + // assign children to school groups + for (final PersonId personId : children) { + final GroupId groupId = schoolGroupIds.get(randomGenerator.nextInt(schoolGroupIds.size())); + groupsDataManager.addPersonToGroup(personId, groupId); + } + + // assign children to home groups + for (final PersonId personId : children) { + final GroupId groupId = homeGroupIds.get(randomGenerator.nextInt(homeGroupIds.size())); + groupsDataManager.addPersonToGroup(personId, groupId); + } + } +} diff --git a/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/Vaccinator.java b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/Vaccinator.java new file mode 100644 index 000000000..089b14e4c --- /dev/null +++ b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/Vaccinator.java @@ -0,0 +1,214 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.DiseaseState; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.GlobalProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.MaterialsProducer; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.PersonProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.Resource; +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.materials.datamangers.MaterialsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.MaterialsProducerResourceUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerId; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.events.PersonPropertyUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.resources.datamanagers.ResourcesDataManager; +import util.wrappers.MutableDouble; +import util.wrappers.MutableLong; + +public class Vaccinator { + + private MaterialsDataManager materialsDataManager; + + private RegionsDataManager regionsDataManager; + + private PeopleDataManager peopleDataManager; + + private PersonPropertiesDataManager personPropertiesDataManager; + + private GlobalPropertiesDataManager globalPropertiesDataManager; + + private ResourcesDataManager resourcesDataManager; + + private final Map vaccinationSchedules = new LinkedHashMap<>(); + + private final Map availableVaccines = new LinkedHashMap<>(); + + private final int vaccinationsPerRegionPerDay = 100; + + private ActorContext actorContext; + + private int infectionPersonCountThreshold; + private int infectedPersonCount; + + private boolean manufactureStarted; + + /* start code_ref=materials_plugin_vaccinator_manufacture_start|code_cap=If vaccine manufacture has not yet started and the number of infected people exceeds a threshold, then the vaccinator set the MANUFACTURE_VACCINE global property to true, signaling to the vaccine related materials producers to start.*/ + private void determineVaccineManufacutureStart() { + if (!manufactureStarted) { + if (infectedPersonCount >= infectionPersonCountThreshold) { + manufactureStarted = true; + globalPropertiesDataManager.setGlobalPropertyValue(GlobalProperty.MANUFACTURE_VACCINE, true); + actorContext.unsubscribe(personPropertiesDataManager + .getEventFilterForPersonPropertyUpdateEvent(PersonProperty.DISEASE_STATE)); + } + } + } + + /* end */ + /* start code_ref=materials_plugin_vaccinator_producer_resource_update|code_cap=When a resource change occurs on a materials producer, the vaccinator determines if the change represents doses of vaccine and whether there is any remaining demand. */ + private void handleMaterialsProducerResourceUpdateEvent(final ActorContext actorContext, + final MaterialsProducerResourceUpdateEvent materialsProducerResourceUpdateEvent) { + if (isCapturableResource(materialsProducerResourceUpdateEvent)) { + + final MaterialsProducerId materialsProducerId = materialsProducerResourceUpdateEvent.materialsProducerId(); + + final long resourceLevel = materialsDataManager.getMaterialsProducerResourceLevel(materialsProducerId, + Resource.VACCINE); + final List regionIds = new ArrayList<>(regionsDataManager.getRegionIds()); + + final long resourceToTransfer = resourceLevel / regionIds.size(); + long remainderResource = resourceLevel % regionIds.size(); + + for (final RegionId regionId : regionIds) { + final MutableLong availableVaccine = availableVaccines.get(regionId); + if (remainderResource > 0) { + materialsDataManager.transferResourceToRegion(materialsProducerId, Resource.VACCINE, regionId, + resourceToTransfer + 1); + remainderResource--; + availableVaccine.increment(resourceToTransfer + 1); + } else { + materialsDataManager.transferResourceToRegion(materialsProducerId, Resource.VACCINE, regionId, + resourceToTransfer); + availableVaccine.increment(resourceToTransfer); + } + } + scheduleVaccinations(); + } + } + + /* end */ + /* start code_ref=materials_plugin_vaccinator_person_property_update|code_cap=If a person become infectious, the vaccinator reviews whether to start vaccine manufacture.*/ + private void handlePersonPropertyUpdateEvent(final ActorContext actorContext, + final PersonPropertyUpdateEvent personPropertyUpdateEvent) { + + final DiseaseState diseaseState = (DiseaseState) personPropertyUpdateEvent.getCurrentPropertyValue(); + if (diseaseState == DiseaseState.INFECTIOUS) { + infectedPersonCount++; + determineVaccineManufacutureStart(); + } + } + + /* end */ + /* start code_ref=materials_plugin_vaccinator_init |code_cap=The vaccinator initializes by subscribing to changes in materials producer resource levels so that it can distribute vaccines to regions. It also subscribes to changes in person disease state to select people for vaccination.*/ + public void init(final ActorContext actorContext) { + this.actorContext = actorContext; + actorContext.addActor(new VaccineProducer(MaterialsProducer.VACCINE_PRODUCER)::init); + actorContext.addActor(new AntigenProducer(MaterialsProducer.ANTIGEN_PRODUCER)::init); + + globalPropertiesDataManager = actorContext.getDataManager(GlobalPropertiesDataManager.class); + peopleDataManager = actorContext.getDataManager(PeopleDataManager.class); + personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class); + resourcesDataManager = actorContext.getDataManager(ResourcesDataManager.class); + regionsDataManager = actorContext.getDataManager(RegionsDataManager.class); + materialsDataManager = actorContext.getDataManager(MaterialsDataManager.class); + actorContext.subscribe(materialsDataManager.getEventFilterForMaterialsProducerResourceUpdateEvent(), + this::handleMaterialsProducerResourceUpdateEvent); + + for (final RegionId regionId : regionsDataManager.getRegionIds()) { + vaccinationSchedules.put(regionId, new MutableDouble()); + availableVaccines.put(regionId, new MutableLong()); + } + + actorContext.subscribe( + personPropertiesDataManager.getEventFilterForPersonPropertyUpdateEvent(PersonProperty.DISEASE_STATE), + this::handlePersonPropertyUpdateEvent); + + final double infectionThreshold = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.INFECTION_THRESHOLD); + infectedPersonCount = personPropertiesDataManager + .getPeopleWithPropertyValue(PersonProperty.DISEASE_STATE, DiseaseState.INFECTIOUS).size(); + infectionPersonCountThreshold = (int) (peopleDataManager.getPopulationCount() * infectionThreshold); + determineVaccineManufacutureStart(); + scheduleVaccinations(); + } + + /* end */ + /* start code_ref=materials_plugin_vaccinator_producer_capturable_resource|code_cap= When a materials producer updates its resource level, the vaccinator confirms that the resource is vaccine doses that have been added to the producer's inventory and that there is current demand for the vaccine. */ + private boolean isCapturableResource( + final MaterialsProducerResourceUpdateEvent materialsProducerResourceUpdateEvent) { + if (!materialsProducerResourceUpdateEvent.resourceId().equals(Resource.VACCINE)) { + return false; + } + final boolean isResourceAdditionToProducer = materialsProducerResourceUpdateEvent + .currentResourceLevel() > materialsProducerResourceUpdateEvent.previousResourceLevel(); + if (!isResourceAdditionToProducer) { + return false; + } + + long distributedVaccineCount = personPropertiesDataManager + .getPersonCountForPropertyValue(PersonProperty.VACCINATED, true); + + for (final RegionId regionId : regionsDataManager.getRegionIds()) { + distributedVaccineCount += resourcesDataManager.getRegionResourceLevel(regionId, Resource.VACCINE); + } + if (distributedVaccineCount >= peopleDataManager.getPopulationCount()) { + return false; + } + return true; + } + + /* end */ + /* start code_ref=materials_plugin_vaccinator_producer_schedule_vaccinations|code_cap=The vaccinator schedules vaccinations at initialization and whenever a materials producer produces resources. The vaccinator tries to distribute the available doses of vaccine with a standard delay time between scheduled vaccinations. */ + private void scheduleVaccinations() { + final double delayTime = 1 / (double) vaccinationsPerRegionPerDay; + + for (final RegionId regionId : vaccinationSchedules.keySet()) { + final MutableLong availableVaccine = availableVaccines.get(regionId); + final MutableDouble vaccineTime = vaccinationSchedules.get(regionId); + vaccineTime.increment(delayTime); + if (vaccineTime.getValue() < actorContext.getTime()) { + vaccineTime.setValue(actorContext.getTime()); + } + final List peopleInRegion = regionsDataManager.getPeopleInRegion(regionId); + for (final PersonId personId : peopleInRegion) { + final boolean vaccine_scheduled = personPropertiesDataManager.getPersonPropertyValue(personId, + PersonProperty.VACCINE_SCHEDULED); + if (availableVaccine.getValue() <= 0) { + break; + } + if (!vaccine_scheduled) { + availableVaccine.decrement(); + personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.VACCINE_SCHEDULED, + true); + actorContext.addPlan((c) -> vaccinatePerson(personId), vaccineTime.getValue()); + vaccineTime.increment(delayTime); + } + } + } + final int populationSize = peopleDataManager.getPopulationCount(); + final int scheduledVaccinationCount = personPropertiesDataManager + .getPersonCountForPropertyValue(PersonProperty.VACCINE_SCHEDULED, true); + if (scheduledVaccinationCount >= populationSize) { + globalPropertiesDataManager.setGlobalPropertyValue(GlobalProperty.MANUFACTURE_VACCINE, false); + } + } + /* end */ + + /* start code_ref=materials_plugin_vaccinator_producer_vaccinate_person|code_cap=The vaccinator sets the person's VACCINATED property to true and moves one unit of vaccine from the person's region to the person. */ + private void vaccinatePerson(final PersonId personId) { + personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.VACCINATED, true); + resourcesDataManager.transferResourceToPersonFromRegion(Resource.VACCINE, personId, 1L); + } + /* end */ + +} diff --git a/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/VaccineProducer.java b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/VaccineProducer.java new file mode 100644 index 000000000..bc1d10354 --- /dev/null +++ b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/VaccineProducer.java @@ -0,0 +1,285 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.math3.util.FastMath; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.GlobalProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.Material; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.MaterialManufactureSpecification; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.Resource; +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.materials.datamangers.MaterialsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.StageOfferUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchConstructionInfo; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.StageId; + +public final class VaccineProducer { + + private ActorContext actorContext; + + private final MaterialsProducerId materialsProducerId; + + private MaterialsDataManager materialsDataManager; + + private GlobalPropertiesDataManager globalPropertiesDataManager; + + private final Map materialRecs = new LinkedHashMap<>(); + + private final int stageCapacity = 15; + + private final double vaccinePreparationTime = 2.0; + + private final long vaccineUnits = 50; + + private final double batchAssemblyDuration = 0.1; + + private double lastBatchAssemblyEndTime; + + private BatchId antigenBatchId; + + private final double antigenAmountPerBatch = 50; + + private final long vaccineCapacity = 100; + + public VaccineProducer(final MaterialsProducerId materialsProducerId) { + this.materialsProducerId = materialsProducerId; + } + + private void addMaterialRec(final MaterialId materialId, final MaterialManufactureSpecification.Builder builder) { + final BatchConstructionInfo batchConstructionInfo = BatchConstructionInfo.builder()// + .setMaterialId(materialId)// + .setMaterialsProducerId(materialsProducerId)// + .build();// + final BatchId batchId = materialsDataManager.addBatch(batchConstructionInfo); + builder.setBatchId(batchId); + builder.setMaterialId(materialId); + final MaterialManufactureSpecification materialManufactureSpecification = builder.build(); + materialRecs.put(materialId, materialManufactureSpecification); + } + + private void captureStage(final StageId stageId) { + + materialsDataManager.transferOfferedStage(stageId, materialsProducerId); + final List batches = materialsDataManager.getStageBatches(stageId); + for (final BatchId batchId : batches) { + final MaterialId material = materialsDataManager.getBatchMaterial(batchId); + if (material.equals(Material.ANTIGEN)) { + final double amount = materialsDataManager.getBatchAmount(batchId); + materialsDataManager.transferMaterialBetweenBatches(batchId, antigenBatchId, amount); + } + } + materialsDataManager.removeStage(stageId, true); + } + + /* start code_ref=materials_plugin_vaccine_producer_end_vaccine_preparation|code_cap=When a vaccine production stage is ready for release, the vaccine producer converts the stage doses of vaccine and places them in its resource inventory.*/ + private void endVaccinePreparation(final StageId stageId) { + materialsDataManager.convertStageToResource(stageId, Resource.VACCINE, vaccineUnits); + planVaccinePrepartion(); + } + /* end */ + + private void handleStageOfferUpdateEvent(final ActorContext actorContext, + final StageOfferUpdateEvent stageOfferUpdateEvent) { + if (isCapturableStage(stageOfferUpdateEvent)) { + captureStage(stageOfferUpdateEvent.stageId()); + planVaccinePrepartion(); + } + } + + private boolean hasSufficientMaterialsForNewStage() { + for (final MaterialId materialId : materialRecs.keySet()) { + final MaterialManufactureSpecification materialManufactureSpecification = materialRecs.get(materialId); + final double batchAmount = materialsDataManager + .getBatchAmount(materialManufactureSpecification.getBatchId()); + if (batchAmount < materialManufactureSpecification.getStageAmount()) { + return false; + } + } + + return materialsDataManager.getBatchAmount(antigenBatchId) >= antigenAmountPerBatch; + + } + + /* start code_ref=materials_plugin_vaccine_producer_init|code_cap=The vaccine producer initializes by subscribing to offered stages(from the antigen producer) and subscribing to the start of vaccine manufacture.*/ + public void init(final ActorContext actorContext) { + this.actorContext = actorContext; + materialsDataManager = actorContext.getDataManager(MaterialsDataManager.class); + globalPropertiesDataManager = actorContext.getDataManager(GlobalPropertiesDataManager.class); + + final BatchConstructionInfo batchConstructionInfo = // + BatchConstructionInfo.builder()// + .setMaterialId(Material.ANTIGEN)// + .setMaterialsProducerId(materialsProducerId)// + .build();// + antigenBatchId = materialsDataManager.addBatch(batchConstructionInfo); + + addMaterialRec(Material.ADJUVANT, MaterialManufactureSpecification.builder()// + .setDeliveryAmount(150.0)// + .setDeliveryDelay(3.0)// + .setStageAmount(2.7));// + + addMaterialRec(Material.PRESERVATIVE, MaterialManufactureSpecification.builder()// + .setDeliveryAmount(1000.0)// + .setDeliveryDelay(14.0)// + .setStageAmount(3.0));// + + addMaterialRec(Material.STABILIZER, MaterialManufactureSpecification.builder()// + .setDeliveryAmount(100.0)// + .setDeliveryDelay(14.0)// + .setStageAmount(1.0));// + + actorContext.subscribe(materialsDataManager.getEventFilterForStageOfferUpdateEvent(), + this::handleStageOfferUpdateEvent); + actorContext.subscribe(globalPropertiesDataManager.getEventFilterForGlobalPropertyUpdateEvent( + GlobalProperty.MANUFACTURE_VACCINE), (c, e) -> planVaccinePrepartion()); + + planVaccinePrepartion(); + } + /* end */ + + private boolean isCapturableStage(final StageOfferUpdateEvent stageOfferUpdateEvent) { + // the stage must be offered + if (!stageOfferUpdateEvent.currentOfferState()) { + return false; + } + + // the stage must be from a materials producer not managed by this actor + final StageId stageId = stageOfferUpdateEvent.stageId(); + final MaterialsProducerId producerId = materialsDataManager.getStageProducer(stageId); + if (materialsProducerId.equals(producerId)) { + return false; + } + + // the stage must contain antigen + final List batches = materialsDataManager.getStageBatches(stageId); + double antigenLevel = 0; + for (final BatchId batchId : batches) { + final MaterialId material = materialsDataManager.getBatchMaterial(batchId); + if (material.equals(Material.ANTIGEN)) { + antigenLevel += materialsDataManager.getBatchAmount(batchId); + } + } + if (antigenLevel == 0) { + return false; + } + + // there must be room for new stages + final List stages = materialsDataManager.getStages(materialsProducerId); + if (stages.size() >= stageCapacity) { + return false; + } + + // there must be a need for more antigen to reach stage capacity + double requiredAmount = stageCapacity - stages.size(); + requiredAmount *= antigenAmountPerBatch; + final double currentAmount = materialsDataManager.getBatchAmount(antigenBatchId); + requiredAmount -= currentAmount; + if (requiredAmount <= 0) { + return false; + } + + return true; + + } + + private void orderMaterial(final MaterialId materialId) { + final MaterialManufactureSpecification materialRec = materialRecs.get(materialId); + if (materialRec.isOnOrder()) { + return; + } + + double requiredAmount = materialRec.getStageAmount() * stageCapacity; + requiredAmount /= batchAssemblyDuration; + requiredAmount *= materialRec.getDeliveryDelay(); + + final List batches = materialsDataManager.getInventoryBatchesByMaterialId(materialsProducerId, + materialId); + double currentAmount = 0; + for (final BatchId batchId : batches) { + currentAmount += materialsDataManager.getBatchAmount(batchId); + } + double amountToOrder = requiredAmount - currentAmount; + if (amountToOrder <= 0) { + return; + } + + amountToOrder = FastMath.ceil(amountToOrder / materialRec.getDeliveryAmount()) + * materialRec.getDeliveryAmount(); + final double deliveryTime = materialRec.getDeliveryDelay() + actorContext.getTime(); + final double amount = amountToOrder; + materialRec.toggleOnOrder(); + + actorContext.addPlan((c) -> receiveMaterial(materialId, amount), deliveryTime); + } + + private void orderMaterials() { + for (final MaterialId materialId : materialRecs.keySet()) { + orderMaterial(materialId); + } + } + + /* start code_ref=materials_plugin_vaccine_producer_plan_vaccine_preparation|code_cap=Responding to events that may allow for additional manufacture of vaccine doses, the vaccine producer attempts to continue manufacturing.*/ + private void planVaccinePrepartion() { + final Boolean continueManufature = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.MANUFACTURE_VACCINE); + if (!continueManufature) { + return; + } + orderMaterials(); + + while (!stagesAtCapacity() && hasSufficientMaterialsForNewStage() && vaccineLevelBelowCapacity()) { + final StageId stageId = materialsDataManager.addStage(materialsProducerId); + for (final MaterialId materialId : materialRecs.keySet()) { + final MaterialManufactureSpecification materialManufactureSpecification = materialRecs.get(materialId); + final BatchId newBatchId = materialsDataManager.addBatch(BatchConstructionInfo.builder() + .setMaterialsProducerId(materialsProducerId).setMaterialId(materialId).build()); + materialsDataManager.transferMaterialBetweenBatches(materialManufactureSpecification.getBatchId(), + newBatchId, materialManufactureSpecification.getStageAmount()); + materialsDataManager.moveBatchToStage(newBatchId, stageId); + } + + BatchId newBatchId = materialsDataManager.addBatch(BatchConstructionInfo.builder() + .setMaterialsProducerId(materialsProducerId).setMaterialId(Material.ANTIGEN).build()); + materialsDataManager.transferMaterialBetweenBatches(antigenBatchId, newBatchId, antigenAmountPerBatch); + materialsDataManager.moveBatchToStage(newBatchId, stageId); + + final double batchAssemblyStartTime = FastMath.max(actorContext.getTime(), lastBatchAssemblyEndTime); + final double fermentationStartTime = batchAssemblyStartTime + batchAssemblyDuration; + lastBatchAssemblyEndTime = fermentationStartTime; + final double planTime = fermentationStartTime + vaccinePreparationTime; + actorContext.addPlan((c) -> endVaccinePreparation(stageId), planTime); + } + } + /* end */ + + private void receiveMaterial(final MaterialId materialId, final double amount) { + final MaterialManufactureSpecification materialRec = materialRecs.get(materialId); + materialRec.toggleOnOrder(); + + final BatchId newBatchId = materialsDataManager.addBatch(BatchConstructionInfo.builder() + .setMaterialsProducerId(materialsProducerId).setMaterialId(materialId).setAmount(amount).build()); + materialsDataManager.transferMaterialBetweenBatches(newBatchId, materialRec.getBatchId(), amount); + materialsDataManager.removeBatch(newBatchId); + + planVaccinePrepartion(); + } + + private boolean stagesAtCapacity() { + final List stages = materialsDataManager.getStages(materialsProducerId); + return stages.size() >= stageCapacity; + } + + private boolean vaccineLevelBelowCapacity() { + final long vaccineCount = materialsDataManager.getMaterialsProducerResourceLevel(materialsProducerId, + Resource.VACCINE); + return vaccineCount <= vaccineCapacity; + } + +} diff --git a/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/reports/DiseaseStateReport.java b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/reports/DiseaseStateReport.java new file mode 100644 index 000000000..318ab128c --- /dev/null +++ b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/reports/DiseaseStateReport.java @@ -0,0 +1,65 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.reports; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.DiseaseState; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.PersonProperty; +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.PeriodicReport; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; + +/** + * A report that groups people at the end of the simulation by their shared + * person property values. + * + * + */ +public final class DiseaseStateReport extends PeriodicReport { + + private ReportHeader reportHeader; + + public DiseaseStateReport(final ReportLabel reportLabel, final ReportPeriod reportPeriod) { + super(reportLabel, reportPeriod); + } + + @Override + protected void flush(final ReportContext reportContext) { + final ReportItem.Builder reportItemBuilder = ReportItem.builder(); + reportItemBuilder.setReportLabel(getReportLabel()); + reportItemBuilder.setReportHeader(getReportHeader()); + fillTimeFields(reportItemBuilder); + reportItemBuilder.addValue(reportContext.getTime()); + + final PersonPropertiesDataManager personPropertiesDataManager = reportContext + .getDataManager(PersonPropertiesDataManager.class); + int vaccinatedCount = personPropertiesDataManager.getPersonCountForPropertyValue(PersonProperty.VACCINATED, + true); + reportItemBuilder.addValue(vaccinatedCount); + for (final DiseaseState diseaseState : DiseaseState.values()) { + final int count = personPropertiesDataManager.getPersonCountForPropertyValue(PersonProperty.DISEASE_STATE, + diseaseState); + reportItemBuilder.addValue(count); + } + + final ReportItem reportItem = reportItemBuilder.build(); + reportContext.releaseOutput(reportItem); + + } + + private ReportHeader getReportHeader() { + if (reportHeader == null) { + final ReportHeader.Builder reportHeaderBuilder = ReportHeader.builder(); + addTimeFieldHeaders(reportHeaderBuilder);// + reportHeaderBuilder.add("day"); + reportHeaderBuilder.add("vaccinated"); + for (final DiseaseState diseaseState : DiseaseState.values()) { + reportHeaderBuilder.add(diseaseState.toString().toLowerCase()); + } + reportHeader = reportHeaderBuilder.build(); + } + return reportHeader; + } + +} diff --git a/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/reports/VaccineProductionReport.java b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/reports/VaccineProductionReport.java new file mode 100644 index 000000000..6f5764bd6 --- /dev/null +++ b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/reports/VaccineProductionReport.java @@ -0,0 +1,195 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.reports; + +import java.util.List; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.Material; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.MaterialsProducer; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.Resource; +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.plugins.materials.datamangers.MaterialsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.materials.events.StageOfferUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.StageId; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.PeriodicReport; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; +import gov.hhs.aspr.ms.gcm.plugins.resources.datamanagers.ResourcesDataManager; + +public final class VaccineProductionReport extends PeriodicReport { + + private ReportHeader reportHeader; + private MaterialsDataManager materialsDataManager; + private RegionsDataManager regionsDataManager; + private ResourcesDataManager resourcesDataManager; + + public VaccineProductionReport(final ReportLabel reportLabel, final ReportPeriod reportPeriod) { + super(reportLabel, reportPeriod); + + } + + private static enum MaterialMode { + INV, STAGED + } + + private String getMaterialHeader(MaterialsProducerId materialsProducerId, MaterialId materialId, + MaterialMode materialMode) { + String result = materialsProducerId.toString() + "_" + materialId.toString() + "_" + materialMode; + return result.toLowerCase(); + } + + @Override + protected void prepare(ReportContext reportContext) { + + materialsDataManager = reportContext.getDataManager(MaterialsDataManager.class); + resourcesDataManager = reportContext.getDataManager(ResourcesDataManager.class); + regionsDataManager = reportContext.getDataManager(RegionsDataManager.class); + + final ReportHeader.Builder reportHeaderBuilder = ReportHeader.builder(); + addTimeFieldHeaders(reportHeaderBuilder);// + + reportHeaderBuilder + .add(getMaterialHeader(MaterialsProducer.ANTIGEN_PRODUCER, Material.VIRUS, MaterialMode.INV)); + reportHeaderBuilder + .add(getMaterialHeader(MaterialsProducer.ANTIGEN_PRODUCER, Material.GROWTH_MEDIUM, MaterialMode.INV)); + + reportHeaderBuilder + .add(getMaterialHeader(MaterialsProducer.ANTIGEN_PRODUCER, Material.VIRUS, MaterialMode.STAGED)); + reportHeaderBuilder.add( + getMaterialHeader(MaterialsProducer.ANTIGEN_PRODUCER, Material.GROWTH_MEDIUM, MaterialMode.STAGED)); + reportHeaderBuilder + .add(getMaterialHeader(MaterialsProducer.ANTIGEN_PRODUCER, Material.ANTIGEN, MaterialMode.STAGED)); + + reportHeaderBuilder.add("antigen production"); + + reportHeaderBuilder + .add(getMaterialHeader(MaterialsProducer.VACCINE_PRODUCER, Material.ANTIGEN, MaterialMode.INV)); + reportHeaderBuilder + .add(getMaterialHeader(MaterialsProducer.VACCINE_PRODUCER, Material.ADJUVANT, MaterialMode.INV)); + reportHeaderBuilder + .add(getMaterialHeader(MaterialsProducer.VACCINE_PRODUCER, Material.PRESERVATIVE, MaterialMode.INV)); + reportHeaderBuilder + .add(getMaterialHeader(MaterialsProducer.VACCINE_PRODUCER, Material.STABILIZER, MaterialMode.INV)); + + reportHeaderBuilder + .add(getMaterialHeader(MaterialsProducer.VACCINE_PRODUCER, Material.ANTIGEN, MaterialMode.STAGED)); + reportHeaderBuilder + .add(getMaterialHeader(MaterialsProducer.VACCINE_PRODUCER, Material.ADJUVANT, MaterialMode.STAGED)); + reportHeaderBuilder + .add(getMaterialHeader(MaterialsProducer.VACCINE_PRODUCER, Material.PRESERVATIVE, MaterialMode.STAGED)); + reportHeaderBuilder + .add(getMaterialHeader(MaterialsProducer.VACCINE_PRODUCER, Material.STABILIZER, MaterialMode.STAGED)); + + String header = MaterialsProducer.VACCINE_PRODUCER + "_" + "vaccine"; + header = header.toLowerCase(); + reportHeaderBuilder.add(header); + + for (RegionId regionId : regionsDataManager.getRegionIds()) { + header = regionId + "_" + "vaccine"; + header = header.toLowerCase(); + reportHeaderBuilder.add(header); + } + header = "vaccine_people"; + reportHeaderBuilder.add(header); + + reportHeader = reportHeaderBuilder.build(); + + reportContext.subscribe(StageOfferUpdateEvent.class, this::handleStageOfferUpdateEvent); + + } + + private double periodAntigenProduction; + + private void handleStageOfferUpdateEvent(ReportContext reportContext, StageOfferUpdateEvent stageOfferUpdateEvent) { + if (stageOfferUpdateEvent.currentOfferState()) { + StageId stageId = stageOfferUpdateEvent.stageId(); + MaterialsProducerId materialsProducerId = materialsDataManager.getStageProducer(stageId); + if (materialsProducerId.equals(MaterialsProducer.ANTIGEN_PRODUCER)) { + for (BatchId batchId : materialsDataManager.getStageBatches(stageId)) { + MaterialId batchMaterial = materialsDataManager.getBatchMaterial(batchId); + if (batchMaterial.equals(Material.ANTIGEN)) { + double batchAmount = materialsDataManager.getBatchAmount(batchId); + periodAntigenProduction += batchAmount; + } + } + } + } + } + + private double getMaterialInventory(MaterialsProducerId materialsProducerId, MaterialId materialId) { + List batches = materialsDataManager.getInventoryBatchesByMaterialId(materialsProducerId, materialId); + double result = 0; + for (BatchId batchId : batches) { + result += materialsDataManager.getBatchAmount(batchId); + } + return result; + } + + private double getStagedMaterialInventory(MaterialsProducerId materialsProducerId, MaterialId materialId) { + List stages = materialsDataManager.getStages(materialsProducerId); + + double result = 0; + for (StageId stageId : stages) { + List batches = materialsDataManager.getStageBatchesByMaterialId(stageId, materialId); + for (BatchId batchId : batches) { + result += materialsDataManager.getBatchAmount(batchId); + } + } + return result; + } + + private long getVaccineCountDistributedToPeople() { + long result = 0; + for (PersonId personId : resourcesDataManager.getPeopleWithResource(Resource.VACCINE)) { + result += resourcesDataManager.getPersonResourceLevel(Resource.VACCINE, personId); + } + return result; + } + + @Override + protected void flush(final ReportContext reportContext) { + final ReportItem.Builder reportItemBuilder = ReportItem.builder(); + reportItemBuilder.setReportLabel(getReportLabel()); + reportItemBuilder.setReportHeader(reportHeader); + fillTimeFields(reportItemBuilder); + + reportItemBuilder.addValue(getMaterialInventory(MaterialsProducer.ANTIGEN_PRODUCER, Material.VIRUS)); + reportItemBuilder.addValue(getMaterialInventory(MaterialsProducer.ANTIGEN_PRODUCER, Material.GROWTH_MEDIUM)); + + reportItemBuilder.addValue(getStagedMaterialInventory(MaterialsProducer.ANTIGEN_PRODUCER, Material.VIRUS)); + reportItemBuilder + .addValue(getStagedMaterialInventory(MaterialsProducer.ANTIGEN_PRODUCER, Material.GROWTH_MEDIUM)); + reportItemBuilder.addValue(getStagedMaterialInventory(MaterialsProducer.ANTIGEN_PRODUCER, Material.ANTIGEN)); + reportItemBuilder.addValue(periodAntigenProduction); + periodAntigenProduction = 0; + + reportItemBuilder.addValue(getMaterialInventory(MaterialsProducer.VACCINE_PRODUCER, Material.ANTIGEN)); + reportItemBuilder.addValue(getMaterialInventory(MaterialsProducer.VACCINE_PRODUCER, Material.ADJUVANT)); + reportItemBuilder.addValue(getMaterialInventory(MaterialsProducer.VACCINE_PRODUCER, Material.PRESERVATIVE)); + reportItemBuilder.addValue(getMaterialInventory(MaterialsProducer.VACCINE_PRODUCER, Material.STABILIZER)); + + reportItemBuilder.addValue(getStagedMaterialInventory(MaterialsProducer.VACCINE_PRODUCER, Material.ANTIGEN)); + reportItemBuilder.addValue(getStagedMaterialInventory(MaterialsProducer.VACCINE_PRODUCER, Material.ADJUVANT)); + reportItemBuilder + .addValue(getStagedMaterialInventory(MaterialsProducer.VACCINE_PRODUCER, Material.PRESERVATIVE)); + reportItemBuilder.addValue(getStagedMaterialInventory(MaterialsProducer.VACCINE_PRODUCER, Material.STABILIZER)); + + reportItemBuilder.addValue(materialsDataManager + .getMaterialsProducerResourceLevel(MaterialsProducer.VACCINE_PRODUCER, Resource.VACCINE)); + + for (RegionId regionId : regionsDataManager.getRegionIds()) { + reportItemBuilder.addValue(resourcesDataManager.getRegionResourceLevel(regionId, Resource.VACCINE)); + } + reportItemBuilder.addValue(getVaccineCountDistributedToPeople()); + + final ReportItem reportItem = reportItemBuilder.build(); + reportContext.releaseOutput(reportItem); + } + +} diff --git a/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/reports/VaccineReport.java b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/reports/VaccineReport.java new file mode 100644 index 000000000..6cf4904c6 --- /dev/null +++ b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/reports/VaccineReport.java @@ -0,0 +1,51 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.reports; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.PersonProperty; +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.PeriodicReport; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportPeriod; + +public final class VaccineReport extends PeriodicReport { + + private ReportHeader reportHeader; + + public VaccineReport(final ReportLabel reportLabel, final ReportPeriod reportPeriod) { + super(reportLabel, reportPeriod); + } + + @Override + protected void flush(final ReportContext reportContext) { + final ReportItem.Builder reportItemBuilder = ReportItem.builder(); + reportItemBuilder.setReportLabel(getReportLabel()); + reportItemBuilder.setReportHeader(getReportHeader()); + fillTimeFields(reportItemBuilder); + + final PersonPropertiesDataManager personPropertiesDataManager = reportContext + .getDataManager(PersonPropertiesDataManager.class); + int vaccinatedCount = personPropertiesDataManager.getPersonCountForPropertyValue(PersonProperty.VACCINATED, + true); + reportItemBuilder.addValue(vaccinatedCount); + int vaccineScheduledCount = personPropertiesDataManager + .getPersonCountForPropertyValue(PersonProperty.VACCINE_SCHEDULED, true); + reportItemBuilder.addValue(vaccineScheduledCount); + + final ReportItem reportItem = reportItemBuilder.build(); + reportContext.releaseOutput(reportItem); + } + + private ReportHeader getReportHeader() { + if (reportHeader == null) { + final ReportHeader.Builder reportHeaderBuilder = ReportHeader.builder(); + addTimeFieldHeaders(reportHeaderBuilder);// + reportHeaderBuilder.add("vaccine_scheduled"); + reportHeaderBuilder.add("vaccinated"); + reportHeader = reportHeaderBuilder.build(); + } + return reportHeader; + } + +} diff --git a/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/DiseaseState.java b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/DiseaseState.java new file mode 100644 index 000000000..8dee0da9a --- /dev/null +++ b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/DiseaseState.java @@ -0,0 +1,8 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.support; + +public enum DiseaseState { + IMMUNE, // the person is immune + SUSCEPTIBLE, INFECTIOUS, // the person is infected + RECOVERED, + +} diff --git a/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/GlobalProperty.java b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/GlobalProperty.java new file mode 100644 index 000000000..17c1893e1 --- /dev/null +++ b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/GlobalProperty.java @@ -0,0 +1,25 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.support; + +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertyId; + +public enum GlobalProperty implements GlobalPropertyId { + + SUSCEPTIBLE_POPULATION_PROPORTION, // the fraction of the population that is susceptible + INITIAL_INFECTIONS, // the number of adults initially infected + MIN_INFECTIOUS_PERIOD, // the minimum number of days a person is infectious + MAX_INFECTIOUS_PERIOD, // the maximum number of days a person is infectious + POPULATION_SIZE, // the initial size of the population + CHILD_POPULATION_PROPORTION, // the fraction of the population between the ages of 0 and 18, inclusive + SENIOR_POPULATION_PROPORTION, // the fraction of the population 65 and older + R0, // the expected number of people a single person will infect if all contacts are + // susceptible and transmission success is 100% + AVERAGE_HOME_SIZE, // the average number of people per household + AVERAGE_SCHOOL_SIZE, // the average number of student in a school + AVERAGE_WORK_SIZE, // the average number of people per work place + COMMUNITY_CONTACT_RATE, // the proportion of contacts that will be randomly chosen from the entire + // population + MANUFACTURE_VACCINE, // Boolean that triggers vaccine manufacture + INFECTION_THRESHOLD,// The fraction of the population that infected in order to start vaccine + // manufacture + ; +} diff --git a/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/GroupType.java b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/GroupType.java new file mode 100644 index 000000000..0f474af70 --- /dev/null +++ b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/GroupType.java @@ -0,0 +1,7 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.support; + +import gov.hhs.aspr.ms.gcm.plugins.groups.support.GroupTypeId; + +public enum GroupType implements GroupTypeId { + HOME, SCHOOL, WORK; +} diff --git a/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/Material.java b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/Material.java new file mode 100644 index 000000000..e0bf13dfe --- /dev/null +++ b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/Material.java @@ -0,0 +1,15 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.support; + +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialId; + +/* start code_ref=materials_plugin_material_ids|code_cap=The material ids are implemented via an enumeration. */ +public enum Material implements MaterialId { + + VIRUS, // + GROWTH_MEDIUM, // + ANTIGEN, // + ADJUVANT, // + PRESERVATIVE, // + STABILIZER;// +} +/* end */ \ No newline at end of file diff --git a/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/MaterialManufactureSpecification.java b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/MaterialManufactureSpecification.java new file mode 100644 index 000000000..d3cb73865 --- /dev/null +++ b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/MaterialManufactureSpecification.java @@ -0,0 +1,106 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.support; + +import gov.hhs.aspr.ms.gcm.plugins.materials.support.BatchId; +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialId; + +public final class MaterialManufactureSpecification { + + public static class Builder { + private Data data = new Data(); + + private Builder() { + + } + + public MaterialManufactureSpecification build() { + return new MaterialManufactureSpecification(new Data(data)); + } + + public Builder setBatchId(final BatchId batchId) { + data.batchId = batchId; + return this; + } + + public Builder setDeliveryAmount(final double deliveryAmount) { + data.deliveryAmount = deliveryAmount; + return this; + } + + public Builder setDeliveryDelay(final double deliveryDelay) { + data.deliveryDelay = deliveryDelay; + return this; + } + + public Builder setMaterialId(final MaterialId materialId) { + data.materialId = materialId; + return this; + } + + public Builder setStageAmount(final double stageAmount) { + data.stageAmount = stageAmount; + return this; + } + + } + + private static class Data { + + private MaterialId materialId; + private boolean onOrder; + private double deliveryAmount; + private double deliveryDelay; + private double stageAmount; + private BatchId batchId; + + public Data() { + } + + public Data(Data data) { + materialId = data.materialId; + onOrder = data.onOrder; + deliveryAmount = data.deliveryAmount; + deliveryDelay = data.deliveryDelay; + stageAmount = data.stageAmount; + batchId = data.batchId; + } + } + + public static Builder builder() { + return new Builder(); + } + + private final Data data; + + private MaterialManufactureSpecification(final Data data) { + this.data = data; + } + + public BatchId getBatchId() { + return data.batchId; + } + + public double getDeliveryAmount() { + return data.deliveryAmount; + } + + public double getDeliveryDelay() { + return data.deliveryDelay; + } + + public MaterialId getMaterialId() { + return data.materialId; + } + + public double getStageAmount() { + return data.stageAmount; + } + + public boolean isOnOrder() { + return data.onOrder; + } + + public void toggleOnOrder() { + data.onOrder = !data.onOrder; + } + +} \ No newline at end of file diff --git a/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/MaterialsProducer.java b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/MaterialsProducer.java new file mode 100644 index 000000000..b490996d5 --- /dev/null +++ b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/MaterialsProducer.java @@ -0,0 +1,12 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.support; + +import gov.hhs.aspr.ms.gcm.plugins.materials.support.MaterialsProducerId; + +/* start code_ref=materials_plugin_materials_producer_ids|code_cap=The ids of the materials producers are implemented via an enumeration.*/ +public enum MaterialsProducer implements MaterialsProducerId { + + VACCINE_PRODUCER, // + ANTIGEN_PRODUCER; + +} +/* end */ \ No newline at end of file diff --git a/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/ModelError.java b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/ModelError.java new file mode 100644 index 000000000..a94556bed --- /dev/null +++ b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/ModelError.java @@ -0,0 +1,26 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.support; + +import util.errors.ContractError; +import util.errors.ContractException; + +/** + * An enumeration supporting {@link ContractException} that acts as a general + * description of the exception. + * + * + */ +public enum ModelError implements ContractError { + + NEGATIVE_REGION_ID("Negative region id"),; + + private final String description; + + private ModelError(final String description) { + this.description = description; + } + + @Override + public String getDescription() { + return description; + } +} diff --git a/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/ModelReportLabel.java b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/ModelReportLabel.java new file mode 100644 index 000000000..c4ccd4222 --- /dev/null +++ b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/ModelReportLabel.java @@ -0,0 +1,13 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.support; + +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; + +public enum ModelReportLabel implements ReportLabel { + + DISEASE_STATE_REPORT, // + PERSON_PROPERTY_REPORT, // + VACCINE_REPORT, // + VACCINE_PRODUCTION_REPORT,// + + ; +} diff --git a/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/PersonProperty.java b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/PersonProperty.java new file mode 100644 index 000000000..cac618e31 --- /dev/null +++ b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/PersonProperty.java @@ -0,0 +1,12 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.support; + +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyId; + +public enum PersonProperty implements PersonPropertyId { + AGE, // the integer age of a person + VACCINATED, // boolean vaccination status + VACCINE_SCHEDULED, // boolean indicating whether a vaccination is scheduled for a person + DISEASE_STATE, // IMMUNE,SUSCEPTIBLE, INFECTIOUS or RECOVERED + ; + +} diff --git a/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/Region.java b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/Region.java new file mode 100644 index 000000000..4c6ce57e5 --- /dev/null +++ b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/Region.java @@ -0,0 +1,59 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.support; + +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; +import net.jcip.annotations.Immutable; +import util.errors.ContractException; + +/** + * Identifier for all regions + * + * + */ + +@Immutable +public final class Region implements RegionId { + + private final int id; + + /** + * Constructs the region + * + * @throws ContractException + *
          • {@linkplain ModelError#NEGATIVE_REGION_ID}
          • + */ + public Region(final int id) { + if (id < 0) { + throw new ContractException(ModelError.NEGATIVE_REGION_ID); + } + this.id = id; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Region)) { + return false; + } + final Region other = (Region) obj; + if (id != other.id) { + return false; + } + return true; + } + + public int getValue() { + return id; + } + + @Override + public int hashCode() { + return id; + } + + @Override + public String toString() { + return "Region_" + id; + } +} diff --git a/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/Resource.java b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/Resource.java new file mode 100644 index 000000000..a5322145a --- /dev/null +++ b/tutorials/lessons/lesson_19_materials_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/Resource.java @@ -0,0 +1,9 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.support; + +import gov.hhs.aspr.ms.gcm.plugins.resources.support.ResourceId; + +/* start code_ref=materials_plugin_resource_ids|code_cap=The resource ids are implemented via an enumeration.*/ +public enum Resource implements ResourceId { + VACCINE; +} +/* end */ \ No newline at end of file diff --git a/tutorials/lessons/lesson_20_partitions_plugin/pom.xml b/tutorials/lessons/lesson_20_partitions_plugin/pom.xml new file mode 100644 index 000000000..349cea96e --- /dev/null +++ b/tutorials/lessons/lesson_20_partitions_plugin/pom.xml @@ -0,0 +1,79 @@ + + 4.0.0 + + + ASPR + https://www.phe.gov + + + + gov.hhs.aspr.ms.gcm.tutorials + lesson_20_partitions_plugin + ${revision} + jar + gcm lesson 20 partitions plugin + Tutorial introducing the partitions plugin + + + + UTF-8 + 17 + 17 + ${gcm.version} + + 1.3.0 + + 4.0.0-SNAPSHOT + + + + + + gov.hhs.aspr.ms + gcm + ${gcm.version} + + + + + + Shawn Hatch + Leidos + https://www.leidos.com + + + + + + + org.codehaus.mojo + flatten-maven-plugin + ${flatten-maven-plugin.version} + + + + flatten + process-resources + + flatten + + + true + + + + + flatten.clean + clean + + clean + + + + + + + + diff --git a/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_20.java b/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_20.java new file mode 100644 index 000000000..b816c9a98 --- /dev/null +++ b/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/Example_20.java @@ -0,0 +1,208 @@ +package gov.hhs.aspr.ms.gcm.lessons; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.ModelPlugin; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.DiseaseState; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.GlobalProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.ModelReportLabel; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.PersonProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.Region; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.VaccinatorType; +import gov.hhs.aspr.ms.gcm.nucleus.Experiment; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.GlobalPropertiesPlugin; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.partitions.PartitionsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.partitions.datamanagers.PartitionsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.people.PeoplePlugin; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeoplePluginData; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.PersonPropertiesPlugin; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.PersonPropertiesPluginId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesPluginData; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesPluginData.Builder; +import gov.hhs.aspr.ms.gcm.plugins.regions.RegionsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.regions.datamanagers.RegionsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.NIOReportItemHandler; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.StochasticsPlugin; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsPluginData; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.WellState; +import gov.hhs.aspr.ms.gcm.plugins.util.properties.PropertyDefinition; + +public final class Example_20 { + + private final Path outputDirectory; + + public static void main(final String[] args) throws IOException { + if (args.length == 0) { + throw new RuntimeException("One output directory argument is required"); + } + Path outputDirectory = Paths.get(args[0]); + if (!Files.exists(outputDirectory)) { + Files.createDirectory(outputDirectory); + } else { + if (!Files.isDirectory(outputDirectory)) { + throw new IOException("Provided path is not a directory"); + } + } + + new Example_20(outputDirectory).execute(); + } + + private Example_20(Path outputDirectory) { + this.outputDirectory = outputDirectory; + } + + /* + * start code_ref=partitions_plugin_partiions_init|code_cap=The partitions + * plugin is simple, but requires that it has dependencies on those plugins that + * will be used to calculate partition filters and labelers. + */ + private Plugin getPartitionsPlugin() { + PartitionsPluginData partitionsPluginData = PartitionsPluginData.builder()// + .setRunContinuitySupport(false).build(); + + return PartitionsPlugin.builder()// + .setPartitionsPluginData(partitionsPluginData)// + .addPluginDependency(PersonPropertiesPluginId.PLUGIN_ID)// + .getPartitionsPlugin(); + } + /* end */ + + private Plugin getPeoplePlugin() { + PeoplePluginData peoplePluginData = PeoplePluginData.builder().build(); + return PeoplePlugin.getPeoplePlugin(peoplePluginData); + } + + private Plugin getStochasticPlugin() { + + WellState mainRNGState = WellState.builder().setSeed(346456567565677L).build(); + + StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder()// + .setMainRNGState(mainRNGState)// + .build(); + + return StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData); + } + + /* + * start code_ref=partitions_plugin_person_properties|code_cap=The four person + * properties are defined. + */ + private Plugin getPersonPropertiesPlugin() { + Builder builder = PersonPropertiesPluginData.builder(); + + PropertyDefinition propertyDefinition = PropertyDefinition.builder()// + .setPropertyValueMutability(false).setType(Integer.class).build(); + builder.definePersonProperty(PersonProperty.AGE, propertyDefinition, 0.0, false); + + propertyDefinition = PropertyDefinition.builder()// + .setPropertyValueMutability(true).setType(Boolean.class)// + .setDefaultValue(false).build(); + builder.definePersonProperty(PersonProperty.WAITING_FOR_NEXT_DOSE, propertyDefinition, 0.0, false); + + propertyDefinition = PropertyDefinition.builder()// + .setPropertyValueMutability(true)// + .setType(DiseaseState.class).setDefaultValue(DiseaseState.SUSCEPTIBLE).build(); + builder.definePersonProperty(PersonProperty.DISEASE_STATE, propertyDefinition, 0.0, false); + + propertyDefinition = PropertyDefinition.builder()// + .setPropertyValueMutability(true)// + .setType(Integer.class)// + .setDefaultValue(0)// + .build(); + builder.definePersonProperty(PersonProperty.VACCINATION_COUNT, propertyDefinition, 0.0, false); + + PersonPropertiesPluginData personPropertiesPluginData = builder.build(); + return PersonPropertiesPlugin.builder()// + .setPersonPropertiesPluginData(personPropertiesPluginData)// + .getPersonPropertyPlugin(); + } + /* end */ + + private Plugin getRegionsPlugin() { + RegionsPluginData.Builder builder = RegionsPluginData.builder(); + for (Region region : Region.values()) { + builder.addRegion(region); + } + RegionsPluginData regionsPluginData = builder.build(); + return RegionsPlugin.builder().setRegionsPluginData(regionsPluginData).getRegionsPlugin(); + } + + /* + * start code_ref=partitions_plugin_global_properties|code_cap= The global + * properties are all fixed values. + */ + private Plugin getGlobalPropertiesPlugin() { + GlobalPropertiesPluginData.Builder builder = GlobalPropertiesPluginData.builder(); + PropertyDefinition propertyDefinition = PropertyDefinition.builder().setPropertyValueMutability(false) + .setDefaultValue(10_000).setType(Integer.class).build(); + builder.defineGlobalProperty(GlobalProperty.POPULATION_SIZE, propertyDefinition, 0); + + propertyDefinition = PropertyDefinition.builder().setPropertyValueMutability(false).setDefaultValue(10) + .setType(Integer.class).build(); + builder.defineGlobalProperty(GlobalProperty.INITIAL_INFECTION_COUNT, propertyDefinition, 0); + + propertyDefinition = PropertyDefinition.builder().setPropertyValueMutability(false) + .setDefaultValue(VaccinatorType.PARTITION).setType(VaccinatorType.class).build(); + builder.defineGlobalProperty(GlobalProperty.VACCINATOR_TYPE, propertyDefinition, 0); + + propertyDefinition = PropertyDefinition.builder().setPropertyValueMutability(false).setDefaultValue(3.0) + .setType(Double.class).build(); + builder.defineGlobalProperty(GlobalProperty.MINIMUM_INFECTIOUS_PERIOD, propertyDefinition, 0); + + propertyDefinition = PropertyDefinition.builder().setPropertyValueMutability(false).setDefaultValue(12.0) + .setType(Double.class).build(); + builder.defineGlobalProperty(GlobalProperty.MAXIMUM_INFECTIOUS_PERIOD, propertyDefinition, 0); + + propertyDefinition = PropertyDefinition.builder().setPropertyValueMutability(false).setDefaultValue(2.0) + .setType(Double.class).build(); + builder.defineGlobalProperty(GlobalProperty.INFECTIOUS_CONTACT_RATE, propertyDefinition, 0); + + propertyDefinition = PropertyDefinition.builder().setPropertyValueMutability(false).setDefaultValue(0.15) + .setType(Double.class).build(); + builder.defineGlobalProperty(GlobalProperty.TRANSMISSION_PROBABILTY, propertyDefinition, 0); + + propertyDefinition = PropertyDefinition.builder().setPropertyValueMutability(false).setDefaultValue(100) + .setType(Integer.class).build(); + builder.defineGlobalProperty(GlobalProperty.VACCINATIONS_PER_DAY, propertyDefinition, 0); + + propertyDefinition = PropertyDefinition.builder().setPropertyValueMutability(false).setDefaultValue(30.0) + .setType(Double.class).build(); + builder.defineGlobalProperty(GlobalProperty.INTER_VACCINATION_DELAY_TIME, propertyDefinition, 0); + + GlobalPropertiesPluginData globalPropertiesPluginData = builder.build(); + return GlobalPropertiesPlugin.builder().setGlobalPropertiesPluginData(globalPropertiesPluginData) + .getGlobalPropertiesPlugin(); + } + + /* end */ + private NIOReportItemHandler getNIOReportItemHandler() { + return NIOReportItemHandler.builder()// + .addReport(ModelReportLabel.DISEASE_STATE_REPORT, outputDirectory.resolve("disease_state_report.xls"))// + .build(); + } + + /* + * start code_ref=partitions_plugin_example_20_execute|code_cap=The various + * plugins are gathered from their initial data and the experiment is executed. + */ + private void execute() { + Experiment.builder()// + .addPlugin(getGlobalPropertiesPlugin())// + .addPlugin(getPersonPropertiesPlugin())// + .addPlugin(getRegionsPlugin())// + .addPlugin(getStochasticPlugin())// + .addPlugin(getPeoplePlugin())// + .addPlugin(getPartitionsPlugin())// + .addPlugin(ModelPlugin.getModelPlugin())// + .addExperimentContextConsumer(getNIOReportItemHandler())// + .build()// + .execute(); + } + /* end */ + +} diff --git a/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java b/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java new file mode 100644 index 000000000..4dda71f2d --- /dev/null +++ b/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPlugin.java @@ -0,0 +1,26 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors.ContactManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors.PopulationLoader; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors.VaccinatorManager; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.reports.PersonStatusReport; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.ModelReportLabel; +import gov.hhs.aspr.ms.gcm.nucleus.Plugin; + +public final class ModelPlugin { + + public static Plugin getModelPlugin() { + + return Plugin.builder()// + .setPluginId(ModelPluginId.PLUGIN_ID)// + .setInitializer((c) -> { + c.addActor(new PopulationLoader()::init); + c.addActor(new ContactManager()::init); + c.addActor(new VaccinatorManager()::init); + c.addReport(new PersonStatusReport(ModelReportLabel.DISEASE_STATE_REPORT)::init); + }).build(); + } + + private ModelPlugin() { + } +} diff --git a/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java b/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java new file mode 100644 index 000000000..f1cdfd7df --- /dev/null +++ b/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/ModelPluginId.java @@ -0,0 +1,10 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model; + +import gov.hhs.aspr.ms.gcm.nucleus.PluginId; +import gov.hhs.aspr.ms.gcm.nucleus.SimplePluginId; + +public final class ModelPluginId { + public final static PluginId PLUGIN_ID = new SimplePluginId("model plugin"); + private ModelPluginId() { + } +} diff --git a/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/ContactManager.java b/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/ContactManager.java new file mode 100644 index 000000000..ab6f73187 --- /dev/null +++ b/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/ContactManager.java @@ -0,0 +1,179 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors; + +import java.util.List; +import java.util.Optional; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.DiseaseState; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.GlobalProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.PersonProperty; +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.partitions.datamanagers.PartitionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.Partition; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionSampler; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.Well; + +public class ContactManager { + + private double minimumInfectiousPeriod; + private double maximumInfectiousPeriod; + private double infectiousContactRate; + private int infectionCount; + private double transmissionProbabilty; + + private final Object partitionKey = new Object(); + + private GlobalPropertiesDataManager globalPropertiesDataManager; + private PersonPropertiesDataManager personPropertiesDataManager; + private PartitionsDataManager partitionsDataManager; + private PeopleDataManager peopleDataManager; + + private ActorContext actorContext; + private Well randomGenerator; + + private void loadGlobalProperties() { + minimumInfectiousPeriod = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.MINIMUM_INFECTIOUS_PERIOD); + + maximumInfectiousPeriod = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.MAXIMUM_INFECTIOUS_PERIOD); + + if (minimumInfectiousPeriod > maximumInfectiousPeriod) { + throw new RuntimeException("Minimum infectious period exceeds maximum infectious period"); + } + + infectiousContactRate = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.INFECTIOUS_CONTACT_RATE); + + if (infectiousContactRate < 0) { + throw new RuntimeException("infectious contact rate is negative"); + } + + transmissionProbabilty = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.TRANSMISSION_PROBABILTY); + if (transmissionProbabilty < 0 || transmissionProbabilty > 1) { + throw new RuntimeException("transmission probability out of bounds[0,1]"); + } + infectionCount = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.INITIAL_INFECTION_COUNT); + } + + private void initializeInfections() { + List people = peopleDataManager.getPeople(); + + if (infectionCount > people.size()) { + throw new RuntimeException("Initial infectious count exceeds population size"); + } + + for (int i = 0; i < infectionCount; i++) { + PersonId personId = people.get(i); + infectPerson(personId); + } + } + + /*start code_ref=partitions_plugin_contact_manager_infect_person|code_cap= Each time a person is infected, the contact manager schedules several follow-on infectious contacts.*/ + private void infectPerson(PersonId personId) { + personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.DISEASE_STATE, + DiseaseState.INFECTIOUS); + + double infectiousPeriod = randomGenerator.nextDouble() * (maximumInfectiousPeriod - minimumInfectiousPeriod) + + minimumInfectiousPeriod; + + double lastContactTime = actorContext.getTime() + infectiousPeriod; + + double infectionTime = actorContext.getTime(); + while (true) { + double contactDelay = (1.0 / infectiousContactRate); + contactDelay *= (1 + randomGenerator.nextDouble() / 5 - 0.1); + infectionTime += contactDelay; + + if (infectionTime < lastContactTime) { + actorContext.addPlan((c2) -> { + processInfectiousContact(personId); + }, infectionTime); + } else { + break; + } + } + actorContext.addPlan((c2) -> { + endInfectiousness(personId); + }, lastContactTime); + + } + /* end */ + + private void endInfectiousness(PersonId personId) { + personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.DISEASE_STATE, + DiseaseState.RECOVERED); + } + + + + /*start code_ref=partitions_plugin_contact_manager_init|code_cap= The Contact Manager uses a simple partition that contains the entire population and uses it to randomly select infectious contacts.*/ + public void init(ActorContext actorContext) { + this.actorContext = actorContext; + partitionsDataManager = actorContext.getDataManager(PartitionsDataManager.class); + personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class); + peopleDataManager = actorContext.getDataManager(PeopleDataManager.class); + globalPropertiesDataManager = actorContext.getDataManager(GlobalPropertiesDataManager.class); + StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class); + randomGenerator = stochasticsDataManager.getRandomGenerator(); + + loadGlobalProperties(); + establishPopulationPartition(); + initializeInfections(); + } + + private void establishPopulationPartition() { + partitionsDataManager.addPartition(Partition.builder().build(), partitionKey); + } + /* end */ + + + /*start code_ref=partitions_plugin_contact_manager_infectious_contact|code_cap= Infectious contacts are subject to mitigation. To be infected, the contacted person must be susceptible and may having varying degrees of protection from previous vaccinations.*/ + private void processInfectiousContact(PersonId personId) { + + PartitionSampler partitionSampler = PartitionSampler.builder().setExcludedPerson(personId).build(); + Optional optionalPersonId = partitionsDataManager.samplePartition(partitionKey, partitionSampler); + if (optionalPersonId.isPresent()) { + + PersonId contactedPersonId = optionalPersonId.get(); + + + DiseaseState diseaseState = personPropertiesDataManager.getPersonPropertyValue(contactedPersonId, + PersonProperty.DISEASE_STATE); + if (diseaseState == DiseaseState.SUSCEPTIBLE) { + + int vaccinationCount = personPropertiesDataManager.getPersonPropertyValue(contactedPersonId, + PersonProperty.VACCINATION_COUNT); + double mitigatedTransmissionProbability; + + switch (vaccinationCount) { + case 0: + mitigatedTransmissionProbability = 1; + break; + case 1: + mitigatedTransmissionProbability = 0.5; + break; + case 2: + mitigatedTransmissionProbability = 0.2; + break; + default: + mitigatedTransmissionProbability = 0; + break; + } + + mitigatedTransmissionProbability *= transmissionProbabilty; + + if (randomGenerator.nextDouble() < mitigatedTransmissionProbability) { + infectPerson(contactedPersonId); + } + } + } + + } + /* end */ +} diff --git a/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/EventVaccinator.java b/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/EventVaccinator.java new file mode 100644 index 000000000..552a2275e --- /dev/null +++ b/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/EventVaccinator.java @@ -0,0 +1,288 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.AgeGroup; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.DiseaseState; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.GlobalProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.PersonProperty; +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.nucleus.EventFilter; +import gov.hhs.aspr.ms.gcm.nucleus.Plan; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.events.PersonPropertyUpdateEvent; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.Well; +import util.wrappers.MultiKey; + +public class EventVaccinator { + + private PeopleDataManager peopleDataManager; + private PersonPropertiesDataManager personPropertiesDataManager; + private GlobalPropertiesDataManager globalPropertiesDataManager; + private double interVaccinationTime; + private ActorContext actorContext; + private Well randomGenerator; + private double personInterVaccinationDelay; + private Object planId = new Object(); + private Map weights = new LinkedHashMap<>(); + private Map> candidates = new LinkedHashMap<>(); + private Map groupMap = new LinkedHashMap<>(); + + /* + * start code_ref=partitions_plugin_event_init|code_cap=The event-based + * vaccinator improves on the inspection-based vaccinator by maintaining the + * eligible sub-populations. + */ + public void init(ActorContext actorContext) { + this.actorContext = actorContext; + + StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class); + randomGenerator = stochasticsDataManager.getRandomGenerator(); + personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class); + peopleDataManager = actorContext.getDataManager(PeopleDataManager.class); + globalPropertiesDataManager = actorContext.getDataManager(GlobalPropertiesDataManager.class); + + establishWorkingVaribles(); + subscribeToPersonPropertyUpdateEvents(); + intializeCandidatesAndWeights(); + planNextVaccination(); + } + + /* end */ + private void establishWorkingVaribles() { + int vaccinationsPerDay = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.VACCINATIONS_PER_DAY); + interVaccinationTime = 1.0 / vaccinationsPerDay; + + personInterVaccinationDelay = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.INTER_VACCINATION_DELAY_TIME); + } + + private void subscribeToPersonPropertyUpdateEvents() { + EventFilter diseaseEventFilter = personPropertiesDataManager + .getEventFilterForPersonPropertyUpdateEvent(); + actorContext.subscribe(diseaseEventFilter, this::handlePersonPropertyChange); + } + + /* + * start code_ref=partitions_plugin_event_handle_property_update|code_cap=The + * event-based vaccinator processes each person property update event by first + * removing the person from the sub-populations and then adding them back in if + * required. + */ + private void handlePersonPropertyChange(ActorContext actorContext, + PersonPropertyUpdateEvent personPropertyUpdateEvent) { + + PersonId personId = personPropertyUpdateEvent.personId(); + + // remove the person if they are being tracked + MultiKey multiKey = groupMap.remove(personId); + List list = candidates.get(multiKey); + if (list != null) { + list.remove(personId); + } + + DiseaseState diseaseState = personPropertiesDataManager.getPersonPropertyValue(personId, + PersonProperty.DISEASE_STATE); + + // the person must be susceptible + if (diseaseState != DiseaseState.SUSCEPTIBLE) { + return; + } + + Integer vaccinationCount = personPropertiesDataManager.getPersonPropertyValue(personId, + PersonProperty.VACCINATION_COUNT); + + if (vaccinationCount > 2) { + return; + } + + int age = personPropertiesDataManager.getPersonPropertyValue(personId, PersonProperty.AGE); + AgeGroup ageGroup = AgeGroup.getAgeGroup(age); + if (ageGroup == AgeGroup.CHILD) { + return; + } + boolean waitingForNextDose = personPropertiesDataManager.getPersonPropertyValue(personId, + PersonProperty.WAITING_FOR_NEXT_DOSE); + if (waitingForNextDose) { + return; + } + + multiKey = new MultiKey(ageGroup, vaccinationCount); + + list = candidates.get(multiKey); + if (list != null) { + list.add(personId); + groupMap.put(personId, multiKey); + } + + } + /* end */ + + private double getWeight(AgeGroup ageGroup, int vaccineCount) { + + double result = 1; + + switch (ageGroup) { + case ADULT_18_44: + result += 0; + break; + case ADULT_45_64: + result += 3; + break; + case CHILD: + result += 0; + break; + case SENIOR: + result += 10; + break; + default: + break; + } + + switch (vaccineCount) { + case 0: + result += 4; + break; + case 1: + result += 3; + break; + case 2: + result += 2; + break; + default: + result += 0; + break; + } + + return result; + } + + private void intializeCandidatesAndWeights() { + + List people = peopleDataManager.getPeople(); + + List eligibleAgeGroups = new ArrayList<>(); + eligibleAgeGroups.add(AgeGroup.ADULT_18_44); + eligibleAgeGroups.add(AgeGroup.ADULT_45_64); + eligibleAgeGroups.add(AgeGroup.SENIOR); + + List eligibleVaccineCounts = new ArrayList<>(); + eligibleVaccineCounts.add(0); + eligibleVaccineCounts.add(1); + eligibleVaccineCounts.add(2); + + for (AgeGroup ageGroup : eligibleAgeGroups) { + for (Integer vaccineCount : eligibleVaccineCounts) { + MultiKey multiKey = new MultiKey(ageGroup, vaccineCount); + double weight = getWeight(ageGroup, vaccineCount); + weights.put(multiKey, weight); + candidates.put(multiKey, new ArrayList<>()); + } + } + + for (PersonId personId : people) { + int age = personPropertiesDataManager.getPersonPropertyValue(personId, PersonProperty.AGE); + if (age < 18) { + continue; + } + DiseaseState diseaseState = personPropertiesDataManager.getPersonPropertyValue(personId, + PersonProperty.DISEASE_STATE); + if (diseaseState != DiseaseState.SUSCEPTIBLE) { + continue; + } + int vaccinationCount = personPropertiesDataManager.getPersonPropertyValue(personId, + PersonProperty.VACCINATION_COUNT); + if (vaccinationCount > 2) { + continue; + } + AgeGroup ageGroup = AgeGroup.getAgeGroup(age); + MultiKey multiKey = new MultiKey(ageGroup, vaccinationCount); + groupMap.put(personId, multiKey); + candidates.get(multiKey).add(personId); + } + } + + /* + * start code_ref=partitions_plugin_event_vaccinate|code_cap= The event-based + * vaccinator selects from maintained sub-populations. + */ + private void planNextVaccination() { + Plan plan = Plan.builder(ActorContext.class)// + .setTime(interVaccinationTime + actorContext.getTime())// + .setKey(planId).setCallbackConsumer(this::vaccinatePerson).build(); + + actorContext.addPlan(plan); + } + + private void vaccinatePerson(ActorContext actorContext) { + + Map extendedWeights = new LinkedHashMap<>(); + + double sumOfExtendedWeights = 0; + for (MultiKey multiKey : weights.keySet()) { + Double weight = weights.get(multiKey); + int candidateCount = candidates.get(multiKey).size(); + Double extenedWeight = weight * candidateCount; + extendedWeights.put(multiKey, extenedWeight); + sumOfExtendedWeights += extenedWeight; + } + + PersonId selectedCandidate = null; + + double selectedWeight = sumOfExtendedWeights * randomGenerator.nextDouble(); + for (MultiKey multiKey : extendedWeights.keySet()) { + Double extendedWeight = extendedWeights.get(multiKey); + selectedWeight -= extendedWeight; + if (selectedWeight <= 0) { + List seletedCandidates = candidates.get(multiKey); + if (!seletedCandidates.isEmpty()) { + int index = randomGenerator.nextInt(seletedCandidates.size()); + selectedCandidate = seletedCandidates.get(index); + } + break; + } + } + + if (selectedCandidate != null) { + + int vaccinationCount = personPropertiesDataManager.getPersonPropertyValue(selectedCandidate, + PersonProperty.VACCINATION_COUNT); + vaccinationCount++; + personPropertiesDataManager.setPersonPropertyValue(selectedCandidate, PersonProperty.VACCINATION_COUNT, + vaccinationCount); + if (vaccinationCount < 3) { + personPropertiesDataManager.setPersonPropertyValue(selectedCandidate, + PersonProperty.WAITING_FOR_NEXT_DOSE, true); + planWaitTermination(selectedCandidate); + } + planNextVaccination(); + } + + } + /* end */ + + private void planWaitTermination(PersonId personId) { + actorContext.addPlan((c) -> this.endWaitTime(personId), personInterVaccinationDelay + actorContext.getTime()); + } + + /* + * start code_ref=partitions_plugin_event_end_wait|code_cap= The + * event-vaccinator can restart the vaccination process when a person becomes + * eligible after the post-vacination waiting period is over. + */ + private void endWaitTime(PersonId personId) { + personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.WAITING_FOR_NEXT_DOSE, false); + if (actorContext.getPlan(planId).isEmpty()) { + vaccinatePerson(actorContext); + } + } + /* end */ +} diff --git a/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/InspectionVaccinator.java b/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/InspectionVaccinator.java new file mode 100644 index 000000000..73c777bdb --- /dev/null +++ b/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/InspectionVaccinator.java @@ -0,0 +1,214 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.AgeGroup; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.DiseaseState; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.GlobalProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.PersonProperty; +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.Well; +import util.wrappers.MultiKey; + +public class InspectionVaccinator { + + private PeopleDataManager peopleDataManager; + private PersonPropertiesDataManager personPropertiesDataManager; + private GlobalPropertiesDataManager globalPropertiesDataManager; + private double interVaccinationTime; + private ActorContext actorContext; + private Well randomGenerator; + private double personInterVaccinationDelay; + private boolean potentialEligiblePeopleExist; + + /*start code_ref=partitions_plugin_inspection_init|code_cap=The inspection-based vaccinator establishes its working variables and begins planning the next vaccination.*/ + public void init(ActorContext actorContext) { + this.actorContext = actorContext; + + StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class); + randomGenerator = stochasticsDataManager.getRandomGenerator(); + personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class); + peopleDataManager = actorContext.getDataManager(PeopleDataManager.class); + globalPropertiesDataManager = actorContext.getDataManager(GlobalPropertiesDataManager.class); + + establishWorkingVaribles(); + planNextVaccination(); + } + /* end */ + + private void establishWorkingVaribles() { + int vaccinationsPerDay = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.VACCINATIONS_PER_DAY); + personInterVaccinationDelay = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.INTER_VACCINATION_DELAY_TIME); + interVaccinationTime = 1.0 / vaccinationsPerDay; + } + + /*start code_ref=partitions_plugin_inspection_weighing|code_cap=The inspection-based vaccinator assigns a probability weight to each person based on their age and number of vaccine doses administered.*/ + private double getWeight(AgeGroup ageGroup, int vaccineCount) { + + double result = 1; + + switch (ageGroup) { + case ADULT_18_44: + result += 0; + break; + case ADULT_45_64: + result += 3; + break; + case CHILD: + result += 0; + break; + case SENIOR: + result += 10; + break; + default: + break; + } + + switch (vaccineCount) { + case 0: + result += 4; + break; + case 1: + result += 3; + break; + case 2: + result += 2; + break; + default: + result += 0; + break; + } + + return result; + } + /* end */ + + private void planWaitTermination(PersonId personId) { + actorContext.addPlan((c) -> this.endWaitTime(personId), personInterVaccinationDelay + actorContext.getTime()); + } + + private void endWaitTime(PersonId personId) { + personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.WAITING_FOR_NEXT_DOSE, false); + } + + + /*start code_ref=partitions_plugin_inspection_vaccination|code_cap=The inspection-based vaccinator vaccinates 100 people per day. Each vaccination attempt considers every person in the simulation.*/ + private void planNextVaccination() { + actorContext.addPlan(this::vaccinatePerson, interVaccinationTime + actorContext.getTime()); + } + + + private void vaccinatePerson(ActorContext actorContext) { + List people = peopleDataManager.getPeople(); + Map> candidates = new LinkedHashMap<>(); + Map weights = new LinkedHashMap<>(); + + List eligibleAgeGroups = new ArrayList<>(); + eligibleAgeGroups.add(AgeGroup.ADULT_18_44); + eligibleAgeGroups.add(AgeGroup.ADULT_45_64); + eligibleAgeGroups.add(AgeGroup.SENIOR); + + List eligibleVaccineCounts = new ArrayList<>(); + eligibleVaccineCounts.add(0); + eligibleVaccineCounts.add(1); + eligibleVaccineCounts.add(2); + + for (AgeGroup ageGroup : eligibleAgeGroups) { + for (Integer vaccineCount : eligibleVaccineCounts) { + MultiKey multiKey = new MultiKey(ageGroup, vaccineCount); + double weight = getWeight(ageGroup, vaccineCount); + weights.put(multiKey, weight); + candidates.put(multiKey, new ArrayList<>()); + } + } + + potentialEligiblePeopleExist = false; + + for (PersonId personId : people) { + int age = personPropertiesDataManager.getPersonPropertyValue(personId, PersonProperty.AGE); + if (age < 18) { + continue; + } + DiseaseState diseaseState = personPropertiesDataManager.getPersonPropertyValue(personId, + PersonProperty.DISEASE_STATE); + if (diseaseState != DiseaseState.SUSCEPTIBLE) { + continue; + } + int vaccinationCount = personPropertiesDataManager.getPersonPropertyValue(personId, + PersonProperty.VACCINATION_COUNT); + if (vaccinationCount > 2) { + continue; + } + + boolean waitingFromPreviousVaccination = personPropertiesDataManager.getPersonPropertyValue(personId, + PersonProperty.WAITING_FOR_NEXT_DOSE); + + if (waitingFromPreviousVaccination) { + potentialEligiblePeopleExist = true; + continue; + } + + AgeGroup ageGroup = AgeGroup.getAgeGroup(age); + MultiKey multiKey = new MultiKey(ageGroup, vaccinationCount); + candidates.get(multiKey).add(personId); + } + + Map extendedWeights = new LinkedHashMap<>(); + + double sumOfExtendedWeights = 0; + for (MultiKey multiKey : weights.keySet()) { + Double weight = weights.get(multiKey); + int candidateCount = candidates.get(multiKey).size(); + Double extenedWeight = weight * candidateCount; + extendedWeights.put(multiKey, extenedWeight); + sumOfExtendedWeights += extenedWeight; + } + + PersonId selectedCandidate = null; + + double selectedWeight = sumOfExtendedWeights * randomGenerator.nextDouble(); + for (MultiKey multiKey : extendedWeights.keySet()) { + Double extendedWeight = extendedWeights.get(multiKey); + selectedWeight -= extendedWeight; + if (selectedWeight <= 0) { + List seletedCandidates = candidates.get(multiKey); + if (!seletedCandidates.isEmpty()) { + int index = randomGenerator.nextInt(seletedCandidates.size()); + selectedCandidate = seletedCandidates.get(index); + } + break; + } + } + + if (selectedCandidate != null) { + + int vaccinationCount = personPropertiesDataManager.getPersonPropertyValue(selectedCandidate, + PersonProperty.VACCINATION_COUNT); + vaccinationCount++; + personPropertiesDataManager.setPersonPropertyValue(selectedCandidate, PersonProperty.VACCINATION_COUNT, + vaccinationCount); + if (vaccinationCount < 3) { + personPropertiesDataManager.setPersonPropertyValue(selectedCandidate, + PersonProperty.WAITING_FOR_NEXT_DOSE, true); + planWaitTermination(selectedCandidate); + } + planNextVaccination(); + } else { + if (potentialEligiblePeopleExist) { + planNextVaccination(); + } + } + } + /* end */ + +} diff --git a/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/PartitionVaccinator.java b/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/PartitionVaccinator.java new file mode 100644 index 000000000..35e82ac77 --- /dev/null +++ b/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/PartitionVaccinator.java @@ -0,0 +1,190 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors; + +import java.util.Optional; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.AgeGroup; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.DiseaseState; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.GlobalProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.PersonProperty; +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.partitions.datamanagers.PartitionsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.Equality; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.LabelSet; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.Labeler; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.Partition; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionSampler; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.PartitionsContext; +import gov.hhs.aspr.ms.gcm.plugins.partitions.support.filters.Filter; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.FunctionalPersonPropertyLabeler; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyFilter; + +public class PartitionVaccinator { + + private Object currentlyEligibleKey = new Object(); + private Object potentiallyEligibleKey = new Object(); + private PartitionsDataManager partitionsDataManager; + private PersonPropertiesDataManager personPropertiesDataManager; + private GlobalPropertiesDataManager globalPropertiesDataManager; + private double vaccinatorDelay; + private double personInterVaccinationDelay; + private ActorContext actorContext; + + /* + * start code_ref=partitions_plugin_partition_init|code_cap= The + * partition-vaccinator manages the eligible population via partitions. + */ + public void init(ActorContext actorContext) { + this.actorContext = actorContext; + personPropertiesDataManager = actorContext.getDataManager(PersonPropertiesDataManager.class); + partitionsDataManager = actorContext.getDataManager(PartitionsDataManager.class); + globalPropertiesDataManager = actorContext.getDataManager(GlobalPropertiesDataManager.class); + establishWorkingVaribles(); + createPartitions(); + planNextVaccination(); + } + /* end */ + + /* + * start code_ref=partitions_plugin_partition_create_partitions|code_cap= The + * partition-vaccinator creates two partitions to help with person selection and + * termination of vaccinations. + */ + private void createPartitions() { + + PersonPropertyFilter ageFilter = new PersonPropertyFilter(PersonProperty.AGE, Equality.GREATER_THAN_EQUAL, 18); + + PersonPropertyFilter diseaseFilter = new PersonPropertyFilter(PersonProperty.DISEASE_STATE, Equality.EQUAL, + DiseaseState.SUSCEPTIBLE); + + PersonPropertyFilter vaccineFilter = new PersonPropertyFilter(PersonProperty.VACCINATION_COUNT, + Equality.LESS_THAN, 3); + + PersonPropertyFilter waitFilter = new PersonPropertyFilter(PersonProperty.WAITING_FOR_NEXT_DOSE, Equality.EQUAL, + false); + + Filter filter = ageFilter.and(diseaseFilter).and(vaccineFilter).and(waitFilter); + + Labeler ageLabeler = new FunctionalPersonPropertyLabeler(PersonProperty.AGE, + (value) -> AgeGroup.getAgeGroup((Integer) value)); + + Labeler vaccineCountLabeler = new FunctionalPersonPropertyLabeler(PersonProperty.VACCINATION_COUNT, + (value) -> value); + + Partition partition = Partition.builder()// + .setFilter(filter)// + .addLabeler(ageLabeler)// + .addLabeler(vaccineCountLabeler)// + .build(); + + partitionsDataManager.addPartition(partition, currentlyEligibleKey); + + filter = ageFilter.and(diseaseFilter).and(vaccineFilter); + partition = Partition.builder()// + .setFilter(filter)// + .build(); + + partitionsDataManager.addPartition(partition, potentiallyEligibleKey); + } + /* end */ + + private void establishWorkingVaribles() { + int vaccinationsPerDay = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.VACCINATIONS_PER_DAY); + + personInterVaccinationDelay = globalPropertiesDataManager + .getGlobalPropertyValue(GlobalProperty.INTER_VACCINATION_DELAY_TIME); + vaccinatorDelay = 1.0 / vaccinationsPerDay; + } + + private void planWaitTermination(PersonId personId) { + actorContext.addPlan((c) -> this.endWaitTime(personId), personInterVaccinationDelay + actorContext.getTime()); + } + + private void endWaitTime(PersonId personId) { + personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.WAITING_FOR_NEXT_DOSE, false); + } + + private double getWeight(PartitionsContext partitionsContext, LabelSet labelSet) { + + AgeGroup ageGroup = (AgeGroup) labelSet.getLabel(PersonProperty.AGE).get(); + + int vaccineCount = (Integer) labelSet.getLabel(PersonProperty.VACCINATION_COUNT).get(); + + double result = 1; + + switch (ageGroup) { + case ADULT_18_44: + result += 0; + break; + case ADULT_45_64: + result += 3; + break; + case CHILD: + result += 0; + break; + case SENIOR: + result += 10; + break; + default: + break; + } + + switch (vaccineCount) { + case 0: + result += 4; + break; + case 1: + result += 3; + break; + case 2: + result += 2; + break; + default: + result += 0; + break; + } + + return result; + } + + /* + * start code_ref=partitions_plugin_partition_vaccinate|code_cap= The + * partition-vaccinator schedules and executes vaccinations using partitions. + */ + private void planNextVaccination() { + if (partitionsDataManager.getPersonCount(potentiallyEligibleKey) == 0) { + return; + } + actorContext.addPlan(this::vaccinatePerson, vaccinatorDelay + actorContext.getTime()); + } + + private void vaccinatePerson(ActorContext actorContext) { + + PartitionSampler partitionSampler = PartitionSampler.builder()// + .setLabelSetWeightingFunction(this::getWeight)// + .build(); + + Optional optionalPersonId = partitionsDataManager.samplePartition(currentlyEligibleKey, + partitionSampler); + if (optionalPersonId.isPresent()) { + PersonId personId = optionalPersonId.get(); + int vaccinationCount = personPropertiesDataManager.getPersonPropertyValue(personId, + PersonProperty.VACCINATION_COUNT); + vaccinationCount++; + personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.VACCINATION_COUNT, + vaccinationCount); + if (vaccinationCount < 3) { + personPropertiesDataManager.setPersonPropertyValue(personId, PersonProperty.WAITING_FOR_NEXT_DOSE, + true); + planWaitTermination(personId); + } + + } + planNextVaccination(); + } + /* end */ + +} diff --git a/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/PopulationLoader.java b/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/PopulationLoader.java new file mode 100644 index 000000000..189ea7b5a --- /dev/null +++ b/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/PopulationLoader.java @@ -0,0 +1,36 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.AgeGroup; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.GlobalProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.PersonProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.Region; +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonConstructionData; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyValueInitialization; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.datamanagers.StochasticsDataManager; +import gov.hhs.aspr.ms.gcm.plugins.stochastics.support.Well; + +/*start code_ref=partitions_plugin_population_loader|code_cap=People are added to the simulation with region and age assignments.*/ +public class PopulationLoader { + public void init(ActorContext actorContext) { + StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class); + Well randomGenerator = stochasticsDataManager.getRandomGenerator(); + PeopleDataManager peopleDataManager = actorContext.getDataManager(PeopleDataManager.class); + GlobalPropertiesDataManager globalPropertiesDataManager = actorContext + .getDataManager(GlobalPropertiesDataManager.class); + Integer populationSize = globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.POPULATION_SIZE); + for (int i = 0; i < populationSize; i++) { + Region region = Region.getRandomRegion(randomGenerator); + int age = AgeGroup.getRandomAge(randomGenerator); + PersonPropertyValueInitialization personPropertyValueInitialization = new PersonPropertyValueInitialization(PersonProperty.AGE,age); + PersonConstructionData personConstructionData = PersonConstructionData.builder()// + .add(region)// + .add(personPropertyValueInitialization) + .build();// + peopleDataManager.addPerson(personConstructionData); + } + } +} +/* end */ diff --git a/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/VaccinatorManager.java b/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/VaccinatorManager.java new file mode 100644 index 000000000..1edd162ec --- /dev/null +++ b/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/actors/VaccinatorManager.java @@ -0,0 +1,29 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.actors; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.GlobalProperty; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.VaccinatorType; +import gov.hhs.aspr.ms.gcm.nucleus.ActorContext; +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.datamanagers.GlobalPropertiesDataManager; + +/*start code_ref=partitions_plugin_vaccine_manager|code_cap=The vaccine manager creates one of three vaccinator actors based on the global property,VACCINATOR_TYPE.*/ +public class VaccinatorManager { + public void init(ActorContext actorContext) { + GlobalPropertiesDataManager globalPropertiesDataManager = actorContext.getDataManager(GlobalPropertiesDataManager.class); + VaccinatorType vaccinatorType = + globalPropertiesDataManager.getGlobalPropertyValue(GlobalProperty.VACCINATOR_TYPE); + switch (vaccinatorType) { + case PARTITION: + actorContext.addActor(new PartitionVaccinator()::init); + break; + case EVENT: + actorContext.addActor(new EventVaccinator()::init); + break; + case INSPECTION: + actorContext.addActor(new InspectionVaccinator()::init); + break; + default: + throw new RuntimeException("unhandled case "+vaccinatorType); + } + } +} +/* end */ diff --git a/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/reports/PersonStatusReport.java b/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/reports/PersonStatusReport.java new file mode 100644 index 000000000..44aab9cec --- /dev/null +++ b/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/reports/PersonStatusReport.java @@ -0,0 +1,85 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.reports; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.AgeGroup; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.DiseaseState; +import gov.hhs.aspr.ms.gcm.lessons.plugins.model.support.PersonProperty; +import gov.hhs.aspr.ms.gcm.nucleus.ReportContext; +import gov.hhs.aspr.ms.gcm.plugins.people.datamanagers.PeopleDataManager; +import gov.hhs.aspr.ms.gcm.plugins.people.support.PersonId; +import gov.hhs.aspr.ms.gcm.plugins.personproperties.datamanagers.PersonPropertiesDataManager; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportHeader; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportItem; +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; +import util.wrappers.MultiKey; +import util.wrappers.MutableInteger; + +public class PersonStatusReport { + private final ReportLabel reportLabel; + private ReportHeader reportHeader; + + public PersonStatusReport(ReportLabel reportLabel) { + this.reportLabel = reportLabel; + } + + private PeopleDataManager peopleDataManager; + private PersonPropertiesDataManager personPropertiesDataManager; + + public void init(ReportContext reportContext) { + personPropertiesDataManager = reportContext.getDataManager(PersonPropertiesDataManager.class); + peopleDataManager = reportContext.getDataManager(PeopleDataManager.class); + reportContext.subscribeToSimulationClose(this::reportState); + + final ReportHeader.Builder reportHeaderBuilder = ReportHeader.builder(); + reportHeaderBuilder.add("age_group"); + reportHeaderBuilder.add("disease_state"); + reportHeaderBuilder.add("vaccinations"); + reportHeaderBuilder.add("people"); + + reportHeader = reportHeaderBuilder.build(); + + } + + private void reportState(ReportContext reportContext) { + List people = peopleDataManager.getPeople(); + + Map map = new LinkedHashMap<>(); + + for (PersonId personId : people) { + int age = personPropertiesDataManager.getPersonPropertyValue(personId, PersonProperty.AGE); + AgeGroup ageGroup = AgeGroup.getAgeGroup(age); + DiseaseState diseaseState = personPropertiesDataManager.getPersonPropertyValue(personId, + PersonProperty.DISEASE_STATE); + int vaccinationCount = personPropertiesDataManager.getPersonPropertyValue(personId, + PersonProperty.VACCINATION_COUNT); + + MultiKey multiKey = new MultiKey(ageGroup, diseaseState, vaccinationCount); + MutableInteger counter = map.get(multiKey); + if (counter == null) { + counter = new MutableInteger(); + map.put(multiKey, counter); + } + counter.increment(); + } + + for (MultiKey multiKey : map.keySet()) { + int personCount = map.get(multiKey).getValue(); + AgeGroup ageGroup = multiKey.getKey(0); + DiseaseState diseaseState = multiKey.getKey(1); + int vaccinationCount = multiKey.getKey(2); + ReportItem reportItem = ReportItem.builder().setReportHeader(reportHeader).setReportLabel(reportLabel) + + .addValue(ageGroup)// + .addValue(diseaseState)// + .addValue(vaccinationCount)// + .addValue(personCount)// + .build();// + + reportContext.releaseOutput(reportItem); + } + + } +} diff --git a/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/AgeGroup.java b/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/AgeGroup.java new file mode 100644 index 000000000..cbed7e0cc --- /dev/null +++ b/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/AgeGroup.java @@ -0,0 +1,43 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.support; + +import org.apache.commons.math3.random.RandomGenerator; + +public enum AgeGroup { + + CHILD, // + ADULT_18_44, // adults 18 to 29 inclusive + ADULT_45_64, // adults 30 to 54 inclusive + SENIOR;// adults 55 and over + + public static AgeGroup getAgeGroup(int age) { + if (age < 18) { + return CHILD; + } + if (age < 45) { + return ADULT_18_44; + } + if (age < 65) { + return ADULT_45_64; + } + return SENIOR; + } + + public static int getRandomAge(RandomGenerator randomGenerator) { + double draw = randomGenerator.nextDouble(); + if (draw < 0.222) { + // Under 18 years 22.2% (2021) + return randomGenerator.nextInt(18); + } + if (draw < 0.581) { + // 18–44 years 35.9% (2021) + return randomGenerator.nextInt(27) + 18; + } + if (draw < 0.833) { + // 45–64 years 25.2% (2021) + return randomGenerator.nextInt(10) + 45; + } + // 65 and over 16.8% (2021) + return randomGenerator.nextInt(21) + 65; + + } +} diff --git a/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/DiseaseState.java b/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/DiseaseState.java new file mode 100644 index 000000000..c84500354 --- /dev/null +++ b/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/DiseaseState.java @@ -0,0 +1,11 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.support; + +public enum DiseaseState { + + SUSCEPTIBLE, + + INFECTIOUS, + + RECOVERED, + +} diff --git a/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/GlobalProperty.java b/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/GlobalProperty.java new file mode 100644 index 000000000..de80d80be --- /dev/null +++ b/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/GlobalProperty.java @@ -0,0 +1,17 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.support; + +import gov.hhs.aspr.ms.gcm.plugins.globalproperties.support.GlobalPropertyId; + +public enum GlobalProperty implements GlobalPropertyId { + + VACCINATOR_TYPE,//the type of vaccinator used by the model--of type VaccinatorType + VACCINATIONS_PER_DAY,//number of vaccinations per day allowed + TRANSMISSION_PROBABILTY,//base probabilty of disease transmission per infectious contact + INFECTIOUS_CONTACT_RATE,//number of potentially infectious contacts per day for an infectious person + MINIMUM_INFECTIOUS_PERIOD,//minimum number of days that a person is infectious + MAXIMUM_INFECTIOUS_PERIOD,//maximum number of days that a person is infectious + INITIAL_INFECTION_COUNT, // the number of people who are infectious at the begining of the simulation + INTER_VACCINATION_DELAY_TIME,//the number of days require between vaccination + POPULATION_SIZE, // the initial size of the population + ; +} diff --git a/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/ModelReportLabel.java b/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/ModelReportLabel.java new file mode 100644 index 000000000..50b62ad59 --- /dev/null +++ b/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/ModelReportLabel.java @@ -0,0 +1,9 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.support; + +import gov.hhs.aspr.ms.gcm.plugins.reports.support.ReportLabel; + +public enum ModelReportLabel implements ReportLabel { + + DISEASE_STATE_REPORT, + ; +} diff --git a/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/PersonProperty.java b/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/PersonProperty.java new file mode 100644 index 000000000..a35b48093 --- /dev/null +++ b/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/PersonProperty.java @@ -0,0 +1,12 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.support; + +import gov.hhs.aspr.ms.gcm.plugins.personproperties.support.PersonPropertyId; + +public enum PersonProperty implements PersonPropertyId { + AGE, // the integer age of a person + WAITING_FOR_NEXT_DOSE,// boolean indicating that the person should not be vaccinated + VACCINATION_COUNT, // boolean vaccination status + DISEASE_STATE, // SUSCEPTIBLE, INFECTIOUS or RECOVERED + ; + +} diff --git a/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/Region.java b/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/Region.java new file mode 100644 index 000000000..9ab5b3950 --- /dev/null +++ b/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/Region.java @@ -0,0 +1,21 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.support; + +import org.apache.commons.math3.random.RandomGenerator; + +import gov.hhs.aspr.ms.gcm.plugins.regions.support.RegionId; + +/** + * Identifier for all regions + * + * + */ + +public enum Region implements RegionId { + + REGION_1, REGION_2, REGION_3; + + public static Region getRandomRegion(final RandomGenerator randomGenerator) { + return Region.values()[randomGenerator.nextInt(Region.values().length)]; + } + +} diff --git a/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/VaccinatorType.java b/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/VaccinatorType.java new file mode 100644 index 000000000..920db0bf8 --- /dev/null +++ b/tutorials/lessons/lesson_20_partitions_plugin/src/main/java/gov/hhs/aspr/ms/gcm/lessons/plugins/model/support/VaccinatorType.java @@ -0,0 +1,5 @@ +package gov.hhs.aspr.ms.gcm.lessons.plugins.model.support; + +public enum VaccinatorType { + PARTITION, EVENT, INSPECTION +} \ No newline at end of file diff --git a/tutorials/lessons/lesson_20_partitions_plugin/src/main/resources/output/partitions_plugin_output.txt b/tutorials/lessons/lesson_20_partitions_plugin/src/main/resources/output/partitions_plugin_output.txt new file mode 100644 index 000000000..994138325 --- /dev/null +++ b/tutorials/lessons/lesson_20_partitions_plugin/src/main/resources/output/partitions_plugin_output.txt @@ -0,0 +1,17 @@ +/* start code_ref=partitions_plugin_output|code_cap=The experiment's single scenario showing expected vaccinations for different age groups.*/ +scenario age_group disease_state vaccinations people +0 CHILD RECOVERED 0 1599 +0 SENIOR RECOVERED 0 377 +0 ADULT_18_44 RECOVERED 0 1745 +0 ADULT_45_64 RECOVERED 0 1018 +0 ADULT_18_44 RECOVERED 1 591 +0 ADULT_18_44 SUSCEPTIBLE 3 1253 +0 SENIOR SUSCEPTIBLE 3 742 +0 ADULT_45_64 SUSCEPTIBLE 3 1026 +0 CHILD SUSCEPTIBLE 0 553 +0 SENIOR RECOVERED 1 484 +0 ADULT_45_64 RECOVERED 1 568 +0 ADULT_45_64 RECOVERED 2 19 +0 ADULT_18_44 RECOVERED 2 10 +0 SENIOR RECOVERED 2 15 +/* end */ \ No newline at end of file